From 2f8d7062cfc9277cf685c147f525d58527b71e16 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Fri, 22 Aug 2025 17:31:38 +0100 Subject: [PATCH 001/782] unchecked cases --- elf-regressions/compressed/c_add.s | 44 +++ elf-regressions/compressed/c_addi.s | 43 +++ elf-regressions/compressed/c_addi16sp.s | 37 +++ elf-regressions/compressed/c_addi4spn.s | 39 +++ elf-regressions/compressed/c_andi.s | 48 ++++ elf-regressions/compressed/c_beqz_bnez.s | 37 +++ elf-regressions/compressed/c_j.s | 37 +++ elf-regressions/compressed/c_jr_jalr.s | 39 +++ elf-regressions/compressed/c_li.s | 31 +++ elf-regressions/compressed/c_lui.s | 31 +++ elf-regressions/compressed/c_lw_sw.s | 57 ++++ elf-regressions/compressed/c_lwsp_swsp.s | 96 +++++++ elf-regressions/compressed/c_mv.s | 47 ++++ elf-regressions/compressed/c_or_and_xor_sub.s | 53 ++++ elf-regressions/compressed/c_slli.s | 49 ++++ elf-regressions/compressed/c_srli_srai.s | 49 ++++ .../compressed/edge_cases_alignment.s | 198 +++++++++++++ .../compressed/edge_cases_branches.s | 253 +++++++++++++++++ .../compressed/edge_cases_immediates.s | 210 ++++++++++++++ .../compressed/edge_cases_registers.s | 194 +++++++++++++ elf-regressions/compressed/mixed_arithmetic.s | 135 +++++++++ .../compressed/mixed_control_flow.s | 260 ++++++++++++++++++ elf-regressions/compressed/mixed_stack_ops.s | 172 ++++++++++++ 23 files changed, 2159 insertions(+) create mode 100644 elf-regressions/compressed/c_add.s create mode 100644 elf-regressions/compressed/c_addi.s create mode 100644 elf-regressions/compressed/c_addi16sp.s create mode 100644 elf-regressions/compressed/c_addi4spn.s create mode 100644 elf-regressions/compressed/c_andi.s create mode 100644 elf-regressions/compressed/c_beqz_bnez.s create mode 100644 elf-regressions/compressed/c_j.s create mode 100644 elf-regressions/compressed/c_jr_jalr.s create mode 100644 elf-regressions/compressed/c_li.s create mode 100644 elf-regressions/compressed/c_lui.s create mode 100644 elf-regressions/compressed/c_lw_sw.s create mode 100644 elf-regressions/compressed/c_lwsp_swsp.s create mode 100644 elf-regressions/compressed/c_mv.s create mode 100644 elf-regressions/compressed/c_or_and_xor_sub.s create mode 100644 elf-regressions/compressed/c_slli.s create mode 100644 elf-regressions/compressed/c_srli_srai.s create mode 100644 elf-regressions/compressed/edge_cases_alignment.s create mode 100644 elf-regressions/compressed/edge_cases_branches.s create mode 100644 elf-regressions/compressed/edge_cases_immediates.s create mode 100644 elf-regressions/compressed/edge_cases_registers.s create mode 100644 elf-regressions/compressed/mixed_arithmetic.s create mode 100644 elf-regressions/compressed/mixed_control_flow.s create mode 100644 elf-regressions/compressed/mixed_stack_ops.s diff --git a/elf-regressions/compressed/c_add.s b/elf-regressions/compressed/c_add.s new file mode 100644 index 000000000..776376026 --- /dev/null +++ b/elf-regressions/compressed/c_add.s @@ -0,0 +1,44 @@ +# Test c.add instruction - compressed add register to register + +.section .text.init +.global _start + +_start: + # Test basic addition + li x8, 10 + li x9, 20 + c.add x8, x9 # x8 = 10 + 20 = 30 + + # Verify result + li t0, 30 + bne x8, t0, error + + # Test with zero + li x10, 42 + li x11, 0 + c.add x10, x11 # x10 = 42 + 0 = 42 + + # Verify result + li t0, 42 + bne x10, t0, error + + # Test negative addition + li x12, -5 + li x13, 3 + c.add x12, x13 # x12 = -5 + 3 = -2 + + # Verify result + li t0, -2 + bne x12, t0, error + + # Success + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_addi.s b/elf-regressions/compressed/c_addi.s new file mode 100644 index 000000000..bf8a64d96 --- /dev/null +++ b/elf-regressions/compressed/c_addi.s @@ -0,0 +1,43 @@ +# Test c.addi instruction - compressed add immediate +# Tests adding immediate values to registers + +.section .text.init +.global _start + +_start: + # Initialize base values + li x1, 100 + li x2, 0 + li x3, -50 + + # Test positive immediate additions + c.addi x1, 1 + c.addi x1, 31 + c.addi x1, 15 + + # Test negative immediate additions + c.addi x1, -1 + c.addi x1, -32 + c.addi x1, -16 + + # Test zero addition + c.addi x2, 0 + + # Test overflow scenarios + li x4, 0x7fffffff + c.addi x4, 1 # Should overflow to negative + + li x5, 0x80000000 + c.addi x5, -1 # Should underflow to positive + + # Test different registers + c.addi t0, 10 + c.addi t1, -5 + c.addi a0, 7 + c.addi a1, -3 + + # Exit + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_addi16sp.s b/elf-regressions/compressed/c_addi16sp.s new file mode 100644 index 000000000..7f3e52179 --- /dev/null +++ b/elf-regressions/compressed/c_addi16sp.s @@ -0,0 +1,37 @@ +# Test c.addi16sp instruction - compressed add immediate to SP scaled by 16 +# Tests stack pointer manipulation with 16-byte alignment + +.section .text.init +.global _start + +_start: + # Save original stack pointer + mv t0, sp + + # Test positive adjustments (allocate stack space) + c.addi16sp sp, -16 # Allocate 16 bytes + c.addi16sp sp, -32 # Allocate 32 bytes + c.addi16sp sp, -64 # Allocate 64 bytes + c.addi16sp sp, -512 # Maximum negative adjustment + + # Test positive adjustments (deallocate stack space) + c.addi16sp sp, 16 # Deallocate 16 bytes + c.addi16sp sp, 32 # Deallocate 32 bytes + c.addi16sp sp, 64 # Deallocate 64 bytes + c.addi16sp sp, 496 # Maximum positive adjustment + + # Test edge cases + c.addi16sp sp, -496 # Near maximum negative + c.addi16sp sp, 496 # Maximum positive + + # Test zero adjustment (should be no-op) + mv t1, sp + + # Restore stack pointer + mv sp, t0 + + # Exit + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_addi4spn.s b/elf-regressions/compressed/c_addi4spn.s new file mode 100644 index 000000000..bc0153c44 --- /dev/null +++ b/elf-regressions/compressed/c_addi4spn.s @@ -0,0 +1,39 @@ +# Test c.addi4spn instruction - compressed add immediate scaled by 4 to SP for narrow registers +# Tests SP-relative addressing for compressed register set (x8-x15) + +.section .text.init +.global _start + +_start: + # Test with different immediate values and compressed registers + c.addi4spn x8, sp, 4 # Add 4 to SP, store in x8 + c.addi4spn x9, sp, 8 # Add 8 to SP, store in x9 + c.addi4spn x10, sp, 12 # Add 12 to SP, store in x10 + c.addi4spn x11, sp, 16 # Add 16 to SP, store in x11 + + # Test larger offsets + c.addi4spn x12, sp, 64 # Add 64 to SP + c.addi4spn x13, sp, 128 # Add 128 to SP + c.addi4spn x14, sp, 256 # Add 256 to SP + c.addi4spn x15, sp, 512 # Add 512 to SP + + # Test maximum offset + c.addi4spn x8, sp, 1020 # Maximum offset (1020 = 255 * 4) + + # Test various multiples of 4 + c.addi4spn x9, sp, 20 + c.addi4spn x10, sp, 36 + c.addi4spn x11, sp, 100 + c.addi4spn x12, sp, 200 + c.addi4spn x13, sp, 300 + c.addi4spn x14, sp, 400 + c.addi4spn x15, sp, 500 + + sub t0, x9, x8 # Should be (20-1020) = -1000 + sub t1, x10, x9 # Should be (36-20) = 16 + + # Exit + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_andi.s b/elf-regressions/compressed/c_andi.s new file mode 100644 index 000000000..d6464ee53 --- /dev/null +++ b/elf-regressions/compressed/c_andi.s @@ -0,0 +1,48 @@ +# Test c.andi instruction - compressed AND immediate + +.section .text.init +.global _start + +_start: + # Test basic AND with positive immediate + li x8, 0xff + c.andi x8, 0x0f # x8 = 0xff & 0x0f = 0x0f + + # Verify result + li t0, 0x0f + bne x8, t0, error + + # Test AND with zero (clears all bits) + li x9, 0x12345678 + c.andi x9, 0 # x9 = 0x12345678 & 0 = 0 + + # Verify result + bne x9, x0, error + + # Test AND with -1 (preserves all bits in range) + li x10, 0x12345678 + c.andi x10, -1 # x10 = 0x12345678 & 0xffffffff = 0x12345678 + + # Verify result (only low bits matter for c.andi) + li t0, 0x12345678 + bne x10, t0, error + + # Test boundary immediate values + li x11, 0xffffffff + c.andi x11, 31 # x11 = 0xffffffff & 31 = 31 + + # Verify result + li t0, 31 + bne x11, t0, error + + # Success + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_beqz_bnez.s b/elf-regressions/compressed/c_beqz_bnez.s new file mode 100644 index 000000000..b9bdba5d3 --- /dev/null +++ b/elf-regressions/compressed/c_beqz_bnez.s @@ -0,0 +1,37 @@ +# Test c.beqz and c.bnez instructions - compressed branch if equal/not equal to zero + +.section .text.init +.global _start + +_start: + # Test c.beqz with zero - should branch + li x8, 0 + c.beqz x8, test1_pass + j error + +test1_pass: + # Test c.beqz with non-zero - should not branch + li x9, 1 + c.beqz x9, error + + # Test c.bnez with non-zero - should branch + li x10, 42 + c.bnez x10, test2_pass + j error + +test2_pass: + # Test c.bnez with zero - should not branch + li x11, 0 + c.bnez x11, error + + # All tests passed + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_j.s b/elf-regressions/compressed/c_j.s new file mode 100644 index 000000000..3bd0d8dec --- /dev/null +++ b/elf-regressions/compressed/c_j.s @@ -0,0 +1,37 @@ +# Test c.j instruction - compressed unconditional jump + +.section .text.init +.global _start + +_start: + # Test forward jump + c.j forward_target + + # Should not reach here + li a0, 1 + li a7, 93 + ecall + +forward_target: + # Test backward jump + c.j test_backward + +after_backward: + # Test jump over instructions + c.j skip_section + + # These should be skipped + li x1, 0xbad + li x2, 0xbad + +skip_section: + # Success - all jumps worked + li a0, 0 + li a7, 93 + ecall + +test_backward: + # Jump back to continue test + c.j after_backward + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_jr_jalr.s b/elf-regressions/compressed/c_jr_jalr.s new file mode 100644 index 000000000..720f2815c --- /dev/null +++ b/elf-regressions/compressed/c_jr_jalr.s @@ -0,0 +1,39 @@ +# Test c.jr and c.jalr instructions - compressed jump register + +.section .text.init +.global _start + +_start: + # Test c.jr (jump register) + la x1, jr_target + c.jr x1 # Jump to address in x1 + + # Should not reach here + li a0, 1 + li a7, 93 + ecall + +jr_target: + # Test c.jalr (jump and link register) + la x2, function + c.jalr x2 # Call function, return address in x1 + + # Should return here + # Test that we returned by calling another function + la x3, function2 + c.jalr x3 + + # Success + li a0, 0 + li a7, 93 + ecall + +function: + # Simple function that returns + c.jr x1 # Return using saved address + +function2: + # Another simple function + c.jr x1 # Return + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_li.s b/elf-regressions/compressed/c_li.s new file mode 100644 index 000000000..e1b6073e5 --- /dev/null +++ b/elf-regressions/compressed/c_li.s @@ -0,0 +1,31 @@ +# Test c.li instruction - compressed load immediate +# Tests various immediate values including edge cases + +.section .text.init +.global _start + +_start: + # Test basic positive values + c.li x1, 0 + c.li x2, 1 + c.li x3, 31 + + # Test negative values (sign-extended) + c.li x4, -1 + c.li x5, -32 + + # Test boundary values + c.li x6, 15 + c.li x7, -16 + + # Test with different registers + c.li t0, 10 + c.li t1, 20 + c.li a0, 5 + c.li a1, -5 + + # Exit + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_lui.s b/elf-regressions/compressed/c_lui.s new file mode 100644 index 000000000..286017776 --- /dev/null +++ b/elf-regressions/compressed/c_lui.s @@ -0,0 +1,31 @@ +# Test c.lui instruction - compressed load upper immediate +# Tests upper immediate loading with various values + +.section .text.init +.global _start + +_start: + # Test basic values (c.lui uses 6-bit signed immediate, not 20-bit) + c.lui x1, 1 + c.lui x3, 0x1f # Maximum positive (31) + c.lui x4, 1 + + # Test various positive values (this toolchain only supports positive c.lui) + c.lui x5, 1 # Small positive + c.lui x6, 2 # Small positive + + # Test boundary conditions (positive values only) + c.lui x7, 30 # Near maximum + c.lui x8, 31 # Most positive (31) + + # Test with different registers (excluding x0, x2/sp) + c.lui t0, 16 # Valid positive immediate + c.lui t1, 20 # Valid positive immediate + c.lui a0, 8 # Valid positive immediate + c.lui a1, 12 # Valid positive immediate + + # Exit + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_lw_sw.s b/elf-regressions/compressed/c_lw_sw.s new file mode 100644 index 000000000..0487c6807 --- /dev/null +++ b/elf-regressions/compressed/c_lw_sw.s @@ -0,0 +1,57 @@ +# Test c.lw and c.sw instructions - compressed load/store word + +.section .data +test_data: + .word 0x12345678 + .word 0x9abcdef0 + .space 32 + +.section .text.init +.global _start + +_start: + # Load base address (must use compressed register) + la x8, test_data + + # Test basic c.sw and c.lw + li x9, 0x11223344 + c.sw x9, 8(x8) # Store at offset 8 + c.lw x10, 8(x8) # Load back from offset 8 + + # Verify result + bne x9, x10, error + + # Test different offsets + li x11, 0x55667788 + c.sw x11, 12(x8) # Store at offset 12 + c.lw x12, 12(x8) # Load back + + # Verify result + bne x11, x12, error + + # Test maximum offset (124 = 31 * 4) + li x13, 0xaabbccdd + c.sw x13, 124(x8) # Store at max offset + c.lw x14, 124(x8) # Load back + + # Verify result + bne x13, x14, error + + # Test loading existing data + c.lw x15, 0(x8) # Load first word (0x12345678) + + # Verify result + li t0, 0x12345678 + bne x15, t0, error + + # Success + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_lwsp_swsp.s b/elf-regressions/compressed/c_lwsp_swsp.s new file mode 100644 index 000000000..fcdddc5e8 --- /dev/null +++ b/elf-regressions/compressed/c_lwsp_swsp.s @@ -0,0 +1,96 @@ +# Test compressed stack operations - using c.addi16sp and regular load/store +# Tests SP-relative operations with compressed instructions where available + +.section .text.init +.global _start + +_start: + # Save original stack pointer + mv t0, sp + + # Allocate stack space using compressed instruction + c.addi16sp sp, -256 # Use compressed SP adjustment + + # Initialize some test data on stack + li t1, 0x11111111 + li t2, 0x22222222 + li t3, 0x33333333 + li t4, 0x44444444 + li t5, 0x55555555 + li t6, 0x66666666 + + # Store test data using regular stack operations + sw t1, 0(sp) # Store at SP + 0 + sw t2, 4(sp) # Store at SP + 4 + sw t3, 8(sp) # Store at SP + 8 + sw t4, 12(sp) # Store at SP + 12 + sw t5, 16(sp) # Store at SP + 16 + sw t6, 20(sp) # Store at SP + 20 + + # Test larger offsets + sw t1, 64(sp) # Store at SP + 64 + sw t2, 128(sp) # Store at SP + 128 + sw t3, 192(sp) # Store at SP + 192 + sw t4, 252(sp) # Store at SP + 252 + + # Clear registers + li x1, 0 + li x2, 0 + li x3, 0 + li x4, 0 + li x5, 0 + li x6, 0 + + # Load back using regular loads + lw x1, 0(sp) # Load from SP + 0 + lw x2, 4(sp) # Load from SP + 4 + lw x3, 8(sp) # Load from SP + 8 + lw x4, 12(sp) # Load from SP + 12 + lw x5, 16(sp) # Load from SP + 16 + lw x6, 20(sp) # Load from SP + 20 + + # Verify values match original + bne x1, t1, error + bne x2, t2, error + bne x3, t3, error + bne x4, t4, error + bne x5, t5, error + bne x6, t6, error + + # Test larger offsets + lw x7, 64(sp) # Load from SP + 64 + lw x8, 128(sp) # Load from SP + 128 + lw x9, 192(sp) # Load from SP + 192 + lw x10, 252(sp) # Load from SP + 252 + + # Verify larger offset values + bne x7, t1, error + bne x8, t2, error + bne x9, t3, error + bne x10, t4, error + + # Test stack frame simulation + sw ra, 248(sp) # Save return address + sw s0, 244(sp) # Save frame pointer + lw s0, 244(sp) # Restore frame pointer + lw ra, 248(sp) # Restore return address + +success: + # Restore stack pointer + mv sp, t0 + + # Exit with success + li a0, 0 + li a7, 93 + ecall + +error: + # Restore stack pointer + mv sp, t0 + + # Exit with error + li a0, 1 + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_mv.s b/elf-regressions/compressed/c_mv.s new file mode 100644 index 000000000..0cfdee28c --- /dev/null +++ b/elf-regressions/compressed/c_mv.s @@ -0,0 +1,47 @@ +# Test c.mv instruction - compressed move register to register + +.section .text.init +.global _start + +_start: + # Test basic move + li x1, 0x12345678 + c.mv x2, x1 # x2 = x1 + + # Verify result + bne x1, x2, error + + # Test move zero + li x3, 0 + c.mv x4, x3 # x4 = 0 + + # Verify result + bne x3, x4, error + + # Test move negative + li x5, -1 + c.mv x6, x5 # x6 = -1 + + # Verify result + bne x5, x6, error + + # Test chain of moves + li x7, 0xdeadbeef + c.mv x8, x7 # x8 = x7 + c.mv x9, x8 # x9 = x8 = x7 + + # Verify chain + bne x7, x8, error + bne x8, x9, error + + # Success + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_or_and_xor_sub.s b/elf-regressions/compressed/c_or_and_xor_sub.s new file mode 100644 index 000000000..223019000 --- /dev/null +++ b/elf-regressions/compressed/c_or_and_xor_sub.s @@ -0,0 +1,53 @@ +# Test c.or, c.and, c.xor, c.sub instructions - compressed ALU operations + +.section .text.init +.global _start + +_start: + # Test c.or + li x8, 0xf0f0f0f0 + li x9, 0x0f0f0f0f + c.or x8, x9 # x8 = 0xf0f0f0f0 | 0x0f0f0f0f = 0xffffffff + + # Verify result + li t0, 0xffffffff + bne x8, t0, error + + # Test c.and + li x10, 0x12345678 + li x11, 0xff00ff00 + c.and x10, x11 # x10 = 0x12345678 & 0xff00ff00 = 0x12005600 + + # Verify result + li t0, 0x12005600 + bne x10, t0, error + + # Test c.xor + li x12, 0xaaaaaaaa + li x13, 0x55555555 + c.xor x12, x13 # x12 = 0xaaaaaaaa ^ 0x55555555 = 0xffffffff + + # Verify result + li t0, 0xffffffff + bne x12, t0, error + + # Test c.sub + li x14, 100 + li x15, 30 + c.sub x14, x15 # x14 = 100 - 30 = 70 + + # Verify result + li t0, 70 + bne x14, t0, error + + # Success + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_slli.s b/elf-regressions/compressed/c_slli.s new file mode 100644 index 000000000..f67fc33ee --- /dev/null +++ b/elf-regressions/compressed/c_slli.s @@ -0,0 +1,49 @@ +# Test c.slli instruction - compressed shift left logical immediate + +.section .text.init +.global _start + +_start: + # Test basic left shift + li x1, 1 + c.slli x1, 1 # x1 = 1 << 1 = 2 + + # Verify result + li t0, 2 + bne x1, t0, error + + # Test larger shift + li x2, 1 + c.slli x2, 8 # x2 = 1 << 8 = 256 + + # Verify result + li t0, 256 + bne x2, t0, error + + # Test shift with data + li x3, 0x12345678 + c.slli x3, 4 # x3 = 0x12345678 << 4 = 0x23456780 + + # Verify result + li t0, 0x23456780 + bne x3, t0, error + + # Test maximum shift + li x4, 1 + c.slli x4, 31 # x4 = 1 << 31 = 0x80000000 + + # Verify result + li t0, 0x80000000 + bne x4, t0, error + + # Success + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/c_srli_srai.s b/elf-regressions/compressed/c_srli_srai.s new file mode 100644 index 000000000..eb5f25f7e --- /dev/null +++ b/elf-regressions/compressed/c_srli_srai.s @@ -0,0 +1,49 @@ +# Test c.srli and c.srai instructions - compressed shift right logical and arithmetic + +.section .text.init +.global _start + +_start: + # Test c.srli (logical right shift) + li x8, 0x80000000 + c.srli x8, 1 # x8 = 0x80000000 >> 1 = 0x40000000 (logical) + + # Verify result + li t0, 0x40000000 + bne x8, t0, error + + # Test c.srli with data + li x9, 0x12345678 + c.srli x9, 4 # x9 = 0x12345678 >> 4 = 0x01234567 + + # Verify result + li t0, 0x01234567 + bne x9, t0, error + + # Test c.srai (arithmetic right shift) with positive number + li x10, 0x12345678 + c.srai x10, 4 # x10 = 0x12345678 >> 4 = 0x01234567 (same as logical) + + # Verify result + li t0, 0x01234567 + bne x10, t0, error + + # Test c.srai with negative number (sign extension) + li x11, 0x80000000 + c.srai x11, 1 # x11 = 0x80000000 >> 1 = 0xc0000000 (sign extended) + + # Verify result + li t0, 0xc0000000 + bne x11, t0, error + + # Success + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/edge_cases_alignment.s b/elf-regressions/compressed/edge_cases_alignment.s new file mode 100644 index 000000000..7c8bfd163 --- /dev/null +++ b/elf-regressions/compressed/edge_cases_alignment.s @@ -0,0 +1,198 @@ +# Edge cases for compressed instruction alignment and encoding +# Tests instruction alignment, mixed 16/32-bit instructions, and boundary conditions + +.section .text.init +.global _start + +_start: + # Test 16-bit instruction alignment + # Compressed instructions must be 16-bit aligned + + # Mix compressed and uncompressed instructions + c.li x1, 10 # 16-bit instruction + addi x2, x1, 20 # 32-bit instruction + c.add x1, x2 # 16-bit instruction + li x3, 0x12345678 # 32-bit instruction + c.mv x4, x3 # 16-bit instruction + + # Test instruction sequence that crosses boundaries + .align 2 +boundary_test: + c.li x5, 1 # At 4-byte boundary + c.li x6, 2 # At 4-byte boundary + 2 + add x7, x5, x6 # 32-bit instruction at odd 16-bit boundary + c.mv x8, x7 # Back to compressed + + # Test maximum compressed immediate values at boundaries + c.li x9, 31 # Maximum 6-bit signed positive + c.li x10, -32 # Maximum 6-bit signed negative + addi x11, x9, 1 # Force to 32-bit to get 32 + addi x12, x10, -1 # Force to 32-bit to get -33 + + # Compare compressed vs uncompressed versions + bne x9, x11, different_immediate_ranges # Should be different (31 vs 32) + c.j error # Should not reach here + +different_immediate_ranges: + # Test shift amount boundaries + li x13, 1 + c.slli x13, 31 # Maximum shift amount for compressed + li x14, 1 + slli x14, x14, 31 # Same shift with 32-bit instruction + bne x13, x14, error # Should be the same + + # Test compressed vs uncompressed load/store + la x15, test_data + li x8, 0xabcdef01 # Use compressed register + + # Compressed store and load (both registers must be x8-x15) + c.sw x8, 0(x15) # Compressed store (both x8,x15 are compressed) + c.lw x9, 0(x15) # Compressed load (both x9,x15 are compressed) + + # Uncompressed store and load with same data + sw x8, 4(x15) # Uncompressed store (larger offset range) + lw x18, 4(x15) # Uncompressed load + + bne x9, x18, error # Should load same data + + # Test offset encoding differences + # Compressed: offset scaled by 4, max 124 (31*4) + # Uncompressed: offset not scaled, max 2047 + + c.sw x8, 124(x15) # Maximum compressed offset (x8 compressed) + sw x8, 128(x15) # Uncompressed beyond compressed range + + c.lw x10, 124(x15) # Load with compressed instruction (x10 compressed) + lw x20, 128(x15) # Load with uncompressed instruction + + bne x10, x20, error # Should be same data + + # Test stack pointer operations with alignment + mv t6, sp + + # Ensure SP is aligned before compressed operations + andi t0, sp, 15 # Check if SP is 16-byte aligned + bnez t0, align_sp # If not, align it + c.j sp_aligned + +align_sp: + addi sp, sp, -16 # Align to 16-byte boundary + +sp_aligned: + # Test compressed stack operations + c.addi16sp sp, -32 # Must be multiple of 16 + c.addi16sp sp, 32 # Restore + + # Test that odd adjustments fail (should use uncompressed) + # c.addi16sp sp, -17 # This would be invalid encoding + addi sp, sp, -17 # Use uncompressed for odd values + addi sp, sp, 17 # Restore with uncompressed + + mv sp, t6 # Restore original SP + + # Test instruction fetch alignment + # Jump to odd 16-bit boundary + c.j odd_boundary_test + + .align 2 + nop # Ensure we start at 4-byte boundary + +odd_boundary_test: + # These instructions start at 4-byte + 2 boundary + li x21, 42 # Use regular li (c.li limited to -32 to 31) + li x22, 84 # Use regular li (c.li limited to -32 to 31) + add x23, x21, x22 # 32-bit instruction at 4-byte boundary + + # Test branch targets and alignment + c.j even_target # Jump to even boundary + + .align 2 + nop # Padding to create even target + +even_target: + li x24, 100 # At 4-byte boundary (c.li limited to -32 to 31) + + # Test that unaligned accesses work properly + la x11, unaligned_data # Use compressed register + + # Load from properly aligned address + c.lw x12, 0(x11) # Aligned load (both x12,x11 compressed) + + # Test memory alignment requirements + la x27, test_data + + # Ensure test_data is word-aligned for compressed operations + andi t0, x27, 3 # Check word alignment + bnez t0, error # Should be word-aligned + + # Test compressed instruction encoding boundaries + # Some instructions have special encodings for certain values + + # c.li x0, 0 is reserved (HINT) + # c.addi x0, 0 is NOP + # c.mv x0, x0 is reserved + + # Test legal NOP variants + nop # Use standard nop instead + addi x0, x0, 0 # 32-bit NOP + + # Test register encoding boundaries + # Compressed registers use 3-bit encoding (x8-x15) + # Full registers use 5-bit encoding (x0-x31) + + # Test that high registers require uncompressed instructions + li x30, 0x30303030 # High register number + li x31, 0x31313131 # Highest register + + # These operations require uncompressed instructions + add x30, x30, x31 # Cannot use compressed form + mv x29, x30 # c.mv can handle any register + + # Test instruction length detection + # Ensure emulator correctly identifies 16-bit vs 32-bit instructions + + # Create specific pattern to test decoder + c.li x28, 15 # 16-bit: should decode as c.li + ori x28, x28, 16 # 32-bit: should decode as ori (not compressed) + + # Result should be 15 | 16 = 31 + li t0, 31 + bne x28, t0, error + + # Test boundary between compressed and uncompressed encodings + # Some instruction patterns overlap between 16-bit and 32-bit encodings + + # Verify all our test values + li t0, 10 + bne x1, t0, error + li t0, 30 + bne x2, t0, error + li t0, 40 + bne x4, t0, error # x4 = x1 + x2 + + # Success + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b + +.section .data +.align 4 +test_data: + .word 0x11111111 + .word 0x22222222 + .word 0x33333333 + .word 0x44444444 + .space 64 + +# Intentionally unaligned data for testing +.align 1 +unaligned_data: + .byte 0x01 + .word 0x12345678 # This will be at odd byte boundary \ No newline at end of file diff --git a/elf-regressions/compressed/edge_cases_branches.s b/elf-regressions/compressed/edge_cases_branches.s new file mode 100644 index 000000000..3bd20cdac --- /dev/null +++ b/elf-regressions/compressed/edge_cases_branches.s @@ -0,0 +1,253 @@ +# Edge cases for compressed branch instructions +# Tests branch distances, target alignment, and conditional edge cases + +.section .text.init +.global _start + +_start: + # Test compressed branch distance limits + # c.beqz and c.bnez have 9-bit signed offset (scaled by 2) + # Range: -256 to +254 bytes + + # Test forward branch near maximum distance + li x8, 0 + c.beqz x8, far_forward_target # Should branch + + # Fill space to test distance (but not too much to exceed limits) + .rept 50 + nop + .endr + + # Should not reach here + li x1, 0xbad + c.j error + +far_forward_target: + li x1, 0x100 # Mark successful far forward branch + + # Test backward branch setup + li x9, 42 + c.j setup_backward_test + +backward_target: + li x2, 0x200 # Mark successful backward branch + c.j continue_test + +setup_backward_test: + # Test backward branch + c.bnez x9, backward_target # Should branch back + + # Should not reach here + li x2, 0xbad + +continue_test: + # Test branch target alignment + # Branch targets should be 2-byte aligned for compressed instructions + + li x10, 1 + c.j aligned_target # Jump to aligned target + + .align 2 + nop # Ensure alignment + +aligned_target: + c.bnez x10, next_aligned # Branch to another aligned target + li x3, 0xbad + + .align 1 # 2-byte alignment +next_aligned: + li x3, 0x300 + + # Test edge case: branch to current instruction (infinite loop prevention) + li x11, 0 + c.bnez x11, after_self_branch # Should not branch (x11 is 0) + li x4, 0x400 # Should execute this + c.j after_self_branch + +after_self_branch: + # Test branch with zero flag edge cases + + # Test with exactly zero + li x12, 0x00000000 + c.beqz x12, zero_branch_taken + li x5, 0xbad + c.j error + +zero_branch_taken: + li x5, 0x500 + + # Test with non-zero patterns + li x13, 0x80000000 # MSB set (negative in signed interpretation) + c.bnez x13, negative_nonzero + li x6, 0xbad + c.j error + +negative_nonzero: + li x6, 0x600 + + li x14, 0x00000001 # LSB set (smallest positive) + c.bnez x14, positive_nonzero + li x7, 0xbad + c.j error + +positive_nonzero: + li x7, 0x700 + + # Test with alternating bit patterns + li x15, 0xaaaaaaaa + c.bnez x15, alternating_nonzero + li x8, 0xbad + c.j error + +alternating_nonzero: + li x8, 0x800 + + # Test branch prediction scenarios + # Loops that are likely to be taken vs not taken + + # Countdown loop (usually taken, except last iteration) + li x8, 3 # Loop counter (compressed register) + li x9, 0 # Accumulator (compressed register) + +countdown_loop: + c.beqz x8, countdown_end # Exit condition (c.beqz only on x8-x15) + c.add x9, x8 # Add counter to accumulator (compressed regs) + c.addi x8, -1 # Decrement (c.addi only on x8-x15) + c.j countdown_loop # Continue loop + +countdown_end: + # x9 should be 3+2+1 = 6 + li t0, 6 + bne x9, t0, error + + # Test nested conditional branches (use compressed registers) + li x10, 1 # Use compressed register + li x11, 0 # Use compressed register + li x12, 2 # Use compressed register + + c.bnez x10, outer_condition # Should branch (x10 is compressed) + li x9, 0xbad + c.j error + +outer_condition: + c.beqz x11, inner_condition # Should branch (x11 is compressed) + li x10, 0xbad + c.j error + +inner_condition: + c.bnez x12, nested_end # Should branch (x12 is compressed) + li x11, 0xbad + c.j error + +nested_end: + li x9, 0x900 + li x10, 0xa00 + li x11, 0xb00 + + # Test branch over different instruction types + li x13, 5 # Use compressed register + c.bnez x13, skip_mixed_instructions + + # Mixed instructions to skip + addi x12, x0, 1 # Valid instruction (0xbad is invalid hex) + li x13, 1 # Use regular li (c.li limited to -32 to 31) + lw x14, 0(x0) # This might fault, but should be skipped + c.add x15, x14 + +skip_mixed_instructions: + li x12, 0xc00 # Mark successful skip + + # Test branch to function-like targets + li x14, 10 # Use compressed register + c.bnez x14, function_like_target + li x13, 0xbad + c.j error + +return_point: + li x13, 0xd00 + c.j final_verification + +function_like_target: + # Simulate function that "returns" + li x14, 0xe00 + c.j return_point # "Return" to caller + +final_verification: + # Test rapid successive branches (use compressed registers) + li x8, 1 # Use compressed register + li x9, 0 # Use compressed register + li x10, 1 # Use compressed register + + c.bnez x8, rapid1 # Should branch (x8 is compressed) + c.j error +rapid1: + c.beqz x9, rapid2 # Should branch (x9 is compressed) + c.j error +rapid2: + c.bnez x10, rapid3 # Should branch (x10 is compressed) + c.j error +rapid3: + li x15, 0xf00 + + # Test branch distance calculations + # Verify that we can branch to nearby targets + li x11, 1 # Use compressed register + c.bnez x11, near_target_1 + c.j error + +near_target_1: + c.bnez x11, near_target_2 + c.j error + +near_target_2: + c.bnez x11, near_target_3 + c.j error + +near_target_3: + li x16, 0x1600 + + # Verify all results + li t0, 0x100 + bne x1, t0, error + li t0, 0x200 + bne x2, t0, error + li t0, 0x300 + bne x3, t0, error + li t0, 0x400 + bne x4, t0, error + li t0, 0x500 + bne x5, t0, error + li t0, 0x600 + bne x6, t0, error + li t0, 0x700 + bne x7, t0, error + li t0, 0x800 + bne x8, t0, error + li t0, 0x900 + bne x9, t0, error + li t0, 0xa00 + bne x10, t0, error + li t0, 0xb00 + bne x11, t0, error + li t0, 0xc00 + bne x12, t0, error + li t0, 0xd00 + bne x13, t0, error + li t0, 0xe00 + bne x14, t0, error + li t0, 0xf00 + bne x15, t0, error + li t0, 0x1600 + bne x16, t0, error + + # Success + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/edge_cases_immediates.s b/elf-regressions/compressed/edge_cases_immediates.s new file mode 100644 index 000000000..69bd0bb3f --- /dev/null +++ b/elf-regressions/compressed/edge_cases_immediates.s @@ -0,0 +1,210 @@ +# Edge cases for compressed instruction immediates +# Tests boundary values, overflow conditions, and special immediate encodings + +.section .text.init +.global _start + +_start: + # c.li edge cases - 6-bit signed immediate (-32 to 31) + c.li x1, 31 # Maximum positive immediate + c.li x2, -32 # Maximum negative immediate + c.li x3, 0 # Zero immediate + c.li x4, 1 # Minimum positive + c.li x5, -1 # Maximum magnitude negative + + # Verify sign extension + li t0, 31 + bne x1, t0, error + li t0, -32 + bne x2, t0, error + li t0, -1 + bne x5, t0, error + + # c.addi edge cases - 6-bit signed immediate + li x6, 100 + c.addi x6, 31 # Add maximum positive + li t0, 131 + bne x6, t0, error + + li x7, 100 + c.addi x7, -32 # Add maximum negative + li t0, 68 + bne x7, t0, error + + # Test zero addition (should be no-op) + li x8, 0x12345678 + c.addi x8, 0 + li t0, 0x12345678 + bne x8, t0, error + + # Test overflow with c.addi + li x9, 0x7fffffff # Max positive 32-bit + c.addi x9, 1 # Should overflow to negative + li t0, 0x80000000 + bne x9, t0, error + + li x10, 0x80000000 # Min negative 32-bit + c.addi x10, -1 # Should underflow to positive + li t0, 0x7fffffff + bne x10, t0, error + + # c.lui edge cases - 6-bit immediate for upper 20 bits + # Note: c.lui encodes different than c.li/c.addi + c.lui x11, 1 # Minimum non-zero value + li t0, 0x1000 # Should be 1 << 12 + bne x11, t0, error + + c.lui x12, 31 # Maximum positive in 6-bit field + li t0, 0x1f000 # Should be 31 << 12 + bne x12, t0, error + + # c.lui with different values + c.lui x13, 16 # Another positive value + # Test completed + + # c.addi16sp edge cases - 10-bit signed immediate scaled by 16 + mv t5, sp # Save original SP + + # Test maximum positive adjustment + c.addi16sp sp, 496 # 31 * 16 = 496 (max positive) + + # Test maximum negative adjustment + c.addi16sp sp, -512 # -32 * 16 = -512 (max negative) + + # Test small adjustments + c.addi16sp sp, 16 # Minimum positive adjustment + c.addi16sp sp, -16 # Minimum negative adjustment + + mv sp, t5 # Restore SP + + # c.addi4spn edge cases - 10-bit unsigned immediate scaled by 4 + # Maximum offset is 1020 (255 * 4) + c.addi4spn x14, sp, 1020 # Maximum offset + c.addi4spn x15, sp, 4 # Minimum offset + + # Verify the calculations + sub t0, x14, sp + li t1, 1020 + bne t0, t1, error + + sub t0, x15, sp + li t1, 4 + bne t0, t1, error + + # c.lwsp/c.swsp edge cases - 8-bit unsigned immediate scaled by 4 + mv t5, sp + addi sp, sp, -256 # Allocate space + + # Test maximum offset using regular instructions + li x16, 0xdeadbeef + sw x16, 252(sp) # Maximum offset + lw x17, 252(sp) # Load back + bne x16, x17, error + + # Test minimum offset + li x18, 0xcafebabe + sw x18, 0(sp) # Minimum offset + lw x19, 0(sp) # Load back + bne x18, x19, error + + mv sp, t5 # Restore SP + + # c.lw/c.sw edge cases - 7-bit unsigned immediate scaled by 4 + la x12, test_data # Use compressed register for base + + # Test maximum offset for compressed load/store (both registers must be x8-x15) + li x8, 0x11223344 # Use compressed register + c.sw x8, 124(x12) # Maximum offset (31 * 4 = 124), both x8,x12 compressed + c.lw x9, 124(x12) # Load back, both x9,x12 compressed + bne x8, x9, error + + # Test minimum offset + li x10, 0x55667788 # Use compressed register + c.sw x10, 0(x12) # Minimum offset, both x10,x12 compressed + c.lw x11, 0(x12) # Load back, both x11,x12 compressed + bne x10, x11, error + + # c.andi edge cases - 6-bit signed immediate (compressed registers only) + li x12, 0xffffffff # Use compressed register + c.andi x12, 31 # AND with max positive immediate + li t0, 31 + bne x12, t0, error + + li x13, 0xffffffff # Use compressed register + c.andi x13, -32 # AND with max negative immediate + li t0, 0xffffffe0 # Should be 0xffffffff & 0xffffffe0 + bne x13, t0, error + + li x14, 0x12345678 # Use compressed register + c.andi x14, 0 # AND with zero (should clear all) + bne x14, x0, error + + li x15, 0x12345678 # Use compressed register + c.andi x15, -1 # AND with -1 (should preserve all) + li t0, 0x12345678 + bne x15, t0, error + + # Shift immediate edge cases (use appropriate registers) + li x8, 1 # Use compressed register for c.srli/c.srai + c.slli x8, 31 # Maximum shift left (c.slli works on any register) + li t0, 0x80000000 + bne x8, t0, error + + li x9, 0x80000000 # Use compressed register + c.srli x9, 31 # Maximum logical shift right (c.srli only on x8-x15) + li t0, 1 + bne x9, t0, error + + li x10, 0x80000000 # Use compressed register + c.srai x10, 31 # Maximum arithmetic shift right (c.srai only on x8-x15) + li t0, -1 # Should sign extend to all 1s + bne x10, t0, error + + # Test shift by 1 (minimum non-zero shift) + li x8, 0x12345678 + c.slli x8, 1 + li t0, 0x2468acf0 + bne x8, t0, error + + li x9, 0x12345678 + c.srli x9, 1 + li t0, 0x091a2b3c + bne x9, t0, error + + li x10, 0x92345678 # Negative number + c.srai x10, 1 + li t0, 0xc91a2b3c # Sign extended + bne x10, t0, error + + # Test boundary between positive and negative immediates + c.li x11, 15 # Positive + c.li x12, 16 # Still positive (but check encoding) + c.li x13, -16 # Negative + c.li x14, -15 # Less negative + + # Verify correct values + li t0, 15 + bne x11, t0, error + li t0, 16 + bne x12, t0, error + li t0, -16 + bne x13, t0, error + li t0, -15 + bne x14, t0, error + + # Success + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b + +.section .data +.align 4 +test_data: + .space 256 # Space for testing various offsets \ No newline at end of file diff --git a/elf-regressions/compressed/edge_cases_registers.s b/elf-regressions/compressed/edge_cases_registers.s new file mode 100644 index 000000000..61a977722 --- /dev/null +++ b/elf-regressions/compressed/edge_cases_registers.s @@ -0,0 +1,194 @@ +# Edge cases for compressed instruction register usage +# Tests register constraints, aliasing, and special register behaviors + +.section .text.init +.global _start + +_start: + # Test compressed register set (x8-x15) vs full register set + + # Initialize all compressed registers + li x8, 0x08080808 + li x9, 0x09090909 + li x10, 0x10101010 + li x11, 0x11111111 + li x12, 0x12121212 + li x13, 0x13131313 + li x14, 0x14141414 + li x15, 0x15151515 + + # Test operations only available on compressed registers + c.and x8, x9 # Both operands must be x8-x15 + c.or x10, x11 + c.xor x12, x13 + c.sub x14, x15 + + # Verify results + li t0, 0x08080808 + li t1, 0x09090909 + and t2, t0, t1 + bne x8, t2, error + + # Test c.andi only works on compressed registers + li x8, 0xffffffff + c.andi x8, 15 # x8 must be in x8-x15 range + li t0, 15 + bne x8, t0, error + + # Test shift operations on compressed registers + li x9, 0x12345678 + c.srli x9, 4 # x9 must be x8-x15 + li t0, 0x01234567 + bne x9, t0, error + + li x10, 0x80000000 + c.srai x10, 1 # x10 must be x8-x15 + li t0, 0xc0000000 + bne x10, t0, error + + # Test c.lw/c.sw compressed register constraints + la x11, test_data # Base register must be x8-x15 + li x12, 0xaabbccdd # Data register must be x8-x15 + + c.sw x12, 0(x11) # Both registers x8-x15 + c.lw x13, 0(x11) # Both registers x8-x15 + bne x12, x13, error + + # Test register x0 special cases + # c.li x0, imm is reserved (should not be used) + # c.addi x0, imm is a hint (NOP-like) + # c.mv x0, rs is reserved + + # Test that x0 always reads as zero even with operations that try to modify it + mv x1, x0 # x1 should be 0 + bne x1, x0, error + + # Test stack pointer (x2/sp) special handling + mv t5, sp # Save original SP + + # c.addi16sp only works with SP + c.addi16sp sp, -16 # Must use SP as target + c.addi16sp sp, 16 # Restore + + # c.lwsp/c.swsp use SP as base + li x1, 0x87654321 + sw x1, 0(sp) # Uses SP as base + lw x2, 0(sp) # Uses SP as base + bne x1, x2, error + + mv sp, t5 # Restore SP + + # Test c.addi4spn with compressed register targets + c.addi4spn x8, sp, 4 # Target must be x8-x15 + c.addi4spn x15, sp, 8 # Test boundary register + + sub t0, x8, sp + li t1, 4 + bne t0, t1, error + + sub t0, x15, sp + li t1, 8 + bne t0, t1, error + + # Test return address register (x1/ra) in c.jalr + la x3, test_function + c.jalr x3 # Should save return address in x1 + + # Verify we returned (x4 should be set by function) + li t0, 0xf00c # Valid hex constant + bne x4, t0, error + + # Test c.jr with various registers + la x5, jump_target1 + c.jr x5 # Can use any register + + li x6, 0xbad # Should not execute + +jump_target1: + la x31, jump_target2 + c.jr x31 # Test with high register number + + li x7, 0xbad + +jump_target2: + # Test register aliasing and name conflicts + # Verify that compressed registers are same as regular registers + li s0, 0x12345678 # s0 is x8 + mv t0, x8 + bne s0, t0, error + + li s1, 0x87654321 # s1 is x9 + mv t0, x9 + bne s1, t0, error + + # Test operations between compressed and non-compressed registers + li x16, 0x11111111 # Non-compressed register + c.mv x8, x16 # Move from non-compressed to compressed allowed + bne x8, x16, error + + c.mv x17, x8 # Move from compressed to non-compressed allowed + bne x17, x8, error + + # Test c.add with register constraints + li x1, 100 # Any register for c.add + li x8, 200 # Compressed register + c.add x1, x8 # First operand any reg, second any reg + li t0, 300 + bne x1, t0, error + + # Test boundary registers + li x7, 0x07070707 # Just before compressed range + li x8, 0x08080808 # First compressed register + li x15, 0x15151515 # Last compressed register + li x16, 0x16161616 # Just after compressed range + + # These should work (c.mv accepts any registers) + c.mv x7, x8 # Move to non-compressed from compressed + c.mv x16, x15 # Move to non-compressed from compressed + c.mv x8, x7 # Move to compressed from non-compressed + c.mv x15, x16 # Move to compressed from non-compressed + + # Verify moves worked + li t0, 0x08080808 + bne x7, t0, error + li t0, 0x15151515 + bne x16, t0, error + li t0, 0x07070707 + bne x8, t0, error + li t0, 0x16161616 + bne x15, t0, error + + # Test special register encodings + # x0 should always be zero regardless of operations + add x0, x1, x2 # Try to modify x0 (should be ignored) + bne x0, zero, error # x0 should still be zero + + # Test that SP operations preserve stack integrity + mv t6, sp + li t0, 0x1000 + sub t1, sp, t0 # Calculate new SP value + + c.addi16sp sp, -64 # Modify SP with compressed instruction + c.addi16sp sp, 64 # Restore SP + bne sp, t6, error # Should be back to original + + # Success + li a0, 0 + li a7, 93 + ecall + +test_function: + li x4, 0xf00c # Mark function called + c.jr x1 # Return using return address + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b + +.section .data +.align 4 +test_data: + .space 64 \ No newline at end of file diff --git a/elf-regressions/compressed/mixed_arithmetic.s b/elf-regressions/compressed/mixed_arithmetic.s new file mode 100644 index 000000000..e701f9b95 --- /dev/null +++ b/elf-regressions/compressed/mixed_arithmetic.s @@ -0,0 +1,135 @@ +# Mixed compressed arithmetic operations test +# Combines various compressed arithmetic and logical instructions + +.section .text.init +.global _start + +_start: + # Test arithmetic expression: ((a + b) - c) * d + li x8, 10 # a + li x9, 5 # b + li x10, 3 # c + li x11, 2 # d + + c.add x8, x9 # x8 = a + b = 15 + c.sub x8, x10 # x8 = (a + b) - c = 12 + # Note: No c.mul, so we'll simulate with shifts/adds + c.slli x8, 1 # x8 = x8 * 2 = 24 + + # Test bit manipulation chain: (x | y) & (~z) + li x12, 0xf0f0f0f0 + li x13, 0x0f0f0f0f + li x14, 0x00ff00ff + + c.or x12, x13 # x12 = 0xf0f0f0f0 | 0x0f0f0f0f = 0xffffffff + c.xor x14, x14 # x14 = x14 ^ x14 = 0 (NOT simulation) + c.addi x14, -1 # x14 = 0 - 1 = 0xffffffff (all 1s) + c.xor x14, x15 # Assume x15 has the value we want to invert + + # Test stack operations simulation + c.addi16sp sp, -32 # Allocate stack space + + # Store some values (use regular instructions since c.swsp/c.lwsp not supported) + li x15, 0x12345678 + sw x15, 0(sp) # Store to stack + sw x8, 4(sp) # Store our calculated result + + # Modify and reload + lw x16, 0(sp) # Load back + addi x16, x16, 1 # Increment + sw x16, 8(sp) # Store modified value + + # Test register move chain with arithmetic + li x17, 100 + c.mv x8, x17 # x8 = 100 (use compressed register) + c.addi x8, 31 # x8 = 131 (c.addi limited to -32 to 31) + c.mv x9, x8 # x9 = 131 + c.sub x9, x8 # x9 = 131 - 131 = 0 (c.sub only on compressed regs) + + # Test shift patterns (shift ops only work on compressed registers for c.srli/c.srai) + li x10, 0x11111111 # Use compressed register x10 + c.slli x10, 1 # x10 = 0x22222222 + c.srli x10, 2 # x10 = 0x08888888 (c.srli only on x8-x15) + c.srai x10, 1 # x10 = 0x04444444 (c.srai only on x8-x15) + + # Test immediate operations chain (use compressed registers) + li x11, 0 + c.li x11, 15 # Load immediate + c.addi x11, 5 # x11 = 20 (c.addi only on compressed regs) + c.andi x11, 0x1f # x11 = 20 & 31 = 20 (c.andi only on x8-x15) + c.srli x11, 2 # x11 = 20 >> 2 = 5 (c.srli only on x8-x15) + + # Test upper immediate with calculations (fix c.lui immediate range) + c.lui x12, 16 # x12 = 16 << 12 (c.lui uses 6-bit signed immediate) + c.srli x12, 4 # x12 shifted right (c.srli only on x8-x15) + c.slli x12, 2 # x12 shifted left + + # Test compressed register operations mixing (all on x8-x15) + li x8, 0xaaaaaaaa + li x9, 0x55555555 + li x10, 0xff00ff00 + li x11, 0x00ff00ff + + c.and x8, x9 # x8 = 0 (no common bits) + c.or x10, x11 # x10 = 0xffffffff (all bits) + c.xor x8, x10 # x8 = 0 ^ 0xffffffff = 0xffffffff + c.sub x10, x8 # x10 = 0xffffffff - 0xffffffff = 0 + + # Test address calculation pattern + la x13, test_data # Use compressed register + c.addi x13, 8 # Point to offset 8 (c.addi only on x8-x15) + c.lw x14, 0(x13) # Load from calculated address (c.lw only on x8-x15) + + # Test loop counter pattern (use compressed registers) + li x15, 5 # Counter (compressed register) + li x8, 0 # Sum (compressed register) + +loop: + c.beqz x15, loop_end # c.beqz only on x8-x15 + c.add x8, x15 # Add counter to sum (c.add with compressed regs) + c.addi x15, -1 # Decrement (c.addi only on x8-x15) + c.j loop + +loop_end: + # x8 should be 5+4+3+2+1 = 15 + + # Test function call pattern + la x9, test_function # Use compressed register + c.jalr x9 # Call function + + # Should return here with x10 modified + + # Restore stack + c.addi16sp sp, 32 + + # Test final calculations + c.add x8, x10 # Combine results (compressed registers) + c.andi x8, 0x1f # Mask result (c.andi only on x8-x15) + + # Verification + li t0, 15 + bne x8, t0, error # Check if sum is correct (before masking) + + # Success + li a0, 0 + li a7, 93 + ecall + +test_function: + # Simple function that modifies x10 + c.li x10, 10 # Use compressed register + c.jr x1 # Return + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b + +.section .data +test_data: + .word 0x11111111 + .word 0x22222222 + .word 0x33333333 + .word 0x44444444 diff --git a/elf-regressions/compressed/mixed_control_flow.s b/elf-regressions/compressed/mixed_control_flow.s new file mode 100644 index 000000000..a2303dce1 --- /dev/null +++ b/elf-regressions/compressed/mixed_control_flow.s @@ -0,0 +1,260 @@ +# Mixed compressed control flow operations test +# Tests combinations of branches, jumps, and function calls + +.section .text.init +.global _start + +_start: + # Test nested conditional branches + li x8, 10 + li x9, 0 + li x10, 5 + + c.bnez x8, branch1 # Should branch (x8 = 10) + li x1, 0xbad + c.j error + +branch1: + c.beqz x9, branch2 # Should branch (x9 = 0) + li x2, 0xbad + c.j error + +branch2: + c.bnez x10, branch3 # Should branch (x10 = 5) + li x3, 0xbad + c.j error + +branch3: + li x1, 0x100 + li x2, 0x200 + li x3, 0x300 + + # Test function call with conditional returns + li x8, 1 # Test condition + la x11, conditional_func + c.jalr x11 + + li x4, 0x400 # Mark return point + + # Test loop with compressed branches + li x12, 5 # Loop counter + li x13, 0 # Accumulator + +loop1: + c.beqz x12, loop1_end + c.add x13, x12 # Add counter to accumulator + c.addi x12, -1 # Decrement counter + c.j loop1 + +loop1_end: + # x13 should be 15 (5+4+3+2+1) + + # Test nested loops + li x14, 3 # Outer counter + li x15, 0 # Result accumulator + +outer_loop: + c.beqz x14, outer_end + + # Inner loop (use compressed registers) + li x8, 2 # Inner counter (use compressed register) + +inner_loop: + c.beqz x8, inner_end # c.beqz only works on x8-x15 + c.add x15, x14 # Add outer counter to result + c.addi x8, -1 # Decrement inner counter (c.addi only on x8-x15) + c.j inner_loop + +inner_end: + c.addi x14, -1 # Decrement outer counter + c.j outer_loop + +outer_end: + # x15 should be 3*2 + 2*2 + 1*2 = 12 + + # Test switch-like construct using jumps + li x9, 2 # Switch value (use compressed register) + + # Switch implementation + c.beqz x9, case0 + li t0, 1 + beq x9, t0, case1 + li t0, 2 + beq x9, t0, case2 + c.j default_case + +case0: + li x18, 0xc0 + c.j switch_end + +case1: + li x18, 0xc1 + c.j switch_end + +case2: + li x18, 0xc2 + c.j switch_end + +default_case: + li x18, 0xcf + +switch_end: + # x18 should be 0xc2 + + # Test recursive-like pattern with return address management + li x19, 3 # Recursion depth + la x20, recursive_func + c.jalr x20 + + li x5, 0x500 # Mark after recursion + + # Test exception-like control flow + la x21, protected_code + c.jalr x21 # "Call" protected code + + # Exception handler simulation + li x6, 0x600 + c.j continue_after_exception + +exception_handler: + li x6, 0x6ee # Mark exception handled + c.jr x1 # "Return" from exception + +protected_code: + # Simulate some code that might "throw" + li x14, 1 # Use compressed register + c.bnez x14, simulate_exception + + # Normal path + li x23, 0x700 + c.jr x1 + +simulate_exception: + # Jump to exception handler + la x24, exception_handler + c.jr x24 + +continue_after_exception: + # Test computed jump simulation + li x13, 1 # Jump table index (use compressed register) + + # Simple jump table using branches + c.beqz x13, jump_target0 + li t0, 1 + beq x13, t0, jump_target1 + li t0, 2 + beq x13, t0, jump_target2 + c.j jump_target_default + +jump_target0: + li x26, 0x100 # Valid hex constant (was 0xj0) + c.j jump_table_end + +jump_target1: + li x26, 0x101 # Valid hex constant (was 0xj1) + c.j jump_table_end + +jump_target2: + li x26, 0x102 # Valid hex constant (was 0xj2) + c.j jump_table_end + +jump_target_default: + li x26, 0x10f # Valid hex constant (was 0xjf) + +jump_table_end: + # x26 should be 0x101 + + # Test early return pattern + li x27, 0 + la x28, early_return_func + c.jalr x28 + + li x7, 0x700 + + # Verification + c.j verify_results + +conditional_func: + # Function with conditional execution + c.bnez x8, cond_true + li x29, 0xfa15e # Valid hex constant (was 0xfa1se) + c.jr x1 + +cond_true: + li x29, 0x701e # Valid hex constant (was 0x7r0e) + c.jr x1 + +recursive_func: + # Simulate recursive function (use compressed register) + c.beqz x10, recursive_base # x19 -> x10 (compressed register) + + # Recursive case + c.addi x10, -1 # Decrement depth (use compressed register) + c.add x11, x10 # Add current depth to result (compressed registers) + c.jr x1 # Return (simplified recursion) + +recursive_base: + # Base case + li x11, 0x800 # Base result + c.jr x1 + +early_return_func: + # Function with early return + c.bnez x12, early_ret # Use compressed register x12 + + # Normal execution + li x31, 0x900 + c.jr x1 + +early_ret: + # Early return path + li x31, 0x901 + c.jr x1 + +verify_results: + # Verify test results + li t0, 0x100 + bne x1, t0, error + + li t0, 0x200 + bne x2, t0, error + + li t0, 0x300 + bne x3, t0, error + + li t0, 0x400 + bne x4, t0, error + + li t0, 0x500 + bne x5, t0, error + + li t0, 0x6ee + bne x6, t0, error + + li t0, 0x700 + bne x7, t0, error + + # Check computed values + li t0, 15 + bne x13, t0, error # Loop sum + + li t0, 12 + bne x15, t0, error # Nested loop result + + li t0, 0xc2 + bne x18, t0, error # Switch result + + li t0, 0x101 + bne x26, t0, error # Jump table result + + # Success + li a0, 0 + li a7, 93 + ecall + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b \ No newline at end of file diff --git a/elf-regressions/compressed/mixed_stack_ops.s b/elf-regressions/compressed/mixed_stack_ops.s new file mode 100644 index 000000000..a2cf3b729 --- /dev/null +++ b/elf-regressions/compressed/mixed_stack_ops.s @@ -0,0 +1,172 @@ +# Mixed compressed stack and memory operations test +# Tests combinations of stack pointer operations, loads, stores, and memory access + +.section .text.init +.global _start + +_start: + # Save original stack pointer + mv t6, sp + + # Test nested stack frame allocation + c.addi16sp sp, -64 # Allocate frame 1 + sw ra, 60(sp) # Save return address + sw s0, 56(sp) # Save frame pointer + + # Create local variables on stack + li x8, 100 # c.li only supports -32 to 31, use regular li + li x9, 200 # c.li only supports -32 to 31, use regular li + sw x8, 0(sp) # Local var 1 + sw x9, 4(sp) # Local var 2 + + # Test compressed register addressing with stack + c.addi4spn x10, sp, 8 # x10 = sp + 8 + c.addi4spn x11, sp, 12 # x11 = sp + 12 + + # Store data using compressed register addressing + li x12, 0x1234 + li x13, 0x5678 + c.sw x12, 0(x10) # Store at sp+8 + c.sw x13, 0(x11) # Store at sp+12 + + # Test mixed load/store with calculations + lw x14, 0(sp) # Load local var 1 (100) + lw x15, 4(sp) # Load local var 2 (200) + c.add x14, x15 # x14 = 100 + 200 = 300 (compressed regs) + sw x14, 8(sp) # Store result + + # Test compressed memory operations with offsets + c.lw x8, 0(x10) # Load from sp+8 (should be 0x1234) + c.lw x9, 0(x11) # Load from sp+12 (should be 0x5678) + c.add x8, x9 # Combine loaded values (compressed regs) + sw x8, 12(sp) # Store combined result + + # Test stack-relative addressing patterns + la x16, stack_data + c.addi4spn x10, sp, 16 # Point to sp+16 + + # Copy data from global to stack + lw x8, 0(x16) + lw x9, 4(x16) + lw x11, 8(x16) + lw x12, 12(x16) + + c.sw x8, 0(x10) # Copy to stack + c.sw x9, 4(x10) + c.sw x11, 8(x10) + c.sw x12, 12(x10) + + # Test array-like access using compressed addressing + c.addi4spn x13, sp, 16 # Base address + + # Access array elements + c.lw x14, 0(x13) # array[0] + c.lw x15, 4(x13) # array[1] + c.add x14, x15 # Sum first two elements + + c.lw x15, 8(x13) # array[2] + c.add x14, x15 # Add third element + + c.lw x15, 12(x13) # array[3] + c.add x14, x15 # Add fourth element + + # Store array sum + sw x14, 16(sp) + + # Test function call with stack operations + c.addi16sp sp, -32 # Allocate more space for function call + + # Pass parameters via stack + li x8, 42 # c.li supports this but use li for consistency + li x9, 58 # Out of c.li range, use regular li + sw x8, 0(sp) # arg1 + sw x9, 4(sp) # arg2 + + # Call function + la x17, stack_function + c.jalr x17 + + # Function returns result in x10 + sw x10, 8(sp) # Store function result + + # Clean up function call stack + c.addi16sp sp, 32 + + # Test stack array manipulation + c.addi4spn x11, sp, 20 # Point to array area + + # Initialize array with sequence + c.li x8, 1 + c.sw x8, 0(x11) + c.li x8, 2 + c.sw x8, 4(x11) + c.li x8, 3 + c.sw x8, 8(x11) + c.li x8, 4 + c.sw x8, 12(x11) + + # Process array in reverse + c.lw x12, 12(x11) # Load element 3 (value 4) + c.lw x13, 8(x11) # Load element 2 (value 3) + c.lw x14, 4(x11) # Load element 1 (value 2) + c.lw x15, 0(x11) # Load element 0 (value 1) + + # Calculate: 4*1000 + 3*100 + 2*10 + 1*1 = 4321 + c.slli x12, 2 # x12 = 4 << 2 = 16 + li t0, 1000 + mul x12, x12, t0 # x12 = 4 * 1000 = 4000 (but we'll use simpler calc) + + # Simplified calculation for compressed ops only + c.slli x12, 10 # Approximate *1000 with shifts + c.slli x13, 6 # Approximate *100 + c.slli x14, 3 # Approximate *10 + # x15 *= 1 (no change) + + c.add x12, x13 + c.add x12, x14 + c.add x12, x15 + + # Store final result + sw x12, 24(sp) + + # Test stack unwinding + lw s0, 56(sp) # Restore frame pointer + lw ra, 60(sp) # Restore return address + c.addi16sp sp, 64 # Deallocate frame + + # Restore original stack pointer + mv sp, t6 + + # Verification (simple check that operations completed) + # Skip potentially faulting negative offset instruction + + # Success + li a0, 0 + li a7, 93 + ecall + +stack_function: + # Load arguments from stack + lw x8, 0(sp) # arg1 = 42 + lw x9, 4(sp) # arg2 = 58 + + # Calculate result + c.add x10, x8 # x10 = 42 + c.add x10, x9 # x10 = 42 + 58 = 100 + + # Return + c.jr x1 + +error: + li a0, 1 + li a7, 93 + ecall + +1: j 1b + +.section .data +stack_data: + .word 0x10101010 + .word 0x20202020 + .word 0x30303030 + .word 0x40404040 \ No newline at end of file From e425712b98e8d0000ce73ce9d44e9b5a2418ce6a Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Fri, 29 Aug 2025 21:54:58 +0100 Subject: [PATCH 002/782] 16 bit zero --- elf-regressions/compressed/test_unimp.s | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 elf-regressions/compressed/test_unimp.s diff --git a/elf-regressions/compressed/test_unimp.s b/elf-regressions/compressed/test_unimp.s new file mode 100644 index 000000000..5329f2947 --- /dev/null +++ b/elf-regressions/compressed/test_unimp.s @@ -0,0 +1,12 @@ +# Test case to reproduce 16 bit zeroes + +.section .text.init +.global _start + +_start: + unimp # Assembler generates 0x0000 (in RVC mode) + + li a7, 93 + ecall + +1: j 1b # Infinite loop \ No newline at end of file From e4cb9d70c572c86c2c0e8ac96aa6d94ea4cfbdb6 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Thu, 4 Sep 2025 19:27:06 +0100 Subject: [PATCH 003/782] 8 byte alignment --- elf-regressions/custom_entry/custom.ld | 1 + elf-regressions/fips_markers/fips.ld | 4 ++++ elf-regressions/jalr_custom/jalr_custom.ld | 1 + elf-regressions/jalr_gap/jalr_gap.ld | 3 +++ 4 files changed, 9 insertions(+) diff --git a/elf-regressions/custom_entry/custom.ld b/elf-regressions/custom_entry/custom.ld index 051952727..c11a0245d 100644 --- a/elf-regressions/custom_entry/custom.ld +++ b/elf-regressions/custom_entry/custom.ld @@ -23,6 +23,7 @@ SECTIONS *(.text) } > ROM + . = ALIGN(8); .rodata : { *(.rodata) } > ROM diff --git a/elf-regressions/fips_markers/fips.ld b/elf-regressions/fips_markers/fips.ld index 78084ffd6..bb4d4dae9 100644 --- a/elf-regressions/fips_markers/fips.ld +++ b/elf-regressions/fips_markers/fips.ld @@ -12,12 +12,16 @@ SECTIONS *(.fipsstart) } + . = ALIGN(8); + /* Main text section with the actual code */ .text : { *(.text.start) /* Entry point code */ *(.text) /* Other text */ } + . = ALIGN(8); + /* Create a separate section for FIPS end marker */ .fipsend : { PROVIDE(_fipsend = .); diff --git a/elf-regressions/jalr_custom/jalr_custom.ld b/elf-regressions/jalr_custom/jalr_custom.ld index 4f3cd2fcb..ae8348749 100644 --- a/elf-regressions/jalr_custom/jalr_custom.ld +++ b/elf-regressions/jalr_custom/jalr_custom.ld @@ -24,6 +24,7 @@ SECTIONS *(.text) } > ROM + . = ALIGN(8); .rodata : { *(.rodata) } > ROM diff --git a/elf-regressions/jalr_gap/jalr_gap.ld b/elf-regressions/jalr_gap/jalr_gap.ld index 5c1a689c9..9a3e71a08 100644 --- a/elf-regressions/jalr_gap/jalr_gap.ld +++ b/elf-regressions/jalr_gap/jalr_gap.ld @@ -10,14 +10,17 @@ SECTIONS *(.text) } + . = ALIGN(8); .rodata : { *(.rodata) } + . = ALIGN(8); .data : { *(.data) } + . = ALIGN(8); .bss : { *(.bss) } From 7c7ea50e82f45d024361e0e557044ebfbeb3d66f Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Sat, 18 Oct 2025 13:51:59 +0200 Subject: [PATCH 004/782] feature op_with_steps that allow blocks more than 2^32 steps. --- Cargo.lock | 36 ++- Cargo.toml | 32 +- common/src/bus/data_bus_operation.rs | 286 +++++++++++------- core/src/riscv2zisk_context.rs | 4 +- core/src/zisk_definitions.rs | 4 +- core/src/zisk_inst.rs | 11 +- core/src/zisk_inst_builder.rs | 6 +- emulator-asm/src/main.c | 26 +- emulator/src/emu.rs | 2 +- emulator/src/emu_options.rs | 2 +- executor/src/executor.rs | 2 +- lib-c/c/src/ffiasm/bls12_381.asm | 3 + lib-c/c/src/ffiasm/bls12_381_384.asm | 3 + lib-c/c/src/ffiasm/fec.asm | 3 + lib-c/c/src/ffiasm/fnec.asm | 3 + lib-c/c/src/ffiasm/fq.asm | 3 + lib-c/c/src/ffiasm/fr.asm | 3 + lib-c/c/src/ffiasm/nsecp256r1.asm | 3 + lib-c/c/src/ffiasm/psecp256r1.asm | 3 + pil/src/pil_helpers/traces.rs | 6 +- pil/zisk.pil | 2 +- precompiles/arith_eq/pil/arith_eq.pil | 2 +- .../arith_eq/src/arith_eq_bus_device.rs | 6 +- precompiles/arith_eq/src/arith_eq_input.rs | 92 +++--- .../arith_eq/src/mem_inputs/arith256.rs | 6 +- .../arith_eq/src/mem_inputs/arith256_mod.rs | 8 +- .../src/mem_inputs/bn254_complex_add.rs | 4 +- .../src/mem_inputs/bn254_complex_mul.rs | 4 +- .../src/mem_inputs/bn254_complex_sub.rs | 4 +- .../src/mem_inputs/bn254_curve_add.rs | 4 +- .../src/mem_inputs/bn254_curve_dbl.rs | 3 +- .../src/mem_inputs/generate_mem_inputs.rs | 10 +- .../arith_eq/src/mem_inputs/secp256k1_add.rs | 4 +- .../arith_eq/src/mem_inputs/secp256k1_dbl.rs | 2 +- precompiles/arith_eq_384/pil/arith_eq_384.pil | 2 +- .../src/arith_eq_384_bus_device.rs | 6 +- .../arith_eq_384/src/arith_eq_384_input.rs | 62 ++-- .../src/mem_inputs/arith384_mod.rs | 2 +- .../src/mem_inputs/bls12_381_complex_add.rs | 2 +- .../src/mem_inputs/bls12_381_complex_mul.rs | 2 +- .../src/mem_inputs/bls12_381_complex_sub.rs | 2 +- .../src/mem_inputs/bls12_381_curve_add.rs | 2 +- .../src/mem_inputs/bls12_381_curve_dbl.rs | 2 +- .../src/mem_inputs/generate_mem_inputs.rs | 10 +- precompiles/big_int/pil/big_int_add.pil | 2 +- precompiles/big_int/src/add256_bus_device.rs | 4 +- precompiles/big_int/src/add256_constants.rs | 4 +- .../big_int/src/add256_gen_mem_inputs.rs | 14 +- precompiles/big_int/src/add256_input.rs | 12 +- precompiles/dma/Cargo.toml | 33 ++ precompiles/dma/pil/dma.pil | 127 ++++++++ precompiles/dma/src/add256.rs | 169 +++++++++++ precompiles/dma/src/add256_bus_device.rs | 148 +++++++++ precompiles/dma/src/add256_constants.rs | 12 + precompiles/dma/src/add256_gen_mem_inputs.rs | 111 +++++++ precompiles/dma/src/add256_input.rs | 33 ++ precompiles/dma/src/add256_instance.rs | 210 +++++++++++++ precompiles/dma/src/add256_manager.rs | 92 ++++++ precompiles/dma/src/add256_planner.rs | 105 +++++++ precompiles/dma/src/lib.rs | 17 ++ precompiles/keccakf/pil/keccakf.pil | 2 +- precompiles/keccakf/src/keccakf_bus_device.rs | 6 +- .../keccakf/src/keccakf_gen_mem_inputs.rs | 6 +- precompiles/keccakf/src/keccakf_input.rs | 4 +- precompiles/sha256f/pil/sha256f.pil | 2 +- precompiles/sha256f/src/sha256f_bus_device.rs | 6 +- .../sha256f/src/sha256f_gen_mem_inputs.rs | 14 +- precompiles/sha256f/src/sha256f_input.rs | 10 +- state-machines/arith/pil/arith.pil | 4 +- state-machines/arith/pil/arith_mul64.pil | 2 +- state-machines/binary/pil/binary.pil | 2 +- state-machines/binary/pil/binary_add.pil | 2 +- .../binary/pil/binary_extension.pil | 2 +- .../frequent-ops/pil/frequent_ops.pil | 5 +- state-machines/main/pil/main.pil | 15 +- state-machines/mem-cpp/cpp/mem_config.hpp | 2 +- 76 files changed, 1520 insertions(+), 341 deletions(-) create mode 100644 precompiles/dma/Cargo.toml create mode 100644 precompiles/dma/pil/dma.pil create mode 100644 precompiles/dma/src/add256.rs create mode 100644 precompiles/dma/src/add256_bus_device.rs create mode 100644 precompiles/dma/src/add256_constants.rs create mode 100644 precompiles/dma/src/add256_gen_mem_inputs.rs create mode 100644 precompiles/dma/src/add256_input.rs create mode 100644 precompiles/dma/src/add256_instance.rs create mode 100644 precompiles/dma/src/add256_manager.rs create mode 100644 precompiles/dma/src/add256_planner.rs create mode 100644 precompiles/dma/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index fbf32fce2..a5896b33b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -993,7 +993,6 @@ dependencies = [ [[package]] name = "curves" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "fields", "num-bigint", @@ -1295,6 +1294,7 @@ dependencies = [ "pil-std-lib", "precomp-arith-eq", "precomp-arith-eq-384", + "precomp-big-int", "precomp-keccakf", "precomp-sha256f", "proofman", @@ -1335,7 +1335,6 @@ dependencies = [ [[package]] name = "fields" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "num-bigint", "paste", @@ -2646,7 +2645,6 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "colored", "fields", @@ -2877,6 +2875,28 @@ dependencies = [ "zisk-pil", ] +[[package]] +name = "precomp-big-int" +version = "0.13.0" +dependencies = [ + "fields", + "generic-array", + "lib-c", + "mem-common", + "pil-std-lib", + "precompiles-common", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sm-mem", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + [[package]] name = "precomp-keccakf" version = "0.13.0" @@ -2980,7 +3000,6 @@ dependencies = [ [[package]] name = "proofman" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "blake3", "borsh", @@ -3013,7 +3032,6 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "borsh", "crossbeam-channel", @@ -3038,7 +3056,6 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "fields", "proofman-common", @@ -3050,7 +3067,6 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "proc-macro2", "quote", @@ -3061,7 +3077,6 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "crossbeam-channel", "tracing", @@ -3070,7 +3085,6 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "colored", "fields", @@ -3081,7 +3095,6 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "bytemuck", "fields", @@ -4628,7 +4641,6 @@ dependencies = [ [[package]] name = "transcript" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "proofman-starks-lib-c", ] @@ -5321,7 +5333,6 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "witness" version = "0.12.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.12.0#b8e874def47dfeabe5e684ad6a78b0205218b1c2" dependencies = [ "colored", "fields", @@ -5635,6 +5646,7 @@ dependencies = [ "pil-std-lib", "precomp-arith-eq", "precomp-arith-eq-384", + "precomp-big-int", "precomp-keccakf", "precomp-sha256f", "proofman", diff --git a/Cargo.toml b/Cargo.toml index 3bb941912..a62630067 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,23 +98,23 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +# proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +# proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +# proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +# proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +# proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +# pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +# witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +# fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } # Proofman Local development -# proofman = { path = "../pil2-proofman/proofman" } -# proofman-common = { path = "../pil2-proofman/common" } -# proofman-macros = { path = "../pil2-proofman/macros" } -# proofman-verifier = { path = "../pil2-proofman/verifier" } -# proofman-util = { path = "../pil2-proofman/util" } -# pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } -# witness = { path = "../pil2-proofman/witness" } -# fields = { path = "../pil2-proofman/fields" } +proofman = { path = "../pil2-proofman/proofman" } +proofman-common = { path = "../pil2-proofman/common" } +proofman-macros = { path = "../pil2-proofman/macros" } +proofman-verifier = { path = "../pil2-proofman/verifier" } +proofman-util = { path = "../pil2-proofman/util" } +pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } +witness = { path = "../pil2-proofman/witness" } +fields = { path = "../pil2-proofman/fields" } # External dependencies rayon = "1.10" diff --git a/common/src/bus/data_bus_operation.rs b/common/src/bus/data_bus_operation.rs index 0b8b97bd1..ec2a90b72 100644 --- a/common/src/bus/data_bus_operation.rs +++ b/common/src/bus/data_bus_operation.rs @@ -12,6 +12,7 @@ pub const OPERATION_BUS_ID: BusId = BusId(0); /// The size of the operation data payload. pub const OPERATION_BUS_DATA_SIZE: usize = 4; // op,op_type,a,b +pub const OPERATION_PRECOMPILED_BUS_DATA_SIZE: usize = 5; // op,op_type,a,b, step // worst case: // arith_256: 3 x 256 + 2 addr = 3 * 4 + 2 = 14 @@ -33,39 +34,39 @@ const POINT_384_BITS_SIZE: usize = 2 * DATA_384_BITS_SIZE; const COMPLEX_OVER_384_BITS_SIZE: usize = 2 * DATA_384_BITS_SIZE; // use OPERATION_BUS_DATA_SIZE because a = step, b = addr -pub const OPERATION_BUS_KECCAKF_DATA_SIZE: usize = OPERATION_BUS_DATA_SIZE + 25; +pub const OPERATION_BUS_KECCAKF_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 25; pub const OPERATION_BUS_SHA256F_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 3 * DATA_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 3 * DATA_256_BITS_SIZE; pub const OPERATION_BUS_ARITH_256_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 3 * DATA_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 3 * DATA_256_BITS_SIZE; pub const OPERATION_BUS_ARITH_256_MOD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 4 * DATA_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 4 * DATA_256_BITS_SIZE; pub const OPERATION_BUS_SECP256K1_ADD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_256_BITS_SIZE; pub const OPERATION_BUS_SECP256K1_DBL_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + POINT_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + POINT_256_BITS_SIZE; pub const OPERATION_BUS_BN254_CURVE_ADD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_256_BITS_SIZE; pub const OPERATION_BUS_BN254_CURVE_DBL_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + POINT_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + POINT_256_BITS_SIZE; pub const OPERATION_BUS_BN254_COMPLEX_ADD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; pub const OPERATION_BUS_BN254_COMPLEX_SUB_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; pub const OPERATION_BUS_BN254_COMPLEX_MUL_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; pub const OPERATION_BUS_ARITH_384_MOD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 4 * DATA_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 4 * DATA_384_BITS_SIZE; pub const OPERATION_BUS_BLS12_381_CURVE_ADD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_384_BITS_SIZE; pub const OPERATION_BUS_BLS12_381_CURVE_DBL_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + POINT_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + POINT_384_BITS_SIZE; pub const OPERATION_BUS_BLS12_381_COMPLEX_ADD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; pub const OPERATION_BUS_BLS12_381_COMPLEX_SUB_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; pub const OPERATION_BUS_BLS12_381_COMPLEX_MUL_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; // bus_data_size + 4 params (&a, &b, cin, &c, a, b) pub const OPERATION_BUS_ADD_256_DATA_SIZE: usize = @@ -86,6 +87,9 @@ pub const A: usize = 2; /// Index of the `b` value in the operation data payload. pub const B: usize = 3; +/// Index of the `STEP` value in the operation data payload (only for precompiled operations). +pub const STEP: usize = 4; + /// Type alias for operation data payload. pub type OperationData = [D; OPERATION_BUS_DATA_SIZE]; @@ -302,21 +306,26 @@ impl OperationBusData { let b = if inst.m32 { ctx.b & 0xffff_ffff } else { ctx.b }; let op = inst.op as u64; let op_type = inst.op_type as u64; + let step = ctx.step as u64; match inst.op_type { ZiskOperationType::Keccak => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationKeccakData(data) } ZiskOperationType::Sha256 => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationSha256Data(data) } @@ -325,72 +334,90 @@ impl OperationBusData { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationArith256Data(data) } ARITH256_MOD_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationArith256ModData(data) } SECP256K1_ADD_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationSecp256k1AddData(data) } SECP256K1_DBL_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationSecp256k1DblData(data) } BN254_CURVE_ADD_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254CurveAddData(data) } BN254_CURVE_DBL_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254CurveDblData(data) } BN254_COMPLEX_ADD_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254ComplexAddData(data) } BN254_COMPLEX_SUB_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254ComplexSubData(data) } BN254_COMPLEX_MUL_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254ComplexMulData(data) } _ => ExtOperationData::OperationData([op, op_type, a, b]), @@ -401,24 +428,30 @@ impl OperationBusData { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationArith384ModData(data) } BLS12_381_CURVE_ADD_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381CurveAddData(data) } BLS12_381_CURVE_DBL_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381CurveDblData(data) } BLS12_381_COMPLEX_ADD_OP => { @@ -426,8 +459,10 @@ impl OperationBusData { uninit_array::() .assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381ComplexAddData(data) } BLS12_381_COMPLEX_SUB_OP => { @@ -435,8 +470,10 @@ impl OperationBusData { uninit_array::() .assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381ComplexSubData(data) } BLS12_381_COMPLEX_MUL_OP => { @@ -444,8 +481,10 @@ impl OperationBusData { uninit_array::() .assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381ComplexMulData(data) } _ => ExtOperationData::OperationData([op, op_type, a, b]), @@ -454,8 +493,10 @@ impl OperationBusData { ADD256_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationAdd256Data(data) } _ => ExtOperationData::OperationData([op, op_type, a, b]), @@ -475,85 +516,106 @@ impl OperationBusData { let b = if inst.m32 { ctx.b & 0xffff_ffff } else { ctx.b }; let op = inst.op as u64; let op_type = inst.op_type as u64; + let step = ctx.step as u64; match inst.op_type { ZiskOperationType::Keccak => { debug_assert_eq!(ctx.precompiled.input_data.len(), 25); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..OPERATION_BUS_KECCAKF_DATA_SIZE] + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..OPERATION_BUS_KECCAKF_DATA_SIZE] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..OPERATION_BUS_KECCAKF_DATA_SIZE] } ZiskOperationType::Sha256 => { debug_assert_eq!(ctx.precompiled.input_data.len(), 14); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..OPERATION_BUS_SHA256F_DATA_SIZE] + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..OPERATION_BUS_SHA256F_DATA_SIZE] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..OPERATION_BUS_SHA256F_DATA_SIZE] } ZiskOperationType::ArithEq => match inst.op { ARITH256_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } ARITH256_MOD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } SECP256K1_ADD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } SECP256K1_DBL_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BN254_CURVE_ADD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BN254_CURVE_DBL_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BN254_COMPLEX_ADD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BN254_COMPLEX_SUB_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BN254_COMPLEX_MUL_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } @@ -565,44 +627,56 @@ impl OperationBusData { ZiskOperationType::ArithEq384 => match inst.op { ARITH384_MOD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BLS12_381_CURVE_ADD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BLS12_381_CURVE_DBL_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BLS12_381_COMPLEX_ADD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BLS12_381_COMPLEX_SUB_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BLS12_381_COMPLEX_MUL_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } @@ -613,9 +687,11 @@ impl OperationBusData { }, ZiskOperationType::BigInt => match inst.op { ADD256_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 87c9d2f8e..c4f14145c 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -1072,7 +1072,7 @@ impl Riscv2ZiskContext<'_> { zib.src_b("reg", i.rs1 as u64, false); zib.j(4, 4); if (CSR_PRECOMPILED_ADDR_START..=CSR_PRECOMPILED_ADDR_END).contains(&i.csr) { - zib.src_a("step", 0, false); + zib.src_a("imm", 0, false); let precompiled = CSR_PRECOMPILED[(i.csr - CSR_PRECOMPILED_ADDR_START) as usize]; zib.op(precompiled).unwrap(); zib.verbose(precompiled); @@ -1116,7 +1116,7 @@ impl Riscv2ZiskContext<'_> { self.s += 4; } else if i.csr == CSR_PRECOMPILED_ADD256 { let mut zib = ZiskInstBuilder::new(self.s); - zib.src_a("step", 0, false); + zib.src_a("imm", 0, false); zib.src_b("reg", i.rs1 as u64, false); zib.op("add256").unwrap(); zib.verbose("add256"); diff --git a/core/src/zisk_definitions.rs b/core/src/zisk_definitions.rs index 02bf67b3a..15e523115 100644 --- a/core/src/zisk_definitions.rs +++ b/core/src/zisk_definitions.rs @@ -1,7 +1,7 @@ //! This module contains constant definitions used by other modules and crates. -pub const DEFAULT_MAX_STEPS: u64 = 0xffffffff; -pub const DEFAULT_MAX_STEPS_STR: &str = "4294967295"; // 2^32 - 1 +pub const DEFAULT_MAX_STEPS: u64 = 0xf_ffff_ffff; +pub const DEFAULT_MAX_STEPS_STR: &str = "68719476735"; // 2^36 - 1 pub const CHUNK_SIZE_BITS: usize = 18; pub const CHUNK_SIZE: u64 = 1 << CHUNK_SIZE_BITS; diff --git a/core/src/zisk_inst.rs b/core/src/zisk_inst.rs index 9625c9e72..d8ca24cc1 100644 --- a/core/src/zisk_inst.rs +++ b/core/src/zisk_inst.rs @@ -121,6 +121,7 @@ pub struct ZiskInst { pub store: u64, pub store_offset: i64, pub set_pc: bool, + pub op_with_step: bool, // #[cfg(feature = "sp")] // pub set_sp: bool, pub ind_width: u64, @@ -157,6 +158,7 @@ impl Default for ZiskInst { store: 0, store_offset: 0, set_pc: false, + op_with_step: false, // #[cfg(feature = "sp")] // set_sp: false, ind_width: 0, @@ -230,6 +232,9 @@ impl ZiskInst { if self.set_pc { s += &format!(" set_pc={}", self.set_pc); } + if self.op_with_step { + s += &format!(" op_with_step={}", self.op_with_step); + } if self.jmp_offset1 != 0 { s += &format!(" jmp_offset1={}", self.jmp_offset1); } @@ -262,7 +267,7 @@ impl ZiskInst { let flags: u64 = 1 | (((self.a_src == SRC_IMM) as u64) << 1) | (((self.a_src == SRC_MEM) as u64) << 2) - | (((self.a_src == SRC_STEP) as u64) << 3) + | ((self.op_with_step as u64) << 3) | (((self.b_src == SRC_IMM) as u64) << 4) | (((self.b_src == SRC_MEM) as u64) << 5) | ((self.is_external_op as u64) << 6) @@ -330,7 +335,7 @@ impl ZiskInst { // a_src_sp: F::from_bool(inst.a_src == SRC_SP), // #[cfg(feature = "sp")] // a_use_sp_imm1: F::from_u64(inst.a_use_sp_imm1), - a_src_step: F::from_bool(self.a_src == SRC_STEP), + op_with_step: F::from_bool(self.op_with_step), b_src_imm: F::from_bool(self.b_src == SRC_IMM), b_src_mem: F::from_bool(self.b_src == SRC_MEM), b_src_reg: F::from_bool(self.b_src == SRC_REG), @@ -399,7 +404,7 @@ impl ZiskInst { // a_src_sp: F::from_bool(inst.a_src == SRC_SP), // #[cfg(feature = "sp")] // a_use_sp_imm1: F::from_u64(inst.a_use_sp_imm1), - trace.a_src_step = F::from_bool(self.a_src == SRC_STEP); + trace.op_with_step = F::from_bool(self.op_with_step); trace.b_src_imm = F::from_bool(self.b_src == SRC_IMM); trace.b_src_mem = F::from_bool(self.b_src == SRC_MEM); trace.b_src_reg = F::from_bool(self.b_src == SRC_REG); diff --git a/core/src/zisk_inst_builder.rs b/core/src/zisk_inst_builder.rs index 9d037b53f..7747faecf 100644 --- a/core/src/zisk_inst_builder.rs +++ b/core/src/zisk_inst_builder.rs @@ -5,7 +5,7 @@ use crate::{ zisk_ops::{InvalidNameError, OpType, ZiskOp}, ZiskInst, REGS_IN_MAIN_FROM, REGS_IN_MAIN_TO, REG_FIRST, SRC_C, SRC_IMM, SRC_IND, SRC_MEM, - SRC_REG, SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, + SRC_REG, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, }; // #[cfg(feature = "sp")] @@ -36,7 +36,7 @@ impl ZiskInstBuilder { "lastc" => SRC_C, // #[cfg(feature = "sp")] // "sp" => SRC_SP, - "step" => SRC_STEP, + // "step" => SRC_STEP, _ => panic!("ZiskInstBuilder::a_src() called with invalid src={src}"), } } @@ -199,6 +199,8 @@ impl ZiskInstBuilder { self.i.func = op.get_call_function(); self.i.op_type = op.op_type().into(); self.i.input_size = op.input_size(); + // assume that input_size > 0 implies a precompiled, and precompiled uses step on operations + self.i.op_with_step = op.input_size() > 0; Ok(()) } diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 5fefdf2d0..1e0e000f2 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -42,11 +42,13 @@ uint64_t get_gen_method(void); #define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) #define TRACE_ADDR (uint64_t)0xc0000000 -#define INITIAL_TRACE_SIZE (uint64_t)0x100000000 // 4GB +#define INITIAL_TRACE_SIZE (uint64_t)0x180000000 // 4GB #define REG_ADDR (uint64_t)0x70000000 #define REG_SIZE (uint64_t)0x1000 // 4kB +#define MAX_STEPS (1ULL << 36) + uint8_t * pInput = (uint8_t *)INPUT_ADDR; uint8_t * pInputLast = (uint8_t *)(INPUT_ADDR + 10440504 - 64); uint8_t * pRam = (uint8_t *)RAM_ADDR; @@ -1630,7 +1632,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_MT_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = 0; request[4] = 0; @@ -1691,7 +1693,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_RH_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 0; request[3] = 0; request[4] = 0; @@ -1752,7 +1754,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_MO_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = 0; request[4] = 0; @@ -1813,7 +1815,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_MA_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = 0; request[4] = 0; @@ -1876,7 +1878,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_CM_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = chunk_player_address; request[4] = 0; @@ -1950,7 +1952,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_CM_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = chunk_player_address; request[4] = 0; @@ -2011,7 +2013,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_FA_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = 0; request[4] = 0; @@ -2072,7 +2074,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_MR_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = 0; request[4] = 0; @@ -2135,7 +2137,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_CA_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = chunk_player_address; request[4] = 0; @@ -2209,7 +2211,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_CA_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = chunk_player_address; request[4] = 0; @@ -3754,4 +3756,4 @@ void log_chunk_player_main_trace(void) } printf("Chunk=%p size=%lu\n", chunk, mem_reads_size); -} \ No newline at end of file +} diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 8a8451158..ba77ee5ff 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -2315,7 +2315,7 @@ impl<'a> Emu<'a> { // a_src_sp: F::from_bool(inst.a_src == SRC_SP), // #[cfg(feature = "sp")] // a_use_sp_imm1: F::from_u64(inst.a_use_sp_imm1), - a_src_step: F::from_bool(inst.a_src == SRC_STEP), + op_with_step: F::from_bool(inst.op_with_step), b_src_imm: F::from_bool(inst.b_src == SRC_IMM), b_src_mem: F::from_bool(inst.b_src == SRC_MEM), b_src_reg: F::from_bool(inst.b_src == SRC_REG), diff --git a/emulator/src/emu_options.rs b/emulator/src/emu_options.rs index 27a90c1c3..09563ebf0 100644 --- a/emulator/src/emu_options.rs +++ b/emulator/src/emu_options.rs @@ -77,7 +77,7 @@ impl Default for EmuOptions { elf: None, inputs: None, output: None, - max_steps: 0xFFFFFFFFFFFFFFFF, + max_steps: 0xFFF_FFFF_FFFF_FFFFF, print_step: None, trace: None, verbose: false, diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 292199463..06cbd6f1d 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -171,7 +171,7 @@ impl ZiskExecutor { const NUM_THREADS: usize = 16; /// The maximum number of steps to execute in the emulator or assembly runner. - const MAX_NUM_STEPS: u64 = 1 << 32; + const MAX_NUM_STEPS: u64 = 1 << 36; /// Creates a new instance of the `ZiskExecutor`. /// diff --git a/lib-c/c/src/ffiasm/bls12_381.asm b/lib-c/c/src/ffiasm/bls12_381.asm index aceee87d4..04e85b56f 100644 --- a/lib-c/c/src/ffiasm/bls12_381.asm +++ b/lib-c/c/src/ffiasm/bls12_381.asm @@ -8792,3 +8792,6 @@ R3 dq 0xc62c1807439b73af,0x1b3e0d188cf06990,0x73d13c71c7b5f418,0x6e2a5 lboMask dq 0x7fffffffffffffff np dq 0xfffffffeffffffff + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/bls12_381_384.asm b/lib-c/c/src/ffiasm/bls12_381_384.asm index 7f065aec7..b737e40f5 100644 --- a/lib-c/c/src/ffiasm/bls12_381_384.asm +++ b/lib-c/c/src/ffiasm/bls12_381_384.asm @@ -10910,3 +10910,6 @@ R3 dq 0xed48ac6bd94ca1e0,0x315f831e03a7adf8,0x9a53352a615e29dd,0x34c04 lboMask dq 0x1fffffffffffffff np dq 0x89f3fffcfffcfffd + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/fec.asm b/lib-c/c/src/ffiasm/fec.asm index 4d1465e25..21b8e1d2c 100644 --- a/lib-c/c/src/ffiasm/fec.asm +++ b/lib-c/c/src/ffiasm/fec.asm @@ -8874,3 +8874,6 @@ R3 dq 0x002bb1e33795f671,0x0000000100000b73,0x0000000000000000,0x00000 lboMask dq 0xffffffffffffffff np dq 0xd838091dd2253531 + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/fnec.asm b/lib-c/c/src/ffiasm/fnec.asm index 3b6b36d3d..fe8abd610 100644 --- a/lib-c/c/src/ffiasm/fnec.asm +++ b/lib-c/c/src/ffiasm/fnec.asm @@ -8874,3 +8874,6 @@ R3 dq 0x7bc0cfe0e9ff41ed,0x0017648444d4322c,0xb1b31347f1d0b2da,0x555d8 lboMask dq 0xffffffffffffffff np dq 0x4b0dff665588b13f + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/fq.asm b/lib-c/c/src/ffiasm/fq.asm index 34d7bd01a..93c36ba05 100644 --- a/lib-c/c/src/ffiasm/fq.asm +++ b/lib-c/c/src/ffiasm/fq.asm @@ -8791,3 +8791,6 @@ R3 dq 0xb1cd6dafda1530df,0x62f210e6a7283db6,0xef7f0b0c0ada0afb,0x20fd6 lboMask dq 0x3fffffffffffffff np dq 0x87d20782e4866389 + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/fr.asm b/lib-c/c/src/ffiasm/fr.asm index 806afaac0..0cac981ae 100644 --- a/lib-c/c/src/ffiasm/fr.asm +++ b/lib-c/c/src/ffiasm/fr.asm @@ -8791,3 +8791,6 @@ R3 dq 0x5e94d8e1b4bf0040,0x2a489cbe1cfbb6b8,0x893cc664a19fcfed,0x0cf85 lboMask dq 0x3fffffffffffffff np dq 0xc2e1f593efffffff + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/nsecp256r1.asm b/lib-c/c/src/ffiasm/nsecp256r1.asm index 963327487..e7f3e4693 100644 --- a/lib-c/c/src/ffiasm/nsecp256r1.asm +++ b/lib-c/c/src/ffiasm/nsecp256r1.asm @@ -8875,3 +8875,6 @@ R3 dq 0xac8ebec90b65a624,0x111f28ae0c0555c9,0x2543b9246ba5e93f,0x503a5 lboMask dq 0xffffffffffffffff np dq 0xccd1c8aaee00bc4f + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/psecp256r1.asm b/lib-c/c/src/ffiasm/psecp256r1.asm index ba7233133..a97b6e8f0 100644 --- a/lib-c/c/src/ffiasm/psecp256r1.asm +++ b/lib-c/c/src/ffiasm/psecp256r1.asm @@ -8875,3 +8875,6 @@ R3 dq 0xfffffffd0000000a,0xffffffedfffffff7,0x00000005fffffffc,0x00000 lboMask dq 0xffffffffffffffff np dq 0x1 + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index b4c6360d8..3f66ae342 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use rayon::prelude::*; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "558b53c15322024acd69f0cca53cc64b7c7002f7f72ee3595dd5d12d997caa17"; +pub const PILOUT_HASH: &str = "a67bacab2b28caf917f9d5eedfb48531dc6af524c40c65f08f3b48cfb045593a"; //AIRGROUP CONSTANTS @@ -113,7 +113,7 @@ trace!(MainFixed { }, 0, 0, 4194304 ); trace!(MainTrace { - a: [F; 2], b: [F; 2], c: [F; 2], flag: F, pc: F, a_src_imm: F, a_src_mem: F, a_offset_imm0: F, a_imm1: F, a_src_step: F, b_src_imm: F, b_src_mem: F, b_offset_imm0: F, b_imm1: F, b_src_ind: F, ind_width: F, is_external_op: F, op: F, store_ra: F, store_mem: F, store_ind: F, store_offset: F, set_pc: F, jmp_offset1: F, jmp_offset2: F, m32: F, addr1: F, a_reg_prev_mem_step: F, b_reg_prev_mem_step: F, store_reg_prev_mem_step: F, store_reg_prev_value: [F; 2], a_src_reg: F, b_src_reg: F, store_reg: F, + a: [F; 2], b: [F; 2], c: [F; 2], flag: F, pc: F, a_src_imm: F, a_src_mem: F, a_offset_imm0: F, a_imm1: F, op_with_step: F, b_src_imm: F, b_src_mem: F, b_offset_imm0: F, b_imm1: F, b_src_ind: F, ind_width: F, is_external_op: F, op: F, store_ra: F, store_mem: F, store_ind: F, store_offset: F, set_pc: F, jmp_offset1: F, jmp_offset2: F, m32: F, addr1: F, a_reg_prev_mem_step: F, b_reg_prev_mem_step: F, store_reg_prev_mem_step: F, store_reg_prev_value: [F; 2], a_src_reg: F, b_src_reg: F, store_reg: F, }, 0, 0, 4194304 ); trace!(RomFixed { @@ -269,7 +269,7 @@ trace!(VirtualTable0Trace { }, 0, 19, 2097152 ); trace!(VirtualTable1Fixed { - UID: [F; 8], column: [F; 64], __L1__: F, + UID: [F; 8], column: [F; 72], __L1__: F, }, 0, 20, 2097152 ); trace!(VirtualTable1Trace { diff --git a/pil/zisk.pil b/pil/zisk.pil index 484b796f6..2f68e547e 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -95,6 +95,6 @@ airgroup Zisk { // Public Inputs for (int i = 0; i < PUBLIC_INPUTS_64_BITS; i++) { - direct_global_update_proves(OPERATION_BUS_ID, [PUBLIC_OP, i, 0, inputs[i*2], inputs[i*2 + 1], inputs[i*2], inputs[i*2 + 1], 0]); + direct_global_update_proves(OPERATION_BUS_ID, [PUBLIC_OP, i, 0, inputs[i*2], inputs[i*2 + 1], inputs[i*2], inputs[i*2 + 1], 0, 0]); } } diff --git a/precompiles/arith_eq/pil/arith_eq.pil b/precompiles/arith_eq/pil/arith_eq.pil index 2ef4a9512..c477c7361 100644 --- a/precompiles/arith_eq/pil/arith_eq.pil +++ b/precompiles/arith_eq/pil/arith_eq.pil @@ -612,7 +612,7 @@ airtemplate ArithEq (int N = 2**18, const int operation_bus_id = OPERATION_BUS_I sel_bn254_complex_sub * COMPLEX_SUB_BN254_OP + sel_bn254_complex_mul * COMPLEX_MUL_BN254_OP; - lookup_proves(operation_bus_id, [bus_op, step_addr'(MAIN_STEP), 0, step_addr'(ADDR_OP), 0, 0, 0, 0], mul: in_use_clk0); + lookup_proves(operation_bus_id, [bus_op, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(MAIN_STEP)], mul: in_use_clk0); // selclk0 is the clock 0 for dedicated to one operation function expr_group_by_cbc(const expr selclk0, const expr chunks[], const int index ): const expr { diff --git a/precompiles/arith_eq/src/arith_eq_bus_device.rs b/precompiles/arith_eq/src/arith_eq_bus_device.rs index 3774e69f0..c988f11b3 100644 --- a/precompiles/arith_eq/src/arith_eq_bus_device.rs +++ b/precompiles/arith_eq/src/arith_eq_bus_device.rs @@ -4,10 +4,10 @@ use std::{collections::VecDeque, ops::Add}; -use zisk_common::MemCollectorInfo; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OP, OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OP, OPERATION_BUS_ID, OP_TYPE, }; +use zisk_common::{MemCollectorInfo, STEP}; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; use crate::mem_inputs::{ @@ -175,7 +175,7 @@ impl BusDevice for ArithEqCounterInputGen { } let op = data[OP] as u8; - let step_main = data[A]; + let step_main = data[STEP]; let addr_main = data[B] as u32; let only_counters = self.mode == BusDeviceMode::Counter; diff --git a/precompiles/arith_eq/src/arith_eq_input.rs b/precompiles/arith_eq/src/arith_eq_input.rs index 648f43fb8..ed227896a 100644 --- a/precompiles/arith_eq/src/arith_eq_input.rs +++ b/precompiles/arith_eq/src/arith_eq_input.rs @@ -35,15 +35,15 @@ impl Arith256Input { pub fn from(values: &OperationArith256Data) -> Self { Self { addr: values[3] as u32, - a_addr: values[4] as u32, - b_addr: values[5] as u32, - c_addr: values[6] as u32, - dl_addr: values[7] as u32, - dh_addr: values[8] as u32, - step: values[2], - a: values[9..13].try_into().unwrap(), - b: values[13..17].try_into().unwrap(), - c: values[17..21].try_into().unwrap(), + a_addr: values[5] as u32, + b_addr: values[6] as u32, + c_addr: values[7] as u32, + dl_addr: values[8] as u32, + dh_addr: values[9] as u32, + step: values[4], + a: values[10..14].try_into().unwrap(), + b: values[14..18].try_into().unwrap(), + c: values[18..22].try_into().unwrap(), } } } @@ -67,16 +67,16 @@ impl Arith256ModInput { pub fn from(values: &OperationArith256ModData) -> Self { Self { addr: values[3] as u32, - a_addr: values[4] as u32, - b_addr: values[5] as u32, - c_addr: values[6] as u32, - module_addr: values[7] as u32, - d_addr: values[8] as u32, - step: values[2], - a: values[9..13].try_into().unwrap(), - b: values[13..17].try_into().unwrap(), - c: values[17..21].try_into().unwrap(), - module: values[21..25].try_into().unwrap(), + a_addr: values[5] as u32, + b_addr: values[6] as u32, + c_addr: values[7] as u32, + module_addr: values[8] as u32, + d_addr: values[9] as u32, + step: values[4], + a: values[10..14].try_into().unwrap(), + b: values[14..18].try_into().unwrap(), + c: values[18..22].try_into().unwrap(), + module: values[22..26].try_into().unwrap(), } } } @@ -95,11 +95,11 @@ impl Secp256k1AddInput { pub fn from(values: &OperationSecp256k1AddData) -> Self { Self { addr: values[3] as u32, - p1_addr: values[4] as u32, - p2_addr: values[5] as u32, - step: values[2], - p1: values[6..14].try_into().unwrap(), - p2: values[14..22].try_into().unwrap(), + p1_addr: values[5] as u32, + p2_addr: values[6] as u32, + step: values[4], + p1: values[7..15].try_into().unwrap(), + p2: values[15..23].try_into().unwrap(), } } } @@ -113,7 +113,7 @@ pub struct Secp256k1DblInput { impl Secp256k1DblInput { pub fn from(values: &OperationSecp256k1DblData) -> Self { - Self { addr: values[3] as u32, step: values[2], p1: values[4..12].try_into().unwrap() } + Self { addr: values[3] as u32, step: values[4], p1: values[5..13].try_into().unwrap() } } } @@ -131,11 +131,11 @@ impl Bn254CurveAddInput { pub fn from(values: &OperationBn254CurveAddData) -> Self { Self { addr: values[3] as u32, - p1_addr: values[4] as u32, - p2_addr: values[5] as u32, - step: values[2], - p1: values[6..14].try_into().unwrap(), - p2: values[14..22].try_into().unwrap(), + p1_addr: values[5] as u32, + p2_addr: values[6] as u32, + step: values[4], + p1: values[7..15].try_into().unwrap(), + p2: values[15..23].try_into().unwrap(), } } } @@ -149,7 +149,7 @@ pub struct Bn254CurveDblInput { impl Bn254CurveDblInput { pub fn from(values: &OperationBn254CurveDblData) -> Self { - Self { addr: values[3] as u32, step: values[2], p1: values[4..12].try_into().unwrap() } + Self { addr: values[3] as u32, step: values[4], p1: values[5..13].try_into().unwrap() } } } @@ -167,11 +167,11 @@ impl Bn254ComplexAddInput { pub fn from(values: &OperationBn254ComplexAddData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..14].try_into().unwrap(), - f2: values[14..22].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..15].try_into().unwrap(), + f2: values[15..23].try_into().unwrap(), } } } @@ -190,11 +190,11 @@ impl Bn254ComplexSubInput { pub fn from(values: &OperationBn254ComplexSubData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..14].try_into().unwrap(), - f2: values[14..22].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..15].try_into().unwrap(), + f2: values[15..23].try_into().unwrap(), } } } @@ -213,11 +213,11 @@ impl Bn254ComplexMulInput { pub fn from(values: &OperationBn254ComplexMulData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..14].try_into().unwrap(), - f2: values[14..22].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..15].try_into().unwrap(), + f2: values[15..23].try_into().unwrap(), } } } diff --git a/precompiles/arith_eq/src/mem_inputs/arith256.rs b/precompiles/arith_eq/src/mem_inputs/arith256.rs index dc6dc5084..3d57f5aa7 100644 --- a/precompiles/arith_eq/src/mem_inputs/arith256.rs +++ b/precompiles/arith_eq/src/mem_inputs/arith256.rs @@ -20,9 +20,9 @@ pub fn generate_arith256_mem_inputs( pending: &mut VecDeque<(BusId, Vec)>, ) { // op,op_type,a,b,addr[5],... - let a: &[u64; 4] = &data[9..13].try_into().unwrap(); - let b: &[u64; 4] = &data[13..17].try_into().unwrap(); - let c: &[u64; 4] = &data[17..21].try_into().unwrap(); + let a: &[u64; 4] = &data[10..14].try_into().unwrap(); + let b: &[u64; 4] = &data[14..18].try_into().unwrap(); + let c: &[u64; 4] = &data[18..22].try_into().unwrap(); // let mut dh = [0u64; 4]; // let mut dl = [0u64; 4]; let mut d: [u64; 8] = [0u64; 8]; diff --git a/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs b/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs index a8c0b8d45..0b1c55e21 100644 --- a/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs +++ b/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs @@ -19,10 +19,10 @@ pub fn generate_arith256_mod_mem_inputs( pending: &mut VecDeque<(BusId, Vec)>, ) { // op,op_type,a,b,addr[5],... - let a: &[u64; 4] = &data[9..13].try_into().unwrap(); - let b: &[u64; 4] = &data[13..17].try_into().unwrap(); - let c: &[u64; 4] = &data[17..21].try_into().unwrap(); - let module: &[u64; 4] = &data[21..25].try_into().unwrap(); + let a: &[u64; 4] = &data[10..14].try_into().unwrap(); + let b: &[u64; 4] = &data[14..18].try_into().unwrap(); + let c: &[u64; 4] = &data[18..22].try_into().unwrap(); + let module: &[u64; 4] = &data[22..26].try_into().unwrap(); let mut d: [u64; 4] = [0u64; 4]; Arith256Mod::calculate(a, b, c, module, &mut d); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs index fca141932..7d0b0f285 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs @@ -20,8 +20,8 @@ pub fn generate_bn254_complex_add_mem_inputs( pending: &mut VecDeque<(BusId, Vec)>, ) { // op,op_type,a,b,addr[2],... - let f1: &[u64; 8] = &data[6..14].try_into().unwrap(); - let f2: &[u64; 8] = &data[14..22].try_into().unwrap(); + let f1: &[u64; 8] = &data[7..15].try_into().unwrap(); + let f2: &[u64; 8] = &data[15..23].try_into().unwrap(); let mut f3 = [0u64; 8]; Bn254Complex::calculate_add(f1, f2, &mut f3); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs index 6c70af05b..7dc5b9b93 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs @@ -20,8 +20,8 @@ pub fn generate_bn254_complex_mul_mem_inputs( pending: &mut VecDeque<(BusId, Vec)>, ) { // op,op_type,a,b,addr[2],... - let f1: &[u64; 8] = &data[6..14].try_into().unwrap(); - let f2: &[u64; 8] = &data[14..22].try_into().unwrap(); + let f1: &[u64; 8] = &data[7..15].try_into().unwrap(); + let f2: &[u64; 8] = &data[15..23].try_into().unwrap(); let mut f3 = [0u64; 8]; Bn254Complex::calculate_mul(f1, f2, &mut f3); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs index df7d18f56..cb92cda99 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs @@ -20,8 +20,8 @@ pub fn generate_bn254_complex_sub_mem_inputs( pending: &mut VecDeque<(BusId, Vec)>, ) { // op,op_type,a,b,addr[2],... - let f1: &[u64; 8] = &data[6..14].try_into().unwrap(); - let f2: &[u64; 8] = &data[14..22].try_into().unwrap(); + let f1: &[u64; 8] = &data[7..15].try_into().unwrap(); + let f2: &[u64; 8] = &data[15..23].try_into().unwrap(); let mut f3 = [0u64; 8]; Bn254Complex::calculate_sub(f1, f2, &mut f3); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs b/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs index e160843ee..18fbbea4f 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs @@ -20,8 +20,8 @@ pub fn generate_bn254_curve_add_mem_inputs( pending: &mut VecDeque<(BusId, Vec)>, ) { // op,op_type,a,b,addr[2],... - let p1: &[u64; 8] = &data[6..14].try_into().unwrap(); - let p2: &[u64; 8] = &data[14..22].try_into().unwrap(); + let p1: &[u64; 8] = &data[7..15].try_into().unwrap(); + let p2: &[u64; 8] = &data[15..23].try_into().unwrap(); let mut p3 = [0u64; 8]; Bn254Curve::calculate_add(p1, p2, &mut p3); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs b/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs index 1c683f1f7..815b6258a 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs @@ -3,6 +3,7 @@ use crate::executors::Bn254Curve; use std::collections::VecDeque; use zisk_common::BusId; use zisk_common::MemCollectorInfo; +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; pub const BN254_CURVE_DBL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 0, @@ -20,7 +21,7 @@ pub fn generate_bn254_curve_dbl_mem_inputs( pending: &mut VecDeque<(BusId, Vec)>, ) { // op,op_type,a,b,addr[2],... - let p1: &[u64; 8] = &data[4..12].try_into().unwrap(); + let p1: &[u64; 8] = &data[5..13].try_into().unwrap(); let mut p3 = [0u64; 8]; Bn254Curve::calculate_dbl(p1, &mut p3); diff --git a/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs b/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs index 2b1b7d943..d03d61470 100644 --- a/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs +++ b/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs @@ -1,7 +1,7 @@ use precompiles_common::MemBusHelpers; use std::collections::VecDeque; use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; +use zisk_common::{BusId, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; #[derive(Debug)] pub struct ArithEqMemInputConfig { @@ -21,13 +21,13 @@ pub fn generate_mem_inputs( config: &ArithEqMemInputConfig, ) { let params_count = config.read_params + config.write_params; - let params_offset = OPERATION_BUS_DATA_SIZE + config.indirect_params; + let params_offset = OPERATION_PRECOMPILED_BUS_DATA_SIZE + config.indirect_params; for iparam in 0..config.indirect_params { MemBusHelpers::mem_aligned_load( addr_main + iparam as u32 * 8, step_main, - data[OPERATION_BUS_DATA_SIZE + iparam], + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], pending, ) } @@ -39,7 +39,7 @@ pub fn generate_mem_inputs( }; let param_addr = if config.indirect_params > 0 { // read indirect parameters, means stored the address of parameter - data[OPERATION_BUS_DATA_SIZE + param_index] as u32 + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32 } else { addr_main + (param_index * 8 * config.chunks_per_param) as u32 }; @@ -97,7 +97,7 @@ pub fn skip_mem_inputs( iparam }; let param_addr = if config.indirect_params > 0 { - data[OPERATION_BUS_DATA_SIZE + param_index] as u32 + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32 } else { addr_main + (param_index * 8 * config.chunks_per_param) as u32 }; diff --git a/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs b/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs index 0c08a0ba0..ad1855d34 100644 --- a/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs @@ -20,8 +20,8 @@ pub fn generate_secp256k1_add_mem_inputs( pending: &mut VecDeque<(BusId, Vec)>, ) { // op,op_type,a,b,addr[2],... - let p1: &[u64; 8] = &data[6..14].try_into().unwrap(); - let p2: &[u64; 8] = &data[14..22].try_into().unwrap(); + let p1: &[u64; 8] = &data[7..15].try_into().unwrap(); + let p2: &[u64; 8] = &data[15..23].try_into().unwrap(); let mut p3 = [0u64; 8]; Secp256k1::calculate_add(p1, p2, &mut p3); diff --git a/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs b/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs index b86bf868a..c32df8a45 100644 --- a/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs +++ b/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs @@ -20,7 +20,7 @@ pub fn generate_secp256k1_dbl_mem_inputs( pending: &mut VecDeque<(BusId, Vec)>, ) { // op,op_type,a,b,... - let p1: &[u64; 8] = &data[4..12].try_into().unwrap(); + let p1: &[u64; 8] = &data[5..13].try_into().unwrap(); let mut p3 = [0u64; 8]; Secp256k1::calculate_dbl(p1, &mut p3); diff --git a/precompiles/arith_eq_384/pil/arith_eq_384.pil b/precompiles/arith_eq_384/pil/arith_eq_384.pil index 6a1477af2..713381c38 100644 --- a/precompiles/arith_eq_384/pil/arith_eq_384.pil +++ b/precompiles/arith_eq_384/pil/arith_eq_384.pil @@ -560,7 +560,7 @@ airtemplate ArithEq384(const int N, const int operation_bus_id = OPERATION_BUS_I sel_bls12_381_complex_sub * COMPLEX_SUB_BLS12_381_OP + sel_bls12_381_complex_mul * COMPLEX_MUL_BLS12_381_OP; - lookup_proves(operation_bus_id, [bus_op, step_addr'(MAIN_STEP), 0, step_addr'(ADDR_OP), 0, 0, 0, 0], mul: in_use_clk0); + lookup_proves(operation_bus_id, [bus_op, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(MAIN_STEP)], mul: in_use_clk0); // selclk0 is the clock 0 for dedicated to one operation function expr_group_by_cbc(const expr selclk0, const expr chunks[], const int index): const expr { diff --git a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs index 5f06e8f25..03463eb6e 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs @@ -5,8 +5,8 @@ use std::{collections::VecDeque, ops::Add}; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, MemCollectorInfo, Metrics, A, B, OP, - OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, MemCollectorInfo, Metrics, B, OP, OPERATION_BUS_ID, + OP_TYPE, STEP, }; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; @@ -163,7 +163,7 @@ impl BusDevice for ArithEq384CounterInputGen { } let op = data[OP] as u8; - let step_main = data[A]; + let step_main = data[STEP]; let addr_main = data[B] as u32; let only_counters = self.mode == BusDeviceMode::Counter; diff --git a/precompiles/arith_eq_384/src/arith_eq_384_input.rs b/precompiles/arith_eq_384/src/arith_eq_384_input.rs index c5f8dbfbc..ba3ce8a59 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_input.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_input.rs @@ -35,16 +35,16 @@ impl Arith384ModInput { pub fn from(values: &OperationArith384ModData) -> Self { Self { addr: values[3] as u32, - a_addr: values[4] as u32, - b_addr: values[5] as u32, - c_addr: values[6] as u32, - module_addr: values[7] as u32, - d_addr: values[8] as u32, - step: values[2], - a: values[9..15].try_into().unwrap(), - b: values[15..21].try_into().unwrap(), - c: values[21..27].try_into().unwrap(), - module: values[27..33].try_into().unwrap(), + a_addr: values[5] as u32, + b_addr: values[6] as u32, + c_addr: values[7] as u32, + module_addr: values[8] as u32, + d_addr: values[9] as u32, + step: values[4], + a: values[10..16].try_into().unwrap(), + b: values[16..22].try_into().unwrap(), + c: values[22..28].try_into().unwrap(), + module: values[28..34].try_into().unwrap(), } } } @@ -63,11 +63,11 @@ impl Bls12_381CurveAddInput { pub fn from(values: &OperationBls12_381CurveAddData) -> Self { Self { addr: values[3] as u32, - p1_addr: values[4] as u32, - p2_addr: values[5] as u32, - step: values[2], - p1: values[6..18].try_into().unwrap(), - p2: values[18..30].try_into().unwrap(), + p1_addr: values[5] as u32, + p2_addr: values[6] as u32, + step: values[4], + p1: values[7..19].try_into().unwrap(), + p2: values[19..31].try_into().unwrap(), } } } @@ -81,7 +81,7 @@ pub struct Bls12_381CurveDblInput { impl Bls12_381CurveDblInput { pub fn from(values: &OperationBls12_381CurveDblData) -> Self { - Self { addr: values[3] as u32, step: values[2], p1: values[4..16].try_into().unwrap() } + Self { addr: values[3] as u32, step: values[4], p1: values[5..17].try_into().unwrap() } } } @@ -99,11 +99,11 @@ impl Bls12_381ComplexAddInput { pub fn from(values: &OperationBls12_381ComplexAddData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..18].try_into().unwrap(), - f2: values[18..30].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..19].try_into().unwrap(), + f2: values[19..31].try_into().unwrap(), } } } @@ -122,11 +122,11 @@ impl Bls12_381ComplexSubInput { pub fn from(values: &OperationBls12_381ComplexSubData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..18].try_into().unwrap(), - f2: values[18..30].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..19].try_into().unwrap(), + f2: values[19..31].try_into().unwrap(), } } } @@ -145,11 +145,11 @@ impl Bls12_381ComplexMulInput { pub fn from(values: &OperationBls12_381ComplexMulData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..18].try_into().unwrap(), - f2: values[18..30].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..19].try_into().unwrap(), + f2: values[19..31].try_into().unwrap(), } } } diff --git a/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs b/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs index a73fa3d93..7425f6512 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs @@ -19,7 +19,7 @@ pub fn generate_arith384_mod_mem_inputs( only_counters: bool, pending: &mut VecDeque<(BusId, Vec)>, ) { - let mut pos_offset: usize = 9; // op,op_type,a,b,addr[5],... + let mut pos_offset: usize = 10; // op,op_type,a,b,addr[5],... let a: &[u64; ARITH_EQ_384_U64S] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S)].try_into().unwrap(); pos_offset += ARITH_EQ_384_U64S; diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs index c10a6e3c7..008abbe53 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs @@ -19,7 +19,7 @@ pub fn generate_bls12_381_complex_add_mem_inputs( only_counters: bool, pending: &mut VecDeque<(BusId, Vec)>, ) { - let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... + let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S_DOUBLE)].try_into().unwrap(); pos_offset += ARITH_EQ_384_U64S_DOUBLE; diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs index fec7de46d..6a9f36a94 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs @@ -19,7 +19,7 @@ pub fn generate_bls12_381_complex_mul_mem_inputs( only_counters: bool, pending: &mut VecDeque<(BusId, Vec)>, ) { - let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... + let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S_DOUBLE)].try_into().unwrap(); pos_offset += ARITH_EQ_384_U64S_DOUBLE; diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs index 5d15fbc29..81507f0e1 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs @@ -19,7 +19,7 @@ pub fn generate_bls12_381_complex_sub_mem_inputs( only_counters: bool, pending: &mut VecDeque<(BusId, Vec)>, ) { - let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... + let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S_DOUBLE)].try_into().unwrap(); pos_offset += ARITH_EQ_384_U64S_DOUBLE; diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs index 983b3c7f6..5384a9c74 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs @@ -19,7 +19,7 @@ pub fn generate_bls12_381_curve_add_mem_inputs( only_counters: bool, pending: &mut VecDeque<(BusId, Vec)>, ) { - let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... + let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let p1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S_DOUBLE)].try_into().unwrap(); pos_offset += ARITH_EQ_384_U64S_DOUBLE; diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs index b61f8da15..497e8f070 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs @@ -19,7 +19,7 @@ pub fn generate_bls12_381_curve_dbl_mem_inputs( only_counters: bool, pending: &mut VecDeque<(BusId, Vec)>, ) { - let pos_offset: usize = 4; // op,op_type,a,b,... + let pos_offset: usize = 5; // op,op_type,a,b,... let p1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S_DOUBLE)].try_into().unwrap(); let mut p3 = [0u64; ARITH_EQ_384_U64S_DOUBLE]; diff --git a/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs b/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs index 2b1602d29..7d6d7b744 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs @@ -1,6 +1,6 @@ use precompiles_common::MemBusHelpers; use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo, OPERATION_BUS_DATA_SIZE}; +use zisk_common::{BusId, MemCollectorInfo, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; #[derive(Debug)] pub struct ArithEq384MemInputConfig { @@ -20,13 +20,13 @@ pub fn generate_mem_inputs( config: &ArithEq384MemInputConfig, ) { let params_count = config.read_params + config.write_params; - let params_offset = OPERATION_BUS_DATA_SIZE + config.indirect_params; + let params_offset = OPERATION_PRECOMPILED_BUS_DATA_SIZE + config.indirect_params; for iparam in 0..config.indirect_params { MemBusHelpers::mem_aligned_load( addr_main + iparam as u32 * 8, step_main, - data[OPERATION_BUS_DATA_SIZE + iparam], + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], pending, ) } @@ -38,7 +38,7 @@ pub fn generate_mem_inputs( }; let param_addr = if config.indirect_params > 0 { // read indirect parameters, means stored the address of parameter - data[OPERATION_BUS_DATA_SIZE + param_index] as u32 + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32 } else { addr_main + (param_index * 8 * config.chunks_per_param) as u32 }; @@ -96,7 +96,7 @@ pub fn skip_mem_inputs( iparam }; let param_addr = if config.indirect_params > 0 { - data[OPERATION_BUS_DATA_SIZE + param_index] as u32 + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32 } else { addr_main + (param_index * 8 * config.chunks_per_param) as u32 }; diff --git a/precompiles/big_int/pil/big_int_add.pil b/precompiles/big_int/pil/big_int_add.pil index bfaff9f28..2d8167e50 100644 --- a/precompiles/big_int/pil/big_int_add.pil +++ b/precompiles/big_int/pil/big_int_add.pil @@ -134,5 +134,5 @@ airtemplate BigIntAdd(const int N = 2**21, const int operation_bus_id = OPERATIO const expr final_cout = cout[N_RC-1][RC-1]; // proves the operation lauched by main, c = flag = carry_out - lookup_proves(operation_bus_id, [operation_code, step, 0, addr_params, 0, final_cout, 0, final_cout], sel); + lookup_proves(operation_bus_id, [operation_code, 0, 0, addr_params, 0, final_cout, 0, final_cout, step], sel); } \ No newline at end of file diff --git a/precompiles/big_int/src/add256_bus_device.rs b/precompiles/big_int/src/add256_bus_device.rs index d65dcff9f..6a0f31e59 100644 --- a/precompiles/big_int/src/add256_bus_device.rs +++ b/precompiles/big_int/src/add256_bus_device.rs @@ -6,7 +6,7 @@ use std::{collections::VecDeque, ops::Add}; use zisk_common::MemCollectorInfo; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, STEP, }; use zisk_core::ZiskOperationType; @@ -120,7 +120,7 @@ impl BusDevice for Add256CounterInputGen { } } - let step_main = data[A]; + let step_main = data[STEP]; let addr_main = data[B] as u32; let only_counters = self.mode == BusDeviceMode::Counter; diff --git a/precompiles/big_int/src/add256_constants.rs b/precompiles/big_int/src/add256_constants.rs index 4b51efd21..6b358c74b 100644 --- a/precompiles/big_int/src/add256_constants.rs +++ b/precompiles/big_int/src/add256_constants.rs @@ -1,4 +1,4 @@ -use zisk_common::OPERATION_BUS_DATA_SIZE; +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; pub const PARAMS: usize = 4; pub const READ_PARAMS: usize = 2; @@ -6,7 +6,7 @@ pub const DIRECT_READ_PARAMS: usize = 1; pub const WRITE_PARAMS: usize = 1; pub const RESULT_PARAMS: usize = 1; pub const PARAM_CHUNKS: usize = 4; -pub const START_READ_PARAMS: usize = OPERATION_BUS_DATA_SIZE + PARAMS; +pub const START_READ_PARAMS: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + PARAMS; pub const START_WRITE_PARAMS: usize = START_READ_PARAMS + READ_PARAMS * PARAM_CHUNKS + RESULT_PARAMS; pub const WRITE_ADDR_PARAM: usize = READ_PARAMS + DIRECT_READ_PARAMS; diff --git a/precompiles/big_int/src/add256_gen_mem_inputs.rs b/precompiles/big_int/src/add256_gen_mem_inputs.rs index 4f26b6c69..87510b3d4 100644 --- a/precompiles/big_int/src/add256_gen_mem_inputs.rs +++ b/precompiles/big_int/src/add256_gen_mem_inputs.rs @@ -4,7 +4,7 @@ use crate::add256_constants::*; use precompiles_common::MemBusHelpers; use std::collections::VecDeque; use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; +use zisk_common::{BusId, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; #[derive(Debug)] pub struct Add256MemInputConfig { @@ -27,14 +27,14 @@ pub fn generate_add256_mem_inputs( MemBusHelpers::mem_aligned_load( addr_main + iparam as u32 * 8, step_main, - data[OPERATION_BUS_DATA_SIZE + iparam], + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], pending, ); } // generate load params for iparam in 0..READ_PARAMS { - let param_addr = data[OPERATION_BUS_DATA_SIZE + iparam] as u32; + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; for ichunk in 0..PARAM_CHUNKS { MemBusHelpers::mem_aligned_load( param_addr + ichunk as u32 * 8, @@ -53,11 +53,11 @@ pub fn generate_add256_mem_inputs( [START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] .try_into() .unwrap(); - add256(&a, &b, data[OPERATION_BUS_DATA_SIZE + READ_PARAMS], &mut write_data); + add256(&a, &b, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS], &mut write_data); } // verify write param - let write_addr = data[OPERATION_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; + let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; for (ichunk, write_data) in write_data.iter().enumerate().take(PARAM_CHUNKS) { let param_addr = write_addr + ichunk as u32 * 8; MemBusHelpers::mem_aligned_write(param_addr, step_main, *write_data, pending); @@ -85,7 +85,7 @@ pub fn skip_add256_mem_inputs( // verify read params for iparam in 0..READ_PARAMS { - let param_addr = data[OPERATION_BUS_DATA_SIZE + iparam] as u32; + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; for ichunk in 0..PARAM_CHUNKS { let addr = param_addr + ichunk as u32 * 8; for mem_collector in mem_collectors_info { @@ -97,7 +97,7 @@ pub fn skip_add256_mem_inputs( } // verify write param - let write_addr = data[OPERATION_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; + let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; for ichunk in 0..PARAM_CHUNKS { let addr = write_addr + ichunk as u32 * 8; for mem_collector in mem_collectors_info { diff --git a/precompiles/big_int/src/add256_input.rs b/precompiles/big_int/src/add256_input.rs index 71e7bc755..b50d12e7a 100644 --- a/precompiles/big_int/src/add256_input.rs +++ b/precompiles/big_int/src/add256_input.rs @@ -1,6 +1,6 @@ use crate::add256_constants::*; use zisk_common::OperationAdd256Data; -use zisk_common::{A, B, OPERATION_BUS_DATA_SIZE}; +use zisk_common::{B, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; #[derive(Debug)] pub struct Add256Input { @@ -17,12 +17,12 @@ pub struct Add256Input { impl Add256Input { pub fn from(values: &OperationAdd256Data) -> Self { Self { - step_main: values[A], + step_main: values[STEP], addr_main: values[B] as u32, - addr_a: values[OPERATION_BUS_DATA_SIZE] as u32, - addr_b: values[OPERATION_BUS_DATA_SIZE + 1] as u32, - addr_c: values[OPERATION_BUS_DATA_SIZE + READ_PARAMS + DIRECT_READ_PARAMS] as u32, - cin: values[OPERATION_BUS_DATA_SIZE + READ_PARAMS], + addr_a: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as u32, + addr_b: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] as u32, + addr_c: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2] as u32, + cin: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 3], a: values[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(), b: values[START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] .try_into() diff --git a/precompiles/dma/Cargo.toml b/precompiles/dma/Cargo.toml new file mode 100644 index 000000000..18691f8f9 --- /dev/null +++ b/precompiles/dma/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "precomp-big-int" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +keywords = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } + +[dependencies] +zisk-core = { workspace = true } +zisk-common = { workspace = true } +zisk-pil = { workspace = true } +precompiles-common = { workspace = true } +sm-mem = { workspace = true } +mem-common = { workspace = true } +lib-c = { workspace = true } + +proofman = { workspace = true } +proofman-common = { workspace = true } +proofman-macros = { workspace = true } +proofman-util = { workspace = true } +pil-std-lib = { workspace = true } +fields = { workspace=true } +tracing = { workspace = true } +rayon = { workspace = true } +generic-array = "0.14" + +[features] +default = [] +no_lib_link = ["proofman-common/no_lib_link"] +diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] +disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file diff --git a/precompiles/dma/pil/dma.pil b/precompiles/dma/pil/dma.pil new file mode 100644 index 000000000..00caf549b --- /dev/null +++ b/precompiles/dma/pil/dma.pil @@ -0,0 +1,127 @@ +const int MEMCPY_CONT_ID = 400; + +airtemplate MemCpy(int N = 2**21, const int RC = 2, const int op_x_row = 4, const int selectors = 1) { + + assert(selectors == 0 || selectors == 1); + assert(op_x_row > 1); + + airval segment_id; // Id of current segment + airval segment_previous_reset; // Last value of `reset` in previous segment. + airval segment_previous_src_addr; // Last value of `src_addr` in previous segment. + airval segment_previous_dst_addr; // Last value of `dst_addr` in previous segment. + airval segment_previous_main_step; // Last value of `main_step` in previous segment. + airval segment_previous_count; // Last value of `count` in previous segment. + + airval segment_last_reset; // Last value of `reset` in current segment. + airval segment_last_src_addr; // Last value of `src_addr` in current segment. + airval segment_last_dst_addr; // Last value of `dst_addr` in current segment. + airval segment_last_main_step; // Last value of `main_step` in current segment. + airval segment_last_count; // Last value of `count` in current segment. + + airval is_last_segment; // 1 if this is the last segment, 0 otherwise. + col witness main_step; + col witness src_addr; + col witness dst_addr; + col witness count; + col witness value[op_x_row][RC]; + + col witness sel_op[selectors ? op_x_row : 0]; // one more for end + col witness reset; + + const expr previous_reset = L1 * (segment_previous_reset - 'reset) + 'reset; + const expr previous_src_addr = L1 * (segment_previous_src_addr - 'src_addr) + 'src_addr; + const expr previous_dst_addr = L1 * (segment_previous_dst_addr - 'dst_addr) + 'dst_addr; + const expr previous_main_step = L1 * (segment_previous_main_step - 'main_step) + 'main_step; + const expr previous_count = L1 * (segment_previous_count - 'count) + 'count; + + LAST * (reset - segment_last_reset) === 0; + LAST * (src_addr - segment_last_src_addr) === 0; + LAST * (dst_addr - segment_last_dst_addr) === 0; + LAST * (main_step - segment_last_main_step) === 0; + LAST * (count - segment_last_count) === 0; + + // Continuations + // AIR_ID, segment_id, reset, src_addr, dst_addr, count, main_step + const int UNIQ_ID = AIRGROUP_ID * 1000 + AIR_ID; + direct_global_update_proves(MEMCPY_CONT_ID, [UNIQ_ID, 0, 1, 0, 0, 0, 0]); + + + direct_update_assumes(MEMCPY_CONT_ID, [ UNIQ_ID, + segment_id, + segment_previous_reset, + segment_previous_src_addr, + segment_previous_dst_addr, + segment_previous_count, + segment_previous_main_step]); + + direct_update_proves(MEMCPY_CONT_ID, [ UNIQ_ID, + segment_id + 1, + segment_last_reset, + segment_last_src_addr, + segment_last_dst_addr, + segment_last_count, + segment_last_main_step], sel: is_last_segment); + + // This global constraint is sent to the bus to define the initial and final values of + // continuations. A global constraint is used to ensure there is only one initial and one final + // value, preventing multiple cycles of `main`. + + // In the initial state define the initial `pc` and final `pc`. Before finish a execution the + // value of "c" must be 0. + + const int zeros[RC]; + for (int index = 0; index < RC; ++index) { + zeros[index] = 0; + } + + // These constraints define the state at the beginning and end of a main continuation. + + direct_global_update_assumes(MAIN_CONTINUATION_ID, expressions: [0, 1, END_PC_ADDR, ...zeros]); + + + reset * (1 - reset) === 0; + + expr _ops_in_row = selectors ? sel_op[0] * op_x_row : sel_op[0]; + + for (int i = 0; i < op_x_row; i++) { + expr sel; + if (selectors || i == 0) { + sel = sel_op[i]; + sel_op[i] * (1 - sel_op[i]) === 0; + } else { + sel = sel_op[0]; + } + + precompiled_mem_load( + sel: sel, + main_step: main_step, + addr: src_addr * 8 + 8 * i, + value: value[i] + ); + + precompiled_mem_store( + sel: sel, + main_step: main_step, + addr: dst_addr * 8 + 8 * i, + value: value[i] + ); + if (selectors && i > 0) { + // current selector only can be 1 if previous is 1 + sel_op[i] * (1 - sel_op[i - 1]) === 0; + _ops_in_row += sel_op[i]; + } + } + const expr ops_in_row = _ops_in_row; + reset * (count - ops_in_row) === 0; + + range_check(count, min: 0, max: 2**24-1); // cost 1.5 + + (count - ('count + op_x_row)) * (1 - reset) === 0; + (src_addr - ('src_addr + 8 * op_x_row)) * (1 - reset) === 0; + (dst_addr - ('dst_addr + 8 * op_x_row)) * (1 - reset) === 0; + + const expr previous_reset = L1 * (previous_segment_reset - 'reset) + 'reset; + + lookup_proves(op_bus_id, [op, dst_addr * 8, 0, src_addr * 8, 0, 0, 0, 0, step], sel: previous_reset); + lookup_proves(op_bus_id, [op_param, count, 0, 0, 0, 0, 0, 0, step], sel: reset); +} \ No newline at end of file diff --git a/precompiles/dma/src/add256.rs b/precompiles/dma/src/add256.rs new file mode 100644 index 000000000..c65e4a7b2 --- /dev/null +++ b/precompiles/dma/src/add256.rs @@ -0,0 +1,169 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use rayon::prelude::*; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use zisk_pil::{Add256Trace, Add256TraceRow}; + +use super::Add256Input; + +/// The `Add256SM` struct encapsulates the logic of the Add256 State Machine. +pub struct Add256SM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Number of available add256s in the trace. + pub num_availables: usize, + + /// Range checks ID's + range_id: usize, +} + +impl Add256SM { + /// Creates a new Add256 State Machine instance. + /// + /// # Returns + /// A new `Add256SM` instance. + pub fn new(std: Arc>) -> Arc { + // Compute some useful values + let num_availables = Add256Trace::::NUM_ROWS; + + let range_id = std.get_range_id(0, (1 << 16) - 1, None); + + Arc::new(Self { std, num_availables, range_id }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Add256 trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_slice( + &self, + input: &Add256Input, + trace: &mut Add256TraceRow, + multiplicities: &mut [u32], + ) { + trace.cin = F::from_bool(input.cin != 0); + let mut cout_2 = input.cin as u32; + + for i in 0..4 { + let al = input.a[i] as u32; + let ah = (input.a[i] >> 32) as u32; + + let bl = input.b[i] as u32; + let bh = (input.b[i] >> 32) as u32; + + trace.a[i][0] = F::from_u32(al); + trace.a[i][1] = F::from_u32(ah); + trace.b[i][0] = F::from_u32(bl); + trace.b[i][1] = F::from_u32(bh); + let cl = al as u64 + bl as u64 + cout_2 as u64; + let cout_1 = cl >> 32; + let ch = ah as u64 + bh as u64 + cout_1; + cout_2 = (ch >> 32) as u32; + + let cll = cl as u16; + let clh = (cl >> 16) as u16; + let chl = ch as u16; + let chh = (ch >> 16) as u16; + + trace.c_chunks[i][0] = F::from_u16(cll); + trace.c_chunks[i][1] = F::from_u16(clh); + trace.c_chunks[i][2] = F::from_u16(chl); + trace.c_chunks[i][3] = F::from_u16(chh); + + trace.cout[i][0] = F::from_u8(cout_1 as u8); + trace.cout[i][1] = F::from_u8(cout_2 as u8); + + multiplicities[cll as usize] += 1; + multiplicities[clh as usize] += 1; + multiplicities[chl as usize] += 1; + multiplicities[chh as usize] += 1; + } + trace.addr_params = F::from_u32(input.addr_main); + trace.addr_a = F::from_u32(input.addr_a); + trace.addr_b = F::from_u32(input.addr_b); + trace.addr_c = F::from_u32(input.addr_c); + trace.step = F::from_u64(input.step_main); + trace.sel = F::ONE; + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> AirInstance { + let mut trace = Add256Trace::new_from_vec(trace_buffer); + + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); + assert!(total_inputs <= num_rows); + + tracing::info!( + "··· Creating Add256 instance [{} / {} rows filled {:.2}%]", + total_inputs, + num_rows, + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(ADD256_TRACE); + + // Split the add256_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.row_slice_mut(); + + // Determinar tamaño óptimo de chunks + let num_threads = rayon::current_num_threads(); + let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); + + // Procesar en chunks para compartir arrays locales de multiplicities + let local_multiplicities_vec: Vec> = flat_inputs + .par_chunks(chunk_size) + .zip(trace_rows.par_chunks_mut(chunk_size)) + .map(|(input_chunk, trace_chunk)| { + // Array local compartido por este chunk + let mut local_multiplicities = vec![0u32; 1 << 16]; + + // Procesar todos los inputs del chunk + for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { + self.process_slice(input, trace_row, &mut local_multiplicities); + } + + local_multiplicities + }) + .collect(); + + // Sumar todos los arrays locales en uno global + let mut global_multiplicities = vec![0u32; 1 << 16]; + for local_multiplicities in local_multiplicities_vec { + for (i, count) in local_multiplicities.iter().enumerate() { + global_multiplicities[i] += count; + } + } + + // Enviar el resultado final al std + self.std.range_checks(self.range_id, global_multiplicities); + + timer_stop_and_log_trace!(ADD256_TRACE); + + trace.row_slice_mut()[total_inputs..num_rows] + .par_iter_mut() + .for_each(|slot| *slot = Add256TraceRow:: { ..Default::default() }); + + AirInstance::::new_from_trace(FromTrace::new(&mut trace)) + } +} diff --git a/precompiles/dma/src/add256_bus_device.rs b/precompiles/dma/src/add256_bus_device.rs new file mode 100644 index 000000000..6a0f31e59 --- /dev/null +++ b/precompiles/dma/src/add256_bus_device.rs @@ -0,0 +1,148 @@ +//! The `Add256Counter` module defines a counter for tracking add256-related operations +//! sent over the data bus. It connects to the bus and gathers metrics for specific +//! `ZiskOperationType::Add256` instructions. + +use std::{collections::VecDeque, ops::Add}; + +use zisk_common::MemCollectorInfo; +use zisk_common::{ + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, STEP, +}; +use zisk_core::ZiskOperationType; + +use crate::{generate_add256_mem_inputs, skip_add256_mem_inputs}; + +/// The `Add256Counter` struct represents a counter that monitors and measures +/// add256-related operations on the data bus. +/// +/// It tracks specific operation types (`ZiskOperationType`) and updates counters for each +/// accepted operation type whenever data is processed on the bus. +pub struct Add256CounterInputGen { + /// Add256 counter. + counter: Counter, + + /// Bus device mode (counter or input generator). + mode: BusDeviceMode, +} + +impl Add256CounterInputGen { + /// Creates a new instance of `Add256Counter`. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus to which this counter is connected. + /// * `op_type` - A vector of `ZiskOperationType` instructions to monitor. + /// + /// # Returns + /// A new `Add256Counter` instance. + pub fn new(mode: BusDeviceMode) -> Self { + Self { counter: Counter::default(), mode } + } + + /// Retrieves the count of instructions for a specific `ZiskOperationType`. + /// + /// # Arguments + /// * `op_type` - The operation type to retrieve the count for. + /// + /// # Returns + /// Returns the count of instructions for the specified operation type. + pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { + (op_type == ZiskOperationType::BigInt).then_some(self.counter.inst_count) + } +} + +impl Metrics for Add256CounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. + /// + /// # Returns + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); + } + + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for Add256CounterInputGen { + type Output = Add256CounterInputGen; + + /// Combines two `Add256Counter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `Add256Counter` instance. + /// * `other` - The second `Add256Counter` instance. + /// + /// # Returns + /// A new `Add256Counter` with combined counters. + fn add(self, other: Self) -> Add256CounterInputGen { + Add256CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } + } +} + +impl BusDevice for Add256CounterInputGen { + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + pending: &mut VecDeque<(BusId, Vec)>, + mem_collector_info: Option<&[MemCollectorInfo]>, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if data[OP_TYPE] as u32 != ZiskOperationType::BigInt as u32 { + return true; + } + + if let Some(mem_collectors_info) = mem_collector_info { + if skip_add256_mem_inputs(data[B] as u32, data, mem_collectors_info) { + return true; + } + } + + let step_main = data[STEP]; + let addr_main = data[B] as u32; + + let only_counters = self.mode == BusDeviceMode::Counter; + if only_counters { + self.measure(data); + } + + generate_add256_mem_inputs(addr_main, step_main, data, only_counters, pending); + + true + } + + /// Returns the bus IDs associated with this counter. + /// + /// # Returns + /// A vector containing the connected bus ID. + fn bus_id(&self) -> Vec { + vec![OPERATION_BUS_ID] + } + + /// Provides a dynamic reference for downcasting purposes. + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/add256_constants.rs b/precompiles/dma/src/add256_constants.rs new file mode 100644 index 000000000..6b358c74b --- /dev/null +++ b/precompiles/dma/src/add256_constants.rs @@ -0,0 +1,12 @@ +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; + +pub const PARAMS: usize = 4; +pub const READ_PARAMS: usize = 2; +pub const DIRECT_READ_PARAMS: usize = 1; +pub const WRITE_PARAMS: usize = 1; +pub const RESULT_PARAMS: usize = 1; +pub const PARAM_CHUNKS: usize = 4; +pub const START_READ_PARAMS: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + PARAMS; +pub const START_WRITE_PARAMS: usize = + START_READ_PARAMS + READ_PARAMS * PARAM_CHUNKS + RESULT_PARAMS; +pub const WRITE_ADDR_PARAM: usize = READ_PARAMS + DIRECT_READ_PARAMS; diff --git a/precompiles/dma/src/add256_gen_mem_inputs.rs b/precompiles/dma/src/add256_gen_mem_inputs.rs new file mode 100644 index 000000000..87510b3d4 --- /dev/null +++ b/precompiles/dma/src/add256_gen_mem_inputs.rs @@ -0,0 +1,111 @@ +use lib_c::add256; + +use crate::add256_constants::*; +use precompiles_common::MemBusHelpers; +use std::collections::VecDeque; +use zisk_common::MemCollectorInfo; +use zisk_common::{BusId, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; + +#[derive(Debug)] +pub struct Add256MemInputConfig { + pub indirect_params: usize, + pub rewrite_params: bool, + pub read_params: usize, + pub write_params: usize, + pub chunks_per_param: usize, +} + +pub fn generate_add256_mem_inputs( + addr_main: u32, + step_main: u64, + data: &[u64], + only_counters: bool, + pending: &mut VecDeque<(BusId, Vec)>, +) { + // Start by generating the params (indirection read, direct, indirection write) + for iparam in 0..PARAMS { + MemBusHelpers::mem_aligned_load( + addr_main + iparam as u32 * 8, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], + pending, + ); + } + + // generate load params + for iparam in 0..READ_PARAMS { + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; + for ichunk in 0..PARAM_CHUNKS { + MemBusHelpers::mem_aligned_load( + param_addr + ichunk as u32 * 8, + step_main, + data[START_READ_PARAMS + iparam * PARAM_CHUNKS + ichunk], + pending, + ); + } + } + + let mut write_data = [0u64; PARAM_CHUNKS]; + if !only_counters { + let a: [u64; 4] = + data[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(); + let b: [u64; 4] = data + [START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] + .try_into() + .unwrap(); + add256(&a, &b, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS], &mut write_data); + } + + // verify write param + let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; + for (ichunk, write_data) in write_data.iter().enumerate().take(PARAM_CHUNKS) { + let param_addr = write_addr + ichunk as u32 * 8; + MemBusHelpers::mem_aligned_write(param_addr, step_main, *write_data, pending); + } +} + +// op_a = step +// op_b = addr_main +// mem_trace: @a, @b, cin, @c, a[0..3], b[0..3], cout, [ c[0..3] ] + +pub fn skip_add256_mem_inputs( + addr_main: u32, + data: &[u64], + mem_collectors_info: &[MemCollectorInfo], +) -> bool { + // verify main params "struct" of indirections + for iparam in 0..PARAMS { + let addr = addr_main + iparam as u32 * 8; + for mem_collector in mem_collectors_info { + if !mem_collector.skip_addr(addr) { + return false; + } + } + } + + // verify read params + for iparam in 0..READ_PARAMS { + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; + for ichunk in 0..PARAM_CHUNKS { + let addr = param_addr + ichunk as u32 * 8; + for mem_collector in mem_collectors_info { + if !mem_collector.skip_addr(addr) { + return false; + } + } + } + } + + // verify write param + let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; + for ichunk in 0..PARAM_CHUNKS { + let addr = write_addr + ichunk as u32 * 8; + for mem_collector in mem_collectors_info { + if !mem_collector.skip_addr(addr) { + return false; + } + } + } + + true +} diff --git a/precompiles/dma/src/add256_input.rs b/precompiles/dma/src/add256_input.rs new file mode 100644 index 000000000..a6239f825 --- /dev/null +++ b/precompiles/dma/src/add256_input.rs @@ -0,0 +1,33 @@ +use crate::add256_constants::*; +use zisk_common::OperationAdd256Data; +use zisk_common::{B, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; + +#[derive(Debug)] +pub struct Add256Input { + pub step_main: u64, + pub addr_main: u32, + pub addr_a: u32, + pub addr_b: u32, + pub addr_c: u32, + pub cin: u64, + pub a: [u64; 4], + pub b: [u64; 4], +} + +impl Add256Input { + pub fn from(values: &OperationAdd256Data) -> Self { + Self { + step_main: values[STEP], + addr_main: values[B] as u32, + addr_a: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as u32, + addr_b: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] as u32, + addr_c: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS + DIRECT_READ_PARAMS] + as u32, + cin: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS], + a: values[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(), + b: values[START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] + .try_into() + .unwrap(), + } + } +} diff --git a/precompiles/dma/src/add256_instance.rs b/precompiles/dma/src/add256_instance.rs new file mode 100644 index 000000000..267251539 --- /dev/null +++ b/precompiles/dma/src/add256_instance.rs @@ -0,0 +1,210 @@ +//! The `Add256Instance` module defines an instance to perform the witness computation +//! for the Add256 State Machine. +//! +//! It manages collected inputs and interacts with the `Add256SM` to compute witnesses for +//! execution plans. + +use crate::{Add256Input, Add256SM}; +use fields::PrimeField64; +use proofman_common::{AirInstance, ProofCtx, SetupCtx}; +use std::collections::VecDeque; +use std::{any::Any, collections::HashMap, sync::Arc}; +use zisk_common::ChunkId; +use zisk_common::{ + BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, + InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, +}; +use zisk_core::ZiskOperationType; +use zisk_pil::Add256Trace; + +/// The `Add256Instance` struct represents an instance for the Add256 State Machine. +/// +/// It encapsulates the `Add256SM` and its associated context, and it processes input data +/// to compute witnesses for the Add256 State Machine. +pub struct Add256Instance { + /// Add256 state machine. + add256_sm: Arc>, + + /// Instance context. + ictx: InstanceCtx, +} + +impl Add256Instance { + /// Creates a new `Add256Instance`. + /// + /// # Arguments + /// * `add256_sm` - An `Arc`-wrapped reference to the Add256 State Machine. + /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. + /// * `bus_id` - The bus ID associated with this instance. + /// + /// # Returns + /// A new `Add256Instance` instance initialized with the provided state machine and + /// context. + pub fn new(add256_sm: Arc>, ictx: InstanceCtx) -> Self { + Self { add256_sm, ictx } + } + + pub fn build_add256_collector(&self, chunk_id: ChunkId) -> Add256Collector { + assert_eq!( + self.ictx.plan.air_id, + Add256Trace::::AIR_ID, + "Add256Instance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::>().unwrap(); + let (num_ops, collect_skipper) = collect_info[&chunk_id]; + Add256Collector::new(num_ops, collect_skipper) + } +} + +impl Instance for Add256Instance { + /// Computes the witness for the Add256 execution plan. + /// + /// This method leverages the `Add256SM` to generate an `AirInstance` using the collected + /// inputs. + /// + /// # Arguments + /// * `_pctx` - The proof context, unused in this implementation. + /// + /// # Returns + /// An `Option` containing the computed `AirInstance`. + fn compute_witness( + &self, + _pctx: &ProofCtx, + _sctx: &SetupCtx, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + ) -> Option> { + let inputs: Vec<_> = collectors + .into_iter() + .map(|(_, collector)| collector.as_any().downcast::().unwrap().inputs) + .collect(); + + Some(self.add256_sm.compute_witness(&inputs, trace_buffer)) + } + + /// Retrieves the checkpoint associated with this instance. + /// + /// # Returns + /// A `CheckPoint` object representing the checkpoint of the execution plan. + fn check_point(&self) -> &CheckPoint { + &self.ictx.plan.check_point + } + + /// Retrieves the type of this instance. + /// + /// # Returns + /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). + fn instance_type(&self) -> InstanceType { + InstanceType::Instance + } + + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { + assert_eq!( + self.ictx.plan.air_id, + Add256Trace::::AIR_ID, + "Add256Instance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::>().unwrap(); + let (num_ops, collect_skipper) = collect_info[&chunk_id]; + Some(Box::new(Add256Collector::new(num_ops, collect_skipper))) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +pub struct Add256Collector { + /// Collected inputs for witness computation. + inputs: Vec, + + /// The number of operations to collect. + num_operations: u64, + + /// Helper to skip instructions based on the plan's configuration. + collect_skipper: CollectSkipper, +} + +impl Add256Collector { + /// Creates a new `Add256Collector`. + /// + /// # Arguments + /// + /// * `bus_id` - The connected bus ID. + /// * `num_operations` - The number of operations to collect. + /// * `collect_skipper` - The helper to skip instructions based on the plan's configuration. + /// + /// # Returns + /// A new `ArithInstanceCollector` instance initialized with the provided parameters. + pub fn new(num_operations: u64, collect_skipper: CollectSkipper) -> Self { + Self { + inputs: Vec::with_capacity(num_operations as usize), + num_operations, + collect_skipper, + } + } +} + +impl BusDevice for Add256Collector { + /// Processes data received on the bus, collecting the inputs necessary for witness computation. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A tuple where: + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + fn process_data( + &mut self, + bus_id: &BusId, + data: &[PayloadType], + _pending: &mut VecDeque<(BusId, Vec)>, + _mem_collector_info: Option<&[MemCollectorInfo]>, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if self.inputs.len() == self.num_operations as usize { + return false; + } + + if data[OP_TYPE] as u32 != ZiskOperationType::BigInt as u32 { + return true; + } + + if self.collect_skipper.should_skip() { + return true; + } + + let data: ExtOperationData = + data.try_into().expect("Regular Metrics: Failed to convert data"); + if let ExtOperationData::OperationAdd256Data(data) = data { + self.inputs.push(Add256Input::from(&data)); + } else { + panic!("Expected ExtOperationData::OperationAdd256Data"); + } + + self.inputs.len() < self.num_operations as usize + } + + /// Returns the bus IDs associated with this instance. + /// + /// # Returns + /// A vector containing the connected bus ID. + fn bus_id(&self) -> Vec { + vec![OPERATION_BUS_ID] + } + + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/add256_manager.rs b/precompiles/dma/src/add256_manager.rs new file mode 100644 index 000000000..854ef7a94 --- /dev/null +++ b/precompiles/dma/src/add256_manager.rs @@ -0,0 +1,92 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use pil_std_lib::Std; +use zisk_common::{ + BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, + InstanceInfo, PayloadType, Planner, +}; +use zisk_core::ZiskOperationType; +use zisk_pil::Add256Trace; + +use crate::{Add256CounterInputGen, Add256Instance, Add256Planner, Add256SM}; + +/// The `Add256Manager` struct represents the Add256 manager, +/// which is responsible for managing the Add256 state machine and its table state machine. +#[allow(dead_code)] +pub struct Add256Manager { + /// Add256 state machine + add256_sm: Arc>, +} + +impl Add256Manager { + /// Creates a new instance of `Add256Manager`. + /// + /// # Returns + /// An `Arc`-wrapped instance of `Add256Manager`. + pub fn new(std: Arc>) -> Arc { + let add256_sm = Add256SM::new(std); + + Arc::new(Self { add256_sm }) + } + + pub fn build_add256_counter(&self) -> Add256CounterInputGen { + Add256CounterInputGen::new(BusDeviceMode::Counter) + } + + pub fn build_add256_input_generator(&self) -> Add256CounterInputGen { + Add256CounterInputGen::new(BusDeviceMode::InputGenerator) + } +} + +impl ComponentBuilder for Add256Manager { + /// Builds and returns a new counter for monitoring Add256 operations. + /// + /// # Returns + /// A boxed implementation of `RegularCounters` configured for Add256 operations. + fn build_counter(&self) -> Option> { + Some(Box::new(Add256CounterInputGen::new(BusDeviceMode::Counter))) + } + + /// Builds a planner to plan Add256-related instances. + /// + /// # Returns + /// A boxed implementation of `RegularPlanner`. + fn build_planner(&self) -> Box { + // Get the number of Add256s that a single Add256 instance can handle + let num_availables = self.add256_sm.num_availables; + + Box::new(Add256Planner::new().add_instance(InstanceInfo::new( + Add256Trace::::AIRGROUP_ID, + Add256Trace::::AIR_ID, + num_availables, + ZiskOperationType::BigInt, + ))) + } + + /// Builds an inputs data collector for Add256 operations. + /// + /// # Arguments + /// * `ictx` - The context of the instance, containing the plan and its associated + /// configurations. + /// + /// # Returns + /// A boxed implementation of `BusDeviceInstance` specific to the requested `air_id` instance. + /// + /// # Panics + /// Panics if the provided `air_id` is not supported. + fn build_instance(&self, ictx: InstanceCtx) -> Box> { + match ictx.plan.air_id { + id if id == Add256Trace::::AIR_ID => { + Box::new(Add256Instance::new(self.add256_sm.clone(), ictx)) + } + _ => { + panic!("Add256Builder::get_instance() Unsupported air_id: {:?}", ictx.plan.air_id) + } + } + } + + fn build_inputs_generator(&self) -> Option>> { + Some(Box::new(Add256CounterInputGen::new(BusDeviceMode::InputGenerator))) + } +} diff --git a/precompiles/dma/src/add256_planner.rs b/precompiles/dma/src/add256_planner.rs new file mode 100644 index 000000000..080e22067 --- /dev/null +++ b/precompiles/dma/src/add256_planner.rs @@ -0,0 +1,105 @@ +//! The `Add256Planner` module defines a planner for generating execution plans specific to +//! arithmetic operations. +//! +//! It organizes execution plans for both regular instances and table instances, +//! leveraging arithmetic operation counts and metadata to construct detailed plans. + +use std::any::Any; + +use crate::Add256CounterInputGen; + +use zisk_common::{ + plan, BusDeviceMetrics, ChunkId, InstCount, InstanceInfo, InstanceType, Metrics, Plan, Planner, +}; + +/// The `Add256Planner` struct organizes execution plans for arithmetic instances and tables. +/// +/// It allows adding metadata about instances and tables and generates plans +/// based on the provided counters. +#[derive(Default)] +pub struct Add256Planner { + /// Add256 instances info to be planned. + instances_info: Vec, +} + +impl Add256Planner { + /// Creates a new `Add256Planner`. + /// + /// # Returns + /// A new `Add256Planner` instance with no preconfigured instances or tables. + pub fn new() -> Self { + Self { instances_info: Vec::new() } + } + + /// Adds an Add256 instance to the planner. + /// + /// # Arguments + /// * `instance_info` - The `InstanceInfo` describing the add256 instance to be added. + /// + /// # Returns + /// The updated `Add256Planner` instance. + pub fn add_instance(mut self, instance_info: InstanceInfo) -> Self { + self.instances_info.push(instance_info); + self + } +} + +impl Planner for Add256Planner { + /// Generates execution plans for Add256 instances. + /// + /// # Arguments + /// * `counters` - A vector of counters, each associated with a `ChunkId` and `Add256Counter` + /// metrics data. + /// + /// # Returns + /// A vector of `Plan` instances representing execution configurations for the instances + /// + /// # Panics + /// Panics if any counter cannot be downcasted to an `Add256Counter`. + fn plan(&self, counters: Vec<(ChunkId, Box)>) -> Vec { + // Prepare counts + let mut count: Vec> = Vec::with_capacity(self.instances_info.len()); + + for _ in 0..self.instances_info.len() { + count.push(Vec::new()); + } + + counters.iter().for_each(|(chunk_id, counter)| { + let reg_counter = + Metrics::as_any(&**counter).downcast_ref::().unwrap(); + + // Iterate over `instances_info` and add `InstCount` objects to the correct vector + for (index, instance_info) in self.instances_info.iter().enumerate() { + if let Some(inst_count) = reg_counter.inst_count(instance_info.op_type) { + let inst_count = InstCount::new(*chunk_id, inst_count); + // Add the `InstCount` to the corresponding inner vector + count[index].push(inst_count); + } + } + }); + + let mut plan_result = Vec::new(); + + for (idx, instance) in self.instances_info.iter().enumerate() { + let plan: Vec<_> = plan(&count[idx], instance.num_ops as u64) + .into_iter() + .map(|(check_point, collect_info)| { + let converted: Box = Box::new(collect_info); + Plan::new( + instance.airgroup_id, + instance.air_id, + None, + InstanceType::Instance, + check_point, + Some(converted), + 1, + ) + }) + .collect(); + + plan_result.extend(plan); + } + + plan_result + } +} diff --git a/precompiles/dma/src/lib.rs b/precompiles/dma/src/lib.rs new file mode 100644 index 000000000..b1734294e --- /dev/null +++ b/precompiles/dma/src/lib.rs @@ -0,0 +1,17 @@ +mod add256; +mod add256_bus_device; +mod add256_constants; +mod add256_gen_mem_inputs; +mod add256_input; +mod add256_instance; +mod add256_manager; +mod add256_planner; + +pub use add256::*; +pub use add256_bus_device::*; +pub use add256_constants::*; +pub use add256_gen_mem_inputs::*; +pub use add256_input::*; +pub use add256_instance::*; +pub use add256_manager::*; +pub use add256_planner::*; diff --git a/precompiles/keccakf/pil/keccakf.pil b/precompiles/keccakf/pil/keccakf.pil index 8d3bc58e7..0c1824a9c 100644 --- a/precompiles/keccakf/pil/keccakf.pil +++ b/precompiles/keccakf/pil/keccakf.pil @@ -312,7 +312,7 @@ airtemplate Keccakf(const int N, const int chunks, const int bits, const int RC, ); // --> Constraints to make sure that this coprocessor is called from the main processor - lookup_proves(operation_bus_id, [KECCAKF_OP, step_addr'(STEP_MAIN), 0, step_addr'(ADDR_STATE), 0, 0, 0, 0], mul: in_use_clk_0); + lookup_proves(operation_bus_id, [KECCAKF_OP, 0, 0, step_addr'(ADDR_STATE), 0, 0, 0, 0, step_addr'(STEP_MAIN)], mul: in_use_clk_0); function clock_map(const expr cl, int pos, int start = 0, int end = -1, int delta = 0, int factor = 1): const expr { expr res = 0; diff --git a/precompiles/keccakf/src/keccakf_bus_device.rs b/precompiles/keccakf/src/keccakf_bus_device.rs index 2a1884298..14c62d6c7 100644 --- a/precompiles/keccakf/src/keccakf_bus_device.rs +++ b/precompiles/keccakf/src/keccakf_bus_device.rs @@ -4,10 +4,10 @@ use std::{collections::VecDeque, ops::Add}; -use zisk_common::MemCollectorInfo; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, }; +use zisk_common::{MemCollectorInfo, STEP}; use zisk_core::ZiskOperationType; use crate::{generate_keccakf_mem_inputs, skip_keccakf_mem_inputs}; @@ -120,7 +120,7 @@ impl BusDevice for KeccakfCounterInputGen { } } - let step_main = data[A]; + let step_main = data[STEP]; let addr_main = data[B] as u32; let only_counters = self.mode == BusDeviceMode::Counter; diff --git a/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs b/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs index bcb09bb72..c9ecc420d 100644 --- a/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs +++ b/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs @@ -3,7 +3,7 @@ use tiny_keccak::keccakf; use precompiles_common::MemBusHelpers; use std::collections::VecDeque; use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; +use zisk_common::{BusId, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; #[derive(Debug)] pub struct KeccakfMemInputConfig { @@ -22,7 +22,7 @@ pub fn generate_keccakf_mem_inputs( ) { // Get the basic data from the input // op,op_type,a,b,... - let state: &mut [u64; 25] = &mut data[4..29].try_into().unwrap(); + let state: &mut [u64; 25] = &mut data[5..30].try_into().unwrap(); // Apply the keccakf function keccakf(state); @@ -32,7 +32,7 @@ pub fn generate_keccakf_mem_inputs( let write_params = 1; let chunks_per_param = 25; let params_count = read_params + write_params; - let params_offset = OPERATION_BUS_DATA_SIZE; + let params_offset = OPERATION_PRECOMPILED_BUS_DATA_SIZE; for iparam in 0..params_count { let is_write = iparam >= read_params; let param_index = if is_write { iparam - read_params } else { iparam }; diff --git a/precompiles/keccakf/src/keccakf_input.rs b/precompiles/keccakf/src/keccakf_input.rs index ebbb38dc3..daeb20f76 100644 --- a/precompiles/keccakf/src/keccakf_input.rs +++ b/precompiles/keccakf/src/keccakf_input.rs @@ -10,9 +10,9 @@ pub struct KeccakfInput { impl KeccakfInput { pub fn from(values: &OperationKeccakData) -> Self { Self { - step_main: values[2], + step_main: values[4], addr_main: values[3] as u32, - state: values[4..29].try_into().unwrap(), + state: values[5..30].try_into().unwrap(), } } } diff --git a/precompiles/sha256f/pil/sha256f.pil b/precompiles/sha256f/pil/sha256f.pil index f7cd9e8aa..6f3c57767 100644 --- a/precompiles/sha256f/pil/sha256f.pil +++ b/precompiles/sha256f/pil/sha256f.pil @@ -250,7 +250,7 @@ airtemplate Sha256f(const int N = 2**22, const int RC = 2, const int RB = 32, ); // --> Constraints to make sure that this coprocessor is called from the main processor - lookup_proves(operation_bus_id, [SHA256F_OP, step_addr'(STEP_MAIN), 0, step_addr'(ADDR_OP), 0, 0, 0, 0], mul: in_use_clk_0); + lookup_proves(operation_bus_id, [SHA256F_OP, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(STEP_MAIN)], mul: in_use_clk_0); function pack(const expr a[]): expr { const int len = length(a); diff --git a/precompiles/sha256f/src/sha256f_bus_device.rs b/precompiles/sha256f/src/sha256f_bus_device.rs index e232b50b3..afbb389be 100644 --- a/precompiles/sha256f/src/sha256f_bus_device.rs +++ b/precompiles/sha256f/src/sha256f_bus_device.rs @@ -4,10 +4,10 @@ use std::{collections::VecDeque, ops::Add}; -use zisk_common::MemCollectorInfo; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, }; +use zisk_common::{MemCollectorInfo, STEP}; use zisk_core::ZiskOperationType; use crate::{generate_sha256f_mem_inputs, skip_sha256f_mem_inputs}; @@ -120,7 +120,7 @@ impl BusDevice for Sha256fCounterInputGen { } } - let step_main = data[A]; + let step_main = data[STEP]; let addr_main = data[B] as u32; let only_counters = self.mode == BusDeviceMode::Counter; diff --git a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs index d6afd10d9..be0171f94 100644 --- a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs +++ b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs @@ -4,7 +4,7 @@ use sha2::compress256; use precompiles_common::MemBusHelpers; use std::collections::VecDeque; use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; +use zisk_common::{BusId, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; use zisk_core::{convert_u32_to_u64, convert_u64_to_generic_array_bytes, convert_u64_to_u32}; #[derive(Debug)] @@ -25,8 +25,8 @@ pub fn generate_sha256f_mem_inputs( ) { // Get the basic data from the input // op,op_type,a,b,addr[2],... - let state: &mut [u64; 4] = &mut data[6..10].try_into().unwrap(); - let input: &[u64; 8] = &data[10..18].try_into().unwrap(); + let state: &mut [u64; 4] = &mut data[7..11].try_into().unwrap(); + let input: &[u64; 8] = &data[11..19].try_into().unwrap(); // Apply the sha256f function and get the output let mut state_u32: [u32; 8] = convert_u64_to_u32(state).try_into().unwrap(); @@ -43,7 +43,7 @@ pub fn generate_sha256f_mem_inputs( MemBusHelpers::mem_aligned_load( addr_main + iparam as u32 * 8, step_main, - data[OPERATION_BUS_DATA_SIZE + iparam], + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], pending, ); } @@ -53,12 +53,12 @@ pub fn generate_sha256f_mem_inputs( let write_params = 1; let chunks_per_param = [4usize, 8, 4]; let params_count = read_params + write_params; - let params_offset = OPERATION_BUS_DATA_SIZE + indirect_params; + let params_offset = OPERATION_PRECOMPILED_BUS_DATA_SIZE + indirect_params; let mut read_chunks = 0; for (iparam, &chunks) in chunks_per_param.iter().enumerate().take(params_count) { let is_write = iparam >= read_params; let param_index = if is_write { iparam - read_params } else { iparam }; - let param_addr = data[OPERATION_BUS_DATA_SIZE + param_index] as u32; + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32; // read/write all chunks of the iparam parameter let current_param_offset = if is_write { // if write calculate index over write_data @@ -110,7 +110,7 @@ pub fn skip_sha256f_mem_inputs( for (iparam, &chunks) in chunks_per_param.iter().enumerate().take(read_params + write_params) { let is_write = iparam >= read_params; let param_index = if is_write { iparam - read_params } else { iparam }; - let param_addr = data[OPERATION_BUS_DATA_SIZE + param_index] as u32; + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32; for ichunk in 0..chunks { let addr = param_addr + ichunk as u32 * 8; diff --git a/precompiles/sha256f/src/sha256f_input.rs b/precompiles/sha256f/src/sha256f_input.rs index 9e2ba78e3..f3e8229c3 100644 --- a/precompiles/sha256f/src/sha256f_input.rs +++ b/precompiles/sha256f/src/sha256f_input.rs @@ -13,12 +13,12 @@ pub struct Sha256fInput { impl Sha256fInput { pub fn from(values: &OperationSha256Data) -> Self { Self { - step_main: values[2], + step_main: values[4], addr_main: values[3] as u32, - state_addr: values[4] as u32, - input_addr: values[5] as u32, - state: values[6..10].try_into().unwrap(), - input: values[10..18].try_into().unwrap(), + state_addr: values[5] as u32, + input_addr: values[6] as u32, + state: values[7..11].try_into().unwrap(), + input: values[11..19].try_into().unwrap(), } } } diff --git a/state-machines/arith/pil/arith.pil b/state-machines/arith/pil/arith.pil index 713ebf104..5fc682ebc 100644 --- a/state-machines/arith/pil/arith.pil +++ b/state-machines/arith/pil/arith.pil @@ -269,7 +269,7 @@ airtemplate Arith(int N = 2**18, const int operation_bus_id = OPERATION_BUS_ID, bus_a0, bus_a1, bus_b0, bus_b1, bus_res0, bus_res1, - div_by_zero /*+ div_overflow*/], mul: multiplicity); + div_by_zero /*+ div_overflow*/, 0], mul: multiplicity); // Check that remainder (d) is lower than divisor (b) when division is performed // Specifically, we ensure that 0 <= |d| < |b| @@ -277,7 +277,7 @@ airtemplate Arith(int N = 2**18, const int operation_bus_id = OPERATION_BUS_ID, (d[0] + CHUNK_SIZE * d[1]), (d[2] + CHUNK_SIZE * d[3]) + m32 * nr * 0xFFFFFFFF, // remainder (b[0] + CHUNK_SIZE * b[1]), (b[2] + CHUNK_SIZE * b[3]) + m32 * nb * 0xFFFFFFFF, // divisor 1, 0, - 1], sel: div * (1 - div_by_zero)); + 1, 0], sel: div * (1 - div_by_zero)); for (int index = 0; index < length(carry); ++index) { arith_range_table_assumes(ARITH_RANGE_CARRY, carry[index]); // TODO: review carry range diff --git a/state-machines/arith/pil/arith_mul64.pil b/state-machines/arith/pil/arith_mul64.pil index e5a10a8b2..193b37c91 100644 --- a/state-machines/arith/pil/arith_mul64.pil +++ b/state-machines/arith/pil/arith_mul64.pil @@ -210,7 +210,7 @@ airtemplate ArithMul64(int N = 2**18, const int operation_bus_id, const int dual (d[0] + CHUNK_SIZE * d[1]), (d[2] + CHUNK_SIZE * d[3]) + m32 * nr * 0xFFFFFFFF, // remainder (b[0] + CHUNK_SIZE * b[1]), (b[2] + CHUNK_SIZE * b[3]) + m32 * nb * 0xFFFFFFFF, // divisor 1, 0, - 1], sel: div * (1 - div_by_zero)); + 1, 0], sel: div * (1 - div_by_zero)); for (int index = 0; index < length(carry); ++index) { arith_range_table_assumes(ARITH_RANGE_CARRY, carry[index]); // TODO: review carry range diff --git a/state-machines/binary/pil/binary.pil b/state-machines/binary/pil/binary.pil index 81f8bf5a6..1f089d26d 100644 --- a/state-machines/binary/pil/binary.pil +++ b/state-machines/binary/pil/binary.pil @@ -171,5 +171,5 @@ airtemplate Binary(const int N = 2**21, const int operation_bus_id = OPERATION_B expr op = m_op + 0x20 * mode32; col witness multiplicity; - lookup_proves(OPERATION_BUS_ID, [op, ...a, ...b, ...c, cout - result_is_a], multiplicity); + lookup_proves(operation_bus_id, [op, ...a, ...b, ...c, cout - result_is_a, 0], multiplicity); } \ No newline at end of file diff --git a/state-machines/binary/pil/binary_add.pil b/state-machines/binary/pil/binary_add.pil index 4860e5ea0..8871c0670 100644 --- a/state-machines/binary/pil/binary_add.pil +++ b/state-machines/binary/pil/binary_add.pil @@ -24,5 +24,5 @@ airtemplate BinaryAdd(const int N = 2**21, const int operation_bus_id = OPERATIO } col witness multiplicity; - lookup_proves(operation_bus_id, [operation_code, ...a, ...b, ...c, 0], multiplicity); + lookup_proves(operation_bus_id, [operation_code, ...a, ...b, ...c, 0, 0], multiplicity); } \ No newline at end of file diff --git a/state-machines/binary/pil/binary_extension.pil b/state-machines/binary/pil/binary_extension.pil index a6a84c503..1ea21fd23 100644 --- a/state-machines/binary/pil/binary_extension.pil +++ b/state-machines/binary/pil/binary_extension.pil @@ -98,7 +98,7 @@ airtemplate BinaryExtension(const int N = 2**18, const int operation_bus_id = OP op_is_shift * (in2[1] - in1_high) + in1_high, out[0][0] + out[1][0] + out[2][0] + out[3][0] + out[4][0] + out[5][0] + out[6][0] + out[7][0], out[0][1] + out[1][1] + out[2][1] + out[3][1] + out[4][1] + out[5][1] + out[6][1] + out[7][1], - 0 + 0, 0 ], multiplicity ); diff --git a/state-machines/frequent-ops/pil/frequent_ops.pil b/state-machines/frequent-ops/pil/frequent_ops.pil index 5ea44fa3e..999ce7e09 100644 --- a/state-machines/frequent-ops/pil/frequent_ops.pil +++ b/state-machines/frequent-ops/pil/frequent_ops.pil @@ -13,5 +13,8 @@ airtemplate FrequentOps(int N = 2**24, int RC = 2, const int operation_bus_id = col fixed C[RC]; col fixed FLAG; - lookup_proves(operation_bus_id, [OP, ...A, ...B, ...C, FLAG], mul: multiplicity, table_id: table_id); + // REVIEW: REPLACE BY CONSTANT + col fixed __ZERO__ = [0...]; + + lookup_proves(operation_bus_id, [OP, ...A, ...B, ...C, FLAG, __ZERO__], mul: multiplicity, table_id: table_id); } \ No newline at end of file diff --git a/state-machines/main/pil/main.pil b/state-machines/main/pil/main.pil index 8dd5f3f24..cfa0ce3ec 100644 --- a/state-machines/main/pil/main.pil +++ b/state-machines/main/pil/main.pil @@ -112,7 +112,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, } else { col witness air.a_imm1; // The higher 32-bit part of a immediate value } - col witness a_src_step; // Selector to load the `step` in register `a` + col witness op_with_step; // Selector to include step on operation bus // Source B @@ -324,7 +324,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, // Sent to bus the external operation - lookup_assumes(operation_bus_id, [op, a[0], (1 - m32) * a[1], b[0], (1 - m32) * b[1], ...c, flag], sel: is_external_op); + lookup_assumes(operation_bus_id, [op, a[0], (1 - m32) * a[1], b[0], (1 - m32) * b[1], ...c, flag, STEP * op_with_step], sel: is_external_op); const expr a_src_c; const expr b_src_c; @@ -338,12 +338,12 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, // Optimization to avoid use extra columns when c is source for a or b. if (stack_enabled) { - a_src_c = 1 - a_src_step - a_src_mem - a_src_imm - a_src_sp; + a_src_c = 1 - a_src_mem - a_src_imm - a_src_sp; b_src_c = 1 - b_src_mem - b_src_imm - b_src_ind - b_src_reg; a_imm[1] = a_use_sp_imm1; b_imm[1] = b_use_sp_imm1; } else { - a_src_c = 1 - a_src_step - a_src_mem - a_src_imm - a_src_reg; + a_src_c = 1 - a_src_mem - a_src_imm - a_src_reg; b_src_c = 1 - b_src_mem - b_src_imm - b_src_ind - b_src_reg; a_imm[1] = a_imm1; b_imm[1] = b_imm1; @@ -358,9 +358,6 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, a_src_sp * (a[index] - (index == 0 ? sp: 0 )) === 0; } - // If source is step, value must be same as STEP - a_src_step * (a[index] - (index == 0 ? STEP : 0)) === 0; - // If source is c, value must be same as previous_c a_src_c * (a[index] - previous_c) === 0; b_src_c * (b[index] - previous_c) === 0; @@ -446,7 +443,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, a_src_imm * (1 - a_src_imm) === 0; a_src_mem * (1 - a_src_mem) === 0; - a_src_step * (1 - a_src_step) === 0; + op_with_step * (1 - op_with_step) === 0; b_src_imm * (1 - b_src_imm) === 0; b_src_mem * (1 - b_src_mem) === 0; is_external_op * (1 - is_external_op) === 0; @@ -460,7 +457,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, b_src_reg * (1 - b_src_reg) === 0; store_reg * (1 - store_reg) === 0; - const expr rom_flags = 1 + 2 * a_src_imm + 4 * a_src_mem + 8 * a_src_step + 16 * b_src_imm + const expr rom_flags = 1 + 2 * a_src_imm + 4 * a_src_mem + 8 * op_with_step + 16 * b_src_imm + 32 * b_src_mem + 64 * is_external_op + 128 * store_ra + 256 * store_mem + 512 * store_ind + 1024 * set_pc + 2048 * m32 + 4096 * b_src_ind + 8192 * a_src_reg + 16384 * b_src_reg + 32768 * store_reg; diff --git a/state-machines/mem-cpp/cpp/mem_config.hpp b/state-machines/mem-cpp/cpp/mem_config.hpp index a1511dda4..92acf2ba4 100644 --- a/state-machines/mem-cpp/cpp/mem_config.hpp +++ b/state-machines/mem-cpp/cpp/mem_config.hpp @@ -47,7 +47,7 @@ #define ADDR_SLOT_BITS 5 #define ADDR_SLOT_SIZE (1 << ADDR_SLOT_BITS) #define ADDR_SLOT_MASK (0xFFFFFFFF << ADDR_SLOT_BITS) -#define ADDR_SLOTS ((1024 * 1024 * 32) / MAX_THREADS) +#define ADDR_SLOTS ((1024 * 1024 * 64) / MAX_THREADS) #define ADDR_SLOTS_SIZE (ADDR_SLOT_SIZE * ADDR_SLOTS) #define TIME_US_BY_CHUNK 173 From 9047629f2b6f033029e7b68a4b82ff1dd6266d44 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 21 Oct 2025 20:59:55 +0200 Subject: [PATCH 005/782] Implement precompile results in assembly --- core/src/zisk_rom_2_asm.rs | 467 ++++++++++++++++++++++++++++--------- emulator-asm/src/emu.c | 8 +- emulator-asm/src/main.c | 259 +++++++++++++++++++- 3 files changed, 619 insertions(+), 115 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 604ad842e..da629c206 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -116,9 +116,10 @@ pub struct ZiskAsmContext { mem_chunk_address: String, mem_chunk_start_step: String, fcall_ctx: String, - mem_chunk_id: String, // 0, 1, 2, 3, 4... - mem_chunk_mask: String, // Module 8 of the chunks we want to activate, e.g. 0x03 - mem_rsp: String, // Backup of rsp register value from caller + mem_chunk_id: String, // 0, 1, 2, 3, 4... + mem_chunk_mask: String, // Module 8 of the chunks we want to activate, e.g. 0x03 + mem_rsp: String, // Backup of rsp register value from caller + mem_precompile_results_address: String, // Address where precompile results are read from comments: bool, // true if we want to generate comments in the assembly source code boc: String, // begin of comment: '/*', ';', '#', etc. @@ -210,6 +211,10 @@ impl ZiskAsmContext { | ZiskOp::Bls12_381ComplexMul ) } + + pub fn precompile_results(&self) -> bool { + true + } } // One-pass (single emulation) memory trace, used to count, plan and collect. @@ -431,6 +436,8 @@ impl ZiskRom2Asm { ctx.mem_chunk_id = format!("qword {}[MEM_CHUNK_ID]", ctx.ptr); ctx.mem_chunk_mask = format!("qword {}[chunk_mask]", ctx.ptr); ctx.mem_rsp = format!("qword {}[MEM_RSP]", ctx.ptr); + ctx.mem_precompile_results_address = + format!("qword {}[MEM_PRECOMPILE_RESULTS_ADDRESS]", ctx.ptr); // Preamble *code += ".intel_syntax noprefix\n"; @@ -447,6 +454,7 @@ impl ZiskRom2Asm { *code += ".comm MEM_CHUNK_ADDRESS, 8, 8\n"; *code += ".comm MEM_CHUNK_START_STEP, 8, 8\n"; *code += ".comm MEM_RSP, 8, 8\n"; + *code += ".comm MEM_PRECOMPILE_RESULTS_ADDRESS, 8, 8\n"; if ctx.zip() { *code += ".comm MEM_CHUNK_ID, 8, 8\n"; } @@ -530,6 +538,9 @@ impl ZiskRom2Asm { *code += ".extern chunk_size\n"; *code += ".extern trace_address\n\n"; *code += ".extern trace_address_threshold\n\n"; + if ctx.precompile_results() { + *code += ".extern precompile_result_address\n\n"; + } } if ctx.zip() { @@ -692,6 +703,22 @@ impl ZiskRom2Asm { ctx.mem_chunk_start_step, ctx.comment_str("chunk_start_step = 0") ); + if ctx.precompile_results() { + *code += &format!( + "\tmov {}, precompile_results_address {}\n", + REG_AUX, + ctx.comment_str("aux = precompile_results_address") + ); + *code += &format!("\tadd {}, 8 {}\n", REG_AUX, ctx.comment_str("aux += 8")); + *code += &format!( + "\tmov {}, {} {}\n", + ctx.mem_precompile_results_address, + REG_AUX, + ctx.comment_str( + "mem_precompile_results_counter = precompile_results_address + " + ) + ); + } } *code += &ctx.full_line_comment("fcall_context initialization".to_string()); @@ -4970,12 +4997,17 @@ impl ZiskRom2Asm { Self::mem_op_array(ctx, code, REG_ADDRESS, true, 8, 25); } - // Call the keccak function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_keccak\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + Self::precompile_results_array(ctx, code, "rdi", 25); + } else { + // Call the keccak function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_keccak\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -5048,12 +5080,18 @@ impl ZiskRom2Asm { Self::mem_op_precompiled_write(ctx, code, 2, 0, 0, 4); } - // Call the SHA256 function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_sha256\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi]\n"); + Self::precompile_results_array(ctx, code, "rdi", 4); + } else { + // Call the SHA256 function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_sha256\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -5158,12 +5196,21 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the arith256 function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_arith256\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov {REG_FLAG}, [rdi+3*8]\n"); + Self::precompile_results_array(ctx, code, REG_FLAG, 4); + *code += &format!("\tmov {REG_FLAG}, [rdi+4*8]\n"); + Self::precompile_results_array(ctx, code, REG_FLAG, 4); + *code += &format!("\tmov {REG_FLAG}, 0\n"); // Is this needed? + } else { + // Call the arith256 function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_arith256\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Set result @@ -5214,12 +5261,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the arith256_mod function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_arith256_mod\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi + 4*8]\n"); + Self::precompile_results_array(ctx, code, "rdi", 4); + } else { + // Call the arith256_mod function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_arith256_mod\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -5293,12 +5346,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the secp256k1_add function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_secp256k1_add\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi]\n"); + Self::precompile_results_array(ctx, code, "rdi", 8); + } else { + // Call the secp256k1_add function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_secp256k1_add\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -5393,12 +5452,17 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the secp256k1_dbl function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_secp256k1_dbl\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + Self::precompile_results_array(ctx, code, "rdi", 8); + } else { + // Call the secp256k1_dbl function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_secp256k1_dbl\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -5532,12 +5596,17 @@ impl ZiskRom2Asm { ctx.comment_str("rdi = fcall context") ); - // Call the fcall function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_fcall\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + Self::precompile_results_fcall(ctx, code, "rdi"); + } else { + // Call the fcall function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_fcall\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } // Get free input address *code += &format!( @@ -5696,12 +5765,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the bn254_curve_add function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_bn254_curve_add\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi]\n"); + Self::precompile_results_array(ctx, code, "rdi", 8); + } else { + // Call the bn254_curve_add function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_bn254_curve_add\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -5797,12 +5872,17 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the bn254_curve_dbl function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_bn254_curve_dbl\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + Self::precompile_results_array(ctx, code, "rdi", 8); + } else { + // Call the bn254_curve_dbl function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_bn254_curve_dbl\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -5877,12 +5957,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the bn254_complex_add function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_bn254_complex_add\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi]\n"); + Self::precompile_results_array(ctx, code, "rdi", 8); + } else { + // Call the bn254_complex_add function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_bn254_complex_add\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -5957,12 +6043,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the bn254_complex_sub function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_bn254_complex_sub\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi]\n"); + Self::precompile_results_array(ctx, code, "rdi", 8); + } else { + // Call the bn254_complex_sub function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_bn254_complex_sub\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -6037,12 +6129,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the bn254_complex_mul function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_bn254_complex_mul\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi]\n"); + Self::precompile_results_array(ctx, code, "rdi", 8); + } else { + // Call the bn254_complex_mul function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_bn254_complex_mul\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -6125,12 +6223,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the arith384_mod function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_arith384_mod\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi + 4*8]\n"); + Self::precompile_results_array(ctx, code, "rdi", 6); + } else { + // Call the arith384_mod function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_arith384_mod\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -6205,12 +6309,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the bls12_381_curve_add function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_bls12_381_curve_add\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi]\n"); + Self::precompile_results_array(ctx, code, "rdi", 12); + } else { + // Call the bls12_381_curve_add function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_bls12_381_curve_add\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -6306,12 +6416,17 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the bls12_381_curve_dbl function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_bls12_381_curve_dbl\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + Self::precompile_results_array(ctx, code, "rdi", 12); + } else { + // Call the bls12_381_curve_dbl function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_bls12_381_curve_dbl\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -6390,12 +6505,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the bls12_381_complex_add function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_bls12_381_complex_add\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi]\n"); + Self::precompile_results_array(ctx, code, "rdi", 12); + } else { + // Call the bls12_381_complex_add function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_bls12_381_complex_add\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -6474,12 +6595,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the bls12_381_complex_sub function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_bls12_381_complex_sub\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi]\n"); + Self::precompile_results_array(ctx, code, "rdi", 12); + } else { + // Call the bls12_381_complex_sub function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_bls12_381_complex_sub\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -6558,12 +6685,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the bls12_381_complex_mul function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_bls12_381_complex_mul\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi]\n"); + Self::precompile_results_array(ctx, code, "rdi", 12); + } else { + // Call the bls12_381_complex_mul function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_bls12_381_complex_mul\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -7571,6 +7704,118 @@ impl ZiskRom2Asm { ); } + /**********************/ + /* PRECOMPILE RESULTS */ + /**********************/ + + // Copies size u64 elements from precompile_results_address to the address in reg_address, + // and increments precompile_results_address by size*8 + fn precompile_results_array( + ctx: &mut ZiskAsmContext, + code: &mut String, + reg_address: &str, + size: u64, + ) { + *code += &format!( + "\tmov {}, {} {}\n", + REG_AUX, + ctx.mem_precompile_results_address, + ctx.comment_str("aux = precompile_results_address") + ); + for k in 0..size { + *code += &format!( + "\tmov {}, [{} + {}*8] {}\n", + REG_VALUE, + REG_AUX, + k, + ctx.comment(format!("value = precompile_results[{}]", k)) + ); + *code += &format!( + "\tmov [{} + {}*8], {} {}\n", + reg_address, + k, + REG_VALUE, + ctx.comment(format!("addr[{}] = value", k)) + ); + } + *code += &format!( + "\tadd {}, {}*8 {}\n", + REG_AUX, + size, + ctx.comment(format!("aux += {}*8", size)) + ); + *code += &format!( + "\tmov {}, {} {}\n", + ctx.mem_precompile_results_address, + REG_AUX, + ctx.comment_str("precompile_results_address = aux") + ); + } + + // Copies the fcall result size and result data to the fcall structure + // address in reg_address, + fn precompile_results_fcall(ctx: &mut ZiskAsmContext, code: &mut String, reg_address: &str) { + *code += &format!( + "\tmov {}, {} {}\n", + REG_AUX, + ctx.mem_precompile_results_address, + ctx.comment_str("aux = precompile_results_address") + ); + *code += &format!("\tmov {REG_ADDRESS}, {reg_address}\n"); + + // Copy the result size, store it in register B + *code += &format!( + "\tmov {}, [{}] {}\n", + REG_B, + REG_AUX, + ctx.comment(format!("b = precompile_results[0]")) + ); + *code += &format!( + "\tmov [{} + {}*8], {} {}\n", + REG_ADDRESS, + FCALL_RESULT_SIZE, + REG_B, + ctx.comment(format!("fcall[result_size] = b")) + ); + *code += &format!("\tadd {}, 1*8 {}\n", REG_AUX, ctx.comment_str("aux += 1*8")); + + // Copy data consuming B u64's starting at A=0 + *code += &format!("\txor {}, {} {}\n", REG_A, REG_A, ctx.comment_str("a = 0")); + + *code += &format!("pc_{:x}_fcall_copy_params_loop_start:\n", ctx.pc); + + *code += &format!("\tcmp {}, {} {}\n", REG_A, REG_B, ctx.comment_str("a =? b")); + *code += &format!("\tje pc_{:x}_fcall_copy_params_loop_end\n", ctx.pc); + + *code += &format!( + "\tmov {}, [{} + {}*8] {}\n", + REG_VALUE, + REG_AUX, + REG_A, + ctx.comment(format!("value = precompile_results[]")) + ); + *code += &format!( + "\tmov [{} + {}*8 + {}*8], {} {}\n", + REG_ADDRESS, + REG_A, + FCALL_RESULT, + REG_VALUE, + ctx.comment(format!("addr[] = value")) + ); + *code += &format!("\tinc {} {}\n", REG_A, ctx.comment_str("a += 1")); + + *code += &format!("\tjmp pc_{:x}_fcall_copy_params_loop_start\n", ctx.pc); + *code += &format!("pc_{:x}_fcall_copy_params_loop_end:\n", ctx.pc); + *code += &format!("\tshl {}, 3 {}\n", REG_A, ctx.comment_str("a *= 8")); + *code += &format!("\tadd {}, {} {}\n", REG_AUX, REG_A, ctx.comment_str("aux += size*8")); + *code += &format!( + "\tmov {}, {} {}\n", + ctx.mem_precompile_results_address, + REG_AUX, + ctx.comment_str("precompile_results_address = aux") + ); + } + /*******************/ /* CHUNK START/END */ /*******************/ diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index e704ef86c..2a2d41221 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -495,7 +495,7 @@ extern int _opcode_sha256(uint64_t * address) { // Load result from cache precompile_cache_load((uint8_t *)address[0], 4*8); - } + } #endif #ifdef DEBUG @@ -889,13 +889,13 @@ extern int _opcode_fcall(struct FcallContext * ctx) #ifdef ASM_PRECOMPILE_CACHE // Store result in cache - precompile_cache_store((uint8_t *)&ctx->result_size, 8*8); + precompile_cache_store((uint8_t *)&ctx->result_size, 1*8); precompile_cache_store((uint8_t *)&ctx->result, ctx->result_size*8); } else if (precompile_cache_loading) { // Load result from cache - precompile_cache_load((uint8_t *)&ctx->result_size, 8*8); + precompile_cache_load((uint8_t *)&ctx->result_size, 1*8); precompile_cache_load((uint8_t *)&ctx->result, ctx->result_size*8); } #endif @@ -908,6 +908,7 @@ extern int _opcode_fcall(struct FcallContext * ctx) return iresult; } +/* extern int _opcode_inverse_fp_ec(uint64_t params, uint64_t result) { #ifdef ASM_CALL_METRICS @@ -1049,6 +1050,7 @@ extern int _opcode_sqrt_fp_ec_parity(uint64_t params, uint64_t result) #endif return 0; } +*/ /*********/ /* BN254 */ diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 4617fe40d..6d3a4994e 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -55,6 +55,7 @@ uint8_t * pRom = (uint8_t *)ROM_ADDR; uint64_t * pInputTrace = (uint64_t *)TRACE_ADDR; uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; +// Assembly service request/response types #define TYPE_PING 1 // Ping #define TYPE_PONG 2 #define TYPE_MT_REQUEST 3 // Minimal trace @@ -76,7 +77,7 @@ uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; #define TYPE_SD_REQUEST 1000000 // Shutdown #define TYPE_SD_RESPONSE 1000001 -// Generation method +// Generation method, to be used with mandatory argument --gen= typedef enum { Fast = 0, MinimalTrace = 1, @@ -207,6 +208,12 @@ int map_locked_flag = MAP_LOCKED; bool precompile_cache_enabled = false; #endif +#define PRECOMPILE_RESULTS + +#ifdef PRECOMPILE_RESULTS +uint64_t * precompile_results_address = NULL; +#endif + void set_chunk_size (uint64_t new_chunk_size) { if (!is_power_of_two(new_chunk_size)) @@ -302,6 +309,20 @@ int process_id = 0; uint64_t input_size = 0; +#ifdef PRECOMPILE_RESULTS + +#define MAX_PRECOMPILE_SIZE (uint64_t)0x08000000 // 128MB + +// Precompile results memory +char shmem_precompile_name[128]; +int shmem_precompile_fd = -1; +uint64_t shmem_precompile_size = 0; +void * shmem_precompile_address = NULL; + +char precompile_file[4096] = {0}; + +#endif + int main(int argc, char *argv[]) { #ifdef DEBUG @@ -818,6 +839,9 @@ void print_usage (void) #ifdef ASM_PRECOMPILE_CACHE printf("\t--precompile-cache-store store precompile results in cache file\n"); printf("\t--precompile-cache-load load precompile results from cache file\n"); +#endif +#ifdef PRECOMPILE_RESULTS + printf("\t-r \n"); #endif printf("\t-h/--help print this\n"); } @@ -1141,6 +1165,26 @@ void parse_arguments(int argc, char *argv[]) continue; } +#endif +#ifdef PRECOMPILE_RESULTS + if (strcmp(argv[i], "-r") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -r in the last position; please provide precompile results file after it\n"); + print_usage(); + exit(-1); + } + if (strlen(argv[i]) > 4095) + { + printf("ERROR: Detected argument -r but next argument is too long\n"); + print_usage(); + exit(-1); + } + strcpy(precompile_file, argv[i]); + continue; + } #endif printf("ERROR: parse_arguments() Unrecognized argument: %s\n", argv[i]); print_usage(); @@ -1201,6 +1245,17 @@ void parse_arguments(int argc, char *argv[]) fflush(stderr); exit(-1); } + +#ifdef PRECOMPILE_RESULTS + if (client && (strlen(precompile_file) == 0)) + { + printf("ERROR! parse_arguments() when in precompile results mode, you need to provide a precompile results file using -r \n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } +#endif } void configure (void) @@ -1212,6 +1267,10 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_FT_input"); +#ifdef PRECOMPILE_RESULTS + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_FT_precompile"); +#endif strcpy(shmem_output_name, ""); strcpy(sem_chunk_done_name, ""); strcpy(sem_shutdown_done_name, shm_prefix); @@ -1224,6 +1283,10 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MT_input"); +#ifdef PRECOMPILE_RESULTS + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_MT_precompile"); +#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1239,6 +1302,10 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_RH_input"); +#ifdef PRECOMPILE_RESULTS + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_RH_precompile"); +#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_RH_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1254,6 +1321,10 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MA_input"); +#ifdef PRECOMPILE_RESULTS + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_MA_precompile"); +#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MA_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1269,6 +1340,9 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_CH_input"); +#ifdef PRECOMPILE_RESULTS + strcpy(shmem_precompile_name, ""); +#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CH_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1293,6 +1367,10 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_ZP_input"); +#ifdef PRECOMPILE_RESULTS + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_ZP_precompile"); +#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_ZP_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1308,6 +1386,10 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MO_input"); +#ifdef PRECOMPILE_RESULTS + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_MO_precompile"); +#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MO_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1322,6 +1404,9 @@ void configure (void) case ChunkPlayerMTCollectMem: { strcpy(shmem_input_name, ""); +#ifdef PRECOMPILE_RESULTS + strcpy(shmem_precompile_name, ""); +#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CM_output"); strcpy(sem_chunk_done_name, ""); @@ -1336,6 +1421,10 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MT_input"); +#ifdef PRECOMPILE_RESULTS + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_MT_precompile"); +#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1350,6 +1439,9 @@ void configure (void) case ChunkPlayerMemReadsCollectMain: { strcpy(shmem_input_name, ""); +#ifdef PRECOMPILE_RESULTS + strcpy(shmem_precompile_name, ""); +#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CA_output"); strcpy(sem_chunk_done_name, ""); @@ -1560,6 +1652,119 @@ void client_run (void) } +#ifdef PRECOMPILE_RESULTS + + /*****************************/ + /* Read precompile file data */ + /*****************************/ + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + + // Open input file + FILE * precompile_fp = fopen(precompile_file, "r"); + if (precompile_fp == NULL) + { + printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", precompile_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Get input file size + if (fseek(precompile_fp, 0, SEEK_END) == -1) + { + printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", precompile_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + long precompile_data_size = ftell(precompile_fp); + if (precompile_data_size == -1) + { + printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", precompile_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Go back to the first byte + if (fseek(precompile_fp, 0, SEEK_SET) == -1) + { + printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", precompile_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check the input data size is inside the proper range + if (precompile_data_size > (MAX_PRECOMPILE_SIZE - 8)) + { + printf("ERROR: Size of precompile file (%s) is too long (%lu)\n", precompile_file, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Open input shared memory + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map the shared memory object into the process address space + shmem_precompile_address = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmem_precompile_fd, 0); + if (shmem_precompile_address == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Write the input size in the first 64 bits + *(uint64_t *)shmem_precompile_address = (uint64_t)precompile_data_size; + + // Copy input data into input memory + size_t precompile_read = fread(shmem_precompile_address + 8, 1, precompile_data_size, precompile_fp); + if (precompile_read != precompile_data_size) + { + printf("ERROR: Input read (%lu) != input file size (%lu)\n", precompile_read, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Close the file pointer + fclose(precompile_fp); + + // Unmap input + result = munmap(shmem_precompile_address, MAX_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + printf("client (precompile): done in %lu us\n", duration); +#endif + + } + +#endif + /*************************/ /* Connect to the server */ /*************************/ @@ -2480,6 +2685,58 @@ void server_setup (void) if (verbose) printf("mmap(input) mapped %lu B and returned address %p in %lu us\n", MAX_INPUT_SIZE, pInput, duration); } +#ifdef PRECOMPILE_RESULTS + + /**********************/ + /* PRECOMPILE_RESULTS */ + /**********************/ + + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_precompile_name); + + // Create the precompile results shared memory + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR | O_CREAT, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_precompile_fd, MAX_PRECOMPILE_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pPrecompile == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + precompile_results_address = (uint64_t *)pPrecompile; + if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); + } + +#endif // PRECOMPILE_RESULTS + /*******/ /* RAM */ /*******/ From a8bd64a91fe16b0d0981ee60c21a68a845a9df7d Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 21 Oct 2025 22:02:29 +0200 Subject: [PATCH 006/782] Implement Add256 precompile results --- core/src/zisk_rom_2_asm.rs | 51 ++++++++++++++++++++++++++++++++------ emulator-asm/src/emu.c | 25 +++++++++++-------- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 80818b612..673091410 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -6739,14 +6739,25 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the add256 function - Self::push_internal_registers_except_c_and_flag(ctx, code, false); - // Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_add256\n"; - *code += &format!("\tmov {}, rax {}\n", REG_C, ctx.comment_str("c = rax")); - *code += - &format!("\tmov {}, rax {}\n", REG_FLAG, ctx.comment_str("flag = rax")); - Self::pop_internal_registers_except_c_and_flag(ctx, code, false); + // Get result from precompile results data + if ctx.precompile_results() { + *code += &format!("\tmov rdi, [rdi+3*8]\n"); + Self::precompile_results_array(ctx, code, "rdi", 4); + Self::precompile_results_register(ctx, code, REG_C); + } else { + // Call the add256 function + Self::push_internal_registers_except_c_and_flag(ctx, code, false); + // Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_add256\n"; + *code += &format!("\tmov {}, rax {}\n", REG_C, ctx.comment_str("c = rax")); + Self::pop_internal_registers_except_c_and_flag(ctx, code, false); + } + *code += &format!( + "\tmov {}, {} {}\n", + REG_FLAG, + REG_C, + ctx.comment_str("flag = rax") + ); // this precompiles store the result in minimal trace if ctx.minimal_trace() || ctx.zip() || ctx.mem_reads() { @@ -7812,6 +7823,30 @@ impl ZiskRom2Asm { ); } + // Copies 1 u64 element from precompile_results_address to the register reg, + // and increments precompile_results_address by 8 + fn precompile_results_register(ctx: &mut ZiskAsmContext, code: &mut String, reg: &str) { + *code += &format!( + "\tmov {}, {} {}\n", + REG_AUX, + ctx.mem_precompile_results_address, + ctx.comment_str("aux = precompile_results_address") + ); + *code += &format!( + "\tmov {}, [{}] {}\n", + reg, + REG_AUX, + ctx.comment(format!("value = precompile_results[0]")) + ); + *code += &format!("\tadd {}, 8 {}\n", REG_AUX, ctx.comment(format!("aux += 8"))); + *code += &format!( + "\tmov {}, {} {}\n", + ctx.mem_precompile_results_address, + REG_AUX, + ctx.comment_str("precompile_results_address = aux") + ); + } + // Copies the fcall result size and result data to the fcall structure // address in reg_address, fn precompile_results_fcall(ctx: &mut ZiskAsmContext, code: &mut String, reg_address: &str) { diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index a683249f0..30227b380 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -1741,28 +1741,33 @@ extern uint64_t _opcode_add256(uint64_t * address) printf("c = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", c[3], c[2], c[1], c[0], c[3], c[2], c[1], c[0]); } #endif + + uint64_t cout = 0; + #ifdef ASM_PRECOMPILE_CACHE if (precompile_cache_storing) { #endif - // cout = [0,1] ok, cout < 0 error - int cout = Add256 (a, b, cin, c); - if (cout < 0) - { - printf("_opcode_add256() failed callilng Add256() cout=%d;", cout); - exit(-1); - } + // cout = [0,1] ok, cout < 0 error + int icout = Add256 (a, b, cin, c); + if (icout < 0) + { + printf("_opcode_add256() failed callilng Add256() cout=%d;", icout); + exit(-1); + } + cout = (uint64_t)icout; + #ifdef ASM_PRECOMPILE_CACHE // Store result in cache precompile_cache_store((uint8_t *)c, 4*8); - precompile_cache_store((uint8_t *)cout, 8); + precompile_cache_store((uint8_t *)&cout, 8); } else if (precompile_cache_loading) { // Load result from cache - precompile_cache_load((uint8_t *)cout, 8); precompile_cache_load((uint8_t *)c, 4*8); + precompile_cache_load((uint8_t *)&cout, 8); } #endif @@ -1770,7 +1775,7 @@ extern uint64_t _opcode_add256(uint64_t * address) if (emu_verbose) printf("opcode_add256() called Add256()\n"); if (emu_verbose) { - printf("cout = %u\n", cout); + printf("cout = %lu\n", cout); printf("c = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", c[3], c[2], c[1], c[0], c[3], c[2], c[1], c[0]); } #endif From c37e0c40711391cd447433d766f0cc34785fbed7 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 22 Oct 2025 17:50:21 +0200 Subject: [PATCH 007/782] Increase precompile size to 256MB --- emulator-asm/src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 6d3a4994e..38cbb99ad 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -311,7 +311,7 @@ uint64_t input_size = 0; #ifdef PRECOMPILE_RESULTS -#define MAX_PRECOMPILE_SIZE (uint64_t)0x08000000 // 128MB +#define MAX_PRECOMPILE_SIZE (uint64_t)0x10000000 // 256MB // Precompile results memory char shmem_precompile_name[128]; From 9a3c0ae469d788e314d383f88388964fb78064c0 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 22 Oct 2025 19:59:23 +0200 Subject: [PATCH 008/782] Change precompile defile by flag in main.c --- core/src/zisk_rom_2_asm.rs | 16 +++++ emulator-asm/src/main.c | 142 +++++++++++++++++++++---------------- 2 files changed, 97 insertions(+), 61 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 673091410..266c00ef5 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -579,16 +579,21 @@ impl ZiskRom2Asm { } // Functions to let C know about ASM generation + + // get_max_bios_pc() returns the maximum bios pc used in the ROM *code += ".global get_max_bios_pc\n"; *code += "get_max_bios_pc:\n"; *code += &format!("\tmov rax, 0x{:08x}\n", rom.max_bios_pc); *code += "\tret\n\n"; + // get_max_program_pc() returns the maximum program pc used in the ROM *code += ".global get_max_program_pc\n"; *code += "get_max_program_pc:\n"; *code += &format!("\tmov rax, 0x{:08x}\n", rom.max_program_pc); *code += "\tret\n\n"; + // get_gen_method() returns the generation method used to generate the assembly + // It must match the one used to call the assembly emulator *code += ".global get_gen_method\n"; *code += "get_gen_method:\n"; if ctx.fast() { @@ -616,6 +621,17 @@ impl ZiskRom2Asm { } *code += "\tret\n\n"; + // get_gen_method() returns the generation method used to generate the assembly + // It must match the one used to call the assembly emulator + *code += ".global get_precompile_results\n"; + *code += "get_precompile_results:\n"; + if ctx.precompile_results() { + *code += "\tmov rax, 1\n"; + } else { + *code += "\tmov rax, 0\n"; + } + *code += "\tret\n\n"; + // Externally callable function label *code += ".global emulator_start\n"; *code += "emulator_start:\n"; diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 38cbb99ad..8f33466bb 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -28,6 +28,7 @@ void write_ro_data(void); uint64_t get_max_bios_pc(void); uint64_t get_max_program_pc(void); uint64_t get_gen_method(void); +uint64_t get_precompile_results(void); // Address map #define ROM_ADDR (uint64_t)0x80000000 @@ -208,11 +209,8 @@ int map_locked_flag = MAP_LOCKED; bool precompile_cache_enabled = false; #endif -#define PRECOMPILE_RESULTS - -#ifdef PRECOMPILE_RESULTS +bool precompile_results_enabled = false; uint64_t * precompile_results_address = NULL; -#endif void set_chunk_size (uint64_t new_chunk_size) { @@ -309,8 +307,6 @@ int process_id = 0; uint64_t input_size = 0; -#ifdef PRECOMPILE_RESULTS - #define MAX_PRECOMPILE_SIZE (uint64_t)0x10000000 // 256MB // Precompile results memory @@ -321,8 +317,6 @@ void * shmem_precompile_address = NULL; char precompile_file[4096] = {0}; -#endif - int main(int argc, char *argv[]) { #ifdef DEBUG @@ -336,6 +330,14 @@ int main(int argc, char *argv[]) // Get current process id process_id = getpid(); + // Get precompiled results configuration + uint64_t precompile_results = get_precompile_results(); + if (precompile_results == 1) { + precompile_results_enabled = true; + } else { + precompile_results_enabled = false; + } + // Parse arguments parse_arguments(argc, argv); @@ -840,9 +842,10 @@ void print_usage (void) printf("\t--precompile-cache-store store precompile results in cache file\n"); printf("\t--precompile-cache-load load precompile results from cache file\n"); #endif -#ifdef PRECOMPILE_RESULTS - printf("\t-r \n"); -#endif + if (precompile_results_enabled) + { + printf("\t-r \n"); + } printf("\t-h/--help print this\n"); } @@ -1166,8 +1169,7 @@ void parse_arguments(int argc, char *argv[]) } #endif -#ifdef PRECOMPILE_RESULTS - if (strcmp(argv[i], "-r") == 0) + if (precompile_results_enabled && (strcmp(argv[i], "-r") == 0)) { i++; if (i >= argc) @@ -1185,7 +1187,6 @@ void parse_arguments(int argc, char *argv[]) strcpy(precompile_file, argv[i]); continue; } -#endif printf("ERROR: parse_arguments() Unrecognized argument: %s\n", argv[i]); print_usage(); fflush(stdout); @@ -1246,8 +1247,7 @@ void parse_arguments(int argc, char *argv[]) exit(-1); } -#ifdef PRECOMPILE_RESULTS - if (client && (strlen(precompile_file) == 0)) + if (precompile_results_enabled && client && (strlen(precompile_file) == 0)) { printf("ERROR! parse_arguments() when in precompile results mode, you need to provide a precompile results file using -r \n"); print_usage(); @@ -1255,7 +1255,6 @@ void parse_arguments(int argc, char *argv[]) fflush(stderr); exit(-1); } -#endif } void configure (void) @@ -1267,10 +1266,15 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_FT_input"); -#ifdef PRECOMPILE_RESULTS - strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_FT_precompile"); -#endif + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_FT_precompile"); + } + else + { + strcpy(shmem_precompile_name, ""); + } strcpy(shmem_output_name, ""); strcpy(sem_chunk_done_name, ""); strcpy(sem_shutdown_done_name, shm_prefix); @@ -1283,10 +1287,15 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MT_input"); -#ifdef PRECOMPILE_RESULTS - strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_MT_precompile"); -#endif + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_MT_precompile"); + } + else + { + strcpy(shmem_precompile_name, ""); + } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1302,10 +1311,15 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_RH_input"); -#ifdef PRECOMPILE_RESULTS - strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_RH_precompile"); -#endif + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_RH_precompile"); + } + else + { + strcpy(shmem_precompile_name, ""); + } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_RH_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1321,10 +1335,15 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MA_input"); -#ifdef PRECOMPILE_RESULTS - strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_MA_precompile"); -#endif + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_MA_precompile"); + } + else + { + strcpy(shmem_precompile_name, ""); + } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MA_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1340,9 +1359,7 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_CH_input"); -#ifdef PRECOMPILE_RESULTS strcpy(shmem_precompile_name, ""); -#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CH_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1367,10 +1384,15 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_ZP_input"); -#ifdef PRECOMPILE_RESULTS - strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_ZP_precompile"); -#endif + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_ZP_precompile"); + } + else + { + strcpy(shmem_precompile_name, ""); + } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_ZP_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1386,10 +1408,15 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MO_input"); -#ifdef PRECOMPILE_RESULTS - strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_MO_precompile"); -#endif + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_MO_precompile"); + } + else + { + strcpy(shmem_precompile_name, ""); + } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MO_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1404,9 +1431,7 @@ void configure (void) case ChunkPlayerMTCollectMem: { strcpy(shmem_input_name, ""); -#ifdef PRECOMPILE_RESULTS strcpy(shmem_precompile_name, ""); -#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CM_output"); strcpy(sem_chunk_done_name, ""); @@ -1421,10 +1446,15 @@ void configure (void) { strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MT_input"); -#ifdef PRECOMPILE_RESULTS - strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_MT_precompile"); -#endif + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + strcat(shmem_precompile_name, "_MT_precompile"); + } + else + { + strcpy(shmem_precompile_name, ""); + } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1439,9 +1469,7 @@ void configure (void) case ChunkPlayerMemReadsCollectMain: { strcpy(shmem_input_name, ""); -#ifdef PRECOMPILE_RESULTS strcpy(shmem_precompile_name, ""); -#endif strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CA_output"); strcpy(sem_chunk_done_name, ""); @@ -1652,12 +1680,10 @@ void client_run (void) } -#ifdef PRECOMPILE_RESULTS - /*****************************/ /* Read precompile file data */ /*****************************/ - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) { #ifdef DEBUG @@ -1763,8 +1789,6 @@ void client_run (void) } -#endif - /*************************/ /* Connect to the server */ /*************************/ @@ -2685,13 +2709,11 @@ void server_setup (void) if (verbose) printf("mmap(input) mapped %lu B and returned address %p in %lu us\n", MAX_INPUT_SIZE, pInput, duration); } -#ifdef PRECOMPILE_RESULTS - /**********************/ /* PRECOMPILE_RESULTS */ /**********************/ - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) { // Make sure the precompile results shared memory is deleted shm_unlink(shmem_precompile_name); @@ -2735,8 +2757,6 @@ void server_setup (void) if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); } -#endif // PRECOMPILE_RESULTS - /*******/ /* RAM */ /*******/ From 08dcfdc5757b0490a5342f7528431f3a7f43ca58 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 23 Oct 2025 12:40:18 +0200 Subject: [PATCH 009/782] Disable precompile results --- core/src/zisk_rom_2_asm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 266c00ef5..b785b8ecf 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -214,7 +214,7 @@ impl ZiskAsmContext { } pub fn precompile_results(&self) -> bool { - true + false } } From ce8c054259535f23611baf46e4d144e8d1fa348d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 7 Nov 2025 21:45:27 +0000 Subject: [PATCH 010/782] added a way to get a slice from mem_reads --- common/src/bus/bus_device.rs | 8 +- common/src/bus/bus_device_metrics.rs | 5 +- common/src/bus/data_bus_operation.rs | 4 +- common/src/regular_counters.rs | 3 +- data-bus/src/data_bus.rs | 17 ++-- data-bus/src/data_bus_file.rs | 5 +- data-bus/src/data_bus_player.rs | 11 ++- emulator/src/emu.rs | 47 ++++++---- executor/src/dummy_counter.rs | 3 +- executor/src/static_data_bus.rs | 51 +++++++---- executor/src/static_data_bus_collect.rs | 85 +++++++++++++------ .../arith_eq/src/arith_eq_bus_device.rs | 3 +- precompiles/arith_eq/src/arith_eq_instance.rs | 3 +- .../arith_eq/src/mem_inputs/arith256.rs | 2 +- .../arith_eq/src/mem_inputs/arith256_mod.rs | 2 +- .../src/mem_inputs/bn254_complex_add.rs | 2 +- .../src/mem_inputs/bn254_complex_mul.rs | 2 +- .../src/mem_inputs/bn254_complex_sub.rs | 2 +- .../src/mem_inputs/bn254_curve_add.rs | 2 +- .../src/mem_inputs/bn254_curve_dbl.rs | 2 +- .../src/mem_inputs/generate_mem_inputs.rs | 2 +- .../arith_eq/src/mem_inputs/secp256k1_add.rs | 2 +- .../arith_eq/src/mem_inputs/secp256k1_dbl.rs | 2 +- .../src/arith_eq_384_bus_device.rs | 3 +- .../arith_eq_384/src/arith_eq_384_instance.rs | 3 +- .../src/mem_inputs/arith384_mod.rs | 2 +- .../src/mem_inputs/bls12_381_complex_add.rs | 2 +- .../src/mem_inputs/bls12_381_complex_mul.rs | 2 +- .../src/mem_inputs/bls12_381_complex_sub.rs | 2 +- .../src/mem_inputs/bls12_381_curve_add.rs | 2 +- .../src/mem_inputs/bls12_381_curve_dbl.rs | 2 +- .../src/mem_inputs/generate_mem_inputs.rs | 2 +- precompiles/big_int/src/add256_bus_device.rs | 3 +- .../big_int/src/add256_gen_mem_inputs.rs | 2 +- precompiles/big_int/src/add256_instance.rs | 3 +- precompiles/common/src/lib.rs | 9 +- precompiles/keccakf/src/keccakf_bus_device.rs | 3 +- .../keccakf/src/keccakf_gen_mem_inputs.rs | 2 +- precompiles/keccakf/src/keccakf_instance.rs | 3 +- precompiles/sha256f/src/sha256f_bus_device.rs | 3 +- .../sha256f/src/sha256f_gen_mem_inputs.rs | 2 +- precompiles/sha256f/src/sha256f_instance.rs | 3 +- state-machines/arith/src/arith_bus_device.rs | 3 +- state-machines/arith/src/arith_full.rs | 5 +- .../arith/src/arith_full_instance.rs | 3 +- .../binary/src/binary_add_collector.rs | 3 +- .../binary/src/binary_basic_collector.rs | 3 +- state-machines/binary/src/binary_counter.rs | 3 +- .../binary/src/binary_extension_collector.rs | 3 +- state-machines/main/src/main_counter.rs | 3 +- state-machines/mem-common/src/mem_counters.rs | 3 +- state-machines/mem/src/mem_align_collector.rs | 3 +- .../mem/src/mem_module_collector.rs | 3 +- state-machines/mem/src/mem_test.rs | 4 + state-machines/rom/src/rom_instance.rs | 3 +- 55 files changed, 234 insertions(+), 126 deletions(-) diff --git a/common/src/bus/bus_device.rs b/common/src/bus/bus_device.rs index 4184286c2..1a66430a4 100644 --- a/common/src/bus/bus_device.rs +++ b/common/src/bus/bus_device.rs @@ -25,7 +25,8 @@ pub trait BusDevice: Any + Send + Sync { &mut self, bus_id: &BusId, data: &[D], - pending: &mut VecDeque<(BusId, Vec)>, + data_ext: &[D], + pending: &mut VecDeque<(BusId, Vec, Vec)>, mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool; @@ -47,10 +48,11 @@ impl BusDevice for Box> { &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, + data_ext: &[u64], + pending: &mut VecDeque<(BusId, Vec, Vec)>, mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { - (**self).process_data(bus_id, data, pending, mem_collector_info) + (**self).process_data(bus_id, data, data_ext, pending, mem_collector_info) } fn bus_id(&self) -> Vec { diff --git a/common/src/bus/bus_device_metrics.rs b/common/src/bus/bus_device_metrics.rs index e1a2e44cc..160f6856a 100644 --- a/common/src/bus/bus_device_metrics.rs +++ b/common/src/bus/bus_device_metrics.rs @@ -27,10 +27,11 @@ impl BusDevice for Box { &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, + data_ext: &[u64], + pending: &mut VecDeque<(BusId, Vec, Vec)>, mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { - (**self).process_data(bus_id, data, pending, mem_collector_info) + (**self).process_data(bus_id, data, data_ext, pending, mem_collector_info) } fn bus_id(&self) -> Vec { diff --git a/common/src/bus/data_bus_operation.rs b/common/src/bus/data_bus_operation.rs index 0b8b97bd1..1c4cc6d2e 100644 --- a/common/src/bus/data_bus_operation.rs +++ b/common/src/bus/data_bus_operation.rs @@ -283,9 +283,9 @@ impl OperationBusData { op_type: PayloadType, a: u64, b: u64, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { - pending.push_back((OPERATION_BUS_ID, vec![op as u64, op_type, a, b])); + pending.push_back((OPERATION_BUS_ID, vec![op as u64, op_type, a, b], Vec::new())); } /// Creates operation data from a `ZiskInst` instruction and its context. diff --git a/common/src/regular_counters.rs b/common/src/regular_counters.rs index 8e43e3a48..5382bb9d7 100644 --- a/common/src/regular_counters.rs +++ b/common/src/regular_counters.rs @@ -126,7 +126,8 @@ impl BusDevice for RegularCounters { &mut self, bus_id: &BusId, data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == self.bus_id); diff --git a/data-bus/src/data_bus.rs b/data-bus/src/data_bus.rs index 8783c1731..d06a6c1a7 100644 --- a/data-bus/src/data_bus.rs +++ b/data-bus/src/data_bus.rs @@ -18,7 +18,7 @@ pub trait DataBusTrait { /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. - fn write_to_bus(&mut self, bus_id: BusId, payload: &[D]) -> bool; + fn write_to_bus(&mut self, bus_id: BusId, data: &[D], data_ext: &[D]) -> bool; fn on_close(&mut self); @@ -42,7 +42,7 @@ pub struct DataBus> { devices_bus_id_map: Vec>, /// Queue of pending data transfers to be processed. - pending_transfers: VecDeque<(BusId, Vec)>, + pending_transfers: VecDeque<(BusId, Vec, Vec)>, /// Indices of devices that are connected to the bus but without a specific instance. none_devices: Vec, @@ -101,7 +101,7 @@ impl> DataBus { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn route_data(&mut self, bus_id: BusId, payload: &[D]) { + fn route_data(&mut self, bus_id: BusId, data: &[D], data_ext: &[D]) { let devices_idx = &mut self.devices_bus_id_map[*bus_id]; let mut i = 0; @@ -110,7 +110,8 @@ impl> DataBus { // When a device returns false, it indicates that it has finished its work and should be disabled. if !self.devices[device_idx].1.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ) { @@ -143,11 +144,11 @@ impl> DataBusTrait for DataBus { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn write_to_bus(&mut self, bus_id: BusId, payload: &[D]) -> bool { - self.route_data(bus_id, payload); + fn write_to_bus(&mut self, bus_id: BusId, data: &[D], data_ext: &[D]) -> bool { + self.route_data(bus_id, data, data_ext); - while let Some((bus_id, payload)) = self.pending_transfers.pop_front() { - self.route_data(bus_id, &payload); + while let Some((bus_id, data, data_ext)) = self.pending_transfers.pop_front() { + self.route_data(bus_id, &data, &data_ext); } self.active_devices > 0 diff --git a/data-bus/src/data_bus_file.rs b/data-bus/src/data_bus_file.rs index bbc270496..620adc031 100644 --- a/data-bus/src/data_bus_file.rs +++ b/data-bus/src/data_bus_file.rs @@ -12,6 +12,7 @@ use std::{ use zisk_common::BusId; +pub type ReadFromFileData = Vec<(BusId, Vec, Vec)>; pub struct DataBusFileReader; impl DataBusFileReader { @@ -35,7 +36,7 @@ impl DataBusFileReader { /// # Errors /// - Returns an error if the file cannot be opened or read. /// - Returns an error if any line is malformed (missing `BusId` or invalid payload values). - pub fn read_from_file(file_path: &str) -> Result)>, io::Error> + pub fn read_from_file(file_path: &str) -> Result, io::Error> where D::Err: std::fmt::Display, { @@ -81,7 +82,7 @@ impl DataBusFileReader { } // Push the parsed data into the pre-allocated vector - data.push((BusId(bus_id), payload)); + data.push((BusId(bus_id), payload, vec![])); } Ok(data) diff --git a/data-bus/src/data_bus_player.rs b/data-bus/src/data_bus_player.rs index 25ee2834a..2a997388c 100644 --- a/data-bus/src/data_bus_player.rs +++ b/data-bus/src/data_bus_player.rs @@ -14,9 +14,14 @@ impl DataBusPlayer { /// # Arguments /// * `data_bus` - The `DataBus` to which the data is sent. /// * `data` - A vector of `(BusId, Payload)` tuples. - pub fn play>(data_bus: &mut DataBus, data: Vec<(BusId, Vec)>) { - for (bus_id, payload) in data { - as DataBusTrait>::write_to_bus(data_bus, bus_id, &payload); + pub fn play>( + data_bus: &mut DataBus, + data: Vec<(BusId, Vec, Vec)>, + ) { + for (bus_id, data, data_ext) in data { + as DataBusTrait>::write_to_bus( + data_bus, bus_id, &data, &data_ext, + ); } } diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 28d221785..765d21297 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -320,7 +320,7 @@ impl<'a> Emu<'a> { 8, [self.ctx.inst_ctx.a, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { let (required_address_1, required_address_2) = Mem::required_addresses(address, 8); @@ -340,7 +340,7 @@ impl<'a> Emu<'a> { 8, [raw_data_1, raw_data_2], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } /*println!( "Emu::source_a_mem_reads_consume() mem_leads_index={} value={:x}", @@ -728,7 +728,7 @@ impl<'a> Emu<'a> { 8, [self.ctx.inst_ctx.b, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { let (required_address_1, required_address_2) = Mem::required_addresses(address, 8); @@ -745,7 +745,7 @@ impl<'a> Emu<'a> { 8, [raw_data, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { assert!(*mem_reads_index < mem_reads.len()); let raw_data_1 = mem_reads[*mem_reads_index]; @@ -762,7 +762,7 @@ impl<'a> Emu<'a> { 8, [raw_data_1, raw_data_2], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } } /*println!( @@ -797,7 +797,7 @@ impl<'a> Emu<'a> { 8, [self.ctx.inst_ctx.b, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { let (required_address_1, required_address_2) = Mem::required_addresses(address, instruction.ind_width); @@ -817,7 +817,7 @@ impl<'a> Emu<'a> { instruction.ind_width as u8, [raw_data, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { assert!(*mem_reads_index < mem_reads.len()); let raw_data_1 = mem_reads[*mem_reads_index]; @@ -838,7 +838,7 @@ impl<'a> Emu<'a> { 8, [raw_data_1, raw_data_2], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } } /*println!( @@ -1237,7 +1237,7 @@ impl<'a> Emu<'a> { value, [value, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } // Otherwise, if not aligned, get old raw data from memory, then write it else { @@ -1256,7 +1256,7 @@ impl<'a> Emu<'a> { value, [raw_data, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { assert!(*mem_reads_index < mem_reads.len()); let raw_data_1 = mem_reads[*mem_reads_index]; @@ -1273,7 +1273,7 @@ impl<'a> Emu<'a> { value, [raw_data_1, raw_data_2], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } } } @@ -1300,7 +1300,7 @@ impl<'a> Emu<'a> { value, [value, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } // Otherwise, if not aligned, get old raw data from memory, then write it else { @@ -1319,7 +1319,7 @@ impl<'a> Emu<'a> { value, [raw_data, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { assert!(*mem_reads_index < mem_reads.len()); let raw_data_1 = mem_reads[*mem_reads_index]; @@ -1336,7 +1336,7 @@ impl<'a> Emu<'a> { value, [raw_data_1, raw_data_2], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } } } @@ -2042,7 +2042,7 @@ impl<'a> Emu<'a> { &self.ctx.inst_ctx, &mut self.static_array, ); - data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload); + data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); } // #[cfg(feature = "sp")] @@ -2095,7 +2095,7 @@ impl<'a> Emu<'a> { &self.ctx.inst_ctx, &mut self.static_array, ); - data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload); + data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); } // #[cfg(feature = "sp")] @@ -2214,14 +2214,14 @@ impl<'a> Emu<'a> { &self.ctx.inst_ctx, &mut self.static_array, ); - _continue = data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload); + _continue = data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); } // Get rom bus data let rom_payload = RomBusData::from_instruction(instruction, &self.ctx.inst_ctx); // Write rom bus data to rom bus - data_bus.write_to_bus(ROM_BUS_ID, &rom_payload); + data_bus.write_to_bus(ROM_BUS_ID, &rom_payload, &[]); // #[cfg(feature = "sp")] // self.set_sp(instruction); @@ -2233,6 +2233,17 @@ impl<'a> Emu<'a> { _continue } + fn get_slice_from_mem_reads<'b>( + &mut self, + mem_reads: &'b [u64], + mem_reads_index: &mut usize, + len: usize, + ) -> &'b [u64] { + let slice = &mem_reads[*mem_reads_index..*mem_reads_index + len]; + *mem_reads_index += len; + slice + } + /// Performs one single step of the emulation #[inline(always)] pub fn step_slice_full_trace( diff --git a/executor/src/dummy_counter.rs b/executor/src/dummy_counter.rs index ea3c3400b..4cf41795c 100644 --- a/executor/src/dummy_counter.rs +++ b/executor/src/dummy_counter.rs @@ -41,7 +41,8 @@ impl BusDevice for DummyCounter { &mut self, _bus_id: &BusId, _data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { true diff --git a/executor/src/static_data_bus.rs b/executor/src/static_data_bus.rs index 92a893f75..f2e70a274 100644 --- a/executor/src/static_data_bus.rs +++ b/executor/src/static_data_bus.rs @@ -47,7 +47,7 @@ pub struct StaticDataBus { pub add_256_counter: (usize, Add256CounterInputGen), pub rom_counter_id: Option, /// Queue of pending data transfers to be processed. - pending_transfers: VecDeque<(BusId, Vec)>, + pending_transfers: VecDeque<(BusId, Vec, Vec)>, } impl StaticDataBus { @@ -92,7 +92,12 @@ impl StaticDataBus { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn route_data(&mut self, bus_id: BusId, payload: &[PayloadType]) -> bool { + fn route_data( + &mut self, + bus_id: BusId, + data: &[PayloadType], + data_ext: &[PayloadType], + ) -> bool { match bus_id { MEM_BUS_ID => { let mut _continue = true; @@ -101,7 +106,8 @@ impl StaticDataBus { // If we are not processing only operation bus, we process memory bus data. _continue &= mem_counter.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); @@ -109,52 +115,60 @@ impl StaticDataBus { } _continue } - OPERATION_BUS_ID => match payload[1] as u32 { + OPERATION_BUS_ID => match data[1] as u32 { PUB_OUT_OP_TYPE_ID => self.main_counter.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ), BINARY_OP_TYPE_ID | BINARY_E_OP_TYPE_ID => self.binary_counter.1.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ), ARITH_OP_TYPE_ID => self.arith_counter.1.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ), KECCAK_OP_TYPE_ID => self.keccakf_counter.1.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ), SHA256_OP_TYPE_ID => self.sha256f_counter.1.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ), ARITH_EQ_OP_TYPE_ID => self.arith_eq_counter.1.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ), ARITH_EQ_384_OP_TYPE_ID => self.arith_eq_384_counter.1.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ), BIG_INT_OP_TYPE_ID => self.add_256_counter.1.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ), @@ -167,11 +181,16 @@ impl StaticDataBus { impl DataBusTrait> for StaticDataBus { #[inline(always)] - fn write_to_bus(&mut self, bus_id: BusId, payload: &[PayloadType]) -> bool { - let mut _continue = self.route_data(bus_id, payload); + fn write_to_bus( + &mut self, + bus_id: BusId, + data: &[PayloadType], + data_ext: &[PayloadType], + ) -> bool { + let mut _continue = self.route_data(bus_id, data, data_ext); - while let Some((bus_id, payload)) = self.pending_transfers.pop_front() { - _continue &= self.route_data(bus_id, &payload); + while let Some((bus_id, data, data_ext)) = self.pending_transfers.pop_front() { + _continue &= self.route_data(bus_id, &data, &data_ext); } _continue diff --git a/executor/src/static_data_bus_collect.rs b/executor/src/static_data_bus_collect.rs index 5ccbae384..cd7fbb330 100644 --- a/executor/src/static_data_bus_collect.rs +++ b/executor/src/static_data_bus_collect.rs @@ -71,7 +71,7 @@ pub struct StaticDataBusCollect { pub rom_collector: Vec<(usize, RomCollector)>, /// Queue of pending data transfers to be processed. - pending_transfers: VecDeque<(BusId, Vec)>, + pending_transfers: VecDeque<(BusId, Vec, Vec)>, mem_collectors_info: Vec, } @@ -146,30 +146,38 @@ impl StaticDataBusCollect { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn route_data(&mut self, bus_id: BusId, payload: &[PayloadType]) { + fn route_data(&mut self, bus_id: BusId, data: &[PayloadType], data_ext: &[PayloadType]) { match bus_id { MEM_BUS_ID => { // Process mem collectors - inverted condition to avoid continue for (_, mem_collector) in &mut self.mem_collector { - mem_collector.process_data(&bus_id, payload, &mut self.pending_transfers, None); + mem_collector.process_data( + &bus_id, + data, + data_ext, + &mut self.pending_transfers, + None, + ); } // Only process align collectors if needed for (_, mem_align_collector) in &mut self.mem_align_collector { mem_align_collector.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); } } - OPERATION_BUS_ID => match payload[OP_TYPE] { + OPERATION_BUS_ID => match data[OP_TYPE] { BINARY_TYPE => { for (_, binary_add_collector) in &mut self.binary_add_collector { binary_add_collector.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); @@ -178,7 +186,8 @@ impl StaticDataBusCollect { for (_, binary_basic_collector) in &mut self.binary_basic_collector { binary_basic_collector.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); @@ -188,7 +197,8 @@ impl StaticDataBusCollect { for (_, binary_extension_collector) in &mut self.binary_extension_collector { binary_extension_collector.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); @@ -198,7 +208,8 @@ impl StaticDataBusCollect { for (_, arith_collector) in &mut self.arith_collector { arith_collector.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); @@ -206,7 +217,8 @@ impl StaticDataBusCollect { self.arith_inputs_generator.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); @@ -215,7 +227,8 @@ impl StaticDataBusCollect { for (_, keccakf_collector) in &mut self.keccakf_collector { keccakf_collector.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); @@ -223,7 +236,8 @@ impl StaticDataBusCollect { self.keccakf_inputs_generator.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, Some(&self.mem_collectors_info), ); @@ -232,7 +246,8 @@ impl StaticDataBusCollect { for (_, sha256f_collector) in &mut self.sha256f_collector { sha256f_collector.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); @@ -240,7 +255,8 @@ impl StaticDataBusCollect { self.sha256f_inputs_generator.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, Some(&self.mem_collectors_info), ); @@ -249,7 +265,8 @@ impl StaticDataBusCollect { for (_, arith_eq_collector) in &mut self.arith_eq_collector { arith_eq_collector.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); @@ -257,7 +274,8 @@ impl StaticDataBusCollect { self.arith_eq_inputs_generator.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, Some(&self.mem_collectors_info), ); @@ -266,7 +284,8 @@ impl StaticDataBusCollect { for (_, arith_eq_384_collector) in &mut self.arith_eq_384_collector { arith_eq_384_collector.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); @@ -274,7 +293,8 @@ impl StaticDataBusCollect { self.arith_eq_384_inputs_generator.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, Some(&self.mem_collectors_info), ); @@ -283,7 +303,8 @@ impl StaticDataBusCollect { for (_, add256_collector) in &mut self.add256_collector { add256_collector.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, None, ); @@ -291,7 +312,8 @@ impl StaticDataBusCollect { self.add256_inputs_generator.process_data( &bus_id, - payload, + data, + data_ext, &mut self.pending_transfers, Some(&self.mem_collectors_info), ); @@ -300,7 +322,13 @@ impl StaticDataBusCollect { }, ROM_BUS_ID => { for (_, rom_collector) in &mut self.rom_collector { - rom_collector.process_data(&bus_id, payload, &mut self.pending_transfers, None); + rom_collector.process_data( + &bus_id, + data, + data_ext, + &mut self.pending_transfers, + None, + ); } } _ => {} @@ -312,12 +340,19 @@ impl DataBusTrait>> for StaticDataBusCollect { #[inline(always)] - fn write_to_bus(&mut self, bus_id: BusId, payload: &[PayloadType]) -> bool { - self.route_data(bus_id, payload); + fn write_to_bus( + &mut self, + bus_id: BusId, + data: &[PayloadType], + data_ext: &[PayloadType], + ) -> bool { + self.route_data(bus_id, data, data_ext); // Process all pending transfers in a batch to improve cache locality - while let Some((pending_bus_id, pending_payload)) = self.pending_transfers.pop_front() { - self.route_data(pending_bus_id, &pending_payload); + while let Some((pending_bus_id, pending_payload, pending_data_ext)) = + self.pending_transfers.pop_front() + { + self.route_data(pending_bus_id, &pending_payload, &pending_data_ext); } true diff --git a/precompiles/arith_eq/src/arith_eq_bus_device.rs b/precompiles/arith_eq/src/arith_eq_bus_device.rs index 3774e69f0..6208659dd 100644 --- a/precompiles/arith_eq/src/arith_eq_bus_device.rs +++ b/precompiles/arith_eq/src/arith_eq_bus_device.rs @@ -157,7 +157,8 @@ impl BusDevice for ArithEqCounterInputGen { &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + pending: &mut VecDeque<(BusId, Vec, Vec)>, mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/precompiles/arith_eq/src/arith_eq_instance.rs b/precompiles/arith_eq/src/arith_eq_instance.rs index 6a13eba9a..be258aea9 100644 --- a/precompiles/arith_eq/src/arith_eq_instance.rs +++ b/precompiles/arith_eq/src/arith_eq_instance.rs @@ -177,7 +177,8 @@ impl BusDevice for ArithEqCollector { &mut self, bus_id: &BusId, data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/precompiles/arith_eq/src/mem_inputs/arith256.rs b/precompiles/arith_eq/src/mem_inputs/arith256.rs index dc6dc5084..d024a09f1 100644 --- a/precompiles/arith_eq/src/mem_inputs/arith256.rs +++ b/precompiles/arith_eq/src/mem_inputs/arith256.rs @@ -17,7 +17,7 @@ pub fn generate_arith256_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // op,op_type,a,b,addr[5],... let a: &[u64; 4] = &data[9..13].try_into().unwrap(); diff --git a/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs b/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs index a8c0b8d45..b12b85062 100644 --- a/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs +++ b/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs @@ -16,7 +16,7 @@ pub fn generate_arith256_mod_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // op,op_type,a,b,addr[5],... let a: &[u64; 4] = &data[9..13].try_into().unwrap(); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs index fca141932..c91c44569 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs @@ -17,7 +17,7 @@ pub fn generate_bn254_complex_add_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // op,op_type,a,b,addr[2],... let f1: &[u64; 8] = &data[6..14].try_into().unwrap(); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs index 6c70af05b..8c5090478 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs @@ -17,7 +17,7 @@ pub fn generate_bn254_complex_mul_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // op,op_type,a,b,addr[2],... let f1: &[u64; 8] = &data[6..14].try_into().unwrap(); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs index df7d18f56..c82bfa9d7 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs @@ -17,7 +17,7 @@ pub fn generate_bn254_complex_sub_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // op,op_type,a,b,addr[2],... let f1: &[u64; 8] = &data[6..14].try_into().unwrap(); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs b/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs index e160843ee..19b0de756 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs @@ -17,7 +17,7 @@ pub fn generate_bn254_curve_add_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // op,op_type,a,b,addr[2],... let p1: &[u64; 8] = &data[6..14].try_into().unwrap(); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs b/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs index 1c683f1f7..8e97018f4 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs @@ -17,7 +17,7 @@ pub fn generate_bn254_curve_dbl_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // op,op_type,a,b,addr[2],... let p1: &[u64; 8] = &data[4..12].try_into().unwrap(); diff --git a/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs b/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs index 2b1b7d943..8266fe633 100644 --- a/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs +++ b/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs @@ -17,7 +17,7 @@ pub fn generate_mem_inputs( data: &[u64], write_data: Option<&[u64]>, only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, config: &ArithEqMemInputConfig, ) { let params_count = config.read_params + config.write_params; diff --git a/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs b/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs index 0c08a0ba0..6a96bd5ff 100644 --- a/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs @@ -17,7 +17,7 @@ pub fn generate_secp256k1_add_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // op,op_type,a,b,addr[2],... let p1: &[u64; 8] = &data[6..14].try_into().unwrap(); diff --git a/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs b/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs index b86bf868a..69fa6939b 100644 --- a/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs +++ b/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs @@ -17,7 +17,7 @@ pub fn generate_secp256k1_dbl_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // op,op_type,a,b,... let p1: &[u64; 8] = &data[4..12].try_into().unwrap(); diff --git a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs index 5f06e8f25..1ffeb5f88 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs @@ -145,7 +145,8 @@ impl BusDevice for ArithEq384CounterInputGen { &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + pending: &mut VecDeque<(BusId, Vec, Vec)>, mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/precompiles/arith_eq_384/src/arith_eq_384_instance.rs b/precompiles/arith_eq_384/src/arith_eq_384_instance.rs index 35090237d..024e5e59e 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_instance.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_instance.rs @@ -175,7 +175,8 @@ impl BusDevice for ArithEq384Collector { &mut self, bus_id: &BusId, data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs b/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs index a73fa3d93..36b1a4636 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs @@ -17,7 +17,7 @@ pub fn generate_arith384_mod_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { let mut pos_offset: usize = 9; // op,op_type,a,b,addr[5],... let a: &[u64; ARITH_EQ_384_U64S] = diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs index c10a6e3c7..87d770ba3 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs @@ -17,7 +17,7 @@ pub fn generate_bls12_381_complex_add_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs index fec7de46d..7ef069de9 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs @@ -17,7 +17,7 @@ pub fn generate_bls12_381_complex_mul_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs index 5d15fbc29..360cd44c6 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs @@ -17,7 +17,7 @@ pub fn generate_bls12_381_complex_sub_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs index 983b3c7f6..8caff9b8b 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs @@ -17,7 +17,7 @@ pub fn generate_bls12_381_curve_add_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... let p1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs index b61f8da15..6c3682a07 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs @@ -17,7 +17,7 @@ pub fn generate_bls12_381_curve_dbl_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { let pos_offset: usize = 4; // op,op_type,a,b,... let p1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = diff --git a/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs b/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs index 2b1602d29..5bd7e5ae0 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs @@ -16,7 +16,7 @@ pub fn generate_mem_inputs( data: &[u64], write_data: Option<&[u64]>, only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, config: &ArithEq384MemInputConfig, ) { let params_count = config.read_params + config.write_params; diff --git a/precompiles/big_int/src/add256_bus_device.rs b/precompiles/big_int/src/add256_bus_device.rs index d65dcff9f..915f5e6ae 100644 --- a/precompiles/big_int/src/add256_bus_device.rs +++ b/precompiles/big_int/src/add256_bus_device.rs @@ -105,7 +105,8 @@ impl BusDevice for Add256CounterInputGen { &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + pending: &mut VecDeque<(BusId, Vec, Vec)>, mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/precompiles/big_int/src/add256_gen_mem_inputs.rs b/precompiles/big_int/src/add256_gen_mem_inputs.rs index 4f26b6c69..ab1ef5a6f 100644 --- a/precompiles/big_int/src/add256_gen_mem_inputs.rs +++ b/precompiles/big_int/src/add256_gen_mem_inputs.rs @@ -20,7 +20,7 @@ pub fn generate_add256_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // Start by generating the params (indirection read, direct, indirection write) for iparam in 0..PARAMS { diff --git a/precompiles/big_int/src/add256_instance.rs b/precompiles/big_int/src/add256_instance.rs index 267251539..97f616583 100644 --- a/precompiles/big_int/src/add256_instance.rs +++ b/precompiles/big_int/src/add256_instance.rs @@ -168,7 +168,8 @@ impl BusDevice for Add256Collector { &mut self, bus_id: &BusId, data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/precompiles/common/src/lib.rs b/precompiles/common/src/lib.rs index f37f9081a..8daa3b1fd 100644 --- a/precompiles/common/src/lib.rs +++ b/precompiles/common/src/lib.rs @@ -50,7 +50,7 @@ impl MemBusHelpers { addr: u32, step: u64, mem_value: u64, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { assert!(addr % 8 == 0); pending.push_back(( @@ -64,13 +64,14 @@ impl MemBusHelpers { 0, 0, ], + vec![], )); } pub fn mem_aligned_write( addr: u32, step: u64, value: u64, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { assert!(addr % 8 == 0); pending.push_back(( @@ -84,6 +85,7 @@ impl MemBusHelpers { 0, value, ], + vec![], )); } pub fn mem_aligned_op( @@ -91,7 +93,7 @@ impl MemBusHelpers { step: u64, value: u64, is_write: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { pending.push_back(( MEM_BUS_ID, @@ -104,6 +106,7 @@ impl MemBusHelpers { 0, if is_write { value } else { 0 }, ], + vec![], )); } } diff --git a/precompiles/keccakf/src/keccakf_bus_device.rs b/precompiles/keccakf/src/keccakf_bus_device.rs index 2a1884298..57c8323b8 100644 --- a/precompiles/keccakf/src/keccakf_bus_device.rs +++ b/precompiles/keccakf/src/keccakf_bus_device.rs @@ -105,7 +105,8 @@ impl BusDevice for KeccakfCounterInputGen { &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + pending: &mut VecDeque<(BusId, Vec, Vec)>, mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs b/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs index bcb09bb72..77e859369 100644 --- a/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs +++ b/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs @@ -18,7 +18,7 @@ pub fn generate_keccakf_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // Get the basic data from the input // op,op_type,a,b,... diff --git a/precompiles/keccakf/src/keccakf_instance.rs b/precompiles/keccakf/src/keccakf_instance.rs index 6ddf66a45..cc22b370a 100644 --- a/precompiles/keccakf/src/keccakf_instance.rs +++ b/precompiles/keccakf/src/keccakf_instance.rs @@ -181,7 +181,8 @@ impl BusDevice for KeccakfCollector { &mut self, bus_id: &BusId, data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/precompiles/sha256f/src/sha256f_bus_device.rs b/precompiles/sha256f/src/sha256f_bus_device.rs index e232b50b3..fec2be2d0 100644 --- a/precompiles/sha256f/src/sha256f_bus_device.rs +++ b/precompiles/sha256f/src/sha256f_bus_device.rs @@ -105,7 +105,8 @@ impl BusDevice for Sha256fCounterInputGen { &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + pending: &mut VecDeque<(BusId, Vec, Vec)>, mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs index 4b4f7c43e..7f5cc06eb 100644 --- a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs +++ b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs @@ -20,7 +20,7 @@ pub fn generate_sha256f_mem_inputs( step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { // Get the basic data from the input // op,op_type,a,b,addr[2],... diff --git a/precompiles/sha256f/src/sha256f_instance.rs b/precompiles/sha256f/src/sha256f_instance.rs index 5d1d164a9..636db7021 100644 --- a/precompiles/sha256f/src/sha256f_instance.rs +++ b/precompiles/sha256f/src/sha256f_instance.rs @@ -168,7 +168,8 @@ impl BusDevice for Sha256fCollector { &mut self, bus_id: &BusId, data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/state-machines/arith/src/arith_bus_device.rs b/state-machines/arith/src/arith_bus_device.rs index c4b4349ff..d298b52d3 100644 --- a/state-machines/arith/src/arith_bus_device.rs +++ b/state-machines/arith/src/arith_bus_device.rs @@ -95,7 +95,8 @@ impl BusDevice for ArithCounterInputGen { &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/state-machines/arith/src/arith_full.rs b/state-machines/arith/src/arith_full.rs index 6a0b28aa9..5f6aa4f72 100644 --- a/state-machines/arith/src/arith_full.rs +++ b/state-machines/arith/src/arith_full.rs @@ -185,7 +185,10 @@ impl ArithFullSM { /// Generates binary inputs for operations requiring additional validation (e.g., division). #[inline(always)] - pub fn generate_inputs(input: &OperationData, pending: &mut VecDeque<(BusId, Vec)>) { + pub fn generate_inputs( + input: &OperationData, + pending: &mut VecDeque<(BusId, Vec, Vec)>, + ) { let mut aop = ArithOperation::new(); let input_data = ExtOperationData::OperationData(*input); diff --git a/state-machines/arith/src/arith_full_instance.rs b/state-machines/arith/src/arith_full_instance.rs index 9f75c3172..dd120b0ac 100644 --- a/state-machines/arith/src/arith_full_instance.rs +++ b/state-machines/arith/src/arith_full_instance.rs @@ -198,7 +198,8 @@ impl BusDevice for ArithInstanceCollector { &mut self, bus_id: &BusId, data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/state-machines/binary/src/binary_add_collector.rs b/state-machines/binary/src/binary_add_collector.rs index 26390c8dc..1dd799d57 100644 --- a/state-machines/binary/src/binary_add_collector.rs +++ b/state-machines/binary/src/binary_add_collector.rs @@ -63,7 +63,8 @@ impl BusDevice for BinaryAddCollector { &mut self, bus_id: &BusId, data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/state-machines/binary/src/binary_basic_collector.rs b/state-machines/binary/src/binary_basic_collector.rs index b03703592..8107b4cfc 100644 --- a/state-machines/binary/src/binary_basic_collector.rs +++ b/state-machines/binary/src/binary_basic_collector.rs @@ -72,7 +72,8 @@ impl BusDevice for BinaryBasicCollector { &mut self, bus_id: &BusId, data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/state-machines/binary/src/binary_counter.rs b/state-machines/binary/src/binary_counter.rs index 2c2b789ce..036cfb77e 100644 --- a/state-machines/binary/src/binary_counter.rs +++ b/state-machines/binary/src/binary_counter.rs @@ -106,7 +106,8 @@ impl BusDevice for BinaryCounter { &mut self, bus_id: &BusId, data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/state-machines/binary/src/binary_extension_collector.rs b/state-machines/binary/src/binary_extension_collector.rs index 0caa5dfc3..62757b8ae 100644 --- a/state-machines/binary/src/binary_extension_collector.rs +++ b/state-machines/binary/src/binary_extension_collector.rs @@ -58,7 +58,8 @@ impl BusDevice for BinaryExtensionCollector { &mut self, bus_id: &BusId, data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/state-machines/main/src/main_counter.rs b/state-machines/main/src/main_counter.rs index 6f055358d..14418d60b 100644 --- a/state-machines/main/src/main_counter.rs +++ b/state-machines/main/src/main_counter.rs @@ -64,7 +64,8 @@ impl BusDevice for MainCounter { &mut self, bus_id: &BusId, data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/state-machines/mem-common/src/mem_counters.rs b/state-machines/mem-common/src/mem_counters.rs index 31cc51e3c..d76c972af 100644 --- a/state-machines/mem-common/src/mem_counters.rs +++ b/state-machines/mem-common/src/mem_counters.rs @@ -328,7 +328,8 @@ impl BusDevice for MemCounters { &mut self, bus_id: &BusId, data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(bus_id == &MEM_BUS_ID); diff --git a/state-machines/mem/src/mem_align_collector.rs b/state-machines/mem/src/mem_align_collector.rs index 7e8097629..f5db472d4 100644 --- a/state-machines/mem/src/mem_align_collector.rs +++ b/state-machines/mem/src/mem_align_collector.rs @@ -73,7 +73,8 @@ impl BusDevice for MemAlignCollector { &mut self, bus_id: &BusId, data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == MEM_BUS_ID); diff --git a/state-machines/mem/src/mem_module_collector.rs b/state-machines/mem/src/mem_module_collector.rs index c5dc3392e..f8eddf80f 100644 --- a/state-machines/mem/src/mem_module_collector.rs +++ b/state-machines/mem/src/mem_module_collector.rs @@ -480,7 +480,8 @@ impl BusDevice for MemModuleCollector { &mut self, bus_id: &BusId, data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == MEM_BUS_ID); diff --git a/state-machines/mem/src/mem_test.rs b/state-machines/mem/src/mem_test.rs index 6cf625d6b..e649e54c1 100644 --- a/state-machines/mem/src/mem_test.rs +++ b/state-machines/mem/src/mem_test.rs @@ -44,6 +44,7 @@ fn add_test_aligned_mem_reads( counter.process_data( &MEM_BUS_ID, &[MEMORY_LOAD_OP as u64, addr as u64, step + i * step_delta, 8, value], + &[], &mut VecDeque::new(), None, ); @@ -75,6 +76,7 @@ fn add_mem_data( counter.process_data( &MEM_BUS_ID, &[op, addr, step, width, value], + &[], &mut VecDeque::new(), None, ); @@ -107,6 +109,7 @@ fn add_mem_read64(counter: &mut MemCounters, addr: u32, step: u64, value: u64) { counter.process_data( &MEM_BUS_ID, &[MEMORY_LOAD_OP as u64, addr as u64, step, 8, value], + &[], &mut VecDeque::new(), None, ); @@ -116,6 +119,7 @@ fn add_mem_write64(counter: &mut MemCounters, addr: u32, step: u64, value: u64) counter.process_data( &MEM_BUS_ID, &[MEMORY_STORE_OP as u64, addr as u64, step, 8, value], + &[], &mut VecDeque::new(), None, ); diff --git a/state-machines/rom/src/rom_instance.rs b/state-machines/rom/src/rom_instance.rs index fe969a7de..97d56afa3 100644 --- a/state-machines/rom/src/rom_instance.rs +++ b/state-machines/rom/src/rom_instance.rs @@ -264,7 +264,8 @@ impl BusDevice for RomCollector { &mut self, bus_id: &BusId, data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == ROM_BUS_ID); From 3aaf19e225910453802e8d405edf9a161f9a4022 Mon Sep 17 00:00:00 2001 From: ilitteri Date: Sat, 22 Nov 2025 10:56:29 -0300 Subject: [PATCH 011/782] add gpu feature flag to zisk_common --- distributed/crates/common/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/distributed/crates/common/Cargo.toml b/distributed/crates/common/Cargo.toml index cb6ca3f3f..0e0b7edc4 100644 --- a/distributed/crates/common/Cargo.toml +++ b/distributed/crates/common/Cargo.toml @@ -17,3 +17,6 @@ uuid = { version = "1.0", features = ["v4"] } thiserror = "2.0" anyhow = "1.0" chrono = { version = "0.4", features = ["serde"] } + +[features] +gpu = ["proofman/gpu", "proofman-common/gpu"] From 26252daa611491242c74cebc2f5fdea2eb007781 Mon Sep 17 00:00:00 2001 From: ilitteri Date: Sat, 22 Nov 2025 10:56:37 -0300 Subject: [PATCH 012/782] enable missing gpu features --- sdk/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 8b9a9e66c..984c1a10e 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -25,7 +25,7 @@ zisk-distributed-common = { workspace = true } [features] default = [] -gpu = [] +gpu = ["proofman/gpu", "proofman-common/gpu", "zisk-distributed-common/gpu"] stats = [] disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] From 58c10414ec8a81e4f3a4992b075df325981f5c9a Mon Sep 17 00:00:00 2001 From: ilitteri Date: Thu, 27 Nov 2025 11:30:32 -0300 Subject: [PATCH 013/782] Enable rom-setup gpu feature --- sdk/Cargo.toml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 984c1a10e..c8a658fd1 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -25,9 +25,14 @@ zisk-distributed-common = { workspace = true } [features] default = [] -gpu = ["proofman/gpu", "proofman-common/gpu", "zisk-distributed-common/gpu"] +gpu = [ + "proofman/gpu", + "proofman-common/gpu", + "zisk-distributed-common/gpu", + "rom-setup/gpu", +] stats = [] disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(distributed)'] } \ No newline at end of file +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(distributed)'] } From 6f02cc1aa7379b3170f7a9260b938c957631aa60 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 27 Nov 2025 17:58:44 +0100 Subject: [PATCH 014/782] Precompile results activation per precompile --- core/src/bin/riscv2zisk.rs | 2 +- core/src/elf2rom.rs | 11 +++- core/src/riscv2zisk.rs | 12 ++++- core/src/zisk_rom_2_asm.rs | 106 +++++++++++++++++++++++++++++-------- emulator/src/emu.rs | 5 ++ rom-setup/src/asm_setup.rs | 2 +- 6 files changed, 109 insertions(+), 29 deletions(-) diff --git a/core/src/bin/riscv2zisk.rs b/core/src/bin/riscv2zisk.rs index 25c70cac1..75a4906ff 100644 --- a/core/src/bin/riscv2zisk.rs +++ b/core/src/bin/riscv2zisk.rs @@ -56,7 +56,7 @@ fn main() { let rv2zk = Riscv2zisk::new(elf_file); // Convert program - if let Err(e) = rv2zk.runfile(asm_file.unwrap(), generation_method, true, true) { + if let Err(e) = rv2zk.runfile(asm_file.unwrap(), generation_method, true, true, false) { println!("Application error: {e}"); process::exit(1); } diff --git a/core/src/elf2rom.rs b/core/src/elf2rom.rs index 50518e0c4..9a64be8e8 100644 --- a/core/src/elf2rom.rs +++ b/core/src/elf2rom.rs @@ -221,10 +221,17 @@ pub fn elf2romfile( generation_method: AsmGenerationMethod, log_output: bool, comments: bool, + precompile_hints: bool, ) -> Result<(), Box> { let rom = elf2rom(elf_file)?; - ZiskRom2Asm::save_to_asm_file(&rom, asm_file, generation_method, log_output, comments); - + ZiskRom2Asm::save_to_asm_file( + &rom, + asm_file, + generation_method, + log_output, + comments, + precompile_hints, + ); Ok(()) } diff --git a/core/src/riscv2zisk.rs b/core/src/riscv2zisk.rs index 28cdefd78..c28663821 100644 --- a/core/src/riscv2zisk.rs +++ b/core/src/riscv2zisk.rs @@ -80,9 +80,17 @@ impl Riscv2zisk { generation_method: AsmGenerationMethod, log_output: bool, comments: bool, + precompile_hints: bool, ) -> Result<(), Box> { - elf2romfile(&self.elf_file, &asm_file.into(), generation_method, log_output, comments) - .map_err(|e| format!("Error converting elf to assembly: {e}").into()) + elf2romfile( + &self.elf_file, + &asm_file.into(), + generation_method, + log_output, + comments, + precompile_hints, + ) + .map_err(|e| format!("Error converting elf to assembly: {e}").into()) } /// Executes the file conversion process by calling elf2rom() diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index c3766ac0b..64ee8c7b5 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -128,7 +128,8 @@ pub struct ZiskAsmContext { ptr: String, // "ptr ", "" - //assert_rsp_counter: u64, + //assert_rsp_counter: u64, + precompile_results: bool, } impl ZiskAsmContext { @@ -215,7 +216,64 @@ impl ZiskAsmContext { } pub fn precompile_results(&self) -> bool { - false + self.precompile_results + } + pub fn precompile_results_keccak(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_sha256(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_arith256(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_arith256mod(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_secp256k1add(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_secp256k1dbl(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_fcall(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_bn254curveadd(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_bn254curvedbl(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_bn254complexadd(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_bn254complexsub(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_bn254complexmul(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_arith384mod(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_bls12_381curveadd(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_bls12_381curvedbl(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_bls12_381complexadd(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_bls12_381complexsub(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_bls12_381complexmul(&self) -> bool { + self.precompile_results() && false + } + pub fn precompile_results_add256(&self) -> bool { + self.precompile_results() && false } } @@ -384,11 +442,11 @@ impl ZiskRom2Asm { generation_method: AsmGenerationMethod, log_output: bool, comments: bool, + precompile_results: bool, ) { // Get a string with the ASM data let mut s = String::new(); - Self::save_to_asm(rom, &mut s, generation_method, log_output, comments); - + Self::save_to_asm(rom, &mut s, generation_method, log_output, comments, precompile_results); // Save to file let path = std::path::PathBuf::from(file_name); let result = std::fs::write(path, s); @@ -407,6 +465,7 @@ impl ZiskRom2Asm { generation_method: AsmGenerationMethod, log_output: bool, comments: bool, + precompile_results: bool, ) { // Clear output data, just in case code.clear(); @@ -423,6 +482,7 @@ impl ZiskRom2Asm { boc: "/* ".to_string(), eoc: " */".to_string(), min_program_pc: rom.min_program_pc, + precompile_results, ..Default::default() }; @@ -5037,7 +5097,7 @@ impl ZiskRom2Asm { } // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_keccak() { Self::precompile_results_array(ctx, code, "rdi", 25); } else { // Call the keccak function @@ -5116,7 +5176,7 @@ impl ZiskRom2Asm { } // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_sha256() { *code += &format!("\tmov rdi, [rdi]\n"); Self::precompile_results_array(ctx, code, "rdi", 4); } else { @@ -5228,7 +5288,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_arith256() { *code += &format!("\tmov {REG_FLAG}, [rdi+3*8]\n"); Self::precompile_results_array(ctx, code, REG_FLAG, 4); *code += &format!("\tmov {REG_FLAG}, [rdi+4*8]\n"); @@ -5289,7 +5349,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_arith256mod() { *code += &format!("\tmov rdi, [rdi + 4*8]\n"); Self::precompile_results_array(ctx, code, "rdi", 4); } else { @@ -5370,7 +5430,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_secp256k1add() { *code += &format!("\tmov rdi, [rdi]\n"); Self::precompile_results_array(ctx, code, "rdi", 8); } else { @@ -5476,7 +5536,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_secp256k1dbl() { Self::precompile_results_array(ctx, code, "rdi", 8); } else { // Call the secp256k1_dbl function @@ -5620,7 +5680,7 @@ impl ZiskRom2Asm { ); // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_fcall() { Self::precompile_results_fcall(ctx, code, "rdi"); } else { // Call the fcall function @@ -5777,7 +5837,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_bn254curveadd() { *code += &format!("\tmov rdi, [rdi]\n"); Self::precompile_results_array(ctx, code, "rdi", 8); } else { @@ -5884,7 +5944,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_bn254curvedbl() { Self::precompile_results_array(ctx, code, "rdi", 8); } else { // Call the bn254_curve_dbl function @@ -5965,7 +6025,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_bn254complexadd() { *code += &format!("\tmov rdi, [rdi]\n"); Self::precompile_results_array(ctx, code, "rdi", 8); } else { @@ -6047,7 +6107,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_bn254complexsub() { *code += &format!("\tmov rdi, [rdi]\n"); Self::precompile_results_array(ctx, code, "rdi", 8); } else { @@ -6129,7 +6189,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_bn254complexmul() { *code += &format!("\tmov rdi, [rdi]\n"); Self::precompile_results_array(ctx, code, "rdi", 8); } else { @@ -6219,7 +6279,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_arith384mod() { *code += &format!("\tmov rdi, [rdi + 4*8]\n"); Self::precompile_results_array(ctx, code, "rdi", 6); } else { @@ -6301,7 +6361,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_bls12_381curveadd() { *code += &format!("\tmov rdi, [rdi]\n"); Self::precompile_results_array(ctx, code, "rdi", 12); } else { @@ -6408,7 +6468,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_bls12_381curvedbl() { Self::precompile_results_array(ctx, code, "rdi", 12); } else { // Call the bls12_381_curve_dbl function @@ -6493,7 +6553,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_bls12_381complexadd() { *code += &format!("\tmov rdi, [rdi]\n"); Self::precompile_results_array(ctx, code, "rdi", 12); } else { @@ -6579,7 +6639,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_bls12_381complexsub() { *code += &format!("\tmov rdi, [rdi]\n"); Self::precompile_results_array(ctx, code, "rdi", 12); } else { @@ -6665,7 +6725,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_bls12_381complexmul() { *code += &format!("\tmov rdi, [rdi]\n"); Self::precompile_results_array(ctx, code, "rdi", 12); } else { @@ -6769,7 +6829,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { // Get result from precompile results data - if ctx.precompile_results() { + if ctx.precompile_results_add256() { *code += &format!("\tmov rdi, [rdi+3*8]\n"); Self::precompile_results_array(ctx, code, "rdi", 4); Self::precompile_results_register(ctx, code, REG_C); diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 28d221785..5fb9444da 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -1857,6 +1857,11 @@ impl<'a> Emu<'a> { // #[cfg(feature = "sp")] // self.set_sp(instruction); + // println!( + // "s={} pc={:x} c={:x}", + // self.ctx.inst_ctx.step, self.ctx.inst_ctx.pc, self.ctx.inst_ctx.c + // ); + // Set PC, based on current PC, current flag and current instruction self.set_pc(instruction); diff --git a/rom-setup/src/asm_setup.rs b/rom-setup/src/asm_setup.rs index 525827c17..22ee23c36 100644 --- a/rom-setup/src/asm_setup.rs +++ b/rom-setup/src/asm_setup.rs @@ -46,7 +46,7 @@ pub fn generate_assembly( // Convert the ELF file to Zisk format and generates an assembly file let rv2zk = Riscv2zisk::new(elf_file_path.to_str().unwrap().to_string()); rv2zk - .runfile(asm_file.to_str().unwrap().to_string(), *gen_method, false, false) + .runfile(asm_file.to_str().unwrap().to_string(), *gen_method, false, false, false) .expect("Error converting elf to assembly"); let emulator_asm_path = zisk_path.join("emulator-asm"); From 159f28f3dea572f8d0cbac252bad9c99f457a2a4 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 1 Dec 2025 11:41:28 +0100 Subject: [PATCH 015/782] First versio of ECRecover hint function --- core/src/zisk_ops.rs | 2 +- hints/Cargo.toml | 17 ++ hints/src/hints.rs | 44 ++++ hints/src/hints_definitions.rs | 6 + hints/src/lib.rs | 7 + hints/src/secp256k1/curve.rs | 231 ++++++++++++++++++ hints/src/secp256k1/mod.rs | 5 + hints/src/secp256k1/scalar.rs | 42 ++++ .../entrypoint/src/zisklib/fcalls_impl/mod.rs | 2 +- .../src/zisklib/fcalls_impl/msb_pos_256.rs | 2 +- ziskos/entrypoint/src/zisklib/lib/mod.rs | 2 +- .../src/zisklib/lib/secp256k1/mod.rs | 2 +- ziskos/entrypoint/src/zisklib/mod.rs | 4 +- 13 files changed, 359 insertions(+), 7 deletions(-) create mode 100644 hints/Cargo.toml create mode 100644 hints/src/hints.rs create mode 100644 hints/src/hints_definitions.rs create mode 100644 hints/src/lib.rs create mode 100644 hints/src/secp256k1/curve.rs create mode 100644 hints/src/secp256k1/mod.rs create mode 100644 hints/src/secp256k1/scalar.rs diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 0911fe368..e0a050221 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -395,7 +395,7 @@ define_ops! { (Arith256, "arith256", ArithEq, ARITH_EQ_COST, 0xf2, 136, 64, opc_arith256, op_arith256, ops_arith256), (Arith256Mod, "arith256_mod", ArithEq, ARITH_EQ_COST, 0xf3, 168, 32, opc_arith256_mod, op_arith256_mod, ops_arith256_mod), (Secp256k1Add, "secp256k1_add", ArithEq, ARITH_EQ_COST, 0xf4, 144, 64, opc_secp256k1_add, op_secp256k1_add, ops_secp256k1_add), - (Secp256k1Dbl, "secp256k1_dbl", ArithEq, ARITH_EQ_COST, 0xf5, 64, 64, opc_secp256k1_dbl, op_secp256k1_add, ops_secp256k1_dbl), + (Secp256k1Dbl, "secp256k1_dbl", ArithEq, ARITH_EQ_COST, 0xf5, 64, 64, opc_secp256k1_dbl, op_secp256k1_dbl, ops_secp256k1_dbl), (FcallParam, "fcall_param", Fcall, FCALL_COST, 0xf6, 0, 0, opc_fcall_param, op_fcall_param, ops_none), (Fcall, "fcall", Fcall, FCALL_COST, 0xf7, 0, 0, opc_fcall, op_fcall, ops_none), (FcallGet, "fcall_get", Fcall, FCALL_COST, 0xf8, 0, 0, opc_fcall_get, op_fcall_get, ops_none), diff --git a/hints/Cargo.toml b/hints/Cargo.toml new file mode 100644 index 000000000..4f812d85c --- /dev/null +++ b/hints/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "zisk-hints" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +keywords = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } + +[lib] +name = "zisk_hints" +path = "src/lib.rs" + +[dependencies] +ziskos = { workspace = true } +lib-c = { workspace = true } +precompiles-helpers = { workspace = true } \ No newline at end of file diff --git a/hints/src/hints.rs b/hints/src/hints.rs new file mode 100644 index 000000000..8628a0483 --- /dev/null +++ b/hints/src/hints.rs @@ -0,0 +1,44 @@ +use crate::secp256k1_ecdsa_verify; +use ziskos::syscalls::SyscallPoint256; + +use crate::hints_definitions::{HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT}; + +pub fn process_hints(hints: Vec) -> Vec { + let mut processed_hints = Vec::new(); + + let mut i = 0; + while i < hints.len() { + let hint = hints[i]; + i += 1; + let hint_type = (hint >> 32) as u32; + let hint_length = (hint & 0xFFFFFFFF) as usize; + if hint_length == 0 { + panic!("process_hints() Invalid hint length: {}", hint_length); + } + assert!(i + hint_length <= hints.len(), "process_hints() Not enough data for RESULT hint"); + match hint_type { + HINTS_TYPE_RESULT => { + // Process result hint: just push the hint data as is + processed_hints.extend_from_slice(&hints[i..i + hint_length]); + } + HINTS_TYPE_ECRECOVER => { + assert!( + hint_length == 8 + 4 + 4 + 4, + "process_hints() Invalid ECRECOVER hint length: {}", + hint_length + ); + let pk: &SyscallPoint256 = unsafe { &*(hints[i] as *const SyscallPoint256) }; + let z: &[u64; 4] = unsafe { &*(hints[i + 8] as *const [u64; 4]) }; + let r: &[u64; 4] = unsafe { &*(hints[i + 8 + 4] as *const [u64; 4]) }; + let s: &[u64; 4] = unsafe { &*(hints[i + 8 + 4 + 4] as *const [u64; 4]) }; + secp256k1_ecdsa_verify(pk, z, r, s, &mut processed_hints); + } + _ => { + panic!("process_hints() Unknown hint type: {}", hint_type); + } + } + i += hint_length; + } + + processed_hints +} diff --git a/hints/src/hints_definitions.rs b/hints/src/hints_definitions.rs new file mode 100644 index 000000000..7c1c2492f --- /dev/null +++ b/hints/src/hints_definitions.rs @@ -0,0 +1,6 @@ +// Every hint is preceded by a u64 prefix that contains its type and length +// The upper 32 bits contain the hint type, and the lower 32 bits contain the length + +// Hints type constants +pub const HINTS_TYPE_RESULT: u32 = 1; // Data is already the result of the precompile +pub const HINTS_TYPE_ECRECOVER: u32 = 2; // Data is the input for the ecrecover precompile diff --git a/hints/src/lib.rs b/hints/src/lib.rs new file mode 100644 index 000000000..85dbfe8c2 --- /dev/null +++ b/hints/src/lib.rs @@ -0,0 +1,7 @@ +pub mod hints; +pub mod hints_definitions; +pub mod secp256k1; + +pub use hints::*; +pub use hints_definitions::*; +pub use secp256k1::*; diff --git a/hints/src/secp256k1/curve.rs b/hints/src/secp256k1/curve.rs new file mode 100644 index 000000000..9152c1440 --- /dev/null +++ b/hints/src/secp256k1/curve.rs @@ -0,0 +1,231 @@ +use crate::{secp256k1_fn_inv, secp256k1_fn_mul, secp256k1_fn_reduce}; +use ziskos::{ + syscalls::SyscallPoint256, + zisklib::{ + constants::{G_X, G_Y}, + eq, + fcalls_impl::msb_pos_256::msb_pos_256, + }, +}; + +/// Given points `p1` and `p2`, performs the point addition `p1 + p2` and assigns the result to `p1`. +/// It assumes that `p1` and `p2` are from the Secp256k1 curve, that `p1,p2 != 𝒪` and that `p2 != p1,-p1` +fn add_points_assign(p1: &mut SyscallPoint256, p2: &SyscallPoint256, hints: &mut Vec) { + let p1_local: [u64; 8] = + [p1.x[0], p1.x[1], p1.x[2], p1.x[3], p1.y[0], p1.y[1], p1.y[2], p1.y[3]]; + let p2_local: [u64; 8] = + [p2.x[0], p2.x[1], p2.x[2], p2.x[3], p2.y[0], p2.y[1], p2.y[2], p2.y[3]]; + let mut p3 = [0u64; 8]; + precompiles_helpers::secp256k1_add(&p1_local, &p2_local, &mut p3); + p1.x = p3[0..4].try_into().unwrap(); + p1.y = p3[4..8].try_into().unwrap(); + hints.extend_from_slice(&p3); +} + +/// Given a point `p1`, performs the point doubling `2·p1` and assigns the result to `p1`. +/// It assumes that `p1` is from the Secp256k1 curve and that `p1 != 𝒪` +/// +/// Note: We don't need to assume that 2·p1 != 𝒪 because there are not points of order 2 on the Secp256k1 curve +fn double_point_assign(p1: &mut SyscallPoint256, hints: &mut Vec) { + let p: [u64; 8] = [p1.x[0], p1.x[1], p1.x[2], p1.x[3], p1.y[0], p1.y[1], p1.y[2], p1.y[3]]; + let mut p3 = [0u64; 8]; + precompiles_helpers::secp256k1_dbl(&p, &mut p3); + p1.x = p3[0..4].try_into().unwrap(); + p1.y = p3[4..8].try_into().unwrap(); + hints.extend_from_slice(&p3); +} + +/// Given points `p1` and `p2`, performs the point addition `p1 + p2` and assigns the result to `p1`. +/// It assumes that `p1` and `p2` are from the Secp256k1 curve, that `p2 != 𝒪` +fn add_points_complete_assign( + p1: &mut SyscallPoint256, + p1_is_infinity: &mut bool, + p2: &SyscallPoint256, + hints: &mut Vec, +) { + if p1.x != p2.x { + add_points_assign(p1, p2, hints); + } else if p1.y == p2.y { + double_point_assign(p1, hints); + } else { + *p1_is_infinity = true; + } +} + +/// Given a point `p` and scalars `k1` and `k2`, computes the double scalar multiplication `k1·G + k2·p` +/// It assumes that `k1,k2 ∈ [1, N-1]` and that `p != 𝒪` +pub fn secp256k1_double_scalar_mul_with_g( + k1: &[u64; 4], + k2: &[u64; 4], + p: &SyscallPoint256, + hints: &mut Vec, +) -> (bool, SyscallPoint256) { + // Start by precomputing g + p + let mut gp = SyscallPoint256 { x: G_X, y: G_Y }; + let mut gp_is_infinity = false; + add_points_complete_assign(&mut gp, &mut gp_is_infinity, p, hints); + + // Hint the maximum length between the binary representations of k1 and k2 + // We will verify the output by recomposing both k1 and k2 + // Moreover, we should check that the first received bit (of either k1 or k2) is 1 + let (max_limb, max_bit) = msb_pos_256(k1, k2); + + // Perform the loop, based on the binary representation of k1 and k2 + // Start at 𝒪 + let mut res = SyscallPoint256 { x: [0u64; 4], y: [0u64; 4] }; + let mut res_is_infinity = true; + let mut k1_rec = [0u64; 4]; + let mut k2_rec = [0u64; 4]; + // We do the first iteration separately + let _max_limb = max_limb as usize; + let k1_bit = (k1[_max_limb] >> max_bit) & 1; + let k2_bit = (k2[_max_limb] >> max_bit) & 1; + assert!(k1_bit == 1 || k2_bit == 1); // At least one of the scalars should start with 1 + if (k1_bit == 0) && (k2_bit == 1) { + // If res is 𝒪, set res = p; otherwise, double res and add p + if res_is_infinity { + res.x = p.x; + res.y = p.y; + res_is_infinity = false; + } else { + double_point_assign(&mut res, hints); + add_points_complete_assign(&mut res, &mut res_is_infinity, p, hints); + } + + // Update k2_rec + k2_rec[_max_limb] |= 1 << max_bit; + } else if (k1_bit == 1) && (k2_bit == 0) { + // If res is 𝒪, set res = g; otherwise, double res and add g + if res_is_infinity { + res.x = G_X; + res.y = G_Y; + res_is_infinity = false; + } else { + double_point_assign(&mut res, hints); + add_points_complete_assign( + &mut res, + &mut res_is_infinity, + &SyscallPoint256 { x: G_X, y: G_Y }, + hints, + ); + } + + // Update k1_rec + k1_rec[_max_limb] |= 1 << max_bit; + } else if (k1_bit == 1) && (k2_bit == 1) { + if res_is_infinity { + // If (g + p) is 𝒪, do nothing; otherwise set res = (g + p) + if !gp_is_infinity { + res.x = gp.x; + res.y = gp.y; + res_is_infinity = false; + } + } else { + // If (g + p) is 𝒪, simply double res; otherwise double res and add (g + p) + double_point_assign(&mut res, hints); + if !gp_is_infinity { + add_points_complete_assign(&mut res, &mut res_is_infinity, &gp, hints); + } + } + + // Update k1_rec and k2_rec + k1_rec[_max_limb] |= 1 << max_bit; + k2_rec[_max_limb] |= 1 << max_bit; + } + + // Perform the rest of the loop + for i in (0..=max_limb).rev() { + let _i = i as usize; + let bit_len = if i == max_limb { max_bit - 1 } else { 63 }; + for j in (0..=bit_len).rev() { + let k1_bit = (k1[_i] >> j) & 1; + let k2_bit = (k2[_i] >> j) & 1; + + if (k1_bit == 0) && (k2_bit == 0) { + // If res is 𝒪, do nothing; otherwise, double + if !res_is_infinity { + double_point_assign(&mut res, hints); + } + } else if (k1_bit == 0) && (k2_bit == 1) { + // If res is 𝒪, set res = p; otherwise, double res and add p + if res_is_infinity { + res.x = p.x; + res.y = p.y; + res_is_infinity = false; + } else { + double_point_assign(&mut res, hints); + add_points_complete_assign(&mut res, &mut res_is_infinity, p, hints); + } + + // Update k2_rec + k2_rec[_i] |= 1 << j; + } else if (k1_bit == 1) && (k2_bit == 0) { + // If res is 𝒪, set res = g; otherwise, double res and add g + if res_is_infinity { + res.x = G_X; + res.y = G_Y; + res_is_infinity = false; + } else { + double_point_assign(&mut res, hints); + add_points_complete_assign( + &mut res, + &mut res_is_infinity, + &SyscallPoint256 { x: G_X, y: G_Y }, + hints, + ); + } + + // Update k1_rec + k1_rec[_i] |= 1 << j; + } else if (k1_bit == 1) && (k2_bit == 1) { + if res_is_infinity { + // If (g + p) is 𝒪, do nothing; otherwise set res = (g + p) + if !gp_is_infinity { + res.x = gp.x; + res.y = gp.y; + res_is_infinity = false; + } + } else { + // If (g + p) is 𝒪, simply double res; otherwise double res and add (g + p) + double_point_assign(&mut res, hints); + if !gp_is_infinity { + add_points_complete_assign(&mut res, &mut res_is_infinity, &gp, hints); + } + } + + // Update k1_rec and k2_rec + k1_rec[_i] |= 1 << j; + k2_rec[_i] |= 1 << j; + } + } + } + + // Check that the recomposed scalars are the same as the received scalars + assert_eq!(k1_rec, *k1); + assert_eq!(k2_rec, *k2); + + (res_is_infinity, res) +} + +pub fn secp256k1_ecdsa_verify( + pk: &SyscallPoint256, + z: &[u64; 4], + r: &[u64; 4], + s: &[u64; 4], + hints: &mut Vec, +) -> bool { + let s_inv: &mut [u64; 4] = &mut [0; 4]; + secp256k1_fn_inv(s, s_inv, hints); + + let u1: &mut [u64; 4] = &mut [0; 4]; + secp256k1_fn_mul(z, &s_inv, u1, hints); + let u2: &mut [u64; 4] = &mut [0; 4]; + secp256k1_fn_mul(r, &s_inv, u2, hints); + + let (is_infinity, res) = secp256k1_double_scalar_mul_with_g(&u1, &u2, pk, hints); + if is_infinity { + return false; + } + + eq(&secp256k1_fn_reduce(&res.x, hints), r) +} diff --git a/hints/src/secp256k1/mod.rs b/hints/src/secp256k1/mod.rs new file mode 100644 index 000000000..bedb88ae1 --- /dev/null +++ b/hints/src/secp256k1/mod.rs @@ -0,0 +1,5 @@ +mod curve; +mod scalar; + +pub use curve::*; +pub use scalar::*; diff --git a/hints/src/secp256k1/scalar.rs b/hints/src/secp256k1/scalar.rs new file mode 100644 index 000000000..b22e87057 --- /dev/null +++ b/hints/src/secp256k1/scalar.rs @@ -0,0 +1,42 @@ +use lib_c::{arith256_mod_c, secp256k1_fn_inv_c}; +use ziskos::zisklib::lib::secp256k1::constants::N; +use ziskos::zisklib::lt; + +const Z: [u64; 4] = [0, 0, 0, 0]; +const O: [u64; 4] = [0, 0, 0, 0]; + +pub fn secp256k1_fn_reduce(x: &[u64; 4], hints: &mut Vec) -> [u64; 4] { + if lt(x, &N) { + return *x; + } + + // x·1 + 0 + let mut module: [u64; 4] = N.clone(); + let mut d: [u64; 4] = [0; 4]; + arith256_mod_c(x, &O, &Z, &mut module, &mut d); + hints.extend_from_slice(&d); + + d +} + +pub fn secp256k1_fn_mul(x: &[u64; 4], y: &[u64; 4], result: &mut [u64; 4], hints: &mut Vec) { + // x·y + 0 + let mut module: [u64; 4] = N.clone(); + arith256_mod_c(x, y, &Z, &mut module, result); + hints.extend_from_slice(result); +} + +/// Inverts a non-zero element `x` +pub fn secp256k1_fn_inv(x: &[u64; 4], x_inv: &mut [u64; 4], hints: &mut Vec) { + // Hint the inverse + secp256k1_fn_inv_c(x, x_inv); + hints.extend_from_slice(x_inv); + + // Check that x·x_inv = 1 (N) + let mut module: [u64; 4] = N.clone(); + let mut d: [u64; 4] = [0; 4]; + arith256_mod_c(x, x_inv, &Z, &mut module, &mut d); + hints.extend_from_slice(&module); + hints.extend_from_slice(&d); + assert_eq!(d, [0x1, 0x0, 0x0, 0x0]); +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs index 6446c8bb7..435a2a501 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs @@ -6,7 +6,7 @@ mod bls12_381_twist; mod bn254_fp; mod bn254_fp2; mod bn254_twist; -mod msb_pos_256; +pub mod msb_pos_256; mod msb_pos_384; mod proxy; mod secp256k1_fn_inv; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_256.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_256.rs index ac5808270..591b3c1ca 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_256.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_256.rs @@ -10,7 +10,7 @@ pub fn fcall_msb_pos_256(parameters: &[u64], results: &mut [u64]) -> i64 { } // Q: Do we prefer constant time functions? -fn msb_pos_256(x: &[u64; 4], y: &[u64; 4]) -> (usize, usize) { +pub fn msb_pos_256(x: &[u64; 4], y: &[u64; 4]) -> (usize, usize) { for i in (0..4).rev() { if x[i] != 0 || y[i] != 0 { let word = if x[i] > y[i] { x[i] } else { y[i] }; diff --git a/ziskos/entrypoint/src/zisklib/lib/mod.rs b/ziskos/entrypoint/src/zisklib/lib/mod.rs index f1742d46b..4afa4bf80 100644 --- a/ziskos/entrypoint/src/zisklib/lib/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/mod.rs @@ -1,7 +1,7 @@ mod bigint256; mod bls12_381; mod bn254; -mod secp256k1; +pub mod secp256k1; mod sha256f_compress; mod utils; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs index 30c4afb6b..3a313d331 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs @@ -1,4 +1,4 @@ -mod constants; +pub mod constants; mod curve; mod field; mod scalar; diff --git a/ziskos/entrypoint/src/zisklib/mod.rs b/ziskos/entrypoint/src/zisklib/mod.rs index ddcd0d5ff..e9d06b582 100644 --- a/ziskos/entrypoint/src/zisklib/mod.rs +++ b/ziskos/entrypoint/src/zisklib/mod.rs @@ -1,6 +1,6 @@ mod fcalls; -mod fcalls_impl; -mod lib; +pub mod fcalls_impl; +pub mod lib; pub use fcalls::*; pub use fcalls_impl::*; From 3cc4a6e8e796108ae2ad50ecd568a10603994870 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 1 Dec 2025 11:42:24 +0100 Subject: [PATCH 016/782] Update Cargo.toml --- Cargo.lock | 9 +++++++++ Cargo.toml | 2 ++ 2 files changed, 11 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 89748fdd3..46d1308c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5712,6 +5712,15 @@ dependencies = [ "zisk-sdk", ] +[[package]] +name = "zisk-hints" +version = "0.15.0" +dependencies = [ + "lib-c", + "precompiles-helpers", + "ziskos", +] + [[package]] name = "zisk-pil" version = "0.15.0" diff --git a/Cargo.toml b/Cargo.toml index 1dfc8381b..3c79d89eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ members = [ "distributed/crates/worker", "sdk", "ziskbuild", + "hints", ] resolver = "2" @@ -95,6 +96,7 @@ ziskos = { path = "ziskos/entrypoint" } circuit = { path = "tools/circuit" } zisk-sdk = { path = "sdk" } zisk-build = { path = "ziskbuild" } +zisk-hints = { path = "hints" } # Distributed crates zisk-distributed-common = { path = "distributed/crates/common" } From bfed9d223871b93b961f0a3120e335cbd12e8e12 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 2 Dec 2025 07:14:32 +0000 Subject: [PATCH 017/782] wip --- Cargo.lock | 2 + precompiles/common/Cargo.toml | 5 +- precompiles/common/src/lib.rs | 2 + precompiles/common/src/precompiles_hints.rs | 735 ++++++++++++++++++++ 4 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 precompiles/common/src/precompiles_hints.rs diff --git a/Cargo.lock b/Cargo.lock index 89748fdd3..c623f497e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3002,7 +3002,9 @@ dependencies = [ name = "precompiles-common" version = "0.15.0" dependencies = [ + "anyhow", "fields", + "rayon", "zisk-common", "zisk-core", ] diff --git a/precompiles/common/Cargo.toml b/precompiles/common/Cargo.toml index dd772a536..667b1c244 100644 --- a/precompiles/common/Cargo.toml +++ b/precompiles/common/Cargo.toml @@ -11,4 +11,7 @@ categories = { workspace = true } zisk-core = { workspace = true } zisk-common = { workspace = true } -fields = { workspace = true } \ No newline at end of file +fields = { workspace = true } + +anyhow = { workspace = true } +rayon = { workspace = true } diff --git a/precompiles/common/src/lib.rs b/precompiles/common/src/lib.rs index f37f9081a..bf46b60c0 100644 --- a/precompiles/common/src/lib.rs +++ b/precompiles/common/src/lib.rs @@ -1,6 +1,8 @@ mod goldilocks_constants; +mod precompiles_hints; pub use goldilocks_constants::{get_ks, GOLDILOCKS_GEN, GOLDILOCKS_K}; +pub use precompiles_hints::{PrecompileHint, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT}; use std::collections::VecDeque; use zisk_common::{BusId, MEM_BUS_ID}; diff --git a/precompiles/common/src/precompiles_hints.rs b/precompiles/common/src/precompiles_hints.rs new file mode 100644 index 000000000..5c47f18f0 --- /dev/null +++ b/precompiles/common/src/precompiles_hints.rs @@ -0,0 +1,735 @@ +//! Precompile Hints Processor +//! +//! This module provides functionality for parsing and processing precompile hints +//! that are received as a stream of `u64` values. Hints are used to provide preprocessed +//! data to precompile operations in the ZisK zkVM. +//! +//! # Hint Format +//! +//! Each hint consists of: +//! - A **header** (`u64`): Contains the hint type (upper 32 bits) and data length (lower 32 bits) +//! - **Data** (`[u64; length]`): The hint payload, where `length` is specified in the header +//! +//! ```text +//! ┌────────────────────────────────────────────────────────────────┐ +//! │ Header (u64) │ +//! ├────────────────────────────────┬───────────────────────────────┤ +//! │ Hint Code (32 bits) │ Length (32 bits) │ +//! ├────────────────────────────────┴───────────────────────────────┤ +//! │ Data[0] (u64) │ +//! ├────────────────────────────────────────────────────────────────┤ +//! │ Data[1] (u64) │ +//! ├────────────────────────────────────────────────────────────────┤ +//! │ ... │ +//! ├────────────────────────────────────────────────────────────────┤ +//! │ Data[length-1] (u64) │ +//! └────────────────────────────────────────────────────────────────┘ +//! +//! - Hint Code — Control code or Data Hint Type +//! - Length — Number of following u64 data words +//! +//! ## Hint Type Layout +//! +//! ### Control codes +//! +//! The following control codes are defined: +//! - `0x00` (START): Reset processor state and global sequence. +//! - `0x01` (END): Wait until completion of all pending hints. +//! - `0x02` (CANCEL): Cancel current stream and stop processing further hints. +//! - `0x03` (ERROR): Indicate an error has occurred; stop processing further hints. +//! +//! Control codes are for control only and do not have any associated data (Length should be zero). +//! +//! ### Data Hint Types: +//! - `0x04` (`HINTS_TYPE_RESULT`): Pass-through data +//! - `0x05` (`HINTS_TYPE_ECRECOVER`): ECRECOVER inputs (currently returns empty) +//! ``` + +use anyhow::Result; +use rayon::{ThreadPool, ThreadPoolBuilder}; +use std::collections::VecDeque; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::{Arc, Condvar, Mutex}; + +/// Hint type indicating that the data is already the precomputed result. +/// +/// When a hint has this type, the processor simply passes through the data +/// without any additional computation. +pub const HINTS_TYPE_RESULT: u32 = 1; + +/// Hint type indicating that the data contains inputs for the ecrecover precompile. +pub const HINTS_TYPE_ECRECOVER: u32 = 2; + +/// Stream control is encoded in the high byte (bits 31..24) of `hint_type`. +/// Base type is the lower 24 bits (bits 23..0). +const STREAM_CTRL_MASK: u32 = 0xFF00_0000; +const STREAM_BASE_MASK: u32 = 0x00FF_FFFF; +const STREAM_CTRL_SHIFT: u32 = 24; + +const STREAM_CTRL_NONE: u32 = 0x00; +const STREAM_CTRL_START: u32 = 0x01; // reset stream state +const STREAM_CTRL_END: u32 = 0x02; // wait until completion +const STREAM_CTRL_CANCEL: u32 = 0x03; // cancel processing +const STREAM_CTRL_ERROR: u32 = 0x04; // signal error + +/// Represents a single precompile hint parsed from a `u64` slice. +/// +/// A hint consists of a type identifier and associated data. The hint type +/// determines how the data should be processed by the [`PrecompileHintsProcessor`]. +pub struct PrecompileHint { + /// The type of hint, determining how the data should be processed. + hint_type: u32, + /// The hint payload data. + data: Vec, +} + +impl std::fmt::Debug for PrecompileHint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PrecompileHint") + .field("hint_type", &self.hint_type) + .field("data", &self.data) + .finish() + } +} + +impl PrecompileHint { + /// Parses a [`PrecompileHint`] from a slice of `u64` values at the given index. + /// + /// # Arguments + /// + /// * `slice` - The source slice containing concatenated hints + /// * `idx` - The index where the hint header starts + /// + /// # Returns + /// + /// * `Ok(PrecompileHint)` - Successfully parsed hint + /// * `Err` - If the slice is too short or the index is out of bounds + #[inline(always)] + fn from_u64_slice(slice: &[u64], idx: usize) -> Result { + if slice.is_empty() || idx >= slice.len() { + return Err(anyhow::anyhow!("Slice too short to contain a hint")); + } + + let header = slice[idx]; + let hint_type = (header >> 32) as u32; + let length = (header & 0xFFFFFFFF) as u32; + + if slice.len() < idx + length as usize + 1 { + return Err(anyhow::anyhow!( + "Slice too short for hint data: expected {}, got {}", + length, + slice.len() - idx - 1 + )); + } + + let data = slice[idx + 1..idx + length as usize + 1].to_vec(); + + Ok(PrecompileHint { hint_type, data }) + } +} + +/// Shared state for the reorder buffer used by `process_hints_2`. +/// +/// This structure maintains a global sequence counter and a VecDeque that +/// holds processed results in order, allowing out-of-order completion while +/// ensuring in-order output. +struct ReorderBuffer { + /// The reorder buffer: None = pending, Some(Ok(...)) = ready, Some(Err(...)) = error + buffer: VecDeque>>>, + /// Sequence ID of buffer[0] (the next result to drain/print) + base_seq: usize, +} + +/// Shared state across multiple calls to `process_hints_2`. +struct SharedState { + /// The reorder buffer protected by a mutex + reorder: Mutex, + /// Condvar to signal when buffer becomes empty or error occurs + buffer_empty: Condvar, + /// Global sequence counter for assigning seq_ids to hints + next_seq: AtomicUsize, + /// Flag to signal that an error occurred and processing should stop + has_error: AtomicBool, + /// Generation counter to detect stale workers after reset + generation: AtomicUsize, +} + +impl SharedState { + fn new() -> Self { + Self { + reorder: Mutex::new(ReorderBuffer { buffer: VecDeque::new(), base_seq: 0 }), + buffer_empty: Condvar::new(), + next_seq: AtomicUsize::new(0), + has_error: AtomicBool::new(false), + generation: AtomicUsize::new(0), + } + } +} + +/// Processor for precompile hints that supports parallel execution. +/// +/// This struct provides methods to parse and process a stream of concatenated +/// hints, using a dedicated Rayon thread pool for parallel processing while +/// preserving the original order of results. +pub struct PrecompileHintsProcessor { + /// The thread pool used for parallel hint processing. + pool: ThreadPool, + /// Shared state for the reorder buffer (used by process_hints_2) + shared: Arc, +} + +impl PrecompileHintsProcessor { + const NUM_THREADS: usize = 32; + + /// Creates a new processor with the default number of threads. + /// + /// The default is the number of available CPU cores. + /// + /// # Returns + /// + /// * `Ok(PrecompileHintsProcessor)` - The configured processor + /// * `Err` - If the thread pool fails to initialize + pub fn new() -> Result { + Self::with_num_threads(Self::NUM_THREADS) + } + + /// Creates a new processor with the specified number of threads. + /// + /// # Arguments + /// + /// * `num_threads` - The number of worker threads in the pool + /// + /// # Returns + /// + /// * `Ok(PrecompileHintsProcessor)` - The configured processor + /// * `Err` - If the thread pool fails to initialize + pub fn with_num_threads(num_threads: usize) -> Result { + let pool = ThreadPoolBuilder::new() + .num_threads(num_threads) + .build() + .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; + + Ok(Self { pool, shared: Arc::new(SharedState::new()) }) + } + + /// Processes hints in parallel with non-blocking, ordered output. + /// + /// This method dispatches each hint to the thread pool for parallel processing. + /// Results are collected in a reorder buffer and drained (printed) in the original + /// order as soon as consecutive results become available. + /// + /// # Key characteristics: + /// - **Non-blocking**: Returns immediately after dispatching work to the pool + /// - **Global sequence**: Sequence IDs are maintained across multiple calls + /// - **Ordered output**: Results are printed in the order hints were received + /// - **Error handling**: Stops processing on first error + /// + /// # Arguments + /// + /// * `hints` - A slice of `u64` values containing concatenated hints + /// + /// # Returns + /// + /// * `Ok(())` - Hints were successfully dispatched (does not mean processing is complete) + /// * `Err` - If a previous error occurred or hints are malformed + pub fn process_hints(&self, hints: &[u64]) -> Result<()> { + // Check if a previous error occurred + if self.shared.has_error.load(Ordering::Acquire) { + return Err(anyhow::anyhow!("Processing stopped due to previous error")); + } + + // Parse hints and dispatch to pool + let mut idx = 0; + while idx < hints.len() { + // Check for error before processing each hint + if self.shared.has_error.load(Ordering::Acquire) { + return Err(anyhow::anyhow!("Processing stopped due to previous error")); + } + + let hint = PrecompileHint::from_u64_slice(hints, idx)?; + let length = hint.data.len(); + + // Decode stream control from high byte + let ctrl = (hint.hint_type & STREAM_CTRL_MASK) >> STREAM_CTRL_SHIFT; + let base_type = hint.hint_type & STREAM_BASE_MASK; + + // Apply stream control actions + match ctrl { + STREAM_CTRL_START => { + // Reset global sequence and buffer at stream start + self.reset(); + // Control hint only; skip processing + idx += length + 1; + continue; + } + STREAM_CTRL_CANCEL => { + // Cancel current stream: set error and notify + self.shared.has_error.store(true, Ordering::Release); + self.shared.buffer_empty.notify_all(); + return Err(anyhow::anyhow!("Stream cancelled")); + } + STREAM_CTRL_ERROR => { + // External error signal + self.shared.has_error.store(true, Ordering::Release); + self.shared.buffer_empty.notify_all(); + return Err(anyhow::anyhow!("Stream error signalled")); + } + STREAM_CTRL_END => { + // Control hint only; wait for completion then skip processing + self.wait_for_completion()?; + idx += length + 1; + continue; + } + _ => {} + } + + // Atomically reserve slot and capture generation inside mutex + // This prevents orphaned slots if reset happens between generation load and push_back + let (generation, seq_id) = { + let mut reorder = self.shared.reorder.lock().unwrap(); + let gen = self.shared.generation.load(Ordering::SeqCst); + let seq = self.shared.next_seq.fetch_add(1, Ordering::SeqCst); + reorder.buffer.push_back(None); + (gen, seq) + }; + + // Spawn processing task + let shared = Arc::clone(&self.shared); + self.pool.spawn(move || { + // Check if we should stop due to error + if shared.has_error.load(Ordering::Acquire) { + return; + } + + // Process the hint + // Override hint type to base type for processing + let mut hint_for_proc = hint; + hint_for_proc.hint_type = base_type; + let result = Self::process_hint(&hint_for_proc); + + // Store result and try to drain + let mut reorder = shared.reorder.lock().unwrap(); + + // Check generation first to detect stale workers from previous sessions + let current_gen = shared.generation.load(Ordering::SeqCst); + if generation != current_gen { + // Worker belongs to old generation; ignore result + return; + } + + // Calculate offset in buffer; handle resets and drained slots + if seq_id < reorder.base_seq { + // This result belongs to a previous stream/session; ignore + return; + } + let offset = seq_id - reorder.base_seq; + if offset >= reorder.buffer.len() { + // Buffer no longer has a slot for this seq (likely after reset); ignore + return; + } + + // Check error flag again before storing to avoid processing after error + if shared.has_error.load(Ordering::Acquire) { + return; + } + + reorder.buffer[offset] = Some(result); + + // Drain consecutive ready results from the front + while let Some(Some(res)) = reorder.buffer.front() { + match res { + Ok(_data) => { + // Print the result (will be replaced with send to another process) + // println!("[seq={}] Result: {:?}", reorder.base_seq, data); + reorder.buffer.pop_front(); + reorder.base_seq += 1; + } + Err(_) => { + // Error found - signal to stop and break + shared.has_error.store(true, Ordering::Release); + // Print error and stop draining + if let Some(Some(Err(e))) = reorder.buffer.pop_front() { + eprintln!("[seq={}] Error: {}", reorder.base_seq, e); + } + reorder.base_seq += 1; + shared.buffer_empty.notify_all(); + break; + } + } + } + + // Notify if buffer is now empty + if reorder.buffer.is_empty() { + shared.buffer_empty.notify_all(); + } + }); + + idx += length + 1; + } + + Ok(()) + } + + /// Waits for all pending hints to be processed and drained. + /// + /// This method blocks until the reorder buffer is empty, meaning all + /// dispatched hints have been processed and their results printed. + /// + /// # Returns + /// + /// * `Ok(())` - All hints processed successfully + /// * `Err` - If an error occurred during processing + fn wait_for_completion(&self) -> Result<()> { + let mut reorder = self.shared.reorder.lock().unwrap(); + + while !reorder.buffer.is_empty() { + if self.shared.has_error.load(Ordering::Acquire) { + return Err(anyhow::anyhow!("Processing stopped due to error")); + } + // Wait for notification that buffer state changed + reorder = self.shared.buffer_empty.wait(reorder).unwrap(); + } + + if self.shared.has_error.load(Ordering::Acquire) { + return Err(anyhow::anyhow!("Processing stopped due to error")); + } + + Ok(()) + } + + /// Resets the processor state, clearing any errors and the reorder buffer. + /// + /// This should be called to start a fresh processing session after an error + /// or when you want to reset the global sequence counter. + /// + /// Increments the generation counter to invalidate any in-flight workers + /// from the previous session, preventing them from corrupting the new state. + fn reset(&self) { + self.shared.has_error.store(false, Ordering::Release); + self.shared.next_seq.store(0, Ordering::Release); + // Increment generation to invalidate stale workers + self.shared.generation.fetch_add(1, Ordering::SeqCst); + let mut reorder = self.shared.reorder.lock().unwrap(); + reorder.buffer.clear(); + reorder.base_seq = 0; + } + + /// Dispatches a single hint to its appropriate handler based on hint type. + /// + /// # Arguments + /// + /// * `hint` - The parsed hint to process + /// + /// # Returns + /// + /// * `Ok(Vec)` - The processed result for this hint + /// * `Err` - If the hint type is unknown + fn process_hint(hint: &PrecompileHint) -> Result> { + let result = match hint.hint_type { + HINTS_TYPE_RESULT => Self::process_hint_result(hint)?, + HINTS_TYPE_ECRECOVER => Self::process_hint_ecrecover(hint)?, + _ => { + return Err(anyhow::anyhow!("Unknown hint type: {}", hint.hint_type)); + } + }; + + Ok(result) + } + + /// Processes a [`HINTS_TYPE_RESULT`] hint. + /// + /// This is a pass-through handler that simply returns the hint data as-is. + /// Used when the hint already contains the precomputed result. + fn process_hint_result(hint: &PrecompileHint) -> Result> { + Ok(hint.data.to_vec()) + } + + /// Processes a [`HINTS_TYPE_ECRECOVER`] hint. + fn process_hint_ecrecover(_hint: &PrecompileHint) -> Result> { + // TODO! + // assert!( + // hint_length == 8 + 4 + 4 + 4, + // "process_hints() Invalid ECRECOVER hint length: {}", + // hint_length + // ); + // let pk: &SyscallPoint256 = unsafe { &(hint.data[i] as const SyscallPoint256) }; + // let z: &[u64; 4] = unsafe { &(hints[i + 8] as const [u64; 4]) }; + // let r: &[u64; 4] = unsafe { &(hints[i + 8 + 4] as const [u64; 4]) }; + // let s: &[u64; 4] = unsafe { &(hints[i + 8 + 4 + 4] as const [u64; 4]) }; + // secp256k1_ecdsa_verify(pk, z, r, s, &mut processedhints); + + Ok(vec![]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_header(hint_type: u32, length: u32) -> u64 { + ((hint_type as u64) << 32) | (length as u64) + } + + fn make_header_with_ctrl(base_type: u32, ctrl: u32, length: u32) -> u64 { + let hint_type = ((ctrl & 0xFF) << STREAM_CTRL_SHIFT) | (base_type & STREAM_BASE_MASK); + make_header(hint_type, length) + } + + fn processor() -> PrecompileHintsProcessor { + PrecompileHintsProcessor::with_num_threads(2).unwrap() + } + + // Positive tests + #[test] + fn test_single_result_hint_non_blocking() { + let p = processor(); + let data = vec![make_header(HINTS_TYPE_RESULT, 2), 0x111, 0x222]; + + // Dispatch should succeed and be non-blocking + p.process_hints(&data).unwrap(); + // Wait for completion + p.wait_for_completion().unwrap(); + } + + #[test] + fn test_multiple_hints_ordered_output() { + let p = processor(); + let data = vec![ + make_header(HINTS_TYPE_RESULT, 1), + 0x111, + make_header(HINTS_TYPE_RESULT, 1), + 0x222, + make_header(HINTS_TYPE_RESULT, 1), + 0x333, + ]; + p.process_hints(&data).unwrap(); + p.wait_for_completion().unwrap(); + } + + #[test] + fn test_multiple_calls_global_sequence() { + let p = processor(); + let data1 = vec![make_header(HINTS_TYPE_RESULT, 1), 0xAAA]; + let data2 = vec![make_header(HINTS_TYPE_RESULT, 1), 0xBBB]; + p.process_hints(&data1).unwrap(); + p.process_hints(&data2).unwrap(); + p.wait_for_completion().unwrap(); + } + + #[test] + fn test_empty_input_ok() { + let p = processor(); + let data: Vec = vec![]; + p.process_hints(&data).unwrap(); + p.wait_for_completion().unwrap(); + } + + // Negative tests + #[test] + fn test_unknown_hint_type_returns_error() { + let p = processor(); + let data = vec![make_header(999, 1), 0x1234]; + // Dispatch enqueues work; error surfaces on wait + p.process_hints(&data).unwrap(); + let err = p.wait_for_completion().err().unwrap(); + assert!(err.to_string().contains("error")); + } + + #[test] + fn test_error_stops_wait() { + let p = processor(); + // First valid, then invalid type + let data = vec![make_header(HINTS_TYPE_RESULT, 1), 0x111, make_header(999, 0)]; + // Dispatch returns error at parse/process of bad hint + let _ = p.process_hints(&data); + // Wait should report error state + let w = p.wait_for_completion(); + assert!(w.is_err()); + } + + #[test] + fn test_reset_clears_error() { + let p = processor(); + let bad = vec![make_header(999, 0)]; + let _ = p.process_hints(&bad); + // Give workers a moment (no busy wait; optional) + std::thread::sleep(std::time::Duration::from_millis(5)); + p.reset(); + + let good = vec![make_header(HINTS_TYPE_RESULT, 1), 0x42]; + p.process_hints(&good).unwrap(); + p.wait_for_completion().unwrap(); + } + + // Stream control tests + #[test] + fn test_stream_start_resets_state() { + let p = processor(); + // First batch increments sequence + let batch1 = vec![make_header(HINTS_TYPE_RESULT, 1), 0x01]; + p.process_hints(&batch1).unwrap(); + + // Send START control; then a new hint + let start = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_START, 0)]; + p.process_hints(&start).unwrap(); + + let batch2 = vec![make_header(HINTS_TYPE_RESULT, 1), 0x02]; + p.process_hints(&batch2).unwrap(); + // End the stream to ensure completion + let end = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_END, 0)]; + p.process_hints(&end).unwrap(); + p.wait_for_completion().unwrap(); + } + + #[test] + fn test_stream_end_waits_until_completion() { + let p = processor(); + // Dispatch a few hints + let data = + vec![make_header(HINTS_TYPE_RESULT, 1), 0x10, make_header(HINTS_TYPE_RESULT, 1), 0x20]; + p.process_hints(&data).unwrap(); + // End control should cause internal wait during processing + let end = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_END, 0)]; + p.process_hints(&end).unwrap(); + // Subsequent explicit wait should be fast (already drained) + p.wait_for_completion().unwrap(); + } + + #[test] + fn test_stream_cancel_returns_error() { + let p = processor(); + let cancel = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_CANCEL, 0)]; + let err = p.process_hints(&cancel).err().unwrap(); + assert!(err.to_string().contains("cancelled")); + } + + #[test] + fn test_stream_error_signal_returns_error() { + let p = processor(); + let signal_err = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_ERROR, 0)]; + let err = p.process_hints(&signal_err).err().unwrap(); + assert!(err.to_string().contains("error")); + } + + // Stress test + #[test] + fn test_stress_throughput() { + use std::time::Instant; + + let p = PrecompileHintsProcessor::with_num_threads(32).unwrap(); + + // Generate a large batch of hints + const NUM_HINTS: usize = 100_000; + let mut data = Vec::with_capacity(NUM_HINTS * 2); + + for i in 0..NUM_HINTS { + data.push(make_header(HINTS_TYPE_RESULT, 1)); + data.push(i as u64); + } + + let start = Instant::now(); + p.process_hints(&data).unwrap(); + p.wait_for_completion().unwrap(); + let duration = start.elapsed(); + + let ops_per_sec = NUM_HINTS as f64 / duration.as_secs_f64(); + println!("\n========================================"); + println!("Stress Test Results:"); + println!(" Total hints: {}", NUM_HINTS); + println!(" Duration: {:.3}s", duration.as_secs_f64()); + println!(" Throughput: {:.0} ops/sec", ops_per_sec); + println!(" Avg latency: {:.2}µs per hint", duration.as_micros() as f64 / NUM_HINTS as f64); + println!("========================================\n"); + + // Sanity check: should be able to process at least 10k ops/sec + assert!(ops_per_sec > 10_000.0, "Throughput too low: {:.0} ops/sec", ops_per_sec); + } + + #[test] + fn test_stress_concurrent_batches() { + use std::time::Instant; + + let p = PrecompileHintsProcessor::with_num_threads(32).unwrap(); + + const NUM_BATCHES: usize = 1_000; + const HINTS_PER_BATCH: usize = 100; + + let start = Instant::now(); + + // Call process_hints multiple times with small batches + for batch_id in 0..NUM_BATCHES { + let mut data = Vec::with_capacity(HINTS_PER_BATCH * 2); + for i in 0..HINTS_PER_BATCH { + data.push(make_header(HINTS_TYPE_RESULT, 1)); + data.push((batch_id * HINTS_PER_BATCH + i) as u64); + } + p.process_hints(&data).unwrap(); + } + + p.wait_for_completion().unwrap(); + let duration = start.elapsed(); + + let total_hints = NUM_BATCHES * HINTS_PER_BATCH; + let ops_per_sec = total_hints as f64 / duration.as_secs_f64(); + + println!("\n========================================"); + println!("Multiple Batches Stress Test:"); + println!(" Number of batches: {}", NUM_BATCHES); + println!(" Hints per batch: {}", HINTS_PER_BATCH); + println!(" Total hints: {}", total_hints); + println!(" Duration: {:.3}s", duration.as_secs_f64()); + println!(" Throughput: {:.0} ops/sec", ops_per_sec); + println!("========================================\n"); + + assert!(ops_per_sec > 10_000.0, "Throughput too low: {:.0} ops/sec", ops_per_sec); + } + + #[test] + fn test_stress_with_resets() { + use std::time::Instant; + + let p = PrecompileHintsProcessor::with_num_threads(32).unwrap(); + + const ITERATIONS: usize = 100; + const HINTS_PER_ITER: usize = 1_000; + + let start = Instant::now(); + + for _iter in 0..ITERATIONS { + // Reset at start of each iteration + let reset = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_START, 0)]; + p.process_hints(&reset).unwrap(); + + // Process batch + let mut data = Vec::with_capacity(HINTS_PER_ITER * 2); + for i in 0..HINTS_PER_ITER { + data.push(make_header(HINTS_TYPE_RESULT, 1)); + data.push(i as u64); + } + p.process_hints(&data).unwrap(); + + // End stream + let end = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_END, 0)]; + p.process_hints(&end).unwrap(); + } + + let duration = start.elapsed(); + let total_hints = ITERATIONS * HINTS_PER_ITER; + let ops_per_sec = total_hints as f64 / duration.as_secs_f64(); + + println!("\n========================================"); + println!("Reset Stress Test:"); + println!(" Iterations: {}", ITERATIONS); + println!(" Hints per iteration: {}", HINTS_PER_ITER); + println!(" Total hints: {}", total_hints); + println!(" Duration: {:.3}s", duration.as_secs_f64()); + println!(" Throughput: {:.0} ops/sec", ops_per_sec); + println!("========================================\n"); + + assert!( + ops_per_sec > 5_000.0, + "Throughput too low with resets: {:.0} ops/sec", + ops_per_sec + ); + } +} From 826104d6489d4ac61173700f1493006dfa5fce27 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 2 Dec 2025 09:44:26 +0000 Subject: [PATCH 018/782] improve doc and naming --- precompiles/common/src/precompiles_hints.rs | 243 ++++++++++---------- 1 file changed, 116 insertions(+), 127 deletions(-) diff --git a/precompiles/common/src/precompiles_hints.rs b/precompiles/common/src/precompiles_hints.rs index 5c47f18f0..f8dc76708 100644 --- a/precompiles/common/src/precompiles_hints.rs +++ b/precompiles/common/src/precompiles_hints.rs @@ -24,22 +24,22 @@ //! ├────────────────────────────────────────────────────────────────┤ //! │ Data[length-1] (u64) │ //! └────────────────────────────────────────────────────────────────┘ -//! +//! //! - Hint Code — Control code or Data Hint Type //! - Length — Number of following u64 data words //! //! ## Hint Type Layout //! //! ### Control codes -//! +//! //! The following control codes are defined: //! - `0x00` (START): Reset processor state and global sequence. //! - `0x01` (END): Wait until completion of all pending hints. -//! - `0x02` (CANCEL): Cancel current stream and stop processing further hints. +//! - `0x02` (CANCEL): Cancel current stream and stop processing further hints. //! - `0x03` (ERROR): Indicate an error has occurred; stop processing further hints. -//! +//! //! Control codes are for control only and do not have any associated data (Length should be zero). -//! +//! //! ### Data Hint Types: //! - `0x04` (`HINTS_TYPE_RESULT`): Pass-through data //! - `0x05` (`HINTS_TYPE_ECRECOVER`): ECRECOVER inputs (currently returns empty) @@ -51,26 +51,28 @@ use std::collections::VecDeque; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; +// TODO! COnvert Control Code to an enum and HINT TYPE to an enum as well + +/// Control code: Reset processor state and global sequence. +const CTRL_START: u32 = 0x00; + +/// Control code: Wait until completion of all pending hints. +const CTRL_END: u32 = 0x01; + +/// Control code: Cancel current stream and stop processing. +const CTRL_CANCEL: u32 = 0x02; + +/// Control code: Signal error and stop processing. +const CTRL_ERROR: u32 = 0x03; + /// Hint type indicating that the data is already the precomputed result. /// /// When a hint has this type, the processor simply passes through the data /// without any additional computation. -pub const HINTS_TYPE_RESULT: u32 = 1; +pub const HINTS_TYPE_RESULT: u32 = 0x04; /// Hint type indicating that the data contains inputs for the ecrecover precompile. -pub const HINTS_TYPE_ECRECOVER: u32 = 2; - -/// Stream control is encoded in the high byte (bits 31..24) of `hint_type`. -/// Base type is the lower 24 bits (bits 23..0). -const STREAM_CTRL_MASK: u32 = 0xFF00_0000; -const STREAM_BASE_MASK: u32 = 0x00FF_FFFF; -const STREAM_CTRL_SHIFT: u32 = 24; - -const STREAM_CTRL_NONE: u32 = 0x00; -const STREAM_CTRL_START: u32 = 0x01; // reset stream state -const STREAM_CTRL_END: u32 = 0x02; // wait until completion -const STREAM_CTRL_CANCEL: u32 = 0x03; // cancel processing -const STREAM_CTRL_ERROR: u32 = 0x04; // signal error +pub const HINTS_TYPE_ECRECOVER: u32 = 0x05; /// Represents a single precompile hint parsed from a `u64` slice. /// @@ -107,7 +109,7 @@ impl PrecompileHint { #[inline(always)] fn from_u64_slice(slice: &[u64], idx: usize) -> Result { if slice.is_empty() || idx >= slice.len() { - return Err(anyhow::anyhow!("Slice too short to contain a hint")); + return Err(anyhow::anyhow!("Slice too short or index out of bounds")); } let header = slice[idx]; @@ -122,45 +124,46 @@ impl PrecompileHint { )); } + // TODO! This creates a new Vec to own the data. Sine performance is critical, + // TODO! consider using a slice reference instead. let data = slice[idx + 1..idx + length as usize + 1].to_vec(); Ok(PrecompileHint { hint_type, data }) } } -/// Shared state for the reorder buffer used by `process_hints_2`. +/// Ordered result buffer with drain state. /// -/// This structure maintains a global sequence counter and a VecDeque that -/// holds processed results in order, allowing out-of-order completion while -/// ensuring in-order output. -struct ReorderBuffer { - /// The reorder buffer: None = pending, Some(Ok(...)) = ready, Some(Err(...)) = error +/// This structure maintains a VecDeque that holds processed results in order, +/// allowing out-of-order completion while ensuring in-order output. +struct ResultQueue { + /// The result buffer: None = pending, Some(Ok(...)) = ready, Some(Err(...)) = error buffer: VecDeque>>>, - /// Sequence ID of buffer[0] (the next result to drain/print) - base_seq: usize, + /// Sequence ID of the next result to drain from buffer[0] + next_drain_seq: usize, } -/// Shared state across multiple calls to `process_hints_2`. -struct SharedState { - /// The reorder buffer protected by a mutex - reorder: Mutex, - /// Condvar to signal when buffer becomes empty or error occurs - buffer_empty: Condvar, - /// Global sequence counter for assigning seq_ids to hints +/// Thread-safe shared state for parallel hint processing. +struct HintProcessorState { + /// Ordered results ready for draining + queue: Mutex, + /// Notifies when queue becomes empty or error occurs + drain_signal: Condvar, + /// Next sequence ID to assign to incoming hints next_seq: AtomicUsize, - /// Flag to signal that an error occurred and processing should stop - has_error: AtomicBool, - /// Generation counter to detect stale workers after reset + /// Signals processing should stop + error_flag: AtomicBool, + /// Invalidates stale workers after reset generation: AtomicUsize, } -impl SharedState { +impl HintProcessorState { fn new() -> Self { Self { - reorder: Mutex::new(ReorderBuffer { buffer: VecDeque::new(), base_seq: 0 }), - buffer_empty: Condvar::new(), + queue: Mutex::new(ResultQueue { buffer: VecDeque::new(), next_drain_seq: 0 }), + drain_signal: Condvar::new(), next_seq: AtomicUsize::new(0), - has_error: AtomicBool::new(false), + error_flag: AtomicBool::new(false), generation: AtomicUsize::new(0), } } @@ -174,12 +177,12 @@ impl SharedState { pub struct PrecompileHintsProcessor { /// The thread pool used for parallel hint processing. pool: ThreadPool, - /// Shared state for the reorder buffer (used by process_hints_2) - shared: Arc, + /// Shared state for parallel hint processing + state: Arc, } impl PrecompileHintsProcessor { - const NUM_THREADS: usize = 32; + const DEFAULT_NUM_THREADS: usize = 32; /// Creates a new processor with the default number of threads. /// @@ -190,7 +193,7 @@ impl PrecompileHintsProcessor { /// * `Ok(PrecompileHintsProcessor)` - The configured processor /// * `Err` - If the thread pool fails to initialize pub fn new() -> Result { - Self::with_num_threads(Self::NUM_THREADS) + Self::with_num_threads(Self::DEFAULT_NUM_THREADS) } /// Creates a new processor with the specified number of threads. @@ -209,19 +212,19 @@ impl PrecompileHintsProcessor { .build() .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; - Ok(Self { pool, shared: Arc::new(SharedState::new()) }) + Ok(Self { pool, state: Arc::new(HintProcessorState::new()) }) } /// Processes hints in parallel with non-blocking, ordered output. /// /// This method dispatches each hint to the thread pool for parallel processing. - /// Results are collected in a reorder buffer and drained (printed) in the original + /// Results are collected in a reorder buffer and drained (printed!!!!!!!!!!!!!!!!!!!!!) in the original /// order as soon as consecutive results become available. /// /// # Key characteristics: /// - **Non-blocking**: Returns immediately after dispatching work to the pool /// - **Global sequence**: Sequence IDs are maintained across multiple calls - /// - **Ordered output**: Results are printed in the order hints were received + /// - **Ordered output**: Results are printed!!!!!!!!!!!!!!!!!!!! in the order hints were received /// - **Error handling**: Stops processing on first error /// /// # Arguments @@ -233,134 +236,121 @@ impl PrecompileHintsProcessor { /// * `Ok(())` - Hints were successfully dispatched (does not mean processing is complete) /// * `Err` - If a previous error occurred or hints are malformed pub fn process_hints(&self, hints: &[u64]) -> Result<()> { - // Check if a previous error occurred - if self.shared.has_error.load(Ordering::Acquire) { - return Err(anyhow::anyhow!("Processing stopped due to previous error")); - } - // Parse hints and dispatch to pool let mut idx = 0; while idx < hints.len() { // Check for error before processing each hint - if self.shared.has_error.load(Ordering::Acquire) { + if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to previous error")); } let hint = PrecompileHint::from_u64_slice(hints, idx)?; let length = hint.data.len(); - // Decode stream control from high byte - let ctrl = (hint.hint_type & STREAM_CTRL_MASK) >> STREAM_CTRL_SHIFT; - let base_type = hint.hint_type & STREAM_BASE_MASK; - - // Apply stream control actions - match ctrl { - STREAM_CTRL_START => { + // Check if this is a control code or data hint type + match hint.hint_type { + CTRL_START => { // Reset global sequence and buffer at stream start self.reset(); // Control hint only; skip processing idx += length + 1; continue; } - STREAM_CTRL_CANCEL => { + CTRL_END => { + // Control hint only; wait for completion then skip processing + self.wait_for_completion()?; + idx += length + 1; + continue; + } + CTRL_CANCEL => { // Cancel current stream: set error and notify - self.shared.has_error.store(true, Ordering::Release); - self.shared.buffer_empty.notify_all(); + self.state.error_flag.store(true, Ordering::Release); + self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream cancelled")); } - STREAM_CTRL_ERROR => { + CTRL_ERROR => { // External error signal - self.shared.has_error.store(true, Ordering::Release); - self.shared.buffer_empty.notify_all(); + self.state.error_flag.store(true, Ordering::Release); + self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream error signalled")); } - STREAM_CTRL_END => { - // Control hint only; wait for completion then skip processing - self.wait_for_completion()?; - idx += length + 1; - continue; + _ => { + // Data hint type - process normally } - _ => {} } // Atomically reserve slot and capture generation inside mutex // This prevents orphaned slots if reset happens between generation load and push_back let (generation, seq_id) = { - let mut reorder = self.shared.reorder.lock().unwrap(); - let gen = self.shared.generation.load(Ordering::SeqCst); - let seq = self.shared.next_seq.fetch_add(1, Ordering::SeqCst); - reorder.buffer.push_back(None); + let mut queue = self.state.queue.lock().unwrap(); + let gen = self.state.generation.load(Ordering::SeqCst); + let seq = self.state.next_seq.fetch_add(1, Ordering::SeqCst); + queue.buffer.push_back(None); (gen, seq) }; // Spawn processing task - let shared = Arc::clone(&self.shared); + let state = Arc::clone(&self.state); self.pool.spawn(move || { + // TODO! Is it necessary? TO increase performance maybe is enough to check error_flag only when storing result // Check if we should stop due to error - if shared.has_error.load(Ordering::Acquire) { + if state.error_flag.load(Ordering::Acquire) { return; } // Process the hint - // Override hint type to base type for processing - let mut hint_for_proc = hint; - hint_for_proc.hint_type = base_type; - let result = Self::process_hint(&hint_for_proc); + let result = Self::process_hint(&hint); // Store result and try to drain - let mut reorder = shared.reorder.lock().unwrap(); + let mut queue = state.queue.lock().unwrap(); // Check generation first to detect stale workers from previous sessions - let current_gen = shared.generation.load(Ordering::SeqCst); + let current_gen = state.generation.load(Ordering::SeqCst); if generation != current_gen { // Worker belongs to old generation; ignore result return; } - // Calculate offset in buffer; handle resets and drained slots - if seq_id < reorder.base_seq { + // Calculate offset in buffer; handle drained slots + if seq_id < queue.next_drain_seq { // This result belongs to a previous stream/session; ignore return; } - let offset = seq_id - reorder.base_seq; - if offset >= reorder.buffer.len() { - // Buffer no longer has a slot for this seq (likely after reset); ignore - return; - } + let offset = seq_id - queue.next_drain_seq; // Check error flag again before storing to avoid processing after error - if shared.has_error.load(Ordering::Acquire) { + if state.error_flag.load(Ordering::Acquire) { return; } - reorder.buffer[offset] = Some(result); + queue.buffer[offset] = Some(result); // Drain consecutive ready results from the front - while let Some(Some(res)) = reorder.buffer.front() { + while let Some(Some(res)) = queue.buffer.front() { match res { Ok(_data) => { // Print the result (will be replaced with send to another process) - // println!("[seq={}] Result: {:?}", reorder.base_seq, data); - reorder.buffer.pop_front(); - reorder.base_seq += 1; + // println!("[seq={}] Result: {:?}", queue.next_drain_seq, data); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; } Err(_) => { // Error found - signal to stop and break - shared.has_error.store(true, Ordering::Release); + state.error_flag.store(true, Ordering::Release); // Print error and stop draining - if let Some(Some(Err(e))) = reorder.buffer.pop_front() { - eprintln!("[seq={}] Error: {}", reorder.base_seq, e); + if let Some(Some(Err(e))) = queue.buffer.pop_front() { + eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); } - reorder.base_seq += 1; - shared.buffer_empty.notify_all(); + queue.next_drain_seq += 1; + state.drain_signal.notify_all(); break; } } } // Notify if buffer is now empty - if reorder.buffer.is_empty() { - shared.buffer_empty.notify_all(); + if queue.buffer.is_empty() { + state.drain_signal.notify_all(); } }); @@ -380,17 +370,17 @@ impl PrecompileHintsProcessor { /// * `Ok(())` - All hints processed successfully /// * `Err` - If an error occurred during processing fn wait_for_completion(&self) -> Result<()> { - let mut reorder = self.shared.reorder.lock().unwrap(); + let mut queue = self.state.queue.lock().unwrap(); - while !reorder.buffer.is_empty() { - if self.shared.has_error.load(Ordering::Acquire) { + while !queue.buffer.is_empty() { + if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to error")); } // Wait for notification that buffer state changed - reorder = self.shared.buffer_empty.wait(reorder).unwrap(); + queue = self.state.drain_signal.wait(queue).unwrap(); } - if self.shared.has_error.load(Ordering::Acquire) { + if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to error")); } @@ -405,13 +395,13 @@ impl PrecompileHintsProcessor { /// Increments the generation counter to invalidate any in-flight workers /// from the previous session, preventing them from corrupting the new state. fn reset(&self) { - self.shared.has_error.store(false, Ordering::Release); - self.shared.next_seq.store(0, Ordering::Release); + self.state.error_flag.store(false, Ordering::Release); + self.state.next_seq.store(0, Ordering::Release); // Increment generation to invalidate stale workers - self.shared.generation.fetch_add(1, Ordering::SeqCst); - let mut reorder = self.shared.reorder.lock().unwrap(); - reorder.buffer.clear(); - reorder.base_seq = 0; + self.state.generation.fetch_add(1, Ordering::SeqCst); + let mut queue = self.state.queue.lock().unwrap(); + queue.buffer.clear(); + queue.next_drain_seq = 0; } /// Dispatches a single hint to its appropriate handler based on hint type. @@ -470,9 +460,8 @@ mod tests { ((hint_type as u64) << 32) | (length as u64) } - fn make_header_with_ctrl(base_type: u32, ctrl: u32, length: u32) -> u64 { - let hint_type = ((ctrl & 0xFF) << STREAM_CTRL_SHIFT) | (base_type & STREAM_BASE_MASK); - make_header(hint_type, length) + fn make_ctrl_header(ctrl: u32, length: u32) -> u64 { + make_header(ctrl, length) } fn processor() -> PrecompileHintsProcessor { @@ -570,13 +559,13 @@ mod tests { p.process_hints(&batch1).unwrap(); // Send START control; then a new hint - let start = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_START, 0)]; + let start = vec![make_ctrl_header(CTRL_START, 0)]; p.process_hints(&start).unwrap(); let batch2 = vec![make_header(HINTS_TYPE_RESULT, 1), 0x02]; p.process_hints(&batch2).unwrap(); // End the stream to ensure completion - let end = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_END, 0)]; + let end = vec![make_ctrl_header(CTRL_END, 0)]; p.process_hints(&end).unwrap(); p.wait_for_completion().unwrap(); } @@ -589,7 +578,7 @@ mod tests { vec![make_header(HINTS_TYPE_RESULT, 1), 0x10, make_header(HINTS_TYPE_RESULT, 1), 0x20]; p.process_hints(&data).unwrap(); // End control should cause internal wait during processing - let end = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_END, 0)]; + let end = vec![make_ctrl_header(CTRL_END, 0)]; p.process_hints(&end).unwrap(); // Subsequent explicit wait should be fast (already drained) p.wait_for_completion().unwrap(); @@ -598,7 +587,7 @@ mod tests { #[test] fn test_stream_cancel_returns_error() { let p = processor(); - let cancel = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_CANCEL, 0)]; + let cancel = vec![make_ctrl_header(CTRL_CANCEL, 0)]; let err = p.process_hints(&cancel).err().unwrap(); assert!(err.to_string().contains("cancelled")); } @@ -606,7 +595,7 @@ mod tests { #[test] fn test_stream_error_signal_returns_error() { let p = processor(); - let signal_err = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_ERROR, 0)]; + let signal_err = vec![make_ctrl_header(CTRL_ERROR, 0)]; let err = p.process_hints(&signal_err).err().unwrap(); assert!(err.to_string().contains("error")); } @@ -697,7 +686,7 @@ mod tests { for _iter in 0..ITERATIONS { // Reset at start of each iteration - let reset = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_START, 0)]; + let reset = vec![make_ctrl_header(CTRL_START, 0)]; p.process_hints(&reset).unwrap(); // Process batch @@ -709,7 +698,7 @@ mod tests { p.process_hints(&data).unwrap(); // End stream - let end = vec![make_header_with_ctrl(HINTS_TYPE_RESULT, STREAM_CTRL_END, 0)]; + let end = vec![make_ctrl_header(CTRL_END, 0)]; p.process_hints(&end).unwrap(); } From f0060ffcb129d0a3d9a95a21455e5c4d24b17090 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 2 Dec 2025 09:44:53 +0000 Subject: [PATCH 019/782] improve tests --- precompiles/common/src/precompiles_hints.rs | 144 +++++++++++++++----- 1 file changed, 111 insertions(+), 33 deletions(-) diff --git a/precompiles/common/src/precompiles_hints.rs b/precompiles/common/src/precompiles_hints.rs index f8dc76708..fefa114ff 100644 --- a/precompiles/common/src/precompiles_hints.rs +++ b/precompiles/common/src/precompiles_hints.rs @@ -475,9 +475,14 @@ mod tests { let data = vec![make_header(HINTS_TYPE_RESULT, 2), 0x111, 0x222]; // Dispatch should succeed and be non-blocking - p.process_hints(&data).unwrap(); - // Wait for completion - p.wait_for_completion().unwrap(); + assert!(p.process_hints(&data).is_ok()); + // Wait for completion should succeed + assert!(p.wait_for_completion().is_ok()); + + // Buffer should be empty after completion + let queue = p.state.queue.lock().unwrap(); + assert!(queue.buffer.is_empty()); + assert_eq!(queue.next_drain_seq, 1); } #[test] @@ -491,8 +496,13 @@ mod tests { make_header(HINTS_TYPE_RESULT, 1), 0x333, ]; - p.process_hints(&data).unwrap(); - p.wait_for_completion().unwrap(); + assert!(p.process_hints(&data).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Verify all hints were processed (buffer empty, next_drain_seq advanced) + let queue = p.state.queue.lock().unwrap(); + assert!(queue.buffer.is_empty()); + assert_eq!(queue.next_drain_seq, 3); } #[test] @@ -500,17 +510,27 @@ mod tests { let p = processor(); let data1 = vec![make_header(HINTS_TYPE_RESULT, 1), 0xAAA]; let data2 = vec![make_header(HINTS_TYPE_RESULT, 1), 0xBBB]; - p.process_hints(&data1).unwrap(); - p.process_hints(&data2).unwrap(); - p.wait_for_completion().unwrap(); + + assert!(p.process_hints(&data1).is_ok()); + assert!(p.process_hints(&data2).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Verify sequence continued across calls + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 2); + assert!(queue.buffer.is_empty()); } #[test] fn test_empty_input_ok() { let p = processor(); let data: Vec = vec![]; - p.process_hints(&data).unwrap(); - p.wait_for_completion().unwrap(); + assert!(p.process_hints(&data).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // No hints processed + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); } // Negative tests @@ -518,10 +538,17 @@ mod tests { fn test_unknown_hint_type_returns_error() { let p = processor(); let data = vec![make_header(999, 1), 0x1234]; - // Dispatch enqueues work; error surfaces on wait - p.process_hints(&data).unwrap(); - let err = p.wait_for_completion().err().unwrap(); - assert!(err.to_string().contains("error")); + + // Dispatch enqueues work + assert!(p.process_hints(&data).is_ok()); + + // Error surfaces on wait + let result = p.wait_for_completion(); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("error")); + + // Error flag should be set + assert!(p.state.error_flag.load(Ordering::Acquire)); } #[test] @@ -529,11 +556,12 @@ mod tests { let p = processor(); // First valid, then invalid type let data = vec![make_header(HINTS_TYPE_RESULT, 1), 0x111, make_header(999, 0)]; - // Dispatch returns error at parse/process of bad hint + let _ = p.process_hints(&data); - // Wait should report error state - let w = p.wait_for_completion(); - assert!(w.is_err()); + let result = p.wait_for_completion(); + + assert!(result.is_err()); + assert!(p.state.error_flag.load(Ordering::Acquire)); } #[test] @@ -541,63 +569,113 @@ mod tests { let p = processor(); let bad = vec![make_header(999, 0)]; let _ = p.process_hints(&bad); - // Give workers a moment (no busy wait; optional) - std::thread::sleep(std::time::Duration::from_millis(5)); + + // Wait briefly for error to propagate + std::thread::sleep(std::time::Duration::from_millis(10)); + + // Error should be set + assert!(p.state.error_flag.load(Ordering::Acquire)); + + // Reset should clear error p.reset(); + assert!(!p.state.error_flag.load(Ordering::Acquire)); + // Should be able to process new hints let good = vec![make_header(HINTS_TYPE_RESULT, 1), 0x42]; - p.process_hints(&good).unwrap(); - p.wait_for_completion().unwrap(); + assert!(p.process_hints(&good).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); } // Stream control tests #[test] fn test_stream_start_resets_state() { let p = processor(); + // First batch increments sequence let batch1 = vec![make_header(HINTS_TYPE_RESULT, 1), 0x01]; p.process_hints(&batch1).unwrap(); + p.wait_for_completion().unwrap(); + + // Sequence should be at 1 + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + } - // Send START control; then a new hint + // Send START control - should reset sequence let start = vec![make_ctrl_header(CTRL_START, 0)]; p.process_hints(&start).unwrap(); + // Sequence should be reset to 0 + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + assert!(queue.buffer.is_empty()); + } + + // Process new batch let batch2 = vec![make_header(HINTS_TYPE_RESULT, 1), 0x02]; p.process_hints(&batch2).unwrap(); - // End the stream to ensure completion + let end = vec![make_ctrl_header(CTRL_END, 0)]; p.process_hints(&end).unwrap(); - p.wait_for_completion().unwrap(); + + // Should have processed 1 hint (starting from 0 again) + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); } #[test] fn test_stream_end_waits_until_completion() { let p = processor(); - // Dispatch a few hints + + // Dispatch hints let data = vec![make_header(HINTS_TYPE_RESULT, 1), 0x10, make_header(HINTS_TYPE_RESULT, 1), 0x20]; p.process_hints(&data).unwrap(); - // End control should cause internal wait during processing + + // END should wait internally let end = vec![make_ctrl_header(CTRL_END, 0)]; p.process_hints(&end).unwrap(); - // Subsequent explicit wait should be fast (already drained) - p.wait_for_completion().unwrap(); + + // Buffer should already be empty + { + let queue = p.state.queue.lock().unwrap(); + assert!(queue.buffer.is_empty()); + assert_eq!(queue.next_drain_seq, 2); + } + + // Explicit wait should be instant + assert!(p.wait_for_completion().is_ok()); } #[test] fn test_stream_cancel_returns_error() { let p = processor(); let cancel = vec![make_ctrl_header(CTRL_CANCEL, 0)]; - let err = p.process_hints(&cancel).err().unwrap(); - assert!(err.to_string().contains("cancelled")); + + let result = p.process_hints(&cancel); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("cancelled")); + + // Error flag should be set + assert!(p.state.error_flag.load(Ordering::Acquire)); } #[test] fn test_stream_error_signal_returns_error() { let p = processor(); let signal_err = vec![make_ctrl_header(CTRL_ERROR, 0)]; - let err = p.process_hints(&signal_err).err().unwrap(); - assert!(err.to_string().contains("error")); + + let result = p.process_hints(&signal_err); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("error")); + + // Error flag should be set + assert!(p.state.error_flag.load(Ordering::Acquire)); } // Stress test From 9ccec79a6f5706f3fe290b716bc2a50c0b0ffb49 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 3 Dec 2025 17:46:43 +0000 Subject: [PATCH 020/782] added precompile_hints_path as CLI execute parameter --- Cargo.lock | 2 + cli/src/commands/execute.rs | 60 ++++++++++++++++++--- cli/src/ux.rs | 4 ++ common/src/io/file_hintin.rs | 52 ++++++++++++++++++ common/src/io/mod.rs | 4 ++ common/src/io/null_hintin.rs | 19 +++++++ common/src/io/zisk_hintin.rs | 84 +++++++++++++++++++++++++++++ common/src/zisk_lib_init.rs | 6 ++- executor/src/executor.rs | 9 +++- sdk/src/prover/asm.rs | 11 ++-- sdk/src/prover/backend.rs | 9 +++- sdk/src/prover/emu.rs | 14 +++-- sdk/src/prover/mod.rs | 20 +++++-- witness-computation/src/zisk_lib.rs | 6 +++ 14 files changed, 280 insertions(+), 20 deletions(-) create mode 100644 common/src/io/file_hintin.rs create mode 100644 common/src/io/null_hintin.rs create mode 100644 common/src/io/zisk_hintin.rs diff --git a/Cargo.lock b/Cargo.lock index 49264c8ef..4f95233ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,6 +300,7 @@ dependencies = [ "mem-common", "mem-planner-cpp", "named-sem", + "precompiles-common", "rayon", "thiserror 2.0.17", "tracing", @@ -3005,6 +3006,7 @@ dependencies = [ "anyhow", "fields", "rayon", + "tracing", "zisk-common", "zisk-core", ] diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index c95f9d19d..5d6d41058 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -5,8 +5,11 @@ use tracing::info; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_sdk::{ProverClient, ZiskExecuteResult}; -use crate::{commands::cli_fail_if_gpu_mode, ux::print_banner}; -use zisk_common::io::ZiskStdin; +use crate::{ + commands::cli_fail_if_gpu_mode, + ux::{print_banner, print_banner_field}, +}; +use zisk_common::io::{ZiskHintin, ZiskStdin}; #[derive(Parser)] #[command(author, about, long_about = None, version = ZISK_VERSION_MESSAGE)] @@ -41,6 +44,10 @@ pub struct ZiskExecute { #[clap(short = 'i', long)] pub input: Option, + /// Precompiles Hints path + #[clap(short = 'h', long)] + pub precompile_hints_path: Option, + /// Setup folder path #[clap(short = 'k', long)] pub proving_key: Option, @@ -75,10 +82,26 @@ impl ZiskExecute { print_banner(); + if self.input.is_some() { + print_banner_field("Input", &self.input.as_ref().unwrap().to_string_lossy()); + } + + if self.precompile_hints_path.is_some() { + print_banner_field( + "Prec. Hints", + &self.precompile_hints_path.as_ref().unwrap().to_string_lossy(), + ); + } + let stdin = self.create_stdin()?; + let hintin = self.create_hintin()?; let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; - let result = if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin)? }; + let result = if emulator { + self.run_emu(stdin, Some(hintin))? + } else { + self.run_asm(stdin, Some(hintin))? + }; info!( "Execution completed in {:.2?}, executed steps: {}", @@ -100,7 +123,26 @@ impl ZiskExecute { Ok(stdin) } - pub fn run_emu(&mut self, stdin: ZiskStdin) -> Result { + fn create_hintin(&mut self) -> Result { + let hintin = if let Some(hints_path) = &self.precompile_hints_path { + if !hints_path.exists() { + return Err(anyhow::anyhow!( + "Precompile Hints file not found at {:?}", + hints_path.display() + )); + } + ZiskHintin::from_file(hints_path)? + } else { + ZiskHintin::null() + }; + Ok(hintin) + } + + pub fn run_emu( + &mut self, + stdin: ZiskStdin, + hintin: Option, + ) -> Result { let prover = ProverClient::builder() .emu() .witness() @@ -112,10 +154,14 @@ impl ZiskExecute { .print_command_info() .build()?; - prover.execute(stdin) + prover.execute(stdin, hintin) } - pub fn run_asm(&mut self, stdin: ZiskStdin) -> Result { + pub fn run_asm( + &mut self, + stdin: ZiskStdin, + hintin: Option, + ) -> Result { let prover = ProverClient::builder() .asm() .verify_constraints() @@ -130,6 +176,6 @@ impl ZiskExecute { .print_command_info() .build()?; - prover.execute(stdin) + prover.execute(stdin, hintin) } } diff --git a/cli/src/ux.rs b/cli/src/ux.rs index 543ab3c8d..0e5af437e 100644 --- a/cli/src/ux.rs +++ b/cli/src/ux.rs @@ -49,3 +49,7 @@ pub fn print_banner() { // available_mem // ); } + +pub fn print_banner_field(label: &str, value: &str) { + println!("{} {}", format!("{: >12}", label).bright_green().bold(), value); +} diff --git a/common/src/io/file_hintin.rs b/common/src/io/file_hintin.rs new file mode 100644 index 000000000..fc8c5e081 --- /dev/null +++ b/common/src/io/file_hintin.rs @@ -0,0 +1,52 @@ +//! A file-based implementation of ZiskHintin. +//! This module provides functionality to read input data from a file. + +use std::fs::{self, File}; +use std::io::{BufReader, Read}; +use std::path::{Path, PathBuf}; + +use crate::io::ZiskIO; + +/// A file-based implementation of ZiskStdin that reads from a file. +pub struct ZiskFileHintin { + /// The path to the input file. + path: PathBuf, + + /// Buffered reader for the file. + reader: BufReader, +} + +impl ZiskFileHintin { + /// Create a new FileHintin from a file path. + pub fn new>(path: P) -> std::io::Result { + let path_buf = path.as_ref().to_path_buf(); + let file = File::open(&path_buf)?; + Ok(ZiskFileHintin { path: path_buf, reader: BufReader::new(file) }) + } +} + +impl ZiskIO for ZiskFileHintin { + fn read(&mut self) -> Vec { + fs::read(&self.path).expect("Could not read inputs file") + } + + fn read_slice(&mut self, slice: &mut [u8]) { + self.reader.read_exact(slice).expect("Failed to read slice"); + } + + fn read_into(&mut self, buffer: &mut [u8]) { + self.reader.read_exact(buffer).expect("Failed to read into buffer"); + } + + fn write_serialized(&mut self, _data: &[u8]) { + // This is a read-only stdin implementation + // Writing is not supported for file-based stdin + panic!("Write operations are not supported for FileStdin"); + } + + fn write_bytes(&mut self, _data: &[u8]) { + // This is a read-only stdin implementation + // Writing is not supported for file-based stdin + panic!("Write operations are not supported for FileStdin"); + } +} diff --git a/common/src/io/mod.rs b/common/src/io/mod.rs index 06d95dd81..2be3722a0 100644 --- a/common/src/io/mod.rs +++ b/common/src/io/mod.rs @@ -1,9 +1,13 @@ +mod file_hintin; mod file_stdin; mod memory_stdin; +mod null_hintin; mod null_stdin; +mod zisk_hintin; mod zisk_stdin; pub use file_stdin::*; pub use memory_stdin::*; pub use null_stdin::*; +pub use zisk_hintin::*; pub use zisk_stdin::*; diff --git a/common/src/io/null_hintin.rs b/common/src/io/null_hintin.rs new file mode 100644 index 000000000..3cf1ef2a8 --- /dev/null +++ b/common/src/io/null_hintin.rs @@ -0,0 +1,19 @@ +use tracing::warn; + +use crate::io::ZiskIO; + +pub struct ZiskNullHintin; + +impl ZiskIO for ZiskNullHintin { + fn read(&mut self) -> Vec { + Vec::new() + } + fn read_slice(&mut self, _slice: &mut [u8]) {} + fn read_into(&mut self, _buffer: &mut [u8]) {} + fn write_serialized(&mut self, _data: &[u8]) { + warn!("NullHintin does not support writing"); + } + fn write_bytes(&mut self, _data: &[u8]) { + warn!("NullHintin does not support writing"); + } +} diff --git a/common/src/io/zisk_hintin.rs b/common/src/io/zisk_hintin.rs new file mode 100644 index 000000000..6f1281aa5 --- /dev/null +++ b/common/src/io/zisk_hintin.rs @@ -0,0 +1,84 @@ +use std::path::Path; + +use crate::io::{file_hintin::ZiskFileHintin, null_hintin::ZiskNullHintin, ZiskIO}; +use anyhow::Result; + +pub enum ZiskHintIOVariant { + File(ZiskFileHintin), + Null(ZiskNullHintin), +} + +impl ZiskIO for ZiskHintIOVariant { + fn read(&mut self) -> Vec { + match self { + ZiskHintIOVariant::File(file_hintin) => file_hintin.read(), + ZiskHintIOVariant::Null(null_hintin) => null_hintin.read(), + } + } + + fn read_slice(&mut self, slice: &mut [u8]) { + match self { + ZiskHintIOVariant::File(file_hintin) => file_hintin.read_slice(slice), + ZiskHintIOVariant::Null(null_hintin) => null_hintin.read_slice(slice), + } + } + + fn read_into(&mut self, buffer: &mut [u8]) { + match self { + ZiskHintIOVariant::File(file_hintin) => file_hintin.read_into(buffer), + ZiskHintIOVariant::Null(null_hintin) => null_hintin.read_into(buffer), + } + } + + fn write_serialized(&mut self, data: &[u8]) { + match self { + ZiskHintIOVariant::File(file_hintin) => file_hintin.write_serialized(data), + ZiskHintIOVariant::Null(null_hintin) => null_hintin.write_serialized(data), + } + } + + fn write_bytes(&mut self, data: &[u8]) { + match self { + ZiskHintIOVariant::File(file_hintin) => file_hintin.write_bytes(data), + ZiskHintIOVariant::Null(null_hintin) => null_hintin.write_bytes(data), + } + } +} + +pub struct ZiskHintin { + io: ZiskHintIOVariant, +} + +impl ZiskIO for ZiskHintin { + fn read(&mut self) -> Vec { + self.io.read() + } + + fn read_slice(&mut self, slice: &mut [u8]) { + self.io.read_slice(slice) + } + + fn read_into(&mut self, buffer: &mut [u8]) { + self.io.read_into(buffer) + } + + fn write_serialized(&mut self, data: &[u8]) { + self.io.write_serialized(data) + } + + fn write_bytes(&mut self, data: &[u8]) { + self.io.write_bytes(data) + } +} + +impl ZiskHintin { + /// Create a null stdin (no input) + pub fn null() -> Self { + Self { io: ZiskHintIOVariant::Null(ZiskNullHintin) } + } + + /// Create a file-based stdin + pub fn from_file>(path: P) -> Result { + Ok(Self { io: ZiskHintIOVariant::File(ZiskFileHintin::new(path)?) }) + } +} diff --git a/common/src/zisk_lib_init.rs b/common/src/zisk_lib_init.rs index 7a7465765..c7cf940c0 100644 --- a/common/src/zisk_lib_init.rs +++ b/common/src/zisk_lib_init.rs @@ -4,7 +4,10 @@ use fields::PrimeField64; use proofman_common::VerboseMode; use witness::WitnessLibrary; -use crate::{io::ZiskStdin, ExecutorStats}; +use crate::{ + io::{ZiskHintin, ZiskStdin}, + ExecutorStats, +}; #[derive(Debug, Default, Clone)] pub struct ZiskExecutionResult { @@ -30,6 +33,7 @@ pub struct Stats { /// Extension trait that provides execution result access without Any boxing pub trait ZiskWitnessLibrary { fn set_stdin(&self, stdin: ZiskStdin); + fn set_hintin(&self, hintin: ZiskHintin); fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)>; } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index ed79415ee..b940e9303 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -32,7 +32,7 @@ use rom_setup::gen_elf_hash; use sm_rom::{RomInstance, RomSM}; use std::sync::atomic::{AtomicUsize, Ordering}; use witness::WitnessComponent; -use zisk_common::io::{ZiskIO, ZiskStdin}; +use zisk_common::io::{ZiskHintin, ZiskIO, ZiskStdin}; use crate::DummyCounter; use data_bus::DataBusTrait; @@ -79,6 +79,7 @@ enum MinimalTraceExecutionMode { /// machines, planning, and witness computation. pub struct ZiskExecutor { stdin: Mutex, + hintin: Mutex, /// ZisK ROM, a binary file containing the ZisK program to be executed. pub zisk_rom: Arc, @@ -188,6 +189,7 @@ impl ZiskExecutor { Self { stdin: Mutex::new(ZiskStdin::null()), + hintin: Mutex::new(ZiskHintin::null()), rom_path, asm_runner_path: asm_path, asm_rom_path, @@ -219,6 +221,11 @@ impl ZiskExecutor { *guard = stdin; } + pub fn set_hintin(&self, hintin: ZiskHintin) { + let mut guard = self.hintin.lock().unwrap(); + *guard = hintin; + } + #[allow(clippy::type_complexity)] pub fn get_execution_result(&self) -> (ZiskExecutionResult, ExecutorStats) { (self.execution_result.lock().unwrap().clone(), self.stats.get_inner()) diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 69b141b78..d34337d9e 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -11,7 +11,7 @@ use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rom_setup::DEFAULT_CACHE_PATH; use std::{collections::HashMap, path::PathBuf}; use tracing::info; -use zisk_common::io::ZiskStdin; +use zisk_common::io::{ZiskHintin, ZiskStdin}; use zisk_common::ExecutorStats; use zisk_distributed_common::LoggingConfig; @@ -98,8 +98,13 @@ impl ProverEngine for AsmProver { .unwrap_or(0) } - fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result { - self.core_prover.backend.execute(stdin, output_path) + fn execute( + &self, + stdin: ZiskStdin, + hintin: Option, + output_path: Option, + ) -> Result { + self.core_prover.backend.execute(stdin, hintin, output_path) } fn stats( diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index e4a316bf3..e7d795501 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -9,7 +9,10 @@ use fields::Goldilocks; use proofman::{AggProofs, ProofInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult}; use proofman_common::{DebugInfo, ProofOptions}; use std::{fs::File, io::Write, path::PathBuf}; -use zisk_common::{io::ZiskStdin, ExecutorStats, ProofLog, ZiskExecutionResult, ZiskLib}; +use zisk_common::{ + io::{ZiskHintin, ZiskStdin}, + ExecutorStats, ProofLog, ZiskExecutionResult, ZiskLib, +}; use zstd::Encoder; pub(crate) struct ProverBackend { @@ -31,9 +34,13 @@ impl ProverBackend { pub(crate) fn execute( &self, stdin: ZiskStdin, + hintin: Option, output_path: Option, ) -> Result { self.witness_lib.set_stdin(stdin); + if let Some(hintin) = hintin { + self.witness_lib.set_hintin(hintin); + } let start = std::time::Instant::now(); diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index e49e69d2f..020add8b9 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -7,7 +7,7 @@ use crate::{ use proofman::{AggProofs, ProofMan, ProvePhase, ProvePhaseInputs}; use proofman_common::{initialize_logger, ParamsGPU, ProofOptions}; use std::path::PathBuf; -use zisk_common::io::ZiskStdin; +use zisk_common::io::{ZiskHintin, ZiskStdin}; use zisk_common::ExecutorStats; use zisk_distributed_common::LoggingConfig; @@ -86,8 +86,16 @@ impl ProverEngine for EmuProver { .unwrap_or(0) } - fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result { - self.core_prover.backend.execute(stdin, output_path) + fn execute( + &self, + stdin: ZiskStdin, + hintin: Option, + output_path: Option, + ) -> Result { + if hintin.is_some() { + return Err(anyhow::anyhow!("EMU prover does not support precompile hints")); + } + self.core_prover.backend.execute(stdin, None, output_path) } fn stats( diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index efb3f9ce0..e1d7c23a3 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -11,7 +11,10 @@ use proofman_common::ProofOptions; use crate::Proof; use anyhow::Result; use std::{path::PathBuf, time::Duration}; -use zisk_common::{io::ZiskStdin, ExecutorStats, ZiskExecutionResult}; +use zisk_common::{ + io::{ZiskHintin, ZiskStdin}, + ExecutorStats, ZiskExecutionResult, +}; pub struct ZiskExecuteResult { pub execution: ZiskExecutionResult, @@ -46,7 +49,12 @@ pub trait ProverEngine { fn executed_steps(&self) -> u64; - fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result; + fn execute( + &self, + stdin: ZiskStdin, + hintin: Option, + output_path: Option, + ) -> Result; fn stats( &self, @@ -121,8 +129,12 @@ impl ZiskProver { /// Execute the prover with the given standard input and output path. /// It only runs the execution without generating a proof. - pub fn execute(&self, stdin: ZiskStdin) -> Result { - self.prover.execute(stdin, None) + pub fn execute( + &self, + stdin: ZiskStdin, + hintin: Option, + ) -> Result { + self.prover.execute(stdin, hintin, None) } /// Get the execution statistics with the given standard input and debug information. diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 0d4ad3712..cd8e52a55 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -215,6 +215,12 @@ impl ZiskWitnessLibrary for WitnessLib { } } + fn set_hintin(&self, hintin: zisk_common::io::ZiskHintin) { + if let Some(executor) = &self.executor { + executor.set_hintin(hintin); + } + } + /// Returns the execution result of the witness computation. /// /// # Returns From 24cf2a37b74726972e494cd65f5f7db0345384e6 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 08:38:27 +0000 Subject: [PATCH 021/782] first version working --- core/src/zisk_rom_2_asm.rs | 5 +- emulator-asm/asm-runner/Cargo.toml | 1 + emulator-asm/asm-runner/src/asm_mt.rs | 14 +++ emulator-asm/asm-runner/src/shmem_utils.rs | 68 ++++++++++- executor/src/executor.rs | 44 ++++++- precompiles/common/Cargo.toml | 1 + precompiles/common/src/lib.rs | 4 +- precompiles/common/src/precompiles_hints.rs | 122 +++++++++++++------- 8 files changed, 206 insertions(+), 53 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 64ee8c7b5..42589fa71 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -216,10 +216,11 @@ impl ZiskAsmContext { } pub fn precompile_results(&self) -> bool { - self.precompile_results + // self.precompile_results + true } pub fn precompile_results_keccak(&self) -> bool { - self.precompile_results() && false + self.precompile_results() && true //false } pub fn precompile_results_sha256(&self) -> bool { self.precompile_results() && false diff --git a/emulator-asm/asm-runner/Cargo.toml b/emulator-asm/asm-runner/Cargo.toml index f1c494afd..59e495a9d 100644 --- a/emulator-asm/asm-runner/Cargo.toml +++ b/emulator-asm/asm-runner/Cargo.toml @@ -14,6 +14,7 @@ path = "src/lib.rs" [dependencies] zisk-common = { workspace = true } zisk-core = { workspace = true } +precompiles-common = { workspace = true } mem-planner-cpp = { workspace = true } mem-common = { workspace = true } diff --git a/emulator-asm/asm-runner/src/asm_mt.rs b/emulator-asm/asm-runner/src/asm_mt.rs index 51154ebb8..f5cde7f79 100644 --- a/emulator-asm/asm-runner/src/asm_mt.rs +++ b/emulator-asm/asm-runner/src/asm_mt.rs @@ -109,3 +109,17 @@ impl AsmInputC2 { bytes } } + +#[repr(C)] +#[derive(Debug)] +pub struct AsmInputC3 { + pub input_data_size: u64, +} + +impl AsmInputC3 { + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(32); + bytes.extend_from_slice(&self.input_data_size.to_le_bytes()); + bytes + } +} diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 84acfefbb..31858f5a5 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -2,6 +2,7 @@ use libc::{ c_uint, close, mmap, munmap, shm_open, shm_unlink, MAP_FAILED, MAP_SHARED, PROT_READ, S_IRUSR, S_IWUSR, }; +use precompiles_common::PrecompileHintsProcessor; use std::{ ffi::CString, fmt::Debug, @@ -10,8 +11,8 @@ use std::{ ptr, sync::atomic::{fence, Ordering}, }; -use tracing::debug; -use zisk_common::io::{ZiskIO, ZiskStdin}; +use tracing::{debug, info}; +use zisk_common::io::{ZiskHintin, ZiskIO, ZiskStdin}; use anyhow::anyhow; use anyhow::Result; @@ -276,6 +277,15 @@ impl AsmSharedMemory { format!("{}_{}_input", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) } + /// Shared memory name for precompile hints data + pub fn shmem_precompile_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + format!( + "{}_{}_precompile", + AsmServices::shmem_prefix(port, local_rank), + asm_service.as_str() + ) + } + pub fn shmem_output_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { format!("{}_{}_output", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) } @@ -359,3 +369,57 @@ pub fn write_input(stdin: &mut ZiskStdin, shmem_input_writer: &SharedMemoryWrite shmem_input_writer.write_input(&full_input).expect("Failed to write input to shared memory"); } + +pub fn write_precompile( + hintin: &mut ZiskHintin, + shmem_input_writer: &SharedMemoryWriter, +) -> Result<()> { + let hints = reinterpret_vec(hintin.read()); + + let processor = PrecompileHintsProcessor::new()?; + let processed = processor.process_hints(&hints)?; + + info!("Precompile hints have generated {} u64 values", processed.len()); + + // Input size includes length prefix as u64 + let shmem_input_size = processed.len() + 1; + + // Save processed hints to a file for debugging + let hints_dir = std::path::Path::new("/data/hints"); + if let Err(e) = std::fs::create_dir_all(hints_dir) { + tracing::warn!("Failed to create hints directory: {}", e); + } else { + let timestamp = + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(); + let filename = hints_dir.join(format!("processed_{}.bin", timestamp)); + + let bytes = reinterpret_vec(processed.clone()); + if let Err(e) = std::fs::write(&filename, &bytes) { + tracing::warn!("Failed to write processed hints to {:?}: {}", filename, e); + } else { + tracing::info!("Saved processed hints to {:?}", filename); + } + } + + let mut full_input = Vec::with_capacity(shmem_input_size * 8); + full_input.extend_from_slice(&processed.len().to_le_bytes()); + full_input.extend_from_slice(&reinterpret_vec(processed)); + + shmem_input_writer.write_input(&full_input).expect("Failed to write input to shared memory"); + + Ok(()) +} + +fn reinterpret_vec(v: Vec) -> Vec { + let size_t = std::mem::size_of::(); + let size_u = std::mem::size_of::(); + + assert_eq!(v.as_ptr() as usize % std::mem::align_of::(), 0, "Vec is not properly aligned"); + + let len = (v.len() * size_t) / size_u; + let cap = (v.capacity() * size_t) / size_u; + let ptr = v.as_ptr() as *mut U; + + std::mem::forget(v); + unsafe { Vec::from_raw_parts(ptr, len, cap) } +} diff --git a/executor/src/executor.rs b/executor/src/executor.rs index b940e9303..e34573c3e 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -20,8 +20,9 @@ //! maintaining clarity and modularity in the computation process. use asm_runner::{ - write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, - MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, TaskFactory, + write_input, write_precompile, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, + AsmSharedMemory, MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, + Task, TaskFactory, }; use fields::PrimeField64; use pil_std_lib::Std; @@ -31,6 +32,7 @@ use rayon::prelude::*; use rom_setup::gen_elf_hash; use sm_rom::{RomInstance, RomSM}; use std::sync::atomic::{AtomicUsize, Ordering}; +use tracing::debug; use witness::WitnessComponent; use zisk_common::io::{ZiskHintin, ZiskIO, ZiskStdin}; @@ -145,6 +147,7 @@ pub struct ZiskExecutor { asm_shmem_rh: Arc>>, shmem_input_writer: [Arc>>; AsmServices::SERVICES.len()], + shmem_precompile_writer: [Arc>>; AsmServices::SERVICES.len()], } impl ZiskExecutor { @@ -213,6 +216,7 @@ impl ZiskExecutor { asm_shmem_mo: Arc::new(Mutex::new(asm_shmem_mo)), asm_shmem_rh: Arc::new(Mutex::new(None)), shmem_input_writer: std::array::from_fn(|_| Arc::new(Mutex::new(None))), + shmem_precompile_writer: std::array::from_fn(|_| Arc::new(Mutex::new(None))), } } @@ -302,15 +306,15 @@ impl ZiskExecutor { AsmServices::default_port(service, self.local_rank) }; + // Write inputs to shared memory let shmem_input_name = AsmSharedMemory::::shmem_input_name(port, *service, self.local_rank); let mut input_writer = self.shmem_input_writer[idx].lock().unwrap(); if input_writer.is_none() { - tracing::info!( + debug!( "Initializing SharedMemoryWriter for service {:?} at '{}'", - service, - shmem_input_name + service, shmem_input_name ); *input_writer = Some( SharedMemoryWriter::new( @@ -324,6 +328,36 @@ impl ZiskExecutor { write_input(&mut self.stdin.lock().unwrap(), input_writer.as_ref().unwrap()); + // Write precompile hints to shared memory + let shmem_prcompile_name = AsmSharedMemory::::shmem_precompile_name( + port, + *service, + self.local_rank, + ); + + let mut precompile_writer = self.shmem_precompile_writer[idx].lock().unwrap(); + if precompile_writer.is_none() { + debug!( + "Initializing SharedMemoryWriter for precompile hints for service {:?} at '{}'", + service, shmem_prcompile_name + ); + const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB + *precompile_writer = Some( + SharedMemoryWriter::new( + &shmem_prcompile_name, + MAX_PRECOMPILE_SIZE as usize, + self.unlock_mapped_memory, + ) + .expect("Failed to create SharedMemoryWriter for precompile hints"), + ); + } + + // TODO! Remove this let _ = ... and change it to ? when execute_with_assembly return Result<...> + let _ = write_precompile( + &mut self.hintin.lock().unwrap(), + precompile_writer.as_ref().unwrap(), + ); + // Add to executor stats #[cfg(feature = "stats")] self.stats.add_stat( diff --git a/precompiles/common/Cargo.toml b/precompiles/common/Cargo.toml index 667b1c244..c0f2fcce6 100644 --- a/precompiles/common/Cargo.toml +++ b/precompiles/common/Cargo.toml @@ -10,6 +10,7 @@ categories = { workspace = true } [dependencies] zisk-core = { workspace = true } zisk-common = { workspace = true } +tracing = { workspace = true } fields = { workspace = true } diff --git a/precompiles/common/src/lib.rs b/precompiles/common/src/lib.rs index bf46b60c0..cdb28d1d9 100644 --- a/precompiles/common/src/lib.rs +++ b/precompiles/common/src/lib.rs @@ -2,7 +2,9 @@ mod goldilocks_constants; mod precompiles_hints; pub use goldilocks_constants::{get_ks, GOLDILOCKS_GEN, GOLDILOCKS_K}; -pub use precompiles_hints::{PrecompileHint, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT}; +pub use precompiles_hints::{ + PrecompileHint, PrecompileHintsProcessor, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT, +}; use std::collections::VecDeque; use zisk_common::{BusId, MEM_BUS_ID}; diff --git a/precompiles/common/src/precompiles_hints.rs b/precompiles/common/src/precompiles_hints.rs index fefa114ff..4c91f25cb 100644 --- a/precompiles/common/src/precompiles_hints.rs +++ b/precompiles/common/src/precompiles_hints.rs @@ -235,7 +235,9 @@ impl PrecompileHintsProcessor { /// /// * `Ok(())` - Hints were successfully dispatched (does not mean processing is complete) /// * `Err` - If a previous error occurred or hints are malformed - pub fn process_hints(&self, hints: &[u64]) -> Result<()> { + pub fn process_hints(&self, hints: &[u64]) -> Result> { + let mut processed = Vec::new(); + // Parse hints and dispatch to pool let mut idx = 0; while idx < hints.len() { @@ -289,60 +291,29 @@ impl PrecompileHintsProcessor { (gen, seq) }; - // Spawn processing task - let state = Arc::clone(&self.state); - self.pool.spawn(move || { - // TODO! Is it necessary? TO increase performance maybe is enough to check error_flag only when storing result - // Check if we should stop due to error - if state.error_flag.load(Ordering::Acquire) { - return; - } - - // Process the hint - let result = Self::process_hint(&hint); - - // Store result and try to drain - let mut queue = state.queue.lock().unwrap(); + // Handle HINTS_TYPE_RESULT synchronously - it doesn't need async processing + if hint.hint_type == HINTS_TYPE_RESULT { + processed.extend_from_slice(&hint.data); - // Check generation first to detect stale workers from previous sessions - let current_gen = state.generation.load(Ordering::SeqCst); - if generation != current_gen { - // Worker belongs to old generation; ignore result - return; - } - - // Calculate offset in buffer; handle drained slots - if seq_id < queue.next_drain_seq { - // This result belongs to a previous stream/session; ignore - return; - } + // Immediately mark this slot as complete and drain + let mut queue = self.state.queue.lock().unwrap(); let offset = seq_id - queue.next_drain_seq; - - // Check error flag again before storing to avoid processing after error - if state.error_flag.load(Ordering::Acquire) { - return; - } - - queue.buffer[offset] = Some(result); + queue.buffer[offset] = Some(Ok(hint.data.clone())); // Drain consecutive ready results from the front while let Some(Some(res)) = queue.buffer.front() { match res { Ok(_data) => { - // Print the result (will be replaced with send to another process) - // println!("[seq={}] Result: {:?}", queue.next_drain_seq, data); queue.buffer.pop_front(); queue.next_drain_seq += 1; } Err(_) => { - // Error found - signal to stop and break - state.error_flag.store(true, Ordering::Release); - // Print error and stop draining + self.state.error_flag.store(true, Ordering::Release); if let Some(Some(Err(e))) = queue.buffer.pop_front() { eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); } queue.next_drain_seq += 1; - state.drain_signal.notify_all(); + self.state.drain_signal.notify_all(); break; } } @@ -350,14 +321,79 @@ impl PrecompileHintsProcessor { // Notify if buffer is now empty if queue.buffer.is_empty() { - state.drain_signal.notify_all(); + self.state.drain_signal.notify_all(); } - }); + } else { + // Spawn processing task + let state = Arc::clone(&self.state); + self.pool.spawn(move || { + // TODO! Is it necessary? TO increase performance maybe is enough to check error_flag only when storing result + // Check if we should stop due to error + if state.error_flag.load(Ordering::Acquire) { + return; + } + + // Process the hint + let result = Self::process_hint(&hint); + + // Store result and try to drain + let mut queue = state.queue.lock().unwrap(); + + // Check generation first to detect stale workers from previous sessions + let current_gen = state.generation.load(Ordering::SeqCst); + if generation != current_gen { + // Worker belongs to old generation; ignore result + return; + } + + // Calculate offset in buffer; handle drained slots + if seq_id < queue.next_drain_seq { + // This result belongs to a previous stream/session; ignore + return; + } + let offset = seq_id - queue.next_drain_seq; + + // Check error flag again before storing to avoid processing after error + if state.error_flag.load(Ordering::Acquire) { + return; + } + + queue.buffer[offset] = Some(result); + + // Drain consecutive ready results from the front + while let Some(Some(res)) = queue.buffer.front() { + match res { + Ok(_data) => { + // Print the result (will be replaced with send to another process) + // println!("[seq={}] Result: {:?}", queue.next_drain_seq, data); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; + } + Err(_) => { + // Error found - signal to stop and break + state.error_flag.store(true, Ordering::Release); + // Print error and stop draining + if let Some(Some(Err(e))) = queue.buffer.pop_front() { + eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); + } + queue.next_drain_seq += 1; + state.drain_signal.notify_all(); + break; + } + } + } + + // Notify if buffer is now empty + if queue.buffer.is_empty() { + state.drain_signal.notify_all(); + } + }); + } idx += length + 1; } - Ok(()) + Ok(processed) } /// Waits for all pending hints to be processed and drained. From 43c36f89988d071212fb327b62e075ee875c5007 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 08:39:11 +0000 Subject: [PATCH 022/782] refactor executor to extract hints pipeline --- Cargo.lock | 3 +- emulator-asm/asm-runner/Cargo.toml | 1 - emulator-asm/asm-runner/src/shmem_utils.rs | 59 +-------- executor/Cargo.toml | 2 + executor/src/executor.rs | 74 ++++++----- executor/src/hints_pipeline.rs | 136 +++++++++++++++++++++ executor/src/lib.rs | 2 + 7 files changed, 178 insertions(+), 99 deletions(-) create mode 100644 executor/src/hints_pipeline.rs diff --git a/Cargo.lock b/Cargo.lock index 4f95233ce..3c812da42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,7 +300,6 @@ dependencies = [ "mem-common", "mem-planner-cpp", "named-sem", - "precompiles-common", "rayon", "thiserror 2.0.17", "tracing", @@ -1335,6 +1334,7 @@ dependencies = [ name = "executor" version = "0.15.0" dependencies = [ + "anyhow", "asm-runner", "crossbeam", "data-bus", @@ -1348,6 +1348,7 @@ dependencies = [ "precomp-big-int", "precomp-keccakf", "precomp-sha256f", + "precompiles-common", "proofman", "proofman-common", "proofman-util", diff --git a/emulator-asm/asm-runner/Cargo.toml b/emulator-asm/asm-runner/Cargo.toml index 59e495a9d..f1c494afd 100644 --- a/emulator-asm/asm-runner/Cargo.toml +++ b/emulator-asm/asm-runner/Cargo.toml @@ -14,7 +14,6 @@ path = "src/lib.rs" [dependencies] zisk-common = { workspace = true } zisk-core = { workspace = true } -precompiles-common = { workspace = true } mem-planner-cpp = { workspace = true } mem-common = { workspace = true } diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 31858f5a5..e4cfe623c 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -2,7 +2,6 @@ use libc::{ c_uint, close, mmap, munmap, shm_open, shm_unlink, MAP_FAILED, MAP_SHARED, PROT_READ, S_IRUSR, S_IWUSR, }; -use precompiles_common::PrecompileHintsProcessor; use std::{ ffi::CString, fmt::Debug, @@ -11,8 +10,8 @@ use std::{ ptr, sync::atomic::{fence, Ordering}, }; -use tracing::{debug, info}; -use zisk_common::io::{ZiskHintin, ZiskIO, ZiskStdin}; +use tracing::debug; +use zisk_common::io::{ZiskIO, ZiskStdin}; use anyhow::anyhow; use anyhow::Result; @@ -369,57 +368,3 @@ pub fn write_input(stdin: &mut ZiskStdin, shmem_input_writer: &SharedMemoryWrite shmem_input_writer.write_input(&full_input).expect("Failed to write input to shared memory"); } - -pub fn write_precompile( - hintin: &mut ZiskHintin, - shmem_input_writer: &SharedMemoryWriter, -) -> Result<()> { - let hints = reinterpret_vec(hintin.read()); - - let processor = PrecompileHintsProcessor::new()?; - let processed = processor.process_hints(&hints)?; - - info!("Precompile hints have generated {} u64 values", processed.len()); - - // Input size includes length prefix as u64 - let shmem_input_size = processed.len() + 1; - - // Save processed hints to a file for debugging - let hints_dir = std::path::Path::new("/data/hints"); - if let Err(e) = std::fs::create_dir_all(hints_dir) { - tracing::warn!("Failed to create hints directory: {}", e); - } else { - let timestamp = - std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(); - let filename = hints_dir.join(format!("processed_{}.bin", timestamp)); - - let bytes = reinterpret_vec(processed.clone()); - if let Err(e) = std::fs::write(&filename, &bytes) { - tracing::warn!("Failed to write processed hints to {:?}: {}", filename, e); - } else { - tracing::info!("Saved processed hints to {:?}", filename); - } - } - - let mut full_input = Vec::with_capacity(shmem_input_size * 8); - full_input.extend_from_slice(&processed.len().to_le_bytes()); - full_input.extend_from_slice(&reinterpret_vec(processed)); - - shmem_input_writer.write_input(&full_input).expect("Failed to write input to shared memory"); - - Ok(()) -} - -fn reinterpret_vec(v: Vec) -> Vec { - let size_t = std::mem::size_of::(); - let size_u = std::mem::size_of::(); - - assert_eq!(v.as_ptr() as usize % std::mem::align_of::(), 0, "Vec is not properly aligned"); - - let len = (v.len() * size_t) / size_u; - let cap = (v.capacity() * size_t) / size_u; - let ptr = v.as_ptr() as *mut U; - - std::mem::forget(v); - unsafe { Vec::from_raw_parts(ptr, len, cap) } -} diff --git a/executor/Cargo.toml b/executor/Cargo.toml index 6487a4499..145f1a47e 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -29,6 +29,8 @@ tracing = { workspace = true } itertools = { workspace = true } rayon = { workspace = true } pil-std-lib = { workspace = true } +precompiles-common = { workspace = true } +anyhow = { workspace = true } crossbeam = "0.8.4" precomp-keccakf = { workspace = true } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index e34573c3e..52145abf6 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -20,9 +20,8 @@ //! maintaining clarity and modularity in the computation process. use asm_runner::{ - write_input, write_precompile, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, - AsmSharedMemory, MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, - Task, TaskFactory, + write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, + MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, TaskFactory, }; use fields::PrimeField64; use pil_std_lib::Std; @@ -36,7 +35,7 @@ use tracing::debug; use witness::WitnessComponent; use zisk_common::io::{ZiskHintin, ZiskIO, ZiskStdin}; -use crate::DummyCounter; +use crate::{DummyCounter, HintsPipeline}; use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; use zisk_common::{ @@ -81,7 +80,8 @@ enum MinimalTraceExecutionMode { /// machines, planning, and witness computation. pub struct ZiskExecutor { stdin: Mutex, - hintin: Mutex, + + hints_pipeline: Mutex, /// ZisK ROM, a binary file containing the ZisK program to be executed. pub zisk_rom: Arc, @@ -147,7 +147,6 @@ pub struct ZiskExecutor { asm_shmem_rh: Arc>>, shmem_input_writer: [Arc>>; AsmServices::SERVICES.len()], - shmem_precompile_writer: [Arc>>; AsmServices::SERVICES.len()], } impl ZiskExecutor { @@ -190,9 +189,30 @@ impl ZiskExecutor { (None, None) }; + let hints_shmem_names = AsmServices::SERVICES + .iter() + .map(|service| { + AsmSharedMemory::::shmem_precompile_name( + if let Some(base_port) = base_port { + AsmServices::port_for(service, base_port, local_rank) + } else { + AsmServices::default_port(service, local_rank) + }, + *service, + local_rank, + ) + }) + .collect::>(); + + let hints_pipeline = Mutex::new(HintsPipeline::new( + ZiskHintin::null(), + hints_shmem_names, + unlock_mapped_memory, + )); + Self { stdin: Mutex::new(ZiskStdin::null()), - hintin: Mutex::new(ZiskHintin::null()), + hints_pipeline, rom_path, asm_runner_path: asm_path, asm_rom_path, @@ -216,7 +236,6 @@ impl ZiskExecutor { asm_shmem_mo: Arc::new(Mutex::new(asm_shmem_mo)), asm_shmem_rh: Arc::new(Mutex::new(None)), shmem_input_writer: std::array::from_fn(|_| Arc::new(Mutex::new(None))), - shmem_precompile_writer: std::array::from_fn(|_| Arc::new(Mutex::new(None))), } } @@ -226,8 +245,7 @@ impl ZiskExecutor { } pub fn set_hintin(&self, hintin: ZiskHintin) { - let mut guard = self.hintin.lock().unwrap(); - *guard = hintin; + self.hints_pipeline.lock().unwrap().set_hintin(hintin); } #[allow(clippy::type_complexity)] @@ -328,36 +346,6 @@ impl ZiskExecutor { write_input(&mut self.stdin.lock().unwrap(), input_writer.as_ref().unwrap()); - // Write precompile hints to shared memory - let shmem_prcompile_name = AsmSharedMemory::::shmem_precompile_name( - port, - *service, - self.local_rank, - ); - - let mut precompile_writer = self.shmem_precompile_writer[idx].lock().unwrap(); - if precompile_writer.is_none() { - debug!( - "Initializing SharedMemoryWriter for precompile hints for service {:?} at '{}'", - service, shmem_prcompile_name - ); - const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB - *precompile_writer = Some( - SharedMemoryWriter::new( - &shmem_prcompile_name, - MAX_PRECOMPILE_SIZE as usize, - self.unlock_mapped_memory, - ) - .expect("Failed to create SharedMemoryWriter for precompile hints"), - ); - } - - // TODO! Remove this let _ = ... and change it to ? when execute_with_assembly return Result<...> - let _ = write_precompile( - &mut self.hintin.lock().unwrap(), - precompile_writer.as_ref().unwrap(), - ); - // Add to executor stats #[cfg(feature = "stats")] self.stats.add_stat( @@ -369,6 +357,12 @@ impl ZiskExecutor { ); }); + self.hints_pipeline + .lock() + .unwrap() + .write_hints() + .expect("Failed to write hints to shared memory"); + let chunk_size = self.chunk_size; let (world_rank, local_rank, base_port) = (self.world_rank, self.local_rank, self.base_port); diff --git a/executor/src/hints_pipeline.rs b/executor/src/hints_pipeline.rs new file mode 100644 index 000000000..8e594501f --- /dev/null +++ b/executor/src/hints_pipeline.rs @@ -0,0 +1,136 @@ +use anyhow::Result; +use asm_runner::SharedMemoryWriter; +use precompiles_common::PrecompileHintsProcessor; +use std::sync::Mutex; +use tracing::{debug, info, warn}; +use zisk_common::io::{ZiskHintin, ZiskIO}; + +pub struct HintsPipeline { + hintin: Mutex, + shmem_names: Vec, + unlock_mapped_memory: bool, + shmem_writers: Mutex>, +} + +impl HintsPipeline { + const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB + + pub fn new(hintin: ZiskHintin, shmem_names: Vec, unlock_mapped_memory: bool) -> Self { + Self { + hintin: Mutex::new(hintin), + shmem_names, + unlock_mapped_memory, + shmem_writers: Mutex::new(Vec::new()), + } + } + + pub fn add_shmem_name(&mut self, name: String) -> Result<()> { + // Check if the writers have already been initialized + let shmem_writers = self.shmem_writers.lock().unwrap(); + if !shmem_writers.is_empty() { + return Err(anyhow::anyhow!( + "Cannot add shared memory name '{}' after initialization", + name + )); + } + + // Check if the name already exists + if self.shmem_names.contains(&name) { + warn!( + "Shared memory name '{}' already exists in the pipeline. Skipping addition.", + name + ); + return Ok(()); + } + + self.shmem_names.push(name); + Ok(()) + } + + pub fn set_hintin(&self, hintin: ZiskHintin) { + let mut guard = self.hintin.lock().unwrap(); + *guard = hintin; + } + + pub fn initialize(&self) { + let mut shmem_writer = self.shmem_writers.lock().unwrap(); + + if !shmem_writer.is_empty() { + warn!( + "SharedMemoryWriters for precompile hints is already initialized at '{}'. Skipping", + self.shmem_names.join(", ") + ); + } else { + debug!( + "Initializing SharedMemoryWriter for precompile hints at '{}'", + self.shmem_names.join(", ") + ); + + *shmem_writer = self + .shmem_names + .iter() + .map(|name| { + SharedMemoryWriter::new( + &name, + Self::MAX_PRECOMPILE_SIZE as usize, + self.unlock_mapped_memory, + ) + .expect("Failed to create SharedMemoryWriter for precompile hints") + }) + .collect(); + } + } + + pub fn write_hints(&self) -> Result<()> { + // Check if initialization is needed without holding the lock + let needs_init = { + let shmem_writers = self.shmem_writers.lock().unwrap(); + shmem_writers.is_empty() + }; + + if needs_init { + self.initialize(); + } + + let mut hintin = self.hintin.lock().unwrap(); + + let hints = Self::reinterpret_vec(hintin.read()); + + let processor = PrecompileHintsProcessor::new()?; + let processed = processor.process_hints(&hints)?; + + info!("Precompile hints have generated {} u64 values", processed.len()); + + // Input size includes length prefix as u64 + let shmem_input_size = processed.len() + 1; + + let mut full_input = Vec::with_capacity(shmem_input_size * 8); + full_input.extend_from_slice(&processed.len().to_le_bytes()); + full_input.extend_from_slice(&Self::reinterpret_vec(processed)); + + let shmem_writers = self.shmem_writers.lock().unwrap(); + for shmem_writer in shmem_writers.iter() { + shmem_writer.write_input(&full_input)? + } + + Ok(()) + } + + fn reinterpret_vec(v: Vec) -> Vec { + let size_t = std::mem::size_of::(); + let size_u = std::mem::size_of::(); + + assert_eq!( + v.as_ptr() as usize % std::mem::align_of::(), + 0, + "Vec is not properly aligned" + ); + + let len = (v.len() * size_t) / size_u; + let cap = (v.capacity() * size_t) / size_u; + let ptr = v.as_ptr() as *mut U; + + std::mem::forget(v); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + } +} diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 936a187d4..ab7bbbb67 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -1,11 +1,13 @@ mod dummy_counter; mod executor; +mod hints_pipeline; mod sm_static_bundle; mod static_data_bus; mod static_data_bus_collect; pub use dummy_counter::*; pub use executor::*; +pub use hints_pipeline::HintsPipeline; pub use sm_static_bundle::*; pub use static_data_bus::*; pub use static_data_bus_collect::*; From a33581c394aa62bd9d53ce619dbeb314547e1291 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 08:59:05 +0000 Subject: [PATCH 023/782] improve pipeline hints doc --- executor/src/executor.rs | 5 +++ executor/src/hints_pipeline.rs | 69 +++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 52145abf6..303098c0a 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -79,8 +79,10 @@ enum MinimalTraceExecutionMode { /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. pub struct ZiskExecutor { + /// Standard input for the ZisK program. stdin: Mutex, + /// Pipeline for handling precompile hints. hints_pipeline: Mutex, /// ZisK ROM, a binary file containing the ZisK program to be executed. @@ -189,6 +191,7 @@ impl ZiskExecutor { (None, None) }; + // Generate shared memory names for hints pipeline based on assembly services. let hints_shmem_names = AsmServices::SERVICES .iter() .map(|service| { @@ -204,6 +207,7 @@ impl ZiskExecutor { }) .collect::>(); + // Create hints pipeline with null hintin initially. let hints_pipeline = Mutex::new(HintsPipeline::new( ZiskHintin::null(), hints_shmem_names, @@ -357,6 +361,7 @@ impl ZiskExecutor { ); }); + // Process and write precompile atomically self.hints_pipeline .lock() .unwrap() diff --git a/executor/src/hints_pipeline.rs b/executor/src/hints_pipeline.rs index 8e594501f..743d4235f 100644 --- a/executor/src/hints_pipeline.rs +++ b/executor/src/hints_pipeline.rs @@ -1,3 +1,9 @@ +//! HintsPipeline is responsible for processing precompile hints and writing them to shared memory. +//! +//! It uses a ZiskHintin as the source of hints, and writes the processed hints to shared memories +//! specified by their names. +//! The pipeline ensures that the shared memory writers are initialized before writing hints. + use anyhow::Result; use asm_runner::SharedMemoryWriter; use precompiles_common::PrecompileHintsProcessor; @@ -5,16 +11,33 @@ use std::sync::Mutex; use tracing::{debug, info, warn}; use zisk_common::io::{ZiskHintin, ZiskIO}; +/// HintsPipeline struct manages the processing of precompile hints and writing them to shared memory. pub struct HintsPipeline { + /// The ZiskHintin source for reading hints. hintin: Mutex, + + /// Names of the shared memories to write hints to. shmem_names: Vec, + + /// Whether to unlock mapped memory after writing. unlock_mapped_memory: bool, + + /// Shared memory writers for writing processed hints. shmem_writers: Mutex>, } impl HintsPipeline { const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB + /// Create a new HintsPipeline with the given ZiskHintin, shared memory names, and unlock option. + /// + /// # Arguments + /// * `hintin` - The ZiskHintin source for reading hints. + /// * `shmem_names` - A vector of shared memory names to write hints to. + /// * `unlock_mapped_memory` - Whether to unlock mapped memory after writing. + /// + /// # Returns + /// A new `HintsPipeline` instance with uninitialized writers. pub fn new(hintin: ZiskHintin, shmem_names: Vec, unlock_mapped_memory: bool) -> Self { Self { hintin: Mutex::new(hintin), @@ -24,6 +47,16 @@ impl HintsPipeline { } } + /// Add a shared memory name to the pipeline. + /// + /// This method must be called before initialization. + /// + /// # Arguments + /// * `name` - The name of the shared memory to add. + /// + /// # Returns + /// * `Ok(())` - If the name was successfully added or already exists + /// * `Err` - If writers have already been initialized pub fn add_shmem_name(&mut self, name: String) -> Result<()> { // Check if the writers have already been initialized let shmem_writers = self.shmem_writers.lock().unwrap(); @@ -47,11 +80,19 @@ impl HintsPipeline { Ok(()) } + /// Set a new ZiskHintin for the pipeline. + /// + /// # Arguments + /// * `hintin` - The new ZiskHintin source for reading hints. pub fn set_hintin(&self, hintin: ZiskHintin) { let mut guard = self.hintin.lock().unwrap(); *guard = hintin; } + /// Initialize the shared memory writers for the pipeline. + /// + /// This method creates SharedMemoryWriter instances for each shared memory name. + /// If writers are already initialized it logs a warning and does nothing. pub fn initialize(&self) { let mut shmem_writer = self.shmem_writers.lock().unwrap(); @@ -81,6 +122,19 @@ impl HintsPipeline { } } + /// Process and write precompile hints to all shared memory writers. + /// + /// This method: + /// 1. Reads hints from the ZiskHintin source + /// 2. Processes them using PrecompileHintsProcessor + /// 3. Prepares the data with a length prefix (u64) followed by the processed hints + /// 4. Writes the data to all configured shared memory writers + /// + /// The shared memory writers will be automatically initialized if needed. + /// + /// # Returns + /// * `Ok(())` - If hints were successfully processed and written + /// * `Err` - If processing or writing fails pub fn write_hints(&self) -> Result<()> { // Check if initialization is needed without holding the lock let needs_init = { @@ -110,12 +164,25 @@ impl HintsPipeline { let shmem_writers = self.shmem_writers.lock().unwrap(); for shmem_writer in shmem_writers.iter() { - shmem_writer.write_input(&full_input)? + shmem_writer.write_input(&full_input)?; } Ok(()) } + /// Reinterprets a `Vec` as a `Vec` by transmuting the underlying memory. + /// + /// # Safety + /// This function performs an unsafe transmutation. It assumes: + /// - The source vector is properly aligned for type `U` + /// - The total byte size is compatible between `T` and `U` + /// + /// # Panics + /// Panics if the source vector is not properly aligned for type `U` + /// + /// # Type Parameters + /// * `T` - Source element type + /// * `U` - Destination element type fn reinterpret_vec(v: Vec) -> Vec { let size_t = std::mem::size_of::(); let size_u = std::mem::size_of::(); From c996e5e53834b7f3582c92a2c9c66b67e6a3e593 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 08:59:37 +0000 Subject: [PATCH 024/782] improve reinterpret_vec fn to return Result<...> --- executor/src/hints_pipeline.rs | 47 +++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/executor/src/hints_pipeline.rs b/executor/src/hints_pipeline.rs index 743d4235f..489fecb8a 100644 --- a/executor/src/hints_pipeline.rs +++ b/executor/src/hints_pipeline.rs @@ -148,7 +148,7 @@ impl HintsPipeline { let mut hintin = self.hintin.lock().unwrap(); - let hints = Self::reinterpret_vec(hintin.read()); + let hints = Self::reinterpret_vec(hintin.read())?; let processor = PrecompileHintsProcessor::new()?; let processed = processor.process_hints(&hints)?; @@ -160,7 +160,7 @@ impl HintsPipeline { let mut full_input = Vec::with_capacity(shmem_input_size * 8); full_input.extend_from_slice(&processed.len().to_le_bytes()); - full_input.extend_from_slice(&Self::reinterpret_vec(processed)); + full_input.extend_from_slice(&Self::reinterpret_vec(processed)?); let shmem_writers = self.shmem_writers.lock().unwrap(); for shmem_writer in shmem_writers.iter() { @@ -172,32 +172,49 @@ impl HintsPipeline { /// Reinterprets a `Vec` as a `Vec` by transmuting the underlying memory. /// - /// # Safety - /// This function performs an unsafe transmutation. It assumes: - /// - The source vector is properly aligned for type `U` - /// - The total byte size is compatible between `T` and `U` + /// This function converts between vector types by reinterpreting the raw memory, + /// adjusting length and capacity based on the size ratio between types. + /// It performs internal unsafe operations but validates all safety requirements + /// before the conversion. /// - /// # Panics - /// Panics if the source vector is not properly aligned for type `U` + /// # Arguments + /// * `v` - The source vector to reinterpret. + /// + /// # Returns + /// * `Ok(Vec)` - A new vector that owns the same memory as the input vector + /// * `Err` - If validation fails (size incompatibility or alignment issues) /// /// # Type Parameters /// * `T` - Source element type /// * `U` - Destination element type - fn reinterpret_vec(v: Vec) -> Vec { + fn reinterpret_vec(v: Vec) -> Result> { let size_t = std::mem::size_of::(); let size_u = std::mem::size_of::(); - assert_eq!( - v.as_ptr() as usize % std::mem::align_of::(), - 0, - "Vec is not properly aligned" - ); + // Check that total byte size is compatible + if (v.len() * size_t) % size_u != 0 { + return Err(anyhow::anyhow!( + "Total byte size {} is not divisible by target type size {}", + v.len() * size_t, + size_u + )); + } + + // Check that the pointer is properly aligned for U + if v.as_ptr() as usize % std::mem::align_of::() != 0 { + return Err(anyhow::anyhow!( + "Vec<{}> is not properly aligned for Vec<{}> (requires {}-byte alignment)", + std::any::type_name::(), + std::any::type_name::(), + std::mem::align_of::() + )); + } let len = (v.len() * size_t) / size_u; let cap = (v.capacity() * size_t) / size_u; let ptr = v.as_ptr() as *mut U; std::mem::forget(v); - unsafe { Vec::from_raw_parts(ptr, len, cap) } + Ok(unsafe { Vec::from_raw_parts(ptr, len, cap) }) } } From 7b53614cba4b525ccb25bbbbdcd4fc3cf54d4ee0 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:13:25 +0000 Subject: [PATCH 025/782] modify shmem_writer write_input fn to accept &[T] instead of &[u8] --- emulator-asm/asm-runner/src/shmem_writer.rs | 26 ++++++++++++++------- executor/src/hints_pipeline.rs | 10 +++++--- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index d4353bd13..8d5dec899 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -78,25 +78,35 @@ impl SharedMemoryWriter { } /// Writes data to the shared memory, always from the start - pub fn write_input(&self, data: &[u8]) -> Result<()> { - if data.len() > self.size { + /// + /// # Type Parameters + /// * `T` - The element type of the slice (e.g., u8, u64) + /// + /// # Arguments + /// * `data` - A slice of data to write to shared memory + /// + /// # Returns + /// * `Ok(())` - If data was successfully written + /// * `Err` - If data size exceeds shared memory capacity or msync fails + pub fn write_input(&self, data: &[T]) -> Result<()> { + let byte_size = data.len() * std::mem::size_of::(); + + if byte_size > self.size { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!( - "Data size ({}) exceeds shared memory capacity ({}) for '{}'", - data.len(), - self.size, - self.name + "Data size ({} bytes) exceeds shared memory capacity ({}) for '{}'", + byte_size, self.size, self.name ), )); } unsafe { - ptr::copy_nonoverlapping(data.as_ptr(), self.ptr, data.len()); + ptr::copy_nonoverlapping(data.as_ptr() as *const u8, self.ptr, byte_size); // Force changes to be flushed to the shared memory #[cfg(all(target_os = "linux", target_arch = "x86_64"))] if msync(self.ptr as *mut _, self.size, MS_SYNC /*| MS_INVALIDATE*/) != 0 { - panic!("msync failed: {}", std::io::Error::last_os_error()); + return Err(io::Error::last_os_error()); } } diff --git a/executor/src/hints_pipeline.rs b/executor/src/hints_pipeline.rs index 489fecb8a..f30ec08a6 100644 --- a/executor/src/hints_pipeline.rs +++ b/executor/src/hints_pipeline.rs @@ -158,9 +158,13 @@ impl HintsPipeline { // Input size includes length prefix as u64 let shmem_input_size = processed.len() + 1; - let mut full_input = Vec::with_capacity(shmem_input_size * 8); - full_input.extend_from_slice(&processed.len().to_le_bytes()); - full_input.extend_from_slice(&Self::reinterpret_vec(processed)?); + let mut full_input = Vec::with_capacity(shmem_input_size); + // Prefix with length as u64 + full_input.extend_from_slice(&[processed.len() as u64]); + // Append processed hints + full_input.extend_from_slice(&processed); + + println!("full_input size: {}", full_input.len()); let shmem_writers = self.shmem_writers.lock().unwrap(); for shmem_writer in shmem_writers.iter() { From 6cc397e4bac142a3124db11a28fb2108eef28f93 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:30:41 +0000 Subject: [PATCH 026/782] move reinterpret_vec to zisk_common:::utils --- common/src/utils.rs | 48 ++++++++++++++++++++++++++++++++ executor/src/hints_pipeline.rs | 50 +--------------------------------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/common/src/utils.rs b/common/src/utils.rs index 5163d8c03..d095e5c71 100644 --- a/common/src/utils.rs +++ b/common/src/utils.rs @@ -75,3 +75,51 @@ pub fn init_tracing(log_path: &str) { .with(file_layer) .init(); } + +/// Reinterprets a `Vec` as a `Vec` by transmuting the underlying memory. +/// +/// This function converts between vector types by reinterpreting the raw memory, +/// adjusting length and capacity based on the size ratio between types. +/// It performs internal unsafe operations but validates all safety requirements +/// before the conversion. +/// +/// # Arguments +/// * `v` - The source vector to reinterpret. +/// +/// # Returns +/// * `Ok(Vec)` - A new vector that owns the same memory as the input vector +/// * `Err` - If validation fails (size incompatibility or alignment issues) +/// +/// # Type Parameters +/// * `T` - Source element type +/// * `U` - Destination element type +pub fn reinterpret_vec(v: Vec) -> anyhow::Result> { + let size_t = std::mem::size_of::(); + let size_u = std::mem::size_of::(); + + // Check that total byte size is compatible + if (v.len() * size_t) % size_u != 0 { + return Err(anyhow::anyhow!( + "Total byte size {} is not divisible by target type size {}", + v.len() * size_t, + size_u + )); + } + + // Check that the pointer is properly aligned for U + if v.as_ptr() as usize % std::mem::align_of::() != 0 { + return Err(anyhow::anyhow!( + "Vec<{}> is not properly aligned for Vec<{}> (requires {}-byte alignment)", + std::any::type_name::(), + std::any::type_name::(), + std::mem::align_of::() + )); + } + + let len = (v.len() * size_t) / size_u; + let cap = (v.capacity() * size_t) / size_u; + let ptr = v.as_ptr() as *mut U; + + std::mem::forget(v); + Ok(unsafe { Vec::from_raw_parts(ptr, len, cap) }) +} diff --git a/executor/src/hints_pipeline.rs b/executor/src/hints_pipeline.rs index f30ec08a6..e2aba4a36 100644 --- a/executor/src/hints_pipeline.rs +++ b/executor/src/hints_pipeline.rs @@ -148,7 +148,7 @@ impl HintsPipeline { let mut hintin = self.hintin.lock().unwrap(); - let hints = Self::reinterpret_vec(hintin.read())?; + let hints = zisk_common::reinterpret_vec(hintin.read())?; let processor = PrecompileHintsProcessor::new()?; let processed = processor.process_hints(&hints)?; @@ -173,52 +173,4 @@ impl HintsPipeline { Ok(()) } - - /// Reinterprets a `Vec` as a `Vec` by transmuting the underlying memory. - /// - /// This function converts between vector types by reinterpreting the raw memory, - /// adjusting length and capacity based on the size ratio between types. - /// It performs internal unsafe operations but validates all safety requirements - /// before the conversion. - /// - /// # Arguments - /// * `v` - The source vector to reinterpret. - /// - /// # Returns - /// * `Ok(Vec)` - A new vector that owns the same memory as the input vector - /// * `Err` - If validation fails (size incompatibility or alignment issues) - /// - /// # Type Parameters - /// * `T` - Source element type - /// * `U` - Destination element type - fn reinterpret_vec(v: Vec) -> Result> { - let size_t = std::mem::size_of::(); - let size_u = std::mem::size_of::(); - - // Check that total byte size is compatible - if (v.len() * size_t) % size_u != 0 { - return Err(anyhow::anyhow!( - "Total byte size {} is not divisible by target type size {}", - v.len() * size_t, - size_u - )); - } - - // Check that the pointer is properly aligned for U - if v.as_ptr() as usize % std::mem::align_of::() != 0 { - return Err(anyhow::anyhow!( - "Vec<{}> is not properly aligned for Vec<{}> (requires {}-byte alignment)", - std::any::type_name::(), - std::any::type_name::(), - std::mem::align_of::() - )); - } - - let len = (v.len() * size_t) / size_u; - let cap = (v.capacity() * size_t) / size_u; - let ptr = v.as_ptr() as *mut U; - - std::mem::forget(v); - Ok(unsafe { Vec::from_raw_parts(ptr, len, cap) }) - } } From 88235fb24dff07f0cc27c9cacf21835fa8719d8f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:31:13 +0000 Subject: [PATCH 027/782] added is_initialized fn to hints_pipeline --- executor/src/hints_pipeline.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/executor/src/hints_pipeline.rs b/executor/src/hints_pipeline.rs index e2aba4a36..ce55f3e6b 100644 --- a/executor/src/hints_pipeline.rs +++ b/executor/src/hints_pipeline.rs @@ -89,11 +89,17 @@ impl HintsPipeline { *guard = hintin; } + /// Check if the shared memory writers have been initialized. + fn is_initialized(&self) -> bool { + let shmem_writers = self.shmem_writers.lock().unwrap(); + !shmem_writers.is_empty() + } + /// Initialize the shared memory writers for the pipeline. /// /// This method creates SharedMemoryWriter instances for each shared memory name. /// If writers are already initialized it logs a warning and does nothing. - pub fn initialize(&self) { + fn initialize(&self) { let mut shmem_writer = self.shmem_writers.lock().unwrap(); if !shmem_writer.is_empty() { @@ -136,13 +142,7 @@ impl HintsPipeline { /// * `Ok(())` - If hints were successfully processed and written /// * `Err` - If processing or writing fails pub fn write_hints(&self) -> Result<()> { - // Check if initialization is needed without holding the lock - let needs_init = { - let shmem_writers = self.shmem_writers.lock().unwrap(); - shmem_writers.is_empty() - }; - - if needs_init { + if !self.is_initialized() { self.initialize(); } From b3a64d360ddb2e76463fe38654f1798337f981f1 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:45:05 +0000 Subject: [PATCH 028/782] move hints_processor to hints crate --- Cargo.lock | 7 +++---- executor/Cargo.toml | 2 +- executor/src/hints_pipeline.rs | 2 +- hints/Cargo.toml | 4 +++- .../src/hints_processor.rs | 16 +++++++++------- hints/src/lib.rs | 5 +++++ precompiles/common/Cargo.toml | 4 ---- precompiles/common/src/lib.rs | 4 ---- 8 files changed, 22 insertions(+), 22 deletions(-) rename precompiles/common/src/precompiles_hints.rs => hints/src/hints_processor.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index 3c812da42..0718989d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1348,7 +1348,6 @@ dependencies = [ "precomp-big-int", "precomp-keccakf", "precomp-sha256f", - "precompiles-common", "proofman", "proofman-common", "proofman-util", @@ -1364,6 +1363,7 @@ dependencies = [ "witness", "zisk-common", "zisk-core", + "zisk-hints", "zisk-pil", "ziskemu", ] @@ -3004,10 +3004,7 @@ dependencies = [ name = "precompiles-common" version = "0.15.0" dependencies = [ - "anyhow", "fields", - "rayon", - "tracing", "zisk-common", "zisk-core", ] @@ -5721,8 +5718,10 @@ dependencies = [ name = "zisk-hints" version = "0.15.0" dependencies = [ + "anyhow", "lib-c", "precompiles-helpers", + "rayon", "ziskos", ] diff --git a/executor/Cargo.toml b/executor/Cargo.toml index 145f1a47e..b76b2a302 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -29,7 +29,7 @@ tracing = { workspace = true } itertools = { workspace = true } rayon = { workspace = true } pil-std-lib = { workspace = true } -precompiles-common = { workspace = true } +zisk-hints = { workspace = true } anyhow = { workspace = true } crossbeam = "0.8.4" diff --git a/executor/src/hints_pipeline.rs b/executor/src/hints_pipeline.rs index ce55f3e6b..4e3e4edce 100644 --- a/executor/src/hints_pipeline.rs +++ b/executor/src/hints_pipeline.rs @@ -6,10 +6,10 @@ use anyhow::Result; use asm_runner::SharedMemoryWriter; -use precompiles_common::PrecompileHintsProcessor; use std::sync::Mutex; use tracing::{debug, info, warn}; use zisk_common::io::{ZiskHintin, ZiskIO}; +use zisk_hints::PrecompileHintsProcessor; /// HintsPipeline struct manages the processing of precompile hints and writing them to shared memory. pub struct HintsPipeline { diff --git a/hints/Cargo.toml b/hints/Cargo.toml index 4f812d85c..fec64fc2b 100644 --- a/hints/Cargo.toml +++ b/hints/Cargo.toml @@ -14,4 +14,6 @@ path = "src/lib.rs" [dependencies] ziskos = { workspace = true } lib-c = { workspace = true } -precompiles-helpers = { workspace = true } \ No newline at end of file +precompiles-helpers = { workspace = true } +anyhow = { workspace = true } +rayon = { workspace = true } \ No newline at end of file diff --git a/precompiles/common/src/precompiles_hints.rs b/hints/src/hints_processor.rs similarity index 98% rename from precompiles/common/src/precompiles_hints.rs rename to hints/src/hints_processor.rs index 4c91f25cb..2cd5229d0 100644 --- a/precompiles/common/src/precompiles_hints.rs +++ b/hints/src/hints_processor.rs @@ -47,6 +47,7 @@ use anyhow::Result; use rayon::{ThreadPool, ThreadPoolBuilder}; +use ziskos::syscalls::SyscallPoint256; use std::collections::VecDeque; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; @@ -471,13 +472,14 @@ impl PrecompileHintsProcessor { } /// Processes a [`HINTS_TYPE_ECRECOVER`] hint. - fn process_hint_ecrecover(_hint: &PrecompileHint) -> Result> { - // TODO! - // assert!( - // hint_length == 8 + 4 + 4 + 4, - // "process_hints() Invalid ECRECOVER hint length: {}", - // hint_length - // ); + fn process_hint_ecrecover(hint: &PrecompileHint) -> Result> { + if hint.data.len() != 8 + 4 + 4 + 4 { + return Err(anyhow::anyhow!( + "Invalid ECRECOVER hint length: expected 20, got {}", + hint.data.len() + )); + } + // let pk: &SyscallPoint256 = unsafe { &(hint.data[i] as const SyscallPoint256) }; // let z: &[u64; 4] = unsafe { &(hints[i + 8] as const [u64; 4]) }; // let r: &[u64; 4] = unsafe { &(hints[i + 8 + 4] as const [u64; 4]) }; diff --git a/hints/src/lib.rs b/hints/src/lib.rs index 85dbfe8c2..af1b81935 100644 --- a/hints/src/lib.rs +++ b/hints/src/lib.rs @@ -1,7 +1,12 @@ pub mod hints; pub mod hints_definitions; +mod hints_processor; + pub mod secp256k1; pub use hints::*; pub use hints_definitions::*; +pub use hints_processor::{ + PrecompileHint, PrecompileHintsProcessor, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT, +}; pub use secp256k1::*; diff --git a/precompiles/common/Cargo.toml b/precompiles/common/Cargo.toml index c0f2fcce6..9bf5decaf 100644 --- a/precompiles/common/Cargo.toml +++ b/precompiles/common/Cargo.toml @@ -10,9 +10,5 @@ categories = { workspace = true } [dependencies] zisk-core = { workspace = true } zisk-common = { workspace = true } -tracing = { workspace = true } fields = { workspace = true } - -anyhow = { workspace = true } -rayon = { workspace = true } diff --git a/precompiles/common/src/lib.rs b/precompiles/common/src/lib.rs index cdb28d1d9..f37f9081a 100644 --- a/precompiles/common/src/lib.rs +++ b/precompiles/common/src/lib.rs @@ -1,10 +1,6 @@ mod goldilocks_constants; -mod precompiles_hints; pub use goldilocks_constants::{get_ks, GOLDILOCKS_GEN, GOLDILOCKS_K}; -pub use precompiles_hints::{ - PrecompileHint, PrecompileHintsProcessor, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT, -}; use std::collections::VecDeque; use zisk_common::{BusId, MEM_BUS_ID}; From ee563c60944140241b1b0afb9dcd89e541a04086 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:56:56 +0000 Subject: [PATCH 029/782] enabled process_hint_ecrecover --- hints/src/hints_processor.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/hints/src/hints_processor.rs b/hints/src/hints_processor.rs index 2cd5229d0..161b11e05 100644 --- a/hints/src/hints_processor.rs +++ b/hints/src/hints_processor.rs @@ -47,10 +47,12 @@ use anyhow::Result; use rayon::{ThreadPool, ThreadPoolBuilder}; -use ziskos::syscalls::SyscallPoint256; use std::collections::VecDeque; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; +use ziskos::syscalls::SyscallPoint256; + +use crate::secp256k1_ecdsa_verify; // TODO! COnvert Control Code to an enum and HINT TYPE to an enum as well @@ -473,20 +475,30 @@ impl PrecompileHintsProcessor { /// Processes a [`HINTS_TYPE_ECRECOVER`] hint. fn process_hint_ecrecover(hint: &PrecompileHint) -> Result> { - if hint.data.len() != 8 + 4 + 4 + 4 { + const EXPECTED_LEN: usize = 8 + 4 + 4 + 4; // pk(8) + z(4) + r(4) + s(4) + + if hint.data.len() != EXPECTED_LEN { return Err(anyhow::anyhow!( - "Invalid ECRECOVER hint length: expected 20, got {}", + "Invalid ECRECOVER hint length: expected {}, got {}", + EXPECTED_LEN, hint.data.len() )); } - // let pk: &SyscallPoint256 = unsafe { &(hint.data[i] as const SyscallPoint256) }; - // let z: &[u64; 4] = unsafe { &(hints[i + 8] as const [u64; 4]) }; - // let r: &[u64; 4] = unsafe { &(hints[i + 8 + 4] as const [u64; 4]) }; - // let s: &[u64; 4] = unsafe { &(hints[i + 8 + 4 + 4] as const [u64; 4]) }; - // secp256k1_ecdsa_verify(pk, z, r, s, &mut processedhints); + let mut processed_hints = Vec::new(); + + // Safety: We've validated the length above + unsafe { + let ptr = hint.data.as_ptr(); + let pk = &*(ptr as *const SyscallPoint256); + let z = &*(ptr.add(8) as *const [u64; 4]); + let r = &*(ptr.add(12) as *const [u64; 4]); + let s = &*(ptr.add(16) as *const [u64; 4]); + + secp256k1_ecdsa_verify(pk, z, r, s, &mut processed_hints); + } - Ok(vec![]) + Ok(processed_hints) } } From 7f4f6a82813d925051b599ac3f35974c7f9632a7 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:53:06 +0000 Subject: [PATCH 030/782] added hints_sink generic --- executor/src/executor.rs | 19 ++-- executor/src/hints_pipeline.rs | 154 +++++---------------------------- executor/src/hints_shmem.rs | 145 +++++++++++++++++++++++++++++++ executor/src/lib.rs | 2 + hints/src/hints_processor.rs | 24 ++++- hints/src/lib.rs | 8 ++ 6 files changed, 211 insertions(+), 141 deletions(-) create mode 100644 executor/src/hints_shmem.rs diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 303098c0a..3a7054b41 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -34,8 +34,9 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use tracing::debug; use witness::WitnessComponent; use zisk_common::io::{ZiskHintin, ZiskIO, ZiskStdin}; +use zisk_hints::PrecompileHintsProcessor; -use crate::{DummyCounter, HintsPipeline}; +use crate::{DummyCounter, HintsPipeline, HintsShmem}; use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; use zisk_common::{ @@ -76,6 +77,8 @@ enum MinimalTraceExecutionMode { AsmWithCounter, } +type HintsProcessorShmem = HintsPipeline; + /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. pub struct ZiskExecutor { @@ -83,7 +86,7 @@ pub struct ZiskExecutor { stdin: Mutex, /// Pipeline for handling precompile hints. - hints_pipeline: Mutex, + hints_pipeline: Mutex, /// ZisK ROM, a binary file containing the ZisK program to be executed. pub zisk_rom: Arc, @@ -208,11 +211,13 @@ impl ZiskExecutor { .collect::>(); // Create hints pipeline with null hintin initially. - let hints_pipeline = Mutex::new(HintsPipeline::new( - ZiskHintin::null(), - hints_shmem_names, - unlock_mapped_memory, - )); + let hints_processor = + PrecompileHintsProcessor::new().expect("Failed to create PrecompileHintsProcessor"); + + let hints_shmem = HintsShmem::new(hints_shmem_names, unlock_mapped_memory); + + let hints_pipeline = + Mutex::new(HintsPipeline::new(hints_processor, hints_shmem, ZiskHintin::null())); Self { stdin: Mutex::new(ZiskStdin::null()), diff --git a/executor/src/hints_pipeline.rs b/executor/src/hints_pipeline.rs index 4e3e4edce..2e240a9da 100644 --- a/executor/src/hints_pipeline.rs +++ b/executor/src/hints_pipeline.rs @@ -1,83 +1,36 @@ -//! HintsPipeline is responsible for processing precompile hints and writing them to shared memory. -//! -//! It uses a ZiskHintin as the source of hints, and writes the processed hints to shared memories -//! specified by their names. -//! The pipeline ensures that the shared memory writers are initialized before writing hints. +//! HintsPipeline is responsible for processing precompile hints and submitting them to a sink. +//! It uses a ZiskHintin as the source of hints, and writes the processed hints to a HintsSink. use anyhow::Result; -use asm_runner::SharedMemoryWriter; use std::sync::Mutex; -use tracing::{debug, info, warn}; +use tracing::info; use zisk_common::io::{ZiskHintin, ZiskIO}; -use zisk_hints::PrecompileHintsProcessor; +use zisk_hints::{HintsProcessor, HintsSink}; /// HintsPipeline struct manages the processing of precompile hints and writing them to shared memory. -pub struct HintsPipeline { - /// The ZiskHintin source for reading hints. - hintin: Mutex, +pub struct HintsPipeline { + /// The hints processor used to process hints before writing. + hints_processor: HP, - /// Names of the shared memories to write hints to. - shmem_names: Vec, + /// The hints sink used to submit processed hints. + hints_sink: HS, - /// Whether to unlock mapped memory after writing. - unlock_mapped_memory: bool, - - /// Shared memory writers for writing processed hints. - shmem_writers: Mutex>, + /// The ZiskHintin source for reading hints. + hintin: Mutex, } -impl HintsPipeline { - const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB - - /// Create a new HintsPipeline with the given ZiskHintin, shared memory names, and unlock option. +impl HintsPipeline { + /// Create a new HintsPipeline with the given processor, ZiskHintin, and sink. /// /// # Arguments + /// * `hints_processor` - The processor used to process hints. + /// * `hints_sink` - The sink used to submit processed hints. /// * `hintin` - The ZiskHintin source for reading hints. - /// * `shmem_names` - A vector of shared memory names to write hints to. - /// * `unlock_mapped_memory` - Whether to unlock mapped memory after writing. /// /// # Returns /// A new `HintsPipeline` instance with uninitialized writers. - pub fn new(hintin: ZiskHintin, shmem_names: Vec, unlock_mapped_memory: bool) -> Self { - Self { - hintin: Mutex::new(hintin), - shmem_names, - unlock_mapped_memory, - shmem_writers: Mutex::new(Vec::new()), - } - } - - /// Add a shared memory name to the pipeline. - /// - /// This method must be called before initialization. - /// - /// # Arguments - /// * `name` - The name of the shared memory to add. - /// - /// # Returns - /// * `Ok(())` - If the name was successfully added or already exists - /// * `Err` - If writers have already been initialized - pub fn add_shmem_name(&mut self, name: String) -> Result<()> { - // Check if the writers have already been initialized - let shmem_writers = self.shmem_writers.lock().unwrap(); - if !shmem_writers.is_empty() { - return Err(anyhow::anyhow!( - "Cannot add shared memory name '{}' after initialization", - name - )); - } - - // Check if the name already exists - if self.shmem_names.contains(&name) { - warn!( - "Shared memory name '{}' already exists in the pipeline. Skipping addition.", - name - ); - return Ok(()); - } - - self.shmem_names.push(name); - Ok(()) + pub fn new(hints_processor: HP, hints_sink: HS, hintin: ZiskHintin) -> Self { + Self { hints_processor, hints_sink, hintin: Mutex::new(hintin) } } /// Set a new ZiskHintin for the pipeline. @@ -89,88 +42,25 @@ impl HintsPipeline { *guard = hintin; } - /// Check if the shared memory writers have been initialized. - fn is_initialized(&self) -> bool { - let shmem_writers = self.shmem_writers.lock().unwrap(); - !shmem_writers.is_empty() - } - - /// Initialize the shared memory writers for the pipeline. - /// - /// This method creates SharedMemoryWriter instances for each shared memory name. - /// If writers are already initialized it logs a warning and does nothing. - fn initialize(&self) { - let mut shmem_writer = self.shmem_writers.lock().unwrap(); - - if !shmem_writer.is_empty() { - warn!( - "SharedMemoryWriters for precompile hints is already initialized at '{}'. Skipping", - self.shmem_names.join(", ") - ); - } else { - debug!( - "Initializing SharedMemoryWriter for precompile hints at '{}'", - self.shmem_names.join(", ") - ); - - *shmem_writer = self - .shmem_names - .iter() - .map(|name| { - SharedMemoryWriter::new( - &name, - Self::MAX_PRECOMPILE_SIZE as usize, - self.unlock_mapped_memory, - ) - .expect("Failed to create SharedMemoryWriter for precompile hints") - }) - .collect(); - } - } - /// Process and write precompile hints to all shared memory writers. /// /// This method: /// 1. Reads hints from the ZiskHintin source /// 2. Processes them using PrecompileHintsProcessor - /// 3. Prepares the data with a length prefix (u64) followed by the processed hints - /// 4. Writes the data to all configured shared memory writers - /// - /// The shared memory writers will be automatically initialized if needed. + /// 3. Submits the processed hints to the HintsSink /// /// # Returns - /// * `Ok(())` - If hints were successfully processed and written - /// * `Err` - If processing or writing fails + /// * `Ok(())` - If hints were successfully processed and submitted + /// * `Err` - If processing or submission fails pub fn write_hints(&self) -> Result<()> { - if !self.is_initialized() { - self.initialize(); - } - let mut hintin = self.hintin.lock().unwrap(); let hints = zisk_common::reinterpret_vec(hintin.read())?; - let processor = PrecompileHintsProcessor::new()?; - let processed = processor.process_hints(&hints)?; + let processed = self.hints_processor.process_hints(&hints)?; info!("Precompile hints have generated {} u64 values", processed.len()); - // Input size includes length prefix as u64 - let shmem_input_size = processed.len() + 1; - - let mut full_input = Vec::with_capacity(shmem_input_size); - // Prefix with length as u64 - full_input.extend_from_slice(&[processed.len() as u64]); - // Append processed hints - full_input.extend_from_slice(&processed); - - println!("full_input size: {}", full_input.len()); - - let shmem_writers = self.shmem_writers.lock().unwrap(); - for shmem_writer in shmem_writers.iter() { - shmem_writer.write_input(&full_input)?; - } - - Ok(()) + self.hints_sink.submit(processed) } } diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs new file mode 100644 index 000000000..3112a1b1e --- /dev/null +++ b/executor/src/hints_shmem.rs @@ -0,0 +1,145 @@ +//! HintsShmem is responsible for writting precompile processed hints to shared memory. +//! +//! It implements the HintsSink trait to receive processed hints and write them to shared memory +//! using SharedMemoryWriter instances. + +use anyhow::Result; +use asm_runner::SharedMemoryWriter; +use std::sync::Mutex; +use tracing::{debug, warn}; +use zisk_hints::HintsSink; + +/// HintsShmem struct manages the writing of processed precompile hints to shared memory. +pub struct HintsShmem { + /// Names of the shared memories to write hints to. + shmem_names: Vec, + + /// Whether to unlock mapped memory after writing. + unlock_mapped_memory: bool, + + /// Shared memory writers for writing processed hints. + shmem_writers: Mutex>, +} + +impl HintsShmem { + const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB + + /// Create a new HintsShmem with the given shared memory names and unlock option. + /// + /// # Arguments + /// * `shmem_names` - A vector of shared memory names to write hints to. + /// * `unlock_mapped_memory` - Whether to unlock mapped memory after writing. + /// + /// # Returns + /// A new `HintsShmem` instance with uninitialized writers. + pub fn new(shmem_names: Vec, unlock_mapped_memory: bool) -> Self { + Self { shmem_names, unlock_mapped_memory, shmem_writers: Mutex::new(Vec::new()) } + } + + /// Add a shared memory name to the pipeline. + /// + /// This method must be called before initialization. + /// + /// # Arguments + /// * `name` - The name of the shared memory to add. + /// + /// # Returns + /// * `Ok(())` - If the name was successfully added or already exists + /// * `Err` - If writers have already been initialized + pub fn add_shmem_name(&mut self, name: String) -> Result<()> { + // Check if the writers have already been initialized + let shmem_writers = self.shmem_writers.lock().unwrap(); + if !shmem_writers.is_empty() { + return Err(anyhow::anyhow!( + "Cannot add shared memory name '{}' after initialization", + name + )); + } + + // Check if the name already exists + if self.shmem_names.contains(&name) { + warn!( + "Shared memory name '{}' already exists in the pipeline. Skipping addition.", + name + ); + return Ok(()); + } + + self.shmem_names.push(name); + Ok(()) + } + + /// Check if the shared memory writers have been initialized. + fn is_initialized(&self) -> bool { + let shmem_writers = self.shmem_writers.lock().unwrap(); + !shmem_writers.is_empty() + } + + /// Initialize the shared memory writers for the pipeline. + /// + /// This method creates SharedMemoryWriter instances for each shared memory name. + /// If writers are already initialized it logs a warning and does nothing. + fn initialize(&self) { + let mut shmem_writer = self.shmem_writers.lock().unwrap(); + + if !shmem_writer.is_empty() { + warn!( + "SharedMemoryWriters for precompile hints is already initialized at '{}'. Skipping", + self.shmem_names.join(", ") + ); + } else { + debug!( + "Initializing SharedMemoryWriter for precompile hints at '{}'", + self.shmem_names.join(", ") + ); + + *shmem_writer = self + .shmem_names + .iter() + .map(|name| { + SharedMemoryWriter::new( + &name, + Self::MAX_PRECOMPILE_SIZE as usize, + self.unlock_mapped_memory, + ) + .expect("Failed to create SharedMemoryWriter for precompile hints") + }) + .collect(); + } + } +} + +impl HintsSink for HintsShmem { + /// Writes processed precompile hints to all shared memory writers. + /// + /// # Arguments + /// * `processed` - A vector of processed precompile hints as u64 values. + /// + /// # Returns + /// * `Ok(())` - If hints were successfully written to all shared memories + /// * `Err` - If writing to any shared memory fails + fn submit(&self, processed: Vec) -> anyhow::Result<()> { + // TODO! Is it necessary???? + if !self.is_initialized() { + self.initialize(); + } + + // Input size includes length prefix as u64 + let shmem_input_size = processed.len() + 1; + + let mut full_input = Vec::with_capacity(shmem_input_size); + // Prefix with length as u64 + full_input.extend_from_slice(&[processed.len() as u64]); + // Append processed hints + full_input.extend_from_slice(&processed); + + println!("full_input size: {}", full_input.len()); + + let shmem_writers = self.shmem_writers.lock().unwrap(); + for shmem_writer in shmem_writers.iter() { + shmem_writer.write_input(&full_input)?; + } + + Ok(()) + } +} diff --git a/executor/src/lib.rs b/executor/src/lib.rs index ab7bbbb67..fcf0f9dc0 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -1,6 +1,7 @@ mod dummy_counter; mod executor; mod hints_pipeline; +mod hints_shmem; mod sm_static_bundle; mod static_data_bus; mod static_data_bus_collect; @@ -8,6 +9,7 @@ mod static_data_bus_collect; pub use dummy_counter::*; pub use executor::*; pub use hints_pipeline::HintsPipeline; +pub use hints_shmem::*; pub use sm_static_bundle::*; pub use static_data_bus::*; pub use static_data_bus_collect::*; diff --git a/hints/src/hints_processor.rs b/hints/src/hints_processor.rs index 161b11e05..fbfde1c33 100644 --- a/hints/src/hints_processor.rs +++ b/hints/src/hints_processor.rs @@ -52,7 +52,7 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use ziskos::syscalls::SyscallPoint256; -use crate::secp256k1_ecdsa_verify; +use crate::{secp256k1_ecdsa_verify, HintsProcessor}; // TODO! COnvert Control Code to an enum and HINT TYPE to an enum as well @@ -77,6 +77,9 @@ pub const HINTS_TYPE_RESULT: u32 = 0x04; /// Hint type indicating that the data contains inputs for the ecrecover precompile. pub const HINTS_TYPE_ECRECOVER: u32 = 0x05; +/// Number if hint types defined. +pub const NUM_HINT_TYPES: u32 = 6; + /// Represents a single precompile hint parsed from a `u64` slice. /// /// A hint consists of a type identifier and associated data. The hint type @@ -180,8 +183,12 @@ impl HintProcessorState { pub struct PrecompileHintsProcessor { /// The thread pool used for parallel hint processing. pool: ThreadPool, + /// Shared state for parallel hint processing state: Arc, + + /// Optional statistics collected during hint processing. + stats: [AtomicUsize; NUM_HINT_TYPES as usize], } impl PrecompileHintsProcessor { @@ -215,7 +222,7 @@ impl PrecompileHintsProcessor { .build() .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; - Ok(Self { pool, state: Arc::new(HintProcessorState::new()) }) + Ok(Self { pool, state: Arc::new(HintProcessorState::new()), stats: Default::default() }) } /// Processes hints in parallel with non-blocking, ordered output. @@ -252,6 +259,8 @@ impl PrecompileHintsProcessor { let hint = PrecompileHint::from_u64_slice(hints, idx)?; let length = hint.data.len(); + self.stats[hint.hint_type as usize].fetch_add(1, Ordering::Relaxed); + // Check if this is a control code or data hint type match hint.hint_type { CTRL_START => { @@ -396,6 +405,11 @@ impl PrecompileHintsProcessor { idx += length + 1; } + println!("Processed hints stats:"); + for (i, count) in self.stats.iter().enumerate() { + println!("Hint type {}: {}", i, count.load(Ordering::Relaxed)); + } + Ok(processed) } @@ -502,6 +516,12 @@ impl PrecompileHintsProcessor { } } +impl HintsProcessor for PrecompileHintsProcessor { + fn process_hints(&self, hints: &[u64]) -> Result> { + self.process_hints(hints) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/hints/src/lib.rs b/hints/src/lib.rs index af1b81935..811c26bfc 100644 --- a/hints/src/lib.rs +++ b/hints/src/lib.rs @@ -10,3 +10,11 @@ pub use hints_processor::{ PrecompileHint, PrecompileHintsProcessor, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT, }; pub use secp256k1::*; + +pub trait HintsProcessor { + fn process_hints(&self, hints: &[u64]) -> anyhow::Result>; +} + +pub trait HintsSink { + fn submit(&self, processed: Vec) -> anyhow::Result<()>; +} From 34f67885993629c13051fe06d71fac26a7669f73 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:11:10 +0000 Subject: [PATCH 031/782] move hints_pipeline to hints crate --- Cargo.lock | 2 ++ executor/src/executor.rs | 3 ++- executor/src/lib.rs | 2 -- hints/Cargo.toml | 4 +++- {executor => hints}/src/hints_pipeline.rs | 2 +- hints/src/lib.rs | 2 ++ 6 files changed, 10 insertions(+), 5 deletions(-) rename {executor => hints}/src/hints_pipeline.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index 0718989d1..5ef0dab50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5722,6 +5722,8 @@ dependencies = [ "lib-c", "precompiles-helpers", "rayon", + "tracing", + "zisk-common", "ziskos", ] diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 3a7054b41..1b7ae04f5 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -36,7 +36,7 @@ use witness::WitnessComponent; use zisk_common::io::{ZiskHintin, ZiskIO, ZiskStdin}; use zisk_hints::PrecompileHintsProcessor; -use crate::{DummyCounter, HintsPipeline, HintsShmem}; +use crate::{DummyCounter, HintsShmem}; use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; use zisk_common::{ @@ -44,6 +44,7 @@ use zisk_common::{ InstanceCtx, InstanceType, Plan, Stats, ZiskExecutionResult, }; use zisk_common::{ChunkId, PayloadType}; +use zisk_hints::HintsPipeline; use zisk_pil::{ RomRomTrace, ZiskPublicValues, INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, ZISK_AIRGROUP_ID, diff --git a/executor/src/lib.rs b/executor/src/lib.rs index fcf0f9dc0..1bfbbad07 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -1,6 +1,5 @@ mod dummy_counter; mod executor; -mod hints_pipeline; mod hints_shmem; mod sm_static_bundle; mod static_data_bus; @@ -8,7 +7,6 @@ mod static_data_bus_collect; pub use dummy_counter::*; pub use executor::*; -pub use hints_pipeline::HintsPipeline; pub use hints_shmem::*; pub use sm_static_bundle::*; pub use static_data_bus::*; diff --git a/hints/Cargo.toml b/hints/Cargo.toml index fec64fc2b..8d1628c45 100644 --- a/hints/Cargo.toml +++ b/hints/Cargo.toml @@ -16,4 +16,6 @@ ziskos = { workspace = true } lib-c = { workspace = true } precompiles-helpers = { workspace = true } anyhow = { workspace = true } -rayon = { workspace = true } \ No newline at end of file +rayon = { workspace = true } +tracing = { workspace = true } +zisk-common = { workspace = true } diff --git a/executor/src/hints_pipeline.rs b/hints/src/hints_pipeline.rs similarity index 98% rename from executor/src/hints_pipeline.rs rename to hints/src/hints_pipeline.rs index 2e240a9da..0e7e4cdb8 100644 --- a/executor/src/hints_pipeline.rs +++ b/hints/src/hints_pipeline.rs @@ -1,11 +1,11 @@ //! HintsPipeline is responsible for processing precompile hints and submitting them to a sink. //! It uses a ZiskHintin as the source of hints, and writes the processed hints to a HintsSink. +use crate::{HintsProcessor, HintsSink}; use anyhow::Result; use std::sync::Mutex; use tracing::info; use zisk_common::io::{ZiskHintin, ZiskIO}; -use zisk_hints::{HintsProcessor, HintsSink}; /// HintsPipeline struct manages the processing of precompile hints and writing them to shared memory. pub struct HintsPipeline { diff --git a/hints/src/lib.rs b/hints/src/lib.rs index 811c26bfc..721940a37 100644 --- a/hints/src/lib.rs +++ b/hints/src/lib.rs @@ -1,11 +1,13 @@ pub mod hints; pub mod hints_definitions; +mod hints_pipeline; mod hints_processor; pub mod secp256k1; pub use hints::*; pub use hints_definitions::*; +pub use hints_pipeline::HintsPipeline; pub use hints_processor::{ PrecompileHint, PrecompileHintsProcessor, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT, }; From 58ed78df9dad5f4c6b278b84477338b21b732c38 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:35:11 +0000 Subject: [PATCH 032/782] added control shared memory --- emulator-asm/asm-runner/src/shmem_utils.rs | 5 ++ executor/src/executor.rs | 21 ++++++- executor/src/hints_shmem.rs | 72 ++++++++++++++-------- 3 files changed, 69 insertions(+), 29 deletions(-) diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index e4cfe623c..f1bc9ea97 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -285,6 +285,11 @@ impl AsmSharedMemory { ) } + /// Shared memory name for precompile hints data control + pub fn shmem_control_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + format!("{}_{}_control", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) + } + pub fn shmem_output_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { format!("{}_{}_output", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 1b7ae04f5..0484d8614 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -195,7 +195,7 @@ impl ZiskExecutor { (None, None) }; - // Generate shared memory names for hints pipeline based on assembly services. + // Generate shared memory names for hints pipeline. let hints_shmem_names = AsmServices::SERVICES .iter() .map(|service| { @@ -211,11 +211,28 @@ impl ZiskExecutor { }) .collect::>(); + // Generate shared memory control names for hints pipeline. + let hints_shmem_control_names = AsmServices::SERVICES + .iter() + .map(|service| { + AsmSharedMemory::::shmem_control_name( + if let Some(base_port) = base_port { + AsmServices::port_for(service, base_port, local_rank) + } else { + AsmServices::default_port(service, local_rank) + }, + *service, + local_rank, + ) + }) + .collect::>(); + // Create hints pipeline with null hintin initially. let hints_processor = PrecompileHintsProcessor::new().expect("Failed to create PrecompileHintsProcessor"); - let hints_shmem = HintsShmem::new(hints_shmem_names, unlock_mapped_memory); + let hints_shmem = + HintsShmem::new(hints_shmem_names, hints_shmem_control_names, unlock_mapped_memory); let hints_pipeline = Mutex::new(HintsPipeline::new(hints_processor, hints_shmem, ZiskHintin::null())); diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index 3112a1b1e..4647f10e8 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -11,28 +11,44 @@ use zisk_hints::HintsSink; /// HintsShmem struct manages the writing of processed precompile hints to shared memory. pub struct HintsShmem { - /// Names of the shared memories to write hints to. - shmem_names: Vec, + /// Names of the shared memories to write hints to. 0 for control, 1 for data + shmem_names: Vec<(String, String)>, /// Whether to unlock mapped memory after writing. unlock_mapped_memory: bool, - /// Shared memory writers for writing processed hints. - shmem_writers: Mutex>, + /// Shared memory writers for writing processed hints. 0 for control, 1 for data + shmem_writers: Mutex>, } impl HintsShmem { + const CONTROL_PRECOMPILE_SIZE: u64 = 0x1000; // 4KB const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB /// Create a new HintsShmem with the given shared memory names and unlock option. /// /// # Arguments + /// * `shmem_control_names` - A vector of shared memory control names to write hints to. /// * `shmem_names` - A vector of shared memory names to write hints to. /// * `unlock_mapped_memory` - Whether to unlock mapped memory after writing. /// /// # Returns /// A new `HintsShmem` instance with uninitialized writers. - pub fn new(shmem_names: Vec, unlock_mapped_memory: bool) -> Self { + pub fn new( + shmem_control_names: Vec, + shmem_names: Vec, + unlock_mapped_memory: bool, + ) -> Self { + assert_eq!( + shmem_control_names.len(), + shmem_names.len(), + "Shared memory names and control names must have the same length" + ); + + // Map names to tuples + let shmem_names: Vec<(String, String)> = + shmem_control_names.into_iter().zip(shmem_names.into_iter()).collect(); + Self { shmem_names, unlock_mapped_memory, shmem_writers: Mutex::new(Vec::new()) } } @@ -41,12 +57,13 @@ impl HintsShmem { /// This method must be called before initialization. /// /// # Arguments + /// * `control_name` - The name of the control shared memory to add. /// * `name` - The name of the shared memory to add. /// /// # Returns /// * `Ok(())` - If the name was successfully added or already exists /// * `Err` - If writers have already been initialized - pub fn add_shmem_name(&mut self, name: String) -> Result<()> { + pub fn add_shmem_name(&mut self, control_name: String, name: String) -> Result<()> { // Check if the writers have already been initialized let shmem_writers = self.shmem_writers.lock().unwrap(); if !shmem_writers.is_empty() { @@ -57,7 +74,7 @@ impl HintsShmem { } // Check if the name already exists - if self.shmem_names.contains(&name) { + if self.shmem_names.contains(&(control_name.clone(), name.clone())) { warn!( "Shared memory name '{}' already exists in the pipeline. Skipping addition.", name @@ -65,7 +82,8 @@ impl HintsShmem { return Ok(()); } - self.shmem_names.push(name); + self.shmem_names.push((control_name, name)); + Ok(()) } @@ -83,26 +101,28 @@ impl HintsShmem { let mut shmem_writer = self.shmem_writers.lock().unwrap(); if !shmem_writer.is_empty() { - warn!( - "SharedMemoryWriters for precompile hints is already initialized at '{}'. Skipping", - self.shmem_names.join(", ") - ); + warn!("SharedMemoryWriters for precompile hints is already initialized. Skipping"); } else { - debug!( - "Initializing SharedMemoryWriter for precompile hints at '{}'", - self.shmem_names.join(", ") - ); + debug!("Initializing SharedMemoryWriter for precompile hints",); *shmem_writer = self .shmem_names .iter() - .map(|name| { - SharedMemoryWriter::new( - &name, - Self::MAX_PRECOMPILE_SIZE as usize, - self.unlock_mapped_memory, + .map(|(control_name, name)| { + ( + SharedMemoryWriter::new( + &control_name, + Self::CONTROL_PRECOMPILE_SIZE as usize, + self.unlock_mapped_memory, + ) + .expect("Failed to create SharedMemoryWriter for precompile hints"), + SharedMemoryWriter::new( + &name, + Self::MAX_PRECOMPILE_SIZE as usize, + self.unlock_mapped_memory, + ) + .expect("Failed to create SharedMemoryWriter for precompile hints"), ) - .expect("Failed to create SharedMemoryWriter for precompile hints") }) .collect(); } @@ -125,19 +145,17 @@ impl HintsSink for HintsShmem { } // Input size includes length prefix as u64 - let shmem_input_size = processed.len() + 1; + let shmem_input_size = processed.len(); let mut full_input = Vec::with_capacity(shmem_input_size); - // Prefix with length as u64 - full_input.extend_from_slice(&[processed.len() as u64]); - // Append processed hints full_input.extend_from_slice(&processed); println!("full_input size: {}", full_input.len()); let shmem_writers = self.shmem_writers.lock().unwrap(); for shmem_writer in shmem_writers.iter() { - shmem_writer.write_input(&full_input)?; + shmem_writer.1.write_input(&full_input)?; + shmem_writer.0.write_input(&[processed.len() as u64])?; } Ok(()) From efcb319568e25af8d9a7e82a41a0dc327470fb1f Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 4 Dec 2025 17:54:01 +0100 Subject: [PATCH 033/782] Control shared memory --- core/src/zisk_rom_2_asm.rs | 121 +++++++-- emulator-asm/src/main.c | 490 +++++++++++++++++++++++++++++++++++-- 2 files changed, 560 insertions(+), 51 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 64ee8c7b5..b5fcd8d20 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -121,6 +121,8 @@ pub struct ZiskAsmContext { mem_rsp: String, // Backup of rsp register value from caller mem_free_input: String, // Free input address (0x90000000) used in free call operations mem_precompile_results_address: String, // Address where precompile results are read from + mem_precompile_written_address: String, // Address where precompile written counter is stored + mem_precompile_read_address: String, // Address where precompile read counter is stored comments: bool, // true if we want to generate comments in the assembly source code boc: String, // begin of comment: '/*', ';', '#', etc. @@ -219,7 +221,7 @@ impl ZiskAsmContext { self.precompile_results } pub fn precompile_results_keccak(&self) -> bool { - self.precompile_results() && false + self.precompile_results() && true } pub fn precompile_results_sha256(&self) -> bool { self.precompile_results() && false @@ -275,6 +277,9 @@ impl ZiskAsmContext { pub fn precompile_results_add256(&self) -> bool { self.precompile_results() && false } + pub fn call_wait_for_prec_avail(&self) -> bool { + self.precompile_results() && false + } } // One-pass (single emulation) memory trace, used to count, plan and collect. @@ -501,6 +506,8 @@ impl ZiskRom2Asm { ctx.mem_free_input = format!("qword {}[MEM_FREE_INPUT]", ctx.ptr); ctx.mem_precompile_results_address = format!("qword {}[MEM_PRECOMPILE_RESULTS_ADDRESS]", ctx.ptr); + ctx.mem_precompile_written_address = format!("qword {}[0x70000000]", ctx.ptr); + ctx.mem_precompile_read_address = format!("qword {}[0x70000008]", ctx.ptr); // Preamble *code += ".intel_syntax noprefix\n"; @@ -589,7 +596,8 @@ impl ZiskRom2Asm { *code += ".extern chunk_done\n"; *code += ".extern print_fcall_ctx\n"; *code += ".extern print_pc\n"; - *code += ".extern realloc_trace\n\n"; + *code += ".extern realloc_trace\n"; + *code += ".extern wait_for_prec_avail\n\n"; if ctx.minimal_trace() || ctx.main_trace() @@ -604,7 +612,9 @@ impl ZiskRom2Asm { *code += ".extern trace_address\n\n"; *code += ".extern trace_address_threshold\n\n"; if ctx.precompile_results() { - *code += ".extern precompile_result_address\n\n"; + *code += ".extern precompile_result_address\n"; + *code += ".extern precompile_written_address\n"; + *code += ".extern precompile_read_address\n\n"; } } @@ -792,14 +802,17 @@ impl ZiskRom2Asm { REG_AUX, ctx.comment_str("aux = precompile_results_address") ); - *code += &format!("\tadd {}, 8 {}\n", REG_AUX, ctx.comment_str("aux += 8")); *code += &format!( "\tmov {}, {} {}\n", ctx.mem_precompile_results_address, REG_AUX, - ctx.comment_str( - "mem_precompile_results_counter = precompile_results_address + " - ) + ctx.comment_str("mem_precompile_results_counter = precompile_results_address") + ); + + *code += &format!( + "\tmov {}, 0 {}\n", + ctx.mem_precompile_read_address, + ctx.comment_str("precompile_read = 0") ); } } @@ -5098,7 +5111,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_keccak() { - Self::precompile_results_array(ctx, code, "rdi", 25); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 25); } else { // Call the keccak function Self::push_internal_registers(ctx, code, false); @@ -5178,7 +5191,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_sha256() { *code += &format!("\tmov rdi, [rdi]\n"); - Self::precompile_results_array(ctx, code, "rdi", 4); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 4); } else { // Call the SHA256 function Self::push_internal_registers(ctx, code, false); @@ -5290,9 +5303,9 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_arith256() { *code += &format!("\tmov {REG_FLAG}, [rdi+3*8]\n"); - Self::precompile_results_array(ctx, code, REG_FLAG, 4); + Self::precompile_results_array(ctx, code, unusual_code, REG_FLAG, 4); *code += &format!("\tmov {REG_FLAG}, [rdi+4*8]\n"); - Self::precompile_results_array(ctx, code, REG_FLAG, 4); + Self::precompile_results_array(ctx, code, unusual_code, REG_FLAG, 4); *code += &format!("\tmov {REG_FLAG}, 0\n"); // Is this needed? } else { // Call the arith256 function @@ -5351,7 +5364,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_arith256mod() { *code += &format!("\tmov rdi, [rdi + 4*8]\n"); - Self::precompile_results_array(ctx, code, "rdi", 4); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 4); } else { // Call the arith256_mod function Self::push_internal_registers(ctx, code, false); @@ -5432,7 +5445,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_secp256k1add() { *code += &format!("\tmov rdi, [rdi]\n"); - Self::precompile_results_array(ctx, code, "rdi", 8); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the secp256k1_add function Self::push_internal_registers(ctx, code, false); @@ -5537,7 +5550,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_secp256k1dbl() { - Self::precompile_results_array(ctx, code, "rdi", 8); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the secp256k1_dbl function Self::push_internal_registers(ctx, code, false); @@ -5839,7 +5852,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_bn254curveadd() { *code += &format!("\tmov rdi, [rdi]\n"); - Self::precompile_results_array(ctx, code, "rdi", 8); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the bn254_curve_add function Self::push_internal_registers(ctx, code, false); @@ -5945,7 +5958,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_bn254curvedbl() { - Self::precompile_results_array(ctx, code, "rdi", 8); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the bn254_curve_dbl function Self::push_internal_registers(ctx, code, false); @@ -6027,7 +6040,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_bn254complexadd() { *code += &format!("\tmov rdi, [rdi]\n"); - Self::precompile_results_array(ctx, code, "rdi", 8); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the bn254_complex_add function Self::push_internal_registers(ctx, code, false); @@ -6109,7 +6122,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_bn254complexsub() { *code += &format!("\tmov rdi, [rdi]\n"); - Self::precompile_results_array(ctx, code, "rdi", 8); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the bn254_complex_sub function Self::push_internal_registers(ctx, code, false); @@ -6191,7 +6204,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_bn254complexmul() { *code += &format!("\tmov rdi, [rdi]\n"); - Self::precompile_results_array(ctx, code, "rdi", 8); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the bn254_complex_mul function Self::push_internal_registers(ctx, code, false); @@ -6281,7 +6294,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_arith384mod() { *code += &format!("\tmov rdi, [rdi + 4*8]\n"); - Self::precompile_results_array(ctx, code, "rdi", 6); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 6); } else { // Call the arith384_mod function Self::push_internal_registers(ctx, code, false); @@ -6363,7 +6376,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_bls12_381curveadd() { *code += &format!("\tmov rdi, [rdi]\n"); - Self::precompile_results_array(ctx, code, "rdi", 12); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 12); } else { // Call the bls12_381_curve_add function Self::push_internal_registers(ctx, code, false); @@ -6469,7 +6482,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_bls12_381curvedbl() { - Self::precompile_results_array(ctx, code, "rdi", 12); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 12); } else { // Call the bls12_381_curve_dbl function Self::push_internal_registers(ctx, code, false); @@ -6555,7 +6568,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_bls12_381complexadd() { *code += &format!("\tmov rdi, [rdi]\n"); - Self::precompile_results_array(ctx, code, "rdi", 12); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 12); } else { // Call the bls12_381_complex_add function Self::push_internal_registers(ctx, code, false); @@ -6641,7 +6654,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_bls12_381complexsub() { *code += &format!("\tmov rdi, [rdi]\n"); - Self::precompile_results_array(ctx, code, "rdi", 12); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 12); } else { // Call the bls12_381_complex_sub function Self::push_internal_registers(ctx, code, false); @@ -6727,7 +6740,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_bls12_381complexmul() { *code += &format!("\tmov rdi, [rdi]\n"); - Self::precompile_results_array(ctx, code, "rdi", 12); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 12); } else { // Call the bls12_381_complex_mul function Self::push_internal_registers(ctx, code, false); @@ -6831,7 +6844,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_add256() { *code += &format!("\tmov rdi, [rdi+3*8]\n"); - Self::precompile_results_array(ctx, code, "rdi", 4); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 4); Self::precompile_results_register(ctx, code, REG_C); } else { // Call the add256 function @@ -7873,9 +7886,14 @@ impl ZiskRom2Asm { fn precompile_results_array( ctx: &mut ZiskAsmContext, code: &mut String, + unusual_code: &mut String, reg_address: &str, size: u64, ) { + if ctx.call_wait_for_prec_avail() { + Self::wait_for_prec_avail(ctx, code, unusual_code); + } + *code += &format!( "\tmov {}, {} {}\n", REG_AUX, @@ -7910,6 +7928,14 @@ impl ZiskRom2Asm { REG_AUX, ctx.comment_str("precompile_results_address = aux") ); + if ctx.call_wait_for_prec_avail() { + *code += &format!( + "\tadd {}, {}*8 {}\n", + ctx.mem_precompile_read_address, + size, + ctx.comment(format!("read += {}*8", size)) + ); + } } // Copies 1 u64 element from precompile_results_address to the register reg, @@ -8000,6 +8026,49 @@ impl ZiskRom2Asm { ); } + fn wait_for_prec_avail(ctx: &mut ZiskAsmContext, code: &mut String, unusual_code: &mut String) { + *code += &ctx.full_line_comment("Wait for precompile results available".to_string()); + + // if *precompile_written_address == *precompile_read_address -> call wait_for_prec_avail + *code += &format!( + "\tmov {}, {} {}\n", + REG_AUX, + ctx.mem_precompile_written_address, + ctx.comment_str("aux = precompile_written") + ); + *code += &format!( + "\tmov {}, {} {}\n", + REG_VALUE, + ctx.mem_precompile_read_address, + ctx.comment_str("value = precompile_read") + ); + *code += &format!( + "\tcmp {}, {} {}\n", + REG_AUX, + ctx.mem_precompile_read_address, + ctx.comment_str("written ?= read") + ); + *code += &format!( + "\tjz pc_{:x}_wait_for_prec_avail {}\n", + ctx.pc, + ctx.comment_str("if there is data, done") + ); + *code += &format!("pc_{:x}_wait_for_prec_avail_done:\n", ctx.pc,); + + // Call wait_for_prec_avail() + *unusual_code += &format!("pc_{:x}_wait_for_prec_avail:\n", ctx.pc,); + Self::push_internal_registers(ctx, unusual_code, false); + *unusual_code += "\tcall _wait_for_prec_avail\n"; // TODO: handle error -1 ret + Self::pop_internal_registers(ctx, unusual_code, false); + + *unusual_code += &format!("\tjmp pc_{:x}_wait_for_prec_avail_done\n", ctx.pc,); + + // TODO: + // else if *precompile_written_address - *precompile_read_address < threshold -> call post_prec_read + + //*code += &format!("pc_{:x}_wait_for_prec_avail_end:\n", ctx.pc,); + } + /*******************/ /* CHUNK START/END */ /*******************/ diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 399d39a9b..fea84a167 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -46,8 +46,8 @@ uint64_t get_precompile_results(void); #define TRACE_ADDR (uint64_t)0xc0000000 #define INITIAL_TRACE_SIZE (uint64_t)0x100000000 // 4GB -#define REG_ADDR (uint64_t)0x70000000 -#define REG_SIZE (uint64_t)0x1000 // 4kB +#define CONTROL_ADDR (uint64_t)0x70000000 +#define CONTROL_SIZE (uint64_t)0x1000 // 4kB uint8_t * pInput = (uint8_t *)INPUT_ADDR; uint8_t * pInputLast = (uint8_t *)(INPUT_ADDR + 10440504 - 64); @@ -318,13 +318,27 @@ uint64_t input_size = 0; #define MAX_PRECOMPILE_SIZE (uint64_t)0x10000000 // 256MB -// Precompile results memory +// Precompile results shared memory char shmem_precompile_name[128]; int shmem_precompile_fd = -1; uint64_t shmem_precompile_size = 0; void * shmem_precompile_address = NULL; -char precompile_file[4096] = {0}; +// Precompile results semaphores +char sem_prec_avail_name[128]; +sem_t * sem_prec_avail = NULL; +char sem_prec_read_name[128]; +sem_t * sem_prec_read = NULL; + +// Precompile results file name (used by client) +char precompile_file_name[4096] = {0}; + +// Control shared memory +char shmem_control_name[128]; +int shmem_control_fd = -1; +uint64_t * shmem_control_address = NULL; +uint64_t * precompile_written_address = NULL; +uint64_t * precompile_read_address = NULL; int main(int argc, char *argv[]) { @@ -1202,7 +1216,7 @@ void parse_arguments(int argc, char *argv[]) print_usage(); exit(-1); } - strcpy(precompile_file, argv[i]); + strcpy(precompile_file_name, argv[i]); continue; } printf("ERROR: parse_arguments() Unrecognized argument: %s\n", argv[i]); @@ -1265,7 +1279,7 @@ void parse_arguments(int argc, char *argv[]) exit(-1); } - if (precompile_results_enabled && client && (strlen(precompile_file) == 0)) + if (precompile_results_enabled && client && (strlen(precompile_file_name) == 0)) { printf("ERROR! parse_arguments() when in precompile results mode, you need to provide a precompile results file using -r \n"); print_usage(); @@ -1282,16 +1296,24 @@ void configure (void) { case Fast: { + strcpy(shmem_control_name, shm_prefix); + strcat(shmem_control_name, "_FT_control"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_FT_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); strcat(shmem_precompile_name, "_FT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_FT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_FT_prec_read"); } else { strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); } strcpy(shmem_output_name, ""); strcpy(sem_chunk_done_name, ""); @@ -1308,16 +1330,24 @@ void configure (void) } case MinimalTrace: { + strcpy(shmem_control_name, shm_prefix); + strcat(shmem_control_name, "_MT_control"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MT_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); strcat(shmem_precompile_name, "_MT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MT_prec_read"); } else { strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); @@ -1337,16 +1367,24 @@ void configure (void) } case RomHistogram: { + strcpy(shmem_control_name, shm_prefix); + strcat(shmem_control_name, "_RH_control"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_RH_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); strcat(shmem_precompile_name, "_RH_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_RH_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_RH_prec_read"); } else { strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_RH_output"); @@ -1366,16 +1404,24 @@ void configure (void) } case MainTrace: { + strcpy(shmem_control_name, shm_prefix); + strcat(shmem_control_name, "_MA_control"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MA_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); strcat(shmem_precompile_name, "_MA_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MA_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MA_prec_read"); } else { strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MA_output"); @@ -1395,9 +1441,13 @@ void configure (void) } case ChunksOnly: { + strcpy(shmem_control_name, shm_prefix); + strcat(shmem_control_name, "_CH_control"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_CH_input"); strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CH_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -1425,16 +1475,24 @@ void configure (void) // } case Zip: { + strcpy(shmem_control_name, shm_prefix); + strcat(shmem_control_name, "_ZP_control"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_ZP_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); strcat(shmem_precompile_name, "_ZP_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_ZP_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_ZP_prec_read"); } else { strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_ZP_output"); @@ -1454,16 +1512,24 @@ void configure (void) } case MemOp: { + strcpy(shmem_control_name, shm_prefix); + strcat(shmem_control_name, "_MO_control"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MO_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); strcat(shmem_precompile_name, "_MO_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MO_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MO_prec_read"); } else { strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MO_output"); @@ -1483,8 +1549,12 @@ void configure (void) } case ChunkPlayerMTCollectMem: { + strcpy(shmem_control_name, shm_prefix); + strcat(shmem_control_name, "_CM_control"); strcpy(shmem_input_name, ""); strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CM_output"); strcpy(sem_chunk_done_name, ""); @@ -1502,16 +1572,24 @@ void configure (void) } case MemReads: { + strcpy(shmem_control_name, shm_prefix); + strcat(shmem_control_name, "_MT_control"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MT_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); strcat(shmem_precompile_name, "_MT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MT_prec_read"); } else { strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); @@ -1531,8 +1609,12 @@ void configure (void) } case ChunkPlayerMemReadsCollectMain: { + strcpy(shmem_control_name, shm_prefix); + strcat(shmem_control_name, "_CA_control"); strcpy(shmem_input_name, ""); strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CA_output"); strcpy(sem_chunk_done_name, ""); @@ -1572,13 +1654,18 @@ void configure (void) printf("\tport=%u\n", port); printf("\tcall_chunk_done=%u\n", call_chunk_done); printf("\tchunk_size=%lu\n", chunk_size); + printf("\tshmem_control=%s\n", shmem_control_name); printf("\tshmem_input=%s\n", shmem_input_name); + printf("\tshmem_precompile=%s\n", shmem_precompile_name); printf("\tshmem_output=%s\n", shmem_output_name); printf("\tshmem_mt=%s\n", shmem_mt_name); printf("\tsem_chunk_done=%s\n", sem_chunk_done_name); printf("\tsem_shutdown_done=%s\n", sem_shutdown_done_name); + printf("\tsem_prec_avail=%s\n", sem_prec_avail_name); + printf("\tsem_prec_read=%s\n", sem_prec_read_name); printf("\tmap_locked_flag=%d\n", map_locked_flag); printf("\toutput=%u\n", output); + printf("\tprecompile_results_enabled=%u\n", precompile_results_enabled); } } @@ -1600,7 +1687,7 @@ void client_setup (void) shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); if (shmem_mt_fd < 0) { - printf("ERROR: Failed calling shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); + printf("ERROR: Failed calling trace shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -1631,6 +1718,134 @@ void client_setup (void) } if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); } + + /**********************/ + /* PRECOMPILE_RESULTS */ + /**********************/ + + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + // Make sure the precompile results shared memory is deleted + //shm_unlink(shmem_precompile_name); + + // Create the precompile results shared memory + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR | O_CREAT, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_precompile_fd, MAX_PRECOMPILE_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pPrecompile == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + precompile_results_address = (uint64_t *)pPrecompile; + if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); + + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_name); + + // Create the control shared memory + shmem_control_fd = shm_open(shmem_control_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_control_fd, CONTROL_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_ADDR, CONTROL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_ADDR) + { + printf("ERROR: Called mmap(control) but returned address = %p != 0x%08lx\n", pControl, CONTROL_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_address[0]; + precompile_read_address = &shmem_control_address[1]; + if (verbose) printf("mmap(control) mapped %lu B and returned address %p in %lu us\n", CONTROL_SIZE, shmem_control_address, duration); + + // Create the semaphore for precompile results available signal + assert(strlen(sem_prec_avail_name) > 0); + + sem_unlink(sem_prec_avail_name); + + sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_avail == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_avail_name); + + // Create the semaphore for precompile results read signal + assert(strlen(sem_prec_read_name) > 0); + + sem_unlink(sem_prec_read_name); + + sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_read == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_read_name); + } } void client_run (void) @@ -1699,7 +1914,7 @@ void client_run (void) shmem_input_fd = shm_open(shmem_input_name, O_RDWR, 0666); if (shmem_input_fd < 0) { - printf("ERROR: Failed calling shm_open(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + printf("ERROR: Failed calling input shm_open(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -1761,10 +1976,10 @@ void client_run (void) #endif // Open input file - FILE * precompile_fp = fopen(precompile_file, "r"); + FILE * precompile_fp = fopen(precompile_file_name, "r"); if (precompile_fp == NULL) { - printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", precompile_file, errno, strerror(errno)); + printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", precompile_file_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -1773,7 +1988,7 @@ void client_run (void) // Get input file size if (fseek(precompile_fp, 0, SEEK_END) == -1) { - printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", precompile_file, errno, strerror(errno)); + printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -1781,7 +1996,7 @@ void client_run (void) long precompile_data_size = ftell(precompile_fp); if (precompile_data_size == -1) { - printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", precompile_file, errno, strerror(errno)); + printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -1790,7 +2005,7 @@ void client_run (void) // Go back to the first byte if (fseek(precompile_fp, 0, SEEK_SET) == -1) { - printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", precompile_file, errno, strerror(errno)); + printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -1799,7 +2014,7 @@ void client_run (void) // Check the input data size is inside the proper range if (precompile_data_size > (MAX_PRECOMPILE_SIZE - 8)) { - printf("ERROR: Size of precompile file (%s) is too long (%lu)\n", precompile_file, precompile_data_size); + printf("ERROR: Size of precompile file (%s) is too long (%lu)\n", precompile_file_name, precompile_data_size); fflush(stdout); fflush(stderr); exit(-1); @@ -1809,7 +2024,7 @@ void client_run (void) shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); if (shmem_precompile_fd < 0) { - printf("ERROR: Failed calling shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -1825,11 +2040,8 @@ void client_run (void) exit(-1); } - // Write the input size in the first 64 bits - *(uint64_t *)shmem_precompile_address = (uint64_t)precompile_data_size; - // Copy input data into input memory - size_t precompile_read = fread(shmem_precompile_address + 8, 1, precompile_data_size, precompile_fp); + size_t precompile_read = fread(shmem_precompile_address, 1, precompile_data_size, precompile_fp); if (precompile_read != precompile_data_size) { printf("ERROR: Input read (%lu) != input file size (%lu)\n", precompile_read, precompile_data_size); @@ -1851,6 +2063,39 @@ void client_run (void) exit(-1); } + // Open control shared memory + shmem_control_fd = shm_open(shmem_control_name, O_RDWR, 0666); + if (shmem_control_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map the shared memory object into the process address space + shmem_control_address = mmap(NULL, CONTROL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmem_control_fd, 0); + if (shmem_control_address == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Write the precompile size in the first 64 bits + *(uint64_t *)shmem_control_address = (uint64_t)precompile_data_size; + + // Unmap control + result = munmap(shmem_control_address, CONTROL_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + #ifdef DEBUG gettimeofday(&stop_time, NULL); duration = TimeDiff(start_time, stop_time); @@ -2738,7 +2983,7 @@ void server_setup (void) shmem_input_fd = shm_open(shmem_input_name, O_RDWR | O_CREAT | O_EXCL, 0666); if (shmem_input_fd < 0) { - printf("ERROR: Failed calling shm_open(%s) as read-write errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + printf("ERROR: Failed calling input RW shm_open(%s) as read-write errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -2770,7 +3015,7 @@ void server_setup (void) shmem_input_fd = shm_open(shmem_input_name, O_RDONLY | O_EXCL, 0666); if (shmem_input_fd < 0) { - printf("ERROR: Failed calling shm_open(%s) as read-only errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + printf("ERROR: Failed calling input RO shm_open(%s) as read-only errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -2814,7 +3059,7 @@ void server_setup (void) shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR | O_CREAT, 0666); if (shmem_precompile_fd < 0) { - printf("ERROR: Failed calling shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -2830,9 +3075,31 @@ void server_setup (void) exit(-1); } + // Sync + fsync(shmem_precompile_fd); + + // Close the descriptor + if (close(shmem_precompile_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Open the precompile shared memory as read-only + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDONLY | O_EXCL, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + // Map precompile address space if (verbose) gettimeofday(&start_time, NULL); - void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); + void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); if (verbose) { gettimeofday(&stop_time, NULL); @@ -2845,8 +3112,89 @@ void server_setup (void) fflush(stderr); exit(-1); } + shmem_precompile_address = (void *)pPrecompile; precompile_results_address = (uint64_t *)pPrecompile; if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); + + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_name); + + // Create the control shared memory + shmem_control_fd = shm_open(shmem_control_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_control_fd, CONTROL_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_ADDR, CONTROL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_ADDR) + { + printf("ERROR: Called mmap(control) but returned address = %p != 0x%08lx\n", pControl, CONTROL_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_address[0]; + precompile_read_address = &shmem_control_address[1]; + if (verbose) printf("mmap(control) mapped %lu B and returned address %p in %lu us\n", CONTROL_SIZE, shmem_control_address, duration); + + // Create the semaphore for precompile results available signal + assert(strlen(sem_prec_avail_name) > 0); + + sem_unlink(sem_prec_avail_name); + + sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_avail == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_avail_name); + + // Create the semaphore for precompile results read signal + assert(strlen(sem_prec_read_name) > 0); + + sem_unlink(sem_prec_read_name); + + sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_read == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_read_name); } /*******/ @@ -2920,7 +3268,7 @@ void server_setup (void) shmem_output_fd = shm_open(shmem_output_name, O_RDWR | O_CREAT | O_EXCL, 0666); if (shmem_output_fd < 0) { - printf("ERROR: Failed calling shm_open(%s) errno=%d=%s\n", shmem_output_name, errno, strerror(errno)); + printf("ERROR: Failed calling trace shm_open(%s) errno=%d=%s\n", shmem_output_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -2992,7 +3340,7 @@ void server_setup (void) shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); if (shmem_mt_fd < 0) { - printf("ERROR: Failed calling shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); + printf("ERROR: Failed calling mt shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -3123,6 +3471,14 @@ void server_run (void) exit(-1); } + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + /*******/ /* ASM */ /*******/ @@ -3300,6 +3656,55 @@ void server_cleanup (void) printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); } + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + // Cleanup PRECOMPILE + result = munmap((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_precompile_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + } + + // Cleanup CONTROL + result = munmap((void *)shmem_control_address, CONTROL_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); + } + + // Semaphores cleanup + result = sem_close(sem_prec_avail); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + } + result = sem_unlink(sem_prec_avail_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + } + result = sem_close(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + } + result = sem_unlink(sem_prec_read_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + } + } + // Cleanup trace result = munmap((void *)TRACE_ADDR, trace_size); if (result == -1) @@ -4199,4 +4604,39 @@ void file_lock(void) fflush(stderr); exit(1); } +} + +int _wait_for_prec_avail (void) +{ + // Tell the writer that we have read the precompile results + sem_post(sem_prec_read); + + // Check if there are precompile results available + sem_trywait(sem_prec_avail); + if (*precompile_written_address > *precompile_read_address) + { + return 0; + } + + // Check again, but blocking this time + int result = sem_wait(sem_prec_avail); + if (result == -1) + { + printf("ERROR: wait_for_prec_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (*precompile_written_address == *precompile_read_address) + { + printf("ERROR: wait_for_prec_avail() found written=%lu == read=%lu\n", *precompile_written_address, *precompile_read_address); + return -1; + } + + return 0; +} + +void post_prec_read (void) +{ + sem_post(sem_prec_read); } \ No newline at end of file From 65dbae51ed25132d1f3d1545da55c2f3af2efb2a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:34:14 +0000 Subject: [PATCH 034/782] minor fix --- executor/src/executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 0484d8614..3d47a20e5 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -232,7 +232,7 @@ impl ZiskExecutor { PrecompileHintsProcessor::new().expect("Failed to create PrecompileHintsProcessor"); let hints_shmem = - HintsShmem::new(hints_shmem_names, hints_shmem_control_names, unlock_mapped_memory); + HintsShmem::new(hints_shmem_control_names, hints_shmem_names, unlock_mapped_memory); let hints_pipeline = Mutex::new(HintsPipeline::new(hints_processor, hints_shmem, ZiskHintin::null())); From b1cb525b17127a219f0689359544ddaefa000de2 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:34:52 +0000 Subject: [PATCH 035/782] remove println --- executor/src/hints_shmem.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index 4647f10e8..b6091ed0a 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -150,8 +150,6 @@ impl HintsSink for HintsShmem { let mut full_input = Vec::with_capacity(shmem_input_size); full_input.extend_from_slice(&processed); - println!("full_input size: {}", full_input.len()); - let shmem_writers = self.shmem_writers.lock().unwrap(); for shmem_writer in shmem_writers.iter() { shmem_writer.1.write_input(&full_input)?; From d4140333839821d51617d949ea0d873ddb34e0de Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 5 Dec 2025 12:03:27 +0100 Subject: [PATCH 036/782] Fix rom histogram precompile results --- core/src/zisk_rom_2_asm.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 7e3871bda..8df8d3c9f 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -797,25 +797,25 @@ impl ZiskRom2Asm { ctx.mem_chunk_start_step, ctx.comment_str("chunk_start_step = 0") ); - if ctx.precompile_results() { - *code += &format!( - "\tmov {}, precompile_results_address {}\n", - REG_AUX, - ctx.comment_str("aux = precompile_results_address") - ); - *code += &format!( - "\tmov {}, {} {}\n", - ctx.mem_precompile_results_address, - REG_AUX, - ctx.comment_str("mem_precompile_results_counter = precompile_results_address") - ); + } + if ctx.precompile_results() { + *code += &format!( + "\tmov {}, precompile_results_address {}\n", + REG_AUX, + ctx.comment_str("aux = precompile_results_address") + ); + *code += &format!( + "\tmov {}, {} {}\n", + ctx.mem_precompile_results_address, + REG_AUX, + ctx.comment_str("mem_precompile_results_counter = precompile_results_address") + ); - *code += &format!( - "\tmov {}, 0 {}\n", - ctx.mem_precompile_read_address, - ctx.comment_str("precompile_read = 0") - ); - } + *code += &format!( + "\tmov {}, 0 {}\n", + ctx.mem_precompile_read_address, + ctx.comment_str("precompile_read = 0") + ); } *code += &ctx.full_line_comment("fcall_context initialization".to_string()); From ab043dea8692b381157c01b05c3b08d87260c0b9 Mon Sep 17 00:00:00 2001 From: fractasy Date: Sat, 6 Dec 2025 19:17:29 +0100 Subject: [PATCH 037/782] Enable wait_for_prec_avail() in assembly --- core/src/zisk_rom_2_asm.rs | 7 ++-- emulator-asm/src/main.c | 65 ++++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 8df8d3c9f..23d4e3cb8 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -279,7 +279,7 @@ impl ZiskAsmContext { self.precompile_results() && false } pub fn call_wait_for_prec_avail(&self) -> bool { - self.precompile_results() && false + self.precompile_results() && true } } @@ -8059,7 +8059,10 @@ impl ZiskRom2Asm { // Call wait_for_prec_avail() *unusual_code += &format!("pc_{:x}_wait_for_prec_avail:\n", ctx.pc,); Self::push_internal_registers(ctx, unusual_code, false); - *unusual_code += "\tcall _wait_for_prec_avail\n"; // TODO: handle error -1 ret + *unusual_code += "\tcall _wait_for_prec_avail\n"; + *unusual_code += "\tcmp rax, 0\n"; + *unusual_code += "\tjne execute_end\n"; + Self::pop_internal_registers(ctx, unusual_code, false); *unusual_code += &format!("\tjmp pc_{:x}_wait_for_prec_avail_done\n", ctx.pc,); diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index fea84a167..982bcec17 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -1729,7 +1729,7 @@ void client_setup (void) //shm_unlink(shmem_precompile_name); // Create the precompile results shared memory - shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR | O_CREAT, 0666); + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); if (shmem_precompile_fd < 0) { printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); @@ -1738,16 +1738,6 @@ void client_setup (void) exit(-1); } - // Size it - result = ftruncate(shmem_precompile_fd, MAX_PRECOMPILE_SIZE); - if (result != 0) - { - printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - // Map precompile address space if (verbose) gettimeofday(&start_time, NULL); void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); @@ -1766,11 +1756,8 @@ void client_setup (void) precompile_results_address = (uint64_t *)pPrecompile; if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); - // Make sure the precompile results shared memory is deleted - shm_unlink(shmem_control_name); - // Create the control shared memory - shmem_control_fd = shm_open(shmem_control_name, O_RDWR | O_CREAT, 0666); + shmem_control_fd = shm_open(shmem_control_name, O_RDWR, 0666); if (shmem_control_fd < 0) { printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); @@ -1779,16 +1766,6 @@ void client_setup (void) exit(-1); } - // Size it - result = ftruncate(shmem_control_fd, CONTROL_SIZE); - if (result != 0) - { - printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - // Map precompile address space if (verbose) gettimeofday(&start_time, NULL); void * pControl = mmap((void *)CONTROL_ADDR, CONTROL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_fd, 0); @@ -4611,14 +4588,31 @@ int _wait_for_prec_avail (void) // Tell the writer that we have read the precompile results sem_post(sem_prec_read); - // Check if there are precompile results available + // Make sure the semaphore is reset before checking the condition sem_trywait(sem_prec_avail); + + // Sync precompile shared memory + if (msync((void *)shmem_control_address, CONTROL_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check if there are precompile results available if (*precompile_written_address > *precompile_read_address) { + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } return 0; } - // Check again, but blocking this time + // Wait again, but blocking this time int result = sem_wait(sem_prec_avail); if (result == -1) { @@ -4627,12 +4621,29 @@ int _wait_for_prec_avail (void) fflush(stderr); exit(-1); } + + // Sync precompile shared memory + if (msync((void *)shmem_control_address, CONTROL_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (*precompile_written_address == *precompile_read_address) { printf("ERROR: wait_for_prec_avail() found written=%lu == read=%lu\n", *precompile_written_address, *precompile_read_address); return -1; } + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + return 0; } From ba7eac3c7ca28beaafebb168c6bfdf7e023fd0fb Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 9 Dec 2025 15:23:43 +0000 Subject: [PATCH 038/782] minor fix --- cli/src/commands/execute.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 5d6d41058..122f04fc2 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -98,7 +98,7 @@ impl ZiskExecute { let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; let result = if emulator { - self.run_emu(stdin, Some(hintin))? + self.run_emu(stdin, None)? } else { self.run_asm(stdin, Some(hintin))? }; From 941d573018bc793cce9bfdb2a83c01067024d01c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:30:19 +0000 Subject: [PATCH 039/782] wip stream source --- cli/src/commands/execute.rs | 19 +++--- common/src/io/file_hintin.rs | 52 ----------------- common/src/io/mod.rs | 6 +- common/src/io/null_hintin.rs | 19 ------ common/src/io/stream/file.rs | 51 ++++++++++++++++ common/src/io/stream/mod.rs | 9 +++ common/src/io/stream/null.rs | 31 ++++++++++ common/src/io/stream/stream_reader.rs | 69 ++++++++++++++++++++++ common/src/io/stream/stream_writer.rs | 22 +++++++ common/src/io/zisk_hintin.rs | 84 --------------------------- common/src/zisk_lib_init.rs | 4 +- executor/src/executor.rs | 6 +- hints/src/hints_pipeline.rs | 19 ++++-- sdk/src/prover/asm.rs | 4 +- sdk/src/prover/backend.rs | 4 +- sdk/src/prover/emu.rs | 4 +- sdk/src/prover/mod.rs | 6 +- witness-computation/src/zisk_lib.rs | 2 +- 18 files changed, 221 insertions(+), 190 deletions(-) delete mode 100644 common/src/io/file_hintin.rs delete mode 100644 common/src/io/null_hintin.rs create mode 100644 common/src/io/stream/file.rs create mode 100644 common/src/io/stream/mod.rs create mode 100644 common/src/io/stream/null.rs create mode 100644 common/src/io/stream/stream_reader.rs create mode 100644 common/src/io/stream/stream_writer.rs delete mode 100644 common/src/io/zisk_hintin.rs diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 122f04fc2..b2991893a 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -9,7 +9,7 @@ use crate::{ commands::cli_fail_if_gpu_mode, ux::{print_banner, print_banner_field}, }; -use zisk_common::io::{ZiskHintin, ZiskStdin}; +use zisk_common::io::{StreamSource, ZiskStdin}; #[derive(Parser)] #[command(author, about, long_about = None, version = ZISK_VERSION_MESSAGE)] @@ -97,11 +97,8 @@ impl ZiskExecute { let hintin = self.create_hintin()?; let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; - let result = if emulator { - self.run_emu(stdin, None)? - } else { - self.run_asm(stdin, Some(hintin))? - }; + let result = + if emulator { self.run_emu(stdin, None)? } else { self.run_asm(stdin, Some(hintin))? }; info!( "Execution completed in {:.2?}, executed steps: {}", @@ -123,7 +120,7 @@ impl ZiskExecute { Ok(stdin) } - fn create_hintin(&mut self) -> Result { + fn create_hintin(&mut self) -> Result { let hintin = if let Some(hints_path) = &self.precompile_hints_path { if !hints_path.exists() { return Err(anyhow::anyhow!( @@ -131,9 +128,9 @@ impl ZiskExecute { hints_path.display() )); } - ZiskHintin::from_file(hints_path)? + StreamSource::from_file(hints_path)? } else { - ZiskHintin::null() + StreamSource::null() }; Ok(hintin) } @@ -141,7 +138,7 @@ impl ZiskExecute { pub fn run_emu( &mut self, stdin: ZiskStdin, - hintin: Option, + hintin: Option, ) -> Result { let prover = ProverClient::builder() .emu() @@ -160,7 +157,7 @@ impl ZiskExecute { pub fn run_asm( &mut self, stdin: ZiskStdin, - hintin: Option, + hintin: Option, ) -> Result { let prover = ProverClient::builder() .asm() diff --git a/common/src/io/file_hintin.rs b/common/src/io/file_hintin.rs deleted file mode 100644 index fc8c5e081..000000000 --- a/common/src/io/file_hintin.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! A file-based implementation of ZiskHintin. -//! This module provides functionality to read input data from a file. - -use std::fs::{self, File}; -use std::io::{BufReader, Read}; -use std::path::{Path, PathBuf}; - -use crate::io::ZiskIO; - -/// A file-based implementation of ZiskStdin that reads from a file. -pub struct ZiskFileHintin { - /// The path to the input file. - path: PathBuf, - - /// Buffered reader for the file. - reader: BufReader, -} - -impl ZiskFileHintin { - /// Create a new FileHintin from a file path. - pub fn new>(path: P) -> std::io::Result { - let path_buf = path.as_ref().to_path_buf(); - let file = File::open(&path_buf)?; - Ok(ZiskFileHintin { path: path_buf, reader: BufReader::new(file) }) - } -} - -impl ZiskIO for ZiskFileHintin { - fn read(&mut self) -> Vec { - fs::read(&self.path).expect("Could not read inputs file") - } - - fn read_slice(&mut self, slice: &mut [u8]) { - self.reader.read_exact(slice).expect("Failed to read slice"); - } - - fn read_into(&mut self, buffer: &mut [u8]) { - self.reader.read_exact(buffer).expect("Failed to read into buffer"); - } - - fn write_serialized(&mut self, _data: &[u8]) { - // This is a read-only stdin implementation - // Writing is not supported for file-based stdin - panic!("Write operations are not supported for FileStdin"); - } - - fn write_bytes(&mut self, _data: &[u8]) { - // This is a read-only stdin implementation - // Writing is not supported for file-based stdin - panic!("Write operations are not supported for FileStdin"); - } -} diff --git a/common/src/io/mod.rs b/common/src/io/mod.rs index 2be3722a0..81013195e 100644 --- a/common/src/io/mod.rs +++ b/common/src/io/mod.rs @@ -1,13 +1,11 @@ -mod file_hintin; mod file_stdin; mod memory_stdin; -mod null_hintin; mod null_stdin; -mod zisk_hintin; +mod stream; mod zisk_stdin; pub use file_stdin::*; pub use memory_stdin::*; pub use null_stdin::*; -pub use zisk_hintin::*; +pub use stream::*; pub use zisk_stdin::*; diff --git a/common/src/io/null_hintin.rs b/common/src/io/null_hintin.rs deleted file mode 100644 index 3cf1ef2a8..000000000 --- a/common/src/io/null_hintin.rs +++ /dev/null @@ -1,19 +0,0 @@ -use tracing::warn; - -use crate::io::ZiskIO; - -pub struct ZiskNullHintin; - -impl ZiskIO for ZiskNullHintin { - fn read(&mut self) -> Vec { - Vec::new() - } - fn read_slice(&mut self, _slice: &mut [u8]) {} - fn read_into(&mut self, _buffer: &mut [u8]) {} - fn write_serialized(&mut self, _data: &[u8]) { - warn!("NullHintin does not support writing"); - } - fn write_bytes(&mut self, _data: &[u8]) { - warn!("NullHintin does not support writing"); - } -} diff --git a/common/src/io/stream/file.rs b/common/src/io/stream/file.rs new file mode 100644 index 000000000..f70a69d0f --- /dev/null +++ b/common/src/io/stream/file.rs @@ -0,0 +1,51 @@ +//! A file-based implementation of ZiskHintin. +//! This module provides functionality to read input data from a file. + +use std::fs::{self, File}; +use std::io::BufReader; +use std::path::{Path, PathBuf}; + +use super::StreamRead; + +use anyhow::Result; + +/// A file-based implementation of ZiskStdin that reads from a file. +pub struct FileStreamReader { + /// The path to the input file. + path: PathBuf, + + /// Buffered reader for the file. + reader: Option>, +} + +impl FileStreamReader { + /// Create a new FileHintin from a file path. + pub fn new>(path: P) -> std::io::Result { + Ok(FileStreamReader { path: path.as_ref().to_path_buf(), reader: None }) + } +} + +impl StreamRead for FileStreamReader { + /// Open/initialize the stream for reading + fn open(&mut self) -> Result<()> { + let file = File::open(&self.path)?; + self.reader = Some(BufReader::new(file)); + Ok(()) + } + + /// Read the next item from the stream + fn next(&mut self) -> Result> { + Ok(fs::read(&self.path)?) + } + + /// Close the stream + fn close(&mut self) -> Result<()> { + self.reader = None; + Ok(()) + } + + /// Check if the stream is currently active + fn is_active(&self) -> bool { + self.reader.is_some() + } +} diff --git a/common/src/io/stream/mod.rs b/common/src/io/stream/mod.rs new file mode 100644 index 000000000..c3e142ccc --- /dev/null +++ b/common/src/io/stream/mod.rs @@ -0,0 +1,9 @@ +mod file; +mod null; +mod stream_reader; +mod stream_writer; + +use file::FileStreamReader; +use null::NullStreamReader; +pub use stream_reader::*; +pub use stream_writer::*; diff --git a/common/src/io/stream/null.rs b/common/src/io/stream/null.rs new file mode 100644 index 000000000..eafb5d3dd --- /dev/null +++ b/common/src/io/stream/null.rs @@ -0,0 +1,31 @@ +use super::StreamRead; +use tracing::debug; + +use anyhow::Result; + +pub struct NullStreamReader; + +impl StreamRead for NullStreamReader { + /// Open/initialize the stream for reading + fn open(&mut self) -> Result<()> { + debug!("NullStreamReader opened - no data will be read"); + Ok(()) + } + + /// Read the next item from the stream + fn next(&mut self) -> Result> { + debug!("NullStreamReader next called - returning empty data"); + Ok(Vec::new()) + } + + /// Close the stream + fn close(&mut self) -> Result<()> { + debug!("NullStreamReader closed"); + Ok(()) + } + + /// Check if the stream is currently active + fn is_active(&self) -> bool { + false + } +} diff --git a/common/src/io/stream/stream_reader.rs b/common/src/io/stream/stream_reader.rs new file mode 100644 index 000000000..2c2d97c47 --- /dev/null +++ b/common/src/io/stream/stream_reader.rs @@ -0,0 +1,69 @@ +use super::{FileStreamReader, NullStreamReader}; + +use anyhow::Result; + +/// Core trait for stream reading operations +pub trait StreamRead: Send + 'static { + /// Open/initialize the stream for reading + fn open(&mut self) -> Result<()>; + + /// Read the next item from the stream + fn next(&mut self) -> Result>; + + /// Close the stream + fn close(&mut self) -> Result<()>; + + /// Check if the stream is currently active + fn is_active(&self) -> bool; +} + +pub enum StreamSource { + File(FileStreamReader), + Null(NullStreamReader), +} + +impl StreamSource { + /// Create a null stdin + pub fn null() -> Self { + StreamSource::Null(NullStreamReader) + } + + /// Create a file-based stdin + pub fn from_file>(path: P) -> Result { + Ok(StreamSource::File(FileStreamReader::new(path)?)) + } +} + +impl StreamRead for StreamSource { + /// Open/initialize the stream for reading + fn open(&mut self) -> Result<()> { + match self { + StreamSource::File(file_hintin) => file_hintin.open(), + StreamSource::Null(null_hintin) => null_hintin.open(), + } + } + + /// Read the next item from the stream + fn next(&mut self) -> Result> { + match self { + StreamSource::File(file_hintin) => file_hintin.next(), + StreamSource::Null(null_hintin) => null_hintin.next(), + } + } + + /// Close the stream + fn close(&mut self) -> Result<()> { + match self { + StreamSource::File(file_hintin) => file_hintin.close(), + StreamSource::Null(null_hintin) => null_hintin.close(), + } + } + + /// Check if the stream is currently active + fn is_active(&self) -> bool { + match self { + StreamSource::File(file_hintin) => file_hintin.is_active(), + StreamSource::Null(null_hintin) => null_hintin.is_active(), + } + } +} diff --git a/common/src/io/stream/stream_writer.rs b/common/src/io/stream/stream_writer.rs new file mode 100644 index 000000000..e5a6c830a --- /dev/null +++ b/common/src/io/stream/stream_writer.rs @@ -0,0 +1,22 @@ +use anyhow::Result; + +/// Core trait for stream writing operations +pub trait StreamWrite: Send + 'static { + /// Open/initialize the stream for writing + fn open(&mut self) -> Result<()>; + + /// Write an item to the stream + fn write(&mut self, item: &[u8]) -> Result<()>; + + /// Write raw bytes to the stream + fn write_bytes(&mut self, data: &[u8]) -> Result; + + /// Flush any buffered data + fn flush(&mut self) -> Result<()>; + + /// Close the stream + fn close(&mut self) -> Result<()>; + + /// Check if the stream is currently active + fn is_active(&self) -> bool; +} diff --git a/common/src/io/zisk_hintin.rs b/common/src/io/zisk_hintin.rs deleted file mode 100644 index 6f1281aa5..000000000 --- a/common/src/io/zisk_hintin.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::path::Path; - -use crate::io::{file_hintin::ZiskFileHintin, null_hintin::ZiskNullHintin, ZiskIO}; -use anyhow::Result; - -pub enum ZiskHintIOVariant { - File(ZiskFileHintin), - Null(ZiskNullHintin), -} - -impl ZiskIO for ZiskHintIOVariant { - fn read(&mut self) -> Vec { - match self { - ZiskHintIOVariant::File(file_hintin) => file_hintin.read(), - ZiskHintIOVariant::Null(null_hintin) => null_hintin.read(), - } - } - - fn read_slice(&mut self, slice: &mut [u8]) { - match self { - ZiskHintIOVariant::File(file_hintin) => file_hintin.read_slice(slice), - ZiskHintIOVariant::Null(null_hintin) => null_hintin.read_slice(slice), - } - } - - fn read_into(&mut self, buffer: &mut [u8]) { - match self { - ZiskHintIOVariant::File(file_hintin) => file_hintin.read_into(buffer), - ZiskHintIOVariant::Null(null_hintin) => null_hintin.read_into(buffer), - } - } - - fn write_serialized(&mut self, data: &[u8]) { - match self { - ZiskHintIOVariant::File(file_hintin) => file_hintin.write_serialized(data), - ZiskHintIOVariant::Null(null_hintin) => null_hintin.write_serialized(data), - } - } - - fn write_bytes(&mut self, data: &[u8]) { - match self { - ZiskHintIOVariant::File(file_hintin) => file_hintin.write_bytes(data), - ZiskHintIOVariant::Null(null_hintin) => null_hintin.write_bytes(data), - } - } -} - -pub struct ZiskHintin { - io: ZiskHintIOVariant, -} - -impl ZiskIO for ZiskHintin { - fn read(&mut self) -> Vec { - self.io.read() - } - - fn read_slice(&mut self, slice: &mut [u8]) { - self.io.read_slice(slice) - } - - fn read_into(&mut self, buffer: &mut [u8]) { - self.io.read_into(buffer) - } - - fn write_serialized(&mut self, data: &[u8]) { - self.io.write_serialized(data) - } - - fn write_bytes(&mut self, data: &[u8]) { - self.io.write_bytes(data) - } -} - -impl ZiskHintin { - /// Create a null stdin (no input) - pub fn null() -> Self { - Self { io: ZiskHintIOVariant::Null(ZiskNullHintin) } - } - - /// Create a file-based stdin - pub fn from_file>(path: P) -> Result { - Ok(Self { io: ZiskHintIOVariant::File(ZiskFileHintin::new(path)?) }) - } -} diff --git a/common/src/zisk_lib_init.rs b/common/src/zisk_lib_init.rs index c7cf940c0..058bb1d7e 100644 --- a/common/src/zisk_lib_init.rs +++ b/common/src/zisk_lib_init.rs @@ -5,7 +5,7 @@ use proofman_common::VerboseMode; use witness::WitnessLibrary; use crate::{ - io::{ZiskHintin, ZiskStdin}, + io::{StreamSource, ZiskStdin}, ExecutorStats, }; @@ -33,7 +33,7 @@ pub struct Stats { /// Extension trait that provides execution result access without Any boxing pub trait ZiskWitnessLibrary { fn set_stdin(&self, stdin: ZiskStdin); - fn set_hintin(&self, hintin: ZiskHintin); + fn set_hintin(&self, hintin: StreamSource); fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)>; } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 3d47a20e5..0698fea2b 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -33,7 +33,7 @@ use sm_rom::{RomInstance, RomSM}; use std::sync::atomic::{AtomicUsize, Ordering}; use tracing::debug; use witness::WitnessComponent; -use zisk_common::io::{ZiskHintin, ZiskIO, ZiskStdin}; +use zisk_common::io::{StreamSource, ZiskIO, ZiskStdin}; use zisk_hints::PrecompileHintsProcessor; use crate::{DummyCounter, HintsShmem}; @@ -235,7 +235,7 @@ impl ZiskExecutor { HintsShmem::new(hints_shmem_control_names, hints_shmem_names, unlock_mapped_memory); let hints_pipeline = - Mutex::new(HintsPipeline::new(hints_processor, hints_shmem, ZiskHintin::null())); + Mutex::new(HintsPipeline::new(hints_processor, hints_shmem, StreamSource::null())); Self { stdin: Mutex::new(ZiskStdin::null()), @@ -271,7 +271,7 @@ impl ZiskExecutor { *guard = stdin; } - pub fn set_hintin(&self, hintin: ZiskHintin) { + pub fn set_hintin(&self, hintin: StreamSource) { self.hints_pipeline.lock().unwrap().set_hintin(hintin); } diff --git a/hints/src/hints_pipeline.rs b/hints/src/hints_pipeline.rs index 0e7e4cdb8..e3c15c76e 100644 --- a/hints/src/hints_pipeline.rs +++ b/hints/src/hints_pipeline.rs @@ -5,7 +5,7 @@ use crate::{HintsProcessor, HintsSink}; use anyhow::Result; use std::sync::Mutex; use tracing::info; -use zisk_common::io::{ZiskHintin, ZiskIO}; +use zisk_common::io::{StreamRead, StreamSource}; /// HintsPipeline struct manages the processing of precompile hints and writing them to shared memory. pub struct HintsPipeline { @@ -16,7 +16,7 @@ pub struct HintsPipeline { hints_sink: HS, /// The ZiskHintin source for reading hints. - hintin: Mutex, + hintin: Mutex, } impl HintsPipeline { @@ -29,7 +29,7 @@ impl HintsPipeline { /// /// # Returns /// A new `HintsPipeline` instance with uninitialized writers. - pub fn new(hints_processor: HP, hints_sink: HS, hintin: ZiskHintin) -> Self { + pub fn new(hints_processor: HP, hints_sink: HS, hintin: StreamSource) -> Self { Self { hints_processor, hints_sink, hintin: Mutex::new(hintin) } } @@ -37,7 +37,7 @@ impl HintsPipeline { /// /// # Arguments /// * `hintin` - The new ZiskHintin source for reading hints. - pub fn set_hintin(&self, hintin: ZiskHintin) { + pub fn set_hintin(&self, hintin: StreamSource) { let mut guard = self.hintin.lock().unwrap(); *guard = hintin; } @@ -55,10 +55,19 @@ impl HintsPipeline { pub fn write_hints(&self) -> Result<()> { let mut hintin = self.hintin.lock().unwrap(); - let hints = zisk_common::reinterpret_vec(hintin.read())?; + let hints = zisk_common::reinterpret_vec(hintin.next()?)?; let processed = self.hints_processor.process_hints(&hints)?; + // // STore processed hints in a temp file for debugging + // std::fs::write( + // "/data/hints/processed_hints.bin", + // &zisk_common::reinterpret_vec::(processed.clone())?, + // )?; + // // // read processed into a /data/hints/precompile_cache.bin + // // let processed = std::fs::read("/data/hints/precompile_cache.bin")?; + // // let processed = zisk_common::reinterpret_vec::(processed)?; + info!("Precompile hints have generated {} u64 values", processed.len()); self.hints_sink.submit(processed) diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index d34337d9e..3b913aa95 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -11,7 +11,7 @@ use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rom_setup::DEFAULT_CACHE_PATH; use std::{collections::HashMap, path::PathBuf}; use tracing::info; -use zisk_common::io::{ZiskHintin, ZiskStdin}; +use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ExecutorStats; use zisk_distributed_common::LoggingConfig; @@ -101,7 +101,7 @@ impl ProverEngine for AsmProver { fn execute( &self, stdin: ZiskStdin, - hintin: Option, + hintin: Option, output_path: Option, ) -> Result { self.core_prover.backend.execute(stdin, hintin, output_path) diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index e7d795501..2e1a1ede2 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -10,7 +10,7 @@ use proofman::{AggProofs, ProofInfo, ProofMan, ProvePhase, ProvePhaseInputs, Pro use proofman_common::{DebugInfo, ProofOptions}; use std::{fs::File, io::Write, path::PathBuf}; use zisk_common::{ - io::{ZiskHintin, ZiskStdin}, + io::{StreamSource, ZiskStdin}, ExecutorStats, ProofLog, ZiskExecutionResult, ZiskLib, }; use zstd::Encoder; @@ -34,7 +34,7 @@ impl ProverBackend { pub(crate) fn execute( &self, stdin: ZiskStdin, - hintin: Option, + hintin: Option, output_path: Option, ) -> Result { self.witness_lib.set_stdin(stdin); diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 020add8b9..f162d8b80 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -7,7 +7,7 @@ use crate::{ use proofman::{AggProofs, ProofMan, ProvePhase, ProvePhaseInputs}; use proofman_common::{initialize_logger, ParamsGPU, ProofOptions}; use std::path::PathBuf; -use zisk_common::io::{ZiskHintin, ZiskStdin}; +use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ExecutorStats; use zisk_distributed_common::LoggingConfig; @@ -89,7 +89,7 @@ impl ProverEngine for EmuProver { fn execute( &self, stdin: ZiskStdin, - hintin: Option, + hintin: Option, output_path: Option, ) -> Result { if hintin.is_some() { diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index e1d7c23a3..19f2b49f0 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -12,7 +12,7 @@ use crate::Proof; use anyhow::Result; use std::{path::PathBuf, time::Duration}; use zisk_common::{ - io::{ZiskHintin, ZiskStdin}, + io::{StreamSource, ZiskStdin}, ExecutorStats, ZiskExecutionResult, }; @@ -52,7 +52,7 @@ pub trait ProverEngine { fn execute( &self, stdin: ZiskStdin, - hintin: Option, + hintin: Option, output_path: Option, ) -> Result; @@ -132,7 +132,7 @@ impl ZiskProver { pub fn execute( &self, stdin: ZiskStdin, - hintin: Option, + hintin: Option, ) -> Result { self.prover.execute(stdin, hintin, None) } diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index cd8e52a55..32270f6a1 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -215,7 +215,7 @@ impl ZiskWitnessLibrary for WitnessLib { } } - fn set_hintin(&self, hintin: zisk_common::io::ZiskHintin) { + fn set_hintin(&self, hintin: zisk_common::io::StreamSource) { if let Some(executor) = &self.executor { executor.set_hintin(hintin); } From 3e2d6e2232133819928abd52ede7e318c9b79ca2 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:09:27 +0000 Subject: [PATCH 040/782] read file as a stream --- cli/src/commands/execute.rs | 22 +++++++--------- common/src/io/stream/file.rs | 26 ++++++++++++++----- common/src/io/stream/null.rs | 29 +++++++++++++++------ common/src/io/stream/stream_reader.rs | 23 +++++++++-------- common/src/zisk_lib_init.rs | 2 +- executor/src/executor.rs | 6 ++--- hints/src/hints_pipeline.rs | 36 +++++++++++++++------------ sdk/src/prover/asm.rs | 4 +-- sdk/src/prover/backend.rs | 6 ++--- sdk/src/prover/emu.rs | 4 +-- sdk/src/prover/mod.rs | 6 ++--- witness-computation/src/zisk_lib.rs | 4 +-- 12 files changed, 98 insertions(+), 70 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index b2991893a..1e9d602ad 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -94,11 +94,11 @@ impl ZiskExecute { } let stdin = self.create_stdin()?; - let hintin = self.create_hintin()?; + let stream = self.create_hints_stream()?; let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; let result = - if emulator { self.run_emu(stdin, None)? } else { self.run_asm(stdin, Some(hintin))? }; + if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, Some(stream))? }; info!( "Execution completed in {:.2?}, executed steps: {}", @@ -120,8 +120,8 @@ impl ZiskExecute { Ok(stdin) } - fn create_hintin(&mut self) -> Result { - let hintin = if let Some(hints_path) = &self.precompile_hints_path { + fn create_hints_stream(&mut self) -> Result { + let stream = if let Some(hints_path) = &self.precompile_hints_path { if !hints_path.exists() { return Err(anyhow::anyhow!( "Precompile Hints file not found at {:?}", @@ -132,14 +132,10 @@ impl ZiskExecute { } else { StreamSource::null() }; - Ok(hintin) + Ok(stream) } - pub fn run_emu( - &mut self, - stdin: ZiskStdin, - hintin: Option, - ) -> Result { + pub fn run_emu(&mut self, stdin: ZiskStdin) -> Result { let prover = ProverClient::builder() .emu() .witness() @@ -151,13 +147,13 @@ impl ZiskExecute { .print_command_info() .build()?; - prover.execute(stdin, hintin) + prover.execute(stdin, None) } pub fn run_asm( &mut self, stdin: ZiskStdin, - hintin: Option, + stream: Option, ) -> Result { let prover = ProverClient::builder() .asm() @@ -173,6 +169,6 @@ impl ZiskExecute { .print_command_info() .build()?; - prover.execute(stdin, hintin) + prover.execute(stdin, stream) } } diff --git a/common/src/io/stream/file.rs b/common/src/io/stream/file.rs index f70a69d0f..d3e3b3e0d 100644 --- a/common/src/io/stream/file.rs +++ b/common/src/io/stream/file.rs @@ -1,4 +1,4 @@ -//! A file-based implementation of ZiskHintin. +//! A file-based implementation of FileStreamReader. //! This module provides functionality to read input data from a file. use std::fs::{self, File}; @@ -16,12 +16,15 @@ pub struct FileStreamReader { /// Buffered reader for the file. reader: Option>, + + /// Track if the file has been read already. + has_read: bool, } impl FileStreamReader { - /// Create a new FileHintin from a file path. + /// Create a new FileStreamReader from a file path. pub fn new>(path: P) -> std::io::Result { - Ok(FileStreamReader { path: path.as_ref().to_path_buf(), reader: None }) + Ok(FileStreamReader { path: path.as_ref().to_path_buf(), reader: None, has_read: false }) } } @@ -30,12 +33,23 @@ impl StreamRead for FileStreamReader { fn open(&mut self) -> Result<()> { let file = File::open(&self.path)?; self.reader = Some(BufReader::new(file)); + self.has_read = false; Ok(()) } - /// Read the next item from the stream - fn next(&mut self) -> Result> { - Ok(fs::read(&self.path)?) + /// Reads the next item from the stream. + /// + /// This method does **not** stream incrementally. Instead, it repeatedly toggles + /// between returning the full file contents and returning `None`, producing the + /// following repeating sequence: `Some(Vec), None, Some(Vec), None, ...` + fn next(&mut self) -> Result>> { + self.has_read = !self.has_read; + + if self.has_read { + Ok(Some(fs::read(&self.path)?)) + } else { + Ok(None) + } } /// Close the stream diff --git a/common/src/io/stream/null.rs b/common/src/io/stream/null.rs index eafb5d3dd..c12933f17 100644 --- a/common/src/io/stream/null.rs +++ b/common/src/io/stream/null.rs @@ -1,31 +1,44 @@ use super::StreamRead; -use tracing::debug; use anyhow::Result; -pub struct NullStreamReader; +pub struct NullStreamReader { + active: bool, +} + +impl Default for NullStreamReader { + fn default() -> Self { + NullStreamReader::new() + } +} + +impl NullStreamReader { + /// Create a new NullStreamReader + pub fn new() -> Self { + NullStreamReader { active: false } + } +} impl StreamRead for NullStreamReader { /// Open/initialize the stream for reading fn open(&mut self) -> Result<()> { - debug!("NullStreamReader opened - no data will be read"); + self.active = true; Ok(()) } /// Read the next item from the stream - fn next(&mut self) -> Result> { - debug!("NullStreamReader next called - returning empty data"); - Ok(Vec::new()) + fn next(&mut self) -> Result>> { + Ok(None) } /// Close the stream fn close(&mut self) -> Result<()> { - debug!("NullStreamReader closed"); + self.active = false; Ok(()) } /// Check if the stream is currently active fn is_active(&self) -> bool { - false + self.active } } diff --git a/common/src/io/stream/stream_reader.rs b/common/src/io/stream/stream_reader.rs index 2c2d97c47..417a7ad5d 100644 --- a/common/src/io/stream/stream_reader.rs +++ b/common/src/io/stream/stream_reader.rs @@ -8,7 +8,8 @@ pub trait StreamRead: Send + 'static { fn open(&mut self) -> Result<()>; /// Read the next item from the stream - fn next(&mut self) -> Result>; + /// Returns None when the stream is finished + fn next(&mut self) -> Result>>; /// Close the stream fn close(&mut self) -> Result<()>; @@ -25,7 +26,7 @@ pub enum StreamSource { impl StreamSource { /// Create a null stdin pub fn null() -> Self { - StreamSource::Null(NullStreamReader) + StreamSource::Null(NullStreamReader::new()) } /// Create a file-based stdin @@ -38,32 +39,32 @@ impl StreamRead for StreamSource { /// Open/initialize the stream for reading fn open(&mut self) -> Result<()> { match self { - StreamSource::File(file_hintin) => file_hintin.open(), - StreamSource::Null(null_hintin) => null_hintin.open(), + StreamSource::File(file_stream) => file_stream.open(), + StreamSource::Null(null_stream) => null_stream.open(), } } /// Read the next item from the stream - fn next(&mut self) -> Result> { + fn next(&mut self) -> Result>> { match self { - StreamSource::File(file_hintin) => file_hintin.next(), - StreamSource::Null(null_hintin) => null_hintin.next(), + StreamSource::File(file_stream) => file_stream.next(), + StreamSource::Null(null_stream) => null_stream.next(), } } /// Close the stream fn close(&mut self) -> Result<()> { match self { - StreamSource::File(file_hintin) => file_hintin.close(), - StreamSource::Null(null_hintin) => null_hintin.close(), + StreamSource::File(file_stream) => file_stream.close(), + StreamSource::Null(null_stream) => null_stream.close(), } } /// Check if the stream is currently active fn is_active(&self) -> bool { match self { - StreamSource::File(file_hintin) => file_hintin.is_active(), - StreamSource::Null(null_hintin) => null_hintin.is_active(), + StreamSource::File(file_stream) => file_stream.is_active(), + StreamSource::Null(null_stream) => null_stream.is_active(), } } } diff --git a/common/src/zisk_lib_init.rs b/common/src/zisk_lib_init.rs index 058bb1d7e..51600496f 100644 --- a/common/src/zisk_lib_init.rs +++ b/common/src/zisk_lib_init.rs @@ -33,7 +33,7 @@ pub struct Stats { /// Extension trait that provides execution result access without Any boxing pub trait ZiskWitnessLibrary { fn set_stdin(&self, stdin: ZiskStdin); - fn set_hintin(&self, hintin: StreamSource); + fn set_hints_stream(&self, stream: StreamSource); fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)>; } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 0698fea2b..565ca8dc5 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -227,7 +227,7 @@ impl ZiskExecutor { }) .collect::>(); - // Create hints pipeline with null hintin initially. + // Create hints pipeline with null hints stream initially. let hints_processor = PrecompileHintsProcessor::new().expect("Failed to create PrecompileHintsProcessor"); @@ -271,8 +271,8 @@ impl ZiskExecutor { *guard = stdin; } - pub fn set_hintin(&self, hintin: StreamSource) { - self.hints_pipeline.lock().unwrap().set_hintin(hintin); + pub fn set_hints_stream(&self, stream: StreamSource) { + self.hints_pipeline.lock().unwrap().set_hints_stream(stream); } #[allow(clippy::type_complexity)] diff --git a/hints/src/hints_pipeline.rs b/hints/src/hints_pipeline.rs index e3c15c76e..8948b7f5f 100644 --- a/hints/src/hints_pipeline.rs +++ b/hints/src/hints_pipeline.rs @@ -1,5 +1,5 @@ //! HintsPipeline is responsible for processing precompile hints and submitting them to a sink. -//! It uses a ZiskHintin as the source of hints, and writes the processed hints to a HintsSink. +//! It uses a StreamSource as the source of hints, and writes the processed hints to a HintsSink. use crate::{HintsProcessor, HintsSink}; use anyhow::Result; @@ -15,37 +15,37 @@ pub struct HintsPipeline { /// The hints sink used to submit processed hints. hints_sink: HS, - /// The ZiskHintin source for reading hints. - hintin: Mutex, + /// The Hints source stream for reading hints. + stream_src: Mutex, } impl HintsPipeline { - /// Create a new HintsPipeline with the given processor, ZiskHintin, and sink. + /// Create a new HintsPipeline with the given processor, StreamSource, and sink. /// /// # Arguments /// * `hints_processor` - The processor used to process hints. /// * `hints_sink` - The sink used to submit processed hints. - /// * `hintin` - The ZiskHintin source for reading hints. + /// * `stream` - The StreamSource source for reading hints. /// /// # Returns /// A new `HintsPipeline` instance with uninitialized writers. - pub fn new(hints_processor: HP, hints_sink: HS, hintin: StreamSource) -> Self { - Self { hints_processor, hints_sink, hintin: Mutex::new(hintin) } + pub fn new(hints_processor: HP, hints_sink: HS, stream: StreamSource) -> Self { + Self { hints_processor, hints_sink, stream_src: Mutex::new(stream) } } - /// Set a new ZiskHintin for the pipeline. + /// Set a new StreamSource for the pipeline. /// /// # Arguments - /// * `hintin` - The new ZiskHintin source for reading hints. - pub fn set_hintin(&self, hintin: StreamSource) { - let mut guard = self.hintin.lock().unwrap(); - *guard = hintin; + /// * `stream` - The new StreamSource source for reading hints. + pub fn set_hints_stream(&self, stream: StreamSource) { + let mut guard = self.stream_src.lock().unwrap(); + *guard = stream; } /// Process and write precompile hints to all shared memory writers. /// /// This method: - /// 1. Reads hints from the ZiskHintin source + /// 1. Reads hints from the StreamSource source /// 2. Processes them using PrecompileHintsProcessor /// 3. Submits the processed hints to the HintsSink /// @@ -53,11 +53,15 @@ impl HintsPipeline { /// * `Ok(())` - If hints were successfully processed and submitted /// * `Err` - If processing or submission fails pub fn write_hints(&self) -> Result<()> { - let mut hintin = self.hintin.lock().unwrap(); + let mut stream = self.stream_src.lock().unwrap(); - let hints = zisk_common::reinterpret_vec(hintin.next()?)?; + let mut processed = Vec::new(); - let processed = self.hints_processor.process_hints(&hints)?; + while let Some(hints) = stream.next()? { + let hints = zisk_common::reinterpret_vec(hints)?; + + processed.extend(self.hints_processor.process_hints(&hints)?); + } // // STore processed hints in a temp file for debugging // std::fs::write( diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 3b913aa95..a6fdffdb5 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -101,10 +101,10 @@ impl ProverEngine for AsmProver { fn execute( &self, stdin: ZiskStdin, - hintin: Option, + hints_stream: Option, output_path: Option, ) -> Result { - self.core_prover.backend.execute(stdin, hintin, output_path) + self.core_prover.backend.execute(stdin, hints_stream, output_path) } fn stats( diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 2e1a1ede2..410f2053b 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -34,12 +34,12 @@ impl ProverBackend { pub(crate) fn execute( &self, stdin: ZiskStdin, - hintin: Option, + hints_stream: Option, output_path: Option, ) -> Result { self.witness_lib.set_stdin(stdin); - if let Some(hintin) = hintin { - self.witness_lib.set_hintin(hintin); + if let Some(stream) = hints_stream { + self.witness_lib.set_hints_stream(stream); } let start = std::time::Instant::now(); diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index f162d8b80..ba15f5675 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -89,10 +89,10 @@ impl ProverEngine for EmuProver { fn execute( &self, stdin: ZiskStdin, - hintin: Option, + hints_stream: Option, output_path: Option, ) -> Result { - if hintin.is_some() { + if hints_stream.is_some() { return Err(anyhow::anyhow!("EMU prover does not support precompile hints")); } self.core_prover.backend.execute(stdin, None, output_path) diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 19f2b49f0..3934732fb 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -52,7 +52,7 @@ pub trait ProverEngine { fn execute( &self, stdin: ZiskStdin, - hintin: Option, + hints_stream: Option, output_path: Option, ) -> Result; @@ -132,9 +132,9 @@ impl ZiskProver { pub fn execute( &self, stdin: ZiskStdin, - hintin: Option, + hints_stream: Option, ) -> Result { - self.prover.execute(stdin, hintin, None) + self.prover.execute(stdin, hints_stream, None) } /// Get the execution statistics with the given standard input and debug information. diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 32270f6a1..6ac1533c2 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -215,9 +215,9 @@ impl ZiskWitnessLibrary for WitnessLib { } } - fn set_hintin(&self, hintin: zisk_common::io::StreamSource) { + fn set_hints_stream(&self, hints_stream: zisk_common::io::StreamSource) { if let Some(executor) = &self.executor { - executor.set_hintin(hintin); + executor.set_hints_stream(hints_stream); } } From f454bd85d98f631d10a4b735996ff4a9381239d6 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 9 Dec 2025 21:37:00 +0100 Subject: [PATCH 041/782] Fix server_run when precompile_results_enabled=false --- emulator-asm/src/main.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 982bcec17..eb736c889 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -3449,11 +3449,14 @@ void server_run (void) } // Sync precompile shared memory - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); + if (precompile_results_enabled) + { + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } } /*******/ From 2a283f5857b66ec8f521289f6fddce62795fe7b9 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:51:07 +0000 Subject: [PATCH 042/782] change order in HintsPipeline --- hints/src/hints_pipeline.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hints/src/hints_pipeline.rs b/hints/src/hints_pipeline.rs index 8948b7f5f..f8b074f6b 100644 --- a/hints/src/hints_pipeline.rs +++ b/hints/src/hints_pipeline.rs @@ -9,14 +9,14 @@ use zisk_common::io::{StreamRead, StreamSource}; /// HintsPipeline struct manages the processing of precompile hints and writing them to shared memory. pub struct HintsPipeline { + /// The Hints source stream for reading hints. + stream_src: Mutex, + /// The hints processor used to process hints before writing. hints_processor: HP, /// The hints sink used to submit processed hints. hints_sink: HS, - - /// The Hints source stream for reading hints. - stream_src: Mutex, } impl HintsPipeline { @@ -30,7 +30,7 @@ impl HintsPipeline { /// # Returns /// A new `HintsPipeline` instance with uninitialized writers. pub fn new(hints_processor: HP, hints_sink: HS, stream: StreamSource) -> Self { - Self { hints_processor, hints_sink, stream_src: Mutex::new(stream) } + Self { stream_src: Mutex::new(stream), hints_processor, hints_sink } } /// Set a new StreamSource for the pipeline. From d4a91d16a6100b9d7e098e438f114238ec119dd0 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:54:58 +0000 Subject: [PATCH 043/782] move HintsPipeline to HintsStream --- executor/src/executor.rs | 6 +++--- hints/src/{hints_pipeline.rs => hints_stream.rs} | 6 +++--- hints/src/lib.rs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) rename hints/src/{hints_pipeline.rs => hints_stream.rs} (93%) diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 565ca8dc5..21b325953 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -44,7 +44,7 @@ use zisk_common::{ InstanceCtx, InstanceType, Plan, Stats, ZiskExecutionResult, }; use zisk_common::{ChunkId, PayloadType}; -use zisk_hints::HintsPipeline; +use zisk_hints::HintsStream; use zisk_pil::{ RomRomTrace, ZiskPublicValues, INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, ZISK_AIRGROUP_ID, @@ -78,7 +78,7 @@ enum MinimalTraceExecutionMode { AsmWithCounter, } -type HintsProcessorShmem = HintsPipeline; +type HintsProcessorShmem = HintsStream; /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. @@ -235,7 +235,7 @@ impl ZiskExecutor { HintsShmem::new(hints_shmem_control_names, hints_shmem_names, unlock_mapped_memory); let hints_pipeline = - Mutex::new(HintsPipeline::new(hints_processor, hints_shmem, StreamSource::null())); + Mutex::new(HintsStream::new(StreamSource::null(), hints_processor, hints_shmem)); Self { stdin: Mutex::new(ZiskStdin::null()), diff --git a/hints/src/hints_pipeline.rs b/hints/src/hints_stream.rs similarity index 93% rename from hints/src/hints_pipeline.rs rename to hints/src/hints_stream.rs index f8b074f6b..23f107dce 100644 --- a/hints/src/hints_pipeline.rs +++ b/hints/src/hints_stream.rs @@ -8,7 +8,7 @@ use tracing::info; use zisk_common::io::{StreamRead, StreamSource}; /// HintsPipeline struct manages the processing of precompile hints and writing them to shared memory. -pub struct HintsPipeline { +pub struct HintsStream { /// The Hints source stream for reading hints. stream_src: Mutex, @@ -19,7 +19,7 @@ pub struct HintsPipeline { hints_sink: HS, } -impl HintsPipeline { +impl HintsStream { /// Create a new HintsPipeline with the given processor, StreamSource, and sink. /// /// # Arguments @@ -29,7 +29,7 @@ impl HintsPipeline { /// /// # Returns /// A new `HintsPipeline` instance with uninitialized writers. - pub fn new(hints_processor: HP, hints_sink: HS, stream: StreamSource) -> Self { + pub fn new(stream: StreamSource, hints_processor: HP, hints_sink: HS) -> Self { Self { stream_src: Mutex::new(stream), hints_processor, hints_sink } } diff --git a/hints/src/lib.rs b/hints/src/lib.rs index 721940a37..968d66682 100644 --- a/hints/src/lib.rs +++ b/hints/src/lib.rs @@ -1,16 +1,16 @@ pub mod hints; pub mod hints_definitions; -mod hints_pipeline; mod hints_processor; +mod hints_stream; pub mod secp256k1; pub use hints::*; pub use hints_definitions::*; -pub use hints_pipeline::HintsPipeline; pub use hints_processor::{ PrecompileHint, PrecompileHintsProcessor, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT, }; +pub use hints_stream::HintsStream; pub use secp256k1::*; pub trait HintsProcessor { From 38afefab6837bce65cf8f42334bf94c3539d3fb3 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 9 Dec 2025 22:00:54 +0000 Subject: [PATCH 044/782] remove HintsStream Mutex --- hints/src/hints_stream.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/hints/src/hints_stream.rs b/hints/src/hints_stream.rs index 23f107dce..5eafd193a 100644 --- a/hints/src/hints_stream.rs +++ b/hints/src/hints_stream.rs @@ -3,14 +3,13 @@ use crate::{HintsProcessor, HintsSink}; use anyhow::Result; -use std::sync::Mutex; use tracing::info; use zisk_common::io::{StreamRead, StreamSource}; /// HintsPipeline struct manages the processing of precompile hints and writing them to shared memory. pub struct HintsStream { /// The Hints source stream for reading hints. - stream_src: Mutex, + stream: StreamSource, /// The hints processor used to process hints before writing. hints_processor: HP, @@ -30,16 +29,15 @@ impl HintsStream { /// # Returns /// A new `HintsPipeline` instance with uninitialized writers. pub fn new(stream: StreamSource, hints_processor: HP, hints_sink: HS) -> Self { - Self { stream_src: Mutex::new(stream), hints_processor, hints_sink } + Self { stream, hints_processor, hints_sink } } /// Set a new StreamSource for the pipeline. /// /// # Arguments /// * `stream` - The new StreamSource source for reading hints. - pub fn set_hints_stream(&self, stream: StreamSource) { - let mut guard = self.stream_src.lock().unwrap(); - *guard = stream; + pub fn set_hints_stream(&mut self, stream: StreamSource) { + self.stream = stream; } /// Process and write precompile hints to all shared memory writers. @@ -52,12 +50,10 @@ impl HintsStream { /// # Returns /// * `Ok(())` - If hints were successfully processed and submitted /// * `Err` - If processing or submission fails - pub fn write_hints(&self) -> Result<()> { - let mut stream = self.stream_src.lock().unwrap(); - + pub fn write_hints(&mut self) -> Result<()> { let mut processed = Vec::new(); - while let Some(hints) = stream.next()? { + while let Some(hints) = self.stream.next()? { let hints = zisk_common::reinterpret_vec(hints)?; processed.extend(self.hints_processor.process_hints(&hints)?); From 97739af1c142dfe61f4c7fd890d7042c9a138c9c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 10 Dec 2025 07:10:42 +0000 Subject: [PATCH 045/782] move HinsStream streaming to a thread --- executor/src/executor.rs | 25 +++---- hints/src/hints_stream.rs | 151 ++++++++++++++++++++++++++++++-------- 2 files changed, 133 insertions(+), 43 deletions(-) diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 21b325953..3b6633df3 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -78,7 +78,7 @@ enum MinimalTraceExecutionMode { AsmWithCounter, } -type HintsProcessorShmem = HintsStream; +type HintsStreamShmem = HintsStream; /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. @@ -87,7 +87,7 @@ pub struct ZiskExecutor { stdin: Mutex, /// Pipeline for handling precompile hints. - hints_pipeline: Mutex, + hints_stream: Mutex, /// ZisK ROM, a binary file containing the ZisK program to be executed. pub zisk_rom: Arc, @@ -234,12 +234,11 @@ impl ZiskExecutor { let hints_shmem = HintsShmem::new(hints_shmem_control_names, hints_shmem_names, unlock_mapped_memory); - let hints_pipeline = - Mutex::new(HintsStream::new(StreamSource::null(), hints_processor, hints_shmem)); + let hints_stream = Mutex::new(HintsStream::new(hints_processor, hints_shmem)); Self { stdin: Mutex::new(ZiskStdin::null()), - hints_pipeline, + hints_stream, rom_path, asm_runner_path: asm_path, asm_rom_path, @@ -272,7 +271,7 @@ impl ZiskExecutor { } pub fn set_hints_stream(&self, stream: StreamSource) { - self.hints_pipeline.lock().unwrap().set_hints_stream(stream); + self.hints_stream.lock().unwrap().set_hints_stream(stream); } #[allow(clippy::type_complexity)] @@ -384,13 +383,6 @@ impl ZiskExecutor { ); }); - // Process and write precompile atomically - self.hints_pipeline - .lock() - .unwrap() - .write_hints() - .expect("Failed to write hints to shared memory"); - let chunk_size = self.chunk_size; let (world_rank, local_rank, base_port) = (self.world_rank, self.local_rank, self.base_port); @@ -1248,6 +1240,13 @@ impl WitnessComponent for ZiskExecutor { // Set the start time of the current execution self.stats.set_start_time(Instant::now()); + // Process and write precompile atomically + self.hints_stream + .lock() + .unwrap() + .start_stream() + .expect("Failed to write hints to shared memory"); + // Process the ROM to collect the Minimal Traces timer_start_info!(COMPUTE_MINIMAL_TRACE); diff --git a/hints/src/hints_stream.rs b/hints/src/hints_stream.rs index 5eafd193a..de13aa6f8 100644 --- a/hints/src/hints_stream.rs +++ b/hints/src/hints_stream.rs @@ -1,62 +1,127 @@ -//! HintsPipeline is responsible for processing precompile hints and submitting them to a sink. +//! HintsStream is responsible for processing precompile hints and submitting them to a sink. //! It uses a StreamSource as the source of hints, and writes the processed hints to a HintsSink. use crate::{HintsProcessor, HintsSink}; use anyhow::Result; +use std::sync::mpsc::{Receiver, Sender}; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; use tracing::info; use zisk_common::io::{StreamRead, StreamSource}; -/// HintsPipeline struct manages the processing of precompile hints and writing them to shared memory. -pub struct HintsStream { - /// The Hints source stream for reading hints. - stream: StreamSource, +enum ThreadCommand { + Process, + Shutdown, +} +/// HintsStream struct manages the processing of precompile hints and writing them to shared memory. +pub struct HintsStream< + HP: HintsProcessor + Send + Sync + 'static, + HS: HintsSink + Send + Sync + 'static, +> { /// The hints processor used to process hints before writing. - hints_processor: HP, + hints_processor: Arc, /// The hints sink used to submit processed hints. - hints_sink: HS, + hints_sink: Arc, + + /// Channel sender to communicate with the background thread. + tx: Option>, + + /// Join handle for the background thread. + thread_handle: Option>, } -impl HintsStream { - /// Create a new HintsPipeline with the given processor, StreamSource, and sink. +impl + HintsStream +{ + /// Create a new HintsStream with the given processor and sink. /// /// # Arguments /// * `hints_processor` - The processor used to process hints. /// * `hints_sink` - The sink used to submit processed hints. - /// * `stream` - The StreamSource source for reading hints. /// /// # Returns - /// A new `HintsPipeline` instance with uninitialized writers. - pub fn new(stream: StreamSource, hints_processor: HP, hints_sink: HS) -> Self { - Self { stream, hints_processor, hints_sink } + /// A new `HintsStream` instance without a running thread. + pub fn new(hints_processor: HP, hints_sink: HS) -> Self { + Self { + hints_processor: Arc::new(hints_processor), + hints_sink: Arc::new(hints_sink), + tx: None, + thread_handle: None, + } + } + + /// Stop the current background thread if running. + fn stop_thread(&mut self) { + if let Some(tx) = self.tx.take() { + let _ = tx.send(ThreadCommand::Shutdown); + } + if let Some(handle) = self.thread_handle.take() { + let _ = handle.join(); + } } - /// Set a new StreamSource for the pipeline. + /// Set a new StreamSource for the pipeline and spawn a background thread to process hints. + /// + /// This will stop any existing background thread and start a new one with the new stream. /// /// # Arguments /// * `stream` - The new StreamSource source for reading hints. pub fn set_hints_stream(&mut self, stream: StreamSource) { - self.stream = stream; + // Stop the existing thread if running + self.stop_thread(); + + // Create a new channel for communication with the thread + let (tx, rx) = std::sync::mpsc::channel(); + self.tx = Some(tx); + + // Clone Arc references for the thread + let hints_processor = Arc::clone(&self.hints_processor); + let hints_sink = Arc::clone(&self.hints_sink); + + // Spawn the background thread + let thread_handle = thread::spawn(move || { + Self::background_thread(stream, hints_processor, hints_sink, rx); + }); + + self.thread_handle = Some(thread_handle); } - /// Process and write precompile hints to all shared memory writers. - /// - /// This method: - /// 1. Reads hints from the StreamSource source - /// 2. Processes them using PrecompileHintsProcessor - /// 3. Submits the processed hints to the HintsSink - /// - /// # Returns - /// * `Ok(())` - If hints were successfully processed and submitted - /// * `Err` - If processing or submission fails - pub fn write_hints(&mut self) -> Result<()> { + /// Background thread function that processes hints when requested. + fn background_thread( + mut stream: StreamSource, + hints_processor: Arc, + hints_sink: Arc, + rx: Receiver, + ) { + loop { + match rx.recv() { + Ok(ThreadCommand::Process) => { + if let Err(e) = Self::process_stream(&mut stream, &hints_processor, &hints_sink) + { + tracing::error!("Error processing hints in background thread: {:?}", e); + } + } + Ok(ThreadCommand::Shutdown) | Err(_) => { + // Channel closed or shutdown requested + break; + } + } + } + } + + /// Process all hints from the stream. + fn process_stream( + stream: &mut StreamSource, + hints_processor: &HP, + hints_sink: &HS, + ) -> Result<()> { let mut processed = Vec::new(); - while let Some(hints) = self.stream.next()? { + while let Some(hints) = stream.next()? { let hints = zisk_common::reinterpret_vec(hints)?; - - processed.extend(self.hints_processor.process_hints(&hints)?); + processed.extend(hints_processor.process_hints(&hints)?); } // // STore processed hints in a temp file for debugging @@ -70,6 +135,32 @@ impl HintsStream { info!("Precompile hints have generated {} u64 values", processed.len()); - self.hints_sink.submit(processed) + hints_sink.submit(processed) + } + + /// Trigger the background thread to process hints asynchronously. + /// + /// This method: + /// 1. Sends a command to the background thread to process hints + /// 2. Returns immediately without waiting for processing to complete + /// + /// # Returns + /// * `Ok(())` - If the command was successfully sent + /// * `Err` - If there's no active thread or the channel is closed + pub fn start_stream(&mut self) -> Result<()> { + if let Some(tx) = &self.tx { + tx.send(ThreadCommand::Process).map_err(|e| { + anyhow::anyhow!("Failed to send process command to background thread: {}", e) + })?; + Ok(()) + } else { + Err(anyhow::anyhow!("No background thread running. Call set_hints_stream first.")) + } + } +} + +impl Drop for HintsStream { + fn drop(&mut self) { + self.stop_thread(); } } From 76fcd807c1bbf7bcd8dede80ce983e04e7b95dc3 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 10 Dec 2025 08:11:42 +0000 Subject: [PATCH 046/782] using open and close fn when calling StreamReaders --- Cargo.lock | 1 + cli/src/commands/execute.rs | 4 ++-- common/src/io/stream/file.rs | 29 ++++++++++++++++++++++------- common/src/zisk_lib_init.rs | 4 +++- executor/src/executor.rs | 6 ++++-- hints/src/hints_stream.rs | 8 +++++++- sdk/src/prover/backend.rs | 4 +++- witness-computation/Cargo.toml | 2 ++ witness-computation/src/zisk_lib.rs | 8 ++++++-- 9 files changed, 50 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ef0dab50..4c00622f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5764,6 +5764,7 @@ dependencies = [ name = "zisk-witness" version = "0.15.0" dependencies = [ + "anyhow", "data-bus", "env_logger", "executor", diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 1e9d602ad..f70b6941c 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -94,11 +94,11 @@ impl ZiskExecute { } let stdin = self.create_stdin()?; - let stream = self.create_hints_stream()?; + let hints_stream = self.create_hints_stream()?; let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; let result = - if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, Some(stream))? }; + if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, Some(hints_stream))? }; info!( "Execution completed in {:.2?}, executed steps: {}", diff --git a/common/src/io/stream/file.rs b/common/src/io/stream/file.rs index d3e3b3e0d..eb78e4417 100644 --- a/common/src/io/stream/file.rs +++ b/common/src/io/stream/file.rs @@ -1,8 +1,8 @@ //! A file-based implementation of FileStreamReader. //! This module provides functionality to read input data from a file. -use std::fs::{self, File}; -use std::io::BufReader; +use std::fs::File; +use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; use super::StreamRead; @@ -31,6 +31,10 @@ impl FileStreamReader { impl StreamRead for FileStreamReader { /// Open/initialize the stream for reading fn open(&mut self) -> Result<()> { + if self.is_active() { + return Ok(()); + } + let file = File::open(&self.path)?; self.reader = Some(BufReader::new(file)); self.has_read = false; @@ -43,13 +47,24 @@ impl StreamRead for FileStreamReader { /// between returning the full file contents and returning `None`, producing the /// following repeating sequence: `Some(Vec), None, Some(Vec), None, ...` fn next(&mut self) -> Result>> { - self.has_read = !self.has_read; - if self.has_read { - Ok(Some(fs::read(&self.path)?)) - } else { - Ok(None) + self.has_read = false; + return Ok(None); } + + self.has_read = true; + + // Open the file if it's not already open + self.open()?; + + let reader = self.reader.as_mut().ok_or_else(|| { + anyhow::anyhow!("FileStreamReader: Reader is not initialized after opening the file") + })?; + + let mut buffer = Vec::new(); + reader.read_to_end(&mut buffer)?; + + Ok(Some(buffer)) } /// Close the stream diff --git a/common/src/zisk_lib_init.rs b/common/src/zisk_lib_init.rs index 51600496f..63690fd99 100644 --- a/common/src/zisk_lib_init.rs +++ b/common/src/zisk_lib_init.rs @@ -9,6 +9,8 @@ use crate::{ ExecutorStats, }; +use anyhow::Result; + #[derive(Debug, Default, Clone)] pub struct ZiskExecutionResult { pub executed_steps: u64, @@ -33,7 +35,7 @@ pub struct Stats { /// Extension trait that provides execution result access without Any boxing pub trait ZiskWitnessLibrary { fn set_stdin(&self, stdin: ZiskStdin); - fn set_hints_stream(&self, stream: StreamSource); + fn set_hints_stream(&self, stream: StreamSource) -> Result<()>; fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)>; } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 3b6633df3..594ca960c 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -68,6 +68,8 @@ use ziskemu::{EmuOptions, ZiskEmulator}; use crate::StaticSMBundle; +use anyhow::Result; + type DeviceMetricsByChunk = (ChunkId, Box); // (chunk_id, metrics) type DeviceMetricsList = Vec; pub type NestedDeviceMetricsList = HashMap; @@ -270,8 +272,8 @@ impl ZiskExecutor { *guard = stdin; } - pub fn set_hints_stream(&self, stream: StreamSource) { - self.hints_stream.lock().unwrap().set_hints_stream(stream); + pub fn set_hints_stream(&self, stream: StreamSource) -> Result<()> { + self.hints_stream.lock().unwrap().set_hints_stream(stream) } #[allow(clippy::type_complexity)] diff --git a/hints/src/hints_stream.rs b/hints/src/hints_stream.rs index de13aa6f8..9bb833fa5 100644 --- a/hints/src/hints_stream.rs +++ b/hints/src/hints_stream.rs @@ -68,10 +68,14 @@ impl Result<()> { // Stop the existing thread if running self.stop_thread(); + if !stream.is_active() { + stream.open()?; + } + // Create a new channel for communication with the thread let (tx, rx) = std::sync::mpsc::channel(); self.tx = Some(tx); @@ -86,6 +90,8 @@ impl Result { self.witness_lib.set_stdin(stdin); if let Some(stream) = hints_stream { - self.witness_lib.set_hints_stream(stream); + self.witness_lib + .set_hints_stream(stream) + .map_err(|e| anyhow::anyhow!("Error setting hints stream: {}", e))?; } let start = std::time::Instant::now(); diff --git a/witness-computation/Cargo.toml b/witness-computation/Cargo.toml index 9b82eb41f..1d71850b3 100644 --- a/witness-computation/Cargo.toml +++ b/witness-computation/Cargo.toml @@ -39,6 +39,8 @@ fields = { workspace=true } pil-std-lib = { workspace = true } tracing = { workspace = true } +anyhow = { workspace = true } + env_logger = "0.11" rayon = { workspace = true } diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 6ac1533c2..1efed10b6 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -32,6 +32,8 @@ use zisk_pil::{ ZISK_AIRGROUP_ID, }; +use anyhow::Result; + pub struct WitnessLib { elf_path: PathBuf, asm_path: Option, @@ -215,9 +217,11 @@ impl ZiskWitnessLibrary for WitnessLib { } } - fn set_hints_stream(&self, hints_stream: zisk_common::io::StreamSource) { + fn set_hints_stream(&self, hints_stream: zisk_common::io::StreamSource) -> Result<()> { if let Some(executor) = &self.executor { - executor.set_hints_stream(hints_stream); + executor.set_hints_stream(hints_stream) + } else { + Err(anyhow::anyhow!("Executor not initialized")) } } From 898b2c78d7fafaf32ddff763cd2c2e71d74e8317 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:25:28 +0000 Subject: [PATCH 047/782] Added stream control flow with CTRL_START/CTRL_END and async processing --- hints/src/hints_processor.rs | 117 ++++++++++++++++++++--------------- hints/src/hints_stream.rs | 32 +++++----- hints/src/lib.rs | 8 ++- 3 files changed, 93 insertions(+), 64 deletions(-) diff --git a/hints/src/hints_processor.rs b/hints/src/hints_processor.rs index fbfde1c33..d324b4112 100644 --- a/hints/src/hints_processor.rs +++ b/hints/src/hints_processor.rs @@ -50,6 +50,7 @@ use rayon::{ThreadPool, ThreadPoolBuilder}; use std::collections::VecDeque; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; +use tracing::debug; use ziskos::syscalls::SyscallPoint256; use crate::{secp256k1_ecdsa_verify, HintsProcessor}; @@ -243,10 +244,11 @@ impl PrecompileHintsProcessor { /// /// # Returns /// - /// * `Ok(())` - Hints were successfully dispatched (does not mean processing is complete) + /// * `Ok((Vec, bool))` - Tuple of (processed data, has_ctrl_end) where has_ctrl_end is true if CTRL_END was encountered /// * `Err` - If a previous error occurred or hints are malformed - pub fn process_hints(&self, hints: &[u64]) -> Result> { + pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result<(Vec, bool)> { let mut processed = Vec::new(); + let mut has_ctrl_end = false; // Parse hints and dispatch to pool let mut idx = 0; @@ -259,11 +261,28 @@ impl PrecompileHintsProcessor { let hint = PrecompileHint::from_u64_slice(hints, idx)?; let length = hint.data.len(); + // Validate hint type is in valid range before accessing stats array + if hint.hint_type >= NUM_HINT_TYPES { + return Err(anyhow::anyhow!("Invalid hint type: {}", hint.hint_type)); + } + self.stats[hint.hint_type as usize].fetch_add(1, Ordering::Relaxed); // Check if this is a control code or data hint type match hint.hint_type { CTRL_START => { + // CTRL_START must be the first message of the first batch + if !first_batch { + return Err(anyhow::anyhow!( + "CTRL_START can only be sent as the first message in the stream" + )); + } + if idx != 0 { + return Err(anyhow::anyhow!( + "CTRL_START must be the first hint in the batch, but found at index {}", + idx + )); + } // Reset global sequence and buffer at stream start self.reset(); // Control hint only; skip processing @@ -271,10 +290,19 @@ impl PrecompileHintsProcessor { continue; } CTRL_END => { - // Control hint only; wait for completion then skip processing + // Control hint only; wait for completion then set flag self.wait_for_completion()?; + has_ctrl_end = true; idx += length + 1; - continue; + + // CTRL_END should be the last message - verify and break + if idx < hints.len() { + return Err(anyhow::anyhow!( + "CTRL_END must be the last hint, but {} bytes remain", + hints.len() - idx + )); + } + break; } CTRL_CANCEL => { // Cancel current stream: set error and notify @@ -405,12 +433,12 @@ impl PrecompileHintsProcessor { idx += length + 1; } - println!("Processed hints stats:"); + debug!("Processed hints stats:"); for (i, count) in self.stats.iter().enumerate() { - println!("Hint type {}: {}", i, count.load(Ordering::Relaxed)); + debug!("Hint type {}: {}", i, count.load(Ordering::Relaxed)); } - Ok(processed) + Ok((processed, has_ctrl_end)) } /// Waits for all pending hints to be processed and drained. @@ -517,8 +545,8 @@ impl PrecompileHintsProcessor { } impl HintsProcessor for PrecompileHintsProcessor { - fn process_hints(&self, hints: &[u64]) -> Result> { - self.process_hints(hints) + fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result<(Vec, bool)> { + self.process_hints(hints, first_batch) } } @@ -545,7 +573,7 @@ mod tests { let data = vec![make_header(HINTS_TYPE_RESULT, 2), 0x111, 0x222]; // Dispatch should succeed and be non-blocking - assert!(p.process_hints(&data).is_ok()); + assert!(p.process_hints(&data, false).is_ok()); // Wait for completion should succeed assert!(p.wait_for_completion().is_ok()); @@ -566,7 +594,7 @@ mod tests { make_header(HINTS_TYPE_RESULT, 1), 0x333, ]; - assert!(p.process_hints(&data).is_ok()); + assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); // Verify all hints were processed (buffer empty, next_drain_seq advanced) @@ -581,8 +609,8 @@ mod tests { let data1 = vec![make_header(HINTS_TYPE_RESULT, 1), 0xAAA]; let data2 = vec![make_header(HINTS_TYPE_RESULT, 1), 0xBBB]; - assert!(p.process_hints(&data1).is_ok()); - assert!(p.process_hints(&data2).is_ok()); + assert!(p.process_hints(&data1, false).is_ok()); + assert!(p.process_hints(&data2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); // Verify sequence continued across calls @@ -595,7 +623,7 @@ mod tests { fn test_empty_input_ok() { let p = processor(); let data: Vec = vec![]; - assert!(p.process_hints(&data).is_ok()); + assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); // No hints processed @@ -609,16 +637,10 @@ mod tests { let p = processor(); let data = vec![make_header(999, 1), 0x1234]; - // Dispatch enqueues work - assert!(p.process_hints(&data).is_ok()); - - // Error surfaces on wait - let result = p.wait_for_completion(); + // Should return error immediately during validation + let result = p.process_hints(&data, false); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("error")); - - // Error flag should be set - assert!(p.state.error_flag.load(Ordering::Acquire)); + assert!(result.unwrap_err().to_string().contains("Invalid hint type")); } #[test] @@ -627,32 +649,29 @@ mod tests { // First valid, then invalid type let data = vec![make_header(HINTS_TYPE_RESULT, 1), 0x111, make_header(999, 0)]; - let _ = p.process_hints(&data); - let result = p.wait_for_completion(); - + // Should error immediately when encountering invalid hint type + let result = p.process_hints(&data, false); assert!(result.is_err()); - assert!(p.state.error_flag.load(Ordering::Acquire)); + assert!(result.unwrap_err().to_string().contains("Invalid hint type")); } #[test] fn test_reset_clears_error() { let p = processor(); let bad = vec![make_header(999, 0)]; - let _ = p.process_hints(&bad); - - // Wait briefly for error to propagate - std::thread::sleep(std::time::Duration::from_millis(10)); + let result = p.process_hints(&bad, false); - // Error should be set - assert!(p.state.error_flag.load(Ordering::Acquire)); + // Should get synchronous error for invalid hint type + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Invalid hint type")); - // Reset should clear error + // Reset should clear any error state p.reset(); assert!(!p.state.error_flag.load(Ordering::Acquire)); - // Should be able to process new hints + // Should be able to process new hints after reset let good = vec![make_header(HINTS_TYPE_RESULT, 1), 0x42]; - assert!(p.process_hints(&good).is_ok()); + assert!(p.process_hints(&good, false).is_ok()); assert!(p.wait_for_completion().is_ok()); let queue = p.state.queue.lock().unwrap(); @@ -666,7 +685,7 @@ mod tests { // First batch increments sequence let batch1 = vec![make_header(HINTS_TYPE_RESULT, 1), 0x01]; - p.process_hints(&batch1).unwrap(); + p.process_hints(&batch1, false).unwrap(); p.wait_for_completion().unwrap(); // Sequence should be at 1 @@ -677,7 +696,7 @@ mod tests { // Send START control - should reset sequence let start = vec![make_ctrl_header(CTRL_START, 0)]; - p.process_hints(&start).unwrap(); + p.process_hints(&start, true).unwrap(); // Sequence should be reset to 0 { @@ -688,10 +707,10 @@ mod tests { // Process new batch let batch2 = vec![make_header(HINTS_TYPE_RESULT, 1), 0x02]; - p.process_hints(&batch2).unwrap(); + p.process_hints(&batch2, false).unwrap(); let end = vec![make_ctrl_header(CTRL_END, 0)]; - p.process_hints(&end).unwrap(); + p.process_hints(&end, false).unwrap(); // Should have processed 1 hint (starting from 0 again) let queue = p.state.queue.lock().unwrap(); @@ -705,11 +724,11 @@ mod tests { // Dispatch hints let data = vec![make_header(HINTS_TYPE_RESULT, 1), 0x10, make_header(HINTS_TYPE_RESULT, 1), 0x20]; - p.process_hints(&data).unwrap(); + p.process_hints(&data, false).unwrap(); // END should wait internally let end = vec![make_ctrl_header(CTRL_END, 0)]; - p.process_hints(&end).unwrap(); + p.process_hints(&end, false).unwrap(); // Buffer should already be empty { @@ -727,7 +746,7 @@ mod tests { let p = processor(); let cancel = vec![make_ctrl_header(CTRL_CANCEL, 0)]; - let result = p.process_hints(&cancel); + let result = p.process_hints(&cancel, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("cancelled")); @@ -740,7 +759,7 @@ mod tests { let p = processor(); let signal_err = vec![make_ctrl_header(CTRL_ERROR, 0)]; - let result = p.process_hints(&signal_err); + let result = p.process_hints(&signal_err, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("error")); @@ -765,7 +784,7 @@ mod tests { } let start = Instant::now(); - p.process_hints(&data).unwrap(); + p.process_hints(&data, false).unwrap(); p.wait_for_completion().unwrap(); let duration = start.elapsed(); @@ -800,7 +819,7 @@ mod tests { data.push(make_header(HINTS_TYPE_RESULT, 1)); data.push((batch_id * HINTS_PER_BATCH + i) as u64); } - p.process_hints(&data).unwrap(); + p.process_hints(&data, false).unwrap(); } p.wait_for_completion().unwrap(); @@ -835,7 +854,7 @@ mod tests { for _iter in 0..ITERATIONS { // Reset at start of each iteration let reset = vec![make_ctrl_header(CTRL_START, 0)]; - p.process_hints(&reset).unwrap(); + p.process_hints(&reset, true).unwrap(); // Process batch let mut data = Vec::with_capacity(HINTS_PER_ITER * 2); @@ -843,11 +862,11 @@ mod tests { data.push(make_header(HINTS_TYPE_RESULT, 1)); data.push(i as u64); } - p.process_hints(&data).unwrap(); + p.process_hints(&data, false).unwrap(); // End stream let end = vec![make_ctrl_header(CTRL_END, 0)]; - p.process_hints(&end).unwrap(); + p.process_hints(&end, false).unwrap(); } let duration = start.elapsed(); diff --git a/hints/src/hints_stream.rs b/hints/src/hints_stream.rs index 9bb833fa5..38d197a34 100644 --- a/hints/src/hints_stream.rs +++ b/hints/src/hints_stream.rs @@ -6,7 +6,7 @@ use anyhow::Result; use std::sync::mpsc::{Receiver, Sender}; use std::sync::Arc; use std::thread::{self, JoinHandle}; -use tracing::info; +use tracing::debug; use zisk_common::io::{StreamRead, StreamSource}; enum ThreadCommand { @@ -118,30 +118,34 @@ impl Result<()> { - let mut processed = Vec::new(); + let mut first_batch = true; while let Some(hints) = stream.next()? { let hints = zisk_common::reinterpret_vec(hints)?; - processed.extend(hints_processor.process_hints(&hints)?); - } + let (processed, has_ctrl_end) = hints_processor.process_hints(&hints, first_batch)?; - // // STore processed hints in a temp file for debugging - // std::fs::write( - // "/data/hints/processed_hints.bin", - // &zisk_common::reinterpret_vec::(processed.clone())?, - // )?; - // // // read processed into a /data/hints/precompile_cache.bin - // // let processed = std::fs::read("/data/hints/precompile_cache.bin")?; - // // let processed = zisk_common::reinterpret_vec::(processed)?; + first_batch = false; - info!("Precompile hints have generated {} u64 values", processed.len()); + if !processed.is_empty() { + hints_sink.submit(processed)?; + } - hints_sink.submit(processed) + // Break if CTRL_END was encountered + if has_ctrl_end { + debug!("CTRL_END encountered, stopping hint processing"); + break; + } + } + + Ok(()) } /// Trigger the background thread to process hints asynchronously. diff --git a/hints/src/lib.rs b/hints/src/lib.rs index 968d66682..f0f745800 100644 --- a/hints/src/lib.rs +++ b/hints/src/lib.rs @@ -14,7 +14,13 @@ pub use hints_stream::HintsStream; pub use secp256k1::*; pub trait HintsProcessor { - fn process_hints(&self, hints: &[u64]) -> anyhow::Result>; + /// Process hints and return the processed data along with a flag indicating if CTRL_END was encountered. + /// + /// # Returns + /// A tuple of (processed_hints, has_ctrl_end) where: + /// - processed_hints: Vec - The processed hint data + /// - has_ctrl_end: bool - True if CTRL_END was found (signals end of batch) + fn process_hints(&self, hints: &[u64], first_batch: bool) -> anyhow::Result<(Vec, bool)>; } pub trait HintsSink { From 3aafb6bf2999a57a1a43a7ed1d8ac8fe2604ca63 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 10 Dec 2025 11:47:47 +0100 Subject: [PATCH 048/782] WIP DMA --- Cargo.toml | 34 +- common/src/bus/data_bus_operation.rs | 73 ++- core/src/zisk_inst.rs | 3 +- core/src/zisk_ops.rs | 72 +++ core/src/zisk_rom_2_asm.rs | 6 + executor/src/executor.rs | 13 +- pil/src/pil_helpers/traces.rs | 608 ++++++++++-------- pil/zisk.pil | 21 +- .../src/mem_inputs/bn254_curve_add.rs | 9 +- .../src/mem_inputs/bn254_curve_dbl.rs | 5 +- precompiles/common/src/lib.rs | 36 ++ precompiles/dma/Cargo.toml | 4 +- precompiles/dma/dma.md | 14 + precompiles/dma/pil/dma.pil | 268 ++++---- precompiles/dma/pil/dma_64_aligned.pil | 150 +++++ precompiles/dma/pil/dma_pre_post.pil | 104 +++ precompiles/dma/pil/dma_pre_post_table.pil | 27 + precompiles/dma/pil/dma_rom.pil | 47 ++ precompiles/dma/pil/dma_unaligned.pil | 229 +++++++ precompiles/dma/pil/tools.pil | 13 + precompiles/dma/src/add256_bus_device.rs | 148 ----- precompiles/dma/src/add256_gen_mem_inputs.rs | 111 ---- precompiles/dma/src/add256_manager.rs | 92 --- precompiles/dma/src/dma.rs | 106 +++ precompiles/dma/src/dma_64_aligned.rs | 168 +++++ ...dd256_input.rs => dma_64_aligned_input.rs} | 19 +- precompiles/dma/src/dma_bus_device.rs | 181 ++++++ .../{add256_constants.rs => dma_constants.rs} | 0 precompiles/dma/src/dma_gen_mem_inputs.rs | 291 +++++++++ precompiles/dma/src/dma_helpers.rs | 44 ++ precompiles/dma/src/dma_input.rs | 53 ++ .../{add256_instance.rs => dma_instance.rs} | 76 +-- precompiles/dma/src/dma_manager.rs | 92 +++ .../src/{add256_planner.rs => dma_planner.rs} | 32 +- precompiles/dma/src/dma_pre_post.rs | 169 +++++ precompiles/dma/src/dma_pre_post_input.rs | 25 + .../dma/src/{add256.rs => dma_unaligned.rs} | 38 +- precompiles/dma/src/dma_unaligned_input.rs | 32 + precompiles/dma/src/lib.rs | 38 +- state-machines/main/pil/main.pil | 1 + state-machines/main/pil/registers.pil | 67 ++ state-machines/mem/pil/dual_byte.pil | 7 +- state-machines/mem/pil/mem.pil | 10 + ziskos/entrypoint/src/memcpy_test.rs | 489 ++++++++++++++ 44 files changed, 3141 insertions(+), 884 deletions(-) create mode 100644 precompiles/dma/dma.md create mode 100644 precompiles/dma/pil/dma_64_aligned.pil create mode 100644 precompiles/dma/pil/dma_pre_post.pil create mode 100644 precompiles/dma/pil/dma_pre_post_table.pil create mode 100644 precompiles/dma/pil/dma_rom.pil create mode 100644 precompiles/dma/pil/dma_unaligned.pil create mode 100644 precompiles/dma/pil/tools.pil delete mode 100644 precompiles/dma/src/add256_bus_device.rs delete mode 100644 precompiles/dma/src/add256_gen_mem_inputs.rs delete mode 100644 precompiles/dma/src/add256_manager.rs create mode 100644 precompiles/dma/src/dma.rs create mode 100644 precompiles/dma/src/dma_64_aligned.rs rename precompiles/dma/src/{add256_input.rs => dma_64_aligned_input.rs} (75%) create mode 100644 precompiles/dma/src/dma_bus_device.rs rename precompiles/dma/src/{add256_constants.rs => dma_constants.rs} (100%) create mode 100644 precompiles/dma/src/dma_gen_mem_inputs.rs create mode 100644 precompiles/dma/src/dma_helpers.rs create mode 100644 precompiles/dma/src/dma_input.rs rename precompiles/dma/src/{add256_instance.rs => dma_instance.rs} (70%) create mode 100644 precompiles/dma/src/dma_manager.rs rename precompiles/dma/src/{add256_planner.rs => dma_planner.rs} (75%) create mode 100644 precompiles/dma/src/dma_pre_post.rs create mode 100644 precompiles/dma/src/dma_pre_post_input.rs rename precompiles/dma/src/{add256.rs => dma_unaligned.rs} (82%) create mode 100644 precompiles/dma/src/dma_unaligned_input.rs create mode 100644 state-machines/main/pil/registers.pil create mode 100644 ziskos/entrypoint/src/memcpy_test.rs diff --git a/Cargo.toml b/Cargo.toml index 7d18b147f..d282ac6f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "precompiles/keccakf", "precompiles/sha256f", "precompiles/big_int", + "precompiles/dma", "lib-c", "lib-float", "emulator-asm/asm-runner", @@ -76,6 +77,7 @@ precompiles-helpers = { path = "precompiles/helpers" } precomp-keccakf = { path = "precompiles/keccakf" } precomp-sha256f = { path = "precompiles/sha256f" } precomp-big-int = { path = "precompiles/big_int" } +precomp-dma = { path = "precompiles/dma" } riscv = { path = "riscv" } rom-setup = { path = "rom-setup" } server = { path = "server" } @@ -99,23 +101,23 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -# proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -# proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -# proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -# proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -# proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -# pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -# witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } -# fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.13.0" } # Proofman Local development -proofman = { path = "../pil2-proofman/proofman" } -proofman-common = { path = "../pil2-proofman/common" } -proofman-macros = { path = "../pil2-proofman/macros" } -proofman-verifier = { path = "../pil2-proofman/verifier" } -proofman-util = { path = "../pil2-proofman/util" } -pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } -witness = { path = "../pil2-proofman/witness" } -fields = { path = "../pil2-proofman/fields" } +# proofman = { path = "../pil2-proofman/proofman" } +# proofman-common = { path = "../pil2-proofman/common" } +# proofman-macros = { path = "../pil2-proofman/macros" } +# proofman-verifier = { path = "../pil2-proofman/verifier" } +# proofman-util = { path = "../pil2-proofman/util" } +# pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } +# witness = { path = "../pil2-proofman/witness" } +# fields = { path = "../pil2-proofman/fields" } # External dependencies rayon = "1.10" diff --git a/common/src/bus/data_bus_operation.rs b/common/src/bus/data_bus_operation.rs index ec2a90b72..8ef056ff9 100644 --- a/common/src/bus/data_bus_operation.rs +++ b/common/src/bus/data_bus_operation.rs @@ -69,8 +69,15 @@ pub const OPERATION_BUS_BLS12_381_COMPLEX_MUL_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; // bus_data_size + 4 params (&a, &b, cin, &c, a, b) -pub const OPERATION_BUS_ADD_256_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 4 * PARAMS_SIZE + 2 * DATA_256_BITS_SIZE + SINGLE_RESULT_SIZE; +pub const OPERATION_BUS_ADD_256_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + + 4 * PARAMS_SIZE + + 2 * DATA_256_BITS_SIZE + + SINGLE_RESULT_SIZE; + +// 5 bus_precompiled_data + count +pub const OPERATION_BUS_DMA_MEMCPY_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1; +// 5 bus_precompiled_data + count + count_eq +pub const OPERATION_BUS_DMA_MEMCMP_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2; // 4 bus_data + 5 addr + 4 x 384 = 4 + 5 + 4 * 6 = 33 pub const MAX_OPERATION_DATA_SIZE: usize = OPERATION_BUS_ARITH_384_MOD_DATA_SIZE; @@ -112,6 +119,8 @@ pub type OperationBls12_381ComplexAddData = [D; OPERATION_BUS_BLS12_381_COMPL pub type OperationBls12_381ComplexSubData = [D; OPERATION_BUS_BLS12_381_COMPLEX_SUB_DATA_SIZE]; pub type OperationBls12_381ComplexMulData = [D; OPERATION_BUS_BLS12_381_COMPLEX_MUL_DATA_SIZE]; pub type OperationAdd256Data = [D; OPERATION_BUS_ADD_256_DATA_SIZE]; +pub type OperationDmaMemCpyData = [D; OPERATION_BUS_DMA_MEMCPY_DATA_SIZE]; +pub type OperationDmaMemCmpData = [D; OPERATION_BUS_DMA_MEMCMP_DATA_SIZE]; pub enum ExtOperationData { OperationData(OperationData), @@ -133,6 +142,8 @@ pub enum ExtOperationData { OperationBls12_381ComplexSubData(OperationBls12_381ComplexSubData), OperationBls12_381ComplexMulData(OperationBls12_381ComplexMulData), OperationAdd256Data(OperationAdd256Data), + OperationDmaMemCpyData(OperationDmaMemCpyData), + OperationDmaMemCmpData(OperationDmaMemCmpData), } const KECCAK_OP: u8 = ZiskOp::Keccak.code(); @@ -153,6 +164,8 @@ const BLS12_381_COMPLEX_ADD_OP: u8 = ZiskOp::Bls12_381ComplexAdd.code(); const BLS12_381_COMPLEX_SUB_OP: u8 = ZiskOp::Bls12_381ComplexSub.code(); const BLS12_381_COMPLEX_MUL_OP: u8 = ZiskOp::Bls12_381ComplexMul.code(); const ADD256_OP: u8 = ZiskOp::Add256.code(); +const DMA_MEMCPY_OP: u8 = ZiskOp::DmaMemCpy.code(); +const DMA_MEMCMP_OP: u8 = ZiskOp::DmaMemCmp.code(); // impl> TryFrom<&[D]> for ExtOperationData { impl> TryFrom<&[D]> for ExtOperationData { @@ -254,6 +267,16 @@ impl> TryFrom<&[D]> for ExtOperationData { data.try_into().map_err(|_| "Invalid OperationAdd256Data size")?; Ok(ExtOperationData::OperationAdd256Data(array)) } + DMA_MEMCPY_OP => { + let array: OperationDmaMemCpyData = + data.try_into().map_err(|_| "Invalid OperationDmaMemCpyData size")?; + Ok(ExtOperationData::OperationDmaMemCpyData(array)) + } + DMA_MEMCMP_OP => { + let array: OperationDmaMemCmpData = + data.try_into().map_err(|_| "Invalid OperationDmaMemCmpData size")?; + Ok(ExtOperationData::OperationDmaMemCmpData(array)) + } _ => { let array: OperationData = data.try_into().map_err(|_| "Invalid OperationData size")?; @@ -501,6 +524,29 @@ impl OperationBusData { } _ => ExtOperationData::OperationData([op, op_type, a, b]), }, + ZiskOperationType::Dma => match inst.op { + DMA_MEMCPY_OP => { + let mut data = unsafe { + uninit_array::().assume_init() + }; + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationDmaMemCpyData(data) + } + DMA_MEMCMP_OP => { + let mut data = unsafe { + uninit_array::().assume_init() + }; + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationDmaMemCmpData(data) + } + _ => ExtOperationData::OperationData([op, op_type, a, b]), + }, _ => ExtOperationData::OperationData([op, op_type, a, b]), } @@ -700,6 +746,21 @@ impl OperationBusData { &buffer[..OPERATION_BUS_DATA_SIZE] } }, + ZiskOperationType::Dma => match inst.op { + DMA_MEMCPY_OP | DMA_MEMCMP_OP => { + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] + .copy_from_slice(&ctx.precompiled.input_data); + &buffer[..len] + } + _ => { + buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); + &buffer[..OPERATION_BUS_DATA_SIZE] + } + }, _ => { buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); @@ -737,6 +798,8 @@ impl OperationBusData { ExtOperationData::OperationBls12_381ComplexSubData(d) => d[OP] as u8, ExtOperationData::OperationBls12_381ComplexMulData(d) => d[OP] as u8, ExtOperationData::OperationAdd256Data(d) => d[OP] as u8, + ExtOperationData::OperationDmaMemCpyData(d) => d[OP] as u8, + ExtOperationData::OperationDmaMemCmpData(d) => d[OP] as u8, } } @@ -769,6 +832,8 @@ impl OperationBusData { ExtOperationData::OperationBls12_381ComplexSubData(d) => d[OP_TYPE], ExtOperationData::OperationBls12_381ComplexMulData(d) => d[OP_TYPE], ExtOperationData::OperationAdd256Data(d) => d[OP_TYPE], + ExtOperationData::OperationDmaMemCpyData(d) => d[OP_TYPE], + ExtOperationData::OperationDmaMemCmpData(d) => d[OP_TYPE], } } @@ -801,6 +866,8 @@ impl OperationBusData { ExtOperationData::OperationBls12_381ComplexSubData(d) => d[A], ExtOperationData::OperationBls12_381ComplexMulData(d) => d[A], ExtOperationData::OperationAdd256Data(d) => d[A], + ExtOperationData::OperationDmaMemCpyData(d) => d[A], + ExtOperationData::OperationDmaMemCmpData(d) => d[A], } } @@ -833,6 +900,8 @@ impl OperationBusData { ExtOperationData::OperationBls12_381ComplexSubData(d) => d[B], ExtOperationData::OperationBls12_381ComplexMulData(d) => d[B], ExtOperationData::OperationAdd256Data(d) => d[B], + ExtOperationData::OperationDmaMemCpyData(d) => d[B], + ExtOperationData::OperationDmaMemCmpData(d) => d[B], } } } diff --git a/core/src/zisk_inst.rs b/core/src/zisk_inst.rs index db1ada47e..fb86be053 100644 --- a/core/src/zisk_inst.rs +++ b/core/src/zisk_inst.rs @@ -82,6 +82,7 @@ pub enum ZiskOperationType { ArithEq, ArithEq384, BigInt, // Note: Add new core operations here + Dma, // ZisK Free Input Operations FcallParam, Fcall, @@ -101,7 +102,7 @@ pub const ARITH_EQ_384_OP_TYPE_ID: u32 = ZiskOperationType::ArithEq384 as u32; pub const BIG_INT_OP_TYPE_ID: u32 = ZiskOperationType::BigInt as u32; pub const FCALL_PARAM_OP_TYPE_ID: u32 = ZiskOperationType::FcallParam as u32; pub const FCALL_OP_TYPE_ID: u32 = ZiskOperationType::Fcall as u32; -pub const FCALL_GET_OP_TYPE_ID: u32 = ZiskOperationType::FcallGet as u32; +pub const DMA_OP_TYPE_ID: u32 = ZiskOperationType::Dma as u32; /// ZisK instruction definition /// diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 5bba53c0f..ea595ac13 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -51,6 +51,7 @@ pub enum OpType { Fcall, ArithEq384, BigInt, + Dma, } impl From for ZiskOperationType { @@ -67,6 +68,7 @@ impl From for ZiskOperationType { OpType::Fcall => ZiskOperationType::Fcall, OpType::ArithEq384 => ZiskOperationType::ArithEq384, OpType::BigInt => ZiskOperationType::BigInt, + OpType::Dma => ZiskOperationType::Dma, } } } @@ -87,6 +89,7 @@ impl Display for OpType { Self::Fcall => write!(f, "Fcall"), Self::ArithEq384 => write!(f, "Arith384"), Self::BigInt => write!(f, "BigInt"), + Self::Dma => write!(f, "Dma"), } } } @@ -108,6 +111,7 @@ impl FromStr for OpType { "fcall" => Ok(Self::Fcall), "aeq384" => Ok(Self::ArithEq384), "bint" => Ok(Self::BigInt), + "dma" => Ok(Self::Dma), _ => Err(InvalidOpTypeError), } } @@ -401,6 +405,8 @@ define_ops! { (Bls12_381ComplexAdd, "bls12_381_complex_add", ArithEq384, ARITH_EQ_384_COST, 0xe5, 208, 96, opc_bls12_381_complex_add, op_bls12_381_complex_add, ops_bls12_381_complex_add), (Bls12_381ComplexSub, "bls12_381_complex_sub", ArithEq384, ARITH_EQ_384_COST, 0xe6, 208, 96, opc_bls12_381_complex_sub, op_bls12_381_complex_sub, ops_bls12_381_complex_sub), (Bls12_381ComplexMul, "bls12_381_complex_mul", ArithEq384, ARITH_EQ_384_COST, 0xe7, 208, 96, opc_bls12_381_complex_mul, op_bls12_381_complex_mul, ops_bls12_381_complex_mul), + (DmaMemCmp, "dma_mem_cmp", Dma, 0, 0xee, 208, 96, opc_dma_memcmp, op_dma_memcmp, ops_dma_memcmp), + (DmaMemCpy, "dma_mem_cpy", Dma, 0, 0xef, 208, 96, opc_dma_memcpy, op_dma_memcpy, ops_dma_memcpy), } /* INTERNAL operations */ @@ -2262,3 +2268,69 @@ pub fn opc_halt(ctx: &mut InstContext) { ctx.c = 0; ctx.flag = false; } + +pub fn opc_dma_memcpy(ctx: &mut InstContext) { + const WORDS: usize = 4 + 1 + 2 * 4; + let mut data = [0u64; WORDS]; + + precompiled_load_data_with_result(ctx, 4, 2, 4, 0, &mut data, "add256"); + + if ctx.emulation_mode != EmulationMode::ConsumeMemReads { + // ignore 3 indirections + // 0 - addr_a + // 1 - addr_b + // 2 - cin + // 3 - addr_c + let cin = data[2]; + let (params, rest) = data.split_at(4); // params(4) + let (a, rest) = rest.split_at(4); + let (b, _) = rest.split_at(4); + + let a: &[u64; 4] = a.try_into().expect("opc_add256: a.len != 4"); + let b: &[u64; 4] = b.try_into().expect("opc_add256: b.len != 4"); + let mut c = [0u64; 4]; + let cout = precompiles_helpers::add256(a, b, cin, &mut c); + + let c_addr = params[3]; + for (i, c_item) in c.iter().enumerate() { + ctx.mem.write(c_addr + (8 * i as u64), *c_item, 8); + } + if let EmulationMode::GenerateMemReads = ctx.emulation_mode { + ctx.precompiled.input_data[4 + 2 * 4] = cout; + } + ctx.c = cout; + ctx.flag = cout != 0; + } else { + assert!(data[4 + 2 * 4] <= 1, "opc_add256: cout > 1"); + ctx.c = data[4 + 2 * 4]; + ctx.flag = data[4 + 2 * 4] != 0; + } +} + +/// Unimplemented. Arith256 can only be called from the system call context via InstContext. +/// This is provided just for completeness. +#[inline(always)] +pub fn op_dma_memcpy(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_dma_memcpy() is not implemented"); +} + +#[inline(always)] +pub fn ops_dma_memcpy(ctx: &InstContext, stats: &mut dyn OpStats) { + unimplemented!("ops_dma_memcpy() is not implemented"); +} + +pub fn opc_dma_memcmp(ctx: &mut InstContext) { + unimplemented!("opc_dma_memcmp() is not implemented"); +} + +/// Unimplemented. Arith256 can only be called from the system call context via InstContext. +/// This is provided just for completeness. +#[inline(always)] +pub fn op_dma_memcmp(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_dma_memcmp() is not implemented"); +} + +#[inline(always)] +pub fn ops_dma_memcmp(ctx: &InstContext, stats: &mut dyn OpStats) { + unimplemented!("ops_dma_memcmp() is not implemented"); +} diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 9750df507..5bfdc8773 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -6629,6 +6629,12 @@ impl ZiskRom2Asm { ctx.c.is_saved = true; ctx.flag_is_always_zero = false; } + ZiskOp::DmaMemCpy => { + unimplemented!(); + } + ZiskOp::DmaMemCmp => { + unimplemented!(); + } } } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index d0f86ea0e..4e2e93201 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -540,8 +540,7 @@ impl ZiskExecutor { let mut main_instances = self.main_instances.write().unwrap(); for mut plan in main_planning { - let global_id = - pctx.add_instance_assign(plan.airgroup_id, plan.air_id, plan.n_threads_witness); + let global_id = pctx.add_instance_assign(plan.airgroup_id, plan.air_id); plan.set_global_id(global_id); global_ids.write().unwrap().push(global_id); main_instances @@ -646,16 +645,10 @@ impl ZiskExecutor { { // If this is the ROM instance, we need to add it to the proof context // with the rank 0. - pctx.add_instance_assign_first_partition( - plan.airgroup_id, - plan.air_id, - plan.n_threads_witness, - ) + pctx.add_instance_assign_first_partition(plan.airgroup_id, plan.air_id) } else { match plan.instance_type { - InstanceType::Instance => { - pctx.add_instance(plan.airgroup_id, plan.air_id, plan.n_threads_witness) - } + InstanceType::Instance => pctx.add_instance(plan.airgroup_id, plan.air_id), InstanceType::Table => pctx.add_table(plan.airgroup_id, plan.air_id), } }; diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 9b12543cc..be0add480 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -5,18 +5,18 @@ #![allow(non_upper_case_globals)] #![allow(dead_code)] -use fields::PrimeField64; use proofman_common as common; use proofman_common::GenericTrace; use proofman_common::PackedInfoConst; pub use proofman_macros::trace_row; pub use proofman_macros::values; +use fields::PrimeField64; use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "f84f0cfddc746ef78a60d60b39590d435cc7459acf576c2d05c93564d2971402"; +pub const PILOUT_HASH: &str = "18a904b2d072776febbdc26adef81d5df53c2eadc100d49e2fe1dc659ec6364a"; //AIRGROUP CONSTANTS @@ -24,53 +24,63 @@ pub const ZISK_AIRGROUP_ID: usize = 0; //AIR CONSTANTS -pub const MAIN_AIR_IDS: &[usize] = &[0]; +pub const DMA_AIR_IDS: &[usize] = &[0]; + +pub const DMA_64_ALIGNED_AIR_IDS: &[usize] = &[1]; + +pub const DMA_UNALIGNED_AIR_IDS: &[usize] = &[2]; + +pub const DMA_PRE_POST_AIR_IDS: &[usize] = &[3]; + +pub const MAIN_AIR_IDS: &[usize] = &[4]; -pub const ROM_AIR_IDS: &[usize] = &[1]; +pub const ROM_AIR_IDS: &[usize] = &[5]; -pub const MEM_AIR_IDS: &[usize] = &[2]; +pub const MEM_AIR_IDS: &[usize] = &[6]; -pub const ROM_DATA_AIR_IDS: &[usize] = &[3]; +pub const ROM_DATA_AIR_IDS: &[usize] = &[7]; -pub const INPUT_DATA_AIR_IDS: &[usize] = &[4]; +pub const INPUT_DATA_AIR_IDS: &[usize] = &[8]; -pub const MEM_ALIGN_AIR_IDS: &[usize] = &[5]; +pub const MEM_ALIGN_AIR_IDS: &[usize] = &[9]; -pub const MEM_ALIGN_BYTE_AIR_IDS: &[usize] = &[6]; +pub const MEM_ALIGN_BYTE_AIR_IDS: &[usize] = &[10]; -pub const MEM_ALIGN_READ_BYTE_AIR_IDS: &[usize] = &[7]; +pub const MEM_ALIGN_READ_BYTE_AIR_IDS: &[usize] = &[11]; -pub const MEM_ALIGN_WRITE_BYTE_AIR_IDS: &[usize] = &[8]; +pub const MEM_ALIGN_WRITE_BYTE_AIR_IDS: &[usize] = &[12]; -pub const ARITH_AIR_IDS: &[usize] = &[9]; +pub const ARITH_AIR_IDS: &[usize] = &[13]; -pub const BINARY_AIR_IDS: &[usize] = &[10]; +pub const BINARY_AIR_IDS: &[usize] = &[14]; -pub const BINARY_ADD_AIR_IDS: &[usize] = &[11]; +pub const BINARY_ADD_AIR_IDS: &[usize] = &[15]; -pub const BINARY_EXTENSION_AIR_IDS: &[usize] = &[12]; +pub const BINARY_EXTENSION_AIR_IDS: &[usize] = &[16]; -pub const ADD_256_AIR_IDS: &[usize] = &[13]; +pub const ADD_256_AIR_IDS: &[usize] = &[17]; -pub const ARITH_EQ_AIR_IDS: &[usize] = &[14]; +pub const ARITH_EQ_AIR_IDS: &[usize] = &[18]; -pub const ARITH_EQ_384_AIR_IDS: &[usize] = &[15]; +pub const ARITH_EQ_384_AIR_IDS: &[usize] = &[19]; -pub const KECCAKF_AIR_IDS: &[usize] = &[16]; +pub const KECCAKF_AIR_IDS: &[usize] = &[20]; -pub const SHA_256_F_AIR_IDS: &[usize] = &[17]; +pub const SHA_256_F_AIR_IDS: &[usize] = &[21]; -pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[18]; +pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[22]; -pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[19]; +pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[23]; + +pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[24]; -pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[20]; //PUBLICS use serde::Deserialize; use serde::Serialize; use serde_arrays; + fn default_array_rom_root() -> [u64; 4] { [0; 4] } @@ -79,279 +89,384 @@ fn default_array_inputs() -> [u64; 64] { [0; 64] } + #[derive(Debug, Serialize, Deserialize)] pub struct ZiskPublics { #[serde(default = "default_array_rom_root", with = "serde_arrays")] pub rom_root: [u64; 4], #[serde(default = "default_array_inputs", with = "serde_arrays")] pub inputs: [u64; 64], + } impl Default for ZiskPublics { fn default() -> Self { - Self { rom_root: [0; 4], inputs: [0; 64] } + Self { + rom_root: [0; 4], + inputs: [0; 64], + } } } values!(ZiskPublicValues { rom_root: [F; 4], inputs: [F; 64], }); - + values!(ZiskProofValues { enable_input_data: F, }); + +trace_row!(DmaFixedRow { + __L1__: F, +}); +pub type DmaFixed = GenericTrace, 2097152, 0, 0>; + +trace_row!(DmaTraceRow { + h_count:ubit(24), count_ge_32:bit, l_count:ubit(6), h_src64:ubit(21), l_src64:u8, src_offset:ubit(3), h_dst64:ubit(21), l_dst64:u8, dst_offset:ubit(3), main_step:u32, sel:bit, use_pre:bit, use_memcpy:bit, use_post:bit, src64_inc_by_pre:bit, pre_count:ubit(3), l_count64:ubit(3), src_offset_after_pre:ubit(3), reg_prev_mem_step:bit, +}); +pub type DmaTrace = GenericTrace, 2097152, 0, 0>; + + +pub type DmaTracePacked = GenericTrace, 2097152, 0, 0>; + + +trace_row!(Dma64AlignedFixedRow { + __L1__: F, +}); +pub type Dma64AlignedFixed = GenericTrace, 2097152, 0, 1>; + +trace_row!(Dma64AlignedTraceRow { + main_step:u32, src64:ubit(29), dst64:ubit(29), count:u32, value:[[u32; 2]; 4], sel_op:[bit; 4], seq_end:bit, is_mem_eq:bit, +}); +pub type Dma64AlignedTrace = GenericTrace, 2097152, 0, 1>; + + +pub type Dma64AlignedTracePacked = GenericTrace, 2097152, 0, 1>; + + +trace_row!(DmaUnalignedFixedRow { + __L1__: F, +}); +pub type DmaUnalignedFixed = GenericTrace, 2097152, 0, 2>; + +trace_row!(DmaUnalignedTraceRow { + main_step:u32, src64:ubit(29), dst64:ubit(29), count:u32, value:[u32; 2], seq_end:bit, sel:bit, previous_seq_end:bit, is_mem_eq:bit, offset_7:bit, offset_6:bit, offset_5:bit, offset_4:bit, offset_3:bit, offset_2:bit, read_bytes:[u8; 8], write_value:[u32; 2], +}); +pub type DmaUnalignedTrace = GenericTrace, 2097152, 0, 2>; + + +pub type DmaUnalignedTracePacked = GenericTrace, 2097152, 0, 2>; + + +trace_row!(DmaPrePostFixedRow { + __L1__: F, +}); +pub type DmaPrePostFixed = GenericTrace, 2097152, 0, 3>; + +trace_row!(DmaPrePostTraceRow { + main_step:u32, src_addr:ubit(29), dst_addr:ubit(29), dst_offset:ubit(3), count:ubit(3), offset_7:bit, offset_6:bit, offset_5:bit, offset_4:bit, offset_3:bit, offset_2:bit, offset_1:bit, enabled:bit, enabled_second_read:bit, bytes:[u8; 24], selb:[bit; 8], write_value:[u32; 2], +}); +pub type DmaPrePostTrace = GenericTrace, 2097152, 0, 3>; + + +pub type DmaPrePostTracePacked = GenericTrace, 2097152, 0, 3>; + trace_row!(MainFixedRow { SEGMENT_L1: F, SEGMENT_STEP: F, __L1__: F, }); -pub type MainFixed = GenericTrace, 4194304, 0, 0>; +pub type MainFixed = GenericTrace, 4194304, 0, 4>; trace_row!(MainTraceRow { - a:[u32; 2], b:[u32; 2], c:[u32; 2], flag:bit, pc:u32, a_src_imm:bit, a_src_mem:bit, a_offset_imm0:u64, a_imm1:u32, a_src_step:bit, b_src_imm:bit, b_src_mem:bit, b_offset_imm0:u64, b_imm1:u32, b_src_ind:bit, ind_width:ubit(4), is_external_op:bit, op:u8, store_ra:bit, store_mem:bit, store_ind:bit, store_offset:u64, set_pc:bit, jmp_offset1:u64, jmp_offset2:u64, m32:bit, addr1:u32, a_reg_prev_mem_step:ubit(40), b_reg_prev_mem_step:ubit(40), store_reg_prev_mem_step:ubit(40), store_reg_prev_value:[u32; 2], a_src_reg:bit, b_src_reg:bit, store_reg:bit, + a:[u32; 2], b:[u32; 2], c:[u32; 2], flag:bit, pc:u32, a_src_imm:bit, a_src_mem:bit, a_offset_imm0:u64, a_imm1:u32, op_with_step:bit, b_src_imm:bit, b_src_mem:bit, b_offset_imm0:u64, b_imm1:u32, b_src_ind:bit, ind_width:ubit(4), is_external_op:bit, op:u8, store_ra:bit, store_mem:bit, store_ind:bit, store_offset:u64, set_pc:bit, jmp_offset1:u64, jmp_offset2:u64, m32:bit, addr1:u32, a_reg_prev_mem_step:ubit(40), b_reg_prev_mem_step:ubit(40), store_reg_prev_mem_step:ubit(40), store_reg_prev_value:[u32; 2], a_src_reg:bit, b_src_reg:bit, store_reg:bit, }); -pub type MainTrace = GenericTrace, 4194304, 0, 0>; +pub type MainTrace = GenericTrace, 4194304, 0, 4>; + + +pub type MainTracePacked = GenericTrace, 4194304, 0, 4>; -pub type MainTracePacked = GenericTrace, 4194304, 0, 0>; trace_row!(RomFixedRow { __L1__: F, }); -pub type RomFixed = GenericTrace, 2097152, 0, 1>; +pub type RomFixed = GenericTrace, 2097152, 0, 5>; trace_row!(RomTraceRow { multiplicity:F, }); -pub type RomTrace = GenericTrace, 2097152, 0, 1>; +pub type RomTrace = GenericTrace, 2097152, 0, 5>; + trace_row!(MemFixedRow { SEGMENT_L1: F, __L1__: F, }); -pub type MemFixed = GenericTrace, 4194304, 0, 2>; +pub type MemFixed = GenericTrace, 4194304, 0, 6>; trace_row!(MemTraceRow { addr:ubit(29), step:ubit(40), sel:bit, addr_changes:bit, step_dual:ubit(40), sel_dual:bit, value:[u32; 2], wr:bit, previous_step:ubit(40), increment:[ubit(18); 2], read_same_addr:bit, }); -pub type MemTrace = GenericTrace, 4194304, 0, 2>; +pub type MemTrace = GenericTrace, 4194304, 0, 6>; + + +pub type MemTracePacked = GenericTrace, 4194304, 0, 6>; -pub type MemTracePacked = GenericTrace, 4194304, 0, 2>; trace_row!(RomDataFixedRow { SEGMENT_L1: F, __L1__: F, }); -pub type RomDataFixed = GenericTrace, 2097152, 0, 3>; +pub type RomDataFixed = GenericTrace, 2097152, 0, 7>; trace_row!(RomDataTraceRow { addr:ubit(29), step:ubit(40), sel:bit, addr_changes:bit, value:[u32; 2], }); -pub type RomDataTrace = GenericTrace, 2097152, 0, 3>; +pub type RomDataTrace = GenericTrace, 2097152, 0, 7>; + + +pub type RomDataTracePacked = GenericTrace, 2097152, 0, 7>; -pub type RomDataTracePacked = GenericTrace, 2097152, 0, 3>; trace_row!(InputDataFixedRow { SEGMENT_L1: F, __L1__: F, }); -pub type InputDataFixed = GenericTrace, 2097152, 0, 4>; +pub type InputDataFixed = GenericTrace, 2097152, 0, 8>; trace_row!(InputDataTraceRow { addr:ubit(29), step:ubit(40), sel:bit, addr_changes:bit, value_word:[u16; 4], is_free_read:bit, }); -pub type InputDataTrace = GenericTrace, 2097152, 0, 4>; +pub type InputDataTrace = GenericTrace, 2097152, 0, 8>; + + +pub type InputDataTracePacked = GenericTrace, 2097152, 0, 8>; -pub type InputDataTracePacked = GenericTrace, 2097152, 0, 4>; trace_row!(MemAlignFixedRow { L1: F, __L1__: F, }); -pub type MemAlignFixed = GenericTrace, 2097152, 0, 5>; +pub type MemAlignFixed = GenericTrace, 2097152, 0, 9>; trace_row!(MemAlignTraceRow { addr:ubit(29), offset:ubit(3), width:ubit(4), wr:bit, pc:u8, reset:bit, sel_up_to_down:bit, sel_down_to_up:bit, reg:[u8; 8], sel:[bit; 8], step:ubit(40), delta_addr:u64, sel_prove:bit, value:[u32; 2], }); -pub type MemAlignTrace = GenericTrace, 2097152, 0, 5>; +pub type MemAlignTrace = GenericTrace, 2097152, 0, 9>; + + +pub type MemAlignTracePacked = GenericTrace, 2097152, 0, 9>; -pub type MemAlignTracePacked = GenericTrace, 2097152, 0, 5>; trace_row!(MemAlignByteFixedRow { __L1__: F, }); -pub type MemAlignByteFixed = GenericTrace, 4194304, 0, 6>; +pub type MemAlignByteFixed = GenericTrace, 4194304, 0, 10>; trace_row!(MemAlignByteTraceRow { sel_high_4b:bit, sel_high_2b:bit, sel_high_b:bit, direct_value:u32, composed_value:u32, written_composed_value:u32, written_byte_value:u8, value_16b:u16, value_8b:u8, byte_value:u8, addr_w:ubit(29), step:ubit(40), is_write:bit, mem_write_values:[u32; 2], bus_byte:u8, }); -pub type MemAlignByteTrace = GenericTrace, 4194304, 0, 6>; +pub type MemAlignByteTrace = GenericTrace, 4194304, 0, 10>; + + +pub type MemAlignByteTracePacked = GenericTrace, 4194304, 0, 10>; -pub type MemAlignByteTracePacked = GenericTrace, 4194304, 0, 6>; trace_row!(MemAlignReadByteFixedRow { __L1__: F, }); -pub type MemAlignReadByteFixed = GenericTrace, 4194304, 0, 7>; +pub type MemAlignReadByteFixed = GenericTrace, 4194304, 0, 11>; trace_row!(MemAlignReadByteTraceRow { sel_high_4b:bit, sel_high_2b:bit, sel_high_b:bit, direct_value:u32, composed_value:u32, value_16b:u16, value_8b:u8, byte_value:u8, addr_w:ubit(29), step:ubit(40), }); -pub type MemAlignReadByteTrace = GenericTrace, 4194304, 0, 7>; +pub type MemAlignReadByteTrace = GenericTrace, 4194304, 0, 11>; + + +pub type MemAlignReadByteTracePacked = GenericTrace, 4194304, 0, 11>; -pub type MemAlignReadByteTracePacked = - GenericTrace, 4194304, 0, 7>; trace_row!(MemAlignWriteByteFixedRow { __L1__: F, }); -pub type MemAlignWriteByteFixed = GenericTrace, 4194304, 0, 8>; +pub type MemAlignWriteByteFixed = GenericTrace, 4194304, 0, 12>; trace_row!(MemAlignWriteByteTraceRow { sel_high_4b:bit, sel_high_2b:bit, sel_high_b:bit, direct_value:u32, composed_value:u32, written_composed_value:u32, written_byte_value:u8, value_16b:u16, value_8b:u8, byte_value:u8, addr_w:ubit(29), step:ubit(40), mem_write_values:[u32; 2], }); -pub type MemAlignWriteByteTrace = GenericTrace, 4194304, 0, 8>; +pub type MemAlignWriteByteTrace = GenericTrace, 4194304, 0, 12>; + + +pub type MemAlignWriteByteTracePacked = GenericTrace, 4194304, 0, 12>; -pub type MemAlignWriteByteTracePacked = - GenericTrace, 4194304, 0, 8>; trace_row!(ArithFixedRow { __L1__: F, }); -pub type ArithFixed = GenericTrace, 2097152, 0, 9>; +pub type ArithFixed = GenericTrace, 2097152, 0, 13>; trace_row!(ArithTraceRow { carry:[u64; 7], a:[u16; 4], b:[u16; 4], c:[u16; 4], d:[u16; 4], na:bit, nb:bit, nr:bit, np:bit, sext:bit, m32:bit, div:bit, fab:u64, na_fb:u64, nb_fa:u64, main_div:bit, main_mul:bit, signed:bit, div_by_zero:bit, div_overflow:bit, inv_sum_all_bs:u64, op:u8, bus_res1:u32, multiplicity:bit, range_ab:ubit(7), range_cd:ubit(7), }); -pub type ArithTrace = GenericTrace, 2097152, 0, 9>; +pub type ArithTrace = GenericTrace, 2097152, 0, 13>; + + +pub type ArithTracePacked = GenericTrace, 2097152, 0, 13>; -pub type ArithTracePacked = GenericTrace, 2097152, 0, 9>; trace_row!(BinaryFixedRow { __L1__: F, }); -pub type BinaryFixed = GenericTrace, 4194304, 0, 10>; +pub type BinaryFixed = GenericTrace, 4194304, 0, 14>; trace_row!(BinaryTraceRow { m_op:ubit(5), mode32:bit, free_in_a:[u8; 8], free_in_b:[u8; 8], free_in_c:[u8; 8], carry:[bit; 8], use_last_carry:bit, op_is_min_max:bit, has_initial_carry:bit, cout:bit, result_is_a:bit, use_last_carry_mode32:bit, use_last_carry_mode64:bit, m_op_or_ext:ubit(5), free_in_a_or_c:[u8; 4], free_in_b_or_zero:[u8; 4], multiplicity:bit, }); -pub type BinaryTrace = GenericTrace, 4194304, 0, 10>; +pub type BinaryTrace = GenericTrace, 4194304, 0, 14>; + + +pub type BinaryTracePacked = GenericTrace, 4194304, 0, 14>; -pub type BinaryTracePacked = GenericTrace, 4194304, 0, 10>; trace_row!(BinaryAddFixedRow { __L1__: F, }); -pub type BinaryAddFixed = GenericTrace, 4194304, 0, 11>; +pub type BinaryAddFixed = GenericTrace, 4194304, 0, 15>; trace_row!(BinaryAddTraceRow { a:[u32; 2], b:[u32; 2], c_chunks:[u16; 4], cout:[bit; 2], multiplicity:bit, }); -pub type BinaryAddTrace = GenericTrace, 4194304, 0, 11>; +pub type BinaryAddTrace = GenericTrace, 4194304, 0, 15>; + + +pub type BinaryAddTracePacked = GenericTrace, 4194304, 0, 15>; -pub type BinaryAddTracePacked = GenericTrace, 4194304, 0, 11>; trace_row!(BinaryExtensionFixedRow { __L1__: F, }); -pub type BinaryExtensionFixed = GenericTrace, 4194304, 0, 12>; +pub type BinaryExtensionFixed = GenericTrace, 4194304, 0, 16>; trace_row!(BinaryExtensionTraceRow { op:ubit(6), in1:[u8; 8], in2_low:u8, out:[[u32; 2]; 8], op_is_shift:bit, in2:[u32; 2], multiplicity:bit, }); -pub type BinaryExtensionTrace = GenericTrace, 4194304, 0, 12>; +pub type BinaryExtensionTrace = GenericTrace, 4194304, 0, 16>; + + +pub type BinaryExtensionTracePacked = GenericTrace, 4194304, 0, 16>; -pub type BinaryExtensionTracePacked = - GenericTrace, 4194304, 0, 12>; trace_row!(Add256FixedRow { __L1__: F, }); -pub type Add256Fixed = GenericTrace, 1048576, 0, 13>; +pub type Add256Fixed = GenericTrace, 1048576, 0, 17>; trace_row!(Add256TraceRow { a:[[u32; 2]; 4], b:[[u32; 2]; 4], c_chunks:[[u16; 4]; 4], cout:[[bit; 2]; 4], addr_params:u32, addr_a:u32, addr_b:u32, addr_c:u32, step:ubit(40), cin:bit, sel:bit, }); -pub type Add256Trace = GenericTrace, 1048576, 0, 13>; +pub type Add256Trace = GenericTrace, 1048576, 0, 17>; + + +pub type Add256TracePacked = GenericTrace, 1048576, 0, 17>; -pub type Add256TracePacked = GenericTrace, 1048576, 0, 13>; trace_row!(ArithEqFixedRow { CLK_0: F, __L1__: F, }); -pub type ArithEqFixed = GenericTrace, 1048576, 0, 14>; +pub type ArithEqFixed = GenericTrace, 1048576, 0, 18>; trace_row!(ArithEqTraceRow { x1:u16, y1:u16, x2:u16, y2:u16, x3:u16, y3:u16, q0:ubit(22), q1:ubit(22), q2:ubit(22), s:ubit(22), sel_op:[bit; 9], sel_op_clk0:[bit; 9], x_delta_chunk_inv:u64, x_are_different:bit, x3_lt:bit, y3_lt:bit, carry:[[u64; 2]; 3], step_addr:ubit(40), }); -pub type ArithEqTrace = GenericTrace, 1048576, 0, 14>; +pub type ArithEqTrace = GenericTrace, 1048576, 0, 18>; + + +pub type ArithEqTracePacked = GenericTrace, 1048576, 0, 18>; -pub type ArithEqTracePacked = GenericTrace, 1048576, 0, 14>; trace_row!(ArithEq384FixedRow { CLK_0: F, __L1__: F, }); -pub type ArithEq384Fixed = GenericTrace, 1048576, 0, 15>; +pub type ArithEq384Fixed = GenericTrace, 1048576, 0, 19>; trace_row!(ArithEq384TraceRow { x1:u16, y1:u16, x2:u16, y2:u16, x3:u16, y3:u16, q0:ubit(22), q1:ubit(22), q2:ubit(22), s:ubit(22), sel_op:[bit; 6], sel_op_clk0:[bit; 6], x_delta_chunk_inv:u64, x_are_different:bit, x3_lt:bit, y3_lt:bit, carry:[[u64; 2]; 3], step_addr:ubit(40), }); -pub type ArithEq384Trace = GenericTrace, 1048576, 0, 15>; +pub type ArithEq384Trace = GenericTrace, 1048576, 0, 19>; + + +pub type ArithEq384TracePacked = GenericTrace, 1048576, 0, 19>; -pub type ArithEq384TracePacked = GenericTrace, 1048576, 0, 15>; trace_row!(KeccakfFixedRow { L1: F, GATE_OP: F, CONN_A: F, CONN_B: F, CONN_C: F, CONN_D: F, ID: F, LATCH_NUM_KECCAKF: F, FACTOR_NUM_KECCAKF: F, CLK_0: F, __L1__: F, }); -pub type KeccakfFixed = GenericTrace, 2097152, 0, 16>; +pub type KeccakfFixed = GenericTrace, 2097152, 0, 20>; trace_row!(KeccakfTraceRow { free_in_a:[ubit(7); 9], free_in_b:[ubit(7); 9], free_in_c:[ubit(7); 9], free_in_d:[ubit(7); 9], bit:[bit; 4], val:[u64; 4], step_addr:ubit(40), in_use_clk_0:bit, in_use:bit, }); -pub type KeccakfTrace = GenericTrace, 2097152, 0, 16>; +pub type KeccakfTrace = GenericTrace, 2097152, 0, 20>; + + +pub type KeccakfTracePacked = GenericTrace, 2097152, 0, 20>; -pub type KeccakfTracePacked = GenericTrace, 2097152, 0, 16>; trace_row!(Sha256fFixedRow { CLK_0: F, __L1__: F, }); -pub type Sha256fFixed = GenericTrace, 262144, 0, 17>; +pub type Sha256fFixed = GenericTrace, 262144, 0, 21>; trace_row!(Sha256fTraceRow { a:[bit; 32], e:[bit; 32], w:[bit; 32], new_a_carry_bits:u8, new_e_carry_bits:u8, new_w_carry_bits:ubit(4), step_addr:ubit(40), in_use_clk_0:bit, in_use:bit, }); -pub type Sha256fTrace = GenericTrace, 262144, 0, 17>; +pub type Sha256fTrace = GenericTrace, 262144, 0, 21>; + + +pub type Sha256fTracePacked = GenericTrace, 262144, 0, 21>; -pub type Sha256fTracePacked = GenericTrace, 262144, 0, 17>; trace_row!(SpecifiedRangesFixedRow { - RANGE: [F; 19], __L1__: F, + RANGE: [F; 20], __L1__: F, }); -pub type SpecifiedRangesFixed = GenericTrace, 2097152, 0, 18>; +pub type SpecifiedRangesFixed = GenericTrace, 2097152, 0, 22>; trace_row!(SpecifiedRangesTraceRow { - mul:[F; 19], + mul:[F; 20], }); -pub type SpecifiedRangesTrace = GenericTrace, 2097152, 0, 18>; +pub type SpecifiedRangesTrace = GenericTrace, 2097152, 0, 22>; + trace_row!(VirtualTable0FixedRow { UID: [F; 11], column: [F; 63], __L1__: F, }); -pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 19>; +pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 23>; trace_row!(VirtualTable0TraceRow { multiplicity:[F; 11], }); -pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 19>; +pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 23>; + trace_row!(VirtualTable1FixedRow { - UID: [F; 8], column: [F; 64], __L1__: F, + UID: [F; 8], column: [F; 72], __L1__: F, }); -pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 20>; +pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 24>; trace_row!(VirtualTable1TraceRow { multiplicity:[F; 8], }); -pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 20>; +pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 24>; + trace_row!(RomRomTraceRow { line: F, a_offset_imm0: F, a_imm1: F, b_offset_imm0: F, b_imm1: F, ind_width: F, op: F, store_offset: F, jmp_offset1: F, jmp_offset2: F, flags: F, }); -pub type RomRomTrace = GenericTrace, 2097152, 0, 1, 0>; +pub type RomRomTrace = GenericTrace, 2097152, 0, 5, 0>; + + +values!(Dma64AlignedAirValues { + segment_id: F, segment_previous_seq_end: F, segment_previous_src64: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count: F, segment_previous_is_mem_eq: F, segment_last_seq_end: F, segment_last_src64: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count: F, segment_last_is_mem_eq: F, is_last_segment: F, im_direct: [FieldExtension; 2], +}); + +values!(DmaUnalignedAirValues { + segment_id: F, segment_previous_seq_end: F, segment_previous_src64: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_offset: F, segment_previous_count: F, segment_previous_is_mem_eq: F, segment_first_bytes: [F; 8], segment_last_seq_end: F, segment_last_src64: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_offset: F, segment_last_count: F, segment_last_is_mem_eq: F, segment_next_bytes: [F; 8], is_last_segment: F, last_count_chunk: [F; 2], padding_rows: F, im_direct: [FieldExtension; 6], +}); values!(MainAirValues { main_last_segment: F, main_segment: F, segment_initial_pc: F, segment_previous_c: [F; 2], segment_next_pc: F, segment_last_c: [F; 2], last_reg_value: [[F; 2]; 31], last_reg_mem_step: [F; 31], im_direct: [FieldExtension; 96], @@ -381,6 +496,22 @@ values!(MemAlignWriteByteAirValues { padding_size: F, im_direct: [FieldExtension; 3], }); +values!(DmaAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(Dma64AlignedAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(DmaUnalignedAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(DmaPrePostAirGroupValues { + gsum_result: FieldExtension, +}); + values!(MainAirGroupValues { gsum_result: FieldExtension, }); @@ -466,190 +597,109 @@ values!(VirtualTable1AirGroupValues { }); pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ - ( - 0, - 0, - PackedInfoConst { - is_packed: true, - num_packed_words: 14, - unpack_info: &[ - 32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, - 64, 1, 64, 64, 1, 32, 40, 40, 40, 32, 32, 1, 1, 1, - ], - }, - ), - ( - 0, - 2, - PackedInfoConst { - is_packed: true, - num_packed_words: 4, - unpack_info: &[29, 40, 1, 1, 40, 1, 32, 32, 1, 40, 18, 18, 1], - }, - ), - ( - 0, - 3, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[29, 40, 1, 1, 32, 32], - }, - ), - ( - 0, - 4, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[29, 40, 1, 1, 16, 16, 16, 16, 1], - }, - ), - ( - 0, - 5, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[ - 29, 3, 4, 1, 8, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 40, 64, 1, - 32, 32, - ], - }, - ), - ( - 0, - 6, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 1, 32, 32, 8], - }, - ), - ( - 0, - 7, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[1, 1, 1, 32, 32, 16, 8, 8, 29, 40], - }, - ), - ( - 0, - 8, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 32, 32], - }, - ), - ( - 0, - 9, - PackedInfoConst { - is_packed: true, - num_packed_words: 17, - unpack_info: &[ - 64, 64, 64, 64, 64, 64, 64, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 1, 1, 1, 1, 1, 1, 1, 64, 64, 64, 1, 1, 1, 1, 1, 64, 8, 32, 1, 7, 7, - ], - }, - ), - ( - 0, - 10, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[ - 5, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 8, 8, 8, 8, 8, 8, 8, 8, 1, - ], - }, - ), - ( - 0, - 11, - PackedInfoConst { - is_packed: true, - num_packed_words: 4, - unpack_info: &[32, 32, 32, 32, 16, 16, 16, 16, 1, 1, 1], - }, - ), - ( - 0, - 12, - PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[ - 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 1, 32, 32, 1, - ], - }, - ), - ( - 0, - 13, - PackedInfoConst { - is_packed: true, - num_packed_words: 15, - unpack_info: &[ - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, - 40, 1, 1, - ], - }, - ), - ( - 0, - 14, - PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[ - 16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40, - ], - }, - ), - ( - 0, - 15, - PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[ - 16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, - 1, 1, 64, 64, 64, 64, 64, 64, 40, - ], - }, - ), - ( - 0, - 16, - PackedInfoConst { - is_packed: true, - num_packed_words: 9, - unpack_info: &[ - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 64, 64, 64, 64, 40, 1, 1, - ], - }, - ), - ( - 0, - 17, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 40, 1, 1, - ], - }, - ), -]; + (0, 0, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[24, 1, 6, 21, 8, 3, 21, 8, 3, 32, 1, 1, 1, 1, 1, 3, 3, 3, 1], + }), + (0, 1, PackedInfoConst { + is_packed: true, + num_packed_words: 6, + unpack_info: &[32, 29, 29, 32, 32, 32, 32, 32, 32, 32, 32, 32, 1, 1, 1, 1, 1, 1], + }), + (0, 2, PackedInfoConst { + is_packed: true, + num_packed_words: 6, + unpack_info: &[32, 29, 29, 32, 32, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32], + }), + (0, 3, PackedInfoConst { + is_packed: true, + num_packed_words: 6, + unpack_info: &[32, 29, 29, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32], + }), + (0, 4, PackedInfoConst { + is_packed: true, + num_packed_words: 14, + unpack_info: &[32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, 64, 1, 64, 64, 1, 32, 40, 40, 40, 32, 32, 1, 1, 1], + }), + (0, 6, PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[29, 40, 1, 1, 40, 1, 32, 32, 1, 40, 18, 18, 1], + }), + (0, 7, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[29, 40, 1, 1, 32, 32], + }), + (0, 8, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[29, 40, 1, 1, 16, 16, 16, 16, 1], + }), + (0, 9, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[29, 3, 4, 1, 8, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 40, 64, 1, 32, 32], + }), + (0, 10, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 1, 32, 32, 8], + }), + (0, 11, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[1, 1, 1, 32, 32, 16, 8, 8, 29, 40], + }), + (0, 12, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 32, 32], + }), + (0, 13, PackedInfoConst { + is_packed: true, + num_packed_words: 17, + unpack_info: &[64, 64, 64, 64, 64, 64, 64, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 64, 64, 64, 1, 1, 1, 1, 1, 64, 8, 32, 1, 7, 7], + }), + (0, 14, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[5, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 8, 8, 8, 8, 8, 8, 8, 8, 1], + }), + (0, 15, PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[32, 32, 32, 32, 16, 16, 16, 16, 1, 1, 1], + }), + (0, 16, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 1, 32, 32, 1], + }), + (0, 17, PackedInfoConst { + is_packed: true, + num_packed_words: 15, + unpack_info: &[32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 40, 1, 1], + }), + (0, 18, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], + }), + (0, 19, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], + }), + (0, 20, PackedInfoConst { + is_packed: true, + num_packed_words: 9, + unpack_info: &[7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 64, 64, 64, 64, 40, 1, 1], + }), + (0, 21, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 40, 1, 1], + }), +]; \ No newline at end of file diff --git a/pil/zisk.pil b/pil/zisk.pil index 2f68e547e..d80a51780 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -1,4 +1,9 @@ const int OPERATION_BUS_ID = 5000; +const int MAIN_STEP_BITS = 32; +const int MAX_STEPS = 1 << MAIN_STEP_BITS; +const int MEM_STEP_BITS = MAIN_STEP_BITS * 4; +const int ADDR_BITS = 32; +const int ADDR_W_BITS = ADDR_BITS - 3; require "std_direct.pil" require "rom/pil/rom.pil" @@ -21,6 +26,13 @@ require "keccakf/pil/keccakf.pil" require "keccakf/pil/keccakf_table.pil" require "sha256f/pil/sha256f.pil" require "big_int/pil/big_int_add.pil" +require "dma/pil/dma.pil" +require "dma/pil/dma_rom.pil" +require "dma/pil/dma_pre_post_table.pil" +require "dma/pil/dma_pre_post.pil" +require "dma/pil/dma_unaligned.pil" +require "dma/pil/dma_64_aligned.pil" +// require "dma/pil/dma_32_aligned.pil" proofval enable_input_data; enable_input_data * (1 - enable_input_data); @@ -41,9 +53,16 @@ airgroup Zisk { set_max_std_tables_bits(21); set_max_num_rows_virtual(1 << 21); // Set the maximum rows for virtual tables set_max_num_virtual_tables(2); - set_group_virtual_tables(table_ids: [ARITH_TABLE_ID, ARITH_RANGE_TABLE_ID, ARITH_EQ_LT_TABLE_ID, BINARY_EXTENSION_TABLE_ID, BINARY_TABLE_ID, MEM_ALIGN_ROM_ID, KECCAKF_TABLE_ID]); + set_group_virtual_tables(table_ids: [ARITH_TABLE_ID, ARITH_RANGE_TABLE_ID, ARITH_EQ_LT_TABLE_ID, BINARY_EXTENSION_TABLE_ID, BINARY_TABLE_ID, MEM_ALIGN_ROM_ID, KECCAKF_TABLE_ID, DMA_ROM_ID, DMA_PRE_POST_TABLE_ID]); set_group_virtual_tables(table_ids: [BINARY_EXTENSION_FROPS_TABLE_ID, BINARY_BASIC_FROPS_TABLE_ID, ARITH_FROPS_TABLE_ID]); + Dma(); + virtual DmaRom(); + Dma64Aligned(); + // Dma32AlignedOp(); + DmaUnaligned(); + DmaPrePost(); + virtual DmaPrePostTable(); // Main Program Main(N: 2**22); Rom(N: 2**21); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs b/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs index 18fbbea4f..aa6ff13de 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs @@ -3,6 +3,7 @@ use crate::executors::Bn254Curve; use std::collections::VecDeque; use zisk_common::BusId; use zisk_common::MemCollectorInfo; +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; pub const BN254_CURVE_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 2, @@ -20,8 +21,12 @@ pub fn generate_bn254_curve_add_mem_inputs( pending: &mut VecDeque<(BusId, Vec)>, ) { // op,op_type,a,b,addr[2],... - let p1: &[u64; 8] = &data[7..15].try_into().unwrap(); - let p2: &[u64; 8] = &data[15..23].try_into().unwrap(); + let p1_start = OPERATION_PRECOMPILED_BUS_DATA_SIZE + BN254_CURVE_ADD_MEM_CONFIG.indirect_params; + let p1: &[u64; 8] = + &data[p1_start..p1_start + BN254_CURVE_ADD_MEM_CONFIG.chunks_per_param].try_into().unwrap(); + let p2_start = p1_start + BN254_CURVE_ADD_MEM_CONFIG.chunks_per_param; + let p2: &[u64; 8] = + &data[p2_start..p2_start + BN254_CURVE_ADD_MEM_CONFIG.chunks_per_param].try_into().unwrap(); let mut p3 = [0u64; 8]; Bn254Curve::calculate_add(p1, p2, &mut p3); diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs b/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs index 815b6258a..20c4f01dd 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs @@ -21,7 +21,10 @@ pub fn generate_bn254_curve_dbl_mem_inputs( pending: &mut VecDeque<(BusId, Vec)>, ) { // op,op_type,a,b,addr[2],... - let p1: &[u64; 8] = &data[5..13].try_into().unwrap(); + let p1: &[u64; 8] = &data + [OPERATION_PRECOMPILED_BUS_DATA_SIZE..OPERATION_PRECOMPILED_BUS_DATA_SIZE + 8] + .try_into() + .unwrap(); let mut p3 = [0u64; 8]; Bn254Curve::calculate_dbl(p1, &mut p3); diff --git a/precompiles/common/src/lib.rs b/precompiles/common/src/lib.rs index f37f9081a..8f74affa1 100644 --- a/precompiles/common/src/lib.rs +++ b/precompiles/common/src/lib.rs @@ -106,6 +106,42 @@ impl MemBusHelpers { ], )); } + pub fn mem_aligned_load_from_slice( + addr: u32, + step: u64, + values: &[u64], + pending: &mut VecDeque<(BusId, Vec)>, + ) { + assert!(addr % 8 == 0); + let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2; + for (i, &value) in values.iter().enumerate() { + pending.push_back(( + MEM_BUS_ID, + vec![MEMORY_LOAD_OP, (addr as usize + i * 8) as u64, mem_step, 8, 0, 0, value], + )); + } + } + pub fn mem_aligned_write_from_slice( + addr: u32, + step: u64, + values: &[u64], + pending: &mut VecDeque<(BusId, Vec)>, + ) { + assert!(addr % 8 == 0); + let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3; + for (i, &value) in values.iter().enumerate() { + pending.push_back(( + MEM_BUS_ID, + vec![MEMORY_STORE_OP, (addr as usize + i * 8) as u64, mem_step, 8, value, 0, 0], + )); + } + } + pub fn get_mem_read_step(step: u64) -> u64 { + MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2 + } + pub fn get_mem_write_step(step: u64) -> u64 { + MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3 + } } pub fn log2(n: usize) -> usize { diff --git a/precompiles/dma/Cargo.toml b/precompiles/dma/Cargo.toml index 18691f8f9..10f4da693 100644 --- a/precompiles/dma/Cargo.toml +++ b/precompiles/dma/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "precomp-big-int" +name = "precomp-dma" version = { workspace = true } edition = { workspace = true } license = { workspace = true } @@ -28,6 +28,8 @@ generic-array = "0.14" [features] default = [] +dma_memcmp = [] +packed = [] no_lib_link = ["proofman-common/no_lib_link"] diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file diff --git a/precompiles/dma/dma.md b/precompiles/dma/dma.md new file mode 100644 index 000000000..4824d297f --- /dev/null +++ b/precompiles/dma/dma.md @@ -0,0 +1,14 @@ +## Translate + +ELF + +ADD X0, DST, SRC ==> DMA_MEMCPY DST, SRC (JMP +8) +CSRW XXX, COUNT ==> FLAG COUNT + + +DST_PRE_DATA +DST_POST_DATA +SRC_DATA ..... (without redundancy) + + +extra param count \ No newline at end of file diff --git a/precompiles/dma/pil/dma.pil b/precompiles/dma/pil/dma.pil index 00caf549b..4cd487054 100644 --- a/precompiles/dma/pil/dma.pil +++ b/precompiles/dma/pil/dma.pil @@ -1,127 +1,155 @@ -const int MEMCPY_CONT_ID = 400; +const int DMA_BUS_ID = 8000; +const int DMA_ROM_ID = 8001; -airtemplate MemCpy(int N = 2**21, const int RC = 2, const int op_x_row = 4, const int selectors = 1) { +const int DMA_MEM_CPY = 1; +const int DMA_MEM_PRE_POST = 2; +const int DMA_MEM_EQ = 3; + +const int MEM_CPY_BYTE_CONT_ID = 8200; +const int MEM_CPY_CONT_ID = 8201; + + + +airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const int selectors = 1) { assert(selectors == 0 || selectors == 1); assert(op_x_row > 1); - airval segment_id; // Id of current segment - airval segment_previous_reset; // Last value of `reset` in previous segment. - airval segment_previous_src_addr; // Last value of `src_addr` in previous segment. - airval segment_previous_dst_addr; // Last value of `dst_addr` in previous segment. - airval segment_previous_main_step; // Last value of `main_step` in previous segment. - airval segment_previous_count; // Last value of `count` in previous segment. - - airval segment_last_reset; // Last value of `reset` in current segment. - airval segment_last_src_addr; // Last value of `src_addr` in current segment. - airval segment_last_dst_addr; // Last value of `dst_addr` in current segment. - airval segment_last_main_step; // Last value of `main_step` in current segment. - airval segment_last_count; // Last value of `count` in current segment. - - airval is_last_segment; // 1 if this is the last segment, 0 otherwise. - col witness main_step; - col witness src_addr; - col witness dst_addr; - col witness count; - col witness value[op_x_row][RC]; - - col witness sel_op[selectors ? op_x_row : 0]; // one more for end - col witness reset; - - const expr previous_reset = L1 * (segment_previous_reset - 'reset) + 'reset; - const expr previous_src_addr = L1 * (segment_previous_src_addr - 'src_addr) + 'src_addr; - const expr previous_dst_addr = L1 * (segment_previous_dst_addr - 'dst_addr) + 'dst_addr; - const expr previous_main_step = L1 * (segment_previous_main_step - 'main_step) + 'main_step; - const expr previous_count = L1 * (segment_previous_count - 'count) + 'count; - - LAST * (reset - segment_last_reset) === 0; - LAST * (src_addr - segment_last_src_addr) === 0; - LAST * (dst_addr - segment_last_dst_addr) === 0; - LAST * (main_step - segment_last_main_step) === 0; - LAST * (count - segment_last_count) === 0; - - // Continuations - // AIR_ID, segment_id, reset, src_addr, dst_addr, count, main_step - const int UNIQ_ID = AIRGROUP_ID * 1000 + AIR_ID; - direct_global_update_proves(MEMCPY_CONT_ID, [UNIQ_ID, 0, 1, 0, 0, 0, 0]); - - - direct_update_assumes(MEMCPY_CONT_ID, [ UNIQ_ID, - segment_id, - segment_previous_reset, - segment_previous_src_addr, - segment_previous_dst_addr, - segment_previous_count, - segment_previous_main_step]); - - direct_update_proves(MEMCPY_CONT_ID, [ UNIQ_ID, - segment_id + 1, - segment_last_reset, - segment_last_src_addr, - segment_last_dst_addr, - segment_last_count, - segment_last_main_step], sel: is_last_segment); - - // This global constraint is sent to the bus to define the initial and final values of - // continuations. A global constraint is used to ensure there is only one initial and one final - // value, preventing multiple cycles of `main`. - - // In the initial state define the initial `pc` and final `pc`. Before finish a execution the - // value of "c" must be 0. - - const int zeros[RC]; - for (int index = 0; index < RC; ++index) { - zeros[index] = 0; - } - - // These constraints define the state at the beginning and end of a main continuation. - - direct_global_update_assumes(MAIN_CONTINUATION_ID, expressions: [0, 1, END_PC_ADDR, ...zeros]); - - - reset * (1 - reset) === 0; - - expr _ops_in_row = selectors ? sel_op[0] * op_x_row : sel_op[0]; - - for (int i = 0; i < op_x_row; i++) { - expr sel; - if (selectors || i == 0) { - sel = sel_op[i]; - sel_op[i] * (1 - sel_op[i]) === 0; - } else { - sel = sel_op[0]; - } - - precompiled_mem_load( - sel: sel, - main_step: main_step, - addr: src_addr * 8 + 8 * i, - value: value[i] - ); - - precompiled_mem_store( - sel: sel, - main_step: main_step, - addr: dst_addr * 8 + 8 * i, - value: value[i] - ); - if (selectors && i > 0) { - // current selector only can be 1 if previous is 1 - sel_op[i] * (1 - sel_op[i - 1]) === 0; - _ops_in_row += sel_op[i]; - } - } - const expr ops_in_row = _ops_in_row; - reset * (count - ops_in_row) === 0; - - range_check(count, min: 0, max: 2**24-1); // cost 1.5 - - (count - ('count + op_x_row)) * (1 - reset) === 0; - (src_addr - ('src_addr + 8 * op_x_row)) * (1 - reset) === 0; - (dst_addr - ('dst_addr + 8 * op_x_row)) * (1 - reset) === 0; - - const expr previous_reset = L1 * (previous_segment_reset - 'reset) + 'reset; + // DMA: MEMCPY + // + // Unlike a CPU memcpy, the DMA memcpy is instantaneous from a temporal point of view all reads + // occur in the same step and all writes in the next step. In the case of reads there are no + // problems in performing the same read several times in the same step because the result is + // always the same in this moment. In the case of writes, this is not the case, because it would + // create an ambiguity to know the written value. Therefore, we must guarantee that each 64-bit + // value is written only once. + // + // The strategy that DMA follows in the case of memcpy is to divide the operation into 3 phases: + // + // 1. Pre-copy: initial bytes are copied to achieve at least destination alignment, to avoid + // double writes. + // + // 2. Fully or partially aligned copy. It's a loop where 64-bit values are copied. There are + // two variants: + // a) Aligned 64-bit copy: both source and destination addresses are aligned to 8 bytes. + // b) Unaligned 64-bit copy: the source address is not aligned to 8 bytes, but it's read in + // bytes and 64-bit values are reassembled. + // + // 3. Post-copy: final bytes that don't complete a 64-bit word are copied. + // + // Not all phases are necessary, they depend on the copy parameters. + // + // Example of fully aligned memcpy operation: + // + // PRE MEMCPY POST + // ┌──────────┐ ┌──────────────────────────────┐ ┌──────────┐ + // + // ┌──────┐ ┌────────┬────────┐ ┌────────┐ ┌────┐ + // src │ 0 │ │ 1 │ 2 │...│ n │ │ n+1│ + // └──────┘ └────────┴────────┘ └────────┘ └────┘ + // ↓ ↓ ↓ ↓ ↓ + // ┌───┬──────┐ ┌────────┬────────┐ ┌────────┐ ┌────┬─────┐ + // dst │ 0 │ │ 1 │ 2 │...│ n │ │ n+1 │ + // └───┴──────┘ └────────┴────────┘ └────────┘ └────┴─────┘ + // ^dst64 ^dst64+1 ^dst64+2 ^dst64+n ^dst64+n+1 <-- each dst64 address is + // written only once + // + // + // Example of partially aligned memcpy operation: + // + // PRE MEMCPY POST + // ┌──────────┐ ┌────────────────────────────────────────┐ ┌──────────┐ + // ┌────┐┌─ ┌────────┬────────┐ ┌────────┐┌─┬──────┐ ┌─┬───┐ + // src │ 0 ││1 │ 1 │ 2 │...│ n ││ n+1 │ │ n+1│ + // └────┘└─ └────────┴────────┘ └────────┘└─┴──────┘ └─┴───┘ + // ↓ ↓ ↓ ↙ ↓ ↙ ↓ ↙ ↙ + // ┌───┬──────┐ ┌────────┬────────┐ ┌────────┐ ┌────┬─────┐ + // dst │ 0 │ │ 1 │ 2 │...│ n │ ↑ │ n+1 │ + // └───┴──────┘ └────────┴────────┘ └────────┘ │ └────┴─────┘ + // ^dst64 ^dst64+1 ^dst64+2 ^dst64+n │ ^dst64+n+1 <-- each dst64 address is + // without written only once + // write + // - lookup_proves(op_bus_id, [op, dst_addr * 8, 0, src_addr * 8, 0, 0, 0, 0, step], sel: previous_reset); - lookup_proves(op_bus_id, [op_param, count, 0, 0, 0, 0, 0, 0, step], sel: reset); + // read count from register directly + + // assuming max memory of 512Mb (29 bits) + // count: 21 bits RC + 5 bits T + col witness bits(24) h_count; + col witness bits(1) count_ge_32; + col witness bits(6) l_count; // 0..31 + 32 = 6 bits + // l_count range verified with dma_rom table + const expr count = h_count * 32 + l_count; + + count_ge_32 * (1 - count_ge_32) === 0; + (1 - count_ge_32) * h_count === 0; + + range_check(expression: h_count - count_ge_32, min: 0, max: 2**21-1); + + + // 32 bit address + // 21 bits RC + 8 bits RC + 3 bits T + col witness bits(21) h_src64; + col witness bits(8) l_src64; + col witness bits(3) src_offset; + const expr src = h_src64 * 2**21 + l_src64 * 2**3 + src_offset; + const expr src64 = h_src64 * 2**21 + l_src64 * 2**3; + // src_offset range verified with dma_rom table + + + // 32 bit address + // 21 bits RC + 8 bits RC + 3 bits T + col witness bits(21) h_dst64; + col witness bits(8) l_dst64; + col witness bits(3) dst_offset; + const expr dst = h_dst64 * 2**21 + l_dst64 * 2**3 + dst_offset; + const expr dst64 = h_dst64 * 2**21 + l_dst64 * 2**3; + // dst_offset range verified with dma_rom table + + col witness bits(MAIN_STEP_BITS) main_step; + col witness bits(1) sel; + + sel * (1 - sel) === 0; + + range_check(expression: h_src64, min: 0, max: 2**21-1); + range_check(expression: h_dst64, min: 0, max: 2**21-1); + range_dual_byte(l_src64, l_dst64); + + col witness bits(1) use_pre; // use memcpy_pre operation + col witness bits(1) use_memcpy; // use memcpy operation + col witness bits(1) use_post; // use memcpy_post operation + col witness bits(1) src64_inc_by_pre; // src64 increment after apply memcpy_pre operation + + use_pre * (1 - use_pre) === 0; + use_memcpy * (1 - use_memcpy) === 0; + use_post * (1 - use_post) === 0; + src64_inc_by_pre * (1 - src64_inc_by_pre) === 0; + + const expr flags = use_pre + use_memcpy * 2 + use_post * 4 + src64_inc_by_pre * 8; + + col witness bits(3) pre_count; // number of bytes of memcpy_pre operation + col witness bits(3) l_count64; // number of 64 bits in l_count after substract pre_count and post_count + col witness bits(3) src_offset_after_pre; // src_offset after apply memcpy_pre operation + + lookup_assumes(DMA_ROM_ID, expressions: [dst_offset, src_offset, l_count, flags, pre_count, src_offset_after_pre, l_count64]); + + col witness bits(1) reg_prev_mem_step; + precompiled_reg_load(reg: REG_A2, prev_mem_step: reg_prev_mem_step, main_step:main_step, value:[count, 0], sel: sel); + + + permutation_assumes(DMA_BUS_ID, [DMA_MEM_CPY, + dst64 + use_pre + l_count64 + h_count * 4, + src64 + src64_inc_by_pre + l_count64 + h_count * 4, + 0, + src_offset_after_pre, + l_count - pre_count - l_count64 * 8, + main_step], sel: use_memcpy); + + // write aligned + // read + // value1, value2, key = value 3 + // (src64, dst64, pre_src_op, dst_offset) + + // verify overlapping + // } \ No newline at end of file diff --git a/precompiles/dma/pil/dma_64_aligned.pil b/precompiles/dma/pil/dma_64_aligned.pil new file mode 100644 index 000000000..eabc693d9 --- /dev/null +++ b/precompiles/dma/pil/dma_64_aligned.pil @@ -0,0 +1,150 @@ +airtemplate Dma64Aligned(int N = 2**21, const int RC = 2, const int op_x_row = 4, + const int selectors = 1, const expr enable_dma = 1) { + assert(RC == 2); + + const expr L1 = get_L1(); + const expr LAST = L1'; + + assert(selectors == 0 || selectors == 1); + assert(op_x_row > 1); + + airval segment_id; // Id of current segment + airval segment_previous_seq_end; // Last value of `seq_end` in previous segment. + airval segment_previous_src64; // Last value of `src64` in previous segment. + airval segment_previous_dst64; // Last value of `dst64` in previous segment. + airval segment_previous_main_step; // Last value of `main_step` in previous segment. + airval segment_previous_count; // Last value of `count` in previous segment. + airval segment_previous_is_mem_eq; // Last value of `is_mem_eq' in previous segment. + + airval segment_last_seq_end; // Last value of `seq_end` in current segment. + airval segment_last_src64; // Last value of `src64` in current segment. + airval segment_last_dst64; // Last value of `dst64` in current segment. + airval segment_last_main_step; // Last value of `main_step` in current segment. + airval segment_last_count; // Last value of `count` in current segment. + airval segment_last_is_mem_eq; // Last value of `is_mem_eq' in current segment. + + airval is_last_segment; // 1 if this is the last segment, 0 otherwise. + + is_last_segment * (1 - is_last_segment) === 0; + segment_previous_seq_end * (1 - segment_previous_seq_end) === 0; + segment_last_seq_end * (1 - segment_last_seq_end) === 0; + + segment_previous_is_mem_eq * (1 - segment_previous_is_mem_eq) === 0; + segment_last_is_mem_eq * (1 - segment_last_is_mem_eq) === 0; + + col witness bits(MAIN_STEP_BITS) main_step; + col witness bits(ADDR_W_BITS) src64; + col witness bits(ADDR_W_BITS) dst64; + col witness bits(32) count; + col witness bits(32) value[op_x_row][RC]; + + col witness bits(1) sel_op[selectors ? op_x_row : 0]; // one more for end + col witness bits(1) seq_end; + col witness bits(1) is_mem_eq; + + seq_end * (1 - seq_end) === 0; + is_mem_eq * (1 - is_mem_eq) === 0; + if (selectors) { + for (int i = 0; i < op_x_row; i++) { + sel_op[i] * (1 - sel_op[i]) === 0; + } + } + + const expr previous_seq_end = L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; + const expr previous_src64 = L1 * (segment_previous_src64 - 'src64) + 'src64; + const expr previous_dst64 = L1 * (segment_previous_dst64 - 'dst64) + 'dst64; + const expr previous_main_step = L1 * (segment_previous_main_step - 'main_step) + 'main_step; + const expr previous_count = L1 * (segment_previous_count - 'count) + 'count; + const expr previous_is_mem_eq = L1 * (segment_previous_is_mem_eq - 'is_mem_eq) + 'is_mem_eq; + + LAST * (seq_end - segment_last_seq_end) === 0; + LAST * (src64 - segment_last_src64) === 0; + LAST * (dst64 - segment_last_dst64) === 0; + LAST * (main_step - segment_last_main_step) === 0; + LAST * (count - segment_last_count) === 0; + LAST * (is_mem_eq - segment_last_is_mem_eq) === 0; + + // Continuations + + // AIR_ID, segment_id, seq_end, src64, dst64, count, main_step + + direct_global_update_proves(MEM_CPY_CONT_ID, [0, + 0, + 1, // initial seq_end + 0, // initial src64 + 0, // initial dst64 + 0, // initial count + 0, // initial main_step + 0], // initial is_mem_eq + sel: enable_dma); + + + direct_update_assumes(MEM_CPY_CONT_ID, [segment_id, + 0, + segment_previous_seq_end, + segment_previous_src64, + segment_previous_dst64, + segment_previous_count, + segment_previous_main_step, + segment_previous_is_mem_eq]); + + direct_update_proves(MEM_CPY_CONT_ID, [segment_id + 1, + is_last_segment, + segment_last_seq_end, + segment_last_src64, + segment_last_dst64, + segment_last_count, + segment_last_main_step, + segment_last_is_mem_eq], sel: 1 - is_last_segment); + + + expr _ops_in_row = selectors ? sel_op[0] * op_x_row : sel_op[0]; + + for (int i = 0; i < op_x_row; i++) { + expr sel; + if (selectors || i == 0) { + sel = sel_op[i]; + sel_op[i] * (1 - sel_op[i]) === 0; + } else { + sel = sel_op[0]; + } + + precompiled_mem_load( + sel: sel, + main_step: main_step, + addr: src64 * 8 + 8 * i, + value: value[i] + ); + + precompiled_mem_op( + is_write: 1 - is_mem_eq, + sel: sel, + main_step: main_step, + addr: dst64 * 8 + 8 * i, + value: value[i] + ); + if (selectors && i > 0) { + // current selector only can be 1 if previous is 1 + sel_op[i] * (1 - sel_op[i - 1]) === 0; + _ops_in_row += sel_op[i]; + } + } + const expr ops_in_row = _ops_in_row; + + // If not finish sequence in previous row, means current it's a continuation of previous sequence + // count match with previous value plus ops_in_row. + (count - (previous_count + op_x_row)) * (1 - previous_seq_end) === 0; + + // If finish sequence in previous row, means current it's a beginning of new sequence and + // count match with ops_in_row. + (count - ops_in_row) * previous_seq_end === 0; + + // If previous row was a seq_end, start with new dst/src addresses, else increment by ops_in_row + (src64 - (previous_src64 + 8 * op_x_row)) * (1 - previous_seq_end) === 0; + (dst64 - (previous_dst64 + 8 * op_x_row)) * (1 - previous_seq_end) === 0; + (is_mem_eq - previous_is_mem_eq) * (1 - previous_seq_end) === 0; + + // At beginning of memcpy blocks we send two address + const expr dma_op = is_mem_eq * (DMA_MEM_EQ - DMA_MEM_CPY) + DMA_MEM_CPY; + permutation_proves(DMA_BUS_ID, [dma_op, dst64, src64, 0, 0, count, main_step], sel: previous_seq_end); +} \ No newline at end of file diff --git a/precompiles/dma/pil/dma_pre_post.pil b/precompiles/dma/pil/dma_pre_post.pil new file mode 100644 index 000000000..9916800a3 --- /dev/null +++ b/precompiles/dma/pil/dma_pre_post.pil @@ -0,0 +1,104 @@ +// src_addr, src_offset +// dst_addr, dst_offset, count + +airtemplate DmaPrePost(int N = 2**21) { + + col witness bits(MAIN_STEP_BITS) main_step; + col witness bits(ADDR_W_BITS) src_addr; + col witness bits(ADDR_W_BITS) dst_addr; + col witness bits(3) dst_offset; + col witness bits(3) count; + + col witness bits(1) offset_7; + col witness bits(1) offset_6; + col witness bits(1) offset_5; + col witness bits(1) offset_4; + col witness bits(1) offset_3; + col witness bits(1) offset_2; + col witness bits(1) offset_1; + const expr offset_7to1 = offset_7 + offset_6 + offset_5 + offset_4 + offset_3 + offset_2 + offset_1; + const expr offset_0 = (1 - offset_7to1); + + offset_7to1 * (offset_7to1) === 0; + const expr src_offset = offset_1 + offset_2 * 2 + offset_3 * 3 + offset_4 * 4 + offset_5 * 5 + + offset_6 * 6 + offset_7 * 7; + + offset_7 * (1 - offset_7) === 0; + offset_6 * (1 - offset_6) === 0; + offset_5 * (1 - offset_5) === 0; + offset_4 * (1 - offset_4) === 0; + offset_3 * (1 - offset_3) === 0; + offset_2 * (1 - offset_2) === 0; + offset_1 * (1 - offset_1) === 0; + + col witness bits(1) enabled; + col witness bits(1) enabled_second_read; + + enabled * (1 - enabled) === 0; + enabled_second_read * (1 - enabled_second_read) === 0; + enabled_second_read * (1 - enabled) === 0; + + const int V2R = 2 * 3; // Values To Read + const int B2R = V2R * 4; // Bytes To Read + + col witness bits(8) bytes[B2R]; + const expr values[V2R]; + + + for (int i = 0; i < B2R; i+=2) { + range_dual_byte(bytes[i], bytes[i+1], enabled); + } + + for (int i = 0; i < V2R; ++i) { + values[i] = bytes[i*4] + P2_8 * bytes[i*4+1] + P2_16 * bytes[i*4+2] + P2_24 * bytes[i*4+3]; + } + col witness bits(1) selb[8]; + expr mask = 0; + for (int i = 0; i < 8; ++i) { + selb[i] * (1 - selb[i]) === 0; + mask += selb[i] * (1 << (7+i)); + } + + col witness bits(32) write_value[2]; + + // byte_0, byte_1, byte_2, byte_3, byte_4, byte_5, byte_6, byte_7, byte_0', byte_1' ... + + write_value[0] === offset_0 * (selb[0] * bytes[0] + selb[1] * P2_8 * bytes[1] + selb[2] * P2_16 * bytes[2] + selb[3] * P2_24 * bytes[3]) + + offset_1 * (selb[0] * bytes[1] + selb[1] * P2_8 * bytes[2] + selb[2] * P2_16 * bytes[3] + selb[3] * P2_24 * bytes[4]) + + offset_2 * (selb[0] * bytes[2] + selb[1] * P2_8 * bytes[3] + selb[2] * P2_16 * bytes[4] + selb[3] * P2_24 * bytes[5]) + + offset_3 * (selb[0] * bytes[3] + selb[1] * P2_8 * bytes[4] + selb[2] * P2_16 * bytes[5] + selb[3] * P2_24 * bytes[6]) + + offset_4 * (selb[0] * bytes[4] + selb[1] * P2_8 * bytes[5] + selb[2] * P2_16 * bytes[6] + selb[3] * P2_24 * bytes[7]) + + offset_5 * (selb[0] * bytes[5] + selb[1] * P2_8 * bytes[6] + selb[2] * P2_16 * bytes[7] + selb[3] * P2_24 * bytes[8]) + + offset_6 * (selb[0] * bytes[6] + selb[1] * P2_8 * bytes[7] + selb[2] * P2_16 * bytes[8] + selb[3] * P2_24 * bytes[9]) + + offset_7 * (selb[0] * bytes[7] + selb[1] * P2_8 * bytes[8] + selb[2] * P2_16 * bytes[9] + selb[3] * P2_24 * bytes[10]) + + (1 - selb[0]) * bytes[16] + (1 - selb[1]) * P2_8 * bytes[17] + (1 - selb[2]) * P2_16 * bytes[18] + (1 - selb[3]) * P2_24 * bytes[19]; + + write_value[1] === offset_0 * (selb[4] * bytes[4] + selb[5] * P2_8 * bytes[5] + selb[6] * P2_16 * bytes[6] + selb[7] * P2_24 * bytes[7]) + + offset_1 * (selb[4] * bytes[5] + selb[5] * P2_8 * bytes[6] + selb[6] * P2_16 * bytes[7] + selb[7] * P2_24 * bytes[8]) + + offset_2 * (selb[4] * bytes[6] + selb[5] * P2_8 * bytes[7] + selb[6] * P2_16 * bytes[8] + selb[7] * P2_24 * bytes[9]) + + offset_3 * (selb[4] * bytes[7] + selb[5] * P2_8 * bytes[8] + selb[6] * P2_16 * bytes[9] + selb[7] * P2_24 * bytes[10]) + + offset_4 * (selb[4] * bytes[8] + selb[5] * P2_8 * bytes[9] + selb[6] * P2_16 * bytes[10] + selb[7] * P2_24 * bytes[11]) + + offset_5 * (selb[4] * bytes[9] + selb[5] * P2_8 * bytes[10] + selb[6] * P2_16 * bytes[11] + selb[7] * P2_24 * bytes[12]) + + offset_6 * (selb[4] * bytes[10] + selb[5] * P2_8 * bytes[11] + selb[6] * P2_16 * bytes[12] + selb[7] * P2_24 * bytes[13]) + + offset_7 * (selb[4] * bytes[11] + selb[5] * P2_8 * bytes[12] + selb[6] * P2_16 * bytes[13] + selb[7] * P2_24 * bytes[14]); + (1 - selb[4]) * bytes[20] + (1 - selb[5]) * P2_8 * bytes[21] + (1 - selb[6]) * P2_16 * bytes[22] + (1 - selb[7]) * P2_24 * bytes[23]; + + + // MEMORY ACCESS + // + // Read first 64 bits + // Read next 64 bits (optional, only if enabled_second_read) + // Read previous value before write + // Write final value + + precompiled_mem_load( sel: enabled, main_step: main_step, addr: src_addr * 8, value: [values[0], values[1]]); + precompiled_mem_load( sel: enabled_second_read, main_step: main_step, addr: src_addr * 8 + 8, value: [values[2], values[3]]); + precompiled_mem_load( sel: enabled, main_step: main_step, addr: dst_addr * 8, value: [values[4], values[5]]); + precompiled_mem_store(sel: enabled, main_step: main_step, addr: dst_addr * 8, value: write_value); + + // Send operation to bus + permutation_proves(DMA_BUS_ID, [DMA_MEM_PRE_POST, src_addr, dst_addr, src_offset, dst_offset, count, main_step], sel: enabled); + + const expr flags = mask + src_offset * P2_8 + dst_offset * P2_11 + count * P2_14 + enabled_second_read * P2_17; + lookup_assumes(DMA_PRE_POST_TABLE_ID, [flags], sel: enabled); +} \ No newline at end of file diff --git a/precompiles/dma/pil/dma_pre_post_table.pil b/precompiles/dma/pil/dma_pre_post_table.pil new file mode 100644 index 000000000..c8af1ee60 --- /dev/null +++ b/precompiles/dma/pil/dma_pre_post_table.pil @@ -0,0 +1,27 @@ +require "std_lookup.pil" + +const int DMA_PRE_POST_TABLE_ID = 13000; +const int DMA_PRE_POST_TABLE_SIZE = 280; + +airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { + col fixed FLAGS; + + int index = 0; + for (int count = 1; count < 8; ++ count) { + for (int src_offset = 0; src_offset < 8; ++src_offset) { + for (int dst_offset = 0; dst_offset < (9 - count); ++dst_offset) { + const int pre_mask = 0xFF >> (8 - count); + const int mask = pre_mask << (8 - (dst_offset + count)); + assert(mask != 0); + assert(mask <= 0xFF); + + const int enabled_second_read = (src_offset + count) > 8 ? 1:0; + FLAGS[index] = mask + src_offset * P2_8 + dst_offset * P2_11 + count * P2_14 + enabled_second_read * P2_17; + index += 1; + } + } + } + + col witness multiplicity; + lookup_proves(DMA_PRE_POST_TABLE_ID, mul: multiplicity, expressions: [FLAGS]); +} \ No newline at end of file diff --git a/precompiles/dma/pil/dma_rom.pil b/precompiles/dma/pil/dma_rom.pil new file mode 100644 index 000000000..0d8de9a9e --- /dev/null +++ b/precompiles/dma/pil/dma_rom.pil @@ -0,0 +1,47 @@ +require "std_lookup.pil" + +const int DMA_ROM_TABLE_SIZE = 8 * 8 * P2_6; + +airtemplate DmaRom(int N = DMA_ROM_TABLE_SIZE) { + + col fixed DST_OFFSET; + col fixed SRC_OFFSET; + col fixed L_COUNT; + col fixed FLAGS; + col fixed PRE_COUNT; + col fixed SRC_OFFSET_AFTER_PRE; + col fixed L_COUNT64; + + int i = 0; + for (int dst_offset = 0; dst_offset < 8; ++dst_offset) { + for (int src_offset = 0; src_offset < 8; ++src_offset) { + for (int l_count = 1; l_count < P2_6; ++ l_count) { + assert(dst_offset < 8); + assert(src_offset < 8); + assert(l_count < 64); + + const int use_pre = dst_offset > 0; + const int pre_count = use_pre ? (((8 - dst_offset) < l_count) ? (8 - dst_offset) : l_count) : 0; + const int post_count = (l_count - pre_count) % 8; + const int use_post = post_count > 0; + const int memcpy_count = l_count - pre_count - post_count; + const int use_memcpy = memcpy_count > 0; + const int src64_inc_by_pre = (use_pre && (src_offset + pre_count) >= 8) ? 1 : 0; + + DST_OFFSET[i] = dst_offset; + SRC_OFFSET[i] = src_offset; + L_COUNT[i] = l_count; + FLAGS[i] = use_pre + use_memcpy * 2 + use_post * 4 + src64_inc_by_pre * 8; + PRE_COUNT[i] = pre_count; + SRC_OFFSET_AFTER_PRE[i] = (src_offset + pre_count) % 8; + L_COUNT64[i] = (l_count - pre_count) / 8; + + i += 1; + } + } + } + println(`${i} rows`); + + col witness multiplicity; + lookup_proves(DMA_ROM_ID, [DST_OFFSET, SRC_OFFSET, L_COUNT, FLAGS, PRE_COUNT, SRC_OFFSET_AFTER_PRE, L_COUNT64], multiplicity); +} \ No newline at end of file diff --git a/precompiles/dma/pil/dma_unaligned.pil b/precompiles/dma/pil/dma_unaligned.pil new file mode 100644 index 000000000..0f4b7a637 --- /dev/null +++ b/precompiles/dma/pil/dma_unaligned.pil @@ -0,0 +1,229 @@ + +// TODO: Optimization Dma32AlignedOp + +airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable_mem_cpy_byte = 1) { + + const expr L1 = get_L1(); + const expr LAST = L1'; + + airval segment_id; // Id of current segment + airval segment_previous_seq_end; // Last value of `seq_end` in previous segment. + airval segment_previous_src64; // Last value of `src64` in previous segment. + airval segment_previous_dst64; // Last value of `dst64` in previous segment. + airval segment_previous_main_step; // Last value of `main_step` in previous segment. + airval segment_previous_offset; // Last value of `offset` in previous segment. + airval segment_previous_count; // Last value of `count` in previous segment. + airval segment_previous_is_mem_eq; // Last value of `is_mem_eq' in previous segment. + + + airval segment_first_bytes[8]; // bytes of next block + + airval segment_last_seq_end; // Last value of `seq_end` in current segment. + airval segment_last_src64; // Last value of `src64` in current segment. + airval segment_last_dst64; // Last value of `dst64` in current segment. + airval segment_last_main_step; // Last value of `main_step` in current segment. + airval segment_last_offset; // Last value of `offset` in current segment. + airval segment_last_count; // Last value of `count` in current segment. + airval segment_last_is_mem_eq; // Last value of `is_mem_eq' in previous segment. + + airval segment_next_bytes[8]; // bytes of next block + + airval is_last_segment; // 1 if this is the last segment, 0 otherwise. + + segment_previous_seq_end * (1 - segment_previous_seq_end) === 0; + segment_last_seq_end * (1 - segment_last_seq_end) === 0; + + is_last_segment * (1 - is_last_segment) === 0; + + // if it's last segment must be the end of sequence or padding that use end of sequence + is_last_segment * (1 - segment_last_seq_end) === 0; + + col witness bits(MAIN_STEP_BITS) main_step; + col witness bits(ADDR_W_BITS) src64; + col witness bits(ADDR_W_BITS) dst64; + col witness bits(32) count; + col witness bits(32) value[RC]; + + col witness bits(1) seq_end; + seq_end * (1 - seq_end) === 0; + + col witness bits(1) sel; + sel * (1 - sel) == 0; + + col witness bits(1) previous_seq_end; + previous_seq_end * (1 - previous_seq_end) === 0; + previous_seq_end === L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; + + col witness bits(1) is_mem_eq; // Last value of `is_mem_eq' in previous segment. + is_mem_eq * (1 - is_mem_eq) === 0; + + const expr previous_src64 = L1 * (segment_previous_src64 - 'src64) + 'src64; + const expr previous_dst64 = L1 * (segment_previous_dst64 - 'dst64) + 'dst64; + const expr previous_main_step = L1 * (segment_previous_main_step - 'main_step) + 'main_step; + const expr previous_count = L1 * (segment_previous_count - 'count) + 'count; + const expr previous_is_mem_eq = L1 * (segment_previous_is_mem_eq - 'is_mem_eq) + 'is_mem_eq; + + LAST * (seq_end - segment_last_seq_end) === 0; + LAST * (src64 - segment_last_src64) === 0; + LAST * (dst64 - segment_last_dst64) === 0; + LAST * (main_step - segment_last_main_step) === 0; + LAST * (count - segment_last_count) === 0; + LAST * (is_mem_eq - segment_last_is_mem_eq) === 0; + + col witness bits(1) offset_7; + col witness bits(1) offset_6; + col witness bits(1) offset_5; + col witness bits(1) offset_4; + col witness bits(1) offset_3; + col witness bits(1) offset_2; + const expr offset_7to2 = offset_7 + offset_6 + offset_5 + offset_4 + offset_3 + offset_2; + const expr offset_1 = (1 - offset_7to2); + + offset_7to2 * (offset_7to2) === 0; + const expr offset = offset_1 + offset_2 * 2 + offset_3 * 3 + offset_4 * 4 + offset_5 * 5 + + offset_6 * 6 + offset_7 * 7; + + offset_7 * (1 - offset_7) === 0; + offset_6 * (1 - offset_6) === 0; + offset_5 * (1 - offset_5) === 0; + offset_4 * (1 - offset_4) === 0; + offset_3 * (1 - offset_3) === 0; + offset_2 * (1 - offset_2) === 0; + + const expr previous_offset = L1 * (segment_previous_offset - 'offset) + 'offset; + LAST * (offset - segment_last_offset) === 0; + + col witness bits(8) read_bytes[8]; + + range_dual_byte(read_bytes[0], read_bytes[1]); + range_dual_byte(read_bytes[2], read_bytes[3]); + range_dual_byte(read_bytes[4], read_bytes[5]); + range_dual_byte(read_bytes[6], read_bytes[7]); + + const expr read_value[2]; + col witness bits(32) write_value[2]; + read_value[0] = read_bytes[0] + P2_8 * read_bytes[1] + P2_16 * read_bytes[2] + P2_24 * read_bytes[3]; + read_value[1] = read_bytes[4] + P2_8 * read_bytes[5] + P2_16 * read_bytes[6] + P2_24 * read_bytes[7]; + + // byte_0, byte_1, byte_2, byte_3, byte_4, byte_5, byte_6, byte_7, byte_0', byte_1' ... + + const expr next_bytes[8]; + + for (int i = 0; i < 8; ++i) { + next_bytes[i] = LAST * (segment_next_bytes[i] - read_bytes[i]') + read_bytes[i]'; + (1 - segment_previous_seq_end) * L1 * (segment_first_bytes[i] - read_bytes[i]) === 0; + segment_previous_seq_end * L1 * segment_first_bytes[i] === 0; + } + + write_value[0] === offset_1 * (read_bytes[1] + P2_8 * read_bytes[2] + P2_16 * read_bytes[3] + P2_24 * read_bytes[4]) + + offset_2 * (read_bytes[2] + P2_8 * read_bytes[3] + P2_16 * read_bytes[4] + P2_24 * read_bytes[5]) + + offset_3 * (read_bytes[3] + P2_8 * read_bytes[4] + P2_16 * read_bytes[5] + P2_24 * read_bytes[6]) + + offset_4 * (read_bytes[4] + P2_8 * read_bytes[5] + P2_16 * read_bytes[6] + P2_24 * read_bytes[7]) + + offset_5 * (read_bytes[5] + P2_8 * read_bytes[6] + P2_16 * read_bytes[7] + P2_24 * read_bytes[0]) + + offset_6 * (read_bytes[6] + P2_8 * read_bytes[7] + P2_16 * next_bytes[0] + P2_24 * next_bytes[1]) + + offset_7 * (read_bytes[7] + P2_8 * next_bytes[0] + P2_16 * next_bytes[1] + P2_24 * next_bytes[2]); + + write_value[1] === offset_1 * (read_bytes[5] + P2_8 * read_bytes[6] + P2_16 * read_bytes[7] + P2_24 * next_bytes[0]) + + offset_2 * (read_bytes[6] + P2_8 * read_bytes[7] + P2_16 * next_bytes[0] + P2_24 * next_bytes[1]) + + offset_3 * (read_bytes[7] + P2_8 * next_bytes[0] + P2_16 * next_bytes[1] + P2_24 * next_bytes[2]) + + offset_4 * (next_bytes[0] + P2_8 * next_bytes[1] + P2_16 * next_bytes[2] + P2_24 * next_bytes[3]) + + offset_5 * (next_bytes[1] + P2_8 * next_bytes[2] + P2_16 * next_bytes[3] + P2_24 * next_bytes[4]) + + offset_6 * (next_bytes[2] + P2_8 * next_bytes[3] + P2_16 * next_bytes[4] + P2_24 * next_bytes[5]) + + offset_7 * (next_bytes[3] + P2_8 * next_bytes[4] + P2_16 * next_bytes[5] + P2_24 * next_bytes[6]); + + // memory access + precompiled_mem_load( + sel: sel, + main_step: main_step, + addr: src64 * 8, + value: read_value + ); + + precompiled_mem_store( + sel: (1 - seq_end), + main_step: main_step, + addr: dst64 * 8, + value: write_value + ); + + // At begining of sequence + // DMA BUS [op, dst64, src64, dst_offset, src_offset, bytes, main_step] + permutation_proves(DMA_BUS_ID, [DMA_MEM_CPY, dst64, src64, 0, offset, count, main_step], sel: previous_seq_end); + + + // TRANSITIONS: + // + // After first element of sequence + // count = 'count - 8 + // offset = 'offset + // src64 = 'src64 + 1 + // dst64 = 'dst64 + 1 + + (count - (previous_count - 8)) * (1 - previous_seq_end) === 0; + (offset - previous_offset) * (1 - previous_seq_end) === 0; + (src64 - (previous_src64 + 1)) * (1 - previous_seq_end) === 0; + (dst64 - (previous_dst64 + 1)) * (1 - previous_seq_end) === 0; + + // At end of sequence + // count must be 0 at end of sequence + count * seq_end === 0; + + // SECURITY: control count no negative + // + // if the seq_end it isn't active when count = 0, in each rows continues decreasing 8 units, + // means in 2^22 rows * 2^3 = 2^25. It's secure, because if any row lies, at end of instance + // this constraint fails. + + airval last_count_chunk[2]; + range_check(expression: last_count_chunk[0], min: 0, max: 2**16-1); + range_check(expression: last_count_chunk[1], min: 0, max: 2**16-1); + last_count_chunk[0] + last_count_chunk[1] * P2_16 === segment_last_count; + + + // PADDING + // + // cancel operation sent to bus src=0, dst=0, offset=1, count=0, main_step=0 + // precompiled_mem_load_padding demostrate read addr=0 width=8 main_step=0 value=0 + // in padding rows seq_end is active, means no precompiled_mem_store + + airval padding_rows; + permutation_proves(DMA_BUS_ID, [DMA_MEM_CPY, 0, 0, 0, 1, 0, 0], sel: padding_rows); + precompiled_mem_load_padding(padding: padding_rows); + + // CONTINUATIONS + + // AIR_ID, segment_id, seq_end, src64, dst64, count, main_step + + direct_global_update_proves(MEM_CPY_BYTE_CONT_ID, [0, // initial segment_id + 0, + 1, // initial seq_end + 0, // initial src64 + 0, // initial dst64 + 0, // initial offset + 0, // initial count + 0, + 0,0,0,0,0,0,0,0], // initial main_step + sel: enable_mem_cpy_byte); + + + direct_update_assumes(MEM_CPY_BYTE_CONT_ID, [ segment_id, + 0, + segment_previous_seq_end, + segment_previous_src64, + segment_previous_dst64, + segment_previous_offset, + segment_previous_count, + segment_previous_main_step, + ...segment_first_bytes]); + + direct_update_proves(MEM_CPY_BYTE_CONT_ID, [ segment_id + 1, + is_last_segment, + segment_last_seq_end, + segment_last_src64, + segment_last_dst64, + segment_last_offset, + segment_last_count, + segment_last_main_step, + ...segment_next_bytes], sel: 1 - is_last_segment); + +} \ No newline at end of file diff --git a/precompiles/dma/pil/tools.pil b/precompiles/dma/pil/tools.pil new file mode 100644 index 000000000..57c6325ff --- /dev/null +++ b/precompiles/dma/pil/tools.pil @@ -0,0 +1,13 @@ + +function get_continuation_id(const int base_cont_id, const int max_cont): int { + container proof.continuation.`${AIRTEMPLATE}` alias continuation { + int id = base_cont_id; + } + const int id = continuation.id; + assert(id < base_cont_id); + continuation.id += continuation.id; + return id; +} + +// const int continuation_id = get_new_continuation_id(MEM_CPY_BYTE_CONT_ID, 1); + diff --git a/precompiles/dma/src/add256_bus_device.rs b/precompiles/dma/src/add256_bus_device.rs deleted file mode 100644 index 6a0f31e59..000000000 --- a/precompiles/dma/src/add256_bus_device.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! The `Add256Counter` module defines a counter for tracking add256-related operations -//! sent over the data bus. It connects to the bus and gathers metrics for specific -//! `ZiskOperationType::Add256` instructions. - -use std::{collections::VecDeque, ops::Add}; - -use zisk_common::MemCollectorInfo; -use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, STEP, -}; -use zisk_core::ZiskOperationType; - -use crate::{generate_add256_mem_inputs, skip_add256_mem_inputs}; - -/// The `Add256Counter` struct represents a counter that monitors and measures -/// add256-related operations on the data bus. -/// -/// It tracks specific operation types (`ZiskOperationType`) and updates counters for each -/// accepted operation type whenever data is processed on the bus. -pub struct Add256CounterInputGen { - /// Add256 counter. - counter: Counter, - - /// Bus device mode (counter or input generator). - mode: BusDeviceMode, -} - -impl Add256CounterInputGen { - /// Creates a new instance of `Add256Counter`. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus to which this counter is connected. - /// * `op_type` - A vector of `ZiskOperationType` instructions to monitor. - /// - /// # Returns - /// A new `Add256Counter` instance. - pub fn new(mode: BusDeviceMode) -> Self { - Self { counter: Counter::default(), mode } - } - - /// Retrieves the count of instructions for a specific `ZiskOperationType`. - /// - /// # Arguments - /// * `op_type` - The operation type to retrieve the count for. - /// - /// # Returns - /// Returns the count of instructions for the specified operation type. - pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { - (op_type == ZiskOperationType::BigInt).then_some(self.counter.inst_count) - } -} - -impl Metrics for Add256CounterInputGen { - /// Tracks activity on the connected bus and updates counters for recognized operations. - /// - /// # Arguments - /// * `_bus_id` - The ID of the bus (unused in this implementation). - /// * `_data` - The data received from the bus. - /// - /// # Returns - /// An empty vector, as this implementation does not produce any derived inputs for the bus. - #[inline(always)] - fn measure(&mut self, _data: &[u64]) { - self.counter.update(1); - } - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl Add for Add256CounterInputGen { - type Output = Add256CounterInputGen; - - /// Combines two `Add256Counter` instances by summing their counters. - /// - /// # Arguments - /// * `self` - The first `Add256Counter` instance. - /// * `other` - The second `Add256Counter` instance. - /// - /// # Returns - /// A new `Add256Counter` with combined counters. - fn add(self, other: Self) -> Add256CounterInputGen { - Add256CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } - } -} - -impl BusDevice for Add256CounterInputGen { - /// Processes data received on the bus, updating counters and generating inputs when applicable. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus sending the data. - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - debug_assert!(*bus_id == OPERATION_BUS_ID); - - if data[OP_TYPE] as u32 != ZiskOperationType::BigInt as u32 { - return true; - } - - if let Some(mem_collectors_info) = mem_collector_info { - if skip_add256_mem_inputs(data[B] as u32, data, mem_collectors_info) { - return true; - } - } - - let step_main = data[STEP]; - let addr_main = data[B] as u32; - - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); - } - - generate_add256_mem_inputs(addr_main, step_main, data, only_counters, pending); - - true - } - - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - - /// Provides a dynamic reference for downcasting purposes. - fn as_any(self: Box) -> Box { - self - } -} diff --git a/precompiles/dma/src/add256_gen_mem_inputs.rs b/precompiles/dma/src/add256_gen_mem_inputs.rs deleted file mode 100644 index 87510b3d4..000000000 --- a/precompiles/dma/src/add256_gen_mem_inputs.rs +++ /dev/null @@ -1,111 +0,0 @@ -use lib_c::add256; - -use crate::add256_constants::*; -use precompiles_common::MemBusHelpers; -use std::collections::VecDeque; -use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; - -#[derive(Debug)] -pub struct Add256MemInputConfig { - pub indirect_params: usize, - pub rewrite_params: bool, - pub read_params: usize, - pub write_params: usize, - pub chunks_per_param: usize, -} - -pub fn generate_add256_mem_inputs( - addr_main: u32, - step_main: u64, - data: &[u64], - only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, -) { - // Start by generating the params (indirection read, direct, indirection write) - for iparam in 0..PARAMS { - MemBusHelpers::mem_aligned_load( - addr_main + iparam as u32 * 8, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], - pending, - ); - } - - // generate load params - for iparam in 0..READ_PARAMS { - let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; - for ichunk in 0..PARAM_CHUNKS { - MemBusHelpers::mem_aligned_load( - param_addr + ichunk as u32 * 8, - step_main, - data[START_READ_PARAMS + iparam * PARAM_CHUNKS + ichunk], - pending, - ); - } - } - - let mut write_data = [0u64; PARAM_CHUNKS]; - if !only_counters { - let a: [u64; 4] = - data[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(); - let b: [u64; 4] = data - [START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] - .try_into() - .unwrap(); - add256(&a, &b, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS], &mut write_data); - } - - // verify write param - let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; - for (ichunk, write_data) in write_data.iter().enumerate().take(PARAM_CHUNKS) { - let param_addr = write_addr + ichunk as u32 * 8; - MemBusHelpers::mem_aligned_write(param_addr, step_main, *write_data, pending); - } -} - -// op_a = step -// op_b = addr_main -// mem_trace: @a, @b, cin, @c, a[0..3], b[0..3], cout, [ c[0..3] ] - -pub fn skip_add256_mem_inputs( - addr_main: u32, - data: &[u64], - mem_collectors_info: &[MemCollectorInfo], -) -> bool { - // verify main params "struct" of indirections - for iparam in 0..PARAMS { - let addr = addr_main + iparam as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } - } - } - - // verify read params - for iparam in 0..READ_PARAMS { - let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; - for ichunk in 0..PARAM_CHUNKS { - let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } - } - } - } - - // verify write param - let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; - for ichunk in 0..PARAM_CHUNKS { - let addr = write_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } - } - } - - true -} diff --git a/precompiles/dma/src/add256_manager.rs b/precompiles/dma/src/add256_manager.rs deleted file mode 100644 index 854ef7a94..000000000 --- a/precompiles/dma/src/add256_manager.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::sync::Arc; - -use fields::PrimeField64; -use pil_std_lib::Std; -use zisk_common::{ - BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, - InstanceInfo, PayloadType, Planner, -}; -use zisk_core::ZiskOperationType; -use zisk_pil::Add256Trace; - -use crate::{Add256CounterInputGen, Add256Instance, Add256Planner, Add256SM}; - -/// The `Add256Manager` struct represents the Add256 manager, -/// which is responsible for managing the Add256 state machine and its table state machine. -#[allow(dead_code)] -pub struct Add256Manager { - /// Add256 state machine - add256_sm: Arc>, -} - -impl Add256Manager { - /// Creates a new instance of `Add256Manager`. - /// - /// # Returns - /// An `Arc`-wrapped instance of `Add256Manager`. - pub fn new(std: Arc>) -> Arc { - let add256_sm = Add256SM::new(std); - - Arc::new(Self { add256_sm }) - } - - pub fn build_add256_counter(&self) -> Add256CounterInputGen { - Add256CounterInputGen::new(BusDeviceMode::Counter) - } - - pub fn build_add256_input_generator(&self) -> Add256CounterInputGen { - Add256CounterInputGen::new(BusDeviceMode::InputGenerator) - } -} - -impl ComponentBuilder for Add256Manager { - /// Builds and returns a new counter for monitoring Add256 operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for Add256 operations. - fn build_counter(&self) -> Option> { - Some(Box::new(Add256CounterInputGen::new(BusDeviceMode::Counter))) - } - - /// Builds a planner to plan Add256-related instances. - /// - /// # Returns - /// A boxed implementation of `RegularPlanner`. - fn build_planner(&self) -> Box { - // Get the number of Add256s that a single Add256 instance can handle - let num_availables = self.add256_sm.num_availables; - - Box::new(Add256Planner::new().add_instance(InstanceInfo::new( - Add256Trace::::AIRGROUP_ID, - Add256Trace::::AIR_ID, - num_availables, - ZiskOperationType::BigInt, - ))) - } - - /// Builds an inputs data collector for Add256 operations. - /// - /// # Arguments - /// * `ictx` - The context of the instance, containing the plan and its associated - /// configurations. - /// - /// # Returns - /// A boxed implementation of `BusDeviceInstance` specific to the requested `air_id` instance. - /// - /// # Panics - /// Panics if the provided `air_id` is not supported. - fn build_instance(&self, ictx: InstanceCtx) -> Box> { - match ictx.plan.air_id { - id if id == Add256Trace::::AIR_ID => { - Box::new(Add256Instance::new(self.add256_sm.clone(), ictx)) - } - _ => { - panic!("Add256Builder::get_instance() Unsupported air_id: {:?}", ictx.plan.air_id) - } - } - } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(Add256CounterInputGen::new(BusDeviceMode::InputGenerator))) - } -} diff --git a/precompiles/dma/src/dma.rs b/precompiles/dma/src/dma.rs new file mode 100644 index 000000000..856633cdb --- /dev/null +++ b/precompiles/dma/src/dma.rs @@ -0,0 +1,106 @@ +use std::sync::Arc; + +use fields::PrimeField64; + +use pil_std_lib::Std; +use proofman_common::AirInstance; +use proofman_util::{timer_start_trace, timer_stop_and_log_info, timer_stop_and_log_trace}; + +use super::DmaMemCpyInput; +/* +#[cfg(feature = "packed")] +mod types { + use zisk_pil::{DmaRowPacked, DmaTracePacked}; + pub type DmaTraceRowType = DmaRowPacked; + pub type DmaTraceType = DmaTracePacked; +} + +#[cfg(not(feature = "packed"))] +mod types { + use zisk_pil::{DmaTrace, DmaTraceRow}; + pub type DmaTraceRowType = DmaTraceRow; + pub type DmaTraceType = DmaTrace; +} + +use types::*; +*/ +#[cfg(feature = "packed")] +pub use zisk_pil::{DmaRowPacked as DmaTraceRow, DmaTracePacked as DmaTrace}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{DmaTrace, DmaTraceRow}; + +/// The `DmaSM` struct encapsulates the logic of the Dma State Machine. +pub struct DmaSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Number of available dmas in the trace. + pub num_availables: usize, + + /// Range checks ID's + range_21_bits_id: usize, +} + +impl DmaSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaSM` instance. + pub fn new(std: Arc>) -> Arc { + // Compute some useful values + let num_availables = DmaTrace::::NUM_ROWS; + + let range_21_bits_id = std.get_range_id(0, (1 << 21) - 1, None); + + Arc::new(Self { std, num_availables, range_21_bits_id }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_slice( + &self, + input: &DmaMemCpyInput, + trace: &mut DmaTraceRow, + multiplicities: &mut [u32], + ) { + unimplemented!(); + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> AirInstance { + let mut trace = DmaTrace::::new_from_vec(trace_buffer); + + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); + assert!(total_inputs <= num_rows); + + tracing::info!( + "··· Creating Dma instance [{} / {} rows filled {:.2}%]", + total_inputs, + num_rows, + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_TRACE); + + timer_stop_and_log_info!(DMA_TRACE); + unimplemented!(); + } +} diff --git a/precompiles/dma/src/dma_64_aligned.rs b/precompiles/dma/src/dma_64_aligned.rs new file mode 100644 index 000000000..62bb59444 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned.rs @@ -0,0 +1,168 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use rayon::prelude::*; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use zisk_pil::{DmaTrace, DmaTraceRow}; + +use super::DmaInput; + +/// The `DmaSM` struct encapsulates the logic of the Dma64Aligned State Machine. +pub struct DmaSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Number of available Dma64Aligned in the trace. + pub num_availables: usize, + + /// Range checks ID's + range_id: usize, +} + +impl DmaSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaSM` instance. + pub fn new(std: Arc>) -> Arc { + // Compute some useful values + let num_availables = DmaTrace::::NUM_ROWS; + + let range_id = std.get_range_id(0, (1 << 16) - 1, None); + + Arc::new(Self { std, num_availables, range_id }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma64Aligned trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_slice( + &self, + input: &DmaInput, + trace: &mut DmaTraceRow, + multiplicities: &mut [u32], + ) { + trace.cin = F::from_bool(input.cin != 0); + let mut cout_2 = input.cin as u32; + + for i in 0..4 { + let al = input.a[i] as u32; + let ah = (input.a[i] >> 32) as u32; + + let bl = input.b[i] as u32; + let bh = (input.b[i] >> 32) as u32; + + trace.a[i][0] = F::from_u32(al); + trace.a[i][1] = F::from_u32(ah); + trace.b[i][0] = F::from_u32(bl); + trace.b[i][1] = F::from_u32(bh); + let cl = al as u64 + bl as u64 + cout_2 as u64; + let cout_1 = cl >> 32; + let ch = ah as u64 + bh as u64 + cout_1; + cout_2 = (ch >> 32) as u32; + + let cll = cl as u16; + let clh = (cl >> 16) as u16; + let chl = ch as u16; + let chh = (ch >> 16) as u16; + + trace.c_chunks[i][0] = F::from_u16(cll); + trace.c_chunks[i][1] = F::from_u16(clh); + trace.c_chunks[i][2] = F::from_u16(chl); + trace.c_chunks[i][3] = F::from_u16(chh); + + trace.cout[i][0] = F::from_u8(cout_1 as u8); + trace.cout[i][1] = F::from_u8(cout_2 as u8); + + multiplicities[cll as usize] += 1; + multiplicities[clh as usize] += 1; + multiplicities[chl as usize] += 1; + multiplicities[chh as usize] += 1; + } + trace.addr_params = F::from_u32(input.addr_main); + trace.addr_a = F::from_u32(input.addr_a); + trace.addr_b = F::from_u32(input.addr_b); + trace.addr_c = F::from_u32(input.addr_c); + trace.step = F::from_u64(input.step_main); + trace.sel = F::ONE; + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> AirInstance { + let mut trace = DmaTrace::new_from_vec(trace_buffer); + + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); + assert!(total_inputs <= num_rows); + + tracing::info!( + "··· Creating Dma64Aligned instance [{} / {} rows filled {:.2}%]", + total_inputs, + num_rows, + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_64_ALIGNED_TRACE); + + // Split the dma64aligned_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.row_slice_mut(); + + // Determinar tamaño óptimo de chunks + let num_threads = rayon::current_num_threads(); + let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); + + // Procesar en chunks para compartir arrays locales de multiplicities + let local_multiplicities_vec: Vec> = flat_inputs + .par_chunks(chunk_size) + .zip(trace_rows.par_chunks_mut(chunk_size)) + .map(|(input_chunk, trace_chunk)| { + // Array local compartido por este chunk + let mut local_multiplicities = vec![0u32; 1 << 16]; + + // Procesar todos los inputs del chunk + for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { + self.process_slice(input, trace_row, &mut local_multiplicities); + } + + local_multiplicities + }) + .collect(); + + // Sumar todos los arrays locales en uno global + let mut global_multiplicities = vec![0u32; 1 << 16]; + for local_multiplicities in local_multiplicities_vec { + for (i, count) in local_multiplicities.iter().enumerate() { + global_multiplicities[i] += count; + } + } + + // Enviar el resultado final al std + self.std.range_checks(self.range_id, global_multiplicities); + + timer_stop_and_log_trace!(DMA_64_ALIGNED_TRACE); + + trace.row_slice_mut()[total_inputs..num_rows] + .par_iter_mut() + .for_each(|slot| *slot = Dma64AlignedTraceRow:: { ..Default::default() }); + AirInstance::::new_from_trace(FromTrace::new(&mut trace)) + } +} diff --git a/precompiles/dma/src/add256_input.rs b/precompiles/dma/src/dma_64_aligned_input.rs similarity index 75% rename from precompiles/dma/src/add256_input.rs rename to precompiles/dma/src/dma_64_aligned_input.rs index a6239f825..534b2c88a 100644 --- a/precompiles/dma/src/add256_input.rs +++ b/precompiles/dma/src/dma_64_aligned_input.rs @@ -1,21 +1,20 @@ -use crate::add256_constants::*; -use zisk_common::OperationAdd256Data; +use crate::dma_constants::*; +use zisk_common::OperationDmaData; use zisk_common::{B, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; #[derive(Debug)] -pub struct Add256Input { +pub struct MemCpyInput { pub step_main: u64, - pub addr_main: u32, - pub addr_a: u32, - pub addr_b: u32, - pub addr_c: u32, - pub cin: u64, + pub addr_src: u32, + pub addr_dst: u32, + pub a, pub a: [u64; 4], pub b: [u64; 4], + pub count: u32, } -impl Add256Input { - pub fn from(values: &OperationAdd256Data) -> Self { +impl MemCpyInput { + pub fn from(values: &OperationDmaData) -> Self { Self { step_main: values[STEP], addr_main: values[B] as u32, diff --git a/precompiles/dma/src/dma_bus_device.rs b/precompiles/dma/src/dma_bus_device.rs new file mode 100644 index 000000000..c68aa835d --- /dev/null +++ b/precompiles/dma/src/dma_bus_device.rs @@ -0,0 +1,181 @@ +//! The `DmaCounter` module defines a counter for tracking dma-related operations +//! sent over the data bus. It connects to the bus and gathers metrics for specific +//! `ZiskOperationType::Dma` instructions. + +use std::{collections::VecDeque, ops::Add}; + +use zisk_common::{ + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_DMA_MEMCMP_DATA_SIZE, + OPERATION_BUS_ID, OP_TYPE, STEP, +}; +use zisk_common::{MemCollectorInfo, A, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; +use zisk_core::ZiskOperationType; + +use crate::{generate_dma_mem_inputs, skip_dma_mem_inputs}; + +/// The `DmaCounter` struct represents a counter that monitors and measures +/// dma-related operations on the data bus. +/// +/// It tracks specific operation types (`ZiskOperationType`) and updates counters for each +/// accepted operation type whenever data is processed on the bus. +pub struct DmaCounterInputGen { + /// sizes of memcpy + dma_pre_post_ops: usize, + dma_ops: usize, + dma_unaligned_ops: usize, + dma_64_aligned_ops: usize, + + /// Bus device mode (counter or input generator). + mode: BusDeviceMode, +} + +impl DmaCounterInputGen { + /// Creates a new instance of `DmaCounter`. + /// + /// # Arguments + /// * `mode` - The ID of the bus to which this counter is connected. + /// + /// # Returns + /// A new `DmaCounter` instance. + pub fn new(mode: BusDeviceMode) -> Self { + Self { dma_pre_post_ops: 0, dma_ops: 0, dma_unaligned_ops: 0, dma_64_aligned_ops: 0, mode } + } + + /// Retrieves the count of instructions for a specific `ZiskOperationType`. + /// + /// # Arguments + /// * `dst` - The destination address of operation. + /// * `src` - The source address of operation. + /// * `count` - The bytes of operation. + pub fn inst_count_memcpy(&mut self, dst: u64, src: u64, count: usize) { + let src_offset = dst & 0x07; + let dst_offset = src & 0x07; + + // offset => max bytes is 8 - offset + if count > 0 { + let remaining = if dst_offset > 0 { + self.dma_pre_post_ops += 1; + std::cmp::min(8 - dst_offset as usize, count) + } else { + count + }; + if (remaining % 8) > 0 { + self.dma_pre_post_ops += 1; + } + if dst_offset == src_offset { + self.dma_64_aligned_ops += remaining >> 3; + } else { + self.dma_unaligned_ops += remaining >> 3; + } + } + self.dma_ops += 1; + } +} + +impl Metrics for DmaCounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. + /// + /// # Returns + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, data: &[u64]) { + let dst = data[A]; + let src = data[B]; + let count = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as usize; + self.inst_count_memcpy(dst, src, count); + } + + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for DmaCounterInputGen { + type Output = DmaCounterInputGen; + + /// Combines two `DmaCounter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `DmaCounter` instance. + /// * `other` - The second `DmaCounter` instance. + /// + /// # Returns + /// A new `DmaCounter` with combined counters. + fn add(self, other: Self) -> DmaCounterInputGen { + DmaCounterInputGen { + dma_pre_post_ops: self.dma_pre_post_ops + other.dma_pre_post_ops, + dma_ops: self.dma_ops + other.dma_ops, + dma_unaligned_ops: self.dma_unaligned_ops + other.dma_unaligned_ops, + dma_64_aligned_ops: self.dma_64_aligned_ops + other.dma_64_aligned_ops, + mode: self.mode, + } + } +} + +impl BusDevice for DmaCounterInputGen { + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + pending: &mut VecDeque<(BusId, Vec)>, + mem_collector_info: Option<&[MemCollectorInfo]>, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if data[OP_TYPE] as u32 != ZiskOperationType::Dma as u32 { + return true; + } + + let dst = data[A]; + let src = data[B]; + let count = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as usize; + if let Some(mem_collectors_info) = mem_collector_info { + if skip_dma_mem_inputs(dst, src, count, mem_collectors_info) { + return true; + } + } + + let only_counters = self.mode == BusDeviceMode::Counter; + if only_counters { + self.measure(data); + } + + let step_main = data[STEP]; + let data_ext = &[0u64; 4]; + generate_dma_mem_inputs(dst, src, count, step_main, data, data_ext, only_counters, pending); + + true + } + + /// Returns the bus IDs associated with this counter. + /// + /// # Returns + /// A vector containing the connected bus ID. + fn bus_id(&self) -> Vec { + vec![OPERATION_BUS_ID] + } + + /// Provides a dynamic reference for downcasting purposes. + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/add256_constants.rs b/precompiles/dma/src/dma_constants.rs similarity index 100% rename from precompiles/dma/src/add256_constants.rs rename to precompiles/dma/src/dma_constants.rs diff --git a/precompiles/dma/src/dma_gen_mem_inputs.rs b/precompiles/dma/src/dma_gen_mem_inputs.rs new file mode 100644 index 000000000..4148fd25b --- /dev/null +++ b/precompiles/dma/src/dma_gen_mem_inputs.rs @@ -0,0 +1,291 @@ +use crate::{DmaHelpers, DmaValues}; +use precompiles_common::MemBusHelpers; +use std::collections::VecDeque; +use zisk_common::MemCollectorInfo; +use zisk_common::{BusId, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; + +#[derive(Debug)] +pub struct DmaMemInputConfig { + pub indirect_params: usize, + pub rewrite_params: bool, + pub read_params: usize, + pub write_params: usize, + pub chunks_per_param: usize, +} + +// all DMA memory operation are aligned + +// minimal trace +// reads + writes +const MASK_ALIGNED_ADDR: u64 = !0x07; +/* +pub fn calculate(dst: u64, src: u64, count: u64) -> (usize, usize, usize) { + let from_src = src & MASK_ALIGNED_ADDR; + let to_src = (src + count - 1) & MASK_ALIGNED_ADDR; + let count_src = (to_src - from_src) >> 3; + + let first_dst_addr = dst & MASK_ALIGNED_ADDR; + let read_first_dst_addr = dst & 0x07 != 0; + + let last_dst_addr = (dst + count - 1) & MASK_ALIGNED_ADDR; + let read_last_dst_addr = (dst + count) & 0x07 != 0 && last_dst_addr > first_dst_addr; +} +*/ + +pub fn generate_dma_mem_inputs( + dst: u64, + src: u64, + count: usize, + step_main: u64, + data: &[u64], + data_ext: &[u64], + only_counters: bool, + pending: &mut VecDeque<(BusId, Vec)>, +) { + let from_src = src & !0x07; + let to_src = (src + count as u64 - 1) & MASK_ALIGNED_ADDR; + let count_src = (to_src - from_src) >> 3; + + let first_dst_addr = dst & MASK_ALIGNED_ADDR; + let read_first_dst_addr = dst & 0x07 != 0; + + let last_dst_addr = (dst + count as u64 - 1) & MASK_ALIGNED_ADDR; + let read_last_dst_addr = (dst + count as u64) & 0x07 != 0 && last_dst_addr > first_dst_addr; + + let dma = DmaHelpers::precalculate_dma_values(dst, src, count as usize); + + unimplemented!(); + /* + if dma.pre_count > 0 { + MemBusHelpers::mem_aligned_load( + dst_aligned, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], + pending, + ); + MemBusHelpers::mem_aligned_load( + dst_aligned, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], + pending, + ); + + MemBusHelpers::mem_aligned_load( + dst_aligned, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], + pending, + ); + MemBusHelpers::mem_aligned_write( + dst_aligned, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], + pending, + ); + } + + for i in 0..dma.memcpy_count / 8 { + MemBusHelpers::mem_aligned_load( + src_aligned + i as u32 * 8, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + i as usize], + pending, + ); + MemBusHelpers::mem_aligned_write( + dst_aligned + i as u32 * 8, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + i as usize], + pending, + ); + } + + if dma.post_count > 0 { + MemBusHelpers::mem_aligned_load( + dst_aligned, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], + pending, + ); + MemBusHelpers::mem_aligned_load( + dst_aligned, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], + pending, + ); + MemBusHelpers::mem_aligned_write( + dst_aligned, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], + pending, + ); + } + */ + /* + // Information collected during memory trace generation includes the alignated reads: + // - previous read dst & ~0x07 if dst % 8 > 0 + // - previous read (dst + count - 1) & ~0x07 if use post + // - src reads from: src & ~0x07 to (src + count - 1) & ~0x07 + + + let write_addr = dst & ~0x07; + let write_count = (((dst + count - 1) - write_addr - 1) / 8) + 1; + + + + // full aligned src % 8 == 0 && dst % 8 == 0 + // parcialy aligned src % 8 == dst % 8 == 0 + + + // block + let mut read_index = 0; + let mut align_write_addr = 0; + + if offset == 0 { + + } else { + for i in 0..count { + MemBusHelpers::mem_aligned_store( + dst_aligned, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], + pending, + ); + } + } + + let src_offset = dst & 0x07; + let dst_offset = src & 0x07; + + if count <= dst_offset { + if count > 0 { + let dst_aligned = dst & 0xFFFF_FFFF_FFFF_FFF8; + + MemBusHelpers::mem_aligned_load( + dst_aligned, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], + pending, + ); + + MemBusHelpers::mem_aligned_store( + dst_aligned, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], + pending, + ); + } + } else { + if dst_offset > 0 { + self.dma_pre_pos_ops += 1 + } + if dst_offset == src_offset { + self.dma_64_aligned_ops += (count - dst_offset) >> 3 + } else { + self.dma_unaligned_ops += (count - dst_offset) >> 3 + } + + if (count - dst_offset) % 8 > 0 { + self.dma_pre_pos_ops += 1 + } + } + self.dma_ops += 1; + + let dst = data[A]; + let src = data[B]; + + // + // precompiled_mem_load( sel: enabled, main_step: main_step, addr: src_addr * 8, value: [values[0], values[1]]); + // precompiled_mem_load( sel: enabled_second_read, main_step: main_step, addr: src_addr * 8 + 8, value: [values[2], values[3]]); + // precompiled_mem_load( sel: enabled, main_step: main_step, addr: dst_addr * 8, value: [values[4], values[5]]); + // precompiled_mem_store(sel: enabled, main_step: main_step, addr: dst_addr * 8, value: write_value); + + let src_aligned = src & 0xFFFF_FFFF_FFFF_FFF8; + MemBusHelpers::mem_aligned_load( + src_aligned + iparam as u32 * 8, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], + pending, + ); + + // ALIGNED, UNALIGNED + for iparam in 0..PARAMS { + MemBusHelpers::mem_aligned_load( + src + iparam as u32 * 8, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], + pending, + ); + } + + // generate load params + for iparam in 0..READ_PARAMS { + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; + for ichunk in 0..PARAM_CHUNKS { + MemBusHelpers::mem_aligned_load( + param_addr + ichunk as u32 * 8, + step_main, + data[START_READ_PARAMS + iparam * PARAM_CHUNKS + ichunk], + pending, + ); + } + } + + let mut write_data = [0u64; PARAM_CHUNKS]; + if !only_counters { + let a: [u64; 4] = + data[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(); + let b: [u64; 4] = data + [START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] + .try_into() + .unwrap(); + dma(&a, &b, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS], &mut write_data); + } + + // verify write param + let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; + for (ichunk, write_data) in write_data.iter().enumerate().take(PARAM_CHUNKS) { + let param_addr = write_addr + ichunk as u32 * 8; + MemBusHelpers::mem_aligned_write(param_addr, step_main, *write_data, pending); + }*/ +} + +// op_a = step +// op_b = addr_main +// mem_trace: @a, @b, cin, @c, a[0..3], b[0..3], cout, [ c[0..3] ] + +pub fn skip_dma_mem_inputs( + dst: u64, + src: u64, + count: usize, + mem_collectors_info: &[MemCollectorInfo], +) -> bool { + let to_dst = (dst + count as u64 - 1) as u32 & !0x07; + let to_src = (src + count as u64 - 1) as u32 & !0x07; + let mut dst = (dst & !0x07) as u32; + let mut src = (src & !0x07) as u32; + // TODO: + // for mem_collector in mem_collectors_info { + // if !mem_collector.skip_addr_range(from_dst, to_dst) || + // !mem_collector.skip_addr_range(from_src, to_src) { + // return false; + // } + // } + + while dst <= to_dst { + for mem_collector in mem_collectors_info { + if !mem_collector.skip_addr(dst) { + return false; + } + } + dst += 8; + } + while src <= to_src { + for mem_collector in mem_collectors_info { + if !mem_collector.skip_addr(src) { + return false; + } + } + src += 8; + } + true +} diff --git a/precompiles/dma/src/dma_helpers.rs b/precompiles/dma/src/dma_helpers.rs new file mode 100644 index 000000000..597d01ca9 --- /dev/null +++ b/precompiles/dma/src/dma_helpers.rs @@ -0,0 +1,44 @@ +// use static_assertions::const_assert; +// const_assert!(CHUNK_MEM_STEP_BITS <= 24); + +pub struct DmaHelpers {} + +pub struct DmaValues { + pub dst64: u64, + pub src64: u64, + pub src_offset: u64, + pub dst_offset: u64, + pub pre_count: u64, + pub post_count: u64, + pub memcpy_count: u64, + pub src64_inc_by_pre: u64, + pub src_offset_after_pre: u64, +} +impl DmaHelpers { + #[inline(always)] + pub fn precalculate_dma_values(dst: u64, src: u64, count: usize) -> DmaValues { + let dst64 = dst & !0x07; + let src64 = src & !0x07; + let dst_offset = dst & 0x07; + let src_offset = src & 0x07; + + let use_pre = dst_offset > 0; + let pre_count = if use_pre { std::cmp::min(8 - dst_offset, count as u64) } else { 0 }; + let post_count = (count as u64 - pre_count) % 8; + let memcpy_count = count as u64 - pre_count - post_count; + let src64_inc_by_pre = if use_pre && (src_offset + pre_count) >= 8 { 1 } else { 0 }; + let src_offset_after_pre = (src_offset + pre_count) % 8; + + DmaValues { + dst64, + src64, + src_offset, + dst_offset, + pre_count, + post_count, + memcpy_count, + src64_inc_by_pre, + src_offset_after_pre, + } + } +} diff --git a/precompiles/dma/src/dma_input.rs b/precompiles/dma/src/dma_input.rs new file mode 100644 index 000000000..8705d409a --- /dev/null +++ b/precompiles/dma/src/dma_input.rs @@ -0,0 +1,53 @@ +use zisk_common::{ + OperationDmaMemCmpData, OperationDmaMemCpyData, A, B, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP, +}; + +#[derive(Debug)] +pub struct DmaMemCpyInput { + pub src: u64, + pub dst: u64, + pub count: usize, + pub main_step: u64, +} + +#[cfg(feature = "dma_memcmp")] +#[derive(Debug)] +pub struct DmaMemCmpInput { + pub addr1: u64, + pub addr2: u64, + // number of bytes to compare + pub count: usize, + pub main_step: u64, + // number of bytes from beginning that are equal + pub count_eq_bytes: usize, + // results of comparation (p1 - p2) + // p1 == p2 ==> result == 0 + // p1 > p2 ==> result === 1..255 + // p1 < p2 ==> result === 0xFFFF_FFFF_FFFF_FFFF .. 0xFFFF_FFFF_FFFF_FF00 + pub result: u64, +} + +impl DmaMemCpyInput { + pub fn from(values: &OperationDmaMemCpyData) -> Self { + Self { + dst: values[A], + src: values[B], + main_step: values[STEP], + count: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as usize, + } + } +} + +#[cfg(feature = "dma_memcmp")] +impl DmaMemCmpInput { + pub fn from(values: &OperationDmaMemCmpData) -> Self { + Self { + addr1: values[A], + addr2: values[B], + main_step: values[STEP], + count: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as usize, + count_eq_bytes: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] as usize, + result: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2], + } + } +} diff --git a/precompiles/dma/src/add256_instance.rs b/precompiles/dma/src/dma_instance.rs similarity index 70% rename from precompiles/dma/src/add256_instance.rs rename to precompiles/dma/src/dma_instance.rs index 267251539..69a5c32c5 100644 --- a/precompiles/dma/src/add256_instance.rs +++ b/precompiles/dma/src/dma_instance.rs @@ -1,10 +1,10 @@ -//! The `Add256Instance` module defines an instance to perform the witness computation -//! for the Add256 State Machine. +//! The `DmaInstance` module defines an instance to perform the witness computation +//! for the Dma State Machine. //! -//! It manages collected inputs and interacts with the `Add256SM` to compute witnesses for +//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for //! execution plans. -use crate::{Add256Input, Add256SM}; +use crate::{DmaMemCpyInput, DmaSM}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, SetupCtx}; use std::collections::VecDeque; @@ -15,54 +15,54 @@ use zisk_common::{ InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; -use zisk_pil::Add256Trace; +use zisk_pil::DmaTrace; -/// The `Add256Instance` struct represents an instance for the Add256 State Machine. +/// The `DmaInstance` struct represents an instance for the Dma State Machine. /// -/// It encapsulates the `Add256SM` and its associated context, and it processes input data -/// to compute witnesses for the Add256 State Machine. -pub struct Add256Instance { - /// Add256 state machine. - add256_sm: Arc>, +/// It encapsulates the `DmaSM` and its associated context, and it processes input data +/// to compute witnesses for the Dma State Machine. +pub struct DmaInstance { + /// Dma state machine. + dma_sm: Arc>, /// Instance context. ictx: InstanceCtx, } -impl Add256Instance { - /// Creates a new `Add256Instance`. +impl DmaInstance { + /// Creates a new `DmaInstance`. /// /// # Arguments - /// * `add256_sm` - An `Arc`-wrapped reference to the Add256 State Machine. + /// * `dma_sm` - An `Arc`-wrapped reference to the Dma State Machine. /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. /// * `bus_id` - The bus ID associated with this instance. /// /// # Returns - /// A new `Add256Instance` instance initialized with the provided state machine and + /// A new `DmaInstance` instance initialized with the provided state machine and /// context. - pub fn new(add256_sm: Arc>, ictx: InstanceCtx) -> Self { - Self { add256_sm, ictx } + pub fn new(dma_sm: Arc>, ictx: InstanceCtx) -> Self { + Self { dma_sm, ictx } } - pub fn build_add256_collector(&self, chunk_id: ChunkId) -> Add256Collector { + pub fn build_dma_collector(&self, chunk_id: ChunkId) -> DmaCollector { assert_eq!( self.ictx.plan.air_id, - Add256Trace::::AIR_ID, - "Add256Instance: Unsupported air_id: {:?}", + DmaTrace::::AIR_ID, + "DmaInstance: Unsupported air_id: {:?}", self.ictx.plan.air_id ); let meta = self.ictx.plan.meta.as_ref().unwrap(); let collect_info = meta.downcast_ref::>().unwrap(); let (num_ops, collect_skipper) = collect_info[&chunk_id]; - Add256Collector::new(num_ops, collect_skipper) + DmaCollector::new(num_ops, collect_skipper) } } -impl Instance for Add256Instance { - /// Computes the witness for the Add256 execution plan. +impl Instance for DmaInstance { + /// Computes the witness for the Dma execution plan. /// - /// This method leverages the `Add256SM` to generate an `AirInstance` using the collected + /// This method leverages the `DmaSM` to generate an `AirInstance` using the collected /// inputs. /// /// # Arguments @@ -79,10 +79,10 @@ impl Instance for Add256Instance { ) -> Option> { let inputs: Vec<_> = collectors .into_iter() - .map(|(_, collector)| collector.as_any().downcast::().unwrap().inputs) + .map(|(_, collector)| collector.as_any().downcast::().unwrap().inputs) .collect(); - Some(self.add256_sm.compute_witness(&inputs, trace_buffer)) + Some(self.dma_sm.compute_witness(&inputs, trace_buffer)) } /// Retrieves the checkpoint associated with this instance. @@ -104,15 +104,15 @@ impl Instance for Add256Instance { fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { assert_eq!( self.ictx.plan.air_id, - Add256Trace::::AIR_ID, - "Add256Instance: Unsupported air_id: {:?}", + DmaTrace::::AIR_ID, + "DmaInstance: Unsupported air_id: {:?}", self.ictx.plan.air_id ); let meta = self.ictx.plan.meta.as_ref().unwrap(); let collect_info = meta.downcast_ref::>().unwrap(); let (num_ops, collect_skipper) = collect_info[&chunk_id]; - Some(Box::new(Add256Collector::new(num_ops, collect_skipper))) + Some(Box::new(DmaCollector::new(num_ops, collect_skipper))) } fn as_any(&self) -> &dyn std::any::Any { @@ -120,9 +120,9 @@ impl Instance for Add256Instance { } } -pub struct Add256Collector { +pub struct DmaCollector { /// Collected inputs for witness computation. - inputs: Vec, + inputs: Vec, /// The number of operations to collect. num_operations: u64, @@ -131,8 +131,8 @@ pub struct Add256Collector { collect_skipper: CollectSkipper, } -impl Add256Collector { - /// Creates a new `Add256Collector`. +impl DmaCollector { + /// Creates a new `DmaCollector`. /// /// # Arguments /// @@ -151,7 +151,7 @@ impl Add256Collector { } } -impl BusDevice for Add256Collector { +impl BusDevice for DmaCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -177,7 +177,7 @@ impl BusDevice for Add256Collector { return false; } - if data[OP_TYPE] as u32 != ZiskOperationType::BigInt as u32 { + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { return true; } @@ -187,10 +187,10 @@ impl BusDevice for Add256Collector { let data: ExtOperationData = data.try_into().expect("Regular Metrics: Failed to convert data"); - if let ExtOperationData::OperationAdd256Data(data) = data { - self.inputs.push(Add256Input::from(&data)); + if let ExtOperationData::OperationDmaMemCpyData(data) = data { + self.inputs.push(DmaMemCpyInput::from(&data)); } else { - panic!("Expected ExtOperationData::OperationAdd256Data"); + panic!("Expected ExtOperationData::OperationDmaData"); } self.inputs.len() < self.num_operations as usize diff --git a/precompiles/dma/src/dma_manager.rs b/precompiles/dma/src/dma_manager.rs new file mode 100644 index 000000000..ba2deb8ba --- /dev/null +++ b/precompiles/dma/src/dma_manager.rs @@ -0,0 +1,92 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use pil_std_lib::Std; +use zisk_common::{ + BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, + InstanceInfo, PayloadType, Planner, +}; +use zisk_core::ZiskOperationType; +use zisk_pil::DmaTrace; + +use crate::{DmaCounterInputGen, DmaInstance, DmaPlanner, DmaSM}; + +/// The `DmaManager` struct represents the Dma manager, +/// which is responsible for managing the Dma state machine and its table state machine. +#[allow(dead_code)] +pub struct DmaManager { + /// Dma state machine + dma_sm: Arc>, +} + +impl DmaManager { + /// Creates a new instance of `DmaManager`. + /// + /// # Returns + /// An `Arc`-wrapped instance of `DmaManager`. + pub fn new(std: Arc>) -> Arc { + let dma_sm = DmaSM::new(std); + + Arc::new(Self { dma_sm }) + } + + pub fn build_dma_counter(&self) -> DmaCounterInputGen { + DmaCounterInputGen::new(BusDeviceMode::Counter) + } + + pub fn build_dma_input_generator(&self) -> DmaCounterInputGen { + DmaCounterInputGen::new(BusDeviceMode::InputGenerator) + } +} + +impl ComponentBuilder for DmaManager { + /// Builds and returns a new counter for monitoring Dma operations. + /// + /// # Returns + /// A boxed implementation of `RegularCounters` configured for Dma operations. + fn build_counter(&self) -> Option> { + Some(Box::new(DmaCounterInputGen::new(BusDeviceMode::Counter))) + } + + /// Builds a planner to plan Dma-related instances. + /// + /// # Returns + /// A boxed implementation of `RegularPlanner`. + fn build_planner(&self) -> Box { + // Get the number of Dmas that a single Dma instance can handle + let num_availables = self.dma_sm.num_availables; + + Box::new(DmaPlanner::new().add_instance(InstanceInfo::new( + DmaTrace::::AIRGROUP_ID, + DmaTrace::::AIR_ID, + num_availables, + ZiskOperationType::BigInt, + ))) + } + + /// Builds an inputs data collector for Dma operations. + /// + /// # Arguments + /// * `ictx` - The context of the instance, containing the plan and its associated + /// configurations. + /// + /// # Returns + /// A boxed implementation of `BusDeviceInstance` specific to the requested `air_id` instance. + /// + /// # Panics + /// Panics if the provided `air_id` is not supported. + fn build_instance(&self, ictx: InstanceCtx) -> Box> { + match ictx.plan.air_id { + id if id == DmaTrace::::AIR_ID => { + Box::new(DmaInstance::new(self.dma_sm.clone(), ictx)) + } + _ => { + panic!("DmaBuilder::get_instance() Unsupported air_id: {:?}", ictx.plan.air_id) + } + } + } + + fn build_inputs_generator(&self) -> Option>> { + Some(Box::new(DmaCounterInputGen::new(BusDeviceMode::InputGenerator))) + } +} diff --git a/precompiles/dma/src/add256_planner.rs b/precompiles/dma/src/dma_planner.rs similarity index 75% rename from precompiles/dma/src/add256_planner.rs rename to precompiles/dma/src/dma_planner.rs index 080e22067..169cb9bf5 100644 --- a/precompiles/dma/src/add256_planner.rs +++ b/precompiles/dma/src/dma_planner.rs @@ -1,4 +1,4 @@ -//! The `Add256Planner` module defines a planner for generating execution plans specific to +//! The `DmaPlanner` module defines a planner for generating execution plans specific to //! arithmetic operations. //! //! It organizes execution plans for both regular instances and table instances, @@ -6,56 +6,56 @@ use std::any::Any; -use crate::Add256CounterInputGen; +use crate::DmaCounterInputGen; use zisk_common::{ plan, BusDeviceMetrics, ChunkId, InstCount, InstanceInfo, InstanceType, Metrics, Plan, Planner, }; -/// The `Add256Planner` struct organizes execution plans for arithmetic instances and tables. +/// The `DmaPlanner` struct organizes execution plans for arithmetic instances and tables. /// /// It allows adding metadata about instances and tables and generates plans /// based on the provided counters. #[derive(Default)] -pub struct Add256Planner { - /// Add256 instances info to be planned. +pub struct DmaPlanner { + /// Dma instances info to be planned. instances_info: Vec, } -impl Add256Planner { - /// Creates a new `Add256Planner`. +impl DmaPlanner { + /// Creates a new `DmaPlanner`. /// /// # Returns - /// A new `Add256Planner` instance with no preconfigured instances or tables. + /// A new `DmaPlanner` instance with no preconfigured instances or tables. pub fn new() -> Self { Self { instances_info: Vec::new() } } - /// Adds an Add256 instance to the planner. + /// Adds an Dma instance to the planner. /// /// # Arguments - /// * `instance_info` - The `InstanceInfo` describing the add256 instance to be added. + /// * `instance_info` - The `InstanceInfo` describing the dma instance to be added. /// /// # Returns - /// The updated `Add256Planner` instance. + /// The updated `DmaPlanner` instance. pub fn add_instance(mut self, instance_info: InstanceInfo) -> Self { self.instances_info.push(instance_info); self } } -impl Planner for Add256Planner { - /// Generates execution plans for Add256 instances. +impl Planner for DmaPlanner { + /// Generates execution plans for Dma instances. /// /// # Arguments - /// * `counters` - A vector of counters, each associated with a `ChunkId` and `Add256Counter` + /// * `counters` - A vector of counters, each associated with a `ChunkId` and `DmaCounter` /// metrics data. /// /// # Returns /// A vector of `Plan` instances representing execution configurations for the instances /// /// # Panics - /// Panics if any counter cannot be downcasted to an `Add256Counter`. + /// Panics if any counter cannot be downcasted to an `DmaCounter`. fn plan(&self, counters: Vec<(ChunkId, Box)>) -> Vec { // Prepare counts let mut count: Vec> = Vec::with_capacity(self.instances_info.len()); @@ -66,7 +66,7 @@ impl Planner for Add256Planner { counters.iter().for_each(|(chunk_id, counter)| { let reg_counter = - Metrics::as_any(&**counter).downcast_ref::().unwrap(); + Metrics::as_any(&**counter).downcast_ref::().unwrap(); // Iterate over `instances_info` and add `InstCount` objects to the correct vector for (index, instance_info) in self.instances_info.iter().enumerate() { diff --git a/precompiles/dma/src/dma_pre_post.rs b/precompiles/dma/src/dma_pre_post.rs new file mode 100644 index 000000000..80eb1a479 --- /dev/null +++ b/precompiles/dma/src/dma_pre_post.rs @@ -0,0 +1,169 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use rayon::prelude::*; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use zisk_pil::{DmaPrePostTrace, DmaPrePostTraceRow}; + +use super::DmaInput; + +/// The `DmaPrePostSM` struct encapsulates the logic of the DmaPrePost State Machine. +pub struct DmaPrePostSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Number of available dmapreposts in the trace. + pub num_availables: usize, + + /// Range checks ID's + range_id: usize, +} + +impl DmaPrePostSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaPrePostSM` instance. + pub fn new(std: Arc>) -> Arc { + // Compute some useful values + let num_availables = DmaTrace::::NUM_ROWS; + + let range_id = std.get_range_id(0, (1 << 16) - 1, None); + + Arc::new(Self { std, num_availables, range_id }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the DmaPrePost trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_slice( + &self, + input: &DmaPrePostInput, + trace: &mut DmaPrePostTraceRow, + multiplicities: &mut [u32], + ) { + trace.cin = F::from_bool(input.cin != 0); + let mut cout_2 = input.cin as u32; + + for i in 0..4 { + let al = input.a[i] as u32; + let ah = (input.a[i] >> 32) as u32; + + let bl = input.b[i] as u32; + let bh = (input.b[i] >> 32) as u32; + + trace.a[i][0] = F::from_u32(al); + trace.a[i][1] = F::from_u32(ah); + trace.b[i][0] = F::from_u32(bl); + trace.b[i][1] = F::from_u32(bh); + let cl = al as u64 + bl as u64 + cout_2 as u64; + let cout_1 = cl >> 32; + let ch = ah as u64 + bh as u64 + cout_1; + cout_2 = (ch >> 32) as u32; + + let cll = cl as u16; + let clh = (cl >> 16) as u16; + let chl = ch as u16; + let chh = (ch >> 16) as u16; + + trace.c_chunks[i][0] = F::from_u16(cll); + trace.c_chunks[i][1] = F::from_u16(clh); + trace.c_chunks[i][2] = F::from_u16(chl); + trace.c_chunks[i][3] = F::from_u16(chh); + + trace.cout[i][0] = F::from_u8(cout_1 as u8); + trace.cout[i][1] = F::from_u8(cout_2 as u8); + + multiplicities[cll as usize] += 1; + multiplicities[clh as usize] += 1; + multiplicities[chl as usize] += 1; + multiplicities[chh as usize] += 1; + } + trace.addr_params = F::from_u32(input.addr_main); + trace.addr_a = F::from_u32(input.addr_a); + trace.addr_b = F::from_u32(input.addr_b); + trace.addr_c = F::from_u32(input.addr_c); + trace.step = F::from_u64(input.step_main); + trace.sel = F::ONE; + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> AirInstance { + let mut trace = DmaTrace::new_from_vec(trace_buffer); + + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); + assert!(total_inputs <= num_rows); + + tracing::info!( + "··· Creating DmaPrePost instance [{} / {} rows filled {:.2}%]", + total_inputs, + num_rows, + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMAPREPOST_TRACE); + + // Split the dmaprepost_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.row_slice_mut(); + + // Determinar tamaño óptimo de chunks + let num_threads = rayon::current_num_threads(); + let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); + + // Procesar en chunks para compartir arrays locales de multiplicities + let local_multiplicities_vec: Vec> = flat_inputs + .par_chunks(chunk_size) + .zip(trace_rows.par_chunks_mut(chunk_size)) + .map(|(input_chunk, trace_chunk)| { + // Array local compartido por este chunk + let mut local_multiplicities = vec![0u32; 1 << 16]; + + // Procesar todos los inputs del chunk + for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { + self.process_slice(input, trace_row, &mut local_multiplicities); + } + + local_multiplicities + }) + .collect(); + + // Sumar todos los arrays locales en uno global + let mut global_multiplicities = vec![0u32; 1 << 16]; + for local_multiplicities in local_multiplicities_vec { + for (i, count) in local_multiplicities.iter().enumerate() { + global_multiplicities[i] += count; + } + } + + // Enviar el resultado final al std + self.std.range_checks(self.range_id, global_multiplicities); + + timer_stop_and_log_trace!(DMAPREPOST_TRACE); + + trace.row_slice_mut()[total_inputs..num_rows] + .par_iter_mut() + .for_each(|slot| *slot = DmaPrePostTraceRow:: { ..Default::default() }); + + AirInstance::::new_from_trace(FromTrace::new(&mut trace)) + } +} diff --git a/precompiles/dma/src/dma_pre_post_input.rs b/precompiles/dma/src/dma_pre_post_input.rs new file mode 100644 index 000000000..df35476de --- /dev/null +++ b/precompiles/dma/src/dma_pre_post_input.rs @@ -0,0 +1,25 @@ +use crate::dma_constants::*; +use zisk_common::OperationDmaData; +use zisk_common::{B, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; + +#[derive(Debug)] +pub struct DmaPrePostInput { + pub src: u32, + pub dst: u32, + pub step: u64, + pub count: u8, + pub src_values: [u64; 2], + pub dst_pre_value: u64, +} + +impl DmaPrePostInput { + pub fn from(data: &OperationDmaData, data_ext: &[u64]) -> Self { + Self { + dst: data[A] as u32, + src: data[B] as u32, + step: data[STEP], + src_values: [data_ext[2], data_ext[3]], + dst_pre_value: data_ext[0], + } + } +} diff --git a/precompiles/dma/src/add256.rs b/precompiles/dma/src/dma_unaligned.rs similarity index 82% rename from precompiles/dma/src/add256.rs rename to precompiles/dma/src/dma_unaligned.rs index c65e4a7b2..16449e999 100644 --- a/precompiles/dma/src/add256.rs +++ b/precompiles/dma/src/dma_unaligned.rs @@ -6,30 +6,30 @@ use rayon::prelude::*; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; -use zisk_pil::{Add256Trace, Add256TraceRow}; +use zisk_pil::{DmaTrace, DmaTraceRow}; -use super::Add256Input; +use super::DmaInput; -/// The `Add256SM` struct encapsulates the logic of the Add256 State Machine. -pub struct Add256SM { +/// The `DmaSM` struct encapsulates the logic of the DmaUnaligned State Machine. +pub struct DmaSM { /// Reference to the PIL2 standard library. pub std: Arc>, - /// Number of available add256s in the trace. + /// Number of available dmaunaligneds in the trace. pub num_availables: usize, /// Range checks ID's range_id: usize, } -impl Add256SM { - /// Creates a new Add256 State Machine instance. +impl DmaSM { + /// Creates a new Dma State Machine instance. /// /// # Returns - /// A new `Add256SM` instance. + /// A new `DmaSM` instance. pub fn new(std: Arc>) -> Arc { // Compute some useful values - let num_availables = Add256Trace::::NUM_ROWS; + let num_availables = DmaTrace::::NUM_ROWS; let range_id = std.get_range_id(0, (1 << 16) - 1, None); @@ -39,13 +39,13 @@ impl Add256SM { /// Processes a slice of operation data, updating the trace. /// /// # Arguments - /// * `trace` - A mutable reference to the Add256 trace. + /// * `trace` - A mutable reference to the DmaUnaligned trace. /// * `input` - The operation data to process. #[inline(always)] pub fn process_slice( &self, - input: &Add256Input, - trace: &mut Add256TraceRow, + input: &DmaInput, + trace: &mut DmaTraceRow, multiplicities: &mut [u32], ) { trace.cin = F::from_bool(input.cin != 0); @@ -103,10 +103,10 @@ impl Add256SM { /// An `AirInstance` containing the computed witness data. pub fn compute_witness( &self, - inputs: &[Vec], + inputs: &[Vec], trace_buffer: Vec, ) -> AirInstance { - let mut trace = Add256Trace::new_from_vec(trace_buffer); + let mut trace = DmaTrace::new_from_vec(trace_buffer); let num_rows = trace.num_rows(); @@ -114,15 +114,15 @@ impl Add256SM { assert!(total_inputs <= num_rows); tracing::info!( - "··· Creating Add256 instance [{} / {} rows filled {:.2}%]", + "··· Creating DmaUnaligned instance [{} / {} rows filled {:.2}%]", total_inputs, num_rows, total_inputs as f64 / num_rows as f64 * 100.0 ); - timer_start_trace!(ADD256_TRACE); + timer_start_trace!(DMAUNALIGNED_TRACE); - // Split the add256_trace.buffer into slices matching each inner vector’s length. + // Split the dmaunaligned_trace.buffer into slices matching each inner vector’s length. let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); let trace_rows = trace.row_slice_mut(); @@ -158,11 +158,11 @@ impl Add256SM { // Enviar el resultado final al std self.std.range_checks(self.range_id, global_multiplicities); - timer_stop_and_log_trace!(ADD256_TRACE); + timer_stop_and_log_trace!(DMAUNALIGNED_TRACE); trace.row_slice_mut()[total_inputs..num_rows] .par_iter_mut() - .for_each(|slot| *slot = Add256TraceRow:: { ..Default::default() }); + .for_each(|slot| *slot = DmaUnalignedTraceRow:: { ..Default::default() }); AirInstance::::new_from_trace(FromTrace::new(&mut trace)) } diff --git a/precompiles/dma/src/dma_unaligned_input.rs b/precompiles/dma/src/dma_unaligned_input.rs new file mode 100644 index 000000000..534b2c88a --- /dev/null +++ b/precompiles/dma/src/dma_unaligned_input.rs @@ -0,0 +1,32 @@ +use crate::dma_constants::*; +use zisk_common::OperationDmaData; +use zisk_common::{B, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; + +#[derive(Debug)] +pub struct MemCpyInput { + pub step_main: u64, + pub addr_src: u32, + pub addr_dst: u32, + pub a, + pub a: [u64; 4], + pub b: [u64; 4], + pub count: u32, +} + +impl MemCpyInput { + pub fn from(values: &OperationDmaData) -> Self { + Self { + step_main: values[STEP], + addr_main: values[B] as u32, + addr_a: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as u32, + addr_b: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] as u32, + addr_c: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS + DIRECT_READ_PARAMS] + as u32, + cin: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS], + a: values[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(), + b: values[START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] + .try_into() + .unwrap(), + } + } +} diff --git a/precompiles/dma/src/lib.rs b/precompiles/dma/src/lib.rs index b1734294e..e01f77fac 100644 --- a/precompiles/dma/src/lib.rs +++ b/precompiles/dma/src/lib.rs @@ -1,17 +1,23 @@ -mod add256; -mod add256_bus_device; -mod add256_constants; -mod add256_gen_mem_inputs; -mod add256_input; -mod add256_instance; -mod add256_manager; -mod add256_planner; +mod dma; +mod dma_bus_device; +mod dma_constants; +mod dma_gen_mem_inputs; +mod dma_input; +mod dma_instance; +// mod dma_manager; +mod dma_helpers; +// mod dma_planner; -pub use add256::*; -pub use add256_bus_device::*; -pub use add256_constants::*; -pub use add256_gen_mem_inputs::*; -pub use add256_input::*; -pub use add256_instance::*; -pub use add256_manager::*; -pub use add256_planner::*; +pub use dma::*; +// pub use dma_64_aligned::*; +// pub use dma_64_aligned_instance::*; +pub use dma_bus_device::*; +pub use dma_constants::*; +pub use dma_gen_mem_inputs::*; +pub use dma_input::*; +pub use dma_instance::*; +// pub use dma_pre_post::*; +// pub use dma_pre_post_instance::*; +// pub use dma_manager::*; +// pub use dma_planner::*; +pub use dma_helpers::*; diff --git a/state-machines/main/pil/main.pil b/state-machines/main/pil/main.pil index 86e3b13e8..728653c7c 100644 --- a/state-machines/main/pil/main.pil +++ b/state-machines/main/pil/main.pil @@ -1,6 +1,7 @@ require "std_lookup.pil" require "std_permutation.pil" require "std_direct.pil" +require "registers.pil" const int BOOT_ADDR = 0x1000; const int END_PC_ADDR = 0x1004; diff --git a/state-machines/main/pil/registers.pil b/state-machines/main/pil/registers.pil new file mode 100644 index 000000000..6488dea12 --- /dev/null +++ b/state-machines/main/pil/registers.pil @@ -0,0 +1,67 @@ +const int REG_ZE = 0; +const int REG_RA = 1; +const int REG_SP = 2; +const int REG_GP = 3; +const int REG_TP = 4; +const int REG_T0 = 5; +const int REG_T1 = 6; +const int REG_T2 = 7; +const int REG_S0 = 8; +const int REG_S1 = 9; +const int REG_A0 = 10; +const int REG_A1 = 11; +const int REG_A2 = 12; +const int REG_A3 = 13; +const int REG_A4 = 14; +const int REG_A5 = 15; +const int REG_A6 = 16; +const int REG_A7 = 17; +const int REG_S2 = 18; +const int REG_S3 = 19; +const int REG_S4 = 20; +const int REG_S5 = 21; +const int REG_S6 = 22; +const int REG_S7 = 23; +const int REG_S8 = 24; +const int REG_S9 = 25; +const int REG_S10 = 26; +const int REG_S11 = 27; +const int REG_T3 = 28; +const int REG_T4 = 29; +const int REG_T5 = 30; +const int REG_T6 = 31; + +const int REG_FP = 8; + +const int REG_X0 = 0; +const int REG_X1 = 1; +const int REG_X2 = 2; +const int REG_X3 = 3; +const int REG_X4 = 4; +const int REG_X5 = 5; +const int REG_X6 = 6; +const int REG_X7 = 7; +const int REG_X8 = 8; +const int REG_X9 = 9; +const int REG_X10 = 10; +const int REG_X11 = 11; +const int REG_X12 = 12; +const int REG_X13 = 13; +const int REG_X14 = 14; +const int REG_X15 = 15; +const int REG_X16 = 16; +const int REG_X17 = 17; +const int REG_X18 = 18; +const int REG_X19 = 19; +const int REG_X20 = 20; +const int REG_X21 = 21; +const int REG_X22 = 22; +const int REG_X23 = 23; +const int REG_X24 = 24; +const int REG_X25 = 25; +const int REG_X26 = 26; +const int REG_X27 = 27; +const int REG_X28 = 28; +const int REG_X29 = 29; +const int REG_X30 = 30; +const int REG_X31 = 31; \ No newline at end of file diff --git a/state-machines/mem/pil/dual_byte.pil b/state-machines/mem/pil/dual_byte.pil index c6a05c890..3d4d13717 100644 --- a/state-machines/mem/pil/dual_byte.pil +++ b/state-machines/mem/pil/dual_byte.pil @@ -10,4 +10,9 @@ airtemplate DualByte(int N = 2**16) { col witness multiplicity; lookup_proves(DUAL_BYTE_TABLE_ID, mul: multiplicity, expressions: [BYTE_A, BYTE_B]); -} \ No newline at end of file +} + +function range_dual_byte(expr byte_a, expr byte_b, expr sel = 1) { + lookup_assumes(DUAL_BYTE_TABLE_ID, expressions: [byte_a, byte_b], sel: sel); +} + diff --git a/state-machines/mem/pil/mem.pil b/state-machines/mem/pil/mem.pil index 5e32f277e..75daa67bb 100644 --- a/state-machines/mem/pil/mem.pil +++ b/state-machines/mem/pil/mem.pil @@ -478,6 +478,10 @@ function precompiled_mem_load(int id = MEMORY_ID, expr addr, expr main_step, exp mem_assumes(id, MEMORY_LOAD_OP, addr, main_step_to_precompiled_mem_step(main_step), 8, value, sel, name); } +function precompiled_mem_load_padding(int id = MEMORY_ID, expr padding = 0, int name = PIOP_NAME_DEFAULT) { + mem_proves(id, MEMORY_LOAD_OP, 0, main_step_to_precompiled_mem_step(0), [0, 0], padding, name); +} + function precompiled_mem_store(int id = MEMORY_ID, expr addr, expr main_step, expr value[], expr sel = 1, int name = PIOP_NAME_DEFAULT ) { mem_assumes(id, MEMORY_STORE_OP, addr, main_step_to_precompiled_mem_step(main_step, 1), 8, value, sel, name); } @@ -488,6 +492,12 @@ function reg_pre_load(int id = MEMORY_ID, expr addr, expr prev_mem_step, expr va mem_proves(id, MEMORY_REG_OP, addr, prev_mem_step, value, sel, name: name); } +function precompiled_reg_load(int id = MEMORY_ID, expr reg, expr prev_mem_step, expr main_step, expr value[], expr sel = 1, int name = PIOP_NAME_DEFAULT) { + mem_proves(id, MEMORY_REG_OP, reg, prev_mem_step, value, sel, name: name); + range_check(expression: main_step_to_precompiled_mem_step(main_step) - prev_mem_step, min: 0, max: MAX_RANGE); + mem_assumes(id, MEMORY_REG_OP, reg, main_step_to_precompiled_mem_step(main_step), 8, value, sel, name: name); +} + function reg_pre_store(int id = MEMORY_ID, expr addr, expr prev_mem_step, expr value[], expr sel = 1, int name = PIOP_NAME_DEFAULT) { mem_proves(id, MEMORY_REG_OP, addr, prev_mem_step, value, sel, name); } diff --git a/ziskos/entrypoint/src/memcpy_test.rs b/ziskos/entrypoint/src/memcpy_test.rs new file mode 100644 index 000000000..074a7c635 --- /dev/null +++ b/ziskos/entrypoint/src/memcpy_test.rs @@ -0,0 +1,489 @@ +#[cfg(test)] +mod memcpy_tests { + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + use super::ziskos::memcpy; + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + fn memcpy(dst: *mut u8, src: *const u8, len: usize) -> *mut u8 { + unsafe { + std::ptr::copy(src, dst, len); + } + dst + } + use std::alloc::{alloc, dealloc, Layout}; + + // Helper function to create aligned memory + unsafe fn alloc_aligned(size: usize, align: usize) -> *mut u8 { + let layout = Layout::from_size_align(size + align, align).unwrap(); + let ptr = alloc(layout); + if ptr.is_null() { + panic!("Failed to allocate memory"); + } + // Align the pointer + let aligned = (ptr as usize + align - 1) & !(align - 1); + aligned as *mut u8 + } + + // Helper function to deallocate aligned memory + unsafe fn dealloc_aligned(ptr: *mut u8, size: usize, align: usize) { + let layout = Layout::from_size_align(size + align, align).unwrap(); + // We need to get back to the original pointer, but for simplicity in tests, + // we'll use a different approach + dealloc(ptr, layout); + } + + #[test] + fn test_memcpy_zero_length() { + unsafe { + let src = [1u8, 2, 3, 4]; + let mut dst = [0u8; 4]; + + let result = memcpy(dst.as_mut_ptr(), src.as_ptr(), 0); + + assert_eq!(result, dst.as_mut_ptr()); + assert_eq!(dst, [0, 0, 0, 0]); // Should remain unchanged + } + } + + #[test] + fn test_memcpy_single_byte() { + unsafe { + let src = [0x42u8]; + let mut dst = [0u8; 1]; + + memcpy(dst.as_mut_ptr(), src.as_ptr(), 1); + + assert_eq!(dst[0], 0x42); + } + } + + #[test] + fn test_memcpy_aligned_8_small() { + unsafe { + // Test 8-byte aligned pointers with small copy (< 32 bytes) + let src = alloc_aligned(64, 8); + let dst = alloc_aligned(64, 8); + + // Initialize source data + for i in 0..16 { + *src.add(i) = (i + 1) as u8; + } + + memcpy(dst, src, 16); + + // Verify copy + for i in 0..16 { + assert_eq!(*dst.add(i), (i + 1) as u8, "Mismatch at byte {}", i); + } + + dealloc_aligned(src, 64, 8); + dealloc_aligned(dst, 64, 8); + } + } + + #[test] + fn test_memcpy_aligned_8_large() { + unsafe { + // Test 8-byte aligned pointers with large copy (> 32 bytes) + let src = alloc_aligned(128, 8); + let dst = alloc_aligned(128, 8); + + // Initialize source data + for i in 0..64 { + *src.add(i) = (i % 256) as u8; + } + + memcpy(dst, src, 64); + + // Verify copy + for i in 0..64 { + assert_eq!(*dst.add(i), (i % 256) as u8, "Mismatch at byte {}", i); + } + + dealloc_aligned(src, 128, 8); + dealloc_aligned(dst, 128, 8); + } + } + + #[test] + fn test_memcpy_src_unaligned() { + unsafe { + // Test unaligned source pointer + let src_base = alloc_aligned(64, 8); + let dst = alloc_aligned(64, 8); + let src = src_base.add(3); // Unaligned by 3 bytes + + // Initialize source data + for i in 0..20 { + *src.add(i) = (i + 0x10) as u8; + } + + memcpy(dst, src, 20); + + // Verify copy + for i in 0..20 { + assert_eq!(*dst.add(i), (i + 0x10) as u8, "Mismatch at byte {}", i); + } + + dealloc_aligned(src_base, 64, 8); + dealloc_aligned(dst, 64, 8); + } + } + + #[test] + fn test_memcpy_dst_unaligned() { + unsafe { + // Test unaligned destination pointer + let src = alloc_aligned(64, 8); + let dst_base = alloc_aligned(64, 8); + let dst = dst_base.add(5); // Unaligned by 5 bytes + + // Initialize source data + for i in 0..20 { + *src.add(i) = (i + 0x20) as u8; + } + + memcpy(dst, src, 20); + + // Verify copy + for i in 0..20 { + assert_eq!(*dst.add(i), (i + 0x20) as u8, "Mismatch at byte {}", i); + } + + dealloc_aligned(src, 64, 8); + dealloc_aligned(dst_base, 64, 8); + } + } + + #[test] + fn test_memcpy_both_unaligned() { + unsafe { + // Test both pointers unaligned + let src_base = alloc_aligned(64, 8); + let dst_base = alloc_aligned(64, 8); + let src = src_base.add(2); // Unaligned by 2 bytes + let dst = dst_base.add(6); // Unaligned by 6 bytes + + // Initialize source data + for i in 0..25 { + *src.add(i) = (i + 0x30) as u8; + } + + memcpy(dst, src, 25); + + // Verify copy + for i in 0..25 { + assert_eq!(*dst.add(i), (i + 0x30) as u8, "Mismatch at byte {}", i); + } + + dealloc_aligned(src_base, 64, 8); + dealloc_aligned(dst_base, 64, 8); + } + } + + #[test] + fn test_memcpy_edge_sizes() { + unsafe { + let sizes = [1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65]; + + for &size in &sizes { + let src = alloc_aligned(128, 8); + let dst = alloc_aligned(128, 8); + + // Initialize source data + for i in 0..size { + *src.add(i) = ((i * 3 + 7) % 256) as u8; + } + + memcpy(dst, src, size); + + // Verify copy + for i in 0..size { + assert_eq!( + *dst.add(i), + ((i * 3 + 7) % 256) as u8, + "Size {} mismatch at byte {}", + size, + i + ); + } + + dealloc_aligned(src, 128, 8); + dealloc_aligned(dst, 128, 8); + } + } + } + + #[test] + fn test_memcpy_overlapping_forward() { + unsafe { + // Test overlapping memory (src before dst) - should work + let mut buffer = [0u8; 20]; + + // Initialize data + for i in 0..10 { + buffer[i] = (i + 0x40) as u8; + } + + // Copy from buffer[0..10] to buffer[5..15] + memcpy(buffer.as_mut_ptr().add(5), buffer.as_ptr(), 10); + + // Verify - first 5 bytes unchanged, next 10 are the copy + for i in 0..5 { + assert_eq!(buffer[i], (i + 0x40) as u8); + } + for i in 5..15 { + assert_eq!(buffer[i], (i - 5 + 0x40) as u8); + } + } + } + + #[test] + fn test_memcpy_return_value() { + unsafe { + let src = [1u8, 2, 3, 4]; + let mut dst = [0u8; 4]; + let dst_ptr = dst.as_mut_ptr(); + + let result = memcpy(dst_ptr, src.as_ptr(), 4); + + assert_eq!(result, dst_ptr, "Return value should be original dst pointer"); + } + } + + #[test] + fn test_memcpy_large_unaligned() { + unsafe { + // Test large copy with unaligned pointers + let src_base = alloc_aligned(256, 8); + let dst_base = alloc_aligned(256, 8); + let src = src_base.add(3); // Unaligned + let dst = dst_base.add(1); // Unaligned + + // Initialize source data with pattern + for i in 0..200 { + *src.add(i) = ((i * 7 + 13) % 256) as u8; + } + + memcpy(dst, src, 200); + + // Verify copy + for i in 0..200 { + assert_eq!( + *dst.add(i), + ((i * 7 + 13) % 256) as u8, + "Large unaligned mismatch at byte {}", + i + ); + } + + dealloc_aligned(src_base, 256, 8); + dealloc_aligned(dst_base, 256, 8); + } + } + + #[test] + fn test_memcpy_debug_print() { + unsafe { + let src = [0x12u8, 0x34, 0x56, 0x78]; + let mut dst = [0u8; 4]; + + println!("Before memcpy:"); + println!(" src: {:p} = {:02x?}", src.as_ptr(), src); + println!(" dst: {:p} = {:02x?}", dst.as_ptr(), dst); + + memcpy(dst.as_mut_ptr(), src.as_ptr(), 4); + + println!("After memcpy:"); + println!(" dst: {:p} = {:02x?}", dst.as_ptr(), dst); + + assert_eq!(dst, src); + } + } + + #[test] + fn test_pointer_printing_formats() { + unsafe { + let data = [0xDEu8, 0xAD, 0xBE, 0xEF]; + let ptr = data.as_ptr(); + + println!("\nDifferent ways to print pointers:"); + println!("Standard format: {:p}", ptr); + println!("Hex lowercase: 0x{:x}", ptr as usize); + println!("Hex UPPERCASE: 0x{:X}", ptr as usize); + println!("With padding: 0x{:016x}", ptr as usize); + println!("Auto-prefixed: {:#x}", ptr as usize); + println!("Debug format: {:?}", ptr); + + // También mostrar como imprimir la data + println!("\nData at pointer:"); + println!("Hex bytes: {:02x?}", data); + println!("Hex UPPER bytes: {:02X?}", data); + println!("Pretty debug: {:#02x?}", data); + } + } + + #[test] + fn test_memcpy_large_aligned_568699() { + unsafe { + const SIZE: usize = 568699; + + // Allocate aligned memory for both source and destination + let src = alloc_aligned(SIZE + 64, 8); + let dst = alloc_aligned(SIZE + 64, 8); + + println!("\nTest memcpy with aligned pointers and size: {}", SIZE); + println!("Source pointer: {:p} (0x{:016x})", src, src as usize); + println!("Destination pointer: {:p} (0x{:016x})", dst, dst as usize); + println!("Alignment check src: {} (should be 0)", (src as usize) & 7); + println!("Alignment check dst: {} (should be 0)", (dst as usize) & 7); + + // Initialize source data with a predictable pattern + for i in 0..SIZE { + *src.add(i) = ((i * 73 + 127) % 256) as u8; + } + + // Perform the copy + let start = std::time::Instant::now(); + let result = memcpy(dst, src, SIZE); + let elapsed = start.elapsed(); + + println!("Copy completed in: {:?}", elapsed); + println!( + "Throughput: {:.2} MB/s", + (SIZE as f64) / (1024.0 * 1024.0) / elapsed.as_secs_f64() + ); + + // Verify the return value + assert_eq!(result, dst, "Return value should be original dst pointer"); + + // Verify the copy by checking every byte + let mut mismatches = 0; + for i in 0..SIZE { + let expected = ((i * 73 + 127) % 256) as u8; + let actual = *dst.add(i); + if actual != expected { + if mismatches < 10 { + // Only print first 10 mismatches + println!( + "Mismatch at byte {}: expected 0x{:02x}, got 0x{:02x}", + i, expected, actual + ); + } + mismatches += 1; + } + } + + if mismatches > 0 { + println!("Total mismatches: {}", mismatches); + panic!("Copy verification failed with {} mismatches", mismatches); + } + + println!("✓ All {} bytes copied correctly", SIZE); + + // Test some specific boundary checks + println!("\nBoundary checks:"); + println!("First byte: src[0]=0x{:02x}, dst[0]=0x{:02x}", *src, *dst); + let last_idx = SIZE - 1; + println!( + "Last byte: src[{}]=0x{:02x}, dst[{}]=0x{:02x}", + last_idx, + *src.add(last_idx), + last_idx, + *dst.add(last_idx) + ); + + // Check bytes at key positions (32-byte boundaries, etc.) + let check_positions = [31, 32, 63, 64, 127, 128, 255, 256, 511, 512, 1023, 1024]; + for &pos in &check_positions { + if pos < SIZE { + let expected = ((pos * 73 + 127) % 256) as u8; + let actual = *dst.add(pos); + println!( + "Position {}: expected=0x{:02x}, actual=0x{:02x} {}", + pos, + expected, + actual, + if expected == actual { "✓" } else { "✗" } + ); + assert_eq!(actual, expected, "Mismatch at position {}", pos); + } + } + + dealloc_aligned(src, SIZE + 64, 8); + dealloc_aligned(dst, SIZE + 64, 8); + } + } + + #[test] + fn test_memcpy_various_large_sizes() { + // Test various large sizes to ensure robustness + let test_sizes = [ + 568699, // Original request + 568700, // Just one more + 568698, // Just one less + 1048576, // 1 MB + 524288, // 512 KB + 131072, // 128 KB + 65536, // 64 KB + 32768, // 32 KB + 16384, // 16 KB + ]; + + for &size in &test_sizes { + unsafe { + println!("\nTesting size: {} bytes", size); + + let src = alloc_aligned(size + 64, 8); + let dst = alloc_aligned(size + 64, 8); + + // Simple pattern for faster initialization + for i in 0..size { + *src.add(i) = (i % 256) as u8; + } + + let start = std::time::Instant::now(); + memcpy(dst, src, size); + let elapsed = start.elapsed(); + + println!( + " Time: {:?}, Throughput: {:.2} MB/s", + elapsed, + (size as f64) / (1024.0 * 1024.0) / elapsed.as_secs_f64() + ); + + // Quick verification - check first, last, and some middle bytes + assert_eq!(*dst, 0, "First byte mismatch for size {}", size); + if size > 1 { + let last_idx = size - 1; + let expected_last = (last_idx % 256) as u8; + assert_eq!( + *dst.add(last_idx), + expected_last, + "Last byte mismatch for size {}", + size + ); + } + + // Check a few strategic positions + let check_positions = [size / 4, size / 2, 3 * size / 4]; + for &pos in &check_positions { + if pos < size { + let expected = (pos % 256) as u8; + assert_eq!( + *dst.add(pos), + expected, + "Mismatch at position {} for size {}", + pos, + size + ); + } + } + + dealloc_aligned(src, size + 64, 8); + dealloc_aligned(dst, size + 64, 8); + } + } + + println!("\n✓ All large size tests passed!"); + } +} From ae9f090bc6643afa11f4082af7ea419f184e83be Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:10:37 +0000 Subject: [PATCH 049/782] move HintsSink into HintsProcessor --- executor/src/executor.rs | 12 ++++---- hints/src/hints_processor.rs | 55 +++++++++++++++++++++++++----------- hints/src/hints_stream.rs | 51 ++++++++------------------------- hints/src/lib.rs | 2 +- 4 files changed, 57 insertions(+), 63 deletions(-) diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 594ca960c..c7a952b5f 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -80,7 +80,7 @@ enum MinimalTraceExecutionMode { AsmWithCounter, } -type HintsStreamShmem = HintsStream; +pub type StreamHintsShmem = HintsStream>; /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. @@ -89,7 +89,7 @@ pub struct ZiskExecutor { stdin: Mutex, /// Pipeline for handling precompile hints. - hints_stream: Mutex, + hints_stream: Mutex, /// ZisK ROM, a binary file containing the ZisK program to be executed. pub zisk_rom: Arc, @@ -230,13 +230,13 @@ impl ZiskExecutor { .collect::>(); // Create hints pipeline with null hints stream initially. - let hints_processor = - PrecompileHintsProcessor::new().expect("Failed to create PrecompileHintsProcessor"); - let hints_shmem = HintsShmem::new(hints_shmem_control_names, hints_shmem_names, unlock_mapped_memory); - let hints_stream = Mutex::new(HintsStream::new(hints_processor, hints_shmem)); + let hints_processor = PrecompileHintsProcessor::new(hints_shmem) + .expect("Failed to create PrecompileHintsProcessor"); + + let hints_stream = Mutex::new(HintsStream::new(hints_processor)); Self { stdin: Mutex::new(ZiskStdin::null()), diff --git a/hints/src/hints_processor.rs b/hints/src/hints_processor.rs index d324b4112..f3f9094fb 100644 --- a/hints/src/hints_processor.rs +++ b/hints/src/hints_processor.rs @@ -53,7 +53,7 @@ use std::sync::{Arc, Condvar, Mutex}; use tracing::debug; use ziskos::syscalls::SyscallPoint256; -use crate::{secp256k1_ecdsa_verify, HintsProcessor}; +use crate::{secp256k1_ecdsa_verify, HintsProcessor, HintsSink}; // TODO! COnvert Control Code to an enum and HINT TYPE to an enum as well @@ -181,7 +181,7 @@ impl HintProcessorState { /// This struct provides methods to parse and process a stream of concatenated /// hints, using a dedicated Rayon thread pool for parallel processing while /// preserving the original order of results. -pub struct PrecompileHintsProcessor { +pub struct PrecompileHintsProcessor { /// The thread pool used for parallel hint processing. pool: ThreadPool, @@ -190,21 +190,24 @@ pub struct PrecompileHintsProcessor { /// Optional statistics collected during hint processing. stats: [AtomicUsize; NUM_HINT_TYPES as usize], + + /// The hints sink used to submit processed hints. + hints_sink: Arc, } -impl PrecompileHintsProcessor { +impl PrecompileHintsProcessor { const DEFAULT_NUM_THREADS: usize = 32; /// Creates a new processor with the default number of threads. /// - /// The default is the number of available CPU cores. + /// The default is 32 threads. /// /// # Returns /// /// * `Ok(PrecompileHintsProcessor)` - The configured processor /// * `Err` - If the thread pool fails to initialize - pub fn new() -> Result { - Self::with_num_threads(Self::DEFAULT_NUM_THREADS) + pub fn new(hints_sink: HS) -> Result { + Self::with_num_threads(Self::DEFAULT_NUM_THREADS, hints_sink) } /// Creates a new processor with the specified number of threads. @@ -212,18 +215,24 @@ impl PrecompileHintsProcessor { /// # Arguments /// /// * `num_threads` - The number of worker threads in the pool + /// * `hints_sink` - The sink used to submit processed hints /// /// # Returns /// /// * `Ok(PrecompileHintsProcessor)` - The configured processor /// * `Err` - If the thread pool fails to initialize - pub fn with_num_threads(num_threads: usize) -> Result { + pub fn with_num_threads(num_threads: usize, hints_sink: HS) -> Result { let pool = ThreadPoolBuilder::new() .num_threads(num_threads) .build() .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; - Ok(Self { pool, state: Arc::new(HintProcessorState::new()), stats: Default::default() }) + Ok(Self { + pool, + state: Arc::new(HintProcessorState::new()), + stats: Default::default(), + hints_sink: Arc::new(hints_sink), + }) } /// Processes hints in parallel with non-blocking, ordered output. @@ -246,7 +255,7 @@ impl PrecompileHintsProcessor { /// /// * `Ok((Vec, bool))` - Tuple of (processed data, has_ctrl_end) where has_ctrl_end is true if CTRL_END was encountered /// * `Err` - If a previous error occurred or hints are malformed - pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result<(Vec, bool)> { + pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { let mut processed = Vec::new(); let mut has_ctrl_end = false; @@ -438,7 +447,11 @@ impl PrecompileHintsProcessor { debug!("Hint type {}: {}", i, count.load(Ordering::Relaxed)); } - Ok((processed, has_ctrl_end)) + if !processed.is_empty() { + self.hints_sink.submit(processed)?; + } + + Ok(has_ctrl_end) } /// Waits for all pending hints to be processed and drained. @@ -544,8 +557,8 @@ impl PrecompileHintsProcessor { } } -impl HintsProcessor for PrecompileHintsProcessor { - fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result<(Vec, bool)> { +impl HintsProcessor for PrecompileHintsProcessor { + fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { self.process_hints(hints, first_batch) } } @@ -554,6 +567,14 @@ impl HintsProcessor for PrecompileHintsProcessor { mod tests { use super::*; + struct NullHints; + + impl HintsSink for NullHints { + fn submit(&self, _processed: Vec) -> Result<()> { + Ok(()) + } + } + fn make_header(hint_type: u32, length: u32) -> u64 { ((hint_type as u64) << 32) | (length as u64) } @@ -562,8 +583,8 @@ mod tests { make_header(ctrl, length) } - fn processor() -> PrecompileHintsProcessor { - PrecompileHintsProcessor::with_num_threads(2).unwrap() + fn processor() -> PrecompileHintsProcessor { + PrecompileHintsProcessor::with_num_threads(2, NullHints).unwrap() } // Positive tests @@ -772,7 +793,7 @@ mod tests { fn test_stress_throughput() { use std::time::Instant; - let p = PrecompileHintsProcessor::with_num_threads(32).unwrap(); + let p = PrecompileHintsProcessor::with_num_threads(32, NullHints).unwrap(); // Generate a large batch of hints const NUM_HINTS: usize = 100_000; @@ -805,7 +826,7 @@ mod tests { fn test_stress_concurrent_batches() { use std::time::Instant; - let p = PrecompileHintsProcessor::with_num_threads(32).unwrap(); + let p = PrecompileHintsProcessor::with_num_threads(32, NullHints).unwrap(); const NUM_BATCHES: usize = 1_000; const HINTS_PER_BATCH: usize = 100; @@ -844,7 +865,7 @@ mod tests { fn test_stress_with_resets() { use std::time::Instant; - let p = PrecompileHintsProcessor::with_num_threads(32).unwrap(); + let p = PrecompileHintsProcessor::with_num_threads(32, NullHints).unwrap(); const ITERATIONS: usize = 100; const HINTS_PER_ITER: usize = 1_000; diff --git a/hints/src/hints_stream.rs b/hints/src/hints_stream.rs index 38d197a34..6dfbe187e 100644 --- a/hints/src/hints_stream.rs +++ b/hints/src/hints_stream.rs @@ -1,7 +1,6 @@ -//! HintsStream is responsible for processing precompile hints and submitting them to a sink. -//! It uses a StreamSource as the source of hints, and writes the processed hints to a HintsSink. +//! HintsStream is responsible for reading precompile hints from a stream source and sent to a hints processor. -use crate::{HintsProcessor, HintsSink}; +use crate::HintsProcessor; use anyhow::Result; use std::sync::mpsc::{Receiver, Sender}; use std::sync::Arc; @@ -15,16 +14,10 @@ enum ThreadCommand { } /// HintsStream struct manages the processing of precompile hints and writing them to shared memory. -pub struct HintsStream< - HP: HintsProcessor + Send + Sync + 'static, - HS: HintsSink + Send + Sync + 'static, -> { +pub struct HintsStream { /// The hints processor used to process hints before writing. hints_processor: Arc, - /// The hints sink used to submit processed hints. - hints_sink: Arc, - /// Channel sender to communicate with the background thread. tx: Option>, @@ -32,24 +25,16 @@ pub struct HintsStream< thread_handle: Option>, } -impl - HintsStream -{ - /// Create a new HintsStream with the given processor and sink. +impl HintsStream { + /// Create a new HintsStream with the given processor. /// /// # Arguments /// * `hints_processor` - The processor used to process hints. - /// * `hints_sink` - The sink used to submit processed hints. /// /// # Returns /// A new `HintsStream` instance without a running thread. - pub fn new(hints_processor: HP, hints_sink: HS) -> Self { - Self { - hints_processor: Arc::new(hints_processor), - hints_sink: Arc::new(hints_sink), - tx: None, - thread_handle: None, - } + pub fn new(hints_processor: HP) -> Self { + Self { hints_processor: Arc::new(hints_processor), tx: None, thread_handle: None } } /// Stop the current background thread if running. @@ -82,11 +67,10 @@ impl, - hints_sink: Arc, rx: Receiver, ) { loop { match rx.recv() { Ok(ThreadCommand::Process) => { - if let Err(e) = Self::process_stream(&mut stream, &hints_processor, &hints_sink) - { + if let Err(e) = Self::process_stream(&mut stream, &hints_processor) { tracing::error!("Error processing hints in background thread: {:?}", e); } } @@ -120,24 +102,15 @@ impl Result<()> { + fn process_stream(stream: &mut StreamSource, hints_processor: &HP) -> Result<()> { let mut first_batch = true; while let Some(hints) = stream.next()? { let hints = zisk_common::reinterpret_vec(hints)?; - let (processed, has_ctrl_end) = hints_processor.process_hints(&hints, first_batch)?; + let has_ctrl_end = hints_processor.process_hints(&hints, first_batch)?; first_batch = false; - if !processed.is_empty() { - hints_sink.submit(processed)?; - } - // Break if CTRL_END was encountered if has_ctrl_end { debug!("CTRL_END encountered, stopping hint processing"); @@ -169,7 +142,7 @@ impl Drop for HintsStream { +impl Drop for HintsStream { fn drop(&mut self) { self.stop_thread(); } diff --git a/hints/src/lib.rs b/hints/src/lib.rs index f0f745800..6181f4490 100644 --- a/hints/src/lib.rs +++ b/hints/src/lib.rs @@ -20,7 +20,7 @@ pub trait HintsProcessor { /// A tuple of (processed_hints, has_ctrl_end) where: /// - processed_hints: Vec - The processed hint data /// - has_ctrl_end: bool - True if CTRL_END was found (signals end of batch) - fn process_hints(&self, hints: &[u64], first_batch: bool) -> anyhow::Result<(Vec, bool)>; + fn process_hints(&self, hints: &[u64], first_batch: bool) -> anyhow::Result; } pub trait HintsSink { From 728e0ce06af92118add3570a64e13f963b5aba60 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:20:47 +0000 Subject: [PATCH 050/782] improve drainer mechanism --- hints/src/hints_processor.rs | 188 ++++++++++++++++++++++------------- 1 file changed, 117 insertions(+), 71 deletions(-) diff --git a/hints/src/hints_processor.rs b/hints/src/hints_processor.rs index f3f9094fb..3d08ddfd2 100644 --- a/hints/src/hints_processor.rs +++ b/hints/src/hints_processor.rs @@ -48,6 +48,7 @@ use anyhow::Result; use rayon::{ThreadPool, ThreadPoolBuilder}; use std::collections::VecDeque; +use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use tracing::debug; @@ -154,12 +155,14 @@ struct ResultQueue { struct HintProcessorState { /// Ordered results ready for draining queue: Mutex, - /// Notifies when queue becomes empty or error occurs + /// Notifies drainer thread when a hint completes drain_signal: Condvar, /// Next sequence ID to assign to incoming hints next_seq: AtomicUsize, /// Signals processing should stop error_flag: AtomicBool, + /// Signals drainer thread to shut down + shutdown: AtomicBool, /// Invalidates stale workers after reset generation: AtomicUsize, } @@ -171,6 +174,7 @@ impl HintProcessorState { drain_signal: Condvar::new(), next_seq: AtomicUsize::new(0), error_flag: AtomicBool::new(false), + shutdown: AtomicBool::new(false), generation: AtomicUsize::new(0), } } @@ -181,7 +185,7 @@ impl HintProcessorState { /// This struct provides methods to parse and process a stream of concatenated /// hints, using a dedicated Rayon thread pool for parallel processing while /// preserving the original order of results. -pub struct PrecompileHintsProcessor { +pub struct PrecompileHintsProcessor { /// The thread pool used for parallel hint processing. pool: ThreadPool, @@ -191,11 +195,15 @@ pub struct PrecompileHintsProcessor { /// Optional statistics collected during hint processing. stats: [AtomicUsize; NUM_HINT_TYPES as usize], - /// The hints sink used to submit processed hints. + /// The hints sink used to submit processed hints (kept for ownership). + #[allow(dead_code)] hints_sink: Arc, + + /// Handle to the drainer thread (wrapped in ManuallyDrop to join in Drop) + drainer_thread: ManuallyDrop>, } -impl PrecompileHintsProcessor { +impl PrecompileHintsProcessor { const DEFAULT_NUM_THREADS: usize = 32; /// Creates a new processor with the default number of threads. @@ -227,36 +235,48 @@ impl PrecompileHintsProcessor { .build() .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; + let state = Arc::new(HintProcessorState::new()); + let hints_sink = Arc::new(hints_sink); + + // Spawn drainer thread + let drainer_state = Arc::clone(&state); + let drainer_sink = Arc::clone(&hints_sink); + let drainer_thread = std::thread::spawn(move || { + Self::drainer_thread(drainer_state, drainer_sink); + }); + Ok(Self { pool, - state: Arc::new(HintProcessorState::new()), + state, stats: Default::default(), - hints_sink: Arc::new(hints_sink), + hints_sink, + drainer_thread: ManuallyDrop::new(drainer_thread), }) } /// Processes hints in parallel with non-blocking, ordered output. /// /// This method dispatches each hint to the thread pool for parallel processing. - /// Results are collected in a reorder buffer and drained (printed!!!!!!!!!!!!!!!!!!!!!) in the original + /// Results are collected in a reorder buffer and submitted to the sink in the original /// order as soon as consecutive results become available. /// /// # Key characteristics: - /// - **Non-blocking**: Returns immediately after dispatching work to the pool - /// - **Global sequence**: Sequence IDs are maintained across multiple calls - /// - **Ordered output**: Results are printed!!!!!!!!!!!!!!!!!!!! in the order hints were received + /// - **Non-blocking**: Returns immediately after enqueuing hints + /// - **Global sequence**: Sequence IDs maintained across multiple batch calls + /// - **Ordered submission**: Results submitted to sink in order hints were received /// - **Error handling**: Stops processing on first error /// /// # Arguments /// /// * `hints` - A slice of `u64` values containing concatenated hints + /// * `first_batch` - Whether this is the first batch (for CTRL_START validation) /// /// # Returns /// - /// * `Ok((Vec, bool))` - Tuple of (processed data, has_ctrl_end) where has_ctrl_end is true if CTRL_END was encountered + /// * `Ok(true)` - CTRL_END was encountered + /// * `Ok(false)` - Batch processed successfully, no CTRL_END /// * `Err` - If a previous error occurred or hints are malformed pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { - let mut processed = Vec::new(); let mut has_ctrl_end = false; // Parse hints and dispatch to pool @@ -342,36 +362,15 @@ impl PrecompileHintsProcessor { // Handle HINTS_TYPE_RESULT synchronously - it doesn't need async processing if hint.hint_type == HINTS_TYPE_RESULT { - processed.extend_from_slice(&hint.data); - - // Immediately mark this slot as complete and drain - let mut queue = self.state.queue.lock().unwrap(); - let offset = seq_id - queue.next_drain_seq; - queue.buffer[offset] = Some(Ok(hint.data.clone())); - - // Drain consecutive ready results from the front - while let Some(Some(res)) = queue.buffer.front() { - match res { - Ok(_data) => { - queue.buffer.pop_front(); - queue.next_drain_seq += 1; - } - Err(_) => { - self.state.error_flag.store(true, Ordering::Release); - if let Some(Some(Err(e))) = queue.buffer.pop_front() { - eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); - } - queue.next_drain_seq += 1; - self.state.drain_signal.notify_all(); - break; - } - } + // Immediately mark this slot as complete + { + let mut queue = self.state.queue.lock().unwrap(); + let offset = seq_id - queue.next_drain_seq; + queue.buffer[offset] = Some(Ok(hint.data.clone())); } - // Notify if buffer is now empty - if queue.buffer.is_empty() { - self.state.drain_signal.notify_all(); - } + // Notify drainer thread + self.state.drain_signal.notify_one(); } else { // Spawn processing task let state = Arc::clone(&self.state); @@ -409,33 +408,11 @@ impl PrecompileHintsProcessor { queue.buffer[offset] = Some(result); - // Drain consecutive ready results from the front - while let Some(Some(res)) = queue.buffer.front() { - match res { - Ok(_data) => { - // Print the result (will be replaced with send to another process) - // println!("[seq={}] Result: {:?}", queue.next_drain_seq, data); - queue.buffer.pop_front(); - queue.next_drain_seq += 1; - } - Err(_) => { - // Error found - signal to stop and break - state.error_flag.store(true, Ordering::Release); - // Print error and stop draining - if let Some(Some(Err(e))) = queue.buffer.pop_front() { - eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); - } - queue.next_drain_seq += 1; - state.drain_signal.notify_all(); - break; - } - } - } + // Release lock before notifying + drop(queue); - // Notify if buffer is now empty - if queue.buffer.is_empty() { - state.drain_signal.notify_all(); - } + // Notify drainer thread + state.drain_signal.notify_one(); }); } @@ -447,13 +424,67 @@ impl PrecompileHintsProcessor { debug!("Hint type {}: {}", i, count.load(Ordering::Relaxed)); } - if !processed.is_empty() { - self.hints_sink.submit(processed)?; - } - Ok(has_ctrl_end) } + /// Drainer thread that waits for hints to complete and drains ready results from queue. + fn drainer_thread(state: Arc, hints_sink: Arc) { + loop { + let mut queue = state.queue.lock().unwrap(); + + // Check for shutdown + if state.shutdown.load(Ordering::Acquire) { + break; + } + + // Drain all consecutive ready results from the front + while let Some(Some(res)) = queue.buffer.front() { + match res { + Ok(data) => { + // Clone data before dropping lock + let data_to_submit = data.clone(); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; + + // Drop lock before submitting to avoid blocking workers + drop(queue); + + // Submit to sink + if let Err(e) = hints_sink.submit(data_to_submit) { + eprintln!("Error submitting to sink: {}", e); + state.error_flag.store(true, Ordering::Release); + state.drain_signal.notify_all(); + return; + } + + // Re-acquire lock for next iteration + queue = state.queue.lock().unwrap(); + } + Err(e) => { + // Error found - signal to stop + state.error_flag.store(true, Ordering::Release); + eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; + state.drain_signal.notify_all(); + return; + } + } + } + + // Notify waiters if buffer is now empty + if queue.buffer.is_empty() { + state.drain_signal.notify_all(); + } + + // Wait for notification that a hint completed + #[allow(unused_assignments)] + { + queue = state.drain_signal.wait(queue).unwrap(); + } + } + } + /// Waits for all pending hints to be processed and drained. /// /// This method blocks until the reorder buffer is empty, meaning all @@ -557,7 +588,22 @@ impl PrecompileHintsProcessor { } } -impl HintsProcessor for PrecompileHintsProcessor { +impl Drop for PrecompileHintsProcessor { + fn drop(&mut self) { + // Signal drainer thread to shut down + self.state.shutdown.store(true, Ordering::Release); + self.state.drain_signal.notify_all(); + + // Join the drainer thread to ensure clean shutdown + // Safety: We only take the value once in drop + unsafe { + let handle = ManuallyDrop::take(&mut self.drainer_thread); + let _ = handle.join(); + } + } +} + +impl HintsProcessor for PrecompileHintsProcessor { fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { self.process_hints(hints, first_batch) } From 6cd8b4b3fc798ef79dd87802562612cbd261820e Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 10 Dec 2025 16:27:41 +0100 Subject: [PATCH 051/782] Circular buffer in precompile results array and register --- core/src/zisk_rom_2_asm.rs | 113 +++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 41 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 23d4e3cb8..10f1c9ad3 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -71,6 +71,10 @@ const F_MEM_WRITE_SHIFT: u64 = 36; const F_MEM_WRITE: u64 = 1 << F_MEM_WRITE_SHIFT; const F_MEM_WIDTH_SHIFT: u64 = 32; +const PRECOMPILE_BUFFER_SIZE_IN_BYTES: u64 = 0x10000000; // 256MB +const PRECOMPILE_BUFFER_SIZE_IN_U64: u64 = PRECOMPILE_BUFFER_SIZE_IN_BYTES / 8; +const PRECOMPILE_BUFFER_SIZE_U64_MASK: u64 = PRECOMPILE_BUFFER_SIZE_IN_U64 - 1; + #[derive(Default, Debug, Clone)] pub struct ZiskAsmRegister { is_constant: bool, // register is a constant value known at compilation time @@ -5695,7 +5699,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_fcall() { - Self::precompile_results_fcall(ctx, code, "rdi"); + Self::precompile_results_fcall(ctx, code, unusual_code, "rdi"); } else { // Call the fcall function Self::push_internal_registers(ctx, code, false); @@ -6846,7 +6850,7 @@ impl ZiskRom2Asm { if ctx.precompile_results_add256() { *code += &format!("\tmov rdi, [rdi+3*8]\n"); Self::precompile_results_array(ctx, code, unusual_code, "rdi", 4); - Self::precompile_results_register(ctx, code, REG_C); + Self::precompile_results_register(ctx, code, unusual_code, REG_C); } else { // Call the add256 function Self::push_internal_registers_except_c_and_flag(ctx, code, false); @@ -7894,19 +7898,30 @@ impl ZiskRom2Asm { if ctx.call_wait_for_prec_avail() { Self::wait_for_prec_avail(ctx, code, unusual_code); } - *code += &format!( "\tmov {}, {} {}\n", - REG_AUX, + REG_ADDRESS, ctx.mem_precompile_results_address, - ctx.comment_str("aux = precompile_results_address") + ctx.comment_str("address = precompile_results_address") + ); + *code += &format!( + "\tmov {}, {} {}\n", + REG_AUX, + ctx.mem_precompile_read_address, + ctx.comment_str("aux = precompile_read") ); for k in 0..size { + *code += &format!( + "\tand {}, 0x{:x} {}\n", + REG_AUX, + PRECOMPILE_BUFFER_SIZE_U64_MASK, + ctx.comment_str("aux &= buffer mask") + ); *code += &format!( "\tmov {}, [{} + {}*8] {}\n", REG_VALUE, + REG_ADDRESS, REG_AUX, - k, ctx.comment(format!("value = precompile_results[{}]", k)) ); *code += &format!( @@ -7916,37 +7931,53 @@ impl ZiskRom2Asm { REG_VALUE, ctx.comment(format!("addr[{}] = value", k)) ); + if k != size - 1 { + *code += &format!("\tinc {} {}\n", REG_AUX, ctx.comment_str("aux++")); + } } *code += &format!( - "\tadd {}, {}*8 {}\n", - REG_AUX, + "\tadd {}, {} {}\n", + ctx.mem_precompile_read_address, size, - ctx.comment(format!("aux += {}*8", size)) + ctx.comment(format!("read += {}", size)) ); - *code += &format!( - "\tmov {}, {} {}\n", - ctx.mem_precompile_results_address, - REG_AUX, - ctx.comment_str("precompile_results_address = aux") - ); - if ctx.call_wait_for_prec_avail() { - *code += &format!( - "\tadd {}, {}*8 {}\n", - ctx.mem_precompile_read_address, - size, - ctx.comment(format!("read += {}*8", size)) - ); - } } // Copies 1 u64 element from precompile_results_address to the register reg, // and increments precompile_results_address by 8 - fn precompile_results_register(ctx: &mut ZiskAsmContext, code: &mut String, reg: &str) { + fn precompile_results_register( + ctx: &mut ZiskAsmContext, + code: &mut String, + unusual_code: &mut String, + reg: &str, + ) { + if ctx.call_wait_for_prec_avail() { + Self::wait_for_prec_avail(ctx, code, unusual_code); + } *code += &format!( "\tmov {}, {} {}\n", - REG_AUX, + REG_ADDRESS, ctx.mem_precompile_results_address, - ctx.comment_str("aux = precompile_results_address") + ctx.comment_str("address = precompile_results_address") + ); + *code += &format!( + "\tmov {}, {} {}\n", + REG_AUX, + ctx.mem_precompile_read_address, + ctx.comment_str("aux = precompile_read") + ); + *code += &format!( + "\tand {}, 0x{:x} {}\n", + REG_AUX, + PRECOMPILE_BUFFER_SIZE_U64_MASK, + ctx.comment_str("aux &= buffer mask") + ); + *code += &format!( + "\tmov {}, [{} + {}*8] {}\n", + REG_AUX, + REG_ADDRESS, + REG_AUX, + ctx.comment_str("value = precompile_results[0]") ); *code += &format!( "\tmov {}, [{}] {}\n", @@ -7954,18 +7985,24 @@ impl ZiskRom2Asm { REG_AUX, ctx.comment(format!("value = precompile_results[0]")) ); - *code += &format!("\tadd {}, 8 {}\n", REG_AUX, ctx.comment(format!("aux += 8"))); *code += &format!( - "\tmov {}, {} {}\n", - ctx.mem_precompile_results_address, - REG_AUX, - ctx.comment_str("precompile_results_address = aux") + "\tinc {} {}\n", + ctx.mem_precompile_read_address, + ctx.comment_str("precompile_read++") ); } // Copies the fcall result size and result data to the fcall structure // address in reg_address, - fn precompile_results_fcall(ctx: &mut ZiskAsmContext, code: &mut String, reg_address: &str) { + fn precompile_results_fcall( + ctx: &mut ZiskAsmContext, + code: &mut String, + unusual_code: &mut String, + reg_address: &str, + ) { + if ctx.call_wait_for_prec_avail() { + Self::wait_for_prec_avail(ctx, code, unusual_code); + } *code += &format!( "\tmov {}, {} {}\n", REG_AUX, @@ -8034,20 +8071,14 @@ impl ZiskRom2Asm { *code += &format!( "\tmov {}, {} {}\n", REG_AUX, - ctx.mem_precompile_written_address, - ctx.comment_str("aux = precompile_written") - ); - *code += &format!( - "\tmov {}, {} {}\n", - REG_VALUE, ctx.mem_precompile_read_address, - ctx.comment_str("value = precompile_read") + ctx.comment_str("aux = precompile_read") ); *code += &format!( "\tcmp {}, {} {}\n", REG_AUX, - ctx.mem_precompile_read_address, - ctx.comment_str("written ?= read") + ctx.mem_precompile_written_address, + ctx.comment_str("read ?= written") ); *code += &format!( "\tjz pc_{:x}_wait_for_prec_avail {}\n", From 941794afb04d2f708ca8e7bb674a477e318c6cdb Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 10 Dec 2025 20:33:59 +0100 Subject: [PATCH 052/782] Add comments to precompile assembly generator methods --- core/src/zisk_rom_2_asm.rs | 42 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 10f1c9ad3..5fa4bcee7 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -7895,9 +7895,12 @@ impl ZiskRom2Asm { reg_address: &str, size: u64, ) { + // Wait for available precompile results data if ctx.call_wait_for_prec_avail() { Self::wait_for_prec_avail(ctx, code, unusual_code); } + + // Load precompile address and read index *code += &format!( "\tmov {}, {} {}\n", REG_ADDRESS, @@ -7910,13 +7913,18 @@ impl ZiskRom2Asm { ctx.mem_precompile_read_address, ctx.comment_str("aux = precompile_read") ); + + // Loop for all u64-sized elements to read for k in 0..size { + // Take the read index module against the precompile buffer size in u64's *code += &format!( "\tand {}, 0x{:x} {}\n", REG_AUX, PRECOMPILE_BUFFER_SIZE_U64_MASK, ctx.comment_str("aux &= buffer mask") ); + + // Copy the ui64 element from precompile_results to the destination address *code += &format!( "\tmov {}, [{} + {}*8] {}\n", REG_VALUE, @@ -7931,10 +7939,14 @@ impl ZiskRom2Asm { REG_VALUE, ctx.comment(format!("addr[{}] = value", k)) ); + + // Increase the register containing the read index if k != size - 1 { *code += &format!("\tinc {} {}\n", REG_AUX, ctx.comment_str("aux++")); } } + + // Add the read size to precompile_read *code += &format!( "\tadd {}, {} {}\n", ctx.mem_precompile_read_address, @@ -7951,9 +7963,12 @@ impl ZiskRom2Asm { unusual_code: &mut String, reg: &str, ) { + // Wait for available precompile results data if ctx.call_wait_for_prec_avail() { Self::wait_for_prec_avail(ctx, code, unusual_code); } + + // Load precompile address and read index *code += &format!( "\tmov {}, {} {}\n", REG_ADDRESS, @@ -7966,6 +7981,8 @@ impl ZiskRom2Asm { ctx.mem_precompile_read_address, ctx.comment_str("aux = precompile_read") ); + + // Take the read index module against the precompile buffer size in u64's *code += &format!( "\tand {}, 0x{:x} {}\n", REG_AUX, @@ -7979,12 +7996,16 @@ impl ZiskRom2Asm { REG_AUX, ctx.comment_str("value = precompile_results[0]") ); + + // Copy the ui64 element from precompile_results to the destination address *code += &format!( "\tmov {}, [{}] {}\n", reg, REG_AUX, ctx.comment(format!("value = precompile_results[0]")) ); + + // Increase precompile_read *code += &format!( "\tinc {} {}\n", ctx.mem_precompile_read_address, @@ -8000,18 +8021,23 @@ impl ZiskRom2Asm { unusual_code: &mut String, reg_address: &str, ) { + // Wait for available precompile results data if ctx.call_wait_for_prec_avail() { Self::wait_for_prec_avail(ctx, code, unusual_code); } + + // Load precompile address into aux *code += &format!( "\tmov {}, {} {}\n", REG_AUX, ctx.mem_precompile_results_address, ctx.comment_str("aux = precompile_results_address") ); + + // Load the destination fcall address into REG_ADDRESS *code += &format!("\tmov {REG_ADDRESS}, {reg_address}\n"); - // Copy the result size, store it in register B + // Copy the result size (first u64 value) and store it in register B *code += &format!( "\tmov {}, [{}] {}\n", REG_B, @@ -8027,14 +8053,19 @@ impl ZiskRom2Asm { ); *code += &format!("\tadd {}, 1*8 {}\n", REG_AUX, ctx.comment_str("aux += 1*8")); - // Copy data consuming B u64's starting at A=0 + // Copy data consuming REG_B u64's starting at REG_A=0, increasing REG_A until REG_A == REG_B + + // Initialize REG_A to 0 *code += &format!("\txor {}, {} {}\n", REG_A, REG_A, ctx.comment_str("a = 0")); + // Loop start *code += &format!("pc_{:x}_fcall_copy_params_loop_start:\n", ctx.pc); + // End loop when REG_A == REG_B *code += &format!("\tcmp {}, {} {}\n", REG_A, REG_B, ctx.comment_str("a =? b")); *code += &format!("\tje pc_{:x}_fcall_copy_params_loop_end\n", ctx.pc); + // Copy value from precompile_results to fcall[result_data + REG_A] *code += &format!( "\tmov {}, [{} + {}*8] {}\n", REG_VALUE, @@ -8050,10 +8081,17 @@ impl ZiskRom2Asm { REG_VALUE, ctx.comment(format!("addr[] = value")) ); + + // Increment REG_A *code += &format!("\tinc {} {}\n", REG_A, ctx.comment_str("a += 1")); + // Jump to loop start *code += &format!("\tjmp pc_{:x}_fcall_copy_params_loop_start\n", ctx.pc); + + // Loop end *code += &format!("pc_{:x}_fcall_copy_params_loop_end:\n", ctx.pc); + + // Update precompile_results_address += (1 + result_size)*8 *code += &format!("\tshl {}, 3 {}\n", REG_A, ctx.comment_str("a *= 8")); *code += &format!("\tadd {}, {} {}\n", REG_AUX, REG_A, ctx.comment_str("aux += size*8")); *code += &format!( From d81fbd4eb1c89ad5613f32c94f83747fbf1d736f Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 11 Dec 2025 10:27:11 +0100 Subject: [PATCH 053/782] Implement ring buffer in fcall percompile --- core/src/zisk_rom_2_asm.rs | 76 +++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 17 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 5fa4bcee7..60e27059e 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -8026,7 +8026,7 @@ impl ZiskRom2Asm { Self::wait_for_prec_avail(ctx, code, unusual_code); } - // Load precompile address into aux + // Load precompile address into REG_AUX *code += &format!( "\tmov {}, {} {}\n", REG_AUX, @@ -8037,21 +8037,45 @@ impl ZiskRom2Asm { // Load the destination fcall address into REG_ADDRESS *code += &format!("\tmov {REG_ADDRESS}, {reg_address}\n"); + // Calculate the address of the first precompile u64 value = address + read % buffer_size + *code += &format!( + "\tmov {}, {} {}\n", + REG_ADDRESS, + ctx.mem_precompile_read_address, + ctx.comment_str("address = precompile_read") + ); + *code += &format!( + "\tand {}, 0x{:x} {}\n", + REG_ADDRESS, + PRECOMPILE_BUFFER_SIZE_U64_MASK, + ctx.comment_str("address %= buffer size") + ); + *code += &format!( + "\tadd {}, {} {}\n", + REG_ADDRESS, + REG_AUX, + ctx.comment_str("address += precompile_results_address") + ); + // Copy the result size (first u64 value) and store it in register B *code += &format!( "\tmov {}, [{}] {}\n", REG_B, - REG_AUX, + REG_ADDRESS, ctx.comment(format!("b = precompile_results[0]")) ); *code += &format!( "\tmov [{} + {}*8], {} {}\n", - REG_ADDRESS, + reg_address, FCALL_RESULT_SIZE, REG_B, ctx.comment(format!("fcall[result_size] = b")) ); - *code += &format!("\tadd {}, 1*8 {}\n", REG_AUX, ctx.comment_str("aux += 1*8")); + *code += &format!( + "\tinc {} {}\n", + ctx.mem_precompile_read_address, + ctx.comment_str("precompile_read++") + ); // Copy data consuming REG_B u64's starting at REG_A=0, increasing REG_A until REG_A == REG_B @@ -8065,17 +8089,37 @@ impl ZiskRom2Asm { *code += &format!("\tcmp {}, {} {}\n", REG_A, REG_B, ctx.comment_str("a =? b")); *code += &format!("\tje pc_{:x}_fcall_copy_params_loop_end\n", ctx.pc); + // Calculate the address of the next precompile u64 value = address + read % buffer_size + *code += &format!( + "\tmov {}, {} {}\n", + REG_ADDRESS, + ctx.mem_precompile_read_address, + ctx.comment_str("address = precompile_read") + ); + *code += &format!("\tadd {}, {} {}\n", REG_ADDRESS, REG_A, ctx.comment_str("address += a")); + *code += &format!( + "\tand {}, 0x{:x} {}\n", + REG_ADDRESS, + PRECOMPILE_BUFFER_SIZE_U64_MASK, + ctx.comment_str("address %= buffer size") + ); + *code += &format!( + "\tadd {}, {} {}\n", + REG_ADDRESS, + REG_AUX, + ctx.comment_str("address += precompile_results_address") + ); + // Copy value from precompile_results to fcall[result_data + REG_A] *code += &format!( - "\tmov {}, [{} + {}*8] {}\n", + "\tmov {}, [{}] {}\n", REG_VALUE, - REG_AUX, - REG_A, + REG_ADDRESS, ctx.comment(format!("value = precompile_results[]")) ); *code += &format!( "\tmov [{} + {}*8 + {}*8], {} {}\n", - REG_ADDRESS, + reg_address, REG_A, FCALL_RESULT, REG_VALUE, @@ -8083,7 +8127,7 @@ impl ZiskRom2Asm { ); // Increment REG_A - *code += &format!("\tinc {} {}\n", REG_A, ctx.comment_str("a += 1")); + *code += &format!("\tinc {} {}\n", REG_A, ctx.comment_str("a++")); // Jump to loop start *code += &format!("\tjmp pc_{:x}_fcall_copy_params_loop_start\n", ctx.pc); @@ -8091,21 +8135,19 @@ impl ZiskRom2Asm { // Loop end *code += &format!("pc_{:x}_fcall_copy_params_loop_end:\n", ctx.pc); - // Update precompile_results_address += (1 + result_size)*8 - *code += &format!("\tshl {}, 3 {}\n", REG_A, ctx.comment_str("a *= 8")); - *code += &format!("\tadd {}, {} {}\n", REG_AUX, REG_A, ctx.comment_str("aux += size*8")); + // Update precompile_read += result_size *code += &format!( - "\tmov {}, {} {}\n", - ctx.mem_precompile_results_address, - REG_AUX, - ctx.comment_str("precompile_results_address = aux") + "\tadd {}, {} {}\n", + ctx.mem_precompile_read_address, + REG_A, + ctx.comment_str("precompile_read += fcall_result_size") ); } fn wait_for_prec_avail(ctx: &mut ZiskAsmContext, code: &mut String, unusual_code: &mut String) { *code += &ctx.full_line_comment("Wait for precompile results available".to_string()); - // if *precompile_written_address == *precompile_read_address -> call wait_for_prec_avail + // if precompile_written == precompile_read then call wait_for_prec_avail *code += &format!( "\tmov {}, {} {}\n", REG_AUX, From 740f6ffb176890fe648305ed40e632f4475c3e30 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:10:07 +0000 Subject: [PATCH 054/782] improve hints_shmem creation --- executor/src/executor.rs | 35 +---------------------------- executor/src/hints_shmem.rs | 44 ++++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/executor/src/executor.rs b/executor/src/executor.rs index c7a952b5f..5b40061b2 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -197,41 +197,8 @@ impl ZiskExecutor { (None, None) }; - // Generate shared memory names for hints pipeline. - let hints_shmem_names = AsmServices::SERVICES - .iter() - .map(|service| { - AsmSharedMemory::::shmem_precompile_name( - if let Some(base_port) = base_port { - AsmServices::port_for(service, base_port, local_rank) - } else { - AsmServices::default_port(service, local_rank) - }, - *service, - local_rank, - ) - }) - .collect::>(); - - // Generate shared memory control names for hints pipeline. - let hints_shmem_control_names = AsmServices::SERVICES - .iter() - .map(|service| { - AsmSharedMemory::::shmem_control_name( - if let Some(base_port) = base_port { - AsmServices::port_for(service, base_port, local_rank) - } else { - AsmServices::default_port(service, local_rank) - }, - *service, - local_rank, - ) - }) - .collect::>(); - // Create hints pipeline with null hints stream initially. - let hints_shmem = - HintsShmem::new(hints_shmem_control_names, hints_shmem_names, unlock_mapped_memory); + let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory); let hints_processor = PrecompileHintsProcessor::new(hints_shmem) .expect("Failed to create PrecompileHintsProcessor"); diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index b6091ed0a..ac532d834 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -4,7 +4,7 @@ //! using SharedMemoryWriter instances. use anyhow::Result; -use asm_runner::SharedMemoryWriter; +use asm_runner::{AsmMTHeader, AsmServices, AsmSharedMemory, SharedMemoryWriter}; use std::sync::Mutex; use tracing::{debug, warn}; use zisk_hints::HintsSink; @@ -35,19 +35,51 @@ impl HintsShmem { /// # Returns /// A new `HintsShmem` instance with uninitialized writers. pub fn new( - shmem_control_names: Vec, - shmem_names: Vec, + base_port: Option, + local_rank: i32, unlock_mapped_memory: bool, ) -> Self { + // Generate shared memory names for hints pipeline. + let hints_shmem_names = AsmServices::SERVICES + .iter() + .map(|service| { + AsmSharedMemory::::shmem_precompile_name( + if let Some(base_port) = base_port { + AsmServices::port_for(service, base_port, local_rank) + } else { + AsmServices::default_port(service, local_rank) + }, + *service, + local_rank, + ) + }) + .collect::>(); + + // Generate shared memory control names for hints pipeline. + let hints_shmem_control_names = AsmServices::SERVICES + .iter() + .map(|service| { + AsmSharedMemory::::shmem_control_name( + if let Some(base_port) = base_port { + AsmServices::port_for(service, base_port, local_rank) + } else { + AsmServices::default_port(service, local_rank) + }, + *service, + local_rank, + ) + }) + .collect::>(); + assert_eq!( - shmem_control_names.len(), - shmem_names.len(), + hints_shmem_control_names.len(), + hints_shmem_names.len(), "Shared memory names and control names must have the same length" ); // Map names to tuples let shmem_names: Vec<(String, String)> = - shmem_control_names.into_iter().zip(shmem_names.into_iter()).collect(); + hints_shmem_control_names.into_iter().zip(hints_shmem_names.into_iter()).collect(); Self { shmem_names, unlock_mapped_memory, shmem_writers: Mutex::new(Vec::new()) } } From 052c4e8095aec6c2acff7847a7db355f9c57372c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:11:34 +0000 Subject: [PATCH 055/782] added sem control creaetion names --- emulator-asm/asm-runner/src/shmem_utils.rs | 26 +++++ executor/src/hints_shmem.rs | 105 +++++++++++++-------- 2 files changed, 92 insertions(+), 39 deletions(-) diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index f1bc9ea97..15bba5b71 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -285,6 +285,32 @@ impl AsmSharedMemory { ) } + /// Shared memory name for precompile hints data + pub fn shmem_semaphore_available_name( + port: u16, + asm_service: AsmService, + local_rank: i32, + ) -> String { + format!( + "{}_{}_prec_avail", + AsmServices::shmem_prefix(port, local_rank), + asm_service.as_str() + ) + } + + /// Shared memory name for precompile hints data + pub fn shmem_semaphore_read_name( + port: u16, + asm_service: AsmService, + local_rank: i32, + ) -> String { + format!( + "{}_{}_prec_read", + AsmServices::shmem_prefix(port, local_rank), + asm_service.as_str() + ) + } + /// Shared memory name for precompile hints data control pub fn shmem_control_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { format!("{}_{}_control", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index ac532d834..e7c48696d 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -4,21 +4,35 @@ //! using SharedMemoryWriter instances. use anyhow::Result; -use asm_runner::{AsmMTHeader, AsmServices, AsmSharedMemory, SharedMemoryWriter}; +use asm_runner::{AsmMTHeader, AsmService, AsmServices, AsmSharedMemory, SharedMemoryWriter}; +use named_sem::NamedSemaphore; use std::sync::Mutex; use tracing::{debug, warn}; use zisk_hints::HintsSink; +enum NameType { + Control, + Data, + SemAvail, + SemRead, +} + /// HintsShmem struct manages the writing of processed precompile hints to shared memory. pub struct HintsShmem { /// Names of the shared memories to write hints to. 0 for control, 1 for data shmem_names: Vec<(String, String)>, + /// Names of the semaphores for synchronization. 0 for available, 1 for read + sem_names: Vec<(String, String)>, + /// Whether to unlock mapped memory after writing. unlock_mapped_memory: bool, /// Shared memory writers for writing processed hints. 0 for control, 1 for data shmem_writers: Mutex>, + + /// Control semaphores for synchronization. 0 for available, 1 for read + sem_control: Mutex>, } impl HintsShmem { @@ -28,60 +42,73 @@ impl HintsShmem { /// Create a new HintsShmem with the given shared memory names and unlock option. /// /// # Arguments - /// * `shmem_control_names` - A vector of shared memory control names to write hints to. - /// * `shmem_names` - A vector of shared memory names to write hints to. + /// * `base_port` - Optional base port for generating shared memory names. + /// * `local_rank` - Local rank for generating shared memory names. /// * `unlock_mapped_memory` - Whether to unlock mapped memory after writing. /// /// # Returns /// A new `HintsShmem` instance with uninitialized writers. - pub fn new( - base_port: Option, - local_rank: i32, - unlock_mapped_memory: bool, - ) -> Self { + pub fn new(base_port: Option, local_rank: i32, unlock_mapped_memory: bool) -> Self { // Generate shared memory names for hints pipeline. - let hints_shmem_names = AsmServices::SERVICES + let shmem_names = AsmServices::SERVICES .iter() .map(|service| { - AsmSharedMemory::::shmem_precompile_name( - if let Some(base_port) = base_port { - AsmServices::port_for(service, base_port, local_rank) - } else { - AsmServices::default_port(service, local_rank) - }, - *service, - local_rank, - ) + let port = if let Some(base_port) = base_port { + AsmServices::port_for(service, base_port, local_rank) + } else { + AsmServices::default_port(service, local_rank) + }; + let control_name = + Self::resource_name(service, port, local_rank, NameType::Control); + let data = Self::resource_name(service, port, local_rank, NameType::Data); + (control_name, data) }) .collect::>(); - // Generate shared memory control names for hints pipeline. - let hints_shmem_control_names = AsmServices::SERVICES + // Generate semaphore names for hints pipeline. + let sem_names = AsmServices::SERVICES .iter() .map(|service| { - AsmSharedMemory::::shmem_control_name( - if let Some(base_port) = base_port { - AsmServices::port_for(service, base_port, local_rank) - } else { - AsmServices::default_port(service, local_rank) - }, - *service, - local_rank, - ) + let port = if let Some(base_port) = base_port { + AsmServices::port_for(service, base_port, local_rank) + } else { + AsmServices::default_port(service, local_rank) + }; + let sem_avail = Self::resource_name(service, port, local_rank, NameType::SemAvail); + let sem_read = Self::resource_name(service, port, local_rank, NameType::SemRead); + (sem_avail, sem_read) }) .collect::>(); - assert_eq!( - hints_shmem_control_names.len(), - hints_shmem_names.len(), - "Shared memory names and control names must have the same length" - ); - - // Map names to tuples - let shmem_names: Vec<(String, String)> = - hints_shmem_control_names.into_iter().zip(hints_shmem_names.into_iter()).collect(); + Self { + shmem_names, + sem_names, + unlock_mapped_memory, + shmem_writers: Mutex::new(Vec::new()), + sem_control: Mutex::new(Vec::new()), + } + } - Self { shmem_names, unlock_mapped_memory, shmem_writers: Mutex::new(Vec::new()) } + fn resource_name( + service: &AsmService, + port: u16, + local_rank: i32, + name_type: NameType, + ) -> String { + match name_type { + NameType::Control => { + AsmSharedMemory::::shmem_control_name(port, *service, local_rank) + } + NameType::Data => { + AsmSharedMemory::::shmem_precompile_name(port, *service, local_rank) + } + NameType::SemAvail => AsmSharedMemory::::shmem_semaphore_available_name( + port, *service, local_rank, + ), + NameType::SemRead => AsmSharedMemory::::shmem_semaphore_read_name( + port, *service, local_rank, + ), + } } /// Add a shared memory name to the pipeline. From 5e0119c3405e4a312a1f28625a82d3230a0a0a9a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:15:10 +0000 Subject: [PATCH 056/782] remove add_shmem_name as is not necessary now --- executor/src/hints_shmem.rs | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index e7c48696d..6363d9783 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -111,41 +111,6 @@ impl HintsShmem { } } - /// Add a shared memory name to the pipeline. - /// - /// This method must be called before initialization. - /// - /// # Arguments - /// * `control_name` - The name of the control shared memory to add. - /// * `name` - The name of the shared memory to add. - /// - /// # Returns - /// * `Ok(())` - If the name was successfully added or already exists - /// * `Err` - If writers have already been initialized - pub fn add_shmem_name(&mut self, control_name: String, name: String) -> Result<()> { - // Check if the writers have already been initialized - let shmem_writers = self.shmem_writers.lock().unwrap(); - if !shmem_writers.is_empty() { - return Err(anyhow::anyhow!( - "Cannot add shared memory name '{}' after initialization", - name - )); - } - - // Check if the name already exists - if self.shmem_names.contains(&(control_name.clone(), name.clone())) { - warn!( - "Shared memory name '{}' already exists in the pipeline. Skipping addition.", - name - ); - return Ok(()); - } - - self.shmem_names.push((control_name, name)); - - Ok(()) - } - /// Check if the shared memory writers have been initialized. fn is_initialized(&self) -> bool { let shmem_writers = self.shmem_writers.lock().unwrap(); From 0e53d92c9abebe2b35711f206fccccd5ab978ef7 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:21:45 +0000 Subject: [PATCH 057/782] wip buffer ring --- Cargo.lock | 1 + emulator-asm/asm-runner/src/shmem_writer.rs | 18 ++++ executor/Cargo.toml | 3 + executor/src/hints_shmem.rs | 96 +++++++++++++++------ 4 files changed, 91 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c00622f4..7b453097f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1342,6 +1342,7 @@ dependencies = [ "itertools 0.14.0", "mem-common", "mem-planner-cpp", + "named-sem", "pil-std-lib", "precomp-arith-eq", "precomp-arith-eq-384", diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index 8d5dec899..a2d002449 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -112,6 +112,24 @@ impl SharedMemoryWriter { Ok(()) } + + /// Reads a u64 from shared memory at a specific offset (in bytes) + /// + /// # Arguments + /// * `offset` - Byte offset from the start of shared memory (must be 8-byte aligned) + /// + /// # Safety + /// This method assumes that: + /// - The shared memory contains at least `offset + 8` bytes of valid data + /// - The offset should be aligned to 8 bytes + /// + /// # Returns + /// * The u64 value read from the specified offset (in native endianness) + #[inline] + pub fn read_u64_at(&self, offset: usize) -> u64 { + debug_assert_eq!(offset % 8, 0, "Offset must be 8-byte aligned"); + unsafe { (self.ptr.add(offset) as *const u64).read() } + } } impl Drop for SharedMemoryWriter { diff --git a/executor/Cargo.toml b/executor/Cargo.toml index b76b2a302..980f8de30 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -44,6 +44,9 @@ sm-binary = { workspace = true } sm-mem = { workspace = true } sm-frequent-ops = { workspace = true } +[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] +named-sem = { workspace = true } + [features] default = [] disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index 6363d9783..d31fce284 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -7,7 +7,7 @@ use anyhow::Result; use asm_runner::{AsmMTHeader, AsmService, AsmServices, AsmSharedMemory, SharedMemoryWriter}; use named_sem::NamedSemaphore; use std::sync::Mutex; -use tracing::{debug, warn}; +use tracing::debug; use zisk_hints::HintsSink; enum NameType { @@ -35,6 +35,9 @@ pub struct HintsShmem { sem_control: Mutex>, } +unsafe impl Send for HintsShmem {} +unsafe impl Sync for HintsShmem {} + impl HintsShmem { const CONTROL_PRECOMPILE_SIZE: u64 = 0x1000; // 4KB const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB @@ -121,35 +124,74 @@ impl HintsShmem { /// /// This method creates SharedMemoryWriter instances for each shared memory name. /// If writers are already initialized it logs a warning and does nothing. - fn initialize(&self) { + fn initialize(&self) -> Result<()> { let mut shmem_writer = self.shmem_writers.lock().unwrap(); + let mut sem_control = self.sem_control.lock().unwrap(); + // Initialize shared memory writers if !shmem_writer.is_empty() { - warn!("SharedMemoryWriters for precompile hints is already initialized. Skipping"); - } else { - debug!("Initializing SharedMemoryWriter for precompile hints",); - - *shmem_writer = self - .shmem_names - .iter() - .map(|(control_name, name)| { - ( - SharedMemoryWriter::new( - &control_name, - Self::CONTROL_PRECOMPILE_SIZE as usize, - self.unlock_mapped_memory, - ) - .expect("Failed to create SharedMemoryWriter for precompile hints"), - SharedMemoryWriter::new( - &name, - Self::MAX_PRECOMPILE_SIZE as usize, - self.unlock_mapped_memory, - ) - .expect("Failed to create SharedMemoryWriter for precompile hints"), - ) - }) - .collect(); + return Err(anyhow::anyhow!( + "SharedMemoryWriters for precompile hints already initialized." + )); + } + + debug!("Initializing SharedMemoryWriter for precompile hints",); + *shmem_writer = self + .shmem_names + .iter() + .map(|(control_name, name)| { + ( + Self::create_writer( + control_name, + Self::CONTROL_PRECOMPILE_SIZE as usize, + self.unlock_mapped_memory, + ), + Self::create_writer( + name, + Self::MAX_PRECOMPILE_SIZE as usize, + self.unlock_mapped_memory, + ), + ) + }) + .collect(); + + // Initialize semaphores + if !sem_control.is_empty() { + return Err(anyhow::anyhow!( + "Control semaphores for precompile hints already initialized." + )); } + + debug!("Initializing control semaphores for precompile hints",); + *sem_control = self + .sem_names + .iter() + .map(|(sem_avail_name, sem_read_name)| { + (Self::create_semaphore(sem_avail_name), Self::create_semaphore(sem_read_name)) + }) + .collect(); + + Ok(()) + } + + /// Create a SharedMemoryWriter with error handling. + fn create_writer(name: &str, size: usize, unlock_mapped_memory: bool) -> SharedMemoryWriter { + SharedMemoryWriter::new(name, size, unlock_mapped_memory) + .expect("Failed to create SharedMemoryWriter for precompile hints") + } + + /// Create a NamedSemaphore with error handling. + fn create_semaphore(name: &str) -> NamedSemaphore { + NamedSemaphore::create(name.to_string(), 0) + .expect("Failed to create semaphore for precompile hints") + } + + fn write_size(&self, writer: &SharedMemoryWriter) -> u64 { + writer.read_u64_at(0) + } + + fn read_size(&self, writer: &SharedMemoryWriter) -> u64 { + writer.read_u64_at(8) } } @@ -165,7 +207,7 @@ impl HintsSink for HintsShmem { fn submit(&self, processed: Vec) -> anyhow::Result<()> { // TODO! Is it necessary???? if !self.is_initialized() { - self.initialize(); + self.initialize()?; } // Input size includes length prefix as u64 From d4b6b2aa5578045b1935551f911a97c39c37ca8c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 12 Dec 2025 07:10:44 +0000 Subject: [PATCH 058/782] wip not working --- core/src/zisk_rom_2_asm.rs | 3 +- emulator-asm/asm-runner/src/shmem_utils.rs | 16 +- emulator-asm/asm-runner/src/shmem_writer.rs | 105 +++++++- emulator-asm/src/main.c | 36 ++- executor/src/executor.rs | 3 +- executor/src/hints_shmem.rs | 264 ++++++++++---------- 6 files changed, 262 insertions(+), 165 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 60e27059e..4c1754fb1 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -71,6 +71,7 @@ const F_MEM_WRITE_SHIFT: u64 = 36; const F_MEM_WRITE: u64 = 1 << F_MEM_WRITE_SHIFT; const F_MEM_WIDTH_SHIFT: u64 = 32; +// const PRECOMPILE_BUFFER_SIZE_IN_BYTES: u64 = 0x100000; // 1MB const PRECOMPILE_BUFFER_SIZE_IN_BYTES: u64 = 0x10000000; // 256MB const PRECOMPILE_BUFFER_SIZE_IN_U64: u64 = PRECOMPILE_BUFFER_SIZE_IN_BYTES / 8; const PRECOMPILE_BUFFER_SIZE_U64_MASK: u64 = PRECOMPILE_BUFFER_SIZE_IN_U64 - 1; @@ -512,7 +513,7 @@ impl ZiskRom2Asm { ctx.mem_precompile_results_address = format!("qword {}[MEM_PRECOMPILE_RESULTS_ADDRESS]", ctx.ptr); ctx.mem_precompile_written_address = format!("qword {}[0x70000000]", ctx.ptr); - ctx.mem_precompile_read_address = format!("qword {}[0x70000008]", ctx.ptr); + ctx.mem_precompile_read_address = format!("qword {}[0x70001000]", ctx.ptr); // Preamble *code += ".intel_syntax noprefix\n"; diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 15bba5b71..9a53f69b3 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -286,26 +286,18 @@ impl AsmSharedMemory { } /// Shared memory name for precompile hints data - pub fn shmem_semaphore_available_name( - port: u16, - asm_service: AsmService, - local_rank: i32, - ) -> String { + pub fn semaphore_available_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { format!( - "{}_{}_prec_avail", + "/{}_{}_prec_avail", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str() ) } /// Shared memory name for precompile hints data - pub fn shmem_semaphore_read_name( - port: u16, - asm_service: AsmService, - local_rank: i32, - ) -> String { + pub fn semaphore_read_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { format!( - "{}_{}_prec_read", + "/{}_{}_prec_read", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str() ) diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index a2d002449..b48b121c9 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -1,12 +1,14 @@ #[cfg(all(target_os = "linux", target_arch = "x86_64"))] -use libc::{mmap, msync, shm_open, MAP_FAILED, MAP_SHARED, MS_SYNC}; +use libc::{mmap, msync, shm_open, MAP_FAILED, MAP_SHARED, MS_INVALIDATE, MS_SYNC}; use std::io::{self, Result}; use std::ptr; +use std::sync::atomic::{fence, Ordering}; use libc::{c_void, close, munmap, PROT_READ, PROT_WRITE, S_IRUSR, S_IWUSR}; pub struct SharedMemoryWriter { ptr: *mut u8, + current_ptr: *mut u8, size: usize, fd: i32, name: String, @@ -26,8 +28,9 @@ impl SharedMemoryWriter { // Map the memory region for read/write let ptr = Self::map(fd, size, PROT_READ | PROT_WRITE, unlock_mapped_memory, name); + let ptr_u8 = ptr as *mut u8; - Ok(Self { ptr: ptr as *mut u8, size, fd, name: name.to_string() }) + Ok(Self { ptr: ptr_u8, current_ptr: ptr_u8, size, fd, name: name.to_string() }) } #[cfg(all(target_os = "linux", target_arch = "x86_64"))] @@ -113,6 +116,63 @@ impl SharedMemoryWriter { Ok(()) } + /// Writes data to the shared memory as a ring buffer, handling wraparound automatically + /// + /// Uses internal pointer tracking - no offset parameter needed. + /// + /// # Type Parameters + /// * `T` - The element type of the slice (e.g., u8, u64) + /// + /// # Arguments + /// * `data` - A slice of data to write to shared memory + /// + /// # Returns + /// * `Ok(())` - If data was successfully written + /// * `Err` - If msync fails + pub fn write_ring_buffer(&mut self, data: &[T]) -> Result<()> { + let byte_size = data.len() * std::mem::size_of::(); + let data_ptr = data.as_ptr() as *const u8; + + unsafe { + let current_offset = self.current_ptr.offset_from(self.ptr) as usize; + + // Check if data wraps around the buffer + if current_offset + byte_size > self.size { + // Split write: first part to end of buffer, second part from start + let first_part_size = self.size - current_offset; + let second_part_size = byte_size - first_part_size; + + // Write first part to end of buffer + ptr::copy_nonoverlapping(data_ptr, self.current_ptr, first_part_size); + + // Write second part to start of buffer + ptr::copy_nonoverlapping(data_ptr.add(first_part_size), self.ptr, second_part_size); + + // Update current_ptr to point after the second part + self.current_ptr = self.ptr.add(second_part_size); + } else { + // Write contiguously + ptr::copy_nonoverlapping(data_ptr, self.current_ptr, byte_size); + + // Update current_ptr, wrapping if at end + self.current_ptr = self.current_ptr.add(byte_size); + let new_offset = self.current_ptr.offset_from(self.ptr) as usize; + if new_offset >= self.size { + self.current_ptr = self.ptr; + } + } + + // Force changes to be flushed to the shared memory + // TODO! only msync the affected regions + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + if msync(self.ptr as *mut _, self.size, MS_SYNC) != 0 { + return Err(io::Error::last_os_error()); + } + } + + Ok(()) + } + /// Reads a u64 from shared memory at a specific offset (in bytes) /// /// # Arguments @@ -126,9 +186,46 @@ impl SharedMemoryWriter { /// # Returns /// * The u64 value read from the specified offset (in native endianness) #[inline] - pub fn read_u64_at(&self, offset: usize) -> u64 { + pub fn read_u64_at(&self, offset: usize) -> Result { debug_assert_eq!(offset % 8, 0, "Offset must be 8-byte aligned"); - unsafe { (self.ptr.add(offset) as *const u64).read() } + + unsafe { + fence(Ordering::SeqCst); + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + if msync(self.ptr.add(offset) as *mut _, std::mem::size_of::(), MS_INVALIDATE) != 0 + { + return Err(io::Error::last_os_error()); + } + } + + Ok(unsafe { (self.ptr.add(offset) as *const u64).read() }) + } + + /// Writes a u64 to shared memory at a specific offset (in bytes) + /// + /// # Arguments + /// * `offset` - Byte offset from the start of shared memory (must be 8-byte aligned) + /// * `value` - The u64 value to write + /// + /// # Safety + /// This method assumes that: + /// - The shared memory contains at least `offset + 8` bytes of valid data + /// - The offset is 8-byte aligned for optimal performance + #[inline] + pub fn write_u64_at(&self, offset: usize, value: u64) -> Result<()> { + debug_assert_eq!(offset % 8, 0, "Offset must be 8-byte aligned"); + + unsafe { + (self.ptr.add(offset) as *mut u64).write(value); + + fence(Ordering::SeqCst); + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + if msync(self.ptr.add(offset) as *mut _, std::mem::size_of::(), MS_SYNC) != 0 { + return Err(io::Error::last_os_error()); + } + } + + Ok(()) } } diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index eb736c889..ab7942f30 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -47,7 +47,7 @@ uint64_t get_precompile_results(void); #define INITIAL_TRACE_SIZE (uint64_t)0x100000000 // 4GB #define CONTROL_ADDR (uint64_t)0x70000000 -#define CONTROL_SIZE (uint64_t)0x1000 // 4kB +#define CONTROL_SIZE (uint64_t)0x2000 // 8kB uint8_t * pInput = (uint8_t *)INPUT_ADDR; uint8_t * pInputLast = (uint8_t *)(INPUT_ADDR + 10440504 - 64); @@ -317,6 +317,7 @@ int process_id = 0; uint64_t input_size = 0; #define MAX_PRECOMPILE_SIZE (uint64_t)0x10000000 // 256MB +// #define MAX_PRECOMPILE_SIZE (uint64_t)0x100000 // 1MB // Precompile results shared memory char shmem_precompile_name[128]; @@ -337,8 +338,8 @@ char precompile_file_name[4096] = {0}; char shmem_control_name[128]; int shmem_control_fd = -1; uint64_t * shmem_control_address = NULL; -uint64_t * precompile_written_address = NULL; -uint64_t * precompile_read_address = NULL; +volatile uint64_t * precompile_written_address = NULL; +volatile uint64_t * precompile_read_address = NULL; int main(int argc, char *argv[]) { @@ -1790,7 +1791,7 @@ void client_setup (void) } shmem_control_address = (uint64_t *)pControl; precompile_written_address = &shmem_control_address[0]; - precompile_read_address = &shmem_control_address[1]; + precompile_read_address = &shmem_control_address[4096/8]; if (verbose) printf("mmap(control) mapped %lu B and returned address %p in %lu us\n", CONTROL_SIZE, shmem_control_address, duration); // Create the semaphore for precompile results available signal @@ -3140,7 +3141,7 @@ void server_setup (void) } shmem_control_address = (uint64_t *)pControl; precompile_written_address = &shmem_control_address[0]; - precompile_read_address = &shmem_control_address[1]; + precompile_read_address = &shmem_control_address[4096/8]; if (verbose) printf("mmap(control) mapped %lu B and returned address %p in %lu us\n", CONTROL_SIZE, shmem_control_address, duration); // Create the semaphore for precompile results available signal @@ -4588,6 +4589,19 @@ void file_lock(void) int _wait_for_prec_avail (void) { + int sem_prec_avail_value = 0; + sem_getvalue(sem_prec_avail, &sem_prec_avail_value); + printf("_wait_for_prec_avail() sem_prec_avail_value=%d\n", sem_prec_avail_value); + + // Sync precompile shared memory + __sync_synchronize(); + if (msync((void *)shmem_control_address + 4096, 8, MS_SYNC) != 0) { + printf("ERROR: 1 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + // Tell the writer that we have read the precompile results sem_post(sem_prec_read); @@ -4595,8 +4609,9 @@ int _wait_for_prec_avail (void) sem_trywait(sem_prec_avail); // Sync precompile shared memory - if (msync((void *)shmem_control_address, CONTROL_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); + __sync_synchronize(); + if (msync((void *)shmem_control_address, 8, MS_SYNC) != 0) { + printf("ERROR: 2 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -4606,6 +4621,7 @@ int _wait_for_prec_avail (void) if (*precompile_written_address > *precompile_read_address) { // Sync precompile shared memory + __sync_synchronize(); if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); @@ -4626,8 +4642,9 @@ int _wait_for_prec_avail (void) } // Sync precompile shared memory - if (msync((void *)shmem_control_address, CONTROL_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); + __sync_synchronize(); + if (msync((void *)shmem_control_address, 8, MS_SYNC) != 0) { + printf("ERROR: 3 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -4640,6 +4657,7 @@ int _wait_for_prec_avail (void) } // Sync precompile shared memory + __sync_synchronize(); if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 5b40061b2..b836422b6 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -198,7 +198,8 @@ impl ZiskExecutor { }; // Create hints pipeline with null hints stream initially. - let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory); + let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory) + .expect("Failed to create HintsShmem"); let hints_processor = PrecompileHintsProcessor::new(hints_shmem) .expect("Failed to create PrecompileHintsProcessor"); diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index d31fce284..a7fdb51a4 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -10,37 +10,59 @@ use std::sync::Mutex; use tracing::debug; use zisk_hints::HintsSink; -enum NameType { - Control, - Data, - SemAvail, - SemRead, +/// Names for a service's shared memory and semaphore resources +struct ServiceResourceNames { + control_name: String, + data_name: String, + sem_available_name: String, + sem_read_name: String, } -/// HintsShmem struct manages the writing of processed precompile hints to shared memory. -pub struct HintsShmem { - /// Names of the shared memories to write hints to. 0 for control, 1 for data - shmem_names: Vec<(String, String)>, - - /// Names of the semaphores for synchronization. 0 for available, 1 for read - sem_names: Vec<(String, String)>, - - /// Whether to unlock mapped memory after writing. - unlock_mapped_memory: bool, +impl ServiceResourceNames { + fn new(service: &AsmService, port: u16, local_rank: i32) -> Self { + Self { + control_name: AsmSharedMemory::::shmem_control_name( + port, *service, local_rank, + ), + data_name: AsmSharedMemory::::shmem_precompile_name( + port, *service, local_rank, + ), + sem_available_name: AsmSharedMemory::::semaphore_available_name( + port, *service, local_rank, + ), + sem_read_name: AsmSharedMemory::::semaphore_read_name( + port, *service, local_rank, + ), + } + } +} - /// Shared memory writers for writing processed hints. 0 for control, 1 for data - shmem_writers: Mutex>, +/// Represents a service's shared memory and synchronization resources +struct ServiceResources { + /// Control shared memory writer + control_writer: SharedMemoryWriter, + /// Data shared memory writer + data_writer: SharedMemoryWriter, + /// Semaphore to signal data availability + sem_available: NamedSemaphore, + /// Semaphore to wait for data consumption + sem_read: NamedSemaphore, +} - /// Control semaphores for synchronization. 0 for available, 1 for read - sem_control: Mutex>, +/// HintsShmem struct manages the writing of processed precompile hints to shared memory. +pub struct HintsShmem { + /// Service resources combining shared memory writers and semaphores + resources: Mutex>, } unsafe impl Send for HintsShmem {} unsafe impl Sync for HintsShmem {} impl HintsShmem { - const CONTROL_PRECOMPILE_SIZE: u64 = 0x1000; // 4KB + const CONTROL_PRECOMPILE_SIZE: u64 = 0x2000; // 8KB const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB + // const MAX_PRECOMPILE_SIZE: u64 = 0x100000; // 1MB + const BUFFER_THRESHOLD: u64 = 1000; // 1000 bytes - threshold for signaling reader /// Create a new HintsShmem with the given shared memory names and unlock option. /// @@ -51,25 +73,12 @@ impl HintsShmem { /// /// # Returns /// A new `HintsShmem` instance with uninitialized writers. - pub fn new(base_port: Option, local_rank: i32, unlock_mapped_memory: bool) -> Self { - // Generate shared memory names for hints pipeline. - let shmem_names = AsmServices::SERVICES - .iter() - .map(|service| { - let port = if let Some(base_port) = base_port { - AsmServices::port_for(service, base_port, local_rank) - } else { - AsmServices::default_port(service, local_rank) - }; - let control_name = - Self::resource_name(service, port, local_rank, NameType::Control); - let data = Self::resource_name(service, port, local_rank, NameType::Data); - (control_name, data) - }) - .collect::>(); - - // Generate semaphore names for hints pipeline. - let sem_names = AsmServices::SERVICES + pub fn new( + base_port: Option, + local_rank: i32, + unlock_mapped_memory: bool, + ) -> Result { + let resources_names = AsmServices::SERVICES .iter() .map(|service| { let port = if let Some(base_port) = base_port { @@ -77,101 +86,42 @@ impl HintsShmem { } else { AsmServices::default_port(service, local_rank) }; - let sem_avail = Self::resource_name(service, port, local_rank, NameType::SemAvail); - let sem_read = Self::resource_name(service, port, local_rank, NameType::SemRead); - (sem_avail, sem_read) + ServiceResourceNames::new(service, port, local_rank) }) - .collect::>(); + .collect(); - Self { - shmem_names, - sem_names, - unlock_mapped_memory, - shmem_writers: Mutex::new(Vec::new()), - sem_control: Mutex::new(Vec::new()), - } - } + let resources = Mutex::new(Self::create_resources(resources_names, unlock_mapped_memory)?); - fn resource_name( - service: &AsmService, - port: u16, - local_rank: i32, - name_type: NameType, - ) -> String { - match name_type { - NameType::Control => { - AsmSharedMemory::::shmem_control_name(port, *service, local_rank) - } - NameType::Data => { - AsmSharedMemory::::shmem_precompile_name(port, *service, local_rank) - } - NameType::SemAvail => AsmSharedMemory::::shmem_semaphore_available_name( - port, *service, local_rank, - ), - NameType::SemRead => AsmSharedMemory::::shmem_semaphore_read_name( - port, *service, local_rank, - ), - } - } - - /// Check if the shared memory writers have been initialized. - fn is_initialized(&self) -> bool { - let shmem_writers = self.shmem_writers.lock().unwrap(); - !shmem_writers.is_empty() + Ok(Self { resources }) } /// Initialize the shared memory writers for the pipeline. /// /// This method creates SharedMemoryWriter instances for each shared memory name. /// If writers are already initialized it logs a warning and does nothing. - fn initialize(&self) -> Result<()> { - let mut shmem_writer = self.shmem_writers.lock().unwrap(); - let mut sem_control = self.sem_control.lock().unwrap(); - - // Initialize shared memory writers - if !shmem_writer.is_empty() { - return Err(anyhow::anyhow!( - "SharedMemoryWriters for precompile hints already initialized." - )); - } + fn create_resources( + resources_names: Vec, + unlock_mapped_memory: bool, + ) -> Result> { + debug!("Initializing resources for precompile hints"); - debug!("Initializing SharedMemoryWriter for precompile hints",); - *shmem_writer = self - .shmem_names + Ok(resources_names .iter() - .map(|(control_name, name)| { - ( - Self::create_writer( - control_name, - Self::CONTROL_PRECOMPILE_SIZE as usize, - self.unlock_mapped_memory, - ), - Self::create_writer( - name, - Self::MAX_PRECOMPILE_SIZE as usize, - self.unlock_mapped_memory, - ), - ) + .map(|names: &ServiceResourceNames| ServiceResources { + control_writer: Self::create_writer( + &names.control_name, + Self::CONTROL_PRECOMPILE_SIZE as usize, + unlock_mapped_memory, + ), + data_writer: Self::create_writer( + &names.data_name, + Self::MAX_PRECOMPILE_SIZE as usize, + unlock_mapped_memory, + ), + sem_available: Self::create_semaphore(&names.sem_available_name), + sem_read: Self::create_semaphore(&names.sem_read_name), }) - .collect(); - - // Initialize semaphores - if !sem_control.is_empty() { - return Err(anyhow::anyhow!( - "Control semaphores for precompile hints already initialized." - )); - } - - debug!("Initializing control semaphores for precompile hints",); - *sem_control = self - .sem_names - .iter() - .map(|(sem_avail_name, sem_read_name)| { - (Self::create_semaphore(sem_avail_name), Self::create_semaphore(sem_read_name)) - }) - .collect(); - - Ok(()) + .collect()) } /// Create a SharedMemoryWriter with error handling. @@ -186,12 +136,19 @@ impl HintsShmem { .expect("Failed to create semaphore for precompile hints") } - fn write_size(&self, writer: &SharedMemoryWriter) -> u64 { - writer.read_u64_at(0) + #[inline] + fn get_write_size(&self, writer: &SharedMemoryWriter) -> Result { + writer.read_u64_at(0).map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn set_write_size(&self, writer: &SharedMemoryWriter, size: u64) -> anyhow::Result<()> { + writer.write_u64_at(0, size).map_err(|e| anyhow::anyhow!(e)) } - fn read_size(&self, writer: &SharedMemoryWriter) -> u64 { - writer.read_u64_at(8) + #[inline] + fn get_read_size(&self, writer: &SharedMemoryWriter) -> Result { + writer.read_u64_at(4096).map_err(|e| anyhow::anyhow!(e)) } } @@ -205,22 +162,53 @@ impl HintsSink for HintsShmem { /// * `Ok(())` - If hints were successfully written to all shared memories /// * `Err` - If writing to any shared memory fails fn submit(&self, processed: Vec) -> anyhow::Result<()> { - // TODO! Is it necessary???? - if !self.is_initialized() { - self.initialize()?; + let data_size = processed.len() as u64; + + let mut resources = self.resources.lock().unwrap(); + + // for resource in resources.iter_mut() { + let resource = &mut resources[0]; + + // Read current positions + let write_pos = self.get_write_size(&resource.control_writer)?; + let read_pos = self.get_read_size(&resource.control_writer)?; + + // Calculate occupied space in ring buffer (positions are absolute values) + let occupied_space = write_pos - read_pos; + let available_space = (Self::MAX_PRECOMPILE_SIZE >> 3) - occupied_space; + + debug_assert!( + available_space <= (Self::MAX_PRECOMPILE_SIZE >> 3), + "Available space calculation error" + ); + // TODO! Check for overflow of write_pos and read_pos and handle it + + // Flow control based on buffer occupancy + if available_space < data_size { + // Not enough space - signal reader and wait for consumption + // resource.sem_available.post()?; + if write_pos > 131000 { + println!("Waiting on sem_read for precompile hints write_pos={} occupied={} available={} needed={}", + write_pos, occupied_space, available_space, data_size); + } + resource.sem_read.wait()?; + } else if available_space < Self::BUFFER_THRESHOLD { + // Buffer getting full - signal reader but don't wait + // resource.sem_available.post()?; } - // Input size includes length prefix as u64 - let shmem_input_size = processed.len(); + // Write data to shared memory with automatic wraparound + resource.data_writer.write_ring_buffer(&processed)?; - let mut full_input = Vec::with_capacity(shmem_input_size); - full_input.extend_from_slice(&processed); + // Update write position in control memory with wraparound + self.set_write_size(&resource.control_writer, write_pos + data_size)?; - let shmem_writers = self.shmem_writers.lock().unwrap(); - for shmem_writer in shmem_writers.iter() { - shmem_writer.1.write_input(&full_input)?; - shmem_writer.0.write_input(&[processed.len() as u64])?; - } + resource.sem_available.post()?; + + // if write_pos > 131000 { + // println!("Posted available semaphore for precompile hints {}", write_pos); + // } + // } Ok(()) } From ae642f976f0a1c884d9e20a276878415bab54b05 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Fri, 12 Dec 2025 12:15:48 +0100 Subject: [PATCH 059/782] WIP dma --- core/src/mem.rs | 163 ++++++++++++++++++++++ core/src/riscv2zisk_context.rs | 114 +++++++++++++-- core/src/zisk_inst.rs | 12 +- core/src/zisk_inst_builder.rs | 6 +- core/src/zisk_ops.rs | 118 +++++++++++++--- core/src/zisk_rom_2_asm.rs | 14 +- emulator/src/emu.rs | 56 ++++++-- emulator/src/stats.rs | 12 +- pil/src/pil_helpers/traces.rs | 4 +- pil/src/pil_helpers/traces_dev.rs | 2 +- precompiles/dma/src/dma_bus_device.rs | 3 +- precompiles/dma/src/dma_gen_mem_inputs.rs | 2 +- precompiles/dma/src/dma_instance.rs | 7 +- riscv/src/riscv_inst.rs | 2 +- state-machines/main/pil/main.pil | 10 +- ziskos/entrypoint/src/lib.rs | 58 ++++++++ ziskos/entrypoint/src/memcmp.s | 30 ++++ ziskos/entrypoint/src/syscalls/mod.rs | 14 +- ziskos/entrypoint/src/syscalls/syscall.rs | 2 + 19 files changed, 550 insertions(+), 79 deletions(-) create mode 100644 ziskos/entrypoint/src/memcmp.s diff --git a/core/src/mem.rs b/core/src/mem.rs index 0d4f7d711..157cdb2a6 100644 --- a/core/src/mem.rs +++ b/core/src/mem.rs @@ -795,5 +795,168 @@ impl Mem { } } + #[inline(always)] + pub fn get_writeable_section(&mut self, addr: u64, count: u64) -> &mut MemSection { + if let Ok(section) = self.read_sections.binary_search_by(|section| { + if addr < section.start { + std::cmp::Ordering::Greater + } else if addr > (section.end - count) { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Equal + } + }) { + panic!( + "Mem::get_write_section() invalid addr={addr}={addr:x},count={count} write section start={:x} end={:x} is read only section", + self.read_sections[section].start, self.read_sections[section].end); + }; + + // If not found in read sections, try write section + let section = &mut self.write_section; + + // Check that the address and count fall into this section address range + if (addr < section.start) || ((addr + count) > section.end) { + panic!( + "Mem::get_section() invalid addr={addr}={addr:x},count={count} write section start={:x} end={:x}", + section.start, section.end + ); + } + section + } + + #[inline(always)] + pub fn get_readable_section(&self, addr: u64, count: u64) -> &MemSection { + let section = if let Ok(section) = self.read_sections.binary_search_by(|section| { + if addr < section.start { + std::cmp::Ordering::Greater + } else if addr > (section.end - count) { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Equal + } + }) { + &self.read_sections[section] + } else { + &self.write_section + }; + if (addr < section.start) || ((addr + count) > section.end) { + panic!( + "Mem::get_read_section() invalid addr={addr}={addr:x},count={count} read section start={:x} end={:x}", + section.start, section.end + ); + } + section + } + + #[inline(always)] + pub fn memcpy(&mut self, dst: u64, src: u64, count: u64) { + // Early return if source and destination are the same or count is zero + if dst == src || count == 0 { + return; + } + + let dst_end = dst + count; + let src_end = src + count; + let count_usize = count as usize; + + // Check if there is an overlap between source and destination + let overlaps = (dst < src_end) && (src < dst_end); + + if overlaps { + // Overlapping case: use temporary buffer to avoid data corruption + let temp_buffer: Vec = { + let src_section = self.get_readable_section(src, count); + let src_offset: usize = (src - src_section.start) as usize; + src_section.buffer[src_offset..src_offset + count_usize].to_vec() + }; + + let dst_section = self.get_writeable_section(dst, count); + let dst_offset: usize = (dst - dst_section.start) as usize; + dst_section.buffer[dst_offset..dst_offset + count_usize].copy_from_slice(&temp_buffer); + } else { + // Non-overlapping case: direct copy + // First, get a copy of the source data + let data_to_copy: Vec = { + let src_section = self.get_readable_section(src, count); + let src_offset: usize = (src - src_section.start) as usize; + src_section.buffer[src_offset..src_offset + count_usize].to_vec() + }; + + // Then, write to destination + let dst_section = self.get_writeable_section(dst, count); + let dst_offset: usize = (dst - dst_section.start) as usize; + dst_section.buffer[dst_offset..dst_offset + count_usize].copy_from_slice(&data_to_copy); + } + } + + pub fn memcpy_from_data(&mut self, dst: u64, count: u64, data: &[u64], data_offset: usize) { + // Early return if source and destination are the same or count is zero + if count == 0 { + return; + } + + let data_bytes: &[u8] = + unsafe { core::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; + + // Then, write to destination + let dst_section = self.get_writeable_section(dst, count); + let dst_offset: usize = (dst - dst_section.start) as usize; + + let count = count as usize; + let bytes = &data_bytes[data_offset..data_offset + count]; + dst_section.buffer[dst_offset..dst_offset + count].copy_from_slice(bytes); + } + + pub fn memcmp(&self, a: u64, b: u64, count: u64) -> u64 { + if count == 0 { + return 0; + } + + let count_usize = count as usize; + + println!("memcmp dump A:{}", self.memdump(a, count)); + println!("memcmp dump B:{}", self.memdump(b, count)); + // Get sections for both addresses + let a_section = self.get_readable_section(a, count); + let b_section = self.get_readable_section(b, count); + + let a_offset: usize = (a - a_section.start) as usize; + let b_offset: usize = (b - b_section.start) as usize; + + // Compare byte by byte + for i in 0..count_usize { + let byte_a = a_section.buffer[a_offset + i] as i8; + let byte_b = b_section.buffer[b_offset + i] as i8; + + if byte_a != byte_b { + // Sign extend the difference to 64 bits + let diff = (byte_a as i64) - (byte_b as i64); + return diff as u64; + } + } + + // All bytes are equal + 0 + } + + pub fn memdump(&self, addr: u64, count: u64) -> String { + if count == 0 { + return String::new(); + } + + let count_usize = count as usize; + + // Get section for the address range + let section = self.get_readable_section(addr, count); + let offset: usize = (addr - section.start) as usize; + + // Convert bytes to hex string + section.buffer[offset..offset + count_usize] + .iter() + .map(|byte| format!("{:02x}", byte)) + .collect::>() + .join("") + } + //pub fn get_non_aligned_data_from_required(address: u64, width: u8,) } diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 5990343e5..cb3b92f57 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -14,7 +14,7 @@ use std::collections::HashMap; // The CSR precompiled addresses are defined in the `ZiskOS` `ziskos/entrypoint/src` files // because legacy versions of Rust do not support constant parameters in `asm!` macros. -const CSR_PRECOMPILED: [&str; 18] = [ +const CSR_PRECOMPILED: [&str; 20] = [ "keccak", "arith256", "arith256_mod", @@ -33,6 +33,8 @@ const CSR_PRECOMPILED: [&str; 18] = [ "bls12_381_complex_sub", "bls12_381_complex_mul", "add256", + "dma_memcpy", + "dma_memcmp", ]; const CSR_PRECOMPILED_ADDR_START: u32 = 0x800; const CSR_PRECOMPILED_ADDR_END: u32 = CSR_PRECOMPILED_ADDR_START + CSR_PRECOMPILED.len() as u32; @@ -61,17 +63,58 @@ impl Riscv2ZiskContext<'_> { /// Converts an input RISCV instruction into a ZisK instruction and stores it into the internal /// map. C instrucions are already expanded into their equivalent RISCV instructions, so we /// only have to map them to their corresponding IMA 32-bits equivalent instructions. - pub fn convert(&mut self, riscv_instruction: &RiscvInstruction) { + /// + /// # Parameters + /// * `riscv_instruction` - The current instruction to convert + /// * `next_instructions` - Slice of the remaining instructions after the current one + pub fn convert( + &mut self, + riscv_instruction: &RiscvInstruction, + next_instructions: &[RiscvInstruction], + ) { // ZisK supports the IMAC RISC-V instruction set + println!("CONVERT {} # {}", riscv_instruction.inst, riscv_instruction.to_text()); match riscv_instruction.inst.as_str() { // I: Base Integer Instruction Set ////////////////////////////////// // I.1. Integer Computational (Register-Register) "add" => { - if riscv_instruction.rs1 == 0 { - // rd = rs1(0) + rs2 = rs2 - self.copyb(riscv_instruction, 4, 2); + if riscv_instruction.rd == 0 + && next_instructions.len() > 0 + && next_instructions[0].inst == "csrrs" + && next_instructions[0].csr == 0x812 + { + println!( + "DETECTED INSTRUCTION MEMCPY '{}' + '{}' AT 0x{:08X}", + riscv_instruction.inst, + next_instructions[0].inst, + riscv_instruction.rom_address + ); + self.create_special_register_op(riscv_instruction, "dma_memcpy", 8); + } else if riscv_instruction.rd == 10 + && next_instructions.len() > 0 + && next_instructions[0].inst == "csrrs" + && next_instructions[0].csr == 0x813 + { + println!( + "DETECTED INSTRUCTION MEMCMP '{}' + '{}' AT 0x{:08X}", + riscv_instruction.inst, + next_instructions[0].inst, + riscv_instruction.rom_address + ); + self.create_special_register_op(riscv_instruction, "dma_memcmp", 8); + } else if riscv_instruction.rs1 == 0 { + // if next_instructions.len() > 0 && next_instructions[0].inst == "csrw" { + println!("Detected instruction pattern"); + if next_instructions.len() > 0 { + // rd = rs1(0) + rs2 = rs2 followed by ret + println!("Detected instruction pattern '{}'", next_instructions[0].inst); + self.copyb(riscv_instruction, 4, 2); + } else { + // rd = rs1(0) + rs2 = rs2 + self.copyb(riscv_instruction, 4, 2); + } } else if riscv_instruction.rs2 == 0 { // rd = rs1 + rs2(0) = rs1 self.copyb(riscv_instruction, 4, 1); @@ -589,6 +632,23 @@ impl Riscv2ZiskContext<'_> { self.insts.insert(i.rom_address, zib); } + /// Creates a Zisk operation that implements a RISC-V register operation, i.e. an operation that + /// loads both input parameters a and b from their respective registers, + /// and stores the result c into a register + pub fn create_special_register_op(&mut self, i: &RiscvInstruction, op: &str, inst_size: u64) { + // assert!(inst_size == 2 || inst_size == 4); + let mut zib = ZiskInstBuilder::new_from_riscv(i.rom_address, i.inst.clone()); + zib.src_a("reg", i.rs1 as u64, false); + zib.src_b("reg", i.rs2 as u64, false); + zib.op(op).unwrap(); + zib.store("reg", i.rd as i64, false, false); + zib.j(inst_size as i64, inst_size as i64); + zib.verbose(&format!("{} r{}, r{}, r{}", i.inst, i.rd, i.rs1, i.rs2)); + zib.build(); + println!("Special at 0x{:08X} op: {:?}", i.rom_address, zib); + self.insts.insert(i.rom_address, zib); + } + // beq rs1, rs2, label // eq([%rs1], [rs2]), j(label) @@ -767,7 +827,7 @@ impl Riscv2ZiskContext<'_> { zib.src_a("imm", 0, false); zib.src_b("imm", 0, false); zib.op("flag").unwrap(); - zib.store_ra("reg", i.rd as i64, false); + zib.store_pc("reg", i.rd as i64, false); zib.j(4, i.imm as i64); zib.verbose(&format!("auipc r{}, 0x{:x}", i.rd, i.imm)); zib.build(); @@ -889,7 +949,7 @@ impl Riscv2ZiskContext<'_> { zib.src_b("reg", i.rs1 as u64, false); zib.op("and").unwrap(); zib.set_pc(); - zib.store_ra("reg", i.rd as i64, false); + zib.store_pc("reg", i.rd as i64, false); zib.j(i.imm as i64, inst_size as i64); zib.verbose(&format!("jalr r{}, r{}, 0x{:x}", i.rd, i.rs1, i.imm)); zib.build(); @@ -912,7 +972,7 @@ impl Riscv2ZiskContext<'_> { zib.src_b("lastc", 0, false); zib.op("and").unwrap(); zib.set_pc(); - zib.store_ra("reg", i.rd as i64, false); + zib.store_pc("reg", i.rd as i64, false); zib.j(0, inst_size as i64 - 1); zib.verbose(&format!("jalr r{}, r{}, 0x{:x} ; 2/2", i.rd, i.rs1, i.imm)); zib.build(); @@ -930,7 +990,7 @@ impl Riscv2ZiskContext<'_> { zib.src_a("imm", 0, false); zib.src_b("imm", 0, false); zib.op("flag").unwrap(); - zib.store_ra("reg", i.rd as i64, false); + zib.store_pc("reg", i.rd as i64, false); zib.j(i.imm as i64, inst_size as i64); zib.verbose(&format!("jal r{}, 0x{:x}", i.rd, i.imm)); zib.build(); @@ -943,7 +1003,7 @@ impl Riscv2ZiskContext<'_> { zib.src_a("imm", 0, false); zib.src_b("mem", MTVEC, false); zib.op("copyb").unwrap(); - zib.store_ra("reg", 1, false); + zib.store_pc("reg", 1, false); zib.set_pc(); zib.j(0, 4); zib.verbose("ecall"); @@ -1698,13 +1758,41 @@ pub fn add_zisk_code(rom: &mut ZiskRom, addr: u64, data: &[u8]) { // Create a context to convert RISCV instructions to ZisK instructions, using rom.insts let mut ctx = Riscv2ZiskContext { insts: &mut rom.insts }; + // for (i, riscv_instruction) in riscv_instructions.iter().enumerate() { + // println!("RISCV#{i} 0x{:08X}", riscv_instruction.rom_address); + // } + // let zisk_memcmp_index = + // riscv_instructions.iter().position(|inst| inst.rom_address == 0x80236edc); + let zisk_memcmp_index: Option = None; + // For all RISCV instructions - for riscv_instruction in riscv_instructions { + for (i, riscv_instruction) in riscv_instructions.iter().enumerate() { //print!("add_zisk_code() converting RISCV instruction={}\n", // riscv_instruction.to_string()); + if riscv_instructions[i].rom_address >= 0x80267b28 + && riscv_instructions[i].rom_address <= 0x80267b30 + { + if let Some(zisk_memcmp_index) = zisk_memcmp_index { + // Get slice of remaining instructions after current one + let index_offset = (riscv_instructions[i].rom_address - 0x80267b28) as usize >> 2; + let next_instructions = + &riscv_instructions[(zisk_memcmp_index + index_offset + 1)..]; + + let mut instruction = riscv_instructions[zisk_memcmp_index + index_offset].clone(); + instruction.rom_address = riscv_instructions[i].rom_address; + + // Convert RICV instruction to ZisK instruction and store it in rom.insts + ctx.convert(&instruction, next_instructions); + continue; + //print!(" to: {}", ctx.insts.iter().last().) + } + } + + // Get slice of remaining instructions after current one + let next_instructions = &riscv_instructions[(i + 1)..]; // Convert RICV instruction to ZisK instruction and store it in rom.insts - ctx.convert(&riscv_instruction); + ctx.convert(riscv_instruction, next_instructions); //print!(" to: {}", ctx.insts.iter().last().) } } @@ -1901,7 +1989,7 @@ pub fn add_entry_exit_jmp(rom: &mut ZiskRom, addr: u64) { zib.src_b("imm", addr, false); zib.op("copyb").unwrap(); zib.set_pc(); - zib.store_ra("reg", 1, false); + zib.store_pc("reg", 1, false); zib.j(0, 4); zib.verbose(&format!("CALL to entry: 0x{addr:08x}")); zib.build(); diff --git a/core/src/zisk_inst.rs b/core/src/zisk_inst.rs index 95755064c..6087b3d9b 100644 --- a/core/src/zisk_inst.rs +++ b/core/src/zisk_inst.rs @@ -82,7 +82,7 @@ pub enum ZiskOperationType { ArithEq, ArithEq384, BigInt, // Note: Add new core operations here - Dma, + Dma, // Note: To add extra params to precompiles calls // ZisK Free Input Operations FcallParam, Fcall, @@ -114,7 +114,7 @@ pub const DMA_OP_TYPE_ID: u32 = ZiskOperationType::Dma as u32; #[derive(Debug, Clone)] pub struct ZiskInst { pub paddr: u64, - pub store_ra: bool, + pub store_pc: bool, pub store_use_sp: bool, pub store: u64, pub store_offset: i64, @@ -152,7 +152,7 @@ impl Default for ZiskInst { fn default() -> Self { Self { paddr: 0, - store_ra: false, + store_pc: false, store_use_sp: false, store: 0, store_offset: 0, @@ -223,8 +223,8 @@ impl ZiskInst { if self.store_offset != 0 { s += &format!(" store_offset=0x{:x}", self.store_offset as u64); } - if self.store_ra { - s += &format!(" store_ra={}", self.store_ra); + if self.store_pc { + s += &format!(" store_pc={}", self.store_pc); } if self.store_use_sp { s += &format!(" store_use_sp={}", self.store_use_sp); @@ -271,7 +271,7 @@ impl ZiskInst { | (((self.b_src == SRC_IMM) as u64) << 4) | (((self.b_src == SRC_MEM) as u64) << 5) | ((self.is_external_op as u64) << 6) - | ((self.store_ra as u64) << 7) + | ((self.store_pc as u64) << 7) | (((self.store == STORE_MEM) as u64) << 8) | (((self.store == STORE_IND) as u64) << 9) | ((self.set_pc as u64) << 10) diff --git a/core/src/zisk_inst_builder.rs b/core/src/zisk_inst_builder.rs index f14a19ba3..615e5dc69 100644 --- a/core/src/zisk_inst_builder.rs +++ b/core/src/zisk_inst_builder.rs @@ -158,7 +158,7 @@ impl ZiskInstBuilder { } /// Sets the c store instruction attributes - pub fn store(&mut self, dst_input: &str, offset_input: i64, use_sp: bool, store_ra: bool) { + pub fn store(&mut self, dst_input: &str, offset_input: i64, use_sp: bool, store_pc: bool) { let mut dst = dst_input; let mut offset = offset_input; if dst == "reg" { @@ -170,7 +170,7 @@ impl ZiskInstBuilder { } } - self.i.store_ra = store_ra; + self.i.store_pc = store_pc; self.i.store = self.c_store(dst); if self.i.store == STORE_REG || self.i.store == STORE_MEM || self.i.store == STORE_IND { @@ -183,7 +183,7 @@ impl ZiskInstBuilder { } /// Set the store as a store ra - pub fn store_ra(&mut self, dst: &str, offset: i64, use_sp: bool) { + pub fn store_pc(&mut self, dst: &str, offset: i64, use_sp: bool) { self.store(dst, offset, use_sp, true); } diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 16d700c74..14a76650f 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -388,6 +388,8 @@ define_ops! { (RemuW, "remu_w", ArithA32, ARITHA32_COST, 0xbd, 0, 0, opc_remu_w, op_remu_w, ops_none), (DivW, "div_w", ArithA32, ARITHA32_COST, 0xbe, 0, 0, opc_div_w, op_div_w, ops_none), (RemW, "rem_w", ArithA32, ARITHA32_COST, 0xbf, 0, 0, opc_rem_w, op_rem_w, ops_none), + (DmaMemCpy, "dma_memcpy", Dma, 0, 0xd0, 208, 96, opc_dma_memcpy, op_dma_memcpy, ops_dma_memcpy), + (DmaMemCmp, "dma_memcmp", Dma, 0, 0xd1, 208, 96, opc_dma_memcmp, op_dma_memcmp, ops_dma_memcmp), (Arith384Mod, "arith384_mod", ArithEq384, ARITH_EQ_384_COST, 0xe2, 232, 48, opc_arith384_mod, op_arith384_mod, ops_arith384_mod), (Bls12_381CurveAdd, "bls12_381_curve_add", ArithEq384, ARITH_EQ_384_COST, 0xe3, 208, 96, opc_bls12_381_curve_add, op_bls12_381_curve_add, ops_bls12_381_curve_add), (Bls12_381CurveDbl, "bls12_381_curve_dbl", ArithEq384, ARITH_EQ_384_COST, 0xe4, 96, 96, opc_bls12_381_curve_dbl, op_bls12_381_curve_dbl, ops_bls12_381_curve_dbl), @@ -410,8 +412,6 @@ define_ops! { (Bn254ComplexSub, "bn254_complex_sub", ArithEq, ARITH_EQ_COST, 0xfd, 144, 64, opc_bn254_complex_sub, op_bn254_complex_sub, ops_bn254_complex_sub), (Bn254ComplexMul, "bn254_complex_mul", ArithEq, ARITH_EQ_COST, 0xfe, 144, 64, opc_bn254_complex_mul, op_bn254_complex_mul, ops_bn254_complex_mul), (Halt, "halt", Internal, INTERNAL_COST, 0xff, 144, 0, opc_halt, op_halt, ops_none), - (DmaMemCmp, "dma_mem_cmp", Dma, 0, 0xee, 208, 96, opc_dma_memcmp, op_dma_memcmp, ops_dma_memcmp), - (DmaMemCpy, "dma_mem_cpy", Dma, 0, 0xef, 208, 96, opc_dma_memcpy, op_dma_memcpy, ops_dma_memcpy), } /* INTERNAL operations */ @@ -2275,6 +2275,96 @@ pub fn opc_halt(ctx: &mut InstContext) { } pub fn opc_dma_memcpy(ctx: &mut InstContext) { + /* let dst = ctx.a; + let src = ctx.b; + let count = ctx.regs[12]; + + if let EmulationMode::ConsumeMemReads = ctx.emulation_mode { + // copy from data + let src64 = src & !0x07; + let data_offset = src - src64; + + // execute from precompile.input_data + // TODO: avoid copy data + ctx.mem.memcpy_from_data(src, count, &ctx.precompiled.input_data, data_offset as usize); + + // Read data from the precompiled context + for (i, d) in data.iter_mut().enumerate() { + *d = ctx.precompiled.input_data[i]; + } + // Write the input data address to the precompiled context + // ctx.precompiled.input_data_address = address; + return; + } + + // Write the indirections to data + for (i, data) in data.iter_mut().enumerate().take(params_count) { + let indirection = ctx.mem.read(address + (8 * i as u64), 8); + if address & 0x7 != 0 { + panic!("precompiled_check_address() found address[{i}] not aligned to 8 bytes"); + } + *data = indirection; + } + + let mut data_offset = params_count; + for i in 0..load_indirections { + let data_offset = i * load_chunks + data_offset; + // if there aren't indirections, take directly from the address + let param_address = if params_count == 0 { address + data_offset as u64 } else { data[i] }; + for j in 0..load_chunks { + let addr = param_address + (8 * j as u64); + data[data_offset + j] = ctx.mem.read(addr, 8); + } + } + + // Process the remanent of the last chunk + if load_rem > 0 { + data_offset += (load_indirections - 1) * load_chunks; + let param_address = if params_count == 0 { + address + data_offset as u64 + } else { + data[load_indirections - 1] + }; + for j in load_chunks..load_chunks + load_rem { + let addr = param_address + (8 * j as u64); + data[data_offset + j] = ctx.mem.read(addr, 8); + } + } + + if let EmulationMode::GenerateMemReads = ctx.emulation_mode { + ctx.precompiled.input_data.clear(); + for (i, d) in data.iter_mut().enumerate() { + ctx.precompiled.input_data.push(*d); + } + ctx.precompiled.step = ctx.step; + } + */ + println!( + "opc_dma_memcpy 0x{:08X} 0x{:08X} A0-A2:[0x{:08X},0x{:08X},{}] {:?}", + ctx.a, ctx.b, ctx.regs[10], ctx.regs[11], ctx.regs[12], ctx.emulation_mode + ); + ctx.mem.memcpy(ctx.a, ctx.b, ctx.regs[12]); + ctx.c = ctx.a; + ctx.flag = false; +} + +/// Unimplemented. Arith256 can only be called from the system call context via InstContext. +/// This is provided just for completeness. +#[inline(always)] +pub fn op_dma_memcpy(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_dma_memcpy() is not implemented"); +} + +#[inline(always)] +pub fn ops_dma_memcpy(ctx: &InstContext, stats: &mut dyn OpStats) { + // unimplemented!("ops_dma_memcpy() is not implemented"); +} + +pub fn opc_dma_memcmp(ctx: &mut InstContext) { + println!( + "opc_dma_memcmp 0x{:08X} 0x{:08X} A0-A2:[0x{:08X},0x{:08X},{}] {:?}", + ctx.a, ctx.b, ctx.regs[10], ctx.regs[11], ctx.regs[12], ctx.emulation_mode + ); const WORDS: usize = 4 + 1 + 2 * 4; let mut data = [0u64; WORDS]; @@ -2310,22 +2400,12 @@ pub fn opc_dma_memcpy(ctx: &mut InstContext) { ctx.c = data[4 + 2 * 4]; ctx.flag = data[4 + 2 * 4] != 0; } -} - -/// Unimplemented. Arith256 can only be called from the system call context via InstContext. -/// This is provided just for completeness. -#[inline(always)] -pub fn op_dma_memcpy(_a: u64, _b: u64) -> (u64, bool) { - unimplemented!("op_dma_memcpy() is not implemented"); -} - -#[inline(always)] -pub fn ops_dma_memcpy(ctx: &InstContext, stats: &mut dyn OpStats) { - unimplemented!("ops_dma_memcpy() is not implemented"); -} - -pub fn opc_dma_memcmp(ctx: &mut InstContext) { - unimplemented!("opc_dma_memcmp() is not implemented"); + ctx.c = ctx.mem.memcmp(ctx.a, ctx.b, ctx.regs[12]); + println!( + "opc_dma_memcmp 0x{:08X} 0x{:08X} = 0x{:016X} A0-A2:[0x{:08X},0x{:08X},{}] {:?}", + ctx.a, ctx.b, ctx.c, ctx.regs[10], ctx.regs[11], ctx.regs[12], ctx.emulation_mode + ); + ctx.flag = false; } /// Unimplemented. Arith256 can only be called from the system call context via InstContext. @@ -2337,5 +2417,5 @@ pub fn op_dma_memcmp(_a: u64, _b: u64) -> (u64, bool) { #[inline(always)] pub fn ops_dma_memcmp(ctx: &InstContext, stats: &mut dyn OpStats) { - unimplemented!("ops_dma_memcmp() is not implemented"); + // unimplemented!("ops_dma_memcmp() is not implemented"); } diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index b42c3288f..210b75a48 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -2406,7 +2406,7 @@ impl ZiskRom2Asm { .full_line_comment(format!("STORE_REG reg={}", instruction.store_offset)); // Store in mem[address] - if instruction.store_ra { + if instruction.store_pc { let value = (ctx.pc as i64 + instruction.jmp_offset2) as u64; Self::write_riscv_reg_constant( &mut ctx, @@ -2530,7 +2530,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov {}, 0x{:x} {}\n", REG_VALUE, @@ -2952,7 +2952,7 @@ impl ZiskRom2Asm { { match instruction.ind_width { 8 => { - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov qword {}[{}], {} {}\n", ctx.ptr, @@ -2970,7 +2970,7 @@ impl ZiskRom2Asm { } } 4 => { - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov dword {}[{}], {} {}\n", ctx.ptr, @@ -2988,7 +2988,7 @@ impl ZiskRom2Asm { } } 2 => { - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov word {}[{}], {} {}\n", ctx.ptr, @@ -3006,7 +3006,7 @@ impl ZiskRom2Asm { } } 1 => { - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov word {}[{}], {} {}\n", ctx.ptr, @@ -3041,7 +3041,7 @@ impl ZiskRom2Asm { ctx.pc, ctx.comment_str("width=1: continue") ); - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov dil, 0x{:x} {}\n", (ctx.pc as i64 + instruction.jmp_offset2) as u64 as u8, diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index ab7790a35..650f10c48 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -1015,7 +1015,7 @@ impl<'a> Emu<'a> { } STORE_IND => { // Calculate value - let val: i64 = if instruction.store_ra { + let val: i64 = if instruction.store_pc { self.ctx.inst_ctx.pc as i64 + instruction.jmp_offset2 } else { self.ctx.inst_ctx.c as i64 @@ -1624,6 +1624,41 @@ impl<'a> Emu<'a> { // While not done while !self.ctx.inst_ctx.end { + if self.ctx.inst_ctx.pc == 0x802681d4 { + println!( + "PC: 0x{:08x} memcmp call (0x{:08x}, 0x{:08x}, {})", + self.ctx.inst_ctx.pc, + self.ctx.inst_ctx.regs[10], + self.ctx.inst_ctx.regs[11], + self.ctx.inst_ctx.regs[12] + ); + println!( + "memcmp dump A:{}", + self.ctx + .inst_ctx + .mem + .memdump(self.ctx.inst_ctx.regs[10], self.ctx.inst_ctx.regs[12]) + ); + println!( + "memcmp dump B:{}", + self.ctx + .inst_ctx + .mem + .memdump(self.ctx.inst_ctx.regs[11], self.ctx.inst_ctx.regs[12]) + ); + } + if self.ctx.inst_ctx.pc == 0x802681f8 { + println!( + "PC: 0x{:08x} memcmp equal 0x{:016X}", + self.ctx.inst_ctx.pc, self.ctx.inst_ctx.regs[10] + ); + } + if self.ctx.inst_ctx.pc == 0x80268200 { + println!( + "PC: 0x{:08x} memcmp different 0x{:016X}", + self.ctx.inst_ctx.pc, self.ctx.inst_ctx.regs[10] + ); + } if options.verbose { println!( "Emu::run() step={} ctx.pc={}", @@ -1823,13 +1858,14 @@ impl<'a> Emu<'a> { let pc = self.ctx.inst_ctx.pc; let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); - // println!( - // "Emu::step() executing step={} pc={:x} inst={}", - // self.ctx.inst_ctx.step, - // self.ctx.inst_ctx.pc, - // instruction.to_text() - // ); - + let pc = self.ctx.inst_ctx.pc; + if pc >= 0x80265b8c && pc <= 0x80265b98 { + println!( + "Emu::step() executing step={} pc={pc:x} inst={}", + self.ctx.inst_ctx.step, + instruction.to_text() + ); + } //println!("PCLOG={}", instruction.to_text()); // Build the 'a' register value based on the source specified by the current instruction @@ -2402,7 +2438,7 @@ impl<'a> Emu<'a> { inst.op }, ); - trace.set_store_ra(inst.store_ra); + trace.set_store_pc(inst.store_pc); trace.set_store_mem(inst.store == STORE_MEM); trace.set_store_reg(inst.store == STORE_REG); trace.set_store_ind(inst.store == STORE_IND); @@ -2526,7 +2562,7 @@ impl<'a> Emu<'a> { #[inline(always)] pub fn get_value_to_store(&self, instruction: &ZiskInst) -> u64 { - if instruction.store_ra { + if instruction.store_pc { (self.ctx.inst_ctx.pc as i64 + instruction.jmp_offset2) as u64 } else { self.ctx.inst_ctx.c diff --git a/emulator/src/stats.rs b/emulator/src/stats.rs index 127f6437e..6df0b0147 100644 --- a/emulator/src/stats.rs +++ b/emulator/src/stats.rs @@ -449,14 +449,14 @@ impl Stats { self.previous_pc = pc; self.previous_verbose = instruction.verbose.clone(); if instruction.set_pc { - // CALL: set_pc=true, store_ra=true, store_offset=1 (stores PC+4 or PC+2 in ra) - // self.is_call = instruction.store_ra && instruction.store_offset == 1; - self.is_call = instruction.store_ra; + // CALL: set_pc=true, store_pc=true, store_offset=1 (stores PC+4 or PC+2 in ra) + // self.is_call = instruction.store_pc && instruction.store_offset == 1; + self.is_call = instruction.store_pc; self.call_return_reg = if self.is_call { instruction.store_offset as u8 } else { 0 }; - // RETURN: set_pc=true, store_ra=false (no stores RA), b_src=SRC_REG, b_offset_imm0=1 (jumps to ra/x1) + // RETURN: set_pc=true, store_pc=false (no stores RA), b_src=SRC_REG, b_offset_imm0=1 (jumps to ra/x1) // Additionally, verify that the target PC matches the expected return address from the call stack - let is_jalr_ra = !instruction.store_ra + let is_jalr_ra = !instruction.store_pc && instruction.b_src == SRC_REG && instruction.b_offset_imm0 == 1; @@ -470,7 +470,7 @@ impl Stats { self.is_return = false; } } else if let Some(top) = self.call_stack.last() { - self.is_return = !instruction.store_ra + self.is_return = !instruction.store_pc && instruction.b_src == SRC_REG && instruction.b_offset_imm0 == top.return_reg as u64; } else { diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index f32bf3bdf..2be142088 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "8fd1a573bca203f3459cd96b0a3b317861a7f8736958da14cc0c8f8314050ad2"; +pub const PILOUT_HASH: &str = "b43b0a9602c2f7aae31d49817972ddde0b08eaf9ac20d3fa0975f58fb093d52b"; //AIRGROUP CONSTANTS @@ -178,7 +178,7 @@ trace_row!(MainFixedRow { pub type MainFixed = GenericTrace, 4194304, 0, 4>; trace_row!(MainTraceRow { - a:[u32; 2], b:[u32; 2], c:[u32; 2], flag:bit, pc:u32, a_src_imm:bit, a_src_mem:bit, a_offset_imm0:u64, a_imm1:u32, op_with_step:bit, b_src_imm:bit, b_src_mem:bit, b_offset_imm0:u64, b_imm1:u32, b_src_ind:bit, ind_width:ubit(4), is_external_op:bit, op:u8, store_ra:bit, store_mem:bit, store_ind:bit, store_offset:u64, set_pc:bit, jmp_offset1:u64, jmp_offset2:u64, m32:bit, addr1:u32, a_reg_prev_mem_step:ubit(40), b_reg_prev_mem_step:ubit(40), store_reg_prev_mem_step:ubit(40), store_reg_prev_value:[u32; 2], a_src_reg:bit, b_src_reg:bit, store_reg:bit, + a:[u32; 2], b:[u32; 2], c:[u32; 2], flag:bit, pc:u32, a_src_imm:bit, a_src_mem:bit, a_offset_imm0:u64, a_imm1:u32, op_with_step:bit, b_src_imm:bit, b_src_mem:bit, b_offset_imm0:u64, b_imm1:u32, b_src_ind:bit, ind_width:ubit(4), is_external_op:bit, op:u8, store_pc:bit, store_mem:bit, store_ind:bit, store_offset:u64, set_pc:bit, jmp_offset1:u64, jmp_offset2:u64, m32:bit, addr1:u32, a_reg_prev_mem_step:ubit(40), b_reg_prev_mem_step:ubit(40), store_reg_prev_mem_step:ubit(40), store_reg_prev_value:[u32; 2], a_src_reg:bit, b_src_reg:bit, store_reg:bit, }); pub type MainTrace = GenericTrace, 4194304, 0, 4>; diff --git a/pil/src/pil_helpers/traces_dev.rs b/pil/src/pil_helpers/traces_dev.rs index 3754c7955..a04f6d83f 100644 --- a/pil/src/pil_helpers/traces_dev.rs +++ b/pil/src/pil_helpers/traces_dev.rs @@ -108,7 +108,7 @@ trace!(MainFixed { }, 0, 0, 4194304 ); trace!(MainTrace { - a: [F; 2], b: [F; 2], c: [F; 2], flag: F, pc: F, a_src_imm: F, a_src_mem: F, a_offset_imm0: F, a_imm1: F, a_src_step: F, b_src_imm: F, b_src_mem: F, b_offset_imm0: F, b_imm1: F, b_src_ind: F, ind_width: F, is_external_op: F, op: F, store_ra: F, store_mem: F, store_ind: F, store_offset: F, set_pc: F, jmp_offset1: F, jmp_offset2: F, m32: F, addr1: F, a_reg_prev_mem_step: F, b_reg_prev_mem_step: F, store_reg_prev_mem_step: F, store_reg_prev_value: [F; 2], a_src_reg: F, b_src_reg: F, store_reg: F, + a: [F; 2], b: [F; 2], c: [F; 2], flag: F, pc: F, a_src_imm: F, a_src_mem: F, a_offset_imm0: F, a_imm1: F, a_src_step: F, b_src_imm: F, b_src_mem: F, b_offset_imm0: F, b_imm1: F, b_src_ind: F, ind_width: F, is_external_op: F, op: F, store_pc: F, store_mem: F, store_ind: F, store_offset: F, set_pc: F, jmp_offset1: F, jmp_offset2: F, m32: F, addr1: F, a_reg_prev_mem_step: F, b_reg_prev_mem_step: F, store_reg_prev_mem_step: F, store_reg_prev_value: [F; 2], a_src_reg: F, b_src_reg: F, store_reg: F, }, 0, 0, 4194304 ); trace!(RomFixed { diff --git a/precompiles/dma/src/dma_bus_device.rs b/precompiles/dma/src/dma_bus_device.rs index 732c30481..5ae0dd27a 100644 --- a/precompiles/dma/src/dma_bus_device.rs +++ b/precompiles/dma/src/dma_bus_device.rs @@ -133,7 +133,8 @@ impl BusDevice for DmaCounterInputGen { &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, + _data_ext: &[u64], + pending: &mut VecDeque<(BusId, Vec, Vec)>, mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/precompiles/dma/src/dma_gen_mem_inputs.rs b/precompiles/dma/src/dma_gen_mem_inputs.rs index 61b839809..17fb6dcb3 100644 --- a/precompiles/dma/src/dma_gen_mem_inputs.rs +++ b/precompiles/dma/src/dma_gen_mem_inputs.rs @@ -39,7 +39,7 @@ pub fn generate_dma_mem_inputs( _data: &[u64], _data_ext: &[u64], _only_counters: bool, - _pending: &mut VecDeque<(BusId, Vec)>, + _pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { let from_src = src & !0x07; let to_src = (src + count as u64 - 1) & MASK_ALIGNED_ADDR; diff --git a/precompiles/dma/src/dma_instance.rs b/precompiles/dma/src/dma_instance.rs index 6f40502f4..80fdf734d 100644 --- a/precompiles/dma/src/dma_instance.rs +++ b/precompiles/dma/src/dma_instance.rs @@ -151,7 +151,7 @@ impl DmaCollector { } } -impl BusDevice for DmaCollector { +impl BusDevice for DmaCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -167,8 +167,9 @@ impl BusDevice for DmaCollector { fn process_data( &mut self, bus_id: &BusId, - data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, + data: &[u64], + _data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); diff --git a/riscv/src/riscv_inst.rs b/riscv/src/riscv_inst.rs index 6fdacdafd..679ea33dd 100644 --- a/riscv/src/riscv_inst.rs +++ b/riscv/src/riscv_inst.rs @@ -33,7 +33,7 @@ //! See /// RISC-V instruction data -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct RiscvInstruction { /// Instruction ROM address, i.e. program counter value pub rom_address: u64, diff --git a/state-machines/main/pil/main.pil b/state-machines/main/pil/main.pil index 0f646caf9..49734c646 100644 --- a/state-machines/main/pil/main.pil +++ b/state-machines/main/pil/main.pil @@ -140,7 +140,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, // Destination C - col witness bits(1) store_ra; + col witness bits(1) store_pc; col witness bits(1) store_mem; @@ -307,8 +307,8 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, const expr store_value[2]; - store_value[0] = store_ra*(pc + jmp_offset2 - c[0]) + c[0]; - store_value[1] = (1 - store_ra) * c[1]; + store_value[0] = store_pc*(pc + jmp_offset2 - c[0]) + c[0]; + store_value[1] = (1 - store_pc) * c[1]; // Memory function to prove that previous register store_offset access. @@ -458,7 +458,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, b_src_imm * (1 - b_src_imm) === 0; b_src_mem * (1 - b_src_mem) === 0; is_external_op * (1 - is_external_op) === 0; - store_ra * (1 - store_ra) === 0; + store_pc * (1 - store_pc) === 0; store_mem * (1 - store_mem) === 0; store_ind * (1 - store_ind) === 0; set_pc * (1 - set_pc) === 0; @@ -469,7 +469,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, store_reg * (1 - store_reg) === 0; const expr rom_flags = 1 + 2 * a_src_imm + 4 * a_src_mem + 8 * op_with_step + 16 * b_src_imm - + 32 * b_src_mem + 64 * is_external_op + 128 * store_ra + 256 * store_mem + + 32 * b_src_mem + 64 * is_external_op + 128 * store_pc + 256 * store_mem + 512 * store_ind + 1024 * set_pc + 2048 * m32 + 4096 * b_src_ind + 8192 * a_src_reg + 16384 * b_src_reg + 32768 * store_reg; diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index ae6ae0903..c6aabf63b 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -286,4 +286,62 @@ mod ziskos { ptr } + #[no_mangle] + #[inline(never)] + #[export_name = "memcpy"] + pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 { + asm!( + ".insn r 0x33, 0, 0, x0, a0, a1 ", + // "csrs 0x812, a0", // Marker: Write count (a0) to CSR 0x812 + // "csrs 0x813, a1", // Marker: Write count (a1) to CSR 0x812 + "csrs 0x812, a2", // Marker: Write count (a2) to CSR 0x812 + in("a0") dest, // Force dest parameter into register a0 + in("a1") src, // Force src parameter into register a1 + in("a2") count, // Force count parameter into register a2 + options(preserves_flags) + ); + + dest + } + #[no_mangle] + #[inline(never)] + #[export_name = "memmove"] + pub unsafe extern "C" fn memmove(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 { + asm!( + ".insn r 0x33, 0, 0, x0, a0, a1 ", + // "csrs 0x812, a0", // Marker: Write count (a0) to CSR 0x812 + // "csrs 0x813, a1", // Marker: Write count (a1) to CSR 0x812 + "csrs 0x812, a2", // Marker: Write count (a2) to CSR 0x812 + in("a0") dest, // Force dest parameter into register a0 + in("a1") src, // Force src parameter into register a1 + in("a2") count, // Force count parameter into register a2 + options(preserves_flags) + ); + + dest + } + // #[no_mangle] + // #[inline(always)] + // #[export_name = "memcmp"] + // pub unsafe extern "C" fn memcmp(a: *const u8, b: *const u8, count: usize) -> i64 { + // let result: i64; + // asm!( + // ".insn r 0x33, 0, 0, a0, a0, a1 ", + // // "csrs 0x812, a0", // Marker: Write count (a0) to CSR 0x812 + // // "csrs 0x813, a1", // Marker: Write count (a1) to CSR 0x812 + // "csrs 0x813, a2", // Marker: Write count (a2) to CSR 0x812 + // in("a0") a, // Force dest parameter into register a0 + // in("a1") b, // Force src parameter into register a1 + // in("a2") count, // Force count parameter into register a2 + // lateout("a0") result, + // options(preserves_flags) + // ); + // result + // } + core::arch::global_asm!(include_str!("memcmp.s")); + // core::arch::global_asm!(include_str!("memcpy.s")); + // core::arch::global_asm!(include_str!("memset.s")); + // #[used] + // pub static KEEP_FUNCTIONS: &[unsafe extern "C" fn(*const u8, *const u8, usize) -> i64] = + // &[zisk_memcmp]; } diff --git a/ziskos/entrypoint/src/memcmp.s b/ziskos/entrypoint/src/memcmp.s new file mode 100644 index 000000000..c97dab76b --- /dev/null +++ b/ziskos/entrypoint/src/memcmp.s @@ -0,0 +1,30 @@ + .section ".note.GNU-stack","",@progbits + .text + .attribute 4, 16 + .attribute 5, "rv64im" + .globl memcmp + .p2align 4 + .type memcmp,@function +memcmp: + add a0,a0,a1 + .insn 4, 0x81362073 + ret +/* + beqz a2, .memcmp_eq + .memcmp_loop: + lbu a3,0(a0) + lbu a4,0(a1) + bne a3,a4, .memcmp_neq + addi a2,a2,-1 + addi a1,a1,1 + addi a0,a0,1 + bnez a2, .memcmp_loop + .memcmp_eq: + li a0,0 + ret + .memcmp_neq: + sub a0,a3,a4 + ret +*/ + .size memcmp, .-memcmp + .section .text.hot,"ax",@progbits \ No newline at end of file diff --git a/ziskos/entrypoint/src/syscalls/mod.rs b/ziskos/entrypoint/src/syscalls/mod.rs index d38ccbfc4..ca143ec87 100644 --- a/ziskos/entrypoint/src/syscalls/mod.rs +++ b/ziskos/entrypoint/src/syscalls/mod.rs @@ -52,6 +52,18 @@ macro_rules! ziskos_syscall { ); } }}; + ($csr_addr:literal, $arg0:expr, $arg1:expr, $arg2: expr) => {{ + unsafe { + asm!( + concat!("csrs ", stringify!($csr_addr), ", {0}"), + "add x0, {1}, {2}", + in(reg) $arg0, // {0} + in(reg) $arg1, // {1} + in(reg) $arg2, // {2} + options(nostack) + ); + } + }}; } #[macro_export] @@ -63,7 +75,7 @@ macro_rules! ziskos_syscall_ret_u64 { concat!("csrrs {0}, ", stringify!($csr_addr), ", {1}"), out(reg) v, in(reg) $addr, - options(nostack, nomem) + options(nostack) ); } v diff --git a/ziskos/entrypoint/src/syscalls/syscall.rs b/ziskos/entrypoint/src/syscalls/syscall.rs index da2835318..bcf553438 100644 --- a/ziskos/entrypoint/src/syscalls/syscall.rs +++ b/ziskos/entrypoint/src/syscalls/syscall.rs @@ -18,3 +18,5 @@ pub const SYSCALL_BLS12_381_COMPLEX_ADD_ID: u16 = 0x80E; pub const SYSCALL_BLS12_381_COMPLEX_SUB_ID: u16 = 0x80F; pub const SYSCALL_BLS12_381_COMPLEX_MUL_ID: u16 = 0x810; pub const SYSCALL_ADD256_ID: u16 = 0x811; +pub const SYSCALL_DMA_MEMCPY_ID: u16 = 0x812; +pub const SYSCALL_DMA_MEMCMP_ID: u16 = 0x813; From 49129c48afd4bc9170299b21bbc19b4a94d65a5e Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 12 Dec 2025 13:16:34 +0100 Subject: [PATCH 060/782] Fix one-chunk precompile write after starting --- emulator-asm/src/main.c | 273 ++++++++++++++++------------------------ 1 file changed, 108 insertions(+), 165 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index ab7942f30..44bd92d95 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -1726,9 +1726,6 @@ void client_setup (void) if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) { - // Make sure the precompile results shared memory is deleted - //shm_unlink(shmem_precompile_name); - // Create the precompile results shared memory shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); if (shmem_precompile_fd < 0) @@ -1755,6 +1752,8 @@ void client_setup (void) exit(-1); } precompile_results_address = (uint64_t *)pPrecompile; + shmem_precompile_address = precompile_results_address; + if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); // Create the control shared memory @@ -1797,9 +1796,7 @@ void client_setup (void) // Create the semaphore for precompile results available signal assert(strlen(sem_prec_avail_name) > 0); - sem_unlink(sem_prec_avail_name); - - sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); + sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT, 0666, 0); if (sem_prec_avail == SEM_FAILED) { printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); @@ -1812,9 +1809,7 @@ void client_setup (void) // Create the semaphore for precompile results read signal assert(strlen(sem_prec_read_name) > 0); - sem_unlink(sem_prec_read_name); - - sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); + sem_prec_read = sem_open(sem_prec_read_name, O_CREAT, 0666, 0); if (sem_prec_read == SEM_FAILED) { printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); @@ -1826,6 +1821,73 @@ void client_setup (void) } } +void client_write_precompile_results (void) +{ + int result; + +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + + // Open input file + FILE * precompile_fp = fopen(precompile_file_name, "r"); + if (precompile_fp == NULL) + { + printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Get input file size + if (fseek(precompile_fp, 0, SEEK_END) == -1) + { + printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + long precompile_data_size = ftell(precompile_fp); + if (precompile_data_size == -1) + { + printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Go back to the first byte + if (fseek(precompile_fp, 0, SEEK_SET) == -1) + { + printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Copy input data into input memory + size_t precompile_read = fread(shmem_precompile_address, 1, precompile_data_size, precompile_fp); + if (precompile_read != precompile_data_size) + { + printf("ERROR: Input read (%lu) != input file size (%lu)\n", precompile_read, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Close the file pointer + fclose(precompile_fp); + + *precompile_written_address = (uint64_t)precompile_data_size; + sem_post(sem_prec_avail); + +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + printf("client (precompile): done in %lu us\n", duration); +#endif +} + void client_run (void) { assert(client); @@ -1948,138 +2010,10 @@ void client_run (void) /*****************************/ if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) { + // reset written counter + *precompile_written_address = 0; -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - - // Open input file - FILE * precompile_fp = fopen(precompile_file_name, "r"); - if (precompile_fp == NULL) - { - printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Get input file size - if (fseek(precompile_fp, 0, SEEK_END) == -1) - { - printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - long precompile_data_size = ftell(precompile_fp); - if (precompile_data_size == -1) - { - printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Go back to the first byte - if (fseek(precompile_fp, 0, SEEK_SET) == -1) - { - printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check the input data size is inside the proper range - if (precompile_data_size > (MAX_PRECOMPILE_SIZE - 8)) - { - printf("ERROR: Size of precompile file (%s) is too long (%lu)\n", precompile_file_name, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Open input shared memory - shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); - if (shmem_precompile_fd < 0) - { - printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map the shared memory object into the process address space - shmem_precompile_address = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmem_precompile_fd, 0); - if (shmem_precompile_address == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Copy input data into input memory - size_t precompile_read = fread(shmem_precompile_address, 1, precompile_data_size, precompile_fp); - if (precompile_read != precompile_data_size) - { - printf("ERROR: Input read (%lu) != input file size (%lu)\n", precompile_read, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Close the file pointer - fclose(precompile_fp); - - // Unmap input - result = munmap(shmem_precompile_address, MAX_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(precompile) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Open control shared memory - shmem_control_fd = shm_open(shmem_control_name, O_RDWR, 0666); - if (shmem_control_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map the shared memory object into the process address space - shmem_control_address = mmap(NULL, CONTROL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmem_control_fd, 0); - if (shmem_control_address == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Write the precompile size in the first 64 bits - *(uint64_t *)shmem_control_address = (uint64_t)precompile_data_size; - - // Unmap control - result = munmap(shmem_control_address, CONTROL_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(control) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - printf("client (precompile): done in %lu us\n", duration); -#endif - + //client_write_precompile_results(); } /*************************/ @@ -2208,6 +2142,11 @@ void client_run (void) exit(-1); } + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + client_write_precompile_results(); + } + // Read server response bytes_received = recv(socket_fd, response, sizeof(response), MSG_WAITALL); if (bytes_received < 0) @@ -4591,11 +4530,11 @@ int _wait_for_prec_avail (void) { int sem_prec_avail_value = 0; sem_getvalue(sem_prec_avail, &sem_prec_avail_value); - printf("_wait_for_prec_avail() sem_prec_avail_value=%d\n", sem_prec_avail_value); + printf("_wait_for_prec_avail() sem_prec_avail_value=%d precompile_written_address=%lu precompile_read_address=%lu\n", sem_prec_avail_value, *precompile_written_address, *precompile_read_address); // Sync precompile shared memory __sync_synchronize(); - if (msync((void *)shmem_control_address + 4096, 8, MS_SYNC) != 0) { + if (msync(((void *)shmem_control_address) + CONTROL_SIZE/2, CONTROL_SIZE/2, MS_SYNC) != 0) { printf("ERROR: 1 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); @@ -4609,20 +4548,20 @@ int _wait_for_prec_avail (void) sem_trywait(sem_prec_avail); // Sync precompile shared memory - __sync_synchronize(); - if (msync((void *)shmem_control_address, 8, MS_SYNC) != 0) { - printf("ERROR: 2 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + //__sync_synchronize(); + // if (msync((void *)shmem_control_address, CONTROL_SIZE/2, MS_SYNC | MS_INVALIDATE) != 0) { + // printf("ERROR: 2 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); + // fflush(stdout); + // fflush(stderr); + // exit(-1); + // } // Check if there are precompile results available if (*precompile_written_address > *precompile_read_address) { // Sync precompile shared memory - __sync_synchronize(); - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + //__sync_synchronize(); + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC |MS_INVALIDATE) != 0) { printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); @@ -4632,7 +4571,11 @@ int _wait_for_prec_avail (void) } // Wait again, but blocking this time + sem_getvalue(sem_prec_avail, &sem_prec_avail_value); + printf("_wait_for_prec_avail() calling sem_wait sem_prec_avail_value=%d precompile_written_address=%lu precompile_read_address=%lu\n", sem_prec_avail_value, *precompile_written_address, *precompile_read_address); int result = sem_wait(sem_prec_avail); + sem_getvalue(sem_prec_avail, &sem_prec_avail_value); + printf("_wait_for_prec_avail() called sem_wait sem_prec_avail_value=%d precompile_written_address=%lu precompile_read_address=%lu\n", sem_prec_avail_value, *precompile_written_address, *precompile_read_address); if (result == -1) { printf("ERROR: wait_for_prec_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); @@ -4642,13 +4585,13 @@ int _wait_for_prec_avail (void) } // Sync precompile shared memory - __sync_synchronize(); - if (msync((void *)shmem_control_address, 8, MS_SYNC) != 0) { - printf("ERROR: 3 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + //__sync_synchronize(); + // if (msync((void *)shmem_control_address, CONTROL_SIZE, MS_SYNC | MS_INVALIDATE) != 0) { + // printf("ERROR: 3 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); + // fflush(stdout); + // fflush(stderr); + // exit(-1); + // } if (*precompile_written_address == *precompile_read_address) { @@ -4657,13 +4600,13 @@ int _wait_for_prec_avail (void) } // Sync precompile shared memory - __sync_synchronize(); - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + //__sync_synchronize(); + // if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC | MS_INVALIDATE) != 0) { + // printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + // fflush(stdout); + // fflush(stderr); + // exit(-1); + // } return 0; } From 60424de928578ceecc4aafb3a87076c3c2687ace Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 12 Dec 2025 16:42:37 +0100 Subject: [PATCH 061/782] Temporary changes --- emulator-asm/src/main.c | 81 ++++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 44bd92d95..eaf1947d0 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -1865,22 +1865,50 @@ void client_write_precompile_results (void) exit(-1); } - // Copy input data into input memory - size_t precompile_read = fread(shmem_precompile_address, 1, precompile_data_size, precompile_fp); - if (precompile_read != precompile_data_size) - { - printf("ERROR: Input read (%lu) != input file size (%lu)\n", precompile_read, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); + // Copy in chunks of 25*8 bytes (Keccak-f state size) + uint64_t precompile_read_so_far = 0; + uint64_t data[25]; + while (precompile_read_so_far < (uint64_t)precompile_data_size) + { + // Wait for server to read precompile results + result = sem_wait(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Number of bytes to read from file and write to shared memory in every loop + uint64_t bytes_to_read = sizeof(data); + + // Copy input data into input memory + size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); + if (precompile_read != bytes_to_read) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Copy data to shared memory + for (int i=0; i<25; i++) + { + memcpy(&precompile_results_address[(precompile_read_so_far >> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); + precompile_read_so_far += 8; + } + // Notify server that precompile results are available + *precompile_written_address = precompile_read_so_far >> 3; // in u64s + __sync_synchronize(); // memory barrier + //usleep(100000); + sem_post(sem_prec_avail); } // Close the file pointer fclose(precompile_fp); - *precompile_written_address = (uint64_t)precompile_data_size; - sem_post(sem_prec_avail); - #ifdef DEBUG gettimeofday(&stop_time, NULL); duration = TimeDiff(start_time, stop_time); @@ -4529,11 +4557,17 @@ void file_lock(void) int _wait_for_prec_avail (void) { int sem_prec_avail_value = 0; - sem_getvalue(sem_prec_avail, &sem_prec_avail_value); + if (sem_getvalue(sem_prec_avail, &sem_prec_avail_value) != 0) + { + printf("ERROR: wait_for_prec_avail() failed calling sem_getvalue(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } printf("_wait_for_prec_avail() sem_prec_avail_value=%d precompile_written_address=%lu precompile_read_address=%lu\n", sem_prec_avail_value, *precompile_written_address, *precompile_read_address); // Sync precompile shared memory - __sync_synchronize(); + //__sync_synchronize(); if (msync(((void *)shmem_control_address) + CONTROL_SIZE/2, CONTROL_SIZE/2, MS_SYNC) != 0) { printf("ERROR: 1 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); @@ -4545,7 +4579,7 @@ int _wait_for_prec_avail (void) sem_post(sem_prec_read); // Make sure the semaphore is reset before checking the condition - sem_trywait(sem_prec_avail); + while (sem_trywait(sem_prec_avail) == 0) {}; // Sync precompile shared memory //__sync_synchronize(); @@ -4561,12 +4595,12 @@ int _wait_for_prec_avail (void) { // Sync precompile shared memory //__sync_synchronize(); - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC |MS_INVALIDATE) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + // if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC |MS_INVALIDATE) != 0) { + // printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + // fflush(stdout); + // fflush(stderr); + // exit(-1); + // } return 0; } @@ -4585,7 +4619,7 @@ int _wait_for_prec_avail (void) } // Sync precompile shared memory - //__sync_synchronize(); + __sync_synchronize(); // if (msync((void *)shmem_control_address, CONTROL_SIZE, MS_SYNC | MS_INVALIDATE) != 0) { // printf("ERROR: 3 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); // fflush(stdout); @@ -4599,9 +4633,12 @@ int _wait_for_prec_avail (void) return -1; } + uint64_t read_index = *precompile_read_address; + printf("_wait_for_prec_avail() proceeding to read data[0]=%lx data[7]=%lx\n", precompile_results_address[read_index], precompile_results_address[read_index + 7]); + // Sync precompile shared memory //__sync_synchronize(); - // if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC | MS_INVALIDATE) != 0) { + // if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_ASYNC | MS_INVALIDATE) != 0) { // printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); // fflush(stdout); // fflush(stderr); From ba59ba2b5b5f15358aa929a8be6baa6ba1a7ff35 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 13 Dec 2025 05:50:51 +0000 Subject: [PATCH 062/782] added file writer --- common/src/io/stream/file.rs | 70 +++++++++++++++++++++++++-- common/src/io/stream/stream_writer.rs | 7 +-- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/common/src/io/stream/file.rs b/common/src/io/stream/file.rs index eb78e4417..9dc224830 100644 --- a/common/src/io/stream/file.rs +++ b/common/src/io/stream/file.rs @@ -1,11 +1,11 @@ -//! A file-based implementation of FileStreamReader. -//! This module provides functionality to read input data from a file. +//! A file-based implementation of FileStreamReader and FileStreamWriter. +//! This module provides functionality to read and write data from/to files. use std::fs::File; -use std::io::{BufReader, Read}; +use std::io::{BufReader, BufWriter, Read, Write}; use std::path::{Path, PathBuf}; -use super::StreamRead; +use super::{StreamRead, StreamWrite}; use anyhow::Result; @@ -78,3 +78,65 @@ impl StreamRead for FileStreamReader { self.reader.is_some() } } + +/// A file-based implementation of StreamWrite that writes to a file. +pub struct FileStreamWriter { + /// The path to the output file. + path: PathBuf, + + /// Buffered writer for the file. + writer: Option>, +} + +impl FileStreamWriter { + /// Create a new FileStreamWriter from a file path. + pub fn new>(path: P) -> std::io::Result { + Ok(FileStreamWriter { path: path.as_ref().to_path_buf(), writer: None }) + } +} + +impl StreamWrite for FileStreamWriter { + /// Open/initialize the stream for writing + fn open(&mut self) -> Result<()> { + if self.is_active() { + return Ok(()); + } + + let file = File::create(&self.path)?; + self.writer = Some(BufWriter::new(file)); + Ok(()) + } + + /// Write data to the stream, returns the number of bytes written + fn write(&mut self, item: &[u8]) -> Result { + // Open the file if it's not already open + self.open()?; + + let writer = self.writer.as_mut().ok_or_else(|| { + anyhow::anyhow!("FileStreamWriter: Writer is not initialized after opening the file") + })?; + + writer.write_all(item)?; + Ok(item.len()) + } + + /// Flush any buffered data + fn flush(&mut self) -> Result<()> { + if let Some(writer) = self.writer.as_mut() { + writer.flush()?; + } + Ok(()) + } + + /// Close the stream + fn close(&mut self) -> Result<()> { + self.flush()?; + self.writer = None; + Ok(()) + } + + /// Check if the stream is currently active + fn is_active(&self) -> bool { + self.writer.is_some() + } +} diff --git a/common/src/io/stream/stream_writer.rs b/common/src/io/stream/stream_writer.rs index e5a6c830a..44e4998fa 100644 --- a/common/src/io/stream/stream_writer.rs +++ b/common/src/io/stream/stream_writer.rs @@ -5,11 +5,8 @@ pub trait StreamWrite: Send + 'static { /// Open/initialize the stream for writing fn open(&mut self) -> Result<()>; - /// Write an item to the stream - fn write(&mut self, item: &[u8]) -> Result<()>; - - /// Write raw bytes to the stream - fn write_bytes(&mut self, data: &[u8]) -> Result; + /// Write data to the stream, returns the number of bytes written + fn write(&mut self, item: &[u8]) -> Result; /// Flush any buffered data fn flush(&mut self) -> Result<()>; From 7b2934c8b5eda5fb62f947e86b6b939362baa34e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 13 Dec 2025 07:12:25 +0000 Subject: [PATCH 063/782] added unix socket input stream server/client --- common/src/io/stream/mod.rs | 10 +- common/src/io/stream/stream_reader.rs | 46 +++ common/src/io/stream/unix_socket.rs | 561 ++++++++++++++++++++++++++ 3 files changed, 615 insertions(+), 2 deletions(-) create mode 100644 common/src/io/stream/unix_socket.rs diff --git a/common/src/io/stream/mod.rs b/common/src/io/stream/mod.rs index c3e142ccc..1d6094ca0 100644 --- a/common/src/io/stream/mod.rs +++ b/common/src/io/stream/mod.rs @@ -3,7 +3,13 @@ mod null; mod stream_reader; mod stream_writer; -use file::FileStreamReader; -use null::NullStreamReader; +#[cfg(unix)] +mod unix_socket; + +pub use file::{FileStreamReader, FileStreamWriter}; +pub use null::NullStreamReader; pub use stream_reader::*; pub use stream_writer::*; + +#[cfg(unix)] +pub use unix_socket::{UnixSocketStreamReader, UnixSocketStreamWriter}; diff --git a/common/src/io/stream/stream_reader.rs b/common/src/io/stream/stream_reader.rs index 417a7ad5d..921483d5d 100644 --- a/common/src/io/stream/stream_reader.rs +++ b/common/src/io/stream/stream_reader.rs @@ -1,3 +1,5 @@ +use crate::io::UnixSocketStreamReader; + use super::{FileStreamReader, NullStreamReader}; use anyhow::Result; @@ -21,6 +23,7 @@ pub trait StreamRead: Send + 'static { pub enum StreamSource { File(FileStreamReader), Null(NullStreamReader), + UnixSocket(UnixSocketStreamReader), } impl StreamSource { @@ -33,6 +36,45 @@ impl StreamSource { pub fn from_file>(path: P) -> Result { Ok(StreamSource::File(FileStreamReader::new(path)?)) } + + /// Create a Unix socket-based stdin + pub fn from_unix_socket>(path: P) -> Result { + Ok(StreamSource::UnixSocket(UnixSocketStreamReader::new(path.as_ref())?)) + } + + /// Create a StreamSource from a URI string + /// + /// # URI Formats + /// - `None` → null stream (no input) + /// - `"scheme://resource"` → parsed based on scheme + /// - No scheme → treated as a file path + /// + /// # Supported Schemes + /// - `file://path/to/file` → File-based stream + /// - `unix://path/to/socket` → Unix domain socket stream + pub fn from_str>(hints_uri: Option) -> Result { + if hints_uri.is_none() { + return Ok(Self::null()); + } + + let uri_str = hints_uri.unwrap().into(); + + // Check if URI contains "://" separator + if let Some(pos) = uri_str.find("://") { + let (scheme, location) = uri_str.split_at(pos); + let path = &location[3..]; // Skip "://" + + match scheme { + "file" => Self::from_file(path), + "unix" => Self::from_unix_socket(path), + // Unknown scheme - could error or fallback + _ => Err(anyhow::anyhow!("Unknown stream source scheme: {}", scheme)), + } + } else { + // No "://" found - fallback as a file path + StreamSource::from_file(uri_str.as_str()) + } + } } impl StreamRead for StreamSource { @@ -41,6 +83,7 @@ impl StreamRead for StreamSource { match self { StreamSource::File(file_stream) => file_stream.open(), StreamSource::Null(null_stream) => null_stream.open(), + StreamSource::UnixSocket(unix_stream) => unix_stream.open(), } } @@ -49,6 +92,7 @@ impl StreamRead for StreamSource { match self { StreamSource::File(file_stream) => file_stream.next(), StreamSource::Null(null_stream) => null_stream.next(), + StreamSource::UnixSocket(unix_stream) => unix_stream.next(), } } @@ -57,6 +101,7 @@ impl StreamRead for StreamSource { match self { StreamSource::File(file_stream) => file_stream.close(), StreamSource::Null(null_stream) => null_stream.close(), + StreamSource::UnixSocket(unix_stream) => unix_stream.close(), } } @@ -65,6 +110,7 @@ impl StreamRead for StreamSource { match self { StreamSource::File(file_stream) => file_stream.is_active(), StreamSource::Null(null_stream) => null_stream.is_active(), + StreamSource::UnixSocket(unix_stream) => unix_stream.is_active(), } } } diff --git a/common/src/io/stream/unix_socket.rs b/common/src/io/stream/unix_socket.rs new file mode 100644 index 000000000..12f963dba --- /dev/null +++ b/common/src/io/stream/unix_socket.rs @@ -0,0 +1,561 @@ +//! A Unix domain socket implementation of StreamReader and StreamWriter. +//! This module provides functionality to read and write data through Unix sockets +//! using SOCK_SEQPACKET for message-oriented communication with built-in boundaries. + +use std::io::Write; +use std::os::unix::io::FromRawFd; +use std::os::unix::net::UnixStream; +use std::path::{Path, PathBuf}; + +use anyhow::{Context, Result}; + +use super::{StreamRead, StreamWrite}; + +/// A Unix domain socket implementation of StreamRead using SOCK_SEQPACKET. +pub struct UnixSocketStreamReader { + /// The path to the Unix socket to connect to. + path: PathBuf, + + /// The connected socket for reading + socket: Option, +} + +impl UnixSocketStreamReader { + /// Create a new UnixSocketStreamReader that connects to the specified socket path. + /// + /// This creates a client socket that connects to the writer to read data. + pub fn new>(path: P) -> Result { + Ok(UnixSocketStreamReader { path: path.as_ref().to_path_buf(), socket: None }) + } + + /// Connect to the Unix socket with SOCK_SEQPACKET type + #[cfg(unix)] + fn connect_socket(&mut self) -> Result<()> { + use std::ffi::CString; + use std::os::unix::ffi::OsStrExt; + + // Create socket with SOCK_SEQPACKET + let sock_fd = + unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC, 0) }; + + if sock_fd < 0 { + return Err(anyhow::anyhow!( + "Failed to create socket: {}", + std::io::Error::last_os_error() + )); + } + + // Connect to the socket path + let c_path = + CString::new(self.path.as_os_str().as_bytes()).context("Invalid socket path")?; + + let mut addr: libc::sockaddr_un = unsafe { std::mem::zeroed() }; + addr.sun_family = libc::AF_UNIX as u16; + + let path_bytes = c_path.as_bytes_with_nul(); + if path_bytes.len() > addr.sun_path.len() { + unsafe { libc::close(sock_fd) }; + return Err(anyhow::anyhow!("Socket path too long")); + } + + unsafe { + std::ptr::copy_nonoverlapping( + path_bytes.as_ptr() as *const i8, + addr.sun_path.as_mut_ptr(), + path_bytes.len(), + ); + } + + let addr_len = std::mem::size_of_val(&addr.sun_family) + path_bytes.len(); + + // Retry connect on EINTR + loop { + let result = unsafe { + libc::connect( + sock_fd, + &addr as *const libc::sockaddr_un as *const libc::sockaddr, + addr_len as u32, + ) + }; + + if result < 0 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::Interrupted { + continue; // Retry on EINTR + } + unsafe { libc::close(sock_fd) }; + return Err(anyhow::anyhow!("Failed to connect to socket: {}", err)); + } + + break; + } + + // Convert to UnixStream + let socket = unsafe { UnixStream::from_raw_fd(sock_fd) }; + self.socket = Some(socket); + + Ok(()) + } +} + +impl StreamRead for UnixSocketStreamReader { + /// Open/initialize the stream for reading + /// + /// Connects to the Unix socket server. + fn open(&mut self) -> Result<()> { + if self.is_active() { + return Ok(()); + } + + self.connect_socket()?; + Ok(()) + } + + /// Reads the next message from the Unix socket. + /// + /// With SOCK_SEQPACKET, each recv() reads exactly one complete message, + /// providing natural message boundaries. + fn next(&mut self) -> Result>> { + self.open()?; + + let socket = self + .socket + .as_mut() + .ok_or_else(|| anyhow::anyhow!("UnixSocketStreamReader: Socket not connected"))?; + + // Buffer for receiving messages (128KB max for SOCK_SEQPACKET) + let mut buffer = vec![0u8; 128 * 1024]; + + // Use raw recv to detect MSG_TRUNC + use std::os::unix::io::AsRawFd; + let fd = socket.as_raw_fd(); + + loop { + let n = unsafe { + libc::recv( + fd, + buffer.as_mut_ptr() as *mut libc::c_void, + buffer.len(), + libc::MSG_TRUNC, + ) + }; + + if n < 0 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::Interrupted { + continue; // Retry on EINTR + } + if err.kind() == std::io::ErrorKind::ConnectionReset { + return Ok(None); + } + return Err(anyhow::anyhow!("Failed to read from socket: {}", err)); + } + + if n == 0 { + // Connection closed + return Ok(None); + } + + let n = n as usize; + + // Check if message was truncated + if n > buffer.len() { + return Err(anyhow::anyhow!( + "Message truncated: received {} bytes, buffer size {} bytes", + n, + buffer.len() + )); + } + + buffer.truncate(n); + return Ok(Some(buffer)); + } + } + + /// Close the stream + fn close(&mut self) -> Result<()> { + self.socket = None; + Ok(()) + } + + /// Check if the stream is currently active + fn is_active(&self) -> bool { + self.socket.is_some() + } +} + +impl Drop for UnixSocketStreamReader { + fn drop(&mut self) { + let _ = self.close(); + } +} + +/// A Unix domain socket implementation of StreamWrite using SOCK_SEQPACKET. +pub struct UnixSocketStreamWriter { + /// The path to the Unix socket. + path: PathBuf, + + /// The listening socket file descriptor (server mode) + listener_fd: Option, + + /// The connected socket for writing + socket: Option, +} + +impl UnixSocketStreamWriter { + /// Create a new UnixSocketStreamWriter that listens on the specified socket path. + /// + /// This creates a server socket that waits for incoming reader connections. + pub fn new>(path: P) -> Result { + Ok(UnixSocketStreamWriter { + path: path.as_ref().to_path_buf(), + listener_fd: None, + socket: None, + }) + } + + /// Create the Unix socket with SOCK_SEQPACKET type + #[cfg(unix)] + fn create_listener(&mut self) -> Result<()> { + use std::ffi::CString; + use std::os::unix::ffi::OsStrExt; + + // Remove socket file if it exists and is stale + if self.path.exists() { + // Try to detect if socket is stale by attempting connection + let is_stale = match std::os::unix::net::UnixStream::connect(&self.path) { + Err(_) => true, // Connection failed = stale socket + Ok(_) => false, // Connection succeeded = socket still in use! + }; + + if is_stale { + std::fs::remove_file(&self.path).context("Failed to remove stale socket file")?; + } else { + return Err(anyhow::anyhow!( + "Socket path {} is already in use", + self.path.display() + )); + } + } + + // Create socket with SOCK_SEQPACKET for message boundaries + let sock_fd = + unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC, 0) }; + + if sock_fd < 0 { + return Err(anyhow::anyhow!( + "Failed to create socket: {}", + std::io::Error::last_os_error() + )); + } + + // Bind to the socket path + let c_path = + CString::new(self.path.as_os_str().as_bytes()).context("Invalid socket path")?; + + let mut addr: libc::sockaddr_un = unsafe { std::mem::zeroed() }; + addr.sun_family = libc::AF_UNIX as u16; + + let path_bytes = c_path.as_bytes_with_nul(); + if path_bytes.len() > addr.sun_path.len() { + unsafe { libc::close(sock_fd) }; + return Err(anyhow::anyhow!("Socket path too long")); + } + + unsafe { + std::ptr::copy_nonoverlapping( + path_bytes.as_ptr() as *const i8, + addr.sun_path.as_mut_ptr(), + path_bytes.len(), + ); + } + + let addr_len = std::mem::size_of_val(&addr.sun_family) + path_bytes.len(); + + let bind_result = unsafe { + libc::bind( + sock_fd, + &addr as *const libc::sockaddr_un as *const libc::sockaddr, + addr_len as u32, + ) + }; + + if bind_result < 0 { + let err = std::io::Error::last_os_error(); + unsafe { libc::close(sock_fd) }; + return Err(anyhow::anyhow!("Failed to bind socket: {}", err)); + } + + // Listen for connections + let listen_result = unsafe { libc::listen(sock_fd, 1) }; + + if listen_result < 0 { + let err = std::io::Error::last_os_error(); + unsafe { libc::close(sock_fd) }; + return Err(anyhow::anyhow!("Failed to listen on socket: {}", err)); + } + + self.listener_fd = Some(sock_fd); + Ok(()) + } + + /// Accept a connection from the listening socket + fn accept_connection(&mut self) -> Result<()> { + let listener_fd = + self.listener_fd.ok_or_else(|| anyhow::anyhow!("Listener socket not initialized"))?; + + // Close old socket if exists to prevent resource leak + self.socket = None; + + // Retry accept on EINTR + let conn_fd = loop { + let fd = + unsafe { libc::accept(listener_fd, std::ptr::null_mut(), std::ptr::null_mut()) }; + + if fd < 0 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::Interrupted { + continue; // Retry on EINTR + } + return Err(anyhow::anyhow!("Failed to accept connection: {}", err)); + } + + break fd; + }; + + // Convert to UnixStream for easier handling + let socket = unsafe { UnixStream::from_raw_fd(conn_fd) }; + self.socket = Some(socket); + + Ok(()) + } +} + +impl StreamWrite for UnixSocketStreamWriter { + /// Open/initialize the stream for writing + /// + /// Creates a listening socket and waits for a reader connection. + fn open(&mut self) -> Result<()> { + if self.is_active() { + return Ok(()); + } + + // Create listener if not exists + if self.listener_fd.is_none() { + self.create_listener()?; + } + + // Accept a connection + self.accept_connection()?; + + Ok(()) + } + + /// Write data to the stream, returns the number of bytes written. + /// + /// With SOCK_SEQPACKET, each write() sends exactly one complete message, + /// providing natural message boundaries. + fn write(&mut self, item: &[u8]) -> Result { + self.open()?; + + let socket = self + .socket + .as_mut() + .ok_or_else(|| anyhow::anyhow!("UnixSocketStreamWriter: Socket not connected"))?; + + socket.write_all(item).context("Failed to write to socket")?; + Ok(item.len()) + } + + /// Flush any buffered data + fn flush(&mut self) -> Result<()> { + if let Some(socket) = self.socket.as_mut() { + socket.flush()?; + } + Ok(()) + } + + /// Close the stream + fn close(&mut self) -> Result<()> { + self.flush()?; + self.socket = None; + + if let Some(fd) = self.listener_fd.take() { + unsafe { libc::close(fd) }; + } + + // Clean up socket file + if self.path.exists() { + let _ = std::fs::remove_file(&self.path); + } + + Ok(()) + } + + /// Check if the stream is currently active + fn is_active(&self) -> bool { + self.socket.is_some() + } +} + +impl Drop for UnixSocketStreamWriter { + fn drop(&mut self) { + let _ = self.close(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::thread; + use std::time::Duration; + + #[test] + fn test_single_message() { + let socket_path = "/tmp/test_unix_socket_single.sock"; + let _ = std::fs::remove_file(socket_path); // Clean up if exists + + let socket_path_clone = socket_path.to_string(); + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); + writer.write(b"Hello, World!").unwrap(); + writer.close().unwrap(); + }); + + // Give writer time to start listening + thread::sleep(Duration::from_millis(100)); + + // Reader connects and reads message + let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); + let message = reader.next().unwrap().unwrap(); + assert_eq!(message, b"Hello, World!"); + reader.close().unwrap(); + + writer_thread.join().unwrap(); + } + + #[test] + fn test_multiple_messages() { + let socket_path = "/tmp/test_unix_socket_multi.sock"; + let _ = std::fs::remove_file(socket_path); + + let socket_path_clone = socket_path.to_string(); + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); + writer.write(b"First").unwrap(); + writer.write(b"Second message").unwrap(); + writer.write(b"Third message with more data!").unwrap(); + writer.close().unwrap(); + }); + + thread::sleep(Duration::from_millis(100)); + + // Reader connects and reads messages + let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); + assert_eq!(msg1, b"First"); + let msg2 = reader.next().unwrap().unwrap(); + assert_eq!(msg2, b"Second message"); + let msg3 = reader.next().unwrap().unwrap(); + assert_eq!(msg3, b"Third message with more data!"); + reader.close().unwrap(); + + writer_thread.join().unwrap(); + } + + #[test] + fn test_message_boundaries() { + let socket_path = "/tmp/test_unix_socket_boundaries.sock"; + let _ = std::fs::remove_file(socket_path); + + let socket_path_clone = socket_path.to_string(); + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); + writer.write(b"ABC").unwrap(); + writer.write(b"DEF").unwrap(); + writer.close().unwrap(); + }); + + thread::sleep(Duration::from_millis(100)); + + // Reader should receive each message as discrete unit + let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); + assert_eq!(msg1, b"ABC"); + let msg2 = reader.next().unwrap().unwrap(); + assert_eq!(msg2, b"DEF"); + // Should NOT be concatenated like "ABCDEF" + reader.close().unwrap(); + + writer_thread.join().unwrap(); + } + + #[test] + fn test_large_message() { + let socket_path = "/tmp/test_unix_socket_large.sock"; + let _ = std::fs::remove_file(socket_path); + + let socket_path_clone = socket_path.to_string(); + + // Create a large message (64KB - within SOCK_SEQPACKET limits) + let large_data: Vec = (0..64 * 1024).map(|i| (i % 256) as u8).collect(); + let large_data_clone = large_data.clone(); + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); + writer.write(&large_data).unwrap(); + writer.close().unwrap(); + }); + + thread::sleep(Duration::from_millis(100)); + + // Reader receives large message + let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); + let message = reader.next().unwrap().unwrap(); + assert_eq!(message, large_data_clone); + reader.close().unwrap(); + + writer_thread.join().unwrap(); + } + + #[test] + fn test_connection_close() { + let socket_path = "/tmp/test_unix_socket_close.sock"; + let _ = std::fs::remove_file(socket_path); + + let socket_path_clone = socket_path.to_string(); + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); + writer.write(b"Message").unwrap(); + writer.close().unwrap(); + }); + + thread::sleep(Duration::from_millis(100)); + + // Reader receives message + let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); + assert_eq!(msg1, b"Message"); + + // After writer closes, next should return None + let msg2 = reader.next().unwrap(); + assert_eq!(msg2, None); + reader.close().unwrap(); + + writer_thread.join().unwrap(); + } + + // Note: Empty messages cannot be reliably distinguished from connection close + // with SOCK_SEQPACKET, so this test is commented out + // #[test] + // fn test_empty_message() { ... } +} From d3be376d67ee90bd51805d417599caa1a76dc933 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 13 Dec 2025 07:12:40 +0000 Subject: [PATCH 064/782] added quic input stream server/client --- Cargo.lock | 308 +++++++++++++++ Cargo.toml | 5 + common/Cargo.toml | 15 +- common/src/io/stream/mod.rs | 2 + common/src/io/stream/quic.rs | 544 ++++++++++++++++++++++++++ common/src/io/stream/stream_reader.rs | 14 +- 6 files changed, 884 insertions(+), 4 deletions(-) create mode 100644 common/src/io/stream/quic.rs diff --git a/Cargo.lock b/Cargo.lock index 7b453097f..5971e1eee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -353,6 +353,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.8.7" @@ -650,6 +672,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -767,6 +795,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "cmake" +version = "0.1.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49d74c227b6cc9f3c51a2c7c667a05b6453f7f0f952a5f8e4493bb9e731d68e" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -782,6 +819,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "config" version = "0.15.19" @@ -865,6 +912,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1159,6 +1216,12 @@ dependencies = [ "const-random", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecdsa" version = "0.16.9" @@ -1369,6 +1432,18 @@ dependencies = [ "ziskemu", ] +[[package]] +name = "fastbloom" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18c1ddb9231d8554c2d6bdf4cfaabf0c59251658c68b6c95cd52dd0c513a912a" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.2", + "siphasher", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1450,6 +1525,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.31" @@ -2101,6 +2182,28 @@ dependencies = [ "syn", ] +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.34" @@ -2219,6 +2322,12 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" version = "0.1.10" @@ -2585,6 +2694,12 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + [[package]] name = "option-ext" version = "0.2.0" @@ -2642,6 +2757,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3284,6 +3409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", + "fastbloom", "getrandom 0.3.4", "lru-slab", "rand 0.9.2", @@ -3291,6 +3417,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "slab", "thiserror 2.0.17", "tinyvec", @@ -3406,6 +3533,19 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fae430c6b28f1ad601274e78b7dffa0546de0b73b4cd32f46723c0c2a16f7a5" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -3638,6 +3778,7 @@ version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -3647,6 +3788,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.13.0" @@ -3657,12 +3810,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3698,6 +3879,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3718,6 +3908,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -3928,6 +4141,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.11" @@ -4641,6 +4860,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5028,6 +5248,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.4" @@ -5219,6 +5448,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -5255,6 +5493,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -5297,6 +5550,12 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -5309,6 +5568,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -5321,6 +5586,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -5345,6 +5616,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -5357,6 +5634,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -5369,6 +5652,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -5381,6 +5670,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -5445,6 +5740,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "yoke" version = "0.8.1" @@ -5583,8 +5887,12 @@ dependencies = [ "mpi", "proofman", "proofman-common", + "quinn", + "rcgen", + "rustls", "serde", "serde_json", + "tokio", "tracing", "tracing-subscriber", "witness", diff --git a/Cargo.toml b/Cargo.toml index 3c79d89eb..7776675de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -163,6 +163,11 @@ uuid = { version = "1.0", features = ["serde", "v4"] } chrono = { version = "0.4", features = ["serde"] } sha2 = { version = "0.10.9", features = ["compress"] } +# QUIC networking +quinn = "0.11" +rustls = { version = "0.23", features = ["ring"] } +rcgen = "0.14" + # gRPC dependencies tonic = "0.14" tonic-prost = "0.14" diff --git a/common/Cargo.toml b/common/Cargo.toml index e2dd40cf3..7756c2a71 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -17,7 +17,7 @@ witness = { workspace = true } proofman-common = { workspace = true } proofman = { workspace = true } fields = { workspace = true } -tracing = { workspace = true} +tracing = { workspace = true } tracing-subscriber = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } @@ -25,6 +25,12 @@ anyhow = { workspace = true } bytemuck = { workspace = true } zstd = { workspace = true } +# QUIC networking +quinn = { workspace = true } +tokio = { workspace = true } +rustls = { workspace = true } +rcgen = { workspace = true } + # Distributed mode (mpi) is only supported on Linux x86_64 [target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] mpi = { workspace = true } @@ -33,8 +39,11 @@ libc = "0.2" [features] default = [] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] +disable_distributed = [ + "proofman/disable_distributed", + "proofman-common/disable_distributed", +] stats = [] [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(distributed)'] } \ No newline at end of file +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(distributed)'] } diff --git a/common/src/io/stream/mod.rs b/common/src/io/stream/mod.rs index 1d6094ca0..eceeb17c9 100644 --- a/common/src/io/stream/mod.rs +++ b/common/src/io/stream/mod.rs @@ -1,5 +1,6 @@ mod file; mod null; +mod quic; mod stream_reader; mod stream_writer; @@ -8,6 +9,7 @@ mod unix_socket; pub use file::{FileStreamReader, FileStreamWriter}; pub use null::NullStreamReader; +pub use quic::{QuicStreamReader, QuicStreamWriter}; pub use stream_reader::*; pub use stream_writer::*; diff --git a/common/src/io/stream/quic.rs b/common/src/io/stream/quic.rs new file mode 100644 index 000000000..f74cdc9c5 --- /dev/null +++ b/common/src/io/stream/quic.rs @@ -0,0 +1,544 @@ +//! A QUIC-based implementation of StreamReader and StreamWriter. +//! This module provides functionality to read and write data over QUIC connections +//! for both local and network communication. + +use std::net::SocketAddr; +use std::sync::Arc; + +use anyhow::{Context, Result}; +use quinn::{Connection, Endpoint, ServerConfig}; +use tokio::runtime::Runtime; + +use super::{StreamRead, StreamWrite}; + +/// A QUIC implementation of StreamRead that receives data over QUIC streams. +pub struct QuicStreamReader { + /// The QUIC connection + connection: Option, + + /// Tokio runtime for async operations + runtime: Arc, + + /// Client endpoint + endpoint: Option, + + /// Server address to connect to + server_addr: SocketAddr, +} + +impl QuicStreamReader { + /// Create a new QuicStreamReader that connects to the specified server address. + /// + /// This creates a client endpoint that connects to the server to read data. + pub fn new(server_addr: SocketAddr) -> Result { + let runtime = Arc::new( + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .context("Failed to create tokio runtime")?, + ); + + Ok(QuicStreamReader { connection: None, runtime, endpoint: None, server_addr }) + } +} + +impl StreamRead for QuicStreamReader { + /// Open/initialize the stream for reading + /// + /// Establishes a QUIC connection to the server. + fn open(&mut self) -> Result<()> { + if self.is_active() { + return Ok(()); + } + + let (endpoint, connection) = self.runtime.block_on(async { + let mut endpoint = Endpoint::client("0.0.0.0:0".parse().unwrap())?; + + // Configure to accept self-signed certificates (for development) + let rustls_config = rustls::ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(Arc::new(SkipServerVerification)) + .with_no_client_auth(); + + let mut client_config = quinn::ClientConfig::new(Arc::new( + quinn::crypto::rustls::QuicClientConfig::try_from(rustls_config) + .map_err(|e| anyhow::anyhow!("Failed to create QUIC client config: {}", e))?, + )); + + // Configure transport for better performance + let mut transport_config = quinn::TransportConfig::default(); + transport_config.max_concurrent_uni_streams(1024u32.into()); + client_config.transport_config(Arc::new(transport_config)); + + endpoint.set_default_client_config(client_config); + + let connection = endpoint + .connect(self.server_addr, "localhost")? + .await + .context("Failed to connect to server")?; + + Ok::<_, anyhow::Error>((endpoint, connection)) + })?; + + self.endpoint = Some(endpoint); + self.connection = Some(connection); + + Ok(()) + } + + /// Reads the next message from a QUIC unidirectional stream. + /// + /// Each call to next() accepts a new unidirectional stream and reads + /// all data from it, providing natural message boundaries. + fn next(&mut self) -> Result>> { + self.open()?; + + let connection = self + .connection + .as_ref() + .ok_or_else(|| anyhow::anyhow!("QuicStreamReader: Connection not established"))?; + + self.runtime.block_on(async { + // Accept next unidirectional stream + let mut recv = match connection.accept_uni().await { + Ok(stream) => stream, + Err(quinn::ConnectionError::ApplicationClosed(_)) => { + return Ok(None); + } + Err(quinn::ConnectionError::ConnectionClosed(_)) => { + return Ok(None); + } + Err(quinn::ConnectionError::TimedOut) => { + return Ok(None); + } + Err(e) => return Err(anyhow::anyhow!("Failed to accept stream: {}", e)), + }; + + // Read all data from the stream (10MB max) + let data = + recv.read_to_end(10 * 1024 * 1024).await.context("Failed to read from stream")?; + + Ok(Some(data)) + }) + } + + /// Close the stream + fn close(&mut self) -> Result<()> { + if let Some(connection) = self.connection.take() { + connection.close(0u32.into(), b"closing"); + } + if let Some(endpoint) = self.endpoint.take() { + self.runtime.block_on(async { + endpoint.wait_idle().await; + }); + } + Ok(()) + } + + /// Check if the stream is currently active + fn is_active(&self) -> bool { + self.connection.is_some() + } +} + +/// A QUIC implementation of StreamWrite that sends data over QUIC streams. +pub struct QuicStreamWriter { + /// The QUIC connection + connection: Option, + + /// Tokio runtime for async operations + runtime: Arc, + + /// Server endpoint + endpoint: Option, + + /// Server address to bind to + bind_addr: SocketAddr, +} + +impl QuicStreamWriter { + /// Create a new QuicStreamWriter that listens on the specified address. + /// + /// This creates a server endpoint that waits for incoming reader connections. + pub fn new(bind_addr: SocketAddr) -> Result { + let runtime = Arc::new( + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .context("Failed to create tokio runtime")?, + ); + + Ok(QuicStreamWriter { connection: None, runtime, endpoint: None, bind_addr }) + } + + /// Configure server with self-signed certificate + fn configure_server() -> Result { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]) + .context("Failed to generate certificate")?; + + let key = rustls::pki_types::PrivateKeyDer::Pkcs8( + cert.signing_key.serialize_der().try_into().unwrap(), + ); + let cert_der = rustls::pki_types::CertificateDer::from(cert.cert); + + let mut server_config = ServerConfig::with_single_cert(vec![cert_der], key) + .context("Failed to create server config")?; + + // Configure transport for better performance + let mut transport_config = quinn::TransportConfig::default(); + transport_config.max_concurrent_uni_streams(1024u32.into()); + server_config.transport_config(Arc::new(transport_config)); + + Ok(server_config) + } +} + +impl StreamWrite for QuicStreamWriter { + /// Open/initialize the stream for writing + /// + /// Starts listening for incoming reader connections. + fn open(&mut self) -> Result<()> { + if self.is_active() { + return Ok(()); + } + + // Clean up old resources if they exist + if let Some(endpoint) = self.endpoint.take() { + self.runtime.block_on(async { + endpoint.wait_idle().await; + }); + } + + let server_config = Self::configure_server()?; + + let (endpoint, connection) = self.runtime.block_on(async { + let endpoint = Endpoint::server(server_config, self.bind_addr) + .context("Failed to create server endpoint")?; + + // Wait for incoming connection + let incoming = endpoint.accept().await.context("Failed to accept connection")?; + + let connection = incoming.await.context("Failed to establish connection")?; + + Ok::<_, anyhow::Error>((endpoint, connection)) + })?; + + self.endpoint = Some(endpoint); + self.connection = Some(connection); + + Ok(()) + } + + /// Write data to the stream, returns the number of bytes written. + /// + /// Each call to write() opens a new unidirectional stream, writes the data, + /// and closes the stream, providing natural message boundaries. + fn write(&mut self, item: &[u8]) -> Result { + self.open()?; + + let connection = self + .connection + .as_ref() + .ok_or_else(|| anyhow::anyhow!("QuicStreamWriter: Connection not established"))?; + + let len = item.len(); + let data = item.to_vec(); + + self.runtime.block_on(async { + // Open a new unidirectional stream for this message + let mut send = connection.open_uni().await.context("Failed to open stream")?; + + // Write all data + send.write_all(&data).await.context("Failed to write to stream")?; + + // Finish the stream (signals end of message) + send.finish().context("Failed to finish stream")?; + + Ok(len) + }) + } + + /// Flush any buffered data + /// + /// QUIC handles flushing automatically, so this is a no-op. + fn flush(&mut self) -> Result<()> { + Ok(()) + } + + /// Close the stream + fn close(&mut self) -> Result<()> { + if let Some(connection) = self.connection.take() { + connection.close(0u32.into(), b"closing"); + } + if let Some(endpoint) = self.endpoint.take() { + self.runtime.block_on(async { + endpoint.wait_idle().await; + }); + } + Ok(()) + } + + /// Check if the stream is currently active + fn is_active(&self) -> bool { + self.connection.is_some() + } +} + +/// Certificate verifier that accepts any certificate (for development only!) +/// +/// ⚠️ WARNING: This is INSECURE and should NEVER be used in production. +/// It accepts all certificates without validation, making you vulnerable to MITM attacks. +/// For production use, implement proper certificate validation. +#[derive(Debug)] +struct SkipServerVerification; + +impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::pki_types::CertificateDer<'_>, + _intermediates: &[rustls::pki_types::CertificateDer<'_>], + _server_name: &rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + rustls::SignatureScheme::RSA_PKCS1_SHA256, + rustls::SignatureScheme::ECDSA_NISTP256_SHA256, + rustls::SignatureScheme::ED25519, + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::thread; + use std::time::Duration; + + // Initialize crypto provider once for all tests + fn init_crypto() { + use std::sync::Once; + static INIT: Once = Once::new(); + INIT.call_once(|| { + let _ = rustls::crypto::ring::default_provider().install_default(); + }); + } + + #[test] + fn test_single_message() { + init_crypto(); + let server_addr: SocketAddr = "127.0.0.1:15001".parse().unwrap(); + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = QuicStreamWriter::new(server_addr).unwrap(); + writer.write(b"Hello, QUIC!").unwrap(); + + // Wait for reader to finish before closing + thread::sleep(Duration::from_millis(200)); + writer.close().unwrap(); + }); + + // Give writer time to start listening + thread::sleep(Duration::from_millis(500)); + + // Reader connects and reads message + let mut reader = QuicStreamReader::new(server_addr).unwrap(); + let message = reader.next().unwrap().unwrap(); + assert_eq!(message, b"Hello, QUIC!"); + reader.close().unwrap(); + + writer_thread.join().unwrap(); + } + + #[test] + fn test_multiple_messages() { + init_crypto(); + let server_addr: SocketAddr = "127.0.0.1:15002".parse().unwrap(); + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = QuicStreamWriter::new(server_addr).unwrap(); + writer.write(b"First").unwrap(); + writer.write(b"Second message").unwrap(); + writer.write(b"Third message with more data!").unwrap(); + + thread::sleep(Duration::from_millis(200)); + writer.close().unwrap(); + }); + + thread::sleep(Duration::from_millis(500)); + + // Reader connects and reads messages + let mut reader = QuicStreamReader::new(server_addr).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); + assert_eq!(msg1, b"First"); + let msg2 = reader.next().unwrap().unwrap(); + assert_eq!(msg2, b"Second message"); + let msg3 = reader.next().unwrap().unwrap(); + assert_eq!(msg3, b"Third message with more data!"); + reader.close().unwrap(); + + writer_thread.join().unwrap(); + } + + #[test] + fn test_message_boundaries() { + init_crypto(); + let server_addr: SocketAddr = "127.0.0.1:15003".parse().unwrap(); + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = QuicStreamWriter::new(server_addr).unwrap(); + writer.write(b"ABC").unwrap(); + writer.write(b"DEF").unwrap(); + + thread::sleep(Duration::from_millis(200)); + writer.close().unwrap(); + }); + + thread::sleep(Duration::from_millis(500)); + + // Reader should receive each message as discrete unit + let mut reader = QuicStreamReader::new(server_addr).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); + assert_eq!(msg1, b"ABC"); + let msg2 = reader.next().unwrap().unwrap(); + assert_eq!(msg2, b"DEF"); + // Should NOT be concatenated like "ABCDEF" + reader.close().unwrap(); + + writer_thread.join().unwrap(); + } + + #[test] + fn test_large_message() { + init_crypto(); + let server_addr: SocketAddr = "127.0.0.1:15004".parse().unwrap(); + + // Create a large message (1MB - QUIC can handle this) + let large_data: Vec = (0..1024 * 1024).map(|i| (i % 256) as u8).collect(); + let large_data_clone = large_data.clone(); + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = QuicStreamWriter::new(server_addr).unwrap(); + writer.write(&large_data).unwrap(); + + thread::sleep(Duration::from_millis(200)); + writer.close().unwrap(); + }); + + thread::sleep(Duration::from_millis(500)); + + // Reader receives large message + let mut reader = QuicStreamReader::new(server_addr).unwrap(); + let message = reader.next().unwrap().unwrap(); + assert_eq!(message, large_data_clone); + reader.close().unwrap(); + + writer_thread.join().unwrap(); + } + + #[test] + fn test_connection_close() { + init_crypto(); + let server_addr: SocketAddr = "127.0.0.1:15005".parse().unwrap(); + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = QuicStreamWriter::new(server_addr).unwrap(); + writer.write(b"Message").unwrap(); + + thread::sleep(Duration::from_millis(200)); + writer.close().unwrap(); + }); + + thread::sleep(Duration::from_millis(500)); + + // Reader receives message and closes + let mut reader = QuicStreamReader::new(server_addr).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); + assert_eq!(msg1, b"Message"); + reader.close().unwrap(); + + writer_thread.join().unwrap(); + } + + #[test] + fn test_multiple_concurrent_messages() { + init_crypto(); + let server_addr: SocketAddr = "127.0.0.1:15006".parse().unwrap(); + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = QuicStreamWriter::new(server_addr).unwrap(); + for i in 0..10 { + writer.write(format!("Message {}", i).as_bytes()).unwrap(); + } + + thread::sleep(Duration::from_millis(200)); + writer.close().unwrap(); + }); + + thread::sleep(Duration::from_millis(500)); + + // Reader receives 10 messages in quick succession + let mut reader = QuicStreamReader::new(server_addr).unwrap(); + for i in 0..10 { + let msg = reader.next().unwrap().unwrap(); + assert_eq!(msg, format!("Message {}", i).as_bytes()); + } + reader.close().unwrap(); + + writer_thread.join().unwrap(); + } + + #[test] + fn test_writer_closes_early() { + init_crypto(); + let server_addr: SocketAddr = "127.0.0.1:15007".parse().unwrap(); + + // Spawn writer (server) thread that closes after writing one message + let writer_thread = thread::spawn(move || { + let mut writer = QuicStreamWriter::new(server_addr).unwrap(); + writer.write(b"First").unwrap(); + + // Writer closes immediately + thread::sleep(Duration::from_millis(100)); + writer.close().unwrap(); + }); + + thread::sleep(Duration::from_millis(500)); + + // Reader receives first message successfully + let mut reader = QuicStreamReader::new(server_addr).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); + assert_eq!(msg1, b"First"); + + reader.close().unwrap(); + writer_thread.join().unwrap(); + } +} diff --git a/common/src/io/stream/stream_reader.rs b/common/src/io/stream/stream_reader.rs index 921483d5d..3a71793fc 100644 --- a/common/src/io/stream/stream_reader.rs +++ b/common/src/io/stream/stream_reader.rs @@ -1,4 +1,4 @@ -use crate::io::UnixSocketStreamReader; +use crate::io::{QuicStreamReader, UnixSocketStreamReader}; use super::{FileStreamReader, NullStreamReader}; @@ -24,6 +24,7 @@ pub enum StreamSource { File(FileStreamReader), Null(NullStreamReader), UnixSocket(UnixSocketStreamReader), + Quic(QuicStreamReader), } impl StreamSource { @@ -42,6 +43,11 @@ impl StreamSource { Ok(StreamSource::UnixSocket(UnixSocketStreamReader::new(path.as_ref())?)) } + /// Create a QUIC-based stdin + pub fn from_quic(addr: std::net::SocketAddr) -> Result { + Ok(StreamSource::Quic(QuicStreamReader::new(addr)?)) + } + /// Create a StreamSource from a URI string /// /// # URI Formats @@ -52,6 +58,7 @@ impl StreamSource { /// # Supported Schemes /// - `file://path/to/file` → File-based stream /// - `unix://path/to/socket` → Unix domain socket stream + /// - `quic://host:port` → QUIC network stream (e.g., `quic://127.0.0.1:8080`) pub fn from_str>(hints_uri: Option) -> Result { if hints_uri.is_none() { return Ok(Self::null()); @@ -67,6 +74,7 @@ impl StreamSource { match scheme { "file" => Self::from_file(path), "unix" => Self::from_unix_socket(path), + "quic" => Self::from_quic(path.parse()?), // Unknown scheme - could error or fallback _ => Err(anyhow::anyhow!("Unknown stream source scheme: {}", scheme)), } @@ -84,6 +92,7 @@ impl StreamRead for StreamSource { StreamSource::File(file_stream) => file_stream.open(), StreamSource::Null(null_stream) => null_stream.open(), StreamSource::UnixSocket(unix_stream) => unix_stream.open(), + StreamSource::Quic(quic_stream) => quic_stream.open(), } } @@ -93,6 +102,7 @@ impl StreamRead for StreamSource { StreamSource::File(file_stream) => file_stream.next(), StreamSource::Null(null_stream) => null_stream.next(), StreamSource::UnixSocket(unix_stream) => unix_stream.next(), + StreamSource::Quic(quic_stream) => quic_stream.next(), } } @@ -102,6 +112,7 @@ impl StreamRead for StreamSource { StreamSource::File(file_stream) => file_stream.close(), StreamSource::Null(null_stream) => null_stream.close(), StreamSource::UnixSocket(unix_stream) => unix_stream.close(), + StreamSource::Quic(quic_stream) => quic_stream.close(), } } @@ -111,6 +122,7 @@ impl StreamRead for StreamSource { StreamSource::File(file_stream) => file_stream.is_active(), StreamSource::Null(null_stream) => null_stream.is_active(), StreamSource::UnixSocket(unix_stream) => unix_stream.is_active(), + StreamSource::Quic(quic_stream) => quic_stream.is_active(), } } } From 48c81df1934f6c756aac4eeb5ad195ed7b0c421a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 13 Dec 2025 08:37:22 +0000 Subject: [PATCH 065/782] improve to get a non-blocking unix socket server --- Cargo.lock | 1 + common/Cargo.toml | 1 + common/src/io/stream/unix_socket.rs | 203 +++++++++++++++++++++------- 3 files changed, 157 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5971e1eee..015a4812a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5892,6 +5892,7 @@ dependencies = [ "rustls", "serde", "serde_json", + "thiserror 2.0.17", "tokio", "tracing", "tracing-subscriber", diff --git a/common/Cargo.toml b/common/Cargo.toml index 7756c2a71..6403344a0 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -22,6 +22,7 @@ tracing-subscriber = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } anyhow = { workspace = true } +thiserror = { workspace = true } bytemuck = { workspace = true } zstd = { workspace = true } diff --git a/common/src/io/stream/unix_socket.rs b/common/src/io/stream/unix_socket.rs index 12f963dba..dbb23d739 100644 --- a/common/src/io/stream/unix_socket.rs +++ b/common/src/io/stream/unix_socket.rs @@ -6,11 +6,26 @@ use std::io::Write; use std::os::unix::io::FromRawFd; use std::os::unix::net::UnixStream; use std::path::{Path, PathBuf}; +use std::sync::mpsc::{self, Receiver}; +use std::thread::{self, JoinHandle}; use anyhow::{Context, Result}; use super::{StreamRead, StreamWrite}; +/// Errors specific to Unix socket operations +#[derive(Debug, thiserror::Error)] +pub enum UnixSocketError { + #[error("No client connected yet")] + NoClientConnected, + + #[error("Socket not connected")] + NotConnected, + + #[error("Failed to write to socket: {0}")] + WriteFailed(#[from] std::io::Error), +} + /// A Unix domain socket implementation of StreamRead using SOCK_SEQPACKET. pub struct UnixSocketStreamReader { /// The path to the Unix socket to connect to. @@ -200,6 +215,12 @@ pub struct UnixSocketStreamWriter { /// The connected socket for writing socket: Option, + + /// Receiver for the accepted socket from background thread + socket_receiver: Option>, + + /// Handle to the accept thread + accept_thread: Option>, } impl UnixSocketStreamWriter { @@ -211,6 +232,8 @@ impl UnixSocketStreamWriter { path: path.as_ref().to_path_buf(), listener_fd: None, socket: None, + socket_receiver: None, + accept_thread: None, }) } @@ -298,55 +321,53 @@ impl UnixSocketStreamWriter { self.listener_fd = Some(sock_fd); Ok(()) } - - /// Accept a connection from the listening socket - fn accept_connection(&mut self) -> Result<()> { - let listener_fd = - self.listener_fd.ok_or_else(|| anyhow::anyhow!("Listener socket not initialized"))?; - - // Close old socket if exists to prevent resource leak - self.socket = None; - - // Retry accept on EINTR - let conn_fd = loop { - let fd = - unsafe { libc::accept(listener_fd, std::ptr::null_mut(), std::ptr::null_mut()) }; - - if fd < 0 { - let err = std::io::Error::last_os_error(); - if err.kind() == std::io::ErrorKind::Interrupted { - continue; // Retry on EINTR - } - return Err(anyhow::anyhow!("Failed to accept connection: {}", err)); - } - - break fd; - }; - - // Convert to UnixStream for easier handling - let socket = unsafe { UnixStream::from_raw_fd(conn_fd) }; - self.socket = Some(socket); - - Ok(()) - } } impl StreamWrite for UnixSocketStreamWriter { /// Open/initialize the stream for writing /// - /// Creates a listening socket and waits for a reader connection. + /// Creates a listening socket and spawns a background thread to accept connections. + /// Returns immediately without blocking. fn open(&mut self) -> Result<()> { - if self.is_active() { - return Ok(()); - } - // Create listener if not exists if self.listener_fd.is_none() { self.create_listener()?; } - // Accept a connection - self.accept_connection()?; + // Spawn accept thread if not already running + if self.accept_thread.is_none() { + let listener_fd = self.listener_fd.unwrap(); + let (tx, rx) = mpsc::channel(); + self.socket_receiver = Some(rx); + + let handle = thread::spawn(move || { + // Retry accept on EINTR + let conn_fd = loop { + let fd = unsafe { + libc::accept(listener_fd, std::ptr::null_mut(), std::ptr::null_mut()) + }; + + if fd < 0 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::Interrupted { + continue; // Retry on EINTR + } + eprintln!("Accept failed: {}", err); + return; + } + + break fd; + }; + + // Convert to UnixStream + let stream = unsafe { UnixStream::from_raw_fd(conn_fd) }; + + // Send socket through channel + let _ = tx.send(stream); + }); + + self.accept_thread = Some(handle); + } Ok(()) } @@ -355,15 +376,29 @@ impl StreamWrite for UnixSocketStreamWriter { /// /// With SOCK_SEQPACKET, each write() sends exactly one complete message, /// providing natural message boundaries. + /// + /// Returns an error if no client is connected yet. fn write(&mut self, item: &[u8]) -> Result { self.open()?; - let socket = self - .socket - .as_mut() - .ok_or_else(|| anyhow::anyhow!("UnixSocketStreamWriter: Socket not connected"))?; + // Receive socket from channel if we don't have it yet + if self.socket.is_none() { + if let Some(rx) = &self.socket_receiver { + // Non-blocking check for socket from accept thread + match rx.try_recv() { + Ok(stream) => { + self.socket = Some(stream); + } + Err(_) => { + return Err(UnixSocketError::NoClientConnected.into()); + } + } + } + } + + let socket = self.socket.as_mut().ok_or(UnixSocketError::NotConnected)?; - socket.write_all(item).context("Failed to write to socket")?; + socket.write_all(item).map_err(UnixSocketError::WriteFailed)?; Ok(item.len()) } @@ -378,6 +413,8 @@ impl StreamWrite for UnixSocketStreamWriter { /// Close the stream fn close(&mut self) -> Result<()> { self.flush()?; + + // Clear the socket self.socket = None; if let Some(fd) = self.listener_fd.take() { @@ -420,7 +457,21 @@ mod tests { // Spawn writer (server) thread let writer_thread = thread::spawn(move || { let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); - writer.write(b"Hello, World!").unwrap(); + + // Retry write until client connects + loop { + if let Err(e) = writer.write(b"Hello, World!") { + if let Some(UnixSocketError::NoClientConnected) = + e.downcast_ref::() + { + thread::sleep(Duration::from_millis(10)); + continue; + } + panic!("Unexpected error: {}", e); + } + break; + } + writer.close().unwrap(); }); @@ -446,7 +497,21 @@ mod tests { // Spawn writer (server) thread let writer_thread = thread::spawn(move || { let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); - writer.write(b"First").unwrap(); + + // Retry until client connects for first message + loop { + if let Err(e) = writer.write(b"First") { + if let Some(UnixSocketError::NoClientConnected) = + e.downcast_ref::() + { + thread::sleep(Duration::from_millis(10)); + continue; + } + panic!("Unexpected error: {}", e); + } + break; + } + writer.write(b"Second message").unwrap(); writer.write(b"Third message with more data!").unwrap(); writer.close().unwrap(); @@ -477,7 +542,21 @@ mod tests { // Spawn writer (server) thread let writer_thread = thread::spawn(move || { let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); - writer.write(b"ABC").unwrap(); + + // Retry until client connects for first message + loop { + if let Err(e) = writer.write(b"ABC") { + if let Some(UnixSocketError::NoClientConnected) = + e.downcast_ref::() + { + thread::sleep(Duration::from_millis(10)); + continue; + } + panic!("Unexpected error: {}", e); + } + break; + } + writer.write(b"DEF").unwrap(); writer.close().unwrap(); }); @@ -510,7 +589,21 @@ mod tests { // Spawn writer (server) thread let writer_thread = thread::spawn(move || { let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); - writer.write(&large_data).unwrap(); + + // Retry until client connects for first message + loop { + if let Err(e) = writer.write(&large_data) { + if let Some(UnixSocketError::NoClientConnected) = + e.downcast_ref::() + { + thread::sleep(Duration::from_millis(10)); + continue; + } + panic!("Unexpected error: {}", e); + } + break; + } + writer.close().unwrap(); }); @@ -535,7 +628,21 @@ mod tests { // Spawn writer (server) thread let writer_thread = thread::spawn(move || { let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); - writer.write(b"Message").unwrap(); + + // Retry until client connects for first message + loop { + if let Err(e) = writer.write(b"Message") { + if let Some(UnixSocketError::NoClientConnected) = + e.downcast_ref::() + { + thread::sleep(Duration::from_millis(10)); + continue; + } + panic!("Unexpected error: {}", e); + } + break; + } + writer.close().unwrap(); }); From d4df3b39d4d0559851191eb618f1efd860901b33 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 13 Dec 2025 08:37:45 +0000 Subject: [PATCH 066/782] added unix socket stress test --- common/src/io/stream/unix_socket.rs | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/common/src/io/stream/unix_socket.rs b/common/src/io/stream/unix_socket.rs index dbb23d739..197153a8b 100644 --- a/common/src/io/stream/unix_socket.rs +++ b/common/src/io/stream/unix_socket.rs @@ -661,6 +661,67 @@ mod tests { writer_thread.join().unwrap(); } + #[test] + fn test_stress_many_messages() { + let socket_path = "/tmp/test_unix_socket_stress.sock"; + let _ = std::fs::remove_file(socket_path); + + let socket_path_clone = socket_path.to_string(); + + const NUM_MESSAGES: usize = 1000; + + // Spawn writer (server) thread + let writer_thread = thread::spawn(move || { + let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); + + // Wait for client to connect with first message + loop { + if let Err(e) = writer.write(b"START") { + if let Some(UnixSocketError::NoClientConnected) = + e.downcast_ref::() + { + thread::sleep(Duration::from_millis(10)); + continue; + } + panic!("Unexpected error: {}", e); + } + break; + } + + // Send many messages rapidly + for i in 0..NUM_MESSAGES { + let msg = format!("Message {}", i); + writer.write(msg.as_bytes()).unwrap(); + } + + writer.write(b"END").unwrap(); + writer.close().unwrap(); + }); + + thread::sleep(Duration::from_millis(100)); + + // Reader receives all messages + let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); + + // Read START marker + let start = reader.next().unwrap().unwrap(); + assert_eq!(start, b"START"); + + // Read all messages and verify order + for i in 0..NUM_MESSAGES { + let expected = format!("Message {}", i); + let msg = reader.next().unwrap().unwrap(); + assert_eq!(msg, expected.as_bytes(), "Message {} mismatch", i); + } + + // Read END marker + let end = reader.next().unwrap().unwrap(); + assert_eq!(end, b"END"); + + reader.close().unwrap(); + writer_thread.join().unwrap(); + } + // Note: Empty messages cannot be reliably distinguished from connection close // with SOCK_SEQPACKET, so this test is commented out // #[test] From 81be711391bed1c09983dd93771802c32f067ab2 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 13 Dec 2025 08:38:44 +0000 Subject: [PATCH 067/782] removed a test --- common/src/io/stream/unix_socket.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/common/src/io/stream/unix_socket.rs b/common/src/io/stream/unix_socket.rs index 197153a8b..0250e7006 100644 --- a/common/src/io/stream/unix_socket.rs +++ b/common/src/io/stream/unix_socket.rs @@ -721,9 +721,4 @@ mod tests { reader.close().unwrap(); writer_thread.join().unwrap(); } - - // Note: Empty messages cannot be reliably distinguished from connection close - // with SOCK_SEQPACKET, so this test is commented out - // #[test] - // fn test_empty_message() { ... } } From 9bdce7a6b906767d3bc600b4c28e4ad0088d4108 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 15 Dec 2025 20:33:27 +0100 Subject: [PATCH 068/782] Fix sem_prec_read corruption --- core/src/zisk_rom_2_asm.rs | 18 +++-- emulator-asm/src/main.c | 145 +++++++++++++++++++++++++------------ 2 files changed, 112 insertions(+), 51 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 4c1754fb1..f84cb167f 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -3421,6 +3421,10 @@ impl ZiskRom2Asm { *code += "\n"; + *code += "execute_pop_internal_regs_and_end:\n"; + Self::pop_internal_registers(&mut ctx, code, false); + *code += "\n"; + *code += "execute_end:\n"; // Update step memory variable with the content of the step register, to make it accessible @@ -5117,7 +5121,13 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_keccak() { - Self::precompile_results_array(ctx, code, unusual_code, "rdi", 25); + Self::precompile_results_array( + ctx, + code, + unusual_code, + &ctx.b.string_value.clone(), + 25, + ); } else { // Call the keccak function Self::push_internal_registers(ctx, code, false); @@ -7888,7 +7898,7 @@ impl ZiskRom2Asm { /**********************/ // Copies size u64 elements from precompile_results_address to the address in reg_address, - // and increments precompile_results_address by size*8 + // and increments precompile_read by size*8 fn precompile_results_array( ctx: &mut ZiskAsmContext, code: &mut String, @@ -8173,10 +8183,8 @@ impl ZiskRom2Asm { Self::push_internal_registers(ctx, unusual_code, false); *unusual_code += "\tcall _wait_for_prec_avail\n"; *unusual_code += "\tcmp rax, 0\n"; - *unusual_code += "\tjne execute_end\n"; - + *unusual_code += "\tjne execute_pop_internal_regs_and_end\n"; Self::pop_internal_registers(ctx, unusual_code, false); - *unusual_code += &format!("\tjmp pc_{:x}_wait_for_prec_avail_done\n", ctx.pc,); // TODO: diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index eaf1947d0..e269d1a17 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -22,6 +22,7 @@ #include #include #include +//#include // Assembly-provided functions void emulator_start(void); @@ -340,6 +341,8 @@ int shmem_control_fd = -1; uint64_t * shmem_control_address = NULL; volatile uint64_t * precompile_written_address = NULL; volatile uint64_t * precompile_read_address = NULL; +// uint64_t * precompile_written_address = NULL; +// uint64_t * precompile_read_address = NULL; int main(int argc, char *argv[]) { @@ -1751,8 +1754,8 @@ void client_setup (void) fflush(stderr); exit(-1); } + shmem_precompile_address = pPrecompile; precompile_results_address = (uint64_t *)pPrecompile; - shmem_precompile_address = precompile_results_address; if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); @@ -1865,12 +1868,28 @@ void client_write_precompile_results (void) exit(-1); } + // Copy input data into input memory + size_t precompile_read = fread(precompile_results_address, 1, precompile_data_size, precompile_fp); + if (precompile_read != precompile_data_size) + { + printf("ERROR: Precompile read (%lu) != expected read size (%lu)\n", precompile_read, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + //printf("SEM_VALUE_MAX = %u\n", SEM_VALUE_MAX); + + //*precompile_written_address = precompile_read >> 3; // in u64s + sleep(1); + //sem_post(sem_prec_avail); + // Copy in chunks of 25*8 bytes (Keccak-f state size) uint64_t precompile_read_so_far = 0; uint64_t data[25]; while (precompile_read_so_far < (uint64_t)precompile_data_size) { // Wait for server to read precompile results + printf("Waiting for sem_prec_read()\n"); result = sem_wait(sem_prec_read); if (result == -1) { @@ -1881,28 +1900,33 @@ void client_write_precompile_results (void) } // Number of bytes to read from file and write to shared memory in every loop - uint64_t bytes_to_read = sizeof(data); + // uint64_t bytes_to_read = sizeof(data); - // Copy input data into input memory - size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); - if (precompile_read != bytes_to_read) - { - printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); - fflush(stdout); - fflush(stderr); - exit(-1); - } + // // Copy input data into input memory + // size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); + // if (precompile_read != bytes_to_read) + // { + // printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); + // fflush(stdout); + // fflush(stderr); + // exit(-1); + // } + + // // Copy data to shared memory + // for (int i=0; i<25; i++) + // { + // memcpy(&precompile_results_address[(precompile_read_so_far >> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); + // precompile_read_so_far += 8; + // } + + precompile_read_so_far += 25*8; - // Copy data to shared memory - for (int i=0; i<25; i++) - { - memcpy(&precompile_results_address[(precompile_read_so_far >> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); - precompile_read_so_far += 8; - } // Notify server that precompile results are available *precompile_written_address = precompile_read_so_far >> 3; // in u64s - __sync_synchronize(); // memory barrier + //__sync_synchronize(); // memory barrier //usleep(100000); + + printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); sem_post(sem_prec_avail); } @@ -1918,6 +1942,7 @@ void client_write_precompile_results (void) void client_run (void) { + printf("client_run(): Starting client...\n"); assert(client); assert(!server); @@ -3057,7 +3082,7 @@ void server_setup (void) fflush(stderr); exit(-1); } - shmem_precompile_address = (void *)pPrecompile; + shmem_precompile_address = pPrecompile; precompile_results_address = (uint64_t *)pPrecompile; if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); @@ -3124,7 +3149,7 @@ void server_setup (void) fflush(stderr); exit(-1); } - if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_avail_name); + if (verbose) printf("sem_open(%s) succeeded sem_prec_avail=%p\n", sem_prec_avail_name, sem_prec_avail); // Create the semaphore for precompile results read signal assert(strlen(sem_prec_read_name) > 0); @@ -3139,7 +3164,7 @@ void server_setup (void) fflush(stderr); exit(-1); } - if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_read_name); + if (verbose) printf("sem_open(%s) succeeded sem_prec_read=%p\n", sem_prec_read_name, sem_prec_read); } /*******/ @@ -4556,30 +4581,30 @@ void file_lock(void) int _wait_for_prec_avail (void) { - int sem_prec_avail_value = 0; - if (sem_getvalue(sem_prec_avail, &sem_prec_avail_value) != 0) - { - printf("ERROR: wait_for_prec_avail() failed calling sem_getvalue(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - printf("_wait_for_prec_avail() sem_prec_avail_value=%d precompile_written_address=%lu precompile_read_address=%lu\n", sem_prec_avail_value, *precompile_written_address, *precompile_read_address); + // int sem_prec_avail_value = 0; + // if (sem_getvalue(sem_prec_avail, &sem_prec_avail_value) != 0) + // { + // printf("ERROR: wait_for_prec_avail() failed calling sem_getvalue(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + // fflush(stdout); + // fflush(stderr); + // exit(-1); + // } + // printf("_wait_for_prec_avail() sem_prec_avail_value=%d precompile_written_address=%lu precompile_read_address=%lu\n", sem_prec_avail_value, *precompile_written_address, *precompile_read_address); // Sync precompile shared memory //__sync_synchronize(); - if (msync(((void *)shmem_control_address) + CONTROL_SIZE/2, CONTROL_SIZE/2, MS_SYNC) != 0) { - printf("ERROR: 1 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + // if (msync(((void *)shmem_control_address) + CONTROL_SIZE/2, CONTROL_SIZE/2, MS_SYNC) != 0) { + // printf("ERROR: 1 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); + // fflush(stdout); + // fflush(stderr); + // exit(-1); + // } // Tell the writer that we have read the precompile results sem_post(sem_prec_read); // Make sure the semaphore is reset before checking the condition - while (sem_trywait(sem_prec_avail) == 0) {}; + //while (sem_trywait(sem_prec_avail) == 0) {printf("Purging sem_prec_avail\n");}; // Sync precompile shared memory //__sync_synchronize(); @@ -4602,14 +4627,20 @@ int _wait_for_prec_avail (void) // exit(-1); // } return 0; + //return; } // Wait again, but blocking this time - sem_getvalue(sem_prec_avail, &sem_prec_avail_value); - printf("_wait_for_prec_avail() calling sem_wait sem_prec_avail_value=%d precompile_written_address=%lu precompile_read_address=%lu\n", sem_prec_avail_value, *precompile_written_address, *precompile_read_address); + //sem_getvalue(sem_prec_avail, &sem_prec_avail_value); + { + printf("_wait_for_prec_avail() calling sem_wait\n"); + uint64_t written = *precompile_written_address; + uint64_t read = *precompile_read_address; + printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", written, read); + } int result = sem_wait(sem_prec_avail); - sem_getvalue(sem_prec_avail, &sem_prec_avail_value); - printf("_wait_for_prec_avail() called sem_wait sem_prec_avail_value=%d precompile_written_address=%lu precompile_read_address=%lu\n", sem_prec_avail_value, *precompile_written_address, *precompile_read_address); + // //sem_getvalue(sem_prec_avail, &sem_prec_avail_value); + // printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); if (result == -1) { printf("ERROR: wait_for_prec_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); @@ -4618,6 +4649,12 @@ int _wait_for_prec_avail (void) exit(-1); } + // while (*precompile_written_address == *precompile_read_address) + // { + // usleep(10000); + // } + printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + // Sync precompile shared memory __sync_synchronize(); // if (msync((void *)shmem_control_address, CONTROL_SIZE, MS_SYNC | MS_INVALIDATE) != 0) { @@ -4627,14 +4664,30 @@ int _wait_for_prec_avail (void) // exit(-1); // } - if (*precompile_written_address == *precompile_read_address) + uint64_t written = *precompile_written_address; + uint64_t read = *precompile_read_address; { - printf("ERROR: wait_for_prec_avail() found written=%lu == read=%lu\n", *precompile_written_address, *precompile_read_address); - return -1; + if (written == read) + { + printf("ERROR: wait_for_prec_avail() found written=%lu == read=%lu\n", written, read); + //fflush(stdout); + //fflush(stderr); + //exit(-1); + return -1; + } } - uint64_t read_index = *precompile_read_address; - printf("_wait_for_prec_avail() proceeding to read data[0]=%lx data[7]=%lx\n", precompile_results_address[read_index], precompile_results_address[read_index + 7]); + { + // printf("_wait_for_prec_avail() proceeding to read\n"); + // for (uint64_t i=0; i<25; i++) + // { + // uint64_t data = precompile_results_address[read + i]; + // printf("%lx \n", data); + // } + //printf("\n"); + //printf("_wait_for_prec_avail() proceeding to read %lx %lx\n", precompile_results_address[read_index], precompile_results_address[read_index + 7]); + //fflush(stdout); + } // Sync precompile shared memory //__sync_synchronize(); From 831f52c7135ab4136af19392d8cb469a80544792 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 16 Dec 2025 10:43:35 +0100 Subject: [PATCH 069/782] Fix precompile synchronization --- emulator-asm/src/main.c | 484 ++++++++++++++++++++++++---------------- 1 file changed, 290 insertions(+), 194 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index e269d1a17..fb3e1e19d 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -47,8 +47,12 @@ uint64_t get_precompile_results(void); #define TRACE_ADDR (uint64_t)0xc0000000 #define INITIAL_TRACE_SIZE (uint64_t)0x100000000 // 4GB -#define CONTROL_ADDR (uint64_t)0x70000000 -#define CONTROL_SIZE (uint64_t)0x2000 // 8kB +#define CONTROL_INPUT_ADDR (uint64_t)0x70000000 +#define CONTROL_INPUT_SIZE (uint64_t)0x1000 // 4kB +#define CONTROL_OUTPUT_ADDR (uint64_t)0x70001000 +#define CONTROL_OUTPUT_SIZE (uint64_t)0x1000 // 4kB +#define CONTROL_RETRY_DELAY_US 1000 // 1ms +#define CONTROL_NUMBER_OF_RETRIES 1000 // 1s max total uint8_t * pInput = (uint8_t *)INPUT_ADDR; uint8_t * pInputLast = (uint8_t *)(INPUT_ADDR + 10440504 - 64); @@ -335,14 +339,17 @@ sem_t * sem_prec_read = NULL; // Precompile results file name (used by client) char precompile_file_name[4096] = {0}; -// Control shared memory -char shmem_control_name[128]; -int shmem_control_fd = -1; -uint64_t * shmem_control_address = NULL; +// Control input shared memory +char shmem_control_input_name[128]; +int shmem_control_input_fd = -1; +uint64_t * shmem_control_input_address = NULL; volatile uint64_t * precompile_written_address = NULL; + +// Control output shared memory +char shmem_control_output_name[128]; +int shmem_control_output_fd = -1; +uint64_t * shmem_control_output_address = NULL; volatile uint64_t * precompile_read_address = NULL; -// uint64_t * precompile_written_address = NULL; -// uint64_t * precompile_read_address = NULL; int main(int argc, char *argv[]) { @@ -1300,8 +1307,10 @@ void configure (void) { case Fast: { - strcpy(shmem_control_name, shm_prefix); - strcat(shmem_control_name, "_FT_control"); + strcpy(shmem_control_input_name, shm_prefix); + strcat(shmem_control_input_name, "_FT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_FT_control_output"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_FT_input"); if (precompile_results_enabled) @@ -1334,8 +1343,10 @@ void configure (void) } case MinimalTrace: { - strcpy(shmem_control_name, shm_prefix); - strcat(shmem_control_name, "_MT_control"); + strcpy(shmem_control_input_name, shm_prefix); + strcat(shmem_control_input_name, "_MT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MT_control_output"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MT_input"); if (precompile_results_enabled) @@ -1371,8 +1382,10 @@ void configure (void) } case RomHistogram: { - strcpy(shmem_control_name, shm_prefix); - strcat(shmem_control_name, "_RH_control"); + strcpy(shmem_control_input_name, shm_prefix); + strcat(shmem_control_input_name, "_RH_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_RH_control_output"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_RH_input"); if (precompile_results_enabled) @@ -1408,8 +1421,10 @@ void configure (void) } case MainTrace: { - strcpy(shmem_control_name, shm_prefix); - strcat(shmem_control_name, "_MA_control"); + strcpy(shmem_control_input_name, shm_prefix); + strcat(shmem_control_input_name, "_MA_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MA_control_output"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MA_input"); if (precompile_results_enabled) @@ -1445,8 +1460,10 @@ void configure (void) } case ChunksOnly: { - strcpy(shmem_control_name, shm_prefix); - strcat(shmem_control_name, "_CH_control"); + strcpy(shmem_control_input_name, shm_prefix); + strcat(shmem_control_input_name, "_CH_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CH_control_output"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_CH_input"); strcpy(shmem_precompile_name, ""); @@ -1479,8 +1496,10 @@ void configure (void) // } case Zip: { - strcpy(shmem_control_name, shm_prefix); - strcat(shmem_control_name, "_ZP_control"); + strcpy(shmem_control_input_name, shm_prefix); + strcat(shmem_control_input_name, "_ZP_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_ZP_control_output"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_ZP_input"); if (precompile_results_enabled) @@ -1516,8 +1535,10 @@ void configure (void) } case MemOp: { - strcpy(shmem_control_name, shm_prefix); - strcat(shmem_control_name, "_MO_control"); + strcpy(shmem_control_input_name, shm_prefix); + strcat(shmem_control_input_name, "_MO_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MO_control_output"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MO_input"); if (precompile_results_enabled) @@ -1553,8 +1574,10 @@ void configure (void) } case ChunkPlayerMTCollectMem: { - strcpy(shmem_control_name, shm_prefix); - strcat(shmem_control_name, "_CM_control"); + strcpy(shmem_control_input_name, shm_prefix); + strcat(shmem_control_input_name, "_CM_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CM_control_output"); strcpy(shmem_input_name, ""); strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); @@ -1576,8 +1599,10 @@ void configure (void) } case MemReads: { - strcpy(shmem_control_name, shm_prefix); - strcat(shmem_control_name, "_MT_control"); + strcpy(shmem_control_input_name, shm_prefix); + strcat(shmem_control_input_name, "_MT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MT_control_output"); strcpy(shmem_input_name, shm_prefix); strcat(shmem_input_name, "_MT_input"); if (precompile_results_enabled) @@ -1613,8 +1638,10 @@ void configure (void) } case ChunkPlayerMemReadsCollectMain: { - strcpy(shmem_control_name, shm_prefix); - strcat(shmem_control_name, "_CA_control"); + strcpy(shmem_control_input_name, shm_prefix); + strcat(shmem_control_input_name, "_CA_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CA_control_output"); strcpy(shmem_input_name, ""); strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); @@ -1658,7 +1685,8 @@ void configure (void) printf("\tport=%u\n", port); printf("\tcall_chunk_done=%u\n", call_chunk_done); printf("\tchunk_size=%lu\n", chunk_size); - printf("\tshmem_control=%s\n", shmem_control_name); + printf("\tshmem_control_input=%s\n", shmem_control_input_name); + printf("\tshmem_control_output=%s\n", shmem_control_output_name); printf("\tshmem_input=%s\n", shmem_input_name); printf("\tshmem_precompile=%s\n", shmem_precompile_name); printf("\tshmem_output=%s\n", shmem_output_name); @@ -1729,6 +1757,10 @@ void client_setup (void) if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) { + /**************/ + /* PRECOMPILE */ + /**************/ + // Create the precompile results shared memory shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); if (shmem_precompile_fd < 0) @@ -1759,19 +1791,23 @@ void client_setup (void) if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); - // Create the control shared memory - shmem_control_fd = shm_open(shmem_control_name, O_RDWR, 0666); - if (shmem_control_fd < 0) + /*****************/ + /* CONTROL INPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR, 0666); + if (shmem_control_input_fd < 0) { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } - // Map precompile address space + // Map control input address space if (verbose) gettimeofday(&start_time, NULL); - void * pControl = mmap((void *)CONTROL_ADDR, CONTROL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_fd, 0); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); if (verbose) { gettimeofday(&stop_time, NULL); @@ -1779,22 +1815,65 @@ void client_setup (void) } if (pControl == MAP_FAILED) { - printf("ERROR: Failed calling mmap(control) errno=%d=%s\n", errno, strerror(errno)); + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_INPUT_ADDR) + { + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); fflush(stdout); fflush(stderr); exit(-1); } - if (pControl != (void *)CONTROL_ADDR) + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + + /*****************/ + /* CONTROL OUTPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR, 0666); + if (shmem_control_output_fd < 0) { - printf("ERROR: Called mmap(control) but returned address = %p != 0x%08lx\n", pControl, CONTROL_ADDR); + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } - shmem_control_address = (uint64_t *)pControl; - precompile_written_address = &shmem_control_address[0]; - precompile_read_address = &shmem_control_address[4096/8]; - if (verbose) printf("mmap(control) mapped %lu B and returned address %p in %lu us\n", CONTROL_SIZE, shmem_control_address, duration); + + // Map control input address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); + + /*************************/ + /* PRECOMPILE SEMAPHORES */ + /*************************/ // Create the semaphore for precompile results available signal assert(strlen(sem_prec_avail_name) > 0); @@ -1868,20 +1947,8 @@ void client_write_precompile_results (void) exit(-1); } - // Copy input data into input memory - size_t precompile_read = fread(precompile_results_address, 1, precompile_data_size, precompile_fp); - if (precompile_read != precompile_data_size) - { - printf("ERROR: Precompile read (%lu) != expected read size (%lu)\n", precompile_read, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - //printf("SEM_VALUE_MAX = %u\n", SEM_VALUE_MAX); - - //*precompile_written_address = precompile_read >> 3; // in u64s - sleep(1); - //sem_post(sem_prec_avail); + // Initialize precompile written address to zero + *precompile_written_address = 0; // in u64s // Copy in chunks of 25*8 bytes (Keccak-f state size) uint64_t precompile_read_so_far = 0; @@ -1889,7 +1956,7 @@ void client_write_precompile_results (void) while (precompile_read_so_far < (uint64_t)precompile_data_size) { // Wait for server to read precompile results - printf("Waiting for sem_prec_read()\n"); + //printf("Waiting for sem_prec_read()\n"); result = sem_wait(sem_prec_read); if (result == -1) { @@ -1900,33 +1967,29 @@ void client_write_precompile_results (void) } // Number of bytes to read from file and write to shared memory in every loop - // uint64_t bytes_to_read = sizeof(data); + uint64_t bytes_to_read = sizeof(data); // // Copy input data into input memory - // size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); - // if (precompile_read != bytes_to_read) - // { - // printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); - // fflush(stdout); - // fflush(stderr); - // exit(-1); - // } - - // // Copy data to shared memory - // for (int i=0; i<25; i++) - // { - // memcpy(&precompile_results_address[(precompile_read_so_far >> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); - // precompile_read_so_far += 8; - // } + size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); + if (precompile_read != bytes_to_read) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); + fflush(stdout); + fflush(stderr); + exit(-1); + } - precompile_read_so_far += 25*8; + // Copy data to shared memory + for (int i=0; i<25; i++) + { + memcpy(&precompile_results_address[(precompile_read_so_far >> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); + precompile_read_so_far += 8; + } // Notify server that precompile results are available *precompile_written_address = precompile_read_so_far >> 3; // in u64s - //__sync_synchronize(); // memory barrier - //usleep(100000); - printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); sem_post(sem_prec_avail); } @@ -3022,6 +3085,10 @@ void server_setup (void) if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) { + /**************/ + /* PRECOMPILE */ + /**************/ + // Make sure the precompile results shared memory is deleted shm_unlink(shmem_precompile_name); @@ -3086,24 +3153,50 @@ void server_setup (void) precompile_results_address = (uint64_t *)pPrecompile; if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); + /*****************/ + /* CONTROL INPUT */ + /*****************/ + // Make sure the precompile results shared memory is deleted - shm_unlink(shmem_control_name); + shm_unlink(shmem_control_input_name); // Create the control shared memory - shmem_control_fd = shm_open(shmem_control_name, O_RDWR | O_CREAT, 0666); - if (shmem_control_fd < 0) + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_input_fd < 0) { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } // Size it - result = ftruncate(shmem_control_fd, CONTROL_SIZE); + result = ftruncate(shmem_control_input_fd, CONTROL_INPUT_SIZE); if (result != 0) { - printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(shmem_control_input_fd); + + // Close the descriptor + if (close(shmem_control_input_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Open the control input shared memory as read-only + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY | O_EXCL, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); @@ -3111,7 +3204,7 @@ void server_setup (void) // Map precompile address space if (verbose) gettimeofday(&start_time, NULL); - void * pControl = mmap((void *)CONTROL_ADDR, CONTROL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_fd, 0); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); if (verbose) { gettimeofday(&stop_time, NULL); @@ -3119,22 +3212,78 @@ void server_setup (void) } if (pControl == MAP_FAILED) { - printf("ERROR: Failed calling mmap(control) errno=%d=%s\n", errno, strerror(errno)); + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } - if (pControl != (void *)CONTROL_ADDR) + if (pControl != (void *)CONTROL_INPUT_ADDR) { - printf("ERROR: Called mmap(control) but returned address = %p != 0x%08lx\n", pControl, CONTROL_ADDR); + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); fflush(stdout); fflush(stderr); exit(-1); } - shmem_control_address = (uint64_t *)pControl; - precompile_written_address = &shmem_control_address[0]; - precompile_read_address = &shmem_control_address[4096/8]; - if (verbose) printf("mmap(control) mapped %lu B and returned address %p in %lu us\n", CONTROL_SIZE, shmem_control_address, duration); + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + + /******************/ + /* CONTROL OUTPUT */ + /******************/ + + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_output_name); + + // Create the control shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_output_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_control_output_fd, CONTROL_OUTPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); + + /*************************/ + /* PRECOMPILE SEMAPHORES */ + /*************************/ // Create the semaphore for precompile results available signal assert(strlen(sem_prec_avail_name) > 0); @@ -3441,17 +3590,6 @@ void server_run (void) exit(-1); } - // Sync precompile shared memory - if (precompile_results_enabled) - { - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - /*******/ /* ASM */ /*******/ @@ -3463,6 +3601,12 @@ void server_run (void) gettimeofday(&stop_time,NULL); assembly_duration = TimeDiff(start_time, stop_time); + // Reset precompile read address for next emulation + if (precompile_results_enabled) + { + *precompile_read_address = 0; + } + uint64_t final_trace_size = MEM_CHUNK_ADDRESS - MEM_TRACE_ADDRESS; trace_used_size = final_trace_size + 32; @@ -3644,15 +3788,25 @@ void server_cleanup (void) } // Cleanup CONTROL - result = munmap((void *)shmem_control_address, CONTROL_SIZE); + result = munmap((void *)shmem_control_input_address, CONTROL_INPUT_SIZE); if (result == -1) { - printf("ERROR: Failed calling munmap(control) errno=%d=%s\n", errno, strerror(errno)); + printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); } - result = shm_unlink(shmem_control_name); + result = shm_unlink(shmem_control_input_name); if (result == -1) { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_name, errno, strerror(errno)); + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + } + result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_output_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); } // Semaphores cleanup @@ -4581,65 +4735,31 @@ void file_lock(void) int _wait_for_prec_avail (void) { - // int sem_prec_avail_value = 0; - // if (sem_getvalue(sem_prec_avail, &sem_prec_avail_value) != 0) - // { - // printf("ERROR: wait_for_prec_avail() failed calling sem_getvalue(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - // fflush(stdout); - // fflush(stderr); - // exit(-1); - // } - // printf("_wait_for_prec_avail() sem_prec_avail_value=%d precompile_written_address=%lu precompile_read_address=%lu\n", sem_prec_avail_value, *precompile_written_address, *precompile_read_address); - // Sync precompile shared memory - //__sync_synchronize(); - // if (msync(((void *)shmem_control_address) + CONTROL_SIZE/2, CONTROL_SIZE/2, MS_SYNC) != 0) { - // printf("ERROR: 1 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); - // fflush(stdout); - // fflush(stderr); - // exit(-1); - // } + if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: 1 msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } // Tell the writer that we have read the precompile results sem_post(sem_prec_read); - // Make sure the semaphore is reset before checking the condition - //while (sem_trywait(sem_prec_avail) == 0) {printf("Purging sem_prec_avail\n");}; + // Make sure the semaphore is reset before checking the condition, + // since the caller may have posted it (even several times) before we called sem_wait() + while (sem_trywait(sem_prec_avail) == 0) {/*printf("Purging sem_prec_avail\n");*/}; - // Sync precompile shared memory - //__sync_synchronize(); - // if (msync((void *)shmem_control_address, CONTROL_SIZE/2, MS_SYNC | MS_INVALIDATE) != 0) { - // printf("ERROR: 2 msync failed for shmem_control_address errno=%d=%s\n", errno, strerror(errno)); - // fflush(stdout); - // fflush(stderr); - // exit(-1); - // } - - // Check if there are precompile results available + // Check if there are already precompile results available if (*precompile_written_address > *precompile_read_address) { - // Sync precompile shared memory - //__sync_synchronize(); - // if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC |MS_INVALIDATE) != 0) { - // printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - // fflush(stdout); - // fflush(stderr); - // exit(-1); - // } return 0; - //return; } // Wait again, but blocking this time - //sem_getvalue(sem_prec_avail, &sem_prec_avail_value); - { - printf("_wait_for_prec_avail() calling sem_wait\n"); - uint64_t written = *precompile_written_address; - uint64_t read = *precompile_read_address; - printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", written, read); - } + // printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + int result = sem_wait(sem_prec_avail); - // //sem_getvalue(sem_prec_avail, &sem_prec_avail_value); // printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); if (result == -1) { @@ -4649,55 +4769,31 @@ int _wait_for_prec_avail (void) exit(-1); } - // while (*precompile_written_address == *precompile_read_address) - // { - // usleep(10000); - // } - printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + // Wait for control input shared memory to synchronize + uint64_t written; + uint64_t read; + for (uint64_t i=0; i Date: Tue, 16 Dec 2025 11:31:10 +0000 Subject: [PATCH 070/782] pipes working using CLI with keccak hints --- .../asm-runner/src/asm_services/services.rs | 31 +++- emulator-asm/asm-runner/src/lib.rs | 2 + emulator-asm/asm-runner/src/shmem_reader.rs | 157 ++++++++++++++++ emulator-asm/asm-runner/src/shmem_utils.rs | 24 ++- emulator-asm/asm-runner/src/shmem_writer.rs | 25 +-- executor/src/hints_shmem.rs | 167 +++++++++--------- hints/src/hints_processor.rs | 2 + 7 files changed, 291 insertions(+), 117 deletions(-) create mode 100644 emulator-asm/asm-runner/src/shmem_reader.rs diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 150596d2e..5efbf068f 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -254,11 +254,36 @@ impl AsmServices { .context("Failed to set read timeout")?; // Send request payload - stream.write_all(&out_buffer).context("Failed to write request payload")?; + if let Err(e) = stream.write_all(&out_buffer) { + return Err(anyhow::anyhow!( + "Failed to write request payload to service {} on {}: {}", + service, + addr, + e + )); + } - // Read exactly 40 bytes + // read_exact will block until all 40 bytes are available or timeout/error occurs let mut in_buffer = [0u8; 40]; - stream.read_exact(&mut in_buffer).context("Failed to read full response payload")?; + let mut total_read = 0; + + if let Err(e) = stream.read_exact(&mut in_buffer) { + // Try to read what's available to show partial data + let mut partial_buffer = vec![0u8; 1024]; + if let Ok(n) = stream.read(&mut partial_buffer) { + total_read += n; + } + + return Err(anyhow::anyhow!( + "Failed to read full response payload (expected 40 bytes, got {} bytes) \ + from service {} on {}: {} (error kind: {:?})", + total_read, + service, + addr, + e, + e.kind(), + )); + } // Decode bytes into ResponseData let mut response = ResponseData::default(); diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 98bcb9e77..6967a4b5d 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -17,6 +17,7 @@ mod asm_rh_runner; mod asm_rh_runner_stub; mod asm_runner; mod asm_services; +mod shmem_reader; mod shmem_utils; mod shmem_writer; @@ -37,5 +38,6 @@ pub use asm_rh_runner::*; pub use asm_rh_runner_stub::*; pub use asm_runner::*; pub use asm_services::*; +pub use shmem_reader::*; pub use shmem_utils::*; pub use shmem_writer::*; diff --git a/emulator-asm/asm-runner/src/shmem_reader.rs b/emulator-asm/asm-runner/src/shmem_reader.rs new file mode 100644 index 000000000..e3079548c --- /dev/null +++ b/emulator-asm/asm-runner/src/shmem_reader.rs @@ -0,0 +1,157 @@ +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +use libc::{mmap, shm_open, MAP_FAILED, MAP_SHARED}; +use std::io::{self, Result}; +use std::ptr; +use std::sync::atomic::{compiler_fence, Ordering}; + +use libc::{c_void, close, munmap, PROT_READ, S_IRUSR}; + +pub struct SharedMemoryReader { + ptr: *const u8, + size: usize, + fd: i32, + name: String, +} + +unsafe impl Send for SharedMemoryReader {} +unsafe impl Sync for SharedMemoryReader {} + +impl SharedMemoryReader { + pub fn new(name: &str, size: usize) -> Result { + // Open existing shared memory (read-only) + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + let fd = Self::open_shmem(name, libc::O_RDONLY, S_IRUSR); + + #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] + let fd = Self::open_shmem(name, libc::O_RDONLY, S_IRUSR as u32); + + // Map the memory region for read-only + let ptr = Self::map(fd, size, PROT_READ, false, name); + let ptr_u8 = ptr as *const u8; + + Ok(Self { ptr: ptr_u8, size, fd, name: name.to_string() }) + } + + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + fn open_shmem(name: &str, flags: i32, mode: u32) -> i32 { + let c_name = std::ffi::CString::new(name).expect("CString::new failed"); + let fd = unsafe { shm_open(c_name.as_ptr(), flags, mode) }; + if fd == -1 { + let errno_value = unsafe { *libc::__errno_location() }; + let err = io::Error::from_raw_os_error(errno_value); + let err2 = io::Error::last_os_error(); + panic!("shm_open('{name}') failed: libc::errno:{err} #### last_os_error:{err2}"); + } + fd + } + + #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] + fn open_shmem(_name: &str, _flags: i32, _mode: u32) -> i32 { + 0 + } + + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + fn map(fd: i32, size: usize, prot: i32, unlock_mapped_memory: bool, desc: &str) -> *mut c_void { + let mut flags = MAP_SHARED; + if !unlock_mapped_memory { + flags |= libc::MAP_LOCKED; + } + let mapped = unsafe { mmap(ptr::null_mut(), size, prot, flags, fd, 0) }; + if mapped == MAP_FAILED { + let err = io::Error::last_os_error(); + panic!("mmap failed for '{desc}': {err:?} ({size} bytes)"); + } + mapped + } + + #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] + fn map(_: i32, _: usize, _: i32, _: bool, _: &str) -> *mut c_void { + ptr::null_mut() + } + + unsafe fn unmap(&mut self) { + if munmap(self.ptr as *mut _, self.size) != 0 { + tracing::error!("munmap failed: {:?}", io::Error::last_os_error()); + } else { + self.ptr = ptr::null(); + self.size = 0; + tracing::trace!("Unmapped shared memory '{}'", self.name); + } + } + + /// Reads a u64 from shared memory at a specific offset (in bytes) + /// + /// # Arguments + /// * `offset` - Byte offset from the start of shared memory (must be 8-byte aligned) + /// + /// # Safety + /// This method assumes that: + /// - The shared memory contains at least `offset + 8` bytes of valid data + /// - The offset should be aligned to 8 bytes + /// + /// # Returns + /// * The u64 value read from the specified offset (in native endianness) + #[inline] + pub fn read_u64_at(&self, offset: usize) -> Result { + debug_assert_eq!(offset % 8, 0, "Offset must be 8-byte aligned"); + + // compiler_fence(Ordering::Acquire); + + Ok(unsafe { (self.ptr.add(offset) as *const u64).read() }) + } + + /// Reads a slice of data from shared memory at a specific offset + /// + /// # Type Parameters + /// * `T` - The element type to read + /// + /// # Arguments + /// * `offset` - Byte offset from the start of shared memory + /// * `len` - Number of elements of type T to read + /// + /// # Returns + /// * `Ok(Vec)` - A vector containing the read data + /// * `Err` - If the read would exceed shared memory bounds + pub fn read_slice(&self, offset: usize, len: usize) -> Result> { + let byte_size = len * std::mem::size_of::(); + + if offset + byte_size > self.size { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "Read of {} bytes at offset {} exceeds shared memory capacity ({}) for '{}'", + byte_size, offset, self.size, self.name + ), + )); + } + + compiler_fence(Ordering::Acquire); + + let mut result = Vec::with_capacity(len); + unsafe { + ptr::copy_nonoverlapping(self.ptr.add(offset) as *const T, result.as_mut_ptr(), len); + result.set_len(len); + } + + Ok(result) + } + + /// Returns the size of the shared memory region in bytes + pub fn size(&self) -> usize { + self.size + } + + /// Returns the name of the shared memory region + pub fn name(&self) -> &str { + &self.name + } +} + +impl Drop for SharedMemoryReader { + fn drop(&mut self) { + unsafe { + self.unmap(); + close(self.fd); + } + } +} diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 9a53f69b3..30b61567a 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -304,8 +304,28 @@ impl AsmSharedMemory { } /// Shared memory name for precompile hints data control - pub fn shmem_control_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - format!("{}_{}_control", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) + pub fn shmem_control_writer_name( + port: u16, + asm_service: AsmService, + local_rank: i32, + ) -> String { + format!( + "{}_{}_control_input", + AsmServices::shmem_prefix(port, local_rank), + asm_service.as_str() + ) + } + + pub fn shmem_control_reader_name( + port: u16, + asm_service: AsmService, + local_rank: i32, + ) -> String { + format!( + "{}_{}_control_output", + AsmServices::shmem_prefix(port, local_rank), + asm_service.as_str() + ) } pub fn shmem_output_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index b48b121c9..02dcc6820 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -1,8 +1,7 @@ #[cfg(all(target_os = "linux", target_arch = "x86_64"))] -use libc::{mmap, msync, shm_open, MAP_FAILED, MAP_SHARED, MS_INVALIDATE, MS_SYNC}; +use libc::{mmap, msync, shm_open, MAP_FAILED, MAP_SHARED, MS_SYNC}; use std::io::{self, Result}; use std::ptr; -use std::sync::atomic::{fence, Ordering}; use libc::{c_void, close, munmap, PROT_READ, PROT_WRITE, S_IRUSR, S_IWUSR}; @@ -161,13 +160,6 @@ impl SharedMemoryWriter { self.current_ptr = self.ptr; } } - - // Force changes to be flushed to the shared memory - // TODO! only msync the affected regions - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - if msync(self.ptr as *mut _, self.size, MS_SYNC) != 0 { - return Err(io::Error::last_os_error()); - } } Ok(()) @@ -189,15 +181,6 @@ impl SharedMemoryWriter { pub fn read_u64_at(&self, offset: usize) -> Result { debug_assert_eq!(offset % 8, 0, "Offset must be 8-byte aligned"); - unsafe { - fence(Ordering::SeqCst); - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - if msync(self.ptr.add(offset) as *mut _, std::mem::size_of::(), MS_INVALIDATE) != 0 - { - return Err(io::Error::last_os_error()); - } - } - Ok(unsafe { (self.ptr.add(offset) as *const u64).read() }) } @@ -217,12 +200,6 @@ impl SharedMemoryWriter { unsafe { (self.ptr.add(offset) as *mut u64).write(value); - - fence(Ordering::SeqCst); - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - if msync(self.ptr.add(offset) as *mut _, std::mem::size_of::(), MS_SYNC) != 0 { - return Err(io::Error::last_os_error()); - } } Ok(()) diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index a7fdb51a4..d48cd42c0 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -4,7 +4,9 @@ //! using SharedMemoryWriter instances. use anyhow::Result; -use asm_runner::{AsmMTHeader, AsmService, AsmServices, AsmSharedMemory, SharedMemoryWriter}; +use asm_runner::{ + AsmMTHeader, AsmService, AsmServices, AsmSharedMemory, SharedMemoryReader, SharedMemoryWriter, +}; use named_sem::NamedSemaphore; use std::sync::Mutex; use tracing::debug; @@ -12,7 +14,8 @@ use zisk_hints::HintsSink; /// Names for a service's shared memory and semaphore resources struct ServiceResourceNames { - control_name: String, + control_writer: String, + control_reader: String, data_name: String, sem_available_name: String, sem_read_name: String, @@ -21,7 +24,10 @@ struct ServiceResourceNames { impl ServiceResourceNames { fn new(service: &AsmService, port: u16, local_rank: i32) -> Self { Self { - control_name: AsmSharedMemory::::shmem_control_name( + control_writer: AsmSharedMemory::::shmem_control_writer_name( + port, *service, local_rank, + ), + control_reader: AsmSharedMemory::::shmem_control_reader_name( port, *service, local_rank, ), data_name: AsmSharedMemory::::shmem_precompile_name( @@ -41,6 +47,8 @@ impl ServiceResourceNames { struct ServiceResources { /// Control shared memory writer control_writer: SharedMemoryWriter, + /// Control shared memory reader + control_reader: SharedMemoryReader, /// Data shared memory writer data_writer: SharedMemoryWriter, /// Semaphore to signal data availability @@ -59,10 +67,9 @@ unsafe impl Send for HintsShmem {} unsafe impl Sync for HintsShmem {} impl HintsShmem { - const CONTROL_PRECOMPILE_SIZE: u64 = 0x2000; // 8KB + const CONTROL_PRECOMPILE_SIZE: u64 = 0x1000; // 4KB const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB // const MAX_PRECOMPILE_SIZE: u64 = 0x100000; // 1MB - const BUFFER_THRESHOLD: u64 = 1000; // 1000 bytes - threshold for signaling reader /// Create a new HintsShmem with the given shared memory names and unlock option. /// @@ -90,9 +97,13 @@ impl HintsShmem { }) .collect(); - let resources = Mutex::new(Self::create_resources(resources_names, unlock_mapped_memory)?); + let mut resources = Self::create_resources(resources_names, unlock_mapped_memory)?; - Ok(Self { resources }) + for resource in resources.iter_mut() { + resource.control_writer.write_u64_at(0, 0)?; + } + + Ok(Self { resources: Mutex::new(resources) }) } /// Initialize the shared memory writers for the pipeline. @@ -105,50 +116,43 @@ impl HintsShmem { ) -> Result> { debug!("Initializing resources for precompile hints"); - Ok(resources_names + resources_names .iter() - .map(|names: &ServiceResourceNames| ServiceResources { - control_writer: Self::create_writer( - &names.control_name, - Self::CONTROL_PRECOMPILE_SIZE as usize, - unlock_mapped_memory, - ), - data_writer: Self::create_writer( - &names.data_name, - Self::MAX_PRECOMPILE_SIZE as usize, - unlock_mapped_memory, - ), - sem_available: Self::create_semaphore(&names.sem_available_name), - sem_read: Self::create_semaphore(&names.sem_read_name), + .map(|names: &ServiceResourceNames| -> Result { + Ok(ServiceResources { + control_writer: SharedMemoryWriter::new( + &names.control_writer, + Self::CONTROL_PRECOMPILE_SIZE as usize, + unlock_mapped_memory, + )?, + control_reader: SharedMemoryReader::new( + &names.control_reader, + Self::CONTROL_PRECOMPILE_SIZE as usize, + )?, + data_writer: SharedMemoryWriter::new( + &names.data_name, + Self::MAX_PRECOMPILE_SIZE as usize, + unlock_mapped_memory, + )?, + sem_available: NamedSemaphore::create(&names.sem_available_name, 0).map_err( + |e| { + anyhow::anyhow!( + "Failed to create semaphore '{}': {}", + names.sem_available_name, + e + ) + }, + )?, + sem_read: NamedSemaphore::create(&names.sem_read_name, 0).map_err(|e| { + anyhow::anyhow!( + "Failed to create semaphore '{}': {}", + names.sem_read_name, + e + ) + })?, + }) }) - .collect()) - } - - /// Create a SharedMemoryWriter with error handling. - fn create_writer(name: &str, size: usize, unlock_mapped_memory: bool) -> SharedMemoryWriter { - SharedMemoryWriter::new(name, size, unlock_mapped_memory) - .expect("Failed to create SharedMemoryWriter for precompile hints") - } - - /// Create a NamedSemaphore with error handling. - fn create_semaphore(name: &str) -> NamedSemaphore { - NamedSemaphore::create(name.to_string(), 0) - .expect("Failed to create semaphore for precompile hints") - } - - #[inline] - fn get_write_size(&self, writer: &SharedMemoryWriter) -> Result { - writer.read_u64_at(0).map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn set_write_size(&self, writer: &SharedMemoryWriter, size: u64) -> anyhow::Result<()> { - writer.write_u64_at(0, size).map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn get_read_size(&self, writer: &SharedMemoryWriter) -> Result { - writer.read_u64_at(4096).map_err(|e| anyhow::anyhow!(e)) + .collect() } } @@ -166,49 +170,36 @@ impl HintsSink for HintsShmem { let mut resources = self.resources.lock().unwrap(); - // for resource in resources.iter_mut() { - let resource = &mut resources[0]; - - // Read current positions - let write_pos = self.get_write_size(&resource.control_writer)?; - let read_pos = self.get_read_size(&resource.control_writer)?; - - // Calculate occupied space in ring buffer (positions are absolute values) - let occupied_space = write_pos - read_pos; - let available_space = (Self::MAX_PRECOMPILE_SIZE >> 3) - occupied_space; - - debug_assert!( - available_space <= (Self::MAX_PRECOMPILE_SIZE >> 3), - "Available space calculation error" - ); - // TODO! Check for overflow of write_pos and read_pos and handle it - - // Flow control based on buffer occupancy - if available_space < data_size { - // Not enough space - signal reader and wait for consumption - // resource.sem_available.post()?; - if write_pos > 131000 { - println!("Waiting on sem_read for precompile hints write_pos={} occupied={} available={} needed={}", - write_pos, occupied_space, available_space, data_size); + for resource in resources.iter_mut() { + // Read current positions + let write_pos = resource.control_writer.read_u64_at(0)?; + let read_pos = resource.control_reader.read_u64_at(0)?; + + // Calculate occupied space in ring buffer (positions are absolute values) + let occupied_space = write_pos - read_pos; + let available_space = (Self::MAX_PRECOMPILE_SIZE >> 3) - occupied_space; + + debug_assert!( + available_space <= (Self::MAX_PRECOMPILE_SIZE >> 3), + "Available space calculation error" + ); + // TODO! Check for overflow of write_pos and read_pos and handle it + + // Flow control based on buffer occupancy + if available_space < data_size { + // Not enough space - signal reader and wait for consumption + // resource.sem_available.post()?; + resource.sem_read.wait()?; } - resource.sem_read.wait()?; - } else if available_space < Self::BUFFER_THRESHOLD { - // Buffer getting full - signal reader but don't wait - // resource.sem_available.post()?; - } - // Write data to shared memory with automatic wraparound - resource.data_writer.write_ring_buffer(&processed)?; + // Write data to shared memory with automatic wraparound + resource.data_writer.write_ring_buffer(&processed)?; - // Update write position in control memory with wraparound - self.set_write_size(&resource.control_writer, write_pos + data_size)?; + // Update write position in control memory with wraparound + resource.control_writer.write_u64_at(0, write_pos + data_size)?; - resource.sem_available.post()?; - - // if write_pos > 131000 { - // println!("Posted available semaphore for precompile hints {}", write_pos); - // } - // } + resource.sem_available.post()?; + } Ok(()) } diff --git a/hints/src/hints_processor.rs b/hints/src/hints_processor.rs index 3d08ddfd2..6acfaba39 100644 --- a/hints/src/hints_processor.rs +++ b/hints/src/hints_processor.rs @@ -324,6 +324,8 @@ impl PrecompileHintsProcessor { has_ctrl_end = true; idx += length + 1; + debug!("CTRL_END received, all hints processed"); + // CTRL_END should be the last message - verify and break if idx < hints.len() { return Err(anyhow::anyhow!( From 59a9a5b54a3d1eeedf36d29d8e28ced9d220f8d5 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:31:56 +0000 Subject: [PATCH 071/782] minor fix settings.json --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9709e08be..56aa3b430 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.formatOnSave": true, - "editor.hover.enabled": true + "editor.hover.enabled": "on" }, "editor.rulers": [ 100 From 68fe758791d3b5780b1c53f2abfecdff145757a1 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:35:48 +0000 Subject: [PATCH 072/782] minor fix --- executor/src/hints_shmem.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index d48cd42c0..8b311df40 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -188,7 +188,6 @@ impl HintsSink for HintsShmem { // Flow control based on buffer occupancy if available_space < data_size { // Not enough space - signal reader and wait for consumption - // resource.sem_available.post()?; resource.sem_read.wait()?; } From 95324c76a66cfa95eca4f065bc6b802855eab80c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 16 Dec 2025 18:00:46 +0000 Subject: [PATCH 073/782] distributed working (non opimized) with hints stream --- distributed/crates/common/src/dto.rs | 23 ++++-- .../coordinator/src/cli/handler_prove.rs | 24 +++--- .../crates/coordinator/src/cli/main.rs | 8 +- .../crates/coordinator/src/coordinator.rs | 8 +- .../grpc-api/proto/zisk_distributed_api.proto | 12 ++- .../crates/grpc-api/src/conversions.rs | 53 ++++++------ distributed/crates/worker/src/worker.rs | 8 +- distributed/crates/worker/src/worker_node.rs | 81 ++++++++++++++----- hints/src/hints_stream.rs | 1 + sdk/src/prover/asm.rs | 4 + sdk/src/prover/emu.rs | 4 + sdk/src/prover/mod.rs | 7 ++ 12 files changed, 156 insertions(+), 77 deletions(-) diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 786c4f832..8856b3e92 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -4,7 +4,7 @@ //! These DTOs serve as the canonical data structures for business logic, separate from external //! representations like gRPC protobuf types or serialization formats. -use std::{fmt::Display, path::PathBuf}; +use std::fmt::Display; use crate::{ComputeCapacity, DataId, JobId, JobPhase, JobState, WorkerId, WorkerState}; use borsh::{BorshDeserialize, BorshSerialize}; @@ -68,17 +68,26 @@ pub struct SystemStatusDto { #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[repr(i32)] pub enum InputModeDto { - InputModeNone = 0, // No input provided - InputModePath(PathBuf) = 1, // Input will be provided as a path - InputModeData(PathBuf) = 2, // Input data will be sent directly + // No input provided + InputModeNone = 0, + // Input will be provided as a path. First String is the inputs path, + // second String is the precompiles hints path + InputModePath(String, String) = 1, + // Input will be provided as a path. First String is the inputs path URI, + // second String is the precompiles hints URI + InputModeData(String, String) = 2, } impl Display for InputModeDto { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { InputModeDto::InputModeNone => write!(f, "None"), - InputModeDto::InputModePath(path) => write!(f, "Path({})", path.display()), - InputModeDto::InputModeData(path) => write!(f, "Data({})", path.display()), + InputModeDto::InputModePath(inputs, hints) => { + write!(f, "Path(inputs: {}, hints: {})", inputs, hints) + } + InputModeDto::InputModeData(inputs, hints) => { + write!(f, "Data(inputs: {}, hints: {})", inputs, hints) + } } } } @@ -160,7 +169,7 @@ pub struct ContributionParamsDto { #[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] pub enum InputSourceDto { - InputPath(String), + InputPath(String, String), // (inputs_path, hints_path) InputData(Vec), InputNull, } diff --git a/distributed/crates/coordinator/src/cli/handler_prove.rs b/distributed/crates/coordinator/src/cli/handler_prove.rs index f8ff8cccb..f19e75690 100644 --- a/distributed/crates/coordinator/src/cli/handler_prove.rs +++ b/distributed/crates/coordinator/src/cli/handler_prove.rs @@ -12,7 +12,8 @@ use zisk_distributed_grpc_api::{ pub async fn handle( coordinator_url: Option, data_id: Option, - input_path: Option, + inputs_uri: Option, + hints_uri: Option, direct_inputs: bool, compute_capacity: u32, simulated_node: Option, @@ -28,24 +29,20 @@ pub async fn handle( let channel = Channel::from_shared(coordinator_url)?.connect().await?; let mut client = ZiskDistributedApiClient::new(channel); - let (input_mode, input_path) = if let Some(ref path) = input_path { - if path.as_os_str().is_empty() { - return Err(anyhow::anyhow!("Input path cannot be empty")); + let input_mode = if inputs_uri.is_some() { + if direct_inputs { + InputMode::Data + } else { + InputMode::Path } - - let input_path = Some(path.to_string_lossy().to_string()); - - let input_mode = if direct_inputs { InputMode::Data } else { InputMode::Path }; - - (input_mode, input_path) } else { - (InputMode::None, None) + InputMode::None }; // ID will be id if present, else input file name or random UUID let data_id = if let Some(id) = data_id { id - } else if let Some(ref path) = input_path { + } else if let Some(ref path) = inputs_uri { PathBuf::from(path).file_stem().unwrap().to_string_lossy().to_string() } else { uuid::Uuid::new_v4().to_string() @@ -55,7 +52,8 @@ pub async fn handle( data_id, compute_capacity, input_mode: input_mode.into(), - input_path, + inputs_uri, + hints_uri, simulated_node, }; diff --git a/distributed/crates/coordinator/src/cli/main.rs b/distributed/crates/coordinator/src/cli/main.rs index 1d9b4df9f..9cc7353a0 100644 --- a/distributed/crates/coordinator/src/cli/main.rs +++ b/distributed/crates/coordinator/src/cli/main.rs @@ -67,7 +67,11 @@ enum ZiskCoordinatorCommands { /// Path to the input file #[arg(long, help = "Path to the input file for proof generation")] - input: Option, + input: Option, + + /// Precompiles Hints path + #[arg(long, help = "Path to the precompiles hints file for proof generation")] + precompile_hints_path: Option, /// Whether to send the input data directly #[clap(short = 'x', long, default_value_t = false)] @@ -92,6 +96,7 @@ async fn main() -> Result<()> { coordinator_url, data_id, input, + precompile_hints_path, direct_inputs, compute_capacity, simulated_node, @@ -101,6 +106,7 @@ async fn main() -> Result<()> { coordinator_url, data_id, input, + precompile_hints_path, direct_inputs, compute_capacity, simulated_node, diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 164e8ce6d..5da507ae9 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -605,11 +605,11 @@ impl Coordinator { active_workers: &[WorkerId], ) -> CoordinatorResult<()> { let input_source = match job.input_mode { - InputModeDto::InputModePath(ref path) => { - InputSourceDto::InputPath(path.display().to_string()) + InputModeDto::InputModePath(ref inputs_uri, ref hints_uri) => { + InputSourceDto::InputPath(inputs_uri.clone(), hints_uri.clone()) } - InputModeDto::InputModeData(ref path) => { - let inputs = tokio::fs::read(path).await.map_err(|e| { + InputModeDto::InputModeData(ref inputs_uri, ref _hints_uri) => { + let inputs = tokio::fs::read(inputs_uri).await.map_err(|e| { CoordinatorError::Internal(format!( "Failed to read input data for job {}: {}", job.job_id, e diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index c843c149e..7508d6547 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -56,8 +56,9 @@ message LaunchProofRequest { string data_id = 1; uint32 compute_capacity = 2; InputMode input_mode = 3; - optional string input_path = 4; - optional uint32 simulated_node = 5; // If set, indicates this is a simulated worker + optional string inputs_uri = 4; + optional string hints_uri = 5; + optional uint32 simulated_node = 6; // If set, indicates this is a simulated worker } enum InputMode { @@ -231,7 +232,7 @@ enum TaskType { message ContributionParams { string data_id = 1; oneof input_source { - string input_path = 2; + InputUris input_path = 2; bytes input_data = 3; } uint32 rank_id = 4; @@ -240,6 +241,11 @@ message ContributionParams { uint32 job_compute_units = 7; } +message InputUris { + string inputs_path = 1; + string hints_path = 2; +} + message ProveParams { repeated Challenges challenges = 1; } diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index eaf863357..140a0be8a 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -8,15 +8,13 @@ //! The gRPC protobuf compiler generates Rust types that don't always match our internal domain //! model. All conversions implement the `From` and/or `Into` traits for idiomatic Rust usage. -use std::path::PathBuf; - use crate::{ contribution_params::InputSource, coordinator_message::Payload, execute_task_request, execute_task_response, job_status_response, jobs_list_response, launch_proof_response, system_status_response, workers_list_response, AggParams, Challenges, ComputeCapacity as GrpcComputeCapacity, ContributionParams, CoordinatorMessage, - ExecuteTaskRequest, ExecuteTaskResponse, Heartbeat, HeartbeatAck, InputMode, JobCancelled, - JobStatus, JobStatusResponse, JobsList, JobsListResponse, LaunchProofRequest, + ExecuteTaskRequest, ExecuteTaskResponse, Heartbeat, HeartbeatAck, InputMode, InputUris, + JobCancelled, JobStatus, JobStatusResponse, JobsList, JobsListResponse, LaunchProofRequest, LaunchProofResponse, Metrics, Proof, ProofList, ProveParams, Shutdown, StatusInfoResponse, SystemStatus, SystemStatusResponse, TaskType, WorkerError, WorkerInfo, WorkerReconnectRequest, WorkerRegisterRequest, WorkerRegisterResponse, WorkersList, WorkersListResponse, @@ -155,13 +153,13 @@ impl From for SystemStatusResponse { impl From for LaunchProofRequest { fn from(dto: LaunchProofRequestDto) -> Self { - let (input_mode, input_path) = match dto.input_mode { - InputModeDto::InputModeNone => (InputMode::None, None), - InputModeDto::InputModePath(path) => { - (InputMode::Path, Some(path.display().to_string())) + let (input_mode, inputs_uri, hints_uri) = match dto.input_mode { + InputModeDto::InputModeNone => (InputMode::None, None, None), + InputModeDto::InputModePath(inputs, hints) => { + (InputMode::Path, Some(inputs), Some(hints)) } - InputModeDto::InputModeData(path) => { - (InputMode::Data, Some(path.display().to_string())) + InputModeDto::InputModeData(inputs, hints) => { + (InputMode::Data, Some(inputs), Some(hints)) } }; @@ -169,7 +167,8 @@ impl From for LaunchProofRequest { data_id: dto.data_id.into(), compute_capacity: dto.compute_capacity, input_mode: input_mode.into(), - input_path, + inputs_uri, + hints_uri, simulated_node: dto.simulated_node, } } @@ -187,22 +186,22 @@ impl TryFrom for LaunchProofRequestDto { input_mode: match InputMode::try_from(req.input_mode).unwrap_or(InputMode::None) { InputMode::None => InputModeDto::InputModeNone, InputMode::Path => { - // Use the input_path field when available - if let Some(path) = req.input_path { - InputModeDto::InputModePath(PathBuf::from(path)) - } else { - return Err(anyhow::anyhow!( - "Input mode is Path but input_path is missing" - )); - } + let inputs_uri = req.inputs_uri.ok_or_else(|| { + anyhow::anyhow!("Input mode is Path but inputs_uri is missing") + })?; + let hints_uri = req.hints_uri.ok_or_else(|| { + anyhow::anyhow!("Input mode is Path but hints_uri is missing") + })?; + InputModeDto::InputModePath(inputs_uri, hints_uri) } InputMode::Data => { - // Use the input_path field when available - if let Some(path) = req.input_path { - InputModeDto::InputModeData(PathBuf::from(path)) - } else { - InputModeDto::InputModeNone // Fallback if path is missing - } + let inputs_uri = req.inputs_uri.ok_or_else(|| { + anyhow::anyhow!("Input mode is Data but inputs_uri is missing") + })?; + let hints_uri = req.hints_uri.ok_or_else(|| { + anyhow::anyhow!("Input mode is Data but hints_uri is missing") + })?; + InputModeDto::InputModeData(inputs_uri, hints_uri) } }, simulated_node: req.simulated_node, @@ -328,7 +327,9 @@ impl From for ExecuteTaskRequest { impl From for ContributionParams { fn from(dto: ContributionParamsDto) -> Self { let input_source = match dto.input_source { - InputSourceDto::InputPath(path) => Some(InputSource::InputPath(path)), + InputSourceDto::InputPath(inputs_path, hints_uri) => { + Some(InputSource::InputPath(InputUris { inputs_path, hints_path: hints_uri })) + } InputSourceDto::InputData(data) => Some(InputSource::InputData(data)), InputSourceDto::InputNull => None, }; diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index a25df6511..bb180ea73 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -9,7 +9,7 @@ use std::fs; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; -use zisk_common::io::ZiskStdin; +use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; use zisk_distributed_common::{ComputeCapacity, JobId, WorkerId}; use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProver}; @@ -531,9 +531,11 @@ impl Worker { let phase = proofman::ProvePhase::Contributions; match input_source { - InputSourceDto::InputPath(input_path) => { - let stdin = ZiskStdin::from_file(input_path)?; + InputSourceDto::InputPath(inputs_uri, hints_uri) => { + let stdin = ZiskStdin::from_file(inputs_uri)?; + let hints_stdin = StreamSource::from_file(hints_uri)?; // TODO!!!!!! CHANGE THIS prover.set_stdin(stdin); + prover.set_hints_stream(hints_stdin)?; } InputSourceDto::InputData(input_data) => { let stdin = ZiskStdin::from_vec(input_data); diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index f656ed2ab..83c85ce6e 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -516,13 +516,24 @@ impl WorkerNodeGrpc { let job_id = JobId::from(request.job_id); let input_source = match params.input_source { - Some(InputSource::InputPath(ref path)) => { - let input_path = self.worker_config.worker.inputs_folder.join(PathBuf::from(path)); + Some(InputSource::InputPath(ref input_uris)) => { + // Validate and get the full path + let inputs_uri = Self::validate_subdir( + &self.worker_config.worker.inputs_folder, + &PathBuf::from(&input_uris.inputs_path), + ) + .await?; - // Validate that input_path is a subdirectory of inputs_folder - Self::validate_subdir(&self.worker_config.worker.inputs_folder, &input_path)?; + let hints_uri = Self::validate_subdir( + &self.worker_config.worker.inputs_folder, + &PathBuf::from(&input_uris.hints_path), + ) + .await?; - InputSourceDto::InputPath(input_path.display().to_string()) + InputSourceDto::InputPath( + inputs_uri.to_string_lossy().to_string(), + hints_uri.to_string_lossy().to_string(), + ) } Some(InputSource::InputData(data)) => InputSourceDto::InputData(data), None => { @@ -549,35 +560,65 @@ impl WorkerNodeGrpc { Ok(()) } - fn validate_subdir(base: &Path, candidate: &Path) -> Result<()> { - let base = base.canonicalize().map_err(|e| anyhow!("Inputs folder error: {e}"))?; - - // Timeout 60 seconds + /// Validates that a subpath is within the base directory and waits for it to exist. + /// + /// This function joins the base directory with the provided subpath, waits for the + /// resulting file/directory to appear (up to 60 seconds), and validates that the + /// resolved path is within the base directory to prevent path traversal attacks. + /// + /// # Security Considerations + /// - Joins base and subpath before validation + /// - Canonicalizes paths to resolve symlinks and relative components (e.g., `..`) + /// - Validates that the resolved path is within the base directory + /// - Note: There's a small TOCTOU window between file existence check and canonicalization + /// where a file could theoretically be replaced with a malicious symlink + /// + /// # Arguments + /// * `base_dir` - The base directory that must contain the subpath + /// * `subpath` - The relative path within base_dir (can include subdirectories) + /// + /// # Returns + /// * `Ok(PathBuf)` - The validated, canonicalized full path + /// * `Err` - If the path doesn't appear within timeout or is outside base directory + async fn validate_subdir(base_dir: &Path, subpath: &Path) -> Result { + let base_canonical = + base_dir.canonicalize().map_err(|e| anyhow!("Inputs folder error: {e}"))?; + + // Join base with subpath to get full path + let full_path = base_dir.join(subpath); + + // Wait for file to appear (timeout: 60 seconds) let timeout = Duration::from_secs(60); let start = std::time::Instant::now(); + let poll_interval = Duration::from_millis(500); // Poll every 500ms - while !candidate.exists() { + while !full_path.exists() { if start.elapsed() > timeout { return Err(anyhow!( - "Input path {:?} did not appear within {:?}", - candidate, + "Input path {:?} (subpath: {:?}) did not appear within {:?}", + full_path, + subpath, timeout )); } - std::thread::sleep(Duration::from_millis(10)); + tokio::time::sleep(poll_interval).await; } - info!("Found input file {:?} (elapsed: {:?})", candidate, start.elapsed()); + info!("Found input path {:?} (elapsed: {:?})", full_path, start.elapsed()); - let candidate = candidate.canonicalize().map_err(|e| anyhow!("Input path error: {e}"))?; + // Canonicalize immediately after existence check to minimize TOCTOU window + let path_canonical = + full_path.canonicalize().map_err(|e| anyhow!("Input path error: {e}"))?; - if candidate.starts_with(&base) { - Ok(()) + // Validate that the canonical path is within the base directory + if path_canonical.starts_with(&base_canonical) { + Ok(path_canonical) } else { Err(anyhow!( - "Input path {:?} must be a subdirectory of inputs folder {:?}", - candidate, - base + "Input path {:?} (resolved to {:?}) is outside base directory {:?}", + subpath, + path_canonical, + base_canonical )) } } diff --git a/hints/src/hints_stream.rs b/hints/src/hints_stream.rs index 6dfbe187e..49f899a61 100644 --- a/hints/src/hints_stream.rs +++ b/hints/src/hints_stream.rs @@ -54,6 +54,7 @@ impl HintsStream { /// # Arguments /// * `stream` - The new StreamSource source for reading hints. pub fn set_hints_stream(&mut self, mut stream: StreamSource) -> Result<()> { + // TODO!!!!!! Check if it is necessary to close the previous stream // Stop the existing thread if running self.stop_thread(); diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index a6fdffdb5..c8a3e1e5a 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -89,6 +89,10 @@ impl ProverEngine for AsmProver { self.core_prover.backend.witness_lib.set_stdin(stdin); } + fn set_hints_stream(&self, hints_stream: StreamSource) -> anyhow::Result<()> { + self.core_prover.backend.witness_lib.set_hints_stream(hints_stream) + } + fn executed_steps(&self) -> u64 { self.core_prover .backend diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index ba15f5675..88a44cdbe 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -77,6 +77,10 @@ impl ProverEngine for EmuProver { self.core_prover.backend.witness_lib.set_stdin(stdin); } + fn set_hints_stream(&self, _: StreamSource) -> Result<()> { + unreachable!("EMU prover does not support precompile hints"); + } + fn executed_steps(&self) -> u64 { self.core_prover .backend diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 3934732fb..0f9b03c03 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -47,6 +47,8 @@ pub trait ProverEngine { fn set_stdin(&self, stdin: ZiskStdin); + fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()>; + fn executed_steps(&self) -> u64; fn execute( @@ -110,6 +112,11 @@ impl ZiskProver { self.prover.set_stdin(stdin); } + /// Set the hints stream for the current proof. + pub fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()> { + self.prover.set_hints_stream(hints_stream) + } + /// Get the world rank of the prover. The world rank is the rank of the prover in the global MPI context. /// If MPI is not used, this will always return 0. pub fn world_rank(&self) -> i32 { From 3bf779798210b439a5c5fb79a29702194645ed1e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:01:08 +0000 Subject: [PATCH 074/782] remove AsmSharedMemory generic parameter --- emulator-asm/asm-runner/src/asm_mo_runner.rs | 15 +++++---- emulator-asm/asm-runner/src/asm_mt_runner.rs | 15 +++++---- emulator-asm/asm-runner/src/asm_rh.rs | 6 ++-- emulator-asm/asm-runner/src/asm_rh_runner.rs | 9 +++--- emulator-asm/asm-runner/src/shmem_utils.rs | 32 +++++++++++--------- executor/src/executor.rs | 4 +-- executor/src/hints_shmem.rs | 24 ++++----------- 7 files changed, 47 insertions(+), 58 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index 93eab3f06..a64f6631a 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -20,7 +20,7 @@ use zisk_common::ExecutorStatsEvent; use mem_common::save_plans; pub struct PreloadedMO { - pub output_shmem: AsmSharedMemory, + pub output_shmem: AsmSharedMemory, mem_planner: Option, handle_mo: Option>, } @@ -37,11 +37,10 @@ impl PreloadedMO { AsmServices::default_port(&AsmService::MO, local_rank) }; - let output_name = - AsmSharedMemory::::shmem_output_name(port, AsmService::MO, local_rank); + let output_name = AsmSharedMemory::shmem_output_name(port, AsmService::MO, local_rank); let output_shared_memory = - AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; + AsmSharedMemory::open_and_map::(&output_name, unlock_mapped_memory)?; Ok(Self { output_shmem: output_shared_memory, @@ -99,7 +98,7 @@ impl AsmRunnerMO { }; let sem_chunk_done_name = - AsmSharedMemory::::shmem_chunk_done_name(port, AsmService::MO, local_rank); + AsmSharedMemory::shmem_chunk_done_name(port, AsmService::MO, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; @@ -128,7 +127,7 @@ impl AsmRunnerMO { .unwrap_or_else(|| preloaded.handle_mo.take().unwrap().join().unwrap()); // Get the pointer to the data in the shared memory. - let mut data_ptr = preloaded.output_shmem.data_ptr() as *const AsmMOChunk; + let mut data_ptr = preloaded.output_shmem.data_ptr::() as *const AsmMOChunk; // Initialize C++ memory operations trace mem_planner.execute(); @@ -162,7 +161,7 @@ impl AsmRunnerMO { if data_ptr >= threshold && preloaded .output_shmem - .check_size_changed(&mut data_ptr) + .check_size_changed::(&mut data_ptr) .context("Failed to check and remap shared memory for MO trace")? { threshold = unsafe { @@ -206,7 +205,7 @@ impl AsmRunnerMO { Err(e) => { error!("Semaphore '{}' error: {:?}", sem_chunk_done_name, e); - break preloaded.output_shmem.map_header().exit_code; + break preloaded.output_shmem.map_header::().exit_code; } } }; diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index d3fbcbe8b..41ce50507 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -30,7 +30,7 @@ pub enum MinimalTraces { } pub struct PreloadedMT { - pub output_shmem: AsmSharedMemory, + pub output_shmem: AsmSharedMemory, } impl PreloadedMT { @@ -45,11 +45,10 @@ impl PreloadedMT { AsmServices::default_port(&AsmService::MT, local_rank) }; - let output_name = - AsmSharedMemory::::shmem_output_name(port, AsmService::MT, local_rank); + let output_name = AsmSharedMemory::shmem_output_name(port, AsmService::MT, local_rank); let output_shared_memory = - AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; + AsmSharedMemory::open_and_map::(&output_name, unlock_mapped_memory)?; Ok(Self { output_shmem: output_shared_memory }) } @@ -90,7 +89,7 @@ impl AsmRunnerMT { }; let sem_chunk_done_name = - AsmSharedMemory::::shmem_chunk_done_name(port, AsmService::MT, local_rank); + AsmSharedMemory::shmem_chunk_done_name(port, AsmService::MT, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; @@ -116,7 +115,7 @@ impl AsmRunnerMT { let mut chunk_id = ChunkId(0); // Get the pointer to the data in the shared memory. - let mut data_ptr = preloaded.output_shmem.data_ptr() as *const AsmMTChunk; + let mut data_ptr = preloaded.output_shmem.data_ptr::() as *const AsmMTChunk; let mut emu_traces = Vec::new(); let mut handles = Vec::new(); @@ -153,7 +152,7 @@ impl AsmRunnerMT { if data_ptr >= threshold && preloaded .output_shmem - .check_size_changed(&mut data_ptr) + .check_size_changed::(&mut data_ptr) .context("Failed to check and remap shared memory for MO trace")? { threshold = unsafe { @@ -187,7 +186,7 @@ impl AsmRunnerMT { break 1; } - break preloaded.output_shmem.map_header().exit_code; + break preloaded.output_shmem.map_header::().exit_code; } } }; diff --git a/emulator-asm/asm-runner/src/asm_rh.rs b/emulator-asm/asm-runner/src/asm_rh.rs index 97c38187d..3f072de0d 100644 --- a/emulator-asm/asm-runner/src/asm_rh.rs +++ b/emulator-asm/asm-runner/src/asm_rh.rs @@ -36,9 +36,9 @@ impl AsmRHData { /// /// # Safety /// This function is unsafe because it reads from a raw pointer in shared memory. - pub fn from_shared_memory(asm_shared_memory: &AsmSharedMemory) -> AsmRHData { + pub fn from_shared_memory(asm_shared_memory: &AsmSharedMemory) -> AsmRHData { unsafe { - let data_ptr = asm_shared_memory.data_ptr() as *mut u64; + let data_ptr = asm_shared_memory.data_ptr::() as *mut u64; // BIOS chunk data let bios_data_ptr = data_ptr; let bios_len = std::ptr::read(bios_data_ptr) as usize; @@ -54,7 +54,7 @@ impl AsmRHData { let prog_inst_count = Vec::from_raw_parts(prog_data_ptr, prog_len, prog_len); AsmRHData { - steps: asm_shared_memory.map_header().steps, + steps: asm_shared_memory.map_header::().steps, bios_inst_count, prog_inst_count, } diff --git a/emulator-asm/asm-runner/src/asm_rh_runner.rs b/emulator-asm/asm-runner/src/asm_rh_runner.rs index c17b6218a..c6ad9b845 100644 --- a/emulator-asm/asm-runner/src/asm_rh_runner.rs +++ b/emulator-asm/asm-runner/src/asm_rh_runner.rs @@ -8,7 +8,7 @@ use std::sync::atomic::{fence, Ordering}; use std::time::Duration; pub struct PreloadedRH { - pub output_shmem: AsmSharedMemory, + pub output_shmem: AsmSharedMemory, } impl PreloadedRH { @@ -23,11 +23,10 @@ impl PreloadedRH { AsmServices::default_port(&AsmService::RH, local_rank) }; - let output_name = - AsmSharedMemory::::shmem_output_name(port, AsmService::RH, local_rank); + let output_name = AsmSharedMemory::shmem_output_name(port, AsmService::RH, local_rank); let output_shared_memory = - AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; + AsmSharedMemory::open_and_map::(&output_name, unlock_mapped_memory)?; Ok(Self { output_shmem: output_shared_memory }) } @@ -76,7 +75,7 @@ impl AsmRunnerRH { }; let sem_chunk_done_name = - AsmSharedMemory::::shmem_chunk_done_name(port, AsmService::RH, local_rank); + AsmSharedMemory::shmem_chunk_done_name(port, AsmService::RH, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 30b61567a..55d07185b 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -23,23 +23,22 @@ pub enum AsmSharedMemoryMode { ReadWrite, } -pub struct AsmSharedMemory { +pub struct AsmSharedMemory { _fd: i32, mapped_ptr: *mut c_void, mapped_size: usize, shmem_name: String, - _phantom: std::marker::PhantomData, } -unsafe impl Send for AsmSharedMemory {} -unsafe impl Sync for AsmSharedMemory {} +unsafe impl Send for AsmSharedMemory {} +unsafe impl Sync for AsmSharedMemory {} pub trait AsmShmemHeader: Debug { fn allocated_size(&self) -> u64; } #[cfg(all(target_os = "linux", target_arch = "x86_64"))] -impl Drop for AsmSharedMemory { +impl Drop for AsmSharedMemory { fn drop(&mut self) { self.unmap().unwrap_or_else(|err| { tracing::error!("Failed to unmap shared memory '{}': {}", self.shmem_name, err) @@ -48,8 +47,11 @@ impl Drop for AsmSharedMemory { } } -impl AsmSharedMemory { - pub fn open_and_map(name: &str, _unlock_mapped_memory: bool) -> Result { +impl AsmSharedMemory { + pub fn open_and_map( + name: &str, + _unlock_mapped_memory: bool, + ) -> Result { unsafe { if name.is_empty() { return Err(anyhow::anyhow!("Shared memory name {name} cannot be empty")); @@ -132,7 +134,6 @@ impl AsmSharedMemory { mapped_ptr, mapped_size: allocated_size, shmem_name: name.to_string(), - _phantom: std::marker::PhantomData::, }) } } @@ -209,8 +210,11 @@ impl AsmSharedMemory { } } - pub fn check_size_changed(&mut self, current_read_ptr: &mut *const T) -> Result { - let read_mapped_size = self.map_header().allocated_size(); + pub fn check_size_changed( + &mut self, + current_read_ptr: &mut *const T, + ) -> Result { + let read_mapped_size = self.map_header::().allocated_size(); if read_mapped_size == self.mapped_size as u64 { return Ok(false); @@ -247,7 +251,7 @@ impl AsmSharedMemory { Ok(()) } - pub fn map_header(&self) -> H { + pub fn map_header(&self) -> H { if !self.is_mapped() { panic!("Shared memory '{}' is not mapped, cannot read header", self.shmem_name); } @@ -267,7 +271,7 @@ impl AsmSharedMemory { self.mapped_ptr } - pub fn data_ptr(&self) -> *mut c_void { + pub fn data_ptr(&self) -> *mut c_void { // Skip the header size to get the data pointer unsafe { self.mapped_ptr.add(size_of::()) } } @@ -286,7 +290,7 @@ impl AsmSharedMemory { } /// Shared memory name for precompile hints data - pub fn semaphore_available_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + pub fn sem_available_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { format!( "/{}_{}_prec_avail", AsmServices::shmem_prefix(port, local_rank), @@ -295,7 +299,7 @@ impl AsmSharedMemory { } /// Shared memory name for precompile hints data - pub fn semaphore_read_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + pub fn sem_read_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { format!( "/{}_{}_prec_read", AsmServices::shmem_prefix(port, local_rank), diff --git a/executor/src/executor.rs b/executor/src/executor.rs index b836422b6..f58286d03 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -20,7 +20,7 @@ //! maintaining clarity and modularity in the computation process. use asm_runner::{ - write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, + write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, TaskFactory, }; use fields::PrimeField64; @@ -322,7 +322,7 @@ impl ZiskExecutor { // Write inputs to shared memory let shmem_input_name = - AsmSharedMemory::::shmem_input_name(port, *service, self.local_rank); + AsmSharedMemory::shmem_input_name(port, *service, self.local_rank); let mut input_writer = self.shmem_input_writer[idx].lock().unwrap(); if input_writer.is_none() { diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index 8b311df40..a08a7ed30 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -4,9 +4,7 @@ //! using SharedMemoryWriter instances. use anyhow::Result; -use asm_runner::{ - AsmMTHeader, AsmService, AsmServices, AsmSharedMemory, SharedMemoryReader, SharedMemoryWriter, -}; +use asm_runner::{AsmService, AsmServices, AsmSharedMemory, SharedMemoryReader, SharedMemoryWriter}; use named_sem::NamedSemaphore; use std::sync::Mutex; use tracing::debug; @@ -24,21 +22,11 @@ struct ServiceResourceNames { impl ServiceResourceNames { fn new(service: &AsmService, port: u16, local_rank: i32) -> Self { Self { - control_writer: AsmSharedMemory::::shmem_control_writer_name( - port, *service, local_rank, - ), - control_reader: AsmSharedMemory::::shmem_control_reader_name( - port, *service, local_rank, - ), - data_name: AsmSharedMemory::::shmem_precompile_name( - port, *service, local_rank, - ), - sem_available_name: AsmSharedMemory::::semaphore_available_name( - port, *service, local_rank, - ), - sem_read_name: AsmSharedMemory::::semaphore_read_name( - port, *service, local_rank, - ), + control_writer: AsmSharedMemory::shmem_control_writer_name(port, *service, local_rank), + control_reader: AsmSharedMemory::shmem_control_reader_name(port, *service, local_rank), + data_name: AsmSharedMemory::shmem_precompile_name(port, *service, local_rank), + sem_available_name: AsmSharedMemory::sem_available_name(port, *service, local_rank), + sem_read_name: AsmSharedMemory::sem_read_name(port, *service, local_rank), } } } From 2cfe634ded3df5bfd799649598f1cbd26764caf2 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:02:46 +0000 Subject: [PATCH 075/782] added from_str fn in StreamSource to create a StreamSource from a given shecme --- cli/src/commands/execute.rs | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index f70b6941c..0c40c73fd 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -46,7 +46,7 @@ pub struct ZiskExecute { /// Precompiles Hints path #[clap(short = 'h', long)] - pub precompile_hints_path: Option, + pub precompile_hints_path: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -87,14 +87,11 @@ impl ZiskExecute { } if self.precompile_hints_path.is_some() { - print_banner_field( - "Prec. Hints", - &self.precompile_hints_path.as_ref().unwrap().to_string_lossy(), - ); + print_banner_field("Prec. Hints", &self.precompile_hints_path.as_ref().unwrap()); } let stdin = self.create_stdin()?; - let hints_stream = self.create_hints_stream()?; + let hints_stream = StreamSource::from_str(self.precompile_hints_path.as_deref())?; let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; let result = @@ -120,21 +117,6 @@ impl ZiskExecute { Ok(stdin) } - fn create_hints_stream(&mut self) -> Result { - let stream = if let Some(hints_path) = &self.precompile_hints_path { - if !hints_path.exists() { - return Err(anyhow::anyhow!( - "Precompile Hints file not found at {:?}", - hints_path.display() - )); - } - StreamSource::from_file(hints_path)? - } else { - StreamSource::null() - }; - Ok(stream) - } - pub fn run_emu(&mut self, stdin: ZiskStdin) -> Result { let prover = ProverClient::builder() .emu() From c65d8bafdd632b619a6e874e105a180e0525820c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:14:02 +0000 Subject: [PATCH 076/782] added from_str fn in ZiskStdin to create a ZiskStdin from a given scheme --- cli/src/commands/execute.rs | 19 ++++--------------- common/src/io/file_stdin.rs | 7 +++++++ common/src/io/zisk_stdin.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 0c40c73fd..dd609fd2b 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -42,7 +42,7 @@ pub struct ZiskExecute { /// Input path #[clap(short = 'i', long)] - pub input: Option, + pub input: Option, /// Precompiles Hints path #[clap(short = 'h', long)] @@ -83,14 +83,15 @@ impl ZiskExecute { print_banner(); if self.input.is_some() { - print_banner_field("Input", &self.input.as_ref().unwrap().to_string_lossy()); + print_banner_field("Input", &self.input.as_ref().unwrap()); } if self.precompile_hints_path.is_some() { print_banner_field("Prec. Hints", &self.precompile_hints_path.as_ref().unwrap()); } - let stdin = self.create_stdin()?; + let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; + let hints_stream = StreamSource::from_str(self.precompile_hints_path.as_deref())?; let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; @@ -105,18 +106,6 @@ impl ZiskExecute { Ok(()) } - fn create_stdin(&mut self) -> Result { - let stdin = if let Some(input) = &self.input { - if !input.exists() { - return Err(anyhow::anyhow!("Input file not found at {:?}", input.display())); - } - ZiskStdin::from_file(input)? - } else { - ZiskStdin::null() - }; - Ok(stdin) - } - pub fn run_emu(&mut self, stdin: ZiskStdin) -> Result { let prover = ProverClient::builder() .emu() diff --git a/common/src/io/file_stdin.rs b/common/src/io/file_stdin.rs index ff51be586..df9f77267 100644 --- a/common/src/io/file_stdin.rs +++ b/common/src/io/file_stdin.rs @@ -20,6 +20,13 @@ impl ZiskFileStdin { /// Create a new FileStdin from a file path. pub fn new>(path: P) -> std::io::Result { let path_buf = path.as_ref().to_path_buf(); + if !path_buf.exists() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Input file not found at {:?}", path_buf.display()), + )); + } + let file = File::open(&path_buf)?; Ok(ZiskFileStdin { path: path_buf, reader: BufReader::new(file) }) } diff --git a/common/src/io/zisk_stdin.rs b/common/src/io/zisk_stdin.rs index 8f5b14006..01e68cb6d 100644 --- a/common/src/io/zisk_stdin.rs +++ b/common/src/io/zisk_stdin.rs @@ -108,4 +108,31 @@ impl ZiskStdin { pub fn from_vec(data: Vec) -> Self { Self { io: ZiskIOVariant::Memory(ZiskMemoryStdin::new(data)) } } + + /// Create a ZiskStdin from a URI string + /// - None -> null stream + /// - "scheme://path" -> parsed based on scheme + /// - No scheme -> treated as file path + pub fn from_str>(hints_uri: Option) -> Result { + if hints_uri.is_none() { + return Ok(ZiskStdin::null()); + } + + let uri_str = hints_uri.unwrap().into(); + + // Check if URI contains "://" separator + if let Some(pos) = uri_str.find("://") { + let (scheme, path) = uri_str.split_at(pos); + let path = &path[3..]; // Skip "://" + + match scheme { + "file" => ZiskStdin::from_file(path), + // Unknown scheme - could error or fallback + _ => Err(anyhow::anyhow!("Unknown stream source scheme: {}", scheme)), + } + } else { + // No "://" found - fallback as a file path + ZiskStdin::from_file(uri_str.as_str()) + } + } } From 9b0148beedbfd45af9302a3288eae3ceb9c7e69a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:19:16 +0000 Subject: [PATCH 077/782] improve execute cli --- cli/src/commands/execute.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index dd609fd2b..095c7b809 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -1,7 +1,7 @@ use anyhow::Result; use clap::Parser; use std::path::PathBuf; -use tracing::info; +use tracing::{info, warn}; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_sdk::{ProverClient, ZiskExecuteResult}; @@ -82,24 +82,32 @@ impl ZiskExecute { print_banner(); - if self.input.is_some() { - print_banner_field("Input", &self.input.as_ref().unwrap()); + if let Some(input) = &self.input { + print_banner_field("Input", input); } - if self.precompile_hints_path.is_some() { - print_banner_field("Prec. Hints", &self.precompile_hints_path.as_ref().unwrap()); + if let Some(hints) = &self.precompile_hints_path { + print_banner_field("Prec. Hints", hints); } let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; let hints_stream = StreamSource::from_str(self.precompile_hints_path.as_deref())?; - let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; + let emulator = if cfg!(target_os = "macos") { + if !self.emulator { + warn!("Emulator mode is forced on macOS due to lack of ASM support."); + } + true + } else { + self.emulator + }; + let result = if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, Some(hints_stream))? }; info!( - "Execution completed in {:.2?}, executed steps: {}", + "Execution completed in {:.2?}, steps: {}", result.duration, result.execution.executed_steps ); From fef3a43b89d2d23f0055e146d344784bd4ebd91e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:20:18 +0000 Subject: [PATCH 078/782] rename executed_steps to steps --- cli/src/commands/execute.rs | 5 +---- cli/src/commands/prove.rs | 6 +----- cli/src/commands/verify_constraints.rs | 2 +- common/src/zisk_lib_init.rs | 2 +- executor/src/executor.rs | 4 ++-- sdk/src/prover/asm.rs | 2 +- sdk/src/prover/backend.rs | 3 +-- sdk/src/prover/emu.rs | 2 +- server/src/handler_prove.rs | 8 ++------ server/src/handler_verify_constraints.rs | 2 +- 10 files changed, 12 insertions(+), 24 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 095c7b809..b8a6144c2 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -106,10 +106,7 @@ impl ZiskExecute { let result = if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, Some(hints_stream))? }; - info!( - "Execution completed in {:.2?}, steps: {}", - result.duration, result.execution.executed_steps - ); + info!("Execution completed in {:.2?}, steps: {}", result.duration, result.execution.steps); Ok(()) } diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 5b3833832..631f30d59 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -143,11 +143,7 @@ impl ZiskProve { tracing::info!(" Proof ID: {}", proof_id); } tracing::info!(" ► Statistics"); - tracing::info!( - " time: {} seconds, steps: {}", - elapsed, - result.execution.executed_steps - ); + tracing::info!(" time: {} seconds, steps: {}", elapsed, result.execution.steps); } Ok(()) diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 4fd80591d..4e44fe2d8 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -94,7 +94,7 @@ impl ZiskVerifyConstraints { tracing::info!( " time: {:.2} seconds, steps: {}", result.duration.as_secs_f32(), - result.execution.executed_steps + result.execution.steps ); Ok(()) diff --git a/common/src/zisk_lib_init.rs b/common/src/zisk_lib_init.rs index 63690fd99..2ca46a2e1 100644 --- a/common/src/zisk_lib_init.rs +++ b/common/src/zisk_lib_init.rs @@ -13,7 +13,7 @@ use anyhow::Result; #[derive(Debug, Default, Clone)] pub struct ZiskExecutionResult { - pub executed_steps: u64, + pub steps: u64, } #[derive(Debug, Clone)] diff --git a/executor/src/executor.rs b/executor/src/executor.rs index f58286d03..cae62918a 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -271,7 +271,7 @@ impl ZiskExecutor { panic!("Expected EmuTrace, got something else"); }; - self.execution_result.lock().unwrap().executed_steps = steps; + self.execution_result.lock().unwrap().steps = steps; min_traces } @@ -407,7 +407,7 @@ impl ZiskExecutor { panic!("Expected AsmEmuTrace, got something else"); }; - self.execution_result.lock().unwrap().executed_steps = steps; + self.execution_result.lock().unwrap().steps = steps; // If the world rank is 0, wait for the ROM Histogram thread to finish and set the handler if has_rom_sm { diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index c8a3e1e5a..e61aacee7 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -98,7 +98,7 @@ impl ProverEngine for AsmProver { .backend .witness_lib .execution_result() - .map(|(exec_result, _)| exec_result.executed_steps) + .map(|(exec_result, _)| exec_result.steps) .unwrap_or(0) } diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 7483f3c49..6bd4ed63f 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -205,8 +205,7 @@ impl ProverBackend { std::fs::create_dir_all(output_dir)?; } - let logs = - ProofLog::new(execution_result.executed_steps, proof_id, elapsed.as_secs_f64()); + let logs = ProofLog::new(execution_result.steps, proof_id, elapsed.as_secs_f64()); let log_path = output_dir.join("result.json"); ProofLog::write_json_log(&log_path, &logs) .map_err(|e| anyhow::anyhow!("Error generating log: {}", e))?; diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 88a44cdbe..c679efde7 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -86,7 +86,7 @@ impl ProverEngine for EmuProver { .backend .witness_lib .execution_result() - .map(|(exec_result, _)| exec_result.executed_steps) + .map(|(exec_result, _)| exec_result.steps) .unwrap_or(0) } diff --git a/server/src/handler_prove.rs b/server/src/handler_prove.rs index d0181a717..0be461ae8 100644 --- a/server/src/handler_prove.rs +++ b/server/src/handler_prove.rs @@ -105,11 +105,7 @@ impl ZiskServiceProveHandler { tracing::info!(" Proof ID: {}", proof_id); } tracing::info!(" ► Statistics"); - tracing::info!( - " time: {} seconds, steps: {}", - elapsed, - result.executed_steps - ); + tracing::info!(" time: {} seconds, steps: {}", elapsed, result.steps); // Store the stats in stats.json #[cfg(feature = "stats")] @@ -120,7 +116,7 @@ impl ZiskServiceProveHandler { } if let Some(proof_id) = proof_id { - let logs = ProofLog::new(result.executed_steps, proof_id, elapsed); + let logs = ProofLog::new(result.steps, proof_id, elapsed); let log_path = request.folder.join(format!("{}-result.json", request.prefix)); println!("Writing proof log to: {}", log_path.display()); diff --git a/server/src/handler_verify_constraints.rs b/server/src/handler_verify_constraints.rs index 518b88863..e94dbfbf3 100644 --- a/server/src/handler_verify_constraints.rs +++ b/server/src/handler_verify_constraints.rs @@ -68,7 +68,7 @@ impl ZiskServiceVerifyConstraintsHandler { tracing::info!( " time: {} seconds, steps: {}", elapsed.as_secs_f32(), - result.executed_steps + result.steps ); is_busy.store(false, std::sync::atomic::Ordering::SeqCst); From 302ae6d16482f1066ffda53380f15f1b58e20b8d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:22:09 +0000 Subject: [PATCH 079/782] minor renaming --- cli/src/commands/execute.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index b8a6144c2..9214fdeff 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -129,7 +129,7 @@ impl ZiskExecute { pub fn run_asm( &mut self, stdin: ZiskStdin, - stream: Option, + hints_stream: Option, ) -> Result { let prover = ProverClient::builder() .asm() @@ -145,6 +145,6 @@ impl ZiskExecute { .print_command_info() .build()?; - prover.execute(stdin, stream) + prover.execute(stdin, hints_stream) } } From 3a7e153f0c0e9c621f1135312bc2c9f7f39cdc31 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:23:05 +0000 Subject: [PATCH 080/782] prepare stats for precompile hints stream --- cli/src/commands/stats.rs | 57 ++++++++++++++++++++++++--------------- sdk/src/prover/asm.rs | 3 ++- sdk/src/prover/backend.rs | 6 +++++ sdk/src/prover/emu.rs | 3 ++- sdk/src/prover/mod.rs | 4 ++- 5 files changed, 49 insertions(+), 24 deletions(-) diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index d18f25e07..5e6836734 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -2,13 +2,14 @@ use anyhow::Result; use clap::Parser; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs, path::PathBuf, time::Instant}; +use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; -use zisk_common::io::ZiskStdin; +use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::{ExecutorStats, Stats}; use zisk_pil::*; use zisk_sdk::ProverClient; -use crate::ux::print_banner; +use crate::ux::{print_banner, print_banner_field}; #[derive(Parser)] #[command(author, about, long_about = None, version = ZISK_VERSION_MESSAGE)] @@ -41,7 +42,11 @@ pub struct ZiskStats { /// Input path #[clap(short = 'i', long)] - pub input: Option, + pub input: Option, + + /// Precompiles Hints path + #[clap(short = 'h', long)] + pub precompile_hints_path: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -91,11 +96,29 @@ impl ZiskStats { pub fn run(&mut self) -> Result<()> { print_banner(); - let stdin = self.create_stdin()?; + if let Some(input) = &self.input { + print_banner_field("Input", input); + } + + if let Some(hints) = &self.precompile_hints_path { + print_banner_field("Prec. Hints", hints); + } + + let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; + + let hints_stream = StreamSource::from_str(self.precompile_hints_path.as_deref())?; + + let emulator = if cfg!(target_os = "macos") { + if !self.emulator { + warn!("Emulator mode is forced on macOS due to lack of ASM support."); + } + true + } else { + self.emulator + }; - let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; let (world_rank, n_processes, stats) = - if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin)? }; + if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, Some(hints_stream))? }; if world_rank % 2 == 1 { std::thread::sleep(std::time::Duration::from_millis(2000)); @@ -115,18 +138,6 @@ impl ZiskStats { Ok(()) } - fn create_stdin(&mut self) -> Result { - let stdin = if let Some(input) = &self.input { - if !input.exists() { - return Err(anyhow::anyhow!("Input file not found at {:?}", input.display())); - } - ZiskStdin::from_file(input)? - } else { - ZiskStdin::null() - }; - Ok(stdin) - } - pub fn run_emu(&mut self, stdin: ZiskStdin) -> Result<(i32, i32, Option)> { let prover = ProverClient::builder() .emu() @@ -139,10 +150,14 @@ impl ZiskStats { .print_command_info() .build()?; - prover.stats(stdin, self.debug.clone(), self.mpi_node.map(|n| n as u32)) + prover.stats(stdin, None, self.debug.clone(), self.mpi_node.map(|n| n as u32)) } - pub fn run_asm(&mut self, stdin: ZiskStdin) -> Result<(i32, i32, Option)> { + pub fn run_asm( + &mut self, + stdin: ZiskStdin, + hints_stream: Option, + ) -> Result<(i32, i32, Option)> { let prover = ProverClient::builder() .asm() .witness() @@ -158,7 +173,7 @@ impl ZiskStats { .build()?; let mpi_node = self.mpi_node.map(|n| n as u32); - prover.stats(stdin, self.debug.clone(), mpi_node) + prover.stats(stdin, hints_stream, self.debug.clone(), mpi_node) } /// Prints stats individually and grouped, with aligned columns. diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index e61aacee7..970be6f1e 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -114,13 +114,14 @@ impl ProverEngine for AsmProver { fn stats( &self, stdin: ZiskStdin, + hints_stream: Option, debug_info: Option>, mpi_node: Option, ) -> Result<(i32, i32, Option)> { let debug_info = create_debug_info(debug_info, self.core_prover.backend.proving_key.clone())?; - self.core_prover.backend.stats(stdin, debug_info, mpi_node) + self.core_prover.backend.stats(stdin, hints_stream, debug_info, mpi_node) } fn verify_constraints_debug( diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 6bd4ed63f..d400faae1 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -62,10 +62,16 @@ impl ProverBackend { pub(crate) fn stats( &self, stdin: ZiskStdin, + hints_stream: Option, debug_info: DebugInfo, _mpi_node: Option, ) -> Result<(i32, i32, Option)> { self.witness_lib.set_stdin(stdin); + if let Some(stream) = hints_stream { + self.witness_lib + .set_hints_stream(stream) + .map_err(|e| anyhow::anyhow!("Error setting hints stream: {}", e))?; + } let world_rank = self.proofman.get_world_rank(); let local_rank = self.proofman.get_local_rank(); diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index c679efde7..0c0cb25c0 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -105,13 +105,14 @@ impl ProverEngine for EmuProver { fn stats( &self, stdin: ZiskStdin, + hints_stream: Option, debug_info: Option>, mpi_node: Option, ) -> Result<(i32, i32, Option)> { let debug_info = create_debug_info(debug_info, self.core_prover.backend.proving_key.clone())?; - self.core_prover.backend.stats(stdin, debug_info, mpi_node) + self.core_prover.backend.stats(stdin, hints_stream, debug_info, mpi_node) } fn verify_constraints_debug( diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 0f9b03c03..ad262040d 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -61,6 +61,7 @@ pub trait ProverEngine { fn stats( &self, stdin: ZiskStdin, + hints_stream: Option, debug_info: Option>, mpi_node: Option, ) -> Result<(i32, i32, Option)>; @@ -148,10 +149,11 @@ impl ZiskProver { pub fn stats( &self, stdin: ZiskStdin, + hints_stream: Option, debug_info: Option>, mpi_node: Option, ) -> Result<(i32, i32, Option)> { - self.prover.stats(stdin, debug_info, mpi_node) + self.prover.stats(stdin, hints_stream, debug_info, mpi_node) } /// Verify the constraints with the given standard input and debug information. From 01302cdcc365b29fc4b6dfc32092e9bfbb223bb2 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:31:58 +0000 Subject: [PATCH 081/782] prepare verify_constraints for precompile hints stream --- cli/src/commands/verify_constraints.rs | 61 +++++++++++++++++--------- sdk/src/prover/asm.rs | 11 +++-- sdk/src/prover/backend.rs | 9 +++- sdk/src/prover/emu.rs | 11 +++-- sdk/src/prover/mod.rs | 18 ++++++-- 5 files changed, 78 insertions(+), 32 deletions(-) diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 4e44fe2d8..f0084eb1d 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -1,11 +1,15 @@ -use crate::{commands::cli_fail_if_gpu_mode, ux::print_banner}; +use crate::{ + commands::cli_fail_if_gpu_mode, + ux::{print_banner, print_banner_field}, +}; use anyhow::Result; use clap::Parser; use colored::Colorize; use std::path::PathBuf; +use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; -use zisk_common::io::ZiskStdin; +use zisk_common::io::{StreamSource, ZiskStdin}; #[cfg(feature = "stats")] use zisk_common::ExecutorStatsEvent; use zisk_sdk::{ProverClient, ZiskVerifyConstraintsResult}; @@ -41,7 +45,11 @@ pub struct ZiskVerifyConstraints { /// Input path #[clap(short = 'i', long)] - pub input: Option, + pub input: Option, + + /// Precompiles Hints path + #[clap(short = 'h', long)] + pub precompile_hints_path: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -80,10 +88,29 @@ impl ZiskVerifyConstraints { print_banner(); - let stdin = self.create_stdin()?; + if let Some(input) = &self.input { + print_banner_field("Input", input); + } + + if let Some(hints) = &self.precompile_hints_path { + print_banner_field("Prec. Hints", hints); + } + + let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; + + let hints_stream = StreamSource::from_str(self.precompile_hints_path.as_deref())?; - let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; - let result = if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin)? }; + let emulator = if cfg!(target_os = "macos") { + if !self.emulator { + warn!("Emulator mode is forced on macOS due to lack of ASM support."); + } + true + } else { + self.emulator + }; + + let result = + if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, Some(hints_stream))? }; tracing::info!(""); tracing::info!( @@ -100,18 +127,6 @@ impl ZiskVerifyConstraints { Ok(()) } - fn create_stdin(&mut self) -> Result { - let stdin = if let Some(input) = &self.input { - if !input.exists() { - return Err(anyhow::anyhow!("Input file not found at {:?}", input.display())); - } - ZiskStdin::from_file(input)? - } else { - ZiskStdin::null() - }; - Ok(stdin) - } - pub fn run_emu(&mut self, stdin: ZiskStdin) -> Result { let prover = ProverClient::builder() .emu() @@ -124,10 +139,14 @@ impl ZiskVerifyConstraints { .print_command_info() .build()?; - prover.verify_constraints_debug(stdin, self.debug.clone()) + prover.verify_constraints_debug(stdin, None, self.debug.clone()) } - pub fn run_asm(&mut self, stdin: ZiskStdin) -> Result { + pub fn run_asm( + &mut self, + stdin: ZiskStdin, + hints_stream: Option, + ) -> Result { let prover = ProverClient::builder() .asm() .verify_constraints() @@ -142,6 +161,6 @@ impl ZiskVerifyConstraints { .print_command_info() .build()?; - prover.verify_constraints_debug(stdin, self.debug.clone()) + prover.verify_constraints_debug(stdin, hints_stream, self.debug.clone()) } } diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 970be6f1e..29f53b33e 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -127,16 +127,21 @@ impl ProverEngine for AsmProver { fn verify_constraints_debug( &self, stdin: ZiskStdin, + hints_stream: Option, debug_info: Option>, ) -> Result { let debug_info = create_debug_info(debug_info, self.core_prover.backend.proving_key.clone())?; - self.core_prover.backend.verify_constraints_debug(stdin, debug_info) + self.core_prover.backend.verify_constraints_debug(stdin, hints_stream, debug_info) } - fn verify_constraints(&self, stdin: ZiskStdin) -> Result { - self.core_prover.backend.verify_constraints(stdin) + fn verify_constraints( + &self, + stdin: ZiskStdin, + hints_stream: Option, + ) -> Result { + self.core_prover.backend.verify_constraints(stdin, hints_stream) } fn prove(&self, stdin: ZiskStdin) -> Result { diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index d400faae1..23393c5e3 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -124,6 +124,7 @@ impl ProverBackend { pub(crate) fn verify_constraints_debug( &self, stdin: ZiskStdin, + hints_stream: Option, debug_info: DebugInfo, ) -> Result { if !self.verify_constraints { @@ -133,6 +134,11 @@ impl ProverBackend { let start = std::time::Instant::now(); self.witness_lib.set_stdin(stdin); + if let Some(stream) = hints_stream { + self.witness_lib + .set_hints_stream(stream) + .map_err(|e| anyhow::anyhow!("Error setting hints stream: {}", e))?; + } self.proofman .verify_proof_constraints_from_lib(&debug_info, false) @@ -157,8 +163,9 @@ impl ProverBackend { pub(crate) fn verify_constraints( &self, stdin: ZiskStdin, + hints_stream: Option, ) -> Result { - self.verify_constraints_debug(stdin, DebugInfo::default()) + self.verify_constraints_debug(stdin, hints_stream, DebugInfo::default()) } pub(crate) fn prove(&self, stdin: ZiskStdin) -> Result { diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 0c0cb25c0..3e232afeb 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -118,16 +118,21 @@ impl ProverEngine for EmuProver { fn verify_constraints_debug( &self, stdin: ZiskStdin, + hints_stream: Option, debug_info: Option>, ) -> Result { let debug_info = create_debug_info(debug_info, self.core_prover.backend.proving_key.clone())?; - self.core_prover.backend.verify_constraints_debug(stdin, debug_info) + self.core_prover.backend.verify_constraints_debug(stdin, hints_stream, debug_info) } - fn verify_constraints(&self, stdin: ZiskStdin) -> Result { - self.core_prover.backend.verify_constraints(stdin) + fn verify_constraints( + &self, + stdin: ZiskStdin, + hints_stream: Option, + ) -> Result { + self.core_prover.backend.verify_constraints(stdin, hints_stream) } fn prove(&self, stdin: ZiskStdin) -> Result { diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index ad262040d..80f83f6f6 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -69,10 +69,15 @@ pub trait ProverEngine { fn verify_constraints_debug( &self, stdin: ZiskStdin, + hints_stream: Option, debug_info: Option>, ) -> Result; - fn verify_constraints(&self, stdin: ZiskStdin) -> Result; + fn verify_constraints( + &self, + stdin: ZiskStdin, + hints_stream: Option, + ) -> Result; fn prove(&self, stdin: ZiskStdin) -> Result; @@ -160,14 +165,19 @@ impl ZiskProver { pub fn verify_constraints_debug( &self, stdin: ZiskStdin, + hints_stream: Option, debug_info: Option>, ) -> Result { - self.prover.verify_constraints_debug(stdin, debug_info) + self.prover.verify_constraints_debug(stdin, hints_stream, debug_info) } /// Verify the constraints with the given standard input. - pub fn verify_constraints(&self, stdin: ZiskStdin) -> Result { - self.prover.verify_constraints(stdin) + pub fn verify_constraints( + &self, + stdin: ZiskStdin, + hints_stream: Option, + ) -> Result { + self.prover.verify_constraints(stdin, hints_stream) } /// Generate a proof with the given standard input. From 7d91098a967c264b2833179f1a0f464109e8e1ad Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:37:14 +0000 Subject: [PATCH 082/782] prepare prove for precompile hints stream --- cli/src/commands/prove.rs | 51 ++++++++++++++++++++++++--------------- sdk/src/prover/asm.rs | 8 ++++-- sdk/src/prover/backend.rs | 11 ++++++++- sdk/src/prover/emu.rs | 8 ++++-- sdk/src/prover/mod.rs | 14 ++++++++--- 5 files changed, 64 insertions(+), 28 deletions(-) diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 631f30d59..8998d53c3 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -1,11 +1,12 @@ -use crate::ux::print_banner; +use crate::ux::{print_banner, print_banner_field}; use anyhow::Result; use colored::Colorize; use proofman_common::ParamsGPU; use std::path::PathBuf; +use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; -use zisk_common::io::ZiskStdin; +use zisk_common::io::{StreamSource, ZiskStdin}; #[cfg(feature = "stats")] use zisk_common::ExecutorStatsEvent; use zisk_sdk::{ProverClient, ZiskProveResult}; @@ -42,7 +43,11 @@ pub struct ZiskProve { /// Input path #[clap(short = 'i', long)] - pub input: Option, + pub input: Option, + + /// Precompiles Hints path + #[clap(short = 'h', long)] + pub precompile_hints_path: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -110,6 +115,14 @@ impl ZiskProve { pub fn run(&mut self) -> Result<()> { print_banner(); + if let Some(input) = &self.input { + print_banner_field("Input", input); + } + + if let Some(hints) = &self.precompile_hints_path { + print_banner_field("Prec. Hints", hints); + } + let mut gpu_params = ParamsGPU::new(self.preallocate); if self.max_streams.is_some() { @@ -122,14 +135,23 @@ impl ZiskProve { gpu_params.with_max_witness_stored(self.max_witness_stored.unwrap()); } - let stdin = self.create_stdin()?; + let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; + + let hints_stream = StreamSource::from_str(self.precompile_hints_path.as_deref())?; - let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; + let emulator = if cfg!(target_os = "macos") { + if !self.emulator { + warn!("Emulator mode is forced on macOS due to lack of ASM support."); + } + true + } else { + self.emulator + }; let (result, world_rank) = if emulator { self.run_emu(stdin, gpu_params)? } else { - self.run_asm(stdin, gpu_params)? + self.run_asm(stdin, Some(hints_stream), gpu_params)? }; if world_rank == 0 { @@ -149,18 +171,6 @@ impl ZiskProve { Ok(()) } - fn create_stdin(&mut self) -> Result { - let stdin = if let Some(input) = &self.input { - if !input.exists() { - return Err(anyhow::anyhow!("Input file not found at {:?}", input.display())); - } - ZiskStdin::from_file(input)? - } else { - ZiskStdin::null() - }; - Ok(stdin) - } - pub fn run_emu( &mut self, stdin: ZiskStdin, @@ -184,7 +194,7 @@ impl ZiskProve { .print_command_info() .build()?; - let result = prover.prove(stdin)?; + let result = prover.prove(stdin, None)?; let world_rank = prover.world_rank(); Ok((result, world_rank)) @@ -193,6 +203,7 @@ impl ZiskProve { pub fn run_asm( &mut self, stdin: ZiskStdin, + hints_stream: Option, gpu_params: ParamsGPU, ) -> Result<(ZiskProveResult, i32)> { let prover = ProverClient::builder() @@ -216,7 +227,7 @@ impl ZiskProve { .print_command_info() .build()?; - let result = prover.prove(stdin)?; + let result = prover.prove(stdin, hints_stream)?; let world_rank = prover.world_rank(); Ok((result, world_rank)) diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 29f53b33e..314693909 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -144,8 +144,12 @@ impl ProverEngine for AsmProver { self.core_prover.backend.verify_constraints(stdin, hints_stream) } - fn prove(&self, stdin: ZiskStdin) -> Result { - self.core_prover.backend.prove(stdin) + fn prove( + &self, + stdin: ZiskStdin, + hints_stream: Option, + ) -> Result { + self.core_prover.backend.prove(stdin, hints_stream) } fn prove_phase( diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 23393c5e3..4874d9770 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -168,7 +168,11 @@ impl ProverBackend { self.verify_constraints_debug(stdin, hints_stream, DebugInfo::default()) } - pub(crate) fn prove(&self, stdin: ZiskStdin) -> Result { + pub(crate) fn prove( + &self, + stdin: ZiskStdin, + hints_stream: Option, + ) -> Result { if self.verify_constraints { return Err(anyhow::anyhow!( "Prover initialized with constraint verification enabled. Use `prove` instead." @@ -178,6 +182,11 @@ impl ProverBackend { let start = std::time::Instant::now(); self.witness_lib.set_stdin(stdin); + if let Some(stream) = hints_stream { + self.witness_lib + .set_hints_stream(stream) + .map_err(|e| anyhow::anyhow!("Error setting hints stream: {}", e))?; + } self.proofman.set_barrier(); let proof = self diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 3e232afeb..bf8643003 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -135,8 +135,12 @@ impl ProverEngine for EmuProver { self.core_prover.backend.verify_constraints(stdin, hints_stream) } - fn prove(&self, stdin: ZiskStdin) -> Result { - self.core_prover.backend.prove(stdin) + fn prove( + &self, + stdin: ZiskStdin, + hints_stream: Option, + ) -> Result { + self.core_prover.backend.prove(stdin, hints_stream) } fn prove_phase( diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 80f83f6f6..962a334a7 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -79,7 +79,11 @@ pub trait ProverEngine { hints_stream: Option, ) -> Result; - fn prove(&self, stdin: ZiskStdin) -> Result; + fn prove( + &self, + stdin: ZiskStdin, + hints_stream: Option, + ) -> Result; fn prove_phase( &self, @@ -181,8 +185,12 @@ impl ZiskProver { } /// Generate a proof with the given standard input. - pub fn prove(&self, stdin: ZiskStdin) -> Result { - self.prover.prove(stdin) + pub fn prove( + &self, + stdin: ZiskStdin, + hints_stream: Option, + ) -> Result { + self.prover.prove(stdin, hints_stream) } pub fn prove_phase( From 492bcaa8b6791f8bcbe5452cacc089039ef6db70 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 07:29:59 +0000 Subject: [PATCH 083/782] using StreamSource::from_str in worker.rs --- distributed/crates/worker/src/worker.rs | 5 +++-- executor/src/hints_shmem.rs | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index bb180ea73..de982dc9a 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -533,9 +533,10 @@ impl Worker { match input_source { InputSourceDto::InputPath(inputs_uri, hints_uri) => { let stdin = ZiskStdin::from_file(inputs_uri)?; - let hints_stdin = StreamSource::from_file(hints_uri)?; // TODO!!!!!! CHANGE THIS + let hints_stream = StreamSource::from_str(hints_uri.into())?; + prover.set_stdin(stdin); - prover.set_hints_stream(hints_stdin)?; + prover.set_hints_stream(hints_stream)?; } InputSourceDto::InputData(input_data) => { let stdin = ZiskStdin::from_vec(input_data); diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index a08a7ed30..b7e8466fb 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -4,7 +4,9 @@ //! using SharedMemoryWriter instances. use anyhow::Result; -use asm_runner::{AsmService, AsmServices, AsmSharedMemory, SharedMemoryReader, SharedMemoryWriter}; +use asm_runner::{ + AsmService, AsmServices, AsmSharedMemory, SharedMemoryReader, SharedMemoryWriter, +}; use named_sem::NamedSemaphore; use std::sync::Mutex; use tracing::debug; From 3646c22a9d9668e0d098a887d84a7ab7e11881e6 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 08:18:13 +0000 Subject: [PATCH 084/782] minor fix --- hints/src/hints_stream.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hints/src/hints_stream.rs b/hints/src/hints_stream.rs index 49f899a61..cdf3d606a 100644 --- a/hints/src/hints_stream.rs +++ b/hints/src/hints_stream.rs @@ -54,11 +54,9 @@ impl HintsStream { /// # Arguments /// * `stream` - The new StreamSource source for reading hints. pub fn set_hints_stream(&mut self, mut stream: StreamSource) -> Result<()> { - // TODO!!!!!! Check if it is necessary to close the previous stream - // Stop the existing thread if running - self.stop_thread(); - if !stream.is_active() { + // Stop the existing thread if running + self.stop_thread(); stream.open()?; } From aee060765dca6f3009337d00cb9c41bfe9750673 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 17 Dec 2025 11:13:05 +0100 Subject: [PATCH 085/782] Fix clippy errors --- core/src/zisk_rom_2_asm.rs | 96 +++++++++++++++++++++++--------------- emulator-asm/src/main.c | 12 ++++- 2 files changed, 69 insertions(+), 39 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index f84cb167f..28791d8b7 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -227,64 +227,84 @@ impl ZiskAsmContext { true } pub fn precompile_results_keccak(&self) -> bool { - self.precompile_results() && true + //self.precompile_results() + true } pub fn precompile_results_sha256(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_arith256(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_arith256mod(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_secp256k1add(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_secp256k1dbl(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_fcall(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_bn254curveadd(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_bn254curvedbl(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_bn254complexadd(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_bn254complexsub(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_bn254complexmul(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_arith384mod(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_bls12_381curveadd(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_bls12_381curvedbl(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_bls12_381complexadd(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_bls12_381complexsub(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_bls12_381complexmul(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn precompile_results_add256(&self) -> bool { - self.precompile_results() && false + //self.precompile_results() + false } pub fn call_wait_for_prec_avail(&self) -> bool { - self.precompile_results() && true + //self.precompile_results() + true } } @@ -5206,7 +5226,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_sha256() { - *code += &format!("\tmov rdi, [rdi]\n"); + *code += "\tmov rdi, [rdi]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 4); } else { // Call the SHA256 function @@ -5379,7 +5399,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_arith256mod() { - *code += &format!("\tmov rdi, [rdi + 4*8]\n"); + *code += "\tmov rdi, [rdi + 4*8]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 4); } else { // Call the arith256_mod function @@ -5460,7 +5480,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_secp256k1add() { - *code += &format!("\tmov rdi, [rdi]\n"); + *code += "\tmov rdi, [rdi]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the secp256k1_add function @@ -5867,7 +5887,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_bn254curveadd() { - *code += &format!("\tmov rdi, [rdi]\n"); + *code += "\tmov rdi, [rdi]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the bn254_curve_add function @@ -6055,7 +6075,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_bn254complexadd() { - *code += &format!("\tmov rdi, [rdi]\n"); + *code += "\tmov rdi, [rdi]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the bn254_complex_add function @@ -6137,7 +6157,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_bn254complexsub() { - *code += &format!("\tmov rdi, [rdi]\n"); + *code += "\tmov rdi, [rdi]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the bn254_complex_sub function @@ -6219,7 +6239,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_bn254complexmul() { - *code += &format!("\tmov rdi, [rdi]\n"); + *code += "\tmov rdi, [rdi]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); } else { // Call the bn254_complex_mul function @@ -6309,7 +6329,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_arith384mod() { - *code += &format!("\tmov rdi, [rdi + 4*8]\n"); + *code += "\tmov rdi, [rdi + 4*8]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 6); } else { // Call the arith384_mod function @@ -6391,7 +6411,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_bls12_381curveadd() { - *code += &format!("\tmov rdi, [rdi]\n"); + *code += "\tmov rdi, [rdi]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 12); } else { // Call the bls12_381_curve_add function @@ -6583,7 +6603,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_bls12_381complexadd() { - *code += &format!("\tmov rdi, [rdi]\n"); + *code += "\tmov rdi, [rdi]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 12); } else { // Call the bls12_381_complex_add function @@ -6669,7 +6689,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_bls12_381complexsub() { - *code += &format!("\tmov rdi, [rdi]\n"); + *code += "\tmov rdi, [rdi]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 12); } else { // Call the bls12_381_complex_sub function @@ -6755,7 +6775,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_bls12_381complexmul() { - *code += &format!("\tmov rdi, [rdi]\n"); + *code += "\tmov rdi, [rdi]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 12); } else { // Call the bls12_381_complex_mul function @@ -6859,7 +6879,7 @@ impl ZiskRom2Asm { { // Get result from precompile results data if ctx.precompile_results_add256() { - *code += &format!("\tmov rdi, [rdi+3*8]\n"); + *code += "\tmov rdi, [rdi+3*8]\n"; Self::precompile_results_array(ctx, code, unusual_code, "rdi", 4); Self::precompile_results_register(ctx, code, unusual_code, REG_C); } else { @@ -8013,7 +8033,7 @@ impl ZiskRom2Asm { "\tmov {}, [{}] {}\n", reg, REG_AUX, - ctx.comment(format!("value = precompile_results[0]")) + ctx.comment_str("value = precompile_results[0]") ); // Increase precompile_read @@ -8073,14 +8093,14 @@ impl ZiskRom2Asm { "\tmov {}, [{}] {}\n", REG_B, REG_ADDRESS, - ctx.comment(format!("b = precompile_results[0]")) + ctx.comment_str("b = precompile_results[0]") ); *code += &format!( "\tmov [{} + {}*8], {} {}\n", reg_address, FCALL_RESULT_SIZE, REG_B, - ctx.comment(format!("fcall[result_size] = b")) + ctx.comment_str("fcall[result_size] = b") ); *code += &format!( "\tinc {} {}\n", @@ -8126,7 +8146,7 @@ impl ZiskRom2Asm { "\tmov {}, [{}] {}\n", REG_VALUE, REG_ADDRESS, - ctx.comment(format!("value = precompile_results[]")) + ctx.comment_str("value = precompile_results[]") ); *code += &format!( "\tmov [{} + {}*8 + {}*8], {} {}\n", @@ -8134,7 +8154,7 @@ impl ZiskRom2Asm { REG_A, FCALL_RESULT, REG_VALUE, - ctx.comment(format!("addr[] = value")) + ctx.comment_str("addr[] = value") ); // Increment REG_A diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index fb3e1e19d..042ef175d 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -322,7 +322,7 @@ int process_id = 0; uint64_t input_size = 0; #define MAX_PRECOMPILE_SIZE (uint64_t)0x10000000 // 256MB -// #define MAX_PRECOMPILE_SIZE (uint64_t)0x100000 // 1MB +//#define MAX_PRECOMPILE_SIZE (uint64_t)0x100000 // 1MB // Precompile results shared memory char shmem_precompile_name[128]; @@ -2324,6 +2324,11 @@ void client_run (void) exit(-1); } + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + client_write_precompile_results(); + } + // Read server response bytes_received = recv(socket_fd, response, sizeof(response), MSG_WAITALL); if (bytes_received < 0) @@ -2385,6 +2390,11 @@ void client_run (void) exit(-1); } + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + client_write_precompile_results(); + } + // Read server response bytes_received = recv(socket_fd, response, sizeof(response), MSG_WAITALL); if (bytes_received < 0) From 6d61b6cef968cc2d980689e849ca9f239ec83bcb Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:36:28 +0000 Subject: [PATCH 086/782] added precompile_hionts flag to rom-setup --- cli/src/commands/rom_setup.rs | 5 +++++ rom-setup/src/asm_setup.rs | 3 ++- rom-setup/src/rom_full_setup.rs | 10 +++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index 66caf6f05..4eaeca666 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -30,6 +30,10 @@ pub struct ZiskRomSetup { #[clap(short = 'o', long)] pub output_dir: Option, + /// Enable precompile hints in assembly generation + #[clap(short = 'p', long, default_value_t = true)] + pub precompile_hints: bool, + #[clap(short = 'v', long, default_value_t = false)] pub verbose: bool, } @@ -54,6 +58,7 @@ impl ZiskRomSetup { &proving_key, &zisk_path, &self.output_dir, + self.precompile_hints, self.verbose, ) } diff --git a/rom-setup/src/asm_setup.rs b/rom-setup/src/asm_setup.rs index 22ee23c36..571a0147d 100644 --- a/rom-setup/src/asm_setup.rs +++ b/rom-setup/src/asm_setup.rs @@ -11,6 +11,7 @@ pub fn generate_assembly( elf_hash: &str, zisk_path: &Path, output_path: &Path, + precompile_hints: bool, verbose: bool, ) -> Result<(), anyhow::Error> { // Read the ELF file and check if it is a valid ELF file @@ -46,7 +47,7 @@ pub fn generate_assembly( // Convert the ELF file to Zisk format and generates an assembly file let rv2zk = Riscv2zisk::new(elf_file_path.to_str().unwrap().to_string()); rv2zk - .runfile(asm_file.to_str().unwrap().to_string(), *gen_method, false, false, false) + .runfile(asm_file.to_str().unwrap().to_string(), *gen_method, false, false, precompile_hints) .expect("Error converting elf to assembly"); let emulator_asm_path = zisk_path.join("emulator-asm"); diff --git a/rom-setup/src/rom_full_setup.rs b/rom-setup/src/rom_full_setup.rs index 95d5c963e..c63c200b4 100644 --- a/rom-setup/src/rom_full_setup.rs +++ b/rom-setup/src/rom_full_setup.rs @@ -13,6 +13,7 @@ pub fn rom_full_setup( proving_key: &Path, zisk_path: &Path, output_dir: &Option, + precompile_hints: bool, verbose: bool, ) -> std::result::Result<(), anyhow::Error> { let output_path = if output_dir.is_none() { @@ -48,7 +49,14 @@ pub fn rom_full_setup( #[cfg(not(target_os = "macos"))] { tracing::info!("Computing assembly setup"); - crate::generate_assembly(elf, &elf_hash, zisk_path, output_path.as_path(), verbose)?; + crate::generate_assembly( + elf, + &elf_hash, + zisk_path, + output_path.as_path(), + precompile_hints, + verbose, + )?; } println!(); From 94fdccfa23c52cdaf7581d25ddfcb444dbac54b9 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:37:07 +0000 Subject: [PATCH 087/782] simplified shmem_reader and shmem_writer API --- emulator-asm/asm-runner/src/shmem_reader.rs | 6 ++---- emulator-asm/asm-runner/src/shmem_writer.rs | 16 ++++------------ executor/src/hints_shmem.rs | 10 +++++----- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/emulator-asm/asm-runner/src/shmem_reader.rs b/emulator-asm/asm-runner/src/shmem_reader.rs index e3079548c..4ab114f38 100644 --- a/emulator-asm/asm-runner/src/shmem_reader.rs +++ b/emulator-asm/asm-runner/src/shmem_reader.rs @@ -92,12 +92,10 @@ impl SharedMemoryReader { /// # Returns /// * The u64 value read from the specified offset (in native endianness) #[inline] - pub fn read_u64_at(&self, offset: usize) -> Result { + pub fn read_u64_at(&self, offset: usize) -> u64 { debug_assert_eq!(offset % 8, 0, "Offset must be 8-byte aligned"); - // compiler_fence(Ordering::Acquire); - - Ok(unsafe { (self.ptr.add(offset) as *const u64).read() }) + unsafe { (self.ptr.add(offset) as *const u64).read() } } /// Reads a slice of data from shared memory at a specific offset diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index 02dcc6820..d23cc9a59 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -124,11 +124,7 @@ impl SharedMemoryWriter { /// /// # Arguments /// * `data` - A slice of data to write to shared memory - /// - /// # Returns - /// * `Ok(())` - If data was successfully written - /// * `Err` - If msync fails - pub fn write_ring_buffer(&mut self, data: &[T]) -> Result<()> { + pub fn write_ring_buffer(&mut self, data: &[T]) { let byte_size = data.len() * std::mem::size_of::(); let data_ptr = data.as_ptr() as *const u8; @@ -161,8 +157,6 @@ impl SharedMemoryWriter { } } } - - Ok(()) } /// Reads a u64 from shared memory at a specific offset (in bytes) @@ -178,10 +172,10 @@ impl SharedMemoryWriter { /// # Returns /// * The u64 value read from the specified offset (in native endianness) #[inline] - pub fn read_u64_at(&self, offset: usize) -> Result { + pub fn read_u64_at(&self, offset: usize) -> u64 { debug_assert_eq!(offset % 8, 0, "Offset must be 8-byte aligned"); - Ok(unsafe { (self.ptr.add(offset) as *const u64).read() }) + unsafe { (self.ptr.add(offset) as *const u64).read() } } /// Writes a u64 to shared memory at a specific offset (in bytes) @@ -195,14 +189,12 @@ impl SharedMemoryWriter { /// - The shared memory contains at least `offset + 8` bytes of valid data /// - The offset is 8-byte aligned for optimal performance #[inline] - pub fn write_u64_at(&self, offset: usize, value: u64) -> Result<()> { + pub fn write_u64_at(&self, offset: usize, value: u64) { debug_assert_eq!(offset % 8, 0, "Offset must be 8-byte aligned"); unsafe { (self.ptr.add(offset) as *mut u64).write(value); } - - Ok(()) } } diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index b7e8466fb..20b9b7de5 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -90,7 +90,7 @@ impl HintsShmem { let mut resources = Self::create_resources(resources_names, unlock_mapped_memory)?; for resource in resources.iter_mut() { - resource.control_writer.write_u64_at(0, 0)?; + resource.control_writer.write_u64_at(0, 0); } Ok(Self { resources: Mutex::new(resources) }) @@ -162,8 +162,8 @@ impl HintsSink for HintsShmem { for resource in resources.iter_mut() { // Read current positions - let write_pos = resource.control_writer.read_u64_at(0)?; - let read_pos = resource.control_reader.read_u64_at(0)?; + let write_pos = resource.control_writer.read_u64_at(0); + let read_pos = resource.control_reader.read_u64_at(0); // Calculate occupied space in ring buffer (positions are absolute values) let occupied_space = write_pos - read_pos; @@ -182,10 +182,10 @@ impl HintsSink for HintsShmem { } // Write data to shared memory with automatic wraparound - resource.data_writer.write_ring_buffer(&processed)?; + resource.data_writer.write_ring_buffer(&processed); // Update write position in control memory with wraparound - resource.control_writer.write_u64_at(0, write_pos + data_size)?; + resource.control_writer.write_u64_at(0, write_pos + data_size); resource.sem_available.post()?; } From 2044d28842e6823a3b8e26196d3d4cf459bdb4e5 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:46:17 +0000 Subject: [PATCH 088/782] enabled precompile_results boolena value in ZiskAsmContext --- core/src/zisk_rom_2_asm.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 28791d8b7..44744d460 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -223,8 +223,7 @@ impl ZiskAsmContext { } pub fn precompile_results(&self) -> bool { - // self.precompile_results - true + self.precompile_results } pub fn precompile_results_keccak(&self) -> bool { //self.precompile_results() From c2e018204de1f7f170f95cea11e897f5eff5832a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:57:33 +0000 Subject: [PATCH 089/782] refactor shmem and sem names, moved to a pub fn --- emulator-asm/asm-runner/src/asm_mo_runner.rs | 7 +- emulator-asm/asm-runner/src/asm_mt_runner.rs | 10 +-- emulator-asm/asm-runner/src/asm_rh_runner.rs | 10 +-- emulator-asm/asm-runner/src/lib.rs | 60 +++++++++++++++++ emulator-asm/asm-runner/src/shmem_utils.rs | 70 +------------------- executor/src/executor.rs | 5 +- executor/src/hints_shmem.rs | 13 ++-- rom-setup/src/asm_setup.rs | 8 ++- 8 files changed, 93 insertions(+), 90 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index a64f6631a..8acf8cb5b 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -8,6 +8,8 @@ use std::sync::atomic::{fence, Ordering}; use std::time::Duration; use tracing::error; +use crate::sem_chunk_done_name; +use crate::shmem_output_name; use crate::{AsmMOChunk, AsmMOHeader, AsmRunError, AsmService, AsmServices, AsmSharedMemory}; use mem_planner_cpp::MemPlanner; @@ -37,7 +39,7 @@ impl PreloadedMO { AsmServices::default_port(&AsmService::MO, local_rank) }; - let output_name = AsmSharedMemory::shmem_output_name(port, AsmService::MO, local_rank); + let output_name = shmem_output_name(port, AsmService::MO, local_rank); let output_shared_memory = AsmSharedMemory::open_and_map::(&output_name, unlock_mapped_memory)?; @@ -97,8 +99,7 @@ impl AsmRunnerMO { AsmServices::default_port(&AsmService::MO, local_rank) }; - let sem_chunk_done_name = - AsmSharedMemory::shmem_chunk_done_name(port, AsmService::MO, local_rank); + let sem_chunk_done_name = sem_chunk_done_name(port, AsmService::MO, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 41ce50507..82d7ae9a7 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -9,7 +9,10 @@ use std::time::{Duration, Instant}; use tracing::{error, info}; -use crate::{AsmMTChunk, AsmMTHeader, AsmRunError, AsmService, AsmServices, AsmSharedMemory}; +use crate::{ + sem_chunk_done_name, shmem_output_name, AsmMTChunk, AsmMTHeader, AsmRunError, AsmService, + AsmServices, AsmSharedMemory, +}; use anyhow::{Context, Result}; @@ -45,7 +48,7 @@ impl PreloadedMT { AsmServices::default_port(&AsmService::MT, local_rank) }; - let output_name = AsmSharedMemory::shmem_output_name(port, AsmService::MT, local_rank); + let output_name = shmem_output_name(port, AsmService::MT, local_rank); let output_shared_memory = AsmSharedMemory::open_and_map::(&output_name, unlock_mapped_memory)?; @@ -88,8 +91,7 @@ impl AsmRunnerMT { AsmServices::default_port(&AsmService::MT, local_rank) }; - let sem_chunk_done_name = - AsmSharedMemory::shmem_chunk_done_name(port, AsmService::MT, local_rank); + let sem_chunk_done_name = sem_chunk_done_name(port, AsmService::MT, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; diff --git a/emulator-asm/asm-runner/src/asm_rh_runner.rs b/emulator-asm/asm-runner/src/asm_rh_runner.rs index c6ad9b845..03ae31b47 100644 --- a/emulator-asm/asm-runner/src/asm_rh_runner.rs +++ b/emulator-asm/asm-runner/src/asm_rh_runner.rs @@ -1,7 +1,10 @@ use tracing::error; use zisk_common::ExecutorStatsHandle; -use crate::{AsmRHData, AsmRHHeader, AsmRunError, AsmService, AsmServices, AsmSharedMemory}; +use crate::{ + sem_chunk_done_name, shmem_output_name, AsmRHData, AsmRHHeader, AsmRunError, AsmService, + AsmServices, AsmSharedMemory, +}; use anyhow::{Context, Result}; use named_sem::NamedSemaphore; use std::sync::atomic::{fence, Ordering}; @@ -23,7 +26,7 @@ impl PreloadedRH { AsmServices::default_port(&AsmService::RH, local_rank) }; - let output_name = AsmSharedMemory::shmem_output_name(port, AsmService::RH, local_rank); + let output_name = shmem_output_name(port, AsmService::RH, local_rank); let output_shared_memory = AsmSharedMemory::open_and_map::(&output_name, unlock_mapped_memory)?; @@ -74,8 +77,7 @@ impl AsmRunnerRH { AsmServices::default_port(&AsmService::RH, local_rank) }; - let sem_chunk_done_name = - AsmSharedMemory::shmem_chunk_done_name(port, AsmService::RH, local_rank); + let sem_chunk_done_name = sem_chunk_done_name(port, AsmService::RH, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 6967a4b5d..6fc23dc5e 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -41,3 +41,63 @@ pub use asm_services::*; pub use shmem_reader::*; pub use shmem_utils::*; pub use shmem_writer::*; + +fn build_name( + prefix: &str, + port: u16, + asm_service: AsmService, + local_rank: i32, + suffix: &str, +) -> String { + format!( + "{}{}_{}_{}", + prefix, + AsmServices::shmem_prefix(port, local_rank), + asm_service.as_str(), + suffix + ) +} + +fn build_shmem_name(port: u16, asm_service: AsmService, local_rank: i32, suffix: &str) -> String { + build_name("", port, asm_service, local_rank, suffix) +} + +fn build_sem_name(port: u16, asm_service: AsmService, local_rank: i32, suffix: &str) -> String { + build_name("/", port, asm_service, local_rank, suffix) +} + +pub fn shmem_input_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + build_shmem_name(port, asm_service, local_rank, "input") +} + +/// Shared memory name for precompile hints data +pub fn shmem_precompile_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + build_shmem_name(port, asm_service, local_rank, "precompile") +} + +/// Shared memory name for precompile hints data +pub fn sem_available_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + build_sem_name(port, asm_service, local_rank, "prec_avail") +} + +/// Shared memory name for precompile hints data +pub fn sem_read_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + build_sem_name(port, asm_service, local_rank, "prec_read") +} + +/// Shared memory name for precompile hints data control +pub fn shmem_control_writer_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + build_shmem_name(port, asm_service, local_rank, "control_input") +} + +pub fn shmem_control_reader_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + build_shmem_name(port, asm_service, local_rank, "control_output") +} + +pub fn shmem_output_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + build_shmem_name(port, asm_service, local_rank, "output") +} + +pub fn sem_chunk_done_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + build_sem_name(port, asm_service, local_rank, "chunk_done") +} diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 55d07185b..4831fe957 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -16,7 +16,7 @@ use zisk_common::io::{ZiskIO, ZiskStdin}; use anyhow::anyhow; use anyhow::Result; -use crate::{AsmInputC2, AsmService, AsmServices, SharedMemoryWriter}; +use crate::{AsmInputC2, SharedMemoryWriter}; pub enum AsmSharedMemoryMode { ReadOnly, @@ -275,74 +275,6 @@ impl AsmSharedMemory { // Skip the header size to get the data pointer unsafe { self.mapped_ptr.add(size_of::()) } } - - pub fn shmem_input_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - format!("{}_{}_input", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) - } - - /// Shared memory name for precompile hints data - pub fn shmem_precompile_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - format!( - "{}_{}_precompile", - AsmServices::shmem_prefix(port, local_rank), - asm_service.as_str() - ) - } - - /// Shared memory name for precompile hints data - pub fn sem_available_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - format!( - "/{}_{}_prec_avail", - AsmServices::shmem_prefix(port, local_rank), - asm_service.as_str() - ) - } - - /// Shared memory name for precompile hints data - pub fn sem_read_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - format!( - "/{}_{}_prec_read", - AsmServices::shmem_prefix(port, local_rank), - asm_service.as_str() - ) - } - - /// Shared memory name for precompile hints data control - pub fn shmem_control_writer_name( - port: u16, - asm_service: AsmService, - local_rank: i32, - ) -> String { - format!( - "{}_{}_control_input", - AsmServices::shmem_prefix(port, local_rank), - asm_service.as_str() - ) - } - - pub fn shmem_control_reader_name( - port: u16, - asm_service: AsmService, - local_rank: i32, - ) -> String { - format!( - "{}_{}_control_output", - AsmServices::shmem_prefix(port, local_rank), - asm_service.as_str() - ) - } - - pub fn shmem_output_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - format!("{}_{}_output", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) - } - - pub fn shmem_chunk_done_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - format!( - "/{}_{}_chunk_done", - AsmServices::shmem_prefix(port, local_rank), - asm_service.as_str() - ) - } } pub fn open_shmem(name: &str, flags: i32, mode: u32) -> Result { diff --git a/executor/src/executor.rs b/executor/src/executor.rs index cae62918a..9ad344bf2 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -20,7 +20,7 @@ //! maintaining clarity and modularity in the computation process. use asm_runner::{ - write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, + shmem_input_name, write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, TaskFactory, }; use fields::PrimeField64; @@ -321,8 +321,7 @@ impl ZiskExecutor { }; // Write inputs to shared memory - let shmem_input_name = - AsmSharedMemory::shmem_input_name(port, *service, self.local_rank); + let shmem_input_name = shmem_input_name(port, *service, self.local_rank); let mut input_writer = self.shmem_input_writer[idx].lock().unwrap(); if input_writer.is_none() { diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index 20b9b7de5..7fc57a05a 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -5,7 +5,8 @@ use anyhow::Result; use asm_runner::{ - AsmService, AsmServices, AsmSharedMemory, SharedMemoryReader, SharedMemoryWriter, + sem_available_name, sem_read_name, shmem_control_reader_name, shmem_control_writer_name, + shmem_precompile_name, AsmService, AsmServices, SharedMemoryReader, SharedMemoryWriter, }; use named_sem::NamedSemaphore; use std::sync::Mutex; @@ -24,11 +25,11 @@ struct ServiceResourceNames { impl ServiceResourceNames { fn new(service: &AsmService, port: u16, local_rank: i32) -> Self { Self { - control_writer: AsmSharedMemory::shmem_control_writer_name(port, *service, local_rank), - control_reader: AsmSharedMemory::shmem_control_reader_name(port, *service, local_rank), - data_name: AsmSharedMemory::shmem_precompile_name(port, *service, local_rank), - sem_available_name: AsmSharedMemory::sem_available_name(port, *service, local_rank), - sem_read_name: AsmSharedMemory::sem_read_name(port, *service, local_rank), + control_writer: shmem_control_writer_name(port, *service, local_rank), + control_reader: shmem_control_reader_name(port, *service, local_rank), + data_name: shmem_precompile_name(port, *service, local_rank), + sem_available_name: sem_available_name(port, *service, local_rank), + sem_read_name: sem_read_name(port, *service, local_rank), } } } diff --git a/rom-setup/src/asm_setup.rs b/rom-setup/src/asm_setup.rs index 571a0147d..a1614d129 100644 --- a/rom-setup/src/asm_setup.rs +++ b/rom-setup/src/asm_setup.rs @@ -47,7 +47,13 @@ pub fn generate_assembly( // Convert the ELF file to Zisk format and generates an assembly file let rv2zk = Riscv2zisk::new(elf_file_path.to_str().unwrap().to_string()); rv2zk - .runfile(asm_file.to_str().unwrap().to_string(), *gen_method, false, false, precompile_hints) + .runfile( + asm_file.to_str().unwrap().to_string(), + *gen_method, + false, + false, + precompile_hints, + ) .expect("Error converting elf to assembly"); let emulator_asm_path = zisk_path.join("emulator-asm"); From 7417e22c1ff1cd8b7384ad8c5842b02b9cc95810 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:05:02 +0000 Subject: [PATCH 090/782] moved StreamSource::from_str to from_uri --- cli/src/commands/execute.rs | 2 +- cli/src/commands/prove.rs | 2 +- cli/src/commands/stats.rs | 2 +- cli/src/commands/verify_constraints.rs | 2 +- common/src/io/stream/stream_reader.rs | 2 +- distributed/crates/worker/src/worker.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 9214fdeff..c7fa85bee 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -92,7 +92,7 @@ impl ZiskExecute { let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; - let hints_stream = StreamSource::from_str(self.precompile_hints_path.as_deref())?; + let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 8998d53c3..62a077966 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -137,7 +137,7 @@ impl ZiskProve { let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; - let hints_stream = StreamSource::from_str(self.precompile_hints_path.as_deref())?; + let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index 5e6836734..274075c1c 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -106,7 +106,7 @@ impl ZiskStats { let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; - let hints_stream = StreamSource::from_str(self.precompile_hints_path.as_deref())?; + let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index f0084eb1d..18de85451 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -98,7 +98,7 @@ impl ZiskVerifyConstraints { let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; - let hints_stream = StreamSource::from_str(self.precompile_hints_path.as_deref())?; + let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { diff --git a/common/src/io/stream/stream_reader.rs b/common/src/io/stream/stream_reader.rs index 3a71793fc..f168bd676 100644 --- a/common/src/io/stream/stream_reader.rs +++ b/common/src/io/stream/stream_reader.rs @@ -59,7 +59,7 @@ impl StreamSource { /// - `file://path/to/file` → File-based stream /// - `unix://path/to/socket` → Unix domain socket stream /// - `quic://host:port` → QUIC network stream (e.g., `quic://127.0.0.1:8080`) - pub fn from_str>(hints_uri: Option) -> Result { + pub fn from_uri>(hints_uri: Option) -> Result { if hints_uri.is_none() { return Ok(Self::null()); } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index de982dc9a..e6da3f6ec 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -533,7 +533,7 @@ impl Worker { match input_source { InputSourceDto::InputPath(inputs_uri, hints_uri) => { let stdin = ZiskStdin::from_file(inputs_uri)?; - let hints_stream = StreamSource::from_str(hints_uri.into())?; + let hints_stream = StreamSource::from_uri(hints_uri.into())?; prover.set_stdin(stdin); prover.set_hints_stream(hints_stream)?; From d8e5413195df753f275c1b0e99079d6cecddaeae Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:09:11 +0000 Subject: [PATCH 091/782] cargo clippy --- cli/src/commands/execute.rs | 2 +- cli/src/commands/prove.rs | 2 +- cli/src/commands/stats.rs | 2 +- cli/src/commands/verify_constraints.rs | 2 +- common/src/io/stream/quic.rs | 4 +--- common/src/io/stream/unix_socket.rs | 5 +---- common/src/io/zisk_stdin.rs | 12 ++++++------ 7 files changed, 12 insertions(+), 17 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index c7fa85bee..ba123e560 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -90,7 +90,7 @@ impl ZiskExecute { print_banner_field("Prec. Hints", hints); } - let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; + let stdin = ZiskStdin::from_uri(self.input.as_ref().as_deref())?; let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 62a077966..9ce113ca1 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -135,7 +135,7 @@ impl ZiskProve { gpu_params.with_max_witness_stored(self.max_witness_stored.unwrap()); } - let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; + let stdin = ZiskStdin::from_uri(self.input.as_ref().as_deref())?; let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index 274075c1c..042bf0196 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -104,7 +104,7 @@ impl ZiskStats { print_banner_field("Prec. Hints", hints); } - let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; + let stdin = ZiskStdin::from_uri(self.input.as_ref().as_deref())?; let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 18de85451..7e4f01654 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -96,7 +96,7 @@ impl ZiskVerifyConstraints { print_banner_field("Prec. Hints", hints); } - let stdin = ZiskStdin::from_str(self.input.as_ref().as_deref())?; + let stdin = ZiskStdin::from_uri(self.input.as_ref().as_deref())?; let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; diff --git a/common/src/io/stream/quic.rs b/common/src/io/stream/quic.rs index f74cdc9c5..7ade68a14 100644 --- a/common/src/io/stream/quic.rs +++ b/common/src/io/stream/quic.rs @@ -176,9 +176,7 @@ impl QuicStreamWriter { let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]) .context("Failed to generate certificate")?; - let key = rustls::pki_types::PrivateKeyDer::Pkcs8( - cert.signing_key.serialize_der().try_into().unwrap(), - ); + let key = rustls::pki_types::PrivateKeyDer::Pkcs8(cert.signing_key.serialize_der().into()); let cert_der = rustls::pki_types::CertificateDer::from(cert.cert); let mut server_config = ServerConfig::with_single_cert(vec![cert_der], key) diff --git a/common/src/io/stream/unix_socket.rs b/common/src/io/stream/unix_socket.rs index 0250e7006..5f2a40b25 100644 --- a/common/src/io/stream/unix_socket.rs +++ b/common/src/io/stream/unix_socket.rs @@ -246,10 +246,7 @@ impl UnixSocketStreamWriter { // Remove socket file if it exists and is stale if self.path.exists() { // Try to detect if socket is stale by attempting connection - let is_stale = match std::os::unix::net::UnixStream::connect(&self.path) { - Err(_) => true, // Connection failed = stale socket - Ok(_) => false, // Connection succeeded = socket still in use! - }; + let is_stale = std::os::unix::net::UnixStream::connect(&self.path).is_err(); if is_stale { std::fs::remove_file(&self.path).context("Failed to remove stale socket file")?; diff --git a/common/src/io/zisk_stdin.rs b/common/src/io/zisk_stdin.rs index 01e68cb6d..ef5ec9a82 100644 --- a/common/src/io/zisk_stdin.rs +++ b/common/src/io/zisk_stdin.rs @@ -113,16 +113,16 @@ impl ZiskStdin { /// - None -> null stream /// - "scheme://path" -> parsed based on scheme /// - No scheme -> treated as file path - pub fn from_str>(hints_uri: Option) -> Result { - if hints_uri.is_none() { + pub fn from_uri>(stdin_uri: Option) -> Result { + if stdin_uri.is_none() { return Ok(ZiskStdin::null()); } - let uri_str = hints_uri.unwrap().into(); + let uri = stdin_uri.unwrap().into(); // Check if URI contains "://" separator - if let Some(pos) = uri_str.find("://") { - let (scheme, path) = uri_str.split_at(pos); + if let Some(pos) = uri.find("://") { + let (scheme, path) = uri.split_at(pos); let path = &path[3..]; // Skip "://" match scheme { @@ -132,7 +132,7 @@ impl ZiskStdin { } } else { // No "://" found - fallback as a file path - ZiskStdin::from_file(uri_str.as_str()) + ZiskStdin::from_file(uri.as_str()) } } } From ee8bd02f6f286032157b0c3567352c646c4f9d80 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:10:56 +0000 Subject: [PATCH 092/782] simplify hints_stream loop prposed by cargo clippy --- hints/src/hints_stream.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/hints/src/hints_stream.rs b/hints/src/hints_stream.rs index cdf3d606a..87acee26c 100644 --- a/hints/src/hints_stream.rs +++ b/hints/src/hints_stream.rs @@ -83,19 +83,12 @@ impl HintsStream { hints_processor: Arc, rx: Receiver, ) { - loop { - match rx.recv() { - Ok(ThreadCommand::Process) => { - if let Err(e) = Self::process_stream(&mut stream, &hints_processor) { - tracing::error!("Error processing hints in background thread: {:?}", e); - } - } - Ok(ThreadCommand::Shutdown) | Err(_) => { - // Channel closed or shutdown requested - break; - } + while let Ok(ThreadCommand::Process) = rx.recv() { + if let Err(e) = Self::process_stream(&mut stream, &hints_processor) { + tracing::error!("Error processing hints in background thread: {:?}", e); } } + // Loop exits when Shutdown is received or channel is closed } /// Process all hints from the stream. From b5e4a6b8f40194722d16d83ab3bc3e4a99d4eede Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:19:01 +0000 Subject: [PATCH 093/782] minor fix on previous commit --- emulator-asm/asm-runner/src/shmem_writer.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index d23cc9a59..939d957a5 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -91,7 +91,7 @@ impl SharedMemoryWriter { /// * `Ok(())` - If data was successfully written /// * `Err` - If data size exceeds shared memory capacity or msync fails pub fn write_input(&self, data: &[T]) -> Result<()> { - let byte_size = data.len() * std::mem::size_of::(); + let byte_size = std::mem::size_of_val(data); if byte_size > self.size { return Err(io::Error::new( @@ -125,7 +125,8 @@ impl SharedMemoryWriter { /// # Arguments /// * `data` - A slice of data to write to shared memory pub fn write_ring_buffer(&mut self, data: &[T]) { - let byte_size = data.len() * std::mem::size_of::(); + let byte_size = std::mem::size_of_val(data); + let data_ptr = data.as_ptr() as *const u8; unsafe { From 4b9c12874113822dfb8f8cc455e28a3e8a606f03 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 17 Dec 2025 12:19:39 +0100 Subject: [PATCH 094/782] Save rdi internal register --- core/src/zisk_rom_2_asm.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 44744d460..af8b52e8c 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -302,8 +302,7 @@ impl ZiskAsmContext { false } pub fn call_wait_for_prec_avail(&self) -> bool { - //self.precompile_results() - true + self.precompile_results() } } @@ -5140,13 +5139,7 @@ impl ZiskRom2Asm { // Get result from precompile results data if ctx.precompile_results_keccak() { - Self::precompile_results_array( - ctx, - code, - unusual_code, - &ctx.b.string_value.clone(), - 25, - ); + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 25); } else { // Call the keccak function Self::push_internal_registers(ctx, code, false); @@ -8731,25 +8724,25 @@ impl ZiskRom2Asm { *code += "\tpush rax\n"; *code += "\tpush rcx\n"; *code += "\tpush rdx\n"; - //*code += "\tpush rdi\n"; + *code += "\tpush rdi\n"; // *code += "\tpush rsi\n"; // *code += "\tpush rsp\n"; *code += "\tpush r8\n"; *code += "\tpush r9\n"; *code += "\tpush r10\n"; *code += "\tpush r11\n"; - Self::push_xmm_regs(ctx, code, !extra_8); + Self::push_xmm_regs(ctx, code, extra_8); } fn pop_internal_registers(ctx: &mut ZiskAsmContext, code: &mut String, extra_8: bool) { - Self::pop_xmm_regs(ctx, code, !extra_8); + Self::pop_xmm_regs(ctx, code, extra_8); *code += "\tpop r11\n"; *code += "\tpop r10\n"; *code += "\tpop r9\n"; *code += "\tpop r8\n"; // *code += "\tpop rsp\n"; // *code += "\tpop rsi\n"; - //*code += "\tpop rdi\n"; + *code += "\tpop rdi\n"; *code += "\tpop rdx\n"; *code += "\tpop rcx\n"; *code += "\tpop rax\n"; From 7342ab7ad6b7927dfa1c51dc36a960407f1adb7a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:29:51 +0000 Subject: [PATCH 095/782] remove duplicated definitions --- hints/src/hints.rs | 2 +- hints/src/hints_definitions.rs | 6 ------ hints/src/hints_processor.rs | 28 +++++++++++++--------------- hints/src/lib.rs | 2 -- 4 files changed, 14 insertions(+), 24 deletions(-) delete mode 100644 hints/src/hints_definitions.rs diff --git a/hints/src/hints.rs b/hints/src/hints.rs index 8628a0483..b2335a79a 100644 --- a/hints/src/hints.rs +++ b/hints/src/hints.rs @@ -1,7 +1,7 @@ use crate::secp256k1_ecdsa_verify; use ziskos::syscalls::SyscallPoint256; -use crate::hints_definitions::{HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT}; +use crate::hints_processor::{HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT}; pub fn process_hints(hints: Vec) -> Vec { let mut processed_hints = Vec::new(); diff --git a/hints/src/hints_definitions.rs b/hints/src/hints_definitions.rs deleted file mode 100644 index 7c1c2492f..000000000 --- a/hints/src/hints_definitions.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Every hint is preceded by a u64 prefix that contains its type and length -// The upper 32 bits contain the hint type, and the lower 32 bits contain the length - -// Hints type constants -pub const HINTS_TYPE_RESULT: u32 = 1; // Data is already the result of the precompile -pub const HINTS_TYPE_ECRECOVER: u32 = 2; // Data is the input for the ecrecover precompile diff --git a/hints/src/hints_processor.rs b/hints/src/hints_processor.rs index 6acfaba39..8dee93e18 100644 --- a/hints/src/hints_processor.rs +++ b/hints/src/hints_processor.rs @@ -11,19 +11,19 @@ //! - **Data** (`[u64; length]`): The hint payload, where `length` is specified in the header //! //! ```text -//! ┌────────────────────────────────────────────────────────────────┐ -//! │ Header (u64) │ -//! ├────────────────────────────────┬───────────────────────────────┤ -//! │ Hint Code (32 bits) │ Length (32 bits) │ -//! ├────────────────────────────────┴───────────────────────────────┤ -//! │ Data[0] (u64) │ -//! ├────────────────────────────────────────────────────────────────┤ -//! │ Data[1] (u64) │ -//! ├────────────────────────────────────────────────────────────────┤ -//! │ ... │ -//! ├────────────────────────────────────────────────────────────────┤ -//! │ Data[length-1] (u64) │ -//! └────────────────────────────────────────────────────────────────┘ +//! ┌─────────────────────────────────────────────────────────────┐ +//! │ Header (u64) │ +//! ├·····························································┤ +//! │ Hint Code (32 bits) Length (32 bits). │ +//! ├─────────────────────────────────────────────────────────────┤ +//! │ Data[0] (u64) │ +//! ├─────────────────────────────────────────────────────────────┤ +//! │ Data[1] (u64) │ +//! ├─────────────────────────────────────────────────────────────┤ +//! │ ... │ +//! ├─────────────────────────────────────────────────────────────┤ +//! │ Data[length-1] (u64) │ +//! └─────────────────────────────────────────────────────────────┘ //! //! - Hint Code — Control code or Data Hint Type //! - Length — Number of following u64 data words @@ -56,8 +56,6 @@ use ziskos::syscalls::SyscallPoint256; use crate::{secp256k1_ecdsa_verify, HintsProcessor, HintsSink}; -// TODO! COnvert Control Code to an enum and HINT TYPE to an enum as well - /// Control code: Reset processor state and global sequence. const CTRL_START: u32 = 0x00; diff --git a/hints/src/lib.rs b/hints/src/lib.rs index 6181f4490..0616c6437 100644 --- a/hints/src/lib.rs +++ b/hints/src/lib.rs @@ -1,12 +1,10 @@ pub mod hints; -pub mod hints_definitions; mod hints_processor; mod hints_stream; pub mod secp256k1; pub use hints::*; -pub use hints_definitions::*; pub use hints_processor::{ PrecompileHint, PrecompileHintsProcessor, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT, }; From afbcd5d87ae3e85a9190c187b85853154c7df3c7 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:18:49 +0000 Subject: [PATCH 096/782] precompile hints ring buffer working --- emulator-asm/asm-runner/src/shmem_writer.rs | 10 ++++-- executor/src/hints_shmem.rs | 38 +++++++++++++-------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index 939d957a5..bb0c841c8 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -117,7 +117,7 @@ impl SharedMemoryWriter { /// Writes data to the shared memory as a ring buffer, handling wraparound automatically /// - /// Uses internal pointer tracking - no offset parameter needed. + /// Uses internal pointer tracking with automatic wraparound. /// /// # Type Parameters /// * `T` - The element type of the slice (e.g., u8, u64) @@ -153,10 +153,16 @@ impl SharedMemoryWriter { // Update current_ptr, wrapping if at end self.current_ptr = self.current_ptr.add(byte_size); let new_offset = self.current_ptr.offset_from(self.ptr) as usize; - if new_offset >= self.size { + if new_offset == self.size { self.current_ptr = self.ptr; } } + + // // Force changes to be flushed to the shared memory + // #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + // if msync(self.ptr as *mut _, self.size, MS_SYNC) != 0 { + // return Err(io::Error::last_os_error()); + // } } } diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index 7fc57a05a..5d74d23dd 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -60,7 +60,8 @@ unsafe impl Sync for HintsShmem {} impl HintsShmem { const CONTROL_PRECOMPILE_SIZE: u64 = 0x1000; // 4KB const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB - // const MAX_PRECOMPILE_SIZE: u64 = 0x100000; // 1MB + // const MAX_PRECOMPILE_SIZE: u64 = 0x100000; // 1MB + const BUFFER_CAPACITY_U64: u64 = Self::MAX_PRECOMPILE_SIZE >> 3; // Capacity in u64 elements /// Create a new HintsShmem with the given shared memory names and unlock option. /// @@ -159,33 +160,40 @@ impl HintsSink for HintsShmem { fn submit(&self, processed: Vec) -> anyhow::Result<()> { let data_size = processed.len() as u64; + debug_assert!( + data_size <= Self::BUFFER_CAPACITY_U64, + "Processed data size ({} u64 elements) exceeds maximum precompile shared memory capacity ({} u64 elements)", + data_size, + Self::BUFFER_CAPACITY_U64 + ); + let mut resources = self.resources.lock().unwrap(); for resource in resources.iter_mut() { - // Read current positions + // Read current write position once (we're the only writer) let write_pos = resource.control_writer.read_u64_at(0); - let read_pos = resource.control_reader.read_u64_at(0); - // Calculate occupied space in ring buffer (positions are absolute values) - let occupied_space = write_pos - read_pos; - let available_space = (Self::MAX_PRECOMPILE_SIZE >> 3) - occupied_space; + loop { + // Read current read position (updated by reader) + let read_pos = resource.control_reader.read_u64_at(0); + + // Calculate occupied space in ring buffer (positions are absolute values in u64 elements) + let occupied_space = write_pos - read_pos; + let available_space = Self::BUFFER_CAPACITY_U64 - occupied_space; - debug_assert!( - available_space <= (Self::MAX_PRECOMPILE_SIZE >> 3), - "Available space calculation error" - ); - // TODO! Check for overflow of write_pos and read_pos and handle it + // Flow control based on buffer occupancy + if available_space >= data_size { + break; + } - // Flow control based on buffer occupancy - if available_space < data_size { - // Not enough space - signal reader and wait for consumption + // Not enough space - wait for consumption resource.sem_read.wait()?; } // Write data to shared memory with automatic wraparound resource.data_writer.write_ring_buffer(&processed); - // Update write position in control memory with wraparound + // Update write position in control memory (absolute position, always increases) resource.control_writer.write_u64_at(0, write_pos + data_size); resource.sem_available.post()?; From 9a0efa0a8671bfb7fd9e58a64df5aaf4277902d4 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:42:38 +0000 Subject: [PATCH 097/782] added inline(s) --- emulator-asm/asm-runner/src/shmem_writer.rs | 1 + executor/src/hints_shmem.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index bb0c841c8..cc3c6f3bd 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -124,6 +124,7 @@ impl SharedMemoryWriter { /// /// # Arguments /// * `data` - A slice of data to write to shared memory + #[inline] pub fn write_ring_buffer(&mut self, data: &[T]) { let byte_size = std::mem::size_of_val(data); diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index 5d74d23dd..7ecaabaff 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -60,7 +60,7 @@ unsafe impl Sync for HintsShmem {} impl HintsShmem { const CONTROL_PRECOMPILE_SIZE: u64 = 0x1000; // 4KB const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB - // const MAX_PRECOMPILE_SIZE: u64 = 0x100000; // 1MB + // const MAX_PRECOMPILE_SIZE: u64 = 0x100000; // 1MB const BUFFER_CAPACITY_U64: u64 = Self::MAX_PRECOMPILE_SIZE >> 3; // Capacity in u64 elements /// Create a new HintsShmem with the given shared memory names and unlock option. @@ -157,6 +157,7 @@ impl HintsSink for HintsShmem { /// # Returns /// * `Ok(())` - If hints were successfully written to all shared memories /// * `Err` - If writing to any shared memory fails + #[inline] fn submit(&self, processed: Vec) -> anyhow::Result<()> { let data_size = processed.len() as u64; From e05f819d446c7b80bf49bd0c01a268f76e81a06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:57:33 +0000 Subject: [PATCH 098/782] fp2 sqrt call --- .../src/zisklib/fcalls/bls12_381_fp2_sqrt.rs | 48 ++++ ziskos/entrypoint/src/zisklib/fcalls/mod.rs | 3 + .../zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs | 248 ++++++++++++++++++ .../entrypoint/src/zisklib/fcalls_impl/mod.rs | 1 + .../src/zisklib/fcalls_impl/proxy.rs | 10 +- 5 files changed, 306 insertions(+), 4 deletions(-) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs create mode 100644 ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs new file mode 100644 index 000000000..8a312d24f --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs @@ -0,0 +1,48 @@ +//! fcall_bls12_381_fp2_sqrt free call +use cfg_if::cfg_if; +cfg_if! { + if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { + use core::arch::asm; + use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; + use super::FCALL_BLS12_381_FP2_SQRT_ID; + } +} + +/// Executes the multiplicative inverse computation over the base field of the `bls12_381` curve. +/// +/// `fcall_bls12_381_fp2_sqrt` performs an inversion of a 256-bit field element, +/// represented as an array of four `u64` values. +/// +/// - `fcall_bls12_381_fp2_sqrt` performs the inversion and **returns the result directly**. +/// +/// ### Safety +/// +/// The caller must ensure that the input pointer (`p_value`) is valid and aligned to an 8-byte boundary. +/// +/// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness +/// of the result. It is the caller's responsibility to ensure it. +#[allow(unused_variables)] +pub fn fcall_bls12_381_fp2_sqrt(p_value: &[u64; 12]) -> [u64; 13] { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!(); + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + ziskos_fcall_param!(p_value, 16); + ziskos_fcall!(FCALL_BLS12_381_FP2_SQRT_ID); + [ + ziskos_fcall_get(), // results[0] - indicates if a sqrt exists (1) or not (0) + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] + } +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs index 616b890ec..5a25548e9 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs @@ -18,11 +18,13 @@ pub const FCALL_MSB_POS_384_ID: u16 = 15; pub const FCALL_BIG_INT256_DIV_ID: u16 = 16; pub const FCALL_BIG_INT_DIV_ID: u16 = 17; pub const FCALL_BIN_DECOMP_ID: u16 = 18; +pub const FCALL_BLS12_381_FP2_SQRT_ID: u16 = 19; mod big_int256_div; mod big_int_div; mod bin_decomp; mod bls12_381_fp2_inv; +mod bls12_381_fp2_sqrt; mod bls12_381_fp_inv; mod bls12_381_fp_sqrt; mod bls12_381_twist; @@ -39,6 +41,7 @@ pub use big_int256_div::*; pub use big_int_div::*; pub use bin_decomp::*; pub use bls12_381_fp2_inv::*; +pub use bls12_381_fp2_sqrt::*; pub use bls12_381_fp_inv::*; pub use bls12_381_fp_sqrt::*; pub use bls12_381_twist::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs new file mode 100644 index 000000000..53fb5d392 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs @@ -0,0 +1,248 @@ +use lazy_static::lazy_static; +use num_bigint::BigUint; +use num_traits::{One, Zero}; + +use super::{ + bls12_381_fp2_inv::{bls12_381_fp2_mul, bls12_381_fp2_square}, + bls12_381_fp_inv::{bls12_381_fp_add, bls12_381_fp_neg}, + utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}, +}; + +const ONE: [u64; 12] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; +const P_MINUS_ONE: [u64; 12] = [ + 0xB9FEFFFFFFFFAAAA, + 0x1EABFFFEB153FFFF, + 0x6730D2A0F6B0F624, + 0x64774B84F38512BF, + 0x4B1BA7B6434BACD7, + 0x1A0111EA397FE69A, + 0, + 0, + 0, + 0, + 0, + 0, +]; +const I: [u64; 12] = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]; // 0 + 1*u +const NQR: [u64; 12] = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]; // 1 + 1*u, a known non-quadratic residue in Fp2 + +lazy_static! { + pub static ref P: BigUint = BigUint::parse_bytes( + b"1A0111EA397FE69A4B1BA7B6434BACD764774B84F38512BF6730D2A0F6B0F6241EABFFFEB153FFFFB9FEFFFFFFFFAAAB", + 16 + ) + .unwrap(); + + pub static ref P_MINUS_3_DIV_4: BigUint = BigUint::parse_bytes( + b"680447A8E5FF9A692C6E9ED90D2EB35D91DD2E13CE144AFD9CC34A83DAC3D8907AAFFFFAC54FFFFEE7FBFFFFFFFEAAA", + 16 + ) + .unwrap(); + + pub static ref P_MINUS_1_DIV_2: BigUint = BigUint::parse_bytes( + b"D0088F51CBFF34D258DD3DB21A5D66BB23BA5C279C2895FB39869507B587B120F55FFFF58A9FFFFDCFF7FFFFFFFD555", + 16 + ) + .unwrap(); +} + +/// Computes the square root of a non-zero field element in Fp2 +pub fn fcall_bls12_381_fp2_sqrt(params: &[u64], results: &mut [u64]) -> i64 { + // Get the input + let a: &[u64; 12] = ¶ms[0..12].try_into().unwrap(); + + // Perform the square root + let (sqrt, is_qr) = bls12_381_fp2_sqrt(a); + results[0] = is_qr as u64; + if !is_qr { + // To check that a is indeed a non-quadratic residue, we check that + // a * NQR is a quadratic residue for some fixed known non-quadratic residue NQR + let a_nqr = bls12_381_fp2_mul(a, &NQR); + + // Compute the square root of a * NQR + let sqrt_nqr = bls12_381_fp2_sqrt(&a_nqr).0; + + results[1..13].copy_from_slice(&sqrt_nqr); + } else { + results[1..13].copy_from_slice(&sqrt); + } + 13 +} + +/// Algorithm 9 from https://eprint.iacr.org/2012/685.pdf +/// Square root computation over F_p^2, with p ≡ 3 (mod 4) +fn bls12_381_fp2_sqrt(a: &[u64; 12]) -> ([u64; 12], bool) { + // Step 1: a1 ← a^((p-3)/4) + let a1 = bls12_381_fp2_exp(a, &P_MINUS_3_DIV_4); + + // Step 2: α ← a1 * a1 * a + let a1_a = bls12_381_fp2_mul(&a1, a); + let alpha = bls12_381_fp2_mul(&a1, &a1_a); + + // Step 3: a0 ← α^p * α = conjugate(α) * α + let a0 = bls12_381_fp2_mul(&bls12_381_fp2_conjugate(&alpha), &alpha); + + // Step 4-6: if a0 == -1 then return false (no square root) + if a0 == P_MINUS_ONE { + return ([0u64; 12], false); + } + + // Step 7: x0 ← a1 * a + let x0 = a1_a; + + // Step 8-13: compute x based on α + let x = if alpha == P_MINUS_ONE { + // Step 9: x ← i * x0 + bls12_381_fp2_mul(&I, &x0) + } else { + // Step 11: b ← (1 + α)^((p-1)/2) + let one_plus_alpha = bls12_381_fp2_add(&ONE, &alpha); + let b = bls12_381_fp2_exp(&one_plus_alpha, &P_MINUS_1_DIV_2); + + // Step 12: x ← b * x0 + bls12_381_fp2_mul(&b, &x0) + }; + + (x, true) +} + +pub(crate) fn bls12_381_fp2_conjugate(a: &[u64; 12]) -> [u64; 12] { + let mut result = [0u64; 12]; + result[0..6].copy_from_slice(&a[0..6]); + let imaginary_part: &[u64; 6] = &a[6..12].try_into().unwrap(); + let neg_imaginary_part = bls12_381_fp_neg(imaginary_part); + result[6..12].copy_from_slice(&neg_imaginary_part); + result +} + +pub(crate) fn bls12_381_fp2_add(a: &[u64; 12], b: &[u64; 12]) -> [u64; 12] { + let a_real = &a[0..6].try_into().unwrap(); + let a_imaginary = &a[6..12].try_into().unwrap(); + let b_real = &b[0..6].try_into().unwrap(); + let b_imaginary = &b[6..12].try_into().unwrap(); + + let real_part = bls12_381_fp_add(a_real, b_real); + let imaginary_part = bls12_381_fp_add(a_imaginary, b_imaginary); + + let mut result = [0u64; 12]; + result[0..6].copy_from_slice(&real_part); + result[6..12].copy_from_slice(&imaginary_part); + result +} + +pub(crate) fn bls12_381_fp2_exp(a: &[u64; 12], e: &BigUint) -> [u64; 12] { + let mut result = [0u64; 12]; + result[0] = 1; + + let mut base = *a; + let mut exp = e.clone(); + + while !exp.is_zero() { + if (&exp & BigUint::one()) == BigUint::one() { + result = bls12_381_fp2_mul(&result, &base); + } + base = bls12_381_fp2_mul(&base, &base); + exp >>= 1; + } + + result +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sqrt_one() { + let x = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let expected_sqrt = P_MINUS_ONE; + + let mut results = [0; 13]; + fcall_bls12_381_fp2_sqrt(&x, &mut results); + let has_sqrt = results[0]; + let sqrt = &results[1..13].try_into().unwrap(); + assert_eq!(has_sqrt, 1); + assert_eq!(sqrt, &expected_sqrt); + assert_eq!(bls12_381_fp2_mul(sqrt, sqrt), x); + } + + #[test] + fn test_sqrt() { + let x = [ + 0x10486089be1876e9, + 0xcf0c3012bf0c13ef, + 0x51621421d2c37a8d, + 0xd52db71259449a47, + 0x370fd7a0a4be29da, + 0xc3d4fd75c076215, + 0x3e6ff1a3151b0959, + 0x9f0b2a8dea2c9f82, + 0xb83d47ccb71501e2, + 0xa8c917818d857f05, + 0xc48150d1cd95e0c6, + 0x112ca78116187cc8, + ]; + let expected_sqrt = [ + 0xcca66dfc0d7f69c9, + 0xaf22cf40d2f4555, + 0x92a6870798aff4d7, + 0xe595438fb87ee1fc, + 0x6f5e96c633b39798, + 0x215675032da3de5, + 0x1ef8b538e151e6f3, + 0x94b37a0021182ef6, + 0xea0d1db797288ba2, + 0x567c72d5af34be56, + 0x5470d2ed597db716, + 0x10b61243878d0170, + ]; + + let mut results = [0; 13]; + fcall_bls12_381_fp2_sqrt(&x, &mut results); + let has_sqrt = results[0]; + let sqrt = &results[1..13].try_into().unwrap(); + assert_eq!(has_sqrt, 1); + assert_eq!(sqrt, &expected_sqrt); + assert_eq!(bls12_381_fp2_mul(sqrt, sqrt), x); + } + + #[test] + fn test_no_sqrt() { + let x = [ + 0x5531f66e0c366bf8, + 0x35f8f154ff2974e6, + 0xaa81eb7e92ae7b5e, + 0x8a521c9ff4654bc0, + 0xa224f0e84356bba8, + 0xffbbc4bdd5425cb, + 0xf16972261c97a569, + 0xbf071b2a52d05a68, + 0xbaa99b2bc5260f74, + 0xedbd0c20e26eb5e5, + 0x6f3229e291d1d67a, + 0x119353ab08784f06, + ]; + let expected_sqrt = [ + 0x6d8e1fc1edb82644, + 0xa6964afc770dab5d, + 0x37d90a0e925a572d, + 0x3547fbc3f051b409, + 0xd3cdef010df23067, + 0x159b8fd2cca0a180, + 0xe0c163a5a7441092, + 0xf61c7202d7c3af80, + 0xf80c7aa929cb1e62, + 0xa076467c356a64cf, + 0x695e3d70b6a86704, + 0xb1ecd8ecdb0e8d2, + ]; // sqrt(x * NQR) + + let mut results = [0; 13]; + fcall_bls12_381_fp2_sqrt(&x, &mut results); + let has_sqrt = results[0]; + let sqrt = &results[1..13].try_into().unwrap(); + assert_eq!(has_sqrt, 0); + assert_eq!(sqrt, &expected_sqrt); + assert_eq!(bls12_381_fp2_mul(sqrt, sqrt), bls12_381_fp2_mul(&x, &NQR)); + } +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs index 4c6102389..2460a788d 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs @@ -2,6 +2,7 @@ mod big_int256_div; mod big_int_div; mod bin_decomp; mod bls12_381_fp2_inv; +mod bls12_381_fp2_sqrt; mod bls12_381_fp_inv; mod bls12_381_fp_sqrt; mod bls12_381_twist; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs index 831785435..1528fc8b5 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs @@ -1,6 +1,6 @@ use crate::zisklib::{ FCALL_BIG_INT256_DIV_ID, FCALL_BIG_INT_DIV_ID, FCALL_BIN_DECOMP_ID, FCALL_BLS12_381_FP2_INV_ID, - FCALL_BLS12_381_FP_INV_ID, FCALL_BLS12_381_FP_SQRT_ID, + FCALL_BLS12_381_FP2_SQRT_ID, FCALL_BLS12_381_FP_INV_ID, FCALL_BLS12_381_FP_SQRT_ID, FCALL_BLS12_381_TWIST_ADD_LINE_COEFFS_ID, FCALL_BLS12_381_TWIST_DBL_LINE_COEFFS_ID, FCALL_BN254_FP2_INV_ID, FCALL_BN254_FP_INV_ID, FCALL_BN254_TWIST_ADD_LINE_COEFFS_ID, FCALL_BN254_TWIST_DBL_LINE_COEFFS_ID, FCALL_MSB_POS_256_ID, FCALL_MSB_POS_384_ID, @@ -8,9 +8,10 @@ use crate::zisklib::{ }; use super::{ - big_int256_div::*, big_int_div::*, bin_decomp::*, bls12_381_fp2_inv::*, bls12_381_fp_inv::*, - bls12_381_fp_sqrt::*, bls12_381_twist::*, bn254_fp::*, bn254_fp2::*, bn254_twist::*, - msb_pos_256::*, msb_pos_384::*, secp256k1_fn_inv::*, secp256k1_fp_inv::*, secp256k1_fp_sqrt::*, + big_int256_div::*, big_int_div::*, bin_decomp::*, bls12_381_fp2_inv::*, bls12_381_fp2_sqrt::*, + bls12_381_fp_inv::*, bls12_381_fp_sqrt::*, bls12_381_twist::*, bn254_fp::*, bn254_fp2::*, + bn254_twist::*, msb_pos_256::*, msb_pos_384::*, secp256k1_fn_inv::*, secp256k1_fp_inv::*, + secp256k1_fp_sqrt::*, }; pub fn fcall_proxy(id: u64, params: &[u64], results: &mut [u64]) -> i64 { @@ -36,6 +37,7 @@ pub fn fcall_proxy(id: u64, params: &[u64], results: &mut [u64]) -> i64 { FCALL_BIG_INT256_DIV_ID => fcall_big_int256_div(params, results), FCALL_BIG_INT_DIV_ID => fcall_big_int_div(params, results), FCALL_BIN_DECOMP_ID => fcall_bin_decomp(params, results), + FCALL_BLS12_381_FP2_SQRT_ID => fcall_bls12_381_fp2_sqrt(params, results), _ => panic!("Unsupported fcall ID {id}"), } } From 427e752393a9b067c7a4bfd328ca315e7263e84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:01:57 +0000 Subject: [PATCH 099/782] Adding more c-style funcs --- .../src/zisklib/lib/bls12_381/constants.rs | 5 +- .../src/zisklib/lib/bls12_381/curve.rs | 141 +++++++++++++++- .../src/zisklib/lib/bls12_381/final_exp.rs | 12 ++ .../src/zisklib/lib/bls12_381/fp.rs | 4 +- .../src/zisklib/lib/bls12_381/fp12.rs | 14 ++ .../src/zisklib/lib/bls12_381/fp2.rs | 27 ++- .../src/zisklib/lib/bls12_381/miller_loop.rs | 17 ++ .../src/zisklib/lib/bls12_381/twist.rs | 154 +++++++++++++++++- 8 files changed, 362 insertions(+), 12 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs index eb1246dcb..e08762f0e 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs @@ -47,7 +47,10 @@ pub const R: [u64; 4] = pub const R_MINUS_ONE: [u64; 4] = [R[0] - 1, R[1], R[2], R[3]]; /// A known non-quadratic residue in Fp -pub const NQR: [u64; 6] = [2, 0, 0, 0, 0, 0]; +pub const NQR_FP: [u64; 6] = [2, 0, 0, 0, 0, 0]; + +/// A known non-quadratic residue in Fp2 +pub const NQR_FP2: [u64; 12] = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]; /// This is the the order-3 element of for the σ endomorphism pub const GAMMA: [u64; 6] = [ diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs index 39e696f83..63db3bd87 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs @@ -5,14 +5,90 @@ use crate::{ syscall_bls12_381_curve_add, syscall_bls12_381_curve_dbl, SyscallBls12_381CurveAddParams, SyscallPoint384, }, - zisklib::{eq, fcall_msb_pos_384}, + zisklib::{eq, fcall_msb_pos_384, lt}, }; use super::{ - constants::{E_B, GAMMA, IDENTITY_G1}, - fp::{add_fp_bls12_381, mul_fp_bls12_381, neg_fp_bls12_381, square_fp_bls12_381}, + constants::{E_B, GAMMA, IDENTITY_G1, P}, + fp::{ + add_fp_bls12_381, mul_fp_bls12_381, neg_fp_bls12_381, sqrt_fp_bls12_381, + square_fp_bls12_381, + }, }; +/// Decompresses a G1 point on the BLS12-381 curve from 48 bytes (compressed format). +/// +/// Format: Big-endian x-coordinate with flag bits in the top 3 bits of the first byte: +/// - Bit 7 (0x80): Compression flag (must be 1 for compressed) +/// - Bit 6 (0x40): Infinity flag (1 = point at infinity) +/// - Bit 5 (0x20): Sign flag (1 = y is lexicographically largest) +pub fn decompress_bls12_381(input: &[u8; 48]) -> Result<([u64; 12], bool), &'static str> { + let flags = input[0]; + + // Check compression bit + if (flags & 0x80) == 0 { + return Err("Expected compressed point (0x80 flag not set)"); + } + + // Check infinity bit + if (flags & 0x40) != 0 { + // Verify rest is zero + if (flags & 0x3f) != 0 { + return Err("Invalid infinity encoding"); + } + for input in input.iter().skip(1) { + if *input != 0 { + return Err("Invalid infinity encoding"); + } + } + return Ok((IDENTITY_G1, true)); + } + + // Extract sign bit + let y_sign = (flags & 0x20) != 0; + + // Extract x-coordinate (big-endian), masking off flag bits + let mut x = [0u64; 6]; + let mut bytes = [0u8; 48]; + bytes.copy_from_slice(input); + bytes[0] &= 0x1f; // Clear flag bits + + // Convert from big-endian bytes to little-endian u64 limbs + for i in 0..6 { + for j in 0..8 { + x[5 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // Verify x < p + if !lt(&x, &P) { + return Err("x coordinate >= field modulus"); + } + + // Calculate the y-coordinate of the point: y = sqrt(x³ + 4) + let x_sq = square_fp_bls12_381(&x); + let x_cb = mul_fp_bls12_381(&x_sq, &x); + let y_sq = add_fp_bls12_381(&x_cb, &E_B); + + let (y, has_sqrt) = sqrt_fp_bls12_381(&y_sq); + if !has_sqrt { + return Err("No square root exists - point not on curve"); + } + + // Determine the sign of y, which is (lexicographically) done by checking if y > -y + let y_neg = neg_fp_bls12_381(&y); + let y_is_larger = lt(&y_neg, &y); + + // Select the correct y based on sign bit + let final_y = if y_is_larger == y_sign { y } else { y_neg }; + + // Return the point (x, final_y) + let mut result = [0u64; 12]; + result[0..6].copy_from_slice(&x); + result[6..12].copy_from_slice(&final_y); + Ok((result, false)) +} + /// Check if a non-zero point `p` is on the BLS12-381 curve pub fn is_on_curve_bls12_381(p: &[u64; 12]) -> bool { let x: [u64; 6] = p[0..6].try_into().unwrap(); @@ -279,3 +355,62 @@ pub unsafe extern "C" fn dbl_bls12_381_c(p: *mut u64) { *(p as *mut [u64; 6]) = p_point.x; *(p.add(6) as *mut [u64; 6]) = p_point.y; } + +/// # Safety +/// - `ret` must point to a valid `[u64; 12]` (96 bytes) for the output. +/// - `input` must point to a valid `[u8; 48]` (48 bytes) for the compressed input. +/// Returns: +/// - 0 = success (regular point) +/// - 1 = success (point at infinity) +/// - 2 = error +#[no_mangle] +pub unsafe extern "C" fn decompress_bls12_381_c(ret: *mut u64, input: *const u8) -> u8 { + let input_arr: &[u8; 48] = &*(input as *const [u8; 48]); + + match decompress_bls12_381(input_arr) { + Ok((result, is_infinity)) => { + let ret_arr: &mut [u64; 12] = &mut *(ret as *mut [u64; 12]); + *ret_arr = result; + if is_infinity { + 1 + } else { + 0 + } + } + Err(_) => 2, + } +} + +/// # Safety +/// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. +/// Returns true if the point is on the curve, false otherwise. +#[no_mangle] +pub unsafe extern "C" fn is_on_curve_bls12_381_c(p: *const u64) -> bool { + let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); + is_on_curve_bls12_381(p_arr) +} + +/// # Safety +/// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. +/// Returns true if the point is in the G1 subgroup, false otherwise. +#[no_mangle] +pub unsafe extern "C" fn is_on_subgroup_bls12_381_c(p: *const u64) -> bool { + let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); + is_on_subgroup_bls12_381(p_arr) +} + +/// # Safety +/// - `ret` must point to a valid `[u64; 12]` (96 bytes) for the output. +/// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. +/// - `k` must point to a valid `[u64; 6]` (48 bytes) for the scalar. +/// - Point must be non-zero. +#[no_mangle] +pub unsafe extern "C" fn scalar_mul_bls12_381_c(ret: *mut u64, p: *const u64, k: *const u64) { + let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); + let k_arr: &[u64; 6] = &*(k as *const [u64; 6]); + + let result = scalar_mul_bls12_381(p_arr, k_arr); + + let ret_arr: &mut [u64; 12] = &mut *(ret as *mut [u64; 12]); + *ret_arr = result; +} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs index fc9d8f727..b05802935 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs @@ -14,6 +14,8 @@ use super::{ // However, I dont think its a good idea in general to optimize verification "at all costs". /// Given f ∈ Fp12*, computes f^((p¹²-1)/r) ∈ Fp12* +/// +/// Note: Unoptimized for the case f == 1 pub fn final_exp_bls12_381(f: &[u64; 72]) -> [u64; 72] { ////////////////// // The easy part: exp by (p^6-1)(p^2+1) @@ -57,3 +59,13 @@ pub fn final_exp_bls12_381(f: &[u64; 72]) -> [u64; 72] { f } + +/// # Safety +/// - `f` must point to a valid `[u64; 72]` (576 bytes), used as both input and output. +/// - Input must be a valid non-zero Fp12 element. +#[no_mangle] +pub unsafe extern "C" fn final_exp_bls12_381_c(f: *mut u64) { + let f_arr: &[u64; 72] = &*(f as *const [u64; 72]); + let result = final_exp_bls12_381(f_arr); + std::ptr::copy_nonoverlapping(result.as_ptr(), f, 72); +} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs index 3298fa314..1e956f989 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs @@ -5,7 +5,7 @@ use crate::{ zisklib::{eq, fcall_bls12_381_fp_inv, fcall_bls12_381_fp_sqrt}, }; -use super::constants::{NQR, P, P_MINUS_ONE}; +use super::constants::{NQR_FP, P, P_MINUS_ONE}; /// Addition in Fp #[inline] @@ -121,7 +121,7 @@ pub fn sqrt_fp_bls12_381(x: &[u64; 6]) -> ([u64; 6], bool) { (sqrt, true) } else { // Check that sqrt * sqrt == x * NQR - let nqr = mul_fp_bls12_381(x, &NQR); + let nqr = mul_fp_bls12_381(x, &NQR_FP); assert_eq!(*params.d, nqr); (sqrt, false) } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs index e6f214dd3..ed558e387 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs @@ -277,3 +277,17 @@ pub fn exp_fp12_bls12_381(e: u64, a: &[u64; 72]) -> [u64; 72] { result } + +/// # Safety +/// - `ret` must point to a valid `[u64; 72]` for the output. +/// - `a` and `b` must point to valid `[u64; 72]` Fp12 elements. +#[no_mangle] +pub unsafe extern "C" fn mul_fp12_bls12_381_c(ret: *mut u64, a: *const u64, b: *const u64) { + let a_arr: &[u64; 72] = &*(a as *const [u64; 72]); + let b_arr: &[u64; 72] = &*(b as *const [u64; 72]); + + let result = mul_fp12_bls12_381(a_arr, b_arr); + + let ret_arr: &mut [u64; 72] = &mut *(ret as *mut [u64; 72]); + *ret_arr = result; +} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs index 0e4f96a82..c25d8f887 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs @@ -6,10 +6,10 @@ use crate::{ syscall_bls12_381_complex_sub, SyscallBls12_381ComplexAddParams, SyscallBls12_381ComplexMulParams, SyscallBls12_381ComplexSubParams, SyscallComplex384, }, - zisklib::{eq, fcall_bls12_381_fp2_inv}, + zisklib::{eq, fcall_bls12_381_fp2_inv, fcall_bls12_381_fp2_sqrt}, }; -use super::constants::P_MINUS_ONE; +use super::constants::{NQR_FP2, P_MINUS_ONE}; /// Helper to convert from array representation to syscall representation #[inline] @@ -103,6 +103,29 @@ pub fn square_fp2_bls12_381(a: &[u64; 12]) -> [u64; 12] { from_syscall_complex(&f1) } +/// Square root in Fp2 +#[inline] +pub fn sqrt_fp2_bls12_381(x: &[u64; 12]) -> ([u64; 12], bool) { + // Hint the sqrt + let hint = fcall_bls12_381_fp2_sqrt(x); + let is_qr = hint[0] == 1; + let sqrt = hint[1..13].try_into().unwrap(); + + // Compute sqrt * sqrt + let mul = mul_fp2_bls12_381(&sqrt, &sqrt); + + if is_qr { + // Check that sqrt * sqrt == x + assert!(eq(&mul, x)); + (sqrt, true) + } else { + // Check that sqrt * sqrt == x * NQR + let nqr = mul_fp2_bls12_381(x, &NQR_FP2); + assert!(eq(&mul, &nqr)); + (sqrt, false) + } +} + /// Inversion in Fp2: returns a⁻¹ #[inline] pub fn inv_fp2_bls12_381(a: &[u64; 12]) -> [u64; 12] { diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs index 9f46f2b26..f4cce702e 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs @@ -13,6 +13,8 @@ use super::{ }; /// Computes the Miller loop of a non-zero point `p` in G1 and a non-zero point `q` in G2 +/// +/// Note: It is not optimized for the case where either `p` or `q` is the point at infinity. pub fn miller_loop_bls12_381(p: &[u64; 12], q: &[u64; 24]) -> [u64; 72] { // Before the loop starts, compute xp' = (-xp/yp)·1/(1+u) and yp' = (1/yp)·1/(1+u) let mut xp: [u64; 6] = p[0..6].try_into().unwrap(); @@ -249,3 +251,18 @@ fn dbl_twist_with_hints_bls12_381(q: &[u64; 24], lambda: &[u64; 12], mu: &[u64; result[12..24].copy_from_slice(&y3); result } + +/// # Safety +/// - `ret` must point to a valid `[u64; 72]` for the Fp12 output. +/// - `q` must point to a valid `[u64; 24]` for the G2 affine point. +/// - `p` must point to a valid `[u64; 12]` for the G1 affine point. +#[no_mangle] +pub unsafe extern "C" fn miller_loop_bls12_381_c(ret: *mut u64, q: *const u64, p: *const u64) { + let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); + let q_arr: &[u64; 24] = &*(q as *const [u64; 24]); + + let result = miller_loop_bls12_381(p_arr, q_arr); + + let ret_arr: &mut [u64; 72] = &mut *(ret as *mut [u64; 72]); + *ret_arr = result; +} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs index e31308356..f220c95d2 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs @@ -1,19 +1,125 @@ //! Operations on the twist E': y² = x³ + 4·(1+u) of the BLS12-381 curve -use crate::zisklib::{eq, fcall_msb_pos_384}; +use crate::zisklib::{eq, fcall_msb_pos_384, lt}; use super::{ constants::{ - ETWISTED_B, EXT_U, EXT_U_INV, FROBENIUS_GAMMA13, FROBENIUS_GAMMA14, IDENTITY_G2, + ETWISTED_B, EXT_U, EXT_U_INV, FROBENIUS_GAMMA13, FROBENIUS_GAMMA14, IDENTITY_G2, P, X_ABS_BIN_BE, }, fp2::{ add_fp2_bls12_381, conjugate_fp2_bls12_381, dbl_fp2_bls12_381, inv_fp2_bls12_381, - mul_fp2_bls12_381, neg_fp2_bls12_381, scalar_mul_fp2_bls12_381, square_fp2_bls12_381, - sub_fp2_bls12_381, + mul_fp2_bls12_381, neg_fp2_bls12_381, scalar_mul_fp2_bls12_381, sqrt_fp2_bls12_381, + square_fp2_bls12_381, sub_fp2_bls12_381, }, }; +/// Decompresses a G2 point on the BLS12-381 twist from 96 bytes (compressed format). +/// +/// Format: Big-endian x-coordinate (in Fp2) with flag bits in the top 3 bits of the first byte: +/// - Bit 7 (0x80): Compression flag (must be 1 for compressed) +/// - Bit 6 (0x40): Infinity flag (1 = point at infinity) +/// - Bit 5 (0x20): Sign flag (1 = y is lexicographically largest) +pub fn decompress_twist_bls12_381(input: &[u8; 96]) -> Result<([u64; 24], bool), &'static str> { + let flags = input[0]; + + // Check compression bit + if (flags & 0x80) == 0 { + return Err("Expected compressed point (0x80 flag not set)"); + } + + // Check infinity bit + if (flags & 0x40) != 0 { + // Verify rest is zero + if (flags & 0x3f) != 0 { + return Err("Invalid infinity encoding"); + } + for item in input.iter().skip(1) { + if *item != 0 { + return Err("Invalid infinity encoding"); + } + } + return Ok((IDENTITY_G2, true)); + } + + // Extract sign bit + let y_sign = (flags & 0x20) != 0; + + // Extract x-coordinate from big-endian bytes + // Format: first 48 bytes = x_i (imaginary), next 48 bytes = x_r (real) + let mut x_i = [0u64; 6]; + let mut x_r = [0u64; 6]; + + // Parse x_i (first 48 bytes, masking flag bits in first byte) + let mut bytes_i = [0u8; 48]; + bytes_i.copy_from_slice(&input[0..48]); + bytes_i[0] &= 0x1f; // Clear flag bits + + for i in 0..6 { + for j in 0..8 { + x_i[5 - i] |= (bytes_i[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // Parse x_r (next 48 bytes) + for i in 0..6 { + for j in 0..8 { + x_r[5 - i] |= (input[48 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // Verify x_r < p and x_i < p + if !lt(&x_r, &P) { + return Err("x_r coordinate >= field modulus"); + } + if !lt(&x_i, &P) { + return Err("x_i coordinate >= field modulus"); + } + + // Build x = x_r + x_i * u as [u64; 12] + let mut x = [0u64; 12]; + x[0..6].copy_from_slice(&x_r); + x[6..12].copy_from_slice(&x_i); + + // Calculate y² = x³ + 4(1+u) + let x_sq = square_fp2_bls12_381(&x); + let x_cb = mul_fp2_bls12_381(&x_sq, &x); + let y_sq = add_fp2_bls12_381(&x_cb, &ETWISTED_B); + + // Compute sqrt + let (y, has_sqrt) = sqrt_fp2_bls12_381(&y_sq); + if !has_sqrt { + return Err("No square root exists - point not on curve"); + } + + // Determine sign of y using lexicographic ordering on Fp2 + // y = y_r + y_i * u is "larger" if: + // - y_i > -y_i, OR + // - y_i == -y_i (i.e., y_i == 0) AND y_r > -y_r + let y_neg = neg_fp2_bls12_381(&y); + let y_r: [u64; 6] = y[0..6].try_into().unwrap(); + let y_i: [u64; 6] = y[6..12].try_into().unwrap(); + let y_neg_r: [u64; 6] = y_neg[0..6].try_into().unwrap(); + let y_neg_i: [u64; 6] = y_neg[6..12].try_into().unwrap(); + + let y_is_larger = if !eq(&y_i, &y_neg_i) { + // Compare i components + lt(&y_neg_i, &y_i) + } else { + // i components equal, compare r + lt(&y_neg_r, &y_r) + }; + + // Select the correct y based on sign bit + let final_y = if y_is_larger == y_sign { y } else { y_neg }; + + // Return the point (x, final_y) + let mut result = [0u64; 24]; + result[0..12].copy_from_slice(&x); + result[12..24].copy_from_slice(&final_y); + Ok((result, false)) +} + /// Check if a non-zero point `p` is on the BLS12-381 twist pub fn is_on_curve_twist_bls12_381(p: &[u64; 24]) -> bool { // q in E' iff y² == x³ + 4·(1+u) @@ -245,3 +351,43 @@ pub fn utf_endomorphism_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { result[12..24].copy_from_slice(&y); result } + +/// # Safety +/// - `ret` must point to a valid `[u64; 24]` (192 bytes) for the output. +/// - `input` must point to a valid `[u8; 96]` (96 bytes) for the compressed input. +/// Returns: +/// - 0 = success (regular point) +/// - 1 = success (point at infinity) +/// - 2 = error +#[no_mangle] +pub unsafe extern "C" fn decompress_twist_bls12_381_c(ret: *mut u64, input: *const u8) -> u8 { + let input_arr: &[u8; 96] = &*(input as *const [u8; 96]); + + match decompress_twist_bls12_381(input_arr) { + Ok((result, is_infinity)) => { + let ret_arr: &mut [u64; 24] = &mut *(ret as *mut [u64; 24]); + *ret_arr = result; + if is_infinity { + 1 + } else { + 0 + } + } + Err(_) => 2, + } +} + +/// # Safety +/// - `ret` must point to a valid `[u64; 24]` for the output affine point. +/// - `p` must point to a valid `[u64; 24]` affine point. +/// - `k` must point to a valid `[u64; 6]` scalar. +#[no_mangle] +pub unsafe extern "C" fn scalar_mul_twist_bls12_381_c(ret: *mut u64, p: *const u64, k: *const u64) { + let p_arr: &[u64; 24] = &*(p as *const [u64; 24]); + let k_arr: &[u64; 6] = &*(k as *const [u64; 6]); + + let result = scalar_mul_twist_bls12_381(p_arr, k_arr); + + let ret_arr: &mut [u64; 24] = &mut *(ret as *mut [u64; 24]); + *ret_arr = result; +} From 50c54df0be023c395e0130dfeb7e02faa3079d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:45:34 +0000 Subject: [PATCH 100/782] Adding twist functions --- .../src/zisklib/lib/bls12_381/curve.rs | 64 +++++++++---------- .../src/zisklib/lib/bls12_381/twist.rs | 36 +++++++++++ 2 files changed, 68 insertions(+), 32 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs index 63db3bd87..69ea2a068 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs @@ -324,38 +324,6 @@ pub fn sigma_endomorphism_bls12_381(p: &[u64; 12]) -> [u64; 12] { // ========== Pointer-based API ========== -/// # Safety -/// - `p1` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -/// - `p2` must point to a valid `[u64; 12]` (96 bytes). -/// - Points must be non-zero and distinct. -#[no_mangle] -pub unsafe extern "C" fn add_bls12_381_c(p1: *mut u64, p2: *const u64) { - let mut p1_point = - SyscallPoint384 { x: *(p1 as *const [u64; 6]), y: *(p1.add(6) as *const [u64; 6]) }; - let p2_point = - SyscallPoint384 { x: *(p2 as *const [u64; 6]), y: *(p2.add(6) as *const [u64; 6]) }; - - let mut params = SyscallBls12_381CurveAddParams { p1: &mut p1_point, p2: &p2_point }; - syscall_bls12_381_curve_add(&mut params); - - *(p1 as *mut [u64; 6]) = p1_point.x; - *(p1.add(6) as *mut [u64; 6]) = p1_point.y; -} - -/// # Safety -/// - `p` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -/// - Point must be non-zero. -#[no_mangle] -pub unsafe extern "C" fn dbl_bls12_381_c(p: *mut u64) { - let mut p_point = - SyscallPoint384 { x: *(p as *const [u64; 6]), y: *(p.add(6) as *const [u64; 6]) }; - - syscall_bls12_381_curve_dbl(&mut p_point); - - *(p as *mut [u64; 6]) = p_point.x; - *(p.add(6) as *mut [u64; 6]) = p_point.y; -} - /// # Safety /// - `ret` must point to a valid `[u64; 12]` (96 bytes) for the output. /// - `input` must point to a valid `[u8; 48]` (48 bytes) for the compressed input. @@ -399,6 +367,38 @@ pub unsafe extern "C" fn is_on_subgroup_bls12_381_c(p: *const u64) -> bool { is_on_subgroup_bls12_381(p_arr) } +/// # Safety +/// - `p1` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. +/// - `p2` must point to a valid `[u64; 12]` (96 bytes). +#[no_mangle] +pub unsafe extern "C" fn add_bls12_381_c(p1: *mut u64, p2: *const u64) -> bool { + let p1_arr: &[u64; 12] = &*(p1 as *const [u64; 12]); + let p2_arr: &[u64; 12] = &*(p2 as *const [u64; 12]); + + let result = add_bls12_381(p1_arr, p2_arr); + if result == IDENTITY_G1 { + return true; + } + + let ret_arr: &mut [u64; 12] = &mut *(p1 as *mut [u64; 12]); + *ret_arr = result; + false +} + +/// # Safety +/// - `p` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. +/// - Point must be non-zero. +#[no_mangle] +pub unsafe extern "C" fn dbl_bls12_381_c(p: *mut u64) { + let mut p_point = + SyscallPoint384 { x: *(p as *const [u64; 6]), y: *(p.add(6) as *const [u64; 6]) }; + + syscall_bls12_381_curve_dbl(&mut p_point); + + *(p as *mut [u64; 6]) = p_point.x; + *(p.add(6) as *mut [u64; 6]) = p_point.y; +} + /// # Safety /// - `ret` must point to a valid `[u64; 12]` (96 bytes) for the output. /// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs index f220c95d2..6e9e2cb3f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs @@ -377,6 +377,42 @@ pub unsafe extern "C" fn decompress_twist_bls12_381_c(ret: *mut u64, input: *con } } +/// # Safety +/// - `p` must point to a valid `[u64; 24]` (192 bytes) for the input point. +/// Returns true if the point is on the twist curve, false otherwise. +#[no_mangle] +pub unsafe extern "C" fn is_on_curve_twist_bls12_381_c(p: *const u64) -> bool { + let p_arr: &[u64; 24] = &*(p as *const [u64; 24]); + is_on_curve_twist_bls12_381(p_arr) +} + +/// # Safety +/// - `p` must point to a valid `[u64; 24]` (192 bytes) for the input point. +/// Returns true if the point is in the G2 subgroup, false otherwise. +#[no_mangle] +pub unsafe extern "C" fn is_on_subgroup_twist_bls12_381_c(p: *const u64) -> bool { + let p_arr: &[u64; 24] = &*(p as *const [u64; 24]); + is_on_subgroup_twist_bls12_381(p_arr) +} + +/// # Safety +/// - `p1` must point to a valid `[u64; 24]` (192 bytes), used as both input and output. +/// - `p2` must point to a valid `[u64; 24]` (192 bytes). +#[no_mangle] +pub unsafe extern "C" fn add_twist_bls12_381_c(p1: *mut u64, p2: *const u64) -> bool { + let p1_arr: &[u64; 24] = &*(p1 as *const [u64; 24]); + let p2_arr: &[u64; 24] = &*(p2 as *const [u64; 24]); + + let result = add_twist_bls12_381(p1_arr, p2_arr); + if result == IDENTITY_G2 { + return true; + } + + let ret_arr: &mut [u64; 24] = &mut *(p1 as *mut [u64; 24]); + *ret_arr = result; + false +} + /// # Safety /// - `ret` must point to a valid `[u64; 24]` for the output affine point. /// - `p` must point to a valid `[u64; 24]` affine point. From 0fef4377d62d9d4e4594f632fba8dea0f170903e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:37:19 +0000 Subject: [PATCH 101/782] cargo clippy rustc 1.92 --- cli/src/commands/execute.rs | 2 +- cli/src/commands/prove.rs | 2 +- cli/src/commands/stats.rs | 2 +- cli/src/commands/verify_constraints.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index ba123e560..837461b02 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -90,7 +90,7 @@ impl ZiskExecute { print_banner_field("Prec. Hints", hints); } - let stdin = ZiskStdin::from_uri(self.input.as_ref().as_deref())?; + let stdin = ZiskStdin::from_uri(self.input.as_ref())?; let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 9ce113ca1..e389e91aa 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -135,7 +135,7 @@ impl ZiskProve { gpu_params.with_max_witness_stored(self.max_witness_stored.unwrap()); } - let stdin = ZiskStdin::from_uri(self.input.as_ref().as_deref())?; + let stdin = ZiskStdin::from_uri(self.input.as_ref())?; let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index 042bf0196..ed72ad500 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -104,7 +104,7 @@ impl ZiskStats { print_banner_field("Prec. Hints", hints); } - let stdin = ZiskStdin::from_uri(self.input.as_ref().as_deref())?; + let stdin = ZiskStdin::from_uri(self.input.as_ref())?; let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 7e4f01654..90b6326bd 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -96,7 +96,7 @@ impl ZiskVerifyConstraints { print_banner_field("Prec. Hints", hints); } - let stdin = ZiskStdin::from_uri(self.input.as_ref().as_deref())?; + let stdin = ZiskStdin::from_uri(self.input.as_ref())?; let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; From 9f4f00079b4b661b63adf947416a0d9a2dfd6de9 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 18 Dec 2025 07:00:44 +0000 Subject: [PATCH 102/782] hints using unix socket working --- common/src/io/stream/unix_socket.rs | 79 ++++++++------ hints/Cargo.toml | 4 + hints/src/bin/hints_socket_server.rs | 151 +++++++++++++++++++++++++++ hints/src/hints_processor.rs | 8 +- hints/src/hints_stream.rs | 2 - 5 files changed, 209 insertions(+), 35 deletions(-) create mode 100644 hints/src/bin/hints_socket_server.rs diff --git a/common/src/io/stream/unix_socket.rs b/common/src/io/stream/unix_socket.rs index 5f2a40b25..189e1b897 100644 --- a/common/src/io/stream/unix_socket.rs +++ b/common/src/io/stream/unix_socket.rs @@ -323,47 +323,66 @@ impl UnixSocketStreamWriter { impl StreamWrite for UnixSocketStreamWriter { /// Open/initialize the stream for writing /// - /// Creates a listening socket and spawns a background thread to accept connections. - /// Returns immediately without blocking. + /// Creates a listening socket and waits for a client to connect (blocking). fn open(&mut self) -> Result<()> { + // If we already have a connected socket, we're done + if self.socket.is_some() { + return Ok(()); + } + // Create listener if not exists if self.listener_fd.is_none() { self.create_listener()?; } - // Spawn accept thread if not already running - if self.accept_thread.is_none() { - let listener_fd = self.listener_fd.unwrap(); - let (tx, rx) = mpsc::channel(); - self.socket_receiver = Some(rx); - - let handle = thread::spawn(move || { - // Retry accept on EINTR - let conn_fd = loop { - let fd = unsafe { - libc::accept(listener_fd, std::ptr::null_mut(), std::ptr::null_mut()) - }; - - if fd < 0 { - let err = std::io::Error::last_os_error(); - if err.kind() == std::io::ErrorKind::Interrupted { - continue; // Retry on EINTR + // If we don't have a socket yet, either spawn accept thread or wait for it + if self.socket.is_none() { + // Spawn accept thread if not already running + if self.accept_thread.is_none() { + let listener_fd = self.listener_fd.unwrap(); + let (tx, rx) = mpsc::channel(); + self.socket_receiver = Some(rx); + + let handle = thread::spawn(move || { + // Retry accept on EINTR + let conn_fd = loop { + let fd = unsafe { + libc::accept(listener_fd, std::ptr::null_mut(), std::ptr::null_mut()) + }; + + if fd < 0 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::Interrupted { + continue; // Retry on EINTR + } + eprintln!("Accept failed: {}", err); + return; } - eprintln!("Accept failed: {}", err); - return; - } - break fd; - }; + break fd; + }; - // Convert to UnixStream - let stream = unsafe { UnixStream::from_raw_fd(conn_fd) }; + // Convert to UnixStream + let stream = unsafe { UnixStream::from_raw_fd(conn_fd) }; - // Send socket through channel - let _ = tx.send(stream); - }); + // Send socket through channel + let _ = tx.send(stream); + }); - self.accept_thread = Some(handle); + self.accept_thread = Some(handle); + } + + // Block waiting for the client connection + if let Some(rx) = &self.socket_receiver { + match rx.recv() { + Ok(stream) => { + self.socket = Some(stream); + } + Err(e) => { + return Err(anyhow::anyhow!("Failed to receive client connection: {}", e)); + } + } + } } Ok(()) diff --git a/hints/Cargo.toml b/hints/Cargo.toml index 8d1628c45..b5fe3634b 100644 --- a/hints/Cargo.toml +++ b/hints/Cargo.toml @@ -19,3 +19,7 @@ anyhow = { workspace = true } rayon = { workspace = true } tracing = { workspace = true } zisk-common = { workspace = true } + +[[bin]] +name = "hints-socket-server" +path = "src/bin/hints_socket_server.rs" diff --git a/hints/src/bin/hints_socket_server.rs b/hints/src/bin/hints_socket_server.rs new file mode 100644 index 000000000..d1c726de8 --- /dev/null +++ b/hints/src/bin/hints_socket_server.rs @@ -0,0 +1,151 @@ +//! Hints Unix Socket Server +//! +//! A development tool that opens a Unix domain socket, writes binary file contents to it, +//! and waits for the user to press '0' to close. +//! +//! Usage: hints-socket-server +//! Example: hints-socket-server hints.bin /tmp/hints.sock + +use std::fs::File; +use std::io::{self, Read}; +use std::path::Path; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; +use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; + +/// Reads binary file and returns its contents +fn read_binary_file>(path: P) -> io::Result> { + let mut file = File::open(path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + Ok(buffer) +} + +fn main() -> io::Result<()> { + let args: Vec = std::env::args().collect(); + + if args.len() != 3 { + eprintln!("Usage: {} ", args[0]); + eprintln!("Example: {} hints.bin /tmp/hints.sock", args[0]); + std::process::exit(1); + } + + let file_path = &args[1]; + let socket_path = &args[2]; + + // Read the binary file + let file_data = read_binary_file(file_path)?; + println!("Read {} bytes from: {}", file_data.len(), file_path); + + println!("========================================"); + println!("Hints Unix Socket Server"); + println!("========================================"); + println!("Binary file: {}", file_path); + println!("Socket path: {}", socket_path); + println!(); + + // Create the Unix socket writer (server) + let mut writer = UnixSocketStreamWriter::new(socket_path).map_err(io::Error::other)?; + + println!("Unix socket server created successfully"); + println!("Waiting for client connection..."); + + // Open the connection (waits for client to connect) + writer.open().map_err(io::Error::other)?; + + println!("Client connected! Starting data transfer..."); + + let shutdown = Arc::new(AtomicBool::new(false)); + + // Spawn shutdown listener thread + let shutdown_clone = Arc::clone(&shutdown); + thread::spawn(move || { + println!("Press '0' + Enter to close at any time"); + let stdin = io::stdin(); + let mut buffer = String::new(); + loop { + buffer.clear(); + if stdin.read_line(&mut buffer).is_ok() && buffer.trim() == "0" { + println!("Shutdown signal received!"); + shutdown_clone.store(true, Ordering::Relaxed); + break; + } + } + }); + + // File structure: + // - First 8 bytes: header + // - Middle: batches of hints (each hint = 26 * 8 = 208 bytes) + // - Last 8 bytes: footer + + const HINT_SIZE: usize = 26 * 8; // 208 bytes per hint + const HINTS_PER_BATCH: usize = 100; + const BATCH_SIZE: usize = HINTS_PER_BATCH * HINT_SIZE; // 20,800 bytes + + if file_data.len() < 16 { + eprintln!("Error: File too small (need at least 16 bytes for header+footer)"); + return Ok(()); + } + + let mut offset = 0; + let mut message_num = 0; + + loop { + if shutdown.load(Ordering::Relaxed) { + println!("\nShutdown requested, exiting..."); + break; + } + + if offset >= file_data.len() { + // All data sent + println!("All data sent successfully!"); + println!("Connection active. Press '0' to close..."); + while !shutdown.load(Ordering::Relaxed) { + thread::sleep(Duration::from_millis(100)); + } + break; + } + + // Determine what to send in this message + let (start, end) = if offset == 0 { + // First message: 8 bytes header + (0, 8) + } else if offset + 8 >= file_data.len() { + // Last message: final 8 bytes + (file_data.len() - 8, file_data.len()) + } else { + // Middle messages: batches of hints + let data_end = file_data.len() - 8; // Before footer + let remaining_data = data_end - offset; + let batch_size = std::cmp::min(BATCH_SIZE, remaining_data); + (offset, offset + batch_size) + }; + + let chunk = &file_data[start..end]; + + match writer.write(chunk) { + Ok(_) => { + message_num += 1; + println!( + "Message {}: Sent {} bytes (offset {}-{})", + message_num, + chunk.len(), + start, + end + ); + offset = end; + } + Err(e) => { + eprintln!("Error writing to Unix socket: {}", e); + break; + } + } + } + + println!("Closing connection..."); + let _ = writer.close(); + println!("Server shutting down..."); + Ok(()) +} diff --git a/hints/src/hints_processor.rs b/hints/src/hints_processor.rs index 8dee93e18..54e95c857 100644 --- a/hints/src/hints_processor.rs +++ b/hints/src/hints_processor.rs @@ -419,9 +419,11 @@ impl PrecompileHintsProcessor { idx += length + 1; } - debug!("Processed hints stats:"); - for (i, count) in self.stats.iter().enumerate() { - debug!("Hint type {}: {}", i, count.load(Ordering::Relaxed)); + if has_ctrl_end { + debug!("Processed hints stats:"); + for (i, count) in self.stats.iter().enumerate() { + debug!("Hint type {}: {}", i, count.load(Ordering::Relaxed)); + } } Ok(has_ctrl_end) diff --git a/hints/src/hints_stream.rs b/hints/src/hints_stream.rs index 87acee26c..c59577e39 100644 --- a/hints/src/hints_stream.rs +++ b/hints/src/hints_stream.rs @@ -5,7 +5,6 @@ use anyhow::Result; use std::sync::mpsc::{Receiver, Sender}; use std::sync::Arc; use std::thread::{self, JoinHandle}; -use tracing::debug; use zisk_common::io::{StreamRead, StreamSource}; enum ThreadCommand { @@ -105,7 +104,6 @@ impl HintsStream { // Break if CTRL_END was encountered if has_ctrl_end { - debug!("CTRL_END encountered, stopping hint processing"); break; } } From cc9d6ee48d9e533c286e93b19a7aa416257a4b72 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 18 Dec 2025 08:09:11 +0000 Subject: [PATCH 103/782] fix case typos --- cli/src/commands/server.rs | 4 ++-- distributed/crates/worker/src/cli/main.rs | 4 ++-- sdk/src/builder.rs | 8 ++++---- tools/test-env/utils.sh | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cli/src/commands/server.rs b/cli/src/commands/server.rs index bae9ac6d1..ded7d0efd 100644 --- a/cli/src/commands/server.rs +++ b/cli/src/commands/server.rs @@ -238,7 +238,7 @@ impl ZiskServer { get_witness_computation_lib(self.witness_lib.as_ref()).display() ); - println!("{: >12} {}", "Elf".bright_green().bold(), self.elf.display()); + println!("{: >12} {}", "ELF".bright_green().bold(), self.elf.display()); if self.asm.is_some() { let asm_path = self.asm.as_ref().unwrap().display(); @@ -253,7 +253,7 @@ impl ZiskServer { println!( "{: >12} {}", - "Proving key".bright_green().bold(), + "Proving Key".bright_green().bold(), get_proving_key(self.proving_key.as_ref()).display() ); diff --git a/distributed/crates/worker/src/cli/main.rs b/distributed/crates/worker/src/cli/main.rs index c092b0c8f..34ce96265 100644 --- a/distributed/crates/worker/src/cli/main.rs +++ b/distributed/crates/worker/src/cli/main.rs @@ -209,7 +209,7 @@ fn print_command_info( get_witness_computation_lib(Some(&prover_config.witness_lib)).display() ); - println!("{: >12} {}", "Elf".bright_green().bold(), prover_config.elf.display()); + println!("{: >12} {}", "ELF".bright_green().bold(), prover_config.elf.display()); if prover_config.asm.is_some() { if let Some(asm_port) = prover_config.asm_port.as_ref() { println!("{: >12} {}", "Asm port".bright_green().bold(), asm_port); @@ -225,7 +225,7 @@ fn print_command_info( } println!( "{: >12} {}", - "Proving key".bright_green().bold(), + "Proving Key".bright_green().bold(), get_proving_key(Some(&prover_config.proving_key)).display() ); diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 0734b0505..404a5b9b5 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -372,13 +372,13 @@ impl ProverClientBuilder { } println!("{: >12} {}", "Witness Lib".bright_green().bold(), witness_lib.display()); - println!("{: >12} {}", "Elf".bright_green().bold(), elf.display()); + println!("{: >12} {}", "ELF".bright_green().bold(), elf.display()); println!( "{: >12} {}", "Emulator".bright_green().bold(), "Running in emulator mode".bright_yellow() ); - println!("{: >12} {}", "Proving key".bright_green().bold(), proving_key.display()); + println!("{: >12} {}", "Proving Key".bright_green().bold(), proving_key.display()); if let Some(output_dir) = output_dir { println!("{: >12} {}", "Output Dir".bright_green().bold(), output_dir.display()); @@ -508,8 +508,8 @@ impl ProverClientBuilder { } println!("{: >12} {}", "Witness Lib".bright_green().bold(), witness_lib.display()); - println!("{: >12} {}", "Elf".bright_green().bold(), elf.display()); - println!("{: >12} {}", "Proving key".bright_green().bold(), proving_key.display()); + println!("{: >12} {}", "ELF".bright_green().bold(), elf.display()); + println!("{: >12} {}", "Proving Key".bright_green().bold(), proving_key.display()); if let Some(output_dir) = output_dir { println!("{: >12} {}", "Output Dir".bright_green().bold(), output_dir.display()); diff --git a/tools/test-env/utils.sh b/tools/test-env/utils.sh index e5b631f7b..0396c1a59 100755 --- a/tools/test-env/utils.sh +++ b/tools/test-env/utils.sh @@ -150,7 +150,7 @@ is_proving_key_installed() { if [[ -d "$HOME/.zisk/provingKey" ]]; then return 0 else - err "Proving key not installed. Please install it first." + err "Proving Key not installed. Please install it first." return 1 fi } From 07f05d6f6961b1b345c130a06473ed9418444106 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 18 Dec 2025 12:17:02 +0100 Subject: [PATCH 104/782] Implement BLS12_381ComplexSqrt in lib-c and fcall --- lib-c/c/src/bls12_381/bls12_381.cpp | 42 ++++++++++ lib-c/c/src/bls12_381/bls12_381.hpp | 18 ++++ lib-c/c/src/bls12_381/bls12_381_fe.hpp | 110 +++++++++++++++++++++++++ lib-c/c/src/common/globals.cpp | 4 +- lib-c/c/src/common/globals.hpp | 4 +- lib-c/c/src/fcall/fcall.cpp | 55 ++++++++++++- lib-c/c/src/fcall/fcall.hpp | 4 + 7 files changed, 234 insertions(+), 3 deletions(-) diff --git a/lib-c/c/src/bls12_381/bls12_381.cpp b/lib-c/c/src/bls12_381/bls12_381.cpp index 032e1e8b5..7ffc7fefb 100644 --- a/lib-c/c/src/bls12_381/bls12_381.cpp +++ b/lib-c/c/src/bls12_381/bls12_381.cpp @@ -187,6 +187,48 @@ int BLS12_381ComplexMulP (const uint64_t * p1, const uint64_t * p2, uint64_t * p return result; } +/**************************/ +/* BLS12_381 complex sqrt */ +/**************************/ + +int BLS12_381ComplexSqrt ( + const uint64_t * _x1, // 6 x 64 bits + const uint64_t * _y1, // 6 x 64 bits + uint64_t * _x2, // 6 x 64 bits + uint64_t * _y2, // 6 x 64 bits + uint64_t * is_qr // 1 x 64 bits +) +{ + RawBLS12_381_384::Element x1, y1, x2, y2; + array2fe(_x1, x1); + array2fe(_y1, y1); + + int result = BLS12_381ComplexSqrtFe (x1, y1, x2, y2, *is_qr); + + fe2array(x2, _x2); + fe2array(y2, _y2); + + return result; +} + +int BLS12_381ComplexSqrtP ( + const uint64_t * p1, // 12 x 64 bits + uint64_t * p2, // 12 x 64 bits + uint64_t * is_qr // 1 x 64 bits +) +{ + RawBLS12_381_384::Element x1, y1, x2, y2; + array2fe(p1, x1); + array2fe(p1 + 6, y1); + + int result = BLS12_381ComplexSqrtFe (x1, y1, x2, y2, *is_qr); + + fe2array(x2, p2); + fe2array(y2, p2 + 6); + + return result; +} + #ifdef __cplusplus } // extern "C" #endif \ No newline at end of file diff --git a/lib-c/c/src/bls12_381/bls12_381.hpp b/lib-c/c/src/bls12_381/bls12_381.hpp index c1871f2ca..ca3627f7a 100644 --- a/lib-c/c/src/bls12_381/bls12_381.hpp +++ b/lib-c/c/src/bls12_381/bls12_381.hpp @@ -99,6 +99,24 @@ int BLS12_381ComplexMulP ( uint64_t * p3 // 12 x 64 bits ); +/**************************/ +/* BLS12_381 complex sqrt */ +/**************************/ + +int BLS12_381ComplexSqrt ( + const uint64_t * x1, // 6 x 64 bits + const uint64_t * y1, // 6 x 64 bits + uint64_t * x2, // 6 x 64 bits + uint64_t * y3, // 6 x 64 bits + uint64_t * is_qr // 1 x 64 bits +); + +int BLS12_381ComplexSqrtP ( + const uint64_t * p1, // 12 x 64 bits + uint64_t * p2, // 12 x 64 bits + uint64_t * is_qr // 1 x 64 bits +); + #ifdef __cplusplus } // extern "C" #endif diff --git a/lib-c/c/src/bls12_381/bls12_381_fe.hpp b/lib-c/c/src/bls12_381/bls12_381_fe.hpp index 739889e00..6b5aa03d7 100644 --- a/lib-c/c/src/bls12_381/bls12_381_fe.hpp +++ b/lib-c/c/src/bls12_381/bls12_381_fe.hpp @@ -136,6 +136,116 @@ int inline BLS12_381ComplexInvFe (const RawBLS12_381_384::Element &real, const R return 0; }; +int inline BLS12_381ComplexExpFe (const RawBLS12_381_384::Element &x1, const RawBLS12_381_384::Element &y1, const mpz_class &_exp, RawBLS12_381_384::Element &x2, RawBLS12_381_384::Element &y2) +{ + // Exponentiation of a complex number using square-and-multiply algorithm + + // Get a local copy of the base to modify it + RawBLS12_381_384::Element base_x, base_y; + base_x = x1; + base_y = y1; + + // Get a scalar copy of the exponent to modify it + mpz_class exp(_exp); + + // Initialize result to 1 + 0i + x2 = bls12_381.one(); // x2 = 1 + y2 = bls12_381.zero(); // y2 = 0 + + // Loop until exponent becomes zero + while (exp != 0) + { + // If exponent is odd, multiply the result by the base + if ((exp & 1) == 1) + { + BLS12_381ComplexMulFe(x2, y2, base_x, base_y, x2, y2); + } + + // Square the base + BLS12_381ComplexMulFe(base_x, base_y, base_x, base_y, base_x, base_y); + + // Divide exponent by 2 + exp = exp >> 1; + } + + return 0; +} + +int inline BLS12_381ComplexSqrtFe (const RawBLS12_381_384::Element &x1, const RawBLS12_381_384::Element &y1, RawBLS12_381_384::Element &x2, RawBLS12_381_384::Element &y2, uint64_t &is_qr) +{ + /// Algorithm 9 from https://eprint.iacr.org/2012/685.pdf + /// Square root computation over F_p^2, with p ≡ 3 (mod 4) + + // Step 1: a1 ← a^((p-3)/4) + RawBLS12_381_384::Element a1_x, a1_y; + BLS12_381ComplexExpFe(x1, x2, ScalarP_MINUS_3_DIV_4, a1_x, a1_y); + + // Step 2: α ← a1 * a1 * a + RawBLS12_381_384::Element a1_a_x, a1_a_y; + BLS12_381ComplexMulFe(a1_x, a1_y, x1, y1, a1_a_x, a1_a_y); + RawBLS12_381_384::Element alpha_x, alpha_y; + BLS12_381ComplexMulFe(a1_x, a1_y, a1_a_x, a1_a_y, alpha_x, alpha_y); + + // Step 3: a0 ← α^p * α = conjugate(α) * α + RawBLS12_381_384::Element alpha_conj_x, alpha_conj_y; + bls12_381.copy(alpha_conj_x, alpha_x); + bls12_381.neg(alpha_conj_y, alpha_y); + RawBLS12_381_384::Element a0_x, a0_y; + BLS12_381ComplexMulFe(alpha_conj_x, alpha_conj_y, alpha_x, alpha_y, a0_x, a0_y); + + // Step 4-6: if a0 == -1 then return false (no square root) + if (bls12_381.eq(a0_x, bls12_381.negOne()) && bls12_381.isZero(a0_y)) + { + // Return false (no square root exists) + is_qr = 0; + x2 = bls12_381.zero(); + y2 = bls12_381.zero(); + return 0; + } + + // Step 7: x0 ← a1 * a + #define x0_x a1_a_x + #define x0_y a1_a_y + + // Step 8-13: compute x based on α + // If α == -1 then x ← i * x0 else x ← b * x0 + if (bls12_381.eq(a0_x, bls12_381.negOne()) && bls12_381.isZero(a0_y)) + { + // Step 9: x ← i * x0 + BLS12_381ComplexMulFe( + bls12_381.zero(), // i real part = 0 + bls12_381.one(), // i imaginary part = 1 + x0_x, + x0_y, + x2, + y2 + ); + } + else + { + // Step 11: b ← (1 + α)^((p-1)/2) + RawBLS12_381_384::Element one_plus_alpha_x, one_plus_alpha_y; + BLS12_381ComplexAddFe( + bls12_381.one(), // 1 real part = 1 + bls12_381.zero(), // 1 imaginary part = 0 + alpha_x, + alpha_y, + one_plus_alpha_x, + one_plus_alpha_y + ); + RawBLS12_381_384::Element b_x, b_y; + BLS12_381ComplexExpFe(one_plus_alpha_x, one_plus_alpha_y, ScalarP_MINUS_1_DIV_2, b_x, b_y); + + // Step 12: x ← b * x0 + BLS12_381ComplexMulFe(b_x, b_y, x0_x, x0_y, x2, y2); + } + + // Return true (square root exists) + is_qr = 1; + + return 0; +} + #ifdef __cplusplus } // extern "C" #endif diff --git a/lib-c/c/src/common/globals.cpp b/lib-c/c/src/common/globals.cpp index 77de2158e..d242ee558 100644 --- a/lib-c/c/src/common/globals.cpp +++ b/lib-c/c/src/common/globals.cpp @@ -9,4 +9,6 @@ mpz_class ScalarMask256 ("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF mpz_class ScalarMask384 ("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16); mpz_class ScalarP_DIV_4 ("680447a8e5ff9a692c6e9ed90d2eb35d91dd2e13ce144afd9cc34a83dac3d8907aaffffac54ffffee7fbfffffffeaab", 16); mpz_class ScalarP ("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", 16); -mpz_class ScalarNQR ("2", 16); // First non-quadratic residue in Fp \ No newline at end of file +mpz_class ScalarNQR_FP ("2", 16); // First non-quadratic residue in Fp +mpz_class ScalarP_MINUS_3_DIV_4 ("680447A8E5FF9A692C6E9ED90D2EB35D91DD2E13CE144AFD9CC34A83DAC3D8907AAFFFFAC54FFFFEE7FBFFFFFFFEAAA", 16); +mpz_class ScalarP_MINUS_1_DIV_2 ("D0088F51CBFF34D258DD3DB21A5D66BB23BA5C279C2895FB39869507B587B120F55FFFF58A9FFFFDCFF7FFFFFFFD555", 16); \ No newline at end of file diff --git a/lib-c/c/src/common/globals.hpp b/lib-c/c/src/common/globals.hpp index 2774092cc..e9939409a 100644 --- a/lib-c/c/src/common/globals.hpp +++ b/lib-c/c/src/common/globals.hpp @@ -16,6 +16,8 @@ extern mpz_class ScalarMask256; extern mpz_class ScalarMask384; extern mpz_class ScalarP_DIV_4; extern mpz_class ScalarP; -extern mpz_class ScalarNQR; +extern mpz_class ScalarNQR_FP; +extern mpz_class ScalarP_MINUS_3_DIV_4; +extern mpz_class ScalarP_MINUS_1_DIV_2; #endif \ No newline at end of file diff --git a/lib-c/c/src/fcall/fcall.cpp b/lib-c/c/src/fcall/fcall.cpp index b5dfa1321..e463e5256 100644 --- a/lib-c/c/src/fcall/fcall.cpp +++ b/lib-c/c/src/fcall/fcall.cpp @@ -2,6 +2,7 @@ #include "../common/utils.hpp" #include "../bn254/bn254_fe.hpp" #include "../bls12_381/bls12_381_fe.hpp" +#include "../bls12_381/bls12_381.hpp" #include #include @@ -98,6 +99,11 @@ int Fcall ( iresult = BinDecompCtx(ctx); break; } + case FCALL_BLS12_381_FP2_SQRT_ID: + { + iresult = BLS12_381Fp2SqrtCtx(ctx); + break; + } default: { printf("Fcall() found unsupported function_id=%lu\n", ctx->function_id); @@ -598,7 +604,7 @@ int BLS12_381FpSqrt ( { // To check that a is indeed a non-quadratic residue, we check that // a * NQR is a quadratic residue for some fixed known non-quadratic residue NQR - mpz_class a_nqr = (a * ScalarNQR) % ScalarP; + mpz_class a_nqr = (a * ScalarNQR_FP) % ScalarP; // Compute the square root of a * NQR mpz_powm(r.get_mpz_t(), a_nqr.get_mpz_t(), ScalarP_DIV_4.get_mpz_t(), ScalarP.get_mpz_t()); @@ -963,5 +969,52 @@ int BinDecompCtx ( ctx->result[0] = ctx->result_size; ctx->result_size++; + return 0; +} + +/**********************/ +/* BLS12 381 FP2 SQRT */ +/**********************/ + +uint64_t NQR[12] = {1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}; + +/// Computes the square root of a non-zero field element in Fp2 +int BLS12_381Fp2SqrtCtx ( + struct FcallContext * ctx // fcall context +) +{ + int result; + + // Perform the square root + result = BLS12_381ComplexSqrtP( + &ctx->params[0], // 12 x 64 bits input parameter: real(6) + imaginary(6) + &ctx->result[1], // 12 x 64 bits output parameter: real(6) + imaginary(6) + &ctx->result[0] // 1 x 64 bits output parameter: is_quadratic_residue (1) + ); + if (result != 0) return result; + + // Check if a is a quadratic residue + if (!ctx->result[0]) + { + // To check that a is indeed a non-quadratic residue, we check that + // a * NQR is a quadratic residue for some fixed known non-quadratic residue NQR + uint64_t a_nqr[12]; + result = BLS12_381ComplexMulP( + &ctx->params[0], // 12 x 64 bits input parameter: real(6) + imaginary(6) + &NQR[0], // 12 x 64 bits input parameter: real(6) + imaginary(6) + &a_nqr[0] // 12 x 64 bits output parameter: real(6) + imaginary(6) + ); + if (result != 0) return result; + + // Compute the square root of a * NQR + uint64_t aux; // Unused + result = BLS12_381ComplexSqrtP( + &a_nqr[0], // 12 x 64 bits input parameter: real(6) + imaginary(6) + &ctx->result[1], // 12 x 64 bits output parameter: real(6) + imaginary(6) + &aux // 1 x 64 bits output parameter: is_quadratic_residue (1) + ); + if (result != 0) return result; + } + return 0; } \ No newline at end of file diff --git a/lib-c/c/src/fcall/fcall.hpp b/lib-c/c/src/fcall/fcall.hpp index 768777e81..b9701abe5 100644 --- a/lib-c/c/src/fcall/fcall.hpp +++ b/lib-c/c/src/fcall/fcall.hpp @@ -25,6 +25,7 @@ extern "C" { #define FCALL_BIGINT256_DIV_ID 16 #define FCALL_BIG_INT_DIV_ID 17 #define FCALL_BIN_DECOMP_ID 18 +#define FCALL_BLS12_381_FP2_SQRT_ID 19 #define FCALL_PARAMS_MAX_SIZE 386 #define FCALL_RESULT_MAX_SIZE 8193 @@ -98,6 +99,9 @@ int BigIntDivCtx ( int BinDecompCtx ( struct FcallContext * ctx // fcall context ); +int BLS12_381Fp2SqrtCtx ( + struct FcallContext * ctx // fcall context +); // Functions supported by fcall, in u64 array format int InverseFpEc ( From 62a2c7c05605e0d54f1bb5c87451c3416da19962 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 18 Dec 2025 14:15:05 +0100 Subject: [PATCH 105/782] Fix fcall bls12 381 sqrt --- lib-c/c/src/bls12_381/bls12_381_fe.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib-c/c/src/bls12_381/bls12_381_fe.hpp b/lib-c/c/src/bls12_381/bls12_381_fe.hpp index 6b5aa03d7..a42ea28a2 100644 --- a/lib-c/c/src/bls12_381/bls12_381_fe.hpp +++ b/lib-c/c/src/bls12_381/bls12_381_fe.hpp @@ -178,7 +178,7 @@ int inline BLS12_381ComplexSqrtFe (const RawBLS12_381_384::Element &x1, const Ra // Step 1: a1 ← a^((p-3)/4) RawBLS12_381_384::Element a1_x, a1_y; - BLS12_381ComplexExpFe(x1, x2, ScalarP_MINUS_3_DIV_4, a1_x, a1_y); + BLS12_381ComplexExpFe(x1, y1, ScalarP_MINUS_3_DIV_4, a1_x, a1_y); // Step 2: α ← a1 * a1 * a RawBLS12_381_384::Element a1_a_x, a1_a_y; From a35d26e15f087222992973609a47533a84248a97 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:07:10 +0000 Subject: [PATCH 106/782] hints stream reorganization --- Cargo.lock | 28 ++++++------ Cargo.toml | 4 +- common/src/io/stream/mod.rs | 2 + .../src/io/stream/stream.rs | 44 ++++++++++++++----- executor/Cargo.toml | 2 +- executor/src/executor.rs | 9 ++-- executor/src/hints_shmem.rs | 4 +- hints/src/hints.rs | 44 ------------------- hints/src/lib.rs | 26 ----------- {hints => precompiles/hints}/Cargo.toml | 4 +- .../hints}/src/bin/hints_socket_server.rs | 0 .../hints}/src/hints_processor.rs | 17 +++---- precompiles/hints/src/lib.rs | 7 +++ .../hints}/src/secp256k1/curve.rs | 0 .../hints}/src/secp256k1/mod.rs | 0 .../hints}/src/secp256k1/scalar.rs | 0 16 files changed, 76 insertions(+), 115 deletions(-) rename hints/src/hints_stream.rs => common/src/io/stream/stream.rs (72%) delete mode 100644 hints/src/hints.rs delete mode 100644 hints/src/lib.rs rename {hints => precompiles/hints}/Cargo.toml (91%) rename {hints => precompiles/hints}/src/bin/hints_socket_server.rs (100%) rename {hints => precompiles/hints}/src/hints_processor.rs (98%) create mode 100644 precompiles/hints/src/lib.rs rename {hints => precompiles/hints}/src/secp256k1/curve.rs (100%) rename {hints => precompiles/hints}/src/secp256k1/mod.rs (100%) rename {hints => precompiles/hints}/src/secp256k1/scalar.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 015a4812a..89631e0ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1412,6 +1412,7 @@ dependencies = [ "precomp-big-int", "precomp-keccakf", "precomp-sha256f", + "precompiles-hints", "proofman", "proofman-common", "proofman-util", @@ -1427,7 +1428,6 @@ dependencies = [ "witness", "zisk-common", "zisk-core", - "zisk-hints", "zisk-pil", "ziskemu", ] @@ -3151,6 +3151,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "precompiles-hints" +version = "0.15.0" +dependencies = [ + "anyhow", + "lib-c", + "precompiles-helpers", + "rayon", + "tracing", + "zisk-common", + "ziskos", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -6024,19 +6037,6 @@ dependencies = [ "zisk-sdk", ] -[[package]] -name = "zisk-hints" -version = "0.15.0" -dependencies = [ - "anyhow", - "lib-c", - "precompiles-helpers", - "rayon", - "tracing", - "zisk-common", - "ziskos", -] - [[package]] name = "zisk-pil" version = "0.15.0" diff --git a/Cargo.toml b/Cargo.toml index 7776675de..9275f4d47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ members = [ "precompiles/arith_eq", "precompiles/arith_eq_384", "precompiles/common", + "precompiles/hints", "precompiles/keccakf", "precompiles/sha256f", "precompiles/big_int", @@ -47,7 +48,6 @@ members = [ "distributed/crates/worker", "sdk", "ziskbuild", - "hints", ] resolver = "2" @@ -96,7 +96,7 @@ ziskos = { path = "ziskos/entrypoint" } circuit = { path = "tools/circuit" } zisk-sdk = { path = "sdk" } zisk-build = { path = "ziskbuild" } -zisk-hints = { path = "hints" } +precompiles-hints = { path = "precompiles/hints" } # Distributed crates zisk-distributed-common = { path = "distributed/crates/common" } diff --git a/common/src/io/stream/mod.rs b/common/src/io/stream/mod.rs index eceeb17c9..08b3d3854 100644 --- a/common/src/io/stream/mod.rs +++ b/common/src/io/stream/mod.rs @@ -3,6 +3,7 @@ mod null; mod quic; mod stream_reader; mod stream_writer; +mod stream; #[cfg(unix)] mod unix_socket; @@ -12,6 +13,7 @@ pub use null::NullStreamReader; pub use quic::{QuicStreamReader, QuicStreamWriter}; pub use stream_reader::*; pub use stream_writer::*; +pub use stream::*; #[cfg(unix)] pub use unix_socket::{UnixSocketStreamReader, UnixSocketStreamWriter}; diff --git a/hints/src/hints_stream.rs b/common/src/io/stream/stream.rs similarity index 72% rename from hints/src/hints_stream.rs rename to common/src/io/stream/stream.rs index c59577e39..4fb42b29b 100644 --- a/hints/src/hints_stream.rs +++ b/common/src/io/stream/stream.rs @@ -1,19 +1,41 @@ -//! HintsStream is responsible for reading precompile hints from a stream source and sent to a hints processor. +//! ZiskStream is responsible for reading precompile hints from a stream source and sent to a hints processor. -use crate::HintsProcessor; use anyhow::Result; use std::sync::mpsc::{Receiver, Sender}; use std::sync::Arc; use std::thread::{self, JoinHandle}; -use zisk_common::io::{StreamRead, StreamSource}; + +use crate::io::{StreamRead, StreamSource}; + +pub trait StreamProcessor { + /// Process data and return the processed result along with a flag indicating if CTRL_END was encountered. + /// + /// # Returns + /// A tuple of (processed_data, has_ctrl_end) where: + /// - processed_data: Vec - The processed data + /// - has_ctrl_end: bool - True if CTRL_END was found (signals end of batch) + fn process(&self, data: &[u64], first_batch: bool) -> anyhow::Result; +} + +/// Trait for submitting processed hints to a sink. +/// +/// # Arguments +/// * `processed` - A vector of processed hints as u64 values. +/// +/// # Returns +/// * `Ok(())` - If hints were successfully submitted +/// * `Err` - If submission fails +pub trait StreamSink { + fn submit(&self, processed: Vec) -> anyhow::Result<()>; +} enum ThreadCommand { Process, Shutdown, } -/// HintsStream struct manages the processing of precompile hints and writing them to shared memory. -pub struct HintsStream { +/// ZiskStream struct manages the processing of precompile hints and writing them to shared memory. +pub struct ZiskStream { /// The hints processor used to process hints before writing. hints_processor: Arc, @@ -24,14 +46,14 @@ pub struct HintsStream { thread_handle: Option>, } -impl HintsStream { - /// Create a new HintsStream with the given processor. +impl ZiskStream { + /// Create a new ZiskStream with the given processor. /// /// # Arguments /// * `hints_processor` - The processor used to process hints. /// /// # Returns - /// A new `HintsStream` instance without a running thread. + /// A new `ZiskStream` instance without a running thread. pub fn new(hints_processor: HP) -> Self { Self { hints_processor: Arc::new(hints_processor), tx: None, thread_handle: None } } @@ -97,8 +119,8 @@ impl HintsStream { let mut first_batch = true; while let Some(hints) = stream.next()? { - let hints = zisk_common::reinterpret_vec(hints)?; - let has_ctrl_end = hints_processor.process_hints(&hints, first_batch)?; + let hints = crate::reinterpret_vec(hints)?; + let has_ctrl_end = hints_processor.process(&hints, first_batch)?; first_batch = false; @@ -132,7 +154,7 @@ impl HintsStream { } } -impl Drop for HintsStream { +impl Drop for ZiskStream { fn drop(&mut self) { self.stop_thread(); } diff --git a/executor/Cargo.toml b/executor/Cargo.toml index 980f8de30..b5df3ef3d 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -29,7 +29,7 @@ tracing = { workspace = true } itertools = { workspace = true } rayon = { workspace = true } pil-std-lib = { workspace = true } -zisk-hints = { workspace = true } +precompiles-hints = { workspace = true } anyhow = { workspace = true } crossbeam = "0.8.4" diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 9ad344bf2..0b46ea671 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -25,6 +25,7 @@ use asm_runner::{ }; use fields::PrimeField64; use pil_std_lib::Std; +use precompiles_hints::PrecompileHintsProcessor; use proofman_common::{create_pool, BufferPool, ProofCtx, ProofmanError, ProofmanResult, SetupCtx}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rayon::prelude::*; @@ -33,8 +34,7 @@ use sm_rom::{RomInstance, RomSM}; use std::sync::atomic::{AtomicUsize, Ordering}; use tracing::debug; use witness::WitnessComponent; -use zisk_common::io::{StreamSource, ZiskIO, ZiskStdin}; -use zisk_hints::PrecompileHintsProcessor; +use zisk_common::io::{StreamSource, ZiskIO, ZiskStdin, ZiskStream}; use crate::{DummyCounter, HintsShmem}; use data_bus::DataBusTrait; @@ -44,7 +44,6 @@ use zisk_common::{ InstanceCtx, InstanceType, Plan, Stats, ZiskExecutionResult, }; use zisk_common::{ChunkId, PayloadType}; -use zisk_hints::HintsStream; use zisk_pil::{ RomRomTrace, ZiskPublicValues, INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, ZISK_AIRGROUP_ID, @@ -80,7 +79,7 @@ enum MinimalTraceExecutionMode { AsmWithCounter, } -pub type StreamHintsShmem = HintsStream>; +pub type StreamHintsShmem = ZiskStream>; /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. @@ -204,7 +203,7 @@ impl ZiskExecutor { let hints_processor = PrecompileHintsProcessor::new(hints_shmem) .expect("Failed to create PrecompileHintsProcessor"); - let hints_stream = Mutex::new(HintsStream::new(hints_processor)); + let hints_stream = Mutex::new(ZiskStream::new(hints_processor)); Self { stdin: Mutex::new(ZiskStdin::null()), diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index 7ecaabaff..3cf6bd355 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -11,7 +11,7 @@ use asm_runner::{ use named_sem::NamedSemaphore; use std::sync::Mutex; use tracing::debug; -use zisk_hints::HintsSink; +use zisk_common::io::StreamSink; /// Names for a service's shared memory and semaphore resources struct ServiceResourceNames { @@ -148,7 +148,7 @@ impl HintsShmem { } } -impl HintsSink for HintsShmem { +impl StreamSink for HintsShmem { /// Writes processed precompile hints to all shared memory writers. /// /// # Arguments diff --git a/hints/src/hints.rs b/hints/src/hints.rs deleted file mode 100644 index b2335a79a..000000000 --- a/hints/src/hints.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::secp256k1_ecdsa_verify; -use ziskos::syscalls::SyscallPoint256; - -use crate::hints_processor::{HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT}; - -pub fn process_hints(hints: Vec) -> Vec { - let mut processed_hints = Vec::new(); - - let mut i = 0; - while i < hints.len() { - let hint = hints[i]; - i += 1; - let hint_type = (hint >> 32) as u32; - let hint_length = (hint & 0xFFFFFFFF) as usize; - if hint_length == 0 { - panic!("process_hints() Invalid hint length: {}", hint_length); - } - assert!(i + hint_length <= hints.len(), "process_hints() Not enough data for RESULT hint"); - match hint_type { - HINTS_TYPE_RESULT => { - // Process result hint: just push the hint data as is - processed_hints.extend_from_slice(&hints[i..i + hint_length]); - } - HINTS_TYPE_ECRECOVER => { - assert!( - hint_length == 8 + 4 + 4 + 4, - "process_hints() Invalid ECRECOVER hint length: {}", - hint_length - ); - let pk: &SyscallPoint256 = unsafe { &*(hints[i] as *const SyscallPoint256) }; - let z: &[u64; 4] = unsafe { &*(hints[i + 8] as *const [u64; 4]) }; - let r: &[u64; 4] = unsafe { &*(hints[i + 8 + 4] as *const [u64; 4]) }; - let s: &[u64; 4] = unsafe { &*(hints[i + 8 + 4 + 4] as *const [u64; 4]) }; - secp256k1_ecdsa_verify(pk, z, r, s, &mut processed_hints); - } - _ => { - panic!("process_hints() Unknown hint type: {}", hint_type); - } - } - i += hint_length; - } - - processed_hints -} diff --git a/hints/src/lib.rs b/hints/src/lib.rs deleted file mode 100644 index 0616c6437..000000000 --- a/hints/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -pub mod hints; -mod hints_processor; -mod hints_stream; - -pub mod secp256k1; - -pub use hints::*; -pub use hints_processor::{ - PrecompileHint, PrecompileHintsProcessor, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT, -}; -pub use hints_stream::HintsStream; -pub use secp256k1::*; - -pub trait HintsProcessor { - /// Process hints and return the processed data along with a flag indicating if CTRL_END was encountered. - /// - /// # Returns - /// A tuple of (processed_hints, has_ctrl_end) where: - /// - processed_hints: Vec - The processed hint data - /// - has_ctrl_end: bool - True if CTRL_END was found (signals end of batch) - fn process_hints(&self, hints: &[u64], first_batch: bool) -> anyhow::Result; -} - -pub trait HintsSink { - fn submit(&self, processed: Vec) -> anyhow::Result<()>; -} diff --git a/hints/Cargo.toml b/precompiles/hints/Cargo.toml similarity index 91% rename from hints/Cargo.toml rename to precompiles/hints/Cargo.toml index b5fe3634b..965405ef6 100644 --- a/hints/Cargo.toml +++ b/precompiles/hints/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "zisk-hints" +name = "precompiles-hints" version = { workspace = true } edition = { workspace = true } license = { workspace = true } @@ -8,7 +8,7 @@ repository = { workspace = true } categories = { workspace = true } [lib] -name = "zisk_hints" +name = "precompiles_hints" path = "src/lib.rs" [dependencies] diff --git a/hints/src/bin/hints_socket_server.rs b/precompiles/hints/src/bin/hints_socket_server.rs similarity index 100% rename from hints/src/bin/hints_socket_server.rs rename to precompiles/hints/src/bin/hints_socket_server.rs diff --git a/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs similarity index 98% rename from hints/src/hints_processor.rs rename to precompiles/hints/src/hints_processor.rs index 54e95c857..03508445e 100644 --- a/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -52,9 +52,10 @@ use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use tracing::debug; +use zisk_common::io::{StreamProcessor, StreamSink}; use ziskos::syscalls::SyscallPoint256; -use crate::{secp256k1_ecdsa_verify, HintsProcessor, HintsSink}; +use crate::secp256k1_ecdsa_verify; /// Control code: Reset processor state and global sequence. const CTRL_START: u32 = 0x00; @@ -183,7 +184,7 @@ impl HintProcessorState { /// This struct provides methods to parse and process a stream of concatenated /// hints, using a dedicated Rayon thread pool for parallel processing while /// preserving the original order of results. -pub struct PrecompileHintsProcessor { +pub struct PrecompileHintsProcessor { /// The thread pool used for parallel hint processing. pool: ThreadPool, @@ -201,7 +202,7 @@ pub struct PrecompileHintsProcessor { drainer_thread: ManuallyDrop>, } -impl PrecompileHintsProcessor { +impl PrecompileHintsProcessor { const DEFAULT_NUM_THREADS: usize = 32; /// Creates a new processor with the default number of threads. @@ -590,7 +591,7 @@ impl PrecompileHintsProcessor { } } -impl Drop for PrecompileHintsProcessor { +impl Drop for PrecompileHintsProcessor { fn drop(&mut self) { // Signal drainer thread to shut down self.state.shutdown.store(true, Ordering::Release); @@ -605,9 +606,9 @@ impl Drop for PrecompileHintsProcessor HintsProcessor for PrecompileHintsProcessor { - fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { - self.process_hints(hints, first_batch) +impl StreamProcessor for PrecompileHintsProcessor { + fn process(&self, data: &[u64], first_batch: bool) -> Result { + self.process_hints(data, first_batch) } } @@ -617,7 +618,7 @@ mod tests { struct NullHints; - impl HintsSink for NullHints { + impl StreamSink for NullHints { fn submit(&self, _processed: Vec) -> Result<()> { Ok(()) } diff --git a/precompiles/hints/src/lib.rs b/precompiles/hints/src/lib.rs new file mode 100644 index 000000000..0053b9d9d --- /dev/null +++ b/precompiles/hints/src/lib.rs @@ -0,0 +1,7 @@ +mod hints_processor; +mod secp256k1; + +pub use hints_processor::{ + PrecompileHint, PrecompileHintsProcessor, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT, +}; +pub use secp256k1::*; diff --git a/hints/src/secp256k1/curve.rs b/precompiles/hints/src/secp256k1/curve.rs similarity index 100% rename from hints/src/secp256k1/curve.rs rename to precompiles/hints/src/secp256k1/curve.rs diff --git a/hints/src/secp256k1/mod.rs b/precompiles/hints/src/secp256k1/mod.rs similarity index 100% rename from hints/src/secp256k1/mod.rs rename to precompiles/hints/src/secp256k1/mod.rs diff --git a/hints/src/secp256k1/scalar.rs b/precompiles/hints/src/secp256k1/scalar.rs similarity index 100% rename from hints/src/secp256k1/scalar.rs rename to precompiles/hints/src/secp256k1/scalar.rs From 82b2e51483f48262bd062a01ff4540efd3459cfc Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 19 Dec 2025 08:33:25 +0100 Subject: [PATCH 107/782] Avoid warning in emu.c debug compilation --- emulator-asm/src/emu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index 5e728b913..30227b380 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -1775,7 +1775,7 @@ extern uint64_t _opcode_add256(uint64_t * address) if (emu_verbose) printf("opcode_add256() called Add256()\n"); if (emu_verbose) { - printf("cout = %u\n", cout); + printf("cout = %lu\n", cout); printf("c = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", c[3], c[2], c[1], c[0], c[3], c[2], c[1], c[0]); } #endif From 43fe03089e417f0a5a0b38f36e83c6a78634c3f8 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 19 Dec 2025 08:56:11 +0000 Subject: [PATCH 108/782] change CLI input and precompile hints argument names --- cli/src/commands/execute.rs | 22 ++++++++++++++-------- cli/src/commands/prove.rs | 21 +++++++++++++-------- cli/src/commands/rom_setup.rs | 2 +- cli/src/commands/stats.rs | 22 ++++++++++++++-------- cli/src/commands/verify_constraints.rs | 21 +++++++++++++-------- 5 files changed, 55 insertions(+), 33 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 837461b02..f2bc167b5 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -1,5 +1,6 @@ use anyhow::Result; use clap::Parser; +use colored::Colorize; use std::path::PathBuf; use tracing::{info, warn}; use zisk_build::ZISK_VERSION_MESSAGE; @@ -41,12 +42,12 @@ pub struct ZiskExecute { pub emulator: bool, /// Input path - #[clap(short = 'i', long)] - pub input: Option, + #[clap(short = 'i', long, alias = "input")] + pub inputs: Option, /// Precompiles Hints path #[clap(short = 'h', long)] - pub precompile_hints_path: Option, + pub precompile_hints: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -80,19 +81,24 @@ impl ZiskExecute { pub fn run(&mut self) -> Result<()> { cli_fail_if_gpu_mode()?; + // Check if the deprecated alias was used + if std::env::args().any(|arg| arg == "--input") { + eprintln!("{}", "Warning: --input is deprecated, use --inputs instead".yellow().bold()); + } + print_banner(); - if let Some(input) = &self.input { - print_banner_field("Input", input); + if let Some(inputs) = &self.inputs { + print_banner_field("Input", inputs); } - if let Some(hints) = &self.precompile_hints_path { + if let Some(hints) = &self.precompile_hints { print_banner_field("Prec. Hints", hints); } - let stdin = ZiskStdin::from_uri(self.input.as_ref())?; + let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; + let hints_stream = StreamSource::from_uri(self.precompile_hints.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index e389e91aa..c307c9d8f 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -42,12 +42,12 @@ pub struct ZiskProve { pub emulator: bool, /// Input path - #[clap(short = 'i', long)] - pub input: Option, + #[clap(short = 'i', long, alias = "input")] + pub inputs: Option, /// Precompiles Hints path #[clap(short = 'h', long)] - pub precompile_hints_path: Option, + pub precompile_hints: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -113,13 +113,18 @@ pub struct ZiskProve { impl ZiskProve { pub fn run(&mut self) -> Result<()> { + // Check if the deprecated alias was used + if std::env::args().any(|arg| arg == "--input") { + eprintln!("{}", "Warning: --input is deprecated, use --inputs instead".yellow().bold()); + } + print_banner(); - if let Some(input) = &self.input { - print_banner_field("Input", input); + if let Some(inputs) = &self.inputs { + print_banner_field("Input", inputs); } - if let Some(hints) = &self.precompile_hints_path { + if let Some(hints) = &self.precompile_hints { print_banner_field("Prec. Hints", hints); } @@ -135,9 +140,9 @@ impl ZiskProve { gpu_params.with_max_witness_stored(self.max_witness_stored.unwrap()); } - let stdin = ZiskStdin::from_uri(self.input.as_ref())?; + let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; + let hints_stream = StreamSource::from_uri(self.precompile_hints.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index 4eaeca666..a789e5684 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -31,7 +31,7 @@ pub struct ZiskRomSetup { pub output_dir: Option, /// Enable precompile hints in assembly generation - #[clap(short = 'p', long, default_value_t = true)] + #[clap(short = 'h', long, default_value_t = true)] pub precompile_hints: bool, #[clap(short = 'v', long, default_value_t = false)] diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index ed72ad500..6c0f541da 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -1,5 +1,6 @@ use anyhow::Result; use clap::Parser; +use colored::Colorize; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs, path::PathBuf, time::Instant}; use tracing::warn; @@ -41,12 +42,12 @@ pub struct ZiskStats { pub emulator: bool, /// Input path - #[clap(short = 'i', long)] - pub input: Option, + #[clap(short = 'i', long, alias = "input")] + pub inputs: Option, /// Precompiles Hints path #[clap(short = 'h', long)] - pub precompile_hints_path: Option, + pub precompile_hints: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -94,19 +95,24 @@ pub struct ZiskStats { impl ZiskStats { pub fn run(&mut self) -> Result<()> { + // Check if the deprecated alias was used + if std::env::args().any(|arg| arg == "--input") { + eprintln!("{}", "Warning: --input is deprecated, use --inputs instead".yellow().bold()); + } + print_banner(); - if let Some(input) = &self.input { - print_banner_field("Input", input); + if let Some(inputs) = &self.inputs { + print_banner_field("Input", inputs); } - if let Some(hints) = &self.precompile_hints_path { + if let Some(hints) = &self.precompile_hints { print_banner_field("Prec. Hints", hints); } - let stdin = ZiskStdin::from_uri(self.input.as_ref())?; + let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; + let hints_stream = StreamSource::from_uri(self.precompile_hints.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 90b6326bd..5c177070a 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -44,12 +44,12 @@ pub struct ZiskVerifyConstraints { pub emulator: bool, /// Input path - #[clap(short = 'i', long)] - pub input: Option, + #[clap(short = 'i', long, alias = "input")] + pub inputs: Option, /// Precompiles Hints path #[clap(short = 'h', long)] - pub precompile_hints_path: Option, + pub precompile_hints: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -86,19 +86,24 @@ impl ZiskVerifyConstraints { pub fn run(&mut self) -> Result<()> { cli_fail_if_gpu_mode()?; + // Check if the deprecated alias was used + if std::env::args().any(|arg| arg == "--input") { + eprintln!("{}", "Warning: --input is deprecated, use --inputs instead".yellow().bold()); + } + print_banner(); - if let Some(input) = &self.input { - print_banner_field("Input", input); + if let Some(inputs) = &self.inputs { + print_banner_field("Input", inputs); } - if let Some(hints) = &self.precompile_hints_path { + if let Some(hints) = &self.precompile_hints { print_banner_field("Prec. Hints", hints); } - let stdin = ZiskStdin::from_uri(self.input.as_ref())?; + let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.precompile_hints_path.as_deref())?; + let hints_stream = StreamSource::from_uri(self.precompile_hints.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { From 6e668caa1a68582910d3a9134cbbee03c548996f Mon Sep 17 00:00:00 2001 From: agnusmor Date: Fri, 19 Dec 2025 20:03:00 +0100 Subject: [PATCH 109/782] Bump version to 0.16.0 and update dependencies for pil2-proofman --- Cargo.lock | 118 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 20 ++++----- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26bf28db4..f67dc3b93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,7 +292,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "asm-runner" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "clap", @@ -578,7 +578,7 @@ dependencies = [ [[package]] name = "cargo-zisk" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "asm-runner", @@ -712,7 +712,7 @@ dependencies = [ [[package]] name = "circuit" -version = "0.15.0" +version = "0.16.0" [[package]] name = "clang-sys" @@ -1039,8 +1039,8 @@ dependencies = [ [[package]] name = "curves" -version = "0.15.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.15.0#284f9b4eacdc58b0f65cec180de9d8a09466ad80" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" dependencies = [ "fields", "num-bigint", @@ -1070,7 +1070,7 @@ dependencies = [ [[package]] name = "data-bus" -version = "0.15.0" +version = "0.16.0" dependencies = [ "zisk-common", "zisk-core", @@ -1330,7 +1330,7 @@ dependencies = [ [[package]] name = "executor" -version = "0.15.0" +version = "0.16.0" dependencies = [ "asm-runner", "crossbeam", @@ -1382,8 +1382,8 @@ dependencies = [ [[package]] name = "fields" -version = "0.15.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.15.0#284f9b4eacdc58b0f65cec180de9d8a09466ad80" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" dependencies = [ "num-bigint", "paste", @@ -2160,11 +2160,11 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lib-c" -version = "0.15.0" +version = "0.16.0" [[package]] name = "lib-float" -version = "0.15.0" +version = "0.16.0" [[package]] name = "libc" @@ -2285,7 +2285,7 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "mem-common" -version = "0.15.0" +version = "0.16.0" dependencies = [ "clap", "fields", @@ -2307,7 +2307,7 @@ dependencies = [ [[package]] name = "mem-planner-cpp" -version = "0.15.0" +version = "0.16.0" dependencies = [ "mem-common", "proofman-common", @@ -2700,8 +2700,8 @@ dependencies = [ [[package]] name = "pil-std-lib" -version = "0.15.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.15.0#284f9b4eacdc58b0f65cec180de9d8a09466ad80" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" dependencies = [ "colored", "fields", @@ -2855,7 +2855,7 @@ dependencies = [ [[package]] name = "precomp-arith-eq" -version = "0.15.0" +version = "0.16.0" dependencies = [ "ark-bn254", "ark-ff", @@ -2893,7 +2893,7 @@ dependencies = [ [[package]] name = "precomp-arith-eq-384" -version = "0.15.0" +version = "0.16.0" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -2932,7 +2932,7 @@ dependencies = [ [[package]] name = "precomp-big-int" -version = "0.15.0" +version = "0.16.0" dependencies = [ "fields", "generic-array", @@ -2954,7 +2954,7 @@ dependencies = [ [[package]] name = "precomp-keccakf" -version = "0.15.0" +version = "0.16.0" dependencies = [ "circuit", "fields", @@ -2976,7 +2976,7 @@ dependencies = [ [[package]] name = "precomp-sha256f" -version = "0.15.0" +version = "0.16.0" dependencies = [ "fields", "mem-common", @@ -2997,7 +2997,7 @@ dependencies = [ [[package]] name = "precompiles-common" -version = "0.15.0" +version = "0.16.0" dependencies = [ "fields", "zisk-common", @@ -3006,7 +3006,7 @@ dependencies = [ [[package]] name = "precompiles-helpers" -version = "0.15.0" +version = "0.16.0" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -3050,8 +3050,8 @@ dependencies = [ [[package]] name = "proofman" -version = "0.15.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.15.0#284f9b4eacdc58b0f65cec180de9d8a09466ad80" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" dependencies = [ "blake3", "borsh", @@ -3085,8 +3085,8 @@ dependencies = [ [[package]] name = "proofman-common" -version = "0.15.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.15.0#284f9b4eacdc58b0f65cec180de9d8a09466ad80" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" dependencies = [ "borsh", "colored", @@ -3116,8 +3116,8 @@ dependencies = [ [[package]] name = "proofman-hints" -version = "0.15.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.15.0#284f9b4eacdc58b0f65cec180de9d8a09466ad80" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" dependencies = [ "fields", "proofman-common", @@ -3128,8 +3128,8 @@ dependencies = [ [[package]] name = "proofman-macros" -version = "0.15.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.15.0#284f9b4eacdc58b0f65cec180de9d8a09466ad80" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" dependencies = [ "proc-macro2", "quote", @@ -3139,8 +3139,8 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" -version = "0.15.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.15.0#284f9b4eacdc58b0f65cec180de9d8a09466ad80" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" dependencies = [ "crossbeam-channel", "tracing", @@ -3148,8 +3148,8 @@ dependencies = [ [[package]] name = "proofman-util" -version = "0.15.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.15.0#284f9b4eacdc58b0f65cec180de9d8a09466ad80" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" dependencies = [ "colored", "fields", @@ -3159,8 +3159,8 @@ dependencies = [ [[package]] name = "proofman-verifier" -version = "0.15.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.15.0#284f9b4eacdc58b0f65cec180de9d8a09466ad80" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" dependencies = [ "bytemuck", "fields", @@ -3525,14 +3525,14 @@ dependencies = [ [[package]] name = "riscv" -version = "0.15.0" +version = "0.16.0" dependencies = [ "elf", ] [[package]] name = "rom-setup" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "blake3", @@ -3806,7 +3806,7 @@ dependencies = [ [[package]] name = "server" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "asm-runner", @@ -3917,7 +3917,7 @@ checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "sm-arith" -version = "0.15.0" +version = "0.16.0" dependencies = [ "fields", "num-bigint", @@ -3938,7 +3938,7 @@ dependencies = [ [[package]] name = "sm-binary" -version = "0.15.0" +version = "0.16.0" dependencies = [ "fields", "num-bigint", @@ -3958,7 +3958,7 @@ dependencies = [ [[package]] name = "sm-frequent-ops" -version = "0.15.0" +version = "0.16.0" dependencies = [ "clap", "fields", @@ -3978,7 +3978,7 @@ dependencies = [ [[package]] name = "sm-main" -version = "0.15.0" +version = "0.16.0" dependencies = [ "asm-runner", "fields", @@ -4000,7 +4000,7 @@ dependencies = [ [[package]] name = "sm-mem" -version = "0.15.0" +version = "0.16.0" dependencies = [ "fields", "mem-common", @@ -4022,7 +4022,7 @@ dependencies = [ [[package]] name = "sm-rom" -version = "0.15.0" +version = "0.16.0" dependencies = [ "asm-runner", "fields", @@ -5409,8 +5409,8 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "witness" -version = "0.15.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.15.0#284f9b4eacdc58b0f65cec180de9d8a09466ad80" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" dependencies = [ "colored", "fields", @@ -5563,7 +5563,7 @@ dependencies = [ [[package]] name = "zisk-build" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "cargo_metadata", @@ -5573,7 +5573,7 @@ dependencies = [ [[package]] name = "zisk-common" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "bytemuck", @@ -5594,7 +5594,7 @@ dependencies = [ [[package]] name = "zisk-core" -version = "0.15.0" +version = "0.16.0" dependencies = [ "elf", "fields", @@ -5632,7 +5632,7 @@ dependencies = [ [[package]] name = "zisk-distributed-coordinator" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "async-stream", @@ -5665,7 +5665,7 @@ dependencies = [ [[package]] name = "zisk-distributed-grpc-api" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "chrono", @@ -5683,7 +5683,7 @@ dependencies = [ [[package]] name = "zisk-distributed-worker" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "asm-runner", @@ -5716,7 +5716,7 @@ dependencies = [ [[package]] name = "zisk-pil" -version = "0.15.0" +version = "0.16.0" dependencies = [ "fields", "proofman", @@ -5729,7 +5729,7 @@ dependencies = [ [[package]] name = "zisk-sdk" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "asm-runner", @@ -5749,7 +5749,7 @@ dependencies = [ [[package]] name = "zisk-witness" -version = "0.15.0" +version = "0.16.0" dependencies = [ "data-bus", "env_logger", @@ -5783,7 +5783,7 @@ dependencies = [ [[package]] name = "ziskclib" -version = "0.15.0" +version = "0.16.0" dependencies = [ "sha2", "tiny-keccak", @@ -5791,7 +5791,7 @@ dependencies = [ [[package]] name = "ziskemu" -version = "0.15.0" +version = "0.16.0" dependencies = [ "clap", "criterion", @@ -5819,7 +5819,7 @@ dependencies = [ [[package]] name = "ziskos" -version = "0.15.0" +version = "0.16.0" dependencies = [ "bincode", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 158b85e51..1eb9df8c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.15.0" +version = "0.16.0" edition = "2021" license = "Apache-2.0 or MIT" keywords = ["zisk", "zkvm", "zero-knowledge"] @@ -7,7 +7,7 @@ repository = "https://github.com/0xPolygonHermez/zisk" categories = ["cryptography"] [workspace.metadata] -gha_pil2_proofman_js_branch = "pre-develop-0.15.0" +gha_pil2_proofman_js_branch = "pre-develop-0.16.0" gha_pil2_compiler_branch = "tags/v0.8.0" [workspace] @@ -103,14 +103,14 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.15.0" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.15.0" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.15.0" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.15.0" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.15.0" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.15.0" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.15.0" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.15.0" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } # Proofman Local development # proofman = { path = "../pil2-proofman/proofman" } # proofman-common = { path = "../pil2-proofman/common" } From 71c3876a2a181638c2681ca44fbd3aa47ef3afb4 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 22 Dec 2025 08:05:15 +0000 Subject: [PATCH 110/782] increase GRPC max message size to 128 MB --- distributed/crates/grpc-api/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distributed/crates/grpc-api/src/lib.rs b/distributed/crates/grpc-api/src/lib.rs index 36a8ad0fb..8d0c39e03 100644 --- a/distributed/crates/grpc-api/src/lib.rs +++ b/distributed/crates/grpc-api/src/lib.rs @@ -7,4 +7,4 @@ pub mod conversions; pub use distributed_api_proto::zisk_distributed_api_server; pub use distributed_api_proto::*; -pub const MAX_MESSAGE_SIZE: usize = 32 * 1024 * 1024; // 32 MB +pub const MAX_MESSAGE_SIZE: usize = 128 * 1024 * 1024; // 128 MB From 1e2cb2104e8195f7758eae2e5ae0987bd3a2ff6a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 22 Dec 2025 08:26:59 +0000 Subject: [PATCH 111/782] rename input_mode to inputs_mode --- distributed/crates/common/src/dto.rs | 2 +- distributed/crates/common/src/types.rs | 6 +++--- .../crates/coordinator/src/cli/handler_prove.rs | 4 ++-- distributed/crates/coordinator/src/coordinator.rs | 12 ++++++------ .../crates/grpc-api/proto/zisk_distributed_api.proto | 2 +- distributed/crates/grpc-api/src/conversions.rs | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 8856b3e92..63c6a0a5d 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -95,7 +95,7 @@ impl Display for InputModeDto { pub struct LaunchProofRequestDto { pub data_id: DataId, pub compute_capacity: u32, - pub input_mode: InputModeDto, + pub inputs_mode: InputModeDto, pub simulated_node: Option, } diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index fba1bfd56..18a43fb9c 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -243,7 +243,7 @@ pub struct Job { pub duration_ms: Option, pub state: JobState, pub data_id: DataId, - pub input_mode: InputModeDto, + pub inputs_mode: InputModeDto, pub compute_capacity: ComputeCapacity, pub workers: Vec, pub agg_worker_id: Option, @@ -259,7 +259,7 @@ pub struct Job { impl Job { pub fn new( data_id: DataId, - input_mode: InputModeDto, + inputs_mode: InputModeDto, compute_capacity: ComputeCapacity, selected_workers: Vec, partitions: Vec>, @@ -271,7 +271,7 @@ impl Job { duration_ms: None, state: JobState::Created, data_id, - input_mode, + inputs_mode, compute_capacity, workers: selected_workers, agg_worker_id: None, diff --git a/distributed/crates/coordinator/src/cli/handler_prove.rs b/distributed/crates/coordinator/src/cli/handler_prove.rs index f19e75690..44a256435 100644 --- a/distributed/crates/coordinator/src/cli/handler_prove.rs +++ b/distributed/crates/coordinator/src/cli/handler_prove.rs @@ -29,7 +29,7 @@ pub async fn handle( let channel = Channel::from_shared(coordinator_url)?.connect().await?; let mut client = ZiskDistributedApiClient::new(channel); - let input_mode = if inputs_uri.is_some() { + let inputs_mode = if inputs_uri.is_some() { if direct_inputs { InputMode::Data } else { @@ -51,7 +51,7 @@ pub async fn handle( let launch_proof_request = LaunchProofRequest { data_id, compute_capacity, - input_mode: input_mode.into(), + inputs_mode: inputs_mode.into(), inputs_uri, hints_uri, simulated_node, diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index fabe128a1..22497ec15 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -334,7 +334,7 @@ impl Coordinator { .create_job( request.data_id.clone(), required_compute_capacity, - request.input_mode, + request.inputs_mode, request.simulated_node, ) .await?; @@ -342,7 +342,7 @@ impl Coordinator { info!( "[Job] Started {} successfully Inputs: {} Capacity: {} Workers: {}", job.job_id, - job.input_mode, + job.inputs_mode, job.compute_capacity, job.workers.len(), ); @@ -527,7 +527,7 @@ impl Coordinator { &self, data_id: DataId, required_compute_capacity: ComputeCapacity, - input_mode: InputModeDto, + inputs_mode: InputModeDto, simulated_node: Option, ) -> CoordinatorResult { let execution_mode = if let Some(node) = simulated_node { @@ -547,7 +547,7 @@ impl Coordinator { Ok(Job::new( data_id, - input_mode, + inputs_mode, required_compute_capacity, selected_workers, partitions, @@ -604,7 +604,7 @@ impl Coordinator { job: &Job, active_workers: &[WorkerId], ) -> CoordinatorResult<()> { - let input_source = match job.input_mode { + let input_source = match job.inputs_mode { InputModeDto::InputModePath(ref inputs_uri, ref hints_uri) => { InputSourceDto::InputPath(inputs_uri.clone(), hints_uri.clone()) } @@ -1803,7 +1803,7 @@ impl Coordinator { phase2_duration.as_seconds_f32(), phase3_duration.as_seconds_f32(), steps_str, - job.input_mode, + job.inputs_mode, job.compute_capacity, ); diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index 7508d6547..340dc0aac 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -55,7 +55,7 @@ message WorkersListRequest { message LaunchProofRequest { string data_id = 1; uint32 compute_capacity = 2; - InputMode input_mode = 3; + InputMode inputs_mode = 3; optional string inputs_uri = 4; optional string hints_uri = 5; optional uint32 simulated_node = 6; // If set, indicates this is a simulated worker diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index 140a0be8a..e44caacc6 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -153,7 +153,7 @@ impl From for SystemStatusResponse { impl From for LaunchProofRequest { fn from(dto: LaunchProofRequestDto) -> Self { - let (input_mode, inputs_uri, hints_uri) = match dto.input_mode { + let (inputs_mode, inputs_uri, hints_uri) = match dto.inputs_mode { InputModeDto::InputModeNone => (InputMode::None, None, None), InputModeDto::InputModePath(inputs, hints) => { (InputMode::Path, Some(inputs), Some(hints)) @@ -166,7 +166,7 @@ impl From for LaunchProofRequest { LaunchProofRequest { data_id: dto.data_id.into(), compute_capacity: dto.compute_capacity, - input_mode: input_mode.into(), + inputs_mode: inputs_mode.into(), inputs_uri, hints_uri, simulated_node: dto.simulated_node, @@ -183,7 +183,7 @@ impl TryFrom for LaunchProofRequestDto { Ok(LaunchProofRequestDto { data_id: req.data_id.into(), compute_capacity: req.compute_capacity, - input_mode: match InputMode::try_from(req.input_mode).unwrap_or(InputMode::None) { + inputs_mode: match InputMode::try_from(req.inputs_mode).unwrap_or(InputMode::None) { InputMode::None => InputModeDto::InputModeNone, InputMode::Path => { let inputs_uri = req.inputs_uri.ok_or_else(|| { From 53c04a76f31a4b2eb402849dc1d181d695340244 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:34:58 +0000 Subject: [PATCH 112/782] adapt proto and code to allow sent hints when using distributed --- common/src/io/stream/memory.rs | 52 +++++++++++++++ common/src/io/stream/mod.rs | 6 +- common/src/io/stream/stream_reader.rs | 12 +++- distributed/crates/common/src/dto.rs | 16 +++-- distributed/crates/common/src/types.rs | 4 ++ .../coordinator/src/cli/handler_prove.rs | 13 +++- .../crates/coordinator/src/cli/main.rs | 12 ++-- .../crates/coordinator/src/coordinator.rs | 27 +++++++- .../grpc-api/proto/zisk_distributed_api.proto | 26 ++++---- .../crates/grpc-api/src/conversions.rs | 64 ++++++++++++------- distributed/crates/worker/src/worker.rs | 24 +++++-- distributed/crates/worker/src/worker_node.rs | 31 +++++---- 12 files changed, 216 insertions(+), 71 deletions(-) create mode 100644 common/src/io/stream/memory.rs diff --git a/common/src/io/stream/memory.rs b/common/src/io/stream/memory.rs new file mode 100644 index 000000000..0de94baad --- /dev/null +++ b/common/src/io/stream/memory.rs @@ -0,0 +1,52 @@ +use std::io::{Cursor, Read}; + +use crate::io::stream::StreamRead; + +/// A memory-based implementation of StreamSource that reads from in-memory data. +pub struct MemoryStreamReader { + data: Vec, + cursor: Cursor>, +} + +impl MemoryStreamReader { + /// Create a new MemoryStreamReader from a vector of bytes. + pub fn new(data: Vec) -> Self { + let cursor = Cursor::new(data.clone()); + MemoryStreamReader { data, cursor } + } + + /// Create a new MemoryStreamReader from a string (UTF-8 encoded). + pub fn from_string(data: String) -> Self { + Self::new(data.into_bytes()) + } + + /// Create a new MemoryStreamReader from a slice of bytes. + pub fn from_slice(data: &[u8]) -> Self { + Self::new(data.to_vec()) + } +} + +impl StreamRead for MemoryStreamReader { + fn open(&mut self) -> anyhow::Result<()> { + self.cursor.set_position(0); + Ok(()) + } + + fn next(&mut self) -> anyhow::Result>> { + let mut buffer = Vec::new(); + let bytes_read = self.cursor.read_to_end(&mut buffer)?; + if bytes_read == 0 { + Ok(None) + } else { + Ok(Some(buffer)) + } + } + + fn close(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn is_active(&self) -> bool { + self.cursor.position() < self.data.len() as u64 + } +} diff --git a/common/src/io/stream/mod.rs b/common/src/io/stream/mod.rs index 08b3d3854..e605f5b58 100644 --- a/common/src/io/stream/mod.rs +++ b/common/src/io/stream/mod.rs @@ -1,19 +1,21 @@ mod file; +mod memory; mod null; mod quic; +mod stream; mod stream_reader; mod stream_writer; -mod stream; #[cfg(unix)] mod unix_socket; pub use file::{FileStreamReader, FileStreamWriter}; +pub use memory::MemoryStreamReader; pub use null::NullStreamReader; pub use quic::{QuicStreamReader, QuicStreamWriter}; +pub use stream::*; pub use stream_reader::*; pub use stream_writer::*; -pub use stream::*; #[cfg(unix)] pub use unix_socket::{UnixSocketStreamReader, UnixSocketStreamWriter}; diff --git a/common/src/io/stream/stream_reader.rs b/common/src/io/stream/stream_reader.rs index f168bd676..6b764f14b 100644 --- a/common/src/io/stream/stream_reader.rs +++ b/common/src/io/stream/stream_reader.rs @@ -1,4 +1,4 @@ -use crate::io::{QuicStreamReader, UnixSocketStreamReader}; +use crate::io::{MemoryStreamReader, QuicStreamReader, UnixSocketStreamReader}; use super::{FileStreamReader, NullStreamReader}; @@ -25,6 +25,7 @@ pub enum StreamSource { Null(NullStreamReader), UnixSocket(UnixSocketStreamReader), Quic(QuicStreamReader), + Memory(MemoryStreamReader), } impl StreamSource { @@ -38,6 +39,11 @@ impl StreamSource { Ok(StreamSource::File(FileStreamReader::new(path)?)) } + /// Create a memory-based stdin + pub fn from_vec(data: Vec) -> Self { + StreamSource::Memory(MemoryStreamReader::new(data)) + } + /// Create a Unix socket-based stdin pub fn from_unix_socket>(path: P) -> Result { Ok(StreamSource::UnixSocket(UnixSocketStreamReader::new(path.as_ref())?)) @@ -93,6 +99,7 @@ impl StreamRead for StreamSource { StreamSource::Null(null_stream) => null_stream.open(), StreamSource::UnixSocket(unix_stream) => unix_stream.open(), StreamSource::Quic(quic_stream) => quic_stream.open(), + StreamSource::Memory(memory_stream) => memory_stream.open(), } } @@ -103,6 +110,7 @@ impl StreamRead for StreamSource { StreamSource::Null(null_stream) => null_stream.next(), StreamSource::UnixSocket(unix_stream) => unix_stream.next(), StreamSource::Quic(quic_stream) => quic_stream.next(), + StreamSource::Memory(memory_stream) => memory_stream.next(), } } @@ -113,6 +121,7 @@ impl StreamRead for StreamSource { StreamSource::Null(null_stream) => null_stream.close(), StreamSource::UnixSocket(unix_stream) => unix_stream.close(), StreamSource::Quic(quic_stream) => quic_stream.close(), + StreamSource::Memory(memory_stream) => memory_stream.close(), } } @@ -123,6 +132,7 @@ impl StreamRead for StreamSource { StreamSource::Null(null_stream) => null_stream.is_active(), StreamSource::UnixSocket(unix_stream) => unix_stream.is_active(), StreamSource::Quic(quic_stream) => quic_stream.is_active(), + StreamSource::Memory(memory_stream) => memory_stream.is_active(), } } } diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 63c6a0a5d..ecf41d17e 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -72,21 +72,21 @@ pub enum InputModeDto { InputModeNone = 0, // Input will be provided as a path. First String is the inputs path, // second String is the precompiles hints path - InputModePath(String, String) = 1, + InputModeUri(String) = 1, // Input will be provided as a path. First String is the inputs path URI, // second String is the precompiles hints URI - InputModeData(String, String) = 2, + InputModeData(String) = 2, } impl Display for InputModeDto { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { InputModeDto::InputModeNone => write!(f, "None"), - InputModeDto::InputModePath(inputs, hints) => { - write!(f, "Path(inputs: {}, hints: {})", inputs, hints) + InputModeDto::InputModeUri(inputs) => { + write!(f, "Path({})", inputs) } - InputModeDto::InputModeData(inputs, hints) => { - write!(f, "Data(inputs: {}, hints: {})", inputs, hints) + InputModeDto::InputModeData(inputs) => { + write!(f, "Data( {})", inputs) } } } @@ -96,6 +96,7 @@ pub struct LaunchProofRequestDto { pub data_id: DataId, pub compute_capacity: u32, pub inputs_mode: InputModeDto, + pub hints_mode: InputModeDto, pub simulated_node: Option, } @@ -161,6 +162,7 @@ pub enum ExecuteTaskRequestTypeDto { pub struct ContributionParamsDto { pub data_id: DataId, pub input_source: InputSourceDto, + pub hints_source: InputSourceDto, pub rank_id: u32, pub total_workers: u32, pub worker_allocation: Vec, @@ -169,7 +171,7 @@ pub struct ContributionParamsDto { #[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] pub enum InputSourceDto { - InputPath(String, String), // (inputs_path, hints_path) + InputPath(String), InputData(Vec), InputNull, } diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index 18a43fb9c..b06beee5d 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -244,6 +244,7 @@ pub struct Job { pub state: JobState, pub data_id: DataId, pub inputs_mode: InputModeDto, + pub hints_mode: InputModeDto, pub compute_capacity: ComputeCapacity, pub workers: Vec, pub agg_worker_id: Option, @@ -260,6 +261,7 @@ impl Job { pub fn new( data_id: DataId, inputs_mode: InputModeDto, + hints_mode: InputModeDto, compute_capacity: ComputeCapacity, selected_workers: Vec, partitions: Vec>, @@ -272,6 +274,7 @@ impl Job { state: JobState::Created, data_id, inputs_mode, + hints_mode, compute_capacity, workers: selected_workers, agg_worker_id: None, @@ -395,6 +398,7 @@ pub struct JobResult { pub struct DataCtx { pub data_id: DataId, pub input_source: InputSourceDto, + pub hints_source: InputSourceDto, } #[repr(u8)] diff --git a/distributed/crates/coordinator/src/cli/handler_prove.rs b/distributed/crates/coordinator/src/cli/handler_prove.rs index 44a256435..e17bb98a2 100644 --- a/distributed/crates/coordinator/src/cli/handler_prove.rs +++ b/distributed/crates/coordinator/src/cli/handler_prove.rs @@ -33,7 +33,17 @@ pub async fn handle( if direct_inputs { InputMode::Data } else { - InputMode::Path + InputMode::Uri + } + } else { + InputMode::None + }; + + let hints_mode = if hints_uri.is_some() { + if direct_inputs { + InputMode::Data + } else { + InputMode::Uri } } else { InputMode::None @@ -53,6 +63,7 @@ pub async fn handle( compute_capacity, inputs_mode: inputs_mode.into(), inputs_uri, + hints_mode: hints_mode.into(), hints_uri, simulated_node, }; diff --git a/distributed/crates/coordinator/src/cli/main.rs b/distributed/crates/coordinator/src/cli/main.rs index 9cc7353a0..b5c1f62c7 100644 --- a/distributed/crates/coordinator/src/cli/main.rs +++ b/distributed/crates/coordinator/src/cli/main.rs @@ -67,11 +67,11 @@ enum ZiskCoordinatorCommands { /// Path to the input file #[arg(long, help = "Path to the input file for proof generation")] - input: Option, + inputs_uri: Option, /// Precompiles Hints path #[arg(long, help = "Path to the precompiles hints file for proof generation")] - precompile_hints_path: Option, + hints_uri: Option, /// Whether to send the input data directly #[clap(short = 'x', long, default_value_t = false)] @@ -95,8 +95,8 @@ async fn main() -> Result<()> { Some(ZiskCoordinatorCommands::Prove { coordinator_url, data_id, - input, - precompile_hints_path, + inputs_uri, + hints_uri, direct_inputs, compute_capacity, simulated_node, @@ -105,8 +105,8 @@ async fn main() -> Result<()> { handler_prove::handle( coordinator_url, data_id, - input, - precompile_hints_path, + inputs_uri, + hints_uri, direct_inputs, compute_capacity, simulated_node, diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 22497ec15..4874e464a 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -335,6 +335,7 @@ impl Coordinator { request.data_id.clone(), required_compute_capacity, request.inputs_mode, + request.hints_mode, request.simulated_node, ) .await?; @@ -528,6 +529,7 @@ impl Coordinator { data_id: DataId, required_compute_capacity: ComputeCapacity, inputs_mode: InputModeDto, + hints_mode: InputModeDto, simulated_node: Option, ) -> CoordinatorResult { let execution_mode = if let Some(node) = simulated_node { @@ -548,6 +550,7 @@ impl Coordinator { Ok(Job::new( data_id, inputs_mode, + hints_mode, required_compute_capacity, selected_workers, partitions, @@ -605,10 +608,10 @@ impl Coordinator { active_workers: &[WorkerId], ) -> CoordinatorResult<()> { let input_source = match job.inputs_mode { - InputModeDto::InputModePath(ref inputs_uri, ref hints_uri) => { - InputSourceDto::InputPath(inputs_uri.clone(), hints_uri.clone()) + InputModeDto::InputModeUri(ref inputs_uri) => { + InputSourceDto::InputPath(inputs_uri.clone()) } - InputModeDto::InputModeData(ref inputs_uri, ref _hints_uri) => { + InputModeDto::InputModeData(ref inputs_uri) => { let inputs = tokio::fs::read(inputs_uri).await.map_err(|e| { CoordinatorError::Internal(format!( "Failed to read input data for job {}: {}", @@ -620,6 +623,22 @@ impl Coordinator { InputModeDto::InputModeNone => InputSourceDto::InputNull, }; + let hints_source = match &job.hints_mode { + InputModeDto::InputModeUri(ref hints_uri) => { + InputSourceDto::InputPath(hints_uri.clone()) + } + InputModeDto::InputModeData(ref hints_uri) => { + let hints = tokio::fs::read(hints_uri).await.map_err(|e| { + CoordinatorError::Internal(format!( + "Failed to read hints data for job {}: {}", + job.job_id, e + )) + })?; + InputSourceDto::InputData(hints) + } + InputModeDto::InputModeNone => InputSourceDto::InputNull, + }; + // Use Arc to avoid expensive clones let active_workers = active_workers.to_vec(); let total_workers = active_workers.len() as u32; @@ -630,6 +649,7 @@ impl Coordinator { let job_id = job.job_id.clone(); let data_id = job.data_id.clone(); let input_source = input_source.clone(); + let hints_source = hints_source.clone(); let worker_allocation = job.partitions[rank_id].clone(); let job_compute_capacity = job.compute_capacity; let workers_pool = &self.workers_pool; @@ -641,6 +661,7 @@ impl Coordinator { params: ExecuteTaskRequestTypeDto::ContributionParams(ContributionParamsDto { data_id, input_source, + hints_source, rank_id: rank_id as u32, total_workers, worker_allocation, diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index 340dc0aac..e29808bff 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -57,13 +57,14 @@ message LaunchProofRequest { uint32 compute_capacity = 2; InputMode inputs_mode = 3; optional string inputs_uri = 4; - optional string hints_uri = 5; - optional uint32 simulated_node = 6; // If set, indicates this is a simulated worker + InputMode hints_mode = 5; + optional string hints_uri = 6; + optional uint32 simulated_node = 7; // If set, indicates this is a simulated worker } enum InputMode { INPUT_MODE_NONE = 0; // No input provided - INPUT_MODE_PATH = 1; // Input will be provided as a path + INPUT_MODE_URI = 1; // Input will be provided as an URI INPUT_MODE_DATA = 2; // Input data will be sent directly } @@ -232,18 +233,17 @@ enum TaskType { message ContributionParams { string data_id = 1; oneof input_source { - InputUris input_path = 2; + string input_path = 2; bytes input_data = 3; } - uint32 rank_id = 4; - uint32 total_workers = 5; - repeated uint32 worker_allocation = 6; - uint32 job_compute_units = 7; -} - -message InputUris { - string inputs_path = 1; - string hints_path = 2; + oneof hints_source { + string hints_path = 4; + bytes hints_data = 5; + } + uint32 rank_id = 6; + uint32 total_workers = 7; + repeated uint32 worker_allocation = 8; + uint32 job_compute_units = 9; } message ProveParams { diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index e44caacc6..280bb9560 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -9,12 +9,13 @@ //! model. All conversions implement the `From` and/or `Into` traits for idiomatic Rust usage. use crate::{ - contribution_params::InputSource, coordinator_message::Payload, execute_task_request, - execute_task_response, job_status_response, jobs_list_response, launch_proof_response, - system_status_response, workers_list_response, AggParams, Challenges, + contribution_params::{HintsSource, InputSource}, + coordinator_message::Payload, + execute_task_request, execute_task_response, job_status_response, jobs_list_response, + launch_proof_response, system_status_response, workers_list_response, AggParams, Challenges, ComputeCapacity as GrpcComputeCapacity, ContributionParams, CoordinatorMessage, - ExecuteTaskRequest, ExecuteTaskResponse, Heartbeat, HeartbeatAck, InputMode, InputUris, - JobCancelled, JobStatus, JobStatusResponse, JobsList, JobsListResponse, LaunchProofRequest, + ExecuteTaskRequest, ExecuteTaskResponse, Heartbeat, HeartbeatAck, InputMode, JobCancelled, + JobStatus, JobStatusResponse, JobsList, JobsListResponse, LaunchProofRequest, LaunchProofResponse, Metrics, Proof, ProofList, ProveParams, Shutdown, StatusInfoResponse, SystemStatus, SystemStatusResponse, TaskType, WorkerError, WorkerInfo, WorkerReconnectRequest, WorkerRegisterRequest, WorkerRegisterResponse, WorkersList, WorkersListResponse, @@ -153,14 +154,16 @@ impl From for SystemStatusResponse { impl From for LaunchProofRequest { fn from(dto: LaunchProofRequestDto) -> Self { - let (inputs_mode, inputs_uri, hints_uri) = match dto.inputs_mode { - InputModeDto::InputModeNone => (InputMode::None, None, None), - InputModeDto::InputModePath(inputs, hints) => { - (InputMode::Path, Some(inputs), Some(hints)) - } - InputModeDto::InputModeData(inputs, hints) => { - (InputMode::Data, Some(inputs), Some(hints)) - } + let (inputs_mode, inputs_uri) = match dto.inputs_mode { + InputModeDto::InputModeNone => (InputMode::None, None), + InputModeDto::InputModeUri(inputs_uri) => (InputMode::Uri, Some(inputs_uri)), + InputModeDto::InputModeData(inputs_uri) => (InputMode::Data, Some(inputs_uri)), + }; + + let (hints_mode, hints_uri) = match dto.hints_mode { + InputModeDto::InputModeNone => (InputMode::None, None), + InputModeDto::InputModeUri(hints_uri) => (InputMode::Uri, Some(hints_uri)), + InputModeDto::InputModeData(hints_uri) => (InputMode::Data, Some(hints_uri)), }; LaunchProofRequest { @@ -168,6 +171,7 @@ impl From for LaunchProofRequest { compute_capacity: dto.compute_capacity, inputs_mode: inputs_mode.into(), inputs_uri, + hints_mode: hints_mode.into(), hints_uri, simulated_node: dto.simulated_node, } @@ -185,23 +189,32 @@ impl TryFrom for LaunchProofRequestDto { compute_capacity: req.compute_capacity, inputs_mode: match InputMode::try_from(req.inputs_mode).unwrap_or(InputMode::None) { InputMode::None => InputModeDto::InputModeNone, - InputMode::Path => { + InputMode::Uri => { let inputs_uri = req.inputs_uri.ok_or_else(|| { anyhow::anyhow!("Input mode is Path but inputs_uri is missing") })?; - let hints_uri = req.hints_uri.ok_or_else(|| { - anyhow::anyhow!("Input mode is Path but hints_uri is missing") - })?; - InputModeDto::InputModePath(inputs_uri, hints_uri) + InputModeDto::InputModeUri(inputs_uri) } InputMode::Data => { let inputs_uri = req.inputs_uri.ok_or_else(|| { anyhow::anyhow!("Input mode is Data but inputs_uri is missing") })?; + InputModeDto::InputModeData(inputs_uri) + } + }, + hints_mode: match InputMode::try_from(req.hints_mode).unwrap_or(InputMode::None) { + InputMode::None => InputModeDto::InputModeNone, + InputMode::Uri => { let hints_uri = req.hints_uri.ok_or_else(|| { - anyhow::anyhow!("Input mode is Data but hints_uri is missing") + anyhow::anyhow!("Hints mode is Path but hints_uri is missing") })?; - InputModeDto::InputModeData(inputs_uri, hints_uri) + InputModeDto::InputModeUri(hints_uri) + } + InputMode::Data => { + let hints_uri = req.hints_uri.ok_or_else(|| { + anyhow::anyhow!("Hints mode is Data but hints_uri is missing") + })?; + InputModeDto::InputModeData(hints_uri) } }, simulated_node: req.simulated_node, @@ -327,16 +340,21 @@ impl From for ExecuteTaskRequest { impl From for ContributionParams { fn from(dto: ContributionParamsDto) -> Self { let input_source = match dto.input_source { - InputSourceDto::InputPath(inputs_path, hints_uri) => { - Some(InputSource::InputPath(InputUris { inputs_path, hints_path: hints_uri })) - } + InputSourceDto::InputPath(inputs_path) => Some(InputSource::InputPath(inputs_path)), InputSourceDto::InputData(data) => Some(InputSource::InputData(data)), InputSourceDto::InputNull => None, }; + let hints_source = match dto.hints_source { + InputSourceDto::InputPath(hints_path) => Some(HintsSource::HintsPath(hints_path)), + InputSourceDto::InputData(data) => Some(HintsSource::HintsData(data)), + InputSourceDto::InputNull => None, + }; + ContributionParams { data_id: dto.data_id.as_string(), input_source, + hints_source, rank_id: dto.rank_id, total_workers: dto.total_workers, worker_allocation: dto.worker_allocation, diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index cb8c50c08..51f95c87c 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -497,6 +497,7 @@ impl Worker { prover.as_ref(), phase_inputs, job.data_ctx.input_source.clone(), + job.data_ctx.hints_source.clone(), options, ) .await; @@ -528,17 +529,16 @@ impl Worker { prover: &ZiskProver, phase_inputs: ProvePhaseInputs, input_source: InputSourceDto, + hints_source: InputSourceDto, options: ProofOptions, ) -> Result> { let phase = proofman::ProvePhase::Contributions; match input_source { - InputSourceDto::InputPath(inputs_uri, hints_uri) => { + InputSourceDto::InputPath(inputs_uri) => { let stdin = ZiskStdin::from_file(inputs_uri)?; - let hints_stream = StreamSource::from_uri(hints_uri.into())?; prover.set_stdin(stdin); - prover.set_hints_stream(hints_stream)?; } InputSourceDto::InputData(input_data) => { let stdin = ZiskStdin::from_vec(input_data); @@ -550,6 +550,20 @@ impl Worker { } } + match hints_source { + InputSourceDto::InputPath(hints_uri) => { + let hints_stream = StreamSource::from_uri(hints_uri.into())?; + prover.set_hints_stream(hints_stream)?; + } + InputSourceDto::InputData(hints_data) => { + let hints_stream = StreamSource::from_vec(hints_data); + prover.set_hints_stream(hints_stream)?; + } + InputSourceDto::InputNull => { + // No hints to set + } + } + let challenge = match prover.prove_phase(phase_inputs, options, phase) { Ok(proofman::ProvePhaseResult::Contributions(challenge)) => { info!("Contribution computation successful for {job_id}"); @@ -754,11 +768,12 @@ impl Worker { match phase { JobPhase::Contributions => { - let (job_id, phase_inputs, options, input_source_dto): ( + let (job_id, phase_inputs, options, input_source_dto, hints_source_dto): ( JobId, ProvePhaseInputs, ProofOptions, InputSourceDto, + InputSourceDto, ) = borsh::from_slice(&bytes[1..]).unwrap(); let result = Self::execute_contribution_task( @@ -766,6 +781,7 @@ impl Worker { self.prover.as_ref(), phase_inputs, input_source_dto, + hints_source_dto, options, ) .await; diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 83c85ce6e..8032e7309 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -12,7 +12,7 @@ use zisk_distributed_common::{ AggProofData, AggregationParams, DataCtx, InputSourceDto, WorkerState, }; use zisk_distributed_common::{DataId, JobId}; -use zisk_distributed_grpc_api::contribution_params::InputSource; +use zisk_distributed_grpc_api::contribution_params::{HintsSource, InputSource}; use zisk_distributed_grpc_api::execute_task_response::ResultData; use zisk_distributed_grpc_api::*; use zisk_sdk::{Asm, Emu, ZiskBackend}; @@ -516,32 +516,41 @@ impl WorkerNodeGrpc { let job_id = JobId::from(request.job_id); let input_source = match params.input_source { - Some(InputSource::InputPath(ref input_uris)) => { + Some(InputSource::InputPath(ref inputs_uris)) => { // Validate and get the full path let inputs_uri = Self::validate_subdir( &self.worker_config.worker.inputs_folder, - &PathBuf::from(&input_uris.inputs_path), + &PathBuf::from(&inputs_uris), ) .await?; + InputSourceDto::InputPath(inputs_uri.to_string_lossy().to_string()) + } + Some(InputSource::InputData(data)) => InputSourceDto::InputData(data), + None => { + return Err(anyhow!("Input source missing in ContributionParams")); + } + }; + + let hints_source = match params.hints_source { + Some(HintsSource::HintsPath(ref hints_uris)) => { + // Validate and get the full path let hints_uri = Self::validate_subdir( &self.worker_config.worker.inputs_folder, - &PathBuf::from(&input_uris.hints_path), + &PathBuf::from(&hints_uris), ) .await?; - InputSourceDto::InputPath( - inputs_uri.to_string_lossy().to_string(), - hints_uri.to_string_lossy().to_string(), - ) + InputSourceDto::InputPath(hints_uri.to_string_lossy().to_string()) } - Some(InputSource::InputData(data)) => InputSourceDto::InputData(data), + Some(HintsSource::HintsData(data)) => InputSourceDto::InputData(data), None => { - return Err(anyhow!("Input source missing in ContributionParams")); + return Err(anyhow!("Hints source missing in ContributionParams")); } }; - let data_ctx = DataCtx { data_id: DataId::from(params.data_id), input_source }; + let data_ctx = + DataCtx { data_id: DataId::from(params.data_id), input_source, hints_source }; let job = self.worker.new_job( job_id, From 2209b68d77c22828f0f7c63deb809c14b4ab0189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:41:53 +0000 Subject: [PATCH 113/782] Fcalls reorganization --- ziskos/entrypoint/src/lib.rs | 19 ++++--- .../{bls12_381_fp_sqrt.rs => bls12_381/fp.rs} | 36 ++++++++++++- .../fp2.rs} | 42 ++++++++++++++- .../src/zisklib/fcalls/bls12_381/mod.rs | 7 +++ .../twist.rs} | 5 +- .../src/zisklib/fcalls/bls12_381_fp2_inv.rs | 47 ---------------- .../src/zisklib/fcalls/bls12_381_fp_inv.rs | 41 -------------- .../fcalls/{bn254_fp.rs => bn254/fp.rs} | 2 +- .../fcalls/{bn254_fp2.rs => bn254/fp2.rs} | 2 +- .../src/zisklib/fcalls/bn254/mod.rs | 7 +++ .../fcalls/{bn254_twist.rs => bn254/twist.rs} | 5 +- ziskos/entrypoint/src/zisklib/fcalls/mod.rs | 28 +++------- .../{secp256k1_fn_inv.rs => secp256k1/fn.rs} | 5 +- .../{secp256k1_fp_sqrt.rs => secp256k1/fp.rs} | 54 ++++++++++++++----- .../src/zisklib/fcalls/secp256k1/mod.rs | 5 ++ .../src/zisklib/fcalls/secp256k1_fp_inv.rs | 47 ---------------- .../src/zisklib/fcalls_impl/big_int_div.rs | 2 +- .../fp2_inv.rs} | 2 +- .../fp2_sqrt.rs} | 7 +-- .../fp_inv.rs} | 2 +- .../fp_sqrt.rs} | 2 +- .../src/zisklib/fcalls_impl/bls12_381/mod.rs | 11 ++++ .../twist.rs} | 2 +- .../fcalls_impl/{bn254_fp.rs => bn254/fp.rs} | 2 +- .../{bn254_fp2.rs => bn254/fp2.rs} | 2 +- .../src/zisklib/fcalls_impl/bn254/mod.rs | 7 +++ .../{bn254_twist.rs => bn254/twist.rs} | 2 +- .../entrypoint/src/zisklib/fcalls_impl/mod.rs | 14 ++--- .../src/zisklib/fcalls_impl/proxy.rs | 6 +-- .../fn_inv.rs} | 0 .../fp_inv.rs} | 0 .../fp_sqrt.rs} | 2 +- .../src/zisklib/fcalls_impl/secp256k1/mod.rs | 7 +++ .../src/zisklib/lib/bls12_381/miller_loop.rs | 12 +++-- .../src/zisklib/lib/bn254/miller_loop.rs | 18 +++---- 35 files changed, 221 insertions(+), 231 deletions(-) rename ziskos/entrypoint/src/zisklib/fcalls/{bls12_381_fp_sqrt.rs => bls12_381/fp.rs} (54%) rename ziskos/entrypoint/src/zisklib/fcalls/{bls12_381_fp2_sqrt.rs => bls12_381/fp2.rs} (53%) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls/bls12_381/mod.rs rename ziskos/entrypoint/src/zisklib/fcalls/{bls12_381_twist.rs => bls12_381/twist.rs} (96%) delete mode 100644 ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs delete mode 100644 ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs rename ziskos/entrypoint/src/zisklib/fcalls/{bn254_fp.rs => bn254/fp.rs} (97%) rename ziskos/entrypoint/src/zisklib/fcalls/{bn254_fp2.rs => bn254/fp2.rs} (97%) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls/bn254/mod.rs rename ziskos/entrypoint/src/zisklib/fcalls/{bn254_twist.rs => bn254/twist.rs} (95%) rename ziskos/entrypoint/src/zisklib/fcalls/{secp256k1_fn_inv.rs => secp256k1/fn.rs} (95%) rename ziskos/entrypoint/src/zisklib/fcalls/{secp256k1_fp_sqrt.rs => secp256k1/fp.rs} (56%) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls/secp256k1/mod.rs delete mode 100644 ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs rename ziskos/entrypoint/src/zisklib/fcalls_impl/{bls12_381_fp2_inv.rs => bls12_381/fp2_inv.rs} (99%) rename ziskos/entrypoint/src/zisklib/fcalls_impl/{bls12_381_fp2_sqrt.rs => bls12_381/fp2_sqrt.rs} (97%) rename ziskos/entrypoint/src/zisklib/fcalls_impl/{bls12_381_fp_inv.rs => bls12_381/fp_inv.rs} (97%) rename ziskos/entrypoint/src/zisklib/fcalls_impl/{bls12_381_fp_sqrt.rs => bls12_381/fp_sqrt.rs} (97%) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/mod.rs rename ziskos/entrypoint/src/zisklib/fcalls_impl/{bls12_381_twist.rs => bls12_381/twist.rs} (98%) rename ziskos/entrypoint/src/zisklib/fcalls_impl/{bn254_fp.rs => bn254/fp.rs} (96%) rename ziskos/entrypoint/src/zisklib/fcalls_impl/{bn254_fp2.rs => bn254/fp2.rs} (99%) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/mod.rs rename ziskos/entrypoint/src/zisklib/fcalls_impl/{bn254_twist.rs => bn254/twist.rs} (99%) rename ziskos/entrypoint/src/zisklib/fcalls_impl/{secp256k1_fn_inv.rs => secp256k1/fn_inv.rs} (100%) rename ziskos/entrypoint/src/zisklib/fcalls_impl/{secp256k1_fp_inv.rs => secp256k1/fp_inv.rs} (100%) rename ziskos/entrypoint/src/zisklib/fcalls_impl/{secp256k1_fp_sqrt.rs => secp256k1/fp_sqrt.rs} (98%) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index ae6ae0903..8f9159430 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -1,14 +1,17 @@ #![allow(unexpected_cfgs)] #![allow(unused_imports)] -#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -use core::arch::asm; -#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -mod fcall; +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { + use core::arch::asm; + mod fcall; + pub use fcall::*; + } +} + mod profile; -#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -pub use fcall::*; -pub use profile::*; pub mod zisklib; @@ -16,6 +19,8 @@ pub mod syscalls; pub mod ziskos_definitions; +pub use profile::*; + #[macro_export] macro_rules! entrypoint { ($path:path) => { diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp.rs similarity index 54% rename from ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs rename to ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp.rs index 8c3059895..cfcbed5b5 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp.rs @@ -1,10 +1,42 @@ -//! fcall_bls12_381_fp_sqrt free call use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::FCALL_BLS12_381_FP_SQRT_ID; + use super::{FCALL_BLS12_381_FP_INV_ID, FCALL_BLS12_381_FP_SQRT_ID}; + } +} + +/// Executes the multiplicative inverse computation over the base field of the `bls12_381` curve. +/// +/// `fcall_bls12_381_fp_inv` performs an inversion of a 256-bit field element, +/// represented as an array of four `u64` values. +/// +/// - `fcall_bls12_381_fp_inv` performs the inversion and **returns the result directly**. +/// +/// ### Safety +/// +/// The caller must ensure that the input pointer (`p_value`) is valid and aligned to an 8-byte boundary. +/// +/// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness +/// of the result. It is the caller's responsibility to ensure it. +#[allow(unused_variables)] +pub fn fcall_bls12_381_fp_inv(p_value: &[u64; 6]) -> [u64; 6] { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!(); + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + ziskos_fcall_param!(p_value, 8); + ziskos_fcall!(FCALL_BLS12_381_FP_INV_ID); + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp2.rs similarity index 53% rename from ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs rename to ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp2.rs index 8a312d24f..a4cbffa62 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp2.rs @@ -1,10 +1,48 @@ -//! fcall_bls12_381_fp2_sqrt free call use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::FCALL_BLS12_381_FP2_SQRT_ID; + use super::{FCALL_BLS12_381_FP2_INV_ID, FCALL_BLS12_381_FP2_SQRT_ID}; + } +} + +/// Executes the multiplicative inverse computation over the complex extension field of the `bls12_381` curve. +/// +/// `fcall_bls12_381_fp2_inv` performs an inversion of a 512-bit extension field element, +/// represented as an array of eight `u64` values. +/// +/// - `fcall_bls12_381_fp2_inv` performs the inversion and **returns the result directly**. +/// +/// ### Safety +/// +/// The caller must ensure that the input pointer (`p_value`) is valid and aligned to an 8-byte boundary. +/// +/// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness +/// of the result. It is the caller's responsibility to ensure it. +#[allow(unused_variables)] +pub fn fcall_bls12_381_fp2_inv(p_value: &[u64; 12]) -> [u64; 12] { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!(); + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + ziskos_fcall_param!(p_value, 12); + ziskos_fcall!(FCALL_BLS12_381_FP2_INV_ID); + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/mod.rs new file mode 100644 index 000000000..a494177c3 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/mod.rs @@ -0,0 +1,7 @@ +mod fp; +mod fp2; +mod twist; + +pub use fp::*; +pub use fp2::*; +pub use twist::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/twist.rs similarity index 96% rename from ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs rename to ziskos/entrypoint/src/zisklib/fcalls/bls12_381/twist.rs index 83dd00247..0f9e7e56a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/twist.rs @@ -1,4 +1,5 @@ use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; @@ -16,7 +17,7 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bls12_381_add_line_coeffs( +pub fn fcall_bls12_381_twist_add_line_coeffs( p1_value: &[u64; 24], p2_value: &[u64; 24], ) -> ([u64; 12], [u64; 12]) { @@ -69,7 +70,7 @@ pub fn fcall_bls12_381_add_line_coeffs( /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bls12_381_dbl_line_coeffs(p_value: &[u64; 24]) -> ([u64; 12], [u64; 12]) { +pub fn fcall_bls12_381_twist_dbl_line_coeffs(p_value: &[u64; 24]) -> ([u64; 12], [u64; 12]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs deleted file mode 100644 index 8cf9cf8b9..000000000 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! fcall_bls12_381_fp2_inv free call -use cfg_if::cfg_if; -cfg_if! { - if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { - use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::FCALL_BLS12_381_FP2_INV_ID; - } -} - -/// Executes the multiplicative inverse computation over the complex extension field of the `bls12_381` curve. -/// -/// `fcall_bls12_381_fp2_inv` performs an inversion of a 512-bit extension field element, -/// represented as an array of eight `u64` values. -/// -/// - `fcall_bls12_381_fp2_inv` performs the inversion and **returns the result directly**. -/// -/// ### Safety -/// -/// The caller must ensure that the input pointer (`p_value`) is valid and aligned to an 8-byte boundary. -/// -/// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness -/// of the result. It is the caller's responsibility to ensure it. -#[allow(unused_variables)] -pub fn fcall_bls12_381_fp2_inv(p_value: &[u64; 12]) -> [u64; 12] { - #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); - #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] - { - ziskos_fcall_param!(p_value, 12); - ziskos_fcall!(FCALL_BLS12_381_FP2_INV_ID); - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ] - } -} diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs deleted file mode 100644 index 9f70241f1..000000000 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! fcall_bls12_381_fp_inv free call -use cfg_if::cfg_if; -cfg_if! { - if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { - use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::FCALL_BLS12_381_FP_INV_ID; - } -} - -/// Executes the multiplicative inverse computation over the base field of the `bls12_381` curve. -/// -/// `fcall_bls12_381_fp_inv` performs an inversion of a 256-bit field element, -/// represented as an array of four `u64` values. -/// -/// - `fcall_bls12_381_fp_inv` performs the inversion and **returns the result directly**. -/// -/// ### Safety -/// -/// The caller must ensure that the input pointer (`p_value`) is valid and aligned to an 8-byte boundary. -/// -/// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness -/// of the result. It is the caller's responsibility to ensure it. -#[allow(unused_variables)] -pub fn fcall_bls12_381_fp_inv(p_value: &[u64; 6]) -> [u64; 6] { - #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); - #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] - { - ziskos_fcall_param!(p_value, 8); - ziskos_fcall!(FCALL_BLS12_381_FP_INV_ID); - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ] - } -} diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp.rs similarity index 97% rename from ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs rename to ziskos/entrypoint/src/zisklib/fcalls/bn254/fp.rs index c34957e5f..bd2146448 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp.rs @@ -1,5 +1,5 @@ -//! fcall_bn254_fp_inv free call use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp2.rs similarity index 97% rename from ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs rename to ziskos/entrypoint/src/zisklib/fcalls/bn254/fp2.rs index 5d27436ec..bf5640af9 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp2.rs @@ -1,5 +1,5 @@ -//! fcall_bn254_fp2_inv free call use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254/mod.rs new file mode 100644 index 000000000..a494177c3 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254/mod.rs @@ -0,0 +1,7 @@ +mod fp; +mod fp2; +mod twist; + +pub use fp::*; +pub use fp2::*; +pub use twist::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254/twist.rs similarity index 95% rename from ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs rename to ziskos/entrypoint/src/zisklib/fcalls/bn254/twist.rs index 756e3976b..4d5987fdd 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254/twist.rs @@ -1,4 +1,5 @@ use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; @@ -16,7 +17,7 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bn254_add_line_coeffs( +pub fn fcall_bn254_twist_add_line_coeffs( p1_value: &[u64; 16], p2_value: &[u64; 16], ) -> ([u64; 8], [u64; 8]) { @@ -61,7 +62,7 @@ pub fn fcall_bn254_add_line_coeffs( /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bn254_dbl_line_coeffs(p_value: &[u64; 16]) -> ([u64; 8], [u64; 8]) { +pub fn fcall_bn254_twist_dbl_line_coeffs(p_value: &[u64; 16]) -> ([u64; 8], [u64; 8]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs index 5a25548e9..d4315282c 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs @@ -23,33 +23,17 @@ pub const FCALL_BLS12_381_FP2_SQRT_ID: u16 = 19; mod big_int256_div; mod big_int_div; mod bin_decomp; -mod bls12_381_fp2_inv; -mod bls12_381_fp2_sqrt; -mod bls12_381_fp_inv; -mod bls12_381_fp_sqrt; -mod bls12_381_twist; -mod bn254_fp; -mod bn254_fp2; -mod bn254_twist; +mod bls12_381; +mod bn254; mod msb_pos_256; mod msb_pos_384; -mod secp256k1_fn_inv; -mod secp256k1_fp_inv; -mod secp256k1_fp_sqrt; +mod secp256k1; pub use big_int256_div::*; pub use big_int_div::*; pub use bin_decomp::*; -pub use bls12_381_fp2_inv::*; -pub use bls12_381_fp2_sqrt::*; -pub use bls12_381_fp_inv::*; -pub use bls12_381_fp_sqrt::*; -pub use bls12_381_twist::*; -pub use bn254_fp::*; -pub use bn254_fp2::*; -pub use bn254_twist::*; +pub use bls12_381::*; +pub use bn254::*; pub use msb_pos_256::*; pub use msb_pos_384::*; -pub use secp256k1_fn_inv::*; -pub use secp256k1_fp_inv::*; -pub use secp256k1_fp_sqrt::*; +pub use secp256k1::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs similarity index 95% rename from ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs rename to ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs index 51dceae11..c8bf82360 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs @@ -1,5 +1,5 @@ -//! fcall_secp256k1_fn_inv free call use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; @@ -7,6 +7,7 @@ cfg_if! { use super::FCALL_SECP256K1_FN_INV_ID; } } + /// Executes the multiplicative inverse computation over the scalar field of the `secp256k1` curve. /// /// Both `fcall_secp256k1_fn_inv` and `fcall2_secp256k1_fn_inv` perform an inversion of a 256-bit @@ -35,7 +36,7 @@ pub fn fcall_secp256k1_fn_inv(p_value: &[u64; 4]) -> [u64; 4] { } #[allow(unused_variables)] -pub fn fcall2_secp256k1_fn_inv(p_value: &[u64; 4]) { +pub fn fcall_secp256k1_fn_inv_in_place(p_value: &[u64; 4]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs similarity index 56% rename from ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs rename to ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs index ed64347aa..4fa863293 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs @@ -1,10 +1,48 @@ -//! fcall_secp256k1_fp_sqrt free call use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::FCALL_SECP256K1_FP_SQRT_ID; + use super::FCALL_SECP256K1_FP_INV_ID; + } +} + +/// Executes the multiplicative inverse computation over the base field of the `secp256k1` curve. +/// +/// Both `fcall_secp256k1_fp_inv` and `fcall2_secp256k1_fp_inv` perform an inversion of a 256-bit field element, +/// represented as an array of four `u64` values. +/// +/// - `fcall_secp256k1_fp_inv` performs the inversion and **returns the result directly**. +/// - `fcall2_secp256k1_fp_inv` performs the inversion but does **not return the result immediately**. +/// You must explicitly retrieve the result using four (4) `fcall_get` instructions. +/// +/// ### Safety +/// +/// The caller must ensure that the input pointer (`p_value`) is valid and aligned to an 8-byte boundary. +/// +/// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness +/// of the result. It is the caller's responsibility to ensure it. +#[allow(unused_variables)] +pub fn fcall_secp256k1_fp_inv(p_value: &[u64; 4]) -> [u64; 4] { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!(); + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + ziskos_fcall_param!(p_value, 4); + ziskos_fcall!(FCALL_SECP256K1_FP_INV_ID); + [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()] + } +} + +#[allow(unused_variables)] +pub fn fcall_secp256k1_fp_inv_in_place(p_value: &[u64; 4]) { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!(); + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + ziskos_fcall_param!(p_value, 4); + ziskos_fcall!(FCALL_SECP256K1_FP_INV_ID); } } @@ -41,15 +79,3 @@ pub fn fcall_secp256k1_fp_sqrt(p_value: &[u64; 4], parity: u64) -> [u64; 5] { ] } } - -#[allow(unused_variables)] -pub fn fcall2_secp256k1_fp_sqrt(p_value: &[u64; 4], parity: u64) { - #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); - #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] - { - ziskos_fcall_param!(p_value, 4); - ziskos_fcall_param!(parity, 1); - ziskos_fcall!(FCALL_SECP256K1_FP_SQRT_ID); - } -} diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/mod.rs new file mode 100644 index 000000000..9bb017229 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/mod.rs @@ -0,0 +1,5 @@ +mod r#fn; +mod fp; + +pub use fp::*; +pub use r#fn::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs deleted file mode 100644 index 84ad3c4ae..000000000 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! fcall_secp256k1_fp_inv free call -use cfg_if::cfg_if; -cfg_if! { - if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { - use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::FCALL_SECP256K1_FP_INV_ID; - } -} - -/// Executes the multiplicative inverse computation over the base field of the `secp256k1` curve. -/// -/// Both `fcall_secp256k1_fp_inv` and `fcall2_secp256k1_fp_inv` perform an inversion of a 256-bit field element, -/// represented as an array of four `u64` values. -/// -/// - `fcall_secp256k1_fp_inv` performs the inversion and **returns the result directly**. -/// - `fcall2_secp256k1_fp_inv` performs the inversion but does **not return the result immediately**. -/// You must explicitly retrieve the result using four (4) `fcall_get` instructions. -/// -/// ### Safety -/// -/// The caller must ensure that the input pointer (`p_value`) is valid and aligned to an 8-byte boundary. -/// -/// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness -/// of the result. It is the caller's responsibility to ensure it. -#[allow(unused_variables)] -pub fn fcall_secp256k1_fp_inv(p_value: &[u64; 4]) -> [u64; 4] { - #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); - #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] - { - ziskos_fcall_param!(p_value, 4); - ziskos_fcall!(FCALL_SECP256K1_FP_INV_ID); - [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()] - } -} - -#[allow(unused_variables)] -pub fn fcall2_secp256k1_fp_inv(p_value: &[u64; 4]) { - #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); - #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] - { - ziskos_fcall_param!(p_value, 4); - ziskos_fcall!(FCALL_SECP256K1_FP_INV_ID); - } -} diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int_div.rs index 8ab86be34..9bdf272ad 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int_div.rs @@ -1,6 +1,6 @@ use num_integer::Integer; -use super::utils::{biguint_from_u64_digits, u64_digits_from_biguint}; +use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, u64_digits_from_biguint}; /// Perform the division of an unsigned integer `a` by another unsigned integer `b`, /// returning the quotient `q` and the remainder `r`, such that `a = b * q + r` diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_inv.rs similarity index 99% rename from ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_inv.rs rename to ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_inv.rs index 2dcafce29..54529a237 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_inv.rs @@ -1,7 +1,7 @@ use lazy_static::lazy_static; use num_bigint::BigUint; -use super::bls12_381_fp_inv::{ +use super::fp_inv::{ bls12_381_fp_add, bls12_381_fp_dbl, bls12_381_fp_inv, bls12_381_fp_mul, bls12_381_fp_neg, bls12_381_fp_square, bls12_381_fp_sub, }; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs similarity index 97% rename from ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs rename to ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs index 53fb5d392..9595faf27 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs @@ -2,10 +2,11 @@ use lazy_static::lazy_static; use num_bigint::BigUint; use num_traits::{One, Zero}; +use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, u64_digits_from_biguint}; + use super::{ - bls12_381_fp2_inv::{bls12_381_fp2_mul, bls12_381_fp2_square}, - bls12_381_fp_inv::{bls12_381_fp_add, bls12_381_fp_neg}, - utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}, + fp2_inv::{bls12_381_fp2_mul, bls12_381_fp2_square}, + fp_inv::{bls12_381_fp_add, bls12_381_fp_neg}, }; const ONE: [u64; 12] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_inv.rs similarity index 97% rename from ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_inv.rs rename to ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_inv.rs index 0402d9e48..843dabf4a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_inv.rs @@ -2,7 +2,7 @@ use lazy_static::lazy_static; use num_bigint::BigUint; use num_traits::Zero; -use super::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; +use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; lazy_static! { pub static ref P: BigUint = BigUint::parse_bytes( diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_sqrt.rs similarity index 97% rename from ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_sqrt.rs rename to ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_sqrt.rs index 6f0c6192d..203b71f95 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_sqrt.rs @@ -1,7 +1,7 @@ use lazy_static::lazy_static; use num_bigint::BigUint; -use super::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; +use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; lazy_static! { pub static ref P: BigUint = BigUint::parse_bytes( diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/mod.rs new file mode 100644 index 000000000..33655e97b --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/mod.rs @@ -0,0 +1,11 @@ +mod fp2_inv; +mod fp2_sqrt; +mod fp_inv; +mod fp_sqrt; +mod twist; + +pub use fp2_inv::*; +pub use fp2_sqrt::*; +pub use fp_inv::*; +pub use fp_sqrt::*; +pub use twist::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/twist.rs similarity index 98% rename from ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_twist.rs rename to ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/twist.rs index c8f045fa0..8a506c5fa 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/twist.rs @@ -1,4 +1,4 @@ -use super::bls12_381_fp2_inv::{ +use super::fp2_inv::{ bls12_381_fp2_dbl, bls12_381_fp2_inv, bls12_381_fp2_mul, bls12_381_fp2_scalar_mul, bls12_381_fp2_square, bls12_381_fp2_sub, }; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_fp.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp.rs similarity index 96% rename from ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_fp.rs rename to ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp.rs index df3bc0858..948f811cb 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp.rs @@ -1,7 +1,7 @@ use lazy_static::lazy_static; use num_bigint::BigUint; -use super::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; +use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; lazy_static! { pub static ref P: BigUint = BigUint::parse_bytes( diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_fp2.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp2.rs similarity index 99% rename from ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_fp2.rs rename to ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp2.rs index 4b16b4c3e..2fee5b381 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_fp2.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp2.rs @@ -1,7 +1,7 @@ use lazy_static::lazy_static; use num_bigint::BigUint; -use super::bn254_fp::{ +use super::fp::{ bn254_fp_add, bn254_fp_dbl, bn254_fp_inv, bn254_fp_mul, bn254_fp_neg, bn254_fp_square, bn254_fp_sub, }; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/mod.rs new file mode 100644 index 000000000..a494177c3 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/mod.rs @@ -0,0 +1,7 @@ +mod fp; +mod fp2; +mod twist; + +pub use fp::*; +pub use fp2::*; +pub use twist::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/twist.rs similarity index 99% rename from ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_twist.rs rename to ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/twist.rs index af0bd98c9..c5ccd44da 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/twist.rs @@ -1,4 +1,4 @@ -use super::bn254_fp2::{ +use super::fp2::{ bn254_fp2_dbl, bn254_fp2_inv, bn254_fp2_mul, bn254_fp2_scalar_mul, bn254_fp2_square, bn254_fp2_sub, }; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs index 2460a788d..ca9ea9138 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs @@ -1,20 +1,12 @@ mod big_int256_div; mod big_int_div; mod bin_decomp; -mod bls12_381_fp2_inv; -mod bls12_381_fp2_sqrt; -mod bls12_381_fp_inv; -mod bls12_381_fp_sqrt; -mod bls12_381_twist; -mod bn254_fp; -mod bn254_fp2; -mod bn254_twist; +mod bls12_381; +mod bn254; mod msb_pos_256; mod msb_pos_384; mod proxy; -mod secp256k1_fn_inv; -mod secp256k1_fp_inv; -mod secp256k1_fp_sqrt; +mod secp256k1; mod utils; pub use proxy::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs index 1528fc8b5..660d55ed6 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs @@ -8,10 +8,8 @@ use crate::zisklib::{ }; use super::{ - big_int256_div::*, big_int_div::*, bin_decomp::*, bls12_381_fp2_inv::*, bls12_381_fp2_sqrt::*, - bls12_381_fp_inv::*, bls12_381_fp_sqrt::*, bls12_381_twist::*, bn254_fp::*, bn254_fp2::*, - bn254_twist::*, msb_pos_256::*, msb_pos_384::*, secp256k1_fn_inv::*, secp256k1_fp_inv::*, - secp256k1_fp_sqrt::*, + big_int256_div::*, big_int_div::*, bin_decomp::*, bls12_381::*, bn254::*, msb_pos_256::*, + msb_pos_384::*, secp256k1::*, }; pub fn fcall_proxy(id: u64, params: &[u64], results: &mut [u64]) -> i64 { diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1_fn_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fn_inv.rs similarity index 100% rename from ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1_fn_inv.rs rename to ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fn_inv.rs diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_inv.rs similarity index 100% rename from ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1_fp_inv.rs rename to ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_inv.rs diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_sqrt.rs similarity index 98% rename from ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1_fp_sqrt.rs rename to ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_sqrt.rs index dae86a0ba..ee4b75b5a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_sqrt.rs @@ -1,7 +1,7 @@ use lazy_static::lazy_static; use num_bigint::BigUint; -use super::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; +use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; lazy_static! { pub static ref P: BigUint = BigUint::parse_bytes( diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs new file mode 100644 index 000000000..b10289b22 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs @@ -0,0 +1,7 @@ +mod fn_inv; +mod fp_inv; +mod fp_sqrt; + +pub use fn_inv::*; +pub use fp_inv::*; +pub use fp_sqrt::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs index f4cce702e..bcc693060 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs @@ -1,6 +1,8 @@ //! Miller loop for BLS12-381 -use crate::zisklib::{eq, fcall_bls12_381_add_line_coeffs, fcall_bls12_381_dbl_line_coeffs}; +use crate::zisklib::{ + eq, fcall_bls12_381_twist_add_line_coeffs, fcall_bls12_381_twist_dbl_line_coeffs, +}; use super::{ constants::{EXT_U_INV, X_ABS_BIN_BE}, @@ -35,7 +37,7 @@ pub fn miller_loop_bls12_381(p: &[u64; 12], q: &[u64; 24]) -> [u64; 72] { }; for &bit in X_ABS_BIN_BE.iter().skip(1) { // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(r)} - let (lambda, mu) = fcall_bls12_381_dbl_line_coeffs(&r); + let (lambda, mu) = fcall_bls12_381_twist_dbl_line_coeffs(&r); // Check that the line is correct assert!(is_tangent_twist_bls12_381(&r, &lambda, &mu)); @@ -50,7 +52,7 @@ pub fn miller_loop_bls12_381(p: &[u64; 12], q: &[u64; 24]) -> [u64; 72] { if bit == 1 { // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(q)} - let (lambda, mu) = fcall_bls12_381_add_line_coeffs(&r, q); + let (lambda, mu) = fcall_bls12_381_twist_add_line_coeffs(&r, q); // Check that the line is correct assert!(is_line_twist_bls12_381(&r, q, &lambda, &mu)); @@ -99,7 +101,7 @@ pub fn miller_loop_batch_bls12_381(g1_points: &[[u64; 12]], g2_points: &[[u64; 2 let r = &mut r[i]; // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(r)} - let (lambda, mu) = fcall_bls12_381_dbl_line_coeffs(r); + let (lambda, mu) = fcall_bls12_381_twist_dbl_line_coeffs(r); // Check that the line is correct assert!(is_tangent_twist_bls12_381(r, &lambda, &mu)); @@ -116,7 +118,7 @@ pub fn miller_loop_batch_bls12_381(g1_points: &[[u64; 12]], g2_points: &[[u64; 2 let q = &g2_points[i]; // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(q')} - let (lambda, mu) = fcall_bls12_381_add_line_coeffs(r, q); + let (lambda, mu) = fcall_bls12_381_twist_add_line_coeffs(r, q); // Check that the line is correct assert!(is_line_twist_bls12_381(r, q, &lambda, &mu)); diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/miller_loop.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/miller_loop.rs index 601c850fc..bb835b639 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/miller_loop.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/miller_loop.rs @@ -1,6 +1,6 @@ //! Miller Loop for the pairings over BN254 -use crate::zisklib::{eq, fcall_bn254_add_line_coeffs, fcall_bn254_dbl_line_coeffs}; +use crate::zisklib::{eq, fcall_bn254_twist_add_line_coeffs, fcall_bn254_twist_dbl_line_coeffs}; use super::{ fp::{inv_fp_bn254, mul_fp_bn254, neg_fp_bn254}, @@ -35,7 +35,7 @@ pub fn miller_loop_bn254(p: &[u64; 8], q: &[u64; 16]) -> [u64; 48] { f[0] = 1; for &bit in LOOP_LENGTH.iter().skip(1) { // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(r)} - let (lambda, mu) = fcall_bn254_dbl_line_coeffs(&r); + let (lambda, mu) = fcall_bn254_twist_dbl_line_coeffs(&r); // Check that the line is correct assert!(is_tangent_twist_bn254(&r, &lambda, &mu)); @@ -52,7 +52,7 @@ pub fn miller_loop_bn254(p: &[u64; 8], q: &[u64; 16]) -> [u64; 48] { let q_prime = if bit == 1 { q } else { &neg_twist_bn254(q) }; // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(q')} - let (lambda, mu) = fcall_bn254_add_line_coeffs(&r, q_prime); + let (lambda, mu) = fcall_bn254_twist_add_line_coeffs(&r, q_prime); // Check that the line is correct assert!(is_line_twist_bn254(&r, q_prime, &lambda, &mu)); @@ -72,7 +72,7 @@ pub fn miller_loop_bn254(p: &[u64; 8], q: &[u64; 16]) -> [u64; 48] { let q_frob = utf_endomorphism_twist_bn254(q); // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(utf(q))} - let (lambda, mu) = fcall_bn254_add_line_coeffs(&r, &q_frob); + let (lambda, mu) = fcall_bn254_twist_add_line_coeffs(&r, &q_frob); assert!(is_line_twist_bn254(&r, &q_frob, &lambda, &mu)); let l = line_eval_twist_bn254(&lambda, &mu, &xp_prime, &yp_prime); @@ -85,7 +85,7 @@ pub fn miller_loop_bn254(p: &[u64; 8], q: &[u64; 16]) -> [u64; 48] { let q_frob2 = neg_twist_bn254(&utf_endomorphism_twist_bn254(&q_frob)); // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(-utf(utf(q)))} - let (lambda, mu) = fcall_bn254_add_line_coeffs(&r, &q_frob2); + let (lambda, mu) = fcall_bn254_twist_add_line_coeffs(&r, &q_frob2); assert!(is_line_twist_bn254(&r, &q_frob2, &lambda, &mu)); let l = line_eval_twist_bn254(&lambda, &mu, &xp_prime, &yp_prime); @@ -123,7 +123,7 @@ pub fn miller_loop_batch_bn254(g1_points: &[[u64; 8]], g2_points: &[[u64; 16]]) let r = &mut r[i]; // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(r)} - let (lambda, mu) = fcall_bn254_dbl_line_coeffs(r); + let (lambda, mu) = fcall_bn254_twist_dbl_line_coeffs(r); // Check that the line is correct assert!(is_tangent_twist_bn254(r, &lambda, &mu)); @@ -141,7 +141,7 @@ pub fn miller_loop_batch_bn254(g1_points: &[[u64; 8]], g2_points: &[[u64; 16]]) let q_prime = if bit == 1 { q } else { &neg_twist_bn254(q) }; // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(q')} - let (lambda, mu) = fcall_bn254_add_line_coeffs(r, q_prime); + let (lambda, mu) = fcall_bn254_twist_add_line_coeffs(r, q_prime); // Check that the line is correct assert!(is_line_twist_bn254(r, q_prime, &lambda, &mu)); @@ -167,7 +167,7 @@ pub fn miller_loop_batch_bn254(g1_points: &[[u64; 8]], g2_points: &[[u64; 16]]) let q_frob = utf_endomorphism_twist_bn254(q); // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(utf(q))} - let (lambda, mu) = fcall_bn254_add_line_coeffs(r, &q_frob); + let (lambda, mu) = fcall_bn254_twist_add_line_coeffs(r, &q_frob); assert!(is_line_twist_bn254(r, &q_frob, &lambda, &mu)); let l = line_eval_twist_bn254(&lambda, &mu, xp_prime, yp_prime); @@ -180,7 +180,7 @@ pub fn miller_loop_batch_bn254(g1_points: &[[u64; 8]], g2_points: &[[u64; 16]]) let q_frob2 = neg_twist_bn254(&utf_endomorphism_twist_bn254(&q_frob)); // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(-utf(utf(q)))} - let (lambda, mu) = fcall_bn254_add_line_coeffs(r, &q_frob2); + let (lambda, mu) = fcall_bn254_twist_add_line_coeffs(r, &q_frob2); assert!(is_line_twist_bn254(r, &q_frob2, &lambda, &mu)); let l = line_eval_twist_bn254(&lambda, &mu, xp_prime, yp_prime); From 3fd73f9362a5bac7615c4939c2ab5b405390fcb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:57:16 +0000 Subject: [PATCH 114/782] Correcting imports --- ziskos/entrypoint/src/lib.rs | 19 +++++++------------ .../src/zisklib/fcalls/bls12_381/fp.rs | 6 ++++-- .../src/zisklib/fcalls/bls12_381/fp2.rs | 6 ++++-- .../src/zisklib/fcalls/bls12_381/twist.rs | 6 ++++-- .../entrypoint/src/zisklib/fcalls/bn254/fp.rs | 3 +-- .../src/zisklib/fcalls/bn254/fp2.rs | 3 +-- .../src/zisklib/fcalls/bn254/twist.rs | 6 ++++-- .../src/zisklib/fcalls/secp256k1/fn.rs | 3 +-- .../src/zisklib/fcalls/secp256k1/fp.rs | 6 ++++-- .../zisklib/fcalls_impl/secp256k1/fn_inv.rs | 2 +- .../zisklib/fcalls_impl/secp256k1/fp_inv.rs | 2 +- 11 files changed, 32 insertions(+), 30 deletions(-) diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 8f9159430..ae6ae0903 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -1,17 +1,14 @@ #![allow(unexpected_cfgs)] #![allow(unused_imports)] -use cfg_if::cfg_if; - -cfg_if! { - if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { - use core::arch::asm; - mod fcall; - pub use fcall::*; - } -} - +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +use core::arch::asm; +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +mod fcall; mod profile; +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +pub use fcall::*; +pub use profile::*; pub mod zisklib; @@ -19,8 +16,6 @@ pub mod syscalls; pub mod ziskos_definitions; -pub use profile::*; - #[macro_export] macro_rules! entrypoint { ($path:path) => { diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp.rs index cfcbed5b5..023d4bc1b 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp.rs @@ -3,8 +3,10 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::{FCALL_BLS12_381_FP_INV_ID, FCALL_BLS12_381_FP_SQRT_ID}; + use crate::{ + ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, + zisklib::{FCALL_BLS12_381_FP_INV_ID, FCALL_BLS12_381_FP_SQRT_ID} + }; } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp2.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp2.rs index a4cbffa62..d96bb0b51 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp2.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp2.rs @@ -3,8 +3,10 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::{FCALL_BLS12_381_FP2_INV_ID, FCALL_BLS12_381_FP2_SQRT_ID}; + use crate::{ + ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, + zisklib::{FCALL_BLS12_381_FP2_INV_ID, FCALL_BLS12_381_FP2_SQRT_ID} + }; } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/twist.rs index 0f9e7e56a..851f9ebe9 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/twist.rs @@ -3,8 +3,10 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::{FCALL_BLS12_381_TWIST_ADD_LINE_COEFFS_ID, FCALL_BLS12_381_TWIST_DBL_LINE_COEFFS_ID}; + use crate::{ + ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, + zisklib::{FCALL_BLS12_381_TWIST_ADD_LINE_COEFFS_ID, FCALL_BLS12_381_TWIST_DBL_LINE_COEFFS_ID}, + }; } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp.rs index bd2146448..32917ab27 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp.rs @@ -3,8 +3,7 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::FCALL_BN254_FP_INV_ID; + use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, zisklib::FCALL_BN254_FP_INV_ID}; } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp2.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp2.rs index bf5640af9..8bf7ae2e8 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp2.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp2.rs @@ -3,8 +3,7 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::FCALL_BN254_FP2_INV_ID; + use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, zisklib::FCALL_BN254_FP2_INV_ID}; } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254/twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254/twist.rs index 4d5987fdd..7789a096e 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254/twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254/twist.rs @@ -3,8 +3,10 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::{FCALL_BN254_TWIST_ADD_LINE_COEFFS_ID, FCALL_BN254_TWIST_DBL_LINE_COEFFS_ID}; + use crate::{ + ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, + zisklib::{FCALL_BN254_TWIST_ADD_LINE_COEFFS_ID, FCALL_BN254_TWIST_DBL_LINE_COEFFS_ID} + }; } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs index c8bf82360..d4aaa7781 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs @@ -3,8 +3,7 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::FCALL_SECP256K1_FN_INV_ID; + use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, zisklib::FCALL_SECP256K1_FN_INV_ID}; } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs index 4fa863293..51df32d08 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs @@ -3,8 +3,10 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; - use super::FCALL_SECP256K1_FP_INV_ID; + use crate::{ + ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, + zisklib::{FCALL_SECP256K1_FP_INV_ID, FCALL_SECP256K1_FP_SQRT_ID} + }; } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fn_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fn_inv.rs index d882b71c8..9f2473ff9 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fn_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fn_inv.rs @@ -15,7 +15,7 @@ cfg_if::cfg_if! { use lazy_static::lazy_static; use num_bigint::BigUint; - use super::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; + use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; lazy_static! { pub static ref N: BigUint = BigUint::parse_bytes( diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_inv.rs index 78bd6c866..66637136f 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_inv.rs @@ -15,7 +15,7 @@ cfg_if::cfg_if! { use lazy_static::lazy_static; use num_bigint::BigUint; - use super::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; + use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; lazy_static! { pub static ref P: BigUint = BigUint::parse_bytes( From b278fe0d2ccbadc09a6c4423b824663e3f7ebed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Tue, 23 Dec 2025 10:54:45 +0000 Subject: [PATCH 115/782] Separating curve and ecdsa --- .../src/zisklib/fcalls/big_int256_div.rs | 2 +- .../src/zisklib/fcalls/big_int_div.rs | 2 +- .../src/zisklib/fcalls/bin_decomp.rs | 1 + .../src/zisklib/fcalls/msb_pos_256.rs | 18 + .../src/zisklib/fcalls/msb_pos_384.rs | 2 + .../src/zisklib/fcalls_impl/msb_pos_256.rs | 37 +- .../fcalls_impl/secp256k1/constants.rs | 30 + .../zisklib/fcalls_impl/secp256k1/fn_inv.rs | 8 +- .../zisklib/fcalls_impl/secp256k1/fp_inv.rs | 8 +- .../zisklib/fcalls_impl/secp256k1/fp_sqrt.rs | 22 +- .../src/zisklib/fcalls_impl/secp256k1/mod.rs | 2 + .../entrypoint/src/zisklib/lib/constants.rs | 8 + ziskos/entrypoint/src/zisklib/lib/mod.rs | 2 + .../src/zisklib/lib/secp256k1/constants.rs | 5 + .../src/zisklib/lib/secp256k1/curve.rs | 803 ++++++++++++------ .../src/zisklib/lib/secp256k1/ecdsa.rs | 100 +++ .../src/zisklib/lib/secp256k1/mod.rs | 2 + ziskos/entrypoint/src/zisklib/lib/utils.rs | 23 + 18 files changed, 769 insertions(+), 306 deletions(-) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/constants.rs create mode 100644 ziskos/entrypoint/src/zisklib/lib/constants.rs create mode 100644 ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs index 56fba88df..7bd9367ef 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs @@ -1,5 +1,5 @@ -//! fcall_bigint256_div free call use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs index a289ca1dd..18f9cd231 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs @@ -1,5 +1,5 @@ -//! fcall_division free call use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs index f506e4491..6399d986a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs @@ -1,4 +1,5 @@ use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs index 8af583b12..7d94ee1dc 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs @@ -1,4 +1,5 @@ use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; @@ -6,15 +7,32 @@ cfg_if! { use super::FCALL_MSB_POS_256_ID; } } + #[allow(unused_variables)] pub fn fcall_msb_pos_256(x: &[u64; 4], y: &[u64; 4]) -> (u64, u64) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { + ziskos_fcall_param!(2, 1); // Number of inputs ziskos_fcall_param!(x, 4); ziskos_fcall_param!(y, 4); ziskos_fcall!(FCALL_MSB_POS_256_ID); (ziskos_fcall_get(), ziskos_fcall_get()) } } + +#[allow(unused_variables)] +pub fn fcall_msb_pos_256_3(x: &[u64; 4], y: &[u64; 4], z: &[u64; 4]) -> (u64, u64) { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!(); + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + ziskos_fcall_param!(3, 1); // Number of inputs + ziskos_fcall_param!(x, 4); + ziskos_fcall_param!(y, 4); + ziskos_fcall_param!(z, 4); + ziskos_fcall!(FCALL_MSB_POS_256_ID); + (ziskos_fcall_get(), ziskos_fcall_get()) + } +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs index 629063c9f..1ab35288a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs @@ -1,4 +1,5 @@ use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; @@ -6,6 +7,7 @@ cfg_if! { use super::FCALL_MSB_POS_384_ID; } } + #[allow(unused_variables)] pub fn fcall_msb_pos_384(x: &[u64; 6], y: &[u64; 6]) -> (u64, u64) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_256.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_256.rs index ac5808270..ed5fd3b57 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_256.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_256.rs @@ -1,23 +1,34 @@ -pub fn fcall_msb_pos_256(parameters: &[u64], results: &mut [u64]) -> i64 { - // Check if the parameters are valid - let x = ¶meters[0..4].try_into().unwrap(); - let y = ¶meters[4..8].try_into().unwrap(); +pub fn fcall_msb_pos_256(params: &[u64], results: &mut [u64]) -> i64 { + let n = params[0] as usize; - let (i, pos) = msb_pos_256(x, y); - results[0] = i as u64; - results[1] = pos as u64; + let (limb, bit) = msb_pos_256(¶ms[1..], n); + + results[0] = limb as u64; + results[1] = bit as u64; 2 } // Q: Do we prefer constant time functions? -fn msb_pos_256(x: &[u64; 4], y: &[u64; 4]) -> (usize, usize) { - for i in (0..4).rev() { - if x[i] != 0 || y[i] != 0 { - let word = if x[i] > y[i] { x[i] } else { y[i] }; - return (i, msb_pos(word)); +// Finds the most significant bit position among n 256-bit integers +// some of which may be zero, but not all +fn msb_pos_256(params: &[u64], n: usize) -> (usize, usize) { + debug_assert!(params.len() >= n * 4, "Not enough data for {} inputs", n); + + for limb in (0..4).rev() { + // Find max value at this limb position across all inputs + let mut max_word = 0u64; + for i in 0..n { + let word = params[i * 4 + limb]; + if word > max_word { + max_word = word; + } + } + + if max_word != 0 { + return (limb, msb_pos(max_word)); } } - panic!("Invalid input: x and y are both zero"); + panic!("Invalid input: all values are zero"); } // Q: Do we prefer constant time functions? diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/constants.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/constants.rs new file mode 100644 index 000000000..e627ef644 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/constants.rs @@ -0,0 +1,30 @@ +use lazy_static::lazy_static; +use num_bigint::BigUint; + +lazy_static! { + pub static ref P: BigUint = BigUint::parse_bytes( + b"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + 16 + ) + .unwrap(); + + pub static ref P_HALF: BigUint = BigUint::parse_bytes( + b"7fffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffe17", + 16 + ) + .unwrap(); + + pub static ref P_DIV_4: BigUint = BigUint::parse_bytes( + b"3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c", + 16 + ) + .unwrap(); + + pub static ref NQR: BigUint = BigUint::from(3u64); // First non-quadratic residue in Fp + + pub static ref N: BigUint = BigUint::parse_bytes( + b"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + 16 + ) + .unwrap(); +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fn_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fn_inv.rs index 9f2473ff9..d3b39c32f 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fn_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fn_inv.rs @@ -17,13 +17,7 @@ cfg_if::cfg_if! { use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; - lazy_static! { - pub static ref N: BigUint = BigUint::parse_bytes( - b"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", - 16 - ) - .unwrap(); - } + use super::N; pub fn fcall_secp256k1_fn_inv(params: &[u64], results: &mut [u64]) -> i64 { // Get the input diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_inv.rs index 66637136f..b4a732cf7 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_inv.rs @@ -17,13 +17,7 @@ cfg_if::cfg_if! { use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; - lazy_static! { - pub static ref P: BigUint = BigUint::parse_bytes( - b"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", - 16 - ) - .unwrap(); - } + use super::P; pub fn fcall_secp256k1_fp_inv(params: &[u64], results: &mut [u64]) -> i64 { // Get the input diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_sqrt.rs index ee4b75b5a..7461d7670 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/fp_sqrt.rs @@ -3,27 +3,7 @@ use num_bigint::BigUint; use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; -lazy_static! { - pub static ref P: BigUint = BigUint::parse_bytes( - b"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", - 16 - ) - .unwrap(); - - pub static ref P_HALF: BigUint = BigUint::parse_bytes( - b"7fffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffe17", - 16 - ) - .unwrap(); - - pub static ref P_DIV_4: BigUint = BigUint::parse_bytes( - b"3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c", - 16 - ) - .unwrap(); - - pub static ref NQR: BigUint = BigUint::from(3u64); // First non-quadratic residue in Fp -} +use super::{NQR, P, P_DIV_4}; pub fn fcall_secp256k1_fp_sqrt(params: &[u64], results: &mut [u64]) -> i64 { // Get the input diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs index b10289b22..908a4459a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs @@ -1,7 +1,9 @@ +mod constants; mod fn_inv; mod fp_inv; mod fp_sqrt; +use constants::*; pub use fn_inv::*; pub use fp_inv::*; pub use fp_sqrt::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/constants.rs b/ziskos/entrypoint/src/zisklib/lib/constants.rs new file mode 100644 index 000000000..eaeae37b0 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/constants.rs @@ -0,0 +1,8 @@ +/// Zero in 256-bit representation +pub const ZERO_256: [u64; 4] = [0, 0, 0, 0]; + +/// One in 256-bit representation +pub const ONE_256: [u64; 4] = [1, 0, 0, 0]; + +/// Two in 256-bit representation +pub const TWO_256: [u64; 4] = [2, 0, 0, 0]; diff --git a/ziskos/entrypoint/src/zisklib/lib/mod.rs b/ziskos/entrypoint/src/zisklib/lib/mod.rs index 0e70a9a4a..6ed327c62 100644 --- a/ziskos/entrypoint/src/zisklib/lib/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/mod.rs @@ -2,6 +2,7 @@ mod array_lib; mod bigint256; mod bls12_381; mod bn254; +mod constants; mod secp256k1; mod sha256f_compress; mod utils; @@ -11,6 +12,7 @@ pub use array_lib::*; pub use bigint256::*; pub use bls12_381::*; pub use bn254::*; +pub use constants::*; pub use secp256k1::*; pub use sha256f_compress::*; pub use utils::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs index 8cc082e21..6bbe4197c 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs @@ -16,8 +16,13 @@ pub const N: [u64; 4] = [0xBFD25E8CD0364141, 0xBAAEDCE6AF48A03B, 0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFF]; pub const N_MINUS_ONE: [u64; 4] = [N[0] - 1, N[1], N[2], N[3]]; +/// Secp256k1 group identity point +pub const IDENTITY_X: [u64; 4] = [0; 4]; +pub const IDENTITY_Y: [u64; 4] = [0; 4]; + /// Secp256k1 group of points generator pub const G_X: [u64; 4] = [0x59F2815B16F81798, 0x029BFCDB2DCE28D9, 0x55A06295CE870B07, 0x79BE667EF9DCBBAC]; pub const G_Y: [u64; 4] = [0x9C47D08FFB10D4B8, 0xFD17B448A6855419, 0x5DA4FBFC0E1108A8, 0x483ADA7726A3C465]; +pub const G: [u64; 8] = [G_X[0], G_X[1], G_X[2], G_X[3], G_Y[0], G_Y[1], G_Y[2], G_Y[3]]; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index 43c600604..0bbcb421a 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -2,69 +2,28 @@ use crate::{ syscalls::{ syscall_secp256k1_add, syscall_secp256k1_dbl, SyscallPoint256, SyscallSecp256k1AddParams, }, - zisklib::{eq, fcall_msb_pos_256}, + zisklib::{eq, fcall_msb_pos_256, fcall_msb_pos_256_3, is_one, ONE_256, TWO_256, ZERO_256}, }; use super::{ - constants::{E_B, G_X, G_Y}, + constants::{E_B, G, G_X, G_Y, IDENTITY_X, IDENTITY_Y}, field::{ secp256k1_fp_add, secp256k1_fp_inv, secp256k1_fp_mul, secp256k1_fp_sqrt, secp256k1_fp_square, }, - scalar::{secp256k1_fn_inv, secp256k1_fn_mul, secp256k1_fn_reduce}, + scalar::{secp256k1_fn_inv, secp256k1_fn_mul, secp256k1_fn_reduce, secp256k1_fn_sub}, }; -/// Converts a non-zero point `p` on the Secp256k1 curve from projective coordinates to affine coordinates -pub fn secp256k1_to_affine(p: &[u64; 12]) -> [u64; 8] { - let z: [u64; 4] = p[8..12].try_into().unwrap(); - - // Point at infinity cannot be converted to affine - debug_assert!(z != [0u64; 4], "Cannot convert point at infinity to affine"); - - let zinv = secp256k1_fp_inv(&z); - let zinv_sq = secp256k1_fp_square(&zinv); +const IDENTITY_POINT256: SyscallPoint256 = SyscallPoint256 { x: IDENTITY_X, y: IDENTITY_Y }; - let x: [u64; 4] = p[0..4].try_into().unwrap(); - let y: [u64; 4] = p[4..8].try_into().unwrap(); - - let x_res = secp256k1_fp_mul(&x, &zinv_sq); - let mut y_res = secp256k1_fp_mul(&y, &zinv_sq); - y_res = secp256k1_fp_mul(&y_res, &zinv); - - [x_res[0], x_res[1], x_res[2], x_res[3], y_res[0], y_res[1], y_res[2], y_res[3]] -} - -/// Checks if two points `p1` and `p2` on the Secp256k1 curve in projective coordinates are equal -pub fn secp256k1_eq_projective(p1: &[u64; 12], p2: &[u64; 12]) -> bool { - // In essence given two points in projective form p1 = (x₁z₁,y₁z₁,z₁) and p2 = (x₂z₂,y₂z₂,z₂) - // We can simply multiply p1 by z2 and p2 by z1 to get tuples: - // p1 = (x₁z₁z₂,y₁z₁z₂,z₁z₂) and p2 = (x₂z₂z₁,y₂z₂z₁,z₂z₁) - // So we can compare the two points by checking if (x₁z₁)z₂ == (x₂z₁)z₂ and (y₁z₂)z₁ == (y₂z₂)z₁ - let x1 = p1[0..4].try_into().unwrap(); - let y1 = p1[4..8].try_into().unwrap(); - let z1 = p1[8..12].try_into().unwrap(); - let x2 = p2[0..4].try_into().unwrap(); - let y2 = p2[4..8].try_into().unwrap(); - let z2 = p2[8..12].try_into().unwrap(); - - let lhs_x = secp256k1_fp_mul(x1, z2); - let rhs_x = secp256k1_fp_mul(x2, z1); - if !eq(&lhs_x, &rhs_x) { - return false; - } - - let lhs_y = secp256k1_fp_mul(y1, z2); - let rhs_y = secp256k1_fp_mul(y2, z1); - if !eq(&lhs_y, &rhs_y) { - return false; - } - - true -} +const G_POINT256: SyscallPoint256 = SyscallPoint256 { x: G_X, y: G_Y }; /// Given a x-coordinate `x_bytes` and a parity `y_is_odd`, /// this function decompresses the point on the secp256k1 curve. -pub fn secp256k1_decompress(x_bytes: &[u8; 32], y_is_odd: bool) -> (([u64; 4], [u64; 4]), bool) { +pub fn secp256k1_decompress( + x_bytes: &[u8; 32], + y_is_odd: bool, +) -> Result<([u64; 4], [u64; 4]), bool> { // Convert the x-coordinate from BEu8 to LEu64 let mut x = [0u64; 4]; for i in 0..32 { @@ -77,45 +36,126 @@ pub fn secp256k1_decompress(x_bytes: &[u8; 32], y_is_odd: bool) -> (([u64; 4], [ let y_sq = secp256k1_fp_add(&x_cb, &E_B); let (y, has_sqrt) = secp256k1_fp_sqrt(&y_sq, y_is_odd as u64); if !has_sqrt { - return (([0u64; 4], [0u64; 4]), false); + return Err(false); } // Check the received parity of the y-coordinate is correct let parity = (y[0] & 1) != 0; assert_eq!(parity, y_is_odd); - ((x, y), true) + Ok((x, y)) } -/// Given points `p1` and `p2`, performs the point addition `p1 + p2` and assigns the result to `p1`. -/// It assumes that `p1` and `p2` are from the Secp256k1 curve, that `p1,p2 != 𝒪` and that `p2 != p1,-p1` -fn add_points_assign(p1: &mut SyscallPoint256, p2: &SyscallPoint256) { - let mut params = SyscallSecp256k1AddParams { p1, p2 }; - syscall_secp256k1_add(&mut params); -} +/// Converts a non-infinity point `p` on the Secp256k1 curve from projective coordinates to affine coordinates +pub fn secp256k1_to_affine(p: &[u64; 12]) -> [u64; 8] { + let z: [u64; 4] = [p[8], p[9], p[10], p[11]]; -/// Given a point `p1`, performs the point doubling `2·p1` and assigns the result to `p1`. -/// It assumes that `p1` is from the Secp256k1 curve and that `p1 != 𝒪` -/// -/// Note: We don't need to assume that 2·p1 != 𝒪 because there are not points of order 2 on the Secp256k1 curve -fn double_point_assign(p1: &mut SyscallPoint256) { - syscall_secp256k1_dbl(p1); + // Point at infinity cannot be converted to affine + debug_assert!(z != ZERO_256, "Cannot convert point at infinity to affine"); + + let zinv = secp256k1_fp_inv(&z); + let zinv_sq = secp256k1_fp_square(&zinv); + + let x: [u64; 4] = [p[0], p[1], p[2], p[3]]; + let y: [u64; 4] = [p[4], p[5], p[6], p[7]]; + + let x_res = secp256k1_fp_mul(&x, &zinv_sq); + let y_res = secp256k1_fp_mul(&secp256k1_fp_mul(&y, &zinv_sq), &zinv); + + [x_res[0], x_res[1], x_res[2], x_res[3], y_res[0], y_res[1], y_res[2], y_res[3]] } /// Given points `p1` and `p2`, performs the point addition `p1 + p2` and assigns the result to `p1`. -/// It assumes that `p1` and `p2` are from the Secp256k1 curve, that `p2 != 𝒪` -fn add_points_complete_assign( - p1: &mut SyscallPoint256, - p1_is_infinity: &mut bool, - p2: &SyscallPoint256, -) { +/// It assumes that `p1` and `p2` are from the Secp256k1 curve, that `p1,p2 != 𝒪` +/// Returns true if the result is the point at infinity. +#[inline] +fn secp256k1_add_non_infinity_points(p1: &mut SyscallPoint256, p2: &SyscallPoint256) -> bool { if p1.x != p2.x { - add_points_assign(p1, p2); + let mut params = SyscallSecp256k1AddParams { p1, p2 }; + syscall_secp256k1_add(&mut params); + false } else if p1.y == p2.y { - double_point_assign(p1); + syscall_secp256k1_dbl(p1); + false + } else { + // p1 + (-p1) = 𝒪 + true + } +} + +/// Given a non-infinity point `p` and a scalar `k`, computes the scalar multiplication `k·p` +/// +/// Note: There are no (non-infinity) points of order 2 in Secp256k1. +/// All (non-infinity) points are of prime order N. +fn secp256k1_scalar_mul(k: &[u64; 4], p: &[u64; 8]) -> Option<[u64; 8]> { + // Direct cases: k = 0, k = 1, k = 2 + if eq(k, &ZERO_256) { + return None; + } else if eq(k, &ONE_256) { + return Some(*p); + } else if eq(k, &TWO_256) { + let mut res = SyscallPoint256 { x: [p[0], p[1], p[2], p[3]], y: [p[4], p[5], p[6], p[7]] }; + syscall_secp256k1_dbl(&mut res); + return Some([ + res.x[0], res.x[1], res.x[2], res.x[3], res.y[0], res.y[1], res.y[2], res.y[3], + ]); + } + // We can assume k > 2 from now on + + // Hint the length the binary representations of k + // We will verify the output by recomposing k + // Moreover, we should check that the first received bit is 1 + let (max_limb, max_bit) = fcall_msb_pos_256(k, &ZERO_256); + + // Perform the loop, based on the binary representation of k + + // We do the first iteration separately + let max_limb = max_limb as usize; + let max_bit = max_bit as usize; + + // The first received bit should be 1 + assert_eq!((k[max_limb] >> max_bit) & 1, 1); + + // Start at P + let mut res = SyscallPoint256 { x: [p[0], p[1], p[2], p[3]], y: [p[4], p[5], p[6], p[7]] }; + let mut k_rec = ZERO_256; + k_rec[max_limb] = 1 << max_bit; + + // Determine starting limb/bit for the loop + let mut limb = max_limb; + let mut bit = if max_bit == 0 { + // If max_bit is 0 then limb > 0; otherwise k = 1, which is excluded here + limb -= 1; + 63 } else { - *p1_is_infinity = true; + max_bit - 1 + }; + + // Perform the rest of the loop + let p = SyscallPoint256 { x: [p[0], p[1], p[2], p[3]], y: [p[4], p[5], p[6], p[7]] }; + for i in (0..=limb).rev() { + for j in (0..=bit).rev() { + // Always double + syscall_secp256k1_dbl(&mut res); + + // Get the next bit b of k. + // If b == 1, we should add P + if ((k[i] >> j) & 1) == 1 { + let mut params = SyscallSecp256k1AddParams { p1: &mut res, p2: &p }; + syscall_secp256k1_add(&mut params); + + // Reconstruct k + k_rec[i] |= 1 << j; + } + } + bit = 63; } + + // Check that the reconstructed k is equal to the input k + assert!(eq(&k_rec, k)); + + // Convert the result back to a single array + Some([res.x[0], res.x[1], res.x[2], res.x[3], res.y[0], res.y[1], res.y[2], res.y[3]]) } /// Given a point `p` and scalars `k1` and `k2`, computes the double scalar multiplication `k1·G + k2·p` @@ -123,17 +163,23 @@ fn add_points_complete_assign( pub fn secp256k1_double_scalar_mul_with_g( k1: &[u64; 4], k2: &[u64; 4], - p: &SyscallPoint256, -) -> (bool, SyscallPoint256) { + p: &[u64; 8], +) -> Option<[u64; 8]> { + let p = SyscallPoint256 { x: [p[0], p[1], p[2], p[3]], y: [p[4], p[5], p[6], p[7]] }; + // Start by precomputing g + p - let mut gp = SyscallPoint256 { x: G_X, y: G_Y }; - let mut gp_is_infinity = false; - add_points_complete_assign(&mut gp, &mut gp_is_infinity, p); - - let one = [1u64, 0, 0, 0]; - if *k1 == one && *k2 == one { - // Return G + p - return (gp_is_infinity, gp); + let mut gp = G_POINT256; + let gp_is_infinity = secp256k1_add_non_infinity_points(&mut gp, &p); + + // If G + P = 𝒪 => P = -G and therefore the operation is k1·G + (-k2)·G = (k1-k2)·G + // Fall back to scalar mul + if gp_is_infinity { + return secp256k1_scalar_mul(&secp256k1_fn_sub(k1, k2), &G); + } + + if is_one(k1) && is_one(k2) { + // Return g + p + return Some([gp.x[0], gp.x[1], gp.x[2], gp.x[3], gp.y[0], gp.y[1], gp.y[2], gp.y[3]]); } // From here on, at least one of k1 or k2 is greater than 1 @@ -154,59 +200,44 @@ pub fn secp256k1_double_scalar_mul_with_g( assert!(k1_bit == 1 || k2_bit == 1); // Start at 𝒪 - let mut res = SyscallPoint256 { x: [0u64; 4], y: [0u64; 4] }; + let mut res = IDENTITY_POINT256; let mut res_is_infinity = true; - let mut k1_rec = [0u64; 4]; - let mut k2_rec = [0u64; 4]; - if (k1_bit == 0) && (k2_bit == 1) { - // If res is 𝒪, set res = p; otherwise, double res and add p - if res_is_infinity { + let mut k1_rec = ZERO_256; + let mut k2_rec = ZERO_256; + + // Three cases based on the bits of k1 and k2 + match (k1_bit, k2_bit) { + (0, 1) => { + // Set res = p res.x = p.x; res.y = p.y; res_is_infinity = false; - } else { - double_point_assign(&mut res); - add_points_complete_assign(&mut res, &mut res_is_infinity, p); - } - // Update k2_rec - k2_rec[max_limb] |= 1 << max_bit; - } else if (k1_bit == 1) && (k2_bit == 0) { - // If res is 𝒪, set res = g; otherwise, double res and add g - if res_is_infinity { - res.x = G_X; - res.y = G_Y; - res_is_infinity = false; - } else { - double_point_assign(&mut res); - add_points_complete_assign( - &mut res, - &mut res_is_infinity, - &SyscallPoint256 { x: G_X, y: G_Y }, - ); + // Update k2_rec + k2_rec[max_limb] = 1 << max_bit; } + (1, 0) => { + // Set res = g + res.x = G_POINT256.x; + res.y = G_POINT256.y; + res_is_infinity = false; - // Update k1_rec - k1_rec[max_limb] |= 1 << max_bit; - } else if (k1_bit == 1) && (k2_bit == 1) { - if res_is_infinity { - // If (g + p) is 𝒪, do nothing; otherwise set res = (g + p) + // Update k1_rec + k1_rec[max_limb] = 1 << max_bit; + } + (1, 1) => { + // Set res = g + p if not infinity if !gp_is_infinity { res.x = gp.x; res.y = gp.y; res_is_infinity = false; } - } else { - // If (g + p) is 𝒪, simply double res; otherwise double res and add (g + p) - double_point_assign(&mut res); - if !gp_is_infinity { - add_points_complete_assign(&mut res, &mut res_is_infinity, &gp); - } - } - // Update k1_rec and k2_rec - k1_rec[max_limb] |= 1 << max_bit; - k2_rec[max_limb] |= 1 << max_bit; + // Update k1_rec and k2_rec + k1_rec[max_limb] = 1 << max_bit; + k2_rec[max_limb] = 1 << max_bit; + } + _ => unreachable!(), } // Determine starting limb/bit for the loop @@ -225,114 +256,385 @@ pub fn secp256k1_double_scalar_mul_with_g( let k1_bit = (k1[i] >> j) & 1; let k2_bit = (k2[i] >> j) & 1; - if (k1_bit == 0) && (k2_bit == 0) { - // If res is 𝒪, do nothing; otherwise, double - if !res_is_infinity { - double_point_assign(&mut res); - } - } else if (k1_bit == 0) && (k2_bit == 1) { - // If res is 𝒪, set res = p; otherwise, double res and add p - if res_is_infinity { - res.x = p.x; - res.y = p.y; - res_is_infinity = false; - } else { - double_point_assign(&mut res); - add_points_complete_assign(&mut res, &mut res_is_infinity, p); + // Four cases based on the bits of k1 and k2 + match (k1_bit, k2_bit) { + (0, 0) => { + // If res is 𝒪, do nothing; otherwise, double + if !res_is_infinity { + syscall_secp256k1_dbl(&mut res); + } } + (0, 1) => { + // If res is 𝒪, set res = p; otherwise, double res and add p + if res_is_infinity { + res.x = p.x; + res.y = p.y; + res_is_infinity = false; + } else { + syscall_secp256k1_dbl(&mut res); + res_is_infinity = secp256k1_add_non_infinity_points(&mut res, &p); + } - // Update k2_rec - k2_rec[i] |= 1 << j; - } else if (k1_bit == 1) && (k2_bit == 0) { - // If res is 𝒪, set res = g; otherwise, double res and add g - if res_is_infinity { - res.x = G_X; - res.y = G_Y; - res_is_infinity = false; - } else { - double_point_assign(&mut res); - add_points_complete_assign( - &mut res, - &mut res_is_infinity, - &SyscallPoint256 { x: G_X, y: G_Y }, - ); + // Update k2_rec + k2_rec[i] |= 1 << j; } - - // Update k1_rec - k1_rec[i] |= 1 << j; - } else if (k1_bit == 1) && (k2_bit == 1) { - if res_is_infinity { - // If (g + p) is 𝒪, do nothing; otherwise set res = (g + p) - if !gp_is_infinity { - res.x = gp.x; - res.y = gp.y; + (1, 0) => { + // If res is 𝒪, set res = g; otherwise, double res and add g + if res_is_infinity { + res.x = G_POINT256.x; + res.y = G_POINT256.y; res_is_infinity = false; + } else { + syscall_secp256k1_dbl(&mut res); + res_is_infinity = secp256k1_add_non_infinity_points(&mut res, &G_POINT256); } - } else { - // If (g + p) is 𝒪, simply double res; otherwise double res and add (g + p) - double_point_assign(&mut res); - if !gp_is_infinity { - add_points_complete_assign(&mut res, &mut res_is_infinity, &gp); - } + + // Update k1_rec + k1_rec[i] |= 1 << j; } + (1, 1) => { + // If res is 𝒪, set res = g + p if not infinity; otherwise, double res and add (g + p) + if res_is_infinity { + if !gp_is_infinity { + res.x = gp.x; + res.y = gp.y; + res_is_infinity = false; + } + } else { + syscall_secp256k1_dbl(&mut res); + if !gp_is_infinity { + res_is_infinity = secp256k1_add_non_infinity_points(&mut res, &gp); + } + } - // Update k1_rec and k2_rec - k1_rec[i] |= 1 << j; - k2_rec[i] |= 1 << j; + // Update k1_rec and k2_rec + k1_rec[i] |= 1 << j; + k2_rec[i] |= 1 << j; + } + _ => unreachable!(), } } bit = 63; } // Check that the recomposed scalars are the same as the received scalars - assert_eq!(k1_rec, *k1); - assert_eq!(k2_rec, *k2); + assert!(eq(&k1_rec, k1)); + assert!(eq(&k2_rec, k2)); - (res_is_infinity, res) + if res_is_infinity { + None + } else { + Some([res.x[0], res.x[1], res.x[2], res.x[3], res.y[0], res.y[1], res.y[2], res.y[3]]) + } } -pub fn secp256k1_ecdsa_verify( - pk: &SyscallPoint256, - z: &[u64; 4], +/// Given two points `p` and `q` and scalars `r`, `s`, and `t`, computes the triple scalar multiplication `r·g + s·p + t·q` +/// It assumes that `r,s,t ∈ [1, N-1]` and that `p,q != 𝒪` +pub fn secp256k1_triple_scalar_mul_with_g( r: &[u64; 4], s: &[u64; 4], -) -> bool { - let s_inv = secp256k1_fn_inv(s); + t: &[u64; 4], + p: &[u64; 8], + q: &[u64; 8], +) -> Option<[u64; 8]> { + let p = SyscallPoint256 { x: [p[0], p[1], p[2], p[3]], y: [p[4], p[5], p[6], p[7]] }; + let q = SyscallPoint256 { x: [q[0], q[1], q[2], q[3]], y: [q[4], q[5], q[6], q[7]] }; + + // Precompute g + p, g + q, p + q, g + p + q + let mut gp = G_POINT256; + let gp_is_infinity = secp256k1_add_non_infinity_points(&mut gp, &p); + + let mut gq = G_POINT256; + let gq_is_infinity = secp256k1_add_non_infinity_points(&mut gq, &q); + + let mut pq = SyscallPoint256 { x: p.x, y: p.y }; + let pq_is_infinity = secp256k1_add_non_infinity_points(&mut pq, &q); + + let mut gpq = SyscallPoint256 { x: gp.x, y: gp.y }; + let gpq_is_infinity = secp256k1_add_non_infinity_points(&mut gpq, &q); + + if is_one(r) && is_one(s) && is_one(t) { + // Return g + p + q + if gpq_is_infinity { + return None; + } else { + return Some([ + gpq.x[0], gpq.x[1], gpq.x[2], gpq.x[3], gpq.y[0], gpq.y[1], gpq.y[2], gpq.y[3], + ]); + } + } + // From here on, at least one of r,s,t is greater than 1 + + // Hint the maximum length between the binary representations of r,s and t + let (max_limb, max_bit) = fcall_msb_pos_256_3(r, s, t); + + // Perform the loop, based on the binary representation of r,s and t + + // We do the first iteration separately + let max_limb = max_limb as usize; + let max_bit = max_bit as usize; + + // At least one of the scalars should have the first received bit as 1 + let r_bit = (r[max_limb] >> max_bit) & 1; + let s_bit = (s[max_limb] >> max_bit) & 1; + let t_bit = (t[max_limb] >> max_bit) & 1; + assert!(r_bit == 1 || s_bit == 1 || t_bit == 1); + + // Start at 𝒪 + let mut res = IDENTITY_POINT256; + let mut res_is_infinity = true; + let mut r_rec = ZERO_256; + let mut s_rec = ZERO_256; + let mut t_rec = ZERO_256; + + // Eight cases based on the bits of r,s and t + match (r_bit, s_bit, t_bit) { + (0, 0, 1) => { + // Set res = q + res.x = q.x; + res.y = q.y; + res_is_infinity = false; + + // Update t_rec + t_rec[max_limb] = 1 << max_bit; + } + (0, 1, 0) => { + // Set res = p + res.x = p.x; + res.y = p.y; + res_is_infinity = false; + + // Update s_rec + s_rec[max_limb] = 1 << max_bit; + } + (0, 1, 1) => { + // Set res = p + q if not infinity + if !pq_is_infinity { + res.x = pq.x; + res.y = pq.y; + res_is_infinity = false; + } + + // Update s_rec and t_rec + s_rec[max_limb] = 1 << max_bit; + t_rec[max_limb] = 1 << max_bit; + } + (1, 0, 0) => { + // Set res = g + res.x = G_POINT256.x; + res.y = G_POINT256.y; + res_is_infinity = false; - let u1 = secp256k1_fn_mul(z, &s_inv); - let u2 = secp256k1_fn_mul(r, &s_inv); + // Update r_rec + r_rec[max_limb] = 1 << max_bit; + } + (1, 0, 1) => { + // Set res = g + q if not infinity + if !gq_is_infinity { + res.x = gq.x; + res.y = gq.y; + res_is_infinity = false; + } - let (is_infinity, res) = secp256k1_double_scalar_mul_with_g(&u1, &u2, pk); - if is_infinity { - return false; + // Update r_rec and t_rec + r_rec[max_limb] = 1 << max_bit; + t_rec[max_limb] = 1 << max_bit; + } + (1, 1, 0) => { + // Set res = g + p if not infinity + if !gp_is_infinity { + res.x = gp.x; + res.y = gp.y; + res_is_infinity = false; + } + + // Update r_rec and s_rec + r_rec[max_limb] = 1 << max_bit; + s_rec[max_limb] = 1 << max_bit; + } + (1, 1, 1) => { + // Set res = g + p + q if not infinity + if !gpq_is_infinity { + res.x = gpq.x; + res.y = gpq.y; + res_is_infinity = false; + } + + // Update r_rec, s_rec and t_rec + r_rec[max_limb] = 1 << max_bit; + s_rec[max_limb] = 1 << max_bit; + t_rec[max_limb] = 1 << max_bit; + } + _ => unreachable!(), } - eq(&secp256k1_fn_reduce(&res.x), r) -} + // Determine starting limb/bit for the loop + let mut limb = max_limb; + let mut bit = if max_bit == 0 { + // If max_bit is 0 then limb > 0; otherwise r,s,t = 1, which is excluded here + limb -= 1; + 63 + } else { + max_bit - 1 + }; -/// # Safety -/// - `p_ptr` must point to 12 u64s (projective point: x[4], y[4], z[4]) -/// - `out_ptr` must point to at least 8 u64s (will write affine x[4], y[4]) -/// -/// Returns 1 on success, 0 if point is at infinity -#[no_mangle] -pub unsafe extern "C" fn secp256k1_to_affine_c(p_ptr: *const u64, out_ptr: *mut u64) { - let p: &[u64; 12] = &*(p_ptr as *const [u64; 12]); - let result = secp256k1_to_affine(p); + // Perform the rest of the loop + for i in (0..=limb).rev() { + for j in (0..=bit).rev() { + let r_bit = (r[i] >> j) & 1; + let s_bit = (s[i] >> j) & 1; + let t_bit = (t[i] >> j) & 1; + + // Eight cases based on the bits of r,s and t + match (r_bit, s_bit, t_bit) { + (0, 0, 0) => { + // If res is 𝒪, do nothing; otherwise, double + if !res_is_infinity { + syscall_secp256k1_dbl(&mut res); + } + } + (0, 0, 1) => { + // If res is 𝒪, set res = q; otherwise, double res and add q + if res_is_infinity { + res.x = q.x; + res.y = q.y; + res_is_infinity = false; + } else { + syscall_secp256k1_dbl(&mut res); + res_is_infinity = secp256k1_add_non_infinity_points(&mut res, &q); + } - *out_ptr.add(0) = result[0]; - *out_ptr.add(1) = result[1]; - *out_ptr.add(2) = result[2]; - *out_ptr.add(3) = result[3]; - *out_ptr.add(4) = result[4]; - *out_ptr.add(5) = result[5]; - *out_ptr.add(6) = result[6]; - *out_ptr.add(7) = result[7]; + // Update t_rec + t_rec[i] |= 1 << j; + } + (0, 1, 0) => { + // If res is 𝒪, set res = p; otherwise, double res and add p + if res_is_infinity { + res.x = p.x; + res.y = p.y; + res_is_infinity = false; + } else { + syscall_secp256k1_dbl(&mut res); + res_is_infinity = secp256k1_add_non_infinity_points(&mut res, &p); + } + + // Update s_rec + s_rec[i] |= 1 << j; + } + (0, 1, 1) => { + // If res is 𝒪, set res = p + q if not infinity; otherwise, double res and add (p + q) + if res_is_infinity { + if !pq_is_infinity { + res.x = pq.x; + res.y = pq.y; + res_is_infinity = false; + } + } else { + syscall_secp256k1_dbl(&mut res); + if !pq_is_infinity { + res_is_infinity = secp256k1_add_non_infinity_points(&mut res, &pq); + } + } + + // Update s_rec and t_rec + s_rec[i] |= 1 << j; + t_rec[i] |= 1 << j; + } + (1, 0, 0) => { + // If res is 𝒪, set res = g; otherwise, double res and add g + if res_is_infinity { + res.x = G_POINT256.x; + res.y = G_POINT256.y; + res_is_infinity = false; + } else { + syscall_secp256k1_dbl(&mut res); + res_is_infinity = secp256k1_add_non_infinity_points(&mut res, &G_POINT256); + } + + // Update r_rec + r_rec[i] |= 1 << j; + } + (1, 0, 1) => { + // If res is 𝒪, set res = g + q if not infinity; otherwise, double res and add (g + q) + if res_is_infinity { + if !gq_is_infinity { + res.x = gq.x; + res.y = gq.y; + res_is_infinity = false; + } + } else { + syscall_secp256k1_dbl(&mut res); + if !gq_is_infinity { + res_is_infinity = secp256k1_add_non_infinity_points(&mut res, &gq); + } + } + + // Update r_rec and t_rec + r_rec[i] |= 1 << j; + t_rec[i] |= 1 << j; + } + (1, 1, 0) => { + // If res is 𝒪, set res = g + p if not infinity + if res_is_infinity { + if !gp_is_infinity { + res.x = gp.x; + res.y = gp.y; + res_is_infinity = false; + } + } else { + syscall_secp256k1_dbl(&mut res); + if !gp_is_infinity { + res_is_infinity = secp256k1_add_non_infinity_points(&mut res, &gp); + } + } + + // Update r_rec and s_rec + r_rec[i] |= 1 << j; + s_rec[i] |= 1 << j; + } + (1, 1, 1) => { + // If res is 𝒪, set res = g + p + q if not infinity; otherwise, double res and add (g + p + q) + if res_is_infinity { + if !gpq_is_infinity { + res.x = gpq.x; + res.y = gpq.y; + res_is_infinity = false; + } + } else { + syscall_secp256k1_dbl(&mut res); + if !gpq_is_infinity { + res_is_infinity = secp256k1_add_non_infinity_points(&mut res, &gpq); + } + } + + // Update r_rec, s_rec and t_rec + r_rec[i] |= 1 << j; + s_rec[i] |= 1 << j; + t_rec[i] |= 1 << j; + } + _ => unreachable!(), + } + } + bit = 63; + } + + // Check that the recomposed scalars are the same as the received scalars + assert!(eq(&r_rec, r)); + assert!(eq(&s_rec, s)); + assert!(eq(&t_rec, t)); + + if res_is_infinity { + None + } else { + Some([res.x[0], res.x[1], res.x[2], res.x[3], res.y[0], res.y[1], res.y[2], res.y[3]]) + } } +// ==================== C FFI Functions ==================== + /// # Safety /// - `x_bytes_ptr` must point to 32 bytes (big-endian x-coordinate) -/// - `out_ptr` must point to at least 8 u64s (will write x[4] and y[4] in little-endian) +/// - `out_ptr` must point to at least 8 u64s /// /// Returns 1 on success, 0 if no valid point exists #[no_mangle] @@ -343,11 +645,10 @@ pub unsafe extern "C" fn secp256k1_decompress_c( ) -> u8 { let x_bytes: &[u8; 32] = &*(x_bytes_ptr as *const [u8; 32]); - let ((x, y), success) = secp256k1_decompress(x_bytes, y_is_odd != 0); - - if !success { - return 0; - } + let (x, y) = match secp256k1_decompress(x_bytes, y_is_odd != 0) { + Ok((x, y)) => (x, y), + Err(_) => return 0, + }; *out_ptr.add(0) = x[0]; *out_ptr.add(1) = x[1]; @@ -362,12 +663,29 @@ pub unsafe extern "C" fn secp256k1_decompress_c( } /// # Safety -/// - `k1_ptr` must point to 4 u64s (scalar k1) -/// - `k2_ptr` must point to 4 u64s (scalar k2) -/// - `p_ptr` must point to 8 u64s (point P: x[4], y[4]) -/// - `out_ptr` must point to at least 8 u64s (will write result x[4], y[4]) +/// - `p_ptr` must point to 12 u64s (projective point) +/// - `out_ptr` must point to at least 8 u64s +#[no_mangle] +pub unsafe extern "C" fn secp256k1_to_affine_c(p_ptr: *const u64, out_ptr: *mut u64) { + let p: &[u64; 12] = &*(p_ptr as *const [u64; 12]); + let result = secp256k1_to_affine(p); + + *out_ptr.add(0) = result[0]; + *out_ptr.add(1) = result[1]; + *out_ptr.add(2) = result[2]; + *out_ptr.add(3) = result[3]; + *out_ptr.add(4) = result[4]; + *out_ptr.add(5) = result[5]; + *out_ptr.add(6) = result[6]; + *out_ptr.add(7) = result[7]; +} + +/// # Safety +/// - `k1_ptr`, `k2_ptr` must point to 4 u64s each +/// - `p_ptr` must point to 8 u64s +/// - `out_ptr` must point to at least 8 u64s /// -/// Returns 1 if result is point at infinity, 0 otherwise +/// Returns true if result is point at infinity #[no_mangle] pub unsafe extern "C" fn secp256k1_double_scalar_mul_with_g_c( k1_ptr: *const u64, @@ -377,47 +695,20 @@ pub unsafe extern "C" fn secp256k1_double_scalar_mul_with_g_c( ) -> bool { let k1: &[u64; 4] = &*(k1_ptr as *const [u64; 4]); let k2: &[u64; 4] = &*(k2_ptr as *const [u64; 4]); - - let p = SyscallPoint256 { - x: [*p_ptr.add(0), *p_ptr.add(1), *p_ptr.add(2), *p_ptr.add(3)], - y: [*p_ptr.add(4), *p_ptr.add(5), *p_ptr.add(6), *p_ptr.add(7)], - }; - - let (is_infinity, res) = secp256k1_double_scalar_mul_with_g(k1, k2, &p); - - *out_ptr.add(0) = res.x[0]; - *out_ptr.add(1) = res.x[1]; - *out_ptr.add(2) = res.x[2]; - *out_ptr.add(3) = res.x[3]; - *out_ptr.add(4) = res.y[0]; - *out_ptr.add(5) = res.y[1]; - *out_ptr.add(6) = res.y[2]; - *out_ptr.add(7) = res.y[3]; - - is_infinity -} - -/// # Safety -/// - `pk_ptr` must point to 8 u64s (public key: x[4], y[4]) -/// - `z_ptr` must point to 4 u64s (message hash) -/// - `r_ptr` must point to 4 u64s (signature r) -/// - `s_ptr` must point to 4 u64s (signature s) -/// -/// Returns 1 if signature is valid, 0 otherwise -#[no_mangle] -pub unsafe extern "C" fn secp256k1_ecdsa_verify_c( - pk_ptr: *const u64, - z_ptr: *const u64, - r_ptr: *const u64, - s_ptr: *const u64, -) -> bool { - let pk = SyscallPoint256 { - x: [*pk_ptr.add(0), *pk_ptr.add(1), *pk_ptr.add(2), *pk_ptr.add(3)], - y: [*pk_ptr.add(4), *pk_ptr.add(5), *pk_ptr.add(6), *pk_ptr.add(7)], - }; - let z: &[u64; 4] = &*(z_ptr as *const [u64; 4]); - let r: &[u64; 4] = &*(r_ptr as *const [u64; 4]); - let s: &[u64; 4] = &*(s_ptr as *const [u64; 4]); - - secp256k1_ecdsa_verify(&pk, z, r, s) + let p: &[u64; 8] = &*(p_ptr as *const [u64; 8]); + + match secp256k1_double_scalar_mul_with_g(k1, k2, p) { + None => true, + Some(res) => { + *out_ptr.add(0) = res[0]; + *out_ptr.add(1) = res[1]; + *out_ptr.add(2) = res[2]; + *out_ptr.add(3) = res[3]; + *out_ptr.add(4) = res[4]; + *out_ptr.add(5) = res[5]; + *out_ptr.add(6) = res[6]; + *out_ptr.add(7) = res[7]; + false + } + } } diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs new file mode 100644 index 000000000..fe91fbeb2 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs @@ -0,0 +1,100 @@ +use crate::{ + syscalls::{ + syscall_secp256k1_add, syscall_secp256k1_dbl, SyscallPoint256, SyscallSecp256k1AddParams, + }, + zisklib::{eq, fcall_msb_pos_256, is_one, ONE_256, TWO_256, ZERO_256}, +}; + +use super::{ + constants::{E_B, G, G_X, G_Y, IDENTITY_X, IDENTITY_Y}, + field::{ + secp256k1_fp_add, secp256k1_fp_inv, secp256k1_fp_mul, secp256k1_fp_sqrt, + secp256k1_fp_square, + }, + scalar::{secp256k1_fn_inv, secp256k1_fn_mul, secp256k1_fn_reduce, secp256k1_fn_sub}, + secp256k1_decompress, secp256k1_double_scalar_mul_with_g, secp256k1_triple_scalar_mul_with_g, +}; + +pub fn secp256k1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u64; 4]) -> bool { + // Ecdsa verification computes (x1, y1) = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]pk + // and checks that r ≡ x1 (mod n) + + // TODO: We can equivalently hint y1 and verify that 𝒪 == [z]G + [r]pk + [-s](x1, y1) + // saving us from fn arithmetic entirely + + // The recovery algorithm computes pk = [-r⁻¹·z (mod n)]G + [r⁻¹·s (mod n)]R + // Equivalently, we can verify that [z]G + [r]pk + [-s]R == 𝒪 + let s_inv = secp256k1_fn_inv(s); + let u1 = secp256k1_fn_mul(z, &s_inv); + let u2 = secp256k1_fn_mul(r, &s_inv); + + match secp256k1_double_scalar_mul_with_g(&u1, &u2, pk) { + None => false, + Some(res) => eq(&secp256k1_fn_reduce(&[res[0], res[1], res[2], res[3]]), r), + } +} + +// ==================== C FFI Functions ==================== + +// TODO +// /// # Safety +// /// - `pk_ptr` must point to 64 bytes (public key: x[32] || y[32], big-endian) +// /// - `z_ptr` must point to 32 bytes (message hash, big-endian) +// /// - `r_ptr` must point to 32 bytes (signature r, big-endian) +// /// - `s_ptr` must point to 32 bytes (signature s, big-endian) +// /// +// /// Returns true if signature is valid, false otherwise +// #[no_mangle] +// pub unsafe extern "C" fn secp256k1_ecdsa_recover_c( +// h_ptr: *const u8, // Message hash +// r_ptr: *const u8, // Signature r +// s_ptr: *const u8, // Signature s +// rec_id: u8, // Recovery ID +// ) -> bool { +// // Helper to convert 32 big-endian bytes to [u64; 4] little-endian limbs +// #[inline] +// fn bytes_be_to_u64_le(bytes: *const u8) -> [u64; 4] { +// let mut result = [0u64; 4]; +// for i in 0..4 { +// let offset = 24 - i * 8; +// result[i] = unsafe { +// u64::from_be_bytes([ +// *bytes.add(offset), +// *bytes.add(offset + 1), +// *bytes.add(offset + 2), +// *bytes.add(offset + 3), +// *bytes.add(offset + 4), +// *bytes.add(offset + 5), +// *bytes.add(offset + 6), +// *bytes.add(offset + 7), +// ]) +// }; +// } +// result +// } + +// let h = bytes_be_to_u64_le(h_ptr); +// let r = bytes_be_to_u64_le(r_ptr); +// let s = bytes_be_to_u64_le(s_ptr); + +// // secp256k1_ecdsa_verify(&pk, &z, &r, &s) +// } + +/// # Safety +/// - `pk_ptr` must point to 8 u64s +/// - `z_ptr`, `r_ptr`, `s_ptr` must point to 4 u64s each +/// +/// Returns true if signature is valid +#[no_mangle] +pub unsafe extern "C" fn secp256k1_ecdsa_verify_c( + pk_ptr: *const u64, + z_ptr: *const u64, + r_ptr: *const u64, + s_ptr: *const u64, +) -> bool { + let pk: &[u64; 8] = &*(pk_ptr as *const [u64; 8]); + let z: &[u64; 4] = &*(z_ptr as *const [u64; 4]); + let r: &[u64; 4] = &*(r_ptr as *const [u64; 4]); + let s: &[u64; 4] = &*(s_ptr as *const [u64; 4]); + secp256k1_ecdsa_verify(pk, z, r, s) +} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs index 30c4afb6b..794d231fd 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs @@ -1,8 +1,10 @@ mod constants; mod curve; +mod ecdsa; mod field; mod scalar; pub use curve::*; +pub use ecdsa::*; pub use field::*; pub use scalar::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/utils.rs b/ziskos/entrypoint/src/zisklib/lib/utils.rs index 8927ebe98..759ca505d 100644 --- a/ziskos/entrypoint/src/zisklib/lib/utils.rs +++ b/ziskos/entrypoint/src/zisklib/lib/utils.rs @@ -37,3 +37,26 @@ pub fn eq(x: &[u64], y: &[u64]) -> bool { } true } + +/// Returns true if x == 0 +pub fn is_zero(x: &[u64]) -> bool { + for &word in x { + if word != 0 { + return false; + } + } + true +} + +/// Returns true if x == 1 +pub fn is_one(x: &[u64]) -> bool { + if x[0] != 1 { + return false; + } + for &word in &x[1..] { + if word != 0 { + return false; + } + } + true +} From eb835124a1293d1d4948056df2d2a1b07f9d40b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:27:32 +0000 Subject: [PATCH 116/782] Starting to fix sha endianess --- core/src/helpers.rs | 26 ++++--------------- .../sha256f/src/sha256f_gen_mem_inputs.rs | 6 ++--- ziskclib/src/helpers.rs | 26 ++++--------------- ziskclib/src/lib.rs | 4 --- 4 files changed, 13 insertions(+), 49 deletions(-) diff --git a/core/src/helpers.rs b/core/src/helpers.rs index cea9672af..6511b71f8 100644 --- a/core/src/helpers.rs +++ b/core/src/helpers.rs @@ -4,21 +4,13 @@ use sha2::digest::generic_array::{typenum::U64, GenericArray}; pub fn sha256f(state: &mut [u64; 4], input: &[u64; 8]) { // Convert both the state and the input to appropriate types - let mut state_u32: [u32; 8] = convert_u64_to_u32(state).try_into().unwrap(); - let block = convert_u64_to_generic_array_bytes(input); - compress256(&mut state_u32, &[block]); + let state_u32: &mut [u32; 8] = unsafe { &mut *(state.as_mut_ptr() as *mut [u32; 8]) }; + let input_u8 = convert_u64_to_generic_array_bytes(input); - // Convert the state back to u64 and write it to the memory address - *state = convert_u32_to_u64(&state_u32); -} + compress256(state_u32, &[input_u8]); -pub fn convert_u64_to_u32(input: &[u64]) -> Vec { - let mut out = Vec::with_capacity(input.len() * 2); - for &word in input { - out.push((word >> 32) as u32); - out.push((word & 0xFFFFFFFF) as u32); - } - out + // Convert the state back to u64 and write it to the memory address + *state = unsafe { *(state_u32 as *mut [u32; 8] as *mut [u64; 4]) }; } #[allow(deprecated)] @@ -31,11 +23,3 @@ pub fn convert_u64_to_generic_array_bytes(input: &[u64; 8]) -> GenericArray::clone_from_slice(&out) } - -pub fn convert_u32_to_u64(words: &[u32; 8]) -> [u64; 4] { - let mut out = [0u64; 4]; - for i in 0..4 { - out[i] = ((words[2 * i] as u64) << 32) | (words[2 * i + 1] as u64); - } - out -} diff --git a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs index 4b4f7c43e..e242775be 100644 --- a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs +++ b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs @@ -4,7 +4,7 @@ use precompiles_common::MemBusHelpers; use std::collections::VecDeque; use zisk_common::MemCollectorInfo; use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; -use zisk_core::{convert_u32_to_u64, convert_u64_to_generic_array_bytes, convert_u64_to_u32}; +use zisk_core::convert_u64_to_generic_array_bytes; #[derive(Debug)] pub struct Sha256MemInputConfig { @@ -28,11 +28,11 @@ pub fn generate_sha256f_mem_inputs( let input: &[u64; 8] = &data[10..18].try_into().unwrap(); // Apply the sha256f function and get the output - let mut state_u32: [u32; 8] = convert_u64_to_u32(state).try_into().unwrap(); + let mut state_u32: &mut [u32; 8] = unsafe { &mut *(state.as_mut_ptr() as *mut [u32; 8]) }; let block = convert_u64_to_generic_array_bytes(input); compress256(&mut state_u32, &[block]); - *state = convert_u32_to_u64(&state_u32); + *state = unsafe { *(state_u32 as *mut [u32; 8] as *mut [u64; 4]) }; // Generate the memory reads/writes let indirect_params = 2; diff --git a/ziskclib/src/helpers.rs b/ziskclib/src/helpers.rs index cea9672af..6511b71f8 100644 --- a/ziskclib/src/helpers.rs +++ b/ziskclib/src/helpers.rs @@ -4,21 +4,13 @@ use sha2::digest::generic_array::{typenum::U64, GenericArray}; pub fn sha256f(state: &mut [u64; 4], input: &[u64; 8]) { // Convert both the state and the input to appropriate types - let mut state_u32: [u32; 8] = convert_u64_to_u32(state).try_into().unwrap(); - let block = convert_u64_to_generic_array_bytes(input); - compress256(&mut state_u32, &[block]); + let state_u32: &mut [u32; 8] = unsafe { &mut *(state.as_mut_ptr() as *mut [u32; 8]) }; + let input_u8 = convert_u64_to_generic_array_bytes(input); - // Convert the state back to u64 and write it to the memory address - *state = convert_u32_to_u64(&state_u32); -} + compress256(state_u32, &[input_u8]); -pub fn convert_u64_to_u32(input: &[u64]) -> Vec { - let mut out = Vec::with_capacity(input.len() * 2); - for &word in input { - out.push((word >> 32) as u32); - out.push((word & 0xFFFFFFFF) as u32); - } - out + // Convert the state back to u64 and write it to the memory address + *state = unsafe { *(state_u32 as *mut [u32; 8] as *mut [u64; 4]) }; } #[allow(deprecated)] @@ -31,11 +23,3 @@ pub fn convert_u64_to_generic_array_bytes(input: &[u64; 8]) -> GenericArray::clone_from_slice(&out) } - -pub fn convert_u32_to_u64(words: &[u32; 8]) -> [u64; 4] { - let mut out = [0u64; 4]; - for i in 0..4 { - out[i] = ((words[2 * i] as u64) << 32) | (words[2 * i + 1] as u64); - } - out -} diff --git a/ziskclib/src/lib.rs b/ziskclib/src/lib.rs index c3ee53bf1..6722535ef 100644 --- a/ziskclib/src/lib.rs +++ b/ziskclib/src/lib.rs @@ -5,14 +5,10 @@ use helpers::sha256f; #[no_mangle] pub extern "C" fn zisk_keccakf(data: &mut [u64; 25]) { - //println!("zisk_keccakf() starting..."); keccakf(data); - //println!("zisk_keccakf() ...done"); } #[no_mangle] pub extern "C" fn zisk_sha256(state: &mut [u64; 4], input: &[u64; 8]) { - //println!("zisk_sha256f() starting..."); sha256f(state, input); - //println!("zisk_sha256f() ...done"); } From b29e2bb800892e910060fe6da5b2717eed80c0bd Mon Sep 17 00:00:00 2001 From: agnusmor Date: Fri, 19 Dec 2025 16:19:07 +0100 Subject: [PATCH 117/782] Update dependencies and branch references for release 0.15.0 --- Cargo.lock | 70 ++++++++++++++++++++++++--------------------- tools/test-env/.env | 6 ++-- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f67dc3b93..3cc406490 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -355,9 +355,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "bytes", @@ -638,9 +638,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.49" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -1040,7 +1040,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "fields", "num-bigint", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "num-bigint", "paste", @@ -2067,9 +2067,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "jiff" @@ -2440,9 +2440,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -2701,7 +2701,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "colored", "fields", @@ -2793,9 +2793,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" [[package]] name = "portable-atomic-util" @@ -3051,7 +3051,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "blake3", "borsh", @@ -3086,7 +3086,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "borsh", "colored", @@ -3117,7 +3117,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "fields", "proofman-common", @@ -3129,7 +3129,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "proc-macro2", "quote", @@ -3140,7 +3140,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "crossbeam-channel", "tracing", @@ -3149,7 +3149,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "colored", "fields", @@ -3160,7 +3160,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "bytemuck", "fields", @@ -3451,9 +3451,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.26" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", @@ -3602,9 +3602,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", @@ -3666,9 +3666,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "same-file" @@ -3762,16 +3762,16 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" dependencies = [ "indexmap", "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -4227,9 +4227,9 @@ checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", @@ -5410,7 +5410,7 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#844a7c2b38f28eefc28171d76a2f4dcce52216fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "colored", "fields", @@ -5835,6 +5835,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "zmij" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e404bcd8afdaf006e529269d3e85a743f9480c3cef60034d77860d02964f3ba" + [[package]] name = "zstd" version = "0.13.3" diff --git a/tools/test-env/.env b/tools/test-env/.env index 042e2dade..fd4f2f696 100644 --- a/tools/test-env/.env +++ b/tools/test-env/.env @@ -1,6 +1,6 @@ -ZISK_BRANCH=tags/v0.14.0 -PIL2_PROOFMAN_BRANCH=tags/v0.14.0 -PIL2_PROOFMAN_JS_BRANCH=tags/v0.14.0 +ZISK_BRANCH=tags/v0.15.0 +PIL2_PROOFMAN_BRANCH=tags/v0.15.0 +PIL2_PROOFMAN_JS_BRANCH=tags/v0.15.0 PIL2_COMPILER_BRANCH=tags/v0.8.0 ZISK_TESTVECTORS_BRANCH=main From 336af4a0c8fc0356f834a4c8fd9fd053fac8ebc1 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Wed, 24 Dec 2025 10:52:32 +0000 Subject: [PATCH 118/782] Adding Plonk and vkey command --- Cargo.lock | 1 + book/getting_started/installation.md | 4 +- cli/src/bin/cargo-zisk.rs | 13 +- cli/src/commands/check_setup.rs | 4 - cli/src/commands/common.rs | 12 + cli/src/commands/mod.rs | 4 + cli/src/commands/prove.rs | 2 + cli/src/commands/prove_snark.rs | 62 +++ cli/src/commands/rom_vkey.rs | 48 ++ executor/src/executor.rs | 31 +- rom-setup/Cargo.toml | 2 +- rom-setup/src/rom_full_setup.rs | 15 +- rom-setup/src/rom_vkey.rs | 67 +-- rom-setup/src/utils.rs | 8 + sdk/src/builder.rs | 49 +- sdk/src/prover/asm.rs | 3 + sdk/src/prover/emu.rs | 3 + sdk/src/utils.rs | 12 + state-machines/publics.json | 7 +- zisk-contracts/IZiskVerifier.sol | 17 + zisk-contracts/PlonkVerifier.sol | 737 +++++++++++++++++++++++++++ zisk-contracts/ZiskVerifier.sol | 56 ++ 22 files changed, 1073 insertions(+), 84 deletions(-) create mode 100644 cli/src/commands/prove_snark.rs create mode 100644 cli/src/commands/rom_vkey.rs create mode 100644 zisk-contracts/IZiskVerifier.sol create mode 100644 zisk-contracts/PlonkVerifier.sol create mode 100644 zisk-contracts/ZiskVerifier.sol diff --git a/Cargo.lock b/Cargo.lock index 3cc406490..7d6ff8bba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3536,6 +3536,7 @@ version = "0.16.0" dependencies = [ "anyhow", "blake3", + "bytemuck", "colored", "fields", "proofman-common", diff --git a/book/getting_started/installation.md b/book/getting_started/installation.md index 5c93bb43f..422a1a483 100644 --- a/book/getting_started/installation.md +++ b/book/getting_started/installation.md @@ -199,14 +199,14 @@ Please note that the process can be long, taking approximately 45-60 minutes dep 5. Compile ZisK PIL: ```bash - node ../pil2-compiler/src/pil.js pil/zisk.pil -I pil,../pil2-proofman/pil2-components/lib/std/pil,state-machines,precompiles -o pil/zisk.pilout -u tmp/fixed -O fixed-to-file + node --max-old-space-size=16384 ../pil2-compiler/src/pil.js pil/zisk.pil -I pil,../pil2-proofman/pil2-components/lib/std/pil,state-machines,precompiles -o pil/zisk.pilout -u tmp/fixed -O fixed-to-file ``` This command will create the `pil/zisk.pilout` file 6. Generate setup data: (this step may take 30-45 minutes): ```bash - node ../pil2-proofman-js/src/main_setup.js --stack-size=8192 -a ./pil/zisk.pilout -b build -t ../pil2-proofman/pil2-components/lib/std/pil -u tmp/fixed -r -s ./state-machines/starkstructs.json + node --max-old-space-size=16384 --stack-size=8192 ../pil2-proofman-js/src/main_setup.js --stack-size=8192 -a ./pil/zisk.pilout -b build -t ../pil2-proofman/pil2-components/lib/std/pil -u tmp/fixed -r -s ./state-machines/starkstructs.json -f -w ../powersOfTau28_hez_final_27.ptau -p ./state-machines/publics.json ``` This command generates the `build/provingKey` directory. diff --git a/cli/src/bin/cargo-zisk.rs b/cli/src/bin/cargo-zisk.rs index a242970c4..4dfae0c92 100644 --- a/cli/src/bin/cargo-zisk.rs +++ b/cli/src/bin/cargo-zisk.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Context, Result}; use cargo_zisk::commands::{ - ZiskBuild, ZiskCheckSetup, ZiskClean, ZiskExecute, ZiskProve, ZiskProveClient, ZiskRomSetup, - ZiskRun, ZiskSdk, ZiskServer, ZiskStats, ZiskVerify, ZiskVerifyConstraints, + ZiskBuild, ZiskCheckSetup, ZiskClean, ZiskExecute, ZiskProve, ZiskProveClient, ZiskProveSnark, + ZiskRomSetup, ZiskRomVkey, ZiskRun, ZiskSdk, ZiskServer, ZiskStats, ZiskVerify, + ZiskVerifyConstraints, }; use clap::Parser; use zisk_build::ZISK_VERSION_MESSAGE; @@ -22,7 +23,9 @@ pub enum Cargo { Execute(ZiskExecute), ProveClient(ZiskProveClient), Prove(ZiskProve), + ProveSnark(ZiskProveSnark), RomSetup(ZiskRomSetup), + RomVkey(ZiskRomVkey), Run(ZiskRun), Sdk(ZiskSdk), Server(ZiskServer), @@ -51,9 +54,15 @@ fn main() -> Result<()> { Cargo::Prove(mut cmd) => { cmd.run().context("Error executing Prove command")?; } + Cargo::ProveSnark(cmd) => { + cmd.run().context("Error executing ProveSnark command")?; + } Cargo::RomSetup(cmd) => { cmd.run().context("Error executing RomSetup command")?; } + Cargo::RomVkey(cmd) => { + cmd.run().context("Error executing RomVkey command")?; + } Cargo::Run(cmd) => { cmd.run().context("Error executing Run command")?; } diff --git a/cli/src/commands/check_setup.rs b/cli/src/commands/check_setup.rs index 5e63cf9fc..26cf8930b 100644 --- a/cli/src/commands/check_setup.rs +++ b/cli/src/commands/check_setup.rs @@ -21,9 +21,6 @@ pub struct ZiskCheckSetup { #[clap(short = 'a', long, default_value_t = false)] pub aggregation: bool, - #[clap(short = 'f', long, default_value_t = false)] - pub final_snark: bool, - /// Verbosity (-v, -vv) #[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity level")] pub verbose: u8, // Using u8 to hold the number of `-v` @@ -39,7 +36,6 @@ impl ZiskCheckSetup { ProofMan::::check_setup( get_proving_key(self.proving_key.as_ref()), self.aggregation, - self.final_snark, self.verbose.into(), ) .map_err(|e| anyhow::anyhow!("Error checking setup: {}", e))?; diff --git a/cli/src/commands/common.rs b/cli/src/commands/common.rs index 39bc75490..2e8fffbf6 100644 --- a/cli/src/commands/common.rs +++ b/cli/src/commands/common.rs @@ -20,6 +20,12 @@ pub fn get_default_proving_key() -> PathBuf { PathBuf::from(proving_key) } +/// Gets the default proving key file location in the home installation directory. +pub fn get_default_proving_key_snark() -> PathBuf { + let proving_key_snark = format!("{}/.zisk/provingKeySnark", get_home_dir()); + PathBuf::from(proving_key_snark) +} + /// Gets the default zisk folder location in the home installation directory. pub fn get_home_zisk_path() -> PathBuf { let zisk_path = format!("{}/.zisk", get_home_dir()); @@ -85,6 +91,12 @@ pub fn get_proving_key(proving_key: Option<&PathBuf>) -> PathBuf { proving_key.cloned().unwrap_or_else(get_default_proving_key) } +/// Gets the proving key snark file location. +/// Uses the default one if not specified by user. +pub fn get_proving_key_snark(proving_key_snark: Option<&PathBuf>) -> PathBuf { + proving_key_snark.cloned().unwrap_or_else(get_default_proving_key_snark) +} + /// Gets the zisk folder. /// Uses the default one if not specified by user. pub fn get_zisk_path(zisk_path: Option<&PathBuf>) -> PathBuf { diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index b1d7fac1b..7d01f4462 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -5,7 +5,9 @@ mod common; mod execute; mod prove; mod prove_client; +mod prove_snark; mod rom_setup; +mod rom_vkey; mod run; mod sdk; mod server; @@ -20,7 +22,9 @@ pub use common::*; pub use execute::*; pub use prove::*; pub use prove_client::*; +pub use prove_snark::*; pub use rom_setup::*; +pub use rom_vkey::*; pub use run::*; pub use sdk::*; pub use server::*; diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 5b3833832..47d7153ed 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -174,6 +174,7 @@ impl ZiskProve { .emu() .prove() .aggregation(self.aggregation) + .final_snark(self.final_snark) .rma(self.rma) .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) @@ -203,6 +204,7 @@ impl ZiskProve { .asm() .prove() .aggregation(self.aggregation) + .final_snark(self.final_snark) .rma(self.rma) .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) diff --git a/cli/src/commands/prove_snark.rs b/cli/src/commands/prove_snark.rs new file mode 100644 index 000000000..439be89cf --- /dev/null +++ b/cli/src/commands/prove_snark.rs @@ -0,0 +1,62 @@ +// extern crate env_logger; +use anyhow::Result; +use bytemuck::cast_slice; +use clap::Parser; +use colored::Colorize; +use fields::Goldilocks; +use std::io::Read; +use std::path::PathBuf; + +use crate::ux::print_banner; +use proofman::SnarkWrapper; +use std::fs::File; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +#[command(propagate_version = true)] +pub struct ZiskProveSnark { + #[clap(short = 'p', long)] + pub proof: String, + + /// Setup folder path + #[clap(short = 'k', long)] + pub proving_key_snark: PathBuf, + + /// Output dir path + #[clap(short = 'o', long, default_value = "tmp")] + pub output_dir: PathBuf, + + /// Verbosity (-v, -vv) + #[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity level")] + pub verbose: u8, // Using u8 to hold the number of `-v` + + #[clap(short = 'j', long, default_value_t = false)] + pub save_json: bool, +} + +impl ZiskProveSnark { + pub fn run(&self) -> Result<()> { + println!("{} ProveSnark", format!("{: >12}", "Command").bright_green().bold()); + println!(); + + print_banner(); + + let mut proof_file = File::open(&self.proof)?; + let mut proof_u64 = Vec::new(); + proof_file.read_to_end(&mut proof_u64)?; + let proof = cast_slice::(&proof_u64); + + let snark_wrapper: SnarkWrapper = + SnarkWrapper::new(&self.proving_key_snark, self.verbose.into())?; + + let snark_proof = + snark_wrapper.generate_final_snark_proof(proof, &self.output_dir, self.save_json)?; + println!( + "{} Final SNARK proof generated. Proof: {:?}, Publics: {:?}", + "Info:".bright_blue().bold(), + snark_proof.proof_bytes, + snark_proof.public_bytes + ); + Ok(()) + } +} diff --git a/cli/src/commands/rom_vkey.rs b/cli/src/commands/rom_vkey.rs new file mode 100644 index 000000000..39a3b302f --- /dev/null +++ b/cli/src/commands/rom_vkey.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use clap::Parser; +use std::path::PathBuf; + +use colored::Colorize; +use proofman_common::initialize_logger; + +use crate::{commands::get_proving_key, ux::print_banner}; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +#[command(propagate_version = true)] +pub struct ZiskRomVkey { + /// ELF file path + #[clap(short = 'e', long)] + pub elf: PathBuf, + + /// Setup folder path + #[clap(short = 'k', long)] + pub proving_key: Option, + + /// VKey file + #[clap(short = 'o', long)] + pub vkey_file: Option, + + #[clap(short = 'v', long, default_value_t = false)] + pub verbose: bool, +} + +impl ZiskRomVkey { + pub fn run(&self) -> Result<()> { + initialize_logger(proofman_common::VerboseMode::Info, None); + + tracing::info!( + "{}", + format!("{} Rom VKey", format!("{: >12}", "Command").bright_green().bold()) + ); + tracing::info!(""); + + print_banner(); + + let proving_key = get_proving_key(self.proving_key.as_ref()); + + rom_setup::rom_vkey(&self.elf, &self.vkey_file, &proving_key)?; + + Ok(()) + } +} diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 25601ea21..c0fe3dff3 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -25,10 +25,9 @@ use asm_runner::{ }; use fields::PrimeField64; use pil_std_lib::Std; -use proofman_common::{create_pool, BufferPool, ProofCtx, ProofmanError, ProofmanResult, SetupCtx}; +use proofman_common::{create_pool, BufferPool, ProofCtx, ProofmanResult, SetupCtx}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rayon::prelude::*; -use rom_setup::gen_elf_hash; use sm_rom::{RomInstance, RomSM}; use std::sync::atomic::{AtomicUsize, Ordering}; use witness::WitnessComponent; @@ -43,8 +42,8 @@ use zisk_common::{ }; use zisk_common::{ChunkId, PayloadType}; use zisk_pil::{ - RomRomTrace, ZiskPublicValues, INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, - ROM_DATA_AIR_IDS, ZISK_AIRGROUP_ID, + ZiskPublicValues, INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, + ZISK_AIRGROUP_ID, }; use std::thread::JoinHandle; @@ -1598,28 +1597,4 @@ impl WitnessComponent for ZiskExecutor { } Ok(()) } - - fn gen_custom_commits_fixed( - &self, - pctx: Arc>, - sctx: Arc>, - check: bool, - ) -> ProofmanResult<()> { - let file_name = pctx.get_custom_commits_fixed_buffer("rom", false)?; - - let setup = sctx.get_setup(RomRomTrace::::AIRGROUP_ID, RomRomTrace::::AIR_ID)?; - let blowup_factor = - 1 << (setup.stark_info.stark_struct.n_bits_ext - setup.stark_info.stark_struct.n_bits); - let arity = setup.stark_info.stark_struct.merkle_tree_arity; - - gen_elf_hash(&self.rom_path, file_name.as_path(), blowup_factor, arity, check).map_err( - |e| { - ProofmanError::ProofmanError(format!( - "Failed to generate custom commits fixed: {}", - e - )) - }, - )?; - Ok(()) - } } diff --git a/rom-setup/Cargo.toml b/rom-setup/Cargo.toml index 194b0fcfe..cbc095a77 100644 --- a/rom-setup/Cargo.toml +++ b/rom-setup/Cargo.toml @@ -17,7 +17,7 @@ fields = { workspace = true } proofman-common = { workspace = true } colored = { workspace = true } anyhow = { workspace = true } - +bytemuck = { workspace = true } blake3 = "1.3.1" [features] diff --git a/rom-setup/src/rom_full_setup.rs b/rom-setup/src/rom_full_setup.rs index 95d5c963e..727c26903 100644 --- a/rom-setup/src/rom_full_setup.rs +++ b/rom-setup/src/rom_full_setup.rs @@ -5,7 +5,7 @@ use std::{ use colored::Colorize; -use crate::{get_elf_data_hash, DEFAULT_CACHE_PATH}; +use crate::{ensure_dir_exists, get_elf_data_hash, DEFAULT_CACHE_PATH}; #[allow(unused_variables)] pub fn rom_full_setup( @@ -40,14 +40,11 @@ pub fn rom_full_setup( tracing::info!("Computing merkle root"); crate::rom_merkle_setup(elf, &elf_hash, output_path.as_path(), proving_key, false)?; - - tracing::info!("Computing Verification key"); - crate::rom_vkey()?; - // Assembly setup is not needed on macOS due to the lack of support for assembly generation. #[cfg(not(target_os = "macos"))] { tracing::info!("Computing assembly setup"); + crate::generate_assembly(elf, &elf_hash, zisk_path, output_path.as_path(), verbose)?; } @@ -60,11 +57,3 @@ pub fn rom_full_setup( Ok(()) } - -fn ensure_dir_exists(path: &PathBuf) { - if let Err(e) = std::fs::create_dir_all(path) { - if e.kind() != std::io::ErrorKind::AlreadyExists { - panic!("Failed to create cache directory {path:?}: {e}"); - } - } -} diff --git a/rom-setup/src/rom_vkey.rs b/rom-setup/src/rom_vkey.rs index aee4e6f9a..68476cd38 100644 --- a/rom-setup/src/rom_vkey.rs +++ b/rom-setup/src/rom_vkey.rs @@ -1,31 +1,38 @@ -// use std::path::Path; - -// use tracing::info; - -// use crate::{gen_elf_hash, get_elf_bin_file_path, get_rom_blowup_factor}; - -pub fn rom_vkey(// elf: &Path, - // output_path: &Path, - // proving_key: &Path, - // mut check: bool, -) -> Result<(), anyhow::Error> { - // // Check if the path is a file and not a directory - // if !elf.is_file() { - // log::error!("Error: The specified ROM path is not a file: {}", elf.display()); - // std::process::exit(1); - // } - - // let blowup_factor = get_rom_blowup_factor(proving_key); - - // let elf_bin_path = get_elf_bin_file_path(elf, output_path, blowup_factor)?; - - // if !elf_bin_path.exists() { - // check = false; - // } - - // let root = gen_elf_hash(elf, elf_bin_path.as_path(), blowup_factor, check)?; - - // info!("Root hash: {:?}", root); - - Ok(()) +use std::path::{Path, PathBuf}; + +use crate::{gen_elf_hash, get_rom_blowup_factor_and_arity}; +use fields::{Goldilocks, PrimeField}; +use std::fs; +use std::fs::File; +use std::io::Write; + +pub fn rom_vkey( + elf: &Path, + verkey_file: &Option, + proving_key: &Path, +) -> Result, anyhow::Error> { + // Check if the path is a file and not a directory + if !elf.is_file() { + tracing::error!("Error: The specified ROM path is not a file: {}", elf.display()); + std::process::exit(1); + } + + let (blowup_factor, merkle_tree_arity) = get_rom_blowup_factor_and_arity(proving_key); + + let root = gen_elf_hash(elf, &PathBuf::new(), blowup_factor, merkle_tree_arity, false)?; + + let verkey: Vec = + root.iter().flat_map(|x| x.as_canonical_biguint().to_bytes_le()).collect(); + + if let Some(verkey_file) = verkey_file { + let parent = Path::new(&verkey_file).parent().unwrap(); + fs::create_dir_all(parent)?; + let mut file = File::create(verkey_file)?; + file.write_all(&verkey)?; + file.flush()?; + } + + tracing::info!("Root hash: {:?}", root); + + Ok(root) } diff --git a/rom-setup/src/utils.rs b/rom-setup/src/utils.rs index 2a2676d33..42cf0fd17 100644 --- a/rom-setup/src/utils.rs +++ b/rom-setup/src/utils.rs @@ -103,3 +103,11 @@ pub fn get_rom_blowup_factor_and_arity(proving_key_path: &Path) -> (u64, u64) { stark_info.stark_struct.merkle_tree_arity, ) } + +pub fn ensure_dir_exists(path: &PathBuf) { + if let Err(e) = std::fs::create_dir_all(path) { + if e.kind() != std::io::ErrorKind::AlreadyExists { + panic!("Failed to create cache directory {path:?}: {e}"); + } + } +} diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 0734b0505..f25a31be0 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use crate::{ - get_asm_paths, get_proving_key, get_witness_computation_lib, + get_asm_paths, get_proving_key, get_proving_key_snark, get_witness_computation_lib, prover::{Asm, AsmProver, Emu, EmuProver, ZiskProver}, }; use colored::Colorize; @@ -54,6 +54,7 @@ pub struct ProverClientBuilder { final_snark: bool, witness_lib: Option, proving_key: Option, + proving_key_snark: Option, elf: Option, verify_constraints: bool, witness: bool, @@ -173,6 +174,18 @@ impl ProverClientBuilder { self } + #[must_use] + pub fn proving_key_snark_path(mut self, proving_key_snark: PathBuf) -> Self { + self.proving_key_snark = Some(proving_key_snark); + self + } + + #[must_use] + pub fn proving_key_snark_path_opt(mut self, proving_key_snark: Option) -> Self { + self.proving_key_snark = proving_key_snark; + self + } + #[must_use] pub fn elf_path(mut self, elf_path: PathBuf) -> Self { self.elf = Some(elf_path); @@ -315,6 +328,10 @@ impl ProverClientBuilder { fn build_emu(self) -> Result> { let witness_lib = get_witness_computation_lib(self.witness_lib.as_ref()); let proving_key = get_proving_key(self.proving_key.as_ref()); + let proving_key_snark = match self.final_snark { + true => Some(get_proving_key_snark(self.proving_key_snark.as_ref())), + false => None, + }; let elf = self.elf.ok_or_else(|| anyhow::anyhow!("ELF path is required"))?; let output_dir = if !self.verify_constraints { @@ -329,6 +346,7 @@ impl ProverClientBuilder { self.verify_constraints, &witness_lib, &proving_key, + &proving_key_snark, &elf, output_dir.as_ref(), ); @@ -341,6 +359,7 @@ impl ProverClientBuilder { self.final_snark, witness_lib, proving_key, + proving_key_snark, elf, self.verbose, self.shared_tables, @@ -360,6 +379,7 @@ impl ProverClientBuilder { verify_constraints: bool, witness_lib: &Path, proving_key: &Path, + proving_key_snark: &Option, elf: &Path, output_dir: Option<&PathBuf>, ) { @@ -380,6 +400,14 @@ impl ProverClientBuilder { ); println!("{: >12} {}", "Proving key".bright_green().bold(), proving_key.display()); + if let Some(proving_key_snark) = proving_key_snark { + println!( + "{: >12} {}", + "Proving key SNARK".bright_green().bold(), + proving_key_snark.display() + ); + } + if let Some(output_dir) = output_dir { println!("{: >12} {}", "Output Dir".bright_green().bold(), output_dir.display()); } @@ -445,6 +473,10 @@ impl ProverClientBuilder { { let witness_lib = get_witness_computation_lib(self.witness_lib.as_ref()); let proving_key = get_proving_key(self.proving_key.as_ref()); + let proving_key_snark = match self.final_snark { + true => Some(get_proving_key_snark(self.proving_key_snark.as_ref())), + false => None, + }; let elf = self.elf.ok_or_else(|| anyhow::anyhow!("ELF path is required"))?; let output_dir = if !self.verify_constraints { @@ -461,6 +493,7 @@ impl ProverClientBuilder { self.verify_constraints, &witness_lib, &proving_key, + &proving_key_snark, &elf, output_dir.as_ref(), ); @@ -473,6 +506,7 @@ impl ProverClientBuilder { self.final_snark, witness_lib, proving_key, + proving_key_snark, elf, self.verbose, self.shared_tables, @@ -496,6 +530,7 @@ impl ProverClientBuilder { verify_constraints: bool, witness_lib: &Path, proving_key: &Path, + proving_key_snark: &Option, elf: &Path, output_dir: Option<&PathBuf>, ) { @@ -511,6 +546,14 @@ impl ProverClientBuilder { println!("{: >12} {}", "Elf".bright_green().bold(), elf.display()); println!("{: >12} {}", "Proving key".bright_green().bold(), proving_key.display()); + if let Some(proving_key_snark) = proving_key_snark { + println!( + "{: >12} {}", + "Proving key SNARK".bright_green().bold(), + proving_key_snark.display() + ); + } + if let Some(output_dir) = output_dir { println!("{: >12} {}", "Output Dir".bright_green().bold(), output_dir.display()); } @@ -530,6 +573,7 @@ impl From> for ProverClientBuilder { final_snark: builder.final_snark, witness_lib: builder.witness_lib, proving_key: builder.proving_key, + proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, elf: builder.elf, verbose: builder.verbose, @@ -565,6 +609,7 @@ impl From> for ProverClientBuilder { final_snark: builder.final_snark, witness_lib: builder.witness_lib, proving_key: builder.proving_key, + proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, elf: builder.elf, verbose: builder.verbose, @@ -602,6 +647,7 @@ impl From> final_snark: builder.final_snark, witness_lib: builder.witness_lib, proving_key: builder.proving_key, + proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, elf: builder.elf, verbose: builder.verbose, @@ -637,6 +683,7 @@ impl From> for ProverClientBuilder, elf: PathBuf, verbose: u8, shared_tables: bool, @@ -57,6 +58,7 @@ impl AsmProver { final_snark, witness_lib, proving_key, + proving_key_snark, elf, verbose, shared_tables, @@ -186,6 +188,7 @@ impl AsmCoreProver { final_snark: bool, witness_lib: PathBuf, proving_key: PathBuf, + _proving_key_snark: Option, elf: PathBuf, verbose: u8, shared_tables: bool, diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index e49e69d2f..b2207fdc1 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -32,6 +32,7 @@ impl EmuProver { final_snark: bool, witness_lib: PathBuf, proving_key: PathBuf, + proving_key_snark: Option, elf: PathBuf, verbose: u8, shared_tables: bool, @@ -49,6 +50,7 @@ impl EmuProver { final_snark, witness_lib, proving_key, + proving_key_snark, elf, verbose, shared_tables, @@ -159,6 +161,7 @@ impl EmuCoreProver { final_snark: bool, witness_lib: PathBuf, proving_key: PathBuf, + _proving_key_snark: Option, elf: PathBuf, verbose: u8, shared_tables: bool, diff --git a/sdk/src/utils.rs b/sdk/src/utils.rs index f3acf7c70..629e9b1f6 100644 --- a/sdk/src/utils.rs +++ b/sdk/src/utils.rs @@ -29,6 +29,12 @@ pub fn get_default_proving_key() -> PathBuf { PathBuf::from(proving_key) } +/// Gets the default proving key file location in the home installation directory. +pub fn get_default_proving_key_snark() -> PathBuf { + let proving_key_snark = format!("{}/.zisk/provingKeySnark", get_home_dir()); + PathBuf::from(proving_key_snark) +} + /// Gets the default zisk folder location in the home installation directory. pub fn get_home_zisk_path() -> PathBuf { let zisk_path = format!("{}/.zisk", get_home_dir()); @@ -94,6 +100,12 @@ pub fn get_proving_key(proving_key: Option<&PathBuf>) -> PathBuf { proving_key.cloned().unwrap_or_else(get_default_proving_key) } +/// Gets the proving key file location. +/// Uses the default one if not specified by user. +pub fn get_proving_key_snark(proving_key_snark: Option<&PathBuf>) -> PathBuf { + proving_key_snark.cloned().unwrap_or_else(get_default_proving_key_snark) +} + /// Gets the zisk folder. /// Uses the default one if not specified by user. pub fn get_zisk_path(zisk_path: Option<&PathBuf>) -> PathBuf { diff --git a/state-machines/publics.json b/state-machines/publics.json index 9e12a690d..3f4911c6e 100644 --- a/state-machines/publics.json +++ b/state-machines/publics.json @@ -1,7 +1,8 @@ { "nPublics": 68, "definitions": [ - { "name": "rom_root", "initialPos": 0, "chunks": [4, 64] }, - { "name": "inputs", "initialPos": 4, "chunks": [64, 32] } - ] + { "name": "rom_root", "initialPos": 0, "nValues": 4, "chunks": [1, 64], "verificationKey": true }, + { "name": "inputs", "initialPos": 4, "nValues": 64, "chunks": [1, 32] } + ], + "hasProgramVK": true } \ No newline at end of file diff --git a/zisk-contracts/IZiskVerifier.sol b/zisk-contracts/IZiskVerifier.sol new file mode 100644 index 000000000..ec49975de --- /dev/null +++ b/zisk-contracts/IZiskVerifier.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.20; + +/// @title Zisk Verifier Interface +/// @author SilentSig +/// @notice This contract is the interface for the Zisk Verifier. +interface IZiskVerifier { + /// @notice Verifies a proof with given public values and vkey. + /// @param programVK The verification key for the RISC-V program. + /// @param publicValues The public values encoded as bytes. + /// @param proofBytes The proof of the program execution the Zisk zkVM encoded as bytes. + function verifySnarkProof( + uint64[4] calldata programVK, + bytes calldata publicValues, + bytes calldata proofBytes + ) external view; +} \ No newline at end of file diff --git a/zisk-contracts/PlonkVerifier.sol b/zisk-contracts/PlonkVerifier.sol new file mode 100644 index 000000000..8dfdfced3 --- /dev/null +++ b/zisk-contracts/PlonkVerifier.sol @@ -0,0 +1,737 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + + +pragma solidity >=0.7.0 <0.9.0; + +contract PlonkVerifier { + // Omega + uint256 constant w1 = 5709868443893258075976348696661355716898495876243883251619397131511003808859; + // Scalar field size + uint256 constant q = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant qf = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // [1]_1 + uint256 constant G1x = 1; + uint256 constant G1y = 2; + // [1]_2 + uint256 constant G2x1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant G2x2 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant G2y1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant G2y2 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + + // Verification Key data + uint32 constant n = 16777216; + uint16 constant nPublic = 1; + uint16 constant nLagrange = 1; + + uint256 constant Qmx = 572058585897115413933308198688680980455081513448360206617674822477027097352; + uint256 constant Qmy = 19520695395476905182400417552180799699716537403076661072544440793833663810330; + uint256 constant Qlx = 9832908426454593805012267200857420955232427230162451920383591798177680906842; + uint256 constant Qly = 6134887858043114941104727813721017175936417585428553547982984062309748590067; + uint256 constant Qrx = 17937669991980819350134444648322386596504213518209816889288337561956505273579; + uint256 constant Qry = 17380198614013544832408943480535610993360041386943658331661438058754652423608; + uint256 constant Qox = 3746383519605994996570654909729447538060398177432538027019038210460830835996; + uint256 constant Qoy = 1024676947590770618963044613714674542516450089379999729241895272070434668163; + uint256 constant Qcx = 19818475363986446083386548432203783753847786675575871159724364841872033339811; + uint256 constant Qcy = 5734908757991782594800656602635866231598638594033863174601859476090728718317; + uint256 constant S1x = 3813951475049395353035144087536163985766398632609172098517655090832064479201; + uint256 constant S1y = 4661041460539871222837848184860991236413816796875875991414335208951982883573; + uint256 constant S2x = 2346297236765228201735212218580864671321819719934943664794817856462628147766; + uint256 constant S2y = 7675333666783282678195557739194219149780835629320882753298243738718972627161; + uint256 constant S3x = 11920762370636635928452991956851650930207813096445461979758461102591469998444; + uint256 constant S3y = 13252580429342734662311430952976723420942336108640176798978862832832900350015; + uint256 constant k1 = 2; + uint256 constant k2 = 3; + uint256 constant X2x1 = 21831381940315734285607113342023901060522397560371972897001948545212302161822; + uint256 constant X2x2 = 17231025384763736816414546592865244497437017442647097510447326538965263639101; + uint256 constant X2y1 = 2388026358213174446665280700919698872609886601280537296205114254867301080648; + uint256 constant X2y2 = 11507326595632554467052522095592665270651932854513688777769618397986436103170; + + // Proof calldata + // Byte offset of every parameter of the calldata + // Polynomial commitments + uint16 constant pA = 4 + 0; + uint16 constant pB = 4 + 64; + uint16 constant pC = 4 + 128; + uint16 constant pZ = 4 + 192; + uint16 constant pT1 = 4 + 256; + uint16 constant pT2 = 4 + 320; + uint16 constant pT3 = 4 + 384; + uint16 constant pWxi = 4 + 448; + uint16 constant pWxiw = 4 + 512; + // Opening evaluations + uint16 constant pEval_a = 4 + 576; + uint16 constant pEval_b = 4 + 608; + uint16 constant pEval_c = 4 + 640; + uint16 constant pEval_s1 = 4 + 672; + uint16 constant pEval_s2 = 4 + 704; + uint16 constant pEval_zw = 4 + 736; + + // Memory data + // Challenges + uint16 constant pAlpha = 0; + uint16 constant pBeta = 32; + uint16 constant pGamma = 64; + uint16 constant pXi = 96; + uint16 constant pXin = 128; + uint16 constant pBetaXi = 160; + uint16 constant pV1 = 192; + uint16 constant pV2 = 224; + uint16 constant pV3 = 256; + uint16 constant pV4 = 288; + uint16 constant pV5 = 320; + uint16 constant pU = 352; + + uint16 constant pPI = 384; + uint16 constant pEval_r0 = 416; + uint16 constant pD = 448; + uint16 constant pF = 512; + uint16 constant pE = 576; + uint16 constant pTmp = 640; + uint16 constant pAlpha2 = 704; + uint16 constant pZh = 736; + uint16 constant pZhInv = 768; + + + uint16 constant pEval_l1 = 800; + + + + uint16 constant lastMem = 832; + + function verifyProof(uint256[24] calldata _proof, uint256[1] calldata _pubSignals) public view returns (bool) { + assembly { + ///////// + // Computes the inverse using the extended euclidean algorithm + ///////// + function inverse(a, q) -> inv { + let t := 0 + let newt := 1 + let r := q + let newr := a + let quotient + let aux + + for { } newr { } { + quotient := sdiv(r, newr) + aux := sub(t, mul(quotient, newt)) + t:= newt + newt:= aux + + aux := sub(r,mul(quotient, newr)) + r := newr + newr := aux + } + + if gt(r, 1) { revert(0,0) } + if slt(t, 0) { t:= add(t, q) } + + inv := t + } + + /////// + // Computes the inverse of an array of values + // See https://vitalik.ca/general/2018/07/21/starks_part_3.html in section where explain fields operations + ////// + function inverseArray(pVals, n) { + + let pAux := mload(0x40) // Point to the next free position + let pIn := pVals + let lastPIn := add(pVals, mul(n, 32)) // Read n elements + let acc := mload(pIn) // Read the first element + pIn := add(pIn, 32) // Point to the second element + let inv + + + for { } lt(pIn, lastPIn) { + pAux := add(pAux, 32) + pIn := add(pIn, 32) + } + { + mstore(pAux, acc) + acc := mulmod(acc, mload(pIn), q) + } + acc := inverse(acc, q) + + // At this point pAux pint to the next free position we subtract 1 to point to the last used + pAux := sub(pAux, 32) + // pIn points to the n+1 element, we subtract to point to n + pIn := sub(pIn, 32) + lastPIn := pVals // We don't process the first element + for { } gt(pIn, lastPIn) { + pAux := sub(pAux, 32) + pIn := sub(pIn, 32) + } + { + inv := mulmod(acc, mload(pAux), q) + acc := mulmod(acc, mload(pIn), q) + mstore(pIn, inv) + } + // pIn points to first element, we just set it. + mstore(pIn, acc) + } + + function checkField(v) { + if iszero(lt(v, q)) { + mstore(0, 0) + return(0,0x20) + } + } + + function checkInput() { + checkField(calldataload(pEval_a)) + checkField(calldataload(pEval_b)) + checkField(calldataload(pEval_c)) + checkField(calldataload(pEval_s1)) + checkField(calldataload(pEval_s2)) + checkField(calldataload(pEval_zw)) + } + + function calculateChallenges(pMem, pPublic) { + let beta + let aux + + let mIn := mload(0x40) // Pointer to the next free memory position + + // Compute challenge.beta & challenge.gamma + mstore(mIn, Qmx) + mstore(add(mIn, 32), Qmy) + mstore(add(mIn, 64), Qlx) + mstore(add(mIn, 96), Qly) + mstore(add(mIn, 128), Qrx) + mstore(add(mIn, 160), Qry) + mstore(add(mIn, 192), Qox) + mstore(add(mIn, 224), Qoy) + mstore(add(mIn, 256), Qcx) + mstore(add(mIn, 288), Qcy) + mstore(add(mIn, 320), S1x) + mstore(add(mIn, 352), S1y) + mstore(add(mIn, 384), S2x) + mstore(add(mIn, 416), S2y) + mstore(add(mIn, 448), S3x) + mstore(add(mIn, 480), S3y) + + + mstore(add(mIn, 512), calldataload(add(pPublic, 0))) + + mstore(add(mIn, 544 ), calldataload(pA)) + mstore(add(mIn, 576 ), calldataload(add(pA, 32))) + mstore(add(mIn, 608 ), calldataload(pB)) + mstore(add(mIn, 640 ), calldataload(add(pB, 32))) + mstore(add(mIn, 672 ), calldataload(pC)) + mstore(add(mIn, 704 ), calldataload(add(pC, 32))) + + beta := mod(keccak256(mIn, 736), q) + mstore(add(pMem, pBeta), beta) + + // challenges.gamma + mstore(add(pMem, pGamma), mod(keccak256(add(pMem, pBeta), 32), q)) + + // challenges.alpha + mstore(mIn, mload(add(pMem, pBeta))) + mstore(add(mIn, 32), mload(add(pMem, pGamma))) + mstore(add(mIn, 64), calldataload(pZ)) + mstore(add(mIn, 96), calldataload(add(pZ, 32))) + + aux := mod(keccak256(mIn, 128), q) + mstore(add(pMem, pAlpha), aux) + mstore(add(pMem, pAlpha2), mulmod(aux, aux, q)) + + // challenges.xi + mstore(mIn, aux) + mstore(add(mIn, 32), calldataload(pT1)) + mstore(add(mIn, 64), calldataload(add(pT1, 32))) + mstore(add(mIn, 96), calldataload(pT2)) + mstore(add(mIn, 128), calldataload(add(pT2, 32))) + mstore(add(mIn, 160), calldataload(pT3)) + mstore(add(mIn, 192), calldataload(add(pT3, 32))) + + aux := mod(keccak256(mIn, 224), q) + mstore( add(pMem, pXi), aux) + + // challenges.v + mstore(mIn, aux) + mstore(add(mIn, 32), calldataload(pEval_a)) + mstore(add(mIn, 64), calldataload(pEval_b)) + mstore(add(mIn, 96), calldataload(pEval_c)) + mstore(add(mIn, 128), calldataload(pEval_s1)) + mstore(add(mIn, 160), calldataload(pEval_s2)) + mstore(add(mIn, 192), calldataload(pEval_zw)) + + let v1 := mod(keccak256(mIn, 224), q) + mstore(add(pMem, pV1), v1) + + // challenges.beta * challenges.xi + mstore(add(pMem, pBetaXi), mulmod(beta, aux, q)) + + // challenges.xi^n + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + mstore(add(pMem, pXin), aux) + + // Zh + aux:= mod(add(sub(aux, 1), q), q) + mstore(add(pMem, pZh), aux) + mstore(add(pMem, pZhInv), aux) // We will invert later together with lagrange pols + + // challenges.v^2, challenges.v^3, challenges.v^4, challenges.v^5 + aux := mulmod(v1, v1, q) + mstore(add(pMem, pV2), aux) + aux := mulmod(aux, v1, q) + mstore(add(pMem, pV3), aux) + aux := mulmod(aux, v1, q) + mstore(add(pMem, pV4), aux) + aux := mulmod(aux, v1, q) + mstore(add(pMem, pV5), aux) + + // challenges.u + mstore(mIn, calldataload(pWxi)) + mstore(add(mIn, 32), calldataload(add(pWxi, 32))) + mstore(add(mIn, 64), calldataload(pWxiw)) + mstore(add(mIn, 96), calldataload(add(pWxiw, 32))) + + mstore(add(pMem, pU), mod(keccak256(mIn, 128), q)) + } + + function calculateLagrange(pMem) { + let w := 1 + + mstore( + add(pMem, pEval_l1), + mulmod( + n, + mod( + add( + sub( + mload(add(pMem, pXi)), + w + ), + q + ), + q + ), + q + ) + ) + + + + inverseArray(add(pMem, pZhInv), 2 ) + + let zh := mload(add(pMem, pZh)) + w := 1 + + + mstore( + add(pMem, pEval_l1 ), + mulmod( + mload(add(pMem, pEval_l1 )), + zh, + q + ) + ) + + + + + + } + + function calculatePI(pMem, pPub) { + let pl := 0 + + + pl := mod( + add( + sub( + pl, + mulmod( + mload(add(pMem, pEval_l1)), + calldataload(add(pPub, 0)), + q + ) + ), + q + ), + q + ) + + + mstore(add(pMem, pPI), pl) + } + + function calculateR0(pMem) { + let e1 := mload(add(pMem, pPI)) + + let e2 := mulmod(mload(add(pMem, pEval_l1)), mload(add(pMem, pAlpha2)), q) + + let e3a := addmod( + calldataload(pEval_a), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s1), q), + q) + e3a := addmod(e3a, mload(add(pMem, pGamma)), q) + + let e3b := addmod( + calldataload(pEval_b), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s2), q), + q) + e3b := addmod(e3b, mload(add(pMem, pGamma)), q) + + let e3c := addmod( + calldataload(pEval_c), + mload(add(pMem, pGamma)), + q) + + let e3 := mulmod(mulmod(e3a, e3b, q), e3c, q) + e3 := mulmod(e3, calldataload(pEval_zw), q) + e3 := mulmod(e3, mload(add(pMem, pAlpha)), q) + + let r0 := addmod(e1, mod(sub(q, e2), q), q) + r0 := addmod(r0, mod(sub(q, e3), q), q) + + mstore(add(pMem, pEval_r0) , r0) + } + + function g1_set(pR, pP) { + mstore(pR, mload(pP)) + mstore(add(pR, 32), mload(add(pP,32))) + } + + function g1_setC(pR, x, y) { + mstore(pR, x) + mstore(add(pR, 32), y) + } + + function g1_calldataSet(pR, pP) { + mstore(pR, calldataload(pP)) + mstore(add(pR, 32), calldataload(add(pP, 32))) + } + + function g1_acc(pR, pP) { + let mIn := mload(0x40) + mstore(mIn, mload(pR)) + mstore(add(mIn,32), mload(add(pR, 32))) + mstore(add(mIn,64), mload(pP)) + mstore(add(mIn,96), mload(add(pP, 32))) + + let success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + } + + function g1_mulAcc(pR, pP, s) { + let success + let mIn := mload(0x40) + mstore(mIn, mload(pP)) + mstore(add(mIn,32), mload(add(pP, 32))) + mstore(add(mIn,64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + + mstore(add(mIn,64), mload(pR)) + mstore(add(mIn,96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + + } + + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn,32), y) + mstore(add(mIn,64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + + mstore(add(mIn,64), mload(pR)) + mstore(add(mIn,96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + } + + function g1_mulSetC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn,32), y) + mstore(add(mIn,64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + } + + function g1_mulSet(pR, pP, s) { + g1_mulSetC(pR, mload(pP), mload(add(pP, 32)), s) + } + + function calculateD(pMem) { + let _pD:= add(pMem, pD) + let gamma := mload(add(pMem, pGamma)) + let mIn := mload(0x40) + mstore(0x40, add(mIn, 256)) // d1, d2, d3 & d4 (4*64 bytes) + + g1_setC(_pD, Qcx, Qcy) + g1_mulAccC(_pD, Qmx, Qmy, mulmod(calldataload(pEval_a), calldataload(pEval_b), q)) + g1_mulAccC(_pD, Qlx, Qly, calldataload(pEval_a)) + g1_mulAccC(_pD, Qrx, Qry, calldataload(pEval_b)) + g1_mulAccC(_pD, Qox, Qoy, calldataload(pEval_c)) + + let betaxi := mload(add(pMem, pBetaXi)) + let val1 := addmod( + addmod(calldataload(pEval_a), betaxi, q), + gamma, q) + + let val2 := addmod( + addmod( + calldataload(pEval_b), + mulmod(betaxi, k1, q), + q), gamma, q) + + let val3 := addmod( + addmod( + calldataload(pEval_c), + mulmod(betaxi, k2, q), + q), gamma, q) + + let d2a := mulmod( + mulmod(mulmod(val1, val2, q), val3, q), + mload(add(pMem, pAlpha)), + q + ) + + let d2b := mulmod( + mload(add(pMem, pEval_l1)), + mload(add(pMem, pAlpha2)), + q + ) + + // We'll use mIn to save d2 + g1_calldataSet(add(mIn, 192), pZ) + g1_mulSet( + mIn, + add(mIn, 192), + addmod(addmod(d2a, d2b, q), mload(add(pMem, pU)), q)) + + + val1 := addmod( + addmod( + calldataload(pEval_a), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s1), q), + q), gamma, q) + + val2 := addmod( + addmod( + calldataload(pEval_b), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s2), q), + q), gamma, q) + + val3 := mulmod( + mulmod(mload(add(pMem, pAlpha)), mload(add(pMem, pBeta)), q), + calldataload(pEval_zw), q) + + + // We'll use mIn + 64 to save d3 + g1_mulSetC( + add(mIn, 64), + S3x, + S3y, + mulmod(mulmod(val1, val2, q), val3, q)) + + // We'll use mIn + 128 to save d4 + g1_calldataSet(add(mIn, 128), pT1) + + g1_mulAccC(add(mIn, 128), calldataload(pT2), calldataload(add(pT2, 32)), mload(add(pMem, pXin))) + let xin2 := mulmod(mload(add(pMem, pXin)), mload(add(pMem, pXin)), q) + g1_mulAccC(add(mIn, 128), calldataload(pT3), calldataload(add(pT3, 32)) , xin2) + + g1_mulSetC(add(mIn, 128), mload(add(mIn, 128)), mload(add(mIn, 160)), mload(add(pMem, pZh))) + + mstore(add(add(mIn, 64), 32), mod(sub(qf, mload(add(add(mIn, 64), 32))), qf)) + mstore(add(mIn, 160), mod(sub(qf, mload(add(mIn, 160))), qf)) + g1_acc(_pD, mIn) + g1_acc(_pD, add(mIn, 64)) + g1_acc(_pD, add(mIn, 128)) + } + + function calculateF(pMem) { + let p := add(pMem, pF) + + g1_set(p, add(pMem, pD)) + g1_mulAccC(p, calldataload(pA), calldataload(add(pA, 32)), mload(add(pMem, pV1))) + g1_mulAccC(p, calldataload(pB), calldataload(add(pB, 32)), mload(add(pMem, pV2))) + g1_mulAccC(p, calldataload(pC), calldataload(add(pC, 32)), mload(add(pMem, pV3))) + g1_mulAccC(p, S1x, S1y, mload(add(pMem, pV4))) + g1_mulAccC(p, S2x, S2y, mload(add(pMem, pV5))) + } + + function calculateE(pMem) { + let s := mod(sub(q, mload(add(pMem, pEval_r0))), q) + + s := addmod(s, mulmod(calldataload(pEval_a), mload(add(pMem, pV1)), q), q) + s := addmod(s, mulmod(calldataload(pEval_b), mload(add(pMem, pV2)), q), q) + s := addmod(s, mulmod(calldataload(pEval_c), mload(add(pMem, pV3)), q), q) + s := addmod(s, mulmod(calldataload(pEval_s1), mload(add(pMem, pV4)), q), q) + s := addmod(s, mulmod(calldataload(pEval_s2), mload(add(pMem, pV5)), q), q) + s := addmod(s, mulmod(calldataload(pEval_zw), mload(add(pMem, pU)), q), q) + + g1_mulSetC(add(pMem, pE), G1x, G1y, s) + } + + function checkPairing(pMem) -> isOk { + let mIn := mload(0x40) + mstore(0x40, add(mIn, 576)) // [0..383] = pairing data, [384..447] = pWxi, [448..512] = pWxiw + + let _pWxi := add(mIn, 384) + let _pWxiw := add(mIn, 448) + let _aux := add(mIn, 512) + + g1_calldataSet(_pWxi, pWxi) + g1_calldataSet(_pWxiw, pWxiw) + + // A1 + g1_mulSet(mIn, _pWxiw, mload(add(pMem, pU))) + g1_acc(mIn, _pWxi) + mstore(add(mIn, 32), mod(sub(qf, mload(add(mIn, 32))), qf)) + + // [X]_2 + mstore(add(mIn,64), X2x2) + mstore(add(mIn,96), X2x1) + mstore(add(mIn,128), X2y2) + mstore(add(mIn,160), X2y1) + + // B1 + g1_mulSet(add(mIn, 192), _pWxi, mload(add(pMem, pXi))) + + let s := mulmod(mload(add(pMem, pU)), mload(add(pMem, pXi)), q) + s := mulmod(s, w1, q) + g1_mulSet(_aux, _pWxiw, s) + g1_acc(add(mIn, 192), _aux) + g1_acc(add(mIn, 192), add(pMem, pF)) + mstore(add(pMem, add(pE, 32)), mod(sub(qf, mload(add(pMem, add(pE, 32)))), qf)) + g1_acc(add(mIn, 192), add(pMem, pE)) + + // [1]_2 + mstore(add(mIn,256), G2x2) + mstore(add(mIn,288), G2x1) + mstore(add(mIn,320), G2y2) + mstore(add(mIn,352), G2y1) + + let success := staticcall(sub(gas(), 2000), 8, mIn, 384, mIn, 0x20) + + isOk := and(success, mload(mIn)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, lastMem)) + + checkInput() + calculateChallenges(pMem, _pubSignals) + calculateLagrange(pMem) + calculatePI(pMem, _pubSignals) + calculateR0(pMem) + calculateD(pMem) + calculateF(pMem) + calculateE(pMem) + let isValid := checkPairing(pMem) + + mstore(0x40, sub(pMem, lastMem)) + mstore(0, isValid) + return(0,0x20) + } + + } +} diff --git a/zisk-contracts/ZiskVerifier.sol b/zisk-contracts/ZiskVerifier.sol new file mode 100644 index 000000000..4bda76b67 --- /dev/null +++ b/zisk-contracts/ZiskVerifier.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.20; + +import {IZiskVerifier} from "./IZiskVerifier.sol"; +import {PlonkVerifier} from "./PlonkVerifier.sol"; + +/// @title Zisk Verifier +/// @author SilentSig +/// @notice This contracts implements a solidity verifier for Zisk. +contract ZiskVerifier is PlonkVerifier, IZiskVerifier { + /// @notice Thrown when the verifier selector from this proof does not match the one in this + /// verifier. This indicates that this proof was sent to the wrong verifier. + + /// @notice Thrown when the proof is invalid. + error InvalidProof(); + + function VERSION() external pure returns (string memory) { + return "v0.15.0"; + } + + // Modulus zkSNARK + uint256 internal constant _RFIELD = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + /// @notice Hashes the public values to a field elements inside Bn254. + /// @param publicValues The public values. + function hashPublicValues( + uint64[4] calldata programVK, + bytes calldata publicValues + ) public pure returns (uint256) { + return uint256(sha256(abi.encodePacked(bytes8(programVK[0]), bytes8(programVK[1]), bytes8(programVK[2]), bytes8(programVK[3]), publicValues))) % _RFIELD; + } + + /// @notice Verifies a proof with given public values and vkey. + /// @param programVK The verification key for the RISC-V program. + /// @param publicValues The public values encoded as bytes. + /// @param proofBytes The proof of the program execution the Zisk zkVM encoded as bytes. + function verifySnarkProof( + uint64[4] calldata programVK, + bytes calldata publicValues, + bytes calldata proofBytes + ) external view { + uint256 publicValuesDigest = hashPublicValues(programVK, publicValues); + + uint256[24] memory proofDecoded = abi.decode(proofBytes, (uint256[24])); + + bool success = this.verifyProof( + proofDecoded, + [publicValuesDigest] + ); + + if (!success) { + revert InvalidProof(); + } + } +} \ No newline at end of file From 594c70c39e98f9e7335916feaa0f0411cd1c7607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 24 Dec 2025 16:47:38 +0000 Subject: [PATCH 119/782] Fixing ziskos sha --- .../zisklib/fcalls_impl/bls12_381_fp_inv.rs | 5 +-- .../src/zisklib/lib/sha256f_compress.rs | 45 +------------------ 2 files changed, 3 insertions(+), 47 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_inv.rs index 0402d9e48..206682d38 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_inv.rs @@ -31,10 +31,7 @@ pub(crate) fn bls12_381_fp_inv(a: &[u64; 6]) -> [u64; 6] { let inv = a_big.modinv(&P); match inv { Some(inverse) => n_u64_digits_from_biguint::<6>(&inverse), - None => { - // Handle the case where the inverse does not exist - panic!("Inverse does not exist"); - } + None => panic!("Inverse does not exist"), } } diff --git a/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs b/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs index 81c6e57ac..ac0224988 100644 --- a/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs +++ b/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs @@ -1,18 +1,5 @@ use crate::syscalls::{syscall_sha256_f, SyscallSha256Params}; -pub fn sha256f_compress(state: &mut [u32; 8], blocks: &[[u8; 64]]) { - let mut state_64 = convert_u32_to_u64(state); - - for block in blocks { - let input_u64 = convert_bytes_to_u64(block); - - let mut sha256_params = SyscallSha256Params { state: &mut state_64, input: &input_u64 }; - syscall_sha256_f(&mut sha256_params); - } - - *state = convert_u64_to_u32(&state_64); -} - /// C-compatible wrapper for sha256f_compress /// /// # Safety @@ -24,42 +11,14 @@ pub unsafe extern "C" fn sha256f_compress_c( blocks_ptr: *const u8, num_blocks: usize, ) { - let state: &mut [u32; 8] = &mut *(state_ptr as *mut [u32; 8]); - let mut state_64 = convert_u32_to_u64(state); + let state_64: &mut [u64; 4] = &mut *(state_ptr as *mut [u64; 4]); for i in 0..num_blocks { let block: &[u8; 64] = &*(blocks_ptr.add(i * 64) as *const [u8; 64]); let input_u64 = convert_bytes_to_u64(block); - - let mut sha256_params = SyscallSha256Params { state: &mut state_64, input: &input_u64 }; + let mut sha256_params = SyscallSha256Params { state: state_64, input: &input_u64 }; syscall_sha256_f(&mut sha256_params); } - - *state = convert_u64_to_u32(&state_64); -} - -#[inline(always)] -fn convert_u32_to_u64(words: &[u32; 8]) -> [u64; 4] { - [ - ((words[0] as u64) << 32) | (words[1] as u64), - ((words[2] as u64) << 32) | (words[3] as u64), - ((words[4] as u64) << 32) | (words[5] as u64), - ((words[6] as u64) << 32) | (words[7] as u64), - ] -} - -#[inline(always)] -fn convert_u64_to_u32(input: &[u64; 4]) -> [u32; 8] { - [ - (input[0] >> 32) as u32, - input[0] as u32, - (input[1] >> 32) as u32, - input[1] as u32, - (input[2] >> 32) as u32, - input[2] as u32, - (input[3] >> 32) as u32, - input[3] as u32, - ] } #[inline(always)] From 537f6aa05138564a7daad86c67f182240cd7d171 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:04:05 +0000 Subject: [PATCH 120/782] ditributed hints working --- common/src/hints.rs | 130 +++++++++++++++++ common/src/lib.rs | 2 + distributed/crates/common/src/dto.rs | 29 +++- .../coordinator/src/cli/handler_prove.rs | 9 +- .../crates/coordinator/src/cli/main.rs | 6 + .../crates/coordinator/src/coordinator.rs | 97 ++++++++++++- .../crates/coordinator/src/hints_relay.rs | 136 ++++++++++++++++++ distributed/crates/coordinator/src/lib.rs | 2 + .../grpc-api/proto/zisk_distributed_api.proto | 27 +++- .../crates/grpc-api/src/conversions.rs | 92 ++++++++++-- distributed/crates/worker/src/worker.rs | 9 +- distributed/crates/worker/src/worker_node.rs | 97 +++++++++++-- precompiles/hints/src/hints_processor.rs | 131 +---------------- precompiles/hints/src/lib.rs | 4 +- 14 files changed, 613 insertions(+), 158 deletions(-) create mode 100644 common/src/hints.rs create mode 100644 distributed/crates/coordinator/src/hints_relay.rs diff --git a/common/src/hints.rs b/common/src/hints.rs new file mode 100644 index 000000000..2a6896152 --- /dev/null +++ b/common/src/hints.rs @@ -0,0 +1,130 @@ +//! Hints for ZisK Precompiles stream processing +//! +//! This module provides functionality for parsing precompile hints +//! that are received as a stream of `u64` values. Hints are used to provide preprocessed +//! data to precompile operations in the ZisK zkVM. +//! +//! # Hint Format +//! +//! Each hint consists of: +//! - A **header** (`u64`): Contains the hint type (upper 32 bits) and data length (lower 32 bits) +//! - **Data** (`[u64; length]`): The hint payload, where `length` is specified in the header +//! +//! ```text +//! ┌─────────────────────────────────────────────────────────────┐ +//! │ Header (u64) │ +//! ├·····························································┤ +//! │ Hint Code (32 bits) Length (32 bits). │ +//! ├─────────────────────────────────────────────────────────────┤ +//! │ Data[0] (u64) │ +//! ├─────────────────────────────────────────────────────────────┤ +//! │ Data[1] (u64) │ +//! ├─────────────────────────────────────────────────────────────┤ +//! │ ... │ +//! ├─────────────────────────────────────────────────────────────┤ +//! │ Data[length-1] (u64) │ +//! └─────────────────────────────────────────────────────────────┘ +//! +//! - Hint Code — Control code or Data Hint Type +//! - Length — Number of following u64 data words +//! +//! ## Hint Type Layout +//! +//! ### Control codes +//! +//! The following control codes are defined: +//! - `0x00` (START): Reset processor state and global sequence. +//! - `0x01` (END): Wait until completion of all pending hints. +//! - `0x02` (CANCEL): Cancel current stream and stop processing further hints. +//! - `0x03` (ERROR): Indicate an error has occurred; stop processing further hints. +//! +//! Control codes are for control only and do not have any associated data (Length should be zero). +//! +//! ### Data Hint Types: +//! - `0x04` (`HINTS_TYPE_RESULT`): Pass-through data +//! - `0x05` (`HINTS_TYPE_ECRECOVER`): ECRECOVER inputs (currently returns empty) +//! ``` + +use anyhow::Result; + +/// Control code: Reset processor state and global sequence. +pub const CTRL_START: u32 = 0x00; + +/// Control code: Wait until completion of all pending hints. +pub const CTRL_END: u32 = 0x01; + +/// Control code: Cancel current stream and stop processing. +pub const CTRL_CANCEL: u32 = 0x02; + +/// Control code: Signal error and stop processing. +pub const CTRL_ERROR: u32 = 0x03; + +/// Hint type indicating that the data is already the precomputed result. +/// +/// When a hint has this type, the processor simply passes through the data +/// without any additional computation. +pub const HINTS_TYPE_RESULT: u32 = 0x04; + +/// Hint type indicating that the data contains inputs for the ecrecover precompile. +pub const HINTS_TYPE_ECRECOVER: u32 = 0x05; + +/// Number if hint types defined. +pub const NUM_HINT_TYPES: u32 = 6; + +/// Represents a single precompile hint parsed from a `u64` slice. +/// +/// A hint consists of a type identifier and associated data. The hint type +/// determines how the data should be processed by the [`PrecompileHintsProcessor`]. +pub struct PrecompileHint { + /// The type of hint, determining how the data should be processed. + pub hint_type: u32, + /// The hint payload data. + pub data: Vec, +} + +impl std::fmt::Debug for PrecompileHint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PrecompileHint") + .field("hint_type", &self.hint_type) + .field("data", &self.data) + .finish() + } +} + +impl PrecompileHint { + /// Parses a [`PrecompileHint`] from a slice of `u64` values at the given index. + /// + /// # Arguments + /// + /// * `slice` - The source slice containing concatenated hints + /// * `idx` - The index where the hint header starts + /// + /// # Returns + /// + /// * `Ok(PrecompileHint)` - Successfully parsed hint + /// * `Err` - If the slice is too short or the index is out of bounds + #[inline(always)] + pub fn from_u64_slice(slice: &[u64], idx: usize) -> Result { + if slice.is_empty() || idx >= slice.len() { + return Err(anyhow::anyhow!("Slice too short or index out of bounds")); + } + + let header = slice[idx]; + let hint_type = (header >> 32) as u32; + let length = (header & 0xFFFFFFFF) as u32; + + if slice.len() < idx + length as usize + 1 { + return Err(anyhow::anyhow!( + "Slice too short for hint data: expected {}, got {}", + length, + slice.len() - idx - 1 + )); + } + + // TODO! This creates a new Vec to own the data. Since performance is critical, + // TODO! consider using a slice reference instead. + let data = slice[idx + 1..idx + length as usize + 1].to_vec(); + + Ok(PrecompileHint { hint_type, data }) + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index e2f28a94e..1d8f3634a 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -2,6 +2,7 @@ mod bus; mod component; mod emu_minimal_trace; mod executor_stats; +mod hints; mod instance_context; pub mod io; mod mpi_context; @@ -18,6 +19,7 @@ pub use bus::*; pub use component::*; pub use emu_minimal_trace::*; pub use executor_stats::*; +pub use hints::*; pub use instance_context::*; pub use mpi_context::*; pub use planner_helpers::*; diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index ecf41d17e..f09c648f4 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -76,6 +76,8 @@ pub enum InputModeDto { // Input will be provided as a path. First String is the inputs path URI, // second String is the precompiles hints URI InputModeData(String) = 2, + // Input will be streamed via StreamStart/Data/End messages. String contains the file path to stream. + InputModeStream(String) = 3, } impl Display for InputModeDto { @@ -86,7 +88,10 @@ impl Display for InputModeDto { write!(f, "Path({})", inputs) } InputModeDto::InputModeData(inputs) => { - write!(f, "Data( {})", inputs) + write!(f, "Data({})", inputs) + } + InputModeDto::InputModeStream(inputs) => { + write!(f, "Stream({})", inputs) } } } @@ -124,6 +129,27 @@ pub enum CoordinatorMessageDto { WorkerRegisterResponse(WorkerRegisterResponseDto), ExecuteTaskRequest(ExecuteTaskRequestDto), JobCancelled(JobCancelledDto), + StreamData(StreamDataDto), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum StreamTypeDto { + Start, + Data, + End, +} + +#[derive(Debug, Clone)] +pub struct StreamDataDto { + pub job_id: JobId, + pub stream_type: StreamTypeDto, + pub stream_payload: Option, +} + +#[derive(Debug, Clone)] +pub struct StreamPayloadDto { + pub sequence_number: u32, + pub payload: Vec, } pub struct HeartbeatDto { @@ -173,6 +199,7 @@ pub struct ContributionParamsDto { pub enum InputSourceDto { InputPath(String), InputData(Vec), + InputStream(String), InputNull, } diff --git a/distributed/crates/coordinator/src/cli/handler_prove.rs b/distributed/crates/coordinator/src/cli/handler_prove.rs index e17bb98a2..449506679 100644 --- a/distributed/crates/coordinator/src/cli/handler_prove.rs +++ b/distributed/crates/coordinator/src/cli/handler_prove.rs @@ -15,6 +15,7 @@ pub async fn handle( inputs_uri: Option, hints_uri: Option, direct_inputs: bool, + direct_hints: bool, compute_capacity: u32, simulated_node: Option, ) -> Result<()> { @@ -40,8 +41,10 @@ pub async fn handle( }; let hints_mode = if hints_uri.is_some() { - if direct_inputs { - InputMode::Data + // TODO!!!! Rethink this enum usage for hints streaming + // InputMode::Data has sense? How to activate it? + if direct_hints { + InputMode::Stream } else { InputMode::Uri } @@ -49,6 +52,8 @@ pub async fn handle( InputMode::None }; + println!("hints_mode: {:?}", hints_mode); + // ID will be id if present, else input file name or random UUID let data_id = if let Some(id) = data_id { id diff --git a/distributed/crates/coordinator/src/cli/main.rs b/distributed/crates/coordinator/src/cli/main.rs index b5c1f62c7..6a3294cba 100644 --- a/distributed/crates/coordinator/src/cli/main.rs +++ b/distributed/crates/coordinator/src/cli/main.rs @@ -77,6 +77,10 @@ enum ZiskCoordinatorCommands { #[clap(short = 'x', long, default_value_t = false)] direct_inputs: bool, + /// Whether to send the input data directly + #[clap(long, default_value_t = false)] + direct_hints: bool, + /// Compute capacity needed to generate the proof #[arg(long, short, help = "Compute capacity needed to generate the proof")] compute_capacity: u32, @@ -98,6 +102,7 @@ async fn main() -> Result<()> { inputs_uri, hints_uri, direct_inputs, + direct_hints, compute_capacity, simulated_node, }) => { @@ -108,6 +113,7 @@ async fn main() -> Result<()> { inputs_uri, hints_uri, direct_inputs, + direct_hints, compute_capacity, simulated_node, ) diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 4874e464a..ca3cdbf0d 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -33,7 +33,7 @@ use crate::{ config::Config, coordinator_errors::{CoordinatorError, CoordinatorResult}, - hooks, WorkersPool, + hooks, PrecompileHintsRelay, WorkersPool, }; use chrono::{DateTime, Utc}; @@ -42,6 +42,7 @@ use dashmap::DashMap; use proofman::ContributionsInfo; use std::{ collections::HashMap, + hint, sync::{ atomic::{AtomicU64, Ordering}, Arc, @@ -50,13 +51,14 @@ use std::{ }; use tokio::sync::RwLock; use tracing::{error, info, warn}; +use zisk_common::io::{StreamSource, ZiskStream}; use zisk_distributed_common::{ AggParamsDto, AggProofData, ChallengesDto, ComputeCapacity, ContributionParamsDto, CoordinatorMessageDto, DataId, ExecuteTaskRequestDto, ExecuteTaskRequestTypeDto, ExecuteTaskResponseDto, ExecuteTaskResponseResultDataDto, HeartbeatAckDto, InputModeDto, InputSourceDto, Job, JobExecutionMode, JobId, JobPhase, JobResult, JobResultData, JobState, JobStatusDto, JobsListDto, LaunchProofRequestDto, LaunchProofResponseDto, MetricsDto, ProofDto, - ProveParamsDto, StatusInfoDto, SystemStatusDto, WorkerErrorDto, WorkerId, + ProveParamsDto, StatusInfoDto, StreamTypeDto, SystemStatusDto, WorkerErrorDto, WorkerId, WorkerReconnectRequestDto, WorkerRegisterRequestDto, WorkerState, WorkersListDto, }; @@ -105,7 +107,7 @@ pub struct Coordinator { start_time_utc: DateTime, /// Manages the pool of connected workers and their communication channels. - workers_pool: WorkersPool, + workers_pool: Arc, /// Concurrent storage for active jobs with fine-grained locking. jobs: DashMap>>, @@ -129,7 +131,7 @@ impl Coordinator { Self { config, start_time_utc, - workers_pool: WorkersPool::new(), + workers_pool: Arc::new(WorkersPool::new()), jobs: DashMap::new(), registrations: AtomicU64::new(0), reconnections: AtomicU64::new(0), @@ -620,6 +622,10 @@ impl Coordinator { })?; InputSourceDto::InputData(inputs) } + InputModeDto::InputModeStream(_) => { + // TODO! Inputs streaming not yet supported + InputSourceDto::InputNull + } InputModeDto::InputModeNone => InputSourceDto::InputNull, }; @@ -636,6 +642,10 @@ impl Coordinator { })?; InputSourceDto::InputData(hints) } + InputModeDto::InputModeStream(hints_uri) => { + // Hints will be streamed separately + InputSourceDto::InputStream(hints_uri.clone()) + } InputModeDto::InputModeNone => InputSourceDto::InputNull, }; @@ -645,6 +655,8 @@ impl Coordinator { use futures::stream::{self, StreamExt}; + // TODO!!!!! Can we avoid this clone ???? + let cloned_active_workers = active_workers.clone(); let tasks = active_workers.into_iter().enumerate().map(|(rank_id, worker_id)| { let job_id = job.job_id.clone(); let data_id = job.data_id.clone(); @@ -682,6 +694,10 @@ impl Coordinator { } }); + if matches!(hints_source, InputSourceDto::InputStream(_)) { + self.initialize_stream(job, cloned_active_workers)?; + } + let results: Vec<_> = stream::iter(tasks).buffer_unordered(10).collect().await; // Check for any errors @@ -704,6 +720,79 @@ impl Coordinator { Ok(()) } + fn initialize_stream( + &self, + job: &Job, + cloned_active_workers: Vec, + ) -> Result<(), CoordinatorError> { + let hints_uri = match &job.hints_mode { + InputModeDto::InputModeStream(uri) => uri, + _ => unreachable!(), + }; + let job_id_clone = job.job_id.clone(); + let workers_clone = Arc::new(cloned_active_workers.clone()); + let workers_pool = Arc::clone(&self.workers_pool); + + // Async dispatcher - no blocking, pure async flow for maximum performance + let dispatcher = + move |sequence_number: u32, stream_type: StreamTypeDto, payload: Vec| { + use futures::future::join_all; + use zisk_distributed_common::{StreamDataDto, StreamPayloadDto}; + + let job_id = job_id_clone.clone(); + let workers = Arc::clone(&workers_clone); + let pool = Arc::clone(&workers_pool); + + Box::pin(async move { + let sends = workers.iter().map(|worker_id| { + let job_id = job_id.clone(); + let worker_id = worker_id.clone(); + let payload = payload.clone(); + let pool = Arc::clone(&pool); + let stream_type = stream_type.clone(); + + async move { + let msg = CoordinatorMessageDto::StreamData(StreamDataDto { + job_id: job_id.clone(), + stream_type, + stream_payload: Some(StreamPayloadDto { sequence_number, payload }), + }); + + if let Err(e) = pool.send_message(&worker_id, msg).await { + error!( + "Failed to send hints to worker {} for job {}: {}", + worker_id, job_id, e + ); + } + } + }); + + join_all(sends).await; + }) + }; + let hints_relay = PrecompileHintsRelay::new(dispatcher); + let mut stream = ZiskStream::new(hints_relay); + let stream_reader = StreamSource::from_uri(Some(hints_uri)).map_err(|e| { + CoordinatorError::Internal(format!( + "Failed to create hints stream reader for job {}: {}", + job.job_id, e + )) + })?; + stream.set_hints_stream(stream_reader).map_err(|e| { + CoordinatorError::Internal(format!( + "Failed to set hints stream for job {}: {}", + job.job_id, e + )) + })?; + stream.start_stream().map_err(|e| { + CoordinatorError::Internal(format!( + "Failed to start hints stream for job {}: {}", + job.job_id, e + )) + })?; + Ok(()) + } + /// Marks a job as failed and performs and cleans up all associated resources /// /// # Parameters diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs new file mode 100644 index 000000000..6457d8531 --- /dev/null +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -0,0 +1,136 @@ +//! Precompile Hints Relay + +use anyhow::Result; +use std::future::Future; +use std::pin::Pin; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Arc; +use zisk_common::{io::StreamProcessor, PrecompileHint, CTRL_END, CTRL_START, NUM_HINT_TYPES}; +use zisk_distributed_common::StreamTypeDto; + +type AsyncDispatcher = Arc< + dyn Fn(u32, StreamTypeDto, Vec) -> Pin + Send>> + Send + Sync, +>; + +pub struct PrecompileHintsRelay { + sequence_number: Arc, + dispatcher: AsyncDispatcher, + runtime_handle: tokio::runtime::Handle, +} + +impl PrecompileHintsRelay { + pub fn new(dispatcher: F) -> Self + where + F: Fn(u32, StreamTypeDto, Vec) -> Fut + Send + Sync + 'static, + Fut: Future + Send + 'static, + { + let dispatcher = Arc::new( + move |seq: u32, + stream_type: StreamTypeDto, + payload: Vec| + -> Pin + Send>> { + Box::pin(dispatcher(seq, stream_type, payload)) + }, + ); + + Self { + sequence_number: Arc::new(AtomicU32::new(0)), + dispatcher, + runtime_handle: tokio::runtime::Handle::current(), + } + } + + pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { + let mut has_ctrl_start = false; + let mut has_ctrl_end = false; + + // Parse hints and dispatch to pool + let mut idx = 0; + while idx < hints.len() { + let hint = PrecompileHint::from_u64_slice(hints, idx)?; + let length = hint.data.len(); + + // Validate hint type is in valid range before accessing stats array + if hint.hint_type >= NUM_HINT_TYPES { + return Err(anyhow::anyhow!("Invalid hint type: {}", hint.hint_type)); + } + + // CTRL_START must be the first message of the first batch + if hint.hint_type == CTRL_START { + if !first_batch { + return Err(anyhow::anyhow!( + "CTRL_START can only be sent as the first message in the stream" + )); + } + if idx != 0 { + return Err(anyhow::anyhow!( + "CTRL_START must be the first hint in the batch, but found at index {}", + idx + )); + } + has_ctrl_start = true; + } + + if has_ctrl_end { + return Err(anyhow::anyhow!( + "Received hint after CTRL_END: type {} at index {}", + hint.hint_type, + idx + )); + } + has_ctrl_end = hint.hint_type == CTRL_END; + + idx += length + 1; + } + + if has_ctrl_start { + self.send_hints_start(); + } + + // Call async dispatcher - blocks on async work for zero overhead + self.send_hints_data(hints.to_vec()); + + if has_ctrl_end { + self.send_hints_end(); + } + + Ok(has_ctrl_end) + } + + fn send_hints_start(&self) { + let seq_num = self.sequence_number.fetch_add(1, Ordering::SeqCst); + println!("Sending CTRL_START with sequence number {}", seq_num); + + self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamTypeDto::Start, vec![])); + } + + fn send_hints_data(&self, hints: Vec) { + let seq_num = self.sequence_number.fetch_add(1, Ordering::SeqCst); + println!("Sending Hints DATA with sequence number {}", seq_num); + + // Convert Vec to Vec for wire protocol + let payload = unsafe { + let mut hints_vec = hints.to_vec(); + let ptr = hints_vec.as_mut_ptr() as *mut u8; + let len = hints_vec.len() * std::mem::size_of::(); + let capacity = hints_vec.capacity() * std::mem::size_of::(); + std::mem::forget(hints_vec); + Vec::from_raw_parts(ptr, len, capacity) + }; + + self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamTypeDto::Data, payload)); + } + + fn send_hints_end(&self) { + let seq_num = self.sequence_number.fetch_add(1, Ordering::SeqCst); + println!("Sending CTRL_END with sequence number {}", seq_num); + + self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamTypeDto::End, vec![])); + } +} + +impl StreamProcessor for PrecompileHintsRelay { + fn process(&self, data: &[u64], first_batch: bool) -> Result { + self.process_hints(data, first_batch) + } +} diff --git a/distributed/crates/coordinator/src/lib.rs b/distributed/crates/coordinator/src/lib.rs index 66b9fdd71..98b4f9e92 100644 --- a/distributed/crates/coordinator/src/lib.rs +++ b/distributed/crates/coordinator/src/lib.rs @@ -2,6 +2,7 @@ mod config; mod coordinator; mod coordinator_errors; mod coordinator_grpc; +mod hints_relay; mod hooks; mod shutdown; mod workers_pool; @@ -9,5 +10,6 @@ mod workers_pool; pub use config::*; use coordinator::*; pub use coordinator_grpc::*; +pub use hints_relay::*; pub use shutdown::*; use workers_pool::*; diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index e29808bff..50ae8d948 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -66,6 +66,7 @@ enum InputMode { INPUT_MODE_NONE = 0; // No input provided INPUT_MODE_URI = 1; // Input will be provided as an URI INPUT_MODE_DATA = 2; // Input data will be sent directly + INPUT_MODE_STREAM = 3; // Input data will be sent as a stream } // ============================================================================ @@ -168,6 +169,7 @@ message CoordinatorMessage { WorkerRegisterResponse register_response = 3; ExecuteTaskRequest execute_task = 4; JobCancelled job_cancelled = 5; + StreamData stream_data = 6; } } @@ -236,10 +238,8 @@ message ContributionParams { string input_path = 2; bytes input_data = 3; } - oneof hints_source { - string hints_path = 4; - bytes hints_data = 5; - } + optional string hints_path = 4; + bool hints_stream = 5; // Indicates whether hints will be streamed uint32 rank_id = 6; uint32 total_workers = 7; repeated uint32 worker_allocation = 8; @@ -265,6 +265,25 @@ message AggParams { bool minimal_memory = 12; } +// Stream type enumeration +enum StreamType { + STREAM_TYPE_START = 0; + STREAM_TYPE_DATA = 1; + STREAM_TYPE_END = 2; +} + +// Streaming messages for data transfer +message StreamData { + string job_id = 1; + StreamType stream_type = 2; + optional StreamPayload payload = 3; +} + +message StreamPayload { + uint32 sequence_number = 3; + bytes payload = 4; +} + message ExecuteTaskResponse { string job_id = 1; string worker_id = 2; diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index 280bb9560..f6b0461b6 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -9,7 +9,7 @@ //! model. All conversions implement the `From` and/or `Into` traits for idiomatic Rust usage. use crate::{ - contribution_params::{HintsSource, InputSource}, + contribution_params::{InputSource}, coordinator_message::Payload, execute_task_request, execute_task_response, job_status_response, jobs_list_response, launch_proof_response, system_status_response, workers_list_response, AggParams, Challenges, @@ -17,8 +17,9 @@ use crate::{ ExecuteTaskRequest, ExecuteTaskResponse, Heartbeat, HeartbeatAck, InputMode, JobCancelled, JobStatus, JobStatusResponse, JobsList, JobsListResponse, LaunchProofRequest, LaunchProofResponse, Metrics, Proof, ProofList, ProveParams, Shutdown, StatusInfoResponse, - SystemStatus, SystemStatusResponse, TaskType, WorkerError, WorkerInfo, WorkerReconnectRequest, - WorkerRegisterRequest, WorkerRegisterResponse, WorkersList, WorkersListResponse, + StreamData, StreamPayload, StreamType, SystemStatus, SystemStatusResponse, TaskType, + WorkerError, WorkerInfo, WorkerReconnectRequest, WorkerRegisterRequest, WorkerRegisterResponse, + WorkersList, WorkersListResponse, }; use zisk_distributed_common::*; @@ -158,12 +159,14 @@ impl From for LaunchProofRequest { InputModeDto::InputModeNone => (InputMode::None, None), InputModeDto::InputModeUri(inputs_uri) => (InputMode::Uri, Some(inputs_uri)), InputModeDto::InputModeData(inputs_uri) => (InputMode::Data, Some(inputs_uri)), + InputModeDto::InputModeStream(inputs_uri) => (InputMode::Stream, Some(inputs_uri)), }; let (hints_mode, hints_uri) = match dto.hints_mode { InputModeDto::InputModeNone => (InputMode::None, None), InputModeDto::InputModeUri(hints_uri) => (InputMode::Uri, Some(hints_uri)), InputModeDto::InputModeData(hints_uri) => (InputMode::Data, Some(hints_uri)), + InputModeDto::InputModeStream(hints_uri) => (InputMode::Stream, Some(hints_uri)), }; LaunchProofRequest { @@ -201,6 +204,12 @@ impl TryFrom for LaunchProofRequestDto { })?; InputModeDto::InputModeData(inputs_uri) } + InputMode::Stream => { + let inputs_uri = req.inputs_uri.ok_or_else(|| { + anyhow::anyhow!("Input mode is Stream but inputs_uri is missing") + })?; + InputModeDto::InputModeStream(inputs_uri) + } }, hints_mode: match InputMode::try_from(req.hints_mode).unwrap_or(InputMode::None) { InputMode::None => InputModeDto::InputModeNone, @@ -216,6 +225,12 @@ impl TryFrom for LaunchProofRequestDto { })?; InputModeDto::InputModeData(hints_uri) } + InputMode::Stream => { + let hints_uri = req.hints_uri.ok_or_else(|| { + anyhow::anyhow!("Hints mode is Stream but hints_uri is missing") + })?; + InputModeDto::InputModeStream(hints_uri) + } }, simulated_node: req.simulated_node, }) @@ -272,6 +287,9 @@ impl From for CoordinatorMessage { CoordinatorMessageDto::JobCancelled(cancel) => { CoordinatorMessage { payload: Some(Payload::JobCancelled(cancel.into())) } } + CoordinatorMessageDto::StreamData(data) => { + CoordinatorMessage { payload: Some(Payload::StreamData(data.into())) } + } } } } @@ -342,19 +360,21 @@ impl From for ContributionParams { let input_source = match dto.input_source { InputSourceDto::InputPath(inputs_path) => Some(InputSource::InputPath(inputs_path)), InputSourceDto::InputData(data) => Some(InputSource::InputData(data)), - InputSourceDto::InputNull => None, + InputSourceDto::InputStream(_) | InputSourceDto::InputNull => None, }; - let hints_source = match dto.hints_source { - InputSourceDto::InputPath(hints_path) => Some(HintsSource::HintsPath(hints_path)), - InputSourceDto::InputData(data) => Some(HintsSource::HintsData(data)), - InputSourceDto::InputNull => None, + let (hints_path, hints_stream) = match dto.hints_source { + InputSourceDto::InputPath(hints_path) => (Some(hints_path), false), + InputSourceDto::InputData(_) => (None, false), + InputSourceDto::InputStream(hints_path) => (Some(hints_path), true), + InputSourceDto::InputNull => (None, false), }; ContributionParams { data_id: dto.data_id.as_string(), input_source, - hints_source, + hints_path, + hints_stream, rank_id: dto.rank_id, total_workers: dto.total_workers, worker_allocation: dto.worker_allocation, @@ -464,6 +484,60 @@ impl From for HeartbeatAckDto { } } +impl From for StreamType { + fn from(dto: StreamTypeDto) -> StreamType { + match dto { + StreamTypeDto::Start => StreamType::Start, + StreamTypeDto::Data => StreamType::Data, + StreamTypeDto::End => StreamType::End, + } + } +} + +impl From for StreamTypeDto { + fn from(stream_type: StreamType) -> StreamTypeDto { + match stream_type { + StreamType::Start => StreamTypeDto::Start, + StreamType::Data => StreamTypeDto::Data, + StreamType::End => StreamTypeDto::End, + } + } +} + +impl From for StreamData { + fn from(dto: StreamDataDto) -> Self { + StreamData { + job_id: dto.job_id.as_string(), + stream_type: StreamType::from(dto.stream_type) as i32, + payload: dto.stream_payload.map(Into::into), + } + } +} + +impl From for StreamDataDto { + fn from(data: StreamData) -> Self { + StreamDataDto { + job_id: JobId::from(data.job_id), + stream_type: StreamType::try_from(data.stream_type) + .map(StreamTypeDto::from) + .unwrap_or(StreamTypeDto::Data), + stream_payload: data.payload.map(Into::into), + } + } +} + +impl From for StreamPayload { + fn from(dto: StreamPayloadDto) -> Self { + StreamPayload { sequence_number: dto.sequence_number, payload: dto.payload } + } +} + +impl From for StreamPayloadDto { + fn from(payload: StreamPayload) -> Self { + StreamPayloadDto { sequence_number: payload.sequence_number, payload: payload.payload } + } +} + impl From for WorkerErrorDto { fn from(error: WorkerError) -> Self { WorkerErrorDto { diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 51f95c87c..702ad276d 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use cargo_zisk::commands::{get_proving_key, get_witness_computation_lib}; use proofman::{AggProofs, ContributionsInfo}; use rom_setup::{ @@ -544,6 +544,9 @@ impl Worker { let stdin = ZiskStdin::from_vec(input_data); prover.set_stdin(stdin); } + InputSourceDto::InputStream(_) => { + return Err(anyhow!("Input source is in streaming mode but stream not completed")); + } InputSourceDto::InputNull => { let stdin = ZiskStdin::null(); prover.set_stdin(stdin); @@ -559,6 +562,10 @@ impl Worker { let hints_stream = StreamSource::from_vec(hints_data); prover.set_hints_stream(hints_stream)?; } + InputSourceDto::InputStream(hints_uri) => { + let hints_stream = StreamSource::from_uri(hints_uri.into())?; + prover.set_hints_stream(hints_stream)?; + } InputSourceDto::InputNull => { // No hints to set } diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 8032e7309..b9a75b880 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -1,6 +1,8 @@ use crate::{worker::ComputationResult, ProverConfig, Worker}; use anyhow::{anyhow, Result}; use proofman::{AggProofs, ContributionsInfo}; +use std::collections::hash_map::Entry; +use std::collections::HashMap; use std::path::Path; use std::{path::PathBuf, time::Duration}; use tokio::sync::mpsc; @@ -9,10 +11,11 @@ use tonic::transport::Channel; use tonic::Request; use tracing::{error, info}; use zisk_distributed_common::{ - AggProofData, AggregationParams, DataCtx, InputSourceDto, WorkerState, + AggProofData, AggregationParams, DataCtx, InputSourceDto, StreamPayloadDto, StreamTypeDto, + WorkerState, }; use zisk_distributed_common::{DataId, JobId}; -use zisk_distributed_grpc_api::contribution_params::{HintsSource, InputSource}; +use zisk_distributed_grpc_api::contribution_params::InputSource; use zisk_distributed_grpc_api::execute_task_response::ResultData; use zisk_distributed_grpc_api::*; use zisk_sdk::{Asm, Emu, ZiskBackend}; @@ -102,11 +105,13 @@ impl WorkerNodeMpi { pub struct WorkerNodeGrpc { worker_config: WorkerServiceConfig, worker: Worker, + + stream_buffers: HashMap>)>, // (job_id, (next_seq, (seq_number, data))) } impl WorkerNodeGrpc { pub async fn new(worker_config: WorkerServiceConfig, worker: Worker) -> Result { - Ok(Self { worker_config, worker }) + Ok(Self { worker_config, worker, stream_buffers: HashMap::new() }) } pub fn world_rank(&self) -> i32 { @@ -494,6 +499,9 @@ impl WorkerNodeGrpc { tokio::time::sleep(Duration::from_secs(shutdown.grace_period_seconds as u64)).await; return Err(anyhow!("Coordinator requested shutdown: {}", shutdown.reason)); } + coordinator_message::Payload::StreamData(stream_data) => { + self.handle_stream_data(stream_data).await?; + } } Ok(()) @@ -532,21 +540,22 @@ impl WorkerNodeGrpc { } }; - let hints_source = match params.hints_source { - Some(HintsSource::HintsPath(ref hints_uris)) => { + let hints_source = if params.hints_path.is_some() { + if !params.hints_stream { // Validate and get the full path let hints_uri = Self::validate_subdir( &self.worker_config.worker.inputs_folder, - &PathBuf::from(&hints_uris), + &PathBuf::from(params.hints_path.as_ref().unwrap()), ) .await?; InputSourceDto::InputPath(hints_uri.to_string_lossy().to_string()) + } else { + // Hints will be streamed - use placeholder, will be updated when stream completes + InputSourceDto::InputStream(params.hints_path.as_ref().unwrap().clone()) } - Some(HintsSource::HintsData(data)) => InputSourceDto::InputData(data), - None => { - return Err(anyhow!("Hints source missing in ContributionParams")); - } + } else { + InputSourceDto::InputNull }; let data_ctx = @@ -733,4 +742,72 @@ impl WorkerNodeGrpc { Ok(()) } + + async fn handle_stream_data(&mut self, stream_data: StreamData) -> Result<()> { + use zisk_distributed_common::StreamDataDto; + + let stream_data_dto: StreamDataDto = stream_data.into(); + let job_id = stream_data_dto.job_id; + let stream_type = stream_data_dto.stream_type; + + println!("Job ID: {}", job_id.as_string()); + println!("Stream Type: {:?}", stream_type); + + // Check the existence of stream buffer based on stream type + if stream_type == StreamTypeDto::Start { + // Check if buffer already exists + match self.stream_buffers.entry(job_id.clone()) { + Entry::Occupied(_) => { + return Err(anyhow!("Received duplicate START for job {}", job_id)); + } + Entry::Vacant(entry) => { + entry.insert((1, HashMap::new())); + } + } + + return Ok(()); + } else if stream_type == StreamTypeDto::End { + // Ensure buffer exists + if !self.stream_buffers.contains_key(&job_id) { + return Err( + anyhow!("Received {:?} without START for job {}", stream_type, job_id,), + ); + } + + return Ok(()); + } + + let element = self.stream_buffers.get_mut(&job_id).ok_or_else(|| { + anyhow!( + "Received stream data without START for job {} stream type {:?}", + job_id, + stream_type + ) + })?; + + let next_seq = &mut element.0; + let stream_buffer = &mut element.1; + + let StreamPayloadDto { sequence_number, payload: data } = + stream_data_dto.stream_payload.ok_or_else(|| { + anyhow!("Missing stream payload for job {} stream type {:?}", job_id, stream_type) + })?; + + // Validate sequence number + if sequence_number != *next_seq { + stream_buffer.insert(sequence_number, data); + return Ok(()); + } + + // If equals, process it and check for subsequent buffered chunks + println!("Processing stream chunk {} for job {}", sequence_number, job_id); + *next_seq += 1; + + while let Some(_buffered_data) = stream_buffer.remove(next_seq) { + println!("Processing buffered stream chunk {} for job {}", next_seq, job_id); + *next_seq += 1; + } + + Ok(()) + } } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 03508445e..654cb8b68 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -1,49 +1,8 @@ //! Precompile Hints Processor //! -//! This module provides functionality for parsing and processing precompile hints +//! This module provides functionality for processing precompile hints //! that are received as a stream of `u64` values. Hints are used to provide preprocessed //! data to precompile operations in the ZisK zkVM. -//! -//! # Hint Format -//! -//! Each hint consists of: -//! - A **header** (`u64`): Contains the hint type (upper 32 bits) and data length (lower 32 bits) -//! - **Data** (`[u64; length]`): The hint payload, where `length` is specified in the header -//! -//! ```text -//! ┌─────────────────────────────────────────────────────────────┐ -//! │ Header (u64) │ -//! ├·····························································┤ -//! │ Hint Code (32 bits) Length (32 bits). │ -//! ├─────────────────────────────────────────────────────────────┤ -//! │ Data[0] (u64) │ -//! ├─────────────────────────────────────────────────────────────┤ -//! │ Data[1] (u64) │ -//! ├─────────────────────────────────────────────────────────────┤ -//! │ ... │ -//! ├─────────────────────────────────────────────────────────────┤ -//! │ Data[length-1] (u64) │ -//! └─────────────────────────────────────────────────────────────┘ -//! -//! - Hint Code — Control code or Data Hint Type -//! - Length — Number of following u64 data words -//! -//! ## Hint Type Layout -//! -//! ### Control codes -//! -//! The following control codes are defined: -//! - `0x00` (START): Reset processor state and global sequence. -//! - `0x01` (END): Wait until completion of all pending hints. -//! - `0x02` (CANCEL): Cancel current stream and stop processing further hints. -//! - `0x03` (ERROR): Indicate an error has occurred; stop processing further hints. -//! -//! Control codes are for control only and do not have any associated data (Length should be zero). -//! -//! ### Data Hint Types: -//! - `0x04` (`HINTS_TYPE_RESULT`): Pass-through data -//! - `0x05` (`HINTS_TYPE_ECRECOVER`): ECRECOVER inputs (currently returns empty) -//! ``` use anyhow::Result; use rayon::{ThreadPool, ThreadPoolBuilder}; @@ -53,92 +12,14 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use tracing::debug; use zisk_common::io::{StreamProcessor, StreamSink}; +use zisk_common::{ + PrecompileHint, CTRL_CANCEL, CTRL_END, CTRL_ERROR, CTRL_START, HINTS_TYPE_ECRECOVER, + HINTS_TYPE_RESULT, NUM_HINT_TYPES, +}; use ziskos::syscalls::SyscallPoint256; use crate::secp256k1_ecdsa_verify; -/// Control code: Reset processor state and global sequence. -const CTRL_START: u32 = 0x00; - -/// Control code: Wait until completion of all pending hints. -const CTRL_END: u32 = 0x01; - -/// Control code: Cancel current stream and stop processing. -const CTRL_CANCEL: u32 = 0x02; - -/// Control code: Signal error and stop processing. -const CTRL_ERROR: u32 = 0x03; - -/// Hint type indicating that the data is already the precomputed result. -/// -/// When a hint has this type, the processor simply passes through the data -/// without any additional computation. -pub const HINTS_TYPE_RESULT: u32 = 0x04; - -/// Hint type indicating that the data contains inputs for the ecrecover precompile. -pub const HINTS_TYPE_ECRECOVER: u32 = 0x05; - -/// Number if hint types defined. -pub const NUM_HINT_TYPES: u32 = 6; - -/// Represents a single precompile hint parsed from a `u64` slice. -/// -/// A hint consists of a type identifier and associated data. The hint type -/// determines how the data should be processed by the [`PrecompileHintsProcessor`]. -pub struct PrecompileHint { - /// The type of hint, determining how the data should be processed. - hint_type: u32, - /// The hint payload data. - data: Vec, -} - -impl std::fmt::Debug for PrecompileHint { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PrecompileHint") - .field("hint_type", &self.hint_type) - .field("data", &self.data) - .finish() - } -} - -impl PrecompileHint { - /// Parses a [`PrecompileHint`] from a slice of `u64` values at the given index. - /// - /// # Arguments - /// - /// * `slice` - The source slice containing concatenated hints - /// * `idx` - The index where the hint header starts - /// - /// # Returns - /// - /// * `Ok(PrecompileHint)` - Successfully parsed hint - /// * `Err` - If the slice is too short or the index is out of bounds - #[inline(always)] - fn from_u64_slice(slice: &[u64], idx: usize) -> Result { - if slice.is_empty() || idx >= slice.len() { - return Err(anyhow::anyhow!("Slice too short or index out of bounds")); - } - - let header = slice[idx]; - let hint_type = (header >> 32) as u32; - let length = (header & 0xFFFFFFFF) as u32; - - if slice.len() < idx + length as usize + 1 { - return Err(anyhow::anyhow!( - "Slice too short for hint data: expected {}, got {}", - length, - slice.len() - idx - 1 - )); - } - - // TODO! This creates a new Vec to own the data. Sine performance is critical, - // TODO! consider using a slice reference instead. - let data = slice[idx + 1..idx + length as usize + 1].to_vec(); - - Ok(PrecompileHint { hint_type, data }) - } -} - /// Ordered result buffer with drain state. /// /// This structure maintains a VecDeque that holds processed results in order, @@ -614,6 +495,8 @@ impl StreamProcessor for PrecompileHints #[cfg(test)] mod tests { + use zisk_common::{CTRL_CANCEL, CTRL_END, CTRL_ERROR, CTRL_START, HINTS_TYPE_RESULT}; + use super::*; struct NullHints; diff --git a/precompiles/hints/src/lib.rs b/precompiles/hints/src/lib.rs index 0053b9d9d..81a4691ef 100644 --- a/precompiles/hints/src/lib.rs +++ b/precompiles/hints/src/lib.rs @@ -1,7 +1,5 @@ mod hints_processor; mod secp256k1; -pub use hints_processor::{ - PrecompileHint, PrecompileHintsProcessor, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT, -}; +pub use hints_processor::PrecompileHintsProcessor; pub use secp256k1::*; From 2d5c675db06e40f443e3bf7bbef688f47ccce4c1 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:10:17 +0000 Subject: [PATCH 121/782] cleaning wip --- distributed/crates/common/src/dto.rs | 47 ++++++++++++++----- distributed/crates/common/src/types.rs | 8 ++-- .../crates/coordinator/src/coordinator.rs | 41 ++++++---------- .../crates/coordinator/src/hints_relay.rs | 3 -- .../crates/grpc-api/src/conversions.rs | 44 ++++++----------- distributed/crates/worker/src/worker.rs | 23 ++++----- distributed/crates/worker/src/worker_node.rs | 13 ++--- 7 files changed, 83 insertions(+), 96 deletions(-) diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index f09c648f4..76f61a7a4 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -70,14 +70,10 @@ pub struct SystemStatusDto { pub enum InputModeDto { // No input provided InputModeNone = 0, - // Input will be provided as a path. First String is the inputs path, - // second String is the precompiles hints path + // Input will be provided as a path. InputModeUri(String) = 1, - // Input will be provided as a path. First String is the inputs path URI, - // second String is the precompiles hints URI + // Input will be embedded directly as data. InputModeData(String) = 2, - // Input will be streamed via StreamStart/Data/End messages. String contains the file path to stream. - InputModeStream(String) = 3, } impl Display for InputModeDto { @@ -90,7 +86,30 @@ impl Display for InputModeDto { InputModeDto::InputModeData(inputs) => { write!(f, "Data({})", inputs) } - InputModeDto::InputModeStream(inputs) => { + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[repr(i32)] +pub enum HintsModeDto { + // No input provided + InputModeNone = 0, + // Input will be provided as a path. First String is the inputs path, + // second String is the precompiles hints path + InputModeUri(String) = 1, + // Input will be streamed via StreamStart/Data/End messages. String contains the file path to stream. + InputModeStream(String) = 2, +} + +impl Display for HintsModeDto { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HintsModeDto::InputModeNone => write!(f, "None"), + HintsModeDto::InputModeUri(inputs) => { + write!(f, "Path({})", inputs) + } + HintsModeDto::InputModeStream(inputs) => { write!(f, "Stream({})", inputs) } } @@ -101,7 +120,7 @@ pub struct LaunchProofRequestDto { pub data_id: DataId, pub compute_capacity: u32, pub inputs_mode: InputModeDto, - pub hints_mode: InputModeDto, + pub hints_mode: HintsModeDto, pub simulated_node: Option, } @@ -135,7 +154,7 @@ pub enum CoordinatorMessageDto { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum StreamTypeDto { Start, - Data, + Data, End, } @@ -188,7 +207,7 @@ pub enum ExecuteTaskRequestTypeDto { pub struct ContributionParamsDto { pub data_id: DataId, pub input_source: InputSourceDto, - pub hints_source: InputSourceDto, + pub hints_source: HintsSourceDto, pub rank_id: u32, pub total_workers: u32, pub worker_allocation: Vec, @@ -199,10 +218,16 @@ pub struct ContributionParamsDto { pub enum InputSourceDto { InputPath(String), InputData(Vec), - InputStream(String), InputNull, } +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub enum HintsSourceDto { + HintsPath(String), + HintsStream(String), + HintsNull, +} + pub struct ProveParamsDto { pub challenges: Vec, } diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index b06beee5d..b1c0081a9 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -16,7 +16,7 @@ use std::{ }; use tracing::error; -use crate::{InputModeDto, InputSourceDto}; +use crate::{HintsModeDto, HintsSourceDto, InputModeDto, InputSourceDto}; /// Job ID wrapper for type safety #[derive( @@ -244,7 +244,7 @@ pub struct Job { pub state: JobState, pub data_id: DataId, pub inputs_mode: InputModeDto, - pub hints_mode: InputModeDto, + pub hints_mode: HintsModeDto, pub compute_capacity: ComputeCapacity, pub workers: Vec, pub agg_worker_id: Option, @@ -261,7 +261,7 @@ impl Job { pub fn new( data_id: DataId, inputs_mode: InputModeDto, - hints_mode: InputModeDto, + hints_mode: HintsModeDto, compute_capacity: ComputeCapacity, selected_workers: Vec, partitions: Vec>, @@ -398,7 +398,7 @@ pub struct JobResult { pub struct DataCtx { pub data_id: DataId, pub input_source: InputSourceDto, - pub hints_source: InputSourceDto, + pub hints_source: HintsSourceDto, } #[repr(u8)] diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index ca3cdbf0d..69bf12b1b 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -42,7 +42,6 @@ use dashmap::DashMap; use proofman::ContributionsInfo; use std::{ collections::HashMap, - hint, sync::{ atomic::{AtomicU64, Ordering}, Arc, @@ -55,11 +54,12 @@ use zisk_common::io::{StreamSource, ZiskStream}; use zisk_distributed_common::{ AggParamsDto, AggProofData, ChallengesDto, ComputeCapacity, ContributionParamsDto, CoordinatorMessageDto, DataId, ExecuteTaskRequestDto, ExecuteTaskRequestTypeDto, - ExecuteTaskResponseDto, ExecuteTaskResponseResultDataDto, HeartbeatAckDto, InputModeDto, - InputSourceDto, Job, JobExecutionMode, JobId, JobPhase, JobResult, JobResultData, JobState, - JobStatusDto, JobsListDto, LaunchProofRequestDto, LaunchProofResponseDto, MetricsDto, ProofDto, - ProveParamsDto, StatusInfoDto, StreamTypeDto, SystemStatusDto, WorkerErrorDto, WorkerId, - WorkerReconnectRequestDto, WorkerRegisterRequestDto, WorkerState, WorkersListDto, + ExecuteTaskResponseDto, ExecuteTaskResponseResultDataDto, HeartbeatAckDto, HintsModeDto, + HintsSourceDto, InputModeDto, InputSourceDto, Job, JobExecutionMode, JobId, JobPhase, + JobResult, JobResultData, JobState, JobStatusDto, JobsListDto, LaunchProofRequestDto, + LaunchProofResponseDto, MetricsDto, ProofDto, ProveParamsDto, StatusInfoDto, StreamTypeDto, + SystemStatusDto, WorkerErrorDto, WorkerId, WorkerReconnectRequestDto, WorkerRegisterRequestDto, + WorkerState, WorkersListDto, }; /// Trait for sending messages to workers through various communication channels. @@ -531,7 +531,7 @@ impl Coordinator { data_id: DataId, required_compute_capacity: ComputeCapacity, inputs_mode: InputModeDto, - hints_mode: InputModeDto, + hints_mode: HintsModeDto, simulated_node: Option, ) -> CoordinatorResult { let execution_mode = if let Some(node) = simulated_node { @@ -622,31 +622,18 @@ impl Coordinator { })?; InputSourceDto::InputData(inputs) } - InputModeDto::InputModeStream(_) => { - // TODO! Inputs streaming not yet supported - InputSourceDto::InputNull - } InputModeDto::InputModeNone => InputSourceDto::InputNull, }; let hints_source = match &job.hints_mode { - InputModeDto::InputModeUri(ref hints_uri) => { - InputSourceDto::InputPath(hints_uri.clone()) + HintsModeDto::InputModeUri(ref hints_uri) => { + HintsSourceDto::HintsPath(hints_uri.clone()) } - InputModeDto::InputModeData(ref hints_uri) => { - let hints = tokio::fs::read(hints_uri).await.map_err(|e| { - CoordinatorError::Internal(format!( - "Failed to read hints data for job {}: {}", - job.job_id, e - )) - })?; - InputSourceDto::InputData(hints) - } - InputModeDto::InputModeStream(hints_uri) => { + HintsModeDto::InputModeStream(hints_uri) => { // Hints will be streamed separately - InputSourceDto::InputStream(hints_uri.clone()) + HintsSourceDto::HintsStream(hints_uri.clone()) } - InputModeDto::InputModeNone => InputSourceDto::InputNull, + HintsModeDto::InputModeNone => HintsSourceDto::HintsNull, }; // Use Arc to avoid expensive clones @@ -694,7 +681,7 @@ impl Coordinator { } }); - if matches!(hints_source, InputSourceDto::InputStream(_)) { + if matches!(hints_source, HintsSourceDto::HintsStream(_)) { self.initialize_stream(job, cloned_active_workers)?; } @@ -726,7 +713,7 @@ impl Coordinator { cloned_active_workers: Vec, ) -> Result<(), CoordinatorError> { let hints_uri = match &job.hints_mode { - InputModeDto::InputModeStream(uri) => uri, + HintsModeDto::InputModeStream(uri) => uri, _ => unreachable!(), }; let job_id_clone = job.job_id.clone(); diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index 6457d8531..b34fe0928 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -99,14 +99,12 @@ impl PrecompileHintsRelay { fn send_hints_start(&self) { let seq_num = self.sequence_number.fetch_add(1, Ordering::SeqCst); - println!("Sending CTRL_START with sequence number {}", seq_num); self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamTypeDto::Start, vec![])); } fn send_hints_data(&self, hints: Vec) { let seq_num = self.sequence_number.fetch_add(1, Ordering::SeqCst); - println!("Sending Hints DATA with sequence number {}", seq_num); // Convert Vec to Vec for wire protocol let payload = unsafe { @@ -123,7 +121,6 @@ impl PrecompileHintsRelay { fn send_hints_end(&self) { let seq_num = self.sequence_number.fetch_add(1, Ordering::SeqCst); - println!("Sending CTRL_END with sequence number {}", seq_num); self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamTypeDto::End, vec![])); } diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index f6b0461b6..a0ee0feb6 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -9,10 +9,9 @@ //! model. All conversions implement the `From` and/or `Into` traits for idiomatic Rust usage. use crate::{ - contribution_params::{InputSource}, - coordinator_message::Payload, - execute_task_request, execute_task_response, job_status_response, jobs_list_response, - launch_proof_response, system_status_response, workers_list_response, AggParams, Challenges, + contribution_params::InputSource, coordinator_message::Payload, execute_task_request, + execute_task_response, job_status_response, jobs_list_response, launch_proof_response, + system_status_response, workers_list_response, AggParams, Challenges, ComputeCapacity as GrpcComputeCapacity, ContributionParams, CoordinatorMessage, ExecuteTaskRequest, ExecuteTaskResponse, Heartbeat, HeartbeatAck, InputMode, JobCancelled, JobStatus, JobStatusResponse, JobsList, JobsListResponse, LaunchProofRequest, @@ -159,14 +158,12 @@ impl From for LaunchProofRequest { InputModeDto::InputModeNone => (InputMode::None, None), InputModeDto::InputModeUri(inputs_uri) => (InputMode::Uri, Some(inputs_uri)), InputModeDto::InputModeData(inputs_uri) => (InputMode::Data, Some(inputs_uri)), - InputModeDto::InputModeStream(inputs_uri) => (InputMode::Stream, Some(inputs_uri)), }; let (hints_mode, hints_uri) = match dto.hints_mode { - InputModeDto::InputModeNone => (InputMode::None, None), - InputModeDto::InputModeUri(hints_uri) => (InputMode::Uri, Some(hints_uri)), - InputModeDto::InputModeData(hints_uri) => (InputMode::Data, Some(hints_uri)), - InputModeDto::InputModeStream(hints_uri) => (InputMode::Stream, Some(hints_uri)), + HintsModeDto::InputModeNone => (InputMode::None, None), + HintsModeDto::InputModeUri(hints_uri) => (InputMode::Uri, Some(hints_uri)), + HintsModeDto::InputModeStream(hints_uri) => (InputMode::Stream, Some(hints_uri)), }; LaunchProofRequest { @@ -204,33 +201,23 @@ impl TryFrom for LaunchProofRequestDto { })?; InputModeDto::InputModeData(inputs_uri) } - InputMode::Stream => { - let inputs_uri = req.inputs_uri.ok_or_else(|| { - anyhow::anyhow!("Input mode is Stream but inputs_uri is missing") - })?; - InputModeDto::InputModeStream(inputs_uri) - } + _ => return Err(anyhow::anyhow!("Invalid inputs_mode for LaunchProofRequestDto")), }, hints_mode: match InputMode::try_from(req.hints_mode).unwrap_or(InputMode::None) { - InputMode::None => InputModeDto::InputModeNone, + InputMode::None => HintsModeDto::InputModeNone, InputMode::Uri => { let hints_uri = req.hints_uri.ok_or_else(|| { anyhow::anyhow!("Hints mode is Path but hints_uri is missing") })?; - InputModeDto::InputModeUri(hints_uri) - } - InputMode::Data => { - let hints_uri = req.hints_uri.ok_or_else(|| { - anyhow::anyhow!("Hints mode is Data but hints_uri is missing") - })?; - InputModeDto::InputModeData(hints_uri) + HintsModeDto::InputModeUri(hints_uri) } InputMode::Stream => { let hints_uri = req.hints_uri.ok_or_else(|| { anyhow::anyhow!("Hints mode is Stream but hints_uri is missing") })?; - InputModeDto::InputModeStream(hints_uri) + HintsModeDto::InputModeStream(hints_uri) } + _ => return Err(anyhow::anyhow!("Invalid hints_mode for LaunchProofRequestDto")), }, simulated_node: req.simulated_node, }) @@ -360,14 +347,13 @@ impl From for ContributionParams { let input_source = match dto.input_source { InputSourceDto::InputPath(inputs_path) => Some(InputSource::InputPath(inputs_path)), InputSourceDto::InputData(data) => Some(InputSource::InputData(data)), - InputSourceDto::InputStream(_) | InputSourceDto::InputNull => None, + InputSourceDto::InputNull => None, }; let (hints_path, hints_stream) = match dto.hints_source { - InputSourceDto::InputPath(hints_path) => (Some(hints_path), false), - InputSourceDto::InputData(_) => (None, false), - InputSourceDto::InputStream(hints_path) => (Some(hints_path), true), - InputSourceDto::InputNull => (None, false), + HintsSourceDto::HintsPath(hints_path) => (Some(hints_path), false), + HintsSourceDto::HintsStream(hints_path) => (Some(hints_path), true), + HintsSourceDto::HintsNull => (None, false), }; ContributionParams { diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 702ad276d..19bddd597 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; use cargo_zisk::commands::{get_proving_key, get_witness_computation_lib}; use proofman::{AggProofs, ContributionsInfo}; use rom_setup::{ @@ -10,7 +10,9 @@ use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; use zisk_common::io::{StreamSource, ZiskStdin}; -use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; +use zisk_distributed_common::{ + AggregationParams, DataCtx, HintsSourceDto, InputSourceDto, JobPhase, WorkerState, +}; use zisk_distributed_common::{ComputeCapacity, JobId, WorkerId}; use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProver}; @@ -529,7 +531,7 @@ impl Worker { prover: &ZiskProver, phase_inputs: ProvePhaseInputs, input_source: InputSourceDto, - hints_source: InputSourceDto, + hints_source: HintsSourceDto, options: ProofOptions, ) -> Result> { let phase = proofman::ProvePhase::Contributions; @@ -544,9 +546,6 @@ impl Worker { let stdin = ZiskStdin::from_vec(input_data); prover.set_stdin(stdin); } - InputSourceDto::InputStream(_) => { - return Err(anyhow!("Input source is in streaming mode but stream not completed")); - } InputSourceDto::InputNull => { let stdin = ZiskStdin::null(); prover.set_stdin(stdin); @@ -554,19 +553,15 @@ impl Worker { } match hints_source { - InputSourceDto::InputPath(hints_uri) => { + HintsSourceDto::HintsPath(hints_uri) => { let hints_stream = StreamSource::from_uri(hints_uri.into())?; prover.set_hints_stream(hints_stream)?; } - InputSourceDto::InputData(hints_data) => { - let hints_stream = StreamSource::from_vec(hints_data); - prover.set_hints_stream(hints_stream)?; - } - InputSourceDto::InputStream(hints_uri) => { + HintsSourceDto::HintsStream(hints_uri) => { let hints_stream = StreamSource::from_uri(hints_uri.into())?; prover.set_hints_stream(hints_stream)?; } - InputSourceDto::InputNull => { + HintsSourceDto::HintsNull => { // No hints to set } } @@ -780,7 +775,7 @@ impl Worker { ProvePhaseInputs, ProofOptions, InputSourceDto, - InputSourceDto, + HintsSourceDto, ) = borsh::from_slice(&bytes[1..]).unwrap(); let result = Self::execute_contribution_task( diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index b9a75b880..526342eee 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -11,8 +11,8 @@ use tonic::transport::Channel; use tonic::Request; use tracing::{error, info}; use zisk_distributed_common::{ - AggProofData, AggregationParams, DataCtx, InputSourceDto, StreamPayloadDto, StreamTypeDto, - WorkerState, + AggProofData, AggregationParams, DataCtx, HintsSourceDto, InputSourceDto, StreamPayloadDto, + StreamTypeDto, WorkerState, }; use zisk_distributed_common::{DataId, JobId}; use zisk_distributed_grpc_api::contribution_params::InputSource; @@ -549,13 +549,13 @@ impl WorkerNodeGrpc { ) .await?; - InputSourceDto::InputPath(hints_uri.to_string_lossy().to_string()) + HintsSourceDto::HintsPath(hints_uri.to_string_lossy().to_string()) } else { // Hints will be streamed - use placeholder, will be updated when stream completes - InputSourceDto::InputStream(params.hints_path.as_ref().unwrap().clone()) + HintsSourceDto::HintsStream(params.hints_path.as_ref().unwrap().clone()) } } else { - InputSourceDto::InputNull + HintsSourceDto::HintsNull }; let data_ctx = @@ -750,9 +750,6 @@ impl WorkerNodeGrpc { let job_id = stream_data_dto.job_id; let stream_type = stream_data_dto.stream_type; - println!("Job ID: {}", job_id.as_string()); - println!("Stream Type: {:?}", stream_type); - // Check the existence of stream buffer based on stream type if stream_type == StreamTypeDto::Start { // Check if buffer already exists From 500fbc1d54f3087867eeadf36c564a202697c7c8 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:21:16 +0000 Subject: [PATCH 122/782] rename HintsModeDto --- distributed/crates/common/src/dto.rs | 27 +++++++++---------- .../crates/coordinator/src/coordinator.rs | 10 +++---- .../crates/grpc-api/src/conversions.rs | 12 ++++----- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 76f61a7a4..4f0072ecd 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -66,14 +66,13 @@ pub struct SystemStatusDto { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[repr(i32)] pub enum InputModeDto { // No input provided - InputModeNone = 0, + InputModeNone, // Input will be provided as a path. - InputModeUri(String) = 1, + InputModeUri(String), // Input will be embedded directly as data. - InputModeData(String) = 2, + InputModeData(String), } impl Display for InputModeDto { @@ -91,25 +90,23 @@ impl Display for InputModeDto { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[repr(i32)] pub enum HintsModeDto { - // No input provided - InputModeNone = 0, - // Input will be provided as a path. First String is the inputs path, - // second String is the precompiles hints path - InputModeUri(String) = 1, - // Input will be streamed via StreamStart/Data/End messages. String contains the file path to stream. - InputModeStream(String) = 2, + /// No hints are provided. + HintsNone, + /// Hints are provided as a complete payload referenced by a URI. + HintsUri(String), + /// Hints will be streamed from the given URI endpoint. + HintsStream(String), } impl Display for HintsModeDto { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - HintsModeDto::InputModeNone => write!(f, "None"), - HintsModeDto::InputModeUri(inputs) => { + HintsModeDto::HintsNone => write!(f, "None"), + HintsModeDto::HintsUri(inputs) => { write!(f, "Path({})", inputs) } - HintsModeDto::InputModeStream(inputs) => { + HintsModeDto::HintsStream(inputs) => { write!(f, "Stream({})", inputs) } } diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 69bf12b1b..44f49dc34 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -626,14 +626,12 @@ impl Coordinator { }; let hints_source = match &job.hints_mode { - HintsModeDto::InputModeUri(ref hints_uri) => { - HintsSourceDto::HintsPath(hints_uri.clone()) - } - HintsModeDto::InputModeStream(hints_uri) => { + HintsModeDto::HintsUri(ref hints_uri) => HintsSourceDto::HintsPath(hints_uri.clone()), + HintsModeDto::HintsStream(hints_uri) => { // Hints will be streamed separately HintsSourceDto::HintsStream(hints_uri.clone()) } - HintsModeDto::InputModeNone => HintsSourceDto::HintsNull, + HintsModeDto::HintsNone => HintsSourceDto::HintsNull, }; // Use Arc to avoid expensive clones @@ -713,7 +711,7 @@ impl Coordinator { cloned_active_workers: Vec, ) -> Result<(), CoordinatorError> { let hints_uri = match &job.hints_mode { - HintsModeDto::InputModeStream(uri) => uri, + HintsModeDto::HintsStream(uri) => uri, _ => unreachable!(), }; let job_id_clone = job.job_id.clone(); diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index a0ee0feb6..054b20969 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -161,9 +161,9 @@ impl From for LaunchProofRequest { }; let (hints_mode, hints_uri) = match dto.hints_mode { - HintsModeDto::InputModeNone => (InputMode::None, None), - HintsModeDto::InputModeUri(hints_uri) => (InputMode::Uri, Some(hints_uri)), - HintsModeDto::InputModeStream(hints_uri) => (InputMode::Stream, Some(hints_uri)), + HintsModeDto::HintsNone => (InputMode::None, None), + HintsModeDto::HintsUri(hints_uri) => (InputMode::Uri, Some(hints_uri)), + HintsModeDto::HintsStream(hints_uri) => (InputMode::Stream, Some(hints_uri)), }; LaunchProofRequest { @@ -204,18 +204,18 @@ impl TryFrom for LaunchProofRequestDto { _ => return Err(anyhow::anyhow!("Invalid inputs_mode for LaunchProofRequestDto")), }, hints_mode: match InputMode::try_from(req.hints_mode).unwrap_or(InputMode::None) { - InputMode::None => HintsModeDto::InputModeNone, + InputMode::None => HintsModeDto::HintsNone, InputMode::Uri => { let hints_uri = req.hints_uri.ok_or_else(|| { anyhow::anyhow!("Hints mode is Path but hints_uri is missing") })?; - HintsModeDto::InputModeUri(hints_uri) + HintsModeDto::HintsUri(hints_uri) } InputMode::Stream => { let hints_uri = req.hints_uri.ok_or_else(|| { anyhow::anyhow!("Hints mode is Stream but hints_uri is missing") })?; - HintsModeDto::InputModeStream(hints_uri) + HintsModeDto::HintsStream(hints_uri) } _ => return Err(anyhow::anyhow!("Invalid hints_mode for LaunchProofRequestDto")), }, From dc4f7657bc2688a9c83aaea20a399883791dae01 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:24:10 +0000 Subject: [PATCH 123/782] rename InputsModeDto --- distributed/crates/common/src/dto.rs | 24 +++++++++---------- distributed/crates/common/src/types.rs | 6 ++--- .../crates/coordinator/src/coordinator.rs | 10 ++++---- .../crates/grpc-api/src/conversions.rs | 12 +++++----- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 4f0072ecd..5d63b4d0f 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -66,23 +66,23 @@ pub struct SystemStatusDto { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum InputModeDto { - // No input provided - InputModeNone, - // Input will be provided as a path. - InputModeUri(String), - // Input will be embedded directly as data. - InputModeData(String), +pub enum InputsModeDto { + // No inputs are provided + InputsNone, + /// Inputs are provided as a complete payload referenced by a URI. + InputsUri(String), + /// Inputs are provided directly as data. + InputsData(String), } -impl Display for InputModeDto { +impl Display for InputsModeDto { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - InputModeDto::InputModeNone => write!(f, "None"), - InputModeDto::InputModeUri(inputs) => { + InputsModeDto::InputsNone => write!(f, "None"), + InputsModeDto::InputsUri(inputs) => { write!(f, "Path({})", inputs) } - InputModeDto::InputModeData(inputs) => { + InputsModeDto::InputsData(inputs) => { write!(f, "Data({})", inputs) } } @@ -116,7 +116,7 @@ impl Display for HintsModeDto { pub struct LaunchProofRequestDto { pub data_id: DataId, pub compute_capacity: u32, - pub inputs_mode: InputModeDto, + pub inputs_mode: InputsModeDto, pub hints_mode: HintsModeDto, pub simulated_node: Option, } diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index b1c0081a9..305129c14 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -16,7 +16,7 @@ use std::{ }; use tracing::error; -use crate::{HintsModeDto, HintsSourceDto, InputModeDto, InputSourceDto}; +use crate::{HintsModeDto, HintsSourceDto, InputSourceDto, InputsModeDto}; /// Job ID wrapper for type safety #[derive( @@ -243,7 +243,7 @@ pub struct Job { pub duration_ms: Option, pub state: JobState, pub data_id: DataId, - pub inputs_mode: InputModeDto, + pub inputs_mode: InputsModeDto, pub hints_mode: HintsModeDto, pub compute_capacity: ComputeCapacity, pub workers: Vec, @@ -260,7 +260,7 @@ pub struct Job { impl Job { pub fn new( data_id: DataId, - inputs_mode: InputModeDto, + inputs_mode: InputsModeDto, hints_mode: HintsModeDto, compute_capacity: ComputeCapacity, selected_workers: Vec, diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 44f49dc34..26f391033 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -55,7 +55,7 @@ use zisk_distributed_common::{ AggParamsDto, AggProofData, ChallengesDto, ComputeCapacity, ContributionParamsDto, CoordinatorMessageDto, DataId, ExecuteTaskRequestDto, ExecuteTaskRequestTypeDto, ExecuteTaskResponseDto, ExecuteTaskResponseResultDataDto, HeartbeatAckDto, HintsModeDto, - HintsSourceDto, InputModeDto, InputSourceDto, Job, JobExecutionMode, JobId, JobPhase, + HintsSourceDto, InputSourceDto, InputsModeDto, Job, JobExecutionMode, JobId, JobPhase, JobResult, JobResultData, JobState, JobStatusDto, JobsListDto, LaunchProofRequestDto, LaunchProofResponseDto, MetricsDto, ProofDto, ProveParamsDto, StatusInfoDto, StreamTypeDto, SystemStatusDto, WorkerErrorDto, WorkerId, WorkerReconnectRequestDto, WorkerRegisterRequestDto, @@ -530,7 +530,7 @@ impl Coordinator { &self, data_id: DataId, required_compute_capacity: ComputeCapacity, - inputs_mode: InputModeDto, + inputs_mode: InputsModeDto, hints_mode: HintsModeDto, simulated_node: Option, ) -> CoordinatorResult { @@ -610,10 +610,10 @@ impl Coordinator { active_workers: &[WorkerId], ) -> CoordinatorResult<()> { let input_source = match job.inputs_mode { - InputModeDto::InputModeUri(ref inputs_uri) => { + InputsModeDto::InputsUri(ref inputs_uri) => { InputSourceDto::InputPath(inputs_uri.clone()) } - InputModeDto::InputModeData(ref inputs_uri) => { + InputsModeDto::InputsData(ref inputs_uri) => { let inputs = tokio::fs::read(inputs_uri).await.map_err(|e| { CoordinatorError::Internal(format!( "Failed to read input data for job {}: {}", @@ -622,7 +622,7 @@ impl Coordinator { })?; InputSourceDto::InputData(inputs) } - InputModeDto::InputModeNone => InputSourceDto::InputNull, + InputsModeDto::InputsNone => InputSourceDto::InputNull, }; let hints_source = match &job.hints_mode { diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index 054b20969..566f21152 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -155,9 +155,9 @@ impl From for SystemStatusResponse { impl From for LaunchProofRequest { fn from(dto: LaunchProofRequestDto) -> Self { let (inputs_mode, inputs_uri) = match dto.inputs_mode { - InputModeDto::InputModeNone => (InputMode::None, None), - InputModeDto::InputModeUri(inputs_uri) => (InputMode::Uri, Some(inputs_uri)), - InputModeDto::InputModeData(inputs_uri) => (InputMode::Data, Some(inputs_uri)), + InputsModeDto::InputsNone => (InputMode::None, None), + InputsModeDto::InputsUri(inputs_uri) => (InputMode::Uri, Some(inputs_uri)), + InputsModeDto::InputsData(inputs_uri) => (InputMode::Data, Some(inputs_uri)), }; let (hints_mode, hints_uri) = match dto.hints_mode { @@ -188,18 +188,18 @@ impl TryFrom for LaunchProofRequestDto { data_id: req.data_id.into(), compute_capacity: req.compute_capacity, inputs_mode: match InputMode::try_from(req.inputs_mode).unwrap_or(InputMode::None) { - InputMode::None => InputModeDto::InputModeNone, + InputMode::None => InputsModeDto::InputsNone, InputMode::Uri => { let inputs_uri = req.inputs_uri.ok_or_else(|| { anyhow::anyhow!("Input mode is Path but inputs_uri is missing") })?; - InputModeDto::InputModeUri(inputs_uri) + InputsModeDto::InputsUri(inputs_uri) } InputMode::Data => { let inputs_uri = req.inputs_uri.ok_or_else(|| { anyhow::anyhow!("Input mode is Data but inputs_uri is missing") })?; - InputModeDto::InputModeData(inputs_uri) + InputsModeDto::InputsData(inputs_uri) } _ => return Err(anyhow::anyhow!("Invalid inputs_mode for LaunchProofRequestDto")), }, From 4d77d798ee3940858ecfb656af818cad133b1569 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:35:39 +0000 Subject: [PATCH 124/782] renamed stream message kind --- distributed/crates/common/src/dto.rs | 7 ++++-- .../crates/coordinator/src/coordinator.rs | 4 ++-- .../crates/coordinator/src/hints_relay.rs | 16 +++++++------ .../crates/grpc-api/src/conversions.rs | 24 +++++++++---------- distributed/crates/worker/src/worker_node.rs | 8 +++---- 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 5d63b4d0f..5ad8bc532 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -149,16 +149,19 @@ pub enum CoordinatorMessageDto { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum StreamTypeDto { +pub enum StreamMessageKind { + /// Marks the beginning of a stream. No payload is expected. Start, + /// Contains a chunk of stream data. Data, + /// Marks the end of a stream. No payload is expected. End, } #[derive(Debug, Clone)] pub struct StreamDataDto { pub job_id: JobId, - pub stream_type: StreamTypeDto, + pub stream_type: StreamMessageKind, pub stream_payload: Option, } diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 26f391033..a7a3d6feb 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -57,7 +57,7 @@ use zisk_distributed_common::{ ExecuteTaskResponseDto, ExecuteTaskResponseResultDataDto, HeartbeatAckDto, HintsModeDto, HintsSourceDto, InputSourceDto, InputsModeDto, Job, JobExecutionMode, JobId, JobPhase, JobResult, JobResultData, JobState, JobStatusDto, JobsListDto, LaunchProofRequestDto, - LaunchProofResponseDto, MetricsDto, ProofDto, ProveParamsDto, StatusInfoDto, StreamTypeDto, + LaunchProofResponseDto, MetricsDto, ProofDto, ProveParamsDto, StatusInfoDto, StreamMessageKind, SystemStatusDto, WorkerErrorDto, WorkerId, WorkerReconnectRequestDto, WorkerRegisterRequestDto, WorkerState, WorkersListDto, }; @@ -720,7 +720,7 @@ impl Coordinator { // Async dispatcher - no blocking, pure async flow for maximum performance let dispatcher = - move |sequence_number: u32, stream_type: StreamTypeDto, payload: Vec| { + move |sequence_number: u32, stream_type: StreamMessageKind, payload: Vec| { use futures::future::join_all; use zisk_distributed_common::{StreamDataDto, StreamPayloadDto}; diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index b34fe0928..db5748807 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -6,10 +6,12 @@ use std::pin::Pin; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use zisk_common::{io::StreamProcessor, PrecompileHint, CTRL_END, CTRL_START, NUM_HINT_TYPES}; -use zisk_distributed_common::StreamTypeDto; +use zisk_distributed_common::StreamMessageKind; type AsyncDispatcher = Arc< - dyn Fn(u32, StreamTypeDto, Vec) -> Pin + Send>> + Send + Sync, + dyn Fn(u32, StreamMessageKind, Vec) -> Pin + Send>> + + Send + + Sync, >; pub struct PrecompileHintsRelay { @@ -21,12 +23,12 @@ pub struct PrecompileHintsRelay { impl PrecompileHintsRelay { pub fn new(dispatcher: F) -> Self where - F: Fn(u32, StreamTypeDto, Vec) -> Fut + Send + Sync + 'static, + F: Fn(u32, StreamMessageKind, Vec) -> Fut + Send + Sync + 'static, Fut: Future + Send + 'static, { let dispatcher = Arc::new( move |seq: u32, - stream_type: StreamTypeDto, + stream_type: StreamMessageKind, payload: Vec| -> Pin + Send>> { Box::pin(dispatcher(seq, stream_type, payload)) @@ -100,7 +102,7 @@ impl PrecompileHintsRelay { fn send_hints_start(&self) { let seq_num = self.sequence_number.fetch_add(1, Ordering::SeqCst); - self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamTypeDto::Start, vec![])); + self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamMessageKind::Start, vec![])); } fn send_hints_data(&self, hints: Vec) { @@ -116,13 +118,13 @@ impl PrecompileHintsRelay { Vec::from_raw_parts(ptr, len, capacity) }; - self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamTypeDto::Data, payload)); + self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamMessageKind::Data, payload)); } fn send_hints_end(&self) { let seq_num = self.sequence_number.fetch_add(1, Ordering::SeqCst); - self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamTypeDto::End, vec![])); + self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamMessageKind::End, vec![])); } } diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index 566f21152..314cbd64e 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -470,22 +470,22 @@ impl From for HeartbeatAckDto { } } -impl From for StreamType { - fn from(dto: StreamTypeDto) -> StreamType { +impl From for StreamType { + fn from(dto: StreamMessageKind) -> StreamType { match dto { - StreamTypeDto::Start => StreamType::Start, - StreamTypeDto::Data => StreamType::Data, - StreamTypeDto::End => StreamType::End, + StreamMessageKind::Start => StreamType::Start, + StreamMessageKind::Data => StreamType::Data, + StreamMessageKind::End => StreamType::End, } } } -impl From for StreamTypeDto { - fn from(stream_type: StreamType) -> StreamTypeDto { +impl From for StreamMessageKind { + fn from(stream_type: StreamType) -> StreamMessageKind { match stream_type { - StreamType::Start => StreamTypeDto::Start, - StreamType::Data => StreamTypeDto::Data, - StreamType::End => StreamTypeDto::End, + StreamType::Start => StreamMessageKind::Start, + StreamType::Data => StreamMessageKind::Data, + StreamType::End => StreamMessageKind::End, } } } @@ -505,8 +505,8 @@ impl From for StreamDataDto { StreamDataDto { job_id: JobId::from(data.job_id), stream_type: StreamType::try_from(data.stream_type) - .map(StreamTypeDto::from) - .unwrap_or(StreamTypeDto::Data), + .map(StreamMessageKind::from) + .unwrap_or(StreamMessageKind::Data), stream_payload: data.payload.map(Into::into), } } diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 526342eee..88b484311 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -11,8 +11,8 @@ use tonic::transport::Channel; use tonic::Request; use tracing::{error, info}; use zisk_distributed_common::{ - AggProofData, AggregationParams, DataCtx, HintsSourceDto, InputSourceDto, StreamPayloadDto, - StreamTypeDto, WorkerState, + AggProofData, AggregationParams, DataCtx, HintsSourceDto, InputSourceDto, StreamMessageKind, + StreamPayloadDto, WorkerState, }; use zisk_distributed_common::{DataId, JobId}; use zisk_distributed_grpc_api::contribution_params::InputSource; @@ -751,7 +751,7 @@ impl WorkerNodeGrpc { let stream_type = stream_data_dto.stream_type; // Check the existence of stream buffer based on stream type - if stream_type == StreamTypeDto::Start { + if stream_type == StreamMessageKind::Start { // Check if buffer already exists match self.stream_buffers.entry(job_id.clone()) { Entry::Occupied(_) => { @@ -763,7 +763,7 @@ impl WorkerNodeGrpc { } return Ok(()); - } else if stream_type == StreamTypeDto::End { + } else if stream_type == StreamMessageKind::End { // Ensure buffer exists if !self.stream_buffers.contains_key(&job_id) { return Err( From 2c932adbc20d9a854ba7010add91b5f867d9e036 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:38:44 +0000 Subject: [PATCH 125/782] remove unnecessary Display code --- distributed/crates/common/src/dto.rs | 30 ------------------- .../crates/coordinator/src/coordinator.rs | 4 +-- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 5ad8bc532..f3f0ba117 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -4,8 +4,6 @@ //! These DTOs serve as the canonical data structures for business logic, separate from external //! representations like gRPC protobuf types or serialization formats. -use std::fmt::Display; - use crate::{ComputeCapacity, DataId, JobId, JobPhase, JobState, WorkerId, WorkerState}; use borsh::{BorshDeserialize, BorshSerialize}; use chrono::{DateTime, Utc}; @@ -75,20 +73,6 @@ pub enum InputsModeDto { InputsData(String), } -impl Display for InputsModeDto { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - InputsModeDto::InputsNone => write!(f, "None"), - InputsModeDto::InputsUri(inputs) => { - write!(f, "Path({})", inputs) - } - InputsModeDto::InputsData(inputs) => { - write!(f, "Data({})", inputs) - } - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum HintsModeDto { /// No hints are provided. @@ -99,20 +83,6 @@ pub enum HintsModeDto { HintsStream(String), } -impl Display for HintsModeDto { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - HintsModeDto::HintsNone => write!(f, "None"), - HintsModeDto::HintsUri(inputs) => { - write!(f, "Path({})", inputs) - } - HintsModeDto::HintsStream(inputs) => { - write!(f, "Stream({})", inputs) - } - } - } -} - pub struct LaunchProofRequestDto { pub data_id: DataId, pub compute_capacity: u32, diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index a7a3d6feb..46ca45684 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -343,7 +343,7 @@ impl Coordinator { .await?; info!( - "[Job] Started {} successfully Inputs: {} Capacity: {} Workers: {}", + "[Job] Started {} successfully Inputs: {:?} Capacity: {} Workers: {}", job.job_id, job.inputs_mode, job.compute_capacity, @@ -1891,7 +1891,7 @@ impl Coordinator { "Steps: N/A".to_string().red().bold() }; info!( - "{} {} ({:.3}s+{:.3}s+{:.3}s) {} Inputs: {}, Capacity: {} ", + "{} {} ({:.3}s+{:.3}s+{:.3}s) {} Inputs: {:?}, Capacity: {} ", header, duration_str, phase1_duration.as_seconds_f32(), From 47f3c1c0c338ec88bf10f9b445041907f8047fad Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:58:16 +0000 Subject: [PATCH 126/782] clippy mv stream.rs to zisk_stream.rs --- common/src/io/stream/mod.rs | 4 ++-- common/src/io/stream/{stream.rs => zisk_stream.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename common/src/io/stream/{stream.rs => zisk_stream.rs} (100%) diff --git a/common/src/io/stream/mod.rs b/common/src/io/stream/mod.rs index e605f5b58..f35168583 100644 --- a/common/src/io/stream/mod.rs +++ b/common/src/io/stream/mod.rs @@ -2,9 +2,9 @@ mod file; mod memory; mod null; mod quic; -mod stream; mod stream_reader; mod stream_writer; +mod zisk_stream; #[cfg(unix)] mod unix_socket; @@ -13,9 +13,9 @@ pub use file::{FileStreamReader, FileStreamWriter}; pub use memory::MemoryStreamReader; pub use null::NullStreamReader; pub use quic::{QuicStreamReader, QuicStreamWriter}; -pub use stream::*; pub use stream_reader::*; pub use stream_writer::*; +pub use zisk_stream::*; #[cfg(unix)] pub use unix_socket::{UnixSocketStreamReader, UnixSocketStreamWriter}; diff --git a/common/src/io/stream/stream.rs b/common/src/io/stream/zisk_stream.rs similarity index 100% rename from common/src/io/stream/stream.rs rename to common/src/io/stream/zisk_stream.rs From 77311362779157d6afafd61d91097ac1867f43c6 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:58:40 +0000 Subject: [PATCH 127/782] clippy secp256k1 --- precompiles/hints/src/secp256k1/curve.rs | 10 +++++----- precompiles/hints/src/secp256k1/scalar.rs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/precompiles/hints/src/secp256k1/curve.rs b/precompiles/hints/src/secp256k1/curve.rs index 9152c1440..a583f5d12 100644 --- a/precompiles/hints/src/secp256k1/curve.rs +++ b/precompiles/hints/src/secp256k1/curve.rs @@ -77,7 +77,7 @@ pub fn secp256k1_double_scalar_mul_with_g( let mut k1_rec = [0u64; 4]; let mut k2_rec = [0u64; 4]; // We do the first iteration separately - let _max_limb = max_limb as usize; + let _max_limb = max_limb; let k1_bit = (k1[_max_limb] >> max_bit) & 1; let k2_bit = (k2[_max_limb] >> max_bit) & 1; assert!(k1_bit == 1 || k2_bit == 1); // At least one of the scalars should start with 1 @@ -135,7 +135,7 @@ pub fn secp256k1_double_scalar_mul_with_g( // Perform the rest of the loop for i in (0..=max_limb).rev() { - let _i = i as usize; + let _i = i; let bit_len = if i == max_limb { max_bit - 1 } else { 63 }; for j in (0..=bit_len).rev() { let k1_bit = (k1[_i] >> j) & 1; @@ -218,11 +218,11 @@ pub fn secp256k1_ecdsa_verify( secp256k1_fn_inv(s, s_inv, hints); let u1: &mut [u64; 4] = &mut [0; 4]; - secp256k1_fn_mul(z, &s_inv, u1, hints); + secp256k1_fn_mul(z, s_inv, u1, hints); let u2: &mut [u64; 4] = &mut [0; 4]; - secp256k1_fn_mul(r, &s_inv, u2, hints); + secp256k1_fn_mul(r, s_inv, u2, hints); - let (is_infinity, res) = secp256k1_double_scalar_mul_with_g(&u1, &u2, pk, hints); + let (is_infinity, res) = secp256k1_double_scalar_mul_with_g(u1, u2, pk, hints); if is_infinity { return false; } diff --git a/precompiles/hints/src/secp256k1/scalar.rs b/precompiles/hints/src/secp256k1/scalar.rs index b22e87057..79b573941 100644 --- a/precompiles/hints/src/secp256k1/scalar.rs +++ b/precompiles/hints/src/secp256k1/scalar.rs @@ -11,7 +11,7 @@ pub fn secp256k1_fn_reduce(x: &[u64; 4], hints: &mut Vec) -> [u64; 4] { } // x·1 + 0 - let mut module: [u64; 4] = N.clone(); + let mut module: [u64; 4] = N; let mut d: [u64; 4] = [0; 4]; arith256_mod_c(x, &O, &Z, &mut module, &mut d); hints.extend_from_slice(&d); @@ -21,7 +21,7 @@ pub fn secp256k1_fn_reduce(x: &[u64; 4], hints: &mut Vec) -> [u64; 4] { pub fn secp256k1_fn_mul(x: &[u64; 4], y: &[u64; 4], result: &mut [u64; 4], hints: &mut Vec) { // x·y + 0 - let mut module: [u64; 4] = N.clone(); + let mut module: [u64; 4] = N; arith256_mod_c(x, y, &Z, &mut module, result); hints.extend_from_slice(result); } @@ -33,7 +33,7 @@ pub fn secp256k1_fn_inv(x: &[u64; 4], x_inv: &mut [u64; 4], hints: &mut Vec hints.extend_from_slice(x_inv); // Check that x·x_inv = 1 (N) - let mut module: [u64; 4] = N.clone(); + let mut module: [u64; 4] = N; let mut d: [u64; 4] = [0; 4]; arith256_mod_c(x, x_inv, &Z, &mut module, &mut d); hints.extend_from_slice(&module); From d3100fd8112d403cf229f44ea6a4b5556f4284a5 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:59:51 +0000 Subject: [PATCH 128/782] improve handler_prove.rs --- .../coordinator/src/cli/handler_prove.rs | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/distributed/crates/coordinator/src/cli/handler_prove.rs b/distributed/crates/coordinator/src/cli/handler_prove.rs index 449506679..7e9f3fdb5 100644 --- a/distributed/crates/coordinator/src/cli/handler_prove.rs +++ b/distributed/crates/coordinator/src/cli/handler_prove.rs @@ -30,30 +30,18 @@ pub async fn handle( let channel = Channel::from_shared(coordinator_url)?.connect().await?; let mut client = ZiskDistributedApiClient::new(channel); - let inputs_mode = if inputs_uri.is_some() { - if direct_inputs { - InputMode::Data - } else { - InputMode::Uri - } - } else { - InputMode::None + let inputs_mode = match inputs_uri { + None => InputMode::None, + Some(_) if direct_inputs => InputMode::Data, + Some(_) => InputMode::Uri, }; - let hints_mode = if hints_uri.is_some() { - // TODO!!!! Rethink this enum usage for hints streaming - // InputMode::Data has sense? How to activate it? - if direct_hints { - InputMode::Stream - } else { - InputMode::Uri - } - } else { - InputMode::None + let hints_mode = match hints_uri { + None => InputMode::None, + Some(_) if direct_hints => InputMode::Stream, + Some(_) => InputMode::Uri, }; - println!("hints_mode: {:?}", hints_mode); - // ID will be id if present, else input file name or random UUID let data_id = if let Some(id) = data_id { id From d1026da4384357f1557fff02e163859446dd740e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 10:15:20 +0000 Subject: [PATCH 129/782] improve proto code --- .../coordinator/src/cli/handler_prove.rs | 9 +++---- .../grpc-api/proto/zisk_distributed_api.proto | 18 +++++++++----- .../crates/grpc-api/src/conversions.rs | 24 +++++++++---------- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/distributed/crates/coordinator/src/cli/handler_prove.rs b/distributed/crates/coordinator/src/cli/handler_prove.rs index 7e9f3fdb5..15e362617 100644 --- a/distributed/crates/coordinator/src/cli/handler_prove.rs +++ b/distributed/crates/coordinator/src/cli/handler_prove.rs @@ -5,10 +5,11 @@ use tonic::transport::Channel; use tracing::{error, info}; use zisk_distributed_coordinator::Config; use zisk_distributed_grpc_api::{ - zisk_distributed_api_client::ZiskDistributedApiClient, InputMode, LaunchProofRequest, + zisk_distributed_api_client::ZiskDistributedApiClient, HintsMode, InputMode, LaunchProofRequest, }; /// Handle the prove subcommand - makes RPC request to coordinator +#[allow(clippy::too_many_arguments)] pub async fn handle( coordinator_url: Option, data_id: Option, @@ -37,9 +38,9 @@ pub async fn handle( }; let hints_mode = match hints_uri { - None => InputMode::None, - Some(_) if direct_hints => InputMode::Stream, - Some(_) => InputMode::Uri, + None => HintsMode::None, + Some(_) if direct_hints => HintsMode::Stream, + Some(_) => HintsMode::Uri, }; // ID will be id if present, else input file name or random UUID diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index 50ae8d948..94e110e5b 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -57,18 +57,24 @@ message LaunchProofRequest { uint32 compute_capacity = 2; InputMode inputs_mode = 3; optional string inputs_uri = 4; - InputMode hints_mode = 5; + HintsMode hints_mode = 5; optional string hints_uri = 6; optional uint32 simulated_node = 7; // If set, indicates this is a simulated worker } -enum InputMode { - INPUT_MODE_NONE = 0; // No input provided - INPUT_MODE_URI = 1; // Input will be provided as an URI - INPUT_MODE_DATA = 2; // Input data will be sent directly - INPUT_MODE_STREAM = 3; // Input data will be sent as a stream + enum InputMode { + INPUT_MODE_NONE = 0; // No input provided + INPUT_MODE_URI = 1; // Input will be provided as an URI + INPUT_MODE_DATA = 2; // Input data will be sent directly } + enum HintsMode { + HINTS_MODE_NONE = 0; // No hints provided + HINTS_MODE_URI = 1; // Hints will be provided as an URI + HINTS_MODE_STREAM = 2; // Hints will be sent as a stream +} + + // ============================================================================ // Admin Commands Response Messages // ============================================================================ diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index 314cbd64e..2b8029d77 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -13,8 +13,8 @@ use crate::{ execute_task_response, job_status_response, jobs_list_response, launch_proof_response, system_status_response, workers_list_response, AggParams, Challenges, ComputeCapacity as GrpcComputeCapacity, ContributionParams, CoordinatorMessage, - ExecuteTaskRequest, ExecuteTaskResponse, Heartbeat, HeartbeatAck, InputMode, JobCancelled, - JobStatus, JobStatusResponse, JobsList, JobsListResponse, LaunchProofRequest, + ExecuteTaskRequest, ExecuteTaskResponse, Heartbeat, HeartbeatAck, HintsMode, InputMode, + JobCancelled, JobStatus, JobStatusResponse, JobsList, JobsListResponse, LaunchProofRequest, LaunchProofResponse, Metrics, Proof, ProofList, ProveParams, Shutdown, StatusInfoResponse, StreamData, StreamPayload, StreamType, SystemStatus, SystemStatusResponse, TaskType, WorkerError, WorkerInfo, WorkerReconnectRequest, WorkerRegisterRequest, WorkerRegisterResponse, @@ -161,9 +161,9 @@ impl From for LaunchProofRequest { }; let (hints_mode, hints_uri) = match dto.hints_mode { - HintsModeDto::HintsNone => (InputMode::None, None), - HintsModeDto::HintsUri(hints_uri) => (InputMode::Uri, Some(hints_uri)), - HintsModeDto::HintsStream(hints_uri) => (InputMode::Stream, Some(hints_uri)), + HintsModeDto::HintsNone => (HintsMode::None, None), + HintsModeDto::HintsUri(hints_uri) => (HintsMode::Uri, Some(hints_uri)), + HintsModeDto::HintsStream(hints_uri) => (HintsMode::Stream, Some(hints_uri)), }; LaunchProofRequest { @@ -191,7 +191,7 @@ impl TryFrom for LaunchProofRequestDto { InputMode::None => InputsModeDto::InputsNone, InputMode::Uri => { let inputs_uri = req.inputs_uri.ok_or_else(|| { - anyhow::anyhow!("Input mode is Path but inputs_uri is missing") + anyhow::anyhow!("Input mode is Uri but inputs_uri is missing") })?; InputsModeDto::InputsUri(inputs_uri) } @@ -201,23 +201,21 @@ impl TryFrom for LaunchProofRequestDto { })?; InputsModeDto::InputsData(inputs_uri) } - _ => return Err(anyhow::anyhow!("Invalid inputs_mode for LaunchProofRequestDto")), }, - hints_mode: match InputMode::try_from(req.hints_mode).unwrap_or(InputMode::None) { - InputMode::None => HintsModeDto::HintsNone, - InputMode::Uri => { + hints_mode: match HintsMode::try_from(req.hints_mode).unwrap_or(HintsMode::None) { + HintsMode::None => HintsModeDto::HintsNone, + HintsMode::Uri => { let hints_uri = req.hints_uri.ok_or_else(|| { - anyhow::anyhow!("Hints mode is Path but hints_uri is missing") + anyhow::anyhow!("Hints mode is Uri but hints_uri is missing") })?; HintsModeDto::HintsUri(hints_uri) } - InputMode::Stream => { + HintsMode::Stream => { let hints_uri = req.hints_uri.ok_or_else(|| { anyhow::anyhow!("Hints mode is Stream but hints_uri is missing") })?; HintsModeDto::HintsStream(hints_uri) } - _ => return Err(anyhow::anyhow!("Invalid hints_mode for LaunchProofRequestDto")), }, simulated_node: req.simulated_node, }) From 789b1bf59fa51b6375218b623be2f0a59879bfa6 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 10:15:56 +0000 Subject: [PATCH 130/782] remove debug println --- distributed/crates/worker/src/worker_node.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 88b484311..6bed16a2b 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -797,11 +797,9 @@ impl WorkerNodeGrpc { } // If equals, process it and check for subsequent buffered chunks - println!("Processing stream chunk {} for job {}", sequence_number, job_id); *next_seq += 1; while let Some(_buffered_data) = stream_buffer.remove(next_seq) { - println!("Processing buffered stream chunk {} for job {}", next_seq, job_id); *next_seq += 1; } From 6fdea99768ad0f3f593d105aede1f0bae94d2c8d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 10:41:37 +0000 Subject: [PATCH 131/782] move quic dependencies to common Cargo.toml --- Cargo.toml | 5 ----- common/Cargo.toml | 8 +++++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1da5f3a47..d7f01ac31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -163,11 +163,6 @@ uuid = { version = "1.0", features = ["serde", "v4"] } chrono = { version = "0.4", features = ["serde"] } sha2 = { version = "0.10.9", features = ["compress"] } -# QUIC networking -quinn = "0.11" -rustls = { version = "0.23", features = ["ring"] } -rcgen = "0.14" - # gRPC dependencies tonic = "0.14" tonic-prost = "0.14" diff --git a/common/Cargo.toml b/common/Cargo.toml index 6403344a0..d7495a748 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -27,10 +27,7 @@ bytemuck = { workspace = true } zstd = { workspace = true } # QUIC networking -quinn = { workspace = true } tokio = { workspace = true } -rustls = { workspace = true } -rcgen = { workspace = true } # Distributed mode (mpi) is only supported on Linux x86_64 [target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] @@ -38,6 +35,11 @@ mpi = { workspace = true } libc = "0.2" +# QUIC networking +quinn = "0.11" +rustls = { version = "0.23", features = ["ring"] } +rcgen = "0.14" + [features] default = [] disable_distributed = [ From d380926463f9e621c7b422a576235ba55d8b4ed5 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 10:52:57 +0000 Subject: [PATCH 132/782] fix typo --- executor/src/hints_shmem.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/executor/src/hints_shmem.rs b/executor/src/hints_shmem.rs index 3cf6bd355..f0f96a474 100644 --- a/executor/src/hints_shmem.rs +++ b/executor/src/hints_shmem.rs @@ -1,4 +1,4 @@ -//! HintsShmem is responsible for writting precompile processed hints to shared memory. +//! HintsShmem is responsible for writing precompile processed hints to shared memory. //! //! It implements the HintsSink trait to receive processed hints and write them to shared memory //! using SharedMemoryWriter instances. From 0c992ba728a7f9f2e33cd59a99a497d81bb93986 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:14:54 +0000 Subject: [PATCH 133/782] rename direct_hints to stream_hints --- distributed/crates/coordinator/src/cli/handler_prove.rs | 4 ++-- distributed/crates/coordinator/src/cli/main.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/distributed/crates/coordinator/src/cli/handler_prove.rs b/distributed/crates/coordinator/src/cli/handler_prove.rs index 15e362617..cabcde4de 100644 --- a/distributed/crates/coordinator/src/cli/handler_prove.rs +++ b/distributed/crates/coordinator/src/cli/handler_prove.rs @@ -16,7 +16,7 @@ pub async fn handle( inputs_uri: Option, hints_uri: Option, direct_inputs: bool, - direct_hints: bool, + stream_hints: bool, compute_capacity: u32, simulated_node: Option, ) -> Result<()> { @@ -39,7 +39,7 @@ pub async fn handle( let hints_mode = match hints_uri { None => HintsMode::None, - Some(_) if direct_hints => HintsMode::Stream, + Some(_) if stream_hints => HintsMode::Stream, Some(_) => HintsMode::Uri, }; diff --git a/distributed/crates/coordinator/src/cli/main.rs b/distributed/crates/coordinator/src/cli/main.rs index 6a3294cba..3c6bb6569 100644 --- a/distributed/crates/coordinator/src/cli/main.rs +++ b/distributed/crates/coordinator/src/cli/main.rs @@ -79,7 +79,7 @@ enum ZiskCoordinatorCommands { /// Whether to send the input data directly #[clap(long, default_value_t = false)] - direct_hints: bool, + stream_hints: bool, /// Compute capacity needed to generate the proof #[arg(long, short, help = "Compute capacity needed to generate the proof")] @@ -102,7 +102,7 @@ async fn main() -> Result<()> { inputs_uri, hints_uri, direct_inputs, - direct_hints, + stream_hints, compute_capacity, simulated_node, }) => { @@ -113,7 +113,7 @@ async fn main() -> Result<()> { inputs_uri, hints_uri, direct_inputs, - direct_hints, + stream_hints, compute_capacity, simulated_node, ) From 0a54feb28d8a369b938c2ee5c4b19e42d0d18669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Sat, 27 Dec 2025 18:17:55 +0000 Subject: [PATCH 134/782] Improving the sha call --- .gitignore | 2 -- Cargo.lock | 1 - core/src/helpers.rs | 24 ++++++------------- precompiles/sha256f/Cargo.toml | 1 - .../sha256f/src/sha256f_gen_mem_inputs.rs | 10 ++------ ziskclib/src/helpers.rs | 24 ++++++------------- .../src/zisklib/lib/sha256f_compress.rs | 19 ++------------- 7 files changed, 18 insertions(+), 63 deletions(-) diff --git a/.gitignore b/.gitignore index 1a3447bf3..6739405b2 100644 --- a/.gitignore +++ b/.gitignore @@ -16,8 +16,6 @@ /lib-c/c/lib /lib-float/c/build /logs -precompiles/keccakf/src/keccakf_fixed.bin -precompiles/sha256f/src/sha256f_fixed.bin state-machines/frequent-ops/src/frequent_ops_fixed.bin state-machines/arith/src/arith_frops_fixed.bin state-machines/binary/src/binary_basic_frops_fixed.bin diff --git a/Cargo.lock b/Cargo.lock index f67dc3b93..b15c48622 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2987,7 +2987,6 @@ dependencies = [ "proofman-macros", "proofman-util", "rayon", - "sha2", "sm-mem", "tracing", "zisk-common", diff --git a/core/src/helpers.rs b/core/src/helpers.rs index 6511b71f8..cc706efb9 100644 --- a/core/src/helpers.rs +++ b/core/src/helpers.rs @@ -1,25 +1,15 @@ use sha2::compress256; + +// TODO: Update when new sha2 version is available + #[allow(deprecated)] use sha2::digest::generic_array::{typenum::U64, GenericArray}; +#[allow(deprecated)] pub fn sha256f(state: &mut [u64; 4], input: &[u64; 8]) { // Convert both the state and the input to appropriate types let state_u32: &mut [u32; 8] = unsafe { &mut *(state.as_mut_ptr() as *mut [u32; 8]) }; - let input_u8 = convert_u64_to_generic_array_bytes(input); - - compress256(state_u32, &[input_u8]); - - // Convert the state back to u64 and write it to the memory address - *state = unsafe { *(state_u32 as *mut [u32; 8] as *mut [u64; 4]) }; -} - -#[allow(deprecated)] -pub fn convert_u64_to_generic_array_bytes(input: &[u64; 8]) -> GenericArray { - let mut out = [0u8; 64]; - for (i, word) in input.iter().enumerate() { - for j in 0..8 { - out[i * 8 + j] = (word >> (56 - j * 8)) as u8; - } - } - GenericArray::::clone_from_slice(&out) + let input_u8: &[GenericArray; 1] = + unsafe { &*(input.as_ptr() as *const [GenericArray; 1]) }; + compress256(state_u32, input_u8); } diff --git a/precompiles/sha256f/Cargo.toml b/precompiles/sha256f/Cargo.toml index 3220332d9..7e666cb27 100644 --- a/precompiles/sha256f/Cargo.toml +++ b/precompiles/sha256f/Cargo.toml @@ -23,7 +23,6 @@ pil-std-lib = { workspace = true } fields = { workspace=true } tracing = { workspace = true } rayon = { workspace = true } -sha2 = { workspace = true } [features] default = [] diff --git a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs index e242775be..a5ad78347 100644 --- a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs +++ b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs @@ -1,10 +1,8 @@ -use sha2::compress256; - use precompiles_common::MemBusHelpers; use std::collections::VecDeque; use zisk_common::MemCollectorInfo; use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; -use zisk_core::convert_u64_to_generic_array_bytes; +use zisk_core::sha256f; #[derive(Debug)] pub struct Sha256MemInputConfig { @@ -28,11 +26,7 @@ pub fn generate_sha256f_mem_inputs( let input: &[u64; 8] = &data[10..18].try_into().unwrap(); // Apply the sha256f function and get the output - let mut state_u32: &mut [u32; 8] = unsafe { &mut *(state.as_mut_ptr() as *mut [u32; 8]) }; - let block = convert_u64_to_generic_array_bytes(input); - compress256(&mut state_u32, &[block]); - - *state = unsafe { *(state_u32 as *mut [u32; 8] as *mut [u64; 4]) }; + sha256f(state, input); // Generate the memory reads/writes let indirect_params = 2; diff --git a/ziskclib/src/helpers.rs b/ziskclib/src/helpers.rs index 6511b71f8..cc706efb9 100644 --- a/ziskclib/src/helpers.rs +++ b/ziskclib/src/helpers.rs @@ -1,25 +1,15 @@ use sha2::compress256; + +// TODO: Update when new sha2 version is available + #[allow(deprecated)] use sha2::digest::generic_array::{typenum::U64, GenericArray}; +#[allow(deprecated)] pub fn sha256f(state: &mut [u64; 4], input: &[u64; 8]) { // Convert both the state and the input to appropriate types let state_u32: &mut [u32; 8] = unsafe { &mut *(state.as_mut_ptr() as *mut [u32; 8]) }; - let input_u8 = convert_u64_to_generic_array_bytes(input); - - compress256(state_u32, &[input_u8]); - - // Convert the state back to u64 and write it to the memory address - *state = unsafe { *(state_u32 as *mut [u32; 8] as *mut [u64; 4]) }; -} - -#[allow(deprecated)] -pub fn convert_u64_to_generic_array_bytes(input: &[u64; 8]) -> GenericArray { - let mut out = [0u8; 64]; - for (i, word) in input.iter().enumerate() { - for j in 0..8 { - out[i * 8 + j] = (word >> (56 - j * 8)) as u8; - } - } - GenericArray::::clone_from_slice(&out) + let input_u8: &[GenericArray; 1] = + unsafe { &*(input.as_ptr() as *const [GenericArray; 1]) }; + compress256(state_u32, input_u8); } diff --git a/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs b/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs index ac0224988..9dac6c976 100644 --- a/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs +++ b/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs @@ -14,23 +14,8 @@ pub unsafe extern "C" fn sha256f_compress_c( let state_64: &mut [u64; 4] = &mut *(state_ptr as *mut [u64; 4]); for i in 0..num_blocks { - let block: &[u8; 64] = &*(blocks_ptr.add(i * 64) as *const [u8; 64]); - let input_u64 = convert_bytes_to_u64(block); - let mut sha256_params = SyscallSha256Params { state: state_64, input: &input_u64 }; + let input_u64: &[u64; 8] = &*(blocks_ptr.add(i * 64) as *const [u64; 8]); + let mut sha256_params = SyscallSha256Params { state: state_64, input: input_u64 }; syscall_sha256_f(&mut sha256_params); } } - -#[inline(always)] -fn convert_bytes_to_u64(input: &[u8; 64]) -> [u64; 8] { - [ - u64::from_be_bytes(input[0..8].try_into().unwrap()), - u64::from_be_bytes(input[8..16].try_into().unwrap()), - u64::from_be_bytes(input[16..24].try_into().unwrap()), - u64::from_be_bytes(input[24..32].try_into().unwrap()), - u64::from_be_bytes(input[32..40].try_into().unwrap()), - u64::from_be_bytes(input[40..48].try_into().unwrap()), - u64::from_be_bytes(input[48..56].try_into().unwrap()), - u64::from_be_bytes(input[56..64].try_into().unwrap()), - ] -} From 5f40d25fb9a07967fc1de802ab5e84fb76b15e2e Mon Sep 17 00:00:00 2001 From: fractasy Date: Sat, 27 Dec 2025 20:09:44 +0100 Subject: [PATCH 135/782] Implement prefixed hints file processor in assembly client --- emulator-asm/src/main.c | 266 +++++++++++++++++++++++++++++++++++----- 1 file changed, 232 insertions(+), 34 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 042ef175d..0e7b4d047 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -1903,6 +1903,25 @@ void client_setup (void) } } +typedef enum { + PrecompileReadMode_NoPrefix, + PrecompileReadMode_Prefixed +} PrecompileReadMode; + +//PrecompileReadMode precompile_read_mode = PrecompileReadMode_NoPrefix; +PrecompileReadMode precompile_read_mode = PrecompileReadMode_Prefixed; + +typedef enum { + PrecompileWriteMode_Full, + PrecompileWriteMode_OnePrecAtATime +} PrecompileWriteMode; + +//PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_Full; +PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_OnePrecAtATime; + +//#define PRECOMPILE_FIXED_SIZE 25 // Keccak-f state size in u64s +#define PRECOMPILE_FIXED_SIZE 4 // SHA-256 state size in u64s + void client_write_precompile_results (void) { int result; @@ -1937,6 +1956,13 @@ void client_write_precompile_results (void) fflush(stderr); exit(-1); } + if ((precompile_data_size & 0x7) != 0) + { + printf("ERROR: Precompile results file (%s) size (%lu) is not a multiple of 8 B\n", precompile_file_name, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } // Go back to the first byte if (fseek(precompile_fp, 0, SEEK_SET) == -1) @@ -1947,50 +1973,222 @@ void client_write_precompile_results (void) exit(-1); } - // Initialize precompile written address to zero - *precompile_written_address = 0; // in u64s - - // Copy in chunks of 25*8 bytes (Keccak-f state size) - uint64_t precompile_read_so_far = 0; - uint64_t data[25]; - while (precompile_read_so_far < (uint64_t)precompile_data_size) - { - // Wait for server to read precompile results - //printf("Waiting for sem_prec_read()\n"); - result = sem_wait(sem_prec_read); - if (result == -1) + assert(precompile_read_mode == PrecompileReadMode_NoPrefix || precompile_read_mode == PrecompileReadMode_Prefixed); + assert(precompile_write_mode == PrecompileWriteMode_Full || precompile_write_mode == PrecompileWriteMode_OnePrecAtATime); + + /*************/ + /* NO PREFIX */ + /*************/ + + if (precompile_read_mode == PrecompileReadMode_NoPrefix) + { + if (precompile_write_mode == PrecompileWriteMode_Full) { - printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + // Check the precompile data size is inside the proper range + if (precompile_data_size > MAX_PRECOMPILE_SIZE) + { + printf("ERROR: Size of precompile results file (%s) is too long (%lu)\n", precompile_file_name, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } - // Number of bytes to read from file and write to shared memory in every loop - uint64_t bytes_to_read = sizeof(data); + // Copy input data into input memory + size_t precompile_read = fread(precompile_results_address, 1, precompile_data_size, precompile_fp); + if (precompile_read != precompile_data_size) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } - // // Copy input data into input memory - size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); - if (precompile_read != bytes_to_read) + // Initialize precompile written address + *precompile_written_address = precompile_data_size >> 3; // in u64s + } + else if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) { - printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); - fflush(stdout); - fflush(stderr); - exit(-1); + // Check the precompile data size is inside the proper range + if (precompile_data_size % (PRECOMPILE_FIXED_SIZE * 8) != 0) + { + printf("ERROR: Size of precompile results file (%s) is not a multiple %u * 8 B\n", precompile_file_name, PRECOMPILE_FIXED_SIZE); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Initialize precompile written address to zero + *precompile_written_address = 0; // in u64s + + // Copy in chunks of PRECOMPILE_FIXED_SIZE*8 bytes (Keccak-f state size) + uint64_t precompile_read_so_far = 0; + uint64_t data[PRECOMPILE_FIXED_SIZE]; + while (precompile_read_so_far < (uint64_t)precompile_data_size) + { + // Wait for server to read precompile results + //printf("Waiting for sem_prec_read()\n"); + result = sem_wait(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Number of bytes to read from file and write to shared memory in every loop + uint64_t bytes_to_read = sizeof(data); + + // Copy input data into input memory + size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); + if (precompile_read != bytes_to_read) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Copy data to shared memory + for (int i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); + precompile_read_so_far += 8; + } + + // Notify server that precompile results are available + *precompile_written_address = precompile_read_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } } + } + + /************/ + /* PREFIXED */ + /************/ - // Copy data to shared memory - for (int i=0; i<25; i++) + else if (precompile_read_mode == PrecompileReadMode_Prefixed) + { +#define CTRL_START 0x00 +#define CTRL_END 0x01 +#define CTRL_CANCEL 0x02 +#define CTRL_ERROR 0x03 +#define HINTS_TYPE_RESULT 0x04 +#define HINTS_TYPE_ECRECOVER 0x05 +#define NUM_HINT_TYPES 0x06 + + uint64_t precompile_read_so_far = 0; + uint64_t precompile_written_so_far = 0; + + while (precompile_read_so_far < (uint64_t)precompile_data_size) { - memcpy(&precompile_results_address[(precompile_read_so_far >> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); - precompile_read_so_far += 8; + uint64_t data; + uint64_t bytes_to_read = sizeof(data); + + // Copy input data into input memory + size_t precompile_read = fread(&data, 1, bytes_to_read, precompile_fp); + if (precompile_read != bytes_to_read) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); + fflush(stdout); + fflush(stderr); + exit(-1); + } + precompile_read_so_far += bytes_to_read; + switch (data >> 32) + { + case CTRL_START: + //printf("Precompile CTRL_START\n"); + assert(precompile_read_so_far == 8); + break; + case CTRL_END: + //printf("Precompile CTRL_END\n"); + assert(precompile_read_so_far == precompile_data_size); + break; + // case CTRL_CANCEL: + // printf("Precompile CTRL_CANCEL\n"); + // break; + // case CTRL_ERROR: + // printf("Precompile CTRL_ERROR\n"); + // break; + case HINTS_TYPE_RESULT: + { + //printf("Precompile HINTS_TYPE_RESULT\n"); + if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) + { + // Wait for server to read precompile results + //printf("Waiting for sem_prec_read()\n"); + result = sem_wait(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + uint64_t result_length = data & 0xFFFFFFFF; + if (result_length > (precompile_data_size - precompile_read_so_far)) + { + printf("ERROR: Precompile HINTS_TYPE_RESULT length=%lu exceeds remaining file size %lu\n", result_length, precompile_data_size - precompile_read_so_far); + fflush(stdout); + fflush(stderr); + exit(-1); + } + //printf("Precompile HINTS_TYPE_RESULT result_length=%lu\n", result_length); + for (uint64_t i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &value, 8); + precompile_read_so_far += 8; + precompile_written_so_far += 8; + //printf(" Precompile result[%lu] = 0x%016lx\n", i, value); + } + + if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) + { + // Notify server that precompile results are available + *precompile_written_address = precompile_written_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + } + break; + // case HINTS_TYPE_ECRECOVER: + // { + // // Not implemented + // printf("Precompile HINTS_TYPE_ECRECOVER not implemented\n"); + // } + // break; + default: + printf("ERROR: Unknown precompile prefix type %lu\n", data >> 32); + fflush(stdout); + fflush(stderr); + exit(-1); + } } - // Notify server that precompile results are available - *precompile_written_address = precompile_read_so_far >> 3; // in u64s + if (precompile_write_mode == PrecompileWriteMode_Full) + { + // Notify server that precompile results are available + *precompile_written_address = precompile_written_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); } // Close the file pointer From 5a375b817a8727cb6a6f184e5d8cd42d4450b503 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 27 Dec 2025 07:21:08 +0000 Subject: [PATCH 136/782] moving process stream data fn to worker.rs --- distributed/crates/worker/src/worker.rs | 80 ++++++++++++++++++- distributed/crates/worker/src/worker_node.rs | 83 +++++--------------- 2 files changed, 99 insertions(+), 64 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 19bddd597..acd61b68a 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -5,13 +5,15 @@ use rom_setup::{ gen_elf_hash, get_elf_bin_file_path, get_elf_data_hash, get_rom_blowup_factor_and_arity, DEFAULT_CACHE_PATH, }; +use std::collections::hash_map::Entry; use std::fs; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_distributed_common::{ - AggregationParams, DataCtx, HintsSourceDto, InputSourceDto, JobPhase, WorkerState, + AggregationParams, DataCtx, HintsSourceDto, InputSourceDto, JobPhase, StreamDataDto, + StreamMessageKind, StreamPayloadDto, WorkerState, }; use zisk_distributed_common::{ComputeCapacity, JobId, WorkerId}; use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProver}; @@ -257,6 +259,8 @@ pub struct Worker { prover: Arc>, prover_config: ProverConfig, + + stream_buffers: HashMap>)>, // (job_id, (next_seq, (seq_number, data))) } impl Worker { @@ -289,6 +293,7 @@ impl Worker { current_computation: None, prover, prover_config, + stream_buffers: HashMap::new(), }) } @@ -324,6 +329,7 @@ impl Worker { current_computation: None, prover, prover_config, + stream_buffers: HashMap::new(), }) } @@ -586,6 +592,78 @@ impl Worker { Ok(challenge) } + pub async fn process_stream_data(&mut self, stream_data: StreamDataDto) -> Result<()> { + let job_id = stream_data.job_id; + let stream_type = stream_data.stream_type; + + // Check the existence of stream buffer based on stream type + if stream_type == StreamMessageKind::Start { + dbg!("Received START for job {}", job_id.clone()); + // Check if buffer already exists + match self.stream_buffers.entry(job_id.clone()) { + Entry::Occupied(_) => { + return Err(anyhow::anyhow!("Received duplicate START for job {}", job_id)); + } + Entry::Vacant(entry) => { + entry.insert((1, HashMap::new())); + } + } + + return Ok(()); + } else if stream_type == StreamMessageKind::End { + dbg!("Received END for job {}", job_id.clone()); + // Ensure buffer exists + if !self.stream_buffers.contains_key(&job_id) { + return Err(anyhow::anyhow!( + "Received {:?} without START for job {}", + stream_type, + job_id, + )); + } + + return Ok(()); + } + + let element = self.stream_buffers.get_mut(&job_id).ok_or_else(|| { + anyhow::anyhow!( + "Received stream data without START for job {} stream type {:?}", + job_id, + stream_type + ) + })?; + + let next_seq = &mut element.0; + let stream_buffer = &mut element.1; + + let StreamPayloadDto { sequence_number, payload: data } = + stream_data.stream_payload.ok_or_else(|| { + anyhow::anyhow!( + "Missing stream payload for job {} stream type {:?}", + job_id, + stream_type + ) + })?; + + // Validate sequence number + if sequence_number != *next_seq { + stream_buffer.insert(sequence_number, data); + return Ok(()); + } + + // TODO!!!!! submit to HintsShmem + + // If equals, process it and check for subsequent buffered chunks + *next_seq += 1; + println!("Received stream data seq {} for job {}", sequence_number, job_id); + + while let Some(_buffered_data) = stream_buffer.remove(next_seq) { + *next_seq += 1; + println!("Processed buffered stream data seq {} for job {}", next_seq, job_id); + } + + Ok(()) + } + pub async fn prove( &self, job: Arc>, diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 6bed16a2b..22a732e53 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -1,8 +1,6 @@ use crate::{worker::ComputationResult, ProverConfig, Worker}; use anyhow::{anyhow, Result}; use proofman::{AggProofs, ContributionsInfo}; -use std::collections::hash_map::Entry; -use std::collections::HashMap; use std::path::Path; use std::{path::PathBuf, time::Duration}; use tokio::sync::mpsc; @@ -11,8 +9,8 @@ use tonic::transport::Channel; use tonic::Request; use tracing::{error, info}; use zisk_distributed_common::{ - AggProofData, AggregationParams, DataCtx, HintsSourceDto, InputSourceDto, StreamMessageKind, - StreamPayloadDto, WorkerState, + AggProofData, AggregationParams, DataCtx, HintsSourceDto, InputSourceDto, StreamDataDto, + WorkerState, }; use zisk_distributed_common::{DataId, JobId}; use zisk_distributed_grpc_api::contribution_params::InputSource; @@ -105,13 +103,11 @@ impl WorkerNodeMpi { pub struct WorkerNodeGrpc { worker_config: WorkerServiceConfig, worker: Worker, - - stream_buffers: HashMap>)>, // (job_id, (next_seq, (seq_number, data))) } impl WorkerNodeGrpc { pub async fn new(worker_config: WorkerServiceConfig, worker: Worker) -> Result { - Ok(Self { worker_config, worker, stream_buffers: HashMap::new() }) + Ok(Self { worker_config, worker }) } pub fn world_rank(&self) -> i32 { @@ -475,6 +471,9 @@ impl WorkerNodeGrpc { } } } + coordinator_message::Payload::StreamData(stream_data) => { + self.handle_stream_data(stream_data).await?; + } coordinator_message::Payload::JobCancelled(cancelled) => { info!("Job {} cancelled: {}", cancelled.job_id, cancelled.reason); @@ -499,9 +498,6 @@ impl WorkerNodeGrpc { tokio::time::sleep(Duration::from_secs(shutdown.grace_period_seconds as u64)).await; return Err(anyhow!("Coordinator requested shutdown: {}", shutdown.reason)); } - coordinator_message::Payload::StreamData(stream_data) => { - self.handle_stream_data(stream_data).await?; - } } Ok(()) @@ -744,65 +740,26 @@ impl WorkerNodeGrpc { } async fn handle_stream_data(&mut self, stream_data: StreamData) -> Result<()> { - use zisk_distributed_common::StreamDataDto; - - let stream_data_dto: StreamDataDto = stream_data.into(); - let job_id = stream_data_dto.job_id; - let stream_type = stream_data_dto.stream_type; - - // Check the existence of stream buffer based on stream type - if stream_type == StreamMessageKind::Start { - // Check if buffer already exists - match self.stream_buffers.entry(job_id.clone()) { - Entry::Occupied(_) => { - return Err(anyhow!("Received duplicate START for job {}", job_id)); - } - Entry::Vacant(entry) => { - entry.insert((1, HashMap::new())); - } - } - - return Ok(()); - } else if stream_type == StreamMessageKind::End { - // Ensure buffer exists - if !self.stream_buffers.contains_key(&job_id) { - return Err( - anyhow!("Received {:?} without START for job {}", stream_type, job_id,), - ); - } - - return Ok(()); + if self.worker.current_job().is_none() { + return Err(anyhow!("Aggregate received without current job context")); } - let element = self.stream_buffers.get_mut(&job_id).ok_or_else(|| { - anyhow!( - "Received stream data without START for job {} stream type {:?}", - job_id, - stream_type - ) - })?; - - let next_seq = &mut element.0; - let stream_buffer = &mut element.1; - - let StreamPayloadDto { sequence_number, payload: data } = - stream_data_dto.stream_payload.ok_or_else(|| { - anyhow!("Missing stream payload for job {} stream type {:?}", job_id, stream_type) - })?; - - // Validate sequence number - if sequence_number != *next_seq { - stream_buffer.insert(sequence_number, data); - return Ok(()); - } + let job = self.worker.current_job().clone().unwrap().clone(); + let current_job_id = job.lock().await.job_id.clone(); - // If equals, process it and check for subsequent buffered chunks - *next_seq += 1; + let stream_data_dto: StreamDataDto = stream_data.into(); + let job_id = stream_data_dto.job_id.clone(); - while let Some(_buffered_data) = stream_buffer.remove(next_seq) { - *next_seq += 1; + if current_job_id != job_id { + return Err(anyhow!( + "Job ID mismatch in Aggregate: expected {}, got {}", + current_job_id.as_string(), + job_id + )); } + self.worker.process_stream_data(stream_data_dto).await?; + Ok(()) } } From 3710786a0ee6e442f124bcb273aa269556d51d22 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sun, 28 Dec 2025 09:23:43 +0000 Subject: [PATCH 137/782] move hints_shmem to emulator-asm --- {executor => emulator-asm/asm-runner}/src/hints_shmem.rs | 4 ++-- emulator-asm/asm-runner/src/lib.rs | 2 ++ executor/src/executor.rs | 4 ++-- executor/src/lib.rs | 2 -- 4 files changed, 6 insertions(+), 6 deletions(-) rename {executor => emulator-asm/asm-runner}/src/hints_shmem.rs (99%) diff --git a/executor/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs similarity index 99% rename from executor/src/hints_shmem.rs rename to emulator-asm/asm-runner/src/hints_shmem.rs index f0f96a474..1b71ffe53 100644 --- a/executor/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -3,11 +3,11 @@ //! It implements the HintsSink trait to receive processed hints and write them to shared memory //! using SharedMemoryWriter instances. -use anyhow::Result; -use asm_runner::{ +use crate::{ sem_available_name, sem_read_name, shmem_control_reader_name, shmem_control_writer_name, shmem_precompile_name, AsmService, AsmServices, SharedMemoryReader, SharedMemoryWriter, }; +use anyhow::Result; use named_sem::NamedSemaphore; use std::sync::Mutex; use tracing::debug; diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 6fc23dc5e..b696a9973 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -17,6 +17,7 @@ mod asm_rh_runner; mod asm_rh_runner_stub; mod asm_runner; mod asm_services; +mod hints_shmem; mod shmem_reader; mod shmem_utils; mod shmem_writer; @@ -38,6 +39,7 @@ pub use asm_rh_runner::*; pub use asm_rh_runner_stub::*; pub use asm_runner::*; pub use asm_services::*; +pub use hints_shmem::*; pub use shmem_reader::*; pub use shmem_utils::*; pub use shmem_writer::*; diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 5b454794a..12d7cdae9 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -20,7 +20,7 @@ //! maintaining clarity and modularity in the computation process. use asm_runner::{ - shmem_input_name, write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, + shmem_input_name, write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, HintsShmem, MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, TaskFactory, }; use fields::PrimeField64; @@ -35,7 +35,7 @@ use tracing::debug; use witness::WitnessComponent; use zisk_common::io::{StreamSource, ZiskIO, ZiskStdin, ZiskStream}; -use crate::{DummyCounter, HintsShmem}; +use crate::DummyCounter; use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; use zisk_common::{ diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 1bfbbad07..936a187d4 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -1,13 +1,11 @@ mod dummy_counter; mod executor; -mod hints_shmem; mod sm_static_bundle; mod static_data_bus; mod static_data_bus_collect; pub use dummy_counter::*; pub use executor::*; -pub use hints_shmem::*; pub use sm_static_bundle::*; pub use static_data_bus::*; pub use static_data_bus_collect::*; From c86bd5d35e99574697684c2512edaf0339e3723d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sun, 28 Dec 2025 09:26:01 +0000 Subject: [PATCH 138/782] rename enum variant --- distributed/crates/common/src/dto.rs | 4 ++-- .../crates/coordinator/src/cli/handler_prove.rs | 4 ++-- distributed/crates/coordinator/src/coordinator.rs | 9 +++++---- .../crates/grpc-api/proto/zisk_distributed_api.proto | 4 ++-- distributed/crates/grpc-api/src/conversions.rs | 12 ++++++------ 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index f3f0ba117..970ed2ed6 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -68,7 +68,7 @@ pub enum InputsModeDto { // No inputs are provided InputsNone, /// Inputs are provided as a complete payload referenced by a URI. - InputsUri(String), + InputsPath(String), /// Inputs are provided directly as data. InputsData(String), } @@ -78,7 +78,7 @@ pub enum HintsModeDto { /// No hints are provided. HintsNone, /// Hints are provided as a complete payload referenced by a URI. - HintsUri(String), + HintsPath(String), /// Hints will be streamed from the given URI endpoint. HintsStream(String), } diff --git a/distributed/crates/coordinator/src/cli/handler_prove.rs b/distributed/crates/coordinator/src/cli/handler_prove.rs index cabcde4de..5e9a726a9 100644 --- a/distributed/crates/coordinator/src/cli/handler_prove.rs +++ b/distributed/crates/coordinator/src/cli/handler_prove.rs @@ -34,13 +34,13 @@ pub async fn handle( let inputs_mode = match inputs_uri { None => InputMode::None, Some(_) if direct_inputs => InputMode::Data, - Some(_) => InputMode::Uri, + Some(_) => InputMode::Path, }; let hints_mode = match hints_uri { None => HintsMode::None, Some(_) if stream_hints => HintsMode::Stream, - Some(_) => HintsMode::Uri, + Some(_) => HintsMode::Path, }; // ID will be id if present, else input file name or random UUID diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 46ca45684..6b376a65f 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -343,9 +343,10 @@ impl Coordinator { .await?; info!( - "[Job] Started {} successfully Inputs: {:?} Capacity: {} Workers: {}", + "[Job] Started {} successfully Inputs: {:?} Hints: {:?} Capacity: {} Workers: {}", job.job_id, job.inputs_mode, + job.hints_mode, job.compute_capacity, job.workers.len(), ); @@ -610,8 +611,8 @@ impl Coordinator { active_workers: &[WorkerId], ) -> CoordinatorResult<()> { let input_source = match job.inputs_mode { - InputsModeDto::InputsUri(ref inputs_uri) => { - InputSourceDto::InputPath(inputs_uri.clone()) + InputsModeDto::InputsPath(ref inputs_path) => { + InputSourceDto::InputPath(inputs_path.clone()) } InputsModeDto::InputsData(ref inputs_uri) => { let inputs = tokio::fs::read(inputs_uri).await.map_err(|e| { @@ -626,7 +627,7 @@ impl Coordinator { }; let hints_source = match &job.hints_mode { - HintsModeDto::HintsUri(ref hints_uri) => HintsSourceDto::HintsPath(hints_uri.clone()), + HintsModeDto::HintsPath(ref hints_uri) => HintsSourceDto::HintsPath(hints_uri.clone()), HintsModeDto::HintsStream(hints_uri) => { // Hints will be streamed separately HintsSourceDto::HintsStream(hints_uri.clone()) diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index 94e110e5b..bddcc041d 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -64,13 +64,13 @@ message LaunchProofRequest { enum InputMode { INPUT_MODE_NONE = 0; // No input provided - INPUT_MODE_URI = 1; // Input will be provided as an URI + INPUT_MODE_PATH = 1; // Input will be provided as a PATH INPUT_MODE_DATA = 2; // Input data will be sent directly } enum HintsMode { HINTS_MODE_NONE = 0; // No hints provided - HINTS_MODE_URI = 1; // Hints will be provided as an URI + HINTS_MODE_PATH = 1; // Hints will be provided as a PATH HINTS_MODE_STREAM = 2; // Hints will be sent as a stream } diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index 2b8029d77..d709e1057 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -156,13 +156,13 @@ impl From for LaunchProofRequest { fn from(dto: LaunchProofRequestDto) -> Self { let (inputs_mode, inputs_uri) = match dto.inputs_mode { InputsModeDto::InputsNone => (InputMode::None, None), - InputsModeDto::InputsUri(inputs_uri) => (InputMode::Uri, Some(inputs_uri)), + InputsModeDto::InputsPath(inputs_path) => (InputMode::Path, Some(inputs_path)), InputsModeDto::InputsData(inputs_uri) => (InputMode::Data, Some(inputs_uri)), }; let (hints_mode, hints_uri) = match dto.hints_mode { HintsModeDto::HintsNone => (HintsMode::None, None), - HintsModeDto::HintsUri(hints_uri) => (HintsMode::Uri, Some(hints_uri)), + HintsModeDto::HintsPath(hints_path) => (HintsMode::Path, Some(hints_path)), HintsModeDto::HintsStream(hints_uri) => (HintsMode::Stream, Some(hints_uri)), }; @@ -189,11 +189,11 @@ impl TryFrom for LaunchProofRequestDto { compute_capacity: req.compute_capacity, inputs_mode: match InputMode::try_from(req.inputs_mode).unwrap_or(InputMode::None) { InputMode::None => InputsModeDto::InputsNone, - InputMode::Uri => { + InputMode::Path => { let inputs_uri = req.inputs_uri.ok_or_else(|| { anyhow::anyhow!("Input mode is Uri but inputs_uri is missing") })?; - InputsModeDto::InputsUri(inputs_uri) + InputsModeDto::InputsPath(inputs_uri) } InputMode::Data => { let inputs_uri = req.inputs_uri.ok_or_else(|| { @@ -204,11 +204,11 @@ impl TryFrom for LaunchProofRequestDto { }, hints_mode: match HintsMode::try_from(req.hints_mode).unwrap_or(HintsMode::None) { HintsMode::None => HintsModeDto::HintsNone, - HintsMode::Uri => { + HintsMode::Path => { let hints_uri = req.hints_uri.ok_or_else(|| { anyhow::anyhow!("Hints mode is Uri but hints_uri is missing") })?; - HintsModeDto::HintsUri(hints_uri) + HintsModeDto::HintsPath(hints_uri) } HintsMode::Stream => { let hints_uri = req.hints_uri.ok_or_else(|| { From 89eea8e98a92057b29e3845f96f8edfd19580581 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sun, 28 Dec 2025 17:48:17 +0000 Subject: [PATCH 139/782] fix error --- distributed/crates/coordinator/src/coordinator.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 6b376a65f..d48c55ba8 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -680,11 +680,8 @@ impl Coordinator { } }); - if matches!(hints_source, HintsSourceDto::HintsStream(_)) { - self.initialize_stream(job, cloned_active_workers)?; - } - - let results: Vec<_> = stream::iter(tasks).buffer_unordered(10).collect().await; + // Process tasks with a concurrency limit + let results: Vec<_> = stream::iter(tasks).buffer_unordered(16).collect().await; // Check for any errors for (worker_id, send_result, state_result) in results { @@ -703,6 +700,10 @@ impl Coordinator { })?; } + if matches!(hints_source, HintsSourceDto::HintsStream(_)) { + self.initialize_stream(job, cloned_active_workers)?; + } + Ok(()) } From a0386f1ed41f67f8f74894b489306a20dec01530 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sun, 28 Dec 2025 17:49:29 +0000 Subject: [PATCH 140/782] fix typo --- distributed/crates/worker/src/worker_node.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 22a732e53..ddd5385aa 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -741,7 +741,7 @@ impl WorkerNodeGrpc { async fn handle_stream_data(&mut self, stream_data: StreamData) -> Result<()> { if self.worker.current_job().is_none() { - return Err(anyhow!("Aggregate received without current job context")); + return Err(anyhow!("Stream data received without current job context")); } let job = self.worker.current_job().clone().unwrap().clone(); @@ -752,7 +752,7 @@ impl WorkerNodeGrpc { if current_job_id != job_id { return Err(anyhow!( - "Job ID mismatch in Aggregate: expected {}, got {}", + "Job ID mismatch in StreamData: expected {}, got {}", current_job_id.as_string(), job_id )); From aa028ec6ac184fbf0446321349b0a349215e5594 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sun, 28 Dec 2025 17:49:52 +0000 Subject: [PATCH 141/782] minor refactor --- distributed/crates/worker/src/worker_node.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index ddd5385aa..3d1ca9168 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -537,7 +537,10 @@ impl WorkerNodeGrpc { }; let hints_source = if params.hints_path.is_some() { - if !params.hints_stream { + if params.hints_stream { + // Hints will be streamed - use placeholder, will be updated when stream completes + HintsSourceDto::HintsStream(params.hints_path.as_ref().unwrap().clone()) + } else { // Validate and get the full path let hints_uri = Self::validate_subdir( &self.worker_config.worker.inputs_folder, @@ -546,9 +549,6 @@ impl WorkerNodeGrpc { .await?; HintsSourceDto::HintsPath(hints_uri.to_string_lossy().to_string()) - } else { - // Hints will be streamed - use placeholder, will be updated when stream completes - HintsSourceDto::HintsStream(params.hints_path.as_ref().unwrap().clone()) } } else { HintsSourceDto::HintsNull From 2d57f7113f9b73b9f992cd9f3372d4c89d1b4042 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sun, 28 Dec 2025 17:50:22 +0000 Subject: [PATCH 142/782] refactor strema hints working --- Cargo.lock | 1 + distributed/crates/worker/Cargo.toml | 2 + distributed/crates/worker/src/worker.rs | 67 +++++++++++++++++-------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19c5657d0..4df0b30b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6020,6 +6020,7 @@ dependencies = [ "config", "fields", "libloading", + "precompiles-hints", "proofman", "proofman-common", "rom-setup", diff --git a/distributed/crates/worker/Cargo.toml b/distributed/crates/worker/Cargo.toml index 58487cd26..a346a19dc 100644 --- a/distributed/crates/worker/Cargo.toml +++ b/distributed/crates/worker/Cargo.toml @@ -33,6 +33,8 @@ rom-setup = { workspace = true } zisk-pil = { workspace = true } zisk-sdk = { workspace = true } +precompiles-hints = { workspace = true } + tonic = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index acd61b68a..ef33532ec 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,5 +1,7 @@ use anyhow::Result; +use asm_runner::HintsShmem; use cargo_zisk::commands::{get_proving_key, get_witness_computation_lib}; +use precompiles_hints::PrecompileHintsProcessor; use proofman::{AggProofs, ContributionsInfo}; use rom_setup::{ gen_elf_hash, get_elf_bin_file_path, get_elf_data_hash, get_rom_blowup_factor_and_arity, @@ -11,6 +13,7 @@ use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; use zisk_common::io::{StreamSource, ZiskStdin}; +use zisk_common::reinterpret_vec; use zisk_distributed_common::{ AggregationParams, DataCtx, HintsSourceDto, InputSourceDto, JobPhase, StreamDataDto, StreamMessageKind, StreamPayloadDto, WorkerState, @@ -261,6 +264,7 @@ pub struct Worker { prover_config: ProverConfig, stream_buffers: HashMap>)>, // (job_id, (next_seq, (seq_number, data))) + hints_processor: Option>, } impl Worker { @@ -294,6 +298,7 @@ impl Worker { prover, prover_config, stream_buffers: HashMap::new(), + hints_processor: None, }) } @@ -330,6 +335,7 @@ impl Worker { prover, prover_config, stream_buffers: HashMap::new(), + hints_processor: None, }) } @@ -487,30 +493,37 @@ impl Worker { let options = self.get_proof_options_partial_contribution(); tokio::spawn(async move { - let mut job = job.lock().await; - let job_id = job.job_id.clone(); + let guard = job.lock().await; + let job_id = guard.job_id.clone(); info!("Computing Contribution for {job_id}"); let proof_info = ProofInfo::new( None, - job.total_compute_units as usize, - job.allocation.clone(), - job.rank_id as usize, + guard.total_compute_units as usize, + guard.allocation.clone(), + guard.rank_id as usize, ); let phase_inputs = proofman::ProvePhaseInputs::Contributions(proof_info); + let inputs_source = guard.data_ctx.input_source.clone(); + let hints_source = guard.data_ctx.hints_source.clone(); + + drop(guard); + let result = Self::execute_contribution_task( job_id.clone(), prover.as_ref(), phase_inputs, - job.data_ctx.input_source.clone(), - job.data_ctx.hints_source.clone(), + inputs_source, + hints_source, options, ) .await; - job.executed_steps = prover.executed_steps(); + let mut guard = job.lock().await; + + guard.executed_steps = prover.executed_steps(); match result { Ok(data) => { @@ -596,9 +609,19 @@ impl Worker { let job_id = stream_data.job_id; let stream_type = stream_data.stream_type; + if self.hints_processor.is_none() { + let base_port = self.prover_config.asm_port; + let local_rank = self.prover.local_rank(); + let unlock_mapped_memory = self.prover_config.unlock_mapped_memory; + let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory)?; + self.hints_processor = Some( + PrecompileHintsProcessor::new(hints_shmem) + .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e))?, + ); + } + // Check the existence of stream buffer based on stream type if stream_type == StreamMessageKind::Start { - dbg!("Received START for job {}", job_id.clone()); // Check if buffer already exists match self.stream_buffers.entry(job_id.clone()) { Entry::Occupied(_) => { @@ -611,7 +634,6 @@ impl Worker { return Ok(()); } else if stream_type == StreamMessageKind::End { - dbg!("Received END for job {}", job_id.clone()); // Ensure buffer exists if !self.stream_buffers.contains_key(&job_id) { return Err(anyhow::anyhow!( @@ -635,7 +657,7 @@ impl Worker { let next_seq = &mut element.0; let stream_buffer = &mut element.1; - let StreamPayloadDto { sequence_number, payload: data } = + let StreamPayloadDto { sequence_number: current_seq, mut payload } = stream_data.stream_payload.ok_or_else(|| { anyhow::anyhow!( "Missing stream payload for job {} stream type {:?}", @@ -644,23 +666,28 @@ impl Worker { ) })?; - // Validate sequence number - if sequence_number != *next_seq { - stream_buffer.insert(sequence_number, data); + // Check if this is the expected sequence number + // If not, buffer it for later processing + if current_seq != *next_seq { + stream_buffer.insert(current_seq, payload); return Ok(()); } - // TODO!!!!! submit to HintsShmem - - // If equals, process it and check for subsequent buffered chunks + // Process the current payload (which has the expected sequence number) + // and increment next_seq to expect the following sequence *next_seq += 1; - println!("Received stream data seq {} for job {}", sequence_number, job_id); - while let Some(_buffered_data) = stream_buffer.remove(next_seq) { + // Check if we have any buffered subsequent payloads waiting + // If so, append them to the current payload in order + while let Some(buffered_data) = stream_buffer.remove(next_seq) { + payload.extend(buffered_data); *next_seq += 1; - println!("Processed buffered stream data seq {} for job {}", next_seq, job_id); } + // Process the hints + let payload = reinterpret_vec(payload)?; + self.hints_processor.as_mut().unwrap().process_hints(&payload, current_seq == 1)?; + Ok(()) } From a80ed5fe8be0fb4681b5908c3634de9044dbb292 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 29 Dec 2025 06:44:35 +0000 Subject: [PATCH 143/782] move stdin related files to a specific folder --- common/src/io/mod.rs | 10 ++-------- common/src/io/{file_stdin.rs => stdin/file.rs} | 0 common/src/io/{memory_stdin.rs => stdin/memory.rs} | 0 common/src/io/stdin/mod.rs | 9 +++++++++ common/src/io/{null_stdin.rs => stdin/null.rs} | 0 common/src/io/{ => stdin}/zisk_stdin.rs | 0 6 files changed, 11 insertions(+), 8 deletions(-) rename common/src/io/{file_stdin.rs => stdin/file.rs} (100%) rename common/src/io/{memory_stdin.rs => stdin/memory.rs} (100%) create mode 100644 common/src/io/stdin/mod.rs rename common/src/io/{null_stdin.rs => stdin/null.rs} (100%) rename common/src/io/{ => stdin}/zisk_stdin.rs (100%) diff --git a/common/src/io/mod.rs b/common/src/io/mod.rs index 81013195e..62e380e24 100644 --- a/common/src/io/mod.rs +++ b/common/src/io/mod.rs @@ -1,11 +1,5 @@ -mod file_stdin; -mod memory_stdin; -mod null_stdin; +mod stdin; mod stream; -mod zisk_stdin; -pub use file_stdin::*; -pub use memory_stdin::*; -pub use null_stdin::*; +pub use stdin::*; pub use stream::*; -pub use zisk_stdin::*; diff --git a/common/src/io/file_stdin.rs b/common/src/io/stdin/file.rs similarity index 100% rename from common/src/io/file_stdin.rs rename to common/src/io/stdin/file.rs diff --git a/common/src/io/memory_stdin.rs b/common/src/io/stdin/memory.rs similarity index 100% rename from common/src/io/memory_stdin.rs rename to common/src/io/stdin/memory.rs diff --git a/common/src/io/stdin/mod.rs b/common/src/io/stdin/mod.rs new file mode 100644 index 000000000..aa38bbece --- /dev/null +++ b/common/src/io/stdin/mod.rs @@ -0,0 +1,9 @@ +mod file; +mod memory; +mod null; +mod zisk_stdin; + +pub use file::*; +pub use memory::*; +pub use null::*; +pub use zisk_stdin::*; diff --git a/common/src/io/null_stdin.rs b/common/src/io/stdin/null.rs similarity index 100% rename from common/src/io/null_stdin.rs rename to common/src/io/stdin/null.rs diff --git a/common/src/io/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs similarity index 100% rename from common/src/io/zisk_stdin.rs rename to common/src/io/stdin/zisk_stdin.rs From 940a9bfe20a0c041b71094888adf4fa4918bef04 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 29 Dec 2025 06:45:05 +0000 Subject: [PATCH 144/782] minor improvements --- common/Cargo.toml | 4 +--- common/src/hints.rs | 7 ++++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/common/Cargo.toml b/common/Cargo.toml index d7495a748..efe5b5400 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -26,9 +26,6 @@ thiserror = { workspace = true } bytemuck = { workspace = true } zstd = { workspace = true } -# QUIC networking -tokio = { workspace = true } - # Distributed mode (mpi) is only supported on Linux x86_64 [target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] mpi = { workspace = true } @@ -36,6 +33,7 @@ mpi = { workspace = true } libc = "0.2" # QUIC networking +tokio = { workspace = true } quinn = "0.11" rustls = { version = "0.23", features = ["ring"] } rcgen = "0.14" diff --git a/common/src/hints.rs b/common/src/hints.rs index 2a6896152..0279115a1 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -84,9 +84,14 @@ pub struct PrecompileHint { impl std::fmt::Debug for PrecompileHint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let data_display = if self.data.len() <= 10 { + format!("{:?}", self.data) + } else { + format!("{:?}... ({} more)", &self.data[..10], self.data.len() - 10) + }; f.debug_struct("PrecompileHint") .field("hint_type", &self.hint_type) - .field("data", &self.data) + .field("data", &data_display) .finish() } } From 2d4ce3617011586fe4b225ebdafbb0e513924ae3 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 29 Dec 2025 08:29:55 +0000 Subject: [PATCH 145/782] minor improvements --- common/src/hints.rs | 3 +-- distributed/crates/worker/src/cli/main.rs | 2 -- precompiles/hints/src/hints_processor.rs | 3 +-- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 0279115a1..20ac78f1f 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -126,8 +126,7 @@ impl PrecompileHint { )); } - // TODO! This creates a new Vec to own the data. Since performance is critical, - // TODO! consider using a slice reference instead. + // Create a new Vec with the hint data. let data = slice[idx + 1..idx + length as usize + 1].to_vec(); Ok(PrecompileHint { hint_type, data }) diff --git a/distributed/crates/worker/src/cli/main.rs b/distributed/crates/worker/src/cli/main.rs index 34ce96265..6e932e18c 100644 --- a/distributed/crates/worker/src/cli/main.rs +++ b/distributed/crates/worker/src/cli/main.rs @@ -231,6 +231,4 @@ fn print_command_info( let std_mode = if debug { "Debug mode" } else { "Standard mode" }; println!("{: >12} {}", "STD".bright_green().bold(), std_mode); - - println!(); } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 654cb8b68..db7759664 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -248,7 +248,7 @@ impl PrecompileHintsProcessor { { let mut queue = self.state.queue.lock().unwrap(); let offset = seq_id - queue.next_drain_seq; - queue.buffer[offset] = Some(Ok(hint.data.clone())); + queue.buffer[offset] = Some(Ok(hint.data)); } // Notify drainer thread @@ -257,7 +257,6 @@ impl PrecompileHintsProcessor { // Spawn processing task let state = Arc::clone(&self.state); self.pool.spawn(move || { - // TODO! Is it necessary? TO increase performance maybe is enough to check error_flag only when storing result // Check if we should stop due to error if state.error_flag.load(Ordering::Acquire) { return; From 374403cf1304e396899769d92f7d03a2334fa89a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 29 Dec 2025 09:04:44 +0000 Subject: [PATCH 146/782] macOS compiling --- common/Cargo.toml | 9 +++--- common/src/io/stream/unix_socket.rs | 30 +++++++++++++++++-- .../asm-runner/src/hints_shmem_stub.rs | 25 ++++++++++++++++ emulator-asm/asm-runner/src/lib.rs | 6 ++++ 4 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 emulator-asm/asm-runner/src/hints_shmem_stub.rs diff --git a/common/Cargo.toml b/common/Cargo.toml index efe5b5400..bd3890a00 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -25,11 +25,6 @@ anyhow = { workspace = true } thiserror = { workspace = true } bytemuck = { workspace = true } zstd = { workspace = true } - -# Distributed mode (mpi) is only supported on Linux x86_64 -[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] -mpi = { workspace = true } - libc = "0.2" # QUIC networking @@ -38,6 +33,10 @@ quinn = "0.11" rustls = { version = "0.23", features = ["ring"] } rcgen = "0.14" +# Distributed mode (mpi) is only supported on Linux x86_64 +[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] +mpi = { workspace = true } + [features] default = [] disable_distributed = [ diff --git a/common/src/io/stream/unix_socket.rs b/common/src/io/stream/unix_socket.rs index 189e1b897..24d391ba2 100644 --- a/common/src/io/stream/unix_socket.rs +++ b/common/src/io/stream/unix_socket.rs @@ -50,9 +50,13 @@ impl UnixSocketStreamReader { use std::os::unix::ffi::OsStrExt; // Create socket with SOCK_SEQPACKET + #[cfg(target_os = "linux")] let sock_fd = unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC, 0) }; + #[cfg(not(target_os = "linux"))] + let sock_fd = unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET, 0) }; + if sock_fd < 0 { return Err(anyhow::anyhow!( "Failed to create socket: {}", @@ -60,12 +64,21 @@ impl UnixSocketStreamReader { )); } + // Set CLOEXEC flag on non-Linux systems + #[cfg(not(target_os = "linux"))] + { + let flags = unsafe { libc::fcntl(sock_fd, libc::F_GETFD) }; + if flags >= 0 { + unsafe { libc::fcntl(sock_fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) }; + } + } + // Connect to the socket path let c_path = CString::new(self.path.as_os_str().as_bytes()).context("Invalid socket path")?; let mut addr: libc::sockaddr_un = unsafe { std::mem::zeroed() }; - addr.sun_family = libc::AF_UNIX as u16; + addr.sun_family = libc::AF_UNIX as _; let path_bytes = c_path.as_bytes_with_nul(); if path_bytes.len() > addr.sun_path.len() { @@ -259,9 +272,13 @@ impl UnixSocketStreamWriter { } // Create socket with SOCK_SEQPACKET for message boundaries + #[cfg(target_os = "linux")] let sock_fd = unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC, 0) }; + #[cfg(not(target_os = "linux"))] + let sock_fd = unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET, 0) }; + if sock_fd < 0 { return Err(anyhow::anyhow!( "Failed to create socket: {}", @@ -269,12 +286,21 @@ impl UnixSocketStreamWriter { )); } + // Set CLOEXEC flag on non-Linux systems + #[cfg(not(target_os = "linux"))] + { + let flags = unsafe { libc::fcntl(sock_fd, libc::F_GETFD) }; + if flags >= 0 { + unsafe { libc::fcntl(sock_fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) }; + } + } + // Bind to the socket path let c_path = CString::new(self.path.as_os_str().as_bytes()).context("Invalid socket path")?; let mut addr: libc::sockaddr_un = unsafe { std::mem::zeroed() }; - addr.sun_family = libc::AF_UNIX as u16; + addr.sun_family = libc::AF_UNIX as _; let path_bytes = c_path.as_bytes_with_nul(); if path_bytes.len() > addr.sun_path.len() { diff --git a/emulator-asm/asm-runner/src/hints_shmem_stub.rs b/emulator-asm/asm-runner/src/hints_shmem_stub.rs new file mode 100644 index 000000000..202d8a35e --- /dev/null +++ b/emulator-asm/asm-runner/src/hints_shmem_stub.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use zisk_common::io::StreamSink; + +/// HintsShmem struct manages the writing of processed precompile hints to shared memory. +pub struct HintsShmem; + +impl HintsShmem { + pub fn new( + _base_port: Option, + _local_rank: i32, + _unlock_mapped_memory: bool, + ) -> Result { + unreachable!( + "HintsShmem::new() is not supported on this platform. Only Linux x86_64 is supported." + ); + } +} + +impl StreamSink for HintsShmem { + fn submit(&self, _processed: Vec) -> anyhow::Result<()> { + unreachable!( + "HintsShmem::submit() is not supported on this platform. Only Linux x86_64 is supported." + ); + } +} diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index b696a9973..f3357c347 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -17,7 +17,10 @@ mod asm_rh_runner; mod asm_rh_runner_stub; mod asm_runner; mod asm_services; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod hints_shmem; +#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] +mod hints_shmem_stub; mod shmem_reader; mod shmem_utils; mod shmem_writer; @@ -39,7 +42,10 @@ pub use asm_rh_runner::*; pub use asm_rh_runner_stub::*; pub use asm_runner::*; pub use asm_services::*; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub use hints_shmem::*; +#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] +pub use hints_shmem_stub::*; pub use shmem_reader::*; pub use shmem_utils::*; pub use shmem_writer::*; From 7e40204183fd1d7f510e009fafa392fe69b05298 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 29 Dec 2025 09:10:25 +0000 Subject: [PATCH 147/782] fix type in distributed banner --- distributed/crates/worker/src/cli/main.rs | 2 ++ distributed/crates/worker/src/worker.rs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distributed/crates/worker/src/cli/main.rs b/distributed/crates/worker/src/cli/main.rs index 6e932e18c..34ce96265 100644 --- a/distributed/crates/worker/src/cli/main.rs +++ b/distributed/crates/worker/src/cli/main.rs @@ -231,4 +231,6 @@ fn print_command_info( let std_mode = if debug { "Debug mode" } else { "Standard mode" }; println!("{: >12} {}", "STD".bright_green().bold(), std_mode); + + println!(); } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index ef33532ec..5c7700a64 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -285,7 +285,6 @@ impl Worker { .verbose(prover_config.verbose) .shared_tables(prover_config.shared_tables) .gpu(prover_config.gpu_params.clone()) - .print_command_info() .build()?, ); @@ -322,7 +321,6 @@ impl Worker { .base_port_opt(prover_config.asm_port) .unlock_mapped_memory(prover_config.unlock_mapped_memory) .gpu(prover_config.gpu_params.clone()) - .print_command_info() .build()?, ); From 71dba03f19f79ad3753df0ceadfcc68362a80788 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 30 Dec 2025 09:07:55 +0000 Subject: [PATCH 148/782] fix in clap argument on coordinator --- distributed/crates/coordinator/src/cli/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distributed/crates/coordinator/src/cli/main.rs b/distributed/crates/coordinator/src/cli/main.rs index 3c6bb6569..9c67047a9 100644 --- a/distributed/crates/coordinator/src/cli/main.rs +++ b/distributed/crates/coordinator/src/cli/main.rs @@ -22,7 +22,7 @@ struct ZiskCoordinatorArgs { port: Option, /// Directory where to save generated proofs - #[arg(long, help = "Directory to save generated proofs", conflicts_with = "no_save_proof")] + #[arg(long, help = "Directory to save generated proofs", conflicts_with = "no_save_proofs")] proofs_dir: Option, /// Disable saving proofs From 77e7a4246c660e165af1abecd4629467a1501e7f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 30 Dec 2025 09:08:13 +0000 Subject: [PATCH 149/782] removed set_hints_stream in worker.rs when relaying stream data --- distributed/crates/worker/src/worker.rs | 7 ++++--- executor/src/executor.rs | 8 +++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 5c7700a64..d9b463eb6 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -502,6 +502,7 @@ impl Worker { guard.allocation.clone(), guard.rank_id as usize, ); + let phase_inputs = proofman::ProvePhaseInputs::Contributions(proof_info); let inputs_source = guard.data_ctx.input_source.clone(); @@ -574,9 +575,9 @@ impl Worker { let hints_stream = StreamSource::from_uri(hints_uri.into())?; prover.set_hints_stream(hints_stream)?; } - HintsSourceDto::HintsStream(hints_uri) => { - let hints_stream = StreamSource::from_uri(hints_uri.into())?; - prover.set_hints_stream(hints_stream)?; + HintsSourceDto::HintsStream(_hints_uri) => { + // let hints_stream = StreamSource::from_uri(hints_uri.into())?; + // prover.set_hints_stream(hints_stream)?; } HintsSourceDto::HintsNull => { // No hints to set diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 12d7cdae9..7d0a54ac7 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -1208,11 +1208,9 @@ impl WitnessComponent for ZiskExecutor { self.stats.set_start_time(Instant::now()); // Process and write precompile atomically - self.hints_stream - .lock() - .unwrap() - .start_stream() - .expect("Failed to write hints to shared memory"); + if let Ok(mut hints_stream) = self.hints_stream.lock() { + let _ = hints_stream.start_stream(); + } // Process the ROM to collect the Minimal Traces timer_start_info!(COMPUTE_MINIMAL_TRACE); From 8419a982651b1d3bec191bd12b59afe91b01799f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 30 Dec 2025 16:56:11 +0000 Subject: [PATCH 150/782] added feature hints --- Cargo.lock | 1 + precompiles/hints/Cargo.toml | 4 + precompiles/hints/src/hints_processor.rs | 53 +-- precompiles/hints/src/lib.rs | 2 - precompiles/hints/src/secp256k1/curve.rs | 231 ------------- precompiles/hints/src/secp256k1/mod.rs | 5 - precompiles/hints/src/secp256k1/scalar.rs | 42 --- ziskos/entrypoint/Cargo.toml | 8 +- .../entrypoint/src/syscalls/arith256_mod.rs | 5 +- .../entrypoint/src/syscalls/secp256k1_add.rs | 5 +- .../entrypoint/src/syscalls/secp256k1_dbl.rs | 5 +- .../src/zisklib/fcalls/msb_pos_256.rs | 6 +- .../src/zisklib/fcalls/secp256k1_fn_inv.rs | 13 +- .../src/zisklib/fcalls/secp256k1_fp_inv.rs | 2 +- .../src/zisklib/fcalls/secp256k1_fp_sqrt.rs | 6 +- .../src/zisklib/lib/secp256k1/curve.rs | 316 +++++++++++++++--- .../src/zisklib/lib/secp256k1/field.rs | 156 +++++++-- .../src/zisklib/lib/secp256k1/scalar.rs | 132 ++++++-- 18 files changed, 582 insertions(+), 410 deletions(-) delete mode 100644 precompiles/hints/src/secp256k1/curve.rs delete mode 100644 precompiles/hints/src/secp256k1/mod.rs delete mode 100644 precompiles/hints/src/secp256k1/scalar.rs diff --git a/Cargo.lock b/Cargo.lock index 4df0b30b7..d7d1e260c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6157,6 +6157,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", + "precompiles-helpers", "rand 0.8.5", "serde", "static_assertions", diff --git a/precompiles/hints/Cargo.toml b/precompiles/hints/Cargo.toml index 965405ef6..95f2e6ce9 100644 --- a/precompiles/hints/Cargo.toml +++ b/precompiles/hints/Cargo.toml @@ -23,3 +23,7 @@ zisk-common = { workspace = true } [[bin]] name = "hints-socket-server" path = "src/bin/hints_socket_server.rs" + +[features] +default = [] +hints = ["ziskos/hints"] diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index db7759664..199e210c2 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -16,9 +16,6 @@ use zisk_common::{ PrecompileHint, CTRL_CANCEL, CTRL_END, CTRL_ERROR, CTRL_START, HINTS_TYPE_ECRECOVER, HINTS_TYPE_RESULT, NUM_HINT_TYPES, }; -use ziskos::syscalls::SyscallPoint256; - -use crate::secp256k1_ecdsa_verify; /// Ordered result buffer with drain state. /// @@ -263,7 +260,7 @@ impl PrecompileHintsProcessor { } // Process the hint - let result = Self::process_hint(&hint); + let result = Self::process_hint(hint); // Store result and try to drain let mut queue = state.queue.lock().unwrap(); @@ -422,9 +419,10 @@ impl PrecompileHintsProcessor { /// /// * `Ok(Vec)` - The processed result for this hint /// * `Err` - If the hint type is unknown - fn process_hint(hint: &PrecompileHint) -> Result> { + #[inline] + fn process_hint(hint: PrecompileHint) -> Result> { let result = match hint.hint_type { - HINTS_TYPE_RESULT => Self::process_hint_result(hint)?, + HINTS_TYPE_RESULT => hint.data, HINTS_TYPE_ECRECOVER => Self::process_hint_ecrecover(hint)?, _ => { return Err(anyhow::anyhow!("Unknown hint type: {}", hint.hint_type)); @@ -434,17 +432,18 @@ impl PrecompileHintsProcessor { Ok(result) } - /// Processes a [`HINTS_TYPE_RESULT`] hint. - /// - /// This is a pass-through handler that simply returns the hint data as-is. - /// Used when the hint already contains the precomputed result. - fn process_hint_result(hint: &PrecompileHint) -> Result> { - Ok(hint.data.to_vec()) - } - /// Processes a [`HINTS_TYPE_ECRECOVER`] hint. - fn process_hint_ecrecover(hint: &PrecompileHint) -> Result> { - const EXPECTED_LEN: usize = 8 + 4 + 4 + 4; // pk(8) + z(4) + r(4) + s(4) + #[inline] + fn process_hint_ecrecover(hint: PrecompileHint) -> Result> { + const PK_SIZE: usize = 8; // x(4) + y(4) + const Z_SIZE: usize = 4; + const R_SIZE: usize = 4; + const S_SIZE: usize = 4; + const EXPECTED_LEN: usize = PK_SIZE + Z_SIZE + R_SIZE + S_SIZE; + + const Z_OFFSET: usize = PK_SIZE; + const R_OFFSET: usize = Z_OFFSET + Z_SIZE; + const S_OFFSET: usize = R_OFFSET + R_SIZE; if hint.data.len() != EXPECTED_LEN { return Err(anyhow::anyhow!( @@ -454,17 +453,25 @@ impl PrecompileHintsProcessor { )); } + #[allow(unused_mut)] let mut processed_hints = Vec::new(); - // Safety: We've validated the length above + // Safety: We've validated that hint.len() == 20, so all slice accesses are in bounds. unsafe { let ptr = hint.data.as_ptr(); - let pk = &*(ptr as *const SyscallPoint256); - let z = &*(ptr.add(8) as *const [u64; 4]); - let r = &*(ptr.add(12) as *const [u64; 4]); - let s = &*(ptr.add(16) as *const [u64; 4]); - - secp256k1_ecdsa_verify(pk, z, r, s, &mut processed_hints); + let pk = &*(ptr as *const u64); + let z = &*(ptr.add(Z_OFFSET) as *const u64); + let r = &*(ptr.add(R_OFFSET) as *const u64); + let s = &*(ptr.add(S_OFFSET) as *const u64); + + ziskos::zisklib::secp256k1_ecdsa_verify_c( + pk, + z, + r, + s, + #[cfg(feature = "hints")] + &mut processed_hints, + ); } Ok(processed_hints) diff --git a/precompiles/hints/src/lib.rs b/precompiles/hints/src/lib.rs index 81a4691ef..3d39d0006 100644 --- a/precompiles/hints/src/lib.rs +++ b/precompiles/hints/src/lib.rs @@ -1,5 +1,3 @@ mod hints_processor; -mod secp256k1; pub use hints_processor::PrecompileHintsProcessor; -pub use secp256k1::*; diff --git a/precompiles/hints/src/secp256k1/curve.rs b/precompiles/hints/src/secp256k1/curve.rs deleted file mode 100644 index a583f5d12..000000000 --- a/precompiles/hints/src/secp256k1/curve.rs +++ /dev/null @@ -1,231 +0,0 @@ -use crate::{secp256k1_fn_inv, secp256k1_fn_mul, secp256k1_fn_reduce}; -use ziskos::{ - syscalls::SyscallPoint256, - zisklib::{ - constants::{G_X, G_Y}, - eq, - fcalls_impl::msb_pos_256::msb_pos_256, - }, -}; - -/// Given points `p1` and `p2`, performs the point addition `p1 + p2` and assigns the result to `p1`. -/// It assumes that `p1` and `p2` are from the Secp256k1 curve, that `p1,p2 != 𝒪` and that `p2 != p1,-p1` -fn add_points_assign(p1: &mut SyscallPoint256, p2: &SyscallPoint256, hints: &mut Vec) { - let p1_local: [u64; 8] = - [p1.x[0], p1.x[1], p1.x[2], p1.x[3], p1.y[0], p1.y[1], p1.y[2], p1.y[3]]; - let p2_local: [u64; 8] = - [p2.x[0], p2.x[1], p2.x[2], p2.x[3], p2.y[0], p2.y[1], p2.y[2], p2.y[3]]; - let mut p3 = [0u64; 8]; - precompiles_helpers::secp256k1_add(&p1_local, &p2_local, &mut p3); - p1.x = p3[0..4].try_into().unwrap(); - p1.y = p3[4..8].try_into().unwrap(); - hints.extend_from_slice(&p3); -} - -/// Given a point `p1`, performs the point doubling `2·p1` and assigns the result to `p1`. -/// It assumes that `p1` is from the Secp256k1 curve and that `p1 != 𝒪` -/// -/// Note: We don't need to assume that 2·p1 != 𝒪 because there are not points of order 2 on the Secp256k1 curve -fn double_point_assign(p1: &mut SyscallPoint256, hints: &mut Vec) { - let p: [u64; 8] = [p1.x[0], p1.x[1], p1.x[2], p1.x[3], p1.y[0], p1.y[1], p1.y[2], p1.y[3]]; - let mut p3 = [0u64; 8]; - precompiles_helpers::secp256k1_dbl(&p, &mut p3); - p1.x = p3[0..4].try_into().unwrap(); - p1.y = p3[4..8].try_into().unwrap(); - hints.extend_from_slice(&p3); -} - -/// Given points `p1` and `p2`, performs the point addition `p1 + p2` and assigns the result to `p1`. -/// It assumes that `p1` and `p2` are from the Secp256k1 curve, that `p2 != 𝒪` -fn add_points_complete_assign( - p1: &mut SyscallPoint256, - p1_is_infinity: &mut bool, - p2: &SyscallPoint256, - hints: &mut Vec, -) { - if p1.x != p2.x { - add_points_assign(p1, p2, hints); - } else if p1.y == p2.y { - double_point_assign(p1, hints); - } else { - *p1_is_infinity = true; - } -} - -/// Given a point `p` and scalars `k1` and `k2`, computes the double scalar multiplication `k1·G + k2·p` -/// It assumes that `k1,k2 ∈ [1, N-1]` and that `p != 𝒪` -pub fn secp256k1_double_scalar_mul_with_g( - k1: &[u64; 4], - k2: &[u64; 4], - p: &SyscallPoint256, - hints: &mut Vec, -) -> (bool, SyscallPoint256) { - // Start by precomputing g + p - let mut gp = SyscallPoint256 { x: G_X, y: G_Y }; - let mut gp_is_infinity = false; - add_points_complete_assign(&mut gp, &mut gp_is_infinity, p, hints); - - // Hint the maximum length between the binary representations of k1 and k2 - // We will verify the output by recomposing both k1 and k2 - // Moreover, we should check that the first received bit (of either k1 or k2) is 1 - let (max_limb, max_bit) = msb_pos_256(k1, k2); - - // Perform the loop, based on the binary representation of k1 and k2 - // Start at 𝒪 - let mut res = SyscallPoint256 { x: [0u64; 4], y: [0u64; 4] }; - let mut res_is_infinity = true; - let mut k1_rec = [0u64; 4]; - let mut k2_rec = [0u64; 4]; - // We do the first iteration separately - let _max_limb = max_limb; - let k1_bit = (k1[_max_limb] >> max_bit) & 1; - let k2_bit = (k2[_max_limb] >> max_bit) & 1; - assert!(k1_bit == 1 || k2_bit == 1); // At least one of the scalars should start with 1 - if (k1_bit == 0) && (k2_bit == 1) { - // If res is 𝒪, set res = p; otherwise, double res and add p - if res_is_infinity { - res.x = p.x; - res.y = p.y; - res_is_infinity = false; - } else { - double_point_assign(&mut res, hints); - add_points_complete_assign(&mut res, &mut res_is_infinity, p, hints); - } - - // Update k2_rec - k2_rec[_max_limb] |= 1 << max_bit; - } else if (k1_bit == 1) && (k2_bit == 0) { - // If res is 𝒪, set res = g; otherwise, double res and add g - if res_is_infinity { - res.x = G_X; - res.y = G_Y; - res_is_infinity = false; - } else { - double_point_assign(&mut res, hints); - add_points_complete_assign( - &mut res, - &mut res_is_infinity, - &SyscallPoint256 { x: G_X, y: G_Y }, - hints, - ); - } - - // Update k1_rec - k1_rec[_max_limb] |= 1 << max_bit; - } else if (k1_bit == 1) && (k2_bit == 1) { - if res_is_infinity { - // If (g + p) is 𝒪, do nothing; otherwise set res = (g + p) - if !gp_is_infinity { - res.x = gp.x; - res.y = gp.y; - res_is_infinity = false; - } - } else { - // If (g + p) is 𝒪, simply double res; otherwise double res and add (g + p) - double_point_assign(&mut res, hints); - if !gp_is_infinity { - add_points_complete_assign(&mut res, &mut res_is_infinity, &gp, hints); - } - } - - // Update k1_rec and k2_rec - k1_rec[_max_limb] |= 1 << max_bit; - k2_rec[_max_limb] |= 1 << max_bit; - } - - // Perform the rest of the loop - for i in (0..=max_limb).rev() { - let _i = i; - let bit_len = if i == max_limb { max_bit - 1 } else { 63 }; - for j in (0..=bit_len).rev() { - let k1_bit = (k1[_i] >> j) & 1; - let k2_bit = (k2[_i] >> j) & 1; - - if (k1_bit == 0) && (k2_bit == 0) { - // If res is 𝒪, do nothing; otherwise, double - if !res_is_infinity { - double_point_assign(&mut res, hints); - } - } else if (k1_bit == 0) && (k2_bit == 1) { - // If res is 𝒪, set res = p; otherwise, double res and add p - if res_is_infinity { - res.x = p.x; - res.y = p.y; - res_is_infinity = false; - } else { - double_point_assign(&mut res, hints); - add_points_complete_assign(&mut res, &mut res_is_infinity, p, hints); - } - - // Update k2_rec - k2_rec[_i] |= 1 << j; - } else if (k1_bit == 1) && (k2_bit == 0) { - // If res is 𝒪, set res = g; otherwise, double res and add g - if res_is_infinity { - res.x = G_X; - res.y = G_Y; - res_is_infinity = false; - } else { - double_point_assign(&mut res, hints); - add_points_complete_assign( - &mut res, - &mut res_is_infinity, - &SyscallPoint256 { x: G_X, y: G_Y }, - hints, - ); - } - - // Update k1_rec - k1_rec[_i] |= 1 << j; - } else if (k1_bit == 1) && (k2_bit == 1) { - if res_is_infinity { - // If (g + p) is 𝒪, do nothing; otherwise set res = (g + p) - if !gp_is_infinity { - res.x = gp.x; - res.y = gp.y; - res_is_infinity = false; - } - } else { - // If (g + p) is 𝒪, simply double res; otherwise double res and add (g + p) - double_point_assign(&mut res, hints); - if !gp_is_infinity { - add_points_complete_assign(&mut res, &mut res_is_infinity, &gp, hints); - } - } - - // Update k1_rec and k2_rec - k1_rec[_i] |= 1 << j; - k2_rec[_i] |= 1 << j; - } - } - } - - // Check that the recomposed scalars are the same as the received scalars - assert_eq!(k1_rec, *k1); - assert_eq!(k2_rec, *k2); - - (res_is_infinity, res) -} - -pub fn secp256k1_ecdsa_verify( - pk: &SyscallPoint256, - z: &[u64; 4], - r: &[u64; 4], - s: &[u64; 4], - hints: &mut Vec, -) -> bool { - let s_inv: &mut [u64; 4] = &mut [0; 4]; - secp256k1_fn_inv(s, s_inv, hints); - - let u1: &mut [u64; 4] = &mut [0; 4]; - secp256k1_fn_mul(z, s_inv, u1, hints); - let u2: &mut [u64; 4] = &mut [0; 4]; - secp256k1_fn_mul(r, s_inv, u2, hints); - - let (is_infinity, res) = secp256k1_double_scalar_mul_with_g(u1, u2, pk, hints); - if is_infinity { - return false; - } - - eq(&secp256k1_fn_reduce(&res.x, hints), r) -} diff --git a/precompiles/hints/src/secp256k1/mod.rs b/precompiles/hints/src/secp256k1/mod.rs deleted file mode 100644 index bedb88ae1..000000000 --- a/precompiles/hints/src/secp256k1/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod curve; -mod scalar; - -pub use curve::*; -pub use scalar::*; diff --git a/precompiles/hints/src/secp256k1/scalar.rs b/precompiles/hints/src/secp256k1/scalar.rs deleted file mode 100644 index 79b573941..000000000 --- a/precompiles/hints/src/secp256k1/scalar.rs +++ /dev/null @@ -1,42 +0,0 @@ -use lib_c::{arith256_mod_c, secp256k1_fn_inv_c}; -use ziskos::zisklib::lib::secp256k1::constants::N; -use ziskos::zisklib::lt; - -const Z: [u64; 4] = [0, 0, 0, 0]; -const O: [u64; 4] = [0, 0, 0, 0]; - -pub fn secp256k1_fn_reduce(x: &[u64; 4], hints: &mut Vec) -> [u64; 4] { - if lt(x, &N) { - return *x; - } - - // x·1 + 0 - let mut module: [u64; 4] = N; - let mut d: [u64; 4] = [0; 4]; - arith256_mod_c(x, &O, &Z, &mut module, &mut d); - hints.extend_from_slice(&d); - - d -} - -pub fn secp256k1_fn_mul(x: &[u64; 4], y: &[u64; 4], result: &mut [u64; 4], hints: &mut Vec) { - // x·y + 0 - let mut module: [u64; 4] = N; - arith256_mod_c(x, y, &Z, &mut module, result); - hints.extend_from_slice(result); -} - -/// Inverts a non-zero element `x` -pub fn secp256k1_fn_inv(x: &[u64; 4], x_inv: &mut [u64; 4], hints: &mut Vec) { - // Hint the inverse - secp256k1_fn_inv_c(x, x_inv); - hints.extend_from_slice(x_inv); - - // Check that x·x_inv = 1 (N) - let mut module: [u64; 4] = N; - let mut d: [u64; 4] = [0; 4]; - arith256_mod_c(x, x_inv, &Z, &mut module, &mut d); - hints.extend_from_slice(&module); - hints.extend_from_slice(&d); - assert_eq!(d, [0x1, 0x0, 0x0, 0x0]); -} diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index 5974a5968..5fdfe7a39 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -14,6 +14,8 @@ num-bigint = { workspace = true } num-integer = { workspace = true } num-traits = { workspace = true } +precompiles-helpers = { workspace = true } + lazy_static = "1.5.0" static_assertions = "1.1" rand = "0.8.5" @@ -21,4 +23,8 @@ getrandom = { version = "0.2", features = ["custom"] } cfg-if = "1.0" tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } -bincode = "2.0" \ No newline at end of file +bincode = "2.0" + +[features] +default = [] +hints = [] \ No newline at end of file diff --git a/ziskos/entrypoint/src/syscalls/arith256_mod.rs b/ziskos/entrypoint/src/syscalls/arith256_mod.rs index 60e9f6bcb..2dd545283 100644 --- a/ziskos/entrypoint/src/syscalls/arith256_mod.rs +++ b/ziskos/entrypoint/src/syscalls/arith256_mod.rs @@ -35,7 +35,10 @@ pub struct SyscallArith256ModParams<'a> { /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_arith256_mod(params: &mut SyscallArith256ModParams) { +pub extern "C" fn syscall_arith256_mod( + params: &mut SyscallArith256ModParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x802, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/secp256k1_add.rs b/ziskos/entrypoint/src/syscalls/secp256k1_add.rs index bdc4ba335..d775d2d7b 100644 --- a/ziskos/entrypoint/src/syscalls/secp256k1_add.rs +++ b/ziskos/entrypoint/src/syscalls/secp256k1_add.rs @@ -34,7 +34,10 @@ pub struct SyscallSecp256k1AddParams<'a> { /// The resulting point will have both coordinates in the range of the Secp256k1 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_secp256k1_add(params: &mut SyscallSecp256k1AddParams) { +pub extern "C" fn syscall_secp256k1_add( + params: &mut SyscallSecp256k1AddParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x803, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs b/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs index a5788f6b1..9a24908f9 100644 --- a/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs +++ b/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs @@ -26,7 +26,10 @@ use super::point::SyscallPoint256; /// The resulting point will have both coordinates in the range of the Secp256k1 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_secp256k1_dbl(p1: &mut SyscallPoint256) { +pub extern "C" fn syscall_secp256k1_dbl( + p1: &mut SyscallPoint256, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x804, p1); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs index 8af583b12..20f60bab7 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs @@ -7,7 +7,11 @@ cfg_if! { } } #[allow(unused_variables)] -pub fn fcall_msb_pos_256(x: &[u64; 4], y: &[u64; 4]) -> (u64, u64) { +pub fn fcall_msb_pos_256( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> (u64, u64) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs index 51dceae11..728ebe11a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs @@ -23,9 +23,18 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_secp256k1_fn_inv(p_value: &[u64; 4]) -> [u64; 4] { +pub fn fcall_secp256k1_fn_inv( + p_value: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + #[cfg(feature = "hints")] + { + unimplemented!(); + } + unimplemented!(); // Change this line by returning the native result + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 4); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs index 84ad3c4ae..a7ef601e5 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs @@ -24,7 +24,7 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_secp256k1_fp_inv(p_value: &[u64; 4]) -> [u64; 4] { +pub fn fcall_secp256k1_fp_inv(p_value: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec,) -> [u64; 4] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs index ed64347aa..daf088134 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs @@ -24,7 +24,11 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_secp256k1_fp_sqrt(p_value: &[u64; 4], parity: u64) -> [u64; 5] { +pub fn fcall_secp256k1_fp_sqrt( + p_value: &[u64; 4], + parity: u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 5] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index 43c600604..30391e16f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -1,3 +1,5 @@ +use std::hint; + use crate::{ syscalls::{ syscall_secp256k1_add, syscall_secp256k1_dbl, SyscallPoint256, SyscallSecp256k1AddParams, @@ -15,27 +17,57 @@ use super::{ }; /// Converts a non-zero point `p` on the Secp256k1 curve from projective coordinates to affine coordinates -pub fn secp256k1_to_affine(p: &[u64; 12]) -> [u64; 8] { +pub fn secp256k1_to_affine( + p: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 8] { let z: [u64; 4] = p[8..12].try_into().unwrap(); // Point at infinity cannot be converted to affine debug_assert!(z != [0u64; 4], "Cannot convert point at infinity to affine"); - let zinv = secp256k1_fp_inv(&z); - let zinv_sq = secp256k1_fp_square(&zinv); + let zinv = secp256k1_fp_inv( + &z, + #[cfg(feature = "hints")] + hints, + ); + let zinv_sq = secp256k1_fp_square( + &zinv, + #[cfg(feature = "hints")] + hints, + ); let x: [u64; 4] = p[0..4].try_into().unwrap(); let y: [u64; 4] = p[4..8].try_into().unwrap(); - let x_res = secp256k1_fp_mul(&x, &zinv_sq); - let mut y_res = secp256k1_fp_mul(&y, &zinv_sq); - y_res = secp256k1_fp_mul(&y_res, &zinv); + let x_res = secp256k1_fp_mul( + &x, + &zinv_sq, + #[cfg(feature = "hints")] + hints, + ); + let mut y_res = secp256k1_fp_mul( + &y, + &zinv_sq, + #[cfg(feature = "hints")] + hints, + ); + y_res = secp256k1_fp_mul( + &y_res, + &zinv, + #[cfg(feature = "hints")] + hints, + ); [x_res[0], x_res[1], x_res[2], x_res[3], y_res[0], y_res[1], y_res[2], y_res[3]] } /// Checks if two points `p1` and `p2` on the Secp256k1 curve in projective coordinates are equal -pub fn secp256k1_eq_projective(p1: &[u64; 12], p2: &[u64; 12]) -> bool { +pub fn secp256k1_eq_projective( + p1: &[u64; 12], + p2: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { // In essence given two points in projective form p1 = (x₁z₁,y₁z₁,z₁) and p2 = (x₂z₂,y₂z₂,z₂) // We can simply multiply p1 by z2 and p2 by z1 to get tuples: // p1 = (x₁z₁z₂,y₁z₁z₂,z₁z₂) and p2 = (x₂z₂z₁,y₂z₂z₁,z₂z₁) @@ -47,14 +79,34 @@ pub fn secp256k1_eq_projective(p1: &[u64; 12], p2: &[u64; 12]) -> bool { let y2 = p2[4..8].try_into().unwrap(); let z2 = p2[8..12].try_into().unwrap(); - let lhs_x = secp256k1_fp_mul(x1, z2); - let rhs_x = secp256k1_fp_mul(x2, z1); + let lhs_x = secp256k1_fp_mul( + x1, + z2, + #[cfg(feature = "hints")] + hints, + ); + let rhs_x = secp256k1_fp_mul( + x2, + z1, + #[cfg(feature = "hints")] + hints, + ); if !eq(&lhs_x, &rhs_x) { return false; } - let lhs_y = secp256k1_fp_mul(y1, z2); - let rhs_y = secp256k1_fp_mul(y2, z1); + let lhs_y = secp256k1_fp_mul( + y1, + z2, + #[cfg(feature = "hints")] + hints, + ); + let rhs_y = secp256k1_fp_mul( + y2, + z1, + #[cfg(feature = "hints")] + hints, + ); if !eq(&lhs_y, &rhs_y) { return false; } @@ -64,7 +116,11 @@ pub fn secp256k1_eq_projective(p1: &[u64; 12], p2: &[u64; 12]) -> bool { /// Given a x-coordinate `x_bytes` and a parity `y_is_odd`, /// this function decompresses the point on the secp256k1 curve. -pub fn secp256k1_decompress(x_bytes: &[u8; 32], y_is_odd: bool) -> (([u64; 4], [u64; 4]), bool) { +pub fn secp256k1_decompress( + x_bytes: &[u8; 32], + y_is_odd: bool, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> (([u64; 4], [u64; 4]), bool) { // Convert the x-coordinate from BEu8 to LEu64 let mut x = [0u64; 4]; for i in 0..32 { @@ -72,10 +128,29 @@ pub fn secp256k1_decompress(x_bytes: &[u8; 32], y_is_odd: bool) -> (([u64; 4], [ } // Calculate the y-coordinate of the point: y = sqrt(x³ + 7) - let x_sq = secp256k1_fp_square(&x); - let x_cb = secp256k1_fp_mul(&x_sq, &x); - let y_sq = secp256k1_fp_add(&x_cb, &E_B); - let (y, has_sqrt) = secp256k1_fp_sqrt(&y_sq, y_is_odd as u64); + let x_sq = secp256k1_fp_square( + &x, + #[cfg(feature = "hints")] + hints, + ); + let x_cb = secp256k1_fp_mul( + &x_sq, + &x, + #[cfg(feature = "hints")] + hints, + ); + let y_sq = secp256k1_fp_add( + &x_cb, + &E_B, + #[cfg(feature = "hints")] + hints, + ); + let (y, has_sqrt) = secp256k1_fp_sqrt( + &y_sq, + y_is_odd as u64, + #[cfg(feature = "hints")] + hints, + ); if !has_sqrt { return (([0u64; 4], [0u64; 4]), false); } @@ -89,17 +164,29 @@ pub fn secp256k1_decompress(x_bytes: &[u8; 32], y_is_odd: bool) -> (([u64; 4], [ /// Given points `p1` and `p2`, performs the point addition `p1 + p2` and assigns the result to `p1`. /// It assumes that `p1` and `p2` are from the Secp256k1 curve, that `p1,p2 != 𝒪` and that `p2 != p1,-p1` -fn add_points_assign(p1: &mut SyscallPoint256, p2: &SyscallPoint256) { +fn add_points_assign( + p1: &mut SyscallPoint256, + p2: &SyscallPoint256, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let mut params = SyscallSecp256k1AddParams { p1, p2 }; - syscall_secp256k1_add(&mut params); + syscall_secp256k1_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); } /// Given a point `p1`, performs the point doubling `2·p1` and assigns the result to `p1`. /// It assumes that `p1` is from the Secp256k1 curve and that `p1 != 𝒪` /// /// Note: We don't need to assume that 2·p1 != 𝒪 because there are not points of order 2 on the Secp256k1 curve -fn double_point_assign(p1: &mut SyscallPoint256) { - syscall_secp256k1_dbl(p1); +fn double_point_assign(p1: &mut SyscallPoint256, #[cfg(feature = "hints")] hints: &mut Vec) { + syscall_secp256k1_dbl( + p1, + #[cfg(feature = "hints")] + hints, + ); } /// Given points `p1` and `p2`, performs the point addition `p1 + p2` and assigns the result to `p1`. @@ -108,11 +195,21 @@ fn add_points_complete_assign( p1: &mut SyscallPoint256, p1_is_infinity: &mut bool, p2: &SyscallPoint256, + #[cfg(feature = "hints")] hints: &mut Vec, ) { if p1.x != p2.x { - add_points_assign(p1, p2); + add_points_assign( + p1, + p2, + #[cfg(feature = "hints")] + hints, + ); } else if p1.y == p2.y { - double_point_assign(p1); + double_point_assign( + p1, + #[cfg(feature = "hints")] + hints, + ); } else { *p1_is_infinity = true; } @@ -124,11 +221,18 @@ pub fn secp256k1_double_scalar_mul_with_g( k1: &[u64; 4], k2: &[u64; 4], p: &SyscallPoint256, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> (bool, SyscallPoint256) { // Start by precomputing g + p let mut gp = SyscallPoint256 { x: G_X, y: G_Y }; let mut gp_is_infinity = false; - add_points_complete_assign(&mut gp, &mut gp_is_infinity, p); + add_points_complete_assign( + &mut gp, + &mut gp_is_infinity, + p, + #[cfg(feature = "hints")] + hints, + ); let one = [1u64, 0, 0, 0]; if *k1 == one && *k2 == one { @@ -140,7 +244,12 @@ pub fn secp256k1_double_scalar_mul_with_g( // Hint the maximum length between the binary representations of k1 and k2 // We will verify the output by recomposing both k1 and k2 // Moreover, we should check that the first received bit (of either k1 or k2) is 1 - let (max_limb, max_bit) = fcall_msb_pos_256(k1, k2); + let (max_limb, max_bit) = fcall_msb_pos_256( + k1, + k2, + #[cfg(feature = "hints")] + hints, + ); // Perform the loop, based on the binary representation of k1 and k2 @@ -165,8 +274,18 @@ pub fn secp256k1_double_scalar_mul_with_g( res.y = p.y; res_is_infinity = false; } else { - double_point_assign(&mut res); - add_points_complete_assign(&mut res, &mut res_is_infinity, p); + double_point_assign( + &mut res, + #[cfg(feature = "hints")] + hints, + ); + add_points_complete_assign( + &mut res, + &mut res_is_infinity, + p, + #[cfg(feature = "hints")] + hints, + ); } // Update k2_rec @@ -178,11 +297,17 @@ pub fn secp256k1_double_scalar_mul_with_g( res.y = G_Y; res_is_infinity = false; } else { - double_point_assign(&mut res); + double_point_assign( + &mut res, + #[cfg(feature = "hints")] + hints, + ); add_points_complete_assign( &mut res, &mut res_is_infinity, &SyscallPoint256 { x: G_X, y: G_Y }, + #[cfg(feature = "hints")] + hints, ); } @@ -198,9 +323,19 @@ pub fn secp256k1_double_scalar_mul_with_g( } } else { // If (g + p) is 𝒪, simply double res; otherwise double res and add (g + p) - double_point_assign(&mut res); + double_point_assign( + &mut res, + #[cfg(feature = "hints")] + hints, + ); if !gp_is_infinity { - add_points_complete_assign(&mut res, &mut res_is_infinity, &gp); + add_points_complete_assign( + &mut res, + &mut res_is_infinity, + &gp, + #[cfg(feature = "hints")] + hints, + ); } } @@ -228,7 +363,11 @@ pub fn secp256k1_double_scalar_mul_with_g( if (k1_bit == 0) && (k2_bit == 0) { // If res is 𝒪, do nothing; otherwise, double if !res_is_infinity { - double_point_assign(&mut res); + double_point_assign( + &mut res, + #[cfg(feature = "hints")] + hints, + ); } } else if (k1_bit == 0) && (k2_bit == 1) { // If res is 𝒪, set res = p; otherwise, double res and add p @@ -237,8 +376,18 @@ pub fn secp256k1_double_scalar_mul_with_g( res.y = p.y; res_is_infinity = false; } else { - double_point_assign(&mut res); - add_points_complete_assign(&mut res, &mut res_is_infinity, p); + double_point_assign( + &mut res, + #[cfg(feature = "hints")] + hints, + ); + add_points_complete_assign( + &mut res, + &mut res_is_infinity, + p, + #[cfg(feature = "hints")] + hints, + ); } // Update k2_rec @@ -250,11 +399,17 @@ pub fn secp256k1_double_scalar_mul_with_g( res.y = G_Y; res_is_infinity = false; } else { - double_point_assign(&mut res); + double_point_assign( + &mut res, + #[cfg(feature = "hints")] + hints, + ); add_points_complete_assign( &mut res, &mut res_is_infinity, &SyscallPoint256 { x: G_X, y: G_Y }, + #[cfg(feature = "hints")] + hints, ); } @@ -270,9 +425,19 @@ pub fn secp256k1_double_scalar_mul_with_g( } } else { // If (g + p) is 𝒪, simply double res; otherwise double res and add (g + p) - double_point_assign(&mut res); + double_point_assign( + &mut res, + #[cfg(feature = "hints")] + hints, + ); if !gp_is_infinity { - add_points_complete_assign(&mut res, &mut res_is_infinity, &gp); + add_points_complete_assign( + &mut res, + &mut res_is_infinity, + &gp, + #[cfg(feature = "hints")] + hints, + ); } } @@ -296,18 +461,46 @@ pub fn secp256k1_ecdsa_verify( z: &[u64; 4], r: &[u64; 4], s: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> bool { - let s_inv = secp256k1_fn_inv(s); - - let u1 = secp256k1_fn_mul(z, &s_inv); - let u2 = secp256k1_fn_mul(r, &s_inv); - - let (is_infinity, res) = secp256k1_double_scalar_mul_with_g(&u1, &u2, pk); + let s_inv = secp256k1_fn_inv( + s, + #[cfg(feature = "hints")] + hints, + ); + + let u1 = secp256k1_fn_mul( + z, + &s_inv, + #[cfg(feature = "hints")] + hints, + ); + let u2 = secp256k1_fn_mul( + r, + &s_inv, + #[cfg(feature = "hints")] + hints, + ); + + let (is_infinity, res) = secp256k1_double_scalar_mul_with_g( + &u1, + &u2, + pk, + #[cfg(feature = "hints")] + hints, + ); if is_infinity { return false; } - eq(&secp256k1_fn_reduce(&res.x), r) + eq( + &secp256k1_fn_reduce( + &res.x, + #[cfg(feature = "hints")] + hints, + ), + r, + ) } /// # Safety @@ -316,9 +509,17 @@ pub fn secp256k1_ecdsa_verify( /// /// Returns 1 on success, 0 if point is at infinity #[no_mangle] -pub unsafe extern "C" fn secp256k1_to_affine_c(p_ptr: *const u64, out_ptr: *mut u64) { +pub unsafe extern "C" fn secp256k1_to_affine_c( + p_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let p: &[u64; 12] = &*(p_ptr as *const [u64; 12]); - let result = secp256k1_to_affine(p); + let result = secp256k1_to_affine( + p, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = result[0]; *out_ptr.add(1) = result[1]; @@ -340,10 +541,16 @@ pub unsafe extern "C" fn secp256k1_decompress_c( x_bytes_ptr: *const u8, y_is_odd: u8, out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> u8 { let x_bytes: &[u8; 32] = &*(x_bytes_ptr as *const [u8; 32]); - let ((x, y), success) = secp256k1_decompress(x_bytes, y_is_odd != 0); + let ((x, y), success) = secp256k1_decompress( + x_bytes, + y_is_odd != 0, + #[cfg(feature = "hints")] + hints, + ); if !success { return 0; @@ -374,6 +581,7 @@ pub unsafe extern "C" fn secp256k1_double_scalar_mul_with_g_c( k2_ptr: *const u64, p_ptr: *const u64, out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> bool { let k1: &[u64; 4] = &*(k1_ptr as *const [u64; 4]); let k2: &[u64; 4] = &*(k2_ptr as *const [u64; 4]); @@ -383,7 +591,13 @@ pub unsafe extern "C" fn secp256k1_double_scalar_mul_with_g_c( y: [*p_ptr.add(4), *p_ptr.add(5), *p_ptr.add(6), *p_ptr.add(7)], }; - let (is_infinity, res) = secp256k1_double_scalar_mul_with_g(k1, k2, &p); + let (is_infinity, res) = secp256k1_double_scalar_mul_with_g( + k1, + k2, + &p, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = res.x[0]; *out_ptr.add(1) = res.x[1]; @@ -410,6 +624,7 @@ pub unsafe extern "C" fn secp256k1_ecdsa_verify_c( z_ptr: *const u64, r_ptr: *const u64, s_ptr: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> bool { let pk = SyscallPoint256 { x: [*pk_ptr.add(0), *pk_ptr.add(1), *pk_ptr.add(2), *pk_ptr.add(3)], @@ -419,5 +634,12 @@ pub unsafe extern "C" fn secp256k1_ecdsa_verify_c( let r: &[u64; 4] = &*(r_ptr as *const [u64; 4]); let s: &[u64; 4] = &*(s_ptr as *const [u64; 4]); - secp256k1_ecdsa_verify(&pk, z, r, s) + secp256k1_ecdsa_verify( + &pk, + z, + r, + s, + #[cfg(feature = "hints")] + hints, + ) } diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs index 04e4380ce..081aa7f7d 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs @@ -5,7 +5,10 @@ use crate::{ use super::constants::{NQR, P, P_MINUS_ONE}; -pub fn secp256k1_fp_reduce(x: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fp_reduce( + x: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { if lt(x, &P) { return *x; } @@ -18,19 +21,34 @@ pub fn secp256k1_fp_reduce(x: &[u64; 4]) -> [u64; 4] { module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256k1_fp_add(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fp_add( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·1 + y let mut params = SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256k1_fp_negate(x: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fp_negate( + x: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·(-1) + 0 let mut params = SyscallArith256ModParams { a: x, @@ -39,21 +57,36 @@ pub fn secp256k1_fp_negate(x: &[u64; 4]) -> [u64; 4] { module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256k1_fp_mul(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fp_mul( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·y + 0 let mut params = SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); - + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256k1_fp_mul_scalar(x: &[u64; 4], scalar: u64) -> [u64; 4] { +pub fn secp256k1_fp_mul_scalar( + x: &[u64; 4], + scalar: u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·scalar + 0 let mut params = SyscallArith256ModParams { a: x, @@ -62,24 +95,38 @@ pub fn secp256k1_fp_mul_scalar(x: &[u64; 4], scalar: u64) -> [u64; 4] { module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256k1_fp_square(x: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fp_square( + x: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·x + 0 let mut params = SyscallArith256ModParams { a: x, b: x, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); - + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Inverts a non-zero element `x` -pub fn secp256k1_fp_inv(x: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fp_inv(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { // Hint the inverse - let x_inv = fcall_secp256k1_fp_inv(x); + let x_inv = fcall_secp256k1_fp_inv( + x, + #[cfg(feature = "hints")] + hints, + ); // Check that x·x_inv = 1 (P) let mut params = SyscallArith256ModParams { @@ -89,15 +136,28 @@ pub fn secp256k1_fp_inv(x: &[u64; 4]) -> [u64; 4] { module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); assert_eq!(*params.d, [0x1, 0x0, 0x0, 0x0]); x_inv } -pub fn secp256k1_fp_sqrt(x: &[u64; 4], parity: u64) -> ([u64; 4], bool) { +pub fn secp256k1_fp_sqrt( + x: &[u64; 4], + parity: u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> ([u64; 4], bool) { // Hint the sqrt - let hint = fcall_secp256k1_fp_sqrt(x, parity); + let hint = fcall_secp256k1_fp_sqrt( + x, + parity, + #[cfg(feature = "hints")] + hints, + ); let is_qr = hint[0] == 1; let sqrt = hint[1..5].try_into().unwrap(); @@ -109,7 +169,11 @@ pub fn secp256k1_fp_sqrt(x: &[u64; 4], parity: u64) -> ([u64; 4], bool) { module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); if is_qr { // Check that sqrt * sqrt == x @@ -117,7 +181,12 @@ pub fn secp256k1_fp_sqrt(x: &[u64; 4], parity: u64) -> ([u64; 4], bool) { (sqrt, true) } else { // Check that sqrt * sqrt == x * NQR - let nqr = secp256k1_fp_mul(x, &NQR); + let nqr = secp256k1_fp_mul( + x, + &NQR, + #[cfg(feature = "hints")] + hints, + ); assert_eq!(*params.d, nqr); (sqrt, false) } @@ -127,7 +196,11 @@ pub fn secp256k1_fp_sqrt(x: &[u64; 4], parity: u64) -> ([u64; 4], bool) { /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s #[no_mangle] -pub unsafe extern "C" fn secp256k1_fp_reduce_c(x_ptr: *const u64, out_ptr: *mut u64) { +pub unsafe extern "C" fn secp256k1_fp_reduce_c( + x_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); if lt(x, &P) { @@ -145,7 +218,11 @@ pub unsafe extern "C" fn secp256k1_fp_reduce_c(x_ptr: *const u64, out_ptr: *mut module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -162,13 +239,18 @@ pub unsafe extern "C" fn secp256k1_fp_add_c( x_ptr: *const u64, y_ptr: *const u64, out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); let mut params = SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -180,7 +262,11 @@ pub unsafe extern "C" fn secp256k1_fp_add_c( /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s #[no_mangle] -pub unsafe extern "C" fn secp256k1_fp_negate_c(x_ptr: *const u64, out_ptr: *mut u64) { +pub unsafe extern "C" fn secp256k1_fp_negate_c( + x_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); let mut params = SyscallArith256ModParams { @@ -190,7 +276,11 @@ pub unsafe extern "C" fn secp256k1_fp_negate_c(x_ptr: *const u64, out_ptr: *mut module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -207,13 +297,18 @@ pub unsafe extern "C" fn secp256k1_fp_mul_c( x_ptr: *const u64, y_ptr: *const u64, out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); let mut params = SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -230,6 +325,7 @@ pub unsafe extern "C" fn secp256k1_fp_mul_scalar_c( x_ptr: *const u64, scalar: u64, out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); @@ -240,7 +336,11 @@ pub unsafe extern "C" fn secp256k1_fp_mul_scalar_c( module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs index 8ee7f6303..53e4ef923 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs @@ -5,7 +5,10 @@ use crate::{ use super::constants::{N, N_MINUS_ONE}; -pub fn secp256k1_fn_reduce(x: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fn_reduce( + x: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { if lt(x, &N) { return *x; } @@ -18,21 +21,33 @@ pub fn secp256k1_fn_reduce(x: &[u64; 4]) -> [u64; 4] { module: &N, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256k1_fn_add(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fn_add( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·1 + y let mut params = SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256k1_fn_neg(x: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fn_neg(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { // x·(-1) + 0 let mut params = SyscallArith256ModParams { a: x, @@ -41,33 +56,57 @@ pub fn secp256k1_fn_neg(x: &[u64; 4]) -> [u64; 4] { module: &N, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256k1_fn_sub(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fn_sub( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // y·(-1) + x let mut params = SyscallArith256ModParams { a: y, b: &N_MINUS_ONE, c: x, module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256k1_fn_mul(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fn_mul( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·y + 0 let mut params = SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Inverts a non-zero element `x` -pub fn secp256k1_fn_inv(x: &[u64; 4]) -> [u64; 4] { +pub fn secp256k1_fn_inv(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { // Hint the inverse - let x_inv = fcall_secp256k1_fn_inv(x); + let x_inv = fcall_secp256k1_fn_inv( + x, + #[cfg(feature = "hints")] + hints, + ); // Check that x·x_inv = 1 (N) let mut params = SyscallArith256ModParams { @@ -77,7 +116,11 @@ pub fn secp256k1_fn_inv(x: &[u64; 4]) -> [u64; 4] { module: &N, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); assert_eq!(*params.d, [0x1, 0x0, 0x0, 0x0]); x_inv @@ -87,7 +130,11 @@ pub fn secp256k1_fn_inv(x: &[u64; 4]) -> [u64; 4] { /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s #[no_mangle] -pub unsafe extern "C" fn secp256k1_fn_reduce_c(x_ptr: *const u64, out_ptr: *mut u64) { +pub unsafe extern "C" fn secp256k1_fn_reduce_c( + x_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); if lt(x, &N) { @@ -105,7 +152,11 @@ pub unsafe extern "C" fn secp256k1_fn_reduce_c(x_ptr: *const u64, out_ptr: *mut module: &N, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -122,13 +173,18 @@ pub unsafe extern "C" fn secp256k1_fn_add_c( x_ptr: *const u64, y_ptr: *const u64, out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); let mut params = SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -140,7 +196,11 @@ pub unsafe extern "C" fn secp256k1_fn_add_c( /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s #[no_mangle] -pub unsafe extern "C" fn secp256k1_fn_neg_c(x_ptr: *const u64, out_ptr: *mut u64) { +pub unsafe extern "C" fn secp256k1_fn_neg_c( + x_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); let mut params = SyscallArith256ModParams { @@ -150,7 +210,11 @@ pub unsafe extern "C" fn secp256k1_fn_neg_c(x_ptr: *const u64, out_ptr: *mut u64 module: &N, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -167,13 +231,18 @@ pub unsafe extern "C" fn secp256k1_fn_sub_c( x_ptr: *const u64, y_ptr: *const u64, out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); let mut params = SyscallArith256ModParams { a: y, b: &N_MINUS_ONE, c: x, module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -190,13 +259,18 @@ pub unsafe extern "C" fn secp256k1_fn_mul_c( x_ptr: *const u64, y_ptr: *const u64, out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); let mut params = SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -208,11 +282,19 @@ pub unsafe extern "C" fn secp256k1_fn_mul_c( /// - `x_ptr` must point to 4 u64s (non-zero element) /// - `out_ptr` must point to at least 4 u64s #[no_mangle] -pub unsafe extern "C" fn secp256k1_fn_inv_c(x_ptr: *const u64, out_ptr: *mut u64) { +pub unsafe extern "C" fn secp256k1_fn_inv_c( + x_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); // Hint the inverse - let x_inv = fcall_secp256k1_fn_inv(x); + let x_inv = fcall_secp256k1_fn_inv( + x, + #[cfg(feature = "hints")] + hints, + ); // Check that x·x_inv = 1 (N) let mut params = SyscallArith256ModParams { @@ -222,7 +304,11 @@ pub unsafe extern "C" fn secp256k1_fn_inv_c(x_ptr: *const u64, out_ptr: *mut u64 module: &N, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); assert_eq!(*params.d, [0x1, 0x0, 0x0, 0x0]); *out_ptr.add(0) = x_inv[0]; From bb51801b3aa2f64e2be99f83361b9e4d6a55d5f3 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 30 Dec 2025 17:53:18 +0000 Subject: [PATCH 151/782] added ziskos-hints --- Cargo.lock | 21 ++++++++++++++++++++- Cargo.toml | 2 ++ precompiles/hints/Cargo.toml | 8 ++------ ziskos-hints/Cargo.toml | 30 ++++++++++++++++++++++++++++++ ziskos-hints/src | 1 + 5 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 ziskos-hints/Cargo.toml create mode 120000 ziskos-hints/src diff --git a/Cargo.lock b/Cargo.lock index d7d1e260c..6cdcb1a97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3158,7 +3158,7 @@ dependencies = [ "rayon", "tracing", "zisk-common", - "ziskos", + "ziskos-hints", ] [[package]] @@ -6164,6 +6164,25 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "ziskos-hints" +version = "0.16.0" +dependencies = [ + "bincode", + "cfg-if", + "getrandom 0.2.16", + "lazy_static", + "lib-c", + "num-bigint", + "num-integer", + "num-traits", + "precompiles-helpers", + "rand 0.8.5", + "serde", + "static_assertions", + "tiny-keccak", +] + [[package]] name = "zmij" version = "0.1.7" diff --git a/Cargo.toml b/Cargo.toml index d7f01ac31..f8fb44605 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ members = [ "state-machines/rom", "witness-computation", "ziskos/entrypoint", + "ziskos-hints", "precompiles/arith_eq", "precompiles/arith_eq_384", "precompiles/common", @@ -93,6 +94,7 @@ sm-rom = { path = "state-machines/rom" } zisk-witness = { path = "witness-computation" } ziskclib = { path = "ziskclib" } ziskos = { path = "ziskos/entrypoint" } +ziskos-hints = { path = "ziskos-hints" } circuit = { path = "tools/circuit" } zisk-sdk = { path = "sdk" } zisk-build = { path = "ziskbuild" } diff --git a/precompiles/hints/Cargo.toml b/precompiles/hints/Cargo.toml index 95f2e6ce9..6961eb670 100644 --- a/precompiles/hints/Cargo.toml +++ b/precompiles/hints/Cargo.toml @@ -12,7 +12,7 @@ name = "precompiles_hints" path = "src/lib.rs" [dependencies] -ziskos = { workspace = true } +ziskos-hints = { workspace = true } lib-c = { workspace = true } precompiles-helpers = { workspace = true } anyhow = { workspace = true } @@ -22,8 +22,4 @@ zisk-common = { workspace = true } [[bin]] name = "hints-socket-server" -path = "src/bin/hints_socket_server.rs" - -[features] -default = [] -hints = ["ziskos/hints"] +path = "src/bin/hints_socket_server.rs" \ No newline at end of file diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml new file mode 100644 index 000000000..2fdfc2e0b --- /dev/null +++ b/ziskos-hints/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "ziskos-hints" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +keywords = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } + +[dependencies] +lib-c = { workspace = true } + +num-bigint = { workspace = true } +num-integer = { workspace = true } +num-traits = { workspace = true } + +precompiles-helpers = { workspace = true } + +lazy_static = "1.5.0" +static_assertions = "1.1" +rand = "0.8.5" +getrandom = { version = "0.2", features = ["custom"] } +cfg-if = "1.0" +tiny-keccak = { version = "2.0.0", features = ["keccak"] } +serde = { workspace = true, features = ["derive"] } +bincode = "2.0" + +[features] +default = ["hints"] +hints = [] \ No newline at end of file diff --git a/ziskos-hints/src b/ziskos-hints/src new file mode 120000 index 000000000..4954a43ef --- /dev/null +++ b/ziskos-hints/src @@ -0,0 +1 @@ +../ziskos/entrypoint/src \ No newline at end of file From 44c1bc2d52f77b51f3943084e46de0695dd3720f Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 31 Dec 2025 09:40:09 +0100 Subject: [PATCH 152/782] Add hints to ziskos zisklib --- precompiles/hints/src/hints_processor.rs | 11 +- ziskos/entrypoint/src/syscalls/add256.rs | 5 +- ziskos/entrypoint/src/syscalls/arith256.rs | 5 +- .../entrypoint/src/syscalls/arith384_mod.rs | 5 +- .../src/syscalls/bls12_381_complex_add.rs | 5 +- .../src/syscalls/bls12_381_complex_mul.rs | 5 +- .../src/syscalls/bls12_381_complex_sub.rs | 5 +- .../src/syscalls/bls12_381_curve_add.rs | 5 +- .../src/syscalls/bls12_381_curve_dbl.rs | 5 +- .../src/syscalls/bn254_complex_add.rs | 5 +- .../src/syscalls/bn254_complex_mul.rs | 5 +- .../src/syscalls/bn254_complex_sub.rs | 5 +- .../src/syscalls/bn254_curve_add.rs | 5 +- .../src/syscalls/bn254_curve_dbl.rs | 5 +- ziskos/entrypoint/src/syscalls/keccakf.rs | 5 +- ziskos/entrypoint/src/syscalls/sha256f.rs | 5 +- .../src/zisklib/fcalls/big_int256_div.rs | 6 +- .../src/zisklib/fcalls/big_int_div.rs | 1 + .../src/zisklib/fcalls/bin_decomp.rs | 5 +- .../src/zisklib/fcalls/bls12_381_fp2_inv.rs | 5 +- .../src/zisklib/fcalls/bls12_381_fp2_sqrt.rs | 5 +- .../src/zisklib/fcalls/bls12_381_fp_inv.rs | 5 +- .../src/zisklib/fcalls/bls12_381_fp_sqrt.rs | 5 +- .../src/zisklib/fcalls/bls12_381_twist.rs | 6 +- .../entrypoint/src/zisklib/fcalls/bn254_fp.rs | 5 +- .../src/zisklib/fcalls/bn254_fp2.rs | 5 +- .../src/zisklib/fcalls/bn254_twist.rs | 6 +- .../src/zisklib/fcalls/msb_pos_384.rs | 6 +- .../src/zisklib/fcalls/secp256k1_fn_inv.rs | 2 +- .../src/zisklib/fcalls/secp256k1_fp_inv.rs | 7 +- .../src/zisklib/fcalls/secp256k1_fp_sqrt.rs | 6 +- .../src/zisklib/lib/array_lib/add_agtb.rs | 25 +- .../src/zisklib/lib/array_lib/add_short.rs | 19 +- .../src/zisklib/lib/array_lib/div_long.rs | 31 +- .../src/zisklib/lib/array_lib/div_short.rs | 31 +- .../src/zisklib/lib/array_lib/modexp.rs | 92 ++- .../src/zisklib/lib/array_lib/mul_long.rs | 74 +- .../src/zisklib/lib/array_lib/mul_short.rs | 41 +- .../src/zisklib/lib/array_lib/rem_long.rs | 70 +- .../src/zisklib/lib/array_lib/rem_short.rs | 70 +- .../src/zisklib/lib/array_lib/square_long.rs | 88 ++- .../src/zisklib/lib/array_lib/square_short.rs | 32 +- .../entrypoint/src/zisklib/lib/bigint256.rs | 189 ++++- .../src/zisklib/lib/bls12_381/curve.rs | 314 +++++++-- .../src/zisklib/lib/bls12_381/cyclotomic.rs | 437 ++++++++++-- .../src/zisklib/lib/bls12_381/final_exp.rs | 126 +++- .../src/zisklib/lib/bls12_381/fp.rs | 192 ++++- .../src/zisklib/lib/bls12_381/fp12.rs | 399 +++++++++-- .../src/zisklib/lib/bls12_381/fp2.rs | 217 +++++- .../src/zisklib/lib/bls12_381/fp6.rs | 584 ++++++++++++++-- .../src/zisklib/lib/bls12_381/fr.rs | 132 +++- .../src/zisklib/lib/bls12_381/miller_loop.rs | 462 ++++++++++-- .../src/zisklib/lib/bls12_381/pairing.rs | 52 +- .../src/zisklib/lib/bls12_381/twist.rs | 471 ++++++++++--- .../entrypoint/src/zisklib/lib/bn254/curve.rs | 164 ++++- .../src/zisklib/lib/bn254/cyclotomic.rs | 397 +++++++++-- .../src/zisklib/lib/bn254/final_exp.rs | 243 +++++-- ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs | 54 +- .../entrypoint/src/zisklib/lib/bn254/fp12.rs | 461 ++++++++++-- .../entrypoint/src/zisklib/lib/bn254/fp2.rs | 97 ++- .../entrypoint/src/zisklib/lib/bn254/fp6.rs | 658 +++++++++++++++--- .../src/zisklib/lib/bn254/miller_loop.rs | 622 ++++++++++++++--- .../src/zisklib/lib/bn254/pairing.rs | 46 +- .../entrypoint/src/zisklib/lib/bn254/twist.rs | 398 +++++++++-- .../src/zisklib/lib/sha256f_compress.rs | 19 +- 65 files changed, 6302 insertions(+), 1166 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 199e210c2..42447188c 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -17,6 +17,8 @@ use zisk_common::{ HINTS_TYPE_RESULT, NUM_HINT_TYPES, }; +use ziskos_hints::zisklib; + /// Ordered result buffer with drain state. /// /// This structure maintains a VecDeque that holds processed results in order, @@ -464,14 +466,7 @@ impl PrecompileHintsProcessor { let r = &*(ptr.add(R_OFFSET) as *const u64); let s = &*(ptr.add(S_OFFSET) as *const u64); - ziskos::zisklib::secp256k1_ecdsa_verify_c( - pk, - z, - r, - s, - #[cfg(feature = "hints")] - &mut processed_hints, - ); + zisklib::secp256k1_ecdsa_verify_c(pk, z, r, s, &mut processed_hints); } Ok(processed_hints) diff --git a/ziskos/entrypoint/src/syscalls/add256.rs b/ziskos/entrypoint/src/syscalls/add256.rs index e895b524f..466f87005 100644 --- a/ziskos/entrypoint/src/syscalls/add256.rs +++ b/ziskos/entrypoint/src/syscalls/add256.rs @@ -29,7 +29,10 @@ pub struct SyscallAdd256Params<'a> { /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_add256(params: &mut SyscallAdd256Params) -> u64 { +pub extern "C" fn syscall_add256( + params: &mut SyscallAdd256Params, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> u64 { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/syscalls/arith256.rs b/ziskos/entrypoint/src/syscalls/arith256.rs index f9b77b7c0..cae12d988 100644 --- a/ziskos/entrypoint/src/syscalls/arith256.rs +++ b/ziskos/entrypoint/src/syscalls/arith256.rs @@ -32,7 +32,10 @@ pub struct SyscallArith256Params<'a> { /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_arith256(params: &mut SyscallArith256Params) { +pub extern "C" fn syscall_arith256( + params: &mut SyscallArith256Params, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x801, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/arith384_mod.rs b/ziskos/entrypoint/src/syscalls/arith384_mod.rs index d030519ed..8231d4c69 100644 --- a/ziskos/entrypoint/src/syscalls/arith384_mod.rs +++ b/ziskos/entrypoint/src/syscalls/arith384_mod.rs @@ -35,7 +35,10 @@ pub struct SyscallArith384ModParams<'a> { /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_arith384_mod(params: &mut SyscallArith384ModParams) { +pub extern "C" fn syscall_arith384_mod( + params: &mut SyscallArith384ModParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80B, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs b/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs index 7e5539e95..29e1233f1 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs @@ -35,7 +35,10 @@ pub struct SyscallBls12_381ComplexAddParams<'a> { /// The resulting field element will have both coordinates in the range of the BLS12-381 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_bls12_381_complex_add(params: &mut SyscallBls12_381ComplexAddParams) { +pub extern "C" fn syscall_bls12_381_complex_add( + params: &mut SyscallBls12_381ComplexAddParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80E, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs b/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs index 72add5b87..220c041b0 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs @@ -35,7 +35,10 @@ pub struct SyscallBls12_381ComplexMulParams<'a> { /// The resulting field element will have both coordinates in the range of the BLS12-381 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_bls12_381_complex_mul(params: &mut SyscallBls12_381ComplexMulParams) { +pub extern "C" fn syscall_bls12_381_complex_mul( + params: &mut SyscallBls12_381ComplexMulParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x810, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs b/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs index b448dd368..22253ed4f 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs @@ -35,7 +35,10 @@ pub struct SyscallBls12_381ComplexSubParams<'a> { /// The resulting field element will have both coordinates in the range of the BLS12-381 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_bls12_381_complex_sub(params: &mut SyscallBls12_381ComplexSubParams) { +pub extern "C" fn syscall_bls12_381_complex_sub( + params: &mut SyscallBls12_381ComplexSubParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80F, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs b/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs index bb6b7a726..b18f231de 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs @@ -34,7 +34,10 @@ pub struct SyscallBls12_381CurveAddParams<'a> { /// The resulting point will have both coordinates in the range of the BLS12-381 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_bls12_381_curve_add(params: &mut SyscallBls12_381CurveAddParams) { +pub extern "C" fn syscall_bls12_381_curve_add( + params: &mut SyscallBls12_381CurveAddParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80C, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs b/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs index 8ed580b7e..1a3b3721e 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs @@ -26,7 +26,10 @@ use super::point::SyscallPoint384; /// The resulting point will have both coordinates in the range of the BLS12-381 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_bls12_381_curve_dbl(p1: &mut SyscallPoint384) { +pub extern "C" fn syscall_bls12_381_curve_dbl( + p1: &mut SyscallPoint384, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80D, p1); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs b/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs index 870470ce3..0b26e9bb1 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs @@ -35,7 +35,10 @@ pub struct SyscallBn254ComplexAddParams<'a> { /// The resulting field element will have both coordinates in the range of the BN254 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_bn254_complex_add(params: &mut SyscallBn254ComplexAddParams) { +pub extern "C" fn syscall_bn254_complex_add( + params: &mut SyscallBn254ComplexAddParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x808, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs b/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs index 86b1ed80d..c2cbf46ea 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs @@ -35,7 +35,10 @@ pub struct SyscallBn254ComplexMulParams<'a> { /// The resulting field element will have both coordinates in the range of the BN254 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_bn254_complex_mul(params: &mut SyscallBn254ComplexMulParams) { +pub extern "C" fn syscall_bn254_complex_mul( + params: &mut SyscallBn254ComplexMulParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80A, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs b/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs index 00a7f9bb4..f36940f22 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs @@ -35,7 +35,10 @@ pub struct SyscallBn254ComplexSubParams<'a> { /// The resulting field element will have both coordinates in the range of the BN254 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_bn254_complex_sub(params: &mut SyscallBn254ComplexSubParams) { +pub extern "C" fn syscall_bn254_complex_sub( + params: &mut SyscallBn254ComplexSubParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x809, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs b/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs index 85d29b490..ebef3febb 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs @@ -34,7 +34,10 @@ pub struct SyscallBn254CurveAddParams<'a> { /// The resulting point will have both coordinates in the range of the BN254 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_bn254_curve_add(params: &mut SyscallBn254CurveAddParams) { +pub extern "C" fn syscall_bn254_curve_add( + params: &mut SyscallBn254CurveAddParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x806, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs b/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs index c066f8ad8..a0ba3685e 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs @@ -26,7 +26,10 @@ use super::point::SyscallPoint256; /// The resulting point will have both coordinates in the range of the BN254 base field. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_bn254_curve_dbl(p1: &mut SyscallPoint256) { +pub extern "C" fn syscall_bn254_curve_dbl( + p1: &mut SyscallPoint256, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x807, p1); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/keccakf.rs b/ziskos/entrypoint/src/syscalls/keccakf.rs index a527739de..a49b1eb8c 100644 --- a/ziskos/entrypoint/src/syscalls/keccakf.rs +++ b/ziskos/entrypoint/src/syscalls/keccakf.rs @@ -19,7 +19,10 @@ use crate::ziskos_syscall; /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_keccak_f(state: *mut [u64; 25]) { +pub extern "C" fn syscall_keccak_f( + state: *mut [u64; 25], + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x800, state); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/syscalls/sha256f.rs b/ziskos/entrypoint/src/syscalls/sha256f.rs index 57a2ad040..3079f7fca 100644 --- a/ziskos/entrypoint/src/syscalls/sha256f.rs +++ b/ziskos/entrypoint/src/syscalls/sha256f.rs @@ -27,7 +27,10 @@ pub struct SyscallSha256Params<'a> { /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] #[no_mangle] -pub extern "C" fn syscall_sha256_f(params: &mut SyscallSha256Params) { +pub extern "C" fn syscall_sha256_f( + params: &mut SyscallSha256Params, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x805, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs index 56fba88df..9592ec704 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs @@ -22,7 +22,11 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bigint256_div(a_value: &[u64; 4], b_value: &[u64; 4]) -> ([u64; 4], [u64; 4]) { +pub fn fcall_bigint256_div( + a_value: &[u64; 4], + b_value: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> ([u64; 4], [u64; 4]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs index a289ca1dd..4a239c8fe 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs @@ -22,6 +22,7 @@ pub fn fcall_division( b_value: &[u64], quo: &mut [u64], rem: &mut [u64], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> (usize, usize) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs index f506e4491..35fbfcb61 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs @@ -9,7 +9,10 @@ cfg_if! { /// Computes the binary decomposition of a NON-ZERO unsigned integer `x` into its bits. #[allow(unused_variables)] -pub fn fcall_bin_decomp(x_val: &[u64]) -> (usize, Vec) { +pub fn fcall_bin_decomp( + x_val: &[u64], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> (usize, Vec) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs index 8cf9cf8b9..b87842040 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs @@ -22,7 +22,10 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bls12_381_fp2_inv(p_value: &[u64; 12]) -> [u64; 12] { +pub fn fcall_bls12_381_fp2_inv( + p_value: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs index 8a312d24f..9b3eb7978 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs @@ -22,7 +22,10 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bls12_381_fp2_sqrt(p_value: &[u64; 12]) -> [u64; 13] { +pub fn fcall_bls12_381_fp2_sqrt( + p_value: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 13] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs index 9f70241f1..51beb5925 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs @@ -22,7 +22,10 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bls12_381_fp_inv(p_value: &[u64; 6]) -> [u64; 6] { +pub fn fcall_bls12_381_fp_inv( + p_value: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 6] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs index 8c3059895..9a002c86d 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs @@ -22,7 +22,10 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bls12_381_fp_sqrt(p_value: &[u64; 6]) -> [u64; 7] { +pub fn fcall_bls12_381_fp_sqrt( + p_value: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 7] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs index 83dd00247..570b71a0d 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs @@ -19,6 +19,7 @@ cfg_if! { pub fn fcall_bls12_381_add_line_coeffs( p1_value: &[u64; 24], p2_value: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> ([u64; 12], [u64; 12]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); @@ -69,7 +70,10 @@ pub fn fcall_bls12_381_add_line_coeffs( /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bls12_381_dbl_line_coeffs(p_value: &[u64; 24]) -> ([u64; 12], [u64; 12]) { +pub fn fcall_bls12_381_dbl_line_coeffs( + p_value: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> ([u64; 12], [u64; 12]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs index c34957e5f..6731f9125 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs @@ -22,7 +22,10 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bn254_fp_inv(p_value: &[u64; 4]) -> [u64; 4] { +pub fn fcall_bn254_fp_inv( + p_value: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs index 5d27436ec..fcdcffc8c 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs @@ -22,7 +22,10 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bn254_fp2_inv(p_value: &[u64; 8]) -> [u64; 8] { +pub fn fcall_bn254_fp2_inv( + p_value: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 8] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs index 756e3976b..3ab8c3d99 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs @@ -19,6 +19,7 @@ cfg_if! { pub fn fcall_bn254_add_line_coeffs( p1_value: &[u64; 16], p2_value: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> ([u64; 8], [u64; 8]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); @@ -61,7 +62,10 @@ pub fn fcall_bn254_add_line_coeffs( /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_bn254_dbl_line_coeffs(p_value: &[u64; 16]) -> ([u64; 8], [u64; 8]) { +pub fn fcall_bn254_dbl_line_coeffs( + p_value: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> ([u64; 8], [u64; 8]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs index 629063c9f..0a1aaae6f 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs @@ -7,7 +7,11 @@ cfg_if! { } } #[allow(unused_variables)] -pub fn fcall_msb_pos_384(x: &[u64; 6], y: &[u64; 6]) -> (u64, u64) { +pub fn fcall_msb_pos_384( + x: &[u64; 6], + y: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> (u64, u64) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs index 728ebe11a..4a2f659d5 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs @@ -44,7 +44,7 @@ pub fn fcall_secp256k1_fn_inv( } #[allow(unused_variables)] -pub fn fcall2_secp256k1_fn_inv(p_value: &[u64; 4]) { +pub fn fcall2_secp256k1_fn_inv(p_value: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs index a7ef601e5..b0c6fc96a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs @@ -24,7 +24,10 @@ cfg_if! { /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] -pub fn fcall_secp256k1_fp_inv(p_value: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec,) -> [u64; 4] { +pub fn fcall_secp256k1_fp_inv( + p_value: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] @@ -36,7 +39,7 @@ pub fn fcall_secp256k1_fp_inv(p_value: &[u64; 4], #[cfg(feature = "hints")] hint } #[allow(unused_variables)] -pub fn fcall2_secp256k1_fp_inv(p_value: &[u64; 4]) { +pub fn fcall2_secp256k1_fp_inv(p_value: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs index daf088134..63830a67f 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs @@ -47,7 +47,11 @@ pub fn fcall_secp256k1_fp_sqrt( } #[allow(unused_variables)] -pub fn fcall2_secp256k1_fp_sqrt(p_value: &[u64; 4], parity: u64) { +pub fn fcall2_secp256k1_fp_sqrt( + p_value: &[u64; 4], + parity: u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/add_agtb.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/add_agtb.rs index 6b4d354b6..778e0bb9a 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/add_agtb.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/add_agtb.rs @@ -11,7 +11,12 @@ use super::U256; /// /// # Returns /// The number of limbs in the result -pub fn add_agtb(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { +pub fn add_agtb( + a: &[U256], + b: &[U256], + out: &mut [U256], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> usize { let len_a = a.len(); let len_b = b.len(); #[cfg(debug_assertions)] @@ -33,7 +38,11 @@ pub fn add_agtb(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { cin: 0, c: out[0].as_limbs_mut(), }; - let mut carry = syscall_add256(&mut params); + let mut carry = syscall_add256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); for i in 1..len_b { // Compute a[i] + b[i] + carry @@ -43,7 +52,11 @@ pub fn add_agtb(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { cin: carry, c: out[i].as_limbs_mut(), }; - carry = syscall_add256(&mut params); + carry = syscall_add256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); } for i in len_b..len_a { @@ -55,7 +68,11 @@ pub fn add_agtb(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { cin: 1, c: out[i].as_limbs_mut(), }; - carry = syscall_add256(&mut params); + carry = syscall_add256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); } else { // Directly copy a[i] to out[i] out[i] = a[i]; diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/add_short.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/add_short.rs index 2bf2afd4a..3d7e5e00c 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/add_short.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/add_short.rs @@ -11,7 +11,12 @@ use super::U256; /// /// # Returns /// The number of limbs in the result -pub fn add_short(a: &[U256], b: &U256, out: &mut [U256]) -> usize { +pub fn add_short( + a: &[U256], + b: &U256, + out: &mut [U256], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> usize { let len_a = a.len(); #[cfg(debug_assertions)] { @@ -28,7 +33,11 @@ pub fn add_short(a: &[U256], b: &U256, out: &mut [U256]) -> usize { cin: 0, c: out[0].as_limbs_mut(), }; - let mut carry = syscall_add256(&mut params); + let mut carry = syscall_add256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); for i in 1..len_a { if carry == 1 { @@ -39,7 +48,11 @@ pub fn add_short(a: &[U256], b: &U256, out: &mut [U256]) -> usize { cin: 1, c: out[i].as_limbs_mut(), }; - carry = syscall_add256(&mut params); + carry = syscall_add256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); } else { // Directly copy a[i] to out[i] out[i] = a[i]; diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/div_long.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/div_long.rs index b29b59b65..637d59bc1 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/div_long.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/div_long.rs @@ -16,7 +16,11 @@ use super::{add_agtb, mul_long, U256}; /// /// # Note /// Not optimal for `len(b) == 1`, use `div_short` instead -pub fn div_long(a: &[U256], b: &[U256]) -> (Vec, Vec) { +pub fn div_long( + a: &[U256], + b: &[U256], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> (Vec, Vec) { let len_a = a.len(); let len_b = b.len(); #[cfg(debug_assertions)] @@ -45,7 +49,14 @@ pub fn div_long(a: &[U256], b: &[U256]) -> (Vec, Vec) { // Hint the quotient and remainder let mut quo_flat = vec![0u64; len_a * 4]; let mut rem_flat = vec![0u64; len_b * 4]; - let (limbs_quo, limbs_rem) = fcall_division(a_flat, b_flat, &mut quo_flat, &mut rem_flat); + let (limbs_quo, limbs_rem) = fcall_division( + a_flat, + b_flat, + &mut quo_flat, + &mut rem_flat, + #[cfg(feature = "hints")] + hints, + ); let quo = U256::flat_to_slice(&quo_flat[..limbs_quo]); let rem = U256::flat_to_slice(&rem_flat[..limbs_rem]); @@ -67,7 +78,13 @@ pub fn div_long(a: &[U256], b: &[U256]) -> (Vec, Vec) { // Multiply the quotient by b let mut q_b = vec![U256::ZERO; len_a + 1]; // The +1 is because mul_long and add_agtb are a general purpose functions - let q_b_len = mul_long(quo, b, &mut q_b); + let q_b_len = mul_long( + quo, + b, + &mut q_b, + #[cfg(feature = "hints")] + hints, + ); // Check 1 <= len(r) let len_rem = rem.len(); @@ -82,7 +99,13 @@ pub fn div_long(a: &[U256], b: &[U256]) -> (Vec, Vec) { assert!(U256::lt_slices(rem, b), "Remainder must be less than divisor"); let mut q_b_r = vec![U256::ZERO; len_a + 1]; // The +1 is because mul_long and add_agtb are a general purpose functions - let q_b_r_len = add_agtb(&q_b[..q_b_len], rem, &mut q_b_r); + let q_b_r_len = add_agtb( + &q_b[..q_b_len], + rem, + &mut q_b_r, + #[cfg(feature = "hints")] + hints, + ); assert!(U256::eq_slices(a, &q_b_r[..q_b_r_len]), "a != q·b + r"); } diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/div_short.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/div_short.rs index 6080484c7..1b668a68c 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/div_short.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/div_short.rs @@ -11,7 +11,11 @@ use super::{add_short, mul_short, U256}; /// /// # Returns /// A tuple of (quotient, remainder) where a = q × b + r -pub fn div_short(a: &[U256], b: &U256) -> (Vec, U256) { +pub fn div_short( + a: &[U256], + b: &U256, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> (Vec, U256) { let len_a = a.len(); #[cfg(debug_assertions)] { @@ -39,7 +43,14 @@ pub fn div_short(a: &[U256], b: &U256) -> (Vec, U256) { // Hint the quotient and remainder let mut quo_flat = vec![0u64; len_a * 4]; let mut rem_flat = [0u64; 4]; - let (limbs_quo, _) = fcall_division(a_flat, b.as_limbs(), &mut quo_flat, &mut rem_flat); + let (limbs_quo, _) = fcall_division( + a_flat, + b.as_limbs(), + &mut quo_flat, + &mut rem_flat, + #[cfg(feature = "hints")] + hints, + ); let quo = U256::flat_to_slice(&quo_flat[..limbs_quo]); let rem = U256::from_u64s(&rem_flat); @@ -53,7 +64,13 @@ pub fn div_short(a: &[U256], b: &U256) -> (Vec, U256) { // Multiply the quotient by b let mut q_b = [U256::ZERO; 2]; - let q_b_len = mul_short(quo, b, &mut q_b); + let q_b_len = mul_short( + quo, + b, + &mut q_b, + #[cfg(feature = "hints")] + hints, + ); if rem.is_zero() { // If the remainder is zero, then a must be equal to q·b @@ -63,7 +80,13 @@ pub fn div_short(a: &[U256], b: &U256) -> (Vec, U256) { assert!(rem.lt(b), "Remainder must be less than divisor"); let mut q_b_r = [U256::ZERO; 2]; - let q_b_r_len = add_short(&q_b[..q_b_len], &rem, &mut q_b_r); + let q_b_r_len = add_short( + &q_b[..q_b_len], + &rem, + &mut q_b_r, + #[cfg(feature = "hints")] + hints, + ); assert!(U256::eq_slices(a, &q_b_r[..q_b_r_len]), "a != q·b + r"); } diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs index 806247569..051795ffe 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs @@ -13,7 +13,12 @@ use super::{ /// Modular exponentiation of three large numbers /// /// It assumes that modulus > 0 and len(base),len(exp),len(modulus) > 0 -pub fn modexp(base: &[U256], exp: &[u64], modulus: &[U256]) -> Vec { +pub fn modexp( + base: &[U256], + exp: &[u64], + modulus: &[U256], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Vec { let len_b = base.len(); let len_e = exp.len(); let len_m = modulus.len(); @@ -67,10 +72,19 @@ pub fn modexp(base: &[U256], exp: &[u64], modulus: &[U256]) -> Vec { let modulus = &modulus[0]; // Compute base = base (mod modulus) - let base = rem_short_init(base, modulus); + let base = rem_short_init( + base, + modulus, + #[cfg(feature = "hints")] + hints, + ); // Hint exponent bits - let (len, bits) = fcall_bin_decomp(exp); + let (len, bits) = fcall_bin_decomp( + exp, + #[cfg(feature = "hints")] + hints, + ); // We should recompose the exponent from bits to verify correctness let mut rec_exp = vec![0u64; len_e]; @@ -92,12 +106,24 @@ pub fn modexp(base: &[U256], exp: &[u64], modulus: &[U256]) -> Vec { } // Compute out = out² (mod modulus) - out = square_and_reduce_short(&out, modulus, &mut scratch); + out = square_and_reduce_short( + &out, + modulus, + &mut scratch, + #[cfg(feature = "hints")] + hints, + ); if bit == 1 { // Compute out = (out * base) (mod modulus); - out = mul_and_reduce_short(&out, &base, modulus, &mut scratch); - + out = mul_and_reduce_short( + &out, + &base, + modulus, + &mut scratch, + #[cfg(feature = "hints")] + hints, + ); // Recompose the exponent let bits_pos = len - 1 - bit_idx; let limb_idx = bits_pos / 64; @@ -111,10 +137,19 @@ pub fn modexp(base: &[U256], exp: &[u64], modulus: &[U256]) -> Vec { vec![out] } else { // Compute base = base (mod modulus) - let base = rem_long_init(base, modulus); + let base = rem_long_init( + base, + modulus, + #[cfg(feature = "hints")] + hints, + ); // Hint exponent bits - let (len, bits) = fcall_bin_decomp(exp); + let (len, bits) = fcall_bin_decomp( + exp, + #[cfg(feature = "hints")] + hints, + ); // We should recompose the exponent from bits to verify correctness let mut rec_exp = vec![0u64; len_e]; @@ -136,11 +171,24 @@ pub fn modexp(base: &[U256], exp: &[u64], modulus: &[U256]) -> Vec { } // Compute out = out² (mod modulus) - out = square_and_reduce_long(&out, modulus, &mut scratch); + out = square_and_reduce_long( + &out, + modulus, + &mut scratch, + #[cfg(feature = "hints")] + hints, + ); if bit == 1 { // Compute out = (out * base) (mod modulus); - out = mul_and_reduce_long(&out, &base, modulus, &mut scratch); + out = mul_and_reduce_long( + &out, + &base, + modulus, + &mut scratch, + #[cfg(feature = "hints")] + hints, + ); // Recompose the exponent let bits_pos = len - 1 - bit_idx; let limb_idx = bits_pos / 64; @@ -155,7 +203,12 @@ pub fn modexp(base: &[U256], exp: &[u64], modulus: &[U256]) -> Vec { } } -pub fn modexp_u64(base: &[u64], exp: &[u64], modulus: &[u64]) -> Vec { +pub fn modexp_u64( + base: &[u64], + exp: &[u64], + modulus: &[u64], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Vec { // Round up to multiple of 4 let base_len = (base.len() + 3) & !3; let modulus_len = (modulus.len() + 3) & !3; @@ -171,7 +224,13 @@ pub fn modexp_u64(base: &[u64], exp: &[u64], modulus: &[u64]) -> Vec { let modulus_u256 = U256::flat_to_slice(&modulus_padded); // Call the main modexp function - let result_u256 = modexp(base_u256, exp, modulus_u256); + let result_u256 = modexp( + base_u256, + exp, + modulus_u256, + #[cfg(feature = "hints")] + hints, + ); // Convert result back to u64 array U256::slice_to_flat(&result_u256).to_vec() @@ -195,6 +254,7 @@ pub unsafe extern "C" fn modexp_u64_c( modulus_ptr: *const u64, modulus_len: usize, result_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> usize { let base = std::slice::from_raw_parts(base_ptr, base_len); let exp = std::slice::from_raw_parts(exp_ptr, exp_len); @@ -215,7 +275,13 @@ pub unsafe extern "C" fn modexp_u64_c( let modulus_u256 = U256::flat_to_slice(&modulus_padded); // Call the main modexp function - let result_u256 = modexp(base_u256, exp, modulus_u256); + let result_u256 = modexp( + base_u256, + exp, + modulus_u256, + #[cfg(feature = "hints")] + hints, + ); let result_slice = U256::slice_to_flat(&result_u256); let result_len = result_slice.len(); diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/mul_long.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/mul_long.rs index a04cbed35..4a7046328 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/mul_long.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/mul_long.rs @@ -16,7 +16,12 @@ use super::{mul_short, rem_long, LongScratch, U256}; /// /// # Note /// Not optimal for `len(b) == 1`, use `mul_short` instead -pub fn mul_long(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { +pub fn mul_long( + a: &[U256], + b: &[U256], + out: &mut [U256], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> usize { let len_a = a.len(); let len_b = b.len(); #[cfg(debug_assertions)] @@ -39,7 +44,11 @@ pub fn mul_long(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { dl: out[0].as_limbs_mut(), dh: &mut [0, 0, 0, 0], }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); // Propagate the carry out[1] = U256::from_u64s(params.dh); @@ -55,7 +64,11 @@ pub fn mul_long(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { dl: out[j].as_limbs_mut(), dh: &mut [0, 0, 0, 0], }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); // Propagate the carry out[j + 1] = U256::from_u64s(params.dh); @@ -74,7 +87,11 @@ pub fn mul_long(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { dl: &mut [0, 0, 0, 0], dh: &mut [0, 0, 0, 0], }; - syscall_arith256(&mut params_arith); + syscall_arith256( + &mut params_arith, + #[cfg(feature = "hints")] + hints, + ); // Set the result out[i + j] = U256::from_u64s(params_arith.dl); @@ -86,7 +103,11 @@ pub fn mul_long(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { cin: 1, c: params_arith.dh, }; - let _carry = syscall_add256(&mut params_add); + let _carry = syscall_add256( + &mut params_add, + #[cfg(feature = "hints")] + hints, + ); debug_assert!(_carry == 0, "Unexpected carry in intermediate addition"); } @@ -99,7 +120,11 @@ pub fn mul_long(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { cin: 0, c: out[i + j + 1].as_limbs_mut(), }; - carry_flag = syscall_add256(&mut params_add); + carry_flag = syscall_add256( + &mut params_add, + #[cfg(feature = "hints")] + hints, + ); } // Last chunk isolated @@ -113,7 +138,11 @@ pub fn mul_long(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { dl: out[i + len_b - 1].as_limbs_mut(), dh: &mut [0, 0, 0, 0], }; - syscall_arith256(&mut params_arith); + syscall_arith256( + &mut params_arith, + #[cfg(feature = "hints")] + hints, + ); if carry_flag == 1 { let a_in = *params_arith.dh; @@ -123,7 +152,11 @@ pub fn mul_long(a: &[U256], b: &[U256], out: &mut [U256]) -> usize { cin: 1, c: params_arith.dh, }; - let _carry = syscall_add256(&mut params_add); + let _carry = syscall_add256( + &mut params_add, + #[cfg(feature = "hints")] + hints, + ); debug_assert!(_carry == 0, "Unexpected carry in intermediate addition"); } @@ -153,6 +186,7 @@ pub fn mul_and_reduce_long( b: &[U256], modulus: &[U256], scratch: &mut LongScratch, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> Vec { #[cfg(debug_assertions)] { @@ -162,10 +196,28 @@ pub fn mul_and_reduce_long( } let mul_len = if b.len() == 1 { - mul_short(a, &b[0], &mut scratch.mul) + mul_short( + a, + &b[0], + &mut scratch.mul, + #[cfg(feature = "hints")] + hints, + ) } else { - mul_long(a, b, &mut scratch.mul) + mul_long( + a, + b, + &mut scratch.mul, + #[cfg(feature = "hints")] + hints, + ) }; - rem_long(&scratch.mul[..mul_len], modulus, &mut scratch.rem) + rem_long( + &scratch.mul[..mul_len], + modulus, + &mut scratch.rem, + #[cfg(feature = "hints")] + hints, + ) } diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/mul_short.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/mul_short.rs index eb98c333e..8c6c3a49f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/mul_short.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/mul_short.rs @@ -11,7 +11,12 @@ use super::{rem_short, ShortScratch, U256}; /// /// # Returns /// The number of limbs in the result -pub fn mul_short(a: &[U256], b: &U256, out: &mut [U256]) -> usize { +pub fn mul_short( + a: &[U256], + b: &U256, + out: &mut [U256], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> usize { let len_a = a.len(); #[cfg(debug_assertions)] { @@ -32,7 +37,11 @@ pub fn mul_short(a: &[U256], b: &U256, out: &mut [U256]) -> usize { dl: out[i].as_limbs_mut(), dh: carry.as_limbs_mut(), }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); } if carry.is_zero() { @@ -47,7 +56,11 @@ pub fn mul_short(a: &[U256], b: &U256, out: &mut [U256]) -> usize { /// /// # Returns /// A tuple of (result array, number of limbs used) -pub fn mul_short_one_limb(a: &U256, b: &U256) -> ([U256; 2], usize) { +pub fn mul_short_one_limb( + a: &U256, + b: &U256, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> ([U256; 2], usize) { let mut out = [U256::ZERO; 2]; // Compute a * b @@ -59,7 +72,11 @@ pub fn mul_short_one_limb(a: &U256, b: &U256) -> ([U256; 2], usize) { dl: out[0].as_limbs_mut(), dh: &mut dh, }; - syscall_arith256(&mut mul_params); + syscall_arith256( + &mut mul_params, + #[cfg(feature = "hints")] + hints, + ); let len = if dh == [0u64; 4] { 1 @@ -83,13 +100,25 @@ pub fn mul_and_reduce_short( b: &U256, modulus: &U256, scratch: &mut ShortScratch, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> U256 { #[cfg(debug_assertions)] { assert!(!modulus.is_zero(), "Input 'modulus' must not be zero"); } - let (mul, len) = mul_short_one_limb(a, b); + let (mul, len) = mul_short_one_limb( + a, + b, + #[cfg(feature = "hints")] + hints, + ); - rem_short(&mul[..len], modulus, scratch) + rem_short( + &mul[..len], + modulus, + scratch, + #[cfg(feature = "hints")] + hints, + ) } diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/rem_long.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/rem_long.rs index a1665755b..ba9641bc0 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/rem_long.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/rem_long.rs @@ -17,7 +17,11 @@ use super::{add_agtb, mul_long, RemLongScratch, U256}; /// # Note /// Use this for the first reduction when `a` can be arbitrarily large. /// For subsequent reductions in a loop, use `rem_long` with scratch space. -pub fn rem_long_init(a: &[U256], b: &[U256]) -> Vec { +pub fn rem_long_init( + a: &[U256], + b: &[U256], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Vec { let len_a = a.len(); let len_b = b.len(); #[cfg(debug_assertions)] @@ -46,14 +50,30 @@ pub fn rem_long_init(a: &[U256], b: &[U256]) -> Vec { // Hint the quotient and remainder let mut quo_flat = vec![0u64; len_a * 4]; let mut rem_flat = vec![0u64; len_b * 4]; - let (limbs_quo, limbs_rem) = fcall_division(a_flat, b_flat, &mut quo_flat, &mut rem_flat); + let (limbs_quo, limbs_rem) = fcall_division( + a_flat, + b_flat, + &mut quo_flat, + &mut rem_flat, + #[cfg(feature = "hints")] + hints, + ); let quo = U256::flat_to_slice(&quo_flat[..limbs_quo]); let rem = U256::flat_to_slice(&rem_flat[..limbs_rem]); // Verify the division let mut q_b = vec![U256::ZERO; len_a + 1]; // The +1 is because mul_long and add_agtb are a general purpose functions let mut q_b_r = vec![U256::ZERO; len_a + 1]; - verify_division(a, b, quo, rem, &mut q_b, &mut q_b_r); + verify_division( + a, + b, + quo, + rem, + &mut q_b, + &mut q_b_r, + #[cfg(feature = "hints")] + hints, + ); rem.to_vec() } @@ -70,7 +90,12 @@ pub fn rem_long_init(a: &[U256], b: &[U256]) -> Vec { /// /// # Note /// Not optimal for `len(b) == 1`, use `rem_short` instead -pub fn rem_long(a: &[U256], b: &[U256], scratch: &mut RemLongScratch) -> Vec { +pub fn rem_long( + a: &[U256], + b: &[U256], + scratch: &mut RemLongScratch, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Vec { #[cfg(debug_assertions)] { let len_a = a.len(); @@ -97,12 +122,28 @@ pub fn rem_long(a: &[U256], b: &[U256], scratch: &mut RemLongScratch) -> Vec, ) { let len_a = a.len(); let len_b = b.len(); @@ -136,7 +178,13 @@ fn verify_division( assert!(!quo[len_quo - 1].is_zero(), "Quotient must not have leading zeros"); // Multiply the quotient by b - let q_b_len = mul_long(quo, b, q_b); + let q_b_len = mul_long( + quo, + b, + q_b, + #[cfg(feature = "hints")] + hints, + ); // Check 1 <= len(r) assert!(len_rem > 0, "Remainder must have at least one limb"); @@ -149,7 +197,13 @@ fn verify_division( assert!(U256::lt_slices(rem, b), "Remainder must be less than divisor"); - let q_b_r_len = add_agtb(&q_b[..q_b_len], rem, q_b_r); + let q_b_r_len = add_agtb( + &q_b[..q_b_len], + rem, + q_b_r, + #[cfg(feature = "hints")] + hints, + ); assert!(U256::eq_slices(a, &q_b_r[..q_b_r_len]), "a != q·b + r"); } } diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/rem_short.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/rem_short.rs index f25f041d7..37a7e03cf 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/rem_short.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/rem_short.rs @@ -15,7 +15,11 @@ use super::{add_short, mul_short, ShortScratch, U256}; /// # Note /// Use this for the first reduction when `a` can be arbitrarily large. /// For subsequent reductions in a loop, use `rem_short` with scratch space. -pub fn rem_short_init(a: &[U256], b: &U256) -> U256 { +pub fn rem_short_init( + a: &[U256], + b: &U256, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> U256 { let len_a = a.len(); #[cfg(debug_assertions)] { @@ -43,14 +47,30 @@ pub fn rem_short_init(a: &[U256], b: &U256) -> U256 { // Hint the quotient and remainder let mut quo_flat = vec![0u64; len_a * 4]; let mut rem_flat = [0u64; 4]; - let (limbs_quo, _) = fcall_division(a_flat, b.as_limbs(), &mut quo_flat, &mut rem_flat); + let (limbs_quo, _) = fcall_division( + a_flat, + b.as_limbs(), + &mut quo_flat, + &mut rem_flat, + #[cfg(feature = "hints")] + hints, + ); let quo = U256::flat_to_slice(&quo_flat[..limbs_quo]); let rem = U256::from_u64s(&rem_flat); // Verify the division let mut q_b = vec![U256::ZERO; len_a + 1]; // The +1 is because mul_long and add_agtb are a general purpose functions let mut q_b_r = vec![U256::ZERO; len_a + 1]; - verify_division(a, b, quo, &rem, &mut q_b, &mut q_b_r); + verify_division( + a, + b, + quo, + &rem, + &mut q_b, + &mut q_b_r, + #[cfg(feature = "hints")] + hints, + ); rem } @@ -64,7 +84,12 @@ pub fn rem_short_init(a: &[U256], b: &U256) -> U256 { /// /// # Returns /// The remainder: a mod b -pub fn rem_short(a: &[U256], b: &U256, scratch: &mut ShortScratch) -> U256 { +pub fn rem_short( + a: &[U256], + b: &U256, + scratch: &mut ShortScratch, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> U256 { let len_a = a.len(); #[cfg(debug_assertions)] { @@ -90,12 +115,28 @@ pub fn rem_short(a: &[U256], b: &U256, scratch: &mut ShortScratch) -> U256 { let a_flat = U256::slice_to_flat(a); // Hint the quotient and remainder - let (limbs_quo, _) = fcall_division(a_flat, b.as_limbs(), &mut scratch.quo, &mut scratch.rem); + let (limbs_quo, _) = fcall_division( + a_flat, + b.as_limbs(), + &mut scratch.quo, + &mut scratch.rem, + #[cfg(feature = "hints")] + hints, + ); let quo = U256::flat_to_slice(&scratch.quo[..limbs_quo]); let rem = U256::from_u64s(&scratch.rem); // Verify the division - verify_division(a, b, quo, &rem, &mut scratch.q_b, &mut scratch.q_b_r); + verify_division( + a, + b, + quo, + &rem, + &mut scratch.q_b, + &mut scratch.q_b_r, + #[cfg(feature = "hints")] + hints, + ); rem } @@ -109,6 +150,7 @@ fn verify_division( rem: &U256, q_b: &mut [U256], q_b_r: &mut [U256], + #[cfg(feature = "hints")] hints: &mut Vec, ) { let len_a = a.len(); let len_quo = quo.len(); @@ -119,7 +161,13 @@ fn verify_division( assert!(!quo[len_quo - 1].is_zero(), "Quotient must not have leading zeros"); // Multiply the quotient by b - let q_b_len = mul_short(quo, b, q_b); + let q_b_len = mul_short( + quo, + b, + q_b, + #[cfg(feature = "hints")] + hints, + ); if rem.is_zero() { // If the remainder is zero, then a must be equal to q·b @@ -128,7 +176,13 @@ fn verify_division( // If the remainder is non-zero, then we should check that a must be equal to q·b + r and r < b assert!(rem.lt(b), "Remainder must be less than divisor"); - let q_b_r_len = add_short(&q_b[..q_b_len], rem, q_b_r); + let q_b_r_len = add_short( + &q_b[..q_b_len], + rem, + q_b_r, + #[cfg(feature = "hints")] + hints, + ); assert!(U256::eq_slices(a, &q_b_r[..q_b_r_len]), "a != q·b + r"); } } diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/square_long.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/square_long.rs index 7f560bdae..4dd9df42f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/square_long.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/square_long.rs @@ -16,7 +16,11 @@ use super::{rem_long, LongScratch, U256}; /// /// # Note /// Not optimal for `len(a) == 1`, use `square_short` instead -pub fn square_long(a: &[U256], out: &mut [U256]) -> usize { +pub fn square_long( + a: &[U256], + out: &mut [U256], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> usize { // a3 a2 a1 a0 // * a3 a2 a1 a0 // ------------------------------------------------------- 0 @@ -51,7 +55,11 @@ pub fn square_long(a: &[U256], out: &mut [U256]) -> usize { dl: out[2 * i].as_limbs_mut(), dh: &mut [0, 0, 0, 0], }; - syscall_arith256(&mut ai_ai); + syscall_arith256( + &mut ai_ai, + #[cfg(feature = "hints")] + hints, + ); out[2 * i + 1] = U256::from_u64s(ai_ai.dh); } @@ -67,19 +75,31 @@ pub fn square_long(a: &[U256], out: &mut [U256]) -> usize { dl: &mut [0, 0, 0, 0], dh: &mut [0, 0, 0, 0], }; - syscall_arith256(&mut ai_aj); + syscall_arith256( + &mut ai_aj, + #[cfg(feature = "hints")] + hints, + ); // Double the result 2·a[i]·a[j] // Start by doubling the lower chunk: 2·l₁ = [1/0]·B + l₂ let mut dbl_low = SyscallAdd256Params { a: ai_aj.dl, b: ai_aj.dl, cin: 0, c: &mut [0, 0, 0, 0] }; - let dbl_low_carry = syscall_add256(&mut dbl_low); + let dbl_low_carry = syscall_add256( + &mut dbl_low, + #[cfg(feature = "hints")] + hints, + ); // Next, double the higher chunk: 2·h₁·B = [1/0]·B² + h₂·B let mut dbl_high = SyscallAdd256Params { a: ai_aj.dh, b: ai_aj.dh, cin: 0, c: &mut [0, 0, 0, 0] }; - let dbl_high_carry = syscall_add256(&mut dbl_high); + let dbl_high_carry = syscall_add256( + &mut dbl_high, + #[cfg(feature = "hints")] + hints, + ); // If there's a carry from doubling the low part, add it to the high part if dbl_low_carry != 0 { @@ -90,7 +110,11 @@ pub fn square_long(a: &[U256], out: &mut [U256]) -> usize { cin: 1, c: dbl_high.c, }; - let _carry = syscall_add256(&mut add); + let _carry = syscall_add256( + &mut add, + #[cfg(feature = "hints")] + hints, + ); debug_assert!(_carry == 0, "Unexpected carry in intermediate addition"); } @@ -106,7 +130,11 @@ pub fn square_long(a: &[U256], out: &mut [U256]) -> usize { cin: 0, c: &mut [0, 0, 0, 0], }; - let add_low_carry = syscall_add256(&mut add_low); + let add_low_carry = syscall_add256( + &mut add_low, + #[cfg(feature = "hints")] + hints, + ); out[i + j] = U256::from_u64s(add_low.c); if add_low_carry != 0 { @@ -117,7 +145,11 @@ pub fn square_long(a: &[U256], out: &mut [U256]) -> usize { cin: 1, c: out[i + j + 1].as_limbs_mut(), }; - let add_carry = syscall_add256(&mut add); + let add_carry = syscall_add256( + &mut add, + #[cfg(feature = "hints")] + hints, + ); if add_carry != 0 { let a_in = out[i + j + 2]; @@ -127,7 +159,11 @@ pub fn square_long(a: &[U256], out: &mut [U256]) -> usize { cin: 1, c: out[i + j + 2].as_limbs_mut(), }; - let _carry = syscall_add256(&mut add2); + let _carry = syscall_add256( + &mut add2, + #[cfg(feature = "hints")] + hints, + ); debug_assert!(_carry == 0, "Unexpected carry in intermediate addition"); } @@ -140,7 +176,11 @@ pub fn square_long(a: &[U256], out: &mut [U256]) -> usize { cin: 0, c: &mut [0, 0, 0, 0], }; - let add_mid_carry = syscall_add256(&mut add_mid); + let add_mid_carry = syscall_add256( + &mut add_mid, + #[cfg(feature = "hints")] + hints, + ); out[i + j + 1] = U256::from_u64s(add_mid.c); if add_mid_carry != 0 { @@ -151,7 +191,11 @@ pub fn square_long(a: &[U256], out: &mut [U256]) -> usize { cin: 1, c: out[i + j + 2].as_limbs_mut(), }; - let _carry = syscall_add256(&mut add); + let _carry = syscall_add256( + &mut add, + #[cfg(feature = "hints")] + hints, + ); debug_assert!(_carry == 0, "Unexpected carry in intermediate addition"); } @@ -165,7 +209,11 @@ pub fn square_long(a: &[U256], out: &mut [U256]) -> usize { cin: 1, c: out[i + j + 2].as_limbs_mut(), }; - let _carry = syscall_add256(&mut add); + let _carry = syscall_add256( + &mut add, + #[cfg(feature = "hints")] + hints, + ); debug_assert!(_carry == 0, "Unexpected carry in intermediate addition"); } @@ -192,6 +240,7 @@ pub fn square_and_reduce_long( a: &[U256], modulus: &[U256], scratch: &mut LongScratch, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> Vec { #[cfg(debug_assertions)] { @@ -200,7 +249,18 @@ pub fn square_and_reduce_long( assert!(!modulus[len_m - 1].is_zero(), "Input 'modulus' must not have leading zeros"); } - let sq_len = square_long(a, &mut scratch.mul); + let sq_len = square_long( + a, + &mut scratch.mul, + #[cfg(feature = "hints")] + hints, + ); - rem_long(&scratch.mul[..sq_len], modulus, &mut scratch.rem) + rem_long( + &scratch.mul[..sq_len], + modulus, + &mut scratch.rem, + #[cfg(feature = "hints")] + hints, + ) } diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/square_short.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/square_short.rs index 5315a2b3a..72d4ba290 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/square_short.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/square_short.rs @@ -9,7 +9,10 @@ use super::{rem_short, ShortScratch, U256}; /// /// # Returns /// A tuple of (result array, number of limbs used) -pub fn square_short(a: &U256) -> ([U256; 2], usize) { +pub fn square_short( + a: &U256, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> ([U256; 2], usize) { #[cfg(debug_assertions)] { assert!(!a.is_zero(), "Input 'a' must not have leading zeros"); @@ -26,7 +29,11 @@ pub fn square_short(a: &U256) -> ([U256; 2], usize) { dl: out[0].as_limbs_mut(), dh: &mut dh, }; - syscall_arith256(&mut sq_params); + syscall_arith256( + &mut sq_params, + #[cfg(feature = "hints")] + hints, + ); let len = if dh == [0u64; 4] { 1 @@ -45,13 +52,28 @@ pub fn square_short(a: &U256) -> ([U256; 2], usize) { /// /// # Returns /// The remainder: a² mod modulus -pub fn square_and_reduce_short(a: &U256, modulus: &U256, scratch: &mut ShortScratch) -> U256 { +pub fn square_and_reduce_short( + a: &U256, + modulus: &U256, + scratch: &mut ShortScratch, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> U256 { #[cfg(debug_assertions)] { assert!(!modulus.is_zero(), "Input 'modulus' must not be zero"); } - let (sq, len) = square_short(a); + let (sq, len) = square_short( + a, + #[cfg(feature = "hints")] + hints, + ); - rem_short(&sq[..len], modulus, scratch) + rem_short( + &sq[..len], + modulus, + scratch, + #[cfg(feature = "hints")] + hints, + ) } diff --git a/ziskos/entrypoint/src/zisklib/lib/bigint256.rs b/ziskos/entrypoint/src/zisklib/lib/bigint256.rs index 1088c48b4..c31023036 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bigint256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bigint256.rs @@ -5,26 +5,51 @@ use crate::{ zisklib::{eq, fcall_bigint256_div, fcall_msb_pos_256, lt}, }; -pub fn mul256(a: &[u64; 4], b: &[u64; 4]) -> ([u64; 4], [u64; 4]) { +pub fn mul256( + a: &[u64; 4], + b: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> ([u64; 4], [u64; 4]) { let mut params = SyscallArith256Params { a, b, c: &[0u64; 4], dl: &mut [0u64; 4], dh: &mut [0u64; 4] }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); (*params.dl, *params.dh) } -pub fn wmul256(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] { +pub fn wmul256( + a: &[u64; 4], + b: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { let mut params = SyscallArith256Params { a, b, c: &[0u64; 4], dl: &mut [0u64; 4], dh: &mut [0u64; 4] }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.dl } -pub fn divrem256(a: &[u64; 4], b: &[u64; 4]) -> ([u64; 4], [u64; 4]) { +pub fn divrem256( + a: &[u64; 4], + b: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> ([u64; 4], [u64; 4]) { // Check for division by zero assert!(!eq(b, &[0u64; 4]), "Division by zero"); // Hint the result of the division - let (quotient, remainder) = fcall_bigint256_div(a, b); + let (quotient, remainder) = fcall_bigint256_div( + a, + b, + #[cfg(feature = "hints")] + hints, + ); // Check that a = b * quotient + remainder and remainder < b assert!(lt(&remainder, b), "Remainder is not less than divisor"); @@ -35,14 +60,23 @@ pub fn divrem256(a: &[u64; 4], b: &[u64; 4]) -> ([u64; 4], [u64; 4]) { dl: &mut [0u64; 4], dh: &mut [0u64; 4], }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); assert!(eq(params.dl, a), "Dividend does not equal divisor * quotient + remainder"); (quotient, remainder) } /// Raises `x` to (2^power_log) modulo `module` using repeated squaring -pub fn exp_power_of_two(x: &[u64; 4], module: &[u64; 4], power_log: usize) -> [u64; 4] { +pub fn exp_power_of_two( + x: &[u64; 4], + module: &[u64; 4], + power_log: usize, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x^1 = x if power_log == 0 { return *x; @@ -58,7 +92,11 @@ pub fn exp_power_of_two(x: &[u64; 4], module: &[u64; 4], power_log: usize) -> [u module, d: &mut [0u64; 4], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); result = *params.d; } @@ -66,7 +104,12 @@ pub fn exp_power_of_two(x: &[u64; 4], module: &[u64; 4], power_log: usize) -> [u } /// Raises `x` to (2^power_log) modulo `module` using repeated squaring -pub fn exp_power_of_two_self(x: &mut [u64; 4], module: &[u64; 4], power_log: usize) { +pub fn exp_power_of_two_self( + x: &mut [u64; 4], + module: &[u64; 4], + power_log: usize, + #[cfg(feature = "hints")] hints: &mut Vec, +) { if power_log == 0 { return; } @@ -75,12 +118,20 @@ pub fn exp_power_of_two_self(x: &mut [u64; 4], module: &[u64; 4], power_log: usi for _ in 0..power_log { let mut params = SyscallArith256ModParams { a: x, b: x, c: &zero, module, d: &mut [0u64; 4] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *x = *params.d; } } -pub fn wpow256(a: &[u64; 4], exp: &[u64; 4]) -> [u64; 4] { +pub fn wpow256( + a: &[u64; 4], + exp: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // 0^0 = 1 by convention // 0^n = 0 for n > 0 if eq(a, &[0u64; 4]) { @@ -103,7 +154,11 @@ pub fn wpow256(a: &[u64; 4], exp: &[u64; 4]) -> [u64; 4] { let mut dh = [0u64; 4]; let mut params = SyscallArith256Params { a, b: a, c: &[0u64; 4], dl: &mut dl, dh: &mut dh }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); return dl; } _ => {} @@ -112,7 +167,12 @@ pub fn wpow256(a: &[u64; 4], exp: &[u64; 4]) -> [u64; 4] { // We can assume exp > 2 from now on // Hint the length the binary representations of exp // We will verify the output by recomposing exp - let (max_limb, max_bit) = fcall_msb_pos_256(exp, &[0, 0, 0, 0]); + let (max_limb, max_bit) = fcall_msb_pos_256( + exp, + &[0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); // Perform the loop, based on the binary representation of exp @@ -141,7 +201,11 @@ pub fn wpow256(a: &[u64; 4], exp: &[u64; 4]) -> [u64; 4] { dl: &mut dl, dh: &mut dh, }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); result = dl; // Get the next bit b of exp @@ -154,7 +218,11 @@ pub fn wpow256(a: &[u64; 4], exp: &[u64; 4]) -> [u64; 4] { dl: &mut dl, dh: &mut dh, }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); result = dl; // Reconstruct exp @@ -178,7 +246,12 @@ pub fn wpow256(a: &[u64; 4], exp: &[u64; 4]) -> [u64; 4] { /// - `m` must point to a valid `[u64; 4]` (32 bytes). /// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. #[no_mangle] -pub unsafe extern "C" fn redmod256_c(a: *const u64, m: *const u64, result: *mut u64) { +pub unsafe extern "C" fn redmod256_c( + a: *const u64, + m: *const u64, + result: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let mut d = [0u64; 4]; let mut params = SyscallArith256ModParams { a: &*(a as *const [u64; 4]), @@ -187,7 +260,11 @@ pub unsafe extern "C" fn redmod256_c(a: *const u64, m: *const u64, result: *mut module: &*(m as *const [u64; 4]), d: &mut d, }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(d.as_ptr(), result, 4); } @@ -205,6 +282,7 @@ pub unsafe extern "C" fn addmod256_c( b: *const u64, m: *const u64, result: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) { let mut d = [0u64; 4]; let mut params = SyscallArith256ModParams { @@ -214,7 +292,11 @@ pub unsafe extern "C" fn addmod256_c( module: &*(m as *const [u64; 4]), d: &mut d, }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(d.as_ptr(), result, 4); } @@ -232,6 +314,7 @@ pub unsafe extern "C" fn mulmod256_c( b: *const u64, m: *const u64, result: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) { let mut d = [0u64; 4]; let mut params = SyscallArith256ModParams { @@ -241,7 +324,11 @@ pub unsafe extern "C" fn mulmod256_c( module: &*(m as *const [u64; 4]), d: &mut d, }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(d.as_ptr(), result, 4); } @@ -253,7 +340,12 @@ pub unsafe extern "C" fn mulmod256_c( /// - `b` must point to a valid `[u64; 4]` (32 bytes). /// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. #[no_mangle] -pub unsafe extern "C" fn wmul256_c(a: *const u64, b: *const u64, result: *mut u64) { +pub unsafe extern "C" fn wmul256_c( + a: *const u64, + b: *const u64, + result: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let mut dl = [0u64; 4]; let mut dh = [0u64; 4]; let mut params = SyscallArith256Params { @@ -263,7 +355,11 @@ pub unsafe extern "C" fn wmul256_c(a: *const u64, b: *const u64, result: *mut u6 dl: &mut dl, dh: &mut dh, }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(dl.as_ptr(), result, 4); } @@ -277,7 +373,12 @@ pub unsafe extern "C" fn wmul256_c(a: *const u64, b: *const u64, result: *mut u6 /// /// Returns `true` if overflow occurred, `false` otherwise. #[no_mangle] -pub unsafe extern "C" fn omul256_c(a: *const u64, b: *const u64, result: *mut u64) -> bool { +pub unsafe extern "C" fn omul256_c( + a: *const u64, + b: *const u64, + result: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let mut dl = [0u64; 4]; let mut dh = [0u64; 4]; let mut params = SyscallArith256Params { @@ -287,7 +388,11 @@ pub unsafe extern "C" fn omul256_c(a: *const u64, b: *const u64, result: *mut u6 dl: &mut dl, dh: &mut dh, }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(dl.as_ptr(), result, 4); @@ -306,7 +411,13 @@ pub unsafe extern "C" fn omul256_c(a: *const u64, b: *const u64, result: *mut u6 /// # Panics /// Panics if `b` is zero. #[no_mangle] -pub unsafe extern "C" fn divrem256_c(a: *const u64, b: *const u64, q: *mut u64, r: *mut u64) { +pub unsafe extern "C" fn divrem256_c( + a: *const u64, + b: *const u64, + q: *mut u64, + r: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 4]); let b_ref = &*(b as *const [u64; 4]); @@ -314,14 +425,23 @@ pub unsafe extern "C" fn divrem256_c(a: *const u64, b: *const u64, q: *mut u64, assert!(!eq(b_ref, &[0u64; 4]), "Division by zero"); // Hint the result of the division - let (quotient, remainder) = fcall_bigint256_div(a_ref, b_ref); + let (quotient, remainder) = fcall_bigint256_div( + a_ref, + b_ref, + #[cfg(feature = "hints")] + hints, + ); // Check that a = b * quotient + remainder and remainder < b let mut dl = [0u64; 4]; let mut dh = [0u64; 4]; let mut params = SyscallArith256Params { a: b_ref, b: "ient, c: &remainder, dl: &mut dl, dh: &mut dh }; - syscall_arith256(&mut params); + syscall_arith256( + &mut params, + #[cfg(feature = "hints")] + hints, + ); assert!(eq(&dl, a_ref), "Dividend does not equal divisor * quotient + remainder"); assert!(lt(&remainder, b_ref), "Remainder is not less than divisor"); @@ -336,11 +456,20 @@ pub unsafe extern "C" fn divrem256_c(a: *const u64, b: *const u64, q: *mut u64, /// - `exp` must point to a valid `[u64; 4]` (32 bytes). /// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. #[no_mangle] -pub unsafe extern "C" fn wpow256_c(a: *const u64, exp: *const u64, result: *mut u64) { +pub unsafe extern "C" fn wpow256_c( + a: *const u64, + exp: *const u64, + result: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 4]); let exp_ref = &*(exp as *const [u64; 4]); - let res = wpow256(a_ref, exp_ref); - + let res = wpow256( + a_ref, + exp_ref, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(res.as_ptr(), result, 4); } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs index 69ea2a068..ce35a5840 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs @@ -22,7 +22,10 @@ use super::{ /// - Bit 7 (0x80): Compression flag (must be 1 for compressed) /// - Bit 6 (0x40): Infinity flag (1 = point at infinity) /// - Bit 5 (0x20): Sign flag (1 = y is lexicographically largest) -pub fn decompress_bls12_381(input: &[u8; 48]) -> Result<([u64; 12], bool), &'static str> { +pub fn decompress_bls12_381( + input: &[u8; 48], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Result<([u64; 12], bool), &'static str> { let flags = input[0]; // Check compression bit @@ -66,17 +69,39 @@ pub fn decompress_bls12_381(input: &[u8; 48]) -> Result<([u64; 12], bool), &'sta } // Calculate the y-coordinate of the point: y = sqrt(x³ + 4) - let x_sq = square_fp_bls12_381(&x); - let x_cb = mul_fp_bls12_381(&x_sq, &x); - let y_sq = add_fp_bls12_381(&x_cb, &E_B); - - let (y, has_sqrt) = sqrt_fp_bls12_381(&y_sq); + let x_sq = square_fp_bls12_381( + &x, + #[cfg(feature = "hints")] + hints, + ); + let x_cb = mul_fp_bls12_381( + &x_sq, + &x, + #[cfg(feature = "hints")] + hints, + ); + let y_sq = add_fp_bls12_381( + &x_cb, + &E_B, + #[cfg(feature = "hints")] + hints, + ); + + let (y, has_sqrt) = sqrt_fp_bls12_381( + &y_sq, + #[cfg(feature = "hints")] + hints, + ); if !has_sqrt { return Err("No square root exists - point not on curve"); } // Determine the sign of y, which is (lexicographically) done by checking if y > -y - let y_neg = neg_fp_bls12_381(&y); + let y_neg = neg_fp_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); let y_is_larger = lt(&y_neg, &y); // Select the correct y based on sign bit @@ -90,39 +115,93 @@ pub fn decompress_bls12_381(input: &[u8; 48]) -> Result<([u64; 12], bool), &'sta } /// Check if a non-zero point `p` is on the BLS12-381 curve -pub fn is_on_curve_bls12_381(p: &[u64; 12]) -> bool { +pub fn is_on_curve_bls12_381( + p: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let x: [u64; 6] = p[0..6].try_into().unwrap(); let y: [u64; 6] = p[6..12].try_into().unwrap(); // p in E iff y² == x³ + 4 - let lhs = square_fp_bls12_381(&y); - let mut rhs = square_fp_bls12_381(&x); - rhs = mul_fp_bls12_381(&rhs, &x); - rhs = add_fp_bls12_381(&rhs, &E_B); + let lhs = square_fp_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); + let mut rhs = square_fp_bls12_381( + &x, + #[cfg(feature = "hints")] + hints, + ); + rhs = mul_fp_bls12_381( + &rhs, + &x, + #[cfg(feature = "hints")] + hints, + ); + rhs = add_fp_bls12_381( + &rhs, + &E_B, + #[cfg(feature = "hints")] + hints, + ); eq(&lhs, &rhs) } /// Check if a non-zero point `p` is on the BLS12-381 subgroup -pub fn is_on_subgroup_bls12_381(p: &[u64; 12]) -> bool { +pub fn is_on_subgroup_bls12_381( + p: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { // p in subgroup iff: // ((x²-1)/3)(2·σ(P) - P - σ²(P)) == σ²(P) // where σ(x,y) = (ɣ·x,y) // Compute σ(P), σ²(P) - let sigma1 = sigma_endomorphism_bls12_381(p); - let rhs = sigma_endomorphism_bls12_381(&sigma1); + let sigma1 = sigma_endomorphism_bls12_381( + p, + #[cfg(feature = "hints")] + hints, + ); + let rhs = sigma_endomorphism_bls12_381( + &sigma1, + #[cfg(feature = "hints")] + hints, + ); // Compute lhs = ((x²-1)/3)(2·σ(P) - P - σ²(P)) - let mut lhs = dbl_bls12_381(&sigma1); - lhs = sub_bls12_381(&lhs, p); - lhs = sub_bls12_381(&lhs, &rhs); - lhs = scalar_mul_by_x2div3_bls12_381(&lhs); + let mut lhs = dbl_bls12_381( + &sigma1, + #[cfg(feature = "hints")] + hints, + ); + lhs = sub_bls12_381( + &lhs, + p, + #[cfg(feature = "hints")] + hints, + ); + lhs = sub_bls12_381( + &lhs, + &rhs, + #[cfg(feature = "hints")] + hints, + ); + lhs = scalar_mul_by_x2div3_bls12_381( + &lhs, + #[cfg(feature = "hints")] + hints, + ); eq(&lhs, &rhs) } /// Adds two non-zero points `p1` and `p2` on the BLS12-381 curve -pub fn add_bls12_381(p1: &[u64; 12], p2: &[u64; 12]) -> [u64; 12] { +pub fn add_bls12_381( + p1: &[u64; 12], + p2: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let x1: [u64; 6] = p1[0..6].try_into().unwrap(); let y1: [u64; 6] = p1[6..12].try_into().unwrap(); let x2: [u64; 6] = p2[0..6].try_into().unwrap(); @@ -133,7 +212,11 @@ pub fn add_bls12_381(p1: &[u64; 12], p2: &[u64; 12]) -> [u64; 12] { // Is y1 == y2? if eq(&y1, &y2) { // Compute the doubling - return dbl_bls12_381(p1); + return dbl_bls12_381( + p1, + #[cfg(feature = "hints")] + hints, + ); } else { // Return 𝒪 return IDENTITY_G1; @@ -144,7 +227,11 @@ pub fn add_bls12_381(p1: &[u64; 12], p2: &[u64; 12]) -> [u64; 12] { let mut p1 = SyscallPoint384 { x: x1, y: y1 }; let p2 = SyscallPoint384 { x: x2, y: y2 }; let mut params = SyscallBls12_381CurveAddParams { p1: &mut p1, p2: &p2 }; - syscall_bls12_381_curve_add(&mut params); + syscall_bls12_381_curve_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 12]; result[0..6].copy_from_slice(&p1.x); @@ -153,12 +240,15 @@ pub fn add_bls12_381(p1: &[u64; 12], p2: &[u64; 12]) -> [u64; 12] { } /// Negation of a non-zero point `p` on the BLS12-381 curve -pub fn neg_bls12_381(p: &[u64; 12]) -> [u64; 12] { +pub fn neg_bls12_381(p: &[u64; 12], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 12] { let x: [u64; 6] = p[0..6].try_into().unwrap(); let y: [u64; 6] = p[6..12].try_into().unwrap(); - let y_neg = neg_fp_bls12_381(&y); - + let y_neg = neg_fp_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 12]; result[0..6].copy_from_slice(&x); result[6..12].copy_from_slice(&y_neg); @@ -166,9 +256,13 @@ pub fn neg_bls12_381(p: &[u64; 12]) -> [u64; 12] { } /// Doubling of a non-zero point `p` on the BLS12-381 curve -pub fn dbl_bls12_381(p: &[u64; 12]) -> [u64; 12] { +pub fn dbl_bls12_381(p: &[u64; 12], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 12] { let mut p = SyscallPoint384 { x: p[0..6].try_into().unwrap(), y: p[6..12].try_into().unwrap() }; - syscall_bls12_381_curve_dbl(&mut p); + syscall_bls12_381_curve_dbl( + &mut p, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 12]; result[0..6].copy_from_slice(&p.x); @@ -177,22 +271,39 @@ pub fn dbl_bls12_381(p: &[u64; 12]) -> [u64; 12] { } /// Subtraction of two non-zero points `p1` and `p2` on the BLS12-381 curve -pub fn sub_bls12_381(p1: &[u64; 12], p2: &[u64; 12]) -> [u64; 12] { +pub fn sub_bls12_381( + p1: &[u64; 12], + p2: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let x2: [u64; 6] = p2[0..6].try_into().unwrap(); let y2: [u64; 6] = p2[6..12].try_into().unwrap(); // P1 - P2 = P1 + (-P2) - let y2_neg = neg_fp_bls12_381(&y2); + let y2_neg = neg_fp_bls12_381( + &y2, + #[cfg(feature = "hints")] + hints, + ); let mut p2_neg = [0u64; 12]; p2_neg[0..6].copy_from_slice(&x2); p2_neg[6..12].copy_from_slice(&y2_neg); - add_bls12_381(p1, &p2_neg) + add_bls12_381( + p1, + &p2_neg, + #[cfg(feature = "hints")] + hints, + ) } /// Multiplies a non-zero point `p` on the BLS12-381 curve by a scalar `k` on the BLS12-381 scalar field -pub fn scalar_mul_bls12_381(p: &[u64; 12], k: &[u64; 6]) -> [u64; 12] { +pub fn scalar_mul_bls12_381( + p: &[u64; 12], + k: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { // Direct cases: k = 0, k = 1, k = 2 match k { [0, 0, 0, 0, 0, 0] => { @@ -205,7 +316,11 @@ pub fn scalar_mul_bls12_381(p: &[u64; 12], k: &[u64; 6]) -> [u64; 12] { } [2, 0, 0, 0, 0, 0] => { // Return 2p - return dbl_bls12_381(p); + return dbl_bls12_381( + p, + #[cfg(feature = "hints")] + hints, + ); } _ => {} } @@ -214,7 +329,12 @@ pub fn scalar_mul_bls12_381(p: &[u64; 12], k: &[u64; 6]) -> [u64; 12] { // Hint the length the binary representations of k // We will verify the output by recomposing k // Moreover, we should check that the first received bit is 1 - let (max_limb, max_bit) = fcall_msb_pos_384(k, &[0, 0, 0, 0, 0, 0]); + let (max_limb, max_bit) = fcall_msb_pos_384( + k, + &[0, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); // Perform the loop, based on the binary representation of k @@ -247,13 +367,21 @@ pub fn scalar_mul_bls12_381(p: &[u64; 12], k: &[u64; 6]) -> [u64; 12] { for i in (0..=limb).rev() { for j in (0..=bit).rev() { // Always double - syscall_bls12_381_curve_dbl(&mut q); + syscall_bls12_381_curve_dbl( + &mut q, + #[cfg(feature = "hints")] + hints, + ); // Get the next bit b of k. // If b == 1, we should add P to Q, otherwise start the next iteration if ((k[i] >> j) & 1) == 1 { let mut params = SyscallBls12_381CurveAddParams { p1: &mut q, p2: &p }; - syscall_bls12_381_curve_add(&mut params); + syscall_bls12_381_curve_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); // Reconstruct k k_rec[i] |= 1 << j; @@ -273,17 +401,29 @@ pub fn scalar_mul_bls12_381(p: &[u64; 12], k: &[u64; 6]) -> [u64; 12] { } /// Scalar multiplication of a non-zero point `p` by a binary scalar `k` -pub fn scalar_mul_bin_bls12_381(p: &[u64; 12], k: &[u8]) -> [u64; 12] { +pub fn scalar_mul_bin_bls12_381( + p: &[u64; 12], + k: &[u8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let x1: [u64; 6] = p[0..6].try_into().unwrap(); let y1: [u64; 6] = p[6..12].try_into().unwrap(); let p = SyscallPoint384 { x: x1, y: y1 }; let mut r = SyscallPoint384 { x: x1, y: y1 }; for &bit in k.iter().skip(1) { - syscall_bls12_381_curve_dbl(&mut r); + syscall_bls12_381_curve_dbl( + &mut r, + #[cfg(feature = "hints")] + hints, + ); if bit == 1 { let mut params = SyscallBls12_381CurveAddParams { p1: &mut r, p2: &p }; - syscall_bls12_381_curve_add(&mut params); + syscall_bls12_381_curve_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); } } @@ -294,7 +434,10 @@ pub fn scalar_mul_bin_bls12_381(p: &[u64; 12], k: &[u8]) -> [u64; 12] { } /// Scalar multiplication of a non-zero point by (x²-1)/3 -pub fn scalar_mul_by_x2div3_bls12_381(p: &[u64; 12]) -> [u64; 12] { +pub fn scalar_mul_by_x2div3_bls12_381( + p: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { /// Family parameter (X²-1)/3 const X2DIV3_BIN_BE: [u8; 126] = [ 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -304,18 +447,30 @@ pub fn scalar_mul_by_x2div3_bls12_381(p: &[u64; 12]) -> [u64; 12] { 0, 1, 0, 1, 0, 1, ]; - scalar_mul_bin_bls12_381(p, &X2DIV3_BIN_BE) + scalar_mul_bin_bls12_381( + p, + &X2DIV3_BIN_BE, + #[cfg(feature = "hints")] + hints, + ) } /// Compute the sigma endomorphism σ of a non-zero point `p`, defined as: /// σ : E(Fp) -> E(Fp) /// (x,y) |-> (ɣ·x,y) -pub fn sigma_endomorphism_bls12_381(p: &[u64; 12]) -> [u64; 12] { +pub fn sigma_endomorphism_bls12_381( + p: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let mut x: [u64; 6] = p[0..6].try_into().unwrap(); let y: [u64; 6] = p[6..12].try_into().unwrap(); - x = mul_fp_bls12_381(&x, &GAMMA); - + x = mul_fp_bls12_381( + &x, + &GAMMA, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 12]; result[0..6].copy_from_slice(&x); result[6..12].copy_from_slice(&y); @@ -332,10 +487,18 @@ pub fn sigma_endomorphism_bls12_381(p: &[u64; 12]) -> [u64; 12] { /// - 1 = success (point at infinity) /// - 2 = error #[no_mangle] -pub unsafe extern "C" fn decompress_bls12_381_c(ret: *mut u64, input: *const u8) -> u8 { +pub unsafe extern "C" fn decompress_bls12_381_c( + ret: *mut u64, + input: *const u8, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> u8 { let input_arr: &[u8; 48] = &*(input as *const [u8; 48]); - match decompress_bls12_381(input_arr) { + match decompress_bls12_381( + input_arr, + #[cfg(feature = "hints")] + hints, + ) { Ok((result, is_infinity)) => { let ret_arr: &mut [u64; 12] = &mut *(ret as *mut [u64; 12]); *ret_arr = result; @@ -353,29 +516,52 @@ pub unsafe extern "C" fn decompress_bls12_381_c(ret: *mut u64, input: *const u8) /// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. /// Returns true if the point is on the curve, false otherwise. #[no_mangle] -pub unsafe extern "C" fn is_on_curve_bls12_381_c(p: *const u64) -> bool { +pub unsafe extern "C" fn is_on_curve_bls12_381_c( + p: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); - is_on_curve_bls12_381(p_arr) + is_on_curve_bls12_381( + p_arr, + #[cfg(feature = "hints")] + hints, + ) } /// # Safety /// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. /// Returns true if the point is in the G1 subgroup, false otherwise. #[no_mangle] -pub unsafe extern "C" fn is_on_subgroup_bls12_381_c(p: *const u64) -> bool { +pub unsafe extern "C" fn is_on_subgroup_bls12_381_c( + p: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); - is_on_subgroup_bls12_381(p_arr) + is_on_subgroup_bls12_381( + p_arr, + #[cfg(feature = "hints")] + hints, + ) } /// # Safety /// - `p1` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - `p2` must point to a valid `[u64; 12]` (96 bytes). #[no_mangle] -pub unsafe extern "C" fn add_bls12_381_c(p1: *mut u64, p2: *const u64) -> bool { +pub unsafe extern "C" fn add_bls12_381_c( + p1: *mut u64, + p2: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let p1_arr: &[u64; 12] = &*(p1 as *const [u64; 12]); let p2_arr: &[u64; 12] = &*(p2 as *const [u64; 12]); - let result = add_bls12_381(p1_arr, p2_arr); + let result = add_bls12_381( + p1_arr, + p2_arr, + #[cfg(feature = "hints")] + hints, + ); if result == IDENTITY_G1 { return true; } @@ -389,11 +575,18 @@ pub unsafe extern "C" fn add_bls12_381_c(p1: *mut u64, p2: *const u64) -> bool { /// - `p` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - Point must be non-zero. #[no_mangle] -pub unsafe extern "C" fn dbl_bls12_381_c(p: *mut u64) { +pub unsafe extern "C" fn dbl_bls12_381_c( + p: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let mut p_point = SyscallPoint384 { x: *(p as *const [u64; 6]), y: *(p.add(6) as *const [u64; 6]) }; - syscall_bls12_381_curve_dbl(&mut p_point); + syscall_bls12_381_curve_dbl( + &mut p_point, + #[cfg(feature = "hints")] + hints, + ); *(p as *mut [u64; 6]) = p_point.x; *(p.add(6) as *mut [u64; 6]) = p_point.y; @@ -405,12 +598,21 @@ pub unsafe extern "C" fn dbl_bls12_381_c(p: *mut u64) { /// - `k` must point to a valid `[u64; 6]` (48 bytes) for the scalar. /// - Point must be non-zero. #[no_mangle] -pub unsafe extern "C" fn scalar_mul_bls12_381_c(ret: *mut u64, p: *const u64, k: *const u64) { +pub unsafe extern "C" fn scalar_mul_bls12_381_c( + ret: *mut u64, + p: *const u64, + k: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); let k_arr: &[u64; 6] = &*(k as *const [u64; 6]); - let result = scalar_mul_bls12_381(p_arr, k_arr); - + let result = scalar_mul_bls12_381( + p_arr, + k_arr, + #[cfg(feature = "hints")] + hints, + ); let ret_arr: &mut [u64; 12] = &mut *(ret as *mut [u64; 12]); *ret_arr = result; } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/cyclotomic.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/cyclotomic.rs index 845a669fc..3da18af5a 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/cyclotomic.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/cyclotomic.rs @@ -48,7 +48,10 @@ pub fn compress_cyclo_bls12_381(a: &[u64; 72]) -> [u64; 48] { /// **NOTE**: If the input is not of the form C(a), where a ∈ GΦ6(p²), then the compression-decompression /// technique is not well defined. This means that D(C(a)) != a. #[inline] -pub fn decompress_cyclo_bls12_381(a: &[u64; 48]) -> [u64; 72] { +pub fn decompress_cyclo_bls12_381( + a: &[u64; 48], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { let a2: &[u64; 12] = &a[0..12].try_into().unwrap(); let a3: &[u64; 12] = &a[12..24].try_into().unwrap(); let a4: &[u64; 12] = &a[24..36].try_into().unwrap(); @@ -56,40 +59,179 @@ pub fn decompress_cyclo_bls12_381(a: &[u64; 48]) -> [u64; 72] { let (a0, a1) = if eq(a2, &[0; 12]) { // a1 = (2·a4·a5)/a3 - let a3_inv = inv_fp2_bls12_381(a3); - let mut a1 = mul_fp2_bls12_381(a4, a5); - a1 = dbl_fp2_bls12_381(&a1); - a1 = mul_fp2_bls12_381(&a1, &a3_inv); + let a3_inv = inv_fp2_bls12_381( + a3, + #[cfg(feature = "hints")] + hints, + ); + let mut a1 = mul_fp2_bls12_381( + a4, + a5, + #[cfg(feature = "hints")] + hints, + ); + a1 = dbl_fp2_bls12_381( + &a1, + #[cfg(feature = "hints")] + hints, + ); + a1 = mul_fp2_bls12_381( + &a1, + &a3_inv, + #[cfg(feature = "hints")] + hints, + ); // a0 = (2·a1² - 3·a3·a4)(1+u) + 1 - let a3a4 = mul_fp2_bls12_381(a3, a4); - let mut a0 = square_fp2_bls12_381(&a1); - a0 = dbl_fp2_bls12_381(&a0); - a0 = sub_fp2_bls12_381(&a0, &scalar_mul_fp2_bls12_381(&a3a4, &[3, 0, 0, 0, 0, 0])); - a0 = mul_fp2_bls12_381(&a0, &EXT_U); - a0 = add_fp2_bls12_381(&a0, &[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let a3a4 = mul_fp2_bls12_381( + a3, + a4, + #[cfg(feature = "hints")] + hints, + ); + let mut a0 = square_fp2_bls12_381( + &a1, + #[cfg(feature = "hints")] + hints, + ); + a0 = dbl_fp2_bls12_381( + &a0, + #[cfg(feature = "hints")] + hints, + ); + a0 = sub_fp2_bls12_381( + &a0, + &scalar_mul_fp2_bls12_381( + &a3a4, + &[3, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + a0 = mul_fp2_bls12_381( + &a0, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + a0 = add_fp2_bls12_381( + &a0, + &[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); (a0, a1) } else { // a1 = (a5²·(1+u) + 3·a4² - 2·a3)/(4·a2) - let a2_inv = inv_fp2_bls12_381(&scalar_mul_fp2_bls12_381(a2, &[4, 0, 0, 0, 0, 0])); - let mut a4_sq = square_fp2_bls12_381(a4); - a4_sq = scalar_mul_fp2_bls12_381(&a4_sq, &[3, 0, 0, 0, 0, 0]); - let mut a1 = square_fp2_bls12_381(a5); - a1 = mul_fp2_bls12_381(&a1, &EXT_U); - a1 = add_fp2_bls12_381(&a1, &a4_sq); - a1 = sub_fp2_bls12_381(&a1, &dbl_fp2_bls12_381(a3)); - a1 = mul_fp2_bls12_381(&a1, &a2_inv); - + let a2_inv = inv_fp2_bls12_381( + &scalar_mul_fp2_bls12_381( + a2, + &[4, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + let mut a4_sq = square_fp2_bls12_381( + a4, + #[cfg(feature = "hints")] + hints, + ); + a4_sq = scalar_mul_fp2_bls12_381( + &a4_sq, + &[3, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + let mut a1 = square_fp2_bls12_381( + a5, + #[cfg(feature = "hints")] + hints, + ); + a1 = mul_fp2_bls12_381( + &a1, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + a1 = add_fp2_bls12_381( + &a1, + &a4_sq, + #[cfg(feature = "hints")] + hints, + ); + a1 = sub_fp2_bls12_381( + &a1, + &dbl_fp2_bls12_381( + a3, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + a1 = mul_fp2_bls12_381( + &a1, + &a2_inv, + #[cfg(feature = "hints")] + hints, + ); // a0 = (2·a1² + a2·a5 - 3·a3·a4)(1+u) + 1 - let a3a4 = mul_fp2_bls12_381(a3, a4); - let a2a5 = mul_fp2_bls12_381(a2, a5); - let mut a0 = square_fp2_bls12_381(&a1); - a0 = dbl_fp2_bls12_381(&a0); - a0 = add_fp2_bls12_381(&a0, &a2a5); - a0 = sub_fp2_bls12_381(&a0, &scalar_mul_fp2_bls12_381(&a3a4, &[3, 0, 0, 0, 0, 0])); - a0 = mul_fp2_bls12_381(&a0, &EXT_U); - a0 = add_fp2_bls12_381(&a0, &[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let a3a4 = mul_fp2_bls12_381( + a3, + a4, + #[cfg(feature = "hints")] + hints, + ); + let a2a5 = mul_fp2_bls12_381( + a2, + a5, + #[cfg(feature = "hints")] + hints, + ); + let mut a0 = square_fp2_bls12_381( + &a1, + #[cfg(feature = "hints")] + hints, + ); + a0 = dbl_fp2_bls12_381( + &a0, + #[cfg(feature = "hints")] + hints, + ); + a0 = add_fp2_bls12_381( + &a0, + &a2a5, + #[cfg(feature = "hints")] + hints, + ); + a0 = sub_fp2_bls12_381( + &a0, + &scalar_mul_fp2_bls12_381( + &a3a4, + &[3, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + a0 = mul_fp2_bls12_381( + &a0, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + a0 = add_fp2_bls12_381( + &a0, + &[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); (a0, a1) }; @@ -118,46 +260,180 @@ pub fn decompress_cyclo_bls12_381(a: &[u64; 48]) -> [u64; 72] { // - B45 = a4·a5 // /// **NOTE**: The output is not guaranteed to be in GΦ6(p²), if the input isn't. -pub fn square_cyclo_bls12_381(a: &[u64; 48]) -> [u64; 48] { +pub fn square_cyclo_bls12_381( + a: &[u64; 48], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { let a2: &[u64; 12] = &a[0..12].try_into().unwrap(); let a3: &[u64; 12] = &a[12..24].try_into().unwrap(); let a4: &[u64; 12] = &a[24..36].try_into().unwrap(); let a5: &[u64; 12] = &a[36..48].try_into().unwrap(); // B23 = a2·a3, B45 = a4·a5 - let b23 = mul_fp2_bls12_381(a2, a3); - let b45 = mul_fp2_bls12_381(a4, a5); + let b23 = mul_fp2_bls12_381( + a2, + a3, + #[cfg(feature = "hints")] + hints, + ); + let b45 = mul_fp2_bls12_381( + a4, + a5, + #[cfg(feature = "hints")] + hints, + ); // A23 = (a2 + a3)·(a2 + (1+u)·a3) - let a3xi = mul_fp2_bls12_381(a3, &EXT_U); - let a23 = mul_fp2_bls12_381(&add_fp2_bls12_381(a2, a3), &add_fp2_bls12_381(a2, &a3xi)); + let a3xi = mul_fp2_bls12_381( + a3, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + let a23 = mul_fp2_bls12_381( + &add_fp2_bls12_381( + a2, + a3, + #[cfg(feature = "hints")] + hints, + ), + &add_fp2_bls12_381( + a2, + &a3xi, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // A45 = (a4 + a5)·(a4 + (1+u)·a5) - let a5xi = mul_fp2_bls12_381(a5, &EXT_U); - let a45 = mul_fp2_bls12_381(&add_fp2_bls12_381(a4, a5), &add_fp2_bls12_381(a4, &a5xi)); + let a5xi = mul_fp2_bls12_381( + a5, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + let a45 = mul_fp2_bls12_381( + &add_fp2_bls12_381( + a4, + a5, + #[cfg(feature = "hints")] + hints, + ), + &add_fp2_bls12_381( + a4, + &a5xi, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // b2 = 2(a2 + 3·(1+u)·B45) - let mut b2 = mul_fp2_bls12_381(&b45, &EXT_U); - b2 = scalar_mul_fp2_bls12_381(&b2, &[3, 0, 0, 0, 0, 0]); - b2 = add_fp2_bls12_381(a2, &b2); - b2 = dbl_fp2_bls12_381(&b2); + let mut b2 = mul_fp2_bls12_381( + &b45, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + b2 = scalar_mul_fp2_bls12_381( + &b2, + &[3, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b2 = add_fp2_bls12_381( + a2, + &b2, + #[cfg(feature = "hints")] + hints, + ); + b2 = dbl_fp2_bls12_381( + &b2, + #[cfg(feature = "hints")] + hints, + ); // b3 = 3·(A45 - (2+u)·B45) - 2·a3 - let mut b3 = mul_fp2_bls12_381(&b45, &[2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]); - b3 = sub_fp2_bls12_381(&a45, &b3); - b3 = scalar_mul_fp2_bls12_381(&b3, &[3, 0, 0, 0, 0, 0]); - b3 = sub_fp2_bls12_381(&b3, &dbl_fp2_bls12_381(a3)); + let mut b3 = mul_fp2_bls12_381( + &b45, + &[2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b3 = sub_fp2_bls12_381( + &a45, + &b3, + #[cfg(feature = "hints")] + hints, + ); + b3 = scalar_mul_fp2_bls12_381( + &b3, + &[3, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b3 = sub_fp2_bls12_381( + &b3, + &dbl_fp2_bls12_381( + a3, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // b4 = 3·(A23 - (2+u)·B23) - 2·a4 - let mut b4 = mul_fp2_bls12_381(&b23, &[2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]); - b4 = sub_fp2_bls12_381(&a23, &b4); - b4 = scalar_mul_fp2_bls12_381(&b4, &[3, 0, 0, 0, 0, 0]); - b4 = sub_fp2_bls12_381(&b4, &dbl_fp2_bls12_381(a4)); + let mut b4 = mul_fp2_bls12_381( + &b23, + &[2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b4 = sub_fp2_bls12_381( + &a23, + &b4, + #[cfg(feature = "hints")] + hints, + ); + b4 = scalar_mul_fp2_bls12_381( + &b4, + &[3, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b4 = sub_fp2_bls12_381( + &b4, + &dbl_fp2_bls12_381( + a4, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // b5 = 2·(a5 + 3·B23) - let mut b5 = scalar_mul_fp2_bls12_381(&b23, &[3, 0, 0, 0, 0, 0]); - b5 = add_fp2_bls12_381(a5, &b5); - b5 = dbl_fp2_bls12_381(&b5); + let mut b5 = scalar_mul_fp2_bls12_381( + &b23, + &[3, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b5 = add_fp2_bls12_381( + a5, + &b5, + #[cfg(feature = "hints")] + hints, + ); + b5 = dbl_fp2_bls12_381( + &b5, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 48]; result[0..12].copy_from_slice(&b2); @@ -173,7 +449,11 @@ pub fn square_cyclo_bls12_381(a: &[u64; 48]) -> [u64; 48] { // out: a^x = (a0 + a4·v + a3·v²) + (a2 + a1·v + a5·v²)·w ∈ ∈ GΦ6(p²) // /// **NOTE**: The output is not guaranteed to be in GΦ6(p²), if the input isn't. -pub fn exp_cyclo_bls12_381(a: &[u64; 72], x: &[u8]) -> [u64; 72] { +pub fn exp_cyclo_bls12_381( + a: &[u64; 72], + x: &[u8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { if eq(a, &[0; 72]) { return [0; 72]; } @@ -190,19 +470,35 @@ pub fn exp_cyclo_bls12_381(a: &[u64; 72], x: &[u8]) -> [u64; 72] { for &bit in x.iter() { if bit == 1 { // decompress and multiply - let decomp = decompress_cyclo_bls12_381(&comp); - result = mul_fp12_bls12_381(&result, &decomp); + let decomp = decompress_cyclo_bls12_381( + &comp, + #[cfg(feature = "hints")] + hints, + ); + result = mul_fp12_bls12_381( + &result, + &decomp, + #[cfg(feature = "hints")] + hints, + ); } // We always square (in compressed form): C(c²) - comp = square_cyclo_bls12_381(&comp); + comp = square_cyclo_bls12_381( + &comp, + #[cfg(feature = "hints")] + hints, + ); } result } /// Exponentiation in the cyclotomic subgroup GΦ6(p²) by x = 15132376222941642752 -pub fn exp_by_x_cyclo_bls12_381(a: &[u64; 72]) -> [u64; 72] { +pub fn exp_by_x_cyclo_bls12_381( + a: &[u64; 72], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { /// Family parameter X const X_ABS_BIN_LE: [u8; 64] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -210,11 +506,19 @@ pub fn exp_by_x_cyclo_bls12_381(a: &[u64; 72]) -> [u64; 72] { 1, 0, 1, 1, ]; - exp_cyclo_bls12_381(a, &X_ABS_BIN_LE) + exp_cyclo_bls12_381( + a, + &X_ABS_BIN_LE, + #[cfg(feature = "hints")] + hints, + ) } /// Exponentiation in the cyclotomic subgroup GΦ6(p²) by x+1 = 15132376222941642753 -pub fn exp_by_xone_cyclo_bls12_381(a: &[u64; 72]) -> [u64; 72] { +pub fn exp_by_xone_cyclo_bls12_381( + a: &[u64; 72], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { /// Family parameter X+1 const XONE_ABS_BIN_LE: [u8; 64] = [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -222,11 +526,19 @@ pub fn exp_by_xone_cyclo_bls12_381(a: &[u64; 72]) -> [u64; 72] { 1, 0, 1, 1, ]; - exp_cyclo_bls12_381(a, &XONE_ABS_BIN_LE) + exp_cyclo_bls12_381( + a, + &XONE_ABS_BIN_LE, + #[cfg(feature = "hints")] + hints, + ) } /// Exponentiation in the cyclotomic subgroup GΦ6(p²) by (x+1)/3 = 5044125407647214251 -pub fn exp_by_xdiv3_cyclo_bls12_381(a: &[u64; 72]) -> [u64; 72] { +pub fn exp_by_xdiv3_cyclo_bls12_381( + a: &[u64; 72], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { /// Family parameter (X+1)/3 const XDIV3_ABS_BIN_LE: [u8; 63] = [ 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, @@ -234,5 +546,10 @@ pub fn exp_by_xdiv3_cyclo_bls12_381(a: &[u64; 72]) -> [u64; 72] { 0, 0, 1, ]; - exp_cyclo_bls12_381(a, &XDIV3_ABS_BIN_LE) + exp_cyclo_bls12_381( + a, + &XDIV3_ABS_BIN_LE, + #[cfg(feature = "hints")] + hints, + ) } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs index b05802935..ca859b09f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs @@ -16,46 +16,127 @@ use super::{ /// Given f ∈ Fp12*, computes f^((p¹²-1)/r) ∈ Fp12* /// /// Note: Unoptimized for the case f == 1 -pub fn final_exp_bls12_381(f: &[u64; 72]) -> [u64; 72] { +pub fn final_exp_bls12_381( + f: &[u64; 72], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { ////////////////// // The easy part: exp by (p^6-1)(p^2+1) ////////////////// // f^(p^6-1) = f̅·f⁻¹ - let f_conj = conjugate_fp12_bls12_381(f); - let f_inv = inv_fp12_bls12_381(f); - let easy1 = mul_fp12_bls12_381(&f_conj, &f_inv); + let f_conj = conjugate_fp12_bls12_381( + f, + #[cfg(feature = "hints")] + hints, + ); + let f_inv = inv_fp12_bls12_381( + f, + #[cfg(feature = "hints")] + hints, + ); + let easy1 = mul_fp12_bls12_381( + &f_conj, + &f_inv, + #[cfg(feature = "hints")] + hints, + ); // easy1^(p²-1) = easy1^p²·easy1 - let mut m = frobenius2_fp12_bls12_381(&easy1); - m = mul_fp12_bls12_381(&m, &easy1); + let mut m = frobenius2_fp12_bls12_381( + &easy1, + #[cfg(feature = "hints")] + hints, + ); + m = mul_fp12_bls12_381( + &m, + &easy1, + #[cfg(feature = "hints")] + hints, + ); ////////////////// // The hard part: exp by (p⁴-p²+1)/r ////////////////// // f = m^{(x+1)/3} - let mut f = exp_by_xdiv3_cyclo_bls12_381(&m); + let mut f = exp_by_xdiv3_cyclo_bls12_381( + &m, + #[cfg(feature = "hints")] + hints, + ); // f = f^(x+1) - f = exp_by_xone_cyclo_bls12_381(&f); + f = exp_by_xone_cyclo_bls12_381( + &f, + #[cfg(feature = "hints")] + hints, + ); // f1 = f^p, f2 = f̅^x - let f1 = frobenius1_fp12_bls12_381(&f); - let f2 = exp_by_x_cyclo_bls12_381(&conjugate_fp12_bls12_381(&f)); + let f1 = frobenius1_fp12_bls12_381( + &f, + #[cfg(feature = "hints")] + hints, + ); + let f2 = exp_by_x_cyclo_bls12_381( + &conjugate_fp12_bls12_381( + &f, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // f = f1*f2 - let f = mul_fp12_bls12_381(&f1, &f2); + let f = mul_fp12_bls12_381( + &f1, + &f2, + #[cfg(feature = "hints")] + hints, + ); // f1 = (f^x)^x, f2 = f^p², f3 = f̅ - let f1 = exp_by_x_cyclo_bls12_381(&exp_by_x_cyclo_bls12_381(&f)); - let f2 = frobenius2_fp12_bls12_381(&f); - let f3 = conjugate_fp12_bls12_381(&f); + let f1 = exp_by_x_cyclo_bls12_381( + &exp_by_x_cyclo_bls12_381( + &f, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + let f2 = frobenius2_fp12_bls12_381( + &f, + #[cfg(feature = "hints")] + hints, + ); + let f3 = conjugate_fp12_bls12_381( + &f, + #[cfg(feature = "hints")] + hints, + ); // f = f1*f2*f3*m - let mut f = mul_fp12_bls12_381(&f1, &f2); - f = mul_fp12_bls12_381(&f, &f3); - f = mul_fp12_bls12_381(&f, &m); + let mut f = mul_fp12_bls12_381( + &f1, + &f2, + #[cfg(feature = "hints")] + hints, + ); + f = mul_fp12_bls12_381( + &f, + &f3, + #[cfg(feature = "hints")] + hints, + ); + f = mul_fp12_bls12_381( + &f, + &m, + #[cfg(feature = "hints")] + hints, + ); f } @@ -64,8 +145,15 @@ pub fn final_exp_bls12_381(f: &[u64; 72]) -> [u64; 72] { /// - `f` must point to a valid `[u64; 72]` (576 bytes), used as both input and output. /// - Input must be a valid non-zero Fp12 element. #[no_mangle] -pub unsafe extern "C" fn final_exp_bls12_381_c(f: *mut u64) { +pub unsafe extern "C" fn final_exp_bls12_381_c( + f: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let f_arr: &[u64; 72] = &*(f as *const [u64; 72]); - let result = final_exp_bls12_381(f_arr); + let result = final_exp_bls12_381( + f_arr, + #[cfg(feature = "hints")] + hints, + ); std::ptr::copy_nonoverlapping(result.as_ptr(), f, 72); } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs index 1e956f989..90cddce35 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs @@ -9,7 +9,11 @@ use super::constants::{NQR_FP, P, P_MINUS_ONE}; /// Addition in Fp #[inline] -pub fn add_fp_bls12_381(x: &[u64; 6], y: &[u64; 6]) -> [u64; 6] { +pub fn add_fp_bls12_381( + x: &[u64; 6], + y: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 6] { // x·1 + y let mut params = SyscallArith384ModParams { a: x, @@ -18,13 +22,17 @@ pub fn add_fp_bls12_381(x: &[u64; 6], y: &[u64; 6]) -> [u64; 6] { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Doubling in Fp #[inline] -pub fn dbl_fp_bls12_381(x: &[u64; 6]) -> [u64; 6] { +pub fn dbl_fp_bls12_381(x: &[u64; 6], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 6] { // 2·x + 0 or x·1 + x let mut params = SyscallArith384ModParams { a: x, @@ -33,13 +41,21 @@ pub fn dbl_fp_bls12_381(x: &[u64; 6]) -> [u64; 6] { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Subtraction in Fp #[inline] -pub fn sub_fp_bls12_381(x: &[u64; 6], y: &[u64; 6]) -> [u64; 6] { +pub fn sub_fp_bls12_381( + x: &[u64; 6], + y: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 6] { // y·(-1) + x let mut params = SyscallArith384ModParams { a: y, @@ -48,13 +64,17 @@ pub fn sub_fp_bls12_381(x: &[u64; 6], y: &[u64; 6]) -> [u64; 6] { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Negation in Fp #[inline] -pub fn neg_fp_bls12_381(x: &[u64; 6]) -> [u64; 6] { +pub fn neg_fp_bls12_381(x: &[u64; 6], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 6] { // x·(-1) + 0 let mut params = SyscallArith384ModParams { a: x, @@ -63,13 +83,21 @@ pub fn neg_fp_bls12_381(x: &[u64; 6]) -> [u64; 6] { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Multiplication in Fp #[inline] -pub fn mul_fp_bls12_381(x: &[u64; 6], y: &[u64; 6]) -> [u64; 6] { +pub fn mul_fp_bls12_381( + x: &[u64; 6], + y: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 6] { // x·y + 0 let mut params = SyscallArith384ModParams { a: x, @@ -78,13 +106,20 @@ pub fn mul_fp_bls12_381(x: &[u64; 6], y: &[u64; 6]) -> [u64; 6] { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Squaring in Fp #[inline] -pub fn square_fp_bls12_381(x: &[u64; 6]) -> [u64; 6] { +pub fn square_fp_bls12_381( + x: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 6] { // x·x + 0 let mut params = SyscallArith384ModParams { a: x, @@ -93,15 +128,26 @@ pub fn square_fp_bls12_381(x: &[u64; 6]) -> [u64; 6] { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Square root in Fp #[inline] -pub fn sqrt_fp_bls12_381(x: &[u64; 6]) -> ([u64; 6], bool) { +pub fn sqrt_fp_bls12_381( + x: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> ([u64; 6], bool) { // Hint the sqrt - let hint = fcall_bls12_381_fp_sqrt(x); + let hint = fcall_bls12_381_fp_sqrt( + x, + #[cfg(feature = "hints")] + hints, + ); let is_qr = hint[0] == 1; let sqrt = hint[1..7].try_into().unwrap(); @@ -113,7 +159,11 @@ pub fn sqrt_fp_bls12_381(x: &[u64; 6]) -> ([u64; 6], bool) { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); if is_qr { // Check that sqrt * sqrt == x @@ -121,7 +171,12 @@ pub fn sqrt_fp_bls12_381(x: &[u64; 6]) -> ([u64; 6], bool) { (sqrt, true) } else { // Check that sqrt * sqrt == x * NQR - let nqr = mul_fp_bls12_381(x, &NQR_FP); + let nqr = mul_fp_bls12_381( + x, + &NQR_FP, + #[cfg(feature = "hints")] + hints, + ); assert_eq!(*params.d, nqr); (sqrt, false) } @@ -129,7 +184,7 @@ pub fn sqrt_fp_bls12_381(x: &[u64; 6]) -> ([u64; 6], bool) { /// Inversion of a non-zero element in Fp #[inline] -pub fn inv_fp_bls12_381(x: &[u64; 6]) -> [u64; 6] { +pub fn inv_fp_bls12_381(x: &[u64; 6], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 6] { // if x == 0, return 0 if eq(x, &[0; 6]) { return *x; @@ -139,7 +194,11 @@ pub fn inv_fp_bls12_381(x: &[u64; 6]) -> [u64; 6] { // Remember that an element y ∈ Fp is the inverse of x ∈ Fp if and only if x·y = 1 in Fp // We will therefore hint the inverse y and check the product with x is 1 - let inv = fcall_bls12_381_fp_inv(x); + let inv = fcall_bls12_381_fp_inv( + x, + #[cfg(feature = "hints")] + hints, + ); // x·y + 0 let mut params = SyscallArith384ModParams { @@ -149,7 +208,11 @@ pub fn inv_fp_bls12_381(x: &[u64; 6]) -> [u64; 6] { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); assert_eq!(*params.d, [1, 0, 0, 0, 0, 0]); inv @@ -161,7 +224,11 @@ pub fn inv_fp_bls12_381(x: &[u64; 6]) -> [u64; 6] { /// - `a` must point to a valid `[u64; 6]` (48 bytes). /// - `b` must point to a valid `[u64; 6]` (48 bytes). #[no_mangle] -pub unsafe extern "C" fn add_fp_bls12_381_c(a: *mut u64, b: *const u64) { +pub unsafe extern "C" fn add_fp_bls12_381_c( + a: *mut u64, + b: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 6]); let b_ref = &*(b as *const [u64; 6]); @@ -172,7 +239,11 @@ pub unsafe extern "C" fn add_fp_bls12_381_c(a: *mut u64, b: *const u64) { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); } @@ -180,7 +251,10 @@ pub unsafe extern "C" fn add_fp_bls12_381_c(a: *mut u64, b: *const u64) { /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. #[no_mangle] -pub unsafe extern "C" fn dbl_fp_bls12_381_c(a: *mut u64) { +pub unsafe extern "C" fn dbl_fp_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 6]); let mut params = SyscallArith384ModParams { @@ -190,7 +264,11 @@ pub unsafe extern "C" fn dbl_fp_bls12_381_c(a: *mut u64) { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); } @@ -199,7 +277,11 @@ pub unsafe extern "C" fn dbl_fp_bls12_381_c(a: *mut u64) { /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 6]` (48 bytes). #[no_mangle] -pub unsafe extern "C" fn sub_fp_bls12_381_c(a: *mut u64, b: *const u64) { +pub unsafe extern "C" fn sub_fp_bls12_381_c( + a: *mut u64, + b: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 6]); let b_ref = &*(b as *const [u64; 6]); @@ -210,7 +292,11 @@ pub unsafe extern "C" fn sub_fp_bls12_381_c(a: *mut u64, b: *const u64) { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); } @@ -218,7 +304,10 @@ pub unsafe extern "C" fn sub_fp_bls12_381_c(a: *mut u64, b: *const u64) { /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. #[no_mangle] -pub unsafe extern "C" fn neg_fp_bls12_381_c(a: *mut u64) { +pub unsafe extern "C" fn neg_fp_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 6]); let mut params = SyscallArith384ModParams { @@ -228,7 +317,11 @@ pub unsafe extern "C" fn neg_fp_bls12_381_c(a: *mut u64) { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); } @@ -237,7 +330,11 @@ pub unsafe extern "C" fn neg_fp_bls12_381_c(a: *mut u64) { /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 6]` (48 bytes). #[no_mangle] -pub unsafe extern "C" fn mul_fp_bls12_381_c(a: *mut u64, b: *const u64) { +pub unsafe extern "C" fn mul_fp_bls12_381_c( + a: *mut u64, + b: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 6]); let b_ref = &*(b as *const [u64; 6]); @@ -248,7 +345,11 @@ pub unsafe extern "C" fn mul_fp_bls12_381_c(a: *mut u64, b: *const u64) { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); } @@ -256,7 +357,10 @@ pub unsafe extern "C" fn mul_fp_bls12_381_c(a: *mut u64, b: *const u64) { /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. #[no_mangle] -pub unsafe extern "C" fn square_fp_bls12_381_c(a: *mut u64) { +pub unsafe extern "C" fn square_fp_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 6]); let mut params = SyscallArith384ModParams { @@ -266,7 +370,11 @@ pub unsafe extern "C" fn square_fp_bls12_381_c(a: *mut u64) { module: &P, d: &mut [0, 0, 0, 0, 0, 0], }; - syscall_arith384_mod(&mut params); + syscall_arith384_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); } @@ -275,9 +383,16 @@ pub unsafe extern "C" fn square_fp_bls12_381_c(a: *mut u64) { /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. /// - `is_qr` must point to a valid `u8`. #[no_mangle] -pub unsafe extern "C" fn sqrt_fp_bls12_381_c(a: *mut u64) -> bool { +pub unsafe extern "C" fn sqrt_fp_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let a_ref = &*(a as *const [u64; 6]); - let (result, qr) = sqrt_fp_bls12_381(a_ref); + let (result, qr) = sqrt_fp_bls12_381( + a_ref, + #[cfg(feature = "hints")] + hints, + ); *(a as *mut [u64; 6]) = result; qr } @@ -285,8 +400,15 @@ pub unsafe extern "C" fn sqrt_fp_bls12_381_c(a: *mut u64) -> bool { /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. #[no_mangle] -pub unsafe extern "C" fn inv_fp_bls12_381_c(a: *mut u64) { +pub unsafe extern "C" fn inv_fp_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 6]); - let result = inv_fp_bls12_381(a_ref); + let result = inv_fp_bls12_381( + a_ref, + #[cfg(feature = "hints")] + hints, + ); *(a as *mut [u64; 6]) = result; } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs index ed558e387..1b6caa673 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs @@ -23,26 +23,75 @@ use super::{ // - c1 = a1·b1 + a2·b2·v // - c2 = (a1+a2)·(b1+b2) - a1·b1 - a2·b2 #[inline] -pub fn mul_fp12_bls12_381(a: &[u64; 72], b: &[u64; 72]) -> [u64; 72] { +pub fn mul_fp12_bls12_381( + a: &[u64; 72], + b: &[u64; 72], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { let a1 = &a[0..36].try_into().unwrap(); let a2 = &a[36..72].try_into().unwrap(); let b1 = &b[0..36].try_into().unwrap(); let b2 = &b[36..72].try_into().unwrap(); // a1·b1, a2·b2 - let a1_b1 = mul_fp6_bls12_381(a1, b1); - let a2_b2 = mul_fp6_bls12_381(a2, b2); + let a1_b1 = mul_fp6_bls12_381( + a1, + b1, + #[cfg(feature = "hints")] + hints, + ); + let a2_b2 = mul_fp6_bls12_381( + a2, + b2, + #[cfg(feature = "hints")] + hints, + ); // c1 = a1·b1 + a2·b2·v - let mut c1 = sparse_mula_fp6_bls12_381(&a2_b2, &EXT_V); - c1 = add_fp6_bls12_381(&c1, &a1_b1); + let mut c1 = sparse_mula_fp6_bls12_381( + &a2_b2, + &EXT_V, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp6_bls12_381( + &c1, + &a1_b1, + #[cfg(feature = "hints")] + hints, + ); // c2 = (a1+a2)·(b1+b2) - a1·b1 - a2·b2 - let a1_plus_a2 = add_fp6_bls12_381(a1, a2); - let b1_plus_b2 = add_fp6_bls12_381(b1, b2); - let mut c2 = mul_fp6_bls12_381(&a1_plus_a2, &b1_plus_b2); - c2 = sub_fp6_bls12_381(&c2, &a1_b1); - c2 = sub_fp6_bls12_381(&c2, &a2_b2); + let a1_plus_a2 = add_fp6_bls12_381( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ); + let b1_plus_b2 = add_fp6_bls12_381( + b1, + b2, + #[cfg(feature = "hints")] + hints, + ); + let mut c2 = mul_fp6_bls12_381( + &a1_plus_a2, + &b1_plus_b2, + #[cfg(feature = "hints")] + hints, + ); + c2 = sub_fp6_bls12_381( + &c2, + &a1_b1, + #[cfg(feature = "hints")] + hints, + ); + c2 = sub_fp6_bls12_381( + &c2, + &a2_b2, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 72]; result[0..36].copy_from_slice(&c1); @@ -57,26 +106,55 @@ pub fn mul_fp12_bls12_381(a: &[u64; 72], b: &[u64; 72]) -> [u64; 72] { // - c1 = a1 + a2·(b23·(1+u) + b22·v²) // - c2 = a2 + a1·(b22·v + b23·v²) #[inline] -pub fn sparse_mul_fp12_bls12_381(a: &[u64; 72], b: &[u64; 24]) -> [u64; 72] { +pub fn sparse_mul_fp12_bls12_381( + a: &[u64; 72], + b: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { let a1 = &a[0..36].try_into().unwrap(); let a2 = &a[36..72].try_into().unwrap(); let b22: &[u64; 12] = &b[0..12].try_into().unwrap(); let b23 = &b[12..24].try_into().unwrap(); // c1 = a1 + a2·(b23·(1+u) + b22·v²) - let b23u = mul_fp2_bls12_381(&EXT_U, b23); + let b23u = mul_fp2_bls12_381( + &EXT_U, + b23, + #[cfg(feature = "hints")] + hints, + ); let mut sparse_c1 = [0u64; 24]; sparse_c1[0..12].copy_from_slice(&b23u); sparse_c1[12..24].copy_from_slice(b22); - let mut c1 = sparse_mulc_fp6_bls12_381(a2, &sparse_c1); - c1 = add_fp6_bls12_381(&c1, a1); + let mut c1 = sparse_mulc_fp6_bls12_381( + a2, + &sparse_c1, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp6_bls12_381( + &c1, + a1, + #[cfg(feature = "hints")] + hints, + ); // c2 = a2 + a1·(b22·v + b23·v²) let mut sparse_c2 = [0u64; 24]; sparse_c2[0..12].copy_from_slice(b22); sparse_c2[12..24].copy_from_slice(b23); - let mut c2 = sparse_mulb_fp6_bls12_381(a1, &sparse_c2); - c2 = add_fp6_bls12_381(&c2, a2); + let mut c2 = sparse_mulb_fp6_bls12_381( + a1, + &sparse_c2, + #[cfg(feature = "hints")] + hints, + ); + c2 = add_fp6_bls12_381( + &c2, + a2, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 72]; result[0..36].copy_from_slice(&c1); @@ -91,24 +169,71 @@ pub fn sparse_mul_fp12_bls12_381(a: &[u64; 72], b: &[u64; 24]) -> [u64; 72] { // - c1 = (a1-a2)·(a1-a2·v) + a1·a2 + a1·a2·v // - c2 = 2·a1·a2 #[inline] -pub fn square_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { +pub fn square_fp12_bls12_381( + a: &[u64; 72], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { let a1 = &a[0..36].try_into().unwrap(); let a2 = &a[36..72].try_into().unwrap(); // a1·a2, a2·v, a1·a2·v - let a1_a2 = mul_fp6_bls12_381(a1, a2); - let a2_v = sparse_mula_fp6_bls12_381(a2, &EXT_V); - let a1_a2_v = sparse_mula_fp6_bls12_381(&a1_a2, &EXT_V); + let a1_a2 = mul_fp6_bls12_381( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ); + let a2_v = sparse_mula_fp6_bls12_381( + a2, + &EXT_V, + #[cfg(feature = "hints")] + hints, + ); + let a1_a2_v = sparse_mula_fp6_bls12_381( + &a1_a2, + &EXT_V, + #[cfg(feature = "hints")] + hints, + ); // c2 = 2·a1·a2 - let c2 = dbl_fp6_bls12_381(&a1_a2); + let c2 = dbl_fp6_bls12_381( + &a1_a2, + #[cfg(feature = "hints")] + hints, + ); // c1 = (a1-a2)·(a1-a2·v) + a1·a2 + a1·a2·v - let a1_minus_a2 = sub_fp6_bls12_381(a1, a2); - let a1_minus_a2v = sub_fp6_bls12_381(a1, &a2_v); - let mut c1 = mul_fp6_bls12_381(&a1_minus_a2, &a1_minus_a2v); - c1 = add_fp6_bls12_381(&c1, &a1_a2); - c1 = add_fp6_bls12_381(&c1, &a1_a2_v); + let a1_minus_a2 = sub_fp6_bls12_381( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ); + let a1_minus_a2v = sub_fp6_bls12_381( + a1, + &a2_v, + #[cfg(feature = "hints")] + hints, + ); + let mut c1 = mul_fp6_bls12_381( + &a1_minus_a2, + &a1_minus_a2v, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp6_bls12_381( + &c1, + &a1_a2, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp6_bls12_381( + &c1, + &a1_a2_v, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 72]; result[0..36].copy_from_slice(&c1); @@ -123,22 +248,61 @@ pub fn square_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { // - c1 = a1·(a1² - a2²·v)⁻¹ // - c2 = -a2·(a1² - a2²·v)⁻¹ #[inline] -pub fn inv_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { +pub fn inv_fp12_bls12_381( + a: &[u64; 72], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { let a1 = &a[0..36].try_into().unwrap(); let a2 = &a[36..72].try_into().unwrap(); // a1², a2², a2²·v - let a1_square = square_fp6_bls12_381(a1); - let a2_square = square_fp6_bls12_381(a2); - let a2_square_v = sparse_mula_fp6_bls12_381(&a2_square, &EXT_V); + let a1_square = square_fp6_bls12_381( + a1, + #[cfg(feature = "hints")] + hints, + ); + let a2_square = square_fp6_bls12_381( + a2, + #[cfg(feature = "hints")] + hints, + ); + let a2_square_v = sparse_mula_fp6_bls12_381( + &a2_square, + &EXT_V, + #[cfg(feature = "hints")] + hints, + ); // (a1² - a2²·v)⁻¹ - let mut denom = sub_fp6_bls12_381(&a1_square, &a2_square_v); - denom = inv_fp6_bls12_381(&denom); + let mut denom = sub_fp6_bls12_381( + &a1_square, + &a2_square_v, + #[cfg(feature = "hints")] + hints, + ); + denom = inv_fp6_bls12_381( + &denom, + #[cfg(feature = "hints")] + hints, + ); // c1 = a1·(a1² - a2²·v)⁻¹, c2 = -a2·(a1² - a2²·v)⁻¹ - let c1 = mul_fp6_bls12_381(a1, &denom); - let c2 = neg_fp6_bls12_381(&mul_fp6_bls12_381(a2, &denom)); + let c1 = mul_fp6_bls12_381( + a1, + &denom, + #[cfg(feature = "hints")] + hints, + ); + let c2 = neg_fp6_bls12_381( + &mul_fp6_bls12_381( + a2, + &denom, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 72]; result[0..36].copy_from_slice(&c1); @@ -148,10 +312,17 @@ pub fn inv_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { /// Conjugation in Fp12 #[inline] -pub fn conjugate_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { +pub fn conjugate_fp12_bls12_381( + a: &[u64; 72], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { let mut result = [0; 72]; result[0..36].copy_from_slice(&a[0..36]); - result[36..72].copy_from_slice(&neg_fp6_bls12_381(&a[36..72].try_into().unwrap())); + result[36..72].copy_from_slice(&neg_fp6_bls12_381( + &a[36..72].try_into().unwrap(), + #[cfg(feature = "hints")] + hints, + )); result } @@ -162,7 +333,10 @@ pub fn conjugate_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { // - c1 = a̅11 + a̅12·γ12·v + a̅13·γ14·v² // - c2 = a̅21·γ11 + a̅22·γ13·v + a̅23·γ15·v² #[inline] -pub fn frobenius1_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { +pub fn frobenius1_fp12_bls12_381( + a: &[u64; 72], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { let a11 = &a[0..12].try_into().unwrap(); let a12 = &a[12..24].try_into().unwrap(); let a13 = &a[24..36].try_into().unwrap(); @@ -173,19 +347,68 @@ pub fn frobenius1_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { let mut result = [0; 72]; // c1 = a̅11 + a̅12·γ12·v + a̅13·γ14·v² - result[0..12].copy_from_slice(&conjugate_fp2_bls12_381(a11)); - let mut tmp = conjugate_fp2_bls12_381(a12); - result[12..24].copy_from_slice(&mul_fp2_bls12_381(&tmp, &FROBENIUS_GAMMA12)); - tmp = conjugate_fp2_bls12_381(a13); - result[24..36].copy_from_slice(&scalar_mul_fp2_bls12_381(&tmp, &FROBENIUS_GAMMA14)); + result[0..12].copy_from_slice(&conjugate_fp2_bls12_381( + a11, + #[cfg(feature = "hints")] + hints, + )); + let mut tmp = conjugate_fp2_bls12_381( + a12, + #[cfg(feature = "hints")] + hints, + ); + result[12..24].copy_from_slice(&mul_fp2_bls12_381( + &tmp, + &FROBENIUS_GAMMA12, + #[cfg(feature = "hints")] + hints, + )); + tmp = conjugate_fp2_bls12_381( + a13, + #[cfg(feature = "hints")] + hints, + ); + result[24..36].copy_from_slice(&scalar_mul_fp2_bls12_381( + &tmp, + &FROBENIUS_GAMMA14, + #[cfg(feature = "hints")] + hints, + )); // c2 = a̅21·γ11 + a̅22·γ13·v + a̅23·γ15·v² - tmp = conjugate_fp2_bls12_381(a21); - result[36..48].copy_from_slice(&mul_fp2_bls12_381(&tmp, &FROBENIUS_GAMMA11)); - tmp = conjugate_fp2_bls12_381(a22); - result[48..60].copy_from_slice(&mul_fp2_bls12_381(&tmp, &FROBENIUS_GAMMA13)); - tmp = conjugate_fp2_bls12_381(a23); - result[60..72].copy_from_slice(&mul_fp2_bls12_381(&tmp, &FROBENIUS_GAMMA15)); + tmp = conjugate_fp2_bls12_381( + a21, + #[cfg(feature = "hints")] + hints, + ); + result[36..48].copy_from_slice(&mul_fp2_bls12_381( + &tmp, + &FROBENIUS_GAMMA11, + #[cfg(feature = "hints")] + hints, + )); + tmp = conjugate_fp2_bls12_381( + a22, + #[cfg(feature = "hints")] + hints, + ); + result[48..60].copy_from_slice(&mul_fp2_bls12_381( + &tmp, + &FROBENIUS_GAMMA13, + #[cfg(feature = "hints")] + hints, + )); + tmp = conjugate_fp2_bls12_381( + a23, + #[cfg(feature = "hints")] + hints, + ); + result[60..72].copy_from_slice(&mul_fp2_bls12_381( + &tmp, + &FROBENIUS_GAMMA15, + #[cfg(feature = "hints")] + hints, + )); result } @@ -197,7 +420,10 @@ pub fn frobenius1_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { // - c1 = a11 + a12·γ22·v + a13·γ24·v² // - c2 = a21·γ21 + a22·γ23·v + a23·γ25·v² #[inline] -pub fn frobenius2_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { +pub fn frobenius2_fp12_bls12_381( + a: &[u64; 72], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { let a11: &[u64; 12] = &a[0..12].try_into().unwrap(); let a12 = &a[12..24].try_into().unwrap(); let a13 = &a[24..36].try_into().unwrap(); @@ -209,13 +435,38 @@ pub fn frobenius2_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { // c1 = a11 + a12·γ22·v + a13·γ24·v² result[0..12].copy_from_slice(a11); - result[12..24].copy_from_slice(&scalar_mul_fp2_bls12_381(a12, &FROBENIUS_GAMMA22)); - result[24..36].copy_from_slice(&scalar_mul_fp2_bls12_381(a13, &FROBENIUS_GAMMA24)); + result[12..24].copy_from_slice(&scalar_mul_fp2_bls12_381( + a12, + &FROBENIUS_GAMMA22, + #[cfg(feature = "hints")] + hints, + )); + result[24..36].copy_from_slice(&scalar_mul_fp2_bls12_381( + a13, + &FROBENIUS_GAMMA24, + #[cfg(feature = "hints")] + hints, + )); // c2 = a21·γ21 + a22·γ23·v + a23·γ25·v² - result[36..48].copy_from_slice(&scalar_mul_fp2_bls12_381(a21, &FROBENIUS_GAMMA21)); - result[48..60].copy_from_slice(&scalar_mul_fp2_bls12_381(a22, &FROBENIUS_GAMMA23)); - result[60..72].copy_from_slice(&scalar_mul_fp2_bls12_381(a23, &FROBENIUS_GAMMA25)); + result[36..48].copy_from_slice(&scalar_mul_fp2_bls12_381( + a21, + &FROBENIUS_GAMMA21, + #[cfg(feature = "hints")] + hints, + )); + result[48..60].copy_from_slice(&scalar_mul_fp2_bls12_381( + a22, + &FROBENIUS_GAMMA23, + #[cfg(feature = "hints")] + hints, + )); + result[60..72].copy_from_slice(&scalar_mul_fp2_bls12_381( + a23, + &FROBENIUS_GAMMA25, + #[cfg(feature = "hints")] + hints, + )); result } @@ -225,7 +476,11 @@ pub fn frobenius2_fp12_bls12_381(a: &[u64; 72]) -> [u64; 72] { // in: e, (a1 + a2·w) ∈ Fp12, where e ∈ [0,p¹²-2] ai ∈ Fp6 // out: (c1 + c2·w) = (a1 + a2·w)^e ∈ Fp12 #[inline] -pub fn exp_fp12_bls12_381(e: u64, a: &[u64; 72]) -> [u64; 72] { +pub fn exp_fp12_bls12_381( + e: u64, + a: &[u64; 72], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { let one = { let mut tmp = [0; 72]; tmp[0] = 1; @@ -244,7 +499,12 @@ pub fn exp_fp12_bls12_381(e: u64, a: &[u64; 72]) -> [u64; 72] { return *a; } - let (_, max_bit) = fcall_msb_pos_384(&[e, 0, 0, 0, 0, 0], &[0, 0, 0, 0, 0, 0]); + let (_, max_bit) = fcall_msb_pos_384( + &[e, 0, 0, 0, 0, 0], + &[0, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); // Perform the loop, based on the binary representation of e @@ -260,12 +520,21 @@ pub fn exp_fp12_bls12_381(e: u64, a: &[u64; 72]) -> [u64; 72] { let _max_bit = max_bit as usize; for i in (0.._max_bit).rev() { // Always square - result = square_fp12_bls12_381(&result); + result = square_fp12_bls12_381( + &result, + #[cfg(feature = "hints")] + hints, + ); // Get the next bit b of e // If b == 1, we should multiply it by a, otherwise start the next iteration if ((e >> i) & 1) == 1 { - result = mul_fp12_bls12_381(&result, a); + result = mul_fp12_bls12_381( + &result, + a, + #[cfg(feature = "hints")] + hints, + ); // Reconstruct e e_rec |= 1 << i; @@ -282,11 +551,21 @@ pub fn exp_fp12_bls12_381(e: u64, a: &[u64; 72]) -> [u64; 72] { /// - `ret` must point to a valid `[u64; 72]` for the output. /// - `a` and `b` must point to valid `[u64; 72]` Fp12 elements. #[no_mangle] -pub unsafe extern "C" fn mul_fp12_bls12_381_c(ret: *mut u64, a: *const u64, b: *const u64) { +pub unsafe extern "C" fn mul_fp12_bls12_381_c( + ret: *mut u64, + a: *const u64, + b: *const u64, + #[cfg(feature = "hints")] hints: *mut Vec, +) { let a_arr: &[u64; 72] = &*(a as *const [u64; 72]); let b_arr: &[u64; 72] = &*(b as *const [u64; 72]); - let result = mul_fp12_bls12_381(a_arr, b_arr); + let result = mul_fp12_bls12_381( + a_arr, + b_arr, + #[cfg(feature = "hints")] + &mut *hints, + ); let ret_arr: &mut [u64; 72] = &mut *(ret as *mut [u64; 72]); *ret_arr = result; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs index c25d8f887..97be06bea 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs @@ -33,86 +33,151 @@ fn from_syscall_complex(complex: &SyscallComplex384) -> [u64; 12] { /// Addition in Fp2 #[inline] -pub fn add_fp2_bls12_381(a: &[u64; 12], b: &[u64; 12]) -> [u64; 12] { +pub fn add_fp2_bls12_381( + a: &[u64; 12], + b: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let mut f1 = to_syscall_complex(a); let f2 = to_syscall_complex(b); let mut params = SyscallBls12_381ComplexAddParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_add(&mut params); + syscall_bls12_381_complex_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); from_syscall_complex(&f1) } /// Doubling in Fp2 #[inline] -pub fn dbl_fp2_bls12_381(a: &[u64; 12]) -> [u64; 12] { +pub fn dbl_fp2_bls12_381( + a: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let mut f1 = to_syscall_complex(a); let f2 = to_syscall_complex(a); let mut params = SyscallBls12_381ComplexAddParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_add(&mut params); + syscall_bls12_381_complex_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); from_syscall_complex(&f1) } /// Negation in Fp2 #[inline] -pub fn neg_fp2_bls12_381(a: &[u64; 12]) -> [u64; 12] { +pub fn neg_fp2_bls12_381( + a: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let mut f1 = to_syscall_complex(a); let f2 = to_syscall_complex_x(&P_MINUS_ONE); let mut params = SyscallBls12_381ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_mul(&mut params); + syscall_bls12_381_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); from_syscall_complex(&f1) } /// Subtraction in Fp2 #[inline] -pub fn sub_fp2_bls12_381(a: &[u64; 12], b: &[u64; 12]) -> [u64; 12] { +pub fn sub_fp2_bls12_381( + a: &[u64; 12], + b: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let mut f1 = to_syscall_complex(a); let f2 = to_syscall_complex(b); let mut params = SyscallBls12_381ComplexSubParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_sub(&mut params); + syscall_bls12_381_complex_sub( + &mut params, + #[cfg(feature = "hints")] + hints, + ); from_syscall_complex(&f1) } /// Multiplication in Fp2 #[inline] -pub fn mul_fp2_bls12_381(a: &[u64; 12], b: &[u64; 12]) -> [u64; 12] { +pub fn mul_fp2_bls12_381( + a: &[u64; 12], + b: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let mut f1 = to_syscall_complex(a); let f2 = to_syscall_complex(b); let mut params = SyscallBls12_381ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_mul(&mut params); + syscall_bls12_381_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); from_syscall_complex(&f1) } /// Scalar multiplication in Fp2 #[inline] -pub fn scalar_mul_fp2_bls12_381(a: &[u64; 12], b: &[u64; 6]) -> [u64; 12] { +pub fn scalar_mul_fp2_bls12_381( + a: &[u64; 12], + b: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let mut f1 = SyscallComplex384 { x: a[0..6].try_into().unwrap(), y: a[6..12].try_into().unwrap() }; let f2 = SyscallComplex384 { x: b[0..6].try_into().unwrap(), y: [0, 0, 0, 0, 0, 0] }; let mut params = SyscallBls12_381ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_mul(&mut params); + syscall_bls12_381_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); from_syscall_complex(&f1) } /// Squaring in Fp2 #[inline] -pub fn square_fp2_bls12_381(a: &[u64; 12]) -> [u64; 12] { +pub fn square_fp2_bls12_381( + a: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let mut f1 = to_syscall_complex(a); let f2 = to_syscall_complex(a); let mut params = SyscallBls12_381ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_mul(&mut params); + syscall_bls12_381_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); from_syscall_complex(&f1) } /// Square root in Fp2 #[inline] -pub fn sqrt_fp2_bls12_381(x: &[u64; 12]) -> ([u64; 12], bool) { +pub fn sqrt_fp2_bls12_381( + x: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> ([u64; 12], bool) { // Hint the sqrt - let hint = fcall_bls12_381_fp2_sqrt(x); + let hint = fcall_bls12_381_fp2_sqrt( + x, + #[cfg(feature = "hints")] + hints, + ); let is_qr = hint[0] == 1; let sqrt = hint[1..13].try_into().unwrap(); // Compute sqrt * sqrt - let mul = mul_fp2_bls12_381(&sqrt, &sqrt); + let mul = mul_fp2_bls12_381( + &sqrt, + &sqrt, + #[cfg(feature = "hints")] + hints, + ); if is_qr { // Check that sqrt * sqrt == x @@ -120,7 +185,12 @@ pub fn sqrt_fp2_bls12_381(x: &[u64; 12]) -> ([u64; 12], bool) { (sqrt, true) } else { // Check that sqrt * sqrt == x * NQR - let nqr = mul_fp2_bls12_381(x, &NQR_FP2); + let nqr = mul_fp2_bls12_381( + x, + &NQR_FP2, + #[cfg(feature = "hints")] + hints, + ); assert!(eq(&mul, &nqr)); (sqrt, false) } @@ -128,7 +198,10 @@ pub fn sqrt_fp2_bls12_381(x: &[u64; 12]) -> ([u64; 12], bool) { /// Inversion in Fp2: returns a⁻¹ #[inline] -pub fn inv_fp2_bls12_381(a: &[u64; 12]) -> [u64; 12] { +pub fn inv_fp2_bls12_381( + a: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { // if a == 0, return 0 if eq(a, &[0; 12]) { return *a; @@ -138,9 +211,18 @@ pub fn inv_fp2_bls12_381(a: &[u64; 12]) -> [u64; 12] { // Remember that an element b ∈ Fp2 is the inverse of a ∈ Fp2 if and only if a·b = 1 in Fp2 // We will therefore hint the inverse b and check the product with a is 1 - let inv = fcall_bls12_381_fp2_inv(a); - - let product = mul_fp2_bls12_381(a, &inv); + let inv = fcall_bls12_381_fp2_inv( + a, + #[cfg(feature = "hints")] + hints, + ); + + let product = mul_fp2_bls12_381( + a, + &inv, + #[cfg(feature = "hints")] + hints, + ); assert_eq!(&product[0..6], &[1, 0, 0, 0, 0, 0]); assert_eq!(&product[6..12], &[0, 0, 0, 0, 0, 0]); @@ -149,12 +231,19 @@ pub fn inv_fp2_bls12_381(a: &[u64; 12]) -> [u64; 12] { /// Conjugation in Fp2 #[inline] -pub fn conjugate_fp2_bls12_381(a: &[u64; 12]) -> [u64; 12] { +pub fn conjugate_fp2_bls12_381( + a: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { let mut f1 = SyscallComplex384 { x: a[0..6].try_into().unwrap(), y: [0, 0, 0, 0, 0, 0] }; let f2 = SyscallComplex384 { x: [0, 0, 0, 0, 0, 0], y: a[6..12].try_into().unwrap() }; let mut params = SyscallBls12_381ComplexSubParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_sub(&mut params); + syscall_bls12_381_complex_sub( + &mut params, + #[cfg(feature = "hints")] + hints, + ); from_syscall_complex(&f1) } @@ -164,13 +253,21 @@ pub fn conjugate_fp2_bls12_381(a: &[u64; 12]) -> [u64; 12] { /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 12]` (96 bytes). #[no_mangle] -pub unsafe extern "C" fn add_fp2_bls12_381_c(a: *mut u64, b: *const u64) { +pub unsafe extern "C" fn add_fp2_bls12_381_c( + a: *mut u64, + b: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let mut f1 = SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; let f2 = SyscallComplex384 { x: *(b as *const [u64; 6]), y: *(b.add(6) as *const [u64; 6]) }; let mut params = SyscallBls12_381ComplexAddParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_add(&mut params); + syscall_bls12_381_complex_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *(a as *mut [u64; 6]) = f1.x; *(a.add(6) as *mut [u64; 6]) = f1.y; @@ -179,13 +276,20 @@ pub unsafe extern "C" fn add_fp2_bls12_381_c(a: *mut u64, b: *const u64) { /// # Safety /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. #[no_mangle] -pub unsafe extern "C" fn dbl_fp2_bls12_381_c(a: *mut u64) { +pub unsafe extern "C" fn dbl_fp2_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let mut f1 = SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; let f2 = SyscallComplex384 { x: f1.x, y: f1.y }; let mut params = SyscallBls12_381ComplexAddParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_add(&mut params); + syscall_bls12_381_complex_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *(a as *mut [u64; 6]) = f1.x; *(a.add(6) as *mut [u64; 6]) = f1.y; @@ -194,13 +298,20 @@ pub unsafe extern "C" fn dbl_fp2_bls12_381_c(a: *mut u64) { /// # Safety /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. #[no_mangle] -pub unsafe extern "C" fn neg_fp2_bls12_381_c(a: *mut u64) { +pub unsafe extern "C" fn neg_fp2_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let mut f1 = SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; let f2 = SyscallComplex384 { x: P_MINUS_ONE, y: [0, 0, 0, 0, 0, 0] }; let mut params = SyscallBls12_381ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_mul(&mut params); + syscall_bls12_381_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *(a as *mut [u64; 6]) = f1.x; *(a.add(6) as *mut [u64; 6]) = f1.y; @@ -210,13 +321,21 @@ pub unsafe extern "C" fn neg_fp2_bls12_381_c(a: *mut u64) { /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 12]` (96 bytes). #[no_mangle] -pub unsafe extern "C" fn sub_fp2_bls12_381_c(a: *mut u64, b: *const u64) { +pub unsafe extern "C" fn sub_fp2_bls12_381_c( + a: *mut u64, + b: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let mut f1 = SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; let f2 = SyscallComplex384 { x: *(b as *const [u64; 6]), y: *(b.add(6) as *const [u64; 6]) }; let mut params = SyscallBls12_381ComplexSubParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_sub(&mut params); + syscall_bls12_381_complex_sub( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *(a as *mut [u64; 6]) = f1.x; *(a.add(6) as *mut [u64; 6]) = f1.y; @@ -226,13 +345,21 @@ pub unsafe extern "C" fn sub_fp2_bls12_381_c(a: *mut u64, b: *const u64) { /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 12]` (96 bytes). #[no_mangle] -pub unsafe extern "C" fn mul_fp2_bls12_381_c(a: *mut u64, b: *const u64) { +pub unsafe extern "C" fn mul_fp2_bls12_381_c( + a: *mut u64, + b: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let mut f1 = SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; let f2 = SyscallComplex384 { x: *(b as *const [u64; 6]), y: *(b.add(6) as *const [u64; 6]) }; let mut params = SyscallBls12_381ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_mul(&mut params); + syscall_bls12_381_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *(a as *mut [u64; 6]) = f1.x; *(a.add(6) as *mut [u64; 6]) = f1.y; @@ -241,13 +368,20 @@ pub unsafe extern "C" fn mul_fp2_bls12_381_c(a: *mut u64, b: *const u64) { /// # Safety /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. #[no_mangle] -pub unsafe extern "C" fn square_fp2_bls12_381_c(a: *mut u64) { +pub unsafe extern "C" fn square_fp2_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let mut f1 = SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; let f2 = SyscallComplex384 { x: f1.x, y: f1.y }; let mut params = SyscallBls12_381ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_mul(&mut params); + syscall_bls12_381_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *(a as *mut [u64; 6]) = f1.x; *(a.add(6) as *mut [u64; 6]) = f1.y; @@ -257,8 +391,15 @@ pub unsafe extern "C" fn square_fp2_bls12_381_c(a: *mut u64) { /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - Element must be non-zero. #[no_mangle] -pub unsafe extern "C" fn inv_fp2_bls12_381_c(a: *mut u64) { +pub unsafe extern "C" fn inv_fp2_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 12]); - let result = inv_fp2_bls12_381(a_ref); + let result = inv_fp2_bls12_381( + a_ref, + #[cfg(feature = "hints")] + hints, + ); *(a as *mut [u64; 12]) = result; } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp6.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp6.rs index 31c3d4ed1..742a5df18 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp6.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp6.rs @@ -10,12 +10,21 @@ use super::{ /// Addition in Fp6 #[inline] -pub fn add_fp6_bls12_381(a: &[u64; 36], b: &[u64; 36]) -> [u64; 36] { +pub fn add_fp6_bls12_381( + a: &[u64; 36], + b: &[u64; 36], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 36] { let mut result = [0; 36]; for i in 0..3 { let a_i = &a[i * 12..(i + 1) * 12].try_into().unwrap(); let b_i = &b[i * 12..(i + 1) * 12].try_into().unwrap(); - let c_i = add_fp2_bls12_381(a_i, b_i); + let c_i = add_fp2_bls12_381( + a_i, + b_i, + #[cfg(feature = "hints")] + hints, + ); result[i * 12..(i + 1) * 12].copy_from_slice(&c_i); } result @@ -23,11 +32,18 @@ pub fn add_fp6_bls12_381(a: &[u64; 36], b: &[u64; 36]) -> [u64; 36] { /// Doubling in Fp6 #[inline] -pub fn dbl_fp6_bls12_381(a: &[u64; 36]) -> [u64; 36] { +pub fn dbl_fp6_bls12_381( + a: &[u64; 36], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 36] { let mut result = [0; 36]; for i in 0..3 { let a_i = &a[i * 12..(i + 1) * 12].try_into().unwrap(); - let c_i = dbl_fp2_bls12_381(a_i); + let c_i = dbl_fp2_bls12_381( + a_i, + #[cfg(feature = "hints")] + hints, + ); result[i * 12..(i + 1) * 12].copy_from_slice(&c_i); } result @@ -35,11 +51,18 @@ pub fn dbl_fp6_bls12_381(a: &[u64; 36]) -> [u64; 36] { /// Negation in Fp6 #[inline] -pub fn neg_fp6_bls12_381(a: &[u64; 36]) -> [u64; 36] { +pub fn neg_fp6_bls12_381( + a: &[u64; 36], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 36] { let mut result = [0; 36]; for i in 0..3 { let a_i = &a[i * 12..(i + 1) * 12].try_into().unwrap(); - let c_i = neg_fp2_bls12_381(a_i); + let c_i = neg_fp2_bls12_381( + a_i, + #[cfg(feature = "hints")] + hints, + ); result[i * 12..(i + 1) * 12].copy_from_slice(&c_i); } result @@ -47,12 +70,21 @@ pub fn neg_fp6_bls12_381(a: &[u64; 36]) -> [u64; 36] { /// Subtraction in Fp6 #[inline] -pub fn sub_fp6_bls12_381(a: &[u64; 36], b: &[u64; 36]) -> [u64; 36] { +pub fn sub_fp6_bls12_381( + a: &[u64; 36], + b: &[u64; 36], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 36] { let mut result = [0; 36]; for i in 0..3 { let a_i = &a[i * 12..(i + 1) * 12].try_into().unwrap(); let b_i = &b[i * 12..(i + 1) * 12].try_into().unwrap(); - let c_i = sub_fp2_bls12_381(a_i, b_i); + let c_i = sub_fp2_bls12_381( + a_i, + b_i, + #[cfg(feature = "hints")] + hints, + ); result[i * 12..(i + 1) * 12].copy_from_slice(&c_i); } result @@ -65,7 +97,11 @@ pub fn sub_fp6_bls12_381(a: &[u64; 36], b: &[u64; 36]) -> [u64; 36] { // - c2 = a1·b2 + a2·b1 + (a3·b3)·(1+u) // - c3 = a1·b3 + a2·b2 + a3·b1 #[inline] -pub fn mul_fp6_bls12_381(a: &[u64; 36], b: &[u64; 36]) -> [u64; 36] { +pub fn mul_fp6_bls12_381( + a: &[u64; 36], + b: &[u64; 36], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 36] { let a1 = &a[0..12].try_into().unwrap(); let a2 = &a[12..24].try_into().unwrap(); let a3 = &a[24..36].try_into().unwrap(); @@ -74,21 +110,106 @@ pub fn mul_fp6_bls12_381(a: &[u64; 36], b: &[u64; 36]) -> [u64; 36] { let b3 = &b[24..36].try_into().unwrap(); // c1 = a1·b1 + [a2·b3 + a3·b2]·(1+u) - let mut c1 = mul_fp2_bls12_381(a2, b3); - c1 = add_fp2_bls12_381(&c1, &mul_fp2_bls12_381(a3, b2)); - c1 = mul_fp2_bls12_381(&c1, &EXT_U); - c1 = add_fp2_bls12_381(&c1, &mul_fp2_bls12_381(a1, b1)); + let mut c1 = mul_fp2_bls12_381( + a2, + b3, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp2_bls12_381( + &c1, + &mul_fp2_bls12_381( + a3, + b2, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + c1 = mul_fp2_bls12_381( + &c1, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp2_bls12_381( + &c1, + &mul_fp2_bls12_381( + a1, + b1, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // c2 = a1·b2 + a2·b1 + (a3·b3)·(1+u) - let mut c2 = mul_fp2_bls12_381(a3, b3); - c2 = mul_fp2_bls12_381(&c2, &EXT_U); - c2 = add_fp2_bls12_381(&c2, &mul_fp2_bls12_381(a1, b2)); - c2 = add_fp2_bls12_381(&c2, &mul_fp2_bls12_381(a2, b1)); + let mut c2 = mul_fp2_bls12_381( + a3, + b3, + #[cfg(feature = "hints")] + hints, + ); + c2 = mul_fp2_bls12_381( + &c2, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + c2 = add_fp2_bls12_381( + &c2, + &mul_fp2_bls12_381( + a1, + b2, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + c2 = add_fp2_bls12_381( + &c2, + &mul_fp2_bls12_381( + a2, + b1, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // c3 = a1·b3 + a2·b2 + a3·b1 - let mut c3 = mul_fp2_bls12_381(a1, b3); - c3 = add_fp2_bls12_381(&c3, &mul_fp2_bls12_381(a2, b2)); - c3 = add_fp2_bls12_381(&c3, &mul_fp2_bls12_381(a3, b1)); + let mut c3 = mul_fp2_bls12_381( + a1, + b3, + #[cfg(feature = "hints")] + hints, + ); + c3 = add_fp2_bls12_381( + &c3, + &mul_fp2_bls12_381( + a2, + b2, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + c3 = add_fp2_bls12_381( + &c3, + &mul_fp2_bls12_381( + a3, + b1, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 36]; result[0..12].copy_from_slice(&c1); @@ -104,20 +225,43 @@ pub fn mul_fp6_bls12_381(a: &[u64; 36], b: &[u64; 36]) -> [u64; 36] { // - c2 = a1·b2 // - c3 = a2·b2 #[inline] -pub fn sparse_mula_fp6_bls12_381(a: &[u64; 36], b2: &[u64; 12]) -> [u64; 36] { +pub fn sparse_mula_fp6_bls12_381( + a: &[u64; 36], + b2: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 36] { let a1 = &a[0..12].try_into().unwrap(); let a2 = &a[12..24].try_into().unwrap(); let a3 = &a[24..36].try_into().unwrap(); // c1 = a3·b2·(1+u) - let mut c1 = mul_fp2_bls12_381(a3, b2); - c1 = mul_fp2_bls12_381(&c1, &EXT_U); + let mut c1 = mul_fp2_bls12_381( + a3, + b2, + #[cfg(feature = "hints")] + hints, + ); + c1 = mul_fp2_bls12_381( + &c1, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); // c2 = a1·b2 - let c2 = mul_fp2_bls12_381(a1, b2); - + let c2 = mul_fp2_bls12_381( + a1, + b2, + #[cfg(feature = "hints")] + hints, + ); // c3 = a2·b2 - let c3 = mul_fp2_bls12_381(a2, b2); + let c3 = mul_fp2_bls12_381( + a2, + b2, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 36]; result[0..12].copy_from_slice(&c1); @@ -134,7 +278,11 @@ pub fn sparse_mula_fp6_bls12_381(a: &[u64; 36], b2: &[u64; 12]) -> [u64; 36] { // - c2 = a1·b2 + a3·b3·(1+u) // - c3 = a1·b3 + a2·b2 #[inline] -pub fn sparse_mulb_fp6_bls12_381(a: &[u64; 36], b: &[u64; 24]) -> [u64; 36] { +pub fn sparse_mulb_fp6_bls12_381( + a: &[u64; 36], + b: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 36] { let a1 = &a[0..12].try_into().unwrap(); let a2 = &a[12..24].try_into().unwrap(); let a3 = &a[24..36].try_into().unwrap(); @@ -142,19 +290,73 @@ pub fn sparse_mulb_fp6_bls12_381(a: &[u64; 36], b: &[u64; 24]) -> [u64; 36] { let b3 = &b[12..24].try_into().unwrap(); // c1 = (a2·b3 + a3·b2)·(1+u) - let mut c1 = mul_fp2_bls12_381(a2, b3); - c1 = add_fp2_bls12_381(&c1, &mul_fp2_bls12_381(a3, b2)); - c1 = mul_fp2_bls12_381(&c1, &EXT_U); + let mut c1 = mul_fp2_bls12_381( + a2, + b3, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp2_bls12_381( + &c1, + &mul_fp2_bls12_381( + a3, + b2, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + c1 = mul_fp2_bls12_381( + &c1, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); // c2 = a1·b2 + a3·b3·(1+u) - let mut c2 = mul_fp2_bls12_381(a3, b3); - c2 = mul_fp2_bls12_381(&c2, &EXT_U); - c2 = add_fp2_bls12_381(&c2, &mul_fp2_bls12_381(a1, b2)); + let mut c2 = mul_fp2_bls12_381( + a3, + b3, + #[cfg(feature = "hints")] + hints, + ); + c2 = mul_fp2_bls12_381( + &c2, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + c2 = add_fp2_bls12_381( + &c2, + &mul_fp2_bls12_381( + a1, + b2, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // c3 = a1·b3 + a2·b2 - let mut c3 = mul_fp2_bls12_381(a1, b3); - c3 = add_fp2_bls12_381(&c3, &mul_fp2_bls12_381(a2, b2)); - + let mut c3 = mul_fp2_bls12_381( + a1, + b3, + #[cfg(feature = "hints")] + hints, + ); + c3 = add_fp2_bls12_381( + &c3, + &mul_fp2_bls12_381( + a2, + b2, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 36]; result[0..12].copy_from_slice(&c1); result[12..24].copy_from_slice(&c2); @@ -170,7 +372,11 @@ pub fn sparse_mulb_fp6_bls12_381(a: &[u64; 36], b: &[u64; 24]) -> [u64; 36] { // - c2 = a2·b1 + a3·b3·(1+u) // - c3 = a1·b3 + a3·b1 #[inline] -pub fn sparse_mulc_fp6_bls12_381(a: &[u64; 36], b: &[u64; 24]) -> [u64; 36] { +pub fn sparse_mulc_fp6_bls12_381( + a: &[u64; 36], + b: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 36] { let a1 = &a[0..12].try_into().unwrap(); let a2 = &a[12..24].try_into().unwrap(); let a3 = &a[24..36].try_into().unwrap(); @@ -178,19 +384,73 @@ pub fn sparse_mulc_fp6_bls12_381(a: &[u64; 36], b: &[u64; 24]) -> [u64; 36] { let b3 = &b[12..24].try_into().unwrap(); // c1 = a1·b1 + a2·b3·(1+u) - let mut c1 = mul_fp2_bls12_381(a2, b3); - c1 = mul_fp2_bls12_381(&c1, &EXT_U); - c1 = add_fp2_bls12_381(&c1, &mul_fp2_bls12_381(a1, b1)); + let mut c1 = mul_fp2_bls12_381( + a2, + b3, + #[cfg(feature = "hints")] + hints, + ); + c1 = mul_fp2_bls12_381( + &c1, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp2_bls12_381( + &c1, + &mul_fp2_bls12_381( + a1, + b1, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // c2 = a2·b1 + a3·b3·(1+u) - let mut c2 = mul_fp2_bls12_381(a3, b3); - c2 = mul_fp2_bls12_381(&c2, &EXT_U); - c2 = add_fp2_bls12_381(&c2, &mul_fp2_bls12_381(a2, b1)); + let mut c2 = mul_fp2_bls12_381( + a3, + b3, + #[cfg(feature = "hints")] + hints, + ); + c2 = mul_fp2_bls12_381( + &c2, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + c2 = add_fp2_bls12_381( + &c2, + &mul_fp2_bls12_381( + a2, + b1, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // c3 = a1·b3 + a3·b1 - let mut c3 = mul_fp2_bls12_381(a1, b3); - c3 = add_fp2_bls12_381(&c3, &mul_fp2_bls12_381(a3, b1)); - + let mut c3 = mul_fp2_bls12_381( + a1, + b3, + #[cfg(feature = "hints")] + hints, + ); + c3 = add_fp2_bls12_381( + &c3, + &mul_fp2_bls12_381( + a3, + b1, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 36]; result[0..12].copy_from_slice(&c1); result[12..24].copy_from_slice(&c2); @@ -206,25 +466,92 @@ pub fn sparse_mulc_fp6_bls12_381(a: &[u64; 36], b: &[u64; 24]) -> [u64; 36] { // - c2 = a3²·(1+u) + 2·a1·a2 // - c3 = a2² + 2·a1·a3 #[inline] -pub fn square_fp6_bls12_381(a: &[u64; 36]) -> [u64; 36] { +pub fn square_fp6_bls12_381( + a: &[u64; 36], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 36] { let a1 = &a[0..12].try_into().unwrap(); let a2 = &a[12..24].try_into().unwrap(); let a3 = &a[24..36].try_into().unwrap(); // c1 = a1² + 2·a2·a3·(1+u) - let mut c1 = mul_fp2_bls12_381(a2, a3); - c1 = dbl_fp2_bls12_381(&c1); - c1 = mul_fp2_bls12_381(&c1, &EXT_U); - c1 = add_fp2_bls12_381(&c1, &square_fp2_bls12_381(a1)); + let mut c1 = mul_fp2_bls12_381( + a2, + a3, + #[cfg(feature = "hints")] + hints, + ); + c1 = dbl_fp2_bls12_381( + &c1, + #[cfg(feature = "hints")] + hints, + ); + c1 = mul_fp2_bls12_381( + &c1, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp2_bls12_381( + &c1, + &square_fp2_bls12_381( + a1, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // c2 = a3²·(1+u) + 2·a1·a2 - let mut c2 = square_fp2_bls12_381(a3); - c2 = mul_fp2_bls12_381(&c2, &EXT_U); - c2 = add_fp2_bls12_381(&c2, &dbl_fp2_bls12_381(&mul_fp2_bls12_381(a1, a2))); + let mut c2 = square_fp2_bls12_381( + a3, + #[cfg(feature = "hints")] + hints, + ); + c2 = mul_fp2_bls12_381( + &c2, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + c2 = add_fp2_bls12_381( + &c2, + &dbl_fp2_bls12_381( + &mul_fp2_bls12_381( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // c3 = a2² + 2·a1·a3 - let mut c3 = square_fp2_bls12_381(a2); - c3 = add_fp2_bls12_381(&c3, &dbl_fp2_bls12_381(&mul_fp2_bls12_381(a1, a3))); + let mut c3 = square_fp2_bls12_381( + a2, + #[cfg(feature = "hints")] + hints, + ); + c3 = add_fp2_bls12_381( + &c3, + &dbl_fp2_bls12_381( + &mul_fp2_bls12_381( + a1, + a3, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 36]; result[0..12].copy_from_slice(&c1); @@ -245,43 +572,146 @@ pub fn square_fp6_bls12_381(a: &[u64; 36]) -> [u64; 36] { // * c2mid = (1 + u)·a3² - (a1·a2) // * c3mid = a2² - (a1·a3) #[inline] -pub fn inv_fp6_bls12_381(a: &[u64; 36]) -> [u64; 36] { +pub fn inv_fp6_bls12_381( + a: &[u64; 36], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 36] { let a1 = &a[0..12].try_into().unwrap(); let a2 = &a[12..24].try_into().unwrap(); let a3 = &a[24..36].try_into().unwrap(); // a1², a2², a3² - let a1_squared = square_fp2_bls12_381(a1); - let a2_squared = square_fp2_bls12_381(a2); - let a3_squared = square_fp2_bls12_381(a3); + let a1_squared = square_fp2_bls12_381( + a1, + #[cfg(feature = "hints")] + hints, + ); + let a2_squared = square_fp2_bls12_381( + a2, + #[cfg(feature = "hints")] + hints, + ); + let a3_squared = square_fp2_bls12_381( + a3, + #[cfg(feature = "hints")] + hints, + ); // a1·a2, a1·a3, a2·a3 - let a1_a2 = mul_fp2_bls12_381(a1, a2); - let a1_a3 = mul_fp2_bls12_381(a1, a3); - let a2_a3 = mul_fp2_bls12_381(a2, a3); + let a1_a2 = mul_fp2_bls12_381( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ); + let a1_a3 = mul_fp2_bls12_381( + a1, + a3, + #[cfg(feature = "hints")] + hints, + ); + let a2_a3 = mul_fp2_bls12_381( + a2, + a3, + #[cfg(feature = "hints")] + hints, + ); // c1mid = a1² - (1 + u)·(a2·a3) - let mut c1mid = mul_fp2_bls12_381(&a2_a3, &EXT_U); - c1mid = sub_fp2_bls12_381(&a1_squared, &c1mid); + let mut c1mid = mul_fp2_bls12_381( + &a2_a3, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + c1mid = sub_fp2_bls12_381( + &a1_squared, + &c1mid, + #[cfg(feature = "hints")] + hints, + ); // c2mid = (1 + u)·a3² - (a1·a2) - let mut c2mid = mul_fp2_bls12_381(&a3_squared, &EXT_U); - c2mid = sub_fp2_bls12_381(&c2mid, &a1_a2); - + let mut c2mid = mul_fp2_bls12_381( + &a3_squared, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + c2mid = sub_fp2_bls12_381( + &c2mid, + &a1_a2, + #[cfg(feature = "hints")] + hints, + ); // c3mid = a2² - (a1·a3) - let c3mid = sub_fp2_bls12_381(&a2_squared, &a1_a3); + let c3mid = sub_fp2_bls12_381( + &a2_squared, + &a1_a3, + #[cfg(feature = "hints")] + hints, + ); // (a1·c1mid + (1 + u)·(a3·c2mid + a2·c3mid))⁻¹ - let mut last = mul_fp2_bls12_381(a3, &c2mid); - last = add_fp2_bls12_381(&last, &mul_fp2_bls12_381(a2, &c3mid)); - last = mul_fp2_bls12_381(&last, &EXT_U); - last = add_fp2_bls12_381(&last, &mul_fp2_bls12_381(a1, &c1mid)); - let last_inv = inv_fp2_bls12_381(&last); + let mut last = mul_fp2_bls12_381( + a3, + &c2mid, + #[cfg(feature = "hints")] + hints, + ); + last = add_fp2_bls12_381( + &last, + &mul_fp2_bls12_381( + a2, + &c3mid, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + last = mul_fp2_bls12_381( + &last, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + last = add_fp2_bls12_381( + &last, + &mul_fp2_bls12_381( + a1, + &c1mid, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + let last_inv = inv_fp2_bls12_381( + &last, + #[cfg(feature = "hints")] + hints, + ); // c1 = c1mid·last_inv, c2 = c2mid·last_inv, c3 = c3mid·last_inv - let c1 = mul_fp2_bls12_381(&c1mid, &last_inv); - let c2 = mul_fp2_bls12_381(&c2mid, &last_inv); - let c3 = mul_fp2_bls12_381(&c3mid, &last_inv); + let c1 = mul_fp2_bls12_381( + &c1mid, + &last_inv, + #[cfg(feature = "hints")] + hints, + ); + let c2 = mul_fp2_bls12_381( + &c2mid, + &last_inv, + #[cfg(feature = "hints")] + hints, + ); + let c3 = mul_fp2_bls12_381( + &c3mid, + &last_inv, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 36]; result[0..12].copy_from_slice(&c1); diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs index b62eac9cf..ab99b2a1d 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs @@ -6,17 +6,25 @@ use super::constants::{R, R_MINUS_ONE}; /// Addition in Fr #[inline] -pub fn add_fr_bls12_381(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn add_fr_bls12_381( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·1 + y let mut params = SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &R, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Doubling in Fr #[inline] -pub fn dbl_fr_bls12_381(x: &[u64; 4]) -> [u64; 4] { +pub fn dbl_fr_bls12_381(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { // 2·x + 0 or x·1 + x let mut params = SyscallArith256ModParams { a: x, @@ -25,23 +33,35 @@ pub fn dbl_fr_bls12_381(x: &[u64; 4]) -> [u64; 4] { module: &R, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Subtraction in Fr #[inline] -pub fn sub_fr_bls12_381(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn sub_fr_bls12_381( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // y·(-1) + x let mut params = SyscallArith256ModParams { a: y, b: &R_MINUS_ONE, c: x, module: &R, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Negation in Fr #[inline] -pub fn neg_fr_bls12_381(x: &[u64; 4]) -> [u64; 4] { +pub fn neg_fr_bls12_381(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { // x·(-1) + 0 let mut params = SyscallArith256ModParams { a: x, @@ -50,27 +70,46 @@ pub fn neg_fr_bls12_381(x: &[u64; 4]) -> [u64; 4] { module: &R, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Multiplication in Fr #[inline] -pub fn mul_fr_bls12_381(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn mul_fr_bls12_381( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·y + 0 let mut params = SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &R, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Squaring in Fr #[inline] -pub fn square_fr_bls12_381(x: &[u64; 4]) -> [u64; 4] { +pub fn square_fr_bls12_381( + x: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·x + 0 let mut params = SyscallArith256ModParams { a: x, b: x, c: &[0, 0, 0, 0], module: &R, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } @@ -80,7 +119,11 @@ pub fn square_fr_bls12_381(x: &[u64; 4]) -> [u64; 4] { /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 4]` (32 bytes). #[no_mangle] -pub unsafe extern "C" fn add_fr_bls12_381_c(a: *mut u64, b: *const u64) { +pub unsafe extern "C" fn add_fr_bls12_381_c( + a: *mut u64, + b: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 4]); let b_ref = &*(b as *const [u64; 4]); @@ -91,7 +134,11 @@ pub unsafe extern "C" fn add_fr_bls12_381_c(a: *mut u64, b: *const u64) { module: &R, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); } @@ -99,7 +146,10 @@ pub unsafe extern "C" fn add_fr_bls12_381_c(a: *mut u64, b: *const u64) { /// # Safety /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. #[no_mangle] -pub unsafe extern "C" fn dbl_fr_bls12_381_c(a: *mut u64) { +pub unsafe extern "C" fn dbl_fr_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 4]); let mut params = SyscallArith256ModParams { @@ -109,7 +159,11 @@ pub unsafe extern "C" fn dbl_fr_bls12_381_c(a: *mut u64) { module: &R, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); } @@ -118,7 +172,11 @@ pub unsafe extern "C" fn dbl_fr_bls12_381_c(a: *mut u64) { /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 4]` (32 bytes). #[no_mangle] -pub unsafe extern "C" fn sub_fr_bls12_381_c(a: *mut u64, b: *const u64) { +pub unsafe extern "C" fn sub_fr_bls12_381_c( + a: *mut u64, + b: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 4]); let b_ref = &*(b as *const [u64; 4]); @@ -129,7 +187,11 @@ pub unsafe extern "C" fn sub_fr_bls12_381_c(a: *mut u64, b: *const u64) { module: &R, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); } @@ -137,7 +199,10 @@ pub unsafe extern "C" fn sub_fr_bls12_381_c(a: *mut u64, b: *const u64) { /// # Safety /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. #[no_mangle] -pub unsafe extern "C" fn neg_fr_bls12_381_c(a: *mut u64) { +pub unsafe extern "C" fn neg_fr_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 4]); let mut params = SyscallArith256ModParams { @@ -147,7 +212,11 @@ pub unsafe extern "C" fn neg_fr_bls12_381_c(a: *mut u64) { module: &R, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); } @@ -156,7 +225,11 @@ pub unsafe extern "C" fn neg_fr_bls12_381_c(a: *mut u64) { /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 4]` (32 bytes). #[no_mangle] -pub unsafe extern "C" fn mul_fr_bls12_381_c(a: *mut u64, b: *const u64) { +pub unsafe extern "C" fn mul_fr_bls12_381_c( + a: *mut u64, + b: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 4]); let b_ref = &*(b as *const [u64; 4]); @@ -167,7 +240,11 @@ pub unsafe extern "C" fn mul_fr_bls12_381_c(a: *mut u64, b: *const u64) { module: &R, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); } @@ -175,7 +252,10 @@ pub unsafe extern "C" fn mul_fr_bls12_381_c(a: *mut u64, b: *const u64) { /// # Safety /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. #[no_mangle] -pub unsafe extern "C" fn square_fr_bls12_381_c(a: *mut u64) { +pub unsafe extern "C" fn square_fr_bls12_381_c( + a: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let a_ref = &*(a as *const [u64; 4]); let mut params = SyscallArith256ModParams { @@ -185,7 +265,11 @@ pub unsafe extern "C" fn square_fr_bls12_381_c(a: *mut u64) { module: &R, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs index f4cce702e..f3fb6d4bb 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs @@ -15,16 +15,43 @@ use super::{ /// Computes the Miller loop of a non-zero point `p` in G1 and a non-zero point `q` in G2 /// /// Note: It is not optimized for the case where either `p` or `q` is the point at infinity. -pub fn miller_loop_bls12_381(p: &[u64; 12], q: &[u64; 24]) -> [u64; 72] { +pub fn miller_loop_bls12_381( + p: &[u64; 12], + q: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { // Before the loop starts, compute xp' = (-xp/yp)·1/(1+u) and yp' = (1/yp)·1/(1+u) let mut xp: [u64; 6] = p[0..6].try_into().unwrap(); let mut yp: [u64; 6] = p[6..12].try_into().unwrap(); - yp = inv_fp_bls12_381(&yp); - xp = neg_fp_bls12_381(&xp); - xp = mul_fp_bls12_381(&xp, &yp); - - let xp_prime: [u64; 12] = scalar_mul_fp2_bls12_381(&EXT_U_INV, &xp); - let yp_prime: [u64; 12] = scalar_mul_fp2_bls12_381(&EXT_U_INV, &yp); + yp = inv_fp_bls12_381( + &yp, + #[cfg(feature = "hints")] + hints, + ); + xp = neg_fp_bls12_381( + &xp, + #[cfg(feature = "hints")] + hints, + ); + xp = mul_fp_bls12_381( + &xp, + &yp, + #[cfg(feature = "hints")] + hints, + ); + + let xp_prime: [u64; 12] = scalar_mul_fp2_bls12_381( + &EXT_U_INV, + &xp, + #[cfg(feature = "hints")] + hints, + ); + let yp_prime: [u64; 12] = scalar_mul_fp2_bls12_381( + &EXT_U_INV, + &yp, + #[cfg(feature = "hints")] + hints, + ); // Initialize the Miller loop with r = q and f = 1 let mut r: [u64; 24] = q[0..24].try_into().unwrap(); @@ -35,41 +62,112 @@ pub fn miller_loop_bls12_381(p: &[u64; 12], q: &[u64; 24]) -> [u64; 72] { }; for &bit in X_ABS_BIN_BE.iter().skip(1) { // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(r)} - let (lambda, mu) = fcall_bls12_381_dbl_line_coeffs(&r); + let (lambda, mu) = fcall_bls12_381_dbl_line_coeffs( + &r, + #[cfg(feature = "hints")] + hints, + ); // Check that the line is correct - assert!(is_tangent_twist_bls12_381(&r, &lambda, &mu)); + assert!(is_tangent_twist_bls12_381( + &r, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + )); // Compute f = f² · line_{twist(r),twist(r)}(p) - f = square_fp12_bls12_381(&f); - let l = line_eval_twist_bls12_381(&lambda, &mu, &xp_prime, &yp_prime); - f = sparse_mul_fp12_bls12_381(&f, &l); + f = square_fp12_bls12_381( + &f, + #[cfg(feature = "hints")] + hints, + ); + let l = line_eval_twist_bls12_381( + &lambda, + &mu, + &xp_prime, + &yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bls12_381( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); // Double r - r = dbl_twist_with_hints_bls12_381(&r, &lambda, &mu); + r = dbl_twist_with_hints_bls12_381( + &r, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + ); if bit == 1 { // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(q)} - let (lambda, mu) = fcall_bls12_381_add_line_coeffs(&r, q); + let (lambda, mu) = fcall_bls12_381_add_line_coeffs( + &r, + q, + #[cfg(feature = "hints")] + hints, + ); // Check that the line is correct - assert!(is_line_twist_bls12_381(&r, q, &lambda, &mu)); + assert!(is_line_twist_bls12_381( + &r, + q, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + )); // Compute f = f · line_{twist(r),twist(q)} - let l = line_eval_twist_bls12_381(&lambda, &mu, &xp_prime, &yp_prime); - f = sparse_mul_fp12_bls12_381(&f, &l); + let l = line_eval_twist_bls12_381( + &lambda, + &mu, + &xp_prime, + &yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bls12_381( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); // Add r and q - r = add_twist_with_hints_bls12_381(&r, q, &lambda, &mu); + r = add_twist_with_hints_bls12_381( + &r, + q, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + ); } } // Finally, compute f̅ - conjugate_fp12_bls12_381(&f) + conjugate_fp12_bls12_381( + &f, + #[cfg(feature = "hints")] + hints, + ) } /// Computes the Miller loop for the BN254 curve for a batch of non-zero points `p_i` in G1 and non-zero points `q_i` in G2 -pub fn miller_loop_batch_bls12_381(g1_points: &[[u64; 12]], g2_points: &[[u64; 24]]) -> [u64; 72] { +pub fn miller_loop_batch_bls12_381( + g1_points: &[[u64; 12]], + g2_points: &[[u64; 24]], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { // Before the loop starts, compute xp' = (-xp/yp)·1/(1+u) and yp' = (1/yp)·1/(1+u) let n = g1_points.len(); let mut xp_primes: Vec<[u64; 12]> = Vec::with_capacity(n); @@ -77,12 +175,35 @@ pub fn miller_loop_batch_bls12_381(g1_points: &[[u64; 12]], g2_points: &[[u64; 2 for p in g1_points.iter() { let mut xp: [u64; 6] = p[0..6].try_into().unwrap(); let mut yp: [u64; 6] = p[6..12].try_into().unwrap(); - yp = inv_fp_bls12_381(&yp); - xp = neg_fp_bls12_381(&xp); - xp = mul_fp_bls12_381(&xp, &yp); - - let xp_prime: [u64; 12] = scalar_mul_fp2_bls12_381(&EXT_U_INV, &xp); - let yp_prime: [u64; 12] = scalar_mul_fp2_bls12_381(&EXT_U_INV, &yp); + yp = inv_fp_bls12_381( + &yp, + #[cfg(feature = "hints")] + hints, + ); + xp = neg_fp_bls12_381( + &xp, + #[cfg(feature = "hints")] + hints, + ); + xp = mul_fp_bls12_381( + &xp, + &yp, + #[cfg(feature = "hints")] + hints, + ); + + let xp_prime: [u64; 12] = scalar_mul_fp2_bls12_381( + &EXT_U_INV, + &xp, + #[cfg(feature = "hints")] + hints, + ); + let yp_prime: [u64; 12] = scalar_mul_fp2_bls12_381( + &EXT_U_INV, + &yp, + #[cfg(feature = "hints")] + hints, + ); xp_primes.push(xp_prime); yp_primes.push(yp_prime); } @@ -93,46 +214,113 @@ pub fn miller_loop_batch_bls12_381(g1_points: &[[u64; 12]], g2_points: &[[u64; 2 f[0] = 1; for &bit in X_ABS_BIN_BE.iter().skip(1) { // Compute f = f² · line_{twist(r),twist(r)}(p) - f = square_fp12_bls12_381(&f); + f = square_fp12_bls12_381( + &f, + #[cfg(feature = "hints")] + hints, + ); for i in 0..n { let r = &mut r[i]; // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(r)} - let (lambda, mu) = fcall_bls12_381_dbl_line_coeffs(r); + let (lambda, mu) = fcall_bls12_381_dbl_line_coeffs( + r, + #[cfg(feature = "hints")] + hints, + ); // Check that the line is correct - assert!(is_tangent_twist_bls12_381(r, &lambda, &mu)); + assert!(is_tangent_twist_bls12_381( + r, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + )); let xp_prime = &xp_primes[i]; let yp_prime = &yp_primes[i]; - let l = line_eval_twist_bls12_381(&lambda, &mu, xp_prime, yp_prime); - f = sparse_mul_fp12_bls12_381(&f, &l); + let l = line_eval_twist_bls12_381( + &lambda, + &mu, + xp_prime, + yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bls12_381( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); // Double r - *r = dbl_twist_with_hints_bls12_381(r, &lambda, &mu); + *r = dbl_twist_with_hints_bls12_381( + r, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + ); if bit == 1 { let q = &g2_points[i]; // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(q')} - let (lambda, mu) = fcall_bls12_381_add_line_coeffs(r, q); + let (lambda, mu) = fcall_bls12_381_add_line_coeffs( + r, + q, + #[cfg(feature = "hints")] + hints, + ); // Check that the line is correct - assert!(is_line_twist_bls12_381(r, q, &lambda, &mu)); + assert!(is_line_twist_bls12_381( + r, + q, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + )); // Compute f = f · line_{twist(r),twist(q')} - let l = line_eval_twist_bls12_381(&lambda, &mu, xp_prime, yp_prime); - f = sparse_mul_fp12_bls12_381(&f, &l); + let l = line_eval_twist_bls12_381( + &lambda, + &mu, + xp_prime, + yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bls12_381( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); // Add r and q - *r = add_twist_with_hints_bls12_381(r, q, &lambda, &mu); + *r = add_twist_with_hints_bls12_381( + r, + q, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + ); } } } // Finally, compute f̅ - conjugate_fp12_bls12_381(&f) + conjugate_fp12_bls12_381( + &f, + #[cfg(feature = "hints")] + hints, + ) } // We follow https://eprint.iacr.org/2024/640.pdf for the line computations. @@ -152,24 +340,66 @@ fn is_line_twist_bls12_381( q2: &[u64; 24], lambda: &[u64; 12], mu: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> bool { - line_check_twist_bls12_381(q1, lambda, mu) && line_check_twist_bls12_381(q2, lambda, mu) + line_check_twist_bls12_381( + q1, + lambda, + mu, + #[cfg(feature = "hints")] + hints, + ) && line_check_twist_bls12_381( + q2, + lambda, + mu, + #[cfg(feature = "hints")] + hints, + ) } /// Checks if the line defined by (𝜆,𝜇) is tangent to the curve at non-zero point `q` in G2 #[inline] -fn is_tangent_twist_bls12_381(q: &[u64; 24], lambda: &[u64; 12], mu: &[u64; 12]) -> bool { +fn is_tangent_twist_bls12_381( + q: &[u64; 24], + lambda: &[u64; 12], + mu: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { // Check the line passes through q - let curve_check = line_check_twist_bls12_381(q, lambda, mu); + let curve_check = line_check_twist_bls12_381( + q, + lambda, + mu, + #[cfg(feature = "hints")] + hints, + ); // Check the line is tangent at q by checking that 2𝜆y = 3x² let x: &[u64; 12] = q[0..12].try_into().unwrap(); let y: &[u64; 12] = q[12..24].try_into().unwrap(); - let mut lhs = mul_fp2_bls12_381(lambda, y); - lhs = dbl_fp2_bls12_381(&lhs); - - let mut rhs = square_fp2_bls12_381(x); - rhs = scalar_mul_fp2_bls12_381(&rhs, &[3, 0, 0, 0, 0, 0]); + let mut lhs = mul_fp2_bls12_381( + lambda, + y, + #[cfg(feature = "hints")] + hints, + ); + lhs = dbl_fp2_bls12_381( + &lhs, + #[cfg(feature = "hints")] + hints, + ); + + let mut rhs = square_fp2_bls12_381( + x, + #[cfg(feature = "hints")] + hints, + ); + rhs = scalar_mul_fp2_bls12_381( + &rhs, + &[3, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); let tangent_check = eq(&lhs, &rhs); curve_check && tangent_check @@ -177,13 +407,28 @@ fn is_tangent_twist_bls12_381(q: &[u64; 24], lambda: &[u64; 12], mu: &[u64; 12]) /// Check if the line defined by (𝜆,𝜇) passes through non-zero point `q` in G2 #[inline] -fn line_check_twist_bls12_381(q: &[u64; 24], lambda: &[u64; 12], mu: &[u64; 12]) -> bool { +fn line_check_twist_bls12_381( + q: &[u64; 24], + lambda: &[u64; 12], + mu: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let x: &[u64; 12] = q[0..12].try_into().unwrap(); let y: &[u64; 12] = q[12..24].try_into().unwrap(); // Check if y = λx + μ - let mut rhs = mul_fp2_bls12_381(lambda, x); - rhs = add_fp2_bls12_381(&rhs, mu); + let mut rhs = mul_fp2_bls12_381( + lambda, + x, + #[cfg(feature = "hints")] + hints, + ); + rhs = add_fp2_bls12_381( + &rhs, + mu, + #[cfg(feature = "hints")] + hints, + ); eq(&rhs, y) } @@ -194,9 +439,24 @@ fn line_eval_twist_bls12_381( mu: &[u64; 12], x: &[u64; 12], y: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 24] { - let coeff1 = mul_fp2_bls12_381(mu, &neg_fp2_bls12_381(y)); - let coeff2 = mul_fp2_bls12_381(lambda, x); + let coeff1 = mul_fp2_bls12_381( + mu, + &neg_fp2_bls12_381( + y, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + let coeff2 = mul_fp2_bls12_381( + lambda, + x, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 24]; result[0..12].copy_from_slice(&coeff1); @@ -212,19 +472,48 @@ fn add_twist_with_hints_bls12_381( q2: &[u64; 24], lambda: &[u64; 12], mu: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 24] { let x1: &[u64; 12] = q1[0..12].try_into().unwrap(); let x2: &[u64; 12] = q2[0..12].try_into().unwrap(); // Compute x3 = λ² - x1 - x2 - let mut x3 = square_fp2_bls12_381(lambda); - x3 = sub_fp2_bls12_381(&x3, x1); - x3 = sub_fp2_bls12_381(&x3, x2); + let mut x3 = square_fp2_bls12_381( + lambda, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bls12_381( + &x3, + x1, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bls12_381( + &x3, + x2, + #[cfg(feature = "hints")] + hints, + ); // Compute y3 = -λx3 - μ - let mut y3 = mul_fp2_bls12_381(lambda, &x3); - y3 = add_fp2_bls12_381(mu, &y3); - y3 = neg_fp2_bls12_381(&y3); + let mut y3 = mul_fp2_bls12_381( + lambda, + &x3, + #[cfg(feature = "hints")] + hints, + ); + y3 = add_fp2_bls12_381( + mu, + &y3, + #[cfg(feature = "hints")] + hints, + ); + y3 = neg_fp2_bls12_381( + &y3, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 24]; result[0..12].copy_from_slice(&x3); @@ -234,17 +523,49 @@ fn add_twist_with_hints_bls12_381( /// Doubling of a non-zero point `q` in G2 with hinted line coefficients (𝜆,𝜇) #[inline] -fn dbl_twist_with_hints_bls12_381(q: &[u64; 24], lambda: &[u64; 12], mu: &[u64; 12]) -> [u64; 24] { +fn dbl_twist_with_hints_bls12_381( + q: &[u64; 24], + lambda: &[u64; 12], + mu: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let x: &[u64; 12] = q[0..12].try_into().unwrap(); // Compute x3 = λ² - 2x - let mut x3 = square_fp2_bls12_381(lambda); - x3 = sub_fp2_bls12_381(&x3, &dbl_fp2_bls12_381(x)); + let mut x3 = square_fp2_bls12_381( + lambda, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bls12_381( + &x3, + &dbl_fp2_bls12_381( + x, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // Compute y3 = -λx3 - μ - let mut y3 = mul_fp2_bls12_381(lambda, &x3); - y3 = add_fp2_bls12_381(mu, &y3); - y3 = neg_fp2_bls12_381(&y3); + let mut y3 = mul_fp2_bls12_381( + lambda, + &x3, + #[cfg(feature = "hints")] + hints, + ); + y3 = add_fp2_bls12_381( + mu, + &y3, + #[cfg(feature = "hints")] + hints, + ); + y3 = neg_fp2_bls12_381( + &y3, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 24]; result[0..12].copy_from_slice(&x3); @@ -257,12 +578,21 @@ fn dbl_twist_with_hints_bls12_381(q: &[u64; 24], lambda: &[u64; 12], mu: &[u64; /// - `q` must point to a valid `[u64; 24]` for the G2 affine point. /// - `p` must point to a valid `[u64; 12]` for the G1 affine point. #[no_mangle] -pub unsafe extern "C" fn miller_loop_bls12_381_c(ret: *mut u64, q: *const u64, p: *const u64) { +pub unsafe extern "C" fn miller_loop_bls12_381_c( + ret: *mut u64, + q: *const u64, + p: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); let q_arr: &[u64; 24] = &*(q as *const [u64; 24]); - let result = miller_loop_bls12_381(p_arr, q_arr); - + let result = miller_loop_bls12_381( + p_arr, + q_arr, + #[cfg(feature = "hints")] + hints, + ); let ret_arr: &mut [u64; 72] = &mut *(ret as *mut [u64; 72]); *ret_arr = result; } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs index 5b09d3281..f65338a85 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs @@ -16,7 +16,11 @@ use super::{ /// pairingBLS12-381: /// input: P ∈ G1 and Q ∈ G2 /// output: e(P,Q) ∈ GT -pub fn pairing_bls12_381(p: &[u64; 12], q: &[u64; 24]) -> [u64; 72] { +pub fn pairing_bls12_381( + p: &[u64; 12], + q: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { // e(P, 𝒪) = e(𝒪, Q) = 1; if *p == IDENTITY_G1 || *q == IDENTITY_G2 { let mut one = [0; 72]; @@ -25,16 +29,29 @@ pub fn pairing_bls12_381(p: &[u64; 12], q: &[u64; 24]) -> [u64; 72] { } // Miller loop - let miller_loop = miller_loop_bls12_381(p, q); + let miller_loop = miller_loop_bls12_381( + p, + q, + #[cfg(feature = "hints")] + hints, + ); // Final exponentiation - final_exp_bls12_381(&miller_loop) + final_exp_bls12_381( + &miller_loop, + #[cfg(feature = "hints")] + hints, + ) } /// Computes the optimal Ate pairing for a batch of G1 and G2 points over the BN254 curve /// and multiplies the results together, i.e.: /// e(P₁, Q₁) · e(P₂, Q₂) · ... · e(Pₙ, Qₙ) ∈ GT -pub fn pairing_batch_bls12_381(g1_points: &[[u64; 12]], g2_points: &[[u64; 24]]) -> [u64; 72] { +pub fn pairing_batch_bls12_381( + g1_points: &[[u64; 12]], + g2_points: &[[u64; 24]], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 72] { // Since each e(Pi, Qi) := FinalExp(MillerLoop(Pi, Qi)) // We have: // e(P₁, Q₁) · e(P₂, Q₂) · ... · e(Pₙ, Qₙ) = FinalExp(MillerLoop(P₁, Q₁) · MillerLoop(P₂, Q₂) · ... · MillerLoop(Pₙ, Qₙ)) @@ -63,10 +80,19 @@ pub fn pairing_batch_bls12_381(g1_points: &[[u64; 12]], g2_points: &[[u64; 24]]) } // Miller loop - let miller_loop = miller_loop_batch_bls12_381(&g1_points_ml, &g2_points_ml); + let miller_loop = miller_loop_batch_bls12_381( + &g1_points_ml, + &g2_points_ml, + #[cfg(feature = "hints")] + hints, + ); // Final exponentiation - final_exp_bls12_381(&miller_loop) + final_exp_bls12_381( + &miller_loop, + #[cfg(feature = "hints")] + hints, + ) } /// C-compatible wrapper for pairing_verify_bls12_381 @@ -83,6 +109,7 @@ pub unsafe extern "C" fn pairing_verify_bls12_381_c( q1_ptr: *const u64, p2_ptr: *const u64, q2_ptr: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> bool { let p1: &[u64; 12] = &*(p1_ptr as *const [u64; 12]); let q1: &[u64; 24] = &*(q1_ptr as *const [u64; 24]); @@ -99,8 +126,17 @@ pub unsafe extern "C" fn pairing_verify_bls12_381_c( } // Checking e(P1, Q1) == e(P2, Q2) is equivalent to checking e(P1, Q1) * e(-P2, Q2) == 1 - let p2_neg = neg_bls12_381(p2); - let pairing_result = pairing_batch_bls12_381(&[*p1, p2_neg], &[*q1, *q2]); + let p2_neg = neg_bls12_381( + p2, + #[cfg(feature = "hints")] + hints, + ); + let pairing_result = pairing_batch_bls12_381( + &[*p1, p2_neg], + &[*q1, *q2], + #[cfg(feature = "hints")] + hints, + ); let one = { let mut one = [0; 72]; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs index 6e9e2cb3f..249e61ae4 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs @@ -20,7 +20,10 @@ use super::{ /// - Bit 7 (0x80): Compression flag (must be 1 for compressed) /// - Bit 6 (0x40): Infinity flag (1 = point at infinity) /// - Bit 5 (0x20): Sign flag (1 = y is lexicographically largest) -pub fn decompress_twist_bls12_381(input: &[u8; 96]) -> Result<([u64; 24], bool), &'static str> { +pub fn decompress_twist_bls12_381( + input: &[u8; 96], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Result<([u64; 24], bool), &'static str> { let flags = input[0]; // Check compression bit @@ -82,12 +85,30 @@ pub fn decompress_twist_bls12_381(input: &[u8; 96]) -> Result<([u64; 24], bool), x[6..12].copy_from_slice(&x_i); // Calculate y² = x³ + 4(1+u) - let x_sq = square_fp2_bls12_381(&x); - let x_cb = mul_fp2_bls12_381(&x_sq, &x); - let y_sq = add_fp2_bls12_381(&x_cb, &ETWISTED_B); + let x_sq = square_fp2_bls12_381( + &x, + #[cfg(feature = "hints")] + hints, + ); + let x_cb = mul_fp2_bls12_381( + &x_sq, + &x, + #[cfg(feature = "hints")] + hints, + ); + let y_sq = add_fp2_bls12_381( + &x_cb, + &ETWISTED_B, + #[cfg(feature = "hints")] + hints, + ); // Compute sqrt - let (y, has_sqrt) = sqrt_fp2_bls12_381(&y_sq); + let (y, has_sqrt) = sqrt_fp2_bls12_381( + &y_sq, + #[cfg(feature = "hints")] + hints, + ); if !has_sqrt { return Err("No square root exists - point not on curve"); } @@ -96,7 +117,11 @@ pub fn decompress_twist_bls12_381(input: &[u8; 96]) -> Result<([u64; 24], bool), // y = y_r + y_i * u is "larger" if: // - y_i > -y_i, OR // - y_i == -y_i (i.e., y_i == 0) AND y_r > -y_r - let y_neg = neg_fp2_bls12_381(&y); + let y_neg = neg_fp2_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); let y_r: [u64; 6] = y[0..6].try_into().unwrap(); let y_i: [u64; 6] = y[6..12].try_into().unwrap(); let y_neg_r: [u64; 6] = y_neg[0..6].try_into().unwrap(); @@ -121,38 +146,91 @@ pub fn decompress_twist_bls12_381(input: &[u8; 96]) -> Result<([u64; 24], bool), } /// Check if a non-zero point `p` is on the BLS12-381 twist -pub fn is_on_curve_twist_bls12_381(p: &[u64; 24]) -> bool { +pub fn is_on_curve_twist_bls12_381( + p: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { // q in E' iff y² == x³ + 4·(1+u) let x: [u64; 12] = p[0..12].try_into().unwrap(); let y: [u64; 12] = p[12..24].try_into().unwrap(); - let x_sq = square_fp2_bls12_381(&x); - let x_cubed = mul_fp2_bls12_381(&x_sq, &x); - let x_cubed_plus_b = add_fp2_bls12_381(&x_cubed, &ETWISTED_B); - let y_sq = square_fp2_bls12_381(&y); + let x_sq = square_fp2_bls12_381( + &x, + #[cfg(feature = "hints")] + hints, + ); + let x_cubed = mul_fp2_bls12_381( + &x_sq, + &x, + #[cfg(feature = "hints")] + hints, + ); + let x_cubed_plus_b = add_fp2_bls12_381( + &x_cubed, + &ETWISTED_B, + #[cfg(feature = "hints")] + hints, + ); + let y_sq = square_fp2_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); eq(&x_cubed_plus_b, &y_sq) } /// Check if a non-zero point `p` is on the BLS12-381 twist subgroup -pub fn is_on_subgroup_twist_bls12_381(p: &[u64; 24]) -> bool { +pub fn is_on_subgroup_twist_bls12_381( + p: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { // p in subgroup iff: // x·𝜓³(P) + P == 𝜓²(P) // where ψ := 𝜑⁻¹𝜋ₚ𝜑 is the untwist-Frobenius-twist endomorphism // Compute ψ²(P), ψ³(P) - let utf1 = utf_endomorphism_twist_bls12_381(p); - let rhs = utf_endomorphism_twist_bls12_381(&utf1); - let utf3 = utf_endomorphism_twist_bls12_381(&rhs); + let utf1 = utf_endomorphism_twist_bls12_381( + p, + #[cfg(feature = "hints")] + hints, + ); + let rhs = utf_endomorphism_twist_bls12_381( + &utf1, + #[cfg(feature = "hints")] + hints, + ); + let utf3 = utf_endomorphism_twist_bls12_381( + &rhs, + #[cfg(feature = "hints")] + hints, + ); // Compute [x]ψ³(P) + P (since x is negative, we compute -[|x|]ψ³(P)) - let xutf3: [u64; 24] = scalar_mul_by_abs_x_twist_bls12_381(&utf3); - let mut lhs = neg_twist_bls12_381(&xutf3); - lhs = add_twist_bls12_381(&lhs, p); + let xutf3: [u64; 24] = scalar_mul_by_abs_x_twist_bls12_381( + &utf3, + #[cfg(feature = "hints")] + hints, + ); + let mut lhs = neg_twist_bls12_381( + &xutf3, + #[cfg(feature = "hints")] + hints, + ); + lhs = add_twist_bls12_381( + &lhs, + p, + #[cfg(feature = "hints")] + hints, + ); eq(&lhs, &rhs) } /// Addition of two non-zero points -pub fn add_twist_bls12_381(p1: &[u64; 24], p2: &[u64; 24]) -> [u64; 24] { +pub fn add_twist_bls12_381( + p1: &[u64; 24], + p2: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let x1: [u64; 12] = p1[0..12].try_into().unwrap(); let y1: [u64; 12] = p1[12..24].try_into().unwrap(); let x2: [u64; 12] = p2[0..12].try_into().unwrap(); @@ -163,7 +241,11 @@ pub fn add_twist_bls12_381(p1: &[u64; 24], p2: &[u64; 24]) -> [u64; 24] { // Is y1 == y2? if eq(&y1, &y2) { // Compute the doubling - return dbl_twist_bls12_381(p1); + return dbl_twist_bls12_381( + p1, + #[cfg(feature = "hints")] + hints, + ); } else { // Points are the inverse of each other, return the point at infinity return IDENTITY_G2; @@ -171,18 +253,65 @@ pub fn add_twist_bls12_381(p1: &[u64; 24], p2: &[u64; 24]) -> [u64; 24] { } // Compute the addition - let mut den = sub_fp2_bls12_381(&x2, &x1); - den = inv_fp2_bls12_381(&den); - let mut lambda = sub_fp2_bls12_381(&y2, &y1); - lambda = mul_fp2_bls12_381(&lambda, &den); - - let mut x3 = square_fp2_bls12_381(&lambda); - x3 = sub_fp2_bls12_381(&x3, &x1); - x3 = sub_fp2_bls12_381(&x3, &x2); - - let mut y3 = sub_fp2_bls12_381(&x1, &x3); - y3 = mul_fp2_bls12_381(&lambda, &y3); - y3 = sub_fp2_bls12_381(&y3, &y1); + let mut den = sub_fp2_bls12_381( + &x2, + &x1, + #[cfg(feature = "hints")] + hints, + ); + den = inv_fp2_bls12_381( + &den, + #[cfg(feature = "hints")] + hints, + ); + let mut lambda = sub_fp2_bls12_381( + &y2, + &y1, + #[cfg(feature = "hints")] + hints, + ); + lambda = mul_fp2_bls12_381( + &lambda, + &den, + #[cfg(feature = "hints")] + hints, + ); + + let mut x3 = square_fp2_bls12_381( + &lambda, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bls12_381( + &x3, + &x1, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bls12_381( + &x3, + &x2, + #[cfg(feature = "hints")] + hints, + ); + let mut y3 = sub_fp2_bls12_381( + &x1, + &x3, + #[cfg(feature = "hints")] + hints, + ); + y3 = mul_fp2_bls12_381( + &lambda, + &y3, + #[cfg(feature = "hints")] + hints, + ); + y3 = sub_fp2_bls12_381( + &y3, + &y1, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 24]; result[0..12].copy_from_slice(&x3); @@ -191,24 +320,79 @@ pub fn add_twist_bls12_381(p1: &[u64; 24], p2: &[u64; 24]) -> [u64; 24] { } /// Doubling of a non-zero point -pub fn dbl_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { +pub fn dbl_twist_bls12_381( + p: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let x: [u64; 12] = p[0..12].try_into().unwrap(); let y: [u64; 12] = p[12..24].try_into().unwrap(); // Compute the doubling - let mut lambda = dbl_fp2_bls12_381(&y); - lambda = inv_fp2_bls12_381(&lambda); - lambda = scalar_mul_fp2_bls12_381(&lambda, &[0x3, 0, 0, 0, 0, 0]); - lambda = mul_fp2_bls12_381(&lambda, &x); - lambda = mul_fp2_bls12_381(&lambda, &x); - - let mut x3 = square_fp2_bls12_381(&lambda); - x3 = sub_fp2_bls12_381(&x3, &x); - x3 = sub_fp2_bls12_381(&x3, &x); - - let mut y3 = sub_fp2_bls12_381(&x, &x3); - y3 = mul_fp2_bls12_381(&lambda, &y3); - y3 = sub_fp2_bls12_381(&y3, &y); + let mut lambda = dbl_fp2_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); + lambda = inv_fp2_bls12_381( + &lambda, + #[cfg(feature = "hints")] + hints, + ); + lambda = scalar_mul_fp2_bls12_381( + &lambda, + &[0x3, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + lambda = mul_fp2_bls12_381( + &lambda, + &x, + #[cfg(feature = "hints")] + hints, + ); + lambda = mul_fp2_bls12_381( + &lambda, + &x, + #[cfg(feature = "hints")] + hints, + ); + + let mut x3 = square_fp2_bls12_381( + &lambda, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bls12_381( + &x3, + &x, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bls12_381( + &x3, + &x, + #[cfg(feature = "hints")] + hints, + ); + + let mut y3 = sub_fp2_bls12_381( + &x, + &x3, + #[cfg(feature = "hints")] + hints, + ); + y3 = mul_fp2_bls12_381( + &lambda, + &y3, + #[cfg(feature = "hints")] + hints, + ); + y3 = sub_fp2_bls12_381( + &y3, + &y, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 24]; result[0..12].copy_from_slice(&x3); @@ -217,12 +401,19 @@ pub fn dbl_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { } /// Negation of a point -pub fn neg_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { +pub fn neg_twist_bls12_381( + p: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let x: [u64; 12] = p[0..12].try_into().unwrap(); let y: [u64; 12] = p[12..24].try_into().unwrap(); // Compute the negation - let y_neg = neg_fp2_bls12_381(&y); + let y_neg = neg_fp2_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 24]; result[0..12].copy_from_slice(&x); @@ -231,7 +422,11 @@ pub fn neg_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { } /// Multiplies a non-zero point `p` on the BLS12-381 curve by a scalar `k` on the BLS12-381 scalar field -pub fn scalar_mul_twist_bls12_381(p: &[u64; 24], k: &[u64; 6]) -> [u64; 24] { +pub fn scalar_mul_twist_bls12_381( + p: &[u64; 24], + k: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { // Direct cases: k = 0, k = 1, k = 2 match k { [0, 0, 0, 0, 0, 0] => { @@ -244,7 +439,11 @@ pub fn scalar_mul_twist_bls12_381(p: &[u64; 24], k: &[u64; 6]) -> [u64; 24] { } [2, 0, 0, 0, 0, 0] => { // Return 2p - return dbl_twist_bls12_381(p); + return dbl_twist_bls12_381( + p, + #[cfg(feature = "hints")] + hints, + ); } _ => {} } @@ -253,7 +452,12 @@ pub fn scalar_mul_twist_bls12_381(p: &[u64; 24], k: &[u64; 6]) -> [u64; 24] { // Hint the length the binary representations of k // We will verify the output by recomposing k // Moreover, we should check that the first received bit is 1 - let (max_limb, max_bit) = fcall_msb_pos_384(k, &[0, 0, 0, 0, 0, 0]); + let (max_limb, max_bit) = fcall_msb_pos_384( + k, + &[0, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); // Perform the loop, based on the binary representation of k @@ -283,12 +487,21 @@ pub fn scalar_mul_twist_bls12_381(p: &[u64; 24], k: &[u64; 6]) -> [u64; 24] { for i in (0..=limb).rev() { for j in (0..=bit).rev() { // Always double - q = dbl_twist_bls12_381(&q); + q = dbl_twist_bls12_381( + &q, + #[cfg(feature = "hints")] + hints, + ); // Get the next bit b of k. // If b == 1, we should add P to Q, otherwise start the next iteration if ((k[i] >> j) & 1) == 1 { - q = add_twist_bls12_381(&q, p); + q = add_twist_bls12_381( + &q, + p, + #[cfg(feature = "hints")] + hints, + ); // Reconstruct k k_rec[i] |= 1 << j; @@ -305,46 +518,108 @@ pub fn scalar_mul_twist_bls12_381(p: &[u64; 24], k: &[u64; 6]) -> [u64; 24] { } /// Scalar multiplication of a non-zero point `p` by a binary scalar `k` -pub fn scalar_mul_bin_twist_bls12_381(p: &[u64; 24], k: &[u8]) -> [u64; 24] { +pub fn scalar_mul_bin_twist_bls12_381( + p: &[u64; 24], + k: &[u8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let mut r = *p; for &bit in k.iter().skip(1) { - r = dbl_twist_bls12_381(&r); + r = dbl_twist_bls12_381( + &r, + #[cfg(feature = "hints")] + hints, + ); if bit == 1 { - r = add_twist_bls12_381(&r, p); + r = add_twist_bls12_381( + &r, + p, + #[cfg(feature = "hints")] + hints, + ); } } r } /// Scalar multiplication of a non-zero point by x -pub fn scalar_mul_by_abs_x_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { - scalar_mul_bin_twist_bls12_381(p, &X_ABS_BIN_BE) +pub fn scalar_mul_by_abs_x_twist_bls12_381( + p: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { + scalar_mul_bin_twist_bls12_381( + p, + &X_ABS_BIN_BE, + #[cfg(feature = "hints")] + hints, + ) } /// Compute the untwist-frobenius-twist (utf) endomorphism ψ := 𝜑⁻¹𝜋ₚ𝜑 of a non-zero point `p`, where: /// 𝜑 : E'(Fp2) -> E(Fp12) defined by 𝜑(x,y) = (x/ω²,y/ω³) is the untwist map /// 𝜋ₚ : E(Fp12) -> E(Fp12) defined by 𝜋ₚ(x,y) = (xᵖ,yᵖ) is the Frobenius map /// 𝜑⁻¹ : E(Fp12) -> E'(Fp2) defined by 𝜑⁻¹(x,y) = (x·ω²,y·ω³) is the twist map -pub fn utf_endomorphism_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { +pub fn utf_endomorphism_twist_bls12_381( + p: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let mut x: [u64; 12] = p[0..12].try_into().unwrap(); let mut y: [u64; 12] = p[12..24].try_into().unwrap(); // 1] Compute 𝜑(x,y) = (x/ω²,y/ω³) = (x·(%W_INV_X + %W_INV_Y·u)·ω⁴,y·(%W_INV_X + %W_INV_Y·u)·ω³) ∈ E(Fp12) - x = mul_fp2_bls12_381(&x, &EXT_U_INV); - y = mul_fp2_bls12_381(&y, &EXT_U_INV); + x = mul_fp2_bls12_381( + &x, + &EXT_U_INV, + #[cfg(feature = "hints")] + hints, + ); + y = mul_fp2_bls12_381( + &y, + &EXT_U_INV, + #[cfg(feature = "hints")] + hints, + ); // 2] Compute 𝜋ₚ(a,b) = (aᵖ,bᵖ), i.e., apply the frobenius operator // Since the previous result has only one non-zero coefficient, we can apply a specialized frobenius directly // (a·ω⁴)ᵖ = a̅·γ14·ω⁴, (b·ω³)ᵖ = b̅·γ13·ω³ - x = conjugate_fp2_bls12_381(&x); - x = scalar_mul_fp2_bls12_381(&x, &FROBENIUS_GAMMA14); - y = conjugate_fp2_bls12_381(&y); - y = mul_fp2_bls12_381(&y, &FROBENIUS_GAMMA13); + x = conjugate_fp2_bls12_381( + &x, + #[cfg(feature = "hints")] + hints, + ); + x = scalar_mul_fp2_bls12_381( + &x, + &FROBENIUS_GAMMA14, + #[cfg(feature = "hints")] + hints, + ); + y = conjugate_fp2_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); + y = mul_fp2_bls12_381( + &y, + &FROBENIUS_GAMMA13, + #[cfg(feature = "hints")] + hints, + ); // 3] Compute 𝜑⁻¹(a,b) = (a·ω²,b·ω³) ∈ E'(Fp2). In our particular case, we have: // 𝜑⁻¹((a̅·γ14·ω⁴)·ω²,(b̅·γ13·ω³)·ω³) = (a̅·γ14·(1+u), b̅·γ13·(1+u)) - x = mul_fp2_bls12_381(&x, &EXT_U); - y = mul_fp2_bls12_381(&y, &EXT_U); + x = mul_fp2_bls12_381( + &x, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); + y = mul_fp2_bls12_381( + &y, + &EXT_U, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0u64; 24]; result[0..12].copy_from_slice(&x); @@ -360,10 +635,18 @@ pub fn utf_endomorphism_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { /// - 1 = success (point at infinity) /// - 2 = error #[no_mangle] -pub unsafe extern "C" fn decompress_twist_bls12_381_c(ret: *mut u64, input: *const u8) -> u8 { +pub unsafe extern "C" fn decompress_twist_bls12_381_c( + ret: *mut u64, + input: *const u8, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> u8 { let input_arr: &[u8; 96] = &*(input as *const [u8; 96]); - match decompress_twist_bls12_381(input_arr) { + match decompress_twist_bls12_381( + input_arr, + #[cfg(feature = "hints")] + hints, + ) { Ok((result, is_infinity)) => { let ret_arr: &mut [u64; 24] = &mut *(ret as *mut [u64; 24]); *ret_arr = result; @@ -381,29 +664,52 @@ pub unsafe extern "C" fn decompress_twist_bls12_381_c(ret: *mut u64, input: *con /// - `p` must point to a valid `[u64; 24]` (192 bytes) for the input point. /// Returns true if the point is on the twist curve, false otherwise. #[no_mangle] -pub unsafe extern "C" fn is_on_curve_twist_bls12_381_c(p: *const u64) -> bool { +pub unsafe extern "C" fn is_on_curve_twist_bls12_381_c( + p: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let p_arr: &[u64; 24] = &*(p as *const [u64; 24]); - is_on_curve_twist_bls12_381(p_arr) + is_on_curve_twist_bls12_381( + p_arr, + #[cfg(feature = "hints")] + hints, + ) } /// # Safety /// - `p` must point to a valid `[u64; 24]` (192 bytes) for the input point. /// Returns true if the point is in the G2 subgroup, false otherwise. #[no_mangle] -pub unsafe extern "C" fn is_on_subgroup_twist_bls12_381_c(p: *const u64) -> bool { +pub unsafe extern "C" fn is_on_subgroup_twist_bls12_381_c( + p: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let p_arr: &[u64; 24] = &*(p as *const [u64; 24]); - is_on_subgroup_twist_bls12_381(p_arr) + is_on_subgroup_twist_bls12_381( + p_arr, + #[cfg(feature = "hints")] + hints, + ) } /// # Safety /// - `p1` must point to a valid `[u64; 24]` (192 bytes), used as both input and output. /// - `p2` must point to a valid `[u64; 24]` (192 bytes). #[no_mangle] -pub unsafe extern "C" fn add_twist_bls12_381_c(p1: *mut u64, p2: *const u64) -> bool { +pub unsafe extern "C" fn add_twist_bls12_381_c( + p1: *mut u64, + p2: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let p1_arr: &[u64; 24] = &*(p1 as *const [u64; 24]); let p2_arr: &[u64; 24] = &*(p2 as *const [u64; 24]); - let result = add_twist_bls12_381(p1_arr, p2_arr); + let result = add_twist_bls12_381( + p1_arr, + p2_arr, + #[cfg(feature = "hints")] + hints, + ); if result == IDENTITY_G2 { return true; } @@ -418,12 +724,21 @@ pub unsafe extern "C" fn add_twist_bls12_381_c(p1: *mut u64, p2: *const u64) -> /// - `p` must point to a valid `[u64; 24]` affine point. /// - `k` must point to a valid `[u64; 6]` scalar. #[no_mangle] -pub unsafe extern "C" fn scalar_mul_twist_bls12_381_c(ret: *mut u64, p: *const u64, k: *const u64) { +pub unsafe extern "C" fn scalar_mul_twist_bls12_381_c( + ret: *mut u64, + p: *const u64, + k: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let p_arr: &[u64; 24] = &*(p as *const [u64; 24]); let k_arr: &[u64; 6] = &*(k as *const [u64; 6]); - let result = scalar_mul_twist_bls12_381(p_arr, k_arr); - + let result = scalar_mul_twist_bls12_381( + p_arr, + k_arr, + #[cfg(feature = "hints")] + hints, + ); let ret_arr: &mut [u64; 24] = &mut *(ret as *mut [u64; 24]); *ret_arr = result; } diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs index b710bb790..b72e6175f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs @@ -14,20 +14,38 @@ use super::{ }; /// Check if a non-zero point `p` is on the BN254 curve -pub fn is_on_curve_bn254(p: &[u64; 8]) -> bool { +pub fn is_on_curve_bn254(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> bool { let x: [u64; 4] = p[0..4].try_into().unwrap(); let y: [u64; 4] = p[4..8].try_into().unwrap(); // p in E iff y² == x³ + 3 - let lhs = square_fp_bn254(&y); - let mut rhs = square_fp_bn254(&x); - rhs = mul_fp_bn254(&rhs, &x); - rhs = add_fp_bn254(&rhs, &E_B); + let lhs = square_fp_bn254( + &y, + #[cfg(feature = "hints")] + hints, + ); + let mut rhs = square_fp_bn254( + &x, + #[cfg(feature = "hints")] + hints, + ); + rhs = mul_fp_bn254( + &rhs, + &x, + #[cfg(feature = "hints")] + hints, + ); + rhs = add_fp_bn254( + &rhs, + &E_B, + #[cfg(feature = "hints")] + hints, + ); eq(&lhs, &rhs) } /// Converts a point `p` on the BN254 curve from Jacobian coordinates to affine coordinates -pub fn to_affine_bn254(p: &[u64; 12]) -> [u64; 8] { +pub fn to_affine_bn254(p: &[u64; 12], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 8] { let z: [u64; 4] = p[8..12].try_into().unwrap(); if z == [0u64; 4] { @@ -39,18 +57,44 @@ pub fn to_affine_bn254(p: &[u64; 12]) -> [u64; 8] { let x: [u64; 4] = p[0..4].try_into().unwrap(); let y: [u64; 4] = p[4..8].try_into().unwrap(); - let zinv = inv_fp_bn254(&z); - let zinv_sq = square_fp_bn254(&zinv); - - let x_res = mul_fp_bn254(&x, &zinv_sq); - let mut y_res = mul_fp_bn254(&y, &zinv_sq); - y_res = mul_fp_bn254(&y_res, &zinv); - + let zinv = inv_fp_bn254( + &z, + #[cfg(feature = "hints")] + hints, + ); + let zinv_sq = square_fp_bn254( + &zinv, + #[cfg(feature = "hints")] + hints, + ); + + let x_res = mul_fp_bn254( + &x, + &zinv_sq, + #[cfg(feature = "hints")] + hints, + ); + let mut y_res = mul_fp_bn254( + &y, + &zinv_sq, + #[cfg(feature = "hints")] + hints, + ); + y_res = mul_fp_bn254( + &y_res, + &zinv, + #[cfg(feature = "hints")] + hints, + ); [x_res[0], x_res[1], x_res[2], x_res[3], y_res[0], y_res[1], y_res[2], y_res[3]] } /// Adds two points `p1` and `p2` on the BN254 curve -pub fn add_bn254(p1: &[u64; 8], p2: &[u64; 8]) -> [u64; 8] { +pub fn add_bn254( + p1: &[u64; 8], + p2: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 8] { if *p1 == IDENTITY_G1 { return *p2; } else if *p2 == IDENTITY_G1 { @@ -67,7 +111,11 @@ pub fn add_bn254(p1: &[u64; 8], p2: &[u64; 8]) -> [u64; 8] { // Is y1 == y2? if eq(&y1, &y2) { // Compute the doubling - return dbl_bn254(p1); + return dbl_bn254( + p1, + #[cfg(feature = "hints")] + hints, + ); } else { // Return 𝒪 return IDENTITY_G1; @@ -82,7 +130,11 @@ pub fn add_bn254(p1: &[u64; 8], p2: &[u64; 8]) -> [u64; 8] { // Call the syscall to add the two points let mut params = SyscallBn254CurveAddParams { p1: &mut p1, p2: &p2 }; - syscall_bn254_curve_add(&mut params); + syscall_bn254_curve_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); // Convert the result back to a single array let x3 = params.p1.x; @@ -90,14 +142,22 @@ pub fn add_bn254(p1: &[u64; 8], p2: &[u64; 8]) -> [u64; 8] { [x3[0], x3[1], x3[2], x3[3], y3[0], y3[1], y3[2], y3[3]] } -pub fn dbl_bn254(p: &[u64; 8]) -> [u64; 8] { +pub fn dbl_bn254(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 8] { let mut p1 = SyscallPoint256 { x: p[0..4].try_into().unwrap(), y: p[4..8].try_into().unwrap() }; - syscall_bn254_curve_dbl(&mut p1); + syscall_bn254_curve_dbl( + &mut p1, + #[cfg(feature = "hints")] + hints, + ); [p1.x[0], p1.x[1], p1.x[2], p1.x[3], p1.y[0], p1.y[1], p1.y[2], p1.y[3]] } /// Multiplies a point `p` on the BN254 curve by a scalar `k` on the BN254 scalar field -pub fn mul_bn254(p: &[u64; 8], k: &[u64; 4]) -> [u64; 8] { +pub fn mul_bn254( + p: &[u64; 8], + k: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 8] { if *p == IDENTITY_G1 { return IDENTITY_G1; } @@ -114,7 +174,11 @@ pub fn mul_bn254(p: &[u64; 8], k: &[u64; 4]) -> [u64; 8] { } [2, 0, 0, 0] => { // Return 2p - return dbl_bn254(p); + return dbl_bn254( + p, + #[cfg(feature = "hints")] + hints, + ); } _ => {} } @@ -123,7 +187,12 @@ pub fn mul_bn254(p: &[u64; 8], k: &[u64; 4]) -> [u64; 8] { // Hint the length the binary representations of k // We will verify the output by recomposing k // Moreover, we should check that the first received bit is 1 - let (max_limb, max_bit) = fcall_msb_pos_256(k, &[0, 0, 0, 0]); + let (max_limb, max_bit) = fcall_msb_pos_256( + k, + &[0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); // Perform the loop, based on the binary representation of k @@ -156,13 +225,21 @@ pub fn mul_bn254(p: &[u64; 8], k: &[u64; 4]) -> [u64; 8] { for i in (0..=limb).rev() { for j in (0..=bit).rev() { // Always double - syscall_bn254_curve_dbl(&mut q); + syscall_bn254_curve_dbl( + &mut q, + #[cfg(feature = "hints")] + hints, + ); // Get the next bit b of k. // If b == 1, we should add P to Q, otherwise start the next iteration if ((k[i] >> j) & 1) == 1 { let mut params = SyscallBn254CurveAddParams { p1: &mut q, p2: &p }; - syscall_bn254_curve_add(&mut params); + syscall_bn254_curve_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); // Reconstruct k k_rec[i] |= 1 << j; @@ -183,18 +260,33 @@ pub fn mul_bn254(p: &[u64; 8], k: &[u64; 4]) -> [u64; 8] { /// # Safety /// `p` must point to a valid `[u64; 8]` (64 bytes, affine G1 point). #[no_mangle] -pub unsafe extern "C" fn is_on_curve_bn254_c(p_ptr: *const u64) -> bool { +pub unsafe extern "C" fn is_on_curve_bn254_c( + p_ptr: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let p = unsafe { &*(p_ptr as *const [u64; 8]) }; - is_on_curve_bn254(p) + is_on_curve_bn254( + p, + #[cfg(feature = "hints")] + hints, + ) } /// # Safety /// - `p` must point to a valid `[u64; 12]` (96 bytes, Jacobian G1 point). /// - `out` must point to a valid `[u64; 8]` (64 bytes) writable buffer. #[no_mangle] -pub unsafe extern "C" fn to_affine_bn254_c(p_ptr: *const u64, out_ptr: *mut u64) -> bool { +pub unsafe extern "C" fn to_affine_bn254_c( + p_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let p = unsafe { &*(p_ptr as *const [u64; 12]) }; - let result = to_affine_bn254(p); + let result = to_affine_bn254( + p, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = result[0]; *out_ptr.add(1) = result[1]; @@ -217,11 +309,16 @@ pub unsafe extern "C" fn add_bn254_c( p1_ptr: *const u64, p2_ptr: *const u64, out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> bool { let p1 = unsafe { &*(p1_ptr as *const [u64; 8]) }; let p2 = unsafe { &*(p2_ptr as *const [u64; 8]) }; - let result = add_bn254(p1, p2); - + let result = add_bn254( + p1, + p2, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = result[0]; *out_ptr.add(1) = result[1]; *out_ptr.add(2) = result[2]; @@ -243,11 +340,16 @@ pub unsafe extern "C" fn mul_bn254_c( p_ptr: *const u64, k_ptr: *const u64, out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> bool { let p = unsafe { &*(p_ptr as *const [u64; 8]) }; let k = unsafe { &*(k_ptr as *const [u64; 4]) }; - let result = mul_bn254(p, k); - + let result = mul_bn254( + p, + k, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = result[0]; *out_ptr.add(1) = result[1]; *out_ptr.add(2) = result[2]; diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/cyclotomic.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/cyclotomic.rs index b54e3a55c..771a94379 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/cyclotomic.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/cyclotomic.rs @@ -47,7 +47,10 @@ pub fn compress_cyclo_bn254(a: &[u64; 48]) -> [u64; 32] { /// **NOTE**: If the input is not of the form C(a), where a ∈ GΦ6(p²), then the compression-decompression /// technique is not well defined. This means that D(C(a)) != a. #[inline] -pub fn decompress_cyclo_bn254(a: &[u64; 32]) -> [u64; 48] { +pub fn decompress_cyclo_bn254( + a: &[u64; 32], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { let a2: &[u64; 8] = &a[0..8].try_into().unwrap(); let a3: &[u64; 8] = &a[8..16].try_into().unwrap(); let a4: &[u64; 8] = &a[16..24].try_into().unwrap(); @@ -55,39 +58,179 @@ pub fn decompress_cyclo_bn254(a: &[u64; 32]) -> [u64; 48] { let (a0, a1) = if eq(a2, &[0, 0, 0, 0, 0, 0, 0, 0]) { // a1 = (2·a4·a5)/a3 - let a3_inv = inv_fp2_bn254(a3); - let mut a1 = mul_fp2_bn254(a4, a5); - a1 = dbl_fp2_bn254(&a1); - a1 = mul_fp2_bn254(&a1, &a3_inv); + let a3_inv = inv_fp2_bn254( + a3, + #[cfg(feature = "hints")] + hints, + ); + let mut a1 = mul_fp2_bn254( + a4, + a5, + #[cfg(feature = "hints")] + hints, + ); + a1 = dbl_fp2_bn254( + &a1, + #[cfg(feature = "hints")] + hints, + ); + a1 = mul_fp2_bn254( + &a1, + &a3_inv, + #[cfg(feature = "hints")] + hints, + ); // a0 = (2·a1² - 3·a3·a4)(9+u) + 1 - let a3a4 = mul_fp2_bn254(a3, a4); - let mut a0 = square_fp2_bn254(&a1); - a0 = dbl_fp2_bn254(&a0); - a0 = sub_fp2_bn254(&a0, &scalar_mul_fp2_bn254(&a3a4, &[3, 0, 0, 0])); - a0 = mul_fp2_bn254(&a0, &[9, 0, 0, 0, 1, 0, 0, 0]); - a0 = add_fp2_bn254(&a0, &[1, 0, 0, 0, 0, 0, 0, 0]); + let a3a4 = mul_fp2_bn254( + a3, + a4, + #[cfg(feature = "hints")] + hints, + ); + let mut a0 = square_fp2_bn254( + &a1, + #[cfg(feature = "hints")] + hints, + ); + a0 = dbl_fp2_bn254( + &a0, + #[cfg(feature = "hints")] + hints, + ); + a0 = sub_fp2_bn254( + &a0, + &scalar_mul_fp2_bn254( + &a3a4, + &[3, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + a0 = mul_fp2_bn254( + &a0, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + a0 = add_fp2_bn254( + &a0, + &[1, 0, 0, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); (a0, a1) } else { // a1 = (a5²·(9+u) + 3·a4² - 2·a3)/(4·a2) - let a2_inv = inv_fp2_bn254(&scalar_mul_fp2_bn254(a2, &[4, 0, 0, 0])); - let a4_sq = square_fp2_bn254(a4); - let mut a1 = square_fp2_bn254(a5); - a1 = mul_fp2_bn254(&a1, &[9, 0, 0, 0, 1, 0, 0, 0]); - a1 = add_fp2_bn254(&a1, &scalar_mul_fp2_bn254(&a4_sq, &[3, 0, 0, 0])); - a1 = sub_fp2_bn254(&a1, &dbl_fp2_bn254(a3)); - a1 = mul_fp2_bn254(&a1, &a2_inv); + let a2_inv = inv_fp2_bn254( + &scalar_mul_fp2_bn254( + a2, + &[4, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + let a4_sq = square_fp2_bn254( + a4, + #[cfg(feature = "hints")] + hints, + ); + let mut a1 = square_fp2_bn254( + a5, + #[cfg(feature = "hints")] + hints, + ); + a1 = mul_fp2_bn254( + &a1, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + a1 = add_fp2_bn254( + &a1, + &scalar_mul_fp2_bn254( + &a4_sq, + &[3, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + a1 = sub_fp2_bn254( + &a1, + &dbl_fp2_bn254( + a3, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + a1 = mul_fp2_bn254( + &a1, + &a2_inv, + #[cfg(feature = "hints")] + hints, + ); // a0 = (2·a1² + a2·a5 - 3·a3·a4)(9+u) + 1 - let a3a4 = mul_fp2_bn254(a3, a4); - let a2a5 = mul_fp2_bn254(a2, a5); - let mut a0 = square_fp2_bn254(&a1); - a0 = dbl_fp2_bn254(&a0); - a0 = add_fp2_bn254(&a0, &a2a5); - a0 = sub_fp2_bn254(&a0, &scalar_mul_fp2_bn254(&a3a4, &[3, 0, 0, 0])); - a0 = mul_fp2_bn254(&a0, &[9, 0, 0, 0, 1, 0, 0, 0]); - a0 = add_fp2_bn254(&a0, &[1, 0, 0, 0, 0, 0, 0, 0]); + let a3a4 = mul_fp2_bn254( + a3, + a4, + #[cfg(feature = "hints")] + hints, + ); + let a2a5 = mul_fp2_bn254( + a2, + a5, + #[cfg(feature = "hints")] + hints, + ); + let mut a0 = square_fp2_bn254( + &a1, + #[cfg(feature = "hints")] + hints, + ); + a0 = dbl_fp2_bn254( + &a0, + #[cfg(feature = "hints")] + hints, + ); + a0 = add_fp2_bn254( + &a0, + &a2a5, + #[cfg(feature = "hints")] + hints, + ); + a0 = sub_fp2_bn254( + &a0, + &scalar_mul_fp2_bn254( + &a3a4, + &[3, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + a0 = mul_fp2_bn254( + &a0, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + a0 = add_fp2_bn254( + &a0, + &[1, 0, 0, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); (a0, a1) }; @@ -117,46 +260,180 @@ pub fn decompress_cyclo_bn254(a: &[u64; 32]) -> [u64; 48] { // - B45 = a4·a5 // /// **NOTE**: The output is not guaranteed to be in GΦ6(p²), if the input isn't. -pub fn square_cyclo_bn254(a: &[u64; 32]) -> [u64; 32] { +pub fn square_cyclo_bn254( + a: &[u64; 32], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 32] { let a2: &[u64; 8] = &a[0..8].try_into().unwrap(); let a3: &[u64; 8] = &a[8..16].try_into().unwrap(); let a4: &[u64; 8] = &a[16..24].try_into().unwrap(); let a5: &[u64; 8] = &a[24..32].try_into().unwrap(); // B23 = a2·a3, B45 = a4·a5 - let b23 = mul_fp2_bn254(a2, a3); - let b45 = mul_fp2_bn254(a4, a5); + let b23 = mul_fp2_bn254( + a2, + a3, + #[cfg(feature = "hints")] + hints, + ); + let b45 = mul_fp2_bn254( + a4, + a5, + #[cfg(feature = "hints")] + hints, + ); // A23 = (a2 + a3)·(a2 + (9+u)·a3) - let a3xi = mul_fp2_bn254(a3, &[9, 0, 0, 0, 1, 0, 0, 0]); - let a23 = mul_fp2_bn254(&add_fp2_bn254(a2, a3), &add_fp2_bn254(a2, &a3xi)); + let a3xi = mul_fp2_bn254( + a3, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + let a23 = mul_fp2_bn254( + &add_fp2_bn254( + a2, + a3, + #[cfg(feature = "hints")] + hints, + ), + &add_fp2_bn254( + a2, + &a3xi, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // A45 = (a4 + a5)·(a4 + (9+u)·a5) - let a5xi = mul_fp2_bn254(a5, &[9, 0, 0, 0, 1, 0, 0, 0]); - let a45 = mul_fp2_bn254(&add_fp2_bn254(a4, a5), &add_fp2_bn254(a4, &a5xi)); + let a5xi = mul_fp2_bn254( + a5, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + let a45 = mul_fp2_bn254( + &add_fp2_bn254( + a4, + a5, + #[cfg(feature = "hints")] + hints, + ), + &add_fp2_bn254( + a4, + &a5xi, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // b2 = 2(a2 + 3·(9+u)·B45) - let mut b2 = mul_fp2_bn254(&b45, &[9, 0, 0, 0, 1, 0, 0, 0]); - b2 = scalar_mul_fp2_bn254(&b2, &[3, 0, 0, 0]); - b2 = add_fp2_bn254(a2, &b2); - b2 = dbl_fp2_bn254(&b2); + let mut b2 = mul_fp2_bn254( + &b45, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b2 = scalar_mul_fp2_bn254( + &b2, + &[3, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b2 = add_fp2_bn254( + a2, + &b2, + #[cfg(feature = "hints")] + hints, + ); + b2 = dbl_fp2_bn254( + &b2, + #[cfg(feature = "hints")] + hints, + ); // b3 = 3·(A45 - (10+u)·B45) - 2·a3 - let mut b3 = mul_fp2_bn254(&b45, &[10, 0, 0, 0, 1, 0, 0, 0]); - b3 = sub_fp2_bn254(&a45, &b3); - b3 = scalar_mul_fp2_bn254(&b3, &[3, 0, 0, 0]); - b3 = sub_fp2_bn254(&b3, &dbl_fp2_bn254(a3)); + let mut b3 = mul_fp2_bn254( + &b45, + &[10, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b3 = sub_fp2_bn254( + &a45, + &b3, + #[cfg(feature = "hints")] + hints, + ); + b3 = scalar_mul_fp2_bn254( + &b3, + &[3, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b3 = sub_fp2_bn254( + &b3, + &dbl_fp2_bn254( + a3, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // b4 = 3·(A23 - (10+u)·B23) - 2·a4 - let mut b4 = mul_fp2_bn254(&b23, &[10, 0, 0, 0, 1, 0, 0, 0]); - b4 = sub_fp2_bn254(&a23, &b4); - b4 = scalar_mul_fp2_bn254(&b4, &[3, 0, 0, 0]); - b4 = sub_fp2_bn254(&b4, &dbl_fp2_bn254(a4)); + let mut b4 = mul_fp2_bn254( + &b23, + &[10, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b4 = sub_fp2_bn254( + &a23, + &b4, + #[cfg(feature = "hints")] + hints, + ); + b4 = scalar_mul_fp2_bn254( + &b4, + &[3, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b4 = sub_fp2_bn254( + &b4, + &dbl_fp2_bn254( + a4, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // b5 = 2·(a5 + 3·B23) - let mut b5 = scalar_mul_fp2_bn254(&b23, &[3, 0, 0, 0]); - b5 = add_fp2_bn254(a5, &b5); - b5 = dbl_fp2_bn254(&b5); + let mut b5 = scalar_mul_fp2_bn254( + &b23, + &[3, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + b5 = add_fp2_bn254( + a5, + &b5, + #[cfg(feature = "hints")] + hints, + ); + b5 = dbl_fp2_bn254( + &b5, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 32]; result[0..8].copy_from_slice(&b2); @@ -173,7 +450,10 @@ pub fn square_cyclo_bn254(a: &[u64; 32]) -> [u64; 32] { // out: a^x = (a0 + a4·v + a3·v²) + (a2 + a1·v + a5·v²)·w ∈ ∈ GΦ6(p²) // /// **NOTE**: The output is not guaranteed to be in GΦ6(p²), if the input isn't. -pub fn exp_by_x_cyclo_bn254(a: &[u64; 48]) -> [u64; 48] { +pub fn exp_by_x_cyclo_bn254( + a: &[u64; 48], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { // Binary representation of the exponent x = 4965661367192848881 in big-endian format const X_BIN_LE: [u8; 63] = [ 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, @@ -188,12 +468,25 @@ pub fn exp_by_x_cyclo_bn254(a: &[u64; 48]) -> [u64; 48] { let mut comp = compress_cyclo_bn254(a); for &bit in X_BIN_LE.iter().skip(1) { // We always square (in compressed form): C(c²) - comp = square_cyclo_bn254(&comp); + comp = square_cyclo_bn254( + &comp, + #[cfg(feature = "hints")] + hints, + ); if bit == 1 { // decompress and multiply - let decomp = decompress_cyclo_bn254(&comp); - result = mul_fp12_bn254(&result, &decomp); + let decomp = decompress_cyclo_bn254( + &comp, + #[cfg(feature = "hints")] + hints, + ); + result = mul_fp12_bn254( + &result, + &decomp, + #[cfg(feature = "hints")] + hints, + ); } } diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/final_exp.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/final_exp.rs index 8a6237807..451fee49a 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/final_exp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/final_exp.rs @@ -12,90 +12,249 @@ use super::{ // However, I dont think its a good idea in general to optimize verification "at all costs". /// Given f ∈ Fp12*, computes f^((p¹²-1)/r) ∈ Fp12* -pub fn final_exp_bn254(f: &[u64; 48]) -> [u64; 48] { +pub fn final_exp_bn254(f: &[u64; 48], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 48] { ////////////////// // The easy part: exp by (p^6-1)(p^2+1) ////////////////// // f^(p^6-1) = f̅·f⁻¹ - let f_conj = conjugate_fp12_bn254(f); - let f_inv = inv_fp12_bn254(f); - let easy1 = mul_fp12_bn254(&f_conj, &f_inv); + let f_conj = conjugate_fp12_bn254( + f, + #[cfg(feature = "hints")] + hints, + ); + let f_inv = inv_fp12_bn254( + f, + #[cfg(feature = "hints")] + hints, + ); + let easy1 = mul_fp12_bn254( + &f_conj, + &f_inv, + #[cfg(feature = "hints")] + hints, + ); // easy1^(p²-1) = easy1^p²·easy1 - let mut m = frobenius2_fp12_bn254(&easy1); - m = mul_fp12_bn254(&m, &easy1); + let mut m = frobenius2_fp12_bn254( + &easy1, + #[cfg(feature = "hints")] + hints, + ); + m = mul_fp12_bn254( + &m, + &easy1, + #[cfg(feature = "hints")] + hints, + ); ////////////////// // The hard part: exp by (p⁴-p²+1)/r ////////////////// // m^x, (m^x)^x, (m^{x²})^x - let mx = exp_by_x_cyclo_bn254(&m); - let mxx = exp_by_x_cyclo_bn254(&mx); - let mxxx = exp_by_x_cyclo_bn254(&mxx); + let mx = exp_by_x_cyclo_bn254( + &m, + #[cfg(feature = "hints")] + hints, + ); + let mxx = exp_by_x_cyclo_bn254( + &mx, + #[cfg(feature = "hints")] + hints, + ); + let mxxx = exp_by_x_cyclo_bn254( + &mxx, + #[cfg(feature = "hints")] + hints, + ); // m^p, m^p², m^p³, (m^x)^p, (m^x²)^p, (m^x³)^p, (m^x²)^p² - let mp = frobenius1_fp12_bn254(&m); - let mpp = frobenius2_fp12_bn254(&m); - let mppp = frobenius3_fp12_bn254(&m); - let mxp = frobenius1_fp12_bn254(&mx); - let mxxp = frobenius1_fp12_bn254(&mxx); - let mxxxp = frobenius1_fp12_bn254(&mxxx); - let mxxpp = frobenius2_fp12_bn254(&mxx); + let mp = frobenius1_fp12_bn254( + &m, + #[cfg(feature = "hints")] + hints, + ); + let mpp = frobenius2_fp12_bn254( + &m, + #[cfg(feature = "hints")] + hints, + ); + let mppp = frobenius3_fp12_bn254( + &m, + #[cfg(feature = "hints")] + hints, + ); + let mxp = frobenius1_fp12_bn254( + &mx, + #[cfg(feature = "hints")] + hints, + ); + let mxxp = frobenius1_fp12_bn254( + &mxx, + #[cfg(feature = "hints")] + hints, + ); + let mxxxp = frobenius1_fp12_bn254( + &mxxx, + #[cfg(feature = "hints")] + hints, + ); + let mxxpp = frobenius2_fp12_bn254( + &mxx, + #[cfg(feature = "hints")] + hints, + ); // y1 = m^p·m^p²·m^p³ - let mut y1 = mul_fp12_bn254(&mp, &mpp); - y1 = mul_fp12_bn254(&y1, &mppp); + let mut y1 = mul_fp12_bn254( + &mp, + &mpp, + #[cfg(feature = "hints")] + hints, + ); + y1 = mul_fp12_bn254( + &y1, + &mppp, + #[cfg(feature = "hints")] + hints, + ); // y2 = m̅ - let y2 = conjugate_fp12_bn254(&m); + let y2 = conjugate_fp12_bn254( + &m, + #[cfg(feature = "hints")] + hints, + ); // y3 = (m^x²)^p² (already done) // y4 = \bar{(m^x)^p} - let y4 = conjugate_fp12_bn254(&mxp); + let y4 = conjugate_fp12_bn254( + &mxp, + #[cfg(feature = "hints")] + hints, + ); // y5 = \bar{m^x·(m^x²)^p} - let mut y5 = mul_fp12_bn254(&mx, &mxxp); - y5 = conjugate_fp12_bn254(&y5); - + let mut y5 = mul_fp12_bn254( + &mx, + &mxxp, + #[cfg(feature = "hints")] + hints, + ); + y5 = conjugate_fp12_bn254( + &y5, + #[cfg(feature = "hints")] + hints, + ); // y6 = \bar{m^x²} - let y6 = conjugate_fp12_bn254(&mxx); + let y6 = conjugate_fp12_bn254( + &mxx, + #[cfg(feature = "hints")] + hints, + ); // y7 = \bar{m^x³·(m^x³)^p} - let mut y7 = mul_fp12_bn254(&mxxx, &mxxxp); - y7 = conjugate_fp12_bn254(&y7); - + let mut y7 = mul_fp12_bn254( + &mxxx, + &mxxxp, + #[cfg(feature = "hints")] + hints, + ); + y7 = conjugate_fp12_bn254( + &y7, + #[cfg(feature = "hints")] + hints, + ); // Compute y1·y2²·y3⁶·y4¹²·y5¹⁸·y6³⁰·y7³⁶ as follows // T11 = y7²·y5·y6 - let mut t11 = square_fp12_bn254(&y7); - t11 = mul_fp12_bn254(&t11, &y5); - t11 = mul_fp12_bn254(&t11, &y6); + let mut t11 = square_fp12_bn254( + &y7, + #[cfg(feature = "hints")] + hints, + ); + t11 = mul_fp12_bn254( + &t11, + &y5, + #[cfg(feature = "hints")] + hints, + ); + t11 = mul_fp12_bn254( + &t11, + &y6, + #[cfg(feature = "hints")] + hints, + ); // T21 = T11·y4·y6 - let mut t21 = mul_fp12_bn254(&t11, &y4); - t21 = mul_fp12_bn254(&t21, &y6); + let mut t21 = mul_fp12_bn254( + &t11, + &y4, + #[cfg(feature = "hints")] + hints, + ); + t21 = mul_fp12_bn254( + &t21, + &y6, + #[cfg(feature = "hints")] + hints, + ); // T12 = T11·y3 - let t12 = mul_fp12_bn254(&t11, &mxxpp); - + let t12 = mul_fp12_bn254( + &t11, + &mxxpp, + #[cfg(feature = "hints")] + hints, + ); // T22 = T21²·T12 - let mut t22 = square_fp12_bn254(&t21); - t22 = mul_fp12_bn254(&t22, &t12); + let mut t22 = square_fp12_bn254( + &t21, + #[cfg(feature = "hints")] + hints, + ); + t22 = mul_fp12_bn254( + &t22, + &t12, + #[cfg(feature = "hints")] + hints, + ); // T23 = T22² - let t23 = square_fp12_bn254(&t22); + let t23 = square_fp12_bn254( + &t22, + #[cfg(feature = "hints")] + hints, + ); // T24 = T23·y1 - let t24 = mul_fp12_bn254(&t23, &y1); + let t24 = mul_fp12_bn254( + &t23, + &y1, + #[cfg(feature = "hints")] + hints, + ); // T13 = T23·y2 - let t13 = mul_fp12_bn254(&t23, &y2); + let t13 = mul_fp12_bn254( + &t23, + &y2, + #[cfg(feature = "hints")] + hints, + ); // T14 = T13²·T24 - let mut t14 = square_fp12_bn254(&t13); - t14 = mul_fp12_bn254(&t14, &t24); - + let mut t14 = square_fp12_bn254( + &t13, + #[cfg(feature = "hints")] + hints, + ); + t14 = mul_fp12_bn254( + &t14, + &t24, + #[cfg(feature = "hints")] + hints, + ); t14 } diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs index c7323346b..b35e4732b 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs @@ -9,17 +9,25 @@ use super::constants::{P, P_MINUS_ONE}; /// Addition in the base field of the BN254 curve #[inline] -pub fn add_fp_bn254(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn add_fp_bn254( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·1 + y let mut params = SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Negation in the base field of the BN254 curve #[inline] -pub fn neg_fp_bn254(x: &[u64; 4]) -> [u64; 4] { +pub fn neg_fp_bn254(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { // x·(-1) + 0 let mut params = SyscallArith256ModParams { a: x, @@ -28,33 +36,49 @@ pub fn neg_fp_bn254(x: &[u64; 4]) -> [u64; 4] { module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Multiplication in the base field of the BN254 curve #[inline] -pub fn mul_fp_bn254(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn mul_fp_bn254( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·y + 0 let mut params = SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Squaring in the base field of the BN254 curve #[inline] -pub fn square_fp_bn254(x: &[u64; 4]) -> [u64; 4] { +pub fn square_fp_bn254(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { // x·x + 0 let mut params = SyscallArith256ModParams { a: x, b: x, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } /// Inversion in the base field of the BN254 curve #[inline] -pub fn inv_fp_bn254(x: &[u64; 4]) -> [u64; 4] { +pub fn inv_fp_bn254(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { // if x == 0, return 0 if eq(x, &[0, 0, 0, 0]) { return [0, 0, 0, 0]; @@ -64,7 +88,11 @@ pub fn inv_fp_bn254(x: &[u64; 4]) -> [u64; 4] { // Remember that an element y ∈ Fp is the inverse of x ∈ Fp if and only if x·y = 1 in Fp // We will therefore hint the inverse y and check the product with x is 1 - let inv = fcall_bn254_fp_inv(x); + let inv = fcall_bn254_fp_inv( + x, + #[cfg(feature = "hints")] + hints, + ); // x·y + 0 let mut params = SyscallArith256ModParams { @@ -74,7 +102,11 @@ pub fn inv_fp_bn254(x: &[u64; 4]) -> [u64; 4] { module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); assert_eq!(*params.d, [1, 0, 0, 0]); inv diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/fp12.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/fp12.rs index 8fea04eb7..b4b76bb9f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/fp12.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/fp12.rs @@ -24,23 +24,71 @@ use super::{ // - c1 = a1·b1 + a2·b2·v // - c2 = (a1+a2)·(b1+b2) - a1·b1 - a2·b2 #[inline] -pub fn mul_fp12_bn254(a: &[u64; 48], b: &[u64; 48]) -> [u64; 48] { +pub fn mul_fp12_bn254( + a: &[u64; 48], + b: &[u64; 48], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { let a1 = &a[0..24].try_into().unwrap(); let a2 = &a[24..48].try_into().unwrap(); let b1 = &b[0..24].try_into().unwrap(); let b2 = &b[24..48].try_into().unwrap(); - let a1b1 = mul_fp6_bn254(a1, b1); - let a2b2 = mul_fp6_bn254(a2, b2); - - let a2b2v = sparse_mula_fp6_bn254(&a2b2, &[1, 0, 0, 0, 0, 0, 0, 0]); - let c1 = add_fp6_bn254(&a1b1, &a2b2v); - - let a1_plus_a2 = add_fp6_bn254(a1, a2); - let b1_plus_b2 = add_fp6_bn254(b1, b2); - let mut c2 = mul_fp6_bn254(&a1_plus_a2, &b1_plus_b2); - c2 = sub_fp6_bn254(&c2, &a1b1); - c2 = sub_fp6_bn254(&c2, &a2b2); + let a1b1 = mul_fp6_bn254( + a1, + b1, + #[cfg(feature = "hints")] + hints, + ); + let a2b2 = mul_fp6_bn254( + a2, + b2, + #[cfg(feature = "hints")] + hints, + ); + + let a2b2v = sparse_mula_fp6_bn254( + &a2b2, + &[1, 0, 0, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + let c1 = add_fp6_bn254( + &a1b1, + &a2b2v, + #[cfg(feature = "hints")] + hints, + ); + let a1_plus_a2 = add_fp6_bn254( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ); + let b1_plus_b2 = add_fp6_bn254( + b1, + b2, + #[cfg(feature = "hints")] + hints, + ); + let mut c2 = mul_fp6_bn254( + &a1_plus_a2, + &b1_plus_b2, + #[cfg(feature = "hints")] + hints, + ); + c2 = sub_fp6_bn254( + &c2, + &a1b1, + #[cfg(feature = "hints")] + hints, + ); + c2 = sub_fp6_bn254( + &c2, + &a2b2, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 48]; result[0..24].copy_from_slice(&c1); @@ -55,15 +103,39 @@ pub fn mul_fp12_bn254(a: &[u64; 48], b: &[u64; 48]) -> [u64; 48] { // - c1 = a1 + a2·(b21·v + b22·v²) // - c2 = a2 + a1·(b21 + b22·v) #[inline] -pub fn sparse_mul_fp12_bn254(a: &[u64; 48], b: &[u64; 16]) -> [u64; 48] { +pub fn sparse_mul_fp12_bn254( + a: &[u64; 48], + b: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { let a1 = &a[0..24].try_into().unwrap(); let a2 = &a[24..48].try_into().unwrap(); - let mut c1 = sparse_mulc_fp6_bn254(a2, b); - c1 = add_fp6_bn254(&c1, a1); - - let mut c2 = sparse_mulb_fp6_bn254(a1, b); - c2 = add_fp6_bn254(&c2, a2); + let mut c1 = sparse_mulc_fp6_bn254( + a2, + b, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp6_bn254( + &c1, + a1, + #[cfg(feature = "hints")] + hints, + ); + + let mut c2 = sparse_mulb_fp6_bn254( + a1, + b, + #[cfg(feature = "hints")] + hints, + ); + c2 = add_fp6_bn254( + &c2, + a2, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 48]; result[0..24].copy_from_slice(&c1); @@ -78,24 +150,70 @@ pub fn sparse_mul_fp12_bn254(a: &[u64; 48], b: &[u64; 16]) -> [u64; 48] { // - c1 = (a1-a2)·(a1-a2·v) + a1·a2 + a1·a2·v // - c2 = 2·a1·a2 #[inline] -pub fn square_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { +pub fn square_fp12_bn254( + a: &[u64; 48], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { let a1 = &a[0..24].try_into().unwrap(); let a2 = &a[24..48].try_into().unwrap(); // a1·a2, a2·v, a1·a2·v - let a1a2 = mul_fp6_bn254(a1, a2); - let a2v = sparse_mula_fp6_bn254(a2, &[1, 0, 0, 0, 0, 0, 0, 0]); - let a1a2v = sparse_mula_fp6_bn254(&a1a2, &[1, 0, 0, 0, 0, 0, 0, 0]); + let a1a2 = mul_fp6_bn254( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ); + let a2v = sparse_mula_fp6_bn254( + a2, + &[1, 0, 0, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + let a1a2v = sparse_mula_fp6_bn254( + &a1a2, + &[1, 0, 0, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); // c1 - let a1_minus_a2 = sub_fp6_bn254(a1, a2); - let a1_minus_a2v = sub_fp6_bn254(a1, &a2v); - let mut c1 = mul_fp6_bn254(&a1_minus_a2, &a1_minus_a2v); - c1 = add_fp6_bn254(&c1, &a1a2); - c1 = add_fp6_bn254(&c1, &a1a2v); - + let a1_minus_a2 = sub_fp6_bn254( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ); + let a1_minus_a2v = sub_fp6_bn254( + a1, + &a2v, + #[cfg(feature = "hints")] + hints, + ); + let mut c1 = mul_fp6_bn254( + &a1_minus_a2, + &a1_minus_a2v, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp6_bn254( + &c1, + &a1a2, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp6_bn254( + &c1, + &a1a2v, + #[cfg(feature = "hints")] + hints, + ); // c2 - let c2 = dbl_fp6_bn254(&a1a2); + let c2 = dbl_fp6_bn254( + &a1a2, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 48]; result[0..24].copy_from_slice(&c1); @@ -110,19 +228,55 @@ pub fn square_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { // - c1 = a1·(a1² - a2²·v)⁻¹ // - c2 = -a2·(a1² - a2²·v)⁻¹ #[inline] -pub fn inv_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { +pub fn inv_fp12_bn254(a: &[u64; 48], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 48] { let a1 = &a[0..24].try_into().unwrap(); let a2 = &a[24..48].try_into().unwrap(); - let a1_sq = square_fp6_bn254(a1); - let a2_sq = square_fp6_bn254(a2); - - let a2_sqv = sparse_mula_fp6_bn254(&a2_sq, &[1, 0, 0, 0, 0, 0, 0, 0]); - let a1_sq_minus_a2_sqv = sub_fp6_bn254(&a1_sq, &a2_sqv); - let inv = inv_fp6_bn254(&a1_sq_minus_a2_sqv); - - let c1 = mul_fp6_bn254(a1, &inv); - let c2 = neg_fp6_bn254(&mul_fp6_bn254(a2, &inv)); + let a1_sq = square_fp6_bn254( + a1, + #[cfg(feature = "hints")] + hints, + ); + let a2_sq = square_fp6_bn254( + a2, + #[cfg(feature = "hints")] + hints, + ); + + let a2_sqv = sparse_mula_fp6_bn254( + &a2_sq, + &[1, 0, 0, 0, 0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + let a1_sq_minus_a2_sqv = sub_fp6_bn254( + &a1_sq, + &a2_sqv, + #[cfg(feature = "hints")] + hints, + ); + let inv = inv_fp6_bn254( + &a1_sq_minus_a2_sqv, + #[cfg(feature = "hints")] + hints, + ); + + let c1 = mul_fp6_bn254( + a1, + &inv, + #[cfg(feature = "hints")] + hints, + ); + let c2 = neg_fp6_bn254( + &mul_fp6_bn254( + a2, + &inv, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 48]; result[0..24].copy_from_slice(&c1); @@ -132,10 +286,17 @@ pub fn inv_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { /// Conjugation in the degree 12 extension of the BN254 curve #[inline] -pub fn conjugate_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { +pub fn conjugate_fp12_bn254( + a: &[u64; 48], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { let mut result = [0; 48]; result[0..24].copy_from_slice(&a[0..24]); - result[24..48].copy_from_slice(&neg_fp6_bn254(&a[24..48].try_into().unwrap())); + result[24..48].copy_from_slice(&neg_fp6_bn254( + &a[24..48].try_into().unwrap(), + #[cfg(feature = "hints")] + hints, + )); result } @@ -146,7 +307,10 @@ pub fn conjugate_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { // - c1 = a̅11 + a̅12·γ12·v + a̅13·γ14·v² // - c2 = a̅21·γ11 + a̅22·γ13·v + a̅23·γ15·v² #[inline] -pub fn frobenius1_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { +pub fn frobenius1_fp12_bn254( + a: &[u64; 48], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { let a11 = &a[0..8].try_into().unwrap(); let a12 = &a[8..16].try_into().unwrap(); let a13 = &a[16..24].try_into().unwrap(); @@ -157,19 +321,68 @@ pub fn frobenius1_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { let mut result = [0; 48]; // c1 = a̅11 + a̅12·γ12·v + a̅13·γ14·v² - result[0..8].copy_from_slice(&conjugate_fp2_bn254(a11)); - let mut tmp = conjugate_fp2_bn254(a12); - result[8..16].copy_from_slice(&mul_fp2_bn254(&tmp, &FROBENIUS_GAMMA12)); - tmp = conjugate_fp2_bn254(a13); - result[16..24].copy_from_slice(&mul_fp2_bn254(&tmp, &FROBENIUS_GAMMA14)); + result[0..8].copy_from_slice(&conjugate_fp2_bn254( + a11, + #[cfg(feature = "hints")] + hints, + )); + let mut tmp = conjugate_fp2_bn254( + a12, + #[cfg(feature = "hints")] + hints, + ); + result[8..16].copy_from_slice(&mul_fp2_bn254( + &tmp, + &FROBENIUS_GAMMA12, + #[cfg(feature = "hints")] + hints, + )); + tmp = conjugate_fp2_bn254( + a13, + #[cfg(feature = "hints")] + hints, + ); + result[16..24].copy_from_slice(&mul_fp2_bn254( + &tmp, + &FROBENIUS_GAMMA14, + #[cfg(feature = "hints")] + hints, + )); // c2 = a̅21·γ11 + a̅22·γ13·v + a̅23·γ15·v² - tmp = conjugate_fp2_bn254(a21); - result[24..32].copy_from_slice(&mul_fp2_bn254(&tmp, &FROBENIUS_GAMMA11)); - tmp = conjugate_fp2_bn254(a22); - result[32..40].copy_from_slice(&mul_fp2_bn254(&tmp, &FROBENIUS_GAMMA13)); - tmp = conjugate_fp2_bn254(a23); - result[40..48].copy_from_slice(&mul_fp2_bn254(&tmp, &FROBENIUS_GAMMA15)); + tmp = conjugate_fp2_bn254( + a21, + #[cfg(feature = "hints")] + hints, + ); + result[24..32].copy_from_slice(&mul_fp2_bn254( + &tmp, + &FROBENIUS_GAMMA11, + #[cfg(feature = "hints")] + hints, + )); + tmp = conjugate_fp2_bn254( + a22, + #[cfg(feature = "hints")] + hints, + ); + result[32..40].copy_from_slice(&mul_fp2_bn254( + &tmp, + &FROBENIUS_GAMMA13, + #[cfg(feature = "hints")] + hints, + )); + tmp = conjugate_fp2_bn254( + a23, + #[cfg(feature = "hints")] + hints, + ); + result[40..48].copy_from_slice(&mul_fp2_bn254( + &tmp, + &FROBENIUS_GAMMA15, + #[cfg(feature = "hints")] + hints, + )); result } @@ -181,7 +394,10 @@ pub fn frobenius1_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { // - c1 = a11 + a12·γ22·v + a13·γ24·v² // - c2 = a21·γ21 + a22·γ23·v + a23·γ25·v² #[inline] -pub fn frobenius2_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { +pub fn frobenius2_fp12_bn254( + a: &[u64; 48], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { let a11: &[u64; 8] = &a[0..8].try_into().unwrap(); let a12 = &a[8..16].try_into().unwrap(); let a13 = &a[16..24].try_into().unwrap(); @@ -193,13 +409,38 @@ pub fn frobenius2_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { // c1 = a11 + a12·γ22·v + a13·γ24·v² result[0..8].copy_from_slice(a11); - result[8..16].copy_from_slice(&scalar_mul_fp2_bn254(a12, &FROBENIUS_GAMMA22)); - result[16..24].copy_from_slice(&scalar_mul_fp2_bn254(a13, &FROBENIUS_GAMMA24)); + result[8..16].copy_from_slice(&scalar_mul_fp2_bn254( + a12, + &FROBENIUS_GAMMA22, + #[cfg(feature = "hints")] + hints, + )); + result[16..24].copy_from_slice(&scalar_mul_fp2_bn254( + a13, + &FROBENIUS_GAMMA24, + #[cfg(feature = "hints")] + hints, + )); // c2 = a21·γ21 + a22·γ23·v + a23·γ25·v² - result[24..32].copy_from_slice(&scalar_mul_fp2_bn254(a21, &FROBENIUS_GAMMA21)); - result[32..40].copy_from_slice(&scalar_mul_fp2_bn254(a22, &FROBENIUS_GAMMA23)); - result[40..48].copy_from_slice(&scalar_mul_fp2_bn254(a23, &FROBENIUS_GAMMA25)); + result[24..32].copy_from_slice(&scalar_mul_fp2_bn254( + a21, + &FROBENIUS_GAMMA21, + #[cfg(feature = "hints")] + hints, + )); + result[32..40].copy_from_slice(&scalar_mul_fp2_bn254( + a22, + &FROBENIUS_GAMMA23, + #[cfg(feature = "hints")] + hints, + )); + result[40..48].copy_from_slice(&scalar_mul_fp2_bn254( + a23, + &FROBENIUS_GAMMA25, + #[cfg(feature = "hints")] + hints, + )); result } @@ -211,7 +452,10 @@ pub fn frobenius2_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { // - c1 = a̅11 + a̅12·γ32·v + a̅13·γ34·v² // - c2 = a̅21·γ31 + a̅22·γ33·v + a̅23·γ35·v² #[inline] -pub fn frobenius3_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { +pub fn frobenius3_fp12_bn254( + a: &[u64; 48], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { let a11 = &a[0..8].try_into().unwrap(); let a12 = &a[8..16].try_into().unwrap(); let a13 = &a[16..24].try_into().unwrap(); @@ -222,19 +466,68 @@ pub fn frobenius3_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { let mut result = [0; 48]; // c1 = a̅11 + a̅12·γ32·v + a̅13·γ34·v² - result[0..8].copy_from_slice(&conjugate_fp2_bn254(a11)); - let mut tmp = conjugate_fp2_bn254(a12); - result[8..16].copy_from_slice(&mul_fp2_bn254(&tmp, &FROBENIUS_GAMMA32)); - tmp = conjugate_fp2_bn254(a13); - result[16..24].copy_from_slice(&mul_fp2_bn254(&tmp, &FROBENIUS_GAMMA34)); + result[0..8].copy_from_slice(&conjugate_fp2_bn254( + a11, + #[cfg(feature = "hints")] + hints, + )); + let mut tmp = conjugate_fp2_bn254( + a12, + #[cfg(feature = "hints")] + hints, + ); + result[8..16].copy_from_slice(&mul_fp2_bn254( + &tmp, + &FROBENIUS_GAMMA32, + #[cfg(feature = "hints")] + hints, + )); + tmp = conjugate_fp2_bn254( + a13, + #[cfg(feature = "hints")] + hints, + ); + result[16..24].copy_from_slice(&mul_fp2_bn254( + &tmp, + &FROBENIUS_GAMMA34, + #[cfg(feature = "hints")] + hints, + )); // c2 = a̅21·γ31 + a̅22·γ33·v + a̅23·γ35·v² - tmp = conjugate_fp2_bn254(a21); - result[24..32].copy_from_slice(&mul_fp2_bn254(&tmp, &FROBENIUS_GAMMA31)); - tmp = conjugate_fp2_bn254(a22); - result[32..40].copy_from_slice(&mul_fp2_bn254(&tmp, &FROBENIUS_GAMMA33)); - tmp = conjugate_fp2_bn254(a23); - result[40..48].copy_from_slice(&mul_fp2_bn254(&tmp, &FROBENIUS_GAMMA35)); + tmp = conjugate_fp2_bn254( + a21, + #[cfg(feature = "hints")] + hints, + ); + result[24..32].copy_from_slice(&mul_fp2_bn254( + &tmp, + &FROBENIUS_GAMMA31, + #[cfg(feature = "hints")] + hints, + )); + tmp = conjugate_fp2_bn254( + a22, + #[cfg(feature = "hints")] + hints, + ); + result[32..40].copy_from_slice(&mul_fp2_bn254( + &tmp, + &FROBENIUS_GAMMA33, + #[cfg(feature = "hints")] + hints, + )); + tmp = conjugate_fp2_bn254( + a23, + #[cfg(feature = "hints")] + hints, + ); + result[40..48].copy_from_slice(&mul_fp2_bn254( + &tmp, + &FROBENIUS_GAMMA35, + #[cfg(feature = "hints")] + hints, + )); result } @@ -244,7 +537,11 @@ pub fn frobenius3_fp12_bn254(a: &[u64; 48]) -> [u64; 48] { // in: e, (a1 + a2·w) ∈ Fp12, where e ∈ [0,p¹²-2] ai ∈ Fp6 // out: (c1 + c2·w) = (a1 + a2·w)^e ∈ Fp12 #[inline] -pub fn exp_fp12_bn254(e: u64, a: &[u64; 48]) -> [u64; 48] { +pub fn exp_fp12_bn254( + e: u64, + a: &[u64; 48], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { let mut one = [0; 48]; one[0] = 1; if eq(a, &[0; 48]) { @@ -259,7 +556,12 @@ pub fn exp_fp12_bn254(e: u64, a: &[u64; 48]) -> [u64; 48] { return *a; } - let (_, max_bit) = fcall_msb_pos_256(&[e, 0, 0, 0], &[0, 0, 0, 0]); + let (_, max_bit) = fcall_msb_pos_256( + &[e, 0, 0, 0], + &[0, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); // Perform the loop, based on the binary representation of e @@ -275,12 +577,21 @@ pub fn exp_fp12_bn254(e: u64, a: &[u64; 48]) -> [u64; 48] { let _max_bit = max_bit as usize; for i in (0.._max_bit).rev() { // Always square - result = square_fp12_bn254(&result); + result = square_fp12_bn254( + &result, + #[cfg(feature = "hints")] + hints, + ); // Get the next bit b of e // If b == 1, we should multiply it by a, otherwise start the next iteration if ((e >> i) & 1) == 1 { - result = mul_fp12_bn254(&result, a); + result = mul_fp12_bn254( + &result, + a, + #[cfg(feature = "hints")] + hints, + ); // Reconstruct e e_rec |= 1 << i; diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/fp2.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/fp2.rs index b5e74bafd..d740ff46b 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/fp2.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/fp2.rs @@ -13,13 +13,21 @@ use super::constants::P_MINUS_ONE; /// Addition in the degree 2 extension of the BN254 curve #[inline] -pub fn add_fp2_bn254(a: &[u64; 8], b: &[u64; 8]) -> [u64; 8] { +pub fn add_fp2_bn254( + a: &[u64; 8], + b: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 8] { let mut f1 = SyscallComplex256 { x: a[0..4].try_into().unwrap(), y: a[4..8].try_into().unwrap() }; let f2 = SyscallComplex256 { x: b[0..4].try_into().unwrap(), y: b[4..8].try_into().unwrap() }; let mut params = SyscallBn254ComplexAddParams { f1: &mut f1, f2: &f2 }; - syscall_bn254_complex_add(&mut params); + syscall_bn254_complex_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); let res_x = params.f1.x; let res_y = params.f1.y; [res_x[0], res_x[1], res_x[2], res_x[3], res_y[0], res_y[1], res_y[2], res_y[3]] @@ -27,13 +35,17 @@ pub fn add_fp2_bn254(a: &[u64; 8], b: &[u64; 8]) -> [u64; 8] { /// Doubling in the degree 2 extension of the BN254 curve #[inline] -pub fn dbl_fp2_bn254(a: &[u64; 8]) -> [u64; 8] { +pub fn dbl_fp2_bn254(a: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 8] { let mut f1 = SyscallComplex256 { x: a[0..4].try_into().unwrap(), y: a[4..8].try_into().unwrap() }; let f2 = SyscallComplex256 { x: f1.x, y: f1.y }; let mut params = SyscallBn254ComplexAddParams { f1: &mut f1, f2: &f2 }; - syscall_bn254_complex_add(&mut params); + syscall_bn254_complex_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); let res_x = params.f1.x; let res_y = params.f1.y; [res_x[0], res_x[1], res_x[2], res_x[3], res_y[0], res_y[1], res_y[2], res_y[3]] @@ -41,13 +53,17 @@ pub fn dbl_fp2_bn254(a: &[u64; 8]) -> [u64; 8] { /// Negation in the degree 2 extension of the BN254 curve #[inline] -pub fn neg_fp2_bn254(a: &[u64; 8]) -> [u64; 8] { +pub fn neg_fp2_bn254(a: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 8] { let mut f1 = SyscallComplex256 { x: a[0..4].try_into().unwrap(), y: a[4..8].try_into().unwrap() }; let f2 = SyscallComplex256 { x: P_MINUS_ONE, y: [0u64; 4] }; let mut params = SyscallBn254ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bn254_complex_mul(&mut params); + syscall_bn254_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); let res_x = params.f1.x; let res_y = params.f1.y; [res_x[0], res_x[1], res_x[2], res_x[3], res_y[0], res_y[1], res_y[2], res_y[3]] @@ -55,13 +71,21 @@ pub fn neg_fp2_bn254(a: &[u64; 8]) -> [u64; 8] { /// Subtraction in the degree 2 extension of the BN254 curve #[inline] -pub fn sub_fp2_bn254(a: &[u64; 8], b: &[u64; 8]) -> [u64; 8] { +pub fn sub_fp2_bn254( + a: &[u64; 8], + b: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 8] { let mut f1 = SyscallComplex256 { x: a[0..4].try_into().unwrap(), y: a[4..8].try_into().unwrap() }; let f2 = SyscallComplex256 { x: b[0..4].try_into().unwrap(), y: b[4..8].try_into().unwrap() }; let mut params = SyscallBn254ComplexSubParams { f1: &mut f1, f2: &f2 }; - syscall_bn254_complex_sub(&mut params); + syscall_bn254_complex_sub( + &mut params, + #[cfg(feature = "hints")] + hints, + ); let res_x = params.f1.x; let res_y = params.f1.y; [res_x[0], res_x[1], res_x[2], res_x[3], res_y[0], res_y[1], res_y[2], res_y[3]] @@ -69,13 +93,21 @@ pub fn sub_fp2_bn254(a: &[u64; 8], b: &[u64; 8]) -> [u64; 8] { /// Multiplication in the degree 2 extension of the BN254 curve #[inline] -pub fn mul_fp2_bn254(a: &[u64; 8], b: &[u64; 8]) -> [u64; 8] { +pub fn mul_fp2_bn254( + a: &[u64; 8], + b: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 8] { let mut f1 = SyscallComplex256 { x: a[0..4].try_into().unwrap(), y: a[4..8].try_into().unwrap() }; let f2 = SyscallComplex256 { x: b[0..4].try_into().unwrap(), y: b[4..8].try_into().unwrap() }; let mut params = SyscallBn254ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bn254_complex_mul(&mut params); + syscall_bn254_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); let res_x = params.f1.x; let res_y = params.f1.y; [res_x[0], res_x[1], res_x[2], res_x[3], res_y[0], res_y[1], res_y[2], res_y[3]] @@ -83,13 +115,21 @@ pub fn mul_fp2_bn254(a: &[u64; 8], b: &[u64; 8]) -> [u64; 8] { /// Scalar multiplication in the degree 2 extension of the BN254 curve #[inline] -pub fn scalar_mul_fp2_bn254(a: &[u64; 8], b: &[u64; 4]) -> [u64; 8] { +pub fn scalar_mul_fp2_bn254( + a: &[u64; 8], + b: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 8] { let mut f1 = SyscallComplex256 { x: a[0..4].try_into().unwrap(), y: a[4..8].try_into().unwrap() }; let f2 = SyscallComplex256 { x: b[0..4].try_into().unwrap(), y: [0, 0, 0, 0] }; let mut params = SyscallBn254ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bn254_complex_mul(&mut params); + syscall_bn254_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); let res_x = params.f1.x; let res_y = params.f1.y; [res_x[0], res_x[1], res_x[2], res_x[3], res_y[0], res_y[1], res_y[2], res_y[3]] @@ -97,13 +137,17 @@ pub fn scalar_mul_fp2_bn254(a: &[u64; 8], b: &[u64; 4]) -> [u64; 8] { /// Squaring in the degree 2 extension of the BN254 curve #[inline] -pub fn square_fp2_bn254(a: &[u64; 8]) -> [u64; 8] { +pub fn square_fp2_bn254(a: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 8] { let mut f1 = SyscallComplex256 { x: a[0..4].try_into().unwrap(), y: a[4..8].try_into().unwrap() }; let f2 = SyscallComplex256 { x: f1.x, y: f1.y }; let mut params = SyscallBn254ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bn254_complex_mul(&mut params); + syscall_bn254_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); let res_x = params.f1.x; let res_y = params.f1.y; [res_x[0], res_x[1], res_x[2], res_x[3], res_y[0], res_y[1], res_y[2], res_y[3]] @@ -111,7 +155,7 @@ pub fn square_fp2_bn254(a: &[u64; 8]) -> [u64; 8] { /// Inversion in the degree 2 extension of the BN254 curve #[inline] -pub fn inv_fp2_bn254(a: &[u64; 8]) -> [u64; 8] { +pub fn inv_fp2_bn254(a: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 8] { // if a == 0, return 0 if eq(a, &[0, 0, 0, 0, 0, 0, 0, 0]) { return [0, 0, 0, 0, 0, 0, 0, 0]; @@ -121,14 +165,22 @@ pub fn inv_fp2_bn254(a: &[u64; 8]) -> [u64; 8] { // Remember that an element b ∈ Fp2 is the inverse of a ∈ Fp2 if and only if a·b = 1 in Fp2 // We will therefore hint the inverse b and check the product with a is 1 - let inv = fcall_bn254_fp2_inv(a); + let inv = fcall_bn254_fp2_inv( + a, + #[cfg(feature = "hints")] + hints, + ); let mut f1 = SyscallComplex256 { x: a[0..4].try_into().unwrap(), y: a[4..8].try_into().unwrap() }; let f2 = SyscallComplex256 { x: inv[0..4].try_into().unwrap(), y: inv[4..8].try_into().unwrap() }; let mut params = SyscallBn254ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bn254_complex_mul(&mut params); + syscall_bn254_complex_mul( + &mut params, + #[cfg(feature = "hints")] + hints, + ); assert_eq!(params.f1.x, [1, 0, 0, 0]); assert_eq!(params.f1.y, [0, 0, 0, 0]); @@ -137,12 +189,19 @@ pub fn inv_fp2_bn254(a: &[u64; 8]) -> [u64; 8] { /// Conjugation in the degree 2 extension of the BN254 curve #[inline] -pub fn conjugate_fp2_bn254(a: &[u64; 8]) -> [u64; 8] { +pub fn conjugate_fp2_bn254( + a: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 8] { let mut f1 = SyscallComplex256 { x: a[0..4].try_into().unwrap(), y: [0, 0, 0, 0] }; let f2 = SyscallComplex256 { x: [0, 0, 0, 0], y: a[4..8].try_into().unwrap() }; let mut params = SyscallBn254ComplexSubParams { f1: &mut f1, f2: &f2 }; - syscall_bn254_complex_sub(&mut params); + syscall_bn254_complex_sub( + &mut params, + #[cfg(feature = "hints")] + hints, + ); let res_x = params.f1.x; let res_y = params.f1.y; [res_x[0], res_x[1], res_x[2], res_x[3], res_y[0], res_y[1], res_y[2], res_y[3]] diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/fp6.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/fp6.rs index 15761e5b0..fde424a79 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/fp6.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/fp6.rs @@ -7,12 +7,21 @@ use super::fp2::{ /// Addition in the degree 6 extension of the BN254 curve #[inline] -pub fn add_fp6_bn254(a: &[u64; 24], b: &[u64; 24]) -> [u64; 24] { +pub fn add_fp6_bn254( + a: &[u64; 24], + b: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let mut result = [0; 24]; for i in 0..3 { let a_i = &a[i * 8..(i + 1) * 8].try_into().unwrap(); let b_i = &b[i * 8..(i + 1) * 8].try_into().unwrap(); - let c_i = add_fp2_bn254(a_i, b_i); + let c_i = add_fp2_bn254( + a_i, + b_i, + #[cfg(feature = "hints")] + hints, + ); result[i * 8..(i + 1) * 8].copy_from_slice(&c_i); } result @@ -20,11 +29,15 @@ pub fn add_fp6_bn254(a: &[u64; 24], b: &[u64; 24]) -> [u64; 24] { /// Doubling in the degree 6 extension of the BN254 curve #[inline] -pub fn dbl_fp6_bn254(a: &[u64; 24]) -> [u64; 24] { +pub fn dbl_fp6_bn254(a: &[u64; 24], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 24] { let mut result = [0; 24]; for i in 0..3 { let a_i = &a[i * 8..(i + 1) * 8].try_into().unwrap(); - let c_i = dbl_fp2_bn254(a_i); + let c_i = dbl_fp2_bn254( + a_i, + #[cfg(feature = "hints")] + hints, + ); result[i * 8..(i + 1) * 8].copy_from_slice(&c_i); } result @@ -32,11 +45,15 @@ pub fn dbl_fp6_bn254(a: &[u64; 24]) -> [u64; 24] { /// Negation in the degree 6 extension of the BN254 curve #[inline] -pub fn neg_fp6_bn254(a: &[u64; 24]) -> [u64; 24] { +pub fn neg_fp6_bn254(a: &[u64; 24], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 24] { let mut result = [0; 24]; for i in 0..3 { let a_i = &a[i * 8..(i + 1) * 8].try_into().unwrap(); - let c_i = neg_fp2_bn254(a_i); + let c_i = neg_fp2_bn254( + a_i, + #[cfg(feature = "hints")] + hints, + ); result[i * 8..(i + 1) * 8].copy_from_slice(&c_i); } result @@ -44,12 +61,21 @@ pub fn neg_fp6_bn254(a: &[u64; 24]) -> [u64; 24] { /// Subtraction in the degree 6 extension of the BN254 curve #[inline] -pub fn sub_fp6_bn254(a: &[u64; 24], b: &[u64; 24]) -> [u64; 24] { +pub fn sub_fp6_bn254( + a: &[u64; 24], + b: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let mut result = [0; 24]; for i in 0..3 { let a_i = &a[i * 8..(i + 1) * 8].try_into().unwrap(); let b_i = &b[i * 8..(i + 1) * 8].try_into().unwrap(); - let c_i = sub_fp2_bn254(a_i, b_i); + let c_i = sub_fp2_bn254( + a_i, + b_i, + #[cfg(feature = "hints")] + hints, + ); result[i * 8..(i + 1) * 8].copy_from_slice(&c_i); } result @@ -63,7 +89,11 @@ pub fn sub_fp6_bn254(a: &[u64; 24], b: &[u64; 24]) -> [u64; 24] { // - c2 = (a1+a2)·(b1+b2) - a1·b1 - a2·b2 + a3·b3·(9+u) // - c3 = (a1+a3)·(b1+b3) - a1·b1 + a2·b2 - a3·b3 #[inline] -pub fn mul_fp6_bn254(a: &[u64; 24], b: &[u64; 24]) -> [u64; 24] { +pub fn mul_fp6_bn254( + a: &[u64; 24], + b: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let a1 = &a[0..8].try_into().unwrap(); let a2 = &a[8..16].try_into().unwrap(); let a3 = &a[16..24].try_into().unwrap(); @@ -72,37 +102,151 @@ pub fn mul_fp6_bn254(a: &[u64; 24], b: &[u64; 24]) -> [u64; 24] { let b3 = &b[16..24].try_into().unwrap(); // a1·b1, a2·b2, a3·b3, a3·b3·(9+u) - let a1b1 = mul_fp2_bn254(a1, b1); - let a2b2 = mul_fp2_bn254(a2, b2); - let a3b3 = mul_fp2_bn254(a3, b3); - let a3b3xi = mul_fp2_bn254(&a3b3, &[9, 0, 0, 0, 1, 0, 0, 0]); + let a1b1 = mul_fp2_bn254( + a1, + b1, + #[cfg(feature = "hints")] + hints, + ); + let a2b2 = mul_fp2_bn254( + a2, + b2, + #[cfg(feature = "hints")] + hints, + ); + let a3b3 = mul_fp2_bn254( + a3, + b3, + #[cfg(feature = "hints")] + hints, + ); + let a3b3xi = mul_fp2_bn254( + &a3b3, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); // a2+a3, b2+b3, a1+a2, b1+b2, a1+a3, b1+b3 - let a2_plus_a3 = add_fp2_bn254(a2, a3); - let b2_plus_b3 = add_fp2_bn254(b2, b3); - let a1_plus_a2 = add_fp2_bn254(a1, a2); - let b1_plus_b2 = add_fp2_bn254(b1, b2); - let a1_plus_a3 = add_fp2_bn254(a1, a3); - let b1_plus_b3 = add_fp2_bn254(b1, b3); + let a2_plus_a3 = add_fp2_bn254( + a2, + a3, + #[cfg(feature = "hints")] + hints, + ); + let b2_plus_b3 = add_fp2_bn254( + b2, + b3, + #[cfg(feature = "hints")] + hints, + ); + let a1_plus_a2 = add_fp2_bn254( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ); + let b1_plus_b2 = add_fp2_bn254( + b1, + b2, + #[cfg(feature = "hints")] + hints, + ); + let a1_plus_a3 = add_fp2_bn254( + a1, + a3, + #[cfg(feature = "hints")] + hints, + ); + let b1_plus_b3 = add_fp2_bn254( + b1, + b3, + #[cfg(feature = "hints")] + hints, + ); // c1 = [(a2+a3)·(b2+b3) - a2·b2 - a3·b3]·(9+u) + a1·b1 - let mut c1 = mul_fp2_bn254(&a2_plus_a3, &b2_plus_b3); - c1 = sub_fp2_bn254(&c1, &a2b2); - c1 = sub_fp2_bn254(&c1, &a3b3); - c1 = mul_fp2_bn254(&c1, &[9, 0, 0, 0, 1, 0, 0, 0]); - c1 = add_fp2_bn254(&c1, &a1b1); - + let mut c1 = mul_fp2_bn254( + &a2_plus_a3, + &b2_plus_b3, + #[cfg(feature = "hints")] + hints, + ); + c1 = sub_fp2_bn254( + &c1, + &a2b2, + #[cfg(feature = "hints")] + hints, + ); + c1 = sub_fp2_bn254( + &c1, + &a3b3, + #[cfg(feature = "hints")] + hints, + ); + c1 = mul_fp2_bn254( + &c1, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp2_bn254( + &c1, + &a1b1, + #[cfg(feature = "hints")] + hints, + ); // c2 = (a1+a2)·(b1+b2) - a1·b1 - a2·b2 + a3·b3·(9+u) - let mut c2 = mul_fp2_bn254(&a1_plus_a2, &b1_plus_b2); - c2 = sub_fp2_bn254(&c2, &a1b1); - c2 = sub_fp2_bn254(&c2, &a2b2); - c2 = add_fp2_bn254(&c2, &a3b3xi); + let mut c2 = mul_fp2_bn254( + &a1_plus_a2, + &b1_plus_b2, + #[cfg(feature = "hints")] + hints, + ); + c2 = sub_fp2_bn254( + &c2, + &a1b1, + #[cfg(feature = "hints")] + hints, + ); + c2 = sub_fp2_bn254( + &c2, + &a2b2, + #[cfg(feature = "hints")] + hints, + ); + c2 = add_fp2_bn254( + &c2, + &a3b3xi, + #[cfg(feature = "hints")] + hints, + ); // c3 = (a1+a3)·(b1+b3) - a1·b1 + a2·b2 - a3·b3 - let mut c3 = mul_fp2_bn254(&a1_plus_a3, &b1_plus_b3); - c3 = sub_fp2_bn254(&c3, &a1b1); - c3 = add_fp2_bn254(&c3, &a2b2); - c3 = sub_fp2_bn254(&c3, &a3b3); + let mut c3 = mul_fp2_bn254( + &a1_plus_a3, + &b1_plus_b3, + #[cfg(feature = "hints")] + hints, + ); + c3 = sub_fp2_bn254( + &c3, + &a1b1, + #[cfg(feature = "hints")] + hints, + ); + c3 = add_fp2_bn254( + &c3, + &a2b2, + #[cfg(feature = "hints")] + hints, + ); + c3 = sub_fp2_bn254( + &c3, + &a3b3, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 24]; result[0..8].copy_from_slice(&c1); @@ -119,20 +263,43 @@ pub fn mul_fp6_bn254(a: &[u64; 24], b: &[u64; 24]) -> [u64; 24] { // - c2 = b2·a1 // - c3 = b2·a2 #[inline] -pub fn sparse_mula_fp6_bn254(a: &[u64; 24], b2: &[u64; 8]) -> [u64; 24] { +pub fn sparse_mula_fp6_bn254( + a: &[u64; 24], + b2: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let a1 = &a[0..8].try_into().unwrap(); let a2 = &a[8..16].try_into().unwrap(); let a3 = &a[16..24].try_into().unwrap(); // c1 = b2·a3·(9+u) - let mut c1 = mul_fp2_bn254(b2, a3); - c1 = mul_fp2_bn254(&c1, &[9, 0, 0, 0, 1, 0, 0, 0]); + let mut c1 = mul_fp2_bn254( + b2, + a3, + #[cfg(feature = "hints")] + hints, + ); + c1 = mul_fp2_bn254( + &c1, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); // c2 = b2·a1 - let c2 = mul_fp2_bn254(b2, a1); - + let c2 = mul_fp2_bn254( + b2, + a1, + #[cfg(feature = "hints")] + hints, + ); // c3 = b2·a2 - let c3 = mul_fp2_bn254(b2, a2); + let c3 = mul_fp2_bn254( + b2, + a2, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 24]; result[0..8].copy_from_slice(&c1); @@ -149,7 +316,11 @@ pub fn sparse_mula_fp6_bn254(a: &[u64; 24], b2: &[u64; 8]) -> [u64; 24] { // - c2 = a1·b2 + a2·b1 // - c3 = a2·b2 + a3·b1 #[inline] -pub fn sparse_mulb_fp6_bn254(a: &[u64; 24], b: &[u64; 16]) -> [u64; 24] { +pub fn sparse_mulb_fp6_bn254( + a: &[u64; 24], + b: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let a1 = &a[0..8].try_into().unwrap(); let a2 = &a[8..16].try_into().unwrap(); let a3 = &a[16..24].try_into().unwrap(); @@ -157,16 +328,66 @@ pub fn sparse_mulb_fp6_bn254(a: &[u64; 24], b: &[u64; 16]) -> [u64; 24] { let b2 = &b[8..16].try_into().unwrap(); // c1 = a1·b1 + a3·b2·(9+u) - let mut c1 = mul_fp2_bn254(a1, b1); - c1 = add_fp2_bn254(&c1, &mul_fp2_bn254(a3, &mul_fp2_bn254(b2, &[9, 0, 0, 0, 1, 0, 0, 0]))); + let mut c1 = mul_fp2_bn254( + a1, + b1, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp2_bn254( + &c1, + &mul_fp2_bn254( + a3, + &mul_fp2_bn254( + b2, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // c2 = a1·b2 + a2·b1 - let mut c2 = mul_fp2_bn254(a1, b2); - c2 = add_fp2_bn254(&c2, &mul_fp2_bn254(a2, b1)); + let mut c2 = mul_fp2_bn254( + a1, + b2, + #[cfg(feature = "hints")] + hints, + ); + c2 = add_fp2_bn254( + &c2, + &mul_fp2_bn254( + a2, + b1, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // c3 = a2·b2 + a3·b1 - let mut c3 = mul_fp2_bn254(a2, b2); - c3 = add_fp2_bn254(&c3, &mul_fp2_bn254(a3, b1)); + let mut c3 = mul_fp2_bn254( + a2, + b2, + #[cfg(feature = "hints")] + hints, + ); + c3 = add_fp2_bn254( + &c3, + &mul_fp2_bn254( + a3, + b1, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 24]; result[0..8].copy_from_slice(&c1); @@ -183,7 +404,11 @@ pub fn sparse_mulb_fp6_bn254(a: &[u64; 24], b: &[u64; 16]) -> [u64; 24] { // - c2 = a1·b2 + a3·b3·(9+u) // - c3 = a1·b3 + a2·b2 #[inline] -pub fn sparse_mulc_fp6_bn254(a: &[u64; 24], b: &[u64; 16]) -> [u64; 24] { +pub fn sparse_mulc_fp6_bn254( + a: &[u64; 24], + b: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let a1 = &a[0..8].try_into().unwrap(); let a2 = &a[8..16].try_into().unwrap(); let a3 = &a[16..24].try_into().unwrap(); @@ -191,18 +416,73 @@ pub fn sparse_mulc_fp6_bn254(a: &[u64; 24], b: &[u64; 16]) -> [u64; 24] { let b3 = &b[8..16].try_into().unwrap(); // c1 = (a2·b3 + a3·b2)·(9+u) - let mut c1 = mul_fp2_bn254(a2, b3); - c1 = add_fp2_bn254(&c1, &mul_fp2_bn254(a3, b2)); - c1 = mul_fp2_bn254(&c1, &[9, 0, 0, 0, 1, 0, 0, 0]); + let mut c1 = mul_fp2_bn254( + a2, + b3, + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp2_bn254( + &c1, + &mul_fp2_bn254( + a3, + b2, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + c1 = mul_fp2_bn254( + &c1, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); // c2 = a1·b2 + a3·b3·(9+u) - let mut c2 = mul_fp2_bn254(a3, b3); - c2 = mul_fp2_bn254(&c2, &[9, 0, 0, 0, 1, 0, 0, 0]); - c2 = add_fp2_bn254(&c2, &mul_fp2_bn254(a1, b2)); + let mut c2 = mul_fp2_bn254( + a3, + b3, + #[cfg(feature = "hints")] + hints, + ); + c2 = mul_fp2_bn254( + &c2, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + c2 = add_fp2_bn254( + &c2, + &mul_fp2_bn254( + a1, + b2, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // c3 = a2·b3 + a2·b2 - let mut c3 = mul_fp2_bn254(a1, b3); - c3 = add_fp2_bn254(&c3, &mul_fp2_bn254(a2, b2)); + let mut c3 = mul_fp2_bn254( + a1, + b3, + #[cfg(feature = "hints")] + hints, + ); + c3 = add_fp2_bn254( + &c3, + &mul_fp2_bn254( + a2, + b2, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 24]; result[0..8].copy_from_slice(&c1); @@ -219,37 +499,119 @@ pub fn sparse_mulc_fp6_bn254(a: &[u64; 24], b: &[u64; 16]) -> [u64; 24] { // - c2 = a3²·(9 + u) + 2·a1·a2 // - c3 = 2·a1·a2 - a3² + (a1 - a2 + a3)² + 2·a2·a3 - a1² #[inline] -pub fn square_fp6_bn254(a: &[u64; 24]) -> [u64; 24] { +pub fn square_fp6_bn254( + a: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { let a1 = &a[0..8].try_into().unwrap(); let a2 = &a[8..16].try_into().unwrap(); let a3 = &a[16..24].try_into().unwrap(); - let mut two_a1a2 = mul_fp2_bn254(a1, a2); - two_a1a2 = dbl_fp2_bn254(&two_a1a2); - - let a3_squared = square_fp2_bn254(a3); + let mut two_a1a2 = mul_fp2_bn254( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ); + two_a1a2 = dbl_fp2_bn254( + &two_a1a2, + #[cfg(feature = "hints")] + hints, + ); + + let a3_squared = square_fp2_bn254( + a3, + #[cfg(feature = "hints")] + hints, + ); // c2 = a3²·(9 + u) + 2·a1·a2 - let mut c2 = mul_fp2_bn254(&a3_squared, &[9, 0, 0, 0, 1, 0, 0, 0]); - c2 = add_fp2_bn254(&c2, &two_a1a2); + let mut c2 = mul_fp2_bn254( + &a3_squared, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + c2 = add_fp2_bn254( + &c2, + &two_a1a2, + #[cfg(feature = "hints")] + hints, + ); // a1², (a1 - a2 + a3)², 2·a2·a3 - let a1_squared = square_fp2_bn254(a1); - let mut a1a2a3 = sub_fp2_bn254(a1, a2); - a1a2a3 = add_fp2_bn254(&a1a2a3, a3); - a1a2a3 = square_fp2_bn254(&a1a2a3); - let mut two_a2a3 = mul_fp2_bn254(a2, a3); - two_a2a3 = dbl_fp2_bn254(&two_a2a3); + let a1_squared = square_fp2_bn254( + a1, + #[cfg(feature = "hints")] + hints, + ); + let mut a1a2a3 = sub_fp2_bn254( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ); + a1a2a3 = add_fp2_bn254( + &a1a2a3, + a3, + #[cfg(feature = "hints")] + hints, + ); + a1a2a3 = square_fp2_bn254( + &a1a2a3, + #[cfg(feature = "hints")] + hints, + ); + let mut two_a2a3 = mul_fp2_bn254( + a2, + a3, + #[cfg(feature = "hints")] + hints, + ); + two_a2a3 = dbl_fp2_bn254( + &two_a2a3, + #[cfg(feature = "hints")] + hints, + ); // c1 = 2·a2·a3·(9 + u) + a1² - let mut c1 = mul_fp2_bn254(&two_a2a3, &[9, 0, 0, 0, 1, 0, 0, 0]); - c1 = add_fp2_bn254(&c1, &a1_squared); - + let mut c1 = mul_fp2_bn254( + &two_a2a3, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + c1 = add_fp2_bn254( + &c1, + &a1_squared, + #[cfg(feature = "hints")] + hints, + ); // c3 = 2·a1·a2 - a3² + (a1 - a2 + a3)² + 2·a2·a3 - a1² - let mut c3 = sub_fp2_bn254(&two_a1a2, &a3_squared); - c3 = add_fp2_bn254(&c3, &a1a2a3); - c3 = add_fp2_bn254(&c3, &two_a2a3); - c3 = sub_fp2_bn254(&c3, &a1_squared); + let mut c3 = sub_fp2_bn254( + &two_a1a2, + &a3_squared, + #[cfg(feature = "hints")] + hints, + ); + c3 = add_fp2_bn254( + &c3, + &a1a2a3, + #[cfg(feature = "hints")] + hints, + ); + c3 = add_fp2_bn254( + &c3, + &two_a2a3, + #[cfg(feature = "hints")] + hints, + ); + c3 = sub_fp2_bn254( + &c3, + &a1_squared, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 24]; result[0..8].copy_from_slice(&c1); @@ -270,44 +632,144 @@ pub fn square_fp6_bn254(a: &[u64; 24]) -> [u64; 24] { // * c2mid = (9 + u)·a3² - (a1·a2) // * c3mid = a2² - (a1·a3) #[inline] -pub fn inv_fp6_bn254(a: &[u64; 24]) -> [u64; 24] { +pub fn inv_fp6_bn254(a: &[u64; 24], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 24] { let a1 = &a[0..8].try_into().unwrap(); let a2 = &a[8..16].try_into().unwrap(); let a3 = &a[16..24].try_into().unwrap(); - let a1_squared = square_fp2_bn254(a1); - let a2_squared = square_fp2_bn254(a2); - let a3_squared = square_fp2_bn254(a3); - - let a1a2 = mul_fp2_bn254(a1, a2); - let a1a3 = mul_fp2_bn254(a1, a3); - let a2a3 = mul_fp2_bn254(a2, a3); + let a1_squared = square_fp2_bn254( + a1, + #[cfg(feature = "hints")] + hints, + ); + let a2_squared = square_fp2_bn254( + a2, + #[cfg(feature = "hints")] + hints, + ); + let a3_squared = square_fp2_bn254( + a3, + #[cfg(feature = "hints")] + hints, + ); + + let a1a2 = mul_fp2_bn254( + a1, + a2, + #[cfg(feature = "hints")] + hints, + ); + let a1a3 = mul_fp2_bn254( + a1, + a3, + #[cfg(feature = "hints")] + hints, + ); + let a2a3 = mul_fp2_bn254( + a2, + a3, + #[cfg(feature = "hints")] + hints, + ); // c1mid = a1² - (9 + u)·(a2·a3) - let mut c1mid = mul_fp2_bn254(&a2a3, &[9, 0, 0, 0, 1, 0, 0, 0]); - c1mid = sub_fp2_bn254(&a1_squared, &c1mid); + let mut c1mid = mul_fp2_bn254( + &a2a3, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + c1mid = sub_fp2_bn254( + &a1_squared, + &c1mid, + #[cfg(feature = "hints")] + hints, + ); // c2mid = (9 + u)·a3² - (a1·a2) - let mut c2mid = mul_fp2_bn254(&a3_squared, &[9, 0, 0, 0, 1, 0, 0, 0]); - c2mid = sub_fp2_bn254(&c2mid, &a1a2); - + let mut c2mid = mul_fp2_bn254( + &a3_squared, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + c2mid = sub_fp2_bn254( + &c2mid, + &a1a2, + #[cfg(feature = "hints")] + hints, + ); // c3mid = a2² - (a1·a3) - let c3mid = sub_fp2_bn254(&a2_squared, &a1a3); + let c3mid = sub_fp2_bn254( + &a2_squared, + &a1a3, + #[cfg(feature = "hints")] + hints, + ); // im = a1·c1mid - let im = mul_fp2_bn254(a1, &c1mid); + let im = mul_fp2_bn254( + a1, + &c1mid, + #[cfg(feature = "hints")] + hints, + ); // last = (im + (9 + u)·(a3·c2mid + a2·c3mid))⁻¹ - let mut last = mul_fp2_bn254(a3, &c2mid); - last = add_fp2_bn254(&last, &mul_fp2_bn254(a2, &c3mid)); - last = mul_fp2_bn254(&last, &[9, 0, 0, 0, 1, 0, 0, 0]); - last = add_fp2_bn254(&last, &im); - last = inv_fp2_bn254(&last); + let mut last = mul_fp2_bn254( + a3, + &c2mid, + #[cfg(feature = "hints")] + hints, + ); + last = add_fp2_bn254( + &last, + &mul_fp2_bn254( + a2, + &c3mid, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + last = mul_fp2_bn254( + &last, + &[9, 0, 0, 0, 1, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); + last = add_fp2_bn254( + &last, + &im, + #[cfg(feature = "hints")] + hints, + ); + last = inv_fp2_bn254( + &last, + #[cfg(feature = "hints")] + hints, + ); // c1 = c1mid·last, c2 = c2mid·last, c3 = c3mid·last - let c1 = mul_fp2_bn254(&c1mid, &last); - let c2 = mul_fp2_bn254(&c2mid, &last); - let c3 = mul_fp2_bn254(&c3mid, &last); + let c1 = mul_fp2_bn254( + &c1mid, + &last, + #[cfg(feature = "hints")] + hints, + ); + let c2 = mul_fp2_bn254( + &c2mid, + &last, + #[cfg(feature = "hints")] + hints, + ); + let c3 = mul_fp2_bn254( + &c3mid, + &last, + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 24]; result[0..8].copy_from_slice(&c1); diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/miller_loop.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/miller_loop.rs index 601c850fc..af8b19dd5 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/miller_loop.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/miller_loop.rs @@ -21,13 +21,30 @@ const LOOP_LENGTH: [i8; 65] = [ ]; /// Computes the Miller loop of a non-zero point `p` in G1 and a non-zero point `q` in G2 -pub fn miller_loop_bn254(p: &[u64; 8], q: &[u64; 16]) -> [u64; 48] { +pub fn miller_loop_bn254( + p: &[u64; 8], + q: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { // Before the loop starts, compute xp' = -xp/yp and yp' = 1/yp let mut xp_prime: [u64; 4] = p[0..4].try_into().unwrap(); let mut yp_prime: [u64; 4] = p[4..8].try_into().unwrap(); - yp_prime = inv_fp_bn254(&yp_prime); - xp_prime = neg_fp_bn254(&xp_prime); - xp_prime = mul_fp_bn254(&xp_prime, &yp_prime); + yp_prime = inv_fp_bn254( + &yp_prime, + #[cfg(feature = "hints")] + hints, + ); + xp_prime = neg_fp_bn254( + &xp_prime, + #[cfg(feature = "hints")] + hints, + ); + xp_prime = mul_fp_bn254( + &xp_prime, + &yp_prime, + #[cfg(feature = "hints")] + hints, + ); // Initialize the Miller loop with r = q and f = 1 let mut r: [u64; 16] = q[0..16].try_into().unwrap(); @@ -35,76 +52,231 @@ pub fn miller_loop_bn254(p: &[u64; 8], q: &[u64; 16]) -> [u64; 48] { f[0] = 1; for &bit in LOOP_LENGTH.iter().skip(1) { // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(r)} - let (lambda, mu) = fcall_bn254_dbl_line_coeffs(&r); + let (lambda, mu) = fcall_bn254_dbl_line_coeffs( + &r, + #[cfg(feature = "hints")] + hints, + ); // Check that the line is correct - assert!(is_tangent_twist_bn254(&r, &lambda, &mu)); + assert!(is_tangent_twist_bn254( + &r, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + )); // Compute f = f² · line_{twist(r),twist(r)}(p) - f = square_fp12_bn254(&f); - let l = line_eval_twist_bn254(&lambda, &mu, &xp_prime, &yp_prime); - f = sparse_mul_fp12_bn254(&f, &l); + f = square_fp12_bn254( + &f, + #[cfg(feature = "hints")] + hints, + ); + let l = line_eval_twist_bn254( + &lambda, + &mu, + &xp_prime, + &yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bn254( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); // Double r - r = dbl_twist_with_hints_bn254(&r, &lambda, &mu); + r = dbl_twist_with_hints_bn254( + &r, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + ); if bit * bit == 1 { - let q_prime = if bit == 1 { q } else { &neg_twist_bn254(q) }; + let q_prime = if bit == 1 { + q + } else { + &neg_twist_bn254( + q, + #[cfg(feature = "hints")] + hints, + ) + }; // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(q')} - let (lambda, mu) = fcall_bn254_add_line_coeffs(&r, q_prime); + let (lambda, mu) = fcall_bn254_add_line_coeffs( + &r, + q_prime, + #[cfg(feature = "hints")] + hints, + ); // Check that the line is correct - assert!(is_line_twist_bn254(&r, q_prime, &lambda, &mu)); + assert!(is_line_twist_bn254( + &r, + q_prime, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + )); // Compute f = f · line_{twist(r),twist(q')} - let l = line_eval_twist_bn254(&lambda, &mu, &xp_prime, &yp_prime); - f = sparse_mul_fp12_bn254(&f, &l); + let l = line_eval_twist_bn254( + &lambda, + &mu, + &xp_prime, + &yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bn254( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); // Add r and q' - r = add_twist_with_hints_bn254(&r, q_prime, &lambda, &mu); + r = add_twist_with_hints_bn254( + &r, + q_prime, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + ); } } // Compute the last two lines // f = f · line_{twist(r),twist(utf(q))}(p) - let q_frob = utf_endomorphism_twist_bn254(q); + let q_frob = utf_endomorphism_twist_bn254( + q, + #[cfg(feature = "hints")] + hints, + ); // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(utf(q))} - let (lambda, mu) = fcall_bn254_add_line_coeffs(&r, &q_frob); - assert!(is_line_twist_bn254(&r, &q_frob, &lambda, &mu)); - - let l = line_eval_twist_bn254(&lambda, &mu, &xp_prime, &yp_prime); - f = sparse_mul_fp12_bn254(&f, &l); + let (lambda, mu) = fcall_bn254_add_line_coeffs( + &r, + &q_frob, + #[cfg(feature = "hints")] + hints, + ); + assert!(is_line_twist_bn254( + &r, + &q_frob, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + )); + + let l = line_eval_twist_bn254( + &lambda, + &mu, + &xp_prime, + &yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bn254( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); // Update r by r + utf(q) - r = add_twist_with_hints_bn254(&r, &q_frob, &lambda, &mu); + r = add_twist_with_hints_bn254( + &r, + &q_frob, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + ); // f = f · line_{twist(r),twist(-utf(utf(q)))}(p) - let q_frob2 = neg_twist_bn254(&utf_endomorphism_twist_bn254(&q_frob)); + let q_frob2 = neg_twist_bn254( + &utf_endomorphism_twist_bn254( + &q_frob, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(-utf(utf(q)))} - let (lambda, mu) = fcall_bn254_add_line_coeffs(&r, &q_frob2); - assert!(is_line_twist_bn254(&r, &q_frob2, &lambda, &mu)); - - let l = line_eval_twist_bn254(&lambda, &mu, &xp_prime, &yp_prime); - f = sparse_mul_fp12_bn254(&f, &l); + let (lambda, mu) = fcall_bn254_add_line_coeffs( + &r, + &q_frob2, + #[cfg(feature = "hints")] + hints, + ); + assert!(is_line_twist_bn254( + &r, + &q_frob2, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints + )); + + let l = line_eval_twist_bn254( + &lambda, + &mu, + &xp_prime, + &yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bn254( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); f } /// Computes the Miller loop for the BN254 curve for a batch of non-zero points `p_i` in G1 and non-zero points `q_i` in G2 -pub fn miller_loop_batch_bn254(g1_points: &[[u64; 8]], g2_points: &[[u64; 16]]) -> [u64; 48] { +pub fn miller_loop_batch_bn254( + g1_points: &[[u64; 8]], + g2_points: &[[u64; 16]], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { // Before the loop starts, compute xp' = -xp/yp and yp' = 1/yp for each point p let mut xp_primes: Vec<[u64; 4]> = Vec::with_capacity(g1_points.len()); let mut yp_primes: Vec<[u64; 4]> = Vec::with_capacity(g1_points.len()); for p in g1_points.iter() { let mut xp_prime: [u64; 4] = p[0..4].try_into().unwrap(); let mut yp_prime: [u64; 4] = p[4..8].try_into().unwrap(); - yp_prime = inv_fp_bn254(&yp_prime); - xp_prime = neg_fp_bn254(&xp_prime); - xp_prime = mul_fp_bn254(&xp_prime, &yp_prime); + yp_prime = inv_fp_bn254( + &yp_prime, + #[cfg(feature = "hints")] + hints, + ); + xp_prime = neg_fp_bn254( + &xp_prime, + #[cfg(feature = "hints")] + hints, + ); + xp_prime = mul_fp_bn254( + &xp_prime, + &yp_prime, + #[cfg(feature = "hints")] + hints, + ); xp_primes.push(xp_prime); yp_primes.push(yp_prime); @@ -117,41 +289,112 @@ pub fn miller_loop_batch_bn254(g1_points: &[[u64; 8]], g2_points: &[[u64; 16]]) let n = g1_points.len(); for &bit in LOOP_LENGTH.iter().skip(1) { // Compute f = f² · line_{twist(r),twist(r)}(p) - f = square_fp12_bn254(&f); + f = square_fp12_bn254( + &f, + #[cfg(feature = "hints")] + hints, + ); for i in 0..n { let r = &mut r[i]; // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(r)} - let (lambda, mu) = fcall_bn254_dbl_line_coeffs(r); + let (lambda, mu) = fcall_bn254_dbl_line_coeffs( + r, + #[cfg(feature = "hints")] + hints, + ); // Check that the line is correct - assert!(is_tangent_twist_bn254(r, &lambda, &mu)); + assert!(is_tangent_twist_bn254( + r, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + )); let xp_prime = &xp_primes[i]; let yp_prime = &yp_primes[i]; - let l = line_eval_twist_bn254(&lambda, &mu, xp_prime, yp_prime); - f = sparse_mul_fp12_bn254(&f, &l); + let l = line_eval_twist_bn254( + &lambda, + &mu, + xp_prime, + yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bn254( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); // Double r - *r = dbl_twist_with_hints_bn254(r, &lambda, &mu); + *r = dbl_twist_with_hints_bn254( + r, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + ); if bit * bit == 1 { let q = &g2_points[i]; - let q_prime = if bit == 1 { q } else { &neg_twist_bn254(q) }; + let q_prime = if bit == 1 { + q + } else { + &neg_twist_bn254( + q, + #[cfg(feature = "hints")] + hints, + ) + }; // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(q')} - let (lambda, mu) = fcall_bn254_add_line_coeffs(r, q_prime); + let (lambda, mu) = fcall_bn254_add_line_coeffs( + r, + q_prime, + #[cfg(feature = "hints")] + hints, + ); // Check that the line is correct - assert!(is_line_twist_bn254(r, q_prime, &lambda, &mu)); + assert!(is_line_twist_bn254( + r, + q_prime, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + )); // Compute f = f · line_{twist(r),twist(q')} - let l = line_eval_twist_bn254(&lambda, &mu, xp_prime, yp_prime); - f = sparse_mul_fp12_bn254(&f, &l); + let l = line_eval_twist_bn254( + &lambda, + &mu, + xp_prime, + yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bn254( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); // Add r and q' - *r = add_twist_with_hints_bn254(r, q_prime, &lambda, &mu); + *r = add_twist_with_hints_bn254( + r, + q_prime, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + ); } } } @@ -164,27 +407,93 @@ pub fn miller_loop_batch_bn254(g1_points: &[[u64; 8]], g2_points: &[[u64; 16]]) let yp_prime = &yp_primes[i]; // f = f · line_{twist(r),twist(utf(q))}(p) - let q_frob = utf_endomorphism_twist_bn254(q); + let q_frob = utf_endomorphism_twist_bn254( + q, + #[cfg(feature = "hints")] + hints, + ); // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(utf(q))} - let (lambda, mu) = fcall_bn254_add_line_coeffs(r, &q_frob); - assert!(is_line_twist_bn254(r, &q_frob, &lambda, &mu)); - - let l = line_eval_twist_bn254(&lambda, &mu, xp_prime, yp_prime); - f = sparse_mul_fp12_bn254(&f, &l); + let (lambda, mu) = fcall_bn254_add_line_coeffs( + r, + &q_frob, + #[cfg(feature = "hints")] + hints, + ); + assert!(is_line_twist_bn254( + r, + &q_frob, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + )); + + let l = line_eval_twist_bn254( + &lambda, + &mu, + xp_prime, + yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bn254( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); // Update r by r + utf(q) - *r = add_twist_with_hints_bn254(r, &q_frob, &lambda, &mu); - + *r = add_twist_with_hints_bn254( + r, + &q_frob, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + ); // f = f · line_{twist(r),twist(-utf(utf(q)))}(p) - let q_frob2 = neg_twist_bn254(&utf_endomorphism_twist_bn254(&q_frob)); + let q_frob2 = neg_twist_bn254( + &utf_endomorphism_twist_bn254( + &q_frob, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // Hint the coefficients (𝜆,𝜇) of the line l_{twist(r),twist(-utf(utf(q)))} - let (lambda, mu) = fcall_bn254_add_line_coeffs(r, &q_frob2); - assert!(is_line_twist_bn254(r, &q_frob2, &lambda, &mu)); - - let l = line_eval_twist_bn254(&lambda, &mu, xp_prime, yp_prime); - f = sparse_mul_fp12_bn254(&f, &l); + let (lambda, mu) = fcall_bn254_add_line_coeffs( + r, + &q_frob2, + #[cfg(feature = "hints")] + hints, + ); + assert!(is_line_twist_bn254( + r, + &q_frob2, + &lambda, + &mu, + #[cfg(feature = "hints")] + hints, + )); + + let l = line_eval_twist_bn254( + &lambda, + &mu, + xp_prime, + yp_prime, + #[cfg(feature = "hints")] + hints, + ); + f = sparse_mul_fp12_bn254( + &f, + &l, + #[cfg(feature = "hints")] + hints, + ); } f @@ -202,44 +511,103 @@ pub fn miller_loop_batch_bn254(g1_points: &[[u64; 8]], g2_points: &[[u64; 16]]) /// Checks if the line defined by (𝜆,𝜇) passes through non-zero points `q1,q2` in G2 #[inline] -fn is_line_twist_bn254(q1: &[u64; 16], q2: &[u64; 16], lambda: &[u64; 8], mu: &[u64; 8]) -> bool { +fn is_line_twist_bn254( + q1: &[u64; 16], + q2: &[u64; 16], + lambda: &[u64; 8], + mu: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { // Check if the line passes through q1 - let check_q1 = line_check_twist_bn254(q1, lambda, mu); + let check_q1 = line_check_twist_bn254( + q1, + lambda, + mu, + #[cfg(feature = "hints")] + hints, + ); // Check if the line passes through q2 - let check_q2 = line_check_twist_bn254(q2, lambda, mu); - + let check_q2 = line_check_twist_bn254( + q2, + lambda, + mu, + #[cfg(feature = "hints")] + hints, + ); check_q1 && check_q2 } /// Checks if the line defined by (𝜆,𝜇) is tangent to the curve at non-zero point `q` in G2 #[inline] -fn is_tangent_twist_bn254(q: &[u64; 16], lambda: &[u64; 8], mu: &[u64; 8]) -> bool { +fn is_tangent_twist_bn254( + q: &[u64; 16], + lambda: &[u64; 8], + mu: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { // Check if the line is tangent to the curve at q // Check if the line passes through q - let check_q = line_check_twist_bn254(q, lambda, mu); - + let check_q = line_check_twist_bn254( + q, + lambda, + mu, + #[cfg(feature = "hints")] + hints, + ); // Check that 2𝜆y = 3x² let x: &[u64; 8] = q[0..8].try_into().unwrap(); let y: &[u64; 8] = q[8..16].try_into().unwrap(); - let mut lhs = mul_fp2_bn254(lambda, y); - lhs = dbl_fp2_bn254(&lhs); - - let mut rhs = square_fp2_bn254(x); - rhs = scalar_mul_fp2_bn254(&rhs, &[3, 0, 0, 0]); - + let mut lhs = mul_fp2_bn254( + lambda, + y, + #[cfg(feature = "hints")] + hints, + ); + lhs = dbl_fp2_bn254( + &lhs, + #[cfg(feature = "hints")] + hints, + ); + + let mut rhs = square_fp2_bn254( + x, + #[cfg(feature = "hints")] + hints, + ); + rhs = scalar_mul_fp2_bn254( + &rhs, + &[3, 0, 0, 0], + #[cfg(feature = "hints")] + hints, + ); check_q && eq(&lhs, &rhs) } /// Check if the line defined by (𝜆,𝜇) passes through non-zero point `q` in G2 #[inline] -fn line_check_twist_bn254(q: &[u64; 16], lambda: &[u64; 8], mu: &[u64; 8]) -> bool { +fn line_check_twist_bn254( + q: &[u64; 16], + lambda: &[u64; 8], + mu: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let x: &[u64; 8] = q[0..8].try_into().unwrap(); let y: &[u64; 8] = q[8..16].try_into().unwrap(); // Check if y = λx + μ - let mut rhs = mul_fp2_bn254(lambda, x); - rhs = add_fp2_bn254(&rhs, mu); + let mut rhs = mul_fp2_bn254( + lambda, + x, + #[cfg(feature = "hints")] + hints, + ); + rhs = add_fp2_bn254( + &rhs, + mu, + #[cfg(feature = "hints")] + hints, + ); eq(&rhs, y) } @@ -250,9 +618,24 @@ fn line_eval_twist_bn254( mu: &[u64; 8], x: &[u64; 4], y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 16] { - let coeff1 = scalar_mul_fp2_bn254(lambda, x); - let coeff2 = scalar_mul_fp2_bn254(mu, &neg_fp_bn254(y)); + let coeff1 = scalar_mul_fp2_bn254( + lambda, + x, + #[cfg(feature = "hints")] + hints, + ); + let coeff2 = scalar_mul_fp2_bn254( + mu, + &neg_fp_bn254( + y, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); let mut result = [0; 16]; result[0..8].copy_from_slice(&coeff1); @@ -268,19 +651,48 @@ fn add_twist_with_hints_bn254( q2: &[u64; 16], lambda: &[u64; 8], mu: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 16] { let x1: &[u64; 8] = q1[0..8].try_into().unwrap(); let x2: &[u64; 8] = q2[0..8].try_into().unwrap(); // Compute x3 = λ² - x1 - x2 - let mut x3 = square_fp2_bn254(lambda); - x3 = sub_fp2_bn254(&x3, x1); - x3 = sub_fp2_bn254(&x3, x2); + let mut x3 = square_fp2_bn254( + lambda, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bn254( + &x3, + x1, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bn254( + &x3, + x2, + #[cfg(feature = "hints")] + hints, + ); // Compute y3 = -λx3 - μ - let mut y3 = mul_fp2_bn254(lambda, &x3); - y3 = add_fp2_bn254(mu, &y3); - y3 = neg_fp2_bn254(&y3); + let mut y3 = mul_fp2_bn254( + lambda, + &x3, + #[cfg(feature = "hints")] + hints, + ); + y3 = add_fp2_bn254( + mu, + &y3, + #[cfg(feature = "hints")] + hints, + ); + y3 = neg_fp2_bn254( + &y3, + #[cfg(feature = "hints")] + hints, + ); [ x3[0], x3[1], x3[2], x3[3], x3[4], x3[5], x3[6], x3[7], y3[0], y3[1], y3[2], y3[3], y3[4], @@ -290,17 +702,49 @@ fn add_twist_with_hints_bn254( /// Doubling of a non-zero point `q` in G2 with hinted line coefficients (𝜆,𝜇) #[inline] -fn dbl_twist_with_hints_bn254(q: &[u64; 16], lambda: &[u64; 8], mu: &[u64; 8]) -> [u64; 16] { +fn dbl_twist_with_hints_bn254( + q: &[u64; 16], + lambda: &[u64; 8], + mu: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 16] { let x: &[u64; 8] = q[0..8].try_into().unwrap(); // Compute x3 = λ² - 2x - let mut x3 = square_fp2_bn254(lambda); - x3 = sub_fp2_bn254(&x3, &dbl_fp2_bn254(x)); + let mut x3 = square_fp2_bn254( + lambda, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bn254( + &x3, + &dbl_fp2_bn254( + x, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); // Compute y3 = -λx3 - μ - let mut y3 = mul_fp2_bn254(lambda, &x3); - y3 = add_fp2_bn254(mu, &y3); - y3 = neg_fp2_bn254(&y3); + let mut y3 = mul_fp2_bn254( + lambda, + &x3, + #[cfg(feature = "hints")] + hints, + ); + y3 = add_fp2_bn254( + mu, + &y3, + #[cfg(feature = "hints")] + hints, + ); + y3 = neg_fp2_bn254( + &y3, + #[cfg(feature = "hints")] + hints, + ); [ x3[0], x3[1], x3[2], x3[3], x3[4], x3[5], x3[6], x3[7], y3[0], y3[1], y3[2], y3[3], y3[4], diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs index ddbe3bbd9..4297bc0a9 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs @@ -17,7 +17,11 @@ use super::{ /// input: P ∈ G1 and Q ∈ G2 /// output: e(P,Q) ∈ GT /// -pub fn pairing_bn254(p: &[u64; 8], q: &[u64; 16]) -> [u64; 48] { +pub fn pairing_bn254( + p: &[u64; 8], + q: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { // Is p = 𝒪? if *p == IDENTITY_G1 || *q == IDENTITY_G2 { // e(P, 𝒪) = e(𝒪, Q) = 1; @@ -27,16 +31,29 @@ pub fn pairing_bn254(p: &[u64; 8], q: &[u64; 16]) -> [u64; 48] { } // Miller loop - let miller_loop = miller_loop_bn254(p, q); + let miller_loop = miller_loop_bn254( + p, + q, + #[cfg(feature = "hints")] + hints, + ); // Final exponentiation - final_exp_bn254(&miller_loop) + final_exp_bn254( + &miller_loop, + #[cfg(feature = "hints")] + hints, + ) } /// Computes the optimal Ate pairing for a batch of G1 and G2 points over the BN254 curve /// and multiplies the results together, i.e.: /// e(P₁, Q₁) · e(P₂, Q₂) · ... · e(Pₙ, Qₙ) ∈ GT -pub fn pairing_batch_bn254(g1_points: &[[u64; 8]], g2_points: &[[u64; 16]]) -> [u64; 48] { +pub fn pairing_batch_bn254( + g1_points: &[[u64; 8]], + g2_points: &[[u64; 16]], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 48] { // Since each e(Pi, Qi) := FinalExp(MillerLoop(Pi, Qi)) // We have: // e(P₁, Q₁) · e(P₂, Q₂) · ... · e(Pₙ, Qₙ) = FinalExp(MillerLoop(P₁, Q₁) · MillerLoop(P₂, Q₂) · ... · MillerLoop(Pₙ, Qₙ)) @@ -68,10 +85,19 @@ pub fn pairing_batch_bn254(g1_points: &[[u64; 8]], g2_points: &[[u64; 16]]) -> [ } // Compute the Miller loop for the batch - let miller_loop = miller_loop_batch_bn254(&g1_points_ml, &g2_points_ml); + let miller_loop = miller_loop_batch_bn254( + &g1_points_ml, + &g2_points_ml, + #[cfg(feature = "hints")] + hints, + ); // Final exponentiation - final_exp_bn254(&miller_loop) + final_exp_bn254( + &miller_loop, + #[cfg(feature = "hints")] + hints, + ) } /// # Safety @@ -87,10 +113,16 @@ pub unsafe extern "C" fn pairing_batch_bn254_c( g2_ptr: *const u64, num_points: usize, out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) { let g1_slice = core::slice::from_raw_parts(g1_ptr as *const [u64; 8], num_points); let g2_slice = core::slice::from_raw_parts(g2_ptr as *const [u64; 16], num_points); - let result = pairing_batch_bn254(g1_slice, g2_slice); + let result = pairing_batch_bn254( + g1_slice, + g2_slice, + #[cfg(feature = "hints")] + hints, + ); out_ptr.copy_from_nonoverlapping(result.as_ptr(), 48); } diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs index a0f30fbe3..4fb630575 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs @@ -11,39 +11,109 @@ use super::{ }; /// Check if a non-zero point `p` is on the BN254 twist -pub fn is_on_curve_twist_bn254(p: &[u64; 16]) -> bool { +pub fn is_on_curve_twist_bn254( + p: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { // q in E' iff y² == x³ + 3 / (9 + u) let x: [u64; 8] = p[0..8].try_into().unwrap(); let y: [u64; 8] = p[8..16].try_into().unwrap(); - let x_sq = square_fp2_bn254(&x); - let x_cubed = mul_fp2_bn254(&x_sq, &x); - let x_cubed_plus_b = add_fp2_bn254(&x_cubed, &ETWISTED_B); - let y_sq = square_fp2_bn254(&y); + let x_sq = square_fp2_bn254( + &x, + #[cfg(feature = "hints")] + hints, + ); + let x_cubed = mul_fp2_bn254( + &x_sq, + &x, + #[cfg(feature = "hints")] + hints, + ); + let x_cubed_plus_b = add_fp2_bn254( + &x_cubed, + &ETWISTED_B, + #[cfg(feature = "hints")] + hints, + ); + let y_sq = square_fp2_bn254( + &y, + #[cfg(feature = "hints")] + hints, + ); eq(&x_cubed_plus_b, &y_sq) } /// Check if a non-zero point `p` is on the BN254 twist subgroup -pub fn is_on_subgroup_twist_bn254(p: &[u64; 16]) -> bool { +pub fn is_on_subgroup_twist_bn254( + p: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { // p in subgroup iff: // (x+1)·Q + 𝜓(x·Q) + 𝜓²(x·Q) == 𝜓³((2x)·Q) // where 𝜓 is the Frobenius endomorphism // as described in https://eprint.iacr.org/2022/348.pdf - let xp: [u64; 16] = scalar_mul_by_x_twist_bn254(p); - let x1p = add_twist_bn254(p, &xp); - let psi_one = utf_endomorphism_twist_bn254(&xp); - let psi_two = utf_endomorphism_twist_bn254(&psi_one); - let mut lhs = add_twist_bn254(&x1p, &psi_one); - lhs = add_twist_bn254(&lhs, &psi_two); - - let mut rhs = dbl_twist_bn254(&xp); - rhs = utf_endomorphism_twist_bn254(&rhs); - rhs = utf_endomorphism_twist_bn254(&rhs); - rhs = utf_endomorphism_twist_bn254(&rhs); + let xp: [u64; 16] = scalar_mul_by_x_twist_bn254( + p, + #[cfg(feature = "hints")] + hints, + ); + let x1p = add_twist_bn254( + p, + &xp, + #[cfg(feature = "hints")] + hints, + ); + let psi_one = utf_endomorphism_twist_bn254( + &xp, + #[cfg(feature = "hints")] + hints, + ); + let psi_two = utf_endomorphism_twist_bn254( + &psi_one, + #[cfg(feature = "hints")] + hints, + ); + let mut lhs = add_twist_bn254( + &x1p, + &psi_one, + #[cfg(feature = "hints")] + hints, + ); + lhs = add_twist_bn254( + &lhs, + &psi_two, + #[cfg(feature = "hints")] + hints, + ); + + let mut rhs = dbl_twist_bn254( + &xp, + #[cfg(feature = "hints")] + hints, + ); + rhs = utf_endomorphism_twist_bn254( + &rhs, + #[cfg(feature = "hints")] + hints, + ); + rhs = utf_endomorphism_twist_bn254( + &rhs, + #[cfg(feature = "hints")] + hints, + ); + rhs = utf_endomorphism_twist_bn254( + &rhs, + #[cfg(feature = "hints")] + hints, + ); eq(&lhs, &rhs) } /// Converts a point `p` on the BN254 curve from Jacobian coordinates to affine coordinates -pub fn to_affine_twist_bn254(p: &[u64; 24]) -> [u64; 16] { +pub fn to_affine_twist_bn254( + p: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 16] { let z: [u64; 8] = p[16..24].try_into().unwrap(); if z == [0u64; 8] { @@ -58,13 +128,35 @@ pub fn to_affine_twist_bn254(p: &[u64; 24]) -> [u64; 16] { let x: [u64; 8] = p[0..8].try_into().unwrap(); let y: [u64; 8] = p[8..16].try_into().unwrap(); - let zinv = inv_fp2_bn254(&z); - let zinv_sq = square_fp2_bn254(&zinv); - - let x_res = mul_fp2_bn254(&x, &zinv_sq); - let mut y_res = mul_fp2_bn254(&y, &zinv_sq); - y_res = mul_fp2_bn254(&y_res, &zinv); - + let zinv = inv_fp2_bn254( + &z, + #[cfg(feature = "hints")] + hints, + ); + let zinv_sq = square_fp2_bn254( + &zinv, + #[cfg(feature = "hints")] + hints, + ); + + let x_res = mul_fp2_bn254( + &x, + &zinv_sq, + #[cfg(feature = "hints")] + hints, + ); + let mut y_res = mul_fp2_bn254( + &y, + &zinv_sq, + #[cfg(feature = "hints")] + hints, + ); + y_res = mul_fp2_bn254( + &y_res, + &zinv, + #[cfg(feature = "hints")] + hints, + ); [ x_res[0], x_res[1], x_res[2], x_res[3], x_res[4], x_res[5], x_res[6], x_res[7], y_res[0], y_res[1], y_res[2], y_res[3], y_res[4], y_res[5], y_res[6], y_res[7], @@ -72,7 +164,11 @@ pub fn to_affine_twist_bn254(p: &[u64; 24]) -> [u64; 16] { } /// Addition of two non-zero points -pub fn add_twist_bn254(p1: &[u64; 16], p2: &[u64; 16]) -> [u64; 16] { +pub fn add_twist_bn254( + p1: &[u64; 16], + p2: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 16] { let x1: [u64; 8] = p1[0..8].try_into().unwrap(); let y1: [u64; 8] = p1[8..16].try_into().unwrap(); let x2: [u64; 8] = p2[0..8].try_into().unwrap(); @@ -83,7 +179,11 @@ pub fn add_twist_bn254(p1: &[u64; 16], p2: &[u64; 16]) -> [u64; 16] { // Is y1 == y2? if eq(&y1, &y2) { // Compute the doubling - return dbl_twist_bn254(p1); + return dbl_twist_bn254( + p1, + #[cfg(feature = "hints")] + hints, + ); } else { // Points are the inverse of each other, return the point at infinity return IDENTITY_G2; @@ -91,19 +191,66 @@ pub fn add_twist_bn254(p1: &[u64; 16], p2: &[u64; 16]) -> [u64; 16] { } // Compute the addition - let mut den = sub_fp2_bn254(&x2, &x1); - den = inv_fp2_bn254(&den); - let mut lambda = sub_fp2_bn254(&y2, &y1); - lambda = mul_fp2_bn254(&lambda, &den); - - let mut x3 = square_fp2_bn254(&lambda); - x3 = sub_fp2_bn254(&x3, &x1); - x3 = sub_fp2_bn254(&x3, &x2); - - let mut y3 = sub_fp2_bn254(&x1, &x3); - y3 = mul_fp2_bn254(&lambda, &y3); - y3 = sub_fp2_bn254(&y3, &y1); - + let mut den = sub_fp2_bn254( + &x2, + &x1, + #[cfg(feature = "hints")] + hints, + ); + den = inv_fp2_bn254( + &den, + #[cfg(feature = "hints")] + hints, + ); + let mut lambda = sub_fp2_bn254( + &y2, + &y1, + #[cfg(feature = "hints")] + hints, + ); + lambda = mul_fp2_bn254( + &lambda, + &den, + #[cfg(feature = "hints")] + hints, + ); + + let mut x3 = square_fp2_bn254( + &lambda, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bn254( + &x3, + &x1, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bn254( + &x3, + &x2, + #[cfg(feature = "hints")] + hints, + ); + + let mut y3 = sub_fp2_bn254( + &x1, + &x3, + #[cfg(feature = "hints")] + hints, + ); + y3 = mul_fp2_bn254( + &lambda, + &y3, + #[cfg(feature = "hints")] + hints, + ); + y3 = sub_fp2_bn254( + &y3, + &y1, + #[cfg(feature = "hints")] + hints, + ); [ x3[0], x3[1], x3[2], x3[3], x3[4], x3[5], x3[6], x3[7], y3[0], y3[1], y3[2], y3[3], y3[4], y3[5], y3[6], y3[7], @@ -111,24 +258,76 @@ pub fn add_twist_bn254(p1: &[u64; 16], p2: &[u64; 16]) -> [u64; 16] { } /// Doubling of a non-zero point -pub fn dbl_twist_bn254(p: &[u64; 16]) -> [u64; 16] { +pub fn dbl_twist_bn254(p: &[u64; 16], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 16] { let x: [u64; 8] = p[0..8].try_into().unwrap(); let y: [u64; 8] = p[8..16].try_into().unwrap(); // Compute the doubling - let mut lambda = dbl_fp2_bn254(&y); - lambda = inv_fp2_bn254(&lambda); - lambda = scalar_mul_fp2_bn254(&lambda, &E_B); - lambda = mul_fp2_bn254(&lambda, &x); - lambda = mul_fp2_bn254(&lambda, &x); - - let mut x3 = square_fp2_bn254(&lambda); - x3 = sub_fp2_bn254(&x3, &x); - x3 = sub_fp2_bn254(&x3, &x); - - let mut y3 = sub_fp2_bn254(&x, &x3); - y3 = mul_fp2_bn254(&lambda, &y3); - y3 = sub_fp2_bn254(&y3, &y); + let mut lambda = dbl_fp2_bn254( + &y, + #[cfg(feature = "hints")] + hints, + ); + lambda = inv_fp2_bn254( + &lambda, + #[cfg(feature = "hints")] + hints, + ); + lambda = scalar_mul_fp2_bn254( + &lambda, + &E_B, + #[cfg(feature = "hints")] + hints, + ); + lambda = mul_fp2_bn254( + &lambda, + &x, + #[cfg(feature = "hints")] + hints, + ); + lambda = mul_fp2_bn254( + &lambda, + &x, + #[cfg(feature = "hints")] + hints, + ); + + let mut x3 = square_fp2_bn254( + &lambda, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bn254( + &x3, + &x, + #[cfg(feature = "hints")] + hints, + ); + x3 = sub_fp2_bn254( + &x3, + &x, + #[cfg(feature = "hints")] + hints, + ); + + let mut y3 = sub_fp2_bn254( + &x, + &x3, + #[cfg(feature = "hints")] + hints, + ); + y3 = mul_fp2_bn254( + &lambda, + &y3, + #[cfg(feature = "hints")] + hints, + ); + y3 = sub_fp2_bn254( + &y3, + &y, + #[cfg(feature = "hints")] + hints, + ); [ x3[0], x3[1], x3[2], x3[3], x3[4], x3[5], x3[6], x3[7], y3[0], y3[1], y3[2], y3[3], y3[4], @@ -137,12 +336,16 @@ pub fn dbl_twist_bn254(p: &[u64; 16]) -> [u64; 16] { } /// Negation of a point -pub fn neg_twist_bn254(p: &[u64; 16]) -> [u64; 16] { +pub fn neg_twist_bn254(p: &[u64; 16], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 16] { let x: [u64; 8] = p[0..8].try_into().unwrap(); let y: [u64; 8] = p[8..16].try_into().unwrap(); // Compute the negation - let y_neg = neg_fp2_bn254(&y); + let y_neg = neg_fp2_bn254( + &y, + #[cfg(feature = "hints")] + hints, + ); [ x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], y_neg[0], y_neg[1], y_neg[2], y_neg[3], y_neg[4], y_neg[5], y_neg[6], y_neg[7], @@ -150,7 +353,10 @@ pub fn neg_twist_bn254(p: &[u64; 16]) -> [u64; 16] { } /// Scalar multiplication of a non-zero point by x -pub fn scalar_mul_by_x_twist_bn254(p: &[u64; 16]) -> [u64; 16] { +pub fn scalar_mul_by_x_twist_bn254( + p: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 16] { // Binary representation of the exponent x = 4965661367192848881 in big-endian format const X_BIN_BE: [u8; 63] = [ 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, @@ -160,26 +366,56 @@ pub fn scalar_mul_by_x_twist_bn254(p: &[u64; 16]) -> [u64; 16] { let mut q = *p; for &bit in X_BIN_BE.iter().skip(1) { - q = dbl_twist_bn254(&q); + q = dbl_twist_bn254( + &q, + #[cfg(feature = "hints")] + hints, + ); if bit == 1 { - q = add_twist_bn254(&q, p); + q = add_twist_bn254( + &q, + p, + #[cfg(feature = "hints")] + hints, + ); } } q } /// Compute the untwist-frobenius-twist (utf) endomorphism 𝜓: (x,y) = (𝛾₁₂·x̄,𝛾₁₃·ȳ) -pub fn utf_endomorphism_twist_bn254(p: &[u64; 16]) -> [u64; 16] { +pub fn utf_endomorphism_twist_bn254( + p: &[u64; 16], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 16] { let mut x: [u64; 8] = p[0..8].try_into().unwrap(); let mut y: [u64; 8] = p[8..16].try_into().unwrap(); // Compute the conjugate of x and y - x = conjugate_fp2_bn254(&x); - y = conjugate_fp2_bn254(&y); + x = conjugate_fp2_bn254( + &x, + #[cfg(feature = "hints")] + hints, + ); + y = conjugate_fp2_bn254( + &y, + #[cfg(feature = "hints")] + hints, + ); // Compute the multiplication - let qx = mul_fp2_bn254(&FROBENIUS_GAMMA12, &x); - let qy = mul_fp2_bn254(&FROBENIUS_GAMMA13, &y); + let qx = mul_fp2_bn254( + &FROBENIUS_GAMMA12, + &x, + #[cfg(feature = "hints")] + hints, + ); + let qy = mul_fp2_bn254( + &FROBENIUS_GAMMA13, + &y, + #[cfg(feature = "hints")] + hints, + ); [ qx[0], qx[1], qx[2], qx[3], qx[4], qx[5], qx[6], qx[7], qy[0], qy[1], qy[2], qy[3], qy[4], @@ -190,26 +426,48 @@ pub fn utf_endomorphism_twist_bn254(p: &[u64; 16]) -> [u64; 16] { /// # Safety /// `p_ptr` must point to a valid `[u64; 16]` (128 bytes, affine G2 twist point). #[no_mangle] -pub unsafe extern "C" fn is_on_curve_twist_bn254_c(p_ptr: *const u64) -> bool { +pub unsafe extern "C" fn is_on_curve_twist_bn254_c( + p_ptr: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let p = unsafe { &*(p_ptr as *const [u64; 16]) }; - is_on_curve_twist_bn254(p) + is_on_curve_twist_bn254( + p, + #[cfg(feature = "hints")] + hints, + ) } /// # Safety /// `p_ptr` must point to a valid `[u64; 16]` (128 bytes, affine G2 twist point). #[no_mangle] -pub unsafe extern "C" fn is_on_subgroup_twist_bn254_c(p_ptr: *const u64) -> bool { +pub unsafe extern "C" fn is_on_subgroup_twist_bn254_c( + p_ptr: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { let p = unsafe { &*(p_ptr as *const [u64; 16]) }; - is_on_subgroup_twist_bn254(p) + is_on_subgroup_twist_bn254( + p, + #[cfg(feature = "hints")] + hints, + ) } /// # Safety /// - `p_ptr` must point to a valid `[u64; 24]` (192 bytes, Jacobian G2 twist point). /// - `out_ptr` must point to a valid `[u64; 16]` (128 bytes) writable buffer. #[no_mangle] -pub unsafe extern "C" fn to_affine_twist_bn254_c(p_ptr: *const u64, out_ptr: *mut u64) { +pub unsafe extern "C" fn to_affine_twist_bn254_c( + p_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let p = unsafe { &*(p_ptr as *const [u64; 24]) }; - let result = to_affine_twist_bn254(p); + let result = to_affine_twist_bn254( + p, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = result[0]; *out_ptr.add(1) = result[1]; diff --git a/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs b/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs index 81c6e57ac..e4854dbbc 100644 --- a/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs +++ b/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs @@ -1,13 +1,21 @@ use crate::syscalls::{syscall_sha256_f, SyscallSha256Params}; -pub fn sha256f_compress(state: &mut [u32; 8], blocks: &[[u8; 64]]) { +pub fn sha256f_compress( + state: &mut [u32; 8], + blocks: &[[u8; 64]], + #[cfg(feature = "hints")] hints: &mut Vec, +) { let mut state_64 = convert_u32_to_u64(state); for block in blocks { let input_u64 = convert_bytes_to_u64(block); let mut sha256_params = SyscallSha256Params { state: &mut state_64, input: &input_u64 }; - syscall_sha256_f(&mut sha256_params); + syscall_sha256_f( + &mut sha256_params, + #[cfg(feature = "hints")] + hints, + ); } *state = convert_u64_to_u32(&state_64); @@ -23,6 +31,7 @@ pub unsafe extern "C" fn sha256f_compress_c( state_ptr: *mut u32, blocks_ptr: *const u8, num_blocks: usize, + #[cfg(feature = "hints")] hints: &mut Vec, ) { let state: &mut [u32; 8] = &mut *(state_ptr as *mut [u32; 8]); let mut state_64 = convert_u32_to_u64(state); @@ -32,7 +41,11 @@ pub unsafe extern "C" fn sha256f_compress_c( let input_u64 = convert_bytes_to_u64(block); let mut sha256_params = SyscallSha256Params { state: &mut state_64, input: &input_u64 }; - syscall_sha256_f(&mut sha256_params); + syscall_sha256_f( + &mut sha256_params, + #[cfg(feature = "hints")] + hints, + ); } *state = convert_u64_to_u32(&state_64); From d550eb645073a27fb1e2a0f4b21a6fd168b49a6d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:36:08 +0000 Subject: [PATCH 153/782] add hints cfg_attr to generate new linkning symbols when compiling with hints feature --- ziskos-hints/Cargo.toml | 2 +- ziskos/entrypoint/Cargo.toml | 2 +- ziskos/entrypoint/src/syscalls/add256.rs | 3 ++- ziskos/entrypoint/src/syscalls/arith256.rs | 3 ++- .../entrypoint/src/syscalls/arith256_mod.rs | 3 ++- .../entrypoint/src/syscalls/arith384_mod.rs | 3 ++- .../src/syscalls/bls12_381_complex_add.rs | 3 ++- .../src/syscalls/bls12_381_complex_mul.rs | 3 ++- .../src/syscalls/bls12_381_complex_sub.rs | 3 ++- .../src/syscalls/bls12_381_curve_add.rs | 3 ++- .../src/syscalls/bls12_381_curve_dbl.rs | 3 ++- .../src/syscalls/bn254_complex_add.rs | 3 ++- .../src/syscalls/bn254_complex_mul.rs | 3 ++- .../src/syscalls/bn254_complex_sub.rs | 3 ++- .../src/syscalls/bn254_curve_add.rs | 3 ++- .../src/syscalls/bn254_curve_dbl.rs | 3 ++- ziskos/entrypoint/src/syscalls/keccakf.rs | 3 ++- .../entrypoint/src/syscalls/secp256k1_add.rs | 3 ++- .../entrypoint/src/syscalls/secp256k1_dbl.rs | 3 ++- ziskos/entrypoint/src/syscalls/sha256f.rs | 3 ++- .../src/zisklib/lib/array_lib/modexp.rs | 3 ++- .../entrypoint/src/zisklib/lib/bigint256.rs | 21 ++++++++++------ .../src/zisklib/lib/bls12_381/curve.rs | 18 +++++++++----- .../src/zisklib/lib/bls12_381/final_exp.rs | 3 ++- .../src/zisklib/lib/bls12_381/fp.rs | 24 ++++++++++++------- .../src/zisklib/lib/bls12_381/fp12.rs | 3 ++- .../src/zisklib/lib/bls12_381/fp2.rs | 21 ++++++++++------ .../src/zisklib/lib/bls12_381/fr.rs | 18 +++++++++----- .../src/zisklib/lib/bls12_381/miller_loop.rs | 3 ++- .../src/zisklib/lib/bls12_381/pairing.rs | 3 ++- .../src/zisklib/lib/bls12_381/twist.rs | 15 ++++++++---- .../entrypoint/src/zisklib/lib/bn254/curve.rs | 12 ++++++---- .../src/zisklib/lib/bn254/pairing.rs | 3 ++- .../entrypoint/src/zisklib/lib/bn254/twist.rs | 9 ++++--- .../src/zisklib/lib/secp256k1/curve.rs | 12 ++++++---- .../src/zisklib/lib/secp256k1/field.rs | 15 ++++++++---- .../src/zisklib/lib/secp256k1/scalar.rs | 18 +++++++++----- .../src/zisklib/lib/sha256f_compress.rs | 3 ++- 38 files changed, 174 insertions(+), 88 deletions(-) diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index 2fdfc2e0b..b954043e0 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -27,4 +27,4 @@ bincode = "2.0" [features] default = ["hints"] -hints = [] \ No newline at end of file +hints = [] diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index 5fdfe7a39..ce78a6589 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -27,4 +27,4 @@ bincode = "2.0" [features] default = [] -hints = [] \ No newline at end of file +hints = [] diff --git a/ziskos/entrypoint/src/syscalls/add256.rs b/ziskos/entrypoint/src/syscalls/add256.rs index 466f87005..d66e73e9a 100644 --- a/ziskos/entrypoint/src/syscalls/add256.rs +++ b/ziskos/entrypoint/src/syscalls/add256.rs @@ -28,7 +28,8 @@ pub struct SyscallAdd256Params<'a> { /// /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_add256")] pub extern "C" fn syscall_add256( params: &mut SyscallAdd256Params, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/arith256.rs b/ziskos/entrypoint/src/syscalls/arith256.rs index cae12d988..b89f4d8bf 100644 --- a/ziskos/entrypoint/src/syscalls/arith256.rs +++ b/ziskos/entrypoint/src/syscalls/arith256.rs @@ -31,7 +31,8 @@ pub struct SyscallArith256Params<'a> { /// /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_arith256")] pub extern "C" fn syscall_arith256( params: &mut SyscallArith256Params, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/arith256_mod.rs b/ziskos/entrypoint/src/syscalls/arith256_mod.rs index 2dd545283..7db9608ee 100644 --- a/ziskos/entrypoint/src/syscalls/arith256_mod.rs +++ b/ziskos/entrypoint/src/syscalls/arith256_mod.rs @@ -34,7 +34,8 @@ pub struct SyscallArith256ModParams<'a> { /// /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_arith256_mod")] pub extern "C" fn syscall_arith256_mod( params: &mut SyscallArith256ModParams, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/arith384_mod.rs b/ziskos/entrypoint/src/syscalls/arith384_mod.rs index 8231d4c69..efb93fd80 100644 --- a/ziskos/entrypoint/src/syscalls/arith384_mod.rs +++ b/ziskos/entrypoint/src/syscalls/arith384_mod.rs @@ -34,7 +34,8 @@ pub struct SyscallArith384ModParams<'a> { /// /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_arith384_mod")] pub extern "C" fn syscall_arith384_mod( params: &mut SyscallArith384ModParams, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs b/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs index 29e1233f1..66fdb7235 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs @@ -34,7 +34,8 @@ pub struct SyscallBls12_381ComplexAddParams<'a> { /// /// The resulting field element will have both coordinates in the range of the BLS12-381 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_bls12_381_complex_add")] pub extern "C" fn syscall_bls12_381_complex_add( params: &mut SyscallBls12_381ComplexAddParams, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs b/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs index 220c041b0..4f6f27d6e 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs @@ -34,7 +34,8 @@ pub struct SyscallBls12_381ComplexMulParams<'a> { /// /// The resulting field element will have both coordinates in the range of the BLS12-381 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_bls12_381_complex_mul")] pub extern "C" fn syscall_bls12_381_complex_mul( params: &mut SyscallBls12_381ComplexMulParams, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs b/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs index 22253ed4f..9412f8183 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs @@ -34,7 +34,8 @@ pub struct SyscallBls12_381ComplexSubParams<'a> { /// /// The resulting field element will have both coordinates in the range of the BLS12-381 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_bls12_381_complex_sub")] pub extern "C" fn syscall_bls12_381_complex_sub( params: &mut SyscallBls12_381ComplexSubParams, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs b/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs index b18f231de..795072825 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs @@ -33,7 +33,8 @@ pub struct SyscallBls12_381CurveAddParams<'a> { /// /// The resulting point will have both coordinates in the range of the BLS12-381 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_bls12_381_curve_add")] pub extern "C" fn syscall_bls12_381_curve_add( params: &mut SyscallBls12_381CurveAddParams, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs b/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs index 1a3b3721e..1b2bd1177 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs @@ -25,7 +25,8 @@ use super::point::SyscallPoint384; /// /// The resulting point will have both coordinates in the range of the BLS12-381 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_bls12_381_curve_dbl")] pub extern "C" fn syscall_bls12_381_curve_dbl( p1: &mut SyscallPoint384, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs b/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs index 0b26e9bb1..6febbb355 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs @@ -34,7 +34,8 @@ pub struct SyscallBn254ComplexAddParams<'a> { /// /// The resulting field element will have both coordinates in the range of the BN254 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_bn254_complex_add")] pub extern "C" fn syscall_bn254_complex_add( params: &mut SyscallBn254ComplexAddParams, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs b/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs index c2cbf46ea..20bc07e23 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs @@ -34,7 +34,8 @@ pub struct SyscallBn254ComplexMulParams<'a> { /// /// The resulting field element will have both coordinates in the range of the BN254 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_bn254_complex_mul")] pub extern "C" fn syscall_bn254_complex_mul( params: &mut SyscallBn254ComplexMulParams, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs b/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs index f36940f22..fd674b17d 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs @@ -34,7 +34,8 @@ pub struct SyscallBn254ComplexSubParams<'a> { /// /// The resulting field element will have both coordinates in the range of the BN254 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_bn254_complex_sub")] pub extern "C" fn syscall_bn254_complex_sub( params: &mut SyscallBn254ComplexSubParams, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs b/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs index ebef3febb..b506cea00 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs @@ -33,7 +33,8 @@ pub struct SyscallBn254CurveAddParams<'a> { /// /// The resulting point will have both coordinates in the range of the BN254 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_bn254_curve_add")] pub extern "C" fn syscall_bn254_curve_add( params: &mut SyscallBn254CurveAddParams, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs b/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs index a0ba3685e..8f7a96659 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs @@ -25,7 +25,8 @@ use super::point::SyscallPoint256; /// /// The resulting point will have both coordinates in the range of the BN254 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_bn254_curve_dbl")] pub extern "C" fn syscall_bn254_curve_dbl( p1: &mut SyscallPoint256, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/keccakf.rs b/ziskos/entrypoint/src/syscalls/keccakf.rs index a49b1eb8c..fb8b82c7d 100644 --- a/ziskos/entrypoint/src/syscalls/keccakf.rs +++ b/ziskos/entrypoint/src/syscalls/keccakf.rs @@ -18,7 +18,8 @@ use crate::ziskos_syscall; /// /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_keccak_f")] pub extern "C" fn syscall_keccak_f( state: *mut [u64; 25], #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/secp256k1_add.rs b/ziskos/entrypoint/src/syscalls/secp256k1_add.rs index d775d2d7b..16f2bff56 100644 --- a/ziskos/entrypoint/src/syscalls/secp256k1_add.rs +++ b/ziskos/entrypoint/src/syscalls/secp256k1_add.rs @@ -33,7 +33,8 @@ pub struct SyscallSecp256k1AddParams<'a> { /// /// The resulting point will have both coordinates in the range of the Secp256k1 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_secp256k1_add")] pub extern "C" fn syscall_secp256k1_add( params: &mut SyscallSecp256k1AddParams, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs b/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs index 9a24908f9..82ed83aa2 100644 --- a/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs +++ b/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs @@ -25,7 +25,8 @@ use super::point::SyscallPoint256; /// /// The resulting point will have both coordinates in the range of the Secp256k1 base field. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_secp256k1_dbl")] pub extern "C" fn syscall_secp256k1_dbl( p1: &mut SyscallPoint256, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/syscalls/sha256f.rs b/ziskos/entrypoint/src/syscalls/sha256f.rs index 3079f7fca..391a61a79 100644 --- a/ziskos/entrypoint/src/syscalls/sha256f.rs +++ b/ziskos/entrypoint/src/syscalls/sha256f.rs @@ -26,7 +26,8 @@ pub struct SyscallSha256Params<'a> { /// /// The caller must ensure that the data is aligned to a 64-bit boundary. #[allow(unused_variables)] -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_sha256_f")] pub extern "C" fn syscall_sha256_f( params: &mut SyscallSha256Params, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs index 051795ffe..90f4d1782 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs @@ -245,7 +245,8 @@ pub fn modexp_u64( /// - `exp_ptr` points to an array of `exp_len` u64 elements /// - `modulus_ptr` points to an array of `modulus_len` u64 elements /// - `result_ptr` points to an array of at least `modulus_len` u64 elements -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_modexp_u64_c")] pub unsafe extern "C" fn modexp_u64_c( base_ptr: *const u64, base_len: usize, diff --git a/ziskos/entrypoint/src/zisklib/lib/bigint256.rs b/ziskos/entrypoint/src/zisklib/lib/bigint256.rs index c31023036..7c829f0c8 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bigint256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bigint256.rs @@ -245,7 +245,8 @@ pub fn wpow256( /// - `a` must point to a valid `[u64; 4]` (32 bytes). /// - `m` must point to a valid `[u64; 4]` (32 bytes). /// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_redmod256_c")] pub unsafe extern "C" fn redmod256_c( a: *const u64, m: *const u64, @@ -276,7 +277,8 @@ pub unsafe extern "C" fn redmod256_c( /// - `b` must point to a valid `[u64; 4]` (32 bytes). /// - `m` must point to a valid `[u64; 4]` (32 bytes). /// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_addmod256_c")] pub unsafe extern "C" fn addmod256_c( a: *const u64, b: *const u64, @@ -308,7 +310,8 @@ pub unsafe extern "C" fn addmod256_c( /// - `b` must point to a valid `[u64; 4]` (32 bytes). /// - `m` must point to a valid `[u64; 4]` (32 bytes). /// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_mulmod256_c")] pub unsafe extern "C" fn mulmod256_c( a: *const u64, b: *const u64, @@ -339,7 +342,8 @@ pub unsafe extern "C" fn mulmod256_c( /// - `a` must point to a valid `[u64; 4]` (32 bytes). /// - `b` must point to a valid `[u64; 4]` (32 bytes). /// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_wmul256_c")] pub unsafe extern "C" fn wmul256_c( a: *const u64, b: *const u64, @@ -372,7 +376,8 @@ pub unsafe extern "C" fn wmul256_c( /// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. /// /// Returns `true` if overflow occurred, `false` otherwise. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_omul256_c")] pub unsafe extern "C" fn omul256_c( a: *const u64, b: *const u64, @@ -410,7 +415,8 @@ pub unsafe extern "C" fn omul256_c( /// /// # Panics /// Panics if `b` is zero. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_divrem256_c")] pub unsafe extern "C" fn divrem256_c( a: *const u64, b: *const u64, @@ -455,7 +461,8 @@ pub unsafe extern "C" fn divrem256_c( /// - `a` must point to a valid `[u64; 4]` (32 bytes). /// - `exp` must point to a valid `[u64; 4]` (32 bytes). /// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_wpow256_c")] pub unsafe extern "C" fn wpow256_c( a: *const u64, exp: *const u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs index ce35a5840..667d6e28d 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs @@ -486,7 +486,8 @@ pub fn sigma_endomorphism_bls12_381( /// - 0 = success (regular point) /// - 1 = success (point at infinity) /// - 2 = error -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_decompress_bls12_381_c")] pub unsafe extern "C" fn decompress_bls12_381_c( ret: *mut u64, input: *const u8, @@ -515,7 +516,8 @@ pub unsafe extern "C" fn decompress_bls12_381_c( /// # Safety /// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. /// Returns true if the point is on the curve, false otherwise. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_is_on_curve_bls12_381_c")] pub unsafe extern "C" fn is_on_curve_bls12_381_c( p: *const u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -531,7 +533,8 @@ pub unsafe extern "C" fn is_on_curve_bls12_381_c( /// # Safety /// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. /// Returns true if the point is in the G1 subgroup, false otherwise. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_is_on_subgroup_bls12_381_c")] pub unsafe extern "C" fn is_on_subgroup_bls12_381_c( p: *const u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -547,7 +550,8 @@ pub unsafe extern "C" fn is_on_subgroup_bls12_381_c( /// # Safety /// - `p1` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - `p2` must point to a valid `[u64; 12]` (96 bytes). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_add_bls12_381_c")] pub unsafe extern "C" fn add_bls12_381_c( p1: *mut u64, p2: *const u64, @@ -574,7 +578,8 @@ pub unsafe extern "C" fn add_bls12_381_c( /// # Safety /// - `p` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - Point must be non-zero. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_dbl_bls12_381_c")] pub unsafe extern "C" fn dbl_bls12_381_c( p: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -597,7 +602,8 @@ pub unsafe extern "C" fn dbl_bls12_381_c( /// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. /// - `k` must point to a valid `[u64; 6]` (48 bytes) for the scalar. /// - Point must be non-zero. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_scalar_mul_bls12_381_c")] pub unsafe extern "C" fn scalar_mul_bls12_381_c( ret: *mut u64, p: *const u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs index ca859b09f..585af6121 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs @@ -144,7 +144,8 @@ pub fn final_exp_bls12_381( /// # Safety /// - `f` must point to a valid `[u64; 72]` (576 bytes), used as both input and output. /// - Input must be a valid non-zero Fp12 element. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_final_exp_bls12_381_c")] pub unsafe extern "C" fn final_exp_bls12_381_c( f: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs index 90cddce35..e213c9ee2 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs @@ -223,7 +223,8 @@ pub fn inv_fp_bls12_381(x: &[u64; 6], #[cfg(feature = "hints")] hints: &mut Vec< /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes). /// - `b` must point to a valid `[u64; 6]` (48 bytes). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_add_fp_bls12_381_c")] pub unsafe extern "C" fn add_fp_bls12_381_c( a: *mut u64, b: *const u64, @@ -250,7 +251,8 @@ pub unsafe extern "C" fn add_fp_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_dbl_fp_bls12_381_c")] pub unsafe extern "C" fn dbl_fp_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -276,7 +278,8 @@ pub unsafe extern "C" fn dbl_fp_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 6]` (48 bytes). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_sub_fp_bls12_381_c")] pub unsafe extern "C" fn sub_fp_bls12_381_c( a: *mut u64, b: *const u64, @@ -303,7 +306,8 @@ pub unsafe extern "C" fn sub_fp_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_neg_fp_bls12_381_c")] pub unsafe extern "C" fn neg_fp_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -329,7 +333,8 @@ pub unsafe extern "C" fn neg_fp_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 6]` (48 bytes). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_mul_fp_bls12_381_c")] pub unsafe extern "C" fn mul_fp_bls12_381_c( a: *mut u64, b: *const u64, @@ -356,7 +361,8 @@ pub unsafe extern "C" fn mul_fp_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_square_fp_bls12_381_c")] pub unsafe extern "C" fn square_fp_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -382,7 +388,8 @@ pub unsafe extern "C" fn square_fp_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. /// - `is_qr` must point to a valid `u8`. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_sqrt_fp_bls12_381_c")] pub unsafe extern "C" fn sqrt_fp_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -399,7 +406,8 @@ pub unsafe extern "C" fn sqrt_fp_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_inv_fp_bls12_381_c")] pub unsafe extern "C" fn inv_fp_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs index 1b6caa673..31bc81f77 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs @@ -550,7 +550,8 @@ pub fn exp_fp12_bls12_381( /// # Safety /// - `ret` must point to a valid `[u64; 72]` for the output. /// - `a` and `b` must point to valid `[u64; 72]` Fp12 elements. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_mul_fp12_bls12_381_c")] pub unsafe extern "C" fn mul_fp12_bls12_381_c( ret: *mut u64, a: *const u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs index 97be06bea..03b635dc0 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs @@ -252,7 +252,8 @@ pub fn conjugate_fp2_bls12_381( /// # Safety /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 12]` (96 bytes). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_add_fp2_bls12_381_c")] pub unsafe extern "C" fn add_fp2_bls12_381_c( a: *mut u64, b: *const u64, @@ -275,7 +276,8 @@ pub unsafe extern "C" fn add_fp2_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_dbl_fp2_bls12_381_c")] pub unsafe extern "C" fn dbl_fp2_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -297,7 +299,8 @@ pub unsafe extern "C" fn dbl_fp2_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_neg_fp2_bls12_381_c")] pub unsafe extern "C" fn neg_fp2_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -320,7 +323,8 @@ pub unsafe extern "C" fn neg_fp2_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 12]` (96 bytes). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_sub_fp2_bls12_381_c")] pub unsafe extern "C" fn sub_fp2_bls12_381_c( a: *mut u64, b: *const u64, @@ -344,7 +348,8 @@ pub unsafe extern "C" fn sub_fp2_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 12]` (96 bytes). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_mul_fp2_bls12_381_c")] pub unsafe extern "C" fn mul_fp2_bls12_381_c( a: *mut u64, b: *const u64, @@ -367,7 +372,8 @@ pub unsafe extern "C" fn mul_fp2_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_square_fp2_bls12_381_c")] pub unsafe extern "C" fn square_fp2_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -390,7 +396,8 @@ pub unsafe extern "C" fn square_fp2_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. /// - Element must be non-zero. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_inv_fp2_bls12_381_c")] pub unsafe extern "C" fn inv_fp2_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs index ab99b2a1d..832b0d9a6 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs @@ -118,7 +118,8 @@ pub fn square_fr_bls12_381( /// # Safety /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 4]` (32 bytes). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_add_fr_bls12_381_c")] pub unsafe extern "C" fn add_fr_bls12_381_c( a: *mut u64, b: *const u64, @@ -145,7 +146,8 @@ pub unsafe extern "C" fn add_fr_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_dbl_fr_bls12_381_c")] pub unsafe extern "C" fn dbl_fr_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -171,7 +173,8 @@ pub unsafe extern "C" fn dbl_fr_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 4]` (32 bytes). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_sub_fr_bls12_381_c")] pub unsafe extern "C" fn sub_fr_bls12_381_c( a: *mut u64, b: *const u64, @@ -198,7 +201,8 @@ pub unsafe extern "C" fn sub_fr_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_neg_fr_bls12_381_c")] pub unsafe extern "C" fn neg_fr_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -224,7 +228,8 @@ pub unsafe extern "C" fn neg_fr_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. /// - `b` must point to a valid `[u64; 4]` (32 bytes). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_mul_fr_bls12_381_c")] pub unsafe extern "C" fn mul_fr_bls12_381_c( a: *mut u64, b: *const u64, @@ -251,7 +256,8 @@ pub unsafe extern "C" fn mul_fr_bls12_381_c( /// # Safety /// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_square_fr_bls12_381_c")] pub unsafe extern "C" fn square_fr_bls12_381_c( a: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec, diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs index f3fb6d4bb..bd2714b55 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs @@ -577,7 +577,8 @@ fn dbl_twist_with_hints_bls12_381( /// - `ret` must point to a valid `[u64; 72]` for the Fp12 output. /// - `q` must point to a valid `[u64; 24]` for the G2 affine point. /// - `p` must point to a valid `[u64; 12]` for the G1 affine point. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_miller_loop_bls12_381_c")] pub unsafe extern "C" fn miller_loop_bls12_381_c( ret: *mut u64, q: *const u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs index f65338a85..56bbe4fe6 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs @@ -103,7 +103,8 @@ pub fn pairing_batch_bls12_381( /// - `q1` and `q2` must point to at least 24 u64s each /// /// Returns 1 if e(P₁, Q₁) == e(P₂, Q₂), 0 otherwise -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_pairing_verify_bls12_381_c")] pub unsafe extern "C" fn pairing_verify_bls12_381_c( p1_ptr: *const u64, q1_ptr: *const u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs index 249e61ae4..6011e8fed 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs @@ -634,7 +634,8 @@ pub fn utf_endomorphism_twist_bls12_381( /// - 0 = success (regular point) /// - 1 = success (point at infinity) /// - 2 = error -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_decompress_twist_bls12_381_c")] pub unsafe extern "C" fn decompress_twist_bls12_381_c( ret: *mut u64, input: *const u8, @@ -663,7 +664,8 @@ pub unsafe extern "C" fn decompress_twist_bls12_381_c( /// # Safety /// - `p` must point to a valid `[u64; 24]` (192 bytes) for the input point. /// Returns true if the point is on the twist curve, false otherwise. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_is_on_curve_twist_bls12_381_c")] pub unsafe extern "C" fn is_on_curve_twist_bls12_381_c( p: *const u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -679,7 +681,8 @@ pub unsafe extern "C" fn is_on_curve_twist_bls12_381_c( /// # Safety /// - `p` must point to a valid `[u64; 24]` (192 bytes) for the input point. /// Returns true if the point is in the G2 subgroup, false otherwise. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_is_on_subgroup_twist_bls12_381_c")] pub unsafe extern "C" fn is_on_subgroup_twist_bls12_381_c( p: *const u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -695,7 +698,8 @@ pub unsafe extern "C" fn is_on_subgroup_twist_bls12_381_c( /// # Safety /// - `p1` must point to a valid `[u64; 24]` (192 bytes), used as both input and output. /// - `p2` must point to a valid `[u64; 24]` (192 bytes). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_add_twist_bls12_381_c")] pub unsafe extern "C" fn add_twist_bls12_381_c( p1: *mut u64, p2: *const u64, @@ -723,7 +727,8 @@ pub unsafe extern "C" fn add_twist_bls12_381_c( /// - `ret` must point to a valid `[u64; 24]` for the output affine point. /// - `p` must point to a valid `[u64; 24]` affine point. /// - `k` must point to a valid `[u64; 6]` scalar. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_scalar_mul_twist_bls12_381_c")] pub unsafe extern "C" fn scalar_mul_twist_bls12_381_c( ret: *mut u64, p: *const u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs index b72e6175f..e3bf05fbc 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs @@ -259,7 +259,8 @@ pub fn mul_bn254( /// # Safety /// `p` must point to a valid `[u64; 8]` (64 bytes, affine G1 point). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_is_on_curve_bn254_c")] pub unsafe extern "C" fn is_on_curve_bn254_c( p_ptr: *const u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -275,7 +276,8 @@ pub unsafe extern "C" fn is_on_curve_bn254_c( /// # Safety /// - `p` must point to a valid `[u64; 12]` (96 bytes, Jacobian G1 point). /// - `out` must point to a valid `[u64; 8]` (64 bytes) writable buffer. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_to_affine_bn254_c")] pub unsafe extern "C" fn to_affine_bn254_c( p_ptr: *const u64, out_ptr: *mut u64, @@ -304,7 +306,8 @@ pub unsafe extern "C" fn to_affine_bn254_c( /// - `p1_ptr` must point to a valid `[u64; 8]` (64 bytes, affine G1 point). /// - `p2_ptr` must point to a valid `[u64; 8]` (64 bytes, affine G1 point). /// - `out_ptr` must point to a valid `[u64; 8]` (64 bytes) writable buffer. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_add_bn254_c")] pub unsafe extern "C" fn add_bn254_c( p1_ptr: *const u64, p2_ptr: *const u64, @@ -335,7 +338,8 @@ pub unsafe extern "C" fn add_bn254_c( /// - `p_ptr` must point to a valid `[u64; 8]` (64 bytes, affine G1 point). /// - `k_ptr` must point to a valid `[u64; 4]` (32 bytes, scalar). /// - `out_ptr` must point to a valid `[u64; 8]` (64 bytes) writable buffer. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_mul_bn254_c")] pub unsafe extern "C" fn mul_bn254_c( p_ptr: *const u64, k_ptr: *const u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs index 4297bc0a9..91bd15e6e 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs @@ -107,7 +107,8 @@ pub fn pairing_batch_bn254( /// each being `[u64; 16]` (128 bytes per point). /// - `out_ptr` must point to a valid `[u64; 48]` (384 bytes) writable buffer for the GT result. /// - `num_points` must correctly reflect the number of points in both arrays. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_pairing_batch_bn254_c")] pub unsafe extern "C" fn pairing_batch_bn254_c( g1_ptr: *const u64, g2_ptr: *const u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs index 4fb630575..358125d45 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs @@ -425,7 +425,8 @@ pub fn utf_endomorphism_twist_bn254( /// # Safety /// `p_ptr` must point to a valid `[u64; 16]` (128 bytes, affine G2 twist point). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_is_on_curve_twist_bn254_c")] pub unsafe extern "C" fn is_on_curve_twist_bn254_c( p_ptr: *const u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -440,7 +441,8 @@ pub unsafe extern "C" fn is_on_curve_twist_bn254_c( /// # Safety /// `p_ptr` must point to a valid `[u64; 16]` (128 bytes, affine G2 twist point). -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_is_on_subgroup_twist_bn254_c")] pub unsafe extern "C" fn is_on_subgroup_twist_bn254_c( p_ptr: *const u64, #[cfg(feature = "hints")] hints: &mut Vec, @@ -456,7 +458,8 @@ pub unsafe extern "C" fn is_on_subgroup_twist_bn254_c( /// # Safety /// - `p_ptr` must point to a valid `[u64; 24]` (192 bytes, Jacobian G2 twist point). /// - `out_ptr` must point to a valid `[u64; 16]` (128 bytes) writable buffer. -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_to_affine_twist_bn254_c")] pub unsafe extern "C" fn to_affine_twist_bn254_c( p_ptr: *const u64, out_ptr: *mut u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index 30391e16f..bc36ea469 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -508,7 +508,8 @@ pub fn secp256k1_ecdsa_verify( /// - `out_ptr` must point to at least 8 u64s (will write affine x[4], y[4]) /// /// Returns 1 on success, 0 if point is at infinity -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_to_affine_c")] pub unsafe extern "C" fn secp256k1_to_affine_c( p_ptr: *const u64, out_ptr: *mut u64, @@ -536,7 +537,8 @@ pub unsafe extern "C" fn secp256k1_to_affine_c( /// - `out_ptr` must point to at least 8 u64s (will write x[4] and y[4] in little-endian) /// /// Returns 1 on success, 0 if no valid point exists -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_decompress_c")] pub unsafe extern "C" fn secp256k1_decompress_c( x_bytes_ptr: *const u8, y_is_odd: u8, @@ -575,7 +577,8 @@ pub unsafe extern "C" fn secp256k1_decompress_c( /// - `out_ptr` must point to at least 8 u64s (will write result x[4], y[4]) /// /// Returns 1 if result is point at infinity, 0 otherwise -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_double_scalar_mul_with_g_c")] pub unsafe extern "C" fn secp256k1_double_scalar_mul_with_g_c( k1_ptr: *const u64, k2_ptr: *const u64, @@ -618,7 +621,8 @@ pub unsafe extern "C" fn secp256k1_double_scalar_mul_with_g_c( /// - `s_ptr` must point to 4 u64s (signature s) /// /// Returns 1 if signature is valid, 0 otherwise -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_ecdsa_verify_c")] pub unsafe extern "C" fn secp256k1_ecdsa_verify_c( pk_ptr: *const u64, z_ptr: *const u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs index 081aa7f7d..5fc5406f6 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs @@ -195,7 +195,8 @@ pub fn secp256k1_fp_sqrt( /// # Safety /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_reduce_c")] pub unsafe extern "C" fn secp256k1_fp_reduce_c( x_ptr: *const u64, out_ptr: *mut u64, @@ -234,7 +235,8 @@ pub unsafe extern "C" fn secp256k1_fp_reduce_c( /// - `x_ptr` must point to 4 u64s /// - `y_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_add_c")] pub unsafe extern "C" fn secp256k1_fp_add_c( x_ptr: *const u64, y_ptr: *const u64, @@ -261,7 +263,8 @@ pub unsafe extern "C" fn secp256k1_fp_add_c( /// # Safety /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_negate_c")] pub unsafe extern "C" fn secp256k1_fp_negate_c( x_ptr: *const u64, out_ptr: *mut u64, @@ -292,7 +295,8 @@ pub unsafe extern "C" fn secp256k1_fp_negate_c( /// - `x_ptr` must point to 4 u64s /// - `y_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_mul_c")] pub unsafe extern "C" fn secp256k1_fp_mul_c( x_ptr: *const u64, y_ptr: *const u64, @@ -320,7 +324,8 @@ pub unsafe extern "C" fn secp256k1_fp_mul_c( /// - `x_ptr` must point to 4 u64s /// - `scalar` is a single u64 value /// - `out_ptr` must point to at least 4 u64s -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_mul_scalar_c")] pub unsafe extern "C" fn secp256k1_fp_mul_scalar_c( x_ptr: *const u64, scalar: u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs index 53e4ef923..572206bfb 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs @@ -129,7 +129,8 @@ pub fn secp256k1_fn_inv(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec< /// # Safety /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_reduce_c")] pub unsafe extern "C" fn secp256k1_fn_reduce_c( x_ptr: *const u64, out_ptr: *mut u64, @@ -168,7 +169,8 @@ pub unsafe extern "C" fn secp256k1_fn_reduce_c( /// - `x_ptr` must point to 4 u64s /// - `y_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_add_c")] pub unsafe extern "C" fn secp256k1_fn_add_c( x_ptr: *const u64, y_ptr: *const u64, @@ -195,7 +197,8 @@ pub unsafe extern "C" fn secp256k1_fn_add_c( /// # Safety /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_neg_c")] pub unsafe extern "C" fn secp256k1_fn_neg_c( x_ptr: *const u64, out_ptr: *mut u64, @@ -226,7 +229,8 @@ pub unsafe extern "C" fn secp256k1_fn_neg_c( /// - `x_ptr` must point to 4 u64s /// - `y_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_sub_c")] pub unsafe extern "C" fn secp256k1_fn_sub_c( x_ptr: *const u64, y_ptr: *const u64, @@ -254,7 +258,8 @@ pub unsafe extern "C" fn secp256k1_fn_sub_c( /// - `x_ptr` must point to 4 u64s /// - `y_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_mul_c")] pub unsafe extern "C" fn secp256k1_fn_mul_c( x_ptr: *const u64, y_ptr: *const u64, @@ -281,7 +286,8 @@ pub unsafe extern "C" fn secp256k1_fn_mul_c( /// # Safety /// - `x_ptr` must point to 4 u64s (non-zero element) /// - `out_ptr` must point to at least 4 u64s -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_inv_c")] pub unsafe extern "C" fn secp256k1_fn_inv_c( x_ptr: *const u64, out_ptr: *mut u64, diff --git a/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs b/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs index e4854dbbc..bce378abf 100644 --- a/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs +++ b/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs @@ -26,7 +26,8 @@ pub fn sha256f_compress( /// # Safety /// - `state_ptr` must point to at least 8 u32s (will be read and written) /// - `blocks_ptr` must point to at least `num_blocks * 64` bytes -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_sha256f_compress_c")] pub unsafe extern "C" fn sha256f_compress_c( state_ptr: *mut u32, blocks_ptr: *const u8, From dec38ae66ff167ef566eb6fd84c7974627c63281 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 31 Dec 2025 13:29:45 +0000 Subject: [PATCH 154/782] added documentation about how ziskos-hints works --- ziskos-hints/Cargo.toml | 6 +++++ ziskos-hints/README.md | 60 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 ziskos-hints/README.md diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index b954043e0..4d0c5850d 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -1,3 +1,9 @@ +# Wrapper crate around ziskos that compiles with hints feature enabled. +# +# src/ is a symlink to ../ziskos/entrypoint/src/ - same source, different features. +# Exports C symbols with "hints_" prefix to avoid linker conflicts with ziskos. +# See README.md for details. + [package] name = "ziskos-hints" version = { workspace = true } diff --git a/ziskos-hints/README.md b/ziskos-hints/README.md new file mode 100644 index 000000000..857b756ed --- /dev/null +++ b/ziskos-hints/README.md @@ -0,0 +1,60 @@ +# ziskos-hints + +This crate is a **wrapper around `ziskos`** that compiles the same source code with the `hints` feature enabled. + +## How it works + +### Symlinked Source +The `src/` directory in this crate is a **symlink** to `../ziskos/entrypoint/src/`. This means: +- Both `ziskos` and `ziskos-hints` compile from the **same source files** +- No code duplication is needed +- Changes to the source are automatically reflected in both crates + +### Conditional Compilation +The source code uses conditional compilation to export different C symbols: + +```rust +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_")] +pub extern "C" fn (...) { ... } +``` + +When compiled: +- **ziskos** (no hints feature): Exports C symbol `` +- **ziskos-hints** (hints feature enabled): Exports C symbol `hints_` + +### Why This Pattern? + +This solves a Cargo limitation: **feature unification**. In a single build, if multiple crates depend on the same crate, Cargo unifies their features. This means you cannot have different feature sets for the same dependency. + +By creating a separate crate (`ziskos-hints`) that always enables the `hints` feature, we can: +1. Use `ziskos` without hints in most places +2. Use `ziskos-hints` with hints where needed (e.g., in `precompiles-hints`) +3. Link both into the same binary without symbol conflicts + +The different C symbol names (`` vs `hints_`) prevent linker duplicate symbol errors. + +## Usage + +In your `Cargo.toml`: +```toml +# For normal usage without hints: +ziskos = { workspace = true } + +# For usage with hints enabled: +ziskos-hints = { workspace = true } +``` + +From Rust code, both have the same API: +```rust +use ziskos::syscall_arith256_mod; + +// or +use ziskos_hints::syscall_arith256_mod; + +// or rename for consistency: +use ziskos_hints as ziskos; +use ziskos::syscall_arith256_mod; +``` + +The function name in Rust is the same; only the exported C symbol differs. From f80302a112f9ccefd90597e09673c0040dd7cda8 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 31 Dec 2025 15:22:48 +0000 Subject: [PATCH 155/782] improvements in ziskos-hints --- precompiles/hints/src/hints_processor.rs | 40 ++----------------- ziskos-hints/Cargo.toml | 3 +- ziskos-hints/src | 1 - ziskos-hints/src/core | 1 + ziskos-hints/src/hints/mod.rs | 50 ++++++++++++++++++++++++ ziskos-hints/src/lib.rs | 14 +++++++ 6 files changed, 70 insertions(+), 39 deletions(-) delete mode 120000 ziskos-hints/src create mode 120000 ziskos-hints/src/core create mode 100644 ziskos-hints/src/hints/mod.rs create mode 100644 ziskos-hints/src/lib.rs diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 42447188c..cb1d6ae81 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -17,8 +17,6 @@ use zisk_common::{ HINTS_TYPE_RESULT, NUM_HINT_TYPES, }; -use ziskos_hints::zisklib; - /// Ordered result buffer with drain state. /// /// This structure maintains a VecDeque that holds processed results in order, @@ -425,7 +423,7 @@ impl PrecompileHintsProcessor { fn process_hint(hint: PrecompileHint) -> Result> { let result = match hint.hint_type { HINTS_TYPE_RESULT => hint.data, - HINTS_TYPE_ECRECOVER => Self::process_hint_ecrecover(hint)?, + HINTS_TYPE_ECRECOVER => Self::process_hint_ecrecover(&hint.data)?, _ => { return Err(anyhow::anyhow!("Unknown hint type: {}", hint.hint_type)); } @@ -436,40 +434,8 @@ impl PrecompileHintsProcessor { /// Processes a [`HINTS_TYPE_ECRECOVER`] hint. #[inline] - fn process_hint_ecrecover(hint: PrecompileHint) -> Result> { - const PK_SIZE: usize = 8; // x(4) + y(4) - const Z_SIZE: usize = 4; - const R_SIZE: usize = 4; - const S_SIZE: usize = 4; - const EXPECTED_LEN: usize = PK_SIZE + Z_SIZE + R_SIZE + S_SIZE; - - const Z_OFFSET: usize = PK_SIZE; - const R_OFFSET: usize = Z_OFFSET + Z_SIZE; - const S_OFFSET: usize = R_OFFSET + R_SIZE; - - if hint.data.len() != EXPECTED_LEN { - return Err(anyhow::anyhow!( - "Invalid ECRECOVER hint length: expected {}, got {}", - EXPECTED_LEN, - hint.data.len() - )); - } - - #[allow(unused_mut)] - let mut processed_hints = Vec::new(); - - // Safety: We've validated that hint.len() == 20, so all slice accesses are in bounds. - unsafe { - let ptr = hint.data.as_ptr(); - let pk = &*(ptr as *const u64); - let z = &*(ptr.add(Z_OFFSET) as *const u64); - let r = &*(ptr.add(R_OFFSET) as *const u64); - let s = &*(ptr.add(S_OFFSET) as *const u64); - - zisklib::secp256k1_ecdsa_verify_c(pk, z, r, s, &mut processed_hints); - } - - Ok(processed_hints) + fn process_hint_ecrecover(data: &[u64]) -> Result> { + ziskos_hints::hints::process_ecrecover_hint(data).map_err(|e| anyhow::anyhow!(e)) } } diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index 4d0c5850d..84e19d1b2 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -1,6 +1,7 @@ # Wrapper crate around ziskos that compiles with hints feature enabled. # -# src/ is a symlink to ../ziskos/entrypoint/src/ - same source, different features. +# src/core/ is a symlink to ../../ziskos/entrypoint/src/ - same source, different features. +# src/lib.rs wraps the symlinked source and adds hints-specific modules. # Exports C symbols with "hints_" prefix to avoid linker conflicts with ziskos. # See README.md for details. diff --git a/ziskos-hints/src b/ziskos-hints/src deleted file mode 120000 index 4954a43ef..000000000 --- a/ziskos-hints/src +++ /dev/null @@ -1 +0,0 @@ -../ziskos/entrypoint/src \ No newline at end of file diff --git a/ziskos-hints/src/core b/ziskos-hints/src/core new file mode 120000 index 000000000..4da0cb079 --- /dev/null +++ b/ziskos-hints/src/core @@ -0,0 +1 @@ +../../ziskos/entrypoint/src \ No newline at end of file diff --git a/ziskos-hints/src/hints/mod.rs b/ziskos-hints/src/hints/mod.rs new file mode 100644 index 000000000..34b34428d --- /dev/null +++ b/ziskos-hints/src/hints/mod.rs @@ -0,0 +1,50 @@ +//! Hint processing utilities for ziskos-hints + +use crate::zisklib; + +/// Processes an ECRECOVER hint. +/// +/// # Arguments +/// +/// * `data` - The hint data containing pk(8) + z(4) + r(4) + s(4) = 20 u64 values +/// +/// # Returns +/// +/// * `Ok(Vec)` - The processed hints from the verification +/// * `Err` - If the data length is invalid +#[inline] +pub fn process_ecrecover_hint(data: &[u64]) -> Result, String> { + const PK_SIZE: usize = 8; // x(4) + y(4) + const Z_SIZE: usize = 4; + const R_SIZE: usize = 4; + const S_SIZE: usize = 4; + const EXPECTED_LEN: usize = PK_SIZE + Z_SIZE + R_SIZE + S_SIZE; + + const Z_OFFSET: usize = PK_SIZE; + const R_OFFSET: usize = Z_OFFSET + Z_SIZE; + const S_OFFSET: usize = R_OFFSET + R_SIZE; + + if data.len() != EXPECTED_LEN { + return Err(format!( + "Invalid ECRECOVER hint length: expected {}, got {}", + EXPECTED_LEN, + data.len() + )); + } + + #[allow(unused_mut)] + let mut processed_hints = Vec::new(); + + // Safety: We've validated that data.len() == 20, so all slice accesses are in bounds. + unsafe { + let ptr = data.as_ptr(); + let pk = &*(ptr as *const u64); + let z = &*(ptr.add(Z_OFFSET) as *const u64); + let r = &*(ptr.add(R_OFFSET) as *const u64); + let s = &*(ptr.add(S_OFFSET) as *const u64); + + zisklib::secp256k1_ecdsa_verify_c(pk, z, r, s, &mut processed_hints); + } + + Ok(processed_hints) +} diff --git a/ziskos-hints/src/lib.rs b/ziskos-hints/src/lib.rs new file mode 100644 index 000000000..98f200795 --- /dev/null +++ b/ziskos-hints/src/lib.rs @@ -0,0 +1,14 @@ +//! ziskos-hints: ziskos compiled with hints feature enabled +//! +//! This crate compiles the symlinked core/ (which points to ziskos/entrypoint/src) +//! with the hints feature enabled, and adds hints-specific processing utilities. + +// Include the symlinked source as a module +#[path = "core/lib.rs"] +mod core; + +// Re-export everything from the symlinked implementation +pub use core::*; + +// Add hints-specific modules that only exist in ziskos-hints +pub mod hints; From ef80b7abf017e19380f49bd90400b860118354c2 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 31 Dec 2025 15:29:34 +0000 Subject: [PATCH 156/782] cargo clippy --- ziskos-hints/src/hints/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ziskos-hints/src/hints/mod.rs b/ziskos-hints/src/hints/mod.rs index 34b34428d..a2d338e79 100644 --- a/ziskos-hints/src/hints/mod.rs +++ b/ziskos-hints/src/hints/mod.rs @@ -38,10 +38,10 @@ pub fn process_ecrecover_hint(data: &[u64]) -> Result, String> { // Safety: We've validated that data.len() == 20, so all slice accesses are in bounds. unsafe { let ptr = data.as_ptr(); - let pk = &*(ptr as *const u64); - let z = &*(ptr.add(Z_OFFSET) as *const u64); - let r = &*(ptr.add(R_OFFSET) as *const u64); - let s = &*(ptr.add(S_OFFSET) as *const u64); + let pk = &*ptr; + let z = &*ptr.add(Z_OFFSET); + let r = &*ptr.add(R_OFFSET); + let s = &*ptr.add(S_OFFSET); zisklib::secp256k1_ecdsa_verify_c(pk, z, r, s, &mut processed_hints); } From 7d634eef24f09a6aa09ca4aef4b55873f4d699b7 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 31 Dec 2025 18:31:09 +0100 Subject: [PATCH 157/782] Implement ECRecover fcalls and syscalls hints --- ziskos/entrypoint/src/syscalls/arith256_mod.rs | 14 +++++++++++++- ziskos/entrypoint/src/syscalls/secp256k1_add.rs | 13 ++++++++++++- ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs | 12 +++++++++++- .../entrypoint/src/zisklib/fcalls/msb_pos_256.rs | 13 ++++++++++++- .../src/zisklib/fcalls/secp256k1_fn_inv.rs | 8 ++++++-- 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/ziskos/entrypoint/src/syscalls/arith256_mod.rs b/ziskos/entrypoint/src/syscalls/arith256_mod.rs index 7db9608ee..9fab2cb69 100644 --- a/ziskos/entrypoint/src/syscalls/arith256_mod.rs +++ b/ziskos/entrypoint/src/syscalls/arith256_mod.rs @@ -43,5 +43,17 @@ pub extern "C" fn syscall_arith256_mod( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x802, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + precompiles_helpers::arith256_mod( + params.a, + params.b, + params.c, + params.module, + &mut params.d, + ); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(params.d); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/secp256k1_add.rs b/ziskos/entrypoint/src/syscalls/secp256k1_add.rs index 16f2bff56..fd71fe47c 100644 --- a/ziskos/entrypoint/src/syscalls/secp256k1_add.rs +++ b/ziskos/entrypoint/src/syscalls/secp256k1_add.rs @@ -42,5 +42,16 @@ pub extern "C" fn syscall_secp256k1_add( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x803, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let p1 = [params.p1.x, params.p1.y].concat().try_into().unwrap(); + let p2 = [params.p2.x, params.p2.y].concat().try_into().unwrap(); + let mut p3: [u64; 8] = [0; 8]; + precompiles_helpers::secp256k1_add(&p1, &p2, &mut p3); + params.p1.x.copy_from_slice(&p3[0..4]); + params.p1.y.copy_from_slice(&p3[4..8]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&p3); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs b/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs index 82ed83aa2..2d2431d8c 100644 --- a/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs +++ b/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs @@ -34,5 +34,15 @@ pub extern "C" fn syscall_secp256k1_dbl( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x804, p1); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let _p1 = [p1.x, p1.y].concat().try_into().unwrap(); + let mut p3: [u64; 8] = [0; 8]; + precompiles_helpers::secp256k1_dbl(&_p1, &mut p3); + p1.x.copy_from_slice(&p3[0..4]); + p1.y.copy_from_slice(&p3[4..8]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&p3); + } + } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs index 20f60bab7..48e3e9516 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs @@ -1,9 +1,12 @@ use cfg_if::cfg_if; + cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_MSB_POS_256_ID; + } else { + use crate::zisklib::fcalls_impl::msb_pos_256::msb_pos_256; } } #[allow(unused_variables)] @@ -13,7 +16,15 @@ pub fn fcall_msb_pos_256( #[cfg(feature = "hints")] hints: &mut Vec, ) -> (u64, u64) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let (i, pos) = msb_pos_256(x, y); + #[cfg(feature = "hints")] + { + hints.push(i as u64); + hints.push(pos as u64); + } + (i as u64, pos as u64) + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(x, 4); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs index 4a2f659d5..420d2f891 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_SECP256K1_FN_INV_ID; + } else { + use lib_c::secp256k1_fn_inv_c; } } /// Executes the multiplicative inverse computation over the scalar field of the `secp256k1` curve. @@ -29,11 +31,13 @@ pub fn fcall_secp256k1_fn_inv( ) -> [u64; 4] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { + let mut result: [u64; 4] = [0; 4]; + secp256k1_fn_inv_c(p_value, &mut result); #[cfg(feature = "hints")] { - unimplemented!(); + hints.extend_from_slice(&result); } - unimplemented!(); // Change this line by returning the native result + result } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { From 25fcabd764fd700e7f38d185fcc8696c267cfa4c Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 1 Jan 2026 13:16:24 +0100 Subject: [PATCH 158/782] Implement rest of precompiles in native --- ziskos/entrypoint/src/syscalls/add256.rs | 7 +++++++ ziskos/entrypoint/src/syscalls/arith256.rs | 9 ++++++++- ziskos/entrypoint/src/syscalls/arith384_mod.rs | 14 +++++++++++++- .../src/syscalls/bls12_381_complex_add.rs | 13 ++++++++++++- .../src/syscalls/bls12_381_complex_mul.rs | 13 ++++++++++++- .../src/syscalls/bls12_381_complex_sub.rs | 13 ++++++++++++- .../entrypoint/src/syscalls/bls12_381_curve_add.rs | 13 ++++++++++++- .../entrypoint/src/syscalls/bls12_381_curve_dbl.rs | 12 +++++++++++- .../entrypoint/src/syscalls/bn254_complex_add.rs | 13 ++++++++++++- .../entrypoint/src/syscalls/bn254_complex_mul.rs | 13 ++++++++++++- .../entrypoint/src/syscalls/bn254_complex_sub.rs | 13 ++++++++++++- ziskos/entrypoint/src/syscalls/bn254_curve_add.rs | 13 ++++++++++++- ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs | 12 +++++++++++- 13 files changed, 146 insertions(+), 12 deletions(-) diff --git a/ziskos/entrypoint/src/syscalls/add256.rs b/ziskos/entrypoint/src/syscalls/add256.rs index d66e73e9a..38400c959 100644 --- a/ziskos/entrypoint/src/syscalls/add256.rs +++ b/ziskos/entrypoint/src/syscalls/add256.rs @@ -35,6 +35,13 @@ pub extern "C" fn syscall_add256( #[cfg(feature = "hints")] hints: &mut Vec, ) -> u64 { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + { + let cout = precompiles_helpers::add256(params.a, params.b, params.cin, &mut params.c); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(params.c); + } + } unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall_ret_u64!(0x811, params) diff --git a/ziskos/entrypoint/src/syscalls/arith256.rs b/ziskos/entrypoint/src/syscalls/arith256.rs index b89f4d8bf..a35d41eb0 100644 --- a/ziskos/entrypoint/src/syscalls/arith256.rs +++ b/ziskos/entrypoint/src/syscalls/arith256.rs @@ -40,5 +40,12 @@ pub extern "C" fn syscall_arith256( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x801, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + precompiles_helpers::arith256(params.a, params.b, params.c, &mut params.dl, &mut params.dh); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(params.dl); + hints.extend_from_slice(params.dh); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/arith384_mod.rs b/ziskos/entrypoint/src/syscalls/arith384_mod.rs index efb93fd80..1a1e8f119 100644 --- a/ziskos/entrypoint/src/syscalls/arith384_mod.rs +++ b/ziskos/entrypoint/src/syscalls/arith384_mod.rs @@ -43,5 +43,17 @@ pub extern "C" fn syscall_arith384_mod( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80B, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + precompiles_helpers::arith384_mod( + params.a, + params.b, + params.c, + params.module, + &mut params.d, + ); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(params.d); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs b/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs index 66fdb7235..2134e3416 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs @@ -43,5 +43,16 @@ pub extern "C" fn syscall_bls12_381_complex_add( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80E, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let f1 = [params.f1.x, params.f1.y].concat().try_into().unwrap(); + let f2 = [params.f2.x, params.f2.y].concat().try_into().unwrap(); + let mut f3: [u64; 12] = [0; 12]; + precompiles_helpers::bls12_381_complex_add(&f1, &f2, &mut f3); + params.f1.x.copy_from_slice(&f3[0..6]); + params.f1.y.copy_from_slice(&f3[6..12]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&f3); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs b/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs index 4f6f27d6e..775721e7d 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_complex_mul.rs @@ -43,5 +43,16 @@ pub extern "C" fn syscall_bls12_381_complex_mul( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x810, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let f1 = [params.f1.x, params.f1.y].concat().try_into().unwrap(); + let f2 = [params.f2.x, params.f2.y].concat().try_into().unwrap(); + let mut f3: [u64; 12] = [0; 12]; + precompiles_helpers::bls12_381_complex_mul(&f1, &f2, &mut f3); + params.f1.x.copy_from_slice(&f3[0..6]); + params.f1.y.copy_from_slice(&f3[6..12]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&f3); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs b/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs index 9412f8183..acb28b5fe 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_complex_sub.rs @@ -43,5 +43,16 @@ pub extern "C" fn syscall_bls12_381_complex_sub( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80F, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let f1 = [params.f1.x, params.f1.y].concat().try_into().unwrap(); + let f2 = [params.f2.x, params.f2.y].concat().try_into().unwrap(); + let mut f3: [u64; 12] = [0; 12]; + precompiles_helpers::bls12_381_complex_sub(&f1, &f2, &mut f3); + params.f1.x.copy_from_slice(&f3[0..6]); + params.f1.y.copy_from_slice(&f3[6..12]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&f3); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs b/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs index 795072825..2dcbf5359 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_curve_add.rs @@ -42,5 +42,16 @@ pub extern "C" fn syscall_bls12_381_curve_add( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80C, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let p1 = [params.p1.x, params.p1.y].concat().try_into().unwrap(); + let p2 = [params.p2.x, params.p2.y].concat().try_into().unwrap(); + let mut p3: [u64; 12] = [0; 12]; + precompiles_helpers::bls12_381_curve_add(&p1, &p2, &mut p3); + params.p1.x.copy_from_slice(&p3[0..6]); + params.p1.y.copy_from_slice(&p3[6..12]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&p3); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs b/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs index 1b2bd1177..5d7c76aa4 100644 --- a/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs +++ b/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs @@ -34,5 +34,15 @@ pub extern "C" fn syscall_bls12_381_curve_dbl( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80D, p1); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let _p1 = [p1.x, p1.y].concat().try_into().unwrap(); + let mut p2: [u64; 12] = [0; 12]; + precompiles_helpers::bls12_381_curve_dbl(&_p1, &mut p2); + p1.x.copy_from_slice(&p2[0..6]); + p1.y.copy_from_slice(&p2[6..12]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&p2); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs b/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs index 6febbb355..d8b1259eb 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs @@ -43,5 +43,16 @@ pub extern "C" fn syscall_bn254_complex_add( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x808, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let f1 = [params.f1.x, params.f1.y].concat().try_into().unwrap(); + let f2 = [params.f2.x, params.f2.y].concat().try_into().unwrap(); + let mut f3: [u64; 8] = [0; 8]; + precompiles_helpers::bn254_complex_add(&f1, &f2, &mut f3); + params.f1.x.copy_from_slice(&f3[0..4]); + params.f1.y.copy_from_slice(&f3[4..8]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&f3); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs b/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs index 20bc07e23..a2896a508 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_complex_mul.rs @@ -43,5 +43,16 @@ pub extern "C" fn syscall_bn254_complex_mul( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x80A, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let f1 = [params.f1.x, params.f1.y].concat().try_into().unwrap(); + let f2 = [params.f2.x, params.f2.y].concat().try_into().unwrap(); + let mut f3: [u64; 8] = [0; 8]; + precompiles_helpers::bn254_complex_mul(&f1, &f2, &mut f3); + params.f1.x.copy_from_slice(&f3[0..4]); + params.f1.y.copy_from_slice(&f3[4..8]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&f3); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs b/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs index fd674b17d..448b00fe3 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_complex_sub.rs @@ -43,5 +43,16 @@ pub extern "C" fn syscall_bn254_complex_sub( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x809, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let f1 = [params.f1.x, params.f1.y].concat().try_into().unwrap(); + let f2 = [params.f2.x, params.f2.y].concat().try_into().unwrap(); + let mut f3: [u64; 8] = [0; 8]; + precompiles_helpers::bn254_complex_sub(&f1, &f2, &mut f3); + params.f1.x.copy_from_slice(&f3[0..4]); + params.f1.y.copy_from_slice(&f3[4..8]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&f3); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs b/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs index b506cea00..27b28a3dd 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs @@ -42,5 +42,16 @@ pub extern "C" fn syscall_bn254_curve_add( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x806, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let p1 = [params.p1.x, params.p1.y].concat().try_into().unwrap(); + let p2 = [params.p2.x, params.p2.y].concat().try_into().unwrap(); + let mut p3: [u64; 8] = [0; 8]; + precompiles_helpers::bn254_curve_add(&p1, &p2, &mut p3); + params.p1.x.copy_from_slice(&p3[0..4]); + params.p1.y.copy_from_slice(&p3[4..8]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&p3); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs b/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs index 8f7a96659..1735925b8 100644 --- a/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs +++ b/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs @@ -34,5 +34,15 @@ pub extern "C" fn syscall_bn254_curve_dbl( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x807, p1); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let _p1 = [p1.x, p1.y].concat().try_into().unwrap(); + let mut p2: [u64; 8] = [0; 8]; + precompiles_helpers::bn254_curve_dbl(&_p1, &mut p2); + p1.x.copy_from_slice(&p2[0..4]); + p1.y.copy_from_slice(&p2[4..8]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&p2); + } + } } From 9076ceab879f7bfffebbfbf4ddc8dbccc0abcbee Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 12:06:00 +0000 Subject: [PATCH 159/782] improve hints_processor docs --- precompiles/hints/src/hints_processor.rs | 34 +++++++++++++----------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index cb1d6ae81..dbc89dcac 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -260,7 +260,7 @@ impl PrecompileHintsProcessor { } // Process the hint - let result = Self::process_hint(hint); + let result = Self::dispatch_hint(hint); // Store result and try to drain let mut queue = state.queue.lock().unwrap(); @@ -413,29 +413,33 @@ impl PrecompileHintsProcessor { /// /// # Arguments /// - /// * `hint` - The parsed hint to process + /// * `hint` - The parsed hint to dispatch /// /// # Returns /// - /// * `Ok(Vec)` - The processed result for this hint - /// * `Err` - If the hint type is unknown + /// The result produced by the selected hint handler. + /// + /// # Errors + /// + /// Returns an error if the hint type is unknown or if the handler fails. #[inline] - fn process_hint(hint: PrecompileHint) -> Result> { - let result = match hint.hint_type { - HINTS_TYPE_RESULT => hint.data, - HINTS_TYPE_ECRECOVER => Self::process_hint_ecrecover(&hint.data)?, - _ => { - return Err(anyhow::anyhow!("Unknown hint type: {}", hint.hint_type)); - } - }; + fn dispatch_hint(hint: PrecompileHint) -> Result> { + match hint.hint_type { + // When hint type is HINTS_TYPE_RESULT, return the data as-is. + HINTS_TYPE_RESULT => Ok(hint.data), - Ok(result) + // Dispatch to the ECRECOVER handler. + HINTS_TYPE_ECRECOVER => Self::process_hint_ecrecover(&hint), + + // Unknown hint type. + _ => Err(anyhow::anyhow!("Unknown hint type: {}", hint.hint_type)), + } } /// Processes a [`HINTS_TYPE_ECRECOVER`] hint. #[inline] - fn process_hint_ecrecover(data: &[u64]) -> Result> { - ziskos_hints::hints::process_ecrecover_hint(data).map_err(|e| anyhow::anyhow!(e)) + fn process_hint_ecrecover(hint: &PrecompileHint) -> Result> { + ziskos_hints::hints::process_ecrecover_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } } From cd72a0bf69ef5acb322151a00abe8917fad7989b Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 13:01:32 +0000 Subject: [PATCH 160/782] move hardcoded hints to an enum --- common/src/hints.rs | 77 +++++++++---- .../crates/coordinator/src/hints_relay.rs | 11 +- precompiles/hints/src/hints_processor.rs | 109 +++++++++--------- 3 files changed, 115 insertions(+), 82 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 20ac78f1f..56c121a15 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -45,31 +45,62 @@ //! - `0x05` (`HINTS_TYPE_ECRECOVER`): ECRECOVER inputs (currently returns empty) //! ``` -use anyhow::Result; - -/// Control code: Reset processor state and global sequence. -pub const CTRL_START: u32 = 0x00; +use std::fmt::Display; -/// Control code: Wait until completion of all pending hints. -pub const CTRL_END: u32 = 0x01; +use anyhow::Result; -/// Control code: Cancel current stream and stop processing. -pub const CTRL_CANCEL: u32 = 0x02; +/// Hint code representing either a control code or a data hint type. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u32)] +pub enum HintCode { + // CONTROL CODES + /// Control code: Reset processor state and global sequence. + CtrlStart = 0x00, + /// Control code: Wait until completion of all pending hints. + CtrlEnd = 0x01, + /// Control code: Cancel current stream and stop processing. + CtrlCancel = 0x02, + /// Control code: Signal error and stop processing. + CtrlError = 0x03, -/// Control code: Signal error and stop processing. -pub const CTRL_ERROR: u32 = 0x03; + // BUILT-IN HINT TYPES + /// Pass-through hint type. + /// When a hint has this type, the processor simply passes through the data + /// without any additional computation. + HintsTypeResult = 0x04, + /// Ecrecover precompile hint type. + HintsTypeEcrecover = 0x05, +} -/// Hint type indicating that the data is already the precomputed result. -/// -/// When a hint has this type, the processor simply passes through the data -/// without any additional computation. -pub const HINTS_TYPE_RESULT: u32 = 0x04; +impl TryFrom for HintCode { + type Error = anyhow::Error; -/// Hint type indicating that the data contains inputs for the ecrecover precompile. -pub const HINTS_TYPE_ECRECOVER: u32 = 0x05; + fn try_from(value: u32) -> Result { + match value { + 0x00 => Ok(HintCode::CtrlStart), + 0x01 => Ok(HintCode::CtrlEnd), + 0x02 => Ok(HintCode::CtrlCancel), + 0x03 => Ok(HintCode::CtrlError), + 0x04 => Ok(HintCode::HintsTypeResult), + 0x05 => Ok(HintCode::HintsTypeEcrecover), + _ => Err(anyhow::anyhow!("Invalid hint code: {:#x}", value)), + } + } +} -/// Number if hint types defined. -pub const NUM_HINT_TYPES: u32 = 6; +impl Display for HintCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = match self { + HintCode::CtrlStart => "CTRL_START", + HintCode::CtrlEnd => "CTRL_END", + HintCode::CtrlCancel => "CTRL_CANCEL", + HintCode::CtrlError => "CTRL_ERROR", + HintCode::HintsTypeResult => "HINTS_TYPE_RESULT", + HintCode::HintsTypeEcrecover => "HINTS_TYPE_ECRECOVER", + }; + write!(f, "{}", name) + } +} /// Represents a single precompile hint parsed from a `u64` slice. /// @@ -77,7 +108,7 @@ pub const NUM_HINT_TYPES: u32 = 6; /// determines how the data should be processed by the [`PrecompileHintsProcessor`]. pub struct PrecompileHint { /// The type of hint, determining how the data should be processed. - pub hint_type: u32, + pub hint_code: HintCode, /// The hint payload data. pub data: Vec, } @@ -90,7 +121,7 @@ impl std::fmt::Debug for PrecompileHint { format!("{:?}... ({} more)", &self.data[..10], self.data.len() - 10) }; f.debug_struct("PrecompileHint") - .field("hint_type", &self.hint_type) + .field("hint_type", &self.hint_code) .field("data", &data_display) .finish() } @@ -115,7 +146,7 @@ impl PrecompileHint { } let header = slice[idx]; - let hint_type = (header >> 32) as u32; + let hint_code = HintCode::try_from((header >> 32) as u32)?; let length = (header & 0xFFFFFFFF) as u32; if slice.len() < idx + length as usize + 1 { @@ -129,6 +160,6 @@ impl PrecompileHint { // Create a new Vec with the hint data. let data = slice[idx + 1..idx + length as usize + 1].to_vec(); - Ok(PrecompileHint { hint_type, data }) + Ok(PrecompileHint { hint_code, data }) } } diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index db5748807..a93b1af95 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -5,7 +5,7 @@ use std::future::Future; use std::pin::Pin; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; -use zisk_common::{io::StreamProcessor, PrecompileHint, CTRL_END, CTRL_START, NUM_HINT_TYPES}; +use zisk_common::{io::StreamProcessor, HintCode, PrecompileHint}; use zisk_distributed_common::StreamMessageKind; type AsyncDispatcher = Arc< @@ -53,12 +53,9 @@ impl PrecompileHintsRelay { let length = hint.data.len(); // Validate hint type is in valid range before accessing stats array - if hint.hint_type >= NUM_HINT_TYPES { - return Err(anyhow::anyhow!("Invalid hint type: {}", hint.hint_type)); - } // CTRL_START must be the first message of the first batch - if hint.hint_type == CTRL_START { + if hint.hint_code == HintCode::CtrlStart { if !first_batch { return Err(anyhow::anyhow!( "CTRL_START can only be sent as the first message in the stream" @@ -76,11 +73,11 @@ impl PrecompileHintsRelay { if has_ctrl_end { return Err(anyhow::anyhow!( "Received hint after CTRL_END: type {} at index {}", - hint.hint_type, + hint.hint_code, idx )); } - has_ctrl_end = hint.hint_type == CTRL_END; + has_ctrl_end = hint.hint_code == HintCode::CtrlEnd; idx += length + 1; } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index dbc89dcac..694b22b1c 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -6,16 +6,13 @@ use anyhow::Result; use rayon::{ThreadPool, ThreadPoolBuilder}; -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use tracing::debug; use zisk_common::io::{StreamProcessor, StreamSink}; -use zisk_common::{ - PrecompileHint, CTRL_CANCEL, CTRL_END, CTRL_ERROR, CTRL_START, HINTS_TYPE_ECRECOVER, - HINTS_TYPE_RESULT, NUM_HINT_TYPES, -}; +use zisk_common::{HintCode, PrecompileHint}; /// Ordered result buffer with drain state. /// @@ -69,8 +66,8 @@ pub struct PrecompileHintsProcessor { /// Shared state for parallel hint processing state: Arc, - /// Optional statistics collected during hint processing. - stats: [AtomicUsize; NUM_HINT_TYPES as usize], + /// Optional statistics collected during hint processing (for debugging). + stats: Mutex>, /// The hints sink used to submit processed hints (kept for ownership). #[allow(dead_code)] @@ -125,7 +122,7 @@ impl PrecompileHintsProcessor { Ok(Self { pool, state, - stats: Default::default(), + stats: Mutex::new(HashMap::new()), hints_sink, drainer_thread: ManuallyDrop::new(drainer_thread), }) @@ -167,16 +164,11 @@ impl PrecompileHintsProcessor { let hint = PrecompileHint::from_u64_slice(hints, idx)?; let length = hint.data.len(); - // Validate hint type is in valid range before accessing stats array - if hint.hint_type >= NUM_HINT_TYPES { - return Err(anyhow::anyhow!("Invalid hint type: {}", hint.hint_type)); - } - - self.stats[hint.hint_type as usize].fetch_add(1, Ordering::Relaxed); + self.stats.lock().unwrap().entry(hint.hint_code).and_modify(|c| *c += 1).or_insert(1); // Check if this is a control code or data hint type - match hint.hint_type { - CTRL_START => { + match HintCode::try_from(hint.hint_code)? { + HintCode::CtrlStart => { // CTRL_START must be the first message of the first batch if !first_batch { return Err(anyhow::anyhow!( @@ -195,7 +187,7 @@ impl PrecompileHintsProcessor { idx += length + 1; continue; } - CTRL_END => { + HintCode::CtrlEnd => { // Control hint only; wait for completion then set flag self.wait_for_completion()?; has_ctrl_end = true; @@ -212,19 +204,19 @@ impl PrecompileHintsProcessor { } break; } - CTRL_CANCEL => { + HintCode::CtrlCancel => { // Cancel current stream: set error and notify self.state.error_flag.store(true, Ordering::Release); self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream cancelled")); } - CTRL_ERROR => { + HintCode::CtrlError => { // External error signal self.state.error_flag.store(true, Ordering::Release); self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream error signalled")); } - _ => { + HintCode::HintsTypeResult | HintCode::HintsTypeEcrecover => { // Data hint type - process normally } } @@ -240,7 +232,7 @@ impl PrecompileHintsProcessor { }; // Handle HINTS_TYPE_RESULT synchronously - it doesn't need async processing - if hint.hint_type == HINTS_TYPE_RESULT { + if hint.hint_code == HintCode::HintsTypeResult { // Immediately mark this slot as complete { let mut queue = self.state.queue.lock().unwrap(); @@ -299,8 +291,11 @@ impl PrecompileHintsProcessor { if has_ctrl_end { debug!("Processed hints stats:"); - for (i, count) in self.stats.iter().enumerate() { - debug!("Hint type {}: {}", i, count.load(Ordering::Relaxed)); + let stats = self.stats.lock().unwrap(); + let mut sorted_stats: Vec<_> = stats.iter().collect(); + sorted_stats.sort_by_key(|(hint_code, _)| **hint_code as u32); + for (hint_code, count) in sorted_stats { + debug!("Hint type {}: {}", hint_code, count); } } @@ -424,15 +419,20 @@ impl PrecompileHintsProcessor { /// Returns an error if the hint type is unknown or if the handler fails. #[inline] fn dispatch_hint(hint: PrecompileHint) -> Result> { - match hint.hint_type { + match hint.hint_code { + // Control codes should not reach here + HintCode::CtrlStart + | HintCode::CtrlEnd + | HintCode::CtrlCancel + | HintCode::CtrlError => { + Err(anyhow::anyhow!("Control code {:?} should not be dispatched", hint.hint_code)) + } + // When hint type is HINTS_TYPE_RESULT, return the data as-is. - HINTS_TYPE_RESULT => Ok(hint.data), + HintCode::HintsTypeResult => Ok(hint.data), // Dispatch to the ECRECOVER handler. - HINTS_TYPE_ECRECOVER => Self::process_hint_ecrecover(&hint), - - // Unknown hint type. - _ => Err(anyhow::anyhow!("Unknown hint type: {}", hint.hint_type)), + HintCode::HintsTypeEcrecover => Self::process_hint_ecrecover(&hint), } } @@ -466,7 +466,7 @@ impl StreamProcessor for PrecompileHints #[cfg(test)] mod tests { - use zisk_common::{CTRL_CANCEL, CTRL_END, CTRL_ERROR, CTRL_START, HINTS_TYPE_RESULT}; + use zisk_common::HintCode; use super::*; @@ -494,7 +494,7 @@ mod tests { #[test] fn test_single_result_hint_non_blocking() { let p = processor(); - let data = vec![make_header(HINTS_TYPE_RESULT, 2), 0x111, 0x222]; + let data = vec![make_header(HintCode::HintsTypeResult as u32, 2), 0x111, 0x222]; // Dispatch should succeed and be non-blocking assert!(p.process_hints(&data, false).is_ok()); @@ -511,11 +511,11 @@ mod tests { fn test_multiple_hints_ordered_output() { let p = processor(); let data = vec![ - make_header(HINTS_TYPE_RESULT, 1), + make_header(HintCode::HintsTypeResult as u32, 1), 0x111, - make_header(HINTS_TYPE_RESULT, 1), + make_header(HintCode::HintsTypeResult as u32, 1), 0x222, - make_header(HINTS_TYPE_RESULT, 1), + make_header(HintCode::HintsTypeResult as u32, 1), 0x333, ]; assert!(p.process_hints(&data, false).is_ok()); @@ -530,8 +530,8 @@ mod tests { #[test] fn test_multiple_calls_global_sequence() { let p = processor(); - let data1 = vec![make_header(HINTS_TYPE_RESULT, 1), 0xAAA]; - let data2 = vec![make_header(HINTS_TYPE_RESULT, 1), 0xBBB]; + let data1 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0xAAA]; + let data2 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0xBBB]; assert!(p.process_hints(&data1, false).is_ok()); assert!(p.process_hints(&data2, false).is_ok()); @@ -571,7 +571,8 @@ mod tests { fn test_error_stops_wait() { let p = processor(); // First valid, then invalid type - let data = vec![make_header(HINTS_TYPE_RESULT, 1), 0x111, make_header(999, 0)]; + let data = + vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x111, make_header(999, 0)]; // Should error immediately when encountering invalid hint type let result = p.process_hints(&data, false); @@ -594,7 +595,7 @@ mod tests { assert!(!p.state.error_flag.load(Ordering::Acquire)); // Should be able to process new hints after reset - let good = vec![make_header(HINTS_TYPE_RESULT, 1), 0x42]; + let good = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x42]; assert!(p.process_hints(&good, false).is_ok()); assert!(p.wait_for_completion().is_ok()); @@ -608,7 +609,7 @@ mod tests { let p = processor(); // First batch increments sequence - let batch1 = vec![make_header(HINTS_TYPE_RESULT, 1), 0x01]; + let batch1 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x01]; p.process_hints(&batch1, false).unwrap(); p.wait_for_completion().unwrap(); @@ -619,7 +620,7 @@ mod tests { } // Send START control - should reset sequence - let start = vec![make_ctrl_header(CTRL_START, 0)]; + let start = vec![make_ctrl_header(HintCode::CtrlStart as u32, 0)]; p.process_hints(&start, true).unwrap(); // Sequence should be reset to 0 @@ -630,10 +631,10 @@ mod tests { } // Process new batch - let batch2 = vec![make_header(HINTS_TYPE_RESULT, 1), 0x02]; + let batch2 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x02]; p.process_hints(&batch2, false).unwrap(); - let end = vec![make_ctrl_header(CTRL_END, 0)]; + let end = vec![make_ctrl_header(HintCode::CtrlEnd as u32, 0)]; p.process_hints(&end, false).unwrap(); // Should have processed 1 hint (starting from 0 again) @@ -646,12 +647,16 @@ mod tests { let p = processor(); // Dispatch hints - let data = - vec![make_header(HINTS_TYPE_RESULT, 1), 0x10, make_header(HINTS_TYPE_RESULT, 1), 0x20]; + let data = vec![ + make_header(HintCode::HintsTypeResult as u32, 1), + 0x10, + make_header(HintCode::HintsTypeResult as u32, 1), + 0x20, + ]; p.process_hints(&data, false).unwrap(); // END should wait internally - let end = vec![make_ctrl_header(CTRL_END, 0)]; + let end = vec![make_ctrl_header(HintCode::CtrlEnd as u32, 0)]; p.process_hints(&end, false).unwrap(); // Buffer should already be empty @@ -668,7 +673,7 @@ mod tests { #[test] fn test_stream_cancel_returns_error() { let p = processor(); - let cancel = vec![make_ctrl_header(CTRL_CANCEL, 0)]; + let cancel = vec![make_ctrl_header(HintCode::CtrlCancel as u32, 0)]; let result = p.process_hints(&cancel, false); assert!(result.is_err()); @@ -681,7 +686,7 @@ mod tests { #[test] fn test_stream_error_signal_returns_error() { let p = processor(); - let signal_err = vec![make_ctrl_header(CTRL_ERROR, 0)]; + let signal_err = vec![make_ctrl_header(HintCode::CtrlError as u32, 0)]; let result = p.process_hints(&signal_err, false); assert!(result.is_err()); @@ -703,7 +708,7 @@ mod tests { let mut data = Vec::with_capacity(NUM_HINTS * 2); for i in 0..NUM_HINTS { - data.push(make_header(HINTS_TYPE_RESULT, 1)); + data.push(make_header(HintCode::HintsTypeResult as u32, 1)); data.push(i as u64); } @@ -740,7 +745,7 @@ mod tests { for batch_id in 0..NUM_BATCHES { let mut data = Vec::with_capacity(HINTS_PER_BATCH * 2); for i in 0..HINTS_PER_BATCH { - data.push(make_header(HINTS_TYPE_RESULT, 1)); + data.push(make_header(HintCode::HintsTypeResult as u32, 1)); data.push((batch_id * HINTS_PER_BATCH + i) as u64); } p.process_hints(&data, false).unwrap(); @@ -777,19 +782,19 @@ mod tests { for _iter in 0..ITERATIONS { // Reset at start of each iteration - let reset = vec![make_ctrl_header(CTRL_START, 0)]; + let reset = vec![make_ctrl_header(HintCode::CtrlStart as u32, 0)]; p.process_hints(&reset, true).unwrap(); // Process batch let mut data = Vec::with_capacity(HINTS_PER_ITER * 2); for i in 0..HINTS_PER_ITER { - data.push(make_header(HINTS_TYPE_RESULT, 1)); + data.push(make_header(HintCode::HintsTypeResult as u32, 1)); data.push(i as u64); } p.process_hints(&data, false).unwrap(); // End stream - let end = vec![make_ctrl_header(CTRL_END, 0)]; + let end = vec![make_ctrl_header(HintCode::CtrlEnd as u32, 0)]; p.process_hints(&end, false).unwrap(); } From cd5d594fc2596e0bc5a6b1b61cbf14aada5be8bc Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 13:09:35 +0000 Subject: [PATCH 161/782] rename PrecompilesHintsProcessor to HintsProcessor --- distributed/crates/worker/src/worker.rs | 6 +++--- executor/src/executor.rs | 8 ++++---- precompiles/hints/src/hints_processor.rs | 18 +++++++++--------- precompiles/hints/src/lib.rs | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index d9b463eb6..99d56333c 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,7 +1,7 @@ use anyhow::Result; use asm_runner::HintsShmem; use cargo_zisk::commands::{get_proving_key, get_witness_computation_lib}; -use precompiles_hints::PrecompileHintsProcessor; +use precompiles_hints::HintsProcessor; use proofman::{AggProofs, ContributionsInfo}; use rom_setup::{ gen_elf_hash, get_elf_bin_file_path, get_elf_data_hash, get_rom_blowup_factor_and_arity, @@ -264,7 +264,7 @@ pub struct Worker { prover_config: ProverConfig, stream_buffers: HashMap>)>, // (job_id, (next_seq, (seq_number, data))) - hints_processor: Option>, + hints_processor: Option>, } impl Worker { @@ -614,7 +614,7 @@ impl Worker { let unlock_mapped_memory = self.prover_config.unlock_mapped_memory; let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory)?; self.hints_processor = Some( - PrecompileHintsProcessor::new(hints_shmem) + HintsProcessor::new(hints_shmem) .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e))?, ); } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 7d0a54ac7..81e4beae8 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -25,7 +25,7 @@ use asm_runner::{ }; use fields::PrimeField64; use pil_std_lib::Std; -use precompiles_hints::PrecompileHintsProcessor; +use precompiles_hints::HintsProcessor; use proofman_common::{create_pool, BufferPool, ProofCtx, ProofmanResult, SetupCtx}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rayon::prelude::*; @@ -78,7 +78,7 @@ enum MinimalTraceExecutionMode { AsmWithCounter, } -pub type StreamHintsShmem = ZiskStream>; +pub type StreamHintsShmem = ZiskStream>; /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. @@ -199,8 +199,8 @@ impl ZiskExecutor { let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory) .expect("Failed to create HintsShmem"); - let hints_processor = PrecompileHintsProcessor::new(hints_shmem) - .expect("Failed to create PrecompileHintsProcessor"); + let hints_processor = + HintsProcessor::new(hints_shmem).expect("Failed to create PrecompileHintsProcessor"); let hints_stream = Mutex::new(ZiskStream::new(hints_processor)); diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 694b22b1c..c76231e04 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -59,7 +59,7 @@ impl HintProcessorState { /// This struct provides methods to parse and process a stream of concatenated /// hints, using a dedicated Rayon thread pool for parallel processing while /// preserving the original order of results. -pub struct PrecompileHintsProcessor { +pub struct HintsProcessor { /// The thread pool used for parallel hint processing. pool: ThreadPool, @@ -77,7 +77,7 @@ pub struct PrecompileHintsProcessor { drainer_thread: ManuallyDrop>, } -impl PrecompileHintsProcessor { +impl HintsProcessor { const DEFAULT_NUM_THREADS: usize = 32; /// Creates a new processor with the default number of threads. @@ -443,7 +443,7 @@ impl PrecompileHintsProcessor { } } -impl Drop for PrecompileHintsProcessor { +impl Drop for HintsProcessor { fn drop(&mut self) { // Signal drainer thread to shut down self.state.shutdown.store(true, Ordering::Release); @@ -458,7 +458,7 @@ impl Drop for PrecompileHintsProcessor StreamProcessor for PrecompileHintsProcessor { +impl StreamProcessor for HintsProcessor { fn process(&self, data: &[u64], first_batch: bool) -> Result { self.process_hints(data, first_batch) } @@ -486,8 +486,8 @@ mod tests { make_header(ctrl, length) } - fn processor() -> PrecompileHintsProcessor { - PrecompileHintsProcessor::with_num_threads(2, NullHints).unwrap() + fn processor() -> HintsProcessor { + HintsProcessor::with_num_threads(2, NullHints).unwrap() } // Positive tests @@ -701,7 +701,7 @@ mod tests { fn test_stress_throughput() { use std::time::Instant; - let p = PrecompileHintsProcessor::with_num_threads(32, NullHints).unwrap(); + let p = HintsProcessor::with_num_threads(32, NullHints).unwrap(); // Generate a large batch of hints const NUM_HINTS: usize = 100_000; @@ -734,7 +734,7 @@ mod tests { fn test_stress_concurrent_batches() { use std::time::Instant; - let p = PrecompileHintsProcessor::with_num_threads(32, NullHints).unwrap(); + let p = HintsProcessor::with_num_threads(32, NullHints).unwrap(); const NUM_BATCHES: usize = 1_000; const HINTS_PER_BATCH: usize = 100; @@ -773,7 +773,7 @@ mod tests { fn test_stress_with_resets() { use std::time::Instant; - let p = PrecompileHintsProcessor::with_num_threads(32, NullHints).unwrap(); + let p = HintsProcessor::with_num_threads(32, NullHints).unwrap(); const ITERATIONS: usize = 100; const HINTS_PER_ITER: usize = 1_000; diff --git a/precompiles/hints/src/lib.rs b/precompiles/hints/src/lib.rs index 3d39d0006..82eefa35c 100644 --- a/precompiles/hints/src/lib.rs +++ b/precompiles/hints/src/lib.rs @@ -1,3 +1,3 @@ mod hints_processor; -pub use hints_processor::PrecompileHintsProcessor; +pub use hints_processor::HintsProcessor; From 57cd1f6a033cbf1d11ae636e1ff2ea5a4dd9a8f3 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 13:24:55 +0000 Subject: [PATCH 162/782] created a HintsProcessorBuilder --- distributed/crates/worker/src/worker.rs | 3 +- executor/src/executor.rs | 5 +- precompiles/hints/src/hints_processor.rs | 192 ++++++++++++++++------- 3 files changed, 143 insertions(+), 57 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 99d56333c..20279811d 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -614,7 +614,8 @@ impl Worker { let unlock_mapped_memory = self.prover_config.unlock_mapped_memory; let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory)?; self.hints_processor = Some( - HintsProcessor::new(hints_shmem) + HintsProcessor::builder(hints_shmem) + .build() .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e))?, ); } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 81e4beae8..c442cd7f4 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -199,8 +199,9 @@ impl ZiskExecutor { let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory) .expect("Failed to create HintsShmem"); - let hints_processor = - HintsProcessor::new(hints_shmem).expect("Failed to create PrecompileHintsProcessor"); + let hints_processor = HintsProcessor::builder(hints_shmem) + .build() + .expect("Failed to create PrecompileHintsProcessor"); let hints_stream = Mutex::new(ZiskStream::new(hints_processor)); diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index c76231e04..fb36cebf0 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -54,6 +54,58 @@ impl HintProcessorState { } } +/// Builder for configuring and constructing a [`HintsProcessor`]. +pub struct HintsProcessorBuilder { + hints_sink: HS, + num_threads: usize, + enable_stats: bool, +} + +impl HintsProcessorBuilder { + /// Sets the number of worker threads in the thread pool. + pub fn num_threads(mut self, num_threads: usize) -> Self { + self.num_threads = num_threads; + self + } + + /// Enables or disables statistics collection. + pub fn enable_stats(mut self, enable: bool) -> Self { + self.enable_stats = enable; + self + } + + /// Builds the [`HintsProcessor`] with the configured settings. + /// + /// # Returns + /// + /// * `Ok(HintsProcessor)` - Successfully constructed processor + /// * `Err` - If the thread pool fails to initialize + pub fn build(self) -> Result> { + let pool = ThreadPoolBuilder::new() + .num_threads(self.num_threads) + .build() + .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; + + let state = Arc::new(HintProcessorState::new()); + let hints_sink = Arc::new(self.hints_sink); + + // Spawn drainer thread + let drainer_state = Arc::clone(&state); + let drainer_sink = Arc::clone(&hints_sink); + let drainer_thread = std::thread::spawn(move || { + HintsProcessor::drainer_thread(drainer_state, drainer_sink); + }); + + Ok(HintsProcessor { + pool, + state, + stats: if self.enable_stats { Some(Mutex::new(HashMap::new())) } else { None }, + hints_sink, + drainer_thread: ManuallyDrop::new(drainer_thread), + }) + } +} + /// Processor for precompile hints that supports parallel execution. /// /// This struct provides methods to parse and process a stream of concatenated @@ -67,7 +119,7 @@ pub struct HintsProcessor { state: Arc, /// Optional statistics collected during hint processing (for debugging). - stats: Mutex>, + stats: Option>>, /// The hints sink used to submit processed hints (kept for ownership). #[allow(dead_code)] @@ -80,52 +132,26 @@ pub struct HintsProcessor { impl HintsProcessor { const DEFAULT_NUM_THREADS: usize = 32; - /// Creates a new processor with the default number of threads. - /// - /// The default is 32 threads. - /// - /// # Returns - /// - /// * `Ok(PrecompileHintsProcessor)` - The configured processor - /// * `Err` - If the thread pool fails to initialize - pub fn new(hints_sink: HS) -> Result { - Self::with_num_threads(Self::DEFAULT_NUM_THREADS, hints_sink) - } - - /// Creates a new processor with the specified number of threads. + /// Creates a builder for configuring a [`HintsProcessor`]. /// /// # Arguments /// - /// * `num_threads` - The number of worker threads in the pool /// * `hints_sink` - The sink used to submit processed hints /// - /// # Returns + /// # Examples /// - /// * `Ok(PrecompileHintsProcessor)` - The configured processor - /// * `Err` - If the thread pool fails to initialize - pub fn with_num_threads(num_threads: usize, hints_sink: HS) -> Result { - let pool = ThreadPoolBuilder::new() - .num_threads(num_threads) - .build() - .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; - - let state = Arc::new(HintProcessorState::new()); - let hints_sink = Arc::new(hints_sink); - - // Spawn drainer thread - let drainer_state = Arc::clone(&state); - let drainer_sink = Arc::clone(&hints_sink); - let drainer_thread = std::thread::spawn(move || { - Self::drainer_thread(drainer_state, drainer_sink); - }); - - Ok(Self { - pool, - state, - stats: Mutex::new(HashMap::new()), + /// ```ignore + /// let processor = HintsProcessor::builder(my_sink) + /// .num_threads(16) + /// .enable_stats(false) + /// .build()?; + /// ``` + pub fn builder(hints_sink: HS) -> HintsProcessorBuilder { + HintsProcessorBuilder { hints_sink, - drainer_thread: ManuallyDrop::new(drainer_thread), - }) + num_threads: Self::DEFAULT_NUM_THREADS, + enable_stats: false, + } } /// Processes hints in parallel with non-blocking, ordered output. @@ -164,7 +190,9 @@ impl HintsProcessor { let hint = PrecompileHint::from_u64_slice(hints, idx)?; let length = hint.data.len(); - self.stats.lock().unwrap().entry(hint.hint_code).and_modify(|c| *c += 1).or_insert(1); + if let Some(stats) = &self.stats { + stats.lock().unwrap().entry(hint.hint_code).and_modify(|c| *c += 1).or_insert(1); + } // Check if this is a control code or data hint type match HintCode::try_from(hint.hint_code)? { @@ -290,12 +318,14 @@ impl HintsProcessor { } if has_ctrl_end { - debug!("Processed hints stats:"); - let stats = self.stats.lock().unwrap(); - let mut sorted_stats: Vec<_> = stats.iter().collect(); - sorted_stats.sort_by_key(|(hint_code, _)| **hint_code as u32); - for (hint_code, count) in sorted_stats { - debug!("Hint type {}: {}", hint_code, count); + if let Some(stats) = &self.stats { + debug!("Processed hints stats:"); + let stats = stats.lock().unwrap(); + let mut sorted_stats: Vec<_> = stats.iter().collect(); + sorted_stats.sort_by_key(|(hint_code, _)| **hint_code as u32); + for (hint_code, count) in sorted_stats { + debug!("Hint type {}: {}", hint_code, count); + } } } @@ -487,7 +517,7 @@ mod tests { } fn processor() -> HintsProcessor { - HintsProcessor::with_num_threads(2, NullHints).unwrap() + HintsProcessor::builder(NullHints).num_threads(2).build().unwrap() } // Positive tests @@ -564,7 +594,7 @@ mod tests { // Should return error immediately during validation let result = p.process_hints(&data, false); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Invalid hint type")); + assert!(result.unwrap_err().to_string().contains("Invalid hint code")); } #[test] @@ -577,7 +607,7 @@ mod tests { // Should error immediately when encountering invalid hint type let result = p.process_hints(&data, false); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Invalid hint type")); + assert!(result.unwrap_err().to_string().contains("Invalid hint code")); } #[test] @@ -588,7 +618,7 @@ mod tests { // Should get synchronous error for invalid hint type assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Invalid hint type")); + assert!(result.unwrap_err().to_string().contains("Invalid hint code")); // Reset should clear any error state p.reset(); @@ -696,12 +726,66 @@ mod tests { assert!(p.state.error_flag.load(Ordering::Acquire)); } + // Builder tests + #[test] + fn test_builder_default() { + let p = HintsProcessor::builder(NullHints).build().unwrap(); + + // Should have stats disabled by default + assert!(p.stats.is_none()); + + // Should process hints normally + let data = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x42]; + assert!(p.process_hints(&data, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + } + + #[test] + fn test_builder_custom_threads() { + let p = HintsProcessor::builder(NullHints).num_threads(4).build().unwrap(); + + // Should process hints normally + let data = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x42]; + assert!(p.process_hints(&data, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + } + + #[test] + fn test_builder_stats_disabled() { + let p = HintsProcessor::builder(NullHints).enable_stats(false).build().unwrap(); + + // Stats should be None + assert!(p.stats.is_none()); + + // Should still process hints normally + let data = vec![ + make_header(HintCode::HintsTypeResult as u32, 1), + 0x111, + make_header(HintCode::HintsTypeResult as u32, 1), + 0x222, + ]; + assert!(p.process_hints(&data, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + } + + #[test] + fn test_builder_chaining() { + let p = + HintsProcessor::builder(NullHints).num_threads(8).enable_stats(true).build().unwrap(); + + assert!(p.stats.is_some()); + + let data = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x42]; + assert!(p.process_hints(&data, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + } + // Stress test #[test] fn test_stress_throughput() { use std::time::Instant; - let p = HintsProcessor::with_num_threads(32, NullHints).unwrap(); + let p = HintsProcessor::builder(NullHints).num_threads(32).build().unwrap(); // Generate a large batch of hints const NUM_HINTS: usize = 100_000; @@ -734,7 +818,7 @@ mod tests { fn test_stress_concurrent_batches() { use std::time::Instant; - let p = HintsProcessor::with_num_threads(32, NullHints).unwrap(); + let p = HintsProcessor::builder(NullHints).num_threads(32).build().unwrap(); const NUM_BATCHES: usize = 1_000; const HINTS_PER_BATCH: usize = 100; @@ -773,7 +857,7 @@ mod tests { fn test_stress_with_resets() { use std::time::Instant; - let p = HintsProcessor::with_num_threads(32, NullHints).unwrap(); + let p = HintsProcessor::builder(NullHints).num_threads(32).build().unwrap(); const ITERATIONS: usize = 100; const HINTS_PER_ITER: usize = 1_000; From a2472fd79b3092ed74a21d674849c7cb59b23c1d Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 1 Jan 2026 16:30:11 +0100 Subject: [PATCH 163/782] Implement rest of fcalls hints --- .../src/zisklib/fcalls/big_int256_div.rs | 12 +++++- .../src/zisklib/fcalls/big_int_div.rs | 14 ++++++- .../src/zisklib/fcalls/bin_decomp.rs | 14 ++++++- .../src/zisklib/fcalls/bls12_381_fp2_inv.rs | 11 +++++- .../src/zisklib/fcalls/bls12_381_fp2_sqrt.rs | 11 +++++- .../src/zisklib/fcalls/bls12_381_fp_inv.rs | 11 +++++- .../src/zisklib/fcalls/bls12_381_fp_sqrt.rs | 12 +++++- .../src/zisklib/fcalls/bls12_381_twist.rs | 29 +++++++++++++- .../entrypoint/src/zisklib/fcalls/bn254_fp.rs | 11 +++++- .../src/zisklib/fcalls/bn254_fp2.rs | 11 +++++- .../src/zisklib/fcalls/bn254_twist.rs | 29 +++++++++++++- .../src/zisklib/fcalls/msb_pos_384.rs | 12 +++++- .../src/zisklib/fcalls/secp256k1_fn_inv.rs | 9 ++++- .../src/zisklib/fcalls/secp256k1_fp_inv.rs | 21 +++++++++- .../src/zisklib/fcalls/secp256k1_fp_sqrt.rs | 21 +++++++++- .../src/zisklib/fcalls_impl/big_int256_div.rs | 2 +- .../src/zisklib/fcalls_impl/big_int_div.rs | 2 +- .../src/zisklib/fcalls_impl/bin_decomp.rs | 2 +- .../zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs | 12 +++++- .../zisklib/fcalls_impl/bls12_381_fp_sqrt.rs | 2 +- .../zisklib/fcalls_impl/bls12_381_twist.rs | 39 ++++++++++++++----- .../src/zisklib/fcalls_impl/bn254_twist.rs | 39 ++++++++++++++----- .../entrypoint/src/zisklib/fcalls_impl/mod.rs | 26 ++++++------- .../src/zisklib/fcalls_impl/msb_pos_384.rs | 2 +- .../zisklib/fcalls_impl/secp256k1_fp_sqrt.rs | 2 +- 25 files changed, 297 insertions(+), 59 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs index 9592ec704..2803567e8 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_BIG_INT256_DIV_ID; + } else { + use crate::zisklib::fcalls_impl::big_int256_div::big_int256_div; } } @@ -28,7 +30,15 @@ pub fn fcall_bigint256_div( #[cfg(feature = "hints")] hints: &mut Vec, ) -> ([u64; 4], [u64; 4]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let (quotient, remainder) = big_int256_div(a_value, b_value); + #[cfg(feature = "hints")] + { + hints.extend_from_slice("ient); + hints.extend_from_slice(&remainder); + } + (quotient, remainder) + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(a_value, 4); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs index 4a239c8fe..3366bc736 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_BIG_INT_DIV_ID; + } else { + use crate::zisklib::fcalls_impl::big_int_div::big_int_div_into; } } @@ -25,7 +27,17 @@ pub fn fcall_division( #[cfg(feature = "hints")] hints: &mut Vec, ) -> (usize, usize) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + big_int_div_into(a_value, b_value, &mut quo.to_vec(), &mut rem.to_vec()); + let len_quo = quo.len(); + let len_rem = rem.len(); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&quo); + hints.extend_from_slice(&rem); + } + return (len_quo, len_rem); + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { let len_a = a_value.len() as usize; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs index 35fbfcb61..19b4587e2 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs @@ -4,6 +4,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_BIN_DECOMP_ID; + } else { + use crate::zisklib::fcalls_impl::bin_decomp::bin_decomp; } } @@ -14,7 +16,17 @@ pub fn fcall_bin_decomp( #[cfg(feature = "hints")] hints: &mut Vec, ) -> (usize, Vec) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let len_x = x_val.len(); + let bits = bin_decomp(x_val, len_x); + let len_bits = bits.len(); + let bits_u64: Vec = bits.into_iter().map(|b| b as u64).collect(); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&bits_u64); + } + return (len_bits, bits_u64); + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { let len_x = x_val.len() as usize; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs index b87842040..a0bddba94 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_BLS12_381_FP2_INV_ID; + } else { + use crate::zisklib::fcalls_impl::bls12_381_fp2_inv::bls12_381_fp2_inv; } } @@ -27,7 +29,14 @@ pub fn fcall_bls12_381_fp2_inv( #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 12] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let result: [u64; 12] = bls12_381_fp2_inv(p_value); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&result); + } + result + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 12); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs index 9b3eb7978..df6cf9d5c 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_BLS12_381_FP2_SQRT_ID; + } else { + use crate::zisklib::fcalls_impl::bls12_381_fp2_sqrt::bls12_381_fp2_sqrt_13; } } @@ -27,7 +29,14 @@ pub fn fcall_bls12_381_fp2_sqrt( #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 13] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let result: [u64; 13] = bls12_381_fp2_sqrt_13(p_value); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&result); + } + result + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 16); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs index 51beb5925..67e799f23 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_BLS12_381_FP_INV_ID; + } else { + use crate::zisklib::fcalls_impl::bls12_381_fp_inv::bls12_381_fp_inv; } } @@ -27,7 +29,14 @@ pub fn fcall_bls12_381_fp_inv( #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 6] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let result: [u64; 6] = bls12_381_fp_inv(p_value); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&result); + } + result + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 8); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs index 9a002c86d..645a5829e 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_BLS12_381_FP_SQRT_ID; + } else { + use crate::zisklib::fcalls_impl::bls12_381_fp_sqrt::bls12_381_fp_sqrt; } } @@ -27,7 +29,15 @@ pub fn fcall_bls12_381_fp_sqrt( #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 7] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let mut result: [u64; 7] = [0; 7]; + bls12_381_fp_sqrt(p_value, &mut result); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&result); + } + result + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 8); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs index 570b71a0d..4bd7b2a8c 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs @@ -4,6 +4,9 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::{FCALL_BLS12_381_TWIST_ADD_LINE_COEFFS_ID, FCALL_BLS12_381_TWIST_DBL_LINE_COEFFS_ID}; + } else { + use crate::zisklib::fcalls_impl::bls12_381_twist::bls12_381_twist_add_line_coeffs; + use crate::zisklib::fcalls_impl::bls12_381_twist::bls12_381_twist_dbl_line_coeffs; } } @@ -22,7 +25,19 @@ pub fn fcall_bls12_381_add_line_coeffs( #[cfg(feature = "hints")] hints: &mut Vec, ) -> ([u64; 12], [u64; 12]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let x1: [u64; 12] = p1_value[0..12].try_into().unwrap(); + let y1: [u64; 12] = p1_value[12..24].try_into().unwrap(); + let x2: [u64; 12] = p2_value[0..12].try_into().unwrap(); + let y2: [u64; 12] = p2_value[12..24].try_into().unwrap(); + let (lambda, mu) = bls12_381_twist_add_line_coeffs(&x1, &y1, &x2, &y2); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&lambda); + hints.extend_from_slice(&mu); + } + (lambda, mu) + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p1_value, 24); @@ -75,7 +90,17 @@ pub fn fcall_bls12_381_dbl_line_coeffs( #[cfg(feature = "hints")] hints: &mut Vec, ) -> ([u64; 12], [u64; 12]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let x: [u64; 12] = p_value[0..12].try_into().unwrap(); + let y: [u64; 12] = p_value[12..24].try_into().unwrap(); + let (lambda, mu) = bls12_381_twist_dbl_line_coeffs(&x, &y); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&lambda); + hints.extend_from_slice(&mu); + } + (lambda, mu) + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 24); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs index 6731f9125..b2658f836 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_BN254_FP_INV_ID; + } else { + use crate::zisklib::fcalls_impl::bn254_fp::bn254_fp_inv; } } @@ -27,7 +29,14 @@ pub fn fcall_bn254_fp_inv( #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 4] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let result: [u64; 4] = bn254_fp_inv(p_value); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&result); + } + result + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 4); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs index fcdcffc8c..ec1c569bd 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_BN254_FP2_INV_ID; + } else { + use crate::zisklib::fcalls_impl::bn254_fp2::bn254_fp2_inv; } } @@ -27,7 +29,14 @@ pub fn fcall_bn254_fp2_inv( #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 8] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let result: [u64; 8] = bn254_fp2_inv(p_value); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&result); + } + result + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 8); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs index 3ab8c3d99..967ab9c4f 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs @@ -4,6 +4,9 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::{FCALL_BN254_TWIST_ADD_LINE_COEFFS_ID, FCALL_BN254_TWIST_DBL_LINE_COEFFS_ID}; + } else { + use crate::zisklib::fcalls_impl::bn254_twist::bn254_twist_add_line_coeffs; + use crate::zisklib::fcalls_impl::bn254_twist::bn254_twist_dbl_line_coeffs; } } @@ -22,7 +25,19 @@ pub fn fcall_bn254_add_line_coeffs( #[cfg(feature = "hints")] hints: &mut Vec, ) -> ([u64; 8], [u64; 8]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let x1: [u64; 8] = p1_value[0..8].try_into().unwrap(); + let y1: [u64; 8] = p1_value[8..16].try_into().unwrap(); + let x2: [u64; 8] = p2_value[0..8].try_into().unwrap(); + let y2: [u64; 8] = p2_value[8..16].try_into().unwrap(); + let (lambda, mu) = bn254_twist_add_line_coeffs(&x1, &y1, &x2, &y2); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&lambda); + hints.extend_from_slice(&mu); + } + (lambda, mu) + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p1_value, 16); @@ -67,7 +82,17 @@ pub fn fcall_bn254_dbl_line_coeffs( #[cfg(feature = "hints")] hints: &mut Vec, ) -> ([u64; 8], [u64; 8]) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let x1: [u64; 8] = p_value[0..8].try_into().unwrap(); + let y1: [u64; 8] = p_value[8..16].try_into().unwrap(); + let (lambda, mu) = bn254_twist_dbl_line_coeffs(&x1, &y1); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&lambda); + hints.extend_from_slice(&mu); + } + (lambda, mu) + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 16); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs index 0a1aaae6f..cd87317b2 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs @@ -4,6 +4,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_MSB_POS_384_ID; + } else { + use crate::zisklib::fcalls_impl::msb_pos_384::msb_pos_384; } } #[allow(unused_variables)] @@ -13,7 +15,15 @@ pub fn fcall_msb_pos_384( #[cfg(feature = "hints")] hints: &mut Vec, ) -> (u64, u64) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let (i, pos) = msb_pos_384(x, y); + #[cfg(feature = "hints")] + { + hints.push(i as u64); + hints.push(pos as u64); + } + (i as u64, pos as u64) + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(x, 8); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs index 420d2f891..6a166df1b 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs @@ -50,7 +50,14 @@ pub fn fcall_secp256k1_fn_inv( #[allow(unused_variables)] pub fn fcall2_secp256k1_fn_inv(p_value: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let mut result: [u64; 4] = [0; 4]; + secp256k1_fn_inv_c(p_value, &mut result); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&result); + } + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 4); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs index b0c6fc96a..9da0b8499 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_SECP256K1_FP_INV_ID; + } else { + use lib_c::secp256k1_fp_inv_c; } } @@ -29,7 +31,15 @@ pub fn fcall_secp256k1_fp_inv( #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 4] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let mut result: [u64; 4] = [0; 4]; + secp256k1_fp_inv_c(p_value, &mut result); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&result); + } + result + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 4); @@ -41,7 +51,14 @@ pub fn fcall_secp256k1_fp_inv( #[allow(unused_variables)] pub fn fcall2_secp256k1_fp_inv(p_value: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let mut result: [u64; 4] = [0; 4]; + secp256k1_fp_inv_c(p_value, &mut result); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&result); + } + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 4); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs index 63830a67f..ac56a1dda 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_SECP256K1_FP_SQRT_ID; + } else { + use crate::zisklib::fcalls_impl::secp256k1_fp_sqrt::secp256k1_fp_sqrt; } } @@ -30,7 +32,15 @@ pub fn fcall_secp256k1_fp_sqrt( #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 5] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let mut result: [u64; 5] = [0; 5]; + secp256k1_fp_sqrt(p_value, parity, &mut result); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&result); + } + result + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 4); @@ -53,7 +63,14 @@ pub fn fcall2_secp256k1_fp_sqrt( #[cfg(feature = "hints")] hints: &mut Vec, ) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let mut result: [u64; 5] = [0; 5]; + secp256k1_fp_sqrt(p_value, parity, &mut result); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&result); + } + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(p_value, 4); diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int256_div.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int256_div.rs index 0133ea26f..cddcf523d 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int256_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int256_div.rs @@ -18,7 +18,7 @@ pub fn fcall_big_int256_div(params: &[u64], results: &mut [u64]) -> i64 { 8 } -fn big_int256_div(a: &[u64; 4], b: &[u64; 4]) -> ([u64; 4], [u64; 4]) { +pub fn big_int256_div(a: &[u64; 4], b: &[u64; 4]) -> ([u64; 4], [u64; 4]) { let a_big = biguint_from_u64_digits(a); let b_big = biguint_from_u64_digits(b); let (quotient, remainder) = a_big.div_rem(&b_big); diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int_div.rs index 8ab86be34..07d61616e 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/big_int_div.rs @@ -26,7 +26,7 @@ pub fn fcall_big_int_div(params: &[u64], results: &mut [u64]) -> i64 { (2 + len_q + len_r) as i64 } -fn big_int_div_into(a: &[u64], b: &[u64], q: &mut Vec, r: &mut Vec) { +pub fn big_int_div_into(a: &[u64], b: &[u64], q: &mut Vec, r: &mut Vec) { let a_big = biguint_from_u64_digits(a); let b_big = biguint_from_u64_digits(b); diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bin_decomp.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bin_decomp.rs index 67f51e122..2986b20f7 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bin_decomp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bin_decomp.rs @@ -12,7 +12,7 @@ pub fn fcall_bin_decomp(parameters: &[u64], results: &mut [u64]) -> i64 { (1 + len_bits) as i64 } -fn bin_decomp(x: &[u64], len_x: usize) -> Vec { +pub fn bin_decomp(x: &[u64], len_x: usize) -> Vec { let mut decomposition = Vec::new(); let mut started = false; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs index 53fb5d392..f2f37e299 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp2_sqrt.rs @@ -51,6 +51,16 @@ pub fn fcall_bls12_381_fp2_sqrt(params: &[u64], results: &mut [u64]) -> i64 { // Get the input let a: &[u64; 12] = ¶ms[0..12].try_into().unwrap(); + // Perform the square root + let _results = bls12_381_fp2_sqrt_13(a); + results.copy_from_slice(&_results); + + 13 +} + +pub fn bls12_381_fp2_sqrt_13(a: &[u64; 12]) -> [u64; 13] { + let mut results = [0u64; 13]; + // Perform the square root let (sqrt, is_qr) = bls12_381_fp2_sqrt(a); results[0] = is_qr as u64; @@ -66,7 +76,7 @@ pub fn fcall_bls12_381_fp2_sqrt(params: &[u64], results: &mut [u64]) -> i64 { } else { results[1..13].copy_from_slice(&sqrt); } - 13 + results } /// Algorithm 9 from https://eprint.iacr.org/2012/685.pdf diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_sqrt.rs index 6f0c6192d..3a3954e2b 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_fp_sqrt.rs @@ -36,7 +36,7 @@ pub fn fcall_bls12_381_fp_sqrt(params: &[u64], results: &mut [u64]) -> i64 { 7 } -fn bls12_381_fp_sqrt(a: &[u64; 6], results: &mut [u64]) { +pub fn bls12_381_fp_sqrt(a: &[u64; 6], results: &mut [u64]) { let a_big = biguint_from_u64_digits(a); // Attempt to compute the square root of a diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_twist.rs index c8f045fa0..76f37bb3a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381_twist.rs @@ -11,6 +11,22 @@ pub fn fcall_bls12_381_twist_add_line_coeffs(params: &[u64], results: &mut [u64] let x2: &[u64; 12] = ¶ms[24..36].try_into().unwrap(); let y2: &[u64; 12] = ¶ms[36..48].try_into().unwrap(); + // Compute the line coefficients + let (lambda, mu) = bls12_381_twist_add_line_coeffs(x1, y1, x2, y2); + + // Store the result + results[0..12].copy_from_slice(&lambda); + results[12..24].copy_from_slice(&mu); + + 24 +} + +pub fn bls12_381_twist_add_line_coeffs( + x1: &[u64; 12], + y1: &[u64; 12], + x2: &[u64; 12], + y2: &[u64; 12], +) -> ([u64; 12], [u64; 12]) { // Compute 𝜆 = (y2 - y1)/(x2 - x1) let mut lambda = bls12_381_fp2_inv(&bls12_381_fp2_sub(x2, x1)); lambda = bls12_381_fp2_mul(&lambda, &bls12_381_fp2_sub(y2, y1)); @@ -18,11 +34,7 @@ pub fn fcall_bls12_381_twist_add_line_coeffs(params: &[u64], results: &mut [u64] // Compute 𝜇 = y - 𝜆x let mu = bls12_381_fp2_sub(y1, &bls12_381_fp2_mul(&lambda, x1)); - // Store the result - results[0..12].copy_from_slice(&lambda); - results[12..24].copy_from_slice(&mu); - - 24 + (lambda, mu) } /// Computes the coefficients (𝜆,𝜇) of the tangent line at the point (x,y) @@ -31,6 +43,17 @@ pub fn fcall_bls12_381_twist_dbl_line_coeffs(params: &[u64], results: &mut [u64] let x: &[u64; 12] = ¶ms[0..12].try_into().unwrap(); let y: &[u64; 12] = ¶ms[12..24].try_into().unwrap(); + // Compute the line coefficients + let (lambda, mu) = bls12_381_twist_dbl_line_coeffs(x, y); + + // Store the result + results[0..12].copy_from_slice(&lambda); + results[12..24].copy_from_slice(&mu); + + 24 +} + +pub fn bls12_381_twist_dbl_line_coeffs(x: &[u64; 12], y: &[u64; 12]) -> ([u64; 12], [u64; 12]) { // Compute 𝜆 = 3x²/2y let mut lambda = bls12_381_fp2_inv(&bls12_381_fp2_dbl(y)); let x_sq = bls12_381_fp2_square(x); @@ -39,9 +62,5 @@ pub fn fcall_bls12_381_twist_dbl_line_coeffs(params: &[u64], results: &mut [u64] // Compute 𝜇 = y - 𝜆x let mu = bls12_381_fp2_sub(y, &bls12_381_fp2_mul(&lambda, x)); - // Store the result - results[0..12].copy_from_slice(&lambda); - results[12..24].copy_from_slice(&mu); - - 24 + (lambda, mu) } diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_twist.rs index af0bd98c9..711718d08 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254_twist.rs @@ -11,6 +11,22 @@ pub fn fcall_bn254_twist_add_line_coeffs(params: &[u64], results: &mut [u64]) -> let x2: &[u64; 8] = ¶ms[16..24].try_into().unwrap(); let y2: &[u64; 8] = ¶ms[24..32].try_into().unwrap(); + // Call the addition line coefficients function + let (lambda, mu) = bn254_twist_add_line_coeffs(x1, y1, x2, y2); + + // Store the result + results[0..8].copy_from_slice(&lambda); + results[8..16].copy_from_slice(&mu); + + 16 +} + +pub fn bn254_twist_add_line_coeffs( + x1: &[u64; 8], + y1: &[u64; 8], + x2: &[u64; 8], + y2: &[u64; 8], +) -> ([u64; 8], [u64; 8]) { // Compute 𝜆 = (y2 - y1)/(x2 - x1) let mut lambda = bn254_fp2_inv(&bn254_fp2_sub(x2, x1)); lambda = bn254_fp2_mul(&lambda, &bn254_fp2_sub(y2, y1)); @@ -18,11 +34,7 @@ pub fn fcall_bn254_twist_add_line_coeffs(params: &[u64], results: &mut [u64]) -> // Compute 𝜇 = y - 𝜆x let mu = bn254_fp2_sub(y1, &bn254_fp2_mul(&lambda, x1)); - // Store the result - results[0..8].copy_from_slice(&lambda); - results[8..16].copy_from_slice(&mu); - - 16 + (lambda, mu) } /// Computes the coefficients (𝜆,𝜇) of the tangent line at the point (x,y) @@ -31,6 +43,17 @@ pub fn fcall_bn254_twist_dbl_line_coeffs(params: &[u64], results: &mut [u64]) -> let x: &[u64; 8] = ¶ms[0..8].try_into().unwrap(); let y: &[u64; 8] = ¶ms[8..16].try_into().unwrap(); + // Call the doubling line coefficients function + let (lambda, mu) = bn254_twist_dbl_line_coeffs(x, y); + + // Store the result + results[0..8].copy_from_slice(&lambda); + results[8..16].copy_from_slice(&mu); + + 16 +} + +pub fn bn254_twist_dbl_line_coeffs(x: &[u64; 8], y: &[u64; 8]) -> ([u64; 8], [u64; 8]) { // Compute 𝜆 = 3x²/2y let mut lambda = bn254_fp2_inv(&bn254_fp2_dbl(y)); let x_sq = bn254_fp2_square(x); @@ -39,11 +62,7 @@ pub fn fcall_bn254_twist_dbl_line_coeffs(params: &[u64], results: &mut [u64]) -> // Compute 𝜇 = y - 𝜆x let mu = bn254_fp2_sub(y, &bn254_fp2_mul(&lambda, x)); - // Store the result - results[0..8].copy_from_slice(&lambda); - results[8..16].copy_from_slice(&mu); - - 16 + (lambda, mu) } #[cfg(test)] diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs index dc40c736d..e70b08c9e 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs @@ -1,20 +1,20 @@ -mod big_int256_div; -mod big_int_div; -mod bin_decomp; -mod bls12_381_fp2_inv; -mod bls12_381_fp2_sqrt; -mod bls12_381_fp_inv; -mod bls12_381_fp_sqrt; -mod bls12_381_twist; -mod bn254_fp; -mod bn254_fp2; -mod bn254_twist; +pub mod big_int256_div; +pub mod big_int_div; +pub mod bin_decomp; +pub mod bls12_381_fp2_inv; +pub mod bls12_381_fp2_sqrt; +pub mod bls12_381_fp_inv; +pub mod bls12_381_fp_sqrt; +pub mod bls12_381_twist; +pub mod bn254_fp; +pub mod bn254_fp2; +pub mod bn254_twist; pub mod msb_pos_256; -mod msb_pos_384; +pub mod msb_pos_384; mod proxy; mod secp256k1_fn_inv; mod secp256k1_fp_inv; -mod secp256k1_fp_sqrt; +pub mod secp256k1_fp_sqrt; mod utils; pub use proxy::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_384.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_384.rs index 68985ad00..98898a706 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_384.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/msb_pos_384.rs @@ -9,7 +9,7 @@ pub fn fcall_msb_pos_384(parameters: &[u64], results: &mut [u64]) -> i64 { 2 } -fn msb_pos_384(x: &[u64; 6], y: &[u64; 6]) -> (usize, usize) { +pub fn msb_pos_384(x: &[u64; 6], y: &[u64; 6]) -> (usize, usize) { for i in (0..6).rev() { if x[i] != 0 || y[i] != 0 { let word = if x[i] > y[i] { x[i] } else { y[i] }; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1_fp_sqrt.rs index dae86a0ba..55606e90c 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1_fp_sqrt.rs @@ -36,7 +36,7 @@ pub fn fcall_secp256k1_fp_sqrt(params: &[u64], results: &mut [u64]) -> i64 { 5 } -fn secp256k1_fp_sqrt(a: &[u64; 4], parity: u64, results: &mut [u64]) { +pub fn secp256k1_fp_sqrt(a: &[u64; 4], parity: u64, results: &mut [u64]) { let a_big = biguint_from_u64_digits(a); // Attempt to compute the square root of a From 5e10567ed23d3fc5815a59c579136981b78bfa62 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 15:52:34 +0000 Subject: [PATCH 164/782] added new hints_processor.rs tests --- precompiles/hints/src/hints_processor.rs | 147 +++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index fb36cebf0..ffe631fe9 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -726,6 +726,131 @@ mod tests { assert!(p.state.error_flag.load(Ordering::Acquire)); } + #[test] + fn test_ctrl_start_must_be_first_in_batch() { + let p = processor(); + + // CTRL_START not at position 0 should fail + let data = vec![ + make_header(HintCode::HintsTypeResult as u32, 1), + 0x42, + make_ctrl_header(HintCode::CtrlStart as u32, 0), + ]; + + let result = p.process_hints(&data, true); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("must be the first hint")); + } + + #[test] + fn test_ctrl_start_only_in_first_batch() { + let p = processor(); + + // First batch is ok + let batch1 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x01]; + p.process_hints(&batch1, false).unwrap(); + + // CTRL_START in non-first batch should fail + let start = vec![make_ctrl_header(HintCode::CtrlStart as u32, 0)]; + let result = p.process_hints(&start, false); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("first message in the stream")); + } + + #[test] + fn test_ctrl_end_must_be_last() { + let p = processor(); + + // CTRL_END not at end should fail + let data = vec![ + make_ctrl_header(HintCode::CtrlEnd as u32, 0), + make_header(HintCode::HintsTypeResult as u32, 1), + 0x42, + ]; + + let result = p.process_hints(&data, false); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("must be the last hint")); + } + + #[test] + fn test_sink_receives_correct_data() { + use std::sync::{Arc, Mutex}; + + struct RecordingSink { + received: Arc>>>, + } + + impl StreamSink for RecordingSink { + fn submit(&self, processed: Vec) -> Result<()> { + self.received.lock().unwrap().push(processed); + Ok(()) + } + } + + let received = Arc::new(Mutex::new(Vec::new())); + let sink = RecordingSink { received: Arc::clone(&received) }; + let p = HintsProcessor::builder(sink).num_threads(2).build().unwrap(); + + // Send some data + let data = vec![ + make_header(HintCode::HintsTypeResult as u32, 2), + 0xAAA, + 0xBBB, + make_header(HintCode::HintsTypeResult as u32, 1), + 0xCCC, + ]; + + p.process_hints(&data, false).unwrap(); + p.wait_for_completion().unwrap(); + + // Verify sink received correct data in order + let received = received.lock().unwrap(); + assert_eq!(received.len(), 2); + assert_eq!(received[0], vec![0xAAA, 0xBBB]); + assert_eq!(received[1], vec![0xCCC]); + } + + #[test] + fn test_sink_error_stops_processing() { + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Arc; + + struct FailingSink { + should_fail: Arc, + } + + impl StreamSink for FailingSink { + fn submit(&self, _processed: Vec) -> Result<()> { + if self.should_fail.load(Ordering::Acquire) { + Err(anyhow::anyhow!("Sink error")) + } else { + Ok(()) + } + } + } + + let should_fail = Arc::new(AtomicBool::new(false)); + let sink = FailingSink { should_fail: Arc::clone(&should_fail) }; + let p = HintsProcessor::builder(sink).num_threads(2).build().unwrap(); + + // First batch succeeds + let data1 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x01]; + assert!(p.process_hints(&data1, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Make sink fail + should_fail.store(true, Ordering::Release); + + // Second batch should trigger sink error + let data2 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x02]; + assert!(p.process_hints(&data2, false).is_ok()); + + // Wait should detect the error from drainer thread + std::thread::sleep(std::time::Duration::from_millis(100)); + assert!(p.state.error_flag.load(Ordering::Acquire)); + } + // Builder tests #[test] fn test_builder_default() { @@ -740,6 +865,28 @@ mod tests { assert!(p.wait_for_completion().is_ok()); } + #[test] + fn test_builder_stats_enabled() { + let p = HintsProcessor::builder(NullHints).enable_stats(true).build().unwrap(); + + // Stats should be Some + assert!(p.stats.is_some()); + + // Process hints + let data = vec![ + make_header(HintCode::HintsTypeResult as u32, 1), + 0x111, + make_header(HintCode::HintsTypeResult as u32, 1), + 0x222, + ]; + assert!(p.process_hints(&data, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Verify stats were collected + let stats = p.stats.as_ref().unwrap().lock().unwrap(); + assert_eq!(stats.get(&HintCode::HintsTypeResult), Some(&2)); + } + #[test] fn test_builder_custom_threads() { let p = HintsProcessor::builder(NullHints).num_threads(4).build().unwrap(); From 6a57633bffbd189c9ec2252e9f1a7a7e92f0f51e Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 1 Jan 2026 16:54:31 +0100 Subject: [PATCH 165/782] Add fcall result length to hints --- ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs | 1 + ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs | 3 +++ ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs | 2 ++ ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs | 1 + ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs | 1 + ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs | 1 + ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs | 1 + ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs | 2 ++ ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs | 1 + ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs | 1 + ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs | 2 ++ ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs | 1 + ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs | 1 + ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs | 2 ++ ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs | 2 ++ ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs | 2 ++ 16 files changed, 24 insertions(+) diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs index 2803567e8..eda9a1411 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs @@ -34,6 +34,7 @@ pub fn fcall_bigint256_div( let (quotient, remainder) = big_int256_div(a_value, b_value); #[cfg(feature = "hints")] { + hints.push(8); hints.extend_from_slice("ient); hints.extend_from_slice(&remainder); } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs index 3366bc736..08cbeb8fe 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs @@ -33,7 +33,10 @@ pub fn fcall_division( let len_rem = rem.len(); #[cfg(feature = "hints")] { + hints.push(len_quo as u64 + len_rem as u64 + 2); + hints.push(len_quo as u64); hints.extend_from_slice(&quo); + hints.push(len_rem as u64); hints.extend_from_slice(&rem); } return (len_quo, len_rem); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs index 19b4587e2..d7bdb2e28 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs @@ -23,6 +23,8 @@ pub fn fcall_bin_decomp( let bits_u64: Vec = bits.into_iter().map(|b| b as u64).collect(); #[cfg(feature = "hints")] { + hints.push(len_bits as u64 + 1); + hints.push(len_bits as u64); hints.extend_from_slice(&bits_u64); } return (len_bits, bits_u64); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs index a0bddba94..7d97964f4 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_inv.rs @@ -33,6 +33,7 @@ pub fn fcall_bls12_381_fp2_inv( let result: [u64; 12] = bls12_381_fp2_inv(p_value); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } result diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs index df6cf9d5c..e852917dd 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp2_sqrt.rs @@ -33,6 +33,7 @@ pub fn fcall_bls12_381_fp2_sqrt( let result: [u64; 13] = bls12_381_fp2_sqrt_13(p_value); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } result diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs index 67e799f23..47796feb1 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_inv.rs @@ -33,6 +33,7 @@ pub fn fcall_bls12_381_fp_inv( let result: [u64; 6] = bls12_381_fp_inv(p_value); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } result diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs index 645a5829e..4a8dbfdff 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_fp_sqrt.rs @@ -34,6 +34,7 @@ pub fn fcall_bls12_381_fp_sqrt( bls12_381_fp_sqrt(p_value, &mut result); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } result diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs index 4bd7b2a8c..4968a258e 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381_twist.rs @@ -33,6 +33,7 @@ pub fn fcall_bls12_381_add_line_coeffs( let (lambda, mu) = bls12_381_twist_add_line_coeffs(&x1, &y1, &x2, &y2); #[cfg(feature = "hints")] { + hints.push(24); hints.extend_from_slice(&lambda); hints.extend_from_slice(&mu); } @@ -96,6 +97,7 @@ pub fn fcall_bls12_381_dbl_line_coeffs( let (lambda, mu) = bls12_381_twist_dbl_line_coeffs(&x, &y); #[cfg(feature = "hints")] { + hints.push(24); hints.extend_from_slice(&lambda); hints.extend_from_slice(&mu); } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs index b2658f836..be7d4cbc0 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp.rs @@ -33,6 +33,7 @@ pub fn fcall_bn254_fp_inv( let result: [u64; 4] = bn254_fp_inv(p_value); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } result diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs index ec1c569bd..f9f12a92a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254_fp2.rs @@ -33,6 +33,7 @@ pub fn fcall_bn254_fp2_inv( let result: [u64; 8] = bn254_fp2_inv(p_value); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } result diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs index 967ab9c4f..b844f0dc9 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254_twist.rs @@ -33,6 +33,7 @@ pub fn fcall_bn254_add_line_coeffs( let (lambda, mu) = bn254_twist_add_line_coeffs(&x1, &y1, &x2, &y2); #[cfg(feature = "hints")] { + hints.push(16); hints.extend_from_slice(&lambda); hints.extend_from_slice(&mu); } @@ -88,6 +89,7 @@ pub fn fcall_bn254_dbl_line_coeffs( let (lambda, mu) = bn254_twist_dbl_line_coeffs(&x1, &y1); #[cfg(feature = "hints")] { + hints.push(16); hints.extend_from_slice(&lambda); hints.extend_from_slice(&mu); } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs index 48e3e9516..2233d7473 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs @@ -20,6 +20,7 @@ pub fn fcall_msb_pos_256( let (i, pos) = msb_pos_256(x, y); #[cfg(feature = "hints")] { + hints.push(2); hints.push(i as u64); hints.push(pos as u64); } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs index cd87317b2..788a25e6c 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_384.rs @@ -19,6 +19,7 @@ pub fn fcall_msb_pos_384( let (i, pos) = msb_pos_384(x, y); #[cfg(feature = "hints")] { + hints.push(2); hints.push(i as u64); hints.push(pos as u64); } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs index 6a166df1b..b63add074 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fn_inv.rs @@ -35,6 +35,7 @@ pub fn fcall_secp256k1_fn_inv( secp256k1_fn_inv_c(p_value, &mut result); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } result @@ -55,6 +56,7 @@ pub fn fcall2_secp256k1_fn_inv(p_value: &[u64; 4], #[cfg(feature = "hints")] hin secp256k1_fn_inv_c(p_value, &mut result); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs index 9da0b8499..137f57ae4 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_inv.rs @@ -36,6 +36,7 @@ pub fn fcall_secp256k1_fp_inv( secp256k1_fp_inv_c(p_value, &mut result); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } result @@ -56,6 +57,7 @@ pub fn fcall2_secp256k1_fp_inv(p_value: &[u64; 4], #[cfg(feature = "hints")] hin secp256k1_fp_inv_c(p_value, &mut result); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs index ac56a1dda..dc6c21c78 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1_fp_sqrt.rs @@ -37,6 +37,7 @@ pub fn fcall_secp256k1_fp_sqrt( secp256k1_fp_sqrt(p_value, parity, &mut result); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } result @@ -68,6 +69,7 @@ pub fn fcall2_secp256k1_fp_sqrt( secp256k1_fp_sqrt(p_value, parity, &mut result); #[cfg(feature = "hints")] { + hints.push(result.len() as u64); hints.extend_from_slice(&result); } } From 9a15f69cd7f820349a21cf4d229e66e3ee5a9ea6 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:04:18 +0000 Subject: [PATCH 166/782] added new hint codes --- common/src/hints.rs | 28 ++++++++++++++++++++++++ precompiles/hints/src/hints_processor.rs | 21 +++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 56c121a15..c0773a8f7 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -70,6 +70,20 @@ pub enum HintCode { HintsTypeResult = 0x04, /// Ecrecover precompile hint type. HintsTypeEcrecover = 0x05, + /// Modular reduction of a 256-bit integer hint type. + RedMod256 = 0x06, + /// Modular addition of 256-bit integers hint type. + AddMod256 = 0x07, + /// Modular multiplication of 256-bit integers hint type. + MulMod256 = 0x08, + /// Division and remainder of 256-bit integers hint type. + DivRem256 = 0x09, + /// Wrapping exponentiation of 256-bit integers hint type. + WPow256 = 0x0A, + /// Overflowing multiplication of 256-bit integers hint type. + OMul256 = 0x0B, + /// Wrapping multiplication of 256-bit integers hint type. + WMul256 = 0x0C, } impl TryFrom for HintCode { @@ -83,6 +97,13 @@ impl TryFrom for HintCode { 0x03 => Ok(HintCode::CtrlError), 0x04 => Ok(HintCode::HintsTypeResult), 0x05 => Ok(HintCode::HintsTypeEcrecover), + 0x06 => Ok(HintCode::RedMod256), + 0x07 => Ok(HintCode::AddMod256), + 0x08 => Ok(HintCode::MulMod256), + 0x09 => Ok(HintCode::DivRem256), + 0x0A => Ok(HintCode::WPow256), + 0x0B => Ok(HintCode::OMul256), + 0x0C => Ok(HintCode::WMul256), _ => Err(anyhow::anyhow!("Invalid hint code: {:#x}", value)), } } @@ -97,6 +118,13 @@ impl Display for HintCode { HintCode::CtrlError => "CTRL_ERROR", HintCode::HintsTypeResult => "HINTS_TYPE_RESULT", HintCode::HintsTypeEcrecover => "HINTS_TYPE_ECRECOVER", + HintCode::RedMod256 => "REDMOD256", + HintCode::AddMod256 => "ADDMOD256", + HintCode::MulMod256 => "MULMOD256", + HintCode::DivRem256 => "DIVREM256", + HintCode::WPow256 => "WPOW256", + HintCode::OMul256 => "OMUL256", + HintCode::WMul256 => "WMUL256", }; write!(f, "{}", name) } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index ffe631fe9..9ae35d98a 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -244,7 +244,15 @@ impl HintsProcessor { self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream error signalled")); } - HintCode::HintsTypeResult | HintCode::HintsTypeEcrecover => { + HintCode::HintsTypeResult + | HintCode::HintsTypeEcrecover + | HintCode::RedMod256 + | HintCode::AddMod256 + | HintCode::MulMod256 + | HintCode::DivRem256 + | HintCode::WPow256 + | HintCode::OMul256 + | HintCode::WMul256 => { // Data hint type - process normally } } @@ -463,6 +471,17 @@ impl HintsProcessor { // Dispatch to the ECRECOVER handler. HintCode::HintsTypeEcrecover => Self::process_hint_ecrecover(&hint), + + // TODO: Implement handlers for 256-bit operations + HintCode::RedMod256 + | HintCode::AddMod256 + | HintCode::MulMod256 + | HintCode::DivRem256 + | HintCode::WPow256 + | HintCode::OMul256 + | HintCode::WMul256 => { + unimplemented!("Handler for hint type {:?} is not yet implemented", hint.hint_code) + } } } From 8ef1f51e8275503668aa1e5f442ea2ebe366aa32 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:08:46 +0000 Subject: [PATCH 167/782] rename ecrecover and typeresult hint codes --- common/src/hints.rs | 12 ++-- precompiles/hints/src/hints_processor.rs | 71 ++++++++++++------------ 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index c0773a8f7..8bb69f351 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -67,9 +67,9 @@ pub enum HintCode { /// Pass-through hint type. /// When a hint has this type, the processor simply passes through the data /// without any additional computation. - HintsTypeResult = 0x04, + Noop = 0x04, /// Ecrecover precompile hint type. - HintsTypeEcrecover = 0x05, + EcRecover = 0x05, /// Modular reduction of a 256-bit integer hint type. RedMod256 = 0x06, /// Modular addition of 256-bit integers hint type. @@ -95,8 +95,8 @@ impl TryFrom for HintCode { 0x01 => Ok(HintCode::CtrlEnd), 0x02 => Ok(HintCode::CtrlCancel), 0x03 => Ok(HintCode::CtrlError), - 0x04 => Ok(HintCode::HintsTypeResult), - 0x05 => Ok(HintCode::HintsTypeEcrecover), + 0x04 => Ok(HintCode::Noop), + 0x05 => Ok(HintCode::EcRecover), 0x06 => Ok(HintCode::RedMod256), 0x07 => Ok(HintCode::AddMod256), 0x08 => Ok(HintCode::MulMod256), @@ -116,8 +116,8 @@ impl Display for HintCode { HintCode::CtrlEnd => "CTRL_END", HintCode::CtrlCancel => "CTRL_CANCEL", HintCode::CtrlError => "CTRL_ERROR", - HintCode::HintsTypeResult => "HINTS_TYPE_RESULT", - HintCode::HintsTypeEcrecover => "HINTS_TYPE_ECRECOVER", + HintCode::Noop => "HINTS_TYPE_RESULT", + HintCode::EcRecover => "HINTS_TYPE_ECRECOVER", HintCode::RedMod256 => "REDMOD256", HintCode::AddMod256 => "ADDMOD256", HintCode::MulMod256 => "MULMOD256", diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 9ae35d98a..e646988f9 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -244,8 +244,8 @@ impl HintsProcessor { self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream error signalled")); } - HintCode::HintsTypeResult - | HintCode::HintsTypeEcrecover + HintCode::Noop + | HintCode::EcRecover | HintCode::RedMod256 | HintCode::AddMod256 | HintCode::MulMod256 @@ -268,7 +268,7 @@ impl HintsProcessor { }; // Handle HINTS_TYPE_RESULT synchronously - it doesn't need async processing - if hint.hint_code == HintCode::HintsTypeResult { + if hint.hint_code == HintCode::Noop { // Immediately mark this slot as complete { let mut queue = self.state.queue.lock().unwrap(); @@ -467,10 +467,10 @@ impl HintsProcessor { } // When hint type is HINTS_TYPE_RESULT, return the data as-is. - HintCode::HintsTypeResult => Ok(hint.data), + HintCode::Noop => Ok(hint.data), // Dispatch to the ECRECOVER handler. - HintCode::HintsTypeEcrecover => Self::process_hint_ecrecover(&hint), + HintCode::EcRecover => Self::process_hint_ecrecover(&hint), // TODO: Implement handlers for 256-bit operations HintCode::RedMod256 @@ -543,7 +543,7 @@ mod tests { #[test] fn test_single_result_hint_non_blocking() { let p = processor(); - let data = vec![make_header(HintCode::HintsTypeResult as u32, 2), 0x111, 0x222]; + let data = vec![make_header(HintCode::Noop as u32, 2), 0x111, 0x222]; // Dispatch should succeed and be non-blocking assert!(p.process_hints(&data, false).is_ok()); @@ -560,11 +560,11 @@ mod tests { fn test_multiple_hints_ordered_output() { let p = processor(); let data = vec![ - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0x111, - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0x222, - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0x333, ]; assert!(p.process_hints(&data, false).is_ok()); @@ -579,8 +579,8 @@ mod tests { #[test] fn test_multiple_calls_global_sequence() { let p = processor(); - let data1 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0xAAA]; - let data2 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0xBBB]; + let data1 = vec![make_header(HintCode::Noop as u32, 1), 0xAAA]; + let data2 = vec![make_header(HintCode::Noop as u32, 1), 0xBBB]; assert!(p.process_hints(&data1, false).is_ok()); assert!(p.process_hints(&data2, false).is_ok()); @@ -620,8 +620,7 @@ mod tests { fn test_error_stops_wait() { let p = processor(); // First valid, then invalid type - let data = - vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x111, make_header(999, 0)]; + let data = vec![make_header(HintCode::Noop as u32, 1), 0x111, make_header(999, 0)]; // Should error immediately when encountering invalid hint type let result = p.process_hints(&data, false); @@ -644,7 +643,7 @@ mod tests { assert!(!p.state.error_flag.load(Ordering::Acquire)); // Should be able to process new hints after reset - let good = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x42]; + let good = vec![make_header(HintCode::Noop as u32, 1), 0x42]; assert!(p.process_hints(&good, false).is_ok()); assert!(p.wait_for_completion().is_ok()); @@ -658,7 +657,7 @@ mod tests { let p = processor(); // First batch increments sequence - let batch1 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x01]; + let batch1 = vec![make_header(HintCode::Noop as u32, 1), 0x01]; p.process_hints(&batch1, false).unwrap(); p.wait_for_completion().unwrap(); @@ -680,7 +679,7 @@ mod tests { } // Process new batch - let batch2 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x02]; + let batch2 = vec![make_header(HintCode::Noop as u32, 1), 0x02]; p.process_hints(&batch2, false).unwrap(); let end = vec![make_ctrl_header(HintCode::CtrlEnd as u32, 0)]; @@ -697,9 +696,9 @@ mod tests { // Dispatch hints let data = vec![ - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0x10, - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0x20, ]; p.process_hints(&data, false).unwrap(); @@ -751,7 +750,7 @@ mod tests { // CTRL_START not at position 0 should fail let data = vec![ - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0x42, make_ctrl_header(HintCode::CtrlStart as u32, 0), ]; @@ -766,7 +765,7 @@ mod tests { let p = processor(); // First batch is ok - let batch1 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x01]; + let batch1 = vec![make_header(HintCode::Noop as u32, 1), 0x01]; p.process_hints(&batch1, false).unwrap(); // CTRL_START in non-first batch should fail @@ -783,7 +782,7 @@ mod tests { // CTRL_END not at end should fail let data = vec![ make_ctrl_header(HintCode::CtrlEnd as u32, 0), - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0x42, ]; @@ -813,10 +812,10 @@ mod tests { // Send some data let data = vec![ - make_header(HintCode::HintsTypeResult as u32, 2), + make_header(HintCode::Noop as u32, 2), 0xAAA, 0xBBB, - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0xCCC, ]; @@ -854,7 +853,7 @@ mod tests { let p = HintsProcessor::builder(sink).num_threads(2).build().unwrap(); // First batch succeeds - let data1 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x01]; + let data1 = vec![make_header(HintCode::Noop as u32, 1), 0x01]; assert!(p.process_hints(&data1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); @@ -862,7 +861,7 @@ mod tests { should_fail.store(true, Ordering::Release); // Second batch should trigger sink error - let data2 = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x02]; + let data2 = vec![make_header(HintCode::Noop as u32, 1), 0x02]; assert!(p.process_hints(&data2, false).is_ok()); // Wait should detect the error from drainer thread @@ -879,7 +878,7 @@ mod tests { assert!(p.stats.is_none()); // Should process hints normally - let data = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x42]; + let data = vec![make_header(HintCode::Noop as u32, 1), 0x42]; assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); } @@ -893,9 +892,9 @@ mod tests { // Process hints let data = vec![ - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0x111, - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0x222, ]; assert!(p.process_hints(&data, false).is_ok()); @@ -903,7 +902,7 @@ mod tests { // Verify stats were collected let stats = p.stats.as_ref().unwrap().lock().unwrap(); - assert_eq!(stats.get(&HintCode::HintsTypeResult), Some(&2)); + assert_eq!(stats.get(&HintCode::Noop), Some(&2)); } #[test] @@ -911,7 +910,7 @@ mod tests { let p = HintsProcessor::builder(NullHints).num_threads(4).build().unwrap(); // Should process hints normally - let data = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x42]; + let data = vec![make_header(HintCode::Noop as u32, 1), 0x42]; assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); } @@ -925,9 +924,9 @@ mod tests { // Should still process hints normally let data = vec![ - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0x111, - make_header(HintCode::HintsTypeResult as u32, 1), + make_header(HintCode::Noop as u32, 1), 0x222, ]; assert!(p.process_hints(&data, false).is_ok()); @@ -941,7 +940,7 @@ mod tests { assert!(p.stats.is_some()); - let data = vec![make_header(HintCode::HintsTypeResult as u32, 1), 0x42]; + let data = vec![make_header(HintCode::Noop as u32, 1), 0x42]; assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); } @@ -958,7 +957,7 @@ mod tests { let mut data = Vec::with_capacity(NUM_HINTS * 2); for i in 0..NUM_HINTS { - data.push(make_header(HintCode::HintsTypeResult as u32, 1)); + data.push(make_header(HintCode::Noop as u32, 1)); data.push(i as u64); } @@ -995,7 +994,7 @@ mod tests { for batch_id in 0..NUM_BATCHES { let mut data = Vec::with_capacity(HINTS_PER_BATCH * 2); for i in 0..HINTS_PER_BATCH { - data.push(make_header(HintCode::HintsTypeResult as u32, 1)); + data.push(make_header(HintCode::Noop as u32, 1)); data.push((batch_id * HINTS_PER_BATCH + i) as u64); } p.process_hints(&data, false).unwrap(); @@ -1038,7 +1037,7 @@ mod tests { // Process batch let mut data = Vec::with_capacity(HINTS_PER_ITER * 2); for i in 0..HINTS_PER_ITER { - data.push(make_header(HintCode::HintsTypeResult as u32, 1)); + data.push(make_header(HintCode::Noop as u32, 1)); data.push(i as u64); } p.process_hints(&data, false).unwrap(); From df046b195b8e2ab3acd1f7a2a32a3adf5bfd327b Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:21:16 +0000 Subject: [PATCH 168/782] fix typo --- common/src/hints.rs | 8 ++++---- precompiles/hints/src/hints_processor.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 8bb69f351..e790edb8c 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -41,8 +41,8 @@ //! Control codes are for control only and do not have any associated data (Length should be zero). //! //! ### Data Hint Types: -//! - `0x04` (`HINTS_TYPE_RESULT`): Pass-through data -//! - `0x05` (`HINTS_TYPE_ECRECOVER`): ECRECOVER inputs (currently returns empty) +//! - `0x04` (`Noop`): Pass-through data +//! - `0x05` (`EcRecover`): ECRECOVER inputs (currently returns empty) //! ``` use std::fmt::Display; @@ -116,8 +116,8 @@ impl Display for HintCode { HintCode::CtrlEnd => "CTRL_END", HintCode::CtrlCancel => "CTRL_CANCEL", HintCode::CtrlError => "CTRL_ERROR", - HintCode::Noop => "HINTS_TYPE_RESULT", - HintCode::EcRecover => "HINTS_TYPE_ECRECOVER", + HintCode::Noop => "NOOP", + HintCode::EcRecover => "ECRECOVER", HintCode::RedMod256 => "REDMOD256", HintCode::AddMod256 => "ADDMOD256", HintCode::MulMod256 => "MULMOD256", diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index e646988f9..ad25488f5 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -485,7 +485,7 @@ impl HintsProcessor { } } - /// Processes a [`HINTS_TYPE_ECRECOVER`] hint. + /// Processes a [`ECRECOVER`] hint. #[inline] fn process_hint_ecrecover(hint: &PrecompileHint) -> Result> { ziskos_hints::hints::process_ecrecover_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) From e8ff170fc5957b773a7c54b2f39de83a37bcfc83 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:21:53 +0000 Subject: [PATCH 169/782] added new functions for new hints codes --- precompiles/hints/src/hints_processor.rs | 55 ++++++++++++++++++------ ziskos-hints/src/hints/mod.rs | 25 +++++++++++ 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index ad25488f5..93d732e58 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -469,19 +469,14 @@ impl HintsProcessor { // When hint type is HINTS_TYPE_RESULT, return the data as-is. HintCode::Noop => Ok(hint.data), - // Dispatch to the ECRECOVER handler. HintCode::EcRecover => Self::process_hint_ecrecover(&hint), - - // TODO: Implement handlers for 256-bit operations - HintCode::RedMod256 - | HintCode::AddMod256 - | HintCode::MulMod256 - | HintCode::DivRem256 - | HintCode::WPow256 - | HintCode::OMul256 - | HintCode::WMul256 => { - unimplemented!("Handler for hint type {:?} is not yet implemented", hint.hint_code) - } + HintCode::RedMod256 => Self::process_hint_redmod256(&hint), + HintCode::AddMod256 => Self::process_hint_addmod256(&hint), + HintCode::MulMod256 => Self::process_hint_mulmod256(&hint), + HintCode::DivRem256 => Self::process_hint_divrem256(&hint), + HintCode::WPow256 => Self::process_hint_wpow256(&hint), + HintCode::OMul256 => Self::process_hint_omul256(&hint), + HintCode::WMul256 => Self::process_hint_wmul256(&hint), } } @@ -490,6 +485,42 @@ impl HintsProcessor { fn process_hint_ecrecover(hint: &PrecompileHint) -> Result> { ziskos_hints::hints::process_ecrecover_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + + /// Processes a [`REDMOD256`] hint. + #[inline] + fn process_hint_redmod256(hint: &PrecompileHint) -> Result> { + ziskos_hints::hints::process_redmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`ADDMOD256`] hint. + #[inline] + fn process_hint_addmod256(hint: &PrecompileHint) -> Result> { + ziskos_hints::hints::process_addmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`MULMOD256`] hint. + #[inline] + fn process_hint_mulmod256(hint: &PrecompileHint) -> Result> { + ziskos_hints::hints::process_mulmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`DIVREM256`] hint. + #[inline] + fn process_hint_divrem256(hint: &PrecompileHint) -> Result> { + ziskos_hints::hints::process_divrem256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`WPOW256`] hint. + #[inline] + fn process_hint_wpow256(hint: &PrecompileHint) -> Result> { + ziskos_hints::hints::process_wpow256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`OMUL256`] hint. + #[inline] + fn process_hint_omul256(hint: &PrecompileHint) -> Result> { + ziskos_hints::hints::process_omul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`WMUL256`] hint. + #[inline] + fn process_hint_wmul256(hint: &PrecompileHint) -> Result> { + ziskos_hints::hints::process_wmul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } } impl Drop for HintsProcessor { diff --git a/ziskos-hints/src/hints/mod.rs b/ziskos-hints/src/hints/mod.rs index a2d338e79..ddde04bff 100644 --- a/ziskos-hints/src/hints/mod.rs +++ b/ziskos-hints/src/hints/mod.rs @@ -48,3 +48,28 @@ pub fn process_ecrecover_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } + +pub fn process_redmod256_hint(_data: &[u64]) -> Result, String> { + unimplemented!("REDMOD256 hint processing is not yet implemented"); +} + +pub fn process_addmod256_hint(_data: &[u64]) -> Result, String> { + unimplemented!("ADDMOD256 hint processing is not yet implemented"); +} + +pub fn process_mulmod256_hint(_data: &[u64]) -> Result, String> { + unimplemented!("MULMOD256 hint processing is not yet implemented"); +} + +pub fn process_divrem256_hint(_data: &[u64]) -> Result, String> { + unimplemented!("DIVREM256 hint processing is not yet implemented"); +} +pub fn process_wpow256_hint(_data: &[u64]) -> Result, String> { + unimplemented!("WPOW256 hint processing is not yet implemented"); +} +pub fn process_omul256_hint(_data: &[u64]) -> Result, String> { + unimplemented!("OMUL256 hint processing is not yet implemented"); +} +pub fn process_wmul256_hint(_data: &[u64]) -> Result, String> { + unimplemented!("WMUL256 hint processing is not yet implemented"); +} From 12b9579577d9916bf17ae289eddcd42148c533bf Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:45:32 +0000 Subject: [PATCH 170/782] added missing calls in ziskos-hints --- Cargo.lock | 1 + ziskos-hints/Cargo.toml | 1 + ziskos-hints/src/hints/mod.rs | 254 +++++++++++++++++++++++++++++----- 3 files changed, 219 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6cdcb1a97..ea39ca8ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6176,6 +6176,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", + "paste", "precompiles-helpers", "rand 0.8.5", "serde", diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index 84e19d1b2..6e1f6ee2c 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -31,6 +31,7 @@ cfg-if = "1.0" tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } bincode = "2.0" +paste = "1.0" [features] default = ["hints"] diff --git a/ziskos-hints/src/hints/mod.rs b/ziskos-hints/src/hints/mod.rs index ddde04bff..a3cb35b9a 100644 --- a/ziskos-hints/src/hints/mod.rs +++ b/ziskos-hints/src/hints/mod.rs @@ -2,6 +2,53 @@ use crate::zisklib; +/// Macro to generate size, offset, and expected length constants for hint data fields. +/// +/// # Example +/// ```ignore +/// hint_fields! { +/// A: 4, +/// B: 4, +/// M: 4 +/// } +/// ``` +/// Generates: +/// - `A_SIZE`, `B_SIZE`, `M_SIZE` constants +/// - `A_OFFSET`, `B_OFFSET`, `M_OFFSET` constants (cumulative offsets) +/// - `EXPECTED_LEN` constant (sum of all sizes) +macro_rules! hint_fields { + ($($name:ident: $size:expr),+ $(,)?) => { + paste::paste! { + $( + #[allow(dead_code)] + const [<$name _SIZE>]: usize = $size; + )+ + } + + hint_fields!(@offsets 0, $($name: $size),+); + + const EXPECTED_LEN: usize = hint_fields!(@sum $($size),+); + }; + + (@offsets $offset:expr, $name:ident: $size:expr) => { + paste::paste! { + const [<$name _OFFSET>]: usize = $offset; + } + }; + + (@offsets $offset:expr, $name:ident: $size:expr, $($rest_name:ident: $rest_size:expr),+) => { + paste::paste! { + const [<$name _OFFSET>]: usize = $offset; + } + hint_fields!(@offsets $offset + $size, $($rest_name: $rest_size),+); + }; + + (@sum $size:expr) => { $size }; + (@sum $size:expr, $($rest:expr),+) => { + $size + hint_fields!(@sum $($rest),+) + }; +} + /// Processes an ECRECOVER hint. /// /// # Arguments @@ -14,62 +61,195 @@ use crate::zisklib; /// * `Err` - If the data length is invalid #[inline] pub fn process_ecrecover_hint(data: &[u64]) -> Result, String> { - const PK_SIZE: usize = 8; // x(4) + y(4) - const Z_SIZE: usize = 4; - const R_SIZE: usize = 4; - const S_SIZE: usize = 4; - const EXPECTED_LEN: usize = PK_SIZE + Z_SIZE + R_SIZE + S_SIZE; + hint_fields![PK: 8, Z: 4, R: 4, S: 4]; - const Z_OFFSET: usize = PK_SIZE; - const R_OFFSET: usize = Z_OFFSET + Z_SIZE; - const S_OFFSET: usize = R_OFFSET + R_SIZE; + validate_hint_length(data, EXPECTED_LEN, "ECRECOVER")?; - if data.len() != EXPECTED_LEN { - return Err(format!( - "Invalid ECRECOVER hint length: expected {}, got {}", - EXPECTED_LEN, - data.len() - )); + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::secp256k1_ecdsa_verify_c( + &data[PK_OFFSET], + &data[Z_OFFSET], + &data[R_OFFSET], + &data[S_OFFSET], + &mut processed_hints, + ); } - #[allow(unused_mut)] + Ok(processed_hints) +} + +/// Processes a REDMOD256 hint. +#[inline] +pub fn process_redmod256_hint(data: &[u64]) -> Result, String> { + hint_fields![A: 4, M: 4]; + + validate_hint_length(data, EXPECTED_LEN, "REDMOD256")?; + + let mut result: [u64; 4] = [0; 4]; let mut processed_hints = Vec::new(); - // Safety: We've validated that data.len() == 20, so all slice accesses are in bounds. unsafe { - let ptr = data.as_ptr(); - let pk = &*ptr; - let z = &*ptr.add(Z_OFFSET); - let r = &*ptr.add(R_OFFSET); - let s = &*ptr.add(S_OFFSET); - - zisklib::secp256k1_ecdsa_verify_c(pk, z, r, s, &mut processed_hints); + zisklib::redmod256_c( + &data[A_OFFSET], + &data[M_OFFSET], + &mut result[0], + &mut processed_hints, + ); } Ok(processed_hints) } -pub fn process_redmod256_hint(_data: &[u64]) -> Result, String> { - unimplemented!("REDMOD256 hint processing is not yet implemented"); +/// Processes an ADDMOD256 hint. +#[inline] +pub fn process_addmod256_hint(data: &[u64]) -> Result, String> { + hint_fields![A: 4, B: 4, M: 4]; + + validate_hint_length(data, EXPECTED_LEN, "ADDMOD256")?; + + let mut result: [u64; 4] = [0; 4]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::addmod256_c( + &data[A_OFFSET], + &data[B_OFFSET], + &data[M_OFFSET], + &mut result[0], + &mut processed_hints, + ); + } + + Ok(processed_hints) } -pub fn process_addmod256_hint(_data: &[u64]) -> Result, String> { - unimplemented!("ADDMOD256 hint processing is not yet implemented"); +/// Processes a MULMOD256 hint. +#[inline] +pub fn process_mulmod256_hint(data: &[u64]) -> Result, String> { + hint_fields![A: 4, B: 4, M: 4]; + + validate_hint_length(data, EXPECTED_LEN, "MULMOD256")?; + + let mut result: [u64; 4] = [0; 4]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::mulmod256_c( + &data[A_OFFSET], + &data[B_OFFSET], + &data[M_OFFSET], + &mut result[0], + &mut processed_hints, + ); + } + + Ok(processed_hints) } -pub fn process_mulmod256_hint(_data: &[u64]) -> Result, String> { - unimplemented!("MULMOD256 hint processing is not yet implemented"); +/// Processes a DIVREM256 hint. +#[inline] +pub fn process_divrem256_hint(data: &[u64]) -> Result, String> { + hint_fields![A: 4, B: 4]; + + validate_hint_length(data, EXPECTED_LEN, "DIVREM256")?; + + let mut processed_hints = Vec::new(); + + let mut q: [u64; 4] = [0; 4]; + let mut r: [u64; 4] = [0; 4]; + + unsafe { + zisklib::divrem256_c( + &data[A_OFFSET], + &data[B_OFFSET], + &mut q[0], + &mut r[0], + &mut processed_hints, + ); + } + + Ok(processed_hints) } -pub fn process_divrem256_hint(_data: &[u64]) -> Result, String> { - unimplemented!("DIVREM256 hint processing is not yet implemented"); +/// Processes a WPOW256 hint. +#[inline] +pub fn process_wpow256_hint(data: &[u64]) -> Result, String> { + hint_fields![A: 4, EXP: 4]; + + validate_hint_length(data, EXPECTED_LEN, "WPOW256")?; + + let mut result: [u64; 4] = [0; 4]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::wpow256_c( + &data[A_OFFSET], + &data[EXP_OFFSET], + &mut result[0], + &mut processed_hints, + ); + } + + Ok(processed_hints) } -pub fn process_wpow256_hint(_data: &[u64]) -> Result, String> { - unimplemented!("WPOW256 hint processing is not yet implemented"); + +/// Processes an OMUL256 hint. +#[inline] +pub fn process_omul256_hint(data: &[u64]) -> Result, String> { + hint_fields![A: 4, B: 4]; + + validate_hint_length(data, EXPECTED_LEN, "OMUL256")?; + + let mut result: [u64; 4] = [0; 4]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::omul256_c(&data[A_OFFSET], &data[B_OFFSET], &mut result[0], &mut processed_hints); + } + + Ok(processed_hints) } -pub fn process_omul256_hint(_data: &[u64]) -> Result, String> { - unimplemented!("OMUL256 hint processing is not yet implemented"); + +/// Processes a WMUL256 hint. +#[inline] +pub fn process_wmul256_hint(data: &[u64]) -> Result, String> { + hint_fields![A: 4, B: 4]; + + validate_hint_length(data, EXPECTED_LEN, "WMUL256")?; + + let mut result: [u64; 4] = [0; 4]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::wmul256_c(&data[A_OFFSET], &data[B_OFFSET], &mut result[0], &mut processed_hints); + } + + Ok(processed_hints) } -pub fn process_wmul256_hint(_data: &[u64]) -> Result, String> { - unimplemented!("WMUL256 hint processing is not yet implemented"); + +/// Validates that the hint data has the expected length. +/// +/// # Arguments +/// +/// * `data` - The hint data to validate +/// * `expected_len` - The expected number of u64 values +/// * `hint_name` - The name of the hint type for error messages +/// +/// # Returns +/// +/// * `Ok(())` - If the length is correct +/// * `Err(String)` - If the length is incorrect +#[inline] +fn validate_hint_length(data: &[u64], expected_len: usize, hint_name: &str) -> Result<(), String> { + if data.len() != expected_len { + return Err(format!( + "Invalid {} hint length: expected {}, got {}", + hint_name, + expected_len, + data.len() + )); + } + Ok(()) } From e39db6fb47d51f73f98986f2b04178ba0266adc0 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:53:55 +0000 Subject: [PATCH 171/782] minor fix --- precompiles/hints/src/hints_processor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 93d732e58..c299a27ce 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -195,7 +195,7 @@ impl HintsProcessor { } // Check if this is a control code or data hint type - match HintCode::try_from(hint.hint_code)? { + match hint.hint_code { HintCode::CtrlStart => { // CTRL_START must be the first message of the first batch if !first_batch { From c79be89c1fa6c839df4e8f3b287197df0849cec5 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 20:17:01 +0000 Subject: [PATCH 172/782] reorganize ziskos-hints --- precompiles/hints/src/hints_processor.rs | 16 +-- .../{hints/mod.rs => handlers/bigint256.rs} | 107 +----------------- ziskos-hints/src/handlers/mod.rs | 74 ++++++++++++ ziskos-hints/src/handlers/secp256k1.rs | 34 ++++++ ziskos-hints/src/lib.rs | 2 +- 5 files changed, 119 insertions(+), 114 deletions(-) rename ziskos-hints/src/{hints/mod.rs => handlers/bigint256.rs} (55%) create mode 100644 ziskos-hints/src/handlers/mod.rs create mode 100644 ziskos-hints/src/handlers/secp256k1.rs diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index c299a27ce..74f06a0dd 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -483,43 +483,43 @@ impl HintsProcessor { /// Processes a [`ECRECOVER`] hint. #[inline] fn process_hint_ecrecover(hint: &PrecompileHint) -> Result> { - ziskos_hints::hints::process_ecrecover_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::process_ecrecover_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`REDMOD256`] hint. #[inline] fn process_hint_redmod256(hint: &PrecompileHint) -> Result> { - ziskos_hints::hints::process_redmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::process_redmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`ADDMOD256`] hint. #[inline] fn process_hint_addmod256(hint: &PrecompileHint) -> Result> { - ziskos_hints::hints::process_addmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::process_addmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`MULMOD256`] hint. #[inline] fn process_hint_mulmod256(hint: &PrecompileHint) -> Result> { - ziskos_hints::hints::process_mulmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::process_mulmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`DIVREM256`] hint. #[inline] fn process_hint_divrem256(hint: &PrecompileHint) -> Result> { - ziskos_hints::hints::process_divrem256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::process_divrem256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`WPOW256`] hint. #[inline] fn process_hint_wpow256(hint: &PrecompileHint) -> Result> { - ziskos_hints::hints::process_wpow256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::process_wpow256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`OMUL256`] hint. #[inline] fn process_hint_omul256(hint: &PrecompileHint) -> Result> { - ziskos_hints::hints::process_omul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::process_omul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`WMUL256`] hint. #[inline] fn process_hint_wmul256(hint: &PrecompileHint) -> Result> { - ziskos_hints::hints::process_wmul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::process_wmul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } } diff --git a/ziskos-hints/src/hints/mod.rs b/ziskos-hints/src/handlers/bigint256.rs similarity index 55% rename from ziskos-hints/src/hints/mod.rs rename to ziskos-hints/src/handlers/bigint256.rs index a3cb35b9a..5702e5d0d 100644 --- a/ziskos-hints/src/hints/mod.rs +++ b/ziskos-hints/src/handlers/bigint256.rs @@ -1,85 +1,7 @@ -//! Hint processing utilities for ziskos-hints - +use crate::handlers::validate_hint_length; +use crate::hint_fields; use crate::zisklib; -/// Macro to generate size, offset, and expected length constants for hint data fields. -/// -/// # Example -/// ```ignore -/// hint_fields! { -/// A: 4, -/// B: 4, -/// M: 4 -/// } -/// ``` -/// Generates: -/// - `A_SIZE`, `B_SIZE`, `M_SIZE` constants -/// - `A_OFFSET`, `B_OFFSET`, `M_OFFSET` constants (cumulative offsets) -/// - `EXPECTED_LEN` constant (sum of all sizes) -macro_rules! hint_fields { - ($($name:ident: $size:expr),+ $(,)?) => { - paste::paste! { - $( - #[allow(dead_code)] - const [<$name _SIZE>]: usize = $size; - )+ - } - - hint_fields!(@offsets 0, $($name: $size),+); - - const EXPECTED_LEN: usize = hint_fields!(@sum $($size),+); - }; - - (@offsets $offset:expr, $name:ident: $size:expr) => { - paste::paste! { - const [<$name _OFFSET>]: usize = $offset; - } - }; - - (@offsets $offset:expr, $name:ident: $size:expr, $($rest_name:ident: $rest_size:expr),+) => { - paste::paste! { - const [<$name _OFFSET>]: usize = $offset; - } - hint_fields!(@offsets $offset + $size, $($rest_name: $rest_size),+); - }; - - (@sum $size:expr) => { $size }; - (@sum $size:expr, $($rest:expr),+) => { - $size + hint_fields!(@sum $($rest),+) - }; -} - -/// Processes an ECRECOVER hint. -/// -/// # Arguments -/// -/// * `data` - The hint data containing pk(8) + z(4) + r(4) + s(4) = 20 u64 values -/// -/// # Returns -/// -/// * `Ok(Vec)` - The processed hints from the verification -/// * `Err` - If the data length is invalid -#[inline] -pub fn process_ecrecover_hint(data: &[u64]) -> Result, String> { - hint_fields![PK: 8, Z: 4, R: 4, S: 4]; - - validate_hint_length(data, EXPECTED_LEN, "ECRECOVER")?; - - let mut processed_hints = Vec::new(); - - unsafe { - zisklib::secp256k1_ecdsa_verify_c( - &data[PK_OFFSET], - &data[Z_OFFSET], - &data[R_OFFSET], - &data[S_OFFSET], - &mut processed_hints, - ); - } - - Ok(processed_hints) -} - /// Processes a REDMOD256 hint. #[inline] pub fn process_redmod256_hint(data: &[u64]) -> Result, String> { @@ -228,28 +150,3 @@ pub fn process_wmul256_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } - -/// Validates that the hint data has the expected length. -/// -/// # Arguments -/// -/// * `data` - The hint data to validate -/// * `expected_len` - The expected number of u64 values -/// * `hint_name` - The name of the hint type for error messages -/// -/// # Returns -/// -/// * `Ok(())` - If the length is correct -/// * `Err(String)` - If the length is incorrect -#[inline] -fn validate_hint_length(data: &[u64], expected_len: usize, hint_name: &str) -> Result<(), String> { - if data.len() != expected_len { - return Err(format!( - "Invalid {} hint length: expected {}, got {}", - hint_name, - expected_len, - data.len() - )); - } - Ok(()) -} diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs new file mode 100644 index 000000000..ec923ee18 --- /dev/null +++ b/ziskos-hints/src/handlers/mod.rs @@ -0,0 +1,74 @@ +mod bigint256; +mod secp256k1; + +pub use bigint256::*; +pub use secp256k1::*; + +/// Macro to generate size, offset, and expected length constants for hint data fields. +/// +/// # Example +/// ```ignore +/// hint_fields![A: 4, B: 4, M: 4] +/// ``` +/// Generates: +/// - `A_SIZE`, `B_SIZE`, `M_SIZE` constants +/// - `A_OFFSET`, `B_OFFSET`, `M_OFFSET` constants (cumulative offsets) +/// - `EXPECTED_LEN` constant (sum of all sizes) +#[macro_export] +macro_rules! hint_fields { + ($($name:ident: $size:expr),+ $(,)?) => { + paste::paste! { + $( + #[allow(dead_code)] + const [<$name _SIZE>]: usize = $size; + )+ + } + + hint_fields!(@offsets 0, $($name: $size),+); + + const EXPECTED_LEN: usize = hint_fields!(@sum $($size),+); + }; + + (@offsets $offset:expr, $name:ident: $size:expr) => { + paste::paste! { + const [<$name _OFFSET>]: usize = $offset; + } + }; + + (@offsets $offset:expr, $name:ident: $size:expr, $($rest_name:ident: $rest_size:expr),+) => { + paste::paste! { + const [<$name _OFFSET>]: usize = $offset; + } + hint_fields!(@offsets $offset + $size, $($rest_name: $rest_size),+); + }; + + (@sum $size:expr) => { $size }; + (@sum $size:expr, $($rest:expr),+) => { + $size + hint_fields!(@sum $($rest),+) + }; +} + +/// Validates that the hint data has the expected length. +/// +/// # Arguments +/// +/// * `data` - The hint data to validate +/// * `expected_len` - The expected number of u64 values +/// * `hint_name` - The name of the hint type for error messages +/// +/// # Returns +/// +/// * `Ok(())` - If the length is correct +/// * `Err(String)` - If the length is incorrect +#[inline] +fn validate_hint_length(data: &[u64], expected_len: usize, hint_name: &str) -> Result<(), String> { + if data.len() != expected_len { + return Err(format!( + "Invalid {} hint length: expected {}, got {}", + hint_name, + expected_len, + data.len() + )); + } + Ok(()) +} diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs new file mode 100644 index 000000000..af9d96419 --- /dev/null +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -0,0 +1,34 @@ +use crate::handlers::validate_hint_length; +use crate::hint_fields; +use crate::zisklib; + +/// Processes an ECRECOVER hint. +/// +/// # Arguments +/// +/// * `data` - The hint data containing pk(8) + z(4) + r(4) + s(4) = 20 u64 values +/// +/// # Returns +/// +/// * `Ok(Vec)` - The processed hints from the verification +/// * `Err` - If the data length is invalid +#[inline] +pub fn process_ecrecover_hint(data: &[u64]) -> Result, String> { + hint_fields![PK: 8, Z: 4, R: 4, S: 4]; + + validate_hint_length(data, EXPECTED_LEN, "ECRECOVER")?; + + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::secp256k1_ecdsa_verify_c( + &data[PK_OFFSET], + &data[Z_OFFSET], + &data[R_OFFSET], + &data[S_OFFSET], + &mut processed_hints, + ); + } + + Ok(processed_hints) +} diff --git a/ziskos-hints/src/lib.rs b/ziskos-hints/src/lib.rs index 98f200795..a3c64b23e 100644 --- a/ziskos-hints/src/lib.rs +++ b/ziskos-hints/src/lib.rs @@ -11,4 +11,4 @@ mod core; pub use core::*; // Add hints-specific modules that only exist in ziskos-hints -pub mod hints; +pub mod handlers; From 201d0188d8efbe67df41bafd1fbdb7e8a1ba0d65 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 21:41:23 +0000 Subject: [PATCH 173/782] rename hint handlers --- precompiles/hints/src/hints_processor.rs | 17 +++++++++-------- ziskos-hints/src/handlers/bigint256.rs | 14 +++++++------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 74f06a0dd..5cc47d98a 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -483,43 +483,44 @@ impl HintsProcessor { /// Processes a [`ECRECOVER`] hint. #[inline] fn process_hint_ecrecover(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::process_ecrecover_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::secp256k1_ecdsa_verify_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`REDMOD256`] hint. #[inline] fn process_hint_redmod256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::process_redmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::redmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`ADDMOD256`] hint. #[inline] fn process_hint_addmod256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::process_addmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::addmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`MULMOD256`] hint. #[inline] fn process_hint_mulmod256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::process_mulmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::mulmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`DIVREM256`] hint. #[inline] fn process_hint_divrem256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::process_divrem256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::divrem256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`WPOW256`] hint. #[inline] fn process_hint_wpow256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::process_wpow256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::wpow256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`OMUL256`] hint. #[inline] fn process_hint_omul256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::process_omul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::omul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } /// Processes a [`WMUL256`] hint. #[inline] fn process_hint_wmul256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::process_wmul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + ziskos_hints::handlers::wmul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } } diff --git a/ziskos-hints/src/handlers/bigint256.rs b/ziskos-hints/src/handlers/bigint256.rs index 5702e5d0d..214c43518 100644 --- a/ziskos-hints/src/handlers/bigint256.rs +++ b/ziskos-hints/src/handlers/bigint256.rs @@ -4,7 +4,7 @@ use crate::zisklib; /// Processes a REDMOD256 hint. #[inline] -pub fn process_redmod256_hint(data: &[u64]) -> Result, String> { +pub fn redmod256_hint(data: &[u64]) -> Result, String> { hint_fields![A: 4, M: 4]; validate_hint_length(data, EXPECTED_LEN, "REDMOD256")?; @@ -26,7 +26,7 @@ pub fn process_redmod256_hint(data: &[u64]) -> Result, String> { /// Processes an ADDMOD256 hint. #[inline] -pub fn process_addmod256_hint(data: &[u64]) -> Result, String> { +pub fn addmod256_hint(data: &[u64]) -> Result, String> { hint_fields![A: 4, B: 4, M: 4]; validate_hint_length(data, EXPECTED_LEN, "ADDMOD256")?; @@ -49,7 +49,7 @@ pub fn process_addmod256_hint(data: &[u64]) -> Result, String> { /// Processes a MULMOD256 hint. #[inline] -pub fn process_mulmod256_hint(data: &[u64]) -> Result, String> { +pub fn mulmod256_hint(data: &[u64]) -> Result, String> { hint_fields![A: 4, B: 4, M: 4]; validate_hint_length(data, EXPECTED_LEN, "MULMOD256")?; @@ -72,7 +72,7 @@ pub fn process_mulmod256_hint(data: &[u64]) -> Result, String> { /// Processes a DIVREM256 hint. #[inline] -pub fn process_divrem256_hint(data: &[u64]) -> Result, String> { +pub fn divrem256_hint(data: &[u64]) -> Result, String> { hint_fields![A: 4, B: 4]; validate_hint_length(data, EXPECTED_LEN, "DIVREM256")?; @@ -97,7 +97,7 @@ pub fn process_divrem256_hint(data: &[u64]) -> Result, String> { /// Processes a WPOW256 hint. #[inline] -pub fn process_wpow256_hint(data: &[u64]) -> Result, String> { +pub fn wpow256_hint(data: &[u64]) -> Result, String> { hint_fields![A: 4, EXP: 4]; validate_hint_length(data, EXPECTED_LEN, "WPOW256")?; @@ -119,7 +119,7 @@ pub fn process_wpow256_hint(data: &[u64]) -> Result, String> { /// Processes an OMUL256 hint. #[inline] -pub fn process_omul256_hint(data: &[u64]) -> Result, String> { +pub fn omul256_hint(data: &[u64]) -> Result, String> { hint_fields![A: 4, B: 4]; validate_hint_length(data, EXPECTED_LEN, "OMUL256")?; @@ -136,7 +136,7 @@ pub fn process_omul256_hint(data: &[u64]) -> Result, String> { /// Processes a WMUL256 hint. #[inline] -pub fn process_wmul256_hint(data: &[u64]) -> Result, String> { +pub fn wmul256_hint(data: &[u64]) -> Result, String> { hint_fields![A: 4, B: 4]; validate_hint_length(data, EXPECTED_LEN, "WMUL256")?; From 27531364d3a47395fa8371b8befba79855a97df6 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 1 Jan 2026 21:41:56 +0000 Subject: [PATCH 174/782] added missed secp256k1 hint functions --- ziskos-hints/src/handlers/secp256k1.rs | 164 ++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index af9d96419..ee1169c9a 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -13,7 +13,7 @@ use crate::zisklib; /// * `Ok(Vec)` - The processed hints from the verification /// * `Err` - If the data length is invalid #[inline] -pub fn process_ecrecover_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { hint_fields![PK: 8, Z: 4, R: 4, S: 4]; validate_hint_length(data, EXPECTED_LEN, "ECRECOVER")?; @@ -32,3 +32,165 @@ pub fn process_ecrecover_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } + +// Processes a SECP256K1_TO_AFFINE hint. +#[inline] +pub fn secp256k1_to_affine_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 12]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_TO_AFFINE")?; + + let mut out: [u64; 8] = [0; 8]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::secp256k1_to_affine_c(&data[P_OFFSET], &mut out[0], &mut processed_hints); + } + + Ok(processed_hints) +} + +// Processes a SECP256K1_DECOMPRESS hint. +#[inline] +pub fn secp256k1_decompress_hint(data: &[u64]) -> Result, String> { + hint_fields![X_BYTES: 4, Y_IS_ODD: 1]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DECOMPRESS")?; + + let mut out: [u64; 8] = [0; 8]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::secp256k1_decompress_c( + &data[X_BYTES_OFFSET] as *const u64 as *const u8, + (data[Y_IS_ODD_OFFSET] >> 56) as u8, + &mut out[0], + &mut processed_hints, + ); + } + + Ok(processed_hints) +} + +// Processes a SECP256K1_DOUBLE_SCALAR_MUL_WITH_G hint. +#[inline] +pub fn secp256k1_double_scalar_mul_with_g_hint(data: &[u64]) -> Result, String> { + hint_fields![K1: 4, K2: 4, P: 8]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DOUBLE_SCALAR_MUL_WITH_G")?; + + let mut out: [u64; 8] = [0; 8]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::secp256k1_double_scalar_mul_with_g_c( + &data[K1_OFFSET], + &data[K2_OFFSET], + &data[P_OFFSET], + &mut out[0], + &mut processed_hints, + ); + } + + Ok(processed_hints) +} + +// Processes a SECP256K1_FP_REDUCE hint. +#[inline] +pub fn secp256k1_fp_reduce_hint(data: &[u64]) -> Result, String> { + hint_fields![X: 4]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_REDUCE")?; + + let mut out: [u64; 4] = [0; 4]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::secp256k1_fp_reduce_c(&data[X_OFFSET], &mut out[0], &mut processed_hints); + } + + Ok(processed_hints) +} + +// Processes a SECP256K1_FP_ADD hint. +#[inline] +pub fn secp256k1_fp_add_hint(data: &[u64]) -> Result, String> { + hint_fields![X: 4, Y: 4]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_ADD")?; + + let mut out: [u64; 4] = [0; 4]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::secp256k1_fp_add_c( + &data[X_OFFSET], + &data[Y_OFFSET], + &mut out[0], + &mut processed_hints, + ); + } + + Ok(processed_hints) +} + +// Processes a SECP256K1_FP_NEGATE hint. +#[inline] +pub fn secp256k1_fp_negate_hint(data: &[u64]) -> Result, String> { + hint_fields![X: 4]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_NEGATE")?; + + let mut out: [u64; 4] = [0; 4]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::secp256k1_fp_negate_c(&data[X_OFFSET], &mut out[0], &mut processed_hints); + } + + Ok(processed_hints) +} + +// Processes a SECP256K1_FP_MUL hint. +#[inline] +pub fn secp256k1_fp_mul_hint(data: &[u64]) -> Result, String> { + hint_fields![X: 4, Y: 4]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_MUL")?; + + let mut out: [u64; 4] = [0; 4]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::secp256k1_fp_mul_c( + &data[X_OFFSET], + &data[Y_OFFSET], + &mut out[0], + &mut processed_hints, + ); + } + + Ok(processed_hints) +} + +// Processes a SECP256K1_FP_MUL_SCALAR hint. +#[inline] +pub fn secp256k1_fp_mul_scalar_hint(data: &[u64]) -> Result, String> { + hint_fields![X: 4, SCALAR: 1]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_MUL_SCALAR")?; + + let mut out: [u64; 4] = [0; 4]; + let mut processed_hints = Vec::new(); + + unsafe { + zisklib::secp256k1_fp_mul_scalar_c( + &data[X_OFFSET], + data[SCALAR_OFFSET], + &mut out[0], + &mut processed_hints, + ); + } + + Ok(processed_hints) +} From 483e370eb798ac4fa73b1de611219e4eb473404f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 2 Jan 2026 05:39:16 +0000 Subject: [PATCH 175/782] cargo clippy --- ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs | 3 ++- ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs index 08cbeb8fe..ec3cc1daa 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs @@ -39,7 +39,8 @@ pub fn fcall_division( hints.push(len_rem as u64); hints.extend_from_slice(&rem); } - return (len_quo, len_rem); + + (len_quo, len_rem) } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs index d7bdb2e28..a7bfa41c8 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs @@ -27,7 +27,8 @@ pub fn fcall_bin_decomp( hints.push(len_bits as u64); hints.extend_from_slice(&bits_u64); } - return (len_bits, bits_u64); + + (len_bits, bits_u64) } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { From a68a3f6dd8f02473cdab32e8cbc4575708c1b757 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 2 Jan 2026 07:14:35 +0000 Subject: [PATCH 176/782] cargo clippy --- ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs | 2 +- ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs index ec3cc1daa..17f2a5618 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs @@ -39,7 +39,7 @@ pub fn fcall_division( hints.push(len_rem as u64); hints.extend_from_slice(&rem); } - + (len_quo, len_rem) } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs index a7bfa41c8..c12b08147 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs @@ -27,7 +27,7 @@ pub fn fcall_bin_decomp( hints.push(len_bits as u64); hints.extend_from_slice(&bits_u64); } - + (len_bits, bits_u64) } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] From 64366fd7dfdc4c586724372fb99c7cbc2c09007b Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 2 Jan 2026 10:19:03 +0000 Subject: [PATCH 177/782] split HintCode un CtrlCode and BuiltInCode --- common/src/hints.rs | 144 +++++++---- .../crates/coordinator/src/hints_relay.rs | 6 +- precompiles/hints/src/hints_processor.rs | 239 +++++++++--------- 3 files changed, 224 insertions(+), 165 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index e790edb8c..984ffa83c 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -49,21 +49,36 @@ use std::fmt::Display; use anyhow::Result; -/// Hint code representing either a control code or a data hint type. +/// Control code variants for stream control. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u32)] -pub enum HintCode { - // CONTROL CODES - /// Control code: Reset processor state and global sequence. - CtrlStart = 0x00, - /// Control code: Wait until completion of all pending hints. - CtrlEnd = 0x01, - /// Control code: Cancel current stream and stop processing. - CtrlCancel = 0x02, - /// Control code: Signal error and stop processing. - CtrlError = 0x03, - - // BUILT-IN HINT TYPES +pub enum CtrlCode { + /// Reset processor state and global sequence. + Start = 0x00, + /// Wait until completion of all pending hints. + End = 0x01, + /// Cancel current stream and stop processing. + Cancel = 0x02, + /// Signal error and stop processing. + Error = 0x03, +} + +impl Display for CtrlCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = match self { + CtrlCode::Start => "CTRL_START", + CtrlCode::End => "CTRL_END", + CtrlCode::Cancel => "CTRL_CANCEL", + CtrlCode::Error => "CTRL_ERROR", + }; + write!(f, "{}", name) + } +} + +/// Built-in hint type variants. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u32)] +pub enum BuiltInHint { /// Pass-through hint type. /// When a hint has this type, the processor simply passes through the data /// without any additional computation. @@ -86,47 +101,84 @@ pub enum HintCode { WMul256 = 0x0C, } +impl Display for BuiltInHint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = match self { + BuiltInHint::Noop => "NOOP", + BuiltInHint::EcRecover => "ECRECOVER", + BuiltInHint::RedMod256 => "REDMOD256", + BuiltInHint::AddMod256 => "ADDMOD256", + BuiltInHint::MulMod256 => "MULMOD256", + BuiltInHint::DivRem256 => "DIVREM256", + BuiltInHint::WPow256 => "WPOW256", + BuiltInHint::OMul256 => "OMUL256", + BuiltInHint::WMul256 => "WMUL256", + }; + write!(f, "{}", name) + } +} + +/// Hint code representing either a control code or built-in hint type. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u32)] +pub enum HintCode { + /// Control code for stream management. + Ctrl(CtrlCode), + /// Built-in hint type. + BuiltIn(BuiltInHint), +} + +impl Display for HintCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HintCode::Ctrl(ctrl) => write!(f, "{}", ctrl), + HintCode::BuiltIn(builtin) => write!(f, "{}", builtin), + } + } +} + impl TryFrom for HintCode { type Error = anyhow::Error; fn try_from(value: u32) -> Result { match value { - 0x00 => Ok(HintCode::CtrlStart), - 0x01 => Ok(HintCode::CtrlEnd), - 0x02 => Ok(HintCode::CtrlCancel), - 0x03 => Ok(HintCode::CtrlError), - 0x04 => Ok(HintCode::Noop), - 0x05 => Ok(HintCode::EcRecover), - 0x06 => Ok(HintCode::RedMod256), - 0x07 => Ok(HintCode::AddMod256), - 0x08 => Ok(HintCode::MulMod256), - 0x09 => Ok(HintCode::DivRem256), - 0x0A => Ok(HintCode::WPow256), - 0x0B => Ok(HintCode::OMul256), - 0x0C => Ok(HintCode::WMul256), + 0x00 => Ok(HintCode::Ctrl(CtrlCode::Start)), + 0x01 => Ok(HintCode::Ctrl(CtrlCode::End)), + 0x02 => Ok(HintCode::Ctrl(CtrlCode::Cancel)), + 0x03 => Ok(HintCode::Ctrl(CtrlCode::Error)), + 0x04 => Ok(HintCode::BuiltIn(BuiltInHint::Noop)), + 0x05 => Ok(HintCode::BuiltIn(BuiltInHint::EcRecover)), + 0x06 => Ok(HintCode::BuiltIn(BuiltInHint::RedMod256)), + 0x07 => Ok(HintCode::BuiltIn(BuiltInHint::AddMod256)), + 0x08 => Ok(HintCode::BuiltIn(BuiltInHint::MulMod256)), + 0x09 => Ok(HintCode::BuiltIn(BuiltInHint::DivRem256)), + 0x0A => Ok(HintCode::BuiltIn(BuiltInHint::WPow256)), + 0x0B => Ok(HintCode::BuiltIn(BuiltInHint::OMul256)), + 0x0C => Ok(HintCode::BuiltIn(BuiltInHint::WMul256)), _ => Err(anyhow::anyhow!("Invalid hint code: {:#x}", value)), } } } -impl Display for HintCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let name = match self { - HintCode::CtrlStart => "CTRL_START", - HintCode::CtrlEnd => "CTRL_END", - HintCode::CtrlCancel => "CTRL_CANCEL", - HintCode::CtrlError => "CTRL_ERROR", - HintCode::Noop => "NOOP", - HintCode::EcRecover => "ECRECOVER", - HintCode::RedMod256 => "REDMOD256", - HintCode::AddMod256 => "ADDMOD256", - HintCode::MulMod256 => "MULMOD256", - HintCode::DivRem256 => "DIVREM256", - HintCode::WPow256 => "WPOW256", - HintCode::OMul256 => "OMUL256", - HintCode::WMul256 => "WMUL256", - }; - write!(f, "{}", name) +impl HintCode { + /// Convert HintCode to its u32 discriminant value. + #[inline] + pub const fn to_u32(self) -> u32 { + match self { + HintCode::Ctrl(CtrlCode::Start) => 0x00, + HintCode::Ctrl(CtrlCode::End) => 0x01, + HintCode::Ctrl(CtrlCode::Cancel) => 0x02, + HintCode::Ctrl(CtrlCode::Error) => 0x03, + HintCode::BuiltIn(BuiltInHint::Noop) => 0x04, + HintCode::BuiltIn(BuiltInHint::EcRecover) => 0x05, + HintCode::BuiltIn(BuiltInHint::RedMod256) => 0x06, + HintCode::BuiltIn(BuiltInHint::AddMod256) => 0x07, + HintCode::BuiltIn(BuiltInHint::MulMod256) => 0x08, + HintCode::BuiltIn(BuiltInHint::DivRem256) => 0x09, + HintCode::BuiltIn(BuiltInHint::WPow256) => 0x0A, + HintCode::BuiltIn(BuiltInHint::OMul256) => 0x0B, + HintCode::BuiltIn(BuiltInHint::WMul256) => 0x0C, + } } } @@ -174,7 +226,6 @@ impl PrecompileHint { } let header = slice[idx]; - let hint_code = HintCode::try_from((header >> 32) as u32)?; let length = (header & 0xFFFFFFFF) as u32; if slice.len() < idx + length as usize + 1 { @@ -185,6 +236,9 @@ impl PrecompileHint { )); } + let hint_code_32 = (header >> 32) as u32; + let hint_code = HintCode::try_from(hint_code_32)?; + // Create a new Vec with the hint data. let data = slice[idx + 1..idx + length as usize + 1].to_vec(); diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index a93b1af95..a8d52418c 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -5,7 +5,7 @@ use std::future::Future; use std::pin::Pin; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; -use zisk_common::{io::StreamProcessor, HintCode, PrecompileHint}; +use zisk_common::{io::StreamProcessor, CtrlCode, HintCode, PrecompileHint}; use zisk_distributed_common::StreamMessageKind; type AsyncDispatcher = Arc< @@ -55,7 +55,7 @@ impl PrecompileHintsRelay { // Validate hint type is in valid range before accessing stats array // CTRL_START must be the first message of the first batch - if hint.hint_code == HintCode::CtrlStart { + if hint.hint_code == HintCode::Ctrl(CtrlCode::Start) { if !first_batch { return Err(anyhow::anyhow!( "CTRL_START can only be sent as the first message in the stream" @@ -77,7 +77,7 @@ impl PrecompileHintsRelay { idx )); } - has_ctrl_end = hint.hint_code == HintCode::CtrlEnd; + has_ctrl_end = hint.hint_code == HintCode::Ctrl(CtrlCode::End); idx += length + 1; } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 5cc47d98a..83ac2d5a1 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -12,7 +12,7 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use tracing::debug; use zisk_common::io::{StreamProcessor, StreamSink}; -use zisk_common::{HintCode, PrecompileHint}; +use zisk_common::{BuiltInHint, CtrlCode, HintCode, PrecompileHint}; /// Ordered result buffer with drain state. /// @@ -195,8 +195,9 @@ impl HintsProcessor { } // Check if this is a control code or data hint type + match hint.hint_code { - HintCode::CtrlStart => { + HintCode::Ctrl(CtrlCode::Start) => { // CTRL_START must be the first message of the first batch if !first_batch { return Err(anyhow::anyhow!( @@ -215,7 +216,7 @@ impl HintsProcessor { idx += length + 1; continue; } - HintCode::CtrlEnd => { + HintCode::Ctrl(CtrlCode::End) => { // Control hint only; wait for completion then set flag self.wait_for_completion()?; has_ctrl_end = true; @@ -232,29 +233,19 @@ impl HintsProcessor { } break; } - HintCode::CtrlCancel => { + HintCode::Ctrl(CtrlCode::Cancel) => { // Cancel current stream: set error and notify self.state.error_flag.store(true, Ordering::Release); self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream cancelled")); } - HintCode::CtrlError => { + HintCode::Ctrl(CtrlCode::Error) => { // External error signal self.state.error_flag.store(true, Ordering::Release); self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream error signalled")); } - HintCode::Noop - | HintCode::EcRecover - | HintCode::RedMod256 - | HintCode::AddMod256 - | HintCode::MulMod256 - | HintCode::DivRem256 - | HintCode::WPow256 - | HintCode::OMul256 - | HintCode::WMul256 => { - // Data hint type - process normally - } + _ => {} // Built-in data hint; continue processing } // Atomically reserve slot and capture generation inside mutex @@ -267,8 +258,8 @@ impl HintsProcessor { (gen, seq) }; - // Handle HINTS_TYPE_RESULT synchronously - it doesn't need async processing - if hint.hint_code == HintCode::Noop { + // Handle HintCode::Noop synchronously - it doesn't need async processing + if hint.hint_code == HintCode::BuiltIn(BuiltInHint::Noop) { // Immediately mark this slot as complete { let mut queue = self.state.queue.lock().unwrap(); @@ -282,43 +273,7 @@ impl HintsProcessor { // Spawn processing task let state = Arc::clone(&self.state); self.pool.spawn(move || { - // Check if we should stop due to error - if state.error_flag.load(Ordering::Acquire) { - return; - } - - // Process the hint - let result = Self::dispatch_hint(hint); - - // Store result and try to drain - let mut queue = state.queue.lock().unwrap(); - - // Check generation first to detect stale workers from previous sessions - let current_gen = state.generation.load(Ordering::SeqCst); - if generation != current_gen { - // Worker belongs to old generation; ignore result - return; - } - - // Calculate offset in buffer; handle drained slots - if seq_id < queue.next_drain_seq { - // This result belongs to a previous stream/session; ignore - return; - } - let offset = seq_id - queue.next_drain_seq; - - // Check error flag again before storing to avoid processing after error - if state.error_flag.load(Ordering::Acquire) { - return; - } - - queue.buffer[offset] = Some(result); - - // Release lock before notifying - drop(queue); - - // Notify drainer thread - state.drain_signal.notify_one(); + Self::worker_thread(state, hint, generation, seq_id); }); } @@ -330,7 +285,7 @@ impl HintsProcessor { debug!("Processed hints stats:"); let stats = stats.lock().unwrap(); let mut sorted_stats: Vec<_> = stats.iter().collect(); - sorted_stats.sort_by_key(|(hint_code, _)| **hint_code as u32); + sorted_stats.sort_by_key(|(&hint_code, _)| hint_code.to_u32()); for (hint_code, count) in sorted_stats { debug!("Hint type {}: {}", hint_code, count); } @@ -340,6 +295,59 @@ impl HintsProcessor { Ok(has_ctrl_end) } + /// Worker thread that processes a single hint and stores the result. + /// + /// # Arguments + /// + /// * `state` - Shared processor state + /// * `hint` - The hint to process + /// * `generation` - Generation number for detecting stale workers + /// * `seq_id` - Sequence ID for ordering results + fn worker_thread( + state: Arc, + hint: PrecompileHint, + generation: usize, + seq_id: usize, + ) { + // Check if we should stop due to error + if state.error_flag.load(Ordering::Acquire) { + return; + } + + // Process the hint + let result = Self::dispatch_hint(hint); + + // Store result and try to drain + let mut queue = state.queue.lock().unwrap(); + + // Check generation first to detect stale workers from previous sessions + let current_gen = state.generation.load(Ordering::SeqCst); + if generation != current_gen { + // Worker belongs to old generation; ignore result + return; + } + + // Calculate offset in buffer; handle drained slots + if seq_id < queue.next_drain_seq { + // This result belongs to a previous stream/session; ignore + return; + } + let offset = seq_id - queue.next_drain_seq; + + // Check error flag again before storing to avoid processing after error + if state.error_flag.load(Ordering::Acquire) { + return; + } + + queue.buffer[offset] = Some(result); + + // Release lock before notifying + drop(queue); + + // Notify drainer thread + state.drain_signal.notify_one(); + } + /// Drainer thread that waits for hints to complete and drains ready results from queue. fn drainer_thread(state: Arc, hints_sink: Arc) { loop { @@ -452,31 +460,23 @@ impl HintsProcessor { /// /// The result produced by the selected hint handler. /// - /// # Errors + /// # Note /// - /// Returns an error if the hint type is unknown or if the handler fails. + /// Control codes and Noop hints are handled before this function is called. #[inline] fn dispatch_hint(hint: PrecompileHint) -> Result> { match hint.hint_code { - // Control codes should not reach here - HintCode::CtrlStart - | HintCode::CtrlEnd - | HintCode::CtrlCancel - | HintCode::CtrlError => { - Err(anyhow::anyhow!("Control code {:?} should not be dispatched", hint.hint_code)) - } - - // When hint type is HINTS_TYPE_RESULT, return the data as-is. - HintCode::Noop => Ok(hint.data), - - HintCode::EcRecover => Self::process_hint_ecrecover(&hint), - HintCode::RedMod256 => Self::process_hint_redmod256(&hint), - HintCode::AddMod256 => Self::process_hint_addmod256(&hint), - HintCode::MulMod256 => Self::process_hint_mulmod256(&hint), - HintCode::DivRem256 => Self::process_hint_divrem256(&hint), - HintCode::WPow256 => Self::process_hint_wpow256(&hint), - HintCode::OMul256 => Self::process_hint_omul256(&hint), - HintCode::WMul256 => Self::process_hint_wmul256(&hint), + HintCode::BuiltIn(BuiltInHint::EcRecover) => Self::process_hint_ecrecover(&hint), + HintCode::BuiltIn(BuiltInHint::RedMod256) => Self::process_hint_redmod256(&hint), + HintCode::BuiltIn(BuiltInHint::AddMod256) => Self::process_hint_addmod256(&hint), + HintCode::BuiltIn(BuiltInHint::MulMod256) => Self::process_hint_mulmod256(&hint), + HintCode::BuiltIn(BuiltInHint::DivRem256) => Self::process_hint_divrem256(&hint), + HintCode::BuiltIn(BuiltInHint::WPow256) => Self::process_hint_wpow256(&hint), + HintCode::BuiltIn(BuiltInHint::OMul256) => Self::process_hint_omul256(&hint), + HintCode::BuiltIn(BuiltInHint::WMul256) => Self::process_hint_wmul256(&hint), + + // Control codes and Noop are handled before dispatch + _ => unreachable!("Unexpected hint code: {:?}", hint.hint_code), } } @@ -575,7 +575,8 @@ mod tests { #[test] fn test_single_result_hint_non_blocking() { let p = processor(); - let data = vec![make_header(HintCode::Noop as u32, 2), 0x111, 0x222]; + let data = + vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 2), 0x111, 0x222]; // Dispatch should succeed and be non-blocking assert!(p.process_hints(&data, false).is_ok()); @@ -592,11 +593,11 @@ mod tests { fn test_multiple_hints_ordered_output() { let p = processor(); let data = vec![ - make_header(HintCode::Noop as u32, 1), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x111, - make_header(HintCode::Noop as u32, 1), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x222, - make_header(HintCode::Noop as u32, 1), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x333, ]; assert!(p.process_hints(&data, false).is_ok()); @@ -611,8 +612,8 @@ mod tests { #[test] fn test_multiple_calls_global_sequence() { let p = processor(); - let data1 = vec![make_header(HintCode::Noop as u32, 1), 0xAAA]; - let data2 = vec![make_header(HintCode::Noop as u32, 1), 0xBBB]; + let data1 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0xAAA]; + let data2 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0xBBB]; assert!(p.process_hints(&data1, false).is_ok()); assert!(p.process_hints(&data2, false).is_ok()); @@ -652,7 +653,11 @@ mod tests { fn test_error_stops_wait() { let p = processor(); // First valid, then invalid type - let data = vec![make_header(HintCode::Noop as u32, 1), 0x111, make_header(999, 0)]; + let data = vec![ + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), + 0x111, + make_header(999, 0), + ]; // Should error immediately when encountering invalid hint type let result = p.process_hints(&data, false); @@ -675,7 +680,7 @@ mod tests { assert!(!p.state.error_flag.load(Ordering::Acquire)); // Should be able to process new hints after reset - let good = vec![make_header(HintCode::Noop as u32, 1), 0x42]; + let good = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42]; assert!(p.process_hints(&good, false).is_ok()); assert!(p.wait_for_completion().is_ok()); @@ -689,7 +694,7 @@ mod tests { let p = processor(); // First batch increments sequence - let batch1 = vec![make_header(HintCode::Noop as u32, 1), 0x01]; + let batch1 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x01]; p.process_hints(&batch1, false).unwrap(); p.wait_for_completion().unwrap(); @@ -700,7 +705,7 @@ mod tests { } // Send START control - should reset sequence - let start = vec![make_ctrl_header(HintCode::CtrlStart as u32, 0)]; + let start = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::Start).to_u32(), 0)]; p.process_hints(&start, true).unwrap(); // Sequence should be reset to 0 @@ -711,10 +716,10 @@ mod tests { } // Process new batch - let batch2 = vec![make_header(HintCode::Noop as u32, 1), 0x02]; + let batch2 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x02]; p.process_hints(&batch2, false).unwrap(); - let end = vec![make_ctrl_header(HintCode::CtrlEnd as u32, 0)]; + let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::End).to_u32(), 0)]; p.process_hints(&end, false).unwrap(); // Should have processed 1 hint (starting from 0 again) @@ -728,15 +733,15 @@ mod tests { // Dispatch hints let data = vec![ - make_header(HintCode::Noop as u32, 1), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x10, - make_header(HintCode::Noop as u32, 1), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x20, ]; p.process_hints(&data, false).unwrap(); // END should wait internally - let end = vec![make_ctrl_header(HintCode::CtrlEnd as u32, 0)]; + let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::End).to_u32(), 0)]; p.process_hints(&end, false).unwrap(); // Buffer should already be empty @@ -753,7 +758,7 @@ mod tests { #[test] fn test_stream_cancel_returns_error() { let p = processor(); - let cancel = vec![make_ctrl_header(HintCode::CtrlCancel as u32, 0)]; + let cancel = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::Cancel).to_u32(), 0)]; let result = p.process_hints(&cancel, false); assert!(result.is_err()); @@ -766,7 +771,7 @@ mod tests { #[test] fn test_stream_error_signal_returns_error() { let p = processor(); - let signal_err = vec![make_ctrl_header(HintCode::CtrlError as u32, 0)]; + let signal_err = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::Error).to_u32(), 0)]; let result = p.process_hints(&signal_err, false); assert!(result.is_err()); @@ -782,9 +787,9 @@ mod tests { // CTRL_START not at position 0 should fail let data = vec![ - make_header(HintCode::Noop as u32, 1), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42, - make_ctrl_header(HintCode::CtrlStart as u32, 0), + make_ctrl_header(HintCode::Ctrl(CtrlCode::Start).to_u32(), 0), ]; let result = p.process_hints(&data, true); @@ -797,11 +802,11 @@ mod tests { let p = processor(); // First batch is ok - let batch1 = vec![make_header(HintCode::Noop as u32, 1), 0x01]; + let batch1 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x01]; p.process_hints(&batch1, false).unwrap(); // CTRL_START in non-first batch should fail - let start = vec![make_ctrl_header(HintCode::CtrlStart as u32, 0)]; + let start = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::Start).to_u32(), 0)]; let result = p.process_hints(&start, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("first message in the stream")); @@ -813,8 +818,8 @@ mod tests { // CTRL_END not at end should fail let data = vec![ - make_ctrl_header(HintCode::CtrlEnd as u32, 0), - make_header(HintCode::Noop as u32, 1), + make_ctrl_header(HintCode::Ctrl(CtrlCode::End).to_u32(), 0), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42, ]; @@ -844,10 +849,10 @@ mod tests { // Send some data let data = vec![ - make_header(HintCode::Noop as u32, 2), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 2), 0xAAA, 0xBBB, - make_header(HintCode::Noop as u32, 1), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0xCCC, ]; @@ -885,7 +890,7 @@ mod tests { let p = HintsProcessor::builder(sink).num_threads(2).build().unwrap(); // First batch succeeds - let data1 = vec![make_header(HintCode::Noop as u32, 1), 0x01]; + let data1 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x01]; assert!(p.process_hints(&data1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); @@ -893,7 +898,7 @@ mod tests { should_fail.store(true, Ordering::Release); // Second batch should trigger sink error - let data2 = vec![make_header(HintCode::Noop as u32, 1), 0x02]; + let data2 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x02]; assert!(p.process_hints(&data2, false).is_ok()); // Wait should detect the error from drainer thread @@ -910,7 +915,7 @@ mod tests { assert!(p.stats.is_none()); // Should process hints normally - let data = vec![make_header(HintCode::Noop as u32, 1), 0x42]; + let data = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42]; assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); } @@ -924,9 +929,9 @@ mod tests { // Process hints let data = vec![ - make_header(HintCode::Noop as u32, 1), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x111, - make_header(HintCode::Noop as u32, 1), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x222, ]; assert!(p.process_hints(&data, false).is_ok()); @@ -934,7 +939,7 @@ mod tests { // Verify stats were collected let stats = p.stats.as_ref().unwrap().lock().unwrap(); - assert_eq!(stats.get(&HintCode::Noop), Some(&2)); + assert_eq!(stats.get(&HintCode::BuiltIn(BuiltInHint::Noop)), Some(&2)); } #[test] @@ -942,7 +947,7 @@ mod tests { let p = HintsProcessor::builder(NullHints).num_threads(4).build().unwrap(); // Should process hints normally - let data = vec![make_header(HintCode::Noop as u32, 1), 0x42]; + let data = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42]; assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); } @@ -956,9 +961,9 @@ mod tests { // Should still process hints normally let data = vec![ - make_header(HintCode::Noop as u32, 1), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x111, - make_header(HintCode::Noop as u32, 1), + make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x222, ]; assert!(p.process_hints(&data, false).is_ok()); @@ -972,7 +977,7 @@ mod tests { assert!(p.stats.is_some()); - let data = vec![make_header(HintCode::Noop as u32, 1), 0x42]; + let data = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42]; assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); } @@ -989,7 +994,7 @@ mod tests { let mut data = Vec::with_capacity(NUM_HINTS * 2); for i in 0..NUM_HINTS { - data.push(make_header(HintCode::Noop as u32, 1)); + data.push(make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1)); data.push(i as u64); } @@ -1026,7 +1031,7 @@ mod tests { for batch_id in 0..NUM_BATCHES { let mut data = Vec::with_capacity(HINTS_PER_BATCH * 2); for i in 0..HINTS_PER_BATCH { - data.push(make_header(HintCode::Noop as u32, 1)); + data.push(make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1)); data.push((batch_id * HINTS_PER_BATCH + i) as u64); } p.process_hints(&data, false).unwrap(); @@ -1063,19 +1068,19 @@ mod tests { for _iter in 0..ITERATIONS { // Reset at start of each iteration - let reset = vec![make_ctrl_header(HintCode::CtrlStart as u32, 0)]; + let reset = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::Start).to_u32(), 0)]; p.process_hints(&reset, true).unwrap(); // Process batch let mut data = Vec::with_capacity(HINTS_PER_ITER * 2); for i in 0..HINTS_PER_ITER { - data.push(make_header(HintCode::Noop as u32, 1)); + data.push(make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1)); data.push(i as u64); } p.process_hints(&data, false).unwrap(); // End stream - let end = vec![make_ctrl_header(HintCode::CtrlEnd as u32, 0)]; + let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::End).to_u32(), 0)]; p.process_hints(&end, false).unwrap(); } From 03226bc97b2e61b90dc3e040d58700b333879c54 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:16:19 +0000 Subject: [PATCH 178/782] added custom hint code with custom handler --- common/src/hints.rs | 139 ++++-- .../crates/coordinator/src/hints_relay.rs | 8 +- precompiles/hints/src/hints_processor.rs | 432 ++++++++++++------ 3 files changed, 403 insertions(+), 176 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 984ffa83c..4b730a30c 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -49,29 +49,60 @@ use std::fmt::Display; use anyhow::Result; +// Control code constants +const CTRL_START: u32 = 0x00; +const CTRL_END: u32 = 0x01; +const CTRL_CANCEL: u32 = 0x02; +const CTRL_ERROR: u32 = 0x03; + +// Built-in hint code constants +const HINT_NOOP: u32 = 0x04; +const HINT_ECRECOVER: u32 = 0x05; +const HINT_REDMOD256: u32 = 0x06; +const HINT_ADDMOD256: u32 = 0x07; +const HINT_MULMOD256: u32 = 0x08; +const HINT_DIVREM256: u32 = 0x09; +const HINT_WPOW256: u32 = 0x0A; +const HINT_OMUL256: u32 = 0x0B; +const HINT_WMUL256: u32 = 0x0C; + /// Control code variants for stream control. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u32)] -pub enum CtrlCode { +pub enum CtrlHint { /// Reset processor state and global sequence. - Start = 0x00, + Start = CTRL_START, /// Wait until completion of all pending hints. - End = 0x01, + End = CTRL_END, /// Cancel current stream and stop processing. - Cancel = 0x02, + Cancel = CTRL_CANCEL, /// Signal error and stop processing. - Error = 0x03, + Error = CTRL_ERROR, } -impl Display for CtrlCode { +impl Display for CtrlHint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = match self { - CtrlCode::Start => "CTRL_START", - CtrlCode::End => "CTRL_END", - CtrlCode::Cancel => "CTRL_CANCEL", - CtrlCode::Error => "CTRL_ERROR", + CtrlHint::Start => "CTRL_START", + CtrlHint::End => "CTRL_END", + CtrlHint::Cancel => "CTRL_CANCEL", + CtrlHint::Error => "CTRL_ERROR", }; - write!(f, "{}", name) + write!(f, "{} ({:#x})", name, *self as u32) + } +} + +impl TryFrom for CtrlHint { + type Error = anyhow::Error; + + fn try_from(value: u32) -> Result { + match value { + CTRL_START => Ok(Self::Start), + CTRL_END => Ok(Self::End), + CTRL_CANCEL => Ok(Self::Cancel), + CTRL_ERROR => Ok(Self::Error), + _ => Err(anyhow::anyhow!("Invalid control code: {:#x}", value)), + } } } @@ -82,23 +113,23 @@ pub enum BuiltInHint { /// Pass-through hint type. /// When a hint has this type, the processor simply passes through the data /// without any additional computation. - Noop = 0x04, + Noop = HINT_NOOP, /// Ecrecover precompile hint type. - EcRecover = 0x05, + EcRecover = HINT_ECRECOVER, /// Modular reduction of a 256-bit integer hint type. - RedMod256 = 0x06, + RedMod256 = HINT_REDMOD256, /// Modular addition of 256-bit integers hint type. - AddMod256 = 0x07, + AddMod256 = HINT_ADDMOD256, /// Modular multiplication of 256-bit integers hint type. - MulMod256 = 0x08, + MulMod256 = HINT_MULMOD256, /// Division and remainder of 256-bit integers hint type. - DivRem256 = 0x09, + DivRem256 = HINT_DIVREM256, /// Wrapping exponentiation of 256-bit integers hint type. - WPow256 = 0x0A, + WPow256 = HINT_WPOW256, /// Overflowing multiplication of 256-bit integers hint type. - OMul256 = 0x0B, + OMul256 = HINT_OMUL256, /// Wrapping multiplication of 256-bit integers hint type. - WMul256 = 0x0C, + WMul256 = HINT_WMUL256, } impl Display for BuiltInHint { @@ -114,7 +145,26 @@ impl Display for BuiltInHint { BuiltInHint::OMul256 => "OMUL256", BuiltInHint::WMul256 => "WMUL256", }; - write!(f, "{}", name) + write!(f, "{} ({:#x})", name, *self as u32) + } +} + +impl TryFrom for BuiltInHint { + type Error = anyhow::Error; + + fn try_from(value: u32) -> Result { + match value { + HINT_NOOP => Ok(Self::Noop), + HINT_ECRECOVER => Ok(Self::EcRecover), + HINT_REDMOD256 => Ok(Self::RedMod256), + HINT_ADDMOD256 => Ok(Self::AddMod256), + HINT_MULMOD256 => Ok(Self::MulMod256), + HINT_DIVREM256 => Ok(Self::DivRem256), + HINT_WPOW256 => Ok(Self::WPow256), + HINT_OMUL256 => Ok(Self::OMul256), + HINT_WMUL256 => Ok(Self::WMul256), + _ => Err(anyhow::anyhow!("Invalid built-in hint code: {:#x}", value)), + } } } @@ -123,9 +173,11 @@ impl Display for BuiltInHint { #[repr(u32)] pub enum HintCode { /// Control code for stream management. - Ctrl(CtrlCode), + Ctrl(CtrlHint), /// Built-in hint type. BuiltIn(BuiltInHint), + /// Custom hint type + Custom(u32), } impl Display for HintCode { @@ -133,6 +185,7 @@ impl Display for HintCode { match self { HintCode::Ctrl(ctrl) => write!(f, "{}", ctrl), HintCode::BuiltIn(builtin) => write!(f, "{}", builtin), + HintCode::Custom(code) => write!(f, "CUSTOM_HINT_{:#x}", code), } } } @@ -141,22 +194,16 @@ impl TryFrom for HintCode { type Error = anyhow::Error; fn try_from(value: u32) -> Result { - match value { - 0x00 => Ok(HintCode::Ctrl(CtrlCode::Start)), - 0x01 => Ok(HintCode::Ctrl(CtrlCode::End)), - 0x02 => Ok(HintCode::Ctrl(CtrlCode::Cancel)), - 0x03 => Ok(HintCode::Ctrl(CtrlCode::Error)), - 0x04 => Ok(HintCode::BuiltIn(BuiltInHint::Noop)), - 0x05 => Ok(HintCode::BuiltIn(BuiltInHint::EcRecover)), - 0x06 => Ok(HintCode::BuiltIn(BuiltInHint::RedMod256)), - 0x07 => Ok(HintCode::BuiltIn(BuiltInHint::AddMod256)), - 0x08 => Ok(HintCode::BuiltIn(BuiltInHint::MulMod256)), - 0x09 => Ok(HintCode::BuiltIn(BuiltInHint::DivRem256)), - 0x0A => Ok(HintCode::BuiltIn(BuiltInHint::WPow256)), - 0x0B => Ok(HintCode::BuiltIn(BuiltInHint::OMul256)), - 0x0C => Ok(HintCode::BuiltIn(BuiltInHint::WMul256)), - _ => Err(anyhow::anyhow!("Invalid hint code: {:#x}", value)), + // Try CtrlCode first + if let Ok(ctrl) = CtrlHint::try_from(value) { + return Ok(HintCode::Ctrl(ctrl)); + } + // Try BuiltInHint next + if let Ok(builtin) = BuiltInHint::try_from(value) { + return Ok(HintCode::BuiltIn(builtin)); } + // Unknown codes return error - custom codes handled separately + Err(anyhow::anyhow!("Unknown hint code: {:#x}", value)) } } @@ -165,10 +212,10 @@ impl HintCode { #[inline] pub const fn to_u32(self) -> u32 { match self { - HintCode::Ctrl(CtrlCode::Start) => 0x00, - HintCode::Ctrl(CtrlCode::End) => 0x01, - HintCode::Ctrl(CtrlCode::Cancel) => 0x02, - HintCode::Ctrl(CtrlCode::Error) => 0x03, + HintCode::Ctrl(CtrlHint::Start) => 0x00, + HintCode::Ctrl(CtrlHint::End) => 0x01, + HintCode::Ctrl(CtrlHint::Cancel) => 0x02, + HintCode::Ctrl(CtrlHint::Error) => 0x03, HintCode::BuiltIn(BuiltInHint::Noop) => 0x04, HintCode::BuiltIn(BuiltInHint::EcRecover) => 0x05, HintCode::BuiltIn(BuiltInHint::RedMod256) => 0x06, @@ -178,6 +225,7 @@ impl HintCode { HintCode::BuiltIn(BuiltInHint::WPow256) => 0x0A, HintCode::BuiltIn(BuiltInHint::OMul256) => 0x0B, HintCode::BuiltIn(BuiltInHint::WMul256) => 0x0C, + HintCode::Custom(code) => code, } } } @@ -214,13 +262,14 @@ impl PrecompileHint { /// /// * `slice` - The source slice containing concatenated hints /// * `idx` - The index where the hint header starts + /// * `allow_custom` - If true, unknown codes create Custom variant; if false, return error /// /// # Returns /// /// * `Ok(PrecompileHint)` - Successfully parsed hint /// * `Err` - If the slice is too short or the index is out of bounds #[inline(always)] - pub fn from_u64_slice(slice: &[u64], idx: usize) -> Result { + pub fn from_u64_slice(slice: &[u64], idx: usize, allow_custom: bool) -> Result { if slice.is_empty() || idx >= slice.len() { return Err(anyhow::anyhow!("Slice too short or index out of bounds")); } @@ -237,7 +286,11 @@ impl PrecompileHint { } let hint_code_32 = (header >> 32) as u32; - let hint_code = HintCode::try_from(hint_code_32)?; + let hint_code = if allow_custom { + HintCode::try_from(hint_code_32).unwrap_or(HintCode::Custom(hint_code_32)) + } else { + HintCode::try_from(hint_code_32)? + }; // Create a new Vec with the hint data. let data = slice[idx + 1..idx + length as usize + 1].to_vec(); diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index a8d52418c..79fe28192 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -5,7 +5,7 @@ use std::future::Future; use std::pin::Pin; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; -use zisk_common::{io::StreamProcessor, CtrlCode, HintCode, PrecompileHint}; +use zisk_common::{io::StreamProcessor, CtrlHint, HintCode, PrecompileHint}; use zisk_distributed_common::StreamMessageKind; type AsyncDispatcher = Arc< @@ -49,13 +49,13 @@ impl PrecompileHintsRelay { // Parse hints and dispatch to pool let mut idx = 0; while idx < hints.len() { - let hint = PrecompileHint::from_u64_slice(hints, idx)?; + let hint = PrecompileHint::from_u64_slice(hints, idx, true)?; let length = hint.data.len(); // Validate hint type is in valid range before accessing stats array // CTRL_START must be the first message of the first batch - if hint.hint_code == HintCode::Ctrl(CtrlCode::Start) { + if hint.hint_code == HintCode::Ctrl(CtrlHint::Start) { if !first_batch { return Err(anyhow::anyhow!( "CTRL_START can only be sent as the first message in the stream" @@ -77,7 +77,7 @@ impl PrecompileHintsRelay { idx )); } - has_ctrl_end = hint.hint_code == HintCode::Ctrl(CtrlCode::End); + has_ctrl_end = hint.hint_code == HintCode::Ctrl(CtrlHint::End); idx += length + 1; } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 83ac2d5a1..c68e633c6 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -12,7 +12,7 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use tracing::debug; use zisk_common::io::{StreamProcessor, StreamSink}; -use zisk_common::{BuiltInHint, CtrlCode, HintCode, PrecompileHint}; +use zisk_common::{BuiltInHint, CtrlHint, HintCode, PrecompileHint}; /// Ordered result buffer with drain state. /// @@ -54,11 +54,15 @@ impl HintProcessorState { } } +/// Type alias for custom hint handler functions. +pub type CustomHintHandler = Arc Result> + Send + Sync>; + /// Builder for configuring and constructing a [`HintsProcessor`]. pub struct HintsProcessorBuilder { hints_sink: HS, num_threads: usize, enable_stats: bool, + custom_handlers: HashMap, } impl HintsProcessorBuilder { @@ -74,6 +78,31 @@ impl HintsProcessorBuilder { self } + /// Registers a custom hint handler for a specific hint code. + /// + /// # Arguments + /// + /// * `hint_code` - The u32 hint code identifier (should not conflict with built-in codes) + /// * `handler` - Function that processes the hint data and returns the result + /// + /// # Examples + /// + /// ```ignore + /// let processor = HintsProcessor::builder(my_sink) + /// .custom_hint(0x10, |data| { + /// // Custom processing logic + /// Ok(vec![data[0] * 2]) + /// }) + /// .build()?; + /// ``` + pub fn custom_hint(mut self, hint_code: u32, handler: F) -> Self + where + F: Fn(&[u64]) -> Result> + Send + Sync + 'static, + { + self.custom_handlers.insert(hint_code, Arc::new(handler)); + self + } + /// Builds the [`HintsProcessor`] with the configured settings. /// /// # Returns @@ -102,6 +131,7 @@ impl HintsProcessorBuilder { stats: if self.enable_stats { Some(Mutex::new(HashMap::new())) } else { None }, hints_sink, drainer_thread: ManuallyDrop::new(drainer_thread), + custom_handlers: Arc::new(self.custom_handlers), }) } } @@ -127,6 +157,9 @@ pub struct HintsProcessor { /// Handle to the drainer thread (wrapped in ManuallyDrop to join in Drop) drainer_thread: ManuallyDrop>, + + /// Custom hint handlers registered by the user + custom_handlers: Arc>, } impl HintsProcessor { @@ -151,6 +184,7 @@ impl HintsProcessor { hints_sink, num_threads: Self::DEFAULT_NUM_THREADS, enable_stats: false, + custom_handlers: HashMap::new(), } } @@ -166,6 +200,11 @@ impl HintsProcessor { /// - **Ordered submission**: Results submitted to sink in order hints were received /// - **Error handling**: Stops processing on first error /// + /// # Concurrency Warning + /// + /// This method takes is designed for **sequential usage only**. + /// Concurrent calls may cause incorrect processing. + /// /// # Arguments /// /// * `hints` - A slice of `u64` values containing concatenated hints @@ -187,7 +226,18 @@ impl HintsProcessor { return Err(anyhow::anyhow!("Processing stopped due to previous error")); } - let hint = PrecompileHint::from_u64_slice(hints, idx)?; + let hint = PrecompileHint::from_u64_slice(hints, idx, true)?; + + // Check if custom handler is registered for custom hints + if let HintCode::Custom(code) = hint.hint_code { + if !self.custom_handlers.contains_key(&code) { + return Err(anyhow::anyhow!( + "Unknown custom hint code {:#x}: no handler registered", + code + )); + } + } + let length = hint.data.len(); if let Some(stats) = &self.stats { @@ -197,7 +247,7 @@ impl HintsProcessor { // Check if this is a control code or data hint type match hint.hint_code { - HintCode::Ctrl(CtrlCode::Start) => { + HintCode::Ctrl(CtrlHint::Start) => { // CTRL_START must be the first message of the first batch if !first_batch { return Err(anyhow::anyhow!( @@ -216,7 +266,7 @@ impl HintsProcessor { idx += length + 1; continue; } - HintCode::Ctrl(CtrlCode::End) => { + HintCode::Ctrl(CtrlHint::End) => { // Control hint only; wait for completion then set flag self.wait_for_completion()?; has_ctrl_end = true; @@ -233,49 +283,53 @@ impl HintsProcessor { } break; } - HintCode::Ctrl(CtrlCode::Cancel) => { + HintCode::Ctrl(CtrlHint::Cancel) => { // Cancel current stream: set error and notify self.state.error_flag.store(true, Ordering::Release); self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream cancelled")); } - HintCode::Ctrl(CtrlCode::Error) => { + HintCode::Ctrl(CtrlHint::Error) => { // External error signal self.state.error_flag.store(true, Ordering::Release); self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream error signalled")); } - _ => {} // Built-in data hint; continue processing + _ => {} // Built-in data hint or custom hint; continue processing } - // Atomically reserve slot and capture generation inside mutex - // This prevents orphaned slots if reset happens between generation load and push_back - let (generation, seq_id) = { - let mut queue = self.state.queue.lock().unwrap(); - let gen = self.state.generation.load(Ordering::SeqCst); - let seq = self.state.next_seq.fetch_add(1, Ordering::SeqCst); - queue.buffer.push_back(None); - (gen, seq) - }; + // Capture generation outside mutex - SeqCst provides sufficient ordering + let generation = self.state.generation.load(Ordering::SeqCst); - // Handle HintCode::Noop synchronously - it doesn't need async processing - if hint.hint_code == HintCode::BuiltIn(BuiltInHint::Noop) { - // Immediately mark this slot as complete - { - let mut queue = self.state.queue.lock().unwrap(); - let offset = seq_id - queue.next_drain_seq; - queue.buffer[offset] = Some(Ok(hint.data)); + // Atomically reserve slot - use Relaxed for seq since mutex provides ordering + let seq_id = { + let mut queue = self.state.queue.lock().unwrap(); + let seq = self.state.next_seq.fetch_add(1, Ordering::Relaxed); + + // Handle HintCode::Noop synchronously - reserve and fill slot in one step + if hint.hint_code == HintCode::BuiltIn(BuiltInHint::Noop) { + queue.buffer.push_back(Some(Ok(hint.data.clone()))); + // Notify immediately while holding the lock to ensure drainer sees the result + // Release lock after this block, avoiding duplicate notification + drop(queue); + // Use notify_all since wait_for_completion also waits on this condvar + self.state.drain_signal.notify_all(); + // Continue to next hint without spawning worker + idx += length + 1; + continue; + } else { + queue.buffer.push_back(None); } - // Notify drainer thread - self.state.drain_signal.notify_one(); - } else { - // Spawn processing task - let state = Arc::clone(&self.state); - self.pool.spawn(move || { - Self::worker_thread(state, hint, generation, seq_id); - }); - } + seq + }; + + // Spawn processing task for async hints (Noop already handled above) + let state = Arc::clone(&self.state); + let custom_handlers = Arc::clone(&self.custom_handlers); + self.pool.spawn(move || { + Self::worker_thread(state, hint, generation, seq_id, custom_handlers); + }); idx += length + 1; } @@ -303,27 +357,37 @@ impl HintsProcessor { /// * `hint` - The hint to process /// * `generation` - Generation number for detecting stale workers /// * `seq_id` - Sequence ID for ordering results + /// * `custom_handlers` - Custom hint handlers fn worker_thread( state: Arc, hint: PrecompileHint, generation: usize, seq_id: usize, + custom_handlers: Arc>, ) { - // Check if we should stop due to error - if state.error_flag.load(Ordering::Acquire) { + // Check generation first to detect stale workers (before processing) + let current_gen = state.generation.load(Ordering::SeqCst); + if generation != current_gen { + // Worker belongs to old generation; ignore return; } - // Process the hint - let result = Self::dispatch_hint(hint); + // Check if we should stop due to error - but still need to fill the slot + let result = if state.error_flag.load(Ordering::Acquire) { + Err(anyhow::anyhow!("Processing stopped due to error")) + } else { + // Process the hint + Self::dispatch_hint(hint, custom_handlers) + }; - // Store result and try to drain + // Store result - MUST fill slot even if error occurred let mut queue = state.queue.lock().unwrap(); - // Check generation first to detect stale workers from previous sessions + // Check generation again in case reset happened during processing let current_gen = state.generation.load(Ordering::SeqCst); if generation != current_gen { - // Worker belongs to old generation; ignore result + // Worker belongs to old generation; buffer was cleared and repopulated + // Our seq_id is from the old session and doesn't correspond to current slots return; } @@ -334,18 +398,20 @@ impl HintsProcessor { } let offset = seq_id - queue.next_drain_seq; - // Check error flag again before storing to avoid processing after error - if state.error_flag.load(Ordering::Acquire) { + // Check if slot exists - if not, drainer already processed and removed it + if offset >= queue.buffer.len() { + // Slot was already drained; safe to drop this result return; } + // Fill the slot to allow drainer to proceed (critical for ordering) queue.buffer[offset] = Some(result); // Release lock before notifying drop(queue); - // Notify drainer thread - state.drain_signal.notify_one(); + // Notify drainer thread (use notify_all to wake any waiting threads) + state.drain_signal.notify_all(); } /// Drainer thread that waits for hints to complete and drains ready results from queue. @@ -359,7 +425,9 @@ impl HintsProcessor { } // Drain all consecutive ready results from the front + let mut drained_any = false; while let Some(Some(res)) = queue.buffer.front() { + drained_any = true; match res { Ok(data) => { // Clone data before dropping lock @@ -393,11 +461,16 @@ impl HintsProcessor { } } - // Notify waiters if buffer is now empty - if queue.buffer.is_empty() { + // If we drained any results, notify wait_for_completion that buffer changed + if drained_any { state.drain_signal.notify_all(); } + // Check for shutdown again before waiting + if state.shutdown.load(Ordering::Acquire) { + break; + } + // Wait for notification that a hint completed #[allow(unused_assignments)] { @@ -415,7 +488,7 @@ impl HintsProcessor { /// /// * `Ok(())` - All hints processed successfully /// * `Err` - If an error occurred during processing - fn wait_for_completion(&self) -> Result<()> { + pub fn wait_for_completion(&self) -> Result<()> { let mut queue = self.state.queue.lock().unwrap(); while !queue.buffer.is_empty() { @@ -441,9 +514,12 @@ impl HintsProcessor { /// Increments the generation counter to invalidate any in-flight workers /// from the previous session, preventing them from corrupting the new state. fn reset(&self) { + // Clear error flag - use Release to synchronize with Acquire loads in workers self.state.error_flag.store(false, Ordering::Release); - self.state.next_seq.store(0, Ordering::Release); - // Increment generation to invalidate stale workers + // Reset sequence counter - Relaxed is sufficient as it's only used within mutex + self.state.next_seq.store(0, Ordering::Relaxed); + // Increment generation with SeqCst to invalidate stale workers + // This provides a total ordering fence that synchronizes with worker generation checks self.state.generation.fetch_add(1, Ordering::SeqCst); let mut queue = self.state.queue.lock().unwrap(); queue.buffer.clear(); @@ -455,6 +531,7 @@ impl HintsProcessor { /// # Arguments /// /// * `hint` - The parsed hint to dispatch + /// * `custom_handlers` - Custom hint handlers /// /// # Returns /// @@ -464,7 +541,10 @@ impl HintsProcessor { /// /// Control codes and Noop hints are handled before this function is called. #[inline] - fn dispatch_hint(hint: PrecompileHint) -> Result> { + fn dispatch_hint( + hint: PrecompileHint, + custom_handlers: Arc>, + ) -> Result> { match hint.hint_code { HintCode::BuiltIn(BuiltInHint::EcRecover) => Self::process_hint_ecrecover(&hint), HintCode::BuiltIn(BuiltInHint::RedMod256) => Self::process_hint_redmod256(&hint), @@ -475,8 +555,17 @@ impl HintsProcessor { HintCode::BuiltIn(BuiltInHint::OMul256) => Self::process_hint_omul256(&hint), HintCode::BuiltIn(BuiltInHint::WMul256) => Self::process_hint_wmul256(&hint), + // Custom hints + HintCode::Custom(code) => { + if let Some(handler) = custom_handlers.get(&code) { + handler(&hint.data) + } else { + Err(anyhow::anyhow!("Unknown custom hint code: {:#x}", code)) + } + } + // Control codes and Noop are handled before dispatch - _ => unreachable!("Unexpected hint code: {:?}", hint.hint_code), + _ => Err(anyhow::anyhow!("Unexpected hint code: {:#x}", hint.hint_code.to_u32())), } } @@ -646,7 +735,7 @@ mod tests { // Should return error immediately during validation let result = p.process_hints(&data, false); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Invalid hint code")); + assert!(result.unwrap_err().to_string().contains("Unknown custom hint code")); } #[test] @@ -662,7 +751,7 @@ mod tests { // Should error immediately when encountering invalid hint type let result = p.process_hints(&data, false); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Invalid hint code")); + assert!(result.unwrap_err().to_string().contains("Unknown custom hint code")); } #[test] @@ -673,7 +762,7 @@ mod tests { // Should get synchronous error for invalid hint type assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Invalid hint code")); + assert!(result.unwrap_err().to_string().contains("Unknown custom hint code")); // Reset should clear any error state p.reset(); @@ -705,7 +794,7 @@ mod tests { } // Send START control - should reset sequence - let start = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::Start).to_u32(), 0)]; + let start = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::Start).to_u32(), 0)]; p.process_hints(&start, true).unwrap(); // Sequence should be reset to 0 @@ -719,7 +808,7 @@ mod tests { let batch2 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x02]; p.process_hints(&batch2, false).unwrap(); - let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::End).to_u32(), 0)]; + let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::End).to_u32(), 0)]; p.process_hints(&end, false).unwrap(); // Should have processed 1 hint (starting from 0 again) @@ -741,7 +830,7 @@ mod tests { p.process_hints(&data, false).unwrap(); // END should wait internally - let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::End).to_u32(), 0)]; + let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::End).to_u32(), 0)]; p.process_hints(&end, false).unwrap(); // Buffer should already be empty @@ -758,7 +847,7 @@ mod tests { #[test] fn test_stream_cancel_returns_error() { let p = processor(); - let cancel = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::Cancel).to_u32(), 0)]; + let cancel = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::Cancel).to_u32(), 0)]; let result = p.process_hints(&cancel, false); assert!(result.is_err()); @@ -771,7 +860,7 @@ mod tests { #[test] fn test_stream_error_signal_returns_error() { let p = processor(); - let signal_err = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::Error).to_u32(), 0)]; + let signal_err = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::Error).to_u32(), 0)]; let result = p.process_hints(&signal_err, false); assert!(result.is_err()); @@ -789,7 +878,7 @@ mod tests { let data = vec![ make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42, - make_ctrl_header(HintCode::Ctrl(CtrlCode::Start).to_u32(), 0), + make_ctrl_header(HintCode::Ctrl(CtrlHint::Start).to_u32(), 0), ]; let result = p.process_hints(&data, true); @@ -806,7 +895,7 @@ mod tests { p.process_hints(&batch1, false).unwrap(); // CTRL_START in non-first batch should fail - let start = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::Start).to_u32(), 0)]; + let start = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::Start).to_u32(), 0)]; let result = p.process_hints(&start, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("first message in the stream")); @@ -818,7 +907,7 @@ mod tests { // CTRL_END not at end should fail let data = vec![ - make_ctrl_header(HintCode::Ctrl(CtrlCode::End).to_u32(), 0), + make_ctrl_header(HintCode::Ctrl(CtrlHint::End).to_u32(), 0), make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42, ]; @@ -908,78 +997,29 @@ mod tests { // Builder tests #[test] - fn test_builder_default() { - let p = HintsProcessor::builder(NullHints).build().unwrap(); - - // Should have stats disabled by default - assert!(p.stats.is_none()); - - // Should process hints normally - let data = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42]; - assert!(p.process_hints(&data, false).is_ok()); - assert!(p.wait_for_completion().is_ok()); - } + fn test_builder_configuration() { + // Default builder - stats disabled + let p1 = HintsProcessor::builder(NullHints).build().unwrap(); + assert!(p1.stats.is_none()); - #[test] - fn test_builder_stats_enabled() { - let p = HintsProcessor::builder(NullHints).enable_stats(true).build().unwrap(); + // Explicitly disabled stats + let p2 = HintsProcessor::builder(NullHints).enable_stats(false).build().unwrap(); + assert!(p2.stats.is_none()); - // Stats should be Some - assert!(p.stats.is_some()); + // Stats enabled + let p3 = HintsProcessor::builder(NullHints).enable_stats(true).build().unwrap(); + assert!(p3.stats.is_some()); - // Process hints - let data = vec![ - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), - 0x111, - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), - 0x222, - ]; - assert!(p.process_hints(&data, false).is_ok()); - assert!(p.wait_for_completion().is_ok()); - - // Verify stats were collected - let stats = p.stats.as_ref().unwrap().lock().unwrap(); - assert_eq!(stats.get(&HintCode::BuiltIn(BuiltInHint::Noop)), Some(&2)); - } - - #[test] - fn test_builder_custom_threads() { - let p = HintsProcessor::builder(NullHints).num_threads(4).build().unwrap(); - - // Should process hints normally + // Custom threads + let p4 = HintsProcessor::builder(NullHints).num_threads(4).build().unwrap(); let data = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42]; - assert!(p.process_hints(&data, false).is_ok()); - assert!(p.wait_for_completion().is_ok()); - } - - #[test] - fn test_builder_stats_disabled() { - let p = HintsProcessor::builder(NullHints).enable_stats(false).build().unwrap(); - - // Stats should be None - assert!(p.stats.is_none()); + assert!(p4.process_hints(&data, false).is_ok()); + assert!(p4.wait_for_completion().is_ok()); - // Should still process hints normally - let data = vec![ - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), - 0x111, - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), - 0x222, - ]; - assert!(p.process_hints(&data, false).is_ok()); - assert!(p.wait_for_completion().is_ok()); - } - - #[test] - fn test_builder_chaining() { - let p = + // Chaining multiple options + let p5 = HintsProcessor::builder(NullHints).num_threads(8).enable_stats(true).build().unwrap(); - - assert!(p.stats.is_some()); - - let data = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42]; - assert!(p.process_hints(&data, false).is_ok()); - assert!(p.wait_for_completion().is_ok()); + assert!(p5.stats.is_some()); } // Stress test @@ -1068,7 +1108,7 @@ mod tests { for _iter in 0..ITERATIONS { // Reset at start of each iteration - let reset = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::Start).to_u32(), 0)]; + let reset = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::Start).to_u32(), 0)]; p.process_hints(&reset, true).unwrap(); // Process batch @@ -1080,7 +1120,7 @@ mod tests { p.process_hints(&data, false).unwrap(); // End stream - let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlCode::End).to_u32(), 0)]; + let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::End).to_u32(), 0)]; p.process_hints(&end, false).unwrap(); } @@ -1103,4 +1143,138 @@ mod tests { ops_per_sec ); } + + #[test] + fn test_custom_handlers_ordered_with_delays() { + use std::sync::{Arc, Mutex}; + use std::thread; + use std::time::Duration; + + struct RecordingSink { + received: Arc>>>, + } + + impl StreamSink for RecordingSink { + fn submit(&self, processed: Vec) -> Result<()> { + self.received.lock().unwrap().push(processed); + Ok(()) + } + } + + let received = Arc::new(Mutex::new(Vec::new())); + let sink = RecordingSink { received: Arc::clone(&received) }; + + // Custom hint codes + const FAST_HINT: u32 = 0x100; // Processes instantly + const SLOW_HINT: u32 = 0x101; // Delays 10ms + const MED_HINT: u32 = 0x102; // Delays 5ms + + let p = HintsProcessor::builder(sink) + .num_threads(8) + .custom_hint(FAST_HINT, |data| { + // No delay - returns immediately + Ok(vec![data[0] * 2]) + }) + .custom_hint(SLOW_HINT, |data| { + // Long delay to complete last + thread::sleep(Duration::from_millis(10)); + Ok(vec![data[0] * 3]) + }) + .custom_hint(MED_HINT, |data| { + // Medium delay + thread::sleep(Duration::from_millis(5)); + Ok(vec![data[0] * 4]) + }) + .build() + .unwrap(); + + // Send hints in order: SLOW, FAST, MED + // They should complete in order: FAST, MED, SLOW + // But results should be returned in submission order: SLOW, FAST, MED + let data = vec![ + make_header(SLOW_HINT, 1), + 10, // Will complete last but should be first result + make_header(FAST_HINT, 1), + 20, // Will complete first but should be second result + make_header(MED_HINT, 1), + 30, // Will complete second but should be third result + make_header(FAST_HINT, 1), + 40, // Fast again + make_header(SLOW_HINT, 1), + 50, // Slow again + ]; + + p.process_hints(&data, false).unwrap(); + p.wait_for_completion().unwrap(); + + // Verify results are in submission order, not completion order + let results = received.lock().unwrap(); + assert_eq!(results.len(), 5); + assert_eq!(results[0], vec![30]); // SLOW: 10 * 3 + assert_eq!(results[1], vec![40]); // FAST: 20 * 2 + assert_eq!(results[2], vec![120]); // MED: 30 * 4 + assert_eq!(results[3], vec![80]); // FAST: 40 * 2 + assert_eq!(results[4], vec![150]); // SLOW: 50 * 3 + } + + #[test] + fn test_custom_handlers_stress_ordering() { + use std::sync::{Arc, Mutex}; + use std::thread; + use std::time::Duration; + + struct RecordingSink { + received: Arc>>>, + } + + impl StreamSink for RecordingSink { + fn submit(&self, processed: Vec) -> Result<()> { + self.received.lock().unwrap().push(processed); + Ok(()) + } + } + + let received = Arc::new(Mutex::new(Vec::new())); + let sink = RecordingSink { received: Arc::clone(&received) }; + + const VARIABLE_HINT: u32 = 0x200; + + let p = HintsProcessor::builder(sink) + .num_threads(16) + .custom_hint(VARIABLE_HINT, |data| { + // Pseudo-random delay based on hash of input value (0-15ms range) + // This creates unpredictable completion order across runs + let hash = data[0].wrapping_mul(2654435761); + let delay_ms = (hash % 16) as u64; + if delay_ms > 0 { + thread::sleep(Duration::from_millis(delay_ms)); + } + Ok(vec![data[0] + 1000]) + }) + .build() + .unwrap(); + + // Generate pseudo-random number of hints between 100 and 500 + // Using current time as seed for variation across test runs + use std::time::SystemTime; + let seed = + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as u64; + let num_hints = 100 + (seed % 401) as usize; // 100 to 500 inclusive + + let mut data = Vec::with_capacity(num_hints * 2); + for i in 0..num_hints { + data.push(make_header(VARIABLE_HINT, 1)); + data.push(i as u64); + } + + p.process_hints(&data, false).unwrap(); + p.wait_for_completion().unwrap(); + + // Verify all results are in correct order despite random completion times + let results = received.lock().unwrap(); + assert_eq!(results.len(), num_hints, "Expected {} results", num_hints); + for i in 0..num_hints { + assert_eq!(results[i][0], i as u64 + 1000, "Result {} out of order", i); + } + } } From b79bac64769d73e50964a01bf6d369fe28962f07 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:17:01 +0000 Subject: [PATCH 179/782] added hints processor benchmarks --- Cargo.lock | 61 ++++++++++++++++++++++++++++++++++-- precompiles/hints/Cargo.toml | 9 +++++- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea39ca8ca..0e48e8314 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,15 @@ dependencies = [ "equator", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -963,7 +972,7 @@ dependencies = [ "cast", "ciborium", "clap", - "criterion-plot", + "criterion-plot 0.5.0", "is-terminal", "itertools 0.10.5", "num-traits", @@ -979,6 +988,31 @@ dependencies = [ "walkdir", ] +[[package]] +name = "criterion" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot 0.8.1", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + [[package]] name = "criterion-plot" version = "0.5.0" @@ -989,6 +1023,16 @@ dependencies = [ "itertools 0.10.5", ] +[[package]] +name = "criterion-plot" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" +dependencies = [ + "cast", + "itertools 0.13.0", +] + [[package]] name = "crossbeam" version = "0.8.4" @@ -2707,6 +2751,16 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "papergrid" version = "0.4.0" @@ -2958,7 +3012,7 @@ dependencies = [ "aligned-vec", "backtrace", "cfg-if", - "criterion", + "criterion 0.5.1", "findshlibs", "inferno", "libc", @@ -3153,6 +3207,7 @@ name = "precompiles-hints" version = "0.16.0" dependencies = [ "anyhow", + "criterion 0.8.1", "lib-c", "precompiles-helpers", "rayon", @@ -6122,7 +6177,7 @@ name = "ziskemu" version = "0.16.0" dependencies = [ "clap", - "criterion", + "criterion 0.5.1", "data-bus", "fields", "mem-common", diff --git a/precompiles/hints/Cargo.toml b/precompiles/hints/Cargo.toml index 6961eb670..12c62ee06 100644 --- a/precompiles/hints/Cargo.toml +++ b/precompiles/hints/Cargo.toml @@ -22,4 +22,11 @@ zisk-common = { workspace = true } [[bin]] name = "hints-socket-server" -path = "src/bin/hints_socket_server.rs" \ No newline at end of file +path = "src/bin/hints_socket_server.rs" + +[dev-dependencies] +criterion = "0.8" + +[[bench]] +name = "hints_benchmarks" +harness = false From 6556d6a789b5314a309e8411a3b87af7ec5c5f96 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:17:25 +0000 Subject: [PATCH 180/782] added hints processor benchmarks --- precompiles/hints/benches/hints_benchmarks.rs | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 precompiles/hints/benches/hints_benchmarks.rs diff --git a/precompiles/hints/benches/hints_benchmarks.rs b/precompiles/hints/benches/hints_benchmarks.rs new file mode 100644 index 000000000..d84e871dd --- /dev/null +++ b/precompiles/hints/benches/hints_benchmarks.rs @@ -0,0 +1,266 @@ +use anyhow::Result; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use precompiles_hints::HintsProcessor; +use std::hint::black_box; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; +use zisk_common::io::StreamSink; +use zisk_common::{BuiltInHint, HintCode}; + +struct BenchSink { + received: Arc>>>, +} + +impl StreamSink for BenchSink { + fn submit(&self, processed: Vec) -> Result<()> { + self.received.lock().unwrap().push(processed); + Ok(()) + } +} + +fn make_header(hint_type: u32, length: u32) -> u64 { + ((hint_type as u64) << 32) | (length as u64) +} + +fn parallel_speedup_benchmark(c: &mut Criterion) { + // Define custom hints with known processing times + const FAST_HINT: u32 = 0x100; // 1ms + const MEDIUM_HINT: u32 = 0x101; // 5ms + const SLOW_HINT: u32 = 0x102; // 10ms + + // Test configuration + const NUM_FAST: usize = 100; + const NUM_MEDIUM: usize = 50; + const NUM_SLOW: usize = 20; + + let mut group = c.benchmark_group("parallel_speedup"); + group.sample_size(10); // Reduce sample size for slower benchmarks + + let thread_counts = [1, 2, 4, 8, 16]; + + for &num_threads in &thread_counts { + group.bench_with_input( + BenchmarkId::from_parameter(format!("{}_threads", num_threads)), + &num_threads, + |b, &threads| { + b.iter(|| { + let received = Arc::new(Mutex::new(Vec::new())); + let received_clone = received.clone(); + let sink = BenchSink { received: received_clone }; + + let p = HintsProcessor::builder(sink) + .num_threads(threads) + .custom_hint(FAST_HINT, |data: &[u64]| -> Result> { + thread::sleep(Duration::from_millis(1)); + Ok(vec![data[0] + 1]) + }) + .custom_hint(MEDIUM_HINT, |data: &[u64]| -> Result> { + thread::sleep(Duration::from_millis(5)); + Ok(vec![data[0] + 2]) + }) + .custom_hint(SLOW_HINT, |data: &[u64]| -> Result> { + thread::sleep(Duration::from_millis(10)); + Ok(vec![data[0] + 3]) + }) + .build() + .unwrap(); + + let mut data = Vec::new(); + let mut hint_idx = 0; + + for _ in 0..NUM_FAST { + data.push(make_header(FAST_HINT, 1)); + data.push(hint_idx); + hint_idx += 1; + } + + for _ in 0..NUM_MEDIUM { + data.push(make_header(MEDIUM_HINT, 1)); + data.push(hint_idx); + hint_idx += 1; + } + + for _ in 0..NUM_SLOW { + data.push(make_header(SLOW_HINT, 1)); + data.push(hint_idx); + hint_idx += 1; + } + + p.process_hints(black_box(&data), false).unwrap(); + p.wait_for_completion().unwrap(); + + let results = received.lock().unwrap(); + assert_eq!(results.len(), NUM_FAST + NUM_MEDIUM + NUM_SLOW); + }); + }, + ); + } + + group.finish(); +} + +fn microsecond_hints_benchmark(c: &mut Criterion) { + const ULTRA_FAST: u32 = 0x110; // 10µs + const VERY_FAST: u32 = 0x111; // 50µs + const FAST: u32 = 0x112; // 100µs + const NUM_HINTS: usize = 1000; + + let mut group = c.benchmark_group("microsecond_hints"); + group.sample_size(50); + + let test_cases = vec![ + ("ultra_fast_10us", ULTRA_FAST, 10), + ("very_fast_50us", VERY_FAST, 50), + ("fast_100us", FAST, 100), + ]; + + for (name, hint_code, micros) in test_cases { + group.bench_function(name, |b| { + b.iter(|| { + let received = Arc::new(Mutex::new(Vec::new())); + let received_clone = received.clone(); + let sink = BenchSink { received: received_clone }; + + let p = HintsProcessor::builder(sink) + .num_threads(16) + .custom_hint(hint_code, move |data: &[u64]| -> Result> { + thread::sleep(Duration::from_micros(micros as u64)); + Ok(vec![data[0] + 1]) + }) + .build() + .unwrap(); + + let mut data = Vec::new(); + for i in 0..NUM_HINTS { + data.push(make_header(hint_code, 1)); + data.push(i as u64); + } + + p.process_hints(black_box(&data), false).unwrap(); + p.wait_for_completion().unwrap(); + + let results = received.lock().unwrap(); + assert_eq!(results.len(), NUM_HINTS); + }); + }); + } + + group.finish(); +} + +fn workload_patterns_benchmark(c: &mut Criterion) { + const VERY_FAST: u32 = 0x100; // 0.5ms + const FAST: u32 = 0x101; // 2ms + const MEDIUM: u32 = 0x102; // 5ms + const SLOW: u32 = 0x103; // 10ms + const VERY_SLOW: u32 = 0x104; // 20ms + + let mut group = c.benchmark_group("workload_patterns"); + group.sample_size(10); + + let patterns = vec![ + ("uniform_fast", vec![(FAST, 100)]), + ("uniform_slow", vec![(SLOW, 50)]), + ("mixed_balanced", vec![(FAST, 40), (MEDIUM, 20), (SLOW, 10)]), + ("skewed_fast", vec![(VERY_FAST, 80), (SLOW, 10), (VERY_SLOW, 10)]), + ("heavy_tail", vec![(FAST, 50), (VERY_SLOW, 5)]), + ]; + + for (name, hints) in patterns { + group.bench_function(name, |b| { + b.iter(|| { + let received = Arc::new(Mutex::new(Vec::new())); + let received_clone = received.clone(); + let sink = BenchSink { received: received_clone }; + + let p = HintsProcessor::builder(sink) + .num_threads(8) + .custom_hint(VERY_FAST, |data: &[u64]| -> Result> { + thread::sleep(Duration::from_micros(500)); + Ok(vec![data[0] + 1]) + }) + .custom_hint(FAST, |data: &[u64]| -> Result> { + thread::sleep(Duration::from_millis(2)); + Ok(vec![data[0] + 1]) + }) + .custom_hint(MEDIUM, |data: &[u64]| -> Result> { + thread::sleep(Duration::from_millis(5)); + Ok(vec![data[0] + 1]) + }) + .custom_hint(SLOW, |data: &[u64]| -> Result> { + thread::sleep(Duration::from_millis(10)); + Ok(vec![data[0] + 1]) + }) + .custom_hint(VERY_SLOW, |data: &[u64]| -> Result> { + thread::sleep(Duration::from_millis(20)); + Ok(vec![data[0] + 1]) + }) + .build() + .unwrap(); + + let mut data = Vec::new(); + let mut idx = 0; + for (hint_code, count) in &hints { + for _ in 0..*count { + data.push(make_header(*hint_code, 1)); + data.push(idx); + idx += 1; + } + } + + p.process_hints(black_box(&data), false).unwrap(); + p.wait_for_completion().unwrap(); + + let total_hints: usize = hints.iter().map(|(_, count)| count).sum(); + let results = received.lock().unwrap(); + assert_eq!(results.len(), total_hints); + }); + }); + } + + group.finish(); +} + +fn noop_throughput_benchmark(c: &mut Criterion) { + struct NullSink; + + impl StreamSink for NullSink { + fn submit(&self, _processed: Vec) -> Result<()> { + Ok(()) + } + } + + let mut group = c.benchmark_group("noop_throughput"); + group.sample_size(20); + + let hint_counts = [1000, 10000, 100000]; + + for &count in &hint_counts { + group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, &num_hints| { + b.iter(|| { + let p = HintsProcessor::builder(NullSink).num_threads(32).build().unwrap(); + + let mut data = Vec::with_capacity(num_hints * 2); + for i in 0..num_hints { + data.push(make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1)); + data.push(i as u64); + } + + p.process_hints(black_box(&data), false).unwrap(); + p.wait_for_completion().unwrap(); + }); + }); + } + + group.finish(); +} + +criterion_group!( + benches, + parallel_speedup_benchmark, + microsecond_hints_benchmark, + workload_patterns_benchmark, + noop_throughput_benchmark +); +criterion_main!(benches); From a83808e0c18f936ad90039558aaf7ca79783476d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:10:07 +0000 Subject: [PATCH 181/782] Fixing bugs in new sha --- core/src/helpers.rs | 1 - pil/src/pil_helpers/traces.rs | 2 +- precompiles/sha256f/pil/sha256f.pil | 34 +++++++++++---- precompiles/sha256f/src/sha256f.rs | 65 ++++++++++++++++------------- 4 files changed, 63 insertions(+), 39 deletions(-) diff --git a/core/src/helpers.rs b/core/src/helpers.rs index cc706efb9..2fe1f4fb5 100644 --- a/core/src/helpers.rs +++ b/core/src/helpers.rs @@ -7,7 +7,6 @@ use sha2::digest::generic_array::{typenum::U64, GenericArray}; #[allow(deprecated)] pub fn sha256f(state: &mut [u64; 4], input: &[u64; 8]) { - // Convert both the state and the input to appropriate types let state_u32: &mut [u32; 8] = unsafe { &mut *(state.as_mut_ptr() as *mut [u32; 8]) }; let input_u8: &[GenericArray; 1] = unsafe { &*(input.as_ptr() as *const [GenericArray; 1]) }; diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 35243ece3..3710d19d9 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "b6a95d37ee456885c594ed4f5dcabe263897c7117efc5992e6f54e42c61217eb"; +pub const PILOUT_HASH: &str = "d2072d6b50a3327861b544ec82fe2e3ba83386f69bc0b044d7b227cac13bdcdf"; pub const MERKLE_TREE_ARITY: u64 = 4; diff --git a/precompiles/sha256f/pil/sha256f.pil b/precompiles/sha256f/pil/sha256f.pil index ff1660a55..3aab7e46b 100644 --- a/precompiles/sha256f/pil/sha256f.pil +++ b/precompiles/sha256f/pil/sha256f.pil @@ -201,18 +201,21 @@ airtemplate Sha256f(const int N = 2**22, const int operation_bus_id = OPERATION_ clock_eq(step_addr, ADDR_STATE, ADDR_IND_0) === 0; clock_eq(step_addr, ADDR_INPUT, ADDR_IND_1) === 0; + // Swap w bytes for memory consistency + const expr w_spacked = swap_bytes_and_pack(w); + expr mem_value[2]; - mem_value[0] = CLK[0] * a_packed'2 + CLK[1] * 'a_packed + CLK[2] * e_packed + CLK[3] * 3'e_packed + - CLK[4] * w_packed' + CLK[5] * w_packed'2 + CLK[6] * w_packed'3 + CLK[7] * w_packed'4 + - CLK[8] * w_packed'5 + CLK[9] * w_packed'6 + CLK[10] * w_packed'7 + CLK[11] * w_packed'8 + - CLK[12] * a_packed'58 + CLK[13] * a_packed'55 + CLK[14] * e_packed'56 + CLK[15] * e_packed'53 + + mem_value[0] = CLK[0] * a_packed'3 + CLK[1] * a_packed + CLK[2] * e_packed' + CLK[3] * 2'e_packed + + CLK[4] * w_spacked + CLK[5] * w_spacked' + CLK[6] * w_spacked'2 + CLK[7] * w_spacked'3 + + CLK[8] * w_spacked'4 + CLK[9] * w_spacked'5 + CLK[10] * w_spacked'6 + CLK[11] * w_spacked'7 + + CLK[12] * a_packed'59 + CLK[13] * a_packed'56 + CLK[14] * e_packed'57 + CLK[15] * e_packed'54 + clock_map(step_addr, ADDR_IND_0, 16) + clock_map(step_addr, ADDR_IND_1, 17); - mem_value[1] = CLK[0] * a_packed'3 + CLK[1] * a_packed + CLK[2] * e_packed' + CLK[3] * 2'e_packed + - CLK[4] * w_packed + CLK[5] * w_packed' + CLK[6] * w_packed'2 + CLK[7] * w_packed'3 + - CLK[8] * w_packed'4 + CLK[9] * w_packed'5 + CLK[10] * w_packed'6 + CLK[11] * w_packed'7 + - CLK[12] * a_packed'59 + CLK[13] * a_packed'56 + CLK[14] * e_packed'57 + CLK[15] * e_packed'54; - // high bits of ADDR_IND_0 and ADDR_IND_ are 0 + mem_value[1] = CLK[0] * a_packed'2 + CLK[1] * 'a_packed + CLK[2] * e_packed + CLK[3] * 3'e_packed + + CLK[4] * w_spacked' + CLK[5] * w_spacked'2 + CLK[6] * w_spacked'3 + CLK[7] * w_spacked'4 + + CLK[8] * w_spacked'5 + CLK[9] * w_spacked'6 + CLK[10] * w_spacked'7 + CLK[11] * w_spacked'8 + + CLK[12] * a_packed'58 + CLK[13] * a_packed'55 + CLK[14] * e_packed'56 + CLK[15] * e_packed'53; + // addresses are 32-bit values const expr mem_addr = clock_map(step_addr, ADDR_STATE, start: 0, end: 3, delta: 8) + clock_map(step_addr, ADDR_INPUT, start: 4, end: 11, delta: 8) + @@ -257,6 +260,19 @@ airtemplate Sha256f(const int N = 2**22, const int operation_bus_id = OPERATION_ return packed; } + function swap_bytes_and_pack(const expr a[]): expr { + const int len = length(a); + expr result = 0; + for (int i = 0; i < len; i++) { + int byte_idx = i / 8; + int bit_in_byte = i % 8; + int swapped_byte = 3 - byte_idx; + int swapped_idx = swapped_byte * 8 + bit_in_byte; + result += a[swapped_idx] * 2**i; + } + return result; + } + // Given an old w, computes the new w for the next round function compute_w(const expr old_w[][]): expr { expr [old_w2, old_w7, old_w15, old_w16] = [old_w[0], old_w[1], old_w[2], old_w[3]]; diff --git a/precompiles/sha256f/src/sha256f.rs b/precompiles/sha256f/src/sha256f.rs index 2141ca0cb..9891e039d 100644 --- a/precompiles/sha256f/src/sha256f.rs +++ b/precompiles/sha256f/src/sha256f.rs @@ -99,19 +99,22 @@ impl Sha256fSM { let mut prev_state = [0u32; 8]; for i in 0..CLOCKS_LOAD_STATE { let word = state[i]; - let word_high = (word >> 32) as u32; - let word_low = (word & 0xFFFF_FFFF) as u32; + + // First word is the low significant 32 bits of word + // Second word is the high significant 32 bits of word + let word_first = (word & 0xFFFF_FFFF) as u32; + let word_second = (word >> 32) as u32; // Store the state as u32 for further processing - prev_state[2 * i] = word_high; - prev_state[2 * i + 1] = word_low; + prev_state[2 * i] = word_first; + prev_state[2 * i + 1] = word_second; let mut row = if i == 1 || i == 3 { offset + 1 } else { offset + 3 }; // Locate the state bits in the trace let is_a = i < 2; for j in 0..32 { - let bit = ((word_high >> j) & 1) != 0; + let bit = ((word_first >> j) & 1) != 0; if is_a { trace[row].set_a(j, bit); } else { @@ -120,7 +123,7 @@ impl Sha256fSM { } row -= 1; for j in 0..32 { - let bit = ((word_low >> j) & 1) != 0; + let bit = ((word_second >> j) & 1) != 0; if is_a { trace[row].set_a(j, bit); } else { @@ -133,10 +136,13 @@ impl Sha256fSM { // Compute the load input stage let mut w = [0u32; 16]; for i in 0..CLOCKS_LOAD_INPUT { - let word = input[i / 2]; + // Input is received as little-endian u64 words, so we need to swap bytes + let word = input[i / 2].swap_bytes(); + let word_low = (word & 0xFFFF_FFFF) as u32; + let word_high = (word >> 32) as u32; // Store the input as u32 for further processing - w[i] = if i % 2 == 0 { (word >> 32) as u32 } else { (word & 0xFFFF_FFFF) as u32 }; + w[i] = if i % 2 == 0 { word_high } else { word_low }; // Compute the a and e values for the current input let [old_a, old_b, old_c, old_d, old_e, old_f, old_g, old_h] = prev_state; @@ -232,32 +238,36 @@ impl Sha256fSM { for i in 0..CLOCKS_WRITE_STATE { let prev = state[i]; - let prev_high = prev >> 32; - let prev_low = prev & 0xFFFF_FFFF; - let curr_high = (prev_state[2 * i]) as u64; - let curr_low = (prev_state[2 * i + 1]) as u64; + // First word is the low significant 32 bits of word + // Second word is the high significant 32 bits of word + let prev_first = prev & 0xFFFF_FFFF; + let prev_second = prev >> 32; + + let curr_first = (prev_state[2 * i]) as u64; + let curr_second = (prev_state[2 * i + 1]) as u64; - let new_high = curr_high + prev_high; - let new_low = curr_low + prev_low; - let (new_high_carry, new_high) = - ((new_high >> 32) as u8, (new_high & 0xFFFF_FFFF) as u32); - let (new_low_carry, new_low) = ((new_low >> 32) as u8, (new_low & 0xFFFF_FFFF) as u32); + let new_first = curr_first + prev_first; + let new_second = curr_second + prev_second; + let (new_first_carry, new_first) = + ((new_first >> 32) as u8, (new_first & 0xFFFF_FFFF) as u32); + let (new_second_carry, new_second) = + ((new_second >> 32) as u8, (new_second & 0xFFFF_FFFF) as u32); let mut row = if i == 1 || i == 3 { offset + 1 } else { offset + 3 }; // Locate the state bits in the trace let is_a = i < 2; if is_a { - trace[row].set_new_a_carry_bits(new_high_carry); - a_range_checks[new_high_carry as usize] += 1; + trace[row].set_new_a_carry_bits(new_first_carry); + a_range_checks[new_first_carry as usize] += 1; } else { - trace[row].set_new_e_carry_bits(new_high_carry); - e_range_checks[new_high_carry as usize] += 1; + trace[row].set_new_e_carry_bits(new_first_carry); + e_range_checks[new_first_carry as usize] += 1; } for j in 0..32 { - let bit = ((new_high >> j) & 1) != 0; + let bit = ((new_first >> j) & 1) != 0; if is_a { trace[row].set_a(j, bit); } else { @@ -267,15 +277,15 @@ impl Sha256fSM { row -= 1; if is_a { - trace[row].set_new_a_carry_bits(new_low_carry); - a_range_checks[new_low_carry as usize] += 1; + trace[row].set_new_a_carry_bits(new_second_carry); + a_range_checks[new_second_carry as usize] += 1; } else { - trace[row].set_new_e_carry_bits(new_low_carry); - e_range_checks[new_low_carry as usize] += 1; + trace[row].set_new_e_carry_bits(new_second_carry); + e_range_checks[new_second_carry as usize] += 1; } for j in 0..32 { - let bit = ((new_low >> j) & 1) != 0; + let bit = ((new_second >> j) & 1) != 0; if is_a { trace[row].set_a(j, bit); } else { @@ -300,7 +310,6 @@ impl Sha256fSM { let a = (t1 as u64) + (t2 as u64); let e = (old_d as u64) + (t1 as u64); (a, e) - // (s0 as u64, s1 as u64) } fn compute_w(old_w2: u32, old_w7: u32, old_w15: u32, old_w16: u32) -> u64 { From 5f189de2a570ed84f114323d46aa3ff5bbfb3e96 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 3 Jan 2026 09:44:35 +0000 Subject: [PATCH 182/782] added modexp hint to hints_processor --- common/src/hints.rs | 6 +++ precompiles/hints/src/hints_processor.rs | 10 +++- ziskos-hints/src/handlers/mod.rs | 2 + ziskos-hints/src/handlers/modexp.rs | 64 ++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 ziskos-hints/src/handlers/modexp.rs diff --git a/common/src/hints.rs b/common/src/hints.rs index 4b730a30c..14a16be74 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -65,6 +65,7 @@ const HINT_DIVREM256: u32 = 0x09; const HINT_WPOW256: u32 = 0x0A; const HINT_OMUL256: u32 = 0x0B; const HINT_WMUL256: u32 = 0x0C; +const HINT_MODEXP: u32 = 0x0D; /// Control code variants for stream control. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -130,6 +131,8 @@ pub enum BuiltInHint { OMul256 = HINT_OMUL256, /// Wrapping multiplication of 256-bit integers hint type. WMul256 = HINT_WMUL256, + /// Modular exponentiation hint type. + ModExp = HINT_MODEXP, } impl Display for BuiltInHint { @@ -144,6 +147,7 @@ impl Display for BuiltInHint { BuiltInHint::WPow256 => "WPOW256", BuiltInHint::OMul256 => "OMUL256", BuiltInHint::WMul256 => "WMUL256", + BuiltInHint::ModExp => "MODEXP", }; write!(f, "{} ({:#x})", name, *self as u32) } @@ -163,6 +167,7 @@ impl TryFrom for BuiltInHint { HINT_WPOW256 => Ok(Self::WPow256), HINT_OMUL256 => Ok(Self::OMul256), HINT_WMUL256 => Ok(Self::WMul256), + HINT_MODEXP => Ok(Self::ModExp), _ => Err(anyhow::anyhow!("Invalid built-in hint code: {:#x}", value)), } } @@ -225,6 +230,7 @@ impl HintCode { HintCode::BuiltIn(BuiltInHint::WPow256) => 0x0A, HintCode::BuiltIn(BuiltInHint::OMul256) => 0x0B, HintCode::BuiltIn(BuiltInHint::WMul256) => 0x0C, + HintCode::BuiltIn(BuiltInHint::ModExp) => 0x0D, HintCode::Custom(code) => code, } } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index c68e633c6..38bae0c79 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -244,8 +244,7 @@ impl HintsProcessor { stats.lock().unwrap().entry(hint.hint_code).and_modify(|c| *c += 1).or_insert(1); } - // Check if this is a control code or data hint type - + // Check if this is a control code match hint.hint_code { HintCode::Ctrl(CtrlHint::Start) => { // CTRL_START must be the first message of the first batch @@ -554,6 +553,7 @@ impl HintsProcessor { HintCode::BuiltIn(BuiltInHint::WPow256) => Self::process_hint_wpow256(&hint), HintCode::BuiltIn(BuiltInHint::OMul256) => Self::process_hint_omul256(&hint), HintCode::BuiltIn(BuiltInHint::WMul256) => Self::process_hint_wmul256(&hint), + HintCode::BuiltIn(BuiltInHint::ModExp) => Self::process_hint_modexp(&hint), // Custom hints HintCode::Custom(code) => { @@ -611,6 +611,12 @@ impl HintsProcessor { fn process_hint_wmul256(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::wmul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + + /// Processes a [`MODEXP`] hint. + #[inline] + fn process_hint_modexp(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::modexp_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } } impl Drop for HintsProcessor { diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index ec923ee18..7cbe76f50 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -1,7 +1,9 @@ mod bigint256; +mod modexp; mod secp256k1; pub use bigint256::*; +pub use modexp::*; pub use secp256k1::*; /// Macro to generate size, offset, and expected length constants for hint data fields. diff --git a/ziskos-hints/src/handlers/modexp.rs b/ziskos-hints/src/handlers/modexp.rs new file mode 100644 index 000000000..1ef57ebae --- /dev/null +++ b/ziskos-hints/src/handlers/modexp.rs @@ -0,0 +1,64 @@ +use crate::zisklib; + +// Processes a MODEXP hint. +#[inline] +pub fn modexp_hint(data: &[u64]) -> Result, String> { + if data.is_empty() { + return Err("MODEXP hint data is empty".to_string()); + } + + // Parse base + let base_len = data[0] as usize; + let base_start = 1; + let base_end = base_start + base_len; + + if data.len() < base_end + 1 { + return Err(format!( + "MODEXP hint data too short for base (expected at least {} elements)", + base_end + 1 + )); + } + let base = &data[base_start..base_end]; + + // Parse exponent + let exp_len = data[base_end] as usize; + let exp_start = base_end + 1; + let exp_end = exp_start + exp_len; + + if data.len() < exp_end + 1 { + return Err(format!( + "MODEXP hint data too short for exponent (expected at least {} elements)", + exp_end + 1 + )); + } + let exp = &data[exp_start..exp_end]; + + // Parse modulus + let modulus_len = data[exp_end] as usize; + let modulus_start = exp_end + 1; + let modulus_end = modulus_start + modulus_len; + + if data.len() != modulus_end { + return Err(format!( + "MODEXP hint data length mismatch (expected {}, got {})", + modulus_end, + data.len() + )); + } + let modulus = &data[modulus_start..modulus_end]; + + // println!( + // "\n\ + // MODEXP data: {:?}\n\ + // MODEXP base: length={} data={:?}\n\ + // MODEXP exp : length={} data={:?}\n\ + // MODEXP mod : length={} data={:?}", + // data, base_len, base, exp_len, exp, modulus_len, modulus + // ); + + let mut processed_hints = Vec::new(); + + zisklib::modexp_u64(base, exp, modulus, &mut processed_hints); + + Ok(processed_hints) +} From c93fc68ba3eeb38a4ef726016b16eee86b461742 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 3 Jan 2026 09:47:46 +0000 Subject: [PATCH 183/782] remove debug print --- ziskos-hints/src/handlers/modexp.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/ziskos-hints/src/handlers/modexp.rs b/ziskos-hints/src/handlers/modexp.rs index 1ef57ebae..815440dc1 100644 --- a/ziskos-hints/src/handlers/modexp.rs +++ b/ziskos-hints/src/handlers/modexp.rs @@ -47,15 +47,6 @@ pub fn modexp_hint(data: &[u64]) -> Result, String> { } let modulus = &data[modulus_start..modulus_end]; - // println!( - // "\n\ - // MODEXP data: {:?}\n\ - // MODEXP base: length={} data={:?}\n\ - // MODEXP exp : length={} data={:?}\n\ - // MODEXP mod : length={} data={:?}", - // data, base_len, base, exp_len, exp, modulus_len, modulus - // ); - let mut processed_hints = Vec::new(); zisklib::modexp_u64(base, exp, modulus, &mut processed_hints); From 7250535c041b6388ffbf88767836aef3df52448f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Sat, 3 Jan 2026 10:19:21 +0000 Subject: [PATCH 184/782] Fixed array arith bugs --- ziskos/entrypoint/src/zisklib/lib/array_lib/div_long.rs | 6 +++--- ziskos/entrypoint/src/zisklib/lib/array_lib/div_short.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/div_long.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/div_long.rs index b29b59b65..22c142842 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/div_long.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/div_long.rs @@ -66,7 +66,7 @@ pub fn div_long(a: &[U256], b: &[U256]) -> (Vec, Vec) { assert!(!quo[len_quo - 1].is_zero(), "Quotient must not have leading zeros"); // Multiply the quotient by b - let mut q_b = vec![U256::ZERO; len_a + 1]; // The +1 is because mul_long and add_agtb are a general purpose functions + let mut q_b = vec![U256::ZERO; len_a + 1]; // The +1 is because mul_long is a general purpose function let q_b_len = mul_long(quo, b, &mut q_b); // Check 1 <= len(r) @@ -75,13 +75,13 @@ pub fn div_long(a: &[U256], b: &[U256]) -> (Vec, Vec) { if rem[len_rem - 1].is_zero() { // If the remainder is zero, then a must be equal to q·b - assert!(U256::eq_slices(a, &q_b), "Remainder is zero, but a != q·b"); + assert!(U256::eq_slices(a, &q_b[..q_b_len]), "Remainder is zero, but a != q·b"); } else { // If the remainder is non-zero, then we should check that a must be equal to q·b + r and r < b assert!(U256::lt_slices(rem, b), "Remainder must be less than divisor"); - let mut q_b_r = vec![U256::ZERO; len_a + 1]; // The +1 is because mul_long and add_agtb are a general purpose functions + let mut q_b_r = vec![U256::ZERO; len_a + 1]; // The +1 is because add_agtb is a general purpose function let q_b_r_len = add_agtb(&q_b[..q_b_len], rem, &mut q_b_r); assert!(U256::eq_slices(a, &q_b_r[..q_b_r_len]), "a != q·b + r"); } diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/div_short.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/div_short.rs index 6080484c7..a2dcf2bcc 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/div_short.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/div_short.rs @@ -52,7 +52,7 @@ pub fn div_short(a: &[U256], b: &U256) -> (Vec, U256) { assert!(!quo[len_quo - 1].is_zero(), "Quotient must not have leading zeros"); // Multiply the quotient by b - let mut q_b = [U256::ZERO; 2]; + let mut q_b = vec![U256::ZERO; len_a + 1]; // The +1 is because mul_short is a general purpose function let q_b_len = mul_short(quo, b, &mut q_b); if rem.is_zero() { @@ -62,7 +62,7 @@ pub fn div_short(a: &[U256], b: &U256) -> (Vec, U256) { // If the remainder is non-zero, then we should check that a must be equal to q·b + r and r < b assert!(rem.lt(b), "Remainder must be less than divisor"); - let mut q_b_r = [U256::ZERO; 2]; + let mut q_b_r = vec![U256::ZERO; len_a + 1]; // The +1 is because add_short is a general purpose function let q_b_r_len = add_short(&q_b[..q_b_len], &rem, &mut q_b_r); assert!(U256::eq_slices(a, &q_b_r[..q_b_r_len]), "a != q·b + r"); } From cbbff1f8f65d507e59c49c2c99d6bc20d875769f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 5 Jan 2026 06:33:49 +0000 Subject: [PATCH 185/782] modified ecrecover hints --- ziskos-hints/Cargo.toml | 3 ++ ziskos-hints/src/handlers/secp256k1.rs | 60 +++++++++++++++++++++----- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index 6e1f6ee2c..5e9e093ad 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -33,6 +33,9 @@ serde = { workspace = true, features = ["derive"] } bincode = "2.0" paste = "1.0" +elliptic-curve = "0.13.8" +k256 = "0.13.4" + [features] default = ["hints"] hints = [] diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index ee1169c9a..d96ba5e9d 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -1,3 +1,7 @@ +use elliptic_curve::FieldBytesEncoding; +use k256::ecdsa::Signature; +use k256::U256; + use crate::handlers::validate_hint_length; use crate::hint_fields; use crate::zisklib; @@ -6,7 +10,7 @@ use crate::zisklib; /// /// # Arguments /// -/// * `data` - The hint data containing pk(8) + z(4) + r(4) + s(4) = 20 u64 values +/// * `data` - The hint data containing pk(33 bytes) + z(32 bytes) + sig(64 bytes) = 129 bytes /// /// # Returns /// @@ -14,23 +18,59 @@ use crate::zisklib; /// * `Err` - If the data length is invalid #[inline] pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { - hint_fields![PK: 8, Z: 4, R: 4, S: 4]; + hint_fields![PK: 4, Y_IS_ODD:1, Z: 4, SIG: 8]; - validate_hint_length(data, EXPECTED_LEN, "ECRECOVER")?; + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_ECDSA_VERIFY")?; - let mut processed_hints = Vec::new(); + let pk = &data[PK_OFFSET..Y_IS_ODD_OFFSET]; + let y_is_odd = (data[Y_IS_ODD_OFFSET] >> 56) as u8; + + let mut hints = Vec::new(); + + let mut out: [u64; 8] = [0; 8]; + unsafe { + zisklib::secp256k1_decompress_c( + &pk[0] as *const u64 as *const u8, + y_is_odd, + &mut out[0], + &mut hints, + ); + } + + // Convert u64 slice to byte slice + // let byte_data = unsafe { slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; + + // Extract z (32 bytes), and sig (64 bytes) + let z = &data[Z_OFFSET..SIG_OFFSET]; + let z_bytes: &[u8; 32] = unsafe { &*(z.as_ptr() as *const [u8; 32]) }; + + let z_dec: U256 = U256::decode_field_bytes(z_bytes.into()); + let z_words = z_dec.to_words(); + + // Parse signature and decode r and s + let sig = &data[SIG_OFFSET..]; + let sig_bytes: &[u8; 64] = unsafe { &*(sig.as_ptr() as *const [u8; 64]) }; + let sig = Signature::try_from(sig_bytes.as_slice()) + .map_err(|e| format!("Failed to parse signature: {}", e))?; + + // Extract r and s as Scalars and convert to U256 + let (r_scalar, s_scalar) = sig.split_scalars(); + let r: U256 = U256::decode_field_bytes(&r_scalar.to_bytes()); + let s: U256 = U256::decode_field_bytes(&s_scalar.to_bytes()); + let r_words = r.to_words(); + let s_words = s.to_words(); unsafe { zisklib::secp256k1_ecdsa_verify_c( - &data[PK_OFFSET], - &data[Z_OFFSET], - &data[R_OFFSET], - &data[S_OFFSET], - &mut processed_hints, + &pk[0], + &z_words[0], + &r_words[0], + &s_words[0], + &mut hints, ); } - Ok(processed_hints) + Ok(hints) } // Processes a SECP256K1_TO_AFFINE hint. From 11647083f363703b638e225c4d040a0725171a07 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:23:03 +0000 Subject: [PATCH 186/782] added bn254 hints flow --- common/src/hints.rs | 96 ++++++++++++++++++++---- precompiles/hints/src/hints_processor.rs | 63 ++++++++++++++++ ziskos-hints/src/handlers/bn254.rs | 47 ++++++++++++ ziskos-hints/src/handlers/mod.rs | 2 + 4 files changed, 192 insertions(+), 16 deletions(-) create mode 100644 ziskos-hints/src/handlers/bn254.rs diff --git a/common/src/hints.rs b/common/src/hints.rs index 14a16be74..d00a50381 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -49,15 +49,19 @@ use std::fmt::Display; use anyhow::Result; -// Control code constants +// === CONTROL CODES === const CTRL_START: u32 = 0x00; const CTRL_END: u32 = 0x01; const CTRL_CANCEL: u32 = 0x02; const CTRL_ERROR: u32 = 0x03; -// Built-in hint code constants +// === BUILT-IN HINT CODES === +// Noop hint code const HINT_NOOP: u32 = 0x04; +// Ecrecover precompile hint code const HINT_ECRECOVER: u32 = 0x05; + +// Big integer arithmetic hint codes const HINT_REDMOD256: u32 = 0x06; const HINT_ADDMOD256: u32 = 0x07; const HINT_MULMOD256: u32 = 0x08; @@ -65,8 +69,20 @@ const HINT_DIVREM256: u32 = 0x09; const HINT_WPOW256: u32 = 0x0A; const HINT_OMUL256: u32 = 0x0B; const HINT_WMUL256: u32 = 0x0C; + +// Modular exponentiation hint code const HINT_MODEXP: u32 = 0x0D; +// BN254 precompile hint codes +const HINT_TO_AFFINE_BN254: u32 = 0x0E; +const HINT_IS_ON_CURVE_BN254: u32 = 0x0F; +const HINT_ADD_BN254: u32 = 0x10; +const HINT_MUL_BN254: u32 = 0x11; +const HINT_TO_AFFINE_TWIST_BN254: u32 = 0x12; +const HINT_IS_ON_CURVE_TWIST_BN254: u32 = 0x13; +const HINT_IS_ON_SUBGROUP_TWIST_BN254: u32 = 0x14; +const HINT_PAIRING_BATCH_BN254: u32 = 0x15; + /// Control code variants for stream control. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u32)] @@ -115,8 +131,11 @@ pub enum BuiltInHint { /// When a hint has this type, the processor simply passes through the data /// without any additional computation. Noop = HINT_NOOP, + /// Ecrecover precompile hint type. EcRecover = HINT_ECRECOVER, + + // Big Integer Arithmetic Hints /// Modular reduction of a 256-bit integer hint type. RedMod256 = HINT_REDMOD256, /// Modular addition of 256-bit integers hint type. @@ -131,8 +150,27 @@ pub enum BuiltInHint { OMul256 = HINT_OMUL256, /// Wrapping multiplication of 256-bit integers hint type. WMul256 = HINT_WMUL256, + /// Modular exponentiation hint type. ModExp = HINT_MODEXP, + + // BN254 Precompile Hints + /// Convert to affine coordinates hint type for BN254 curve. + ToAffineBn254 = HINT_TO_AFFINE_BN254, + /// Check if point is on curve hint type for BN254 curve. + IsOnCurveBn254 = HINT_IS_ON_CURVE_BN254, + /// Point addition hint type for BN254 curve. + AddBn254 = HINT_ADD_BN254, + /// Scalar multiplication hint type for BN254 curve. + MulBn254 = HINT_MUL_BN254, + /// Convert to affine coordinates hint type for BN254 twist. + ToAffineTwistBn254 = HINT_TO_AFFINE_TWIST_BN254, + /// Check if point is on curve hint type for BN254 twist. + IsOnCurveTwistBn254 = HINT_IS_ON_CURVE_TWIST_BN254, + /// Check if point is in subgroup hint type for BN254 twist. + IsOnSubgroupTwistBn254 = HINT_IS_ON_SUBGROUP_TWIST_BN254, + /// Pairing batch computation hint type for BN254 curve. + PairingBatchBn254 = HINT_PAIRING_BATCH_BN254, } impl Display for BuiltInHint { @@ -148,6 +186,14 @@ impl Display for BuiltInHint { BuiltInHint::OMul256 => "OMUL256", BuiltInHint::WMul256 => "WMUL256", BuiltInHint::ModExp => "MODEXP", + BuiltInHint::ToAffineBn254 => "TO_AFFINE_BN254", + BuiltInHint::IsOnCurveBn254 => "IS_ON_CURVE_BN254", + BuiltInHint::AddBn254 => "ADD_BN254", + BuiltInHint::MulBn254 => "MUL_BN254", + BuiltInHint::ToAffineTwistBn254 => "TO_AFFINE_TWIST_BN254", + BuiltInHint::IsOnCurveTwistBn254 => "IS_ON_CURVE_TWIST_BN254", + BuiltInHint::IsOnSubgroupTwistBn254 => "IS_ON_SUBGROUP_TWIST_BN254", + BuiltInHint::PairingBatchBn254 => "PAIRING_BATCH_BN254", }; write!(f, "{} ({:#x})", name, *self as u32) } @@ -168,6 +214,14 @@ impl TryFrom for BuiltInHint { HINT_OMUL256 => Ok(Self::OMul256), HINT_WMUL256 => Ok(Self::WMul256), HINT_MODEXP => Ok(Self::ModExp), + HINT_TO_AFFINE_BN254 => Ok(Self::ToAffineBn254), + HINT_IS_ON_CURVE_BN254 => Ok(Self::IsOnCurveBn254), + HINT_ADD_BN254 => Ok(Self::AddBn254), + HINT_MUL_BN254 => Ok(Self::MulBn254), + HINT_TO_AFFINE_TWIST_BN254 => Ok(Self::ToAffineTwistBn254), + HINT_IS_ON_CURVE_TWIST_BN254 => Ok(Self::IsOnCurveTwistBn254), + HINT_IS_ON_SUBGROUP_TWIST_BN254 => Ok(Self::IsOnSubgroupTwistBn254), + HINT_PAIRING_BATCH_BN254 => Ok(Self::PairingBatchBn254), _ => Err(anyhow::anyhow!("Invalid built-in hint code: {:#x}", value)), } } @@ -217,20 +271,30 @@ impl HintCode { #[inline] pub const fn to_u32(self) -> u32 { match self { - HintCode::Ctrl(CtrlHint::Start) => 0x00, - HintCode::Ctrl(CtrlHint::End) => 0x01, - HintCode::Ctrl(CtrlHint::Cancel) => 0x02, - HintCode::Ctrl(CtrlHint::Error) => 0x03, - HintCode::BuiltIn(BuiltInHint::Noop) => 0x04, - HintCode::BuiltIn(BuiltInHint::EcRecover) => 0x05, - HintCode::BuiltIn(BuiltInHint::RedMod256) => 0x06, - HintCode::BuiltIn(BuiltInHint::AddMod256) => 0x07, - HintCode::BuiltIn(BuiltInHint::MulMod256) => 0x08, - HintCode::BuiltIn(BuiltInHint::DivRem256) => 0x09, - HintCode::BuiltIn(BuiltInHint::WPow256) => 0x0A, - HintCode::BuiltIn(BuiltInHint::OMul256) => 0x0B, - HintCode::BuiltIn(BuiltInHint::WMul256) => 0x0C, - HintCode::BuiltIn(BuiltInHint::ModExp) => 0x0D, + HintCode::Ctrl(CtrlHint::Start) => CTRL_START, + HintCode::Ctrl(CtrlHint::End) => CTRL_END, + HintCode::Ctrl(CtrlHint::Cancel) => CTRL_CANCEL, + HintCode::Ctrl(CtrlHint::Error) => CTRL_ERROR, + HintCode::BuiltIn(BuiltInHint::Noop) => HINT_NOOP, + HintCode::BuiltIn(BuiltInHint::EcRecover) => HINT_ECRECOVER, + HintCode::BuiltIn(BuiltInHint::RedMod256) => HINT_REDMOD256, + HintCode::BuiltIn(BuiltInHint::AddMod256) => HINT_ADDMOD256, + HintCode::BuiltIn(BuiltInHint::MulMod256) => HINT_MULMOD256, + HintCode::BuiltIn(BuiltInHint::DivRem256) => HINT_DIVREM256, + HintCode::BuiltIn(BuiltInHint::WPow256) => HINT_WPOW256, + HintCode::BuiltIn(BuiltInHint::OMul256) => HINT_OMUL256, + HintCode::BuiltIn(BuiltInHint::WMul256) => HINT_WMUL256, + HintCode::BuiltIn(BuiltInHint::ModExp) => HINT_MODEXP, + HintCode::BuiltIn(BuiltInHint::ToAffineBn254) => HINT_TO_AFFINE_BN254, + HintCode::BuiltIn(BuiltInHint::IsOnCurveBn254) => HINT_IS_ON_CURVE_BN254, + HintCode::BuiltIn(BuiltInHint::AddBn254) => HINT_ADD_BN254, + HintCode::BuiltIn(BuiltInHint::MulBn254) => HINT_MUL_BN254, + HintCode::BuiltIn(BuiltInHint::ToAffineTwistBn254) => HINT_TO_AFFINE_TWIST_BN254, + HintCode::BuiltIn(BuiltInHint::IsOnCurveTwistBn254) => HINT_IS_ON_CURVE_TWIST_BN254, + HintCode::BuiltIn(BuiltInHint::IsOnSubgroupTwistBn254) => { + HINT_IS_ON_SUBGROUP_TWIST_BN254 + } + HintCode::BuiltIn(BuiltInHint::PairingBatchBn254) => HINT_PAIRING_BATCH_BN254, HintCode::Custom(code) => code, } } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 38bae0c79..c6b956422 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -554,6 +554,26 @@ impl HintsProcessor { HintCode::BuiltIn(BuiltInHint::OMul256) => Self::process_hint_omul256(&hint), HintCode::BuiltIn(BuiltInHint::WMul256) => Self::process_hint_wmul256(&hint), HintCode::BuiltIn(BuiltInHint::ModExp) => Self::process_hint_modexp(&hint), + HintCode::BuiltIn(BuiltInHint::ToAffineBn254) => { + Self::process_hint_to_affine_bn254(&hint) + } + HintCode::BuiltIn(BuiltInHint::IsOnCurveBn254) => { + Self::process_hint_is_on_curve_bn254(&hint) + } + HintCode::BuiltIn(BuiltInHint::AddBn254) => Self::process_hint_add_bn254(&hint), + HintCode::BuiltIn(BuiltInHint::MulBn254) => Self::process_hint_mul_bn254(&hint), + HintCode::BuiltIn(BuiltInHint::ToAffineTwistBn254) => { + Self::process_hint_to_affine_twist_bn254(&hint) + } + HintCode::BuiltIn(BuiltInHint::IsOnCurveTwistBn254) => { + Self::process_hint_is_on_curve_twist_bn254(&hint) + } + HintCode::BuiltIn(BuiltInHint::IsOnSubgroupTwistBn254) => { + Self::process_hint_is_on_subgroup_twist_bn254(&hint) + } + HintCode::BuiltIn(BuiltInHint::PairingBatchBn254) => { + Self::process_hint_pairing_batch_bn254(&hint) + } // Custom hints HintCode::Custom(code) => { @@ -617,6 +637,49 @@ impl HintsProcessor { fn process_hint_modexp(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::modexp_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + + #[inline] + fn process_hint_to_affine_bn254(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::to_affine_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_is_on_curve_bn254(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::is_on_curve_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_add_bn254(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::add_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_mul_bn254(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::mul_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_to_affine_twist_bn254(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::to_affine_twist_bn254_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_is_on_curve_twist_bn254(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::is_on_curve_twist_bn254_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_is_on_subgroup_twist_bn254(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::is_on_subgroup_twist_bn254_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_pairing_batch_bn254(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::pairing_batch_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } } impl Drop for HintsProcessor { diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs new file mode 100644 index 000000000..da407988d --- /dev/null +++ b/ziskos-hints/src/handlers/bn254.rs @@ -0,0 +1,47 @@ +/// Processes a TO_AFFINE_BN254 hint. +#[inline] +pub fn to_affine_bn254_hint(_data: &[u64]) -> Result, String> { + unimplemented!("to_affine_bn254_hint is not implemented yet"); +} + +/// Processes an IS_ON_CURVE_BN254 hint. +#[inline] +pub fn is_on_curve_bn254_hint(_data: &[u64]) -> Result, String> { + unimplemented!("is_on_curve_bn254_hint is not implemented yet"); +} + +/// Processes an ADD_BN254 hint. +#[inline] +pub fn add_bn254_hint(_data: &[u64]) -> Result, String> { + unimplemented!("add_bn254_hint is not implemented yet"); +} + +/// Processes a MUL_BN254 hint. +#[inline] +pub fn mul_bn254_hint(_data: &[u64]) -> Result, String> { + unimplemented!("mul_bn254_hint is not implemented yet"); +} + +/// Processes a TO_AFFINE_TWIST_BN254 hint. +#[inline] +pub fn to_affine_twist_bn254_hint(_data: &[u64]) -> Result, String> { + unimplemented!("to_affine_twist_bn254_hint is not implemented yet"); +} + +/// Processes an IS_ON_CURVE_TWIST_BN254 hint. +#[inline] +pub fn is_on_curve_twist_bn254_hint(_data: &[u64]) -> Result, String> { + unimplemented!("is_on_curve_twist_bn254_hint is not implemented yet"); +} + +/// Processes an IS_ON_SUBGROUP_TWIST_BN254 hint. +#[inline] +pub fn is_on_subgroup_twist_bn254_hint(_data: &[u64]) -> Result, String> { + unimplemented!("is_on_subgroup_twist_bn254_hint is not implemented yet"); +} + +/// Processes a PAIRING_BATCH_BN254 hint. +#[inline] +pub fn pairing_batch_bn254_hint(_data: &[u64]) -> Result, String> { + unimplemented!("pairing_batch_bn254_hint is not implemented yet"); +} diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 7cbe76f50..81901066c 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -1,8 +1,10 @@ mod bigint256; +mod bn254; mod modexp; mod secp256k1; pub use bigint256::*; +pub use bn254::*; pub use modexp::*; pub use secp256k1::*; From d6cc8a60e5f1df35c95f3452e0e569fe0bb0bd31 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 6 Jan 2026 11:17:46 +0100 Subject: [PATCH 187/782] Fix multiple wait_for_prec symbols --- core/src/zisk_rom_2_asm.rs | 84 +++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 2ba3e2cba..eb7e6dfe7 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -141,7 +141,8 @@ pub struct ZiskAsmContext { ptr: String, // "ptr ", "" //assert_rsp_counter: u64, - precompile_results: bool, + precompile_results: bool, // Set to true is we are consuming precompile results + wait_for_prec_counter: u64, // Counter of wait_for_prec_avail calls, reset at every instruction } impl ZiskAsmContext { @@ -231,80 +232,61 @@ impl ZiskAsmContext { self.precompile_results } pub fn precompile_results_keccak(&self) -> bool { - //self.precompile_results() - true + self.precompile_results() } pub fn precompile_results_sha256(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_arith256(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_arith256mod(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_secp256k1add(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_secp256k1dbl(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_fcall(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_bn254curveadd(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_bn254curvedbl(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_bn254complexadd(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_bn254complexsub(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_bn254complexmul(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_arith384mod(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_bls12_381curveadd(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_bls12_381curvedbl(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_bls12_381complexadd(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_bls12_381complexsub(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_bls12_381complexmul(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn precompile_results_add256(&self) -> bool { - //self.precompile_results() - false + self.precompile_results() } pub fn call_wait_for_prec_avail(&self) -> bool { self.precompile_results() @@ -501,6 +483,10 @@ impl ZiskRom2Asm { comments: bool, precompile_results: bool, ) { + // println!( + // "ZiskRom2Asm::save_to_asm() generation_method={:?}, log_output={}, comments={}, precompile_results={}", + // generation_method, log_output, comments, precompile_results + // ); // Clear output data, just in case code.clear(); @@ -917,6 +903,9 @@ impl ZiskRom2Asm { // For all program addresses in the vector, create an assembly set of instructions with an // instruction label pc_ for k in 0..rom.sorted_pc_list.len() { + // Reset wait for prec counter + ctx.wait_for_prec_counter = 0; + // Get pc ctx.pc = rom.sorted_pc_list[k]; @@ -8234,25 +8223,34 @@ impl ZiskRom2Asm { ctx.comment_str("read ?= written") ); *code += &format!( - "\tjz pc_{:x}_wait_for_prec_avail {}\n", + "\tjz pc_{:x}_{}_wait_for_prec_avail {}\n", ctx.pc, + ctx.wait_for_prec_counter, ctx.comment_str("if there is data, done") ); - *code += &format!("pc_{:x}_wait_for_prec_avail_done:\n", ctx.pc,); + *code += + &format!("pc_{:x}_{}_wait_for_prec_avail_done:\n", ctx.pc, ctx.wait_for_prec_counter); // Call wait_for_prec_avail() - *unusual_code += &format!("pc_{:x}_wait_for_prec_avail:\n", ctx.pc,); + *unusual_code += + &format!("pc_{:x}_{}_wait_for_prec_avail:\n", ctx.pc, ctx.wait_for_prec_counter); Self::push_internal_registers(ctx, unusual_code, false); *unusual_code += "\tcall _wait_for_prec_avail\n"; *unusual_code += "\tcmp rax, 0\n"; *unusual_code += "\tjne execute_pop_internal_regs_and_end\n"; Self::pop_internal_registers(ctx, unusual_code, false); - *unusual_code += &format!("\tjmp pc_{:x}_wait_for_prec_avail_done\n", ctx.pc,); + *unusual_code += &format!( + "\tjmp pc_{:x}_{}_wait_for_prec_avail_done\n", + ctx.pc, ctx.wait_for_prec_counter + ); // TODO: // else if *precompile_written_address - *precompile_read_address < threshold -> call post_prec_read //*code += &format!("pc_{:x}_wait_for_prec_avail_end:\n", ctx.pc,); + + // Increment wait_for_prec_counter + ctx.wait_for_prec_counter += 1; } /*******************/ From 2130d726e3687f52e99a914fa63bdd18d71ee16a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 6 Jan 2026 11:58:01 +0000 Subject: [PATCH 188/782] added bn254 hint implementation --- Cargo.lock | 2 + common/src/hints.rs | 14 +- precompiles/hints/src/hints_processor.rs | 14 +- ziskos-hints/src/handlers/bn254.rs | 156 ++++++++++++++++++++--- 4 files changed, 154 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e48e8314..0ddf875f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6225,7 +6225,9 @@ version = "0.16.0" dependencies = [ "bincode", "cfg-if", + "elliptic-curve", "getrandom 0.2.16", + "k256", "lazy_static", "lib-c", "num-bigint", diff --git a/common/src/hints.rs b/common/src/hints.rs index d00a50381..74256888f 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -74,8 +74,8 @@ const HINT_WMUL256: u32 = 0x0C; const HINT_MODEXP: u32 = 0x0D; // BN254 precompile hint codes -const HINT_TO_AFFINE_BN254: u32 = 0x0E; -const HINT_IS_ON_CURVE_BN254: u32 = 0x0F; +const HINT_IS_ON_CURVE_BN254: u32 = 0x0E; +const HINT_TO_AFFINE_BN254: u32 = 0x0F; const HINT_ADD_BN254: u32 = 0x10; const HINT_MUL_BN254: u32 = 0x11; const HINT_TO_AFFINE_TWIST_BN254: u32 = 0x12; @@ -155,10 +155,10 @@ pub enum BuiltInHint { ModExp = HINT_MODEXP, // BN254 Precompile Hints - /// Convert to affine coordinates hint type for BN254 curve. - ToAffineBn254 = HINT_TO_AFFINE_BN254, /// Check if point is on curve hint type for BN254 curve. IsOnCurveBn254 = HINT_IS_ON_CURVE_BN254, + /// Convert to affine coordinates hint type for BN254 curve. + ToAffineBn254 = HINT_TO_AFFINE_BN254, /// Point addition hint type for BN254 curve. AddBn254 = HINT_ADD_BN254, /// Scalar multiplication hint type for BN254 curve. @@ -186,8 +186,8 @@ impl Display for BuiltInHint { BuiltInHint::OMul256 => "OMUL256", BuiltInHint::WMul256 => "WMUL256", BuiltInHint::ModExp => "MODEXP", - BuiltInHint::ToAffineBn254 => "TO_AFFINE_BN254", BuiltInHint::IsOnCurveBn254 => "IS_ON_CURVE_BN254", + BuiltInHint::ToAffineBn254 => "TO_AFFINE_BN254", BuiltInHint::AddBn254 => "ADD_BN254", BuiltInHint::MulBn254 => "MUL_BN254", BuiltInHint::ToAffineTwistBn254 => "TO_AFFINE_TWIST_BN254", @@ -214,8 +214,8 @@ impl TryFrom for BuiltInHint { HINT_OMUL256 => Ok(Self::OMul256), HINT_WMUL256 => Ok(Self::WMul256), HINT_MODEXP => Ok(Self::ModExp), - HINT_TO_AFFINE_BN254 => Ok(Self::ToAffineBn254), HINT_IS_ON_CURVE_BN254 => Ok(Self::IsOnCurveBn254), + HINT_TO_AFFINE_BN254 => Ok(Self::ToAffineBn254), HINT_ADD_BN254 => Ok(Self::AddBn254), HINT_MUL_BN254 => Ok(Self::MulBn254), HINT_TO_AFFINE_TWIST_BN254 => Ok(Self::ToAffineTwistBn254), @@ -285,8 +285,8 @@ impl HintCode { HintCode::BuiltIn(BuiltInHint::OMul256) => HINT_OMUL256, HintCode::BuiltIn(BuiltInHint::WMul256) => HINT_WMUL256, HintCode::BuiltIn(BuiltInHint::ModExp) => HINT_MODEXP, - HintCode::BuiltIn(BuiltInHint::ToAffineBn254) => HINT_TO_AFFINE_BN254, HintCode::BuiltIn(BuiltInHint::IsOnCurveBn254) => HINT_IS_ON_CURVE_BN254, + HintCode::BuiltIn(BuiltInHint::ToAffineBn254) => HINT_TO_AFFINE_BN254, HintCode::BuiltIn(BuiltInHint::AddBn254) => HINT_ADD_BN254, HintCode::BuiltIn(BuiltInHint::MulBn254) => HINT_MUL_BN254, HintCode::BuiltIn(BuiltInHint::ToAffineTwistBn254) => HINT_TO_AFFINE_TWIST_BN254, diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index c6b956422..fb2131d7b 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -554,12 +554,12 @@ impl HintsProcessor { HintCode::BuiltIn(BuiltInHint::OMul256) => Self::process_hint_omul256(&hint), HintCode::BuiltIn(BuiltInHint::WMul256) => Self::process_hint_wmul256(&hint), HintCode::BuiltIn(BuiltInHint::ModExp) => Self::process_hint_modexp(&hint), - HintCode::BuiltIn(BuiltInHint::ToAffineBn254) => { - Self::process_hint_to_affine_bn254(&hint) - } HintCode::BuiltIn(BuiltInHint::IsOnCurveBn254) => { Self::process_hint_is_on_curve_bn254(&hint) } + HintCode::BuiltIn(BuiltInHint::ToAffineBn254) => { + Self::process_hint_to_affine_bn254(&hint) + } HintCode::BuiltIn(BuiltInHint::AddBn254) => Self::process_hint_add_bn254(&hint), HintCode::BuiltIn(BuiltInHint::MulBn254) => Self::process_hint_mul_bn254(&hint), HintCode::BuiltIn(BuiltInHint::ToAffineTwistBn254) => { @@ -639,13 +639,13 @@ impl HintsProcessor { } #[inline] - fn process_hint_to_affine_bn254(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::to_affine_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + fn process_hint_is_on_curve_bn254(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::is_on_curve_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } #[inline] - fn process_hint_is_on_curve_bn254(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::is_on_curve_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + fn process_hint_to_affine_bn254(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::to_affine_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } #[inline] diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index da407988d..897d91394 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -1,47 +1,167 @@ -/// Processes a TO_AFFINE_BN254 hint. +use crate::{handlers::validate_hint_length, hint_fields, zisklib}; + +/// Processes an IS_ON_CURVE_BN254 hint. #[inline] -pub fn to_affine_bn254_hint(_data: &[u64]) -> Result, String> { - unimplemented!("to_affine_bn254_hint is not implemented yet"); +pub fn is_on_curve_bn254_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 8]; + + validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_BN254")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::is_on_curve_bn254(p, &mut processed_hints); + + Ok(processed_hints) } -/// Processes an IS_ON_CURVE_BN254 hint. +/// Processes a TO_AFFINE_BN254 hint. #[inline] -pub fn is_on_curve_bn254_hint(_data: &[u64]) -> Result, String> { - unimplemented!("is_on_curve_bn254_hint is not implemented yet"); +pub fn to_affine_bn254_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 12]; + + validate_hint_length(data, EXPECTED_LEN, "TO_AFFINE_BN254")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::to_affine_bn254(p, &mut processed_hints); + + Ok(processed_hints) } /// Processes an ADD_BN254 hint. #[inline] -pub fn add_bn254_hint(_data: &[u64]) -> Result, String> { - unimplemented!("add_bn254_hint is not implemented yet"); +pub fn add_bn254_hint(data: &[u64]) -> Result, String> { + hint_fields![P1: 8, P2: 8]; + + validate_hint_length(data, EXPECTED_LEN, "ADD_BN254")?; + + // Safe to unwrap due to prior length validation. + let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); + let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::add_bn254(p1, p2, &mut processed_hints); + + Ok(processed_hints) } /// Processes a MUL_BN254 hint. #[inline] -pub fn mul_bn254_hint(_data: &[u64]) -> Result, String> { - unimplemented!("mul_bn254_hint is not implemented yet"); +pub fn mul_bn254_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 8, K: 4]; + + validate_hint_length(data, EXPECTED_LEN, "MUL_BN254")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::mul_bn254(p, k, &mut processed_hints); + + Ok(processed_hints) } /// Processes a TO_AFFINE_TWIST_BN254 hint. #[inline] -pub fn to_affine_twist_bn254_hint(_data: &[u64]) -> Result, String> { - unimplemented!("to_affine_twist_bn254_hint is not implemented yet"); +pub fn to_affine_twist_bn254_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 24]; + + validate_hint_length(data, EXPECTED_LEN, "TO_AFFINE_TWIST_BN254")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::to_affine_twist_bn254(p, &mut processed_hints); + + Ok(processed_hints) } /// Processes an IS_ON_CURVE_TWIST_BN254 hint. #[inline] -pub fn is_on_curve_twist_bn254_hint(_data: &[u64]) -> Result, String> { - unimplemented!("is_on_curve_twist_bn254_hint is not implemented yet"); +pub fn is_on_curve_twist_bn254_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 16]; + + validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_TWIST_BN254")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::is_on_curve_twist_bn254(p, &mut processed_hints); + + Ok(processed_hints) } /// Processes an IS_ON_SUBGROUP_TWIST_BN254 hint. #[inline] -pub fn is_on_subgroup_twist_bn254_hint(_data: &[u64]) -> Result, String> { - unimplemented!("is_on_subgroup_twist_bn254_hint is not implemented yet"); +pub fn is_on_subgroup_twist_bn254_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 16]; + + validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_TWIST_BN254")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::is_on_curve_twist_bn254(p, &mut processed_hints); + + Ok(processed_hints) } /// Processes a PAIRING_BATCH_BN254 hint. +/// Format: [num_points:u64][g1_points:&[u64]][g2_points:&[u64]] +/// where g1_points has length num_points * 8 and g2_points has length num_points * 16 #[inline] -pub fn pairing_batch_bn254_hint(_data: &[u64]) -> Result, String> { - unimplemented!("pairing_batch_bn254_hint is not implemented yet"); +pub fn pairing_batch_bn254_hint(data: &[u64]) -> Result, String> { + if data.is_empty() { + return Err("PAIRING_BATCH_BN254: data is empty".to_string()); + } + + let num_points = data[0] as usize; + + const G1_POINT_SIZE: usize = 8; + const G2_POINT_SIZE: usize = 16; + + let expected_len = 1 + num_points * G1_POINT_SIZE + num_points * G2_POINT_SIZE; + + validate_hint_length(data, expected_len, "PAIRING_BATCH_BN254")?; + + let g1_start = 1; + let g1_end = g1_start + num_points * G1_POINT_SIZE; + let g2_start = g1_end; + let g2_end = g2_start + num_points * G2_POINT_SIZE; + + let g1_points_slice = &data[g1_start..g1_end]; + let g2_points_slice = &data[g2_start..g2_end]; + + // SAFETY: We've validated the length, and the memory layout of &[u64] with length num_points * 8 + // is identical to &[[u64; 8]] with length num_points + let g1_points: &[[u64; 8]] = unsafe { + std::slice::from_raw_parts(g1_points_slice.as_ptr() as *const [u64; 8], num_points) + }; + + // SAFETY: We've validated the length, and the memory layout of &[u64] with length num_points * 16 + // is identical to &[[u64; 16]] with length num_points + let g2_points: &[[u64; 16]] = unsafe { + std::slice::from_raw_parts(g2_points_slice.as_ptr() as *const [u64; 16], num_points) + }; + + let mut processed_hints = Vec::new(); + + zisklib::pairing_batch_bn254(g1_points, g2_points, &mut processed_hints); + + Ok(processed_hints) } From 154b1571294046dde70389c2cf9f87aba0d30915 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 6 Jan 2026 13:51:20 +0000 Subject: [PATCH 189/782] improve modexp hint handler --- ziskos-hints/src/handlers/modexp.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/ziskos-hints/src/handlers/modexp.rs b/ziskos-hints/src/handlers/modexp.rs index 815440dc1..02f8b34c8 100644 --- a/ziskos-hints/src/handlers/modexp.rs +++ b/ziskos-hints/src/handlers/modexp.rs @@ -1,4 +1,4 @@ -use crate::zisklib; +use crate::{handlers::validate_hint_length, zisklib}; // Processes a MODEXP hint. #[inline] @@ -36,16 +36,11 @@ pub fn modexp_hint(data: &[u64]) -> Result, String> { // Parse modulus let modulus_len = data[exp_end] as usize; let modulus_start = exp_end + 1; - let modulus_end = modulus_start + modulus_len; + let expected_len = modulus_start + modulus_len; - if data.len() != modulus_end { - return Err(format!( - "MODEXP hint data length mismatch (expected {}, got {})", - modulus_end, - data.len() - )); - } - let modulus = &data[modulus_start..modulus_end]; + validate_hint_length(data, expected_len, "MODEXP")?; + + let modulus = &data[modulus_start..expected_len]; let mut processed_hints = Vec::new(); From 3de0eafa33f2a456f53cb399913c677f2c950730 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:45:23 +0000 Subject: [PATCH 190/782] cargo clippy --- ziskos/entrypoint/src/syscalls/add256.rs | 2 +- ziskos/entrypoint/src/syscalls/arith256.rs | 2 +- ziskos/entrypoint/src/syscalls/arith256_mod.rs | 8 +------- ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs | 4 ++-- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/ziskos/entrypoint/src/syscalls/add256.rs b/ziskos/entrypoint/src/syscalls/add256.rs index 38400c959..476b05766 100644 --- a/ziskos/entrypoint/src/syscalls/add256.rs +++ b/ziskos/entrypoint/src/syscalls/add256.rs @@ -36,7 +36,7 @@ pub extern "C" fn syscall_add256( ) -> u64 { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { - let cout = precompiles_helpers::add256(params.a, params.b, params.cin, &mut params.c); + let cout = precompiles_helpers::add256(params.a, params.b, params.cin, params.c); #[cfg(feature = "hints")] { hints.extend_from_slice(params.c); diff --git a/ziskos/entrypoint/src/syscalls/arith256.rs b/ziskos/entrypoint/src/syscalls/arith256.rs index a35d41eb0..2ca71f39b 100644 --- a/ziskos/entrypoint/src/syscalls/arith256.rs +++ b/ziskos/entrypoint/src/syscalls/arith256.rs @@ -41,7 +41,7 @@ pub extern "C" fn syscall_arith256( ziskos_syscall!(0x801, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { - precompiles_helpers::arith256(params.a, params.b, params.c, &mut params.dl, &mut params.dh); + precompiles_helpers::arith256(params.a, params.b, params.c, params.dl, params.dh); #[cfg(feature = "hints")] { hints.extend_from_slice(params.dl); diff --git a/ziskos/entrypoint/src/syscalls/arith256_mod.rs b/ziskos/entrypoint/src/syscalls/arith256_mod.rs index 9fab2cb69..77ac53242 100644 --- a/ziskos/entrypoint/src/syscalls/arith256_mod.rs +++ b/ziskos/entrypoint/src/syscalls/arith256_mod.rs @@ -44,13 +44,7 @@ pub extern "C" fn syscall_arith256_mod( ziskos_syscall!(0x802, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { - precompiles_helpers::arith256_mod( - params.a, - params.b, - params.c, - params.module, - &mut params.d, - ); + precompiles_helpers::arith256_mod(params.a, params.b, params.c, params.module, params.d); #[cfg(feature = "hints")] { hints.extend_from_slice(params.d); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs index 17f2a5618..b4f408282 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs @@ -35,9 +35,9 @@ pub fn fcall_division( { hints.push(len_quo as u64 + len_rem as u64 + 2); hints.push(len_quo as u64); - hints.extend_from_slice(&quo); + hints.extend_from_slice(quo); hints.push(len_rem as u64); - hints.extend_from_slice(&rem); + hints.extend_from_slice(rem); } (len_quo, len_rem) From ee7b1435620cd559bec9809011143c5043e92cc1 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:47:29 +0000 Subject: [PATCH 191/782] cargo clippy --- precompiles/hints/src/hints_processor.rs | 2 +- ziskos/entrypoint/src/syscalls/arith384_mod.rs | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index fb2131d7b..1249eb663 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -1314,7 +1314,7 @@ mod tests { // Pseudo-random delay based on hash of input value (0-15ms range) // This creates unpredictable completion order across runs let hash = data[0].wrapping_mul(2654435761); - let delay_ms = (hash % 16) as u64; + let delay_ms = hash % 16; if delay_ms > 0 { thread::sleep(Duration::from_millis(delay_ms)); } diff --git a/ziskos/entrypoint/src/syscalls/arith384_mod.rs b/ziskos/entrypoint/src/syscalls/arith384_mod.rs index 1a1e8f119..86aa84a1c 100644 --- a/ziskos/entrypoint/src/syscalls/arith384_mod.rs +++ b/ziskos/entrypoint/src/syscalls/arith384_mod.rs @@ -44,13 +44,7 @@ pub extern "C" fn syscall_arith384_mod( ziskos_syscall!(0x80B, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { - precompiles_helpers::arith384_mod( - params.a, - params.b, - params.c, - params.module, - &mut params.d, - ); + precompiles_helpers::arith384_mod(params.a, params.b, params.c, params.module, params.d); #[cfg(feature = "hints")] { hints.extend_from_slice(params.d); From 78b574c85a03a635dd22e54d1aafe2da49978652 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:25:33 +0000 Subject: [PATCH 192/782] added bls12-381 hints --- common/src/hints.rs | 135 ++++++++++++++++++++++- precompiles/hints/src/hints_processor.rs | 120 ++++++++++++++++++++ ziskos-hints/src/handlers/bls318.rs | 77 +++++++++++++ ziskos-hints/src/handlers/mod.rs | 2 + 4 files changed, 331 insertions(+), 3 deletions(-) create mode 100644 ziskos-hints/src/handlers/bls318.rs diff --git a/common/src/hints.rs b/common/src/hints.rs index 74256888f..98ba72bae 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -58,7 +58,7 @@ const CTRL_ERROR: u32 = 0x03; // === BUILT-IN HINT CODES === // Noop hint code const HINT_NOOP: u32 = 0x04; -// Ecrecover precompile hint code +// Ecrecover hint code const HINT_ECRECOVER: u32 = 0x05; // Big integer arithmetic hint codes @@ -73,7 +73,7 @@ const HINT_WMUL256: u32 = 0x0C; // Modular exponentiation hint code const HINT_MODEXP: u32 = 0x0D; -// BN254 precompile hint codes +// BN254 hint codes const HINT_IS_ON_CURVE_BN254: u32 = 0x0E; const HINT_TO_AFFINE_BN254: u32 = 0x0F; const HINT_ADD_BN254: u32 = 0x10; @@ -83,6 +83,21 @@ const HINT_IS_ON_CURVE_TWIST_BN254: u32 = 0x13; const HINT_IS_ON_SUBGROUP_TWIST_BN254: u32 = 0x14; const HINT_PAIRING_BATCH_BN254: u32 = 0x15; +// BLS12-381 hint codes +const HINT_MUL_FP12_BLS12_381: u32 = 0x16; +const HINT_DECOMPRESS_BLS12_381: u32 = 0x17; +const HINT_IS_ON_CURVE_BLS12_381: u32 = 0x18; +const HINT_IS_ON_SUBGROUP_BLS12_381: u32 = 0x19; +const HINT_ADD_BLS12_381: u32 = 0x1A; +const HINT_SCALAR_MUL_BLS12_381: u32 = 0x1B; +const HINT_DECOMPRESS_TWIST_BLS12_381: u32 = 0x1C; +const HINT_IS_ON_CURVE_TWIST_BLS12_381: u32 = 0x1D; +const HINT_IS_ON_SUBGROUP_TWIST_BLS12_381: u32 = 0x1E; +const HINT_ADD_TWIST_BLS12_381: u32 = 0x1F; +const HINT_SCALAR_MUL_TWIST_BLS12_381: u32 = 0x20; +const HINT_MILLER_LOOP_BLS12_381: u32 = 0x21; +const HINT_FINAL_EXP_BLS12_381: u32 = 0x22; + /// Control code variants for stream control. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u32)] @@ -132,7 +147,7 @@ pub enum BuiltInHint { /// without any additional computation. Noop = HINT_NOOP, - /// Ecrecover precompile hint type. + /// Ecrecover hint type. EcRecover = HINT_ECRECOVER, // Big Integer Arithmetic Hints @@ -171,13 +186,46 @@ pub enum BuiltInHint { IsOnSubgroupTwistBn254 = HINT_IS_ON_SUBGROUP_TWIST_BN254, /// Pairing batch computation hint type for BN254 curve. PairingBatchBn254 = HINT_PAIRING_BATCH_BN254, + + // BLS12-381 Precompile Hints + /// Multiplication in Fp12 hint type for BLS12-381 curve. + MulFp12Bls12_381 = HINT_MUL_FP12_BLS12_381, + /// Point decompression hint type for BLS12-381 curve. + DecompressBls12_381 = HINT_DECOMPRESS_BLS12_381, + /// Check if point is on curve hint type for BLS12-381 curve. + IsOnCurveBls12_381 = HINT_IS_ON_CURVE_BLS12_381, + /// Check if point is in subgroup hint type for BLS12-381 curve. + IsOnSubgroupBls12_381 = HINT_IS_ON_SUBGROUP_BLS12_381, + /// Point addition hint type for BLS12-381 curve. + AddBls12_381 = HINT_ADD_BLS12_381, + /// Scalar multiplication hint type for BLS12-381 curve. + ScalarMulBls12_381 = HINT_SCALAR_MUL_BLS12_381, + /// Point decompression hint type for BLS12-381 twist. + DecompressTwistBls12_381 = HINT_DECOMPRESS_TWIST_BLS12_381, + /// Check if point is on curve hint type for BLS12-381 twist. + IsOnCurveTwistBls12_381 = HINT_IS_ON_CURVE_TWIST_BLS12_381, + /// Check if point is in subgroup hint type for BLS12-381 twist. + IsOnSubgroupTwistBls12_381 = HINT_IS_ON_SUBGROUP_TWIST_BLS12_381, + /// Point addition hint type for BLS12-381 twist. + AddTwistBls12_381 = HINT_ADD_TWIST_BLS12_381, + /// Scalar multiplication hint type for BLS12-381 twist. + ScalarMulTwistBls12_381 = HINT_SCALAR_MUL_TWIST_BLS12_381, + /// Miller loop computation hint type for BLS12-381 curve. + MillerLoopBls12_381 = HINT_MILLER_LOOP_BLS12_381, + /// Final exponentiation computation hint type for BLS12-381 curve. + FinalExpBls12_381 = HINT_FINAL_EXP_BLS12_381, } impl Display for BuiltInHint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = match self { + // Noop Hint BuiltInHint::Noop => "NOOP", + + // Ecrecover Hint BuiltInHint::EcRecover => "ECRECOVER", + + // Big Integer Arithmetic Hints BuiltInHint::RedMod256 => "REDMOD256", BuiltInHint::AddMod256 => "ADDMOD256", BuiltInHint::MulMod256 => "MULMOD256", @@ -185,7 +233,11 @@ impl Display for BuiltInHint { BuiltInHint::WPow256 => "WPOW256", BuiltInHint::OMul256 => "OMUL256", BuiltInHint::WMul256 => "WMUL256", + + // Modular Exponentiation Hint BuiltInHint::ModExp => "MODEXP", + + // BN254 Hints BuiltInHint::IsOnCurveBn254 => "IS_ON_CURVE_BN254", BuiltInHint::ToAffineBn254 => "TO_AFFINE_BN254", BuiltInHint::AddBn254 => "ADD_BN254", @@ -194,6 +246,21 @@ impl Display for BuiltInHint { BuiltInHint::IsOnCurveTwistBn254 => "IS_ON_CURVE_TWIST_BN254", BuiltInHint::IsOnSubgroupTwistBn254 => "IS_ON_SUBGROUP_TWIST_BN254", BuiltInHint::PairingBatchBn254 => "PAIRING_BATCH_BN254", + + // BLS12-381 Hints + BuiltInHint::MulFp12Bls12_381 => "MUL_FP12_BLS12_381", + BuiltInHint::DecompressBls12_381 => "DECOMPRESS_BLS12_381", + BuiltInHint::IsOnCurveBls12_381 => "IS_ON_CURVE_BLS12_381", + BuiltInHint::IsOnSubgroupBls12_381 => "IS_ON_SUBGROUP_BLS12_381", + BuiltInHint::AddBls12_381 => "ADD_BLS12_381", + BuiltInHint::ScalarMulBls12_381 => "SCALAR_MUL_BLS12_381", + BuiltInHint::DecompressTwistBls12_381 => "DECOMPRESS_TWIST_BLS12_381", + BuiltInHint::IsOnCurveTwistBls12_381 => "IS_ON_CURVE_TWIST_BLS12_381", + BuiltInHint::IsOnSubgroupTwistBls12_381 => "IS_ON_SUBGROUP_TWIST_BLS12_381", + BuiltInHint::AddTwistBls12_381 => "ADD_TWIST_BLS12_381", + BuiltInHint::ScalarMulTwistBls12_381 => "SCALAR_MUL_TWIST_BLS12_381", + BuiltInHint::MillerLoopBls12_381 => "MILLER_LOOP_BLS12_381", + BuiltInHint::FinalExpBls12_381 => "FINAL_EXP_BLS12_381", }; write!(f, "{} ({:#x})", name, *self as u32) } @@ -204,8 +271,13 @@ impl TryFrom for BuiltInHint { fn try_from(value: u32) -> Result { match value { + // Noop Hint HINT_NOOP => Ok(Self::Noop), + + // Ecrecover Hint HINT_ECRECOVER => Ok(Self::EcRecover), + + // Big Integer Arithmetic Hints HINT_REDMOD256 => Ok(Self::RedMod256), HINT_ADDMOD256 => Ok(Self::AddMod256), HINT_MULMOD256 => Ok(Self::MulMod256), @@ -213,7 +285,11 @@ impl TryFrom for BuiltInHint { HINT_WPOW256 => Ok(Self::WPow256), HINT_OMUL256 => Ok(Self::OMul256), HINT_WMUL256 => Ok(Self::WMul256), + + // Modular Exponentiation Hint HINT_MODEXP => Ok(Self::ModExp), + + // BN254 Hints HINT_IS_ON_CURVE_BN254 => Ok(Self::IsOnCurveBn254), HINT_TO_AFFINE_BN254 => Ok(Self::ToAffineBn254), HINT_ADD_BN254 => Ok(Self::AddBn254), @@ -222,6 +298,22 @@ impl TryFrom for BuiltInHint { HINT_IS_ON_CURVE_TWIST_BN254 => Ok(Self::IsOnCurveTwistBn254), HINT_IS_ON_SUBGROUP_TWIST_BN254 => Ok(Self::IsOnSubgroupTwistBn254), HINT_PAIRING_BATCH_BN254 => Ok(Self::PairingBatchBn254), + + // BLS12-381 Hints + HINT_MUL_FP12_BLS12_381 => Ok(Self::MulFp12Bls12_381), + HINT_DECOMPRESS_BLS12_381 => Ok(Self::DecompressBls12_381), + HINT_IS_ON_CURVE_BLS12_381 => Ok(Self::IsOnCurveBls12_381), + HINT_IS_ON_SUBGROUP_BLS12_381 => Ok(Self::IsOnSubgroupBls12_381), + HINT_ADD_BLS12_381 => Ok(Self::AddBls12_381), + HINT_SCALAR_MUL_BLS12_381 => Ok(Self::ScalarMulBls12_381), + HINT_DECOMPRESS_TWIST_BLS12_381 => Ok(Self::DecompressTwistBls12_381), + HINT_IS_ON_CURVE_TWIST_BLS12_381 => Ok(Self::IsOnCurveTwistBls12_381), + HINT_IS_ON_SUBGROUP_TWIST_BLS12_381 => Ok(Self::IsOnSubgroupTwistBls12_381), + HINT_ADD_TWIST_BLS12_381 => Ok(Self::AddTwistBls12_381), + HINT_SCALAR_MUL_TWIST_BLS12_381 => Ok(Self::ScalarMulTwistBls12_381), + HINT_MILLER_LOOP_BLS12_381 => Ok(Self::MillerLoopBls12_381), + HINT_FINAL_EXP_BLS12_381 => Ok(Self::FinalExpBls12_381), + _ => Err(anyhow::anyhow!("Invalid built-in hint code: {:#x}", value)), } } @@ -271,12 +363,20 @@ impl HintCode { #[inline] pub const fn to_u32(self) -> u32 { match self { + // Control Codes HintCode::Ctrl(CtrlHint::Start) => CTRL_START, HintCode::Ctrl(CtrlHint::End) => CTRL_END, HintCode::Ctrl(CtrlHint::Cancel) => CTRL_CANCEL, HintCode::Ctrl(CtrlHint::Error) => CTRL_ERROR, + + // Built-In Hint Codes + // Noop Hint HintCode::BuiltIn(BuiltInHint::Noop) => HINT_NOOP, + + // Ecrecover Hint HintCode::BuiltIn(BuiltInHint::EcRecover) => HINT_ECRECOVER, + + // Big Integer Arithmetic Hints HintCode::BuiltIn(BuiltInHint::RedMod256) => HINT_REDMOD256, HintCode::BuiltIn(BuiltInHint::AddMod256) => HINT_ADDMOD256, HintCode::BuiltIn(BuiltInHint::MulMod256) => HINT_MULMOD256, @@ -284,7 +384,11 @@ impl HintCode { HintCode::BuiltIn(BuiltInHint::WPow256) => HINT_WPOW256, HintCode::BuiltIn(BuiltInHint::OMul256) => HINT_OMUL256, HintCode::BuiltIn(BuiltInHint::WMul256) => HINT_WMUL256, + + // Modular Exponentiation Hint HintCode::BuiltIn(BuiltInHint::ModExp) => HINT_MODEXP, + + // BN254 Hints HintCode::BuiltIn(BuiltInHint::IsOnCurveBn254) => HINT_IS_ON_CURVE_BN254, HintCode::BuiltIn(BuiltInHint::ToAffineBn254) => HINT_TO_AFFINE_BN254, HintCode::BuiltIn(BuiltInHint::AddBn254) => HINT_ADD_BN254, @@ -295,6 +399,31 @@ impl HintCode { HINT_IS_ON_SUBGROUP_TWIST_BN254 } HintCode::BuiltIn(BuiltInHint::PairingBatchBn254) => HINT_PAIRING_BATCH_BN254, + + // BLS12-381 Hints + HintCode::BuiltIn(BuiltInHint::MulFp12Bls12_381) => HINT_MUL_FP12_BLS12_381, + HintCode::BuiltIn(BuiltInHint::DecompressBls12_381) => HINT_DECOMPRESS_BLS12_381, + HintCode::BuiltIn(BuiltInHint::IsOnCurveBls12_381) => HINT_IS_ON_CURVE_BLS12_381, + HintCode::BuiltIn(BuiltInHint::IsOnSubgroupBls12_381) => HINT_IS_ON_SUBGROUP_BLS12_381, + HintCode::BuiltIn(BuiltInHint::AddBls12_381) => HINT_ADD_BLS12_381, + HintCode::BuiltIn(BuiltInHint::ScalarMulBls12_381) => HINT_SCALAR_MUL_BLS12_381, + HintCode::BuiltIn(BuiltInHint::DecompressTwistBls12_381) => { + HINT_DECOMPRESS_TWIST_BLS12_381 + } + HintCode::BuiltIn(BuiltInHint::IsOnCurveTwistBls12_381) => { + HINT_IS_ON_CURVE_TWIST_BLS12_381 + } + HintCode::BuiltIn(BuiltInHint::IsOnSubgroupTwistBls12_381) => { + HINT_IS_ON_SUBGROUP_TWIST_BLS12_381 + } + HintCode::BuiltIn(BuiltInHint::AddTwistBls12_381) => HINT_ADD_TWIST_BLS12_381, + HintCode::BuiltIn(BuiltInHint::ScalarMulTwistBls12_381) => { + HINT_SCALAR_MUL_TWIST_BLS12_381 + } + HintCode::BuiltIn(BuiltInHint::MillerLoopBls12_381) => HINT_MILLER_LOOP_BLS12_381, + HintCode::BuiltIn(BuiltInHint::FinalExpBls12_381) => HINT_FINAL_EXP_BLS12_381, + + // Custom Hints HintCode::Custom(code) => code, } } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 1249eb663..cea34ae24 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -545,7 +545,10 @@ impl HintsProcessor { custom_handlers: Arc>, ) -> Result> { match hint.hint_code { + // EcRecover Hint HintCode::BuiltIn(BuiltInHint::EcRecover) => Self::process_hint_ecrecover(&hint), + + // Big Integer Arithmetic Hints HintCode::BuiltIn(BuiltInHint::RedMod256) => Self::process_hint_redmod256(&hint), HintCode::BuiltIn(BuiltInHint::AddMod256) => Self::process_hint_addmod256(&hint), HintCode::BuiltIn(BuiltInHint::MulMod256) => Self::process_hint_mulmod256(&hint), @@ -553,7 +556,11 @@ impl HintsProcessor { HintCode::BuiltIn(BuiltInHint::WPow256) => Self::process_hint_wpow256(&hint), HintCode::BuiltIn(BuiltInHint::OMul256) => Self::process_hint_omul256(&hint), HintCode::BuiltIn(BuiltInHint::WMul256) => Self::process_hint_wmul256(&hint), + + // Modular Exponentiation Hint HintCode::BuiltIn(BuiltInHint::ModExp) => Self::process_hint_modexp(&hint), + + // BN254 hints HintCode::BuiltIn(BuiltInHint::IsOnCurveBn254) => { Self::process_hint_is_on_curve_bn254(&hint) } @@ -575,6 +582,45 @@ impl HintsProcessor { Self::process_hint_pairing_batch_bn254(&hint) } + // BLS12-381 hints + HintCode::BuiltIn(BuiltInHint::MulFp12Bls12_381) => { + Self::process_hint_mul_fp_bls12_381(&hint) + } + HintCode::BuiltIn(BuiltInHint::DecompressBls12_381) => { + Self::process_hint_decompress_bls12_381(&hint) + } + HintCode::BuiltIn(BuiltInHint::IsOnCurveBls12_381) => { + Self::process_hint_is_on_curve_bls12_381(&hint) + } + HintCode::BuiltIn(BuiltInHint::IsOnSubgroupBls12_381) => { + Self::process_hint_is_on_subgroup_bls12_381(&hint) + } + HintCode::BuiltIn(BuiltInHint::AddBls12_381) => Self::process_hint_add_bls12_381(&hint), + HintCode::BuiltIn(BuiltInHint::ScalarMulBls12_381) => { + Self::process_hint_scalar_mul_bls12_381(&hint) + } + HintCode::BuiltIn(BuiltInHint::DecompressTwistBls12_381) => { + Self::process_hint_decompress_twist_bls12_381(&hint) + } + HintCode::BuiltIn(BuiltInHint::IsOnCurveTwistBls12_381) => { + Self::process_hint_is_on_curve_twist_bls12_381(&hint) + } + HintCode::BuiltIn(BuiltInHint::IsOnSubgroupTwistBls12_381) => { + Self::process_hint_is_on_subgroup_twist_bls12_381(&hint) + } + HintCode::BuiltIn(BuiltInHint::AddTwistBls12_381) => { + Self::process_hint_add_twist_bls12_381(&hint) + } + HintCode::BuiltIn(BuiltInHint::ScalarMulTwistBls12_381) => { + Self::process_hint_scalar_mul_twist_bls12_381(&hint) + } + HintCode::BuiltIn(BuiltInHint::MillerLoopBls12_381) => { + Self::process_hint_miller_loop_bls12_381(&hint) + } + HintCode::BuiltIn(BuiltInHint::FinalExpBls12_381) => { + Self::process_hint_final_exp_bls12_381(&hint) + } + // Custom hints HintCode::Custom(code) => { if let Some(handler) = custom_handlers.get(&code) { @@ -680,6 +726,80 @@ impl HintsProcessor { fn process_hint_pairing_batch_bn254(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::pairing_batch_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + + #[inline] + fn process_hint_mul_fp_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::mul_fp12_bls12_381_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_decompress_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::decompress_bls12_381_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_is_on_curve_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::is_on_curve_bls12_381_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_is_on_subgroup_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::is_on_subgroup_bls12_381_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_add_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::add_bls12_381_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_scalar_mul_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::scalar_mul_bls12_381_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_decompress_twist_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::decompress_twist_bls12_381_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_is_on_curve_twist_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::is_on_curve_twist_bls12_381_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_is_on_subgroup_twist_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::is_on_subgroup_twist_bls12_381_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_add_twist_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::add_twist_bls12_381_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_scalar_mul_twist_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::scalar_mul_twist_bls12_381_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_miller_loop_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::miller_loop_bls12_381_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + #[inline] + fn process_hint_final_exp_bls12_381(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::final_exp_bls12_381_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } } impl Drop for HintsProcessor { diff --git a/ziskos-hints/src/handlers/bls318.rs b/ziskos-hints/src/handlers/bls318.rs new file mode 100644 index 000000000..e32b0f44b --- /dev/null +++ b/ziskos-hints/src/handlers/bls318.rs @@ -0,0 +1,77 @@ +/// Processes an MUL_FP12_BLS12_381 hint. +#[inline] +pub fn mul_fp12_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("mul_fp12_bls12_381_hint is not yet implemented"); +} + +/// Processes a DECOMPRESS_BLS12_381 hint. +#[inline] +pub fn decompress_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("decompress_bls12_381_hint is not yet implemented"); +} + +/// Processes an IS_ON_CURVE_BLS12_381 hint. +#[inline] +pub fn is_on_curve_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("is_on_curve_bls12_381_hint is not yet implemented"); +} + +/// Processes an IS_ON_SUBGROUP_BLS12_381 hint. +#[inline] +pub fn is_on_subgroup_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("is_on_subgroup_bls12_381_hint is not yet implemented"); +} + +/// Processes an ADD_BLS12_381 hint. +#[inline] +pub fn add_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("add_bls12_381_hint is not yet implemented"); +} + +/// Processes a SCALAR_MUL_BLS12_381 hint. +#[inline] +pub fn scalar_mul_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("scalar_mul_bls12_381_hint is not yet implemented"); +} + +/// Processes a DECOMPRESS_TWIST_BLS12_381 hint. +#[inline] +pub fn decompress_twist_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("decompress_twist_bls12_381_hint is not yet implemented"); +} + +/// Processes an IS_ON_CURVE_TWIST_BLS12_381 hint. +#[inline] +pub fn is_on_curve_twist_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("is_on_curve_twist_bls12_381_hint is not yet implemented"); +} + +/// Processes an IS_ON_SUBGROUP_TWIST_BLS12_381 hint. +#[inline] +pub fn is_on_subgroup_twist_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("is_on_subgroup_twist_bls12_381_hint is not yet implemented"); +} + +/// Processes an ADD_TWIST_BLS12_381 hint. +#[inline] +pub fn add_twist_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("add_twist_bls12_381_hint is not yet implemented"); +} + +/// Processes a SCALAR_MUL_TWIST_BLS12_381 hint. +#[inline] +pub fn scalar_mul_twist_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("scalar_mul_twist_bls12_381_hint is not yet implemented"); +} + +/// Processes a MILLER_LOOP_BLS12_381 hint. +#[inline] +pub fn miller_loop_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("miller_loop_bls12_381_hint is not yet implemented"); +} + +/// Processes a FINAL_EXP_BLS12_381 hint. +#[inline] +pub fn final_exp_bls12_381_hint(_data: &[u64]) -> Result, String> { + unimplemented!("final_exp_bls12_381_hint is not yet implemented"); +} diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 81901066c..150bc624c 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -1,9 +1,11 @@ mod bigint256; +mod bls318; mod bn254; mod modexp; mod secp256k1; pub use bigint256::*; +pub use bls318::*; pub use bn254::*; pub use modexp::*; pub use secp256k1::*; From aa97427a1464dcffa7f4114cf5da8ba51aa0784e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:37:18 +0000 Subject: [PATCH 193/782] added bls12-381 hints handlers implementations --- ziskos-hints/src/handlers/bls318.rs | 209 +++++++++++++++--- .../src/zisklib/lib/bls12_381/mod.rs | 1 + 2 files changed, 184 insertions(+), 26 deletions(-) diff --git a/ziskos-hints/src/handlers/bls318.rs b/ziskos-hints/src/handlers/bls318.rs index e32b0f44b..ad6afdad5 100644 --- a/ziskos-hints/src/handlers/bls318.rs +++ b/ziskos-hints/src/handlers/bls318.rs @@ -1,77 +1,234 @@ +use crate::{handlers::validate_hint_length, hint_fields, zisklib}; + /// Processes an MUL_FP12_BLS12_381 hint. #[inline] -pub fn mul_fp12_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("mul_fp12_bls12_381_hint is not yet implemented"); +pub fn mul_fp12_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![A: 72, B: 72]; + + validate_hint_length(data, EXPECTED_LEN, "MUL_FP12_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let a: &[u64; A_SIZE] = data[A_OFFSET..A_OFFSET + A_SIZE].try_into().unwrap(); + let b: &[u64; B_SIZE] = data[B_OFFSET..B_OFFSET + B_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::mul_fp12_bls12_381(a, b, &mut processed_hints); + + Ok(processed_hints) } /// Processes a DECOMPRESS_BLS12_381 hint. #[inline] -pub fn decompress_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("decompress_bls12_381_hint is not yet implemented"); +pub fn decompress_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![INPUT: 6]; + + validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let input: &[u64; INPUT_SIZE] = + data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); + // Map a [u64; 6] to a [u8; 48] + let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; + + let mut processed_hints = Vec::new(); + + zisklib::decompress_bls12_381(input, &mut processed_hints)?; + + Ok(processed_hints) } /// Processes an IS_ON_CURVE_BLS12_381 hint. #[inline] -pub fn is_on_curve_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("is_on_curve_bls12_381_hint is not yet implemented"); +pub fn is_on_curve_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 12]; + + validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::is_on_curve_bls12_381(p, &mut processed_hints); + + Ok(processed_hints) } /// Processes an IS_ON_SUBGROUP_BLS12_381 hint. #[inline] -pub fn is_on_subgroup_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("is_on_subgroup_bls12_381_hint is not yet implemented"); +pub fn is_on_subgroup_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 12]; + + validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::is_on_subgroup_bls12_381(p, &mut processed_hints); + + Ok(processed_hints) } /// Processes an ADD_BLS12_381 hint. #[inline] -pub fn add_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("add_bls12_381_hint is not yet implemented"); +pub fn add_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![P1: 12, P2: 12]; + + validate_hint_length(data, EXPECTED_LEN, "ADD_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); + let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::add_bls12_381(p1, p2, &mut processed_hints); + + Ok(processed_hints) } /// Processes a SCALAR_MUL_BLS12_381 hint. #[inline] -pub fn scalar_mul_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("scalar_mul_bls12_381_hint is not yet implemented"); +pub fn scalar_mul_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 12, K: 6]; + + validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::scalar_mul_bls12_381(p, k, &mut processed_hints); + + Ok(processed_hints) } /// Processes a DECOMPRESS_TWIST_BLS12_381 hint. #[inline] -pub fn decompress_twist_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("decompress_twist_bls12_381_hint is not yet implemented"); +pub fn decompress_twist_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![INPUT: 12]; + + validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_TWIST_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let input: &[u64; INPUT_SIZE] = + data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); + // Map a [u64; 6] to a [u8; 48] + let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; + + let mut processed_hints = Vec::new(); + + zisklib::decompress_twist_bls12_381(input, &mut processed_hints)?; + + Ok(processed_hints) } /// Processes an IS_ON_CURVE_TWIST_BLS12_381 hint. #[inline] -pub fn is_on_curve_twist_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("is_on_curve_twist_bls12_381_hint is not yet implemented"); +pub fn is_on_curve_twist_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 24]; + + validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_TWIST_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::is_on_curve_twist_bls12_381(p, &mut processed_hints); + + Ok(processed_hints) } /// Processes an IS_ON_SUBGROUP_TWIST_BLS12_381 hint. #[inline] -pub fn is_on_subgroup_twist_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("is_on_subgroup_twist_bls12_381_hint is not yet implemented"); +pub fn is_on_subgroup_twist_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 24]; + + validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_TWIST_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::is_on_subgroup_twist_bls12_381(p, &mut processed_hints); + + Ok(processed_hints) } /// Processes an ADD_TWIST_BLS12_381 hint. #[inline] -pub fn add_twist_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("add_twist_bls12_381_hint is not yet implemented"); +pub fn add_twist_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![P1: 24, P2: 24]; + + validate_hint_length(data, EXPECTED_LEN, "ADD_TWIST_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); + let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::add_twist_bls12_381(p1, p2, &mut processed_hints); + + Ok(processed_hints) } /// Processes a SCALAR_MUL_TWIST_BLS12_381 hint. #[inline] -pub fn scalar_mul_twist_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("scalar_mul_twist_bls12_381_hint is not yet implemented"); +pub fn scalar_mul_twist_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 24, K: 6]; + + validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_TWIST_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::scalar_mul_twist_bls12_381(p, k, &mut processed_hints); + + Ok(processed_hints) } /// Processes a MILLER_LOOP_BLS12_381 hint. #[inline] -pub fn miller_loop_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("miller_loop_bls12_381_hint is not yet implemented"); +pub fn miller_loop_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 12, Q: 24]; + + validate_hint_length(data, EXPECTED_LEN, "MILLER_LOOP_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + let q: &[u64; Q_SIZE] = data[Q_OFFSET..Q_OFFSET + Q_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::miller_loop_bls12_381(p, q, &mut processed_hints); + + Ok(processed_hints) } /// Processes a FINAL_EXP_BLS12_381 hint. #[inline] -pub fn final_exp_bls12_381_hint(_data: &[u64]) -> Result, String> { - unimplemented!("final_exp_bls12_381_hint is not yet implemented"); +pub fn final_exp_bls12_381_hint(data: &[u64]) -> Result, String> { + hint_fields![F: 72]; + + validate_hint_length(data, EXPECTED_LEN, "FINAL_EXP_BLS12_381")?; + + // Safe to unwrap due to prior length validation. + let f: &[u64; F_SIZE] = data[F_OFFSET..F_OFFSET + F_SIZE].try_into().unwrap(); + + let mut processed_hints = Vec::new(); + + zisklib::final_exp_bls12_381(f, &mut processed_hints); + + Ok(processed_hints) } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs index f7cf0a85b..ce9277fba 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs @@ -19,5 +19,6 @@ pub use fp12::*; pub use fp2::*; pub use fp6::*; pub use fr::*; +pub use miller_loop::*; pub use pairing::*; pub use twist::*; From 27132f882981e2fbafa2a9434eb9a2db71dd57f9 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:20:44 +0000 Subject: [PATCH 194/782] minor improvement on bn254.rs ziskos-hints --- ziskos-hints/src/handlers/bn254.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 897d91394..3a4986d77 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -149,14 +149,20 @@ pub fn pairing_batch_bn254_hint(data: &[u64]) -> Result, String> { // SAFETY: We've validated the length, and the memory layout of &[u64] with length num_points * 8 // is identical to &[[u64; 8]] with length num_points - let g1_points: &[[u64; 8]] = unsafe { - std::slice::from_raw_parts(g1_points_slice.as_ptr() as *const [u64; 8], num_points) + let g1_points: &[[u64; G1_POINT_SIZE]] = unsafe { + std::slice::from_raw_parts( + g1_points_slice.as_ptr() as *const [u64; G1_POINT_SIZE], + num_points, + ) }; // SAFETY: We've validated the length, and the memory layout of &[u64] with length num_points * 16 // is identical to &[[u64; 16]] with length num_points - let g2_points: &[[u64; 16]] = unsafe { - std::slice::from_raw_parts(g2_points_slice.as_ptr() as *const [u64; 16], num_points) + let g2_points: &[[u64; G2_POINT_SIZE]] = unsafe { + std::slice::from_raw_parts( + g2_points_slice.as_ptr() as *const [u64; G2_POINT_SIZE], + num_points, + ) }; let mut processed_hints = Vec::new(); From b73f288afc011c844c882ac553d00a1b6cf603f8 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:19:24 +0000 Subject: [PATCH 195/782] added hints sink file --- emulator-asm/asm-runner/src/hints_file.rs | 66 +++++++++++++++++++++++ emulator-asm/asm-runner/src/lib.rs | 2 + 2 files changed, 68 insertions(+) create mode 100644 emulator-asm/asm-runner/src/hints_file.rs diff --git a/emulator-asm/asm-runner/src/hints_file.rs b/emulator-asm/asm-runner/src/hints_file.rs new file mode 100644 index 000000000..ebb208eb6 --- /dev/null +++ b/emulator-asm/asm-runner/src/hints_file.rs @@ -0,0 +1,66 @@ +//! HintsFile is responsible for writing precompile processed hints to a file. +//! +//! It implements the StreamSink trait to receive processed hints and write them to a file. + +use anyhow::Result; +use std::fs::File; +use std::io::Write; +use std::sync::Mutex; +use zisk_common::io::StreamSink; + +/// HintsFile struct manages the writing of processed precompile hints to a file. +pub struct HintsFile { + file: Mutex, +} + +unsafe impl Send for HintsFile {} +unsafe impl Sync for HintsFile {} + +impl HintsFile { + /// Create a new HintsFile with the given filename. + /// + /// # Arguments + /// * `filename` - Path to the file where hints will be written. + /// + /// # Returns + /// A new `HintsFile` instance. + pub fn new(filename: String) -> Result { + let file = File::create(&filename)?; + Ok(Self { file: Mutex::new(file) }) + } +} + +impl StreamSink for HintsFile { + /// Writes processed precompile hints to the file. + /// + /// # Arguments + /// * `processed` - A vector of processed precompile hints as u64 values. + /// + /// # Returns + /// * `Ok(())` - If hints were successfully written to the file + /// * `Err` - If writing to the file fails + #[inline] + fn submit(&self, processed: Vec) -> anyhow::Result<()> { + let mut file = self.file.lock().unwrap(); + + // Write each u64 as 8 bytes (little-endian) + for value in processed { + file.write_all(&value.to_le_bytes())?; + } + + // Flush to ensure data is written immediately + file.flush()?; + + Ok(()) + } +} + +impl Drop for HintsFile { + fn drop(&mut self) { + // File is automatically closed when dropped + // We can ensure final flush here + if let Ok(mut file) = self.file.lock() { + let _ = file.flush(); + } + } +} diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index f3357c347..9e4ccc471 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -17,6 +17,7 @@ mod asm_rh_runner; mod asm_rh_runner_stub; mod asm_runner; mod asm_services; +mod hints_file; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod hints_shmem; #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] @@ -42,6 +43,7 @@ pub use asm_rh_runner::*; pub use asm_rh_runner_stub::*; pub use asm_runner::*; pub use asm_services::*; +pub use hints_file::*; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub use hints_shmem::*; #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] From 43b7e53ede544dc6adf37edd81052836723a89f4 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:45:35 +0000 Subject: [PATCH 196/782] move precompile_hints argument name to hints --- cli/src/commands/execute.rs | 8 ++++---- cli/src/commands/prove.rs | 8 ++++---- cli/src/commands/rom_setup.rs | 6 +++--- cli/src/commands/stats.rs | 8 ++++---- cli/src/commands/verify_constraints.rs | 8 ++++---- core/src/elf2rom.rs | 11 ++--------- core/src/riscv2zisk.rs | 4 ++-- rom-setup/src/asm_setup.rs | 10 ++-------- rom-setup/src/rom_full_setup.rs | 11 ++--------- 9 files changed, 27 insertions(+), 47 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index f2bc167b5..6e82f34d7 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -46,8 +46,8 @@ pub struct ZiskExecute { pub inputs: Option, /// Precompiles Hints path - #[clap(short = 'h', long)] - pub precompile_hints: Option, + #[clap(long)] + pub hints: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -92,13 +92,13 @@ impl ZiskExecute { print_banner_field("Input", inputs); } - if let Some(hints) = &self.precompile_hints { + if let Some(hints) = &self.hints { print_banner_field("Prec. Hints", hints); } let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.precompile_hints.as_deref())?; + let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 93c014ccb..b90ea7e43 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -46,8 +46,8 @@ pub struct ZiskProve { pub inputs: Option, /// Precompiles Hints path - #[clap(short = 'h', long)] - pub precompile_hints: Option, + #[clap(long)] + pub hints: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -124,7 +124,7 @@ impl ZiskProve { print_banner_field("Input", inputs); } - if let Some(hints) = &self.precompile_hints { + if let Some(hints) = &self.hints { print_banner_field("Prec. Hints", hints); } @@ -142,7 +142,7 @@ impl ZiskProve { let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.precompile_hints.as_deref())?; + let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index a789e5684..34e413193 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -31,8 +31,8 @@ pub struct ZiskRomSetup { pub output_dir: Option, /// Enable precompile hints in assembly generation - #[clap(short = 'h', long, default_value_t = true)] - pub precompile_hints: bool, + #[clap(long, default_value_t = true)] + pub hints: bool, #[clap(short = 'v', long, default_value_t = false)] pub verbose: bool, @@ -58,7 +58,7 @@ impl ZiskRomSetup { &proving_key, &zisk_path, &self.output_dir, - self.precompile_hints, + self.hints, self.verbose, ) } diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index 6c0f541da..e280acc7c 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -46,8 +46,8 @@ pub struct ZiskStats { pub inputs: Option, /// Precompiles Hints path - #[clap(short = 'h', long)] - pub precompile_hints: Option, + #[clap(long)] + pub hints: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -106,13 +106,13 @@ impl ZiskStats { print_banner_field("Input", inputs); } - if let Some(hints) = &self.precompile_hints { + if let Some(hints) = &self.hints { print_banner_field("Prec. Hints", hints); } let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.precompile_hints.as_deref())?; + let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 5c177070a..41b782d51 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -48,8 +48,8 @@ pub struct ZiskVerifyConstraints { pub inputs: Option, /// Precompiles Hints path - #[clap(short = 'h', long)] - pub precompile_hints: Option, + #[clap(long)] + pub hints: Option, /// Setup folder path #[clap(short = 'k', long)] @@ -97,13 +97,13 @@ impl ZiskVerifyConstraints { print_banner_field("Input", inputs); } - if let Some(hints) = &self.precompile_hints { + if let Some(hints) = &self.hints { print_banner_field("Prec. Hints", hints); } let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.precompile_hints.as_deref())?; + let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; let emulator = if cfg!(target_os = "macos") { if !self.emulator { diff --git a/core/src/elf2rom.rs b/core/src/elf2rom.rs index 9a64be8e8..7a8e02a8f 100644 --- a/core/src/elf2rom.rs +++ b/core/src/elf2rom.rs @@ -221,17 +221,10 @@ pub fn elf2romfile( generation_method: AsmGenerationMethod, log_output: bool, comments: bool, - precompile_hints: bool, + hints: bool, ) -> Result<(), Box> { let rom = elf2rom(elf_file)?; - ZiskRom2Asm::save_to_asm_file( - &rom, - asm_file, - generation_method, - log_output, - comments, - precompile_hints, - ); + ZiskRom2Asm::save_to_asm_file(&rom, asm_file, generation_method, log_output, comments, hints); Ok(()) } diff --git a/core/src/riscv2zisk.rs b/core/src/riscv2zisk.rs index c28663821..8a0aebe1b 100644 --- a/core/src/riscv2zisk.rs +++ b/core/src/riscv2zisk.rs @@ -80,7 +80,7 @@ impl Riscv2zisk { generation_method: AsmGenerationMethod, log_output: bool, comments: bool, - precompile_hints: bool, + hints: bool, ) -> Result<(), Box> { elf2romfile( &self.elf_file, @@ -88,7 +88,7 @@ impl Riscv2zisk { generation_method, log_output, comments, - precompile_hints, + hints, ) .map_err(|e| format!("Error converting elf to assembly: {e}").into()) } diff --git a/rom-setup/src/asm_setup.rs b/rom-setup/src/asm_setup.rs index a1614d129..b7d5d964b 100644 --- a/rom-setup/src/asm_setup.rs +++ b/rom-setup/src/asm_setup.rs @@ -11,7 +11,7 @@ pub fn generate_assembly( elf_hash: &str, zisk_path: &Path, output_path: &Path, - precompile_hints: bool, + hints: bool, verbose: bool, ) -> Result<(), anyhow::Error> { // Read the ELF file and check if it is a valid ELF file @@ -47,13 +47,7 @@ pub fn generate_assembly( // Convert the ELF file to Zisk format and generates an assembly file let rv2zk = Riscv2zisk::new(elf_file_path.to_str().unwrap().to_string()); rv2zk - .runfile( - asm_file.to_str().unwrap().to_string(), - *gen_method, - false, - false, - precompile_hints, - ) + .runfile(asm_file.to_str().unwrap().to_string(), *gen_method, false, false, hints) .expect("Error converting elf to assembly"); let emulator_asm_path = zisk_path.join("emulator-asm"); diff --git a/rom-setup/src/rom_full_setup.rs b/rom-setup/src/rom_full_setup.rs index 92b2dc757..63200a054 100644 --- a/rom-setup/src/rom_full_setup.rs +++ b/rom-setup/src/rom_full_setup.rs @@ -13,7 +13,7 @@ pub fn rom_full_setup( proving_key: &Path, zisk_path: &Path, output_dir: &Option, - precompile_hints: bool, + hints: bool, verbose: bool, ) -> std::result::Result<(), anyhow::Error> { let output_path = if output_dir.is_none() { @@ -45,14 +45,7 @@ pub fn rom_full_setup( #[cfg(not(target_os = "macos"))] { tracing::info!("Computing assembly setup"); - crate::generate_assembly( - elf, - &elf_hash, - zisk_path, - output_path.as_path(), - precompile_hints, - verbose, - )?; + crate::generate_assembly(elf, &elf_hash, zisk_path, output_path.as_path(), hints, verbose)?; } println!(); From 2e5b697089a9754449000b3525c110ad3fd0ff63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:26:08 +0000 Subject: [PATCH 197/782] Finishing optimization ecdas --- ziskos/entrypoint/src/zisklib/fcalls/mod.rs | 1 + .../src/zisklib/fcalls/secp256k1/ecdsa.rs | 66 ++++ .../src/zisklib/fcalls/secp256k1/mod.rs | 2 + .../src/zisklib/fcalls_impl/bn254/fp.rs | 4 + .../src/zisklib/fcalls_impl/proxy.rs | 8 +- .../fcalls_impl/secp256k1/constants.rs | 13 + .../zisklib/fcalls_impl/secp256k1/ecdsa.rs | 296 ++++++++++++++++++ .../src/zisklib/fcalls_impl/secp256k1/mod.rs | 2 + .../src/zisklib/fcalls_impl/utils.rs | 4 + .../src/zisklib/lib/secp256k1/curve.rs | 55 +++- .../src/zisklib/lib/secp256k1/ecdsa.rs | 83 ++--- 11 files changed, 457 insertions(+), 77 deletions(-) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs create mode 100644 ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs diff --git a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs index d4315282c..2de781ae3 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs @@ -19,6 +19,7 @@ pub const FCALL_BIG_INT256_DIV_ID: u16 = 16; pub const FCALL_BIG_INT_DIV_ID: u16 = 17; pub const FCALL_BIN_DECOMP_ID: u16 = 18; pub const FCALL_BLS12_381_FP2_SQRT_ID: u16 = 19; +pub const FCALL_SECP256K1_ECDSA_VERIFY_ID: u16 = 20; mod big_int256_div; mod big_int_div; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs new file mode 100644 index 000000000..97f0584ec --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs @@ -0,0 +1,66 @@ +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { + use core::arch::asm; + use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, zisklib::FCALL_SECP256K1_ECDSA_VERIFY_ID}; + } +} + +/// Hints the ECDSA recovery computation over the `secp256k1` curve. +/// +/// Given the public key `PK`, a message hash `z`, and signature components `(r, s)`, +/// this function hints a curve point `P` such that: +/// +/// ```text +/// P = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]PK +/// ``` +/// +/// ### Parameters +/// +/// - `pk_value`: The public key `PK = (x, y)`, +/// represented as 8 `u64` limbs in little-endian order: `[x₀, x₁, x₂, x₃, y₀, y₁, y₂, y₃]` +/// - `z_value`: The message hash (prehash), represented as 4 `u64` limbs in little-endian order +/// - `r_value`: The signature `r` component, represented as 4 `u64` limbs in little-endian order +/// - `s_value`: The signature `s` component, represented as 4 `u64` limbs in little-endian order +/// +/// ### Returns +/// +/// The curve point `P = (x, y)` as 8 `u64` limbs in little-endian order: +/// `[x₀, x₁, x₂, x₃, y₀, y₁, y₂, y₃]` +/// +/// ### Safety +/// +/// The caller must ensure that all input pointers (`pk_value`, `z_value`, `r_value`, `s_value`) are +/// valid and aligned to an 8-byte boundary. +/// +/// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness +/// of the result. It is the caller's responsibility to ensure it. +#[allow(unused_variables)] +pub fn fcall_secp256k1_ecdsa_verify( + pk_value: &[u64; 8], + z_value: &[u64; 4], + r_value: &[u64; 4], + s_value: &[u64; 4], +) -> [u64; 8] { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!(); + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + ziskos_fcall_param!(pk_value, 8); + ziskos_fcall_param!(z_value, 4); + ziskos_fcall_param!(r_value, 4); + ziskos_fcall_param!(s_value, 4); + ziskos_fcall!(FCALL_SECP256K1_ECDSA_VERIFY_ID); + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] + } +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/mod.rs index 9bb017229..36d3d36d9 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/mod.rs @@ -1,5 +1,7 @@ +mod ecdsa; mod r#fn; mod fp; +pub use ecdsa::*; pub use fp::*; pub use r#fn::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp.rs index 948f811cb..2e6d5a1eb 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp.rs @@ -1,5 +1,6 @@ use lazy_static::lazy_static; use num_bigint::BigUint; +use num_traits::Zero; use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; @@ -47,6 +48,9 @@ pub fn bn254_fp_sub(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] { pub fn bn254_fp_neg(a: &[u64; 4]) -> [u64; 4] { let a_big = biguint_from_u64_digits(a); + if a_big.is_zero() { + return [0u64; 4]; + } let neg = &*P - a_big; n_u64_digits_from_biguint(&neg) } diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs index 660d55ed6..1697f91db 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs @@ -4,7 +4,8 @@ use crate::zisklib::{ FCALL_BLS12_381_TWIST_ADD_LINE_COEFFS_ID, FCALL_BLS12_381_TWIST_DBL_LINE_COEFFS_ID, FCALL_BN254_FP2_INV_ID, FCALL_BN254_FP_INV_ID, FCALL_BN254_TWIST_ADD_LINE_COEFFS_ID, FCALL_BN254_TWIST_DBL_LINE_COEFFS_ID, FCALL_MSB_POS_256_ID, FCALL_MSB_POS_384_ID, - FCALL_SECP256K1_FN_INV_ID, FCALL_SECP256K1_FP_INV_ID, FCALL_SECP256K1_FP_SQRT_ID, + FCALL_SECP256K1_ECDSA_VERIFY_ID, FCALL_SECP256K1_FN_INV_ID, FCALL_SECP256K1_FP_INV_ID, + FCALL_SECP256K1_FP_SQRT_ID, }; use super::{ @@ -17,7 +18,7 @@ pub fn fcall_proxy(id: u64, params: &[u64], results: &mut [u64]) -> i64 { FCALL_SECP256K1_FN_INV_ID => fcall_secp256k1_fn_inv(params, results), FCALL_SECP256K1_FP_INV_ID => fcall_secp256k1_fp_inv(params, results), FCALL_SECP256K1_FP_SQRT_ID => fcall_secp256k1_fp_sqrt(params, results), - FCALL_MSB_POS_256_ID => fcall_msb_pos_256(params, results), + FCALL_SECP256K1_ECDSA_VERIFY_ID => fcall_secp256k1_ecdsa_verify(params, results), FCALL_BN254_FP_INV_ID => fcall_bn254_fp_inv(params, results), FCALL_BN254_FP2_INV_ID => fcall_bn254_fp2_inv(params, results), FCALL_BN254_TWIST_ADD_LINE_COEFFS_ID => fcall_bn254_twist_add_line_coeffs(params, results), @@ -31,11 +32,12 @@ pub fn fcall_proxy(id: u64, params: &[u64], results: &mut [u64]) -> i64 { FCALL_BLS12_381_TWIST_DBL_LINE_COEFFS_ID => { fcall_bls12_381_twist_dbl_line_coeffs(params, results) } + FCALL_BLS12_381_FP2_SQRT_ID => fcall_bls12_381_fp2_sqrt(params, results), + FCALL_MSB_POS_256_ID => fcall_msb_pos_256(params, results), FCALL_MSB_POS_384_ID => fcall_msb_pos_384(params, results), FCALL_BIG_INT256_DIV_ID => fcall_big_int256_div(params, results), FCALL_BIG_INT_DIV_ID => fcall_big_int_div(params, results), FCALL_BIN_DECOMP_ID => fcall_bin_decomp(params, results), - FCALL_BLS12_381_FP2_SQRT_ID => fcall_bls12_381_fp2_sqrt(params, results), _ => panic!("Unsupported fcall ID {id}"), } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/constants.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/constants.rs index e627ef644..0b298eb52 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/constants.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/constants.rs @@ -28,3 +28,16 @@ lazy_static! { ) .unwrap(); } + +pub const IDENTITY: [u64; 8] = [0u64; 8]; + +pub const G: [u64; 8] = [ + 0x59F2815B16F81798, + 0x029BFCDB2DCE28D9, + 0x55A06295CE870B07, + 0x79BE667EF9DCBBAC, + 0x9C47D08FFB10D4B8, + 0xFD17B448A6855419, + 0x5DA4FBFC0E1108A8, + 0x483ADA7726A3C465, +]; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs new file mode 100644 index 000000000..bd3aa81bd --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs @@ -0,0 +1,296 @@ +use num_traits::Zero; + +use crate::zisklib::fcalls_impl::utils::{ + biguint_from_u64, biguint_from_u64_digits, n_u64_digits_from_biguint, +}; + +use super::constants::{G, IDENTITY, N, P}; + +pub fn fcall_secp256k1_ecdsa_verify(params: &[u64], results: &mut [u64]) -> i64 { + // Get the input + let pk: &[u64; 8] = ¶ms[0..8].try_into().unwrap(); + let z: &[u64; 4] = ¶ms[8..12].try_into().unwrap(); + let r: &[u64; 4] = ¶ms[12..16].try_into().unwrap(); + let s: &[u64; 4] = ¶ms[16..20].try_into().unwrap(); + + // Get the public key + let pk = secp256k1_ecdsa_verify(pk, z, r, s); + + // Store the result + results[0..8].copy_from_slice(&pk); + + 8 +} + +fn secp256k1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u64; 4]) -> [u64; 8] { + // Verifies the signature (r, s) over the message hash z using the public key pk + // 1. Compute s_inv = s⁻¹ mod n + // 2. Compute u1 = z·s_inv mod n + // 3. Compute u2 = r·s_inv mod n + // 4. Compute the curve point p = u1·G + u2·PK + let s_inv = secp256k1_fn_inv(s); + let u1 = secp256k1_fn_mul(z, &s_inv); + let u2 = secp256k1_fn_mul(r, &s_inv); + secp256k1_curve_dbl_scalar_mul(&u1, &G, &u2, pk) +} + +fn secp256k1_fn_mul(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let b_big = biguint_from_u64_digits(b); + let product = (a_big * b_big) % &*N; + n_u64_digits_from_biguint(&product) +} + +fn secp256k1_fn_inv(a: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let inv = a_big.modinv(&N); + match inv { + Some(inverse) => n_u64_digits_from_biguint(&inverse), + None => panic!("Inverse does not exist"), + } +} + +fn secp256k1_fp_add(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let b_big = biguint_from_u64_digits(b); + let sum = (a_big + b_big) % &*P; + n_u64_digits_from_biguint(&sum) +} + +fn secp256k1_fp_sub(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let b_big = biguint_from_u64_digits(b); + let diff = if a_big >= b_big { a_big - b_big } else { (a_big + &*P) - b_big }; + n_u64_digits_from_biguint(&diff) +} + +fn secp256k1_fp_scalar_mul(a: &[u64; 4], scalar: u64) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let scalar_big = biguint_from_u64(scalar); + let product = (a_big * scalar_big) % &*P; + n_u64_digits_from_biguint(&product) +} + +fn secp256k1_fp_mul(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let b_big = biguint_from_u64_digits(b); + let product = (a_big * b_big) % &*P; + n_u64_digits_from_biguint(&product) +} + +fn secp256k1_fp_square(a: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let square = (a_big.clone() * a_big) % &*P; + n_u64_digits_from_biguint(&square) +} + +fn secp256k1_fp_inv(a: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let inv = a_big.modinv(&P); + match inv { + Some(inverse) => n_u64_digits_from_biguint(&inverse), + None => panic!("Inverse does not exist"), + } +} + +fn secp256k1_curve_add(p: &[u64; 8], q: &[u64; 8]) -> [u64; 8] { + let x1: &[u64; 4] = &p[0..4].try_into().unwrap(); + let y1: &[u64; 4] = &p[4..8].try_into().unwrap(); + let x2: &[u64; 4] = &q[0..4].try_into().unwrap(); + let y2: &[u64; 4] = &q[4..8].try_into().unwrap(); + + if x1 == x2 { + if y1 == y2 { + return secp256k1_curve_dbl(p); + } else { + return IDENTITY; + } + } + + if p == &IDENTITY { + return *q; + } else if q == &IDENTITY { + return *p; + } + + let lambda = { + let y2_minus_y1 = secp256k1_fp_sub(y2, y1); + let x2_minus_x1 = secp256k1_fp_sub(x2, x1); + let x2_minus_x1_inv = secp256k1_fp_inv(&x2_minus_x1); + secp256k1_fp_mul(&y2_minus_y1, &x2_minus_x1_inv) + }; + + let x3 = { + let lambda_sq = secp256k1_fp_square(&lambda); + let x1_plus_x2 = secp256k1_fp_add(x1, x2); + secp256k1_fp_sub(&lambda_sq, &x1_plus_x2) + }; + + let y3 = { + let lambda_x1_minus_x3 = { + let x1_minus_x3 = secp256k1_fp_sub(x1, &x3); + secp256k1_fp_mul(&lambda, &x1_minus_x3) + }; + secp256k1_fp_sub(&lambda_x1_minus_x3, y1) + }; + + let mut result = [0u64; 8]; + result[0..4].copy_from_slice(&x3); + result[4..8].copy_from_slice(&y3); + result +} + +fn secp256k1_curve_dbl(p: &[u64; 8]) -> [u64; 8] { + if p == &IDENTITY { + return *p; + } + + let x: &[u64; 4] = &p[0..4].try_into().unwrap(); + let y: &[u64; 4] = &p[4..8].try_into().unwrap(); + + let lambda = { + let three_x1_sq = { + let x1_sq = secp256k1_fp_square(x); + secp256k1_fp_scalar_mul(&x1_sq, 3) + }; + + let two_y1 = secp256k1_fp_scalar_mul(y, 2); + let two_y1_inv = secp256k1_fp_inv(&two_y1); + + secp256k1_fp_mul(&three_x1_sq, &two_y1_inv) + }; + + let x3 = { + let lambda_sq = secp256k1_fp_square(&lambda); + let two_x1 = secp256k1_fp_scalar_mul(x, 2); + secp256k1_fp_sub(&lambda_sq, &two_x1) + }; + + let y3 = { + let lambda_x1_minus_x3 = { + let x1_minus_x3 = secp256k1_fp_sub(x, &x3); + secp256k1_fp_mul(&lambda, &x1_minus_x3) + }; + secp256k1_fp_sub(&lambda_x1_minus_x3, y) + }; + + let mut result = [0u64; 8]; + result[0..4].copy_from_slice(&x3); + result[4..8].copy_from_slice(&y3); + result +} + +fn secp256k1_curve_dbl_scalar_mul( + k1: &[u64; 4], + p1: &[u64; 8], + k2: &[u64; 4], + p2: &[u64; 8], +) -> [u64; 8] { + let mut r = IDENTITY; + for i in (0..256).rev() { + r = secp256k1_curve_dbl(&r); + + let k1_bit = (k1[i / 64] >> (i % 64)) & 1; + let k2_bit = (k2[i / 64] >> (i % 64)) & 1; + + if k1_bit == 1 { + r = secp256k1_curve_add(&r, p1); + } + if k2_bit == 1 { + r = secp256k1_curve_add(&r, p2); + } + } + + r +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_dbl_scalar_mul() { + // 0 * IDENTITY + 0 * IDENTITY = IDENTITY + let k1 = [0u64; 4]; + let p1 = IDENTITY; + let k2 = [0u64; 4]; + let p2 = IDENTITY; + + let result = secp256k1_curve_dbl_scalar_mul(&k1, &p1, &k2, &p2); + assert_eq!(result, IDENTITY); + + // 1 * G + 0 * IDENTITY = G + let k1 = [1u64, 0, 0, 0]; + let p1 = G; + let k2 = [0u64; 4]; + let p2 = IDENTITY; + + let result = secp256k1_curve_dbl_scalar_mul(&k1, &p1, &k2, &p2); + assert_eq!(result, G); + + // 0 * IDENTITY + 1 * G = G + let k1 = [0u64; 4]; + let p1 = IDENTITY; + let k2 = [1u64, 0, 0, 0]; + let p2 = G; + + let result = secp256k1_curve_dbl_scalar_mul(&k1, &p1, &k2, &p2); + assert_eq!(result, G); + + // 2 * G + 3 * G = 5 * G + let k1 = [2u64, 0, 0, 0]; + let p1 = G; + let k2 = [3u64, 0, 0, 0]; + let p2 = G; + + let result = secp256k1_curve_dbl_scalar_mul(&k1, &p1, &k2, &p2); + let expected = [ + 0xcba8d569b240efe4, + 0xe88b84bddc619ab7, + 0x55b4a7250a5c5128, + 0x2f8bde4d1a072093, + 0xdca87d3aa6ac62d6, + 0xf788271bab0d6840, + 0xd4dba9dda6c9c426, + 0xd8ac222636e5e3d6, + ]; + assert_eq!(result, expected); + + // Random test + let k1 = [0x761923728d37303, 0x1f0e6f2fa8a32ab5, 0x7bb7458c6ea47f08, 0xe2cf4fd21aef19e1]; + let p1 = [ + 0xd77a8f3f445d2c43, + 0xd8404b226e191e33, + 0x3f542469b3a1f4ce, + 0x73613de6799853d9, + 0x9722df4889803b47, + 0x9055e100179fe79a, + 0xdf46f38d013fda72, + 0xd769a27efc36598c, + ]; + let k2 = [0xe9c44fa1510380c0, 0x16d1daea9be6a28, 0x2a4bb6bbdc0a031e, 0xefda864ae6c22f24]; + let p2 = [ + 0x77fb10949fdba7d6, + 0x84e5d96e491b9daf, + 0x66c77ea552e760cd, + 0x434feb1463e34ff8, + 0x5258fc8877bdff59, + 0x25586ed50053a57f, + 0x55858e1de54a18ac, + 0x3393bec7dd4067f7, + ]; + + let result = secp256k1_curve_dbl_scalar_mul(&k1, &p1, &k2, &p2); + let expected = [ + 0xb0531ccb6c1c9b1, + 0xc7c48529c9569495, + 0x18edf1edb9351c8d, + 0x572d78c95d7f964, + 0x9d41caf8f65f3690, + 0x21eea422b3a37e0a, + 0x1c10371d5a68938c, + 0xcc37bbabaf4204de, + ]; + assert_eq!(result, expected); + } +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs index 908a4459a..0343b8ad6 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/mod.rs @@ -1,9 +1,11 @@ mod constants; +mod ecdsa; mod fn_inv; mod fp_inv; mod fp_sqrt; use constants::*; +pub use ecdsa::*; pub use fn_inv::*; pub use fp_inv::*; pub use fp_sqrt::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/utils.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/utils.rs index a6254459d..2e650707f 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/utils.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/utils.rs @@ -1,6 +1,10 @@ use num_bigint::BigUint; use num_traits::Zero; +pub fn biguint_from_u64(value: u64) -> BigUint { + BigUint::from(value) +} + pub fn biguint_from_u64_digits(limbs: &[u64]) -> BigUint { limbs.iter().rev().fold(BigUint::zero(), |acc, &limb| (acc << 64) + BigUint::from(limb)) } diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index 0bbcb421a..2e61f8905 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -20,19 +20,10 @@ const G_POINT256: SyscallPoint256 = SyscallPoint256 { x: G_X, y: G_Y }; /// Given a x-coordinate `x_bytes` and a parity `y_is_odd`, /// this function decompresses the point on the secp256k1 curve. -pub fn secp256k1_decompress( - x_bytes: &[u8; 32], - y_is_odd: bool, -) -> Result<([u64; 4], [u64; 4]), bool> { - // Convert the x-coordinate from BEu8 to LEu64 - let mut x = [0u64; 4]; - for i in 0..32 { - x[3 - i / 8] |= (x_bytes[i] as u64) << (8 * (7 - (i % 8))); - } - +pub fn secp256k1_decompress(x: &[u64; 4], y_is_odd: bool) -> Result<([u64; 4], [u64; 4]), bool> { // Calculate the y-coordinate of the point: y = sqrt(x³ + 7) - let x_sq = secp256k1_fp_square(&x); - let x_cb = secp256k1_fp_mul(&x_sq, &x); + let x_sq = secp256k1_fp_square(x); + let x_cb = secp256k1_fp_mul(&x_sq, x); let y_sq = secp256k1_fp_add(&x_cb, &E_B); let (y, has_sqrt) = secp256k1_fp_sqrt(&y_sq, y_is_odd as u64); if !has_sqrt { @@ -43,7 +34,7 @@ pub fn secp256k1_decompress( let parity = (y[0] & 1) != 0; assert_eq!(parity, y_is_odd); - Ok((x, y)) + Ok((*x, y)) } /// Converts a non-infinity point `p` on the Secp256k1 curve from projective coordinates to affine coordinates @@ -83,6 +74,20 @@ fn secp256k1_add_non_infinity_points(p1: &mut SyscallPoint256, p2: &SyscallPoint } } +/// Checks whether the given point `p` is on the Secp256k1 curve. +/// It assumes that `p` is not the point at infinity. +pub fn secp256k1_is_on_curve(p: &[u64; 8]) -> bool { + let x: [u64; 4] = p[0..4].try_into().unwrap(); + let y: [u64; 4] = p[4..8].try_into().unwrap(); + + // p in E iff y² == x³ + 7 + let lhs = secp256k1_fp_square(&y); + let mut rhs = secp256k1_fp_square(&x); + rhs = secp256k1_fp_mul(&rhs, &x); + rhs = secp256k1_fp_add(&rhs, &E_B); + eq(&lhs, &rhs) +} + /// Given a non-infinity point `p` and a scalar `k`, computes the scalar multiplication `k·p` /// /// Note: There are no (non-infinity) points of order 2 in Secp256k1. @@ -643,9 +648,11 @@ pub unsafe extern "C" fn secp256k1_decompress_c( y_is_odd: u8, out_ptr: *mut u64, ) -> u8 { + // Convert the x-coordinate from BEu8 to LEu64 let x_bytes: &[u8; 32] = &*(x_bytes_ptr as *const [u8; 32]); + let x = bytes_be_to_u64_le(x_bytes); - let (x, y) = match secp256k1_decompress(x_bytes, y_is_odd != 0) { + let (x, y) = match secp256k1_decompress(&x, y_is_odd != 0) { Ok((x, y)) => (x, y), Err(_) => return 0, }; @@ -712,3 +719,23 @@ pub unsafe extern "C" fn secp256k1_double_scalar_mul_with_g_c( } } } + +// Helper to convert 32 big-endian bytes to [u64; 4] little-endian limbs +#[inline] +fn bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { + let mut result = [0u64; 4]; + for (i, r) in result.iter_mut().enumerate() { + let offset = 24 - i * 8; + *r = u64::from_be_bytes([ + bytes[offset], + bytes[offset + 1], + bytes[offset + 2], + bytes[offset + 3], + bytes[offset + 4], + bytes[offset + 5], + bytes[offset + 6], + bytes[offset + 7], + ]); + } + result +} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs index fe91fbeb2..e44255fbf 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs @@ -2,84 +2,47 @@ use crate::{ syscalls::{ syscall_secp256k1_add, syscall_secp256k1_dbl, SyscallPoint256, SyscallSecp256k1AddParams, }, - zisklib::{eq, fcall_msb_pos_256, is_one, ONE_256, TWO_256, ZERO_256}, + zisklib::{ + eq, fcall_msb_pos_256, fcall_secp256k1_ecdsa_verify, is_one, ONE_256, TWO_256, ZERO_256, + }, }; use super::{ constants::{E_B, G, G_X, G_Y, IDENTITY_X, IDENTITY_Y}, + curve::{ + secp256k1_decompress, secp256k1_double_scalar_mul_with_g, secp256k1_is_on_curve, + secp256k1_triple_scalar_mul_with_g, + }, field::{ secp256k1_fp_add, secp256k1_fp_inv, secp256k1_fp_mul, secp256k1_fp_sqrt, secp256k1_fp_square, }, - scalar::{secp256k1_fn_inv, secp256k1_fn_mul, secp256k1_fn_reduce, secp256k1_fn_sub}, - secp256k1_decompress, secp256k1_double_scalar_mul_with_g, secp256k1_triple_scalar_mul_with_g, + scalar::{ + secp256k1_fn_inv, secp256k1_fn_mul, secp256k1_fn_neg, secp256k1_fn_reduce, secp256k1_fn_sub, + }, }; +/// Verifies the signature (r, s) over the message hash z using the public key pk +/// Returns true if the signature is valid, false otherwise pub fn secp256k1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u64; 4]) -> bool { - // Ecdsa verification computes (x1, y1) = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]pk - // and checks that r ≡ x1 (mod n) + // Ecdsa verification computes P = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]PK + // We can equivalently hint p, check it's correct and verify that + // [z]G + [r]pk + [-s]P == 𝒪, + // saving us from expensive fn arithmetic - // TODO: We can equivalently hint y1 and verify that 𝒪 == [z]G + [r]pk + [-s](x1, y1) - // saving us from fn arithmetic entirely + // Hint the result + let p = fcall_secp256k1_ecdsa_verify(pk, z, r, s); - // The recovery algorithm computes pk = [-r⁻¹·z (mod n)]G + [r⁻¹·s (mod n)]R - // Equivalently, we can verify that [z]G + [r]pk + [-s]R == 𝒪 - let s_inv = secp256k1_fn_inv(s); - let u1 = secp256k1_fn_mul(z, &s_inv); - let u2 = secp256k1_fn_mul(r, &s_inv); + // Check the recovered point is valid + assert!(secp256k1_is_on_curve(&p)); // Note: Identity point would be raised here - match secp256k1_double_scalar_mul_with_g(&u1, &u2, pk) { - None => false, - Some(res) => eq(&secp256k1_fn_reduce(&[res[0], res[1], res[2], res[3]]), r), - } + // Check that [z]G + [r]pk + [-s]P == 𝒪 + let neg_s = secp256k1_fn_neg(s); + secp256k1_triple_scalar_mul_with_g(z, r, &neg_s, pk, &p).is_none().then_some(()).is_some() } // ==================== C FFI Functions ==================== -// TODO -// /// # Safety -// /// - `pk_ptr` must point to 64 bytes (public key: x[32] || y[32], big-endian) -// /// - `z_ptr` must point to 32 bytes (message hash, big-endian) -// /// - `r_ptr` must point to 32 bytes (signature r, big-endian) -// /// - `s_ptr` must point to 32 bytes (signature s, big-endian) -// /// -// /// Returns true if signature is valid, false otherwise -// #[no_mangle] -// pub unsafe extern "C" fn secp256k1_ecdsa_recover_c( -// h_ptr: *const u8, // Message hash -// r_ptr: *const u8, // Signature r -// s_ptr: *const u8, // Signature s -// rec_id: u8, // Recovery ID -// ) -> bool { -// // Helper to convert 32 big-endian bytes to [u64; 4] little-endian limbs -// #[inline] -// fn bytes_be_to_u64_le(bytes: *const u8) -> [u64; 4] { -// let mut result = [0u64; 4]; -// for i in 0..4 { -// let offset = 24 - i * 8; -// result[i] = unsafe { -// u64::from_be_bytes([ -// *bytes.add(offset), -// *bytes.add(offset + 1), -// *bytes.add(offset + 2), -// *bytes.add(offset + 3), -// *bytes.add(offset + 4), -// *bytes.add(offset + 5), -// *bytes.add(offset + 6), -// *bytes.add(offset + 7), -// ]) -// }; -// } -// result -// } - -// let h = bytes_be_to_u64_le(h_ptr); -// let r = bytes_be_to_u64_le(r_ptr); -// let s = bytes_be_to_u64_le(s_ptr); - -// // secp256k1_ecdsa_verify(&pk, &z, &r, &s) -// } - /// # Safety /// - `pk_ptr` must point to 8 u64s /// - `z_ptr`, `r_ptr`, `s_ptr` must point to 4 u64s each From 404222ba14c2506ab230eb1451ed288d9a3fdbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:31:51 +0000 Subject: [PATCH 198/782] Fixing naming --- .../src/zisklib/fcalls_impl/secp256k1/ecdsa.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs index bd3aa81bd..2c141bb52 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs @@ -13,21 +13,21 @@ pub fn fcall_secp256k1_ecdsa_verify(params: &[u64], results: &mut [u64]) -> i64 let r: &[u64; 4] = ¶ms[12..16].try_into().unwrap(); let s: &[u64; 4] = ¶ms[16..20].try_into().unwrap(); - // Get the public key - let pk = secp256k1_ecdsa_verify(pk, z, r, s); + // Get the curve point P + let p = secp256k1_ecdsa_verify(pk, z, r, s); // Store the result - results[0..8].copy_from_slice(&pk); + results[0..8].copy_from_slice(&p); 8 } fn secp256k1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u64; 4]) -> [u64; 8] { - // Verifies the signature (r, s) over the message hash z using the public key pk - // 1. Compute s_inv = s⁻¹ mod n - // 2. Compute u1 = z·s_inv mod n - // 3. Compute u2 = r·s_inv mod n - // 4. Compute the curve point p = u1·G + u2·PK + // Given the public key pk and the signature (r, s) over the message hash z: + // 1. Computes s_inv = s⁻¹ mod n + // 2. Computes u1 = z·s_inv mod n + // 3. Computes u2 = r·s_inv mod n + // 4. Computes and returns the curve point p = u1·G + u2·PK let s_inv = secp256k1_fn_inv(s); let u1 = secp256k1_fn_mul(z, &s_inv); let u2 = secp256k1_fn_mul(r, &s_inv); From 36cda7a6ab2a3e2033620a13d10a90f8edc6dec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:33:32 +0000 Subject: [PATCH 199/782] clippy --- .../entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs index 97f0584ec..4695646c2 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs @@ -9,7 +9,7 @@ cfg_if! { /// Hints the ECDSA recovery computation over the `secp256k1` curve. /// -/// Given the public key `PK`, a message hash `z`, and signature components `(r, s)`, +/// Given the public key `PK`, a message hash `z`, and signature components `(r, s)`, /// this function hints a curve point `P` such that: /// /// ```text @@ -18,8 +18,8 @@ cfg_if! { /// /// ### Parameters /// -/// - `pk_value`: The public key `PK = (x, y)`, -/// represented as 8 `u64` limbs in little-endian order: `[x₀, x₁, x₂, x₃, y₀, y₁, y₂, y₃]` +/// - `pk_value`: The public key `PK = (x, y)`, +/// represented as 8 `u64` limbs in little-endian order: `[x₀, x₁, x₂, x₃, y₀, y₁, y₂, y₃]` /// - `z_value`: The message hash (prehash), represented as 4 `u64` limbs in little-endian order /// - `r_value`: The signature `r` component, represented as 4 `u64` limbs in little-endian order /// - `s_value`: The signature `s` component, represented as 4 `u64` limbs in little-endian order @@ -31,9 +31,9 @@ cfg_if! { /// /// ### Safety /// -/// The caller must ensure that all input pointers (`pk_value`, `z_value`, `r_value`, `s_value`) are +/// The caller must ensure that all input pointers (`pk_value`, `z_value`, `r_value`, `s_value`) are /// valid and aligned to an 8-byte boundary. -/// +/// /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. #[allow(unused_variables)] From b26eff2d37b65f8c7931d9d165214fc93cfd8474 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 8 Jan 2026 12:25:59 +0100 Subject: [PATCH 200/782] Fix FCall precompile results address calculation --- core/src/zisk_rom_2_asm.rs | 22 ++++++---------------- emulator-asm/src/emu.c | 5 +++++ emulator-asm/src/main.c | 8 ++++---- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index eb7e6dfe7..6e4b772a9 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -502,7 +502,7 @@ impl ZiskRom2Asm { boc: "/* ".to_string(), eoc: " */".to_string(), min_program_pc: rom.min_program_pc, - precompile_results, + precompile_results: true, ..Default::default() }; @@ -8112,17 +8112,12 @@ impl ZiskRom2Asm { PRECOMPILE_BUFFER_SIZE_U64_MASK, ctx.comment_str("address %= buffer size") ); - *code += &format!( - "\tadd {}, {} {}\n", - REG_ADDRESS, - REG_AUX, - ctx.comment_str("address += precompile_results_address") - ); // Copy the result size (first u64 value) and store it in register B *code += &format!( - "\tmov {}, [{}] {}\n", + "\tmov {}, [{} + {}*8] {}\n", REG_B, + REG_AUX, REG_ADDRESS, ctx.comment_str("b = precompile_results[0]") ); @@ -8165,19 +8160,14 @@ impl ZiskRom2Asm { PRECOMPILE_BUFFER_SIZE_U64_MASK, ctx.comment_str("address %= buffer size") ); - *code += &format!( - "\tadd {}, {} {}\n", - REG_ADDRESS, - REG_AUX, - ctx.comment_str("address += precompile_results_address") - ); // Copy value from precompile_results to fcall[result_data + REG_A] *code += &format!( - "\tmov {}, [{}] {}\n", + "\tmov {}, [{} + {}*8] {}\n", REG_VALUE, + REG_AUX, REG_ADDRESS, - ctx.comment_str("value = precompile_results[]") + ctx.comment_str("value = precompile_results[a]") ); *code += &format!( "\tmov [{} + {}*8 + {}*8], {} {}\n", diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index 2f6033c92..e73f1e317 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -347,12 +347,17 @@ void precompile_cache_cleanup(void) precompile_cache_loading = false; } +uint64_t total_precompile_cache_size = 0; +uint64_t total_precompile_cache_counter = 0; void precompile_cache_store( uint8_t* data, uint64_t size) { assert(precompile_file != NULL); assert(precompile_cache_storing == true); fwrite(data, 1, size, precompile_file); fflush(precompile_file); + total_precompile_cache_size += size; + total_precompile_cache_counter++; + //printf("precompile_cache_store() Stored %lu bytes at pos=%lu total_precompile_cache_size=%lu total_precompile_cache_counter=%lu\n", size, ftell(precompile_file), total_precompile_cache_size, total_precompile_cache_counter); } void precompile_cache_load( uint8_t* data, uint64_t size) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 0e7b4d047..946718305 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -1908,16 +1908,16 @@ typedef enum { PrecompileReadMode_Prefixed } PrecompileReadMode; -//PrecompileReadMode precompile_read_mode = PrecompileReadMode_NoPrefix; -PrecompileReadMode precompile_read_mode = PrecompileReadMode_Prefixed; +PrecompileReadMode precompile_read_mode = PrecompileReadMode_NoPrefix; +//PrecompileReadMode precompile_read_mode = PrecompileReadMode_Prefixed; typedef enum { PrecompileWriteMode_Full, PrecompileWriteMode_OnePrecAtATime } PrecompileWriteMode; -//PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_Full; -PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_OnePrecAtATime; +PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_Full; +//PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_OnePrecAtATime; //#define PRECOMPILE_FIXED_SIZE 25 // Keccak-f state size in u64s #define PRECOMPILE_FIXED_SIZE 4 // SHA-256 state size in u64s From 0a9fd4a175eb9d5451102f5e21fcdee05a44a2c0 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 8 Jan 2026 14:12:13 +0100 Subject: [PATCH 201/782] Fix modexp hints conversion to results --- ziskos/entrypoint/src/syscalls/add256.rs | 2 +- .../entrypoint/src/zisklib/fcalls/big_int_div.rs | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ziskos/entrypoint/src/syscalls/add256.rs b/ziskos/entrypoint/src/syscalls/add256.rs index 476b05766..2732c2006 100644 --- a/ziskos/entrypoint/src/syscalls/add256.rs +++ b/ziskos/entrypoint/src/syscalls/add256.rs @@ -41,8 +41,8 @@ pub extern "C" fn syscall_add256( { hints.extend_from_slice(params.c); } + cout } - unreachable!(); #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall_ret_u64!(0x811, params) } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs index b4f408282..60b425bd7 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs @@ -28,16 +28,20 @@ pub fn fcall_division( ) -> (usize, usize) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { - big_int_div_into(a_value, b_value, &mut quo.to_vec(), &mut rem.to_vec()); - let len_quo = quo.len(); - let len_rem = rem.len(); + let mut quo_vector: Vec = Vec::new(); + let mut rem_vector: Vec = Vec::new(); + big_int_div_into(a_value, b_value, &mut quo_vector, &mut rem_vector); + quo[..quo_vector.len()].copy_from_slice(&quo_vector); + rem[..rem_vector.len()].copy_from_slice(&rem_vector); + let len_quo = quo_vector.len(); + let len_rem = rem_vector.len(); #[cfg(feature = "hints")] { hints.push(len_quo as u64 + len_rem as u64 + 2); hints.push(len_quo as u64); - hints.extend_from_slice(quo); + hints.extend_from_slice(&quo_vector); hints.push(len_rem as u64); - hints.extend_from_slice(rem); + hints.extend_from_slice(&rem_vector); } (len_quo, len_rem) From d21edb30bc44490a67450fc6db7747f52a164adb Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 8 Jan 2026 14:19:40 +0100 Subject: [PATCH 202/782] Undo hardcoded activation of precompile results in assembly --- core/src/zisk_rom_2_asm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 6e4b772a9..30f3ab6c1 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -502,7 +502,7 @@ impl ZiskRom2Asm { boc: "/* ".to_string(), eoc: " */".to_string(), min_program_pc: rom.min_program_pc, - precompile_results: true, + precompile_results, ..Default::default() }; From f39b06b375add516f8aede07a71cee2926edbde3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:20:39 +0000 Subject: [PATCH 203/782] Comment removal --- core/src/helpers.rs | 2 -- ziskclib/src/helpers.rs | 3 --- 2 files changed, 5 deletions(-) diff --git a/core/src/helpers.rs b/core/src/helpers.rs index 2fe1f4fb5..76882c1e4 100644 --- a/core/src/helpers.rs +++ b/core/src/helpers.rs @@ -1,7 +1,5 @@ use sha2::compress256; -// TODO: Update when new sha2 version is available - #[allow(deprecated)] use sha2::digest::generic_array::{typenum::U64, GenericArray}; diff --git a/ziskclib/src/helpers.rs b/ziskclib/src/helpers.rs index cc706efb9..76882c1e4 100644 --- a/ziskclib/src/helpers.rs +++ b/ziskclib/src/helpers.rs @@ -1,13 +1,10 @@ use sha2::compress256; -// TODO: Update when new sha2 version is available - #[allow(deprecated)] use sha2::digest::generic_array::{typenum::U64, GenericArray}; #[allow(deprecated)] pub fn sha256f(state: &mut [u64; 4], input: &[u64; 8]) { - // Convert both the state and the input to appropriate types let state_u32: &mut [u32; 8] = unsafe { &mut *(state.as_mut_ptr() as *mut [u32; 8]) }; let input_u8: &[GenericArray; 1] = unsafe { &*(input.as_ptr() as *const [GenericArray; 1]) }; From 3735706ab0611091f5180c0ce2835cab2f566448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Fri, 9 Jan 2026 11:33:26 +0000 Subject: [PATCH 204/782] Minor changes --- lib-float/c/lib/libziskfloat.a | Bin 339576 -> 332576 bytes lib-float/c/lib/ziskfloat.elf | Bin 112096 -> 110208 bytes .../src/zisklib/fcalls/secp256k1/fn.rs | 5 +- .../src/zisklib/fcalls/secp256k1/fp.rs | 24 -------- .../fcalls_impl/bls12_381/constants.rs | 57 ++++++++++++++++++ .../zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs | 43 +------------ .../zisklib/fcalls_impl/bls12_381/fp_inv.rs | 8 +-- .../zisklib/fcalls_impl/bls12_381/fp_sqrt.rs | 26 +------- .../src/zisklib/fcalls_impl/bls12_381/mod.rs | 2 + .../zisklib/fcalls_impl/bn254/constants.rs | 10 +++ .../src/zisklib/fcalls_impl/bn254/fp.rs | 8 +-- .../src/zisklib/fcalls_impl/bn254/fp2.rs | 8 +-- .../src/zisklib/fcalls_impl/bn254/mod.rs | 2 + 13 files changed, 84 insertions(+), 109 deletions(-) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/constants.rs create mode 100644 ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/constants.rs diff --git a/lib-float/c/lib/libziskfloat.a b/lib-float/c/lib/libziskfloat.a index 194d15c63eb1fa75fd86c5d85f3c17c9bf80b122..411c06a9ce07a54e3d63ea741fc5de18de66a672 100644 GIT binary patch literal 332576 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 literal 339576 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 K_t=|~d;EWBklq~t diff --git a/lib-float/c/lib/ziskfloat.elf b/lib-float/c/lib/ziskfloat.elf index f3987097e69a68cb506662fdca16fb3eebf4d761..640341da0de3ada5097691015fa716fc5ae423d4 100755 GIT binary patch delta 16607 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 fOX!?eVR)#wMo|7ElIPh(MMb$N-2s{45yh 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 I&PFExFL4AL9smFU diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs index cba8908ab..1b27e77c9 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs @@ -49,7 +49,10 @@ pub fn fcall_secp256k1_fn_inv( } #[allow(unused_variables)] -pub fn fcall2_secp256k1_fn_inv(p_value: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) { +pub fn fcall_secp256k1_fn_inv_in_place( + p_value: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { let mut result: [u64; 4] = [0; 4]; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs index 34e920187..6d156ae12 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs @@ -121,27 +121,3 @@ pub fn fcall_secp256k1_fp_sqrt( ] } } - -#[allow(unused_variables)] -pub fn fcall2_secp256k1_fp_sqrt( - p_value: &[u64; 4], - parity: u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - { - let mut result: [u64; 5] = [0; 5]; - secp256k1_fp_sqrt(p_value, parity, &mut result); - #[cfg(feature = "hints")] - { - hints.push(result.len() as u64); - hints.extend_from_slice(&result); - } - } - #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] - { - ziskos_fcall_param!(p_value, 4); - ziskos_fcall_param!(parity, 1); - ziskos_fcall!(FCALL_SECP256K1_FP_SQRT_ID); - } -} diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/constants.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/constants.rs new file mode 100644 index 000000000..6f016f9d8 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/constants.rs @@ -0,0 +1,57 @@ +use lazy_static::lazy_static; +use num_bigint::BigUint; + +lazy_static! { + pub(crate) static ref P: BigUint = BigUint::parse_bytes( + b"1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", + 16 + ) + .unwrap(); + + pub static ref P_HALF: BigUint = BigUint::parse_bytes( + b"d0088f51cbff34d258dd3db21a5d66bb23ba5c279c2895fb39869507b587b120f55ffff58a9ffffdcff7fffffffd555", + 16 + ) + .unwrap(); + + pub static ref P_DIV_4: BigUint = BigUint::parse_bytes( + b"680447a8e5ff9a692c6e9ed90d2eb35d91dd2e13ce144afd9cc34a83dac3d8907aaffffac54ffffee7fbfffffffeaab", + 16 + ) + .unwrap(); + + pub static ref P_MINUS_3_DIV_4: BigUint = BigUint::parse_bytes( + b"680447A8E5FF9A692C6E9ED90D2EB35D91DD2E13CE144AFD9CC34A83DAC3D8907AAFFFFAC54FFFFEE7FBFFFFFFFEAAA", + 16 + ) + .unwrap(); + + pub static ref P_MINUS_1_DIV_2: BigUint = BigUint::parse_bytes( + b"D0088F51CBFF34D258DD3DB21A5D66BB23BA5C279C2895FB39869507B587B120F55FFFF58A9FFFFDCFF7FFFFFFFD555", + 16 + ) + .unwrap(); + + pub static ref NQR_FP: BigUint = BigUint::from(2u64); // First non-quadratic residue in Fp +} + +pub const ONE: [u64; 12] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + +pub const P_MINUS_ONE: [u64; 12] = [ + 0xB9FEFFFFFFFFAAAA, + 0x1EABFFFEB153FFFF, + 0x6730D2A0F6B0F624, + 0x64774B84F38512BF, + 0x4B1BA7B6434BACD7, + 0x1A0111EA397FE69A, + 0, + 0, + 0, + 0, + 0, + 0, +]; + +pub const I: [u64; 12] = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]; // 0 + 1*u + +pub const NQR_FP2: [u64; 12] = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]; // 1 + 1*u, a known non-quadratic residue in Fp2 diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs index 8fb593f13..8181583d3 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs @@ -7,46 +7,9 @@ use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, u64_digits_fro use super::{ fp2_inv::{bls12_381_fp2_mul, bls12_381_fp2_square}, fp_inv::{bls12_381_fp_add, bls12_381_fp_neg}, + I, NQR_FP2, ONE, P_MINUS_1_DIV_2, P_MINUS_3_DIV_4, P_MINUS_ONE, }; -const ONE: [u64; 12] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; -const P_MINUS_ONE: [u64; 12] = [ - 0xB9FEFFFFFFFFAAAA, - 0x1EABFFFEB153FFFF, - 0x6730D2A0F6B0F624, - 0x64774B84F38512BF, - 0x4B1BA7B6434BACD7, - 0x1A0111EA397FE69A, - 0, - 0, - 0, - 0, - 0, - 0, -]; -const I: [u64; 12] = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]; // 0 + 1*u -const NQR: [u64; 12] = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]; // 1 + 1*u, a known non-quadratic residue in Fp2 - -lazy_static! { - pub(crate) static ref P: BigUint = BigUint::parse_bytes( - b"1A0111EA397FE69A4B1BA7B6434BACD764774B84F38512BF6730D2A0F6B0F6241EABFFFEB153FFFFB9FEFFFFFFFFAAAB", - 16 - ) - .unwrap(); - - pub static ref P_MINUS_3_DIV_4: BigUint = BigUint::parse_bytes( - b"680447A8E5FF9A692C6E9ED90D2EB35D91DD2E13CE144AFD9CC34A83DAC3D8907AAFFFFAC54FFFFEE7FBFFFFFFFEAAA", - 16 - ) - .unwrap(); - - pub static ref P_MINUS_1_DIV_2: BigUint = BigUint::parse_bytes( - b"D0088F51CBFF34D258DD3DB21A5D66BB23BA5C279C2895FB39869507B587B120F55FFFF58A9FFFFDCFF7FFFFFFFD555", - 16 - ) - .unwrap(); -} - /// Computes the square root of a non-zero field element in Fp2 pub fn fcall_bls12_381_fp2_sqrt(params: &[u64], results: &mut [u64]) -> i64 { // Get the input @@ -68,7 +31,7 @@ pub fn bls12_381_fp2_sqrt_13(a: &[u64; 12]) -> [u64; 13] { if !is_qr { // To check that a is indeed a non-quadratic residue, we check that // a * NQR is a quadratic residue for some fixed known non-quadratic residue NQR - let a_nqr = bls12_381_fp2_mul(a, &NQR); + let a_nqr = bls12_381_fp2_mul(a, &NQR_FP2); // Compute the square root of a * NQR let sqrt_nqr = bls12_381_fp2_sqrt(&a_nqr).0; @@ -254,6 +217,6 @@ mod tests { let sqrt = &results[1..13].try_into().unwrap(); assert_eq!(has_sqrt, 0); assert_eq!(sqrt, &expected_sqrt); - assert_eq!(bls12_381_fp2_mul(sqrt, sqrt), bls12_381_fp2_mul(&x, &NQR)); + assert_eq!(bls12_381_fp2_mul(sqrt, sqrt), bls12_381_fp2_mul(&x, &NQR_FP2)); } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_inv.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_inv.rs index d5ac8baf9..2a076f153 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_inv.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_inv.rs @@ -4,13 +4,7 @@ use num_traits::Zero; use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; -lazy_static! { - pub(crate) static ref P: BigUint = BigUint::parse_bytes( - b"1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - 16 - ) - .unwrap(); -} +use super::P; /// Perform the inversion of a non-zero field element in Fp pub fn fcall_bls12_381_fp_inv(params: &[u64], results: &mut [u64]) -> i64 { diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_sqrt.rs index 89e825bf5..58df26ec0 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp_sqrt.rs @@ -3,27 +3,7 @@ use num_bigint::BigUint; use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; -lazy_static! { - pub(crate) static ref P: BigUint = BigUint::parse_bytes( - b"1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - 16 - ) - .unwrap(); - - pub static ref P_HALF: BigUint = BigUint::parse_bytes( - b"d0088f51cbff34d258dd3db21a5d66bb23ba5c279c2895fb39869507b587b120f55ffff58a9ffffdcff7fffffffd555", - 16 - ) - .unwrap(); - - pub static ref P_DIV_4: BigUint = BigUint::parse_bytes( - b"680447a8e5ff9a692c6e9ed90d2eb35d91dd2e13ce144afd9cc34a83dac3d8907aaffffac54ffffee7fbfffffffeaab", - 16 - ) - .unwrap(); - - pub static ref NQR: BigUint = BigUint::from(2u64); // First non-quadratic residue in Fp -} +use super::{NQR_FP, P, P_DIV_4}; /// Computes the square root of a non-zero field element in Fp pub fn fcall_bls12_381_fp_sqrt(params: &[u64], results: &mut [u64]) -> i64 { @@ -49,7 +29,7 @@ pub fn bls12_381_fp_sqrt(a: &[u64; 6], results: &mut [u64]) { if !a_is_qr { // To check that a is indeed a non-quadratic residue, we check that // a * NQR is a quadratic residue for some fixed known non-quadratic residue NQR - let a_nqr = (a_big * &*NQR) % &*P; + let a_nqr = (a_big * &*NQR_FP) % &*P; // Compute the square root of a * NQR let sqrt_nqr = a_nqr.modpow(&P_DIV_4, &P); @@ -139,7 +119,7 @@ mod tests { let sqrt = &results[1..7].try_into().unwrap(); assert_eq!(has_sqrt, 0); assert_eq!(sqrt, &expected_sqrt); - let nqr = n_u64_digits_from_biguint::<6>(&NQR); + let nqr = n_u64_digits_from_biguint::<6>(&NQR_FP); assert_eq!(bls12_381_fp_mul(sqrt, sqrt), bls12_381_fp_mul(&x, &nqr)); } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/mod.rs index 33655e97b..a8aa55e58 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/mod.rs @@ -1,9 +1,11 @@ +mod constants; mod fp2_inv; mod fp2_sqrt; mod fp_inv; mod fp_sqrt; mod twist; +use constants::*; pub use fp2_inv::*; pub use fp2_sqrt::*; pub use fp_inv::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/constants.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/constants.rs new file mode 100644 index 000000000..669cbe1f1 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/constants.rs @@ -0,0 +1,10 @@ +use lazy_static::lazy_static; +use num_bigint::BigUint; + +lazy_static! { + pub(crate) static ref P: BigUint = BigUint::parse_bytes( + b"30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", + 16 + ) + .unwrap(); +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp.rs index c94334202..9a0fc0728 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp.rs @@ -4,13 +4,7 @@ use num_traits::Zero; use crate::zisklib::fcalls_impl::utils::{biguint_from_u64_digits, n_u64_digits_from_biguint}; -lazy_static! { - pub(crate) static ref P: BigUint = BigUint::parse_bytes( - b"30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", - 16 - ) - .unwrap(); -} +use super::P; /// Perform the inversion of a non-zero field element in Fp pub fn fcall_bn254_fp_inv(params: &[u64], results: &mut [u64]) -> i64 { diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp2.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp2.rs index 2fee5b381..b22e92cea 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp2.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/fp2.rs @@ -6,13 +6,7 @@ use super::fp::{ bn254_fp_sub, }; -lazy_static! { - static ref P: BigUint = BigUint::parse_bytes( - b"30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", - 16 - ) - .unwrap(); -} +use super::P; /// Perform the inversion of a non-zero field element in Fp2 pub fn fcall_bn254_fp2_inv(params: &[u64], results: &mut [u64]) -> i64 { diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/mod.rs index a494177c3..57acbee6b 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bn254/mod.rs @@ -1,7 +1,9 @@ +mod constants; mod fp; mod fp2; mod twist; +use constants::*; pub use fp::*; pub use fp2::*; pub use twist::*; From 4099634826101ff419cb48b730a6efe342e99435 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 9 Jan 2026 12:35:04 +0000 Subject: [PATCH 205/782] modexp simplified --- ziskos-hints/src/handlers/modexp.rs | 53 ++++++++--------------------- 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/ziskos-hints/src/handlers/modexp.rs b/ziskos-hints/src/handlers/modexp.rs index 02f8b34c8..e713b5f75 100644 --- a/ziskos-hints/src/handlers/modexp.rs +++ b/ziskos-hints/src/handlers/modexp.rs @@ -1,49 +1,26 @@ use crate::{handlers::validate_hint_length, zisklib}; +/// Read a length-prefixed field from hint data +#[inline] +fn read_field<'a>(data: &'a [u64], pos: &mut usize) -> Result<&'a [u64], String> { + let len = *data.get(*pos).ok_or("MODEXP hint data too short")? as usize; + *pos += 1; + let field = data.get(*pos..*pos + len).ok_or("MODEXP hint data too short")?; + *pos += len; + Ok(field) +} + // Processes a MODEXP hint. #[inline] pub fn modexp_hint(data: &[u64]) -> Result, String> { - if data.is_empty() { - return Err("MODEXP hint data is empty".to_string()); - } - - // Parse base - let base_len = data[0] as usize; - let base_start = 1; - let base_end = base_start + base_len; - - if data.len() < base_end + 1 { - return Err(format!( - "MODEXP hint data too short for base (expected at least {} elements)", - base_end + 1 - )); - } - let base = &data[base_start..base_end]; + let mut pos = 0; + let base = read_field(data, &mut pos)?; + let exp = read_field(data, &mut pos)?; + let modulus = read_field(data, &mut pos)?; - // Parse exponent - let exp_len = data[base_end] as usize; - let exp_start = base_end + 1; - let exp_end = exp_start + exp_len; - - if data.len() < exp_end + 1 { - return Err(format!( - "MODEXP hint data too short for exponent (expected at least {} elements)", - exp_end + 1 - )); - } - let exp = &data[exp_start..exp_end]; - - // Parse modulus - let modulus_len = data[exp_end] as usize; - let modulus_start = exp_end + 1; - let expected_len = modulus_start + modulus_len; - - validate_hint_length(data, expected_len, "MODEXP")?; - - let modulus = &data[modulus_start..expected_len]; + validate_hint_length(data, pos, "MODEXP")?; let mut processed_hints = Vec::new(); - zisklib::modexp_u64(base, exp, modulus, &mut processed_hints); Ok(processed_hints) From 6db94af7da78816aab8ccf16b38dc90023fb6235 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 9 Jan 2026 12:35:27 +0000 Subject: [PATCH 206/782] added debug info in hints processor --- precompiles/hints/src/hints_processor.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index cea34ae24..e8de63e19 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -127,6 +127,7 @@ impl HintsProcessorBuilder { Ok(HintsProcessor { pool, + num_hint: AtomicUsize::new(0), state, stats: if self.enable_stats { Some(Mutex::new(HashMap::new())) } else { None }, hints_sink, @@ -145,6 +146,8 @@ pub struct HintsProcessor { /// The thread pool used for parallel hint processing. pool: ThreadPool, + num_hint: AtomicUsize, + /// Shared state for parallel hint processing state: Arc, @@ -163,7 +166,7 @@ pub struct HintsProcessor { } impl HintsProcessor { - const DEFAULT_NUM_THREADS: usize = 32; + const DEFAULT_NUM_THREADS: usize = 1; /// Creates a builder for configuring a [`HintsProcessor`]. /// @@ -227,6 +230,8 @@ impl HintsProcessor { } let hint = PrecompileHint::from_u64_slice(hints, idx, true)?; + self.num_hint.fetch_add(1, Ordering::Relaxed); + println!("[{}] Hint processed {:?}:", self.num_hint.load(Ordering::Relaxed), hint); // Check if custom handler is registered for custom hints if let HintCode::Custom(code) = hint.hint_code { @@ -371,6 +376,8 @@ impl HintsProcessor { return; } + println!("Hint processed {:?}:", hint); + // Check if we should stop due to error - but still need to fill the slot let result = if state.error_flag.load(Ordering::Acquire) { Err(anyhow::anyhow!("Processing stopped due to error")) @@ -379,6 +386,14 @@ impl HintsProcessor { Self::dispatch_hint(hint, custom_handlers) }; + println!( + "Hint result: {:x?} bytes", + match &result { + Ok(data) => format!("{:?}", data), + Err(e) => format!("Err({})", e), + } + ); + // Store result - MUST fill slot even if error occurred let mut queue = state.queue.lock().unwrap(); From c9623da8bb29f492c142093dca33dab2cf99d7e1 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:24:59 +0000 Subject: [PATCH 207/782] added a HintsFile to executor while debugging --- executor/src/executor.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/executor/src/executor.rs b/executor/src/executor.rs index c442cd7f4..1ae30ec0a 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -20,8 +20,9 @@ //! maintaining clarity and modularity in the computation process. use asm_runner::{ - shmem_input_name, write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, HintsShmem, - MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, TaskFactory, + shmem_input_name, write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, HintsFile, + HintsShmem, MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, + TaskFactory, }; use fields::PrimeField64; use pil_std_lib::Std; @@ -79,6 +80,7 @@ enum MinimalTraceExecutionMode { } pub type StreamHintsShmem = ZiskStream>; +pub type StreamHintsFile = ZiskStream>; /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. @@ -88,6 +90,7 @@ pub struct ZiskExecutor { /// Pipeline for handling precompile hints. hints_stream: Mutex, + // hints_stream: Mutex, /// ZisK ROM, a binary file containing the ZisK program to be executed. pub zisk_rom: Arc, @@ -198,6 +201,8 @@ impl ZiskExecutor { // Create hints pipeline with null hints stream initially. let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory) .expect("Failed to create HintsShmem"); + // let hints_shmem = HintsFile::new("test_modexp_results.bin".to_string()) + // .expect("Failed to create HintsFile"); let hints_processor = HintsProcessor::builder(hints_shmem) .build() From 62c29a3896369fad9b76afc2d16e04cc5ae8b196 Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 9 Jan 2026 14:44:17 +0100 Subject: [PATCH 208/782] Fix lib-c fcall BigIntDivCtx() and BLS12_381Fp2SqrtCtx() result_size --- lib-c/c/src/fcall/fcall.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib-c/c/src/fcall/fcall.cpp b/lib-c/c/src/fcall/fcall.cpp index e463e5256..0283782fd 100644 --- a/lib-c/c/src/fcall/fcall.cpp +++ b/lib-c/c/src/fcall/fcall.cpp @@ -921,7 +921,12 @@ int BigIntDivCtx ( ctx->result[2 + quotient_size + i] = 0; } - return 2 + quotient_size + remainder_size; + uint64_t total_size = 2 + quotient_size + remainder_size; + assert(total_size < FCALL_RESULT_MAX_SIZE); + + ctx->result_size = total_size; + + return total_size; } /************************/ @@ -1015,6 +1020,8 @@ int BLS12_381Fp2SqrtCtx ( ); if (result != 0) return result; } + + ctx->result_size = 13; return 0; } \ No newline at end of file From 9ecb6967e460a129543d20291965360ccfa6a905 Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 9 Jan 2026 14:57:13 +0100 Subject: [PATCH 209/782] Fix hints in syscall_add256() by adding carry out --- ziskos/entrypoint/src/syscalls/add256.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ziskos/entrypoint/src/syscalls/add256.rs b/ziskos/entrypoint/src/syscalls/add256.rs index 2732c2006..d495fe7f0 100644 --- a/ziskos/entrypoint/src/syscalls/add256.rs +++ b/ziskos/entrypoint/src/syscalls/add256.rs @@ -40,6 +40,7 @@ pub extern "C" fn syscall_add256( #[cfg(feature = "hints")] { hints.extend_from_slice(params.c); + hints.push(cout); } cout } From 83ec061ca9c67798d790d1e8aa60d1430ad95992 Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 9 Jan 2026 15:29:00 +0100 Subject: [PATCH 210/782] Add function id to opcode_fcall() trace --- emulator-asm/src/emu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index e73f1e317..c02f01415 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -885,9 +885,9 @@ extern int _opcode_fcall(struct FcallContext * ctx) #endif #ifdef DEBUG #ifdef ASM_CALL_METRICS - if (emu_verbose) printf("_opcode_fcall() counter=%lu\n", asm_call_metrics.fcall_counter); + if (emu_verbose) printf("_opcode_fcall(%lu) counter=%lu\n", ctx->function_id, asm_call_metrics.fcall_counter); #else - if (emu_verbose) printf("_opcode_fcall()\n"); + if (emu_verbose) printf("_opcode_fcall(%lu)\n", ctx->function_id); #endif #endif From 34e497cc71dc4afada1d6fa07a53533bc5e01574 Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 9 Jan 2026 18:14:10 +0100 Subject: [PATCH 211/782] Fix precompile_results_register() in ZiskRom2Asm --- core/src/zisk_rom_2_asm.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 30f3ab6c1..98ecd7e39 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -8053,7 +8053,7 @@ impl ZiskRom2Asm { ); *code += &format!( "\tmov {}, [{} + {}*8] {}\n", - REG_AUX, + REG_VALUE, REG_ADDRESS, REG_AUX, ctx.comment_str("value = precompile_results[0]") @@ -8061,10 +8061,10 @@ impl ZiskRom2Asm { // Copy the ui64 element from precompile_results to the destination address *code += &format!( - "\tmov {}, [{}] {}\n", + "\tmov {}, {} {}\n", reg, - REG_AUX, - ctx.comment_str("value = precompile_results[0]") + REG_VALUE, + ctx.comment_str("reg = precompile_results[0]") ); // Increase precompile_read From 853621972a3597f0c239ece3335964d7fb7bc48d Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 9 Jan 2026 19:13:42 +0100 Subject: [PATCH 212/782] Add ASM_PRECOMPILE_CACHE_DEBUG to emu.c --- emulator-asm/src/emu.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index c02f01415..48fa49f03 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -347,17 +347,22 @@ void precompile_cache_cleanup(void) precompile_cache_loading = false; } +// #define ASM_PRECOMPILE_CACHE_DEBUG +#ifdef ASM_PRECOMPILE_CACHE_DEBUG uint64_t total_precompile_cache_size = 0; uint64_t total_precompile_cache_counter = 0; +#endif void precompile_cache_store( uint8_t* data, uint64_t size) { assert(precompile_file != NULL); assert(precompile_cache_storing == true); fwrite(data, 1, size, precompile_file); fflush(precompile_file); +#ifdef ASM_PRECOMPILE_CACHE_DEBUG total_precompile_cache_size += size; total_precompile_cache_counter++; - //printf("precompile_cache_store() Stored %lu bytes at pos=%lu total_precompile_cache_size=%lu total_precompile_cache_counter=%lu\n", size, ftell(precompile_file), total_precompile_cache_size, total_precompile_cache_counter); + printf("precompile_cache_store() Stored %lu bytes at pos=%lu total_precompile_cache_size=%lu total_precompile_cache_counter=%lu\n", size, ftell(precompile_file), total_precompile_cache_size, total_precompile_cache_counter); +#endif } void precompile_cache_load( uint8_t* data, uint64_t size) @@ -369,6 +374,11 @@ void precompile_cache_load( uint8_t* data, uint64_t size) printf("precompile_cache_load() Error reading file %s read_size=%lu expected size=%lu pos=%lu\n", precompile_cache_filename, read_size, size, ftell(precompile_file)); exit(-1); } +#ifdef ASM_PRECOMPILE_CACHE_DEBUG + total_precompile_cache_size += size; + total_precompile_cache_counter++; + printf("precompile_cache_load() Loaded %lu bytes at pos=%lu total_precompile_cache_size=%lu total_precompile_cache_counter=%lu\n", size, ftell(precompile_file), total_precompile_cache_size, total_precompile_cache_counter); +#endif } #endif From 0afdd33839f65b2fc5e1c253bbab115cdbdf2d7a Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 9 Jan 2026 19:14:37 +0100 Subject: [PATCH 213/782] Disable hints data trace in hints_processor.rs --- precompiles/hints/src/hints_processor.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index e8de63e19..f203f7970 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -386,13 +386,13 @@ impl HintsProcessor { Self::dispatch_hint(hint, custom_handlers) }; - println!( - "Hint result: {:x?} bytes", - match &result { - Ok(data) => format!("{:?}", data), - Err(e) => format!("Err({})", e), - } - ); + // println!( + // "Hint result: {:x?} bytes", + // match &result { + // Ok(data) => format!("{:?}", data), + // Err(e) => format!("Err({})", e), + // } + // ); // Store result - MUST fill slot even if error occurred let mut queue = state.queue.lock().unwrap(); From 630af5f60ef82483b2b0835e9bb44f879f1431c7 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Mon, 12 Jan 2026 10:17:35 +0100 Subject: [PATCH 214/782] dma without assembly, with memcpy, memmove. --- Cargo.lock | 4 + Cargo.toml | 1 + common/src/bus/data_bus_mem.rs | 6 + common/src/bus/data_bus_operation.rs | 9 +- common/src/component/component_planner.rs | 86 ++++ core/Cargo.toml | 3 + core/src/inst_context.rs | 102 +++- core/src/mem.rs | 33 +- core/src/riscv2zisk_context.rs | 122 +++-- core/src/zisk_definitions.rs | 2 +- core/src/zisk_inst.rs | 2 + core/src/zisk_inst_builder.rs | 3 +- core/src/zisk_ops.rs | 430 +++++++++++++---- core/src/zisk_rom_2_asm.rs | 21 + emulator/Cargo.toml | 1 + emulator/src/emu.rs | 155 ++++-- emulator/src/emu_context.rs | 20 +- emulator/src/emu_options.rs | 4 +- emulator/src/stats.rs | 5 + executor/Cargo.toml | 1 + executor/src/executor.rs | 7 +- executor/src/sm_static_bundle.rs | 66 +++ executor/src/static_data_bus.rs | 15 +- executor/src/static_data_bus_collect.rs | 85 ++++ pil/config.pil | 6 + pil/operations.pil | 6 + pil/opids.pil | 13 +- pil/src/constants.rs | 5 + pil/src/lib.rs | 2 + pil/src/pil_helpers/traces.rs | 48 +- pil/zisk.pil | 73 +-- precompiles/big_int/src/add256.rs | 12 +- precompiles/common/src/lib.rs | 78 ++- precompiles/dma/Cargo.toml | 2 + precompiles/dma/pil/dma.pil | 151 ++++-- precompiles/dma/pil/dma_64_aligned.pil | 199 +++++--- precompiles/dma/pil/dma_pre_post.pil | 183 ++++--- precompiles/dma/pil/dma_pre_post_table.pil | 62 ++- precompiles/dma/pil/dma_rom.pil | 43 +- precompiles/dma/pil/dma_unaligned.pil | 119 +++-- precompiles/dma/pil/dual_range.pil | 17 + precompiles/dma/src/dma.rs | 106 ---- precompiles/dma/src/dma/dma.rs | 295 ++++++++++++ precompiles/dma/src/dma/dma_collector.rs | 103 ++++ precompiles/dma/src/dma/dma_input.rs | 54 +++ precompiles/dma/src/dma/dma_instance.rs | 117 +++++ precompiles/dma/src/dma/dma_rom.rs | 20 + precompiles/dma/src/dma/mod.rs | 11 + precompiles/dma/src/dma_64_aligned.rs | 168 ------- .../dma/src/dma_64_aligned/dma_64_aligned.rs | 271 +++++++++++ .../dma_64_aligned_collector.rs | 115 +++++ .../dma_64_aligned/dma_64_aligned_input.rs | 72 +++ .../dma_64_aligned/dma_64_aligned_instance.rs | 141 ++++++ precompiles/dma/src/dma_64_aligned/mod.rs | 10 + precompiles/dma/src/dma_64_aligned_input.rs | 32 -- precompiles/dma/src/dma_bus_device.rs | 73 ++- precompiles/dma/src/dma_constants.rs | 1 + precompiles/dma/src/dma_gen_mem_inputs.rs | 423 +++++++--------- precompiles/dma/src/dma_helpers.rs | 44 -- precompiles/dma/src/dma_input.rs | 51 -- precompiles/dma/src/dma_instance.rs | 211 -------- precompiles/dma/src/dma_manager.rs | 56 ++- precompiles/dma/src/dma_planner.rs | 237 +++++++-- precompiles/dma/src/dma_pre_post.rs | 169 ------- .../dma/src/dma_pre_post/dma_pre_post.rs | 337 +++++++++++++ .../dma_pre_post/dma_pre_post_collector.rs | 99 ++++ .../src/dma_pre_post/dma_pre_post_input.rs | 79 +++ .../src/dma_pre_post/dma_pre_post_instance.rs | 118 +++++ .../dma/src/dma_pre_post/dma_pre_post_rom.rs | 23 + precompiles/dma/src/dma_pre_post/mod.rs | 12 + precompiles/dma/src/dma_pre_post_input.rs | 25 - precompiles/dma/src/dma_unaligned.rs | 169 ------- .../dma/src/dma_unaligned/dma_unaligned.rs | 355 ++++++++++++++ .../dma_unaligned/dma_unaligned_collector.rs | 115 +++++ .../src/dma_unaligned/dma_unaligned_input.rs | 85 ++++ .../dma_unaligned/dma_unaligned_instance.rs | 141 ++++++ precompiles/dma/src/dma_unaligned/mod.rs | 10 + precompiles/dma/src/dma_unaligned_input.rs | 32 -- precompiles/dma/src/lib.rs | 24 +- precompiles/helpers/Cargo.toml | 3 +- precompiles/helpers/src/dma.rs | 455 ++++++++++++++++++ precompiles/helpers/src/lib.rs | 2 + state-machines/main/pil/main.pil | 10 +- state-machines/main/src/main_sm.rs | 5 +- state-machines/mem/pil/dual_byte.pil | 17 - state-machines/mem/pil/mem.pil | 11 +- state-machines/mem/pil/mem_align_byte.pil | 3 +- state-machines/mem/src/mem_align_byte_sm.rs | 8 +- state-machines/mem/src/mem_counters_cursor.rs | 2 +- state-machines/mem/src/mem_sm.rs | 34 +- state-machines/mem/src/rom_data_sm.rs | 2 +- witness-computation/Cargo.toml | 1 + witness-computation/src/zisk_lib.rs | 13 +- ziskos/entrypoint/src/{ => dma}/memcmp.s | 2 +- ziskos/entrypoint/src/dma/memcpy.s | 14 + ziskos/entrypoint/src/dma/memmove.s | 13 + ziskos/entrypoint/src/lib.rs | 61 +-- 97 files changed, 5229 insertions(+), 2033 deletions(-) create mode 100644 pil/config.pil create mode 100644 pil/src/constants.rs create mode 100644 precompiles/dma/pil/dual_range.pil delete mode 100644 precompiles/dma/src/dma.rs create mode 100644 precompiles/dma/src/dma/dma.rs create mode 100644 precompiles/dma/src/dma/dma_collector.rs create mode 100644 precompiles/dma/src/dma/dma_input.rs create mode 100644 precompiles/dma/src/dma/dma_instance.rs create mode 100644 precompiles/dma/src/dma/dma_rom.rs create mode 100644 precompiles/dma/src/dma/mod.rs delete mode 100644 precompiles/dma/src/dma_64_aligned.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs create mode 100644 precompiles/dma/src/dma_64_aligned/mod.rs delete mode 100644 precompiles/dma/src/dma_64_aligned_input.rs delete mode 100644 precompiles/dma/src/dma_helpers.rs delete mode 100644 precompiles/dma/src/dma_input.rs delete mode 100644 precompiles/dma/src/dma_instance.rs delete mode 100644 precompiles/dma/src/dma_pre_post.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs create mode 100644 precompiles/dma/src/dma_pre_post/mod.rs delete mode 100644 precompiles/dma/src/dma_pre_post_input.rs delete mode 100644 precompiles/dma/src/dma_unaligned.rs create mode 100644 precompiles/dma/src/dma_unaligned/dma_unaligned.rs create mode 100644 precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs create mode 100644 precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs create mode 100644 precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs create mode 100644 precompiles/dma/src/dma_unaligned/mod.rs delete mode 100644 precompiles/dma/src/dma_unaligned_input.rs create mode 100644 precompiles/helpers/src/dma.rs delete mode 100644 state-machines/mem/pil/dual_byte.pil rename ziskos/entrypoint/src/{ => dma}/memcmp.s (91%) create mode 100644 ziskos/entrypoint/src/dma/memcpy.s create mode 100644 ziskos/entrypoint/src/dma/memmove.s diff --git a/Cargo.lock b/Cargo.lock index c3139ea27..e3cadc1bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1345,6 +1345,7 @@ dependencies = [ "precomp-arith-eq", "precomp-arith-eq-384", "precomp-big-int", + "precomp-dma", "precomp-keccakf", "precomp-sha256f", "proofman", @@ -2962,6 +2963,7 @@ dependencies = [ "mem-common", "pil-std-lib", "precompiles-common", + "precompiles-helpers", "proofman", "proofman-common", "proofman-macros", @@ -5621,6 +5623,7 @@ dependencies = [ "indexmap", "json", "lib-c", + "paste", "precompiles-helpers", "rayon", "riscv", @@ -5780,6 +5783,7 @@ dependencies = [ "precomp-arith-eq", "precomp-arith-eq-384", "precomp-big-int", + "precomp-dma", "precomp-keccakf", "precomp-sha256f", "proofman", diff --git a/Cargo.toml b/Cargo.toml index 53d7a3b0c..510bb4dd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -162,6 +162,7 @@ futures-util = "0.3" uuid = { version = "1.0", features = ["serde", "v4"] } chrono = { version = "0.4", features = ["serde"] } sha2 = { version = "0.10.9", features = ["compress"] } +paste = "1.0" # gRPC dependencies tonic = "0.14" diff --git a/common/src/bus/data_bus_mem.rs b/common/src/bus/data_bus_mem.rs index b42fa663e..0d6c7467d 100644 --- a/common/src/bus/data_bus_mem.rs +++ b/common/src/bus/data_bus_mem.rs @@ -63,4 +63,10 @@ impl MemCollectorInfo { } false } + pub fn skip_addr_range(&self, addr_from: u32, addr_to: u32) -> bool { + if addr_from > self.to_addr || addr_to < self.from_addr { + return true; + } + false + } } diff --git a/common/src/bus/data_bus_operation.rs b/common/src/bus/data_bus_operation.rs index 690a7df79..0b20602d9 100644 --- a/common/src/bus/data_bus_operation.rs +++ b/common/src/bus/data_bus_operation.rs @@ -75,9 +75,10 @@ pub const OPERATION_BUS_ADD_256_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DAT + 2 * DATA_256_BITS_SIZE + SINGLE_RESULT_SIZE; -// 5 bus_precompiled_data + count +pub const DMA_ENCODED: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE; +// 5 bus_precompiled_data + encoded pub const OPERATION_BUS_DMA_MEMCPY_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1; -// 5 bus_precompiled_data + count + count_eq +// 5 bus_precompiled_data + encoded + count_eq pub const OPERATION_BUS_DMA_MEMCMP_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2; // 4 bus_data + 5 addr + 4 x 384 = 4 + 5 + 4 * 6 = 33 @@ -330,7 +331,7 @@ impl OperationBusData { let b = if inst.m32 { ctx.b & 0xffff_ffff } else { ctx.b }; let op = inst.op as u64; let op_type = inst.op_type as u64; - let step = ctx.step as u64; + let step = ctx.step; match inst.op_type { ZiskOperationType::Keccak => { @@ -564,7 +565,7 @@ impl OperationBusData { let b = if inst.m32 { ctx.b & 0xffff_ffff } else { ctx.b }; let op = inst.op as u64; let op_type = inst.op_type as u64; - let step = ctx.step as u64; + let step = ctx.step; match inst.op_type { ZiskOperationType::Keccak => { diff --git a/common/src/component/component_planner.rs b/common/src/component/component_planner.rs index 03a14879c..e23fd3e99 100644 --- a/common/src/component/component_planner.rs +++ b/common/src/component/component_planner.rs @@ -53,6 +53,33 @@ impl CollectSkipper { true } + /// Determines how many rows of the current instruction should be skipped. This method is useful + /// when an instruction spans multiple rows. + /// + /// # Returns + /// number of rows to skip if the instruction should be skipped, `0` otherwise. + #[inline(always)] + pub fn rows_to_skip(&mut self, rows: u64) -> u64 { + if !self.skipping { + return 0; + } + + if self.skip == 0 || self.skipped >= self.skip { + self.skipping = false; + return 0; + } + + if (self.skipped + rows) >= self.skip { + let result = self.skip - self.skipped; + self.skipped = self.skip; + self.skipping = false; + return result; + } + + self.skipped += rows; + rows + } + #[inline(always)] pub fn should_skip_query(&mut self, apply: bool) -> bool { if !self.skipping { @@ -146,6 +173,65 @@ impl CollectCounter { true } + /// Determines whether the current instruction should be skipped. + /// + /// Behavior: + /// 1. Skip first `initial_skip` elements + /// 2. Don't skip next `collect_count` elements + /// 3. Skip all remaining elements + /// + /// Arguments: + /// * `rows` - Number of rows in the current instruction + /// + /// # Returns + /// `Some((skip, count))` where: + /// - `skip` is the number of rows to skip + /// - `count` is the number of rows to collect + /// `None` if all rows should be skipped. + #[inline(always)] + pub fn should_process(&mut self, rows: u32) -> Option<(u32, u32)> { + // Phase 1: Initial skipping + let mut skip = 0; + let mut rows = rows; + if self.initial_skipping { + if self.initial_skip == 0 { + self.initial_skipping = false; + } else if (self.initial_skipped + rows) >= self.initial_skip { + skip = self.initial_skip - self.initial_skipped; + rows -= skip; + self.initial_skipped = self.initial_skip; + self.initial_skipping = false; + // skip only a part of rows, at this point need + // to calculate count of rows not skipped + if rows == 0 { + return None; + } + } else { + self.initial_skipped += rows; + // skip all rows + return None; + } + } + + if self.final_skip_phase { + // Phase 3: Skip all remaining elements + None + } else if (self.collected + rows) >= self.collect_count { + // Phase 2: Collecting (not skipping) + let rows_to_collect = self.collect_count - self.collected; + self.final_skip_phase = true; + self.collected = self.collect_count; + if rows_to_collect == 0 { + None + } else { + Some((skip, rows_to_collect)) + } + } else { + self.collected += rows; + Some((skip, rows)) + } + } + /// Reset to initial state with new parameters pub fn reset(&mut self, initial_skip: u32, collect_count: u32) { self.initial_skip = initial_skip; diff --git a/core/Cargo.toml b/core/Cargo.toml index cf3b890c2..5e26391c5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -27,6 +27,8 @@ serde = { workspace = true } serde_json = { workspace = true } fields = { workspace = true } sha2 = { workspace = true } +paste = { workspace = true } + indexmap = { version = "2.2.6", features = ["serde"] } json = "0.12.4" @@ -36,4 +38,5 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] } [features] default = [] +debug_dma = [] # sp = [] diff --git a/core/src/inst_context.rs b/core/src/inst_context.rs index 9b82dba6a..ef0a7aa89 100644 --- a/core/src/inst_context.rs +++ b/core/src/inst_context.rs @@ -8,7 +8,9 @@ use crate::{ Mem, FCALL_PARAMS_MAX_SIZE, FCALL_RESULT_MAX_SIZE, REGS_IN_MAIN_TOTAL_NUMBER, ROM_ENTRY, }; -/// Zisk precompiled +const PARAMS_MAX_SIZE: usize = 4; + +/// Zisk precompiled emulation mode #[derive(Debug, Default, PartialEq, Eq)] pub enum EmulationMode { #[default] @@ -42,27 +44,27 @@ pub struct PrecompiledInstContext { #[derive(Debug)] pub struct FcallInstContext { /// Fcall parameters data - /// Maximum size is FCALL_PARAMS_MAX_SIZE u64's + /// Maximum size is FCALL_PARAMS_MAX_SIZE u64s pub parameters: [u64; FCALL_PARAMS_MAX_SIZE], - /// Indicates how many parameters u64's contain valid data + /// Indicates how many parameter u64s contain valid data pub parameters_size: u64, /// Fcall result data - /// Maximum size is FCALL_RESULT_MAX_SIZE u64's + /// Maximum size is FCALL_RESULT_MAX_SIZE u64s pub result: [u64; FCALL_RESULT_MAX_SIZE], - /// Indicates how many result u64's contain valid data + /// Indicates how many result u64s contain valid data pub result_size: u64, - /// Indicates how many result u64's have been read using fcall_get() + /// Indicates how many result u64s have been read using fcall_get() pub result_got: u64, } impl Default for FcallInstContext { /// Default fcall instruction context constructor fn default() -> Self { - FcallInstContext { + Self { parameters: [0; FCALL_PARAMS_MAX_SIZE], parameters_size: 0, result: [0; FCALL_RESULT_MAX_SIZE], @@ -72,6 +74,83 @@ impl Default for FcallInstContext { } } +/// Zisk param instruction context, these instructions are used to pass extra parameters to +/// precompiles. Currently precompiles can receive up to 2 parameters directly in instruction call, +/// but if this precompile needs more parameters use these instructions to pass them. It's important +/// to note that these parameters must be called in the instructions immediately before, because when +/// precompiles prove them they use step - 1, step - 2 and so on. +/// +/// Stores the precompile arguments. +#[derive(Debug)] +pub struct ParamInstContext { + /// Maximum size is PARAMS_MAX_SIZE u64s + pub parameters: [u64; PARAMS_MAX_SIZE], + + /// Indicates how many parameter u64s contain valid data + pub parameters_size: usize, + + /// Indicates the max step for these parameters + pub step_limit: u64, +} + +impl Default for ParamInstContext { + /// Default param instruction context constructor + fn default() -> Self { + Self { parameters: [0; PARAMS_MAX_SIZE], parameters_size: 0, step_limit: 0 } + } +} + +impl ParamInstContext { + /// Adds a single param. + pub fn add_param(&mut self, value: u64, step: u64) { + if step > self.step_limit { + self.step_limit = step + PARAMS_MAX_SIZE as u64 + 1; + self.parameters_size = 0; + } + if self.parameters_size >= PARAMS_MAX_SIZE { + panic!( + "ERROR: no space for one more parameter ({}/{} step_limit:{})", + self.parameters_size, PARAMS_MAX_SIZE, self.step_limit + ); + } + self.parameters[self.parameters_size] = value; + self.parameters_size += 1; + } + /// Adds multiple params (double normally). + pub fn add_params(&mut self, values: &[u64], step: u64) { + if step > self.step_limit { + self.step_limit = PARAMS_MAX_SIZE as u64 + 1; + self.parameters_size = 0; + } + if self.parameters_size + values.len() > PARAMS_MAX_SIZE { + panic!( + "ERROR: no space for {} more parameters ({}/{} step_limit:{})", + values.len(), + self.parameters_size, + PARAMS_MAX_SIZE, + self.step_limit + ); + } + for value in values { + self.parameters[self.parameters_size] = *value; + self.parameters_size += 1; + } + } + /// Clears params. + pub fn clear(&mut self) { + self.step_limit = 0; + self.parameters_size = 0; + } + /// Gets a param by index. + pub fn get_param(&self, index: usize) -> Option { + if index < self.parameters_size { + Some(self.parameters[index]) + } else { + None + } + } +} + #[derive(Debug)] /// ZisK instruction context data container, storing the state of the execution pub struct InstContext { @@ -117,6 +196,13 @@ pub struct InstContext { /// Fcall data pub fcall: FcallInstContext, + + /// Params data + pub params: ParamInstContext, + + /// DataExt 64 bytes size. With this information it is possible to specify which variable part of the minimal trace + /// is associated with the current instruction. Used by DMA precompile. + pub data_ext_len: usize, } /// RisK instruction context implementation @@ -138,6 +224,8 @@ impl InstContext { emulation_mode: EmulationMode::default(), precompiled: PrecompiledInstContext::default(), fcall: FcallInstContext::default(), + params: ParamInstContext::default(), + data_ext_len: 0, } } diff --git a/core/src/mem.rs b/core/src/mem.rs index 157cdb2a6..02016640a 100644 --- a/core/src/mem.rs +++ b/core/src/mem.rs @@ -907,15 +907,36 @@ impl Mem { dst_section.buffer[dst_offset..dst_offset + count].copy_from_slice(bytes); } - pub fn memcmp(&self, a: u64, b: u64, count: u64) -> u64 { + /// Reads `count` bytes from memory starting at `addr` and appends them as u64 values to `data`. + /// The data is read in 64-bit aligned chunks and pushed to the vector. + pub fn push_from_mem(&mut self, data: &mut Vec, addr: u64, count: u64) { if count == 0 { - return 0; + return; + } + + let section = self.get_readable_section(addr, count); + let addr64 = addr >> 3; + let to_addr64 = (addr + count - 1) >> 3; + let count64 = (to_addr64 - addr64 + 1) as usize; + let addr_offset: usize = (addr - section.start) as usize & !0x07; + let addr_offset64: usize = addr_offset >> 3; + + let mem64: &[u64] = unsafe { + core::slice::from_raw_parts( + section.buffer.as_ptr() as *const u64, + section.buffer.len() / 8, + ) + }; + data.extend_from_slice(&mem64[addr_offset64..addr_offset64 + count64]); + } + + pub fn memcmp(&self, a: u64, b: u64, count: u64) -> (u64, usize) { + if count == 0 { + return (0, 0); } let count_usize = count as usize; - println!("memcmp dump A:{}", self.memdump(a, count)); - println!("memcmp dump B:{}", self.memdump(b, count)); // Get sections for both addresses let a_section = self.get_readable_section(a, count); let b_section = self.get_readable_section(b, count); @@ -931,12 +952,12 @@ impl Mem { if byte_a != byte_b { // Sign extend the difference to 64 bits let diff = (byte_a as i64) - (byte_b as i64); - return diff as u64; + return (diff as u64, i); } } // All bytes are equal - 0 + (0, count_usize) } pub fn memdump(&self, addr: u64, count: u64) -> String { diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index cb3b92f57..240f74a58 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -57,6 +57,8 @@ const FLOAT_HANDLER_RETURN_ADDR: u64 = FLOAT_HANDLER_ADDR + 4 * 34; // 31 regs + pub struct Riscv2ZiskContext<'a> { /// Map of program address to ZisK instructions pub insts: &'a mut HashMap, + pub input_precompile: Option, + pub output_precompile: Option, } impl Riscv2ZiskContext<'_> { @@ -73,41 +75,19 @@ impl Riscv2ZiskContext<'_> { next_instructions: &[RiscvInstruction], ) { // ZisK supports the IMAC RISC-V instruction set - println!("CONVERT {} # {}", riscv_instruction.inst, riscv_instruction.to_text()); match riscv_instruction.inst.as_str() { // I: Base Integer Instruction Set ////////////////////////////////// // I.1. Integer Computational (Register-Register) "add" => { - if riscv_instruction.rd == 0 - && next_instructions.len() > 0 - && next_instructions[0].inst == "csrrs" - && next_instructions[0].csr == 0x812 - { - println!( - "DETECTED INSTRUCTION MEMCPY '{}' + '{}' AT 0x{:08X}", - riscv_instruction.inst, - next_instructions[0].inst, - riscv_instruction.rom_address - ); - self.create_special_register_op(riscv_instruction, "dma_memcpy", 8); - } else if riscv_instruction.rd == 10 - && next_instructions.len() > 0 - && next_instructions[0].inst == "csrrs" - && next_instructions[0].csr == 0x813 - { - println!( - "DETECTED INSTRUCTION MEMCMP '{}' + '{}' AT 0x{:08X}", - riscv_instruction.inst, - next_instructions[0].inst, - riscv_instruction.rom_address - ); - self.create_special_register_op(riscv_instruction, "dma_memcmp", 8); + if riscv_instruction.rd == 0 && self.input_precompile == Some(0x812) { + self.create_register_op(riscv_instruction, "dma_memcpy", 4); + } else if riscv_instruction.rd == 10 && self.input_precompile == Some(0x813) { + self.create_register_op(riscv_instruction, "dma_memcmp", 4); } else if riscv_instruction.rs1 == 0 { - // if next_instructions.len() > 0 && next_instructions[0].inst == "csrw" { println!("Detected instruction pattern"); - if next_instructions.len() > 0 { + if !next_instructions.is_empty() { // rd = rs1(0) + rs2 = rs2 followed by ret println!("Detected instruction pattern '{}'", next_instructions[0].inst); self.copyb(riscv_instruction, 4, 2); @@ -632,23 +612,6 @@ impl Riscv2ZiskContext<'_> { self.insts.insert(i.rom_address, zib); } - /// Creates a Zisk operation that implements a RISC-V register operation, i.e. an operation that - /// loads both input parameters a and b from their respective registers, - /// and stores the result c into a register - pub fn create_special_register_op(&mut self, i: &RiscvInstruction, op: &str, inst_size: u64) { - // assert!(inst_size == 2 || inst_size == 4); - let mut zib = ZiskInstBuilder::new_from_riscv(i.rom_address, i.inst.clone()); - zib.src_a("reg", i.rs1 as u64, false); - zib.src_b("reg", i.rs2 as u64, false); - zib.op(op).unwrap(); - zib.store("reg", i.rd as i64, false, false); - zib.j(inst_size as i64, inst_size as i64); - zib.verbose(&format!("{} r{}, r{}, r{}", i.inst, i.rd, i.rs1, i.rs2)); - zib.build(); - println!("Special at 0x{:08X} op: {:?}", i.rom_address, zib); - self.insts.insert(i.rom_address, zib); - } - // beq rs1, rs2, label // eq([%rs1], [rs2]), j(label) @@ -1212,10 +1175,27 @@ impl Riscv2ZiskContext<'_> { zib.src_b("reg", i.rs1 as u64, false); zib.j(4, 4); if (CSR_PRECOMPILED_ADDR_START..=CSR_PRECOMPILED_ADDR_END).contains(&i.csr) { - zib.src_a("imm", 0, false); - let precompiled = CSR_PRECOMPILED[(i.csr - CSR_PRECOMPILED_ADDR_START) as usize]; - zib.op(precompiled).unwrap(); - zib.verbose(precompiled); + match i.csr { + 0x812 => { + self.output_precompile = Some(0x812); + zib.src_a("imm", 0, false); + zib.op("param").unwrap(); + zib.verbose("param"); + } + 0x813 => { + self.output_precompile = Some(0x813); + zib.src_a("imm", 0, false); + zib.op("param").unwrap(); + zib.verbose("param"); + } + _ => { + let precompiled = + CSR_PRECOMPILED[(i.csr - CSR_PRECOMPILED_ADDR_START) as usize]; + zib.src_a("imm", 0, false); + zib.op(precompiled).unwrap(); + zib.verbose(precompiled); + } + } } else if (CSR_FCALL_PARAM_ADDR_START..=CSR_FCALL_PARAM_ADDR_END).contains(&i.csr) { let words = CSR_FCALL_PARAM_OFFSET_TO_WORDS[(i.csr - CSR_FCALL_PARAM_ADDR_START) as usize]; @@ -1756,42 +1736,48 @@ pub fn add_zisk_code(rom: &mut ZiskRom, addr: u64, data: &[u8]) { let riscv_instructions = riscv_interpreter(addr, &code_vector); // Create a context to convert RISCV instructions to ZisK instructions, using rom.insts - let mut ctx = Riscv2ZiskContext { insts: &mut rom.insts }; + let mut ctx = Riscv2ZiskContext { + insts: &mut rom.insts, + input_precompile: None, + output_precompile: None, + }; // for (i, riscv_instruction) in riscv_instructions.iter().enumerate() { // println!("RISCV#{i} 0x{:08X}", riscv_instruction.rom_address); // } // let zisk_memcmp_index = // riscv_instructions.iter().position(|inst| inst.rom_address == 0x80236edc); - let zisk_memcmp_index: Option = None; + // let zisk_memcmp_index: Option = None; // For all RISCV instructions for (i, riscv_instruction) in riscv_instructions.iter().enumerate() { //print!("add_zisk_code() converting RISCV instruction={}\n", // riscv_instruction.to_string()); - if riscv_instructions[i].rom_address >= 0x80267b28 - && riscv_instructions[i].rom_address <= 0x80267b30 - { - if let Some(zisk_memcmp_index) = zisk_memcmp_index { - // Get slice of remaining instructions after current one - let index_offset = (riscv_instructions[i].rom_address - 0x80267b28) as usize >> 2; - let next_instructions = - &riscv_instructions[(zisk_memcmp_index + index_offset + 1)..]; - - let mut instruction = riscv_instructions[zisk_memcmp_index + index_offset].clone(); - instruction.rom_address = riscv_instructions[i].rom_address; - - // Convert RICV instruction to ZisK instruction and store it in rom.insts - ctx.convert(&instruction, next_instructions); - continue; - //print!(" to: {}", ctx.insts.iter().last().) - } - } + // if riscv_instructions[i].rom_address >= 0x80267b28 + // && riscv_instructions[i].rom_address <= 0x80267b30 + // { + // if let Some(zisk_memcmp_index) = zisk_memcmp_index { + // // Get slice of remaining instructions after current one + // let index_offset = (riscv_instructions[i].rom_address - 0x80267b28) as usize >> 2; + // let next_instructions = + // &riscv_instructions[(zisk_memcmp_index + index_offset + 1)..]; + + // let mut instruction = riscv_instructions[zisk_memcmp_index + index_offset].clone(); + // instruction.rom_address = riscv_instructions[i].rom_address; + + // // Convert RICV instruction to ZisK instruction and store it in rom.insts + // ctx.convert(&instruction, next_instructions); + // continue; + // //print!(" to: {}", ctx.insts.iter().last().) + // } + // } // Get slice of remaining instructions after current one let next_instructions = &riscv_instructions[(i + 1)..]; // Convert RICV instruction to ZisK instruction and store it in rom.insts + ctx.input_precompile = ctx.output_precompile; + ctx.output_precompile = None; ctx.convert(riscv_instruction, next_instructions); //print!(" to: {}", ctx.insts.iter().last().) } diff --git a/core/src/zisk_definitions.rs b/core/src/zisk_definitions.rs index 15e523115..2c232d29b 100644 --- a/core/src/zisk_definitions.rs +++ b/core/src/zisk_definitions.rs @@ -1,6 +1,6 @@ //! This module contains constant definitions used by other modules and crates. -pub const DEFAULT_MAX_STEPS: u64 = 0xf_ffff_ffff; +pub const DEFAULT_MAX_STEPS: u64 = 0xF_FFFF_FFFF; pub const DEFAULT_MAX_STEPS_STR: &str = "68719476735"; // 2^36 - 1 pub const CHUNK_SIZE_BITS: usize = 18; diff --git a/core/src/zisk_inst.rs b/core/src/zisk_inst.rs index 6087b3d9b..a4e2831ef 100644 --- a/core/src/zisk_inst.rs +++ b/core/src/zisk_inst.rs @@ -83,6 +83,7 @@ pub enum ZiskOperationType { ArithEq384, BigInt, // Note: Add new core operations here Dma, // Note: To add extra params to precompiles calls + Param, // ZisK Free Input Operations FcallParam, Fcall, @@ -103,6 +104,7 @@ pub const BIG_INT_OP_TYPE_ID: u32 = ZiskOperationType::BigInt as u32; pub const FCALL_PARAM_OP_TYPE_ID: u32 = ZiskOperationType::FcallParam as u32; pub const FCALL_OP_TYPE_ID: u32 = ZiskOperationType::Fcall as u32; pub const DMA_OP_TYPE_ID: u32 = ZiskOperationType::Dma as u32; +pub const PARAM_OP_TYPE_ID: u32 = ZiskOperationType::Param as u32; /// ZisK instruction definition /// diff --git a/core/src/zisk_inst_builder.rs b/core/src/zisk_inst_builder.rs index 615e5dc69..32356d4cc 100644 --- a/core/src/zisk_inst_builder.rs +++ b/core/src/zisk_inst_builder.rs @@ -208,7 +208,8 @@ impl ZiskInstBuilder { self.i.op_type = op.op_type().into(); self.i.input_size = op.input_size(); // assume that input_size > 0 implies a precompiled, and precompiled uses step on operations - self.i.op_with_step = op.input_size() > 0; + self.i.op_with_step = + op.input_size() > 0 || [ZiskOp::PARAM, ZiskOp::PARAMS].contains(&op.code()); Ok(()) } diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 8e7cf5b9c..36d23ab32 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -9,8 +9,10 @@ #![allow(unused)] +use precompiles_helpers::DmaInfo; use ziskos::zisklib::fcall_proxy; +use paste::paste; use std::{ collections::HashMap, fmt::{Debug, Display}, @@ -52,6 +54,7 @@ pub enum OpType { ArithEq384, BigInt, Dma, + Param, } impl From for ZiskOperationType { @@ -69,6 +72,7 @@ impl From for ZiskOperationType { OpType::ArithEq384 => ZiskOperationType::ArithEq384, OpType::BigInt => ZiskOperationType::BigInt, OpType::Dma => ZiskOperationType::Dma, + OpType::Param => ZiskOperationType::Param, } } } @@ -90,6 +94,7 @@ impl Display for OpType { Self::ArithEq384 => write!(f, "Arith384"), Self::BigInt => write!(f, "BigInt"), Self::Dma => write!(f, "Dma"), + Self::Param => write!(f, "Param"), } } } @@ -112,6 +117,7 @@ impl FromStr for OpType { "aeq384" => Ok(Self::ArithEq384), "bint" => Ok(Self::BigInt), "dma" => Ok(Self::Dma), + "param" => Ok(Self::Param), _ => Err(InvalidOpTypeError), } } @@ -147,6 +153,7 @@ impl Display for InvalidCodeError { pub trait OpStats { fn mem_align_read(&mut self, addr: u64, count: usize); fn mem_align_write(&mut self, addr: u64, count: usize); + fn add_extras(&mut self, extras: &[(u8, usize)]); } /// Stats gathering function that does nothing (used as default) @@ -155,6 +162,21 @@ pub fn ops_none(_ctx: &InstContext, _stats: &mut dyn OpStats) { // No-op implementation } +#[inline(always)] +pub fn opc_virtual(ctx: &mut InstContext) { + unimplemented!("opc_virtual: virtual operation") +} + +#[inline(always)] +pub fn op_virtual(a: u64, b: u64) -> (u64, bool) { + unimplemented!("op_virtual: virtual operation") +} + +#[inline(always)] +pub fn ops_virtual(_ctx: &InstContext, _stats: &mut dyn OpStats) { + unimplemented!("ops_virtual: virtual operation") +} + /// Internal macro used to define all ops in the [`ZiskOp`] enum macro_rules! define_ops { ( $( ($name:ident, $str_name:expr, $type:ident, $steps:expr, $code:expr, $input_size:expr, $output_size:expr, $call_fn:ident, $call_ab_fn:ident, $call_stats_fn:ident ) ),* $(,)? ) => { @@ -171,6 +193,11 @@ macro_rules! define_ops { } impl ZiskOp { + $( + paste! { + pub const [<$str_name:upper>]: u8 = $code; + } + )* /// Returns the (string) name of the operation pub const fn name(&self) -> &'static str { match self { @@ -313,6 +340,8 @@ macro_rules! define_ops { }; } +const DMA_64_ALIGNED_OPS_BY_ROW: usize = 4; + // Cost definitions: Area x Op const INTERNAL_COST: u64 = 0; const BINARY_COST: u64 = 75; @@ -326,6 +355,17 @@ const ARITH_EQ_COST: u64 = 85 * 16; const FCALL_COST: u64 = INTERNAL_COST; const ARITH_EQ_384_COST: u64 = 79 * 24; const ADD256_COST: u64 = 104; +const DMA_COST: u64 = 39; + +const DMA_64_ALIGNED_COST: u64 = 40; +const DMA_UNALIGNED_COST: u64 = 42; +const DMA_PRE_POST_COST: u64 = 84; + +// const OP_DMA_64_ALIGNED: u8 = 0xda; +// const OP_DMA_UNALIGNED: u8 = 0xdb; +// const OP_DMA_PRE: u8 = 0xdc; +// const OP_DMA_POST: u8 = 0xdd; +// const OP_DMA_CMP_BYTE: u8 = 0xde; /// Table of Zisk opcode definitions: enum, name, type, cost, code and implementation functions /// This table is the backbone of the Zisk processor, it determines what functionality is supported, @@ -388,8 +428,20 @@ define_ops! { (RemuW, "remu_w", ArithA32, ARITHA32_COST, 0xbd, 0, 0, opc_remu_w, op_remu_w, ops_none), (DivW, "div_w", ArithA32, ARITHA32_COST, 0xbe, 0, 0, opc_div_w, op_div_w, ops_none), (RemW, "rem_w", ArithA32, ARITHA32_COST, 0xbf, 0, 0, opc_rem_w, op_rem_w, ops_none), - (DmaMemCpy, "dma_memcpy", Dma, 0, 0xd0, 208, 96, opc_dma_memcpy, op_dma_memcpy, ops_dma_memcpy), - (DmaMemCmp, "dma_memcmp", Dma, 0, 0xd1, 208, 96, opc_dma_memcmp, op_dma_memcmp, ops_dma_memcmp), + (Param, "param", Param, 0, 0xc0, 0, 0, opc_param, op_param, ops_param), + (Params, "params", Param, 0, 0xc1, 0, 0, opc_params, op_params, ops_params), + // opcpdes 0xc2,0xc3 futured reserved for params + // opcodes 0xc4-0xcf are available + (DmaMemCpy, "dma_memcpy", Dma, DMA_COST, 0xd0, 8, 0, opc_dma_memcpy, op_dma_memcpy, ops_dma_memcpy), + (DmaMemCmp, "dma_memcmp", Dma, DMA_COST, 0xd1, 16, 0, opc_dma_memcmp, op_dma_memcmp, ops_dma_memcmp), + // opcodes 0xd2-0xd9 future reserved for dma operations (memset, memcpy256, memcmp256) + (Dma64Aligned, "_dma_64_aligned", Dma, DMA_64_ALIGNED_COST, 0xda, 8, 0, opc_virtual, op_virtual, ops_virtual), + (DmaUnaligned, "_dma_unaligned", Dma, DMA_UNALIGNED_COST, 0xdb, 8, 0, opc_virtual, op_virtual, ops_virtual), + (DmaPre, "_dma_pre", Dma, DMA_PRE_POST_COST, 0xdc, 8, 0, opc_virtual, op_virtual, ops_virtual), + (DmaPost, "_dma_post", Dma, DMA_PRE_POST_COST, 0xdd, 8, 0, opc_virtual, op_virtual, ops_virtual), + (DmaCmpByte, "_dma_cmp_byte", Dma, DMA_PRE_POST_COST, 0xde, 8, 0, opc_virtual, op_virtual, ops_virtual), + // opcodes 0xda-0xdf reserved for dma extra operations (costs) + // opcodes 0xe0,0xe1 are available (Arith384Mod, "arith384_mod", ArithEq384, ARITH_EQ_384_COST, 0xe2, 232, 48, opc_arith384_mod, op_arith384_mod, ops_arith384_mod), (Bls12_381CurveAdd, "bls12_381_curve_add", ArithEq384, ARITH_EQ_384_COST, 0xe3, 208, 96, opc_bls12_381_curve_add, op_bls12_381_curve_add, ops_bls12_381_curve_add), (Bls12_381CurveDbl, "bls12_381_curve_dbl", ArithEq384, ARITH_EQ_384_COST, 0xe4, 96, 96, opc_bls12_381_curve_dbl, op_bls12_381_curve_dbl, ops_bls12_381_curve_dbl), @@ -2274,76 +2326,88 @@ pub fn opc_halt(ctx: &mut InstContext) { } pub fn opc_dma_memcpy(ctx: &mut InstContext) { - /* let dst = ctx.a; - let src = ctx.b; - let count = ctx.regs[12]; + let dst = ctx.a; + let src = ctx.b; - if let EmulationMode::ConsumeMemReads = ctx.emulation_mode { - // copy from data - let src64 = src & !0x07; - let data_offset = src - src64; + match ctx.emulation_mode { + EmulationMode::Mem => { + let count = ctx.params.get_param(0).unwrap(); + ctx.mem.memcpy(dst, src, count); + } + EmulationMode::GenerateMemReads => { + // In generate mode we need to populate precompiled.input_data with + // information needed + let count = ctx.params.get_param(0).unwrap(); + ctx.precompiled.input_data.clear(); - // execute from precompile.input_data - // TODO: avoid copy data - ctx.mem.memcpy_from_data(src, count, &ctx.precompiled.input_data, data_offset as usize); + #[cfg(feature = "debug_dma")] + println!( + "opc_dma_memcpy 0x{dst:08X} 0x{src:08X} {count} GMR STEP:{}", + ctx.emulation_mode, ctx.step + ); - // Read data from the precompiled context - for (i, d) in data.iter_mut().enumerate() { - *d = ctx.precompiled.input_data[i]; - } - // Write the input data address to the precompiled context - // ctx.precompiled.input_data_address = address; - return; - } + let encoded = DmaInfo::encode_memcpy(dst, src, count as usize); + ctx.precompiled.input_data.push(encoded); + + if count > 0 { + // read first dst unaligned part for dma-pre + let mut data_len = 0; + let dst64 = dst & !0x07; + // if dst64 != dst { + if DmaInfo::get_pre_count(encoded) > 0 { + let pre_data = ctx.mem.read(dst64, 8); + data_len += 1; + ctx.precompiled.input_data.push(pre_data); + } - // Write the indirections to data - for (i, data) in data.iter_mut().enumerate().take(params_count) { - let indirection = ctx.mem.read(address + (8 * i as u64), 8); - if address & 0x7 != 0 { - panic!("precompiled_check_address() found address[{i}] not aligned to 8 bytes"); - } - *data = indirection; - } + // read last dst unaligned part for dma-post + let to_dst = dst + count - 1; + // if to_dst & 0x07 != 0x07 { + if DmaInfo::get_post_count(encoded) > 0 { + let post_data = ctx.mem.read(to_dst & !0x07, 8); + data_len += 1; + // println!("ADDING_POST_DATA 0x{:08X} 0x{post_data:016X}", to_dst & !0x07); + ctx.precompiled.input_data.push(post_data); + } - let mut data_offset = params_count; - for i in 0..load_indirections { - let data_offset = i * load_chunks + data_offset; - // if there aren't indirections, take directly from the address - let param_address = if params_count == 0 { address + data_offset as u64 } else { data[i] }; - for j in 0..load_chunks { - let addr = param_address + (8 * j as u64); - data[data_offset + j] = ctx.mem.read(addr, 8); - } - } + // read all source 64-words + let src64 = src & !0x07; + let to_src64 = (src + count - 1) & !0x07; + + let src64_count = (to_src64 - src64 + 8) >> 3; + ctx.mem.push_from_mem(&mut ctx.precompiled.input_data, src64, src64_count * 8); + data_len += src64_count; + #[cfg(feature = "debug_dma")] + println!( + "PRECOMPILED.INPUT_DATA: [{}] data_len:{data_len}", + ctx.precompiled + .input_data + .iter() + .map(|x| format!("0x{x:016X}")) + .collect::>() + .join(",") + ); + assert_eq!(data_len as usize, DmaInfo::get_data_size(encoded)); - // Process the remanent of the last chunk - if load_rem > 0 { - data_offset += (load_indirections - 1) * load_chunks; - let param_address = if params_count == 0 { - address + data_offset as u64 - } else { - data[load_indirections - 1] - }; - for j in load_chunks..load_chunks + load_rem { - let addr = param_address + (8 * j as u64); - data[data_offset + j] = ctx.mem.read(addr, 8); + ctx.mem.memcpy(dst, src, count); } - } + ctx.precompiled.output_data.clear(); - if let EmulationMode::GenerateMemReads = ctx.emulation_mode { - ctx.precompiled.input_data.clear(); - for (i, d) in data.iter_mut().enumerate() { - ctx.precompiled.input_data.push(*d); - } ctx.precompiled.step = ctx.step; } - */ - println!( - "opc_dma_memcpy 0x{:08X} 0x{:08X} A0-A2:[0x{:08X},0x{:08X},{}] {:?}", - ctx.a, ctx.b, ctx.regs[10], ctx.regs[11], ctx.regs[12], ctx.emulation_mode - ); - ctx.mem.memcpy(ctx.a, ctx.b, ctx.regs[12]); - ctx.c = ctx.a; + EmulationMode::ConsumeMemReads => { + let encoded = ctx.precompiled.input_data[0]; + let count = DmaInfo::get_count(encoded); + #[cfg(feature = "debug_dma")] + println!( + "opc_dma_memcpy 0x{dst:08X} 0x{src:08X} {count} CMR STEP:{} DATA_EXT_LEN:{}", + ctx.step, + DmaInfo::get_data_size(encoded) + ); + ctx.data_ext_len = DmaInfo::get_data_size(encoded); + } + } + ctx.c = 0; ctx.flag = false; } @@ -2356,54 +2420,96 @@ pub fn op_dma_memcpy(_a: u64, _b: u64) -> (u64, bool) { #[inline(always)] pub fn ops_dma_memcpy(ctx: &InstContext, stats: &mut dyn OpStats) { - // unimplemented!("ops_dma_memcpy() is not implemented"); + let dst = ctx.a; + let src = ctx.b; + let count = ctx.params.get_param(0).unwrap(); + + // pre, post, dma_align, dma_unalign + if count == 0 { + return; + } + + let pre = dst & 0x07 != 0; + let dst64 = dst & !0x07; + let src64 = src & !0x07; + + if pre { + stats.mem_align_read(dst64, 1); + stats.mem_align_read(src64, 2); + stats.mem_align_write(dst64, 1); + } + + let dst64_end = (dst + count - 1) & !0x07; + let src64_end = (src + count - 1) & !0x07; + let post = dst64_end > dst64 && (dst + count - 1) & 0x07 != 7; + if post { + stats.mem_align_read(dst64_end, 1); + stats.mem_align_read(src64_end - 8, 2); + stats.mem_align_write(dst64_end, 1); + } + + if count < 8 { + stats.add_extras(&[(ZiskOp::_DMA_PRE, pre as usize), (ZiskOp::_DMA_POST, post as usize)]); + } else { + let first_loop_dst64 = (dst + 7) >> 3; + let first_loop_src64 = (src + 7) >> 3; + let last_loop_dst64 = (dst + count - 8) >> 3; + let loop_count = (last_loop_dst64 + 1 - first_loop_dst64) as usize; + + // same alignment + if dst & 0x07 == src & 0x07 { + stats.mem_align_read(first_loop_src64, loop_count); + stats.mem_align_write(first_loop_dst64, loop_count); + let units = loop_count.div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, pre as usize), + (ZiskOp::_DMA_POST, post as usize), + (ZiskOp::_DMA_64_ALIGNED, units), + ]); + } else { + stats.mem_align_read(first_loop_src64, loop_count + 1); + stats.mem_align_write(first_loop_dst64, loop_count); + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, pre as usize), + (ZiskOp::_DMA_POST, post as usize), + (ZiskOp::_DMA_UNALIGNED, loop_count + 1), + ]); + } + } } pub fn opc_dma_memcmp(ctx: &mut InstContext) { - println!( - "opc_dma_memcmp 0x{:08X} 0x{:08X} A0-A2:[0x{:08X},0x{:08X},{}] {:?}", - ctx.a, ctx.b, ctx.regs[10], ctx.regs[11], ctx.regs[12], ctx.emulation_mode - ); - const WORDS: usize = 4 + 1 + 2 * 4; - let mut data = [0u64; WORDS]; + let addr_a = ctx.a; + let addr_b = ctx.b; + let count = ctx.params.get_param(0).unwrap(); - precompiled_load_data_with_result(ctx, 4, 2, 4, 0, &mut data, "add256"); + println!("opc_dma_memcmp 0x{addr_a:08X} 0x{addr_b:08X} {count} {:?}", ctx.emulation_mode); - if ctx.emulation_mode != EmulationMode::ConsumeMemReads { - // ignore 3 indirections - // 0 - addr_a - // 1 - addr_b - // 2 - cin - // 3 - addr_c - let cin = data[2]; - let (params, rest) = data.split_at(4); // params(4) - let (a, rest) = rest.split_at(4); - let (b, _) = rest.split_at(4); + if ctx.emulation_mode == EmulationMode::ConsumeMemReads { + ctx.c = ctx.precompiled.input_data[0]; + ctx.flag = false; + return; + } + let (op_result, count_eq) = ctx.mem.memcmp(addr_a, addr_b, count); - let a: &[u64; 4] = a.try_into().expect("opc_add256: a.len != 4"); - let b: &[u64; 4] = b.try_into().expect("opc_add256: b.len != 4"); - let mut c = [0u64; 4]; - let cout = precompiles_helpers::add256(a, b, cin, &mut c); + if let EmulationMode::GenerateMemReads = ctx.emulation_mode { + // In generate mode we need to populate precompiled.input_data with + // information needed + ctx.precompiled.input_data.clear(); - let c_addr = params[3]; - for (i, c_item) in c.iter().enumerate() { - ctx.mem.write(c_addr + (8 * i as u64), *c_item, 8); - } - if let EmulationMode::GenerateMemReads = ctx.emulation_mode { - ctx.precompiled.input_data[4 + 2 * 4] = cout; - } - ctx.c = cout; - ctx.flag = cout != 0; - } else { - assert!(data[4 + 2 * 4] <= 1, "opc_add256: cout > 1"); - ctx.c = data[4 + 2 * 4]; - ctx.flag = data[4 + 2 * 4] != 0; + // first element was the result of operation + ctx.precompiled.input_data.push(op_result); + ctx.precompiled.input_data.push(count_eq as u64); + + let count_used = std::cmp::min(count, count_eq as u64 + 1); + ctx.mem.push_from_mem(&mut ctx.precompiled.input_data, addr_a, count_used); + ctx.mem.push_from_mem(&mut ctx.precompiled.input_data, addr_b, count_used); + + // read full source data + ctx.precompiled.step = ctx.step; } - ctx.c = ctx.mem.memcmp(ctx.a, ctx.b, ctx.regs[12]); - println!( - "opc_dma_memcmp 0x{:08X} 0x{:08X} = 0x{:016X} A0-A2:[0x{:08X},0x{:08X},{}] {:?}", - ctx.a, ctx.b, ctx.c, ctx.regs[10], ctx.regs[11], ctx.regs[12], ctx.emulation_mode - ); + + ctx.c = op_result; ctx.flag = false; } @@ -2416,5 +2522,123 @@ pub fn op_dma_memcmp(_a: u64, _b: u64) -> (u64, bool) { #[inline(always)] pub fn ops_dma_memcmp(ctx: &InstContext, stats: &mut dyn OpStats) { - // unimplemented!("ops_dma_memcmp() is not implemented"); + let addr_a = ctx.a; + let addr_b = ctx.b; + let count = ctx.params.get_param(0).unwrap(); + + // pre, post, dma_align, dma_unalign + if count == 0 { + return; + } + + let (res, count_eq) = ctx.mem.memcmp(addr_a, addr_b, count); + let pre = addr_a & 0x07 != 0; + let addr64_a = addr_a & !0x07; + let addr64_b = addr_b & !0x07; + + if pre { + stats.mem_align_read(addr64_a, 1); + stats.mem_align_read(addr64_b, 2); + stats.mem_align_read(addr64_a, 1); + } + + let addr64_a_end = (addr_a + count - 1) & !0x07; + let addr64_b_end = (addr_b + count - 1) & !0x07; + let post = addr64_a_end > addr64_a && (addr_a + count - 1) & 0x07 != 7; + if post { + stats.mem_align_read(addr64_a_end, 1); + stats.mem_align_read(addr64_b_end - 8, 2); + stats.mem_align_read(addr64_a_end, 1); + } + + if count < 8 { + // with count < 8, there aren't 64-bits loops. + stats.add_extras(&[(ZiskOp::_DMA_PRE, pre as usize), (ZiskOp::_DMA_POST, post as usize)]); + } else { + // calculate the resources used by 64-bits loop. + // count used are number of bytes read to demostrate memcmp(), usually count_eq + 1, + // but if all bytes are equal count = count_eq, no need extra reads + let count_used = std::cmp::min(count, count_eq as u64 + 1); + let first_loop_dst64 = (addr_a + 7) >> 3; + let first_loop_src64 = (addr_b + 7) >> 3; + let last_loop_dst64 = (addr_a + count_used - 8) >> 3; + let loop_count = (last_loop_dst64 + 1 - first_loop_dst64) as usize; + + // need a machine to compare one byte + let compare_byte = count == count_eq as u64; + + // same alignment + if addr_a & 0x07 == addr_b & 0x07 { + stats.mem_align_read(first_loop_src64, loop_count); + stats.mem_align_read(first_loop_dst64, loop_count); + // add information about other machines to demostrate operation + let units = loop_count.div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, pre as usize), + (ZiskOp::_DMA_POST, post as usize), + (ZiskOp::_DMA_64_ALIGNED, units), + (ZiskOp::_DMA_CMP_BYTE, compare_byte as usize), + ]); + } else { + stats.mem_align_read(first_loop_src64, loop_count + 1); + stats.mem_align_read(first_loop_dst64, loop_count); + // add information about other machines to demostrate operation + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, pre as usize), + (ZiskOp::_DMA_POST, post as usize), + (ZiskOp::_DMA_64_ALIGNED, loop_count + 1), + (ZiskOp::_DMA_CMP_BYTE, compare_byte as usize), + ]); + } + } +} + +pub fn opc_param(ctx: &mut InstContext) { + match ctx.emulation_mode { + EmulationMode::GenerateMemReads => { + let b = ctx.b; + // println!("opc_param 0x{b:08X}({b}) STEP:{} PC:0x{:08X}", ctx.step, ctx.pc); + ctx.params.add_param(b, ctx.step); + } + _ => { + // in other modes the length was read from minimal trace, + // to avoid move params info between chunks + } + } + ctx.c = 0; + ctx.flag = false; } + +/// Unimplemented. Param can only be called from the system call context via InstContext. +/// This is provided just for completeness. +#[inline(always)] +pub fn op_param(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_param() is not implemented"); +} + +#[inline(always)] +pub fn ops_param(ctx: &InstContext, stats: &mut dyn OpStats) { + let param = ctx.b; + + println!("ops_param 0x{param:08X} {:?}", ctx.emulation_mode); +} + +pub fn opc_params(ctx: &mut InstContext) { + let a = ctx.a; + let b = ctx.b; + + // println!("opc_params 0x{a:016X}({a}),0x{b:016X}({b}) {:?}", ctx.emulation_mode); + ctx.params.add_params(&[a, b], ctx.step); + ctx.c = 0; + ctx.flag = false; +} + +/// Unimplemented. Param can only be called from the system call context via InstContext. +/// This is provided just for completeness. +#[inline(always)] +pub fn op_params(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_params() is not implemented"); +} + +#[inline(always)] +pub fn ops_params(ctx: &InstContext, stats: &mut dyn OpStats) {} diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 210b75a48..6ab9ab416 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -6648,6 +6648,27 @@ impl ZiskRom2Asm { ZiskOp::DmaMemCmp => { unimplemented!(); } + ZiskOp::Param => { + unimplemented!(); + } + ZiskOp::Params => { + unimplemented!(); + } + ZiskOp::Dma64Aligned => { + unimplemented!(); + } + ZiskOp::DmaUnaligned => { + unimplemented!(); + } + ZiskOp::DmaPre => { + unimplemented!(); + } + ZiskOp::DmaPost => { + unimplemented!(); + } + ZiskOp::DmaCmpByte => { + unimplemented!(); + } } } diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml index a401e3b1d..06f144d30 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -55,6 +55,7 @@ harness = false [features] default = [] debug_stats_trace = [] +minimal_trace_index_debug = [] gpu = ["proofman-common/gpu", "packed"] packed = ["proofman-common/packed"] # sp = [] diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 650f10c48..28a76e50f 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -799,6 +799,12 @@ impl<'a> Emu<'a> { ); data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { + if instruction.ind_width == 0 || address > 0xFFFF_FFFF { + println!( + "ILLEGAL INSTRUCTION/ADDRESS 0x{:08X} {} S:{} {:?}", + address, instruction.ind_width, self.ctx.inst_ctx.step, instruction + ); + } let (required_address_1, required_address_2) = Mem::required_addresses(address, instruction.ind_width); if required_address_1 == required_address_2 { @@ -1624,41 +1630,10 @@ impl<'a> Emu<'a> { // While not done while !self.ctx.inst_ctx.end { - if self.ctx.inst_ctx.pc == 0x802681d4 { - println!( - "PC: 0x{:08x} memcmp call (0x{:08x}, 0x{:08x}, {})", - self.ctx.inst_ctx.pc, - self.ctx.inst_ctx.regs[10], - self.ctx.inst_ctx.regs[11], - self.ctx.inst_ctx.regs[12] - ); - println!( - "memcmp dump A:{}", - self.ctx - .inst_ctx - .mem - .memdump(self.ctx.inst_ctx.regs[10], self.ctx.inst_ctx.regs[12]) - ); - println!( - "memcmp dump B:{}", - self.ctx - .inst_ctx - .mem - .memdump(self.ctx.inst_ctx.regs[11], self.ctx.inst_ctx.regs[12]) - ); - } - if self.ctx.inst_ctx.pc == 0x802681f8 { - println!( - "PC: 0x{:08x} memcmp equal 0x{:016X}", - self.ctx.inst_ctx.pc, self.ctx.inst_ctx.regs[10] - ); - } - if self.ctx.inst_ctx.pc == 0x80268200 { - println!( - "PC: 0x{:08x} memcmp different 0x{:016X}", - self.ctx.inst_ctx.pc, self.ctx.inst_ctx.regs[10] - ); - } + // println!( + // "DEBUG_TRACE {:09} 0x{:08x} {:?}", + // self.ctx.inst_ctx.step, self.ctx.inst_ctx.pc, self.ctx.inst_ctx.regs + // ); if options.verbose { println!( "Emu::run() step={} ctx.pc={}", @@ -1859,13 +1834,6 @@ impl<'a> Emu<'a> { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); let pc = self.ctx.inst_ctx.pc; - if pc >= 0x80265b8c && pc <= 0x80265b98 { - println!( - "Emu::step() executing step={} pc={pc:x} inst={}", - self.ctx.inst_ctx.step, - instruction.to_text() - ); - } //println!("PCLOG={}", instruction.to_text()); // Build the 'a' register value based on the source specified by the current instruction @@ -1974,6 +1942,14 @@ impl<'a> Emu<'a> { #[inline(always)] pub fn par_step_my_block(&mut self, emu_full_trace_vec: &mut EmuTrace) { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + + #[cfg(feature = "minimal_trace_index_debug")] + println!( + "MINIMAL_TRACE par_step_my_block {} {}", + self.ctx.inst_ctx.step, + emu_full_trace_vec.mem_reads.len() + ); + // Build the 'a' register value based on the source specified by the current instruction self.source_a_mem_reads_generate(instruction, &mut emu_full_trace_vec.mem_reads); @@ -1990,7 +1966,22 @@ impl<'a> Emu<'a> { (instruction.func)(&mut self.ctx.inst_ctx); // If this is a precompiled, copy input data generated by precompile call to mem_reads. + // when generate mem traces the input data containts also data_ext. if instruction.input_size > 0 { + #[cfg(feature = "minimal_trace_index_debug")] + { + let input_data_bytes = self.ctx.inst_ctx.precompiled.input_data.len() * 8; + if input_data_bytes > instruction.input_size as usize { + println!( + "MINIMAL_TRACE data_ext_len:{} input_data:{} input_size:{} mem_reads[{}..{}]", + input_data_bytes - instruction.input_size as usize, + input_data_bytes, + instruction.input_size, + emu_full_trace_vec.mem_reads.len(), + emu_full_trace_vec.mem_reads.len() + (input_data_bytes >> 3) + ); + } + } emu_full_trace_vec.mem_reads.append(&mut self.ctx.inst_ctx.precompiled.input_data); } @@ -2056,6 +2047,16 @@ impl<'a> Emu<'a> { ) -> bool { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + #[cfg(feature = "minimal_trace_index_debug")] + println!( + "MINIMAL_TRACE step_emu_trace {} {}", + self.ctx.inst_ctx.step, mem_reads_index + ); + // println!( + // "DEBUG_TRACE {:09} 0x{:08x} {:?}", + // self.ctx.inst_ctx.step, self.ctx.inst_ctx.pc, self.ctx.inst_ctx.regs + // ); + self.source_a_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); self.source_b_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); // If this is a precompiled, get the required input data from mem_reads @@ -2072,6 +2073,7 @@ impl<'a> Emu<'a> { } } + self.ctx.inst_ctx.data_ext_len = 0; (instruction.func)(&mut self.ctx.inst_ctx); self.store_c_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); @@ -2085,7 +2087,26 @@ impl<'a> Emu<'a> { &self.ctx.inst_ctx, &mut self.static_array, ); - data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); + if self.ctx.inst_ctx.data_ext_len > 0 { + // println!( + // "DETECTED DATA_EXT_LEN READ {} {} ", + // self.ctx.inst_ctx.data_ext_len, mem_reads_index + // ); + if mem_reads.len() < *mem_reads_index + self.ctx.inst_ctx.data_ext_len { + println!( + "OUT_OF_DATA_EXT({}) S:{}", + self.ctx.inst_ctx.data_ext_len, self.ctx.inst_ctx.step + ); + } + data_bus.write_to_bus( + OPERATION_BUS_ID, + operation_payload, + &mem_reads[*mem_reads_index..*mem_reads_index + self.ctx.inst_ctx.data_ext_len], + ); + *mem_reads_index += self.ctx.inst_ctx.data_ext_len; + } else { + data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); + } } // #[cfg(feature = "sp")] @@ -2109,6 +2130,12 @@ impl<'a> Emu<'a> { ) -> bool { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + #[cfg(feature = "minimal_trace_index_debug")] + println!( + "MINIMAL_TRACE step_emu_trace_no_mem_ops {} {}", + self.ctx.inst_ctx.step, mem_reads_index + ); + self.source_a_mem_reads_consume_no_mem_ops(instruction, mem_reads, mem_reads_index); self.source_b_mem_reads_consume_no_mem_ops(instruction, mem_reads, mem_reads_index); // If this is a precompiled, get the required input data from mem_reads @@ -2125,6 +2152,7 @@ impl<'a> Emu<'a> { } } + self.ctx.inst_ctx.data_ext_len = 0; (instruction.func)(&mut self.ctx.inst_ctx); self.store_c_mem_reads_consume_no_mem_ops(instruction, mem_reads, mem_reads_index); @@ -2138,7 +2166,16 @@ impl<'a> Emu<'a> { &self.ctx.inst_ctx, &mut self.static_array, ); - data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); + if self.ctx.inst_ctx.data_ext_len > 0 { + data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); + } else { + data_bus.write_to_bus( + OPERATION_BUS_ID, + operation_payload, + &mem_reads[*mem_reads_index..*mem_reads_index + self.ctx.inst_ctx.data_ext_len], + ); + *mem_reads_index += self.ctx.inst_ctx.data_ext_len; + } } // #[cfg(feature = "sp")] @@ -2232,6 +2269,13 @@ impl<'a> Emu<'a> { ) -> bool { let mut _continue = true; let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + + #[cfg(feature = "minimal_trace_index_debug")] + println!( + "MINIMAL_TRACE step_emu_traces {} {} 0x{:08x}", + self.ctx.inst_ctx.step, mem_reads_index, self.ctx.inst_ctx.pc + ); + self.source_a_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); self.source_b_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); // If this is a precompiled, get the required input data from mem_reads @@ -2245,6 +2289,8 @@ impl<'a> Emu<'a> { self.ctx.inst_ctx.precompiled.input_data.push(mem_read); } } + + self.ctx.inst_ctx.data_ext_len = 0; (instruction.func)(&mut self.ctx.inst_ctx); self.store_c_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); @@ -2257,7 +2303,17 @@ impl<'a> Emu<'a> { &self.ctx.inst_ctx, &mut self.static_array, ); - _continue = data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); + _continue = if self.ctx.inst_ctx.data_ext_len > 0 { + let data_ext_index = *mem_reads_index; + *mem_reads_index += self.ctx.inst_ctx.data_ext_len; + data_bus.write_to_bus( + OPERATION_BUS_ID, + operation_payload, + &mem_reads[data_ext_index..*mem_reads_index], + ) + } else { + data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]) + } } // Get rom bus data @@ -2276,6 +2332,7 @@ impl<'a> Emu<'a> { _continue } + #[allow(dead_code)] fn get_slice_from_mem_reads<'b>( &mut self, mem_reads: &'b [u64], @@ -2301,6 +2358,12 @@ impl<'a> Emu<'a> { } let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + #[cfg(feature = "minimal_trace_index_debug")] + println!( + "MINIMAL_TRACE step_slice_full_trace {} {} 0x{:08x}", + self.ctx.inst_ctx.step, mem_reads_index, self.ctx.inst_ctx.pc + ); + reg_trace.clear_reg_step_ranges(); self.source_a_mem_reads_consume(instruction, mem_reads, mem_reads_index, reg_trace); @@ -2318,6 +2381,7 @@ impl<'a> Emu<'a> { } } + self.ctx.inst_ctx.data_ext_len = 0; (instruction.func)(&mut self.ctx.inst_ctx); self.store_c_mem_reads_consume(instruction, mem_reads, mem_reads_index, reg_trace); @@ -2334,6 +2398,7 @@ impl<'a> Emu<'a> { let full_trace_step = Self::build_full_trace_step(instruction, &self.ctx.inst_ctx, reg_trace); + *mem_reads_index += self.ctx.inst_ctx.data_ext_len; self.ctx.inst_ctx.step += 1; full_trace_step diff --git a/emulator/src/emu_context.rs b/emulator/src/emu_context.rs index d5487768d..8bb7dacdb 100644 --- a/emulator/src/emu_context.rs +++ b/emulator/src/emu_context.rs @@ -1,8 +1,7 @@ use crate::Stats; use zisk_common::EmuTrace; use zisk_core::{ - EmulationMode, FcallInstContext, InstContext, Mem, PrecompiledInstContext, INPUT_ADDR, - MAX_INPUT_SIZE, RAM_ADDR, RAM_SIZE, REGS_IN_MAIN_TOTAL_NUMBER, ROM_ENTRY, + InstContext, INPUT_ADDR, MAX_INPUT_SIZE, RAM_ADDR, RAM_SIZE, REGS_IN_MAIN_TOTAL_NUMBER, }; /// ZisK emulator context data container, storing the state of the emulation @@ -26,22 +25,7 @@ impl EmuContext { /// RisK emulator context constructor pub fn new(input: Vec) -> EmuContext { let mut ctx = EmuContext { - inst_ctx: InstContext { - mem: Mem::default(), - a: 0, - b: 0, - c: 0, - flag: false, - sp: 0, - pc: ROM_ENTRY, - step: 0, - end: false, - error: false, - regs: [0; REGS_IN_MAIN_TOTAL_NUMBER], - emulation_mode: EmulationMode::default(), - precompiled: PrecompiledInstContext::default(), - fcall: FcallInstContext::default(), - }, + inst_ctx: InstContext::default(), tracerv: Vec::new(), tracerv_step: 0, tracerv_current_regs: [0; REGS_IN_MAIN_TOTAL_NUMBER], diff --git a/emulator/src/emu_options.rs b/emulator/src/emu_options.rs index fe4f0bf3d..ab1056ef4 100644 --- a/emulator/src/emu_options.rs +++ b/emulator/src/emu_options.rs @@ -2,7 +2,7 @@ use clap::Parser; use std::fmt; -use zisk_core::DEFAULT_MAX_STEPS_STR; +use zisk_core::{DEFAULT_MAX_STEPS, DEFAULT_MAX_STEPS_STR}; pub const ZISK_VERSION_MESSAGE: &str = concat!( env!("CARGO_PKG_VERSION"), @@ -103,7 +103,7 @@ impl Default for EmuOptions { elf: None, inputs: None, output: None, - max_steps: 0xFFF_FFFF_FFFF_FFFFF, + max_steps: DEFAULT_MAX_STEPS, print_step: None, trace: None, verbose: false, diff --git a/emulator/src/stats.rs b/emulator/src/stats.rs index 6df0b0147..ebc5be214 100644 --- a/emulator/src/stats.rs +++ b/emulator/src/stats.rs @@ -877,4 +877,9 @@ impl OpStats for Stats { self.on_memory_write(addr + 8 * index as u64, 8, 0); } } + fn add_extras(&mut self, extras: &[(u8, usize)]) { + for (opcode, count) in extras { + self.costs.ops[*opcode as usize] += *count as u64; + } + } } diff --git a/executor/Cargo.toml b/executor/Cargo.toml index 6487a4499..e6abccfdf 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -36,6 +36,7 @@ precomp-sha256f = { workspace = true } precomp-arith-eq = { workspace = true } precomp-arith-eq-384 = { workspace = true } precomp-big-int = { workspace = true } +precomp-dma = { workspace = true } sm-arith = { workspace = true } sm-binary = { workspace = true } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 1e4bf8ac3..6e5fcac02 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -816,7 +816,12 @@ impl ZiskExecutor { .remove(&global_id) .expect("Missing collectors for given global_id") .into_iter() - .map(Option::unwrap) // All are guaranteed to be Some + .enumerate() + .map(|(idx, opt)| { + opt.unwrap_or_else(|| { + panic!("Collector at index {} for global_id {} is None", idx, global_id) + }) + }) .collect() }; diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index 3aa8edb76..1abde80b6 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -8,6 +8,11 @@ use precomp_arith_eq::{ArithEqInstance, ArithEqManager}; use precomp_arith_eq_384::ArithEq384Instance; use precomp_arith_eq_384::ArithEq384Manager; use precomp_big_int::{Add256Instance, Add256Manager}; +use precomp_dma::Dma64AlignedInstance; +use precomp_dma::DmaInstance; +use precomp_dma::DmaManager; +use precomp_dma::DmaPrePostInstance; +use precomp_dma::DmaUnalignedInstance; use precomp_keccakf::{KeccakfInstance, KeccakfManager}; use precomp_sha256f::{Sha256fInstance, Sha256fManager}; use proofman_common::ProofCtx; @@ -21,6 +26,10 @@ use sm_rom::{RomInstance, RomSM}; use std::collections::{BTreeMap, HashMap}; use zisk_common::{BusDeviceMetrics, ChunkId, ComponentBuilder, Instance, InstanceCtx, Plan}; use zisk_pil::ADD_256_AIR_IDS; +use zisk_pil::DMA_64_ALIGNED_AIR_IDS; +use zisk_pil::DMA_AIR_IDS; +use zisk_pil::DMA_PRE_POST_AIR_IDS; +use zisk_pil::DMA_UNALIGNED_AIR_IDS; use zisk_pil::{ ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, MEM_ALIGN_AIR_IDS, @@ -44,6 +53,7 @@ pub enum StateMachines { ArithEqManager(Arc>), ArithEq384Manager(Arc>), Add256Manager(Arc>), + DmaManager(Arc>), } impl StateMachines { @@ -58,6 +68,7 @@ impl StateMachines { StateMachines::ArithEqManager(_) => 6, StateMachines::ArithEq384Manager(_) => 7, StateMachines::Add256Manager(_) => 8, + StateMachines::DmaManager(_) => 9, } } @@ -78,6 +89,7 @@ impl StateMachines { StateMachines::ArithEqManager(sm) => (**sm).build_planner(), StateMachines::ArithEq384Manager(sm) => (**sm).build_planner(), StateMachines::Add256Manager(sm) => (**sm).build_planner(), + StateMachines::DmaManager(sm) => (**sm).build_planner(), } } @@ -94,6 +106,7 @@ impl StateMachines { StateMachines::ArithEqManager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::ArithEq384Manager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::Add256Manager(sm) => (**sm).configure_instances(pctx, plans), + StateMachines::DmaManager(sm) => (**sm).configure_instances(pctx, plans), } } @@ -108,6 +121,7 @@ impl StateMachines { StateMachines::ArithEqManager(sm) => (**sm).build_instance(ictx), StateMachines::ArithEq384Manager(sm) => (**sm).build_instance(ictx), StateMachines::Add256Manager(sm) => (**sm).build_instance(ictx), + StateMachines::DmaManager(sm) => (**sm).build_instance(ictx), } } } @@ -187,6 +201,7 @@ impl StaticSMBundle { let mut arith_eq_counter = None; let mut arith_eq_384_counter = None; let mut add256_counter = None; + let mut dma_counter = None; for (_, sm) in self.sm.values() { match sm { @@ -219,6 +234,9 @@ impl StaticSMBundle { StateMachines::Add256Manager(add256_sm) => { add256_counter = Some((sm.type_id(), add256_sm.build_add256_counter())); } + StateMachines::DmaManager(dma_sm) => { + dma_counter = Some((sm.type_id(), dma_sm.build_dma_counter())); + } StateMachines::RomSM(_) => {} } } @@ -233,6 +251,7 @@ impl StaticSMBundle { arith_eq_counter.expect("ArithEq counter not found"), arith_eq_384_counter.expect("ArithEq384 counter not found"), add256_counter.expect("Add256 counter not found"), + dma_counter.expect("Dma counter not found"), Some(0), ) } @@ -264,6 +283,10 @@ impl StaticSMBundle { let mut arith_eq_384_collectors = Vec::new(); let mut add256_collectors = Vec::new(); let mut rom_collectors = Vec::new(); + let mut dma_collectors = Vec::new(); + let mut dma_pre_post_collectors = Vec::new(); + let mut dma_64_aligned_collectors = Vec::new(); + let mut dma_unaligned_collectors = Vec::new(); for global_idx in global_idxs { let secn_instance = secn_instances.get(global_idx).unwrap(); @@ -399,6 +422,40 @@ impl StaticSMBundle { add256_instance.build_add256_collector(ChunkId(chunk_id)); add256_collectors.push((*global_idx, add256_collector)); } + // DMA AIRS + air_id if air_id == DMA_AIR_IDS[0] => { + let dma_instance = + secn_instance.as_any().downcast_ref::>().unwrap(); + let dma_collector = dma_instance.build_dma_collector(ChunkId(chunk_id)); + dma_collectors.push((*global_idx, dma_collector)); + } + air_id if air_id == DMA_PRE_POST_AIR_IDS[0] => { + let dma_pre_post_instance = secn_instance + .as_any() + .downcast_ref::>() + .unwrap(); + let dma_pre_post_collector = + dma_pre_post_instance.build_dma_collector(ChunkId(chunk_id)); + dma_pre_post_collectors.push((*global_idx, dma_pre_post_collector)); + } + air_id if air_id == DMA_64_ALIGNED_AIR_IDS[0] => { + let dma_64_aligned_instance = secn_instance + .as_any() + .downcast_ref::>() + .unwrap(); + let dma_64_aligned_collector = + dma_64_aligned_instance.build_dma_collector(ChunkId(chunk_id)); + dma_64_aligned_collectors.push((*global_idx, dma_64_aligned_collector)); + } + air_id if air_id == DMA_UNALIGNED_AIR_IDS[0] => { + let dma_unaligned_instance = secn_instance + .as_any() + .downcast_ref::>() + .unwrap(); + let dma_unaligned_collector = + dma_unaligned_instance.build_dma_collector(ChunkId(chunk_id)); + dma_unaligned_collectors.push((*global_idx, dma_unaligned_collector)); + } air_id if air_id == ROM_AIR_IDS[0] => { let rom_instance = secn_instance.as_any().downcast_ref::().unwrap(); @@ -419,6 +476,7 @@ impl StaticSMBundle { let mut sha256f_inputs_generator = None; let mut arith_inputs_generator = None; let mut add256_inputs_generator = None; + let mut dma_inputs_generator = None; for (_, sm) in self.sm.values() { match sm { StateMachines::ArithSM(arith_sm) => { @@ -444,6 +502,9 @@ impl StaticSMBundle { add256_inputs_generator = Some(add256_sm.build_add256_input_generator()); } + StateMachines::DmaManager(dma_sm) => { + dma_inputs_generator = Some(dma_sm.build_dma_input_generator()); + } _ => {} } } @@ -460,6 +521,10 @@ impl StaticSMBundle { arith_eq_collectors, arith_eq_384_collectors, add256_collectors, + dma_collectors, + dma_pre_post_collectors, + dma_64_aligned_collectors, + dma_unaligned_collectors, rom_collectors, arith_eq_inputs_generator.expect("ArithEq input generator not found"), arith_eq_384_inputs_generator.expect("ArithEq384 input generator not found"), @@ -467,6 +532,7 @@ impl StaticSMBundle { sha256f_inputs_generator.expect("SHA256F input generator not found"), arith_inputs_generator.expect("Arith input generator not found"), add256_inputs_generator.expect("Add256 input generator not found"), + dma_inputs_generator.expect("Dma input generator not found"), ); Some(data_bus) diff --git a/executor/src/static_data_bus.rs b/executor/src/static_data_bus.rs index f2e70a274..d937060fa 100644 --- a/executor/src/static_data_bus.rs +++ b/executor/src/static_data_bus.rs @@ -10,6 +10,7 @@ use mem_common::MemCounters; use precomp_arith_eq::ArithEqCounterInputGen; use precomp_arith_eq_384::ArithEq384CounterInputGen; use precomp_big_int::Add256CounterInputGen; +use precomp_dma::DmaCounterInputGen; use precomp_keccakf::KeccakfCounterInputGen; use precomp_sha256f::Sha256fCounterInputGen; use sm_arith::ArithCounterInputGen; @@ -18,7 +19,7 @@ use sm_main::MainCounter; use zisk_common::{BusDevice, BusDeviceMetrics, BusId, PayloadType, MEM_BUS_ID, OPERATION_BUS_ID}; use zisk_core::{ ARITH_EQ_384_OP_TYPE_ID, ARITH_EQ_OP_TYPE_ID, ARITH_OP_TYPE_ID, BIG_INT_OP_TYPE_ID, - BINARY_E_OP_TYPE_ID, BINARY_OP_TYPE_ID, KECCAK_OP_TYPE_ID, PUB_OUT_OP_TYPE_ID, + BINARY_E_OP_TYPE_ID, BINARY_OP_TYPE_ID, DMA_OP_TYPE_ID, KECCAK_OP_TYPE_ID, PUB_OUT_OP_TYPE_ID, SHA256_OP_TYPE_ID, }; @@ -45,6 +46,7 @@ pub struct StaticDataBus { pub arith_eq_counter: (usize, ArithEqCounterInputGen), pub arith_eq_384_counter: (usize, ArithEq384CounterInputGen), pub add_256_counter: (usize, Add256CounterInputGen), + pub dma_counter: (usize, DmaCounterInputGen), pub rom_counter_id: Option, /// Queue of pending data transfers to be processed. pending_transfers: VecDeque<(BusId, Vec, Vec)>, @@ -63,6 +65,7 @@ impl StaticDataBus { arith_eq_counter: (usize, ArithEqCounterInputGen), arith_eq_384_counter: (usize, ArithEq384CounterInputGen), add_256_counter: (usize, Add256CounterInputGen), + dma_counter: (usize, DmaCounterInputGen), rom_counter_id: Option, ) -> Self { Self { @@ -76,6 +79,7 @@ impl StaticDataBus { arith_eq_counter, arith_eq_384_counter, add_256_counter, + dma_counter, rom_counter_id, pending_transfers: VecDeque::new(), } @@ -172,6 +176,13 @@ impl StaticDataBus { &mut self.pending_transfers, None, ), + DMA_OP_TYPE_ID => self.dma_counter.1.process_data( + &bus_id, + data, + data_ext, + &mut self.pending_transfers, + None, + ), _ => true, }, _ => true, @@ -208,6 +219,7 @@ impl DataBusTrait> for StaticDataBus> for StaticDataBus { pub add256_collector: Vec<(usize, Add256Collector)>, pub add256_inputs_generator: Add256CounterInputGen, + /// Dma collectors + pub dma_collector: Vec<(usize, DmaCollector)>, + pub dma_pre_post_collector: Vec<(usize, DmaPrePostCollector)>, + pub dma_64_aligned_collector: Vec<(usize, Dma64AlignedCollector)>, + pub dma_unaligned_collector: Vec<(usize, DmaUnalignedCollector)>, + pub dma_inputs_generator: DmaCounterInputGen, + /// ROM collector pub rom_collector: Vec<(usize, RomCollector)>, @@ -84,6 +96,7 @@ const SHA256_TYPE: u64 = ZiskOperationType::Sha256 as u64; const ARITH_EQ_TYPE: u64 = ZiskOperationType::ArithEq as u64; const ARITH_EQ_384_TYPE: u64 = ZiskOperationType::ArithEq384 as u64; const BIG_INT_OP_TYPE_ID: u64 = ZiskOperationType::BigInt as u64; +const DMA_OP_TYPE_ID: u64 = ZiskOperationType::Dma as u64; impl StaticDataBusCollect { /// Creates a new `DataBus` instance. @@ -100,6 +113,10 @@ impl StaticDataBusCollect { arith_eq_collector: Vec<(usize, ArithEqCollector)>, arith_eq_384_collector: Vec<(usize, ArithEq384Collector)>, add256_collector: Vec<(usize, Add256Collector)>, + dma_collector: Vec<(usize, DmaCollector)>, + dma_pre_post_collector: Vec<(usize, DmaPrePostCollector)>, + dma_64_aligned_collector: Vec<(usize, Dma64AlignedCollector)>, + dma_unaligned_collector: Vec<(usize, DmaUnalignedCollector)>, rom_collector: Vec<(usize, RomCollector)>, arith_eq_inputs_generator: ArithEqCounterInputGen, arith_eq_384_inputs_generator: ArithEq384CounterInputGen, @@ -107,6 +124,7 @@ impl StaticDataBusCollect { sha256f_inputs_generator: Sha256fCounterInputGen, arith_inputs_generator: ArithCounterInputGen, add256_inputs_generator: Add256CounterInputGen, + dma_inputs_generator: DmaCounterInputGen, ) -> Self { let mem_collectors_info: Vec = mem_collector.iter().map(|(_, collector)| collector.get_mem_collector_info()).collect(); @@ -123,6 +141,10 @@ impl StaticDataBusCollect { arith_eq_collector, arith_eq_384_collector, add256_collector, + dma_collector, + dma_pre_post_collector, + dma_64_aligned_collector, + dma_unaligned_collector, rom_collector, arith_eq_inputs_generator, arith_eq_384_inputs_generator, @@ -130,6 +152,7 @@ impl StaticDataBusCollect { sha256f_inputs_generator, arith_inputs_generator, add256_inputs_generator, + dma_inputs_generator, pending_transfers: VecDeque::with_capacity(64), mem_collectors_info, } @@ -318,6 +341,52 @@ impl StaticDataBusCollect { Some(&self.mem_collectors_info), ); } + DMA_OP_TYPE_ID => { + for (_, dma_collector) in &mut self.dma_collector { + dma_collector.process_data( + &bus_id, + data, + data_ext, + &mut self.pending_transfers, + None, + ); + } + for (_, dma_pre_post_collector) in &mut self.dma_pre_post_collector { + dma_pre_post_collector.process_data( + &bus_id, + data, + data_ext, + &mut self.pending_transfers, + None, + ); + } + for (_, dma_64_aligned_collector) in &mut self.dma_64_aligned_collector { + dma_64_aligned_collector.process_data( + &bus_id, + data, + data_ext, + &mut self.pending_transfers, + None, + ); + } + for (_, dma_unaligned_collector) in &mut self.dma_unaligned_collector { + dma_unaligned_collector.process_data( + &bus_id, + data, + data_ext, + &mut self.pending_transfers, + None, + ); + } + + self.dma_inputs_generator.process_data( + &bus_id, + data, + data_ext, + &mut self.pending_transfers, + Some(&self.mem_collectors_info), + ); + } _ => {} }, ROM_BUS_ID => { @@ -415,6 +484,22 @@ impl DataBusTrait>> result.push((Some(id), Some(Box::new(collector) as Box>))); } + for (id, collector) in self.dma_collector { + result.push((Some(id), Some(Box::new(collector) as Box>))); + } + + for (id, collector) in self.dma_pre_post_collector { + result.push((Some(id), Some(Box::new(collector) as Box>))); + } + + for (id, collector) in self.dma_64_aligned_collector { + result.push((Some(id), Some(Box::new(collector) as Box>))); + } + + for (id, collector) in self.dma_unaligned_collector { + result.push((Some(id), Some(Box::new(collector) as Box>))); + } + for (id, collector) in self.rom_collector { result.push((Some(id), Some(Box::new(collector) as Box>))); } diff --git a/pil/config.pil b/pil/config.pil new file mode 100644 index 000000000..954a45829 --- /dev/null +++ b/pil/config.pil @@ -0,0 +1,6 @@ +const int MAIN_STEP_BITS = 36; +const int MAX_STEPS = 1 << MAIN_STEP_BITS; +const int MEM_STEP_BITS = MAIN_STEP_BITS + 2; +const int REG_STEP_BITS = MAIN_STEP_BITS + 2; +const int ADDR_BITS = 32; +const int ADDR_W_BITS = ADDR_BITS - 3; diff --git a/pil/operations.pil b/pil/operations.pil index 2d36e2dc2..35ffa2144 100644 --- a/pil/operations.pil +++ b/pil/operations.pil @@ -83,6 +83,12 @@ const int OP_REMU_W = 0xBD; const int OP_DIV_W = 0xBE; const int OP_REM_W = 0xBF; +const int OP_PARAM = 0xC0; +const int OP_PARAMS = 0xC1; + +const int OP_DMA_MEMCPY = 0xD0; +const int OP_DMA_MEMCMP = 0xD1; + const int OP_ARITH_384_MOD = 0xE2; const int OP_EC_ADD_BLS12_381 = 0xE3; const int OP_EC_DBL_BLS12_381 = 0xE4; diff --git a/pil/opids.pil b/pil/opids.pil index 1378e1529..c1ee73de8 100644 --- a/pil/opids.pil +++ b/pil/opids.pil @@ -11,7 +11,6 @@ const int ROM_BUS_ID = 7890; // Memory ids const int MEMORY_ID = 10; const int MEMORY_ALIGN_ROM_ID = 133; -const int DUAL_BYTE_TABLE_ID = 88; // Arith table ids const int ARITH_TABLE_ID = 331; @@ -28,4 +27,14 @@ const int BINARY_EXTENSION_FROPS_TABLE_ID = 5012; // Precompiles const int ARITH_EQ_LT_TABLE_ID = 5002; -const int KECCAKF_TABLE_ID = 126; \ No newline at end of file +const int KECCAKF_TABLE_ID = 126; + + +// DMA +const int DMA_BUS_ID = 8000; +const int DMA_ROM_ID = 8001; +const int DMA_PRE_POST_TABLE_ID = 8002; + +// Ranges +const int DUAL_RANGE_7_BITS_ID = 77; +const int DUAL_RANGE_BYTE_ID = 88; diff --git a/pil/src/constants.rs b/pil/src/constants.rs new file mode 100644 index 000000000..5fcb5657b --- /dev/null +++ b/pil/src/constants.rs @@ -0,0 +1,5 @@ +pub const DUAL_RANGE_BYTE_ID: usize = 88; +pub const DUAL_RANGE_7_BITS_ID: usize = 77; +pub const DMA_ROM_ID: usize = 8001; +pub const DMA_PRE_POST_TABLE_ID: usize = 8002; +pub const DMA_PRE_POST_TABLE_SIZE: usize = 280; diff --git a/pil/src/lib.rs b/pil/src/lib.rs index 77de853a8..92c1ba497 100644 --- a/pil/src/lib.rs +++ b/pil/src/lib.rs @@ -1,3 +1,5 @@ +mod constants; mod pil_helpers; +pub use constants::*; pub use pil_helpers::*; diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 2be142088..d479ee7bd 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "b43b0a9602c2f7aae31d49817972ddde0b08eaf9ac20d3fa0975f58fb093d52b"; +pub const PILOUT_HASH: &str = "df61da337695fede0a6f599b8ccabd837dee610600b52f0567af38ccd6c0e808"; //AIRGROUP CONSTANTS @@ -113,7 +113,7 @@ values!(ZiskPublicValues { }); values!(ZiskProofValues { - enable_input_data: F, enable_rom_data: F, + enable_input_data: F, enable_rom_data: F, enable_dma_64_aligned: F, enable_dma_64_aligned_input: F, enable_dma_unaligned: F, }); trace_row!(DmaFixedRow { @@ -122,7 +122,7 @@ trace_row!(DmaFixedRow { pub type DmaFixed = GenericTrace, 2097152, 0, 0>; trace_row!(DmaTraceRow { - h_count:ubit(24), count_ge_32:bit, l_count:ubit(6), h_src64:ubit(21), l_src64:u8, src_offset:ubit(3), h_dst64:ubit(21), l_dst64:u8, dst_offset:ubit(3), main_step:u32, sel:bit, use_pre:bit, use_memcpy:bit, use_post:bit, src64_inc_by_pre:bit, pre_count:ubit(3), l_count64:ubit(3), src_offset_after_pre:ubit(3), reg_prev_mem_step:bit, + sel:bit, h_count:ubit(24), count_lt_256:bit, l_count:ubit(9), h_src64:ubit(22), l_src64:ubit(7), src_offset:ubit(3), h_dst64:ubit(22), l_dst64:ubit(7), dst_offset:ubit(3), main_step:ubit(36), use_pre:bit, use_memcpy:bit, use_post:bit, src64_inc_by_pre:bit, pre_count:ubit(3), l_count64:ubit(9), src_offset_after_pre:ubit(3), }); pub type DmaTrace = GenericTrace, 2097152, 0, 0>; @@ -136,7 +136,7 @@ trace_row!(Dma64AlignedFixedRow { pub type Dma64AlignedFixed = GenericTrace, 2097152, 0, 1>; trace_row!(Dma64AlignedTraceRow { - main_step:u32, src64:ubit(29), dst64:ubit(29), count:u32, value:[[u32; 2]; 4], sel_op:[bit; 4], seq_end:bit, is_mem_eq:bit, + main_step:ubit(36), dst64:ubit(29), count:u32, sel_op:[bit; 4], seq_end:bit, is_mem_eq:bit, value:[[u32; 2]; 4], src64:ubit(29), previous_seq_end:bit, }); pub type Dma64AlignedTrace = GenericTrace, 2097152, 0, 1>; @@ -150,7 +150,7 @@ trace_row!(DmaUnalignedFixedRow { pub type DmaUnalignedFixed = GenericTrace, 2097152, 0, 2>; trace_row!(DmaUnalignedTraceRow { - main_step:u32, src64:ubit(29), dst64:ubit(29), count:u32, value:[u32; 2], seq_end:bit, sel:bit, previous_seq_end:bit, is_mem_eq:bit, offset_7:bit, offset_6:bit, offset_5:bit, offset_4:bit, offset_3:bit, offset_2:bit, read_bytes:[u8; 8], write_value:[u32; 2], + main_step:ubit(36), src64:ubit(29), dst64:ubit(29), count:u32, seq_end:bit, previous_seq_end:bit, is_mem_eq:bit, offset_7:bit, offset_6:bit, offset_5:bit, offset_4:bit, offset_3:bit, offset_2:bit, read_bytes:[u8; 8], no_last_no_seq_end:bit, write_value:[u32; 2], }); pub type DmaUnalignedTrace = GenericTrace, 2097152, 0, 2>; @@ -164,7 +164,7 @@ trace_row!(DmaPrePostFixedRow { pub type DmaPrePostFixed = GenericTrace, 2097152, 0, 3>; trace_row!(DmaPrePostTraceRow { - main_step:u32, src_addr:ubit(29), dst_addr:ubit(29), dst_offset:ubit(3), count:ubit(3), offset_7:bit, offset_6:bit, offset_5:bit, offset_4:bit, offset_3:bit, offset_2:bit, offset_1:bit, enabled:bit, enabled_second_read:bit, bytes:[u8; 24], selb:[bit; 8], write_value:[u32; 2], + main_step:ubit(36), src64:ubit(29), dst64:ubit(29), dst_offset:ubit(3), src_offset:ubit(3), count:ubit(3), selread:[bit; 7], dst_offset_gt_src_offset:bit, enabled:bit, enabled_second_read:bit, bytes:[u8; 24], selb:[bit; 8], write_value:[u32; 4], }); pub type DmaPrePostTrace = GenericTrace, 2097152, 0, 3>; @@ -178,7 +178,7 @@ trace_row!(MainFixedRow { pub type MainFixed = GenericTrace, 4194304, 0, 4>; trace_row!(MainTraceRow { - a:[u32; 2], b:[u32; 2], c:[u32; 2], flag:bit, pc:u32, a_src_imm:bit, a_src_mem:bit, a_offset_imm0:u64, a_imm1:u32, op_with_step:bit, b_src_imm:bit, b_src_mem:bit, b_offset_imm0:u64, b_imm1:u32, b_src_ind:bit, ind_width:ubit(4), is_external_op:bit, op:u8, store_pc:bit, store_mem:bit, store_ind:bit, store_offset:u64, set_pc:bit, jmp_offset1:u64, jmp_offset2:u64, m32:bit, addr1:u32, a_reg_prev_mem_step:ubit(40), b_reg_prev_mem_step:ubit(40), store_reg_prev_mem_step:ubit(40), store_reg_prev_value:[u32; 2], a_src_reg:bit, b_src_reg:bit, store_reg:bit, + a:[u32; 2], b:[u32; 2], c:[u32; 2], flag:bit, pc:u32, a_src_imm:bit, a_src_mem:bit, a_offset_imm0:u64, a_imm1:u32, op_with_step:bit, b_src_imm:bit, b_src_mem:bit, b_offset_imm0:u64, b_imm1:u32, b_src_ind:bit, ind_width:ubit(4), is_external_op:bit, op:u8, store_pc:bit, store_mem:bit, store_ind:bit, store_offset:u64, set_pc:bit, jmp_offset1:u64, jmp_offset2:u64, m32:bit, addr1:u32, a_reg_prev_mem_step:ubit(38), b_reg_prev_mem_step:ubit(38), store_reg_prev_mem_step:ubit(38), store_reg_prev_value:[u32; 2], a_src_reg:bit, b_src_reg:bit, store_reg:bit, }); pub type MainTrace = GenericTrace, 4194304, 0, 4>; @@ -203,7 +203,7 @@ trace_row!(MemFixedRow { pub type MemFixed = GenericTrace, 4194304, 0, 6>; trace_row!(MemTraceRow { - addr:ubit(29), step:ubit(40), sel:bit, addr_changes:bit, step_dual:ubit(40), sel_dual:bit, value:[u32; 2], wr:bit, previous_step:ubit(40), increment:[ubit(18); 2], read_same_addr:bit, + addr:ubit(29), step:ubit(38), sel:bit, addr_changes:bit, step_dual:ubit(38), sel_dual:bit, value:[u32; 2], wr:bit, previous_step:ubit(40), increment:[ubit(18); 2], read_same_addr:bit, }); pub type MemTrace = GenericTrace, 4194304, 0, 6>; @@ -217,7 +217,7 @@ trace_row!(RomDataFixedRow { pub type RomDataFixed = GenericTrace, 2097152, 0, 7>; trace_row!(RomDataTraceRow { - addr:ubit(29), step:ubit(40), sel:bit, addr_changes:bit, value:[u32; 2], + addr:ubit(29), step:ubit(38), sel:bit, addr_changes:bit, value:[u32; 2], }); pub type RomDataTrace = GenericTrace, 2097152, 0, 7>; @@ -231,7 +231,7 @@ trace_row!(InputDataFixedRow { pub type InputDataFixed = GenericTrace, 2097152, 0, 8>; trace_row!(InputDataTraceRow { - addr:ubit(29), step:ubit(40), sel:bit, addr_changes:bit, value_word:[u16; 4], is_free_read:bit, + addr:ubit(29), step:ubit(38), sel:bit, addr_changes:bit, value_word:[u16; 4], is_free_read:bit, }); pub type InputDataTrace = GenericTrace, 2097152, 0, 8>; @@ -422,12 +422,12 @@ pub type Sha256fTracePacked = GenericTrace, 262144, trace_row!(SpecifiedRangesFixedRow { - RANGE: [F; 35], __L1__: F, + RANGE: [F; 33], __L1__: F, }); pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 22>; trace_row!(SpecifiedRangesTraceRow { - mul:[F; 35], + mul:[F; 33], }); pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 22>; @@ -461,7 +461,7 @@ pub type RomRomTrace = GenericTrace, 4194304, 0, 5, 0>; values!(Dma64AlignedAirValues { - segment_id: F, segment_previous_seq_end: F, segment_previous_src64: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count: F, segment_previous_is_mem_eq: F, segment_last_seq_end: F, segment_last_src64: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count: F, segment_last_is_mem_eq: F, is_last_segment: F, im_direct: [FieldExtension; 2], + segment_id: F, segment_previous_seq_end: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count: F, segment_previous_is_mem_eq: F, segment_last_seq_end: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count: F, segment_last_is_mem_eq: F, is_last_segment: F, segment_previous_src64: F, segment_last_src64: F, last_count_chunk: [F; 2], padding_size: F, im_direct: [FieldExtension; 5], }); values!(DmaUnalignedAirValues { @@ -612,42 +612,42 @@ pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ (0, 0, PackedInfoConst { is_packed: true, num_packed_words: 3, - unpack_info: &[24, 1, 6, 21, 8, 3, 21, 8, 3, 32, 1, 1, 1, 1, 1, 3, 3, 3, 1], + unpack_info: &[1, 24, 1, 9, 22, 7, 3, 22, 7, 3, 36, 1, 1, 1, 1, 3, 9, 3], }), (0, 1, PackedInfoConst { is_packed: true, - num_packed_words: 6, - unpack_info: &[32, 29, 29, 32, 32, 32, 32, 32, 32, 32, 32, 32, 1, 1, 1, 1, 1, 1], + num_packed_words: 7, + unpack_info: &[36, 29, 32, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 29, 1], }), (0, 2, PackedInfoConst { is_packed: true, - num_packed_words: 6, - unpack_info: &[32, 29, 29, 32, 32, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32], + num_packed_words: 5, + unpack_info: &[36, 29, 29, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 32, 32], }), (0, 3, PackedInfoConst { is_packed: true, - num_packed_words: 6, - unpack_info: &[32, 29, 29, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32], + num_packed_words: 7, + unpack_info: &[36, 29, 29, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32], }), (0, 4, PackedInfoConst { is_packed: true, num_packed_words: 14, - unpack_info: &[32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, 64, 1, 64, 64, 1, 32, 40, 40, 40, 32, 32, 1, 1, 1], + unpack_info: &[32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, 64, 1, 64, 64, 1, 32, 38, 38, 38, 32, 32, 1, 1, 1], }), (0, 6, PackedInfoConst { is_packed: true, num_packed_words: 4, - unpack_info: &[29, 40, 1, 1, 40, 1, 32, 32, 1, 40, 18, 18, 1], + unpack_info: &[29, 38, 1, 1, 38, 1, 32, 32, 1, 40, 18, 18, 1], }), (0, 7, PackedInfoConst { is_packed: true, num_packed_words: 3, - unpack_info: &[29, 40, 1, 1, 32, 32], + unpack_info: &[29, 38, 1, 1, 32, 32], }), (0, 8, PackedInfoConst { is_packed: true, num_packed_words: 3, - unpack_info: &[29, 40, 1, 1, 16, 16, 16, 16, 1], + unpack_info: &[29, 38, 1, 1, 16, 16, 16, 16, 1], }), (0, 9, PackedInfoConst { is_packed: true, diff --git a/pil/zisk.pil b/pil/zisk.pil index e2c7e48a8..63a845be1 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -1,35 +1,31 @@ -const int MAIN_STEP_BITS = 32; -const int MAX_STEPS = 1 << MAIN_STEP_BITS; -const int MEM_STEP_BITS = MAIN_STEP_BITS * 4; -const int ADDR_BITS = 32; -const int ADDR_W_BITS = ADDR_BITS - 3; - -require "std_direct.pil"; - -require "operations.pil"; -require "opids.pil"; - -require "rom/pil/rom.pil"; -require "main/pil/main.pil"; -require "mem/pil/mem.pil"; -require "mem/pil/mem_align.pil"; -require "mem/pil/mem_align_byte.pil"; -require "frequent-ops/pil/frequent_ops.pil"; -require "binary/pil/binary.pil"; -require "binary/pil/binary_extension.pil"; -require "binary/pil/binary_add.pil"; -require "arith/pil/arith.pil"; -require "big_int/pil/big_int_add.pil"; -require "arith_eq/pil/arith_eq.pil"; -require "arith_eq_384/pil/arith_eq_384.pil"; -require "keccakf/pil/keccakf.pil"; -require "sha256f/pil/sha256f.pil"; +require "std_direct.pil" + +require "config.pil" +require "operations.pil" +require "opids.pil" + +require "rom/pil/rom.pil" +require "main/pil/main.pil" +require "mem/pil/mem.pil" +require "mem/pil/mem_align.pil" +require "mem/pil/mem_align_byte.pil" +require "frequent-ops/pil/frequent_ops.pil" +require "binary/pil/binary.pil" +require "binary/pil/binary_extension.pil" +require "binary/pil/binary_add.pil" +require "arith/pil/arith.pil" +require "big_int/pil/big_int_add.pil" +require "arith_eq/pil/arith_eq.pil" +require "arith_eq_384/pil/arith_eq_384.pil" +require "keccakf/pil/keccakf.pil" +require "sha256f/pil/sha256f.pil" require "dma/pil/dma.pil" require "dma/pil/dma_rom.pil" require "dma/pil/dma_pre_post_table.pil" require "dma/pil/dma_pre_post.pil" require "dma/pil/dma_unaligned.pil" require "dma/pil/dma_64_aligned.pil" +require "dma/pil/dual_range.pil" proofval enable_input_data; enable_input_data * (1 - enable_input_data); @@ -37,9 +33,22 @@ enable_input_data * (1 - enable_input_data); proofval enable_rom_data; enable_rom_data * (1 - enable_rom_data); +proofval enable_dma_64_aligned; +enable_dma_64_aligned * (1 - enable_dma_64_aligned); + +proofval enable_dma_64_aligned_input; +enable_dma_64_aligned_input * (1 - enable_dma_64_aligned_input); + +proofval enable_dma_unaligned; +enable_dma_unaligned * (1 - enable_dma_unaligned); + const int PUBLIC_INPUTS_64_BITS = 32; // 32 x 64 bits = 2048 bits public inputs[PUBLIC_INPUTS_64_BITS * 2]; // 2 x 32-bits = 64 bits +function range_dual_byte(expr byte_a, expr byte_b, expr sel = 1) { + lookup_assumes(DUAL_RANGE_BYTE_ID, expressions: [byte_a, byte_b], sel: sel); +} + airgroup Zisk { // Virtual Tables Configuration set_max_std_tables_bits(20); @@ -49,11 +58,14 @@ airgroup Zisk { BINARY_TABLE_ID, MEMORY_ALIGN_ROM_ID, KECCAKF_TABLE_ID, DMA_ROM_ID, DMA_PRE_POST_TABLE_ID]); set_group_virtual_tables(table_ids: [BINARY_EXTENSION_FROPS_TABLE_ID, BINARY_FROPS_TABLE_ID, ARITH_FROPS_TABLE_ID]); + virtual DualRange(id: DUAL_RANGE_7_BITS_ID, min1: 0, max1: P2_7-1, min2: 0, max2: P2_7-1) alias DualRange7Bits; + virtual DualRange(id: DUAL_RANGE_BYTE_ID, min1: 0, max1: P2_8-1, min2: 0, max2: P2_8-1) alias DualByte; + Dma(); virtual DmaRom(); - Dma64Aligned(); - // Dma32AlignedOp(); - DmaUnaligned(); + Dma64Aligned(enable: enable_dma_64_aligned); + // Dma64Aligned(enable: enable_dma_64_aligned_input, src_is_free_input: 1) alias Dma64AlignedInput; + DmaUnaligned(enable: enable_dma_unaligned); DmaPrePost(); virtual DmaPrePostTable(); // Main Program @@ -70,8 +82,7 @@ airgroup Zisk { MemAlignByte(N: 2**22, read: 1, write: 0) alias MemAlignReadByte; MemAlignByte(N: 2**22, read: 0, write: 1) alias MemAlignWriteByte; virtual MemAlignRom(); - virtual DualByte(); - + // Standard operations Arith(N: 2**21); virtual ArithTable(); diff --git a/precompiles/big_int/src/add256.rs b/precompiles/big_int/src/add256.rs index acea6702d..b3418636d 100644 --- a/precompiles/big_int/src/add256.rs +++ b/precompiles/big_int/src/add256.rs @@ -140,19 +140,19 @@ impl Add256SM { let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); let trace_rows = trace.buffer.as_mut_slice(); - // Determinar tamaño óptimo de chunks + // Calculate optimal chunk size let num_threads = rayon::current_num_threads(); let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); - // Procesar en chunks para compartir arrays locales de multiplicities + // Process in chunks to allow per-chunk local multiplicities arrays let local_multiplicities_vec: Vec> = flat_inputs .par_chunks(chunk_size) .zip(trace_rows.par_chunks_mut(chunk_size)) .map(|(input_chunk, trace_chunk)| { - // Array local compartido por este chunk + // Local array shared by this chunk let mut local_multiplicities = vec![0u32; 1 << 16]; - // Procesar todos los inputs del chunk + // Sum all local arrays into a global one for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { self.process_slice(input, trace_row, &mut local_multiplicities); } @@ -161,7 +161,7 @@ impl Add256SM { }) .collect(); - // Sumar todos los arrays locales en uno global + // Sum all local arrays into a global one let mut global_multiplicities = vec![0u32; 1 << 16]; for local_multiplicities in local_multiplicities_vec { for (i, count) in local_multiplicities.iter().enumerate() { @@ -169,7 +169,7 @@ impl Add256SM { } } - // Enviar el resultado final al std + // Send final result to std self.std.range_checks(self.range_id, global_multiplicities); timer_stop_and_log_trace!(ADD256_TRACE); diff --git a/precompiles/common/src/lib.rs b/precompiles/common/src/lib.rs index a575a03b9..7a00db84f 100644 --- a/precompiles/common/src/lib.rs +++ b/precompiles/common/src/lib.rs @@ -1,3 +1,5 @@ +//! Common utilities and helpers for Zisk precompiles. + mod goldilocks_constants; pub use goldilocks_constants::{get_ks, GOLDILOCKS_GEN, GOLDILOCKS_K}; @@ -6,14 +8,17 @@ use std::collections::VecDeque; use zisk_common::{BusId, MEM_BUS_ID}; use zisk_core::InstContext; +/// Represents a precompile operation code. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct PrecompileCode(u16); impl PrecompileCode { + /// Creates a new precompile code from a u16 value. pub fn new(value: u16) -> Self { PrecompileCode(value) } + /// Returns the underlying u16 value of the precompile code. pub fn value(&self) -> u16 { self.0 } @@ -31,21 +36,32 @@ impl From for u16 { } } +/// Context for precompile execution. pub struct PrecompileContext {} +/// Trait for implementing precompile calls. pub trait PrecompileCall: Send + Sync { + /// Executes the precompile operation with the given opcode and instruction context. + /// Returns an optional tuple containing the result value and a boolean flag. fn execute(&self, opcode: PrecompileCode, ctx: &mut InstContext) -> Option<(u64, bool)>; } +/// Helper functions for memory bus operations. pub struct MemBusHelpers {} +/// Memory load operation code. const MEMORY_LOAD_OP: u64 = 1; +/// Memory store operation code. const MEMORY_STORE_OP: u64 = 2; +/// Base step for memory operations. const MEM_STEP_BASE: u64 = 1; +/// Maximum number of memory operations per main step. const MAX_MEM_OPS_BY_MAIN_STEP: u64 = 4; impl MemBusHelpers { + /// Generates an aligned memory load operation. + /// The address must be 8-byte aligned. pub fn mem_aligned_load( addr: u32, step: u64, @@ -67,6 +83,8 @@ impl MemBusHelpers { vec![], )); } + /// Generates an aligned memory write operation. + /// The address must be 8-byte aligned. pub fn mem_aligned_write( addr: u32, step: u64, @@ -88,6 +106,8 @@ impl MemBusHelpers { vec![], )); } + /// Generates an aligned memory operation (load or write). + /// The address must be 8-byte aligned. pub fn mem_aligned_op( addr: u32, step: u64, @@ -109,44 +129,94 @@ impl MemBusHelpers { vec![], )); } + /// Generates multiple aligned memory load operations from a slice of values. + /// The address must be 8-byte aligned. pub fn mem_aligned_load_from_slice( addr: u32, step: u64, values: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { assert!(addr % 8 == 0); let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2; for (i, &value) in values.iter().enumerate() { pending.push_back(( MEM_BUS_ID, - vec![MEMORY_LOAD_OP, (addr as usize + i * 8) as u64, mem_step, 8, 0, 0, value], + vec![MEMORY_LOAD_OP, (addr as usize + i * 8) as u64, mem_step, 8, value, 0, 0], + vec![], )); } } + /// Generates multiple aligned memory write operations from a slice of values. + /// The address must be 8-byte aligned. pub fn mem_aligned_write_from_slice( addr: u32, step: u64, values: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { assert!(addr % 8 == 0); let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3; for (i, &value) in values.iter().enumerate() { pending.push_back(( MEM_BUS_ID, - vec![MEMORY_STORE_OP, (addr as usize + i * 8) as u64, mem_step, 8, value, 0, 0], + vec![MEMORY_STORE_OP, (addr as usize + i * 8) as u64, mem_step, 8, 0, 0, value], + vec![], + )); + } + } + /// Generates aligned memory writes from an unaligned read slice using the specified source offset. + /// The number of writes generated is `values.len() - 1` because the last value is not enough to + /// create a full 8-byte write. This function is useful to use the same slice of values to generate + /// first aligned reads and then aligned writes. + /// The address must be 8-byte aligned. + pub fn mem_aligned_write_from_read_unaligned_slice( + addr: u32, + step: u64, + src_offset: u8, + values: &[u64], + pending: &mut VecDeque<(BusId, Vec, Vec)>, + ) { + assert!(addr % 8 == 0); + let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3; + let write_count = values.len() - 1; + for i in 0..write_count { + let write_value = match src_offset { + 1 => (values[i] >> 8) | (values[i + 1] << 56), + 2 => (values[i] >> 16) | (values[i + 1] << 48), + 3 => (values[i] >> 24) | (values[i + 1] << 40), + 4 => (values[i] >> 32) | (values[i + 1] << 32), + 5 => (values[i] >> 40) | (values[i + 1] << 24), + 6 => (values[i] >> 48) | (values[i + 1] << 16), + 7 => (values[i] >> 56) | (values[i + 1] << 8), + _ => panic!("invalid src_offset {src_offset} on DmaUnaligned"), + }; + pending.push_back(( + MEM_BUS_ID, + vec![ + MEMORY_STORE_OP, + (addr as usize + i * 8) as u64, + mem_step, + 8, + 0, + 0, + write_value, + ], + vec![], )); } } + /// Returns the memory read step for the given step number. pub fn get_mem_read_step(step: u64) -> u64 { MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2 } + /// Returns the memory write step for the given step number. pub fn get_mem_write_step(step: u64) -> u64 { MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3 } } +/// Calculates the base-2 logarithm of n (floor). pub fn log2(n: usize) -> usize { let mut res = 0; let mut n = n; diff --git a/precompiles/dma/Cargo.toml b/precompiles/dma/Cargo.toml index 10f4da693..348ab34db 100644 --- a/precompiles/dma/Cargo.toml +++ b/precompiles/dma/Cargo.toml @@ -12,6 +12,7 @@ zisk-core = { workspace = true } zisk-common = { workspace = true } zisk-pil = { workspace = true } precompiles-common = { workspace = true } +precompiles-helpers = { workspace = true } sm-mem = { workspace = true } mem-common = { workspace = true } lib-c = { workspace = true } @@ -29,6 +30,7 @@ generic-array = "0.14" [features] default = [] dma_memcmp = [] +debug_dma = [] packed = [] no_lib_link = ["proofman-common/no_lib_link"] diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] diff --git a/precompiles/dma/pil/dma.pil b/precompiles/dma/pil/dma.pil index 4cd487054..7aff9e70c 100644 --- a/precompiles/dma/pil/dma.pil +++ b/precompiles/dma/pil/dma.pil @@ -1,6 +1,3 @@ -const int DMA_BUS_ID = 8000; -const int DMA_ROM_ID = 8001; - const int DMA_MEM_CPY = 1; const int DMA_MEM_PRE_POST = 2; const int DMA_MEM_EQ = 3; @@ -10,7 +7,8 @@ const int MEM_CPY_CONT_ID = 8201; -airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const int selectors = 1) { +airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const int selectors = 1, + const int operation_bus_id = OPERATION_BUS_ID) { assert(selectors == 0 || selectors == 1); assert(op_x_row > 1); @@ -70,50 +68,82 @@ airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const i // without written only once // write // - - // read count from register directly - // assuming max memory of 512Mb (29 bits) - // count: 21 bits RC + 5 bits T - col witness bits(24) h_count; - col witness bits(1) count_ge_32; - col witness bits(6) l_count; // 0..31 + 32 = 6 bits - // l_count range verified with dma_rom table - const expr count = h_count * 32 + l_count; + col witness bits(1) sel; + sel * (1 - sel) === 0; - count_ge_32 * (1 - count_ge_32) === 0; - (1 - count_ge_32) * h_count === 0; + // read count from register directly - range_check(expression: h_count - count_ge_32, min: 0, max: 2**21-1); + // assuming max memory of 4GB (32 bits) + // count: 24 bits RC + 8 bits T + col witness bits(24) h_count; + // What is count_lt_256 used for? + // + // If count were split in the traditional way into h_count|l_count there would be a problem + // because the DMA ROM has to check whether the count is small. + // + // But what would happen if the bits of l_count were 0 while h_count was not? + // The DMA ROM could not distinguish whether count is 0 or, for example, 1024 = 4|0 + // + // To avoid this, was defined that in such a case one is subtracted from h_count and 256 is added + // to l_count so that the overall count value remains the same and so that for any k up to 511, + // if l_count > k ==> count > k. + // + // To ensure this condition a flag count_lt_256 must be enforced to verify that h_count is actually + // 0 when l_count < 256, and the ROM verifies the consistency between count_lt_256 and l_count. + + col witness bits(1) count_lt_256; + col witness bits(9) l_count; // 0..255 + 256 = 9 bits + const expr count = h_count * 256 + l_count; + + count_lt_256 * (1 - count_lt_256) === 0; + + // constraint (1) + count_lt_256 * h_count === 0; + + // count count count cons Range h_count * 256 + // 31..8 7..0 h_count lt_256 (1) l_count Check Rom + l_count + // ----- ----- ------- ------ ---- ------- ----- ---- ------------- + // 0 x 0 1 OK x OK OK 0 | x OK + // 0 x 0 0 OK x OK FAIL 0 | x OK <== DETECTED + // 1 x 0 0 OK 256 + x OK OK 1 | x OK + // 1 x 0 1 OK 256 + x OK FAIL 1 | x OK <== DETECTED + // 1 x 1 1 FAIL x OK OK 1 | x OK <== DETECTED + // y > 1 x y - 1 0 OK 256 + x OK y | x OK + // MAX x MAX - 1 0 OK 256 + x OK MAX | x OK + // 0 x -1 0 OK 256 + x FAIL 0 | x OK <== DETECTED + // MAX x MAX 0 OK 256 + x FAIL MAX+1 | x FAIL <== DETECTED + // - - - - - x < 0 - FAIL - | - - <== DETECTED + // - - - - - x >=512 - FAIL - | - - <== DETECTED + + range_check(expression: h_count, min: 0, max: 2**24-1, sel: sel); // 32 bit address - // 21 bits RC + 8 bits RC + 3 bits T - col witness bits(21) h_src64; - col witness bits(8) l_src64; + // 22 bits RC + 5 bits RC + 3 bits T + col witness bits(22) h_src64; + col witness bits(7) l_src64; col witness bits(3) src_offset; - const expr src = h_src64 * 2**21 + l_src64 * 2**3 + src_offset; - const expr src64 = h_src64 * 2**21 + l_src64 * 2**3; + const expr src = h_src64 * 2**10 + l_src64 * 2**3 + src_offset; + const expr src64 = h_src64 * 2**10 + l_src64 * 2**3; // src_offset range verified with dma_rom table // 32 bit address - // 21 bits RC + 8 bits RC + 3 bits T - col witness bits(21) h_dst64; - col witness bits(8) l_dst64; + // 22 bits RC + 7 bits RC + 3 bits T + col witness bits(22) h_dst64; + col witness bits(7) l_dst64; col witness bits(3) dst_offset; - const expr dst = h_dst64 * 2**21 + l_dst64 * 2**3 + dst_offset; - const expr dst64 = h_dst64 * 2**21 + l_dst64 * 2**3; + const expr dst = h_dst64 * 2**10 + l_dst64 * 2**3 + dst_offset; + const expr dst64 = h_dst64 * 2**10 + l_dst64 * 2**3; // dst_offset range verified with dma_rom table col witness bits(MAIN_STEP_BITS) main_step; - col witness bits(1) sel; - - sel * (1 - sel) === 0; - range_check(expression: h_src64, min: 0, max: 2**21-1); - range_check(expression: h_dst64, min: 0, max: 2**21-1); - range_dual_byte(l_src64, l_dst64); + range_check(expression: h_src64, min: 0, max: 2**22-1, sel: sel); + range_check(expression: h_dst64, min: 0, max: 2**22-1, sel: sel); + + lookup_assumes(DUAL_RANGE_7_BITS_ID, expressions: [l_src64, l_dst64], sel: sel); col witness bits(1) use_pre; // use memcpy_pre operation col witness bits(1) use_memcpy; // use memcpy operation @@ -125,31 +155,48 @@ airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const i use_post * (1 - use_post) === 0; src64_inc_by_pre * (1 - src64_inc_by_pre) === 0; - const expr flags = use_pre + use_memcpy * 2 + use_post * 4 + src64_inc_by_pre * 8; + const expr flags = use_pre + use_memcpy * 2 + use_post * 4 + src64_inc_by_pre * 8 + count_lt_256 * 16; col witness bits(3) pre_count; // number of bytes of memcpy_pre operation - col witness bits(3) l_count64; // number of 64 bits in l_count after substract pre_count and post_count + col witness bits(9) l_count64; // number of 64 bits words in l_count after substract pre_count and post_count col witness bits(3) src_offset_after_pre; // src_offset after apply memcpy_pre operation + + const expr post_count = count - pre_count - h_count * 256 - l_count64 * 8; - lookup_assumes(DMA_ROM_ID, expressions: [dst_offset, src_offset, l_count, flags, pre_count, src_offset_after_pre, l_count64]); - - col witness bits(1) reg_prev_mem_step; - precompiled_reg_load(reg: REG_A2, prev_mem_step: reg_prev_mem_step, main_step:main_step, value:[count, 0], sel: sel); + lookup_assumes(DMA_ROM_ID, expressions: [dst_offset, src_offset, l_count, flags, pre_count, src_offset_after_pre, l_count64], sel: sel); + // if operation not selected then other selectors must be 0. + (1 - sel) * use_memcpy === 0; + (1 - sel) * use_pre === 0; + (1 - sel) * use_post === 0; permutation_assumes(DMA_BUS_ID, [DMA_MEM_CPY, - dst64 + use_pre + l_count64 + h_count * 4, - src64 + src64_inc_by_pre + l_count64 + h_count * 4, - 0, - src_offset_after_pre, - l_count - pre_count - l_count64 * 8, - main_step], sel: use_memcpy); - - // write aligned - // read - // value1, value2, key = value 3 - // (src64, dst64, pre_src_op, dst_offset) - - // verify overlapping - // + (dst64 + use_pre * 8), + (src64 + src64_inc_by_pre * 8), + 0, + src_offset_after_pre, + h_count * 256 + l_count64 * 8, // count64 = h_count * (256/8 = 32) + l_count64 + main_step], sel: use_memcpy); + + permutation_assumes(DMA_BUS_ID, [DMA_MEM_PRE_POST, + dst64, + src64, + dst_offset, + src_offset, + pre_count, + main_step], sel: use_pre); + + permutation_assumes(DMA_BUS_ID, [DMA_MEM_PRE_POST, + dst64 + use_pre * 8 + l_count64 * 8 + h_count * 256, + src64 + src64_inc_by_pre * 8 + l_count64 * 8 + h_count * 256, + 0, + src_offset_after_pre, + post_count, + main_step], sel: use_post); + + // TODO: memcmp + + // used to link operation with main + lookup_proves(operation_bus_id, [OP_DMA_MEMCPY, dst, 0, src, 0, 0, 0, 0, main_step], mul: sel); + lookup_proves(operation_bus_id, [OP_PARAM, 0, 0, count, 0, 0, 0, 0, main_step-1], mul: sel); } \ No newline at end of file diff --git a/precompiles/dma/pil/dma_64_aligned.pil b/precompiles/dma/pil/dma_64_aligned.pil index eabc693d9..973bab77d 100644 --- a/precompiles/dma/pil/dma_64_aligned.pil +++ b/precompiles/dma/pil/dma_64_aligned.pil @@ -1,23 +1,25 @@ airtemplate Dma64Aligned(int N = 2**21, const int RC = 2, const int op_x_row = 4, - const int selectors = 1, const expr enable_dma = 1) { + const expr enable = 1, const int src_is_free_input = 0, + const int operation_bus_id = OPERATION_BUS_ID) { assert(RC == 2); const expr L1 = get_L1(); const expr LAST = L1'; - assert(selectors == 0 || selectors == 1); assert(op_x_row > 1); + // dst64 aligned, word address + // src64 aligned, word address + // count number of words + airval segment_id; // Id of current segment airval segment_previous_seq_end; // Last value of `seq_end` in previous segment. - airval segment_previous_src64; // Last value of `src64` in previous segment. airval segment_previous_dst64; // Last value of `dst64` in previous segment. airval segment_previous_main_step; // Last value of `main_step` in previous segment. airval segment_previous_count; // Last value of `count` in previous segment. airval segment_previous_is_mem_eq; // Last value of `is_mem_eq' in previous segment. airval segment_last_seq_end; // Last value of `seq_end` in current segment. - airval segment_last_src64; // Last value of `src64` in current segment. airval segment_last_dst64; // Last value of `dst64` in current segment. airval segment_last_main_step; // Last value of `main_step` in current segment. airval segment_last_count; // Last value of `count` in current segment. @@ -33,36 +35,70 @@ airtemplate Dma64Aligned(int N = 2**21, const int RC = 2, const int op_x_row = 4 segment_last_is_mem_eq * (1 - segment_last_is_mem_eq) === 0; col witness bits(MAIN_STEP_BITS) main_step; - col witness bits(ADDR_W_BITS) src64; col witness bits(ADDR_W_BITS) dst64; col witness bits(32) count; - col witness bits(32) value[op_x_row][RC]; - col witness bits(1) sel_op[selectors ? op_x_row : 0]; // one more for end + col witness bits(1) sel_op[op_x_row]; + const expr sel = sel_op[0]; col witness bits(1) seq_end; col witness bits(1) is_mem_eq; - seq_end * (1 - seq_end) === 0; - is_mem_eq * (1 - is_mem_eq) === 0; - if (selectors) { + if (src_is_free_input) { + const expr air.value[op_x_row][RC]; + col witness bits(16) value_chunks[op_x_row][RC][2]; for (int i = 0; i < op_x_row; i++) { - sel_op[i] * (1 - sel_op[i]) === 0; + for (int rc = 0; rc < RC; ++rc) { + value[i][rc] = value_chunks[i][rc][0] + value_chunks[i][rc][1] * P2_16; + range_check(value_chunks[i][rc][0], 0, P2_16 - 1); + range_check(value_chunks[i][rc][1], 0, P2_16 - 1); + } } + + const int air.segment_previous_src64 = 0; + const int air.segment_last_src64 = 0; + } else { + col witness bits(32) air.value[op_x_row][RC]; + col witness bits(ADDR_W_BITS) air.src64; + airval air.segment_previous_src64; // Last value of `src64` in previous segment. + airval air.segment_last_src64; // Last value of `src64` in current segment. } - const expr previous_seq_end = L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; - const expr previous_src64 = L1 * (segment_previous_src64 - 'src64) + 'src64; - const expr previous_dst64 = L1 * (segment_previous_dst64 - 'dst64) + 'dst64; - const expr previous_main_step = L1 * (segment_previous_main_step - 'main_step) + 'main_step; - const expr previous_count = L1 * (segment_previous_count - 'count) + 'count; - const expr previous_is_mem_eq = L1 * (segment_previous_is_mem_eq - 'is_mem_eq) + 'is_mem_eq; + seq_end * (1 - seq_end) === 0; + is_mem_eq * (1 - is_mem_eq) === 0; + + col witness bits(1) previous_seq_end; + previous_seq_end === L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; LAST * (seq_end - segment_last_seq_end) === 0; - LAST * (src64 - segment_last_src64) === 0; - LAST * (dst64 - segment_last_dst64) === 0; - LAST * (main_step - segment_last_main_step) === 0; - LAST * (count - segment_last_count) === 0; - LAST * (is_mem_eq - segment_last_is_mem_eq) === 0; + LAST * (1 - seq_end) * (dst64 - segment_last_dst64) === 0; + LAST * (1 - seq_end) * (main_step - segment_last_main_step) === 0; + LAST * (1 - seq_end) * (count - segment_last_count) === 0; + LAST * (1 - seq_end) * (is_mem_eq - segment_last_is_mem_eq) === 0; + + // when segment_last_seq_end = 1, means that the next segment starts with new input, no need to + // compare with previous, to simplify WC of continuations, zeros are sent to bus + // these constraints aren't strictly necessary, because these values are used only when seq_end = 0 + + segment_last_dst64 * segment_last_seq_end === 0; + segment_last_count * segment_last_seq_end === 0; + segment_last_main_step * segment_last_seq_end === 0; + segment_last_is_mem_eq * segment_last_seq_end === 0; + + // when segment_previous_seq_end = 1, means that this segment starts with new input, no need to + // compare with previous, to simplify WC of continuations, zeros are sent to bus + // these constraints aren't strictly necessary, because these values are used only when seq_end = 0 + + segment_previous_dst64 * segment_previous_seq_end === 0; + segment_previous_count * segment_previous_seq_end === 0; + segment_previous_main_step * segment_previous_seq_end === 0; + segment_previous_is_mem_eq * segment_previous_seq_end === 0; + + if (!src_is_free_input) { + LAST * (1 - seq_end) * (src64 - segment_last_src64) === 0; + const expr air.previous_src64 = L1 * (segment_previous_src64 - 'src64) + 'src64; + segment_previous_src64 * segment_previous_seq_end === 0; + segment_last_src64 * segment_last_seq_end === 0; + } // Continuations @@ -70,17 +106,19 @@ airtemplate Dma64Aligned(int N = 2**21, const int RC = 2, const int op_x_row = 4 direct_global_update_proves(MEM_CPY_CONT_ID, [0, 0, + src_is_free_input, 1, // initial seq_end 0, // initial src64 0, // initial dst64 0, // initial count 0, // initial main_step 0], // initial is_mem_eq - sel: enable_dma); + sel: enable); direct_update_assumes(MEM_CPY_CONT_ID, [segment_id, 0, + src_is_free_input, segment_previous_seq_end, segment_previous_src64, segment_previous_dst64, @@ -90,61 +128,110 @@ airtemplate Dma64Aligned(int N = 2**21, const int RC = 2, const int op_x_row = 4 direct_update_proves(MEM_CPY_CONT_ID, [segment_id + 1, is_last_segment, + src_is_free_input, segment_last_seq_end, segment_last_src64, segment_last_dst64, segment_last_count, segment_last_main_step, - segment_last_is_mem_eq], sel: 1 - is_last_segment); - + segment_last_is_mem_eq] + , sel: 1 - is_last_segment); - expr _ops_in_row = selectors ? sel_op[0] * op_x_row : sel_op[0]; + + expr _ops_in_row = 0; for (int i = 0; i < op_x_row; i++) { - expr sel; - if (selectors || i == 0) { - sel = sel_op[i]; - sel_op[i] * (1 - sel_op[i]) === 0; - } else { - sel = sel_op[0]; + sel_op[i] * (1 - sel_op[i]) === 0; + _ops_in_row += sel_op[i]; + + if (!src_is_free_input) { + precompiled_mem_load( + sel: sel_op[i], + main_step: main_step, + addr: src64 * 8 + 8 * i, + value: value[i] + ); } - precompiled_mem_load( - sel: sel, - main_step: main_step, - addr: src64 * 8 + 8 * i, - value: value[i] - ); - precompiled_mem_op( is_write: 1 - is_mem_eq, - sel: sel, + sel: sel_op[i], main_step: main_step, addr: dst64 * 8 + 8 * i, value: value[i] ); - if (selectors && i > 0) { + if (i > 0) { // current selector only can be 1 if previous is 1 sel_op[i] * (1 - sel_op[i - 1]) === 0; - _ops_in_row += sel_op[i]; } } const expr ops_in_row = _ops_in_row; - - // If not finish sequence in previous row, means current it's a continuation of previous sequence - // count match with previous value plus ops_in_row. - (count - (previous_count + op_x_row)) * (1 - previous_seq_end) === 0; - // If finish sequence in previous row, means current it's a beginning of new sequence and - // count match with ops_in_row. - (count - ops_in_row) * previous_seq_end === 0; + const expr continue_seq_on_l1 = L1 * (1 - segment_previous_seq_end); + const expr continue_seq_on_no_l1 = (1 - L1) * (1 - 'seq_end); + + // const expr new_seq_on_l1 = L1 * segment_previous_seq_end; + // const expr new_seq_on_no_l1 = (1 - L1) * 'seq_end; - // If previous row was a seq_end, start with new dst/src addresses, else increment by ops_in_row - (src64 - (previous_src64 + 8 * op_x_row)) * (1 - previous_seq_end) === 0; - (dst64 - (previous_dst64 + 8 * op_x_row)) * (1 - previous_seq_end) === 0; - (is_mem_eq - previous_is_mem_eq) * (1 - previous_seq_end) === 0; + // TRANSITIONS: + // + // If not finish sequence in previous row, means current it's a continuation of previous sequence + // count match with previous value plus ops_in_row. + continue_seq_on_l1 * (count - (segment_previous_count - op_x_row)) == 0; + continue_seq_on_no_l1 * (count - ('count - op_x_row)) === 0; - // At beginning of memcpy blocks we send two address - const expr dma_op = is_mem_eq * (DMA_MEM_EQ - DMA_MEM_CPY) + DMA_MEM_CPY; - permutation_proves(DMA_BUS_ID, [dma_op, dst64, src64, 0, 0, count, main_step], sel: previous_seq_end); + // If finish sequence count match with ops_in_row, because final count must be 0. + // new_seq_on_l1 * (count - ops_in_row) === 0; + // new_seq_on_no_l1 * (count - ops_in_row) === 0; + seq_end * (count - ops_in_row) === 0; + + // If previous row was a seq_end, start with new dst/src addresses, else increment by ops_in_row + continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + op_x_row)) === 0; + continue_seq_on_no_l1 * (dst64 - ('dst64 + op_x_row)) === 0; + + // NOTE: transition of src64 is defined only + + // LATCHS: + // + // is_mem_eq = 'is_mem_eq + // main_step = 'main_step + + continue_seq_on_l1 * (is_mem_eq - segment_previous_is_mem_eq) === 0; + continue_seq_on_no_l1 * (is_mem_eq - 'is_mem_eq) === 0; + + continue_seq_on_l1 * (main_step - segment_previous_main_step) === 0; + continue_seq_on_no_l1 * (main_step - 'main_step) === 0; + + // SECURITY: control count no negative + // + // if the seq_end it isn't active when count = 0, in each rows continues decreasing 8 x op_x_row + // units, means in 2^22 rows * 2^3 * 2^2 = 2^27. It's secure, because if any row lies, at end of + // instance this constraint fails. + + airval last_count_chunk[2]; + range_check(expression: last_count_chunk[0], min: 0, max: 2**16-1); + range_check(expression: last_count_chunk[1], min: 0, max: 2**16-1); + last_count_chunk[0] + last_count_chunk[1] * P2_16 === segment_last_count; + + airval padding_size; + + if (src_is_free_input) { + const int DMA_MEM_INPUT_CPY_OP = 0xD3; + const expr dma_op = DMA_MEM_INPUT_CPY_OP; + permutation_proves(operation_bus_id, [dma_op, dst64 * 8, 0, count * 8, 0, 0, 0, 0, main_step], sel: previous_seq_end); + + // Used to cancel padding operations + direct_update_assumes(operation_bus_id, [dma_op, 0, 0, 0, 0, 0, 0, 0, 0], sel: adding_size); + } else { + // TRANSITION src64: + continue_seq_on_l1 * (src64 - (segment_previous_src64 + op_x_row)) === 0; + continue_seq_on_no_l1 * (src64 - ('src64 + op_x_row)) === 0; + + // At beginning of memcpy blocks we send two address + const expr dma_op = is_mem_eq * (DMA_MEM_EQ - DMA_MEM_CPY) + DMA_MEM_CPY; + permutation_proves(DMA_BUS_ID, [dma_op, dst64 * 8, src64 * 8, 0, 0, count * 8, main_step], sel: previous_seq_end); + + // Used to cancel padding operations + direct_update_assumes(DMA_BUS_ID, [DMA_MEM_CPY, 0, 0, 0, 0, 0, 0], sel: padding_size); + } } \ No newline at end of file diff --git a/precompiles/dma/pil/dma_pre_post.pil b/precompiles/dma/pil/dma_pre_post.pil index 9916800a3..a61bcdc87 100644 --- a/precompiles/dma/pil/dma_pre_post.pil +++ b/precompiles/dma/pil/dma_pre_post.pil @@ -4,85 +4,156 @@ airtemplate DmaPrePost(int N = 2**21) { col witness bits(MAIN_STEP_BITS) main_step; - col witness bits(ADDR_W_BITS) src_addr; - col witness bits(ADDR_W_BITS) dst_addr; + col witness bits(ADDR_W_BITS) src64; + col witness bits(ADDR_W_BITS) dst64; col witness bits(3) dst_offset; + col witness bits(3) src_offset; col witness bits(3) count; - col witness bits(1) offset_7; - col witness bits(1) offset_6; - col witness bits(1) offset_5; - col witness bits(1) offset_4; - col witness bits(1) offset_3; - col witness bits(1) offset_2; - col witness bits(1) offset_1; - const expr offset_7to1 = offset_7 + offset_6 + offset_5 + offset_4 + offset_3 + offset_2 + offset_1; - const expr offset_0 = (1 - offset_7to1); - - offset_7to1 * (offset_7to1) === 0; - const expr src_offset = offset_1 + offset_2 * 2 + offset_3 * 3 + offset_4 * 4 + offset_5 * 5 + - offset_6 * 6 + offset_7 * 7; - - offset_7 * (1 - offset_7) === 0; - offset_6 * (1 - offset_6) === 0; - offset_5 * (1 - offset_5) === 0; - offset_4 * (1 - offset_4) === 0; - offset_3 * (1 - offset_3) === 0; - offset_2 * (1 - offset_2) === 0; - offset_1 * (1 - offset_1) === 0; + col witness bits(1) selread[7]; + const expr selr[8]; + expr _selr_0to6 = 0; + expr _selr_value = 0; + for (int i = 0; i < 7; ++i) { + selread[i] * (1 - selread[i]) === 0; + selr[i] = selread[i]; + _selr_0to6 = _selr_0to6 + selread[i]; + if (i > 0) { + _selr_value = _selr_value + i * selr[i]; + } + } + const expr selr_0to6 = _selr_0to6; + _selr_value = _selr_value + 7 * selr[7]; + + const expr selr_value = _selr_value; + + selr_0to6 * (1 - selr_0to6) === 0; + selr[7] = (1 - selr_0to6); + + col witness bits(1) dst_offset_gt_src_offset; + dst_offset_gt_src_offset * (1 - dst_offset_gt_src_offset) === 0; col witness bits(1) enabled; col witness bits(1) enabled_second_read; enabled * (1 - enabled) === 0; enabled_second_read * (1 - enabled_second_read) === 0; + + // to force use table with enable when enabled_second_read is 1. If enable is 1, with table + // it's verified the correcte value of enabled_second_read using offset an count enabled_second_read * (1 - enabled) === 0; - const int V2R = 2 * 3; // Values To Read - const int B2R = V2R * 4; // Bytes To Read + const int V2R = 2 * 3; // Values To Read (2 x 32 bits = 64 bits x (R,R+8,PRE-W)) + const int B2R = V2R * 4; // Bytes To Read (4 bytes = value 32 bits) col witness bits(8) bytes[B2R]; const expr values[V2R]; - for (int i = 0; i < B2R; i+=2) { - range_dual_byte(bytes[i], bytes[i+1], enabled); + range_dual_byte(bytes[i+1], bytes[i], enabled); } for (int i = 0; i < V2R; ++i) { values[i] = bytes[i*4] + P2_8 * bytes[i*4+1] + P2_16 * bytes[i*4+2] + P2_24 * bytes[i*4+3]; } col witness bits(1) selb[8]; - expr mask = 0; + expr selectors_mask = 0; for (int i = 0; i < 8; ++i) { selb[i] * (1 - selb[i]) === 0; - mask += selb[i] * (1 << (7+i)); + selectors_mask += selb[i] * (1 << (7-i)); } - col witness bits(32) write_value[2]; + col witness bits(32) write_value[4]; // byte_0, byte_1, byte_2, byte_3, byte_4, byte_5, byte_6, byte_7, byte_0', byte_1' ... - write_value[0] === offset_0 * (selb[0] * bytes[0] + selb[1] * P2_8 * bytes[1] + selb[2] * P2_16 * bytes[2] + selb[3] * P2_24 * bytes[3]) + - offset_1 * (selb[0] * bytes[1] + selb[1] * P2_8 * bytes[2] + selb[2] * P2_16 * bytes[3] + selb[3] * P2_24 * bytes[4]) + - offset_2 * (selb[0] * bytes[2] + selb[1] * P2_8 * bytes[3] + selb[2] * P2_16 * bytes[4] + selb[3] * P2_24 * bytes[5]) + - offset_3 * (selb[0] * bytes[3] + selb[1] * P2_8 * bytes[4] + selb[2] * P2_16 * bytes[5] + selb[3] * P2_24 * bytes[6]) + - offset_4 * (selb[0] * bytes[4] + selb[1] * P2_8 * bytes[5] + selb[2] * P2_16 * bytes[6] + selb[3] * P2_24 * bytes[7]) + - offset_5 * (selb[0] * bytes[5] + selb[1] * P2_8 * bytes[6] + selb[2] * P2_16 * bytes[7] + selb[3] * P2_24 * bytes[8]) + - offset_6 * (selb[0] * bytes[6] + selb[1] * P2_8 * bytes[7] + selb[2] * P2_16 * bytes[8] + selb[3] * P2_24 * bytes[9]) + - offset_7 * (selb[0] * bytes[7] + selb[1] * P2_8 * bytes[8] + selb[2] * P2_16 * bytes[9] + selb[3] * P2_24 * bytes[10]) + + // src_offset 0 1 2 3 4 5 6 7 + // dst_offset 0 0 1 2 3 4 5 6 7 + // dst_offset 1 -1 + + + + // │ src_offset: + // selr_value │ 0 1 2 3 4 5 6 7 + // ──────────────┼─────────────────────── + // dst_offset: 0 │ 0 1 2 3 4 5 6 7 + // 1 │ 1 0 1 2 3 4 5 6 + // 2 │ 2 1 0 1 2 3 4 5 + // 3 │ 3 2 1 0 1 2 3 4 + // 4 │ 4 3 2 1 0 1 2 3 + // 5 │ 5 4 3 2 1 0 1 2 + // 6 │ 6 5 4 3 2 1 0 1 + // 7 │ 7 6 5 4 3 2 1 0 + // + // NOTE: selr_value = ABS(src_offset - dst_offset) + + // ┌────── src_offset - dst_offset + // │ ┌── abs(src_offset - dst_offset) + // │ │ + // │ │ 0 1 2 3 4 5 6 7 + // ┌─────────────────┬─────────────────┐ + // -7 7 │ │ R0 │ + // -6 6 │ │ R0 R1 │ + // -5 5 │ │ R0 R1 R2 │ + // -4 4 │ │ R0 R1 R2 R3 │ + // -3 3 │ R0 │ R1 R2 R3 R4 │ + // -2 2 │ R0 R1 │ R2 R3 R4 R5 │ + // -1 1 │ R0 R1 R2 │ R3 R4 R5 R6 │ + // ├─────────────────┼─────────────────┤ + // 0 0 │ R0 R1 R2 R3 │ R4 R5 R6 R7 │ + // 1 1 │ R1 R2 R3 R4 │ R5 R6 R7 R8 │ + // 2 2 │ R2 R3 R4 R5 │ R6 R7 R8 R9 │ + // 3 3 │ R3 R4 R5 R6 │ R7 R8 R9 R10 │ + // 4 4 │ R4 R5 R6 R7 │ R8 R9 R10 R11 │ + // 5 5 │ R5 R6 R7 R8 │ R9 R10 R11 R12 │ + // 6 6 │ R6 R7 R8 R9 │ R10 R11 R12 R13 │ + // 7 7 │ R7 R8 R9 R10 │ R11 R12 R13 R14 │ + // └─────────────────┴─────────────────┘ + + // dst_offset <= src_offset + + write_value[0] === selr[0] * (selb[0] * bytes[0] + selb[1] * P2_8 * bytes[1] + selb[2] * P2_16 * bytes[2] + selb[3] * P2_24 * bytes[3]) + + selr[1] * (selb[0] * bytes[1] + selb[1] * P2_8 * bytes[2] + selb[2] * P2_16 * bytes[3] + selb[3] * P2_24 * bytes[4]) + + selr[2] * (selb[0] * bytes[2] + selb[1] * P2_8 * bytes[3] + selb[2] * P2_16 * bytes[4] + selb[3] * P2_24 * bytes[5]) + + selr[3] * (selb[0] * bytes[3] + selb[1] * P2_8 * bytes[4] + selb[2] * P2_16 * bytes[5] + selb[3] * P2_24 * bytes[6]) + + selr[4] * (selb[0] * bytes[4] + selb[1] * P2_8 * bytes[5] + selb[2] * P2_16 * bytes[6] + selb[3] * P2_24 * bytes[7]) + + selr[5] * (selb[0] * bytes[5] + selb[1] * P2_8 * bytes[6] + selb[2] * P2_16 * bytes[7] + selb[3] * P2_24 * bytes[8]) + + selr[6] * (selb[0] * bytes[6] + selb[1] * P2_8 * bytes[7] + selb[2] * P2_16 * bytes[8] + selb[3] * P2_24 * bytes[9]) + + selr[7] * (selb[0] * bytes[7] + selb[1] * P2_8 * bytes[8] + selb[2] * P2_16 * bytes[9] + selb[3] * P2_24 * bytes[10]) + (1 - selb[0]) * bytes[16] + (1 - selb[1]) * P2_8 * bytes[17] + (1 - selb[2]) * P2_16 * bytes[18] + (1 - selb[3]) * P2_24 * bytes[19]; - write_value[1] === offset_0 * (selb[4] * bytes[4] + selb[5] * P2_8 * bytes[5] + selb[6] * P2_16 * bytes[6] + selb[7] * P2_24 * bytes[7]) + - offset_1 * (selb[4] * bytes[5] + selb[5] * P2_8 * bytes[6] + selb[6] * P2_16 * bytes[7] + selb[7] * P2_24 * bytes[8]) + - offset_2 * (selb[4] * bytes[6] + selb[5] * P2_8 * bytes[7] + selb[6] * P2_16 * bytes[8] + selb[7] * P2_24 * bytes[9]) + - offset_3 * (selb[4] * bytes[7] + selb[5] * P2_8 * bytes[8] + selb[6] * P2_16 * bytes[9] + selb[7] * P2_24 * bytes[10]) + - offset_4 * (selb[4] * bytes[8] + selb[5] * P2_8 * bytes[9] + selb[6] * P2_16 * bytes[10] + selb[7] * P2_24 * bytes[11]) + - offset_5 * (selb[4] * bytes[9] + selb[5] * P2_8 * bytes[10] + selb[6] * P2_16 * bytes[11] + selb[7] * P2_24 * bytes[12]) + - offset_6 * (selb[4] * bytes[10] + selb[5] * P2_8 * bytes[11] + selb[6] * P2_16 * bytes[12] + selb[7] * P2_24 * bytes[13]) + - offset_7 * (selb[4] * bytes[11] + selb[5] * P2_8 * bytes[12] + selb[6] * P2_16 * bytes[13] + selb[7] * P2_24 * bytes[14]); + write_value[1] === selr[0] * (selb[4] * bytes[4] + selb[5] * P2_8 * bytes[5] + selb[6] * P2_16 * bytes[6] + selb[7] * P2_24 * bytes[7]) + + selr[1] * (selb[4] * bytes[5] + selb[5] * P2_8 * bytes[6] + selb[6] * P2_16 * bytes[7] + selb[7] * P2_24 * bytes[8]) + + selr[2] * (selb[4] * bytes[6] + selb[5] * P2_8 * bytes[7] + selb[6] * P2_16 * bytes[8] + selb[7] * P2_24 * bytes[9]) + + selr[3] * (selb[4] * bytes[7] + selb[5] * P2_8 * bytes[8] + selb[6] * P2_16 * bytes[9] + selb[7] * P2_24 * bytes[10]) + + selr[4] * (selb[4] * bytes[8] + selb[5] * P2_8 * bytes[9] + selb[6] * P2_16 * bytes[10] + selb[7] * P2_24 * bytes[11]) + + selr[5] * (selb[4] * bytes[9] + selb[5] * P2_8 * bytes[10] + selb[6] * P2_16 * bytes[11] + selb[7] * P2_24 * bytes[12]) + + selr[6] * (selb[4] * bytes[10] + selb[5] * P2_8 * bytes[11] + selb[6] * P2_16 * bytes[12] + selb[7] * P2_24 * bytes[13]) + + selr[7] * (selb[4] * bytes[11] + selb[5] * P2_8 * bytes[12] + selb[6] * P2_16 * bytes[13] + selb[7] * P2_24 * bytes[14]) + (1 - selb[4]) * bytes[20] + (1 - selb[5]) * P2_8 * bytes[21] + (1 - selb[6]) * P2_16 * bytes[22] + (1 - selb[7]) * P2_24 * bytes[23]; + // dst_offset > src_offset + + write_value[2] === selr[3] * ( selb[3] * P2_24 * bytes[0]) + + selr[2] * ( selb[2] * P2_16 * bytes[0] + selb[3] * P2_24 * bytes[1]) + + selr[1] * ( selb[1] * P2_8 * bytes[0] + selb[2] * P2_16 * bytes[1] + selb[3] * P2_24 * bytes[2]) + + (1 - selb[0]) * bytes[16] + (1 - selb[1]) * P2_8 * bytes[17] + (1 - selb[2]) * P2_16 * bytes[18] + (1 - selb[3]) * P2_24 * bytes[19]; + + write_value[3] === selr[7] * ( + selb[7] * P2_24 * bytes[0]) + + selr[6] * ( + selb[6] * P2_16 * bytes[0] + selb[7] * P2_24 * bytes[1]) + + selr[5] * ( + selb[5] * P2_8 * bytes[0] + selb[6] * P2_16 * bytes[1] + selb[7] * P2_24 * bytes[2]) + + selr[4] * (selb[4] * bytes[0] + selb[5] * P2_8 * bytes[1] + selb[6] * P2_16 * bytes[2] + selb[7] * P2_24 * bytes[3]) + + selr[3] * (selb[4] * bytes[1] + selb[5] * P2_8 * bytes[2] + selb[6] * P2_16 * bytes[3] + selb[7] * P2_24 * bytes[4]) + + selr[2] * (selb[4] * bytes[2] + selb[5] * P2_8 * bytes[3] + selb[6] * P2_16 * bytes[4] + selb[7] * P2_24 * bytes[5]) + + selr[1] * (selb[4] * bytes[3] + selb[5] * P2_8 * bytes[4] + selb[6] * P2_16 * bytes[5] + selb[7] * P2_24 * bytes[6]) + + (1 - selb[4]) * bytes[20] + (1 - selb[5]) * P2_8 * bytes[21] + (1 - selb[6]) * P2_16 * bytes[22] + (1 - selb[7]) * P2_24 * bytes[23]; + + + + const expr bus_write_value[2]; + + bus_write_value[0] = dst_offset_gt_src_offset * (write_value[2] - write_value[0]) + write_value[0]; + bus_write_value[1] = dst_offset_gt_src_offset * (write_value[3] - write_value[1]) + write_value[1]; // MEMORY ACCESS // @@ -91,14 +162,18 @@ airtemplate DmaPrePost(int N = 2**21) { // Read previous value before write // Write final value - precompiled_mem_load( sel: enabled, main_step: main_step, addr: src_addr * 8, value: [values[0], values[1]]); - precompiled_mem_load( sel: enabled_second_read, main_step: main_step, addr: src_addr * 8 + 8, value: [values[2], values[3]]); - precompiled_mem_load( sel: enabled, main_step: main_step, addr: dst_addr * 8, value: [values[4], values[5]]); - precompiled_mem_store(sel: enabled, main_step: main_step, addr: dst_addr * 8, value: write_value); + precompiled_mem_load(sel: enabled, main_step: main_step, addr: src64 * 8, value: [values[0], values[1]]); + precompiled_mem_load(sel: enabled_second_read, main_step: main_step, addr: src64 * 8 + 8, value: [values[2], values[3]]); + precompiled_mem_load(sel: enabled, main_step: main_step, addr: dst64 * 8, value: [values[4], values[5]]); + precompiled_mem_store(sel: enabled, main_step: main_step, addr: dst64 * 8, value: bus_write_value); // Send operation to bus - permutation_proves(DMA_BUS_ID, [DMA_MEM_PRE_POST, src_addr, dst_addr, src_offset, dst_offset, count, main_step], sel: enabled); - - const expr flags = mask + src_offset * P2_8 + dst_offset * P2_11 + count * P2_14 + enabled_second_read * P2_17; - lookup_assumes(DMA_PRE_POST_TABLE_ID, [flags], sel: enabled); + permutation_proves(DMA_BUS_ID, [DMA_MEM_PRE_POST, dst64 * 8, src64 * 8, dst_offset, src_offset, count, main_step], sel: enabled); + + // selectors_mask (8 bits) + // enabled_second_read (1 bit) + // dst_offset_gt_src_offset (1 bit) + // selr_value (0-7) (3 bits) + const expr flags = selectors_mask + enabled_second_read * P2_8 + dst_offset_gt_src_offset * P2_9 + selr_value * P2_10; + lookup_assumes(DMA_PRE_POST_TABLE_ID, [flags, dst_offset, src_offset, count], sel: enabled); } \ No newline at end of file diff --git a/precompiles/dma/pil/dma_pre_post_table.pil b/precompiles/dma/pil/dma_pre_post_table.pil index c8af1ee60..803af229b 100644 --- a/precompiles/dma/pil/dma_pre_post_table.pil +++ b/precompiles/dma/pil/dma_pre_post_table.pil @@ -1,27 +1,71 @@ require "std_lookup.pil" -const int DMA_PRE_POST_TABLE_ID = 13000; const int DMA_PRE_POST_TABLE_SIZE = 280; airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { col fixed FLAGS; + col fixed DST_OFFSET; + col fixed SRC_OFFSET; + col fixed COUNT; int index = 0; - for (int count = 1; count < 8; ++ count) { + + // POST OPERATION objective execute the last incomplete "write": + // dst_offset = 0 + // count ∈ [1,7] + + int table_offsets[64]; + + for (int src_offset = 0; src_offset < 8; ++src_offset) { + table_offsets[src_offset] = index; + for (int count = 1; count < 8; ++ count) { + SRC_OFFSET[index] = src_offset; + DST_OFFSET[index] = 0; + COUNT[index] = count; + + const int selectors = (0xFF << (8 - count)) & 0xFF; + assert(selectors != 0); + assert(selectors <= 0xFF); + + const int enabled_second_read = (src_offset + count) > 8 ? 1:0; + // selr_value = src_offset + // dst_offset_gt_src_offset = 0 + FLAGS[index] = selectors + enabled_second_read * P2_8 + P2_10 * src_offset; + index += 1; + } + } + + // PRE OPERATION objective execute the "first" incomplete "write" + // dst_offset > 0 + // count = 8 - dst_offset + + for (int dst_offset = 1; dst_offset < 8; ++dst_offset) { + const int mask = 0xFF >> dst_offset; for (int src_offset = 0; src_offset < 8; ++src_offset) { - for (int dst_offset = 0; dst_offset < (9 - count); ++dst_offset) { - const int pre_mask = 0xFF >> (8 - count); - const int mask = pre_mask << (8 - (dst_offset + count)); - assert(mask != 0); - assert(mask <= 0xFF); + table_offsets[dst_offset * 8 + src_offset] = index; + for (int count = 1; count < (9 - dst_offset); ++ count) { + SRC_OFFSET[index] = src_offset; + DST_OFFSET[index] = dst_offset; + COUNT[index] = count; + + const int selectors = mask & (0xFF << (8 - (dst_offset + count))); + assert(selectors != 0); + assert(selectors <= 0xFF); const int enabled_second_read = (src_offset + count) > 8 ? 1:0; - FLAGS[index] = mask + src_offset * P2_8 + dst_offset * P2_11 + count * P2_14 + enabled_second_read * P2_17; + const int dst_offset_gt_src_offset = dst_offset > src_offset ? 1:0; + const int selr_value = dst_offset > src_offset ? dst_offset - src_offset: src_offset - dst_offset; + // selr_value = src_offset + // dst_offset_gt_src_offset = 0 + FLAGS[index] = selectors + enabled_second_read * P2_8 + dst_offset_gt_src_offset * P2_9 + selr_value * P2_10; index += 1; } } } + println(`TABLE_OFFSETS[${length(table_offsets)}] = [` ,table_offsets, "]"); + println(`DMA_PRE_POST_TABLE_SIZE=${DMA_PRE_POST_TABLE_SIZE}`); + assert_eq(index, DMA_PRE_POST_TABLE_SIZE); col witness multiplicity; - lookup_proves(DMA_PRE_POST_TABLE_ID, mul: multiplicity, expressions: [FLAGS]); + lookup_proves(DMA_PRE_POST_TABLE_ID, mul: multiplicity, expressions: [FLAGS, DST_OFFSET, SRC_OFFSET, COUNT]); } \ No newline at end of file diff --git a/precompiles/dma/pil/dma_rom.pil b/precompiles/dma/pil/dma_rom.pil index 0d8de9a9e..c21f7b55b 100644 --- a/precompiles/dma/pil/dma_rom.pil +++ b/precompiles/dma/pil/dma_rom.pil @@ -1,6 +1,6 @@ require "std_lookup.pil" -const int DMA_ROM_TABLE_SIZE = 8 * 8 * P2_6; +const int DMA_ROM_TABLE_SIZE = 8 * 8 * P2_9; airtemplate DmaRom(int N = DMA_ROM_TABLE_SIZE) { @@ -15,27 +15,32 @@ airtemplate DmaRom(int N = DMA_ROM_TABLE_SIZE) { int i = 0; for (int dst_offset = 0; dst_offset < 8; ++dst_offset) { for (int src_offset = 0; src_offset < 8; ++src_offset) { - for (int l_count = 1; l_count < P2_6; ++ l_count) { - assert(dst_offset < 8); - assert(src_offset < 8); - assert(l_count < 64); - - const int use_pre = dst_offset > 0; - const int pre_count = use_pre ? (((8 - dst_offset) < l_count) ? (8 - dst_offset) : l_count) : 0; - const int post_count = (l_count - pre_count) % 8; - const int use_post = post_count > 0; - const int memcpy_count = l_count - pre_count - post_count; - const int use_memcpy = memcpy_count > 0; - const int src64_inc_by_pre = (use_pre && (src_offset + pre_count) >= 8) ? 1 : 0; - + for (int l_count = 0; l_count < P2_9; ++l_count) { DST_OFFSET[i] = dst_offset; SRC_OFFSET[i] = src_offset; - L_COUNT[i] = l_count; - FLAGS[i] = use_pre + use_memcpy * 2 + use_post * 4 + src64_inc_by_pre * 8; - PRE_COUNT[i] = pre_count; - SRC_OFFSET_AFTER_PRE[i] = (src_offset + pre_count) % 8; - L_COUNT64[i] = (l_count - pre_count) / 8; + if (l_count == 0) { + L_COUNT[i] = 0; + FLAGS[i] = 16; + PRE_COUNT[i] = 0; + SRC_OFFSET_AFTER_PRE[i] = src_offset; + L_COUNT64[i] = 0; + } else { + const int use_pre = dst_offset > 0; + const int pre_count = use_pre ? (((8 - dst_offset) < l_count) ? (8 - dst_offset) : l_count) : 0; + const int post_count = (l_count - pre_count) % 8; + const int use_post = post_count > 0; + const int memcpy_count = l_count - pre_count - post_count; + const int use_memcpy = memcpy_count > 0; + const int src64_inc_by_pre = (use_pre && (src_offset + pre_count) >= 8) ? 1 : 0; + const int count_lt_256 = l_count < 256 ? 1 : 0; + + L_COUNT[i] = l_count; + FLAGS[i] = use_pre + use_memcpy * 2 + use_post * 4 + src64_inc_by_pre * 8 + count_lt_256 * 16; + PRE_COUNT[i] = pre_count; + SRC_OFFSET_AFTER_PRE[i] = (src_offset + pre_count) % 8; + L_COUNT64[i] = (l_count - pre_count) / 8; + } i += 1; } } diff --git a/precompiles/dma/pil/dma_unaligned.pil b/precompiles/dma/pil/dma_unaligned.pil index 0f4b7a637..c2159613e 100644 --- a/precompiles/dma/pil/dma_unaligned.pil +++ b/precompiles/dma/pil/dma_unaligned.pil @@ -1,7 +1,7 @@ // TODO: Optimization Dma32AlignedOp -airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable_mem_cpy_byte = 1) { +airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) { const expr L1 = get_L1(); const expr LAST = L1'; @@ -41,34 +41,23 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable_mem_ col witness bits(MAIN_STEP_BITS) main_step; col witness bits(ADDR_W_BITS) src64; col witness bits(ADDR_W_BITS) dst64; - col witness bits(32) count; - col witness bits(32) value[RC]; + col witness bits(32) count; // number of words col witness bits(1) seq_end; seq_end * (1 - seq_end) === 0; - col witness bits(1) sel; - sel * (1 - sel) == 0; - col witness bits(1) previous_seq_end; - previous_seq_end * (1 - previous_seq_end) === 0; previous_seq_end === L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; - col witness bits(1) is_mem_eq; // Last value of `is_mem_eq' in previous segment. + col witness bits(1) is_mem_eq; // Last value of `is_mem_eq' in previous segment. is_mem_eq * (1 - is_mem_eq) === 0; - const expr previous_src64 = L1 * (segment_previous_src64 - 'src64) + 'src64; - const expr previous_dst64 = L1 * (segment_previous_dst64 - 'dst64) + 'dst64; - const expr previous_main_step = L1 * (segment_previous_main_step - 'main_step) + 'main_step; - const expr previous_count = L1 * (segment_previous_count - 'count) + 'count; - const expr previous_is_mem_eq = L1 * (segment_previous_is_mem_eq - 'is_mem_eq) + 'is_mem_eq; - LAST * (seq_end - segment_last_seq_end) === 0; - LAST * (src64 - segment_last_src64) === 0; - LAST * (dst64 - segment_last_dst64) === 0; - LAST * (main_step - segment_last_main_step) === 0; - LAST * (count - segment_last_count) === 0; - LAST * (is_mem_eq - segment_last_is_mem_eq) === 0; + LAST * (1 - seq_end) * (src64 - segment_last_src64) === 0; + LAST * (1 - seq_end) * (dst64 - segment_last_dst64) === 0; + LAST * (1 - seq_end) * (main_step - segment_last_main_step) === 0; + LAST * (1 - seq_end) * (count - segment_last_count) === 0; + LAST * (1 - seq_end) * (is_mem_eq - segment_last_is_mem_eq) === 0; col witness bits(1) offset_7; col witness bits(1) offset_6; @@ -76,10 +65,14 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable_mem_ col witness bits(1) offset_4; col witness bits(1) offset_3; col witness bits(1) offset_2; + + // To save one witness column, define offset_1 using other columns, because no offset_0 supported + // offset = 0 means aligned memcpy. + // CONSIDERATION: support offset_0 to prove also aligned memcpy. const expr offset_7to2 = offset_7 + offset_6 + offset_5 + offset_4 + offset_3 + offset_2; const expr offset_1 = (1 - offset_7to2); - offset_7to2 * (offset_7to2) === 0; + offset_7to2 * (offset_7to2 - 1) === 0; const expr offset = offset_1 + offset_2 * 2 + offset_3 * 3 + offset_4 * 4 + offset_5 * 5 + offset_6 * 6 + offset_7 * 7; @@ -91,17 +84,17 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable_mem_ offset_2 * (1 - offset_2) === 0; const expr previous_offset = L1 * (segment_previous_offset - 'offset) + 'offset; - LAST * (offset - segment_last_offset) === 0; + LAST * (1 - seq_end) * (offset - segment_last_offset) === 0; col witness bits(8) read_bytes[8]; - range_dual_byte(read_bytes[0], read_bytes[1]); - range_dual_byte(read_bytes[2], read_bytes[3]); - range_dual_byte(read_bytes[4], read_bytes[5]); - range_dual_byte(read_bytes[6], read_bytes[7]); + range_dual_byte(read_bytes[1], read_bytes[0]); + range_dual_byte(read_bytes[3], read_bytes[2]); + range_dual_byte(read_bytes[5], read_bytes[4]); + range_dual_byte(read_bytes[7], read_bytes[6]); const expr read_value[2]; - col witness bits(32) write_value[2]; + read_value[0] = read_bytes[0] + P2_8 * read_bytes[1] + P2_16 * read_bytes[2] + P2_24 * read_bytes[3]; read_value[1] = read_bytes[4] + P2_8 * read_bytes[5] + P2_16 * read_bytes[6] + P2_24 * read_bytes[7]; @@ -109,17 +102,39 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable_mem_ const expr next_bytes[8]; + // when an instance last row has seq_end = 1, means that next_bytes don't need to match + // with first read_bytes, because it's new dma operation, in this situation set value + // of segment_next_bytes to 0. + + col witness bits(1) no_last_no_seq_end; + no_last_no_seq_end === (1 - LAST) * (1 - seq_end); + for (int i = 0; i < 8; ++i) { - next_bytes[i] = LAST * (segment_next_bytes[i] - read_bytes[i]') + read_bytes[i]'; + // if LAST next_bytes are bytes sent to bus, if no LAST but is a seq_end, no sense + // use bytes of the next input, in this case next_bytes = 0, otherwise next_bytes are + // the next read (src64 + 1) * 8 + next_bytes[i] = LAST * segment_next_bytes[i] + no_last_no_seq_end * read_bytes[i]'; + + // if last row of previous instance aren't the the last row of an input, segment_first_bytes + // must match with bytes "received" from bus. Otherwise, zeros was sent to bus and them not + // need to match with read_bytes because are bytes of other input. (1 - segment_previous_seq_end) * L1 * (segment_first_bytes[i] - read_bytes[i]) === 0; + + // force segment_next_bytes to zero, when it's finish of input + segment_last_seq_end * segment_next_bytes[i] == 0; + + // to force the "received" values from the bus are 0 when the last row of the previous segment + // is final of input (seq_end). segment_previous_seq_end * L1 * segment_first_bytes[i] === 0; } + col witness bits(32) write_value[2]; + write_value[0] === offset_1 * (read_bytes[1] + P2_8 * read_bytes[2] + P2_16 * read_bytes[3] + P2_24 * read_bytes[4]) + offset_2 * (read_bytes[2] + P2_8 * read_bytes[3] + P2_16 * read_bytes[4] + P2_24 * read_bytes[5]) + offset_3 * (read_bytes[3] + P2_8 * read_bytes[4] + P2_16 * read_bytes[5] + P2_24 * read_bytes[6]) + offset_4 * (read_bytes[4] + P2_8 * read_bytes[5] + P2_16 * read_bytes[6] + P2_24 * read_bytes[7]) + - offset_5 * (read_bytes[5] + P2_8 * read_bytes[6] + P2_16 * read_bytes[7] + P2_24 * read_bytes[0]) + + offset_5 * (read_bytes[5] + P2_8 * read_bytes[6] + P2_16 * read_bytes[7] + P2_24 * next_bytes[0]) + offset_6 * (read_bytes[6] + P2_8 * read_bytes[7] + P2_16 * next_bytes[0] + P2_24 * next_bytes[1]) + offset_7 * (read_bytes[7] + P2_8 * next_bytes[0] + P2_16 * next_bytes[1] + P2_24 * next_bytes[2]); @@ -133,7 +148,7 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable_mem_ // memory access precompiled_mem_load( - sel: sel, + sel: 1, main_step: main_step, addr: src64 * 8, value: read_value @@ -148,21 +163,43 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable_mem_ // At begining of sequence // DMA BUS [op, dst64, src64, dst_offset, src_offset, bytes, main_step] - permutation_proves(DMA_BUS_ID, [DMA_MEM_CPY, dst64, src64, 0, offset, count, main_step], sel: previous_seq_end); + permutation_proves(DMA_BUS_ID, [DMA_MEM_CPY, dst64 * 8, src64 * 8, 0, offset, count * 8, main_step], sel: previous_seq_end); + + const expr continue_seq_on_l1 = L1 * (1 - segment_previous_seq_end); + const expr continue_seq_on_no_l1 = (1 - L1) * (1 - 'seq_end); + const expr new_seq_on_l1 = L1 * segment_previous_seq_end; + const expr new_seq_on_no_l1 = (1 - L1) * 'seq_end; // TRANSITIONS: // // After first element of sequence - // count = 'count - 8 - // offset = 'offset + // count = 'count - 1 // src64 = 'src64 + 1 // dst64 = 'dst64 + 1 - (count - (previous_count - 8)) * (1 - previous_seq_end) === 0; - (offset - previous_offset) * (1 - previous_seq_end) === 0; - (src64 - (previous_src64 + 1)) * (1 - previous_seq_end) === 0; - (dst64 - (previous_dst64 + 1)) * (1 - previous_seq_end) === 0; + continue_seq_on_l1 * (count - (segment_previous_count - 1)) == 0; + continue_seq_on_no_l1 * (count - ('count - 1)) === 0; + + continue_seq_on_l1 * (src64 - (segment_previous_src64 + 1)) == 0; + continue_seq_on_no_l1 * (src64 - ('src64 + 1)) === 0; + + continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + 1)) == 0; + continue_seq_on_no_l1 * (dst64 - ('dst64 + 1)) === 0; + + // LATCHS: + // + // offset = 'offset + // is_mem_eq = 'is_mem_eq + // main_step = 'main_step + continue_seq_on_l1 * (offset - segment_previous_offset) == 0; + continue_seq_on_no_l1 * (offset - 'offset) === 0; + + continue_seq_on_l1 * (is_mem_eq - segment_previous_is_mem_eq) == 0; + continue_seq_on_no_l1 * (is_mem_eq - 'is_mem_eq) === 0; + + continue_seq_on_l1 * (main_step - segment_previous_main_step) == 0; + continue_seq_on_no_l1 * (main_step - 'main_step) === 0; // At end of sequence // count must be 0 at end of sequence @@ -187,7 +224,7 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable_mem_ // in padding rows seq_end is active, means no precompiled_mem_store airval padding_rows; - permutation_proves(DMA_BUS_ID, [DMA_MEM_CPY, 0, 0, 0, 1, 0, 0], sel: padding_rows); + permutation_assumes(DMA_BUS_ID, [DMA_MEM_CPY, 0, 0, 0, 1, 0, 0], sel: padding_rows); precompiled_mem_load_padding(padding: padding_rows); // CONTINUATIONS @@ -202,8 +239,8 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable_mem_ 0, // initial offset 0, // initial count 0, - 0,0,0,0,0,0,0,0], // initial main_step - sel: enable_mem_cpy_byte); + 0,0,0,0,0,0,0,0], // next bytes + sel: enable); direct_update_assumes(MEM_CPY_BYTE_CONT_ID, [ segment_id, @@ -221,8 +258,8 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable_mem_ segment_last_seq_end, segment_last_src64, segment_last_dst64, - segment_last_offset, - segment_last_count, + segment_last_offset, + segment_last_count, segment_last_main_step, ...segment_next_bytes], sel: 1 - is_last_segment); diff --git a/precompiles/dma/pil/dual_range.pil b/precompiles/dma/pil/dual_range.pil new file mode 100644 index 000000000..c8dd82f55 --- /dev/null +++ b/precompiles/dma/pil/dual_range.pil @@ -0,0 +1,17 @@ +require "std_lookup.pil" + +// Template to verify two small ranges of values in the same lookup (typically used for bytes). +// The lookup table size is limited by (range1_size * range2_size). + +airtemplate DualRange(int N = 0, const int id, const int min1, const int max1, const int min2, const int max2 ) { + // Number of elements in B + const int COUNT_B = (max2 - min2 + 1); + N = (max1 - min1 + 1) * COUNT_B; + + // Repeat each A element COUNT_B times so every A value pairs with every B value + col fixed A = [min1:COUNT_B..max1:COUNT_B]...; + col fixed B = [min2..max2]...; + + col witness multiplicity; + lookup_proves(id, mul: multiplicity, expressions: [A, B]); +} \ No newline at end of file diff --git a/precompiles/dma/src/dma.rs b/precompiles/dma/src/dma.rs deleted file mode 100644 index c820f550c..000000000 --- a/precompiles/dma/src/dma.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::sync::Arc; - -use fields::PrimeField64; - -use pil_std_lib::Std; -use proofman_common::{AirInstance, ProofmanResult}; -use proofman_util::{timer_start_trace, timer_stop_and_log_info}; - -use super::DmaMemCpyInput; -/* -#[cfg(feature = "packed")] -mod types { - use zisk_pil::{DmaRowPacked, DmaTracePacked}; - pub type DmaTraceRowType = DmaRowPacked; - pub type DmaTraceType = DmaTracePacked; -} - -#[cfg(not(feature = "packed"))] -mod types { - use zisk_pil::{DmaTrace, DmaTraceRow}; - pub type DmaTraceRowType = DmaTraceRow; - pub type DmaTraceType = DmaTrace; -} - -use types::*; -*/ -#[cfg(feature = "packed")] -pub use zisk_pil::{DmaRowPacked as DmaTraceRow, DmaTracePacked as DmaTrace}; - -#[cfg(not(feature = "packed"))] -pub use zisk_pil::{DmaTrace, DmaTraceRow}; - -/// The `DmaSM` struct encapsulates the logic of the Dma State Machine. -pub struct DmaSM { - /// Reference to the PIL2 standard library. - pub std: Arc>, - - /// Number of available dmas in the trace. - pub num_availables: usize, - - /// Range checks ID's - _range_21_bits_id: usize, -} - -impl DmaSM { - /// Creates a new Dma State Machine instance. - /// - /// # Returns - /// A new `DmaSM` instance. - pub fn new(std: Arc>) -> Arc { - // Compute some useful values - let num_availables = DmaTrace::::NUM_ROWS; - - let _range_21_bits_id = std.get_range_id(0, (1 << 21) - 1, None).unwrap(); - - Arc::new(Self { std, num_availables, _range_21_bits_id }) - } - - /// Processes a slice of operation data, updating the trace. - /// - /// # Arguments - /// * `trace` - A mutable reference to the Dma trace. - /// * `input` - The operation data to process. - #[inline(always)] - pub fn process_slice( - &self, - _input: &DmaMemCpyInput, - _trace: &mut DmaTraceRow, - _multiplicities: &mut [u32], - ) { - unimplemented!(); - } - - /// Computes the witness for a series of inputs and produces an `AirInstance`. - /// - /// # Arguments - /// * `sctx` - The setup context containing the setup data. - /// * `inputs` - A slice of operations to process. - /// - /// # Returns - /// An `AirInstance` containing the computed witness data. - pub fn compute_witness( - &self, - inputs: &[Vec], - trace_buffer: Vec, - ) -> ProofmanResult> { - let trace = DmaTrace::::new_from_vec(trace_buffer)?; - - let num_rows = trace.num_rows(); - - let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); - assert!(total_inputs <= num_rows); - - tracing::info!( - "··· Creating Dma instance [{} / {} rows filled {:.2}%]", - total_inputs, - num_rows, - total_inputs as f64 / num_rows as f64 * 100.0 - ); - - timer_start_trace!(DMA_TRACE); - - timer_stop_and_log_info!(DMA_TRACE); - unimplemented!(); - } -} diff --git a/precompiles/dma/src/dma/dma.rs b/precompiles/dma/src/dma/dma.rs new file mode 100644 index 000000000..3b2cfc933 --- /dev/null +++ b/precompiles/dma/src/dma/dma.rs @@ -0,0 +1,295 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use rayon::prelude::*; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_info}; +use zisk_pil::{DMA_ROM_ID, DUAL_RANGE_7_BITS_ID}; + +use crate::{dma::dma_rom::DmaRom, DmaInput}; +use precompiles_helpers::DmaInfo; + +#[cfg(feature = "packed")] +pub use zisk_pil::{DmaRowPacked as DmaTraceRow, DmaTracePacked as DmaTrace}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{DmaTrace, DmaTraceRow}; + +/// The `DmaSM` struct encapsulates the logic of the Dma State Machine. +pub struct DmaSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + pub rom_table_id: usize, + pub dual_range_7_bits_id: usize, + pub range_22_bits_id: usize, + pub range_24_bits_id: usize, +} + +impl DmaSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaSM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + rom_table_id: std.get_virtual_table_id(DMA_ROM_ID).expect("Failed to get dma rom ID"), + dual_range_7_bits_id: std + .get_virtual_table_id(DUAL_RANGE_7_BITS_ID) + .expect("Failed to get dual 7-bits table ID"), + range_22_bits_id: std + .get_range_id(0, 0x3F_FFFF, None) + .expect("Failed to get 22b table ID"), + range_24_bits_id: std + .get_range_id(0, 0xFF_FFFF, None) + .expect("Failed to get 24b table ID"), + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[allow(clippy::too_many_arguments)] + #[inline(always)] + pub fn process_slice( + &self, + input: &DmaInput, + trace: &mut DmaTraceRow, + local_dual_7_bits_multiplicities: &mut [u64], + local_22_bits_values: &mut Vec, + local_24_bits_values: &mut Vec, + local_24_bits_low_values: &mut [u32], + local_rom_multiplicities: &mut [u64], + ) { + let count = DmaInfo::get_count(input.encoded); + let count_lt_256 = count < 256; + let count_ge_256 = 1 - count_lt_256 as usize; + let h_count = ((count >> 8) - count_ge_256) as u32; + trace.set_count_lt_256(count_lt_256); + trace.set_h_count(h_count); + let l_count = (count & 0xFF) as u16 + 256 * count_ge_256 as u16; + trace.set_l_count(l_count); + + // to increase performance because the 99.99% of count is < 64K => h_count < 256 + if h_count < 256 { + local_24_bits_low_values[h_count as usize] += 1; + } else { + local_24_bits_values.push(h_count); + } + + let h_src64 = input.src >> 10; + let h_dst64 = input.dst >> 10; + let l_src64 = (input.src >> 3) as u8 & 0x7F; + let l_dst64 = (input.dst >> 3) as u8 & 0x7F; + + trace.set_h_src64(h_src64); + trace.set_l_src64(l_src64); + let src_offset = input.src as u8 & 0x07; + trace.set_src_offset(src_offset); + + trace.set_h_dst64(h_dst64); + trace.set_l_dst64(l_dst64); + trace.set_dst_offset(input.dst as u8 & 0x07); + + local_22_bits_values.push(h_src64); + local_22_bits_values.push(h_dst64); + let dual_7_bits_row = ((l_src64 as usize) << 7) | l_dst64 as usize; + local_dual_7_bits_multiplicities[dual_7_bits_row] += 1; + // println!( + // "local_dual_7_bits_multiplicities[{dual_7_bits_row} ({l_src64}|{l_dst64})] = {}", + // local_dual_7_bits_multiplicities[dual_7_bits_row] + // ); + + local_rom_multiplicities[DmaRom::get_row(input.dst & 0x07, input.src & 0x07, count)] += 1; + + trace.set_main_step(input.step); + + let pre_count = DmaInfo::get_pre_count(input.encoded) as u8; + let loop_count = DmaInfo::get_loop_count(input.encoded); + let post_count = DmaInfo::get_post_count(input.encoded); + trace.set_use_pre(pre_count > 0); + trace.set_use_memcpy(loop_count > 0); + trace.set_use_post(post_count > 0); + + trace.set_src64_inc_by_pre(DmaInfo::get_src64_inc_by_pre(input.encoded) > 0); + + trace.set_pre_count(pre_count); + trace.set_l_count64((l_count - pre_count as u16 - post_count as u16) >> 3); + trace.set_src_offset_after_pre((src_offset + pre_count) % 8); + + trace.set_sel(true); + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut DmaTraceRow) { + trace.set_count_lt_256(true); + trace.set_h_count(0); + trace.set_l_count(0); + + // to increase performance because the 99.99% of count is < 64K => h_count < 256 + trace.set_h_src64(0); + trace.set_l_src64(0); + trace.set_src_offset(0); + + trace.set_h_dst64(0); + trace.set_l_dst64(0); + trace.set_dst_offset(0); + + trace.set_main_step(0); + + trace.set_use_pre(false); + trace.set_use_memcpy(false); + trace.set_use_post(false); + + trace.set_src64_inc_by_pre(false); + + trace.set_pre_count(0); + trace.set_l_count64(0); + trace.set_src_offset_after_pre(0); + + trace.set_sel(false); + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = DmaTrace::::new_from_vec(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); + assert!(total_inputs <= num_rows); + + tracing::info!( + "··· Creating Dma instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + // Calculate optimal chunk size + let num_threads = rayon::current_num_threads(); + let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); + + // TODO: add new interface with u32 to std to be used with global_rom_multiplicities + // Split the add256_trace.buffer into slices matching each inner vector’s length. + let ( + global_dual_7_bits_multiplicities, + global_22_bits_values, + global_24_bits_values, + global_24_bits_low_values, + global_rom_multiplicities, + ) = flat_inputs + .par_chunks(chunk_size) + .zip(trace_rows.par_chunks_mut(chunk_size)) + .map(|(input_chunk, trace_chunk)| { + // Local array shared by this chunk + let mut local_dual_7_bits_multiplicities = vec![0u64; 1 << 14]; + let mut local_22_bits_values = Vec::::with_capacity(inputs.len() * 2); + let mut local_24_bits_values = Vec::::new(); + let mut local_24_bits_low_values = vec![0u32; 256]; + let mut local_rom_multiplicities = vec![0u64; 1 << 15]; + // Sum all local arrays into a global one + for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { + self.process_slice( + input, + trace_row, + &mut local_dual_7_bits_multiplicities, + &mut local_22_bits_values, + &mut local_24_bits_values, + &mut local_24_bits_low_values, + &mut local_rom_multiplicities, + ); + } + ( + local_dual_7_bits_multiplicities, + local_22_bits_values, + local_24_bits_values, + local_24_bits_low_values, + local_rom_multiplicities, + ) + }) + .reduce( + // Identity: create empty accumulators + || { + ( + vec![0u64; 1 << 14], + Vec::new(), + Vec::new(), + vec![0u32; 256], + vec![0u64; 1 << 15], + ) + }, + // Combine two results + |mut acc, local| { + // Merge multiplicities (element-wise addition) + for (i, &val) in local.0.iter().enumerate() { + acc.0[i] += val; + } + // Concatenate value vectors + acc.1.extend(local.1); + acc.2.extend(local.2); + // Merge low values (element-wise addition) + for (i, &val) in local.3.iter().enumerate() { + acc.3[i] += val; + } + for (i, &val) in local.4.iter().enumerate() { + acc.4[i] += val; + } + acc + }, + ); + + // for (index, value) in global_dual_7_bits_multiplicities.iter().enumerate() { + // if *value != 0 { + // println!("DUAL_7_BITS[{index}]={value}") + // } + // } + self.std + .inc_virtual_rows_ranged(self.dual_range_7_bits_id, &global_dual_7_bits_multiplicities); + self.std.range_checks(self.range_24_bits_id, global_24_bits_low_values); + self.std.inc_virtual_rows_ranged(self.rom_table_id, &global_rom_multiplicities); + + for value in global_22_bits_values { + self.std.range_check(self.range_22_bits_id, value as i64, 1); + } + for value in global_24_bits_values { + self.std.range_check(self.range_24_bits_id, value as i64, 1); + } + + if total_inputs < num_rows { + self.process_empty_slice(&mut trace_rows[total_inputs]); + let empty_row = trace_rows[total_inputs]; + trace_rows[total_inputs + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + } + + timer_stop_and_log_info!(DMA_TRACE); + let from_trace = FromTrace::new(&mut trace); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma/dma_collector.rs b/precompiles/dma/src/dma/dma_collector.rs new file mode 100644 index 000000000..89787947f --- /dev/null +++ b/precompiles/dma/src/dma/dma_collector.rs @@ -0,0 +1,103 @@ +//! The `DmaCollector` module defines an collector to calculate all inputs of an instance +//! for the Dma State Machine. + +use std::{any::Any, collections::VecDeque}; + +use zisk_common::{ + BusDevice, BusId, CollectCounter, ExtOperationData, MemCollectorInfo, OPERATION_BUS_ID, OP_TYPE, +}; +use zisk_core::ZiskOperationType; + +use crate::DmaInput; + +pub struct DmaCollector { + /// Collected inputs for witness computation. + pub inputs: Vec, + + /// The number of operations to collect. + pub num_operations: u64, + + /// Helper to skip instructions based on the plan's configuration. + pub collect_counter: CollectCounter, +} + +impl DmaCollector { + /// Creates a new `DmaCollector`. + /// + /// # Arguments + /// + /// * `bus_id` - The connected bus ID. + /// * `num_operations` - The number of operations to collect. + /// * `collect_skipper` - The helper to skip instructions based on the plan's configuration. + /// + /// # Returns + /// A new `ArithInstanceCollector` instance initialized with the provided parameters. + pub fn new(num_operations: u64, collect_counter: CollectCounter) -> Self { + Self { + inputs: Vec::with_capacity(num_operations as usize), + num_operations, + collect_counter, + } + } +} + +impl BusDevice for DmaCollector { + /// Processes data received on the bus, collecting the inputs necessary for witness computation. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A tuple where: + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, + _mem_collector_info: Option<&[MemCollectorInfo]>, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if self.inputs.len() == self.num_operations as usize { + return false; + } + + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + return true; + } + + if self.collect_counter.should_skip() { + return true; + } + + let data: ExtOperationData = + data.try_into().expect("Regular Metrics: Failed to convert data"); + if let ExtOperationData::OperationDmaMemCpyData(data) = data { + self.inputs.push(DmaInput::from_memcpy(&data, data_ext)); + } else if let ExtOperationData::OperationDmaMemCmpData(data) = data { + self.inputs.push(DmaInput::from_memcmp(&data, data_ext)); + } else { + panic!("Expected ExtOperationData::OperationDmaData"); + } + + self.inputs.len() < self.num_operations as usize + } + + /// Returns the bus IDs associated with this instance. + /// + /// # Returns + /// A vector containing the connected bus ID. + fn bus_id(&self) -> Vec { + vec![OPERATION_BUS_ID] + } + + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/dma/dma_input.rs b/precompiles/dma/src/dma/dma_input.rs new file mode 100644 index 000000000..5dd7aba3a --- /dev/null +++ b/precompiles/dma/src/dma/dma_input.rs @@ -0,0 +1,54 @@ +use zisk_common::{ + OperationDmaMemCmpData, OperationDmaMemCpyData, A, B, OPERATION_BUS_DMA_MEMCMP_DATA_SIZE, + OPERATION_BUS_DMA_MEMCPY_DATA_SIZE, STEP, +}; + +#[derive(Debug)] +pub enum DmaOperation { + MemCpy, + MemCmp, + InputCpy, + MemSet, + MemCpy256, +} +#[derive(Debug)] +pub struct DmaInput { + pub src: u32, + pub dst: u32, + pub operation: DmaOperation, + pub encoded: u64, + pub count_eq: u32, + pub result: i32, + pub step: u64, // main step +} + +impl DmaInput { + pub fn from_memcpy(data: &OperationDmaMemCpyData, _data_ext: &[u64]) -> Self { + let encoded = data[OPERATION_BUS_DMA_MEMCPY_DATA_SIZE - 1]; + Self { + dst: data[A] as u32, + src: data[B] as u32, + step: data[STEP], + encoded, + count_eq: 0, + result: 0, + operation: DmaOperation::MemCpy, + } + } + + pub fn from_memcmp(data: &OperationDmaMemCmpData, _data_ext: &[u64]) -> Self { + let encoded = data[OPERATION_BUS_DMA_MEMCMP_DATA_SIZE - 2]; + let count_eq = data[OPERATION_BUS_DMA_MEMCMP_DATA_SIZE - 1] as u32; + let result = (data[OPERATION_BUS_DMA_MEMCMP_DATA_SIZE - 1] >> 32) as i32; + + Self { + dst: data[A] as u32, + src: data[B] as u32, + step: data[STEP], + encoded, + count_eq, + result, + operation: DmaOperation::MemCmp, + } + } +} diff --git a/precompiles/dma/src/dma/dma_instance.rs b/precompiles/dma/src/dma/dma_instance.rs new file mode 100644 index 000000000..9d37c60a8 --- /dev/null +++ b/precompiles/dma/src/dma/dma_instance.rs @@ -0,0 +1,117 @@ +//! The `DmaInstance` module defines an instance to perform the witness computation +//! for the Dma State Machine. +//! +//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for +//! execution plans. + +use crate::dma::dma_collector::DmaCollector; +use crate::{DmaCheckPoint, DmaSM}; +use fields::PrimeField64; +use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; +use std::sync::Arc; +use zisk_common::ChunkId; +use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; +use zisk_pil::DmaTrace; + +/// The `DmaInstance` struct represents an instance for the Dma State Machine. +/// +/// It encapsulates the `DmaSM` and its associated context, and it processes input data +/// to compute witnesses for the Dma State Machine. +pub struct DmaInstance { + /// Dma state machine. + dma_sm: Arc>, + + /// Instance context. + ictx: InstanceCtx, +} + +impl DmaInstance { + /// Creates a new `DmaInstance`. + /// + /// # Arguments + /// * `dma_sm` - An `Arc`-wrapped reference to the Dma State Machine. + /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. + /// * `bus_id` - The bus ID associated with this instance. + /// + /// # Returns + /// A new `DmaInstance` instance initialized with the provided state machine and + /// context. + pub fn new(dma_sm: Arc>, ictx: InstanceCtx) -> Self { + Self { dma_sm, ictx } + } + + pub fn build_dma_collector(&self, chunk_id: ChunkId) -> DmaCollector { + assert_eq!( + self.ictx.plan.air_id, + DmaTrace::::AIR_ID, + "DmaInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; + DmaCollector::new(num_ops, collect_counter) + } +} + +impl Instance for DmaInstance { + /// Computes the witness for the Dma execution plan. + /// + /// This method leverages the `DmaSM` to generate an `AirInstance` using the collected + /// inputs. + /// + /// # Arguments + /// * `_pctx` - The proof context, unused in this implementation. + /// + /// # Returns + /// An `Option` containing the computed `AirInstance`. + fn compute_witness( + &self, + _pctx: &ProofCtx, + _sctx: &SetupCtx, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + ) -> ProofmanResult>> { + let inputs: Vec<_> = collectors + .into_iter() + .map(|(_, collector)| collector.as_any().downcast::().unwrap().inputs) + .collect(); + + Ok(Some(self.dma_sm.compute_witness(&inputs, trace_buffer)?)) + } + + /// Retrieves the checkpoint associated with this instance. + /// + /// # Returns + /// A `CheckPoint` object representing the checkpoint of the execution plan. + fn check_point(&self) -> &CheckPoint { + &self.ictx.plan.check_point + } + + /// Retrieves the type of this instance. + /// + /// # Returns + /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). + fn instance_type(&self) -> InstanceType { + InstanceType::Instance + } + + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { + assert_eq!( + self.ictx.plan.air_id, + DmaTrace::::AIR_ID, + "DmaInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; + Some(Box::new(DmaCollector::new(num_ops, collect_counter))) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/precompiles/dma/src/dma/dma_rom.rs b/precompiles/dma/src/dma/dma_rom.rs new file mode 100644 index 000000000..b68d88c69 --- /dev/null +++ b/precompiles/dma/src/dma/dma_rom.rs @@ -0,0 +1,20 @@ +use precompiles_helpers::DmaInfo; + +pub enum DmaRom {} + +impl DmaRom { + #[allow(dead_code)] + pub fn get_row_from_encoded(encoded: u64) -> usize { + let src_offset = DmaInfo::get_src_offset(encoded); + let dst_offset = DmaInfo::get_dst_offset(encoded); + let count = DmaInfo::get_count(encoded); + Self::get_row(dst_offset as u32, src_offset as u32, count) + } + pub fn get_row(dst_offset: u32, src_offset: u32, count: usize) -> usize { + assert!(dst_offset < 8, "dst_offset too big {dst_offset}"); + assert!(src_offset < 8, "src_offset too big {src_offset}"); + assert!(count < u32::MAX as usize, "count too big {count}"); + let count = if count >= 256 { (count & 0xFF) + 256 } else { count & 0xFF }; + (dst_offset as usize * 8 + src_offset as usize) * 512 + count + } +} diff --git a/precompiles/dma/src/dma/mod.rs b/precompiles/dma/src/dma/mod.rs new file mode 100644 index 000000000..4a85a16a5 --- /dev/null +++ b/precompiles/dma/src/dma/mod.rs @@ -0,0 +1,11 @@ +#[allow(clippy::module_inception)] +mod dma; +mod dma_collector; +mod dma_input; +mod dma_instance; +mod dma_rom; + +pub use dma::*; +pub use dma_collector::*; +pub use dma_input::*; +pub use dma_instance::*; diff --git a/precompiles/dma/src/dma_64_aligned.rs b/precompiles/dma/src/dma_64_aligned.rs deleted file mode 100644 index 62bb59444..000000000 --- a/precompiles/dma/src/dma_64_aligned.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::sync::Arc; - -use fields::PrimeField64; -use rayon::prelude::*; - -use pil_std_lib::Std; -use proofman_common::{AirInstance, FromTrace}; -use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; -use zisk_pil::{DmaTrace, DmaTraceRow}; - -use super::DmaInput; - -/// The `DmaSM` struct encapsulates the logic of the Dma64Aligned State Machine. -pub struct DmaSM { - /// Reference to the PIL2 standard library. - pub std: Arc>, - - /// Number of available Dma64Aligned in the trace. - pub num_availables: usize, - - /// Range checks ID's - range_id: usize, -} - -impl DmaSM { - /// Creates a new Dma State Machine instance. - /// - /// # Returns - /// A new `DmaSM` instance. - pub fn new(std: Arc>) -> Arc { - // Compute some useful values - let num_availables = DmaTrace::::NUM_ROWS; - - let range_id = std.get_range_id(0, (1 << 16) - 1, None); - - Arc::new(Self { std, num_availables, range_id }) - } - - /// Processes a slice of operation data, updating the trace. - /// - /// # Arguments - /// * `trace` - A mutable reference to the Dma64Aligned trace. - /// * `input` - The operation data to process. - #[inline(always)] - pub fn process_slice( - &self, - input: &DmaInput, - trace: &mut DmaTraceRow, - multiplicities: &mut [u32], - ) { - trace.cin = F::from_bool(input.cin != 0); - let mut cout_2 = input.cin as u32; - - for i in 0..4 { - let al = input.a[i] as u32; - let ah = (input.a[i] >> 32) as u32; - - let bl = input.b[i] as u32; - let bh = (input.b[i] >> 32) as u32; - - trace.a[i][0] = F::from_u32(al); - trace.a[i][1] = F::from_u32(ah); - trace.b[i][0] = F::from_u32(bl); - trace.b[i][1] = F::from_u32(bh); - let cl = al as u64 + bl as u64 + cout_2 as u64; - let cout_1 = cl >> 32; - let ch = ah as u64 + bh as u64 + cout_1; - cout_2 = (ch >> 32) as u32; - - let cll = cl as u16; - let clh = (cl >> 16) as u16; - let chl = ch as u16; - let chh = (ch >> 16) as u16; - - trace.c_chunks[i][0] = F::from_u16(cll); - trace.c_chunks[i][1] = F::from_u16(clh); - trace.c_chunks[i][2] = F::from_u16(chl); - trace.c_chunks[i][3] = F::from_u16(chh); - - trace.cout[i][0] = F::from_u8(cout_1 as u8); - trace.cout[i][1] = F::from_u8(cout_2 as u8); - - multiplicities[cll as usize] += 1; - multiplicities[clh as usize] += 1; - multiplicities[chl as usize] += 1; - multiplicities[chh as usize] += 1; - } - trace.addr_params = F::from_u32(input.addr_main); - trace.addr_a = F::from_u32(input.addr_a); - trace.addr_b = F::from_u32(input.addr_b); - trace.addr_c = F::from_u32(input.addr_c); - trace.step = F::from_u64(input.step_main); - trace.sel = F::ONE; - } - - /// Computes the witness for a series of inputs and produces an `AirInstance`. - /// - /// # Arguments - /// * `sctx` - The setup context containing the setup data. - /// * `inputs` - A slice of operations to process. - /// - /// # Returns - /// An `AirInstance` containing the computed witness data. - pub fn compute_witness( - &self, - inputs: &[Vec], - trace_buffer: Vec, - ) -> AirInstance { - let mut trace = DmaTrace::new_from_vec(trace_buffer); - - let num_rows = trace.num_rows(); - - let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); - assert!(total_inputs <= num_rows); - - tracing::info!( - "··· Creating Dma64Aligned instance [{} / {} rows filled {:.2}%]", - total_inputs, - num_rows, - total_inputs as f64 / num_rows as f64 * 100.0 - ); - - timer_start_trace!(DMA_64_ALIGNED_TRACE); - - // Split the dma64aligned_trace.buffer into slices matching each inner vector’s length. - let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); - let trace_rows = trace.row_slice_mut(); - - // Determinar tamaño óptimo de chunks - let num_threads = rayon::current_num_threads(); - let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); - - // Procesar en chunks para compartir arrays locales de multiplicities - let local_multiplicities_vec: Vec> = flat_inputs - .par_chunks(chunk_size) - .zip(trace_rows.par_chunks_mut(chunk_size)) - .map(|(input_chunk, trace_chunk)| { - // Array local compartido por este chunk - let mut local_multiplicities = vec![0u32; 1 << 16]; - - // Procesar todos los inputs del chunk - for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { - self.process_slice(input, trace_row, &mut local_multiplicities); - } - - local_multiplicities - }) - .collect(); - - // Sumar todos los arrays locales en uno global - let mut global_multiplicities = vec![0u32; 1 << 16]; - for local_multiplicities in local_multiplicities_vec { - for (i, count) in local_multiplicities.iter().enumerate() { - global_multiplicities[i] += count; - } - } - - // Enviar el resultado final al std - self.std.range_checks(self.range_id, global_multiplicities); - - timer_stop_and_log_trace!(DMA_64_ALIGNED_TRACE); - - trace.row_slice_mut()[total_inputs..num_rows] - .par_iter_mut() - .for_each(|slot| *slot = Dma64AlignedTraceRow:: { ..Default::default() }); - AirInstance::::new_from_trace(FromTrace::new(&mut trace)) - } -} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs new file mode 100644 index 000000000..9ce2566f8 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs @@ -0,0 +1,271 @@ +use std::sync::Arc; + +use fields::PrimeField64; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_info}; +use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; +use zisk_common::SegmentId; +use zisk_pil::{Dma64AlignedAirValues, Dma64AlignedTrace, Dma64AlignedTraceRow}; + +use crate::{Dma64AlignedInput, DMA_64_ALIGNED_OPS_BY_ROW}; +use precompiles_helpers::DmaInfo; + +/// The `Dma64AlignedSM` struct encapsulates the logic of the Dma64Aligned State Machine. +pub struct Dma64AlignedSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + range_16_bits_id: usize, + op_x_rows: usize, +} + +impl Dma64AlignedSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `Dma64AlignedSM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + range_16_bits_id: std + .get_range_id(0, 0xFFFF, None) + .expect("Failed to get 16b table ID"), + op_x_rows: DMA_64_ALIGNED_OPS_BY_ROW, + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_input( + &self, + input: &Dma64AlignedInput, + trace: &mut [Dma64AlignedTraceRow], + _local_16_bits_table: &mut [u32], // for input_cpy + air_values: &mut Dma64AlignedAirValues, + ) -> usize { + let rows = input.rows as usize; + let skip_count = input.skip_rows as usize * self.op_x_rows; + let initial_count = DmaInfo::get_loop_count(input.encoded) - skip_count; + let mut count64 = initial_count; + // println!( + // "DMA_64_ALIGNED INPUT {input:?} count:{count64} rows:{rows} dma_info:{}", + // DmaInfo::to_string(input.encoded) + // ); + let mut src_values_index = 0; + let mut dst64 = ((input.dst + 7) >> 3) + skip_count as u32; + let mut src64 = ((input.src + 7) >> 3) + skip_count as u32; + let mut seq_end = false; + let addr_incr_by_row = self.op_x_rows as u32; + for (irow, row) in trace.iter_mut().enumerate().take(rows) { + row.set_main_step(input.step); + row.set_is_mem_eq(input.is_mem_eq); + row.set_previous_seq_end(irow == 0 && input.skip_rows == 0); + + // calculate the first aligned address + // if dst is aligned is same address if not it's addr + 8 + row.set_dst64(dst64); + row.set_src64(src64); + dst64 += addr_incr_by_row; + src64 += addr_incr_by_row; + + row.set_count(count64 as u32); + let use_count = if count64 <= self.op_x_rows { + seq_end = true; + for index in count64..self.op_x_rows { + row.set_sel_op(index, false); + row.set_value(index, 0, 0); + row.set_value(index, 1, 0); + } + count64 + } else { + count64 -= self.op_x_rows; + self.op_x_rows + }; + row.set_seq_end(seq_end); + for index in 0..use_count { + row.set_sel_op(index, true); + let value = input.src_values[src_values_index]; + src_values_index += 1; + row.set_value(index, 0, value as u32); + row.set_value(index, 1, (value >> 32) as u32); + } + } + + if input.is_last_instance_input { + println!("Dma64Aligned LAST_INSTANCE_INPUT {seq_end}"); + if seq_end { + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_is_mem_eq = F::ZERO; + } else { + air_values.segment_last_seq_end = F::ZERO; + air_values.segment_last_src64 = F::from_u32(src64 - addr_incr_by_row); + air_values.segment_last_dst64 = F::from_u32(dst64 - addr_incr_by_row); + air_values.segment_last_main_step = F::from_u64(input.step); + let last_count = initial_count - (rows - 1) * self.op_x_rows; + air_values.segment_last_count = F::from_u32(last_count as u32); + air_values.last_count_chunk[0] = F::from_u16(last_count as u16); + air_values.last_count_chunk[1] = F::from_u16((last_count >> 16) as u16); + air_values.segment_last_is_mem_eq = F::ZERO; + } + } + rows + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut Dma64AlignedTraceRow) { + trace.set_main_step(0); + trace.set_is_mem_eq(false); + trace.set_dst64(0); + trace.set_src64(0); + trace.set_count(0); + for index in 0..self.op_x_rows { + trace.set_sel_op(index, false); + trace.set_value(index, 0, 0); + trace.set_value(index, 1, 0); + } + trace.set_seq_end(true); + trace.set_previous_seq_end(true); + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + segment_id: SegmentId, + is_last_segment: bool, + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = Dma64AlignedTrace::::new_from_vec(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs + .iter() + .map(|inputs| inputs.iter().map(|input| input.rows as usize).sum::()) + .sum(); + + assert!(total_inputs > 0); + // println!("LAST INPUT: {:?}", inputs.last().unwrap()); + // println!("DMA_64_ALIGNED TOTALS total_inputs:{total_inputs} num_rows:{num_rows}"); + assert!( + total_inputs <= num_rows, + "Too many inputs, total_inputs:{total_inputs} num_rows:{num_rows}" + ); + + tracing::info!( + "··· Creating Dma64Aligned instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_64_ALIGNED_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + let mut local_16_bits_table = vec![0u32; 1 << 16]; + let mut air_values = Dma64AlignedAirValues::::new(); + + // TODO: inputs between instances + let mut row_offset = 0; + for input in flat_inputs.iter() { + let rows_used = self.process_input( + input, + &mut trace_rows[row_offset..], + &mut local_16_bits_table, + &mut air_values, + ); + row_offset += rows_used; + } + + // padding + if row_offset < num_rows { + air_values.padding_size = F::from_u32((num_rows - row_offset) as u32); + self.process_empty_slice(&mut trace_rows[row_offset]); + let empty_row = trace_rows[row_offset]; + trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_is_mem_eq = F::ZERO; + } + + // add range check of count to check that it's a positive 32-bits number + let last_count = air_values.segment_last_count.as_canonical_u64(); + local_16_bits_table[(last_count & 0xFFFF) as usize] += 1; + local_16_bits_table[((last_count >> 16) & 0xFFFF) as usize] += 1; + + self.std.range_checks(self.range_16_bits_id, local_16_bits_table); + + let segment_id = segment_id.into(); + air_values.segment_id = F::from_usize(segment_id); + air_values.is_last_segment = F::from_bool(is_last_segment); + + let first_input = flat_inputs.first().unwrap(); + if first_input.skip_rows == 0 { + air_values.segment_previous_seq_end = F::ONE; + air_values.segment_previous_dst64 = F::from_u32(0); + air_values.segment_previous_src64 = F::from_u32(0); + air_values.segment_previous_main_step = F::from_u64(0); + air_values.segment_previous_count = F::from_u32(0); + air_values.segment_previous_is_mem_eq = F::from_bool(false); + } else { + assert!(segment_id > 0); + air_values.segment_previous_seq_end = F::ZERO; + air_values.segment_previous_dst64 = F::from_u32( + (trace_rows[0].dst64.as_canonical_u64() - self.op_x_rows as u64) as u32, + ); + air_values.segment_previous_src64 = F::from_u32( + (trace_rows[0].src64.as_canonical_u64() - self.op_x_rows as u64) as u32, + ); + air_values.segment_previous_main_step = trace_rows[0].main_step; + air_values.segment_previous_count = F::from_u32( + (trace_rows[0].count.as_canonical_u64() + self.op_x_rows as u64) as u32, + ); + air_values.segment_previous_is_mem_eq = trace_rows[0].is_mem_eq; + } + #[cfg(feature = "debug_dma")] + { + println!("TRACE Dma64AlignedSM @{segment_id} [0] {:?}", trace[0]); + println!( + "TRACE Dma64AlignedSM @{segment_id} [{}] {:?}", + num_rows - 1, + trace[num_rows - 1] + ); + println!("TRACE Dma64AlignedSM AIR_VALUES {:?}", air_values); + } + timer_stop_and_log_info!(DMA_64_ALIGNED_TRACE); + let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs new file mode 100644 index 000000000..3b6177e07 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs @@ -0,0 +1,115 @@ +//! The `Dma64AlignedInstance` module defines an instance to perform the witness computation +//! for the Dma State Machine. +//! +//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for +//! execution plans. + +use crate::Dma64AlignedInput; +use std::any::Any; +use std::collections::VecDeque; +use zisk_common::{BusDevice, BusId, CollectCounter, MemCollectorInfo, OPERATION_BUS_ID, OP_TYPE}; +use zisk_core::ZiskOperationType; + +pub struct Dma64AlignedCollector { + /// Collected inputs for witness computation. + pub inputs: Vec, + + /// The number of inputs to collect. + pub num_inputs: u64, + + /// Helper to skip instructions based on the plan's configuration. + pub collect_counter: CollectCounter, + + pub trace_offset: usize, + pub last_segment_collector: bool, +} + +impl Dma64AlignedCollector { + /// Creates a new `Dma64AlignedCollector`. + /// + /// # Arguments + /// + /// * `bus_id` - The connected bus ID. + /// * `num_inputs` - The number of inputs to collect. + /// * `collect_counter` - The helper to skip instructions based on the plan's configuration. + /// + /// # Returns + /// A new `Dma64AlignedCollector` instance initialized with the provided parameters. + pub fn new( + num_inputs: u64, + collect_counter: CollectCounter, + last_segment_collector: bool, + ) -> Self { + Self { + inputs: Vec::with_capacity(num_inputs as usize), + num_inputs, + collect_counter, + trace_offset: 0, + last_segment_collector, + } + } +} + +impl BusDevice for Dma64AlignedCollector { + /// Processes data received on the bus, collecting the inputs necessary for witness computation. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A tuple where: + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, + _mem_collector_info: Option<&[MemCollectorInfo]>, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if self.inputs.len() == self.num_inputs as usize { + return false; + } + + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + return true; + } + + let rows = Dma64AlignedInput::get_rows(data) as u32; + if rows == 0 { + return true; + } + + if let Some((skip, max_count)) = self.collect_counter.should_process(rows) { + self.inputs.push(Dma64AlignedInput::from( + data, + data_ext, + self.trace_offset, + skip as usize, + max_count as usize, + self.last_segment_collector && self.collect_counter.is_final_skip(), + )); + self.trace_offset += max_count as usize; + } + + self.inputs.len() < self.num_inputs as usize + } + + /// Returns the bus IDs associated with this instance. + /// + /// # Returns + /// A vector containing the connected bus ID. + fn bus_id(&self) -> Vec { + vec![OPERATION_BUS_ID] + } + + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs new file mode 100644 index 000000000..b1d159ac6 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs @@ -0,0 +1,72 @@ +use precompiles_helpers::DmaInfo; +use zisk_common::{A, B, DMA_ENCODED, STEP}; + +use crate::DMA_64_ALIGNED_OPS_BY_ROW; + +#[derive(Debug)] +pub struct Dma64AlignedInput { + pub src: u32, + pub dst: u32, + pub is_first_instance_input: bool, + pub is_last_instance_input: bool, + pub is_mem_eq: bool, + pub trace_offset: u32, // offset inside trace to paralelize + pub skip_rows: u32, // inside input how many rows skip + pub rows: u32, // number of rows used + pub step: u64, + pub encoded: u64, + pub src_values: Vec, +} + +impl Dma64AlignedInput { + pub fn get_rows(data: &[u64]) -> usize { + let encoded = data[DMA_ENCODED]; + if DmaInfo::get_dst_offset(encoded) == DmaInfo::get_src_offset(encoded) { + let count = DmaInfo::get_loop_count(encoded); + if count > 0 { + count.div_ceil(DMA_64_ALIGNED_OPS_BY_ROW) + } else { + 0 + } + } else { + 0 + } + } + pub fn get_count(data: &[u64]) -> usize { + let encoded = data[DMA_ENCODED]; + if DmaInfo::get_dst_offset(encoded) == DmaInfo::get_src_offset(encoded) { + DmaInfo::get_loop_count(encoded) + } else { + 0 + } + } + pub fn from( + data: &[u64], + data_ext: &[u64], + trace_offset: usize, + skip_rows: usize, + max_rows: usize, + is_last_instance_input: bool, + ) -> Self { + let encoded = data[DMA_ENCODED]; + let pre_count = DmaInfo::get_pre_count(encoded) as u32; + let skip_count = skip_rows * DMA_64_ALIGNED_OPS_BY_ROW; + let data_offset = DmaInfo::get_loop_data_offset(encoded) + skip_count; + let count = DmaInfo::get_loop_count(encoded) - skip_count; + let total_rows = DmaInfo::get_loop_count(encoded).div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); + let rows = std::cmp::min(total_rows - skip_rows, max_rows) as u32; + Self { + dst: data[A] as u32 + pre_count, + src: data[B] as u32 + pre_count, + trace_offset: trace_offset as u32, + is_first_instance_input: trace_offset == 0, + is_last_instance_input, + step: data[STEP], + skip_rows: skip_rows as u32, + rows, + encoded, + src_values: data_ext[data_offset..data_offset + count].to_vec(), + is_mem_eq: false, + } + } +} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs new file mode 100644 index 000000000..054d5c847 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs @@ -0,0 +1,141 @@ +//! The `Dma64AlignedInstance` module defines an instance to perform the witness computation +//! for the Dma State Machine. +//! +//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for +//! execution plans. + +use crate::{Dma64AlignedCollector, Dma64AlignedSM, DmaCheckPoint}; +use fields::PrimeField64; +use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; +use std::sync::Arc; +use zisk_common::ChunkId; +use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; +use zisk_pil::Dma64AlignedTrace; + +/// The `Dma64AlignedInstance` struct represents an instance for the Dma State Machine. +/// +/// It encapsulates the `Dma64AlignedSM` and its associated context, and it processes input data +/// to compute witnesses for the Dma64Aligned State Machine. +pub struct Dma64AlignedInstance { + /// Dma state machine. + dma_64_aligned_sm: Arc>, + + /// Instance context. + ictx: InstanceCtx, + + /// Flag to define that it's last segment + is_last_segment: bool, +} + +impl Dma64AlignedInstance { + /// Creates a new `Dma64AlignedInstance`. + /// + /// # Arguments + /// * `dma_64_aligned_sm` - An `Arc`-wrapped reference to the Dma 64 Aligned State Machine. + /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. + /// * `bus_id` - The bus ID associated with this instance. + /// + /// # Returns + /// A new `Dma64AlignedInstance` instance initialized with the provided state machine and + /// context. + pub fn new(dma_64_aligned_sm: Arc>, ictx: InstanceCtx) -> Self { + let is_last_segment = { + let meta = ictx.plan.meta.as_ref().unwrap(); + let checkpoint = meta.downcast_ref::().unwrap(); + checkpoint.is_last_segment + }; + Self { dma_64_aligned_sm, ictx, is_last_segment } + } + + pub fn build_dma_collector(&self, chunk_id: ChunkId) -> Dma64AlignedCollector { + assert_eq!( + self.ictx.plan.air_id, + Dma64AlignedTrace::::AIR_ID, + "Dma64AlignedInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; + Dma64AlignedCollector::new( + num_inputs, + collect_counter, + Some(chunk_id) == collect_info.last_chunk, + ) + } +} + +impl Instance for Dma64AlignedInstance { + /// Computes the witness for the Dma execution plan. + /// + /// This method leverages the `Dma64AlignedSM` to generate an `AirInstance` using the collected + /// inputs. + /// + /// # Arguments + /// * `_pctx` - The proof context, unused in this implementation. + /// + /// # Returns + /// An `Option` containing the computed `AirInstance`. + fn compute_witness( + &self, + _pctx: &ProofCtx, + _sctx: &SetupCtx, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + ) -> ProofmanResult>> { + let inputs: Vec<_> = collectors + .into_iter() + .map(|(_, collector)| { + collector.as_any().downcast::().unwrap().inputs + }) + .collect(); + // Extract segment id from instance context + let segment_id = self.ictx.plan.segment_id.unwrap(); + + Ok(Some(self.dma_64_aligned_sm.compute_witness( + &inputs, + segment_id, + self.is_last_segment, + trace_buffer, + )?)) + } + + /// Retrieves the checkpoint associated with this instance. + /// + /// # Returns + /// A `CheckPoint` object representing the checkpoint of the execution plan. + fn check_point(&self) -> &CheckPoint { + &self.ictx.plan.check_point + } + + /// Retrieves the type of this instance. + /// + /// # Returns + /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). + fn instance_type(&self) -> InstanceType { + InstanceType::Instance + } + + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { + assert_eq!( + self.ictx.plan.air_id, + Dma64AlignedTrace::::AIR_ID, + "Dma64AlignedInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; + Some(Box::new(Dma64AlignedCollector::new( + num_inputs, + collect_counter, + Some(chunk_id) == collect_info.last_chunk, + ))) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/precompiles/dma/src/dma_64_aligned/mod.rs b/precompiles/dma/src/dma_64_aligned/mod.rs new file mode 100644 index 000000000..9f48724d5 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/mod.rs @@ -0,0 +1,10 @@ +#[allow(clippy::module_inception)] +mod dma_64_aligned; +mod dma_64_aligned_collector; +mod dma_64_aligned_input; +mod dma_64_aligned_instance; + +pub use dma_64_aligned::*; +pub use dma_64_aligned_collector::*; +pub use dma_64_aligned_input::*; +pub use dma_64_aligned_instance::*; diff --git a/precompiles/dma/src/dma_64_aligned_input.rs b/precompiles/dma/src/dma_64_aligned_input.rs deleted file mode 100644 index 534b2c88a..000000000 --- a/precompiles/dma/src/dma_64_aligned_input.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::dma_constants::*; -use zisk_common::OperationDmaData; -use zisk_common::{B, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; - -#[derive(Debug)] -pub struct MemCpyInput { - pub step_main: u64, - pub addr_src: u32, - pub addr_dst: u32, - pub a, - pub a: [u64; 4], - pub b: [u64; 4], - pub count: u32, -} - -impl MemCpyInput { - pub fn from(values: &OperationDmaData) -> Self { - Self { - step_main: values[STEP], - addr_main: values[B] as u32, - addr_a: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as u32, - addr_b: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] as u32, - addr_c: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS + DIRECT_READ_PARAMS] - as u32, - cin: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS], - a: values[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(), - b: values[START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] - .try_into() - .unwrap(), - } - } -} diff --git a/precompiles/dma/src/dma_bus_device.rs b/precompiles/dma/src/dma_bus_device.rs index 5ae0dd27a..c8199fc0e 100644 --- a/precompiles/dma/src/dma_bus_device.rs +++ b/precompiles/dma/src/dma_bus_device.rs @@ -4,26 +4,30 @@ use std::{collections::VecDeque, ops::Add}; -use zisk_common::{BusDevice, BusDeviceMode, BusId, Metrics, B, OPERATION_BUS_ID, OP_TYPE, STEP}; +use precompiles_helpers::DmaInfo; +use zisk_common::{BusDevice, BusDeviceMode, BusId, Metrics, B, OPERATION_BUS_ID, OP_TYPE}; use zisk_common::{MemCollectorInfo, A, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; use zisk_core::ZiskOperationType; -use crate::{generate_dma_mem_inputs, skip_dma_mem_inputs}; +use crate::{generate_dma_mem_inputs, skip_dma_mem_inputs, DMA_64_ALIGNED_OPS_BY_ROW}; /// The `DmaCounter` struct represents a counter that monitors and measures /// dma-related operations on the data bus. /// /// It tracks specific operation types (`ZiskOperationType`) and updates counters for each /// accepted operation type whenever data is processed on the bus. +#[derive(Debug)] pub struct DmaCounterInputGen { /// sizes of memcpy - dma_pre_post_ops: usize, - dma_ops: usize, - dma_unaligned_ops: usize, - dma_64_aligned_ops: usize, + pub dma_pre_post_ops: usize, + pub dma_ops: usize, + pub dma_unaligned_inputs: usize, + pub dma_unaligned_rows: usize, + pub dma_64_aligned_inputs: usize, + pub dma_64_aligned_rows: usize, /// Bus device mode (counter or input generator). - mode: BusDeviceMode, + pub mode: BusDeviceMode, } impl DmaCounterInputGen { @@ -35,7 +39,15 @@ impl DmaCounterInputGen { /// # Returns /// A new `DmaCounter` instance. pub fn new(mode: BusDeviceMode) -> Self { - Self { dma_pre_post_ops: 0, dma_ops: 0, dma_unaligned_ops: 0, dma_64_aligned_ops: 0, mode } + Self { + dma_pre_post_ops: 0, + dma_ops: 0, + dma_unaligned_inputs: 0, + dma_64_aligned_inputs: 0, + dma_unaligned_rows: 0, + dma_64_aligned_rows: 0, + mode, + } } /// Retrieves the count of instructions for a specific `ZiskOperationType`. @@ -45,24 +57,37 @@ impl DmaCounterInputGen { /// * `src` - The source address of operation. /// * `count` - The bytes of operation. pub fn inst_count_memcpy(&mut self, dst: u64, src: u64, count: usize) { - let src_offset = dst & 0x07; - let dst_offset = src & 0x07; + let dst_offset = dst & 0x07; + let src_offset = src & 0x07; // offset => max bytes is 8 - offset if count > 0 { let remaining = if dst_offset > 0 { self.dma_pre_post_ops += 1; - std::cmp::min(8 - dst_offset as usize, count) + count - std::cmp::min(8 - dst_offset as usize, count) } else { count }; + if (remaining % 8) > 0 { + // adding a post because last write isn't full (8-bytes) self.dma_pre_post_ops += 1; } if dst_offset == src_offset { - self.dma_64_aligned_ops += remaining >> 3; - } else { - self.dma_unaligned_ops += remaining >> 3; + // println!( + // "count: {count} remaining: {remaining} self.dma_64_aligned_ops: {}", + // self.dma_64_aligned_rows + // ); + let rows = (remaining >> 3).div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); + self.dma_64_aligned_rows += rows; + self.dma_64_aligned_inputs += 1; + } else if remaining > 7 { + // check remaining because unaligned add an extra row for each unaligned, means + // if remaming >> 3 is 0, add extra row. + // on unalignmed_ops, each dst write use its src read and next src read also. + // the last src read don't have write. + self.dma_unaligned_rows += (remaining >> 3) + 1; + self.dma_unaligned_inputs += 1; } } self.dma_ops += 1; @@ -82,7 +107,7 @@ impl Metrics for DmaCounterInputGen { fn measure(&mut self, data: &[u64]) { let dst = data[A]; let src = data[B]; - let count = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as usize; + let count = DmaInfo::get_count(data[OPERATION_PRECOMPILED_BUS_DATA_SIZE]); self.inst_count_memcpy(dst, src, count); } @@ -110,8 +135,10 @@ impl Add for DmaCounterInputGen { DmaCounterInputGen { dma_pre_post_ops: self.dma_pre_post_ops + other.dma_pre_post_ops, dma_ops: self.dma_ops + other.dma_ops, - dma_unaligned_ops: self.dma_unaligned_ops + other.dma_unaligned_ops, - dma_64_aligned_ops: self.dma_64_aligned_ops + other.dma_64_aligned_ops, + dma_unaligned_inputs: self.dma_unaligned_inputs + other.dma_unaligned_inputs, + dma_64_aligned_inputs: self.dma_64_aligned_inputs + other.dma_64_aligned_inputs, + dma_unaligned_rows: self.dma_unaligned_rows + other.dma_unaligned_rows, + dma_64_aligned_rows: self.dma_64_aligned_rows + other.dma_64_aligned_rows, mode: self.mode, } } @@ -133,7 +160,7 @@ impl BusDevice for DmaCounterInputGen { &mut self, bus_id: &BusId, data: &[u64], - _data_ext: &[u64], + data_ext: &[u64], pending: &mut VecDeque<(BusId, Vec, Vec)>, mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { @@ -143,11 +170,8 @@ impl BusDevice for DmaCounterInputGen { return true; } - let dst = data[A]; - let src = data[B]; - let count = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as usize; if let Some(mem_collectors_info) = mem_collector_info { - if skip_dma_mem_inputs(dst, src, count, mem_collectors_info) { + if skip_dma_mem_inputs(data, data_ext, mem_collectors_info) { return true; } } @@ -157,10 +181,7 @@ impl BusDevice for DmaCounterInputGen { self.measure(data); } - let step_main = data[STEP]; - let data_ext = &[0u64; 4]; - generate_dma_mem_inputs(dst, src, count, step_main, data, data_ext, only_counters, pending); - + generate_dma_mem_inputs(data, data_ext, only_counters, pending); true } diff --git a/precompiles/dma/src/dma_constants.rs b/precompiles/dma/src/dma_constants.rs index 6b358c74b..d85d443a3 100644 --- a/precompiles/dma/src/dma_constants.rs +++ b/precompiles/dma/src/dma_constants.rs @@ -10,3 +10,4 @@ pub const START_READ_PARAMS: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + PARAM pub const START_WRITE_PARAMS: usize = START_READ_PARAMS + READ_PARAMS * PARAM_CHUNKS + RESULT_PARAMS; pub const WRITE_ADDR_PARAM: usize = READ_PARAMS + DIRECT_READ_PARAMS; +pub const DMA_64_ALIGNED_OPS_BY_ROW: usize = 4; diff --git a/precompiles/dma/src/dma_gen_mem_inputs.rs b/precompiles/dma/src/dma_gen_mem_inputs.rs index 17fb6dcb3..962da783b 100644 --- a/precompiles/dma/src/dma_gen_mem_inputs.rs +++ b/precompiles/dma/src/dma_gen_mem_inputs.rs @@ -1,290 +1,207 @@ -use crate::DmaHelpers; +use precompiles_common::MemBusHelpers; +use precompiles_helpers::{DmaHelpers, DmaInfo}; use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; - -#[derive(Debug)] -pub struct DmaMemInputConfig { - pub indirect_params: usize, - pub rewrite_params: bool, - pub read_params: usize, - pub write_params: usize, - pub chunks_per_param: usize, -} - -// all DMA memory operation are aligned - -// minimal trace -// reads + writes -const MASK_ALIGNED_ADDR: u64 = !0x07; -/* -pub fn calculate(dst: u64, src: u64, count: u64) -> (usize, usize, usize) { - let from_src = src & MASK_ALIGNED_ADDR; - let to_src = (src + count - 1) & MASK_ALIGNED_ADDR; - let count_src = (to_src - from_src) >> 3; - - let first_dst_addr = dst & MASK_ALIGNED_ADDR; - let read_first_dst_addr = dst & 0x07 != 0; - - let last_dst_addr = (dst + count - 1) & MASK_ALIGNED_ADDR; - let read_last_dst_addr = (dst + count) & 0x07 != 0 && last_dst_addr > first_dst_addr; -} -*/ +use zisk_common::{ + BusId, MemCollectorInfo, A, B, DMA_ENCODED, OP, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP, +}; +use zisk_core::zisk_ops::ZiskOp; pub fn generate_dma_mem_inputs( - dst: u64, - src: u64, - count: usize, - _step_main: u64, - _data: &[u64], - _data_ext: &[u64], + data: &[u64], + data_ext: &[u64], _only_counters: bool, - _pending: &mut VecDeque<(BusId, Vec, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { - let from_src = src & !0x07; - let to_src = (src + count as u64 - 1) & MASK_ALIGNED_ADDR; - let _count_src = (to_src - from_src) >> 3; - - let first_dst_addr = dst & MASK_ALIGNED_ADDR; - let _read_first_dst_addr = dst & 0x07 != 0; - - let last_dst_addr = (dst + count as u64 - 1) & MASK_ALIGNED_ADDR; - let _read_last_dst_addr = (dst + count as u64) & 0x07 != 0 && last_dst_addr > first_dst_addr; - - let _dma = DmaHelpers::precalculate_dma_values(dst, src, count as usize); - - unimplemented!(); - /* - if dma.pre_count > 0 { - MemBusHelpers::mem_aligned_load( - dst_aligned, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], - pending, - ); - MemBusHelpers::mem_aligned_load( - dst_aligned, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], - pending, - ); - - MemBusHelpers::mem_aligned_load( - dst_aligned, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], - pending, - ); - MemBusHelpers::mem_aligned_write( - dst_aligned, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], - pending, - ); - } - - for i in 0..dma.memcpy_count / 8 { - MemBusHelpers::mem_aligned_load( - src_aligned + i as u32 * 8, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + i as usize], - pending, - ); - MemBusHelpers::mem_aligned_write( - dst_aligned + i as u32 * 8, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + i as usize], - pending, - ); - } - - if dma.post_count > 0 { - MemBusHelpers::mem_aligned_load( - dst_aligned, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], - pending, - ); - MemBusHelpers::mem_aligned_load( - dst_aligned, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], - pending, - ); - MemBusHelpers::mem_aligned_write( - dst_aligned, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], - pending, - ); - } - */ - /* - // Information collected during memory trace generation includes the alignated reads: - // - previous read dst & ~0x07 if dst % 8 > 0 - // - previous read (dst + count - 1) & ~0x07 if use post - // - src reads from: src & ~0x07 to (src + count - 1) & ~0x07 + let dst = data[A]; + let src = data[B]; + let encoded = data[DMA_ENCODED]; + let dst64 = (dst & !0x07) as u32; + let src64 = (src & !0x07) as u32; + let main_step = data[STEP]; + let pre_count = DmaInfo::get_pre_count(encoded) as u64; + let dst_offset = dst & 0x07; + let src_offset = src & 0x07; + let aligned = dst_offset == src_offset; - let write_addr = dst & ~0x07; - let write_count = (((dst + count - 1) - write_addr - 1) / 8) + 1; + // NOTE: for dual memories it's very important to keep the order of loads and stores because + // stores happend after loads. + let mut wr_pending = VecDeque::new(); + if pre_count > 0 { + let pre_data_offset = DmaInfo::get_pre_data_offset(encoded); + let read_value = data_ext[pre_data_offset]; + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@pre 0x{src64:08X} S:{main_step} V:{read_value} (0x{read_value:016X})"); - // full aligned src % 8 == 0 && dst % 8 == 0 - // parcialy aligned src % 8 == dst % 8 == 0 + MemBusHelpers::mem_aligned_load(src64, main_step, read_value, pending); + // pre-load of write address before unaligned write + let pre_value = data_ext[DmaInfo::get_pre_write_offset(encoded)]; - // block - let mut read_index = 0; - let mut align_write_addr = 0; + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@pre-p 0x{dst64:08X} S:{main_step} V:{pre_value} (0x{pre_value:016X})"); - if offset == 0 { + MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, pending); - } else { - for i in 0..count { - MemBusHelpers::mem_aligned_store( - dst_aligned, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], - pending, + let write_value = if DmaInfo::is_double_read_pre(encoded) { + let second_read_value = data_ext[pre_data_offset + 1]; + #[cfg(feature = "debug_dma")] + println!( + "DMA: mem_aligned_load@pre2 0x{:08X} S:{main_step} V:{second_read_value} (0x{second_read_value:016X})", + src64 + 8 ); - } + MemBusHelpers::mem_aligned_load(src64 + 8, main_step, second_read_value, pending); + DmaHelpers::calculate_write_value( + dst_offset, + src_offset, + pre_count, + pre_value, + &[read_value, second_read_value], + ) + } else { + DmaHelpers::calculate_write_value( + dst_offset, + src_offset, + pre_count, + pre_value, + &[read_value], + ) + }; + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write@pre 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); + + MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, &mut wr_pending); } - let src_offset = dst & 0x07; - let dst_offset = src & 0x07; - - if count <= dst_offset { - if count > 0 { - let dst_aligned = dst & 0xFFFF_FFFF_FFFF_FFF8; - - MemBusHelpers::mem_aligned_load( - dst_aligned, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], - pending, - ); - - MemBusHelpers::mem_aligned_store( - dst_aligned, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 0], - pending, - ); - } - } else { - if dst_offset > 0 { - self.dma_pre_pos_ops += 1 - } - if dst_offset == src_offset { - self.dma_64_aligned_ops += (count - dst_offset) >> 3 + // this is part of words loop + let post_count = DmaInfo::get_post_count(encoded) as u64; + let loop_count = DmaInfo::get_loop_count(encoded); + if loop_count > 0 { + let loop_src = src as u32 + pre_count as u32; + let dst64 = (dst as u32 + pre_count as u32) & !0x07; + let src64 = loop_src & !0x07; + let loop_data_offset = DmaInfo::get_loop_data_offset(encoded); + let loop_data_count = DmaInfo::get_loop_count(encoded); + let loop_src_data_end = + loop_data_offset + loop_data_count + ((loop_src & 0x07) > 0) as usize; + let values = &data_ext[loop_data_offset..loop_src_data_end]; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load_from_slice 0x{src64:08X} S:{main_step} V:{values:?}"); + + MemBusHelpers::mem_aligned_load_from_slice(src64, main_step, values, pending); + + let src_offset = (src_offset + pre_count) & 0x07; + if aligned { + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write_from_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); + MemBusHelpers::mem_aligned_write_from_slice(dst64, main_step, values, &mut wr_pending); } else { - self.dma_unaligned_ops += (count - dst_offset) >> 3 - } - - if (count - dst_offset) % 8 > 0 { - self.dma_pre_pos_ops += 1 + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write_from_read_unaligned_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); + MemBusHelpers::mem_aligned_write_from_read_unaligned_slice( + dst64, + main_step, + src_offset as u8, + values, + &mut wr_pending, + ); } } - self.dma_ops += 1; + if post_count > 0 { + let src_offset = src & 0x07; - let dst = data[A]; - let src = data[B]; + let post_data_offset = DmaInfo::get_post_data_offset(encoded); + let src64 = (src as u32 + pre_count as u32 + loop_count as u32 * 8) & !0x07; + let dst64 = dst as u32 + pre_count as u32 + loop_count as u32 * 8; + let read_value = data_ext[post_data_offset]; - // - // precompiled_mem_load( sel: enabled, main_step: main_step, addr: src_addr * 8, value: [values[0], values[1]]); - // precompiled_mem_load( sel: enabled_second_read, main_step: main_step, addr: src_addr * 8 + 8, value: [values[2], values[3]]); - // precompiled_mem_load( sel: enabled, main_step: main_step, addr: dst_addr * 8, value: [values[4], values[5]]); - // precompiled_mem_store(sel: enabled, main_step: main_step, addr: dst_addr * 8, value: write_value); + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@post 0x{src64:08X} S:{main_step} V:{read_value} (0x{read_value:016X})"); - let src_aligned = src & 0xFFFF_FFFF_FFFF_FFF8; - MemBusHelpers::mem_aligned_load( - src_aligned + iparam as u32 * 8, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], - pending, - ); + MemBusHelpers::mem_aligned_load(src64, main_step, read_value, pending); - // ALIGNED, UNALIGNED - for iparam in 0..PARAMS { - MemBusHelpers::mem_aligned_load( - src + iparam as u32 * 8, - step_main, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], - pending, - ); - } + // pre-load of write address before unaligned write + let pre_value = data_ext[DmaInfo::get_post_write_offset(encoded)]; - // generate load params - for iparam in 0..READ_PARAMS { - let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; - for ichunk in 0..PARAM_CHUNKS { - MemBusHelpers::mem_aligned_load( - param_addr + ichunk as u32 * 8, - step_main, - data[START_READ_PARAMS + iparam * PARAM_CHUNKS + ichunk], - pending, - ); - } - } + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@post-p 0x{dst64:08X} S:{main_step} V:{pre_value} (0x{pre_value:016X})"); - let mut write_data = [0u64; PARAM_CHUNKS]; - if !only_counters { - let a: [u64; 4] = - data[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(); - let b: [u64; 4] = data - [START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] - .try_into() - .unwrap(); - dma(&a, &b, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS], &mut write_data); - } + MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, pending); - // verify write param - let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; - for (ichunk, write_data) in write_data.iter().enumerate().take(PARAM_CHUNKS) { - let param_addr = write_addr + ichunk as u32 * 8; - MemBusHelpers::mem_aligned_write(param_addr, step_main, *write_data, pending); - }*/ + let write_value = if DmaInfo::is_double_read_post(encoded) { + let second_read_value = data_ext[post_data_offset + 1]; + #[cfg(feature = "debug_dma")] + println!( + "DMA: mem_aligned_load@post2 0x{:08X} S:{main_step} V:{second_read_value} (0x{second_read_value:016X})", + src64 + 8 + ); + MemBusHelpers::mem_aligned_load(src64 + 8, main_step, second_read_value, pending); + DmaHelpers::calculate_write_value( + 0, // in post offset it's 0 + (src_offset + pre_count) & 0x07, // src_offset it's modified by pre, aligned/unaligned no change offset + post_count, + pre_value, + &[read_value, second_read_value], + ) + } else { + DmaHelpers::calculate_write_value( + 0, // in post offset it's 0 + (src_offset + pre_count) & 0x07, // src_offset it's modified by pre, aligned/unaligned no change offset + post_count, + pre_value, + &[read_value], + ) + }; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write@post 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); + MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, &mut wr_pending); + } + pending.extend(wr_pending); } -// op_a = step -// op_b = addr_main -// mem_trace: @a, @b, cin, @c, a[0..3], b[0..3], cout, [ c[0..3] ] - pub fn skip_dma_mem_inputs( - dst: u64, - src: u64, - count: usize, + data: &[u64], + _data_ext: &[u64], mem_collectors_info: &[MemCollectorInfo], ) -> bool { - let to_dst = (dst + count as u64 - 1) as u32 & !0x07; - let to_src = (src + count as u64 - 1) as u32 & !0x07; - let mut dst = (dst & !0x07) as u32; - let mut src = (src & !0x07) as u32; - // TODO: - // for mem_collector in mem_collectors_info { - // if !mem_collector.skip_addr_range(from_dst, to_dst) || - // !mem_collector.skip_addr_range(from_src, to_src) { - // return false; - // } - // } - - while dst <= to_dst { - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(dst) { - return false; - } + let dst = data[A]; + let src = data[B]; + let op = data[OP] as u8; + + // A memcmp operation has two parts, any of them could be empty. + // - equal part, means that bytes of src and dst are the same (count_eq) + // - different part, at maximum one byte, to obtain difference (count - count_eq) + let count = DmaInfo::get_count(data[DMA_ENCODED]) as u64; + let use_count = match op { + ZiskOp::DMA_MEMCPY => count, + ZiskOp::DMA_MEMCMP => std::cmp::min( + count, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] + 1, // count_eq + 1 (different byte) + ), + _ => panic!("Invalid operation inside skip_dma_mem_inputs (op:{op})"), + }; + // calculate range for dst and src to verify if any of them are included + // in the memcollector addresses. + + let dst64_from = dst as u32 & !0x07; + let src64_from = src as u32 & !0x07; + let dst64_to = (dst + use_count + 7) as u32 & !0x07; + let src64_to = (src + use_count + 7) as u32 & !0x07; + + for mem_collector in mem_collectors_info { + if !mem_collector.skip_addr_range(dst64_from, dst64_to) { + return false; } - dst += 8; - } - while src <= to_src { - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(src) { - return false; - } + if !mem_collector.skip_addr_range(src64_from, src64_to) { + return false; } - src += 8; } + + // If any mem_collector includes this addresses we could skip this precompiles + // at mem input data generation. true } diff --git a/precompiles/dma/src/dma_helpers.rs b/precompiles/dma/src/dma_helpers.rs deleted file mode 100644 index 597d01ca9..000000000 --- a/precompiles/dma/src/dma_helpers.rs +++ /dev/null @@ -1,44 +0,0 @@ -// use static_assertions::const_assert; -// const_assert!(CHUNK_MEM_STEP_BITS <= 24); - -pub struct DmaHelpers {} - -pub struct DmaValues { - pub dst64: u64, - pub src64: u64, - pub src_offset: u64, - pub dst_offset: u64, - pub pre_count: u64, - pub post_count: u64, - pub memcpy_count: u64, - pub src64_inc_by_pre: u64, - pub src_offset_after_pre: u64, -} -impl DmaHelpers { - #[inline(always)] - pub fn precalculate_dma_values(dst: u64, src: u64, count: usize) -> DmaValues { - let dst64 = dst & !0x07; - let src64 = src & !0x07; - let dst_offset = dst & 0x07; - let src_offset = src & 0x07; - - let use_pre = dst_offset > 0; - let pre_count = if use_pre { std::cmp::min(8 - dst_offset, count as u64) } else { 0 }; - let post_count = (count as u64 - pre_count) % 8; - let memcpy_count = count as u64 - pre_count - post_count; - let src64_inc_by_pre = if use_pre && (src_offset + pre_count) >= 8 { 1 } else { 0 }; - let src_offset_after_pre = (src_offset + pre_count) % 8; - - DmaValues { - dst64, - src64, - src_offset, - dst_offset, - pre_count, - post_count, - memcpy_count, - src64_inc_by_pre, - src_offset_after_pre, - } - } -} diff --git a/precompiles/dma/src/dma_input.rs b/precompiles/dma/src/dma_input.rs deleted file mode 100644 index 42dba0765..000000000 --- a/precompiles/dma/src/dma_input.rs +++ /dev/null @@ -1,51 +0,0 @@ -use zisk_common::{OperationDmaMemCpyData, A, B, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; - -#[derive(Debug)] -pub struct DmaMemCpyInput { - pub src: u64, - pub dst: u64, - pub count: usize, - pub main_step: u64, -} - -#[cfg(feature = "dma_memcmp")] -#[derive(Debug)] -pub struct DmaMemCmpInput { - pub addr1: u64, - pub addr2: u64, - // number of bytes to compare - pub count: usize, - pub main_step: u64, - // number of bytes from beginning that are equal - pub count_eq_bytes: usize, - // results of comparation (p1 - p2) - // p1 == p2 ==> result == 0 - // p1 > p2 ==> result === 1..255 - // p1 < p2 ==> result === 0xFFFF_FFFF_FFFF_FFFF .. 0xFFFF_FFFF_FFFF_FF00 - pub result: u64, -} - -impl DmaMemCpyInput { - pub fn from(values: &OperationDmaMemCpyData) -> Self { - Self { - dst: values[A], - src: values[B], - main_step: values[STEP], - count: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as usize, - } - } -} - -#[cfg(feature = "dma_memcmp")] -impl DmaMemCmpInput { - pub fn from(values: &OperationDmaMemCmpData) -> Self { - Self { - addr1: values[A], - addr2: values[B], - main_step: values[STEP], - count: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as usize, - count_eq_bytes: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] as usize, - result: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2], - } - } -} diff --git a/precompiles/dma/src/dma_instance.rs b/precompiles/dma/src/dma_instance.rs deleted file mode 100644 index 80fdf734d..000000000 --- a/precompiles/dma/src/dma_instance.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! The `DmaInstance` module defines an instance to perform the witness computation -//! for the Dma State Machine. -//! -//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for -//! execution plans. - -use crate::{DmaMemCpyInput, DmaSM}; -use fields::PrimeField64; -use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::collections::VecDeque; -use std::{any::Any, collections::HashMap, sync::Arc}; -use zisk_common::ChunkId; -use zisk_common::{ - BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, -}; -use zisk_core::ZiskOperationType; -use zisk_pil::DmaTrace; - -/// The `DmaInstance` struct represents an instance for the Dma State Machine. -/// -/// It encapsulates the `DmaSM` and its associated context, and it processes input data -/// to compute witnesses for the Dma State Machine. -pub struct DmaInstance { - /// Dma state machine. - dma_sm: Arc>, - - /// Instance context. - ictx: InstanceCtx, -} - -impl DmaInstance { - /// Creates a new `DmaInstance`. - /// - /// # Arguments - /// * `dma_sm` - An `Arc`-wrapped reference to the Dma State Machine. - /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. - /// * `bus_id` - The bus ID associated with this instance. - /// - /// # Returns - /// A new `DmaInstance` instance initialized with the provided state machine and - /// context. - pub fn new(dma_sm: Arc>, ictx: InstanceCtx) -> Self { - Self { dma_sm, ictx } - } - - pub fn build_dma_collector(&self, chunk_id: ChunkId) -> DmaCollector { - assert_eq!( - self.ictx.plan.air_id, - DmaTrace::::AIR_ID, - "DmaInstance: Unsupported air_id: {:?}", - self.ictx.plan.air_id - ); - - let meta = self.ictx.plan.meta.as_ref().unwrap(); - let collect_info = meta.downcast_ref::>().unwrap(); - let (num_ops, collect_skipper) = collect_info[&chunk_id]; - DmaCollector::new(num_ops, collect_skipper) - } -} - -impl Instance for DmaInstance { - /// Computes the witness for the Dma execution plan. - /// - /// This method leverages the `DmaSM` to generate an `AirInstance` using the collected - /// inputs. - /// - /// # Arguments - /// * `_pctx` - The proof context, unused in this implementation. - /// - /// # Returns - /// An `Option` containing the computed `AirInstance`. - fn compute_witness( - &self, - _pctx: &ProofCtx, - _sctx: &SetupCtx, - collectors: Vec<(usize, Box>)>, - trace_buffer: Vec, - ) -> ProofmanResult>> { - let inputs: Vec<_> = collectors - .into_iter() - .map(|(_, collector)| collector.as_any().downcast::().unwrap().inputs) - .collect(); - - Ok(Some(self.dma_sm.compute_witness(&inputs, trace_buffer)?)) - } - - /// Retrieves the checkpoint associated with this instance. - /// - /// # Returns - /// A `CheckPoint` object representing the checkpoint of the execution plan. - fn check_point(&self) -> &CheckPoint { - &self.ictx.plan.check_point - } - - /// Retrieves the type of this instance. - /// - /// # Returns - /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). - fn instance_type(&self) -> InstanceType { - InstanceType::Instance - } - - fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { - assert_eq!( - self.ictx.plan.air_id, - DmaTrace::::AIR_ID, - "DmaInstance: Unsupported air_id: {:?}", - self.ictx.plan.air_id - ); - - let meta = self.ictx.plan.meta.as_ref().unwrap(); - let collect_info = meta.downcast_ref::>().unwrap(); - let (num_ops, collect_skipper) = collect_info[&chunk_id]; - Some(Box::new(DmaCollector::new(num_ops, collect_skipper))) - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -pub struct DmaCollector { - /// Collected inputs for witness computation. - inputs: Vec, - - /// The number of operations to collect. - num_operations: u64, - - /// Helper to skip instructions based on the plan's configuration. - collect_skipper: CollectSkipper, -} - -impl DmaCollector { - /// Creates a new `DmaCollector`. - /// - /// # Arguments - /// - /// * `bus_id` - The connected bus ID. - /// * `num_operations` - The number of operations to collect. - /// * `collect_skipper` - The helper to skip instructions based on the plan's configuration. - /// - /// # Returns - /// A new `ArithInstanceCollector` instance initialized with the provided parameters. - pub fn new(num_operations: u64, collect_skipper: CollectSkipper) -> Self { - Self { - inputs: Vec::with_capacity(num_operations as usize), - num_operations, - collect_skipper, - } - } -} - -impl BusDevice for DmaCollector { - /// Processes data received on the bus, collecting the inputs necessary for witness computation. - /// - /// # Arguments - /// * `_bus_id` - The ID of the bus (unused in this implementation). - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A tuple where: - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - debug_assert!(*bus_id == OPERATION_BUS_ID); - - if self.inputs.len() == self.num_operations as usize { - return false; - } - - if data[OP_TYPE] != ZiskOperationType::Dma as u64 { - return true; - } - - if self.collect_skipper.should_skip() { - return true; - } - - let data: ExtOperationData = - data.try_into().expect("Regular Metrics: Failed to convert data"); - if let ExtOperationData::OperationDmaMemCpyData(data) = data { - self.inputs.push(DmaMemCpyInput::from(&data)); - } else { - panic!("Expected ExtOperationData::OperationDmaData"); - } - - self.inputs.len() < self.num_operations as usize - } - - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - - fn as_any(self: Box) -> Box { - self - } -} diff --git a/precompiles/dma/src/dma_manager.rs b/precompiles/dma/src/dma_manager.rs index ba2deb8ba..19bc0f8af 100644 --- a/precompiles/dma/src/dma_manager.rs +++ b/precompiles/dma/src/dma_manager.rs @@ -2,14 +2,17 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; +use proofman_common::ProofCtx; use zisk_common::{ BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, - InstanceInfo, PayloadType, Planner, + PayloadType, Plan, Planner, }; -use zisk_core::ZiskOperationType; -use zisk_pil::DmaTrace; +use zisk_pil::{Dma64AlignedTrace, DmaPrePostTrace, DmaTrace, DmaUnalignedTrace, ZiskProofValues}; -use crate::{DmaCounterInputGen, DmaInstance, DmaPlanner, DmaSM}; +use crate::{ + Dma64AlignedInstance, Dma64AlignedSM, DmaCounterInputGen, DmaInstance, DmaPlanner, + DmaPrePostInstance, DmaPrePostSM, DmaSM, DmaUnalignedInstance, DmaUnalignedSM, +}; /// The `DmaManager` struct represents the Dma manager, /// which is responsible for managing the Dma state machine and its table state machine. @@ -17,6 +20,9 @@ use crate::{DmaCounterInputGen, DmaInstance, DmaPlanner, DmaSM}; pub struct DmaManager { /// Dma state machine dma_sm: Arc>, + dma_pre_post_sm: Arc>, + dma_64_aligned_sm: Arc>, + dma_unaligned_sm: Arc>, } impl DmaManager { @@ -25,9 +31,12 @@ impl DmaManager { /// # Returns /// An `Arc`-wrapped instance of `DmaManager`. pub fn new(std: Arc>) -> Arc { - let dma_sm = DmaSM::new(std); + let dma_sm = DmaSM::new(std.clone()); + let dma_pre_post_sm = DmaPrePostSM::new(std.clone()); + let dma_64_aligned_sm = Dma64AlignedSM::new(std.clone()); + let dma_unaligned_sm = DmaUnalignedSM::new(std); - Arc::new(Self { dma_sm }) + Arc::new(Self { dma_sm, dma_pre_post_sm, dma_64_aligned_sm, dma_unaligned_sm }) } pub fn build_dma_counter(&self) -> DmaCounterInputGen { @@ -54,14 +63,7 @@ impl ComponentBuilder for DmaManager { /// A boxed implementation of `RegularPlanner`. fn build_planner(&self) -> Box { // Get the number of Dmas that a single Dma instance can handle - let num_availables = self.dma_sm.num_availables; - - Box::new(DmaPlanner::new().add_instance(InstanceInfo::new( - DmaTrace::::AIRGROUP_ID, - DmaTrace::::AIR_ID, - num_availables, - ZiskOperationType::BigInt, - ))) + Box::new(DmaPlanner::::new()) } /// Builds an inputs data collector for Dma operations. @@ -76,10 +78,24 @@ impl ComponentBuilder for DmaManager { /// # Panics /// Panics if the provided `air_id` is not supported. fn build_instance(&self, ictx: InstanceCtx) -> Box> { + println!("BUILD DMA INSTANCE {}", ictx.plan.air_id); match ictx.plan.air_id { - id if id == DmaTrace::::AIR_ID => { + DmaTrace::::AIR_ID => { + println!("BUILD DMA INSTANCE"); Box::new(DmaInstance::new(self.dma_sm.clone(), ictx)) } + DmaPrePostTrace::::AIR_ID => { + println!("BUILD DMA PRE-POST INSTANCE"); + Box::new(DmaPrePostInstance::new(self.dma_pre_post_sm.clone(), ictx)) + } + Dma64AlignedTrace::::AIR_ID => { + println!("BUILD DMA 64-ALIGNED INSTANCE"); + Box::new(Dma64AlignedInstance::new(self.dma_64_aligned_sm.clone(), ictx)) + } + DmaUnalignedTrace::::AIR_ID => { + println!("BUILD DMA UNALIGNED INSTANCE"); + Box::new(DmaUnalignedInstance::new(self.dma_unaligned_sm.clone(), ictx)) + } _ => { panic!("DmaBuilder::get_instance() Unsupported air_id: {:?}", ictx.plan.air_id) } @@ -89,4 +105,14 @@ impl ComponentBuilder for DmaManager { fn build_inputs_generator(&self) -> Option>> { Some(Box::new(DmaCounterInputGen::new(BusDeviceMode::InputGenerator))) } + + fn configure_instances(&self, pctx: &ProofCtx, plannings: &[Plan]) { + let enable_dma_64_aligned = + plannings.iter().any(|p| p.air_id == Dma64AlignedTrace::::AIR_ID); + let enable_dma_unaligned = + plannings.iter().any(|p| p.air_id == DmaUnalignedTrace::::AIR_ID); + let mut proof_values = ZiskProofValues::from_vec_guard(pctx.get_proof_values()); + proof_values.enable_dma_64_aligned = F::from_bool(enable_dma_64_aligned); + proof_values.enable_dma_unaligned = F::from_bool(enable_dma_unaligned); + } } diff --git a/precompiles/dma/src/dma_planner.rs b/precompiles/dma/src/dma_planner.rs index 169cb9bf5..e031bd161 100644 --- a/precompiles/dma/src/dma_planner.rs +++ b/precompiles/dma/src/dma_planner.rs @@ -4,47 +4,158 @@ //! It organizes execution plans for both regular instances and table instances, //! leveraging arithmetic operation counts and metadata to construct detailed plans. -use std::any::Any; - use crate::DmaCounterInputGen; +use std::any::Any; +use std::collections::HashMap; +use fields::PrimeField64; use zisk_common::{ - plan, BusDeviceMetrics, ChunkId, InstCount, InstanceInfo, InstanceType, Metrics, Plan, Planner, + BusDeviceMetrics, CheckPoint, ChunkId, CollectCounter, InstanceType, Plan, Planner, SegmentId, }; +use zisk_pil::{Dma64AlignedTrace, DmaPrePostTrace, DmaTrace, DmaUnalignedTrace}; /// The `DmaPlanner` struct organizes execution plans for arithmetic instances and tables. /// /// It allows adding metadata about instances and tables and generates plans /// based on the provided counters. #[derive(Default)] -pub struct DmaPlanner { - /// Dma instances info to be planned. - instances_info: Vec, +pub struct DmaPlanner { + _marker: std::marker::PhantomData, } -impl DmaPlanner { +#[derive(Default)] +pub struct DmaCheckPoint { + pub chunks: HashMap, + pub last_chunk: Option, + pub is_last_segment: bool, +} + +/// Macro to generate a plan function for a specific field of a struct. +/// +/// This macro creates a function that generates checkpoints from counts across multiple chunks, +/// allowing you to specify which field of the struct to use for counting. +/// +/// # Macro Arguments +/// * `$fn_name` - The name of the generated function. +/// * `$type` - The struct type containing the count field (must have a `chunk_id: ChunkId` field). +/// * `$field` - The field name to use as the count value (must be `u64`). +/// +/// # Generated Function +/// The generated function has the signature: +/// ```ignore +/// pub fn $fn_name( +/// counts: &[$type], +/// size: u64, +/// ) -> Vec<(CheckPoint, , bool>)> +/// ``` +/// +/// # Example +/// ```ignore +/// define_plan_for_field!(plan_by_inst_count, InstFropsCount, inst_count); +/// define_plan_for_field!(plan_by_frops_count, InstFropsCount, frops_count); +/// ``` +macro_rules! define_plan_for_field { + ($fn_name:ident, $type:ty, $field:ident) => { + define_plan_for_field!($fn_name, $type, $field, false, $field); + }; + ($fn_name:ident, $type:ty, $field:ident, $field_inputs: ident) => { + define_plan_for_field!($fn_name, $type, $field, true, $field_inputs); + }; + ($fn_name:ident, $type:ty, $field:ident, $has_input_counter: literal, $field_inputs: ident) => { + pub fn $fn_name( + counts: &Vec<(ChunkId, Box)>, + size: u64, + ) -> Vec<(CheckPoint, DmaCheckPoint)> { + if counts.is_empty() || size == 0 { + return vec![]; + } + // let tag = stringify!($field); + // let total = counts.len(); + // println!("plan_{tag} counts:{total} size:{size}"); + let mut checkpoints = Vec::new(); + let mut current_scope = + DmaCheckPoint { chunks: HashMap::new(), last_chunk: None, is_last_segment: false }; + let mut remaining_size = size; // Remaining size for the current scope. + + for (current_chunk, dyn_counter) in counts.iter() { + let counter = (**dyn_counter).as_any().downcast_ref::<$type>().unwrap(); + // println!("counter: {:?}", counter); + let mut inst_count = counter.$field as u64; + let mut cumulative_offset = 0u64; // Reset cumulative offset for each chunk. + + while inst_count > 0 { + let checkpoint_size = remaining_size.min(inst_count); + // println!("plan_{tag} C:{}/{total} #{current_chunk} I:{remaining_size}/{size} +{checkpoint_size}/{inst_count} skip:{cumulative_offset} count:{checkpoint_size}", index+1); + + current_scope.chunks.insert( + *current_chunk, + ( + // Use input counter to calculate the capacity of collector inputs vector, used + // for state machines that has different number of rows by input. + if $has_input_counter { + counter.$field_inputs as u64 + } else { + checkpoint_size + }, + CollectCounter::new(cumulative_offset as u32, checkpoint_size as u32), + ), + ); + current_scope.last_chunk = Some(*current_chunk); + + cumulative_offset += checkpoint_size; + inst_count -= checkpoint_size; + remaining_size -= checkpoint_size; + + if remaining_size == 0 { + // println!("plan_{tag} adding instance .... inst_count = {inst_count}"); + let keys = current_scope.chunks.keys().cloned().collect::>(); + checkpoints + .push((CheckPoint::Multiple(keys), std::mem::take(&mut current_scope))); + remaining_size = size; + } + } + } + // println!("plan_{tag} final counters interation"); + // Push any remaining checkpoints into the result. + if !current_scope.chunks.is_empty() { + let keys = current_scope.chunks.keys().cloned().collect::>(); + current_scope.is_last_segment = true; + checkpoints.push((CheckPoint::Multiple(keys), std::mem::take(&mut current_scope))); + } else if let Some(last) = checkpoints.last_mut() { + last.1.is_last_segment = true; + } + + checkpoints + } + }; +} + +impl DmaPlanner { /// Creates a new `DmaPlanner`. /// /// # Returns /// A new `DmaPlanner` instance with no preconfigured instances or tables. pub fn new() -> Self { - Self { instances_info: Vec::new() } + Self { _marker: std::marker::PhantomData } } - /// Adds an Dma instance to the planner. - /// - /// # Arguments - /// * `instance_info` - The `InstanceInfo` describing the dma instance to be added. - /// - /// # Returns - /// The updated `DmaPlanner` instance. - pub fn add_instance(mut self, instance_info: InstanceInfo) -> Self { - self.instances_info.push(instance_info); - self - } + define_plan_for_field!(plan_dma_controller, DmaCounterInputGen, dma_ops); + define_plan_for_field!(plan_dma_pre_post, DmaCounterInputGen, dma_pre_post_ops); + define_plan_for_field!( + plan_dma_unaligned, + DmaCounterInputGen, + dma_unaligned_rows, + dma_unaligned_inputs + ); + define_plan_for_field!( + plan_dma_64_aligned, + DmaCounterInputGen, + dma_64_aligned_rows, + dma_64_aligned_inputs + ); } -impl Planner for DmaPlanner { +impl Planner for DmaPlanner { /// Generates execution plans for Dma instances. /// /// # Arguments @@ -57,49 +168,75 @@ impl Planner for DmaPlanner { /// # Panics /// Panics if any counter cannot be downcasted to an `DmaCounter`. fn plan(&self, counters: Vec<(ChunkId, Box)>) -> Vec { - // Prepare counts - let mut count: Vec> = Vec::with_capacity(self.instances_info.len()); - - for _ in 0..self.instances_info.len() { - count.push(Vec::new()); - } - - counters.iter().for_each(|(chunk_id, counter)| { - let reg_counter = - Metrics::as_any(&**counter).downcast_ref::().unwrap(); - - // Iterate over `instances_info` and add `InstCount` objects to the correct vector - for (index, instance_info) in self.instances_info.iter().enumerate() { - if let Some(inst_count) = reg_counter.inst_count(instance_info.op_type) { - let inst_count = InstCount::new(*chunk_id, inst_count); - // Add the `InstCount` to the corresponding inner vector - count[index].push(inst_count); - } - } - }); - - let mut plan_result = Vec::new(); + let mut dma_plans: Vec = + Self::plan_dma_controller(&counters, DmaTrace::::NUM_ROWS as u64) + .into_iter() + .map(|(check_point, collect_info)| { + let converted: Box = Box::new(collect_info); + Plan::new( + DmaTrace::::AIRGROUP_ID, + DmaTrace::::AIR_ID, + None, + InstanceType::Instance, + check_point, + Some(converted), + ) + }) + .collect(); - for (idx, instance) in self.instances_info.iter().enumerate() { - let plan: Vec<_> = plan(&count[idx], instance.num_ops as u64) + let pre_post_plans: Vec = + Self::plan_dma_pre_post(&counters, DmaPrePostTrace::::NUM_ROWS as u64) .into_iter() .map(|(check_point, collect_info)| { let converted: Box = Box::new(collect_info); Plan::new( - instance.airgroup_id, - instance.air_id, + DmaPrePostTrace::::AIRGROUP_ID, + DmaPrePostTrace::::AIR_ID, None, InstanceType::Instance, check_point, Some(converted), - 1, ) }) .collect(); + dma_plans.extend(pre_post_plans); - plan_result.extend(plan); - } + let aligned_plans: Vec = + Self::plan_dma_64_aligned(&counters, Dma64AlignedTrace::::NUM_ROWS as u64) + .into_iter() + .enumerate() + .map(|(segment_id, (check_point, collect_info))| { + let converted: Box = Box::new(collect_info); + Plan::new( + Dma64AlignedTrace::::AIRGROUP_ID, + Dma64AlignedTrace::::AIR_ID, + Some(SegmentId(segment_id)), + InstanceType::Instance, + check_point, + Some(converted), + ) + }) + .collect(); + dma_plans.extend(aligned_plans); + + let unaligned_plans: Vec = + Self::plan_dma_unaligned(&counters, DmaUnalignedTrace::::NUM_ROWS as u64) + .into_iter() + .enumerate() + .map(|(segment_id, (check_point, collect_info))| { + let converted: Box = Box::new(collect_info); + Plan::new( + DmaUnalignedTrace::::AIRGROUP_ID, + DmaUnalignedTrace::::AIR_ID, + Some(SegmentId(segment_id)), + InstanceType::Instance, + check_point, + Some(converted), + ) + }) + .collect(); + dma_plans.extend(unaligned_plans); - plan_result + dma_plans } } diff --git a/precompiles/dma/src/dma_pre_post.rs b/precompiles/dma/src/dma_pre_post.rs deleted file mode 100644 index 80eb1a479..000000000 --- a/precompiles/dma/src/dma_pre_post.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::sync::Arc; - -use fields::PrimeField64; -use rayon::prelude::*; - -use pil_std_lib::Std; -use proofman_common::{AirInstance, FromTrace}; -use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; -use zisk_pil::{DmaPrePostTrace, DmaPrePostTraceRow}; - -use super::DmaInput; - -/// The `DmaPrePostSM` struct encapsulates the logic of the DmaPrePost State Machine. -pub struct DmaPrePostSM { - /// Reference to the PIL2 standard library. - pub std: Arc>, - - /// Number of available dmapreposts in the trace. - pub num_availables: usize, - - /// Range checks ID's - range_id: usize, -} - -impl DmaPrePostSM { - /// Creates a new Dma State Machine instance. - /// - /// # Returns - /// A new `DmaPrePostSM` instance. - pub fn new(std: Arc>) -> Arc { - // Compute some useful values - let num_availables = DmaTrace::::NUM_ROWS; - - let range_id = std.get_range_id(0, (1 << 16) - 1, None); - - Arc::new(Self { std, num_availables, range_id }) - } - - /// Processes a slice of operation data, updating the trace. - /// - /// # Arguments - /// * `trace` - A mutable reference to the DmaPrePost trace. - /// * `input` - The operation data to process. - #[inline(always)] - pub fn process_slice( - &self, - input: &DmaPrePostInput, - trace: &mut DmaPrePostTraceRow, - multiplicities: &mut [u32], - ) { - trace.cin = F::from_bool(input.cin != 0); - let mut cout_2 = input.cin as u32; - - for i in 0..4 { - let al = input.a[i] as u32; - let ah = (input.a[i] >> 32) as u32; - - let bl = input.b[i] as u32; - let bh = (input.b[i] >> 32) as u32; - - trace.a[i][0] = F::from_u32(al); - trace.a[i][1] = F::from_u32(ah); - trace.b[i][0] = F::from_u32(bl); - trace.b[i][1] = F::from_u32(bh); - let cl = al as u64 + bl as u64 + cout_2 as u64; - let cout_1 = cl >> 32; - let ch = ah as u64 + bh as u64 + cout_1; - cout_2 = (ch >> 32) as u32; - - let cll = cl as u16; - let clh = (cl >> 16) as u16; - let chl = ch as u16; - let chh = (ch >> 16) as u16; - - trace.c_chunks[i][0] = F::from_u16(cll); - trace.c_chunks[i][1] = F::from_u16(clh); - trace.c_chunks[i][2] = F::from_u16(chl); - trace.c_chunks[i][3] = F::from_u16(chh); - - trace.cout[i][0] = F::from_u8(cout_1 as u8); - trace.cout[i][1] = F::from_u8(cout_2 as u8); - - multiplicities[cll as usize] += 1; - multiplicities[clh as usize] += 1; - multiplicities[chl as usize] += 1; - multiplicities[chh as usize] += 1; - } - trace.addr_params = F::from_u32(input.addr_main); - trace.addr_a = F::from_u32(input.addr_a); - trace.addr_b = F::from_u32(input.addr_b); - trace.addr_c = F::from_u32(input.addr_c); - trace.step = F::from_u64(input.step_main); - trace.sel = F::ONE; - } - - /// Computes the witness for a series of inputs and produces an `AirInstance`. - /// - /// # Arguments - /// * `sctx` - The setup context containing the setup data. - /// * `inputs` - A slice of operations to process. - /// - /// # Returns - /// An `AirInstance` containing the computed witness data. - pub fn compute_witness( - &self, - inputs: &[Vec], - trace_buffer: Vec, - ) -> AirInstance { - let mut trace = DmaTrace::new_from_vec(trace_buffer); - - let num_rows = trace.num_rows(); - - let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); - assert!(total_inputs <= num_rows); - - tracing::info!( - "··· Creating DmaPrePost instance [{} / {} rows filled {:.2}%]", - total_inputs, - num_rows, - total_inputs as f64 / num_rows as f64 * 100.0 - ); - - timer_start_trace!(DMAPREPOST_TRACE); - - // Split the dmaprepost_trace.buffer into slices matching each inner vector’s length. - let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); - let trace_rows = trace.row_slice_mut(); - - // Determinar tamaño óptimo de chunks - let num_threads = rayon::current_num_threads(); - let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); - - // Procesar en chunks para compartir arrays locales de multiplicities - let local_multiplicities_vec: Vec> = flat_inputs - .par_chunks(chunk_size) - .zip(trace_rows.par_chunks_mut(chunk_size)) - .map(|(input_chunk, trace_chunk)| { - // Array local compartido por este chunk - let mut local_multiplicities = vec![0u32; 1 << 16]; - - // Procesar todos los inputs del chunk - for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { - self.process_slice(input, trace_row, &mut local_multiplicities); - } - - local_multiplicities - }) - .collect(); - - // Sumar todos los arrays locales en uno global - let mut global_multiplicities = vec![0u32; 1 << 16]; - for local_multiplicities in local_multiplicities_vec { - for (i, count) in local_multiplicities.iter().enumerate() { - global_multiplicities[i] += count; - } - } - - // Enviar el resultado final al std - self.std.range_checks(self.range_id, global_multiplicities); - - timer_stop_and_log_trace!(DMAPREPOST_TRACE); - - trace.row_slice_mut()[total_inputs..num_rows] - .par_iter_mut() - .for_each(|slot| *slot = DmaPrePostTraceRow:: { ..Default::default() }); - - AirInstance::::new_from_trace(FromTrace::new(&mut trace)) - } -} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs new file mode 100644 index 000000000..ad42e3133 --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs @@ -0,0 +1,337 @@ +use std::sync::Arc; + +use fields::PrimeField64; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_info}; +use rayon::{ + iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}, + slice::{ParallelSlice, ParallelSliceMut}, +}; +use zisk_pil::{ + DmaPrePostTrace, DmaPrePostTraceRow, DMA_PRE_POST_TABLE_ID, DMA_PRE_POST_TABLE_SIZE, + DUAL_RANGE_BYTE_ID, +}; + +use crate::{DmaPrePostInput, DmaPrePostRom}; +use precompiles_helpers::DmaInfo; + +/// The `DmaPrePostSM` struct encapsulates the logic of the DmaPrePost State Machine. +pub struct DmaPrePostSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + pre_post_table_id: usize, + + /// Dual Byte Range checks + dual_range_byte_id: usize, +} + +impl DmaPrePostSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaPrePostSM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + dual_range_byte_id: std + .get_virtual_table_id(DUAL_RANGE_BYTE_ID) + .expect("Failed to get tabl eDUAL_RANGE_BYTE ID ID"), + pre_post_table_id: std + .get_virtual_table_id(DMA_PRE_POST_TABLE_ID) + .expect("Failed to get table DMA_PRE_POST_TABLE_ID ID ID"), + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_slice( + &self, + input: &DmaPrePostInput, + trace: &mut DmaPrePostTraceRow, + pre_post_table_mul: &mut [u64], + local_dual_range_byte_mul: &mut [u64], + ) { + let dst_offset = input.dst & 0x07; + let src_offset = input.src & 0x07; + let is_pre = dst_offset > 0; + + let dst64 = input.dst >> 3; + let src64 = input.src >> 3; + + trace.set_main_step(input.step); + trace.set_dst64(dst64); + trace.set_src64(src64); + trace.set_dst_offset(dst_offset as u8); + trace.set_src_offset(src_offset as u8); + + let count = if is_pre { + DmaInfo::get_pre_count(input.encoded) + } else { + DmaInfo::get_post_count(input.encoded) + }; + + trace.set_count(count as u8); + trace.set_enabled(true); + let second_read = (src_offset as usize + count) > 8; + //println!("SECOND_READ: {second_read}"); + trace.set_enabled_second_read(second_read); + + let mut value = input.src_values[0]; + let mut bytes = [0u8; 24]; + + bytes[0] = value as u8; + bytes[1] = (value >> 8) as u8; + bytes[2] = (value >> 16) as u8; + bytes[3] = (value >> 24) as u8; + bytes[4] = (value >> 32) as u8; + bytes[5] = (value >> 40) as u8; + bytes[6] = (value >> 48) as u8; + bytes[7] = (value >> 56) as u8; + + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + + if second_read { + value = input.src_values[1]; + bytes[8] = value as u8; + bytes[9] = (value >> 8) as u8; + bytes[10] = (value >> 16) as u8; + bytes[11] = (value >> 24) as u8; + bytes[12] = (value >> 32) as u8; + bytes[13] = (value >> 40) as u8; + bytes[14] = (value >> 48) as u8; + bytes[15] = (value >> 56) as u8; + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + } else { + local_dual_range_byte_mul[0] += 4; + } + + value = input.dst_pre_value; + bytes[16] = value as u8; + bytes[17] = (value >> 8) as u8; + bytes[18] = (value >> 16) as u8; + bytes[19] = (value >> 24) as u8; + bytes[20] = (value >> 32) as u8; + bytes[21] = (value >> 40) as u8; + bytes[22] = (value >> 48) as u8; + bytes[23] = (value >> 56) as u8; + + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + + let selr_value = if dst_offset > src_offset { + trace.set_dst_offset_gt_src_offset(true); + dst_offset - src_offset + } else { + trace.set_dst_offset_gt_src_offset(false); + src_offset - dst_offset + }; + + let read_value_23 = + if selr_value > 0 { input.src_values[0] << (selr_value * 8) } else { 0 }; + let read_value_01 = (input.src_values[0] >> (selr_value * 8)) + | if selr_value > 0 { input.src_values[1] << (64 - selr_value * 8) } else { 0 }; + + let _mask = 0xFFFF_FFFF_FFFF_FFFFu64 << (dst_offset * 8); + let mask = _mask ^ (_mask << (count * 8)); + + let write_value_01 = (read_value_01 & mask) | (input.dst_pre_value & !mask); + let write_value_23 = (read_value_23 & mask) | (input.dst_pre_value & !mask); + + trace.set_write_value(0, write_value_01 as u32); + trace.set_write_value(1, (write_value_01 >> 32) as u32); + trace.set_write_value(2, write_value_23 as u32); + trace.set_write_value(3, (write_value_23 >> 32) as u32); + + trace.set_selb(0, (mask & 0x0000_0000_0000_00FF) != 0); + trace.set_selb(1, (mask & 0x0000_0000_0000_FF00) != 0); + trace.set_selb(2, (mask & 0x0000_0000_00FF_0000) != 0); + trace.set_selb(3, (mask & 0x0000_0000_FF00_0000) != 0); + trace.set_selb(4, (mask & 0x0000_00FF_0000_0000) != 0); + trace.set_selb(5, (mask & 0x0000_FF00_0000_0000) != 0); + trace.set_selb(6, (mask & 0x00FF_0000_0000_0000) != 0); + trace.set_selb(7, (mask & 0xFF00_0000_0000_0000) != 0); + + for (index, byte) in bytes.iter().enumerate() { + // println!("PRE-POST bytes[{index}]: 0x{byte:02X}"); + trace.set_bytes(index, *byte); + } + + trace.set_selread(0, selr_value == 0); + trace.set_selread(1, selr_value == 1); + trace.set_selread(2, selr_value == 2); + trace.set_selread(3, selr_value == 3); + trace.set_selread(4, selr_value == 4); + trace.set_selread(5, selr_value == 5); + trace.set_selread(6, selr_value == 6); + + // println!("PRE-POST write_value: 0x{write_value_01:016X} 0x{write_value_23:016X}"); + + let table_row = DmaPrePostRom::get_row(dst_offset as usize, src_offset as usize, count); + // println!("PRE-POST-ROM [{table_row}] dst_offset: {dst_offset} src_offset: {src_offset} count: {count}"); + pre_post_table_mul[table_row] += 1; + + // println!("DMA_PRE_POST: bytes={bytes:?} selr_value={selr_value} mask=0x{mask:016X}"); + // println!( + // "DMA_PRE_POST: read_value_01=0x{read_value_01:016X} read_value_23=0x{read_value_23:016X}" + // ); + // println!("DMA_PRE_POST: write_value_xx=[0x{write_value_01:016X},0x{write_value_23:016X}] dst_offset={dst_offset} src_offset={src_offset}"); + // println!( + // "DMA_PRE_POST: selb={:?}", + // [ + // ((mask & 0x0000_0000_0000_00FF) != 0) as u8, + // ((mask & 0x0000_0000_0000_FF00) != 0) as u8, + // ((mask & 0x0000_0000_00FF_0000) != 0) as u8, + // ((mask & 0x0000_0000_FF00_0000) != 0) as u8, + // ((mask & 0x0000_00FF_0000_0000) != 0) as u8, + // ((mask & 0x0000_FF00_0000_0000) != 0) as u8, + // ((mask & 0x00FF_0000_0000_0000) != 0) as u8, + // ((mask & 0xFF00_0000_0000_0000) != 0) as u8 + // ] + // ); + // println!( + // "DMA_PRE_POST: selread={:?}", + // [ + // (selr_value == 0) as u8, + // (selr_value == 1) as u8, + // (selr_value == 2) as u8, + // (selr_value == 3) as u8, + // (selr_value == 4) as u8, + // (selr_value == 5) as u8, + // (selr_value == 6) as u8, + // (selr_value == 7) as u8 + // ] + // ); + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut DmaPrePostTraceRow) { + trace.set_main_step(0); + trace.set_dst64(0); + trace.set_src64(0); + trace.set_dst_offset(0); + trace.set_src_offset(0); + for index in 0..7 { + trace.set_selread(index, false); + } + + trace.set_dst_offset_gt_src_offset(false); + trace.set_count(0); + trace.set_enabled(false); + trace.set_enabled_second_read(false); + + for index in 0..24 { + trace.set_bytes(index, 0); + } + for index in 0..8 { + trace.set_selb(index, false); + } + trace.set_write_value(0, 0); + trace.set_write_value(1, 0); + trace.set_write_value(2, 0); + trace.set_write_value(3, 0); + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = DmaPrePostTrace::::new_from_vec(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|inputs| inputs.len()).sum(); + + assert!(total_inputs <= num_rows); + assert!(total_inputs > 0); + + tracing::info!( + "··· Creating DmaPrePost instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_PRE_POST_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + // Calculate optimal chunk size + let num_threads = rayon::current_num_threads(); + let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); + + // Process in chunks to allow per-chunk local multiplicities arrays + let (global_pre_post_table_mul, global_dual_range_byte_mul): ( + Vec>, + Vec>, + ) = flat_inputs + .par_chunks(chunk_size) + .zip(trace_rows.par_chunks_mut(chunk_size)) + .map(|(input_chunk, trace_chunk)| { + // Local array shared by this chunk + let mut local_pre_post_table_mul = vec![0u64; DMA_PRE_POST_TABLE_SIZE]; + let mut local_dual_range_byte_mul = vec![0u64; 1 << 16]; + + // Sum all local arrays into a global one + for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { + self.process_slice( + input, + trace_row, + &mut local_pre_post_table_mul, + &mut local_dual_range_byte_mul, + ); + } + + (local_pre_post_table_mul, local_dual_range_byte_mul) + }) + .collect(); + + for pre_post_table_mul in global_pre_post_table_mul.iter() { + // println!("PRE_POST_TABLE_MUL {:?}", pre_post_table_mul); + self.std.inc_virtual_rows_ranged(self.pre_post_table_id, pre_post_table_mul); + } + + for dual_range_byte_mul in global_dual_range_byte_mul.iter() { + self.std.inc_virtual_rows_ranged(self.dual_range_byte_id, dual_range_byte_mul); + } + + if total_inputs < num_rows { + self.process_empty_slice(&mut trace_rows[total_inputs]); + let empty_row = trace_rows[total_inputs]; + trace_rows[total_inputs + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + } + let from_trace = FromTrace::new(&mut trace); + timer_stop_and_log_info!(DMA_PRE_POST_TRACE); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs new file mode 100644 index 000000000..69e59103a --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs @@ -0,0 +1,99 @@ +//! The `DmaPrePostCollector` module defines an collector to calculate all inputs of an instance +//! for the DmaPrePost State Machine. + +use std::{any::Any, collections::VecDeque}; + +use zisk_common::{BusDevice, BusId, CollectCounter, MemCollectorInfo, OPERATION_BUS_ID, OP_TYPE}; +use zisk_core::ZiskOperationType; + +use crate::DmaPrePostInput; + +pub struct DmaPrePostCollector { + /// Collected inputs for witness computation. + pub inputs: Vec, + + /// The number of operations to collect. + pub num_operations: u64, + + /// Helper to skip instructions based on the plan's configuration. + pub collect_counter: CollectCounter, +} + +impl DmaPrePostCollector { + /// Creates a new `DmaPrePostCollector`. + /// + /// # Arguments + /// + /// * `bus_id` - The connected bus ID. + /// * `num_operations` - The number of operations to collect. + /// * `collect_skipper` - The helper to skip instructions based on the plan's configuration. + /// + /// # Returns + /// A new `ArithInstanceCollector` instance initialized with the provided parameters. + pub fn new(num_operations: u64, collect_counter: CollectCounter) -> Self { + Self { + inputs: Vec::with_capacity(num_operations as usize), + num_operations, + collect_counter, + } + } +} + +impl BusDevice for DmaPrePostCollector { + /// Processes data received on the bus, collecting the inputs necessary for witness computation. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A tuple where: + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, + _mem_collector_info: Option<&[MemCollectorInfo]>, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if self.inputs.len() == self.num_operations as usize { + return false; + } + + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + return true; + } + + // println!( + // "DmaPrePostCollector::process_data {} {:?}", + // DmaInfo::to_string(data[DMA_ENCODED]), + // self.collect_counter + // ); + let rows = DmaPrePostInput::get_count(data); + let res = self.collect_counter.should_process(rows as u32); + // println!("DmaPrePostCollector::process_data2 {} {:?}", rows, res); + if let Some((skip, max_count)) = res { + self.inputs.extend(DmaPrePostInput::from(data, data_ext, skip, max_count)); + } + // println!("DmaPrePostCollector::process_data3 input.len()={}", self.inputs.len()); + self.inputs.len() < self.num_operations as usize + } + + /// Returns the bus IDs associated with this instance. + /// + /// # Returns + /// A vector containing the connected bus ID. + fn bus_id(&self) -> Vec { + vec![OPERATION_BUS_ID] + } + + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs new file mode 100644 index 000000000..51f7fef69 --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs @@ -0,0 +1,79 @@ +use precompiles_helpers::DmaInfo; +use zisk_common::{A, B, DMA_ENCODED, STEP}; + +#[derive(Debug)] +pub struct DmaPrePostInput { + pub src: u32, + pub dst: u32, + pub step: u64, + pub encoded: u64, + pub src_values: [u64; 2], + pub dst_pre_value: u64, +} +impl std::fmt::Display for DmaPrePostInput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "DmaPrePostInput {{ src: 0x{:08x}, dst: 0x{:08x}, step: {}, encoded: 0x{:016x} ({}), src_values: [0x{:016x}, 0x{:016x}], dst_pre_value: 0x{:016x} }}", + self.src, self.dst, self.step, self.encoded, DmaInfo::to_string(self.encoded), self.src_values[0], self.src_values[1], self.dst_pre_value + ) + } +} +impl DmaPrePostInput { + pub fn get_count(data: &[u64]) -> usize { + let encoded = data[DMA_ENCODED]; + (DmaInfo::get_pre_count(encoded) > 0) as usize + + (DmaInfo::get_post_count(encoded) > 0) as usize + } + pub fn from(data: &[u64], data_ext: &[u64], skip: u32, max_count: u32) -> Vec { + let encoded = data[DMA_ENCODED]; + let mut inputs = Vec::new(); + let pre_count = DmaInfo::get_pre_count(encoded); + let mut skipped = 0; + if pre_count > 0 { + if skipped < skip { + skipped += 1; + } else { + let src_offset = DmaInfo::get_pre_data_offset(encoded); + let input = Self { + dst: data[A] as u32, + src: data[B] as u32, + step: data[STEP], + encoded, + src_values: [ + data_ext[src_offset], + if DmaInfo::is_double_read_pre(encoded) { + data_ext[src_offset + 1] + } else { + 0 + }, + ], + dst_pre_value: data_ext[DmaInfo::get_pre_write_offset(encoded)], + }; + inputs.push(input); + } + } + let post_count = DmaInfo::get_post_count(encoded); + if post_count > 0 && skipped >= skip && max_count as usize > inputs.len() { + let src_offset = DmaInfo::get_post_data_offset(encoded); + let loop_count = DmaInfo::get_loop_count(encoded); + let input = Self { + dst: data[A] as u32 + pre_count as u32 + loop_count as u32 * 8, + src: data[B] as u32 + pre_count as u32 + loop_count as u32 * 8, + step: data[STEP], + encoded, + src_values: [ + data_ext[src_offset], + if DmaInfo::is_double_read_post(encoded) { + data_ext[src_offset + 1] + } else { + 0 + }, + ], + dst_pre_value: data_ext[DmaInfo::get_post_write_offset(encoded)], + }; + inputs.push(input); + } + inputs + } +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs new file mode 100644 index 000000000..2d5dc5090 --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs @@ -0,0 +1,118 @@ +//! The `DmaPrePostInstance` module defines an instance to perform the witness computation +//! for the DmaPrePost State Machine. +//! +//! It manages collected inputs and interacts with the `DmaPrePostSM` to compute witnesses for +//! execution plans. + +use crate::{DmaCheckPoint, DmaPrePostCollector, DmaPrePostSM}; +use fields::PrimeField64; +use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; +use std::sync::Arc; +use zisk_common::ChunkId; +use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; +use zisk_pil::DmaPrePostTrace; + +/// The `DmaPrePostInstance` struct represents an instance for the DmaPrePost State Machine. +/// +/// It encapsulates the `DmaPrePostSM` and its associated context, and it processes input data +/// to compute witnesses for the DmaPrePost State Machine. +pub struct DmaPrePostInstance { + /// DmaPrePost State machine. + dma_sm: Arc>, + + /// Instance context. + ictx: InstanceCtx, +} + +impl DmaPrePostInstance { + /// Creates a new `DmaPrePostInstance`. + /// + /// # Arguments + /// * `dma_sm` - An `Arc`-wrapped reference to the DmaPrePost State Machine. + /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. + /// * `bus_id` - The bus ID associated with this instance. + /// + /// # Returns + /// A new `DmaPrePostInstance` instance initialized with the provided state machine and + /// context. + pub fn new(dma_sm: Arc>, ictx: InstanceCtx) -> Self { + Self { dma_sm, ictx } + } + + pub fn build_dma_collector(&self, chunk_id: ChunkId) -> DmaPrePostCollector { + assert_eq!( + self.ictx.plan.air_id, + DmaPrePostTrace::::AIR_ID, + "DmaPrePostInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info: &DmaCheckPoint = meta.downcast_ref::().unwrap(); + let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; + DmaPrePostCollector::new(num_ops, collect_counter) + } +} + +impl Instance for DmaPrePostInstance { + /// Computes the witness for the Dma execution plan. + /// + /// This method leverages the `DmaPrePostSM` to generate an `AirInstance` using the collected + /// inputs. + /// + /// # Arguments + /// * `_pctx` - The proof context, unused in this implementation. + /// + /// # Returns + /// An `Option` containing the computed `AirInstance`. + fn compute_witness( + &self, + _pctx: &ProofCtx, + _sctx: &SetupCtx, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + ) -> ProofmanResult>> { + let inputs: Vec<_> = collectors + .into_iter() + .map(|(_, collector)| { + collector.as_any().downcast::().unwrap().inputs + }) + .collect(); + + Ok(Some(self.dma_sm.compute_witness(&inputs, trace_buffer)?)) + } + + /// Retrieves the checkpoint associated with this instance. + /// + /// # Returns + /// A `CheckPoint` object representing the checkpoint of the execution plan. + fn check_point(&self) -> &CheckPoint { + &self.ictx.plan.check_point + } + + /// Retrieves the type of this instance. + /// + /// # Returns + /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). + fn instance_type(&self) -> InstanceType { + InstanceType::Instance + } + + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { + assert_eq!( + self.ictx.plan.air_id, + DmaPrePostTrace::::AIR_ID, + "DmaPrePostInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; + Some(Box::new(DmaPrePostCollector::new(num_ops, collect_counter))) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs new file mode 100644 index 000000000..0ca2bf430 --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs @@ -0,0 +1,23 @@ +use precompiles_helpers::DmaInfo; + +pub enum DmaPrePostRom {} + +impl DmaPrePostRom { + // Table generated from pil + const TABLE_OFFSETS: [usize; 64] = [ + 0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98, 105, 112, 118, 124, 130, 136, + 142, 148, 154, 160, 165, 170, 175, 180, 185, 190, 195, 200, 204, 208, 212, 216, 220, 224, + 228, 232, 235, 238, 241, 244, 247, 250, 253, 256, 258, 260, 262, 264, 266, 268, 270, 272, + 273, 274, 275, 276, 277, 278, 279, + ]; + #[allow(dead_code)] + pub fn get_row_from_encoded(encoded: u64) -> usize { + let src_offset = DmaInfo::get_src_offset(encoded); + let dst_offset = DmaInfo::get_dst_offset(encoded); + let count = DmaInfo::get_count(encoded); + Self::get_row(dst_offset, src_offset, count) + } + pub fn get_row(dst_offset: usize, src_offset: usize, count: usize) -> usize { + Self::TABLE_OFFSETS[dst_offset * 8 + src_offset] + count - 1 + } +} diff --git a/precompiles/dma/src/dma_pre_post/mod.rs b/precompiles/dma/src/dma_pre_post/mod.rs new file mode 100644 index 000000000..9e8132ecc --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/mod.rs @@ -0,0 +1,12 @@ +#[allow(clippy::module_inception)] +mod dma_pre_post; +mod dma_pre_post_collector; +mod dma_pre_post_input; +mod dma_pre_post_instance; +mod dma_pre_post_rom; + +pub use dma_pre_post::*; +pub use dma_pre_post_collector::*; +pub use dma_pre_post_input::*; +pub use dma_pre_post_instance::*; +pub use dma_pre_post_rom::*; diff --git a/precompiles/dma/src/dma_pre_post_input.rs b/precompiles/dma/src/dma_pre_post_input.rs deleted file mode 100644 index df35476de..000000000 --- a/precompiles/dma/src/dma_pre_post_input.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::dma_constants::*; -use zisk_common::OperationDmaData; -use zisk_common::{B, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; - -#[derive(Debug)] -pub struct DmaPrePostInput { - pub src: u32, - pub dst: u32, - pub step: u64, - pub count: u8, - pub src_values: [u64; 2], - pub dst_pre_value: u64, -} - -impl DmaPrePostInput { - pub fn from(data: &OperationDmaData, data_ext: &[u64]) -> Self { - Self { - dst: data[A] as u32, - src: data[B] as u32, - step: data[STEP], - src_values: [data_ext[2], data_ext[3]], - dst_pre_value: data_ext[0], - } - } -} diff --git a/precompiles/dma/src/dma_unaligned.rs b/precompiles/dma/src/dma_unaligned.rs deleted file mode 100644 index 16449e999..000000000 --- a/precompiles/dma/src/dma_unaligned.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::sync::Arc; - -use fields::PrimeField64; -use rayon::prelude::*; - -use pil_std_lib::Std; -use proofman_common::{AirInstance, FromTrace}; -use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; -use zisk_pil::{DmaTrace, DmaTraceRow}; - -use super::DmaInput; - -/// The `DmaSM` struct encapsulates the logic of the DmaUnaligned State Machine. -pub struct DmaSM { - /// Reference to the PIL2 standard library. - pub std: Arc>, - - /// Number of available dmaunaligneds in the trace. - pub num_availables: usize, - - /// Range checks ID's - range_id: usize, -} - -impl DmaSM { - /// Creates a new Dma State Machine instance. - /// - /// # Returns - /// A new `DmaSM` instance. - pub fn new(std: Arc>) -> Arc { - // Compute some useful values - let num_availables = DmaTrace::::NUM_ROWS; - - let range_id = std.get_range_id(0, (1 << 16) - 1, None); - - Arc::new(Self { std, num_availables, range_id }) - } - - /// Processes a slice of operation data, updating the trace. - /// - /// # Arguments - /// * `trace` - A mutable reference to the DmaUnaligned trace. - /// * `input` - The operation data to process. - #[inline(always)] - pub fn process_slice( - &self, - input: &DmaInput, - trace: &mut DmaTraceRow, - multiplicities: &mut [u32], - ) { - trace.cin = F::from_bool(input.cin != 0); - let mut cout_2 = input.cin as u32; - - for i in 0..4 { - let al = input.a[i] as u32; - let ah = (input.a[i] >> 32) as u32; - - let bl = input.b[i] as u32; - let bh = (input.b[i] >> 32) as u32; - - trace.a[i][0] = F::from_u32(al); - trace.a[i][1] = F::from_u32(ah); - trace.b[i][0] = F::from_u32(bl); - trace.b[i][1] = F::from_u32(bh); - let cl = al as u64 + bl as u64 + cout_2 as u64; - let cout_1 = cl >> 32; - let ch = ah as u64 + bh as u64 + cout_1; - cout_2 = (ch >> 32) as u32; - - let cll = cl as u16; - let clh = (cl >> 16) as u16; - let chl = ch as u16; - let chh = (ch >> 16) as u16; - - trace.c_chunks[i][0] = F::from_u16(cll); - trace.c_chunks[i][1] = F::from_u16(clh); - trace.c_chunks[i][2] = F::from_u16(chl); - trace.c_chunks[i][3] = F::from_u16(chh); - - trace.cout[i][0] = F::from_u8(cout_1 as u8); - trace.cout[i][1] = F::from_u8(cout_2 as u8); - - multiplicities[cll as usize] += 1; - multiplicities[clh as usize] += 1; - multiplicities[chl as usize] += 1; - multiplicities[chh as usize] += 1; - } - trace.addr_params = F::from_u32(input.addr_main); - trace.addr_a = F::from_u32(input.addr_a); - trace.addr_b = F::from_u32(input.addr_b); - trace.addr_c = F::from_u32(input.addr_c); - trace.step = F::from_u64(input.step_main); - trace.sel = F::ONE; - } - - /// Computes the witness for a series of inputs and produces an `AirInstance`. - /// - /// # Arguments - /// * `sctx` - The setup context containing the setup data. - /// * `inputs` - A slice of operations to process. - /// - /// # Returns - /// An `AirInstance` containing the computed witness data. - pub fn compute_witness( - &self, - inputs: &[Vec], - trace_buffer: Vec, - ) -> AirInstance { - let mut trace = DmaTrace::new_from_vec(trace_buffer); - - let num_rows = trace.num_rows(); - - let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); - assert!(total_inputs <= num_rows); - - tracing::info!( - "··· Creating DmaUnaligned instance [{} / {} rows filled {:.2}%]", - total_inputs, - num_rows, - total_inputs as f64 / num_rows as f64 * 100.0 - ); - - timer_start_trace!(DMAUNALIGNED_TRACE); - - // Split the dmaunaligned_trace.buffer into slices matching each inner vector’s length. - let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); - let trace_rows = trace.row_slice_mut(); - - // Determinar tamaño óptimo de chunks - let num_threads = rayon::current_num_threads(); - let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); - - // Procesar en chunks para compartir arrays locales de multiplicities - let local_multiplicities_vec: Vec> = flat_inputs - .par_chunks(chunk_size) - .zip(trace_rows.par_chunks_mut(chunk_size)) - .map(|(input_chunk, trace_chunk)| { - // Array local compartido por este chunk - let mut local_multiplicities = vec![0u32; 1 << 16]; - - // Procesar todos los inputs del chunk - for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { - self.process_slice(input, trace_row, &mut local_multiplicities); - } - - local_multiplicities - }) - .collect(); - - // Sumar todos los arrays locales en uno global - let mut global_multiplicities = vec![0u32; 1 << 16]; - for local_multiplicities in local_multiplicities_vec { - for (i, count) in local_multiplicities.iter().enumerate() { - global_multiplicities[i] += count; - } - } - - // Enviar el resultado final al std - self.std.range_checks(self.range_id, global_multiplicities); - - timer_stop_and_log_trace!(DMAUNALIGNED_TRACE); - - trace.row_slice_mut()[total_inputs..num_rows] - .par_iter_mut() - .for_each(|slot| *slot = DmaUnalignedTraceRow:: { ..Default::default() }); - - AirInstance::::new_from_trace(FromTrace::new(&mut trace)) - } -} diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs new file mode 100644 index 000000000..ac6e87571 --- /dev/null +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs @@ -0,0 +1,355 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator as _}; + +use crate::DmaUnalignedInput; +use pil_std_lib::Std; +use precompiles_helpers::DmaInfo; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_info}; +use zisk_common::SegmentId; +use zisk_pil::{ + DmaUnalignedAirValues, DmaUnalignedTrace, DmaUnalignedTraceRow, DUAL_RANGE_BYTE_ID, +}; + +pub struct DmaUnalignedPrevSegment { + pub seq_end: bool, + pub dst64: u32, + pub src64: u32, + pub src_offset: u8, + pub main_step: u64, + pub count: u32, + pub is_mem_eq: bool, +} + +/// The `DmaUnalignedSM` struct encapsulates the logic of the DmaUnaligned State Machine. +pub struct DmaUnalignedSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + range_16_bits_id: usize, + dual_range_byte_id: usize, +} + +impl DmaUnalignedSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaUnalignedSM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + dual_range_byte_id: std + .get_virtual_table_id(DUAL_RANGE_BYTE_ID) + .expect("Failed to get tabl eDUAL_RANGE_BYTE ID ID"), + range_16_bits_id: std + .get_range_id(0, 0xFFFF, None) + .expect("Failed to get 16b table ID"), + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_input( + &self, + input: &DmaUnalignedInput, + trace: &mut [DmaUnalignedTraceRow], + local_dual_byte_table: &mut [u64], + air_values: &mut DmaUnalignedAirValues, + ) -> usize { + let rows = input.count as usize; + let initial_count = DmaInfo::get_loop_count(input.encoded) - input.skip as usize; + let mut count = initial_count; + let src_offset = DmaInfo::get_loop_src_offset(input.encoded); + let mut dst64 = (input.dst >> 3) + input.skip; + let mut src64 = (input.src >> 3) + input.skip; + + let mut src_values_index = 0; + let mut seq_end = false; + let mut next_value = 0; + // println!( + // "DMA_UNALIGNED INPUT {input:?} count:{count} rows:{rows} dma_info:{}", + // DmaInfo::to_string(input.encoded) + // ); + assert!(rows > 0); + for (irow, row) in trace.iter_mut().enumerate().take(rows) { + row.set_main_step(input.step); + row.set_is_mem_eq(false); + row.set_no_last_no_seq_end(count != 0); + row.set_previous_seq_end(input.skip == 0 && irow == 0); + + row.set_dst64(dst64); + row.set_src64(src64); + dst64 += 1; + src64 += 1; + + row.set_offset_2(src_offset == 2); + row.set_offset_3(src_offset == 3); + row.set_offset_4(src_offset == 4); + row.set_offset_5(src_offset == 5); + row.set_offset_6(src_offset == 6); + row.set_offset_7(src_offset == 7); + + row.set_count(count as u32); + // println!("DMA_UNALIGNED: trace[{irow}] count:{count}"); + row.set_seq_end(count == 0); + + let value = input.src_values[src_values_index]; + src_values_index += 1; + let write_value = if count == 0 { + seq_end = true; + next_value = 0; + match src_offset { + 1 => value >> 8, + 2 => value >> 16, + 3 => value >> 24, + 4 => value >> 32, + 5 => value >> 40, + 6 => value >> 48, + 7 => value >> 56, + _ => panic!("invalid src_offset {src_offset} on DmaUnaligned"), + } + } else { + count -= 1; + if src_values_index >= input.src_values.len() { + println!( + "DMA_UNALIGNED INPUT src_values_index out of bounds {} / {} count:{count} irow:{irow} INPUT:{:?}", + src_values_index, + input.src_values.len(), + input + ); + } + next_value = input.src_values[src_values_index]; + match src_offset { + 1 => (value >> 8) | (next_value << 56), + 2 => (value >> 16) | (next_value << 48), + 3 => (value >> 24) | (next_value << 40), + 4 => (value >> 32) | (next_value << 32), + 5 => (value >> 40) | (next_value << 24), + 6 => (value >> 48) | (next_value << 16), + 7 => (value >> 56) | (next_value << 8), + _ => panic!("invalid src_offset {src_offset} on DmaUnaligned"), + } + }; + + row.set_read_bytes(0, value as u8); + row.set_read_bytes(1, (value >> 8) as u8); + row.set_read_bytes(2, (value >> 16) as u8); + row.set_read_bytes(3, (value >> 24) as u8); + row.set_read_bytes(4, (value >> 32) as u8); + row.set_read_bytes(5, (value >> 40) as u8); + row.set_read_bytes(6, (value >> 48) as u8); + row.set_read_bytes(7, (value >> 56) as u8); + + row.set_write_value(0, write_value as u32); + row.set_write_value(1, (write_value >> 32) as u32); + + let value = value as usize; + local_dual_byte_table[value & 0xFFFF] += 1; + local_dual_byte_table[(value >> 16) & 0xFFFF] += 1; + local_dual_byte_table[(value >> 32) & 0xFFFF] += 1; + local_dual_byte_table[(value >> 48) & 0xFFFF] += 1; + } + + if input.is_last_instance_input { + println!("DmaUnaligned LAST_INSTANCE_INPUT {seq_end}"); + if seq_end { + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count = F::ZERO; + air_values.segment_last_count = F::ZERO; + air_values.segment_last_offset = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_is_mem_eq = F::ZERO; + air_values.segment_next_bytes = [F::ZERO; 8]; + } else { + let last_row = rows - 1; + air_values.segment_last_seq_end = F::ZERO; + air_values.segment_last_src64 = trace[last_row].src64; + air_values.segment_last_dst64 = trace[last_row].dst64; + air_values.segment_last_main_step = trace[last_row].main_step; + air_values.segment_last_count = trace[last_row].count; + air_values.segment_last_offset = F::from_u8(src_offset); + let count = trace[last_row].count.as_canonical_u64(); + air_values.last_count_chunk[0] = F::from_u16(count as u16); + air_values.last_count_chunk[1] = F::from_u16((count >> 16) as u16); + air_values.segment_last_is_mem_eq = trace[last_row].is_mem_eq; + for (index, byte) in air_values.segment_next_bytes.iter_mut().enumerate() { + *byte = F::from_u8((next_value >> (index * 8)) as u8); + } + } + } + rows + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut DmaUnalignedTraceRow) { + trace.set_main_step(0); + trace.set_is_mem_eq(false); + trace.set_no_last_no_seq_end(false); + trace.set_previous_seq_end(true); + + trace.set_dst64(0); + trace.set_src64(0); + + trace.set_offset_2(false); + trace.set_offset_3(false); + trace.set_offset_4(false); + trace.set_offset_5(false); + trace.set_offset_6(false); + trace.set_offset_7(false); + + trace.set_count(0); + trace.set_seq_end(true); + + trace.set_read_bytes(0, 0); + trace.set_read_bytes(1, 0); + trace.set_read_bytes(2, 0); + trace.set_read_bytes(3, 0); + trace.set_read_bytes(4, 0); + trace.set_read_bytes(5, 0); + trace.set_read_bytes(6, 0); + trace.set_read_bytes(7, 0); + + trace.set_write_value(0, 0); + trace.set_write_value(1, 0); + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + segment_id: SegmentId, + is_last_segment: bool, + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = DmaUnalignedTrace::::new_from_vec(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs + .iter() + .map(|inputs| inputs.iter().map(|input| input.count as usize).sum::()) + .sum(); + + assert!(total_inputs <= num_rows); + assert!(total_inputs > 0); + + tracing::info!( + "··· Creating DmaUnaligned instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_UNALIGNED_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + // TODO: add std method to used short table, no sense with instances around 2^22 use 64 bits, need more space. + let mut local_dual_byte_table = vec![0u64; 1 << 16]; + let mut air_values = DmaUnalignedAirValues::::new(); + let mut row_offset = 0; + for input in flat_inputs.iter() { + let rows_used = self.process_input( + input, + &mut trace_rows[row_offset..], + &mut local_dual_byte_table, + &mut air_values, + ); + row_offset += rows_used; + } + + let padding_rows = num_rows - row_offset; + let last_count = if padding_rows == 0 && trace_rows[num_rows - 1].seq_end.is_zero() { + trace_rows[num_rows - 1].count.as_canonical_u64() + } else { + 0 + }; + self.std.range_check(self.range_16_bits_id, (last_count & 0xFFFF) as i64, 1); + self.std.range_check(self.range_16_bits_id, ((last_count >> 16) & 0xFFFF) as i64, 1); + + local_dual_byte_table[0] += (padding_rows * 4) as u64; + self.std.inc_virtual_rows_ranged(self.dual_range_byte_id, &local_dual_byte_table); + + air_values.segment_id = F::from_usize(segment_id.into()); + air_values.is_last_segment = F::from_bool(is_last_segment); + + let first_input = flat_inputs.first().unwrap(); + if first_input.skip == 0 { + air_values.segment_previous_seq_end = F::ONE; + air_values.segment_previous_dst64 = F::ZERO; + air_values.segment_previous_src64 = F::ZERO; + air_values.segment_previous_main_step = F::ZERO; + air_values.segment_previous_count = F::ZERO; + air_values.segment_previous_is_mem_eq = F::ZERO; + air_values.segment_previous_offset = F::ZERO; + air_values.segment_first_bytes = [F::ZERO; 8]; + } else { + air_values.segment_previous_seq_end = F::ZERO; + air_values.segment_previous_dst64 = + F::from_u32((trace_rows[0].dst64.as_canonical_u64() - 1) as u32); + air_values.segment_previous_src64 = + F::from_u32((trace_rows[0].src64.as_canonical_u64() - 1) as u32); + air_values.segment_previous_main_step = trace_rows[0].main_step; + air_values.segment_previous_count = + F::from_u32((trace_rows[0].count.as_canonical_u64() + 1) as u32); + air_values.segment_previous_is_mem_eq = trace_rows[0].is_mem_eq; + air_values.segment_previous_offset = + F::from_u8(DmaInfo::get_loop_src_offset(first_input.encoded)); + air_values.segment_first_bytes = trace_rows[0].read_bytes; + } + + // padding + if padding_rows > 0 { + air_values.padding_rows = F::from_u32(padding_rows as u32); + self.process_empty_slice(&mut trace_rows[row_offset]); + let empty_row = trace_rows[row_offset]; + trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count = F::ZERO; + air_values.segment_last_is_mem_eq = F::ZERO; + air_values.segment_next_bytes = [F::ZERO; 8]; + } else { + trace[num_rows - 1].set_no_last_no_seq_end(false); + } + #[cfg(feature = "debug_dma")] + { + println!("TRACE DmaUnalignedSM @{segment_id} [0] {:?}", trace[0]); + println!( + "TRACE DmaUnalignedSM @{segment_id} [{}] {:?}", + num_rows - 1, + trace[num_rows - 1] + ); + println!("TRACE DmaUnalignedSM AIR_VALUES {:?}", air_values); + } + timer_stop_and_log_info!(DMA_UNALIGNED_TRACE); + let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs new file mode 100644 index 000000000..7208f5b57 --- /dev/null +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs @@ -0,0 +1,115 @@ +//! The `DmaUnalignedInstance` module defines an instance to perform the witness computation +//! for the Dma State Machine. +//! +//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for +//! execution plans. + +use crate::DmaUnalignedInput; +use std::any::Any; +use std::collections::VecDeque; +use zisk_common::{BusDevice, BusId, CollectCounter, MemCollectorInfo, OPERATION_BUS_ID, OP_TYPE}; +use zisk_core::ZiskOperationType; + +pub struct DmaUnalignedCollector { + /// Collected inputs for witness computation. + pub inputs: Vec, + + /// The number of operations to collect. + pub num_inputs: u64, + + /// Helper to skip instructions based on the plan's configuration. + pub collect_counter: CollectCounter, + + pub trace_offset: usize, + pub last_segment_collector: bool, +} + +impl DmaUnalignedCollector { + /// Creates a new `DmaUnalignedCollector`. + /// + /// # Arguments + /// + /// * `bus_id` - The connected bus ID. + /// * `num_inputs` - The number of inputs to collect. + /// * `collect_counter` - The helper to skip instructions based on the plan's configuration. + /// + /// # Returns + /// A new `DmaUnalignedCollector` instance initialized with the provided parameters. + pub fn new( + num_inputs: u64, + collect_counter: CollectCounter, + last_segment_collector: bool, + ) -> Self { + Self { + inputs: Vec::with_capacity(num_inputs as usize), + num_inputs, + collect_counter, + trace_offset: 0, + last_segment_collector, + } + } +} + +impl BusDevice for DmaUnalignedCollector { + /// Processes data received on the bus, collecting the inputs necessary for witness computation. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A tuple where: + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + data_ext: &[u64], + _pending: &mut VecDeque<(BusId, Vec, Vec)>, + _mem_collector_info: Option<&[MemCollectorInfo]>, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if self.collect_counter.is_final_skip() { + return false; + } + + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + return true; + } + + let rows = DmaUnalignedInput::get_count(data) as u32; + if rows == 0 { + return true; + } + + if let Some((skip, max_count)) = self.collect_counter.should_process(rows) { + self.inputs.push(DmaUnalignedInput::from( + data, + data_ext, + self.trace_offset, + skip as usize, + max_count as usize, + self.last_segment_collector && self.collect_counter.is_final_skip(), + )); + self.trace_offset += max_count as usize; + } + + !self.collect_counter.is_final_skip() + } + + /// Returns the bus IDs associated with this instance. + /// + /// # Returns + /// A vector containing the connected bus ID. + fn bus_id(&self) -> Vec { + vec![OPERATION_BUS_ID] + } + + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs new file mode 100644 index 000000000..ce1761a65 --- /dev/null +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs @@ -0,0 +1,85 @@ +use precompiles_helpers::DmaInfo; +use zisk_common::{A, B, DMA_ENCODED, STEP}; + +#[derive(Debug)] +pub struct DmaUnalignedInput { + pub src: u32, + pub dst: u32, + pub is_first_instance_input: bool, + pub is_last_instance_input: bool, + pub trace_offset: u32, // offset inside trace to paralelize + pub skip: u32, // inside input how many rows skip + pub count: u32, // number of rows used + pub step: u64, + pub encoded: u64, + pub src_values: Vec, +} + +impl DmaUnalignedInput { + pub fn get_count(data: &[u64]) -> usize { + let encoded = data[DMA_ENCODED]; + if DmaInfo::get_dst_offset(encoded) == DmaInfo::get_src_offset(encoded) { + 0 + } else { + let count = DmaInfo::get_loop_count(encoded); + if count > 0 { + count + 1 + } else { + 0 + } + } + } + pub fn get_last_count(&self) -> usize { + let rows = self.count as usize; + let initial_count = self.get_initial_count(); + initial_count - rows + 1 + } + pub fn get_initial_count(&self) -> usize { + DmaInfo::get_count(self.encoded) - self.skip as usize + } + pub fn from( + data: &[u64], + data_ext: &[u64], + trace_offset: usize, + skip: usize, + max_count: usize, + is_last_instance_input: bool, + ) -> Self { + let encoded = data[DMA_ENCODED]; + + let pre_count = DmaInfo::get_pre_count(encoded) as u32; + let data_offset = DmaInfo::get_loop_data_offset(encoded) + skip; + + // unaligned need an extra row to read part of next bytes + let pending_count = DmaInfo::get_loop_count(encoded) + 1 - skip; + let count = std::cmp::min(pending_count, max_count); + if data_offset >= data_ext.len() || (data_offset + count) > data_ext.len() { + println!( + "PROBLEM ON INPUT GENERATION STEP:{} data_ext.len={} src_values[{data_offset}..{}] {}", + data[STEP], + data_ext.len(), + data_offset + count, + DmaInfo::to_string(encoded) + ); + } + // if count not enough to finish unaligned memcpy, add extra source because one row + // use next source value + let src_values_count = if count < pending_count { count + 1 } else { count }; + assert!(DmaInfo::get_loop_count(encoded) > 0); + Self { + dst: data[A] as u32 + pre_count, + src: data[B] as u32 + DmaInfo::get_src64_inc_by_pre(encoded) as u32 * 8, + trace_offset: trace_offset as u32, + is_first_instance_input: trace_offset == 0, + is_last_instance_input, + step: data[STEP], + skip: skip as u32, + count: count as u32, + encoded, + src_values: data_ext[data_offset..data_offset + src_values_count].to_vec(), + } + } + pub fn get_rows(&self) -> usize { + DmaInfo::get_loop_count(self.encoded) + } +} diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs new file mode 100644 index 000000000..174a893d0 --- /dev/null +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs @@ -0,0 +1,141 @@ +//! The `DmaUnalignedInstance` module defines an instance to perform the witness computation +//! for the Dma State Machine. +//! +//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for +//! execution plans. + +use crate::{DmaCheckPoint, DmaUnalignedCollector, DmaUnalignedSM}; +use fields::PrimeField64; +use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; +use std::sync::Arc; +use zisk_common::ChunkId; +use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; +use zisk_pil::DmaUnalignedTrace; + +/// The `DmaUnalignedInstance` struct represents an instance for the Dma State Machine. +/// +/// It encapsulates the `DmaUnalignedSM` and its associated context, and it processes input data +/// to compute witnesses for the DmaUnaligned State Machine. +pub struct DmaUnalignedInstance { + /// Dma state machine. + dma_64_aligned_sm: Arc>, + + /// Instance context. + ictx: InstanceCtx, + + /// Flag to define that it's last segment + is_last_segment: bool, +} + +impl DmaUnalignedInstance { + /// Creates a new `DmaUnalignedInstance`. + /// + /// # Arguments + /// * `dma_64_aligned_sm` - An `Arc`-wrapped reference to the Dma 64 Aligned State Machine. + /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. + /// * `bus_id` - The bus ID associated with this instance. + /// + /// # Returns + /// A new `DmaUnalignedInstance` instance initialized with the provided state machine and + /// context. + pub fn new(dma_64_aligned_sm: Arc>, ictx: InstanceCtx) -> Self { + let is_last_segment = { + let meta = ictx.plan.meta.as_ref().unwrap(); + let checkpoint = meta.downcast_ref::().unwrap(); + checkpoint.is_last_segment + }; + Self { dma_64_aligned_sm, ictx, is_last_segment } + } + + pub fn build_dma_collector(&self, chunk_id: ChunkId) -> DmaUnalignedCollector { + assert_eq!( + self.ictx.plan.air_id, + DmaUnalignedTrace::::AIR_ID, + "DmaUnalignedInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; + DmaUnalignedCollector::new( + num_inputs, + collect_counter, + Some(chunk_id) == collect_info.last_chunk, + ) + } +} + +impl Instance for DmaUnalignedInstance { + /// Computes the witness for the Dma execution plan. + /// + /// This method leverages the `DmaUnalignedSM` to generate an `AirInstance` using the collected + /// inputs. + /// + /// # Arguments + /// * `_pctx` - The proof context, unused in this implementation. + /// + /// # Returns + /// An `Option` containing the computed `AirInstance`. + fn compute_witness( + &self, + _pctx: &ProofCtx, + _sctx: &SetupCtx, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + ) -> ProofmanResult>> { + let inputs: Vec<_> = collectors + .into_iter() + .map(|(_, collector)| { + collector.as_any().downcast::().unwrap().inputs + }) + .collect(); + // Extract segment id from instance context + let segment_id = self.ictx.plan.segment_id.unwrap(); + + Ok(Some(self.dma_64_aligned_sm.compute_witness( + &inputs, + segment_id, + self.is_last_segment, + trace_buffer, + )?)) + } + + /// Retrieves the checkpoint associated with this instance. + /// + /// # Returns + /// A `CheckPoint` object representing the checkpoint of the execution plan. + fn check_point(&self) -> &CheckPoint { + &self.ictx.plan.check_point + } + + /// Retrieves the type of this instance. + /// + /// # Returns + /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). + fn instance_type(&self) -> InstanceType { + InstanceType::Instance + } + + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { + assert_eq!( + self.ictx.plan.air_id, + DmaUnalignedTrace::::AIR_ID, + "DmaUnalignedInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; + Some(Box::new(DmaUnalignedCollector::new( + num_inputs, + collect_counter, + Some(chunk_id) == collect_info.last_chunk, + ))) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/precompiles/dma/src/dma_unaligned/mod.rs b/precompiles/dma/src/dma_unaligned/mod.rs new file mode 100644 index 000000000..4949c3c18 --- /dev/null +++ b/precompiles/dma/src/dma_unaligned/mod.rs @@ -0,0 +1,10 @@ +#[allow(clippy::module_inception)] +mod dma_unaligned; +mod dma_unaligned_collector; +mod dma_unaligned_input; +mod dma_unaligned_instance; + +pub use dma_unaligned::*; +pub use dma_unaligned_collector::*; +pub use dma_unaligned_input::*; +pub use dma_unaligned_instance::*; diff --git a/precompiles/dma/src/dma_unaligned_input.rs b/precompiles/dma/src/dma_unaligned_input.rs deleted file mode 100644 index 534b2c88a..000000000 --- a/precompiles/dma/src/dma_unaligned_input.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::dma_constants::*; -use zisk_common::OperationDmaData; -use zisk_common::{B, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; - -#[derive(Debug)] -pub struct MemCpyInput { - pub step_main: u64, - pub addr_src: u32, - pub addr_dst: u32, - pub a, - pub a: [u64; 4], - pub b: [u64; 4], - pub count: u32, -} - -impl MemCpyInput { - pub fn from(values: &OperationDmaData) -> Self { - Self { - step_main: values[STEP], - addr_main: values[B] as u32, - addr_a: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as u32, - addr_b: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] as u32, - addr_c: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS + DIRECT_READ_PARAMS] - as u32, - cin: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS], - a: values[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(), - b: values[START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] - .try_into() - .unwrap(), - } - } -} diff --git a/precompiles/dma/src/lib.rs b/precompiles/dma/src/lib.rs index e01f77fac..26d62ace9 100644 --- a/precompiles/dma/src/lib.rs +++ b/precompiles/dma/src/lib.rs @@ -1,23 +1,19 @@ mod dma; +mod dma_64_aligned; mod dma_bus_device; mod dma_constants; mod dma_gen_mem_inputs; -mod dma_input; -mod dma_instance; -// mod dma_manager; -mod dma_helpers; -// mod dma_planner; +mod dma_manager; +mod dma_planner; +mod dma_pre_post; +mod dma_unaligned; pub use dma::*; -// pub use dma_64_aligned::*; -// pub use dma_64_aligned_instance::*; +pub use dma_64_aligned::*; pub use dma_bus_device::*; pub use dma_constants::*; pub use dma_gen_mem_inputs::*; -pub use dma_input::*; -pub use dma_instance::*; -// pub use dma_pre_post::*; -// pub use dma_pre_post_instance::*; -// pub use dma_manager::*; -// pub use dma_planner::*; -pub use dma_helpers::*; +pub use dma_manager::*; +pub use dma_planner::*; +pub use dma_pre_post::*; +pub use dma_unaligned::*; diff --git a/precompiles/helpers/Cargo.toml b/precompiles/helpers/Cargo.toml index 65a7e9764..b768c194f 100644 --- a/precompiles/helpers/Cargo.toml +++ b/precompiles/helpers/Cargo.toml @@ -22,4 +22,5 @@ num-traits = { workspace = true } cfg-if = "1.0" [features] -default = [] \ No newline at end of file +default = [] +debug_dma = [] \ No newline at end of file diff --git a/precompiles/helpers/src/dma.rs b/precompiles/helpers/src/dma.rs new file mode 100644 index 000000000..c21896bf4 --- /dev/null +++ b/precompiles/helpers/src/dma.rs @@ -0,0 +1,455 @@ +// use static_assertions::const_assert; +// const_assert!(CHUNK_MEM_STEP_BITS <= 24); + +pub struct DmaHelpers {} + +pub struct DmaValues { + pub dst64: u64, + pub src64: u64, + pub src_offset: u64, + pub dst_offset: u64, + pub pre_count: u64, + pub post_count: u64, + pub memcpy_count: u64, + pub src64_inc_by_pre: u64, + pub src_offset_after_pre: u64, +} + +pub struct DmaInfo {} + +impl DmaInfo { + #[inline(always)] + pub fn to_string(encoded: u64) -> String { + format!("loop_count: {}|pre_writes: {}|dst_offset: {}|src_offset: {}|pre_count: {}|post_count: {}|extra_src_reads: {}", + Self::get_loop_count(encoded), + Self::get_pre_writes(encoded), + Self::get_dst_offset(encoded), + Self::get_src_offset(encoded), + Self::get_pre_count(encoded), + Self::get_post_count(encoded), + Self::get_extra_src_reads(encoded)) + } + pub fn encode_memcpy(dst: u64, src: u64, count: usize) -> u64 { + // bits t_bits + // loop_count 32 32 + // pre_writes: 0,1,2 2 34 + // dst_offset: 0-7 3 37 + // src_offset: 0-7 3 40 + // pre_count: 0-7 3 43 + // post_count: 0-7 3 46 + // double_src_pre: 0,1 1 47 + // double_src_post: 0,1 1 48 + // extra_src_reads: 0-4 3 51 + + let dst_offset = dst & 0x07; + let src_offset = src & 0x07; + + let count = count as u64; + let (pre_count, loop_count, post_count) = if dst_offset > 0 { + let _pre_count = 8 - dst_offset; + if _pre_count >= count { + (count, 0, 0) + } else { + let pending = count - _pre_count; + (_pre_count, pending >> 3, pending & 0x07) + } + } else { + (0, count >> 3, count & 0x07) + }; + let pre_writes = (pre_count > 0) as u64 + (post_count > 0) as u64; + // let to_src_offset = (src + count - 1) & 0x07; + let src_offset_pos = (src_offset + pre_count) & 0x07; + let double_src_post = (src_offset_pos + post_count) > 8; + let double_src_pre = (src_offset + pre_count) > 8; + let extra_src_reads = + if count == 0 { 0 } else { (((src + count - 1) >> 3) - (src >> 3) + 1) - loop_count }; + + (loop_count + (pre_writes << 32)) + | (dst_offset << 34) + | (src_offset << 37) + | (pre_count << 40) + | (post_count << 43) + | ((double_src_pre as u64) << 46) + | ((double_src_post as u64) << 47) + | (extra_src_reads << 48) + } + + pub fn get_extra_src_reads(encoded: u64) -> usize { + (encoded as usize) >> 48 & 0x07 + } + pub fn get_count(encoded: u64) -> usize { + Self::get_loop_count(encoded) * 8 + + Self::get_pre_count(encoded) + + Self::get_post_count(encoded) + } + pub fn get_dst_offset(encoded: u64) -> usize { + (encoded as usize >> 34) & 0x07 + } + + pub fn get_src_offset(encoded: u64) -> usize { + (encoded as usize >> 37) & 0x07 + } + + pub fn get_loop_count(encoded: u64) -> usize { + (encoded & 0xFFFF_FFFF) as usize + } + + pub fn get_pre_writes(encoded: u64) -> usize { + (encoded as usize >> 32) & 0x03 + } + + pub fn is_double_read_pre(encoded: u64) -> bool { + encoded & (1 << 46) != 0 + } + + pub fn is_double_read_post(encoded: u64) -> bool { + encoded & (1 << 47) != 0 + } + + pub fn get_pre_count(encoded: u64) -> usize { + (encoded as usize >> 40) & 0x07 + } + + pub fn get_post_count(encoded: u64) -> usize { + (encoded as usize >> 43) & 0x07 + } + + pub fn get_pre(encoded: u64) -> u8 { + (Self::get_pre_count(encoded) > 0) as u8 + Self::is_double_read_pre(encoded) as u8 + } + + pub fn get_post(encoded: u64) -> u8 { + (Self::get_post_count(encoded) > 0) as u8 + Self::is_double_read_post(encoded) as u8 + } + + pub fn get_src64_inc_by_pre(encoded: u64) -> usize { + let pre_count = Self::get_pre_count(encoded); + (pre_count > 0 && (Self::get_src_offset(encoded) + pre_count) >= 8) as usize + } + + pub fn get_loop_data_offset(encoded: u64) -> usize { + let pre_count = Self::get_pre_count(encoded); + Self::get_pre_writes(encoded) + + (pre_count > 0 && (Self::get_src_offset(encoded) + pre_count) >= 8) as usize + } + + pub fn get_loop_src_offset(encoded: u64) -> u8 { + (Self::get_src_offset(encoded) + Self::get_pre_count(encoded)) as u8 & 0x07 + } + + pub fn get_src_size(encoded: u64) -> usize { + Self::get_loop_count(encoded) + Self::get_extra_src_reads(encoded) + } + pub fn get_data_size(encoded: u64) -> usize { + Self::get_pre_writes(encoded) + Self::get_src_size(encoded) + } + pub fn get_post_data_offset(encoded: u64) -> usize { + Self::get_pre_writes(encoded) + Self::get_src_size(encoded) + - (Self::is_double_read_post(encoded) as usize + 1) + } + pub fn get_pre_write_offset(_encoded: u64) -> usize { + 0 + } + pub fn get_post_write_offset(encoded: u64) -> usize { + (Self::get_pre_count(encoded) != 0) as usize + } + pub fn get_pre_data_offset(encoded: u64) -> usize { + Self::get_pre_writes(encoded) + } +} + +impl DmaHelpers { + pub fn calculate_write_value( + dst_offset: u64, + src_offset: u64, + count: u64, + pre_value: u64, + src_values: &[u64], + ) -> u64 { + let write_mask = + (0xFFFF_FFFF_FFFF_FFFF << ((8 - count) * 8)) >> ((8 - dst_offset - count) * 8); + let value = if dst_offset <= src_offset { + (src_values[0] >> ((src_offset - dst_offset) * 8)) + | if dst_offset == src_offset { + 0 + } else if (src_offset + count) > 8 { + if src_values.len() < 2 { + panic!("ERROR src_values: {:?} dst_offset: {dst_offset} src_offset: {src_offset} count: {count}", src_values); + } + src_values[1] << ((8 - src_offset + dst_offset) * 8) + } else { + 0 + } + } else if dst_offset > src_offset { + src_values[0] << ((dst_offset - src_offset) * 8) + } else { + // dst_offset = src_offset + src_values[0] + }; + #[cfg(feature = "debug_dma")] + println!( + "WRITE_MASK 0x{write_mask:016X} VALUE 0x{value:016X} SRC_VALUES 0x{:016X},0x{:016X} PRE_VALUE:{pre_value:016X} DST_OFFSET:{dst_offset} SRC_OFFSET:{src_offset} COUNT:{count}", + src_values[0], if src_values.len() > 1 { src_values[1] } else { 0 } + ); + (pre_value & !write_mask) | (value & write_mask) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Helper function to compute expected value using byte-by-byte copy + fn expected_write_value( + dst_offset: u64, + src_offset: u64, + count: u64, + pre_value: u64, + src_values: &[u64], + ) -> u64 { + // Convert pre_value to bytes (big-endian layout as used in the function) + let mut result_bytes = pre_value.to_le_bytes(); + + // Convert src_values to a contiguous byte array (big-endian) + let mut src_bytes = Vec::new(); + for &val in src_values { + src_bytes.extend_from_slice(&val.to_le_bytes()); + } + + // Copy count bytes from src_bytes[src_offset..] to result_bytes[dst_offset..] + for i in 0..count as usize { + result_bytes[dst_offset as usize + i] = src_bytes[src_offset as usize + i]; + } + + u64::from_le_bytes(result_bytes) + } + + #[test] + fn test_calculate_write_value_all_combinations() { + // Test patterns for src_values + let src0: u64 = 0x0102030405060708; + let src1: u64 = 0x1112131415161718; + + // Test pattern for pre_value + let pre_value: u64 = 0xAABBCCDDEEFF0011; + + // Iterate over all dst_offset values (0..8) + for dst_offset in 0..8 { + // For each dst_offset, count can be 1 to (8 - dst_offset) + for count in 1..=(8 - dst_offset) { + // For each valid (dst_offset, count), test all src_offset values + // src_offset can be 0..8, but we need to ensure we have enough src data + for src_offset in 0..8 { + // Determine if we need one or two src values + // We need two src values if (src_offset + count) > 8 + let needs_two_src = (src_offset + count) > 8; + let src_values: Vec = if needs_two_src { + vec![src0, src1] + } else { + vec![src0, 0] // Always provide both for safety + }; + + let result = DmaHelpers::calculate_write_value( + dst_offset, + src_offset, + count, + pre_value, + &src_values, + ); + + let expected = + expected_write_value(dst_offset, src_offset, count, pre_value, &src_values); + + assert_eq!( + result, expected, + "Failed for dst_offset={}, src_offset={}, count={}\n\ + pre_value: 0x{:016X}\n\ + src[0]: 0x{:016X}\n\ + src[1]: 0x{:016X}\n\ + expected: 0x{:016X}\n\ + got: 0x{:016X}", + dst_offset, src_offset, count, pre_value, src0, src1, expected, result + ); + } + } + } + } + + #[test] + fn test_calculate_write_value_edge_cases() { + let src0: u64 = 0x0102030405060708; + let src1: u64 = 0x1112131415161718; + let pre_value: u64 = 0xAABBCCDDEEFF0011; + + // Test case: dst_offset=0, count=8 (full overwrite) + let result = DmaHelpers::calculate_write_value(0, 0, 8, pre_value, &[src0, src1]); + assert_eq!(result, src0, "Full overwrite with aligned offsets failed"); + + // Test case: dst_offset=0, count=1 (single byte at start) + let result = DmaHelpers::calculate_write_value(0, 0, 1, pre_value, &[src0, src1]); + let expected = 0xAABBCCDDEEFF0008u64; + assert_eq!(result, expected, "Single byte at start failed"); + + // Test case: dst_offset=7, count=1 (single byte at end) + let result = DmaHelpers::calculate_write_value(7, 0, 1, pre_value, &[src0, src1]); + let expected = 0x08BBCCDDEEFF0011; + assert_eq!(result, expected, "Single byte at end failed"); + + // Test case: src spans two values (src_offset=7, count=2) + let result = DmaHelpers::calculate_write_value(0, 7, 2, pre_value, &[src0, src1]); + let expected = 0xAABBCCDDEEFF1801; + assert_eq!(result, expected, "Src spanning two values failed"); + } + + #[test] + fn test_calculate_write_value_zero_patterns() { + let src0: u64 = 0x0000000000000000; + let src1: u64 = 0x0000000000000000; + let pre_value: u64 = 0xFFFFFFFFFFFFFFFF; + + // Writing zeros should clear the appropriate bytes + for dst_offset in 0..8 { + for count in 1..=(8 - dst_offset) { + let result = DmaHelpers::calculate_write_value( + dst_offset, + 0, + count, + pre_value, + &[src0, src1], + ); + let expected = expected_write_value(dst_offset, 0, count, pre_value, &[src0, src1]); + assert_eq!( + result, expected, + "Zero pattern failed for dst_offset={}, count={}", + dst_offset, count + ); + } + } + } + + #[test] + fn test_calculate_write_value_ff_patterns() { + let src0: u64 = 0xFFFFFFFFFFFFFFFF; + let src1: u64 = 0xFFFFFFFFFFFFFFFF; + let pre_value: u64 = 0x0000000000000000; + + // Writing 0xFF should set the appropriate bytes + for dst_offset in 0..8 { + for count in 1..=(8 - dst_offset) { + let result = DmaHelpers::calculate_write_value( + dst_offset, + 0, + count, + pre_value, + &[src0, src1], + ); + let expected = expected_write_value(dst_offset, 0, count, pre_value, &[src0, src1]); + assert_eq!( + result, expected, + "FF pattern failed for dst_offset={}, count={}", + dst_offset, count + ); + } + } + } + + /// Byte-based implementation for comparison + #[inline(always)] + fn calculate_write_value_bytes( + dst_offset: usize, + src_offset: usize, + count: usize, + pre_value: u64, + src_values: &[u64], + ) -> u64 { + let mut result_bytes = pre_value.to_le_bytes(); + let src0_bytes = src_values[0].to_le_bytes(); + let src1_bytes = src_values[1].to_le_bytes(); + + for i in 0..count { + let src_idx = src_offset + i; + result_bytes[dst_offset + i] = + if src_idx < 8 { src0_bytes[src_idx] } else { src1_bytes[src_idx - 8] }; + } + + u64::from_le_bytes(result_bytes) + } + + #[test] + fn benchmark_calculate_write_value() { + use std::time::Instant; + + let src0: u64 = 0x0102030405060708; + let src1: u64 = 0x1112131415161718; + let pre_value: u64 = 0xAABBCCDDEEFF0011; + let src_values = [src0, src1]; + + const ITERATIONS: usize = 1_000_000; + + // Warm up + let mut sum_bitwise: u64 = 0; + let mut sum_bytes: u64 = 0; + + // Benchmark bitwise implementation + let start = Instant::now(); + for _ in 0..ITERATIONS { + for dst_offset in 0..8 { + for count in 1..=(8 - dst_offset) { + for src_offset in 0..8 { + sum_bitwise = sum_bitwise.wrapping_add(DmaHelpers::calculate_write_value( + dst_offset, + src_offset, + count, + pre_value, + &src_values, + )); + } + } + } + } + let bitwise_duration = start.elapsed(); + + // Benchmark byte-based implementation + let start = Instant::now(); + for _ in 0..ITERATIONS { + for dst_offset in 0..8 { + for count in 1..=(8 - dst_offset) { + for src_offset in 0..8 { + sum_bytes = sum_bytes.wrapping_add(calculate_write_value_bytes( + dst_offset, + src_offset, + count, + pre_value, + &src_values, + )); + } + } + } + } + let bytes_duration = start.elapsed(); + + // Verify both produce same results + assert_eq!(sum_bitwise, sum_bytes, "Results differ!"); + + // 288 combinations per iteration (8 dst * varying count * 8 src) + let total_ops = ITERATIONS * 288; + + println!("\n=== Benchmark Results ==="); + println!("Iterations: {} ({} total operations)", ITERATIONS, total_ops); + println!("Bitwise implementation: {:?}", bitwise_duration); + println!("Byte-based implementation: {:?}", bytes_duration); + println!( + "Bitwise ops/sec: {:.2}M", + total_ops as f64 / bitwise_duration.as_secs_f64() / 1_000_000.0 + ); + println!( + "Bytes ops/sec: {:.2}M", + total_ops as f64 / bytes_duration.as_secs_f64() / 1_000_000.0 + ); + println!( + "Speedup (bitwise vs bytes): {:.2}x", + bytes_duration.as_secs_f64() / bitwise_duration.as_secs_f64() + ); + println!("Checksum (to prevent optimization): {}", sum_bitwise); + } +} diff --git a/precompiles/helpers/src/lib.rs b/precompiles/helpers/src/lib.rs index 6ef6de64e..5f1148b6b 100644 --- a/precompiles/helpers/src/lib.rs +++ b/precompiles/helpers/src/lib.rs @@ -2,12 +2,14 @@ mod arith_eq; mod arith_eq_384; mod big_int; mod common; +mod dma; mod keccak; pub use arith_eq::*; pub use arith_eq_384::*; pub use big_int::*; pub use common::*; +pub use dma::*; pub use keccak::{ keccakf_topology, KECCAKF_BITS, KECCAKF_CHUNKS, KECCAKF_INPUT_BITS_IN_PARALLEL, KECCAKF_INPUT_SIZE_BITS, KECCAKF_OUTPUT_BITS_IN_PARALLEL, KECCAKF_OUTPUT_SIZE_BITS, diff --git a/state-machines/main/pil/main.pil b/state-machines/main/pil/main.pil index 49734c646..107140b22 100644 --- a/state-machines/main/pil/main.pil +++ b/state-machines/main/pil/main.pil @@ -257,9 +257,9 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, // region. As a result, no memory state machine can validate memory access in this area; // only the main state machine can 'prove' such memory access. - col witness bits(40) a_reg_prev_mem_step; - col witness bits(40) b_reg_prev_mem_step; - col witness bits(40) store_reg_prev_mem_step; + col witness bits(REG_STEP_BITS) a_reg_prev_mem_step; + col witness bits(REG_STEP_BITS) b_reg_prev_mem_step; + col witness bits(REG_STEP_BITS) store_reg_prev_mem_step; col witness bits(32) store_reg_prev_value[RC]; col witness bits(1) a_src_reg; col witness bits(1) b_src_reg; @@ -331,7 +331,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, range_check(expression: a_mem_step - a_reg_prev_mem_step - 1, min: 0, max: MAX_RANGE, sel: a_src_reg); range_check(expression: b_mem_step - b_reg_prev_mem_step - 1, min: 0, max: MAX_RANGE, sel: b_src_reg); - range_check(expression: store_mem_step - store_reg_prev_mem_step -1 , min: 0, max: MAX_RANGE, sel: store_reg); + range_check(expression: store_mem_step - store_reg_prev_mem_step - 1 , min: 0, max: MAX_RANGE, sel: store_reg); // Sent to bus the external operation @@ -526,5 +526,5 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, // Main Segment constraint - range_check(main_segment, min: 0, max: ((2**32)/N) - 1); + range_check(main_segment, min: 0, max: ((2**MAIN_STEP_BITS)/N) - 1); } \ No newline at end of file diff --git a/state-machines/main/src/main_sm.rs b/state-machines/main/src/main_sm.rs index 280f1a8f8..5f442045d 100644 --- a/state-machines/main/src/main_sm.rs +++ b/state-machines/main/src/main_sm.rs @@ -16,7 +16,7 @@ use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofCtx, ProofmanResult, SetupCtx}; use rayon::prelude::*; use zisk_common::{BusDeviceMetrics, EmuTrace, InstanceCtx, SegmentId}; -use zisk_core::{ZiskRom, REGS_IN_MAIN, REGS_IN_MAIN_FROM, REGS_IN_MAIN_TO}; +use zisk_core::{ZiskRom, DEFAULT_MAX_STEPS, REGS_IN_MAIN, REGS_IN_MAIN_FROM, REGS_IN_MAIN_TO}; use zisk_pil::MainAirValues; use ziskemu::{Emu, EmuRegTrace}; @@ -45,7 +45,8 @@ pub struct MainInstance { } impl MainInstance { - const MAX_SEGMENT_ID: usize = ((1 << 32) / MainTraceType::::NUM_ROWS) - 1; + const MAX_SEGMENT_ID: usize = + ((DEFAULT_MAX_STEPS + 1) as usize / MainTraceType::::NUM_ROWS) - 1; /// Creates a new `MainInstance`. /// diff --git a/state-machines/mem/pil/dual_byte.pil b/state-machines/mem/pil/dual_byte.pil deleted file mode 100644 index 8add52ef3..000000000 --- a/state-machines/mem/pil/dual_byte.pil +++ /dev/null @@ -1,17 +0,0 @@ -require "std_lookup.pil"; -require "opids.pil"; - -airtemplate DualByte(const int N = 2**16) { - - col fixed BYTE_A = [0:256..255:256]...; - col fixed BYTE_B = [0..255]...; - - col witness multiplicity; - - lookup_proves(DUAL_BYTE_TABLE_ID, mul: multiplicity, expressions: [BYTE_A, BYTE_B]); -} - -function range_dual_byte(expr byte_a, expr byte_b, expr sel = 1) { - lookup_assumes(DUAL_BYTE_TABLE_ID, expressions: [byte_a, byte_b], sel: sel); -} - diff --git a/state-machines/mem/pil/mem.pil b/state-machines/mem/pil/mem.pil index 7ecd366c3..3d3413153 100644 --- a/state-machines/mem/pil/mem.pil +++ b/state-machines/mem/pil/mem.pil @@ -107,7 +107,7 @@ airtemplate Mem(const int N = 2**21, const int id = MEMORY_ID, const int RC = 2, is_first_segment * segment_id === 0; col witness bits(29) addr; - col witness bits(40) step; + col witness bits(MEM_STEP_BITS) step; col witness bits(1) sel; col witness bits(1) addr_changes; @@ -119,7 +119,7 @@ airtemplate Mem(const int N = 2**21, const int id = MEMORY_ID, const int RC = 2, // Two columns, one for step (timestap) of second operation, and other // to enable dual operation in the row. - col witness bits(40) air.step_dual; + col witness bits(MEM_STEP_BITS) air.step_dual; col witness bits(1) air.sel_dual; sel_dual * (1 - sel_dual) === 0; @@ -375,9 +375,10 @@ airtemplate Mem(const int N = 2**21, const int id = MEMORY_ID, const int RC = 2, is_first_segment * SEGMENT_L1 * (1 - addr_changes) === 0; - // addr_change == 1 => [0,2^18-1] => 18 + 18 = 36 bits + 3 => 39 bits => 512 GB - // addr_change == 0 => [0,2^18-1] => 18 + 18 = 36 bits => STEP upto 2^34 => 16 GB steps - // 36 bits x 2^22 = 2^58 + 2^32 address = 2^59 secure. + // addr_change == 1 => [0,2^19-1] => 19 + 19 = 38 bits + 3 => 41 bits => 2 TB (limit address 32 bits) + // addr_change == 0 => [0,2^19-1] => 19 + 19 = 38 bits => 2 slots => 2^37 => 128 GB steps (32K main instances) + // 38 bits x 2^22 = 2^60 + 2^32 address = 2^61 secure. + // using control in the middle of instance, reduce to 2^60 range_check(expression: increment[0], min: 0, max: 2**18 - 1); range_check(expression: increment[1], min: 0, max: 2**18 - 1); diff --git a/state-machines/mem/pil/mem_align_byte.pil b/state-machines/mem/pil/mem_align_byte.pil index e0bfad98d..9915376c1 100644 --- a/state-machines/mem/pil/mem_align_byte.pil +++ b/state-machines/mem/pil/mem_align_byte.pil @@ -2,7 +2,6 @@ require "std_permutation.pil"; require "std_lookup.pil"; require "std_range_check.pil"; require "opids.pil"; -require "dual_byte.pil"; // Specific low cost machine for specific byte unaligned memory access. @@ -102,7 +101,7 @@ airtemplate MemAlignByte(const int N = 2**10, const int read = 1, const int writ } range_check(min: 0, max: 0xFFFF, expression: value_16b); - lookup_assumes(DUAL_BYTE_TABLE_ID, [byte_value, value_8b]); + range_dual_byte(byte_value, value_8b); airval padding_size; direct_update_proves(MEMORY_ID, [MEMORY_LOAD_OP, 0, 0, 8, 0, 0], sel: padding_size); diff --git a/state-machines/mem/src/mem_align_byte_sm.rs b/state-machines/mem/src/mem_align_byte_sm.rs index 542b0e915..3cb653675 100644 --- a/state-machines/mem/src/mem_align_byte_sm.rs +++ b/state-machines/mem/src/mem_align_byte_sm.rs @@ -9,7 +9,10 @@ use rayon::prelude::*; use crate::MemAlignInput; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; -use zisk_pil::{MemAlignByteAirValues, MemAlignReadByteAirValues, MemAlignWriteByteAirValues}; +use zisk_pil::{ + MemAlignByteAirValues, MemAlignReadByteAirValues, MemAlignWriteByteAirValues, + DUAL_RANGE_BYTE_ID, +}; #[cfg(not(feature = "packed"))] use zisk_pil::{ @@ -356,7 +359,6 @@ impl MemAlignByteRow> const OFFSET_MASK: u32 = 0x07; const OFFSET_BITS: u32 = 3; -const DUAL_BYTE_TABLE_ID: usize = 88; pub struct MemAlignByteSM { /// PIL2 standard library @@ -378,7 +380,7 @@ impl MemAlignByteSM { Arc::new(Self { std: std.clone(), table_dual_byte_id: std - .get_virtual_table_id(DUAL_BYTE_TABLE_ID) + .get_virtual_table_id(DUAL_RANGE_BYTE_ID) .expect("Failed to get dual byte table ID"), table_16b_id: std.get_range_id(0, 0xFFFF, None).expect("Failed to get 16b table ID"), table_8b_id: std.get_range_id(0, 0xFF, None).expect("Failed to get 8b table ID"), diff --git a/state-machines/mem/src/mem_counters_cursor.rs b/state-machines/mem/src/mem_counters_cursor.rs index fe04e756d..403291f55 100644 --- a/state-machines/mem/src/mem_counters_cursor.rs +++ b/state-machines/mem/src/mem_counters_cursor.rs @@ -81,7 +81,7 @@ impl MemCountersCursor { return sorted_boxes.first().cloned().unwrap_or_default(); } let total_size: usize = sorted_boxes.iter().map(|b| b.len()).sum(); - let target_size: usize = arity * (total_size / sorted_boxes.len()); + let target_size: usize = std::cmp::max(arity * (total_size / sorted_boxes.len()), 1); let mut groups: Vec<&[Vec]> = Vec::new(); let mut group_weight = 0; diff --git a/state-machines/mem/src/mem_sm.rs b/state-machines/mem/src/mem_sm.rs index 8d001fd75..8d1d0ccbc 100644 --- a/state-machines/mem/src/mem_sm.rs +++ b/state-machines/mem/src/mem_sm.rs @@ -12,13 +12,10 @@ type MemTraceType = MemTracePacked; #[cfg(not(feature = "packed"))] type MemTraceType = MemTrace; #[cfg(feature = "debug_mem")] -use { - num_bigint::ToBigInt, - std::{ - env, - fs::File, - io::{BufWriter, Write}, - }, +use std::{ + env, + fs::File, + io::{BufWriter, Write}, }; use crate::{MemInput, MemModule}; @@ -72,17 +69,22 @@ impl MemSM { println!("[MemDebug] writing information {} .....", file_name); let file = File::create(file_name).unwrap(); let mut writer = BufWriter::new(file); - let num_rows = MemTrace::NUM_ROWS; + let num_rows = MemTrace::::NUM_ROWS; for i in 0..num_rows { - let addr = trace[i].addr.as_canonical_biguint().to_bigint().unwrap() * 8; - let step = trace[i].step.as_canonical_biguint().to_bigint().unwrap(); - writeln!( - writer, - "{:#010X} {} {} {:?}", - addr, trace[i].step, trace[i].wr, trace[i].value - ) - .unwrap(); + let addr = trace[i].addr.as_canonical_u64() * 8; + let step = trace[i].step.as_canonical_u64(); + let op = if trace[i].wr.is_zero() { 'R' } else { 'W' }; + let values = + [trace[i].value[0].as_canonical_u64(), trace[i].value[1].as_canonical_u64()]; + let value = values[0] | (values[1] << 32); + writeln!(writer, "{i:<8} {addr:#010X} {step} {op} {values:?} 0x{value:016X}").unwrap(); + let dual = !trace[i].sel_dual.is_zero(); + if dual { + let step = trace[i].step_dual.as_canonical_u64(); + writeln!(writer, "{i:<8} {addr:#010X} {step} R {values:?} 0x{value:016X} DUAL") + .unwrap(); + } } println!("[MemDebug] done"); } diff --git a/state-machines/mem/src/rom_data_sm.rs b/state-machines/mem/src/rom_data_sm.rs index 315d120f8..6233588d7 100644 --- a/state-machines/mem/src/rom_data_sm.rs +++ b/state-machines/mem/src/rom_data_sm.rs @@ -65,7 +65,7 @@ impl RomDataSM { pub fn save_to_file(trace: &RomDataTrace, file_name: &str) { let file = File::create(file_name).unwrap(); let mut writer = BufWriter::new(file); - let num_rows = RomDataTrace::NUM_ROWS; + let num_rows = RomDataTrace::::NUM_ROWS; for i in 0..num_rows { let addr = trace[i].get_addr() * 8; diff --git a/witness-computation/Cargo.toml b/witness-computation/Cargo.toml index 9b82eb41f..18a57d5ab 100644 --- a/witness-computation/Cargo.toml +++ b/witness-computation/Cargo.toml @@ -25,6 +25,7 @@ precomp-sha256f = { workspace = true } precomp-big-int = { workspace = true } precomp-arith-eq = { workspace = true } precomp-arith-eq-384 = { workspace = true } +precomp-dma = { workspace = true } zisk-pil = { workspace = true } ziskemu = { workspace = true } zisk-core = { workspace = true } diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 0d4ad3712..c49d26e93 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -10,6 +10,7 @@ use pil_std_lib::Std; use precomp_arith_eq::ArithEqManager; use precomp_arith_eq_384::ArithEq384Manager; use precomp_big_int::Add256Manager; +use precomp_dma::DmaManager; use precomp_keccakf::KeccakfManager; use precomp_sha256f::Sha256fManager; use proofman::register_std; @@ -26,7 +27,8 @@ use zisk_core::{Riscv2zisk, CHUNK_SIZE}; use zisk_pil::PACKED_INFO; use zisk_pil::{ ADD_256_AIR_IDS, ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, - BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, + BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, DMA_64_ALIGNED_AIR_IDS, DMA_AIR_IDS, + DMA_PRE_POST_AIR_IDS, DMA_UNALIGNED_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, MEM_ALIGN_AIR_IDS, MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, MEM_ALIGN_WRITE_BYTE_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, @@ -113,6 +115,7 @@ impl WitnessLibrary for WitnessLib { let arith_eq_sm = ArithEqManager::new(std.clone()); let arith_eq_384_sm = ArithEq384Manager::new(std.clone()); let add256_sm = Add256Manager::new(std.clone()); + let dma_sm = DmaManager::new(std.clone()); let mem_instances = vec![ (ZISK_AIRGROUP_ID, MEM_AIR_IDS[0]), @@ -130,6 +133,13 @@ impl WitnessLibrary for WitnessLib { (ZISK_AIRGROUP_ID, BINARY_EXTENSION_AIR_IDS[0]), ]; + let dma_instances = vec![ + (ZISK_AIRGROUP_ID, DMA_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_PRE_POST_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_UNALIGNED_AIR_IDS[0]), + ]; + let sm_bundle = StaticSMBundle::new( self.asm_path.is_some(), vec![ @@ -161,6 +171,7 @@ impl WitnessLibrary for WitnessLib { vec![(ZISK_AIRGROUP_ID, ADD_256_AIR_IDS[0])], StateMachines::Add256Manager(add256_sm.clone()), ), + (dma_instances, StateMachines::DmaManager(dma_sm.clone())), ], ); diff --git a/ziskos/entrypoint/src/memcmp.s b/ziskos/entrypoint/src/dma/memcmp.s similarity index 91% rename from ziskos/entrypoint/src/memcmp.s rename to ziskos/entrypoint/src/dma/memcmp.s index c97dab76b..9bbc9231a 100644 --- a/ziskos/entrypoint/src/memcmp.s +++ b/ziskos/entrypoint/src/dma/memcmp.s @@ -6,8 +6,8 @@ .p2align 4 .type memcmp,@function memcmp: + csrs 0x813, a2 # Marker: Write count (a2) to CSR 0x813 add a0,a0,a1 - .insn 4, 0x81362073 ret /* beqz a2, .memcmp_eq diff --git a/ziskos/entrypoint/src/dma/memcpy.s b/ziskos/entrypoint/src/dma/memcpy.s new file mode 100644 index 000000000..c59a5122e --- /dev/null +++ b/ziskos/entrypoint/src/dma/memcpy.s @@ -0,0 +1,14 @@ + .section ".note.GNU-stack","",@progbits + .text + .attribute 4, 16 + .attribute 5, "rv64im" + .globl memcpy + .p2align 4 + .type memcpy,@function +memcpy: + csrs 0x812, a2 # Marker: Write count (a2) to CSR 0x812 + add x0,a0,a1 + ret + + .size memcpy, .-memcpy + .section .text.hot,"ax",@progbits \ No newline at end of file diff --git a/ziskos/entrypoint/src/dma/memmove.s b/ziskos/entrypoint/src/dma/memmove.s new file mode 100644 index 000000000..c04803feb --- /dev/null +++ b/ziskos/entrypoint/src/dma/memmove.s @@ -0,0 +1,13 @@ + .section ".note.GNU-stack","",@progbits + .text + .attribute 4, 16 + .attribute 5, "rv64im" + .globl memmove + .p2align 4 + .type memmove,@function +memmove: + csrs 0x812, a2 # Marker: Write count (a2) to CSR 0x812 + add x0,a0,a1 + ret + .size memmove, .-memmove + .section .text.hot,"ax",@progbits \ No newline at end of file diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index c6aabf63b..530a898ab 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -286,62 +286,7 @@ mod ziskos { ptr } - #[no_mangle] - #[inline(never)] - #[export_name = "memcpy"] - pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 { - asm!( - ".insn r 0x33, 0, 0, x0, a0, a1 ", - // "csrs 0x812, a0", // Marker: Write count (a0) to CSR 0x812 - // "csrs 0x813, a1", // Marker: Write count (a1) to CSR 0x812 - "csrs 0x812, a2", // Marker: Write count (a2) to CSR 0x812 - in("a0") dest, // Force dest parameter into register a0 - in("a1") src, // Force src parameter into register a1 - in("a2") count, // Force count parameter into register a2 - options(preserves_flags) - ); - - dest - } - #[no_mangle] - #[inline(never)] - #[export_name = "memmove"] - pub unsafe extern "C" fn memmove(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 { - asm!( - ".insn r 0x33, 0, 0, x0, a0, a1 ", - // "csrs 0x812, a0", // Marker: Write count (a0) to CSR 0x812 - // "csrs 0x813, a1", // Marker: Write count (a1) to CSR 0x812 - "csrs 0x812, a2", // Marker: Write count (a2) to CSR 0x812 - in("a0") dest, // Force dest parameter into register a0 - in("a1") src, // Force src parameter into register a1 - in("a2") count, // Force count parameter into register a2 - options(preserves_flags) - ); - - dest - } - // #[no_mangle] - // #[inline(always)] - // #[export_name = "memcmp"] - // pub unsafe extern "C" fn memcmp(a: *const u8, b: *const u8, count: usize) -> i64 { - // let result: i64; - // asm!( - // ".insn r 0x33, 0, 0, a0, a0, a1 ", - // // "csrs 0x812, a0", // Marker: Write count (a0) to CSR 0x812 - // // "csrs 0x813, a1", // Marker: Write count (a1) to CSR 0x812 - // "csrs 0x813, a2", // Marker: Write count (a2) to CSR 0x812 - // in("a0") a, // Force dest parameter into register a0 - // in("a1") b, // Force src parameter into register a1 - // in("a2") count, // Force count parameter into register a2 - // lateout("a0") result, - // options(preserves_flags) - // ); - // result - // } - core::arch::global_asm!(include_str!("memcmp.s")); - // core::arch::global_asm!(include_str!("memcpy.s")); - // core::arch::global_asm!(include_str!("memset.s")); - // #[used] - // pub static KEEP_FUNCTIONS: &[unsafe extern "C" fn(*const u8, *const u8, usize) -> i64] = - // &[zisk_memcmp]; + core::arch::global_asm!(include_str!("dma/memcpy.s")); + core::arch::global_asm!(include_str!("dma/memmove.s")); + // core::arch::global_asm!(include_str!("dma/memcmp.s")); } From 4e24c948bb709dcc3860814ede10652a1d5c7b84 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:00:06 +0000 Subject: [PATCH 215/782] Added a constant for semaphore chunk done wait default time --- emulator-asm/asm-runner/src/asm_mo_runner.rs | 3 ++- emulator-asm/asm-runner/src/asm_mt_runner.rs | 4 ++-- emulator-asm/asm-runner/src/asm_rh_runner.rs | 4 ++-- emulator-asm/asm-runner/src/lib.rs | 2 ++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index 8acf8cb5b..e427f6f51 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -10,6 +10,7 @@ use tracing::error; use crate::sem_chunk_done_name; use crate::shmem_output_name; +use crate::SEM_CHUNK_DONE_WAIT_DURATION; use crate::{AsmMOChunk, AsmMOHeader, AsmRunError, AsmService, AsmServices, AsmSharedMemory}; use mem_planner_cpp::MemPlanner; @@ -153,7 +154,7 @@ impl AsmRunnerMO { }; let exit_code = loop { - match sem_chunk_done.timed_wait(Duration::from_secs(10)) { + match sem_chunk_done.timed_wait(SEM_CHUNK_DONE_WAIT_DURATION) { Ok(()) => { // Synchronize with memory changes from the C++ side fence(Ordering::Acquire); diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 82d7ae9a7..eb2ea639d 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -11,7 +11,7 @@ use tracing::{error, info}; use crate::{ sem_chunk_done_name, shmem_output_name, AsmMTChunk, AsmMTHeader, AsmRunError, AsmService, - AsmServices, AsmSharedMemory, + AsmServices, AsmSharedMemory, SEM_CHUNK_DONE_WAIT_DURATION, }; use anyhow::{Context, Result}; @@ -133,7 +133,7 @@ impl AsmRunnerMT { }; let exit_code = loop { - match sem_chunk_done.timed_wait(Duration::from_secs(10)) { + match sem_chunk_done.timed_wait(SEM_CHUNK_DONE_WAIT_DURATION) { Ok(()) => { #[cfg(feature = "stats")] { diff --git a/emulator-asm/asm-runner/src/asm_rh_runner.rs b/emulator-asm/asm-runner/src/asm_rh_runner.rs index 03ae31b47..2bd7ba9b4 100644 --- a/emulator-asm/asm-runner/src/asm_rh_runner.rs +++ b/emulator-asm/asm-runner/src/asm_rh_runner.rs @@ -3,7 +3,7 @@ use zisk_common::ExecutorStatsHandle; use crate::{ sem_chunk_done_name, shmem_output_name, AsmRHData, AsmRHHeader, AsmRunError, AsmService, - AsmServices, AsmSharedMemory, + AsmServices, AsmSharedMemory, SEM_CHUNK_DONE_WAIT_DURATION, }; use anyhow::{Context, Result}; use named_sem::NamedSemaphore; @@ -86,7 +86,7 @@ impl AsmRunnerRH { asm_services.send_rom_histogram_request(max_steps)?; loop { - match sem_chunk_done.timed_wait(Duration::from_secs(10)) { + match sem_chunk_done.timed_wait(SEM_CHUNK_DONE_WAIT_DURATION) { Ok(()) => { // Synchronize with memory changes from the C++ side fence(Ordering::Acquire); diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 9e4ccc471..2ffac998c 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -52,6 +52,8 @@ pub use shmem_reader::*; pub use shmem_utils::*; pub use shmem_writer::*; +const SEM_CHUNK_DONE_WAIT_DURATION: std::time::Duration = std::time::Duration::from_secs(10); + fn build_name( prefix: &str, port: u16, From e3e7ca54c82bb86fb3867b88ec479da6eb6e38b0 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 12 Jan 2026 15:55:10 +0100 Subject: [PATCH 216/782] Implement hints in fcall_secp256k1_ecdsa_verify() --- .../src/zisklib/fcalls/secp256k1/ecdsa.rs | 24 ++++++++++++++++++- .../src/zisklib/lib/secp256k1/ecdsa.rs | 9 ++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs index 4695646c2..a7b50fbf0 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs @@ -42,9 +42,31 @@ pub fn fcall_secp256k1_ecdsa_verify( z_value: &[u64; 4], r_value: &[u64; 4], s_value: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 8] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + use crate::zisklib::fcalls_impl; + + // Convert inputs into a single params array + let mut params: [u64; 20] = [0u64; 20]; + params[0..8].copy_from_slice(pk_value); + params[8..12].copy_from_slice(z_value); + params[12..16].copy_from_slice(r_value); + params[16..20].copy_from_slice(s_value); + + // Call the implementation + let mut results = [0u64; 8]; + fcalls_impl::secp256k1::fcall_secp256k1_ecdsa_verify(¶ms, &mut results); + + // Hint the result + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&results); + } + + results + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(pk_value, 8); diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs index c7f2848dd..0743f1898 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs @@ -37,7 +37,14 @@ pub fn secp256k1_ecdsa_verify( // saving us from expensive fn arithmetic // Hint the result - let p = fcall_secp256k1_ecdsa_verify(pk, z, r, s); + let p = fcall_secp256k1_ecdsa_verify( + pk, + z, + r, + s, + #[cfg(feature = "hints")] + hints, + ); // Check the recovered point is valid assert!(secp256k1_is_on_curve( From be802cf72baf8496fd50c6deb81e7593c770b7b2 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 12 Jan 2026 16:03:54 +0100 Subject: [PATCH 217/782] Implement hints in fcall_msb_pos_256_3() --- .../src/zisklib/fcalls/msb_pos_256.rs | 20 +++++++++++++++++-- .../src/zisklib/lib/secp256k1/curve.rs | 8 +++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs index c3a1d38b6..8bb788246 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs @@ -39,9 +39,25 @@ pub fn fcall_msb_pos_256( } #[allow(unused_variables)] -pub fn fcall_msb_pos_256_3(x: &[u64; 4], y: &[u64; 4], z: &[u64; 4]) -> (u64, u64) { +pub fn fcall_msb_pos_256_3( + x: &[u64; 4], + y: &[u64; 4], + z: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> (u64, u64) { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + let tmp: [u64; 12] = + [x[0], x[1], x[2], x[3], y[0], y[1], y[2], y[3], z[0], z[1], z[2], z[3]]; + let (i, pos) = msb_pos_256(&tmp, 3); + #[cfg(feature = "hints")] + { + hints.push(3); + hints.push(i as u64); + hints.push(pos as u64); + } + (i as u64, pos as u64) + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(3, 1); // Number of inputs diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index 69ca226fb..4d752f745 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -547,7 +547,13 @@ pub fn secp256k1_triple_scalar_mul_with_g( // From here on, at least one of r,s,t is greater than 1 // Hint the maximum length between the binary representations of r,s and t - let (max_limb, max_bit) = fcall_msb_pos_256_3(r, s, t); + let (max_limb, max_bit) = fcall_msb_pos_256_3( + r, + s, + t, + #[cfg(feature = "hints")] + hints, + ); // Perform the loop, based on the binary representation of r,s and t From 0c66d78b990c855043610565ec942ac4ef5187c1 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:08:21 +0000 Subject: [PATCH 218/782] cargo clippy --- emulator-asm/asm-runner/src/asm_mo_runner.rs | 1 - emulator-asm/asm-runner/src/asm_mt_runner.rs | 2 +- emulator-asm/asm-runner/src/asm_rh_runner.rs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index e427f6f51..ce7c147f9 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -5,7 +5,6 @@ use zisk_common::Plan; use std::ffi::c_void; use std::sync::atomic::{fence, Ordering}; -use std::time::Duration; use tracing::error; use crate::sem_chunk_done_name; diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index eb2ea639d..d7a758191 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -5,7 +5,7 @@ use zisk_common::{ChunkId, EmuTrace, ExecutorStatsHandle}; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] use std::sync::atomic::{fence, Ordering}; use std::sync::Arc; -use std::time::{Duration, Instant}; +use std::time::Instant; use tracing::{error, info}; diff --git a/emulator-asm/asm-runner/src/asm_rh_runner.rs b/emulator-asm/asm-runner/src/asm_rh_runner.rs index 2bd7ba9b4..2b373ba79 100644 --- a/emulator-asm/asm-runner/src/asm_rh_runner.rs +++ b/emulator-asm/asm-runner/src/asm_rh_runner.rs @@ -8,7 +8,6 @@ use crate::{ use anyhow::{Context, Result}; use named_sem::NamedSemaphore; use std::sync::atomic::{fence, Ordering}; -use std::time::Duration; pub struct PreloadedRH { pub output_shmem: AsmSharedMemory, From 79cf1a6b066f53c71324efd7ead71e5cdb2dbf3f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 12 Jan 2026 16:54:24 +0000 Subject: [PATCH 219/782] fix y_is_odd in secp256k1 --- ziskos-hints/src/handlers/secp256k1.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index d96ba5e9d..dd3ca268b 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -23,7 +23,7 @@ pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { validate_hint_length(data, EXPECTED_LEN, "SECP256K1_ECDSA_VERIFY")?; let pk = &data[PK_OFFSET..Y_IS_ODD_OFFSET]; - let y_is_odd = (data[Y_IS_ODD_OFFSET] >> 56) as u8; + let y_is_odd = data[Y_IS_ODD_OFFSET] as u8; let mut hints = Vec::new(); @@ -60,15 +60,7 @@ pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { let r_words = r.to_words(); let s_words = s.to_words(); - unsafe { - zisklib::secp256k1_ecdsa_verify_c( - &pk[0], - &z_words[0], - &r_words[0], - &s_words[0], - &mut hints, - ); - } + zisklib::secp256k1_ecdsa_verify(&out, &z_words, &r_words, &s_words, &mut hints); Ok(hints) } From 4418b0850d4d8286dd8712acf88fae81293bf2a0 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 12 Jan 2026 20:32:56 +0100 Subject: [PATCH 220/782] Fix client precompile when no prefix and full --- emulator-asm/src/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 946718305..1f6f4bf25 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -2005,6 +2005,9 @@ void client_write_precompile_results (void) // Initialize precompile written address *precompile_written_address = precompile_data_size >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); } else if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) { From ddc6d2060775aa69835cb0c76ced83f672f04e67 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Tue, 13 Jan 2026 10:43:06 +0100 Subject: [PATCH 221/782] fix some merge errors, remove logs. --- pil/src/pil_helpers/traces.rs | 504 +++++++++--------------- pil/zisk.pil | 8 + precompiles/big_int/src/add256.rs | 2 +- precompiles/big_int/src/add256_input.rs | 4 +- precompiles/dma/src/dma_manager.rs | 9 +- ziskos/entrypoint/src/lib.rs | 4 +- 6 files changed, 190 insertions(+), 341 deletions(-) diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index a4b217431..964e1c7c6 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -5,18 +5,18 @@ #![allow(non_upper_case_globals)] #![allow(dead_code)] -use fields::PrimeField64; use proofman_common as common; use proofman_common::GenericTrace; use proofman_common::PackedInfoConst; pub use proofman_macros::trace_row; pub use proofman_macros::values; +use fields::PrimeField64; use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "d2072d6b50a3327861b544ec82fe2e3ba83386f69bc0b044d7b227cac13bdcdf"; +pub const PILOUT_HASH: &str = "2d7998bef15b4649190a77286431f9988c2c47bfa43e76da2969871654dfea59"; pub const MERKLE_TREE_ARITY: u64 = 4; @@ -76,11 +76,13 @@ pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[23]; pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[24]; + //PUBLICS use serde::Deserialize; use serde::Serialize; use serde_arrays; + fn default_array_rom_root() -> [u64; 4] { [0; 4] } @@ -89,28 +91,33 @@ fn default_array_inputs() -> [u64; 64] { [0; 64] } + #[derive(Debug, Serialize, Deserialize)] pub struct ZiskPublics { #[serde(default = "default_array_rom_root", with = "serde_arrays")] pub rom_root: [u64; 4], #[serde(default = "default_array_inputs", with = "serde_arrays")] pub inputs: [u64; 64], + } impl Default for ZiskPublics { fn default() -> Self { - Self { rom_root: [0; 4], inputs: [0; 64] } + Self { + rom_root: [0; 4], + inputs: [0; 64], + } } } values!(ZiskPublicValues { rom_root: [F; 4], inputs: [F; 64], }); - + values!(ZiskProofValues { enable_input_data: F, enable_rom_data: F, enable_dma_64_aligned: F, enable_dma_64_aligned_input: F, enable_dma_unaligned: F, }); - + trace_row!(DmaFixedRow { __L1__: F, }); @@ -121,8 +128,10 @@ trace_row!(DmaTraceRow { }); pub type DmaTrace = GenericTrace, 2097152, 0, 0>; + pub type DmaTracePacked = GenericTrace, 2097152, 0, 0>; + trace_row!(Dma64AlignedFixedRow { __L1__: F, }); @@ -133,8 +142,10 @@ trace_row!(Dma64AlignedTraceRow { }); pub type Dma64AlignedTrace = GenericTrace, 2097152, 0, 1>; + pub type Dma64AlignedTracePacked = GenericTrace, 2097152, 0, 1>; + trace_row!(DmaUnalignedFixedRow { __L1__: F, }); @@ -145,8 +156,10 @@ trace_row!(DmaUnalignedTraceRow { }); pub type DmaUnalignedTrace = GenericTrace, 2097152, 0, 2>; + pub type DmaUnalignedTracePacked = GenericTrace, 2097152, 0, 2>; + trace_row!(DmaPrePostFixedRow { __L1__: F, }); @@ -157,8 +170,10 @@ trace_row!(DmaPrePostTraceRow { }); pub type DmaPrePostTrace = GenericTrace, 2097152, 0, 3>; + pub type DmaPrePostTracePacked = GenericTrace, 2097152, 0, 3>; + trace_row!(MainFixedRow { SEGMENT_L1: F, SEGMENT_STEP: F, __L1__: F, }); @@ -169,8 +184,10 @@ trace_row!(MainTraceRow { }); pub type MainTrace = GenericTrace, 4194304, 0, 4>; + pub type MainTracePacked = GenericTrace, 4194304, 0, 4>; + trace_row!(RomFixedRow { __L1__: F, }); @@ -181,6 +198,7 @@ trace_row!(RomTraceRow { }); pub type RomTrace = GenericTrace, 4194304, 0, 5>; + trace_row!(MemFixedRow { SEGMENT_L1: F, __L1__: F, }); @@ -191,8 +209,10 @@ trace_row!(MemTraceRow { }); pub type MemTrace = GenericTrace, 4194304, 0, 6>; + pub type MemTracePacked = GenericTrace, 4194304, 0, 6>; + trace_row!(RomDataFixedRow { SEGMENT_L1: F, __L1__: F, }); @@ -203,8 +223,10 @@ trace_row!(RomDataTraceRow { }); pub type RomDataTrace = GenericTrace, 2097152, 0, 7>; + pub type RomDataTracePacked = GenericTrace, 2097152, 0, 7>; + trace_row!(InputDataFixedRow { SEGMENT_L1: F, __L1__: F, }); @@ -215,8 +237,10 @@ trace_row!(InputDataTraceRow { }); pub type InputDataTrace = GenericTrace, 2097152, 0, 8>; + pub type InputDataTracePacked = GenericTrace, 2097152, 0, 8>; + trace_row!(MemAlignFixedRow { L1: F, __L1__: F, }); @@ -227,8 +251,10 @@ trace_row!(MemAlignTraceRow { }); pub type MemAlignTrace = GenericTrace, 2097152, 0, 9>; + pub type MemAlignTracePacked = GenericTrace, 2097152, 0, 9>; + trace_row!(MemAlignByteFixedRow { __L1__: F, }); @@ -239,8 +265,10 @@ trace_row!(MemAlignByteTraceRow { }); pub type MemAlignByteTrace = GenericTrace, 4194304, 0, 10>; + pub type MemAlignByteTracePacked = GenericTrace, 4194304, 0, 10>; + trace_row!(MemAlignReadByteFixedRow { __L1__: F, }); @@ -251,8 +279,9 @@ trace_row!(MemAlignReadByteTraceRow { }); pub type MemAlignReadByteTrace = GenericTrace, 4194304, 0, 11>; -pub type MemAlignReadByteTracePacked = - GenericTrace, 4194304, 0, 11>; + +pub type MemAlignReadByteTracePacked = GenericTrace, 4194304, 0, 11>; + trace_row!(MemAlignWriteByteFixedRow { __L1__: F, @@ -264,8 +293,9 @@ trace_row!(MemAlignWriteByteTraceRow { }); pub type MemAlignWriteByteTrace = GenericTrace, 4194304, 0, 12>; -pub type MemAlignWriteByteTracePacked = - GenericTrace, 4194304, 0, 12>; + +pub type MemAlignWriteByteTracePacked = GenericTrace, 4194304, 0, 12>; + trace_row!(ArithFixedRow { __L1__: F, @@ -277,8 +307,10 @@ trace_row!(ArithTraceRow { }); pub type ArithTrace = GenericTrace, 2097152, 0, 13>; + pub type ArithTracePacked = GenericTrace, 2097152, 0, 13>; + trace_row!(BinaryFixedRow { __L1__: F, }); @@ -289,8 +321,10 @@ trace_row!(BinaryTraceRow { }); pub type BinaryTrace = GenericTrace, 4194304, 0, 14>; + pub type BinaryTracePacked = GenericTrace, 4194304, 0, 14>; + trace_row!(BinaryAddFixedRow { __L1__: F, }); @@ -301,8 +335,10 @@ trace_row!(BinaryAddTraceRow { }); pub type BinaryAddTrace = GenericTrace, 4194304, 0, 15>; + pub type BinaryAddTracePacked = GenericTrace, 4194304, 0, 15>; + trace_row!(BinaryExtensionFixedRow { __L1__: F, }); @@ -313,8 +349,9 @@ trace_row!(BinaryExtensionTraceRow { }); pub type BinaryExtensionTrace = GenericTrace, 4194304, 0, 16>; -pub type BinaryExtensionTracePacked = - GenericTrace, 4194304, 0, 16>; + +pub type BinaryExtensionTracePacked = GenericTrace, 4194304, 0, 16>; + trace_row!(Add256FixedRow { __L1__: F, @@ -326,8 +363,10 @@ trace_row!(Add256TraceRow { }); pub type Add256Trace = GenericTrace, 1048576, 0, 17>; + pub type Add256TracePacked = GenericTrace, 1048576, 0, 17>; + trace_row!(ArithEqFixedRow { CLK_0: F, __L1__: F, }); @@ -338,8 +377,10 @@ trace_row!(ArithEqTraceRow { }); pub type ArithEqTrace = GenericTrace, 1048576, 0, 18>; + pub type ArithEqTracePacked = GenericTrace, 1048576, 0, 18>; + trace_row!(ArithEq384FixedRow { CLK_0: F, __L1__: F, }); @@ -350,19 +391,23 @@ trace_row!(ArithEq384TraceRow { }); pub type ArithEq384Trace = GenericTrace, 1048576, 0, 19>; + pub type ArithEq384TracePacked = GenericTrace, 1048576, 0, 19>; + trace_row!(KeccakfFixedRow { CLK_0: F, __L1__: F, }); -pub type KeccakfFixed = GenericTrace, 131072, 0, 16>; +pub type KeccakfFixed = GenericTrace, 131072, 0, 20>; trace_row!(KeccakfTraceRow { in_use_clk_0:bit, in_use:bit, state:[bit; 1600], chunk_acc:[ubit(22); 533], rem_acc:u8, step_addr:ubit(40), }); -pub type KeccakfTrace = GenericTrace, 131072, 0, 16>; +pub type KeccakfTrace = GenericTrace, 131072, 0, 20>; + + +pub type KeccakfTracePacked = GenericTrace, 131072, 0, 20>; -pub type KeccakfTracePacked = GenericTrace, 131072, 0, 16>; trace_row!(Sha256fFixedRow { CLK_0: F, __L1__: F, @@ -374,8 +419,10 @@ trace_row!(Sha256fTraceRow { }); pub type Sha256fTrace = GenericTrace, 262144, 0, 21>; + pub type Sha256fTracePacked = GenericTrace, 262144, 0, 21>; + trace_row!(SpecifiedRangesFixedRow { RANGE: [F; 33], __L1__: F, }); @@ -386,31 +433,35 @@ trace_row!(SpecifiedRangesTraceRow { }); pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 22>; + trace_row!(VirtualTable0FixedRow { UID: [F; 8], column: [F; 43], __L1__: F, }); -pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 19>; +pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 23>; trace_row!(VirtualTable0TraceRow { multiplicity:[F; 8], }); -pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 19>; +pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 23>; + trace_row!(VirtualTable1FixedRow { - UID: [F; 8], column: [F; 64], __L1__: F, + UID: [F; 8], column: [F; 72], __L1__: F, }); -pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 20>; +pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 24>; trace_row!(VirtualTable1TraceRow { multiplicity:[F; 8], }); -pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 20>; +pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 24>; + trace_row!(RomRomTraceRow { line: F, a_offset_imm0: F, a_imm1: F, b_offset_imm0: F, b_imm1: F, ind_width: F, op: F, store_offset: F, jmp_offset1: F, jmp_offset2: F, flags: F, }); pub type RomRomTrace = GenericTrace, 4194304, 0, 5, 0>; + values!(Dma64AlignedAirValues { segment_id: F, segment_previous_seq_end: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count: F, segment_previous_is_mem_eq: F, segment_last_seq_end: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count: F, segment_last_is_mem_eq: F, is_last_segment: F, segment_previous_src64: F, segment_last_src64: F, last_count_chunk: [F; 2], padding_size: F, im_direct: [FieldExtension; 5], }); @@ -560,312 +611,109 @@ values!(VirtualTable1AirGroupValues { }); pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ - ( - 0, - 0, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[1, 24, 1, 9, 22, 7, 3, 22, 7, 3, 36, 1, 1, 1, 1, 3, 9, 3], - }, - ), - ( - 0, - 1, - PackedInfoConst { - is_packed: true, - num_packed_words: 7, - unpack_info: &[36, 29, 32, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 29, 1], - }, - ), - ( - 0, - 2, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[ - 36, 29, 29, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 32, 32, - ], - }, - ), - ( - 0, - 3, - PackedInfoConst { - is_packed: true, - num_packed_words: 7, - unpack_info: &[ - 36, 29, 29, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, - ], - }, - ), - ( - 0, - 4, - PackedInfoConst { - is_packed: true, - num_packed_words: 14, - unpack_info: &[ - 32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, - 64, 1, 64, 64, 1, 32, 38, 38, 38, 32, 32, 1, 1, 1, - ], - }, - ), - ( - 0, - 6, - PackedInfoConst { - is_packed: true, - num_packed_words: 4, - unpack_info: &[29, 38, 1, 1, 38, 1, 32, 32, 1, 40, 18, 18, 1], - }, - ), - ( - 0, - 7, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[29, 38, 1, 1, 32, 32], - }, - ), - ( - 0, - 8, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[29, 38, 1, 1, 16, 16, 16, 16, 1], - }, - ), - ( - 0, - 9, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[ - 29, 3, 4, 1, 8, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 40, 64, 1, - 32, 32, - ], - }, - ), - ( - 0, - 10, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 1, 32, 32, 8], - }, - ), - ( - 0, - 11, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[1, 1, 1, 32, 32, 16, 8, 8, 29, 40], - }, - ), - ( - 0, - 12, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 32, 32], - }, - ), - ( - 0, - 13, - PackedInfoConst { - is_packed: true, - num_packed_words: 17, - unpack_info: &[ - 64, 64, 64, 64, 64, 64, 64, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 1, 1, 1, 1, 1, 1, 1, 64, 64, 64, 1, 1, 1, 1, 1, 64, 8, 32, 1, 7, 7, - ], - }, - ), - ( - 0, - 14, - PackedInfoConst { - is_packed: true, - num_packed_words: 4, - unpack_info: &[ - 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, - ], - }, - ), - ( - 0, - 15, - PackedInfoConst { - is_packed: true, - num_packed_words: 4, - unpack_info: &[32, 32, 32, 32, 16, 16, 16, 16, 1, 1], - }, - ), - ( - 0, - 16, - PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[ - 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 1, 32, 32, - ], - }, - ), - ( - 0, - 17, - PackedInfoConst { - is_packed: true, - num_packed_words: 15, - unpack_info: &[ - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, - 40, 1, 1, - ], - }, - ), - ( - 0, - 18, - PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[ - 16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40, - ], - }, - ), - ( - 0, - 19, - PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[ - 16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, - 1, 1, 64, 64, 64, 64, 64, 64, 40, - ], - }, - ), - ( - 0, - 20, - PackedInfoConst { - is_packed: true, - num_packed_words: 209, - unpack_info: &[ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 8, 40, - ], - }, - ), - ( - 0, - 21, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 40, 1, 1, - ], - }, - ), -]; + (0, 0, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[1, 24, 1, 9, 22, 7, 3, 22, 7, 3, 36, 1, 1, 1, 1, 3, 9, 3], + }), + (0, 1, PackedInfoConst { + is_packed: true, + num_packed_words: 7, + unpack_info: &[36, 29, 32, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 29, 1], + }), + (0, 2, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[36, 29, 29, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 32, 32], + }), + (0, 3, PackedInfoConst { + is_packed: true, + num_packed_words: 7, + unpack_info: &[36, 29, 29, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32], + }), + (0, 4, PackedInfoConst { + is_packed: true, + num_packed_words: 14, + unpack_info: &[32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, 64, 1, 64, 64, 1, 32, 38, 38, 38, 32, 32, 1, 1, 1], + }), + (0, 6, PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[29, 38, 1, 1, 38, 1, 32, 32, 1, 40, 18, 18, 1], + }), + (0, 7, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[29, 38, 1, 1, 32, 32], + }), + (0, 8, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[29, 38, 1, 1, 16, 16, 16, 16, 1], + }), + (0, 9, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[29, 3, 4, 1, 8, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 40, 64, 1, 32, 32], + }), + (0, 10, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 1, 32, 32, 8], + }), + (0, 11, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[1, 1, 1, 32, 32, 16, 8, 8, 29, 40], + }), + (0, 12, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 32, 32], + }), + (0, 13, PackedInfoConst { + is_packed: true, + num_packed_words: 17, + unpack_info: &[64, 64, 64, 64, 64, 64, 64, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 64, 64, 64, 1, 1, 1, 1, 1, 64, 8, 32, 1, 7, 7], + }), + (0, 14, PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1], + }), + (0, 15, PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[32, 32, 32, 32, 16, 16, 16, 16, 1, 1], + }), + (0, 16, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 1, 32, 32], + }), + (0, 17, PackedInfoConst { + is_packed: true, + num_packed_words: 15, + unpack_info: &[32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 40, 1, 1], + }), + (0, 18, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], + }), + (0, 19, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], + }), + (0, 20, PackedInfoConst { + is_packed: true, + num_packed_words: 209, + unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 8, 40], + }), + (0, 21, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 40, 1, 1], + }), +]; \ No newline at end of file diff --git a/pil/zisk.pil b/pil/zisk.pil index c28c70e4d..10778d7a3 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -2,7 +2,9 @@ require "std_direct.pil" require "operations.pil" require "opids.pil" +require "config.pil" +require "dma/pil/dual_range.pil" require "rom/pil/rom.pil" require "main/pil/main.pil" require "mem/pil/mem.pil" @@ -18,6 +20,12 @@ require "arith_eq/pil/arith_eq.pil" require "arith_eq_384/pil/arith_eq_384.pil" require "keccakf/pil/keccakf.pil" require "sha256f/pil/sha256f.pil" +require "dma/pil/dma.pil" +require "dma/pil/dma_rom.pil" +require "dma/pil/dma_pre_post.pil" +require "dma/pil/dma_pre_post_table.pil" +require "dma/pil/dma_64_aligned.pil" +require "dma/pil/dma_unaligned.pil" proofval enable_input_data; enable_input_data * (1 - enable_input_data); diff --git a/precompiles/big_int/src/add256.rs b/precompiles/big_int/src/add256.rs index b3418636d..c339491d7 100644 --- a/precompiles/big_int/src/add256.rs +++ b/precompiles/big_int/src/add256.rs @@ -62,9 +62,9 @@ impl Add256SM { trace: &mut Add256TraceRowType, multiplicities: &mut [u32], ) { + debug_assert!(input.cin < 2); trace.set_cin(input.cin != 0); let mut cout_2 = input.cin as u32; - for i in 0..4 { let al = input.a[i] as u32; let ah = (input.a[i] >> 32) as u32; diff --git a/precompiles/big_int/src/add256_input.rs b/precompiles/big_int/src/add256_input.rs index b50d12e7a..746b58593 100644 --- a/precompiles/big_int/src/add256_input.rs +++ b/precompiles/big_int/src/add256_input.rs @@ -21,8 +21,8 @@ impl Add256Input { addr_main: values[B] as u32, addr_a: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as u32, addr_b: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] as u32, - addr_c: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2] as u32, - cin: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 3], + cin: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2], + addr_c: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 3] as u32, a: values[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(), b: values[START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] .try_into() diff --git a/precompiles/dma/src/dma_manager.rs b/precompiles/dma/src/dma_manager.rs index 19bc0f8af..2194b7686 100644 --- a/precompiles/dma/src/dma_manager.rs +++ b/precompiles/dma/src/dma_manager.rs @@ -78,22 +78,15 @@ impl ComponentBuilder for DmaManager { /// # Panics /// Panics if the provided `air_id` is not supported. fn build_instance(&self, ictx: InstanceCtx) -> Box> { - println!("BUILD DMA INSTANCE {}", ictx.plan.air_id); match ictx.plan.air_id { - DmaTrace::::AIR_ID => { - println!("BUILD DMA INSTANCE"); - Box::new(DmaInstance::new(self.dma_sm.clone(), ictx)) - } + DmaTrace::::AIR_ID => Box::new(DmaInstance::new(self.dma_sm.clone(), ictx)), DmaPrePostTrace::::AIR_ID => { - println!("BUILD DMA PRE-POST INSTANCE"); Box::new(DmaPrePostInstance::new(self.dma_pre_post_sm.clone(), ictx)) } Dma64AlignedTrace::::AIR_ID => { - println!("BUILD DMA 64-ALIGNED INSTANCE"); Box::new(Dma64AlignedInstance::new(self.dma_64_aligned_sm.clone(), ictx)) } DmaUnalignedTrace::::AIR_ID => { - println!("BUILD DMA UNALIGNED INSTANCE"); Box::new(DmaUnalignedInstance::new(self.dma_unaligned_sm.clone(), ictx)) } _ => { diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 530a898ab..ed9e0a696 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -286,7 +286,7 @@ mod ziskos { ptr } - core::arch::global_asm!(include_str!("dma/memcpy.s")); - core::arch::global_asm!(include_str!("dma/memmove.s")); + // core::arch::global_asm!(include_str!("dma/memcpy.s")); + // core::arch::global_asm!(include_str!("dma/memmove.s")); // core::arch::global_asm!(include_str!("dma/memcmp.s")); } From 126cdd2e2488d3fa19c10ae118ab70fa965e4d4b Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 13 Jan 2026 11:27:47 +0100 Subject: [PATCH 222/782] Fix typo in is_on_subgroup_twist_bn254_hint() --- ziskos-hints/src/handlers/bn254.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 3a4986d77..4e8884c81 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -14,6 +14,8 @@ pub fn is_on_curve_bn254_hint(data: &[u64]) -> Result, String> { zisklib::is_on_curve_bn254(p, &mut processed_hints); + //println!("is_on_curve_bn254_hint() processed_hints len={:x}", processed_hints.len()); + Ok(processed_hints) } @@ -31,6 +33,8 @@ pub fn to_affine_bn254_hint(data: &[u64]) -> Result, String> { zisklib::to_affine_bn254(p, &mut processed_hints); + //println!("to_affine_bn254_hint() processed_hints len={:x}", processed_hints.len()); + Ok(processed_hints) } @@ -49,6 +53,8 @@ pub fn add_bn254_hint(data: &[u64]) -> Result, String> { zisklib::add_bn254(p1, p2, &mut processed_hints); + //println!("add_bn254_hint() processed_hints len={:x}", processed_hints.len()); + Ok(processed_hints) } @@ -67,6 +73,8 @@ pub fn mul_bn254_hint(data: &[u64]) -> Result, String> { zisklib::mul_bn254(p, k, &mut processed_hints); + //println!("mul_bn254_hint() processed_hints len={:x}", processed_hints.len()); + Ok(processed_hints) } @@ -84,6 +92,8 @@ pub fn to_affine_twist_bn254_hint(data: &[u64]) -> Result, String> { zisklib::to_affine_twist_bn254(p, &mut processed_hints); + //println!("to_affine_twist_bn254_hint() processed_hints len={:x}", processed_hints.len()); + Ok(processed_hints) } @@ -101,6 +111,8 @@ pub fn is_on_curve_twist_bn254_hint(data: &[u64]) -> Result, String> { zisklib::is_on_curve_twist_bn254(p, &mut processed_hints); + //println!("is_on_curve_twist_bn254_hint() processed_hints len={:x}", processed_hints.len()); + Ok(processed_hints) } @@ -116,7 +128,9 @@ pub fn is_on_subgroup_twist_bn254_hint(data: &[u64]) -> Result, String> let mut processed_hints = Vec::new(); - zisklib::is_on_curve_twist_bn254(p, &mut processed_hints); + zisklib::is_on_subgroup_twist_bn254(p, &mut processed_hints); + + //println!("is_on_subgroup_twist_bn254_hint() processed_hints len={:x}", processed_hints.len()); Ok(processed_hints) } @@ -169,5 +183,7 @@ pub fn pairing_batch_bn254_hint(data: &[u64]) -> Result, String> { zisklib::pairing_batch_bn254(g1_points, g2_points, &mut processed_hints); + //println!("pairing_batch_bn254_hint() processed_hints len={:x}", processed_hints.len()); + Ok(processed_hints) } From 82f77c4fafa04b9da602774a53cec7f11544f2b6 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 13 Jan 2026 14:42:09 +0100 Subject: [PATCH 223/782] Implement Secp256k1EcdsaVerifyCtx in fcall --- lib-c/c/src/ec/ec.cpp | 87 +++++++++++++++++++++++++++++++++++++ lib-c/c/src/ec/ec.hpp | 15 +++++++ lib-c/c/src/fcall/fcall.cpp | 15 +++++++ lib-c/c/src/fcall/fcall.hpp | 4 ++ 4 files changed, 121 insertions(+) diff --git a/lib-c/c/src/ec/ec.cpp b/lib-c/c/src/ec/ec.cpp index b25f59113..6cc92b70a 100644 --- a/lib-c/c/src/ec/ec.cpp +++ b/lib-c/c/src/ec/ec.cpp @@ -182,6 +182,93 @@ int AddPointEcP (uint64_t _dbl, const uint64_t * p1, const uint64_t * p2, uint64 return result; } +uint64_t G[8] = { + 0x59F2815B16F81798, + 0x029BFCDB2DCE28D9, + 0x55A06295CE870B07, + 0x79BE667EF9DCBBAC, + 0x9C47D08FFB10D4B8, + 0xFD17B448A6855419, + 0x5DA4FBFC0E1108A8, + 0x483ADA7726A3C465, +}; + +int secp256k1_ecdsa_verify ( + const uint64_t * pk, // 8 x 64 bits + const uint64_t * _z, // 4 x 64 bits + const uint64_t * _r, // 4 x 64 bits + const uint64_t * _s, // 4 x 64 bits + uint64_t * result // 8 x 64 bits +) +{ + // Convert z, r, s inputs to field elements + RawFec::Element z, r, s; + array2fe(_z, z); + array2fe(_r, r); + array2fe(_s, s); + + // Given the public key pk and the signature (r, s) over the message hash z: + // 1. Computes s_inv = s⁻¹ mod n + // 2. Computes u1 = z·s_inv mod n + // 3. Computes u2 = r·s_inv mod n + // 4. Computes and returns the curve point p = u1·G + u2·PK + + // s_inv = s⁻¹ mod n + RawFec::Element s_inv; + fec.inv(s_inv, s); + + // u1 = z·s_inv mod n + RawFec::Element u1; + fec.mul(u1, z, s_inv); + + // u2 = r·s_inv mod n + RawFec::Element u2; + fec.mul(u2, r, s_inv); + + uint64_t u1_array[4]; + uint64_t u2_array[4]; + fe2array(u1, u1_array); + fe2array(u2, u2_array); + + secp256k1_curve_dbl_scalar_mul(u1_array, G, u2_array, pk, result); + + return 0; +} + +int secp256k1_curve_dbl_scalar_mul( + const uint64_t * k1, // 4 x 64 bits + const uint64_t * p1, // 8 x 64 bits + const uint64_t * k2, // 4 x 64 bits + const uint64_t * p2, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +) +{ + for (uint64_t i = 0; i < 8; i++) { + r[i] = 0; + } + + for (int64_t ii=255; ii>=0; ii--) { + uint64_t i = ii; + + // r = r + r + AddPointEcDbl(r, r); + + // If k1[i] == 1 then r = r + p1 + uint64_t k1_bit = (k1[i / 64] >> (i % 64)) & 1; + if (k1_bit == 1) { + AddPointEcP(0, r, p1, r); + } + + // If k2[i] == 1 then r = r + p2 + uint64_t k2_bit = (k2[i / 64] >> (i % 64)) & 1; + if (k2_bit == 1) { + AddPointEcP(0, r, p2, r); + } + } + + return 0; +} + #ifdef __cplusplus } // extern "C" #endif \ No newline at end of file diff --git a/lib-c/c/src/ec/ec.hpp b/lib-c/c/src/ec/ec.hpp index cec4e836d..ef0745d50 100644 --- a/lib-c/c/src/ec/ec.hpp +++ b/lib-c/c/src/ec/ec.hpp @@ -24,6 +24,21 @@ int AddPointEcP ( uint64_t * p3 // 8 x 64 bits ); +int secp256k1_ecdsa_verify ( + const uint64_t * pk, // 8 x 64 bits + const uint64_t * z, // 4 x 64 bits + const uint64_t * r, // 4 x 64 bits + const uint64_t * s, // 4 x 64 bits + uint64_t * result // 8 x 64 bits +); + +int secp256k1_curve_dbl_scalar_mul( + const uint64_t * k1, // 4 x 64 bits + const uint64_t * p1, // 8 x 64 bits + const uint64_t * k2, // 4 x 64 bits + const uint64_t * p2, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +); #ifdef __cplusplus } // extern "C" diff --git a/lib-c/c/src/fcall/fcall.cpp b/lib-c/c/src/fcall/fcall.cpp index e463e5256..dc6f02587 100644 --- a/lib-c/c/src/fcall/fcall.cpp +++ b/lib-c/c/src/fcall/fcall.cpp @@ -3,6 +3,7 @@ #include "../bn254/bn254_fe.hpp" #include "../bls12_381/bls12_381_fe.hpp" #include "../bls12_381/bls12_381.hpp" +#include "../ec/ec.hpp" #include #include @@ -104,6 +105,11 @@ int Fcall ( iresult = BLS12_381Fp2SqrtCtx(ctx); break; } + case FCALL_SECP256K1_ECDSA_VERIFY_ID: + { + iresult = Secp256k1EcdsaVerifyCtx(ctx); + break; + } default: { printf("Fcall() found unsupported function_id=%lu\n", ctx->function_id); @@ -1016,5 +1022,14 @@ int BLS12_381Fp2SqrtCtx ( if (result != 0) return result; } + return 0; +} + +int Secp256k1EcdsaVerifyCtx( + struct FcallContext * ctx // fcall context +) +{ + secp256k1_ecdsa_verify( &ctx->params[0], &ctx->params[8], &ctx->params[12], &ctx->params[16], &ctx->result[0]); + ctx->result_size = 8; return 0; } \ No newline at end of file diff --git a/lib-c/c/src/fcall/fcall.hpp b/lib-c/c/src/fcall/fcall.hpp index b9701abe5..5464bde09 100644 --- a/lib-c/c/src/fcall/fcall.hpp +++ b/lib-c/c/src/fcall/fcall.hpp @@ -26,6 +26,7 @@ extern "C" { #define FCALL_BIG_INT_DIV_ID 17 #define FCALL_BIN_DECOMP_ID 18 #define FCALL_BLS12_381_FP2_SQRT_ID 19 +#define FCALL_SECP256K1_ECDSA_VERIFY_ID 20 #define FCALL_PARAMS_MAX_SIZE 386 #define FCALL_RESULT_MAX_SIZE 8193 @@ -102,6 +103,9 @@ int BinDecompCtx ( int BLS12_381Fp2SqrtCtx ( struct FcallContext * ctx // fcall context ); +int Secp256k1EcdsaVerifyCtx ( + struct FcallContext * ctx // fcall context +); // Functions supported by fcall, in u64 array format int InverseFpEc ( From 41c2c4129c1f77d325b44229cc2400c97e79e8b2 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:02:36 +0000 Subject: [PATCH 224/782] wip ecrecover hint --- ziskos-hints/src/handlers/secp256k1.rs | 36 +++++++------------ .../zisklib/fcalls_impl/secp256k1/ecdsa.rs | 2 +- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index dd3ca268b..770877032 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -1,5 +1,7 @@ use elliptic_curve::FieldBytesEncoding; +use elliptic_curve::PrimeField; use k256::ecdsa::Signature; +use k256::Secp256k1; use k256::U256; use crate::handlers::validate_hint_length; @@ -18,32 +20,19 @@ use crate::zisklib; /// * `Err` - If the data length is invalid #[inline] pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { - hint_fields![PK: 4, Y_IS_ODD:1, Z: 4, SIG: 8]; + hint_fields![X_Y: 8, INFINITY:1, Z: 4, SIG: 8]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_ECDSA_VERIFY")?; - let pk = &data[PK_OFFSET..Y_IS_ODD_OFFSET]; - let y_is_odd = data[Y_IS_ODD_OFFSET] as u8; - - let mut hints = Vec::new(); - - let mut out: [u64; 8] = [0; 8]; - unsafe { - zisklib::secp256k1_decompress_c( - &pk[0] as *const u64 as *const u8, - y_is_odd, - &mut out[0], - &mut hints, - ); + // If the point is at infinity, return an error + if data[INFINITY_OFFSET] != 0 { + return Err("Error in secp256k1_ecdsa_verify: point at infinity".to_string()); } - // Convert u64 slice to byte slice - // let byte_data = unsafe { slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; + let pk = unsafe { &*(data[X_Y_OFFSET] as *const [u64; X_Y_SIZE]) }; // Extract z (32 bytes), and sig (64 bytes) - let z = &data[Z_OFFSET..SIG_OFFSET]; - let z_bytes: &[u8; 32] = unsafe { &*(z.as_ptr() as *const [u8; 32]) }; - + let z_bytes: &[u8; 32] = unsafe { &*(data[Z_OFFSET..SIG_OFFSET].as_ptr() as *const [u8; 32]) }; let z_dec: U256 = U256::decode_field_bytes(z_bytes.into()); let z_words = z_dec.to_words(); @@ -54,13 +43,14 @@ pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { .map_err(|e| format!("Failed to parse signature: {}", e))?; // Extract r and s as Scalars and convert to U256 - let (r_scalar, s_scalar) = sig.split_scalars(); - let r: U256 = U256::decode_field_bytes(&r_scalar.to_bytes()); - let s: U256 = U256::decode_field_bytes(&s_scalar.to_bytes()); + let r = >::decode_field_bytes(&sig.r().to_repr()); + let s = >::decode_field_bytes(&sig.s().to_repr()); let r_words = r.to_words(); let s_words = s.to_words(); - zisklib::secp256k1_ecdsa_verify(&out, &z_words, &r_words, &s_words, &mut hints); + let mut hints = Vec::new(); + + zisklib::secp256k1_ecdsa_verify(pk, &z_words, &r_words, &s_words, &mut hints); Ok(hints) } diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs index 2c141bb52..7c6661bc4 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256k1/ecdsa.rs @@ -22,7 +22,7 @@ pub fn fcall_secp256k1_ecdsa_verify(params: &[u64], results: &mut [u64]) -> i64 8 } -fn secp256k1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u64; 4]) -> [u64; 8] { +pub fn secp256k1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u64; 4]) -> [u64; 8] { // Given the public key pk and the signature (r, s) over the message hash z: // 1. Computes s_inv = s⁻¹ mod n // 2. Computes u1 = z·s_inv mod n From e84fbc79324d38f1d3a6258245049c8b1c3936ec Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:47:25 +0000 Subject: [PATCH 225/782] fix mem address alignment --- ziskos-hints/src/handlers/secp256k1.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 770877032..78855bd09 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -29,7 +29,7 @@ pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { return Err("Error in secp256k1_ecdsa_verify: point at infinity".to_string()); } - let pk = unsafe { &*(data[X_Y_OFFSET] as *const [u64; X_Y_SIZE]) }; + let pk: &[u64; X_Y_SIZE] = data[X_Y_OFFSET..X_Y_OFFSET + X_Y_SIZE].try_into().unwrap(); // Extract z (32 bytes), and sig (64 bytes) let z_bytes: &[u8; 32] = unsafe { &*(data[Z_OFFSET..SIG_OFFSET].as_ptr() as *const [u8; 32]) }; From 33b549db0f719a982f18b5e0c6ae16b98fa3da61 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 14 Jan 2026 09:41:43 +0100 Subject: [PATCH 226/782] update limit on mem count and plan --- Cargo.lock | 2 +- state-machines/mem-cpp/cpp/mem_config.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 916fc34ad..12daf28a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2955,7 +2955,7 @@ dependencies = [ [[package]] name = "precomp-dma" -version = "0.15.0" +version = "0.16.0" dependencies = [ "fields", "generic-array", diff --git a/state-machines/mem-cpp/cpp/mem_config.hpp b/state-machines/mem-cpp/cpp/mem_config.hpp index dba4e9b0b..46dd7e63d 100644 --- a/state-machines/mem-cpp/cpp/mem_config.hpp +++ b/state-machines/mem-cpp/cpp/mem_config.hpp @@ -28,7 +28,7 @@ #define ROM_ROWS (1 << 21) #define INPUT_ROWS (1 << 21) #define MEM_ROWS (1 << 22) -#define MAX_CHUNKS 8192 // 2^13 * 2^18 = 2^31 +#define MAX_CHUNKS (1 << 18) // 2^36 / 2^18 = 2^18 // THREAD_BITS >= 1 #define THREAD_BITS 2 From e6e0dffe01593a9036f0a15f516e2c76ce8f15ec Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 14 Jan 2026 15:25:17 +0100 Subject: [PATCH 227/782] fix problem with mem range check for large steps --- pil/src/pil_helpers/traces.rs | 10 ++-- pil/zisk.pil | 2 + .../mem-common/src/mem_constants.rs | 5 -- state-machines/mem/pil/mem.pil | 13 ++-- state-machines/mem/src/mem_sm.rs | 59 ++++++++----------- ziskos/entrypoint/src/lib.rs | 4 +- 6 files changed, 41 insertions(+), 52 deletions(-) diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 964e1c7c6..e3f040982 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "2d7998bef15b4649190a77286431f9988c2c47bfa43e76da2969871654dfea59"; +pub const PILOUT_HASH: &str = "b7f913482b89b755b4830f0fe02af1f8c04fe783376b10489324799d42021761"; pub const MERKLE_TREE_ARITY: u64 = 4; @@ -205,7 +205,7 @@ trace_row!(MemFixedRow { pub type MemFixed = GenericTrace, 4194304, 0, 6>; trace_row!(MemTraceRow { - addr:ubit(29), step:ubit(38), sel:bit, addr_changes:bit, step_dual:ubit(38), sel_dual:bit, value:[u32; 2], wr:bit, previous_step:ubit(40), increment:[ubit(18); 2], read_same_addr:bit, + addr:ubit(29), step:ubit(38), sel:bit, addr_changes:bit, step_dual:ubit(38), sel_dual:bit, value:[u32; 2], wr:bit, previous_step:ubit(40), l_increment:ubit(22), h_increment:u16, read_same_addr:bit, }); pub type MemTrace = GenericTrace, 4194304, 0, 6>; @@ -424,12 +424,12 @@ pub type Sha256fTracePacked = GenericTrace, 262144, trace_row!(SpecifiedRangesFixedRow { - RANGE: [F; 33], __L1__: F, + RANGE: [F; 32], __L1__: F, }); pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 22>; trace_row!(SpecifiedRangesTraceRow { - mul:[F; 33], + mul:[F; 32], }); pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 22>; @@ -639,7 +639,7 @@ pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ (0, 6, PackedInfoConst { is_packed: true, num_packed_words: 4, - unpack_info: &[29, 38, 1, 1, 38, 1, 32, 32, 1, 40, 18, 18, 1], + unpack_info: &[29, 38, 1, 1, 38, 1, 32, 32, 1, 40, 22, 16, 1], }), (0, 7, PackedInfoConst { is_packed: true, diff --git a/pil/zisk.pil b/pil/zisk.pil index 10778d7a3..a39ecf9d8 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -27,6 +27,8 @@ require "dma/pil/dma_pre_post_table.pil" require "dma/pil/dma_64_aligned.pil" require "dma/pil/dma_unaligned.pil" +enable_range_stats(); + proofval enable_input_data; enable_input_data * (1 - enable_input_data); diff --git a/state-machines/mem-common/src/mem_constants.rs b/state-machines/mem-common/src/mem_constants.rs index 8fd746c85..0920d6a2f 100644 --- a/state-machines/mem-common/src/mem_constants.rs +++ b/state-machines/mem-common/src/mem_constants.rs @@ -32,8 +32,3 @@ pub const MAX_MEM_ADDR: u64 = 0xFFFF_FFFF; pub const SEGMENT_ADDR_MAX_RANGE: usize = (1 << 24) - 1; pub const SEGMENT_LARGE_ADDR_C_MAX_RANGE: usize = (1 << 16) - 1; - -pub const MEM_INC_C_BITS: usize = 18; -pub const MEM_INC_C_SIZE: usize = 1 << MEM_INC_C_BITS; -pub const MEM_INC_C_MAX_RANGE: usize = MEM_INC_C_SIZE - 1; -pub const MEM_INC_C_MASK: usize = MEM_INC_C_SIZE - 1; diff --git a/state-machines/mem/pil/mem.pil b/state-machines/mem/pil/mem.pil index bac82d9df..d7baa9076 100644 --- a/state-machines/mem/pil/mem.pil +++ b/state-machines/mem/pil/mem.pil @@ -370,18 +370,19 @@ airtemplate Mem(const int N = 2**21, const int id = MEMORY_ID, const int RC = 2, } const expr delta_step = step - previous_step + (1 - wr); - col witness bits(18) air.increment[2]; - increment[0] + 2**18 * increment[1] + 1 === addr_changes * (delta_addr - delta_step) + delta_step; + col witness bits(22) air.l_increment; + col witness bits(16) air.h_increment; + l_increment + 2**22 * h_increment + 1 === addr_changes * (delta_addr - delta_step) + delta_step; is_first_segment * SEGMENT_L1 * (1 - addr_changes) === 0; - // addr_change == 1 => [0,2^19-1] => 19 + 19 = 38 bits + 3 => 41 bits => 2 TB (limit address 32 bits) - // addr_change == 0 => [0,2^19-1] => 19 + 19 = 38 bits => 2 slots => 2^37 => 128 GB steps (32K main instances) + // addr_change == 1 => 22 + 16 = 38 bits + 3 => 41 bits => 2 TB (limit address 32 bits) + // addr_change == 0 => 22 + 16 = 38 bits => 4 slots => 2^36 => 32 GB steps (16K main instances) // 38 bits x 2^22 = 2^60 + 2^32 address = 2^61 secure. // using control in the middle of instance, reduce to 2^60 - range_check(expression: increment[0], min: 0, max: 2**18 - 1); - range_check(expression: increment[1], min: 0, max: 2**18 - 1); + range_check(expression: l_increment, min: 0, max: 2**22 - 1); + range_check(expression: h_increment, min: 0, max: 2**16 - 1); // to avoid intermediate column col witness bits(1) air.read_same_addr; diff --git a/state-machines/mem/src/mem_sm.rs b/state-machines/mem/src/mem_sm.rs index 8d1d0ccbc..fed056328 100644 --- a/state-machines/mem/src/mem_sm.rs +++ b/state-machines/mem/src/mem_sm.rs @@ -20,10 +20,7 @@ use std::{ use crate::{MemInput, MemModule}; use fields::PrimeField64; -use mem_common::{ - MemHelpers, MEM_INC_C_BITS, MEM_INC_C_MASK, MEM_INC_C_MAX_RANGE, MEM_INC_C_SIZE, - RAM_W_ADDR_END, RAM_W_ADDR_INIT, -}; +use mem_common::{MemHelpers, RAM_W_ADDR_END, RAM_W_ADDR_INIT}; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use zisk_core::{RAM_ADDR, RAM_SIZE}; @@ -35,10 +32,8 @@ pub struct MemSM { /// PIL2 standard library std: Arc>, - range_id: usize, - + range_22bits_id: usize, dual_range_id: usize, - range_16bits_id: usize, } #[derive(Debug, Default)] @@ -51,14 +46,14 @@ pub struct MemPreviousSegment { #[allow(unused, unused_variables)] impl MemSM { pub fn new(std: Arc>) -> Arc { - let range_id = - std.get_range_id(0, MEM_INC_C_MAX_RANGE as i64, None).expect("Failed to get range ID"); + let range_22bits_id = + std.get_range_id(0, (1 << 22) - 1, None).expect("Failed to get 22 bits range ID"); let dual_range_id = std.get_range_id(0, DUAL_RANGE_MAX as i64, None).expect("Failed to get dual range ID"); let range_16bits_id = - std.get_range_id(0, 0xFFFF, None).expect("Failed to get 16 bits range ID"); + std.get_range_id(0, (1 << 16) - 1, None).expect("Failed to get 16 bits range ID"); - Arc::new(Self { range_id, dual_range_id, range_16bits_id, std: std.clone() }) + Arc::new(Self { range_22bits_id, dual_range_id, range_16bits_id, std: std.clone() }) } pub fn get_to_addr() -> u32 { @@ -114,7 +109,8 @@ impl MemModule for MemSM { ) -> ProofmanResult> { let mut trace = MemTraceType::::new_from_vec(trace_buffer)?; - let mut range_check_data: Vec = vec![0; MEM_INC_C_SIZE]; + let mut range_22bits: Vec = vec![0; 1 << 22]; + let mut range_16bits: Vec = vec![0; 1 << 16]; // 2^20 * 2 = 2^21 = 2MB let mut dual_partial_range: Vec = vec![0; DUAL_PARTIAL_RANGE_MAX]; @@ -133,13 +129,6 @@ impl MemModule for MemSM { for index in 0..mem_op_count { let mem_op = &mem_ops[index]; step = mem_op.step; - // if step >= 28184622 && step <= 28184624 { - // println!( - // "@@@@@@@@@@ 0x{:08X} {step} 8 OP:{}", - // mem_op.addr * 8, - // if mem_op.is_write { 2 } else { 1 } - // ); - // } let addr_changes = last_addr != mem_op.addr; if dual_candidate { @@ -224,10 +213,10 @@ impl MemModule for MemSM { } else { trace[i].set_read_same_addr(true); } - let lsb_increment = increment & MEM_INC_C_MASK; - let msb_increment = increment >> MEM_INC_C_BITS; - trace[i].set_increment(0, lsb_increment as u32); - trace[i].set_increment(1, msb_increment as u32); + let l_increment = increment & ((1 << 22) - 1); + let h_increment = increment >> 22; + trace[i].set_l_increment(l_increment as u32); + trace[i].set_h_increment(h_increment as u16); trace[i].set_wr(mem_op.is_write); #[cfg(feature = "debug_mem")] @@ -236,8 +225,8 @@ impl MemModule for MemSM { increment, i, addr_changes as u8, mem_op.addr, last_addr, mem_op.step, last_step); } - range_check_data[lsb_increment] += 1; - range_check_data[msb_increment] += 1; + range_22bits[l_increment] += 1; + range_16bits[h_increment] += 1; last_addr = mem_op.addr; last_value = mem_op.value; @@ -275,8 +264,8 @@ impl MemModule for MemSM { trace[i].set_value(1, high_value); trace[i].set_addr_changes(false); - trace[i].set_increment(0, 0); - trace[i].set_increment(1, 0); + trace[i].set_h_increment(0); + trace[i].set_l_increment(0); trace[i].set_read_same_addr(true); trace[i].set_sel_dual(false); trace[i].set_step_dual(0); @@ -284,15 +273,14 @@ impl MemModule for MemSM { if padding_size > 0 { // Store the padding range checks - range_check_data[0] += (2 * padding_size) as u32; + range_16bits[0] += padding_size as u32; + range_22bits[0] += padding_size as u32; } // no add extra +1 because index = value - 1 // RAM_W_ADDR_END - last_addr + 1 - 1 = RAM_W_ADDR_END - last_addr let distance_end = RAM_W_ADDR_END - last_addr; - self.std.range_checks(self.range_id, range_check_data); - // Add one in range_check_data_max because it's used by intermediate reads, and reads // add one to distance to allow same step on read operations. @@ -320,10 +308,13 @@ impl MemModule for MemSM { air_values.distance_end[0] = F::from_u16(distance_end[0]); air_values.distance_end[1] = F::from_u16(distance_end[1]); - self.std.range_check(self.range_16bits_id, distance_base[0] as i64, 1); - self.std.range_check(self.range_16bits_id, distance_base[1] as i64, 1); - self.std.range_check(self.range_16bits_id, distance_end[0] as i64, 1); - self.std.range_check(self.range_16bits_id, distance_end[1] as i64, 1); + range_16bits[distance_base[0] as usize] += 1; + range_16bits[distance_base[1] as usize] += 1; + range_16bits[distance_end[0] as usize] += 1; + range_16bits[distance_end[1] as usize] += 1; + + self.std.range_checks(self.range_22bits_id, range_22bits); + self.std.range_checks(self.range_16bits_id, range_16bits); for (value, count) in dual_partial_range.iter().enumerate() { if *count == 0 { diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index ed9e0a696..530a898ab 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -286,7 +286,7 @@ mod ziskos { ptr } - // core::arch::global_asm!(include_str!("dma/memcpy.s")); - // core::arch::global_asm!(include_str!("dma/memmove.s")); + core::arch::global_asm!(include_str!("dma/memcpy.s")); + core::arch::global_asm!(include_str!("dma/memmove.s")); // core::arch::global_asm!(include_str!("dma/memcmp.s")); } From a8c17f4754b3060e1962bf6f97f27b62f9e2eb0c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:02:23 +0000 Subject: [PATCH 228/782] Add support for QUIC protocol to transmit hints to the coordinator in distributed mode --- Cargo.lock | 1 + common/src/io/stream/quic.rs | 61 +++++-- distributed/crates/worker/src/worker_node.rs | 4 +- precompiles/hints/Cargo.toml | 13 +- .../hints/src/bin/hints_quic_server.rs | 159 ++++++++++++++++++ precompiles/hints/src/hints_processor.rs | 1 - 6 files changed, 215 insertions(+), 24 deletions(-) create mode 100644 precompiles/hints/src/bin/hints_quic_server.rs diff --git a/Cargo.lock b/Cargo.lock index 9a8c196ad..e53df17c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3210,6 +3210,7 @@ dependencies = [ "lib-c", "precompiles-helpers", "rayon", + "rustls", "tracing", "zisk-common", "ziskos-hints", diff --git a/common/src/io/stream/quic.rs b/common/src/io/stream/quic.rs index 7ade68a14..eb2810f99 100644 --- a/common/src/io/stream/quic.rs +++ b/common/src/io/stream/quic.rs @@ -7,18 +7,44 @@ use std::sync::Arc; use anyhow::{Context, Result}; use quinn::{Connection, Endpoint, ServerConfig}; -use tokio::runtime::Runtime; +use tokio::runtime::{Handle, Runtime}; use super::{StreamRead, StreamWrite}; +/// Helper to run async code, either using current runtime or creating one +fn run_async(f: F) -> Result +where + F: std::future::Future> + Send + 'static, + T: Send + 'static, +{ + // Try to use current runtime handle if we're already in a tokio context + match Handle::try_current() { + Ok(handle) => { + // We're in a tokio runtime, use block_in_place to allow blocking + tokio::task::block_in_place(move || handle.block_on(f)) + } + Err(_) => { + // Not in a runtime, create a temporary one + let rt = Runtime::new().context("Failed to create tokio runtime")?; + rt.block_on(f) + } + } +} + +/// Ensure crypto provider is initialized (idempotent) +fn ensure_crypto_provider() { + use std::sync::Once; + static INIT: Once = Once::new(); + INIT.call_once(|| { + let _ = rustls::crypto::ring::default_provider().install_default(); + }); +} + /// A QUIC implementation of StreamRead that receives data over QUIC streams. pub struct QuicStreamReader { /// The QUIC connection connection: Option, - /// Tokio runtime for async operations - runtime: Arc, - /// Client endpoint endpoint: Option, @@ -31,14 +57,11 @@ impl QuicStreamReader { /// /// This creates a client endpoint that connects to the server to read data. pub fn new(server_addr: SocketAddr) -> Result { - let runtime = Arc::new( - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .context("Failed to create tokio runtime")?, - ); + // Ensure crypto provider is initialized + ensure_crypto_provider(); - Ok(QuicStreamReader { connection: None, runtime, endpoint: None, server_addr }) + // We don't need to store a runtime anymore since we'll use run_async helper + Ok(QuicStreamReader { connection: None, endpoint: None, server_addr }) } } @@ -51,7 +74,8 @@ impl StreamRead for QuicStreamReader { return Ok(()); } - let (endpoint, connection) = self.runtime.block_on(async { + let server_addr = self.server_addr; + let (endpoint, connection) = run_async(async move { let mut endpoint = Endpoint::client("0.0.0.0:0".parse().unwrap())?; // Configure to accept self-signed certificates (for development) @@ -73,7 +97,7 @@ impl StreamRead for QuicStreamReader { endpoint.set_default_client_config(client_config); let connection = endpoint - .connect(self.server_addr, "localhost")? + .connect(server_addr, "localhost")? .await .context("Failed to connect to server")?; @@ -96,9 +120,10 @@ impl StreamRead for QuicStreamReader { let connection = self .connection .as_ref() - .ok_or_else(|| anyhow::anyhow!("QuicStreamReader: Connection not established"))?; + .ok_or_else(|| anyhow::anyhow!("QuicStreamReader: Connection not established"))? + .clone(); - self.runtime.block_on(async { + run_async(async move { // Accept next unidirectional stream let mut recv = match connection.accept_uni().await { Ok(stream) => stream, @@ -128,8 +153,9 @@ impl StreamRead for QuicStreamReader { connection.close(0u32.into(), b"closing"); } if let Some(endpoint) = self.endpoint.take() { - self.runtime.block_on(async { + let _ = run_async(async move { endpoint.wait_idle().await; + Ok::<_, anyhow::Error>(()) }); } Ok(()) @@ -161,6 +187,9 @@ impl QuicStreamWriter { /// /// This creates a server endpoint that waits for incoming reader connections. pub fn new(bind_addr: SocketAddr) -> Result { + // Ensure crypto provider is initialized + ensure_crypto_provider(); + let runtime = Arc::new( tokio::runtime::Builder::new_multi_thread() .enable_all() diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 3d1ca9168..196efdc3a 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -531,9 +531,7 @@ impl WorkerNodeGrpc { InputSourceDto::InputPath(inputs_uri.to_string_lossy().to_string()) } Some(InputSource::InputData(data)) => InputSourceDto::InputData(data), - None => { - return Err(anyhow!("Input source missing in ContributionParams")); - } + None => InputSourceDto::InputNull, }; let hints_source = if params.hints_path.is_some() { diff --git a/precompiles/hints/Cargo.toml b/precompiles/hints/Cargo.toml index 12c62ee06..eaf0d6b18 100644 --- a/precompiles/hints/Cargo.toml +++ b/precompiles/hints/Cargo.toml @@ -11,6 +11,14 @@ categories = { workspace = true } name = "precompiles_hints" path = "src/lib.rs" +[[bin]] +name = "hints-socket-server" +path = "src/bin/hints_socket_server.rs" + +[[bin]] +name = "hints-quic-server" +path = "src/bin/hints_quic_server.rs" + [dependencies] ziskos-hints = { workspace = true } lib-c = { workspace = true } @@ -19,10 +27,7 @@ anyhow = { workspace = true } rayon = { workspace = true } tracing = { workspace = true } zisk-common = { workspace = true } - -[[bin]] -name = "hints-socket-server" -path = "src/bin/hints_socket_server.rs" +rustls = { version = "0.23", features = ["ring"] } [dev-dependencies] criterion = "0.8" diff --git a/precompiles/hints/src/bin/hints_quic_server.rs b/precompiles/hints/src/bin/hints_quic_server.rs new file mode 100644 index 000000000..fa4e60c02 --- /dev/null +++ b/precompiles/hints/src/bin/hints_quic_server.rs @@ -0,0 +1,159 @@ +//! Hints QUIC Server +//! +//! A development tool that opens a QUIC server, writes binary file contents to it, +//! and waits for the user to press '0' to close. +//! +//! Usage: hints-quic-server +//! Example: hints-quic-server hints.bin 127.0.0.1:8080 + +use std::fs::File; +use std::io::{self, Read}; +use std::path::Path; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; +use zisk_common::io::{QuicStreamWriter, StreamWrite}; + +/// Reads binary file and returns its contents +fn read_binary_file>(path: P) -> io::Result> { + let mut file = File::open(path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + Ok(buffer) +} + +fn main() -> io::Result<()> { + // Initialize crypto provider for QUIC + let _ = rustls::crypto::ring::default_provider().install_default(); + + let args: Vec = std::env::args().collect(); + + if args.len() != 3 { + eprintln!("Usage: {} ", args[0]); + eprintln!("Example: {} hints.bin 127.0.0.1:8080", args[0]); + std::process::exit(1); + } + + let file_path = &args[1]; + let bind_address = &args[2]; + + // Parse bind address + let bind_addr: std::net::SocketAddr = bind_address.parse().map_err(|e| { + io::Error::new(io::ErrorKind::InvalidInput, format!("Invalid bind address: {}", e)) + })?; + + // Read the binary file + let file_data = read_binary_file(file_path)?; + println!("Read {} bytes from: {}", file_data.len(), file_path); + + println!("========================================"); + println!("Hints QUIC Server"); + println!("========================================"); + println!("Binary file: {}", file_path); + println!("Bind address: {}", bind_addr); + println!(); + + // Create the QUIC writer (server) + let mut writer = QuicStreamWriter::new(bind_addr).map_err(io::Error::other)?; + + println!("QUIC server created successfully"); + println!("Waiting for client connection..."); + + // Open the connection (waits for client to connect) + writer.open().map_err(io::Error::other)?; + + println!("Client connected! Starting data transfer..."); + + let shutdown = Arc::new(AtomicBool::new(false)); + + // Spawn shutdown listener thread + let shutdown_clone = Arc::clone(&shutdown); + thread::spawn(move || { + println!("Press '0' + Enter to close at any time"); + let stdin = io::stdin(); + let mut buffer = String::new(); + loop { + buffer.clear(); + if stdin.read_line(&mut buffer).is_ok() && buffer.trim() == "0" { + println!("Shutdown signal received!"); + shutdown_clone.store(true, Ordering::Relaxed); + break; + } + } + }); + + // File structure: + // - First 8 bytes: header + // - Middle: batches of hints (each hint = 26 * 8 = 208 bytes) + // - Last 8 bytes: footer + + const HINT_SIZE: usize = 26 * 8; // 208 bytes per hint + const HINTS_PER_BATCH: usize = 100; + const BATCH_SIZE: usize = HINTS_PER_BATCH * HINT_SIZE; // 20,800 bytes + + if file_data.len() < 16 { + eprintln!("Error: File too small (need at least 16 bytes for header+footer)"); + return Ok(()); + } + + let mut offset = 0; + let mut message_num = 0; + + loop { + if shutdown.load(Ordering::Relaxed) { + println!("\nShutdown requested, exiting..."); + break; + } + + if offset >= file_data.len() { + // All data sent + println!("All data sent successfully!"); + println!("Connection active. Press '0' to close..."); + while !shutdown.load(Ordering::Relaxed) { + thread::sleep(Duration::from_millis(100)); + } + break; + } + + // Determine what to send in this message + let (start, end) = if offset == 0 { + // First message: 8 bytes header + (0, 8) + } else if offset + 8 >= file_data.len() { + // Last message: final 8 bytes + (file_data.len() - 8, file_data.len()) + } else { + // Middle messages: batches of hints + let data_end = file_data.len() - 8; // Before footer + let remaining_data = data_end - offset; + let batch_size = std::cmp::min(BATCH_SIZE, remaining_data); + (offset, offset + batch_size) + }; + + let chunk = &file_data[start..end]; + + match writer.write(chunk) { + Ok(_) => { + message_num += 1; + println!( + "Message {}: Sent {} bytes (offset {}-{})", + message_num, + chunk.len(), + start, + end + ); + offset = end; + } + Err(e) => { + eprintln!("Error writing to QUIC stream: {}", e); + break; + } + } + } + + println!("Closing connection..."); + let _ = writer.close(); + println!("Server shutting down..."); + Ok(()) +} diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index f203f7970..b1500016d 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -231,7 +231,6 @@ impl HintsProcessor { let hint = PrecompileHint::from_u64_slice(hints, idx, true)?; self.num_hint.fetch_add(1, Ordering::Relaxed); - println!("[{}] Hint processed {:?}:", self.num_hint.load(Ordering::Relaxed), hint); // Check if custom handler is registered for custom hints if let HintCode::Custom(code) = hint.hint_code { From db2aeef1ee0fd5328a2b3265fe0661ee8a69c5f7 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:40:31 +0000 Subject: [PATCH 229/782] Reverse limb order of X, Y and sig for ecdsa verification hints: big-endian to little-endian --- ziskos-hints/src/handlers/secp256k1.rs | 31 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 78855bd09..bd43cf78b 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -20,24 +20,45 @@ use crate::zisklib; /// * `Err` - If the data length is invalid #[inline] pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { - hint_fields![X_Y: 8, INFINITY:1, Z: 4, SIG: 8]; + hint_fields![X: 4, Y: 4, INFINITY:1, Z: 4, SIG: 8]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_ECDSA_VERIFY")?; + // Reverse limb order for x and y coordinates (big-endian to little-endian) + let pk = [ + data[X_OFFSET + 3].to_be(), + data[X_OFFSET + 2].to_be(), + data[X_OFFSET + 1].to_be(), + data[X_OFFSET + 0].to_be(), + data[Y_OFFSET + 3].to_be(), + data[Y_OFFSET + 2].to_be(), + data[Y_OFFSET + 1].to_be(), + data[Y_OFFSET + 0].to_be(), + ]; + + // Reverse limb order for signature (big-endian to little-endian) + let sig = [ + data[SIG_OFFSET + 3].to_be(), + data[SIG_OFFSET + 2].to_be(), + data[SIG_OFFSET + 1].to_be(), + data[SIG_OFFSET + 0].to_be(), + data[SIG_OFFSET + 7].to_be(), + data[SIG_OFFSET + 6].to_be(), + data[SIG_OFFSET + 5].to_be(), + data[SIG_OFFSET + 4].to_be(), + ]; + // If the point is at infinity, return an error if data[INFINITY_OFFSET] != 0 { return Err("Error in secp256k1_ecdsa_verify: point at infinity".to_string()); } - let pk: &[u64; X_Y_SIZE] = data[X_Y_OFFSET..X_Y_OFFSET + X_Y_SIZE].try_into().unwrap(); - // Extract z (32 bytes), and sig (64 bytes) let z_bytes: &[u8; 32] = unsafe { &*(data[Z_OFFSET..SIG_OFFSET].as_ptr() as *const [u8; 32]) }; let z_dec: U256 = U256::decode_field_bytes(z_bytes.into()); let z_words = z_dec.to_words(); // Parse signature and decode r and s - let sig = &data[SIG_OFFSET..]; let sig_bytes: &[u8; 64] = unsafe { &*(sig.as_ptr() as *const [u8; 64]) }; let sig = Signature::try_from(sig_bytes.as_slice()) .map_err(|e| format!("Failed to parse signature: {}", e))?; @@ -50,7 +71,7 @@ pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { let mut hints = Vec::new(); - zisklib::secp256k1_ecdsa_verify(pk, &z_words, &r_words, &s_words, &mut hints); + zisklib::secp256k1_ecdsa_verify(&pk, &z_words, &r_words, &s_words, &mut hints); Ok(hints) } From 412daba934a7d29c7694fed3c35c271efdc3c600 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:42:38 +0000 Subject: [PATCH 230/782] cargo clippy --- ziskos-hints/src/handlers/secp256k1.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index bd43cf78b..84a1820a0 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -29,11 +29,11 @@ pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { data[X_OFFSET + 3].to_be(), data[X_OFFSET + 2].to_be(), data[X_OFFSET + 1].to_be(), - data[X_OFFSET + 0].to_be(), + data[X_OFFSET].to_be(), data[Y_OFFSET + 3].to_be(), data[Y_OFFSET + 2].to_be(), data[Y_OFFSET + 1].to_be(), - data[Y_OFFSET + 0].to_be(), + data[Y_OFFSET].to_be(), ]; // Reverse limb order for signature (big-endian to little-endian) @@ -41,7 +41,7 @@ pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { data[SIG_OFFSET + 3].to_be(), data[SIG_OFFSET + 2].to_be(), data[SIG_OFFSET + 1].to_be(), - data[SIG_OFFSET + 0].to_be(), + data[SIG_OFFSET].to_be(), data[SIG_OFFSET + 7].to_be(), data[SIG_OFFSET + 6].to_be(), data[SIG_OFFSET + 5].to_be(), From 08f562b1035fe88183a3d49c5bc7147e73ebfb5e Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 15 Jan 2026 08:33:22 +0100 Subject: [PATCH 231/782] Fix emu.c traces --- emulator-asm/src/emu.c | 44 +++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index 48fa49f03..368eb4b1d 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -460,9 +460,9 @@ extern int _opcode_keccak(uint64_t address) #endif #ifdef DEBUG #ifdef ASM_CALL_METRICS - if (emu_verbose) printf("opcode_keccak() calling KeccakF1600() counter=%lu address=%08lx\n", asm_call_metrics.keccak_counter, address); + if (emu_verbose) printf("opcode_keccak() calling keccakf1600_generic() counter=%lu address=%08lx\n", asm_call_metrics.keccak_counter, address); #else - if (emu_verbose) printf("opcode_keccak() calling KeccakF1600() address=%08lx\n", address); + if (emu_verbose) printf("opcode_keccak() calling keccakf1600_generic() address=%08lx\n", address); #endif #endif @@ -485,7 +485,7 @@ extern int _opcode_keccak(uint64_t address) #endif #ifdef DEBUG - if (emu_verbose) printf("opcode_keccak() called KeccakF1600()\n"); + if (emu_verbose) printf("opcode_keccak() called keccakf1600_generic()\n"); #endif #ifdef ASM_CALL_METRICS asm_call_metrics.keccak_counter++; @@ -502,9 +502,9 @@ extern int _opcode_sha256(uint64_t * address) #endif #ifdef DEBUG #ifdef ASM_CALL_METRICS - if (emu_verbose) printf("opcode_sha256() calling sha256_transform_2() counter=%lu address=%p\n", asm_call_metrics.sha256_counter, address); + if (emu_verbose) printf("opcode_sha256() calling zisk_sha256() counter=%lu address=%p\n", asm_call_metrics.sha256_counter, address); #else - if (emu_verbose) printf("opcode_sha256() calling sha256_transform_2() address=%p\n", address); + if (emu_verbose) printf("opcode_sha256() calling zisk_sha256() address=%p\n", address); #endif #endif @@ -527,7 +527,7 @@ extern int _opcode_sha256(uint64_t * address) #endif #ifdef DEBUG - if (emu_verbose) printf("opcode_sha256() called sha256_transform_2()\n"); + if (emu_verbose) printf("opcode_sha256() called zisk_sha256()\n"); #endif #ifdef ASM_CALL_METRICS asm_call_metrics.sha256_counter++; @@ -899,8 +899,17 @@ extern int _opcode_fcall(struct FcallContext * ctx) #else if (emu_verbose) printf("_opcode_fcall(%lu)\n", ctx->function_id); #endif + if (emu_verbose) + { + printf("_opcode_fcall() calling Fcall() with params_size=%lu\n", ctx->params_size); + printf("params="); + for (uint64_t i=0; iparams_size; i++) + { + printf("%lx ", ctx->params[i]); + } + printf("\n"); + } #endif - int iresult; #ifdef ASM_PRECOMPILE_CACHE @@ -928,6 +937,19 @@ extern int _opcode_fcall(struct FcallContext * ctx) } #endif +#ifdef DEBUG + if (emu_verbose) + { + printf("_opcode_fcall() called Fcall() and got result_size=%lu\n", ctx->result_size); + printf("results="); + for (uint64_t i=0; iresult_size; i++) + { + printf("%lx ", ctx->result[i]); + } + printf("\n"); + } +#endif + #ifdef ASM_CALL_METRICS asm_call_metrics.fcall_counter++; gettimeofday(&asm_call_stop, NULL); @@ -1096,9 +1118,9 @@ extern int _opcode_bn254_curve_add(uint64_t * address) if (emu_verbose) { #ifdef ASM_CALL_METRICS - printf("_opcode_bn254_curve_add() calling AddPointEcP() counter=%lu address=%p p1_address=%p p2_address=%p\n", asm_call_metrics.bn254_curve_add_counter, address, p1, p2); + printf("_opcode_bn254_curve_add() calling BN254CurveAddP() counter=%lu address=%p p1_address=%p p2_address=%p\n", asm_call_metrics.bn254_curve_add_counter, address, p1, p2); #else - printf("_opcode_bn254_curve_add() calling AddPointEcP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); + printf("_opcode_bn254_curve_add() calling BN254CurveAddP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); #endif printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); @@ -1421,9 +1443,9 @@ extern int _opcode_bls12_381_curve_add(uint64_t * address) if (emu_verbose) { #ifdef ASM_CALL_METRICS - printf("_opcode_bls12_381_curve_add() calling AddPointEcP() counter=%lu address=%p p1_address=%p p2_address=%p\n", asm_call_metrics.bl12_381_curve_add_counter, address, p1, p2); + printf("_opcode_bls12_381_curve_add() calling BLS12_381CurveAddP() counter=%lu address=%p p1_address=%p p2_address=%p\n", asm_call_metrics.bl12_381_curve_add_counter, address, p1, p2); #else - printf("_opcode_bls12_381_curve_add() calling AddPointEcP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); + printf("_opcode_bls12_381_curve_add() calling BLS12_381CurveAddP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); #endif printf("p1.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0], p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); printf("p1.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6], p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); From 54925032b06aec1f958dd45f99c219430dec5551 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:47:33 +0000 Subject: [PATCH 232/782] added control to avoid use QUIC for precompiles hints when executing an interactive CLI command --- cli/src/commands/execute.rs | 4 ++++ cli/src/commands/prove.rs | 4 ++++ cli/src/commands/stats.rs | 4 ++++ cli/src/commands/verify_constraints.rs | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 6e82f34d7..401eea0a7 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -100,6 +100,10 @@ impl ZiskExecute { let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; + if matches!(hints_stream, StreamSource::Quic(_)) { + return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); + } + let emulator = if cfg!(target_os = "macos") { if !self.emulator { warn!("Emulator mode is forced on macOS due to lack of ASM support."); diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index b90ea7e43..420b5046b 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -144,6 +144,10 @@ impl ZiskProve { let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; + if matches!(hints_stream, StreamSource::Quic(_)) { + return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); + } + let emulator = if cfg!(target_os = "macos") { if !self.emulator { warn!("Emulator mode is forced on macOS due to lack of ASM support."); diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index e280acc7c..55ed2c5fd 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -114,6 +114,10 @@ impl ZiskStats { let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; + if matches!(hints_stream, StreamSource::Quic(_)) { + return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); + } + let emulator = if cfg!(target_os = "macos") { if !self.emulator { warn!("Emulator mode is forced on macOS due to lack of ASM support."); diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 41b782d51..fb138cd10 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -105,6 +105,10 @@ impl ZiskVerifyConstraints { let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; + if matches!(hints_stream, StreamSource::Quic(_)) { + return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); + } + let emulator = if cfg!(target_os = "macos") { if !self.emulator { warn!("Emulator mode is forced on macOS due to lack of ASM support."); From 4ea4b02b8abf64c89becd69165e71f5129976260 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 15 Jan 2026 14:08:05 +0100 Subject: [PATCH 233/782] Fix fcall(ecdsa_verify) in lib-c --- lib-c/c/src/ec/ec.cpp | 198 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 184 insertions(+), 14 deletions(-) diff --git a/lib-c/c/src/ec/ec.cpp b/lib-c/c/src/ec/ec.cpp index 6cc92b70a..ac2f994af 100644 --- a/lib-c/c/src/ec/ec.cpp +++ b/lib-c/c/src/ec/ec.cpp @@ -98,7 +98,7 @@ int inline AddPointEcDblFe (RawFec::Element &x1, RawFec::Element &y1) fec.add(aux2, y1, y1); if (fec.isZero(aux2)) { - printf("AddPointEc() got denominator=0 1\n"); + printf("AddPointEcDbl() got denominator=0 1\n"); return -1; } fec.div(s, aux1, aux2); @@ -202,7 +202,7 @@ int secp256k1_ecdsa_verify ( ) { // Convert z, r, s inputs to field elements - RawFec::Element z, r, s; + RawFnec::Element z, r, s; array2fe(_z, z); array2fe(_r, r); array2fe(_s, s); @@ -214,17 +214,16 @@ int secp256k1_ecdsa_verify ( // 4. Computes and returns the curve point p = u1·G + u2·PK // s_inv = s⁻¹ mod n - RawFec::Element s_inv; - fec.inv(s_inv, s); + RawFnec::Element s_inv; + fnec.inv(s_inv, s); // u1 = z·s_inv mod n - RawFec::Element u1; - fec.mul(u1, z, s_inv); + RawFnec::Element u1; + fnec.mul(u1, z, s_inv); // u2 = r·s_inv mod n - RawFec::Element u2; - fec.mul(u2, r, s_inv); - + RawFnec::Element u2; + fnec.mul(u2, r, s_inv); uint64_t u1_array[4]; uint64_t u2_array[4]; fe2array(u1, u1_array); @@ -235,6 +234,175 @@ int secp256k1_ecdsa_verify ( return 0; } +const uint64_t IDENTITY[8] = {0,0,0,0,0,0,0,0}; + +void secp256k1_curve_add( + const uint64_t * p, // 8 x 64 bits + const uint64_t * q, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +) +{ + // Get the 2 points coordinates + const uint64_t * x1 = &p[0]; + const uint64_t * y1 = &p[4]; + const uint64_t * x2 = &q[0]; + const uint64_t * y2 = &q[4]; + + // If p==q return dbl(p) + if (x1[0] == x2[0] && + x1[1] == x2[1] && + x1[2] == x2[2] && + x1[3] == x2[3]) + { + if (y1[0] == y2[0] && + y1[1] == y2[1] && + y1[2] == y2[2] && + y1[3] == y2[3]) { + secp256k1_curve_dbl(p, r); + return; + } else { + for (int i = 0; i < 8; i++) { + r[i] = IDENTITY[i]; + } + return; + } + } + + // If p==0 return q + if ( p[0] == IDENTITY[0] && + p[1] == IDENTITY[1] && + p[2] == IDENTITY[2] && + p[3] == IDENTITY[3] && + p[4] == IDENTITY[4] && + p[5] == IDENTITY[5] && + p[6] == IDENTITY[6] && + p[7] == IDENTITY[7] ) + { + for (int i = 0; i < 8; i++) + { + r[i] = q[i]; + } + return; + } + // if q == 0 return p + else if ( q[0] == IDENTITY[0] && + q[1] == IDENTITY[1] && + q[2] == IDENTITY[2] && + q[3] == IDENTITY[3] && + q[4] == IDENTITY[4] && + q[5] == IDENTITY[5] && + q[6] == IDENTITY[6] && + q[7] == IDENTITY[7] ) + { + for (int i = 0; i < 8; i++) + { + r[i] = p[i]; + } + return; + } + + // Convert coordinates to field elements + RawFec::Element x1_fe, y1_fe, x2_fe, y2_fe; + array2fe(x1, x1_fe); + array2fe(y1, y1_fe); + array2fe(x2, x2_fe); + array2fe(y2, y2_fe); + + // Calculate lambda = (y2 - y1) / (x2 - x1) + RawFec::Element y2_minus_y1; + fec.sub(y2_minus_y1, y2_fe, y1_fe); + RawFec::Element x2_minus_x1; + fec.sub(x2_minus_x1, x2_fe, x1_fe); + RawFec::Element x2_minus_x1_inv; + fec.inv(x2_minus_x1_inv, x2_minus_x1); + RawFec::Element lambda; + fec.mul(lambda, y2_minus_y1, x2_minus_x1_inv); + + // Calculate x3 = lambda^2 - (x1 + x2) + RawFec::Element x3_fe; + RawFec::Element lambda_sq; + fec.square(lambda_sq, lambda); + RawFec::Element x1_plus_x2; + fec.add(x1_plus_x2, x1_fe, x2_fe); + fec.sub(x3_fe, lambda_sq, x1_plus_x2); + + // Calculate y3 = lambda * (x1 - x3) - y1 + RawFec::Element y3_fe; + RawFec::Element x1_minus_x3; + fec.sub(x1_minus_x3, x1_fe, x3_fe); + RawFec::Element lambda_x1_minus_x3; + fec.mul(lambda_x1_minus_x3, lambda, x1_minus_x3); + fec.sub(y3_fe, lambda_x1_minus_x3, y1_fe); + + // Convert to result + fe2array(x3_fe, r); + fe2array(y3_fe, r + 4); +} + +void secp256k1_curve_dbl( + const uint64_t * p, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +) +{ + // If p==0 return p + if ( p[0] == IDENTITY[0] && + p[1] == IDENTITY[1] && + p[2] == IDENTITY[2] && + p[3] == IDENTITY[3] && + p[4] == IDENTITY[4] && + p[5] == IDENTITY[5] && + p[6] == IDENTITY[6] && + p[7] == IDENTITY[7] ) + { + for (int i = 0; i < 8; i++) + { + r[i] = p[i]; + } + return; + } + + // Convert coordinates to field elements + uint64_t * x = (uint64_t *)&p[0]; + uint64_t * y = (uint64_t *)&p[4]; + RawFec::Element x_fe, y_fe; + array2fe(x, x_fe); + array2fe(y, y_fe); + + // Calculate lambda = (3*x1^2) / (2*y1) + RawFec::Element x1_sq; + fec.square(x1_sq, x_fe); + RawFec::Element three; + fec.fromUI(three, 3); + RawFec::Element three_x1_sq; + fec.mul(three_x1_sq, x1_sq, three); + RawFec::Element two_y1; + fec.add(two_y1, y_fe, y_fe); + RawFec::Element two_y1_inv; + fec.inv(two_y1_inv, two_y1); + RawFec::Element lambda; + fec.mul(lambda, three_x1_sq, two_y1_inv); + + // Calculate x3 = lambda^2 - 2*x1 + RawFec::Element lambda_sq; + fec.square(lambda_sq, lambda); + RawFec::Element two_x1; + fec.add(two_x1, x_fe, x_fe); + RawFec::Element x3_fe; + fec.sub(x3_fe, lambda_sq, two_x1); + + // Calculate y3 = lambda * (x1 - x3) - y1 + RawFec::Element x1_minus_x3; + fec.sub(x1_minus_x3, x_fe, x3_fe); + RawFec::Element lambda_x1_minus_x3; + fec.mul(lambda_x1_minus_x3, lambda, x1_minus_x3); + RawFec::Element y3_fe; + fec.sub(y3_fe, lambda_x1_minus_x3, y_fe); + + // Convert to result + fe2array(x3_fe, r); + fe2array(y3_fe, r + 4); +} + int secp256k1_curve_dbl_scalar_mul( const uint64_t * k1, // 4 x 64 bits const uint64_t * p1, // 8 x 64 bits @@ -251,18 +419,20 @@ int secp256k1_curve_dbl_scalar_mul( uint64_t i = ii; // r = r + r - AddPointEcDbl(r, r); + secp256k1_curve_dbl(r, r); // If k1[i] == 1 then r = r + p1 uint64_t k1_bit = (k1[i / 64] >> (i % 64)) & 1; - if (k1_bit == 1) { - AddPointEcP(0, r, p1, r); + if (k1_bit == 1) + { + secp256k1_curve_add(r, p1, r); } // If k2[i] == 1 then r = r + p2 uint64_t k2_bit = (k2[i / 64] >> (i % 64)) & 1; - if (k2_bit == 1) { - AddPointEcP(0, r, p2, r); + if (k2_bit == 1) + { + secp256k1_curve_add(r, p2, r); } } From 456579eee2acec8b7fb39cb4f49a37895f256661 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 15 Jan 2026 14:13:48 +0100 Subject: [PATCH 234/782] Add missing secp256k1 functions to ec.hpp --- lib-c/c/src/ec/ec.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib-c/c/src/ec/ec.hpp b/lib-c/c/src/ec/ec.hpp index ef0745d50..a3fadab21 100644 --- a/lib-c/c/src/ec/ec.hpp +++ b/lib-c/c/src/ec/ec.hpp @@ -32,6 +32,17 @@ int secp256k1_ecdsa_verify ( uint64_t * result // 8 x 64 bits ); +void secp256k1_curve_add( + const uint64_t * p, // 8 x 64 bits + const uint64_t * q, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +); + +void secp256k1_curve_dbl( + const uint64_t * p, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +); + int secp256k1_curve_dbl_scalar_mul( const uint64_t * k1, // 4 x 64 bits const uint64_t * p1, // 8 x 64 bits From 12d9be6e1c6406e79490e83d17088e4a7fa9f81e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:22:39 +0000 Subject: [PATCH 235/782] add allow-multiple-definition flag when building the project to avoid linking problems --- .cargo/config.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..a93d96687 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.x86_64-unknown-linux-gnu] +rustflags = ["-C", "link-arg=-Wl,--allow-multiple-definition"] From f5bd139399e3e148b4811d83ed0d8e50cced656c Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 15 Jan 2026 16:28:14 +0100 Subject: [PATCH 236/782] Modify _wait_for_prec_avail() to wake up every 5 seconds, and detect abort command --- emulator-asm/src/main.c | 88 +++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 30 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 1f6f4bf25..2358d21f3 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -22,6 +22,7 @@ #include #include #include +#include //#include // Assembly-provided functions @@ -344,6 +345,7 @@ char shmem_control_input_name[128]; int shmem_control_input_fd = -1; uint64_t * shmem_control_input_address = NULL; volatile uint64_t * precompile_written_address = NULL; +volatile uint64_t * precompile_exit_address = NULL; // Control output shared memory char shmem_control_output_name[128]; @@ -1829,6 +1831,7 @@ void client_setup (void) } shmem_control_input_address = (uint64_t *)pControl; precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); /*****************/ @@ -3437,6 +3440,7 @@ void server_setup (void) } shmem_control_input_address = (uint64_t *)pControl; precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); /******************/ @@ -4968,42 +4972,66 @@ int _wait_for_prec_avail (void) } // Wait again, but blocking this time - // printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); - - int result = sem_wait(sem_prec_avail); - // printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); - if (result == -1) + while (true) { - printf("ERROR: wait_for_prec_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); + struct timespec ts; + int result = clock_gettime(CLOCK_REALTIME, &ts); + if (result == -1) + { + printf("ERROR: wait_for_prec_avail() failed calling clock_gettime() errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + ts.tv_sec += 5; // 5 seconds timeout + + //printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + result = sem_timedwait(sem_prec_avail, &ts); + //printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + if ((result == -1) && (errno != ETIMEDOUT)) + { + printf("ERROR: wait_for_prec_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (*precompile_exit_address != 0) + { + printf("ERROR: wait_for_prec_avail() found precompile_exit_address=%lu\n", *precompile_exit_address); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (*precompile_written_address > *precompile_read_address) + { + return 0; + } } - // Wait for control input shared memory to synchronize - uint64_t written; - uint64_t read; - for (uint64_t i=0; i Date: Thu, 15 Jan 2026 15:54:20 +0000 Subject: [PATCH 237/782] removed server and prove-client CLI commands --- Cargo.lock | 27 - Cargo.toml | 2 - cli/Cargo.toml | 1 - cli/src/bin/cargo-zisk.rs | 13 +- cli/src/commands/mod.rs | 4 - cli/src/commands/prove_client.rs | 179 ------- cli/src/commands/server.rs | 280 ----------- server/Cargo.toml | 47 -- server/build.rs | 11 - server/src/handler_prove.rs | 182 ------- server/src/handler_shutdown.rs | 64 --- server/src/handler_status.rs | 62 --- server/src/handler_verify_constraints.rs | 114 ----- server/src/lib.rs | 11 - server/src/zisk_service.rs | 609 ----------------------- 15 files changed, 2 insertions(+), 1604 deletions(-) delete mode 100644 cli/src/commands/prove_client.rs delete mode 100644 cli/src/commands/server.rs delete mode 100644 server/Cargo.toml delete mode 100644 server/build.rs delete mode 100644 server/src/handler_prove.rs delete mode 100644 server/src/handler_shutdown.rs delete mode 100644 server/src/handler_status.rs delete mode 100644 server/src/handler_verify_constraints.rs delete mode 100644 server/src/lib.rs delete mode 100644 server/src/zisk_service.rs diff --git a/Cargo.lock b/Cargo.lock index a89061e9d..2e9d2c9cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,7 +601,6 @@ dependencies = [ "rom-setup", "serde", "serde_json", - "server", "sysinfo 0.37.2", "target-lexicon", "tokio", @@ -3804,32 +3803,6 @@ dependencies = [ "serde", ] -[[package]] -name = "server" -version = "0.16.0" -dependencies = [ - "anyhow", - "asm-runner", - "bytemuck", - "clap", - "colored", - "executor", - "fields", - "libloading", - "mpi", - "named-sem", - "proofman", - "proofman-common", - "serde", - "serde_json", - "tracing", - "uuid", - "witness", - "zisk-common", - "zisk-witness", - "zstd", -] - [[package]] name = "sha2" version = "0.10.9" diff --git a/Cargo.toml b/Cargo.toml index 1eb9df8c4..2aa3b2569 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,6 @@ members = [ "ziskclib", "common", "tools/circuit", - "server", "distributed/crates/coordinator", "distributed/crates/grpc-api", "distributed/crates/common", @@ -80,7 +79,6 @@ precomp-sha256f = { path = "precompiles/sha256f" } precomp-big-int = { path = "precompiles/big_int" } riscv = { path = "riscv" } rom-setup = { path = "rom-setup" } -server = { path = "server" } sm-arith = { path = "state-machines/arith" } sm-binary = { path = "state-machines/binary" } sm-main = { path = "state-machines/main" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9ae695612..14bc77ad5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -26,7 +26,6 @@ rom-setup = { workspace = true } zisk-core = { workspace = true } zisk-pil = { workspace = true } asm-runner = { workspace = true } -server = { workspace = true } colored = { workspace = true } fields = { workspace = true } diff --git a/cli/src/bin/cargo-zisk.rs b/cli/src/bin/cargo-zisk.rs index 4dfae0c92..22bc5fef8 100644 --- a/cli/src/bin/cargo-zisk.rs +++ b/cli/src/bin/cargo-zisk.rs @@ -1,8 +1,7 @@ use anyhow::{anyhow, Context, Result}; use cargo_zisk::commands::{ - ZiskBuild, ZiskCheckSetup, ZiskClean, ZiskExecute, ZiskProve, ZiskProveClient, ZiskProveSnark, - ZiskRomSetup, ZiskRomVkey, ZiskRun, ZiskSdk, ZiskServer, ZiskStats, ZiskVerify, - ZiskVerifyConstraints, + ZiskBuild, ZiskCheckSetup, ZiskClean, ZiskExecute, ZiskProve, ZiskProveSnark, ZiskRomSetup, + ZiskRomVkey, ZiskRun, ZiskSdk, ZiskStats, ZiskVerify, ZiskVerifyConstraints, }; use clap::Parser; use zisk_build::ZISK_VERSION_MESSAGE; @@ -21,14 +20,12 @@ pub enum Cargo { CheckSetup(ZiskCheckSetup), Clean(ZiskClean), Execute(ZiskExecute), - ProveClient(ZiskProveClient), Prove(ZiskProve), ProveSnark(ZiskProveSnark), RomSetup(ZiskRomSetup), RomVkey(ZiskRomVkey), Run(ZiskRun), Sdk(ZiskSdk), - Server(ZiskServer), Stats(ZiskStats), Verify(ZiskVerify), VerifyConstraints(ZiskVerifyConstraints), @@ -48,9 +45,6 @@ fn main() -> Result<()> { Cargo::Clean(cmd) => { cmd.run().context("Error executing Clean command")?; } - Cargo::ProveClient(cmd) => { - cmd.run().context("Error executing ProveClient command")?; - } Cargo::Prove(mut cmd) => { cmd.run().context("Error executing Prove command")?; } @@ -75,9 +69,6 @@ fn main() -> Result<()> { Cargo::Sdk(cmd) => { cmd.command.run().context("Error executing SDK command")?; } - Cargo::Server(mut cmd) => { - cmd.run().context("Error executing Server command")?; - } Cargo::Verify(cmd) => { cmd.run().map_err(|e| anyhow!("Error executing Verify command: {}", e))?; } diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 7d01f4462..ace972dcc 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -4,13 +4,11 @@ mod clean; mod common; mod execute; mod prove; -mod prove_client; mod prove_snark; mod rom_setup; mod rom_vkey; mod run; mod sdk; -mod server; mod stats; mod verify_constraints; mod verify_stark; @@ -21,13 +19,11 @@ pub use clean::*; pub use common::*; pub use execute::*; pub use prove::*; -pub use prove_client::*; pub use prove_snark::*; pub use rom_setup::*; pub use rom_vkey::*; pub use run::*; pub use sdk::*; -pub use server::*; pub use stats::*; pub use verify_constraints::*; pub use verify_stark::*; diff --git a/cli/src/commands/prove_client.rs b/cli/src/commands/prove_client.rs deleted file mode 100644 index a6cb478f2..000000000 --- a/cli/src/commands/prove_client.rs +++ /dev/null @@ -1,179 +0,0 @@ -use anyhow::Result; -use clap::{Parser, Subcommand}; -use server::{ - ZiskProveRequest, ZiskRequest, ZiskResponse, ZiskShutdownRequest, ZiskStatusRequest, - ZiskVerifyConstraintsRequest, -}; -use std::{ - io::{BufRead, BufReader, Write}, - net::TcpStream, - path::PathBuf, -}; - -use crate::commands::DEFAULT_PORT; - -use colored::Colorize; - -#[derive(Parser)] -#[command(name = "Zisk Prover Client", version, about = "Send commands to the prover server")] -pub struct ZiskProveClient { - #[command(subcommand)] - pub command: ClientCommand, -} - -#[derive(Subcommand, Debug)] -#[command(rename_all = "snake_case")] -pub enum ClientCommand { - /// Get server status - Status { - /// Port of the server (by default DEFAULT_PORT) - #[clap(long)] - port: Option, - }, - - /// Shut down the server - Shutdown { - /// Port of the server (by default DEFAULT_PORT) - #[clap(long)] - port: Option, - }, - - Prove { - /// Path to the input file - #[arg(short, long)] - input: PathBuf, - - /// Use aggregation - #[clap(short = 'a', long, default_value_t = false)] - aggregation: bool, - - #[clap(short = 'r', long, default_value_t = false)] - rma: bool, - - /// Use final snark - #[clap(short = 'f', long, default_value_t = false)] - final_snark: bool, - - /// Verify proofs - #[clap(short = 'y', long, default_value_t = false)] - verify_proofs: bool, - - /// Output folder for the proof - #[clap(short = 'o', long, default_value = "tmp")] - output_dir: PathBuf, - - #[clap(short = 'p')] - prefix: String, - - /// Use minimal memory - #[clap(long, default_value_t = false)] - minimal_memory: bool, - - /// Port of the server (by default DEFAULT_PORT) - #[clap(long)] - port: Option, - - /// Verbosity (-v, -vv) - #[arg(short ='v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] - verbose: u8, // Using u8 to hold the number of `-v` - }, - /// Verify constraints from input file - VerifyConstraints { - /// Path to the input file - #[arg(short, long)] - input: PathBuf, - - /// Port of the server (by default DEFAULT_PORT) - #[clap(long)] - port: Option, - - /// Verbosity (-v, -vv) - #[arg(short ='v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] - verbose: u8, // Using u8 to hold the number of `-v` - }, -} - -impl ZiskProveClient { - pub fn run(&self) -> Result<()> { - let request = match &self.command { - ClientCommand::Status { port: _ } => { - ZiskRequest::Status { payload: ZiskStatusRequest {} } - } - ClientCommand::Shutdown { port: _ } => { - ZiskRequest::Shutdown { payload: ZiskShutdownRequest {} } - } - ClientCommand::Prove { - input, - aggregation, - rma, - final_snark, - verify_proofs, - minimal_memory, - output_dir, - prefix, - verbose: _, - port: _, - } => ZiskRequest::Prove { - payload: ZiskProveRequest { - input: input.clone(), - aggregation: *aggregation, - rma: *rma, - final_snark: *final_snark, - verify_proofs: *verify_proofs, - minimal_memory: *minimal_memory, - folder: output_dir.clone(), - prefix: prefix.clone(), - }, - }, - ClientCommand::VerifyConstraints { input, verbose: _, port: _ } => { - ZiskRequest::VerifyConstraints { - payload: ZiskVerifyConstraintsRequest { input: input.clone() }, - } - } - }; - - // Determine the port to use for this client instance. - // - If no port is specified, default to DEFAULT_PORT. - // - If a port is specified, use it as the base port. - // In both cases, the local MPI rank is added to the port to avoid conflicts - // when running multiple processes on the same machine. - let port = match self.command { - ClientCommand::Prove { port, .. } - | ClientCommand::VerifyConstraints { port, .. } - | ClientCommand::Status { port } - | ClientCommand::Shutdown { port } => port.unwrap_or(DEFAULT_PORT), - }; - - // TODO: FIX! - // port += mpi_context.node_rank as u16; - - let address = format!("localhost:{port}"); - - // Open connection - let mut stream = TcpStream::connect(&address) - .map_err(|e| anyhow::anyhow!("Failed to connect to server: {}", e))?; - - // Serialize and send request - let mut request_json = serde_json::to_string(&request)?; - request_json.push('\n'); - stream.write_all(request_json.as_bytes())?; - - // Read and parse response - let mut reader = BufReader::new(stream); - let mut response_line = String::new(); - reader.read_line(&mut response_line)?; - - if let Err(e) = serde_json::from_str::(&response_line) { - return Err(anyhow::anyhow!( - "Failed to parse server response: {}\nRaw: {}", - e, - response_line - )); - } - - println!(); - println!("{} {}", format!("{: >12}", "Response").bright_green().bold(), response_line); - - Ok(()) - } -} diff --git a/cli/src/commands/server.rs b/cli/src/commands/server.rs deleted file mode 100644 index e68d18e92..000000000 --- a/cli/src/commands/server.rs +++ /dev/null @@ -1,280 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use colored::Colorize; -use proofman_common::{json_to_debug_instances_map, DebugInfo, ParamsGPU}; -use rom_setup::{ - gen_elf_hash, get_elf_bin_file_path, get_elf_data_hash, get_rom_blowup_factor_and_arity, - DEFAULT_CACHE_PATH, -}; -use server::ZiskServerParams; -use server::ZiskService; -use std::collections::HashMap; -use std::fs; -use std::{path::PathBuf, process}; -use zisk_common::init_tracing; - -use crate::commands::{get_proving_key, get_witness_computation_lib}; -use crate::ux::print_banner; -use zisk_build::ZISK_VERSION_MESSAGE; - -pub const DEFAULT_PORT: u16 = 7878; -const LOG_PATH: &str = "zisk_prover_server.log"; - -// Structure representing the 'prove' subcommand of cargo. -#[derive(Parser, Debug)] -#[command(name = "Prover Server", version, about = "A TCP-based prover control server", long_about = None, version = ZISK_VERSION_MESSAGE)] -#[command(propagate_version = true)] -#[command(group( - clap::ArgGroup::new("input_mode") - .args(["asm", "emulator"]) - .multiple(false) - .required(false) -))] -pub struct ZiskServer { - /// Optional port number (default 7878) - #[arg(short, long, default_value_t = DEFAULT_PORT)] - port: u16, - - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - - /// ELF file path - /// This is the path to the ROM file that the witness computation dynamic library will use - /// to generate the witness. - #[clap(short = 'e', long)] - pub elf: PathBuf, - - /// ASM file path - /// Optional, mutually exclusive with `--emulator` - #[clap(short = 's', long)] - pub asm: Option, - - /// Use prebuilt emulator (mutually exclusive with `--asm`) - #[clap(short = 'l', long, action = clap::ArgAction::SetTrue)] - pub emulator: bool, - - /// Setup folder path - #[clap(short = 'k', long)] - pub proving_key: Option, - - /// Base port for Assembly microservices (default: 23115). - /// A single execution will use 3 consecutive ports, from this port to port + 2. - /// If you are running multiple instances of ZisK using mpi on the same machine, - /// it will use from this base port to base port + 2 * number_of_instances. - /// For example, if you run 2 mpi instances of ZisK, it will use ports from 23115 to 23117 - /// for the first instance, and from 23118 to 23120 for the second instance. - #[clap(long, conflicts_with = "emulator")] - pub asm_port: Option, - - /// Map unlocked flag - /// This is used to unlock the memory map for the ROM file. - /// If you are running ZisK on a machine with limited memory, you may want to enable this option. - /// This option is mutually exclusive with `--emulator`. - #[clap(short = 'u', long, conflicts_with = "emulator")] - pub unlock_mapped_memory: bool, - - /// Verbosity (-v, -vv) - #[arg(short ='v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] - pub verbose: u8, // Using u8 to hold the number of `-v` - - #[clap(short = 'd', long)] - pub debug: Option>, - - #[clap(short = 'c', long, default_value_t = false)] - pub verify_constraints: bool, - - #[clap(short = 'a', long, default_value_t = false)] - pub aggregation: bool, - - #[clap(short = 'f', long, default_value_t = false)] - pub final_snark: bool, - - #[clap(short = 'r', long, default_value_t = false)] - pub rma: bool, - - /// GPU PARAMS - #[clap(short = 'z', long, default_value_t = false)] - pub preallocate: bool, - - #[clap(short = 't', long)] - pub max_streams: Option, - - #[clap(short = 'n', long)] - pub number_threads_witness: Option, - - #[clap(short = 'x', long)] - pub max_witness_stored: Option, - - #[clap(short = 'j', long, default_value_t = false)] - pub shared_tables: bool, -} - -impl ZiskServer { - pub fn run(&mut self) -> Result<()> { - init_tracing(LOG_PATH); - - print_banner(); - - if !self.elf.exists() { - eprintln!("Error: ELF file '{}' not found.", self.elf.display()); - process::exit(1); - } - - let proving_key = get_proving_key(self.proving_key.as_ref()); - - let debug_info = match &self.debug { - None => DebugInfo::default(), - Some(None) => DebugInfo::new_debug(), - Some(Some(debug_value)) => { - json_to_debug_instances_map(proving_key.clone(), debug_value.clone())? - } - }; - - let default_cache_path = - std::env::var("HOME").ok().map(PathBuf::from).unwrap().join(DEFAULT_CACHE_PATH); - - if !default_cache_path.exists() { - if let Err(e) = fs::create_dir_all(default_cache_path.clone()) { - if e.kind() != std::io::ErrorKind::AlreadyExists { - // prevent collision in distributed mode - panic!("Failed to create the cache directory: {e:?}"); - } - } - } - - let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; - - let mut asm_rom = None; - if emulator { - self.asm = None; - } else if self.asm.is_none() { - let stem = self.elf.file_stem().unwrap().to_str().unwrap(); - let hash = get_elf_data_hash(&self.elf) - .map_err(|e| anyhow::anyhow!("Error computing ELF hash: {}", e))?; - let new_filename = format!("{stem}-{hash}-mt.bin"); - let asm_rom_filename = format!("{stem}-{hash}-rh.bin"); - asm_rom = Some(default_cache_path.join(asm_rom_filename)); - self.asm = Some(default_cache_path.join(new_filename)); - } - - if let Some(asm_path) = &self.asm { - if !asm_path.exists() { - return Err(anyhow::anyhow!("ASM file not found at {:?}", asm_path.display())); - } - } - - if let Some(asm_rom) = &asm_rom { - if !asm_rom.exists() { - return Err(anyhow::anyhow!("ASM file not found at {:?}", asm_rom.display())); - } - } - - let (blowup_factor, merkle_tree_arity) = get_rom_blowup_factor_and_arity(&proving_key); - - let rom_bin_path = get_elf_bin_file_path( - &self.elf.to_path_buf(), - &default_cache_path, - blowup_factor, - merkle_tree_arity, - )?; - - if !rom_bin_path.exists() { - let _ = gen_elf_hash( - &self.elf.clone(), - rom_bin_path.as_path(), - blowup_factor, - merkle_tree_arity, - false, - ) - .map_err(|e| anyhow::anyhow!("Error generating elf hash: {}", e)); - } - - self.print_command_info(); - let mut custom_commits_map: HashMap = HashMap::new(); - custom_commits_map.insert("rom".to_string(), rom_bin_path); - - let mut gpu_params = ParamsGPU::new(self.preallocate); - - if self.max_streams.is_some() { - gpu_params.with_max_number_streams(self.max_streams.unwrap()); - } - if self.number_threads_witness.is_some() { - gpu_params.with_number_threads_pools_witness(self.number_threads_witness.unwrap()); - } - if self.max_witness_stored.is_some() { - gpu_params.with_max_witness_stored(self.max_witness_stored.unwrap()); - } - - let server_params = ZiskServerParams::new( - self.port, - self.elf.clone(), - get_witness_computation_lib(self.witness_lib.as_ref()), - self.asm.clone(), - asm_rom, - self.asm_port, - custom_commits_map, - emulator, - proving_key, - self.verbose, - debug_info, - self.verify_constraints, - self.aggregation, - self.final_snark, - gpu_params, - self.unlock_mapped_memory, - self.shared_tables, - ); - - if let Err(e) = ZiskService::new(&server_params)?.run() { - eprintln!("Error starting server: {e}"); - process::exit(1); - } - - Ok(()) - } - - fn print_command_info(&self) { - println!("{} Prove Server", format!("{: >12}", "Command").bright_green().bold()); - println!( - "{} TCP server listening on 127.0.0.1:{}", - format!("{: >12}", "Socket").bright_green().bold(), - self.port - ); - println!("{} {}", format!("{: >12}", "Logfile").bright_green().bold(), LOG_PATH); - println!( - "{: >12} {}", - "Witness Lib".bright_green().bold(), - get_witness_computation_lib(self.witness_lib.as_ref()).display() - ); - - println!("{: >12} {}", "Elf".bright_green().bold(), self.elf.display()); - - if self.asm.is_some() { - let asm_path = self.asm.as_ref().unwrap().display(); - println!("{: >12} {}", "ASM runner".bright_green().bold(), asm_path); - } else { - println!( - "{: >12} {}", - "Emulator".bright_green().bold(), - "Running in emulator mode".bright_yellow() - ); - } - - println!( - "{: >12} {}", - "Proving key".bright_green().bold(), - get_proving_key(self.proving_key.as_ref()).display() - ); - - let std_mode = match &self.debug { - None => "Standard mode", - Some(None) => "Debug mode (fast)", - Some(Some(json_file)) => &format!("Debug mode (from config file: {})", json_file), - }; - println!("{: >12} {}", "STD".bright_green().bold(), std_mode); - // println!("{}", format!("{: >12} {}", "Distributed".bright_green().bold(), "ON (nodes: 4, threads: 32)")); - - println!(); - } -} diff --git a/server/Cargo.toml b/server/Cargo.toml deleted file mode 100644 index 1f0464cec..000000000 --- a/server/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "server" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -keywords = { workspace = true } -repository = { workspace = true } -categories = { workspace = true } - -build = "build.rs" - -[dependencies] -zisk-common = { workspace = true } -executor = { workspace = true } -zisk-witness = { workspace = true } -asm-runner = { workspace = true } - -proofman = { workspace = true } -proofman-common = { workspace = true } -witness = { workspace = true } -anyhow = { workspace = true} -libloading = { workspace = true } -colored = { workspace = true } -fields = { workspace = true } -tracing = { workspace = true} - -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -uuid = { version = "1.6", features = ["v4"] } -clap = { workspace = true } -bytemuck = { workspace = true } -zstd = { workspace = true } - -# Distributed mode (mpi) is only supported on Linux x86_64 -[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] -mpi = { workspace = true } -named-sem = { workspace = true } - -[features] -default = [] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -stats = [] - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(distributed)'] } \ No newline at end of file diff --git a/server/build.rs b/server/build.rs deleted file mode 100644 index 8da52f323..000000000 --- a/server/build.rs +++ /dev/null @@ -1,11 +0,0 @@ -fn main() { - let disable_distributed = - std::env::vars().any(|(k, _)| k == "CARGO_FEATURE_DISABLE_DISTRIBUTED"); - let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); - let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); - - // Distributed feature is only available on linux x86_64 - if !disable_distributed && target_os == "linux" && target_arch == "x86_64" { - println!("cargo:rustc-cfg=distributed"); - } -} diff --git a/server/src/handler_prove.rs b/server/src/handler_prove.rs deleted file mode 100644 index a0eed6cd8..000000000 --- a/server/src/handler_prove.rs +++ /dev/null @@ -1,182 +0,0 @@ -use bytemuck::cast_slice; -use colored::Colorize; -use fields::Goldilocks; -use proofman::ProofMan; -use proofman::{ProofInfo, ProvePhase, ProvePhaseInputs, ProvePhaseResult}; -use proofman_common::ProofOptions; -use serde::{Deserialize, Serialize}; -use std::io::Write; -use std::sync::Arc; -use std::thread::JoinHandle; -use std::{fs::File, path::PathBuf}; -use zisk_common::{ExecutorStats, ProofLog, ZiskExecutionResult, ZiskLib}; - -use crate::{ - ServerConfig, ZiskBaseResponse, ZiskCmdResult, ZiskResponse, ZiskResultCode, ZiskService, -}; - -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct ZiskProveRequest { - pub input: PathBuf, - pub aggregation: bool, - pub rma: bool, - pub final_snark: bool, - pub verify_proofs: bool, - pub minimal_memory: bool, - pub folder: PathBuf, - pub prefix: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskProveResponse { - #[serde(flatten)] - pub base: ZiskBaseResponse, - - server_id: String, - elf_file: String, - input: String, -} -pub struct ZiskServiceProveHandler; - -impl ZiskServiceProveHandler { - pub fn handle( - config: Arc, - request: ZiskProveRequest, - // It is important to keep the witness_lib declaration before the proofman declaration - // to ensure that the witness library is dropped before the proofman. - witness_lib: Arc>>, - proofman: Arc>, - is_busy: Arc, - ) -> (ZiskResponse, Option>) { - is_busy.store(true, std::sync::atomic::Ordering::SeqCst); - - let handle = std::thread::spawn({ - let request_input = request.input.clone(); - let config = config.clone(); - move || { - let start = std::time::Instant::now(); - - let result = proofman - .generate_proof_from_lib( - ProvePhaseInputs::Full(ProofInfo::new(Some(request_input), 1, vec![0], 0)), - ProofOptions::new( - false, - request.aggregation, - request.rma, - request.final_snark, - request.verify_proofs, - request.minimal_memory, - false, - request.folder.clone(), - ), - ProvePhase::Full, - ) - .map_err(|e| anyhow::anyhow!("Error generating proof: {}", e)) - .expect("Failed to generate proof"); - - let (proof_id, vadcop_final_proof) = - if let ProvePhaseResult::Full(proof_id, vadcop_final_proof) = result { - (proof_id, vadcop_final_proof) - } else { - (None, None) - }; - - let elapsed = start.elapsed(); - - if proofman.rank().unwrap() == 0 { - #[allow(clippy::type_complexity)] - let (result, mut _stats): ( - ZiskExecutionResult, - ExecutorStats, - ) = witness_lib.execution_result().expect("Failed to get execution result"); - - proofman.set_barrier(); - let elapsed = elapsed.as_secs_f64(); - tracing::info!(""); - tracing::info!( - "{}", - "--- PROVE SUMMARY ------------------------".bright_green().bold() - ); - if let Some(proof_id) = &proof_id { - tracing::info!(" Proof ID: {}", proof_id); - } - tracing::info!(" ► Statistics"); - tracing::info!( - " time: {} seconds, steps: {}", - elapsed, - result.executed_steps - ); - - // Store the stats in stats.json - #[cfg(feature = "stats")] - { - let stats_id = _stats.next_id(); - _stats.add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); - _stats.store_stats(); - } - - if let Some(proof_id) = proof_id { - let logs = ProofLog::new(result.executed_steps, proof_id, elapsed); - let log_path = - request.folder.join(format!("{}-result.json", request.prefix)); - println!("Writing proof log to: {}", log_path.display()); - ProofLog::write_json_log(&log_path, &logs) - .map_err(|e| anyhow::anyhow!("Error generating log: {}", e)) - .expect("Failed to generate proof"); - // Save the uncompressed vadcop final proof - let output_file_path = request - .folder - .join(format!("{}-vadcop_final_proof.bin", request.prefix)); - - let vadcop_proof = vadcop_final_proof.unwrap(); - let proof_data = cast_slice(&vadcop_proof); - let mut file = - File::create(&output_file_path).expect("Error while creating file"); - file.write_all(proof_data).expect("Error while writing to file"); - } - } - is_busy.store(false, std::sync::atomic::Ordering::SeqCst); - ZiskService::print_waiting_message(&config); - } - }); - - ( - ZiskResponse::ZiskProveResponse(ZiskProveResponse { - base: ZiskBaseResponse { - cmd: "prove".to_string(), - result: ZiskCmdResult::InProgress, - code: ZiskResultCode::Ok, - msg: None, - node: config.asm_runner_options.world_rank, - }, - server_id: config.server_id.to_string(), - elf_file: config.elf.display().to_string(), - input: request.input.display().to_string(), - }), - Some(handle), - ) - } - pub fn process_handle(request: ZiskProveRequest, proofman: Arc>) { - proofman - .generate_proof_from_lib( - ProvePhaseInputs::Full(ProofInfo::new(Some(request.input), 1, vec![0], 0)), - ProofOptions::new( - false, - request.aggregation, - request.rma, - request.final_snark, - request.verify_proofs, - request.minimal_memory, - false, - request.folder.clone(), - ), - ProvePhase::Full, - ) - .map_err(|e| anyhow::anyhow!("Error generating proof: {}", e)) - .expect("Failed to generate proof"); - proofman.set_barrier(); - } -} diff --git a/server/src/handler_shutdown.rs b/server/src/handler_shutdown.rs deleted file mode 100644 index 4185a9d12..000000000 --- a/server/src/handler_shutdown.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::thread::JoinHandle; - -use asm_runner::AsmServices; - -use serde::{Deserialize, Serialize}; - -use crate::{ServerConfig, ZiskBaseResponse, ZiskResponse}; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct ZiskShutdownRequest; - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskShutdownResponse { - #[serde(flatten)] - pub base: ZiskBaseResponse, -} - -pub struct ZiskServiceShutdownHandler; - -impl ZiskServiceShutdownHandler { - pub fn handle( - config: &ServerConfig, - _payload: ZiskShutdownRequest, - asm_services: Option<&AsmServices>, - ) -> (ZiskResponse, Option>) { - tracing::info!( - "<<< [{}] Shutting down ASM microservices.", - config.asm_runner_options.world_rank - ); - - if let Some(asm_services) = asm_services { - let shutdown_result = asm_services.stop_asm_services(); - - if let Err(e) = shutdown_result { - tracing::error!("Failed to stop ASM services: {}", e); - return ( - ZiskResponse::ZiskShutdownResponse(ZiskShutdownResponse { - base: ZiskBaseResponse { - cmd: "shutdown".to_string(), - result: crate::ZiskCmdResult::Error, - code: crate::ZiskResultCode::Error, - msg: Some(format!("Failed to stop ASM services: {e}")), - node: config.asm_runner_options.world_rank, - }, - }), - None, - ); - } - } - - ( - ZiskResponse::ZiskShutdownResponse(ZiskShutdownResponse { - base: ZiskBaseResponse { - cmd: "shutdown".to_string(), - result: crate::ZiskCmdResult::Ok, - code: crate::ZiskResultCode::Ok, - msg: None, - node: config.asm_runner_options.world_rank, - }, - }), - None, - ) - } -} diff --git a/server/src/handler_status.rs b/server/src/handler_status.rs deleted file mode 100644 index 11f244589..000000000 --- a/server/src/handler_status.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::{ - sync::{atomic::AtomicBool, Arc}, - thread::JoinHandle, -}; - -use crate::{ServerConfig, ZiskBaseResponse, ZiskResponse}; - -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct ZiskStatusRequest; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum ZiskStatus { - Idle, - Working, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskStatusResponse { - #[serde(flatten)] - pub base: ZiskBaseResponse, - - server_id: String, - elf_file: String, - uptime: String, - status: ZiskStatus, -} - -pub struct ZiskServiceStatusHandler; - -impl ZiskServiceStatusHandler { - pub fn handle( - config: &ServerConfig, - _payload: ZiskStatusRequest, - is_busy: Arc, - ) -> (ZiskResponse, Option>) { - let uptime = config.launch_time.elapsed(); - - ( - ZiskResponse::ZiskStatusResponse(ZiskStatusResponse { - base: ZiskBaseResponse { - cmd: "status".to_string(), - result: crate::ZiskCmdResult::Ok, - code: crate::ZiskResultCode::Ok, - msg: None, - node: config.asm_runner_options.world_rank, - }, - server_id: config.server_id.to_string(), - elf_file: config.elf.display().to_string(), - uptime: format!("{uptime:.2?}"), - status: if is_busy.load(std::sync::atomic::Ordering::SeqCst) { - ZiskStatus::Working - } else { - ZiskStatus::Idle - }, - }), - None, - ) - } -} diff --git a/server/src/handler_verify_constraints.rs b/server/src/handler_verify_constraints.rs deleted file mode 100644 index 518b88863..000000000 --- a/server/src/handler_verify_constraints.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::{path::PathBuf, sync::Arc, thread::JoinHandle}; - -use crate::{ - ServerConfig, ZiskBaseResponse, ZiskCmdResult, ZiskResponse, ZiskResultCode, ZiskService, -}; -use colored::Colorize; -use fields::Goldilocks; -use proofman::ProofMan; -use proofman_common::DebugInfo; -use serde::{Deserialize, Serialize}; -use zisk_common::{ExecutorStats, ZiskExecutionResult, ZiskLib}; - -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct ZiskVerifyConstraintsRequest { - pub input: PathBuf, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskVerifyConstraintsResponse { - #[serde(flatten)] - pub base: ZiskBaseResponse, - - server_id: String, - elf_file: String, - input: String, -} - -pub struct ZiskServiceVerifyConstraintsHandler; - -impl ZiskServiceVerifyConstraintsHandler { - pub fn handle( - config: Arc, - request: ZiskVerifyConstraintsRequest, - // It is important to keep the witness_lib declaration before the proofman declaration - // to ensure that the witness library is dropped before the proofman. - witness_lib: Arc>>, - proofman: Arc>, - is_busy: Arc, - debug_info: Arc, - ) -> (ZiskResponse, Option>) { - is_busy.store(true, std::sync::atomic::Ordering::SeqCst); - - let handle = std::thread::spawn({ - let config = config.clone(); - move || { - let start = std::time::Instant::now(); - - proofman - .verify_proof_constraints_from_lib(&debug_info, false) - .map_err(|e| anyhow::anyhow!("Error verifying proof: {}", e)) - .expect("Failed to generate proof"); - proofman.set_barrier(); - let elapsed = start.elapsed(); - - #[allow(clippy::type_complexity)] - let (result, mut _stats): (ZiskExecutionResult, ExecutorStats) = - witness_lib.execution_result().expect("Failed to get execution result"); - - println!(); - tracing::info!( - "{}", - "--- VERIFY CONSTRAINTS SUMMARY ------------------------".bright_green().bold() - ); - tracing::info!(" ► Statistics"); - tracing::info!( - " time: {} seconds, steps: {}", - elapsed.as_secs_f32(), - result.executed_steps - ); - - is_busy.store(false, std::sync::atomic::Ordering::SeqCst); - ZiskService::print_waiting_message(&config); - - // Store the stats in stats.json - #[cfg(feature = "stats")] - { - let stats_id = _stats.next_id(); - _stats.add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); - _stats.store_stats(); - } - } - }); - - ( - ZiskResponse::ZiskVerifyConstraintsResponse(ZiskVerifyConstraintsResponse { - base: ZiskBaseResponse { - cmd: "verify_constraints".to_string(), - result: ZiskCmdResult::InProgress, - code: ZiskResultCode::Ok, - msg: None, - node: config.asm_runner_options.world_rank, - }, - server_id: config.server_id.to_string(), - elf_file: config.elf.display().to_string(), - input: request.input.display().to_string(), - }), - Some(handle), - ) - } - pub fn process_handle( - _request: ZiskVerifyConstraintsRequest, - proofman: Arc>, - debug_info: Arc, - ) { - proofman - .verify_proof_constraints_from_lib(&debug_info, false) - .map_err(|e| anyhow::anyhow!("Error verifying proof: {}", e)) - .expect("Failed to generate proof"); - proofman.set_barrier(); - } -} diff --git a/server/src/lib.rs b/server/src/lib.rs deleted file mode 100644 index 3ac5597ba..000000000 --- a/server/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod handler_prove; -mod handler_shutdown; -mod handler_status; -mod handler_verify_constraints; -mod zisk_service; - -pub use handler_prove::*; -pub use handler_shutdown::*; -pub use handler_status::*; -pub use handler_verify_constraints::*; -pub use zisk_service::*; diff --git a/server/src/zisk_service.rs b/server/src/zisk_service.rs deleted file mode 100644 index e23d32fe9..000000000 --- a/server/src/zisk_service.rs +++ /dev/null @@ -1,609 +0,0 @@ -use std::{ - collections::HashMap, - fmt, - io::{BufRead, BufReader, Write}, - net::{TcpListener, TcpStream}, - path::PathBuf, - sync::{atomic::AtomicBool, Arc}, - time::Instant, -}; - -use asm_runner::{AsmRunnerOptions, AsmServices}; -use fields::Goldilocks; -use libloading::{Library, Symbol}; -use proofman::ProofMan; -use proofman_common::{initialize_logger, DebugInfo, ParamsGPU}; -use serde::{Deserialize, Serialize}; -use tracing::error; -use uuid::Uuid; -use zisk_common::{info_file, ZiskLib, ZiskLibInitFn}; - -use anyhow::Result; - -use crate::{ - handler_prove::{ZiskProveRequest, ZiskServiceProveHandler}, - handler_shutdown::ZiskServiceShutdownHandler, - handler_status::{ZiskStatusRequest, ZiskStatusResponse}, - handler_verify_constraints::{ - ZiskServiceVerifyConstraintsHandler, ZiskVerifyConstraintsRequest, - }, - ZiskProveResponse, ZiskServiceStatusHandler, ZiskShutdownRequest, ZiskShutdownResponse, - ZiskVerifyConstraintsResponse, -}; - -pub struct ZiskServerParams { - /// Port number for the server to listen on - pub port: u16, - - /// Path to the ELF file - pub elf: PathBuf, - - /// Path to the witness computation dynamic library - pub witness_lib: PathBuf, - - /// Path to the ASM file (optional) - pub asm: Option, - - /// Path to the ASM ROM file (optional) - pub asm_rom: Option, - - pub asm_port: Option, - - /// Map of custom commits - pub custom_commits_map: HashMap, - - /// Flag indicating whether to use the prebuilt emulator - pub emulator: bool, - - /// Path to the proving key - pub proving_key: PathBuf, - - /// Verbosity level for logging - pub verbose: u8, - - /// Debug information - pub debug_info: DebugInfo, - - /// Time when the server was launched - pub launch_time: Instant, - - /// Unique identifier for the server instance - pub server_id: Uuid, - - pub verify_constraints: bool, - pub aggregation: bool, - pub final_snark: bool, - - pub gpu_params: ParamsGPU, - - pub unlock_mapped_memory: bool, - - pub shared_tables: bool, -} - -#[allow(clippy::too_many_arguments)] -impl ZiskServerParams { - pub fn new( - port: u16, - elf: PathBuf, - witness_lib: PathBuf, - asm: Option, - asm_rom: Option, - asm_port: Option, - custom_commits_map: HashMap, - emulator: bool, - proving_key: PathBuf, - verbose: u8, - debug: DebugInfo, - verify_constraints: bool, - aggregation: bool, - final_snark: bool, - gpu_params: ParamsGPU, - unlock_mapped_memory: bool, - shared_tables: bool, - ) -> Self { - Self { - port, - elf, - witness_lib, - asm, - asm_rom, - asm_port, - custom_commits_map, - emulator, - proving_key, - verbose, - debug_info: debug, - launch_time: Instant::now(), - server_id: Uuid::new_v4(), - verify_constraints, - aggregation, - final_snark, - gpu_params, - unlock_mapped_memory, - shared_tables, - } - } -} - -pub struct ServerConfig { - /// Port number for the server to listen on - pub port: u16, - - /// Path to the ELF file - pub elf: PathBuf, - - /// Path to the witness computation dynamic library - pub witness_lib: PathBuf, - - /// Path to the ASM file (optional) - pub asm: Option, - - /// Path to the ASM ROM file (optional) - pub asm_rom: Option, - - /// Map of custom commits - pub custom_commits_map: HashMap, - - /// Flag indicating whether to use the prebuilt emulator - pub emulator: bool, - - /// Path to the proving key - pub proving_key: PathBuf, - - /// Verbosity level for logging - pub verbose: u8, - - /// Debug information - pub debug_info: Arc, - - /// Time when the server was launched - pub launch_time: Instant, - - /// Unique identifier for the server instance - pub server_id: Uuid, - - /// Additional options for the ASM runner - pub asm_runner_options: AsmRunnerOptions, - - pub verify_constraints: bool, - pub aggregation: bool, - pub final_snark: bool, - - pub gpu_params: ParamsGPU, - - pub shared_tables: bool, -} - -#[allow(clippy::too_many_arguments)] -impl ServerConfig { - pub fn new( - port: u16, - elf: PathBuf, - witness_lib: PathBuf, - asm: Option, - asm_rom: Option, - custom_commits_map: HashMap, - emulator: bool, - proving_key: PathBuf, - verbose: u8, - debug: DebugInfo, - asm_runner_options: AsmRunnerOptions, - verify_constraints: bool, - aggregation: bool, - final_snark: bool, - gpu_params: ParamsGPU, - shared_tables: bool, - ) -> Self { - Self { - port, - elf, - witness_lib, - asm, - asm_rom, - custom_commits_map, - emulator, - proving_key, - verbose, - debug_info: Arc::new(debug), - launch_time: Instant::now(), - server_id: Uuid::new_v4(), - asm_runner_options, - verify_constraints, - aggregation, - final_snark, - gpu_params, - shared_tables, - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "command", rename_all = "snake_case")] -pub enum ZiskRequest { - Status { - #[serde(flatten)] - payload: ZiskStatusRequest, - }, - Shutdown { - #[serde(flatten)] - payload: ZiskShutdownRequest, - }, - Prove { - #[serde(flatten)] - payload: ZiskProveRequest, - }, - VerifyConstraints { - #[serde(flatten)] - payload: ZiskVerifyConstraintsRequest, - }, -} - -impl fmt::Display for ZiskRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let variant = match self { - ZiskRequest::Status { .. } => "Status", - ZiskRequest::Shutdown { .. } => "Shutdown", - ZiskRequest::Prove { .. } => "Prove", - ZiskRequest::VerifyConstraints { .. } => "VerifyConstraints", - }; - write!(f, "{variant}") - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ZiskCmdResult { - Ok, - Error, - InProgress, - Busy, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u32)] -pub enum ZiskResultCode { - Ok = 0, - Error = 1001, - InvalidRequest = 1002, - Busy = 1003, -} - -// Serialize as a number -impl Serialize for ZiskResultCode { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_u32(*self as u32) - } -} - -// Deserialize from a number -impl<'de> Deserialize<'de> for ZiskResultCode { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let value = u32::deserialize(deserializer)?; - match value { - 0 => Ok(ZiskResultCode::Ok), - 1001 => Ok(ZiskResultCode::Error), - 1002 => Ok(ZiskResultCode::InvalidRequest), - 1003 => Ok(ZiskResultCode::Busy), - _ => Err(serde::de::Error::custom(format!("Unknown ZiskResultCode: {value}"))), - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskBaseResponse { - pub cmd: String, - pub result: ZiskCmdResult, - pub code: ZiskResultCode, - pub node: i32, - - #[serde(skip_serializing_if = "Option::is_none")] - pub msg: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskInvalidRequestResponse { - #[serde(flatten)] - pub base: ZiskBaseResponse, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "zisk_response", rename_all = "snake_case")] -pub enum ZiskResponse { - ZiskStatusResponse(ZiskStatusResponse), - ZiskShutdownResponse(ZiskShutdownResponse), - ZiskProveResponse(ZiskProveResponse), - ZiskVerifyConstraintsResponse(ZiskVerifyConstraintsResponse), - ZiskErrorResponse(ZiskBaseResponse), - ZiskInvalidRequestResponse { base: ZiskBaseResponse }, -} - -pub struct ZiskService { - config: Arc, - // It is important to keep the witness_lib declaration before the proofman declaration - // to ensure that the witness library is dropped before the proofman. - witness_lib: Arc>>, - proofman: Arc>, - asm_services: Option, - is_busy: Arc, - pending_handles: Vec>, -} - -impl ZiskService { - pub fn new(params: &ZiskServerParams) -> Result { - info_file!("Starting asm microservices..."); - let library = - unsafe { Library::new(params.witness_lib.clone()).expect("Failed to load library") }; - let witness_lib_constructor: Symbol> = - unsafe { library.get(b"init_library").expect("Failed to get symbol") }; - - let unlock_mapped_memory = params.unlock_mapped_memory; - - let mut witness_lib = witness_lib_constructor( - params.verbose.into(), - params.elf.clone(), - params.asm.clone(), - params.asm_rom.clone(), - params.asm_port, - unlock_mapped_memory, - params.shared_tables, - ) - .expect("Failed to initialize witness library"); - - let proofman = ProofMan::::new( - params.proving_key.clone(), - params.custom_commits_map.clone(), - params.verify_constraints, - params.aggregation, - params.final_snark, - params.gpu_params.clone(), - params.verbose.into(), - witness_lib.get_packed_info(), - ) - .expect("Failed to initialize proofman"); - - let world_rank = proofman.get_world_rank(); - let local_rank = proofman.get_local_rank(); - - initialize_logger(params.verbose.into(), Some(world_rank)); - - let port = params.port + local_rank as u16; - - let asm_runner_options = AsmRunnerOptions::new() - .with_verbose(params.verbose > 0) - .with_base_port(params.asm_port) - .with_world_rank(world_rank) - .with_local_rank(local_rank) - .with_unlock_mapped_memory(params.unlock_mapped_memory); - - let asm_services = if params.emulator { - None - } else { - let asm_services = AsmServices::new(world_rank, local_rank, params.asm_port); - asm_services - .start_asm_services(params.asm.as_ref().unwrap(), asm_runner_options.clone())?; - Some(asm_services) - }; - - proofman.register_witness(witness_lib.as_mut(), library)?; - - let witness_lib = Arc::new(witness_lib); - - let config = ServerConfig::new( - port, - params.elf.clone(), - params.witness_lib.clone(), - params.asm.clone(), - params.asm_rom.clone(), - params.custom_commits_map.clone(), - params.emulator, - params.proving_key.clone(), - params.verbose, - params.debug_info.clone(), - asm_runner_options, - params.verify_constraints, - params.aggregation, - params.final_snark, - params.gpu_params.clone(), - params.shared_tables, - ); - - Ok(Self { - config: Arc::new(config), - proofman: Arc::new(proofman), - witness_lib, - asm_services, - is_busy: Arc::new(AtomicBool::new(false)), - pending_handles: Vec::new(), - }) - } - - pub fn print_waiting_message(config: &ServerConfig) { - info_file!( - "ZisK Server waiting for requests on port {} for ELF '{}'", - config.port, - config.elf.display() - ); - } - - pub fn run(&mut self) -> std::io::Result<()> { - if self.proofman.rank() == Some(0) || self.proofman.rank().is_none() { - let listener = TcpListener::bind(("127.0.0.1", self.config.port))?; - Self::print_waiting_message(&self.config); - - for stream in listener.incoming() { - match stream { - Ok(stream) => { - let config = Arc::clone(&self.config); - if let Ok(should_shutdown) = self.handle_client(stream, config) { - if should_shutdown { - info_file!("{}", "Shutdown signal received. Exiting."); - break; - } - } - } - Err(e) => error!("Connection failed: {}", e), - } - } - } else { - // Other MPI ranks just wait for rank 0 instructions - loop { - self.receive_request()?; - } - } - - Ok(()) - } - - fn handle_client( - &mut self, - mut stream: TcpStream, - config: Arc, - ) -> std::io::Result { - let mut reader = BufReader::new(&stream); - let mut line = String::new(); - - reader.read_line(&mut line)?; - - let request: ZiskRequest = match serde_json::from_str(&line) { - Ok(req) => req, - Err(e) => { - let response = ZiskResponse::ZiskInvalidRequestResponse { - base: ZiskBaseResponse { - cmd: "invalid_request".to_string(), - result: ZiskCmdResult::Error, - code: ZiskResultCode::InvalidRequest, - msg: Some(format!("Invalid request format or data. {e}")), - node: config.asm_runner_options.world_rank, - }, - }; - Self::send_json(&mut stream, &response)?; - return Ok(false); - } - }; - - info_file!("Received '{}' request", request); - - let mut must_shutdown = false; - - if self.is_busy.load(std::sync::atomic::Ordering::SeqCst) - && !matches!(request, ZiskRequest::Status { .. }) - { - let response = ZiskResponse::ZiskErrorResponse(ZiskBaseResponse { - cmd: "busy".to_string(), - result: ZiskCmdResult::InProgress, - code: ZiskResultCode::Busy, - msg: Some("Server is busy, please try again later.".to_string()), - node: config.asm_runner_options.world_rank, - }); - Self::send_json(&mut stream, &response)?; - return Ok(false); - } - - // Wait for all pending handles to finish - for handle in self.pending_handles.drain(..) { - handle.join().expect("Failed to join thread"); - } - - let (response, handle) = match request { - ZiskRequest::Status { payload } => { - let result = - ZiskServiceStatusHandler::handle(&config, payload, self.is_busy.clone()); - Self::print_waiting_message(&config); - result - } - ZiskRequest::Shutdown { payload } => { - must_shutdown = true; - ZiskServiceShutdownHandler::handle(&config, payload, self.asm_services.as_ref()) - } - ZiskRequest::VerifyConstraints { payload } => { - let mut bytes = Vec::new(); - bytes.push(0u8); // option 0 for verify constraints - let serialized: Vec = - serde_json::to_vec(&payload).expect("Failed to serialize payload"); - bytes.extend_from_slice(&serialized); - self.proofman.mpi_broadcast(&mut bytes); - - ZiskServiceVerifyConstraintsHandler::handle( - config.clone(), - payload, - self.witness_lib.clone(), - self.proofman.clone(), - self.is_busy.clone(), - self.config.debug_info.clone(), - ) - } - ZiskRequest::Prove { payload } => { - let mut bytes = Vec::new(); - bytes.push(1u8); // option 1 for prove - let serialized: Vec = - serde_json::to_vec(&payload).expect("Failed to serialize payload"); - bytes.extend_from_slice(&serialized); - self.proofman.mpi_broadcast(&mut bytes); - - ZiskServiceProveHandler::handle( - config.clone(), - payload, - self.witness_lib.clone(), - self.proofman.clone(), - self.is_busy.clone(), - ) - } - }; - if let Some(handle) = handle { - self.pending_handles.push(handle); - } - - Self::send_json(&mut stream, &response)?; - Ok(must_shutdown) - } - - fn send_json(stream: &mut TcpStream, response: &ZiskResponse) -> std::io::Result<()> { - let json = serde_json::to_string(response)?; - stream.write_all(json.as_bytes())?; - stream.flush() - } - - fn receive_request(&self) -> std::io::Result<()> { - let mut bytes: Vec = Vec::new(); - self.proofman.mpi_broadcast(&mut bytes); - - // extract byte 0 to decide the option - let option = bytes.first().cloned(); - match option { - Some(0) => { - info_file!("Received process 'VerifyConstraints' request"); - // Deserialize the rest of bytes into ZiskVerifyConstraintsRequest - let payload: ZiskVerifyConstraintsRequest = - serde_json::from_slice(&bytes[1..]).expect("Failed to deserialize payload"); - ZiskServiceVerifyConstraintsHandler::process_handle( - payload, - self.proofman.clone(), - self.config.debug_info.clone(), - ); - } - Some(1) => { - info_file!("Received process 'Prove' request"); - // Prove request - // Deserialize the rest of bytes into ZiskProveRequest - let payload: ZiskProveRequest = - serde_json::from_slice(&bytes[1..]).expect("Failed to deserialize payload"); - ZiskServiceProveHandler::process_handle(payload, self.proofman.clone()); - } - _ => { - info_file!( - "Rank {} received unknown request: {:?}", - self.proofman.rank().unwrap_or(0), - option - ); - } - } - Ok(()) - } -} From 85c4b208d621e00bb217d78243b4afe96f7b97e2 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 15 Jan 2026 16:54:57 +0100 Subject: [PATCH 238/782] Fix fcall_secp256k1_ecdsa_verify() hints result length --- ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs index a7b50fbf0..b9b95b1c3 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs @@ -62,6 +62,7 @@ pub fn fcall_secp256k1_ecdsa_verify( // Hint the result #[cfg(feature = "hints")] { + hints.push(results.len() as u64); hints.extend_from_slice(&results); } From 74101110a58c997e93ea414930df943afd2e6392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:54:47 +0000 Subject: [PATCH 239/782] Fixing a bug in ecdsa --- .../src/zisklib/lib/secp256k1/curve.rs | 8 ++++---- .../src/zisklib/lib/secp256k1/ecdsa.rs | 16 +++++++++------- .../src/zisklib/lib/secp256k1/field.rs | 2 ++ .../src/zisklib/lib/secp256k1/scalar.rs | 2 ++ 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index 2e61f8905..222a46291 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -19,7 +19,7 @@ const IDENTITY_POINT256: SyscallPoint256 = SyscallPoint256 { x: IDENTITY_X, y: I const G_POINT256: SyscallPoint256 = SyscallPoint256 { x: G_X, y: G_Y }; /// Given a x-coordinate `x_bytes` and a parity `y_is_odd`, -/// this function decompresses the point on the secp256k1 curve. +/// this function decompresses a non-infinity point on the secp256k1 curve. pub fn secp256k1_decompress(x: &[u64; 4], y_is_odd: bool) -> Result<([u64; 4], [u64; 4]), bool> { // Calculate the y-coordinate of the point: y = sqrt(x³ + 7) let x_sq = secp256k1_fp_square(x); @@ -37,7 +37,7 @@ pub fn secp256k1_decompress(x: &[u64; 4], y_is_odd: bool) -> Result<([u64; 4], [ Ok((*x, y)) } -/// Converts a non-infinity point `p` on the Secp256k1 curve from projective coordinates to affine coordinates +/// Converts a non-infinity point `p` on the Secp256k1 curve from jacobian coordinates to affine coordinates pub fn secp256k1_to_affine(p: &[u64; 12]) -> [u64; 8] { let z: [u64; 4] = [p[8], p[9], p[10], p[11]]; @@ -92,7 +92,7 @@ pub fn secp256k1_is_on_curve(p: &[u64; 8]) -> bool { /// /// Note: There are no (non-infinity) points of order 2 in Secp256k1. /// All (non-infinity) points are of prime order N. -fn secp256k1_scalar_mul(k: &[u64; 4], p: &[u64; 8]) -> Option<[u64; 8]> { +pub fn secp256k1_scalar_mul(k: &[u64; 4], p: &[u64; 8]) -> Option<[u64; 8]> { // Direct cases: k = 0, k = 1, k = 2 if eq(k, &ZERO_256) { return None; @@ -670,7 +670,7 @@ pub unsafe extern "C" fn secp256k1_decompress_c( } /// # Safety -/// - `p_ptr` must point to 12 u64s (projective point) +/// - `p_ptr` must point to 12 u64s (jacobian point) /// - `out_ptr` must point to at least 8 u64s #[no_mangle] pub unsafe extern "C" fn secp256k1_to_affine_c(p_ptr: *const u64, out_ptr: *mut u64) { diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs index e44255fbf..98e50a109 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs @@ -25,20 +25,22 @@ use super::{ /// Verifies the signature (r, s) over the message hash z using the public key pk /// Returns true if the signature is valid, false otherwise pub fn secp256k1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u64; 4]) -> bool { - // Ecdsa verification computes P = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]PK - // We can equivalently hint p, check it's correct and verify that - // [z]G + [r]pk + [-s]P == 𝒪, + // Ecdsa verification computes (x, y) = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]PK + // and checks that x ≡ r (mod n) + // We can equivalently hint y, and verify that + // [z]G + [r]PK + [-s](r,y) == 𝒪, // saving us from expensive fn arithmetic // Hint the result - let p = fcall_secp256k1_ecdsa_verify(pk, z, r, s); + let coords = fcall_secp256k1_ecdsa_verify(pk, z, r, s); + let point = [r[0], r[1], r[2], r[3], coords[4], coords[5], coords[6], coords[7]]; // Check the recovered point is valid - assert!(secp256k1_is_on_curve(&p)); // Note: Identity point would be raised here + assert!(secp256k1_is_on_curve(&point)); // Note: Identity point would be raised here - // Check that [z]G + [r]pk + [-s]P == 𝒪 + // Check that [z]G + [r]PK + [-s](r,y) == 𝒪 let neg_s = secp256k1_fn_neg(s); - secp256k1_triple_scalar_mul_with_g(z, r, &neg_s, pk, &p).is_none().then_some(()).is_some() + secp256k1_triple_scalar_mul_with_g(z, r, &neg_s, pk, &point).is_none() } // ==================== C FFI Functions ==================== diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs index 04e4380ce..9e3598972 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs @@ -123,6 +123,8 @@ pub fn secp256k1_fp_sqrt(x: &[u64; 4], parity: u64) -> ([u64; 4], bool) { } } +// ==================== C FFI Functions ==================== + /// # Safety /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs index 8ee7f6303..fc2a39a9b 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs @@ -83,6 +83,8 @@ pub fn secp256k1_fn_inv(x: &[u64; 4]) -> [u64; 4] { x_inv } +// ==================== C FFI Functions ==================== + /// # Safety /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s From 61f84ceff955ff68aaaa8f10507647dac8c91846 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 15 Jan 2026 14:08:05 +0100 Subject: [PATCH 240/782] Fix fcall(ecdsa_verify) in lib-c --- lib-c/c/src/ec/ec.cpp | 198 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 184 insertions(+), 14 deletions(-) diff --git a/lib-c/c/src/ec/ec.cpp b/lib-c/c/src/ec/ec.cpp index 6cc92b70a..ac2f994af 100644 --- a/lib-c/c/src/ec/ec.cpp +++ b/lib-c/c/src/ec/ec.cpp @@ -98,7 +98,7 @@ int inline AddPointEcDblFe (RawFec::Element &x1, RawFec::Element &y1) fec.add(aux2, y1, y1); if (fec.isZero(aux2)) { - printf("AddPointEc() got denominator=0 1\n"); + printf("AddPointEcDbl() got denominator=0 1\n"); return -1; } fec.div(s, aux1, aux2); @@ -202,7 +202,7 @@ int secp256k1_ecdsa_verify ( ) { // Convert z, r, s inputs to field elements - RawFec::Element z, r, s; + RawFnec::Element z, r, s; array2fe(_z, z); array2fe(_r, r); array2fe(_s, s); @@ -214,17 +214,16 @@ int secp256k1_ecdsa_verify ( // 4. Computes and returns the curve point p = u1·G + u2·PK // s_inv = s⁻¹ mod n - RawFec::Element s_inv; - fec.inv(s_inv, s); + RawFnec::Element s_inv; + fnec.inv(s_inv, s); // u1 = z·s_inv mod n - RawFec::Element u1; - fec.mul(u1, z, s_inv); + RawFnec::Element u1; + fnec.mul(u1, z, s_inv); // u2 = r·s_inv mod n - RawFec::Element u2; - fec.mul(u2, r, s_inv); - + RawFnec::Element u2; + fnec.mul(u2, r, s_inv); uint64_t u1_array[4]; uint64_t u2_array[4]; fe2array(u1, u1_array); @@ -235,6 +234,175 @@ int secp256k1_ecdsa_verify ( return 0; } +const uint64_t IDENTITY[8] = {0,0,0,0,0,0,0,0}; + +void secp256k1_curve_add( + const uint64_t * p, // 8 x 64 bits + const uint64_t * q, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +) +{ + // Get the 2 points coordinates + const uint64_t * x1 = &p[0]; + const uint64_t * y1 = &p[4]; + const uint64_t * x2 = &q[0]; + const uint64_t * y2 = &q[4]; + + // If p==q return dbl(p) + if (x1[0] == x2[0] && + x1[1] == x2[1] && + x1[2] == x2[2] && + x1[3] == x2[3]) + { + if (y1[0] == y2[0] && + y1[1] == y2[1] && + y1[2] == y2[2] && + y1[3] == y2[3]) { + secp256k1_curve_dbl(p, r); + return; + } else { + for (int i = 0; i < 8; i++) { + r[i] = IDENTITY[i]; + } + return; + } + } + + // If p==0 return q + if ( p[0] == IDENTITY[0] && + p[1] == IDENTITY[1] && + p[2] == IDENTITY[2] && + p[3] == IDENTITY[3] && + p[4] == IDENTITY[4] && + p[5] == IDENTITY[5] && + p[6] == IDENTITY[6] && + p[7] == IDENTITY[7] ) + { + for (int i = 0; i < 8; i++) + { + r[i] = q[i]; + } + return; + } + // if q == 0 return p + else if ( q[0] == IDENTITY[0] && + q[1] == IDENTITY[1] && + q[2] == IDENTITY[2] && + q[3] == IDENTITY[3] && + q[4] == IDENTITY[4] && + q[5] == IDENTITY[5] && + q[6] == IDENTITY[6] && + q[7] == IDENTITY[7] ) + { + for (int i = 0; i < 8; i++) + { + r[i] = p[i]; + } + return; + } + + // Convert coordinates to field elements + RawFec::Element x1_fe, y1_fe, x2_fe, y2_fe; + array2fe(x1, x1_fe); + array2fe(y1, y1_fe); + array2fe(x2, x2_fe); + array2fe(y2, y2_fe); + + // Calculate lambda = (y2 - y1) / (x2 - x1) + RawFec::Element y2_minus_y1; + fec.sub(y2_minus_y1, y2_fe, y1_fe); + RawFec::Element x2_minus_x1; + fec.sub(x2_minus_x1, x2_fe, x1_fe); + RawFec::Element x2_minus_x1_inv; + fec.inv(x2_minus_x1_inv, x2_minus_x1); + RawFec::Element lambda; + fec.mul(lambda, y2_minus_y1, x2_minus_x1_inv); + + // Calculate x3 = lambda^2 - (x1 + x2) + RawFec::Element x3_fe; + RawFec::Element lambda_sq; + fec.square(lambda_sq, lambda); + RawFec::Element x1_plus_x2; + fec.add(x1_plus_x2, x1_fe, x2_fe); + fec.sub(x3_fe, lambda_sq, x1_plus_x2); + + // Calculate y3 = lambda * (x1 - x3) - y1 + RawFec::Element y3_fe; + RawFec::Element x1_minus_x3; + fec.sub(x1_minus_x3, x1_fe, x3_fe); + RawFec::Element lambda_x1_minus_x3; + fec.mul(lambda_x1_minus_x3, lambda, x1_minus_x3); + fec.sub(y3_fe, lambda_x1_minus_x3, y1_fe); + + // Convert to result + fe2array(x3_fe, r); + fe2array(y3_fe, r + 4); +} + +void secp256k1_curve_dbl( + const uint64_t * p, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +) +{ + // If p==0 return p + if ( p[0] == IDENTITY[0] && + p[1] == IDENTITY[1] && + p[2] == IDENTITY[2] && + p[3] == IDENTITY[3] && + p[4] == IDENTITY[4] && + p[5] == IDENTITY[5] && + p[6] == IDENTITY[6] && + p[7] == IDENTITY[7] ) + { + for (int i = 0; i < 8; i++) + { + r[i] = p[i]; + } + return; + } + + // Convert coordinates to field elements + uint64_t * x = (uint64_t *)&p[0]; + uint64_t * y = (uint64_t *)&p[4]; + RawFec::Element x_fe, y_fe; + array2fe(x, x_fe); + array2fe(y, y_fe); + + // Calculate lambda = (3*x1^2) / (2*y1) + RawFec::Element x1_sq; + fec.square(x1_sq, x_fe); + RawFec::Element three; + fec.fromUI(three, 3); + RawFec::Element three_x1_sq; + fec.mul(three_x1_sq, x1_sq, three); + RawFec::Element two_y1; + fec.add(two_y1, y_fe, y_fe); + RawFec::Element two_y1_inv; + fec.inv(two_y1_inv, two_y1); + RawFec::Element lambda; + fec.mul(lambda, three_x1_sq, two_y1_inv); + + // Calculate x3 = lambda^2 - 2*x1 + RawFec::Element lambda_sq; + fec.square(lambda_sq, lambda); + RawFec::Element two_x1; + fec.add(two_x1, x_fe, x_fe); + RawFec::Element x3_fe; + fec.sub(x3_fe, lambda_sq, two_x1); + + // Calculate y3 = lambda * (x1 - x3) - y1 + RawFec::Element x1_minus_x3; + fec.sub(x1_minus_x3, x_fe, x3_fe); + RawFec::Element lambda_x1_minus_x3; + fec.mul(lambda_x1_minus_x3, lambda, x1_minus_x3); + RawFec::Element y3_fe; + fec.sub(y3_fe, lambda_x1_minus_x3, y_fe); + + // Convert to result + fe2array(x3_fe, r); + fe2array(y3_fe, r + 4); +} + int secp256k1_curve_dbl_scalar_mul( const uint64_t * k1, // 4 x 64 bits const uint64_t * p1, // 8 x 64 bits @@ -251,18 +419,20 @@ int secp256k1_curve_dbl_scalar_mul( uint64_t i = ii; // r = r + r - AddPointEcDbl(r, r); + secp256k1_curve_dbl(r, r); // If k1[i] == 1 then r = r + p1 uint64_t k1_bit = (k1[i / 64] >> (i % 64)) & 1; - if (k1_bit == 1) { - AddPointEcP(0, r, p1, r); + if (k1_bit == 1) + { + secp256k1_curve_add(r, p1, r); } // If k2[i] == 1 then r = r + p2 uint64_t k2_bit = (k2[i / 64] >> (i % 64)) & 1; - if (k2_bit == 1) { - AddPointEcP(0, r, p2, r); + if (k2_bit == 1) + { + secp256k1_curve_add(r, p2, r); } } From 97e26ca5ce74647063e9138ec1a2aa5d6ac5a161 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 15 Jan 2026 14:13:48 +0100 Subject: [PATCH 241/782] Add missing secp256k1 functions to ec.hpp --- lib-c/c/src/ec/ec.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib-c/c/src/ec/ec.hpp b/lib-c/c/src/ec/ec.hpp index ef0745d50..a3fadab21 100644 --- a/lib-c/c/src/ec/ec.hpp +++ b/lib-c/c/src/ec/ec.hpp @@ -32,6 +32,17 @@ int secp256k1_ecdsa_verify ( uint64_t * result // 8 x 64 bits ); +void secp256k1_curve_add( + const uint64_t * p, // 8 x 64 bits + const uint64_t * q, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +); + +void secp256k1_curve_dbl( + const uint64_t * p, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +); + int secp256k1_curve_dbl_scalar_mul( const uint64_t * k1, // 4 x 64 bits const uint64_t * p1, // 8 x 64 bits From ee322599a77d2006795c2382489d559c68f41824 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:22:39 +0000 Subject: [PATCH 242/782] add allow-multiple-definition flag when building the project to avoid linking problems --- .cargo/config.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..a93d96687 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.x86_64-unknown-linux-gnu] +rustflags = ["-C", "link-arg=-Wl,--allow-multiple-definition"] From f1d24237f7a8cc347809d14ab15060bd5356f5af Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:02:16 +0000 Subject: [PATCH 243/782] decoupled emulators --- common/src/zisk_lib_init.rs | 6 + executor/src/emu_asm.rs | 400 +++++++++++++++++++++ executor/src/emu_rust.rs | 199 +++++++++++ executor/src/executor.rs | 525 +++------------------------- executor/src/lib.rs | 30 ++ executor/src/sm_static_bundle.rs | 2 +- witness-computation/src/zisk_lib.rs | 1 - 7 files changed, 682 insertions(+), 481 deletions(-) create mode 100644 executor/src/emu_asm.rs create mode 100644 executor/src/emu_rust.rs diff --git a/common/src/zisk_lib_init.rs b/common/src/zisk_lib_init.rs index 7a7465765..0ec1d50b9 100644 --- a/common/src/zisk_lib_init.rs +++ b/common/src/zisk_lib_init.rs @@ -11,6 +11,12 @@ pub struct ZiskExecutionResult { pub executed_steps: u64, } +impl ZiskExecutionResult { + pub fn new(executed_steps: u64) -> Self { + Self { executed_steps } + } +} + #[derive(Debug, Clone)] pub struct Stats { pub airgroup_id: usize, diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs new file mode 100644 index 000000000..e0e6c4c97 --- /dev/null +++ b/executor/src/emu_asm.rs @@ -0,0 +1,400 @@ +use std::{ + collections::HashMap, + path::PathBuf, + sync::{Arc, Mutex}, + thread::JoinHandle, +}; + +use crate::{DeviceMetricsByChunk, DummyCounter, StaticSMBundle, ZiskExecutor}; +use asm_runner::{ + write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, + MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, TaskFactory, +}; +use data_bus::DataBusTrait; +use fields::PrimeField64; +use proofman_common::ProofCtx; +use rayon::prelude::*; +use sm_rom::RomSM; +use zisk_common::{ + io::ZiskStdin, BusDeviceMetrics, ChunkId, EmuTrace, ExecutorStatsHandle, PayloadType, + ZiskExecutionResult, +}; +use zisk_core::{ZiskRom, MAX_INPUT_SIZE}; +use ziskemu::ZiskEmulator; + +pub type DeviceMetricsList = Vec; +pub type NestedDeviceMetricsList = HashMap; + +pub struct EmulatorAsm { + /// ZisK ROM, a binary file containing the ZisK program to be executed. + pub zisk_rom: Arc, + + /// World rank for distributed execution. Default to 0 for single-node execution. + world_rank: i32, + + /// Local rank for distributed execution. Default to 0 for single-node execution. + local_rank: i32, + + /// Optional baseline port to communicate with assembly microservices. + base_port: Option, + + /// Map unlocked flag + /// This is used to unlock the memory map for the ROM file. + unlock_mapped_memory: bool, + + /// Chunk size for processing. + chunk_size: u64, + + /// Optional ROM state machine, used for assembly ROM execution. + rom_sm: Option>, + + asm_shmem_mt: Arc>>, + asm_shmem_mo: Arc>>, + asm_shmem_rh: Arc>>, + + /// Shared memory writers for each assembly service. + shmem_input_writer: [Arc>>; AsmServices::SERVICES.len()], +} + +impl EmulatorAsm { + #[allow(clippy::too_many_arguments)] + pub fn new( + zisk_rom: Arc, + asm_path: Option, + world_rank: i32, + local_rank: i32, + base_port: Option, + unlock_mapped_memory: bool, + chunk_size: u64, + rom_sm: Option>, + ) -> Self { + let (asm_shmem_mt, asm_shmem_mo) = if asm_path.is_some() { + let mt = PreloadedMT::new(local_rank, base_port, unlock_mapped_memory) + .expect("Failed to create PreloadedMT"); + let mo = PreloadedMO::new(local_rank, base_port, unlock_mapped_memory) + .expect("Failed to create PreloadedMO"); + (Some(mt), Some(mo)) + } else { + (None, None) + }; + + Self { + zisk_rom, + world_rank, + local_rank, + base_port, + unlock_mapped_memory, + chunk_size, + rom_sm, + asm_shmem_mt: Arc::new(Mutex::new(asm_shmem_mt)), + asm_shmem_mo: Arc::new(Mutex::new(asm_shmem_mo)), + asm_shmem_rh: Arc::new(Mutex::new(None)), + shmem_input_writer: std::array::from_fn(|_| Arc::new(Mutex::new(None))), + } + } + + /// Computes minimal traces by processing the ZisK ROM with given public inputs. + /// + /// # Arguments + /// * `input_data` - Input data for the ROM execution. + /// * `num_threads` - Number of threads to use for parallel execution. + /// + /// # Returns + /// A vector of `EmuTrace` instances representing minimal traces. + #[allow(clippy::type_complexity)] + pub fn execute( + &self, + stdin: &Mutex, + pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + _caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ) { + #[cfg(feature = "stats")] + let parent_stats_id = self.stats.next_id(); + #[cfg(feature = "stats")] + self.stats.add_stat( + _caller_stats_id, + parent_stats_id, + "EXECUTE_WITH_ASSEMBLY", + 0, + ExecutorStatsEvent::Begin, + ); + + AsmServices::SERVICES.par_iter().enumerate().for_each(|(idx, service)| { + #[cfg(feature = "stats")] + let stats_id = self.stats.next_id(); + #[cfg(feature = "stats")] + self.stats.add_stat( + parent_stats_id, + stats_id, + "ASM_WRITE_INPUT", + 0, + ExecutorStatsEvent::Begin, + ); + + let port = if let Some(base_port) = self.base_port { + AsmServices::port_for(service, base_port, self.local_rank) + } else { + AsmServices::default_port(service, self.local_rank) + }; + + let shmem_input_name = + AsmSharedMemory::::shmem_input_name(port, *service, self.local_rank); + + let mut input_writer = self.shmem_input_writer[idx].lock().unwrap(); + if input_writer.is_none() { + tracing::info!( + "Initializing SharedMemoryWriter for service {:?} at '{}'", + service, + shmem_input_name + ); + *input_writer = Some( + SharedMemoryWriter::new( + &shmem_input_name, + MAX_INPUT_SIZE as usize, + self.unlock_mapped_memory, + ) + .expect("Failed to create SharedMemoryWriter"), + ); + } + + write_input(&mut stdin.lock().unwrap(), input_writer.as_ref().unwrap()); + + // Add to executor stats + #[cfg(feature = "stats")] + self.stats.add_stat( + parent_stats_id, + stats_id, + "ASM_WRITE_INPUT", + 0, + ExecutorStatsEvent::End, + ); + }); + + let chunk_size = self.chunk_size; + let (world_rank, local_rank, base_port) = + (self.world_rank, self.local_rank, self.base_port); + + let _stats = stats.clone(); + + // Run the assembly Memory Operations (MO) runner thread + let handle_mo = std::thread::spawn({ + let asm_shmem_mo = self.asm_shmem_mo.clone(); + move || { + AsmRunnerMO::run( + asm_shmem_mo.lock().unwrap().as_mut().unwrap(), + ZiskExecutor::::MAX_NUM_STEPS, + chunk_size, + world_rank, + local_rank, + base_port, + _stats, + ) + .expect("Error during Assembly Memory Operations execution") + } + }); + + // Run the ROM histogram only on partition 0 as it is always computed by this partition + let has_rom_sm = pctx.dctx_is_first_partition(); + + let _stats = stats.clone(); + + let handle_rh = (has_rom_sm).then(|| { + let asm_shmem_rh = self.asm_shmem_rh.clone(); + let unlock_mapped_memory = self.unlock_mapped_memory; + std::thread::spawn(move || { + AsmRunnerRH::run( + &mut asm_shmem_rh.lock().unwrap(), + ZiskExecutor::::MAX_NUM_STEPS, + world_rank, + local_rank, + base_port, + unlock_mapped_memory, + _stats, + ) + .expect("Error during ROM Histogram execution") + }) + }); + + let (min_traces, main_count, secn_count) = self.run_mt_assembly(sm_bundle, stats); + + // Store execute steps + let steps = if let MinimalTraces::AsmEmuTrace(asm_min_traces) = &min_traces { + asm_min_traces.vec_chunks.iter().map(|trace| trace.steps).sum::() + } else { + panic!("Expected AsmEmuTrace, got something else"); + }; + + let execution_result = ZiskExecutionResult::new(steps); + + // If the world rank is 0, wait for the ROM Histogram thread to finish and set the handler + if has_rom_sm { + self.rom_sm.as_ref().unwrap().set_asm_runner_handler( + handle_rh.expect("Error during Assembly ROM Histogram thread execution"), + ); + } + + #[cfg(feature = "stats")] + self.stats.add_stat( + 0, + parent_stats_id, + "EXECUTE_WITH_ASSEMBLY", + 0, + ExecutorStatsEvent::End, + ); + + (min_traces, main_count, secn_count, Some(handle_mo), execution_result) + } + + fn run_mt_assembly( + &self, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + ) -> (MinimalTraces, DeviceMetricsList, NestedDeviceMetricsList) { + #[cfg(feature = "stats")] + let parent_stats_id = self.stats.next_id(); + #[cfg(feature = "stats")] + self.stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::Begin); + + struct CounterTask + where + DB: DataBusTrait>, + { + chunk_id: ChunkId, + emu_trace: Arc, + data_bus: DB, + zisk_rom: Arc, + _phantom: std::marker::PhantomData, + _stats: ExecutorStatsHandle, + _parent_stats_id: u64, + } + + impl Task for CounterTask + where + F: PrimeField64, + DB: DataBusTrait> + Send + Sync + 'static, + { + type Output = (ChunkId, DB); + + fn execute(mut self) -> Self::Output { + #[cfg(feature = "stats")] + let stats_id = self._stats.next_id(); + #[cfg(feature = "stats")] + self._stats.add_stat( + self._parent_stats_id, + stats_id, + "MT_CHUNK_PLAYER", + 0, + ExecutorStatsEvent::Begin, + ); + + ZiskEmulator::process_emu_trace::( + &self.zisk_rom, + &self.emu_trace, + &mut self.data_bus, + false, + ); + + self.data_bus.on_close(); + + // Add to executor stats + #[cfg(feature = "stats")] + self._stats.add_stat( + self._parent_stats_id, + stats_id, + "MT_CHUNK_PLAYER", + 0, + ExecutorStatsEvent::End, + ); + + (self.chunk_id, self.data_bus) + } + } + + let task_factory: TaskFactory<_> = + Box::new(|chunk_id: ChunkId, emu_trace: Arc| { + let data_bus = sm_bundle.build_data_bus_counters(); + CounterTask { + chunk_id, + emu_trace, + data_bus, + zisk_rom: self.zisk_rom.clone(), + _phantom: std::marker::PhantomData::, + _stats: stats.clone(), + #[cfg(feature = "stats")] + _parent_stats_id: parent_stats_id, + #[cfg(not(feature = "stats"))] + _parent_stats_id: 0, + } + }); + + let (asm_runner_mt, mut data_buses) = AsmRunnerMT::run_and_count( + self.asm_shmem_mt.lock().unwrap().as_mut().unwrap(), + ZiskExecutor::::MAX_NUM_STEPS, + self.chunk_size, + task_factory, + self.world_rank, + self.local_rank, + self.base_port, + stats.clone(), + ) + .expect("Error during ASM execution"); + + data_buses.sort_by_key(|(chunk_id, _)| chunk_id.0); + + let mut main_count = Vec::with_capacity(data_buses.len()); + let mut secn_count = HashMap::new(); + + for (chunk_id, data_bus) in data_buses { + let databus_counters = data_bus.into_devices(false); + + for (idx, counter) in databus_counters.into_iter() { + match idx { + None => { + main_count.push((chunk_id, counter.unwrap_or(Box::new(DummyCounter {})))); + } + Some(idx) => { + secn_count + .entry(idx) + .or_insert_with(Vec::new) + .push((chunk_id, counter.unwrap())); + } + } + } + } + + #[cfg(feature = "stats")] + self.stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::End); + (MinimalTraces::AsmEmuTrace(asm_runner_mt), main_count, secn_count) + } +} + +impl crate::Emulator for EmulatorAsm { + fn execute( + &self, + stdin: &Mutex, + pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ) { + self.execute(stdin, pctx, sm_bundle, stats, caller_stats_id) + } + + fn is_asm_emulator(&self) -> bool { + true + } +} diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs new file mode 100644 index 000000000..85a9fd82b --- /dev/null +++ b/executor/src/emu_rust.rs @@ -0,0 +1,199 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, + thread::JoinHandle, +}; + +use asm_runner::{AsmRunnerMO, MinimalTraces}; +use data_bus::DataBusTrait; +use fields::PrimeField64; +use proofman_common::ProofCtx; +use proofman_util::{timer_start_info, timer_stop_and_log_info}; +use rayon::prelude::*; +use zisk_common::{ + io::{ZiskIO, ZiskStdin}, + ChunkId, ExecutorStatsHandle, ZiskExecutionResult, +}; +use zisk_core::ZiskRom; +use ziskemu::{EmuOptions, ZiskEmulator}; + +use crate::{ + emu_asm::{DeviceMetricsList, NestedDeviceMetricsList}, + DummyCounter, StaticSMBundle, ZiskExecutor, +}; + +pub struct EmulatorRust { + /// ZisK ROM, a binary file containing the ZisK program to be executed. + pub zisk_rom: Arc, + + /// Chunk size for processing. + chunk_size: u64, +} + +impl EmulatorRust { + /// The number of threads to use for parallel processing when computing minimal traces. + const NUM_THREADS: usize = 16; + + pub fn new(zisk_rom: Arc, chunk_size: u64) -> Self { + Self { zisk_rom, chunk_size } + } + + /// Computes minimal traces by processing the ZisK ROM with given public inputs. + /// + /// # Arguments + /// * `input_data` - Input data for the ROM execution. + /// * `num_threads` - Number of threads to use for parallel execution. + /// + /// # Returns + /// A vector of `EmuTrace` instances representing minimal traces. + pub fn execute( + &self, + stdin: &Mutex, + _pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + _stats: &ExecutorStatsHandle, + _caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ) { + let min_traces = self.run_emulator::(Self::NUM_THREADS, &mut stdin.lock().unwrap()); + + // Store execute steps + let steps = if let MinimalTraces::EmuTrace(min_traces) = &min_traces { + min_traces.iter().map(|trace| trace.steps).sum::() + } else { + panic!("Expected EmuTrace, got something else"); + }; + + let execution_result = ZiskExecutionResult::new(steps); + + timer_start_info!(COUNT); + let (main_count, secn_count) = self.count(&min_traces, sm_bundle); + timer_stop_and_log_info!(COUNT); + + (min_traces, main_count, secn_count, None, execution_result) + } + + fn run_emulator( + &self, + num_threads: usize, + stdin: &mut ZiskStdin, + ) -> MinimalTraces { + // Call emulate with these options + let input_data = stdin.read(); + + // Settings for the emulator + let emu_options = EmuOptions { + chunk_size: Some(self.chunk_size), + max_steps: ZiskExecutor::::MAX_NUM_STEPS, + ..EmuOptions::default() + }; + + let min_traces = ZiskEmulator::compute_minimal_traces( + &self.zisk_rom, + &input_data, + &emu_options, + num_threads, + ) + .expect("Error during emulator execution"); + + MinimalTraces::EmuTrace(min_traces) + } + + /// Counts metrics for secondary state machines based on minimal traces. + /// + /// # Arguments + /// * `min_traces` - Minimal traces obtained from the ROM execution. + /// + /// # Returns + /// A tuple containing two vectors: + /// * A vector of main state machine metrics grouped by chunk ID. + /// * A vector of secondary state machine metrics grouped by chunk ID. The vector is nested, + /// with the outer vector representing the secondary state machines and the inner vector + /// containing the metrics for each chunk. + fn count( + &self, + min_traces: &MinimalTraces, + sm_bundle: &StaticSMBundle, + ) -> (DeviceMetricsList, NestedDeviceMetricsList) { + let min_traces = match min_traces { + MinimalTraces::EmuTrace(min_traces) => min_traces, + MinimalTraces::AsmEmuTrace(asm_min_traces) => &asm_min_traces.vec_chunks, + _ => unreachable!(), + }; + + let metrics_slices: Vec<_> = min_traces + .par_iter() + .map(|minimal_trace| { + let mut data_bus = sm_bundle.build_data_bus_counters(); + + ZiskEmulator::process_emu_trace::( + &self.zisk_rom, + minimal_trace, + &mut data_bus, + true, + ); + + let mut counters = Vec::new(); + + let databus_counters = data_bus.into_devices(true); + for counter in databus_counters.into_iter() { + counters.push(counter); + } + + counters + }) + .collect(); + + let mut main_count = Vec::new(); + let mut secn_count = HashMap::new(); + + for (chunk_id, counter_slice) in metrics_slices.into_iter().enumerate() { + for (idx, counter) in counter_slice.into_iter() { + match idx { + None => { + main_count.push(( + ChunkId(chunk_id), + counter.unwrap_or_else(|| Box::new(DummyCounter {})), + )); + } + Some(idx) => { + secn_count + .entry(idx) + .or_insert_with(Vec::new) + .push((ChunkId(chunk_id), counter.unwrap())); + } + } + } + } + + (main_count, secn_count) + } +} + +impl crate::Emulator for EmulatorRust { + fn execute( + &self, + stdin: &Mutex, + pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ) { + self.execute(stdin, pctx, sm_bundle, stats, caller_stats_id) + } + + fn is_asm_emulator(&self) -> bool { + false + } +} diff --git a/executor/src/executor.rs b/executor/src/executor.rs index c0fe3dff3..6259d2120 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -19,34 +19,30 @@ //! By structuring these phases, the `ZiskExecutor` ensures high-performance execution while //! maintaining clarity and modularity in the computation process. -use asm_runner::{ - write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, - MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, TaskFactory, -}; +use asm_runner::MinimalTraces; use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{create_pool, BufferPool, ProofCtx, ProofmanResult, SetupCtx}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; -use rayon::prelude::*; use sm_rom::{RomInstance, RomSM}; use std::sync::atomic::{AtomicUsize, Ordering}; use witness::WitnessComponent; -use zisk_common::io::{ZiskIO, ZiskStdin}; +use zisk_common::io::ZiskStdin; -use crate::DummyCounter; +use crate::emu_asm::EmulatorAsm; +use crate::emu_rust::EmulatorRust; use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; +use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusDeviceMetrics, CheckPoint, ExecutorStats, ExecutorStatsHandle, Instance, InstanceCtx, InstanceType, Plan, Stats, ZiskExecutionResult, }; -use zisk_common::{ChunkId, PayloadType}; use zisk_pil::{ ZiskPublicValues, INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, ZISK_AIRGROUP_ID, }; -use std::thread::JoinHandle; use std::time::Instant; use std::{ collections::HashMap, @@ -59,14 +55,12 @@ use zisk_common::ExecutorStatsEvent; use crossbeam::atomic::AtomicCell; use zisk_common::EmuTrace; -use zisk_core::{ZiskRom, MAX_INPUT_SIZE}; -use ziskemu::{EmuOptions, ZiskEmulator}; +use zisk_core::ZiskRom; +use ziskemu::ZiskEmulator; -use crate::StaticSMBundle; +use crate::{Emulator, StaticSMBundle}; -type DeviceMetricsByChunk = (ChunkId, Box); // (chunk_id, metrics) -type DeviceMetricsList = Vec; -pub type NestedDeviceMetricsList = HashMap; +pub type DeviceMetricsByChunk = (ChunkId, Box); // (chunk_id, metrics) #[allow(dead_code)] enum MinimalTraceExecutionMode { @@ -80,28 +74,19 @@ pub struct ZiskExecutor { stdin: Mutex, /// ZisK ROM, a binary file containing the ZisK program to be executed. - pub zisk_rom: Arc, - - /// Path to the ZisK ROM file. - pub rom_path: PathBuf, - - /// Path to the assembly minimal trace binary file, if applicable. - pub asm_runner_path: Option, - - /// Path to the assembly ROM binary file, if applicable. - pub asm_rom_path: Option, + zisk_rom: Arc, /// Planning information for main state machines. - pub min_traces: Arc>, + min_traces: Arc>, /// Planning information for secondary state machines. - pub secn_planning: RwLock>, + secn_planning: RwLock>, /// Main state machine instances, indexed by their global ID. - pub main_instances: RwLock>>, + main_instances: RwLock>>, /// Secondary state machine instances, indexed by their global ID. - pub secn_instances: RwLock>>>, + secn_instances: RwLock>>>, /// Standard library instance, providing common functionalities. std: Arc>, @@ -112,9 +97,6 @@ pub struct ZiskExecutor { /// State machine bundle, containing the state machines and their configurations. sm_bundle: StaticSMBundle, - /// Optional ROM state machine, used for assembly ROM execution. - rom_sm: Option>, - /// Collectors by instance, storing statistics and collectors for each instance. #[allow(clippy::type_complexity)] collectors_by_instance: @@ -125,32 +107,12 @@ pub struct ZiskExecutor { chunk_size: u64, - /// World rank for distributed execution. Default to 0 for single-node execution. - world_rank: i32, - - /// Local rank for distributed execution. Default to 0 for single-node execution. - local_rank: i32, - - /// Optional baseline port to communicate with assembly microservices. - base_port: Option, - - /// Map unlocked flag - /// This is used to unlock the memory map for the ROM file. - unlock_mapped_memory: bool, - - asm_shmem_mt: Arc>>, - asm_shmem_mo: Arc>>, - asm_shmem_rh: Arc>>, - - shmem_input_writer: [Arc>>; AsmServices::SERVICES.len()], + emulator: Box>, } impl ZiskExecutor { - /// The number of threads to use for parallel processing when computing minimal traces. - const NUM_THREADS: usize = 16; - /// The maximum number of steps to execute in the emulator or assembly runner. - const MAX_NUM_STEPS: u64 = 1 << 32; + pub const MAX_NUM_STEPS: u64 = 1 << 32; /// Creates a new instance of the `ZiskExecutor`. /// @@ -158,9 +120,8 @@ impl ZiskExecutor { /// * `zisk_rom` - An `Arc`-wrapped ZisK ROM instance. #[allow(clippy::too_many_arguments)] pub fn new( - rom_path: PathBuf, - asm_path: Option, - asm_rom_path: Option, + asm_mt_path: Option, + asm_rh_path: Option, zisk_rom: Arc, std: Arc>, sm_bundle: StaticSMBundle, @@ -171,25 +132,27 @@ impl ZiskExecutor { base_port: Option, unlock_mapped_memory: bool, ) -> Self { - #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] - let (asm_shmem_mt, asm_shmem_mo) = (None, None); - - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - let (asm_shmem_mt, asm_shmem_mo) = if asm_path.is_some() { - let mt = PreloadedMT::new(local_rank, base_port, unlock_mapped_memory) - .expect("Failed to create PreloadedMT"); - let mo = PreloadedMO::new(local_rank, base_port, unlock_mapped_memory) - .expect("Failed to create PreloadedMO"); - (Some(mt), Some(mo)) + assert_eq!(asm_mt_path.is_some(), asm_rh_path.is_some()); + + let is_asm_emulator = asm_mt_path.is_some(); + + let emulator: Box> = if is_asm_emulator { + Box::new(EmulatorAsm::new( + zisk_rom.clone(), + asm_mt_path.clone(), + world_rank, + local_rank, + base_port, + unlock_mapped_memory, + chunk_size, + rom_sm.clone(), + )) } else { - (None, None) + Box::new(EmulatorRust::new(zisk_rom.clone(), chunk_size)) }; Self { stdin: Mutex::new(ZiskStdin::null()), - rom_path, - asm_runner_path: asm_path, - asm_rom_path, zisk_rom, min_traces: Arc::new(RwLock::new(MinimalTraces::None)), secn_planning: RwLock::new(Vec::new()), @@ -199,17 +162,9 @@ impl ZiskExecutor { std, execution_result: Mutex::new(ZiskExecutionResult::default()), sm_bundle, - rom_sm, stats: ExecutorStatsHandle::new(), chunk_size, - world_rank, - local_rank, - base_port, - unlock_mapped_memory, - asm_shmem_mt: Arc::new(Mutex::new(asm_shmem_mt)), - asm_shmem_mo: Arc::new(Mutex::new(asm_shmem_mo)), - asm_shmem_rh: Arc::new(Mutex::new(None)), - shmem_input_writer: std::array::from_fn(|_| Arc::new(Mutex::new(None))), + emulator, } } @@ -227,321 +182,6 @@ impl ZiskExecutor { self.stats.store_stats(); } - /// Computes minimal traces by processing the ZisK ROM with given public inputs. - /// - /// # Arguments - /// * `input_data` - Input data for the ROM execution. - /// * `num_threads` - Number of threads to use for parallel execution. - /// - /// # Returns - /// A vector of `EmuTrace` instances representing minimal traces. - fn execute_with_emulator(&self) -> MinimalTraces { - let min_traces = self.run_emulator(Self::NUM_THREADS, &mut self.stdin.lock().unwrap()); - - // Store execute steps - let steps = if let MinimalTraces::EmuTrace(min_traces) = &min_traces { - min_traces.iter().map(|trace| trace.steps).sum::() - } else { - panic!("Expected EmuTrace, got something else"); - }; - - self.execution_result.lock().unwrap().executed_steps = steps; - - min_traces - } - - /// Computes minimal traces by processing the ZisK ROM with given public inputs. - /// - /// # Arguments - /// * `input_data` - Input data for the ROM execution. - /// * `num_threads` - Number of threads to use for parallel execution. - /// - /// # Returns - /// A vector of `EmuTrace` instances representing minimal traces. - #[allow(clippy::type_complexity)] - fn execute_with_assembly( - &self, - pctx: &ProofCtx, - _caller_stats_id: u64, - ) -> (MinimalTraces, DeviceMetricsList, NestedDeviceMetricsList, Option>) - { - #[cfg(feature = "stats")] - let parent_stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat( - _caller_stats_id, - parent_stats_id, - "EXECUTE_WITH_ASSEMBLY", - 0, - ExecutorStatsEvent::Begin, - ); - - AsmServices::SERVICES.par_iter().enumerate().for_each(|(idx, service)| { - #[cfg(feature = "stats")] - let stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat( - parent_stats_id, - stats_id, - "ASM_WRITE_INPUT", - 0, - ExecutorStatsEvent::Begin, - ); - - let port = if let Some(base_port) = self.base_port { - AsmServices::port_for(service, base_port, self.local_rank) - } else { - AsmServices::default_port(service, self.local_rank) - }; - - let shmem_input_name = - AsmSharedMemory::::shmem_input_name(port, *service, self.local_rank); - - let mut input_writer = self.shmem_input_writer[idx].lock().unwrap(); - if input_writer.is_none() { - tracing::info!( - "Initializing SharedMemoryWriter for service {:?} at '{}'", - service, - shmem_input_name - ); - *input_writer = Some( - SharedMemoryWriter::new( - &shmem_input_name, - MAX_INPUT_SIZE as usize, - self.unlock_mapped_memory, - ) - .expect("Failed to create SharedMemoryWriter"), - ); - } - - write_input(&mut self.stdin.lock().unwrap(), input_writer.as_ref().unwrap()); - - // Add to executor stats - #[cfg(feature = "stats")] - self.stats.add_stat( - parent_stats_id, - stats_id, - "ASM_WRITE_INPUT", - 0, - ExecutorStatsEvent::End, - ); - }); - - let chunk_size = self.chunk_size; - let (world_rank, local_rank, base_port) = - (self.world_rank, self.local_rank, self.base_port); - - let stats = self.stats.clone(); - - // Run the assembly Memory Operations (MO) runner thread - let handle_mo = std::thread::spawn({ - let asm_shmem_mo = self.asm_shmem_mo.clone(); - move || { - AsmRunnerMO::run( - asm_shmem_mo.lock().unwrap().as_mut().unwrap(), - Self::MAX_NUM_STEPS, - chunk_size, - world_rank, - local_rank, - base_port, - stats, - ) - .expect("Error during Assembly Memory Operations execution") - } - }); - - let stats = self.stats.clone(); - - // Run the ROM histogram only on partition 0 as it is always computed by this partition - let has_rom_sm = pctx.dctx_is_first_partition(); - - let handle_rh = (has_rom_sm).then(|| { - let asm_shmem_rh = self.asm_shmem_rh.clone(); - let unlock_mapped_memory = self.unlock_mapped_memory; - std::thread::spawn(move || { - AsmRunnerRH::run( - &mut asm_shmem_rh.lock().unwrap(), - Self::MAX_NUM_STEPS, - world_rank, - local_rank, - base_port, - unlock_mapped_memory, - stats, - ) - .expect("Error during ROM Histogram execution") - }) - }); - - let (min_traces, main_count, secn_count) = self.run_mt_assembly(); - - // Store execute steps - let steps = if let MinimalTraces::AsmEmuTrace(asm_min_traces) = &min_traces { - asm_min_traces.vec_chunks.iter().map(|trace| trace.steps).sum::() - } else { - panic!("Expected AsmEmuTrace, got something else"); - }; - - self.execution_result.lock().unwrap().executed_steps = steps; - - // If the world rank is 0, wait for the ROM Histogram thread to finish and set the handler - if has_rom_sm { - self.rom_sm.as_ref().unwrap().set_asm_runner_handler( - handle_rh.expect("Error during Assembly ROM Histogram thread execution"), - ); - } - - #[cfg(feature = "stats")] - self.stats.add_stat( - 0, - parent_stats_id, - "EXECUTE_WITH_ASSEMBLY", - 0, - ExecutorStatsEvent::End, - ); - - (min_traces, main_count, secn_count, Some(handle_mo)) - } - - fn run_mt_assembly(&self) -> (MinimalTraces, DeviceMetricsList, NestedDeviceMetricsList) { - #[cfg(feature = "stats")] - let parent_stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::Begin); - - struct CounterTask - where - DB: DataBusTrait>, - { - chunk_id: ChunkId, - emu_trace: Arc, - data_bus: DB, - zisk_rom: Arc, - _phantom: std::marker::PhantomData, - _stats: ExecutorStatsHandle, - _parent_stats_id: u64, - } - - impl Task for CounterTask - where - F: PrimeField64, - DB: DataBusTrait> + Send + Sync + 'static, - { - type Output = (ChunkId, DB); - - fn execute(mut self) -> Self::Output { - #[cfg(feature = "stats")] - let stats_id = self._stats.next_id(); - #[cfg(feature = "stats")] - self._stats.add_stat( - self._parent_stats_id, - stats_id, - "MT_CHUNK_PLAYER", - 0, - ExecutorStatsEvent::Begin, - ); - - ZiskEmulator::process_emu_trace::( - &self.zisk_rom, - &self.emu_trace, - &mut self.data_bus, - false, - ); - - self.data_bus.on_close(); - - // Add to executor stats - #[cfg(feature = "stats")] - self._stats.add_stat( - self._parent_stats_id, - stats_id, - "MT_CHUNK_PLAYER", - 0, - ExecutorStatsEvent::End, - ); - - (self.chunk_id, self.data_bus) - } - } - - let task_factory: TaskFactory<_> = - Box::new(|chunk_id: ChunkId, emu_trace: Arc| { - let data_bus = self.sm_bundle.build_data_bus_counters(); - CounterTask { - chunk_id, - emu_trace, - data_bus, - zisk_rom: self.zisk_rom.clone(), - _phantom: std::marker::PhantomData::, - _stats: self.stats.clone(), - #[cfg(feature = "stats")] - _parent_stats_id: parent_stats_id, - #[cfg(not(feature = "stats"))] - _parent_stats_id: 0, - } - }); - - let (asm_runner_mt, mut data_buses) = AsmRunnerMT::run_and_count( - self.asm_shmem_mt.lock().unwrap().as_mut().unwrap(), - Self::MAX_NUM_STEPS, - self.chunk_size, - task_factory, - self.world_rank, - self.local_rank, - self.base_port, - self.stats.clone(), - ) - .expect("Error during ASM execution"); - - data_buses.sort_by_key(|(chunk_id, _)| chunk_id.0); - - let mut main_count = Vec::with_capacity(data_buses.len()); - let mut secn_count = HashMap::new(); - - for (chunk_id, data_bus) in data_buses { - let databus_counters = data_bus.into_devices(false); - - for (idx, counter) in databus_counters.into_iter() { - match idx { - None => { - main_count.push((chunk_id, counter.unwrap_or(Box::new(DummyCounter {})))); - } - Some(idx) => { - secn_count - .entry(idx) - .or_insert_with(Vec::new) - .push((chunk_id, counter.unwrap())); - } - } - } - } - - #[cfg(feature = "stats")] - self.stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::End); - (MinimalTraces::AsmEmuTrace(asm_runner_mt), main_count, secn_count) - } - - fn run_emulator(&self, num_threads: usize, stdin: &mut ZiskStdin) -> MinimalTraces { - // Call emulate with these options - let input_data = stdin.read(); - - // Settings for the emulator - let emu_options = EmuOptions { - chunk_size: Some(self.chunk_size), - max_steps: Self::MAX_NUM_STEPS, - ..EmuOptions::default() - }; - - let min_traces = ZiskEmulator::compute_minimal_traces( - &self.zisk_rom, - &input_data, - &emu_options, - num_threads, - ) - .expect("Error during emulator execution"); - - MinimalTraces::EmuTrace(min_traces) - } - /// Adds main state machine instances to the proof context and assigns global IDs. /// /// # Arguments @@ -578,72 +218,6 @@ impl ZiskExecutor { MainInstance::new(InstanceCtx::new(global_id, plan), self.std.clone()) } - /// Counts metrics for secondary state machines based on minimal traces. - /// - /// # Arguments - /// * `min_traces` - Minimal traces obtained from the ROM execution. - /// - /// # Returns - /// A tuple containing two vectors: - /// * A vector of main state machine metrics grouped by chunk ID. - /// * A vector of secondary state machine metrics grouped by chunk ID. The vector is nested, - /// with the outer vector representing the secondary state machines and the inner vector - /// containing the metrics for each chunk. - fn count(&self, min_traces: &MinimalTraces) -> (DeviceMetricsList, NestedDeviceMetricsList) { - let min_traces = match min_traces { - MinimalTraces::EmuTrace(min_traces) => min_traces, - MinimalTraces::AsmEmuTrace(asm_min_traces) => &asm_min_traces.vec_chunks, - _ => unreachable!(), - }; - - let metrics_slices: Vec<_> = min_traces - .par_iter() - .map(|minimal_trace| { - let mut data_bus = self.sm_bundle.build_data_bus_counters(); - - ZiskEmulator::process_emu_trace::( - &self.zisk_rom, - minimal_trace, - &mut data_bus, - true, - ); - - let mut counters = Vec::new(); - - let databus_counters = data_bus.into_devices(true); - for counter in databus_counters.into_iter() { - counters.push(counter); - } - - counters - }) - .collect(); - - let mut main_count = Vec::new(); - let mut secn_count = HashMap::new(); - - for (chunk_id, counter_slice) in metrics_slices.into_iter().enumerate() { - for (idx, counter) in counter_slice.into_iter() { - match idx { - None => { - main_count.push(( - ChunkId(chunk_id), - counter.unwrap_or_else(|| Box::new(DummyCounter {})), - )); - } - Some(idx) => { - secn_count - .entry(idx) - .or_insert_with(Vec::new) - .push((ChunkId(chunk_id), counter.unwrap())); - } - } - } - } - - (main_count, secn_count) - } - /// Adds secondary state machine instances to the proof context and assigns global IDs. /// /// # Arguments @@ -1187,30 +761,23 @@ impl WitnessComponent for ZiskExecutor { // Process the ROM to collect the Minimal Traces timer_start_info!(COMPUTE_MINIMAL_TRACE); - assert_eq!(self.asm_runner_path.is_some(), self.asm_rom_path.is_some()); - - let (min_traces, main_count, mut secn_count, handle_mo) = if self.asm_runner_path.is_some() - { - // If we are executing in assembly mode - self.execute_with_assembly( + let (min_traces, main_count, mut secn_count, handle_mo, execution_result) = + self.emulator.execute( + &self.stdin, &pctx, + &self.sm_bundle, + &self.stats, #[cfg(feature = "stats")] parent_stats_id, #[cfg(not(feature = "stats"))] 0, - ) - } else { - // Otherwise, use the emulator - let min_traces = self.execute_with_emulator(); - - timer_start_info!(COUNT); - let (main_count, secn_count) = self.count(&min_traces); - timer_stop_and_log_info!(COUNT); + ); - (min_traces, main_count, secn_count, None) - }; timer_stop_and_log_info!(COMPUTE_MINIMAL_TRACE); + // Store the execution result + *self.execution_result.lock().unwrap() = execution_result; + // Plan the main and secondary instances using the counted metrics // stats_begin!(stats_next_id!(), parent_stats_id, "PLAN"); #[cfg(feature = "stats")] @@ -1424,7 +991,7 @@ impl WitnessComponent for ZiskExecutor { InstanceType::Instance => { if !self.collectors_by_instance.read().unwrap().contains_key(&global_id) { - if air_id == ROM_AIR_IDS[0] && self.asm_runner_path.is_some() { + if air_id == ROM_AIR_IDS[0] && self.emulator.is_asm_emulator() { let stats = Stats { airgroup_id, air_id, @@ -1514,7 +1081,7 @@ impl WitnessComponent for ZiskExecutor { if MAIN_AIR_IDS.contains(&air_id) { pctx.set_witness_ready(global_id, false); } else if air_id == ROM_AIR_IDS[0] { - if self.asm_runner_path.is_some() { + if self.emulator.is_asm_emulator() { pctx.set_witness_ready(global_id, false); } else { let secn_instance = &secn_instances_guard[&global_id]; diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 936a187d4..1fe040a5e 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -1,4 +1,6 @@ mod dummy_counter; +mod emu_asm; +mod emu_rust; mod executor; mod sm_static_bundle; mod static_data_bus; @@ -9,3 +11,31 @@ pub use executor::*; pub use sm_static_bundle::*; pub use static_data_bus::*; pub use static_data_bus_collect::*; + +use crate::emu_asm::{DeviceMetricsList, NestedDeviceMetricsList}; +use asm_runner::{AsmRunnerMO, MinimalTraces}; +use fields::PrimeField64; +use proofman_common::ProofCtx; +use std::{sync::Mutex, thread::JoinHandle}; +use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, ZiskExecutionResult}; + +/// Trait for unified execution across different emulator backends +pub trait Emulator: Send + Sync { + /// Execute the emulator + fn execute( + &self, + stdin: &Mutex, + pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ); + + fn is_asm_emulator(&self) -> bool; +} diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index 3aa8edb76..b496f7aca 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::NestedDeviceMetricsList; +use crate::emu_asm::NestedDeviceMetricsList; use crate::StaticDataBusCollect; use data_bus::DataBusTrait; use fields::PrimeField64; diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 399e06c9f..5f392e127 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -166,7 +166,6 @@ impl WitnessLibrary for WitnessLib { // Step 5: Create the executor and register the secondary state machines let executor: ZiskExecutor = ZiskExecutor::new( - self.elf_path.clone(), self.asm_path.clone(), self.asm_rom_path.clone(), zisk_rom, From 6913d19a91be504380fe3d1450ff9f6147e682b4 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:02:57 +0000 Subject: [PATCH 244/782] added EmulatorKind --- executor/src/emu_asm.rs | 11 ++---- executor/src/emu_rust.rs | 7 +--- executor/src/executor.rs | 49 +++++++------------------ executor/src/lib.rs | 43 ++++++++++++++++++++-- executor/src/sm_static_bundle.rs | 3 +- witness-computation/src/zisk_lib.rs | 56 ++++++++++++++++------------- 6 files changed, 88 insertions(+), 81 deletions(-) diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index e0e6c4c97..71cce3bee 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -5,7 +5,9 @@ use std::{ thread::JoinHandle, }; -use crate::{DeviceMetricsByChunk, DummyCounter, StaticSMBundle, ZiskExecutor}; +use crate::{ + DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, ZiskExecutor, +}; use asm_runner::{ write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, TaskFactory, @@ -22,9 +24,6 @@ use zisk_common::{ use zisk_core::{ZiskRom, MAX_INPUT_SIZE}; use ziskemu::ZiskEmulator; -pub type DeviceMetricsList = Vec; -pub type NestedDeviceMetricsList = HashMap; - pub struct EmulatorAsm { /// ZisK ROM, a binary file containing the ZisK program to be executed. pub zisk_rom: Arc, @@ -393,8 +392,4 @@ impl crate::Emulator for EmulatorAsm { ) { self.execute(stdin, pctx, sm_bundle, stats, caller_stats_id) } - - fn is_asm_emulator(&self) -> bool { - true - } } diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index 85a9fd82b..1402bf9d7 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -18,8 +18,7 @@ use zisk_core::ZiskRom; use ziskemu::{EmuOptions, ZiskEmulator}; use crate::{ - emu_asm::{DeviceMetricsList, NestedDeviceMetricsList}, - DummyCounter, StaticSMBundle, ZiskExecutor, + DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, ZiskExecutor, }; pub struct EmulatorRust { @@ -192,8 +191,4 @@ impl crate::Emulator for EmulatorRust { ) { self.execute(stdin, pctx, sm_bundle, stats, caller_stats_id) } - - fn is_asm_emulator(&self) -> bool { - false - } } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 6259d2120..5221f7b71 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -24,13 +24,11 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{create_pool, BufferPool, ProofCtx, ProofmanResult, SetupCtx}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; -use sm_rom::{RomInstance, RomSM}; +use sm_rom::RomInstance; use std::sync::atomic::{AtomicUsize, Ordering}; use witness::WitnessComponent; use zisk_common::io::ZiskStdin; -use crate::emu_asm::EmulatorAsm; -use crate::emu_rust::EmulatorRust; use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; use zisk_common::ChunkId; @@ -46,7 +44,6 @@ use zisk_pil::{ use std::time::Instant; use std::{ collections::HashMap, - path::PathBuf, sync::{Arc, Mutex, RwLock}, }; #[cfg(feature = "stats")] @@ -58,7 +55,7 @@ use zisk_common::EmuTrace; use zisk_core::ZiskRom; use ziskemu::ZiskEmulator; -use crate::{Emulator, StaticSMBundle}; +use crate::{Emulator, EmulatorKind, StaticSMBundle}; pub type DeviceMetricsByChunk = (ChunkId, Box); // (chunk_id, metrics) @@ -71,8 +68,15 @@ enum MinimalTraceExecutionMode { /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. pub struct ZiskExecutor { + /// Standard input for the ZisK program execution. stdin: Mutex, + /// The emulator backend used for execution. + emulator: EmulatorKind, + + /// Chunk size for processing. + chunk_size: u64, + /// ZisK ROM, a binary file containing the ZisK program to be executed. zisk_rom: Arc, @@ -104,10 +108,6 @@ pub struct ZiskExecutor { /// Statistics collected during the execution, including time taken for collection and witness computation. stats: ExecutorStatsHandle, - - chunk_size: u64, - - emulator: Box>, } impl ZiskExecutor { @@ -120,39 +120,16 @@ impl ZiskExecutor { /// * `zisk_rom` - An `Arc`-wrapped ZisK ROM instance. #[allow(clippy::too_many_arguments)] pub fn new( - asm_mt_path: Option, - asm_rh_path: Option, zisk_rom: Arc, std: Arc>, sm_bundle: StaticSMBundle, - rom_sm: Option>, chunk_size: u64, - world_rank: i32, - local_rank: i32, - base_port: Option, - unlock_mapped_memory: bool, + emulator: EmulatorKind, ) -> Self { - assert_eq!(asm_mt_path.is_some(), asm_rh_path.is_some()); - - let is_asm_emulator = asm_mt_path.is_some(); - - let emulator: Box> = if is_asm_emulator { - Box::new(EmulatorAsm::new( - zisk_rom.clone(), - asm_mt_path.clone(), - world_rank, - local_rank, - base_port, - unlock_mapped_memory, - chunk_size, - rom_sm.clone(), - )) - } else { - Box::new(EmulatorRust::new(zisk_rom.clone(), chunk_size)) - }; - Self { stdin: Mutex::new(ZiskStdin::null()), + emulator, + chunk_size, zisk_rom, min_traces: Arc::new(RwLock::new(MinimalTraces::None)), secn_planning: RwLock::new(Vec::new()), @@ -163,8 +140,6 @@ impl ZiskExecutor { execution_result: Mutex::new(ZiskExecutionResult::default()), sm_bundle, stats: ExecutorStatsHandle::new(), - chunk_size, - emulator, } } diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 1fe040a5e..a6002b862 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -7,16 +7,20 @@ mod static_data_bus; mod static_data_bus_collect; pub use dummy_counter::*; +pub use emu_asm::*; +pub use emu_rust::*; pub use executor::*; pub use sm_static_bundle::*; pub use static_data_bus::*; pub use static_data_bus_collect::*; -use crate::emu_asm::{DeviceMetricsList, NestedDeviceMetricsList}; +pub type DeviceMetricsList = Vec; +pub type NestedDeviceMetricsList = HashMap; + use asm_runner::{AsmRunnerMO, MinimalTraces}; use fields::PrimeField64; use proofman_common::ProofCtx; -use std::{sync::Mutex, thread::JoinHandle}; +use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, ZiskExecutionResult}; /// Trait for unified execution across different emulator backends @@ -36,6 +40,39 @@ pub trait Emulator: Send + Sync { Option>, ZiskExecutionResult, ); +} + +/// Enum wrapper for different emulator backends (no heap allocation) +pub enum EmulatorKind { + Asm(EmulatorAsm), + Rust(EmulatorRust), +} + +impl EmulatorKind { + /// Check if this is an ASM emulator (non-generic, can be called without F) + pub fn is_asm_emulator(&self) -> bool { + matches!(self, Self::Asm(_)) + } +} - fn is_asm_emulator(&self) -> bool; +impl Emulator for EmulatorKind { + fn execute( + &self, + stdin: &Mutex, + pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ) { + match self { + Self::Asm(e) => e.execute(stdin, pctx, sm_bundle, stats, caller_stats_id), + Self::Rust(e) => e.execute(stdin, pctx, sm_bundle, stats, caller_stats_id), + } + } } diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index b496f7aca..e77b56333 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -1,7 +1,6 @@ use std::sync::Arc; -use crate::emu_asm::NestedDeviceMetricsList; -use crate::StaticDataBusCollect; +use crate::{NestedDeviceMetricsList, StaticDataBusCollect}; use data_bus::DataBusTrait; use fields::PrimeField64; use precomp_arith_eq::{ArithEqInstance, ArithEqManager}; diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 5f392e127..44141d796 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -4,7 +4,9 @@ //! This module leverages `WitnessLibrary` to orchestrate the setup of state machines, //! program conversion, and execution pipelines to generate required witnesses. -use executor::{StateMachines, StaticSMBundle, ZiskExecutor}; +use executor::{ + EmulatorAsm, EmulatorKind, EmulatorRust, StateMachines, StaticSMBundle, ZiskExecutor, +}; use fields::{Goldilocks, PrimeField64}; use pil_std_lib::Std; use precomp_arith_eq::ArithEqManager; @@ -34,8 +36,8 @@ use zisk_pil::{ pub struct WitnessLib { elf_path: PathBuf, - asm_path: Option, - asm_rom_path: Option, + asm_mt_path: Option, + asm_rh_path: Option, executor: Option>>, chunk_size: u64, base_port: Option, @@ -49,8 +51,8 @@ pub struct WitnessLib { fn init_library( verbose_mode: proofman_common::VerboseMode, elf_path: PathBuf, - asm_path: Option, - asm_rom_path: Option, + asm_mt_path: Option, + asm_rh_path: Option, base_port: Option, unlock_mapped_memory: bool, shared_tables: bool, @@ -59,8 +61,8 @@ fn init_library( let result = Box::new(WitnessLib { elf_path, - asm_path, - asm_rom_path, + asm_mt_path, + asm_rh_path, executor: None, chunk_size, base_port, @@ -103,7 +105,7 @@ impl WitnessLibrary for WitnessLib { let std = Std::new(wcm.get_pctx(), wcm.get_sctx(), self.shared_tables)?; register_std(wcm, &std); - let rom_sm = RomSM::new(zisk_rom.clone(), self.asm_rom_path.clone()); + let rom_sm = RomSM::new(zisk_rom.clone(), self.asm_rh_path.clone()); let binary_sm = BinarySM::new(std.clone()); let arith_sm = ArithSM::new(std.clone()); let mem_sm = Mem::new(std.clone()); @@ -131,7 +133,7 @@ impl WitnessLibrary for WitnessLib { ]; let sm_bundle = StaticSMBundle::new( - self.asm_path.is_some(), + self.asm_mt_path.is_some(), vec![ (vec![(ZISK_AIRGROUP_ID, ROM_AIR_IDS[0])], StateMachines::RomSM(rom_sm.clone())), (mem_instances, StateMachines::MemSM(mem_sm.clone())), @@ -164,22 +166,26 @@ impl WitnessLibrary for WitnessLib { ], ); - // Step 5: Create the executor and register the secondary state machines - let executor: ZiskExecutor = ZiskExecutor::new( - self.asm_path.clone(), - self.asm_rom_path.clone(), - zisk_rom, - std, - sm_bundle, - Some(rom_sm.clone()), - self.chunk_size, - world_rank, - local_rank, - self.base_port, - self.unlock_mapped_memory, - ); - - let executor = Arc::new(executor); + assert_eq!(self.asm_mt_path.is_some(), self.asm_rh_path.is_some()); + + let is_asm_emulator = self.asm_mt_path.is_some(); + let emulator = if is_asm_emulator { + EmulatorKind::Asm(EmulatorAsm::new( + zisk_rom.clone(), + self.asm_mt_path.clone(), + world_rank, + local_rank, + self.base_port, + self.unlock_mapped_memory, + self.chunk_size, + Some(rom_sm.clone()), + )) + } else { + EmulatorKind::Rust(EmulatorRust::new(zisk_rom.clone(), self.chunk_size)) + }; + + let executor = + Arc::new(ZiskExecutor::new(zisk_rom, std, sm_bundle, self.chunk_size, emulator)); // Step 7: Register the executor as a component in the Witness Manager wcm.register_component(executor.clone()); From cd880bf300437009ced9aeae0b021936eafe2535 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:13:10 +0000 Subject: [PATCH 245/782] minor fix --- witness-computation/src/zisk_lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 44141d796..ce48745b6 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -89,6 +89,8 @@ impl WitnessLibrary for WitnessLib { /// # Panics /// Panics if the `Riscv2zisk` conversion fails or if required paths cannot be resolved. fn register_witness(&mut self, wcm: &WitnessManager) -> ProofmanResult<()> { + assert_eq!(self.asm_mt_path.is_some(), self.asm_rh_path.is_some()); + let world_rank = wcm.get_world_rank(); let local_rank = wcm.get_local_rank(); @@ -166,8 +168,6 @@ impl WitnessLibrary for WitnessLib { ], ); - assert_eq!(self.asm_mt_path.is_some(), self.asm_rh_path.is_some()); - let is_asm_emulator = self.asm_mt_path.is_some(); let emulator = if is_asm_emulator { EmulatorKind::Asm(EmulatorAsm::new( From 70100f77ad702e287e4cb22a994847c7678609f6 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:34:37 +0100 Subject: [PATCH 246/782] fix MacOS compilation --- executor/src/emu_asm.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 71cce3bee..fe6ffaded 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -59,7 +59,7 @@ impl EmulatorAsm { #[allow(clippy::too_many_arguments)] pub fn new( zisk_rom: Arc, - asm_path: Option, + _asm_path: Option, world_rank: i32, local_rank: i32, base_port: Option, @@ -67,7 +67,11 @@ impl EmulatorAsm { chunk_size: u64, rom_sm: Option>, ) -> Self { - let (asm_shmem_mt, asm_shmem_mo) = if asm_path.is_some() { + #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] + let (asm_shmem_mt, asm_shmem_mo) = (None, None); + + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + let (asm_shmem_mt, asm_shmem_mo) = if _asm_path.is_some() { let mt = PreloadedMT::new(local_rank, base_port, unlock_mapped_memory) .expect("Failed to create PreloadedMT"); let mo = PreloadedMO::new(local_rank, base_port, unlock_mapped_memory) From fb814f0194822b4787a276cf97f076dd268b04b4 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:45:53 +0000 Subject: [PATCH 247/782] Fix documentation --- executor/src/emu_asm.rs | 14 +++++++++++--- executor/src/emu_rust.rs | 16 ++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index fe6ffaded..83139153b 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -99,11 +99,19 @@ impl EmulatorAsm { /// Computes minimal traces by processing the ZisK ROM with given public inputs. /// /// # Arguments - /// * `input_data` - Input data for the ROM execution. - /// * `num_threads` - Number of threads to use for parallel execution. + /// * `stdin` - Shared mutable access to the ZiskStdin providing public inputs. + /// * `pctx` - Proof context used during execution. + /// * `sm_bundle` - Static shared-memory bundle used by the executor. + /// * `stats` - Handle for collecting executor statistics. + /// * `_caller_stats_id` - Identifier used to attribute collected statistics to the caller. /// /// # Returns - /// A vector of `EmuTrace` instances representing minimal traces. + /// A tuple containing: + /// * `MinimalTraces` - The computed minimal traces. + /// * `DeviceMetricsList` - Flat device metrics collected during execution. + /// * `NestedDeviceMetricsList` - Hierarchical device metrics collected during execution. + /// * `Option>` - Optional join handle for the memory-only ASM runner. + /// * `ZiskExecutionResult` - The result of executing the ZisK ROM. #[allow(clippy::type_complexity)] pub fn execute( &self, diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index 1402bf9d7..ff3c382a3 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -37,14 +37,22 @@ impl EmulatorRust { Self { zisk_rom, chunk_size } } - /// Computes minimal traces by processing the ZisK ROM with given public inputs. + /// Computes minimal traces by processing the ZisK ROM with the given public inputs. /// /// # Arguments - /// * `input_data` - Input data for the ROM execution. - /// * `num_threads` - Number of threads to use for parallel execution. + /// * `stdin` - Shared standard input source used to feed data into the emulator. + /// * `_pctx` - Proof context carrying field-parameterized configuration for execution. + /// * `sm_bundle` - Static state machine bundle used for counting device metrics. + /// * `_stats` - Handle to executor statistics collection. + /// * `_caller_stats_id` - Identifier used to associate collected statistics with the caller. /// /// # Returns - /// A vector of `EmuTrace` instances representing minimal traces. + /// A tuple containing: + /// * `MinimalTraces` - The minimal traces produced by the emulator. + /// * `DeviceMetricsList` - Metrics for primary devices. + /// * `NestedDeviceMetricsList` - Metrics for secondary/nested devices. + /// * `None`. + /// * `ZiskExecutionResult` - Summary of the emulator execution, including the total number of steps. pub fn execute( &self, stdin: &Mutex, From ba45e3be6b28407c322757dd732151d15f278183 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:46:20 +0000 Subject: [PATCH 248/782] fixes when stats feature enabled --- executor/src/emu_asm.rs | 26 +++++++++++--------------- executor/src/executor.rs | 4 ++-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 83139153b..bf8a66443 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -17,6 +17,8 @@ use fields::PrimeField64; use proofman_common::ProofCtx; use rayon::prelude::*; use sm_rom::RomSM; +#[cfg(feature = "stats")] +use zisk_common::ExecutorStatsEvent; use zisk_common::{ io::ZiskStdin, BusDeviceMetrics, ChunkId, EmuTrace, ExecutorStatsHandle, PayloadType, ZiskExecutionResult, @@ -128,9 +130,9 @@ impl EmulatorAsm { ZiskExecutionResult, ) { #[cfg(feature = "stats")] - let parent_stats_id = self.stats.next_id(); + let parent_stats_id = stats.next_id(); #[cfg(feature = "stats")] - self.stats.add_stat( + stats.add_stat( _caller_stats_id, parent_stats_id, "EXECUTE_WITH_ASSEMBLY", @@ -140,9 +142,9 @@ impl EmulatorAsm { AsmServices::SERVICES.par_iter().enumerate().for_each(|(idx, service)| { #[cfg(feature = "stats")] - let stats_id = self.stats.next_id(); + let stats_id = stats.next_id(); #[cfg(feature = "stats")] - self.stats.add_stat( + stats.add_stat( parent_stats_id, stats_id, "ASM_WRITE_INPUT", @@ -180,7 +182,7 @@ impl EmulatorAsm { // Add to executor stats #[cfg(feature = "stats")] - self.stats.add_stat( + stats.add_stat( parent_stats_id, stats_id, "ASM_WRITE_INPUT", @@ -253,13 +255,7 @@ impl EmulatorAsm { } #[cfg(feature = "stats")] - self.stats.add_stat( - 0, - parent_stats_id, - "EXECUTE_WITH_ASSEMBLY", - 0, - ExecutorStatsEvent::End, - ); + stats.add_stat(0, parent_stats_id, "EXECUTE_WITH_ASSEMBLY", 0, ExecutorStatsEvent::End); (min_traces, main_count, secn_count, Some(handle_mo), execution_result) } @@ -270,9 +266,9 @@ impl EmulatorAsm { stats: &ExecutorStatsHandle, ) -> (MinimalTraces, DeviceMetricsList, NestedDeviceMetricsList) { #[cfg(feature = "stats")] - let parent_stats_id = self.stats.next_id(); + let parent_stats_id = stats.next_id(); #[cfg(feature = "stats")] - self.stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::Begin); + stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::Begin); struct CounterTask where @@ -382,7 +378,7 @@ impl EmulatorAsm { } #[cfg(feature = "stats")] - self.stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::End); + stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::End); (MinimalTraces::AsmEmuTrace(asm_runner_mt), main_count, secn_count) } } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 5221f7b71..7b1af04de 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -345,7 +345,7 @@ impl ZiskExecutor { let witness_start_time = Instant::now(); #[cfg(feature = "stats")] - let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id); + let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id)?; #[cfg(feature = "stats")] let stats_id = self.stats.next_id(); #[cfg(feature = "stats")] @@ -623,7 +623,7 @@ impl ZiskExecutor { _caller_stats_id: u64, ) -> ProofmanResult<()> { #[cfg(feature = "stats")] - let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id); + let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id)?; #[cfg(feature = "stats")] let stats_id = self.stats.next_id(); #[cfg(feature = "stats")] From 421a69b5deb503619fa4e70c440d990cbc6000c0 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 17 Jan 2026 14:27:48 +0000 Subject: [PATCH 249/782] added new secp256k1 hint codes --- common/src/hints.rs | 116 ++++++++- precompiles/hints/src/hints_processor.rs | 129 +++++++++- ziskos-hints/src/handlers/secp256k1.rs | 295 +++++++++++++++-------- 3 files changed, 428 insertions(+), 112 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 98ba72bae..9393cd7a0 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -58,8 +58,25 @@ const CTRL_ERROR: u32 = 0x03; // === BUILT-IN HINT CODES === // Noop hint code const HINT_NOOP: u32 = 0x04; -// Ecrecover hint code -const HINT_ECRECOVER: u32 = 0x05; + +// Secp256k1 Scalar hint codes +const HINT_SECP256K1_FN_REDUCE: u32 = 0x02000; +const HINT_SECP256K1_FN_ADD: u32 = 0x02001; +const HINT_SECP256K1_FN_NEG: u32 = 0x02002; +const HINT_SECP256K1_FN_SUB: u32 = 0x02003; +const HINT_SECP256K1_FN_MUL: u32 = 0x02004; +const HINT_SECP256K1_FN_INV: u32 = 0x02005; +// Secp256k1 Field hint codes +const HINT_SECP256K1_FP_REDUCE: u32 = 0x02010; +const HINT_SECP256K1_FP_ADD: u32 = 0x02011; +const HINT_SECP256K1_FP_NEGATE: u32 = 0x02012; +const HINT_SECP256K1_FP_MUL: u32 = 0x02013; +const HINT_SECP256K1_FP_MUL_SCALAR: u32 = 0x02014; +// Secp256k1 Curve hint codes +const HINT_SECP256K1_TO_AFFINE: u32 = 0x02020; +const HINT_SECP256K1_DECOMPRESS: u32 = 0x02021; +const HINT_SECP256K1_DOUBLE_SCALAR_MUL_WITH_G: u32 = 0x02022; +const HINT_SECP256K1_ECDSA_VERIFY: u32 = 0x02023; // Big integer arithmetic hint codes const HINT_REDMOD256: u32 = 0x06; @@ -147,8 +164,37 @@ pub enum BuiltInHint { /// without any additional computation. Noop = HINT_NOOP, - /// Ecrecover hint type. - EcRecover = HINT_ECRECOVER, + // Secp256k1 hint types. + /// Secp256k1 scalar field reduction hint type. + Secp256K1FnReduce = HINT_SECP256K1_FN_REDUCE, + /// Secp256k1 scalar field addition hint type. + Secp256K1FnAdd = HINT_SECP256K1_FN_ADD, + /// Secp256k1 scalar field negation hint type. + Secp256K1FnNeg = HINT_SECP256K1_FN_NEG, + /// Secp256k1 scalar field subtraction hint type. + Secp256K1FnSub = HINT_SECP256K1_FN_SUB, + /// Secp256k1 scalar field multiplication hint type. + Secp256K1FnMul = HINT_SECP256K1_FN_MUL, + /// Secp256k1 scalar field inversion hint type. + Secp256K1FnInv = HINT_SECP256K1_FN_INV, + /// Secp256k1 base field reduction hint type. + Secp256K1FpReduce = HINT_SECP256K1_FP_REDUCE, + /// Secp256k1 base field addition hint type. + Secp256K1FpAdd = HINT_SECP256K1_FP_ADD, + /// Secp256k1 base field negation hint type. + Secp256K1FpNegate = HINT_SECP256K1_FP_NEGATE, + /// Secp256k1 base field multiplication hint type. + Secp256K1FpMul = HINT_SECP256K1_FP_MUL, + /// Secp256k1 base field scalar multiplication hint type. + Secp256K1FpMulScalar = HINT_SECP256K1_FP_MUL_SCALAR, + /// Secp256k1 to affine coordinates hint type. + Secp256K1ToAffine = HINT_SECP256K1_TO_AFFINE, + /// Secp256k1 point decompression hint type. + Secp256K1Decompress = HINT_SECP256K1_DECOMPRESS, + /// Secp256k1 double scalar multiplication with G hint type. + Secp256K1DoubleScalarMulWithG = HINT_SECP256K1_DOUBLE_SCALAR_MUL_WITH_G, + /// Secp256k1 ECDSA verification hint type. + Secp256K1EcdsaVerify = HINT_SECP256K1_ECDSA_VERIFY, // Big Integer Arithmetic Hints /// Modular reduction of a 256-bit integer hint type. @@ -222,8 +268,24 @@ impl Display for BuiltInHint { // Noop Hint BuiltInHint::Noop => "NOOP", - // Ecrecover Hint - BuiltInHint::EcRecover => "ECRECOVER", + // Secp256k1 Scalar Hints + BuiltInHint::Secp256K1FnReduce => "SECP256K1_FN_REDUCE", + BuiltInHint::Secp256K1FnAdd => "SECP256K1_FN_ADD", + BuiltInHint::Secp256K1FnNeg => "SECP256K1_FN_NEG", + BuiltInHint::Secp256K1FnSub => "SECP256K1_FN_SUB", + BuiltInHint::Secp256K1FnMul => "SECP256K1_FN_MUL", + BuiltInHint::Secp256K1FnInv => "SECP256K1_FN_INV", + // Secp256k1 Field Hints + BuiltInHint::Secp256K1FpReduce => "SECP256K1_FP_REDUCE", + BuiltInHint::Secp256K1FpAdd => "SECP256K1_FP_ADD", + BuiltInHint::Secp256K1FpNegate => "SECP256K1_FP_NEGATE", + BuiltInHint::Secp256K1FpMul => "SECP256K1_FP_MUL", + BuiltInHint::Secp256K1FpMulScalar => "SECP256K1_FP_MUL_SCALAR", + // Secp256k1 Curve Hints + BuiltInHint::Secp256K1ToAffine => "SECP256K1_TO_AFFINE", + BuiltInHint::Secp256K1Decompress => "SECP256K1_DECOMPRESS", + BuiltInHint::Secp256K1DoubleScalarMulWithG => "SECP256K1_DOUBLE_SCALAR_MUL_WITH_G", + BuiltInHint::Secp256K1EcdsaVerify => "SECP256K1_ECDSA_VERIFY", // Big Integer Arithmetic Hints BuiltInHint::RedMod256 => "REDMOD256", @@ -274,8 +336,24 @@ impl TryFrom for BuiltInHint { // Noop Hint HINT_NOOP => Ok(Self::Noop), - // Ecrecover Hint - HINT_ECRECOVER => Ok(Self::EcRecover), + // Secp256K1 Scalar Hints + HINT_SECP256K1_FN_REDUCE => Ok(Self::Secp256K1FnReduce), + HINT_SECP256K1_FN_ADD => Ok(Self::Secp256K1FnAdd), + HINT_SECP256K1_FN_NEG => Ok(Self::Secp256K1FnNeg), + HINT_SECP256K1_FN_SUB => Ok(Self::Secp256K1FnSub), + HINT_SECP256K1_FN_MUL => Ok(Self::Secp256K1FnMul), + HINT_SECP256K1_FN_INV => Ok(Self::Secp256K1FnInv), + // Secp256k1 Field Hints + HINT_SECP256K1_FP_REDUCE => Ok(Self::Secp256K1FpReduce), + HINT_SECP256K1_FP_ADD => Ok(Self::Secp256K1FpAdd), + HINT_SECP256K1_FP_NEGATE => Ok(Self::Secp256K1FpNegate), + HINT_SECP256K1_FP_MUL => Ok(Self::Secp256K1FpMul), + HINT_SECP256K1_FP_MUL_SCALAR => Ok(Self::Secp256K1FpMulScalar), + // Secp256k1 Curve Hints + HINT_SECP256K1_TO_AFFINE => Ok(Self::Secp256K1ToAffine), + HINT_SECP256K1_DECOMPRESS => Ok(Self::Secp256K1Decompress), + HINT_SECP256K1_DOUBLE_SCALAR_MUL_WITH_G => Ok(Self::Secp256K1DoubleScalarMulWithG), + HINT_SECP256K1_ECDSA_VERIFY => Ok(Self::Secp256K1EcdsaVerify), // Big Integer Arithmetic Hints HINT_REDMOD256 => Ok(Self::RedMod256), @@ -373,8 +451,26 @@ impl HintCode { // Noop Hint HintCode::BuiltIn(BuiltInHint::Noop) => HINT_NOOP, - // Ecrecover Hint - HintCode::BuiltIn(BuiltInHint::EcRecover) => HINT_ECRECOVER, + // Secp256K1 Scalar Hint Codes + HintCode::BuiltIn(BuiltInHint::Secp256K1FnReduce) => HINT_SECP256K1_FN_REDUCE, + HintCode::BuiltIn(BuiltInHint::Secp256K1FnAdd) => HINT_SECP256K1_FN_ADD, + HintCode::BuiltIn(BuiltInHint::Secp256K1FnNeg) => HINT_SECP256K1_FN_NEG, + HintCode::BuiltIn(BuiltInHint::Secp256K1FnSub) => HINT_SECP256K1_FN_SUB, + HintCode::BuiltIn(BuiltInHint::Secp256K1FnMul) => HINT_SECP256K1_FN_MUL, + HintCode::BuiltIn(BuiltInHint::Secp256K1FnInv) => HINT_SECP256K1_FN_INV, + // Secp256k1 Field Hint Codes + HintCode::BuiltIn(BuiltInHint::Secp256K1FpReduce) => HINT_SECP256K1_FP_REDUCE, + HintCode::BuiltIn(BuiltInHint::Secp256K1FpAdd) => HINT_SECP256K1_FP_ADD, + HintCode::BuiltIn(BuiltInHint::Secp256K1FpNegate) => HINT_SECP256K1_FP_NEGATE, + HintCode::BuiltIn(BuiltInHint::Secp256K1FpMul) => HINT_SECP256K1_FP_MUL, + HintCode::BuiltIn(BuiltInHint::Secp256K1FpMulScalar) => HINT_SECP256K1_FP_MUL_SCALAR, + // Secp256k1 Curve Hint Codes + HintCode::BuiltIn(BuiltInHint::Secp256K1ToAffine) => HINT_SECP256K1_TO_AFFINE, + HintCode::BuiltIn(BuiltInHint::Secp256K1Decompress) => HINT_SECP256K1_DECOMPRESS, + HintCode::BuiltIn(BuiltInHint::Secp256K1DoubleScalarMulWithG) => { + HINT_SECP256K1_DOUBLE_SCALAR_MUL_WITH_G + } + HintCode::BuiltIn(BuiltInHint::Secp256K1EcdsaVerify) => HINT_SECP256K1_ECDSA_VERIFY, // Big Integer Arithmetic Hints HintCode::BuiltIn(BuiltInHint::RedMod256) => HINT_REDMOD256, diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index b1500016d..cfdbc006d 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -559,8 +559,54 @@ impl HintsProcessor { custom_handlers: Arc>, ) -> Result> { match hint.hint_code { - // EcRecover Hint - HintCode::BuiltIn(BuiltInHint::EcRecover) => Self::process_hint_ecrecover(&hint), + // Secp256K1 Hint + HintCode::BuiltIn(BuiltInHint::Secp256K1FnReduce) => { + Self::process_hint_secp256k1_fn_reduce(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1FnAdd) => { + Self::process_hint_secp256k1_fn_add(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1FnNeg) => { + Self::process_hint_secp256k1_fn_neg(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1FnSub) => { + Self::process_hint_secp256k1_fn_sub(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1FnMul) => { + Self::process_hint_secp256k1_fn_mul(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1FnInv) => { + Self::process_hint_secp256k1_fn_inv(&hint) + } + // Secp256k1 Field Hint Codes + HintCode::BuiltIn(BuiltInHint::Secp256K1FpReduce) => { + Self::process_hint_secp256k1_fp_reduce(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1FpAdd) => { + Self::process_hint_secp256k1_fp_add(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1FpNegate) => { + Self::process_hint_secp256k1_fp_negate(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1FpMul) => { + Self::process_hint_secp256k1_fp_mul(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1FpMulScalar) => { + Self::process_hint_secp256k1_fp_mul_scalar(&hint) + } + // Secp256k1 Curve Hint Codes + HintCode::BuiltIn(BuiltInHint::Secp256K1ToAffine) => { + Self::process_hint_secp256k1_to_affine(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1Decompress) => { + Self::process_hint_secp256k1_decompress(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1DoubleScalarMulWithG) => { + Self::process_hint_secp256k1_double_scalar_mul_with_g(&hint) + } + HintCode::BuiltIn(BuiltInHint::Secp256K1EcdsaVerify) => { + Self::process_hint_secp256k1_ecdsa_verify(&hint) + } // Big Integer Arithmetic Hints HintCode::BuiltIn(BuiltInHint::RedMod256) => Self::process_hint_redmod256(&hint), @@ -649,9 +695,84 @@ impl HintsProcessor { } } - /// Processes a [`ECRECOVER`] hint. + /// Processes a [`SECP256K1_FN_REDUCE`] hint. + #[inline] + fn process_hint_secp256k1_fn_reduce(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_fn_reduce_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_FN_ADD`] hint. + #[inline] + fn process_hint_secp256k1_fn_add(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_fn_add_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_FN_NEG`] hint. + #[inline] + fn process_hint_secp256k1_fn_neg(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_fn_neg_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_FN_SUB`] hint. + #[inline] + fn process_hint_secp256k1_fn_sub(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_fn_sub_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_FN_MUL`] hint. + #[inline] + fn process_hint_secp256k1_fn_mul(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_fn_mul_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_FN_INV`] hint. + #[inline] + fn process_hint_secp256k1_fn_inv(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_fn_inv_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_FP_REDUCE`] hint. + #[inline] + fn process_hint_secp256k1_fp_reduce(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_fp_reduce_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_FP_ADD`] hint. + #[inline] + fn process_hint_secp256k1_fp_add(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_fp_add_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_FP_NEGATE`] hint. + #[inline] + fn process_hint_secp256k1_fp_negate(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_fp_negate_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_FP_MUL`] hint. + #[inline] + fn process_hint_secp256k1_fp_mul(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_fp_mul_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_FP_MUL_SCALAR`] hint. + #[inline] + fn process_hint_secp256k1_fp_mul_scalar(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_fp_mul_scalar_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + /// Processes a [`SECP256K1_TO_AFFINE`] hint. + #[inline] + fn process_hint_secp256k1_to_affine(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_to_affine_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_DECOMPRESS`] hint. + #[inline] + fn process_hint_secp256k1_decompress(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_decompress_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + /// Processes a [`SECP256K1_DOUBLE_SCALAR_MUL_WITH_G`] hint. + #[inline] + fn process_hint_secp256k1_double_scalar_mul_with_g(hint: &PrecompileHint) -> Result> { + ziskos_hints::handlers::secp256k1_double_scalar_mul_with_g_hint(&hint.data) + .map_err(|e| anyhow::anyhow!(e)) + } + + /// Processes a [`SECP256K1_ECDSA_VERIFY`] hint. #[inline] - fn process_hint_ecrecover(hint: &PrecompileHint) -> Result> { + fn process_hint_secp256k1_ecdsa_verify(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::secp256k1_ecdsa_verify_hint(&hint.data) .map_err(|e| anyhow::anyhow!(e)) } diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 84a1820a0..357ad0829 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -8,134 +8,97 @@ use crate::handlers::validate_hint_length; use crate::hint_fields; use crate::zisklib; -/// Processes an ECRECOVER hint. -/// -/// # Arguments -/// -/// * `data` - The hint data containing pk(33 bytes) + z(32 bytes) + sig(64 bytes) = 129 bytes -/// -/// # Returns -/// -/// * `Ok(Vec)` - The processed hints from the verification -/// * `Err` - If the data length is invalid #[inline] -pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { - hint_fields![X: 4, Y: 4, INFINITY:1, Z: 4, SIG: 8]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_ECDSA_VERIFY")?; - - // Reverse limb order for x and y coordinates (big-endian to little-endian) - let pk = [ - data[X_OFFSET + 3].to_be(), - data[X_OFFSET + 2].to_be(), - data[X_OFFSET + 1].to_be(), - data[X_OFFSET].to_be(), - data[Y_OFFSET + 3].to_be(), - data[Y_OFFSET + 2].to_be(), - data[Y_OFFSET + 1].to_be(), - data[Y_OFFSET].to_be(), - ]; - - // Reverse limb order for signature (big-endian to little-endian) - let sig = [ - data[SIG_OFFSET + 3].to_be(), - data[SIG_OFFSET + 2].to_be(), - data[SIG_OFFSET + 1].to_be(), - data[SIG_OFFSET].to_be(), - data[SIG_OFFSET + 7].to_be(), - data[SIG_OFFSET + 6].to_be(), - data[SIG_OFFSET + 5].to_be(), - data[SIG_OFFSET + 4].to_be(), - ]; - - // If the point is at infinity, return an error - if data[INFINITY_OFFSET] != 0 { - return Err("Error in secp256k1_ecdsa_verify: point at infinity".to_string()); - } +pub fn secp256k1_fn_reduce_hint(data: &[u64]) -> Result, String> { + hint_fields![X: 4]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_REDUCE")?; + + let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + + zisklib::secp256k1_fn_reduce(x, &mut hints); - // Extract z (32 bytes), and sig (64 bytes) - let z_bytes: &[u8; 32] = unsafe { &*(data[Z_OFFSET..SIG_OFFSET].as_ptr() as *const [u8; 32]) }; - let z_dec: U256 = U256::decode_field_bytes(z_bytes.into()); - let z_words = z_dec.to_words(); + Ok(hints) +} + +#[inline] +pub fn secp256k1_fn_add_hint(data: &[u64]) -> Result, String> { + hint_fields![X: 4, Y: 4]; - // Parse signature and decode r and s - let sig_bytes: &[u8; 64] = unsafe { &*(sig.as_ptr() as *const [u8; 64]) }; - let sig = Signature::try_from(sig_bytes.as_slice()) - .map_err(|e| format!("Failed to parse signature: {}", e))?; + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_ADD")?; - // Extract r and s as Scalars and convert to U256 - let r = >::decode_field_bytes(&sig.r().to_repr()); - let s = >::decode_field_bytes(&sig.s().to_repr()); - let r_words = r.to_words(); - let s_words = s.to_words(); + let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); + let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - zisklib::secp256k1_ecdsa_verify(&pk, &z_words, &r_words, &s_words, &mut hints); + zisklib::secp256k1_fn_add(x, y, &mut hints); Ok(hints) } -// Processes a SECP256K1_TO_AFFINE hint. #[inline] -pub fn secp256k1_to_affine_hint(data: &[u64]) -> Result, String> { - hint_fields![P: 12]; +pub fn secp256k1_fn_neg_hint(data: &[u64]) -> Result, String> { + hint_fields![X: 4]; - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_TO_AFFINE")?; + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_NEG")?; - let mut out: [u64; 8] = [0; 8]; - let mut processed_hints = Vec::new(); + let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - unsafe { - zisklib::secp256k1_to_affine_c(&data[P_OFFSET], &mut out[0], &mut processed_hints); - } + let mut hints = Vec::new(); - Ok(processed_hints) + zisklib::secp256k1_fn_neg(x, &mut hints); + + Ok(hints) } -// Processes a SECP256K1_DECOMPRESS hint. #[inline] -pub fn secp256k1_decompress_hint(data: &[u64]) -> Result, String> { - hint_fields![X_BYTES: 4, Y_IS_ODD: 1]; +pub fn secp256k1_fn_sub_hint(data: &[u64]) -> Result, String> { + hint_fields![X: 4, Y: 4]; - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DECOMPRESS")?; + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_SUB")?; - let mut out: [u64; 8] = [0; 8]; - let mut processed_hints = Vec::new(); + let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); + let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); - unsafe { - zisklib::secp256k1_decompress_c( - &data[X_BYTES_OFFSET] as *const u64 as *const u8, - (data[Y_IS_ODD_OFFSET] >> 56) as u8, - &mut out[0], - &mut processed_hints, - ); - } + let mut hints = Vec::new(); - Ok(processed_hints) + zisklib::secp256k1_fn_sub(x, y, &mut hints); + + Ok(hints) } -// Processes a SECP256K1_DOUBLE_SCALAR_MUL_WITH_G hint. #[inline] -pub fn secp256k1_double_scalar_mul_with_g_hint(data: &[u64]) -> Result, String> { - hint_fields![K1: 4, K2: 4, P: 8]; +pub fn secp256k1_fn_mul_hint(data: &[u64]) -> Result, String> { + hint_fields![X: 4, Y: 4]; - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DOUBLE_SCALAR_MUL_WITH_G")?; + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_MUL")?; - let mut out: [u64; 8] = [0; 8]; - let mut processed_hints = Vec::new(); + let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); + let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); - unsafe { - zisklib::secp256k1_double_scalar_mul_with_g_c( - &data[K1_OFFSET], - &data[K2_OFFSET], - &data[P_OFFSET], - &mut out[0], - &mut processed_hints, - ); - } + let mut hints = Vec::new(); - Ok(processed_hints) + zisklib::secp256k1_fn_mul(x, y, &mut hints); + + Ok(hints) +} + +#[inline] +pub fn secp256k1_fn_inv_hint(data: &[u64]) -> Result, String> { + hint_fields![X: 4]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_INV")?; + + let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + + zisklib::secp256k1_fn_inv(x, &mut hints); + + Ok(hints) } // Processes a SECP256K1_FP_REDUCE hint. @@ -237,3 +200,139 @@ pub fn secp256k1_fp_mul_scalar_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } + +// Processes a SECP256K1_TO_AFFINE hint. +#[inline] +pub fn secp256k1_to_affine_hint(data: &[u64]) -> Result, String> { + hint_fields![P: 12]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_TO_AFFINE")?; + + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + + zisklib::secp256k1_to_affine(p, &mut hints); + + Ok(hints) +} + +// Processes a SECP256K1_DECOMPRESS hint. +#[inline] +pub fn secp256k1_decompress_hint(data: &[u64]) -> Result, String> { + hint_fields![X_BYTES: 4, Y_IS_ODD: 1]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DECOMPRESS")?; + + let x: &[u64; X_BYTES_SIZE] = + data[X_BYTES_OFFSET..X_BYTES_OFFSET + X_BYTES_SIZE].try_into().unwrap(); + let y_is_odd = (data[Y_IS_ODD_OFFSET] >> 56) != 0; + + let mut hints = Vec::new(); + + zisklib::secp256k1_decompress(x, y_is_odd, &mut hints) + .map_err(|e| format!("secp256k1_decompress failed: {}", e))?; + + Ok(hints) +} + +// Processes a SECP256K1_DOUBLE_SCALAR_MUL_WITH_G hint. +#[inline] +pub fn secp256k1_double_scalar_mul_with_g_hint(data: &[u64]) -> Result, String> { + hint_fields![K1: 4, K2: 4, P: 8]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DOUBLE_SCALAR_MUL_WITH_G")?; + + let k1: &[u64; K1_SIZE] = data[K1_OFFSET..K1_OFFSET + K1_SIZE].try_into().unwrap(); + let k2: &[u64; K2_SIZE] = data[K2_OFFSET..K2_OFFSET + K2_SIZE].try_into().unwrap(); + let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + + zisklib::secp256k1_double_scalar_mul_with_g(k1, k2, p, &mut hints); + + Ok(hints) +} + +/// Processes an ECRECOVER hint. +/// +/// # Arguments +/// +/// * `data` - The hint data containing pk(33 bytes) + z(32 bytes) + sig(64 bytes) = 129 bytes +/// +/// # Returns +/// +/// * `Ok(Vec)` - The processed hints from the verification +/// * `Err` - If the data length is invalid +#[inline] +pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { + hint_fields![PK: 8, Z: 4, R: 4, S: 4]; + + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DOUBLE_SCALAR_MUL_WITH_G")?; + + let pk: &[u64; PK_SIZE] = data[PK_OFFSET..PK_OFFSET + PK_SIZE].try_into().unwrap(); + let z: &[u64; Z_SIZE] = data[Z_OFFSET..Z_OFFSET + Z_SIZE].try_into().unwrap(); + let r: &[u64; R_SIZE] = data[R_OFFSET..R_OFFSET + R_SIZE].try_into().unwrap(); + let s: &[u64; S_SIZE] = data[S_OFFSET..S_OFFSET + S_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + + zisklib::secp256k1_ecdsa_verify(pk, z, r, s, &mut hints); + + Ok(hints) + + // hint_fields![X: 4, Y: 4, INFINITY:1, Z: 4, SIG: 8]; + + // validate_hint_length(data, EXPECTED_LEN, "SECP256K1_ECDSA_VERIFY")?; + + // // Reverse limb order for x and y coordinates (big-endian to little-endian) + // let pk = [ + // data[X_OFFSET + 3].to_be(), + // data[X_OFFSET + 2].to_be(), + // data[X_OFFSET + 1].to_be(), + // data[X_OFFSET].to_be(), + // data[Y_OFFSET + 3].to_be(), + // data[Y_OFFSET + 2].to_be(), + // data[Y_OFFSET + 1].to_be(), + // data[Y_OFFSET].to_be(), + // ]; + + // // Reverse limb order for signature (big-endian to little-endian) + // let sig = [ + // data[SIG_OFFSET + 3].to_be(), + // data[SIG_OFFSET + 2].to_be(), + // data[SIG_OFFSET + 1].to_be(), + // data[SIG_OFFSET].to_be(), + // data[SIG_OFFSET + 7].to_be(), + // data[SIG_OFFSET + 6].to_be(), + // data[SIG_OFFSET + 5].to_be(), + // data[SIG_OFFSET + 4].to_be(), + // ]; + + // // If the point is at infinity, return an error + // if data[INFINITY_OFFSET] != 0 { + // return Err("Error in secp256k1_ecdsa_verify: point at infinity".to_string()); + // } + + // // Extract z (32 bytes), and sig (64 bytes) + // let z_bytes: &[u8; 32] = unsafe { &*(data[Z_OFFSET..SIG_OFFSET].as_ptr() as *const [u8; 32]) }; + // let z_dec: U256 = U256::decode_field_bytes(z_bytes.into()); + // let z_words = z_dec.to_words(); + + // // Parse signature and decode r and s + // let sig_bytes: &[u8; 64] = unsafe { &*(sig.as_ptr() as *const [u8; 64]) }; + // let sig = Signature::try_from(sig_bytes.as_slice()) + // .map_err(|e| format!("Failed to parse signature: {}", e))?; + + // // Extract r and s as Scalars and convert to U256 + // let r = >::decode_field_bytes(&sig.r().to_repr()); + // let s = >::decode_field_bytes(&sig.s().to_repr()); + // let r_words = r.to_words(); + // let s_words = s.to_words(); + + // let mut hints = Vec::new(); + + // zisklib::secp256k1_ecdsa_verify(&pk, &z_words, &r_words, &s_words, &mut hints); + + // Ok(hints) +} From 57a209f60afcbe167b7e4592e15d3e57c94a2f8a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 17 Jan 2026 21:44:21 +0000 Subject: [PATCH 250/782] wip --- precompiles/hints/src/hints_processor.rs | 10 ++ ziskos-hints/src/handlers/secp256k1.rs | 158 +++++------------------ 2 files changed, 41 insertions(+), 127 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index cfdbc006d..9861985d4 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -700,51 +700,61 @@ impl HintsProcessor { fn process_hint_secp256k1_fn_reduce(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::secp256k1_fn_reduce_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + /// Processes a [`SECP256K1_FN_ADD`] hint. #[inline] fn process_hint_secp256k1_fn_add(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::secp256k1_fn_add_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + /// Processes a [`SECP256K1_FN_NEG`] hint. #[inline] fn process_hint_secp256k1_fn_neg(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::secp256k1_fn_neg_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + /// Processes a [`SECP256K1_FN_SUB`] hint. #[inline] fn process_hint_secp256k1_fn_sub(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::secp256k1_fn_sub_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + /// Processes a [`SECP256K1_FN_MUL`] hint. #[inline] fn process_hint_secp256k1_fn_mul(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::secp256k1_fn_mul_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + /// Processes a [`SECP256K1_FN_INV`] hint. #[inline] fn process_hint_secp256k1_fn_inv(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::secp256k1_fn_inv_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + /// Processes a [`SECP256K1_FP_REDUCE`] hint. #[inline] fn process_hint_secp256k1_fp_reduce(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::secp256k1_fp_reduce_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + /// Processes a [`SECP256K1_FP_ADD`] hint. #[inline] fn process_hint_secp256k1_fp_add(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::secp256k1_fp_add_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + /// Processes a [`SECP256K1_FP_NEGATE`] hint. #[inline] fn process_hint_secp256k1_fp_negate(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::secp256k1_fp_negate_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + /// Processes a [`SECP256K1_FP_MUL`] hint. #[inline] fn process_hint_secp256k1_fp_mul(hint: &PrecompileHint) -> Result> { ziskos_hints::handlers::secp256k1_fp_mul_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) } + /// Processes a [`SECP256K1_FP_MUL_SCALAR`] hint. #[inline] fn process_hint_secp256k1_fp_mul_scalar(hint: &PrecompileHint) -> Result> { diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 357ad0829..ec0d07b7d 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -1,13 +1,8 @@ -use elliptic_curve::FieldBytesEncoding; -use elliptic_curve::PrimeField; -use k256::ecdsa::Signature; -use k256::Secp256k1; -use k256::U256; - use crate::handlers::validate_hint_length; use crate::hint_fields; use crate::zisklib; +// Processes a SECP256K1_FN_REDUCE hint. #[inline] pub fn secp256k1_fn_reduce_hint(data: &[u64]) -> Result, String> { hint_fields![X: 4]; @@ -17,12 +12,12 @@ pub fn secp256k1_fn_reduce_hint(data: &[u64]) -> Result, String> { let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - zisklib::secp256k1_fn_reduce(x, &mut hints); Ok(hints) } +// Processes a SECP256K1_FN_ADD hint. #[inline] pub fn secp256k1_fn_add_hint(data: &[u64]) -> Result, String> { hint_fields![X: 4, Y: 4]; @@ -33,12 +28,12 @@ pub fn secp256k1_fn_add_hint(data: &[u64]) -> Result, String> { let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - zisklib::secp256k1_fn_add(x, y, &mut hints); Ok(hints) } +// Processes a SECP256K1_FN_NEG hint. #[inline] pub fn secp256k1_fn_neg_hint(data: &[u64]) -> Result, String> { hint_fields![X: 4]; @@ -48,12 +43,12 @@ pub fn secp256k1_fn_neg_hint(data: &[u64]) -> Result, String> { let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - zisklib::secp256k1_fn_neg(x, &mut hints); Ok(hints) } +// Processes a SECP256K1_FN_SUB hint. #[inline] pub fn secp256k1_fn_sub_hint(data: &[u64]) -> Result, String> { hint_fields![X: 4, Y: 4]; @@ -64,12 +59,12 @@ pub fn secp256k1_fn_sub_hint(data: &[u64]) -> Result, String> { let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - zisklib::secp256k1_fn_sub(x, y, &mut hints); Ok(hints) } +// Processes a SECP256K1_FN_MUL hint. #[inline] pub fn secp256k1_fn_mul_hint(data: &[u64]) -> Result, String> { hint_fields![X: 4, Y: 4]; @@ -80,12 +75,12 @@ pub fn secp256k1_fn_mul_hint(data: &[u64]) -> Result, String> { let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - zisklib::secp256k1_fn_mul(x, y, &mut hints); Ok(hints) } +// Processes a SECP256K1_FN_INV hint. #[inline] pub fn secp256k1_fn_inv_hint(data: &[u64]) -> Result, String> { hint_fields![X: 4]; @@ -95,7 +90,6 @@ pub fn secp256k1_fn_inv_hint(data: &[u64]) -> Result, String> { let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - zisklib::secp256k1_fn_inv(x, &mut hints); Ok(hints) @@ -108,14 +102,12 @@ pub fn secp256k1_fp_reduce_hint(data: &[u64]) -> Result, String> { validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_REDUCE")?; - let mut out: [u64; 4] = [0; 4]; - let mut processed_hints = Vec::new(); + let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - unsafe { - zisklib::secp256k1_fp_reduce_c(&data[X_OFFSET], &mut out[0], &mut processed_hints); - } + let mut hints = Vec::new(); + zisklib::secp256k1_fp_reduce(x, &mut hints); - Ok(processed_hints) + Ok(hints) } // Processes a SECP256K1_FP_ADD hint. @@ -125,19 +117,13 @@ pub fn secp256k1_fp_add_hint(data: &[u64]) -> Result, String> { validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_ADD")?; - let mut out: [u64; 4] = [0; 4]; - let mut processed_hints = Vec::new(); + let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); + let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); - unsafe { - zisklib::secp256k1_fp_add_c( - &data[X_OFFSET], - &data[Y_OFFSET], - &mut out[0], - &mut processed_hints, - ); - } + let mut hints = Vec::new(); + zisklib::secp256k1_fp_add(x, y, &mut hints); - Ok(processed_hints) + Ok(hints) } // Processes a SECP256K1_FP_NEGATE hint. @@ -147,14 +133,12 @@ pub fn secp256k1_fp_negate_hint(data: &[u64]) -> Result, String> { validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_NEGATE")?; - let mut out: [u64; 4] = [0; 4]; - let mut processed_hints = Vec::new(); + let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - unsafe { - zisklib::secp256k1_fp_negate_c(&data[X_OFFSET], &mut out[0], &mut processed_hints); - } + let mut hints = Vec::new(); + zisklib::secp256k1_fp_negate(x, &mut hints); - Ok(processed_hints) + Ok(hints) } // Processes a SECP256K1_FP_MUL hint. @@ -164,19 +148,13 @@ pub fn secp256k1_fp_mul_hint(data: &[u64]) -> Result, String> { validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_MUL")?; - let mut out: [u64; 4] = [0; 4]; - let mut processed_hints = Vec::new(); + let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); + let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); - unsafe { - zisklib::secp256k1_fp_mul_c( - &data[X_OFFSET], - &data[Y_OFFSET], - &mut out[0], - &mut processed_hints, - ); - } + let mut hints = Vec::new(); + zisklib::secp256k1_fp_mul(x, y, &mut hints); - Ok(processed_hints) + Ok(hints) } // Processes a SECP256K1_FP_MUL_SCALAR hint. @@ -186,19 +164,13 @@ pub fn secp256k1_fp_mul_scalar_hint(data: &[u64]) -> Result, String> { validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_MUL_SCALAR")?; - let mut out: [u64; 4] = [0; 4]; - let mut processed_hints = Vec::new(); + let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); + let scalar: u64 = data[SCALAR_OFFSET]; - unsafe { - zisklib::secp256k1_fp_mul_scalar_c( - &data[X_OFFSET], - data[SCALAR_OFFSET], - &mut out[0], - &mut processed_hints, - ); - } + let mut hints = Vec::new(); + zisklib::secp256k1_fp_mul_scalar(x, scalar, &mut hints); - Ok(processed_hints) + Ok(hints) } // Processes a SECP256K1_TO_AFFINE hint. @@ -211,7 +183,6 @@ pub fn secp256k1_to_affine_hint(data: &[u64]) -> Result, String> { let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - zisklib::secp256k1_to_affine(p, &mut hints); Ok(hints) @@ -229,7 +200,6 @@ pub fn secp256k1_decompress_hint(data: &[u64]) -> Result, String> { let y_is_odd = (data[Y_IS_ODD_OFFSET] >> 56) != 0; let mut hints = Vec::new(); - zisklib::secp256k1_decompress(x, y_is_odd, &mut hints) .map_err(|e| format!("secp256k1_decompress failed: {}", e))?; @@ -248,27 +218,17 @@ pub fn secp256k1_double_scalar_mul_with_g_hint(data: &[u64]) -> Result, let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - zisklib::secp256k1_double_scalar_mul_with_g(k1, k2, p, &mut hints); Ok(hints) } -/// Processes an ECRECOVER hint. -/// -/// # Arguments -/// -/// * `data` - The hint data containing pk(33 bytes) + z(32 bytes) + sig(64 bytes) = 129 bytes -/// -/// # Returns -/// -/// * `Ok(Vec)` - The processed hints from the verification -/// * `Err` - If the data length is invalid +/// Processes an ECDSA_VERIFY hint. #[inline] pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { hint_fields![PK: 8, Z: 4, R: 4, S: 4]; - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DOUBLE_SCALAR_MUL_WITH_G")?; + validate_hint_length(data, EXPECTED_LEN, "SECP256K1_ECDSA_VERIFY")?; let pk: &[u64; PK_SIZE] = data[PK_OFFSET..PK_OFFSET + PK_SIZE].try_into().unwrap(); let z: &[u64; Z_SIZE] = data[Z_OFFSET..Z_OFFSET + Z_SIZE].try_into().unwrap(); @@ -276,63 +236,7 @@ pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { let s: &[u64; S_SIZE] = data[S_OFFSET..S_OFFSET + S_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - zisklib::secp256k1_ecdsa_verify(pk, z, r, s, &mut hints); Ok(hints) - - // hint_fields![X: 4, Y: 4, INFINITY:1, Z: 4, SIG: 8]; - - // validate_hint_length(data, EXPECTED_LEN, "SECP256K1_ECDSA_VERIFY")?; - - // // Reverse limb order for x and y coordinates (big-endian to little-endian) - // let pk = [ - // data[X_OFFSET + 3].to_be(), - // data[X_OFFSET + 2].to_be(), - // data[X_OFFSET + 1].to_be(), - // data[X_OFFSET].to_be(), - // data[Y_OFFSET + 3].to_be(), - // data[Y_OFFSET + 2].to_be(), - // data[Y_OFFSET + 1].to_be(), - // data[Y_OFFSET].to_be(), - // ]; - - // // Reverse limb order for signature (big-endian to little-endian) - // let sig = [ - // data[SIG_OFFSET + 3].to_be(), - // data[SIG_OFFSET + 2].to_be(), - // data[SIG_OFFSET + 1].to_be(), - // data[SIG_OFFSET].to_be(), - // data[SIG_OFFSET + 7].to_be(), - // data[SIG_OFFSET + 6].to_be(), - // data[SIG_OFFSET + 5].to_be(), - // data[SIG_OFFSET + 4].to_be(), - // ]; - - // // If the point is at infinity, return an error - // if data[INFINITY_OFFSET] != 0 { - // return Err("Error in secp256k1_ecdsa_verify: point at infinity".to_string()); - // } - - // // Extract z (32 bytes), and sig (64 bytes) - // let z_bytes: &[u8; 32] = unsafe { &*(data[Z_OFFSET..SIG_OFFSET].as_ptr() as *const [u8; 32]) }; - // let z_dec: U256 = U256::decode_field_bytes(z_bytes.into()); - // let z_words = z_dec.to_words(); - - // // Parse signature and decode r and s - // let sig_bytes: &[u8; 64] = unsafe { &*(sig.as_ptr() as *const [u8; 64]) }; - // let sig = Signature::try_from(sig_bytes.as_slice()) - // .map_err(|e| format!("Failed to parse signature: {}", e))?; - - // // Extract r and s as Scalars and convert to U256 - // let r = >::decode_field_bytes(&sig.r().to_repr()); - // let s = >::decode_field_bytes(&sig.s().to_repr()); - // let r_words = r.to_words(); - // let s_words = s.to_words(); - - // let mut hints = Vec::new(); - - // zisklib::secp256k1_ecdsa_verify(&pk, &z_words, &r_words, &s_words, &mut hints); - - // Ok(hints) } From 3ce95b04d90dcc8815b7c2c86c6db136c81ec619 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sun, 18 Jan 2026 07:59:06 +0000 Subject: [PATCH 251/782] improve hint handler calls --- Cargo.lock | 1 + precompiles/hints/src/hints_processor.rs | 429 ++++------------------- ziskos-hints/Cargo.toml | 2 + ziskos-hints/src/handlers/bigint256.rs | 30 +- ziskos-hints/src/handlers/bls318.rs | 58 +-- ziskos-hints/src/handlers/bn254.rs | 36 +- ziskos-hints/src/handlers/mod.rs | 40 ++- ziskos-hints/src/handlers/modexp.rs | 19 +- ziskos-hints/src/handlers/secp256k1.rs | 65 ++-- 9 files changed, 191 insertions(+), 489 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e53df17c0..774cabf5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6223,6 +6223,7 @@ dependencies = [ name = "ziskos-hints" version = "0.16.0" dependencies = [ + "anyhow", "bincode", "cfg-if", "elliptic-curve", diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 9861985d4..aa602f365 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -13,6 +13,7 @@ use std::sync::{Arc, Condvar, Mutex}; use tracing::debug; use zisk_common::io::{StreamProcessor, StreamSink}; use zisk_common::{BuiltInHint, CtrlHint, HintCode, PrecompileHint}; +use ziskos_hints::handlers::{bigint256::*, bls318::*, bn254::*, modexp::*, secp256k1::*}; /// Ordered result buffer with drain state. /// @@ -559,392 +560,80 @@ impl HintsProcessor { custom_handlers: Arc>, ) -> Result> { match hint.hint_code { + HintCode::BuiltIn(builtin) => Self::dispatch_builtin_hint(builtin, hint.data), + HintCode::Custom(code) => custom_handlers + .get(&code) + .map(|handler| handler(&hint.data)) + .unwrap_or_else(|| Err(anyhow::anyhow!("Unknown custom hint"))), + _ => unreachable!("Control hints handled before dispatch"), + } + } + + #[inline] + fn dispatch_builtin_hint(hint: BuiltInHint, data: Vec) -> Result> { + match hint { // Secp256K1 Hint - HintCode::BuiltIn(BuiltInHint::Secp256K1FnReduce) => { - Self::process_hint_secp256k1_fn_reduce(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1FnAdd) => { - Self::process_hint_secp256k1_fn_add(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1FnNeg) => { - Self::process_hint_secp256k1_fn_neg(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1FnSub) => { - Self::process_hint_secp256k1_fn_sub(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1FnMul) => { - Self::process_hint_secp256k1_fn_mul(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1FnInv) => { - Self::process_hint_secp256k1_fn_inv(&hint) - } + BuiltInHint::Secp256K1FnReduce => secp256k1_fn_reduce_hint(&data), + BuiltInHint::Secp256K1FnAdd => secp256k1_fn_add_hint(&data), + BuiltInHint::Secp256K1FnNeg => secp256k1_fn_neg_hint(&data), + BuiltInHint::Secp256K1FnSub => secp256k1_fn_sub_hint(&data), + BuiltInHint::Secp256K1FnMul => secp256k1_fn_mul_hint(&data), + BuiltInHint::Secp256K1FnInv => secp256k1_fn_inv_hint(&data), // Secp256k1 Field Hint Codes - HintCode::BuiltIn(BuiltInHint::Secp256K1FpReduce) => { - Self::process_hint_secp256k1_fp_reduce(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1FpAdd) => { - Self::process_hint_secp256k1_fp_add(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1FpNegate) => { - Self::process_hint_secp256k1_fp_negate(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1FpMul) => { - Self::process_hint_secp256k1_fp_mul(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1FpMulScalar) => { - Self::process_hint_secp256k1_fp_mul_scalar(&hint) - } + BuiltInHint::Secp256K1FpReduce => secp256k1_fp_reduce_hint(&data), + BuiltInHint::Secp256K1FpAdd => secp256k1_fp_add_hint(&data), + BuiltInHint::Secp256K1FpNegate => secp256k1_fp_negate_hint(&data), + BuiltInHint::Secp256K1FpMul => secp256k1_fp_mul_hint(&data), + BuiltInHint::Secp256K1FpMulScalar => secp256k1_fp_mul_scalar_hint(&data), // Secp256k1 Curve Hint Codes - HintCode::BuiltIn(BuiltInHint::Secp256K1ToAffine) => { - Self::process_hint_secp256k1_to_affine(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1Decompress) => { - Self::process_hint_secp256k1_decompress(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1DoubleScalarMulWithG) => { - Self::process_hint_secp256k1_double_scalar_mul_with_g(&hint) - } - HintCode::BuiltIn(BuiltInHint::Secp256K1EcdsaVerify) => { - Self::process_hint_secp256k1_ecdsa_verify(&hint) + BuiltInHint::Secp256K1ToAffine => secp256k1_to_affine_hint(&data), + BuiltInHint::Secp256K1Decompress => secp256k1_decompress_hint(&data), + BuiltInHint::Secp256K1DoubleScalarMulWithG => { + secp256k1_double_scalar_mul_with_g_hint(&data) } + BuiltInHint::Secp256K1EcdsaVerify => secp256k1_ecdsa_verify_hint(&data), // Big Integer Arithmetic Hints - HintCode::BuiltIn(BuiltInHint::RedMod256) => Self::process_hint_redmod256(&hint), - HintCode::BuiltIn(BuiltInHint::AddMod256) => Self::process_hint_addmod256(&hint), - HintCode::BuiltIn(BuiltInHint::MulMod256) => Self::process_hint_mulmod256(&hint), - HintCode::BuiltIn(BuiltInHint::DivRem256) => Self::process_hint_divrem256(&hint), - HintCode::BuiltIn(BuiltInHint::WPow256) => Self::process_hint_wpow256(&hint), - HintCode::BuiltIn(BuiltInHint::OMul256) => Self::process_hint_omul256(&hint), - HintCode::BuiltIn(BuiltInHint::WMul256) => Self::process_hint_wmul256(&hint), + BuiltInHint::RedMod256 => redmod256_hint(&data), + BuiltInHint::AddMod256 => addmod256_hint(&data), + BuiltInHint::MulMod256 => mulmod256_hint(&data), + BuiltInHint::DivRem256 => divrem256_hint(&data), + BuiltInHint::WPow256 => wpow256_hint(&data), + BuiltInHint::OMul256 => omul256_hint(&data), + BuiltInHint::WMul256 => wmul256_hint(&data), // Modular Exponentiation Hint - HintCode::BuiltIn(BuiltInHint::ModExp) => Self::process_hint_modexp(&hint), + BuiltInHint::ModExp => modexp_hint(&data), // BN254 hints - HintCode::BuiltIn(BuiltInHint::IsOnCurveBn254) => { - Self::process_hint_is_on_curve_bn254(&hint) - } - HintCode::BuiltIn(BuiltInHint::ToAffineBn254) => { - Self::process_hint_to_affine_bn254(&hint) - } - HintCode::BuiltIn(BuiltInHint::AddBn254) => Self::process_hint_add_bn254(&hint), - HintCode::BuiltIn(BuiltInHint::MulBn254) => Self::process_hint_mul_bn254(&hint), - HintCode::BuiltIn(BuiltInHint::ToAffineTwistBn254) => { - Self::process_hint_to_affine_twist_bn254(&hint) - } - HintCode::BuiltIn(BuiltInHint::IsOnCurveTwistBn254) => { - Self::process_hint_is_on_curve_twist_bn254(&hint) - } - HintCode::BuiltIn(BuiltInHint::IsOnSubgroupTwistBn254) => { - Self::process_hint_is_on_subgroup_twist_bn254(&hint) - } - HintCode::BuiltIn(BuiltInHint::PairingBatchBn254) => { - Self::process_hint_pairing_batch_bn254(&hint) - } + BuiltInHint::IsOnCurveBn254 => is_on_curve_bn254_hint(&data), + BuiltInHint::ToAffineBn254 => to_affine_bn254_hint(&data), + BuiltInHint::AddBn254 => add_bn254_hint(&data), + BuiltInHint::MulBn254 => mul_bn254_hint(&data), + BuiltInHint::ToAffineTwistBn254 => to_affine_twist_bn254_hint(&data), + BuiltInHint::IsOnCurveTwistBn254 => is_on_curve_twist_bn254_hint(&data), + BuiltInHint::IsOnSubgroupTwistBn254 => is_on_subgroup_twist_bn254_hint(&data), + BuiltInHint::PairingBatchBn254 => pairing_batch_bn254_hint(&data), // BLS12-381 hints - HintCode::BuiltIn(BuiltInHint::MulFp12Bls12_381) => { - Self::process_hint_mul_fp_bls12_381(&hint) - } - HintCode::BuiltIn(BuiltInHint::DecompressBls12_381) => { - Self::process_hint_decompress_bls12_381(&hint) - } - HintCode::BuiltIn(BuiltInHint::IsOnCurveBls12_381) => { - Self::process_hint_is_on_curve_bls12_381(&hint) - } - HintCode::BuiltIn(BuiltInHint::IsOnSubgroupBls12_381) => { - Self::process_hint_is_on_subgroup_bls12_381(&hint) - } - HintCode::BuiltIn(BuiltInHint::AddBls12_381) => Self::process_hint_add_bls12_381(&hint), - HintCode::BuiltIn(BuiltInHint::ScalarMulBls12_381) => { - Self::process_hint_scalar_mul_bls12_381(&hint) - } - HintCode::BuiltIn(BuiltInHint::DecompressTwistBls12_381) => { - Self::process_hint_decompress_twist_bls12_381(&hint) - } - HintCode::BuiltIn(BuiltInHint::IsOnCurveTwistBls12_381) => { - Self::process_hint_is_on_curve_twist_bls12_381(&hint) - } - HintCode::BuiltIn(BuiltInHint::IsOnSubgroupTwistBls12_381) => { - Self::process_hint_is_on_subgroup_twist_bls12_381(&hint) - } - HintCode::BuiltIn(BuiltInHint::AddTwistBls12_381) => { - Self::process_hint_add_twist_bls12_381(&hint) - } - HintCode::BuiltIn(BuiltInHint::ScalarMulTwistBls12_381) => { - Self::process_hint_scalar_mul_twist_bls12_381(&hint) - } - HintCode::BuiltIn(BuiltInHint::MillerLoopBls12_381) => { - Self::process_hint_miller_loop_bls12_381(&hint) - } - HintCode::BuiltIn(BuiltInHint::FinalExpBls12_381) => { - Self::process_hint_final_exp_bls12_381(&hint) - } - - // Custom hints - HintCode::Custom(code) => { - if let Some(handler) = custom_handlers.get(&code) { - handler(&hint.data) - } else { - Err(anyhow::anyhow!("Unknown custom hint code: {:#x}", code)) - } - } + BuiltInHint::MulFp12Bls12_381 => mul_fp12_bls12_381_hint(&data), + BuiltInHint::DecompressBls12_381 => decompress_bls12_381_hint(&data), + BuiltInHint::IsOnCurveBls12_381 => is_on_curve_bls12_381_hint(&data), + BuiltInHint::IsOnSubgroupBls12_381 => is_on_subgroup_bls12_381_hint(&data), + BuiltInHint::AddBls12_381 => add_bls12_381_hint(&data), + BuiltInHint::ScalarMulBls12_381 => scalar_mul_bls12_381_hint(&data), + BuiltInHint::DecompressTwistBls12_381 => decompress_twist_bls12_381_hint(&data), + BuiltInHint::IsOnCurveTwistBls12_381 => is_on_curve_twist_bls12_381_hint(&data), + BuiltInHint::IsOnSubgroupTwistBls12_381 => is_on_subgroup_twist_bls12_381_hint(&data), + BuiltInHint::AddTwistBls12_381 => add_twist_bls12_381_hint(&data), + BuiltInHint::ScalarMulTwistBls12_381 => scalar_mul_twist_bls12_381_hint(&data), + BuiltInHint::MillerLoopBls12_381 => miller_loop_bls12_381_hint(&data), + BuiltInHint::FinalExpBls12_381 => final_exp_bls12_381_hint(&data), // Control codes and Noop are handled before dispatch - _ => Err(anyhow::anyhow!("Unexpected hint code: {:#x}", hint.hint_code.to_u32())), + _ => Err(anyhow::anyhow!("Unexpected builtin hint: {:?}", hint)), } } - - /// Processes a [`SECP256K1_FN_REDUCE`] hint. - #[inline] - fn process_hint_secp256k1_fn_reduce(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_fn_reduce_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_FN_ADD`] hint. - #[inline] - fn process_hint_secp256k1_fn_add(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_fn_add_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_FN_NEG`] hint. - #[inline] - fn process_hint_secp256k1_fn_neg(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_fn_neg_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_FN_SUB`] hint. - #[inline] - fn process_hint_secp256k1_fn_sub(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_fn_sub_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_FN_MUL`] hint. - #[inline] - fn process_hint_secp256k1_fn_mul(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_fn_mul_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_FN_INV`] hint. - #[inline] - fn process_hint_secp256k1_fn_inv(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_fn_inv_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_FP_REDUCE`] hint. - #[inline] - fn process_hint_secp256k1_fp_reduce(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_fp_reduce_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_FP_ADD`] hint. - #[inline] - fn process_hint_secp256k1_fp_add(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_fp_add_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_FP_NEGATE`] hint. - #[inline] - fn process_hint_secp256k1_fp_negate(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_fp_negate_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_FP_MUL`] hint. - #[inline] - fn process_hint_secp256k1_fp_mul(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_fp_mul_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_FP_MUL_SCALAR`] hint. - #[inline] - fn process_hint_secp256k1_fp_mul_scalar(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_fp_mul_scalar_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_TO_AFFINE`] hint. - #[inline] - fn process_hint_secp256k1_to_affine(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_to_affine_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - /// Processes a [`SECP256K1_DECOMPRESS`] hint. - #[inline] - fn process_hint_secp256k1_decompress(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_decompress_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - /// Processes a [`SECP256K1_DOUBLE_SCALAR_MUL_WITH_G`] hint. - #[inline] - fn process_hint_secp256k1_double_scalar_mul_with_g(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_double_scalar_mul_with_g_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`SECP256K1_ECDSA_VERIFY`] hint. - #[inline] - fn process_hint_secp256k1_ecdsa_verify(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::secp256k1_ecdsa_verify_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`REDMOD256`] hint. - #[inline] - fn process_hint_redmod256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::redmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - /// Processes a [`ADDMOD256`] hint. - #[inline] - fn process_hint_addmod256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::addmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - /// Processes a [`MULMOD256`] hint. - #[inline] - fn process_hint_mulmod256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::mulmod256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - /// Processes a [`DIVREM256`] hint. - #[inline] - fn process_hint_divrem256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::divrem256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - /// Processes a [`WPOW256`] hint. - #[inline] - fn process_hint_wpow256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::wpow256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - /// Processes a [`OMUL256`] hint. - #[inline] - fn process_hint_omul256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::omul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - /// Processes a [`WMUL256`] hint. - #[inline] - fn process_hint_wmul256(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::wmul256_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - /// Processes a [`MODEXP`] hint. - #[inline] - fn process_hint_modexp(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::modexp_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_is_on_curve_bn254(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::is_on_curve_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_to_affine_bn254(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::to_affine_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_add_bn254(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::add_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_mul_bn254(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::mul_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_to_affine_twist_bn254(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::to_affine_twist_bn254_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_is_on_curve_twist_bn254(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::is_on_curve_twist_bn254_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_is_on_subgroup_twist_bn254(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::is_on_subgroup_twist_bn254_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_pairing_batch_bn254(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::pairing_batch_bn254_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_mul_fp_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::mul_fp12_bls12_381_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_decompress_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::decompress_bls12_381_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_is_on_curve_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::is_on_curve_bls12_381_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_is_on_subgroup_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::is_on_subgroup_bls12_381_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_add_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::add_bls12_381_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_scalar_mul_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::scalar_mul_bls12_381_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_decompress_twist_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::decompress_twist_bls12_381_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_is_on_curve_twist_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::is_on_curve_twist_bls12_381_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_is_on_subgroup_twist_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::is_on_subgroup_twist_bls12_381_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_add_twist_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::add_twist_bls12_381_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_scalar_mul_twist_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::scalar_mul_twist_bls12_381_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_miller_loop_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::miller_loop_bls12_381_hint(&hint.data) - .map_err(|e| anyhow::anyhow!(e)) - } - - #[inline] - fn process_hint_final_exp_bls12_381(hint: &PrecompileHint) -> Result> { - ziskos_hints::handlers::final_exp_bls12_381_hint(&hint.data).map_err(|e| anyhow::anyhow!(e)) - } } impl Drop for HintsProcessor { diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index 5e9e093ad..c61082fe5 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -33,8 +33,10 @@ serde = { workspace = true, features = ["derive"] } bincode = "2.0" paste = "1.0" +# zisk-os specific dependencies elliptic-curve = "0.13.8" k256 = "0.13.4" +anyhow = { workspace = true } [features] default = ["hints"] diff --git a/ziskos-hints/src/handlers/bigint256.rs b/ziskos-hints/src/handlers/bigint256.rs index 214c43518..75b040f26 100644 --- a/ziskos-hints/src/handlers/bigint256.rs +++ b/ziskos-hints/src/handlers/bigint256.rs @@ -2,9 +2,11 @@ use crate::handlers::validate_hint_length; use crate::hint_fields; use crate::zisklib; -/// Processes a REDMOD256 hint. +use anyhow::Result; + +/// Processes a `REDMOD256`` hint. #[inline] -pub fn redmod256_hint(data: &[u64]) -> Result, String> { +pub fn redmod256_hint(data: &[u64]) -> Result> { hint_fields![A: 4, M: 4]; validate_hint_length(data, EXPECTED_LEN, "REDMOD256")?; @@ -24,9 +26,9 @@ pub fn redmod256_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes an ADDMOD256 hint. +/// Processes an `ADDMOD256` hint. #[inline] -pub fn addmod256_hint(data: &[u64]) -> Result, String> { +pub fn addmod256_hint(data: &[u64]) -> Result> { hint_fields![A: 4, B: 4, M: 4]; validate_hint_length(data, EXPECTED_LEN, "ADDMOD256")?; @@ -47,9 +49,9 @@ pub fn addmod256_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a MULMOD256 hint. +/// Processes a `MULMOD256` hint. #[inline] -pub fn mulmod256_hint(data: &[u64]) -> Result, String> { +pub fn mulmod256_hint(data: &[u64]) -> Result> { hint_fields![A: 4, B: 4, M: 4]; validate_hint_length(data, EXPECTED_LEN, "MULMOD256")?; @@ -70,9 +72,9 @@ pub fn mulmod256_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a DIVREM256 hint. +/// Processes a `DIVREM256` hint. #[inline] -pub fn divrem256_hint(data: &[u64]) -> Result, String> { +pub fn divrem256_hint(data: &[u64]) -> Result> { hint_fields![A: 4, B: 4]; validate_hint_length(data, EXPECTED_LEN, "DIVREM256")?; @@ -95,9 +97,9 @@ pub fn divrem256_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a WPOW256 hint. +/// Processes a `WPOW256` hint. #[inline] -pub fn wpow256_hint(data: &[u64]) -> Result, String> { +pub fn wpow256_hint(data: &[u64]) -> Result> { hint_fields![A: 4, EXP: 4]; validate_hint_length(data, EXPECTED_LEN, "WPOW256")?; @@ -117,9 +119,9 @@ pub fn wpow256_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes an OMUL256 hint. +/// Processes an `OMUL256` hint. #[inline] -pub fn omul256_hint(data: &[u64]) -> Result, String> { +pub fn omul256_hint(data: &[u64]) -> Result> { hint_fields![A: 4, B: 4]; validate_hint_length(data, EXPECTED_LEN, "OMUL256")?; @@ -134,9 +136,9 @@ pub fn omul256_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a WMUL256 hint. +/// Processes a `WMUL256` hint. #[inline] -pub fn wmul256_hint(data: &[u64]) -> Result, String> { +pub fn wmul256_hint(data: &[u64]) -> Result> { hint_fields![A: 4, B: 4]; validate_hint_length(data, EXPECTED_LEN, "WMUL256")?; diff --git a/ziskos-hints/src/handlers/bls318.rs b/ziskos-hints/src/handlers/bls318.rs index ad6afdad5..a0d5d9c0b 100644 --- a/ziskos-hints/src/handlers/bls318.rs +++ b/ziskos-hints/src/handlers/bls318.rs @@ -1,8 +1,10 @@ use crate::{handlers::validate_hint_length, hint_fields, zisklib}; -/// Processes an MUL_FP12_BLS12_381 hint. +use anyhow::Result; + +/// Processes an `MUL_FP12_BLS12_381` hint. #[inline] -pub fn mul_fp12_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn mul_fp12_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![A: 72, B: 72]; validate_hint_length(data, EXPECTED_LEN, "MUL_FP12_BLS12_381")?; @@ -18,9 +20,9 @@ pub fn mul_fp12_bls12_381_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a DECOMPRESS_BLS12_381 hint. +/// Processes a `DECOMPRESS_BLS12_381` hint. #[inline] -pub fn decompress_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn decompress_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![INPUT: 6]; validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_BLS12_381")?; @@ -33,14 +35,14 @@ pub fn decompress_bls12_381_hint(data: &[u64]) -> Result, String> { let mut processed_hints = Vec::new(); - zisklib::decompress_bls12_381(input, &mut processed_hints)?; + zisklib::decompress_bls12_381(input, &mut processed_hints).map_err(anyhow::Error::msg)?; Ok(processed_hints) } -/// Processes an IS_ON_CURVE_BLS12_381 hint. +/// Processes an `IS_ON_CURVE_BLS12_381` hint. #[inline] -pub fn is_on_curve_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn is_on_curve_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![P: 12]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_BLS12_381")?; @@ -55,9 +57,9 @@ pub fn is_on_curve_bls12_381_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes an IS_ON_SUBGROUP_BLS12_381 hint. +/// Processes an `IS_ON_SUBGROUP_BLS12_381` hint. #[inline] -pub fn is_on_subgroup_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn is_on_subgroup_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![P: 12]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_BLS12_381")?; @@ -72,9 +74,9 @@ pub fn is_on_subgroup_bls12_381_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes an ADD_BLS12_381 hint. +/// Processes an `ADD_BLS12_381` hint. #[inline] -pub fn add_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn add_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![P1: 12, P2: 12]; validate_hint_length(data, EXPECTED_LEN, "ADD_BLS12_381")?; @@ -90,9 +92,9 @@ pub fn add_bls12_381_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a SCALAR_MUL_BLS12_381 hint. +/// Processes a `SCALAR_MUL_BLS12_381` hint. #[inline] -pub fn scalar_mul_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn scalar_mul_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![P: 12, K: 6]; validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_BLS12_381")?; @@ -108,9 +110,9 @@ pub fn scalar_mul_bls12_381_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a DECOMPRESS_TWIST_BLS12_381 hint. +/// Processes a `DECOMPRESS_TWIST_BLS12_381` hint. #[inline] -pub fn decompress_twist_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn decompress_twist_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![INPUT: 12]; validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_TWIST_BLS12_381")?; @@ -123,14 +125,14 @@ pub fn decompress_twist_bls12_381_hint(data: &[u64]) -> Result, String> let mut processed_hints = Vec::new(); - zisklib::decompress_twist_bls12_381(input, &mut processed_hints)?; + zisklib::decompress_twist_bls12_381(input, &mut processed_hints).map_err(anyhow::Error::msg)?; Ok(processed_hints) } -/// Processes an IS_ON_CURVE_TWIST_BLS12_381 hint. +/// Processes an `IS_ON_CURVE_TWIST_BLS12_381` hint. #[inline] -pub fn is_on_curve_twist_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn is_on_curve_twist_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![P: 24]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_TWIST_BLS12_381")?; @@ -145,9 +147,9 @@ pub fn is_on_curve_twist_bls12_381_hint(data: &[u64]) -> Result, String Ok(processed_hints) } -/// Processes an IS_ON_SUBGROUP_TWIST_BLS12_381 hint. +/// Processes an `IS_ON_SUBGROUP_TWIST_BLS12_381` hint. #[inline] -pub fn is_on_subgroup_twist_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn is_on_subgroup_twist_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![P: 24]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_TWIST_BLS12_381")?; @@ -162,9 +164,9 @@ pub fn is_on_subgroup_twist_bls12_381_hint(data: &[u64]) -> Result, Str Ok(processed_hints) } -/// Processes an ADD_TWIST_BLS12_381 hint. +/// Processes an `ADD_TWIST_BLS12_381` hint. #[inline] -pub fn add_twist_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn add_twist_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![P1: 24, P2: 24]; validate_hint_length(data, EXPECTED_LEN, "ADD_TWIST_BLS12_381")?; @@ -180,9 +182,9 @@ pub fn add_twist_bls12_381_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a SCALAR_MUL_TWIST_BLS12_381 hint. +/// Processes a `SCALAR_MUL_TWIST_BLS12_381` hint. #[inline] -pub fn scalar_mul_twist_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn scalar_mul_twist_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![P: 24, K: 6]; validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_TWIST_BLS12_381")?; @@ -198,9 +200,9 @@ pub fn scalar_mul_twist_bls12_381_hint(data: &[u64]) -> Result, String> Ok(processed_hints) } -/// Processes a MILLER_LOOP_BLS12_381 hint. +/// Processes a `MILLER_LOOP_BLS12_381` hint. #[inline] -pub fn miller_loop_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn miller_loop_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![P: 12, Q: 24]; validate_hint_length(data, EXPECTED_LEN, "MILLER_LOOP_BLS12_381")?; @@ -216,9 +218,9 @@ pub fn miller_loop_bls12_381_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a FINAL_EXP_BLS12_381 hint. +/// Processes a `FINAL_EXP_BLS12_381` hint. #[inline] -pub fn final_exp_bls12_381_hint(data: &[u64]) -> Result, String> { +pub fn final_exp_bls12_381_hint(data: &[u64]) -> Result> { hint_fields![F: 72]; validate_hint_length(data, EXPECTED_LEN, "FINAL_EXP_BLS12_381")?; diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 4e8884c81..a77d34564 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -1,8 +1,10 @@ use crate::{handlers::validate_hint_length, hint_fields, zisklib}; -/// Processes an IS_ON_CURVE_BN254 hint. +use anyhow::Result; + +/// Processes an `IS_ON_CURVE_BN254`` hint. #[inline] -pub fn is_on_curve_bn254_hint(data: &[u64]) -> Result, String> { +pub fn is_on_curve_bn254_hint(data: &[u64]) -> Result> { hint_fields![P: 8]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_BN254")?; @@ -19,9 +21,9 @@ pub fn is_on_curve_bn254_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a TO_AFFINE_BN254 hint. +/// Processes a `TO_AFFINE_BN254` hint. #[inline] -pub fn to_affine_bn254_hint(data: &[u64]) -> Result, String> { +pub fn to_affine_bn254_hint(data: &[u64]) -> Result> { hint_fields![P: 12]; validate_hint_length(data, EXPECTED_LEN, "TO_AFFINE_BN254")?; @@ -38,9 +40,9 @@ pub fn to_affine_bn254_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes an ADD_BN254 hint. +/// Processes an `ADD_BN254` hint. #[inline] -pub fn add_bn254_hint(data: &[u64]) -> Result, String> { +pub fn add_bn254_hint(data: &[u64]) -> Result> { hint_fields![P1: 8, P2: 8]; validate_hint_length(data, EXPECTED_LEN, "ADD_BN254")?; @@ -58,9 +60,9 @@ pub fn add_bn254_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a MUL_BN254 hint. +/// Processes a `MUL_BN254` hint. #[inline] -pub fn mul_bn254_hint(data: &[u64]) -> Result, String> { +pub fn mul_bn254_hint(data: &[u64]) -> Result> { hint_fields![P: 8, K: 4]; validate_hint_length(data, EXPECTED_LEN, "MUL_BN254")?; @@ -78,9 +80,9 @@ pub fn mul_bn254_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes a TO_AFFINE_TWIST_BN254 hint. +/// Processes a `TO_AFFINE_TWIST_BN254` hint. #[inline] -pub fn to_affine_twist_bn254_hint(data: &[u64]) -> Result, String> { +pub fn to_affine_twist_bn254_hint(data: &[u64]) -> Result> { hint_fields![P: 24]; validate_hint_length(data, EXPECTED_LEN, "TO_AFFINE_TWIST_BN254")?; @@ -97,9 +99,9 @@ pub fn to_affine_twist_bn254_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes an IS_ON_CURVE_TWIST_BN254 hint. +/// Processes an `IS_ON_CURVE_TWIST_BN254` hint. #[inline] -pub fn is_on_curve_twist_bn254_hint(data: &[u64]) -> Result, String> { +pub fn is_on_curve_twist_bn254_hint(data: &[u64]) -> Result> { hint_fields![P: 16]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_TWIST_BN254")?; @@ -116,9 +118,9 @@ pub fn is_on_curve_twist_bn254_hint(data: &[u64]) -> Result, String> { Ok(processed_hints) } -/// Processes an IS_ON_SUBGROUP_TWIST_BN254 hint. +/// Processes an `IS_ON_SUBGROUP_TWIST_BN254` hint. #[inline] -pub fn is_on_subgroup_twist_bn254_hint(data: &[u64]) -> Result, String> { +pub fn is_on_subgroup_twist_bn254_hint(data: &[u64]) -> Result> { hint_fields![P: 16]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_TWIST_BN254")?; @@ -135,13 +137,13 @@ pub fn is_on_subgroup_twist_bn254_hint(data: &[u64]) -> Result, String> Ok(processed_hints) } -/// Processes a PAIRING_BATCH_BN254 hint. +/// Processes a `PAIRING_BATCH_BN254` hint. /// Format: [num_points:u64][g1_points:&[u64]][g2_points:&[u64]] /// where g1_points has length num_points * 8 and g2_points has length num_points * 16 #[inline] -pub fn pairing_batch_bn254_hint(data: &[u64]) -> Result, String> { +pub fn pairing_batch_bn254_hint(data: &[u64]) -> Result> { if data.is_empty() { - return Err("PAIRING_BATCH_BN254: data is empty".to_string()); + anyhow::bail!("PAIRING_BATCH_BN254: data is empty"); } let num_points = data[0] as usize; diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 150bc624c..a98f60539 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -1,14 +1,8 @@ -mod bigint256; -mod bls318; -mod bn254; -mod modexp; -mod secp256k1; - -pub use bigint256::*; -pub use bls318::*; -pub use bn254::*; -pub use modexp::*; -pub use secp256k1::*; +pub mod bigint256; +pub mod bls318; +pub mod bn254; +pub mod modexp; +pub mod secp256k1; /// Macro to generate size, offset, and expected length constants for hint data fields. /// @@ -54,6 +48,20 @@ macro_rules! hint_fields { }; } +/// Read a length-prefixed field from hint data +#[inline] +fn read_field<'a>(data: &'a [u64], pos: &mut usize) -> anyhow::Result<&'a [u64]> { + let len = + *data.get(*pos).ok_or("MODEXP hint data too short").map_err(anyhow::Error::msg)? as usize; + *pos += 1; + let field = data + .get(*pos..*pos + len) + .ok_or("MODEXP hint data too short") + .map_err(anyhow::Error::msg)?; + *pos += len; + Ok(field) +} + /// Validates that the hint data has the expected length. /// /// # Arguments @@ -65,16 +73,16 @@ macro_rules! hint_fields { /// # Returns /// /// * `Ok(())` - If the length is correct -/// * `Err(String)` - If the length is incorrect +/// * `Err(anyhow::Error)` - If the length is incorrect #[inline] -fn validate_hint_length(data: &[u64], expected_len: usize, hint_name: &str) -> Result<(), String> { +fn validate_hint_length(data: &[u64], expected_len: usize, hint_name: &str) -> anyhow::Result<()> { if data.len() != expected_len { - return Err(format!( + anyhow::bail!( "Invalid {} hint length: expected {}, got {}", hint_name, expected_len, - data.len() - )); + data.len(), + ); } Ok(()) } diff --git a/ziskos-hints/src/handlers/modexp.rs b/ziskos-hints/src/handlers/modexp.rs index e713b5f75..502ade297 100644 --- a/ziskos-hints/src/handlers/modexp.rs +++ b/ziskos-hints/src/handlers/modexp.rs @@ -1,18 +1,13 @@ -use crate::{handlers::validate_hint_length, zisklib}; +use crate::{ + handlers::{read_field, validate_hint_length}, + zisklib, +}; -/// Read a length-prefixed field from hint data -#[inline] -fn read_field<'a>(data: &'a [u64], pos: &mut usize) -> Result<&'a [u64], String> { - let len = *data.get(*pos).ok_or("MODEXP hint data too short")? as usize; - *pos += 1; - let field = data.get(*pos..*pos + len).ok_or("MODEXP hint data too short")?; - *pos += len; - Ok(field) -} +use anyhow::Result; -// Processes a MODEXP hint. +// Processes a `MODEXP` hint. #[inline] -pub fn modexp_hint(data: &[u64]) -> Result, String> { +pub fn modexp_hint(data: &[u64]) -> Result> { let mut pos = 0; let base = read_field(data, &mut pos)?; let exp = read_field(data, &mut pos)?; diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index ec0d07b7d..456ffef0c 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -2,9 +2,11 @@ use crate::handlers::validate_hint_length; use crate::hint_fields; use crate::zisklib; -// Processes a SECP256K1_FN_REDUCE hint. +use anyhow::Result; + +// Processes a `SECP256K1_FN_REDUCE`` hint. #[inline] -pub fn secp256k1_fn_reduce_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_fn_reduce_hint(data: &[u64]) -> Result> { hint_fields![X: 4]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_REDUCE")?; @@ -17,9 +19,9 @@ pub fn secp256k1_fn_reduce_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_FN_ADD hint. +// Processes a `SECP256K1_FN_ADD` hint. #[inline] -pub fn secp256k1_fn_add_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_fn_add_hint(data: &[u64]) -> Result> { hint_fields![X: 4, Y: 4]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_ADD")?; @@ -33,9 +35,9 @@ pub fn secp256k1_fn_add_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_FN_NEG hint. +// Processes a `SECP256K1_FN_NEG`` hint. #[inline] -pub fn secp256k1_fn_neg_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_fn_neg_hint(data: &[u64]) -> Result> { hint_fields![X: 4]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_NEG")?; @@ -48,9 +50,9 @@ pub fn secp256k1_fn_neg_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_FN_SUB hint. +// Processes a `SECP256K1_FN_SUB`` hint. #[inline] -pub fn secp256k1_fn_sub_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_fn_sub_hint(data: &[u64]) -> Result> { hint_fields![X: 4, Y: 4]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_SUB")?; @@ -64,9 +66,9 @@ pub fn secp256k1_fn_sub_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_FN_MUL hint. +// Processes a `SECP256K1_FN_MUL`` hint. #[inline] -pub fn secp256k1_fn_mul_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_fn_mul_hint(data: &[u64]) -> Result> { hint_fields![X: 4, Y: 4]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_MUL")?; @@ -80,9 +82,9 @@ pub fn secp256k1_fn_mul_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_FN_INV hint. +// Processes a `SECP256K1_FN_INV`` hint. #[inline] -pub fn secp256k1_fn_inv_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_fn_inv_hint(data: &[u64]) -> Result> { hint_fields![X: 4]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_INV")?; @@ -95,9 +97,9 @@ pub fn secp256k1_fn_inv_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_FP_REDUCE hint. +// Processes a `SECP256K1_FP_REDUCE`` hint. #[inline] -pub fn secp256k1_fp_reduce_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_fp_reduce_hint(data: &[u64]) -> Result> { hint_fields![X: 4]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_REDUCE")?; @@ -110,9 +112,9 @@ pub fn secp256k1_fp_reduce_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_FP_ADD hint. +// Processes a `SECP256K1_FP_ADD` hint. #[inline] -pub fn secp256k1_fp_add_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_fp_add_hint(data: &[u64]) -> Result> { hint_fields![X: 4, Y: 4]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_ADD")?; @@ -126,9 +128,9 @@ pub fn secp256k1_fp_add_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_FP_NEGATE hint. +// Processes a `SECP256K1_FP_NEGATE` hint. #[inline] -pub fn secp256k1_fp_negate_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_fp_negate_hint(data: &[u64]) -> Result> { hint_fields![X: 4]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_NEGATE")?; @@ -141,9 +143,9 @@ pub fn secp256k1_fp_negate_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_FP_MUL hint. +// Processes a `SECP256K1_FP_MUL`` hint. #[inline] -pub fn secp256k1_fp_mul_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_fp_mul_hint(data: &[u64]) -> Result> { hint_fields![X: 4, Y: 4]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_MUL")?; @@ -157,9 +159,9 @@ pub fn secp256k1_fp_mul_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_FP_MUL_SCALAR hint. +// Processes a `SECP256K1_FP_MUL_SCALAR` hint. #[inline] -pub fn secp256k1_fp_mul_scalar_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_fp_mul_scalar_hint(data: &[u64]) -> Result> { hint_fields![X: 4, SCALAR: 1]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_MUL_SCALAR")?; @@ -173,9 +175,9 @@ pub fn secp256k1_fp_mul_scalar_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_TO_AFFINE hint. +// Processes a `SECP256K1_TO_AFFINE` hint. #[inline] -pub fn secp256k1_to_affine_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_to_affine_hint(data: &[u64]) -> Result> { hint_fields![P: 12]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_TO_AFFINE")?; @@ -188,9 +190,9 @@ pub fn secp256k1_to_affine_hint(data: &[u64]) -> Result, String> { Ok(hints) } -// Processes a SECP256K1_DECOMPRESS hint. +// Processes a `SECP256K1_DECOMPRESS` hint. #[inline] -pub fn secp256k1_decompress_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_decompress_hint(data: &[u64]) -> Result> { hint_fields![X_BYTES: 4, Y_IS_ODD: 1]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DECOMPRESS")?; @@ -200,15 +202,14 @@ pub fn secp256k1_decompress_hint(data: &[u64]) -> Result, String> { let y_is_odd = (data[Y_IS_ODD_OFFSET] >> 56) != 0; let mut hints = Vec::new(); - zisklib::secp256k1_decompress(x, y_is_odd, &mut hints) - .map_err(|e| format!("secp256k1_decompress failed: {}", e))?; + zisklib::secp256k1_decompress(x, y_is_odd, &mut hints).map_err(anyhow::Error::msg)?; Ok(hints) } -// Processes a SECP256K1_DOUBLE_SCALAR_MUL_WITH_G hint. +// Processes a `SECP256K1_DOUBLE_SCALAR_MUL_WITH_G` hint. #[inline] -pub fn secp256k1_double_scalar_mul_with_g_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_double_scalar_mul_with_g_hint(data: &[u64]) -> Result> { hint_fields![K1: 4, K2: 4, P: 8]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DOUBLE_SCALAR_MUL_WITH_G")?; @@ -223,9 +224,9 @@ pub fn secp256k1_double_scalar_mul_with_g_hint(data: &[u64]) -> Result, Ok(hints) } -/// Processes an ECDSA_VERIFY hint. +/// Processes an `SECP256K1_ECDSA_VERIFY` hint. #[inline] -pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result, String> { +pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result> { hint_fields![PK: 8, Z: 4, R: 4, S: 4]; validate_hint_length(data, EXPECTED_LEN, "SECP256K1_ECDSA_VERIFY")?; From 57497155fca7ad430f068340a5b68ed5001bc8fb Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sun, 18 Jan 2026 08:01:00 +0000 Subject: [PATCH 252/782] fix filename typo --- precompiles/hints/src/hints_processor.rs | 2 +- ziskos-hints/src/handlers/{bls318.rs => bls381.rs} | 0 ziskos-hints/src/handlers/mod.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename ziskos-hints/src/handlers/{bls318.rs => bls381.rs} (100%) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index aa602f365..ed6ba8401 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -13,7 +13,7 @@ use std::sync::{Arc, Condvar, Mutex}; use tracing::debug; use zisk_common::io::{StreamProcessor, StreamSink}; use zisk_common::{BuiltInHint, CtrlHint, HintCode, PrecompileHint}; -use ziskos_hints::handlers::{bigint256::*, bls318::*, bn254::*, modexp::*, secp256k1::*}; +use ziskos_hints::handlers::{bigint256::*, bls381::*, bn254::*, modexp::*, secp256k1::*}; /// Ordered result buffer with drain state. /// diff --git a/ziskos-hints/src/handlers/bls318.rs b/ziskos-hints/src/handlers/bls381.rs similarity index 100% rename from ziskos-hints/src/handlers/bls318.rs rename to ziskos-hints/src/handlers/bls381.rs diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index a98f60539..50ec992f5 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -1,5 +1,5 @@ pub mod bigint256; -pub mod bls318; +pub mod bls381; pub mod bn254; pub mod modexp; pub mod secp256k1; From 8f435c103b0bcc11097f68dd8405c7f77d0fc706 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 07:21:03 +0000 Subject: [PATCH 253/782] preparing merge --- Cargo.lock | 29 +- Cargo.toml | 2 - cli/Cargo.toml | 1 - cli/src/bin/cargo-zisk.rs | 13 +- cli/src/commands/mod.rs | 4 - cli/src/commands/prove_client.rs | 179 ----- cli/src/commands/server.rs | 280 -------- common/src/zisk_lib_init.rs | 6 + emulator-asm/asm-runner/src/asm_mo_runner.rs | 18 +- emulator-asm/asm-runner/src/asm_mt_runner.rs | 20 +- emulator-asm/asm-runner/src/asm_rh.rs | 6 +- emulator-asm/asm-runner/src/asm_rh_runner.rs | 14 +- emulator-asm/asm-runner/src/hints_shmem.rs | 1 - emulator-asm/asm-runner/src/shmem_utils.rs | 46 +- executor/src/emu_asm.rs | 399 ++++++++++++ executor/src/emu_rust.rs | 202 ++++++ executor/src/executor.rs | 550 ++-------------- executor/src/lib.rs | 67 ++ executor/src/sm_static_bundle.rs | 3 +- server/Cargo.toml | 47 -- server/build.rs | 11 - server/src/handler_prove.rs | 178 ----- server/src/handler_shutdown.rs | 64 -- server/src/handler_status.rs | 62 -- server/src/handler_verify_constraints.rs | 114 ---- server/src/lib.rs | 11 - server/src/zisk_service.rs | 609 ------------------ witness-computation/Cargo.toml | 2 + witness-computation/src/zisk_lib.rs | 74 ++- .../src/zisklib/lib/secp256k1/curve.rs | 4 +- .../src/zisklib/lib/secp256k1/ecdsa.rs | 18 +- .../src/zisklib/lib/secp256k1/field.rs | 2 + .../src/zisklib/lib/secp256k1/scalar.rs | 2 + 33 files changed, 846 insertions(+), 2192 deletions(-) delete mode 100644 cli/src/commands/prove_client.rs delete mode 100644 cli/src/commands/server.rs create mode 100644 executor/src/emu_asm.rs create mode 100644 executor/src/emu_rust.rs delete mode 100644 server/Cargo.toml delete mode 100644 server/build.rs delete mode 100644 server/src/handler_prove.rs delete mode 100644 server/src/handler_shutdown.rs delete mode 100644 server/src/handler_status.rs delete mode 100644 server/src/handler_verify_constraints.rs delete mode 100644 server/src/lib.rs delete mode 100644 server/src/zisk_service.rs diff --git a/Cargo.lock b/Cargo.lock index 774cabf5d..7a26e8ef6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -632,7 +632,6 @@ dependencies = [ "rom-setup", "serde", "serde_json", - "server", "sysinfo 0.37.2", "target-lexicon", "tokio", @@ -4089,32 +4088,6 @@ dependencies = [ "serde", ] -[[package]] -name = "server" -version = "0.16.0" -dependencies = [ - "anyhow", - "asm-runner", - "bytemuck", - "clap", - "colored", - "executor", - "fields", - "libloading", - "mpi", - "named-sem", - "proofman", - "proofman-common", - "serde", - "serde_json", - "tracing", - "uuid", - "witness", - "zisk-common", - "zisk-witness", - "zstd", -] - [[package]] name = "sha2" version = "0.10.9" @@ -6134,6 +6107,7 @@ name = "zisk-witness" version = "0.16.0" dependencies = [ "anyhow", + "asm-runner", "data-bus", "env_logger", "executor", @@ -6145,6 +6119,7 @@ dependencies = [ "precomp-big-int", "precomp-keccakf", "precomp-sha256f", + "precompiles-hints", "proofman", "proofman-common", "proofman-macros", diff --git a/Cargo.toml b/Cargo.toml index f8fb44605..d251f0c6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,6 @@ members = [ "ziskclib", "common", "tools/circuit", - "server", "distributed/crates/coordinator", "distributed/crates/grpc-api", "distributed/crates/common", @@ -82,7 +81,6 @@ precomp-sha256f = { path = "precompiles/sha256f" } precomp-big-int = { path = "precompiles/big_int" } riscv = { path = "riscv" } rom-setup = { path = "rom-setup" } -server = { path = "server" } sm-arith = { path = "state-machines/arith" } sm-binary = { path = "state-machines/binary" } sm-main = { path = "state-machines/main" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9ae695612..14bc77ad5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -26,7 +26,6 @@ rom-setup = { workspace = true } zisk-core = { workspace = true } zisk-pil = { workspace = true } asm-runner = { workspace = true } -server = { workspace = true } colored = { workspace = true } fields = { workspace = true } diff --git a/cli/src/bin/cargo-zisk.rs b/cli/src/bin/cargo-zisk.rs index 4dfae0c92..22bc5fef8 100644 --- a/cli/src/bin/cargo-zisk.rs +++ b/cli/src/bin/cargo-zisk.rs @@ -1,8 +1,7 @@ use anyhow::{anyhow, Context, Result}; use cargo_zisk::commands::{ - ZiskBuild, ZiskCheckSetup, ZiskClean, ZiskExecute, ZiskProve, ZiskProveClient, ZiskProveSnark, - ZiskRomSetup, ZiskRomVkey, ZiskRun, ZiskSdk, ZiskServer, ZiskStats, ZiskVerify, - ZiskVerifyConstraints, + ZiskBuild, ZiskCheckSetup, ZiskClean, ZiskExecute, ZiskProve, ZiskProveSnark, ZiskRomSetup, + ZiskRomVkey, ZiskRun, ZiskSdk, ZiskStats, ZiskVerify, ZiskVerifyConstraints, }; use clap::Parser; use zisk_build::ZISK_VERSION_MESSAGE; @@ -21,14 +20,12 @@ pub enum Cargo { CheckSetup(ZiskCheckSetup), Clean(ZiskClean), Execute(ZiskExecute), - ProveClient(ZiskProveClient), Prove(ZiskProve), ProveSnark(ZiskProveSnark), RomSetup(ZiskRomSetup), RomVkey(ZiskRomVkey), Run(ZiskRun), Sdk(ZiskSdk), - Server(ZiskServer), Stats(ZiskStats), Verify(ZiskVerify), VerifyConstraints(ZiskVerifyConstraints), @@ -48,9 +45,6 @@ fn main() -> Result<()> { Cargo::Clean(cmd) => { cmd.run().context("Error executing Clean command")?; } - Cargo::ProveClient(cmd) => { - cmd.run().context("Error executing ProveClient command")?; - } Cargo::Prove(mut cmd) => { cmd.run().context("Error executing Prove command")?; } @@ -75,9 +69,6 @@ fn main() -> Result<()> { Cargo::Sdk(cmd) => { cmd.command.run().context("Error executing SDK command")?; } - Cargo::Server(mut cmd) => { - cmd.run().context("Error executing Server command")?; - } Cargo::Verify(cmd) => { cmd.run().map_err(|e| anyhow!("Error executing Verify command: {}", e))?; } diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 7d01f4462..ace972dcc 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -4,13 +4,11 @@ mod clean; mod common; mod execute; mod prove; -mod prove_client; mod prove_snark; mod rom_setup; mod rom_vkey; mod run; mod sdk; -mod server; mod stats; mod verify_constraints; mod verify_stark; @@ -21,13 +19,11 @@ pub use clean::*; pub use common::*; pub use execute::*; pub use prove::*; -pub use prove_client::*; pub use prove_snark::*; pub use rom_setup::*; pub use rom_vkey::*; pub use run::*; pub use sdk::*; -pub use server::*; pub use stats::*; pub use verify_constraints::*; pub use verify_stark::*; diff --git a/cli/src/commands/prove_client.rs b/cli/src/commands/prove_client.rs deleted file mode 100644 index a6cb478f2..000000000 --- a/cli/src/commands/prove_client.rs +++ /dev/null @@ -1,179 +0,0 @@ -use anyhow::Result; -use clap::{Parser, Subcommand}; -use server::{ - ZiskProveRequest, ZiskRequest, ZiskResponse, ZiskShutdownRequest, ZiskStatusRequest, - ZiskVerifyConstraintsRequest, -}; -use std::{ - io::{BufRead, BufReader, Write}, - net::TcpStream, - path::PathBuf, -}; - -use crate::commands::DEFAULT_PORT; - -use colored::Colorize; - -#[derive(Parser)] -#[command(name = "Zisk Prover Client", version, about = "Send commands to the prover server")] -pub struct ZiskProveClient { - #[command(subcommand)] - pub command: ClientCommand, -} - -#[derive(Subcommand, Debug)] -#[command(rename_all = "snake_case")] -pub enum ClientCommand { - /// Get server status - Status { - /// Port of the server (by default DEFAULT_PORT) - #[clap(long)] - port: Option, - }, - - /// Shut down the server - Shutdown { - /// Port of the server (by default DEFAULT_PORT) - #[clap(long)] - port: Option, - }, - - Prove { - /// Path to the input file - #[arg(short, long)] - input: PathBuf, - - /// Use aggregation - #[clap(short = 'a', long, default_value_t = false)] - aggregation: bool, - - #[clap(short = 'r', long, default_value_t = false)] - rma: bool, - - /// Use final snark - #[clap(short = 'f', long, default_value_t = false)] - final_snark: bool, - - /// Verify proofs - #[clap(short = 'y', long, default_value_t = false)] - verify_proofs: bool, - - /// Output folder for the proof - #[clap(short = 'o', long, default_value = "tmp")] - output_dir: PathBuf, - - #[clap(short = 'p')] - prefix: String, - - /// Use minimal memory - #[clap(long, default_value_t = false)] - minimal_memory: bool, - - /// Port of the server (by default DEFAULT_PORT) - #[clap(long)] - port: Option, - - /// Verbosity (-v, -vv) - #[arg(short ='v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] - verbose: u8, // Using u8 to hold the number of `-v` - }, - /// Verify constraints from input file - VerifyConstraints { - /// Path to the input file - #[arg(short, long)] - input: PathBuf, - - /// Port of the server (by default DEFAULT_PORT) - #[clap(long)] - port: Option, - - /// Verbosity (-v, -vv) - #[arg(short ='v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] - verbose: u8, // Using u8 to hold the number of `-v` - }, -} - -impl ZiskProveClient { - pub fn run(&self) -> Result<()> { - let request = match &self.command { - ClientCommand::Status { port: _ } => { - ZiskRequest::Status { payload: ZiskStatusRequest {} } - } - ClientCommand::Shutdown { port: _ } => { - ZiskRequest::Shutdown { payload: ZiskShutdownRequest {} } - } - ClientCommand::Prove { - input, - aggregation, - rma, - final_snark, - verify_proofs, - minimal_memory, - output_dir, - prefix, - verbose: _, - port: _, - } => ZiskRequest::Prove { - payload: ZiskProveRequest { - input: input.clone(), - aggregation: *aggregation, - rma: *rma, - final_snark: *final_snark, - verify_proofs: *verify_proofs, - minimal_memory: *minimal_memory, - folder: output_dir.clone(), - prefix: prefix.clone(), - }, - }, - ClientCommand::VerifyConstraints { input, verbose: _, port: _ } => { - ZiskRequest::VerifyConstraints { - payload: ZiskVerifyConstraintsRequest { input: input.clone() }, - } - } - }; - - // Determine the port to use for this client instance. - // - If no port is specified, default to DEFAULT_PORT. - // - If a port is specified, use it as the base port. - // In both cases, the local MPI rank is added to the port to avoid conflicts - // when running multiple processes on the same machine. - let port = match self.command { - ClientCommand::Prove { port, .. } - | ClientCommand::VerifyConstraints { port, .. } - | ClientCommand::Status { port } - | ClientCommand::Shutdown { port } => port.unwrap_or(DEFAULT_PORT), - }; - - // TODO: FIX! - // port += mpi_context.node_rank as u16; - - let address = format!("localhost:{port}"); - - // Open connection - let mut stream = TcpStream::connect(&address) - .map_err(|e| anyhow::anyhow!("Failed to connect to server: {}", e))?; - - // Serialize and send request - let mut request_json = serde_json::to_string(&request)?; - request_json.push('\n'); - stream.write_all(request_json.as_bytes())?; - - // Read and parse response - let mut reader = BufReader::new(stream); - let mut response_line = String::new(); - reader.read_line(&mut response_line)?; - - if let Err(e) = serde_json::from_str::(&response_line) { - return Err(anyhow::anyhow!( - "Failed to parse server response: {}\nRaw: {}", - e, - response_line - )); - } - - println!(); - println!("{} {}", format!("{: >12}", "Response").bright_green().bold(), response_line); - - Ok(()) - } -} diff --git a/cli/src/commands/server.rs b/cli/src/commands/server.rs deleted file mode 100644 index cd3e80ebe..000000000 --- a/cli/src/commands/server.rs +++ /dev/null @@ -1,280 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use colored::Colorize; -use proofman_common::{json_to_debug_instances_map, DebugInfo, ParamsGPU}; -use rom_setup::{ - gen_elf_hash, get_elf_bin_file_path, get_elf_data_hash, get_rom_blowup_factor_and_arity, - DEFAULT_CACHE_PATH, -}; -use server::ZiskServerParams; -use server::ZiskService; -use std::collections::HashMap; -use std::fs; -use std::{path::PathBuf, process}; -use zisk_common::init_tracing; - -use crate::commands::{get_proving_key, get_witness_computation_lib}; -use crate::ux::print_banner; -use zisk_build::ZISK_VERSION_MESSAGE; - -pub const DEFAULT_PORT: u16 = 7878; -const LOG_PATH: &str = "zisk_prover_server.log"; - -// Structure representing the 'prove' subcommand of cargo. -#[derive(Parser, Debug)] -#[command(name = "Prover Server", version, about = "A TCP-based prover control server", long_about = None, version = ZISK_VERSION_MESSAGE)] -#[command(propagate_version = true)] -#[command(group( - clap::ArgGroup::new("input_mode") - .args(["asm", "emulator"]) - .multiple(false) - .required(false) -))] -pub struct ZiskServer { - /// Optional port number (default 7878) - #[arg(short, long, default_value_t = DEFAULT_PORT)] - port: u16, - - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - - /// ELF file path - /// This is the path to the ROM file that the witness computation dynamic library will use - /// to generate the witness. - #[clap(short = 'e', long)] - pub elf: PathBuf, - - /// ASM file path - /// Optional, mutually exclusive with `--emulator` - #[clap(short = 's', long)] - pub asm: Option, - - /// Use prebuilt emulator (mutually exclusive with `--asm`) - #[clap(short = 'l', long, action = clap::ArgAction::SetTrue)] - pub emulator: bool, - - /// Setup folder path - #[clap(short = 'k', long)] - pub proving_key: Option, - - /// Base port for Assembly microservices (default: 23115). - /// A single execution will use 3 consecutive ports, from this port to port + 2. - /// If you are running multiple instances of ZisK using mpi on the same machine, - /// it will use from this base port to base port + 2 * number_of_instances. - /// For example, if you run 2 mpi instances of ZisK, it will use ports from 23115 to 23117 - /// for the first instance, and from 23118 to 23120 for the second instance. - #[clap(long, conflicts_with = "emulator")] - pub asm_port: Option, - - /// Map unlocked flag - /// This is used to unlock the memory map for the ROM file. - /// If you are running ZisK on a machine with limited memory, you may want to enable this option. - /// This option is mutually exclusive with `--emulator`. - #[clap(short = 'u', long, conflicts_with = "emulator")] - pub unlock_mapped_memory: bool, - - /// Verbosity (-v, -vv) - #[arg(short ='v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] - pub verbose: u8, // Using u8 to hold the number of `-v` - - #[clap(short = 'd', long)] - pub debug: Option>, - - #[clap(short = 'c', long, default_value_t = false)] - pub verify_constraints: bool, - - #[clap(short = 'a', long, default_value_t = false)] - pub aggregation: bool, - - #[clap(short = 'f', long, default_value_t = false)] - pub final_snark: bool, - - #[clap(short = 'r', long, default_value_t = false)] - pub rma: bool, - - /// GPU PARAMS - #[clap(short = 'z', long, default_value_t = false)] - pub preallocate: bool, - - #[clap(short = 't', long)] - pub max_streams: Option, - - #[clap(short = 'n', long)] - pub number_threads_witness: Option, - - #[clap(short = 'x', long)] - pub max_witness_stored: Option, - - #[clap(short = 'j', long, default_value_t = false)] - pub shared_tables: bool, -} - -impl ZiskServer { - pub fn run(&mut self) -> Result<()> { - init_tracing(LOG_PATH); - - print_banner(); - - if !self.elf.exists() { - eprintln!("Error: ELF file '{}' not found.", self.elf.display()); - process::exit(1); - } - - let proving_key = get_proving_key(self.proving_key.as_ref()); - - let debug_info = match &self.debug { - None => DebugInfo::default(), - Some(None) => DebugInfo::new_debug(), - Some(Some(debug_value)) => { - json_to_debug_instances_map(proving_key.clone(), debug_value.clone())? - } - }; - - let default_cache_path = - std::env::var("HOME").ok().map(PathBuf::from).unwrap().join(DEFAULT_CACHE_PATH); - - if !default_cache_path.exists() { - if let Err(e) = fs::create_dir_all(default_cache_path.clone()) { - if e.kind() != std::io::ErrorKind::AlreadyExists { - // prevent collision in distributed mode - panic!("Failed to create the cache directory: {e:?}"); - } - } - } - - let emulator = if cfg!(target_os = "macos") { true } else { self.emulator }; - - let mut asm_rom = None; - if emulator { - self.asm = None; - } else if self.asm.is_none() { - let stem = self.elf.file_stem().unwrap().to_str().unwrap(); - let hash = get_elf_data_hash(&self.elf) - .map_err(|e| anyhow::anyhow!("Error computing ELF hash: {}", e))?; - let new_filename = format!("{stem}-{hash}-mt.bin"); - let asm_rom_filename = format!("{stem}-{hash}-rh.bin"); - asm_rom = Some(default_cache_path.join(asm_rom_filename)); - self.asm = Some(default_cache_path.join(new_filename)); - } - - if let Some(asm_path) = &self.asm { - if !asm_path.exists() { - return Err(anyhow::anyhow!("ASM file not found at {:?}", asm_path.display())); - } - } - - if let Some(asm_rom) = &asm_rom { - if !asm_rom.exists() { - return Err(anyhow::anyhow!("ASM file not found at {:?}", asm_rom.display())); - } - } - - let (blowup_factor, merkle_tree_arity) = get_rom_blowup_factor_and_arity(&proving_key); - - let rom_bin_path = get_elf_bin_file_path( - &self.elf.to_path_buf(), - &default_cache_path, - blowup_factor, - merkle_tree_arity, - )?; - - if !rom_bin_path.exists() { - let _ = gen_elf_hash( - &self.elf.clone(), - rom_bin_path.as_path(), - blowup_factor, - merkle_tree_arity, - false, - ) - .map_err(|e| anyhow::anyhow!("Error generating elf hash: {}", e)); - } - - self.print_command_info(); - let mut custom_commits_map: HashMap = HashMap::new(); - custom_commits_map.insert("rom".to_string(), rom_bin_path); - - let mut gpu_params = ParamsGPU::new(self.preallocate); - - if self.max_streams.is_some() { - gpu_params.with_max_number_streams(self.max_streams.unwrap()); - } - if self.number_threads_witness.is_some() { - gpu_params.with_number_threads_pools_witness(self.number_threads_witness.unwrap()); - } - if self.max_witness_stored.is_some() { - gpu_params.with_max_witness_stored(self.max_witness_stored.unwrap()); - } - - let server_params = ZiskServerParams::new( - self.port, - self.elf.clone(), - get_witness_computation_lib(self.witness_lib.as_ref()), - self.asm.clone(), - asm_rom, - self.asm_port, - custom_commits_map, - emulator, - proving_key, - self.verbose, - debug_info, - self.verify_constraints, - self.aggregation, - self.final_snark, - gpu_params, - self.unlock_mapped_memory, - self.shared_tables, - ); - - if let Err(e) = ZiskService::new(&server_params)?.run() { - eprintln!("Error starting server: {e}"); - process::exit(1); - } - - Ok(()) - } - - fn print_command_info(&self) { - println!("{} Prove Server", format!("{: >12}", "Command").bright_green().bold()); - println!( - "{} TCP server listening on 127.0.0.1:{}", - format!("{: >12}", "Socket").bright_green().bold(), - self.port - ); - println!("{} {}", format!("{: >12}", "Logfile").bright_green().bold(), LOG_PATH); - println!( - "{: >12} {}", - "Witness Lib".bright_green().bold(), - get_witness_computation_lib(self.witness_lib.as_ref()).display() - ); - - println!("{: >12} {}", "ELF".bright_green().bold(), self.elf.display()); - - if self.asm.is_some() { - let asm_path = self.asm.as_ref().unwrap().display(); - println!("{: >12} {}", "ASM runner".bright_green().bold(), asm_path); - } else { - println!( - "{: >12} {}", - "Emulator".bright_green().bold(), - "Running in emulator mode".bright_yellow() - ); - } - - println!( - "{: >12} {}", - "Proving Key".bright_green().bold(), - get_proving_key(self.proving_key.as_ref()).display() - ); - - let std_mode = match &self.debug { - None => "Standard mode", - Some(None) => "Debug mode (fast)", - Some(Some(json_file)) => &format!("Debug mode (from config file: {})", json_file), - }; - println!("{: >12} {}", "STD".bright_green().bold(), std_mode); - // println!("{}", format!("{: >12} {}", "Distributed".bright_green().bold(), "ON (nodes: 4, threads: 32)")); - - println!(); - } -} diff --git a/common/src/zisk_lib_init.rs b/common/src/zisk_lib_init.rs index 2ca46a2e1..96d7411c8 100644 --- a/common/src/zisk_lib_init.rs +++ b/common/src/zisk_lib_init.rs @@ -16,6 +16,12 @@ pub struct ZiskExecutionResult { pub steps: u64, } +impl ZiskExecutionResult { + pub fn new(steps: u64) -> Self { + Self { steps } + } +} + #[derive(Debug, Clone)] pub struct Stats { pub airgroup_id: usize, diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index ce7c147f9..c632e08ba 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -7,8 +7,6 @@ use std::ffi::c_void; use std::sync::atomic::{fence, Ordering}; use tracing::error; -use crate::sem_chunk_done_name; -use crate::shmem_output_name; use crate::SEM_CHUNK_DONE_WAIT_DURATION; use crate::{AsmMOChunk, AsmMOHeader, AsmRunError, AsmService, AsmServices, AsmSharedMemory}; use mem_planner_cpp::MemPlanner; @@ -22,7 +20,7 @@ use zisk_common::ExecutorStatsEvent; use mem_common::save_plans; pub struct PreloadedMO { - pub output_shmem: AsmSharedMemory, + pub output_shmem: AsmSharedMemory, mem_planner: Option, handle_mo: Option>, } @@ -39,10 +37,11 @@ impl PreloadedMO { AsmServices::default_port(&AsmService::MO, local_rank) }; - let output_name = shmem_output_name(port, AsmService::MO, local_rank); + let output_name = + AsmSharedMemory::::shmem_output_name(port, AsmService::MO, local_rank); let output_shared_memory = - AsmSharedMemory::open_and_map::(&output_name, unlock_mapped_memory)?; + AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; Ok(Self { output_shmem: output_shared_memory, @@ -99,7 +98,8 @@ impl AsmRunnerMO { AsmServices::default_port(&AsmService::MO, local_rank) }; - let sem_chunk_done_name = sem_chunk_done_name(port, AsmService::MO, local_rank); + let sem_chunk_done_name = + AsmSharedMemory::::shmem_chunk_done_name(port, AsmService::MO, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; @@ -128,7 +128,7 @@ impl AsmRunnerMO { .unwrap_or_else(|| preloaded.handle_mo.take().unwrap().join().unwrap()); // Get the pointer to the data in the shared memory. - let mut data_ptr = preloaded.output_shmem.data_ptr::() as *const AsmMOChunk; + let mut data_ptr = preloaded.output_shmem.data_ptr() as *const AsmMOChunk; // Initialize C++ memory operations trace mem_planner.execute(); @@ -162,7 +162,7 @@ impl AsmRunnerMO { if data_ptr >= threshold && preloaded .output_shmem - .check_size_changed::(&mut data_ptr) + .check_size_changed(&mut data_ptr) .context("Failed to check and remap shared memory for MO trace")? { threshold = unsafe { @@ -206,7 +206,7 @@ impl AsmRunnerMO { Err(e) => { error!("Semaphore '{}' error: {:?}", sem_chunk_done_name, e); - break preloaded.output_shmem.map_header::().exit_code; + break preloaded.output_shmem.map_header().exit_code; } } }; diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index d7a758191..74830e946 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -10,8 +10,8 @@ use std::time::Instant; use tracing::{error, info}; use crate::{ - sem_chunk_done_name, shmem_output_name, AsmMTChunk, AsmMTHeader, AsmRunError, AsmService, - AsmServices, AsmSharedMemory, SEM_CHUNK_DONE_WAIT_DURATION, + AsmMTChunk, AsmMTHeader, AsmRunError, AsmService, AsmServices, AsmSharedMemory, + SEM_CHUNK_DONE_WAIT_DURATION, }; use anyhow::{Context, Result}; @@ -33,7 +33,7 @@ pub enum MinimalTraces { } pub struct PreloadedMT { - pub output_shmem: AsmSharedMemory, + pub output_shmem: AsmSharedMemory, } impl PreloadedMT { @@ -48,10 +48,11 @@ impl PreloadedMT { AsmServices::default_port(&AsmService::MT, local_rank) }; - let output_name = shmem_output_name(port, AsmService::MT, local_rank); + let output_name = + AsmSharedMemory::::shmem_output_name(port, AsmService::MT, local_rank); let output_shared_memory = - AsmSharedMemory::open_and_map::(&output_name, unlock_mapped_memory)?; + AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; Ok(Self { output_shmem: output_shared_memory }) } @@ -91,7 +92,8 @@ impl AsmRunnerMT { AsmServices::default_port(&AsmService::MT, local_rank) }; - let sem_chunk_done_name = sem_chunk_done_name(port, AsmService::MT, local_rank); + let sem_chunk_done_name = + AsmSharedMemory::::shmem_chunk_done_name(port, AsmService::MT, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; @@ -117,7 +119,7 @@ impl AsmRunnerMT { let mut chunk_id = ChunkId(0); // Get the pointer to the data in the shared memory. - let mut data_ptr = preloaded.output_shmem.data_ptr::() as *const AsmMTChunk; + let mut data_ptr = preloaded.output_shmem.data_ptr() as *const AsmMTChunk; let mut emu_traces = Vec::new(); let mut handles = Vec::new(); @@ -154,7 +156,7 @@ impl AsmRunnerMT { if data_ptr >= threshold && preloaded .output_shmem - .check_size_changed::(&mut data_ptr) + .check_size_changed(&mut data_ptr) .context("Failed to check and remap shared memory for MO trace")? { threshold = unsafe { @@ -188,7 +190,7 @@ impl AsmRunnerMT { break 1; } - break preloaded.output_shmem.map_header::().exit_code; + break preloaded.output_shmem.map_header().exit_code; } } }; diff --git a/emulator-asm/asm-runner/src/asm_rh.rs b/emulator-asm/asm-runner/src/asm_rh.rs index 3f072de0d..97c38187d 100644 --- a/emulator-asm/asm-runner/src/asm_rh.rs +++ b/emulator-asm/asm-runner/src/asm_rh.rs @@ -36,9 +36,9 @@ impl AsmRHData { /// /// # Safety /// This function is unsafe because it reads from a raw pointer in shared memory. - pub fn from_shared_memory(asm_shared_memory: &AsmSharedMemory) -> AsmRHData { + pub fn from_shared_memory(asm_shared_memory: &AsmSharedMemory) -> AsmRHData { unsafe { - let data_ptr = asm_shared_memory.data_ptr::() as *mut u64; + let data_ptr = asm_shared_memory.data_ptr() as *mut u64; // BIOS chunk data let bios_data_ptr = data_ptr; let bios_len = std::ptr::read(bios_data_ptr) as usize; @@ -54,7 +54,7 @@ impl AsmRHData { let prog_inst_count = Vec::from_raw_parts(prog_data_ptr, prog_len, prog_len); AsmRHData { - steps: asm_shared_memory.map_header::().steps, + steps: asm_shared_memory.map_header().steps, bios_inst_count, prog_inst_count, } diff --git a/emulator-asm/asm-runner/src/asm_rh_runner.rs b/emulator-asm/asm-runner/src/asm_rh_runner.rs index 2b373ba79..4625b7a78 100644 --- a/emulator-asm/asm-runner/src/asm_rh_runner.rs +++ b/emulator-asm/asm-runner/src/asm_rh_runner.rs @@ -2,15 +2,15 @@ use tracing::error; use zisk_common::ExecutorStatsHandle; use crate::{ - sem_chunk_done_name, shmem_output_name, AsmRHData, AsmRHHeader, AsmRunError, AsmService, - AsmServices, AsmSharedMemory, SEM_CHUNK_DONE_WAIT_DURATION, + AsmRHData, AsmRHHeader, AsmRunError, AsmService, AsmServices, AsmSharedMemory, + SEM_CHUNK_DONE_WAIT_DURATION, }; use anyhow::{Context, Result}; use named_sem::NamedSemaphore; use std::sync::atomic::{fence, Ordering}; pub struct PreloadedRH { - pub output_shmem: AsmSharedMemory, + pub output_shmem: AsmSharedMemory, } impl PreloadedRH { @@ -25,10 +25,11 @@ impl PreloadedRH { AsmServices::default_port(&AsmService::RH, local_rank) }; - let output_name = shmem_output_name(port, AsmService::RH, local_rank); + let output_name = + AsmSharedMemory::::shmem_output_name(port, AsmService::RH, local_rank); let output_shared_memory = - AsmSharedMemory::open_and_map::(&output_name, unlock_mapped_memory)?; + AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; Ok(Self { output_shmem: output_shared_memory }) } @@ -76,7 +77,8 @@ impl AsmRunnerRH { AsmServices::default_port(&AsmService::RH, local_rank) }; - let sem_chunk_done_name = sem_chunk_done_name(port, AsmService::RH, local_rank); + let sem_chunk_done_name = + AsmSharedMemory::::shmem_chunk_done_name(port, AsmService::RH, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index 1b71ffe53..5d3215058 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -107,7 +107,6 @@ impl HintsShmem { unlock_mapped_memory: bool, ) -> Result> { debug!("Initializing resources for precompile hints"); - resources_names .iter() .map(|names: &ServiceResourceNames| -> Result { diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 4831fe957..84acfefbb 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -16,29 +16,30 @@ use zisk_common::io::{ZiskIO, ZiskStdin}; use anyhow::anyhow; use anyhow::Result; -use crate::{AsmInputC2, SharedMemoryWriter}; +use crate::{AsmInputC2, AsmService, AsmServices, SharedMemoryWriter}; pub enum AsmSharedMemoryMode { ReadOnly, ReadWrite, } -pub struct AsmSharedMemory { +pub struct AsmSharedMemory { _fd: i32, mapped_ptr: *mut c_void, mapped_size: usize, shmem_name: String, + _phantom: std::marker::PhantomData, } -unsafe impl Send for AsmSharedMemory {} -unsafe impl Sync for AsmSharedMemory {} +unsafe impl Send for AsmSharedMemory {} +unsafe impl Sync for AsmSharedMemory {} pub trait AsmShmemHeader: Debug { fn allocated_size(&self) -> u64; } #[cfg(all(target_os = "linux", target_arch = "x86_64"))] -impl Drop for AsmSharedMemory { +impl Drop for AsmSharedMemory { fn drop(&mut self) { self.unmap().unwrap_or_else(|err| { tracing::error!("Failed to unmap shared memory '{}': {}", self.shmem_name, err) @@ -47,11 +48,8 @@ impl Drop for AsmSharedMemory { } } -impl AsmSharedMemory { - pub fn open_and_map( - name: &str, - _unlock_mapped_memory: bool, - ) -> Result { +impl AsmSharedMemory { + pub fn open_and_map(name: &str, _unlock_mapped_memory: bool) -> Result { unsafe { if name.is_empty() { return Err(anyhow::anyhow!("Shared memory name {name} cannot be empty")); @@ -134,6 +132,7 @@ impl AsmSharedMemory { mapped_ptr, mapped_size: allocated_size, shmem_name: name.to_string(), + _phantom: std::marker::PhantomData::, }) } } @@ -210,11 +209,8 @@ impl AsmSharedMemory { } } - pub fn check_size_changed( - &mut self, - current_read_ptr: &mut *const T, - ) -> Result { - let read_mapped_size = self.map_header::().allocated_size(); + pub fn check_size_changed(&mut self, current_read_ptr: &mut *const T) -> Result { + let read_mapped_size = self.map_header().allocated_size(); if read_mapped_size == self.mapped_size as u64 { return Ok(false); @@ -251,7 +247,7 @@ impl AsmSharedMemory { Ok(()) } - pub fn map_header(&self) -> H { + pub fn map_header(&self) -> H { if !self.is_mapped() { panic!("Shared memory '{}' is not mapped, cannot read header", self.shmem_name); } @@ -271,10 +267,26 @@ impl AsmSharedMemory { self.mapped_ptr } - pub fn data_ptr(&self) -> *mut c_void { + pub fn data_ptr(&self) -> *mut c_void { // Skip the header size to get the data pointer unsafe { self.mapped_ptr.add(size_of::()) } } + + pub fn shmem_input_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + format!("{}_{}_input", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) + } + + pub fn shmem_output_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + format!("{}_{}_output", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) + } + + pub fn shmem_chunk_done_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + format!( + "/{}_{}_chunk_done", + AsmServices::shmem_prefix(port, local_rank), + asm_service.as_str() + ) + } } pub fn open_shmem(name: &str, flags: i32, mode: u32) -> Result { diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs new file mode 100644 index 000000000..b70cfdba7 --- /dev/null +++ b/executor/src/emu_asm.rs @@ -0,0 +1,399 @@ +use std::{ + collections::HashMap, + path::PathBuf, + sync::{Arc, Mutex}, + thread::JoinHandle, +}; + +use crate::{ + DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, ZiskExecutor, +}; +use asm_runner::{ + write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, + MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, TaskFactory, +}; +use data_bus::DataBusTrait; +use fields::PrimeField64; +use proofman_common::ProofCtx; +use rayon::prelude::*; +use sm_rom::RomSM; +#[cfg(feature = "stats")] +use zisk_common::ExecutorStatsEvent; +use zisk_common::{ + io::ZiskStdin, BusDeviceMetrics, ChunkId, EmuTrace, ExecutorStatsHandle, PayloadType, + ZiskExecutionResult, +}; +use zisk_core::{ZiskRom, MAX_INPUT_SIZE}; +use ziskemu::ZiskEmulator; + +pub struct EmulatorAsm { + /// ZisK ROM, a binary file containing the ZisK program to be executed. + pub zisk_rom: Arc, + + /// World rank for distributed execution. Default to 0 for single-node execution. + world_rank: i32, + + /// Local rank for distributed execution. Default to 0 for single-node execution. + local_rank: i32, + + /// Optional baseline port to communicate with assembly microservices. + base_port: Option, + + /// Map unlocked flag + /// This is used to unlock the memory map for the ROM file. + unlock_mapped_memory: bool, + + /// Chunk size for processing. + chunk_size: u64, + + /// Optional ROM state machine, used for assembly ROM execution. + rom_sm: Option>, + + asm_shmem_mt: Arc>>, + asm_shmem_mo: Arc>>, + asm_shmem_rh: Arc>>, + + /// Shared memory writers for each assembly service. + shmem_input_writer: [Arc>>; AsmServices::SERVICES.len()], +} + +impl EmulatorAsm { + #[allow(clippy::too_many_arguments)] + pub fn new( + zisk_rom: Arc, + _asm_path: PathBuf, + world_rank: i32, + local_rank: i32, + base_port: Option, + unlock_mapped_memory: bool, + chunk_size: u64, + rom_sm: Option>, + ) -> Self { + #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] + let (asm_shmem_mt, asm_shmem_mo) = (None, None); + + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + let asm_shmem_mt = PreloadedMT::new(local_rank, base_port, unlock_mapped_memory) + .expect("Failed to create PreloadedMT"); + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + let asm_shmem_mo = PreloadedMO::new(local_rank, base_port, unlock_mapped_memory) + .expect("Failed to create PreloadedMO"); + + Self { + zisk_rom, + world_rank, + local_rank, + base_port, + unlock_mapped_memory, + chunk_size, + rom_sm, + asm_shmem_mt: Arc::new(Mutex::new(Some(asm_shmem_mt))), + asm_shmem_mo: Arc::new(Mutex::new(Some(asm_shmem_mo))), + asm_shmem_rh: Arc::new(Mutex::new(None)), + shmem_input_writer: std::array::from_fn(|_| Arc::new(Mutex::new(None))), + } + } + + /// Computes minimal traces by processing the ZisK ROM with given public inputs. + /// + /// # Arguments + /// * `stdin` - Shared mutable access to the ZiskStdin providing public inputs. + /// * `pctx` - Proof context used during execution. + /// * `sm_bundle` - Static shared-memory bundle used by the executor. + /// * `stats` - Handle for collecting executor statistics. + /// * `_caller_stats_id` - Identifier used to attribute collected statistics to the caller. + /// + /// # Returns + /// A tuple containing: + /// * `MinimalTraces` - The computed minimal traces. + /// * `DeviceMetricsList` - Flat device metrics collected during execution. + /// * `NestedDeviceMetricsList` - Hierarchical device metrics collected during execution. + /// * `Option>` - Optional join handle for the memory-only ASM runner. + /// * `ZiskExecutionResult` - The result of executing the ZisK ROM. + #[allow(clippy::type_complexity)] + pub fn execute( + &self, + stdin: &Mutex, + pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + _caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ) { + #[cfg(feature = "stats")] + let parent_stats_id = stats.next_id(); + #[cfg(feature = "stats")] + stats.add_stat( + _caller_stats_id, + parent_stats_id, + "EXECUTE_WITH_ASSEMBLY", + 0, + ExecutorStatsEvent::Begin, + ); + + AsmServices::SERVICES.par_iter().enumerate().for_each(|(idx, service)| { + #[cfg(feature = "stats")] + let stats_id = stats.next_id(); + #[cfg(feature = "stats")] + stats.add_stat( + parent_stats_id, + stats_id, + "ASM_WRITE_INPUT", + 0, + ExecutorStatsEvent::Begin, + ); + + let port = if let Some(base_port) = self.base_port { + AsmServices::port_for(service, base_port, self.local_rank) + } else { + AsmServices::default_port(service, self.local_rank) + }; + + let shmem_input_name = + AsmSharedMemory::::shmem_input_name(port, *service, self.local_rank); + + let mut input_writer = self.shmem_input_writer[idx].lock().unwrap(); + if input_writer.is_none() { + tracing::info!( + "Initializing SharedMemoryWriter for service {:?} at '{}'", + service, + shmem_input_name + ); + *input_writer = Some( + SharedMemoryWriter::new( + &shmem_input_name, + MAX_INPUT_SIZE as usize, + self.unlock_mapped_memory, + ) + .expect("Failed to create SharedMemoryWriter"), + ); + } + + write_input(&mut stdin.lock().unwrap(), input_writer.as_ref().unwrap()); + + // Add to executor stats + #[cfg(feature = "stats")] + stats.add_stat( + parent_stats_id, + stats_id, + "ASM_WRITE_INPUT", + 0, + ExecutorStatsEvent::End, + ); + }); + + let chunk_size = self.chunk_size; + let (world_rank, local_rank, base_port) = + (self.world_rank, self.local_rank, self.base_port); + + let _stats = stats.clone(); + + // Run the assembly Memory Operations (MO) runner thread + let handle_mo = std::thread::spawn({ + let asm_shmem_mo = self.asm_shmem_mo.clone(); + move || { + AsmRunnerMO::run( + asm_shmem_mo.lock().unwrap().as_mut().unwrap(), + ZiskExecutor::::MAX_NUM_STEPS, + chunk_size, + world_rank, + local_rank, + base_port, + _stats, + ) + .expect("Error during Assembly Memory Operations execution") + } + }); + + // Run the ROM histogram only on partition 0 as it is always computed by this partition + let has_rom_sm = pctx.dctx_is_first_partition(); + + let _stats = stats.clone(); + + let handle_rh = (has_rom_sm).then(|| { + let asm_shmem_rh = self.asm_shmem_rh.clone(); + let unlock_mapped_memory = self.unlock_mapped_memory; + std::thread::spawn(move || { + AsmRunnerRH::run( + &mut asm_shmem_rh.lock().unwrap(), + ZiskExecutor::::MAX_NUM_STEPS, + world_rank, + local_rank, + base_port, + unlock_mapped_memory, + _stats, + ) + .expect("Error during ROM Histogram execution") + }) + }); + + let (min_traces, main_count, secn_count) = self.run_mt_assembly(sm_bundle, stats); + + // Store execute steps + let steps = if let MinimalTraces::AsmEmuTrace(asm_min_traces) = &min_traces { + asm_min_traces.vec_chunks.iter().map(|trace| trace.steps).sum::() + } else { + panic!("Expected AsmEmuTrace, got something else"); + }; + + let execution_result = ZiskExecutionResult::new(steps); + + // If the world rank is 0, wait for the ROM Histogram thread to finish and set the handler + if has_rom_sm { + self.rom_sm.as_ref().unwrap().set_asm_runner_handler( + handle_rh.expect("Error during Assembly ROM Histogram thread execution"), + ); + } + + #[cfg(feature = "stats")] + stats.add_stat(0, parent_stats_id, "EXECUTE_WITH_ASSEMBLY", 0, ExecutorStatsEvent::End); + + (min_traces, main_count, secn_count, Some(handle_mo), execution_result) + } + + fn run_mt_assembly( + &self, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + ) -> (MinimalTraces, DeviceMetricsList, NestedDeviceMetricsList) { + #[cfg(feature = "stats")] + let parent_stats_id = stats.next_id(); + #[cfg(feature = "stats")] + stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::Begin); + + struct CounterTask + where + DB: DataBusTrait>, + { + chunk_id: ChunkId, + emu_trace: Arc, + data_bus: DB, + zisk_rom: Arc, + _phantom: std::marker::PhantomData, + _stats: ExecutorStatsHandle, + _parent_stats_id: u64, + } + + impl Task for CounterTask + where + F: PrimeField64, + DB: DataBusTrait> + Send + Sync + 'static, + { + type Output = (ChunkId, DB); + + fn execute(mut self) -> Self::Output { + #[cfg(feature = "stats")] + let stats_id = self._stats.next_id(); + #[cfg(feature = "stats")] + self._stats.add_stat( + self._parent_stats_id, + stats_id, + "MT_CHUNK_PLAYER", + 0, + ExecutorStatsEvent::Begin, + ); + + ZiskEmulator::process_emu_trace::( + &self.zisk_rom, + &self.emu_trace, + &mut self.data_bus, + false, + ); + + self.data_bus.on_close(); + + // Add to executor stats + #[cfg(feature = "stats")] + self._stats.add_stat( + self._parent_stats_id, + stats_id, + "MT_CHUNK_PLAYER", + 0, + ExecutorStatsEvent::End, + ); + + (self.chunk_id, self.data_bus) + } + } + + let task_factory: TaskFactory<_> = + Box::new(|chunk_id: ChunkId, emu_trace: Arc| { + let data_bus = sm_bundle.build_data_bus_counters(); + CounterTask { + chunk_id, + emu_trace, + data_bus, + zisk_rom: self.zisk_rom.clone(), + _phantom: std::marker::PhantomData::, + _stats: stats.clone(), + #[cfg(feature = "stats")] + _parent_stats_id: parent_stats_id, + #[cfg(not(feature = "stats"))] + _parent_stats_id: 0, + } + }); + + let (asm_runner_mt, mut data_buses) = AsmRunnerMT::run_and_count( + self.asm_shmem_mt.lock().unwrap().as_mut().unwrap(), + ZiskExecutor::::MAX_NUM_STEPS, + self.chunk_size, + task_factory, + self.world_rank, + self.local_rank, + self.base_port, + stats.clone(), + ) + .expect("Error during ASM execution"); + + data_buses.sort_by_key(|(chunk_id, _)| chunk_id.0); + + let mut main_count = Vec::with_capacity(data_buses.len()); + let mut secn_count = HashMap::new(); + + for (chunk_id, data_bus) in data_buses { + let databus_counters = data_bus.into_devices(false); + + for (idx, counter) in databus_counters.into_iter() { + match idx { + None => { + main_count.push((chunk_id, counter.unwrap_or(Box::new(DummyCounter {})))); + } + Some(idx) => { + secn_count + .entry(idx) + .or_insert_with(Vec::new) + .push((chunk_id, counter.unwrap())); + } + } + } + } + + #[cfg(feature = "stats")] + stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::End); + (MinimalTraces::AsmEmuTrace(asm_runner_mt), main_count, secn_count) + } +} + +impl crate::Emulator for EmulatorAsm { + fn execute( + &self, + stdin: &Mutex, + pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ) { + self.execute(stdin, pctx, sm_bundle, stats, caller_stats_id) + } +} diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs new file mode 100644 index 000000000..ff3c382a3 --- /dev/null +++ b/executor/src/emu_rust.rs @@ -0,0 +1,202 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, + thread::JoinHandle, +}; + +use asm_runner::{AsmRunnerMO, MinimalTraces}; +use data_bus::DataBusTrait; +use fields::PrimeField64; +use proofman_common::ProofCtx; +use proofman_util::{timer_start_info, timer_stop_and_log_info}; +use rayon::prelude::*; +use zisk_common::{ + io::{ZiskIO, ZiskStdin}, + ChunkId, ExecutorStatsHandle, ZiskExecutionResult, +}; +use zisk_core::ZiskRom; +use ziskemu::{EmuOptions, ZiskEmulator}; + +use crate::{ + DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, ZiskExecutor, +}; + +pub struct EmulatorRust { + /// ZisK ROM, a binary file containing the ZisK program to be executed. + pub zisk_rom: Arc, + + /// Chunk size for processing. + chunk_size: u64, +} + +impl EmulatorRust { + /// The number of threads to use for parallel processing when computing minimal traces. + const NUM_THREADS: usize = 16; + + pub fn new(zisk_rom: Arc, chunk_size: u64) -> Self { + Self { zisk_rom, chunk_size } + } + + /// Computes minimal traces by processing the ZisK ROM with the given public inputs. + /// + /// # Arguments + /// * `stdin` - Shared standard input source used to feed data into the emulator. + /// * `_pctx` - Proof context carrying field-parameterized configuration for execution. + /// * `sm_bundle` - Static state machine bundle used for counting device metrics. + /// * `_stats` - Handle to executor statistics collection. + /// * `_caller_stats_id` - Identifier used to associate collected statistics with the caller. + /// + /// # Returns + /// A tuple containing: + /// * `MinimalTraces` - The minimal traces produced by the emulator. + /// * `DeviceMetricsList` - Metrics for primary devices. + /// * `NestedDeviceMetricsList` - Metrics for secondary/nested devices. + /// * `None`. + /// * `ZiskExecutionResult` - Summary of the emulator execution, including the total number of steps. + pub fn execute( + &self, + stdin: &Mutex, + _pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + _stats: &ExecutorStatsHandle, + _caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ) { + let min_traces = self.run_emulator::(Self::NUM_THREADS, &mut stdin.lock().unwrap()); + + // Store execute steps + let steps = if let MinimalTraces::EmuTrace(min_traces) = &min_traces { + min_traces.iter().map(|trace| trace.steps).sum::() + } else { + panic!("Expected EmuTrace, got something else"); + }; + + let execution_result = ZiskExecutionResult::new(steps); + + timer_start_info!(COUNT); + let (main_count, secn_count) = self.count(&min_traces, sm_bundle); + timer_stop_and_log_info!(COUNT); + + (min_traces, main_count, secn_count, None, execution_result) + } + + fn run_emulator( + &self, + num_threads: usize, + stdin: &mut ZiskStdin, + ) -> MinimalTraces { + // Call emulate with these options + let input_data = stdin.read(); + + // Settings for the emulator + let emu_options = EmuOptions { + chunk_size: Some(self.chunk_size), + max_steps: ZiskExecutor::::MAX_NUM_STEPS, + ..EmuOptions::default() + }; + + let min_traces = ZiskEmulator::compute_minimal_traces( + &self.zisk_rom, + &input_data, + &emu_options, + num_threads, + ) + .expect("Error during emulator execution"); + + MinimalTraces::EmuTrace(min_traces) + } + + /// Counts metrics for secondary state machines based on minimal traces. + /// + /// # Arguments + /// * `min_traces` - Minimal traces obtained from the ROM execution. + /// + /// # Returns + /// A tuple containing two vectors: + /// * A vector of main state machine metrics grouped by chunk ID. + /// * A vector of secondary state machine metrics grouped by chunk ID. The vector is nested, + /// with the outer vector representing the secondary state machines and the inner vector + /// containing the metrics for each chunk. + fn count( + &self, + min_traces: &MinimalTraces, + sm_bundle: &StaticSMBundle, + ) -> (DeviceMetricsList, NestedDeviceMetricsList) { + let min_traces = match min_traces { + MinimalTraces::EmuTrace(min_traces) => min_traces, + MinimalTraces::AsmEmuTrace(asm_min_traces) => &asm_min_traces.vec_chunks, + _ => unreachable!(), + }; + + let metrics_slices: Vec<_> = min_traces + .par_iter() + .map(|minimal_trace| { + let mut data_bus = sm_bundle.build_data_bus_counters(); + + ZiskEmulator::process_emu_trace::( + &self.zisk_rom, + minimal_trace, + &mut data_bus, + true, + ); + + let mut counters = Vec::new(); + + let databus_counters = data_bus.into_devices(true); + for counter in databus_counters.into_iter() { + counters.push(counter); + } + + counters + }) + .collect(); + + let mut main_count = Vec::new(); + let mut secn_count = HashMap::new(); + + for (chunk_id, counter_slice) in metrics_slices.into_iter().enumerate() { + for (idx, counter) in counter_slice.into_iter() { + match idx { + None => { + main_count.push(( + ChunkId(chunk_id), + counter.unwrap_or_else(|| Box::new(DummyCounter {})), + )); + } + Some(idx) => { + secn_count + .entry(idx) + .or_insert_with(Vec::new) + .push((ChunkId(chunk_id), counter.unwrap())); + } + } + } + } + + (main_count, secn_count) + } +} + +impl crate::Emulator for EmulatorRust { + fn execute( + &self, + stdin: &Mutex, + pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ) { + self.execute(stdin, pctx, sm_bundle, stats, caller_stats_id) + } +} diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 1ae30ec0a..7a94f134e 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -19,41 +19,32 @@ //! By structuring these phases, the `ZiskExecutor` ensures high-performance execution while //! maintaining clarity and modularity in the computation process. -use asm_runner::{ - shmem_input_name, write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, HintsFile, - HintsShmem, MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, - TaskFactory, -}; +use asm_runner::{HintsFile, HintsShmem, MinimalTraces}; use fields::PrimeField64; use pil_std_lib::Std; use precompiles_hints::HintsProcessor; use proofman_common::{create_pool, BufferPool, ProofCtx, ProofmanResult, SetupCtx}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; -use rayon::prelude::*; -use sm_rom::{RomInstance, RomSM}; +use sm_rom::RomInstance; use std::sync::atomic::{AtomicUsize, Ordering}; -use tracing::debug; use witness::WitnessComponent; -use zisk_common::io::{StreamSource, ZiskIO, ZiskStdin, ZiskStream}; +use zisk_common::io::{StreamSource, ZiskStdin, ZiskStream}; -use crate::DummyCounter; use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; +use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusDeviceMetrics, CheckPoint, ExecutorStats, ExecutorStatsHandle, Instance, InstanceCtx, InstanceType, Plan, Stats, ZiskExecutionResult, }; -use zisk_common::{ChunkId, PayloadType}; use zisk_pil::{ ZiskPublicValues, INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, ZISK_AIRGROUP_ID, }; -use std::thread::JoinHandle; use std::time::Instant; use std::{ collections::HashMap, - path::PathBuf, sync::{Arc, Mutex, RwLock}, }; #[cfg(feature = "stats")] @@ -62,16 +53,14 @@ use zisk_common::ExecutorStatsEvent; use crossbeam::atomic::AtomicCell; use zisk_common::EmuTrace; -use zisk_core::{ZiskRom, MAX_INPUT_SIZE}; -use ziskemu::{EmuOptions, ZiskEmulator}; +use zisk_core::ZiskRom; +use ziskemu::ZiskEmulator; -use crate::StaticSMBundle; +use crate::{Emulator, EmulatorKind, StaticSMBundle}; use anyhow::Result; -type DeviceMetricsByChunk = (ChunkId, Box); // (chunk_id, metrics) -type DeviceMetricsList = Vec; -pub type NestedDeviceMetricsList = HashMap; +pub type DeviceMetricsByChunk = (ChunkId, Box); // (chunk_id, metrics) #[allow(dead_code)] enum MinimalTraceExecutionMode { @@ -85,36 +74,32 @@ pub type StreamHintsFile = ZiskStream>; /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. pub struct ZiskExecutor { - /// Standard input for the ZisK program. + /// Standard input for the ZisK program execution. stdin: Mutex, + /// The emulator backend used for execution. + emulator: EmulatorKind, + + /// Chunk size for processing. + chunk_size: u64, + /// Pipeline for handling precompile hints. hints_stream: Mutex, // hints_stream: Mutex, - /// ZisK ROM, a binary file containing the ZisK program to be executed. - pub zisk_rom: Arc, - - /// Path to the ZisK ROM file. - pub rom_path: PathBuf, - - /// Path to the assembly minimal trace binary file, if applicable. - pub asm_runner_path: Option, - - /// Path to the assembly ROM binary file, if applicable. - pub asm_rom_path: Option, + zisk_rom: Arc, /// Planning information for main state machines. - pub min_traces: Arc>, + min_traces: Arc>, /// Planning information for secondary state machines. - pub secn_planning: RwLock>, + secn_planning: RwLock>, /// Main state machine instances, indexed by their global ID. - pub main_instances: RwLock>>, + main_instances: RwLock>>, /// Secondary state machine instances, indexed by their global ID. - pub secn_instances: RwLock>>>, + secn_instances: RwLock>>>, /// Standard library instance, providing common functionalities. std: Arc>, @@ -125,9 +110,6 @@ pub struct ZiskExecutor { /// State machine bundle, containing the state machines and their configurations. sm_bundle: StaticSMBundle, - /// Optional ROM state machine, used for assembly ROM execution. - rom_sm: Option>, - /// Collectors by instance, storing statistics and collectors for each instance. #[allow(clippy::type_complexity)] collectors_by_instance: @@ -135,35 +117,11 @@ pub struct ZiskExecutor { /// Statistics collected during the execution, including time taken for collection and witness computation. stats: ExecutorStatsHandle, - - chunk_size: u64, - - /// World rank for distributed execution. Default to 0 for single-node execution. - world_rank: i32, - - /// Local rank for distributed execution. Default to 0 for single-node execution. - local_rank: i32, - - /// Optional baseline port to communicate with assembly microservices. - base_port: Option, - - /// Map unlocked flag - /// This is used to unlock the memory map for the ROM file. - unlock_mapped_memory: bool, - - asm_shmem_mt: Arc>>, - asm_shmem_mo: Arc>>, - asm_shmem_rh: Arc>>, - - shmem_input_writer: [Arc>>; AsmServices::SERVICES.len()], } impl ZiskExecutor { - /// The number of threads to use for parallel processing when computing minimal traces. - const NUM_THREADS: usize = 16; - /// The maximum number of steps to execute in the emulator or assembly runner. - const MAX_NUM_STEPS: u64 = 1 << 32; + pub const MAX_NUM_STEPS: u64 = 1 << 32; /// Creates a new instance of the `ZiskExecutor`. /// @@ -171,51 +129,18 @@ impl ZiskExecutor { /// * `zisk_rom` - An `Arc`-wrapped ZisK ROM instance. #[allow(clippy::too_many_arguments)] pub fn new( - rom_path: PathBuf, - asm_path: Option, - asm_rom_path: Option, zisk_rom: Arc, std: Arc>, sm_bundle: StaticSMBundle, - rom_sm: Option>, chunk_size: u64, - world_rank: i32, - local_rank: i32, - base_port: Option, - unlock_mapped_memory: bool, + emulator: EmulatorKind, + hints_stream: StreamHintsShmem, ) -> Self { - #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] - let (asm_shmem_mt, asm_shmem_mo) = (None, None); - - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - let (asm_shmem_mt, asm_shmem_mo) = if asm_path.is_some() { - let mt = PreloadedMT::new(local_rank, base_port, unlock_mapped_memory) - .expect("Failed to create PreloadedMT"); - let mo = PreloadedMO::new(local_rank, base_port, unlock_mapped_memory) - .expect("Failed to create PreloadedMO"); - (Some(mt), Some(mo)) - } else { - (None, None) - }; - - // Create hints pipeline with null hints stream initially. - let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory) - .expect("Failed to create HintsShmem"); - // let hints_shmem = HintsFile::new("test_modexp_results.bin".to_string()) - // .expect("Failed to create HintsFile"); - - let hints_processor = HintsProcessor::builder(hints_shmem) - .build() - .expect("Failed to create PrecompileHintsProcessor"); - - let hints_stream = Mutex::new(ZiskStream::new(hints_processor)); - Self { stdin: Mutex::new(ZiskStdin::null()), - hints_stream, - rom_path, - asm_runner_path: asm_path, - asm_rom_path, + emulator, + chunk_size, + hints_stream: Mutex::new(hints_stream), zisk_rom, min_traces: Arc::new(RwLock::new(MinimalTraces::None)), secn_planning: RwLock::new(Vec::new()), @@ -225,17 +150,7 @@ impl ZiskExecutor { std, execution_result: Mutex::new(ZiskExecutionResult::default()), sm_bundle, - rom_sm, stats: ExecutorStatsHandle::new(), - chunk_size, - world_rank, - local_rank, - base_port, - unlock_mapped_memory, - asm_shmem_mt: Arc::new(Mutex::new(asm_shmem_mt)), - asm_shmem_mo: Arc::new(Mutex::new(asm_shmem_mo)), - asm_shmem_rh: Arc::new(Mutex::new(None)), - shmem_input_writer: std::array::from_fn(|_| Arc::new(Mutex::new(None))), } } @@ -257,320 +172,6 @@ impl ZiskExecutor { self.stats.store_stats(); } - /// Computes minimal traces by processing the ZisK ROM with given public inputs. - /// - /// # Arguments - /// * `input_data` - Input data for the ROM execution. - /// * `num_threads` - Number of threads to use for parallel execution. - /// - /// # Returns - /// A vector of `EmuTrace` instances representing minimal traces. - fn execute_with_emulator(&self) -> MinimalTraces { - let min_traces = self.run_emulator(Self::NUM_THREADS, &mut self.stdin.lock().unwrap()); - - // Store execute steps - let steps = if let MinimalTraces::EmuTrace(min_traces) = &min_traces { - min_traces.iter().map(|trace| trace.steps).sum::() - } else { - panic!("Expected EmuTrace, got something else"); - }; - - self.execution_result.lock().unwrap().steps = steps; - - min_traces - } - - /// Computes minimal traces by processing the ZisK ROM with given public inputs. - /// - /// # Arguments - /// * `input_data` - Input data for the ROM execution. - /// * `num_threads` - Number of threads to use for parallel execution. - /// - /// # Returns - /// A vector of `EmuTrace` instances representing minimal traces. - #[allow(clippy::type_complexity)] - fn execute_with_assembly( - &self, - pctx: &ProofCtx, - _caller_stats_id: u64, - ) -> (MinimalTraces, DeviceMetricsList, NestedDeviceMetricsList, Option>) - { - #[cfg(feature = "stats")] - let parent_stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat( - _caller_stats_id, - parent_stats_id, - "EXECUTE_WITH_ASSEMBLY", - 0, - ExecutorStatsEvent::Begin, - ); - - AsmServices::SERVICES.par_iter().enumerate().for_each(|(idx, service)| { - #[cfg(feature = "stats")] - let stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat( - parent_stats_id, - stats_id, - "ASM_WRITE_INPUT", - 0, - ExecutorStatsEvent::Begin, - ); - - let port = if let Some(base_port) = self.base_port { - AsmServices::port_for(service, base_port, self.local_rank) - } else { - AsmServices::default_port(service, self.local_rank) - }; - - // Write inputs to shared memory - let shmem_input_name = shmem_input_name(port, *service, self.local_rank); - - let mut input_writer = self.shmem_input_writer[idx].lock().unwrap(); - if input_writer.is_none() { - debug!( - "Initializing SharedMemoryWriter for service {:?} at '{}'", - service, shmem_input_name - ); - *input_writer = Some( - SharedMemoryWriter::new( - &shmem_input_name, - MAX_INPUT_SIZE as usize, - self.unlock_mapped_memory, - ) - .expect("Failed to create SharedMemoryWriter"), - ); - } - - write_input(&mut self.stdin.lock().unwrap(), input_writer.as_ref().unwrap()); - - // Add to executor stats - #[cfg(feature = "stats")] - self.stats.add_stat( - parent_stats_id, - stats_id, - "ASM_WRITE_INPUT", - 0, - ExecutorStatsEvent::End, - ); - }); - - let chunk_size = self.chunk_size; - let (world_rank, local_rank, base_port) = - (self.world_rank, self.local_rank, self.base_port); - - let stats = self.stats.clone(); - - // Run the assembly Memory Operations (MO) runner thread - let handle_mo = std::thread::spawn({ - let asm_shmem_mo = self.asm_shmem_mo.clone(); - move || { - AsmRunnerMO::run( - asm_shmem_mo.lock().unwrap().as_mut().unwrap(), - Self::MAX_NUM_STEPS, - chunk_size, - world_rank, - local_rank, - base_port, - stats, - ) - .expect("Error during Assembly Memory Operations execution") - } - }); - - let stats = self.stats.clone(); - - // Run the ROM histogram only on partition 0 as it is always computed by this partition - let has_rom_sm = pctx.dctx_is_first_partition(); - - let handle_rh = (has_rom_sm).then(|| { - let asm_shmem_rh = self.asm_shmem_rh.clone(); - let unlock_mapped_memory = self.unlock_mapped_memory; - std::thread::spawn(move || { - AsmRunnerRH::run( - &mut asm_shmem_rh.lock().unwrap(), - Self::MAX_NUM_STEPS, - world_rank, - local_rank, - base_port, - unlock_mapped_memory, - stats, - ) - .expect("Error during ROM Histogram execution") - }) - }); - - let (min_traces, main_count, secn_count) = self.run_mt_assembly(); - - // Store execute steps - let steps = if let MinimalTraces::AsmEmuTrace(asm_min_traces) = &min_traces { - asm_min_traces.vec_chunks.iter().map(|trace| trace.steps).sum::() - } else { - panic!("Expected AsmEmuTrace, got something else"); - }; - - self.execution_result.lock().unwrap().steps = steps; - - // If the world rank is 0, wait for the ROM Histogram thread to finish and set the handler - if has_rom_sm { - self.rom_sm.as_ref().unwrap().set_asm_runner_handler( - handle_rh.expect("Error during Assembly ROM Histogram thread execution"), - ); - } - - #[cfg(feature = "stats")] - self.stats.add_stat( - 0, - parent_stats_id, - "EXECUTE_WITH_ASSEMBLY", - 0, - ExecutorStatsEvent::End, - ); - - (min_traces, main_count, secn_count, Some(handle_mo)) - } - - fn run_mt_assembly(&self) -> (MinimalTraces, DeviceMetricsList, NestedDeviceMetricsList) { - #[cfg(feature = "stats")] - let parent_stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::Begin); - - struct CounterTask - where - DB: DataBusTrait>, - { - chunk_id: ChunkId, - emu_trace: Arc, - data_bus: DB, - zisk_rom: Arc, - _phantom: std::marker::PhantomData, - _stats: ExecutorStatsHandle, - _parent_stats_id: u64, - } - - impl Task for CounterTask - where - F: PrimeField64, - DB: DataBusTrait> + Send + Sync + 'static, - { - type Output = (ChunkId, DB); - - fn execute(mut self) -> Self::Output { - #[cfg(feature = "stats")] - let stats_id = self._stats.next_id(); - #[cfg(feature = "stats")] - self._stats.add_stat( - self._parent_stats_id, - stats_id, - "MT_CHUNK_PLAYER", - 0, - ExecutorStatsEvent::Begin, - ); - - ZiskEmulator::process_emu_trace::( - &self.zisk_rom, - &self.emu_trace, - &mut self.data_bus, - false, - ); - - self.data_bus.on_close(); - - // Add to executor stats - #[cfg(feature = "stats")] - self._stats.add_stat( - self._parent_stats_id, - stats_id, - "MT_CHUNK_PLAYER", - 0, - ExecutorStatsEvent::End, - ); - - (self.chunk_id, self.data_bus) - } - } - - let task_factory: TaskFactory<_> = - Box::new(|chunk_id: ChunkId, emu_trace: Arc| { - let data_bus = self.sm_bundle.build_data_bus_counters(); - CounterTask { - chunk_id, - emu_trace, - data_bus, - zisk_rom: self.zisk_rom.clone(), - _phantom: std::marker::PhantomData::, - _stats: self.stats.clone(), - #[cfg(feature = "stats")] - _parent_stats_id: parent_stats_id, - #[cfg(not(feature = "stats"))] - _parent_stats_id: 0, - } - }); - - let (asm_runner_mt, mut data_buses) = AsmRunnerMT::run_and_count( - self.asm_shmem_mt.lock().unwrap().as_mut().unwrap(), - Self::MAX_NUM_STEPS, - self.chunk_size, - task_factory, - self.world_rank, - self.local_rank, - self.base_port, - self.stats.clone(), - ) - .expect("Error during ASM execution"); - - data_buses.sort_by_key(|(chunk_id, _)| chunk_id.0); - - let mut main_count = Vec::with_capacity(data_buses.len()); - let mut secn_count = HashMap::new(); - - for (chunk_id, data_bus) in data_buses { - let databus_counters = data_bus.into_devices(false); - - for (idx, counter) in databus_counters.into_iter() { - match idx { - None => { - main_count.push((chunk_id, counter.unwrap_or(Box::new(DummyCounter {})))); - } - Some(idx) => { - secn_count - .entry(idx) - .or_insert_with(Vec::new) - .push((chunk_id, counter.unwrap())); - } - } - } - } - - #[cfg(feature = "stats")] - self.stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::End); - (MinimalTraces::AsmEmuTrace(asm_runner_mt), main_count, secn_count) - } - - fn run_emulator(&self, num_threads: usize, stdin: &mut ZiskStdin) -> MinimalTraces { - // Call emulate with these options - let input_data = stdin.read(); - - // Settings for the emulator - let emu_options = EmuOptions { - chunk_size: Some(self.chunk_size), - max_steps: Self::MAX_NUM_STEPS, - ..EmuOptions::default() - }; - - let min_traces = ZiskEmulator::compute_minimal_traces( - &self.zisk_rom, - &input_data, - &emu_options, - num_threads, - ) - .expect("Error during emulator execution"); - - MinimalTraces::EmuTrace(min_traces) - } - /// Adds main state machine instances to the proof context and assigns global IDs. /// /// # Arguments @@ -607,72 +208,6 @@ impl ZiskExecutor { MainInstance::new(InstanceCtx::new(global_id, plan), self.std.clone()) } - /// Counts metrics for secondary state machines based on minimal traces. - /// - /// # Arguments - /// * `min_traces` - Minimal traces obtained from the ROM execution. - /// - /// # Returns - /// A tuple containing two vectors: - /// * A vector of main state machine metrics grouped by chunk ID. - /// * A vector of secondary state machine metrics grouped by chunk ID. The vector is nested, - /// with the outer vector representing the secondary state machines and the inner vector - /// containing the metrics for each chunk. - fn count(&self, min_traces: &MinimalTraces) -> (DeviceMetricsList, NestedDeviceMetricsList) { - let min_traces = match min_traces { - MinimalTraces::EmuTrace(min_traces) => min_traces, - MinimalTraces::AsmEmuTrace(asm_min_traces) => &asm_min_traces.vec_chunks, - _ => unreachable!(), - }; - - let metrics_slices: Vec<_> = min_traces - .par_iter() - .map(|minimal_trace| { - let mut data_bus = self.sm_bundle.build_data_bus_counters(); - - ZiskEmulator::process_emu_trace::( - &self.zisk_rom, - minimal_trace, - &mut data_bus, - true, - ); - - let mut counters = Vec::new(); - - let databus_counters = data_bus.into_devices(true); - for counter in databus_counters.into_iter() { - counters.push(counter); - } - - counters - }) - .collect(); - - let mut main_count = Vec::new(); - let mut secn_count = HashMap::new(); - - for (chunk_id, counter_slice) in metrics_slices.into_iter().enumerate() { - for (idx, counter) in counter_slice.into_iter() { - match idx { - None => { - main_count.push(( - ChunkId(chunk_id), - counter.unwrap_or_else(|| Box::new(DummyCounter {})), - )); - } - Some(idx) => { - secn_count - .entry(idx) - .or_insert_with(Vec::new) - .push((ChunkId(chunk_id), counter.unwrap())); - } - } - } - } - - (main_count, secn_count) - } - /// Adds secondary state machine instances to the proof context and assigns global IDs. /// /// # Arguments @@ -825,7 +360,7 @@ impl ZiskExecutor { let witness_start_time = Instant::now(); #[cfg(feature = "stats")] - let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id); + let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id)?; #[cfg(feature = "stats")] let stats_id = self.stats.next_id(); #[cfg(feature = "stats")] @@ -1103,7 +638,7 @@ impl ZiskExecutor { _caller_stats_id: u64, ) -> ProofmanResult<()> { #[cfg(feature = "stats")] - let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id); + let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id)?; #[cfg(feature = "stats")] let stats_id = self.stats.next_id(); #[cfg(feature = "stats")] @@ -1221,30 +756,23 @@ impl WitnessComponent for ZiskExecutor { // Process the ROM to collect the Minimal Traces timer_start_info!(COMPUTE_MINIMAL_TRACE); - assert_eq!(self.asm_runner_path.is_some(), self.asm_rom_path.is_some()); - - let (min_traces, main_count, mut secn_count, handle_mo) = if self.asm_runner_path.is_some() - { - // If we are executing in assembly mode - self.execute_with_assembly( + let (min_traces, main_count, mut secn_count, handle_mo, execution_result) = + self.emulator.execute( + &self.stdin, &pctx, + &self.sm_bundle, + &self.stats, #[cfg(feature = "stats")] parent_stats_id, #[cfg(not(feature = "stats"))] 0, - ) - } else { - // Otherwise, use the emulator - let min_traces = self.execute_with_emulator(); - - timer_start_info!(COUNT); - let (main_count, secn_count) = self.count(&min_traces); - timer_stop_and_log_info!(COUNT); + ); - (min_traces, main_count, secn_count, None) - }; timer_stop_and_log_info!(COMPUTE_MINIMAL_TRACE); + // Store the execution result + *self.execution_result.lock().unwrap() = execution_result; + // Plan the main and secondary instances using the counted metrics // stats_begin!(stats_next_id!(), parent_stats_id, "PLAN"); #[cfg(feature = "stats")] @@ -1458,7 +986,7 @@ impl WitnessComponent for ZiskExecutor { InstanceType::Instance => { if !self.collectors_by_instance.read().unwrap().contains_key(&global_id) { - if air_id == ROM_AIR_IDS[0] && self.asm_runner_path.is_some() { + if air_id == ROM_AIR_IDS[0] && self.emulator.is_asm_emulator() { let stats = Stats { airgroup_id, air_id, @@ -1548,7 +1076,7 @@ impl WitnessComponent for ZiskExecutor { if MAIN_AIR_IDS.contains(&air_id) { pctx.set_witness_ready(global_id, false); } else if air_id == ROM_AIR_IDS[0] { - if self.asm_runner_path.is_some() { + if self.emulator.is_asm_emulator() { pctx.set_witness_ready(global_id, false); } else { let secn_instance = &secn_instances_guard[&global_id]; diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 936a187d4..a6002b862 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -1,11 +1,78 @@ mod dummy_counter; +mod emu_asm; +mod emu_rust; mod executor; mod sm_static_bundle; mod static_data_bus; mod static_data_bus_collect; pub use dummy_counter::*; +pub use emu_asm::*; +pub use emu_rust::*; pub use executor::*; pub use sm_static_bundle::*; pub use static_data_bus::*; pub use static_data_bus_collect::*; + +pub type DeviceMetricsList = Vec; +pub type NestedDeviceMetricsList = HashMap; + +use asm_runner::{AsmRunnerMO, MinimalTraces}; +use fields::PrimeField64; +use proofman_common::ProofCtx; +use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; +use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, ZiskExecutionResult}; + +/// Trait for unified execution across different emulator backends +pub trait Emulator: Send + Sync { + /// Execute the emulator + fn execute( + &self, + stdin: &Mutex, + pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ); +} + +/// Enum wrapper for different emulator backends (no heap allocation) +pub enum EmulatorKind { + Asm(EmulatorAsm), + Rust(EmulatorRust), +} + +impl EmulatorKind { + /// Check if this is an ASM emulator (non-generic, can be called without F) + pub fn is_asm_emulator(&self) -> bool { + matches!(self, Self::Asm(_)) + } +} + +impl Emulator for EmulatorKind { + fn execute( + &self, + stdin: &Mutex, + pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + stats: &ExecutorStatsHandle, + caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ) { + match self { + Self::Asm(e) => e.execute(stdin, pctx, sm_bundle, stats, caller_stats_id), + Self::Rust(e) => e.execute(stdin, pctx, sm_bundle, stats, caller_stats_id), + } + } +} diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index 3aa8edb76..e77b56333 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -1,7 +1,6 @@ use std::sync::Arc; -use crate::NestedDeviceMetricsList; -use crate::StaticDataBusCollect; +use crate::{NestedDeviceMetricsList, StaticDataBusCollect}; use data_bus::DataBusTrait; use fields::PrimeField64; use precomp_arith_eq::{ArithEqInstance, ArithEqManager}; diff --git a/server/Cargo.toml b/server/Cargo.toml deleted file mode 100644 index 1f0464cec..000000000 --- a/server/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "server" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -keywords = { workspace = true } -repository = { workspace = true } -categories = { workspace = true } - -build = "build.rs" - -[dependencies] -zisk-common = { workspace = true } -executor = { workspace = true } -zisk-witness = { workspace = true } -asm-runner = { workspace = true } - -proofman = { workspace = true } -proofman-common = { workspace = true } -witness = { workspace = true } -anyhow = { workspace = true} -libloading = { workspace = true } -colored = { workspace = true } -fields = { workspace = true } -tracing = { workspace = true} - -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -uuid = { version = "1.6", features = ["v4"] } -clap = { workspace = true } -bytemuck = { workspace = true } -zstd = { workspace = true } - -# Distributed mode (mpi) is only supported on Linux x86_64 -[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] -mpi = { workspace = true } -named-sem = { workspace = true } - -[features] -default = [] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -stats = [] - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(distributed)'] } \ No newline at end of file diff --git a/server/build.rs b/server/build.rs deleted file mode 100644 index 8da52f323..000000000 --- a/server/build.rs +++ /dev/null @@ -1,11 +0,0 @@ -fn main() { - let disable_distributed = - std::env::vars().any(|(k, _)| k == "CARGO_FEATURE_DISABLE_DISTRIBUTED"); - let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); - let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); - - // Distributed feature is only available on linux x86_64 - if !disable_distributed && target_os == "linux" && target_arch == "x86_64" { - println!("cargo:rustc-cfg=distributed"); - } -} diff --git a/server/src/handler_prove.rs b/server/src/handler_prove.rs deleted file mode 100644 index 9130a0925..000000000 --- a/server/src/handler_prove.rs +++ /dev/null @@ -1,178 +0,0 @@ -use bytemuck::cast_slice; -use colored::Colorize; -use fields::Goldilocks; -use proofman::ProofMan; -use proofman::{ProofInfo, ProvePhase, ProvePhaseInputs, ProvePhaseResult}; -use proofman_common::ProofOptions; -use serde::{Deserialize, Serialize}; -use std::io::Write; -use std::sync::Arc; -use std::thread::JoinHandle; -use std::{fs::File, path::PathBuf}; -use zisk_common::{ExecutorStats, ProofLog, ZiskExecutionResult, ZiskLib}; - -use crate::{ - ServerConfig, ZiskBaseResponse, ZiskCmdResult, ZiskResponse, ZiskResultCode, ZiskService, -}; - -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct ZiskProveRequest { - pub input: PathBuf, - pub aggregation: bool, - pub rma: bool, - pub final_snark: bool, - pub verify_proofs: bool, - pub minimal_memory: bool, - pub folder: PathBuf, - pub prefix: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskProveResponse { - #[serde(flatten)] - pub base: ZiskBaseResponse, - - server_id: String, - elf_file: String, - input: String, -} -pub struct ZiskServiceProveHandler; - -impl ZiskServiceProveHandler { - pub fn handle( - config: Arc, - request: ZiskProveRequest, - // It is important to keep the witness_lib declaration before the proofman declaration - // to ensure that the witness library is dropped before the proofman. - witness_lib: Arc>>, - proofman: Arc>, - is_busy: Arc, - ) -> (ZiskResponse, Option>) { - is_busy.store(true, std::sync::atomic::Ordering::SeqCst); - - let handle = std::thread::spawn({ - let request_input = request.input.clone(); - let config = config.clone(); - move || { - let start = std::time::Instant::now(); - - let result = proofman - .generate_proof_from_lib( - ProvePhaseInputs::Full(ProofInfo::new(Some(request_input), 1, vec![0], 0)), - ProofOptions::new( - false, - request.aggregation, - request.rma, - request.final_snark, - request.verify_proofs, - request.minimal_memory, - false, - request.folder.clone(), - ), - ProvePhase::Full, - ) - .map_err(|e| anyhow::anyhow!("Error generating proof: {}", e)) - .expect("Failed to generate proof"); - - let (proof_id, vadcop_final_proof) = - if let ProvePhaseResult::Full(proof_id, vadcop_final_proof) = result { - (proof_id, vadcop_final_proof) - } else { - (None, None) - }; - - let elapsed = start.elapsed(); - - if proofman.rank().unwrap() == 0 { - #[allow(clippy::type_complexity)] - let (result, mut _stats): ( - ZiskExecutionResult, - ExecutorStats, - ) = witness_lib.execution_result().expect("Failed to get execution result"); - - proofman.set_barrier(); - let elapsed = elapsed.as_secs_f64(); - tracing::info!(""); - tracing::info!( - "{}", - "--- PROVE SUMMARY ------------------------".bright_green().bold() - ); - if let Some(proof_id) = &proof_id { - tracing::info!(" Proof ID: {}", proof_id); - } - tracing::info!(" ► Statistics"); - tracing::info!(" time: {} seconds, steps: {}", elapsed, result.steps); - - // Store the stats in stats.json - #[cfg(feature = "stats")] - { - let stats_id = _stats.next_id(); - _stats.add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); - _stats.store_stats(); - } - - if let Some(proof_id) = proof_id { - let logs = ProofLog::new(result.steps, proof_id, elapsed); - let log_path = - request.folder.join(format!("{}-result.json", request.prefix)); - println!("Writing proof log to: {}", log_path.display()); - ProofLog::write_json_log(&log_path, &logs) - .map_err(|e| anyhow::anyhow!("Error generating log: {}", e)) - .expect("Failed to generate proof"); - // Save the uncompressed vadcop final proof - let output_file_path = request - .folder - .join(format!("{}-vadcop_final_proof.bin", request.prefix)); - - let vadcop_proof = vadcop_final_proof.unwrap(); - let proof_data = cast_slice(&vadcop_proof); - let mut file = - File::create(&output_file_path).expect("Error while creating file"); - file.write_all(proof_data).expect("Error while writing to file"); - } - } - is_busy.store(false, std::sync::atomic::Ordering::SeqCst); - ZiskService::print_waiting_message(&config); - } - }); - - ( - ZiskResponse::ZiskProveResponse(ZiskProveResponse { - base: ZiskBaseResponse { - cmd: "prove".to_string(), - result: ZiskCmdResult::InProgress, - code: ZiskResultCode::Ok, - msg: None, - node: config.asm_runner_options.world_rank, - }, - server_id: config.server_id.to_string(), - elf_file: config.elf.display().to_string(), - input: request.input.display().to_string(), - }), - Some(handle), - ) - } - pub fn process_handle(request: ZiskProveRequest, proofman: Arc>) { - proofman - .generate_proof_from_lib( - ProvePhaseInputs::Full(ProofInfo::new(Some(request.input), 1, vec![0], 0)), - ProofOptions::new( - false, - request.aggregation, - request.rma, - request.final_snark, - request.verify_proofs, - request.minimal_memory, - false, - request.folder.clone(), - ), - ProvePhase::Full, - ) - .map_err(|e| anyhow::anyhow!("Error generating proof: {}", e)) - .expect("Failed to generate proof"); - proofman.set_barrier(); - } -} diff --git a/server/src/handler_shutdown.rs b/server/src/handler_shutdown.rs deleted file mode 100644 index 4185a9d12..000000000 --- a/server/src/handler_shutdown.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::thread::JoinHandle; - -use asm_runner::AsmServices; - -use serde::{Deserialize, Serialize}; - -use crate::{ServerConfig, ZiskBaseResponse, ZiskResponse}; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct ZiskShutdownRequest; - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskShutdownResponse { - #[serde(flatten)] - pub base: ZiskBaseResponse, -} - -pub struct ZiskServiceShutdownHandler; - -impl ZiskServiceShutdownHandler { - pub fn handle( - config: &ServerConfig, - _payload: ZiskShutdownRequest, - asm_services: Option<&AsmServices>, - ) -> (ZiskResponse, Option>) { - tracing::info!( - "<<< [{}] Shutting down ASM microservices.", - config.asm_runner_options.world_rank - ); - - if let Some(asm_services) = asm_services { - let shutdown_result = asm_services.stop_asm_services(); - - if let Err(e) = shutdown_result { - tracing::error!("Failed to stop ASM services: {}", e); - return ( - ZiskResponse::ZiskShutdownResponse(ZiskShutdownResponse { - base: ZiskBaseResponse { - cmd: "shutdown".to_string(), - result: crate::ZiskCmdResult::Error, - code: crate::ZiskResultCode::Error, - msg: Some(format!("Failed to stop ASM services: {e}")), - node: config.asm_runner_options.world_rank, - }, - }), - None, - ); - } - } - - ( - ZiskResponse::ZiskShutdownResponse(ZiskShutdownResponse { - base: ZiskBaseResponse { - cmd: "shutdown".to_string(), - result: crate::ZiskCmdResult::Ok, - code: crate::ZiskResultCode::Ok, - msg: None, - node: config.asm_runner_options.world_rank, - }, - }), - None, - ) - } -} diff --git a/server/src/handler_status.rs b/server/src/handler_status.rs deleted file mode 100644 index 11f244589..000000000 --- a/server/src/handler_status.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::{ - sync::{atomic::AtomicBool, Arc}, - thread::JoinHandle, -}; - -use crate::{ServerConfig, ZiskBaseResponse, ZiskResponse}; - -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct ZiskStatusRequest; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum ZiskStatus { - Idle, - Working, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskStatusResponse { - #[serde(flatten)] - pub base: ZiskBaseResponse, - - server_id: String, - elf_file: String, - uptime: String, - status: ZiskStatus, -} - -pub struct ZiskServiceStatusHandler; - -impl ZiskServiceStatusHandler { - pub fn handle( - config: &ServerConfig, - _payload: ZiskStatusRequest, - is_busy: Arc, - ) -> (ZiskResponse, Option>) { - let uptime = config.launch_time.elapsed(); - - ( - ZiskResponse::ZiskStatusResponse(ZiskStatusResponse { - base: ZiskBaseResponse { - cmd: "status".to_string(), - result: crate::ZiskCmdResult::Ok, - code: crate::ZiskResultCode::Ok, - msg: None, - node: config.asm_runner_options.world_rank, - }, - server_id: config.server_id.to_string(), - elf_file: config.elf.display().to_string(), - uptime: format!("{uptime:.2?}"), - status: if is_busy.load(std::sync::atomic::Ordering::SeqCst) { - ZiskStatus::Working - } else { - ZiskStatus::Idle - }, - }), - None, - ) - } -} diff --git a/server/src/handler_verify_constraints.rs b/server/src/handler_verify_constraints.rs deleted file mode 100644 index e94dbfbf3..000000000 --- a/server/src/handler_verify_constraints.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::{path::PathBuf, sync::Arc, thread::JoinHandle}; - -use crate::{ - ServerConfig, ZiskBaseResponse, ZiskCmdResult, ZiskResponse, ZiskResultCode, ZiskService, -}; -use colored::Colorize; -use fields::Goldilocks; -use proofman::ProofMan; -use proofman_common::DebugInfo; -use serde::{Deserialize, Serialize}; -use zisk_common::{ExecutorStats, ZiskExecutionResult, ZiskLib}; - -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct ZiskVerifyConstraintsRequest { - pub input: PathBuf, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskVerifyConstraintsResponse { - #[serde(flatten)] - pub base: ZiskBaseResponse, - - server_id: String, - elf_file: String, - input: String, -} - -pub struct ZiskServiceVerifyConstraintsHandler; - -impl ZiskServiceVerifyConstraintsHandler { - pub fn handle( - config: Arc, - request: ZiskVerifyConstraintsRequest, - // It is important to keep the witness_lib declaration before the proofman declaration - // to ensure that the witness library is dropped before the proofman. - witness_lib: Arc>>, - proofman: Arc>, - is_busy: Arc, - debug_info: Arc, - ) -> (ZiskResponse, Option>) { - is_busy.store(true, std::sync::atomic::Ordering::SeqCst); - - let handle = std::thread::spawn({ - let config = config.clone(); - move || { - let start = std::time::Instant::now(); - - proofman - .verify_proof_constraints_from_lib(&debug_info, false) - .map_err(|e| anyhow::anyhow!("Error verifying proof: {}", e)) - .expect("Failed to generate proof"); - proofman.set_barrier(); - let elapsed = start.elapsed(); - - #[allow(clippy::type_complexity)] - let (result, mut _stats): (ZiskExecutionResult, ExecutorStats) = - witness_lib.execution_result().expect("Failed to get execution result"); - - println!(); - tracing::info!( - "{}", - "--- VERIFY CONSTRAINTS SUMMARY ------------------------".bright_green().bold() - ); - tracing::info!(" ► Statistics"); - tracing::info!( - " time: {} seconds, steps: {}", - elapsed.as_secs_f32(), - result.steps - ); - - is_busy.store(false, std::sync::atomic::Ordering::SeqCst); - ZiskService::print_waiting_message(&config); - - // Store the stats in stats.json - #[cfg(feature = "stats")] - { - let stats_id = _stats.next_id(); - _stats.add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); - _stats.store_stats(); - } - } - }); - - ( - ZiskResponse::ZiskVerifyConstraintsResponse(ZiskVerifyConstraintsResponse { - base: ZiskBaseResponse { - cmd: "verify_constraints".to_string(), - result: ZiskCmdResult::InProgress, - code: ZiskResultCode::Ok, - msg: None, - node: config.asm_runner_options.world_rank, - }, - server_id: config.server_id.to_string(), - elf_file: config.elf.display().to_string(), - input: request.input.display().to_string(), - }), - Some(handle), - ) - } - pub fn process_handle( - _request: ZiskVerifyConstraintsRequest, - proofman: Arc>, - debug_info: Arc, - ) { - proofman - .verify_proof_constraints_from_lib(&debug_info, false) - .map_err(|e| anyhow::anyhow!("Error verifying proof: {}", e)) - .expect("Failed to generate proof"); - proofman.set_barrier(); - } -} diff --git a/server/src/lib.rs b/server/src/lib.rs deleted file mode 100644 index 3ac5597ba..000000000 --- a/server/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod handler_prove; -mod handler_shutdown; -mod handler_status; -mod handler_verify_constraints; -mod zisk_service; - -pub use handler_prove::*; -pub use handler_shutdown::*; -pub use handler_status::*; -pub use handler_verify_constraints::*; -pub use zisk_service::*; diff --git a/server/src/zisk_service.rs b/server/src/zisk_service.rs deleted file mode 100644 index e23d32fe9..000000000 --- a/server/src/zisk_service.rs +++ /dev/null @@ -1,609 +0,0 @@ -use std::{ - collections::HashMap, - fmt, - io::{BufRead, BufReader, Write}, - net::{TcpListener, TcpStream}, - path::PathBuf, - sync::{atomic::AtomicBool, Arc}, - time::Instant, -}; - -use asm_runner::{AsmRunnerOptions, AsmServices}; -use fields::Goldilocks; -use libloading::{Library, Symbol}; -use proofman::ProofMan; -use proofman_common::{initialize_logger, DebugInfo, ParamsGPU}; -use serde::{Deserialize, Serialize}; -use tracing::error; -use uuid::Uuid; -use zisk_common::{info_file, ZiskLib, ZiskLibInitFn}; - -use anyhow::Result; - -use crate::{ - handler_prove::{ZiskProveRequest, ZiskServiceProveHandler}, - handler_shutdown::ZiskServiceShutdownHandler, - handler_status::{ZiskStatusRequest, ZiskStatusResponse}, - handler_verify_constraints::{ - ZiskServiceVerifyConstraintsHandler, ZiskVerifyConstraintsRequest, - }, - ZiskProveResponse, ZiskServiceStatusHandler, ZiskShutdownRequest, ZiskShutdownResponse, - ZiskVerifyConstraintsResponse, -}; - -pub struct ZiskServerParams { - /// Port number for the server to listen on - pub port: u16, - - /// Path to the ELF file - pub elf: PathBuf, - - /// Path to the witness computation dynamic library - pub witness_lib: PathBuf, - - /// Path to the ASM file (optional) - pub asm: Option, - - /// Path to the ASM ROM file (optional) - pub asm_rom: Option, - - pub asm_port: Option, - - /// Map of custom commits - pub custom_commits_map: HashMap, - - /// Flag indicating whether to use the prebuilt emulator - pub emulator: bool, - - /// Path to the proving key - pub proving_key: PathBuf, - - /// Verbosity level for logging - pub verbose: u8, - - /// Debug information - pub debug_info: DebugInfo, - - /// Time when the server was launched - pub launch_time: Instant, - - /// Unique identifier for the server instance - pub server_id: Uuid, - - pub verify_constraints: bool, - pub aggregation: bool, - pub final_snark: bool, - - pub gpu_params: ParamsGPU, - - pub unlock_mapped_memory: bool, - - pub shared_tables: bool, -} - -#[allow(clippy::too_many_arguments)] -impl ZiskServerParams { - pub fn new( - port: u16, - elf: PathBuf, - witness_lib: PathBuf, - asm: Option, - asm_rom: Option, - asm_port: Option, - custom_commits_map: HashMap, - emulator: bool, - proving_key: PathBuf, - verbose: u8, - debug: DebugInfo, - verify_constraints: bool, - aggregation: bool, - final_snark: bool, - gpu_params: ParamsGPU, - unlock_mapped_memory: bool, - shared_tables: bool, - ) -> Self { - Self { - port, - elf, - witness_lib, - asm, - asm_rom, - asm_port, - custom_commits_map, - emulator, - proving_key, - verbose, - debug_info: debug, - launch_time: Instant::now(), - server_id: Uuid::new_v4(), - verify_constraints, - aggregation, - final_snark, - gpu_params, - unlock_mapped_memory, - shared_tables, - } - } -} - -pub struct ServerConfig { - /// Port number for the server to listen on - pub port: u16, - - /// Path to the ELF file - pub elf: PathBuf, - - /// Path to the witness computation dynamic library - pub witness_lib: PathBuf, - - /// Path to the ASM file (optional) - pub asm: Option, - - /// Path to the ASM ROM file (optional) - pub asm_rom: Option, - - /// Map of custom commits - pub custom_commits_map: HashMap, - - /// Flag indicating whether to use the prebuilt emulator - pub emulator: bool, - - /// Path to the proving key - pub proving_key: PathBuf, - - /// Verbosity level for logging - pub verbose: u8, - - /// Debug information - pub debug_info: Arc, - - /// Time when the server was launched - pub launch_time: Instant, - - /// Unique identifier for the server instance - pub server_id: Uuid, - - /// Additional options for the ASM runner - pub asm_runner_options: AsmRunnerOptions, - - pub verify_constraints: bool, - pub aggregation: bool, - pub final_snark: bool, - - pub gpu_params: ParamsGPU, - - pub shared_tables: bool, -} - -#[allow(clippy::too_many_arguments)] -impl ServerConfig { - pub fn new( - port: u16, - elf: PathBuf, - witness_lib: PathBuf, - asm: Option, - asm_rom: Option, - custom_commits_map: HashMap, - emulator: bool, - proving_key: PathBuf, - verbose: u8, - debug: DebugInfo, - asm_runner_options: AsmRunnerOptions, - verify_constraints: bool, - aggregation: bool, - final_snark: bool, - gpu_params: ParamsGPU, - shared_tables: bool, - ) -> Self { - Self { - port, - elf, - witness_lib, - asm, - asm_rom, - custom_commits_map, - emulator, - proving_key, - verbose, - debug_info: Arc::new(debug), - launch_time: Instant::now(), - server_id: Uuid::new_v4(), - asm_runner_options, - verify_constraints, - aggregation, - final_snark, - gpu_params, - shared_tables, - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "command", rename_all = "snake_case")] -pub enum ZiskRequest { - Status { - #[serde(flatten)] - payload: ZiskStatusRequest, - }, - Shutdown { - #[serde(flatten)] - payload: ZiskShutdownRequest, - }, - Prove { - #[serde(flatten)] - payload: ZiskProveRequest, - }, - VerifyConstraints { - #[serde(flatten)] - payload: ZiskVerifyConstraintsRequest, - }, -} - -impl fmt::Display for ZiskRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let variant = match self { - ZiskRequest::Status { .. } => "Status", - ZiskRequest::Shutdown { .. } => "Shutdown", - ZiskRequest::Prove { .. } => "Prove", - ZiskRequest::VerifyConstraints { .. } => "VerifyConstraints", - }; - write!(f, "{variant}") - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum ZiskCmdResult { - Ok, - Error, - InProgress, - Busy, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u32)] -pub enum ZiskResultCode { - Ok = 0, - Error = 1001, - InvalidRequest = 1002, - Busy = 1003, -} - -// Serialize as a number -impl Serialize for ZiskResultCode { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_u32(*self as u32) - } -} - -// Deserialize from a number -impl<'de> Deserialize<'de> for ZiskResultCode { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let value = u32::deserialize(deserializer)?; - match value { - 0 => Ok(ZiskResultCode::Ok), - 1001 => Ok(ZiskResultCode::Error), - 1002 => Ok(ZiskResultCode::InvalidRequest), - 1003 => Ok(ZiskResultCode::Busy), - _ => Err(serde::de::Error::custom(format!("Unknown ZiskResultCode: {value}"))), - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskBaseResponse { - pub cmd: String, - pub result: ZiskCmdResult, - pub code: ZiskResultCode, - pub node: i32, - - #[serde(skip_serializing_if = "Option::is_none")] - pub msg: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ZiskInvalidRequestResponse { - #[serde(flatten)] - pub base: ZiskBaseResponse, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(tag = "zisk_response", rename_all = "snake_case")] -pub enum ZiskResponse { - ZiskStatusResponse(ZiskStatusResponse), - ZiskShutdownResponse(ZiskShutdownResponse), - ZiskProveResponse(ZiskProveResponse), - ZiskVerifyConstraintsResponse(ZiskVerifyConstraintsResponse), - ZiskErrorResponse(ZiskBaseResponse), - ZiskInvalidRequestResponse { base: ZiskBaseResponse }, -} - -pub struct ZiskService { - config: Arc, - // It is important to keep the witness_lib declaration before the proofman declaration - // to ensure that the witness library is dropped before the proofman. - witness_lib: Arc>>, - proofman: Arc>, - asm_services: Option, - is_busy: Arc, - pending_handles: Vec>, -} - -impl ZiskService { - pub fn new(params: &ZiskServerParams) -> Result { - info_file!("Starting asm microservices..."); - let library = - unsafe { Library::new(params.witness_lib.clone()).expect("Failed to load library") }; - let witness_lib_constructor: Symbol> = - unsafe { library.get(b"init_library").expect("Failed to get symbol") }; - - let unlock_mapped_memory = params.unlock_mapped_memory; - - let mut witness_lib = witness_lib_constructor( - params.verbose.into(), - params.elf.clone(), - params.asm.clone(), - params.asm_rom.clone(), - params.asm_port, - unlock_mapped_memory, - params.shared_tables, - ) - .expect("Failed to initialize witness library"); - - let proofman = ProofMan::::new( - params.proving_key.clone(), - params.custom_commits_map.clone(), - params.verify_constraints, - params.aggregation, - params.final_snark, - params.gpu_params.clone(), - params.verbose.into(), - witness_lib.get_packed_info(), - ) - .expect("Failed to initialize proofman"); - - let world_rank = proofman.get_world_rank(); - let local_rank = proofman.get_local_rank(); - - initialize_logger(params.verbose.into(), Some(world_rank)); - - let port = params.port + local_rank as u16; - - let asm_runner_options = AsmRunnerOptions::new() - .with_verbose(params.verbose > 0) - .with_base_port(params.asm_port) - .with_world_rank(world_rank) - .with_local_rank(local_rank) - .with_unlock_mapped_memory(params.unlock_mapped_memory); - - let asm_services = if params.emulator { - None - } else { - let asm_services = AsmServices::new(world_rank, local_rank, params.asm_port); - asm_services - .start_asm_services(params.asm.as_ref().unwrap(), asm_runner_options.clone())?; - Some(asm_services) - }; - - proofman.register_witness(witness_lib.as_mut(), library)?; - - let witness_lib = Arc::new(witness_lib); - - let config = ServerConfig::new( - port, - params.elf.clone(), - params.witness_lib.clone(), - params.asm.clone(), - params.asm_rom.clone(), - params.custom_commits_map.clone(), - params.emulator, - params.proving_key.clone(), - params.verbose, - params.debug_info.clone(), - asm_runner_options, - params.verify_constraints, - params.aggregation, - params.final_snark, - params.gpu_params.clone(), - params.shared_tables, - ); - - Ok(Self { - config: Arc::new(config), - proofman: Arc::new(proofman), - witness_lib, - asm_services, - is_busy: Arc::new(AtomicBool::new(false)), - pending_handles: Vec::new(), - }) - } - - pub fn print_waiting_message(config: &ServerConfig) { - info_file!( - "ZisK Server waiting for requests on port {} for ELF '{}'", - config.port, - config.elf.display() - ); - } - - pub fn run(&mut self) -> std::io::Result<()> { - if self.proofman.rank() == Some(0) || self.proofman.rank().is_none() { - let listener = TcpListener::bind(("127.0.0.1", self.config.port))?; - Self::print_waiting_message(&self.config); - - for stream in listener.incoming() { - match stream { - Ok(stream) => { - let config = Arc::clone(&self.config); - if let Ok(should_shutdown) = self.handle_client(stream, config) { - if should_shutdown { - info_file!("{}", "Shutdown signal received. Exiting."); - break; - } - } - } - Err(e) => error!("Connection failed: {}", e), - } - } - } else { - // Other MPI ranks just wait for rank 0 instructions - loop { - self.receive_request()?; - } - } - - Ok(()) - } - - fn handle_client( - &mut self, - mut stream: TcpStream, - config: Arc, - ) -> std::io::Result { - let mut reader = BufReader::new(&stream); - let mut line = String::new(); - - reader.read_line(&mut line)?; - - let request: ZiskRequest = match serde_json::from_str(&line) { - Ok(req) => req, - Err(e) => { - let response = ZiskResponse::ZiskInvalidRequestResponse { - base: ZiskBaseResponse { - cmd: "invalid_request".to_string(), - result: ZiskCmdResult::Error, - code: ZiskResultCode::InvalidRequest, - msg: Some(format!("Invalid request format or data. {e}")), - node: config.asm_runner_options.world_rank, - }, - }; - Self::send_json(&mut stream, &response)?; - return Ok(false); - } - }; - - info_file!("Received '{}' request", request); - - let mut must_shutdown = false; - - if self.is_busy.load(std::sync::atomic::Ordering::SeqCst) - && !matches!(request, ZiskRequest::Status { .. }) - { - let response = ZiskResponse::ZiskErrorResponse(ZiskBaseResponse { - cmd: "busy".to_string(), - result: ZiskCmdResult::InProgress, - code: ZiskResultCode::Busy, - msg: Some("Server is busy, please try again later.".to_string()), - node: config.asm_runner_options.world_rank, - }); - Self::send_json(&mut stream, &response)?; - return Ok(false); - } - - // Wait for all pending handles to finish - for handle in self.pending_handles.drain(..) { - handle.join().expect("Failed to join thread"); - } - - let (response, handle) = match request { - ZiskRequest::Status { payload } => { - let result = - ZiskServiceStatusHandler::handle(&config, payload, self.is_busy.clone()); - Self::print_waiting_message(&config); - result - } - ZiskRequest::Shutdown { payload } => { - must_shutdown = true; - ZiskServiceShutdownHandler::handle(&config, payload, self.asm_services.as_ref()) - } - ZiskRequest::VerifyConstraints { payload } => { - let mut bytes = Vec::new(); - bytes.push(0u8); // option 0 for verify constraints - let serialized: Vec = - serde_json::to_vec(&payload).expect("Failed to serialize payload"); - bytes.extend_from_slice(&serialized); - self.proofman.mpi_broadcast(&mut bytes); - - ZiskServiceVerifyConstraintsHandler::handle( - config.clone(), - payload, - self.witness_lib.clone(), - self.proofman.clone(), - self.is_busy.clone(), - self.config.debug_info.clone(), - ) - } - ZiskRequest::Prove { payload } => { - let mut bytes = Vec::new(); - bytes.push(1u8); // option 1 for prove - let serialized: Vec = - serde_json::to_vec(&payload).expect("Failed to serialize payload"); - bytes.extend_from_slice(&serialized); - self.proofman.mpi_broadcast(&mut bytes); - - ZiskServiceProveHandler::handle( - config.clone(), - payload, - self.witness_lib.clone(), - self.proofman.clone(), - self.is_busy.clone(), - ) - } - }; - if let Some(handle) = handle { - self.pending_handles.push(handle); - } - - Self::send_json(&mut stream, &response)?; - Ok(must_shutdown) - } - - fn send_json(stream: &mut TcpStream, response: &ZiskResponse) -> std::io::Result<()> { - let json = serde_json::to_string(response)?; - stream.write_all(json.as_bytes())?; - stream.flush() - } - - fn receive_request(&self) -> std::io::Result<()> { - let mut bytes: Vec = Vec::new(); - self.proofman.mpi_broadcast(&mut bytes); - - // extract byte 0 to decide the option - let option = bytes.first().cloned(); - match option { - Some(0) => { - info_file!("Received process 'VerifyConstraints' request"); - // Deserialize the rest of bytes into ZiskVerifyConstraintsRequest - let payload: ZiskVerifyConstraintsRequest = - serde_json::from_slice(&bytes[1..]).expect("Failed to deserialize payload"); - ZiskServiceVerifyConstraintsHandler::process_handle( - payload, - self.proofman.clone(), - self.config.debug_info.clone(), - ); - } - Some(1) => { - info_file!("Received process 'Prove' request"); - // Prove request - // Deserialize the rest of bytes into ZiskProveRequest - let payload: ZiskProveRequest = - serde_json::from_slice(&bytes[1..]).expect("Failed to deserialize payload"); - ZiskServiceProveHandler::process_handle(payload, self.proofman.clone()); - } - _ => { - info_file!( - "Rank {} received unknown request: {:?}", - self.proofman.rank().unwrap_or(0), - option - ); - } - } - Ok(()) - } -} diff --git a/witness-computation/Cargo.toml b/witness-computation/Cargo.toml index 1d71850b3..4df0a3926 100644 --- a/witness-computation/Cargo.toml +++ b/witness-computation/Cargo.toml @@ -12,6 +12,8 @@ crate-type = ["dylib"] [dependencies] executor = { workspace = true } +asm-runner = { workspace = true } +precompiles-hints = { workspace = true } sm-arith = { workspace = true } sm-binary = { workspace = true } sm-main = { workspace = true } diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 4f476e6cd..d2d007f57 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -4,7 +4,10 @@ //! This module leverages `WitnessLibrary` to orchestrate the setup of state machines, //! program conversion, and execution pipelines to generate required witnesses. -use executor::{StateMachines, StaticSMBundle, ZiskExecutor}; +use asm_runner::HintsShmem; +use executor::{ + EmulatorAsm, EmulatorKind, EmulatorRust, StateMachines, StaticSMBundle, ZiskExecutor, +}; use fields::{Goldilocks, PrimeField64}; use pil_std_lib::Std; use precomp_arith_eq::ArithEqManager; @@ -12,6 +15,7 @@ use precomp_arith_eq_384::ArithEq384Manager; use precomp_big_int::Add256Manager; use precomp_keccakf::KeccakfManager; use precomp_sha256f::Sha256fManager; +use precompiles_hints::HintsProcessor; use proofman::register_std; use proofman_common::{PackedInfo, ProofmanResult}; use sm_arith::ArithSM; @@ -20,7 +24,10 @@ use sm_mem::Mem; use sm_rom::RomSM; use std::{collections::HashMap, path::PathBuf, sync::Arc}; use witness::{WitnessLibrary, WitnessManager}; -use zisk_common::{io::ZiskStdin, ExecutorStats, ZiskExecutionResult, ZiskLib, ZiskWitnessLibrary}; +use zisk_common::{ + io::{ZiskStdin, ZiskStream}, + ExecutorStats, ZiskExecutionResult, ZiskLib, ZiskWitnessLibrary, +}; use zisk_core::{Riscv2zisk, CHUNK_SIZE}; #[cfg(feature = "packed")] use zisk_pil::PACKED_INFO; @@ -36,8 +43,8 @@ use anyhow::Result; pub struct WitnessLib { elf_path: PathBuf, - asm_path: Option, - asm_rom_path: Option, + asm_mt_path: Option, + asm_rh_path: Option, executor: Option>>, chunk_size: u64, base_port: Option, @@ -51,8 +58,8 @@ pub struct WitnessLib { fn init_library( verbose_mode: proofman_common::VerboseMode, elf_path: PathBuf, - asm_path: Option, - asm_rom_path: Option, + asm_mt_path: Option, + asm_rh_path: Option, base_port: Option, unlock_mapped_memory: bool, shared_tables: bool, @@ -61,8 +68,8 @@ fn init_library( let result = Box::new(WitnessLib { elf_path, - asm_path, - asm_rom_path, + asm_mt_path, + asm_rh_path, executor: None, chunk_size, base_port, @@ -89,6 +96,8 @@ impl WitnessLibrary for WitnessLib { /// # Panics /// Panics if the `Riscv2zisk` conversion fails or if required paths cannot be resolved. fn register_witness(&mut self, wcm: &WitnessManager) -> ProofmanResult<()> { + assert_eq!(self.asm_mt_path.is_some(), self.asm_rh_path.is_some()); + let world_rank = wcm.get_world_rank(); let local_rank = wcm.get_local_rank(); @@ -105,7 +114,7 @@ impl WitnessLibrary for WitnessLib { let std = Std::new(wcm.get_pctx(), wcm.get_sctx(), self.shared_tables)?; register_std(wcm, &std); - let rom_sm = RomSM::new(zisk_rom.clone(), self.asm_rom_path.clone()); + let rom_sm = RomSM::new(zisk_rom.clone(), self.asm_rh_path.clone()); let binary_sm = BinarySM::new(std.clone()); let arith_sm = ArithSM::new(std.clone()); let mem_sm = Mem::new(std.clone()); @@ -133,7 +142,7 @@ impl WitnessLibrary for WitnessLib { ]; let sm_bundle = StaticSMBundle::new( - self.asm_path.is_some(), + self.asm_mt_path.is_some(), vec![ (vec![(ZISK_AIRGROUP_ID, ROM_AIR_IDS[0])], StateMachines::RomSM(rom_sm.clone())), (mem_instances, StateMachines::MemSM(mem_sm.clone())), @@ -166,23 +175,42 @@ impl WitnessLibrary for WitnessLib { ], ); - // Step 5: Create the executor and register the secondary state machines - let executor: ZiskExecutor = ZiskExecutor::new( - self.elf_path.clone(), - self.asm_path.clone(), - self.asm_rom_path.clone(), + let is_asm_emulator = self.asm_mt_path.is_some(); + let emulator = if is_asm_emulator { + EmulatorKind::Asm(EmulatorAsm::new( + zisk_rom.clone(), + self.asm_mt_path.clone(), + world_rank, + local_rank, + self.base_port, + self.unlock_mapped_memory, + self.chunk_size, + Some(rom_sm.clone()), + )) + } else { + EmulatorKind::Rust(EmulatorRust::new(zisk_rom.clone(), self.chunk_size)) + }; + + // Create hints pipeline with null hints stream initially. + let hints_shmem = HintsShmem::new(self.base_port, local_rank, self.unlock_mapped_memory) + .expect("Failed to create HintsShmem"); + // let hints_shmem = HintsFile::new("test_modexp_results.bin".to_string()) + // .expect("Failed to create HintsFile"); + + let hints_processor = HintsProcessor::builder(hints_shmem) + .build() + .expect("Failed to create PrecompileHintsProcessor"); + + let hints_stream = ZiskStream::new(hints_processor); + + let executor = Arc::new(ZiskExecutor::new( zisk_rom, std, sm_bundle, - Some(rom_sm.clone()), self.chunk_size, - world_rank, - local_rank, - self.base_port, - self.unlock_mapped_memory, - ); - - let executor = Arc::new(executor); + emulator, + hints_stream, + )); // Step 7: Register the executor as a component in the Witness Manager wcm.register_component(executor.clone()); diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index 4d752f745..b97d9609c 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -174,7 +174,7 @@ pub fn secp256k1_is_on_curve(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut /// /// Note: There are no (non-infinity) points of order 2 in Secp256k1. /// All (non-infinity) points are of prime order N. -fn secp256k1_scalar_mul( +pub fn secp256k1_scalar_mul( k: &[u64; 4], p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec, @@ -926,7 +926,7 @@ pub unsafe extern "C" fn secp256k1_decompress_c( } /// # Safety -/// - `p_ptr` must point to 12 u64s (projective point) +/// - `p_ptr` must point to 12 u64s (jacobian point) /// - `out_ptr` must point to at least 8 u64s #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_secp256k1_to_affine_c")] diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs index 0743f1898..720e5f3a8 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs @@ -31,13 +31,14 @@ pub fn secp256k1_ecdsa_verify( s: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec, ) -> bool { - // Ecdsa verification computes P = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]PK - // We can equivalently hint p, check it's correct and verify that - // [z]G + [r]pk + [-s]P == 𝒪, + // Ecdsa verification computes (x, y) = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]PK + // and checks that x ≡ r (mod n) + // We can equivalently hint y, and verify that + // [z]G + [r]PK + [-s](r,y) == 𝒪, // saving us from expensive fn arithmetic // Hint the result - let p = fcall_secp256k1_ecdsa_verify( + let coords = fcall_secp256k1_ecdsa_verify( pk, z, r, @@ -45,15 +46,16 @@ pub fn secp256k1_ecdsa_verify( #[cfg(feature = "hints")] hints, ); + let point = [r[0], r[1], r[2], r[3], coords[4], coords[5], coords[6], coords[7]]; // Check the recovered point is valid assert!(secp256k1_is_on_curve( - &p, + &point, #[cfg(feature = "hints")] hints, )); // Note: Identity point would be raised here - // Check that [z]G + [r]pk + [-s]P == 𝒪 + // Check that [z]G + [r]PK + [-s](r,y) == 𝒪 let neg_s = secp256k1_fn_neg( s, #[cfg(feature = "hints")] @@ -64,13 +66,11 @@ pub fn secp256k1_ecdsa_verify( r, &neg_s, pk, - &p, + &point, #[cfg(feature = "hints")] hints, ) .is_none() - .then_some(()) - .is_some() } // ==================== C FFI Functions ==================== diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs index 5fc5406f6..5b7736c7f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs @@ -192,6 +192,8 @@ pub fn secp256k1_fp_sqrt( } } +// ==================== C FFI Functions ==================== + /// # Safety /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs index 572206bfb..365f571c8 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs @@ -126,6 +126,8 @@ pub fn secp256k1_fn_inv(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec< x_inv } +// ==================== C FFI Functions ==================== + /// # Safety /// - `x_ptr` must point to 4 u64s /// - `out_ptr` must point to at least 4 u64s From a76f4a0d569d26e254e927a05874e0b99b06818f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 08:31:27 +0000 Subject: [PATCH 254/782] wip --- executor/src/emu_asm.rs | 27 ++++++++++++++++----------- executor/src/emu_rust.rs | 4 ++-- executor/src/executor.rs | 14 +++++++++----- executor/src/lib.rs | 4 ++-- witness-computation/src/zisk_lib.rs | 5 ++++- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index b70cfdba7..ac2062a41 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -49,11 +49,15 @@ pub struct EmulatorAsm { /// Optional ROM state machine, used for assembly ROM execution. rom_sm: Option>, - asm_shmem_mt: Arc>>, - asm_shmem_mo: Arc>>, + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + asm_shmem_mt: Arc>, + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + asm_shmem_mo: Arc>, + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] asm_shmem_rh: Arc>>, /// Shared memory writers for each assembly service. + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] shmem_input_writer: [Arc>>; AsmServices::SERVICES.len()], } @@ -69,9 +73,6 @@ impl EmulatorAsm { chunk_size: u64, rom_sm: Option>, ) -> Self { - #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] - let (asm_shmem_mt, asm_shmem_mo) = (None, None); - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] let asm_shmem_mt = PreloadedMT::new(local_rank, base_port, unlock_mapped_memory) .expect("Failed to create PreloadedMT"); @@ -87,9 +88,13 @@ impl EmulatorAsm { unlock_mapped_memory, chunk_size, rom_sm, - asm_shmem_mt: Arc::new(Mutex::new(Some(asm_shmem_mt))), - asm_shmem_mo: Arc::new(Mutex::new(Some(asm_shmem_mo))), + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + asm_shmem_mt: Arc::new(Mutex::new(asm_shmem_mt)), + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + asm_shmem_mo: Arc::new(Mutex::new(asm_shmem_mo)), + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] asm_shmem_rh: Arc::new(Mutex::new(None)), + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] shmem_input_writer: std::array::from_fn(|_| Arc::new(Mutex::new(None))), } } @@ -112,7 +117,7 @@ impl EmulatorAsm { /// * `ZiskExecutionResult` - The result of executing the ZisK ROM. #[allow(clippy::type_complexity)] pub fn execute( - &self, + &mut self, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, @@ -198,7 +203,7 @@ impl EmulatorAsm { let asm_shmem_mo = self.asm_shmem_mo.clone(); move || { AsmRunnerMO::run( - asm_shmem_mo.lock().unwrap().as_mut().unwrap(), + &mut asm_shmem_mo.lock().unwrap(), ZiskExecutor::::MAX_NUM_STEPS, chunk_size, world_rank, @@ -339,7 +344,7 @@ impl EmulatorAsm { }); let (asm_runner_mt, mut data_buses) = AsmRunnerMT::run_and_count( - self.asm_shmem_mt.lock().unwrap().as_mut().unwrap(), + &mut self.asm_shmem_mt.lock().unwrap(), ZiskExecutor::::MAX_NUM_STEPS, self.chunk_size, task_factory, @@ -381,7 +386,7 @@ impl EmulatorAsm { impl crate::Emulator for EmulatorAsm { fn execute( - &self, + &mut self, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index ff3c382a3..b861f400f 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -54,7 +54,7 @@ impl EmulatorRust { /// * `None`. /// * `ZiskExecutionResult` - Summary of the emulator execution, including the total number of steps. pub fn execute( - &self, + &mut self, stdin: &Mutex, _pctx: &ProofCtx, sm_bundle: &StaticSMBundle, @@ -184,7 +184,7 @@ impl EmulatorRust { impl crate::Emulator for EmulatorRust { fn execute( - &self, + &mut self, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 7a94f134e..c02cb8939 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -78,7 +78,7 @@ pub struct ZiskExecutor { stdin: Mutex, /// The emulator backend used for execution. - emulator: EmulatorKind, + emulator: Mutex, /// Chunk size for processing. chunk_size: u64, @@ -138,7 +138,7 @@ impl ZiskExecutor { ) -> Self { Self { stdin: Mutex::new(ZiskStdin::null()), - emulator, + emulator: Mutex::new(emulator), chunk_size, hints_stream: Mutex::new(hints_stream), zisk_rom, @@ -757,7 +757,7 @@ impl WitnessComponent for ZiskExecutor { timer_start_info!(COMPUTE_MINIMAL_TRACE); let (min_traces, main_count, mut secn_count, handle_mo, execution_result) = - self.emulator.execute( + self.emulator.lock().unwrap().execute( &self.stdin, &pctx, &self.sm_bundle, @@ -961,6 +961,8 @@ impl WitnessComponent for ZiskExecutor { #[cfg(feature = "stats")] self.stats.add_stat(0, parent_stats_id, "CALCULATE_WITNESS", 0, ExecutorStatsEvent::Begin); + let is_asm_emulator = self.emulator.lock().unwrap().is_asm_emulator(); + let pool = create_pool(n_cores); pool.install(|| -> ProofmanResult<()> { for &global_id in global_ids { @@ -986,7 +988,7 @@ impl WitnessComponent for ZiskExecutor { InstanceType::Instance => { if !self.collectors_by_instance.read().unwrap().contains_key(&global_id) { - if air_id == ROM_AIR_IDS[0] && self.emulator.is_asm_emulator() { + if air_id == ROM_AIR_IDS[0] && is_asm_emulator { let stats = Stats { airgroup_id, air_id, @@ -1069,6 +1071,8 @@ impl WitnessComponent for ZiskExecutor { } let secn_instances_guard = self.secn_instances.read().unwrap(); + let is_asm_emulator = self.emulator.lock().unwrap().is_asm_emulator(); + let mut secn_instances = HashMap::new(); for &global_id in global_ids { let (airgroup_id, air_id) = @@ -1076,7 +1080,7 @@ impl WitnessComponent for ZiskExecutor { if MAIN_AIR_IDS.contains(&air_id) { pctx.set_witness_ready(global_id, false); } else if air_id == ROM_AIR_IDS[0] { - if self.emulator.is_asm_emulator() { + if is_asm_emulator { pctx.set_witness_ready(global_id, false); } else { let secn_instance = &secn_instances_guard[&global_id]; diff --git a/executor/src/lib.rs b/executor/src/lib.rs index a6002b862..97c5b7265 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -27,7 +27,7 @@ use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, ZiskExecutionResult}; pub trait Emulator: Send + Sync { /// Execute the emulator fn execute( - &self, + &mut self, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, @@ -57,7 +57,7 @@ impl EmulatorKind { impl Emulator for EmulatorKind { fn execute( - &self, + &mut self, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index d2d007f57..69452acf7 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -23,6 +23,7 @@ use sm_binary::BinarySM; use sm_mem::Mem; use sm_rom::RomSM; use std::{collections::HashMap, path::PathBuf, sync::Arc}; +use tracing::debug; use witness::{WitnessLibrary, WitnessManager}; use zisk_common::{ io::{ZiskStdin, ZiskStream}, @@ -177,9 +178,10 @@ impl WitnessLibrary for WitnessLib { let is_asm_emulator = self.asm_mt_path.is_some(); let emulator = if is_asm_emulator { + debug!("Using ASM emulator"); EmulatorKind::Asm(EmulatorAsm::new( zisk_rom.clone(), - self.asm_mt_path.clone(), + self.asm_mt_path.as_ref().unwrap().clone(), world_rank, local_rank, self.base_port, @@ -188,6 +190,7 @@ impl WitnessLibrary for WitnessLib { Some(rom_sm.clone()), )) } else { + debug!("Using Rust emulator"); EmulatorKind::Rust(EmulatorRust::new(zisk_rom.clone(), self.chunk_size)) }; From 42d18ed79430635ea393cd72c30cb912304346f3 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 08:39:29 +0000 Subject: [PATCH 255/782] Added StreamSink generic parameter to executor --- common/src/io/stream/zisk_stream.rs | 10 +++++----- executor/src/emu_asm.rs | 8 ++++---- executor/src/emu_rust.rs | 12 ++++-------- executor/src/executor.rs | 18 +++++++++--------- precompiles/hints/src/hints_processor.rs | 12 ++++++------ witness-computation/src/zisk_lib.rs | 2 +- 6 files changed, 29 insertions(+), 33 deletions(-) diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index 4fb42b29b..69a10f8dc 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -7,7 +7,7 @@ use std::thread::{self, JoinHandle}; use crate::io::{StreamRead, StreamSource}; -pub trait StreamProcessor { +pub trait StreamProcessor: Send + Sync + 'static { /// Process data and return the processed result along with a flag indicating if CTRL_END was encountered. /// /// # Returns @@ -25,7 +25,7 @@ pub trait StreamProcessor { /// # Returns /// * `Ok(())` - If hints were successfully submitted /// * `Err` - If submission fails -pub trait StreamSink { +pub trait StreamSink: Send + Sync + 'static { fn submit(&self, processed: Vec) -> anyhow::Result<()>; } @@ -35,7 +35,7 @@ enum ThreadCommand { } /// ZiskStream struct manages the processing of precompile hints and writing them to shared memory. -pub struct ZiskStream { +pub struct ZiskStream { /// The hints processor used to process hints before writing. hints_processor: Arc, @@ -46,7 +46,7 @@ pub struct ZiskStream { thread_handle: Option>, } -impl ZiskStream { +impl ZiskStream { /// Create a new ZiskStream with the given processor. /// /// # Arguments @@ -154,7 +154,7 @@ impl ZiskStream { } } -impl Drop for ZiskStream { +impl Drop for ZiskStream { fn drop(&mut self) { self.stop_thread(); } diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index ac2062a41..180b5ff4a 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -6,7 +6,7 @@ use std::{ }; use crate::{ - DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, ZiskExecutor, + DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, MAX_NUM_STEPS, }; use asm_runner::{ write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, @@ -204,7 +204,7 @@ impl EmulatorAsm { move || { AsmRunnerMO::run( &mut asm_shmem_mo.lock().unwrap(), - ZiskExecutor::::MAX_NUM_STEPS, + MAX_NUM_STEPS, chunk_size, world_rank, local_rank, @@ -226,7 +226,7 @@ impl EmulatorAsm { std::thread::spawn(move || { AsmRunnerRH::run( &mut asm_shmem_rh.lock().unwrap(), - ZiskExecutor::::MAX_NUM_STEPS, + MAX_NUM_STEPS, world_rank, local_rank, base_port, @@ -345,7 +345,7 @@ impl EmulatorAsm { let (asm_runner_mt, mut data_buses) = AsmRunnerMT::run_and_count( &mut self.asm_shmem_mt.lock().unwrap(), - ZiskExecutor::::MAX_NUM_STEPS, + MAX_NUM_STEPS, self.chunk_size, task_factory, self.world_rank, diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index b861f400f..df872bcf8 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -18,7 +18,7 @@ use zisk_core::ZiskRom; use ziskemu::{EmuOptions, ZiskEmulator}; use crate::{ - DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, ZiskExecutor, + DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, MAX_NUM_STEPS, }; pub struct EmulatorRust { @@ -67,7 +67,7 @@ impl EmulatorRust { Option>, ZiskExecutionResult, ) { - let min_traces = self.run_emulator::(Self::NUM_THREADS, &mut stdin.lock().unwrap()); + let min_traces = self.run_emulator(Self::NUM_THREADS, &mut stdin.lock().unwrap()); // Store execute steps let steps = if let MinimalTraces::EmuTrace(min_traces) = &min_traces { @@ -85,18 +85,14 @@ impl EmulatorRust { (min_traces, main_count, secn_count, None, execution_result) } - fn run_emulator( - &self, - num_threads: usize, - stdin: &mut ZiskStdin, - ) -> MinimalTraces { + fn run_emulator(&self, num_threads: usize, stdin: &mut ZiskStdin) -> MinimalTraces { // Call emulate with these options let input_data = stdin.read(); // Settings for the emulator let emu_options = EmuOptions { chunk_size: Some(self.chunk_size), - max_steps: ZiskExecutor::::MAX_NUM_STEPS, + max_steps: MAX_NUM_STEPS, ..EmuOptions::default() }; diff --git a/executor/src/executor.rs b/executor/src/executor.rs index c02cb8939..e387777f5 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -28,7 +28,7 @@ use proofman_util::{timer_start_info, timer_stop_and_log_info}; use sm_rom::RomInstance; use std::sync::atomic::{AtomicUsize, Ordering}; use witness::WitnessComponent; -use zisk_common::io::{StreamSource, ZiskStdin, ZiskStream}; +use zisk_common::io::{StreamSink, StreamSource, ZiskStdin, ZiskStream}; use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; @@ -71,9 +71,12 @@ enum MinimalTraceExecutionMode { pub type StreamHintsShmem = ZiskStream>; pub type StreamHintsFile = ZiskStream>; +/// The maximum number of steps to execute in the emulator or assembly runner. +pub const MAX_NUM_STEPS: u64 = 1 << 32; + /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. -pub struct ZiskExecutor { +pub struct ZiskExecutor { /// Standard input for the ZisK program execution. stdin: Mutex, @@ -84,7 +87,7 @@ pub struct ZiskExecutor { chunk_size: u64, /// Pipeline for handling precompile hints. - hints_stream: Mutex, + hints_stream: Mutex>>, // hints_stream: Mutex, /// ZisK ROM, a binary file containing the ZisK program to be executed. zisk_rom: Arc, @@ -119,10 +122,7 @@ pub struct ZiskExecutor { stats: ExecutorStatsHandle, } -impl ZiskExecutor { - /// The maximum number of steps to execute in the emulator or assembly runner. - pub const MAX_NUM_STEPS: u64 = 1 << 32; - +impl ZiskExecutor { /// Creates a new instance of the `ZiskExecutor`. /// /// # Arguments @@ -134,7 +134,7 @@ impl ZiskExecutor { sm_bundle: StaticSMBundle, chunk_size: u64, emulator: EmulatorKind, - hints_stream: StreamHintsShmem, + hints_stream: ZiskStream>, ) -> Self { Self { stdin: Mutex::new(ZiskStdin::null()), @@ -725,7 +725,7 @@ impl ZiskExecutor { } } -impl WitnessComponent for ZiskExecutor { +impl WitnessComponent for ZiskExecutor { /// Executes the ZisK ROM program and calculate the plans for main and secondary state machines. /// /// # Arguments diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index ed6ba8401..a2a4780a5 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -59,14 +59,14 @@ impl HintProcessorState { pub type CustomHintHandler = Arc Result> + Send + Sync>; /// Builder for configuring and constructing a [`HintsProcessor`]. -pub struct HintsProcessorBuilder { +pub struct HintsProcessorBuilder { hints_sink: HS, num_threads: usize, enable_stats: bool, custom_handlers: HashMap, } -impl HintsProcessorBuilder { +impl HintsProcessorBuilder { /// Sets the number of worker threads in the thread pool. pub fn num_threads(mut self, num_threads: usize) -> Self { self.num_threads = num_threads; @@ -143,7 +143,7 @@ impl HintsProcessorBuilder { /// This struct provides methods to parse and process a stream of concatenated /// hints, using a dedicated Rayon thread pool for parallel processing while /// preserving the original order of results. -pub struct HintsProcessor { +pub struct HintsProcessor { /// The thread pool used for parallel hint processing. pool: ThreadPool, @@ -166,7 +166,7 @@ pub struct HintsProcessor { custom_handlers: Arc>, } -impl HintsProcessor { +impl HintsProcessor { const DEFAULT_NUM_THREADS: usize = 1; /// Creates a builder for configuring a [`HintsProcessor`]. @@ -636,7 +636,7 @@ impl HintsProcessor { } } -impl Drop for HintsProcessor { +impl Drop for HintsProcessor { fn drop(&mut self) { // Signal drainer thread to shut down self.state.shutdown.store(true, Ordering::Release); @@ -651,7 +651,7 @@ impl Drop for HintsProcessor { } } -impl StreamProcessor for HintsProcessor { +impl StreamProcessor for HintsProcessor { fn process(&self, data: &[u64], first_batch: bool) -> Result { self.process_hints(data, first_batch) } diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 69452acf7..66e6b18d0 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -46,7 +46,7 @@ pub struct WitnessLib { elf_path: PathBuf, asm_mt_path: Option, asm_rh_path: Option, - executor: Option>>, + executor: Option>>, chunk_size: u64, base_port: Option, unlock_mapped_memory: bool, From 20b4260b0c589817c5736181c4dffe1a09b19cf2 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:45:46 +0000 Subject: [PATCH 256/782] Replace generic type parameters with dynamic dispatch for ZiskStream, HintsProcessor, and ZiskExecutor to simplify the API. --- common/src/io/stream/zisk_stream.rs | 19 ++++++++------ distributed/crates/worker/src/worker.rs | 2 +- executor/src/executor.rs | 33 +++++++++++++----------- precompiles/hints/src/hints_processor.rs | 32 +++++++++++------------ witness-computation/src/zisk_lib.rs | 31 +++++++++++++++------- 5 files changed, 67 insertions(+), 50 deletions(-) diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index 69a10f8dc..17782afb1 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -35,9 +35,9 @@ enum ThreadCommand { } /// ZiskStream struct manages the processing of precompile hints and writing them to shared memory. -pub struct ZiskStream { +pub struct ZiskStream { /// The hints processor used to process hints before writing. - hints_processor: Arc, + hints_processor: Arc, /// Channel sender to communicate with the background thread. tx: Option>, @@ -46,7 +46,7 @@ pub struct ZiskStream { thread_handle: Option>, } -impl ZiskStream { +impl ZiskStream { /// Create a new ZiskStream with the given processor. /// /// # Arguments @@ -54,7 +54,7 @@ impl ZiskStream { /// /// # Returns /// A new `ZiskStream` instance without a running thread. - pub fn new(hints_processor: HP) -> Self { + pub fn new(hints_processor: impl StreamProcessor) -> Self { Self { hints_processor: Arc::new(hints_processor), tx: None, thread_handle: None } } @@ -101,11 +101,11 @@ impl ZiskStream { /// Background thread function that processes hints when requested. fn background_thread( mut stream: StreamSource, - hints_processor: Arc, + hints_processor: Arc, rx: Receiver, ) { while let Ok(ThreadCommand::Process) = rx.recv() { - if let Err(e) = Self::process_stream(&mut stream, &hints_processor) { + if let Err(e) = Self::process_stream(&mut stream, &*hints_processor) { tracing::error!("Error processing hints in background thread: {:?}", e); } } @@ -115,7 +115,10 @@ impl ZiskStream { /// Process all hints from the stream. /// /// Processes hints in batches until CTRL_END is encountered or the stream ends. - fn process_stream(stream: &mut StreamSource, hints_processor: &HP) -> Result<()> { + fn process_stream( + stream: &mut StreamSource, + hints_processor: &dyn StreamProcessor, + ) -> Result<()> { let mut first_batch = true; while let Some(hints) = stream.next()? { @@ -154,7 +157,7 @@ impl ZiskStream { } } -impl Drop for ZiskStream { +impl Drop for ZiskStream { fn drop(&mut self) { self.stop_thread(); } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 20279811d..e38791db3 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -264,7 +264,7 @@ pub struct Worker { prover_config: ProverConfig, stream_buffers: HashMap>)>, // (job_id, (next_seq, (seq_number, data))) - hints_processor: Option>, + hints_processor: Option, } impl Worker { diff --git a/executor/src/executor.rs b/executor/src/executor.rs index e387777f5..af04c08fe 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -19,16 +19,15 @@ //! By structuring these phases, the `ZiskExecutor` ensures high-performance execution while //! maintaining clarity and modularity in the computation process. -use asm_runner::{HintsFile, HintsShmem, MinimalTraces}; +use asm_runner::MinimalTraces; use fields::PrimeField64; use pil_std_lib::Std; -use precompiles_hints::HintsProcessor; use proofman_common::{create_pool, BufferPool, ProofCtx, ProofmanResult, SetupCtx}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use sm_rom::RomInstance; use std::sync::atomic::{AtomicUsize, Ordering}; use witness::WitnessComponent; -use zisk_common::io::{StreamSink, StreamSource, ZiskStdin, ZiskStream}; +use zisk_common::io::{StreamSource, ZiskStdin, ZiskStream}; use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; @@ -68,15 +67,12 @@ enum MinimalTraceExecutionMode { AsmWithCounter, } -pub type StreamHintsShmem = ZiskStream>; -pub type StreamHintsFile = ZiskStream>; - /// The maximum number of steps to execute in the emulator or assembly runner. pub const MAX_NUM_STEPS: u64 = 1 << 32; /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. -pub struct ZiskExecutor { +pub struct ZiskExecutor { /// Standard input for the ZisK program execution. stdin: Mutex, @@ -87,8 +83,8 @@ pub struct ZiskExecutor { chunk_size: u64, /// Pipeline for handling precompile hints. - hints_stream: Mutex>>, - // hints_stream: Mutex, + hints_stream: Mutex>, + /// ZisK ROM, a binary file containing the ZisK program to be executed. zisk_rom: Arc, @@ -122,11 +118,12 @@ pub struct ZiskExecutor { stats: ExecutorStatsHandle, } -impl ZiskExecutor { +impl ZiskExecutor { /// Creates a new instance of the `ZiskExecutor`. /// /// # Arguments /// * `zisk_rom` - An `Arc`-wrapped ZisK ROM instance. + /// * `hints_stream` - Optional hints stream for processing precompile hints. #[allow(clippy::too_many_arguments)] pub fn new( zisk_rom: Arc, @@ -134,7 +131,7 @@ impl ZiskExecutor { sm_bundle: StaticSMBundle, chunk_size: u64, emulator: EmulatorKind, - hints_stream: ZiskStream>, + hints_stream: Option, ) -> Self { Self { stdin: Mutex::new(ZiskStdin::null()), @@ -160,7 +157,11 @@ impl ZiskExecutor { } pub fn set_hints_stream(&self, stream: StreamSource) -> Result<()> { - self.hints_stream.lock().unwrap().set_hints_stream(stream) + if let Some(hints_stream) = self.hints_stream.lock().unwrap().as_mut() { + hints_stream.set_hints_stream(stream) + } else { + Err(anyhow::anyhow!("No hints stream configured")) + } } #[allow(clippy::type_complexity)] @@ -725,7 +726,7 @@ impl ZiskExecutor { } } -impl WitnessComponent for ZiskExecutor { +impl WitnessComponent for ZiskExecutor { /// Executes the ZisK ROM program and calculate the plans for main and secondary state machines. /// /// # Arguments @@ -749,8 +750,10 @@ impl WitnessComponent for ZiskExecutor Result> + Send + Sync>; /// Builder for configuring and constructing a [`HintsProcessor`]. -pub struct HintsProcessorBuilder { - hints_sink: HS, +pub struct HintsProcessorBuilder { + hints_sink: Arc, num_threads: usize, enable_stats: bool, custom_handlers: HashMap, } -impl HintsProcessorBuilder { +impl HintsProcessorBuilder { /// Sets the number of worker threads in the thread pool. pub fn num_threads(mut self, num_threads: usize) -> Self { self.num_threads = num_threads; @@ -110,14 +110,14 @@ impl HintsProcessorBuilder { /// /// * `Ok(HintsProcessor)` - Successfully constructed processor /// * `Err` - If the thread pool fails to initialize - pub fn build(self) -> Result> { + pub fn build(self) -> Result { let pool = ThreadPoolBuilder::new() .num_threads(self.num_threads) .build() .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; let state = Arc::new(HintProcessorState::new()); - let hints_sink = Arc::new(self.hints_sink); + let hints_sink = self.hints_sink; // Spawn drainer thread let drainer_state = Arc::clone(&state); @@ -143,7 +143,7 @@ impl HintsProcessorBuilder { /// This struct provides methods to parse and process a stream of concatenated /// hints, using a dedicated Rayon thread pool for parallel processing while /// preserving the original order of results. -pub struct HintsProcessor { +pub struct HintsProcessor { /// The thread pool used for parallel hint processing. pool: ThreadPool, @@ -157,7 +157,7 @@ pub struct HintsProcessor { /// The hints sink used to submit processed hints (kept for ownership). #[allow(dead_code)] - hints_sink: Arc, + hints_sink: Arc, /// Handle to the drainer thread (wrapped in ManuallyDrop to join in Drop) drainer_thread: ManuallyDrop>, @@ -166,14 +166,14 @@ pub struct HintsProcessor { custom_handlers: Arc>, } -impl HintsProcessor { +impl HintsProcessor { const DEFAULT_NUM_THREADS: usize = 1; /// Creates a builder for configuring a [`HintsProcessor`]. /// /// # Arguments /// - /// * `hints_sink` - The sink used to submit processed hints + /// * `hints_sink` - The sink used to submit processed hints (any type implementing StreamSink) /// /// # Examples /// @@ -183,9 +183,9 @@ impl HintsProcessor { /// .enable_stats(false) /// .build()?; /// ``` - pub fn builder(hints_sink: HS) -> HintsProcessorBuilder { + pub fn builder(hints_sink: impl StreamSink) -> HintsProcessorBuilder { HintsProcessorBuilder { - hints_sink, + hints_sink: Arc::new(hints_sink), num_threads: Self::DEFAULT_NUM_THREADS, enable_stats: false, custom_handlers: HashMap::new(), @@ -376,7 +376,7 @@ impl HintsProcessor { return; } - println!("Hint processed {:?}:", hint); + // println!("Hint processed {:?}:", hint); // Check if we should stop due to error - but still need to fill the slot let result = if state.error_flag.load(Ordering::Acquire) { @@ -429,7 +429,7 @@ impl HintsProcessor { } /// Drainer thread that waits for hints to complete and drains ready results from queue. - fn drainer_thread(state: Arc, hints_sink: Arc) { + fn drainer_thread(state: Arc, hints_sink: Arc) { loop { let mut queue = state.queue.lock().unwrap(); @@ -636,7 +636,7 @@ impl HintsProcessor { } } -impl Drop for HintsProcessor { +impl Drop for HintsProcessor { fn drop(&mut self) { // Signal drainer thread to shut down self.state.shutdown.store(true, Ordering::Release); @@ -651,7 +651,7 @@ impl Drop for HintsProcessor { } } -impl StreamProcessor for HintsProcessor { +impl StreamProcessor for HintsProcessor { fn process(&self, data: &[u64], first_batch: bool) -> Result { self.process_hints(data, first_batch) } @@ -679,7 +679,7 @@ mod tests { make_header(ctrl, length) } - fn processor() -> HintsProcessor { + fn processor() -> HintsProcessor { HintsProcessor::builder(NullHints).num_threads(2).build().unwrap() } diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 66e6b18d0..096561a6c 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -4,7 +4,7 @@ //! This module leverages `WitnessLibrary` to orchestrate the setup of state machines, //! program conversion, and execution pipelines to generate required witnesses. -use asm_runner::HintsShmem; +use asm_runner::{HintsFile, HintsShmem}; use executor::{ EmulatorAsm, EmulatorKind, EmulatorRust, StateMachines, StaticSMBundle, ZiskExecutor, }; @@ -46,7 +46,7 @@ pub struct WitnessLib { elf_path: PathBuf, asm_mt_path: Option, asm_rh_path: Option, - executor: Option>>, + executor: Option>>, chunk_size: u64, base_port: Option, unlock_mapped_memory: bool, @@ -195,14 +195,25 @@ impl WitnessLibrary for WitnessLib { }; // Create hints pipeline with null hints stream initially. - let hints_shmem = HintsShmem::new(self.base_port, local_rank, self.unlock_mapped_memory) - .expect("Failed to create HintsShmem"); - // let hints_shmem = HintsFile::new("test_modexp_results.bin".to_string()) - // .expect("Failed to create HintsFile"); + /// Debug flag: true = HintsShmem (shared memory), false = HintsFile (file output) + const USE_SHARED_MEMORY_HINTS: bool = true; - let hints_processor = HintsProcessor::builder(hints_shmem) - .build() - .expect("Failed to create PrecompileHintsProcessor"); + let hints_processor = if USE_SHARED_MEMORY_HINTS { + let hints_shmem = + HintsShmem::new(self.base_port, local_rank, self.unlock_mapped_memory) + .expect("zisk_lib: Failed to create HintsShmem"); + + HintsProcessor::builder(hints_shmem) + .build() + .expect("zisk_lib: Failed to create PrecompileHintsProcessor") + } else { + let hints_file = HintsFile::new(format!("hints_results_{}.bin", local_rank)) + .expect("zisk_lib: Failed to create HintsFile"); + + HintsProcessor::builder(hints_file) + .build() + .expect("zisk_lib: Failed to create PrecompileHintsProcessor") + }; let hints_stream = ZiskStream::new(hints_processor); @@ -212,7 +223,7 @@ impl WitnessLibrary for WitnessLib { sm_bundle, self.chunk_size, emulator, - hints_stream, + Some(hints_stream), )); // Step 7: Register the executor as a component in the Witness Manager From 139706fce944c2e1e6e9b381a1a7969452e5f9cf Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:55:47 +0000 Subject: [PATCH 257/782] remove asm_path argument from EmulatorAsm::new as is not necessary --- executor/src/emu_asm.rs | 2 -- witness-computation/src/zisk_lib.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 180b5ff4a..8306af152 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -1,6 +1,5 @@ use std::{ collections::HashMap, - path::PathBuf, sync::{Arc, Mutex}, thread::JoinHandle, }; @@ -65,7 +64,6 @@ impl EmulatorAsm { #[allow(clippy::too_many_arguments)] pub fn new( zisk_rom: Arc, - _asm_path: PathBuf, world_rank: i32, local_rank: i32, base_port: Option, diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 096561a6c..3c5f21a58 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -181,7 +181,6 @@ impl WitnessLibrary for WitnessLib { debug!("Using ASM emulator"); EmulatorKind::Asm(EmulatorAsm::new( zisk_rom.clone(), - self.asm_mt_path.as_ref().unwrap().clone(), world_rank, local_rank, self.base_port, From 9ddb0161b89a76912205602b96e4d871473bdf29 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:56:37 +0000 Subject: [PATCH 258/782] fix typo --- witness-computation/src/zisk_lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 3c5f21a58..11d9a1b49 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -194,7 +194,7 @@ impl WitnessLibrary for WitnessLib { }; // Create hints pipeline with null hints stream initially. - /// Debug flag: true = HintsShmem (shared memory), false = HintsFile (file output) + // Debug flag: true = HintsShmem (shared memory), false = HintsFile (file output) const USE_SHARED_MEMORY_HINTS: bool = true; let hints_processor = if USE_SHARED_MEMORY_HINTS { From 57188cfe9d45800e09f6cbbb2c96492775ecf6f5 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:25:16 +0000 Subject: [PATCH 259/782] modified set_hints_stream to set_hints_stream_src function name --- common/src/io/stream/zisk_stream.rs | 2 +- distributed/crates/coordinator/src/coordinator.rs | 2 +- executor/src/executor.rs | 4 ++-- witness-computation/src/zisk_lib.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index 17782afb1..96e7a5a8f 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -74,7 +74,7 @@ impl ZiskStream { /// /// # Arguments /// * `stream` - The new StreamSource source for reading hints. - pub fn set_hints_stream(&mut self, mut stream: StreamSource) -> Result<()> { + pub fn set_hints_stream_src(&mut self, mut stream: StreamSource) -> Result<()> { if !stream.is_active() { // Stop the existing thread if running self.stop_thread(); diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index d48c55ba8..852c1d91d 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -765,7 +765,7 @@ impl Coordinator { job.job_id, e )) })?; - stream.set_hints_stream(stream_reader).map_err(|e| { + stream.set_hints_stream_src(stream_reader).map_err(|e| { CoordinatorError::Internal(format!( "Failed to set hints stream for job {}: {}", job.job_id, e diff --git a/executor/src/executor.rs b/executor/src/executor.rs index af04c08fe..96ff33cf2 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -156,9 +156,9 @@ impl ZiskExecutor { *guard = stdin; } - pub fn set_hints_stream(&self, stream: StreamSource) -> Result<()> { + pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { if let Some(hints_stream) = self.hints_stream.lock().unwrap().as_mut() { - hints_stream.set_hints_stream(stream) + hints_stream.set_hints_stream_src(stream) } else { Err(anyhow::anyhow!("No hints stream configured")) } diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 11d9a1b49..be5e4100d 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -260,7 +260,7 @@ impl ZiskWitnessLibrary for WitnessLib { fn set_hints_stream(&self, hints_stream: zisk_common::io::StreamSource) -> Result<()> { if let Some(executor) = &self.executor { - executor.set_hints_stream(hints_stream) + executor.set_hints_stream_src(hints_stream) } else { Err(anyhow::anyhow!("Executor not initialized")) } From 3a1d27d8953af091daab73c10512c3c9bbbf50f7 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:45:11 +0000 Subject: [PATCH 260/782] improve emu_Asm writer initialization --- executor/src/emu_asm.rs | 78 +++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 8306af152..a4b24805e 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -139,57 +139,21 @@ impl EmulatorAsm { ExecutorStatsEvent::Begin, ); - AsmServices::SERVICES.par_iter().enumerate().for_each(|(idx, service)| { - #[cfg(feature = "stats")] - let stats_id = stats.next_id(); - #[cfg(feature = "stats")] - stats.add_stat( - parent_stats_id, - stats_id, - "ASM_WRITE_INPUT", - 0, - ExecutorStatsEvent::Begin, - ); - - let port = if let Some(base_port) = self.base_port { - AsmServices::port_for(service, base_port, self.local_rank) - } else { - AsmServices::default_port(service, self.local_rank) - }; - - let shmem_input_name = - AsmSharedMemory::::shmem_input_name(port, *service, self.local_rank); + #[cfg(feature = "stats")] + let stats_id = stats.next_id(); + #[cfg(feature = "stats")] + stats.add_stat(parent_stats_id, stats_id, "ASM_WRITE_INPUT", 0, ExecutorStatsEvent::Begin); + AsmServices::SERVICES.par_iter().enumerate().for_each(|(idx, service)| { let mut input_writer = self.shmem_input_writer[idx].lock().unwrap(); - if input_writer.is_none() { - tracing::info!( - "Initializing SharedMemoryWriter for service {:?} at '{}'", - service, - shmem_input_name - ); - *input_writer = Some( - SharedMemoryWriter::new( - &shmem_input_name, - MAX_INPUT_SIZE as usize, - self.unlock_mapped_memory, - ) - .expect("Failed to create SharedMemoryWriter"), - ); - } + input_writer.get_or_insert_with(|| self.create_shmem_writer(service)); write_input(&mut stdin.lock().unwrap(), input_writer.as_ref().unwrap()); - - // Add to executor stats - #[cfg(feature = "stats")] - stats.add_stat( - parent_stats_id, - stats_id, - "ASM_WRITE_INPUT", - 0, - ExecutorStatsEvent::End, - ); }); + #[cfg(feature = "stats")] + stats.add_stat(parent_stats_id, stats_id, "ASM_WRITE_INPUT", 0, ExecutorStatsEvent::End); + let chunk_size = self.chunk_size; let (world_rank, local_rank, base_port) = (self.world_rank, self.local_rank, self.base_port); @@ -259,6 +223,30 @@ impl EmulatorAsm { (min_traces, main_count, secn_count, Some(handle_mo), execution_result) } + fn create_shmem_writer(&self, service: &asm_runner::AsmService) -> SharedMemoryWriter { + let port = if let Some(base_port) = self.base_port { + AsmServices::port_for(service, base_port, self.local_rank) + } else { + AsmServices::default_port(service, self.local_rank) + }; + + let shmem_input_name = + AsmSharedMemory::::shmem_input_name(port, *service, self.local_rank); + + tracing::info!( + "Initializing SharedMemoryWriter for service {:?} at '{}'", + service, + shmem_input_name + ); + + SharedMemoryWriter::new( + &shmem_input_name, + MAX_INPUT_SIZE as usize, + self.unlock_mapped_memory, + ) + .expect("Failed to create SharedMemoryWriter") + } + fn run_mt_assembly( &self, sm_bundle: &StaticSMBundle, From 9884c2911384f970134b9a2bf29f777aacb851d2 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 10:52:42 +0000 Subject: [PATCH 261/782] removed unnecessary Mutex --- executor/src/emu_asm.rs | 4 ++-- executor/src/emu_rust.rs | 4 ++-- executor/src/executor.rs | 10 +++++----- executor/src/lib.rs | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index a4b24805e..b5755ee9b 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -115,7 +115,7 @@ impl EmulatorAsm { /// * `ZiskExecutionResult` - The result of executing the ZisK ROM. #[allow(clippy::type_complexity)] pub fn execute( - &mut self, + &self, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, @@ -372,7 +372,7 @@ impl EmulatorAsm { impl crate::Emulator for EmulatorAsm { fn execute( - &mut self, + &self, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index df872bcf8..50115a253 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -54,7 +54,7 @@ impl EmulatorRust { /// * `None`. /// * `ZiskExecutionResult` - Summary of the emulator execution, including the total number of steps. pub fn execute( - &mut self, + &self, stdin: &Mutex, _pctx: &ProofCtx, sm_bundle: &StaticSMBundle, @@ -180,7 +180,7 @@ impl EmulatorRust { impl crate::Emulator for EmulatorRust { fn execute( - &mut self, + &self, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 96ff33cf2..070c6efbf 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -77,7 +77,7 @@ pub struct ZiskExecutor { stdin: Mutex, /// The emulator backend used for execution. - emulator: Mutex, + emulator: EmulatorKind, /// Chunk size for processing. chunk_size: u64, @@ -135,7 +135,7 @@ impl ZiskExecutor { ) -> Self { Self { stdin: Mutex::new(ZiskStdin::null()), - emulator: Mutex::new(emulator), + emulator, chunk_size, hints_stream: Mutex::new(hints_stream), zisk_rom, @@ -760,7 +760,7 @@ impl WitnessComponent for ZiskExecutor { timer_start_info!(COMPUTE_MINIMAL_TRACE); let (min_traces, main_count, mut secn_count, handle_mo, execution_result) = - self.emulator.lock().unwrap().execute( + self.emulator.execute( &self.stdin, &pctx, &self.sm_bundle, @@ -964,7 +964,7 @@ impl WitnessComponent for ZiskExecutor { #[cfg(feature = "stats")] self.stats.add_stat(0, parent_stats_id, "CALCULATE_WITNESS", 0, ExecutorStatsEvent::Begin); - let is_asm_emulator = self.emulator.lock().unwrap().is_asm_emulator(); + let is_asm_emulator = self.emulator.is_asm_emulator(); let pool = create_pool(n_cores); pool.install(|| -> ProofmanResult<()> { @@ -1074,7 +1074,7 @@ impl WitnessComponent for ZiskExecutor { } let secn_instances_guard = self.secn_instances.read().unwrap(); - let is_asm_emulator = self.emulator.lock().unwrap().is_asm_emulator(); + let is_asm_emulator = self.emulator.is_asm_emulator(); let mut secn_instances = HashMap::new(); for &global_id in global_ids { diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 97c5b7265..a6002b862 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -27,7 +27,7 @@ use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, ZiskExecutionResult}; pub trait Emulator: Send + Sync { /// Execute the emulator fn execute( - &mut self, + &self, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, @@ -57,7 +57,7 @@ impl EmulatorKind { impl Emulator for EmulatorKind { fn execute( - &mut self, + &self, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, From 6c554fc0da4b226b6712f8e5d9cda9b50bf0ea64 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 19 Jan 2026 11:56:51 +0100 Subject: [PATCH 262/782] Fix fnec inverse --- .cargo/config.toml | 2 -- ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index a93d96687..000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.x86_64-unknown-linux-gnu] -rustflags = ["-C", "link-arg=-Wl,--allow-multiple-definition"] diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs index 8181583d3..53bb79925 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs @@ -17,7 +17,7 @@ pub fn fcall_bls12_381_fp2_sqrt(params: &[u64], results: &mut [u64]) -> i64 { // Perform the square root let _results = bls12_381_fp2_sqrt_13(a); - results.copy_from_slice(&_results); + results[0..13].copy_from_slice(&_results); 13 } From 7071f8134b42ae91f23a84bb3c754966fc1b6841 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 19 Jan 2026 11:56:51 +0100 Subject: [PATCH 263/782] Fix fnec inv --- .cargo/config.toml | 2 -- .../src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs | 10 ++++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) delete mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index a93d96687..000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.x86_64-unknown-linux-gnu] -rustflags = ["-C", "link-arg=-Wl,--allow-multiple-definition"] diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs index 9595faf27..ac5b90c1d 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs @@ -52,6 +52,16 @@ pub fn fcall_bls12_381_fp2_sqrt(params: &[u64], results: &mut [u64]) -> i64 { // Get the input let a: &[u64; 12] = ¶ms[0..12].try_into().unwrap(); + // Perform the square root + let _results = bls12_381_fp2_sqrt_13(a); + results[0..13].copy_from_slice(&_results); + + 13 +} + +pub fn bls12_381_fp2_sqrt_13(a: &[u64; 12]) -> [u64; 13] { + let mut results = [0u64; 13]; + // Perform the square root let (sqrt, is_qr) = bls12_381_fp2_sqrt(a); results[0] = is_qr as u64; From ab3f96c56c5921162fe48ecee17b68fed7f5a931 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 19 Jan 2026 13:06:41 +0100 Subject: [PATCH 264/782] Fix bls12_381_fp2_sqrt_13 merge --- ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs index ac5b90c1d..8b90a02aa 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/bls12_381/fp2_sqrt.rs @@ -77,7 +77,7 @@ pub fn bls12_381_fp2_sqrt_13(a: &[u64; 12]) -> [u64; 13] { } else { results[1..13].copy_from_slice(&sqrt); } - 13 + results } /// Algorithm 9 from https://eprint.iacr.org/2012/685.pdf From 80d041da10821ff8dd2b44dc7480bc8922e401cf Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 19 Jan 2026 13:23:17 +0100 Subject: [PATCH 265/782] Update Cargo.lock --- Cargo.lock | 441 +++++++++++++++++++++++++++-------------------------- 1 file changed, 224 insertions(+), 217 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e9d2c9cd..095f84d8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,7 +196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -209,7 +209,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -259,7 +259,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -301,7 +301,7 @@ dependencies = [ "mem-planner-cpp", "named-sem", "rayon", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "ureq", "zisk-common", @@ -327,7 +327,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -338,7 +338,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -380,9 +380,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -425,9 +425,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bincode" @@ -466,7 +466,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -486,15 +486,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", + "cpufeatures", ] [[package]] @@ -526,7 +527,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -626,7 +627,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -637,9 +638,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.50" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", @@ -670,9 +671,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -726,9 +727,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -736,9 +737,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -755,14 +756,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -772,11 +773,11 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -794,7 +795,7 @@ dependencies = [ "serde-untagged", "serde_core", "serde_json", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "winnow", "yaml-rust2", ] @@ -833,16 +834,16 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "tiny-keccak", ] [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "conv" @@ -1039,7 +1040,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "fields", "num-bigint", @@ -1144,7 +1145,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1179,7 +1180,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1245,7 +1246,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1297,7 +1298,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1382,7 +1383,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "num-bigint", "paste", @@ -1391,9 +1392,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "findshlibs" @@ -1415,9 +1416,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -1500,7 +1501,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1546,9 +1547,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -1609,9 +1610,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1954,9 +1955,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -2003,9 +2004,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -2066,15 +2067,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", @@ -2085,13 +2086,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2106,9 +2107,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -2167,15 +2168,15 @@ version = "0.16.0" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libffi" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0444124f3ffd67e1b0b0c661a7f81a278a135eb54aaad4078e79fbc8be50c8a5" +checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b" dependencies = [ "libc", "libffi-sys", @@ -2183,9 +2184,9 @@ dependencies = [ [[package]] name = "libffi-sys" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d722da8817ea580d0669da6babe2262d7b86a1af1103da24102b8bb9c101ce7" +checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb" dependencies = [ "cc", ] @@ -2214,9 +2215,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", @@ -2376,7 +2377,7 @@ dependencies = [ "mpi-sys", "once_cell", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2412,7 +2413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0875efe1a57a20d0cee7034499aa9d764b3c7525563fa3c3f16a2ccf01ddfa04" dependencies = [ "libc", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows", ] @@ -2646,9 +2647,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", "ucd-trie", @@ -2656,9 +2657,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -2666,22 +2667,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "pest_meta" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2", @@ -2689,18 +2690,19 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", + "hashbrown 0.15.5", "indexmap", ] [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "colored", "fields", @@ -2710,6 +2712,9 @@ dependencies = [ "proofman-hints", "proofman-util", "rayon", + "rustc-hash 2.1.1", + "serde", + "serde_json", "tracing", "witness", ] @@ -2731,7 +2736,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2792,9 +2797,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -3025,7 +3030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3039,9 +3044,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -3049,7 +3054,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "blake3", "borsh", @@ -3084,7 +3089,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "borsh", "colored", @@ -3106,7 +3111,7 @@ dependencies = [ "serde_json", "sysinfo 0.35.2", "tabled", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "tracing-subscriber", "yansi", @@ -3115,9 +3120,10 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "fields", + "itoa", "proofman-common", "proofman-starks-lib-c", "proofman-util", @@ -3127,18 +3133,18 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "proc-macro2", "quote", "rayon", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "crossbeam-channel", "tracing", @@ -3147,7 +3153,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "colored", "fields", @@ -3158,7 +3164,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "bytemuck", "fields", @@ -3168,9 +3174,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", "prost-derive", @@ -3178,15 +3184,14 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", "itertools 0.14.0", "log", "multimap", - "once_cell", "petgraph", "prettyplease", "prost", @@ -3194,28 +3199,28 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.111", + "syn 2.0.114", "tempfile", ] [[package]] name = "prost-derive" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "prost-types" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" dependencies = [ "prost", ] @@ -3233,9 +3238,9 @@ dependencies = [ [[package]] name = "pulldown-cmark-to-cmark" -version = "21.1.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8246feae3db61428fd0bb94285c690b460e4517d83152377543ca802357785f1" +checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" dependencies = [ "pulldown-cmark", ] @@ -3263,7 +3268,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3284,7 +3289,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -3306,9 +3311,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -3337,7 +3342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3357,7 +3362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3366,14 +3371,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -3413,9 +3418,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3515,7 +3520,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -3570,9 +3575,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" @@ -3614,9 +3619,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "log", "once_cell", @@ -3629,9 +3634,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -3639,9 +3644,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -3665,9 +3670,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -3756,14 +3761,14 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.147" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "indexmap", "itoa", @@ -3847,10 +3852,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -4078,9 +4084,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.17.0" +version = "12.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d8046c5674ab857104bc4559d505f4809b8060d57806e45d49737c97afeb60" +checksum = "520cf51c674f8b93d533f80832babe413214bb766b6d7cb74ee99ad2971f8467" dependencies = [ "debugid", "memmap2", @@ -4090,9 +4096,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.17.0" +version = "12.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1accb6e5c4b0f682de907623912e616b44be1c9e725775155546669dbff720ec" +checksum = "9f0de2ee0ffa2641e17ba715ad51d48b9259778176517979cb38b6aa86fa7425" dependencies = [ "cc", "cpp_demangle", @@ -4114,9 +4120,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -4140,7 +4146,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4222,11 +4228,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -4237,18 +4243,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4262,9 +4268,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", @@ -4272,22 +4278,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", @@ -4339,9 +4345,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -4362,7 +4368,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4377,9 +4383,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -4388,9 +4394,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -4413,9 +4419,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.10+spec-1.1.0" +version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ "indexmap", "serde_core", @@ -4529,7 +4535,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4554,7 +4560,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.111", + "syn 2.0.114", "tempfile", "tonic-build", ] @@ -4574,9 +4580,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -4639,7 +4645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing-subscriber", ] @@ -4652,7 +4658,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4739,9 +4745,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" @@ -4810,9 +4816,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -4914,18 +4920,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -4936,11 +4942,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -4949,9 +4956,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4959,22 +4966,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -4994,9 +5001,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -5014,9 +5021,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -5119,7 +5126,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5130,7 +5137,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5376,14 +5383,14 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "colored", "fields", @@ -5436,28 +5443,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5477,7 +5484,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] @@ -5492,13 +5499,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5531,7 +5538,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5596,7 +5603,7 @@ dependencies = [ "proofman-common", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "tracing-appender", "tracing-subscriber", @@ -5625,7 +5632,7 @@ dependencies = [ "serde_json", "signal-hook", "signal-hook-tokio", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tonic", "tracing", @@ -5673,7 +5680,7 @@ dependencies = [ "serde", "tokio", "tokio-stream", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "tonic", "tracing", "uuid", @@ -5796,7 +5803,7 @@ version = "0.16.0" dependencies = [ "bincode", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "lazy_static", "lib-c", "num-bigint", @@ -5810,9 +5817,9 @@ dependencies = [ [[package]] name = "zmij" -version = "0.1.7" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e404bcd8afdaf006e529269d3e85a743f9480c3cef60034d77860d02964f3ba" +checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" [[package]] name = "zstd" From 9befc3e1c97b87ad2b70927d61b190345454cc61 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:12:35 +0000 Subject: [PATCH 266/782] cargo update new pil2-proofman --- Cargo.lock | 57 +++++++++++++++++++++++++----------------------------- Cargo.toml | 16 +++++++-------- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a26e8ef6..3f1d27c49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -489,7 +489,7 @@ dependencies = [ "bitflags 2.10.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.10.5", "lazy_static", "lazycell", "proc-macro2", @@ -822,7 +822,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1140,7 +1140,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" dependencies = [ "fields", "num-bigint", @@ -1431,7 +1431,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1504,7 +1504,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" dependencies = [ "num-bigint", "paste", @@ -2147,7 +2147,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2165,15 +2165,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -2194,9 +2185,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" @@ -2882,7 +2873,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" dependencies = [ "colored", "fields", @@ -2892,6 +2883,9 @@ dependencies = [ "proofman-hints", "proofman-util", "rayon", + "rustc-hash 2.1.1", + "serde", + "serde_json", "tracing", "witness", ] @@ -3246,7 +3240,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" dependencies = [ "blake3", "borsh", @@ -3281,7 +3275,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" dependencies = [ "borsh", "colored", @@ -3312,9 +3306,10 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" dependencies = [ "fields", + "itoa", "proofman-common", "proofman-starks-lib-c", "proofman-util", @@ -3324,7 +3319,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" dependencies = [ "proc-macro2", "quote", @@ -3335,7 +3330,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" dependencies = [ "crossbeam-channel", "tracing", @@ -3344,7 +3339,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" dependencies = [ "colored", "fields", @@ -3355,7 +3350,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" dependencies = [ "bytemuck", "fields", @@ -3500,7 +3495,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -3821,7 +3816,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3880,7 +3875,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4499,7 +4494,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5344,7 +5339,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5750,7 +5745,7 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" dependencies = [ "colored", "fields", diff --git a/Cargo.toml b/Cargo.toml index d251f0c6c..4e7ade593 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,14 +105,14 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } # Proofman Local development # proofman = { path = "../pil2-proofman/proofman" } # proofman-common = { path = "../pil2-proofman/common" } From 736b1eb3ea669aaca4dea70f101c713e230dbe6e Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Wed, 7 Jan 2026 08:43:37 +0000 Subject: [PATCH 267/782] Adding verify_zisk_proof --- Cargo.lock | 10 +++++++++- Cargo.toml | 1 + cli/Cargo.toml | 2 +- cli/src/commands/verify_stark.rs | 14 +++++--------- test-verifier/.gitignore | 2 ++ test-verifier/Cargo.toml | 14 ++++++++++++++ test-verifier/build/input.bin | Bin 0 -> 243616 bytes test-verifier/build/zisk.vk.bin | 2 ++ test-verifier/src/main.rs | 19 +++++++++++++++++++ verifier/Cargo.toml | 15 +++++++++++++++ verifier/src/lib.rs | 3 +++ verifier/src/verifier.rs | 10 ++++++++++ 12 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 test-verifier/.gitignore create mode 100644 test-verifier/Cargo.toml create mode 100644 test-verifier/build/input.bin create mode 100644 test-verifier/build/zisk.vk.bin create mode 100644 test-verifier/src/main.rs create mode 100644 verifier/Cargo.toml create mode 100644 verifier/src/lib.rs create mode 100644 verifier/src/verifier.rs diff --git a/Cargo.lock b/Cargo.lock index 2e9d2c9cd..5594357b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -595,7 +595,6 @@ dependencies = [ "proofman", "proofman-common", "proofman-util", - "proofman-verifier", "rand 0.9.2", "reqwest", "rom-setup", @@ -612,6 +611,7 @@ dependencies = [ "zisk-core", "zisk-pil", "zisk-sdk", + "zisk-verifier", "zstd", ] @@ -5720,6 +5720,14 @@ dependencies = [ "zstd", ] +[[package]] +name = "zisk-verifier" +version = "0.16.0" +dependencies = [ + "anyhow", + "proofman-verifier", +] + [[package]] name = "zisk-witness" version = "0.16.0" diff --git a/Cargo.toml b/Cargo.toml index 2aa3b2569..6b9c9ebc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,6 +93,7 @@ ziskos = { path = "ziskos/entrypoint" } circuit = { path = "tools/circuit" } zisk-sdk = { path = "sdk" } zisk-build = { path = "ziskbuild" } +zisk-verifier = { path = "verifier" } # Distributed crates zisk-distributed-common = { path = "distributed/crates/common" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 14bc77ad5..6c4bf5013 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -31,7 +31,6 @@ colored = { workspace = true } fields = { workspace = true } proofman = { workspace = true } proofman-common = { workspace = true } -proofman-verifier = { workspace = true } proofman-util = { workspace = true } sysinfo = { workspace = true } tracing = { workspace = true } @@ -40,6 +39,7 @@ serde_json = { workspace = true } anyhow = { workspace = true } libloading = { workspace = true } zisk-sdk = { workspace = true } +zisk-verifier = { workspace = true } zstd = { workspace = true } bytemuck = { workspace = true } diff --git a/cli/src/commands/verify_stark.rs b/cli/src/commands/verify_stark.rs index 0a595a752..74147414b 100644 --- a/cli/src/commands/verify_stark.rs +++ b/cli/src/commands/verify_stark.rs @@ -1,9 +1,9 @@ -use anyhow::{anyhow, Ok, Result}; +use anyhow::Result; use clap::Parser; use colored::Colorize; use proofman_common::initialize_logger; -use proofman_verifier::verify; use std::fs; +use zisk_verifier::verify_zisk_proof; use zisk_build::ZISK_VERSION_MESSAGE; @@ -40,11 +40,11 @@ impl ZiskVerify { let vk = &self.get_verkey(); - let valid = verify(&proof, vk); + let result = verify_zisk_proof(&proof, vk); let elapsed = start.elapsed(); - if !valid { + if result.is_err() { tracing::info!("{}", "\u{2717} Stark proof was not verified".bright_red().bold()); } else { tracing::info!("{}", "\u{2713} Stark proof was verified".bright_green().bold()); @@ -54,11 +54,7 @@ impl ZiskVerify { tracing::info!(" time: {} milliseconds", elapsed.as_millis()); tracing::info!("{}", "----------------------------".bright_green().bold()); - if !valid { - Err(anyhow!("Stark proof was not verified")) - } else { - Ok(()) - } + result } /// Gets the verification key diff --git a/test-verifier/.gitignore b/test-verifier/.gitignore new file mode 100644 index 000000000..f2f9e58ec --- /dev/null +++ b/test-verifier/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock \ No newline at end of file diff --git a/test-verifier/Cargo.toml b/test-verifier/Cargo.toml new file mode 100644 index 000000000..7ef9ebebd --- /dev/null +++ b/test-verifier/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "test-zisk-verifier" +version = "0.1.0" +edition = "2021" +default-run = "test-zisk-verifier" + +[dependencies] +ziskos = { path = "../ziskos/entrypoint" } +zisk-verifier = { path = "../verifier" } + +[profile.release.package."proofman-verifier"] +opt-level = 0 + +[workspace] \ No newline at end of file diff --git a/test-verifier/build/input.bin b/test-verifier/build/input.bin new file mode 100644 index 0000000000000000000000000000000000000000..d3aa9c014fd294ea7086a2591f8c7f9aedeba72b GIT binary patch literal 243616 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∨sE3=S9{MJC7N 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 literal 0 HcmV?d00001 diff --git a/test-verifier/build/zisk.vk.bin b/test-verifier/build/zisk.vk.bin new file mode 100644 index 000000000..fb72af9dc --- /dev/null +++ b/test-verifier/build/zisk.vk.bin @@ -0,0 +1,2 @@ +t� ��� +a���_���qM�����ɌESS \ No newline at end of file diff --git a/test-verifier/src/main.rs b/test-verifier/src/main.rs new file mode 100644 index 000000000..307d4fdda --- /dev/null +++ b/test-verifier/src/main.rs @@ -0,0 +1,19 @@ +// This example program takes a number `n` as input and computes the SHA-256 hash `n` times sequentially. + +// Mark the main function as the entry point for ZisK +#![no_main] +ziskos::entrypoint!(main); + +use ziskos::{read_input_slice, set_output}; +use zisk_verifier::verify_zisk_proof; + +fn main() { + // Read the input data as a byte array from ziskos + let zisk_proof = read_input_slice(); + + let vk: &[u8] = include_bytes!("../build/zisk.vk.bin"); + + let result = verify_zisk_proof(&zisk_proof, &vk); + + set_output(0, result.is_ok() as u32); +} diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml new file mode 100644 index 000000000..b8fd115a9 --- /dev/null +++ b/verifier/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "zisk-verifier" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +keywords = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } + +[dependencies] +proofman-verifier = { workspace = true, features = ["verify"] } +anyhow = { workspace = true } + +[features] +default = [] diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs new file mode 100644 index 000000000..a1a47de57 --- /dev/null +++ b/verifier/src/lib.rs @@ -0,0 +1,3 @@ +mod verifier; + +pub use verifier::*; diff --git a/verifier/src/verifier.rs b/verifier/src/verifier.rs new file mode 100644 index 000000000..38231d8e1 --- /dev/null +++ b/verifier/src/verifier.rs @@ -0,0 +1,10 @@ +use anyhow::{anyhow, Ok, Result}; +use proofman_verifier::verify; + +pub fn verify_zisk_proof(zisk_proof: &[u8], vk: &[u8]) -> Result<()> { + if !verify(zisk_proof, vk) { + Err(anyhow!("Zisk Proof was not verified")) + } else { + Ok(()) + } +} From bfb237202ad4156509d10140f608b4393a78196e Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 13 Jan 2026 15:37:07 +0000 Subject: [PATCH 268/782] Adding poseidon2 precompile. Missing assembly --- Cargo.lock | 35 ++- Cargo.toml | 33 +-- common/src/bus/data_bus_operation.rs | 29 ++ core/src/riscv2zisk_context.rs | 3 +- core/src/zisk_inst.rs | 2 + core/src/zisk_ops.rs | 99 +++++++ core/src/zisk_rom_2_asm.rs | 5 + distributed/crates/common/src/types.rs | 1 - distributed/crates/worker/src/worker.rs | 6 +- distributed/crates/worker/src/worker_node.rs | 1 - executor/Cargo.toml | 1 + executor/src/sm_static_bundle.rs | 39 ++- executor/src/static_data_bus.rs | 16 +- executor/src/static_data_bus_collect.rs | 29 ++ pil/operations.pil | 5 +- pil/src/pil_helpers/traces.rs | 82 ++---- pil/zisk.pil | 3 + precompiles/poseidon2/Cargo.toml | 34 +++ precompiles/poseidon2/pil/poseidon2.pil | 263 +++++++++++++++++ .../poseidon2/pil/poseidon2_constants.pil | 271 ++++++++++++++++++ precompiles/poseidon2/src/lib.rs | 15 + precompiles/poseidon2/src/poseidon2.rs | 226 +++++++++++++++ .../poseidon2/src/poseidon2_bus_device.rs | 148 ++++++++++ .../poseidon2/src/poseidon2_gen_mem_inputs.rs | 84 ++++++ precompiles/poseidon2/src/poseidon2_input.rs | 18 ++ .../poseidon2/src/poseidon2_instance.rs | 212 ++++++++++++++ .../poseidon2/src/poseidon2_manager.rs | 94 ++++++ .../poseidon2/src/poseidon2_planner.rs | 136 +++++++++ sdk/src/prover/asm.rs | 1 - sdk/src/prover/emu.rs | 1 - sdk/src/zisk_lib_loader.rs | 1 + test-verifier/build/input.bin | Bin 243616 -> 312848 bytes test-verifier/build/zisk.vk.bin | 3 +- verifier/Cargo.toml | 2 +- witness-computation/Cargo.toml | 1 + witness-computation/src/zisk_lib.rs | 10 +- ziskos/entrypoint/src/syscalls/mod.rs | 2 + ziskos/entrypoint/src/syscalls/poseidon2.rs | 27 ++ ziskos/entrypoint/src/syscalls/syscall.rs | 1 + 39 files changed, 1839 insertions(+), 100 deletions(-) create mode 100644 precompiles/poseidon2/Cargo.toml create mode 100644 precompiles/poseidon2/pil/poseidon2.pil create mode 100644 precompiles/poseidon2/pil/poseidon2_constants.pil create mode 100644 precompiles/poseidon2/src/lib.rs create mode 100644 precompiles/poseidon2/src/poseidon2.rs create mode 100644 precompiles/poseidon2/src/poseidon2_bus_device.rs create mode 100644 precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs create mode 100644 precompiles/poseidon2/src/poseidon2_input.rs create mode 100644 precompiles/poseidon2/src/poseidon2_instance.rs create mode 100644 precompiles/poseidon2/src/poseidon2_manager.rs create mode 100644 precompiles/poseidon2/src/poseidon2_planner.rs create mode 100644 ziskos/entrypoint/src/syscalls/poseidon2.rs diff --git a/Cargo.lock b/Cargo.lock index 5594357b1..168ecd9c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1039,7 +1039,6 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "fields", "num-bigint", @@ -1343,6 +1342,7 @@ dependencies = [ "precomp-arith-eq-384", "precomp-big-int", "precomp-keccakf", + "precomp-poseidon2", "precomp-sha256f", "proofman", "proofman-common", @@ -1382,8 +1382,8 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ + "cfg-if", "num-bigint", "paste", "serde", @@ -2700,7 +2700,6 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "colored", "fields", @@ -2973,6 +2972,27 @@ dependencies = [ "zisk-pil", ] +[[package]] +name = "precomp-poseidon2" +version = "0.16.0" +dependencies = [ + "fields", + "mem-common", + "pil-std-lib", + "precompiles-common", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sha2", + "sm-mem", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + [[package]] name = "precomp-sha256f" version = "0.16.0" @@ -3049,7 +3069,6 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "blake3", "borsh", @@ -3084,7 +3103,6 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "borsh", "colored", @@ -3115,7 +3133,6 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "fields", "proofman-common", @@ -3127,7 +3144,6 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "proc-macro2", "quote", @@ -3138,7 +3154,6 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "crossbeam-channel", "tracing", @@ -3147,7 +3162,6 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "colored", "fields", @@ -3158,7 +3172,6 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "bytemuck", "fields", @@ -5383,7 +5396,6 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "colored", "fields", @@ -5742,6 +5754,7 @@ dependencies = [ "precomp-arith-eq-384", "precomp-big-int", "precomp-keccakf", + "precomp-poseidon2", "precomp-sha256f", "proofman", "proofman-common", diff --git a/Cargo.toml b/Cargo.toml index 6b9c9ebc7..4923c81c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ precompiles-common = { path = "precompiles/common" } precompiles-helpers = { path = "precompiles/helpers" } precomp-keccakf = { path = "precompiles/keccakf" } precomp-sha256f = { path = "precompiles/sha256f" } +precomp-poseidon2 = { path = "precompiles/poseidon2" } precomp-big-int = { path = "precompiles/big_int" } riscv = { path = "riscv" } rom-setup = { path = "rom-setup" } @@ -102,23 +103,23 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } # Proofman Local development -# proofman = { path = "../pil2-proofman/proofman" } -# proofman-common = { path = "../pil2-proofman/common" } -# proofman-macros = { path = "../pil2-proofman/macros" } -# proofman-verifier = { path = "../pil2-proofman/verifier" } -# proofman-util = { path = "../pil2-proofman/util" } -# pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } -# witness = { path = "../pil2-proofman/witness" } -# fields = { path = "../pil2-proofman/fields" } +proofman = { path = "../pil2-proofman/proofman" } +proofman-common = { path = "../pil2-proofman/common" } +proofman-macros = { path = "../pil2-proofman/macros" } +proofman-verifier = { path = "../pil2-proofman/verifier" } +proofman-util = { path = "../pil2-proofman/util" } +pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } +witness = { path = "../pil2-proofman/witness" } +fields = { path = "../pil2-proofman/fields" } # External dependencies rayon = "1.10" diff --git a/common/src/bus/data_bus_operation.rs b/common/src/bus/data_bus_operation.rs index 595b9b390..a5d120917 100644 --- a/common/src/bus/data_bus_operation.rs +++ b/common/src/bus/data_bus_operation.rs @@ -35,6 +35,7 @@ const COMPLEX_OVER_384_BITS_SIZE: usize = 2 * DATA_384_BITS_SIZE; // use OPERATION_BUS_DATA_SIZE because a = step, b = addr pub const OPERATION_BUS_KECCAKF_DATA_SIZE: usize = OPERATION_BUS_DATA_SIZE + 25; +pub const OPERATION_BUS_POSEIDON2_DATA_SIZE: usize = OPERATION_BUS_DATA_SIZE + 16; pub const OPERATION_BUS_SHA256F_DATA_SIZE: usize = OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 3 * DATA_256_BITS_SIZE; pub const OPERATION_BUS_ARITH_256_DATA_SIZE: usize = @@ -93,6 +94,7 @@ pub type OperationData = [D; OPERATION_BUS_DATA_SIZE]; /// Type alias for precompiles operation data payload. pub type OperationKeccakData = [D; OPERATION_BUS_KECCAKF_DATA_SIZE]; pub type OperationSha256Data = [D; OPERATION_BUS_SHA256F_DATA_SIZE]; +pub type OperationPoseidon2Data = [D; OPERATION_BUS_POSEIDON2_DATA_SIZE]; pub type OperationArith256Data = [D; OPERATION_BUS_ARITH_256_DATA_SIZE]; pub type OperationArith256ModData = [D; OPERATION_BUS_ARITH_256_MOD_DATA_SIZE]; pub type OperationSecp256k1AddData = [D; OPERATION_BUS_SECP256K1_ADD_DATA_SIZE]; @@ -114,6 +116,7 @@ pub enum ExtOperationData { OperationData(OperationData), OperationKeccakData(OperationKeccakData), OperationSha256Data(OperationSha256Data), + OperationPoseidon2Data(OperationPoseidon2Data), OperationArith256Data(OperationArith256Data), OperationArith256ModData(OperationArith256ModData), OperationSecp256k1AddData(OperationSecp256k1AddData), @@ -134,6 +137,7 @@ pub enum ExtOperationData { const KECCAK_OP: u8 = ZiskOp::Keccak.code(); const SHA256_OP: u8 = ZiskOp::Sha256.code(); +const POSEIDON2_OP: u8 = ZiskOp::Poseidon2.code(); const ARITH256_OP: u8 = ZiskOp::Arith256.code(); const ARITH256_MOD_OP: u8 = ZiskOp::Arith256Mod.code(); const SECP256K1_ADD_OP: u8 = ZiskOp::Secp256k1Add.code(); @@ -171,6 +175,11 @@ impl> TryFrom<&[D]> for ExtOperationData { data.try_into().map_err(|_| "Invalid OperationSha256Data size")?; Ok(ExtOperationData::OperationSha256Data(array)) } + POSEIDON2_OP => { + let array: OperationPoseidon2Data = + data.try_into().map_err(|_| "Invalid OperationPoseidon2Data size")?; + Ok(ExtOperationData::OperationPoseidon2Data(array)) + } ARITH256_OP => { let array: OperationArith256Data = data.try_into().map_err(|_| "Invalid OperationArith256Data size")?; @@ -321,6 +330,14 @@ impl OperationBusData { ExtOperationData::OperationSha256Data(data) } + ZiskOperationType::Poseidon2 => { + let mut data = + unsafe { uninit_array::().assume_init() }; + data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); + data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationPoseidon2Data(data) + } + ZiskOperationType::ArithEq => match inst.op { ARITH256_OP => { let mut data = unsafe { @@ -495,6 +512,14 @@ impl OperationBusData { &buffer[..OPERATION_BUS_SHA256F_DATA_SIZE] } + ZiskOperationType::Poseidon2 => { + debug_assert_eq!(ctx.precompiled.input_data.len(), 16); + buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); + buffer[OPERATION_BUS_DATA_SIZE..OPERATION_BUS_POSEIDON2_DATA_SIZE] + .copy_from_slice(&ctx.precompiled.input_data); + &buffer[..OPERATION_BUS_POSEIDON2_DATA_SIZE] + } + ZiskOperationType::ArithEq => match inst.op { ARITH256_OP => { let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); @@ -647,6 +672,7 @@ impl OperationBusData { ExtOperationData::OperationData(d) => d[OP] as u8, ExtOperationData::OperationKeccakData(d) => d[OP] as u8, ExtOperationData::OperationSha256Data(d) => d[OP] as u8, + ExtOperationData::OperationPoseidon2Data(d) => d[OP] as u8, ExtOperationData::OperationArith256Data(d) => d[OP] as u8, ExtOperationData::OperationArith256ModData(d) => d[OP] as u8, ExtOperationData::OperationSecp256k1AddData(d) => d[OP] as u8, @@ -679,6 +705,7 @@ impl OperationBusData { ExtOperationData::OperationData(d) => d[OP_TYPE], ExtOperationData::OperationKeccakData(d) => d[OP_TYPE], ExtOperationData::OperationSha256Data(d) => d[OP_TYPE], + ExtOperationData::OperationPoseidon2Data(d) => d[OP_TYPE], ExtOperationData::OperationArith256Data(d) => d[OP_TYPE], ExtOperationData::OperationArith256ModData(d) => d[OP_TYPE], ExtOperationData::OperationSecp256k1AddData(d) => d[OP_TYPE], @@ -711,6 +738,7 @@ impl OperationBusData { ExtOperationData::OperationData(d) => d[A], ExtOperationData::OperationKeccakData(d) => d[A], ExtOperationData::OperationSha256Data(d) => d[A], + ExtOperationData::OperationPoseidon2Data(d) => d[A], ExtOperationData::OperationArith256Data(d) => d[A], ExtOperationData::OperationArith256ModData(d) => d[A], ExtOperationData::OperationSecp256k1AddData(d) => d[A], @@ -743,6 +771,7 @@ impl OperationBusData { ExtOperationData::OperationData(d) => d[B], ExtOperationData::OperationKeccakData(d) => d[B], ExtOperationData::OperationSha256Data(d) => d[B], + ExtOperationData::OperationPoseidon2Data(d) => d[B], ExtOperationData::OperationArith256Data(d) => d[B], ExtOperationData::OperationArith256ModData(d) => d[B], ExtOperationData::OperationSecp256k1AddData(d) => d[B], diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 56bc4cb68..0d50c3180 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -14,7 +14,7 @@ use std::collections::HashMap; // The CSR precompiled addresses are defined in the `ZiskOS` `ziskos/entrypoint/src` files // because legacy versions of Rust do not support constant parameters in `asm!` macros. -const CSR_PRECOMPILED: [&str; 18] = [ +const CSR_PRECOMPILED: [&str; 19] = [ "keccak", "arith256", "arith256_mod", @@ -33,6 +33,7 @@ const CSR_PRECOMPILED: [&str; 18] = [ "bls12_381_complex_sub", "bls12_381_complex_mul", "add256", + "poseidon2", ]; const CSR_PRECOMPILED_ADDR_START: u32 = 0x800; const CSR_PRECOMPILED_ADDR_END: u32 = CSR_PRECOMPILED_ADDR_START + CSR_PRECOMPILED.len() as u32; diff --git a/core/src/zisk_inst.rs b/core/src/zisk_inst.rs index d88c9e4cf..155d55124 100644 --- a/core/src/zisk_inst.rs +++ b/core/src/zisk_inst.rs @@ -78,6 +78,7 @@ pub enum ZiskOperationType { BinaryE, Keccak, Sha256, + Poseidon2, PubOut, ArithEq, ArithEq384, @@ -95,6 +96,7 @@ pub const BINARY_OP_TYPE_ID: u32 = ZiskOperationType::Binary as u32; pub const BINARY_E_OP_TYPE_ID: u32 = ZiskOperationType::BinaryE as u32; pub const KECCAK_OP_TYPE_ID: u32 = ZiskOperationType::Keccak as u32; pub const SHA256_OP_TYPE_ID: u32 = ZiskOperationType::Sha256 as u32; +pub const POSEIDON2_OP_TYPE_ID: u32 = ZiskOperationType::Poseidon2 as u32; pub const PUB_OUT_OP_TYPE_ID: u32 = ZiskOperationType::PubOut as u32; pub const ARITH_EQ_OP_TYPE_ID: u32 = ZiskOperationType::ArithEq as u32; pub const ARITH_EQ_384_OP_TYPE_ID: u32 = ZiskOperationType::ArithEq384 as u32; diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index e11089778..92bfd551d 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -11,6 +11,7 @@ use ziskos::zisklib::fcall_proxy; +use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; use std::{ collections::HashMap, fmt::{Debug, Display}, @@ -46,6 +47,7 @@ pub enum OpType { BinaryE, Keccak, Sha256, + Poseidon2, PubOut, ArithEq, Fcall, @@ -62,6 +64,7 @@ impl From for ZiskOperationType { OpType::BinaryE => ZiskOperationType::BinaryE, OpType::Keccak => ZiskOperationType::Keccak, OpType::Sha256 => ZiskOperationType::Sha256, + OpType::Poseidon2 => ZiskOperationType::Poseidon2, OpType::PubOut => ZiskOperationType::PubOut, OpType::ArithEq => ZiskOperationType::ArithEq, OpType::Fcall => ZiskOperationType::Fcall, @@ -82,6 +85,7 @@ impl Display for OpType { Self::BinaryE => write!(f, "BinaryE"), Self::Keccak => write!(f, "Keccak"), Self::Sha256 => write!(f, "Sha256"), + Self::Poseidon2 => write!(f, "Poseidon2"), Self::PubOut => write!(f, "PubOut"), Self::ArithEq => write!(f, "Arith256"), Self::Fcall => write!(f, "Fcall"), @@ -104,6 +108,7 @@ impl FromStr for OpType { "be" => Ok(Self::BinaryE), "k" => Ok(Self::Keccak), "s" => Ok(Self::Sha256), + "p" => Ok(Self::Poseidon2), "aeq" => Ok(Self::ArithEq), "fcall" => Ok(Self::Fcall), "aeq384" => Ok(Self::ArithEq384), @@ -318,6 +323,7 @@ const ARITHA32_COST: u64 = 95; const ARITHAM32_COST: u64 = 95; const KECCAK_COST: u64 = (((93846 * 86) - 1) / 63) + 1; const SHA256_COST: u64 = 72 * 121; +const POSEIDON2_COST: u64 = 14 * 16; const ARITH_EQ_COST: u64 = 85 * 16; const FCALL_COST: u64 = INTERNAL_COST; const ARITH_EQ_384_COST: u64 = 79 * 24; @@ -400,6 +406,7 @@ define_ops! { (Fcall, "fcall", Fcall, FCALL_COST, 0xf7, 0, 0, opc_fcall, op_fcall, ops_none), (FcallGet, "fcall_get", Fcall, FCALL_COST, 0xf8, 0, 0, opc_fcall_get, op_fcall_get, ops_none), (Sha256, "sha256", Sha256, SHA256_COST, 0xf9, 112, 112, opc_sha256, op_sha256, ops_sha256), + (Poseidon2, "poseidon2", Poseidon2, POSEIDON2_COST, 0xe1, 128, 128, opc_poseidon2, op_poseidon2, ops_poseidon2), (Bn254CurveAdd, "bn254_curve_add", ArithEq, ARITH_EQ_COST, 0xfa, 144, 64, opc_bn254_curve_add, op_bn254_curve_add, ops_bn254_curve_add), (Bn254CurveDbl, "bn254_curve_dbl", ArithEq, ARITH_EQ_COST, 0xfb, 64, 64, opc_bn254_curve_dbl, op_bn254_curve_dbl, ops_bn254_curve_dbl), (Bn254ComplexAdd, "bn254_complex_add", ArithEq, ARITH_EQ_COST, 0xfc, 144, 64, opc_bn254_complex_add, op_bn254_complex_add, ops_bn254_complex_add), @@ -1292,6 +1299,98 @@ pub fn ops_sha256(ctx: &InstContext, stats: &mut dyn OpStats) { precompiled_stats_data(ctx, stats, &[4, 8], &[], 1); } +/// Performs a Poseidon2 hash over a 16 elements stored in memory at the address +/// specified by register A0, and stores the output state in the same memory address +#[inline(always)] +pub fn opc_poseidon2(ctx: &mut InstContext) { + // Get address from b (a = step) + let address = ctx.b; + if address & 0x7 != 0 { + panic!("opc_poseidon2() found address not aligned to 8 bytes"); + } + + // Allocate room for 16 u64 = 128 bytes = 1024 bits + const WORDS: usize = 16; + let mut data = [0u64; WORDS]; + + // Get input data from memory or from the precompiled context + match ctx.emulation_mode { + EmulationMode::Mem => { + // Read data from the memory address + for (i, d) in data.iter_mut().enumerate() { + *d = ctx.mem.read(address + (8 * i as u64), 8); + } + + // Call poseidon2 + let data_gl = data.map(Goldilocks::new); + let res_gl = poseidon2_hash::(&data_gl); + for (i, d) in data.iter_mut().enumerate() { + *d = res_gl[i].as_canonical_u64(); + } + + // Write data to the memory address + for (i, d) in data.iter().enumerate() { + ctx.mem.write(address + (8 * i as u64), *d, 8); + } + } + EmulationMode::GenerateMemReads => { + // Read data from the memory address + for (i, d) in data.iter_mut().enumerate() { + *d = ctx.mem.read(address + (8 * i as u64), 8); + } + + // Copy data to the precompiled context + ctx.precompiled.input_data.clear(); + for (i, d) in data.iter_mut().enumerate() { + ctx.precompiled.input_data.push(*d); + } + + // Call poseidon2 + let data_gl = data.map(Goldilocks::new); + let res_gl = poseidon2_hash::(&data_gl); + for (i, d) in data.iter_mut().enumerate() { + *d = res_gl[i].as_canonical_u64(); + } + + // Write data to the memory address + for (i, d) in data.iter().enumerate() { + ctx.mem.write(address + (8 * i as u64), *d, 8); + } + + // Write data to the precompiled context + ctx.precompiled.output_data.clear(); + for (i, d) in data.iter_mut().enumerate() { + ctx.precompiled.output_data.push(*d); + } + } + EmulationMode::ConsumeMemReads => { + // Check input data has the expected length + if ctx.precompiled.input_data.len() != WORDS { + panic!( + "opc_poseidon2() found ctx.precompiled.input_data.len={} != {}", + ctx.precompiled.input_data.len(), + WORDS + ); + } + } + } + + ctx.c = 0; + ctx.flag = false; +} + +/// Unimplemented. Poseidon2 can only be called from the system call context via InstContext. +/// This is provided just for completeness. +#[inline(always)] +pub fn op_poseidon2(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_poseidon2() is not implemented"); +} + +#[inline(always)] +pub fn ops_poseidon2(ctx: &InstContext, stats: &mut dyn OpStats) { + precompiled_stats_direct_data(ctx, stats, 16, 16); +} + #[inline(always)] pub fn precompiled_load_data( ctx: &mut InstContext, diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index b0411a2cb..9283570a1 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -199,6 +199,7 @@ impl ZiskAsmContext { zisk_op, ZiskOp::Keccak | ZiskOp::Sha256 + | ZiskOp::Poseidon2 | ZiskOp::Arith256 | ZiskOp::Arith256Mod | ZiskOp::Secp256k1Add @@ -506,6 +507,7 @@ impl ZiskRom2Asm { *code += ".extern print_step\n"; *code += ".extern opcode_keccak\n"; *code += ".extern opcode_sha256\n"; + *code += ".extern opcode_poseidon2\n"; *code += ".extern opcode_arith256\n"; *code += ".extern opcode_arith256_mod\n"; *code += ".extern opcode_secp256k1_add\n"; @@ -5154,6 +5156,9 @@ impl ZiskRom2Asm { ctx.c.is_saved = true; ctx.flag_is_always_zero = true; } + ZiskOp::Poseidon2 => { + // TODO + } ZiskOp::PubOut => { assert!(ctx.store_b_in_c); ctx.c.is_constant = ctx.b.is_constant; diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index fba1bfd56..b5efd88d4 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -441,7 +441,6 @@ pub struct AggregationParams { pub verify_constraints: bool, pub aggregation: bool, pub rma: bool, - pub final_snark: bool, pub verify_proofs: bool, pub save_proofs: bool, pub test_mode: bool, diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 822a01c93..f8b761dd2 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -699,7 +699,7 @@ impl Worker { ProofOptions { verify_constraints: false, aggregation: false, - final_snark: false, + perf: false, verify_proofs: true, save_proofs: true, test_mode: false, @@ -713,7 +713,7 @@ impl Worker { ProofOptions { verify_constraints: false, aggregation: true, - final_snark: false, + perf: false, verify_proofs: false, save_proofs: false, test_mode: false, @@ -728,7 +728,7 @@ impl Worker { verify_constraints: agg_params.verify_constraints, aggregation: agg_params.aggregation, rma: agg_params.rma, - final_snark: agg_params.final_snark, + perf: false, verify_proofs: agg_params.verify_proofs, save_proofs: agg_params.save_proofs, test_mode: agg_params.test_mode, diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index f656ed2ab..db2a61239 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -669,7 +669,6 @@ impl WorkerNodeGrpc { verify_constraints: agg_params.verify_constraints, aggregation: agg_params.aggregation, rma: agg_params.rma, - final_snark: agg_params.final_snark, verify_proofs: agg_params.verify_proofs, save_proofs: agg_params.save_proofs, test_mode: agg_params.test_mode, diff --git a/executor/Cargo.toml b/executor/Cargo.toml index 6487a4499..d48ced4f9 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -33,6 +33,7 @@ crossbeam = "0.8.4" precomp-keccakf = { workspace = true } precomp-sha256f = { workspace = true } +precomp-poseidon2 = { workspace = true } precomp-arith-eq = { workspace = true } precomp-arith-eq-384 = { workspace = true } precomp-big-int = { workspace = true } diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index e77b56333..ba6e289f3 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -8,6 +8,7 @@ use precomp_arith_eq_384::ArithEq384Instance; use precomp_arith_eq_384::ArithEq384Manager; use precomp_big_int::{Add256Instance, Add256Manager}; use precomp_keccakf::{KeccakfInstance, KeccakfManager}; +use precomp_poseidon2::{Poseidon2Instance, Poseidon2Manager}; use precomp_sha256f::{Sha256fInstance, Sha256fManager}; use proofman_common::ProofCtx; use sm_arith::{ArithFullInstance, ArithSM}; @@ -23,8 +24,8 @@ use zisk_pil::ADD_256_AIR_IDS; use zisk_pil::{ ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, MEM_ALIGN_AIR_IDS, - MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, MEM_ALIGN_WRITE_BYTE_AIR_IDS, ROM_AIR_IDS, - ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, + MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, MEM_ALIGN_WRITE_BYTE_AIR_IDS, + POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, }; use crate::StaticDataBus; @@ -40,6 +41,7 @@ pub enum StateMachines { ArithSM(Arc>), KeccakfManager(Arc>), Sha256fManager(Arc>), + Poseidon2Manager(Arc>), ArithEqManager(Arc>), ArithEq384Manager(Arc>), Add256Manager(Arc>), @@ -54,9 +56,10 @@ impl StateMachines { StateMachines::ArithSM(_) => 3, StateMachines::KeccakfManager(_) => 4, StateMachines::Sha256fManager(_) => 5, - StateMachines::ArithEqManager(_) => 6, - StateMachines::ArithEq384Manager(_) => 7, - StateMachines::Add256Manager(_) => 8, + StateMachines::Poseidon2Manager(_) => 6, + StateMachines::ArithEqManager(_) => 7, + StateMachines::ArithEq384Manager(_) => 8, + StateMachines::Add256Manager(_) => 9, } } @@ -74,6 +77,7 @@ impl StateMachines { StateMachines::ArithSM(sm) => (**sm).build_planner(), StateMachines::KeccakfManager(sm) => (**sm).build_planner(), StateMachines::Sha256fManager(sm) => (**sm).build_planner(), + StateMachines::Poseidon2Manager(sm) => (**sm).build_planner(), StateMachines::ArithEqManager(sm) => (**sm).build_planner(), StateMachines::ArithEq384Manager(sm) => (**sm).build_planner(), StateMachines::Add256Manager(sm) => (**sm).build_planner(), @@ -90,6 +94,7 @@ impl StateMachines { StateMachines::ArithSM(sm) => (**sm).configure_instances(pctx, plans), StateMachines::KeccakfManager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::Sha256fManager(sm) => (**sm).configure_instances(pctx, plans), + StateMachines::Poseidon2Manager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::ArithEqManager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::ArithEq384Manager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::Add256Manager(sm) => (**sm).configure_instances(pctx, plans), @@ -104,6 +109,7 @@ impl StateMachines { StateMachines::ArithSM(sm) => (**sm).build_instance(ictx), StateMachines::KeccakfManager(sm) => (**sm).build_instance(ictx), StateMachines::Sha256fManager(sm) => (**sm).build_instance(ictx), + StateMachines::Poseidon2Manager(sm) => (**sm).build_instance(ictx), StateMachines::ArithEqManager(sm) => (**sm).build_instance(ictx), StateMachines::ArithEq384Manager(sm) => (**sm).build_instance(ictx), StateMachines::Add256Manager(sm) => (**sm).build_instance(ictx), @@ -183,6 +189,7 @@ impl StaticSMBundle { let mut arith_counter = None; let mut keccakf_counter = None; let mut sha256f_counter = None; + let mut poseidon2_counter = None; let mut arith_eq_counter = None; let mut arith_eq_384_counter = None; let mut add256_counter = None; @@ -208,6 +215,10 @@ impl StaticSMBundle { StateMachines::Sha256fManager(sha256_sm) => { sha256f_counter = Some((sm.type_id(), sha256_sm.build_sha256f_counter())); } + StateMachines::Poseidon2Manager(poseidon2_sm) => { + poseidon2_counter = + Some((sm.type_id(), poseidon2_sm.build_poseidon2_counter())); + } StateMachines::ArithEqManager(arith_eq_sm) => { arith_eq_counter = Some((sm.type_id(), arith_eq_sm.build_arith_eq_counter())); } @@ -229,6 +240,7 @@ impl StaticSMBundle { arith_counter.expect("Arith counter not found"), keccakf_counter.expect("Keccakf counter not found"), sha256f_counter.expect("Sha256f counter not found"), + poseidon2_counter.expect("Poseidon2 counter not found"), arith_eq_counter.expect("ArithEq counter not found"), arith_eq_384_counter.expect("ArithEq384 counter not found"), add256_counter.expect("Add256 counter not found"), @@ -259,6 +271,7 @@ impl StaticSMBundle { let mut arith_collectors = Vec::new(); let mut keccakf_collectors = Vec::new(); let mut sha256f_collectors = Vec::new(); + let mut poseidon2_collectors = Vec::new(); let mut arith_eq_collectors = Vec::new(); let mut arith_eq_384_collectors = Vec::new(); let mut add256_collectors = Vec::new(); @@ -373,6 +386,15 @@ impl StaticSMBundle { sha256f_instance.build_sha256f_collector(ChunkId(chunk_id)); sha256f_collectors.push((*global_idx, sha256f_collector)); } + air_id if air_id == POSEIDON_2_AIR_IDS[0] => { + let poseidon2_instance = secn_instance + .as_any() + .downcast_ref::>() + .unwrap(); + let poseidon2_collector = + poseidon2_instance.build_poseidon2_collector(ChunkId(chunk_id)); + poseidon2_collectors.push((*global_idx, poseidon2_collector)); + } air_id if air_id == ARITH_EQ_AIR_IDS[0] => { let arith_eq_instance = secn_instance .as_any() @@ -416,6 +438,7 @@ impl StaticSMBundle { let mut arith_eq_384_inputs_generator = None; let mut keccakf_inputs_generator = None; let mut sha256f_inputs_generator = None; + let mut poseidon2_inputs_generator = None; let mut arith_inputs_generator = None; let mut add256_inputs_generator = None; for (_, sm) in self.sm.values() { @@ -431,6 +454,10 @@ impl StaticSMBundle { sha256f_inputs_generator = Some(sha256_sm.build_sha256f_input_generator()); } + StateMachines::Poseidon2Manager(poseidon2_sm) => { + poseidon2_inputs_generator = + Some(poseidon2_sm.build_poseidon2_input_generator()); + } StateMachines::ArithEqManager(arith_eq_sm) => { arith_eq_inputs_generator = Some(arith_eq_sm.build_arith_eq_input_generator()); @@ -456,6 +483,7 @@ impl StaticSMBundle { arith_collectors, keccakf_collectors, sha256f_collectors, + poseidon2_collectors, arith_eq_collectors, arith_eq_384_collectors, add256_collectors, @@ -464,6 +492,7 @@ impl StaticSMBundle { arith_eq_384_inputs_generator.expect("ArithEq384 input generator not found"), keccakf_inputs_generator.expect("KeccakF input generator not found"), sha256f_inputs_generator.expect("SHA256F input generator not found"), + poseidon2_inputs_generator.expect("Poseidon2 input generator not found"), arith_inputs_generator.expect("Arith input generator not found"), add256_inputs_generator.expect("Add256 input generator not found"), ); diff --git a/executor/src/static_data_bus.rs b/executor/src/static_data_bus.rs index 92a893f75..e55dde6eb 100644 --- a/executor/src/static_data_bus.rs +++ b/executor/src/static_data_bus.rs @@ -11,6 +11,7 @@ use precomp_arith_eq::ArithEqCounterInputGen; use precomp_arith_eq_384::ArithEq384CounterInputGen; use precomp_big_int::Add256CounterInputGen; use precomp_keccakf::KeccakfCounterInputGen; +use precomp_poseidon2::Poseidon2CounterInputGen; use precomp_sha256f::Sha256fCounterInputGen; use sm_arith::ArithCounterInputGen; use sm_binary::BinaryCounter; @@ -18,8 +19,8 @@ use sm_main::MainCounter; use zisk_common::{BusDevice, BusDeviceMetrics, BusId, PayloadType, MEM_BUS_ID, OPERATION_BUS_ID}; use zisk_core::{ ARITH_EQ_384_OP_TYPE_ID, ARITH_EQ_OP_TYPE_ID, ARITH_OP_TYPE_ID, BIG_INT_OP_TYPE_ID, - BINARY_E_OP_TYPE_ID, BINARY_OP_TYPE_ID, KECCAK_OP_TYPE_ID, PUB_OUT_OP_TYPE_ID, - SHA256_OP_TYPE_ID, + BINARY_E_OP_TYPE_ID, BINARY_OP_TYPE_ID, KECCAK_OP_TYPE_ID, POSEIDON2_OP_TYPE_ID, + PUB_OUT_OP_TYPE_ID, SHA256_OP_TYPE_ID, }; /// A bus system facilitating communication between multiple publishers and subscribers. @@ -42,6 +43,7 @@ pub struct StaticDataBus { pub arith_counter: (usize, ArithCounterInputGen), pub keccakf_counter: (usize, KeccakfCounterInputGen), pub sha256f_counter: (usize, Sha256fCounterInputGen), + pub poseidon2_counter: (usize, Poseidon2CounterInputGen), pub arith_eq_counter: (usize, ArithEqCounterInputGen), pub arith_eq_384_counter: (usize, ArithEq384CounterInputGen), pub add_256_counter: (usize, Add256CounterInputGen), @@ -60,6 +62,7 @@ impl StaticDataBus { arith_counter: (usize, ArithCounterInputGen), keccakf_counter: (usize, KeccakfCounterInputGen), sha256f_counter: (usize, Sha256fCounterInputGen), + poseidon2_counter: (usize, Poseidon2CounterInputGen), arith_eq_counter: (usize, ArithEqCounterInputGen), arith_eq_384_counter: (usize, ArithEq384CounterInputGen), add_256_counter: (usize, Add256CounterInputGen), @@ -73,6 +76,7 @@ impl StaticDataBus { arith_counter, keccakf_counter, sha256f_counter, + poseidon2_counter, arith_eq_counter, arith_eq_384_counter, add_256_counter, @@ -140,6 +144,12 @@ impl StaticDataBus { &mut self.pending_transfers, None, ), + POSEIDON2_OP_TYPE_ID => self.poseidon2_counter.1.process_data( + &bus_id, + payload, + &mut self.pending_transfers, + None, + ), ARITH_EQ_OP_TYPE_ID => self.arith_eq_counter.1.process_data( &bus_id, payload, @@ -186,6 +196,7 @@ impl DataBusTrait> for StaticDataBus> for StaticDataBus { pub keccakf_inputs_generator: KeccakfCounterInputGen, pub sha256f_collector: Vec<(usize, Sha256fCollector)>, pub sha256f_inputs_generator: Sha256fCounterInputGen, + pub poseidon2_collector: Vec<(usize, Poseidon2Collector)>, + pub poseidon2_inputs_generator: Poseidon2CounterInputGen, /// Arithmetic equality collectors pub arith_eq_collector: Vec<(usize, ArithEqCollector)>, @@ -81,6 +85,7 @@ const BINARY_E_TYPE: u64 = ZiskOperationType::BinaryE as u64; const ARITH_TYPE: u64 = ZiskOperationType::Arith as u64; const KECCAK_TYPE: u64 = ZiskOperationType::Keccak as u64; const SHA256_TYPE: u64 = ZiskOperationType::Sha256 as u64; +const POSEIDON2_TYPE: u64 = ZiskOperationType::Poseidon2 as u64; const ARITH_EQ_TYPE: u64 = ZiskOperationType::ArithEq as u64; const ARITH_EQ_384_TYPE: u64 = ZiskOperationType::ArithEq384 as u64; const BIG_INT_OP_TYPE_ID: u64 = ZiskOperationType::BigInt as u64; @@ -97,6 +102,7 @@ impl StaticDataBusCollect { arith_collector: Vec<(usize, ArithInstanceCollector)>, keccakf_collector: Vec<(usize, KeccakfCollector)>, sha256f_collector: Vec<(usize, Sha256fCollector)>, + poseidon2_collector: Vec<(usize, Poseidon2Collector)>, arith_eq_collector: Vec<(usize, ArithEqCollector)>, arith_eq_384_collector: Vec<(usize, ArithEq384Collector)>, add256_collector: Vec<(usize, Add256Collector)>, @@ -105,6 +111,7 @@ impl StaticDataBusCollect { arith_eq_384_inputs_generator: ArithEq384CounterInputGen, keccakf_inputs_generator: KeccakfCounterInputGen, sha256f_inputs_generator: Sha256fCounterInputGen, + poseidon2_inputs_generator: Poseidon2CounterInputGen, arith_inputs_generator: ArithCounterInputGen, add256_inputs_generator: Add256CounterInputGen, ) -> Self { @@ -120,6 +127,7 @@ impl StaticDataBusCollect { arith_collector, keccakf_collector, sha256f_collector, + poseidon2_collector, arith_eq_collector, arith_eq_384_collector, add256_collector, @@ -128,6 +136,7 @@ impl StaticDataBusCollect { arith_eq_384_inputs_generator, keccakf_inputs_generator, sha256f_inputs_generator, + poseidon2_inputs_generator, arith_inputs_generator, add256_inputs_generator, pending_transfers: VecDeque::with_capacity(64), @@ -245,6 +254,22 @@ impl StaticDataBusCollect { Some(&self.mem_collectors_info), ); } + POSEIDON2_TYPE => { + for (_, poseidon2_collector) in &mut self.poseidon2_collector { + poseidon2_collector.process_data( + &bus_id, + payload, + &mut self.pending_transfers, + None, + ); + } + self.poseidon2_inputs_generator.process_data( + &bus_id, + payload, + &mut self.pending_transfers, + Some(&self.mem_collectors_info), + ); + } ARITH_EQ_TYPE => { for (_, arith_eq_collector) in &mut self.arith_eq_collector { arith_eq_collector.process_data( @@ -368,6 +393,10 @@ impl DataBusTrait>> result.push((Some(id), Some(Box::new(collector) as Box>))); } + for (id, collector) in self.poseidon2_collector { + result.push((Some(id), Some(Box::new(collector) as Box>))); + } + for (id, collector) in self.arith_eq_collector { result.push((Some(id), Some(Box::new(collector) as Box>))); } diff --git a/pil/operations.pil b/pil/operations.pil index 2d36e2dc2..f873069a5 100644 --- a/pil/operations.pil +++ b/pil/operations.pil @@ -9,7 +9,7 @@ - Arithmetic Operations: - 0xB0-0xBF - Precompiles: - - 0xE2-0xE7 + - 0xE1-0xE7 - 0xF0-0xF5 - 0xF9-0xFE - Misc: @@ -83,6 +83,8 @@ const int OP_REMU_W = 0xBD; const int OP_DIV_W = 0xBE; const int OP_REM_W = 0xBF; +const int OP_POSEIDON2 = 0xE1; + const int OP_ARITH_384_MOD = 0xE2; const int OP_EC_ADD_BLS12_381 = 0xE3; const int OP_EC_DBL_BLS12_381 = 0xE4; @@ -90,6 +92,7 @@ const int OP_COMPLEX_ADD_BLS12_381 = 0xE5; const int OP_COMPLEX_SUB_BLS12_381 = 0xE6; const int OP_COMPLEX_MUL_BLS12_381 = 0xE7; + const int OP_ADD256 = 0xF0; const int OP_KECCAKF = 0xF1; const int OP_ARITH_256 = 0xF2; diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 3710d19d9..0b6eb1d5a 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -62,12 +62,13 @@ pub const KECCAKF_AIR_IDS: &[usize] = &[16]; pub const SHA_256_F_AIR_IDS: &[usize] = &[17]; -pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[18]; +pub const POSEIDON_2_AIR_IDS: &[usize] = &[13]; -pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[19]; +pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[14]; -pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[20]; +pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[15]; +pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[16]; //PUBLICS use serde::Deserialize; @@ -358,38 +359,51 @@ pub type Sha256fTrace = GenericTrace, 262144, 0, 17>; pub type Sha256fTracePacked = GenericTrace, 262144, 0, 17>; +trace_row!(Poseidon2FixedRow { + CLK_0: F, __L1__: F, +}); +pub type Poseidon2Fixed = GenericTrace, 131072, 0, 13>; + +trace_row!(Poseidon2TraceRow { + in_use_clk_0:bit, in_use:bit, chunks:[[u32; 2]; 16], step_addr:ubit(40), +}); +pub type Poseidon2Trace = GenericTrace, 131072, 0, 13>; + + +pub type Poseidon2TracePacked = GenericTrace, 131072, 0, 13>; + trace_row!(SpecifiedRangesFixedRow { - RANGE: [F; 33], __L1__: F, + RANGE: [F; 20], __L1__: F, }); -pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 18>; +pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 14>; trace_row!(SpecifiedRangesTraceRow { - mul:[F; 33], + mul:[F; 20], }); -pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 18>; +pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 14>; trace_row!(VirtualTable0FixedRow { - UID: [F; 8], column: [F; 43], __L1__: F, + UID: [F; 7], column: [F; 39], __L1__: F, }); -pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 19>; +pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 15>; trace_row!(VirtualTable0TraceRow { - multiplicity:[F; 8], + multiplicity:[F; 7], }); -pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 19>; +pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 15>; trace_row!(VirtualTable1FixedRow { UID: [F; 8], column: [F; 64], __L1__: F, }); -pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 20>; +pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 16>; trace_row!(VirtualTable1TraceRow { multiplicity:[F; 8], }); -pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 20>; +pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 16>; trace_row!(RomRomTraceRow { @@ -490,23 +504,7 @@ values!(BinaryExtensionAirGroupValues { gsum_result: FieldExtension, }); -values!(Add256AirGroupValues { - gsum_result: FieldExtension, -}); - -values!(ArithEqAirGroupValues { - gsum_result: FieldExtension, -}); - -values!(ArithEq384AirGroupValues { - gsum_result: FieldExtension, -}); - -values!(KeccakfAirGroupValues { - gsum_result: FieldExtension, -}); - -values!(Sha256fAirGroupValues { +values!(Poseidon2AirGroupValues { gsum_result: FieldExtension, }); @@ -585,27 +583,7 @@ pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ }), (0, 13, PackedInfoConst { is_packed: true, - num_packed_words: 15, - unpack_info: &[32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 40, 1, 1], - }), - (0, 14, PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], - }), - (0, 15, PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], - }), - (0, 16, PackedInfoConst { - is_packed: true, - num_packed_words: 209, - unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 8, 40], - }), - (0, 17, PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 40, 1, 1], + num_packed_words: 17, + unpack_info: &[1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40], }), ]; \ No newline at end of file diff --git a/pil/zisk.pil b/pil/zisk.pil index f8b058082..ddb451838 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -18,6 +18,7 @@ require "arith_eq/pil/arith_eq.pil" require "arith_eq_384/pil/arith_eq_384.pil" require "keccakf/pil/keccakf.pil" require "sha256f/pil/sha256f.pil" +require "poseidon2/pil/poseidon2.pil" proofval enable_input_data; enable_input_data * (1 - enable_input_data); @@ -81,6 +82,8 @@ airgroup Zisk { Sha256f(N: 2**18); + Poseidon2(N: 2**17); + // Public Inputs for (int i = 0; i < PUBLIC_INPUTS_64_BITS; i++) { direct_global_update_proves(OPERATION_BUS_ID, [OP_PUBOUT, i, 0, inputs[i*2], inputs[i*2 + 1], inputs[i*2], inputs[i*2 + 1], 0]); diff --git a/precompiles/poseidon2/Cargo.toml b/precompiles/poseidon2/Cargo.toml new file mode 100644 index 000000000..788c87d88 --- /dev/null +++ b/precompiles/poseidon2/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "precomp-poseidon2" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +keywords = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } + +[dependencies] +zisk-core = { workspace = true } +zisk-common = { workspace = true } +zisk-pil = { workspace = true } +precompiles-common = { workspace = true } +sm-mem = { workspace = true } +mem-common = { workspace = true } + +proofman = { workspace = true } +proofman-common = { workspace = true } +proofman-macros = { workspace = true } +proofman-util = { workspace = true } +pil-std-lib = { workspace = true } +fields = { workspace=true } +tracing = { workspace = true } +rayon = { workspace = true } +sha2 = { workspace = true } + +[features] +default = [] +gpu = ["proofman-common/gpu", "packed"] +packed = ["proofman-common/packed"] +no_lib_link = ["proofman-common/no_lib_link"] +diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] +disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file diff --git a/precompiles/poseidon2/pil/poseidon2.pil b/precompiles/poseidon2/pil/poseidon2.pil new file mode 100644 index 000000000..a9a3522d7 --- /dev/null +++ b/precompiles/poseidon2/pil/poseidon2.pil @@ -0,0 +1,263 @@ +require "std_lookup.pil" +require "operations.pil" +require "opids.pil" +require "poseidon2_constants.pil" + +// Precompile in charge of performing the Poseidon2 permutation. +// For reference: https://eprint.iacr.org/2023/323.pdf + + +airtemplate Poseidon2(const int N = 2**17, const int operation_bus_id = OPERATION_BUS_ID) { + const int n = 16; + + // Compute some stats + const int CLOCKS = 14; + + const int NUM_NON_USABLE_ROWS = N % CLOCKS; + const int NUM_POSEIDON2; + if (NUM_NON_USABLE_ROWS == 0) { + // N is perfectly divisible by CLOCKS + if (N < CLOCKS) { + error(`N must be at least ${CLOCKS} to fit the Poseidon2 arithmetization, but received N=${N}`); + } + + NUM_POSEIDON2 = N / CLOCKS; + } else { + // N is not divisible by CLOCKS + if (N < 2*CLOCKS) { + error(`N must be at least ${2*CLOCKS} to fit the Poseidon2 arithmetization, but received N=${N}`); + } + + // Subtract 1 because we can't fit a complete cycle in the remaining rows + NUM_POSEIDON2 = (N - NUM_NON_USABLE_ROWS) / CLOCKS - 1; + } + + col fixed CLK_0 = [[1, 0:(CLOCKS-1)]:NUM_POSEIDON2, 0...]; + + const expr CLK[CLOCKS]; + for (int i = 0; i < CLOCKS; i++) { + CLK[i] = (i)'CLK_0; + } + + const expr r0 = 'CLK_0; + const expr r1 = (2)'CLK_0; + const expr r2 = (3)'CLK_0; + const expr r3 = (4)'CLK_0; + const expr r4 = (5)'CLK_0; + const expr p1 = (6)'CLK_0; + const expr r15 = (7)'CLK_0; + const expr p2 = (8)'CLK_0; + const expr r26 = (9)'CLK_0; + const expr r27 = (10)'CLK_0; + const expr r28 = (11)'CLK_0; + const expr r29 = (12)'CLK_0; + + const expr full_round = r0 + r1 + r2 + r3 + r26 + r27 + r28 + r29; + + // Define the clock selectors + col witness bits(1) in_use_clk_0; // 1 at the first clock cycle of the Poseidon2 operation, 0 otherwise + col witness bits(1) in_use; // 1 when the Poseidon2 operation is in use, 0 otherwise + + in_use_clk_0 * (1 - in_use_clk_0) === 0; + in_use * (1 - in_use) === 0; + + in_use_clk_0 - CLK_0 * in_use === 0; // This constraint is two-fold: + // · in_use_clk_0 can only be active when CLK_0 is active + // · if in_use is active then so is in_use_clk_0 + + const expr in_use_active = clock_set(start: 1, end: CLOCKS); + in_use_active * (in_use - 'in_use) === 0; // selector latching + + // --> Constraints to assert the Poseidon2 permutation + + // Poseidon2 state represented as a 1D array of elements of 64 bits, represented in two limbs of 32 bits each + col witness bits(32) chunks[n][2]; + + const expr prev_state[n]; + const expr state[n]; + const expr next_state[n]; + for (int i = 0; i < n; i++) { + prev_state[i] = 'chunks[i][1] * 2**32 + 'chunks[i][0]; + state[i] = chunks[i][1] * 2**32 + chunks[i][0]; + next_state[i] = chunks[i][1]' * 2**32 + chunks[i][0]'; + } + + poseidonFullRound(n, state, next_state, in_use_clk_0); + + const expr state_7[n]; + const expr rc[n]; + const expr im_values[11]; + const expr im_values_7[11]; + for (int i = 0; i < n; i++) { + rc[i] = r0 * RC_16[i] + r1 * RC_16[i + n] + r2 * RC_16[i + 2*n] + r3 * RC_16[i + 3*n] + p1 * RC_16[i + 4*n] + p2 * RC_16[i + 4*n + 11] + r26 * RC_16[i + 4*n + 22] + r27 * RC_16[i + 5*n + 22] + r28 * RC_16[i + 6*n + 22] + r29 * RC_16[i + 7*n + 22]; + state_7[i] = pow7(state[i] + rc[i]); + if (i < 11) { + im_values[i] = state[i]; + im_values_7[i] = state_7[i]; + } + } + + poseidonFullRound(n, state_7, next_state, full_round); + + poseidonPartialRound(n, 11, prev_state, next_state, im_values, im_values_7, D_16, p1 + p2); + + // --> Constraints to read inputs from memory and write outputs to memory + /* + MEMORY ACCESS MAP + ===================================================================================== + 0 | STEP_MAIN | R | ADDR_STATE | input_state[0] + 1 | STEP_MAIN | R | ADDR_STATE + 8 | input_state[1] + 2 | STEP_MAIN | R | ADDR_STATE + 16 | input_state[2] + . | ... | . | ... | ... + n - 1 | STEP_MAIN | R | ADDR_STATE + (n - 1)*8 | input_state[n-1] + n | STEP_MAIN + 1 | W | ADDR_STATE | output_state[0] + n + 1 | STEP_MAIN + 1 | W | ADDR_STATE + 8 | output_state[1] + n + 2 | STEP_MAIN + 1 | W | ADDR_STATE + 16 | output_state[2] + . | ... | . | ... | ... + 2*n - 1 | STEP_MAIN + 1 | . | ADDR_STATE + (n - 1)*8 | output_state[n-1] + ===================================================================================== + */ + col witness bits(40) step_addr; + + const int STEP_MAIN = 0; + const int ADDR_STATE = STEP_MAIN + 1; + + const int MEM_OPS = n * 64; + const int MEM_OPS_PARALLEL = n / 4; + + const expr mem_sel = clock_set(in_use, start: 0, end: 4) + clock_set(in_use, start: CLOCKS - 4, end: CLOCKS); + + const expr mem_is_write = clock_set(start: CLOCKS - 4, end: CLOCKS); + + const expr main_step = clock_shift(step_addr, STEP_MAIN, start: 0, end: 4) + clock_shift(step_addr, STEP_MAIN, start: CLOCKS - 4, end: CLOCKS); + + for (int i = 0; i < MEM_OPS_PARALLEL; i++) { + const expr mem_addr = clock_shift(step_addr, ADDR_STATE, start: 0, end: 4, offset: 8*i, delta: 8 * 4) + clock_shift(step_addr, ADDR_STATE, start: CLOCKS - 4, end: CLOCKS, offset: 8*i, delta: 8 * 4); + + expr mem_value[2]; + mem_value[0] = 0; + mem_value[1] = 0; + + // Reads + for(int j = 0; j < 4; j++) { + mem_value[0] += (j)'CLK_0 * (j)'chunks[MEM_OPS_PARALLEL*j + i][0]; + mem_value[1] += (j)'CLK_0 * (j)'chunks[MEM_OPS_PARALLEL*j + i][1]; + } + + // Writes + for(int j = 0; j < 4; j++) { + mem_value[0] += (CLOCKS - 4 + j)'CLK_0 * chunks[MEM_OPS_PARALLEL*j + i][0]'(3 - j); + mem_value[1] += (CLOCKS - 4 + j)'CLK_0 * chunks[MEM_OPS_PARALLEL*j + i][1]'(3 - j); + } + + precompiled_mem_op( + sel: mem_sel, + is_write: mem_is_write, + main_step: main_step, + addr: mem_addr, + value: mem_value + ); + } + + + // --> Constraints to make sure that this coprocessor is called from the main processor + lookup_proves(operation_bus_id, [OP_POSEIDON2, step_addr'(STEP_MAIN), 0, step_addr'(ADDR_STATE), 0, 0, 0, 0], mul: in_use_clk_0); + + function clock_set(const expr mvcol = 1, const int start = 0, int end = -1): const expr { + expr result = 0; + if (end == -1) { + end = start; + } + for (int i = start; i < end; i++) { + result += air.CLK[i]; + } + return result * mvcol; + } + + function clock_shift(const expr mvcol, const int pos, const int start = 0, int end = -1, int offset = 0, const int delta = 0): const expr { + expr result = 0; + if (end == -1) { + end = start + 1; + } + for (int i = start; i < end; i++) { + const int iclock = (pos - i) % air.CLOCKS; + if (offset != 0) { + result += air.CLK[i] * (mvcol'(iclock) + offset); + } else { + result += air.CLK[i] * mvcol'(iclock); + } + offset += delta; + } + return result; + } + + function pow7(const expr input): const expr { + const expr input3 = input * input * input; + const expr input6 = input3 * input3; + return input6 * input; + } + + function poseidonFullRound(const int n, const expr input[], const expr output[], const expr sel) { + const expr mat[n]; + + const expr t0[n/4], t1[n/4], t2[n/4], t3[n/4]; + for (int i = 0; i < n/4; i++) { + t0[i] = input[4*i] + input[4*i + 1]; + t1[i] = input[4*i + 2] + input[4*i + 3]; + t2[i] = 2*input[4*i + 1] + t1[i]; + t3[i] = 2*input[4*i + 3] + t0[i]; + + mat[4*i + 3] = 4*t1[i] + t3[i]; + mat[4*i + 1] = 4*t0[i] + t2[i]; + mat[4*i] = t3[i] + mat[4*i + 1]; + mat[4*i + 2] = t2[i] + mat[4*i + 3]; + + } + + expr stored[n/4]; + for (int i = 0; i < n/4; ++i) { + stored[i] = 0; + } + + for (int i = 0; i < n/4; i++) { + for (int j = 0; j < 4; j++) { + stored[j] += mat[4*i + j]; + } + } + + for (int i = 0; i < n; i++) { + sel * (output[i] - (mat[i] + stored[i%4])) === 0; + } + } + + function poseidonPartialRound(const int n, const int rounds, const expr input[], const expr output[], const expr st0[], const expr intermediateValues[], const int D[], const expr sel) { + const expr sR[rounds + 1][n]; + const expr sum_partial[rounds]; + for (int i = 0; i < n; ++i) { + sR[0][i] = input[i]; + } + + for (int i = 0; i < rounds; i++) { + sel * (st0[i] - sR[i][0]) === 0; + sum_partial[i] = partial_sum(n, sR[i], intermediateValues[i]); + + sR[i+1][0] = intermediateValues[i] * D[0] + sum_partial[i]; + for (int j = 1; j < n; j++) { + sR[i+1][j] = sR[i][j] * D[j] + sum_partial[i]; + } + } + + for (int i = 0; i < n; i++) { + sel * (output[i] - sR[rounds][i]) === 0; + } + } + + function partial_sum(const int n, const expr sR[], const expr intermediateValue) : const expr { + const expr res[n-1]; + res[0] = intermediateValue + sR[1]; + for (int i = 2; i < n; i++) { + res[i - 1] = res[i - 2] + sR[i]; + } + return res[n-2]; + } +} \ No newline at end of file diff --git a/precompiles/poseidon2/pil/poseidon2_constants.pil b/precompiles/poseidon2/pil/poseidon2_constants.pil new file mode 100644 index 000000000..3e0e69c45 --- /dev/null +++ b/precompiles/poseidon2/pil/poseidon2_constants.pil @@ -0,0 +1,271 @@ +const int D_8[8] = [ + 0xa98811a1fed4e3a5, + 0x1cc48b54f377e2a0, + 0xe40cd4f6c5609a26, + 0x11de79ebca97a4a3, + 0x9177c73d8b7e929c, + 0x2a6fe8085797e791, + 0x3de6e93329f8d5ad, + 0x3f7af9125da962fe +]; + +const int D_16[16] = [ + 0xde9b91a467d6afc0, + 0xc5f16b9c76a9be17, + 0x0ab0fef2d540ac55, + 0x3001d27009d05773, + 0xed23b1f906d3d9eb, + 0x5ce73743cba97054, + 0x1c3bab944af4ba24, + 0x2faa105854dbafae, + 0x53ffb3ae6d421a10, + 0xbcda9df8884ba396, + 0xfc1273e4a31807bb, + 0xc77952573d5142c0, + 0x56683339a819b85e, + 0x328fcbd8f0ddc8eb, + 0xb5101e303fce9cb7, + 0x774487b8c40089bb +]; + +const int RC_8[86] = [ + 0xdd5743e7f2a5a5d9, + 0xcb3a864e58ada44b, + 0xffa2449ed32f8cdc, + 0x42025f65d6bd13ee, + 0x7889175e25506323, + 0x34b98bb03d24b737, + 0xbdcc535ecc4faa2a, + 0x5b20ad869fc0d033, + 0xf1dda5b9259dfcb4, + 0x27515210be112d59, + 0x4227d1718c766c3f, + 0x26d333161a5bd794, + 0x49b938957bf4b026, + 0x4a56b5938b213669, + 0x1120426b48c8353d, + 0x6b323c3f10a56cad, + 0xce57d6245ddca6b2, + 0xb1fc8d402bba1eb1, + 0xb5c5096ca959bd04, + 0x6db55cd306d31f7f, + 0xc49d293a81cb9641, + 0x1ce55a4fe979719f, + 0xa92e60a9d178a4d1, + 0x002cc64973bcfd8c, + 0xcea721cce82fb11b, + 0xe5b55eb8098ece81, + 0x4e30525c6f1ddd66, + 0x43c6702827070987, + 0xaca68430a7b5762a, + 0x3674238634df9c93, + 0x88cee1c825e33433, + 0xde99ae8d74b57176, + 0x488897d85ff51f56, + 0x1140737ccb162218, + 0xa7eeb9215866ed35, + 0x9bd2976fee49fcc9, + 0xc0c8f0de580a3fcc, + 0x4fb2dae6ee8fc793, + 0x343a89f35f37395b, + 0x223b525a77ca72c8, + 0x56ccb62574aaa918, + 0xc4d507d8027af9ed, + 0xa080673cf0b7e95c, + 0xf0184884eb70dcf8, + 0x044f10b0cb3d5c69, + 0xe9e3f7993938f186, + 0x1b761c80e772f459, + 0x606cec607a1b5fac, + 0x14a0c2e1d45f03cd, + 0x4eace8855398574f, + 0xf905ca7103eff3e6, + 0xf8c8f8d20862c059, + 0xb524fe8bdd678e5a, + 0xfbb7865901a1ec41, + 0x014ef1197d341346, + 0x9725e20825d07394, + 0xfdb25aef2c5bae3b, + 0xbe5402dc598c971e, + 0x93a5711f04cdca3d, + 0xc45a9a5b2f8fb97b, + 0xfe8946a924933545, + 0x2af997a27369091c, + 0xaa62c88e0b294011, + 0x058eb9d810ce9f74, + 0xb3cb23eced349ae4, + 0xa3648177a77b4a84, + 0x43153d905992d95d, + 0xf4e2a97cda44aa4b, + 0x5baa2702b908682f, + 0x082923bdf4f750d1, + 0x98ae09a325893803, + 0xf8a6475077968838, + 0xceb0735bf00b2c5f, + 0x0a1a5d953888e072, + 0x2fcb190489f94475, + 0xb5be06270dec69fc, + 0x739cb934b09acf8b, + 0x537750b75ec7f25b, + 0xe9dd318bae1f3961, + 0xf7462137299efe1a, + 0xb1f6b8eee9adb940, + 0xbdebcc8a809dfe6b, + 0x40fc1f791b178113, + 0x3ac1c3362d014864, + 0x9a016184bdb8aeba, + 0x95f2394459fbc25e +]; + +const int RC_16[150] = [ + 0x15ebea3fc73397c3, + 0xd73cd9fbfe8e275c, + 0x8c096bfce77f6c26, + 0x4e128f68b53d8fea, + 0x29b779a36b2763f6, + 0xfe2adc6fb65acd08, + 0x8d2520e725ad0955, + 0x1c2392b214624d2a, + 0x37482118206dcc6e, + 0x2f829bed19be019a, + 0x2fe298cb6f8159b0, + 0x2bbad982deccdbbf, + 0xbad568b8cc60a81e, + 0xb86a814265baad10, + 0xbec2005513b3acb3, + 0x6bf89b59a07c2a94, + 0xa25deeb835e230f5, + 0x3c5bad8512b8b12a, + 0x7230f73c3cb7a4f2, + 0xa70c87f095c74d0f, + 0x6b7606b830bb2e80, + 0x6cd467cfc4f24274, + 0xfeed794df42a9b0a, + 0x8cf7cf6163b7dbd3, + 0x9a6e9dda597175a0, + 0xaa52295a684faf7b, + 0x017b811cc3589d8d, + 0x55bfb699b6181648, + 0xc2ccaf71501c2421, + 0x1707950327596402, + 0xdd2fcdcd42a8229f, + 0x8b9d7d5b27778a21, + 0xac9a05525f9cf512, + 0x2ba125c58627b5e8, + 0xc74e91250a8147a5, + 0xa3e64b640d5bb384, + 0xf53047d18d1f9292, + 0xbaaeddacae3a6374, + 0xf2d0914a808b3db1, + 0x18af1a3742bfa3b0, + 0x9a621ef50c55bdb8, + 0xc615f4d1cc5466f3, + 0xb7fbac19a35cf793, + 0xd2b1a15ba517e46d, + 0x4a290c4d7fd26f6f, + 0x4f0cf1bb1770c4c4, + 0x548345386cd377f5, + 0x33978d2789fddd42, + 0xab78c59deb77e211, + 0xc485b2a933d2be7f, + 0xbde3792c00c03c53, + 0xab4cefe8f893d247, + 0xc5c0e752eab7f85f, + 0xdbf5a76f893bafea, + 0xa91f6003e3d984de, + 0x099539077f311e87, + 0x097ec52232f9559e, + 0x53641bdf8991e48c, + 0x2afe9711d5ed9d7c, + 0xa7b13d3661b5d117, + 0x5a0e243fe7af6556, + 0x1076fae8932d5f00, + 0x9b53a83d434934e3, + 0xed3fd595a3c0344a, + 0x28eff4b01103d100, + 0x60400ca3e2685a45, + 0x1c8636beb3389b84, + 0xac1332b60e13eff0, + 0x2adafcc364e20f87, + 0x79ffc2b14054ea0b, + 0x3f98e4c0908f0a05, + 0xcdb230bc4e8a06c4, + 0x1bcaf7705b152a74, + 0xd9bca249a82a7470, + 0x91e24af19bf82551, + 0xa62b43ba5cb78858, + 0xb4898117472e797f, + 0xb3228bca606cdaa0, + 0x844461051bca39c9, + 0xf3411581f6617d68, + 0xf7fd50646782b533, + 0x6ca664253c18fb48, + 0x2d2fcdec0886a08f, + 0x29da00dd799b575e, + 0x47d966cc3b6e1e93, + 0xde884e9a17ced59e, + 0xdacf46dc1c31a045, + 0x5d2e3c121eb387f2, + 0x51f8b0658b124499, + 0x1e7dbd1daa72167d, + 0x8275015a25c55b88, + 0xe8521c24ac7a70b3, + 0x6521d121c40b3f67, + 0xac12de797de135b0, + 0xafa28ead79f6ed6a, + 0x685174a7a8d26f0b, + 0xeff92a08d35d9874, + 0x3058734b76dd123a, + 0xfa55dcfba429f79c, + 0x559294d4324c7728, + 0x7a770f53012dc178, + 0xedd8f7c408f3883b, + 0x39b533cf8d795fa5, + 0x160ef9de243a8c0a, + 0x431d52da6215fe3f, + 0x54c51a2a2ef6d528, + 0x9b13892b46ff9d16, + 0x263c46fcee210289, + 0xb738c96d25aabdc4, + 0x5c33a5203996d38f, + 0x2626496e7c98d8dd, + 0xc669e0a52785903a, + 0xaecde726c8ae1f47, + 0x039343ef3a81e999, + 0x2615ceaf044a54f9, + 0x7e41e834662b66e1, + 0x4ca5fd4895335783, + 0x64b334d02916f2b0, + 0x87268837389a6981, + 0x034b75bcb20a6274, + 0x58e658296cc2cd6e, + 0xe2d0f759acc31df4, + 0x81a652e435093e20, + 0x0b72b6e0172eaf47, + 0x4aec43cec577d66d, + 0xde78365b028a84e6, + 0x444e19569adc0ee4, + 0x942b2451fa40d1da, + 0xe24506623ea5bd6c, + 0x082854bf2ef7c743, + 0x69dbbc566f59d62e, + 0x248c38d02a7b5cb2, + 0x4f4e8f8c09d15edb, + 0xd96682f188d310cf, + 0x6f9a25d56818b54c, + 0xb6cefed606546cd9, + 0x5bc07523da38a67b, + 0x7df5a3c35b8111cf, + 0xaaa2cc5d4db34bb0, + 0x9e673ff22a4653f8, + 0xbd8b278d60739c62, + 0xe10d20f6925b8815, + 0xf6c87b91dd4da2bf, + 0xfed623e2f71b6f1a, + 0xa0f02fa52a94d0d3, + 0xbb5794711b39fa16, + 0xd3b94fba9d005c7f, + 0x15a26e89fad946c9, + 0xf3cb87db8a67cf49, + 0x400d2bf56aa2a577 +]; \ No newline at end of file diff --git a/precompiles/poseidon2/src/lib.rs b/precompiles/poseidon2/src/lib.rs new file mode 100644 index 000000000..b673e370e --- /dev/null +++ b/precompiles/poseidon2/src/lib.rs @@ -0,0 +1,15 @@ +mod poseidon2; +mod poseidon2_bus_device; +mod poseidon2_gen_mem_inputs; +mod poseidon2_input; +mod poseidon2_instance; +mod poseidon2_manager; +mod poseidon2_planner; + +pub use poseidon2::*; +pub use poseidon2_bus_device::*; +pub use poseidon2_gen_mem_inputs::*; +pub use poseidon2_input::*; +pub use poseidon2_instance::*; +pub use poseidon2_manager::*; +pub use poseidon2_planner::*; diff --git a/precompiles/poseidon2/src/poseidon2.rs b/precompiles/poseidon2/src/poseidon2.rs new file mode 100644 index 000000000..e8ebbf9ef --- /dev/null +++ b/precompiles/poseidon2/src/poseidon2.rs @@ -0,0 +1,226 @@ +use core::panic; +use std::sync::Arc; + +use fields::{ + add, matmul_external, pow7, pow7add, prodadd, Poseidon16, Poseidon2Constants, PrimeField64, +}; +use rayon::prelude::*; + +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +#[cfg(not(feature = "packed"))] +use zisk_pil::{Poseidon2Trace, Poseidon2TraceRow}; +#[cfg(feature = "packed")] +use zisk_pil::{Poseidon2TracePacked, Poseidon2TraceRowPacked}; + +#[cfg(feature = "packed")] +type Poseidon2TraceRowType = Poseidon2TraceRowPacked; +#[cfg(feature = "packed")] +type Poseidon2TraceType = Poseidon2TracePacked; + +#[cfg(not(feature = "packed"))] +type Poseidon2TraceRowType = Poseidon2TraceRow; +#[cfg(not(feature = "packed"))] +type Poseidon2TraceType = Poseidon2Trace; + +use super::Poseidon2Input; + +/// The `Poseidon2SM` struct encapsulates the logic of the Poseidon2 State Machine. +pub struct Poseidon2SM { + /// Number of available poseidon2s in the trace. + pub num_available_poseidon2s: usize, + _phantom: std::marker::PhantomData, +} + +pub const CLOCKS: usize = 14; + +impl Poseidon2SM { + /// Creates a new Poseidon2 State Machine instance. + /// + /// # Returns + /// A new `Poseidon2SM` instance. + pub fn new() -> Arc { + // Compute some useful values + let num_available_poseidon2s = Poseidon2TraceType::::NUM_ROWS / CLOCKS - 1; + + Arc::new(Self { num_available_poseidon2s, _phantom: std::marker::PhantomData }) + } + + /// Processes a slice of operation data, updating the trace and multiplicities. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Poseidon2 trace. + /// * `num_circuits` - The number of circuits to process. + /// * `input` - The operation data to process. + /// * `multiplicity` - A mutable slice to update with multiplicities for the operation. + #[inline(always)] + pub fn process_input( + &self, + trace: &mut [Poseidon2TraceRowType], + input: &Poseidon2Input, + is_active: bool, + ) { + // Fill the states + let mut round_states = [[0u64; 16]; CLOCKS]; + round_states[0] = input.state; + + let mut state = input.state.map(|x| F::from_u64(x)); + matmul_external::(&mut state); + round_states[1] = state.map(|x| x.as_canonical_u64()); + + for r in 0..Poseidon16::HALF_ROUNDS { + let mut c_slice = [F::ZERO; 16]; + for (i, c) in c_slice.iter_mut().enumerate() { + *c = F::from_u64(Poseidon16::RC[r * 16 + i]); + } + pow7add::(&mut state, &c_slice); + matmul_external::(&mut state); + round_states[2 + r] = state.map(|x| x.as_canonical_u64()); + } + + let mut row = 6; + let mut index = 0; + for r in 0..Poseidon16::N_PARTIAL_ROUNDS { + round_states[row][index] = state[0].as_canonical_u64(); + index += 1; + + state[0] += F::from_u64(Poseidon16::RC[Poseidon16::HALF_ROUNDS * 16 + r]); + state[0] = pow7(state[0]); + let sum = add::(&state); + prodadd::(&mut state, Poseidon16::DIAG, sum); + if r == 10 { + round_states[7] = state.map(|x| x.as_canonical_u64()); + row = 8; + index = 0; + } + } + + round_states[9] = state.map(|x| x.as_canonical_u64()); + + for r in 0..Poseidon16::HALF_ROUNDS { + let mut c_slice = [F::ZERO; 16]; + for (i, c) in c_slice.iter_mut().enumerate() { + *c = F::from_u64( + Poseidon16::RC + [Poseidon16::HALF_ROUNDS * 16 + Poseidon16::N_PARTIAL_ROUNDS + r * 16 + i], + ); + } + pow7add::(&mut state, &c_slice); + matmul_external::(&mut state); + round_states[10 + r] = state.map(|x| x.as_canonical_u64()); + } + + for r in 0..CLOCKS { + for (i, &state) in round_states[r].iter().enumerate() { + trace[r].set_chunks(i, 0, state as u32); + trace[r].set_chunks(i, 1, (state >> 32) as u32); + } + } + + if !is_active { + return; + } + + // Fill step and addr + trace[0].set_step_addr(input.step_main); + trace[1].set_step_addr(input.addr_main as u64); + + // Fill in_use_clk_0 + trace[0].set_in_use_clk_0(true); + + // Fill in_use + for item in trace.iter_mut().take(CLOCKS) { + item.set_in_use(true); + } + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut poseidon2_trace = Poseidon2TraceType::new_from_vec_zeroes(trace_buffer)?; + let num_rows = poseidon2_trace.num_rows(); + let num_available_poseidon2s = self.num_available_poseidon2s; + + // Check that we can fit all the poseidon2s in the trace + let num_inputs = inputs.iter().map(|v| v.len()).sum::(); + let num_rows_needed = if num_inputs < num_available_poseidon2s { + num_inputs * CLOCKS + } else if num_inputs == num_available_poseidon2s { + num_rows + } else { + panic!( + "Exceeded available Poseidon2 inputs: requested {}, but only {} are available.", + num_inputs, self.num_available_poseidon2s + ); + }; + + tracing::debug!( + "··· Creating Poseidon2 instance [{}{{}} / {} rows filled {:.2}%]", + num_rows_needed, + num_rows, + (num_rows_needed as f64 / num_rows as f64 * 100.0) as usize + ); + + timer_start_trace!(POSEIDON2_TRACE); + let mut trace_rows = poseidon2_trace.buffer.as_mut_slice(); + let mut par_traces = Vec::new(); + let mut inputs_indexes = Vec::new(); + for (i, inputs) in inputs.iter().enumerate() { + for (j, _) in inputs.iter().enumerate() { + let (head, tail) = trace_rows.split_at_mut(CLOCKS); + par_traces.push(head); + inputs_indexes.push((i, j)); + trace_rows = tail; + } + } + + // Fill the trace + par_traces.into_par_iter().enumerate().for_each(|(index, trace)| { + let input_index = inputs_indexes[index]; + let input = &inputs[input_index.0][input_index.1]; + self.process_input(trace, input, true); + }); + + timer_stop_and_log_trace!(POSEIDON2_TRACE); + + timer_start_trace!(POSEIDON2_PADDING); + + // 3] Fill the padding rows with Poseidon2(0) + let padding_rows_start = num_rows_needed; + let padding_rows_end: usize = + padding_rows_start + ((num_available_poseidon2s - num_inputs) * CLOCKS); + + // Split the padding trace into padding chunks + let padding_trace = &mut poseidon2_trace.buffer[padding_rows_start..padding_rows_end]; + let mut padding_chunks: Vec<_> = padding_trace.chunks_mut(CLOCKS).collect(); + + // Process padding in parallel + if let Some((first, rest)) = padding_chunks.split_first_mut() { + self.process_input( + first, + &Poseidon2Input { state: [0; 16], step_main: 0, addr_main: 0 }, + false, + ); + + rest.par_iter_mut().for_each(|chunk| { + chunk.copy_from_slice(first); + }); + } + + // 4] The non-usable rows should be zeroes, which are already set at initialization + + timer_stop_and_log_trace!(POSEIDON2_PADDING); + + Ok(AirInstance::new_from_trace(FromTrace::new(&mut poseidon2_trace))) + } +} diff --git a/precompiles/poseidon2/src/poseidon2_bus_device.rs b/precompiles/poseidon2/src/poseidon2_bus_device.rs new file mode 100644 index 000000000..d3c65ca33 --- /dev/null +++ b/precompiles/poseidon2/src/poseidon2_bus_device.rs @@ -0,0 +1,148 @@ +//! The `Poseidon2Counter` module defines a counter for tracking poseidon2-related operations +//! sent over the data bus. It connects to the bus and gathers metrics for specific +//! `ZiskOperationType::Poseidon2` instructions. + +use std::{collections::VecDeque, ops::Add}; + +use zisk_common::MemCollectorInfo; +use zisk_common::{ + BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE, +}; +use zisk_core::ZiskOperationType; + +use crate::{generate_poseidon2_mem_inputs, skip_poseidon2_mem_inputs}; + +/// The `Poseidon2Counter` struct represents a counter that monitors and measures +/// poseidon2-related operations on the data bus. +/// +/// It tracks specific operation types (`ZiskOperationType`) and updates counters for each +/// accepted operation type whenever data is processed on the bus. +pub struct Poseidon2CounterInputGen { + /// Poseidon2 counter. + counter: Counter, + + /// Bus device mode (counter or input generator). + mode: BusDeviceMode, +} + +impl Poseidon2CounterInputGen { + /// Creates a new instance of `Poseidon2Counter`. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus to which this counter is connected. + /// * `op_type` - A vector of `ZiskOperationType` instructions to monitor. + /// + /// # Returns + /// A new `Poseidon2Counter` instance. + pub fn new(mode: BusDeviceMode) -> Self { + Self { counter: Counter::default(), mode } + } + + /// Retrieves the count of instructions for a specific `ZiskOperationType`. + /// + /// # Arguments + /// * `op_type` - The operation type to retrieve the count for. + /// + /// # Returns + /// Returns the count of instructions for the specified operation type. + pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { + (op_type == ZiskOperationType::Poseidon2).then_some(self.counter.inst_count) + } +} + +impl Metrics for Poseidon2CounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. + /// + /// # Returns + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); + } + + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for Poseidon2CounterInputGen { + type Output = Poseidon2CounterInputGen; + + /// Combines two `Poseidon2Counter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `Poseidon2Counter` instance. + /// * `other` - The second `Poseidon2Counter` instance. + /// + /// # Returns + /// A new `Poseidon2Counter` with combined counters. + fn add(self, other: Self) -> Poseidon2CounterInputGen { + Poseidon2CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } + } +} + +impl BusDevice for Poseidon2CounterInputGen { + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + pending: &mut VecDeque<(BusId, Vec)>, + mem_collector_info: Option<&[MemCollectorInfo]>, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if data[OP_TYPE] as u32 != ZiskOperationType::Poseidon2 as u32 { + return true; + } + + if let Some(mem_collectors_info) = mem_collector_info { + if skip_poseidon2_mem_inputs(data[B] as u32, mem_collectors_info) { + return true; + } + } + + let step_main = data[A]; + let addr_main = data[B] as u32; + + let only_counters = self.mode == BusDeviceMode::Counter; + if only_counters { + self.measure(data); + } + + generate_poseidon2_mem_inputs(addr_main, step_main, data, only_counters, pending); + + true + } + + /// Returns the bus IDs associated with this counter. + /// + /// # Returns + /// A vector containing the connected bus ID. + fn bus_id(&self) -> Vec { + vec![OPERATION_BUS_ID] + } + + /// Provides a dynamic reference for downcasting purposes. + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs b/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs new file mode 100644 index 000000000..4cb07bfe3 --- /dev/null +++ b/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs @@ -0,0 +1,84 @@ +use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; +use precompiles_common::MemBusHelpers; +use std::collections::VecDeque; +use zisk_common::MemCollectorInfo; +use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; + +#[derive(Debug)] +pub struct Poseidon2MemInputConfig { + pub rewrite_params: bool, + pub read_params: usize, + pub write_params: usize, + pub chunks_per_param: usize, +} + +pub fn generate_poseidon2_mem_inputs( + addr_main: u32, + step_main: u64, + data: &[u64], + only_counters: bool, + pending: &mut VecDeque<(BusId, Vec)>, +) { + // Get the basic data from the input + // op,op_type,a,b,... + let state: &mut [u64; 16] = &mut data[4..20].try_into().unwrap(); + + // Apply the poseidon2 hash function + let state_gl = state.map(Goldilocks::new); + let res_gl = poseidon2_hash::(&state_gl); + for (i, d) in state.iter_mut().enumerate() { + *d = res_gl[i].as_canonical_u64(); + } + + let read_params = 1; + let write_params = 1; + let chunks_per_param = 16; + let params_count = read_params + write_params; + let params_offset = OPERATION_BUS_DATA_SIZE; + for iparam in 0..params_count { + let is_write = iparam >= read_params; + let param_index = if is_write { iparam - read_params } else { iparam }; + let param_addr = addr_main + (param_index * 8 * chunks_per_param) as u32; + + // read/write all chunks of the iparam parameter + let current_param_offset = if is_write { + // if write calculate index over write_data + chunks_per_param * (iparam - read_params) + } else { + params_offset + chunks_per_param * iparam + }; + for ichunk in 0..chunks_per_param { + let chunk_data = if only_counters && is_write { + 0 + } else if is_write { + state[current_param_offset + ichunk] + } else { + data[current_param_offset + ichunk] + }; + MemBusHelpers::mem_aligned_op( + param_addr + ichunk as u32 * 8, + step_main, + chunk_data, + is_write, + pending, + ); + } + } +} + +pub fn skip_poseidon2_mem_inputs(addr_main: u32, mem_collectors_info: &[MemCollectorInfo]) -> bool { + let write_params = 1; + let chunks_per_param = 16; + for param_index in 0..write_params { + let param_addr = addr_main + (param_index * 8 * chunks_per_param) as u32; + for ichunk in 0..chunks_per_param { + let addr = param_addr + ichunk as u32 * 8; + for mem_collector in mem_collectors_info { + if !mem_collector.skip_addr(addr) { + return false; + } + } + } + } + true +} diff --git a/precompiles/poseidon2/src/poseidon2_input.rs b/precompiles/poseidon2/src/poseidon2_input.rs new file mode 100644 index 000000000..ad50b39c4 --- /dev/null +++ b/precompiles/poseidon2/src/poseidon2_input.rs @@ -0,0 +1,18 @@ +use zisk_common::OperationPoseidon2Data; + +#[derive(Debug)] +pub struct Poseidon2Input { + pub step_main: u64, + pub addr_main: u32, + pub state: [u64; 16], +} + +impl Poseidon2Input { + pub fn from(values: &OperationPoseidon2Data) -> Self { + Self { + step_main: values[2], + addr_main: values[3] as u32, + state: values[4..20].try_into().unwrap(), + } + } +} diff --git a/precompiles/poseidon2/src/poseidon2_instance.rs b/precompiles/poseidon2/src/poseidon2_instance.rs new file mode 100644 index 000000000..a0ba4f6ed --- /dev/null +++ b/precompiles/poseidon2/src/poseidon2_instance.rs @@ -0,0 +1,212 @@ +//! The `Poseidon2Instance` module defines an instance to perform the witness computation +//! for the Poseidon2 State Machine. +//! +//! It manages collected inputs and interacts with the `Poseidon2SM` to compute witnesses for +//! execution plans. + +use crate::{Poseidon2Input, Poseidon2SM}; +use fields::PrimeField64; +use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; +use std::collections::VecDeque; +use std::{any::Any, collections::HashMap, sync::Arc}; +use zisk_common::ChunkId; +use zisk_common::{ + BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, + InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, +}; +use zisk_core::ZiskOperationType; +use zisk_pil::Poseidon2Trace; + +/// The `Poseidon2Instance` struct represents an instance for the Poseidon2 State Machine. +/// +/// It encapsulates the `Poseidon2SM` and its associated context, and it processes input data +/// to compute witnesses for the Poseidon2 State Machine. +pub struct Poseidon2Instance { + /// Poseidon2 state machine. + poseidon2_sm: Arc>, + + /// Instance context. + ictx: InstanceCtx, +} + +impl Poseidon2Instance { + /// Creates a new `Poseidon2Instance`. + /// + /// # Arguments + /// * `poseidon2_sm` - An `Arc`-wrapped reference to the Poseidon2 State Machine. + /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. + /// * `bus_id` - The bus ID associated with this instance. + /// + /// # Returns + /// A new `Poseidon2Instance` instance initialized with the provided state machine and + /// context. + pub fn new(poseidon2_sm: Arc>, ictx: InstanceCtx) -> Self { + Self { poseidon2_sm, ictx } + } + + pub fn build_poseidon2_collector(&self, chunk_id: ChunkId) -> Poseidon2Collector { + assert_eq!( + self.ictx.plan.air_id, + Poseidon2Trace::::AIR_ID, + "Poseidon2Instance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::>().unwrap(); + let (num_ops, collect_skipper) = collect_info[&chunk_id]; + Poseidon2Collector::new(num_ops, collect_skipper) + } +} + +impl Instance for Poseidon2Instance { + /// Computes the witness for the poseidon2 execution plan. + /// + /// This method leverages the `Poseidon2SM` to generate an `AirInstance` using the collected + /// inputs. + /// + /// # Arguments + /// * `_pctx` - The proof context, unused in this implementation. + /// + /// # Returns + /// An `Option` containing the computed `AirInstance`. + fn compute_witness( + &self, + _pctx: &ProofCtx, + _sctx: &SetupCtx, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + ) -> ProofmanResult>> { + let inputs: Vec<_> = collectors + .into_iter() + .map(|(_, collector)| { + collector.as_any().downcast::().unwrap().inputs + }) + .collect(); + + Ok(Some(self.poseidon2_sm.compute_witness(&inputs, trace_buffer)?)) + } + + /// Retrieves the checkpoint associated with this instance. + /// + /// # Returns + /// A `CheckPoint` object representing the checkpoint of the execution plan. + fn check_point(&self) -> &CheckPoint { + &self.ictx.plan.check_point + } + + /// Retrieves the type of this instance. + /// + /// # Returns + /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). + fn instance_type(&self) -> InstanceType { + InstanceType::Instance + } + + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { + assert_eq!( + self.ictx.plan.air_id, + Poseidon2Trace::::AIR_ID, + "Poseidon2Instance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::>().unwrap(); + let (num_ops, collect_skipper) = collect_info[&chunk_id]; + Some(Box::new(Poseidon2Collector::new(num_ops, collect_skipper))) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +pub struct Poseidon2Collector { + /// Collected inputs for witness computation. + inputs: Vec, + + /// The number of operations to collect. + num_operations: u64, + + /// Helper to skip instructions based on the plan's configuration. + collect_skipper: CollectSkipper, +} + +impl Poseidon2Collector { + /// Creates a new `Poseidon2Collector`. + /// + /// # Arguments + /// + /// * `bus_id` - The connected bus ID. + /// * `num_operations` - The number of operations to collect. + /// * `collect_skipper` - The helper to skip instructions based on the plan's configuration. + /// + /// # Returns + /// A new `ArithInstanceCollector` instance initialized with the provided parameters. + pub fn new(num_operations: u64, collect_skipper: CollectSkipper) -> Self { + Self { + inputs: Vec::with_capacity(num_operations as usize), + num_operations, + collect_skipper, + } + } +} + +impl BusDevice for Poseidon2Collector { + /// Processes data received on the bus, collecting the inputs necessary for witness computation. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A tuple where: + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + fn process_data( + &mut self, + bus_id: &BusId, + data: &[PayloadType], + _pending: &mut VecDeque<(BusId, Vec)>, + _mem_collector_info: Option<&[MemCollectorInfo]>, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if self.inputs.len() == self.num_operations as usize { + return false; + } + + if data[OP_TYPE] as u32 != ZiskOperationType::Poseidon2 as u32 { + return true; + } + + if self.collect_skipper.should_skip() { + return true; + } + + let data: ExtOperationData = + data.try_into().expect("Regular Metrics: Failed to convert data"); + if let ExtOperationData::OperationPoseidon2Data(data) = data { + self.inputs.push(Poseidon2Input::from(&data)); + } else { + panic!("Expected ExtOperationData::OperationData"); + } + + self.inputs.len() < self.num_operations as usize + } + + /// Returns the bus IDs associated with this instance. + /// + /// # Returns + /// A vector containing the connected bus ID. + fn bus_id(&self) -> Vec { + vec![OPERATION_BUS_ID] + } + + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/poseidon2/src/poseidon2_manager.rs b/precompiles/poseidon2/src/poseidon2_manager.rs new file mode 100644 index 000000000..77ed48897 --- /dev/null +++ b/precompiles/poseidon2/src/poseidon2_manager.rs @@ -0,0 +1,94 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use zisk_common::{ + BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, + InstanceInfo, PayloadType, Planner, +}; +use zisk_core::ZiskOperationType; +use zisk_pil::Poseidon2Trace; + +use crate::{Poseidon2CounterInputGen, Poseidon2Instance, Poseidon2Planner, Poseidon2SM}; + +/// The `Poseidon2Manager` struct represents the Poseidon2 manager, +/// which is responsible for managing the Poseidon2 state machine and its table state machine. +#[allow(dead_code)] +pub struct Poseidon2Manager { + /// Poseidon2 state machine + poseidon2_sm: Arc>, +} + +impl Poseidon2Manager { + /// Creates a new instance of `Poseidon2Manager`. + /// + /// # Returns + /// An `Arc`-wrapped instance of `Poseidon2Manager`. + pub fn new() -> Arc { + let poseidon2_sm = Poseidon2SM::new(); + + Arc::new(Self { poseidon2_sm }) + } + + pub fn build_poseidon2_counter(&self) -> Poseidon2CounterInputGen { + Poseidon2CounterInputGen::new(BusDeviceMode::Counter) + } + + pub fn build_poseidon2_input_generator(&self) -> Poseidon2CounterInputGen { + Poseidon2CounterInputGen::new(BusDeviceMode::InputGenerator) + } +} + +impl ComponentBuilder for Poseidon2Manager { + /// Builds and returns a new counter for monitoring poseidon2 operations. + /// + /// # Returns + /// A boxed implementation of `RegularCounters` configured for poseidon2 operations. + fn build_counter(&self) -> Option> { + Some(Box::new(Poseidon2CounterInputGen::new(BusDeviceMode::Counter))) + } + + /// Builds a planner to plan poseidon2-related instances. + /// + /// # Returns + /// A boxed implementation of `RegularPlanner`. + fn build_planner(&self) -> Box { + // Get the number of poseidon2s that a single poseidon2 instance can handle + let num_available_poseidon2s = self.poseidon2_sm.num_available_poseidon2s; + + Box::new(Poseidon2Planner::new().add_instance(InstanceInfo::new( + Poseidon2Trace::::AIRGROUP_ID, + Poseidon2Trace::::AIR_ID, + num_available_poseidon2s, + ZiskOperationType::Poseidon2, + ))) + } + + /// Builds an inputs data collector for poseidon2 operations. + /// + /// # Arguments + /// * `ictx` - The context of the instance, containing the plan and its associated + /// configurations. + /// + /// # Returns + /// A boxed implementation of `BusDeviceInstance` specific to the requested `air_id` instance. + /// + /// # Panics + /// Panics if the provided `air_id` is not supported. + fn build_instance(&self, ictx: InstanceCtx) -> Box> { + match ictx.plan.air_id { + id if id == Poseidon2Trace::::AIR_ID => { + Box::new(Poseidon2Instance::new(self.poseidon2_sm.clone(), ictx)) + } + _ => { + panic!( + "Poseidon2Builder::get_instance() Unsupported air_id: {:?}", + ictx.plan.air_id + ) + } + } + } + + fn build_inputs_generator(&self) -> Option>> { + Some(Box::new(Poseidon2CounterInputGen::new(BusDeviceMode::InputGenerator))) + } +} diff --git a/precompiles/poseidon2/src/poseidon2_planner.rs b/precompiles/poseidon2/src/poseidon2_planner.rs new file mode 100644 index 000000000..b6eba1e75 --- /dev/null +++ b/precompiles/poseidon2/src/poseidon2_planner.rs @@ -0,0 +1,136 @@ +//! The `Poseidon2Planner` module defines a planner for generating execution plans specific to +//! arithmetic operations. +//! +//! It organizes execution plans for both regular instances and table instances, +//! leveraging arithmetic operation counts and metadata to construct detailed plans. + +use std::any::Any; + +use crate::Poseidon2CounterInputGen; + +use zisk_common::{ + plan, BusDeviceMetrics, CheckPoint, ChunkId, InstCount, InstanceInfo, InstanceType, Metrics, + Plan, Planner, TableInfo, +}; + +/// The `Poseidon2Planner` struct organizes execution plans for arithmetic instances and tables. +/// +/// It allows adding metadata about instances and tables and generates plans +/// based on the provided counters. +#[derive(Default)] +pub struct Poseidon2Planner { + /// Arithmetic instances info to be planned. + instances_info: Vec, + + /// Arithmetic table instances info to be planned. + tables_info: Vec, +} + +impl Poseidon2Planner { + /// Creates a new `Poseidon2Planner`. + /// + /// # Returns + /// A new `Poseidon2Planner` instance with no preconfigured instances or tables. + pub fn new() -> Self { + Self { instances_info: Vec::new(), tables_info: Vec::new() } + } + + /// Adds an arithmetic instance to the planner. + /// + /// # Arguments + /// * `instance_info` - The `InstanceInfo` describing the arithmetic instance to be added. + /// + /// # Returns + /// The updated `Poseidon2Planner` instance. + pub fn add_instance(mut self, instance_info: InstanceInfo) -> Self { + self.instances_info.push(instance_info); + self + } + + /// Adds an arithmetic table instance to the planner. + /// + /// # Arguments + /// * `table_info` - The `TableInfo` describing the arithmetic table instance to be added. + /// + /// # Returns + /// The updated `Poseidon2Planner` instance. + pub fn add_table_instance(mut self, table_info: TableInfo) -> Self { + self.tables_info.push(table_info); + self + } +} + +impl Planner for Poseidon2Planner { + /// Generates execution plans for arithmetic instances and tables. + /// + /// # Arguments + /// * `counters` - A vector of counters, each associated with a `ChunkId` and `ArithCounter` + /// metrics data. + /// + /// # Returns + /// A vector of `Plan` instances representing execution configurations for the instances and + /// tables. + /// + /// # Panics + /// Panics if any counter cannot be downcasted to an `ArithCounter`. + fn plan(&self, counters: Vec<(ChunkId, Box)>) -> Vec { + // Prepare counts + let mut count: Vec> = Vec::with_capacity(self.instances_info.len()); + + for _ in 0..self.instances_info.len() { + count.push(Vec::new()); + } + + counters.iter().for_each(|(chunk_id, counter)| { + let reg_counter = + Metrics::as_any(&**counter).downcast_ref::().unwrap(); + + // Iterate over `instances_info` and add `InstCount` objects to the correct vector + for (index, instance_info) in self.instances_info.iter().enumerate() { + let inst_count = InstCount::new( + *chunk_id, + reg_counter.inst_count(instance_info.op_type).unwrap(), + ); + + // Add the `InstCount` to the corresponding inner vector + count[index].push(inst_count); + } + }); + + let mut plan_result = Vec::new(); + + for (idx, instance) in self.instances_info.iter().enumerate() { + let plan: Vec<_> = plan(&count[idx], instance.num_ops as u64) + .into_iter() + .map(|(check_point, collect_info)| { + let converted: Box = Box::new(collect_info); + Plan::new( + instance.airgroup_id, + instance.air_id, + None, + InstanceType::Instance, + check_point, + Some(converted), + ) + }) + .collect(); + + plan_result.extend(plan); + } + + if !plan_result.is_empty() { + for table_instance in self.tables_info.iter() { + plan_result.push(Plan::new( + table_instance.airgroup_id, + table_instance.air_id, + None, + InstanceType::Table, + CheckPoint::None, + None, + )); + } + } + + plan_result + } +} diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 7da4380c5..748c7c2f2 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -236,7 +236,6 @@ impl AsmCoreProver { custom_commits_map, verify_constraints, aggregation, - final_snark, gpu_params, verbose.into(), witness_lib.get_packed_info(), diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index b2207fdc1..e2fd6025f 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -187,7 +187,6 @@ impl EmuCoreProver { custom_commits_map, verify_constraints, aggregation, - final_snark, gpu_params, verbose.into(), witness_lib.get_packed_info(), diff --git a/sdk/src/zisk_lib_loader.rs b/sdk/src/zisk_lib_loader.rs index 62b47fc36..d62243abe 100644 --- a/sdk/src/zisk_lib_loader.rs +++ b/sdk/src/zisk_lib_loader.rs @@ -27,6 +27,7 @@ impl ZiskLibLoader { let lib_path = get_witness_computation_lib(Some(&witness_lib)); let library = unsafe { Library::new(lib_path) }?; + tracing::info!("Loading witness library from {:?}", witness_lib); let witness_lib_constructor: Symbol> = unsafe { library.get(b"init_library")? }; diff --git a/test-verifier/build/input.bin b/test-verifier/build/input.bin index d3aa9c014fd294ea7086a2591f8c7f9aedeba72b..91550aeb02613ef4f5d5fe4537a717cc39d04b75 100644 GIT binary patch literal 312848 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%0DxV87 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

fxzLzN0%IgYB` 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 literal 243616 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∨sE3=S9{MJC7N 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 diff --git a/test-verifier/build/zisk.vk.bin b/test-verifier/build/zisk.vk.bin index fb72af9dc..58fe9db48 100644 --- a/test-verifier/build/zisk.vk.bin +++ b/test-verifier/build/zisk.vk.bin @@ -1,2 +1 @@ -t� ��� -a���_���qM�����ɌESS \ No newline at end of file +3�/�Ѿ���U����11h[` Mo=�ʊ��[� \ No newline at end of file diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml index b8fd115a9..b61d01826 100644 --- a/verifier/Cargo.toml +++ b/verifier/Cargo.toml @@ -8,7 +8,7 @@ repository = { workspace = true } categories = { workspace = true } [dependencies] -proofman-verifier = { workspace = true, features = ["verify"] } +proofman-verifier = { path = "../../pil2-proofman/verifier", features = ["verify"] } anyhow = { workspace = true } [features] diff --git a/witness-computation/Cargo.toml b/witness-computation/Cargo.toml index 9b82eb41f..1cb0ac53b 100644 --- a/witness-computation/Cargo.toml +++ b/witness-computation/Cargo.toml @@ -22,6 +22,7 @@ sm-frequent-ops = { workspace = true } data-bus = { workspace = true } precomp-keccakf = { workspace = true } precomp-sha256f = { workspace = true } +precomp-poseidon2 = { workspace = true } precomp-big-int = { workspace = true } precomp-arith-eq = { workspace = true } precomp-arith-eq-384 = { workspace = true } diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index ce48745b6..27179f709 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -13,6 +13,7 @@ use precomp_arith_eq::ArithEqManager; use precomp_arith_eq_384::ArithEq384Manager; use precomp_big_int::Add256Manager; use precomp_keccakf::KeccakfManager; +use precomp_poseidon2::Poseidon2Manager; use precomp_sha256f::Sha256fManager; use proofman::register_std; use proofman_common::{PackedInfo, ProofmanResult}; @@ -30,8 +31,8 @@ use zisk_pil::{ ADD_256_AIR_IDS, ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, MEM_ALIGN_AIR_IDS, MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, - MEM_ALIGN_WRITE_BYTE_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, - ZISK_AIRGROUP_ID, + MEM_ALIGN_WRITE_BYTE_AIR_IDS, POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, + SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, }; pub struct WitnessLib { @@ -114,6 +115,7 @@ impl WitnessLibrary for WitnessLib { // Step 4: Initialize the precompiles state machines let keccakf_sm = KeccakfManager::new(std.clone()); let sha256f_sm = Sha256fManager::new(std.clone()); + let poseidon2_sm = Poseidon2Manager::new(); let arith_eq_sm = ArithEqManager::new(std.clone()); let arith_eq_384_sm = ArithEq384Manager::new(std.clone()); let add256_sm = Add256Manager::new(std.clone()); @@ -153,6 +155,10 @@ impl WitnessLibrary for WitnessLib { vec![(ZISK_AIRGROUP_ID, SHA_256_F_AIR_IDS[0])], StateMachines::Sha256fManager(sha256f_sm.clone()), ), + ( + vec![(ZISK_AIRGROUP_ID, POSEIDON_2_AIR_IDS[0])], + StateMachines::Poseidon2Manager(poseidon2_sm.clone()), + ), ( vec![(ZISK_AIRGROUP_ID, ARITH_EQ_AIR_IDS[0])], StateMachines::ArithEqManager(arith_eq_sm.clone()), diff --git a/ziskos/entrypoint/src/syscalls/mod.rs b/ziskos/entrypoint/src/syscalls/mod.rs index 4822af0c2..236c001f3 100644 --- a/ziskos/entrypoint/src/syscalls/mod.rs +++ b/ziskos/entrypoint/src/syscalls/mod.rs @@ -15,6 +15,7 @@ mod bn254_curve_dbl; mod complex; mod keccakf; mod point; +mod poseidon2; mod secp256k1_add; mod secp256k1_dbl; mod sha256f; @@ -37,6 +38,7 @@ pub use bn254_curve_dbl::*; pub use complex::*; pub use keccakf::*; pub use point::*; +pub use poseidon2::*; pub use secp256k1_add::*; pub use secp256k1_dbl::*; pub use sha256f::*; diff --git a/ziskos/entrypoint/src/syscalls/poseidon2.rs b/ziskos/entrypoint/src/syscalls/poseidon2.rs new file mode 100644 index 000000000..93b11e08f --- /dev/null +++ b/ziskos/entrypoint/src/syscalls/poseidon2.rs @@ -0,0 +1,27 @@ +//! Poseidon2 system call interception + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +use core::arch::asm; + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +use crate::ziskos_syscall; + +/// Executes the Poseidon2 permutation on the given state. +/// +/// The `Poseidon2` system call executes a CSR set on a custom port. When transpiling from RISC-V to Zisk, +/// this instruction is replaced with a precompiled operation—specifically, `Poseidon2`. +/// +/// The syscall takes as a parameter the address of a state data (1024 bits = 128 bytes) +/// and the result of the poseidon2 operation is stored at the same location +/// +/// ### Safety +/// +/// The caller must ensure that the data is aligned to a 64-bit boundary. +#[allow(unused_variables)] +#[no_mangle] +pub extern "C" fn syscall_poseidon2(state: *mut [u64; 16]) { + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + ziskos_syscall!(0x812, state); + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!() +} diff --git a/ziskos/entrypoint/src/syscalls/syscall.rs b/ziskos/entrypoint/src/syscalls/syscall.rs index da2835318..2efada2ed 100644 --- a/ziskos/entrypoint/src/syscalls/syscall.rs +++ b/ziskos/entrypoint/src/syscalls/syscall.rs @@ -18,3 +18,4 @@ pub const SYSCALL_BLS12_381_COMPLEX_ADD_ID: u16 = 0x80E; pub const SYSCALL_BLS12_381_COMPLEX_SUB_ID: u16 = 0x80F; pub const SYSCALL_BLS12_381_COMPLEX_MUL_ID: u16 = 0x810; pub const SYSCALL_ADD256_ID: u16 = 0x811; +pub const SYSCALL_POSEIDON2_ID: u16 = 0x812; From 1543c332cdc28e4e00fa6072054a8d09373de30a Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 15 Jan 2026 15:34:24 +0000 Subject: [PATCH 269/782] Adding compressed vadcop final wrapper --- Cargo.lock | 380 +++++++++--------- Cargo.toml | 32 +- cli/src/commands/prove.rs | 8 +- cli/src/commands/verify_stark.rs | 11 +- distributed/crates/common/src/dto.rs | 2 +- distributed/crates/common/src/types.rs | 1 + .../crates/coordinator/src/coordinator.rs | 2 +- .../grpc-api/proto/zisk_distributed_api.proto | 2 +- .../crates/grpc-api/src/conversions.rs | 2 +- distributed/crates/worker/src/cli/main.rs | 8 +- distributed/crates/worker/src/config.rs | 4 +- distributed/crates/worker/src/worker.rs | 12 +- distributed/crates/worker/src/worker_node.rs | 1 + sdk/src/builder.rs | 32 +- sdk/src/prover/asm.rs | 8 +- sdk/src/prover/backend.rs | 4 +- sdk/src/prover/emu.rs | 8 +- state-machines/starkstructs.json | 3 +- verifier/Cargo.toml | 2 +- verifier/src/verifier.rs | 13 +- 20 files changed, 281 insertions(+), 254 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 168ecd9c9..1d7413471 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,7 +196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -209,7 +209,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -259,7 +259,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -327,7 +327,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -338,7 +338,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -380,9 +380,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -425,9 +425,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bincode" @@ -466,7 +466,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -486,15 +486,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", + "cpufeatures", ] [[package]] @@ -526,7 +527,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -637,9 +638,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.50" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", "jobserver", @@ -670,9 +671,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -726,9 +727,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -736,9 +737,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -755,14 +756,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -794,7 +795,7 @@ dependencies = [ "serde-untagged", "serde_core", "serde_json", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "winnow", "yaml-rust2", ] @@ -833,16 +834,16 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "tiny-keccak", ] [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "conv" @@ -1039,6 +1040,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" dependencies = [ "fields", "num-bigint", @@ -1143,7 +1145,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1178,7 +1180,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1244,7 +1246,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1296,7 +1298,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1382,6 +1384,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" dependencies = [ "cfg-if", "num-bigint", @@ -1391,9 +1394,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" [[package]] name = "findshlibs" @@ -1415,9 +1418,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -1500,7 +1503,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1546,9 +1549,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -1609,9 +1612,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1954,9 +1957,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -2003,9 +2006,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -2066,15 +2069,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", @@ -2085,13 +2088,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2106,9 +2109,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -2167,15 +2170,15 @@ version = "0.16.0" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libffi" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0444124f3ffd67e1b0b0c661a7f81a278a135eb54aaad4078e79fbc8be50c8a5" +checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b" dependencies = [ "libc", "libffi-sys", @@ -2183,9 +2186,9 @@ dependencies = [ [[package]] name = "libffi-sys" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d722da8817ea580d0669da6babe2262d7b86a1af1103da24102b8bb9c101ce7" +checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb" dependencies = [ "cc", ] @@ -2214,9 +2217,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", @@ -2646,9 +2649,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", "ucd-trie", @@ -2656,9 +2659,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -2666,22 +2669,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "pest_meta" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2", @@ -2689,17 +2692,19 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", + "hashbrown 0.15.5", "indexmap", ] [[package]] name = "pil-std-lib" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" dependencies = [ "colored", "fields", @@ -2730,7 +2735,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2791,9 +2796,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -3045,7 +3050,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3059,9 +3064,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -3069,6 +3074,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" dependencies = [ "blake3", "borsh", @@ -3103,6 +3109,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" dependencies = [ "borsh", "colored", @@ -3133,6 +3140,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" dependencies = [ "fields", "proofman-common", @@ -3144,16 +3152,18 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" dependencies = [ "proc-macro2", "quote", "rayon", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" dependencies = [ "crossbeam-channel", "tracing", @@ -3162,6 +3172,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" dependencies = [ "colored", "fields", @@ -3172,6 +3183,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" dependencies = [ "bytemuck", "fields", @@ -3181,9 +3193,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", "prost-derive", @@ -3191,15 +3203,14 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", "itertools 0.14.0", "log", "multimap", - "once_cell", "petgraph", "prettyplease", "prost", @@ -3207,28 +3218,28 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.111", + "syn 2.0.114", "tempfile", ] [[package]] name = "prost-derive" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "prost-types" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" dependencies = [ "prost", ] @@ -3246,9 +3257,9 @@ dependencies = [ [[package]] name = "pulldown-cmark-to-cmark" -version = "21.1.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8246feae3db61428fd0bb94285c690b460e4517d83152377543ca802357785f1" +checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" dependencies = [ "pulldown-cmark", ] @@ -3319,9 +3330,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -3350,7 +3361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3370,7 +3381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3379,14 +3390,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -3426,7 +3437,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 2.0.17", ] @@ -3528,7 +3539,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -3583,9 +3594,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" @@ -3627,9 +3638,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "log", "once_cell", @@ -3678,9 +3689,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -3769,14 +3780,14 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.147" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "indexmap", "itoa", @@ -3860,10 +3871,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -4091,9 +4103,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.17.0" +version = "12.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d8046c5674ab857104bc4559d505f4809b8060d57806e45d49737c97afeb60" +checksum = "520cf51c674f8b93d533f80832babe413214bb766b6d7cb74ee99ad2971f8467" dependencies = [ "debugid", "memmap2", @@ -4103,9 +4115,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.17.0" +version = "12.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1accb6e5c4b0f682de907623912e616b44be1c9e725775155546669dbff720ec" +checksum = "9f0de2ee0ffa2641e17ba715ad51d48b9259778176517979cb38b6aa86fa7425" dependencies = [ "cc", "cpp_demangle", @@ -4127,9 +4139,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -4153,7 +4165,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4250,7 +4262,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4261,7 +4273,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4275,9 +4287,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", @@ -4285,22 +4297,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", @@ -4352,9 +4364,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -4375,7 +4387,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4390,9 +4402,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -4401,9 +4413,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -4426,9 +4438,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.10+spec-1.1.0" +version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ "indexmap", "serde_core", @@ -4542,7 +4554,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4567,7 +4579,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.111", + "syn 2.0.114", "tempfile", "tonic-build", ] @@ -4587,9 +4599,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -4665,7 +4677,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4752,9 +4764,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" @@ -4823,9 +4835,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -4927,18 +4939,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -4949,11 +4961,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -4962,9 +4975,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4972,22 +4985,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -5007,9 +5020,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -5027,9 +5040,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -5132,7 +5145,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5143,7 +5156,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5389,13 +5402,14 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" dependencies = [ "colored", "fields", @@ -5448,28 +5462,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5489,7 +5503,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] @@ -5504,13 +5518,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5543,7 +5557,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5685,7 +5699,7 @@ dependencies = [ "serde", "tokio", "tokio-stream", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "tonic", "tracing", "uuid", @@ -5817,7 +5831,7 @@ version = "0.16.0" dependencies = [ "bincode", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "lazy_static", "lib-c", "num-bigint", @@ -5831,9 +5845,9 @@ dependencies = [ [[package]] name = "zmij" -version = "0.1.7" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e404bcd8afdaf006e529269d3e85a743f9480c3cef60034d77860d02964f3ba" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 4923c81c7..f0d656c23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,23 +103,23 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -# proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } # Proofman Local development -proofman = { path = "../pil2-proofman/proofman" } -proofman-common = { path = "../pil2-proofman/common" } -proofman-macros = { path = "../pil2-proofman/macros" } -proofman-verifier = { path = "../pil2-proofman/verifier" } -proofman-util = { path = "../pil2-proofman/util" } -pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } -witness = { path = "../pil2-proofman/witness" } -fields = { path = "../pil2-proofman/fields" } +# proofman = { path = "../pil2-proofman/proofman" } +# proofman-common = { path = "../pil2-proofman/common" } +# proofman-macros = { path = "../pil2-proofman/macros" } +# proofman-verifier = { path = "../pil2-proofman/verifier" } +# proofman-util = { path = "../pil2-proofman/util" } +# pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } +# witness = { path = "../pil2-proofman/witness" } +# fields = { path = "../pil2-proofman/fields" } # External dependencies rayon = "1.10" diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 47d7153ed..721f4739e 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -55,8 +55,8 @@ pub struct ZiskProve { #[clap(short = 'a', long, default_value_t = false)] pub aggregation: bool, - #[clap(short = 'f', long, default_value_t = false)] - pub final_snark: bool, + #[clap(short = 'c', long, default_value_t = false)] + pub compressed: bool, #[clap(short = 'y', long, default_value_t = false)] pub verify_proofs: bool, @@ -174,7 +174,7 @@ impl ZiskProve { .emu() .prove() .aggregation(self.aggregation) - .final_snark(self.final_snark) + .compressed(self.compressed) .rma(self.rma) .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) @@ -204,7 +204,7 @@ impl ZiskProve { .asm() .prove() .aggregation(self.aggregation) - .final_snark(self.final_snark) + .compressed(self.compressed) .rma(self.rma) .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) diff --git a/cli/src/commands/verify_stark.rs b/cli/src/commands/verify_stark.rs index 74147414b..aa26261c4 100644 --- a/cli/src/commands/verify_stark.rs +++ b/cli/src/commands/verify_stark.rs @@ -3,7 +3,7 @@ use clap::Parser; use colored::Colorize; use proofman_common::initialize_logger; use std::fs; -use zisk_verifier::verify_zisk_proof; +use zisk_verifier::{verify_zisk_proof, verify_zisk_proof_compressed}; use zisk_build::ZISK_VERSION_MESSAGE; @@ -22,6 +22,9 @@ pub struct ZiskVerify { #[clap(short = 'k', long)] pub vk: Option, + + #[clap(short = 'c', long, default_value_t = false)] + pub compressed: bool, } impl ZiskVerify { @@ -40,7 +43,11 @@ impl ZiskVerify { let vk = &self.get_verkey(); - let result = verify_zisk_proof(&proof, vk); + let result = if self.compressed { + verify_zisk_proof_compressed(&proof, vk) + } else { + verify_zisk_proof(&proof, vk) + }; let elapsed = start.elapsed(); diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 786c4f832..1c8e7a621 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -183,7 +183,7 @@ pub struct AggParamsDto { pub verify_constraints: bool, pub aggregation: bool, pub rma: bool, - pub final_snark: bool, + pub compressed: bool, pub verify_proofs: bool, pub save_proofs: bool, pub test_mode: bool, diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index b5efd88d4..e7b5b48b1 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -441,6 +441,7 @@ pub struct AggregationParams { pub verify_constraints: bool, pub aggregation: bool, pub rma: bool, + pub compressed: bool, pub verify_proofs: bool, pub save_proofs: bool, pub test_mode: bool, diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index c0b1d0683..f41ac3314 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -1680,7 +1680,7 @@ impl Coordinator { verify_constraints: true, aggregation: true, rma: true, - final_snark: false, + compressed: true, verify_proofs: true, save_proofs: false, test_mode: false, diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index c843c149e..277633ac6 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -251,7 +251,7 @@ message AggParams { bool verify_constraints = 4; bool aggregation = 5; bool rma = 6; - bool final_snark = 7; + bool compressed = 7; bool verify_proofs = 8; bool save_proofs = 9; bool test_mode = 10; diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index eaf863357..0d450d710 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -373,7 +373,7 @@ impl From for AggParams { verify_constraints: dto.verify_constraints, aggregation: dto.aggregation, rma: dto.rma, - final_snark: dto.final_snark, + compressed: dto.compressed, verify_proofs: dto.verify_proofs, save_proofs: dto.save_proofs, test_mode: dto.test_mode, diff --git a/distributed/crates/worker/src/cli/main.rs b/distributed/crates/worker/src/cli/main.rs index c092b0c8f..c3ac5667c 100644 --- a/distributed/crates/worker/src/cli/main.rs +++ b/distributed/crates/worker/src/cli/main.rs @@ -98,9 +98,9 @@ struct Cli { #[clap(long, default_value_t = false)] pub verify_constraints: bool, - /// Whether to generate the final SNARK - #[clap(short = 'f', long, default_value_t = false)] - pub final_snark: bool, + /// Whether to generate the final vadcop proof compressed + #[clap(short = 'c', long, default_value_t = false)] + pub compressed: bool, /// GPU parameters #[clap(short = 'z', long, default_value_t = false)] @@ -150,7 +150,7 @@ async fn main() -> Result<()> { debug: cli.debug.clone(), verify_constraints: cli.verify_constraints, aggregation: true, // we always aggregate - final_snark: cli.final_snark, + compressed: cli.compressed, preallocate: cli.preallocate, max_streams: cli.max_streams, number_threads_witness: cli.number_threads_witness, diff --git a/distributed/crates/worker/src/config.rs b/distributed/crates/worker/src/config.rs index 5dceb7256..6f94f5c37 100644 --- a/distributed/crates/worker/src/config.rs +++ b/distributed/crates/worker/src/config.rs @@ -155,7 +155,7 @@ pub struct ProverServiceConfigDto { pub debug: Option>, pub verify_constraints: bool, pub aggregation: bool, - pub final_snark: bool, + pub compressed: bool, pub preallocate: bool, pub max_streams: Option, pub number_threads_witness: Option, @@ -179,7 +179,7 @@ impl Default for ProverServiceConfigDto { debug: None, verify_constraints: false, aggregation: false, - final_snark: false, + compressed: true, preallocate: false, max_streams: None, number_threads_witness: None, diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index f8b761dd2..a737143ae 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -89,8 +89,8 @@ pub struct ProverConfig { /// Flag to enable aggregation pub aggregation: bool, - /// Flag to enable final SNARK - pub final_snark: bool, + /// Flag to enable vadcop final compressed proof + pub compressed: bool, /// Preallocate resources pub gpu_params: ParamsGPU, @@ -224,7 +224,7 @@ impl ProverConfig { unlock_mapped_memory: prover_service_config.unlock_mapped_memory, verify_constraints: prover_service_config.verify_constraints, aggregation: prover_service_config.aggregation, - final_snark: prover_service_config.final_snark, + compressed: prover_service_config.compressed, gpu_params, shared_tables: prover_service_config.shared_tables, rma: prover_service_config.rma, @@ -699,7 +699,7 @@ impl Worker { ProofOptions { verify_constraints: false, aggregation: false, - perf: false, + compressed: false, verify_proofs: true, save_proofs: true, test_mode: false, @@ -713,7 +713,7 @@ impl Worker { ProofOptions { verify_constraints: false, aggregation: true, - perf: false, + compressed: true, verify_proofs: false, save_proofs: false, test_mode: false, @@ -728,7 +728,7 @@ impl Worker { verify_constraints: agg_params.verify_constraints, aggregation: agg_params.aggregation, rma: agg_params.rma, - perf: false, + compressed: agg_params.compressed, verify_proofs: agg_params.verify_proofs, save_proofs: agg_params.save_proofs, test_mode: agg_params.test_mode, diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index db2a61239..6b0db138e 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -669,6 +669,7 @@ impl WorkerNodeGrpc { verify_constraints: agg_params.verify_constraints, aggregation: agg_params.aggregation, rma: agg_params.rma, + compressed: agg_params.compressed, verify_proofs: agg_params.verify_proofs, save_proofs: agg_params.save_proofs, test_mode: agg_params.test_mode, diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index f25a31be0..399fa73f0 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use crate::{ - get_asm_paths, get_proving_key, get_proving_key_snark, get_witness_computation_lib, + get_asm_paths, get_proving_key, get_witness_computation_lib, prover::{Asm, AsmProver, Emu, EmuProver, ZiskProver}, }; use colored::Colorize; @@ -51,7 +51,7 @@ pub struct ProverClientBuilder { // Common fields for both EMU and ASM aggregation: bool, rma: bool, - final_snark: bool, + compressed: bool, witness_lib: Option, proving_key: Option, proving_key_snark: Option, @@ -143,10 +143,10 @@ impl ProverClientBuilder { self } - /// Enables final SNARK generation. + /// Enables final vadcop is compressed proof. #[must_use] - pub fn final_snark(mut self, enable: bool) -> Self { - self.final_snark = enable; + pub fn compressed(mut self, enable: bool) -> Self { + self.compressed = enable; self } @@ -328,10 +328,7 @@ impl ProverClientBuilder { fn build_emu(self) -> Result> { let witness_lib = get_witness_computation_lib(self.witness_lib.as_ref()); let proving_key = get_proving_key(self.proving_key.as_ref()); - let proving_key_snark = match self.final_snark { - true => Some(get_proving_key_snark(self.proving_key_snark.as_ref())), - false => None, - }; + let proving_key_snark = None; let elf = self.elf.ok_or_else(|| anyhow::anyhow!("ELF path is required"))?; let output_dir = if !self.verify_constraints { @@ -356,7 +353,7 @@ impl ProverClientBuilder { self.verify_constraints, self.aggregation, self.rma, - self.final_snark, + self.compressed, witness_lib, proving_key, proving_key_snark, @@ -473,10 +470,7 @@ impl ProverClientBuilder { { let witness_lib = get_witness_computation_lib(self.witness_lib.as_ref()); let proving_key = get_proving_key(self.proving_key.as_ref()); - let proving_key_snark = match self.final_snark { - true => Some(get_proving_key_snark(self.proving_key_snark.as_ref())), - false => None, - }; + let proving_key_snark = None; let elf = self.elf.ok_or_else(|| anyhow::anyhow!("ELF path is required"))?; let output_dir = if !self.verify_constraints { @@ -503,7 +497,7 @@ impl ProverClientBuilder { self.verify_constraints, self.aggregation, self.rma, - self.final_snark, + self.compressed, witness_lib, proving_key, proving_key_snark, @@ -570,7 +564,7 @@ impl From> for ProverClientBuilder { aggregation: builder.aggregation, witness: builder.witness, rma: builder.rma, - final_snark: builder.final_snark, + compressed: builder.compressed, witness_lib: builder.witness_lib, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, @@ -606,7 +600,7 @@ impl From> for ProverClientBuilder { aggregation: builder.aggregation, witness: builder.witness, rma: builder.rma, - final_snark: builder.final_snark, + compressed: builder.compressed, witness_lib: builder.witness_lib, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, @@ -644,7 +638,7 @@ impl From> aggregation: builder.aggregation, witness: builder.witness, rma: builder.rma, - final_snark: builder.final_snark, + compressed: builder.compressed, witness_lib: builder.witness_lib, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, @@ -680,7 +674,7 @@ impl From> for ProverClientBuilder, @@ -55,7 +55,7 @@ impl AsmProver { verify_constraints, aggregation, rma, - final_snark, + compressed, witness_lib, proving_key, proving_key_snark, @@ -185,7 +185,7 @@ impl AsmCoreProver { verify_constraints: bool, aggregation: bool, rma: bool, - final_snark: bool, + compressed: bool, witness_lib: PathBuf, proving_key: PathBuf, _proving_key_snark: Option, @@ -272,7 +272,7 @@ impl AsmCoreProver { verify_constraints, aggregation, rma, - final_snark, + compressed, witness_lib, proving_key: proving_key.clone(), verify_proofs, diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index abb82c33e..0dd397dbd 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -15,7 +15,7 @@ pub(crate) struct ProverBackend { pub verify_constraints: bool, pub aggregation: bool, pub rma: bool, - pub final_snark: bool, + pub compressed: bool, pub witness_lib: Box>, pub proving_key: PathBuf, pub verify_proofs: bool, @@ -165,7 +165,7 @@ impl ProverBackend { self.verify_constraints, self.aggregation, self.rma, - self.final_snark, + self.compressed, self.verify_proofs, self.minimal_memory, self.save_proofs, diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index e2fd6025f..ce66d3a71 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -29,7 +29,7 @@ impl EmuProver { verify_constraints: bool, aggregation: bool, rma: bool, - final_snark: bool, + compressed: bool, witness_lib: PathBuf, proving_key: PathBuf, proving_key_snark: Option, @@ -47,7 +47,7 @@ impl EmuProver { verify_constraints, aggregation, rma, - final_snark, + compressed, witness_lib, proving_key, proving_key_snark, @@ -158,7 +158,7 @@ impl EmuCoreProver { verify_constraints: bool, aggregation: bool, rma: bool, - final_snark: bool, + compressed: bool, witness_lib: PathBuf, proving_key: PathBuf, _proving_key_snark: Option, @@ -210,7 +210,7 @@ impl EmuCoreProver { verify_constraints, aggregation, rma, - final_snark, + compressed, witness_lib, proving_key: proving_key.clone(), verify_proofs, diff --git a/state-machines/starkstructs.json b/state-machines/starkstructs.json index ee3018636..6976d5404 100644 --- a/state-machines/starkstructs.json +++ b/state-machines/starkstructs.json @@ -4,5 +4,6 @@ "ArithEq": { "hasCompressor": true }, "ArithEq384": { "hasCompressor": true }, "VirtualTable0": { "lastLevelVerification": 1 }, - "VirtualTable1": { "lastLevelVerification": 1 } + "VirtualTable1": { "lastLevelVerification": 1 }, + "Poseidon2": { "blowupFactor": 2 } } \ No newline at end of file diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml index b61d01826..b8fd115a9 100644 --- a/verifier/Cargo.toml +++ b/verifier/Cargo.toml @@ -8,7 +8,7 @@ repository = { workspace = true } categories = { workspace = true } [dependencies] -proofman-verifier = { path = "../../pil2-proofman/verifier", features = ["verify"] } +proofman-verifier = { workspace = true, features = ["verify"] } anyhow = { workspace = true } [features] diff --git a/verifier/src/verifier.rs b/verifier/src/verifier.rs index 38231d8e1..7b7c27098 100644 --- a/verifier/src/verifier.rs +++ b/verifier/src/verifier.rs @@ -1,10 +1,19 @@ use anyhow::{anyhow, Ok, Result}; -use proofman_verifier::verify; +use proofman_verifier::{verify_vadcop_final_compressed, verify_vadcop_final}; pub fn verify_zisk_proof(zisk_proof: &[u8], vk: &[u8]) -> Result<()> { - if !verify(zisk_proof, vk) { + if !verify_vadcop_final(zisk_proof, vk) { Err(anyhow!("Zisk Proof was not verified")) } else { Ok(()) } } + +pub fn verify_zisk_proof_compressed(zisk_proof: &[u8], vk: &[u8]) -> Result<()> { + if !verify_vadcop_final_compressed(zisk_proof, vk) { + Err(anyhow!("Zisk Proof was not verified")) + } else { + Ok(()) + } +} + From 5e3b6b22a66ea6c297c8ba475e2e4549712e9ae5 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 16 Jan 2026 09:20:41 +0000 Subject: [PATCH 270/782] Update installation guide --- book/getting_started/installation.md | 11 +++- pil/src/pil_helpers/traces.rs | 83 ++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/book/getting_started/installation.md b/book/getting_started/installation.md index 422a1a483..3a4e20346 100644 --- a/book/getting_started/installation.md +++ b/book/getting_started/installation.md @@ -206,11 +206,20 @@ Please note that the process can be long, taking approximately 45-60 minutes dep 6. Generate setup data: (this step may take 30-45 minutes): ```bash - node --max-old-space-size=16384 --stack-size=8192 ../pil2-proofman-js/src/main_setup.js --stack-size=8192 -a ./pil/zisk.pilout -b build -t ../pil2-proofman/pil2-components/lib/std/pil -u tmp/fixed -r -s ./state-machines/starkstructs.json -f -w ../powersOfTau28_hez_final_27.ptau -p ./state-machines/publics.json + node --max-old-space-size=16384 --stack-size=8192 ../pil2-proofman-js/src/main_setup.js -a ./pil/zisk.pilout -b build -t ../pil2-proofman/pil2-components/lib/std/pil -u tmp/fixed -r -s ./state-machines/starkstructs.json ``` This command generates the `build/provingKey` directory. + Additionally, to generate the snark wrapper: + + ```bash + node ../pil2-proofman-js/src/main_setup_snark.js -b build -t ../pil2-proofman/pil2-components/lib/std/pil -f -w ../powersOfTau28_hez_final_27.ptau -p ./state-machines/publics.json -n plonk + ``` + + It is stored under the `build/provingKeySnark` directory. + + 7. Copy (or move) the `build/provingKey` directory to `$HOME/.zisk` directory: ```bash diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 0b6eb1d5a..7e65ecf58 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "d2072d6b50a3327861b544ec82fe2e3ba83386f69bc0b044d7b227cac13bdcdf"; +pub const PILOUT_HASH: &str = "92d0e6393f156ecee7a7d087ce5b417d708dd0023de05bcdf569ff0daed5c287"; pub const MERKLE_TREE_ARITY: u64 = 4; @@ -62,13 +62,14 @@ pub const KECCAKF_AIR_IDS: &[usize] = &[16]; pub const SHA_256_F_AIR_IDS: &[usize] = &[17]; -pub const POSEIDON_2_AIR_IDS: &[usize] = &[13]; +pub const POSEIDON_2_AIR_IDS: &[usize] = &[18]; -pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[14]; +pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[19]; -pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[15]; +pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[20]; + +pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[21]; -pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[16]; //PUBLICS use serde::Deserialize; @@ -359,51 +360,52 @@ pub type Sha256fTrace = GenericTrace, 262144, 0, 17>; pub type Sha256fTracePacked = GenericTrace, 262144, 0, 17>; + trace_row!(Poseidon2FixedRow { CLK_0: F, __L1__: F, }); -pub type Poseidon2Fixed = GenericTrace, 131072, 0, 13>; +pub type Poseidon2Fixed = GenericTrace, 131072, 0, 18>; trace_row!(Poseidon2TraceRow { in_use_clk_0:bit, in_use:bit, chunks:[[u32; 2]; 16], step_addr:ubit(40), }); -pub type Poseidon2Trace = GenericTrace, 131072, 0, 13>; +pub type Poseidon2Trace = GenericTrace, 131072, 0, 18>; -pub type Poseidon2TracePacked = GenericTrace, 131072, 0, 13>; +pub type Poseidon2TracePacked = GenericTrace, 131072, 0, 18>; trace_row!(SpecifiedRangesFixedRow { - RANGE: [F; 20], __L1__: F, + RANGE: [F; 33], __L1__: F, }); -pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 14>; +pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 19>; trace_row!(SpecifiedRangesTraceRow { - mul:[F; 20], + mul:[F; 33], }); -pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 14>; +pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 19>; trace_row!(VirtualTable0FixedRow { - UID: [F; 7], column: [F; 39], __L1__: F, + UID: [F; 8], column: [F; 43], __L1__: F, }); -pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 15>; +pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 20>; trace_row!(VirtualTable0TraceRow { - multiplicity:[F; 7], + multiplicity:[F; 8], }); -pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 15>; +pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 20>; trace_row!(VirtualTable1FixedRow { UID: [F; 8], column: [F; 64], __L1__: F, }); -pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 16>; +pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 21>; trace_row!(VirtualTable1TraceRow { multiplicity:[F; 8], }); -pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 16>; +pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 21>; trace_row!(RomRomTraceRow { @@ -504,6 +506,26 @@ values!(BinaryExtensionAirGroupValues { gsum_result: FieldExtension, }); +values!(Add256AirGroupValues { + gsum_result: FieldExtension, +}); + +values!(ArithEqAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(ArithEq384AirGroupValues { + gsum_result: FieldExtension, +}); + +values!(KeccakfAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(Sha256fAirGroupValues { + gsum_result: FieldExtension, +}); + values!(Poseidon2AirGroupValues { gsum_result: FieldExtension, }); @@ -582,6 +604,31 @@ pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ unpack_info: &[6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 1, 32, 32], }), (0, 13, PackedInfoConst { + is_packed: true, + num_packed_words: 15, + unpack_info: &[32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 40, 1, 1], + }), + (0, 14, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], + }), + (0, 15, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], + }), + (0, 16, PackedInfoConst { + is_packed: true, + num_packed_words: 209, + unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 8, 40], + }), + (0, 17, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 40, 1, 1], + }), + (0, 18, PackedInfoConst { is_packed: true, num_packed_words: 17, unpack_info: &[1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40], From 07b54972d0b7c5e77f4a67db9a13c74af9bf680f Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 16 Jan 2026 11:18:22 +0000 Subject: [PATCH 271/782] Updating bin files --- Cargo.lock | 30 +++++++++++++++++------------- test-verifier/build/input.bin | Bin 312848 -> 341600 bytes test-verifier/build/zisk.vk.bin | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d7413471..e66ef8150 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1040,7 +1040,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" dependencies = [ "fields", "num-bigint", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" dependencies = [ "cfg-if", "num-bigint", @@ -2704,7 +2704,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" dependencies = [ "colored", "fields", @@ -2714,6 +2714,9 @@ dependencies = [ "proofman-hints", "proofman-util", "rayon", + "rustc-hash 2.1.1", + "serde", + "serde_json", "tracing", "witness", ] @@ -3074,7 +3077,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" dependencies = [ "blake3", "borsh", @@ -3109,7 +3112,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" dependencies = [ "borsh", "colored", @@ -3140,9 +3143,10 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" dependencies = [ "fields", + "itoa", "proofman-common", "proofman-starks-lib-c", "proofman-util", @@ -3152,7 +3156,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" dependencies = [ "proc-macro2", "quote", @@ -3163,7 +3167,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" dependencies = [ "crossbeam-channel", "tracing", @@ -3172,7 +3176,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" dependencies = [ "colored", "fields", @@ -3183,7 +3187,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" dependencies = [ "bytemuck", "fields", @@ -3653,9 +3657,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "4910321ebe4151be888e35fe062169554e74aad01beafed60410131420ceffbc" dependencies = [ "web-time", "zeroize", @@ -5409,7 +5413,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#eecd8d401aa6df9a96dbf62445a3adee7b7e8f5c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" dependencies = [ "colored", "fields", diff --git a/test-verifier/build/input.bin b/test-verifier/build/input.bin index 91550aeb02613ef4f5d5fe4537a717cc39d04b75..ed06a2c7d44a8d811c6401343411a25f4bd6e394 100644 GIT binary patch literal 341600 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%0DxV87 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

fxzLzN0%IgYB` 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 diff --git a/test-verifier/build/zisk.vk.bin b/test-verifier/build/zisk.vk.bin index 58fe9db48..cc11f23bb 100644 --- a/test-verifier/build/zisk.vk.bin +++ b/test-verifier/build/zisk.vk.bin @@ -1 +1 @@ -3�/�Ѿ���U����11h[` Mo=�ʊ��[� \ No newline at end of file +���m=ixo0���n�q:С5C�FF�� \ No newline at end of file From 8eb489419330ed1cd7094e6b53db561deeb7974b Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 16 Jan 2026 12:16:59 +0000 Subject: [PATCH 272/782] Adding poseidon2_hash function C++ --- .../c/src/poseidon2/goldilocks_base_field.cpp | 128 +++++++ .../c/src/poseidon2/goldilocks_base_field.hpp | 170 ++++++++++ .../goldilocks_base_field_scalar.hpp | 317 ++++++++++++++++++ .../poseidon2/goldilocks_base_field_tools.hpp | 191 +++++++++++ .../c/src/poseidon2/poseidon2_goldilocks.cpp | 127 +++++++ .../poseidon2_goldilocks_constants.hpp | 181 ++++++++++ 6 files changed, 1114 insertions(+) create mode 100644 lib-c/c/src/poseidon2/goldilocks_base_field.cpp create mode 100644 lib-c/c/src/poseidon2/goldilocks_base_field.hpp create mode 100644 lib-c/c/src/poseidon2/goldilocks_base_field_scalar.hpp create mode 100644 lib-c/c/src/poseidon2/goldilocks_base_field_tools.hpp create mode 100644 lib-c/c/src/poseidon2/poseidon2_goldilocks.cpp create mode 100644 lib-c/c/src/poseidon2/poseidon2_goldilocks_constants.hpp diff --git a/lib-c/c/src/poseidon2/goldilocks_base_field.cpp b/lib-c/c/src/poseidon2/goldilocks_base_field.cpp new file mode 100644 index 000000000..1991c9830 --- /dev/null +++ b/lib-c/c/src/poseidon2/goldilocks_base_field.cpp @@ -0,0 +1,128 @@ +#include "goldilocks_base_field.hpp" +#include "goldilocks_base_field_tools.hpp" +#include "goldilocks_base_field_scalar.hpp" + +const Goldilocks::Element Goldilocks::ZR = {(uint64_t)0x0000000000000000LL}; +const Goldilocks::Element Goldilocks::Q = {(uint64_t)0xFFFFFFFF00000001LL}; +const Goldilocks::Element Goldilocks::MM = {(uint64_t)0xFFFFFFFeFFFFFFFFLL}; +const Goldilocks::Element Goldilocks::CQ = {(uint64_t)0x00000000FFFFFFFFLL}; +const Goldilocks::Element Goldilocks::R2 = {(uint64_t)0xFFFFFFFe00000001LL}; + +const Goldilocks::Element Goldilocks::W[33] = { + Goldilocks::fromU64(0x1), + Goldilocks::fromU64(18446744069414584320ULL), + Goldilocks::fromU64(281474976710656ULL), + Goldilocks::fromU64(16777216ULL), + Goldilocks::fromU64(4096ULL), + Goldilocks::fromU64(64ULL), + Goldilocks::fromU64(8ULL), + Goldilocks::fromU64(2198989700608ULL), + Goldilocks::fromU64(4404853092538523347ULL), + Goldilocks::fromU64(6434636298004421797ULL), + Goldilocks::fromU64(4255134452441852017ULL), + Goldilocks::fromU64(9113133275150391358ULL), + Goldilocks::fromU64(4355325209153869931ULL), + Goldilocks::fromU64(4308460244895131701ULL), + Goldilocks::fromU64(7126024226993609386ULL), + Goldilocks::fromU64(1873558160482552414ULL), + Goldilocks::fromU64(8167150655112846419ULL), + Goldilocks::fromU64(5718075921287398682ULL), + Goldilocks::fromU64(3411401055030829696ULL), + Goldilocks::fromU64(8982441859486529725ULL), + Goldilocks::fromU64(1971462654193939361ULL), + Goldilocks::fromU64(6553637399136210105ULL), + Goldilocks::fromU64(8124823329697072476ULL), + Goldilocks::fromU64(5936499541590631774ULL), + Goldilocks::fromU64(2709866199236980323ULL), + Goldilocks::fromU64(8877499657461974390ULL), + Goldilocks::fromU64(3757607247483852735ULL), + Goldilocks::fromU64(4969973714567017225ULL), + Goldilocks::fromU64(2147253751702802259ULL), + Goldilocks::fromU64(2530564950562219707ULL), + Goldilocks::fromU64(1905180297017055339ULL), + Goldilocks::fromU64(3524815499551269279ULL), + Goldilocks::fromU64(7277203076849721926ULL)}; + +const Goldilocks::Element Goldilocks::ONE = {(uint64_t)0x0000000000000001LL}; +const Goldilocks::Element Goldilocks::ZERO = {(uint64_t)0x0000000000000000LL}; +const Goldilocks::Element Goldilocks::NEGONE = {(uint64_t)0xFFFFFFFF00000000LL}; +const Goldilocks::Element Goldilocks::TWO32 = {0x0000000100000000LL}; +const Goldilocks::Element Goldilocks::SHIFT = Goldilocks::fromU64(7); + +/* + Scalar operations +*/ +void Goldilocks::parcpy(Element *dst, const Element *src, uint64_t size, int num_threads_copy) +{ + if (num_threads_copy < 1) + { + num_threads_copy = 1; + } + uint64_t components_thread = (size + num_threads_copy - 1) / num_threads_copy; + +#pragma omp parallel for num_threads(num_threads_copy) + for (uint64_t i = 0; i < size; i += components_thread) + { + uint64_t dim_ = components_thread * sizeof(Goldilocks::Element); + if (size - i < components_thread) + { + dim_ = (size - i) * sizeof(Goldilocks::Element); + } + std::memcpy(&dst[i], &src[i], dim_); + } +} + +void Goldilocks::parSetZero(Element *dst, uint64_t size, int num_threads_copy) +{ + + if (num_threads_copy < 1) + { + num_threads_copy = 1; + } + uint64_t components_thread = (size + num_threads_copy - 1) / num_threads_copy; + +#pragma omp parallel for num_threads(num_threads_copy) + for (uint64_t i = 0; i < size; i += components_thread) + { + uint64_t dim_ = components_thread * sizeof(Goldilocks::Element); + if (size - i < components_thread) + { + dim_ = (size - i) * sizeof(Goldilocks::Element); + } + std::memset(&dst[i], 0, dim_); + } +} + +// TODO: Review and optimize inv imlementation +void Goldilocks::inv(Element &result, const Element &in1) +{ + if (Goldilocks::isZero(in1)) + { + throw std::runtime_error("Error: Goldilocks::inv called with zero"); + } + u_int64_t t = 0; + u_int64_t r = GOLDILOCKS_PRIME; + u_int64_t newt = 1; + + u_int64_t newr = Goldilocks::toU64(in1); + Element q; + Element aux1; + Element aux2; + while (newr != 0) + { + q = Goldilocks::fromU64(r / newr); + aux1 = Goldilocks::fromU64(t); + aux2 = Goldilocks::fromU64(newt); + t = Goldilocks::toU64(aux2); + newt = Goldilocks::toU64(Goldilocks::sub(aux1, Goldilocks::mul(q, aux2))); + aux1 = Goldilocks::fromU64(r); + aux2 = Goldilocks::fromU64(newr); + r = Goldilocks::toU64(aux2); + newr = Goldilocks::toU64(Goldilocks::sub(aux1, Goldilocks::mul(q, aux2))); + } + + Goldilocks::fromU64(result, t); +#if GOLDILOCKS_DEBUG == 1 + result.fe = result.fe % GOLDILOCKS_PRIME; +#endif +}; \ No newline at end of file diff --git a/lib-c/c/src/poseidon2/goldilocks_base_field.hpp b/lib-c/c/src/poseidon2/goldilocks_base_field.hpp new file mode 100644 index 000000000..63787ec59 --- /dev/null +++ b/lib-c/c/src/poseidon2/goldilocks_base_field.hpp @@ -0,0 +1,170 @@ +#ifndef GOLDILOCKS_BASE +#define GOLDILOCKS_BASE + +#include // uint64_t +#include // string +#include +#include // string +#include +#include + +#define GOLDILOCKS_DEBUG 0 +#ifndef USE_ASSEMBLY +#define USE_ASSEMBLY 1 // Default value if not set by the Makefile +#endif +#define GOLDILOCKS_NUM_ROOTS 33 +#define GOLDILOCKS_PRIME 0xFFFFFFFF00000001ULL +#define GOLDILOCKS_PRIME_NEG 0xFFFFFFFF +#define MSB_ 0x8000000000000000 // Most Significant Bit + +class Goldilocks +{ +public: + typedef struct + { + uint64_t fe; + } Element; + +private: + static const Element ZR; + static const Element Q; + static const Element MM; + static const Element CQ; + static const Element R2; + static const Element TWO32; + + static const Element ZERO; + static const Element ONE; + static const Element NEGONE; + static const Element SHIFT; + static const Element W[GOLDILOCKS_NUM_ROOTS]; + +public: + /* + Basic functionality + */ + + static const Element &zero(); + static void zero(Element &result); + + static const Element &one(); + static void one(Element &result); + + static const Element &negone(); + static void negone(Element &result); + + static const Element &shift(); + static void shift(Element &result); + + static const Element &w(uint64_t i); + static void w(Element &result, uint64_t i); + + static Element fromU64(uint64_t in1); + static void fromU64(Element &result, uint64_t in1); + static Element fromS64(int64_t in1); + static void fromS64(Element &result, int64_t in1); + static Element fromS32(int32_t in1); + static void fromS32(Element &result, int32_t in1); + static Element fromString(const std::string &in1, int radix = 10); + static void fromString(Element &result, const std::string &in1, int radix = 10); + static Element fromScalar(const mpz_class &scalar); + static void fromScalar(Element &result, const mpz_class &scalar); + + static uint64_t toU64(const Element &in1); + static void toU64(uint64_t &result, const Element &in1); + static int64_t toS64(const Element &in1); + static void toS64(int64_t &result, const Element &in1); + static bool toS32(int32_t &result, const Element &in1); + static std::string toString(const Element &in1, int radix = 10); + static void toString(std::string &result, const Element &in1, int radix = 10); + static std::string toString(const Element *in1, const uint64_t size, int radix = 10); + + /* + Scalar operations + */ + static void copy(Element &dst, const Element &src); + static void copy(Element *dst, const Element *src); + + static void parcpy(Element *dst, const Element *src, uint64_t size, int num_threads_copy = 64); + static void parSetZero(Element *dst, uint64_t size, int num_threads_copy = 64); + + static Element add(const Element &in1, const Element &in2); + static void add(Element &result, const Element &in1, const Element &in2); + static void add_no_double_carry(uint64_t &result, const uint64_t &in1, const uint64_t &in2); + static Element inc(const Goldilocks::Element &fe); + + static Element sub(const Element &in1, const Element &in2); + static void sub(Element &result, const Element &in1, const Element &in2); + static Element dec(const Goldilocks::Element &fe); + + static Element mul(const Element &in1, const Element &in2); + static void mul(Element &result, const Element &in1, const Element &in2); + static void mul1(Element &result, const Element &in1, const Element &in2); + static void mul2(Element &result, const Element &in1, const Element &in2); + + static Element square(const Element &in1); + static void square(Element &result, const Element &in1); + + static Element pow(const Element& base, uint64_t exp); + + static Element div(const Element &in1, const Element &in2); + static void div(Element &result, const Element &in1, const Element &in2); + + static Element neg(const Element &in1); + static void neg(Element &result, const Element &in1); + + static bool isZero(const Element &in1); + static bool isOne(const Element &in1); + static bool isNegone(const Element &in1); + + static bool equal(const Element &in1, const Element &in2); + + static Element inv(const Element &in1); + static void inv(Element &result, const Element &in1); + + static Element mulScalar(const Element &base, const uint64_t &scalar); + static void mulScalar(Element &result, const Element &base, const uint64_t &scalar); + + static Element exp(Element base, uint64_t exp); + static void exp(Element &result, Element base, uint64_t exps); + + static void batchInverse(Element *res, const Element *src, uint64_t size) + { + Element* tmp = new Element[size]; + copy(tmp[0], src[0]); + + for (uint64_t i = 1; i < size; i++) + { + mul(tmp[i], tmp[i - 1], src[i]); + } + + Element z, z2; + inv(z, tmp[size - 1]); + + for (uint64_t i = size - 1; i > 0; i--) + { + mul(z2, z, src[i]); + mul(res[i], z, tmp[i - 1]); + copy(z, z2); + } + copy(res[0], z); + + delete[] tmp; + } +}; + +/* + Operator Overloading +*/ +inline Goldilocks::Element operator+(const Goldilocks::Element &in1, const Goldilocks::Element &in2) { return Goldilocks::add(in1, in2); } +inline Goldilocks::Element operator*(const Goldilocks::Element &in1, const Goldilocks::Element &in2) { return Goldilocks::mul(in1, in2); } +inline Goldilocks::Element operator-(const Goldilocks::Element &in1, const Goldilocks::Element &in2) { return Goldilocks::sub(in1, in2); } +inline Goldilocks::Element operator/(const Goldilocks::Element &in1, const Goldilocks::Element &in2) { return Goldilocks::div(in1, in2); } +inline bool operator==(const Goldilocks::Element &in1, const Goldilocks::Element &in2) { return Goldilocks::equal(in1, in2); } +inline Goldilocks::Element operator-(const Goldilocks::Element &in1) { return Goldilocks::neg(in1); } +inline Goldilocks::Element operator+(const Goldilocks::Element &in1) { return in1; } + +#include "goldilocks_base_field_tools.hpp" +#include "goldilocks_base_field_scalar.hpp" + +#endif // GOLDILOCKS_BASE diff --git a/lib-c/c/src/poseidon2/goldilocks_base_field_scalar.hpp b/lib-c/c/src/poseidon2/goldilocks_base_field_scalar.hpp new file mode 100644 index 000000000..fa54c520a --- /dev/null +++ b/lib-c/c/src/poseidon2/goldilocks_base_field_scalar.hpp @@ -0,0 +1,317 @@ +#ifndef GOLDILOCKS_SCALAR +#define GOLDILOCKS_SCALAR +#include "goldilocks_base_field.hpp" + +inline void Goldilocks::copy(Element &dst, const Element &src) { dst.fe = src.fe; }; + +inline void Goldilocks::copy(Element *dst, const Element *src) { dst->fe = src->fe; }; + +inline Goldilocks::Element Goldilocks::add(const Element &in1, const Element &in2) +{ + Goldilocks::Element result; + Goldilocks::add(result, in1, in2); + return result; +} + +inline void Goldilocks::add(Element &result, const Element &in1, const Element &in2) +{ +#ifdef __USE_ASSEMBLY__ + uint64_t in_1 = in1.fe; + uint64_t in_2 = in2.fe; + __asm__("xor %%r10, %%r10\n\t" + "mov %1, %0\n\t" + "add %2, %0\n\t" + "cmovc %3, %%r10\n\t" + "add %%r10, %0\n\t" + "jnc 1f\n\t" + "add %3, %0\n\t" + "1: \n\t" + : "=&a"(result.fe) + : "r"(in_1), "r"(in_2), "m"(CQ), "m"(ZR) + : "%r10"); +#else + uint64_t in_1 = in1.fe; + if(in_1 >= GOLDILOCKS_PRIME){ + in_1 -= GOLDILOCKS_PRIME; + } + result.fe = in_1 + in2.fe; + if(in_1 > result.fe){ + result.fe -= GOLDILOCKS_PRIME; + } +#endif +} + +inline Goldilocks::Element Goldilocks::inc(const Goldilocks::Element &fe) +{ + Goldilocks::Element result; + if (fe.fe < GOLDILOCKS_PRIME - 2) + { + result.fe = fe.fe + 1; + } + else if (fe.fe == GOLDILOCKS_PRIME - 1) + { + result.fe = 0; + } + else + { + result = Goldilocks::add(fe, Goldilocks::one()); + } + return result; +} + +inline Goldilocks::Element Goldilocks::sub(const Element &in1, const Element &in2) +{ + Goldilocks::Element result; + Goldilocks::sub(result, in1, in2); + return result; +} + +inline void Goldilocks::sub(Element &result, const Element &in1, const Element &in2) +{ +#ifdef __USE_ASSEMBLY__ + uint64_t in_1 = in1.fe; + uint64_t in_2 = in2.fe; + __asm__("xor %%r10, %%r10\n\t" + "mov %1, %0\n\t" + "sub %2, %0\n\t" + "cmovc %3, %%r10\n\t" + "sub %%r10, %0\n\t" + "jnc 1f\n\t" + "sub %3, %0\n\t" + "1: \n\t" + : "=&a"(result.fe) + : "r"(in_1), "r"(in_2), "m"(CQ), "m"(ZR) + : "%r10"); +#else + uint64_t in_2 = in2.fe; + if(in_2 >= GOLDILOCKS_PRIME){ + in_2 -= GOLDILOCKS_PRIME; + } + result.fe = in1.fe - in_2; + if(in_2 > in1.fe){ + result.fe += GOLDILOCKS_PRIME; + } +#endif +#if GOLDILOCKS_DEBUG == 1 + result.fe = result.fe % GOLDILOCKS_PRIME; +#endif +} + +inline Goldilocks::Element Goldilocks::dec(const Goldilocks::Element &fe) +{ + Goldilocks::Element result; + if (fe.fe > 0) + { + result.fe = fe.fe - 1; + } + else + { + result.fe = GOLDILOCKS_PRIME - 1; + } + return result; +} + +inline Goldilocks::Element Goldilocks::mul(const Element &in1, const Element &in2) +{ + Goldilocks::Element result; + Goldilocks::mul(result, in1, in2); + return result; +} + +inline Goldilocks::Element Goldilocks::pow(const Element& base, uint64_t exp) +{ + Element result; + one(result); + Element temp; + copy(temp, base); + while (exp > 0) + { + if (exp % 2 == 1) + { + mul(result, result, temp); + } + mul(temp, temp, temp); + exp /= 2; + } + return result; +} + +/* +* Stable version used until new optimization based on branch_hint was introduced (see mul function) +*/ +inline void Goldilocks::mul1(Element &result, const Element &in1, const Element &in2) +{ + +#ifdef __USE_ASSEMBLY__ + __asm__("mov %1, %0\n\t" + "mul %2\n\t" + // "xor %%rbx, %%rbx\n\t" + "mov %%edx, %%ebx\n\t" + "sub %4, %%rbx\n\t" + "rol $32, %%rdx\n\t" + //"xor %%rcx, %%rcx;\n\t" + "mov %%edx, %%ecx\n\t" + "sub %%rcx, %%rdx\n\t" + "add %4, %%rcx\n\t" + "sub %%rbx, %%rdx\n\t" + //"mov %3,%%r10 \n\t" + "xor %%rbx, %%rbx\n\t" + "add %%rdx, %0\n\t" + "cmovc %3, %%rbx\n\t" + "add %%rbx, %0\n\t" + // TODO: migrate to labels + //"xor %%rbx, %%rbx\n\t" + //"sub %%rcx, %0\n\t" + //"cmovc %%r10, %%rbx\n\t" + //"sub %%rbx, %0\n\t" + "sub %%rcx, %0\n\t" + "jnc 1f\n\t" + "sub %3, %0\n\t" + "1: \n\t" + : "=&a"(result.fe) + : "r"(in1.fe), "r"(in2.fe), "m"(CQ), "m"(TWO32) + : "%rbx", "%rcx", "%rdx"); + +#if GOLDILOCKS_DEBUG == 1 + result.fe = result.fe % GOLDILOCKS_PRIME; +#endif +#else + mul(result, in1, in2); +#endif +} + +inline void Goldilocks::mul2(Element &result, const Element &in1, const Element &in2) +{ + +#ifdef __USE_ASSEMBLY__ + __asm__( + "mov %1, %%rax\n\t" + "mul %2\n\t" + "divq %3\n\t" + : "=&d"(result.fe) + : "r"(in1.fe), "r"(in2.fe), "m"(Q) + : "%rax"); + +#if GOLDILOCKS_DEBUG == 1 + result.fe = result.fe % GOLDILOCKS_PRIME; +#endif +#else + mul(result, in1, in2); +#endif +} + +inline void branch_hint() { + asm("nop"); +} +inline void Goldilocks::add_no_double_carry(uint64_t &result, const uint64_t &in1, const uint64_t &in2) +{ + +#ifdef __USE_ASSEMBLY__ + __asm__("xor %%r10, %%r10\n\t" + "mov %1, %0\n\t" + "add %2, %0\n\t" + "cmovc %3, %%r10\n\t" + "add %%r10, %0\n\t" + : "=&a"(result) + : "r"(in1), "r"(in2), "m"(CQ) + : "%r10"); +#endif +} +/** + * Optimized version inspired in Plonky3 optimizations, using branch_hint hint the processor that the branch is unlikely to be taken + */ + +inline void Goldilocks::mul(Element &result, const Element &in1, const Element &in2){ + + + uint64_t rh; + uint64_t rl; + + __uint128_t res = static_cast<__uint128_t>(in1.fe) * static_cast<__uint128_t>(in2.fe); + rl = (uint64_t)res; + rh = (uint64_t)(res>>64); + uint64_t rhh = rh >> 32; + uint64_t rhl = rh & 0xFFFFFFFF; + + uint64_t aux1; + aux1 = rl - rhh; + if(rhh>rl){ //this branch is unlikely to be taken + branch_hint(); + aux1-=0xFFFFFFFF; + } + uint64_t aux = 0xFFFFFFFF* rhl; + // aux1 <= 2^64-1 + // aux <= (2^32-1)*(2^32-1) = 2^64-2^32+1-2^32 = P-2^32 + // aux1 + aux <= 2^64-1 + P-2^32 = P+P-2=2P-2 + #ifdef __USE_ASSEMBLY__ + add_no_double_carry(result.fe, aux1, aux); + #else + Goldilocks::Element aux1_, aux2_; + aux1_.fe = aux1; + aux2_.fe = aux; + add(result, aux2_, aux1_); + #endif + +} + +inline Goldilocks::Element Goldilocks::square(const Element &in1) { return mul(in1, in1); }; + +inline void Goldilocks::square(Element &result, const Element &in1) { return mul(result, in1, in1); }; + +inline Goldilocks::Element Goldilocks::div(const Element &in1, const Element &in2) { return mul(in1, inv(in2)); }; + +inline void Goldilocks::div(Element &result, const Element &in1, const Element &in2) { mul(result, in1, inv(in2)); }; + +inline Goldilocks::Element Goldilocks::neg(const Element &in1) { return sub(Goldilocks::zero(), in1); }; + +inline void Goldilocks::neg(Element &result, const Element &in1) { return sub(result, Goldilocks::zero(), in1); }; + +inline bool Goldilocks::isZero(const Element &in1) { return equal(in1, Goldilocks::zero()); }; + +inline bool Goldilocks::isOne(const Element &in1) { return equal(in1, Goldilocks::one()); }; + +inline bool Goldilocks::isNegone(const Element &in1) { return equal(in1, Goldilocks::negone()); }; + +inline bool Goldilocks::equal(const Element &in1, const Element &in2) { return Goldilocks::toU64(in1) == Goldilocks::toU64(in2); } + +inline Goldilocks::Element Goldilocks::inv(const Element &in1) +{ + Goldilocks::Element result; + Goldilocks::inv(result, in1); + return result; +}; + +inline Goldilocks::Element Goldilocks::mulScalar(const Element &base, const uint64_t &scalar) +{ + Goldilocks::Element result; + Goldilocks::mulScalar(result, base, scalar); + return result; +}; +inline void Goldilocks::mulScalar(Element &result, const Element &base, const uint64_t &scalar) +{ + Element eScalar = fromU64(scalar); + mul(result, base, eScalar); +}; + +inline Goldilocks::Element Goldilocks::exp(Element base, uint64_t exp) +{ + Goldilocks::Element result; + Goldilocks::exp(result, base, exp); + return result; +}; + +inline void Goldilocks::exp(Element &result, Element base, uint64_t exp) +{ + result = Goldilocks::one(); + + for (;;) + { + if (exp & 1) + mul(result, result, base); + exp >>= 1; + if (!exp) + break; + mul(base, base, base); + } +}; +#endif \ No newline at end of file diff --git a/lib-c/c/src/poseidon2/goldilocks_base_field_tools.hpp b/lib-c/c/src/poseidon2/goldilocks_base_field_tools.hpp new file mode 100644 index 000000000..7c9feb76a --- /dev/null +++ b/lib-c/c/src/poseidon2/goldilocks_base_field_tools.hpp @@ -0,0 +1,191 @@ +#ifndef GOLDILOCKS_BASIC +#define GOLDILOCKS_BASIC +#include "goldilocks_base_field.hpp" + + +inline const Goldilocks::Element &Goldilocks::zero() { return ZERO; }; +inline void Goldilocks::zero(Element &result) { result.fe = ZERO.fe; }; + +inline const Goldilocks::Element &Goldilocks::one() { return ONE; }; +inline void Goldilocks::one(Element &result) { result.fe = ONE.fe; }; + +inline const Goldilocks::Element &Goldilocks::negone() { return NEGONE; }; +inline void Goldilocks::negone(Element &result) { result.fe = NEGONE.fe; }; + +inline const Goldilocks::Element &Goldilocks::shift() { return SHIFT; }; +inline void Goldilocks::shift(Element &result) { result.fe = SHIFT.fe; }; + +inline const Goldilocks::Element &Goldilocks::w(uint64_t i) { return W[i]; }; +inline void Goldilocks::w(Element &result, uint64_t i) { result.fe = W[i].fe; }; + +inline Goldilocks::Element Goldilocks::fromU64(uint64_t in1) +{ + Goldilocks::Element res; + Goldilocks::fromU64(res, in1); + return res; +} + +inline void Goldilocks::fromU64(Element &result, uint64_t in1) +{ + result.fe = in1; +} + +inline Goldilocks::Element Goldilocks::fromS64(int64_t in1) +{ + Goldilocks::Element res; + Goldilocks::fromS64(res, in1); + return res; +} + +inline void Goldilocks::fromS64(Element &result, int64_t in1) +{ + uint64_t aux; + (in1 < 0) ? aux = static_cast(in1) + GOLDILOCKS_PRIME : aux = static_cast(in1); + result.fe = aux; +} + + +inline Goldilocks::Element Goldilocks::fromS32(int32_t in1) +{ + Goldilocks::Element res; + Goldilocks::fromS32(res, in1); + return res; +} + +inline void Goldilocks::fromS32(Element &result, int32_t in1) +{ + uint64_t aux; + (in1 < 0) ? aux = static_cast(in1) + GOLDILOCKS_PRIME : aux = static_cast(in1); + result.fe = aux; + +} + +inline Goldilocks::Element Goldilocks::fromString(const std::string &in1, int radix) +{ + Goldilocks::Element result; + Goldilocks::fromString(result, in1, radix); + return result; +}; + +inline void Goldilocks::fromString(Element &result, const std::string &in1, int radix) +{ + mpz_class aux(in1, radix); + mpz_class gl(0xFFFFFFFF00000001); + + aux = (aux + gl) % gl; + result.fe = aux.get_ui(); + +}; + +inline Goldilocks::Element Goldilocks::fromScalar(const mpz_class &scalar) +{ + Goldilocks::Element result; + Goldilocks::fromScalar(result, scalar); + return result; +}; + +inline void Goldilocks::fromScalar(Element &result, const mpz_class &scalar) +{ + mpz_class gl(0xFFFFFFFF00000001); + mpz_class aux = (scalar + gl) % gl; + result.fe = aux.get_ui(); +}; + +inline uint64_t Goldilocks::toU64(const Element &in1) +{ + uint64_t res; + Goldilocks::toU64(res, in1); + return res; +}; +inline void Goldilocks::toU64(uint64_t &result, const Element &in1) +{ + result = in1.fe; + if( result >= GOLDILOCKS_PRIME ) + result -= GOLDILOCKS_PRIME; +}; + +inline int64_t Goldilocks::toS64(const Element &in1) +{ + int64_t res; + Goldilocks::toS64(res, in1); + return res; +} + +/* Converts a field element into a signed 64bits integer */ +inline void Goldilocks::toS64(int64_t &result, const Element &in1) +{ + + mpz_class out(std::to_string(Goldilocks::toU64(in1))); + mpz_class gl(0xFFFFFFFF00000001); + + mpz_class maxInt = (gl - 1) / 2; + + if (out > maxInt) + { + mpz_class onegative = gl - out; + result = -onegative.get_si(); + } + else + { + result = out.get_si(); + } +} + +/* Converts a field element into a signed 32bits integer */ +/* Precondition: Goldilocks::Element < 2^31 */ +inline bool Goldilocks::toS32(int32_t &result, const Element &in1) +{ + mpz_class out(std::to_string(Goldilocks::toU64(in1))); + mpz_class gl(0xFFFFFFFF00000001); + + mpz_class maxInt(0x7FFFFFFF); + mpz_class minInt = gl - mpz_class(0x80000000); + + if (out > maxInt) + { + mpz_class onegative = gl - out; + if (out > minInt) + { + result = -onegative.get_si(); + } + else + { + std::cerr << "Error: Goldilocks::toS32 accessing a non-32bit value: " << Goldilocks::toString(in1, 16) << " out=" << out.get_str(16) << " minInt=" << minInt.get_str(16) << " maxInt=" << maxInt.get_str(16) << std::endl; + return false; + } + } + else + { + result = out.get_si(); + } + return true; +} + +inline std::string Goldilocks::toString(const Element &in1, int radix) +{ + std::string result; + Goldilocks::toString(result, in1, radix); + return result; +} + +inline void Goldilocks::toString(std::string &result, const Element &in1, int radix) +{ + mpz_class aux; + uint64_t value = Goldilocks::toU64(in1); + mpz_import(aux.get_mpz_t(), 1, -1, sizeof(value), 0, 0, &value); + result = aux.get_str(radix); +} + +inline std::string Goldilocks::toString(const Element *in1, const uint64_t size, int radix) +{ + std::string result = ""; + for (uint64_t i = 0; i < size; i++) + { + mpz_class aux; + uint64_t value = Goldilocks::toU64(in1[i]); + mpz_import(aux.get_mpz_t(), 1, -1, sizeof(value), 0, 0, &value); + result += std::to_string(i) + ": " + aux.get_str(radix) + "\n"; + } + return result; +} +#endif \ No newline at end of file diff --git a/lib-c/c/src/poseidon2/poseidon2_goldilocks.cpp b/lib-c/c/src/poseidon2/poseidon2_goldilocks.cpp new file mode 100644 index 000000000..e3cc869cd --- /dev/null +++ b/lib-c/c/src/poseidon2/poseidon2_goldilocks.cpp @@ -0,0 +1,127 @@ +#ifndef POSEIDON2_GOLDILOCKS +#define POSEIDON2_GOLDILOCKS + +#include +#include "poseidon2_goldilocks_constants.hpp" +#include "goldilocks_base_field.hpp" + +#define WIDTH 16 + +inline void pow7(Goldilocks::Element &x) +{ + Goldilocks::Element x2 = x * x; + Goldilocks::Element x3 = x * x2; + Goldilocks::Element x4 = x2 * x2; + x = x3 * x4; +}; + +inline void add_(Goldilocks::Element &x, const Goldilocks::Element *st) +{ + for (int i = 0; i < WIDTH; ++i) + { + x = x + st[i]; + } +} +inline void prodadd_(Goldilocks::Element *x, const Goldilocks::Element *D, const Goldilocks::Element &sum) +{ + for (int i = 0; i < WIDTH; ++i) + { + x[i] = x[i]*D[i] + sum; + } +} + +inline void pow7add_(Goldilocks::Element *x, const Goldilocks::Element *C) +{ + Goldilocks::Element x2[WIDTH], x3[WIDTH], x4[WIDTH]; + + for (int i = 0; i < WIDTH; ++i) + { + Goldilocks::Element xi = x[i] + C[i]; + x2[i] = xi * xi; + x3[i] = xi * x2[i]; + x4[i] = x2[i] * x2[i]; + x[i] = x3[i] * x4[i]; + } +}; + +inline void matmul_m4_(Goldilocks::Element *x) { + Goldilocks::Element t0 = x[0] + x[1]; + Goldilocks::Element t1 = x[2] + x[3]; + Goldilocks::Element t2 = x[1] + x[1] + t1; + Goldilocks::Element t3 = x[3] + x[3] + t0; + Goldilocks::Element t1_2 = t1 + t1; + Goldilocks::Element t0_2 = t0 + t0; + Goldilocks::Element t4 = t1_2 + t1_2 + t3; + Goldilocks::Element t5 = t0_2 + t0_2 + t2; + Goldilocks::Element t6 = t3 + t5; + Goldilocks::Element t7 = t2 + t4; + + x[0] = t6; + x[1] = t5; + x[2] = t7; + x[3] = t4; +} + +inline void matmul_external_(Goldilocks::Element *x) { + for (int i = 0; i < WIDTH/4; ++i) { + matmul_m4_(&x[i*4]); + } + + Goldilocks::Element stored[4] = {Goldilocks::zero(), Goldilocks::zero(), Goldilocks::zero(), Goldilocks::zero()}; + + for(int i = 0; i < 4; ++i) { + for (int j = 0; j < WIDTH/4; ++j) { + stored[i] = stored[i] + x[j*4 + i]; + } + } + + for (int i = 0; i < WIDTH; ++i) + { + x[i] = x[i] + stored[i % 4]; + } +} + +void Poseidon2(Goldilocks::Element *state) +{ + const Goldilocks::Element *RC = Poseidon2GoldilocksConstants::RC; + const Goldilocks::Element *D = Poseidon2GoldilocksConstants::DIAG; + + matmul_external_(state); + + for (int r = 0; r < 4; r++) + { + pow7add_(state, &(RC[WIDTH * r])); + matmul_external_(state); + } + + for (int r = 0; r < 22; r++) + { + state[0] = state[0] + RC[4 * WIDTH + r]; + pow7(state[0]); + Goldilocks::Element sum_ = Goldilocks::zero(); + add_(sum_, state); + prodadd_(state, D, sum_); + } + + for (int r = 0; r < 4; r++) + { + pow7add_(state, &(RC[4 * WIDTH + 22 + r * WIDTH])); + matmul_external_(state); + } +} + + +void poseidon2_hash(uint64_t *state) +{ + Goldilocks::Element stateGL[16]; + for(uint64_t i = 0; i < 16; ++i) { + stateGL[i] = Goldilocks::fromU64(state[i]); + } + Poseidon2(stateGL); + + for(uint64_t i = 0; i < WIDTH; ++i) { + state[i] = Goldilocks::toU64(stateGL[i]); + } +} + +#endif \ No newline at end of file diff --git a/lib-c/c/src/poseidon2/poseidon2_goldilocks_constants.hpp b/lib-c/c/src/poseidon2/poseidon2_goldilocks_constants.hpp new file mode 100644 index 000000000..8c36738e9 --- /dev/null +++ b/lib-c/c/src/poseidon2/poseidon2_goldilocks_constants.hpp @@ -0,0 +1,181 @@ +#ifndef POSEIDON2_GOLDILOCKS_CONSTANTS +#define POSEIDON2_GOLDILOCKS_CONSTANTS +#endif // POSEIDON2_GOLDILOCKS_CONSTANTS +#include "goldilocks_base_field.hpp" + +namespace Poseidon2GoldilocksConstants +{ + inline constexpr static Goldilocks::Element DIAG[16] = { + {0xde9b91a467d6afc0}, + {0xc5f16b9c76a9be17}, + {0x0ab0fef2d540ac55}, + {0x3001d27009d05773}, + {0xed23b1f906d3d9eb}, + {0x5ce73743cba97054}, + {0x1c3bab944af4ba24}, + {0x2faa105854dbafae}, + {0x53ffb3ae6d421a10}, + {0xbcda9df8884ba396}, + {0xfc1273e4a31807bb}, + {0xc77952573d5142c0}, + {0x56683339a819b85e}, + {0x328fcbd8f0ddc8eb}, + {0xb5101e303fce9cb7}, + {0x774487b8c40089bb}, + }; + + inline constexpr static Goldilocks::Element RC[150] = { + {0x15ebea3fc73397c3}, + {0xd73cd9fbfe8e275c}, + {0x8c096bfce77f6c26}, + {0x4e128f68b53d8fea}, + {0x29b779a36b2763f6}, + {0xfe2adc6fb65acd08}, + {0x8d2520e725ad0955}, + {0x1c2392b214624d2a}, + {0x37482118206dcc6e}, + {0x2f829bed19be019a}, + {0x2fe298cb6f8159b0}, + {0x2bbad982deccdbbf}, + {0xbad568b8cc60a81e}, + {0xb86a814265baad10}, + {0xbec2005513b3acb3}, + {0x6bf89b59a07c2a94}, + {0xa25deeb835e230f5}, + {0x3c5bad8512b8b12a}, + {0x7230f73c3cb7a4f2}, + {0xa70c87f095c74d0f}, + {0x6b7606b830bb2e80}, + {0x6cd467cfc4f24274}, + {0xfeed794df42a9b0a}, + {0x8cf7cf6163b7dbd3}, + {0x9a6e9dda597175a0}, + {0xaa52295a684faf7b}, + {0x017b811cc3589d8d}, + {0x55bfb699b6181648}, + {0xc2ccaf71501c2421}, + {0x1707950327596402}, + {0xdd2fcdcd42a8229f}, + {0x8b9d7d5b27778a21}, + {0xac9a05525f9cf512}, + {0x2ba125c58627b5e8}, + {0xc74e91250a8147a5}, + {0xa3e64b640d5bb384}, + {0xf53047d18d1f9292}, + {0xbaaeddacae3a6374}, + {0xf2d0914a808b3db1}, + {0x18af1a3742bfa3b0}, + {0x9a621ef50c55bdb8}, + {0xc615f4d1cc5466f3}, + {0xb7fbac19a35cf793}, + {0xd2b1a15ba517e46d}, + {0x4a290c4d7fd26f6f}, + {0x4f0cf1bb1770c4c4}, + {0x548345386cd377f5}, + {0x33978d2789fddd42}, + {0xab78c59deb77e211}, + {0xc485b2a933d2be7f}, + {0xbde3792c00c03c53}, + {0xab4cefe8f893d247}, + {0xc5c0e752eab7f85f}, + {0xdbf5a76f893bafea}, + {0xa91f6003e3d984de}, + {0x099539077f311e87}, + {0x097ec52232f9559e}, + {0x53641bdf8991e48c}, + {0x2afe9711d5ed9d7c}, + {0xa7b13d3661b5d117}, + {0x5a0e243fe7af6556}, + {0x1076fae8932d5f00}, + {0x9b53a83d434934e3}, + {0xed3fd595a3c0344a}, + {0x28eff4b01103d100}, + {0x60400ca3e2685a45}, + {0x1c8636beb3389b84}, + {0xac1332b60e13eff0}, + {0x2adafcc364e20f87}, + {0x79ffc2b14054ea0b}, + {0x3f98e4c0908f0a05}, + {0xcdb230bc4e8a06c4}, + {0x1bcaf7705b152a74}, + {0xd9bca249a82a7470}, + {0x91e24af19bf82551}, + {0xa62b43ba5cb78858}, + {0xb4898117472e797f}, + {0xb3228bca606cdaa0}, + {0x844461051bca39c9}, + {0xf3411581f6617d68}, + {0xf7fd50646782b533}, + {0x6ca664253c18fb48}, + {0x2d2fcdec0886a08f}, + {0x29da00dd799b575e}, + {0x47d966cc3b6e1e93}, + {0xde884e9a17ced59e}, + {0xdacf46dc1c31a045}, + {0x5d2e3c121eb387f2}, + {0x51f8b0658b124499}, + {0x1e7dbd1daa72167d}, + {0x8275015a25c55b88}, + {0xe8521c24ac7a70b3}, + {0x6521d121c40b3f67}, + {0xac12de797de135b0}, + {0xafa28ead79f6ed6a}, + {0x685174a7a8d26f0b}, + {0xeff92a08d35d9874}, + {0x3058734b76dd123a}, + {0xfa55dcfba429f79c}, + {0x559294d4324c7728}, + {0x7a770f53012dc178}, + {0xedd8f7c408f3883b}, + {0x39b533cf8d795fa5}, + {0x160ef9de243a8c0a}, + {0x431d52da6215fe3f}, + {0x54c51a2a2ef6d528}, + {0x9b13892b46ff9d16}, + {0x263c46fcee210289}, + {0xb738c96d25aabdc4}, + {0x5c33a5203996d38f}, + {0x2626496e7c98d8dd}, + {0xc669e0a52785903a}, + {0xaecde726c8ae1f47}, + {0x039343ef3a81e999}, + {0x2615ceaf044a54f9}, + {0x7e41e834662b66e1}, + {0x4ca5fd4895335783}, + {0x64b334d02916f2b0}, + {0x87268837389a6981}, + {0x034b75bcb20a6274}, + {0x58e658296cc2cd6e}, + {0xe2d0f759acc31df4}, + {0x81a652e435093e20}, + {0x0b72b6e0172eaf47}, + {0x4aec43cec577d66d}, + {0xde78365b028a84e6}, + {0x444e19569adc0ee4}, + {0x942b2451fa40d1da}, + {0xe24506623ea5bd6c}, + {0x082854bf2ef7c743}, + {0x69dbbc566f59d62e}, + {0x248c38d02a7b5cb2}, + {0x4f4e8f8c09d15edb}, + {0xd96682f188d310cf}, + {0x6f9a25d56818b54c}, + {0xb6cefed606546cd9}, + {0x5bc07523da38a67b}, + {0x7df5a3c35b8111cf}, + {0xaaa2cc5d4db34bb0}, + {0x9e673ff22a4653f8}, + {0xbd8b278d60739c62}, + {0xe10d20f6925b8815}, + {0xf6c87b91dd4da2bf}, + {0xfed623e2f71b6f1a}, + {0xa0f02fa52a94d0d3}, + {0xbb5794711b39fa16}, + {0xd3b94fba9d005c7f}, + {0x15a26e89fad946c9}, + {0xf3cb87db8a67cf49}, + {0x400d2bf56aa2a577}, + }; + + +} \ No newline at end of file From 2b3ec8f68ab5f12f7a2060da9b968e76549801cf Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 16 Jan 2026 15:59:40 +0100 Subject: [PATCH 273/782] Add Poseidon2 precompile support to assembly --- core/src/zisk_rom_2_asm.rs | 106 +++++++++++++++++- emulator-asm/src/emu.c | 55 +++++++++ emulator-asm/src/emu.hpp | 3 + lib-c/c/Makefile | 6 +- .../c/src/poseidon2/poseidon2_goldilocks.cpp | 7 ++ .../c/src/poseidon2/poseidon2_goldilocks.hpp | 16 +++ 6 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 lib-c/c/src/poseidon2/poseidon2_goldilocks.hpp diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 9283570a1..ce2ffd236 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -5157,7 +5157,111 @@ impl ZiskRom2Asm { ctx.flag_is_always_zero = true; } ZiskOp::Poseidon2 => { - // TODO + // Use the memory address as the first and unique parameter + *code += &ctx.full_line_comment("Poseidon2: rdi = A0".to_string()); + + // Generate mem reads + if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() + { + // Use the memory address as the first and unique parameter + *code += &format!( + "\tmov rdi, {} {}\n", + ctx.b.string_value, + ctx.comment_str("rdi = b = address") + ); + + // Copy read data into mem_reads_address and advance it + if ctx.minimal_trace() || ctx.zip() || ctx.mem_reads() { + // If zip, check if chunk is active + if ctx.zip() { + *code += &format!( + "\ttest {}, 1 {}\n", + REG_ACTIVE_CHUNK, + ctx.comment_str("active_chunk == 1 ?") + ); + *code += &format!("\tjnz pc_{:x}_poseidon2_active_chunk\n", ctx.pc); + *code += + &format!("\tjmp pc_{:x}_poseidon2_active_chunk_done\n", ctx.pc); + *code += &format!("pc_{:x}_poseidon2_active_chunk:\n", ctx.pc); + } + *code += &format!("\tmov {REG_ADDRESS}, rdi\n"); + for k in 0..16 { + *code += &format!( + "\tmov {}, [{} + {}] {}\n", + REG_VALUE, + REG_ADDRESS, + k * 8, + ctx.comment(format!("value = mem[poseidon2_address[{k}]]")) + ); + *code += &format!( + "\tmov [{} + {}*8 + {}], {} {}\n", + REG_MEM_READS_ADDRESS, + REG_MEM_READS_SIZE, + k * 8, + REG_VALUE, + ctx.comment(format!("mem_reads[{k}] = value")) + ); + } + + // Increment chunk.steps.mem_reads_size in 16 units + *code += &format!( + "\tadd {}, 16 {}\n", + REG_MEM_READS_SIZE, + ctx.comment_str("mem_reads_size += 16") + ); + + if ctx.zip() { + *code += &format!("pc_{:x}_poseidon2_active_chunk_done:\n", ctx.pc); + } + } + + // Trace 16 memory read operations + if ctx.mem_op() { + *code += &format!("\tmov {REG_ADDRESS}, rdi\n"); + Self::mem_op_array(ctx, code, REG_ADDRESS, false, 8, 16); + Self::mem_op_array(ctx, code, REG_ADDRESS, true, 8, 16); + } + + // Call the poseidon2 function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_poseidon2\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } + + // Consume mem reads + if ctx.chunk_player_mem_reads_collect_main() { + *code += &format!( + "\tmov [{} + {}*8], {} {}\n", + REG_MEM_READS_ADDRESS, + REG_MEM_READS_SIZE, + REG_CHUNK_PLAYER_ADDRESS, + ctx.comment_str("Main[4] = precompiler data address") + ); + *code += &format!( + "\tinc {} {}\n", + REG_MEM_READS_SIZE, + ctx.comment_str("mem_reads_size++") + ); + } + if ctx.chunk_player_mt_collect_mem() || ctx.chunk_player_mem_reads_collect_main() { + *code += &format!( + "\tadd {}, 16*8 {}\n", + REG_CHUNK_PLAYER_ADDRESS, + ctx.comment_str("chunk_address += 16*8") + ); + } + + // Set result + *code += &format!( + "\txor {}, {} {}\n", + REG_C, + REG_C, + ctx.comment_str("Poseidon2: c = 0") + ); + ctx.c.is_saved = true; + ctx.flag_is_always_zero = true; } ZiskOp::PubOut => { assert!(ctx.store_b_in_c); diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index ba92f3800..a5f20ebc0 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -15,6 +15,7 @@ #include "../../lib-c/c/src/arith384/arith384.hpp" #include "../../lib-c/c/src/bn254/bn254.hpp" #include "../../lib-c/c/src/bls12_381/bls12_381.hpp" +#include "../../lib-c/c/src/poseidon2/poseidon2_goldilocks.hpp" #include "bcon/bcon_sha256.hpp" extern void zisk_sha256(uint64_t state[4], uint64_t input[8]); @@ -37,6 +38,8 @@ void reset_asm_call_metrics (void) asm_call_metrics.keccak_duration = 0; asm_call_metrics.sha256_counter = 0; asm_call_metrics.sha256_duration = 0; + asm_call_metrics.poseidon2_counter = 0; + asm_call_metrics.poseidon2_duration = 0; asm_call_metrics.arith256_counter = 0; asm_call_metrics.arith256_duration = 0; asm_call_metrics.arith256_mod_counter = 0; @@ -103,6 +106,16 @@ void print_asm_call_metrics (uint64_t total_duration) duration, percentage); + // Print poseidon2 metrics + percentage = total_duration == 0 ? 0 : (asm_call_metrics.poseidon2_duration * 1000) / total_duration; + duration = asm_call_metrics.poseidon2_counter == 0 ? 0 : (asm_call_metrics.poseidon2_duration * 1000) / asm_call_metrics.poseidon2_counter; + asm_call_total_duration += asm_call_metrics.poseidon2_duration; + printf("Poseidon2: counter = %lu, duration = %lu us, single duration = %lu ns, per thousand = %lu \n", + asm_call_metrics.poseidon2_counter, + asm_call_metrics.poseidon2_duration, + duration, + percentage); + // Print arith256 metrics percentage = total_duration == 0 ? 0 : (asm_call_metrics.arith256_duration * 1000) / total_duration; duration = asm_call_metrics.arith256_counter == 0 ? 0 : (asm_call_metrics.arith256_duration * 1000) / asm_call_metrics.arith256_counter; @@ -522,6 +535,48 @@ extern int _opcode_sha256(uint64_t * address) return 0; } +extern int _opcode_poseidon2(uint64_t address) +{ +#ifdef ASM_CALL_METRICS + gettimeofday(&asm_call_start, NULL); +#endif +#ifdef DEBUG +#ifdef ASM_CALL_METRICS + if (emu_verbose) printf("opcode_poseidon2() calling Poseidon2() counter=%lu address=%08lx\n", asm_call_metrics.poseidon2_counter, address); +#else + if (emu_verbose) printf("opcode_poseidon2() calling Poseidon2() address=%08lx\n", address); +#endif +#endif + +#ifdef ASM_PRECOMPILE_CACHE + if (precompile_cache_storing) + { +#endif + // Call poseidon2 compression function + poseidon2_hash((uint64_t *)address); + +#ifdef ASM_PRECOMPILE_CACHE + // Store result in cache + precompile_cache_store((uint8_t *)address, 16*8); + } + else if (precompile_cache_loading) + { + // Load result from cache + precompile_cache_load((uint8_t *)address, 16*8); + } +#endif + +#ifdef DEBUG + if (emu_verbose) printf("opcode_poseidon2() called Poseidon2()\n"); +#endif +#ifdef ASM_CALL_METRICS + asm_call_metrics.poseidon2_counter++; + gettimeofday(&asm_call_stop, NULL); + asm_call_metrics.poseidon2_duration += TimeDiff(asm_call_start, asm_call_stop); +#endif + return 0; +} + extern int _opcode_arith256(uint64_t * address) { #ifdef ASM_CALL_METRICS diff --git a/emulator-asm/src/emu.hpp b/emulator-asm/src/emu.hpp index 13c36f04d..7fe7cc080 100644 --- a/emulator-asm/src/emu.hpp +++ b/emulator-asm/src/emu.hpp @@ -27,6 +27,9 @@ typedef struct { uint64_t sha256_counter; uint64_t sha256_duration; + uint64_t poseidon2_counter; + uint64_t poseidon2_duration; + uint64_t arith256_counter; uint64_t arith256_duration; diff --git a/lib-c/c/Makefile b/lib-c/c/Makefile index e196df67b..ab3686ff6 100644 --- a/lib-c/c/Makefile +++ b/lib-c/c/Makefile @@ -24,6 +24,8 @@ all: gcc $(CFLAGS) -c src/arith384/arith384.cpp -o build/arith384.o gcc $(CFLAGS) -c src/bigint/add256.cpp -o build/add256.o gcc $(CFLAGS) -c src/common/globals.cpp -o build/globals.o + gcc $(CFLAGS) -c src/poseidon2/goldilocks_base_field.cpp -o build/goldilocks_base_field.o + gcc $(CFLAGS) -c src/poseidon2/poseidon2_goldilocks.cpp -o build/poseidon2_goldilocks.o ar rcs\ build/libziskc.a\ build/fec.o\ @@ -41,7 +43,9 @@ all: build/arith256.o\ build/arith384.o\ build/add256.o\ - build/globals.o + build/globals.o\ + build/goldilocks_base_field.o\ + build/poseidon2_goldilocks.o gcc $(CFLAGS) src/main.cpp -lc build/libziskc.a -o build/clib -lgmp -lstdc++ -lgmpxx mkdir -p lib cp build/libziskc.a lib/ diff --git a/lib-c/c/src/poseidon2/poseidon2_goldilocks.cpp b/lib-c/c/src/poseidon2/poseidon2_goldilocks.cpp index e3cc869cd..1fc5f1d13 100644 --- a/lib-c/c/src/poseidon2/poseidon2_goldilocks.cpp +++ b/lib-c/c/src/poseidon2/poseidon2_goldilocks.cpp @@ -110,6 +110,9 @@ void Poseidon2(Goldilocks::Element *state) } } +#ifdef __cplusplus +extern "C" { +#endif void poseidon2_hash(uint64_t *state) { @@ -124,4 +127,8 @@ void poseidon2_hash(uint64_t *state) } } +#ifdef __cplusplus +} // extern "C" +#endif + #endif \ No newline at end of file diff --git a/lib-c/c/src/poseidon2/poseidon2_goldilocks.hpp b/lib-c/c/src/poseidon2/poseidon2_goldilocks.hpp new file mode 100644 index 000000000..dbdde90ec --- /dev/null +++ b/lib-c/c/src/poseidon2/poseidon2_goldilocks.hpp @@ -0,0 +1,16 @@ +#ifndef POSEIDON2_GOLDILOCKS_HPP +#define POSEIDON2_GOLDILOCKS_HPP + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void poseidon2_hash(uint64_t *state); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif \ No newline at end of file From 77963b2c1c4a3b09257fdf4a07b2fde9fea65fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20i=20Buxadera?= Date: Mon, 19 Jan 2026 12:52:17 +0100 Subject: [PATCH 274/782] Cargo fmt --- verifier/src/verifier.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/verifier/src/verifier.rs b/verifier/src/verifier.rs index 7b7c27098..31d42325e 100644 --- a/verifier/src/verifier.rs +++ b/verifier/src/verifier.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Ok, Result}; -use proofman_verifier::{verify_vadcop_final_compressed, verify_vadcop_final}; +use proofman_verifier::{verify_vadcop_final, verify_vadcop_final_compressed}; pub fn verify_zisk_proof(zisk_proof: &[u8], vk: &[u8]) -> Result<()> { if !verify_vadcop_final(zisk_proof, vk) { @@ -16,4 +16,3 @@ pub fn verify_zisk_proof_compressed(zisk_proof: &[u8], vk: &[u8]) -> Result<()> Ok(()) } } - From bdcddef943bd7b852be551c280d7f16af02d8dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20i=20Buxadera?= Date: Mon, 19 Jan 2026 15:12:58 +0100 Subject: [PATCH 275/782] Update Cargo --- Cargo.lock | 80 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 16 +++++------ 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e66ef8150..ac9d5c775 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,7 +301,7 @@ dependencies = [ "mem-planner-cpp", "named-sem", "rayon", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "ureq", "zisk-common", @@ -627,7 +627,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -638,9 +638,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", @@ -773,11 +773,11 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1040,7 +1040,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" dependencies = [ "fields", "num-bigint", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" dependencies = [ "cfg-if", "num-bigint", @@ -1394,9 +1394,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "findshlibs" @@ -2379,7 +2379,7 @@ dependencies = [ "mpi-sys", "once_cell", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2415,7 +2415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0875efe1a57a20d0cee7034499aa9d764b3c7525563fa3c3f16a2ccf01ddfa04" dependencies = [ "libc", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows", ] @@ -2704,7 +2704,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" dependencies = [ "colored", "fields", @@ -3077,7 +3077,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" dependencies = [ "blake3", "borsh", @@ -3112,7 +3112,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" dependencies = [ "borsh", "colored", @@ -3134,7 +3134,7 @@ dependencies = [ "serde_json", "sysinfo 0.35.2", "tabled", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "tracing-subscriber", "yansi", @@ -3143,7 +3143,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" dependencies = [ "fields", "itoa", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" dependencies = [ "proc-macro2", "quote", @@ -3167,7 +3167,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" dependencies = [ "crossbeam-channel", "tracing", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" dependencies = [ "colored", "fields", @@ -3187,7 +3187,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" dependencies = [ "bytemuck", "fields", @@ -3291,7 +3291,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3312,7 +3312,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -3443,7 +3443,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3657,9 +3657,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4910321ebe4151be888e35fe062169554e74aad01beafed60410131420ceffbc" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -3667,9 +3667,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -4251,11 +4251,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -4271,9 +4271,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -4668,7 +4668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing-subscriber", ] @@ -5413,7 +5413,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fopt-verifier#de99550be802f2d046843b0ec301a4df7d418663" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" dependencies = [ "colored", "fields", @@ -5626,7 +5626,7 @@ dependencies = [ "proofman-common", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "tracing-appender", "tracing-subscriber", @@ -5655,7 +5655,7 @@ dependencies = [ "serde_json", "signal-hook", "signal-hook-tokio", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tonic", "tracing", @@ -5849,9 +5849,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" +checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index f0d656c23..5c42140b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,14 +103,14 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/opt-verifier" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } # Proofman Local development # proofman = { path = "../pil2-proofman/proofman" } # proofman-common = { path = "../pil2-proofman/common" } From 8191e9bdfef32378d3d220262c0e46c7f3375685 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 14:14:56 +0000 Subject: [PATCH 276/782] update Cargo.toml and Cargo.lock --- Cargo.lock | 590 +++++++++++++++++++++++++++++++---------------------- Cargo.toml | 16 +- 2 files changed, 357 insertions(+), 249 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f1d27c49..dcda357aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,7 +205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -218,7 +218,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -268,7 +268,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -310,13 +310,52 @@ dependencies = [ "mem-planner-cpp", "named-sem", "rayon", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "ureq", "zisk-common", "zisk-core", ] +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -336,7 +375,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -347,7 +386,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -364,9 +403,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.1" +version = "1.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +checksum = "e84ce723ab67259cfeb9877c6a639ee9eb7a27b28123abd71db7f0d5d0cc9d86" dependencies = [ "aws-lc-sys", "zeroize", @@ -374,9 +413,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.34.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +checksum = "43a442ece363113bd4bd4c8b18977a7798dd4d3c3383f34fb61936960e8f4ad8" dependencies = [ "cc", "cmake", @@ -411,9 +450,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -456,9 +495,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bincode" @@ -489,7 +528,7 @@ dependencies = [ "bitflags 2.10.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "lazycell", "proc-macro2", @@ -497,7 +536,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -517,15 +556,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", + "cpufeatures", ] [[package]] @@ -557,7 +597,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -657,7 +697,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -668,9 +708,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.50" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", @@ -707,9 +747,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -763,9 +803,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -773,9 +813,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -792,20 +832,20 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "cmake" -version = "0.1.55" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49d74c227b6cc9f3c51a2c7c667a05b6453f7f0f952a5f8e4493bb9e731d68e" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] @@ -818,11 +858,11 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -850,7 +890,7 @@ dependencies = [ "serde-untagged", "serde_core", "serde_json", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "winnow", "yaml-rust2", ] @@ -889,16 +929,16 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "tiny-keccak", ] [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "conv" @@ -1140,7 +1180,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "fields", "num-bigint", @@ -1176,6 +1216,12 @@ dependencies = [ "zisk-core", ] +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + [[package]] name = "debugid" version = "0.8.0" @@ -1195,6 +1241,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.5" @@ -1245,7 +1305,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1286,7 +1346,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1352,7 +1412,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1404,7 +1464,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1431,7 +1491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1475,9 +1535,9 @@ dependencies = [ [[package]] name = "fastbloom" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c1ddb9231d8554c2d6bdf4cfaabf0c59251658c68b6c95cd52dd0c513a912a" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" dependencies = [ "getrandom 0.3.4", "libm", @@ -1504,7 +1564,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "num-bigint", "paste", @@ -1513,9 +1573,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "findshlibs" @@ -1537,9 +1597,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -1628,7 +1688,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1674,9 +1734,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -1737,9 +1797,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -2082,9 +2142,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -2131,9 +2191,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -2147,7 +2207,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2165,6 +2225,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -2191,9 +2260,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" dependencies = [ "jiff-static", "log", @@ -2204,13 +2273,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2247,9 +2316,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -2308,15 +2377,15 @@ version = "0.16.0" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libffi" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0444124f3ffd67e1b0b0c661a7f81a278a135eb54aaad4078e79fbc8be50c8a5" +checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b" dependencies = [ "libc", "libffi-sys", @@ -2324,9 +2393,9 @@ dependencies = [ [[package]] name = "libffi-sys" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d722da8817ea580d0669da6babe2262d7b86a1af1103da24102b8bb9c101ce7" +checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb" dependencies = [ "cc", ] @@ -2361,9 +2430,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", @@ -2523,7 +2592,7 @@ dependencies = [ "mpi-sys", "once_cell", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2559,7 +2628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0875efe1a57a20d0cee7034499aa9d764b3c7525563fa3c3f16a2ccf01ddfa04" dependencies = [ "libc", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows", ] @@ -2701,6 +2770,15 @@ dependencies = [ "ruzstd", ] +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -2721,9 +2799,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" [[package]] name = "option-ext" @@ -2819,9 +2897,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", "ucd-trie", @@ -2829,9 +2907,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -2839,22 +2917,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "pest_meta" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2", @@ -2862,18 +2940,19 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", + "hashbrown 0.15.5", "indexmap", ] [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "colored", "fields", @@ -2907,7 +2986,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2968,9 +3047,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-atomic-util" @@ -3216,7 +3295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3230,9 +3309,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -3240,7 +3319,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "blake3", "borsh", @@ -3275,7 +3354,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "borsh", "colored", @@ -3297,7 +3376,7 @@ dependencies = [ "serde_json", "sysinfo 0.35.2", "tabled", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "tracing-subscriber", "yansi", @@ -3306,7 +3385,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "fields", "itoa", @@ -3319,18 +3398,18 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "proc-macro2", "quote", "rayon", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "crossbeam-channel", "tracing", @@ -3339,7 +3418,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "colored", "fields", @@ -3350,7 +3429,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "bytemuck", "fields", @@ -3360,9 +3439,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", "prost-derive", @@ -3370,15 +3449,14 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", "itertools 0.14.0", "log", "multimap", - "once_cell", "petgraph", "prettyplease", "prost", @@ -3386,28 +3464,28 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.111", + "syn 2.0.114", "tempfile", ] [[package]] name = "prost-derive" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "prost-types" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" dependencies = [ "prost", ] @@ -3425,9 +3503,9 @@ dependencies = [ [[package]] name = "pulldown-cmark-to-cmark" -version = "21.1.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8246feae3db61428fd0bb94285c690b460e4517d83152377543ca802357785f1" +checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" dependencies = [ "pulldown-cmark", ] @@ -3455,7 +3533,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3478,7 +3556,7 @@ dependencies = [ "rustls-pki-types", "rustls-platform-verifier", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -3495,14 +3573,14 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -3531,7 +3609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3551,7 +3629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3560,14 +3638,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -3594,14 +3672,15 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fae430c6b28f1ad601274e78b7dffa0546de0b73b4cd32f46723c0c2a16f7a5" +checksum = "3ec0a99f2de91c3cddc84b37e7db80e4d96b743e05607f647eb236fc0455907f" dependencies = [ "pem", "ring", "rustls-pki-types", "time", + "x509-parser", "yasna", ] @@ -3620,9 +3699,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3722,7 +3801,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -3777,9 +3856,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" @@ -3806,6 +3885,15 @@ dependencies = [ "toolchain_find", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "1.1.3" @@ -3816,14 +3904,14 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "log", @@ -3837,9 +3925,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -3849,9 +3937,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -3875,7 +3963,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3886,9 +3974,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring", @@ -3913,9 +4001,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -4036,14 +4124,14 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.147" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "indexmap", "itoa", @@ -4127,10 +4215,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -4364,9 +4453,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.17.0" +version = "12.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d8046c5674ab857104bc4559d505f4809b8060d57806e45d49737c97afeb60" +checksum = "520cf51c674f8b93d533f80832babe413214bb766b6d7cb74ee99ad2971f8467" dependencies = [ "debugid", "memmap2", @@ -4376,9 +4465,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.17.0" +version = "12.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1accb6e5c4b0f682de907623912e616b44be1c9e725775155546669dbff720ec" +checksum = "9f0de2ee0ffa2641e17ba715ad51d48b9259778176517979cb38b6aa86fa7425" dependencies = [ "cc", "cpp_demangle", @@ -4400,9 +4489,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -4426,7 +4515,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4494,7 +4583,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4508,11 +4597,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -4523,18 +4612,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4548,9 +4637,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", @@ -4558,22 +4647,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", @@ -4625,9 +4714,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -4648,7 +4737,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4663,9 +4752,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -4674,9 +4763,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -4699,9 +4788,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.10+spec-1.1.0" +version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ "indexmap", "serde_core", @@ -4815,7 +4904,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4840,7 +4929,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.111", + "syn 2.0.114", "tempfile", "tonic-build", ] @@ -4860,9 +4949,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -4926,7 +5015,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing-subscriber", ] @@ -4939,7 +5028,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5026,9 +5115,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" @@ -5097,9 +5186,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -5201,18 +5290,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -5223,11 +5312,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -5236,9 +5326,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5246,22 +5336,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -5281,9 +5371,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -5301,18 +5391,18 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -5339,7 +5429,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5415,7 +5505,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5426,7 +5516,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5738,14 +5828,14 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?rev=b313d269405e28450f56199b0061d94b59f84ad7#b313d269405e28450f56199b0061d94b59f84ad7" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" dependencies = [ "colored", "fields", @@ -5762,6 +5852,24 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "x509-parser" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + [[package]] name = "yaml-rust2" version = "0.10.4" @@ -5807,28 +5915,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5848,7 +5956,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] @@ -5863,13 +5971,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5902,7 +6010,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5931,7 +6039,7 @@ dependencies = [ "rustls", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "tracing-subscriber", @@ -5972,7 +6080,7 @@ dependencies = [ "proofman-common", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "tracing-appender", "tracing-subscriber", @@ -6001,7 +6109,7 @@ dependencies = [ "serde_json", "signal-hook", "signal-hook-tokio", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tonic", "tracing", @@ -6050,7 +6158,7 @@ dependencies = [ "serde", "tokio", "tokio-stream", - "toml 0.9.10+spec-1.1.0", + "toml 0.9.11+spec-1.1.0", "tonic", "tracing", "uuid", @@ -6176,7 +6284,7 @@ version = "0.16.0" dependencies = [ "bincode", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "lazy_static", "lib-c", "num-bigint", @@ -6197,7 +6305,7 @@ dependencies = [ "bincode", "cfg-if", "elliptic-curve", - "getrandom 0.2.16", + "getrandom 0.2.17", "k256", "lazy_static", "lib-c", @@ -6214,9 +6322,9 @@ dependencies = [ [[package]] name = "zmij" -version = "0.1.7" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e404bcd8afdaf006e529269d3e85a743f9480c3cef60034d77860d02964f3ba" +checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 4e7ade593..d251f0c6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,14 +105,14 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", rev = "b313d269405e28450f56199b0061d94b59f84ad7" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } # Proofman Local development # proofman = { path = "../pil2-proofman/proofman" } # proofman-common = { path = "../pil2-proofman/common" } From da7af6a6e1f05ad052633b589a8859f507827a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20i=20Buxadera?= Date: Mon, 19 Jan 2026 20:20:32 +0100 Subject: [PATCH 277/782] Updating cost poseidon2 precompile --- core/src/zisk_ops.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 92bfd551d..5f82f3a7d 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -323,7 +323,7 @@ const ARITHA32_COST: u64 = 95; const ARITHAM32_COST: u64 = 95; const KECCAK_COST: u64 = (((93846 * 86) - 1) / 63) + 1; const SHA256_COST: u64 = 72 * 121; -const POSEIDON2_COST: u64 = 14 * 16; +const POSEIDON2_COST: u64 = 14 * 75; const ARITH_EQ_COST: u64 = 85 * 16; const FCALL_COST: u64 = INTERNAL_COST; const ARITH_EQ_384_COST: u64 = 79 * 24; From e6f2449cc4828d300ae285e30ec33344e049101a Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Mon, 19 Jan 2026 12:20:28 +0000 Subject: [PATCH 278/782] Modifying FROPs processing to avoid memory increasing --- .vscode/settings.json | 2 +- Cargo.lock | 22 ++++---- cli/src/commands/verify_constraints.rs | 4 +- executor/src/sm_static_bundle.rs | 2 +- executor/src/static_data_bus_collect.rs | 25 +++++---- state-machines/arith/src/arith.rs | 9 ++- state-machines/arith/src/arith_full.rs | 18 +----- .../arith/src/arith_full_instance.rs | 56 ++++++++++++------- state-machines/binary/src/binary.rs | 24 +++++--- state-machines/binary/src/binary_add.rs | 16 +----- .../binary/src/binary_add_collector.rs | 28 +++++++--- .../binary/src/binary_add_instance.rs | 27 +++++---- state-machines/binary/src/binary_basic.rs | 19 +------ .../binary/src/binary_basic_collector.rs | 29 +++++++--- .../binary/src/binary_basic_instance.rs | 28 ++++++---- state-machines/binary/src/binary_extension.rs | 20 +------ .../binary/src/binary_extension_collector.rs | 29 +++++++--- .../binary/src/binary_extension_instance.rs | 31 ++++++---- 18 files changed, 206 insertions(+), 183 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9709e08be..56aa3b430 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.formatOnSave": true, - "editor.hover.enabled": true + "editor.hover.enabled": "on" }, "editor.rulers": [ 100 diff --git a/Cargo.lock b/Cargo.lock index ac9d5c775..3e482ac47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1040,7 +1040,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#29b52dc5f1863eb82b104e495e6262bfb9d3327d" dependencies = [ "fields", "num-bigint", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#29b52dc5f1863eb82b104e495e6262bfb9d3327d" dependencies = [ "cfg-if", "num-bigint", @@ -2704,7 +2704,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#29b52dc5f1863eb82b104e495e6262bfb9d3327d" dependencies = [ "colored", "fields", @@ -3077,7 +3077,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#29b52dc5f1863eb82b104e495e6262bfb9d3327d" dependencies = [ "blake3", "borsh", @@ -3112,7 +3112,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#29b52dc5f1863eb82b104e495e6262bfb9d3327d" dependencies = [ "borsh", "colored", @@ -3143,7 +3143,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#29b52dc5f1863eb82b104e495e6262bfb9d3327d" dependencies = [ "fields", "itoa", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#29b52dc5f1863eb82b104e495e6262bfb9d3327d" dependencies = [ "proc-macro2", "quote", @@ -3167,7 +3167,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#29b52dc5f1863eb82b104e495e6262bfb9d3327d" dependencies = [ "crossbeam-channel", "tracing", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#29b52dc5f1863eb82b104e495e6262bfb9d3327d" dependencies = [ "colored", "fields", @@ -3187,7 +3187,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#29b52dc5f1863eb82b104e495e6262bfb9d3327d" dependencies = [ "bytemuck", "fields", @@ -5413,7 +5413,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#17af326a816de1ffbbbd30006fc05f3cdcc7fdd8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#29b52dc5f1863eb82b104e495e6262bfb9d3327d" dependencies = [ "colored", "fields", diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 4fd80591d..7a7d8061b 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -1,4 +1,4 @@ -use crate::{commands::cli_fail_if_gpu_mode, ux::print_banner}; +use crate::ux::print_banner; use anyhow::Result; use clap::Parser; @@ -76,8 +76,6 @@ pub struct ZiskVerifyConstraints { impl ZiskVerifyConstraints { pub fn run(&mut self) -> Result<()> { - cli_fail_if_gpu_mode()?; - print_banner(); let stdin = self.create_stdin()?; diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index ba6e289f3..adcf0f123 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -254,7 +254,7 @@ impl StaticSMBundle { pctx: &ProofCtx, secn_instances: &HashMap>>, chunks_to_execute: &[Vec], - ) -> Vec>> { + ) -> Vec>> { chunks_to_execute .par_iter() .enumerate() diff --git a/executor/src/static_data_bus_collect.rs b/executor/src/static_data_bus_collect.rs index d5e118143..ecd21bdec 100644 --- a/executor/src/static_data_bus_collect.rs +++ b/executor/src/static_data_bus_collect.rs @@ -5,6 +5,7 @@ use std::collections::VecDeque; use data_bus::DataBusTrait; +use fields::PrimeField64; use precomp_arith_eq::ArithEqCollector; use precomp_arith_eq::ArithEqCounterInputGen; use precomp_arith_eq_384::ArithEq384Collector; @@ -37,18 +38,18 @@ use zisk_core::ZiskOperationType; /// * `D` - The type of data payloads handled by the bus. /// * `BD` - The type of devices (subscribers) connected to the bus, implementing the `BusDevice` /// trait. -pub struct StaticDataBusCollect { +pub struct StaticDataBusCollect { /// Memory-related collectors (grouped for cache locality) pub mem_collector: Vec<(usize, MemModuleCollector)>, pub mem_align_collector: Vec<(usize, MemAlignCollector)>, /// Binary operation collectors (grouped for cache locality) - pub binary_basic_collector: Vec<(usize, BinaryBasicCollector)>, - pub binary_add_collector: Vec<(usize, BinaryAddCollector)>, - pub binary_extension_collector: Vec<(usize, BinaryExtensionCollector)>, + pub binary_basic_collector: Vec<(usize, BinaryBasicCollector)>, + pub binary_add_collector: Vec<(usize, BinaryAddCollector)>, + pub binary_extension_collector: Vec<(usize, BinaryExtensionCollector)>, /// Arithmetic collectors (grouped for cache locality) - pub arith_collector: Vec<(usize, ArithInstanceCollector)>, + pub arith_collector: Vec<(usize, ArithInstanceCollector)>, pub arith_inputs_generator: ArithCounterInputGen, /// Cryptographic hash collectors (grouped for cache locality) @@ -90,16 +91,16 @@ const ARITH_EQ_TYPE: u64 = ZiskOperationType::ArithEq as u64; const ARITH_EQ_384_TYPE: u64 = ZiskOperationType::ArithEq384 as u64; const BIG_INT_OP_TYPE_ID: u64 = ZiskOperationType::BigInt as u64; -impl StaticDataBusCollect { +impl StaticDataBusCollect { /// Creates a new `DataBus` instance. #[allow(clippy::too_many_arguments)] pub fn new( mem_collector: Vec<(usize, MemModuleCollector)>, mem_align_collector: Vec<(usize, MemAlignCollector)>, - binary_basic_collector: Vec<(usize, BinaryBasicCollector)>, - binary_add_collector: Vec<(usize, BinaryAddCollector)>, - binary_extension_collector: Vec<(usize, BinaryExtensionCollector)>, - arith_collector: Vec<(usize, ArithInstanceCollector)>, + binary_basic_collector: Vec<(usize, BinaryBasicCollector)>, + binary_add_collector: Vec<(usize, BinaryAddCollector)>, + binary_extension_collector: Vec<(usize, BinaryExtensionCollector)>, + arith_collector: Vec<(usize, ArithInstanceCollector)>, keccakf_collector: Vec<(usize, KeccakfCollector)>, sha256f_collector: Vec<(usize, Sha256fCollector)>, poseidon2_collector: Vec<(usize, Poseidon2Collector)>, @@ -333,8 +334,8 @@ impl StaticDataBusCollect { } } -impl DataBusTrait>> - for StaticDataBusCollect +impl DataBusTrait>> + for StaticDataBusCollect { #[inline(always)] fn write_to_bus(&mut self, bus_id: BusId, payload: &[PayloadType]) -> bool { diff --git a/state-machines/arith/src/arith.rs b/state-machines/arith/src/arith.rs index 92c9c6c8f..bf41af61f 100644 --- a/state-machines/arith/src/arith.rs +++ b/state-machines/arith/src/arith.rs @@ -24,6 +24,9 @@ use crate::{ArithCounterInputGen, ArithFullInstance, ArithFullSM, ArithPlanner}; pub struct ArithSM { /// Arith Full state machine arith_full_sm: Arc>, + + /// Standard library instance, providing common functionalities. + std: Arc>, } impl ArithSM { @@ -32,9 +35,9 @@ impl ArithSM { /// # Returns /// An `Arc`-wrapped instance of `ArithSM` containing initialized sub-state machines. pub fn new(std: Arc>) -> Arc { - let arith_full_sm = ArithFullSM::new(std); + let arith_full_sm = ArithFullSM::new(std.clone()); - Arc::new(Self { arith_full_sm }) + Arc::new(Self { arith_full_sm, std }) } pub fn build_arith_counter(&self) -> ArithCounterInputGen { @@ -78,7 +81,7 @@ impl ComponentBuilder for ArithSM { fn build_instance(&self, ictx: InstanceCtx) -> Box> { match ictx.plan.air_id { ArithTrace::::AIR_ID => { - Box::new(ArithFullInstance::new(self.arith_full_sm.clone(), ictx)) + Box::new(ArithFullInstance::new(self.arith_full_sm.clone(), ictx, self.std.clone())) } _ => panic!("BinarySM::get_instance() Unsupported air_id: {:?}", ictx.plan.air_id), } diff --git a/state-machines/arith/src/arith_full.rs b/state-machines/arith/src/arith_full.rs index 86b673fe9..35b1a986c 100644 --- a/state-machines/arith/src/arith_full.rs +++ b/state-machines/arith/src/arith_full.rs @@ -8,8 +8,7 @@ use std::collections::VecDeque; use std::sync::Arc; use crate::{ - ArithFrops, ArithOperation, ArithRangeTableInputs, ArithRangeTableSM, ArithTableInputs, - ArithTableSM, + ArithOperation, ArithRangeTableInputs, ArithRangeTableSM, ArithTableInputs, ArithTableSM, }; use fields::PrimeField64; use pil_std_lib::Std; @@ -49,9 +48,6 @@ pub struct ArithFullSM { /// The table ID for the Range Table State Machine range_table_id: usize, - - /// The table ID for the FROPS - frops_table_id: usize, } impl ArithFullSM { @@ -72,11 +68,7 @@ impl ArithFullSM { .get_virtual_table_id(ArithRangeTableSM::TABLE_ID) .expect("Failed to get range table ID"); - // Get the Arithmetic FROPS table ID - let frops_table_id = - std.get_virtual_table_id(ArithFrops::TABLE_ID).expect("Failed to get FROPS table ID"); - - Arc::new(Self { std, table_id, range_table_id, frops_table_id }) + Arc::new(Self { std, table_id, range_table_id }) } /// Computes the witness for arithmetic operations and updates associated tables. @@ -177,12 +169,6 @@ impl ArithFullSM { Ok(AirInstance::new_from_trace(FromTrace::new(&mut arith_trace))) } - pub fn compute_frops(&self, frops_inputs: &Vec) { - for row in frops_inputs { - self.std.inc_virtual_row(self.frops_table_id, *row as u64, 1); - } - } - /// Generates binary inputs for operations requiring additional validation (e.g., division). #[inline(always)] pub fn generate_inputs(input: &OperationData, pending: &mut VecDeque<(BusId, Vec)>) { diff --git a/state-machines/arith/src/arith_full_instance.rs b/state-machines/arith/src/arith_full_instance.rs index e25017b01..6e4afa402 100644 --- a/state-machines/arith/src/arith_full_instance.rs +++ b/state-machines/arith/src/arith_full_instance.rs @@ -6,6 +6,7 @@ use crate::{ArithFrops, ArithFullSM}; use fields::PrimeField64; +use pil_std_lib::Std; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{ collections::{HashMap, VecDeque}, @@ -33,6 +34,9 @@ pub struct ArithFullInstance { /// The instance context. ictx: InstanceCtx, + + /// Standard library instance, providing common functionalities. + std: Arc>, } impl ArithFullInstance { @@ -44,7 +48,11 @@ impl ArithFullInstance { /// /// # Returns /// A new `ArithFullInstance` instance initialized with the provided state machine and context. - pub fn new(arith_full_sm: Arc>, mut ictx: InstanceCtx) -> Self { + pub fn new( + arith_full_sm: Arc>, + mut ictx: InstanceCtx, + std: Arc>, + ) -> Self { assert_eq!( ictx.plan.air_id, ArithTrace::::AIR_ID, @@ -58,13 +66,17 @@ impl ArithFullInstance { .downcast::>() .expect("Failed to downcast ictx.plan.meta to expected type"); - Self { arith_full_sm, collect_info, ictx } + Self { arith_full_sm, collect_info, ictx, std } } - pub fn build_arith_collector(&self, chunk_id: ChunkId) -> ArithInstanceCollector { - let (num_ops, num_freq_ops, force_execute_to_end, collect_skipper) = - self.collect_info[&chunk_id]; - ArithInstanceCollector::new(num_ops, num_freq_ops, collect_skipper, force_execute_to_end) + pub fn build_arith_collector(&self, chunk_id: ChunkId) -> ArithInstanceCollector { + let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; + ArithInstanceCollector::new( + num_ops, + collect_skipper, + force_execute_to_end, + self.std.clone(), + ) } } @@ -91,8 +103,8 @@ impl Instance for ArithFullInstance { let inputs: Vec<_> = collectors .into_iter() .map(|(_, collector)| { - let _collector = collector.as_any().downcast::().unwrap(); - self.arith_full_sm.compute_frops(&_collector.frops_inputs); + let _collector = + collector.as_any().downcast::>().unwrap(); _collector.inputs }) .collect(); @@ -123,13 +135,12 @@ impl Instance for ArithFullInstance { /// # Returns /// An `Option` containing the input collector for the instance. fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { - let (num_ops, num_freq_ops, force_execute_to_end, collect_skipper) = - self.collect_info[&chunk_id]; + let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; Some(Box::new(ArithInstanceCollector::new( num_ops, - num_freq_ops, collect_skipper, force_execute_to_end, + self.std.clone(), ))) } @@ -139,11 +150,9 @@ impl Instance for ArithFullInstance { } /// The `ArithInstanceCollector` struct represents an input collector for arithmetic state machines. -pub struct ArithInstanceCollector { +pub struct ArithInstanceCollector { /// Collected inputs for witness computation. inputs: Vec>, - /// Collected rows for FROPS - frops_inputs: Vec, /// The number of operations to collect. num_operations: u64, @@ -153,9 +162,15 @@ pub struct ArithInstanceCollector { /// Flag to indicate that force to execute to end of chunk force_execute_to_end: bool, + + /// The table ID for the Arith FROPS + frops_table_id: usize, + + /// Standard library instance, providing common functionalities. + std: Arc>, } -impl ArithInstanceCollector { +impl ArithInstanceCollector { /// Creates a new `ArithInstanceCollector`. /// /// # Arguments @@ -168,21 +183,24 @@ impl ArithInstanceCollector { /// A new `ArithInstanceCollector` instance initialized with the provided parameters. pub fn new( num_operations: u64, - num_freq_ops: u64, collect_skipper: CollectSkipper, force_execute_to_end: bool, + std: Arc>, ) -> Self { + let frops_table_id = + std.get_virtual_table_id(ArithFrops::TABLE_ID).expect("Failed to get FROPS table ID"); Self { inputs: Vec::with_capacity(num_operations as usize), num_operations, collect_skipper, - frops_inputs: Vec::with_capacity(num_freq_ops as usize), force_execute_to_end, + std, + frops_table_id, } } } -impl BusDevice for ArithInstanceCollector { +impl BusDevice for ArithInstanceCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -219,7 +237,7 @@ impl BusDevice for ArithInstanceCollector { } if frops_row != ArithFrops::NO_FROPS { - self.frops_inputs.push(frops_row as u32); + self.std.inc_virtual_row(self.frops_table_id, frops_row as u64, 1); return true; } diff --git a/state-machines/binary/src/binary.rs b/state-machines/binary/src/binary.rs index 508009833..cc3c0c6e6 100644 --- a/state-machines/binary/src/binary.rs +++ b/state-machines/binary/src/binary.rs @@ -30,6 +30,8 @@ pub struct BinarySM { /// Binary Add state machine (optimal only for addition) binary_add_sm: Arc>, + + std: Arc>, } impl BinarySM { @@ -45,9 +47,9 @@ impl BinarySM { let binary_extension_sm = BinaryExtensionSM::new(std.clone()); - let binary_add_sm = BinaryAddSM::new(std); + let binary_add_sm = BinaryAddSM::new(std.clone()); - Arc::new(Self { binary_basic_sm, binary_extension_sm, binary_add_sm }) + Arc::new(Self { binary_basic_sm, binary_extension_sm, binary_add_sm, std }) } pub fn build_binary_counter(&self) -> BinaryCounter { @@ -82,14 +84,18 @@ impl ComponentBuilder for BinarySM { /// A boxed implementation of `Instance` for binary operations. fn build_instance(&self, ictx: InstanceCtx) -> Box> { match ictx.plan.air_id { - BinaryTrace::::AIR_ID => { - Box::new(BinaryBasicInstance::new(self.binary_basic_sm.clone(), ictx)) - } - BinaryExtensionTrace::::AIR_ID => { - Box::new(BinaryExtensionInstance::new(self.binary_extension_sm.clone(), ictx)) - } + BinaryTrace::::AIR_ID => Box::new(BinaryBasicInstance::new( + self.binary_basic_sm.clone(), + ictx, + self.std.clone(), + )), + BinaryExtensionTrace::::AIR_ID => Box::new(BinaryExtensionInstance::new( + self.binary_extension_sm.clone(), + ictx, + self.std.clone(), + )), BinaryAddTrace::::AIR_ID => { - Box::new(BinaryAddInstance::new(self.binary_add_sm.clone(), ictx)) + Box::new(BinaryAddInstance::new(self.binary_add_sm.clone(), ictx, self.std.clone())) } _ => panic!("BinarySM::get_instance() Unsupported air_id: {:?}", ictx.plan.air_id), } diff --git a/state-machines/binary/src/binary_add.rs b/state-machines/binary/src/binary_add.rs index 608a2ad30..d30f61250 100644 --- a/state-machines/binary/src/binary_add.rs +++ b/state-machines/binary/src/binary_add.rs @@ -2,7 +2,6 @@ //! //! This state machine processes binary-related operations. -use crate::BinaryBasicFrops; use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; @@ -31,9 +30,6 @@ pub struct BinaryAddSM { /// Reference to the PIL2 standard library. std: Arc>, range_id: usize, - - /// The table ID for the FROPS - frops_table_id: usize, } impl BinaryAddSM { @@ -47,12 +43,8 @@ impl BinaryAddSM { pub fn new(std: Arc>) -> Arc { let range_id = std.get_range_id(0, 0xFFFF, None).expect("Failed to get range ID"); - // Get the Arithmetic FROPS table ID - let frops_table_id = std - .get_virtual_table_id(BinaryBasicFrops::TABLE_ID) - .expect("Failed to get FROPS table ID"); // Create the BinaryAdd state machine - Arc::new(Self { std, range_id, frops_table_id }) + Arc::new(Self { std, range_id }) } /// Processes a slice of operation data, generating a trace row and updating multiplicities. @@ -168,10 +160,4 @@ impl BinaryAddSM { FromTrace::new(&mut add_trace).with_air_values(&mut air_values), )) } - - pub fn compute_frops(&self, frops_inputs: &Vec) { - for row in frops_inputs { - self.std.inc_virtual_row(self.frops_table_id, *row as u64, 1); - } - } } diff --git a/state-machines/binary/src/binary_add_collector.rs b/state-machines/binary/src/binary_add_collector.rs index 26390c8dc..2e5982a85 100644 --- a/state-machines/binary/src/binary_add_collector.rs +++ b/state-machines/binary/src/binary_add_collector.rs @@ -8,21 +8,29 @@ use zisk_common::{ }; use zisk_core::zisk_ops::ZiskOp; +use fields::PrimeField64; +use pil_std_lib::Std; +use std::sync::Arc; + /// The `BinaryAddCollector` struct represents an input collector for binary add operations. -pub struct BinaryAddCollector { +pub struct BinaryAddCollector { /// Collected inputs for witness computation. pub inputs: Vec<[u64; 2]>, - /// Collected rows for FROPS - pub frops_inputs: Vec, pub num_operations: usize, pub collect_skipper: CollectSkipper, /// Flag to indicate that force to execute to end of chunk force_execute_to_end: bool, + + /// The table ID for the Binary Add FROPS + frops_table_id: usize, + + /// Standard library instance, providing common functionalities. + std: Arc>, } -impl BinaryAddCollector { +impl BinaryAddCollector { /// Creates a new `BinaryAddCollector`. /// /// # Arguments @@ -33,21 +41,25 @@ impl BinaryAddCollector { /// A new `BinaryAddCollector` instance initialized with the provided parameters. pub fn new( num_operations: usize, - num_freq_ops: usize, collect_skipper: CollectSkipper, force_execute_to_end: bool, + std: Arc>, ) -> Self { + let frops_table_id = std + .get_virtual_table_id(BinaryBasicFrops::TABLE_ID) + .expect("Failed to get FROPS table ID"); Self { inputs: Vec::with_capacity(num_operations), num_operations, collect_skipper, - frops_inputs: Vec::with_capacity(num_freq_ops), force_execute_to_end, + frops_table_id, + std, } } } -impl BusDevice for BinaryAddCollector { +impl BusDevice for BinaryAddCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -89,7 +101,7 @@ impl BusDevice for BinaryAddCollector { } if frops_row != BinaryBasicFrops::NO_FROPS { - self.frops_inputs.push(frops_row as u32); + self.std.inc_virtual_row(self.frops_table_id, frops_row as u64, 1); return true; } diff --git a/state-machines/binary/src/binary_add_instance.rs b/state-machines/binary/src/binary_add_instance.rs index 3642b70f8..e315744dc 100644 --- a/state-machines/binary/src/binary_add_instance.rs +++ b/state-machines/binary/src/binary_add_instance.rs @@ -6,6 +6,7 @@ use crate::{BinaryAddCollector, BinaryAddSM}; use fields::PrimeField64; +use pil_std_lib::Std; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{collections::HashMap, sync::Arc}; use zisk_common::{ @@ -27,6 +28,9 @@ pub struct BinaryAddInstance { /// Instance context. ictx: InstanceCtx, + + /// Standard library instance, providing common functionalities. + std: Arc>, } impl BinaryAddInstance { @@ -39,7 +43,11 @@ impl BinaryAddInstance { /// # Returns /// A new `BinaryAddInstance` instance initialized with the provided state machine and /// context. - pub fn new(binary_add_sm: Arc>, mut ictx: InstanceCtx) -> Self { + pub fn new( + binary_add_sm: Arc>, + mut ictx: InstanceCtx, + std: Arc>, + ) -> Self { assert_eq!( ictx.plan.air_id, BinaryAddTrace::::AIR_ID, @@ -53,23 +61,22 @@ impl BinaryAddInstance { .downcast::>() .expect("Failed to downcast ictx.plan.meta to expected type"); - Self { binary_add_sm, collect_info, ictx } + Self { binary_add_sm, collect_info, ictx, std } } - pub fn build_binary_add_collector(&self, chunk_id: ChunkId) -> BinaryAddCollector { + pub fn build_binary_add_collector(&self, chunk_id: ChunkId) -> BinaryAddCollector { assert_eq!( self.ictx.plan.air_id, BinaryAddTrace::::AIR_ID, "BinaryAddInstance: Unsupported air_id: {:?}", self.ictx.plan.air_id ); - let (num_ops, num_freq_ops, force_execute_to_end, collect_skipper) = - self.collect_info[&chunk_id]; + let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; BinaryAddCollector::new( num_ops as usize, - num_freq_ops as usize, collect_skipper, force_execute_to_end, + self.std.clone(), ) } } @@ -97,8 +104,7 @@ impl Instance for BinaryAddInstance { let inputs: Vec<_> = collectors .into_iter() .map(|(_, collector)| { - let _collector = collector.as_any().downcast::().unwrap(); - self.binary_add_sm.compute_frops(&_collector.frops_inputs); + let _collector = collector.as_any().downcast::>().unwrap(); _collector.inputs }) .collect(); @@ -136,13 +142,12 @@ impl Instance for BinaryAddInstance { "BinaryAddInstance: Unsupported air_id: {:?}", self.ictx.plan.air_id ); - let (num_ops, num_freq_ops, force_execute_to_end, collect_skipper) = - self.collect_info[&chunk_id]; + let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; Some(Box::new(BinaryAddCollector::new( num_ops as usize, - num_freq_ops as usize, collect_skipper, force_execute_to_end, + self.std.clone(), ))) } diff --git a/state-machines/binary/src/binary_basic.rs b/state-machines/binary/src/binary_basic.rs index 7c376df88..51e4f9fe6 100644 --- a/state-machines/binary/src/binary_basic.rs +++ b/state-machines/binary/src/binary_basic.rs @@ -4,9 +4,7 @@ use std::sync::Arc; -use crate::{ - binary_constants::*, BinaryBasicFrops, BinaryBasicTableOp, BinaryBasicTableSM, BinaryInput, -}; +use crate::{binary_constants::*, BinaryBasicTableOp, BinaryBasicTableSM, BinaryInput}; use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; @@ -39,9 +37,6 @@ pub struct BinaryBasicSM { /// The table ID for the Binary Basic State Machine table_id: usize, - - /// The table ID for the FROPS - frops_table_id: usize, } impl BinaryBasicSM { @@ -57,12 +52,7 @@ impl BinaryBasicSM { let table_id = std.get_virtual_table_id(BinaryBasicTableSM::TABLE_ID).expect("Failed to get range ID"); - // Get the FROPS table ID - let frops_table_id = std - .get_virtual_table_id(BinaryBasicFrops::TABLE_ID) - .expect("Failed to get FROPS table ID"); - - Arc::new(Self { std, table_id, frops_table_id }) + Arc::new(Self { std, table_id }) } /// Determines if an opcode corresponds to a 32-bit operation. @@ -958,9 +948,4 @@ impl BinaryBasicSM { FromTrace::new(&mut binary_trace).with_air_values(&mut air_values), )) } - pub fn compute_frops(&self, frops_inputs: &Vec) { - for row in frops_inputs { - self.std.inc_virtual_row(self.frops_table_id, *row as u64, 1); - } - } } diff --git a/state-machines/binary/src/binary_basic_collector.rs b/state-machines/binary/src/binary_basic_collector.rs index b03703592..2a33e0fd8 100644 --- a/state-machines/binary/src/binary_basic_collector.rs +++ b/state-machines/binary/src/binary_basic_collector.rs @@ -11,12 +11,14 @@ use zisk_common::{ }; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; +use fields::PrimeField64; +use pil_std_lib::Std; +use std::sync::Arc; + /// The `BinaryBasicCollector` struct represents an input collector for binary-related operations. -pub struct BinaryBasicCollector { +pub struct BinaryBasicCollector { /// Collected inputs for witness computation. pub inputs: Vec, - /// Collected rows for FROPS - pub frops_inputs: Vec, pub num_operations: usize, @@ -27,9 +29,15 @@ pub struct BinaryBasicCollector { /// Flag to indicate that force to execute to end of chunk force_execute_to_end: bool, + + /// The table ID for the Binary FROPS + frops_table_id: usize, + + /// Standard library instance, providing common functionalities. + std: Arc>, } -impl BinaryBasicCollector { +impl BinaryBasicCollector { /// Creates a new `BinaryBasicCollector`. /// /// # Arguments @@ -40,23 +48,28 @@ impl BinaryBasicCollector { /// A new `BinaryBasicCollector` instance initialized with the provided parameters. pub fn new( num_operations: usize, - num_freq_ops: usize, collect_skipper: CollectSkipper, with_adds: bool, force_execute_to_end: bool, + std: Arc>, ) -> Self { + let frops_table_id = std + .get_virtual_table_id(BinaryBasicFrops::TABLE_ID) + .expect("Failed to get FROPS table ID"); + Self { inputs: Vec::with_capacity(num_operations), num_operations, collect_skipper, with_adds, - frops_inputs: Vec::with_capacity(num_freq_ops), force_execute_to_end, + frops_table_id, + std, } } } -impl BusDevice for BinaryBasicCollector { +impl BusDevice for BinaryBasicCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -102,7 +115,7 @@ impl BusDevice for BinaryBasicCollector { } if frops_row != BinaryBasicFrops::NO_FROPS { - self.frops_inputs.push(frops_row as u32); + self.std.inc_virtual_row(self.frops_table_id, frops_row as u64, 1); return true; } diff --git a/state-machines/binary/src/binary_basic_instance.rs b/state-machines/binary/src/binary_basic_instance.rs index 2ebd2ddd8..b8a8404df 100644 --- a/state-machines/binary/src/binary_basic_instance.rs +++ b/state-machines/binary/src/binary_basic_instance.rs @@ -6,13 +6,13 @@ use crate::{BinaryBasicCollector, BinaryBasicSM}; use fields::PrimeField64; +use pil_std_lib::Std; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{collections::HashMap, sync::Arc}; use zisk_common::{ BusDevice, CheckPoint, ChunkId, CollectSkipper, Instance, InstanceCtx, InstanceType, PayloadType, }; - use zisk_pil::BinaryTrace; /// The `BinaryBasicInstance` struct represents an instance for binary-related witness computations. @@ -31,6 +31,9 @@ pub struct BinaryBasicInstance { /// Collect info for each chunk ID, containing the number of rows and a skipper for collection. collect_info: HashMap, + + /// Standard library instance, providing common functionalities. + std: Arc>, } impl BinaryBasicInstance { @@ -43,7 +46,11 @@ impl BinaryBasicInstance { /// # Returns /// A new `BinaryBasicInstance` instance initialized with the provided state machine and /// context. - pub fn new(binary_basic_sm: Arc>, mut ictx: InstanceCtx) -> Self { + pub fn new( + binary_basic_sm: Arc>, + mut ictx: InstanceCtx, + std: Arc>, + ) -> Self { assert_eq!( ictx.plan.air_id, BinaryTrace::::AIR_ID, @@ -57,18 +64,17 @@ impl BinaryBasicInstance { .downcast::<(bool, HashMap)>() .expect("Failed to downcast ictx.plan.meta to expected type"); - Self { binary_basic_sm, ictx, with_adds, collect_info } + Self { binary_basic_sm, ictx, with_adds, collect_info, std } } - pub fn build_binary_basic_collector(&self, chunk_id: ChunkId) -> BinaryBasicCollector { - let (num_ops, num_freq_ops, force_execute_to_end, collect_skipper) = - self.collect_info[&chunk_id]; + pub fn build_binary_basic_collector(&self, chunk_id: ChunkId) -> BinaryBasicCollector { + let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; BinaryBasicCollector::new( num_ops as usize, - num_freq_ops as usize, collect_skipper, self.with_adds, force_execute_to_end, + self.std.clone(), ) } } @@ -96,8 +102,7 @@ impl Instance for BinaryBasicInstance { let inputs: Vec<_> = collectors .into_iter() .map(|(_, collector)| { - let _collector = collector.as_any().downcast::().unwrap(); - self.binary_basic_sm.compute_frops(&_collector.frops_inputs); + let _collector = collector.as_any().downcast::>().unwrap(); _collector.inputs }) .collect(); @@ -129,14 +134,13 @@ impl Instance for BinaryBasicInstance { /// # Returns /// An `Option` containing the input collector for the instance. fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { - let (num_ops, num_freq_ops, force_execute_to_end, collect_skipper) = - self.collect_info[&chunk_id]; + let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; Some(Box::new(BinaryBasicCollector::new( num_ops as usize, - num_freq_ops as usize, collect_skipper, self.with_adds, force_execute_to_end, + self.std.clone(), ))) } diff --git a/state-machines/binary/src/binary_extension.rs b/state-machines/binary/src/binary_extension.rs index 988903fc0..84098153e 100644 --- a/state-machines/binary/src/binary_extension.rs +++ b/state-machines/binary/src/binary_extension.rs @@ -5,10 +5,7 @@ use std::sync::Arc; -use crate::{ - binary_constants::*, BinaryExtensionFrops, BinaryExtensionTableOp, BinaryExtensionTableSM, - BinaryInput, -}; +use crate::{binary_constants::*, BinaryExtensionTableOp, BinaryExtensionTableSM, BinaryInput}; use fields::PrimeField64; use pil_std_lib::Std; @@ -58,9 +55,6 @@ pub struct BinaryExtensionSM { /// The table ID for the Binary Basic State Machine table_id: usize, - - /// The table ID for the Binary Extension FROPS - frops_table_id: usize, } impl BinaryExtensionSM { @@ -80,12 +74,7 @@ impl BinaryExtensionSM { .get_virtual_table_id(BinaryExtensionTableSM::TABLE_ID) .expect("Failed to get table ID"); - // Get the FROPS table ID - let frops_table_id = std - .get_virtual_table_id(BinaryExtensionFrops::TABLE_ID) - .expect("Failed to get FROPS table ID"); - - Arc::new(Self { std, range_id, table_id, frops_table_id }) + Arc::new(Self { std, range_id, table_id }) } /// Determines if the given opcode represents a shift operation. @@ -413,9 +402,4 @@ impl BinaryExtensionSM { FromTrace::new(&mut binary_e_trace).with_air_values(&mut air_values), )) } - pub fn compute_frops(&self, frops_inputs: &Vec) { - for row in frops_inputs { - self.std.inc_virtual_row(self.frops_table_id, *row as u64, 1); - } - } } diff --git a/state-machines/binary/src/binary_extension_collector.rs b/state-machines/binary/src/binary_extension_collector.rs index 0caa5dfc3..deeb14269 100644 --- a/state-machines/binary/src/binary_extension_collector.rs +++ b/state-machines/binary/src/binary_extension_collector.rs @@ -9,40 +9,53 @@ use zisk_common::{ BusDevice, BusId, CollectSkipper, ExtOperationData, MemCollectorInfo, OperationBusData, A, B, OP, OPERATION_BUS_ID, }; + +use fields::PrimeField64; +use pil_std_lib::Std; +use std::sync::Arc; + use zisk_core::ZiskOperationType; /// The `BinaryExtensionCollector` struct represents an input collector for binary extension -pub struct BinaryExtensionCollector { +pub struct BinaryExtensionCollector { /// Collected inputs for witness computation. pub inputs: Vec, - /// Collected rows for FROPS - pub frops_inputs: Vec, pub num_operations: usize, pub collect_skipper: CollectSkipper, /// Flag to indicate that force to execute to end of chunk force_execute_to_end: bool, + + /// The table ID for the Binary Extension FROPS + frops_table_id: usize, + + /// Standard library instance, providing common functionalities. + std: Arc>, } -impl BinaryExtensionCollector { +impl BinaryExtensionCollector { pub fn new( num_operations: usize, - num_freq_ops: usize, collect_skipper: CollectSkipper, force_execute_to_end: bool, + std: Arc>, ) -> Self { + let frops_table_id = std + .get_virtual_table_id(BinaryExtensionFrops::TABLE_ID) + .expect("Failed to get FROPS table ID"); Self { inputs: Vec::with_capacity(num_operations), num_operations, collect_skipper, - frops_inputs: Vec::with_capacity(num_freq_ops), force_execute_to_end, + frops_table_id, + std, } } } -impl BusDevice for BinaryExtensionCollector { +impl BusDevice for BinaryExtensionCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -84,7 +97,7 @@ impl BusDevice for BinaryExtensionCollector { } if frops_row != BinaryExtensionFrops::NO_FROPS { - self.frops_inputs.push(frops_row as u32); + self.std.inc_virtual_row(self.frops_table_id, frops_row as u64, 1); return true; } diff --git a/state-machines/binary/src/binary_extension_instance.rs b/state-machines/binary/src/binary_extension_instance.rs index ac0c83372..9858cc296 100644 --- a/state-machines/binary/src/binary_extension_instance.rs +++ b/state-machines/binary/src/binary_extension_instance.rs @@ -6,6 +6,7 @@ use crate::{BinaryExtensionCollector, BinaryExtensionSM}; use fields::PrimeField64; +use pil_std_lib::Std; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{collections::HashMap, sync::Arc}; use zisk_common::{ @@ -28,6 +29,9 @@ pub struct BinaryExtensionInstance { /// Instance context. ictx: InstanceCtx, + + /// Standard library instance, providing common functionalities. + std: Arc>, } impl BinaryExtensionInstance { @@ -41,7 +45,11 @@ impl BinaryExtensionInstance { /// # Returns /// A new `BinaryExtensionInstance` instance initialized with the provided state machine and /// context. - pub fn new(binary_extension_sm: Arc>, mut ictx: InstanceCtx) -> Self { + pub fn new( + binary_extension_sm: Arc>, + mut ictx: InstanceCtx, + std: Arc>, + ) -> Self { assert_eq!( ictx.plan.air_id, BinaryExtensionTrace::::AIR_ID, @@ -55,10 +63,13 @@ impl BinaryExtensionInstance { .downcast::>() .expect("Failed to downcast ictx.plan.meta to expected type"); - Self { binary_extension_sm, collect_info, ictx } + Self { binary_extension_sm, collect_info, ictx, std } } - pub fn build_binary_extension_collector(&self, chunk_id: ChunkId) -> BinaryExtensionCollector { + pub fn build_binary_extension_collector( + &self, + chunk_id: ChunkId, + ) -> BinaryExtensionCollector { assert_eq!( self.ictx.plan.air_id, BinaryExtensionTrace::::AIR_ID, @@ -66,13 +77,12 @@ impl BinaryExtensionInstance { self.ictx.plan.air_id ); - let (num_ops, num_freq_ops, force_execute_to_end, collect_skipper) = - self.collect_info[&chunk_id]; + let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; BinaryExtensionCollector::new( num_ops as usize, - num_freq_ops as usize, collect_skipper, force_execute_to_end, + self.std.clone(), ) } } @@ -100,8 +110,8 @@ impl Instance for BinaryExtensionInstance { let inputs: Vec<_> = collectors .into_iter() .map(|(_, collector)| { - let _collector = collector.as_any().downcast::().unwrap(); - self.binary_extension_sm.compute_frops(&_collector.frops_inputs); + let _collector = + collector.as_any().downcast::>().unwrap(); _collector.inputs }) .collect(); @@ -133,13 +143,12 @@ impl Instance for BinaryExtensionInstance { /// # Returns /// An `Option` containing the input collector for the instance. fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { - let (num_ops, num_freq_ops, force_execute_to_end, collect_skipper) = - self.collect_info[&chunk_id]; + let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; Some(Box::new(BinaryExtensionCollector::new( num_ops as usize, - num_freq_ops as usize, collect_skipper, force_execute_to_end, + self.std.clone(), ))) } From 1094cbba110ef6948412bcf01afbdb17ef305a67 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Mon, 19 Jan 2026 13:35:33 +0000 Subject: [PATCH 279/782] Removing unnecessary value in collect_info --- cli/src/commands/common.rs | 9 --------- cli/src/commands/execute.rs | 4 +--- common/src/planner_helpers.rs | 11 +++-------- sdk/src/utils.rs | 9 --------- state-machines/arith/src/arith_full_instance.rs | 8 ++++---- state-machines/binary/src/binary_add_instance.rs | 8 ++++---- state-machines/binary/src/binary_basic_instance.rs | 8 ++++---- .../binary/src/binary_extension_instance.rs | 8 ++++---- 8 files changed, 20 insertions(+), 45 deletions(-) diff --git a/cli/src/commands/common.rs b/cli/src/commands/common.rs index 2e8fffbf6..cb5890265 100644 --- a/cli/src/commands/common.rs +++ b/cli/src/commands/common.rs @@ -70,15 +70,6 @@ pub fn cli_fail_if_macos() -> anyhow::Result<()> { } } -/// If the feature "gpu" is enabled, returns an error indicating that the command is not supported. -pub fn cli_fail_if_gpu_mode() -> anyhow::Result<()> { - if cfg!(feature = "gpu") { - Err(anyhow::anyhow!("Command is not supported on GPU mode")) - } else { - Ok(()) - } -} - /// Gets the witness computation library file location. /// Uses the default one if not specified by user. pub fn get_witness_computation_lib(witness_lib: Option<&PathBuf>) -> PathBuf { diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index c95f9d19d..a89f44d02 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -5,7 +5,7 @@ use tracing::info; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_sdk::{ProverClient, ZiskExecuteResult}; -use crate::{commands::cli_fail_if_gpu_mode, ux::print_banner}; +use crate::ux::print_banner; use zisk_common::io::ZiskStdin; #[derive(Parser)] @@ -71,8 +71,6 @@ pub struct ZiskExecute { impl ZiskExecute { pub fn run(&mut self) -> Result<()> { - cli_fail_if_gpu_mode()?; - print_banner(); let stdin = self.create_stdin()?; diff --git a/common/src/planner_helpers.rs b/common/src/planner_helpers.rs index ce089a25b..7d2fe9b92 100644 --- a/common/src/planner_helpers.rs +++ b/common/src/planner_helpers.rs @@ -143,13 +143,13 @@ pub fn plan( pub fn plan_with_frops( counts: &[InstFropsCount], size: u64, -) -> Vec<(CheckPoint, HashMap)> { +) -> Vec<(CheckPoint, HashMap)> { if counts.is_empty() || size == 0 { return vec![]; } let mut checkpoints = Vec::new(); - let mut current_scope: HashMap = HashMap::new(); + let mut current_scope: HashMap = HashMap::new(); let mut remaining_size = size; // Remaining size for the current scope. for (current_chunk, count) in counts.iter().enumerate() { @@ -168,12 +168,7 @@ pub fn plan_with_frops( let force_execute_to_end = has_frops && inst_count == 0; current_scope.insert( ChunkId(current_chunk), - ( - checkpoint_size, - count.frops_count, - force_execute_to_end, - CollectSkipper::new(cumulative_offset), - ), + (checkpoint_size, force_execute_to_end, CollectSkipper::new(cumulative_offset)), ); cumulative_offset += checkpoint_size; diff --git a/sdk/src/utils.rs b/sdk/src/utils.rs index 629e9b1f6..f128284ed 100644 --- a/sdk/src/utils.rs +++ b/sdk/src/utils.rs @@ -79,15 +79,6 @@ pub fn cli_fail_if_macos() -> anyhow::Result<()> { } } -/// If the feature "gpu" is enabled, returns an error indicating that the command is not supported. -pub fn cli_fail_if_gpu_mode() -> anyhow::Result<()> { - if cfg!(feature = "gpu") { - Err(anyhow::anyhow!("Command is not supported on GPU mode")) - } else { - Ok(()) - } -} - /// Gets the witness computation library file location. /// Uses the default one if not specified by user. pub fn get_witness_computation_lib(witness_lib: Option<&PathBuf>) -> PathBuf { diff --git a/state-machines/arith/src/arith_full_instance.rs b/state-machines/arith/src/arith_full_instance.rs index 6e4afa402..9569e3bf6 100644 --- a/state-machines/arith/src/arith_full_instance.rs +++ b/state-machines/arith/src/arith_full_instance.rs @@ -30,7 +30,7 @@ pub struct ArithFullInstance { arith_full_sm: Arc>, /// Collect info for each chunk ID, containing the number of rows and a skipper for collection. - collect_info: HashMap, + collect_info: HashMap, /// The instance context. ictx: InstanceCtx, @@ -63,14 +63,14 @@ impl ArithFullInstance { let meta = ictx.plan.meta.take().expect("Expected metadata in ictx.plan.meta"); let collect_info = *meta - .downcast::>() + .downcast::>() .expect("Failed to downcast ictx.plan.meta to expected type"); Self { arith_full_sm, collect_info, ictx, std } } pub fn build_arith_collector(&self, chunk_id: ChunkId) -> ArithInstanceCollector { - let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; + let (num_ops, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; ArithInstanceCollector::new( num_ops, collect_skipper, @@ -135,7 +135,7 @@ impl Instance for ArithFullInstance { /// # Returns /// An `Option` containing the input collector for the instance. fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { - let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; + let (num_ops, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; Some(Box::new(ArithInstanceCollector::new( num_ops, collect_skipper, diff --git a/state-machines/binary/src/binary_add_instance.rs b/state-machines/binary/src/binary_add_instance.rs index e315744dc..4ce176141 100644 --- a/state-machines/binary/src/binary_add_instance.rs +++ b/state-machines/binary/src/binary_add_instance.rs @@ -24,7 +24,7 @@ pub struct BinaryAddInstance { binary_add_sm: Arc>, /// Collect info for each chunk ID, containing the number of rows and a skipper for collection. - collect_info: HashMap, + collect_info: HashMap, /// Instance context. ictx: InstanceCtx, @@ -58,7 +58,7 @@ impl BinaryAddInstance { let meta = ictx.plan.meta.take().expect("Expected metadata in ictx.plan.meta"); let collect_info = *meta - .downcast::>() + .downcast::>() .expect("Failed to downcast ictx.plan.meta to expected type"); Self { binary_add_sm, collect_info, ictx, std } @@ -71,7 +71,7 @@ impl BinaryAddInstance { "BinaryAddInstance: Unsupported air_id: {:?}", self.ictx.plan.air_id ); - let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; + let (num_ops, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; BinaryAddCollector::new( num_ops as usize, collect_skipper, @@ -142,7 +142,7 @@ impl Instance for BinaryAddInstance { "BinaryAddInstance: Unsupported air_id: {:?}", self.ictx.plan.air_id ); - let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; + let (num_ops, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; Some(Box::new(BinaryAddCollector::new( num_ops as usize, collect_skipper, diff --git a/state-machines/binary/src/binary_basic_instance.rs b/state-machines/binary/src/binary_basic_instance.rs index b8a8404df..1ca42150c 100644 --- a/state-machines/binary/src/binary_basic_instance.rs +++ b/state-machines/binary/src/binary_basic_instance.rs @@ -30,7 +30,7 @@ pub struct BinaryBasicInstance { with_adds: bool, /// Collect info for each chunk ID, containing the number of rows and a skipper for collection. - collect_info: HashMap, + collect_info: HashMap, /// Standard library instance, providing common functionalities. std: Arc>, @@ -61,14 +61,14 @@ impl BinaryBasicInstance { let meta = ictx.plan.meta.take().expect("Expected metadata in ictx.plan.meta"); let (with_adds, collect_info) = *meta - .downcast::<(bool, HashMap)>() + .downcast::<(bool, HashMap)>() .expect("Failed to downcast ictx.plan.meta to expected type"); Self { binary_basic_sm, ictx, with_adds, collect_info, std } } pub fn build_binary_basic_collector(&self, chunk_id: ChunkId) -> BinaryBasicCollector { - let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; + let (num_ops, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; BinaryBasicCollector::new( num_ops as usize, collect_skipper, @@ -134,7 +134,7 @@ impl Instance for BinaryBasicInstance { /// # Returns /// An `Option` containing the input collector for the instance. fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { - let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; + let (num_ops, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; Some(Box::new(BinaryBasicCollector::new( num_ops as usize, collect_skipper, diff --git a/state-machines/binary/src/binary_extension_instance.rs b/state-machines/binary/src/binary_extension_instance.rs index 9858cc296..037d17f91 100644 --- a/state-machines/binary/src/binary_extension_instance.rs +++ b/state-machines/binary/src/binary_extension_instance.rs @@ -25,7 +25,7 @@ pub struct BinaryExtensionInstance { binary_extension_sm: Arc>, /// Collect info for each chunk ID, containing the number of rows and a skipper for collection. - collect_info: HashMap, + collect_info: HashMap, /// Instance context. ictx: InstanceCtx, @@ -60,7 +60,7 @@ impl BinaryExtensionInstance { let meta = ictx.plan.meta.take().expect("Expected metadata in ictx.plan.meta"); let collect_info = *meta - .downcast::>() + .downcast::>() .expect("Failed to downcast ictx.plan.meta to expected type"); Self { binary_extension_sm, collect_info, ictx, std } @@ -77,7 +77,7 @@ impl BinaryExtensionInstance { self.ictx.plan.air_id ); - let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; + let (num_ops, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; BinaryExtensionCollector::new( num_ops as usize, collect_skipper, @@ -143,7 +143,7 @@ impl Instance for BinaryExtensionInstance { /// # Returns /// An `Option` containing the input collector for the instance. fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { - let (num_ops, _, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; + let (num_ops, force_execute_to_end, collect_skipper) = self.collect_info[&chunk_id]; Some(Box::new(BinaryExtensionCollector::new( num_ops as usize, collect_skipper, From 630cbb446ceb1335283f2d2ff940f915ae22001b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20i=20Buxadera?= Date: Mon, 19 Jan 2026 17:23:38 +0100 Subject: [PATCH 280/782] Fix tests --- common/src/planner_helpers.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/common/src/planner_helpers.rs b/common/src/planner_helpers.rs index 7d2fe9b92..4ad65e5e8 100644 --- a/common/src/planner_helpers.rs +++ b/common/src/planner_helpers.rs @@ -308,7 +308,7 @@ mod tests_frops { let size = 10; let expected = vec![( CheckPoint::Multiple(vec![ChunkId(0)]), - [(ChunkId(0), (10, frops, frops > 0, CollectSkipper::new(0)))] + [(ChunkId(0), (10, frops > 0, CollectSkipper::new(0)))] .into_iter() .collect::>(), )]; @@ -333,19 +333,19 @@ mod tests_frops { let expected = vec![ ( CheckPoint::Multiple(vec![ChunkId(0)]), - [(ChunkId(0), (10, frops, false, CollectSkipper::new(0)))] + [(ChunkId(0), (10, false, CollectSkipper::new(0)))] .into_iter() .collect::>(), ), ( CheckPoint::Multiple(vec![ChunkId(0)]), - [(ChunkId(0), (10, frops, false, CollectSkipper::new(10)))] + [(ChunkId(0), (10, false, CollectSkipper::new(10)))] .into_iter() .collect::>(), ), ( CheckPoint::Multiple(vec![ChunkId(0)]), - [(ChunkId(0), (5, frops, frops > 0, CollectSkipper::new(20)))] + [(ChunkId(0), (5, frops > 0, CollectSkipper::new(20)))] .into_iter() .collect::>(), ), @@ -374,15 +374,15 @@ mod tests_frops { let mut expected = vec![ ( CheckPoint::Multiple(vec![ChunkId(0)]), - [(ChunkId(0), (10, frops[0], false, CollectSkipper::new(0)))] + [(ChunkId(0), (10, false, CollectSkipper::new(0)))] .into_iter() .collect::>(), ), ( CheckPoint::Multiple(vec![ChunkId(0), ChunkId(1)]), [ - (ChunkId(0), (5, frops[0], frops[0] > 0, CollectSkipper::new(10))), - (ChunkId(1), (5, frops[1], frops[1] > 0, CollectSkipper::new(0))), + (ChunkId(0), (5, frops[0] > 0, CollectSkipper::new(10))), + (ChunkId(1), (5, frops[1] > 0, CollectSkipper::new(0))), ] .into_iter() .collect::>(), From accae045f06a45dab28a670e80868549da54bb24 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:56:54 +0100 Subject: [PATCH 281/782] fix to compile for macOS --- emulator-asm/asm-runner/src/lib.rs | 1 + executor/src/emu_asm_stub.rs | 48 ++++++++++++++++++++++++++++++ executor/src/lib.rs | 6 ++++ 3 files changed, 55 insertions(+) create mode 100644 executor/src/emu_asm_stub.rs diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 2ffac998c..48be2bac3 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -52,6 +52,7 @@ pub use shmem_reader::*; pub use shmem_utils::*; pub use shmem_writer::*; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] const SEM_CHUNK_DONE_WAIT_DURATION: std::time::Duration = std::time::Duration::from_secs(10); fn build_name( diff --git a/executor/src/emu_asm_stub.rs b/executor/src/emu_asm_stub.rs new file mode 100644 index 000000000..1f1fe8109 --- /dev/null +++ b/executor/src/emu_asm_stub.rs @@ -0,0 +1,48 @@ +use std::{ + sync::{Arc, Mutex}, + thread::JoinHandle, +}; + +use crate::{DeviceMetricsList, NestedDeviceMetricsList, StaticSMBundle}; +use asm_runner::{AsmRunnerMO, MinimalTraces}; + +use fields::PrimeField64; +use proofman_common::ProofCtx; +use sm_rom::RomSM; +use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, ZiskExecutionResult}; +use zisk_core::ZiskRom; + +pub struct EmulatorAsm {} + +impl EmulatorAsm { + #[allow(clippy::too_many_arguments)] + pub fn new( + _zisk_rom: Arc, + _world_rank: i32, + _local_rank: i32, + _base_port: Option, + _unlock_mapped_memory: bool, + _chunk_size: u64, + _rom_sm: Option>, + ) -> Self { + unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); + } + + #[allow(clippy::type_complexity)] + pub fn execute( + &self, + _stdin: &Mutex, + _pctx: &ProofCtx, + _sm_bundle: &StaticSMBundle, + _stats: &ExecutorStatsHandle, + _caller_stats_id: u64, + ) -> ( + MinimalTraces, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + ZiskExecutionResult, + ) { + unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); + } +} diff --git a/executor/src/lib.rs b/executor/src/lib.rs index a6002b862..cf077defd 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -1,5 +1,8 @@ mod dummy_counter; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod emu_asm; +#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] +mod emu_asm_stub; mod emu_rust; mod executor; mod sm_static_bundle; @@ -7,7 +10,10 @@ mod static_data_bus; mod static_data_bus_collect; pub use dummy_counter::*; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub use emu_asm::*; +#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] +pub use emu_asm_stub::*; pub use emu_rust::*; pub use executor::*; pub use sm_static_bundle::*; From 9fcd05d88df3818f7f89e3d9e76d3f4e6271ae90 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 19 Jan 2026 15:25:21 +0000 Subject: [PATCH 282/782] clean code --- ziskos-hints/src/handlers/bigint256.rs | 58 ++++++---------- ziskos-hints/src/handlers/bls381.rs | 91 +++++++++++--------------- ziskos-hints/src/handlers/bn254.rs | 79 +++++++--------------- ziskos-hints/src/handlers/modexp.rs | 6 +- 4 files changed, 87 insertions(+), 147 deletions(-) diff --git a/ziskos-hints/src/handlers/bigint256.rs b/ziskos-hints/src/handlers/bigint256.rs index 75b040f26..a99d5e315 100644 --- a/ziskos-hints/src/handlers/bigint256.rs +++ b/ziskos-hints/src/handlers/bigint256.rs @@ -12,18 +12,13 @@ pub fn redmod256_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "REDMOD256")?; let mut result: [u64; 4] = [0; 4]; - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); unsafe { - zisklib::redmod256_c( - &data[A_OFFSET], - &data[M_OFFSET], - &mut result[0], - &mut processed_hints, - ); + zisklib::redmod256_c(&data[A_OFFSET], &data[M_OFFSET], &mut result[0], &mut hints); } - Ok(processed_hints) + Ok(hints) } /// Processes an `ADDMOD256` hint. @@ -34,7 +29,7 @@ pub fn addmod256_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "ADDMOD256")?; let mut result: [u64; 4] = [0; 4]; - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); unsafe { zisklib::addmod256_c( @@ -42,11 +37,11 @@ pub fn addmod256_hint(data: &[u64]) -> Result> { &data[B_OFFSET], &data[M_OFFSET], &mut result[0], - &mut processed_hints, + &mut hints, ); } - Ok(processed_hints) + Ok(hints) } /// Processes a `MULMOD256` hint. @@ -57,7 +52,7 @@ pub fn mulmod256_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "MULMOD256")?; let mut result: [u64; 4] = [0; 4]; - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); unsafe { zisklib::mulmod256_c( @@ -65,11 +60,11 @@ pub fn mulmod256_hint(data: &[u64]) -> Result> { &data[B_OFFSET], &data[M_OFFSET], &mut result[0], - &mut processed_hints, + &mut hints, ); } - Ok(processed_hints) + Ok(hints) } /// Processes a `DIVREM256` hint. @@ -79,22 +74,16 @@ pub fn divrem256_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "DIVREM256")?; - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); let mut q: [u64; 4] = [0; 4]; let mut r: [u64; 4] = [0; 4]; unsafe { - zisklib::divrem256_c( - &data[A_OFFSET], - &data[B_OFFSET], - &mut q[0], - &mut r[0], - &mut processed_hints, - ); + zisklib::divrem256_c(&data[A_OFFSET], &data[B_OFFSET], &mut q[0], &mut r[0], &mut hints); } - Ok(processed_hints) + Ok(hints) } /// Processes a `WPOW256` hint. @@ -105,18 +94,13 @@ pub fn wpow256_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "WPOW256")?; let mut result: [u64; 4] = [0; 4]; - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); unsafe { - zisklib::wpow256_c( - &data[A_OFFSET], - &data[EXP_OFFSET], - &mut result[0], - &mut processed_hints, - ); + zisklib::wpow256_c(&data[A_OFFSET], &data[EXP_OFFSET], &mut result[0], &mut hints); } - Ok(processed_hints) + Ok(hints) } /// Processes an `OMUL256` hint. @@ -127,13 +111,13 @@ pub fn omul256_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "OMUL256")?; let mut result: [u64; 4] = [0; 4]; - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); unsafe { - zisklib::omul256_c(&data[A_OFFSET], &data[B_OFFSET], &mut result[0], &mut processed_hints); + zisklib::omul256_c(&data[A_OFFSET], &data[B_OFFSET], &mut result[0], &mut hints); } - Ok(processed_hints) + Ok(hints) } /// Processes a `WMUL256` hint. @@ -144,11 +128,11 @@ pub fn wmul256_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "WMUL256")?; let mut result: [u64; 4] = [0; 4]; - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); unsafe { - zisklib::wmul256_c(&data[A_OFFSET], &data[B_OFFSET], &mut result[0], &mut processed_hints); + zisklib::wmul256_c(&data[A_OFFSET], &data[B_OFFSET], &mut result[0], &mut hints); } - Ok(processed_hints) + Ok(hints) } diff --git a/ziskos-hints/src/handlers/bls381.rs b/ziskos-hints/src/handlers/bls381.rs index a0d5d9c0b..cbaa85ed3 100644 --- a/ziskos-hints/src/handlers/bls381.rs +++ b/ziskos-hints/src/handlers/bls381.rs @@ -13,11 +13,10 @@ pub fn mul_fp12_bls12_381_hint(data: &[u64]) -> Result> { let a: &[u64; A_SIZE] = data[A_OFFSET..A_OFFSET + A_SIZE].try_into().unwrap(); let b: &[u64; B_SIZE] = data[B_OFFSET..B_OFFSET + B_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); + zisklib::mul_fp12_bls12_381(a, b, &mut hints); - zisklib::mul_fp12_bls12_381(a, b, &mut processed_hints); - - Ok(processed_hints) + Ok(hints) } /// Processes a `DECOMPRESS_BLS12_381` hint. @@ -33,11 +32,10 @@ pub fn decompress_bls12_381_hint(data: &[u64]) -> Result> { // Map a [u64; 6] to a [u8; 48] let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; - let mut processed_hints = Vec::new(); - - zisklib::decompress_bls12_381(input, &mut processed_hints).map_err(anyhow::Error::msg)?; + let mut hints = Vec::new(); + zisklib::decompress_bls12_381(input, &mut hints).map_err(anyhow::Error::msg)?; - Ok(processed_hints) + Ok(hints) } /// Processes an `IS_ON_CURVE_BLS12_381` hint. @@ -50,11 +48,10 @@ pub fn is_on_curve_bls12_381_hint(data: &[u64]) -> Result> { // Safe to unwrap due to prior length validation. let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); + zisklib::is_on_curve_bls12_381(p, &mut hints); - zisklib::is_on_curve_bls12_381(p, &mut processed_hints); - - Ok(processed_hints) + Ok(hints) } /// Processes an `IS_ON_SUBGROUP_BLS12_381` hint. @@ -67,11 +64,10 @@ pub fn is_on_subgroup_bls12_381_hint(data: &[u64]) -> Result> { // Safe to unwrap due to prior length validation. let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); - - zisklib::is_on_subgroup_bls12_381(p, &mut processed_hints); + let mut hints = Vec::new(); + zisklib::is_on_subgroup_bls12_381(p, &mut hints); - Ok(processed_hints) + Ok(hints) } /// Processes an `ADD_BLS12_381` hint. @@ -85,11 +81,10 @@ pub fn add_bls12_381_hint(data: &[u64]) -> Result> { let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); + zisklib::add_bls12_381(p1, p2, &mut hints); - zisklib::add_bls12_381(p1, p2, &mut processed_hints); - - Ok(processed_hints) + Ok(hints) } /// Processes a `SCALAR_MUL_BLS12_381` hint. @@ -103,11 +98,10 @@ pub fn scalar_mul_bls12_381_hint(data: &[u64]) -> Result> { let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); - - zisklib::scalar_mul_bls12_381(p, k, &mut processed_hints); + let mut hints = Vec::new(); + zisklib::scalar_mul_bls12_381(p, k, &mut hints); - Ok(processed_hints) + Ok(hints) } /// Processes a `DECOMPRESS_TWIST_BLS12_381` hint. @@ -123,11 +117,10 @@ pub fn decompress_twist_bls12_381_hint(data: &[u64]) -> Result> { // Map a [u64; 6] to a [u8; 48] let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; - let mut processed_hints = Vec::new(); - - zisklib::decompress_twist_bls12_381(input, &mut processed_hints).map_err(anyhow::Error::msg)?; + let mut hints = Vec::new(); + zisklib::decompress_twist_bls12_381(input, &mut hints).map_err(anyhow::Error::msg)?; - Ok(processed_hints) + Ok(hints) } /// Processes an `IS_ON_CURVE_TWIST_BLS12_381` hint. @@ -140,11 +133,10 @@ pub fn is_on_curve_twist_bls12_381_hint(data: &[u64]) -> Result> { // Safe to unwrap due to prior length validation. let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); + zisklib::is_on_curve_twist_bls12_381(p, &mut hints); - zisklib::is_on_curve_twist_bls12_381(p, &mut processed_hints); - - Ok(processed_hints) + Ok(hints) } /// Processes an `IS_ON_SUBGROUP_TWIST_BLS12_381` hint. @@ -157,11 +149,10 @@ pub fn is_on_subgroup_twist_bls12_381_hint(data: &[u64]) -> Result> { // Safe to unwrap due to prior length validation. let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); - - zisklib::is_on_subgroup_twist_bls12_381(p, &mut processed_hints); + let mut hints = Vec::new(); + zisklib::is_on_subgroup_twist_bls12_381(p, &mut hints); - Ok(processed_hints) + Ok(hints) } /// Processes an `ADD_TWIST_BLS12_381` hint. @@ -175,11 +166,10 @@ pub fn add_twist_bls12_381_hint(data: &[u64]) -> Result> { let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); - - zisklib::add_twist_bls12_381(p1, p2, &mut processed_hints); + let mut hints = Vec::new(); + zisklib::add_twist_bls12_381(p1, p2, &mut hints); - Ok(processed_hints) + Ok(hints) } /// Processes a `SCALAR_MUL_TWIST_BLS12_381` hint. @@ -193,11 +183,10 @@ pub fn scalar_mul_twist_bls12_381_hint(data: &[u64]) -> Result> { let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); + zisklib::scalar_mul_twist_bls12_381(p, k, &mut hints); - zisklib::scalar_mul_twist_bls12_381(p, k, &mut processed_hints); - - Ok(processed_hints) + Ok(hints) } /// Processes a `MILLER_LOOP_BLS12_381` hint. @@ -211,11 +200,10 @@ pub fn miller_loop_bls12_381_hint(data: &[u64]) -> Result> { let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); let q: &[u64; Q_SIZE] = data[Q_OFFSET..Q_OFFSET + Q_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); - - zisklib::miller_loop_bls12_381(p, q, &mut processed_hints); + let mut hints = Vec::new(); + zisklib::miller_loop_bls12_381(p, q, &mut hints); - Ok(processed_hints) + Ok(hints) } /// Processes a `FINAL_EXP_BLS12_381` hint. @@ -228,9 +216,8 @@ pub fn final_exp_bls12_381_hint(data: &[u64]) -> Result> { // Safe to unwrap due to prior length validation. let f: &[u64; F_SIZE] = data[F_OFFSET..F_OFFSET + F_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); - - zisklib::final_exp_bls12_381(f, &mut processed_hints); + let mut hints = Vec::new(); + zisklib::final_exp_bls12_381(f, &mut hints); - Ok(processed_hints) + Ok(hints) } diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index a77d34564..7d0cb4bd6 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -9,16 +9,12 @@ pub fn is_on_curve_bn254_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_BN254")?; - // Safe to unwrap due to prior length validation. let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); + zisklib::is_on_curve_bn254(p, &mut hints); - zisklib::is_on_curve_bn254(p, &mut processed_hints); - - //println!("is_on_curve_bn254_hint() processed_hints len={:x}", processed_hints.len()); - - Ok(processed_hints) + Ok(hints) } /// Processes a `TO_AFFINE_BN254` hint. @@ -28,16 +24,12 @@ pub fn to_affine_bn254_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "TO_AFFINE_BN254")?; - // Safe to unwrap due to prior length validation. let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); - - zisklib::to_affine_bn254(p, &mut processed_hints); - - //println!("to_affine_bn254_hint() processed_hints len={:x}", processed_hints.len()); + let mut hints = Vec::new(); + zisklib::to_affine_bn254(p, &mut hints); - Ok(processed_hints) + Ok(hints) } /// Processes an `ADD_BN254` hint. @@ -47,17 +39,13 @@ pub fn add_bn254_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "ADD_BN254")?; - // Safe to unwrap due to prior length validation. let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); + zisklib::add_bn254(p1, p2, &mut hints); - zisklib::add_bn254(p1, p2, &mut processed_hints); - - //println!("add_bn254_hint() processed_hints len={:x}", processed_hints.len()); - - Ok(processed_hints) + Ok(hints) } /// Processes a `MUL_BN254` hint. @@ -67,17 +55,13 @@ pub fn mul_bn254_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "MUL_BN254")?; - // Safe to unwrap due to prior length validation. let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); - - zisklib::mul_bn254(p, k, &mut processed_hints); - - //println!("mul_bn254_hint() processed_hints len={:x}", processed_hints.len()); + let mut hints = Vec::new(); + zisklib::mul_bn254(p, k, &mut hints); - Ok(processed_hints) + Ok(hints) } /// Processes a `TO_AFFINE_TWIST_BN254` hint. @@ -87,16 +71,12 @@ pub fn to_affine_twist_bn254_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "TO_AFFINE_TWIST_BN254")?; - // Safe to unwrap due to prior length validation. let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); + zisklib::to_affine_twist_bn254(p, &mut hints); - zisklib::to_affine_twist_bn254(p, &mut processed_hints); - - //println!("to_affine_twist_bn254_hint() processed_hints len={:x}", processed_hints.len()); - - Ok(processed_hints) + Ok(hints) } /// Processes an `IS_ON_CURVE_TWIST_BN254` hint. @@ -106,16 +86,12 @@ pub fn is_on_curve_twist_bn254_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_TWIST_BN254")?; - // Safe to unwrap due to prior length validation. let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); - - zisklib::is_on_curve_twist_bn254(p, &mut processed_hints); - - //println!("is_on_curve_twist_bn254_hint() processed_hints len={:x}", processed_hints.len()); + let mut hints = Vec::new(); + zisklib::is_on_curve_twist_bn254(p, &mut hints); - Ok(processed_hints) + Ok(hints) } /// Processes an `IS_ON_SUBGROUP_TWIST_BN254` hint. @@ -125,16 +101,12 @@ pub fn is_on_subgroup_twist_bn254_hint(data: &[u64]) -> Result> { validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_TWIST_BN254")?; - // Safe to unwrap due to prior length validation. let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let mut processed_hints = Vec::new(); + let mut hints = Vec::new(); + zisklib::is_on_subgroup_twist_bn254(p, &mut hints); - zisklib::is_on_subgroup_twist_bn254(p, &mut processed_hints); - - //println!("is_on_subgroup_twist_bn254_hint() processed_hints len={:x}", processed_hints.len()); - - Ok(processed_hints) + Ok(hints) } /// Processes a `PAIRING_BATCH_BN254` hint. @@ -181,11 +153,8 @@ pub fn pairing_batch_bn254_hint(data: &[u64]) -> Result> { ) }; - let mut processed_hints = Vec::new(); - - zisklib::pairing_batch_bn254(g1_points, g2_points, &mut processed_hints); - - //println!("pairing_batch_bn254_hint() processed_hints len={:x}", processed_hints.len()); + let mut hints = Vec::new(); + zisklib::pairing_batch_bn254(g1_points, g2_points, &mut hints); - Ok(processed_hints) + Ok(hints) } diff --git a/ziskos-hints/src/handlers/modexp.rs b/ziskos-hints/src/handlers/modexp.rs index 502ade297..bf16eaac5 100644 --- a/ziskos-hints/src/handlers/modexp.rs +++ b/ziskos-hints/src/handlers/modexp.rs @@ -15,8 +15,8 @@ pub fn modexp_hint(data: &[u64]) -> Result> { validate_hint_length(data, pos, "MODEXP")?; - let mut processed_hints = Vec::new(); - zisklib::modexp_u64(base, exp, modulus, &mut processed_hints); + let mut hints = Vec::new(); + zisklib::modexp_u64(base, exp, modulus, &mut hints); - Ok(processed_hints) + Ok(hints) } From b2873ee00f1b6dd3f775d2474fcf2be8d930281d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 20 Jan 2026 07:32:20 +0000 Subject: [PATCH 283/782] twist bytes in hint decompress functions --- ziskos-hints/src/handlers/bls381.rs | 24 ++++++++++++++++++++++++ ziskos-hints/src/handlers/secp256k1.rs | 5 +++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/ziskos-hints/src/handlers/bls381.rs b/ziskos-hints/src/handlers/bls381.rs index cbaa85ed3..bb556728b 100644 --- a/ziskos-hints/src/handlers/bls381.rs +++ b/ziskos-hints/src/handlers/bls381.rs @@ -29,6 +29,15 @@ pub fn decompress_bls12_381_hint(data: &[u64]) -> Result> { // Safe to unwrap due to prior length validation. let input: &[u64; INPUT_SIZE] = data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); + let input: [u64; INPUT_SIZE] = [ + input[3].to_be(), + input[2].to_be(), + input[1].to_be(), + input[0].to_be(), + input[5].to_be(), + input[4].to_be(), + ]; + // Map a [u64; 6] to a [u8; 48] let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; @@ -114,6 +123,21 @@ pub fn decompress_twist_bls12_381_hint(data: &[u64]) -> Result> { // Safe to unwrap due to prior length validation. let input: &[u64; INPUT_SIZE] = data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); + let input: [u64; INPUT_SIZE] = [ + input[3].to_be(), + input[2].to_be(), + input[1].to_be(), + input[0].to_be(), + input[7].to_be(), + input[6].to_be(), + input[5].to_be(), + input[4].to_be(), + input[11].to_be(), + input[10].to_be(), + input[9].to_be(), + input[8].to_be(), + ]; + // Map a [u64; 6] to a [u8; 48] let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 456ffef0c..4d4830aa0 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -199,10 +199,11 @@ pub fn secp256k1_decompress_hint(data: &[u64]) -> Result> { let x: &[u64; X_BYTES_SIZE] = data[X_BYTES_OFFSET..X_BYTES_OFFSET + X_BYTES_SIZE].try_into().unwrap(); - let y_is_odd = (data[Y_IS_ODD_OFFSET] >> 56) != 0; + let x: [u64; X_BYTES_SIZE] = [x[3].to_be(), x[2].to_be(), x[1].to_be(), x[0].to_be()]; + let y_is_odd = data[Y_IS_ODD_OFFSET] != 0; let mut hints = Vec::new(); - zisklib::secp256k1_decompress(x, y_is_odd, &mut hints).map_err(anyhow::Error::msg)?; + zisklib::secp256k1_decompress(&x, y_is_odd, &mut hints).map_err(anyhow::Error::msg)?; Ok(hints) } From 2afb51ba4b382a5d752b9d957632bb37ae78c883 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 20 Jan 2026 07:53:40 +0000 Subject: [PATCH 284/782] normalize hint car/fn names --- common/src/hints.rs | 220 +++++++++++------------ precompiles/hints/src/hints_processor.rs | 42 ++--- ziskos-hints/src/handlers/bls381.rs | 26 +-- ziskos-hints/src/handlers/bn254.rs | 16 +- 4 files changed, 152 insertions(+), 152 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 9393cd7a0..b652a0feb 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -91,29 +91,29 @@ const HINT_WMUL256: u32 = 0x0C; const HINT_MODEXP: u32 = 0x0D; // BN254 hint codes -const HINT_IS_ON_CURVE_BN254: u32 = 0x0E; -const HINT_TO_AFFINE_BN254: u32 = 0x0F; -const HINT_ADD_BN254: u32 = 0x10; -const HINT_MUL_BN254: u32 = 0x11; -const HINT_TO_AFFINE_TWIST_BN254: u32 = 0x12; -const HINT_IS_ON_CURVE_TWIST_BN254: u32 = 0x13; -const HINT_IS_ON_SUBGROUP_TWIST_BN254: u32 = 0x14; -const HINT_PAIRING_BATCH_BN254: u32 = 0x15; +const HINT_BN254_IS_ON_CURVE: u32 = 0x0E; +const HINT_BN254_TO_AFFINE: u32 = 0x0F; +const HINT_BN254_ADD: u32 = 0x10; +const HINT_BN254_MUL: u32 = 0x11; +const HINT_BN254_TO_AFFINE_TWIST: u32 = 0x12; +const HINT_BN254_IS_ON_CURVE_TWIST: u32 = 0x13; +const HINT_BN254_IS_ON_SUBGROUP_TWIST: u32 = 0x14; +const HINT_BN254_PAIRING_BATCH: u32 = 0x15; // BLS12-381 hint codes -const HINT_MUL_FP12_BLS12_381: u32 = 0x16; -const HINT_DECOMPRESS_BLS12_381: u32 = 0x17; -const HINT_IS_ON_CURVE_BLS12_381: u32 = 0x18; -const HINT_IS_ON_SUBGROUP_BLS12_381: u32 = 0x19; -const HINT_ADD_BLS12_381: u32 = 0x1A; -const HINT_SCALAR_MUL_BLS12_381: u32 = 0x1B; -const HINT_DECOMPRESS_TWIST_BLS12_381: u32 = 0x1C; -const HINT_IS_ON_CURVE_TWIST_BLS12_381: u32 = 0x1D; -const HINT_IS_ON_SUBGROUP_TWIST_BLS12_381: u32 = 0x1E; -const HINT_ADD_TWIST_BLS12_381: u32 = 0x1F; -const HINT_SCALAR_MUL_TWIST_BLS12_381: u32 = 0x20; -const HINT_MILLER_LOOP_BLS12_381: u32 = 0x21; -const HINT_FINAL_EXP_BLS12_381: u32 = 0x22; +const HINT_BLS12_381_MUL_FP12: u32 = 0x16; +const HINT_BLS12_381_DECOMPRESS: u32 = 0x17; +const HINT_BLS12_381_IS_ON_CURVE: u32 = 0x18; +const HINT_BLS12_381_IS_ON_SUBGROUP: u32 = 0x19; +const HINT_BLS12_381_ADD: u32 = 0x1A; +const HINT_BLS12_381_SCALAR_MUL: u32 = 0x1B; +const HINT_BLS12_381_DECOMPRESS_TWIST: u32 = 0x1C; +const HINT_BLS12_381_IS_ON_CURVE_TWIST: u32 = 0x1D; +const HINT_BLS12_381_IS_ON_SUBGROUP_TWIST: u32 = 0x1E; +const HINT_BLS12_381_ADD_TWIST: u32 = 0x1F; +const HINT_BLS12_381_SCALAR_MUL_TWIST: u32 = 0x20; +const HINT_BLS12_381_MILLER_LOOP: u32 = 0x21; +const HINT_BLS12_381_FINAL_EXP: u32 = 0x22; /// Control code variants for stream control. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -217,49 +217,49 @@ pub enum BuiltInHint { // BN254 Precompile Hints /// Check if point is on curve hint type for BN254 curve. - IsOnCurveBn254 = HINT_IS_ON_CURVE_BN254, + Bn254IsOnCurve = HINT_BN254_IS_ON_CURVE, /// Convert to affine coordinates hint type for BN254 curve. - ToAffineBn254 = HINT_TO_AFFINE_BN254, + Bn254ToAffine = HINT_BN254_TO_AFFINE, /// Point addition hint type for BN254 curve. - AddBn254 = HINT_ADD_BN254, + Bn254Add = HINT_BN254_ADD, /// Scalar multiplication hint type for BN254 curve. - MulBn254 = HINT_MUL_BN254, + Bn254Mul = HINT_BN254_MUL, /// Convert to affine coordinates hint type for BN254 twist. - ToAffineTwistBn254 = HINT_TO_AFFINE_TWIST_BN254, + Bn254ToAffineTwist = HINT_BN254_TO_AFFINE_TWIST, /// Check if point is on curve hint type for BN254 twist. - IsOnCurveTwistBn254 = HINT_IS_ON_CURVE_TWIST_BN254, + Bn254IsOnCurveTwist = HINT_BN254_IS_ON_CURVE_TWIST, /// Check if point is in subgroup hint type for BN254 twist. - IsOnSubgroupTwistBn254 = HINT_IS_ON_SUBGROUP_TWIST_BN254, + Bn254IsOnSubgroupTwist = HINT_BN254_IS_ON_SUBGROUP_TWIST, /// Pairing batch computation hint type for BN254 curve. - PairingBatchBn254 = HINT_PAIRING_BATCH_BN254, + Bn254PairingBatch = HINT_BN254_PAIRING_BATCH, // BLS12-381 Precompile Hints /// Multiplication in Fp12 hint type for BLS12-381 curve. - MulFp12Bls12_381 = HINT_MUL_FP12_BLS12_381, + Bls12_381MulFp12 = HINT_BLS12_381_MUL_FP12, /// Point decompression hint type for BLS12-381 curve. - DecompressBls12_381 = HINT_DECOMPRESS_BLS12_381, + Bls12_381Decompress = HINT_BLS12_381_DECOMPRESS, /// Check if point is on curve hint type for BLS12-381 curve. - IsOnCurveBls12_381 = HINT_IS_ON_CURVE_BLS12_381, + Bls12_381IsOnCurve = HINT_BLS12_381_IS_ON_CURVE, /// Check if point is in subgroup hint type for BLS12-381 curve. - IsOnSubgroupBls12_381 = HINT_IS_ON_SUBGROUP_BLS12_381, + Bls12_381IsOnSubgroup = HINT_BLS12_381_IS_ON_SUBGROUP, /// Point addition hint type for BLS12-381 curve. - AddBls12_381 = HINT_ADD_BLS12_381, + Bls12_381Add = HINT_BLS12_381_ADD, /// Scalar multiplication hint type for BLS12-381 curve. - ScalarMulBls12_381 = HINT_SCALAR_MUL_BLS12_381, + Bls12_381ScalarMul = HINT_BLS12_381_SCALAR_MUL, /// Point decompression hint type for BLS12-381 twist. - DecompressTwistBls12_381 = HINT_DECOMPRESS_TWIST_BLS12_381, + Bls12_381DecompressTwist = HINT_BLS12_381_DECOMPRESS_TWIST, /// Check if point is on curve hint type for BLS12-381 twist. - IsOnCurveTwistBls12_381 = HINT_IS_ON_CURVE_TWIST_BLS12_381, + Bls12_381IsOnCurveTwist = HINT_BLS12_381_IS_ON_CURVE_TWIST, /// Check if point is in subgroup hint type for BLS12-381 twist. - IsOnSubgroupTwistBls12_381 = HINT_IS_ON_SUBGROUP_TWIST_BLS12_381, + Bls12_381IsOnSubgroupTwist = HINT_BLS12_381_IS_ON_SUBGROUP_TWIST, /// Point addition hint type for BLS12-381 twist. - AddTwistBls12_381 = HINT_ADD_TWIST_BLS12_381, + Bls12_381AddTwist = HINT_BLS12_381_ADD_TWIST, /// Scalar multiplication hint type for BLS12-381 twist. - ScalarMulTwistBls12_381 = HINT_SCALAR_MUL_TWIST_BLS12_381, + Bls12_381ScalarMulTwist = HINT_BLS12_381_SCALAR_MUL_TWIST, /// Miller loop computation hint type for BLS12-381 curve. - MillerLoopBls12_381 = HINT_MILLER_LOOP_BLS12_381, + Bls12_381MillerLoop = HINT_BLS12_381_MILLER_LOOP, /// Final exponentiation computation hint type for BLS12-381 curve. - FinalExpBls12_381 = HINT_FINAL_EXP_BLS12_381, + Bls12_381FinalExp = HINT_BLS12_381_FINAL_EXP, } impl Display for BuiltInHint { @@ -300,29 +300,29 @@ impl Display for BuiltInHint { BuiltInHint::ModExp => "MODEXP", // BN254 Hints - BuiltInHint::IsOnCurveBn254 => "IS_ON_CURVE_BN254", - BuiltInHint::ToAffineBn254 => "TO_AFFINE_BN254", - BuiltInHint::AddBn254 => "ADD_BN254", - BuiltInHint::MulBn254 => "MUL_BN254", - BuiltInHint::ToAffineTwistBn254 => "TO_AFFINE_TWIST_BN254", - BuiltInHint::IsOnCurveTwistBn254 => "IS_ON_CURVE_TWIST_BN254", - BuiltInHint::IsOnSubgroupTwistBn254 => "IS_ON_SUBGROUP_TWIST_BN254", - BuiltInHint::PairingBatchBn254 => "PAIRING_BATCH_BN254", + BuiltInHint::Bn254IsOnCurve => "BN254_IS_ON_CURVE", + BuiltInHint::Bn254ToAffine => "BN254_TO_AFFINE", + BuiltInHint::Bn254Add => "BN254_ADD", + BuiltInHint::Bn254Mul => "BN254_MUL", + BuiltInHint::Bn254ToAffineTwist => "BN254_TO_AFFINE_TWIST", + BuiltInHint::Bn254IsOnCurveTwist => "BN254_IS_ON_CURVE_TWIST", + BuiltInHint::Bn254IsOnSubgroupTwist => "BN254_IS_ON_SUBGROUP_TWIST", + BuiltInHint::Bn254PairingBatch => "BN254_PAIRING_BATCH", // BLS12-381 Hints - BuiltInHint::MulFp12Bls12_381 => "MUL_FP12_BLS12_381", - BuiltInHint::DecompressBls12_381 => "DECOMPRESS_BLS12_381", - BuiltInHint::IsOnCurveBls12_381 => "IS_ON_CURVE_BLS12_381", - BuiltInHint::IsOnSubgroupBls12_381 => "IS_ON_SUBGROUP_BLS12_381", - BuiltInHint::AddBls12_381 => "ADD_BLS12_381", - BuiltInHint::ScalarMulBls12_381 => "SCALAR_MUL_BLS12_381", - BuiltInHint::DecompressTwistBls12_381 => "DECOMPRESS_TWIST_BLS12_381", - BuiltInHint::IsOnCurveTwistBls12_381 => "IS_ON_CURVE_TWIST_BLS12_381", - BuiltInHint::IsOnSubgroupTwistBls12_381 => "IS_ON_SUBGROUP_TWIST_BLS12_381", - BuiltInHint::AddTwistBls12_381 => "ADD_TWIST_BLS12_381", - BuiltInHint::ScalarMulTwistBls12_381 => "SCALAR_MUL_TWIST_BLS12_381", - BuiltInHint::MillerLoopBls12_381 => "MILLER_LOOP_BLS12_381", - BuiltInHint::FinalExpBls12_381 => "FINAL_EXP_BLS12_381", + BuiltInHint::Bls12_381MulFp12 => "BLS12_381_MUL_FP12", + BuiltInHint::Bls12_381Decompress => "BLS12_381_DECOMPRESS", + BuiltInHint::Bls12_381IsOnCurve => "BLS12_381_IS_ON_CURVE", + BuiltInHint::Bls12_381IsOnSubgroup => "BLS12_381_IS_ON_SUBGROUP", + BuiltInHint::Bls12_381Add => "BLS12_381_ADD", + BuiltInHint::Bls12_381ScalarMul => "BLS12_381_SCALAR_MUL", + BuiltInHint::Bls12_381DecompressTwist => "BLS12_381_DECOMPRESS_TWIST", + BuiltInHint::Bls12_381IsOnCurveTwist => "BLS12_381_IS_ON_CURVE_TWIST", + BuiltInHint::Bls12_381IsOnSubgroupTwist => "BLS12_381_IS_ON_SUBGROUP_TWIST", + BuiltInHint::Bls12_381AddTwist => "BLS12_381_ADD_TWIST", + BuiltInHint::Bls12_381ScalarMulTwist => "BLS12_381_SCALAR_MUL_TWIST", + BuiltInHint::Bls12_381MillerLoop => "BLS12_381_MILLER_LOOP", + BuiltInHint::Bls12_381FinalExp => "BLS12_381_FINAL_EXP", }; write!(f, "{} ({:#x})", name, *self as u32) } @@ -368,29 +368,29 @@ impl TryFrom for BuiltInHint { HINT_MODEXP => Ok(Self::ModExp), // BN254 Hints - HINT_IS_ON_CURVE_BN254 => Ok(Self::IsOnCurveBn254), - HINT_TO_AFFINE_BN254 => Ok(Self::ToAffineBn254), - HINT_ADD_BN254 => Ok(Self::AddBn254), - HINT_MUL_BN254 => Ok(Self::MulBn254), - HINT_TO_AFFINE_TWIST_BN254 => Ok(Self::ToAffineTwistBn254), - HINT_IS_ON_CURVE_TWIST_BN254 => Ok(Self::IsOnCurveTwistBn254), - HINT_IS_ON_SUBGROUP_TWIST_BN254 => Ok(Self::IsOnSubgroupTwistBn254), - HINT_PAIRING_BATCH_BN254 => Ok(Self::PairingBatchBn254), + HINT_BN254_IS_ON_CURVE => Ok(Self::Bn254IsOnCurve), + HINT_BN254_TO_AFFINE => Ok(Self::Bn254ToAffine), + HINT_BN254_ADD => Ok(Self::Bn254Add), + HINT_BN254_MUL => Ok(Self::Bn254Mul), + HINT_BN254_TO_AFFINE_TWIST => Ok(Self::Bn254ToAffineTwist), + HINT_BN254_IS_ON_CURVE_TWIST => Ok(Self::Bn254IsOnCurveTwist), + HINT_BN254_IS_ON_SUBGROUP_TWIST => Ok(Self::Bn254IsOnSubgroupTwist), + HINT_BN254_PAIRING_BATCH => Ok(Self::Bn254PairingBatch), // BLS12-381 Hints - HINT_MUL_FP12_BLS12_381 => Ok(Self::MulFp12Bls12_381), - HINT_DECOMPRESS_BLS12_381 => Ok(Self::DecompressBls12_381), - HINT_IS_ON_CURVE_BLS12_381 => Ok(Self::IsOnCurveBls12_381), - HINT_IS_ON_SUBGROUP_BLS12_381 => Ok(Self::IsOnSubgroupBls12_381), - HINT_ADD_BLS12_381 => Ok(Self::AddBls12_381), - HINT_SCALAR_MUL_BLS12_381 => Ok(Self::ScalarMulBls12_381), - HINT_DECOMPRESS_TWIST_BLS12_381 => Ok(Self::DecompressTwistBls12_381), - HINT_IS_ON_CURVE_TWIST_BLS12_381 => Ok(Self::IsOnCurveTwistBls12_381), - HINT_IS_ON_SUBGROUP_TWIST_BLS12_381 => Ok(Self::IsOnSubgroupTwistBls12_381), - HINT_ADD_TWIST_BLS12_381 => Ok(Self::AddTwistBls12_381), - HINT_SCALAR_MUL_TWIST_BLS12_381 => Ok(Self::ScalarMulTwistBls12_381), - HINT_MILLER_LOOP_BLS12_381 => Ok(Self::MillerLoopBls12_381), - HINT_FINAL_EXP_BLS12_381 => Ok(Self::FinalExpBls12_381), + HINT_BLS12_381_MUL_FP12 => Ok(Self::Bls12_381MulFp12), + HINT_BLS12_381_DECOMPRESS => Ok(Self::Bls12_381Decompress), + HINT_BLS12_381_IS_ON_CURVE => Ok(Self::Bls12_381IsOnCurve), + HINT_BLS12_381_IS_ON_SUBGROUP => Ok(Self::Bls12_381IsOnSubgroup), + HINT_BLS12_381_ADD => Ok(Self::Bls12_381Add), + HINT_BLS12_381_SCALAR_MUL => Ok(Self::Bls12_381ScalarMul), + HINT_BLS12_381_DECOMPRESS_TWIST => Ok(Self::Bls12_381DecompressTwist), + HINT_BLS12_381_IS_ON_CURVE_TWIST => Ok(Self::Bls12_381IsOnCurveTwist), + HINT_BLS12_381_IS_ON_SUBGROUP_TWIST => Ok(Self::Bls12_381IsOnSubgroupTwist), + HINT_BLS12_381_ADD_TWIST => Ok(Self::Bls12_381AddTwist), + HINT_BLS12_381_SCALAR_MUL_TWIST => Ok(Self::Bls12_381ScalarMulTwist), + HINT_BLS12_381_MILLER_LOOP => Ok(Self::Bls12_381MillerLoop), + HINT_BLS12_381_FINAL_EXP => Ok(Self::Bls12_381FinalExp), _ => Err(anyhow::anyhow!("Invalid built-in hint code: {:#x}", value)), } @@ -485,39 +485,39 @@ impl HintCode { HintCode::BuiltIn(BuiltInHint::ModExp) => HINT_MODEXP, // BN254 Hints - HintCode::BuiltIn(BuiltInHint::IsOnCurveBn254) => HINT_IS_ON_CURVE_BN254, - HintCode::BuiltIn(BuiltInHint::ToAffineBn254) => HINT_TO_AFFINE_BN254, - HintCode::BuiltIn(BuiltInHint::AddBn254) => HINT_ADD_BN254, - HintCode::BuiltIn(BuiltInHint::MulBn254) => HINT_MUL_BN254, - HintCode::BuiltIn(BuiltInHint::ToAffineTwistBn254) => HINT_TO_AFFINE_TWIST_BN254, - HintCode::BuiltIn(BuiltInHint::IsOnCurveTwistBn254) => HINT_IS_ON_CURVE_TWIST_BN254, - HintCode::BuiltIn(BuiltInHint::IsOnSubgroupTwistBn254) => { - HINT_IS_ON_SUBGROUP_TWIST_BN254 + HintCode::BuiltIn(BuiltInHint::Bn254IsOnCurve) => HINT_BN254_IS_ON_CURVE, + HintCode::BuiltIn(BuiltInHint::Bn254ToAffine) => HINT_BN254_TO_AFFINE, + HintCode::BuiltIn(BuiltInHint::Bn254Add) => HINT_BN254_ADD, + HintCode::BuiltIn(BuiltInHint::Bn254Mul) => HINT_BN254_MUL, + HintCode::BuiltIn(BuiltInHint::Bn254ToAffineTwist) => HINT_BN254_TO_AFFINE_TWIST, + HintCode::BuiltIn(BuiltInHint::Bn254IsOnCurveTwist) => HINT_BN254_IS_ON_CURVE_TWIST, + HintCode::BuiltIn(BuiltInHint::Bn254IsOnSubgroupTwist) => { + HINT_BN254_IS_ON_SUBGROUP_TWIST } - HintCode::BuiltIn(BuiltInHint::PairingBatchBn254) => HINT_PAIRING_BATCH_BN254, + HintCode::BuiltIn(BuiltInHint::Bn254PairingBatch) => HINT_BN254_PAIRING_BATCH, // BLS12-381 Hints - HintCode::BuiltIn(BuiltInHint::MulFp12Bls12_381) => HINT_MUL_FP12_BLS12_381, - HintCode::BuiltIn(BuiltInHint::DecompressBls12_381) => HINT_DECOMPRESS_BLS12_381, - HintCode::BuiltIn(BuiltInHint::IsOnCurveBls12_381) => HINT_IS_ON_CURVE_BLS12_381, - HintCode::BuiltIn(BuiltInHint::IsOnSubgroupBls12_381) => HINT_IS_ON_SUBGROUP_BLS12_381, - HintCode::BuiltIn(BuiltInHint::AddBls12_381) => HINT_ADD_BLS12_381, - HintCode::BuiltIn(BuiltInHint::ScalarMulBls12_381) => HINT_SCALAR_MUL_BLS12_381, - HintCode::BuiltIn(BuiltInHint::DecompressTwistBls12_381) => { - HINT_DECOMPRESS_TWIST_BLS12_381 + HintCode::BuiltIn(BuiltInHint::Bls12_381MulFp12) => HINT_BLS12_381_MUL_FP12, + HintCode::BuiltIn(BuiltInHint::Bls12_381Decompress) => HINT_BLS12_381_DECOMPRESS, + HintCode::BuiltIn(BuiltInHint::Bls12_381IsOnCurve) => HINT_BLS12_381_IS_ON_CURVE, + HintCode::BuiltIn(BuiltInHint::Bls12_381IsOnSubgroup) => HINT_BLS12_381_IS_ON_SUBGROUP, + HintCode::BuiltIn(BuiltInHint::Bls12_381Add) => HINT_BLS12_381_ADD, + HintCode::BuiltIn(BuiltInHint::Bls12_381ScalarMul) => HINT_BLS12_381_SCALAR_MUL, + HintCode::BuiltIn(BuiltInHint::Bls12_381DecompressTwist) => { + HINT_BLS12_381_DECOMPRESS_TWIST } - HintCode::BuiltIn(BuiltInHint::IsOnCurveTwistBls12_381) => { - HINT_IS_ON_CURVE_TWIST_BLS12_381 + HintCode::BuiltIn(BuiltInHint::Bls12_381IsOnCurveTwist) => { + HINT_BLS12_381_IS_ON_CURVE_TWIST } - HintCode::BuiltIn(BuiltInHint::IsOnSubgroupTwistBls12_381) => { - HINT_IS_ON_SUBGROUP_TWIST_BLS12_381 + HintCode::BuiltIn(BuiltInHint::Bls12_381IsOnSubgroupTwist) => { + HINT_BLS12_381_IS_ON_SUBGROUP_TWIST } - HintCode::BuiltIn(BuiltInHint::AddTwistBls12_381) => HINT_ADD_TWIST_BLS12_381, - HintCode::BuiltIn(BuiltInHint::ScalarMulTwistBls12_381) => { - HINT_SCALAR_MUL_TWIST_BLS12_381 + HintCode::BuiltIn(BuiltInHint::Bls12_381AddTwist) => HINT_BLS12_381_ADD_TWIST, + HintCode::BuiltIn(BuiltInHint::Bls12_381ScalarMulTwist) => { + HINT_BLS12_381_SCALAR_MUL_TWIST } - HintCode::BuiltIn(BuiltInHint::MillerLoopBls12_381) => HINT_MILLER_LOOP_BLS12_381, - HintCode::BuiltIn(BuiltInHint::FinalExpBls12_381) => HINT_FINAL_EXP_BLS12_381, + HintCode::BuiltIn(BuiltInHint::Bls12_381MillerLoop) => HINT_BLS12_381_MILLER_LOOP, + HintCode::BuiltIn(BuiltInHint::Bls12_381FinalExp) => HINT_BLS12_381_FINAL_EXP, // Custom Hints HintCode::Custom(code) => code, diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 81174677d..3b4538027 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -606,29 +606,29 @@ impl HintsProcessor { BuiltInHint::ModExp => modexp_hint(&data), // BN254 hints - BuiltInHint::IsOnCurveBn254 => is_on_curve_bn254_hint(&data), - BuiltInHint::ToAffineBn254 => to_affine_bn254_hint(&data), - BuiltInHint::AddBn254 => add_bn254_hint(&data), - BuiltInHint::MulBn254 => mul_bn254_hint(&data), - BuiltInHint::ToAffineTwistBn254 => to_affine_twist_bn254_hint(&data), - BuiltInHint::IsOnCurveTwistBn254 => is_on_curve_twist_bn254_hint(&data), - BuiltInHint::IsOnSubgroupTwistBn254 => is_on_subgroup_twist_bn254_hint(&data), - BuiltInHint::PairingBatchBn254 => pairing_batch_bn254_hint(&data), + BuiltInHint::Bn254IsOnCurve => bn254_is_on_curve_hint(&data), + BuiltInHint::Bn254ToAffine => bn254_to_affine_hint(&data), + BuiltInHint::Bn254Add => bn254_add_hint(&data), + BuiltInHint::Bn254Mul => bn254_mul_hint(&data), + BuiltInHint::Bn254ToAffineTwist => bn254_to_affine_twist_hint(&data), + BuiltInHint::Bn254IsOnCurveTwist => bn254_is_on_curve_twist_hint(&data), + BuiltInHint::Bn254IsOnSubgroupTwist => bn254_is_on_subgroup_twist_hint(&data), + BuiltInHint::Bn254PairingBatch => bn254_pairing_batch_hint(&data), // BLS12-381 hints - BuiltInHint::MulFp12Bls12_381 => mul_fp12_bls12_381_hint(&data), - BuiltInHint::DecompressBls12_381 => decompress_bls12_381_hint(&data), - BuiltInHint::IsOnCurveBls12_381 => is_on_curve_bls12_381_hint(&data), - BuiltInHint::IsOnSubgroupBls12_381 => is_on_subgroup_bls12_381_hint(&data), - BuiltInHint::AddBls12_381 => add_bls12_381_hint(&data), - BuiltInHint::ScalarMulBls12_381 => scalar_mul_bls12_381_hint(&data), - BuiltInHint::DecompressTwistBls12_381 => decompress_twist_bls12_381_hint(&data), - BuiltInHint::IsOnCurveTwistBls12_381 => is_on_curve_twist_bls12_381_hint(&data), - BuiltInHint::IsOnSubgroupTwistBls12_381 => is_on_subgroup_twist_bls12_381_hint(&data), - BuiltInHint::AddTwistBls12_381 => add_twist_bls12_381_hint(&data), - BuiltInHint::ScalarMulTwistBls12_381 => scalar_mul_twist_bls12_381_hint(&data), - BuiltInHint::MillerLoopBls12_381 => miller_loop_bls12_381_hint(&data), - BuiltInHint::FinalExpBls12_381 => final_exp_bls12_381_hint(&data), + BuiltInHint::Bls12_381MulFp12 => bls12_381_mul_fp12_hint(&data), + BuiltInHint::Bls12_381Decompress => bls12_381_decompress_hint(&data), + BuiltInHint::Bls12_381IsOnCurve => bls12_381_is_on_curve_hint(&data), + BuiltInHint::Bls12_381IsOnSubgroup => bls12_381_is_on_subgroup_hint(&data), + BuiltInHint::Bls12_381Add => bls12_381_add_hint(&data), + BuiltInHint::Bls12_381ScalarMul => bls12_381_scalar_mul_hint(&data), + BuiltInHint::Bls12_381DecompressTwist => bls12_381_decompress_twist_hint(&data), + BuiltInHint::Bls12_381IsOnCurveTwist => bls12_381_is_on_curve_twist_hint(&data), + BuiltInHint::Bls12_381IsOnSubgroupTwist => bls12_381_is_on_subgroup_twist_hint(&data), + BuiltInHint::Bls12_381AddTwist => bls12_381_add_twist_hint(&data), + BuiltInHint::Bls12_381ScalarMulTwist => bls12_381_scalar_mul_twist_hint(&data), + BuiltInHint::Bls12_381MillerLoop => bls12_381_miller_loop_hint(&data), + BuiltInHint::Bls12_381FinalExp => bls12_381_final_exp_hint(&data), // Control codes and Noop are handled before dispatch _ => Err(anyhow::anyhow!("Unexpected builtin hint: {:?}", hint)), diff --git a/ziskos-hints/src/handlers/bls381.rs b/ziskos-hints/src/handlers/bls381.rs index bb556728b..0a565fbc8 100644 --- a/ziskos-hints/src/handlers/bls381.rs +++ b/ziskos-hints/src/handlers/bls381.rs @@ -4,7 +4,7 @@ use anyhow::Result; /// Processes an `MUL_FP12_BLS12_381` hint. #[inline] -pub fn mul_fp12_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_mul_fp12_hint(data: &[u64]) -> Result> { hint_fields![A: 72, B: 72]; validate_hint_length(data, EXPECTED_LEN, "MUL_FP12_BLS12_381")?; @@ -21,7 +21,7 @@ pub fn mul_fp12_bls12_381_hint(data: &[u64]) -> Result> { /// Processes a `DECOMPRESS_BLS12_381` hint. #[inline] -pub fn decompress_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_decompress_hint(data: &[u64]) -> Result> { hint_fields![INPUT: 6]; validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_BLS12_381")?; @@ -49,7 +49,7 @@ pub fn decompress_bls12_381_hint(data: &[u64]) -> Result> { /// Processes an `IS_ON_CURVE_BLS12_381` hint. #[inline] -pub fn is_on_curve_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_is_on_curve_hint(data: &[u64]) -> Result> { hint_fields![P: 12]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_BLS12_381")?; @@ -65,7 +65,7 @@ pub fn is_on_curve_bls12_381_hint(data: &[u64]) -> Result> { /// Processes an `IS_ON_SUBGROUP_BLS12_381` hint. #[inline] -pub fn is_on_subgroup_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_is_on_subgroup_hint(data: &[u64]) -> Result> { hint_fields![P: 12]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_BLS12_381")?; @@ -81,7 +81,7 @@ pub fn is_on_subgroup_bls12_381_hint(data: &[u64]) -> Result> { /// Processes an `ADD_BLS12_381` hint. #[inline] -pub fn add_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_add_hint(data: &[u64]) -> Result> { hint_fields![P1: 12, P2: 12]; validate_hint_length(data, EXPECTED_LEN, "ADD_BLS12_381")?; @@ -98,7 +98,7 @@ pub fn add_bls12_381_hint(data: &[u64]) -> Result> { /// Processes a `SCALAR_MUL_BLS12_381` hint. #[inline] -pub fn scalar_mul_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_scalar_mul_hint(data: &[u64]) -> Result> { hint_fields![P: 12, K: 6]; validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_BLS12_381")?; @@ -115,7 +115,7 @@ pub fn scalar_mul_bls12_381_hint(data: &[u64]) -> Result> { /// Processes a `DECOMPRESS_TWIST_BLS12_381` hint. #[inline] -pub fn decompress_twist_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_decompress_twist_hint(data: &[u64]) -> Result> { hint_fields![INPUT: 12]; validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_TWIST_BLS12_381")?; @@ -149,7 +149,7 @@ pub fn decompress_twist_bls12_381_hint(data: &[u64]) -> Result> { /// Processes an `IS_ON_CURVE_TWIST_BLS12_381` hint. #[inline] -pub fn is_on_curve_twist_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_is_on_curve_twist_hint(data: &[u64]) -> Result> { hint_fields![P: 24]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_TWIST_BLS12_381")?; @@ -165,7 +165,7 @@ pub fn is_on_curve_twist_bls12_381_hint(data: &[u64]) -> Result> { /// Processes an `IS_ON_SUBGROUP_TWIST_BLS12_381` hint. #[inline] -pub fn is_on_subgroup_twist_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_is_on_subgroup_twist_hint(data: &[u64]) -> Result> { hint_fields![P: 24]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_TWIST_BLS12_381")?; @@ -181,7 +181,7 @@ pub fn is_on_subgroup_twist_bls12_381_hint(data: &[u64]) -> Result> { /// Processes an `ADD_TWIST_BLS12_381` hint. #[inline] -pub fn add_twist_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_add_twist_hint(data: &[u64]) -> Result> { hint_fields![P1: 24, P2: 24]; validate_hint_length(data, EXPECTED_LEN, "ADD_TWIST_BLS12_381")?; @@ -198,7 +198,7 @@ pub fn add_twist_bls12_381_hint(data: &[u64]) -> Result> { /// Processes a `SCALAR_MUL_TWIST_BLS12_381` hint. #[inline] -pub fn scalar_mul_twist_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_scalar_mul_twist_hint(data: &[u64]) -> Result> { hint_fields![P: 24, K: 6]; validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_TWIST_BLS12_381")?; @@ -215,7 +215,7 @@ pub fn scalar_mul_twist_bls12_381_hint(data: &[u64]) -> Result> { /// Processes a `MILLER_LOOP_BLS12_381` hint. #[inline] -pub fn miller_loop_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_miller_loop_hint(data: &[u64]) -> Result> { hint_fields![P: 12, Q: 24]; validate_hint_length(data, EXPECTED_LEN, "MILLER_LOOP_BLS12_381")?; @@ -232,7 +232,7 @@ pub fn miller_loop_bls12_381_hint(data: &[u64]) -> Result> { /// Processes a `FINAL_EXP_BLS12_381` hint. #[inline] -pub fn final_exp_bls12_381_hint(data: &[u64]) -> Result> { +pub fn bls12_381_final_exp_hint(data: &[u64]) -> Result> { hint_fields![F: 72]; validate_hint_length(data, EXPECTED_LEN, "FINAL_EXP_BLS12_381")?; diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 7d0cb4bd6..104d35ca1 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -4,7 +4,7 @@ use anyhow::Result; /// Processes an `IS_ON_CURVE_BN254`` hint. #[inline] -pub fn is_on_curve_bn254_hint(data: &[u64]) -> Result> { +pub fn bn254_is_on_curve_hint(data: &[u64]) -> Result> { hint_fields![P: 8]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_BN254")?; @@ -19,7 +19,7 @@ pub fn is_on_curve_bn254_hint(data: &[u64]) -> Result> { /// Processes a `TO_AFFINE_BN254` hint. #[inline] -pub fn to_affine_bn254_hint(data: &[u64]) -> Result> { +pub fn bn254_to_affine_hint(data: &[u64]) -> Result> { hint_fields![P: 12]; validate_hint_length(data, EXPECTED_LEN, "TO_AFFINE_BN254")?; @@ -34,7 +34,7 @@ pub fn to_affine_bn254_hint(data: &[u64]) -> Result> { /// Processes an `ADD_BN254` hint. #[inline] -pub fn add_bn254_hint(data: &[u64]) -> Result> { +pub fn bn254_add_hint(data: &[u64]) -> Result> { hint_fields![P1: 8, P2: 8]; validate_hint_length(data, EXPECTED_LEN, "ADD_BN254")?; @@ -50,7 +50,7 @@ pub fn add_bn254_hint(data: &[u64]) -> Result> { /// Processes a `MUL_BN254` hint. #[inline] -pub fn mul_bn254_hint(data: &[u64]) -> Result> { +pub fn bn254_mul_hint(data: &[u64]) -> Result> { hint_fields![P: 8, K: 4]; validate_hint_length(data, EXPECTED_LEN, "MUL_BN254")?; @@ -66,7 +66,7 @@ pub fn mul_bn254_hint(data: &[u64]) -> Result> { /// Processes a `TO_AFFINE_TWIST_BN254` hint. #[inline] -pub fn to_affine_twist_bn254_hint(data: &[u64]) -> Result> { +pub fn bn254_to_affine_twist_hint(data: &[u64]) -> Result> { hint_fields![P: 24]; validate_hint_length(data, EXPECTED_LEN, "TO_AFFINE_TWIST_BN254")?; @@ -81,7 +81,7 @@ pub fn to_affine_twist_bn254_hint(data: &[u64]) -> Result> { /// Processes an `IS_ON_CURVE_TWIST_BN254` hint. #[inline] -pub fn is_on_curve_twist_bn254_hint(data: &[u64]) -> Result> { +pub fn bn254_is_on_curve_twist_hint(data: &[u64]) -> Result> { hint_fields![P: 16]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_TWIST_BN254")?; @@ -96,7 +96,7 @@ pub fn is_on_curve_twist_bn254_hint(data: &[u64]) -> Result> { /// Processes an `IS_ON_SUBGROUP_TWIST_BN254` hint. #[inline] -pub fn is_on_subgroup_twist_bn254_hint(data: &[u64]) -> Result> { +pub fn bn254_is_on_subgroup_twist_hint(data: &[u64]) -> Result> { hint_fields![P: 16]; validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_TWIST_BN254")?; @@ -113,7 +113,7 @@ pub fn is_on_subgroup_twist_bn254_hint(data: &[u64]) -> Result> { /// Format: [num_points:u64][g1_points:&[u64]][g2_points:&[u64]] /// where g1_points has length num_points * 8 and g2_points has length num_points * 16 #[inline] -pub fn pairing_batch_bn254_hint(data: &[u64]) -> Result> { +pub fn bn254_pairing_batch_hint(data: &[u64]) -> Result> { if data.is_empty() { anyhow::bail!("PAIRING_BATCH_BN254: data is empty"); } From 9de2a56aecdc8b9a6578e59bc3ffa25fffb123ce Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 20 Jan 2026 10:27:59 +0100 Subject: [PATCH 285/782] Fix fcall_msb_pos_256_3() hints length --- ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs index 8bb788246..2cf69da19 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/msb_pos_256.rs @@ -52,7 +52,7 @@ pub fn fcall_msb_pos_256_3( let (i, pos) = msb_pos_256(&tmp, 3); #[cfg(feature = "hints")] { - hints.push(3); + hints.push(2); hints.push(i as u64); hints.push(pos as u64); } From 5a7ebc7b2d843ae606ba5f1aa9f0d11cf46a7c67 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:41:33 +0000 Subject: [PATCH 286/782] fix bls hint handler --- ziskos-hints/src/handlers/bls381.rs | 44 +++++++++---------- .../src/zisklib/lib/bls12_381/curve.rs | 2 +- .../src/zisklib/lib/bls12_381/twist.rs | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/ziskos-hints/src/handlers/bls381.rs b/ziskos-hints/src/handlers/bls381.rs index 0a565fbc8..742a37b58 100644 --- a/ziskos-hints/src/handlers/bls381.rs +++ b/ziskos-hints/src/handlers/bls381.rs @@ -29,14 +29,14 @@ pub fn bls12_381_decompress_hint(data: &[u64]) -> Result> { // Safe to unwrap due to prior length validation. let input: &[u64; INPUT_SIZE] = data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); - let input: [u64; INPUT_SIZE] = [ - input[3].to_be(), - input[2].to_be(), - input[1].to_be(), - input[0].to_be(), - input[5].to_be(), - input[4].to_be(), - ]; + // let input: [u64; INPUT_SIZE] = [ + // input[3].to_be(), + // input[2].to_be(), + // input[1].to_be(), + // input[0].to_be(), + // input[5].to_be(), + // input[4].to_be(), + // ]; // Map a [u64; 6] to a [u8; 48] let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; @@ -123,20 +123,20 @@ pub fn bls12_381_decompress_twist_hint(data: &[u64]) -> Result> { // Safe to unwrap due to prior length validation. let input: &[u64; INPUT_SIZE] = data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); - let input: [u64; INPUT_SIZE] = [ - input[3].to_be(), - input[2].to_be(), - input[1].to_be(), - input[0].to_be(), - input[7].to_be(), - input[6].to_be(), - input[5].to_be(), - input[4].to_be(), - input[11].to_be(), - input[10].to_be(), - input[9].to_be(), - input[8].to_be(), - ]; + // let input: [u64; INPUT_SIZE] = [ + // input[3].to_be(), + // input[2].to_be(), + // input[1].to_be(), + // input[0].to_be(), + // input[7].to_be(), + // input[6].to_be(), + // input[5].to_be(), + // input[4].to_be(), + // input[11].to_be(), + // input[10].to_be(), + // input[9].to_be(), + // input[8].to_be(), + // ]; // Map a [u64; 6] to a [u8; 48] let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs index 667d6e28d..46cbc2084 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs @@ -30,7 +30,7 @@ pub fn decompress_bls12_381( // Check compression bit if (flags & 0x80) == 0 { - return Err("Expected compressed point (0x80 flag not set)"); + return Err("decompress_bls12_381: Expected compressed point (0x80 flag not set)"); } // Check infinity bit diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs index 6011e8fed..9dfe29794 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs @@ -28,7 +28,7 @@ pub fn decompress_twist_bls12_381( // Check compression bit if (flags & 0x80) == 0 { - return Err("Expected compressed point (0x80 flag not set)"); + return Err("decompress_twist_bls12_381: Expected compressed point (0x80 flag not set)"); } // Check infinity bit From f8e3909daea2ff3669b612a032b92b9a4c50aa0e Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 20 Jan 2026 16:19:46 +0100 Subject: [PATCH 287/782] Update Cargo.lock --- Cargo.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27035b91b..6d42a318a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1040,7 +1040,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "fields", "num-bigint", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "cfg-if", "num-bigint", @@ -2704,7 +2704,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "colored", "fields", @@ -3077,7 +3077,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "blake3", "borsh", @@ -3112,7 +3112,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "borsh", "colored", @@ -3143,7 +3143,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "fields", "itoa", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "proc-macro2", "quote", @@ -3167,7 +3167,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "crossbeam-channel", "tracing", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "colored", "fields", @@ -3187,7 +3187,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "bytemuck", "fields", @@ -5413,7 +5413,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ee4523532b65b7c9471550e089da4256f2276fc0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "colored", "fields", @@ -5849,9 +5849,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" [[package]] name = "zstd" From db63170cf112b85754494ff2814aa2bb91634ab4 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 20 Jan 2026 18:41:34 +0100 Subject: [PATCH 288/782] Fix MsbPos256() in lib-c --- lib-c/c/src/fcall/fcall.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib-c/c/src/fcall/fcall.cpp b/lib-c/c/src/fcall/fcall.cpp index d7fd97110..7410334e3 100644 --- a/lib-c/c/src/fcall/fcall.cpp +++ b/lib-c/c/src/fcall/fcall.cpp @@ -307,19 +307,28 @@ int MsbPos256 ( uint64_t * r // 2 x 64 bits ) { - const uint64_t * x = a; - const uint64_t * y = &a[4]; + const uint64_t n = a[0]; // number of inputs + const uint64_t * params = &a[1]; - for (int i=3; i>=0; i--) + for (int limb=3; limb>=0; limb--) { - if ((x[i] != 0) || (y[i] != 0)) + // Find max value at this limb position across all inputs + uint64_t max_word = 0; + for (uint64_t i=0; i y[i] ? x[i] : y[i]; - r[0] = i; - r[1] = msb_pos(word); + uint64_t word = params[i * 4 + limb]; + if (word > max_word) { + max_word = word; + } + } + if (max_word != 0) + { + r[0] = limb; + r[1] = msb_pos(max_word); return 0; } } + printf("MsbPos256() error: both x and y are zero\n"); exit(-1); } From 5c25280da9a6de86a560ce664ef7fd06c66d3f58 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 20 Jan 2026 19:11:53 +0100 Subject: [PATCH 289/782] Fix trace in _opcode_secp256k1_add() --- emulator-asm/src/emu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index 368eb4b1d..efe53262e 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -788,7 +788,8 @@ extern int _opcode_secp256k1_add(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p3 = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); + printf("p3.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); + printf("p3.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); } #endif #ifdef ASM_CALL_METRICS From cf0688108b913e3d2e995d11159e422f390eaada Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 21 Jan 2026 04:09:27 +0100 Subject: [PATCH 290/782] assembly updated to support memcpy --- .gitignore | 2 + Cargo.lock | 12 +- Cargo.toml | 32 +- core/src/inst_context.rs | 84 -- core/src/mem.rs | 2 + core/src/riscv2zisk_context.rs | 19 +- core/src/zisk_inst.rs | 2 - core/src/zisk_inst_builder.rs | 3 +- core/src/zisk_ops.rs | 75 +- core/src/zisk_rom_2_asm.rs | 589 +++++------ emulator-asm/Makefile | 12 +- emulator-asm/asm-runner/src/asm_mo_runner.rs | 1 - emulator-asm/src/dma/Makefile | 33 + emulator-asm/src/dma/direct_memcpy_mops.asm | 330 ++++++ emulator-asm/src/dma/direct_memcpy_mtrace.asm | 251 +++++ emulator-asm/src/dma/dma_constants.inc | 59 ++ emulator-asm/src/dma/fast_dma_encode.asm | 74 ++ .../src/dma/fast_dma_encode_table.asm | 264 +++++ emulator-asm/src/dma/memcpy_fast.asm | 97 ++ emulator-asm/src/dma/test_dma.cpp | 978 ++++++++++++++++++ emulator/src/emu.rs | 8 +- pil/src/pil_helpers/traces.rs | 2 +- precompiles/dma/pil/dma.pil | 7 +- precompiles/dma/src/dma/dma.rs | 6 +- .../dma/src/dma_64_aligned/dma_64_aligned.rs | 6 +- precompiles/dma/src/dma_gen_mem_inputs.rs | 15 +- .../dma/src/dma_pre_post/dma_pre_post.rs | 6 +- .../dma/src/dma_unaligned/dma_unaligned.rs | 7 +- precompiles/helpers/src/dma.rs | 237 ++++- .../mem-cpp/cpp/mem_align_counter.cpp | 68 +- state-machines/mem-cpp/cpp/mem_config.hpp | 38 +- state-machines/mem-cpp/cpp/mem_counter.cpp | 151 ++- state-machines/mem-cpp/cpp/tools.hpp | 2 +- 33 files changed, 2836 insertions(+), 636 deletions(-) create mode 100644 emulator-asm/src/dma/Makefile create mode 100644 emulator-asm/src/dma/direct_memcpy_mops.asm create mode 100644 emulator-asm/src/dma/direct_memcpy_mtrace.asm create mode 100644 emulator-asm/src/dma/dma_constants.inc create mode 100644 emulator-asm/src/dma/fast_dma_encode.asm create mode 100644 emulator-asm/src/dma/fast_dma_encode_table.asm create mode 100644 emulator-asm/src/dma/memcpy_fast.asm create mode 100644 emulator-asm/src/dma/test_dma.cpp diff --git a/.gitignore b/.gitignore index 6739405b2..992799b46 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ /tmp *.log /emulator-asm/build* +/emulator-asm/src/dma/*.o +/emulator-asm/src/dma/test_dma /emulator-asm/src/emu.asm /cache /lib-c/c/build diff --git a/Cargo.lock b/Cargo.lock index 12daf28a1..f010b26f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1040,7 +1040,6 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "fields", "num-bigint", @@ -1384,7 +1383,6 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "num-bigint", "paste", @@ -2702,7 +2700,6 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "colored", "fields", @@ -2712,6 +2709,7 @@ dependencies = [ "proofman-hints", "proofman-util", "rayon", + "rustc-hash 2.1.1", "tracing", "witness", ] @@ -3074,7 +3072,6 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "blake3", "borsh", @@ -3109,7 +3106,6 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "borsh", "colored", @@ -3140,7 +3136,6 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "fields", "proofman-common", @@ -3152,7 +3147,6 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "proc-macro2", "quote", @@ -3163,7 +3157,6 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "crossbeam-channel", "tracing", @@ -3172,7 +3165,6 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "colored", "fields", @@ -3183,7 +3175,6 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "bytemuck", "fields", @@ -5434,7 +5425,6 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#33f8260fdb92090280e6f09bffdef21cb38128de" dependencies = [ "colored", "fields", diff --git a/Cargo.toml b/Cargo.toml index befd52162..e3aa50bf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,23 +105,23 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } # Proofman Local development -# proofman = { path = "../pil2-proofman/proofman" } -# proofman-common = { path = "../pil2-proofman/common" } -# proofman-macros = { path = "../pil2-proofman/macros" } -# proofman-verifier = { path = "../pil2-proofman/verifier" } -# proofman-util = { path = "../pil2-proofman/util" } -# pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } -# witness = { path = "../pil2-proofman/witness" } -# fields = { path = "../pil2-proofman/fields" } +proofman = { path = "../pil2-proofman/proofman" } +proofman-common = { path = "../pil2-proofman/common" } +proofman-macros = { path = "../pil2-proofman/macros" } +proofman-verifier = { path = "../pil2-proofman/verifier" } +proofman-util = { path = "../pil2-proofman/util" } +pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } +witness = { path = "../pil2-proofman/witness" } +fields = { path = "../pil2-proofman/fields" } # External dependencies rayon = "1.10" diff --git a/core/src/inst_context.rs b/core/src/inst_context.rs index ef0a7aa89..381c23d54 100644 --- a/core/src/inst_context.rs +++ b/core/src/inst_context.rs @@ -8,8 +8,6 @@ use crate::{ Mem, FCALL_PARAMS_MAX_SIZE, FCALL_RESULT_MAX_SIZE, REGS_IN_MAIN_TOTAL_NUMBER, ROM_ENTRY, }; -const PARAMS_MAX_SIZE: usize = 4; - /// Zisk precompiled emulation mode #[derive(Debug, Default, PartialEq, Eq)] pub enum EmulationMode { @@ -73,84 +71,6 @@ impl Default for FcallInstContext { } } } - -/// Zisk param instruction context, these instructions are used to pass extra parameters to -/// precompiles. Currently precompiles can receive up to 2 parameters directly in instruction call, -/// but if this precompile needs more parameters use these instructions to pass them. It's important -/// to note that these parameters must be called in the instructions immediately before, because when -/// precompiles prove them they use step - 1, step - 2 and so on. -/// -/// Stores the precompile arguments. -#[derive(Debug)] -pub struct ParamInstContext { - /// Maximum size is PARAMS_MAX_SIZE u64s - pub parameters: [u64; PARAMS_MAX_SIZE], - - /// Indicates how many parameter u64s contain valid data - pub parameters_size: usize, - - /// Indicates the max step for these parameters - pub step_limit: u64, -} - -impl Default for ParamInstContext { - /// Default param instruction context constructor - fn default() -> Self { - Self { parameters: [0; PARAMS_MAX_SIZE], parameters_size: 0, step_limit: 0 } - } -} - -impl ParamInstContext { - /// Adds a single param. - pub fn add_param(&mut self, value: u64, step: u64) { - if step > self.step_limit { - self.step_limit = step + PARAMS_MAX_SIZE as u64 + 1; - self.parameters_size = 0; - } - if self.parameters_size >= PARAMS_MAX_SIZE { - panic!( - "ERROR: no space for one more parameter ({}/{} step_limit:{})", - self.parameters_size, PARAMS_MAX_SIZE, self.step_limit - ); - } - self.parameters[self.parameters_size] = value; - self.parameters_size += 1; - } - /// Adds multiple params (double normally). - pub fn add_params(&mut self, values: &[u64], step: u64) { - if step > self.step_limit { - self.step_limit = PARAMS_MAX_SIZE as u64 + 1; - self.parameters_size = 0; - } - if self.parameters_size + values.len() > PARAMS_MAX_SIZE { - panic!( - "ERROR: no space for {} more parameters ({}/{} step_limit:{})", - values.len(), - self.parameters_size, - PARAMS_MAX_SIZE, - self.step_limit - ); - } - for value in values { - self.parameters[self.parameters_size] = *value; - self.parameters_size += 1; - } - } - /// Clears params. - pub fn clear(&mut self) { - self.step_limit = 0; - self.parameters_size = 0; - } - /// Gets a param by index. - pub fn get_param(&self, index: usize) -> Option { - if index < self.parameters_size { - Some(self.parameters[index]) - } else { - None - } - } -} - #[derive(Debug)] /// ZisK instruction context data container, storing the state of the execution pub struct InstContext { @@ -197,9 +117,6 @@ pub struct InstContext { /// Fcall data pub fcall: FcallInstContext, - /// Params data - pub params: ParamInstContext, - /// DataExt 64 bytes size. With this information it is possible to specify which variable part of the minimal trace /// is associated with the current instruction. Used by DMA precompile. pub data_ext_len: usize, @@ -224,7 +141,6 @@ impl InstContext { emulation_mode: EmulationMode::default(), precompiled: PrecompiledInstContext::default(), fcall: FcallInstContext::default(), - params: ParamInstContext::default(), data_ext_len: 0, } } diff --git a/core/src/mem.rs b/core/src/mem.rs index 02016640a..ed91c39f5 100644 --- a/core/src/mem.rs +++ b/core/src/mem.rs @@ -141,6 +141,8 @@ pub const FLOAT_LIB_SP: u64 = 0xc0000000 - 16; // 0xbffffff0 pub const ARCH_ID_ZISK: u64 = 0xFFFEEEE; /// UART memory address; single bytes written here will be copied to the standard output pub const UART_ADDR: u64 = SYS_ADDR + 0x200; +/// Extra parameters of repcompiles are stored in fixed memory area (256 bytes => 32 parameters) +pub const EXTRA_PARAMS: u64 = SYS_ADDR + 0x0F00; /// Float registers first address pub const FREG_FIRST: u64 = SYS_ADDR + 0x1000; /// CSR memory address; contains control and status registers diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 240f74a58..4ca9c57c3 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -6,8 +6,8 @@ use riscv::{riscv_interpreter, RiscvInstruction}; use crate::{ convert_vector, ZiskInstBuilder, ZiskRom, ARCH_ID_CSR_ADDR, ARCH_ID_ZISK, CSR_ADDR, - FLOAT_LIB_ROM_ADDR, FLOAT_LIB_SP, FREG_F0, FREG_INST, FREG_RA, FREG_X0, INPUT_ADDR, MTVEC, - OUTPUT_ADDR, REG_X0, ROM_ENTRY, ROM_EXIT, + EXTRA_PARAMS, FLOAT_LIB_ROM_ADDR, FLOAT_LIB_SP, FREG_F0, FREG_INST, FREG_RA, FREG_X0, + INPUT_ADDR, MTVEC, OUTPUT_ADDR, REG_X0, ROM_ENTRY, ROM_EXIT, }; use std::collections::HashMap; @@ -86,10 +86,8 @@ impl Riscv2ZiskContext<'_> { } else if riscv_instruction.rd == 10 && self.input_precompile == Some(0x813) { self.create_register_op(riscv_instruction, "dma_memcmp", 4); } else if riscv_instruction.rs1 == 0 { - println!("Detected instruction pattern"); if !next_instructions.is_empty() { // rd = rs1(0) + rs2 = rs2 followed by ret - println!("Detected instruction pattern '{}'", next_instructions[0].inst); self.copyb(riscv_instruction, 4, 2); } else { // rd = rs1(0) + rs2 = rs2 @@ -1176,16 +1174,11 @@ impl Riscv2ZiskContext<'_> { zib.j(4, 4); if (CSR_PRECOMPILED_ADDR_START..=CSR_PRECOMPILED_ADDR_END).contains(&i.csr) { match i.csr { - 0x812 => { - self.output_precompile = Some(0x812); + 0x812 | 0x813 => { + self.output_precompile = Some(i.csr); zib.src_a("imm", 0, false); - zib.op("param").unwrap(); - zib.verbose("param"); - } - 0x813 => { - self.output_precompile = Some(0x813); - zib.src_a("imm", 0, false); - zib.op("param").unwrap(); + zib.op("copyb").unwrap(); + zib.store("mem", EXTRA_PARAMS as i64, false, false); zib.verbose("param"); } _ => { diff --git a/core/src/zisk_inst.rs b/core/src/zisk_inst.rs index a4e2831ef..6087b3d9b 100644 --- a/core/src/zisk_inst.rs +++ b/core/src/zisk_inst.rs @@ -83,7 +83,6 @@ pub enum ZiskOperationType { ArithEq384, BigInt, // Note: Add new core operations here Dma, // Note: To add extra params to precompiles calls - Param, // ZisK Free Input Operations FcallParam, Fcall, @@ -104,7 +103,6 @@ pub const BIG_INT_OP_TYPE_ID: u32 = ZiskOperationType::BigInt as u32; pub const FCALL_PARAM_OP_TYPE_ID: u32 = ZiskOperationType::FcallParam as u32; pub const FCALL_OP_TYPE_ID: u32 = ZiskOperationType::Fcall as u32; pub const DMA_OP_TYPE_ID: u32 = ZiskOperationType::Dma as u32; -pub const PARAM_OP_TYPE_ID: u32 = ZiskOperationType::Param as u32; /// ZisK instruction definition /// diff --git a/core/src/zisk_inst_builder.rs b/core/src/zisk_inst_builder.rs index 32356d4cc..615e5dc69 100644 --- a/core/src/zisk_inst_builder.rs +++ b/core/src/zisk_inst_builder.rs @@ -208,8 +208,7 @@ impl ZiskInstBuilder { self.i.op_type = op.op_type().into(); self.i.input_size = op.input_size(); // assume that input_size > 0 implies a precompiled, and precompiled uses step on operations - self.i.op_with_step = - op.input_size() > 0 || [ZiskOp::PARAM, ZiskOp::PARAMS].contains(&op.code()); + self.i.op_with_step = op.input_size() > 0; Ok(()) } diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 36d23ab32..e2d665767 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -22,8 +22,8 @@ use std::{ use tiny_keccak::keccakf; use crate::{ - sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, M64, - REG_A0, SYS_ADDR, + sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, + EXTRA_PARAMS, M64, REG_A0, SYS_ADDR, }; use lib_c::{inverse_fn_ec_c, inverse_fp_ec_c, sqrt_fp_ec_parity_c, Fcall, FcallContext}; @@ -54,7 +54,6 @@ pub enum OpType { ArithEq384, BigInt, Dma, - Param, } impl From for ZiskOperationType { @@ -72,7 +71,6 @@ impl From for ZiskOperationType { OpType::ArithEq384 => ZiskOperationType::ArithEq384, OpType::BigInt => ZiskOperationType::BigInt, OpType::Dma => ZiskOperationType::Dma, - OpType::Param => ZiskOperationType::Param, } } } @@ -94,7 +92,6 @@ impl Display for OpType { Self::ArithEq384 => write!(f, "Arith384"), Self::BigInt => write!(f, "BigInt"), Self::Dma => write!(f, "Dma"), - Self::Param => write!(f, "Param"), } } } @@ -117,7 +114,6 @@ impl FromStr for OpType { "aeq384" => Ok(Self::ArithEq384), "bint" => Ok(Self::BigInt), "dma" => Ok(Self::Dma), - "param" => Ok(Self::Param), _ => Err(InvalidOpTypeError), } } @@ -428,10 +424,7 @@ define_ops! { (RemuW, "remu_w", ArithA32, ARITHA32_COST, 0xbd, 0, 0, opc_remu_w, op_remu_w, ops_none), (DivW, "div_w", ArithA32, ARITHA32_COST, 0xbe, 0, 0, opc_div_w, op_div_w, ops_none), (RemW, "rem_w", ArithA32, ARITHA32_COST, 0xbf, 0, 0, opc_rem_w, op_rem_w, ops_none), - (Param, "param", Param, 0, 0xc0, 0, 0, opc_param, op_param, ops_param), - (Params, "params", Param, 0, 0xc1, 0, 0, opc_params, op_params, ops_params), - // opcpdes 0xc2,0xc3 futured reserved for params - // opcodes 0xc4-0xcf are available + // opcpdes 0xc0-0xcf are available (DmaMemCpy, "dma_memcpy", Dma, DMA_COST, 0xd0, 8, 0, opc_dma_memcpy, op_dma_memcpy, ops_dma_memcpy), (DmaMemCmp, "dma_memcmp", Dma, DMA_COST, 0xd1, 16, 0, opc_dma_memcmp, op_dma_memcmp, ops_dma_memcmp), // opcodes 0xd2-0xd9 future reserved for dma operations (memset, memcpy256, memcmp256) @@ -2331,13 +2324,13 @@ pub fn opc_dma_memcpy(ctx: &mut InstContext) { match ctx.emulation_mode { EmulationMode::Mem => { - let count = ctx.params.get_param(0).unwrap(); + let count = ctx.mem.read(EXTRA_PARAMS, 8); ctx.mem.memcpy(dst, src, count); } EmulationMode::GenerateMemReads => { // In generate mode we need to populate precompiled.input_data with // information needed - let count = ctx.params.get_param(0).unwrap(); + let count = ctx.mem.read(EXTRA_PARAMS, 8); ctx.precompiled.input_data.clear(); #[cfg(feature = "debug_dma")] @@ -2346,7 +2339,7 @@ pub fn opc_dma_memcpy(ctx: &mut InstContext) { ctx.emulation_mode, ctx.step ); - let encoded = DmaInfo::encode_memcpy(dst, src, count as usize); + let encoded = DmaInfo::fast_encode_memcpy(dst, src, count as usize); ctx.precompiled.input_data.push(encoded); if count > 0 { @@ -2422,7 +2415,7 @@ pub fn op_dma_memcpy(_a: u64, _b: u64) -> (u64, bool) { pub fn ops_dma_memcpy(ctx: &InstContext, stats: &mut dyn OpStats) { let dst = ctx.a; let src = ctx.b; - let count = ctx.params.get_param(0).unwrap(); + let count = ctx.mem.read(EXTRA_PARAMS, 8); // pre, post, dma_align, dma_unalign if count == 0 { @@ -2481,7 +2474,7 @@ pub fn ops_dma_memcpy(ctx: &InstContext, stats: &mut dyn OpStats) { pub fn opc_dma_memcmp(ctx: &mut InstContext) { let addr_a = ctx.a; let addr_b = ctx.b; - let count = ctx.params.get_param(0).unwrap(); + let count = ctx.mem.read(EXTRA_PARAMS, 8); println!("opc_dma_memcmp 0x{addr_a:08X} 0x{addr_b:08X} {count} {:?}", ctx.emulation_mode); @@ -2524,7 +2517,7 @@ pub fn op_dma_memcmp(_a: u64, _b: u64) -> (u64, bool) { pub fn ops_dma_memcmp(ctx: &InstContext, stats: &mut dyn OpStats) { let addr_a = ctx.a; let addr_b = ctx.b; - let count = ctx.params.get_param(0).unwrap(); + let count = ctx.mem.read(EXTRA_PARAMS, 8); // pre, post, dma_align, dma_unalign if count == 0 { @@ -2592,53 +2585,3 @@ pub fn ops_dma_memcmp(ctx: &InstContext, stats: &mut dyn OpStats) { } } } - -pub fn opc_param(ctx: &mut InstContext) { - match ctx.emulation_mode { - EmulationMode::GenerateMemReads => { - let b = ctx.b; - // println!("opc_param 0x{b:08X}({b}) STEP:{} PC:0x{:08X}", ctx.step, ctx.pc); - ctx.params.add_param(b, ctx.step); - } - _ => { - // in other modes the length was read from minimal trace, - // to avoid move params info between chunks - } - } - ctx.c = 0; - ctx.flag = false; -} - -/// Unimplemented. Param can only be called from the system call context via InstContext. -/// This is provided just for completeness. -#[inline(always)] -pub fn op_param(_a: u64, _b: u64) -> (u64, bool) { - unimplemented!("op_param() is not implemented"); -} - -#[inline(always)] -pub fn ops_param(ctx: &InstContext, stats: &mut dyn OpStats) { - let param = ctx.b; - - println!("ops_param 0x{param:08X} {:?}", ctx.emulation_mode); -} - -pub fn opc_params(ctx: &mut InstContext) { - let a = ctx.a; - let b = ctx.b; - - // println!("opc_params 0x{a:016X}({a}),0x{b:016X}({b}) {:?}", ctx.emulation_mode); - ctx.params.add_params(&[a, b], ctx.step); - ctx.c = 0; - ctx.flag = false; -} - -/// Unimplemented. Param can only be called from the system call context via InstContext. -/// This is provided just for completeness. -#[inline(always)] -pub fn op_params(_a: u64, _b: u64) -> (u64, bool) { - unimplemented!("op_params() is not implemented"); -} - -#[inline(always)] -pub fn ops_params(ctx: &InstContext, stats: &mut dyn OpStats) {} diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 34f60c00f..67b075d76 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -4,9 +4,9 @@ use std::path::Path; use crate::{ - zisk_ops::ZiskOp, AsmGenerationMethod, ZiskInst, ZiskRom, FLOAT_LIB_ROM_ADDR, FREE_INPUT_ADDR, - M64, P2_32, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, SRC_C, SRC_IMM, SRC_IND, SRC_MEM, SRC_REG, - SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, + zisk_ops::ZiskOp, AsmGenerationMethod, ZiskInst, ZiskRom, EXTRA_PARAMS, FLOAT_LIB_ROM_ADDR, + FREE_INPUT_ADDR, M64, P2_32, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, SRC_C, SRC_IMM, SRC_IND, + SRC_MEM, SRC_REG, SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, }; // Regs rax, rcx, rdx, rdi, rsi, rsp, and r8-r11 are caller-save, not saved across function calls. @@ -71,10 +71,26 @@ const FCALL_LENGTH: u64 = FCALL_RESULT_GOT + 1; const XMM_MAPPED_REGS: [u64; 16] = [1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]; //const XMM_MAPPED_REGS: [u64; 0] = []; // Used for debugging -const F_MEM_CLEAR_WRITE_BYTE: u64 = 1 << 37; -const F_MEM_WRITE_SHIFT: u64 = 36; -const F_MEM_WRITE: u64 = 1 << F_MEM_WRITE_SHIFT; -const F_MEM_WIDTH_SHIFT: u64 = 32; +const F_MOPS_CLEAR_WRITE_BYTE: u64 = 1 << 37; + +const F_MOPS_BLOCK_READ: u64 = 0x0000_000A_0000_0000; +const F_MOPS_BLOCK_WRITE: u64 = 0x0000_000B_0000_0000; + +const F_MOPS_READ_8: u64 = 0x0000_0008_0000_0000; +const F_MOPS_READ_4: u64 = 0x0000_0004_0000_0000; +const F_MOPS_READ_2: u64 = 0x0000_0002_0000_0000; +const F_MOPS_READ_1: u64 = 0x0000_0001_0000_0000; + +const F_MOPS_WRITE_8: u64 = 0x0000_0018_0000_0000; +const F_MOPS_WRITE_4: u64 = 0x0000_0014_0000_0000; +const F_MOPS_WRITE_2: u64 = 0x0000_0012_0000_0000; +const F_MOPS_WRITE_1: u64 = 0x0000_0011_0000_0000; + +const F_MOPS_ALIGNED_READ: u64 = 0x0000_000C_0000_0000; +const F_MOPS_ALIGNED_WRITE: u64 = 0x0000_000D_0000_0000; +// const F_MOPS_ALIGNED_BLOCK_READ: u64 = 0x0000_000E_0000_0000; +// const F_MOPS_ALIGNED_BLOCK_WRITE: u64 = 0x0000_000F_0000_0000; +const F_MOPS_BLOCK_LENGTH_SHIFT: u64 = 36; #[derive(Default, Debug, Clone)] pub struct ZiskAsmRegister { @@ -1179,7 +1195,7 @@ impl ZiskRom2Asm { } if ctx.mem_op() { - Self::a_src_mem_op(&mut ctx, code); + Self::src_read_mops(&mut ctx, code); } ctx.a.is_saved = true; @@ -1563,7 +1579,7 @@ impl ZiskRom2Asm { } if ctx.mem_op() { - Self::b_src_mem_op(&mut ctx, code); + Self::src_read_mops(&mut ctx, code); } } SRC_IMM => { @@ -2307,7 +2323,7 @@ impl ZiskRom2Asm { } if ctx.mem_op() { - Self::b_src_ind_mem_op(&mut ctx, code, reg_address, instruction.ind_width); + Self::b_src_ind_mops(&mut ctx, code, reg_address, instruction.ind_width); } } _ => panic!( @@ -2402,13 +2418,12 @@ impl ZiskRom2Asm { /*************/ // Execute operation, storing result is registers c and flag - Self::operation_to_asm(&mut ctx, instruction.op, code, &mut unusual_code); - - // At this point, REG_C must contain the value of c - assert!(ctx.c.is_saved); + Self::operation_to_asm(&mut ctx, instruction, code, &mut unusual_code); // Copy c value to main trace if ctx.main_trace() { + // At this point, REG_C must contain the value of c + assert!(ctx.c.is_saved); *code += &ctx.full_line_comment("Main[3]=c".to_string()); *code += &format!( "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8 + 3*8], {REG_C}\n" @@ -2429,6 +2444,7 @@ impl ZiskRom2Asm { } } STORE_REG => { + assert!(ctx.c.is_saved); assert!(instruction.store_offset >= 0); assert!(instruction.store_offset <= 34); @@ -2480,6 +2496,7 @@ impl ZiskRom2Asm { } } STORE_MEM => { + assert!(ctx.c.is_saved); *code += &ctx.full_line_comment("STORE_MEM".to_string()); // Calculate memory address and store it in REG_ADDRESS @@ -2612,6 +2629,7 @@ impl ZiskRom2Asm { } } STORE_IND => { + assert!(ctx.c.is_saved); *code += &ctx .full_line_comment(format!("STORE_IND width={}", instruction.ind_width)); @@ -3571,10 +3589,11 @@ impl ZiskRom2Asm { fn operation_to_asm( ctx: &mut ZiskAsmContext, - opcode: u8, + inst: &ZiskInst, code: &mut String, unusual_code: &mut String, ) { + let opcode = inst.op; // Set flags to false, by default ctx.flag_is_always_one = false; ctx.flag_is_always_zero = false; @@ -5039,8 +5058,8 @@ impl ZiskRom2Asm { // Trace 25 memory read operations if ctx.mem_op() { *code += &format!("\tmov {REG_ADDRESS}, rdi\n"); - Self::mem_op_array(ctx, code, REG_ADDRESS, false, 8, 25); - Self::mem_op_array(ctx, code, REG_ADDRESS, true, 8, 25); + Self::mem_op_array(ctx, code, REG_ADDRESS, false, 25); + Self::mem_op_array(ctx, code, REG_ADDRESS, true, 25); } // Call the keccak function @@ -5444,8 +5463,8 @@ impl ZiskRom2Asm { // Save memory operations into mem_reads if ctx.mem_op() { - Self::mem_op_array(ctx, code, "rdi", false, 8, 8); - Self::mem_op_array(ctx, code, "rdi", true, 8, 8); + Self::mem_op_array(ctx, code, "rdi", false, 8); + Self::mem_op_array(ctx, code, "rdi", true, 8); } if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() @@ -5836,8 +5855,8 @@ impl ZiskRom2Asm { // Save memory operations into mem_reads if ctx.mem_op() { - Self::mem_op_array(ctx, code, "rdi", false, 8, 8); - Self::mem_op_array(ctx, code, "rdi", true, 8, 8); + Self::mem_op_array(ctx, code, "rdi", false, 8); + Self::mem_op_array(ctx, code, "rdi", true, 8); } if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() @@ -6325,8 +6344,8 @@ impl ZiskRom2Asm { // Save memory operations into mem_reads if ctx.mem_op() { - Self::mem_op_array(ctx, code, "rdi", false, 8, 12); - Self::mem_op_array(ctx, code, "rdi", true, 8, 12); + Self::mem_op_array(ctx, code, "rdi", false, 12); + Self::mem_op_array(ctx, code, "rdi", true, 12); } if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() @@ -6693,31 +6712,64 @@ impl ZiskRom2Asm { ctx.flag_is_always_zero = false; } ZiskOp::DmaMemCpy => { - unimplemented!(); + assert_eq!(inst.store, STORE_NONE); + // Use the memory address as the first and unique parameter + *code += &ctx.full_line_comment("DmaMemCpy".to_string()); + + *code += &format!( + "\tmov rdi, {} {}\n", + ctx.a.string_value, + ctx.comment_str("rdi = a = destination") + ); + *code += &format!( + "\tmov rsi, {} {}\n", + ctx.b.string_value, + ctx.comment_str("rsi = b = source") + ); + *code += &format!( + "\tmov rdx, 0x{:08x} {}\n", + EXTRA_PARAMS, + ctx.comment_str("rdx = @EXTERN_PARAM") + ); + *code += &format!("\tmov rdx, [rdx] {}\n", ctx.comment_str("rdx = [EXTERN_PARAM]")); + assert_eq!(REG_MEM_READS_ADDRESS, "r12"); + assert_eq!(REG_MEM_READS_SIZE, "r13"); + + match ctx.mode { + AsmGenerationMethod::AsmMinimalTraces => { + *code += "\tcall direct_dma_memcpy_mtrace\n"; + } + AsmGenerationMethod::AsmRomHistogram => { + *code += "\tcall dma_memcpy_fast\n"; + } + AsmGenerationMethod::AsmMemOp => { + *code += "\tcall direct_dma_memcpy_mops\n"; + } + _ => unimplemented!("dma_memcpy not implemented for method {:?}", ctx.mode), + } + + // Set result + *code += &format!("\txor {}, {} {}\n", REG_C, REG_C, ctx.comment_str("c = 0")); + ctx.c.is_saved = true; + ctx.flag_is_always_zero = true; } ZiskOp::DmaMemCmp => { unimplemented!(); } - ZiskOp::Param => { - unimplemented!(); - } - ZiskOp::Params => { - unimplemented!(); - } ZiskOp::Dma64Aligned => { - unimplemented!(); + unimplemented!("Internal opcode Dma64Aligned"); } ZiskOp::DmaUnaligned => { - unimplemented!(); + unimplemented!("Internal opcode DmaUnaligned"); } ZiskOp::DmaPre => { - unimplemented!(); + unimplemented!("Internal opcode DmaPre"); } ZiskOp::DmaPost => { - unimplemented!(); + unimplemented!("Internal opcode DmaPost"); } ZiskOp::DmaCmpByte => { - unimplemented!(); + unimplemented!("Internal opcode DmaCmpByte"); } } } @@ -7220,37 +7272,32 @@ impl ZiskRom2Asm { /* MEMORY OPERATIONS */ /*********************/ - fn a_src_mem_op(ctx: &mut ZiskAsmContext, code: &mut String) { + fn src_read_mops(ctx: &mut ZiskAsmContext, code: &mut String) { // Calculate the trace value on top of the address - const WIDTH: u64 = 8; if ctx.address_is_constant { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_ADDRESS, - (WIDTH << F_MEM_WIDTH_SHIFT) | ctx.address_constant_value, + "\tmov {REG_ADDRESS}, 0x{:x} {}\n", + if ctx.address_constant_value & 0x07 == 0 { + F_MOPS_ALIGNED_READ + } else { + F_MOPS_READ_8 + } + ctx.address_constant_value, ctx.comment_str("aux = constant mem op") ); } else { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - WIDTH << F_MEM_WIDTH_SHIFT, + "\tmov {REG_AUX}, 0x{F_MOPS_READ_8:x} {}\n", ctx.comment_str("aux = mem op mask") ); *code += &format!( - "\tor {}, {} {}\n", - REG_ADDRESS, - REG_AUX, + "\tor {REG_ADDRESS}, {REG_AUX} {}\n", ctx.comment_str("address |= mem op mask") ); } // Copy read data into mem_reads_address and increment it *code += &format!( - "\tmov [{} + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - REG_ADDRESS, + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8], {REG_ADDRESS} {}\n", ctx.comment_str("mem_reads[@+size*8] = mem op") ); @@ -7258,126 +7305,83 @@ impl ZiskRom2Asm { *code += &format!("\tinc {} {}\n", REG_MEM_READS_SIZE, ctx.comment_str("mem_reads_size++")); } - fn b_src_mem_op(ctx: &mut ZiskAsmContext, code: &mut String) { - // Calculate the trace value on top of the address - const WIDTH: u64 = 8; + fn b_src_ind_mops(ctx: &mut ZiskAsmContext, code: &mut String, reg_address: &str, width: u64) { if ctx.address_is_constant { + let mops = if width == 8 && ctx.address_constant_value & 0x07 == 0 { + F_MOPS_ALIGNED_READ + ctx.address_constant_value + } else { + ctx.address_constant_value + + match width { + 1 => F_MOPS_READ_1, + 2 => F_MOPS_READ_2, + 4 => F_MOPS_READ_4, + 8 => F_MOPS_READ_8, + _ => panic!("Invalid width"), + } + }; *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_ADDRESS, - (WIDTH << F_MEM_WIDTH_SHIFT) | ctx.address_constant_value, + "\tmov {reg_address}, 0x{mops:x} {}\n", ctx.comment_str("aux = constant mem op") ); } else { // Calculate the trace value on top of the address - *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - WIDTH << F_MEM_WIDTH_SHIFT, - ctx.comment_str("aux = mem op mask") - ); - *code += &format!( - "\tor {}, {} {}\n", - REG_ADDRESS, - REG_AUX, - ctx.comment_str("address |= mem op mask") - ); - } - - // Copy read data into mem_reads_address and increment it - *code += &format!( - "\tmov [{} + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - REG_ADDRESS, - ctx.comment_str("mem_reads[@+size*8] = mem op") - ); - - // Increment chunk.steps.mem_reads_size - *code += &format!("\tinc {} {}\n", REG_MEM_READS_SIZE, ctx.comment_str("mem_reads_size++")); - } + let mops = match width { + 1 => F_MOPS_READ_1, + 2 => F_MOPS_READ_2, + 4 => F_MOPS_READ_4, + 8 => F_MOPS_READ_8, + _ => panic!("Invalid width"), + }; - fn b_src_ind_mem_op( - ctx: &mut ZiskAsmContext, - code: &mut String, - reg_address: &str, - width: u64, - ) { - if ctx.address_is_constant { - *code += &format!( - "\tmov {}, 0x{:x} {}\n", - reg_address, - (width << F_MEM_WIDTH_SHIFT) + ctx.address_constant_value, - ctx.comment_str("aux = constant mem op") - ); - } else { - // Calculate the trace value on top of the address - *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - width << F_MEM_WIDTH_SHIFT, - ctx.comment_str("aux = mem op mask") - ); + *code += + &format!("\tmov {REG_AUX}, 0x{mops:x} {}\n", ctx.comment_str("aux = mem op mask")); *code += &format!( - "\tor {}, {} {}\n", - reg_address, - REG_AUX, + "\tor {reg_address}, {REG_AUX} {}\n", ctx.comment_str("address |= mem op mask") ); } // Copy read data into mem_reads_address and increment it *code += &format!( - "\tmov [{} + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - reg_address, + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8], {reg_address} {}\n", ctx.comment_str("mem_reads[@+size*8] = mem op") ); // Increment chunk.steps.mem_reads_size - *code += &format!("\tinc {} {}\n", REG_MEM_READS_SIZE, ctx.comment_str("mem_reads_size++")); + *code += &format!("\tinc {REG_MEM_READS_SIZE} {}\n", ctx.comment_str("mem_reads_size++")); } fn c_store_mem_mem_op(ctx: &mut ZiskAsmContext, code: &mut String) { // Calculate the trace value on top of the address - const WRITE: u64 = 1; - const WIDTH: u64 = 8; if ctx.address_is_constant { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_ADDRESS, - (WRITE << F_MEM_WRITE_SHIFT) - + (WIDTH << F_MEM_WIDTH_SHIFT) - + ctx.address_constant_value, + "\tmov {REG_ADDRESS}, 0x{:x} {}\n", + if ctx.address_constant_value & 0x07 == 0 { + F_MOPS_ALIGNED_WRITE + } else { + F_MOPS_WRITE_8 + } + ctx.address_constant_value, ctx.comment_str("aux = constant mem op") ); } else { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - (WRITE << F_MEM_WRITE_SHIFT) + (WIDTH << F_MEM_WIDTH_SHIFT), + "\tmov {REG_AUX}, 0x{F_MOPS_WRITE_8:x} {}\n", ctx.comment_str("aux = mem op mask") ); *code += &format!( - "\tor {}, {} {}\n", - REG_ADDRESS, - REG_AUX, + "\tor {REG_ADDRESS}, {REG_AUX} {}\n", ctx.comment_str("address |= mem op mask") ); } // Copy read data into mem_reads_address and increment it *code += &format!( - "\tmov [{} + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - REG_ADDRESS, + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8], {REG_ADDRESS} {}\n", ctx.comment_str("mem_reads[@+size*8] = mem op") ); // Increment chunk.steps.mem_reads_size - *code += &format!("\tinc {} {}\n", REG_MEM_READS_SIZE, ctx.comment_str("mem_reads_size++")); + *code += &format!("\tinc {REG_MEM_READS_SIZE} {}\n", ctx.comment_str("mem_reads_size++")); } fn c_store_ind_mem_op(ctx: &mut ZiskAsmContext, code: &mut String, width: u64) { @@ -7385,55 +7389,59 @@ impl ZiskRom2Asm { // With this information, the mem_planner can use a specific state machine for // this kind of byte writes if width == 1 { - *code += &format!("\tmov {}, {} {}\n", REG_VALUE, REG_C, ctx.comment_str("value = c")); + *code += &format!("\tmov {REG_VALUE}, {REG_C} {}\n", ctx.comment_str("value = c")); } // Calculate the fixed trace value adding write (bit 36) and width (bits 32-35) on top of // the address if ctx.address_is_constant { + let mops = if width == 8 && ctx.address_constant_value & 0x07 == 0 { + F_MOPS_ALIGNED_WRITE + ctx.address_constant_value + } else { + ctx.address_constant_value + + match width { + 1 => F_MOPS_WRITE_1, + 2 => F_MOPS_WRITE_2, + 4 => F_MOPS_WRITE_4, + 8 => F_MOPS_WRITE_8, + _ => panic!("Invalid width"), + } + }; *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_ADDRESS, - F_MEM_WRITE | (width << F_MEM_WIDTH_SHIFT) | ctx.address_constant_value, + "\tmov {REG_ADDRESS}, 0x{mops:x} {}\n", ctx.comment_str("aux = constant mem op") ); } else { + let mops = match width { + 1 => F_MOPS_WRITE_1, + 2 => F_MOPS_WRITE_2, + 4 => F_MOPS_WRITE_4, + 8 => F_MOPS_WRITE_8, + _ => panic!("Invalid width"), + }; + *code += + &format!("\tmov {REG_AUX}, 0x{mops:x} {}\n", ctx.comment_str("aux = mem op mask")); *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - F_MEM_WRITE | (width << F_MEM_WIDTH_SHIFT), - ctx.comment_str("aux = mem op mask") - ); - *code += &format!( - "\tor {}, {} {}\n", - REG_ADDRESS, - REG_AUX, + "\tor {REG_ADDRESS}, {REG_AUX} {}\n", ctx.comment_str("address |= mem op mask") ); } // Dynamic trace value: if rest of bytes were zero, set flag on bit F_MEM_CLEAR_WRITE_BYTE if width == 1 { - *code += &format!( - "\tshr {}, 8 {}\n", - REG_VALUE, - ctx.comment_str("value & 0xFFFFFF00 == 0 ?") - ); + *code += + &format!("\tshr {REG_VALUE}, 8 {}\n", ctx.comment_str("value & 0xFFFFFF00 == 0 ?")); *code += &format!( "\tjnz pc_{}_rest_of_bytes_not_zero {}\n", ctx.pc, ctx.comment_str("aux & 0xFFFFFF00 != 0 ?") ); *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - F_MEM_CLEAR_WRITE_BYTE, + "\tmov {REG_AUX}, 0x{F_MOPS_CLEAR_WRITE_BYTE:x} {}\n", ctx.comment_str("aux = F_MEM_CLEAR_WRITE_BYTE") ); *code += &format!( - "\tor {}, {} {}\n", - REG_ADDRESS, - REG_AUX, + "\tor {REG_ADDRESS}, {REG_AUX} {}\n", ctx.comment_str("address |= F_MEM_CLEAR_WRITE_BYTE") ); *code += &format!("\npc_{}_rest_of_bytes_not_zero:\n", ctx.pc); @@ -7441,76 +7449,49 @@ impl ZiskRom2Asm { // Copy read data into mem_reads_address and increment it *code += &format!( - "\tmov [{} + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - REG_ADDRESS, + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8], {REG_ADDRESS} {}\n", ctx.comment_str("mem_reads[@+size*8] = mem op") ); // Increment chunk.steps.mem_reads_size - *code += &format!("\tinc {} {}\n", REG_MEM_READS_SIZE, ctx.comment_str("mem_reads_size++")); + *code += &format!("\tinc {REG_MEM_READS_SIZE} {}\n", ctx.comment_str("mem_reads_size++")); } fn mem_op_array( ctx: &mut ZiskAsmContext, code: &mut String, reg_address: &str, - _write: bool, - width: u64, + write: bool, length: u64, ) { - let write: u64 = if _write { 1 } else { 0 }; - let mem_op_mask: u64 = (write << F_MEM_WRITE_SHIFT) | (width << F_MEM_WIDTH_SHIFT); + let mops_mask: u64 = if length > 1 { + // compress operation in one single block + (if write { F_MOPS_BLOCK_WRITE } else { F_MOPS_BLOCK_READ }) + | (length << F_MOPS_BLOCK_LENGTH_SHIFT) + } else if write { + F_MOPS_WRITE_8 + } else { + F_MOPS_READ_8 + }; - // Get a copy of the address register + // Load mask the mask *code += &format!( - "\tmov {}, {} {}\n", - REG_VALUE, - reg_address, - ctx.comment_str("value = address") + "\tmov {REG_VALUE}, 0x{mops_mask:x} {}\n", + ctx.comment_str("value = mem op mask") ); - // Calculate the mask - *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - mem_op_mask, - ctx.comment_str("aux = mem op mask + offset") - ); + // Get a copy of the address register + *code += + &format!("\tadd {REG_VALUE}, {reg_address} {}\n", ctx.comment_str("value = address")); - // Add the mask to the address *code += &format!( - "\tadd {}, {} {}\n", - REG_VALUE, - REG_AUX, - ctx.comment_str("value |= mem op mask") + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8], {REG_VALUE} {}\n", + ctx.comment_str("mem_reads[@+size*8] = mem op") ); - // Iterate for all memory operations - for i in 0..length { - // Copy read data into mem_reads_address and increment it - *code += &format!( - "\tmov [{} + {}*8 + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - i, - REG_VALUE, - ctx.comment_str("mem_reads[@+size*8] = mem op") - ); - - if i != (length - 1) { - // Get a copy of the address register - *code += &format!("\tadd {}, 8 {}\n", REG_VALUE, ctx.comment_str("value += 8")); - } - } // Increment chunk.steps.mem_reads_size - *code += &format!( - "\tadd {}, {} {}\n", - REG_MEM_READS_SIZE, - length, - ctx.comment_str("mem_reads_size += length") - ); + *code += + &format!("\tinc {REG_MEM_READS_SIZE} {}\n", ctx.comment_str("mem_reads_size += 1")); } fn internal_mem_op_precompiled_read( @@ -7520,105 +7501,81 @@ impl ZiskRom2Asm { load_sizes: &[usize], update_index: bool, ) -> u64 { - // Calculate the mask - let mem_op_mask: u64 = 8u64 << 32; - // This index will be incremented as we insert data into mem_reads let mut mem_reads_index: u64 = 0; // We get a copy of the precompiled data address - *code += &format!("\tmov {}, rdi {}\n", REG_ADDRESS, ctx.comment_str("address = rdi")); - - for i in 0..params_count { - // Store next aligned address value in mem_reads, and advance it - *code += &format!( - "\tmov {}, [{} + {}*8] {}\n", - REG_VALUE, - REG_ADDRESS, - i, - ctx.comment(format!("value = mem[address+{i}]")) - ); + *code += &format!("\tmov {REG_ADDRESS}, rdi {}\n", ctx.comment_str("address = rdi")); - // Load the mask + offset + if params_count > 0 { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - mem_op_mask + 8 * i, - ctx.comment_str("aux = mem op mask + offset") + "\tmov {REG_AUX}, 0x{:x} {}\n", + F_MOPS_BLOCK_READ | (params_count << F_MOPS_BLOCK_LENGTH_SHIFT), + ctx.comment_str(&format!("aux = MOPS_BLOCK_READ({})", params_count)) ); // Add the address - *code += &format!( - "\tadd {}, {} {}\n", - REG_AUX, - REG_ADDRESS, - ctx.comment_str("aux += address") - ); + *code += + &format!("\tadd {REG_AUX}, {REG_ADDRESS} {}\n", ctx.comment_str("aux += address")); // Store it in the trace *code += &format!( - "\tmov [{} + {}*8 + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - mem_reads_index, - REG_AUX, + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8 + {mem_reads_index}*8], {REG_AUX} {}\n", ctx.comment_str("mem_reads[@+size*8+ind*8] = mem_op") ); mem_reads_index += 1; } + let mut previous_size = 0; + for (i, size) in load_sizes.iter().enumerate() { // Store next aligned address value in mem_reads, and advance it *code += &format!( - "\tmov {}, [{} + {}*8] {}\n", - REG_VALUE, - REG_ADDRESS, - i, + "\tmov {REG_VALUE}, [{REG_ADDRESS} + {i}*8] {}\n", ctx.comment(format!("value = mem[address+{i}]")) ); - // Store the first load_count iterations - // load_size elements in mem_reads - - // For each chunk of the indirection, store it in mem_reads - for l in 0..*size { - // Load the mask + offset + // if previous_size = size, means that REG_AUX has the correct value + // and not need to generate again + if previous_size != *size { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - mem_op_mask + 8 * (l as u64), - ctx.comment_str("aux = mem op mask + offset") + "\tmov {REG_AUX}, 0x{:x} {}\n", + F_MOPS_BLOCK_READ | ((*size as u64) << F_MOPS_BLOCK_LENGTH_SHIFT), + ctx.comment(format!("aux = MOPS_BLOCK_READ({})", size)) ); + previous_size = *size; + } + + // Store a block with all consecutive mem_reads + + // Add the mask over the reg_value to reuse mops_mask (reg_aux) if width is the + // same of last previous parameter + + *code += + &format!("\tadd {REG_VALUE}, {REG_AUX} {}\n", ctx.comment_str("value += aux ")); - // Add the address + // Store it in the trace + *code += &format!( + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8 + {mem_reads_index}*8], {REG_VALUE} {}\n", + ctx.comment_str("mem_reads[@+size*8+ind*8] = mops") + ); + + mem_reads_index += 1; + } + if update_index && mem_reads_index > 0 { + // Increment chunk.steps.mem_reads_size + if mem_reads_index == 1 { *code += &format!( - "\tadd {}, {} {}\n", - REG_AUX, - REG_VALUE, - ctx.comment_str("aux += address") + "\tinc {REG_MEM_READS_SIZE}, {}\n", + ctx.comment_str("mem_reads_size+=1") ); - - // Store it in the trace + } else { *code += &format!( - "\tmov [{} + {}*8 + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - mem_reads_index, - REG_AUX, - ctx.comment_str("mem_reads[@+size*8+ind*8] = mem_op") + "\tadd {REG_MEM_READS_SIZE}, {mem_reads_index} {}\n", + ctx.comment(format!("mem_reads_size+={mem_reads_index}")) ); - mem_reads_index += 1; } } - if update_index { - // Increment chunk.steps.mem_reads_size - *code += &format!( - "\tadd {}, {} {}\n", - REG_MEM_READS_SIZE, - mem_reads_index, - ctx.comment(format!("mem_reads_size+={mem_reads_index}")) - ); - } mem_reads_index } @@ -7645,80 +7602,68 @@ impl ZiskRom2Asm { load_size: u64, initial_mem_reads_index: u64, ) { - // Calculate the mask - let mem_op_mask: u64 = F_MEM_WRITE + (8u64 << F_MEM_WIDTH_SHIFT); - // This index will be incremented as we insert data into mem_reads let mut mem_reads_index: u64 = initial_mem_reads_index; if initial_mem_reads_index == 0 { // We get a copy of the precompiled data address - *code += &format!("\tmov {}, rdi {}\n", REG_ADDRESS, ctx.comment_str("address = rdi")); + *code += &format!("\tmov {REG_ADDRESS}, rdi {}\n", ctx.comment_str("address = rdi")); + } + if begin <= end { + // Load the mask + offset + *code += &format!( + "\tmov {REG_AUX}, 0x{:x} {}\n", + F_MOPS_BLOCK_WRITE | (load_size << F_MOPS_BLOCK_LENGTH_SHIFT), + ctx.comment(format!("aux = BLOCK_WRITE({})", load_size)) + ); } + // For every parameter for i in begin..=end { // Store next aligned address value in mem_reads, and advance it *code += &format!( - "\tmov {}, [{} + {}*8] {}\n", - REG_VALUE, - REG_ADDRESS, - i, + "\tmov {REG_VALUE}, [{REG_ADDRESS} + {i}*8] {}\n", ctx.comment(format!("value = mem[address+{i}]")) ); - // For each of the indirection parameter, store it in mem_reads - for l in 0..load_size { - // Load the mask + offset - *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - mem_op_mask + 8 * l, - ctx.comment_str("aux = mem op mask + offset") + // Add the address + *code += + &format!("\tadd {REG_VALUE}, {REG_AUX} {}\n", ctx.comment_str("value += address")); + + // Store it in the trace + *code += &format!( + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8 + {mem_reads_index}*8], {REG_VALUE} {}\n", + ctx.comment_str("mem_reads[@+size*8+ind*8] = value (mops)") ); + mem_reads_index += 1; + } - // Add the address + // Increment chunk.steps.mem_reads_size + if mem_reads_index > 0 { + if mem_reads_index == 1 { *code += &format!( - "\tadd {}, {} {}\n", - REG_AUX, - REG_VALUE, - ctx.comment_str("aux += address") + "\tinc {REG_MEM_READS_SIZE} {}\n", + ctx.comment_str("mem_reads_size+=1") ); - - // Store it in the trace + } else { *code += &format!( - "\tmov [{} + {}*8 + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - mem_reads_index, - REG_AUX, - ctx.comment_str("mem_reads[@+size*8+ind*8] = mem_op") + "\tadd {REG_MEM_READS_SIZE}, {mem_reads_index} {}\n", + ctx.comment(format!("mem_reads_size+={mem_reads_index}")) ); - mem_reads_index += 1; } } - - // Increment chunk.steps.mem_reads_size - *code += &format!( - "\tadd {}, {} {}\n", - REG_MEM_READS_SIZE, - mem_reads_index, - ctx.comment(format!("mem_reads_size+={mem_reads_index}")) - ); } fn mem_op_precompiled_restore_c_and_flags(ctx: &mut ZiskAsmContext, code: &mut String) { // We get a copy of the precompiled data address - *code += &format!("\tmov {}, rdi {}\n", REG_ADDRESS, ctx.comment_str("address = rdi")); + *code += &format!("\tmov {REG_ADDRESS}, rdi {}\n", ctx.comment_str("address = rdi")); // read last mem_read into c *code += &format!( - "\tmov {}, [{} + {}*8 - 8] {}\n", - REG_C, - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, + "\tmov {REG_C}, [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8 - 8] {}\n", ctx.comment_str("c = mem_reads[@+size*8+ind*8]") ); - *code += &format!("\tmov {}, {} {}\n", REG_FLAG, REG_C, ctx.comment_str("flag = c")); + *code += &format!("\tmov {REG_FLAG}, {REG_C} {}\n", ctx.comment_str("flag = c")); } /*******************/ @@ -8743,10 +8688,7 @@ impl ZiskRom2Asm { /////////////// // Build the mask for this case - const WIDTH: u64 = 8; - const WRITE: u64 = 0; - let addr_step_mask: u64 = - (WIDTH << F_MEM_WIDTH_SHIFT) + (WRITE << F_MEM_WRITE_SHIFT) + (micro_step << 38); + let addr_step_mask: u64 = F_MOPS_READ_8 + (micro_step << 38); // Add mask to address *code += &format!( @@ -8925,10 +8867,15 @@ impl ZiskRom2Asm { /////////////// // Build the mask for this case - const WRITE: u64 = 1; const MICRO_STEP: u64 = 3; - let addr_step_mask: u64 = - (width << F_MEM_WIDTH_SHIFT) + (WRITE << F_MEM_WRITE_SHIFT) + (MICRO_STEP << 40); + let addr_step_mask: u64 = (MICRO_STEP << 40) + + match width { + 1 => F_MOPS_WRITE_1, + 2 => F_MOPS_WRITE_2, + 4 => F_MOPS_WRITE_4, + 8 => F_MOPS_WRITE_8, + _ => panic!("Invalid width {width}"), + }; // Add mask to address *code += &format!( @@ -9038,7 +8985,7 @@ impl ZiskRom2Asm { const WIDTH: u64 = 8; const MICRO_STEP: u64 = 2; let addr_step_mask: u64 = - (WIDTH << F_MEM_WIDTH_SHIFT) + (write << F_MEM_WRITE_SHIFT) + (MICRO_STEP << 40); + if write == 0 { F_MOPS_READ_8 } else { F_MOPS_WRITE_8 } + (MICRO_STEP << 40); // For every element for i in 0..buffer_size { diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 2d006d7ae..218ad9824 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -11,6 +11,7 @@ endif # Default EMU_PATH and OUT_PATH EMU_PATH ?= src/emu.asm OUT_PATH ?= build/ziskemuasm +DMA_OBJS = build/dma/fast_dma_encode.o build/dma/memcpy_fast.o build/dma/direct_memcpy_mtrace.o build/dma/direct_memcpy_mops.o # Ensure the output directory exists OUT_DIR := $(dir $(OUT_PATH)) @@ -22,10 +23,17 @@ build/emu.o: $(EMU_PATH) mkdir -p build as $(ASMFLAGS) -o build/emu.o $< +build/dma/%.o: src/dma/%.asm src/dma/dma_constants.inc + mkdir -p build/dma + as $(ASMFLAGS) -I./src/dma -o $@ $< + +build/dma.o: $(DMA_OBJS) + ld -r $(DMA_OBJS) -o $@ + # Compile the final executable -$(OUT_PATH): build/emu.o src/main.c src/emu.c src/chfast/keccak.c +$(OUT_PATH): build/emu.o src/main.c src/emu.c src/chfast/keccak.c build/dma.o mkdir -p $(OUT_DIR) - gcc $(CFLAGS) src/main.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ + gcc $(CFLAGS) src/main.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ clean: rm -rf build diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index 93eab3f06..2fed835f0 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -263,7 +263,6 @@ impl AsmRunnerMO { // _stats.add_stat(i); // } // } - preloaded.handle_mo = Some(std::thread::spawn(move || { drop(mem_planner); MemPlanner::new() diff --git a/emulator-asm/src/dma/Makefile b/emulator-asm/src/dma/Makefile new file mode 100644 index 000000000..0f82efa4d --- /dev/null +++ b/emulator-asm/src/dma/Makefile @@ -0,0 +1,33 @@ +# Makefile for encode_memcpy_inline test + +CXX = g++ +CXXFLAGS = -std=c++17 -O0 -Wall -Wextra -g -no-pie +# CXXFLAGS = -std=c++17 -O2 -Wall -Wextra -g -no-pie +LDFLAGS = -no-pie +ASMFLAGS = -g --noexecstack -I$(CURDIR) + +SRCS_ASM = direct_memcpy_mtrace.asm memcpy_fast.asm fast_dma_encode.asm direct_memcpy_mops.asm +SRCS_CPP = test_dma.cpp +OBJS_ASM = direct_memcpy_mtrace.o memcpy_fast.o fast_dma_encode.o direct_memcpy_mops.o +OBJS_CPP = $(SRCS_CPP:.cpp=.o) +OBJS = $(OBJS_ASM) $(OBJS_CPP) +TARGET = test_dma + +all: $(TARGET) + +%.o: %.asm + as $(ASMFLAGS) -o $@ $< + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +$(TARGET): $(OBJS) + $(CXX) $(LDFLAGS) -o $@ $^ + +test: $(TARGET) + ./$(TARGET) + +clean: + rm -f $(OBJS) $(TARGET) + +.PHONY: all test_dma clean diff --git a/emulator-asm/src/dma/direct_memcpy_mops.asm b/emulator-asm/src/dma/direct_memcpy_mops.asm new file mode 100644 index 000000000..d256096a4 --- /dev/null +++ b/emulator-asm/src/dma/direct_memcpy_mops.asm @@ -0,0 +1,330 @@ +.intel_syntax noprefix +.code64 + +################################################################################ +# memcpy_mops - Optimized version with memory ops tracing and actual copy +# +# This function performs two main tasks: +# 1. Records all addresses of memory operations (read and write addresses) +# 2. Performs the actual memory copy from src to dst (with overlap handling) +# +# REGISTER USAGE: +# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r9, r11, r12, r13 +# Does NOT use XMM registers (caller doesn't need to save them) +# Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) +# +# PARAMETERS (NON System V AMD64 ABI): +# rdi -> rbx = dst (u64) - Destination address +# rsi -> rax = src (u64) - Source address +# rdx -> count (usize) - Number of bytes to copy +# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) +# +# MEMORY COPY BEHAVIOR: +# - Handles overlapping src/dst correctly (like memmove) +# - For non-overlapping: optimized copy using pre_count/loop_count/post_count +# - For overlapping: backward byte-by-byte copy to avoid corruption +################################################################################ + +.global direct_dma_memcpy_mops +.global dma_memcpy_mops +.extern fast_dma_encode + +.include "dma_constants.inc" +.equ MOPS_ALIGNED_READ_2W, ((2 << MOPS_BLOCK_WORDS_SBITS) + MOPS_ALIGNED_BLOCK_READ) +.equ LOOP_COUNT_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_SBITS - LOOP_COUNT_SBITS) +.equ PRE_WRITES_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_SBITS - PRE_WRITES_SBITS) + +.section .text + +# mov rdx, [0xA000_0F00] # save count before call +# mov rdi, rbx # rdi = dst (rbx) +# mov rsi, rax # rsi = src (rax) + +# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) + +# call function with standard ABI call +dma_memcpy_mops: + + # save registers used + push r12 # register used as mops base address + push r13 # register used as mops index + push rbx # + + mov r12, rcx + xor r13, r13 + call direct_dma_memcpy_mops + + mov rax, r13 + pop rbx + pop r13 + pop r12 + + ret + +# call directly from assembly without standard ABI call +# more eficient + +direct_dma_memcpy_mops: + + # updated registers: + # r9 = no save (value_reg) + # rcx = no save (available from asm) + # rdi = no save (available from asm) + # rsi = no save (available from asm) + # r13 = with new mops index (output) + # rax = encoded + + # Call fast_dma_encode to calculate encoding + # Parameters already in correct registers: rdi=dst, rsi=src, rdx=count + # Result will be returned in rax (encoded value) + + call fast_dma_encode # ~15-20 cycles - table lookup encoding + + # add read parameter count to mops + + mov r9, (MOPS_ALIGNED_READ + EXTRA_PARAMETER_ADDR) + mov [r12 + r13 * 8], r9 + inc r13 + + # check if count is zero + test rdx, rdx # compare count + jz .L_done # jump if zero + +.L_pre_dst_to_mops: + # If pre_count > 0, write aligned dst value to trace + test rax, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_post_dst_to_mops # 2 cycles (predicted taken) + +.L_pre_is_active: + # Branch with pre_count > 0: save original dst value before it's overwritten + mov r9, MOPS_ALIGNED_READ # r9 = flags aligned read + add r9, rdi # 1 cycle - get original dst + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov [r12 + r13 * 8], r9 # ~4 cycles - write dst pre-address to trace + + test rax, DOUBLE_SRC_PRE_MASK + jnz .L_pre_double_src_to_mops + +.L_pre_single_src_to_mops: + + mov r9, MOPS_ALIGNED_READ # r9 = flags aligned read + add r9, rsi # 1 cycle - get original src + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + jmp .L_pre_src_inc_mops_index + +.L_pre_double_src_to_mops: + + mov r9, MOPS_ALIGNED_READ_2W # r9 = flags double read + add r9, rsi # 1 cycle - get original src + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + +.L_pre_src_inc_mops_index: + add r13, 2 # add 2 (pre-write, block single/dual src) + +.L_post_dst_to_mops: + + # If post_count > 0, write aligned (dst+count) value to trace + test rax, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_src_to_mops # 2 cycles (predicted taken) - skip to src copy + +.L_post_is_active: + mov rcx, MOPS_ALIGNED_READ # rcx = flags aligned read + lea r9, [rdi + rdx - 1] # 1 cycle - r9 = dst + count - 1 (last dst byte) + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + add r9, rcx # 1 cycle - r9 mops with dst aligned address + mov [r12 + r13 * 8], r9 # ~4 cycles - write dst post-value to trace + + mov r9, rax + shr r9, PRE_AND_LOOP_BYTES_SBITS + add r9, rsi + and r9, ALIGN_MASK + + test rax, DOUBLE_SRC_POST_MASK + jnz .L_post_double_src_to_mops + +.L_post_single_src_to_mops: + + mov rcx, MOPS_ALIGNED_READ # r9 = flags aligned read + add r9, rcx # 1 cycle - get original src + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + jmp .L_post_src_inc_mops_index + +.L_post_double_src_to_mops: + + mov rcx, MOPS_ALIGNED_READ_2W # r9 = flags aligned read + add r9, rcx # 1 cycle - get original src + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + +.L_post_src_inc_mops_index: + add r13, 2 # add 2 (pre-write, block single/dual src) + +.L_src_to_mops: + mov rcx, rax # 1 cycle - rcx = encoded + shr rcx, LOOP_COUNT_SBITS # 1 cycle - rcx = loop (32 bits) + jz .L_save_dst_with_loop_count_zero + shl rcx, MOPS_BLOCK_WORDS_SBITS # 1 cycle - rcx = loop | 0 (4 bits) | (32 bits) + + test rax, UNALIGNED_DST_SRC_MASK + jnz .L_src_extra_for_unaligned_loop + + mov r9, MOPS_ALIGNED_BLOCK_READ # 1 cycle - r9 = read block + jmp .L_src_block_before_address + +.L_src_extra_for_unaligned_loop: + mov r9, MOPS_ALIGNED_BLOCK_READ + MOPS_BLOCK_ONE_WORD + +.L_src_block_before_address: + add r9, rcx + test rax, SRC64_INC_BY_PRE_MASK + jnz .L_src_incr_by_pre + add r9, rsi # 1 cycle - rcx = first block src address + jmp .L_src_to_mops_ready + +.L_src_incr_by_pre: + lea r9, [rsi + r9 + 8] + +.L_src_to_mops_ready: + and r9, ALIGN_MASK + mov [r12 + r13 * 8], r9 # ~4 cycles - write first block src read address + inc r13 + +.L_save_dst_addr_reusing_rcx: + + mov r9, rax # rcx = encoded + and r9, PRE_WRITES_MASK # rcx = pre_writes mask + shl r9, PRE_WRITES_TO_MOPS_BLOCK # rcx = pre_writes offset (with correct shift) + add r9, rcx + add r9, rdi + + mov rcx, MOPS_ALIGNED_BLOCK_WRITE + add r9, rcx + and r9, ALIGN_MASK + + mov [r12 + r13 * 8], r9 + inc r13 + jmp .L_mops_done + +.L_save_dst_with_loop_count_zero: + mov r9, rax # rcx = encoded + and r9, PRE_WRITES_MASK # rcx = pre_writes mask + shl r9, PRE_WRITES_TO_MOPS_BLOCK # rcx = pre_writes offset (with correct shift) + add r9, rdi + + mov rcx, MOPS_ALIGNED_BLOCK_WRITE + add r9, rcx + and r9, ALIGN_MASK + + mov [r12 + r13 * 8], r9 + inc r13 + +.L_mops_done: + + # Check for memory overlap to decide copy direction + # NOTE: rdi and rsi contain their ORIGINAL values (not modified in mops section) + # Overlap exists if: src < dst < src+count (forward overlap) + cmp rdi, rsi # 1 cycle - compare dst with src + jb .L_copy_forward # 2 cycles (predicted) - dst < src, no overlap + lea r9, [rsi + rdx] # 1 cycle - r9 = src + count + cmp rdi, r9 # 1 cycle - compare dst with (src+count) + jae .L_copy_forward # 2 cycles (predicted) - dst >= src+count, no overlap + + # Overlap detected (src < dst < src+count), must copy backward + # Setup: rsi = src+count-1, rdi = dst+count-1, rcx = count + # Uses ORIGINAL rsi and rdi values (not modified during mops recording) + + lea rsi, [rsi + rdx - 1] # 1 cycle - rsi = src + count - 1 (from original) + lea rdi, [rdi + rdx - 1] # 1 cycle - rdi = dst + count - 1 (from original) + mov rcx, rdx # 1 cycle - rcx = count + + std # ~20-50 cycles - set DF (serializing, pipeline flush) + rep movsb # ~3-5 cycles per byte (backward copy, slower than forward) + cld # ~20-50 cycles - clear DF (serializing, pipeline flush) + + ret # ~5 cycles + +.L_copy_forward: + # No overlap detected, perform optimized forward copy + cmp rdx, 16 # 1 cycle - check if count >= 16 (worth alignment) + jae .L_copy_forward_pre # 2 cycles (predicted) - use 3-phase aligned copy + + # Small copy (count < 16): copy all bytes directly + mov rcx, rdx # 1 cycle - rcx = count + rep movsb # ~3-5 cycles per byte (unaligned small copy) + + ret # ~5 cycles + +.L_copy_forward_pre: + # Copy in 3 phases: pre-alignment bytes, aligned qwords, post-alignment bytes + # If pre_count > 0, copy unaligned prefix bytes + test rax, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_copy_forward_loop # 2 cycles (predicted) + + # Extract and copy pre_count bytes (1-7 bytes to reach alignment) + mov rcx, rax # 1 cycle + and rcx, PRE_COUNT_MASK # 1 cycle - rcx = pre_count (bits 0-3) + + rep movsb # ~3-5 cycles per byte + # rsi, rdi now 8-byte aligned + +.L_copy_forward_loop: + # Copy aligned qwords (main bulk of data) + mov rcx, rax # 1 cycle + shr rcx, LOOP_COUNT_SBITS # 1 cycle - rcx = loop_count (bits 32-63) + rep movsq # ~1.5-2 cycles per qword (aligned, optimized) + # rsi, rdi advanced by loop_count * 8 + +.L_check_forward_post: + + # If post_count > 0, copy remaining unaligned suffix bytes + test rax, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_done # 2 cycles (predicted) + + # Extract and copy post_count bytes (1-7 bytes after aligned data) + mov rcx, rax # 1 cycle + shr rcx, POST_COUNT_SBITS # 1 cycle - shift post_count to position + and rcx, 0x07 # 1 cycle - rcx = post_count (bits 43-45) + + rep movsb # ~3-5 cycles per byte + # rsi, rdi now point past end of data + +.L_done: + ret # ~5 cycles + +# Performance estimate (Modern x86-64, L1 cache hits): +# +# NON-OVERLAPPING FORWARD COPY PATH: +# - fast_dma_encode call: ~15-20 cycles (function call + table lookup) +# - Write mops entries: ~4-6 cycles per entry +# - Pre-read mops (conditional): ~12 cycles (if pre_count > 0) +# - Post-read mops (conditional): ~12 cycles (if post_count > 0) +# - Block src read mops: ~8-12 cycles (address calculation + write) +# - Pre-bytes copy: ~3-5 cycles per byte (if pre_count > 0, max 7 bytes) +# - Aligned qwords copy: ~1.5-2 cycles per qword (rep movsq, main data) +# - Post-bytes copy: ~3-5 cycles per byte (if post_count > 0, max 7 bytes) +# - Function overhead: ~10 cycles (push/pop, branches, return) +# +# TOTAL (best case, aligned, no pre/post): +# ~30 cycles base + ~2 cycles per qword (trace + copy) +# +# TOTAL (typical case, some alignment): +# ~50 cycles base + ~2 cycles per qword + ~4 cycles per pre/post byte +# +# OVERLAPPING BACKWARD COPY PATH: +# - Same mops overhead: ~30-50 cycles +# - std instruction: ~20-50 cycles (serializing, causes pipeline flush) +# - Backward byte copy: ~3-5 cycles per byte (rep movsb backward) +# - cld instruction: ~20-50 cycles (serializing, causes pipeline flush) +# +# TOTAL (overlap, worst case): +# ~100-150 cycles base + ~4-5 cycles per byte +# +# NOTES: +# - Assumes L1 cache hits for all memory accesses +# - rep movsq/movsb performance varies by microarchitecture +# - Actual cycles may vary ±20% depending on CPU model and memory alignment +# - Fast path (aligned, no overlap) is ~2-3x faster than overlap path + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/direct_memcpy_mtrace.asm b/emulator-asm/src/dma/direct_memcpy_mtrace.asm new file mode 100644 index 000000000..4d34e1b42 --- /dev/null +++ b/emulator-asm/src/dma/direct_memcpy_mtrace.asm @@ -0,0 +1,251 @@ +.intel_syntax noprefix +.code64 + +# +# memcpy_mtrace - Optimized version with memory tracing and actual copy +# +# This function performs three main tasks: +# 1. Encodes memcpy metadata (offsets, counts, flags) using fast_dma_encode +# 2. Records memory trace (pre-values and src data for verification/rollback) +# 3. Performs the actual memory copy from src to dst (with overlap handling) +# +# REGISTER USAGE: +# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r9, r10, r11, r12, r13 +# Does NOT use XMM registers (caller doesn't need to save them) +# Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) +# +# PARAMETERS (NON System V AMD64 ABI): +# rdi -> rbx = dst (u64) - Destination address +# rsi -> rax = src (u64) - Source address +# rdx -> count (usize) - Number of bytes to copy +# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) +# +# RETURN VALUE: +# RAX = Number of 64-bit words written to trace buffer +# +# MEMORY TRACE FORMAT (written to trace buffer sequentially): +# [0] = Encoded metadata (64-bit value with offsets, counts, flags) +# [1] = Pre-write value at aligned(dst) IF pre_count > 0 +# [1 or 2] = Post-write value at aligned(dst+count) IF post_count > 0 +# [...] = All aligned qwords from aligned(src) to aligned(src+count) +# +# The trace buffer captures: +# - Original destination values (for undo/verification) +# - Source data (for verification) +# - Metadata needed to reconstruct the operation +# +# MEMORY COPY BEHAVIOR: +# - Handles overlapping src/dst correctly (like memmove) +# - For non-overlapping: optimized copy using pre_count/loop_count/post_count +# - For overlapping: backward byte-by-byte copy to avoid corruption +# +# PERFORMANCE: +# - Encoding: ~15-20 cycles (function call to fast_dma_encode, table lookup) +# - Trace writes: ~4 cycles per qword write +# - Src data copy to trace: ~1.5-2 cycles per qword (rep movsq) +# - Final memcpy (non-overlap): ~1.5-2 cycles per qword (rep movsq aligned) +# - Final memcpy (overlap): ~100-150 cycles overhead + ~4-5 cycles per byte (std/rep movsb/cld) +# +# SIDE EFFECTS: +# - Modifies memory at dst (count bytes) +# - Modifies trace buffer (variable size depending on pre/post counts) +# - Preserves direction flag (cld called after any std) + +.global direct_dma_memcpy_mtrace +.global dma_memcpy_mtrace +.extern fast_dma_encode + +.include "dma_constants.inc" + +.section .text + +# mov rdx, [0xA000_0F00] ; save count before call +# mov rdi, rbx ; rdi = dst (rbx) +# mov rsi, rax ; rsi = src (rax) + +# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) + +dma_memcpy_mtrace: + push r12 + push r13 + push rbx + + mov r12, rcx + xor r13, r13 + call direct_dma_memcpy_mtrace + + mov rax, r13 + pop rbx + pop r13 + pop r12 + + ret + +direct_dma_memcpy_mtrace: + + # Call fast_dma_encode to calculate encoding + # Parameters already in correct registers: rdi=dst, rsi=src, rdx=count + # Result will be returned in rax (encoded value) + + call fast_dma_encode # ~15-20 cycles - table lookup encoding + + mov [r12 + r13 * 8], rax # ~4 cycles - write encoded result to mem trace + inc r13 # 1 cycle - advance r13 (mem trace index) + +.L_pre_dst_to_mtrace: + # If pre_count > 0, write aligned dst value to trace + test rax, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_post_dst_to_mtrace # 2 cycles (predicted taken) + + # Branch with pre_count > 0: save original dst value before it's overwritten + mov r9, rdi # 1 cycle - get original dst + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov r9, [r9] # ~4 cycles - read qword from aligned dst + mov [r12 + r13 * 8], r9 # ~4 cycles - write dst pre-value to trace + inc r13 # 1 cycle - advance trace index + +.L_post_dst_to_mtrace: + + # If post_count > 0, write aligned (dst+count) value to trace + test rax, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_src_to_mtrace # 2 cycles (predicted taken) - skip to src copy + + lea r9, [rdi + rdx - 1] # 1 cycle - r9 = dst + count - 1 (last dst byte) + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov r9, [r9] # ~4 cycles - read qword at (dst+count) aligned + mov [r12 + r13 * 8], r9 # ~4 cycles - write dst post-value to trace + inc r13 # 1 cycle - advance trace index + +.L_src_to_mtrace: + # Copy source data to trace buffer + # Total qwords = loop_count (bits 0-31) + extra_src_reads (bits 48-50) + + mov rcx, rax # 1 cycle - rcx = encoded + shr rcx, LOOP_COUNT_SBITS # 1 cycle - rcx = loop_count (bits 32-63) + + mov r9, rax # 1 cycle - r9 = encoded + shr r9, EXTRA_SRC_READS_SBITS # 1 cycle - shift extra_src_reads to position + and r9, 0x03 # 1 cycle - r9 = extra_src_reads (bits 48-50) + add rcx, r9 # 1 cycle - rcx = total qwords to copy + + # Setup for rep movsq: copy from aligned src to trace buffer + mov r9, rsi # 1 cycle - preserve original src pointer + and rsi, ALIGN_MASK # 1 cycle - rsi = src aligned to 8 bytes + + push rdi # ~3 cycles - save dst pointer + lea rdi, [r12 + r13 * 8] # 1 cycle - rdi = trace buffer destination + add r13, rcx # 1 cycle - advance trace index by qwords copied + + rep movsq # ~1.5-2 cycles per qword (hardware optimized) + + pop rdi # ~3 cycles - restore dst pointer + mov rsi, r9 # 1 cycle - restore original src pointer + +.L_mtrace_done: + # Check for memory overlap to decide copy direction + # NOTE: rdi and rsi now contain their ORIGINAL values (restored above) + # Overlap exists if: src < dst < src+count (forward overlap) + cmp rdi, rsi # 1 cycle - compare dst with src + jb .L_copy_forward # 2 cycles (predicted) - dst < src, no overlap + lea r9, [rsi + rdx] # 1 cycle - r9 = src + count + cmp rdi, r9 # 1 cycle - compare dst with (src+count) + jae .L_copy_forward # 2 cycles (predicted) - dst >= src+count, no overlap + + # Overlap detected (src < dst < src+count), must copy backward + # Setup: rsi = src+count-1, rdi = dst+count-1, rcx = count + # Uses ORIGINAL rsi and rdi values (restored from r9 and stack) + + lea rsi, [rsi + rdx - 1] # 1 cycle - rsi = src + count - 1 (from original) + lea rdi, [rdi + rdx - 1] # 1 cycle - rdi = dst + count - 1 (from original) + mov rcx, rdx # 1 cycle - rcx = count + + std # ~20-50 cycles - set DF (serializing, pipeline flush) + rep movsb # ~3-5 cycles per byte (backward copy, slower than forward) + cld # ~20-50 cycles - clear DF (serializing, pipeline flush) + + ret # ~5 cycles + +.L_copy_forward: + # No overlap detected, perform optimized forward copy + cmp rdx, 16 # 1 cycle - check if count >= 16 (worth alignment) + jae .L_copy_forward_pre # 2 cycles (predicted) - use 3-phase aligned copy + + # Small copy (count < 16): copy all bytes directly + mov rcx, rdx # 1 cycle - rcx = count + rep movsb # ~3-5 cycles per byte (unaligned small copy) + + ret # ~5 cycles + +.L_copy_forward_pre: + # Copy in 3 phases: pre-alignment bytes, aligned qwords, post-alignment bytes + # If pre_count > 0, copy unaligned prefix bytes + test rax, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_copy_forward_loop # 2 cycles (predicted) + + # Extract and copy pre_count bytes (1-7 bytes to reach alignment) + mov rcx, rax # 1 cycle + and rcx, PRE_COUNT_MASK # 1 cycle - rcx = pre_count (bits 0-3) + + rep movsb # ~3-5 cycles per byte + # rsi, rdi now 8-byte aligned + +.L_copy_forward_loop: + # Copy aligned qwords (main bulk of data) + mov rcx, rax # 1 cycle + shr rcx, LOOP_COUNT_SBITS # 1 cycle - rcx = loop_count (bits 32-63) + rep movsq # ~1.5-2 cycles per qword (aligned, optimized) + # rsi, rdi advanced by loop_count * 8 + +.L_check_forward_post: + + # If post_count > 0, copy remaining unaligned suffix bytes + test rax, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_done # 2 cycles (predicted) + + # Extract and copy post_count bytes (1-7 bytes after aligned data) + mov rcx, rax # 1 cycle + shr rcx, POST_COUNT_SBITS # 1 cycle - shift post_count to position + and rcx, 0x07 # 1 cycle - rcx = post_count (bits 43-45) + + rep movsb # ~3-5 cycles per byte + # rsi, rdi now point past end of data + +.L_done: + ret # ~5 cycles + +# Performance estimate (Modern x86-64, L1 cache hits): +# +# NON-OVERLAPPING FORWARD COPY PATH: +# - fast_dma_encode call: ~15-20 cycles (function call + table lookup) +# - Write encoding to trace: ~4 cycles +# - Pre-value trace (conditional): ~12 cycles (if pre_count > 0) +# - Post-value trace (conditional): ~12 cycles (if post_count > 0) +# - Source data to trace: ~1.5-2 cycles per qword (rep movsq) +# - Pre-bytes copy: ~3-5 cycles per byte (if pre_count > 0, max 7 bytes) +# - Aligned qwords copy: ~1.5-2 cycles per qword (rep movsq, main data) +# - Post-bytes copy: ~3-5 cycles per byte (if post_count > 0, max 7 bytes) +# - Function overhead: ~10 cycles (push/pop, branches, return) +# +# TOTAL (best case, aligned, no pre/post): +# ~30 cycles base + ~2 cycles per qword (trace + copy) +# +# TOTAL (typical case, some alignment): +# ~50 cycles base + ~2 cycles per qword + ~4 cycles per pre/post byte +# +# OVERLAPPING BACKWARD COPY PATH: +# - Same trace overhead: ~30-50 cycles +# - std instruction: ~20-50 cycles (serializing, causes pipeline flush) +# - Backward byte copy: ~3-5 cycles per byte (rep movsb backward) +# - cld instruction: ~20-50 cycles (serializing, causes pipeline flush) +# +# TOTAL (overlap, worst case): +# ~100-150 cycles base + ~4-5 cycles per byte +# +# NOTES: +# - Assumes L1 cache hits for all memory accesses +# - rep movsq/movsb performance varies by microarchitecture +# - Actual cycles may vary ±20% depending on CPU model and memory alignment +# - Fast path (aligned, no overlap) is ~2-3x faster than overlap path + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/dma_constants.inc b/emulator-asm/src/dma/dma_constants.inc new file mode 100644 index 000000000..3b8dfb57d --- /dev/null +++ b/emulator-asm/src/dma/dma_constants.inc @@ -0,0 +1,59 @@ +.intel_syntax noprefix + +# GENERAL CONSTANTS + +.equ EXTRA_PARAMETER_ADDR, 0xA0000F00 + +# ENCODE CONSTANTS +# +# bits offset mask +# ---- ------ -------- +# pre_count: 0-7 3 0 0x0000_0000_0000_0007 +# post_count: 0-7 3 3 0x0000_0000_0000_0038 +# pre_writes: 0,1,2 2 6 0x0000_0000_0000_00C0 +# dst_offset: 0-7 3 8 0x0000_0000_0000_0700 +# src_offset: 0-7 3 11 0x0000_0000_0000_3800 +# double_src_pre: 0,1 1 14 0x0000_0000_0000_4000 +# double_src_post: 0,1 1 15 0x0000_0000_0000_8000 +# extra_src_reads: 0-3 2 16 0x0000_0000_0003_0000 +# src64_inc_by_pre: 1 18 0x0000_0000_0004_0000 +# unaligned_dst_src: 1 19 0x0000_0000_0008_0000 +# pre_count: 0-7 3 29 only for optimization PRE_AND_LOOP_BYTES +# loop_count 32 0 0xFFFF_FFFF_0000_0000 + +# BITS 31,32,32 + +.equ PRE_COUNT_MASK, 0x0000000000000007 +.equ POST_COUNT_MASK, 0x0000000000000038 +.equ PRE_WRITES_MASK, 0x00000000000000C0 +.equ DST_OFFSET_MASK, 0x0000000000000700 +.equ SRC_OFFSET_MASK, 0x0000000000003800 +.equ DOUBLE_SRC_PRE_MASK, 0x0000000000004000 +.equ DOUBLE_SRC_POST_MASK, 0x0000000000008000 +.equ EXTRA_SRC_READS_MASK, 0x0000000000030000 +.equ SRC64_INC_BY_PRE_MASK, 0x0000000000040000 +.equ UNALIGNED_DST_SRC_MASK, 0x0000000000080000 +.equ LOOP_COUNT_MASK, 0xFFFFFFFF00000000 +.equ ONLY_LOOP_COUNT_MASK, 0xFFFFFFFF00000000 + +# PRE_COUNT_SBITS 0 +.equ POST_COUNT_SBITS, 3 +.equ PRE_WRITES_SBITS, 6 +.equ DST_OFFSET_SBITS, 8 +.equ SRC_OFFSET_SBITS, 11 +.equ DOUBLE_SRC_PRE_SBITS, 14 +.equ DOUBLE_SRC_POST_SBITS, 15 +.equ EXTRA_SRC_READS_SBITS, 16 +.equ SRC64_INC_BY_PRE_SBITS, 18 +.equ UNALIGNED_DST_SRC_SBITS, 19 +.equ LOOP_COUNT_SBITS, 32 +.equ PRE_AND_LOOP_BYTES_SBITS, 29 +.equ ALIGN_MASK, 0xFFFFFFFFFFFFFFF8 +.equ MOPS_BLOCK_WORDS_SBITS, 36 +.equ MOPS_BLOCK_ONE_WORD, 0x0000001000000000 +.equ MOPS_BLOCK_READ, 0x0000000A00000000 +.equ MOPS_BLOCK_WRITE, 0x0000000B00000000 +.equ MOPS_ALIGNED_READ, 0x0000000C00000000 +.equ MOPS_ALIGNED_WRITE, 0x0000000D00000000 +.equ MOPS_ALIGNED_BLOCK_READ, 0x0000000E00000000 +.equ MOPS_ALIGNED_BLOCK_WRITE, 0x0000000F00000000 diff --git a/emulator-asm/src/dma/fast_dma_encode.asm b/emulator-asm/src/dma/fast_dma_encode.asm new file mode 100644 index 000000000..533fba3d2 --- /dev/null +++ b/emulator-asm/src/dma/fast_dma_encode.asm @@ -0,0 +1,74 @@ +.intel_syntax noprefix +.code64 + +################################################################################ +# fast_dma_encode - Optimized function to encode dma information +# +# REGISTER USAGE: +# Modified registers: rax, r8 +# Does NOT use XMM registers (caller doesn't need to save them) +# +# PARAMETERS (System V AMD64 ABI): +# rdi = dst (u64) - Destination address +# rsi = src (u64) - Source address +# rdx = count (usize) - Number of bytes to copy +# +# RETURN VALUE: +# rax = encoded value +# +# ENCODED METADATA (bits): +# 0-2: pre_count - Bytes to copy before alignment (0-7) +# 3-5: post_count - Bytes to copy after aligned chunks (0-7) +# 6-7: pre_writes - Number of pre/post partial writes (0, 1, or 2) +# 8-10: dst_offset - Byte offset within dst qword (0-7) +# 11-13: src_offset - Byte offset within src qword (0-7) +# 14: double_src_pre - Flag: pre-read spans two src qwords +# 15: double_src_post - Flag: post-read spans two src qwords +# 16-17: extra_src_reads - Additional src qword reads needed (0-3) +# 18: src64_inc_by_pre - Flag: indicate loop use src64 + 8 +# 19: unaligned_dst_src - Flag: dst and src has diferent alignement +# 32-63: loop_count - Number of 8-byte chunks in main copy loop +################################################################################ + +.global fast_dma_encode + +.section .text + +fast_dma_encode: + mov rax, rdi + and rax, 0x07 # dst_offset (0-7) + shl rax, 7 # dst_offset << 7 + + mov r8, rsi + and r8, 0x07 # src_offset (0-7) + shl r8, 4 # src_offset << 4 + + or rax, r8 # combine dst and src offsets + + # Calculate table_count + mov r8, rdx + cmp r8, 16 + jb .L_count_lt_16 + + # count >= 16: table_count = (count & 0x07) | 0x08 + and r8, 0x07 + or r8, 0x08 + +.L_count_lt_16: + or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count + + # Look up encoded value in table (direct access since it's in the same file) + mov rax, [fast_dma_encode_table + rax * 8] + + # Add (count >> 3) to result + mov r8, rdx + shl r8, 29 # r8 = count << 29 + add rax, r8 # result += (count << 29) + + ret + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits + +# Include the lookup table in the .rodata section +.include "fast_dma_encode_table.asm" diff --git a/emulator-asm/src/dma/fast_dma_encode_table.asm b/emulator-asm/src/dma/fast_dma_encode_table.asm new file mode 100644 index 000000000..86f837ccc --- /dev/null +++ b/emulator-asm/src/dma/fast_dma_encode_table.asm @@ -0,0 +1,264 @@ +.intel_syntax noprefix +.code64 + +.section .data +# generated with precompiles/helpers/src/dma +# asm_fast_encode_table test + +fast_dma_encode_table: + .quad 0x0000000000000000, 0xFFFFFFFFE0010048, 0xFFFFFFFFC0010050, 0xFFFFFFFFA0010058 # 0 - 3 D0 S0 C0 + .quad 0xffffffff80010060, 0xFFFFFFFF60010068, 0xFFFFFFFF40010070, 0xFFFFFFFF20010078 # 4 - 7 D0 S0 C4 + .quad 0x0000000000000000, 0xFFFFFFFFE0010048, 0xFFFFFFFFC0010050, 0xFFFFFFFFA0010058 # 8 - 11 D0 S0 C8 + .quad 0xffffffff80010060, 0xFFFFFFFF60010068, 0xFFFFFFFF40010070, 0xFFFFFFFF20010078 # 12 - 15 D0 S0 C12 + .quad 0x0000000000080800, 0xFFFFFFFFE0090848, 0xFFFFFFFFC0090850, 0xFFFFFFFFA0090858 # 16 - 19 D0 S1 C0 + .quad 0xffffffff80090860, 0xFFFFFFFF60090868, 0xFFFFFFFF40090870, 0xFFFFFFFF20090878 # 20 - 23 D0 S1 C4 + .quad 0x0000000000090800, 0xFFFFFFFFE0090848, 0xFFFFFFFFC0090850, 0xFFFFFFFFA0090858 # 24 - 27 D0 S1 C8 + .quad 0xffffffff80090860, 0xFFFFFFFF60090868, 0xFFFFFFFF40090870, 0xFFFFFFFF20090878 # 28 - 31 D0 S1 C12 + .quad 0x0000000000081000, 0xFFFFFFFFE0091048, 0xFFFFFFFFC0091050, 0xFFFFFFFFA0091058 # 32 - 35 D0 S2 C0 + .quad 0xffffffff80091060, 0xFFFFFFFF60091068, 0xFFFFFFFF40091070, 0xFFFFFFFF200A9078 # 36 - 39 D0 S2 C4 + .quad 0x0000000000091000, 0xFFFFFFFFE0091048, 0xFFFFFFFFC0091050, 0xFFFFFFFFA0091058 # 40 - 43 D0 S2 C8 + .quad 0xffffffff80091060, 0xFFFFFFFF60091068, 0xFFFFFFFF40091070, 0xFFFFFFFF200A9078 # 44 - 47 D0 S2 C12 + .quad 0x0000000000081800, 0xFFFFFFFFE0091848, 0xFFFFFFFFC0091850, 0xFFFFFFFFA0091858 # 48 - 51 D0 S3 C0 + .quad 0xffffffff80091860, 0xFFFFFFFF60091868, 0xFFFFFFFF400A9870, 0xFFFFFFFF200A9878 # 52 - 55 D0 S3 C4 + .quad 0x0000000000091800, 0xFFFFFFFFE0091848, 0xFFFFFFFFC0091850, 0xFFFFFFFFA0091858 # 56 - 59 D0 S3 C8 + .quad 0xffffffff80091860, 0xFFFFFFFF60091868, 0xFFFFFFFF400A9870, 0xFFFFFFFF200A9878 # 60 - 63 D0 S3 C12 + .quad 0x0000000000082000, 0xFFFFFFFFE0092048, 0xFFFFFFFFC0092050, 0xFFFFFFFFA0092058 # 64 - 67 D0 S4 C0 + .quad 0xffffffff80092060, 0xFFFFFFFF600AA068, 0xFFFFFFFF400AA070, 0xFFFFFFFF200AA078 # 68 - 71 D0 S4 C4 + .quad 0x0000000000092000, 0xFFFFFFFFE0092048, 0xFFFFFFFFC0092050, 0xFFFFFFFFA0092058 # 72 - 75 D0 S4 C8 + .quad 0xffffffff80092060, 0xFFFFFFFF600AA068, 0xFFFFFFFF400AA070, 0xFFFFFFFF200AA078 # 76 - 79 D0 S4 C12 + .quad 0x0000000000082800, 0xFFFFFFFFE0092848, 0xFFFFFFFFC0092850, 0xFFFFFFFFA0092858 # 80 - 83 D0 S5 C0 + .quad 0xffffffff800aa860, 0xFFFFFFFF600AA868, 0xFFFFFFFF400AA870, 0xFFFFFFFF200AA878 # 84 - 87 D0 S5 C4 + .quad 0x0000000000092800, 0xFFFFFFFFE0092848, 0xFFFFFFFFC0092850, 0xFFFFFFFFA0092858 # 88 - 91 D0 S5 C8 + .quad 0xffffffff800aa860, 0xFFFFFFFF600AA868, 0xFFFFFFFF400AA870, 0xFFFFFFFF200AA878 # 92 - 95 D0 S5 C12 + .quad 0x0000000000083000, 0xFFFFFFFFE0093048, 0xFFFFFFFFC0093050, 0xFFFFFFFFA00AB058 # 96 - 99 D0 S6 C0 + .quad 0xffffffff800ab060, 0xFFFFFFFF600AB068, 0xFFFFFFFF400AB070, 0xFFFFFFFF200AB078 # 100 - 103 D0 S6 C4 + .quad 0x0000000000093000, 0xFFFFFFFFE0093048, 0xFFFFFFFFC0093050, 0xFFFFFFFFA00AB058 # 104 - 107 D0 S6 C8 + .quad 0xffffffff800ab060, 0xFFFFFFFF600AB068, 0xFFFFFFFF400AB070, 0xFFFFFFFF200AB078 # 108 - 111 D0 S6 C12 + .quad 0x0000000000083800, 0xFFFFFFFFE0093848, 0xFFFFFFFFC00AB850, 0xFFFFFFFFA00AB858 # 112 - 115 D0 S7 C0 + .quad 0xffffffff800ab860, 0xFFFFFFFF600AB868, 0xFFFFFFFF400AB870, 0xFFFFFFFF200AB878 # 116 - 119 D0 S7 C4 + .quad 0x0000000000093800, 0xFFFFFFFFE0093848, 0xFFFFFFFFC00AB850, 0xFFFFFFFFA00AB858 # 120 - 123 D0 S7 C8 + .quad 0xffffffff800ab860, 0xFFFFFFFF600AB868, 0xFFFFFFFF400AB870, 0xFFFFFFFF200AB878 # 124 - 127 D0 S7 C12 + .quad 0x0000000000080100, 0x0000000000090141, 0x0000000000090142, 0x0000000000090143 # 128 - 131 D1 S0 C0 + .quad 0x0000000000090144, 0x0000000000090145, 0x0000000000090146, 0x0000000000090147 # 132 - 135 D1 S0 C4 + .quad 0xffffffffe009018f, 0xFFFFFFFFC00A8197, 0xFFFFFFFFA00A819F, 0xFFFFFFFF800A81A7 # 136 - 139 D1 S0 C8 + .quad 0xffffffff600a81af, 0xFFFFFFFF400A81B7, 0xFFFFFFFF200A81BF, 0x0000000000090147 # 140 - 143 D1 S0 C12 + .quad 0x0000000000000900, 0x0000000000010941, 0x0000000000010942, 0x0000000000010943 # 144 - 147 D1 S1 C0 + .quad 0x0000000000010944, 0x0000000000010945, 0x0000000000010946, 0x0000000000050947 # 148 - 151 D1 S1 C4 + .quad 0xffffffffe006098f, 0xFFFFFFFFC0060997, 0xFFFFFFFFA006099F, 0xFFFFFFFF800609A7 # 152 - 155 D1 S1 C8 + .quad 0xffffffff600609af, 0xFFFFFFFF400609B7, 0xFFFFFFFF200609BF, 0x0000000000050947 # 156 - 159 D1 S1 C12 + .quad 0x0000000000081100, 0x0000000000091141, 0x0000000000091142, 0x0000000000091143 # 160 - 163 D1 S2 C0 + .quad 0x0000000000091144, 0x0000000000091145, 0x00000000000D1146, 0x00000000000E5147 # 164 - 167 D1 S2 C4 + .quad 0xffffffffe00e518f, 0xFFFFFFFFC00E5197, 0xFFFFFFFFA00E519F, 0xFFFFFFFF800E51A7 # 168 - 171 D1 S2 C8 + .quad 0xffffffff600e51af, 0xFFFFFFFF400E51B7, 0xFFFFFFFF200E51BF, 0x00000000000E5147 # 172 - 175 D1 S2 C12 + .quad 0x0000000000081900, 0x0000000000091941, 0x0000000000091942, 0x0000000000091943 # 176 - 179 D1 S3 C0 + .quad 0x0000000000091944, 0x00000000000D1945, 0x00000000000E5946, 0x00000000000E5947 # 180 - 183 D1 S3 C4 + .quad 0xffffffffe00e598f, 0xFFFFFFFFC00E5997, 0xFFFFFFFFA00E599F, 0xFFFFFFFF800E59A7 # 184 - 187 D1 S3 C8 + .quad 0xffffffff600e59af, 0xFFFFFFFF400E59B7, 0xFFFFFFFF200FD9BF, 0x00000000000E5947 # 188 - 191 D1 S3 C12 + .quad 0x0000000000082100, 0x0000000000092141, 0x0000000000092142, 0x0000000000092143 # 192 - 195 D1 S4 C0 + .quad 0x00000000000d2144, 0x00000000000E6145, 0x00000000000E6146, 0x00000000000E6147 # 196 - 199 D1 S4 C4 + .quad 0xffffffffe00e618f, 0xFFFFFFFFC00E6197, 0xFFFFFFFFA00E619F, 0xFFFFFFFF800E61A7 # 200 - 203 D1 S4 C8 + .quad 0xffffffff600e61af, 0xFFFFFFFF400FE1B7, 0xFFFFFFFF200FE1BF, 0x00000000000E6147 # 204 - 207 D1 S4 C12 + .quad 0x0000000000082900, 0x0000000000092941, 0x0000000000092942, 0x00000000000D2943 # 208 - 211 D1 S5 C0 + .quad 0x00000000000e6944, 0x00000000000E6945, 0x00000000000E6946, 0x00000000000E6947 # 212 - 215 D1 S5 C4 + .quad 0xffffffffe00e698f, 0xFFFFFFFFC00E6997, 0xFFFFFFFFA00E699F, 0xFFFFFFFF800E69A7 # 216 - 219 D1 S5 C8 + .quad 0xffffffff600fe9af, 0xFFFFFFFF400FE9B7, 0xFFFFFFFF200FE9BF, 0x00000000000E6947 # 220 - 223 D1 S5 C12 + .quad 0x0000000000083100, 0x0000000000093141, 0x00000000000D3142, 0x00000000000E7143 # 224 - 227 D1 S6 C0 + .quad 0x00000000000e7144, 0x00000000000E7145, 0x00000000000E7146, 0x00000000000E7147 # 228 - 231 D1 S6 C4 + .quad 0xffffffffe00e718f, 0xFFFFFFFFC00E7197, 0xFFFFFFFFA00E719F, 0xFFFFFFFF800FF1A7 # 232 - 235 D1 S6 C8 + .quad 0xffffffff600ff1af, 0xFFFFFFFF400FF1B7, 0xFFFFFFFF200FF1BF, 0x00000000000E7147 # 236 - 239 D1 S6 C12 + .quad 0x0000000000083900, 0x00000000000D3941, 0x00000000000E7942, 0x00000000000E7943 # 240 - 243 D1 S7 C0 + .quad 0x00000000000e7944, 0x00000000000E7945, 0x00000000000E7946, 0x00000000000E7947 # 244 - 247 D1 S7 C4 + .quad 0xffffffffe00e798f, 0xFFFFFFFFC00E7997, 0xFFFFFFFFA00FF99F, 0xFFFFFFFF800FF9A7 # 248 - 251 D1 S7 C8 + .quad 0xffffffff600ff9af, 0xFFFFFFFF400FF9B7, 0xFFFFFFFF200FF9BF, 0x00000000000E7947 # 252 - 255 D1 S7 C12 + .quad 0x0000000000080200, 0x0000000000090241, 0x0000000000090242, 0x0000000000090243 # 256 - 259 D2 S0 C0 + .quad 0x0000000000090244, 0x0000000000090245, 0x0000000000090246, 0xFFFFFFFFE009028E # 260 - 263 D2 S0 C4 + .quad 0xffffffffc0090296, 0xFFFFFFFFA00A829E, 0xFFFFFFFF800A82A6, 0xFFFFFFFF600A82AE # 264 - 267 D2 S0 C8 + .quad 0xffffffff400a82b6, 0xFFFFFFFF200A82BE, 0x0000000000090246, 0xFFFFFFFFE009028E # 268 - 271 D2 S0 C12 + .quad 0x0000000000080a00, 0x0000000000090A41, 0x0000000000090A42, 0x0000000000090A43 # 272 - 275 D2 S1 C0 + .quad 0x0000000000090a44, 0x0000000000090A45, 0x0000000000090A46, 0xFFFFFFFFE0090A8E # 276 - 279 D2 S1 C4 + .quad 0xffffffffc00a8a96, 0xFFFFFFFFA00A8A9E, 0xFFFFFFFF800A8AA6, 0xFFFFFFFF600A8AAE # 280 - 283 D2 S1 C8 + .quad 0xffffffff400a8ab6, 0xFFFFFFFF200A8ABE, 0x0000000000090A46, 0xFFFFFFFFE0090A8E # 284 - 287 D2 S1 C12 + .quad 0x0000000000001200, 0x0000000000011241, 0x0000000000011242, 0x0000000000011243 # 288 - 291 D2 S2 C0 + .quad 0x0000000000011244, 0x0000000000011245, 0x0000000000051246, 0xFFFFFFFFE006128E # 292 - 295 D2 S2 C4 + .quad 0xffffffffc0061296, 0xFFFFFFFFA006129E, 0xFFFFFFFF800612A6, 0xFFFFFFFF600612AE # 296 - 299 D2 S2 C8 + .quad 0xffffffff400612b6, 0xFFFFFFFF200612BE, 0x0000000000051246, 0xFFFFFFFFE006128E # 300 - 303 D2 S2 C12 + .quad 0x0000000000081a00, 0x0000000000091A41, 0x0000000000091A42, 0x0000000000091A43 # 304 - 307 D2 S3 C0 + .quad 0x0000000000091a44, 0x00000000000D1A45, 0x00000000000E5A46, 0xFFFFFFFFE00E5A8E # 308 - 311 D2 S3 C4 + .quad 0xffffffffc00e5a96, 0xFFFFFFFFA00E5A9E, 0xFFFFFFFF800E5AA6, 0xFFFFFFFF600E5AAE # 312 - 315 D2 S3 C8 + .quad 0xffffffff400e5ab6, 0xFFFFFFFF200E5ABE, 0x00000000000E5A46, 0xFFFFFFFFE00E5A8E # 316 - 319 D2 S3 C12 + .quad 0x0000000000082200, 0x0000000000092241, 0x0000000000092242, 0x0000000000092243 # 320 - 323 D2 S4 C0 + .quad 0x00000000000d2244, 0x00000000000E6245, 0x00000000000E6246, 0xFFFFFFFFE00E628E # 324 - 327 D2 S4 C4 + .quad 0xffffffffc00e6296, 0xFFFFFFFFA00E629E, 0xFFFFFFFF800E62A6, 0xFFFFFFFF600E62AE # 328 - 331 D2 S4 C8 + .quad 0xffffffff400e62b6, 0xFFFFFFFF200FE2BE, 0x00000000000E6246, 0xFFFFFFFFE00E628E # 332 - 335 D2 S4 C12 + .quad 0x0000000000082a00, 0x0000000000092A41, 0x0000000000092A42, 0x00000000000D2A43 # 336 - 339 D2 S5 C0 + .quad 0x00000000000e6a44, 0x00000000000E6A45, 0x00000000000E6A46, 0xFFFFFFFFE00E6A8E # 340 - 343 D2 S5 C4 + .quad 0xffffffffc00e6a96, 0xFFFFFFFFA00E6A9E, 0xFFFFFFFF800E6AA6, 0xFFFFFFFF600E6AAE # 344 - 347 D2 S5 C8 + .quad 0xffffffff400feab6, 0xFFFFFFFF200FEABE, 0x00000000000E6A46, 0xFFFFFFFFE00E6A8E # 348 - 351 D2 S5 C12 + .quad 0x0000000000083200, 0x0000000000093241, 0x00000000000D3242, 0x00000000000E7243 # 352 - 355 D2 S6 C0 + .quad 0x00000000000e7244, 0x00000000000E7245, 0x00000000000E7246, 0xFFFFFFFFE00E728E # 356 - 359 D2 S6 C4 + .quad 0xffffffffc00e7296, 0xFFFFFFFFA00E729E, 0xFFFFFFFF800E72A6, 0xFFFFFFFF600FF2AE # 360 - 363 D2 S6 C8 + .quad 0xffffffff400ff2b6, 0xFFFFFFFF200FF2BE, 0x00000000000E7246, 0xFFFFFFFFE00E728E # 364 - 367 D2 S6 C12 + .quad 0x0000000000083a00, 0x00000000000D3A41, 0x00000000000E7A42, 0x00000000000E7A43 # 368 - 371 D2 S7 C0 + .quad 0x00000000000e7a44, 0x00000000000E7A45, 0x00000000000E7A46, 0xFFFFFFFFE00E7A8E # 372 - 375 D2 S7 C4 + .quad 0xffffffffc00e7a96, 0xFFFFFFFFA00E7A9E, 0xFFFFFFFF800FFAA6, 0xFFFFFFFF600FFAAE # 376 - 379 D2 S7 C8 + .quad 0xffffffff400ffab6, 0xFFFFFFFF200FFABE, 0x00000000000E7A46, 0xFFFFFFFFE00E7A8E # 380 - 383 D2 S7 C12 + .quad 0x0000000000080300, 0x0000000000090341, 0x0000000000090342, 0x0000000000090343 # 384 - 387 D3 S0 C0 + .quad 0x0000000000090344, 0x0000000000090345, 0xFFFFFFFFE009038D, 0xFFFFFFFFC0090395 # 388 - 391 D3 S0 C4 + .quad 0xffffffffa009039d, 0xFFFFFFFF800A83A5, 0xFFFFFFFF600A83AD, 0xFFFFFFFF400A83B5 # 392 - 395 D3 S0 C8 + .quad 0xffffffff200a83bd, 0x0000000000090345, 0xFFFFFFFFE009038D, 0xFFFFFFFFC0090395 # 396 - 399 D3 S0 C12 + .quad 0x0000000000080b00, 0x0000000000090B41, 0x0000000000090B42, 0x0000000000090B43 # 400 - 403 D3 S1 C0 + .quad 0x0000000000090b44, 0x0000000000090B45, 0xFFFFFFFFE0090B8D, 0xFFFFFFFFC0090B95 # 404 - 407 D3 S1 C4 + .quad 0xffffffffa00a8b9d, 0xFFFFFFFF800A8BA5, 0xFFFFFFFF600A8BAD, 0xFFFFFFFF400A8BB5 # 408 - 411 D3 S1 C8 + .quad 0xffffffff200a8bbd, 0x0000000000090B45, 0xFFFFFFFFE0090B8D, 0xFFFFFFFFC0090B95 # 412 - 415 D3 S1 C12 + .quad 0x0000000000081300, 0x0000000000091341, 0x0000000000091342, 0x0000000000091343 # 416 - 419 D3 S2 C0 + .quad 0x0000000000091344, 0x0000000000091345, 0xFFFFFFFFE009138D, 0xFFFFFFFFC00A9395 # 420 - 423 D3 S2 C4 + .quad 0xffffffffa00a939d, 0xFFFFFFFF800A93A5, 0xFFFFFFFF600A93AD, 0xFFFFFFFF400A93B5 # 424 - 427 D3 S2 C8 + .quad 0xffffffff200a93bd, 0x0000000000091345, 0xFFFFFFFFE009138D, 0xFFFFFFFFC00A9395 # 428 - 431 D3 S2 C12 + .quad 0x0000000000001b00, 0x0000000000011B41, 0x0000000000011B42, 0x0000000000011B43 # 432 - 435 D3 S3 C0 + .quad 0x0000000000011b44, 0x0000000000051B45, 0xFFFFFFFFE0061B8D, 0xFFFFFFFFC0061B95 # 436 - 439 D3 S3 C4 + .quad 0xffffffffa0061b9d, 0xFFFFFFFF80061BA5, 0xFFFFFFFF60061BAD, 0xFFFFFFFF40061BB5 # 440 - 443 D3 S3 C8 + .quad 0xffffffff20061bbd, 0x0000000000051B45, 0xFFFFFFFFE0061B8D, 0xFFFFFFFFC0061B95 # 444 - 447 D3 S3 C12 + .quad 0x0000000000082300, 0x0000000000092341, 0x0000000000092342, 0x0000000000092343 # 448 - 451 D3 S4 C0 + .quad 0x00000000000d2344, 0x00000000000E6345, 0xFFFFFFFFE00E638D, 0xFFFFFFFFC00E6395 # 452 - 455 D3 S4 C4 + .quad 0xffffffffa00e639d, 0xFFFFFFFF800E63A5, 0xFFFFFFFF600E63AD, 0xFFFFFFFF400E63B5 # 456 - 459 D3 S4 C8 + .quad 0xffffffff200e63bd, 0x00000000000E6345, 0xFFFFFFFFE00E638D, 0xFFFFFFFFC00E6395 # 460 - 463 D3 S4 C12 + .quad 0x0000000000082b00, 0x0000000000092B41, 0x0000000000092B42, 0x00000000000D2B43 # 464 - 467 D3 S5 C0 + .quad 0x00000000000e6b44, 0x00000000000E6B45, 0xFFFFFFFFE00E6B8D, 0xFFFFFFFFC00E6B95 # 468 - 471 D3 S5 C4 + .quad 0xffffffffa00e6b9d, 0xFFFFFFFF800E6BA5, 0xFFFFFFFF600E6BAD, 0xFFFFFFFF400E6BB5 # 472 - 475 D3 S5 C8 + .quad 0xffffffff200febbd, 0x00000000000E6B45, 0xFFFFFFFFE00E6B8D, 0xFFFFFFFFC00E6B95 # 476 - 479 D3 S5 C12 + .quad 0x0000000000083300, 0x0000000000093341, 0x00000000000D3342, 0x00000000000E7343 # 480 - 483 D3 S6 C0 + .quad 0x00000000000e7344, 0x00000000000E7345, 0xFFFFFFFFE00E738D, 0xFFFFFFFFC00E7395 # 484 - 487 D3 S6 C4 + .quad 0xffffffffa00e739d, 0xFFFFFFFF800E73A5, 0xFFFFFFFF600E73AD, 0xFFFFFFFF400FF3B5 # 488 - 491 D3 S6 C8 + .quad 0xffffffff200ff3bd, 0x00000000000E7345, 0xFFFFFFFFE00E738D, 0xFFFFFFFFC00E7395 # 492 - 495 D3 S6 C12 + .quad 0x0000000000083b00, 0x00000000000D3B41, 0x00000000000E7B42, 0x00000000000E7B43 # 496 - 499 D3 S7 C0 + .quad 0x00000000000e7b44, 0x00000000000E7B45, 0xFFFFFFFFE00E7B8D, 0xFFFFFFFFC00E7B95 # 500 - 503 D3 S7 C4 + .quad 0xffffffffa00e7b9d, 0xFFFFFFFF800E7BA5, 0xFFFFFFFF600FFBAD, 0xFFFFFFFF400FFBB5 # 504 - 507 D3 S7 C8 + .quad 0xffffffff200ffbbd, 0x00000000000E7B45, 0xFFFFFFFFE00E7B8D, 0xFFFFFFFFC00E7B95 # 508 - 511 D3 S7 C12 + .quad 0x0000000000080400, 0x0000000000090441, 0x0000000000090442, 0x0000000000090443 # 512 - 515 D4 S0 C0 + .quad 0x0000000000090444, 0xFFFFFFFFE009048C, 0xFFFFFFFFC0090494, 0xFFFFFFFFA009049C # 516 - 519 D4 S0 C4 + .quad 0xffffffff800904a4, 0xFFFFFFFF600A84AC, 0xFFFFFFFF400A84B4, 0xFFFFFFFF200A84BC # 520 - 523 D4 S0 C8 + .quad 0x0000000000090444, 0xFFFFFFFFE009048C, 0xFFFFFFFFC0090494, 0xFFFFFFFFA009049C # 524 - 527 D4 S0 C12 + .quad 0x0000000000080c00, 0x0000000000090C41, 0x0000000000090C42, 0x0000000000090C43 # 528 - 531 D4 S1 C0 + .quad 0x0000000000090c44, 0xFFFFFFFFE0090C8C, 0xFFFFFFFFC0090C94, 0xFFFFFFFFA0090C9C # 532 - 535 D4 S1 C4 + .quad 0xffffffff800a8ca4, 0xFFFFFFFF600A8CAC, 0xFFFFFFFF400A8CB4, 0xFFFFFFFF200A8CBC # 536 - 539 D4 S1 C8 + .quad 0x0000000000090c44, 0xFFFFFFFFE0090C8C, 0xFFFFFFFFC0090C94, 0xFFFFFFFFA0090C9C # 540 - 543 D4 S1 C12 + .quad 0x0000000000081400, 0x0000000000091441, 0x0000000000091442, 0x0000000000091443 # 544 - 547 D4 S2 C0 + .quad 0x0000000000091444, 0xFFFFFFFFE009148C, 0xFFFFFFFFC0091494, 0xFFFFFFFFA00A949C # 548 - 551 D4 S2 C4 + .quad 0xffffffff800a94a4, 0xFFFFFFFF600A94AC, 0xFFFFFFFF400A94B4, 0xFFFFFFFF200A94BC # 552 - 555 D4 S2 C8 + .quad 0x0000000000091444, 0xFFFFFFFFE009148C, 0xFFFFFFFFC0091494, 0xFFFFFFFFA00A949C # 556 - 559 D4 S2 C12 + .quad 0x0000000000081c00, 0x0000000000091C41, 0x0000000000091C42, 0x0000000000091C43 # 560 - 563 D4 S3 C0 + .quad 0x0000000000091c44, 0xFFFFFFFFE0091C8C, 0xFFFFFFFFC00A9C94, 0xFFFFFFFFA00A9C9C # 564 - 567 D4 S3 C4 + .quad 0xffffffff800a9ca4, 0xFFFFFFFF600A9CAC, 0xFFFFFFFF400A9CB4, 0xFFFFFFFF200A9CBC # 568 - 571 D4 S3 C8 + .quad 0x0000000000091c44, 0xFFFFFFFFE0091C8C, 0xFFFFFFFFC00A9C94, 0xFFFFFFFFA00A9C9C # 572 - 575 D4 S3 C12 + .quad 0x0000000000002400, 0x0000000000012441, 0x0000000000012442, 0x0000000000012443 # 576 - 579 D4 S4 C0 + .quad 0x0000000000052444, 0xFFFFFFFFE006248C, 0xFFFFFFFFC0062494, 0xFFFFFFFFA006249C # 580 - 583 D4 S4 C4 + .quad 0xffffffff800624a4, 0xFFFFFFFF600624AC, 0xFFFFFFFF400624B4, 0xFFFFFFFF200624BC # 584 - 587 D4 S4 C8 + .quad 0x0000000000052444, 0xFFFFFFFFE006248C, 0xFFFFFFFFC0062494, 0xFFFFFFFFA006249C # 588 - 591 D4 S4 C12 + .quad 0x0000000000082c00, 0x0000000000092C41, 0x0000000000092C42, 0x00000000000D2C43 # 592 - 595 D4 S5 C0 + .quad 0x00000000000e6c44, 0xFFFFFFFFE00E6C8C, 0xFFFFFFFFC00E6C94, 0xFFFFFFFFA00E6C9C # 596 - 599 D4 S5 C4 + .quad 0xffffffff800e6ca4, 0xFFFFFFFF600E6CAC, 0xFFFFFFFF400E6CB4, 0xFFFFFFFF200E6CBC # 600 - 603 D4 S5 C8 + .quad 0x00000000000e6c44, 0xFFFFFFFFE00E6C8C, 0xFFFFFFFFC00E6C94, 0xFFFFFFFFA00E6C9C # 604 - 607 D4 S5 C12 + .quad 0x0000000000083400, 0x0000000000093441, 0x00000000000D3442, 0x00000000000E7443 # 608 - 611 D4 S6 C0 + .quad 0x00000000000e7444, 0xFFFFFFFFE00E748C, 0xFFFFFFFFC00E7494, 0xFFFFFFFFA00E749C # 612 - 615 D4 S6 C4 + .quad 0xffffffff800e74a4, 0xFFFFFFFF600E74AC, 0xFFFFFFFF400E74B4, 0xFFFFFFFF200FF4BC # 616 - 619 D4 S6 C8 + .quad 0x00000000000e7444, 0xFFFFFFFFE00E748C, 0xFFFFFFFFC00E7494, 0xFFFFFFFFA00E749C # 620 - 623 D4 S6 C12 + .quad 0x0000000000083c00, 0x00000000000D3C41, 0x00000000000E7C42, 0x00000000000E7C43 # 624 - 627 D4 S7 C0 + .quad 0x00000000000e7c44, 0xFFFFFFFFE00E7C8C, 0xFFFFFFFFC00E7C94, 0xFFFFFFFFA00E7C9C # 628 - 631 D4 S7 C4 + .quad 0xffffffff800e7ca4, 0xFFFFFFFF600E7CAC, 0xFFFFFFFF400FFCB4, 0xFFFFFFFF200FFCBC # 632 - 635 D4 S7 C8 + .quad 0x00000000000e7c44, 0xFFFFFFFFE00E7C8C, 0xFFFFFFFFC00E7C94, 0xFFFFFFFFA00E7C9C # 636 - 639 D4 S7 C12 + .quad 0x0000000000080500, 0x0000000000090541, 0x0000000000090542, 0x0000000000090543 # 640 - 643 D5 S0 C0 + .quad 0xffffffffe009058b, 0xFFFFFFFFC0090593, 0xFFFFFFFFA009059B, 0xFFFFFFFF800905A3 # 644 - 647 D5 S0 C4 + .quad 0xffffffff600905ab, 0xFFFFFFFF400A85B3, 0xFFFFFFFF200A85BB, 0x0000000000090543 # 648 - 651 D5 S0 C8 + .quad 0xffffffffe009058b, 0xFFFFFFFFC0090593, 0xFFFFFFFFA009059B, 0xFFFFFFFF800905A3 # 652 - 655 D5 S0 C12 + .quad 0x0000000000080d00, 0x0000000000090D41, 0x0000000000090D42, 0x0000000000090D43 # 656 - 659 D5 S1 C0 + .quad 0xffffffffe0090d8b, 0xFFFFFFFFC0090D93, 0xFFFFFFFFA0090D9B, 0xFFFFFFFF80090DA3 # 660 - 663 D5 S1 C4 + .quad 0xffffffff600a8dab, 0xFFFFFFFF400A8DB3, 0xFFFFFFFF200A8DBB, 0x0000000000090D43 # 664 - 667 D5 S1 C8 + .quad 0xffffffffe0090d8b, 0xFFFFFFFFC0090D93, 0xFFFFFFFFA0090D9B, 0xFFFFFFFF80090DA3 # 668 - 671 D5 S1 C12 + .quad 0x0000000000081500, 0x0000000000091541, 0x0000000000091542, 0x0000000000091543 # 672 - 675 D5 S2 C0 + .quad 0xffffffffe009158b, 0xFFFFFFFFC0091593, 0xFFFFFFFFA009159B, 0xFFFFFFFF800A95A3 # 676 - 679 D5 S2 C4 + .quad 0xffffffff600a95ab, 0xFFFFFFFF400A95B3, 0xFFFFFFFF200A95BB, 0x0000000000091543 # 680 - 683 D5 S2 C8 + .quad 0xffffffffe009158b, 0xFFFFFFFFC0091593, 0xFFFFFFFFA009159B, 0xFFFFFFFF800A95A3 # 684 - 687 D5 S2 C12 + .quad 0x0000000000081d00, 0x0000000000091D41, 0x0000000000091D42, 0x0000000000091D43 # 688 - 691 D5 S3 C0 + .quad 0xffffffffe0091d8b, 0xFFFFFFFFC0091D93, 0xFFFFFFFFA00A9D9B, 0xFFFFFFFF800A9DA3 # 692 - 695 D5 S3 C4 + .quad 0xffffffff600a9dab, 0xFFFFFFFF400A9DB3, 0xFFFFFFFF200A9DBB, 0x0000000000091D43 # 696 - 699 D5 S3 C8 + .quad 0xffffffffe0091d8b, 0xFFFFFFFFC0091D93, 0xFFFFFFFFA00A9D9B, 0xFFFFFFFF800A9DA3 # 700 - 703 D5 S3 C12 + .quad 0x0000000000082500, 0x0000000000092541, 0x0000000000092542, 0x0000000000092543 # 704 - 707 D5 S4 C0 + .quad 0xffffffffe009258b, 0xFFFFFFFFC00AA593, 0xFFFFFFFFA00AA59B, 0xFFFFFFFF800AA5A3 # 708 - 711 D5 S4 C4 + .quad 0xffffffff600aa5ab, 0xFFFFFFFF400AA5B3, 0xFFFFFFFF200AA5BB, 0x0000000000092543 # 712 - 715 D5 S4 C8 + .quad 0xffffffffe009258b, 0xFFFFFFFFC00AA593, 0xFFFFFFFFA00AA59B, 0xFFFFFFFF800AA5A3 # 716 - 719 D5 S4 C12 + .quad 0x0000000000002d00, 0x0000000000012D41, 0x0000000000012D42, 0x0000000000052D43 # 720 - 723 D5 S5 C0 + .quad 0xffffffffe0062d8b, 0xFFFFFFFFC0062D93, 0xFFFFFFFFA0062D9B, 0xFFFFFFFF80062DA3 # 724 - 727 D5 S5 C4 + .quad 0xffffffff60062dab, 0xFFFFFFFF40062DB3, 0xFFFFFFFF20062DBB, 0x0000000000052D43 # 728 - 731 D5 S5 C8 + .quad 0xffffffffe0062d8b, 0xFFFFFFFFC0062D93, 0xFFFFFFFFA0062D9B, 0xFFFFFFFF80062DA3 # 732 - 735 D5 S5 C12 + .quad 0x0000000000083500, 0x0000000000093541, 0x00000000000D3542, 0x00000000000E7543 # 736 - 739 D5 S6 C0 + .quad 0xffffffffe00e758b, 0xFFFFFFFFC00E7593, 0xFFFFFFFFA00E759B, 0xFFFFFFFF800E75A3 # 740 - 743 D5 S6 C4 + .quad 0xffffffff600e75ab, 0xFFFFFFFF400E75B3, 0xFFFFFFFF200E75BB, 0x00000000000E7543 # 744 - 747 D5 S6 C8 + .quad 0xffffffffe00e758b, 0xFFFFFFFFC00E7593, 0xFFFFFFFFA00E759B, 0xFFFFFFFF800E75A3 # 748 - 751 D5 S6 C12 + .quad 0x0000000000083d00, 0x00000000000D3D41, 0x00000000000E7D42, 0x00000000000E7D43 # 752 - 755 D5 S7 C0 + .quad 0xffffffffe00e7d8b, 0xFFFFFFFFC00E7D93, 0xFFFFFFFFA00E7D9B, 0xFFFFFFFF800E7DA3 # 756 - 759 D5 S7 C4 + .quad 0xffffffff600e7dab, 0xFFFFFFFF400E7DB3, 0xFFFFFFFF200FFDBB, 0x00000000000E7D43 # 760 - 763 D5 S7 C8 + .quad 0xffffffffe00e7d8b, 0xFFFFFFFFC00E7D93, 0xFFFFFFFFA00E7D9B, 0xFFFFFFFF800E7DA3 # 764 - 767 D5 S7 C12 + .quad 0x0000000000080600, 0x0000000000090641, 0x0000000000090642, 0xFFFFFFFFE009068A # 768 - 771 D6 S0 C0 + .quad 0xffffffffc0090692, 0xFFFFFFFFA009069A, 0xFFFFFFFF800906A2, 0xFFFFFFFF600906AA # 772 - 775 D6 S0 C4 + .quad 0xffffffff400906b2, 0xFFFFFFFF200A86BA, 0x0000000000090642, 0xFFFFFFFFE009068A # 776 - 779 D6 S0 C8 + .quad 0xffffffffc0090692, 0xFFFFFFFFA009069A, 0xFFFFFFFF800906A2, 0xFFFFFFFF600906AA # 780 - 783 D6 S0 C12 + .quad 0x0000000000080e00, 0x0000000000090E41, 0x0000000000090E42, 0xFFFFFFFFE0090E8A # 784 - 787 D6 S1 C0 + .quad 0xffffffffc0090e92, 0xFFFFFFFFA0090E9A, 0xFFFFFFFF80090EA2, 0xFFFFFFFF60090EAA # 788 - 791 D6 S1 C4 + .quad 0xffffffff400a8eb2, 0xFFFFFFFF200A8EBA, 0x0000000000090E42, 0xFFFFFFFFE0090E8A # 792 - 795 D6 S1 C8 + .quad 0xffffffffc0090e92, 0xFFFFFFFFA0090E9A, 0xFFFFFFFF80090EA2, 0xFFFFFFFF60090EAA # 796 - 799 D6 S1 C12 + .quad 0x0000000000081600, 0x0000000000091641, 0x0000000000091642, 0xFFFFFFFFE009168A # 800 - 803 D6 S2 C0 + .quad 0xffffffffc0091692, 0xFFFFFFFFA009169A, 0xFFFFFFFF800916A2, 0xFFFFFFFF600A96AA # 804 - 807 D6 S2 C4 + .quad 0xffffffff400a96b2, 0xFFFFFFFF200A96BA, 0x0000000000091642, 0xFFFFFFFFE009168A # 808 - 811 D6 S2 C8 + .quad 0xffffffffc0091692, 0xFFFFFFFFA009169A, 0xFFFFFFFF800916A2, 0xFFFFFFFF600A96AA # 812 - 815 D6 S2 C12 + .quad 0x0000000000081e00, 0x0000000000091E41, 0x0000000000091E42, 0xFFFFFFFFE0091E8A # 816 - 819 D6 S3 C0 + .quad 0xffffffffc0091e92, 0xFFFFFFFFA0091E9A, 0xFFFFFFFF800A9EA2, 0xFFFFFFFF600A9EAA # 820 - 823 D6 S3 C4 + .quad 0xffffffff400a9eb2, 0xFFFFFFFF200A9EBA, 0x0000000000091E42, 0xFFFFFFFFE0091E8A # 824 - 827 D6 S3 C8 + .quad 0xffffffffc0091e92, 0xFFFFFFFFA0091E9A, 0xFFFFFFFF800A9EA2, 0xFFFFFFFF600A9EAA # 828 - 831 D6 S3 C12 + .quad 0x0000000000082600, 0x0000000000092641, 0x0000000000092642, 0xFFFFFFFFE009268A # 832 - 835 D6 S4 C0 + .quad 0xffffffffc0092692, 0xFFFFFFFFA00AA69A, 0xFFFFFFFF800AA6A2, 0xFFFFFFFF600AA6AA # 836 - 839 D6 S4 C4 + .quad 0xffffffff400aa6b2, 0xFFFFFFFF200AA6BA, 0x0000000000092642, 0xFFFFFFFFE009268A # 840 - 843 D6 S4 C8 + .quad 0xffffffffc0092692, 0xFFFFFFFFA00AA69A, 0xFFFFFFFF800AA6A2, 0xFFFFFFFF600AA6AA # 844 - 847 D6 S4 C12 + .quad 0x0000000000082e00, 0x0000000000092E41, 0x0000000000092E42, 0xFFFFFFFFE0092E8A # 848 - 851 D6 S5 C0 + .quad 0xffffffffc00aae92, 0xFFFFFFFFA00AAE9A, 0xFFFFFFFF800AAEA2, 0xFFFFFFFF600AAEAA # 852 - 855 D6 S5 C4 + .quad 0xffffffff400aaeb2, 0xFFFFFFFF200AAEBA, 0x0000000000092E42, 0xFFFFFFFFE0092E8A # 856 - 859 D6 S5 C8 + .quad 0xffffffffc00aae92, 0xFFFFFFFFA00AAE9A, 0xFFFFFFFF800AAEA2, 0xFFFFFFFF600AAEAA # 860 - 863 D6 S5 C12 + .quad 0x0000000000003600, 0x0000000000013641, 0x0000000000053642, 0xFFFFFFFFE006368A # 864 - 867 D6 S6 C0 + .quad 0xffffffffc0063692, 0xFFFFFFFFA006369A, 0xFFFFFFFF800636A2, 0xFFFFFFFF600636AA # 868 - 871 D6 S6 C4 + .quad 0xffffffff400636b2, 0xFFFFFFFF200636BA, 0x0000000000053642, 0xFFFFFFFFE006368A # 872 - 875 D6 S6 C8 + .quad 0xffffffffc0063692, 0xFFFFFFFFA006369A, 0xFFFFFFFF800636A2, 0xFFFFFFFF600636AA # 876 - 879 D6 S6 C12 + .quad 0x0000000000083e00, 0x00000000000D3E41, 0x00000000000E7E42, 0xFFFFFFFFE00E7E8A # 880 - 883 D6 S7 C0 + .quad 0xffffffffc00e7e92, 0xFFFFFFFFA00E7E9A, 0xFFFFFFFF800E7EA2, 0xFFFFFFFF600E7EAA # 884 - 887 D6 S7 C4 + .quad 0xffffffff400e7eb2, 0xFFFFFFFF200E7EBA, 0x00000000000E7E42, 0xFFFFFFFFE00E7E8A # 888 - 891 D6 S7 C8 + .quad 0xffffffffc00e7e92, 0xFFFFFFFFA00E7E9A, 0xFFFFFFFF800E7EA2, 0xFFFFFFFF600E7EAA # 892 - 895 D6 S7 C12 + .quad 0x0000000000080700, 0x0000000000090741, 0xFFFFFFFFE0090789, 0xFFFFFFFFC0090791 # 896 - 899 D7 S0 C0 + .quad 0xffffffffa0090799, 0xFFFFFFFF800907A1, 0xFFFFFFFF600907A9, 0xFFFFFFFF400907B1 # 900 - 903 D7 S0 C4 + .quad 0xffffffff200907b9, 0x0000000000090741, 0xFFFFFFFFE0090789, 0xFFFFFFFFC0090791 # 904 - 907 D7 S0 C8 + .quad 0xffffffffa0090799, 0xFFFFFFFF800907A1, 0xFFFFFFFF600907A9, 0xFFFFFFFF400907B1 # 908 - 911 D7 S0 C12 + .quad 0x0000000000080f00, 0x0000000000090F41, 0xFFFFFFFFE0090F89, 0xFFFFFFFFC0090F91 # 912 - 915 D7 S1 C0 + .quad 0xffffffffa0090f99, 0xFFFFFFFF80090FA1, 0xFFFFFFFF60090FA9, 0xFFFFFFFF40090FB1 # 916 - 919 D7 S1 C4 + .quad 0xffffffff200a8fb9, 0x0000000000090F41, 0xFFFFFFFFE0090F89, 0xFFFFFFFFC0090F91 # 920 - 923 D7 S1 C8 + .quad 0xffffffffa0090f99, 0xFFFFFFFF80090FA1, 0xFFFFFFFF60090FA9, 0xFFFFFFFF40090FB1 # 924 - 927 D7 S1 C12 + .quad 0x0000000000081700, 0x0000000000091741, 0xFFFFFFFFE0091789, 0xFFFFFFFFC0091791 # 928 - 931 D7 S2 C0 + .quad 0xffffffffa0091799, 0xFFFFFFFF800917A1, 0xFFFFFFFF600917A9, 0xFFFFFFFF400A97B1 # 932 - 935 D7 S2 C4 + .quad 0xffffffff200a97b9, 0x0000000000091741, 0xFFFFFFFFE0091789, 0xFFFFFFFFC0091791 # 936 - 939 D7 S2 C8 + .quad 0xffffffffa0091799, 0xFFFFFFFF800917A1, 0xFFFFFFFF600917A9, 0xFFFFFFFF400A97B1 # 940 - 943 D7 S2 C12 + .quad 0x0000000000081f00, 0x0000000000091F41, 0xFFFFFFFFE0091F89, 0xFFFFFFFFC0091F91 # 944 - 947 D7 S3 C0 + .quad 0xffffffffa0091f99, 0xFFFFFFFF80091FA1, 0xFFFFFFFF600A9FA9, 0xFFFFFFFF400A9FB1 # 948 - 951 D7 S3 C4 + .quad 0xffffffff200a9fb9, 0x0000000000091F41, 0xFFFFFFFFE0091F89, 0xFFFFFFFFC0091F91 # 952 - 955 D7 S3 C8 + .quad 0xffffffffa0091f99, 0xFFFFFFFF80091FA1, 0xFFFFFFFF600A9FA9, 0xFFFFFFFF400A9FB1 # 956 - 959 D7 S3 C12 + .quad 0x0000000000082700, 0x0000000000092741, 0xFFFFFFFFE0092789, 0xFFFFFFFFC0092791 # 960 - 963 D7 S4 C0 + .quad 0xffffffffa0092799, 0xFFFFFFFF800AA7A1, 0xFFFFFFFF600AA7A9, 0xFFFFFFFF400AA7B1 # 964 - 967 D7 S4 C4 + .quad 0xffffffff200aa7b9, 0x0000000000092741, 0xFFFFFFFFE0092789, 0xFFFFFFFFC0092791 # 968 - 971 D7 S4 C8 + .quad 0xffffffffa0092799, 0xFFFFFFFF800AA7A1, 0xFFFFFFFF600AA7A9, 0xFFFFFFFF400AA7B1 # 972 - 975 D7 S4 C12 + .quad 0x0000000000082f00, 0x0000000000092F41, 0xFFFFFFFFE0092F89, 0xFFFFFFFFC0092F91 # 976 - 979 D7 S5 C0 + .quad 0xffffffffa00aaf99, 0xFFFFFFFF800AAFA1, 0xFFFFFFFF600AAFA9, 0xFFFFFFFF400AAFB1 # 980 - 983 D7 S5 C4 + .quad 0xffffffff200aafb9, 0x0000000000092F41, 0xFFFFFFFFE0092F89, 0xFFFFFFFFC0092F91 # 984 - 987 D7 S5 C8 + .quad 0xffffffffa00aaf99, 0xFFFFFFFF800AAFA1, 0xFFFFFFFF600AAFA9, 0xFFFFFFFF400AAFB1 # 988 - 991 D7 S5 C12 + .quad 0x0000000000083700, 0x0000000000093741, 0xFFFFFFFFE0093789, 0xFFFFFFFFC00AB791 # 992 - 995 D7 S6 C0 + .quad 0xffffffffa00ab799, 0xFFFFFFFF800AB7A1, 0xFFFFFFFF600AB7A9, 0xFFFFFFFF400AB7B1 # 996 - 999 D7 S6 C4 + .quad 0xffffffff200ab7b9, 0x0000000000093741, 0xFFFFFFFFE0093789, 0xFFFFFFFFC00AB791 # 1000 - 1003 D7 S6 C8 + .quad 0xffffffffa00ab799, 0xFFFFFFFF800AB7A1, 0xFFFFFFFF600AB7A9, 0xFFFFFFFF400AB7B1 # 1004 - 1007 D7 S6 C12 + .quad 0x0000000000003f00, 0x0000000000053F41, 0xFFFFFFFFE0063F89, 0xFFFFFFFFC0063F91 # 1008 - 1011 D7 S7 C0 + .quad 0xffffffffa0063f99, 0xFFFFFFFF80063FA1, 0xFFFFFFFF60063FA9, 0xFFFFFFFF40063FB1 # 1012 - 1015 D7 S7 C4 + .quad 0xffffffff20063fb9, 0x0000000000053F41, 0xFFFFFFFFE0063F89, 0xFFFFFFFFC0063F91 # 1016 - 1019 D7 S7 C8 + .quad 0xffffffffa0063f99, 0xFFFFFFFF80063FA1, 0xFFFFFFFF60063FA9, 0xFFFFFFFF40063FB1 # 1020 - 1023 D7 S7 C12 diff --git a/emulator-asm/src/dma/memcpy_fast.asm b/emulator-asm/src/dma/memcpy_fast.asm new file mode 100644 index 000000000..0ad97dae1 --- /dev/null +++ b/emulator-asm/src/dma/memcpy_fast.asm @@ -0,0 +1,97 @@ +.intel_syntax noprefix +.code64 + +################################################################################ +# dma_memcpy_fast - Optimized memcpy using rep movsq (no tracing) +# +# Fast memory copy function optimized for performance using hardware-accelerated +# instructions. Handles overlapping memory regions correctly (like memmove). +# +# PARAMETERS (System V AMD64 ABI): +# rdi = dst (u64) - Destination address +# rsi = src (u64) - Source address +# rdx = count (usize) - Number of bytes to copy +# +# RETURN VALUE: None +# +# STRATEGY: +# For non-overlapping regions: +# 1. Copy pre_count unaligned bytes (0-7 bytes to reach alignment) +# 2. Copy aligned qwords using rep movsq (~1-2 cycles/qword) +# 3. Copy post_count remaining bytes (0-7 bytes) +# +# For overlapping regions (dst between src and src+count): +# - Copy backward byte-by-byte using rep movsb with std flag +# +# PERFORMANCE: ~10-20 cycles overhead + ~1-2 cycles per qword +# +# REGISTERS USED: rax, rcx, rdi, rsi, rdx, r8, r9 +################################################################################ + +.global dma_memcpy_fast + +.section .text + +dma_memcpy_fast: + # Check if count is 0 + test rdx, rdx # 1 cycle + jz .L_fast_done # nothing to copy + + # Save original values + mov r8, rdi # r8 = dst + mov r9, rsi # r9 = src + + # Check for overlap: if dst < src or dst >= src+count, no overlap + lea rax, [rsi + rdx] # rax = src + count + cmp rdi, rsi # compare dst with src + jb .L_fast_forward # dst < src, copy forward + cmp rdi, rax # compare dst with src+count + jae .L_fast_forward # dst >= src+count, no overlap + + # Overlap detected: copy backward + lea rsi, [r9 + rdx - 1] # rsi = src + count - 1 (use r9, original src) + lea rdi, [r8 + rdx - 1] # rdi = dst + count - 1 (use r8, original dst) + mov rcx, rdx # rcx = count + std # set direction flag (backward) + rep movsb # copy backward + cld # clear direction flag + jmp .L_fast_done + +.L_fast_forward: + # No overlap: optimized 3-phase copy + # Calculate dst_offset and pre_count + mov rax, r8 # rax = dst + and rax, 0x07 # rax = dst_offset + test rax, rax # check if already aligned + jz .L_fast_aligned # skip pre-copy if aligned + + # Copy pre_count bytes to align dst + mov rcx, 8 # rcx = 8 + sub rcx, rax # rcx = 8 - dst_offset = pre_count + cmp rcx, rdx # check if pre_count > count + jbe .L_fast_pre_ok # pre_count <= count + mov rcx, rdx # pre_count = count (copy all) +.L_fast_pre_ok: + sub rdx, rcx # count -= pre_count + rep movsb # copy pre_count bytes + # rsi and rdi are now advanced and rdi is aligned + +.L_fast_aligned: + # Copy aligned qwords using rep movsq + mov rcx, rdx # rcx = remaining count + shr rcx, 3 # rcx = count / 8 (qword count) + jz .L_fast_post # skip if no qwords to copy + rep movsq # copy qwords (~1-2 cycles each) + +.L_fast_post: + # Copy remaining bytes (0-7) + mov rcx, rdx # rcx = original count + and rcx, 0x07 # rcx = count % 8 (post_count) + jz .L_fast_done # skip if no remaining bytes + rep movsb # copy remaining bytes + +.L_fast_done: + ret + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/test_dma.cpp b/emulator-asm/src/dma/test_dma.cpp new file mode 100644 index 000000000..dc7005b44 --- /dev/null +++ b/emulator-asm/src/dma/test_dma.cpp @@ -0,0 +1,978 @@ +#include +#include +#include +#include +#include +#include + +#define NO_OVERLAPPING 0xFFFFFFFF + +// External assembly function declarations +extern "C" { + uint64_t dma_memcpy_mtrace(uint64_t dst, uint64_t src, uint64_t count, uint64_t* trace_ptr); + uint64_t dma_memcpy_mops(uint64_t dst, uint64_t src, uint64_t count, uint64_t* mops_ptr); + void dma_memcpy_fast(uint64_t dst, uint64_t src, uint64_t count); + uint64_t fast_dma_encode(uint64_t dst, uint64_t src, uint64_t count); +} +const char *mops_labels[16] = {"NOP", "CWR1", "RD1", "WR1", "RD2", "WR2", "RD4", "WR4", "RD8", "WR8", + "ARD", "AWR", "BR", "BW", "ABR", "ABW"}; + +// MOPS constants from dma_constants.inc +const uint64_t EXTRA_PARAMETER_ADDR = 0xA000'0F00; +const uint64_t MOPS_ALIGNED_READ = 0x0000'000C'0000'0000ULL; +const uint64_t MOPS_ALIGNED_BLOCK_READ = 0x0000'000E'0000'0000ULL; +const uint64_t MOPS_ALIGNED_BLOCK_WRITE = 0x0000'000F'0000'0000ULL; +const uint64_t MOPS_BLOCK_WORDS_SBITS = 36; + +class Memory { +protected: + uint8_t *original_bytes; +public: + uint8_t *bytes; + size_t size; + Memory (size_t size): size(size) { + bytes = (uint8_t *) aligned_alloc(8, size); + if (bytes == NULL) { + printf("Failed to allocate bytes\n"); + exit(1); + } + if ((((uint64_t) bytes) & 0x07) != 0) { + printf("Invalid allocation %p\n", bytes); + exit(1); + } + original_bytes = (uint8_t *) aligned_alloc(8, size); + if (original_bytes == NULL) { + printf("Failed to allocate original_bytes\n"); + free(bytes); + exit(1); + } + } + Memory(const Memory&) = delete; + Memory& operator=(const Memory&) = delete; + ~Memory() { + if (original_bytes) free(original_bytes); + if (bytes) free(bytes); + original_bytes = NULL; + bytes = NULL; + } + + const uint8_t *get_original_bytes(uint8_t *reference) { + return original_bytes + (reference - bytes); + } + void fill_pattern(uint8_t start = 0) { + for (size_t i = 0; i < size; ++i) { + bytes[i] = start + i; + } + memcpy(original_bytes, bytes, size); + } + + bool verify_pattern(uint8_t start = 0, const char *title = "") { + for (size_t i = 0; i < size; ++i) { + uint8_t expected = start + i; + if (bytes[i] != expected) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", + title, expected, i, bytes[i]); + return false; + } + } + return true; + } + + bool verify_pattern_except(uint8_t start, size_t addr, size_t count, const char *title = "") { + size_t from = addr - (size_t)bytes; + size_t to = from + count - 1; + for (size_t i = 0; i < size; ++i) { + if (i >= from && i <= to) continue; + uint8_t expected = start + i; + if (bytes[i] != expected) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", + title, expected, i, bytes[i]); + return false; + } + } + return true; + } +}; + +// Helper class to manage aligned test buffers +class AlignedBuffer { +public: + std::vector data; + + AlignedBuffer(size_t size) : data(size, 0) {} + + uint64_t* aligned_ptr() { + return reinterpret_cast(data.data()); + } + + uint8_t* byte_ptr() { + return data.data(); + } + + void fill_pattern(uint8_t start = 0) { + for (size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast(start + i); + } + } + + bool verify_pattern(uint8_t start = 0, const char *title = "") { + for (size_t i = 0; i < data.size(); ++i) { + uint8_t expected = static_cast(start + i); + if (data[i] != expected) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", + title, expected, i, data[i]); + return false; + } + } + return true; + } + + bool verify_pattern_except(uint8_t start, size_t from, size_t count, const char *title = "") { + size_t to = from + count - 1; + for (size_t i = 0; i < data.size(); ++i) { + if (i >= from && i <= to) continue; + uint8_t expected = static_cast(start + i); + if (data[i] != expected) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", + title, expected, i, data[i]); + return false; + } + } + return true; + } + + bool verify_fill(uint8_t value, const char *title = "") { + for (size_t i = 0; i < data.size(); ++i) { + if (data[i] != value) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]:0x%02X\n", + title, value, i, data[i]); + return false; + } + } + return true; + } + + bool verify_fill_except(uint8_t value, size_t from, size_t count, const char *title = "") { + size_t to = from + count - 1; + for (size_t i = 0; i < data.size(); ++i) { + if (i >= from && i <= to) continue; + if (data[i] != value) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]:0x%02X\n", + title, value, i, data[i]); + return false; + } + } + return true; + } + + void fill_value(uint8_t value) { + std::fill(data.begin(), data.end(), value); + } +}; + +#define TRACE_EQ(EXPECTED, CALCULATED, MSG) \ + if (EXPECTED != CALCULATED) { \ + fprintf(stderr, "❌ FAIL: Trace comparation on %s (E: 0x%016lX vs 0x%016lX) \n", MSG, EXPECTED, CALCULATED); \ + exit(1); \ + } + +// Reference implementation for encode_memcpy from Rust +uint64_t encode_memcpy_reference(uint64_t dst, uint64_t src, uint64_t count) { + uint64_t dst_offset = dst & 0x07; + uint64_t src_offset = src & 0x07; + + uint64_t pre_count, loop_count, post_count; + + if (dst_offset > 0) { + uint64_t _pre_count = 8 - dst_offset; + if (_pre_count >= count) { + pre_count = count; + loop_count = 0; + post_count = 0; + } else { + uint64_t pending = count - _pre_count; + pre_count = _pre_count; + loop_count = pending >> 3; + post_count = pending & 0x07; + } + } else { + pre_count = 0; + loop_count = count >> 3; + post_count = count & 0x07; + } + + uint64_t pre_writes = (pre_count > 0) + (post_count > 0); + uint64_t src_offset_pos = (src_offset + pre_count) & 0x07; + uint64_t double_src_post = (src_offset_pos + post_count) > 8; + uint64_t double_src_pre = (src_offset + pre_count) > 8; + uint64_t extra_src_reads = + (count == 0) ? 0 : ((((src + count - 1) >> 3) - (src >> 3) + 1) - loop_count); + uint64_t src64_inc_by_pre = (pre_count > 0 && (src_offset + pre_count) >= 8); + uint64_t unaligned_dst_src = (src_offset != dst_offset); + + return pre_count + | (post_count << 3) + | (pre_writes << 6) + | (dst_offset << 8) + | (src_offset << 11) + | (double_src_pre << 14) + | (double_src_post << 15) + | (extra_src_reads << 16) + | (src64_inc_by_pre << 18) + | (unaligned_dst_src << 19) + | (pre_count << 29) + | (loop_count << 32); +} + +// Extract fields from encoded value +struct EncodedInfo { + uint64_t loop_count; + uint64_t pre_writes; + uint64_t dst_offset; + uint64_t src_offset; + uint64_t pre_count; + uint64_t post_count; + bool double_src_pre; + bool double_src_post; + uint64_t extra_src_reads; + uint64_t src64_inc_by_pre; + uint64_t unaligned_dst_src; + + EncodedInfo(uint64_t encoded) { + loop_count = encoded >> 32; + pre_count = encoded & 0x07; + post_count = (encoded >> 3) & 0x07; + pre_writes = (encoded >> 6) & 0x03; + dst_offset = (encoded >> 8) & 0x07; + src_offset = (encoded >> 11) & 0x07; + double_src_pre = (encoded >> 14) & 0x01; + double_src_post = (encoded >> 15) & 0x01; + extra_src_reads = (encoded >> 16) & 0x03; + src64_inc_by_pre = (encoded >> 18) & 0x01; + unaligned_dst_src = (encoded >> 19) & 0x01; + } + + void print() const { + std::cout << " loop_count: " << loop_count << "\n" + << " pre_writes: " << pre_writes << "\n" + << " dst_offset: " << dst_offset << "\n" + << " src_offset: " << src_offset << "\n" + << " pre_count: " << pre_count << "\n" + << " post_count: " << post_count << "\n" + << " double_src_pre: " << double_src_pre << "\n" + << " double_src_post: " << double_src_post << "\n" + << " extra_src_reads: " << extra_src_reads << "\n" + << " src64_inc_by_pre: " << src64_inc_by_pre << "\n" + << " unaligned_dst_src: " << unaligned_dst_src << "\n"; + } +}; + +void print_hex_dump(const char* label, const uint8_t* data, size_t size) { + std::cout << label << " (" << size << " bytes):\n"; + for (size_t i = 0; i < size; i += 16) { + std::cout << " " << std::hex << std::setw(4) << std::setfill('0') << i << ": "; + for (size_t j = 0; j < 16 && i + j < size; ++j) { + std::cout << std::hex << std::setw(2) << std::setfill('0') + << static_cast(data[i + j]) << " "; + } + std::cout << "\n"; + } + std::cout << std::dec; +} + +bool test_memcpy_mtrace(Memory &mem, uint64_t dst_offset, uint64_t src_offset, size_t count, + const char* description, int64_t overlapping = NO_OVERLAPPING) { + + + bool is_overlapping = overlapping != NO_OVERLAPPING; + if (is_overlapping) { + printf("\n\x1b[1;36m##### test_memcpy_mtrace(%ld, %ld, %ld,\"%s\", overlapping:%ld) #####\x1b[0m\n", + dst_offset, src_offset, count, description, overlapping); + } else { + printf("\n\x1b[1;36m##### test_memcpy_mtrace(%ld, %ld, %ld,\"%s\") #####\x1b[0m\n", + dst_offset, src_offset, count, description); + } + mem.fill_pattern(0x10); + + uint8_t *src; + uint8_t *dst; + + if (is_overlapping) { + src = mem.bytes + 1024; + dst = src + overlapping; + } else { + src = mem.bytes + 1024; + dst = src + ((count + 7) & ~0x07) + 1024; + } + AlignedBuffer trace_buf(4096); + + // Clear trace buffer + trace_buf.fill_value(0); + + if (!mem.verify_pattern(0x10) || !trace_buf.verify_fill(0, "trace_buff")) { + return false; + } + + uint64_t src_addr = ((uint64_t) src) + src_offset; + uint64_t dst_addr = ((uint64_t) dst) + dst_offset; + uint64_t* trace_ptr = trace_buf.aligned_ptr(); + + printf("TEST dst:0x%08lX src:0x%08lX count:%ld trace:%p\n", dst_addr, src_addr, count, trace_ptr); + + // Calculate reference encoding BEFORE assembly call to avoid register corruption + uint64_t encoded_ref = encode_memcpy_reference(dst_addr, src_addr, count); + + // Call assembly function and capture return value + uint64_t qwords_written = dma_memcpy_mtrace(dst_addr, src_addr, count, trace_ptr); + + // Get encoded value from trace + uint64_t encoded_asm = trace_ptr[0]; + + if (encoded_asm != encoded_ref) { + printf("Encoded (ASM): 0x%016lX\n", encoded_asm); + printf("Encoded (REF): 0x%016lX\n", encoded_ref); + + std::cerr << "❌ FAIL: Encoded value mismatch!\n"; + EncodedInfo info_asm(encoded_asm); + EncodedInfo info_ref(encoded_ref); + std::cout << "ASM info:\n"; + info_asm.print(); + std::cout << "REF info:\n"; + info_ref.print(); + return false; + } + + // Verify the actual memcpy was performed + const uint8_t* src_bytes; + if (is_overlapping) { + src_bytes = mem.get_original_bytes((uint8_t *)src_addr); + } else { + src_bytes = (uint8_t *)src_addr; + } + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + + bool copy_ok = true; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != src_bytes[i]) { + std::cerr << "❌ FAIL: Memory copy mismatch at byte " << i << "\n"; + std::cerr << " Expected: 0x" << std::hex << static_cast(src_bytes[i]) + << ", Got: 0x" << static_cast(dst_bytes[i]) << std::dec << "\n"; + copy_ok = false; + break; + } + } + + if (!copy_ok) { + print_hex_dump("Source", src_bytes, std::min(count, size_t(64))); + print_hex_dump("Destination", dst_bytes, std::min(count, size_t(64))); + return false; + } + + std::cout << "✅ PASS: Encoding and copy correct\n"; + + // Print trace buffer summary + EncodedInfo info(encoded_asm); + size_t trace_idx = 1; + std::cout << "Trace buffer:\n"; + std::cout << " [0] Encoded: 0x" << std::hex << trace_ptr[0] << std::dec << "\n"; + + uint64_t *dst_original = (uint64_t*) mem.get_original_bytes((uint8_t *) (dst_addr & ~0x07)); + if (info.pre_count > 0) { + TRACE_EQ(dst_original[0], trace_ptr[trace_idx], "PRE pre-write value not match"); + trace_idx++; + } + + if (info.post_count > 0) { + size_t last_dst_index = (dst_offset + count - 1) >> 3; + TRACE_EQ(dst_original[last_dst_index], trace_ptr[trace_idx], "POST pre-write value not match"); + trace_idx++; + } + + size_t expected_src_qwords = info.loop_count + info.extra_src_reads; + // Verify that the function returned the correct number of qwords written + size_t expected_total_qwords = trace_idx + expected_src_qwords; + + if (qwords_written != expected_total_qwords) { + std::cerr << "❌ FAIL: Incorrect number of qwords returned!\n"; + std::cerr << " Expected: " << expected_total_qwords << " qwords\n"; + std::cerr << " Got: " << qwords_written << " qwords\n"; + return false; + } + + uint64_t *src_original = (uint64_t*) mem.get_original_bytes((uint8_t *) (src_addr & ~0x07)); + for (size_t index = 0; index < expected_src_qwords; ++index) { + TRACE_EQ(src_original[index], trace_ptr[trace_idx], "SRC values not match"); + trace_idx++; + } + if (!mem.verify_pattern_except(0x10, dst_addr, count, "mem (out)") || + !trace_buf.verify_fill_except(0, 0, qwords_written * 8, "trace_buff (out)")) { + return false; + } + std::cout << "✅ Returned correct qword count: " << qwords_written << " qwords\n"; + + return true; +} + +bool test_memcpy_mops(Memory &mem, uint64_t dst_offset, uint64_t src_offset, size_t count, + const char* description, int64_t overlapping = NO_OVERLAPPING) { + + + bool is_overlapping = overlapping != NO_OVERLAPPING; + if (is_overlapping) { + printf("\n\x1b[1;35m##### test_memcpy_mops(%ld, %ld, %ld,\"%s\", overlapping:%ld) #####\x1b[0m\n", + dst_offset, src_offset, count, description, overlapping); + } else { + printf("\n\x1b[1;35m##### test_memcpy_mops(%ld, %ld, %ld,\"%s\") #####\x1b[0m\n", + dst_offset, src_offset, count, description); + } + mem.fill_pattern(0x10); + + uint8_t *src; + uint8_t *dst; + + if (is_overlapping) { + src = mem.bytes + 1024; + dst = src + overlapping; + } else { + src = mem.bytes + 1024; + dst = src + ((count + 7) & ~0x07) + 1024; + } + AlignedBuffer mops_buf(4096); + + // Clear mops buffer + mops_buf.fill_value(0); + + if (!mem.verify_pattern(0x10) || !mops_buf.verify_fill(0, "mops_buff")) { + return false; + } + + uint64_t src_addr = ((uint64_t) src) + src_offset; + uint64_t dst_addr = ((uint64_t) dst) + dst_offset; + uint64_t* mops_ptr = mops_buf.aligned_ptr(); + + printf("TEST dst:0x%08lX src:0x%08lX count:%ld mops:%p\n", dst_addr, src_addr, count, mops_ptr); + + // Calculate reference encoding to know expected structure + uint64_t encoded_ref = encode_memcpy_reference(dst_addr, src_addr, count); + EncodedInfo info(encoded_ref); + printf("INFO pre:%ld%s post:%ld%s loop:%ld sibp:%ld\n", info.pre_count, info.double_src_pre ? "+D":"", + info.post_count, info.double_src_post ? "+D":"", info.loop_count, info.src64_inc_by_pre); + + // Call assembly function and capture return value + uint64_t mops_entries = dma_memcpy_mops(dst_addr, src_addr, count, mops_ptr); + + // Verify the actual memcpy was performed + const uint8_t* src_bytes; + if (is_overlapping) { + src_bytes = mem.get_original_bytes((uint8_t *)src_addr); + } else { + src_bytes = (uint8_t *)src_addr; + } + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + + bool copy_ok = true; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != src_bytes[i]) { + std::cerr << "❌ FAIL: Memory copy mismatch at byte " << i << "\n"; + std::cerr << " Expected: 0x" << std::hex << static_cast(src_bytes[i]) + << ", Got: 0x" << static_cast(dst_bytes[i]) << std::dec << "\n"; + copy_ok = false; + break; + } + } + + if (!copy_ok) { + print_hex_dump("Source", src_bytes, std::min(count, size_t(64))); + print_hex_dump("Destination", dst_bytes, std::min(count, size_t(64))); + return false; + } + + std::vector> expected; + + expected.emplace_back(MOPS_ALIGNED_READ + EXTRA_PARAMETER_ADDR, "PARAM count"); + + if (info.pre_count > 0) { + expected.emplace_back(MOPS_ALIGNED_READ + (dst_addr & ~0x07ULL), "PRE preread dst"); + expected.emplace_back((info.double_src_pre ? (MOPS_ALIGNED_BLOCK_READ + (2ULL << MOPS_BLOCK_WORDS_SBITS)): MOPS_ALIGNED_READ) + + (src_addr & ~0x07ULL), "PRE src read"); + } + + if (info.post_count > 0) { + expected.emplace_back(MOPS_ALIGNED_READ + ((dst_addr + count - 1) & ~0x07ULL), "POST preread dst"); + expected.emplace_back((info.double_src_post ? (MOPS_ALIGNED_BLOCK_READ + (2ULL << MOPS_BLOCK_WORDS_SBITS)): MOPS_ALIGNED_READ) + + ((src_addr + info.pre_count + info.loop_count * 8) & ~0x07ULL), "POST src read"); + } + + if (info.loop_count > 0) { + expected.emplace_back(MOPS_ALIGNED_BLOCK_READ + ((info.loop_count + (info.unaligned_dst_src)) << MOPS_BLOCK_WORDS_SBITS) + + ((src_addr + info.pre_count) & ~0x07ULL), "LOOP read src"); + } + + if (count > 0) { + expected.emplace_back(MOPS_ALIGNED_BLOCK_WRITE + ((info.loop_count + info.pre_writes) << MOPS_BLOCK_WORDS_SBITS) + + (dst_addr & ~0x07ULL), "write dst"); + } + + size_t max_entries = std::max(mops_entries, expected.size()); + bool errors = false; + for (size_t i = 0; i < max_entries; ++i) { + printf("MOPS[%2ld] = ", i); + if (i < mops_entries) { + printf("%3ld %3s 0x%08lX # ", mops_ptr[i] >> MOPS_BLOCK_WORDS_SBITS, + mops_labels[(mops_ptr[i] >> 32) & 0x0F], mops_ptr[i] & 0xFFFF'FFFF); + } else { + printf("--- --- ---------- #"); + } + if (i < expected.size()) { + printf("%3ld %3s 0x%08lX %s", expected[i].first >> MOPS_BLOCK_WORDS_SBITS, + mops_labels[(expected[i].first >> 32) & 0x0F], expected[i].first & 0xFFFF'FFFF, expected[i].second.c_str()); + } + if (i >= mops_entries || i >= expected.size()) { + printf(" \x1B[31;1mFAIL\x1B[0m\n"); + errors = true; + } else if (mops_ptr[i] != expected[i].first) { + printf(" \x1B[31;1mNOT MATCH\x1B[0m\n"); + errors = true; + } else { + printf("\n"); + } + } + + // Verify total mops entries count + if (mops_entries != expected.size()) { + printf("FAIL: Incorrect number of mops entries (E:%ld vs %ld)", expected.size(), mops_entries); + return false; + } + + if (errors) { + return false; + } + std::cout << "✅ PASS: MOPS entries and copy correct (" << mops_entries << " entries)\n"; + + if (!mem.verify_pattern_except(0x10, dst_addr, count, "mem (out)") || + !mops_buf.verify_fill_except(0, 0, mops_entries * 8, "mops_buff (out)")) { + return false; + } + + return true; +} + +bool test_overlapping_copy(const char* description, int64_t offset) { + std::cout << "\n=== Test: " << description << " ===\n"; + std::cout << "Offset: " << offset << " bytes\n"; + + AlignedBuffer buf(2048); + AlignedBuffer trace_buf(2048); + + // Fill with pattern + buf.fill_pattern(0x20); + trace_buf.fill_value(0); + + size_t count = 32; + uint64_t src_addr = reinterpret_cast(buf.byte_ptr() + 64); + uint64_t dst_addr = src_addr + offset; + + // Validate bounds + if (dst_addr < reinterpret_cast(buf.byte_ptr()) || + dst_addr + count > reinterpret_cast(buf.byte_ptr() + buf.data.size())) { + std::cerr << "❌ FAIL: dst_addr out of bounds\n"; + return false; + } + uint64_t* trace_ptr = trace_buf.aligned_ptr(); + + // Save original data + std::vector original_src(count); + std::memcpy(original_src.data(), reinterpret_cast(src_addr), count); + + // Call function + dma_memcpy_mtrace(dst_addr, src_addr, count, trace_ptr); + + // Verify copy + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + bool ok = true; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != original_src[i]) { + std::cerr << "❌ FAIL: Overlapping copy mismatch at byte " << i << "\n"; + ok = false; + break; + } + } + + if (ok) { + std::cout << "✅ PASS: Overlapping copy correct\n"; + } + + return ok; +} + +bool test_fast_memcpy(uint64_t dst_offset, uint64_t src_offset, size_t count, + const char* description) { + std::cout << "\n=== Test Fast: " << description << " ===\n"; + std::cout << "dst_offset=" << dst_offset << ", src_offset=" << src_offset + << ", count=" << count << "\n"; + + // Allocate buffers + AlignedBuffer src_buf(2048); + AlignedBuffer dst_buf(2048); + + // Fill source with pattern + src_buf.fill_pattern(0x10); + // Fill destination with different pattern + dst_buf.fill_pattern(0xA0); + + // Calculate actual addresses with offsets + uint64_t src_addr = reinterpret_cast(src_buf.byte_ptr() + 64) + src_offset; + uint64_t dst_addr = reinterpret_cast(dst_buf.byte_ptr() + 64) + dst_offset; + + // Call assembly function (no trace) + dma_memcpy_fast(dst_addr, src_addr, count); + + // Verify the memcpy was performed + const uint8_t* src_bytes = reinterpret_cast(src_addr); + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + + bool copy_ok = true; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != src_bytes[i]) { + std::cout << "❌ FAIL: Mismatch at byte " << i << "\n"; + std::cout << " Expected: 0x" << std::hex << (int)src_bytes[i] + << " Got: 0x" << (int)dst_bytes[i] << std::dec << "\n"; + copy_ok = false; + break; + } + } + + if (!copy_ok) { + return false; + } + + std::cout << "✅ PASS: Fast copy correct\n"; + return true; +} + +bool test_fast_overlapping_heap(const char* description, int64_t offset) { + std::cout << "\n=== Test Fast Overlap (HEAP): " << description << " ===\n"; + std::cout << "Offset: " << offset << " bytes\n"; + + size_t count = 32; + size_t buffer_size = 4096; + + // Allocate with aligned_alloc to get proper alignment and avoid vector overhead + uint8_t* buf = (uint8_t*)aligned_alloc(8, buffer_size); + if (!buf) { + std::cerr << "❌ FAIL: allocation failed\n"; + return false; + } + + // Fill with pattern + for (size_t i = 0; i < buffer_size; ++i) { + buf[i] = 0x20 + (i & 0xFF); + } + + uint64_t src_addr = reinterpret_cast(buf + 1024); + uint64_t dst_addr = src_addr + offset; + + // Validate bounds + if (dst_addr < reinterpret_cast(buf) || + dst_addr + count > reinterpret_cast(buf + buffer_size)) { + std::cerr << "❌ FAIL: dst_addr out of bounds\n"; + free(buf); + return false; + } + + // Set canaries + size_t min_addr = std::min(src_addr, dst_addr) - reinterpret_cast(buf); + size_t max_addr = std::max(src_addr + count, dst_addr + count) - reinterpret_cast(buf); + + const uint8_t CANARY = 0xCA; + for (size_t i = min_addr - 8; i < min_addr; ++i) { + buf[i] = CANARY; + } + for (size_t i = max_addr; i < max_addr + 8; ++i) { + buf[i] = CANARY; + } + + // Save original data + uint8_t original_src[32]; + const uint8_t* src_bytes = reinterpret_cast(src_addr); + for (size_t i = 0; i < count; ++i) { + original_src[i] = src_bytes[i]; + } + + // Call function + printf("dma_memcpy_fast(0x%016lx,0x%016lx,%ld)\n", dst_addr, src_addr, count); + dma_memcpy_fast(dst_addr, src_addr, count); + printf("dma_memcpy_fast-END\n"); + + // Check canaries + bool canaries_ok = true; + for (size_t i = min_addr - 8; i < min_addr; ++i) { + if (buf[i] != CANARY) { + std::cerr << "❌ FAIL: Canary corrupted BEFORE at offset " << i << "\n"; + canaries_ok = false; + } + } + for (size_t i = max_addr; i < max_addr + 8; ++i) { + if (buf[i] != CANARY) { + std::cerr << "❌ FAIL: Canary corrupted AFTER at offset " << i << "\n"; + canaries_ok = false; + } + } + + // Verify result + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + bool ok = canaries_ok; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != original_src[i]) { + std::cout << "❌ FAIL: Mismatch at byte " << i << "\n"; + ok = false; + break; + } + } + + free(buf); + + if (ok) { + std::cout << "✅ PASS: Fast overlapping copy correct (heap)\n"; + } + + return ok; +} + +bool test_fast_overlapping(const char* description, int64_t offset) { + std::cout << "\n=== Test Fast Overlap: " << description << " ===\n"; + std::cout << "Offset: " << offset << " bytes\n"; + + size_t count = 32; + + // Use statically allocated aligned buffer with canaries + static uint8_t static_buf[4096] __attribute__((aligned(8))); + + // Fill with pattern + for (size_t i = 0; i < sizeof(static_buf); ++i) { + static_buf[i] = 0x20 + (i & 0xFF); + } + + // Calculate addresses with safety margins + size_t guard_size = 64; // 64 bytes before and after + uint64_t src_addr = reinterpret_cast(static_buf + 1024); + uint64_t dst_addr = src_addr + offset; + + // Validate bounds + if (dst_addr < reinterpret_cast(static_buf + guard_size) || + dst_addr + count > reinterpret_cast(static_buf + sizeof(static_buf) - guard_size)) { + std::cerr << "❌ FAIL: dst_addr out of bounds\n"; + return false; + } + + // Set canary values around the operation zone + size_t min_addr = std::min(src_addr, dst_addr) - reinterpret_cast(static_buf); + size_t max_addr = std::max(src_addr + count, dst_addr + count) - reinterpret_cast(static_buf); + + // Fill canaries before and after + const uint8_t CANARY = 0xCA; + for (size_t i = min_addr - 8; i < min_addr; ++i) { + static_buf[i] = CANARY; + } + for (size_t i = max_addr; i < max_addr + 8; ++i) { + static_buf[i] = CANARY; + } + + // Save original data using simple array + uint8_t original_src[32]; + const uint8_t* src_bytes = reinterpret_cast(src_addr); + for (size_t i = 0; i < count; ++i) { + original_src[i] = src_bytes[i]; + } + // std::memcpy(original_src.data(), reinterpret_cast(src_addr), count); + + // Call fast function + printf("dma_memcpy_fast(0x%016lx,0x%016lx,%ld)\n", dst_addr, src_addr, count); + dma_memcpy_fast(dst_addr, src_addr, count); + printf("dma_memcpy_fast-END\n"); + + // Check canaries for buffer overflow + bool canaries_ok = true; + for (size_t i = min_addr - 8; i < min_addr; ++i) { + if (static_buf[i] != CANARY) { + std::cerr << "❌ FAIL: Canary corrupted BEFORE region at offset " << i + << " (expected 0x" << std::hex << (int)CANARY + << ", got 0x" << (int)static_buf[i] << std::dec << ")\n"; + canaries_ok = false; + } + } + for (size_t i = max_addr; i < max_addr + 8; ++i) { + if (static_buf[i] != CANARY) { + std::cerr << "❌ FAIL: Canary corrupted AFTER region at offset " << i + << " (expected 0x" << std::hex << (int)CANARY + << ", got 0x" << (int)static_buf[i] << std::dec << ")\n"; + canaries_ok = false; + } + } + + if (!canaries_ok) { + std::cerr << "❌ BUFFER OVERFLOW DETECTED!\n"; + std::cerr << " src_addr: 0x" << std::hex << src_addr << std::dec << "\n"; + std::cerr << " dst_addr: 0x" << std::hex << dst_addr << std::dec << "\n"; + std::cerr << " count: " << count << "\n"; + std::cerr << " offset: " << offset << "\n"; + return false; + } + + // Verify result + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + bool ok = true; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != original_src[i]) { + std::cout << "❌ FAIL: Mismatch at byte " << i << "\n"; + ok = false; + break; + } + } + + if (!ok) { + return false; + } + + std::cout << "✅ PASS: Fast overlapping copy correct\n"; + return true; +} + +int main() { + Memory mem (8192); + std::cout << "Testing DMA memory operations assembly implementation\n"; + std::cout << "=====================================================\n"; + + int passed = 0; + int total = 0; + + // Test mtrace (memory trace with full data) + std::cout << "\n\x1b[1;33m=== MTRACE Tests (Full Memory Trace) ===\x1b[0m\n"; + auto run_mtrace_test = [&](uint64_t dst_off, uint64_t src_off, size_t count, const char* desc) { + total++; + if (test_memcpy_mtrace(mem, dst_off, src_off, count, desc)) { + passed++; + } + }; + + run_mtrace_test(0, 0, 0, "Zero count"); + run_mtrace_test(0, 0, 1, "Single byte, aligned"); + run_mtrace_test(0, 0, 8, "One qword, aligned"); + run_mtrace_test(0, 0, 16, "Two qwords, aligned"); + run_mtrace_test(1, 0, 7, "dst_offset=1, count=7"); + run_mtrace_test(7, 0, 1, "dst_offset=7, count=1"); + run_mtrace_test(7, 0, 2, "dst_offset=7, count=2"); + run_mtrace_test(3, 5, 10, "dst_offset=3, src_offset=5, count=10"); + run_mtrace_test(0, 0, 100, "Large aligned copy"); + run_mtrace_test(3, 5, 100, "Large unaligned copy"); + + // Test mops (memory operations - addresses only) + std::cout << "\n\x1b[1;33m=== MOPS Tests (Memory Operations) ===\x1b[0m\n"; + auto run_mops_test = [&](uint64_t dst_off, uint64_t src_off, size_t count, const char* desc) { + total++; + if (test_memcpy_mops(mem, dst_off, src_off, count, desc)) { + passed++; + } else { + exit(1); + } + }; + + run_mops_test(0, 0, 0, "Zero count"); + run_mops_test(0, 0, 1, "Single byte, aligned"); + run_mops_test(0, 0, 8, "One qword, aligned"); + run_mops_test(0, 0, 16, "Two qwords, aligned"); + run_mops_test(1, 0, 7, "dst_offset=1, count=7"); + run_mops_test(7, 0, 1, "dst_offset=7, count=1"); + run_mops_test(7, 0, 2, "dst_offset=7, count=2"); + run_mops_test(3, 5, 10, "dst_offset=3, src_offset=5, count=10"); + run_mops_test(0, 0, 100, "Large aligned copy"); + run_mops_test(3, 5, 100, "Large unaligned copy"); + + // Comprehensive test + std::cout << "\n=== Comprehensive Test ===\n"; + for (uint64_t dst_off = 0; dst_off < 8; ++dst_off) { + for (uint64_t src_off = 0; src_off < 8; ++src_off) { + for (size_t count = 0; count < 128; ++count) { + total++; + if (test_memcpy_mtrace(mem, dst_off, src_off, count, "Comprehensive")) { + passed++; + } + total++; + if (test_memcpy_mtrace(mem, dst_off, src_off, count, "Comprehensive overlapping 0", 0)) { + passed++; + } + // total++; + // if (count > 4) { + // if (test_encode_memcpy(mem, dst_off, src_off, count, "Comprehensive overlapping -4", -4)) { + // passed++; + // } + // total++; + // if (test_encode_memcpy(mem, dst_off, src_off, count, "Comprehensive overlapping 4", 4)) { + // passed++; + // } + // total++; + // if (test_encode_memcpy(mem, dst_off, src_off, count, "Comprehensive overlapping 1 byte", count-1)) { + // passed++; + // } + // } + } + } + } + // Overlapping tests + total++; + if (test_overlapping_copy("Forward overlap (dst > src)", 8)) passed++; + + total++; + if (test_overlapping_copy("Backward overlap (dst < src)", -8)) passed++; + + total++; + if (test_overlapping_copy("No overlap (large gap)", 100)) passed++; + + // Fast memcpy tests + std::cout << "\n=== Fast Memcpy Tests ===\n"; + auto run_fast_test = [&](uint64_t dst_off, uint64_t src_off, size_t count, const char* desc) { + total++; + if (test_fast_memcpy(dst_off, src_off, count, desc)) { + passed++; + } + }; + + run_fast_test(0, 0, 0, "Zero count"); + run_fast_test(0, 0, 1, "Single byte, aligned"); + run_fast_test(0, 0, 8, "One qword, aligned"); + run_fast_test(0, 0, 16, "Two qwords, aligned"); + run_fast_test(1, 0, 7, "dst_offset=1, count=7"); + run_fast_test(7, 0, 1, "dst_offset=7, count=1"); + run_fast_test(3, 5, 10, "dst_offset=3, src_offset=5, count=10"); + run_fast_test(0, 0, 100, "Large aligned copy"); + run_fast_test(3, 5, 100, "Large unaligned copy"); + run_fast_test(1, 2, 1000, "Very large copy"); + + // Fast overlapping tests + total++; + if (test_fast_overlapping("Forward overlap (dst > src)", 8)) passed++; + + // Test with heap allocation + total++; + if (test_fast_overlapping_heap("Forward overlap (dst > src) HEAP", 8)) passed++; + + + total++; + if (test_fast_overlapping("Backward overlap (dst < src)", -8)) passed++; + + total++; + if (test_fast_overlapping("No overlap (large gap)", 100)) passed++; + + // Summary + std::cout << "\n=== Test Summary ===\n"; + std::cout << "Passed: " << passed << "/" << total << " tests\n"; + std::cout << "Success rate: " << (100.0 * passed / total) << "%\n"; + + if (passed == total) { + std::cout << "\n✅ All tests passed!\n"; + return 0; + } else { + std::cout << "\n❌ Some tests failed!\n"; + return 1; + } +} diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 28a76e50f..b040fb8ce 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -2088,10 +2088,6 @@ impl<'a> Emu<'a> { &mut self.static_array, ); if self.ctx.inst_ctx.data_ext_len > 0 { - // println!( - // "DETECTED DATA_EXT_LEN READ {} {} ", - // self.ctx.inst_ctx.data_ext_len, mem_reads_index - // ); if mem_reads.len() < *mem_reads_index + self.ctx.inst_ctx.data_ext_len { println!( "OUT_OF_DATA_EXT({}) S:{}", @@ -2167,14 +2163,14 @@ impl<'a> Emu<'a> { &mut self.static_array, ); if self.ctx.inst_ctx.data_ext_len > 0 { - data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); - } else { data_bus.write_to_bus( OPERATION_BUS_ID, operation_payload, &mem_reads[*mem_reads_index..*mem_reads_index + self.ctx.inst_ctx.data_ext_len], ); *mem_reads_index += self.ctx.inst_ctx.data_ext_len; + } else { + data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); } } diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index e3f040982..fe4b8246b 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "b7f913482b89b755b4830f0fe02af1f8c04fe783376b10489324799d42021761"; +pub const PILOUT_HASH: &str = "6a645a8a622600209da99c919fd1833af715f2ee2b748890e0a246da5a283bad"; pub const MERKLE_TREE_ARITY: u64 = 4; diff --git a/precompiles/dma/pil/dma.pil b/precompiles/dma/pil/dma.pil index 7aff9e70c..5103a493d 100644 --- a/precompiles/dma/pil/dma.pil +++ b/precompiles/dma/pil/dma.pil @@ -5,6 +5,7 @@ const int DMA_MEM_EQ = 3; const int MEM_CPY_BYTE_CONT_ID = 8200; const int MEM_CPY_CONT_ID = 8201; +const int DMA_MEM_CPY_COUNT_ADDR = 0xA0000F00; airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const int selectors = 1, @@ -195,8 +196,10 @@ airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const i main_step], sel: use_post); // TODO: memcmp - + // used to link operation with main lookup_proves(operation_bus_id, [OP_DMA_MEMCPY, dst, 0, src, 0, 0, 0, 0, main_step], mul: sel); - lookup_proves(operation_bus_id, [OP_PARAM, 0, 0, count, 0, 0, 0, 0, main_step-1], mul: sel); + + // lookup_proves(operation_bus_id, [OP_PARAM, 0, 0, count, 0, 0, 0, 0, main_step-1], mul: sel); + precompiled_mem_load(sel: sel, main_step: main_step, addr: DMA_MEM_CPY_COUNT_ADDR, value: [count, 0]); } \ No newline at end of file diff --git a/precompiles/dma/src/dma/dma.rs b/precompiles/dma/src/dma/dma.rs index 3b2cfc933..8b30dcc9c 100644 --- a/precompiles/dma/src/dma/dma.rs +++ b/precompiles/dma/src/dma/dma.rs @@ -5,7 +5,7 @@ use rayon::prelude::*; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; -use proofman_util::{timer_start_trace, timer_stop_and_log_info}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; use zisk_pil::{DMA_ROM_ID, DUAL_RANGE_7_BITS_ID}; use crate::{dma::dma_rom::DmaRom, DmaInput}; @@ -179,7 +179,7 @@ impl DmaSM { let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); assert!(total_inputs <= num_rows); - tracing::info!( + tracing::debug!( "··· Creating Dma instance [{total_inputs} / {num_rows} rows filled {:.2}%]", total_inputs as f64 / num_rows as f64 * 100.0 ); @@ -288,7 +288,7 @@ impl DmaSM { }); } - timer_stop_and_log_info!(DMA_TRACE); + timer_stop_and_log_trace!(DMA_TRACE); let from_trace = FromTrace::new(&mut trace); Ok(AirInstance::new_from_trace(from_trace)) } diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs index 9ce2566f8..3d0f6cc6d 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs @@ -4,7 +4,7 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; -use proofman_util::{timer_start_trace, timer_stop_and_log_info}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use zisk_common::SegmentId; use zisk_pil::{Dma64AlignedAirValues, Dma64AlignedTrace, Dma64AlignedTraceRow}; @@ -176,7 +176,7 @@ impl Dma64AlignedSM { "Too many inputs, total_inputs:{total_inputs} num_rows:{num_rows}" ); - tracing::info!( + tracing::debug!( "··· Creating Dma64Aligned instance [{total_inputs} / {num_rows} rows filled {:.2}%]", total_inputs as f64 / num_rows as f64 * 100.0 ); @@ -264,7 +264,7 @@ impl Dma64AlignedSM { ); println!("TRACE Dma64AlignedSM AIR_VALUES {:?}", air_values); } - timer_stop_and_log_info!(DMA_64_ALIGNED_TRACE); + timer_stop_and_log_trace!(DMA_64_ALIGNED_TRACE); let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); Ok(AirInstance::new_from_trace(from_trace)) } diff --git a/precompiles/dma/src/dma_gen_mem_inputs.rs b/precompiles/dma/src/dma_gen_mem_inputs.rs index 962da783b..bc4e72de4 100644 --- a/precompiles/dma/src/dma_gen_mem_inputs.rs +++ b/precompiles/dma/src/dma_gen_mem_inputs.rs @@ -4,7 +4,7 @@ use std::collections::VecDeque; use zisk_common::{ BusId, MemCollectorInfo, A, B, DMA_ENCODED, OP, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP, }; -use zisk_core::zisk_ops::ZiskOp; +use zisk_core::{zisk_ops::ZiskOp, EXTRA_PARAMS}; pub fn generate_dma_mem_inputs( data: &[u64], @@ -27,6 +27,13 @@ pub fn generate_dma_mem_inputs( // NOTE: for dual memories it's very important to keep the order of loads and stores because // stores happend after loads. + MemBusHelpers::mem_aligned_load( + EXTRA_PARAMS as u32, + main_step, + DmaInfo::get_count(encoded) as u64, + pending, + ); + let mut wr_pending = VecDeque::new(); if pre_count > 0 { let pre_data_offset = DmaInfo::get_pre_data_offset(encoded); @@ -86,6 +93,9 @@ pub fn generate_dma_mem_inputs( let loop_data_count = DmaInfo::get_loop_count(encoded); let loop_src_data_end = loop_data_offset + loop_data_count + ((loop_src & 0x07) > 0) as usize; + if data_ext.len() <= loop_data_offset || data_ext.len() < loop_src_data_end { + println!("PRE-CRASH data_ext[{loop_data_offset}..{loop_src_data_end}] data_ext.len() = {} DATA={data:?} INFO{}", data_ext.len(), DmaInfo::to_string(encoded)); + } let values = &data_ext[loop_data_offset..loop_src_data_end]; #[cfg(feature = "debug_dma")] @@ -193,6 +203,9 @@ pub fn skip_dma_mem_inputs( let src64_to = (src + use_count + 7) as u32 & !0x07; for mem_collector in mem_collectors_info { + if !mem_collector.skip_addr(EXTRA_PARAMS as u32) { + return false; + } if !mem_collector.skip_addr_range(dst64_from, dst64_to) { return false; } diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs index ad42e3133..bed85186b 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs @@ -4,7 +4,7 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; -use proofman_util::{timer_start_trace, timer_stop_and_log_info}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; use rayon::{ iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}, slice::{ParallelSlice, ParallelSliceMut}, @@ -273,7 +273,7 @@ impl DmaPrePostSM { assert!(total_inputs <= num_rows); assert!(total_inputs > 0); - tracing::info!( + tracing::debug!( "··· Creating DmaPrePost instance [{total_inputs} / {num_rows} rows filled {:.2}%]", total_inputs as f64 / num_rows as f64 * 100.0 ); @@ -331,7 +331,7 @@ impl DmaPrePostSM { }); } let from_trace = FromTrace::new(&mut trace); - timer_stop_and_log_info!(DMA_PRE_POST_TRACE); + timer_stop_and_log_trace!(DMA_PRE_POST_TRACE); Ok(AirInstance::new_from_trace(from_trace)) } } diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs index ac6e87571..333df1d55 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs @@ -7,7 +7,7 @@ use crate::DmaUnalignedInput; use pil_std_lib::Std; use precompiles_helpers::DmaInfo; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; -use proofman_util::{timer_start_trace, timer_stop_and_log_info}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; use zisk_common::SegmentId; use zisk_pil::{ DmaUnalignedAirValues, DmaUnalignedTrace, DmaUnalignedTraceRow, DUAL_RANGE_BYTE_ID, @@ -158,7 +158,6 @@ impl DmaUnalignedSM { } if input.is_last_instance_input { - println!("DmaUnaligned LAST_INSTANCE_INPUT {seq_end}"); if seq_end { air_values.segment_last_seq_end = F::ONE; air_values.segment_last_src64 = F::ZERO; @@ -255,7 +254,7 @@ impl DmaUnalignedSM { assert!(total_inputs <= num_rows); assert!(total_inputs > 0); - tracing::info!( + tracing::debug!( "··· Creating DmaUnaligned instance [{total_inputs} / {num_rows} rows filled {:.2}%]", total_inputs as f64 / num_rows as f64 * 100.0 ); @@ -348,7 +347,7 @@ impl DmaUnalignedSM { ); println!("TRACE DmaUnalignedSM AIR_VALUES {:?}", air_values); } - timer_stop_and_log_info!(DMA_UNALIGNED_TRACE); + timer_stop_and_log_trace!(DMA_UNALIGNED_TRACE); let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); Ok(AirInstance::new_from_trace(from_trace)) } diff --git a/precompiles/helpers/src/dma.rs b/precompiles/helpers/src/dma.rs index c21896bf4..955fcdc86 100644 --- a/precompiles/helpers/src/dma.rs +++ b/precompiles/helpers/src/dma.rs @@ -15,6 +15,36 @@ pub struct DmaValues { pub src_offset_after_pre: u64, } +const FAST_ENCODE_TABLE_SIZE: usize = 8 * 8 * 16; +const FAST_ENCODE_TABLE: [u64; FAST_ENCODE_TABLE_SIZE] = generate_fast_encode_table(); + +const fn generate_fast_encode_table() -> [u64; FAST_ENCODE_TABLE_SIZE] { + let mut table = [0u64; FAST_ENCODE_TABLE_SIZE]; + // fill table + let mut dst_offset: u64 = 0; + while dst_offset < 8 { + let base_index = dst_offset << 7; + let mut src_offset: u64 = 0; + while src_offset < 8 { + let index = (base_index + (src_offset << 4)) as usize; + let mut count: usize = 0; + while count < 16 { + let value = DmaInfo::encode_memcpy(dst_offset, src_offset, count); + let loop_count = DmaInfo::get_loop_count(value) as u64; + // The table is create to add directly de loop count and after all values + // are correct, for this reason substract de count, because we need diference + // between loop_count (shifted 32) and count (shifted 29) + table[index + count] = ((value & 0x0000_0000_FFFF_FFFF) + (loop_count << 32)) + .wrapping_sub((count as u64) << 29); + count += 1; + } + src_offset += 1; + } + dst_offset += 1; + } + table +} + pub struct DmaInfo {} impl DmaInfo { @@ -29,17 +59,26 @@ impl DmaInfo { Self::get_post_count(encoded), Self::get_extra_src_reads(encoded)) } - pub fn encode_memcpy(dst: u64, src: u64, count: usize) -> u64 { - // bits t_bits - // loop_count 32 32 - // pre_writes: 0,1,2 2 34 - // dst_offset: 0-7 3 37 - // src_offset: 0-7 3 40 - // pre_count: 0-7 3 43 - // post_count: 0-7 3 46 - // double_src_pre: 0,1 1 47 - // double_src_post: 0,1 1 48 - // extra_src_reads: 0-4 3 51 + #[inline(always)] + pub const fn fast_encode_memcpy(dst: u64, src: u64, count: usize) -> u64 { + let table_count = if count >= 16 { count & 0x07 | 0x08 } else { count }; + FAST_ENCODE_TABLE[(((dst & 0x07) << 7) + ((src & 0x07) << 4)) as usize + table_count] + .wrapping_add((count as u64) << 29) + } + + pub const fn encode_memcpy(dst: u64, src: u64, count: usize) -> u64 { + // #bits bits + // pre_count: 0-7 3 0-2 + // post_count: 0-7 3 3-5 + // pre_writes: 0,1,2 2 6-7 + // dst_offset: 0-7 3 8-10 + // src_offset: 0-7 3 11-13 + // double_src_pre: 0,1 1 14 + // double_src_post: 0,1 1 15 + // extra_src_reads: 0-3 2 16-17 + // src64_inc_by_pre: 1 18 + // unaligned_dst_src: 1 20 + // loop_count 32 32-63 let dst_offset = dst & 0x07; let src_offset = src & 0x07; @@ -64,96 +103,102 @@ impl DmaInfo { let extra_src_reads = if count == 0 { 0 } else { (((src + count - 1) >> 3) - (src >> 3) + 1) - loop_count }; - (loop_count + (pre_writes << 32)) - | (dst_offset << 34) - | (src_offset << 37) - | (pre_count << 40) - | (post_count << 43) - | ((double_src_pre as u64) << 46) - | ((double_src_post as u64) << 47) - | (extra_src_reads << 48) + let src64_inc_by_pre = (pre_count > 0 && (src_offset + pre_count) >= 8) as u64; + let unaligned_dst_src = (src_offset != dst_offset) as u64; + + pre_count + | (post_count << 3) + | (pre_writes << 6) + | (dst_offset << 8) + | (src_offset << 11) + | ((double_src_pre as u64) << 14) + | ((double_src_post as u64) << 15) + | (extra_src_reads << 16) + | (src64_inc_by_pre << 18) + | (unaligned_dst_src << 19) + | (pre_count << 29) // optimization to read loop_count * 8 + pre_count + | (loop_count << 32) } - pub fn get_extra_src_reads(encoded: u64) -> usize { - (encoded as usize) >> 48 & 0x07 + pub const fn get_extra_src_reads(encoded: u64) -> usize { + (encoded as usize) >> 16 & 0x03 } - pub fn get_count(encoded: u64) -> usize { + pub const fn get_count(encoded: u64) -> usize { Self::get_loop_count(encoded) * 8 + Self::get_pre_count(encoded) + Self::get_post_count(encoded) } - pub fn get_dst_offset(encoded: u64) -> usize { - (encoded as usize >> 34) & 0x07 + pub const fn get_dst_offset(encoded: u64) -> usize { + (encoded as usize >> 8) & 0x07 } - pub fn get_src_offset(encoded: u64) -> usize { - (encoded as usize >> 37) & 0x07 + pub const fn get_src_offset(encoded: u64) -> usize { + (encoded as usize >> 11) & 0x07 } - pub fn get_loop_count(encoded: u64) -> usize { - (encoded & 0xFFFF_FFFF) as usize + pub const fn get_loop_count(encoded: u64) -> usize { + (encoded >> 32) as usize } - pub fn get_pre_writes(encoded: u64) -> usize { - (encoded as usize >> 32) & 0x03 + pub const fn get_pre_writes(encoded: u64) -> usize { + (encoded as usize >> 6) & 0x03 } - pub fn is_double_read_pre(encoded: u64) -> bool { - encoded & (1 << 46) != 0 + pub const fn is_double_read_pre(encoded: u64) -> bool { + encoded & (1 << 14) != 0 } - pub fn is_double_read_post(encoded: u64) -> bool { - encoded & (1 << 47) != 0 + pub const fn is_double_read_post(encoded: u64) -> bool { + encoded & (1 << 15) != 0 } - pub fn get_pre_count(encoded: u64) -> usize { - (encoded as usize >> 40) & 0x07 + pub const fn get_pre_count(encoded: u64) -> usize { + (encoded as usize) & 0x07 } - pub fn get_post_count(encoded: u64) -> usize { - (encoded as usize >> 43) & 0x07 + pub const fn get_post_count(encoded: u64) -> usize { + (encoded as usize >> 3) & 0x07 } - pub fn get_pre(encoded: u64) -> u8 { + pub const fn get_pre(encoded: u64) -> u8 { (Self::get_pre_count(encoded) > 0) as u8 + Self::is_double_read_pre(encoded) as u8 } - pub fn get_post(encoded: u64) -> u8 { + pub const fn get_post(encoded: u64) -> u8 { (Self::get_post_count(encoded) > 0) as u8 + Self::is_double_read_post(encoded) as u8 } - pub fn get_src64_inc_by_pre(encoded: u64) -> usize { - let pre_count = Self::get_pre_count(encoded); - (pre_count > 0 && (Self::get_src_offset(encoded) + pre_count) >= 8) as usize + pub const fn get_src64_inc_by_pre(encoded: u64) -> usize { + (encoded as usize >> 18) & 0x01 } - pub fn get_loop_data_offset(encoded: u64) -> usize { + pub const fn get_loop_data_offset(encoded: u64) -> usize { let pre_count = Self::get_pre_count(encoded); Self::get_pre_writes(encoded) + (pre_count > 0 && (Self::get_src_offset(encoded) + pre_count) >= 8) as usize } - pub fn get_loop_src_offset(encoded: u64) -> u8 { + pub const fn get_loop_src_offset(encoded: u64) -> u8 { (Self::get_src_offset(encoded) + Self::get_pre_count(encoded)) as u8 & 0x07 } - pub fn get_src_size(encoded: u64) -> usize { + pub const fn get_src_size(encoded: u64) -> usize { Self::get_loop_count(encoded) + Self::get_extra_src_reads(encoded) } - pub fn get_data_size(encoded: u64) -> usize { + pub const fn get_data_size(encoded: u64) -> usize { Self::get_pre_writes(encoded) + Self::get_src_size(encoded) } - pub fn get_post_data_offset(encoded: u64) -> usize { + pub const fn get_post_data_offset(encoded: u64) -> usize { Self::get_pre_writes(encoded) + Self::get_src_size(encoded) - (Self::is_double_read_post(encoded) as usize + 1) } - pub fn get_pre_write_offset(_encoded: u64) -> usize { + pub const fn get_pre_write_offset(_encoded: u64) -> usize { 0 } - pub fn get_post_write_offset(encoded: u64) -> usize { + pub const fn get_post_write_offset(encoded: u64) -> usize { (Self::get_pre_count(encoded) != 0) as usize } - pub fn get_pre_data_offset(encoded: u64) -> usize { + pub const fn get_pre_data_offset(encoded: u64) -> usize { Self::get_pre_writes(encoded) } } @@ -452,4 +497,94 @@ mod tests { ); println!("Checksum (to prevent optimization): {}", sum_bitwise); } + + #[test] + fn asm_fast_encode_table() { + let table = generate_fast_encode_table(); + for i in 0..256 { + let dst_offset = i >> 5; + let src_offset = (i >> 2) & 0x7; + println!( + "\t.quad 0x{:016x}, 0x{:016X}, 0x{:016X}, 0x{:016X} # {:4} - {:4} D{dst_offset} S{src_offset} C{}", + table[i * 4], + table[i * 4 + 1], + table[i * 4 + 2], + table[i * 4 + 3], + i * 4, + i * 4 + 3, + (i * 4) & 0xF + ); + } + assert!(table.len() == 1024); + } + #[test] + fn test_fast_encode_table() { + for dst in 0..256 { + for src in 0..256 { + for count in 0..256 { + let encode = DmaInfo::encode_memcpy(dst, src, count); + let fast_encode = DmaInfo::fast_encode_memcpy(dst, src, count); + assert_eq!( + encode, + fast_encode, + "testing dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}" + ); + } + } + } + } + + #[test] + fn benchmark_fast_encode_vs_encode_memcpy() { + use std::time::Instant; + + const ITERATIONS: usize = 10_000_000; + let mut checksum1 = DmaInfo::encode_memcpy(0, 0, 8); + let mut checksum2 = DmaInfo::fast_encode_memcpy(0, 0, 8); + + // Benchmark encode_memcpy (original) + let start = Instant::now(); + for i in 0..ITERATIONS { + let dst = (i & 0xFF) as u64; + let src = ((i >> 8) & 0xFF) as u64; + let count = (i >> 16) & 0xFF; + checksum1 ^= DmaInfo::encode_memcpy(dst, src, count); + } + let duration_encode = start.elapsed(); + + // Benchmark fast_encode (table-based) + let start = Instant::now(); + for i in 0..ITERATIONS { + let dst = (i & 0xFF) as u64; + let src = ((i >> 8) & 0xFF) as u64; + let count = (i >> 16) & 0xFF; + checksum2 ^= DmaInfo::fast_encode_memcpy(dst, src, count); + } + let duration_fast = start.elapsed(); + + // Verify both produce same results + assert_eq!(checksum1, checksum2, "Checksums must match!"); + + // Print results + println!("\n═══════════════════════════════════════════════════════"); + println!(" DMA Encoding Benchmark ({} iterations)", ITERATIONS); + println!("═══════════════════════════════════════════════════════"); + println!( + " encode_memcpy: {:?} ({:.2} ns/op)", + duration_encode, + duration_encode.as_nanos() as f64 / ITERATIONS as f64 + ); + println!( + " fast_encode: {:?} ({:.2} ns/op)", + duration_fast, + duration_fast.as_nanos() as f64 / ITERATIONS as f64 + ); + println!("───────────────────────────────────────────────────────"); + let speedup = duration_encode.as_nanos() as f64 / duration_fast.as_nanos() as f64; + println!(" Speedup: {:.2}x faster", speedup); + println!("═══════════════════════════════════════════════════════\n"); + + // Assert that fast_encode is actually faster + assert!(duration_fast < duration_encode, "fast_encode should be faster than encode_memcpy"); + } } diff --git a/state-machines/mem-cpp/cpp/mem_align_counter.cpp b/state-machines/mem-cpp/cpp/mem_align_counter.cpp index 103b9b455..08923c01e 100644 --- a/state-machines/mem-cpp/cpp/mem_align_counter.cpp +++ b/state-machines/mem-cpp/cpp/mem_align_counter.cpp @@ -6,17 +6,6 @@ #include #include - -#define FLAGS_1_BYTE_READ 1 -#define FLAGS_2_BYTES_READ 2 -#define FLAGS_4_BYTES_READ 4 -#define FLAGS_8_BYTES_READ 8 -#define FLAGS_1_BYTE_CLEAR_WRITE (MEM_WRITE_FLAG + MEM_WRITE_BYTE_CLEAR_FLAG + 1) -#define FLAGS_1_BYTE_WRITE (MEM_WRITE_FLAG + 1) -#define FLAGS_2_BYTES_WRITE (MEM_WRITE_FLAG + 2) -#define FLAGS_4_BYTES_WRITE (MEM_WRITE_FLAG + 4) -#define FLAGS_8_BYTES_WRITE (MEM_WRITE_FLAG + 8) - MemAlignCounter::MemAlignCounter(std::shared_ptr context) :context(context) { total_counters.chunk_id = 0xFFFFFFFF; total_counters.full_5 = 0; @@ -59,13 +48,13 @@ void MemAlignCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData uint32_t write_byte = 0; for (uint32_t i = 0; i < chunk_size; i++) { - switch (chunk_data[i].flags & 0xFF) { + switch (chunk_data[i].flags & 0x3F) { // 1 byte read - case FLAGS_1_BYTE_READ: + case MOPS_READ_1: read_byte += 1; break; // 2 bytes read - case FLAGS_2_BYTES_READ: + case MOPS_READ_2: if ((chunk_data[i].addr & 0x07) > 6) { full_3 += 1; } else { @@ -73,7 +62,7 @@ void MemAlignCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData } break; // 4 bytes read - case FLAGS_4_BYTES_READ: + case MOPS_READ_4: if ((chunk_data[i].addr & 0x07) > 4) { full_3 += 1; } else { @@ -81,23 +70,22 @@ void MemAlignCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData } break; // 8 bytes read - case FLAGS_8_BYTES_READ: + case MOPS_READ_8: if ((chunk_data[i].addr & 0x07) > 0) { full_3 += 1; } // if chunk_data[i].addr & 0x07 == 0 ==> aligned read break; // 1 byte write (clear) - case FLAGS_1_BYTE_CLEAR_WRITE: + case MOPS_CWRITE_1: write_byte += 1; break; - // 1 byte write - case FLAGS_1_BYTE_WRITE: + case MOPS_WRITE_1: full_3 += 1; break; // 2 bytes write - case FLAGS_2_BYTES_WRITE: + case MOPS_WRITE_2: if ((chunk_data[i].addr & 0x07) > 6) { full_5 += 1; } else { @@ -105,7 +93,7 @@ void MemAlignCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData } break; // 4 bytes write - case FLAGS_4_BYTES_WRITE: + case MOPS_WRITE_4: if ((chunk_data[i].addr & 0x07) > 4) { full_5 += 1; } else { @@ -113,12 +101,48 @@ void MemAlignCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData } break; // 8 bytes write - case FLAGS_8_BYTES_WRITE: + case MOPS_WRITE_8: if ((chunk_data[i].addr & 0x07) > 0) { full_5 += 1; } // if chunk_data[i].addr & 0x07 == 0 ==> aligned write break; + case MOPS_BLOCK_READ + 0x00: + case MOPS_BLOCK_READ + 0x10: + case MOPS_BLOCK_READ + 0x20: + case MOPS_BLOCK_READ + 0x30: + if ((chunk_data[i].addr & 0x07) > 0) { + const uint32_t count = chunk_data[i].flags >> MOPS_BLOCK_COUNT_SBITS; + full_5 += count; + } + break; + case MOPS_BLOCK_WRITE + 0x00: + case MOPS_BLOCK_WRITE + 0x10: + case MOPS_BLOCK_WRITE + 0x20: + case MOPS_BLOCK_WRITE + 0x30: + if ((chunk_data[i].addr & 0x07) > 0) { + const uint32_t count = chunk_data[i].flags >> MOPS_BLOCK_COUNT_SBITS; + full_5 += count; + } + break; + + case MOPS_ALIGNED_READ + 0x00: + case MOPS_ALIGNED_READ + 0x10: + case MOPS_ALIGNED_READ + 0x20: + case MOPS_ALIGNED_READ + 0x30: + case MOPS_ALIGNED_WRITE + 0x00: + case MOPS_ALIGNED_WRITE + 0x10: + case MOPS_ALIGNED_WRITE + 0x20: + case MOPS_ALIGNED_WRITE + 0x30: + case MOPS_ALIGNED_BLOCK_READ + 0x00: + case MOPS_ALIGNED_BLOCK_READ + 0x10: + case MOPS_ALIGNED_BLOCK_READ + 0x20: + case MOPS_ALIGNED_BLOCK_READ + 0x30: + case MOPS_ALIGNED_BLOCK_WRITE + 0x00: + case MOPS_ALIGNED_BLOCK_WRITE + 0x10: + case MOPS_ALIGNED_BLOCK_WRITE + 0x20: + case MOPS_ALIGNED_BLOCK_WRITE + 0x30: + break; default: printf("MemAlignCounter: Unknown flags: 0x%X\n", chunk_data[i].flags); assert(false && "Unknown flags in MemAlignCounter"); diff --git a/state-machines/mem-cpp/cpp/mem_config.hpp b/state-machines/mem-cpp/cpp/mem_config.hpp index 46dd7e63d..d198a850c 100644 --- a/state-machines/mem-cpp/cpp/mem_config.hpp +++ b/state-machines/mem-cpp/cpp/mem_config.hpp @@ -55,7 +55,41 @@ #define NO_CHUNK_ID 0xFFFFFFFF #define EMPTY_PAGE 0xFFFFFFFF -#define MEM_WRITE_FLAG 0x10 -#define MEM_WRITE_BYTE_CLEAR_FLAG 0x20 + +// SINGLE WRITE FLAGS +// bits +// bytes(4) 0-3 (values 1,2,4,8) +// write_flag (1) 4 +// clear_flag (1) 8 + +// ALIGNED WRITE BLOCKS FLAGS +// bits +// bytes(4) 0-3 (14 read block/15 write block) +// word_count(28) 4-31 2^28 * 2^3 = 2^31 bytes = 2GB MAX_MEMCPY_SIZE + + +#define MOPS_WRITE_FLAG 0x10 +#define MOPS_WRITE_BYTE_CLEAR_FLAG 0x20 + +#define MOPS_READ_8 0x08 +#define MOPS_READ_4 0x04 +#define MOPS_READ_2 0x02 +#define MOPS_READ_1 0x01 + +#define MOPS_WRITE_8 0x18 +#define MOPS_WRITE_4 0x14 +#define MOPS_WRITE_2 0x12 +#define MOPS_WRITE_1 0x11 + +#define MOPS_CWRITE_1 0x31 + +#define MOPS_BLOCK_READ 0x0A +#define MOPS_BLOCK_WRITE 0x0B +#define MOPS_ALIGNED_READ 0x0C +#define MOPS_ALIGNED_WRITE 0x0D +#define MOPS_ALIGNED_BLOCK_READ 0x0E +#define MOPS_ALIGNED_BLOCK_WRITE 0x0F + +#define MOPS_BLOCK_COUNT_SBITS 4 #endif diff --git a/state-machines/mem-cpp/cpp/mem_counter.cpp b/state-machines/mem-cpp/cpp/mem_counter.cpp index cc03b8254..c13d839ac 100644 --- a/state-machines/mem-cpp/cpp/mem_counter.cpp +++ b/state-machines/mem-cpp/cpp/mem_counter.cpp @@ -1,6 +1,9 @@ #include "mem_counter.hpp" +#include +#include #include #include +#include #define ST_INI 0 #define ST_READ 1 @@ -10,6 +13,8 @@ #define ST_READ_TO_WRITE ((ST_WRITE - ST_READ) << ST_BITS_OFFSET) #define ST_X_TO_INI_MASK (0xFFFFFFFF >> (32 - ST_BITS_OFFSET)) +#define ALIGN_MASK 0xFFFF'FFFF'FFFF'FFF8ULL + MemCounter::MemCounter(uint32_t id, std::shared_ptr context) :id(id), context(context), addr_mask(id * 8) { count = 0; @@ -100,32 +105,140 @@ void MemCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData *chun const uint8_t bytes = chunk_data->flags & 0x0F; const uint32_t addr = chunk_data->addr; switch (bytes) { - case 1: // byte - case 2: // half word - case 4: // word - case 8: // double word + // byte + case 1: + if ((addr & ADDR_MASK) != addr_mask) { + continue; + } + incr_counter(addr & ALIGN_MASK, chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + break; + + // half word + case 2: + if ((addr & ADDR_MASK) == addr_mask) { + incr_counter(addr & ALIGN_MASK, chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + else if (((addr + 1) & ADDR_MASK) == addr_mask) { + incr_counter((addr & ALIGN_MASK) + 8 , chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + break; + + // word + case 4: + if ((addr & ADDR_MASK) == addr_mask) { + incr_counter(addr & ALIGN_MASK, chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + else if (((addr + 3) & ADDR_MASK) == addr_mask) { + incr_counter((addr & ALIGN_MASK) + 8, chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + break; + + // double word + case 8: + if ((addr & 0x07) == 0) { + // aligned access + if ((addr & ADDR_MASK) != addr_mask) { + continue; + } + incr_counter(addr, chunk_id, true, chunk_data->flags & MOPS_WRITE_FLAG); + } else { + const uint32_t aligned_addr = addr & ALIGN_MASK; + + if ((aligned_addr & ADDR_MASK) == addr_mask) { + incr_counter(aligned_addr, chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + else if (((aligned_addr + 7) & ADDR_MASK) == addr_mask) { + incr_counter(aligned_addr + 8 , chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + } + break; + + case MOPS_ALIGNED_READ: { + assert((addr & 0x07) == 0); + if ((addr & ADDR_MASK) == addr_mask) { + incr_counter(addr , chunk_id, true, false); + } + break; + } + + case MOPS_ALIGNED_WRITE: { + assert((addr & 0x07) == 0); + if ((addr & ADDR_MASK) == addr_mask) { + incr_counter(addr , chunk_id, true, true); + } break; + } + + case MOPS_BLOCK_READ: + case MOPS_BLOCK_WRITE: { + bool write = bytes == MOPS_BLOCK_WRITE; + const uint32_t count = chunk_data->flags >> MOPS_BLOCK_COUNT_SBITS; + if ((addr & 0x07) == 0) { + uint32_t to_addr = addr + count * 8; + uint32_t c_addr = (addr & ~ADDR_MASK) + addr_mask; + if (c_addr < addr) { + c_addr += (MAX_THREADS * 8); + } + while (c_addr < to_addr) { + incr_counter(c_addr , chunk_id, true, write); + c_addr += (MAX_THREADS * 8); + } + } else { + // increase range, because if width = 8 and not aligned means + // each access is double, addr and addr + 8 + const uint32_t from_addr = (addr & ~0x07); + const uint32_t to_addr = from_addr + (count + 1) * 8; + uint32_t c_addr = (from_addr & ~ADDR_MASK) + addr_mask; + if (c_addr < from_addr) { + c_addr += (MAX_THREADS * 8); + } + while (c_addr < to_addr) { + incr_counter(c_addr , chunk_id, false, write); + c_addr += (MAX_THREADS * 8); + } + } + break; + } + + case MOPS_ALIGNED_BLOCK_READ: + case MOPS_ALIGNED_BLOCK_WRITE: { + assert((addr & 0x07) == 0); + bool write = bytes == MOPS_ALIGNED_BLOCK_WRITE; + uint32_t count = chunk_data->flags >> 4; + uint32_t to_addr = addr + count * 8; + uint32_t c_addr = (addr & ~ADDR_MASK) + addr_mask; + if (c_addr < addr) { + c_addr += (MAX_THREADS * 8); + } + while (c_addr < to_addr) { + incr_counter(c_addr , chunk_id, true, write); + c_addr += (MAX_THREADS * 8); + } + break; + } + + default: std::ostringstream msg; msg << "ERROR: MemCounter execute_chunk: invalid bytes size " << bytes << " at chunk_id " << chunk_id << " addr 0x" << std::hex << addr; throw std::runtime_error(msg.str()); } - if (bytes == 8 && (addr & 0x07) == 0) { - // aligned access - if ((addr & ADDR_MASK) != addr_mask) { - continue; - } - incr_counter(addr, chunk_id, true, chunk_data->flags & MEM_WRITE_FLAG); - } else { - const uint32_t aligned_addr = addr & 0xFFFFFFF8; + // if (bytes == 8 && (addr & 0x07) == 0) { + // // aligned access + // if ((addr & ADDR_MASK) != addr_mask) { + // continue; + // } + // incr_counter(addr, chunk_id, true, chunk_data->flags & MEM_WRITE_FLAG); + // } else { + // const uint32_t aligned_addr = addr & 0xFFFFFFF8; - if ((aligned_addr & ADDR_MASK) == addr_mask) { - incr_counter(aligned_addr, chunk_id, false, chunk_data->flags & MEM_WRITE_FLAG); - } - else if ((bytes + (addr & 0x07)) > 8 && ((aligned_addr + 8) & ADDR_MASK) == addr_mask) { - incr_counter(aligned_addr + 8 , chunk_id, false, chunk_data->flags & MEM_WRITE_FLAG); - } - } + // if ((aligned_addr & ADDR_MASK) == addr_mask) { + // incr_counter(aligned_addr, chunk_id, false, chunk_data->flags & MEM_WRITE_FLAG); + // } + // else if ((bytes + (addr & 0x07)) > 8 && ((aligned_addr + 8) & ADDR_MASK) == addr_mask) { + // incr_counter(aligned_addr + 8 , chunk_id, false, chunk_data->flags & MEM_WRITE_FLAG); + // } + // } } #ifdef MEM_STATS_ACTIVE diff --git a/state-machines/mem-cpp/cpp/tools.hpp b/state-machines/mem-cpp/cpp/tools.hpp index be1313c72..af166d9ac 100644 --- a/state-machines/mem-cpp/cpp/tools.hpp +++ b/state-machines/mem-cpp/cpp/tools.hpp @@ -60,7 +60,7 @@ inline uint32_t count_operations(MemCountersBusData *chunk_data, int count) { for (int i = 0; i < count; ++i) { const uint32_t bytes = chunk_data[i].flags & 0x0F; const uint32_t offset = chunk_data[i].addr & 0x07; - const bool wr = (chunk_data[i].flags & MEM_WRITE_FLAG) != 0; + const bool wr = (chunk_data[i].flags & MOPS_WRITE_FLAG) != 0; if (offset == 0 && bytes == 8) { cops = 1; } else if (offset + bytes > 8) { From ba4c6edaa8b2a5f46c98355b01a298525af20e99 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 21 Jan 2026 10:33:56 +0100 Subject: [PATCH 291/782] update pil2-compiler version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ed867a465..73ac8d527 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ categories = ["cryptography"] [workspace.metadata] gha_pil2_proofman_js_branch = "pre-develop-0.16.0" -gha_pil2_compiler_branch = "tags/v0.8.0" +gha_pil2_compiler_branch = "develop-0.9.0" [workspace] members = [ From c3b3b7b7b099e7e86dae5f4906f3899a051212a1 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 21 Jan 2026 11:02:22 +0100 Subject: [PATCH 292/782] Fix fcall MsbPos256 --- lib-c/c/src/fcall/fcall.cpp | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lib-c/c/src/fcall/fcall.cpp b/lib-c/c/src/fcall/fcall.cpp index dc6f02587..7410334e3 100644 --- a/lib-c/c/src/fcall/fcall.cpp +++ b/lib-c/c/src/fcall/fcall.cpp @@ -307,19 +307,28 @@ int MsbPos256 ( uint64_t * r // 2 x 64 bits ) { - const uint64_t * x = a; - const uint64_t * y = &a[4]; + const uint64_t n = a[0]; // number of inputs + const uint64_t * params = &a[1]; - for (int i=3; i>=0; i--) + for (int limb=3; limb>=0; limb--) { - if ((x[i] != 0) || (y[i] != 0)) + // Find max value at this limb position across all inputs + uint64_t max_word = 0; + for (uint64_t i=0; i y[i] ? x[i] : y[i]; - r[0] = i; - r[1] = msb_pos(word); + uint64_t word = params[i * 4 + limb]; + if (word > max_word) { + max_word = word; + } + } + if (max_word != 0) + { + r[0] = limb; + r[1] = msb_pos(max_word); return 0; } } + printf("MsbPos256() error: both x and y are zero\n"); exit(-1); } @@ -927,7 +936,12 @@ int BigIntDivCtx ( ctx->result[2 + quotient_size + i] = 0; } - return 2 + quotient_size + remainder_size; + uint64_t total_size = 2 + quotient_size + remainder_size; + assert(total_size < FCALL_RESULT_MAX_SIZE); + + ctx->result_size = total_size; + + return total_size; } /************************/ @@ -1021,6 +1035,8 @@ int BLS12_381Fp2SqrtCtx ( ); if (result != 0) return result; } + + ctx->result_size = 13; return 0; } From de81d90d4fa2facb4082aa2ed82953a6573c49cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20i=20Buxadera?= Date: Mon, 19 Jan 2026 14:56:50 +0100 Subject: [PATCH 293/782] Minimal compute capacity --- Cargo.lock | 2 ++ distributed/crates/common/src/dto.rs | 1 + distributed/crates/common/src/types.rs | 3 +++ .../coordinator/src/cli/handler_prove.rs | 2 ++ .../crates/coordinator/src/cli/main.rs | 5 ++++ .../crates/coordinator/src/coordinator.rs | 26 +++++++++++++++++-- .../crates/coordinator/src/workers_pool.rs | 21 ++++++++++++--- .../grpc-api/proto/zisk_distributed_api.proto | 3 ++- .../crates/grpc-api/src/conversions.rs | 2 ++ 9 files changed, 59 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d42a318a..27ed037f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1332,6 +1332,7 @@ dependencies = [ name = "executor" version = "0.16.0" dependencies = [ + "anyhow", "asm-runner", "crossbeam", "data-bus", @@ -4026,6 +4027,7 @@ dependencies = [ name = "sm-rom" version = "0.16.0" dependencies = [ + "anyhow", "asm-runner", "fields", "itertools 0.14.0", diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 1c8e7a621..a18510d43 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -86,6 +86,7 @@ impl Display for InputModeDto { pub struct LaunchProofRequestDto { pub data_id: DataId, pub compute_capacity: u32, + pub minimal_compute_capacity: Option, pub input_mode: InputModeDto, pub simulated_node: Option, } diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index e7b5b48b1..e42d66e59 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -245,6 +245,7 @@ pub struct Job { pub data_id: DataId, pub input_mode: InputModeDto, pub compute_capacity: ComputeCapacity, + pub minimal_compute_capacity: Option, pub workers: Vec, pub agg_worker_id: Option, pub partitions: Vec>, @@ -261,6 +262,7 @@ impl Job { data_id: DataId, input_mode: InputModeDto, compute_capacity: ComputeCapacity, + minimal_compute_capacity: Option, selected_workers: Vec, partitions: Vec>, execution_mode: JobExecutionMode, @@ -273,6 +275,7 @@ impl Job { data_id, input_mode, compute_capacity, + minimal_compute_capacity, workers: selected_workers, agg_worker_id: None, partitions, diff --git a/distributed/crates/coordinator/src/cli/handler_prove.rs b/distributed/crates/coordinator/src/cli/handler_prove.rs index f8ff8cccb..48678e016 100644 --- a/distributed/crates/coordinator/src/cli/handler_prove.rs +++ b/distributed/crates/coordinator/src/cli/handler_prove.rs @@ -16,6 +16,7 @@ pub async fn handle( direct_inputs: bool, compute_capacity: u32, simulated_node: Option, + minimal_compute_capacity: Option, ) -> Result<()> { // Initialize tracing - keep guard alive for application lifetime let _log_guard = zisk_distributed_common::tracing::init(None, None)?; @@ -54,6 +55,7 @@ pub async fn handle( let launch_proof_request = LaunchProofRequest { data_id, compute_capacity, + minimal_compute_capacity, input_mode: input_mode.into(), input_path, simulated_node, diff --git a/distributed/crates/coordinator/src/cli/main.rs b/distributed/crates/coordinator/src/cli/main.rs index 1d9b4df9f..708a792ae 100644 --- a/distributed/crates/coordinator/src/cli/main.rs +++ b/distributed/crates/coordinator/src/cli/main.rs @@ -77,6 +77,9 @@ enum ZiskCoordinatorCommands { #[arg(long, short, help = "Compute capacity needed to generate the proof")] compute_capacity: u32, + #[arg(long, short, help = "Minimal compute capacity needed to generate the proof")] + minimal_compute_capacity: Option, + #[arg(long, help = "Simulated node ID")] simulated_node: Option, }, @@ -94,6 +97,7 @@ async fn main() -> Result<()> { input, direct_inputs, compute_capacity, + minimal_compute_capacity, simulated_node, }) => { // Run the "prove" subcommand @@ -104,6 +108,7 @@ async fn main() -> Result<()> { direct_inputs, compute_capacity, simulated_node, + minimal_compute_capacity, ) .await } diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index f41ac3314..560baab88 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -284,6 +284,15 @@ impl Coordinator { )); } + if let Some(minimal_compute_capacity) = request.minimal_compute_capacity { + if minimal_compute_capacity > request.compute_capacity { + error!("Invalid requested minimal compute capacity"); + return Err(CoordinatorError::InvalidArgument( + "Minimal compute capacity must not exceed compute capacity".to_string(), + )); + } + } + // Check if we have enough capacity to compute the proof is already checked // in create_job > partition_and_allocate_by_capacity @@ -329,18 +338,25 @@ impl Coordinator { let required_compute_capacity = ComputeCapacity::from(request.compute_capacity); + let minimal_compute_capacity = if request.minimal_compute_capacity.is_some() { + request.minimal_compute_capacity.map(ComputeCapacity::from) + } else { + None + }; + // Create and configure a new job let mut job = self .create_job( request.data_id.clone(), required_compute_capacity, + minimal_compute_capacity, request.input_mode, request.simulated_node, ) .await?; info!( - "[Job] Started {} successfully Inputs: {} Capacity: {} Workers: {}", + "[Job] Started {} successfully Inputs: {} Compute Capacity: {} Workers: {}", job.job_id, job.input_mode, job.compute_capacity, @@ -527,6 +543,7 @@ impl Coordinator { &self, data_id: DataId, required_compute_capacity: ComputeCapacity, + minimal_compute_capacity: Option, input_mode: InputModeDto, simulated_node: Option, ) -> CoordinatorResult { @@ -538,7 +555,11 @@ impl Coordinator { let (selected_workers, mut partitions) = self .workers_pool - .partition_and_allocate_by_capacity(required_compute_capacity, execution_mode) + .partition_and_allocate_by_capacity( + required_compute_capacity, + minimal_compute_capacity, + execution_mode, + ) .await?; if let Some(simulated_node) = simulated_node { @@ -549,6 +570,7 @@ impl Coordinator { data_id, input_mode, required_compute_capacity, + minimal_compute_capacity, selected_workers, partitions, execution_mode, diff --git a/distributed/crates/coordinator/src/workers_pool.rs b/distributed/crates/coordinator/src/workers_pool.rs index 293d931a8..52a0fefde 100644 --- a/distributed/crates/coordinator/src/workers_pool.rs +++ b/distributed/crates/coordinator/src/workers_pool.rs @@ -427,6 +427,7 @@ impl WorkersPool { pub async fn partition_and_allocate_by_capacity( &self, required_compute_capacity: ComputeCapacity, + minimal_compute_capacity: Option, execution_mode: JobExecutionMode, ) -> CoordinatorResult<(Vec, Vec>)> { // Simulation mode requires exactly one worker @@ -445,6 +446,14 @@ impl WorkersPool { )); } + if let Some(min_cc) = minimal_compute_capacity { + if min_cc.compute_units > required_compute_capacity.compute_units { + return Err(CoordinatorError::InvalidArgument( + "Minimal compute capacity cannot exceed required capacity".to_string(), + )); + } + } + let workers = self.workers.write().await; // For simulation mode, replicate single worker multiple times @@ -470,7 +479,14 @@ impl WorkersPool { available_workers.iter().map(|(_, p)| p.compute_capacity.compute_units).sum(); // Check if we have enough total capacity - if required_compute_capacity.compute_units > available_capacity { + + let compute_capacity_needed = if let Some(min_cc) = minimal_compute_capacity { + min_cc.compute_units + } else { + required_compute_capacity.compute_units + }; + + if compute_capacity_needed > available_capacity { return Err(CoordinatorError::InsufficientCapacity); } @@ -496,11 +512,10 @@ impl WorkersPool { // Step 2: Distribute work units using round-robin allocation let num_workers = selected_workers.len(); - let total_units = required_compute_capacity.compute_units; let mut worker_allocations = vec![Vec::new(); num_workers]; // Round-robin assignment of compute units - for unit in 0..total_units { + for unit in 0..total_capacity { let worker_idx = (unit as usize) % num_workers; // Check if this worker still has capacity diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index 277633ac6..93af2a5e8 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -57,7 +57,8 @@ message LaunchProofRequest { uint32 compute_capacity = 2; InputMode input_mode = 3; optional string input_path = 4; - optional uint32 simulated_node = 5; // If set, indicates this is a simulated worker + optional uint32 minimal_compute_capacity = 5; + optional uint32 simulated_node = 6; // If set, indicates this is a simulated worker } enum InputMode { diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index 0d450d710..74406abc3 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -168,6 +168,7 @@ impl From for LaunchProofRequest { LaunchProofRequest { data_id: dto.data_id.into(), compute_capacity: dto.compute_capacity, + minimal_compute_capacity: dto.minimal_compute_capacity, input_mode: input_mode.into(), input_path, simulated_node: dto.simulated_node, @@ -184,6 +185,7 @@ impl TryFrom for LaunchProofRequestDto { Ok(LaunchProofRequestDto { data_id: req.data_id.into(), compute_capacity: req.compute_capacity, + minimal_compute_capacity: req.minimal_compute_capacity, input_mode: match InputMode::try_from(req.input_mode).unwrap_or(InputMode::None) { InputMode::None => InputModeDto::InputModeNone, InputMode::Path => { From 1b40ec25c61b7a4c2d5766a2a7a60fe7708cf089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20i=20Buxadera?= Date: Mon, 19 Jan 2026 15:04:10 +0100 Subject: [PATCH 294/782] Cargo lock --- Cargo.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27ed037f7..6d42a318a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1332,7 +1332,6 @@ dependencies = [ name = "executor" version = "0.16.0" dependencies = [ - "anyhow", "asm-runner", "crossbeam", "data-bus", @@ -4027,7 +4026,6 @@ dependencies = [ name = "sm-rom" version = "0.16.0" dependencies = [ - "anyhow", "asm-runner", "fields", "itertools 0.14.0", From e618e3c695ade8e80d4da49fc9f6d1b88578fb5a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:10:38 +0000 Subject: [PATCH 295/782] removed optional from minimal_comput_capacity --- distributed/crates/common/src/dto.rs | 2 +- distributed/crates/common/src/types.rs | 4 ++-- .../coordinator/src/cli/handler_prove.rs | 12 ++++++++++- .../crates/coordinator/src/cli/main.rs | 2 +- .../crates/coordinator/src/coordinator.rs | 21 +++++++------------ .../crates/coordinator/src/workers_pool.rs | 21 ++++++------------- .../grpc-api/proto/zisk_distributed_api.proto | 6 +++--- 7 files changed, 31 insertions(+), 37 deletions(-) diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index a18510d43..d31d13dbf 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -86,7 +86,7 @@ impl Display for InputModeDto { pub struct LaunchProofRequestDto { pub data_id: DataId, pub compute_capacity: u32, - pub minimal_compute_capacity: Option, + pub minimal_compute_capacity: u32, pub input_mode: InputModeDto, pub simulated_node: Option, } diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index e42d66e59..0a41e48df 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -245,7 +245,7 @@ pub struct Job { pub data_id: DataId, pub input_mode: InputModeDto, pub compute_capacity: ComputeCapacity, - pub minimal_compute_capacity: Option, + pub minimal_compute_capacity: ComputeCapacity, pub workers: Vec, pub agg_worker_id: Option, pub partitions: Vec>, @@ -262,7 +262,7 @@ impl Job { data_id: DataId, input_mode: InputModeDto, compute_capacity: ComputeCapacity, - minimal_compute_capacity: Option, + minimal_compute_capacity: ComputeCapacity, selected_workers: Vec, partitions: Vec>, execution_mode: JobExecutionMode, diff --git a/distributed/crates/coordinator/src/cli/handler_prove.rs b/distributed/crates/coordinator/src/cli/handler_prove.rs index 48678e016..059c68f78 100644 --- a/distributed/crates/coordinator/src/cli/handler_prove.rs +++ b/distributed/crates/coordinator/src/cli/handler_prove.rs @@ -15,8 +15,8 @@ pub async fn handle( input_path: Option, direct_inputs: bool, compute_capacity: u32, - simulated_node: Option, minimal_compute_capacity: Option, + simulated_node: Option, ) -> Result<()> { // Initialize tracing - keep guard alive for application lifetime let _log_guard = zisk_distributed_common::tracing::init(None, None)?; @@ -52,6 +52,16 @@ pub async fn handle( uuid::Uuid::new_v4().to_string() }; + // Check compute capacity + let minimal_compute_capacity = minimal_compute_capacity.unwrap_or(compute_capacity); + if minimal_compute_capacity > compute_capacity { + return Err(anyhow::anyhow!( + "Minimal compute capacity ({}) cannot be greater than compute capacity ({})", + minimal_compute_capacity, + compute_capacity + )); + } + let launch_proof_request = LaunchProofRequest { data_id, compute_capacity, diff --git a/distributed/crates/coordinator/src/cli/main.rs b/distributed/crates/coordinator/src/cli/main.rs index 708a792ae..62cb5be60 100644 --- a/distributed/crates/coordinator/src/cli/main.rs +++ b/distributed/crates/coordinator/src/cli/main.rs @@ -107,8 +107,8 @@ async fn main() -> Result<()> { input, direct_inputs, compute_capacity, - simulated_node, minimal_compute_capacity, + simulated_node, ) .await } diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 560baab88..affb0aca4 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -284,13 +284,11 @@ impl Coordinator { )); } - if let Some(minimal_compute_capacity) = request.minimal_compute_capacity { - if minimal_compute_capacity > request.compute_capacity { - error!("Invalid requested minimal compute capacity"); - return Err(CoordinatorError::InvalidArgument( - "Minimal compute capacity must not exceed compute capacity".to_string(), - )); - } + if request.minimal_compute_capacity > request.compute_capacity { + error!("Invalid requested minimal compute capacity"); + return Err(CoordinatorError::InvalidArgument( + "Minimal compute capacity must not exceed compute capacity".to_string(), + )); } // Check if we have enough capacity to compute the proof is already checked @@ -337,12 +335,7 @@ impl Coordinator { self.pre_launch_proof(&request)?; let required_compute_capacity = ComputeCapacity::from(request.compute_capacity); - - let minimal_compute_capacity = if request.minimal_compute_capacity.is_some() { - request.minimal_compute_capacity.map(ComputeCapacity::from) - } else { - None - }; + let minimal_compute_capacity = ComputeCapacity::from(request.minimal_compute_capacity); // Create and configure a new job let mut job = self @@ -543,7 +536,7 @@ impl Coordinator { &self, data_id: DataId, required_compute_capacity: ComputeCapacity, - minimal_compute_capacity: Option, + minimal_compute_capacity: ComputeCapacity, input_mode: InputModeDto, simulated_node: Option, ) -> CoordinatorResult { diff --git a/distributed/crates/coordinator/src/workers_pool.rs b/distributed/crates/coordinator/src/workers_pool.rs index 52a0fefde..67827999f 100644 --- a/distributed/crates/coordinator/src/workers_pool.rs +++ b/distributed/crates/coordinator/src/workers_pool.rs @@ -427,7 +427,7 @@ impl WorkersPool { pub async fn partition_and_allocate_by_capacity( &self, required_compute_capacity: ComputeCapacity, - minimal_compute_capacity: Option, + minimal_compute_capacity: ComputeCapacity, execution_mode: JobExecutionMode, ) -> CoordinatorResult<(Vec, Vec>)> { // Simulation mode requires exactly one worker @@ -446,12 +446,10 @@ impl WorkersPool { )); } - if let Some(min_cc) = minimal_compute_capacity { - if min_cc.compute_units > required_compute_capacity.compute_units { - return Err(CoordinatorError::InvalidArgument( - "Minimal compute capacity cannot exceed required capacity".to_string(), - )); - } + if minimal_compute_capacity.compute_units > required_compute_capacity.compute_units { + return Err(CoordinatorError::InvalidArgument( + "Minimal compute capacity cannot exceed required capacity".to_string(), + )); } let workers = self.workers.write().await; @@ -479,14 +477,7 @@ impl WorkersPool { available_workers.iter().map(|(_, p)| p.compute_capacity.compute_units).sum(); // Check if we have enough total capacity - - let compute_capacity_needed = if let Some(min_cc) = minimal_compute_capacity { - min_cc.compute_units - } else { - required_compute_capacity.compute_units - }; - - if compute_capacity_needed > available_capacity { + if minimal_compute_capacity.compute_units > available_capacity { return Err(CoordinatorError::InsufficientCapacity); } diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index 93af2a5e8..ca421892d 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -55,9 +55,9 @@ message WorkersListRequest { message LaunchProofRequest { string data_id = 1; uint32 compute_capacity = 2; - InputMode input_mode = 3; - optional string input_path = 4; - optional uint32 minimal_compute_capacity = 5; + uint32 minimal_compute_capacity = 3; + InputMode input_mode = 4; + optional string input_path = 5; optional uint32 simulated_node = 6; // If set, indicates this is a simulated worker } From fbc6e853ddf058242123e8779513f9e323682eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20i=20Buxadera?= Date: Wed, 21 Jan 2026 11:20:54 +0100 Subject: [PATCH 296/782] Setting compressed by parameter --- distributed/crates/worker/src/config.rs | 2 +- distributed/crates/worker/src/worker.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/distributed/crates/worker/src/config.rs b/distributed/crates/worker/src/config.rs index 6f94f5c37..09b1be093 100644 --- a/distributed/crates/worker/src/config.rs +++ b/distributed/crates/worker/src/config.rs @@ -179,7 +179,7 @@ impl Default for ProverServiceConfigDto { debug: None, verify_constraints: false, aggregation: false, - compressed: true, + compressed: false, preallocate: false, max_streams: None, number_threads_witness: None, diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index a737143ae..43b9011f9 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -699,7 +699,7 @@ impl Worker { ProofOptions { verify_constraints: false, aggregation: false, - compressed: false, + compressed: self.prover_config.compressed, verify_proofs: true, save_proofs: true, test_mode: false, @@ -713,7 +713,7 @@ impl Worker { ProofOptions { verify_constraints: false, aggregation: true, - compressed: true, + compressed: self.prover_config.compressed, verify_proofs: false, save_proofs: false, test_mode: false, From b75a079ac0b6fbb008e899db5066c78d52e8694d Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 21 Jan 2026 13:16:21 +0100 Subject: [PATCH 297/782] increase time test ubutu --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 738c7290a..6cfbc7cc9 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -47,7 +47,7 @@ jobs: test-x86_64: name: Test on Ubuntu x86_64 runs-on: self-hosted - timeout-minutes: 120 + timeout-minutes: 180 container: image: ubuntu:22.04 options: From 9847ad2ef731b4ad7ef717ca785e93d661a202e8 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Wed, 21 Jan 2026 13:45:48 +0000 Subject: [PATCH 298/782] Fix packed --- Cargo.lock | 11 ---- Cargo.toml | 32 +++++----- pil/src/pil_helpers/traces.rs | 2 +- precompiles/dma/Cargo.toml | 3 +- precompiles/dma/src/dma/dma.rs | 18 ++++-- .../dma/src/dma_64_aligned/dma_64_aligned.rs | 43 +++++++++----- .../dma/src/dma_pre_post/dma_pre_post.rs | 27 ++++++--- .../dma/src/dma_unaligned/dma_unaligned.rs | 59 +++++++++++-------- 8 files changed, 117 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d86cc3407..6327a51d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1040,7 +1040,6 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "fields", "num-bigint", @@ -1385,7 +1384,6 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "cfg-if", "num-bigint", @@ -2705,7 +2703,6 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "colored", "fields", @@ -3101,7 +3098,6 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "blake3", "borsh", @@ -3136,7 +3132,6 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "borsh", "colored", @@ -3167,7 +3162,6 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "fields", "itoa", @@ -3180,7 +3174,6 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "proc-macro2", "quote", @@ -3191,7 +3184,6 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "crossbeam-channel", "tracing", @@ -3200,7 +3192,6 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "colored", "fields", @@ -3211,7 +3202,6 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "bytemuck", "fields", @@ -5437,7 +5427,6 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "colored", "fields", diff --git a/Cargo.toml b/Cargo.toml index 73ac8d527..4f03fbb5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,23 +105,23 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +# fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } # Proofman Local development -# proofman = { path = "../pil2-proofman/proofman" } -# proofman-common = { path = "../pil2-proofman/common" } -# proofman-macros = { path = "../pil2-proofman/macros" } -# proofman-verifier = { path = "../pil2-proofman/verifier" } -# proofman-util = { path = "../pil2-proofman/util" } -# pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } -# witness = { path = "../pil2-proofman/witness" } -# fields = { path = "../pil2-proofman/fields" } +proofman = { path = "../pil2-proofman/proofman" } +proofman-common = { path = "../pil2-proofman/common" } +proofman-macros = { path = "../pil2-proofman/macros" } +proofman-verifier = { path = "../pil2-proofman/verifier" } +proofman-util = { path = "../pil2-proofman/util" } +pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } +witness = { path = "../pil2-proofman/witness" } +fields = { path = "../pil2-proofman/fields" } # External dependencies rayon = "1.10" diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 915eb17f3..eb94603e0 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "354f59043e8d6c486c5c9980e808997a07cf4121ebf521487b02c685c151c478"; +pub const PILOUT_HASH: &str = "28c8185c73aa9a58d122501d67edea0d8dd62d1893c974f4155b3944a7c84bbd"; pub const MERKLE_TREE_ARITY: u64 = 4; diff --git a/precompiles/dma/Cargo.toml b/precompiles/dma/Cargo.toml index 348ab34db..7c197b12b 100644 --- a/precompiles/dma/Cargo.toml +++ b/precompiles/dma/Cargo.toml @@ -31,7 +31,8 @@ generic-array = "0.14" default = [] dma_memcmp = [] debug_dma = [] -packed = [] +gpu = ["proofman-common/gpu", "packed"] +packed = ["proofman-common/packed"] no_lib_link = ["proofman-common/no_lib_link"] diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file diff --git a/precompiles/dma/src/dma/dma.rs b/precompiles/dma/src/dma/dma.rs index 8b30dcc9c..cffd8cbd6 100644 --- a/precompiles/dma/src/dma/dma.rs +++ b/precompiles/dma/src/dma/dma.rs @@ -12,11 +12,21 @@ use crate::{dma::dma_rom::DmaRom, DmaInput}; use precompiles_helpers::DmaInfo; #[cfg(feature = "packed")] -pub use zisk_pil::{DmaRowPacked as DmaTraceRow, DmaTracePacked as DmaTrace}; +pub use zisk_pil::{DmaTracePacked, DmaTraceRowPacked}; #[cfg(not(feature = "packed"))] pub use zisk_pil::{DmaTrace, DmaTraceRow}; +#[cfg(feature = "packed")] +type DmaTraceRowType = DmaTraceRowPacked; +#[cfg(feature = "packed")] +type DmaTraceType = DmaTracePacked; + +#[cfg(not(feature = "packed"))] +type DmaTraceRowType = DmaTraceRow; +#[cfg(not(feature = "packed"))] +type DmaTraceType = DmaTrace; + /// The `DmaSM` struct encapsulates the logic of the Dma State Machine. pub struct DmaSM { /// Reference to the PIL2 standard library. @@ -59,7 +69,7 @@ impl DmaSM { pub fn process_slice( &self, input: &DmaInput, - trace: &mut DmaTraceRow, + trace: &mut DmaTraceRowType, local_dual_7_bits_multiplicities: &mut [u64], local_22_bits_values: &mut Vec, local_24_bits_values: &mut Vec, @@ -131,7 +141,7 @@ impl DmaSM { /// * `trace` - A mutable reference to the Dma trace. /// * `input` - The operation data to process. #[inline(always)] - pub fn process_empty_slice(&self, trace: &mut DmaTraceRow) { + pub fn process_empty_slice(&self, trace: &mut DmaTraceRowType) { trace.set_count_lt_256(true); trace.set_h_count(0); trace.set_l_count(0); @@ -173,7 +183,7 @@ impl DmaSM { inputs: &[Vec], trace_buffer: Vec, ) -> ProofmanResult> { - let mut trace = DmaTrace::::new_from_vec(trace_buffer)?; + let mut trace = DmaTraceType::::new_from_vec(trace_buffer)?; let num_rows = trace.num_rows(); let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs index 3d0f6cc6d..7d5f2c4bd 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs @@ -7,7 +7,23 @@ use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use zisk_common::SegmentId; -use zisk_pil::{Dma64AlignedAirValues, Dma64AlignedTrace, Dma64AlignedTraceRow}; +use zisk_pil::Dma64AlignedAirValues; + +#[cfg(feature = "packed")] +pub use zisk_pil::{Dma64AlignedTracePacked, Dma64AlignedTraceRowPacked}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{Dma64AlignedTrace, Dma64AlignedTraceRow}; + +#[cfg(feature = "packed")] +type Dma64AlignedTraceRowType = Dma64AlignedTraceRowPacked; +#[cfg(feature = "packed")] +type Dma64AlignedTraceType = Dma64AlignedTracePacked; + +#[cfg(not(feature = "packed"))] +type Dma64AlignedTraceRowType = Dma64AlignedTraceRow; +#[cfg(not(feature = "packed"))] +type Dma64AlignedTraceType = Dma64AlignedTrace; use crate::{Dma64AlignedInput, DMA_64_ALIGNED_OPS_BY_ROW}; use precompiles_helpers::DmaInfo; @@ -46,7 +62,7 @@ impl Dma64AlignedSM { pub fn process_input( &self, input: &Dma64AlignedInput, - trace: &mut [Dma64AlignedTraceRow], + trace: &mut [Dma64AlignedTraceRowType], _local_16_bits_table: &mut [u32], // for input_cpy air_values: &mut Dma64AlignedAirValues, ) -> usize { @@ -130,7 +146,7 @@ impl Dma64AlignedSM { /// * `trace` - A mutable reference to the Dma trace. /// * `input` - The operation data to process. #[inline(always)] - pub fn process_empty_slice(&self, trace: &mut Dma64AlignedTraceRow) { + pub fn process_empty_slice(&self, trace: &mut Dma64AlignedTraceRowType) { trace.set_main_step(0); trace.set_is_mem_eq(false); trace.set_dst64(0); @@ -160,7 +176,7 @@ impl Dma64AlignedSM { is_last_segment: bool, trace_buffer: Vec, ) -> ProofmanResult> { - let mut trace = Dma64AlignedTrace::::new_from_vec(trace_buffer)?; + let mut trace = Dma64AlignedTraceType::::new_from_vec(trace_buffer)?; let num_rows = trace.num_rows(); let total_inputs: usize = inputs @@ -242,17 +258,14 @@ impl Dma64AlignedSM { } else { assert!(segment_id > 0); air_values.segment_previous_seq_end = F::ZERO; - air_values.segment_previous_dst64 = F::from_u32( - (trace_rows[0].dst64.as_canonical_u64() - self.op_x_rows as u64) as u32, - ); - air_values.segment_previous_src64 = F::from_u32( - (trace_rows[0].src64.as_canonical_u64() - self.op_x_rows as u64) as u32, - ); - air_values.segment_previous_main_step = trace_rows[0].main_step; - air_values.segment_previous_count = F::from_u32( - (trace_rows[0].count.as_canonical_u64() + self.op_x_rows as u64) as u32, - ); - air_values.segment_previous_is_mem_eq = trace_rows[0].is_mem_eq; + air_values.segment_previous_dst64 = + F::from_u32(trace_rows[0].get_dst64() - self.op_x_rows as u32); + air_values.segment_previous_src64 = + F::from_u32(trace_rows[0].get_src64() - self.op_x_rows as u32); + air_values.segment_previous_main_step = F::from_u64(trace_rows[0].get_main_step()); + air_values.segment_previous_count = + F::from_u32(trace_rows[0].get_count() + self.op_x_rows as u32); + air_values.segment_previous_is_mem_eq = F::from_bool(trace_rows[0].get_is_mem_eq()); } #[cfg(feature = "debug_dma")] { diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs index bed85186b..3b04aa360 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs @@ -9,10 +9,23 @@ use rayon::{ iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}, slice::{ParallelSlice, ParallelSliceMut}, }; -use zisk_pil::{ - DmaPrePostTrace, DmaPrePostTraceRow, DMA_PRE_POST_TABLE_ID, DMA_PRE_POST_TABLE_SIZE, - DUAL_RANGE_BYTE_ID, -}; +use zisk_pil::{DMA_PRE_POST_TABLE_ID, DMA_PRE_POST_TABLE_SIZE, DUAL_RANGE_BYTE_ID}; + +#[cfg(feature = "packed")] +pub use zisk_pil::{DmaPrePostTracePacked, DmaPrePostTraceRowPacked}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{DmaPrePostTrace, DmaPrePostTraceRow}; + +#[cfg(feature = "packed")] +type DmaPrePostTraceRowType = DmaPrePostTraceRowPacked; +#[cfg(feature = "packed")] +type DmaPrePostTraceType = DmaPrePostTracePacked; + +#[cfg(not(feature = "packed"))] +type DmaPrePostTraceRowType = DmaPrePostTraceRow; +#[cfg(not(feature = "packed"))] +type DmaPrePostTraceType = DmaPrePostTrace; use crate::{DmaPrePostInput, DmaPrePostRom}; use precompiles_helpers::DmaInfo; @@ -55,7 +68,7 @@ impl DmaPrePostSM { pub fn process_slice( &self, input: &DmaPrePostInput, - trace: &mut DmaPrePostTraceRow, + trace: &mut DmaPrePostTraceRowType, pre_post_table_mul: &mut [u64], local_dual_range_byte_mul: &mut [u64], ) { @@ -225,7 +238,7 @@ impl DmaPrePostSM { /// * `trace` - A mutable reference to the Dma trace. /// * `input` - The operation data to process. #[inline(always)] - pub fn process_empty_slice(&self, trace: &mut DmaPrePostTraceRow) { + pub fn process_empty_slice(&self, trace: &mut DmaPrePostTraceRowType) { trace.set_main_step(0); trace.set_dst64(0); trace.set_src64(0); @@ -265,7 +278,7 @@ impl DmaPrePostSM { inputs: &[Vec], trace_buffer: Vec, ) -> ProofmanResult> { - let mut trace = DmaPrePostTrace::::new_from_vec(trace_buffer)?; + let mut trace = DmaPrePostTraceType::::new_from_vec(trace_buffer)?; let num_rows = trace.num_rows(); let total_inputs: usize = inputs.iter().map(|inputs| inputs.len()).sum(); diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs index 333df1d55..584e78306 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs @@ -9,9 +9,23 @@ use precompiles_helpers::DmaInfo; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; use zisk_common::SegmentId; -use zisk_pil::{ - DmaUnalignedAirValues, DmaUnalignedTrace, DmaUnalignedTraceRow, DUAL_RANGE_BYTE_ID, -}; +use zisk_pil::{DmaUnalignedAirValues, DUAL_RANGE_BYTE_ID}; + +#[cfg(feature = "packed")] +pub use zisk_pil::{DmaUnalignedTracePacked, DmaUnalignedTraceRowPacked}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{DmaUnalignedTrace, DmaUnalignedTraceRow}; + +#[cfg(feature = "packed")] +type DmaUnalignedTraceRowType = DmaUnalignedTraceRowPacked; +#[cfg(feature = "packed")] +type DmaUnalignedTraceType = DmaUnalignedTracePacked; + +#[cfg(not(feature = "packed"))] +type DmaUnalignedTraceRowType = DmaUnalignedTraceRow; +#[cfg(not(feature = "packed"))] +type DmaUnalignedTraceType = DmaUnalignedTrace; pub struct DmaUnalignedPrevSegment { pub seq_end: bool, @@ -59,7 +73,7 @@ impl DmaUnalignedSM { pub fn process_input( &self, input: &DmaUnalignedInput, - trace: &mut [DmaUnalignedTraceRow], + trace: &mut [DmaUnalignedTraceRowType], local_dual_byte_table: &mut [u64], air_values: &mut DmaUnalignedAirValues, ) -> usize { @@ -173,15 +187,15 @@ impl DmaUnalignedSM { } else { let last_row = rows - 1; air_values.segment_last_seq_end = F::ZERO; - air_values.segment_last_src64 = trace[last_row].src64; - air_values.segment_last_dst64 = trace[last_row].dst64; - air_values.segment_last_main_step = trace[last_row].main_step; - air_values.segment_last_count = trace[last_row].count; + air_values.segment_last_src64 = F::from_u32(trace[last_row].get_src64()); + air_values.segment_last_dst64 = F::from_u32(trace[last_row].get_dst64()); + air_values.segment_last_main_step = F::from_u64(trace[last_row].get_main_step()); + air_values.segment_last_count = F::from_u32(trace[last_row].get_count()); air_values.segment_last_offset = F::from_u8(src_offset); - let count = trace[last_row].count.as_canonical_u64(); + let count = trace[last_row].get_count(); air_values.last_count_chunk[0] = F::from_u16(count as u16); air_values.last_count_chunk[1] = F::from_u16((count >> 16) as u16); - air_values.segment_last_is_mem_eq = trace[last_row].is_mem_eq; + air_values.segment_last_is_mem_eq = F::from_bool(trace[last_row].get_is_mem_eq()); for (index, byte) in air_values.segment_next_bytes.iter_mut().enumerate() { *byte = F::from_u8((next_value >> (index * 8)) as u8); } @@ -196,7 +210,7 @@ impl DmaUnalignedSM { /// * `trace` - A mutable reference to the Dma trace. /// * `input` - The operation data to process. #[inline(always)] - pub fn process_empty_slice(&self, trace: &mut DmaUnalignedTraceRow) { + pub fn process_empty_slice(&self, trace: &mut DmaUnalignedTraceRowType) { trace.set_main_step(0); trace.set_is_mem_eq(false); trace.set_no_last_no_seq_end(false); @@ -243,7 +257,7 @@ impl DmaUnalignedSM { is_last_segment: bool, trace_buffer: Vec, ) -> ProofmanResult> { - let mut trace = DmaUnalignedTrace::::new_from_vec(trace_buffer)?; + let mut trace = DmaUnalignedTraceType::::new_from_vec(trace_buffer)?; let num_rows = trace.num_rows(); let total_inputs: usize = inputs @@ -280,8 +294,8 @@ impl DmaUnalignedSM { } let padding_rows = num_rows - row_offset; - let last_count = if padding_rows == 0 && trace_rows[num_rows - 1].seq_end.is_zero() { - trace_rows[num_rows - 1].count.as_canonical_u64() + let last_count = if padding_rows == 0 && !trace_rows[num_rows - 1].get_seq_end() { + trace_rows[num_rows - 1].get_count() } else { 0 }; @@ -306,17 +320,16 @@ impl DmaUnalignedSM { air_values.segment_first_bytes = [F::ZERO; 8]; } else { air_values.segment_previous_seq_end = F::ZERO; - air_values.segment_previous_dst64 = - F::from_u32((trace_rows[0].dst64.as_canonical_u64() - 1) as u32); - air_values.segment_previous_src64 = - F::from_u32((trace_rows[0].src64.as_canonical_u64() - 1) as u32); - air_values.segment_previous_main_step = trace_rows[0].main_step; - air_values.segment_previous_count = - F::from_u32((trace_rows[0].count.as_canonical_u64() + 1) as u32); - air_values.segment_previous_is_mem_eq = trace_rows[0].is_mem_eq; + air_values.segment_previous_dst64 = F::from_u32((trace_rows[0].get_dst64() - 1) as u32); + air_values.segment_previous_src64 = F::from_u32((trace_rows[0].get_src64() - 1) as u32); + air_values.segment_previous_main_step = F::from_u64(trace_rows[0].get_main_step()); + air_values.segment_previous_count = F::from_u32((trace_rows[0].get_count() + 1) as u32); + air_values.segment_previous_is_mem_eq = F::from_bool(trace_rows[0].get_is_mem_eq()); air_values.segment_previous_offset = F::from_u8(DmaInfo::get_loop_src_offset(first_input.encoded)); - air_values.segment_first_bytes = trace_rows[0].read_bytes; + for (index, byte) in air_values.segment_first_bytes.iter_mut().enumerate() { + *byte = F::from_u8(trace_rows[0].get_read_bytes(index)); + } } // padding From 3c7f7b1c8a6cdaefb662183ea7dbe2adc41d63fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:30:23 +0000 Subject: [PATCH 299/782] Simplifed the patching API --- .../src/zisklib/lib/array_lib/modexp.rs | 111 ++++-- .../entrypoint/src/zisklib/lib/bigint256.rs | 2 - .../src/zisklib/lib/bls12_381/constants.rs | 60 +++- .../src/zisklib/lib/bls12_381/curve.rs | 306 ++++++++++++----- .../src/zisklib/lib/bls12_381/final_exp.rs | 10 - .../src/zisklib/lib/bls12_381/fp.rs | 136 -------- .../src/zisklib/lib/bls12_381/fp12.rs | 14 - .../src/zisklib/lib/bls12_381/fp2.rs | 105 ------ .../src/zisklib/lib/bls12_381/fr.rs | 116 ------- .../src/zisklib/lib/bls12_381/kzg.rs | 123 +++++++ .../src/zisklib/lib/bls12_381/miller_loop.rs | 15 - .../src/zisklib/lib/bls12_381/mod.rs | 2 + .../src/zisklib/lib/bls12_381/pairing.rs | 121 ++++--- .../src/zisklib/lib/bls12_381/twist.rs | 323 ++++++++++++++---- .../entrypoint/src/zisklib/lib/bn254/curve.rs | 278 +++++++++++---- .../src/zisklib/lib/bn254/pairing.rs | 204 +++++++++-- .../entrypoint/src/zisklib/lib/bn254/twist.rs | 42 --- ziskos/entrypoint/src/zisklib/lib/mod.rs | 4 +- .../src/zisklib/lib/secp256k1/curve.rs | 105 ------ .../src/zisklib/lib/secp256k1/ecdsa.rs | 21 -- .../src/zisklib/lib/secp256k1/ecrecover.rs | 234 +++++++++++++ .../src/zisklib/lib/secp256k1/field.rs | 127 ------- .../src/zisklib/lib/secp256k1/mod.rs | 2 + .../src/zisklib/lib/secp256k1/scalar.rs | 150 -------- ziskos/entrypoint/src/zisklib/lib/sha256.rs | 77 +++++ .../src/zisklib/lib/sha256f_compress.rs | 21 -- 26 files changed, 1529 insertions(+), 1180 deletions(-) create mode 100644 ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs create mode 100644 ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs create mode 100644 ziskos/entrypoint/src/zisklib/lib/sha256.rs delete mode 100644 ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs index 806247569..6e722ebc3 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs @@ -177,51 +177,100 @@ pub fn modexp_u64(base: &[u64], exp: &[u64], modulus: &[u64]) -> Vec { U256::slice_to_flat(&result_u256).to_vec() } -/// Compute modular exponentiation of three large numbers +/// Compute modular exponentiation from big-endian byte arrays +/// +/// This function is designed to patch `fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Vec` /// /// ### Safety /// /// The caller must ensure that: -/// - `base_ptr` points to an array of `base_len` u64 elements -/// - `exp_ptr` points to an array of `exp_len` u64 elements -/// - `modulus_ptr` points to an array of `modulus_len` u64 elements -/// - `result_ptr` points to an array of at least `modulus_len` u64 elements +/// - `base_ptr` points to an array of `base_len` bytes (big-endian) +/// - `exp_ptr` points to an array of `exp_len` bytes (big-endian) +/// - `modulus_ptr` points to an array of `modulus_len` bytes (big-endian) +/// - `result_ptr` points to an array of at least `modulus_len` bytes +/// +/// Returns the number of bytes written to `result_ptr` (always equals `modulus_len`, zero-padded) #[no_mangle] -pub unsafe extern "C" fn modexp_u64_c( - base_ptr: *const u64, +pub unsafe extern "C" fn modexp_bytes_c( + base_ptr: *const u8, base_len: usize, - exp_ptr: *const u64, + exp_ptr: *const u8, exp_len: usize, - modulus_ptr: *const u64, + modulus_ptr: *const u8, modulus_len: usize, - result_ptr: *mut u64, + result_ptr: *mut u8, ) -> usize { - let base = std::slice::from_raw_parts(base_ptr, base_len); - let exp = std::slice::from_raw_parts(exp_ptr, exp_len); - let modulus = std::slice::from_raw_parts(modulus_ptr, modulus_len); + let base_bytes = std::slice::from_raw_parts(base_ptr, base_len); + let exp_bytes = std::slice::from_raw_parts(exp_ptr, exp_len); + let modulus_bytes = std::slice::from_raw_parts(modulus_ptr, modulus_len); + + // Convert big-endian bytes to little-endian u64 arrays + let base_u64 = bytes_be_to_u64_le(base_bytes); + let exp_u64 = bytes_be_to_u64_le(exp_bytes); + let modulus_u64 = bytes_be_to_u64_le(modulus_bytes); + + // Handle empty/zero cases + if modulus_u64.is_empty() || (modulus_u64.len() == 1 && modulus_u64[0] == 0) { + // modulus == 0: return all zeros + let result = std::slice::from_raw_parts_mut(result_ptr, modulus_len); + result.fill(0); + return modulus_len; + } - // Round up to multiple of 4 - let base_len = (base.len() + 3) & !3; - let modulus_len = (modulus.len() + 3) & !3; + // Call the u64 version + let result_u64 = modexp_u64(&base_u64, &exp_u64, &modulus_u64); - let mut base_padded = vec![0u64; base_len]; - let mut modulus_padded = vec![0u64; modulus_len]; + // Convert result back to big-endian bytes with proper length + let result = std::slice::from_raw_parts_mut(result_ptr, modulus_len); + u64_le_to_bytes_be(&result_u64, result); - base_padded[..base.len()].copy_from_slice(base); - modulus_padded[..modulus.len()].copy_from_slice(modulus); + modulus_len +} - // Convert u64 arrays to U256 chunks - let base_u256 = U256::flat_to_slice(&base_padded); - let modulus_u256 = U256::flat_to_slice(&modulus_padded); +/// Convert big-endian bytes to little-endian u64 array +fn bytes_be_to_u64_le(bytes: &[u8]) -> Vec { + if bytes.is_empty() { + return vec![0]; + } - // Call the main modexp function - let result_u256 = modexp(base_u256, exp, modulus_u256); - let result_slice = U256::slice_to_flat(&result_u256); - let result_len = result_slice.len(); + // Skip leading zeros but keep at least one limb + let first_nonzero = bytes.iter().position(|&b| b != 0).unwrap_or(bytes.len() - 1); + let bytes = &bytes[first_nonzero..]; - // Convert result back to u64 array - let result = std::slice::from_raw_parts_mut(result_ptr, modulus_len); - result[..result_len].copy_from_slice(result_slice); + if bytes.is_empty() { + return vec![0]; + } + + // Calculate number of u64 limbs needed + let num_limbs = (bytes.len() + 7) / 8; + let mut result = vec![0u64; num_limbs]; - result_len + // Process bytes from the end (least significant) to the beginning + for (i, &byte) in bytes.iter().rev().enumerate() { + let limb_idx = i / 8; + let byte_idx = i % 8; + result[limb_idx] |= (byte as u64) << (byte_idx * 8); + } + + result +} + +/// Convert little-endian u64 array to big-endian bytes with specified length +fn u64_le_to_bytes_be(limbs: &[u64], output: &mut [u8]) { + let out_len = output.len(); + output.fill(0); + + // Calculate how many bytes the result actually has + let result_bytes = limbs.len() * 8; + + for (i, &limb) in limbs.iter().enumerate() { + for j in 0..8 { + let byte_val = ((limb >> (j * 8)) & 0xFF) as u8; + // Position from the end of the result + let pos_from_end = i * 8 + j; + if pos_from_end < out_len { + output[out_len - 1 - pos_from_end] = byte_val; + } + } + } } diff --git a/ziskos/entrypoint/src/zisklib/lib/bigint256.rs b/ziskos/entrypoint/src/zisklib/lib/bigint256.rs index 1088c48b4..5a111291a 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bigint256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bigint256.rs @@ -169,8 +169,6 @@ pub fn wpow256(a: &[u64; 4], exp: &[u64; 4]) -> [u64; 4] { result } -// ========== Pointer-based API ========== - /// Modular reduction of a 256-bit integer /// /// # Safety diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs index e08762f0e..eeea66734 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs @@ -13,19 +13,63 @@ pub const E_B: [u64; 6] = [0x4, 0, 0, 0, 0, 0]; pub const ETWISTED_B: [u64; 12] = [0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0]; /// Identity element in G1 -pub const IDENTITY_G1: [u64; 12] = { +pub const G1_IDENTITY: [u64; 12] = { let mut tmp = [0u64; 12]; tmp[6] = 1; tmp }; /// Identity element in G2 -pub const IDENTITY_G2: [u64; 24] = { +pub const G2_IDENTITY: [u64; 24] = { let mut tmp = [0u64; 24]; tmp[12] = 1; tmp }; +/// G1 generator point for BLS12-381 +pub const G1_GENERATOR: [u64; 12] = [ + 0xFB3A_F00A_DB22_C6BB, + 0x6C55_E83F_F97A_1AEF, + 0xA14E_3A3F_171B_AC58, + 0xC368_8C4F_9774_B905, + 0x2695_638C_4FA9_AC0F, + 0x17F1_D3A7_3197_D794, + 0x0CAA_2329_46C5_E7E1, + 0xD03C_C744_A288_8AE4, + 0x00DB_18CB_2C04_B3ED, + 0xFCF5_E095_D5D0_0AF6, + 0xA09E_30ED_741D_8AE4, + 0x08B3_F481_E3AA_A0F1, +]; + +/// G2 generator point for BLS12-381 +pub const G2_GENERATOR: [u64; 24] = [ + 0xD480_56C8_C121_BDB8, + 0x0BAC_0326_A805_BBEF, + 0xB451_0B64_7AE3_D177, + 0xC6E4_7AD4_FA40_3B02, + 0x2608_0527_2DC5_1051, + 0x024A_A2B2_F08F_0A91, + 0xE5AC_7D05_5D04_2B7E, + 0x334C_F112_1394_5D57, + 0xB5DA_61BB_DC7F_5049, + 0x596B_D0D0_9920_B61A, + 0x7DAC_D3A0_8827_4F65, + 0x13E0_2B60_5271_9F60, + 0xE193_5486_08B8_2801, + 0x923A_C9CC_3BAC_A289, + 0x6D42_9A69_5160_D12C, + 0xADFD_9BAA_8CBD_D3A7, + 0x8CC9_CDC6_DA2E_351A, + 0x0CE5_D527_727D_6E11, + 0xAAA9_075F_F05F_79BE, + 0x3F37_0D27_5CEC_1DA1, + 0x2674_92AB_572E_99AB, + 0xCB3E_287E_85A7_63AF, + 0x32AC_D2B0_2BC2_8B99, + 0x0606_C4A0_2EA7_34CC, +]; + /// Base field size pub const P: [u64; 6] = [ 0xB9FEFFFFFFFFAAAB, @@ -210,3 +254,15 @@ pub const FROBENIUS_GAMMA25: [u64; 6] = [ 0xEC02_4086_63D4_DE85, 0x1A01_11EA_397F_E699, ]; + +/// Trusted setup G2 point `[τ]₂` from the Ethereum KZG ceremony (compressed format) +/// Hex: b5bfd7dd8cdeb128843bc287230af38926187075cbfbefa81009a2ce615ac53d2914e5870cb452d2afaaab24f3499f72185cbfee53492714734429b7b38608e23926c911cceceac9a36851477ba4c60b087041de621000edc98edada20c1def2 +// TODO: I can save computations by decompressing this +pub const TRUSTED_SETUP_TAU_G2_COMPRESSED: [u8; 96] = [ + 0xb5, 0xbf, 0xd7, 0xdd, 0x8c, 0xde, 0xb1, 0x28, 0x84, 0x3b, 0xc2, 0x87, 0x23, 0x0a, 0xf3, 0x89, + 0x26, 0x18, 0x70, 0x75, 0xcb, 0xfb, 0xef, 0xa8, 0x10, 0x09, 0xa2, 0xce, 0x61, 0x5a, 0xc5, 0x3d, + 0x29, 0x14, 0xe5, 0x87, 0x0c, 0xb4, 0x52, 0xd2, 0xaf, 0xaa, 0xab, 0x24, 0xf3, 0x49, 0x9f, 0x72, + 0x18, 0x5c, 0xbf, 0xee, 0x53, 0x49, 0x27, 0x14, 0x73, 0x44, 0x29, 0xb7, 0xb3, 0x86, 0x08, 0xe2, + 0x39, 0x26, 0xc9, 0x11, 0xcc, 0xec, 0xea, 0xc9, 0xa3, 0x68, 0x51, 0x47, 0x7b, 0xa4, 0xc6, 0x0b, + 0x08, 0x70, 0x41, 0xde, 0x62, 0x10, 0x00, 0xed, 0xc9, 0x8e, 0xda, 0xda, 0x20, 0xc1, 0xde, 0xf2, +]; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs index 69ea2a068..7ad0a474f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs @@ -5,11 +5,11 @@ use crate::{ syscall_bls12_381_curve_add, syscall_bls12_381_curve_dbl, SyscallBls12_381CurveAddParams, SyscallPoint384, }, - zisklib::{eq, fcall_msb_pos_384, lt}, + zisklib::{eq, fcall_msb_pos_256, lt}, }; use super::{ - constants::{E_B, GAMMA, IDENTITY_G1, P}, + constants::{E_B, G1_IDENTITY, GAMMA, P}, fp::{ add_fp_bls12_381, mul_fp_bls12_381, neg_fp_bls12_381, sqrt_fp_bls12_381, square_fp_bls12_381, @@ -41,7 +41,7 @@ pub fn decompress_bls12_381(input: &[u8; 48]) -> Result<([u64; 12], bool), &'sta return Err("Invalid infinity encoding"); } } - return Ok((IDENTITY_G1, true)); + return Ok((G1_IDENTITY, true)); } // Extract sign bit @@ -136,7 +136,7 @@ pub fn add_bls12_381(p1: &[u64; 12], p2: &[u64; 12]) -> [u64; 12] { return dbl_bls12_381(p1); } else { // Return 𝒪 - return IDENTITY_G1; + return G1_IDENTITY; } } @@ -191,19 +191,37 @@ pub fn sub_bls12_381(p1: &[u64; 12], p2: &[u64; 12]) -> [u64; 12] { add_bls12_381(p1, &p2_neg) } +// pub fn sub_complete_bls12_381(p1: &[u64; 12], p2: &[u64; 12]) -> [u64; 12] { +// if p1 == G1_IDENTITY { +// return neg_bls12_381(p2); +// } + +// let x2: [u64; 6] = p2[0..6].try_into().unwrap(); +// let y2: [u64; 6] = p2[6..12].try_into().unwrap(); + +// // P1 - P2 = P1 + (-P2) +// let y2_neg = neg_fp_bls12_381(&y2); + +// let mut p2_neg = [0u64; 12]; +// p2_neg[0..6].copy_from_slice(&x2); +// p2_neg[6..12].copy_from_slice(&y2_neg); + +// add_bls12_381(p1, &p2_neg) +// } + /// Multiplies a non-zero point `p` on the BLS12-381 curve by a scalar `k` on the BLS12-381 scalar field -pub fn scalar_mul_bls12_381(p: &[u64; 12], k: &[u64; 6]) -> [u64; 12] { +pub fn scalar_mul_bls12_381(p: &[u64; 12], k: &[u64; 4]) -> [u64; 12] { // Direct cases: k = 0, k = 1, k = 2 match k { - [0, 0, 0, 0, 0, 0] => { + [0, 0, 0, 0] => { // Return 𝒪 - return IDENTITY_G1; + return G1_IDENTITY; } - [1, 0, 0, 0, 0, 0] => { + [1, 0, 0, 0] => { // Return p return *p; } - [2, 0, 0, 0, 0, 0] => { + [2, 0, 0, 0] => { // Return 2p return dbl_bls12_381(p); } @@ -214,7 +232,7 @@ pub fn scalar_mul_bls12_381(p: &[u64; 12], k: &[u64; 6]) -> [u64; 12] { // Hint the length the binary representations of k // We will verify the output by recomposing k // Moreover, we should check that the first received bit is 1 - let (max_limb, max_bit) = fcall_msb_pos_384(k, &[0, 0, 0, 0, 0, 0]); + let (max_limb, max_bit) = fcall_msb_pos_256(k, &[0, 0, 0, 0]); // Perform the loop, based on the binary representation of k @@ -229,7 +247,7 @@ pub fn scalar_mul_bls12_381(p: &[u64; 12], k: &[u64; 6]) -> [u64; 12] { let x1: [u64; 6] = p[0..6].try_into().unwrap(); let y1: [u64; 6] = p[6..12].try_into().unwrap(); let mut q = SyscallPoint384 { x: x1, y: y1 }; - let mut k_rec = [0u64; 6]; + let mut k_rec = [0u64; 4]; k_rec[max_limb] |= 1 << max_bit; // Determine starting limb/bit for the loop @@ -322,95 +340,213 @@ pub fn sigma_endomorphism_bls12_381(p: &[u64; 12]) -> [u64; 12] { result } -// ========== Pointer-based API ========== - -/// # Safety -/// - `ret` must point to a valid `[u64; 12]` (96 bytes) for the output. -/// - `input` must point to a valid `[u8; 48]` (48 bytes) for the compressed input. -/// Returns: -/// - 0 = success (regular point) -/// - 1 = success (point at infinity) -/// - 2 = error +/// G1 point addition for uncompressed 96-byte points (big-endian format) +/// +/// Input format: 96 bytes per point = 48 bytes x-coordinate + 48 bytes y-coordinate (big-endian) +/// Output format: Same as input +/// +/// ### Safety +/// - `a` must point to a valid `[u8; 96]` for the first input point +/// - `b` must point to a valid `[u8; 96]` for the second input point +/// - `ret` must point to a valid `[u8; 96]` for the output +/// +/// Returns: +/// - 0 = success (regular point) +/// - 1 = success (point at infinity) +/// - 2 = error (point not on curve or invalid) #[no_mangle] -pub unsafe extern "C" fn decompress_bls12_381_c(ret: *mut u64, input: *const u8) -> u8 { - let input_arr: &[u8; 48] = &*(input as *const [u8; 48]); - - match decompress_bls12_381(input_arr) { - Ok((result, is_infinity)) => { - let ret_arr: &mut [u64; 12] = &mut *(ret as *mut [u64; 12]); - *ret_arr = result; - if is_infinity { - 1 - } else { - 0 - } - } - Err(_) => 2, +pub unsafe extern "C" fn bls12_381_g1_add_c(ret: *mut u8, a: *const u8, b: *const u8) -> u8 { + // TODO: Check we can remove code using assumptions! + + let a_bytes: &[u8; 96] = &*(a as *const [u8; 96]); + let b_bytes: &[u8; 96] = &*(b as *const [u8; 96]); + let ret_bytes: &mut [u8; 96] = &mut *(ret as *mut [u8; 96]); + + // Check for infinity points (all zeros) + let a_is_inf = a_bytes.iter().all(|&x| x == 0); + let b_is_inf = b_bytes.iter().all(|&x| x == 0); + + if a_is_inf && b_is_inf { + // 𝒪 + 𝒪 = 𝒪 + ret_bytes.fill(0); + return 1; } -} -/// # Safety -/// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. -/// Returns true if the point is on the curve, false otherwise. -#[no_mangle] -pub unsafe extern "C" fn is_on_curve_bls12_381_c(p: *const u64) -> bool { - let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); - is_on_curve_bls12_381(p_arr) -} + if a_is_inf { + // 𝒪 + B = B + ret_bytes.copy_from_slice(b_bytes); + return 0; + } -/// # Safety -/// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. -/// Returns true if the point is in the G1 subgroup, false otherwise. -#[no_mangle] -pub unsafe extern "C" fn is_on_subgroup_bls12_381_c(p: *const u64) -> bool { - let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); - is_on_subgroup_bls12_381(p_arr) -} + if b_is_inf { + // A + 𝒪 = A + ret_bytes.copy_from_slice(a_bytes); + return 0; + } -/// # Safety -/// - `p1` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -/// - `p2` must point to a valid `[u64; 12]` (96 bytes). -#[no_mangle] -pub unsafe extern "C" fn add_bls12_381_c(p1: *mut u64, p2: *const u64) -> bool { - let p1_arr: &[u64; 12] = &*(p1 as *const [u64; 12]); - let p2_arr: &[u64; 12] = &*(p2 as *const [u64; 12]); + // Convert big-endian bytes to little-endian u64 limbs + let a_u64 = g1_bytes_be_to_u64_le(a_bytes); + let b_u64 = g1_bytes_be_to_u64_le(b_bytes); + + // Verify points are on curve + if !is_on_curve_bls12_381(&a_u64) || !is_on_curve_bls12_381(&b_u64) { + return 2; + } + + // Perform addition + let result = add_bls12_381(&a_u64, &b_u64); - let result = add_bls12_381(p1_arr, p2_arr); - if result == IDENTITY_G1 { - return true; + // Check if result is identity + if result == G1_IDENTITY { + ret_bytes.fill(0); + return 1; } - let ret_arr: &mut [u64; 12] = &mut *(p1 as *mut [u64; 12]); - *ret_arr = result; - false + // Convert result back to big-endian bytes + g1_u64_le_to_bytes_be(&result, ret_bytes); + 0 } -/// # Safety -/// - `p` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -/// - Point must be non-zero. -#[no_mangle] -pub unsafe extern "C" fn dbl_bls12_381_c(p: *mut u64) { - let mut p_point = - SyscallPoint384 { x: *(p as *const [u64; 6]), y: *(p.add(6) as *const [u64; 6]) }; +/// Convert 96-byte big-endian G1 point to [u64; 12] little-endian +pub fn g1_bytes_be_to_u64_le(bytes: &[u8; 96]) -> [u64; 12] { + let mut result = [0u64; 12]; + + // x-coordinate (first 48 bytes) + for i in 0..6 { + for j in 0..8 { + result[5 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } - syscall_bls12_381_curve_dbl(&mut p_point); + // y-coordinate (next 48 bytes) + for i in 0..6 { + for j in 0..8 { + result[11 - i] |= (bytes[48 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } - *(p as *mut [u64; 6]) = p_point.x; - *(p.add(6) as *mut [u64; 6]) = p_point.y; + result } -/// # Safety -/// - `ret` must point to a valid `[u64; 12]` (96 bytes) for the output. -/// - `p` must point to a valid `[u64; 12]` (96 bytes) for the input point. -/// - `k` must point to a valid `[u64; 6]` (48 bytes) for the scalar. -/// - Point must be non-zero. +/// Convert [u64; 12] little-endian G1 point to 96-byte big-endian +fn g1_u64_le_to_bytes_be(limbs: &[u64; 12], bytes: &mut [u8; 96]) { + // x-coordinate (first 48 bytes) + for i in 0..6 { + let limb = limbs[5 - i]; + for j in 0..8 { + bytes[i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; + } + } + + // y-coordinate (next 48 bytes) + for i in 0..6 { + let limb = limbs[11 - i]; + for j in 0..8 { + bytes[48 + i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; + } + } +} + +/// G1 Multi-Scalar Multiplication (MSM) for uncompressed points (big-endian format) +/// +/// This function is designed to patch: +/// `fn bls12_381_g1_msm(&self, pairs: &mut dyn Iterator>) -> Result<[u8; 96]>` +/// +/// Input format per pair: 128 bytes = 96 bytes G1 point (x || y big-endian) + 32 bytes scalar (big-endian) +/// Output format: 96 bytes G1 point (x || y big-endian) +/// +/// ### Safety +/// - `pairs` must point to an array of `num_pairs * 128` bytes +/// - `ret` must point to a valid `[u8; 96]` for the output +/// +/// Returns: +/// - 0 = success (regular point) +/// - 1 = success (point at infinity) +/// - 2 = error (point not on curve or invalid) #[no_mangle] -pub unsafe extern "C" fn scalar_mul_bls12_381_c(ret: *mut u64, p: *const u64, k: *const u64) { - let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); - let k_arr: &[u64; 6] = &*(k as *const [u64; 6]); +pub unsafe extern "C" fn bls12_381_g1_msm_c( + ret: *mut u8, + pairs: *const u8, + num_pairs: usize, +) -> u8 { + let ret_bytes: &mut [u8; 96] = &mut *(ret as *mut [u8; 96]); + + // Handle empty input + if num_pairs == 0 { + ret_bytes.fill(0); + return 1; // Point at infinity + } + + // Accumulator starts at infinity + let mut acc = G1_IDENTITY; + let mut acc_is_inf = true; + + for i in 0..num_pairs { + let pair_ptr = pairs.add(i * 128); + + // Extract point (96 bytes) and scalar (32 bytes) + let point_bytes: &[u8; 96] = &*(pair_ptr as *const [u8; 96]); + let scalar_bytes: &[u8; 32] = &*(pair_ptr.add(96) as *const [u8; 32]); + + // Check if point is infinity + let point_is_inf = point_bytes.iter().all(|&x| x == 0); + if point_is_inf { + continue; // 𝒪 * k = 𝒪, skip + } + + // Check if scalar is zero + let scalar_is_zero = scalar_bytes.iter().all(|&x| x == 0); + if scalar_is_zero { + continue; // P * 0 = 𝒪, skip + } - let result = scalar_mul_bls12_381(p_arr, k_arr); + // Convert point from big-endian bytes to u64 limbs + let point_u64 = g1_bytes_be_to_u64_le(point_bytes); - let ret_arr: &mut [u64; 12] = &mut *(ret as *mut [u64; 12]); - *ret_arr = result; + // Verify point is on curve + if !is_on_curve_bls12_381(&point_u64) { + return 2; + } + + // Convert scalar from big-endian bytes to u64 limbs (only need 4 limbs for 256-bit field) + let scalar_u64 = scalar_bytes_be_to_u64_le(scalar_bytes); + + // Compute P * k + let product = scalar_mul_bls12_381(&point_u64, &scalar_u64); + + // Check if product is infinity + if product == G1_IDENTITY { + continue; + } + + // Add to accumulator + if acc_is_inf { + acc = product; + acc_is_inf = false; + } else { + acc = add_bls12_381(&acc, &product); + acc_is_inf = acc == G1_IDENTITY; + } + } + + // Write result + if acc_is_inf { + ret_bytes.fill(0); + return 1; + } + + g1_u64_le_to_bytes_be(&acc, ret_bytes); + 0 +} + +/// Convert 32-byte big-endian scalar to [u64; 4] little-endian (zero-padded for 256-bit) +fn scalar_bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { + let mut result = [0u64; 4]; + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + result } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs index b05802935..50d3407d1 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/final_exp.rs @@ -59,13 +59,3 @@ pub fn final_exp_bls12_381(f: &[u64; 72]) -> [u64; 72] { f } - -/// # Safety -/// - `f` must point to a valid `[u64; 72]` (576 bytes), used as both input and output. -/// - Input must be a valid non-zero Fp12 element. -#[no_mangle] -pub unsafe extern "C" fn final_exp_bls12_381_c(f: *mut u64) { - let f_arr: &[u64; 72] = &*(f as *const [u64; 72]); - let result = final_exp_bls12_381(f_arr); - std::ptr::copy_nonoverlapping(result.as_ptr(), f, 72); -} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs index 1e956f989..aa177a058 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs @@ -154,139 +154,3 @@ pub fn inv_fp_bls12_381(x: &[u64; 6]) -> [u64; 6] { inv } - -// ========== Pointer-based API ========== - -/// # Safety -/// - `a` must point to a valid `[u64; 6]` (48 bytes). -/// - `b` must point to a valid `[u64; 6]` (48 bytes). -#[no_mangle] -pub unsafe extern "C" fn add_fp_bls12_381_c(a: *mut u64, b: *const u64) { - let a_ref = &*(a as *const [u64; 6]); - let b_ref = &*(b as *const [u64; 6]); - - let mut params = SyscallArith384ModParams { - a: a_ref, - b: &[1, 0, 0, 0, 0, 0], - c: b_ref, - module: &P, - d: &mut [0, 0, 0, 0, 0, 0], - }; - syscall_arith384_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); -} - -/// # Safety -/// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. -#[no_mangle] -pub unsafe extern "C" fn dbl_fp_bls12_381_c(a: *mut u64) { - let a_ref = &*(a as *const [u64; 6]); - - let mut params = SyscallArith384ModParams { - a: a_ref, - b: &[2, 0, 0, 0, 0, 0], - c: &[0, 0, 0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0, 0, 0], - }; - syscall_arith384_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); -} - -/// # Safety -/// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. -/// - `b` must point to a valid `[u64; 6]` (48 bytes). -#[no_mangle] -pub unsafe extern "C" fn sub_fp_bls12_381_c(a: *mut u64, b: *const u64) { - let a_ref = &*(a as *const [u64; 6]); - let b_ref = &*(b as *const [u64; 6]); - - let mut params = SyscallArith384ModParams { - a: b_ref, - b: &P_MINUS_ONE, - c: a_ref, - module: &P, - d: &mut [0, 0, 0, 0, 0, 0], - }; - syscall_arith384_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); -} - -/// # Safety -/// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. -#[no_mangle] -pub unsafe extern "C" fn neg_fp_bls12_381_c(a: *mut u64) { - let a_ref = &*(a as *const [u64; 6]); - - let mut params = SyscallArith384ModParams { - a: a_ref, - b: &P_MINUS_ONE, - c: &[0, 0, 0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0, 0, 0], - }; - syscall_arith384_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); -} - -/// # Safety -/// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. -/// - `b` must point to a valid `[u64; 6]` (48 bytes). -#[no_mangle] -pub unsafe extern "C" fn mul_fp_bls12_381_c(a: *mut u64, b: *const u64) { - let a_ref = &*(a as *const [u64; 6]); - let b_ref = &*(b as *const [u64; 6]); - - let mut params = SyscallArith384ModParams { - a: a_ref, - b: b_ref, - c: &[0, 0, 0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0, 0, 0], - }; - syscall_arith384_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); -} - -/// # Safety -/// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. -#[no_mangle] -pub unsafe extern "C" fn square_fp_bls12_381_c(a: *mut u64) { - let a_ref = &*(a as *const [u64; 6]); - - let mut params = SyscallArith384ModParams { - a: a_ref, - b: a_ref, - c: &[0, 0, 0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0, 0, 0], - }; - syscall_arith384_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 6); -} - -/// # Safety -/// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. -/// - `is_qr` must point to a valid `u8`. -#[no_mangle] -pub unsafe extern "C" fn sqrt_fp_bls12_381_c(a: *mut u64) -> bool { - let a_ref = &*(a as *const [u64; 6]); - let (result, qr) = sqrt_fp_bls12_381(a_ref); - *(a as *mut [u64; 6]) = result; - qr -} - -/// # Safety -/// - `a` must point to a valid `[u64; 6]` (48 bytes), used as both input and output. -#[no_mangle] -pub unsafe extern "C" fn inv_fp_bls12_381_c(a: *mut u64) { - let a_ref = &*(a as *const [u64; 6]); - let result = inv_fp_bls12_381(a_ref); - *(a as *mut [u64; 6]) = result; -} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs index ed558e387..e6f214dd3 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp12.rs @@ -277,17 +277,3 @@ pub fn exp_fp12_bls12_381(e: u64, a: &[u64; 72]) -> [u64; 72] { result } - -/// # Safety -/// - `ret` must point to a valid `[u64; 72]` for the output. -/// - `a` and `b` must point to valid `[u64; 72]` Fp12 elements. -#[no_mangle] -pub unsafe extern "C" fn mul_fp12_bls12_381_c(ret: *mut u64, a: *const u64, b: *const u64) { - let a_arr: &[u64; 72] = &*(a as *const [u64; 72]); - let b_arr: &[u64; 72] = &*(b as *const [u64; 72]); - - let result = mul_fp12_bls12_381(a_arr, b_arr); - - let ret_arr: &mut [u64; 72] = &mut *(ret as *mut [u64; 72]); - *ret_arr = result; -} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs index c25d8f887..8a0c7105e 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs @@ -157,108 +157,3 @@ pub fn conjugate_fp2_bls12_381(a: &[u64; 12]) -> [u64; 12] { syscall_bls12_381_complex_sub(&mut params); from_syscall_complex(&f1) } - -// ========== Pointer-based API ========== - -/// # Safety -/// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -/// - `b` must point to a valid `[u64; 12]` (96 bytes). -#[no_mangle] -pub unsafe extern "C" fn add_fp2_bls12_381_c(a: *mut u64, b: *const u64) { - let mut f1 = - SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; - let f2 = SyscallComplex384 { x: *(b as *const [u64; 6]), y: *(b.add(6) as *const [u64; 6]) }; - - let mut params = SyscallBls12_381ComplexAddParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_add(&mut params); - - *(a as *mut [u64; 6]) = f1.x; - *(a.add(6) as *mut [u64; 6]) = f1.y; -} - -/// # Safety -/// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -#[no_mangle] -pub unsafe extern "C" fn dbl_fp2_bls12_381_c(a: *mut u64) { - let mut f1 = - SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; - let f2 = SyscallComplex384 { x: f1.x, y: f1.y }; - - let mut params = SyscallBls12_381ComplexAddParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_add(&mut params); - - *(a as *mut [u64; 6]) = f1.x; - *(a.add(6) as *mut [u64; 6]) = f1.y; -} - -/// # Safety -/// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -#[no_mangle] -pub unsafe extern "C" fn neg_fp2_bls12_381_c(a: *mut u64) { - let mut f1 = - SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; - let f2 = SyscallComplex384 { x: P_MINUS_ONE, y: [0, 0, 0, 0, 0, 0] }; - - let mut params = SyscallBls12_381ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_mul(&mut params); - - *(a as *mut [u64; 6]) = f1.x; - *(a.add(6) as *mut [u64; 6]) = f1.y; -} - -/// # Safety -/// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -/// - `b` must point to a valid `[u64; 12]` (96 bytes). -#[no_mangle] -pub unsafe extern "C" fn sub_fp2_bls12_381_c(a: *mut u64, b: *const u64) { - let mut f1 = - SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; - let f2 = SyscallComplex384 { x: *(b as *const [u64; 6]), y: *(b.add(6) as *const [u64; 6]) }; - - let mut params = SyscallBls12_381ComplexSubParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_sub(&mut params); - - *(a as *mut [u64; 6]) = f1.x; - *(a.add(6) as *mut [u64; 6]) = f1.y; -} - -/// # Safety -/// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -/// - `b` must point to a valid `[u64; 12]` (96 bytes). -#[no_mangle] -pub unsafe extern "C" fn mul_fp2_bls12_381_c(a: *mut u64, b: *const u64) { - let mut f1 = - SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; - let f2 = SyscallComplex384 { x: *(b as *const [u64; 6]), y: *(b.add(6) as *const [u64; 6]) }; - - let mut params = SyscallBls12_381ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_mul(&mut params); - - *(a as *mut [u64; 6]) = f1.x; - *(a.add(6) as *mut [u64; 6]) = f1.y; -} - -/// # Safety -/// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -#[no_mangle] -pub unsafe extern "C" fn square_fp2_bls12_381_c(a: *mut u64) { - let mut f1 = - SyscallComplex384 { x: *(a as *const [u64; 6]), y: *(a.add(6) as *const [u64; 6]) }; - let f2 = SyscallComplex384 { x: f1.x, y: f1.y }; - - let mut params = SyscallBls12_381ComplexMulParams { f1: &mut f1, f2: &f2 }; - syscall_bls12_381_complex_mul(&mut params); - - *(a as *mut [u64; 6]) = f1.x; - *(a.add(6) as *mut [u64; 6]) = f1.y; -} - -/// # Safety -/// - `a` must point to a valid `[u64; 12]` (96 bytes), used as both input and output. -/// - Element must be non-zero. -#[no_mangle] -pub unsafe extern "C" fn inv_fp2_bls12_381_c(a: *mut u64) { - let a_ref = &*(a as *const [u64; 12]); - let result = inv_fp2_bls12_381(a_ref); - *(a as *mut [u64; 12]) = result; -} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs index b62eac9cf..34fa54928 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs @@ -73,119 +73,3 @@ pub fn square_fr_bls12_381(x: &[u64; 4]) -> [u64; 4] { syscall_arith256_mod(&mut params); *params.d } - -// ========== Pointer-based API ========== - -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. -/// - `b` must point to a valid `[u64; 4]` (32 bytes). -#[no_mangle] -pub unsafe extern "C" fn add_fr_bls12_381_c(a: *mut u64, b: *const u64) { - let a_ref = &*(a as *const [u64; 4]); - let b_ref = &*(b as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: a_ref, - b: &[1, 0, 0, 0], - c: b_ref, - module: &R, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); -} - -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. -#[no_mangle] -pub unsafe extern "C" fn dbl_fr_bls12_381_c(a: *mut u64) { - let a_ref = &*(a as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: a_ref, - b: &[2, 0, 0, 0], - c: &[0, 0, 0, 0], - module: &R, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); -} - -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. -/// - `b` must point to a valid `[u64; 4]` (32 bytes). -#[no_mangle] -pub unsafe extern "C" fn sub_fr_bls12_381_c(a: *mut u64, b: *const u64) { - let a_ref = &*(a as *const [u64; 4]); - let b_ref = &*(b as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: b_ref, - b: &R_MINUS_ONE, - c: a_ref, - module: &R, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); -} - -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. -#[no_mangle] -pub unsafe extern "C" fn neg_fr_bls12_381_c(a: *mut u64) { - let a_ref = &*(a as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: a_ref, - b: &R_MINUS_ONE, - c: &[0, 0, 0, 0], - module: &R, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); -} - -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. -/// - `b` must point to a valid `[u64; 4]` (32 bytes). -#[no_mangle] -pub unsafe extern "C" fn mul_fr_bls12_381_c(a: *mut u64, b: *const u64) { - let a_ref = &*(a as *const [u64; 4]); - let b_ref = &*(b as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: a_ref, - b: b_ref, - c: &[0, 0, 0, 0], - module: &R, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); -} - -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes), used as both input and output. -#[no_mangle] -pub unsafe extern "C" fn square_fr_bls12_381_c(a: *mut u64) { - let a_ref = &*(a as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: a_ref, - b: a_ref, - c: &[0, 0, 0, 0], - module: &R, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - - core::ptr::copy_nonoverlapping(params.d.as_ptr(), a, 4); -} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs new file mode 100644 index 000000000..17b277ff2 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs @@ -0,0 +1,123 @@ +use crate::zisklib::lib::utils::{is_one, lt}; + +use super::{ + constants::{ + G1_GENERATOR, G1_IDENTITY, G2_GENERATOR, G2_IDENTITY, R, TRUSTED_SETUP_TAU_G2_COMPRESSED, + }, + curve::{decompress_bls12_381, scalar_mul_bls12_381, sub_bls12_381}, + pairing::pairing_batch_bls12_381, + twist::{ + decompress_twist_bls12_381, neg_twist_bls12_381, scalar_mul_twist_bls12_381, + sub_twist_bls12_381, + }, +}; + +/// Verify KZG proof using BLS12-381 implementation. +/// +/// # Arguments +/// * `z` - 32 bytes big-endian scalar (evaluation point) +/// * `y` - 32 bytes big-endian scalar (claimed evaluation) +/// * `commitment` - 48 bytes compressed G1 point (polynomial commitment) +/// * `proof` - 48 bytes compressed G1 point (KZG proof) +/// +/// # Safety +/// All pointers must be valid and properly aligned. +/// +/// # Returns +/// * 1 if the proof is valid +/// * 0 if the proof is invalid +/// * 2 if there was a parsing error (invalid input) +#[no_mangle] +pub unsafe extern "C" fn verify_kzg_proof_c( + z: *const u8, + y: *const u8, + commitment: *const u8, + proof: *const u8, +) -> u8 { + let z_bytes: &[u8; 32] = &*(z as *const [u8; 32]); + let y_bytes: &[u8; 32] = &*(y as *const [u8; 32]); + let commitment_bytes: &[u8; 48] = &*(commitment as *const [u8; 48]); + let proof_bytes: &[u8; 48] = &*(proof as *const [u8; 48]); + + // Parse the commitment (G1 point, compressed) + let commitment_point = match decompress_bls12_381(commitment_bytes) { + Ok((point, _is_inf)) => point, + Err(_) => return 2, // Invalid commitment + }; + + // Parse the proof (G1 point, compressed) + let proof_point = match decompress_bls12_381(proof_bytes) { + Ok((point, _is_inf)) => point, + Err(_) => return 2, // Invalid proof + }; + + // Parse z and y as scalar field elements (must be canonical < R) + let z_scalar = match read_scalar_canonical(z_bytes) { + Some(s) => s, + None => return 2, // z not canonical + }; + + let y_scalar = match read_scalar_canonical(y_bytes) { + Some(s) => s, + None => return 2, // y not canonical + }; + + // Get the trusted setup G2 point [τ]₂ + let tau_g2 = get_trusted_setup_g2(); + + // Get generators + let g1 = G1_GENERATOR; + let g2 = G2_GENERATOR; + + // Compute c_minus_y = commitment - [y]G₁ + let y_g1 = scalar_mul_bls12_381(&g1, &y_scalar); + let c_minus_y = sub_bls12_381(&commitment_point, &y_g1); + + // Compute t_minus_z = [τ]₂ - [z]G₂ + let z_g2 = scalar_mul_twist_bls12_381(&g2, &z_scalar); + let t_minus_z = sub_twist_bls12_381(&tau_g2, &z_g2); + + // The verification equation is: + // e(commitment - [y]G₁, G₂) = e(proof, [τ]₂ - [z]G₂) + // + // Which is equivalent to checking: + // e(commitment - [y]G₁, -G₂) · e(proof, [τ]₂ - [z]G₂) = 1 + let neg_g2 = neg_twist_bls12_381(&g2); + + // Batch pairing check + let g1_points = [c_minus_y, proof_point]; + let g2_points = [neg_g2, t_minus_z]; + + // Check if the pairing result equals 1 + if is_one(&pairing_batch_bls12_381(&g1_points, &g2_points)) { + 1 // Valid proof + } else { + 0 // Invalid proof + } +} + +/// Read a scalar from 32 big-endian bytes and check if it's canonical (< R) +/// Returns None if the scalar is not canonical +fn read_scalar_canonical(bytes: &[u8; 32]) -> Option<[u64; 4]> { + // Convert big-endian bytes to little-endian u64 limbs + let mut scalar = [0u64; 4]; + for i in 0..4 { + for j in 0..8 { + scalar[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // Check if scalar < R (scalar field order) + if !lt(&scalar, &R) { + return None; // scalar >= R, not canonical + } + + Some(scalar) +} + +/// Get the trusted setup G2 point `[τ]₂` +fn get_trusted_setup_g2() -> [u64; 24] { + decompress_twist_bls12_381(&TRUSTED_SETUP_TAU_G2_COMPRESSED) + .expect("Failed to decompress trusted setup G2") + .0 +} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs index bcc693060..c2bf7053c 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/miller_loop.rs @@ -253,18 +253,3 @@ fn dbl_twist_with_hints_bls12_381(q: &[u64; 24], lambda: &[u64; 12], mu: &[u64; result[12..24].copy_from_slice(&y3); result } - -/// # Safety -/// - `ret` must point to a valid `[u64; 72]` for the Fp12 output. -/// - `q` must point to a valid `[u64; 24]` for the G2 affine point. -/// - `p` must point to a valid `[u64; 12]` for the G1 affine point. -#[no_mangle] -pub unsafe extern "C" fn miller_loop_bls12_381_c(ret: *mut u64, q: *const u64, p: *const u64) { - let p_arr: &[u64; 12] = &*(p as *const [u64; 12]); - let q_arr: &[u64; 24] = &*(q as *const [u64; 24]); - - let result = miller_loop_bls12_381(p_arr, q_arr); - - let ret_arr: &mut [u64; 72] = &mut *(ret as *mut [u64; 72]); - *ret_arr = result; -} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs index f7cf0a85b..852eed18b 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs @@ -7,6 +7,7 @@ mod fp12; mod fp2; mod fp6; mod fr; +mod kzg; mod miller_loop; mod pairing; mod twist; @@ -19,5 +20,6 @@ pub use fp12::*; pub use fp2::*; pub use fp6::*; pub use fr::*; +pub use kzg::*; pub use pairing::*; pub use twist::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs index 5b09d3281..e509edfea 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs @@ -1,13 +1,15 @@ //! Pairing over BLS12-381 curve -use crate::zisklib::lib::utils::gt; +use crate::zisklib::lib::utils::{gt, is_one}; use super::{ - constants::{IDENTITY_G1, IDENTITY_G2, P_MINUS_ONE}, - curve::{is_on_curve_bls12_381, is_on_subgroup_bls12_381, neg_bls12_381}, + constants::{G1_IDENTITY, G2_IDENTITY, P_MINUS_ONE}, + curve::{ + g1_bytes_be_to_u64_le, is_on_curve_bls12_381, is_on_subgroup_bls12_381, neg_bls12_381, + }, final_exp::final_exp_bls12_381, miller_loop::{miller_loop_batch_bls12_381, miller_loop_bls12_381}, - twist::{is_on_curve_twist_bls12_381, is_on_subgroup_twist_bls12_381}, + twist::{g2_bytes_be_to_u64_le, is_on_curve_twist_bls12_381, is_on_subgroup_twist_bls12_381}, }; /// Optimal Ate Pairing e: G1 x G2 -> GT over the BLS12-381 curve @@ -18,7 +20,7 @@ use super::{ /// output: e(P,Q) ∈ GT pub fn pairing_bls12_381(p: &[u64; 12], q: &[u64; 24]) -> [u64; 72] { // e(P, 𝒪) = e(𝒪, Q) = 1; - if *p == IDENTITY_G1 || *q == IDENTITY_G2 { + if *p == G1_IDENTITY || *q == G2_IDENTITY { let mut one = [0; 72]; one[0] = 1; return one; @@ -49,7 +51,7 @@ pub fn pairing_batch_bls12_381(g1_points: &[[u64; 12]], g2_points: &[[u64; 24]]) let mut g2_points_ml = Vec::with_capacity(n); for (p, q) in g1_points.iter().zip(g2_points.iter()) { // If p = 𝒪 or q = 𝒪 => MillerLoop(P, 𝒪) = MillerLoop(𝒪, Q) = 1; we can skip - if *p != IDENTITY_G1 && *q != IDENTITY_G2 { + if *p != G1_IDENTITY && *q != G2_IDENTITY { g1_points_ml.push(*p); g2_points_ml.push(*q); } @@ -69,43 +71,84 @@ pub fn pairing_batch_bls12_381(g1_points: &[[u64; 12]], g2_points: &[[u64; 24]]) final_exp_bls12_381(&miller_loop) } -/// C-compatible wrapper for pairing_verify_bls12_381 +/// BLS12-381 pairing check for big-endian byte format /// -/// # Safety -/// - All pointers must be valid and properly aligned -/// - `p1` and `p2` must point to at least 12 u64s each -/// - `q1` and `q2` must point to at least 24 u64s each +/// This function is designed to patch: +/// `fn bls12_381_pairing_check(&self, pairs: &[(G1Point, G2Point)]) -> Result` /// -/// Returns 1 if e(P₁, Q₁) == e(P₂, Q₂), 0 otherwise +/// Input format per pair: 288 bytes = 96 bytes G1 point + 192 bytes G2 point (big-endian) +/// - G1 point: 48 bytes x + 48 bytes y +/// - G2 point: 48 bytes x_i + 48 bytes x_r + 48 bytes y_i + 48 bytes y_r +/// +/// ### Safety +/// - `pairs` must point to an array of `num_pairs * 288` bytes +/// +/// Returns: +/// - true if the pairing check passes (the product of pairings is equal to 1 in GT) +/// - false otherwise #[no_mangle] -pub unsafe extern "C" fn pairing_verify_bls12_381_c( - p1_ptr: *const u64, - q1_ptr: *const u64, - p2_ptr: *const u64, - q2_ptr: *const u64, -) -> bool { - let p1: &[u64; 12] = &*(p1_ptr as *const [u64; 12]); - let q1: &[u64; 24] = &*(q1_ptr as *const [u64; 24]); - let p2: &[u64; 12] = &*(p2_ptr as *const [u64; 12]); - let q2: &[u64; 24] = &*(q2_ptr as *const [u64; 24]); - - // Treat P₁,Q₁,P₂,Q₂ == 𝒪 at first, as this is a common case - // e(P₁, 𝒪) == e(P₂, Q₂) <--> P₂ == 𝒪 || Q₂ == 𝒪 - // e(𝒪, Q₁) == e(P₂, Q₂) <--> P₂ == 𝒪 || Q₂ == 𝒪 - if *p1 == IDENTITY_G1 || *q1 == IDENTITY_G2 { - return *p2 == IDENTITY_G1 || *q2 == IDENTITY_G2; - } else if *p2 == IDENTITY_G1 || *q2 == IDENTITY_G2 { - return false; +pub unsafe extern "C" fn bls12_381_pairing_check_c(pairs: *const u8, num_pairs: usize) -> bool { + // Handle empty input - empty product is 1, so pairing check passes + if num_pairs == 0 { + return true; } - // Checking e(P1, Q1) == e(P2, Q2) is equivalent to checking e(P1, Q1) * e(-P2, Q2) == 1 - let p2_neg = neg_bls12_381(p2); - let pairing_result = pairing_batch_bls12_381(&[*p1, p2_neg], &[*q1, *q2]); + let mut g1_points: Vec<[u64; 12]> = Vec::with_capacity(num_pairs); + let mut g2_points: Vec<[u64; 24]> = Vec::with_capacity(num_pairs); - let one = { - let mut one = [0; 72]; - one[0] = 1; - one - }; - pairing_result == one + for i in 0..num_pairs { + let pair_ptr = pairs.add(i * 288); + + // Extract G1 point (96 bytes) and G2 point (192 bytes) + let g1_bytes: &[u8; 96] = &*(pair_ptr as *const [u8; 96]); + let g2_bytes: &[u8; 192] = &*(pair_ptr.add(96) as *const [u8; 192]); + + // Check if G1 point is infinity + let g1_is_inf = g1_bytes.iter().all(|&x| x == 0); + + // Check if G2 point is infinity + let g2_is_inf = g2_bytes.iter().all(|&x| x == 0); + + // If either point is infinity, skip this pair (contributes 1 to product) + if g1_is_inf || g2_is_inf { + continue; + } + + // Convert G1 from big-endian bytes to u64 limbs + let g1_u64 = g1_bytes_be_to_u64_le(g1_bytes); + + // Verify G1 point is on curve + if !is_on_curve_bls12_381(&g1_u64) { + return false; + } + + // Verify G1 point is in subgroup + if !is_on_subgroup_bls12_381(&g1_u64) { + return false; + } + + // Convert G2 from big-endian bytes to u64 limbs + let g2_u64 = g2_bytes_be_to_u64_le(g2_bytes); + + // Verify G2 point is on twist curve + if !is_on_curve_twist_bls12_381(&g2_u64) { + return false; + } + + // Verify G2 point is in subgroup + if !is_on_subgroup_twist_bls12_381(&g2_u64) { + return false; + } + + g1_points.push(g1_u64); + g2_points.push(g2_u64); + } + + // If all pairs were skipped (all infinities), result is 1 + if g1_points.is_empty() { + return true; + } + + // Compute batch pairing and check if result is 1 + is_one(&pairing_batch_bls12_381(&g1_points, &g2_points)) } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs index 6e9e2cb3f..b68488d3b 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs @@ -1,10 +1,10 @@ //! Operations on the twist E': y² = x³ + 4·(1+u) of the BLS12-381 curve -use crate::zisklib::{eq, fcall_msb_pos_384, lt}; +use crate::zisklib::{eq, fcall_msb_pos_256, lt}; use super::{ constants::{ - ETWISTED_B, EXT_U, EXT_U_INV, FROBENIUS_GAMMA13, FROBENIUS_GAMMA14, IDENTITY_G2, P, + ETWISTED_B, EXT_U, EXT_U_INV, FROBENIUS_GAMMA13, FROBENIUS_GAMMA14, G2_IDENTITY, P, X_ABS_BIN_BE, }, fp2::{ @@ -39,7 +39,7 @@ pub fn decompress_twist_bls12_381(input: &[u8; 96]) -> Result<([u64; 24], bool), return Err("Invalid infinity encoding"); } } - return Ok((IDENTITY_G2, true)); + return Ok((G2_IDENTITY, true)); } // Extract sign bit @@ -166,7 +166,7 @@ pub fn add_twist_bls12_381(p1: &[u64; 24], p2: &[u64; 24]) -> [u64; 24] { return dbl_twist_bls12_381(p1); } else { // Points are the inverse of each other, return the point at infinity - return IDENTITY_G2; + return G2_IDENTITY; } } @@ -216,6 +216,21 @@ pub fn dbl_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { result } +/// Subtraction of two non-zero points `p1` and `p2` on the BLS12-381 curve +pub fn sub_twist_bls12_381(p1: &[u64; 24], p2: &[u64; 24]) -> [u64; 24] { + let x2: [u64; 12] = p2[0..12].try_into().unwrap(); + let y2: [u64; 12] = p2[12..24].try_into().unwrap(); + + // P1 - P2 = P1 + (-P2) + let y2_neg = neg_fp2_bls12_381(&y2); + + let mut p2_neg = [0u64; 24]; + p2_neg[0..12].copy_from_slice(&x2); + p2_neg[12..24].copy_from_slice(&y2_neg); + + add_twist_bls12_381(p1, &p2_neg) +} + /// Negation of a point pub fn neg_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { let x: [u64; 12] = p[0..12].try_into().unwrap(); @@ -231,18 +246,18 @@ pub fn neg_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { } /// Multiplies a non-zero point `p` on the BLS12-381 curve by a scalar `k` on the BLS12-381 scalar field -pub fn scalar_mul_twist_bls12_381(p: &[u64; 24], k: &[u64; 6]) -> [u64; 24] { +pub fn scalar_mul_twist_bls12_381(p: &[u64; 24], k: &[u64; 4]) -> [u64; 24] { // Direct cases: k = 0, k = 1, k = 2 match k { - [0, 0, 0, 0, 0, 0] => { + [0, 0, 0, 0] => { // Return 𝒪 - return IDENTITY_G2; + return G2_IDENTITY; } - [1, 0, 0, 0, 0, 0] => { + [1, 0, 0, 0] => { // Return p return *p; } - [2, 0, 0, 0, 0, 0] => { + [2, 0, 0, 0] => { // Return 2p return dbl_twist_bls12_381(p); } @@ -253,7 +268,7 @@ pub fn scalar_mul_twist_bls12_381(p: &[u64; 24], k: &[u64; 6]) -> [u64; 24] { // Hint the length the binary representations of k // We will verify the output by recomposing k // Moreover, we should check that the first received bit is 1 - let (max_limb, max_bit) = fcall_msb_pos_384(k, &[0, 0, 0, 0, 0, 0]); + let (max_limb, max_bit) = fcall_msb_pos_256(k, &[0, 0, 0, 0]); // Perform the loop, based on the binary representation of k @@ -266,7 +281,7 @@ pub fn scalar_mul_twist_bls12_381(p: &[u64; 24], k: &[u64; 6]) -> [u64; 24] { // Start at P let mut q = *p; - let mut k_rec = [0u64; 6]; + let mut k_rec = [0u64; 4]; k_rec[max_limb] |= 1 << max_bit; // Determine starting limb/bit for the loop @@ -352,78 +367,248 @@ pub fn utf_endomorphism_twist_bls12_381(p: &[u64; 24]) -> [u64; 24] { result } -/// # Safety -/// - `ret` must point to a valid `[u64; 24]` (192 bytes) for the output. -/// - `input` must point to a valid `[u8; 96]` (96 bytes) for the compressed input. -/// Returns: -/// - 0 = success (regular point) -/// - 1 = success (point at infinity) -/// - 2 = error +/// G2 point addition for uncompressed 192-byte points (big-endian format) +/// +/// This function is designed to patch `fn bls12_381_g2_add(&self, a: G2Point, b: G2Point) -> Result<[u8; 192]>` +/// +/// Input format: 192 bytes per point = 96 bytes x-coordinate (Fp2) + 96 bytes y-coordinate (Fp2) +/// Each Fp2 element: 48 bytes imaginary + 48 bytes real (big-endian) +/// Output format: Same as input +/// +/// ### Safety +/// - `a` must point to a valid `[u8; 192]` for the first input point +/// - `b` must point to a valid `[u8; 192]` for the second input point +/// - `ret` must point to a valid `[u8; 192]` for the output +/// +/// Returns: +/// - 0 = success (regular point) +/// - 1 = success (point at infinity) +/// - 2 = error (point not on curve or invalid) #[no_mangle] -pub unsafe extern "C" fn decompress_twist_bls12_381_c(ret: *mut u64, input: *const u8) -> u8 { - let input_arr: &[u8; 96] = &*(input as *const [u8; 96]); - - match decompress_twist_bls12_381(input_arr) { - Ok((result, is_infinity)) => { - let ret_arr: &mut [u64; 24] = &mut *(ret as *mut [u64; 24]); - *ret_arr = result; - if is_infinity { - 1 - } else { - 0 - } - } - Err(_) => 2, +pub unsafe extern "C" fn bls12_381_g2_add_c(ret: *mut u8, a: *const u8, b: *const u8) -> u8 { + let a_bytes: &[u8; 192] = &*(a as *const [u8; 192]); + let b_bytes: &[u8; 192] = &*(b as *const [u8; 192]); + let ret_bytes: &mut [u8; 192] = &mut *(ret as *mut [u8; 192]); + + // Check for infinity points (all zeros) + let a_is_inf = a_bytes.iter().all(|&x| x == 0); + let b_is_inf = b_bytes.iter().all(|&x| x == 0); + + if a_is_inf && b_is_inf { + // 𝒪 + 𝒪 = 𝒪 + ret_bytes.fill(0); + return 1; + } + + if a_is_inf { + // 𝒪 + B = B + ret_bytes.copy_from_slice(b_bytes); + return 0; + } + + if b_is_inf { + // A + 𝒪 = A + ret_bytes.copy_from_slice(a_bytes); + return 0; + } + + // Convert big-endian bytes to little-endian u64 limbs + let a_u64 = g2_bytes_be_to_u64_le(a_bytes); + let b_u64 = g2_bytes_be_to_u64_le(b_bytes); + + // Verify points are on curve + if !is_on_curve_twist_bls12_381(&a_u64) || !is_on_curve_twist_bls12_381(&b_u64) { + return 2; + } + + // Perform addition + let result = add_twist_bls12_381(&a_u64, &b_u64); + + // Check if result is identity + if result == G2_IDENTITY { + ret_bytes.fill(0); + return 1; } + + // Convert result back to big-endian bytes + g2_u64_le_to_bytes_be(&result, ret_bytes); + 0 } -/// # Safety -/// - `p` must point to a valid `[u64; 24]` (192 bytes) for the input point. -/// Returns true if the point is on the twist curve, false otherwise. +/// G2 Multi-Scalar Multiplication (MSM) for uncompressed points (big-endian format) +/// +/// This function is designed to patch: +/// `fn bls12_381_g2_msm(&self, pairs: &mut dyn Iterator>) -> Result<[u8; 192]>` +/// +/// Input format per pair: 224 bytes = 192 bytes G2 point + 32 bytes scalar (big-endian) +/// Output format: 192 bytes G2 point +/// +/// ### Safety +/// - `pairs` must point to an array of `num_pairs * 224` bytes +/// - `ret` must point to a valid `[u8; 192]` for the output +/// +/// Returns: +/// - 0 = success (regular point) +/// - 1 = success (point at infinity) +/// - 2 = error (point not on curve or invalid) #[no_mangle] -pub unsafe extern "C" fn is_on_curve_twist_bls12_381_c(p: *const u64) -> bool { - let p_arr: &[u64; 24] = &*(p as *const [u64; 24]); - is_on_curve_twist_bls12_381(p_arr) +pub unsafe extern "C" fn bls12_381_g2_msm_c( + ret: *mut u8, + pairs: *const u8, + num_pairs: usize, +) -> u8 { + let ret_bytes: &mut [u8; 192] = &mut *(ret as *mut [u8; 192]); + + // Handle empty input + if num_pairs == 0 { + ret_bytes.fill(0); + return 1; // Point at infinity + } + + // Accumulator starts at infinity + let mut acc = G2_IDENTITY; + let mut acc_is_inf = true; + + for i in 0..num_pairs { + let pair_ptr = pairs.add(i * 224); + + // Extract point (192 bytes) and scalar (32 bytes) + let point_bytes: &[u8; 192] = &*(pair_ptr as *const [u8; 192]); + let scalar_bytes: &[u8; 32] = &*(pair_ptr.add(192) as *const [u8; 32]); + + // Check if point is infinity + let point_is_inf = point_bytes.iter().all(|&x| x == 0); + if point_is_inf { + continue; // 𝒪 * k = 𝒪, skip + } + + // Check if scalar is zero + let scalar_is_zero = scalar_bytes.iter().all(|&x| x == 0); + if scalar_is_zero { + continue; // P * 0 = 𝒪, skip + } + + // Convert point from big-endian bytes to u64 limbs + let point_u64 = g2_bytes_be_to_u64_le(point_bytes); + + // Verify point is on curve + if !is_on_curve_twist_bls12_381(&point_u64) { + return 2; + } + + // Convert scalar from big-endian bytes to u64 limbs + let scalar_u64 = scalar_bytes_be_to_u64_le_g2(scalar_bytes); + + // Compute P * k + let product = scalar_mul_twist_bls12_381(&point_u64, &scalar_u64); + + // Check if product is infinity + if product == G2_IDENTITY { + continue; + } + + // Add to accumulator + if acc_is_inf { + acc = product; + acc_is_inf = false; + } else { + acc = add_twist_bls12_381(&acc, &product); + acc_is_inf = acc == G2_IDENTITY; + } + } + + // Write result + if acc_is_inf { + ret_bytes.fill(0); + return 1; + } + + g2_u64_le_to_bytes_be(&acc, ret_bytes); + 0 } -/// # Safety -/// - `p` must point to a valid `[u64; 24]` (192 bytes) for the input point. -/// Returns true if the point is in the G2 subgroup, false otherwise. -#[no_mangle] -pub unsafe extern "C" fn is_on_subgroup_twist_bls12_381_c(p: *const u64) -> bool { - let p_arr: &[u64; 24] = &*(p as *const [u64; 24]); - is_on_subgroup_twist_bls12_381(p_arr) +/// Convert 192-byte big-endian G2 point to [u64; 24] little-endian +/// Format: x_i (48 bytes) || x_r (48 bytes) || y_i (48 bytes) || y_r (48 bytes) +pub fn g2_bytes_be_to_u64_le(bytes: &[u8; 192]) -> [u64; 24] { + let mut result = [0u64; 24]; + + // x_r (bytes 48-95) -> result[0..6] + for i in 0..6 { + for j in 0..8 { + result[5 - i] |= (bytes[48 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // x_i (bytes 0-47) -> result[6..12] + for i in 0..6 { + for j in 0..8 { + result[11 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // y_r (bytes 144-191) -> result[12..18] + for i in 0..6 { + for j in 0..8 { + result[17 - i] |= (bytes[144 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // y_i (bytes 96-143) -> result[18..24] + for i in 0..6 { + for j in 0..8 { + result[23 - i] |= (bytes[96 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + result } -/// # Safety -/// - `p1` must point to a valid `[u64; 24]` (192 bytes), used as both input and output. -/// - `p2` must point to a valid `[u64; 24]` (192 bytes). -#[no_mangle] -pub unsafe extern "C" fn add_twist_bls12_381_c(p1: *mut u64, p2: *const u64) -> bool { - let p1_arr: &[u64; 24] = &*(p1 as *const [u64; 24]); - let p2_arr: &[u64; 24] = &*(p2 as *const [u64; 24]); +/// Convert [u64; 24] little-endian G2 point to 192-byte big-endian +/// Format: x_i (48 bytes) || x_r (48 bytes) || y_i (48 bytes) || y_r (48 bytes) +fn g2_u64_le_to_bytes_be(limbs: &[u64; 24], bytes: &mut [u8; 192]) { + // x_i (result[6..12]) -> bytes 0-47 + for i in 0..6 { + let limb = limbs[11 - i]; + for j in 0..8 { + bytes[i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; + } + } + + // x_r (result[0..6]) -> bytes 48-95 + for i in 0..6 { + let limb = limbs[5 - i]; + for j in 0..8 { + bytes[48 + i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; + } + } - let result = add_twist_bls12_381(p1_arr, p2_arr); - if result == IDENTITY_G2 { - return true; + // y_i (result[18..24]) -> bytes 96-143 + for i in 0..6 { + let limb = limbs[23 - i]; + for j in 0..8 { + bytes[96 + i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; + } } - let ret_arr: &mut [u64; 24] = &mut *(p1 as *mut [u64; 24]); - *ret_arr = result; - false + // y_r (result[12..18]) -> bytes 144-191 + for i in 0..6 { + let limb = limbs[17 - i]; + for j in 0..8 { + bytes[144 + i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; + } + } } -/// # Safety -/// - `ret` must point to a valid `[u64; 24]` for the output affine point. -/// - `p` must point to a valid `[u64; 24]` affine point. -/// - `k` must point to a valid `[u64; 6]` scalar. -#[no_mangle] -pub unsafe extern "C" fn scalar_mul_twist_bls12_381_c(ret: *mut u64, p: *const u64, k: *const u64) { - let p_arr: &[u64; 24] = &*(p as *const [u64; 24]); - let k_arr: &[u64; 6] = &*(k as *const [u64; 6]); +/// Convert 32-byte big-endian scalar to [u64; 4] little-endian +fn scalar_bytes_be_to_u64_le_g2(bytes: &[u8; 32]) -> [u64; 4] { + let mut result = [0u64; 4]; - let result = scalar_mul_twist_bls12_381(p_arr, k_arr); + // 32 bytes = 4 u64 limbs, remaining 2 limbs are zero + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } - let ret_arr: &mut [u64; 24] = &mut *(ret as *mut [u64; 24]); - *ret_arr = result; + result } diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs index b710bb790..eed60013b 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs @@ -9,7 +9,7 @@ use crate::{ }; use super::{ - constants::{E_B, IDENTITY_G1}, + constants::{E_B, IDENTITY_G1, P}, fp::{add_fp_bn254, inv_fp_bn254, mul_fp_bn254, square_fp_bn254}, }; @@ -180,82 +180,222 @@ pub fn mul_bn254(p: &[u64; 8], k: &[u64; 4]) -> [u64; 8] { [x3[0], x3[1], x3[2], x3[3], y3[0], y3[1], y3[2], y3[3]] } -/// # Safety -/// `p` must point to a valid `[u64; 8]` (64 bytes, affine G1 point). -#[no_mangle] -pub unsafe extern "C" fn is_on_curve_bn254_c(p_ptr: *const u64) -> bool { - let p = unsafe { &*(p_ptr as *const [u64; 8]) }; - is_on_curve_bn254(p) +/// Convert big-endian bytes to little-endian u64 limbs for a G1 point (64 bytes -> [u64; 8]) +/// Format: 32 bytes x (big-endian) + 32 bytes y (big-endian) +fn g1_bytes_be_to_u64_le(bytes: &[u8; 64]) -> [u64; 8] { + let mut result = [0u64; 8]; + + // Parse x coordinate (first 32 bytes, big-endian) + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // Parse y coordinate (next 32 bytes, big-endian) + for i in 0..4 { + for j in 0..8 { + result[7 - i] |= (bytes[32 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + result } -/// # Safety -/// - `p` must point to a valid `[u64; 12]` (96 bytes, Jacobian G1 point). -/// - `out` must point to a valid `[u64; 8]` (64 bytes) writable buffer. -#[no_mangle] -pub unsafe extern "C" fn to_affine_bn254_c(p_ptr: *const u64, out_ptr: *mut u64) -> bool { - let p = unsafe { &*(p_ptr as *const [u64; 12]) }; - let result = to_affine_bn254(p); - - *out_ptr.add(0) = result[0]; - *out_ptr.add(1) = result[1]; - *out_ptr.add(2) = result[2]; - *out_ptr.add(3) = result[3]; - *out_ptr.add(4) = result[4]; - *out_ptr.add(5) = result[5]; - *out_ptr.add(6) = result[6]; - *out_ptr.add(7) = result[7]; - - result == IDENTITY_G1 +/// Convert little-endian u64 limbs to big-endian bytes for a G1 point ([u64; 8] -> 64 bytes) +fn g1_u64_le_to_bytes_be(point: &[u64; 8]) -> [u8; 64] { + let mut result = [0u8; 64]; + + // Encode x coordinate (first 32 bytes, big-endian) + for i in 0..4 { + for j in 0..8 { + result[i * 8 + j] = ((point[3 - i] >> (8 * (7 - j))) & 0xff) as u8; + } + } + + // Encode y coordinate (next 32 bytes, big-endian) + for i in 0..4 { + for j in 0..8 { + result[32 + i * 8 + j] = ((point[7 - i] >> (8 * (7 - j))) & 0xff) as u8; + } + } + + result +} + +/// Convert big-endian bytes to little-endian u64 limbs for a scalar (32 bytes -> [u64; 4]) +fn scalar_bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { + let mut result = [0u64; 4]; + + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + result } +/// Check if a field element is valid (< P) +fn is_valid_field_element(x: &[u64; 4]) -> bool { + // Compare from most significant limb + for i in (0..4).rev() { + if x[i] > P[i] { + return false; + } + if x[i] < P[i] { + return true; + } + } + // x == P, which is not valid + false +} + +/// BN254 G1 point addition with big-endian byte format +/// +/// This function is designed to patch: +/// `fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileError>` +/// +/// Input format: 64 bytes per point = 32 bytes x + 32 bytes y (big-endian) +/// Output format: 64 bytes = 32 bytes x + 32 bytes y (big-endian) +/// /// # Safety -/// - `p1_ptr` must point to a valid `[u64; 8]` (64 bytes, affine G1 point). -/// - `p2_ptr` must point to a valid `[u64; 8]` (64 bytes, affine G1 point). -/// - `out_ptr` must point to a valid `[u64; 8]` (64 bytes) writable buffer. +/// - `p1` must point to at least 64 bytes +/// - `p2` must point to at least 64 bytes +/// - `result` must point to a writable buffer of at least 64 bytes +/// +/// # Returns +/// - 0 if the operation succeeded +/// - 1 if p1 is invalid (not on curve or invalid field element) +/// - 2 if p2 is invalid (not on curve or invalid field element) #[no_mangle] -pub unsafe extern "C" fn add_bn254_c( - p1_ptr: *const u64, - p2_ptr: *const u64, - out_ptr: *mut u64, -) -> bool { - let p1 = unsafe { &*(p1_ptr as *const [u64; 8]) }; - let p2 = unsafe { &*(p2_ptr as *const [u64; 8]) }; - let result = add_bn254(p1, p2); - - *out_ptr.add(0) = result[0]; - *out_ptr.add(1) = result[1]; - *out_ptr.add(2) = result[2]; - *out_ptr.add(3) = result[3]; - *out_ptr.add(4) = result[4]; - *out_ptr.add(5) = result[5]; - *out_ptr.add(6) = result[6]; - *out_ptr.add(7) = result[7]; - - result == IDENTITY_G1 +pub unsafe extern "C" fn bn254_g1_add_c(p1: *const u8, p2: *const u8, result: *mut u8) -> u8 { + let p1_bytes: &[u8; 64] = &*(p1 as *const [u8; 64]); + let p2_bytes: &[u8; 64] = &*(p2 as *const [u8; 64]); + + // Check if p1 is infinity (all zeros) + let p1_is_inf = p1_bytes.iter().all(|&x| x == 0); + + // Check if p2 is infinity (all zeros) + let p2_is_inf = p2_bytes.iter().all(|&x| x == 0); + + // Convert to internal format + let p1_u64 = g1_bytes_be_to_u64_le(p1_bytes); + let p2_u64 = g1_bytes_be_to_u64_le(p2_bytes); + + // Validate field elements and curve membership for non-infinity points + if !p1_is_inf { + let x1: [u64; 4] = p1_u64[0..4].try_into().unwrap(); + let y1: [u64; 4] = p1_u64[4..8].try_into().unwrap(); + + if !is_valid_field_element(&x1) || !is_valid_field_element(&y1) { + return 1; // Invalid field element + } + + if !is_on_curve_bn254(&p1_u64) { + return 1; // Not on curve + } + } + + if !p2_is_inf { + let x2: [u64; 4] = p2_u64[0..4].try_into().unwrap(); + let y2: [u64; 4] = p2_u64[4..8].try_into().unwrap(); + + if !is_valid_field_element(&x2) || !is_valid_field_element(&y2) { + return 2; // Invalid field element + } + + if !is_on_curve_bn254(&p2_u64) { + return 2; // Not on curve + } + } + + // Handle infinity cases + let sum = if p1_is_inf && p2_is_inf { + IDENTITY_G1 + } else if p1_is_inf { + p2_u64 + } else if p2_is_inf { + p1_u64 + } else { + add_bn254(&p1_u64, &p2_u64) + }; + + // Convert result to big-endian bytes + let result_bytes = if sum == IDENTITY_G1 { [0u8; 64] } else { g1_u64_le_to_bytes_be(&sum) }; + + // Write result + let result_slice = core::slice::from_raw_parts_mut(result, 64); + result_slice.copy_from_slice(&result_bytes); + + 0 // Success } +/// BN254 G1 scalar multiplication with big-endian byte format +/// +/// This function is designed to patch: +/// `fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileError>` +/// +/// Input format: +/// - point: 64 bytes = 32 bytes x + 32 bytes y (big-endian) +/// - scalar: 32 bytes (big-endian), does NOT need to be canonical +/// +/// Output format: 64 bytes = 32 bytes x + 32 bytes y (big-endian) +/// /// # Safety -/// - `p_ptr` must point to a valid `[u64; 8]` (64 bytes, affine G1 point). -/// - `k_ptr` must point to a valid `[u64; 4]` (32 bytes, scalar). -/// - `out_ptr` must point to a valid `[u64; 8]` (64 bytes) writable buffer. +/// - `point` must point to at least 64 bytes +/// - `scalar` must point to at least 32 bytes +/// - `result` must point to a writable buffer of at least 64 bytes +/// +/// # Returns +/// - 0 if the operation succeeded +/// - 1 if point is invalid (not on curve or invalid field element) #[no_mangle] -pub unsafe extern "C" fn mul_bn254_c( - p_ptr: *const u64, - k_ptr: *const u64, - out_ptr: *mut u64, -) -> bool { - let p = unsafe { &*(p_ptr as *const [u64; 8]) }; - let k = unsafe { &*(k_ptr as *const [u64; 4]) }; - let result = mul_bn254(p, k); - - *out_ptr.add(0) = result[0]; - *out_ptr.add(1) = result[1]; - *out_ptr.add(2) = result[2]; - *out_ptr.add(3) = result[3]; - *out_ptr.add(4) = result[4]; - *out_ptr.add(5) = result[5]; - *out_ptr.add(6) = result[6]; - *out_ptr.add(7) = result[7]; - - result == IDENTITY_G1 +pub unsafe extern "C" fn bn254_g1_mul_c( + point: *const u8, + scalar: *const u8, + result: *mut u8, +) -> u8 { + let point_bytes: &[u8; 64] = &*(point as *const [u8; 64]); + let scalar_bytes: &[u8; 32] = &*(scalar as *const [u8; 32]); + + // Check if point is infinity (all zeros) + let point_is_inf = point_bytes.iter().all(|&x| x == 0); + + // Convert point to internal format + let point_u64 = g1_bytes_be_to_u64_le(point_bytes); + + // Validate field elements and curve membership for non-infinity point + if !point_is_inf { + let x: [u64; 4] = point_u64[0..4].try_into().unwrap(); + let y: [u64; 4] = point_u64[4..8].try_into().unwrap(); + + if !is_valid_field_element(&x) || !is_valid_field_element(&y) { + return 1; // Invalid field element + } + + if !is_on_curve_bn254(&point_u64) { + return 1; // Not on curve + } + } + + // Convert scalar to internal format + let scalar_u64 = scalar_bytes_be_to_u64_le(scalar_bytes); + + // Perform scalar multiplication + let product = if point_is_inf || scalar_u64 == [0, 0, 0, 0] { + IDENTITY_G1 + } else { + mul_bn254(&point_u64, &scalar_u64) + }; + + // Convert result to big-endian bytes + let result_bytes = + if product == IDENTITY_G1 { [0u8; 64] } else { g1_u64_le_to_bytes_be(&product) }; + + // Write result + let result_slice = core::slice::from_raw_parts_mut(result, 64); + result_slice.copy_from_slice(&result_bytes); + + 0 // Success } diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs index ddbe3bbd9..dbe58cfa5 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs @@ -1,9 +1,9 @@ //! Pairing over BN254 -use crate::zisklib::lib::utils::gt; +use crate::zisklib::lib::utils::{gt, is_one}; use super::{ - constants::{IDENTITY_G1, IDENTITY_G2, P_MINUS_ONE}, + constants::{IDENTITY_G1, IDENTITY_G2, P, P_MINUS_ONE}, curve::is_on_curve_bn254, final_exp::final_exp_bn254, miller_loop::{miller_loop_batch_bn254, miller_loop_bn254}, @@ -74,23 +74,189 @@ pub fn pairing_batch_bn254(g1_points: &[[u64; 8]], g2_points: &[[u64; 16]]) -> [ final_exp_bn254(&miller_loop) } +/// Check if a field element is valid (< P) +fn is_valid_field_element(x: &[u64; 4]) -> bool { + // Compare from most significant limb + for i in (0..4).rev() { + if x[i] > P[i] { + return false; + } + if x[i] < P[i] { + return true; + } + } + // x == P, which is not valid + false +} + +/// Convert big-endian bytes to little-endian u64 limbs for a G1 point (64 bytes -> [u64; 8]) +/// Format: 32 bytes x (big-endian) + 32 bytes y (big-endian) +fn g1_bytes_be_to_u64_le(bytes: &[u8; 64]) -> [u64; 8] { + let mut result = [0u64; 8]; + + // Parse x coordinate (first 32 bytes, big-endian) + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // Parse y coordinate (next 32 bytes, big-endian) + for i in 0..4 { + for j in 0..8 { + result[7 - i] |= (bytes[32 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + result +} + +/// Convert big-endian bytes to little-endian u64 limbs for a G2 point (128 bytes -> [u64; 16]) +/// Format: 64 bytes x (32 bytes x_i + 32 bytes x_r) + 64 bytes y (32 bytes y_i + 32 bytes y_r) +/// Note: In BN254, Fq2 elements are encoded as (imaginary, real), i.e., y + x*u +fn g2_bytes_be_to_u64_le(bytes: &[u8; 128]) -> [u64; 16] { + let mut result = [0u64; 16]; + + // Parse x coordinate (Fq2: first 32 bytes = x_i, next 32 bytes = x_r) + // Internal format: x_r at [0..4], x_i at [4..8] + + // x_i (imaginary part, first 32 bytes) + for i in 0..4 { + for j in 0..8 { + result[7 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // x_r (real part, next 32 bytes) + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[32 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // Parse y coordinate (Fq2: next 32 bytes = y_i, final 32 bytes = y_r) + // Internal format: y_r at [8..12], y_i at [12..16] + + // y_i (imaginary part) + for i in 0..4 { + for j in 0..8 { + result[15 - i] |= (bytes[64 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // y_r (real part) + for i in 0..4 { + for j in 0..8 { + result[11 - i] |= (bytes[96 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + result +} + +/// BN254 pairing check with big-endian byte format +/// +/// This function is designed to patch: +/// `fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result` +/// +/// Input format per pair: +/// - G1 point: 64 bytes = 32 bytes x + 32 bytes y (big-endian) +/// - G2 point: 128 bytes = 64 bytes x (32 x_i + 32 x_r) + 64 bytes y (32 y_i + 32 y_r) (big-endian) +/// /// # Safety -/// - `g1_ptr` must point to a contiguous array of `num_points` G1 affine points, -/// each being `[u64; 8]` (64 bytes per point). -/// - `g2_ptr` must point to a contiguous array of `num_points` G2 twist affine points, -/// each being `[u64; 16]` (128 bytes per point). -/// - `out_ptr` must point to a valid `[u64; 48]` (384 bytes) writable buffer for the GT result. -/// - `num_points` must correctly reflect the number of points in both arrays. +/// - `pairs` must point to an array of `num_pairs` pair structures +/// - Each pair is: 8 bytes g1_ptr + 8 bytes g2_ptr (pointers to the actual data) +/// +/// # Returns +/// - 1 if the pairing check passes (the product of pairings equals 1 in GT) +/// - 0 if the pairing check fails +/// - 2 if there was a parsing error (invalid input) #[no_mangle] -pub unsafe extern "C" fn pairing_batch_bn254_c( - g1_ptr: *const u64, - g2_ptr: *const u64, - num_points: usize, - out_ptr: *mut u64, -) { - let g1_slice = core::slice::from_raw_parts(g1_ptr as *const [u64; 8], num_points); - let g2_slice = core::slice::from_raw_parts(g2_ptr as *const [u64; 16], num_points); - let result = pairing_batch_bn254(g1_slice, g2_slice); - - out_ptr.copy_from_nonoverlapping(result.as_ptr(), 48); +pub unsafe extern "C" fn bn254_pairing_check_c( + g1_ptrs: *const *const u8, + g2_ptrs: *const *const u8, + num_pairs: usize, +) -> u8 { + // Handle empty input - empty product is 1, so pairing check passes + if num_pairs == 0 { + return 1; + } + + let mut g1_points: Vec<[u64; 8]> = Vec::with_capacity(num_pairs); + let mut g2_points: Vec<[u64; 16]> = Vec::with_capacity(num_pairs); + + for i in 0..num_pairs { + let g1_ptr = *g1_ptrs.add(i); + let g2_ptr = *g2_ptrs.add(i); + + let g1_bytes: &[u8; 64] = &*(g1_ptr as *const [u8; 64]); + let g2_bytes: &[u8; 128] = &*(g2_ptr as *const [u8; 128]); + + // Check if G1 point is infinity + let g1_is_inf = g1_bytes.iter().all(|&x| x == 0); + + // Check if G2 point is infinity + let g2_is_inf = g2_bytes.iter().all(|&x| x == 0); + + // If either point is infinity, skip this pair (contributes 1 to product) + if g1_is_inf || g2_is_inf { + continue; + } + + // Convert G1 from big-endian bytes to u64 limbs + let g1_u64 = g1_bytes_be_to_u64_le(g1_bytes); + + // Validate G1 field elements + let x1: [u64; 4] = g1_u64[0..4].try_into().unwrap(); + let y1: [u64; 4] = g1_u64[4..8].try_into().unwrap(); + if !is_valid_field_element(&x1) || !is_valid_field_element(&y1) { + return 2; // Invalid field element + } + + // Verify G1 point is on curve + if !is_on_curve_bn254(&g1_u64) { + return 2; + } + + // Convert G2 from big-endian bytes to u64 limbs + let g2_u64 = g2_bytes_be_to_u64_le(g2_bytes); + + // Validate G2 field elements (4 field elements for Fq2) + let x2_r: [u64; 4] = g2_u64[0..4].try_into().unwrap(); + let x2_i: [u64; 4] = g2_u64[4..8].try_into().unwrap(); + let y2_r: [u64; 4] = g2_u64[8..12].try_into().unwrap(); + let y2_i: [u64; 4] = g2_u64[12..16].try_into().unwrap(); + if !is_valid_field_element(&x2_r) + || !is_valid_field_element(&x2_i) + || !is_valid_field_element(&y2_r) + || !is_valid_field_element(&y2_i) + { + return 2; // Invalid field element + } + + // Verify G2 point is on twist curve + if !is_on_curve_twist_bn254(&g2_u64) { + return 2; + } + + // Verify G2 point is in subgroup + if !is_on_subgroup_twist_bn254(&g2_u64) { + return 2; + } + + g1_points.push(g1_u64); + g2_points.push(g2_u64); + } + + // If all pairs were skipped (all infinities), result is 1 + if g1_points.is_empty() { + return 1; + } + + // Compute batch pairing and check if result is 1 + if is_one(&pairing_batch_bn254(&g1_points, &g2_points)) { + 1 // Pairing check passed + } else { + 0 // Pairing check failed + } } diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs index a0f30fbe3..eebdd75b0 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs @@ -186,45 +186,3 @@ pub fn utf_endomorphism_twist_bn254(p: &[u64; 16]) -> [u64; 16] { qy[5], qy[6], qy[7], ] } - -/// # Safety -/// `p_ptr` must point to a valid `[u64; 16]` (128 bytes, affine G2 twist point). -#[no_mangle] -pub unsafe extern "C" fn is_on_curve_twist_bn254_c(p_ptr: *const u64) -> bool { - let p = unsafe { &*(p_ptr as *const [u64; 16]) }; - is_on_curve_twist_bn254(p) -} - -/// # Safety -/// `p_ptr` must point to a valid `[u64; 16]` (128 bytes, affine G2 twist point). -#[no_mangle] -pub unsafe extern "C" fn is_on_subgroup_twist_bn254_c(p_ptr: *const u64) -> bool { - let p = unsafe { &*(p_ptr as *const [u64; 16]) }; - is_on_subgroup_twist_bn254(p) -} - -/// # Safety -/// - `p_ptr` must point to a valid `[u64; 24]` (192 bytes, Jacobian G2 twist point). -/// - `out_ptr` must point to a valid `[u64; 16]` (128 bytes) writable buffer. -#[no_mangle] -pub unsafe extern "C" fn to_affine_twist_bn254_c(p_ptr: *const u64, out_ptr: *mut u64) { - let p = unsafe { &*(p_ptr as *const [u64; 24]) }; - let result = to_affine_twist_bn254(p); - - *out_ptr.add(0) = result[0]; - *out_ptr.add(1) = result[1]; - *out_ptr.add(2) = result[2]; - *out_ptr.add(3) = result[3]; - *out_ptr.add(4) = result[4]; - *out_ptr.add(5) = result[5]; - *out_ptr.add(6) = result[6]; - *out_ptr.add(7) = result[7]; - *out_ptr.add(8) = result[8]; - *out_ptr.add(9) = result[9]; - *out_ptr.add(10) = result[10]; - *out_ptr.add(11) = result[11]; - *out_ptr.add(12) = result[12]; - *out_ptr.add(13) = result[13]; - *out_ptr.add(14) = result[14]; - *out_ptr.add(15) = result[15]; -} diff --git a/ziskos/entrypoint/src/zisklib/lib/mod.rs b/ziskos/entrypoint/src/zisklib/lib/mod.rs index 6ed327c62..e023c1ec4 100644 --- a/ziskos/entrypoint/src/zisklib/lib/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/mod.rs @@ -4,7 +4,7 @@ mod bls12_381; mod bn254; mod constants; mod secp256k1; -mod sha256f_compress; +mod sha256; mod utils; // For public consumption @@ -14,5 +14,5 @@ pub use bls12_381::*; pub use bn254::*; pub use constants::*; pub use secp256k1::*; -pub use sha256f_compress::*; +pub use sha256::*; pub use utils::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index 222a46291..a69aef464 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -634,108 +634,3 @@ pub fn secp256k1_triple_scalar_mul_with_g( Some([res.x[0], res.x[1], res.x[2], res.x[3], res.y[0], res.y[1], res.y[2], res.y[3]]) } } - -// ==================== C FFI Functions ==================== - -/// # Safety -/// - `x_bytes_ptr` must point to 32 bytes (big-endian x-coordinate) -/// - `out_ptr` must point to at least 8 u64s -/// -/// Returns 1 on success, 0 if no valid point exists -#[no_mangle] -pub unsafe extern "C" fn secp256k1_decompress_c( - x_bytes_ptr: *const u8, - y_is_odd: u8, - out_ptr: *mut u64, -) -> u8 { - // Convert the x-coordinate from BEu8 to LEu64 - let x_bytes: &[u8; 32] = &*(x_bytes_ptr as *const [u8; 32]); - let x = bytes_be_to_u64_le(x_bytes); - - let (x, y) = match secp256k1_decompress(&x, y_is_odd != 0) { - Ok((x, y)) => (x, y), - Err(_) => return 0, - }; - - *out_ptr.add(0) = x[0]; - *out_ptr.add(1) = x[1]; - *out_ptr.add(2) = x[2]; - *out_ptr.add(3) = x[3]; - *out_ptr.add(4) = y[0]; - *out_ptr.add(5) = y[1]; - *out_ptr.add(6) = y[2]; - *out_ptr.add(7) = y[3]; - - 1 -} - -/// # Safety -/// - `p_ptr` must point to 12 u64s (jacobian point) -/// - `out_ptr` must point to at least 8 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_to_affine_c(p_ptr: *const u64, out_ptr: *mut u64) { - let p: &[u64; 12] = &*(p_ptr as *const [u64; 12]); - let result = secp256k1_to_affine(p); - - *out_ptr.add(0) = result[0]; - *out_ptr.add(1) = result[1]; - *out_ptr.add(2) = result[2]; - *out_ptr.add(3) = result[3]; - *out_ptr.add(4) = result[4]; - *out_ptr.add(5) = result[5]; - *out_ptr.add(6) = result[6]; - *out_ptr.add(7) = result[7]; -} - -/// # Safety -/// - `k1_ptr`, `k2_ptr` must point to 4 u64s each -/// - `p_ptr` must point to 8 u64s -/// - `out_ptr` must point to at least 8 u64s -/// -/// Returns true if result is point at infinity -#[no_mangle] -pub unsafe extern "C" fn secp256k1_double_scalar_mul_with_g_c( - k1_ptr: *const u64, - k2_ptr: *const u64, - p_ptr: *const u64, - out_ptr: *mut u64, -) -> bool { - let k1: &[u64; 4] = &*(k1_ptr as *const [u64; 4]); - let k2: &[u64; 4] = &*(k2_ptr as *const [u64; 4]); - let p: &[u64; 8] = &*(p_ptr as *const [u64; 8]); - - match secp256k1_double_scalar_mul_with_g(k1, k2, p) { - None => true, - Some(res) => { - *out_ptr.add(0) = res[0]; - *out_ptr.add(1) = res[1]; - *out_ptr.add(2) = res[2]; - *out_ptr.add(3) = res[3]; - *out_ptr.add(4) = res[4]; - *out_ptr.add(5) = res[5]; - *out_ptr.add(6) = res[6]; - *out_ptr.add(7) = res[7]; - false - } - } -} - -// Helper to convert 32 big-endian bytes to [u64; 4] little-endian limbs -#[inline] -fn bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { - let mut result = [0u64; 4]; - for (i, r) in result.iter_mut().enumerate() { - let offset = 24 - i * 8; - *r = u64::from_be_bytes([ - bytes[offset], - bytes[offset + 1], - bytes[offset + 2], - bytes[offset + 3], - bytes[offset + 4], - bytes[offset + 5], - bytes[offset + 6], - bytes[offset + 7], - ]); - } - result -} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs index 98e50a109..641f2b9f4 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs @@ -42,24 +42,3 @@ pub fn secp256k1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u6 let neg_s = secp256k1_fn_neg(s); secp256k1_triple_scalar_mul_with_g(z, r, &neg_s, pk, &point).is_none() } - -// ==================== C FFI Functions ==================== - -/// # Safety -/// - `pk_ptr` must point to 8 u64s -/// - `z_ptr`, `r_ptr`, `s_ptr` must point to 4 u64s each -/// -/// Returns true if signature is valid -#[no_mangle] -pub unsafe extern "C" fn secp256k1_ecdsa_verify_c( - pk_ptr: *const u64, - z_ptr: *const u64, - r_ptr: *const u64, - s_ptr: *const u64, -) -> bool { - let pk: &[u64; 8] = &*(pk_ptr as *const [u64; 8]); - let z: &[u64; 4] = &*(z_ptr as *const [u64; 4]); - let r: &[u64; 4] = &*(r_ptr as *const [u64; 4]); - let s: &[u64; 4] = &*(s_ptr as *const [u64; 4]); - secp256k1_ecdsa_verify(pk, z, r, s) -} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs new file mode 100644 index 000000000..81745b233 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs @@ -0,0 +1,234 @@ +use crate::{ + syscalls::{ + syscall_secp256k1_add, syscall_secp256k1_dbl, SyscallPoint256, SyscallSecp256k1AddParams, + }, + zisklib::{ + eq, fcall_msb_pos_256, fcall_secp256k1_ecdsa_verify, is_one, ONE_256, TWO_256, ZERO_256, + }, +}; + +use super::{ + constants::{E_B, G, G_X, G_Y, IDENTITY_X, IDENTITY_Y, N, P}, + curve::{ + secp256k1_decompress, secp256k1_double_scalar_mul_with_g, secp256k1_is_on_curve, + secp256k1_scalar_mul, secp256k1_triple_scalar_mul_with_g, + }, + field::{ + secp256k1_fp_add, secp256k1_fp_inv, secp256k1_fp_mul, secp256k1_fp_sqrt, + secp256k1_fp_square, + }, + scalar::{ + secp256k1_fn_add, secp256k1_fn_inv, secp256k1_fn_mul, secp256k1_fn_neg, + secp256k1_fn_reduce, secp256k1_fn_sub, + }, +}; + +use tiny_keccak::{Hasher, Keccak}; + +/// Convert big-endian bytes to little-endian u64 limbs (32 bytes -> [u64; 4]) +fn bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { + let mut result = [0u64; 4]; + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + result +} + +/// Convert little-endian u64 limbs to big-endian bytes ([u64; 4] -> 32 bytes) +fn u64_le_to_bytes_be(limbs: &[u64; 4]) -> [u8; 32] { + let mut result = [0u8; 32]; + for i in 0..4 { + for j in 0..8 { + result[i * 8 + j] = ((limbs[3 - i] >> (8 * (7 - j))) & 0xff) as u8; + } + } + result +} + +/// Check if a scalar is valid (0 < x < N) +fn is_valid_scalar(x: &[u64; 4]) -> bool { + // Must be non-zero + if *x == ZERO_256 { + return false; + } + // Must be less than N + for i in (0..4).rev() { + if x[i] > N[i] { + return false; + } + if x[i] < N[i] { + return true; + } + } + // x == N, not valid + false +} + +/// Check if r is valid for ecrecover (0 < r < P for the base case) +/// When recid >= 2, we need r + N < P +fn is_valid_r(r: &[u64; 4], recid: u8) -> bool { + // Must be non-zero + if *r == ZERO_256 { + return false; + } + + if recid >= 2 { + // Need to check r + N < P (which is practically never true for secp256k1) + // Since N is very close to P, r + N will almost always exceed P + // This case is extremely rare in practice + // For simplicity, we can add r + N and check if it's < P + // But practically this never happens, so we can return false + return false; + } + + // Must be less than P (field modulus) for recovery + for i in (0..4).rev() { + if r[i] > P[i] { + return false; + } + if r[i] < P[i] { + return true; + } + } + // r == P, not valid + false +} + +/// Recover the public key from an ECDSA signature +/// +/// The recovery formula is: +/// R = (r, y) where y is recovered from r using the curve equation +/// PK = r⁻¹ * (s*R - z*G) +/// +/// Returns the recovered public key as [u64; 8] (x, y coordinates) or None if recovery fails +pub fn secp256k1_ecrecover_point( + r: &[u64; 4], + s: &[u64; 4], + z: &[u64; 4], + recid: u8, +) -> Option<[u64; 8]> { + // Validate r and s + if !is_valid_scalar(r) || !is_valid_scalar(s) { + return None; + } + + // Determine the x-coordinate of R + // If recid >= 2, x = r + N (but this is extremely rare and usually invalid) + let x = if recid >= 2 { + // r + N would need to be < P, which is practically never true + return None; + } else { + *r + }; + + // Recover the y-coordinate from x + // y² = x³ + 7 + let y_is_odd = (recid & 1) == 1; + let (rx, ry) = secp256k1_decompress(&x, y_is_odd).ok()?; + + let r_point = [rx[0], rx[1], rx[2], rx[3], ry[0], ry[1], ry[2], ry[3]]; + + // Compute r_inv = r⁻¹ (mod N) + let r_inv = secp256k1_fn_inv(r); + + // Compute u1 = -z * r_inv (mod N) + let neg_z = secp256k1_fn_neg(z); + let u1 = secp256k1_fn_mul(&neg_z, &r_inv); + + // Compute u2 = s * r_inv (mod N) + let u2 = secp256k1_fn_mul(s, &r_inv); + + // Compute PK = u1*G + u2*R + let pk = secp256k1_double_scalar_mul_with_g(&u1, &u2, &r_point)?; + + Some(pk) +} + +/// Recover the Ethereum address from an ECDSA signature +/// +/// This function is designed to patch: +/// `fn secp256k1_ecrecover(&self, sig: &[u8; 64], recid: u8, msg: &[u8; 32]) -> Result<[u8; 32], PrecompileError>` +/// +/// # Arguments +/// * `sig` - 64 bytes: r (32 bytes) || s (32 bytes), big-endian +/// * `recid` - Recovery ID (0, 1, 2, or 3) +/// * `msg` - 32 bytes message hash (big-endian) +/// +/// # Returns +/// * 32 bytes where the first 12 bytes are 0 and the last 20 bytes are the Ethereum address +/// * Returns all zeros if recovery fails +pub fn secp256k1_ecrecover(sig: &[u8; 64], recid: u8, msg: &[u8; 32]) -> Option<[u8; 32]> { + // Parse r and s from signature + let r_bytes: [u8; 32] = sig[0..32].try_into().unwrap(); + let s_bytes: [u8; 32] = sig[32..64].try_into().unwrap(); + + let r = bytes_be_to_u64_le(&r_bytes); + let s = bytes_be_to_u64_le(&s_bytes); + let z = bytes_be_to_u64_le(msg); + + // Recover the public key point + let pk = secp256k1_ecrecover_point(&r, &s, &z, recid)?; + + // Convert public key to uncompressed format (65 bytes: 0x04 || x || y) + // But for keccak hashing, we only use x || y (64 bytes) + let x = [pk[0], pk[1], pk[2], pk[3]]; + let y = [pk[4], pk[5], pk[6], pk[7]]; + + let x_bytes = u64_le_to_bytes_be(&x); + let y_bytes = u64_le_to_bytes_be(&y); + + // Concatenate x and y for hashing + let mut pk_bytes = [0u8; 64]; + pk_bytes[0..32].copy_from_slice(&x_bytes); + pk_bytes[32..64].copy_from_slice(&y_bytes); + + // Hash with keccak256 + let mut hasher = Keccak::v256(); + hasher.update(&pk_bytes); + let mut hash = [0u8; 32]; + hasher.finalize(&mut hash); + + // Return with first 12 bytes zeroed (Ethereum address is last 20 bytes) + let mut result = [0u8; 32]; + result[12..32].copy_from_slice(&hash[12..32]); + + Some(result) +} + +/// C-compatible wrapper for secp256k1_ecrecover +/// +/// # Safety +/// - `sig` must point to at least 64 bytes +/// - `msg` must point to at least 32 bytes +/// - `output` must point to a writable buffer of at least 32 bytes +/// +/// # Returns +/// - 0 if recovery succeeded +/// - 1 if recovery failed (invalid signature or point not on curve) +// TODO: This function has two modes: tx recovery and precompile. Check it is correct +#[no_mangle] +pub unsafe extern "C" fn secp256k1_ecrecover_c( + sig: *const u8, + recid: u8, + msg: *const u8, + output: *mut u8, +) -> u8 { + let sig_bytes: &[u8; 64] = &*(sig as *const [u8; 64]); + let msg_bytes: &[u8; 32] = &*(msg as *const [u8; 32]); + + match secp256k1_ecrecover(sig_bytes, recid, msg_bytes) { + Some(result) => { + let output_slice = core::slice::from_raw_parts_mut(output, 32); + output_slice.copy_from_slice(&result); + 0 // Success + } + None => { + // Zero out output on failure + let output_slice = core::slice::from_raw_parts_mut(output, 32); + output_slice.fill(0); + 1 // Failure + } + } +} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs index 9e3598972..2c37c8575 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs @@ -122,130 +122,3 @@ pub fn secp256k1_fp_sqrt(x: &[u64; 4], parity: u64) -> ([u64; 4], bool) { (sqrt, false) } } - -// ==================== C FFI Functions ==================== - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_fp_reduce_c(x_ptr: *const u64, out_ptr: *mut u64) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - if lt(x, &P) { - *out_ptr.add(0) = x[0]; - *out_ptr.add(1) = x[1]; - *out_ptr.add(2) = x[2]; - *out_ptr.add(3) = x[3]; - return; - } - - let mut params = SyscallArith256ModParams { - a: x, - b: &[1, 0, 0, 0], - c: &[0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `y_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_fp_add_c( - x_ptr: *const u64, - y_ptr: *const u64, - out_ptr: *mut u64, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); - - let mut params = - SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_fp_negate_c(x_ptr: *const u64, out_ptr: *mut u64) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: x, - b: &P_MINUS_ONE, - c: &[0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `y_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_fp_mul_c( - x_ptr: *const u64, - y_ptr: *const u64, - out_ptr: *mut u64, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); - - let mut params = - SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `scalar` is a single u64 value -/// - `out_ptr` must point to at least 4 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_fp_mul_scalar_c( - x_ptr: *const u64, - scalar: u64, - out_ptr: *mut u64, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: x, - b: &[scalar, 0, 0, 0], - c: &[0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs index 794d231fd..d315cf963 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs @@ -1,10 +1,12 @@ mod constants; mod curve; mod ecdsa; +mod ecrecover; mod field; mod scalar; pub use curve::*; pub use ecdsa::*; +pub use ecrecover::*; pub use field::*; pub use scalar::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs index fc2a39a9b..6d23cd35c 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs @@ -82,153 +82,3 @@ pub fn secp256k1_fn_inv(x: &[u64; 4]) -> [u64; 4] { x_inv } - -// ==================== C FFI Functions ==================== - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_fn_reduce_c(x_ptr: *const u64, out_ptr: *mut u64) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - if lt(x, &N) { - *out_ptr.add(0) = x[0]; - *out_ptr.add(1) = x[1]; - *out_ptr.add(2) = x[2]; - *out_ptr.add(3) = x[3]; - return; - } - - let mut params = SyscallArith256ModParams { - a: x, - b: &[1, 0, 0, 0], - c: &[0, 0, 0, 0], - module: &N, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `y_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_fn_add_c( - x_ptr: *const u64, - y_ptr: *const u64, - out_ptr: *mut u64, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); - - let mut params = - SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_fn_neg_c(x_ptr: *const u64, out_ptr: *mut u64) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: x, - b: &N_MINUS_ONE, - c: &[0, 0, 0, 0], - module: &N, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `y_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_fn_sub_c( - x_ptr: *const u64, - y_ptr: *const u64, - out_ptr: *mut u64, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); - - let mut params = - SyscallArith256ModParams { a: y, b: &N_MINUS_ONE, c: x, module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `y_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_fn_mul_c( - x_ptr: *const u64, - y_ptr: *const u64, - out_ptr: *mut u64, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); - - let mut params = - SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s (non-zero element) -/// - `out_ptr` must point to at least 4 u64s -#[no_mangle] -pub unsafe extern "C" fn secp256k1_fn_inv_c(x_ptr: *const u64, out_ptr: *mut u64) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - // Hint the inverse - let x_inv = fcall_secp256k1_fn_inv(x); - - // Check that x·x_inv = 1 (N) - let mut params = SyscallArith256ModParams { - a: x, - b: &x_inv, - c: &[0, 0, 0, 0], - module: &N, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod(&mut params); - assert_eq!(*params.d, [0x1, 0x0, 0x0, 0x0]); - - *out_ptr.add(0) = x_inv[0]; - *out_ptr.add(1) = x_inv[1]; - *out_ptr.add(2) = x_inv[2]; - *out_ptr.add(3) = x_inv[3]; -} diff --git a/ziskos/entrypoint/src/zisklib/lib/sha256.rs b/ziskos/entrypoint/src/zisklib/lib/sha256.rs new file mode 100644 index 000000000..79842dfb5 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/sha256.rs @@ -0,0 +1,77 @@ +use crate::syscalls::{syscall_sha256_f, SyscallSha256Params}; + +/// SHA-256 initial hash values +const SHA256_INIT: [u32; 8] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +]; + +/// Compress a single 64-byte block into the state +#[inline] +fn compress_block(state: &mut [u32; 8], block: &[u8; 64]) { + let state_64: &mut [u64; 4] = unsafe { &mut *(state.as_mut_ptr() as *mut [u64; 4]) }; + let input_u64: &[u64; 8] = unsafe { &*(block.as_ptr() as *const [u64; 8]) }; + let mut sha256_params = SyscallSha256Params { state: state_64, input: input_u64 }; + syscall_sha256_f(&mut sha256_params); +} + +pub fn sha256(input: &[u8]) -> [u8; 32] { + let mut state = SHA256_INIT; + let input_len = input.len(); + + // Process complete 64-byte blocks + let mut offset = 0; + while offset + 64 <= input_len { + let block: &[u8; 64] = input[offset..offset + 64].try_into().unwrap(); + compress_block(&mut state, block); + offset += 64; + } + + // Handle final block(s) with padding + let remaining = input_len - offset; + let bit_len = (input_len as u64) * 8; + + // We need: remaining bytes + 1 (0x80) + padding + 8 (length) + // If remaining + 9 > 64, we need 2 blocks + let mut final_block = [0u8; 64]; + + // Copy remaining bytes + final_block[..remaining].copy_from_slice(&input[offset..]); + + // Append 0x80 + final_block[remaining] = 0x80; + + if remaining + 9 > 64 { + // Need two blocks: process first block, then second with length + compress_block(&mut state, &final_block); + + // Second block: all zeros except length at the end + final_block = [0u8; 64]; + final_block[56..64].copy_from_slice(&bit_len.to_be_bytes()); + compress_block(&mut state, &final_block); + } else { + // Single block: append length at the end + final_block[56..64].copy_from_slice(&bit_len.to_be_bytes()); + compress_block(&mut state, &final_block); + } + + // Convert state to big-endian bytes + let mut result = [0u8; 32]; + for (i, &word) in state.iter().enumerate() { + result[i * 4..(i + 1) * 4].copy_from_slice(&word.to_be_bytes()); + } + + result +} + +/// C-compatible wrapper for full SHA-256 hash +/// +/// # Safety +/// - `input` must point to at least `input_len` bytes +/// - `output` must point to a writable buffer of at least 32 bytes +#[no_mangle] +pub unsafe extern "C" fn sha256_c(input: *const u8, input_len: usize, output: *mut u8) { + let input_slice = core::slice::from_raw_parts(input, input_len); + let hash = sha256(input_slice); + let output_slice = core::slice::from_raw_parts_mut(output, 32); + output_slice.copy_from_slice(&hash); +} diff --git a/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs b/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs deleted file mode 100644 index 9dac6c976..000000000 --- a/ziskos/entrypoint/src/zisklib/lib/sha256f_compress.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::syscalls::{syscall_sha256_f, SyscallSha256Params}; - -/// C-compatible wrapper for sha256f_compress -/// -/// # Safety -/// - `state_ptr` must point to at least 8 u32s (will be read and written) -/// - `blocks_ptr` must point to at least `num_blocks * 64` bytes -#[no_mangle] -pub unsafe extern "C" fn sha256f_compress_c( - state_ptr: *mut u32, - blocks_ptr: *const u8, - num_blocks: usize, -) { - let state_64: &mut [u64; 4] = &mut *(state_ptr as *mut [u64; 4]); - - for i in 0..num_blocks { - let input_u64: &[u64; 8] = &*(blocks_ptr.add(i * 64) as *const [u64; 8]); - let mut sha256_params = SyscallSha256Params { state: state_64, input: input_u64 }; - syscall_sha256_f(&mut sha256_params); - } -} From f148202807698b8dc8cc40b07498a045aed370a5 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 21 Jan 2026 19:18:40 +0100 Subject: [PATCH 300/782] fix timeout bug with long executions --- .../asm-runner/src/asm_services/services.rs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 150596d2e..403cc430e 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -256,9 +256,30 @@ impl AsmServices { // Send request payload stream.write_all(&out_buffer).context("Failed to write request payload")?; + let total_timeout = Duration::from_secs(120); + let start = Instant::now(); + // Read exactly 40 bytes let mut in_buffer = [0u8; 40]; - stream.read_exact(&mut in_buffer).context("Failed to read full response payload")?; + loop { + if start.elapsed() >= total_timeout { + return Err(anyhow::anyhow!("Total timeout exceeded")); + } + + match stream.read_exact(&mut in_buffer) { + Ok(_) => break, + Err(e) + if e.kind() == std::io::ErrorKind::TimedOut + || e.kind() == std::io::ErrorKind::WouldBlock => + { + tracing::debug!("Read timeout after {:?}, retrying...", start.elapsed()); + continue; + } + Err(e) => { + return Err(e.into()); + } + } + } // Decode bytes into ResponseData let mut response = ResponseData::default(); @@ -292,7 +313,7 @@ impl AsmServices { // Wait for the shutdown signal (up to 30s) loop { - match sem.timed_wait(Duration::from_secs(30)) { + match sem.timed_wait(Duration::from_secs(60)) { Ok(_) => break, Err(named_sem::Error::WaitFailed(e)) if e.kind() == std::io::ErrorKind::Interrupted => From 2f477ba01cecf70aa22e0f53b7d3727158f59949 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 21 Jan 2026 19:24:21 +0100 Subject: [PATCH 301/782] fix local dependencies in Cargo.toml --- Cargo.lock | 11 +++++++++++ Cargo.toml | 32 ++++++++++++++++---------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6327a51d0..d86cc3407 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1040,6 +1040,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "fields", "num-bigint", @@ -1384,6 +1385,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "cfg-if", "num-bigint", @@ -2703,6 +2705,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "colored", "fields", @@ -3098,6 +3101,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "blake3", "borsh", @@ -3132,6 +3136,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "borsh", "colored", @@ -3162,6 +3167,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "fields", "itoa", @@ -3174,6 +3180,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "proc-macro2", "quote", @@ -3184,6 +3191,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "crossbeam-channel", "tracing", @@ -3192,6 +3200,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "colored", "fields", @@ -3202,6 +3211,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "bytemuck", "fields", @@ -5427,6 +5437,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" dependencies = [ "colored", "fields", diff --git a/Cargo.toml b/Cargo.toml index 4f03fbb5d..73ac8d527 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,23 +105,23 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -# proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -# fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } # Proofman Local development -proofman = { path = "../pil2-proofman/proofman" } -proofman-common = { path = "../pil2-proofman/common" } -proofman-macros = { path = "../pil2-proofman/macros" } -proofman-verifier = { path = "../pil2-proofman/verifier" } -proofman-util = { path = "../pil2-proofman/util" } -pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } -witness = { path = "../pil2-proofman/witness" } -fields = { path = "../pil2-proofman/fields" } +# proofman = { path = "../pil2-proofman/proofman" } +# proofman-common = { path = "../pil2-proofman/common" } +# proofman-macros = { path = "../pil2-proofman/macros" } +# proofman-verifier = { path = "../pil2-proofman/verifier" } +# proofman-util = { path = "../pil2-proofman/util" } +# pil-std-lib = { path = "../pil2-proofman/pil2-components/lib/std/rs" } +# witness = { path = "../pil2-proofman/witness" } +# fields = { path = "../pil2-proofman/fields" } # External dependencies rayon = "1.10" From 4de741ef1d5a8dc36c330b5ca95615dae4d37f31 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Thu, 22 Jan 2026 02:33:56 +0100 Subject: [PATCH 302/782] fix bug with timeout request to rom histogram, add control of bytes of memcpy to increase trace. --- core/src/zisk_rom_2_asm.rs | 11 +- emulator-asm/src/dma/direct_memcpy_mtrace.asm | 277 ++++++++++++------ emulator-asm/src/dma/dma_constants.inc | 12 + emulator-asm/src/main.c | 18 +- .../dma/src/dma_64_aligned/dma_64_aligned.rs | 1 - .../dma/src/dma_unaligned/dma_unaligned.rs | 6 +- 6 files changed, 227 insertions(+), 98 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index b2f1528aa..3fe26b6ab 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -6841,17 +6841,26 @@ impl ZiskRom2Asm { ctx.comment_str("rdx = @EXTERN_PARAM") ); *code += &format!("\tmov rdx, [rdx] {}\n", ctx.comment_str("rdx = [EXTERN_PARAM]")); + assert_eq!(REG_MEM_READS_ADDRESS, "r12"); assert_eq!(REG_MEM_READS_SIZE, "r13"); match ctx.mode { AsmGenerationMethod::AsmMinimalTraces => { - *code += "\tcall direct_dma_memcpy_mtrace\n"; + // the number of mem_reads of trace used by memcpy could be + // large, need to control the count of each operation, and + // if it's necessary call to increase minimal trace + *code += "\tcall direct_dma_memcpy_mtrace_with_count_check\n"; } AsmGenerationMethod::AsmRomHistogram => { + // ROM hasn't a variable trace, only multiplicities *code += "\tcall dma_memcpy_fast\n"; } AsmGenerationMethod::AsmMemOp => { + // the maximum number of mops of memcpy is limited because in case of range + // of address only send one address as block. The maximum number of mops + // generated by memcpy is 6, means that no need check size. + // 2 pre-reads + 2 src-reads + 1 src-loop + 1 write block = 6 mops *code += "\tcall direct_dma_memcpy_mops\n"; } _ => unimplemented!("dma_memcpy not implemented for method {:?}", ctx.mode), diff --git a/emulator-asm/src/dma/direct_memcpy_mtrace.asm b/emulator-asm/src/dma/direct_memcpy_mtrace.asm index 4d34e1b42..b906f00f3 100644 --- a/emulator-asm/src/dma/direct_memcpy_mtrace.asm +++ b/emulator-asm/src/dma/direct_memcpy_mtrace.asm @@ -10,15 +10,15 @@ # 3. Performs the actual memory copy from src to dst (with overlap handling) # # REGISTER USAGE: -# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r9, r10, r11, r12, r13 +# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r9, r12, r13 # Does NOT use XMM registers (caller doesn't need to save them) # Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) # # PARAMETERS (NON System V AMD64 ABI): -# rdi -> rbx = dst (u64) - Destination address -# rsi -> rax = src (u64) - Source address -# rdx -> count (usize) - Number of bytes to copy -# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) +# rdi = dst (u64) - Destination address +# rsi = src (u64) - Source address +# rdx = count (usize) - Number of bytes to copy +# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) # # RETURN VALUE: # RAX = Number of 64-bit words written to trace buffer @@ -53,165 +53,268 @@ .global direct_dma_memcpy_mtrace .global dma_memcpy_mtrace +.global direct_dma_memcpy_mtrace_with_count_check + .extern fast_dma_encode +.extern trace_address_threshold + .include "dma_constants.inc" .section .text -# mov rdx, [0xA000_0F00] ; save count before call -# mov rdi, rbx ; rdi = dst (rbx) -# mov rsi, rax ; rsi = src (rax) - -# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) +.set R_MT_INDEX, r13 +.set R_MT_ADDR, r12 +.set R_STEP, r15 +.set R_AUX, r9 +.set R_AUX2, rcx # NOTE: used by rep +.set R_SRC, rsi # NOTE: used by rep +.set R_DST, rdi # NOTE: used by rep +.set R_COUNT, rdx +.set R_ENCODE, rax dma_memcpy_mtrace: - push r12 - push r13 - push rbx + push R_MT_ADDR # ~3 cycles - save callee-saved register + push R_MT_INDEX # ~3 cycles - save callee-saved register + push R_AUX # ~3 cycles - save callee-saved register + push rbx # ~3 cycles - save callee-saved register + + mov R_MT_ADDR, R_COUNT # 1 cycle - setup trace address from count + xor R_MT_INDEX, R_MT_INDEX # 1 cycle - initialize trace index to 0 + call direct_dma_memcpy_mtrace # ~5 cycles + function cost + + mov R_ENCODE, R_MT_INDEX # 1 cycle - return trace index in R_ENCODE + pop rbx # ~3 cycles - restore register + pop R_AUX # ~3 cycles - restore register + pop R_MT_INDEX # ~3 cycles - restore register + pop R_MT_ADDR # ~3 cycles - restore register + + ret # ~5 cycles + +.L_memcpy_check_mtrace_available: + + # trace_address_threshold containt the address "limit" before call _realloc_trace + # trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE + + # calculate bytes of mtrace used and verify if throw the limit + + lea R_AUX, [R_MT_ADDR + 8 * R_MT_INDEX] # 1 cycle - calculate address mtrace + lea R_AUX, [R_AUX + R_COUNT + MAX_DMA_MT_MARGIN] # 1 cycle - calculate current mtrace bytes usage + sub R_AUX, [trace_address_threshold] # ~4 cycles - bytes over threshold (can be negative) + jc .L_memcpy_mtrace_continue # 2 cycles (predicted) - negative means space available + + # check if bytes over threshold are usual for current situation on inside chunk + # R_STEP contain number the steps to end of chunk, we need number the steps consumed + + mov R_AUX2, CHUNK_SIZE # 1 cycle - load chunk size constant + sub R_AUX2, R_STEP # 1 cycle - calculate steps consumed in chunk + imul R_AUX2, MAX_BYTES_MTRACE_STEP # ~3 cycles - bytes expected for consumed steps + cmp R_AUX2, R_AUX # 1 cycle - compare expected vs actual + jae .L_memcpy_mtrace_continue # 2 cycles (predicted) - expected >= actual, ok + + # at this point we need to increase trace, registers R_ENCODE, R_AUX no need to save. + push R_COUNT # ~3 cycles - save general purpose registers + push r8 # ~3 cycles + push r10 # ~3 cycles + push r11 # ~3 cycles + push R_SRC # ~3 cycles + push R_DST # ~3 cycles + + # IMPORTANT: inside call means unaligned to 16 bits + + sub rsp, 16*16 + 8 # 1 cycle - allocate stack space for 16 XMM registers + + movaps [rsp + 0*16], xmm0 # ~4 cycles - save XMM registers (aligned stores) + movaps [rsp + 1*16], xmm1 # ~4 cycles + movaps [rsp + 2*16], xmm2 # ~4 cycles + movaps [rsp + 3*16], xmm3 # ~4 cycles + movaps [rsp + 4*16], xmm4 # ~4 cycles + movaps [rsp + 5*16], xmm5 # ~4 cycles + movaps [rsp + 6*16], xmm6 # ~4 cycles + movaps [rsp + 7*16], xmm7 # ~4 cycles + movaps [rsp + 8*16], xmm8 # ~4 cycles + movaps [rsp + 9*16], xmm9 # ~4 cycles + movaps [rsp + 10*16], xmm10 # ~4 cycles + movaps [rsp + 11*16], xmm11 # ~4 cycles + movaps [rsp + 12*16], xmm12 # ~4 cycles + movaps [rsp + 13*16], xmm13 # ~4 cycles + movaps [rsp + 14*16], xmm14 # ~4 cycles + movaps [rsp + 15*16], xmm15 # ~4 cycles + + call _realloc_trace # ~5 cycles + function cost (~100-500 cycles) + + movaps xmm0, [rsp + 0*16] # ~4 cycles - restore XMM registers (aligned loads) + movaps xmm1, [rsp + 1*16] # ~4 cycles + movaps xmm2, [rsp + 2*16] # ~4 cycles + movaps xmm3, [rsp + 3*16] # ~4 cycles + movaps xmm4, [rsp + 4*16] # ~4 cycles + movaps xmm5, [rsp + 5*16] # ~4 cycles + movaps xmm6, [rsp + 6*16] # ~4 cycles + movaps xmm7, [rsp + 7*16] # ~4 cycles + movaps xmm8, [rsp + 8*16] # ~4 cycles + movaps xmm9, [rsp + 9*16] # ~4 cycles + movaps xmm10, [rsp + 10*16] # ~4 cycles + movaps xmm11, [rsp + 11*16] # ~4 cycles + movaps xmm12, [rsp + 12*16] # ~4 cycles + movaps xmm13, [rsp + 13*16] # ~4 cycles + movaps xmm14, [rsp + 14*16] # ~4 cycles + movaps xmm15, [rsp + 15*16] # ~4 cycles - mov r12, rcx - xor r13, r13 - call direct_dma_memcpy_mtrace + add rsp, 16*16 +8 # 1 cycle - deallocate stack space - mov rax, r13 - pop rbx - pop r13 - pop r12 + pop R_DST # ~3 cycles - restore general purpose registers + pop R_SRC # ~3 cycles + pop r11 # ~3 cycles + pop r10 # ~3 cycles + pop r8 # ~3 cycles + pop R_COUNT # ~3 cycles - ret + jmp .L_memcpy_mtrace_continue + +direct_dma_memcpy_mtrace_with_count_check: + + # Call fast_dma_encode to calculate encoding + # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count + # Result will be returned in R_ENCODE (encoded value) + cmp R_COUNT, MAX_BYTES_DIRECT_MTRACE # 1 cycle - check if count exceeds direct threshold + ja .L_memcpy_check_mtrace_available # 2 cycles (not taken usually) - large count, check trace space + +.L_memcpy_mtrace_continue: direct_dma_memcpy_mtrace: # Call fast_dma_encode to calculate encoding - # Parameters already in correct registers: rdi=dst, rsi=src, rdx=count - # Result will be returned in rax (encoded value) + # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count + # Result will be returned in R_ENCODE (encoded value) call fast_dma_encode # ~15-20 cycles - table lookup encoding - mov [r12 + r13 * 8], rax # ~4 cycles - write encoded result to mem trace - inc r13 # 1 cycle - advance r13 (mem trace index) + mov [R_MT_ADDR + R_MT_INDEX * 8], R_ENCODE # ~4 cycles - write encoded result to mem trace + inc R_MT_INDEX # 1 cycle - advance R_MT_INDEX (mem trace index) .L_pre_dst_to_mtrace: # If pre_count > 0, write aligned dst value to trace - test rax, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 - jz .L_post_dst_to_mtrace # 2 cycles (predicted taken) + test R_ENCODE, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_post_dst_to_mtrace # 2 cycles (predicted taken) # Branch with pre_count > 0: save original dst value before it's overwritten - mov r9, rdi # 1 cycle - get original dst - and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary - mov r9, [r9] # ~4 cycles - read qword from aligned dst - mov [r12 + r13 * 8], r9 # ~4 cycles - write dst pre-value to trace - inc r13 # 1 cycle - advance trace index + mov R_AUX, R_DST # 1 cycle - get original dst + and R_AUX, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov R_AUX, [R_AUX] # ~4 cycles - read qword from aligned dst + mov [R_MT_ADDR + R_MT_INDEX * 8], R_AUX # ~4 cycles - write dst pre-value to trace + inc R_MT_INDEX # 1 cycle - advance trace index .L_post_dst_to_mtrace: # If post_count > 0, write aligned (dst+count) value to trace - test rax, POST_COUNT_MASK # 1 cycle - check if post_count > 0 - jz .L_src_to_mtrace # 2 cycles (predicted taken) - skip to src copy - - lea r9, [rdi + rdx - 1] # 1 cycle - r9 = dst + count - 1 (last dst byte) - and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary - mov r9, [r9] # ~4 cycles - read qword at (dst+count) aligned - mov [r12 + r13 * 8], r9 # ~4 cycles - write dst post-value to trace - inc r13 # 1 cycle - advance trace index + test R_ENCODE, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_src_to_mtrace # 2 cycles (predicted taken) - skip to src copy + + lea R_AUX, [R_DST + R_COUNT - 1] # 1 cycle - R_AUX = dst + count - 1 (last dst byte) + and R_AUX, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov R_AUX, [R_AUX] # ~4 cycles - read qword at (dst+count) aligned + mov [R_MT_ADDR + R_MT_INDEX * 8], R_AUX # ~4 cycles - write dst post-value to trace + inc R_MT_INDEX # 1 cycle - advance trace index .L_src_to_mtrace: # Copy source data to trace buffer # Total qwords = loop_count (bits 0-31) + extra_src_reads (bits 48-50) - mov rcx, rax # 1 cycle - rcx = encoded - shr rcx, LOOP_COUNT_SBITS # 1 cycle - rcx = loop_count (bits 32-63) + mov R_AUX2, R_ENCODE # 1 cycle - R_AUX2 = encoded + shr R_AUX2, LOOP_COUNT_SBITS # 1 cycle - R_AUX2 = loop_count (bits 32-63) - mov r9, rax # 1 cycle - r9 = encoded - shr r9, EXTRA_SRC_READS_SBITS # 1 cycle - shift extra_src_reads to position - and r9, 0x03 # 1 cycle - r9 = extra_src_reads (bits 48-50) - add rcx, r9 # 1 cycle - rcx = total qwords to copy + mov R_AUX, R_ENCODE # 1 cycle - R_AUX = encoded + shr R_AUX, EXTRA_SRC_READS_SBITS # 1 cycle - shift extra_src_reads to position + and R_AUX, 0x03 # 1 cycle - R_AUX = extra_src_reads (bits 48-50) + add R_AUX2, R_AUX # 1 cycle - R_AUX2 = total qwords to copy # Setup for rep movsq: copy from aligned src to trace buffer - mov r9, rsi # 1 cycle - preserve original src pointer - and rsi, ALIGN_MASK # 1 cycle - rsi = src aligned to 8 bytes + mov R_AUX, R_SRC # 1 cycle - preserve original src pointer + and R_SRC, ALIGN_MASK # 1 cycle - R_SRC = src aligned to 8 bytes - push rdi # ~3 cycles - save dst pointer - lea rdi, [r12 + r13 * 8] # 1 cycle - rdi = trace buffer destination - add r13, rcx # 1 cycle - advance trace index by qwords copied + push R_DST # ~3 cycles - save dst pointer + lea R_DST, [R_MT_ADDR + R_MT_INDEX * 8] # 1 cycle - R_DST = trace buffer destination + add R_MT_INDEX, R_AUX2 # 1 cycle - advance trace index by qwords copied - rep movsq # ~1.5-2 cycles per qword (hardware optimized) + rep movsq # ~1.5-2 cycles per qword (hardware optimized) - pop rdi # ~3 cycles - restore dst pointer - mov rsi, r9 # 1 cycle - restore original src pointer + pop R_DST # ~3 cycles - restore dst pointer + mov R_SRC, R_AUX # 1 cycle - restore original src pointer .L_mtrace_done: # Check for memory overlap to decide copy direction - # NOTE: rdi and rsi now contain their ORIGINAL values (restored above) + # NOTE: R_DST and R_SRC now contain their ORIGINAL values (restored above) # Overlap exists if: src < dst < src+count (forward overlap) - cmp rdi, rsi # 1 cycle - compare dst with src + cmp R_DST, R_SRC # 1 cycle - compare dst with src jb .L_copy_forward # 2 cycles (predicted) - dst < src, no overlap - lea r9, [rsi + rdx] # 1 cycle - r9 = src + count - cmp rdi, r9 # 1 cycle - compare dst with (src+count) + lea R_AUX, [R_SRC + R_COUNT] # 1 cycle - R_AUX = src + count + cmp R_DST, R_AUX # 1 cycle - compare dst with (src+count) jae .L_copy_forward # 2 cycles (predicted) - dst >= src+count, no overlap # Overlap detected (src < dst < src+count), must copy backward - # Setup: rsi = src+count-1, rdi = dst+count-1, rcx = count - # Uses ORIGINAL rsi and rdi values (restored from r9 and stack) + # Setup: R_SRC = src+count-1, R_DST = dst+count-1, R_AUX2 = count + # Uses ORIGINAL R_SRC and R_DST values (restored from R_AUX and stack) - lea rsi, [rsi + rdx - 1] # 1 cycle - rsi = src + count - 1 (from original) - lea rdi, [rdi + rdx - 1] # 1 cycle - rdi = dst + count - 1 (from original) - mov rcx, rdx # 1 cycle - rcx = count + lea R_SRC, [R_SRC + R_COUNT - 1] # 1 cycle - R_SRC = src + count - 1 (from original) + lea R_DST, [R_DST + R_COUNT - 1] # 1 cycle - R_DST = dst + count - 1 (from original) + mov R_AUX2, R_COUNT # 1 cycle - R_AUX2 = count - std # ~20-50 cycles - set DF (serializing, pipeline flush) - rep movsb # ~3-5 cycles per byte (backward copy, slower than forward) - cld # ~20-50 cycles - clear DF (serializing, pipeline flush) + std # ~20-50 cycles - set DF (serializing, pipeline flush) + rep movsb # ~3-5 cycles per byte (backward copy, slower than forward) + cld # ~20-50 cycles - clear DF (serializing, pipeline flush) - ret # ~5 cycles + ret # ~5 cycles .L_copy_forward: # No overlap detected, perform optimized forward copy - cmp rdx, 16 # 1 cycle - check if count >= 16 (worth alignment) - jae .L_copy_forward_pre # 2 cycles (predicted) - use 3-phase aligned copy + cmp R_COUNT, 16 # 1 cycle - check if count >= 16 (worth alignment) + jae .L_copy_forward_pre # 2 cycles (predicted) - use 3-phase aligned copy # Small copy (count < 16): copy all bytes directly - mov rcx, rdx # 1 cycle - rcx = count + mov R_AUX2, R_COUNT # 1 cycle - R_AUX2 = count rep movsb # ~3-5 cycles per byte (unaligned small copy) ret # ~5 cycles .L_copy_forward_pre: # Copy in 3 phases: pre-alignment bytes, aligned qwords, post-alignment bytes + # If pre_count > 0, copy unaligned prefix bytes - test rax, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + + test R_ENCODE, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 jz .L_copy_forward_loop # 2 cycles (predicted) # Extract and copy pre_count bytes (1-7 bytes to reach alignment) - mov rcx, rax # 1 cycle - and rcx, PRE_COUNT_MASK # 1 cycle - rcx = pre_count (bits 0-3) - rep movsb # ~3-5 cycles per byte - # rsi, rdi now 8-byte aligned + mov R_AUX2, R_ENCODE # 1 cycle + and R_AUX2, PRE_COUNT_MASK # 1 cycle - R_AUX2 = pre_count (bits 0-3) + + rep movsb # ~3-5 cycles per byte + # R_SRC, R_DST now 8-byte aligned .L_copy_forward_loop: # Copy aligned qwords (main bulk of data) - mov rcx, rax # 1 cycle - shr rcx, LOOP_COUNT_SBITS # 1 cycle - rcx = loop_count (bits 32-63) - rep movsq # ~1.5-2 cycles per qword (aligned, optimized) - # rsi, rdi advanced by loop_count * 8 + mov R_AUX2, R_ENCODE # 1 cycle + shr R_AUX2, LOOP_COUNT_SBITS # 1 cycle - R_AUX2 = loop_count (bits 32-63) + rep movsq # ~1.5-2 cycles per qword (aligned, optimized) + # R_SRC, R_DST advanced by loop_count * 8 .L_check_forward_post: # If post_count > 0, copy remaining unaligned suffix bytes - test rax, POST_COUNT_MASK # 1 cycle - check if post_count > 0 - jz .L_done # 2 cycles (predicted) + test R_ENCODE, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_done # 2 cycles (predicted) # Extract and copy post_count bytes (1-7 bytes after aligned data) - mov rcx, rax # 1 cycle - shr rcx, POST_COUNT_SBITS # 1 cycle - shift post_count to position - and rcx, 0x07 # 1 cycle - rcx = post_count (bits 43-45) + mov R_AUX2, R_ENCODE # 1 cycle + shr R_AUX2, POST_COUNT_SBITS # 1 cycle - shift post_count to position + and R_AUX2, 0x07 # 1 cycle - R_AUX2 = post_count (bits 43-45) - rep movsb # ~3-5 cycles per byte - # rsi, rdi now point past end of data + rep movsb # ~3-5 cycles per byte + # R_SRC, R_DST now point past end of data .L_done: - ret # ~5 cycles + ret # ~5 cycles # Performance estimate (Modern x86-64, L1 cache hits): # diff --git a/emulator-asm/src/dma/dma_constants.inc b/emulator-asm/src/dma/dma_constants.inc index 3b8dfb57d..058285e5f 100644 --- a/emulator-asm/src/dma/dma_constants.inc +++ b/emulator-asm/src/dma/dma_constants.inc @@ -2,6 +2,18 @@ # GENERAL CONSTANTS +.equ MAX_MTRACE_REGS_ACCESS_SIZE, (2 + 2 + 3) * 8 +.equ CHUNK_SIZE, (1 << 18) +.equ MAX_TRACE_CHUNK_INFO, ((44*8) + 32) +.equ MAX_BYTES_DIRECT_MTRACE, 256 +.equ MAX_BYTES_MTRACE_STEP, (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) +.equ MAX_CHUNK_TRACE_SIZE, CHUNK_SIZE * MAX_BYTES_MTRACE_STEP + MAX_TRACE_CHUNK_INFO + +# 1 encoded + 2 prewrites + 2 src reads +.equ MAX_DMA_EXTRA_BYTES, (2 + 2 + 1) * 8 +.equ MAX_DMA_BYTES_DIRECT_MTRACE, (MAX_BYTES_DIRECT_MTRACE - MAX_DMA_EXTRA_BYTES) +.equ MAX_DMA_MT_MARGIN, (MAX_DMA_BYTES_DIRECT_MTRACE + MAX_DMA_EXTRA_BYTES) + .equ EXTRA_PARAMETER_ADDR, 0xA0000F00 # ENCODE CONSTANTS diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 29dde70bf..67589308f 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -43,7 +43,8 @@ uint64_t get_gen_method(void); #define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) #define TRACE_ADDR (uint64_t)0xc0000000 -#define INITIAL_TRACE_SIZE (uint64_t)0x180000000 // 4GB +#define INITIAL_TRACE_SIZE (uint64_t)0x180000000 // 6GB +#define DELTA_TRACE_SIZE (uint64_t)0x080000000 // 2GB #define REG_ADDR (uint64_t)0x70000000 #define REG_SIZE (uint64_t)0x1000 // 4kB @@ -197,10 +198,15 @@ uint64_t trace_address = TRACE_ADDR; uint64_t trace_size = INITIAL_TRACE_SIZE; uint64_t trace_used_size = 0; -// Worst case: every chunk instruction is a keccak operation, with an input data of 200 bytes -#define MAX_CHUNK_TRACE_SIZE (INITIAL_CHUNK_SIZE * 200) + (44 * 8) + 32 -uint64_t trace_address_threshold = TRACE_ADDR + INITIAL_TRACE_SIZE - MAX_CHUNK_TRACE_SIZE; +// Worst case: every chunk instruction is a keccak operation, with an input data of 256 bytes + +#define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) +#define MAX_TRACE_CHUNK_INFO ((44*8) + 32) +#define MAX_BYTES_DIRECT_MTRACE 256 +#define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) +#define MAX_CHUNK_TRACE_SIZE ((INITIAL_CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) +uint64_t trace_address_threshold = TRACE_ADDR + INITIAL_TRACE_SIZE - MAX_CHUNK_TRACE_SIZE; uint64_t print_pc_counter = 0; int map_locked_flag = MAP_LOCKED; @@ -220,7 +226,7 @@ void set_chunk_size (uint64_t new_chunk_size) } chunk_size = new_chunk_size; chunk_size_mask = chunk_size - 1; - trace_address_threshold = TRACE_ADDR + trace_size - ((chunk_size*200) + (44*8) + 32); + trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; } void set_trace_size (uint64_t new_trace_size) @@ -3209,7 +3215,7 @@ extern void _realloc_trace (void) realloc_counter++; // Calculate new trace size - uint64_t new_trace_size = trace_size * 2; + uint64_t new_trace_size = trace_size + DELTA_TRACE_SIZE; // Extend the underlying file to the new size int result = ftruncate(shmem_output_fd, new_trace_size); diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs index 7d5f2c4bd..e883d2c10 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs @@ -115,7 +115,6 @@ impl Dma64AlignedSM { } if input.is_last_instance_input { - println!("Dma64Aligned LAST_INSTANCE_INPUT {seq_end}"); if seq_end { air_values.segment_last_seq_end = F::ONE; air_values.segment_last_src64 = F::ZERO; diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs index 584e78306..43e7a8b02 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs @@ -320,10 +320,10 @@ impl DmaUnalignedSM { air_values.segment_first_bytes = [F::ZERO; 8]; } else { air_values.segment_previous_seq_end = F::ZERO; - air_values.segment_previous_dst64 = F::from_u32((trace_rows[0].get_dst64() - 1) as u32); - air_values.segment_previous_src64 = F::from_u32((trace_rows[0].get_src64() - 1) as u32); + air_values.segment_previous_dst64 = F::from_u32(trace_rows[0].get_dst64() - 1); + air_values.segment_previous_src64 = F::from_u32(trace_rows[0].get_src64() - 1); air_values.segment_previous_main_step = F::from_u64(trace_rows[0].get_main_step()); - air_values.segment_previous_count = F::from_u32((trace_rows[0].get_count() + 1) as u32); + air_values.segment_previous_count = F::from_u32(trace_rows[0].get_count() + 1); air_values.segment_previous_is_mem_eq = F::from_bool(trace_rows[0].get_is_mem_eq()); air_values.segment_previous_offset = F::from_u8(DmaInfo::get_loop_src_offset(first_input.encoded)); From fe73e978c268e841de75294d36f97f6ba69076b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Thu, 22 Jan 2026 08:46:39 +0000 Subject: [PATCH 303/782] Keccak new style --- .../entrypoint/src/zisklib/lib/keccak256.rs | 86 +++++++++++++++++++ ziskos/entrypoint/src/zisklib/lib/mod.rs | 2 + 2 files changed, 88 insertions(+) create mode 100644 ziskos/entrypoint/src/zisklib/lib/keccak256.rs diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs new file mode 100644 index 000000000..1fd1752d7 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -0,0 +1,86 @@ +use crate::syscalls::syscall_keccak_f; + +/// Keccak-256 rate in bytes (1600 - 2*256) / 8 = 136 bytes +const KECCAK256_RATE: usize = 136; + +/// Computes the Keccak-256 hash of the input data. +/// +/// This implements the Keccak sponge construction with: +/// - Rate: 1088 bits (136 bytes) +/// - Capacity: 512 bits (64 bytes) +/// - Output: 256 bits (32 bytes) +/// - Padding: Keccak padding (0x01...0x80) +pub fn keccak256(input: &[u8]) -> [u8; 32] { + let mut state = [0u64; 25]; + let input_len = input.len(); + + // Absorb phase: process complete rate-sized blocks + let mut offset = 0; + while offset + KECCAK256_RATE <= input_len { + // XOR block into state + xor_block_into_state(&mut state, &input[offset..offset + KECCAK256_RATE]); + // Apply Keccak-f permutation + syscall_keccak_f(&mut state); + offset += KECCAK256_RATE; + } + + // Handle final block with padding + let remaining = input_len - offset; + let mut final_block = [0u8; KECCAK256_RATE]; + + // Copy remaining bytes + final_block[..remaining].copy_from_slice(&input[offset..]); + + // Keccak padding: append 0x01, then zeros, then 0x80 at the end of the rate + // For Keccak-256: domain separator is 0x01 + final_block[remaining] = 0x01; + final_block[KECCAK256_RATE - 1] |= 0x80; + + // XOR final padded block into state + xor_block_into_state(&mut state, &final_block); + + // Final permutation + syscall_keccak_f(&mut state); + + // Squeeze phase: extract first 32 bytes (256 bits) from state + let mut result = [0u8; 32]; + let state_bytes: &[u8; 200] = unsafe { &*(&state as *const [u64; 25] as *const [u8; 200]) }; + result.copy_from_slice(&state_bytes[..32]); + + result +} + +/// XOR a rate-sized block into the state (first 136 bytes = 17 u64 words) +#[inline] +fn xor_block_into_state(state: &mut [u64; 25], block: &[u8]) { + // XOR block bytes into state, interpreting as little-endian u64s + for i in 0..KECCAK256_RATE / 8 { + let word = u64::from_le_bytes(block[i * 8..(i + 1) * 8].try_into().unwrap()); + state[i] ^= word; + } +} + +/// C-compatible wrapper for Keccak-256 hash +/// +/// This is the function that `alloy-primitives` will call when the `native-keccak` feature is enabled. +/// +/// # Safety +/// - `input` must point to at least `input_len` bytes +/// - `output` must point to a writable buffer of at least 32 bytes +#[no_mangle] +pub unsafe extern "C" fn keccak256_c(input: *const u8, input_len: usize, output: *mut u8) { + let input_slice = core::slice::from_raw_parts(input, input_len); + let hash = keccak256(input_slice); + let output_slice = core::slice::from_raw_parts_mut(output, 32); + output_slice.copy_from_slice(&hash); +} + +/// Native keccak256 implementation for external callers +/// +/// # Safety +/// - `bytes` must point to at least `len` bytes +/// - `output` must point to a writable buffer of at least 32 bytes +#[no_mangle] +pub unsafe extern "C" fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8) { + keccak256_c(bytes, len, output); +} diff --git a/ziskos/entrypoint/src/zisklib/lib/mod.rs b/ziskos/entrypoint/src/zisklib/lib/mod.rs index e023c1ec4..21ac59380 100644 --- a/ziskos/entrypoint/src/zisklib/lib/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/mod.rs @@ -3,6 +3,7 @@ mod bigint256; mod bls12_381; mod bn254; mod constants; +mod keccak256; mod secp256k1; mod sha256; mod utils; @@ -13,6 +14,7 @@ pub use bigint256::*; pub use bls12_381::*; pub use bn254::*; pub use constants::*; +pub use keccak256::*; pub use secp256k1::*; pub use sha256::*; pub use utils::*; From c48b7464775adb14503afe3f7b1b8bbd7e1b7ffd Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 22 Jan 2026 09:35:56 +0000 Subject: [PATCH 304/782] Moving ROM counters to U64 --- common/src/component/component_counter.rs | 10 +++++----- state-machines/rom/src/rom.rs | 6 +++--- state-machines/rom/src/rom_counter.rs | 4 ++-- state-machines/rom/src/rom_instance.rs | 14 +++++++------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/common/src/component/component_counter.rs b/common/src/component/component_counter.rs index 133494f1a..5bfbede94 100644 --- a/common/src/component/component_counter.rs +++ b/common/src/component/component_counter.rs @@ -6,7 +6,7 @@ use std::{ any::Any, fmt::Debug, ops::{Add, AddAssign}, - sync::{atomic::AtomicU32, Arc}, + sync::{atomic::AtomicU64, Arc}, }; use zisk_core::{ROM_ADDR, ROM_ENTRY}; @@ -94,10 +94,10 @@ impl AddAssign<&Counter> for Counter { #[derive(Debug)] pub struct CounterStats { /// Shared biod instruction counter for monitoring ROM operations. - pub bios_inst_count: Arc>, + pub bios_inst_count: Arc>, /// Shared program instruction counter for monitoring ROM operations. - pub prog_inst_count: Arc>, + pub prog_inst_count: Arc>, /// The PC of the last executed instruction. pub end_pc: u64, @@ -107,7 +107,7 @@ pub struct CounterStats { } impl CounterStats { - pub fn new(entry_inst_count: Arc>, inst_count: Arc>) -> Self { + pub fn new(entry_inst_count: Arc>, inst_count: Arc>) -> Self { CounterStats { bios_inst_count: entry_inst_count, prog_inst_count: inst_count, @@ -124,7 +124,7 @@ impl CounterStats { /// * `num` - The number of instructions executed at the given PC. /// * `end` - A flag indicating if this is the final instruction in the execution. #[inline(always)] - pub fn update(&mut self, pc: u64, step: u64, num: u32, end: bool) { + pub fn update(&mut self, pc: u64, step: u64, num: u64, end: bool) { if pc < ROM_ADDR { let addr = ((pc - ROM_ENTRY) as usize) >> 2; self.bios_inst_count[addr].fetch_add(num, std::sync::atomic::Ordering::Relaxed); diff --git a/state-machines/rom/src/rom.rs b/state-machines/rom/src/rom.rs index 8858f3114..4216f57cd 100644 --- a/state-machines/rom/src/rom.rs +++ b/state-machines/rom/src/rom.rs @@ -11,7 +11,7 @@ use std::{ path::PathBuf, sync::{ - atomic::{AtomicBool, AtomicU32}, + atomic::{AtomicBool, AtomicU64}, Arc, Mutex, }, thread::JoinHandle, @@ -37,10 +37,10 @@ pub struct RomSM { zisk_rom: Arc, /// Shared biod instruction counter for monitoring ROM operations. - bios_inst_count: Arc>, + bios_inst_count: Arc>, /// Shared program instruction counter for monitoring ROM operations. - prog_inst_count: Arc>, + prog_inst_count: Arc>, asm_runner_handler: Mutex>>, } diff --git a/state-machines/rom/src/rom_counter.rs b/state-machines/rom/src/rom_counter.rs index 8c5a033ff..1f47d2a9b 100644 --- a/state-machines/rom/src/rom_counter.rs +++ b/state-machines/rom/src/rom_counter.rs @@ -4,7 +4,7 @@ use std::{ any::Any, - sync::{atomic::AtomicU32, Arc}, + sync::{atomic::AtomicU64, Arc}, }; use zisk_common::{CounterStats, Metrics, RomBusData, RomData}; @@ -24,7 +24,7 @@ impl RomCounter { /// /// # Returns /// A new `RomCounter` instance. - pub fn new(bios_inst_count: Arc>, prog_inst_count: Arc>) -> Self { + pub fn new(bios_inst_count: Arc>, prog_inst_count: Arc>) -> Self { let counter_stats = CounterStats::new(bios_inst_count, prog_inst_count); Self { counter_stats } } diff --git a/state-machines/rom/src/rom_instance.rs b/state-machines/rom/src/rom_instance.rs index 667e36f09..b32a99215 100644 --- a/state-machines/rom/src/rom_instance.rs +++ b/state-machines/rom/src/rom_instance.rs @@ -5,7 +5,7 @@ use std::{ collections::VecDeque, sync::{ - atomic::{AtomicBool, AtomicU32}, + atomic::{AtomicBool, AtomicU64}, Arc, }, thread::JoinHandle, @@ -35,10 +35,10 @@ pub struct RomInstance { ictx: InstanceCtx, /// Shared biod instruction counter for monitoring ROM operations. - bios_inst_count: Mutex>>, + bios_inst_count: Mutex>>, /// Shared program instruction counter for monitoring ROM operations. - prog_inst_count: Mutex>>, + prog_inst_count: Mutex>>, /// Execution statistics counter for ROM instructions. counter_stats: Mutex>, @@ -64,8 +64,8 @@ impl RomInstance { pub fn new( zisk_rom: Arc, ictx: InstanceCtx, - bios_inst_count: Arc>, - prog_inst_count: Arc>, + bios_inst_count: Arc>, + prog_inst_count: Arc>, handle_rh: Option>, ) -> Self { Self { @@ -240,8 +240,8 @@ impl RomCollector { /// A new `RomCounter` instance. pub fn new( computed: bool, - bios_inst_count: Arc>, - prog_inst_count: Arc>, + bios_inst_count: Arc>, + prog_inst_count: Arc>, ) -> Self { let rom_counter = RomCounter::new(bios_inst_count, prog_inst_count); Self { already_computed: computed, rom_counter } From 8ec5dabb7d46d46d5ba208b53979cb836a71487a Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 22 Jan 2026 09:44:52 +0000 Subject: [PATCH 305/782] Clippy --- state-machines/rom/src/rom.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/state-machines/rom/src/rom.rs b/state-machines/rom/src/rom.rs index 4216f57cd..c7c230bfd 100644 --- a/state-machines/rom/src/rom.rs +++ b/state-machines/rom/src/rom.rs @@ -111,14 +111,12 @@ impl RomSM { true => { multiplicity = counter_stats.bios_inst_count [((inst.paddr - ROM_ENTRY) as usize) >> 2] - .swap(0, std::sync::atomic::Ordering::Relaxed) - as u64; + .swap(0, std::sync::atomic::Ordering::Relaxed); } false => { multiplicity = counter_stats.bios_inst_count [((inst.paddr - ROM_ENTRY) as usize) >> 2] - .load(std::sync::atomic::Ordering::Relaxed) - as u64; + .load(std::sync::atomic::Ordering::Relaxed); } } @@ -135,13 +133,11 @@ impl RomSM { multiplicity = counter_stats.prog_inst_count [(inst.paddr - ROM_ADDR) as usize] .swap(0, std::sync::atomic::Ordering::Relaxed) - as u64 } false => { multiplicity = counter_stats.prog_inst_count [(inst.paddr - ROM_ADDR) as usize] .load(std::sync::atomic::Ordering::Relaxed) - as u64 } } if multiplicity == 0 { From da68f3fdb00c41e4c5ce815e2115e1ba3149a7eb Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:45:03 +0000 Subject: [PATCH 306/782] wip new crypto syscalls --- common/src/hints.rs | 511 ++++++------------ precompiles/hints/benches/hints_benchmarks.rs | 30 +- precompiles/hints/src/hints_processor.rs | 201 ++++--- ziskos-hints/src/handlers/bls381.rs | 381 +++++++------ ziskos-hints/src/handlers/bn254.rs | 49 +- ziskos-hints/src/handlers/mod.rs | 31 +- ziskos-hints/src/handlers/secp256k1.rs | 31 ++ ziskos-hints/src/handlers/sha256.rs | 18 + .../src/zisklib/lib/array_lib/modexp.rs | 8 +- .../src/zisklib/lib/bls12_381/kzg.rs | 70 ++- .../src/zisklib/lib/bls12_381/pairing.rs | 7 +- .../entrypoint/src/zisklib/lib/keccak256.rs | 28 +- .../src/zisklib/lib/secp256k1/ecrecover.rs | 67 ++- ziskos/entrypoint/src/zisklib/lib/sha256.rs | 48 +- 14 files changed, 789 insertions(+), 691 deletions(-) create mode 100644 ziskos-hints/src/handlers/sha256.rs diff --git a/common/src/hints.rs b/common/src/hints.rs index b652a0feb..ffb880baa 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -41,8 +41,11 @@ //! Control codes are for control only and do not have any associated data (Length should be zero). //! //! ### Data Hint Types: -//! - `0x04` (`Noop`): Pass-through data -//! - `0x05` (`EcRecover`): ECRECOVER inputs (currently returns empty) +//! +//! For data hints, the hint code (32 bits) is structured as follows: +//! - **Bit 31 (MSB)**: Indicates if the data is pass-through (1) or requires computation (0) +//! - **Bits 0-30**: Encode the built-in hint code as defined in the constants +//! (e.g., `HINT_SHA256`, `HINT_BN254_G1_ADD`, `HINT_SECP256K1_ECRECOVER`, etc.) //! ``` use std::fmt::Display; @@ -50,70 +53,38 @@ use std::fmt::Display; use anyhow::Result; // === CONTROL CODES === -const CTRL_START: u32 = 0x00; -const CTRL_END: u32 = 0x01; -const CTRL_CANCEL: u32 = 0x02; -const CTRL_ERROR: u32 = 0x03; +const CTRL_START: u32 = 0x0000; +const CTRL_END: u32 = 0x0001; +const CTRL_CANCEL: u32 = 0x0002; +const CTRL_ERROR: u32 = 0x0003; // === BUILT-IN HINT CODES === -// Noop hint code -const HINT_NOOP: u32 = 0x04; - -// Secp256k1 Scalar hint codes -const HINT_SECP256K1_FN_REDUCE: u32 = 0x02000; -const HINT_SECP256K1_FN_ADD: u32 = 0x02001; -const HINT_SECP256K1_FN_NEG: u32 = 0x02002; -const HINT_SECP256K1_FN_SUB: u32 = 0x02003; -const HINT_SECP256K1_FN_MUL: u32 = 0x02004; -const HINT_SECP256K1_FN_INV: u32 = 0x02005; -// Secp256k1 Field hint codes -const HINT_SECP256K1_FP_REDUCE: u32 = 0x02010; -const HINT_SECP256K1_FP_ADD: u32 = 0x02011; -const HINT_SECP256K1_FP_NEGATE: u32 = 0x02012; -const HINT_SECP256K1_FP_MUL: u32 = 0x02013; -const HINT_SECP256K1_FP_MUL_SCALAR: u32 = 0x02014; -// Secp256k1 Curve hint codes -const HINT_SECP256K1_TO_AFFINE: u32 = 0x02020; -const HINT_SECP256K1_DECOMPRESS: u32 = 0x02021; -const HINT_SECP256K1_DOUBLE_SCALAR_MUL_WITH_G: u32 = 0x02022; -const HINT_SECP256K1_ECDSA_VERIFY: u32 = 0x02023; - -// Big integer arithmetic hint codes -const HINT_REDMOD256: u32 = 0x06; -const HINT_ADDMOD256: u32 = 0x07; -const HINT_MULMOD256: u32 = 0x08; -const HINT_DIVREM256: u32 = 0x09; -const HINT_WPOW256: u32 = 0x0A; -const HINT_OMUL256: u32 = 0x0B; -const HINT_WMUL256: u32 = 0x0C; - -// Modular exponentiation hint code -const HINT_MODEXP: u32 = 0x0D; +// SHA256 hint codes +const HINT_SHA256: u32 = 0x0100; // BN254 hint codes -const HINT_BN254_IS_ON_CURVE: u32 = 0x0E; -const HINT_BN254_TO_AFFINE: u32 = 0x0F; -const HINT_BN254_ADD: u32 = 0x10; -const HINT_BN254_MUL: u32 = 0x11; -const HINT_BN254_TO_AFFINE_TWIST: u32 = 0x12; -const HINT_BN254_IS_ON_CURVE_TWIST: u32 = 0x13; -const HINT_BN254_IS_ON_SUBGROUP_TWIST: u32 = 0x14; -const HINT_BN254_PAIRING_BATCH: u32 = 0x15; +const HINT_BN254_G1_ADD: u32 = 0x0200; +const HINT_BN254_G1_MUL: u32 = 0x0201; +const HINT_BN254_PAIRING_CHECK: u32 = 0x0205; + +// Secp256k1 hint codes +const HINT_SECP256K1_ECRECOVER: u32 = 0x0300; +const HINT_SECP256R1_VERIFY_SIGNATURE: u32 = 0x0301; // BLS12-381 hint codes -const HINT_BLS12_381_MUL_FP12: u32 = 0x16; -const HINT_BLS12_381_DECOMPRESS: u32 = 0x17; -const HINT_BLS12_381_IS_ON_CURVE: u32 = 0x18; -const HINT_BLS12_381_IS_ON_SUBGROUP: u32 = 0x19; -const HINT_BLS12_381_ADD: u32 = 0x1A; -const HINT_BLS12_381_SCALAR_MUL: u32 = 0x1B; -const HINT_BLS12_381_DECOMPRESS_TWIST: u32 = 0x1C; -const HINT_BLS12_381_IS_ON_CURVE_TWIST: u32 = 0x1D; -const HINT_BLS12_381_IS_ON_SUBGROUP_TWIST: u32 = 0x1E; -const HINT_BLS12_381_ADD_TWIST: u32 = 0x1F; -const HINT_BLS12_381_SCALAR_MUL_TWIST: u32 = 0x20; -const HINT_BLS12_381_MILLER_LOOP: u32 = 0x21; -const HINT_BLS12_381_FINAL_EXP: u32 = 0x22; +const HINT_BLS12_381_G1_ADD: u32 = 0x0400; +const HINT_BLS12_381_G1_MSM: u32 = 0x0401; +const HINT_BLS12_381_G2_ADD: u32 = 0x0405; +const HINT_BLS12_381_G2_MSM: u32 = 0x0406; +const HINT_BLS12_381_PAIRING_CHECK: u32 = 0x040A; +const HINT_BLS12_381_FP_TO_G1: u32 = 0x0410; +const HINT_BLS12_381_FP2_TO_G2: u32 = 0x0411; + +// Modular exponentiation hint codes +const HINT_MODEXP: u32 = 0x0500; + +// KZG hint codes +const HINT_VERIFY_KZG_PROOF: u32 = 0x0600; /// Control code variants for stream control. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -159,171 +130,75 @@ impl TryFrom for CtrlHint { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u32)] pub enum BuiltInHint { - /// Pass-through hint type. - /// When a hint has this type, the processor simply passes through the data - /// without any additional computation. - Noop = HINT_NOOP, + // SHA256 hint types. + /// Compute SHA-256 hash + Sha256 = HINT_SHA256, + + // BN254 hint types + /// BN254 elliptic curve addition. + Bn254G1Add = HINT_BN254_G1_ADD, + /// BN254 elliptic curve scalar multiplication. + Bn254G1Mul = HINT_BN254_G1_MUL, + /// BN254 pairing check. + Bn254PairingCheck = HINT_BN254_PAIRING_CHECK, // Secp256k1 hint types. - /// Secp256k1 scalar field reduction hint type. - Secp256K1FnReduce = HINT_SECP256K1_FN_REDUCE, - /// Secp256k1 scalar field addition hint type. - Secp256K1FnAdd = HINT_SECP256K1_FN_ADD, - /// Secp256k1 scalar field negation hint type. - Secp256K1FnNeg = HINT_SECP256K1_FN_NEG, - /// Secp256k1 scalar field subtraction hint type. - Secp256K1FnSub = HINT_SECP256K1_FN_SUB, - /// Secp256k1 scalar field multiplication hint type. - Secp256K1FnMul = HINT_SECP256K1_FN_MUL, - /// Secp256k1 scalar field inversion hint type. - Secp256K1FnInv = HINT_SECP256K1_FN_INV, - /// Secp256k1 base field reduction hint type. - Secp256K1FpReduce = HINT_SECP256K1_FP_REDUCE, - /// Secp256k1 base field addition hint type. - Secp256K1FpAdd = HINT_SECP256K1_FP_ADD, - /// Secp256k1 base field negation hint type. - Secp256K1FpNegate = HINT_SECP256K1_FP_NEGATE, - /// Secp256k1 base field multiplication hint type. - Secp256K1FpMul = HINT_SECP256K1_FP_MUL, - /// Secp256k1 base field scalar multiplication hint type. - Secp256K1FpMulScalar = HINT_SECP256K1_FP_MUL_SCALAR, - /// Secp256k1 to affine coordinates hint type. - Secp256K1ToAffine = HINT_SECP256K1_TO_AFFINE, - /// Secp256k1 point decompression hint type. - Secp256K1Decompress = HINT_SECP256K1_DECOMPRESS, - /// Secp256k1 double scalar multiplication with G hint type. - Secp256K1DoubleScalarMulWithG = HINT_SECP256K1_DOUBLE_SCALAR_MUL_WITH_G, - /// Secp256k1 ECDSA verification hint type. - Secp256K1EcdsaVerify = HINT_SECP256K1_ECDSA_VERIFY, - - // Big Integer Arithmetic Hints - /// Modular reduction of a 256-bit integer hint type. - RedMod256 = HINT_REDMOD256, - /// Modular addition of 256-bit integers hint type. - AddMod256 = HINT_ADDMOD256, - /// Modular multiplication of 256-bit integers hint type. - MulMod256 = HINT_MULMOD256, - /// Division and remainder of 256-bit integers hint type. - DivRem256 = HINT_DIVREM256, - /// Wrapping exponentiation of 256-bit integers hint type. - WPow256 = HINT_WPOW256, - /// Overflowing multiplication of 256-bit integers hint type. - OMul256 = HINT_OMUL256, - /// Wrapping multiplication of 256-bit integers hint type. - WMul256 = HINT_WMUL256, - - /// Modular exponentiation hint type. + /// secp256k1 ECDSA signature recovery. + Secp256k1EcRecover = HINT_SECP256K1_ECRECOVER, + /// secp256r1 (P-256) signature verification. + Secp256r1VerifySignature = HINT_SECP256R1_VERIFY_SIGNATURE, + + // BLS12-381 hint types. + /// BLS12-381 G1 addition (returns 96-byte unpadded G1 point) + Bls12_381G1Add = HINT_BLS12_381_G1_ADD, + /// BLS12-381 G1 multi-scalar multiplication (returns 96-byte unpadded G1 point) + Bls12_381G1Msm = HINT_BLS12_381_G1_MSM, + /// BLS12-381 G2 addition (returns 192-byte unpadded G2 point) + Bls12_381G2Add = HINT_BLS12_381_G2_ADD, + /// BLS12-381 G2 multi-scalar multiplication (returns 192-byte unpadded G2 point) + Bls12_381G2Msm = HINT_BLS12_381_G2_MSM, + /// BLS12-381 pairing check. + Bls12_381PairingCheck = HINT_BLS12_381_PAIRING_CHECK, + /// BLS12-381 map field element to G1. + Bls12_381FpToG1 = HINT_BLS12_381_FP_TO_G1, + /// BLS12-381 map field element to G2. + Bls12_381Fp2ToG2 = HINT_BLS12_381_FP2_TO_G2, + + // Modular exponentiation hint types. + /// Modular exponentiation. ModExp = HINT_MODEXP, - // BN254 Precompile Hints - /// Check if point is on curve hint type for BN254 curve. - Bn254IsOnCurve = HINT_BN254_IS_ON_CURVE, - /// Convert to affine coordinates hint type for BN254 curve. - Bn254ToAffine = HINT_BN254_TO_AFFINE, - /// Point addition hint type for BN254 curve. - Bn254Add = HINT_BN254_ADD, - /// Scalar multiplication hint type for BN254 curve. - Bn254Mul = HINT_BN254_MUL, - /// Convert to affine coordinates hint type for BN254 twist. - Bn254ToAffineTwist = HINT_BN254_TO_AFFINE_TWIST, - /// Check if point is on curve hint type for BN254 twist. - Bn254IsOnCurveTwist = HINT_BN254_IS_ON_CURVE_TWIST, - /// Check if point is in subgroup hint type for BN254 twist. - Bn254IsOnSubgroupTwist = HINT_BN254_IS_ON_SUBGROUP_TWIST, - /// Pairing batch computation hint type for BN254 curve. - Bn254PairingBatch = HINT_BN254_PAIRING_BATCH, - - // BLS12-381 Precompile Hints - /// Multiplication in Fp12 hint type for BLS12-381 curve. - Bls12_381MulFp12 = HINT_BLS12_381_MUL_FP12, - /// Point decompression hint type for BLS12-381 curve. - Bls12_381Decompress = HINT_BLS12_381_DECOMPRESS, - /// Check if point is on curve hint type for BLS12-381 curve. - Bls12_381IsOnCurve = HINT_BLS12_381_IS_ON_CURVE, - /// Check if point is in subgroup hint type for BLS12-381 curve. - Bls12_381IsOnSubgroup = HINT_BLS12_381_IS_ON_SUBGROUP, - /// Point addition hint type for BLS12-381 curve. - Bls12_381Add = HINT_BLS12_381_ADD, - /// Scalar multiplication hint type for BLS12-381 curve. - Bls12_381ScalarMul = HINT_BLS12_381_SCALAR_MUL, - /// Point decompression hint type for BLS12-381 twist. - Bls12_381DecompressTwist = HINT_BLS12_381_DECOMPRESS_TWIST, - /// Check if point is on curve hint type for BLS12-381 twist. - Bls12_381IsOnCurveTwist = HINT_BLS12_381_IS_ON_CURVE_TWIST, - /// Check if point is in subgroup hint type for BLS12-381 twist. - Bls12_381IsOnSubgroupTwist = HINT_BLS12_381_IS_ON_SUBGROUP_TWIST, - /// Point addition hint type for BLS12-381 twist. - Bls12_381AddTwist = HINT_BLS12_381_ADD_TWIST, - /// Scalar multiplication hint type for BLS12-381 twist. - Bls12_381ScalarMulTwist = HINT_BLS12_381_SCALAR_MUL_TWIST, - /// Miller loop computation hint type for BLS12-381 curve. - Bls12_381MillerLoop = HINT_BLS12_381_MILLER_LOOP, - /// Final exponentiation computation hint type for BLS12-381 curve. - Bls12_381FinalExp = HINT_BLS12_381_FINAL_EXP, + // KZG hint types. + /// Verify KZG proof. + VerifyKzgProof = HINT_VERIFY_KZG_PROOF, } impl Display for BuiltInHint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = match self { - // Noop Hint - BuiltInHint::Noop => "NOOP", - - // Secp256k1 Scalar Hints - BuiltInHint::Secp256K1FnReduce => "SECP256K1_FN_REDUCE", - BuiltInHint::Secp256K1FnAdd => "SECP256K1_FN_ADD", - BuiltInHint::Secp256K1FnNeg => "SECP256K1_FN_NEG", - BuiltInHint::Secp256K1FnSub => "SECP256K1_FN_SUB", - BuiltInHint::Secp256K1FnMul => "SECP256K1_FN_MUL", - BuiltInHint::Secp256K1FnInv => "SECP256K1_FN_INV", - // Secp256k1 Field Hints - BuiltInHint::Secp256K1FpReduce => "SECP256K1_FP_REDUCE", - BuiltInHint::Secp256K1FpAdd => "SECP256K1_FP_ADD", - BuiltInHint::Secp256K1FpNegate => "SECP256K1_FP_NEGATE", - BuiltInHint::Secp256K1FpMul => "SECP256K1_FP_MUL", - BuiltInHint::Secp256K1FpMulScalar => "SECP256K1_FP_MUL_SCALAR", - // Secp256k1 Curve Hints - BuiltInHint::Secp256K1ToAffine => "SECP256K1_TO_AFFINE", - BuiltInHint::Secp256K1Decompress => "SECP256K1_DECOMPRESS", - BuiltInHint::Secp256K1DoubleScalarMulWithG => "SECP256K1_DOUBLE_SCALAR_MUL_WITH_G", - BuiltInHint::Secp256K1EcdsaVerify => "SECP256K1_ECDSA_VERIFY", - - // Big Integer Arithmetic Hints - BuiltInHint::RedMod256 => "REDMOD256", - BuiltInHint::AddMod256 => "ADDMOD256", - BuiltInHint::MulMod256 => "MULMOD256", - BuiltInHint::DivRem256 => "DIVREM256", - BuiltInHint::WPow256 => "WPOW256", - BuiltInHint::OMul256 => "OMUL256", - BuiltInHint::WMul256 => "WMUL256", - - // Modular Exponentiation Hint - BuiltInHint::ModExp => "MODEXP", - + // SHA256 hint types + BuiltInHint::Sha256 => "SHA256", // BN254 Hints - BuiltInHint::Bn254IsOnCurve => "BN254_IS_ON_CURVE", - BuiltInHint::Bn254ToAffine => "BN254_TO_AFFINE", - BuiltInHint::Bn254Add => "BN254_ADD", - BuiltInHint::Bn254Mul => "BN254_MUL", - BuiltInHint::Bn254ToAffineTwist => "BN254_TO_AFFINE_TWIST", - BuiltInHint::Bn254IsOnCurveTwist => "BN254_IS_ON_CURVE_TWIST", - BuiltInHint::Bn254IsOnSubgroupTwist => "BN254_IS_ON_SUBGROUP_TWIST", - BuiltInHint::Bn254PairingBatch => "BN254_PAIRING_BATCH", - + BuiltInHint::Bn254G1Add => "BN254_G1_ADD", + BuiltInHint::Bn254G1Mul => "BN254_G1_MUL", + BuiltInHint::Bn254PairingCheck => "BN254_PAIRING_CHECK", + // Secp256k1 Hints + BuiltInHint::Secp256k1EcRecover => "SECP256K1_ECRECOVER", + BuiltInHint::Secp256r1VerifySignature => "SECP256R1_VERIFY_SIGNATURE", // BLS12-381 Hints - BuiltInHint::Bls12_381MulFp12 => "BLS12_381_MUL_FP12", - BuiltInHint::Bls12_381Decompress => "BLS12_381_DECOMPRESS", - BuiltInHint::Bls12_381IsOnCurve => "BLS12_381_IS_ON_CURVE", - BuiltInHint::Bls12_381IsOnSubgroup => "BLS12_381_IS_ON_SUBGROUP", - BuiltInHint::Bls12_381Add => "BLS12_381_ADD", - BuiltInHint::Bls12_381ScalarMul => "BLS12_381_SCALAR_MUL", - BuiltInHint::Bls12_381DecompressTwist => "BLS12_381_DECOMPRESS_TWIST", - BuiltInHint::Bls12_381IsOnCurveTwist => "BLS12_381_IS_ON_CURVE_TWIST", - BuiltInHint::Bls12_381IsOnSubgroupTwist => "BLS12_381_IS_ON_SUBGROUP_TWIST", - BuiltInHint::Bls12_381AddTwist => "BLS12_381_ADD_TWIST", - BuiltInHint::Bls12_381ScalarMulTwist => "BLS12_381_SCALAR_MUL_TWIST", - BuiltInHint::Bls12_381MillerLoop => "BLS12_381_MILLER_LOOP", - BuiltInHint::Bls12_381FinalExp => "BLS12_381_FINAL_EXP", + BuiltInHint::Bls12_381G1Add => "BLS12_381_G1_ADD", + BuiltInHint::Bls12_381G1Msm => "BLS12_381_G1_MSM", + BuiltInHint::Bls12_381G2Add => "BLS12_381_G2_ADD", + BuiltInHint::Bls12_381G2Msm => "BLS12_381_G2_MSM", + BuiltInHint::Bls12_381PairingCheck => "BLS12_381_PAIRING_CHECK", + BuiltInHint::Bls12_381FpToG1 => "BLS12_381_FP_TO_G1", + BuiltInHint::Bls12_381Fp2ToG2 => "BLS12_381_FP2_TO_G2", + // Modular Exponentiation Hint + BuiltInHint::ModExp => "MODEXP", + // KZG Hint + BuiltInHint::VerifyKzgProof => "VERIFY_KZG_PROOF", }; + write!(f, "{} ({:#x})", name, *self as u32) } } @@ -333,65 +208,27 @@ impl TryFrom for BuiltInHint { fn try_from(value: u32) -> Result { match value { - // Noop Hint - HINT_NOOP => Ok(Self::Noop), - - // Secp256K1 Scalar Hints - HINT_SECP256K1_FN_REDUCE => Ok(Self::Secp256K1FnReduce), - HINT_SECP256K1_FN_ADD => Ok(Self::Secp256K1FnAdd), - HINT_SECP256K1_FN_NEG => Ok(Self::Secp256K1FnNeg), - HINT_SECP256K1_FN_SUB => Ok(Self::Secp256K1FnSub), - HINT_SECP256K1_FN_MUL => Ok(Self::Secp256K1FnMul), - HINT_SECP256K1_FN_INV => Ok(Self::Secp256K1FnInv), - // Secp256k1 Field Hints - HINT_SECP256K1_FP_REDUCE => Ok(Self::Secp256K1FpReduce), - HINT_SECP256K1_FP_ADD => Ok(Self::Secp256K1FpAdd), - HINT_SECP256K1_FP_NEGATE => Ok(Self::Secp256K1FpNegate), - HINT_SECP256K1_FP_MUL => Ok(Self::Secp256K1FpMul), - HINT_SECP256K1_FP_MUL_SCALAR => Ok(Self::Secp256K1FpMulScalar), - // Secp256k1 Curve Hints - HINT_SECP256K1_TO_AFFINE => Ok(Self::Secp256K1ToAffine), - HINT_SECP256K1_DECOMPRESS => Ok(Self::Secp256K1Decompress), - HINT_SECP256K1_DOUBLE_SCALAR_MUL_WITH_G => Ok(Self::Secp256K1DoubleScalarMulWithG), - HINT_SECP256K1_ECDSA_VERIFY => Ok(Self::Secp256K1EcdsaVerify), - - // Big Integer Arithmetic Hints - HINT_REDMOD256 => Ok(Self::RedMod256), - HINT_ADDMOD256 => Ok(Self::AddMod256), - HINT_MULMOD256 => Ok(Self::MulMod256), - HINT_DIVREM256 => Ok(Self::DivRem256), - HINT_WPOW256 => Ok(Self::WPow256), - HINT_OMUL256 => Ok(Self::OMul256), - HINT_WMUL256 => Ok(Self::WMul256), - - // Modular Exponentiation Hint - HINT_MODEXP => Ok(Self::ModExp), - + // SHA256 hint types + HINT_SHA256 => Ok(Self::Sha256), // BN254 Hints - HINT_BN254_IS_ON_CURVE => Ok(Self::Bn254IsOnCurve), - HINT_BN254_TO_AFFINE => Ok(Self::Bn254ToAffine), - HINT_BN254_ADD => Ok(Self::Bn254Add), - HINT_BN254_MUL => Ok(Self::Bn254Mul), - HINT_BN254_TO_AFFINE_TWIST => Ok(Self::Bn254ToAffineTwist), - HINT_BN254_IS_ON_CURVE_TWIST => Ok(Self::Bn254IsOnCurveTwist), - HINT_BN254_IS_ON_SUBGROUP_TWIST => Ok(Self::Bn254IsOnSubgroupTwist), - HINT_BN254_PAIRING_BATCH => Ok(Self::Bn254PairingBatch), - + HINT_BN254_G1_ADD => Ok(Self::Bn254G1Add), + HINT_BN254_G1_MUL => Ok(Self::Bn254G1Mul), + HINT_BN254_PAIRING_CHECK => Ok(Self::Bn254PairingCheck), + // Secp256k1 Hints + HINT_SECP256K1_ECRECOVER => Ok(Self::Secp256k1EcRecover), + HINT_SECP256R1_VERIFY_SIGNATURE => Ok(Self::Secp256r1VerifySignature), // BLS12-381 Hints - HINT_BLS12_381_MUL_FP12 => Ok(Self::Bls12_381MulFp12), - HINT_BLS12_381_DECOMPRESS => Ok(Self::Bls12_381Decompress), - HINT_BLS12_381_IS_ON_CURVE => Ok(Self::Bls12_381IsOnCurve), - HINT_BLS12_381_IS_ON_SUBGROUP => Ok(Self::Bls12_381IsOnSubgroup), - HINT_BLS12_381_ADD => Ok(Self::Bls12_381Add), - HINT_BLS12_381_SCALAR_MUL => Ok(Self::Bls12_381ScalarMul), - HINT_BLS12_381_DECOMPRESS_TWIST => Ok(Self::Bls12_381DecompressTwist), - HINT_BLS12_381_IS_ON_CURVE_TWIST => Ok(Self::Bls12_381IsOnCurveTwist), - HINT_BLS12_381_IS_ON_SUBGROUP_TWIST => Ok(Self::Bls12_381IsOnSubgroupTwist), - HINT_BLS12_381_ADD_TWIST => Ok(Self::Bls12_381AddTwist), - HINT_BLS12_381_SCALAR_MUL_TWIST => Ok(Self::Bls12_381ScalarMulTwist), - HINT_BLS12_381_MILLER_LOOP => Ok(Self::Bls12_381MillerLoop), - HINT_BLS12_381_FINAL_EXP => Ok(Self::Bls12_381FinalExp), - + HINT_BLS12_381_G1_ADD => Ok(Self::Bls12_381G1Add), + HINT_BLS12_381_G1_MSM => Ok(Self::Bls12_381G1Msm), + HINT_BLS12_381_G2_ADD => Ok(Self::Bls12_381G2Add), + HINT_BLS12_381_G2_MSM => Ok(Self::Bls12_381G2Msm), + HINT_BLS12_381_PAIRING_CHECK => Ok(Self::Bls12_381PairingCheck), + HINT_BLS12_381_FP_TO_G1 => Ok(Self::Bls12_381FpToG1), + HINT_BLS12_381_FP2_TO_G2 => Ok(Self::Bls12_381Fp2ToG2), + // Modular Exponentiation Hint + HINT_MODEXP => Ok(Self::ModExp), + // KZG Hint + HINT_VERIFY_KZG_PROOF => Ok(Self::VerifyKzgProof), _ => Err(anyhow::anyhow!("Invalid built-in hint code: {:#x}", value)), } } @@ -448,76 +285,29 @@ impl HintCode { HintCode::Ctrl(CtrlHint::Error) => CTRL_ERROR, // Built-In Hint Codes - // Noop Hint - HintCode::BuiltIn(BuiltInHint::Noop) => HINT_NOOP, - - // Secp256K1 Scalar Hint Codes - HintCode::BuiltIn(BuiltInHint::Secp256K1FnReduce) => HINT_SECP256K1_FN_REDUCE, - HintCode::BuiltIn(BuiltInHint::Secp256K1FnAdd) => HINT_SECP256K1_FN_ADD, - HintCode::BuiltIn(BuiltInHint::Secp256K1FnNeg) => HINT_SECP256K1_FN_NEG, - HintCode::BuiltIn(BuiltInHint::Secp256K1FnSub) => HINT_SECP256K1_FN_SUB, - HintCode::BuiltIn(BuiltInHint::Secp256K1FnMul) => HINT_SECP256K1_FN_MUL, - HintCode::BuiltIn(BuiltInHint::Secp256K1FnInv) => HINT_SECP256K1_FN_INV, - // Secp256k1 Field Hint Codes - HintCode::BuiltIn(BuiltInHint::Secp256K1FpReduce) => HINT_SECP256K1_FP_REDUCE, - HintCode::BuiltIn(BuiltInHint::Secp256K1FpAdd) => HINT_SECP256K1_FP_ADD, - HintCode::BuiltIn(BuiltInHint::Secp256K1FpNegate) => HINT_SECP256K1_FP_NEGATE, - HintCode::BuiltIn(BuiltInHint::Secp256K1FpMul) => HINT_SECP256K1_FP_MUL, - HintCode::BuiltIn(BuiltInHint::Secp256K1FpMulScalar) => HINT_SECP256K1_FP_MUL_SCALAR, - // Secp256k1 Curve Hint Codes - HintCode::BuiltIn(BuiltInHint::Secp256K1ToAffine) => HINT_SECP256K1_TO_AFFINE, - HintCode::BuiltIn(BuiltInHint::Secp256K1Decompress) => HINT_SECP256K1_DECOMPRESS, - HintCode::BuiltIn(BuiltInHint::Secp256K1DoubleScalarMulWithG) => { - HINT_SECP256K1_DOUBLE_SCALAR_MUL_WITH_G - } - HintCode::BuiltIn(BuiltInHint::Secp256K1EcdsaVerify) => HINT_SECP256K1_ECDSA_VERIFY, - - // Big Integer Arithmetic Hints - HintCode::BuiltIn(BuiltInHint::RedMod256) => HINT_REDMOD256, - HintCode::BuiltIn(BuiltInHint::AddMod256) => HINT_ADDMOD256, - HintCode::BuiltIn(BuiltInHint::MulMod256) => HINT_MULMOD256, - HintCode::BuiltIn(BuiltInHint::DivRem256) => HINT_DIVREM256, - HintCode::BuiltIn(BuiltInHint::WPow256) => HINT_WPOW256, - HintCode::BuiltIn(BuiltInHint::OMul256) => HINT_OMUL256, - HintCode::BuiltIn(BuiltInHint::WMul256) => HINT_WMUL256, - - // Modular Exponentiation Hint - HintCode::BuiltIn(BuiltInHint::ModExp) => HINT_MODEXP, - + // SHA256 Hints + HintCode::BuiltIn(BuiltInHint::Sha256) => HINT_SHA256, // BN254 Hints - HintCode::BuiltIn(BuiltInHint::Bn254IsOnCurve) => HINT_BN254_IS_ON_CURVE, - HintCode::BuiltIn(BuiltInHint::Bn254ToAffine) => HINT_BN254_TO_AFFINE, - HintCode::BuiltIn(BuiltInHint::Bn254Add) => HINT_BN254_ADD, - HintCode::BuiltIn(BuiltInHint::Bn254Mul) => HINT_BN254_MUL, - HintCode::BuiltIn(BuiltInHint::Bn254ToAffineTwist) => HINT_BN254_TO_AFFINE_TWIST, - HintCode::BuiltIn(BuiltInHint::Bn254IsOnCurveTwist) => HINT_BN254_IS_ON_CURVE_TWIST, - HintCode::BuiltIn(BuiltInHint::Bn254IsOnSubgroupTwist) => { - HINT_BN254_IS_ON_SUBGROUP_TWIST + HintCode::BuiltIn(BuiltInHint::Bn254G1Add) => HINT_BN254_G1_ADD, + HintCode::BuiltIn(BuiltInHint::Bn254G1Mul) => HINT_BN254_G1_MUL, + HintCode::BuiltIn(BuiltInHint::Bn254PairingCheck) => HINT_BN254_PAIRING_CHECK, + // Secp256k1 Hints + HintCode::BuiltIn(BuiltInHint::Secp256k1EcRecover) => HINT_SECP256K1_ECRECOVER, + HintCode::BuiltIn(BuiltInHint::Secp256r1VerifySignature) => { + HINT_SECP256R1_VERIFY_SIGNATURE } - HintCode::BuiltIn(BuiltInHint::Bn254PairingBatch) => HINT_BN254_PAIRING_BATCH, - // BLS12-381 Hints - HintCode::BuiltIn(BuiltInHint::Bls12_381MulFp12) => HINT_BLS12_381_MUL_FP12, - HintCode::BuiltIn(BuiltInHint::Bls12_381Decompress) => HINT_BLS12_381_DECOMPRESS, - HintCode::BuiltIn(BuiltInHint::Bls12_381IsOnCurve) => HINT_BLS12_381_IS_ON_CURVE, - HintCode::BuiltIn(BuiltInHint::Bls12_381IsOnSubgroup) => HINT_BLS12_381_IS_ON_SUBGROUP, - HintCode::BuiltIn(BuiltInHint::Bls12_381Add) => HINT_BLS12_381_ADD, - HintCode::BuiltIn(BuiltInHint::Bls12_381ScalarMul) => HINT_BLS12_381_SCALAR_MUL, - HintCode::BuiltIn(BuiltInHint::Bls12_381DecompressTwist) => { - HINT_BLS12_381_DECOMPRESS_TWIST - } - HintCode::BuiltIn(BuiltInHint::Bls12_381IsOnCurveTwist) => { - HINT_BLS12_381_IS_ON_CURVE_TWIST - } - HintCode::BuiltIn(BuiltInHint::Bls12_381IsOnSubgroupTwist) => { - HINT_BLS12_381_IS_ON_SUBGROUP_TWIST - } - HintCode::BuiltIn(BuiltInHint::Bls12_381AddTwist) => HINT_BLS12_381_ADD_TWIST, - HintCode::BuiltIn(BuiltInHint::Bls12_381ScalarMulTwist) => { - HINT_BLS12_381_SCALAR_MUL_TWIST - } - HintCode::BuiltIn(BuiltInHint::Bls12_381MillerLoop) => HINT_BLS12_381_MILLER_LOOP, - HintCode::BuiltIn(BuiltInHint::Bls12_381FinalExp) => HINT_BLS12_381_FINAL_EXP, + HintCode::BuiltIn(BuiltInHint::Bls12_381G1Add) => HINT_BLS12_381_G1_ADD, + HintCode::BuiltIn(BuiltInHint::Bls12_381G1Msm) => HINT_BLS12_381_G1_MSM, + HintCode::BuiltIn(BuiltInHint::Bls12_381G2Add) => HINT_BLS12_381_G2_ADD, + HintCode::BuiltIn(BuiltInHint::Bls12_381G2Msm) => HINT_BLS12_381_G2_MSM, + HintCode::BuiltIn(BuiltInHint::Bls12_381PairingCheck) => HINT_BLS12_381_PAIRING_CHECK, + HintCode::BuiltIn(BuiltInHint::Bls12_381FpToG1) => HINT_BLS12_381_FP_TO_G1, + HintCode::BuiltIn(BuiltInHint::Bls12_381Fp2ToG2) => HINT_BLS12_381_FP2_TO_G2, + // Modular Exponentiation Hint + HintCode::BuiltIn(BuiltInHint::ModExp) => HINT_MODEXP, + // KZG Hint + HintCode::BuiltIn(BuiltInHint::VerifyKzgProof) => HINT_VERIFY_KZG_PROOF, // Custom Hints HintCode::Custom(code) => code, @@ -532,19 +322,26 @@ impl HintCode { pub struct PrecompileHint { /// The type of hint, determining how the data should be processed. pub hint_code: HintCode, + /// Whether this hint contains pass-through data (true) or requires computation (false). + /// Determined by bit 31 (MSB) of the hint code. + pub is_passthrough: bool, /// The hint payload data. pub data: Vec, + /// Data length in bytes + pub data_len_bytes: usize, } impl std::fmt::Debug for PrecompileHint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let data_display = if self.data.len() <= 10 { - format!("{:?}", self.data) + format!("{:x?}", self.data) } else { - format!("{:?}... ({} more)", &self.data[..10], self.data.len() - 10) + format!("{:x?}... ({} more)", &self.data[..10], self.data.len() - 10) }; f.debug_struct("PrecompileHint") .field("hint_type", &self.hint_code) + .field("is_passthrough", &self.is_passthrough) + .field("data_len_bytes", &self.data_len_bytes) .field("data", &data_display) .finish() } @@ -570,26 +367,36 @@ impl PrecompileHint { } let header = slice[idx]; - let length = (header & 0xFFFFFFFF) as u32; - - if slice.len() < idx + length as usize + 1 { - return Err(anyhow::anyhow!( - "Slice too short for hint data: expected {}, got {}", - length, - slice.len() - idx - 1 - )); - } + // Extract length from lower 32 bits + let length = header & 0xFFFFFFFF; + + // Calculate how many u64s are needed to hold length + let num_u64s = ((length + 7) / 8) as usize; + + anyhow::ensure!( + slice.len() >= idx + 1 + num_u64s, + "Slice too short for hint data: expected {} u64s, got {}", + num_u64s, + slice.len() - idx - 1 + ); + + // Extract hint code from upper 32 bits let hint_code_32 = (header >> 32) as u32; + // Extract pass-through flag from bit 31 (MSB) - shift is faster than mask + let is_passthrough = hint_code_32 >> 31 != 0; + // Extract the actual hint code from bits 0-30 - mask is optimal + let hint_code_value = hint_code_32 & 0x7FFFFFFF; + let hint_code = if allow_custom { - HintCode::try_from(hint_code_32).unwrap_or(HintCode::Custom(hint_code_32)) + HintCode::try_from(hint_code_value).unwrap_or(HintCode::Custom(hint_code_value)) } else { - HintCode::try_from(hint_code_32)? + HintCode::try_from(hint_code_value)? }; // Create a new Vec with the hint data. - let data = slice[idx + 1..idx + length as usize + 1].to_vec(); + let data = slice[idx + 1..idx + 1 + num_u64s].to_vec(); - Ok(PrecompileHint { hint_code, data }) + Ok(PrecompileHint { hint_code, is_passthrough, data, data_len_bytes: length as usize }) } } diff --git a/precompiles/hints/benches/hints_benchmarks.rs b/precompiles/hints/benches/hints_benchmarks.rs index d84e871dd..7681624cc 100644 --- a/precompiles/hints/benches/hints_benchmarks.rs +++ b/precompiles/hints/benches/hints_benchmarks.rs @@ -6,7 +6,6 @@ use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; use zisk_common::io::StreamSink; -use zisk_common::{BuiltInHint, HintCode}; struct BenchSink { received: Arc>>>, @@ -24,10 +23,10 @@ fn make_header(hint_type: u32, length: u32) -> u64 { } fn parallel_speedup_benchmark(c: &mut Criterion) { - // Define custom hints with known processing times - const FAST_HINT: u32 = 0x100; // 1ms - const MEDIUM_HINT: u32 = 0x101; // 5ms - const SLOW_HINT: u32 = 0x102; // 10ms + // Define custom hints with known processing times (use high values to avoid built-in conflicts) + const FAST_HINT: u32 = 0x7FFF_0000; // 1ms + const MEDIUM_HINT: u32 = 0x7FFF_0001; // 5ms + const SLOW_HINT: u32 = 0x7FFF_0002; // 10ms // Test configuration const NUM_FAST: usize = 100; @@ -101,9 +100,9 @@ fn parallel_speedup_benchmark(c: &mut Criterion) { } fn microsecond_hints_benchmark(c: &mut Criterion) { - const ULTRA_FAST: u32 = 0x110; // 10µs - const VERY_FAST: u32 = 0x111; // 50µs - const FAST: u32 = 0x112; // 100µs + const ULTRA_FAST: u32 = 0x7FFF_0010; // 10µs + const VERY_FAST: u32 = 0x7FFF_0011; // 50µs + const FAST: u32 = 0x7FFF_0012; // 100µs const NUM_HINTS: usize = 1000; let mut group = c.benchmark_group("microsecond_hints"); @@ -150,11 +149,11 @@ fn microsecond_hints_benchmark(c: &mut Criterion) { } fn workload_patterns_benchmark(c: &mut Criterion) { - const VERY_FAST: u32 = 0x100; // 0.5ms - const FAST: u32 = 0x101; // 2ms - const MEDIUM: u32 = 0x102; // 5ms - const SLOW: u32 = 0x103; // 10ms - const VERY_SLOW: u32 = 0x104; // 20ms + const VERY_FAST: u32 = 0x7FFF_0020; // 0.5ms + const FAST: u32 = 0x7FFF_0021; // 2ms + const MEDIUM: u32 = 0x7FFF_0022; // 5ms + const SLOW: u32 = 0x7FFF_0023; // 10ms + const VERY_SLOW: u32 = 0x7FFF_0024; // 20ms let mut group = c.benchmark_group("workload_patterns"); group.sample_size(10); @@ -236,6 +235,9 @@ fn noop_throughput_benchmark(c: &mut Criterion) { let hint_counts = [1000, 10000, 100000]; + // Pass-through hint code (bit 31 set = pass-through, no computation needed) + const PASSTHROUGH_HINT: u32 = 0x8000_1000; + for &count in &hint_counts { group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, &num_hints| { b.iter(|| { @@ -243,7 +245,7 @@ fn noop_throughput_benchmark(c: &mut Criterion) { let mut data = Vec::with_capacity(num_hints * 2); for i in 0..num_hints { - data.push(make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1)); + data.push(make_header(PASSTHROUGH_HINT, 1)); data.push(i as u64); } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 3b4538027..03d148166 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -13,7 +13,10 @@ use std::sync::{Arc, Condvar, Mutex}; use tracing::debug; use zisk_common::io::{StreamProcessor, StreamSink}; use zisk_common::{BuiltInHint, CtrlHint, HintCode, PrecompileHint}; -use ziskos_hints::handlers::{bigint256::*, bls381::*, bn254::*, modexp::*, secp256k1::*}; +use ziskos_hints::handlers::bls381::bls12_381_g1_add_hint; +use ziskos_hints::handlers::bn254::{bn254_g1_add_hint, bn254_g1_mul_hint}; +use ziskos_hints::handlers::secp256k1::secp256k1_ecrecover_hint; +use ziskos_hints::handlers::sha256::sha256_hint; /// Ordered result buffer with drain state. /// @@ -233,13 +236,15 @@ impl HintsProcessor { let hint = PrecompileHint::from_u64_slice(hints, idx, true)?; self.num_hint.fetch_add(1, Ordering::Relaxed); - // Check if custom handler is registered for custom hints - if let HintCode::Custom(code) = hint.hint_code { - if !self.custom_handlers.contains_key(&code) { - return Err(anyhow::anyhow!( - "Unknown custom hint code {:#x}: no handler registered", - code - )); + // Check if custom handler is registered for custom hints (skip pass-through) + if !hint.is_passthrough { + if let HintCode::Custom(code) = hint.hint_code { + if !self.custom_handlers.contains_key(&code) { + return Err(anyhow::anyhow!( + "Unknown custom hint code {:#x}: no handler registered", + code + )); + } } } @@ -310,8 +315,8 @@ impl HintsProcessor { let mut queue = self.state.queue.lock().unwrap(); let seq = self.state.next_seq.fetch_add(1, Ordering::Relaxed); - // Handle HintCode::Noop synchronously - reserve and fill slot in one step - if hint.hint_code == HintCode::BuiltIn(BuiltInHint::Noop) { + // Handle pass-through hints immediately + if hint.is_passthrough { queue.buffer.push_back(Some(Ok(hint.data.clone()))); // Notify immediately while holding the lock to ensure drainer sees the result // Release lock after this block, avoiding duplicate notification @@ -376,7 +381,7 @@ impl HintsProcessor { return; } - // println!("Hint processed {:?}:", hint); + println!("Processing Hint => {:?}:", hint); // Check if we should stop due to error - but still need to fill the slot let result = if state.error_flag.load(Ordering::Acquire) { @@ -560,7 +565,9 @@ impl HintsProcessor { custom_handlers: Arc>, ) -> Result> { match hint.hint_code { - HintCode::BuiltIn(builtin) => Self::dispatch_builtin_hint(builtin, hint.data), + HintCode::BuiltIn(builtin) => { + Self::dispatch_builtin_hint(builtin, hint.data, hint.data_len_bytes) + } HintCode::Custom(code) => custom_handlers .get(&code) .map(|handler| handler(&hint.data)) @@ -570,68 +577,43 @@ impl HintsProcessor { } #[inline] - fn dispatch_builtin_hint(hint: BuiltInHint, data: Vec) -> Result> { + fn dispatch_builtin_hint( + hint: BuiltInHint, + data: Vec, + data_len_bytes: usize, + ) -> Result> { match hint { - // Secp256K1 Hint - BuiltInHint::Secp256K1FnReduce => secp256k1_fn_reduce_hint(&data), - BuiltInHint::Secp256K1FnAdd => secp256k1_fn_add_hint(&data), - BuiltInHint::Secp256K1FnNeg => secp256k1_fn_neg_hint(&data), - BuiltInHint::Secp256K1FnSub => secp256k1_fn_sub_hint(&data), - BuiltInHint::Secp256K1FnMul => secp256k1_fn_mul_hint(&data), - BuiltInHint::Secp256K1FnInv => secp256k1_fn_inv_hint(&data), - // Secp256k1 Field Hint Codes - BuiltInHint::Secp256K1FpReduce => secp256k1_fp_reduce_hint(&data), - BuiltInHint::Secp256K1FpAdd => secp256k1_fp_add_hint(&data), - BuiltInHint::Secp256K1FpNegate => secp256k1_fp_negate_hint(&data), - BuiltInHint::Secp256K1FpMul => secp256k1_fp_mul_hint(&data), - BuiltInHint::Secp256K1FpMulScalar => secp256k1_fp_mul_scalar_hint(&data), - // Secp256k1 Curve Hint Codes - BuiltInHint::Secp256K1ToAffine => secp256k1_to_affine_hint(&data), - BuiltInHint::Secp256K1Decompress => secp256k1_decompress_hint(&data), - BuiltInHint::Secp256K1DoubleScalarMulWithG => { - secp256k1_double_scalar_mul_with_g_hint(&data) + // SHA256 Hint Codes + BuiltInHint::Sha256 => sha256_hint(&data, data_len_bytes), + // BN254 Hint Codes + BuiltInHint::Bn254G1Add => bn254_g1_add_hint(&data), + BuiltInHint::Bn254G1Mul => bn254_g1_mul_hint(&data), + BuiltInHint::Bn254PairingCheck => { + unimplemented!("BN254 Pairing Check hint not implemented") + } + // Secp256k1 Hints + BuiltInHint::Secp256k1EcRecover => secp256k1_ecrecover_hint(&data), + BuiltInHint::Secp256r1VerifySignature => { + unimplemented!("Secp256r1 Verify Signature hint not implemented") + } + // BLS12-381 Hint Codes + BuiltInHint::Bls12_381G1Add => bls12_381_g1_add_hint(&data), + BuiltInHint::Bls12_381G1Msm => unimplemented!("BLS12-381 G1 MSM hint not implemented"), + BuiltInHint::Bls12_381G2Add => unimplemented!("BLS12-381 G2 Add hint not implemented"), + BuiltInHint::Bls12_381G2Msm => unimplemented!("BLS12-381 G2 MSM hint not implemented"), + BuiltInHint::Bls12_381PairingCheck => { + unimplemented!("BLS12-381 Pairing Check hint not implemented") + } + BuiltInHint::Bls12_381FpToG1 => { + unimplemented!("BLS12-381 FP to G1 hint not implemented") } - BuiltInHint::Secp256K1EcdsaVerify => secp256k1_ecdsa_verify_hint(&data), - - // Big Integer Arithmetic Hints - BuiltInHint::RedMod256 => redmod256_hint(&data), - BuiltInHint::AddMod256 => addmod256_hint(&data), - BuiltInHint::MulMod256 => mulmod256_hint(&data), - BuiltInHint::DivRem256 => divrem256_hint(&data), - BuiltInHint::WPow256 => wpow256_hint(&data), - BuiltInHint::OMul256 => omul256_hint(&data), - BuiltInHint::WMul256 => wmul256_hint(&data), - - // Modular Exponentiation Hint - BuiltInHint::ModExp => modexp_hint(&data), - - // BN254 hints - BuiltInHint::Bn254IsOnCurve => bn254_is_on_curve_hint(&data), - BuiltInHint::Bn254ToAffine => bn254_to_affine_hint(&data), - BuiltInHint::Bn254Add => bn254_add_hint(&data), - BuiltInHint::Bn254Mul => bn254_mul_hint(&data), - BuiltInHint::Bn254ToAffineTwist => bn254_to_affine_twist_hint(&data), - BuiltInHint::Bn254IsOnCurveTwist => bn254_is_on_curve_twist_hint(&data), - BuiltInHint::Bn254IsOnSubgroupTwist => bn254_is_on_subgroup_twist_hint(&data), - BuiltInHint::Bn254PairingBatch => bn254_pairing_batch_hint(&data), - - // BLS12-381 hints - BuiltInHint::Bls12_381MulFp12 => bls12_381_mul_fp12_hint(&data), - BuiltInHint::Bls12_381Decompress => bls12_381_decompress_hint(&data), - BuiltInHint::Bls12_381IsOnCurve => bls12_381_is_on_curve_hint(&data), - BuiltInHint::Bls12_381IsOnSubgroup => bls12_381_is_on_subgroup_hint(&data), - BuiltInHint::Bls12_381Add => bls12_381_add_hint(&data), - BuiltInHint::Bls12_381ScalarMul => bls12_381_scalar_mul_hint(&data), - BuiltInHint::Bls12_381DecompressTwist => bls12_381_decompress_twist_hint(&data), - BuiltInHint::Bls12_381IsOnCurveTwist => bls12_381_is_on_curve_twist_hint(&data), - BuiltInHint::Bls12_381IsOnSubgroupTwist => bls12_381_is_on_subgroup_twist_hint(&data), - BuiltInHint::Bls12_381AddTwist => bls12_381_add_twist_hint(&data), - BuiltInHint::Bls12_381ScalarMulTwist => bls12_381_scalar_mul_twist_hint(&data), - BuiltInHint::Bls12_381MillerLoop => bls12_381_miller_loop_hint(&data), - BuiltInHint::Bls12_381FinalExp => bls12_381_final_exp_hint(&data), - - // Control codes and Noop are handled before dispatch - _ => Err(anyhow::anyhow!("Unexpected builtin hint: {:?}", hint)), + BuiltInHint::Bls12_381Fp2ToG2 => { + unimplemented!("BLS12-381 FP2 to G2 hint not implemented") + } + // Modular Exponentiation Hint Codes + BuiltInHint::ModExp => unimplemented!("Modular Exponentiation hint not implemented"), + // KZG Hint Codes + BuiltInHint::VerifyKzgProof => unimplemented!("KZG Verify Proof hint not implemented"), } } } @@ -679,16 +661,27 @@ mod tests { make_header(ctrl, length) } + // Pass-through hint code for testing (bit 31 set = pass-through) + // Use high value (0x7FFF_xxxx range) to avoid conflicting with any built-in hint codes + const TEST_PASSTHROUGH_HINT: u32 = 0x8000_0000 | 0x7FFF_0000; + fn processor() -> HintsProcessor { - HintsProcessor::builder(NullHints).num_threads(2).build().unwrap() + HintsProcessor::builder(NullHints) + .num_threads(2) + .custom_hint(0x7FFF_0000, |data| { + // This should never be called for pass-through hints + // but we register it to avoid "no handler" errors + Ok(data.to_vec()) + }) + .build() + .unwrap() } // Positive tests #[test] fn test_single_result_hint_non_blocking() { let p = processor(); - let data = - vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 2), 0x111, 0x222]; + let data = vec![make_header(TEST_PASSTHROUGH_HINT, 2), 0x111, 0x222]; // Dispatch should succeed and be non-blocking assert!(p.process_hints(&data, false).is_ok()); @@ -705,11 +698,11 @@ mod tests { fn test_multiple_hints_ordered_output() { let p = processor(); let data = vec![ - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), + make_header(TEST_PASSTHROUGH_HINT, 1), 0x111, - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), + make_header(TEST_PASSTHROUGH_HINT, 1), 0x222, - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), + make_header(TEST_PASSTHROUGH_HINT, 1), 0x333, ]; assert!(p.process_hints(&data, false).is_ok()); @@ -724,8 +717,8 @@ mod tests { #[test] fn test_multiple_calls_global_sequence() { let p = processor(); - let data1 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0xAAA]; - let data2 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0xBBB]; + let data1 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0xAAA]; + let data2 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0xBBB]; assert!(p.process_hints(&data1, false).is_ok()); assert!(p.process_hints(&data2, false).is_ok()); @@ -765,11 +758,7 @@ mod tests { fn test_error_stops_wait() { let p = processor(); // First valid, then invalid type - let data = vec![ - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), - 0x111, - make_header(999, 0), - ]; + let data = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x111, make_header(999, 0)]; // Should error immediately when encountering invalid hint type let result = p.process_hints(&data, false); @@ -792,7 +781,7 @@ mod tests { assert!(!p.state.error_flag.load(Ordering::Acquire)); // Should be able to process new hints after reset - let good = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42]; + let good = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x42]; assert!(p.process_hints(&good, false).is_ok()); assert!(p.wait_for_completion().is_ok()); @@ -806,7 +795,7 @@ mod tests { let p = processor(); // First batch increments sequence - let batch1 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x01]; + let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x01]; p.process_hints(&batch1, false).unwrap(); p.wait_for_completion().unwrap(); @@ -828,7 +817,7 @@ mod tests { } // Process new batch - let batch2 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x02]; + let batch2 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x02]; p.process_hints(&batch2, false).unwrap(); let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::End).to_u32(), 0)]; @@ -845,9 +834,9 @@ mod tests { // Dispatch hints let data = vec![ - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), + make_header(TEST_PASSTHROUGH_HINT, 1), 0x10, - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), + make_header(TEST_PASSTHROUGH_HINT, 1), 0x20, ]; p.process_hints(&data, false).unwrap(); @@ -899,7 +888,7 @@ mod tests { // CTRL_START not at position 0 should fail let data = vec![ - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), + make_header(TEST_PASSTHROUGH_HINT, 1), 0x42, make_ctrl_header(HintCode::Ctrl(CtrlHint::Start).to_u32(), 0), ]; @@ -914,7 +903,7 @@ mod tests { let p = processor(); // First batch is ok - let batch1 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x01]; + let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x01]; p.process_hints(&batch1, false).unwrap(); // CTRL_START in non-first batch should fail @@ -931,7 +920,7 @@ mod tests { // CTRL_END not at end should fail let data = vec![ make_ctrl_header(HintCode::Ctrl(CtrlHint::End).to_u32(), 0), - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), + make_header(TEST_PASSTHROUGH_HINT, 1), 0x42, ]; @@ -961,10 +950,10 @@ mod tests { // Send some data let data = vec![ - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 2), + make_header(TEST_PASSTHROUGH_HINT, 2), 0xAAA, 0xBBB, - make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), + make_header(TEST_PASSTHROUGH_HINT, 1), 0xCCC, ]; @@ -1002,7 +991,7 @@ mod tests { let p = HintsProcessor::builder(sink).num_threads(2).build().unwrap(); // First batch succeeds - let data1 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x01]; + let data1 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x01]; assert!(p.process_hints(&data1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); @@ -1010,7 +999,7 @@ mod tests { should_fail.store(true, Ordering::Release); // Second batch should trigger sink error - let data2 = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x02]; + let data2 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x02]; assert!(p.process_hints(&data2, false).is_ok()); // Wait should detect the error from drainer thread @@ -1035,7 +1024,7 @@ mod tests { // Custom threads let p4 = HintsProcessor::builder(NullHints).num_threads(4).build().unwrap(); - let data = vec![make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1), 0x42]; + let data = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x42]; assert!(p4.process_hints(&data, false).is_ok()); assert!(p4.wait_for_completion().is_ok()); @@ -1057,7 +1046,7 @@ mod tests { let mut data = Vec::with_capacity(NUM_HINTS * 2); for i in 0..NUM_HINTS { - data.push(make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1)); + data.push(make_header(TEST_PASSTHROUGH_HINT, 1)); data.push(i as u64); } @@ -1094,7 +1083,7 @@ mod tests { for batch_id in 0..NUM_BATCHES { let mut data = Vec::with_capacity(HINTS_PER_BATCH * 2); for i in 0..HINTS_PER_BATCH { - data.push(make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1)); + data.push(make_header(TEST_PASSTHROUGH_HINT, 1)); data.push((batch_id * HINTS_PER_BATCH + i) as u64); } p.process_hints(&data, false).unwrap(); @@ -1137,7 +1126,7 @@ mod tests { // Process batch let mut data = Vec::with_capacity(HINTS_PER_ITER * 2); for i in 0..HINTS_PER_ITER { - data.push(make_header(HintCode::BuiltIn(BuiltInHint::Noop).to_u32(), 1)); + data.push(make_header(TEST_PASSTHROUGH_HINT, 1)); data.push(i as u64); } p.process_hints(&data, false).unwrap(); @@ -1187,10 +1176,10 @@ mod tests { let received = Arc::new(Mutex::new(Vec::new())); let sink = RecordingSink { received: Arc::clone(&received) }; - // Custom hint codes - const FAST_HINT: u32 = 0x100; // Processes instantly - const SLOW_HINT: u32 = 0x101; // Delays 10ms - const MED_HINT: u32 = 0x102; // Delays 5ms + // Custom hint codes (use high values to avoid conflicts with built-ins) + const FAST_HINT: u32 = 0x7FFF_0000; // Processes instantly + const SLOW_HINT: u32 = 0x7FFF_0001; // Delays 10ms + const MED_HINT: u32 = 0x7FFF_0002; // Delays 5ms let p = HintsProcessor::builder(sink) .num_threads(8) @@ -1260,7 +1249,7 @@ mod tests { let received = Arc::new(Mutex::new(Vec::new())); let sink = RecordingSink { received: Arc::clone(&received) }; - const VARIABLE_HINT: u32 = 0x200; + const VARIABLE_HINT: u32 = 0x7FFF_0100; let p = HintsProcessor::builder(sink) .num_threads(16) diff --git a/ziskos-hints/src/handlers/bls381.rs b/ziskos-hints/src/handlers/bls381.rs index 742a37b58..45dcaeb6c 100644 --- a/ziskos-hints/src/handlers/bls381.rs +++ b/ziskos-hints/src/handlers/bls381.rs @@ -1,247 +1,268 @@ -use crate::{handlers::validate_hint_length, hint_fields, zisklib}; +use crate::{handlers::validate_hint_min_length, hint_fields, zisklib}; use anyhow::Result; -/// Processes an `MUL_FP12_BLS12_381` hint. +/// Processes an `HINT_BLS12_381_G1_ADD` hint. #[inline] -pub fn bls12_381_mul_fp12_hint(data: &[u64]) -> Result> { - hint_fields![A: 72, B: 72]; +pub fn bls12_381_g1_add_hint(data: &[u64]) -> Result> { + hint_fields![A: 96, B: 96]; - validate_hint_length(data, EXPECTED_LEN, "MUL_FP12_BLS12_381")?; + validate_hint_min_length(data, EXPECTED_LEN_U64, "HINT_BLS12_381_G1_ADD")?; - // Safe to unwrap due to prior length validation. - let a: &[u64; A_SIZE] = data[A_OFFSET..A_OFFSET + A_SIZE].try_into().unwrap(); - let b: &[u64; B_SIZE] = data[B_OFFSET..B_OFFSET + B_SIZE].try_into().unwrap(); + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + + let a: &[u8; A_SIZE] = bytes[A_OFFSET..A_OFFSET + A_SIZE].try_into().unwrap(); + let b: &[u8; B_SIZE] = bytes[B_OFFSET..B_OFFSET + B_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - zisklib::mul_fp12_bls12_381(a, b, &mut hints); + let result: &mut [u8; 96] = &mut [0u8; 96]; + unsafe { + zisklib::bls12_381_g1_add_c(result.as_mut_ptr(), a.as_ptr(), b.as_ptr(), &mut hints); + } Ok(hints) } -/// Processes a `DECOMPRESS_BLS12_381` hint. -#[inline] -pub fn bls12_381_decompress_hint(data: &[u64]) -> Result> { - hint_fields![INPUT: 6]; - - validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_BLS12_381")?; - - // Safe to unwrap due to prior length validation. - let input: &[u64; INPUT_SIZE] = - data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); - // let input: [u64; INPUT_SIZE] = [ - // input[3].to_be(), - // input[2].to_be(), - // input[1].to_be(), - // input[0].to_be(), - // input[5].to_be(), - // input[4].to_be(), - // ]; - - // Map a [u64; 6] to a [u8; 48] - let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; +// /// Processes an `MUL_FP12_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_mul_fp12_hint(data: &[u64]) -> Result> { +// hint_fields![A: 72, B: 72]; - let mut hints = Vec::new(); - zisklib::decompress_bls12_381(input, &mut hints).map_err(anyhow::Error::msg)?; +// validate_hint_length(data, EXPECTED_LEN, "MUL_FP12_BLS12_381")?; - Ok(hints) -} +// // Safe to unwrap due to prior length validation. +// let a: &[u64; A_SIZE] = data[A_OFFSET..A_OFFSET + A_SIZE].try_into().unwrap(); +// let b: &[u64; B_SIZE] = data[B_OFFSET..B_OFFSET + B_SIZE].try_into().unwrap(); -/// Processes an `IS_ON_CURVE_BLS12_381` hint. -#[inline] -pub fn bls12_381_is_on_curve_hint(data: &[u64]) -> Result> { - hint_fields![P: 12]; +// let mut hints = Vec::new(); +// zisklib::mul_fp12_bls12_381(a, b, &mut hints); - validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_BLS12_381")?; +// Ok(hints) +// } - // Safe to unwrap due to prior length validation. - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); +// /// Processes a `DECOMPRESS_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_decompress_hint(data: &[u64]) -> Result> { +// hint_fields![INPUT: 6]; - let mut hints = Vec::new(); - zisklib::is_on_curve_bls12_381(p, &mut hints); +// validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_BLS12_381")?; - Ok(hints) -} +// // Safe to unwrap due to prior length validation. +// let input: &[u64; INPUT_SIZE] = +// data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); +// // let input: [u64; INPUT_SIZE] = [ +// // input[3].to_be(), +// // input[2].to_be(), +// // input[1].to_be(), +// // input[0].to_be(), +// // input[5].to_be(), +// // input[4].to_be(), +// // ]; -/// Processes an `IS_ON_SUBGROUP_BLS12_381` hint. -#[inline] -pub fn bls12_381_is_on_subgroup_hint(data: &[u64]) -> Result> { - hint_fields![P: 12]; +// // Map a [u64; 6] to a [u8; 48] +// let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; - validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_BLS12_381")?; +// let mut hints = Vec::new(); +// zisklib::decompress_bls12_381(input, &mut hints).map_err(anyhow::Error::msg)?; - // Safe to unwrap due to prior length validation. - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); +// Ok(hints) +// } - let mut hints = Vec::new(); - zisklib::is_on_subgroup_bls12_381(p, &mut hints); +// /// Processes an `IS_ON_CURVE_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_is_on_curve_hint(data: &[u64]) -> Result> { +// hint_fields![P: 12]; - Ok(hints) -} +// validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_BLS12_381")?; -/// Processes an `ADD_BLS12_381` hint. -#[inline] -pub fn bls12_381_add_hint(data: &[u64]) -> Result> { - hint_fields![P1: 12, P2: 12]; +// // Safe to unwrap due to prior length validation. +// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - validate_hint_length(data, EXPECTED_LEN, "ADD_BLS12_381")?; +// let mut hints = Vec::new(); +// zisklib::is_on_curve_bls12_381(p, &mut hints); - // Safe to unwrap due to prior length validation. - let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); - let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); +// Ok(hints) +// } - let mut hints = Vec::new(); - zisklib::add_bls12_381(p1, p2, &mut hints); +// /// Processes an `IS_ON_SUBGROUP_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_is_on_subgroup_hint(data: &[u64]) -> Result> { +// hint_fields![P: 12]; - Ok(hints) -} +// validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_BLS12_381")?; -/// Processes a `SCALAR_MUL_BLS12_381` hint. -#[inline] -pub fn bls12_381_scalar_mul_hint(data: &[u64]) -> Result> { - hint_fields![P: 12, K: 6]; +// // Safe to unwrap due to prior length validation. +// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_BLS12_381")?; +// let mut hints = Vec::new(); +// zisklib::is_on_subgroup_bls12_381(p, &mut hints); - // Safe to unwrap due to prior length validation. - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); +// Ok(hints) +// } - let mut hints = Vec::new(); - zisklib::scalar_mul_bls12_381(p, k, &mut hints); +// /// Processes an `ADD_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_add_hint(data: &[u64]) -> Result> { +// hint_fields![P1: 12, P2: 12]; - Ok(hints) -} +// validate_hint_length(data, EXPECTED_LEN, "ADD_BLS12_381")?; -/// Processes a `DECOMPRESS_TWIST_BLS12_381` hint. -#[inline] -pub fn bls12_381_decompress_twist_hint(data: &[u64]) -> Result> { - hint_fields![INPUT: 12]; - - validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_TWIST_BLS12_381")?; - - // Safe to unwrap due to prior length validation. - let input: &[u64; INPUT_SIZE] = - data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); - // let input: [u64; INPUT_SIZE] = [ - // input[3].to_be(), - // input[2].to_be(), - // input[1].to_be(), - // input[0].to_be(), - // input[7].to_be(), - // input[6].to_be(), - // input[5].to_be(), - // input[4].to_be(), - // input[11].to_be(), - // input[10].to_be(), - // input[9].to_be(), - // input[8].to_be(), - // ]; - - // Map a [u64; 6] to a [u8; 48] - let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; +// // Safe to unwrap due to prior length validation. +// let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); +// let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); - let mut hints = Vec::new(); - zisklib::decompress_twist_bls12_381(input, &mut hints).map_err(anyhow::Error::msg)?; +// let mut hints = Vec::new(); +// zisklib::add_bls12_381(p1, p2, &mut hints); - Ok(hints) -} +// Ok(hints) +// } -/// Processes an `IS_ON_CURVE_TWIST_BLS12_381` hint. -#[inline] -pub fn bls12_381_is_on_curve_twist_hint(data: &[u64]) -> Result> { - hint_fields![P: 24]; +// /// Processes a `SCALAR_MUL_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_scalar_mul_hint(data: &[u64]) -> Result> { +// hint_fields![P: 12, K: 6]; - validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_TWIST_BLS12_381")?; +// validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_BLS12_381")?; - // Safe to unwrap due to prior length validation. - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); +// // Safe to unwrap due to prior length validation. +// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); +// let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); - let mut hints = Vec::new(); - zisklib::is_on_curve_twist_bls12_381(p, &mut hints); +// let mut hints = Vec::new(); +// zisklib::scalar_mul_bls12_381(p, k, &mut hints); - Ok(hints) -} +// Ok(hints) +// } -/// Processes an `IS_ON_SUBGROUP_TWIST_BLS12_381` hint. -#[inline] -pub fn bls12_381_is_on_subgroup_twist_hint(data: &[u64]) -> Result> { - hint_fields![P: 24]; +// /// Processes a `DECOMPRESS_TWIST_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_decompress_twist_hint(data: &[u64]) -> Result> { +// hint_fields![INPUT: 12]; - validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_TWIST_BLS12_381")?; +// validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_TWIST_BLS12_381")?; - // Safe to unwrap due to prior length validation. - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); +// // Safe to unwrap due to prior length validation. +// let input: &[u64; INPUT_SIZE] = +// data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); +// // let input: [u64; INPUT_SIZE] = [ +// // input[3].to_be(), +// // input[2].to_be(), +// // input[1].to_be(), +// // input[0].to_be(), +// // input[7].to_be(), +// // input[6].to_be(), +// // input[5].to_be(), +// // input[4].to_be(), +// // input[11].to_be(), +// // input[10].to_be(), +// // input[9].to_be(), +// // input[8].to_be(), +// // ]; - let mut hints = Vec::new(); - zisklib::is_on_subgroup_twist_bls12_381(p, &mut hints); +// // Map a [u64; 6] to a [u8; 48] +// let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; - Ok(hints) -} +// let mut hints = Vec::new(); +// zisklib::decompress_twist_bls12_381(input, &mut hints).map_err(anyhow::Error::msg)?; -/// Processes an `ADD_TWIST_BLS12_381` hint. -#[inline] -pub fn bls12_381_add_twist_hint(data: &[u64]) -> Result> { - hint_fields![P1: 24, P2: 24]; +// Ok(hints) +// } - validate_hint_length(data, EXPECTED_LEN, "ADD_TWIST_BLS12_381")?; +// /// Processes an `IS_ON_CURVE_TWIST_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_is_on_curve_twist_hint(data: &[u64]) -> Result> { +// hint_fields![P: 24]; - // Safe to unwrap due to prior length validation. - let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); - let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); +// validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_TWIST_BLS12_381")?; - let mut hints = Vec::new(); - zisklib::add_twist_bls12_381(p1, p2, &mut hints); +// // Safe to unwrap due to prior length validation. +// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - Ok(hints) -} +// let mut hints = Vec::new(); +// zisklib::is_on_curve_twist_bls12_381(p, &mut hints); -/// Processes a `SCALAR_MUL_TWIST_BLS12_381` hint. -#[inline] -pub fn bls12_381_scalar_mul_twist_hint(data: &[u64]) -> Result> { - hint_fields![P: 24, K: 6]; +// Ok(hints) +// } - validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_TWIST_BLS12_381")?; +// /// Processes an `IS_ON_SUBGROUP_TWIST_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_is_on_subgroup_twist_hint(data: &[u64]) -> Result> { +// hint_fields![P: 24]; - // Safe to unwrap due to prior length validation. - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); +// validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_TWIST_BLS12_381")?; - let mut hints = Vec::new(); - zisklib::scalar_mul_twist_bls12_381(p, k, &mut hints); +// // Safe to unwrap due to prior length validation. +// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - Ok(hints) -} +// let mut hints = Vec::new(); +// zisklib::is_on_subgroup_twist_bls12_381(p, &mut hints); -/// Processes a `MILLER_LOOP_BLS12_381` hint. -#[inline] -pub fn bls12_381_miller_loop_hint(data: &[u64]) -> Result> { - hint_fields![P: 12, Q: 24]; +// Ok(hints) +// } - validate_hint_length(data, EXPECTED_LEN, "MILLER_LOOP_BLS12_381")?; +// /// Processes an `ADD_TWIST_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_add_twist_hint(data: &[u64]) -> Result> { +// hint_fields![P1: 24, P2: 24]; - // Safe to unwrap due to prior length validation. - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let q: &[u64; Q_SIZE] = data[Q_OFFSET..Q_OFFSET + Q_SIZE].try_into().unwrap(); +// validate_hint_length(data, EXPECTED_LEN, "ADD_TWIST_BLS12_381")?; - let mut hints = Vec::new(); - zisklib::miller_loop_bls12_381(p, q, &mut hints); +// // Safe to unwrap due to prior length validation. +// let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); +// let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); - Ok(hints) -} +// let mut hints = Vec::new(); +// zisklib::add_twist_bls12_381(p1, p2, &mut hints); -/// Processes a `FINAL_EXP_BLS12_381` hint. -#[inline] -pub fn bls12_381_final_exp_hint(data: &[u64]) -> Result> { - hint_fields![F: 72]; +// Ok(hints) +// } - validate_hint_length(data, EXPECTED_LEN, "FINAL_EXP_BLS12_381")?; +// /// Processes a `SCALAR_MUL_TWIST_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_scalar_mul_twist_hint(data: &[u64]) -> Result> { +// hint_fields![P: 24, K: 6]; - // Safe to unwrap due to prior length validation. - let f: &[u64; F_SIZE] = data[F_OFFSET..F_OFFSET + F_SIZE].try_into().unwrap(); +// validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_TWIST_BLS12_381")?; - let mut hints = Vec::new(); - zisklib::final_exp_bls12_381(f, &mut hints); +// // Safe to unwrap due to prior length validation. +// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); +// let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); - Ok(hints) -} +// let mut hints = Vec::new(); +// zisklib::scalar_mul_twist_bls12_381(p, k, &mut hints); + +// Ok(hints) +// } + +// /// Processes a `MILLER_LOOP_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_miller_loop_hint(data: &[u64]) -> Result> { +// hint_fields![P: 12, Q: 24]; + +// validate_hint_length(data, EXPECTED_LEN, "MILLER_LOOP_BLS12_381")?; + +// // Safe to unwrap due to prior length validation. +// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); +// let q: &[u64; Q_SIZE] = data[Q_OFFSET..Q_OFFSET + Q_SIZE].try_into().unwrap(); + +// let mut hints = Vec::new(); +// zisklib::miller_loop_bls12_381(p, q, &mut hints); + +// Ok(hints) +// } + +// /// Processes a `FINAL_EXP_BLS12_381` hint. +// #[inline] +// pub fn bls12_381_final_exp_hint(data: &[u64]) -> Result> { +// hint_fields![F: 72]; + +// validate_hint_length(data, EXPECTED_LEN, "FINAL_EXP_BLS12_381")?; + +// // Safe to unwrap due to prior length validation. +// let f: &[u64; F_SIZE] = data[F_OFFSET..F_OFFSET + F_SIZE].try_into().unwrap(); + +// let mut hints = Vec::new(); +// zisklib::final_exp_bls12_381(f, &mut hints); + +// Ok(hints) +// } diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 104d35ca1..ef722d2ed 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -1,7 +1,54 @@ -use crate::{handlers::validate_hint_length, hint_fields, zisklib}; +use crate::{ + handlers::{validate_hint_length, validate_hint_min_length}, + hint_fields, zisklib, +}; use anyhow::Result; +/// Processes an `HINT_BN254_G1_ADD` hint. +#[inline] +pub fn bn254_g1_add_hint(data: &[u64]) -> Result> { + hint_fields![P1: 64, P2: 64]; + + validate_hint_min_length(data, EXPECTED_LEN_U64, "HINT_BN254_G1_ADD")?; + + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + + let p1: &[u8; P1_SIZE] = bytes[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); + let p2: &[u8; P2_SIZE] = bytes[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + let result: &mut [u8; 64] = &mut [0u8; 64]; + unsafe { + zisklib::bn254_g1_add_c(p1.as_ptr(), p2.as_ptr(), result.as_mut_ptr(), &mut hints); + } + + Ok(hints) +} + +/// Processes an `HINT_BN254_G1_MUL` hint. +#[inline] +pub fn bn254_g1_mul_hint(data: &[u64]) -> Result> { + hint_fields![POINT: 64, SCALAR: 32]; + + validate_hint_min_length(data, EXPECTED_LEN_U64, "HINT_BN254_G1_MUL")?; + + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + + let point: &[u8; POINT_SIZE] = + bytes[POINT_OFFSET..POINT_OFFSET + POINT_SIZE].try_into().unwrap(); + let scalar: &[u8; SCALAR_SIZE] = + bytes[SCALAR_OFFSET..SCALAR_OFFSET + SCALAR_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + let result: &mut [u8; 64] = &mut [0u8; 64]; + unsafe { + zisklib::bn254_g1_mul_c(point.as_ptr(), scalar.as_ptr(), result.as_mut_ptr(), &mut hints); + } + + Ok(hints) +} + /// Processes an `IS_ON_CURVE_BN254`` hint. #[inline] pub fn bn254_is_on_curve_hint(data: &[u64]) -> Result> { diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 50ec992f5..00bc855b0 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -3,6 +3,7 @@ pub mod bls381; pub mod bn254; pub mod modexp; pub mod secp256k1; +pub mod sha256; /// Macro to generate size, offset, and expected length constants for hint data fields. /// @@ -26,7 +27,10 @@ macro_rules! hint_fields { hint_fields!(@offsets 0, $($name: $size),+); + #[allow(dead_code)] const EXPECTED_LEN: usize = hint_fields!(@sum $($size),+); + #[allow(dead_code)] + const EXPECTED_LEN_U64: usize = (EXPECTED_LEN + 7) / 8; }; (@offsets $offset:expr, $name:ident: $size:expr) => { @@ -75,7 +79,7 @@ fn read_field<'a>(data: &'a [u64], pos: &mut usize) -> anyhow::Result<&'a [u64]> /// * `Ok(())` - If the length is correct /// * `Err(anyhow::Error)` - If the length is incorrect #[inline] -fn validate_hint_length(data: &[u64], expected_len: usize, hint_name: &str) -> anyhow::Result<()> { +fn validate_hint_length(data: &[T], expected_len: usize, hint_name: &str) -> anyhow::Result<()> { if data.len() != expected_len { anyhow::bail!( "Invalid {} hint length: expected {}, got {}", @@ -86,3 +90,28 @@ fn validate_hint_length(data: &[u64], expected_len: usize, hint_name: &str) -> a } Ok(()) } + +/// Validates that the hint data has at least the minimum required length. +/// +/// # Arguments +/// +/// * `data` - The hint data to validate +/// * `min_len` - The minimum required length +/// * `hint_name` - The name of the hint type for error messages +/// +/// # Returns +/// +/// * `Ok(())` - If the length is sufficient +/// * `Err(anyhow::Error)` - If the length is too short +#[inline] +fn validate_hint_min_length(data: &[T], min_len: usize, hint_name: &str) -> anyhow::Result<()> { + if data.len() < min_len { + anyhow::bail!( + "Invalid {} hint length: expected at least {}, got {}", + hint_name, + min_len, + data.len(), + ); + } + Ok(()) +} diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 4d4830aa0..491962110 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -1,9 +1,40 @@ use crate::handlers::validate_hint_length; +use crate::handlers::validate_hint_min_length; use crate::hint_fields; use crate::zisklib; use anyhow::Result; +/// Processes an `HINT_SECP256K1_ECRECOVER` hint. +#[inline] +pub fn secp256k1_ecrecover_hint(data: &[u64]) -> Result> { + hint_fields![SIG: 64, RECID: 8, MSG: 32]; + + validate_hint_min_length(data, EXPECTED_LEN_U64, "HINT_SECP256K1_ECRECOVER")?; + + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + + let sig: &[u8; SIG_SIZE] = bytes[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); + let recid: &[u8; RECID_SIZE] = + bytes[RECID_OFFSET..RECID_OFFSET + RECID_SIZE].try_into().unwrap(); + let recid: u8 = u64::from_le_bytes(*recid) as u8; + let msg: &[u8; MSG_SIZE] = bytes[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + let result: &mut [u8; 32] = &mut [0u8; 32]; + unsafe { + zisklib::secp256k1_ecrecover_c( + sig.as_ptr(), + recid, + msg.as_ptr(), + result.as_mut_ptr(), + &mut hints, + ); + } + + Ok(hints) +} + // Processes a `SECP256K1_FN_REDUCE`` hint. #[inline] pub fn secp256k1_fn_reduce_hint(data: &[u64]) -> Result> { diff --git a/ziskos-hints/src/handlers/sha256.rs b/ziskos-hints/src/handlers/sha256.rs new file mode 100644 index 000000000..24152b654 --- /dev/null +++ b/ziskos-hints/src/handlers/sha256.rs @@ -0,0 +1,18 @@ +use crate::{handlers::validate_hint_min_length, zisklib}; + +use anyhow::Result; + +/// Processes an `HINT_SHA256` hint. +#[inline] +pub fn sha256_hint(data: &[u64], data_len_bytes: usize) -> Result> { + let data_len_u64 = (data_len_bytes + 7) / 8; // convert bytes to u64 length + + validate_hint_min_length(data, data_len_u64, "HINT_SHA256")?; + + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data_len_bytes) }; + + let mut hints = Vec::new(); + zisklib::sha256(bytes, &mut hints); + + Ok(hints) +} diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs index dc8941891..38baff414 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs @@ -279,7 +279,13 @@ pub unsafe extern "C" fn modexp_bytes_c( } // Call the u64 version - let result_u64 = modexp_u64(&base_u64, &exp_u64, &modulus_u64, #[cfg(feature = "hints")] hints); + let result_u64 = modexp_u64( + &base_u64, + &exp_u64, + &modulus_u64, + #[cfg(feature = "hints")] + hints, + ); // Convert result back to big-endian bytes with proper length let result = std::slice::from_raw_parts_mut(result_ptr, modulus_len); diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs index 67cccb8a5..4d80f2bfc 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs @@ -42,13 +42,21 @@ pub unsafe extern "C" fn verify_kzg_proof_c( let proof_bytes: &[u8; 48] = &*(proof as *const [u8; 48]); // Parse the commitment (G1 point, compressed) - let commitment_point = match decompress_bls12_381(commitment_bytes) { + let commitment_point = match decompress_bls12_381( + commitment_bytes, + #[cfg(feature = "hints")] + hints, + ) { Ok((point, _is_inf)) => point, Err(_) => return 2, // Invalid commitment }; // Parse the proof (G1 point, compressed) - let proof_point = match decompress_bls12_381(proof_bytes) { + let proof_point = match decompress_bls12_381( + proof_bytes, + #[cfg(feature = "hints")] + hints, + ) { Ok((point, _is_inf)) => point, Err(_) => return 2, // Invalid proof }; @@ -65,33 +73,65 @@ pub unsafe extern "C" fn verify_kzg_proof_c( }; // Get the trusted setup G2 point [τ]₂ - let tau_g2 = get_trusted_setup_g2(); + let tau_g2 = get_trusted_setup_g2( + #[cfg(feature = "hints")] + hints, + ); // Get generators let g1 = G1_GENERATOR; let g2 = G2_GENERATOR; // Compute c_minus_y = commitment - [y]G₁ - let y_g1 = scalar_mul_bls12_381(&g1, &y_scalar); - let c_minus_y = sub_bls12_381(&commitment_point, &y_g1); + let y_g1 = scalar_mul_bls12_381( + &g1, + &y_scalar, + #[cfg(feature = "hints")] + hints, + ); + let c_minus_y = sub_bls12_381( + &commitment_point, + &y_g1, + #[cfg(feature = "hints")] + hints, + ); // Compute t_minus_z = [τ]₂ - [z]G₂ - let z_g2 = scalar_mul_twist_bls12_381(&g2, &z_scalar); - let t_minus_z = sub_twist_bls12_381(&tau_g2, &z_g2); + let z_g2 = scalar_mul_twist_bls12_381( + &g2, + &z_scalar, + #[cfg(feature = "hints")] + hints, + ); + let t_minus_z = sub_twist_bls12_381( + &tau_g2, + &z_g2, + #[cfg(feature = "hints")] + hints, + ); // The verification equation is: // e(commitment - [y]G₁, G₂) = e(proof, [τ]₂ - [z]G₂) // // Which is equivalent to checking: // e(commitment - [y]G₁, -G₂) · e(proof, [τ]₂ - [z]G₂) = 1 - let neg_g2 = neg_twist_bls12_381(&g2); + let neg_g2 = neg_twist_bls12_381( + &g2, + #[cfg(feature = "hints")] + hints, + ); // Batch pairing check let g1_points = [c_minus_y, proof_point]; let g2_points = [neg_g2, t_minus_z]; // Check if the pairing result equals 1 - if is_one(&pairing_batch_bls12_381(&g1_points, &g2_points)) { + if is_one(&pairing_batch_bls12_381( + &g1_points, + &g2_points, + #[cfg(feature = "hints")] + hints, + )) { 1 // Valid proof } else { 0 // Invalid proof @@ -118,8 +158,12 @@ fn read_scalar_canonical(bytes: &[u8; 32]) -> Option<[u64; 4]> { } /// Get the trusted setup G2 point `[τ]₂` -fn get_trusted_setup_g2() -> [u64; 24] { - decompress_twist_bls12_381(&TRUSTED_SETUP_TAU_G2_COMPRESSED) - .expect("Failed to decompress trusted setup G2") - .0 +fn get_trusted_setup_g2(#[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 24] { + decompress_twist_bls12_381( + &TRUSTED_SETUP_TAU_G2_COMPRESSED, + #[cfg(feature = "hints")] + hints, + ) + .expect("Failed to decompress trusted setup G2") + .0 } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs index e3bfd43e6..2ef20f30c 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs @@ -197,5 +197,10 @@ pub unsafe extern "C" fn bls12_381_pairing_check_c( } // Compute batch pairing and check if result is 1 - is_one(&pairing_batch_bls12_381(&g1_points, &g2_points, #[cfg(feature = "hints")] hints)) + is_one(&pairing_batch_bls12_381( + &g1_points, + &g2_points, + #[cfg(feature = "hints")] + hints, + )) } diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index d9da4a618..5386a751d 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -10,7 +10,7 @@ const KECCAK256_RATE: usize = 136; /// - Capacity: 512 bits (64 bytes) /// - Output: 256 bits (32 bytes) /// - Padding: Keccak padding (0x01...0x80) -pub fn keccak256(input: &[u8]) -> [u8; 32] { +pub fn keccak256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u8; 32] { let mut state = [0u64; 25]; let input_len = input.len(); @@ -20,7 +20,11 @@ pub fn keccak256(input: &[u8]) -> [u8; 32] { // XOR block into state xor_block_into_state(&mut state, &input[offset..offset + KECCAK256_RATE]); // Apply Keccak-f permutation - syscall_keccak_f(&mut state); + syscall_keccak_f( + &mut state, + #[cfg(feature = "hints")] + hints, + ); offset += KECCAK256_RATE; } @@ -40,7 +44,11 @@ pub fn keccak256(input: &[u8]) -> [u8; 32] { xor_block_into_state(&mut state, &final_block); // Final permutation - syscall_keccak_f(&mut state); + syscall_keccak_f( + &mut state, + #[cfg(feature = "hints")] + hints, + ); // Squeeze phase: extract first 32 bytes (256 bits) from state let mut result = [0u8; 32]; @@ -76,7 +84,11 @@ pub unsafe extern "C" fn keccak256_c( #[cfg(feature = "hints")] hints: &mut Vec, ) { let input_slice = core::slice::from_raw_parts(input, input_len); - let hash = keccak256(input_slice); + let hash = keccak256( + input_slice, + #[cfg(feature = "hints")] + hints, + ); let output_slice = core::slice::from_raw_parts_mut(output, 32); output_slice.copy_from_slice(&hash); } @@ -94,5 +106,11 @@ pub unsafe extern "C" fn native_keccak256( output: *mut u8, #[cfg(feature = "hints")] hints: &mut Vec, ) { - keccak256_c(bytes, len, output); + keccak256_c( + bytes, + len, + output, + #[cfg(feature = "hints")] + hints, + ); } diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs index ce64fbcd6..0f445c4a1 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs @@ -108,6 +108,7 @@ pub fn secp256k1_ecrecover_point( s: &[u64; 4], z: &[u64; 4], recid: u8, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> Option<[u64; 8]> { // Validate r and s if !is_valid_scalar(r) || !is_valid_scalar(s) { @@ -126,22 +127,52 @@ pub fn secp256k1_ecrecover_point( // Recover the y-coordinate from x // y² = x³ + 7 let y_is_odd = (recid & 1) == 1; - let (rx, ry) = secp256k1_decompress(&x, y_is_odd).ok()?; + let (rx, ry) = secp256k1_decompress( + &x, + y_is_odd, + #[cfg(feature = "hints")] + hints, + ) + .ok()?; let r_point = [rx[0], rx[1], rx[2], rx[3], ry[0], ry[1], ry[2], ry[3]]; // Compute r_inv = r⁻¹ (mod N) - let r_inv = secp256k1_fn_inv(r); + let r_inv = secp256k1_fn_inv( + r, + #[cfg(feature = "hints")] + hints, + ); // Compute u1 = -z * r_inv (mod N) - let neg_z = secp256k1_fn_neg(z); - let u1 = secp256k1_fn_mul(&neg_z, &r_inv); + let neg_z = secp256k1_fn_neg( + z, + #[cfg(feature = "hints")] + hints, + ); + let u1 = secp256k1_fn_mul( + &neg_z, + &r_inv, + #[cfg(feature = "hints")] + hints, + ); // Compute u2 = s * r_inv (mod N) - let u2 = secp256k1_fn_mul(s, &r_inv); + let u2 = secp256k1_fn_mul( + s, + &r_inv, + #[cfg(feature = "hints")] + hints, + ); // Compute PK = u1*G + u2*R - let pk = secp256k1_double_scalar_mul_with_g(&u1, &u2, &r_point)?; + let pk = secp256k1_double_scalar_mul_with_g( + &u1, + &u2, + &r_point, + #[cfg(feature = "hints")] + hints, + )?; Some(pk) } @@ -159,7 +190,12 @@ pub fn secp256k1_ecrecover_point( /// # Returns /// * 32 bytes where the first 12 bytes are 0 and the last 20 bytes are the Ethereum address /// * Returns all zeros if recovery fails -pub fn secp256k1_ecrecover(sig: &[u8; 64], recid: u8, msg: &[u8; 32]) -> Option<[u8; 32]> { +pub fn secp256k1_ecrecover( + sig: &[u8; 64], + recid: u8, + msg: &[u8; 32], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Option<[u8; 32]> { // Parse r and s from signature let r_bytes: [u8; 32] = sig[0..32].try_into().unwrap(); let s_bytes: [u8; 32] = sig[32..64].try_into().unwrap(); @@ -169,7 +205,14 @@ pub fn secp256k1_ecrecover(sig: &[u8; 64], recid: u8, msg: &[u8; 32]) -> Option< let z = bytes_be_to_u64_le(msg); // Recover the public key point - let pk = secp256k1_ecrecover_point(&r, &s, &z, recid)?; + let pk = secp256k1_ecrecover_point( + &r, + &s, + &z, + recid, + #[cfg(feature = "hints")] + hints, + )?; // Convert public key to uncompressed format (65 bytes: 0x04 || x || y) // But for keccak hashing, we only use x || y (64 bytes) @@ -220,7 +263,13 @@ pub unsafe extern "C" fn secp256k1_ecrecover_c( let sig_bytes: &[u8; 64] = &*(sig as *const [u8; 64]); let msg_bytes: &[u8; 32] = &*(msg as *const [u8; 32]); - match secp256k1_ecrecover(sig_bytes, recid, msg_bytes) { + match secp256k1_ecrecover( + sig_bytes, + recid, + msg_bytes, + #[cfg(feature = "hints")] + hints, + ) { Some(result) => { let output_slice = core::slice::from_raw_parts_mut(output, 32); output_slice.copy_from_slice(&result); diff --git a/ziskos/entrypoint/src/zisklib/lib/sha256.rs b/ziskos/entrypoint/src/zisklib/lib/sha256.rs index 0ec7ef3bc..7571f2b6c 100644 --- a/ziskos/entrypoint/src/zisklib/lib/sha256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/sha256.rs @@ -7,14 +7,22 @@ const SHA256_INIT: [u32; 8] = [ /// Compress a single 64-byte block into the state #[inline] -fn compress_block(state: &mut [u32; 8], block: &[u8; 64]) { +fn compress_block( + state: &mut [u32; 8], + block: &[u8; 64], + #[cfg(feature = "hints")] hints: &mut Vec, +) { let state_64: &mut [u64; 4] = unsafe { &mut *(state.as_mut_ptr() as *mut [u64; 4]) }; let input_u64: &[u64; 8] = unsafe { &*(block.as_ptr() as *const [u64; 8]) }; let mut sha256_params = SyscallSha256Params { state: state_64, input: input_u64 }; - syscall_sha256_f(&mut sha256_params); + syscall_sha256_f( + &mut sha256_params, + #[cfg(feature = "hints")] + hints, + ); } -pub fn sha256(input: &[u8]) -> [u8; 32] { +pub fn sha256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u8; 32] { let mut state = SHA256_INIT; let input_len = input.len(); @@ -22,7 +30,12 @@ pub fn sha256(input: &[u8]) -> [u8; 32] { let mut offset = 0; while offset + 64 <= input_len { let block: &[u8; 64] = input[offset..offset + 64].try_into().unwrap(); - compress_block(&mut state, block); + compress_block( + &mut state, + block, + #[cfg(feature = "hints")] + hints, + ); offset += 64; } @@ -42,16 +55,31 @@ pub fn sha256(input: &[u8]) -> [u8; 32] { if remaining + 9 > 64 { // Need two blocks: process first block, then second with length - compress_block(&mut state, &final_block); + compress_block( + &mut state, + &final_block, + #[cfg(feature = "hints")] + hints, + ); // Second block: all zeros except length at the end final_block = [0u8; 64]; final_block[56..64].copy_from_slice(&bit_len.to_be_bytes()); - compress_block(&mut state, &final_block); + compress_block( + &mut state, + &final_block, + #[cfg(feature = "hints")] + hints, + ); } else { // Single block: append length at the end final_block[56..64].copy_from_slice(&bit_len.to_be_bytes()); - compress_block(&mut state, &final_block); + compress_block( + &mut state, + &final_block, + #[cfg(feature = "hints")] + hints, + ); } // Convert state to big-endian bytes @@ -77,7 +105,11 @@ pub unsafe extern "C" fn sha256_c( #[cfg(feature = "hints")] hints: &mut Vec, ) { let input_slice = core::slice::from_raw_parts(input, input_len); - let hash = sha256(input_slice); + let hash = sha256( + input_slice, + #[cfg(feature = "hints")] + hints, + ); let output_slice = core::slice::from_raw_parts_mut(output, 32); output_slice.copy_from_slice(&hash); } From 72a386d66e9bbf41c71c2f2c804578944d4fc27b Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 22 Jan 2026 12:12:07 +0100 Subject: [PATCH 307/782] Implement sha256f in zisklib syscall with hints --- ziskos-hints/Cargo.toml | 1 + ziskos/entrypoint/Cargo.toml | 1 + ziskos/entrypoint/src/syscalls/sha256f.rs | 25 ++++++++++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index c61082fe5..e6002a67d 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -32,6 +32,7 @@ tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } bincode = "2.0" paste = "1.0" +sha2 = { workspace = true } # zisk-os specific dependencies elliptic-curve = "0.13.8" diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index ce78a6589..a486f9e54 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -24,6 +24,7 @@ cfg-if = "1.0" tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } bincode = "2.0" +sha2 = { workspace = true } [features] default = [] diff --git a/ziskos/entrypoint/src/syscalls/sha256f.rs b/ziskos/entrypoint/src/syscalls/sha256f.rs index 391a61a79..81a8b7336 100644 --- a/ziskos/entrypoint/src/syscalls/sha256f.rs +++ b/ziskos/entrypoint/src/syscalls/sha256f.rs @@ -6,6 +6,13 @@ use core::arch::asm; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] use crate::ziskos_syscall; +#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] +use sha2::compress256; + +#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] +#[allow(deprecated)] +use sha2::digest::generic_array::{typenum::U64, GenericArray}; + #[derive(Debug)] #[repr(C)] pub struct SyscallSha256Params<'a> { @@ -35,5 +42,21 @@ pub extern "C" fn syscall_sha256_f( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x805, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + sha256f(params.state, params.input); + + #[cfg(feature = "hints")] + { + hints.extend_from_slice(params.state); + } + } +} + +#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] +#[allow(deprecated)] +fn sha256f(state: &mut [u64; 4], input: &[u64; 8]) { + let state_u32: &mut [u32; 8] = unsafe { &mut *(state.as_mut_ptr() as *mut [u32; 8]) }; + let input_u8: &[GenericArray; 1] = + unsafe { &*(input.as_ptr() as *const [GenericArray; 1]) }; + compress256(state_u32, input_u8); } From 7933f49bd4836f399fc30bc8d9c007dab859ceb5 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 22 Jan 2026 12:36:35 +0100 Subject: [PATCH 308/782] Implement hints in syscall_keccak_f() --- ziskos/entrypoint/src/syscalls/keccakf.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ziskos/entrypoint/src/syscalls/keccakf.rs b/ziskos/entrypoint/src/syscalls/keccakf.rs index fb8b82c7d..2addd2fa6 100644 --- a/ziskos/entrypoint/src/syscalls/keccakf.rs +++ b/ziskos/entrypoint/src/syscalls/keccakf.rs @@ -6,6 +6,9 @@ use core::arch::asm; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] use crate::ziskos_syscall; +#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] +use tiny_keccak::keccakf; + /// Executes the Keccak256 permutation on the given state. /// /// The `Keccak` system call executes a CSR set on a custom port. When transpiling from RISC-V to Zisk, @@ -27,5 +30,14 @@ pub extern "C" fn syscall_keccak_f( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x800, state); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + // Call keccakf + keccakf(unsafe { &mut *state }); + + // Store results in hints vector + #[cfg(feature = "hints")] + { + hints.extend_from_slice(unsafe { &*state }); + } + } } From c41a574455fb34e52fb3b06c1b275b7c891f0588 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:32:38 +0000 Subject: [PATCH 309/782] update Cargo.lock --- Cargo.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 42d144f0c..30af4a5bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6325,6 +6325,7 @@ dependencies = [ "precompiles-helpers", "rand 0.8.5", "serde", + "sha2", "static_assertions", "tiny-keccak", ] @@ -6348,6 +6349,7 @@ dependencies = [ "precompiles-helpers", "rand 0.8.5", "serde", + "sha2", "static_assertions", "tiny-keccak", ] From 4eff75c6f64295a2f363d719edc396ff393011b7 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:33:35 +0000 Subject: [PATCH 310/782] remove bigint256 hint handlers --- ziskos-hints/src/handlers/bigint256.rs | 138 ------------------------- ziskos-hints/src/handlers/mod.rs | 1 - 2 files changed, 139 deletions(-) delete mode 100644 ziskos-hints/src/handlers/bigint256.rs diff --git a/ziskos-hints/src/handlers/bigint256.rs b/ziskos-hints/src/handlers/bigint256.rs deleted file mode 100644 index a99d5e315..000000000 --- a/ziskos-hints/src/handlers/bigint256.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::handlers::validate_hint_length; -use crate::hint_fields; -use crate::zisklib; - -use anyhow::Result; - -/// Processes a `REDMOD256`` hint. -#[inline] -pub fn redmod256_hint(data: &[u64]) -> Result> { - hint_fields![A: 4, M: 4]; - - validate_hint_length(data, EXPECTED_LEN, "REDMOD256")?; - - let mut result: [u64; 4] = [0; 4]; - let mut hints = Vec::new(); - - unsafe { - zisklib::redmod256_c(&data[A_OFFSET], &data[M_OFFSET], &mut result[0], &mut hints); - } - - Ok(hints) -} - -/// Processes an `ADDMOD256` hint. -#[inline] -pub fn addmod256_hint(data: &[u64]) -> Result> { - hint_fields![A: 4, B: 4, M: 4]; - - validate_hint_length(data, EXPECTED_LEN, "ADDMOD256")?; - - let mut result: [u64; 4] = [0; 4]; - let mut hints = Vec::new(); - - unsafe { - zisklib::addmod256_c( - &data[A_OFFSET], - &data[B_OFFSET], - &data[M_OFFSET], - &mut result[0], - &mut hints, - ); - } - - Ok(hints) -} - -/// Processes a `MULMOD256` hint. -#[inline] -pub fn mulmod256_hint(data: &[u64]) -> Result> { - hint_fields![A: 4, B: 4, M: 4]; - - validate_hint_length(data, EXPECTED_LEN, "MULMOD256")?; - - let mut result: [u64; 4] = [0; 4]; - let mut hints = Vec::new(); - - unsafe { - zisklib::mulmod256_c( - &data[A_OFFSET], - &data[B_OFFSET], - &data[M_OFFSET], - &mut result[0], - &mut hints, - ); - } - - Ok(hints) -} - -/// Processes a `DIVREM256` hint. -#[inline] -pub fn divrem256_hint(data: &[u64]) -> Result> { - hint_fields![A: 4, B: 4]; - - validate_hint_length(data, EXPECTED_LEN, "DIVREM256")?; - - let mut hints = Vec::new(); - - let mut q: [u64; 4] = [0; 4]; - let mut r: [u64; 4] = [0; 4]; - - unsafe { - zisklib::divrem256_c(&data[A_OFFSET], &data[B_OFFSET], &mut q[0], &mut r[0], &mut hints); - } - - Ok(hints) -} - -/// Processes a `WPOW256` hint. -#[inline] -pub fn wpow256_hint(data: &[u64]) -> Result> { - hint_fields![A: 4, EXP: 4]; - - validate_hint_length(data, EXPECTED_LEN, "WPOW256")?; - - let mut result: [u64; 4] = [0; 4]; - let mut hints = Vec::new(); - - unsafe { - zisklib::wpow256_c(&data[A_OFFSET], &data[EXP_OFFSET], &mut result[0], &mut hints); - } - - Ok(hints) -} - -/// Processes an `OMUL256` hint. -#[inline] -pub fn omul256_hint(data: &[u64]) -> Result> { - hint_fields![A: 4, B: 4]; - - validate_hint_length(data, EXPECTED_LEN, "OMUL256")?; - - let mut result: [u64; 4] = [0; 4]; - let mut hints = Vec::new(); - - unsafe { - zisklib::omul256_c(&data[A_OFFSET], &data[B_OFFSET], &mut result[0], &mut hints); - } - - Ok(hints) -} - -/// Processes a `WMUL256` hint. -#[inline] -pub fn wmul256_hint(data: &[u64]) -> Result> { - hint_fields![A: 4, B: 4]; - - validate_hint_length(data, EXPECTED_LEN, "WMUL256")?; - - let mut result: [u64; 4] = [0; 4]; - let mut hints = Vec::new(); - - unsafe { - zisklib::wmul256_c(&data[A_OFFSET], &data[B_OFFSET], &mut result[0], &mut hints); - } - - Ok(hints) -} diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 00bc855b0..ec4a5c139 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -1,4 +1,3 @@ -pub mod bigint256; pub mod bls381; pub mod bn254; pub mod modexp; From 7dcd71a84081def3fe2008ef391f43e98478547e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:34:15 +0000 Subject: [PATCH 311/782] wip --- precompiles/hints/src/hints_processor.rs | 46 ++-- ziskos-hints/src/handlers/bls381.rs | 302 +++++++---------------- ziskos-hints/src/handlers/bn254.rs | 196 +++++---------- ziskos-hints/src/handlers/kzg.rs | 33 +++ ziskos-hints/src/handlers/mod.rs | 3 +- ziskos-hints/src/handlers/secp256k1.rs | 240 ------------------ ziskos-hints/src/handlers/sha256.rs | 2 +- 7 files changed, 208 insertions(+), 614 deletions(-) create mode 100644 ziskos-hints/src/handlers/kzg.rs diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 03d148166..dda78cc27 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -13,8 +13,15 @@ use std::sync::{Arc, Condvar, Mutex}; use tracing::debug; use zisk_common::io::{StreamProcessor, StreamSink}; use zisk_common::{BuiltInHint, CtrlHint, HintCode, PrecompileHint}; -use ziskos_hints::handlers::bls381::bls12_381_g1_add_hint; -use ziskos_hints::handlers::bn254::{bn254_g1_add_hint, bn254_g1_mul_hint}; +use ziskos_hints::handlers::bls381::{ + bls12_381_g1_add_hint, bls12_381_g1_msm_hint, bls12_381_g2_add_hint, bls12_381_g2_msm_hint, + bls12_381_pairing_check_hint, +}; +use ziskos_hints::handlers::bn254::{ + bn254_g1_add_hint, bn254_g1_mul_hint, bn254_pairing_check_hint, +}; +use ziskos_hints::handlers::kzg::verify_kzg_proof_hint; +use ziskos_hints::handlers::modexp::modexp_hint; use ziskos_hints::handlers::secp256k1::secp256k1_ecrecover_hint; use ziskos_hints::handlers::sha256::sha256_hint; @@ -585,35 +592,30 @@ impl HintsProcessor { match hint { // SHA256 Hint Codes BuiltInHint::Sha256 => sha256_hint(&data, data_len_bytes), + // BN254 Hint Codes BuiltInHint::Bn254G1Add => bn254_g1_add_hint(&data), BuiltInHint::Bn254G1Mul => bn254_g1_mul_hint(&data), - BuiltInHint::Bn254PairingCheck => { - unimplemented!("BN254 Pairing Check hint not implemented") - } + BuiltInHint::Bn254PairingCheck => bn254_pairing_check_hint(&data), // TODO: check + // Secp256k1 Hints BuiltInHint::Secp256k1EcRecover => secp256k1_ecrecover_hint(&data), - BuiltInHint::Secp256r1VerifySignature => { - unimplemented!("Secp256r1 Verify Signature hint not implemented") - } + BuiltInHint::Secp256r1VerifySignature => unimplemented!(), + // BLS12-381 Hint Codes BuiltInHint::Bls12_381G1Add => bls12_381_g1_add_hint(&data), - BuiltInHint::Bls12_381G1Msm => unimplemented!("BLS12-381 G1 MSM hint not implemented"), - BuiltInHint::Bls12_381G2Add => unimplemented!("BLS12-381 G2 Add hint not implemented"), - BuiltInHint::Bls12_381G2Msm => unimplemented!("BLS12-381 G2 MSM hint not implemented"), - BuiltInHint::Bls12_381PairingCheck => { - unimplemented!("BLS12-381 Pairing Check hint not implemented") - } - BuiltInHint::Bls12_381FpToG1 => { - unimplemented!("BLS12-381 FP to G1 hint not implemented") - } - BuiltInHint::Bls12_381Fp2ToG2 => { - unimplemented!("BLS12-381 FP2 to G2 hint not implemented") - } + BuiltInHint::Bls12_381G1Msm => bls12_381_g1_msm_hint(&data), // TODO: check + BuiltInHint::Bls12_381G2Add => bls12_381_g2_add_hint(&data), // TODO: check + BuiltInHint::Bls12_381G2Msm => bls12_381_g2_msm_hint(&data), // TODO: check + BuiltInHint::Bls12_381PairingCheck => bls12_381_pairing_check_hint(&data), // TODO: check + BuiltInHint::Bls12_381FpToG1 => unimplemented!(), + BuiltInHint::Bls12_381Fp2ToG2 => unimplemented!(), + // Modular Exponentiation Hint Codes - BuiltInHint::ModExp => unimplemented!("Modular Exponentiation hint not implemented"), + BuiltInHint::ModExp => modexp_hint(&data), // TODO: check + // KZG Hint Codes - BuiltInHint::VerifyKzgProof => unimplemented!("KZG Verify Proof hint not implemented"), + BuiltInHint::VerifyKzgProof => verify_kzg_proof_hint(&data), // TODO: check } } } diff --git a/ziskos-hints/src/handlers/bls381.rs b/ziskos-hints/src/handlers/bls381.rs index 45dcaeb6c..f3c25c539 100644 --- a/ziskos-hints/src/handlers/bls381.rs +++ b/ziskos-hints/src/handlers/bls381.rs @@ -1,4 +1,7 @@ -use crate::{handlers::validate_hint_min_length, hint_fields, zisklib}; +use crate::{ + handlers::{validate_hint_length, validate_hint_min_length}, + hint_fields, zisklib, +}; use anyhow::Result; @@ -23,246 +26,115 @@ pub fn bls12_381_g1_add_hint(data: &[u64]) -> Result> { Ok(hints) } -// /// Processes an `MUL_FP12_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_mul_fp12_hint(data: &[u64]) -> Result> { -// hint_fields![A: 72, B: 72]; - -// validate_hint_length(data, EXPECTED_LEN, "MUL_FP12_BLS12_381")?; - -// // Safe to unwrap due to prior length validation. -// let a: &[u64; A_SIZE] = data[A_OFFSET..A_OFFSET + A_SIZE].try_into().unwrap(); -// let b: &[u64; B_SIZE] = data[B_OFFSET..B_OFFSET + B_SIZE].try_into().unwrap(); - -// let mut hints = Vec::new(); -// zisklib::mul_fp12_bls12_381(a, b, &mut hints); - -// Ok(hints) -// } - -// /// Processes a `DECOMPRESS_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_decompress_hint(data: &[u64]) -> Result> { -// hint_fields![INPUT: 6]; - -// validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_BLS12_381")?; - -// // Safe to unwrap due to prior length validation. -// let input: &[u64; INPUT_SIZE] = -// data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); -// // let input: [u64; INPUT_SIZE] = [ -// // input[3].to_be(), -// // input[2].to_be(), -// // input[1].to_be(), -// // input[0].to_be(), -// // input[5].to_be(), -// // input[4].to_be(), -// // ]; - -// // Map a [u64; 6] to a [u8; 48] -// let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; - -// let mut hints = Vec::new(); -// zisklib::decompress_bls12_381(input, &mut hints).map_err(anyhow::Error::msg)?; - -// Ok(hints) -// } - -// /// Processes an `IS_ON_CURVE_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_is_on_curve_hint(data: &[u64]) -> Result> { -// hint_fields![P: 12]; - -// validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_BLS12_381")?; - -// // Safe to unwrap due to prior length validation. -// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - -// let mut hints = Vec::new(); -// zisklib::is_on_curve_bls12_381(p, &mut hints); - -// Ok(hints) -// } - -// /// Processes an `IS_ON_SUBGROUP_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_is_on_subgroup_hint(data: &[u64]) -> Result> { -// hint_fields![P: 12]; - -// validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_BLS12_381")?; - -// // Safe to unwrap due to prior length validation. -// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - -// let mut hints = Vec::new(); -// zisklib::is_on_subgroup_bls12_381(p, &mut hints); - -// Ok(hints) -// } - -// /// Processes an `ADD_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_add_hint(data: &[u64]) -> Result> { -// hint_fields![P1: 12, P2: 12]; - -// validate_hint_length(data, EXPECTED_LEN, "ADD_BLS12_381")?; - -// // Safe to unwrap due to prior length validation. -// let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); -// let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); - -// let mut hints = Vec::new(); -// zisklib::add_bls12_381(p1, p2, &mut hints); - -// Ok(hints) -// } - -// /// Processes a `SCALAR_MUL_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_scalar_mul_hint(data: &[u64]) -> Result> { -// hint_fields![P: 12, K: 6]; - -// validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_BLS12_381")?; - -// // Safe to unwrap due to prior length validation. -// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); -// let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); - -// let mut hints = Vec::new(); -// zisklib::scalar_mul_bls12_381(p, k, &mut hints); - -// Ok(hints) -// } - -// /// Processes a `DECOMPRESS_TWIST_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_decompress_twist_hint(data: &[u64]) -> Result> { -// hint_fields![INPUT: 12]; - -// validate_hint_length(data, EXPECTED_LEN, "DECOMPRESS_TWIST_BLS12_381")?; - -// // Safe to unwrap due to prior length validation. -// let input: &[u64; INPUT_SIZE] = -// data[INPUT_OFFSET..INPUT_OFFSET + INPUT_SIZE].try_into().unwrap(); -// // let input: [u64; INPUT_SIZE] = [ -// // input[3].to_be(), -// // input[2].to_be(), -// // input[1].to_be(), -// // input[0].to_be(), -// // input[7].to_be(), -// // input[6].to_be(), -// // input[5].to_be(), -// // input[4].to_be(), -// // input[11].to_be(), -// // input[10].to_be(), -// // input[9].to_be(), -// // input[8].to_be(), -// // ]; - -// // Map a [u64; 6] to a [u8; 48] -// let input: &[u8; INPUT_SIZE * 8] = unsafe { &*(input.as_ptr() as *const [u8; INPUT_SIZE * 8]) }; - -// let mut hints = Vec::new(); -// zisklib::decompress_twist_bls12_381(input, &mut hints).map_err(anyhow::Error::msg)?; - -// Ok(hints) -// } - -// /// Processes an `IS_ON_CURVE_TWIST_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_is_on_curve_twist_hint(data: &[u64]) -> Result> { -// hint_fields![P: 24]; +/// Processes an `HINT_BLS12_381_G1_MSM` hint. +#[inline] +pub fn bls12_381_g1_msm_hint(data: &[u64]) -> Result> { + if data.is_empty() { + anyhow::bail!("HINT_BLS12_381_G1_MSM: data is empty"); + } -// validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_TWIST_BLS12_381")?; + let num_pairs = data[0] as usize; -// // Safe to unwrap due to prior length validation. -// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + const POINT_SIZE: usize = 96; + const SCALAR_SIZE: usize = 32; + const PAIR_SIZE_BYTES: usize = POINT_SIZE + SCALAR_SIZE; + const PAIR_SIZE: usize = PAIR_SIZE_BYTES.div_ceil(8); -// let mut hints = Vec::new(); -// zisklib::is_on_curve_twist_bls12_381(p, &mut hints); + let expected_len = 1 + num_pairs * PAIR_SIZE; -// Ok(hints) -// } + validate_hint_length(data, expected_len, "HINT_BLS12_381_G1_MSM")?; -// /// Processes an `IS_ON_SUBGROUP_TWIST_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_is_on_subgroup_twist_hint(data: &[u64]) -> Result> { -// hint_fields![P: 24]; + let bytes = unsafe { + std::slice::from_raw_parts(data.as_ptr().add(1) as *const u8, num_pairs * PAIR_SIZE_BYTES) + }; -// validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_TWIST_BLS12_381")?; + let mut hints = Vec::new(); + let result: &mut [u8; 96] = &mut [0u8; 96]; + unsafe { + zisklib::bls12_381_g1_msm_c(result.as_mut_ptr(), bytes.as_ptr(), num_pairs, &mut hints); + } -// // Safe to unwrap due to prior length validation. -// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + Ok(hints) +} -// let mut hints = Vec::new(); -// zisklib::is_on_subgroup_twist_bls12_381(p, &mut hints); +/// Processes an `HINT_BLS12_381_G2_ADD` hint. +#[inline] +pub fn bls12_381_g2_add_hint(data: &[u64]) -> Result> { + hint_fields![A: 192, B: 192]; -// Ok(hints) -// } + validate_hint_min_length(data, EXPECTED_LEN_U64, "HINT_BLS12_381_G2_ADD")?; -// /// Processes an `ADD_TWIST_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_add_twist_hint(data: &[u64]) -> Result> { -// hint_fields![P1: 24, P2: 24]; + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; -// validate_hint_length(data, EXPECTED_LEN, "ADD_TWIST_BLS12_381")?; + let a: &[u8; A_SIZE] = bytes[A_OFFSET..A_OFFSET + A_SIZE].try_into().unwrap(); + let b: &[u8; B_SIZE] = bytes[B_OFFSET..B_OFFSET + B_SIZE].try_into().unwrap(); -// // Safe to unwrap due to prior length validation. -// let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); -// let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); + let mut hints = Vec::new(); + let result: &mut [u8; 192] = &mut [0u8; 192]; + unsafe { + zisklib::bls12_381_g2_add_c(result.as_mut_ptr(), a.as_ptr(), b.as_ptr(), &mut hints); + } -// let mut hints = Vec::new(); -// zisklib::add_twist_bls12_381(p1, p2, &mut hints); + Ok(hints) +} -// Ok(hints) -// } +/// Processes an `HINT_BLS12_381_G2_MSM` hint. +#[inline] +pub fn bls12_381_g2_msm_hint(data: &[u64]) -> Result> { + if data.is_empty() { + anyhow::bail!("HINT_BLS12_381_G1_MSM: data is empty"); + } -// /// Processes a `SCALAR_MUL_TWIST_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_scalar_mul_twist_hint(data: &[u64]) -> Result> { -// hint_fields![P: 24, K: 6]; + let num_pairs = data[0] as usize; -// validate_hint_length(data, EXPECTED_LEN, "SCALAR_MUL_TWIST_BLS12_381")?; + const POINT_SIZE: usize = 192; + const SCALAR_SIZE: usize = 32; + const PAIR_SIZE_BYTES: usize = POINT_SIZE + SCALAR_SIZE; + const PAIR_SIZE: usize = PAIR_SIZE_BYTES.div_ceil(8); -// // Safe to unwrap due to prior length validation. -// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); -// let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); + let expected_len = 1 + num_pairs * PAIR_SIZE; -// let mut hints = Vec::new(); -// zisklib::scalar_mul_twist_bls12_381(p, k, &mut hints); + validate_hint_length(data, expected_len, "HINT_BLS12_381_G1_MSM")?; -// Ok(hints) -// } + let bytes = unsafe { + std::slice::from_raw_parts(data.as_ptr().add(1) as *const u8, num_pairs * PAIR_SIZE_BYTES) + }; -// /// Processes a `MILLER_LOOP_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_miller_loop_hint(data: &[u64]) -> Result> { -// hint_fields![P: 12, Q: 24]; + let mut hints = Vec::new(); + let result: &mut [u8; 192] = &mut [0u8; 192]; + unsafe { + zisklib::bls12_381_g2_msm_c(result.as_mut_ptr(), bytes.as_ptr(), num_pairs, &mut hints); + } -// validate_hint_length(data, EXPECTED_LEN, "MILLER_LOOP_BLS12_381")?; + Ok(hints) +} -// // Safe to unwrap due to prior length validation. -// let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); -// let q: &[u64; Q_SIZE] = data[Q_OFFSET..Q_OFFSET + Q_SIZE].try_into().unwrap(); +/// Processes an `HINT_BLS12_381_PAIRING_CHECK` hint. +#[inline] +pub fn bls12_381_pairing_check_hint(data: &[u64]) -> Result> { + if data.is_empty() { + anyhow::bail!("HINT_BLS12_381_G1_MSM: data is empty"); + } -// let mut hints = Vec::new(); -// zisklib::miller_loop_bls12_381(p, q, &mut hints); + let num_pairs = data[0] as usize; -// Ok(hints) -// } + const G1_SIZE: usize = 96; + const G2_SIZE: usize = 192; + const PAIR_SIZE_BYTES: usize = G1_SIZE + G2_SIZE; + const PAIR_SIZE: usize = PAIR_SIZE_BYTES.div_ceil(8); -// /// Processes a `FINAL_EXP_BLS12_381` hint. -// #[inline] -// pub fn bls12_381_final_exp_hint(data: &[u64]) -> Result> { -// hint_fields![F: 72]; + let expected_len = 1 + num_pairs * PAIR_SIZE; -// validate_hint_length(data, EXPECTED_LEN, "FINAL_EXP_BLS12_381")?; + validate_hint_length(data, expected_len, "HINT_BLS12_381_PAIRING_CHECK")?; -// // Safe to unwrap due to prior length validation. -// let f: &[u64; F_SIZE] = data[F_OFFSET..F_OFFSET + F_SIZE].try_into().unwrap(); + let bytes = unsafe { + std::slice::from_raw_parts(data.as_ptr().add(1) as *const u8, num_pairs * PAIR_SIZE_BYTES) + }; -// let mut hints = Vec::new(); -// zisklib::final_exp_bls12_381(f, &mut hints); + let mut hints = Vec::new(); + unsafe { + zisklib::bls12_381_pairing_check_c(bytes.as_ptr(), num_pairs, &mut hints); + } -// Ok(hints) -// } + Ok(hints) +} diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index ef722d2ed..83d01f7ae 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -49,159 +49,85 @@ pub fn bn254_g1_mul_hint(data: &[u64]) -> Result> { Ok(hints) } -/// Processes an `IS_ON_CURVE_BN254`` hint. +/// Processes an `HINT_BN254_PAIRING_CHECK` hint. #[inline] -pub fn bn254_is_on_curve_hint(data: &[u64]) -> Result> { - hint_fields![P: 8]; +pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { + // --------------------------------------------------------------------- + // Input format: + // + // data[0] = num_pairs + // data[1 ..] = G1 points (num_pairs * 8 u64s) + // data[1 + g1_len ..] = G2 points (num_pairs * 16 u64s) + // + // Each G1 point: 8 u64 = 64 bytes + // Each G2 point: 16 u64 = 128 bytes + // + // All data is interpreted in *native-endian* u64 layout. + // --------------------------------------------------------------------- + + const G1_WORDS: usize = 8; + const G2_WORDS: usize = 16; - validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_BN254")?; - - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::is_on_curve_bn254(p, &mut hints); - - Ok(hints) -} - -/// Processes a `TO_AFFINE_BN254` hint. -#[inline] -pub fn bn254_to_affine_hint(data: &[u64]) -> Result> { - hint_fields![P: 12]; - - validate_hint_length(data, EXPECTED_LEN, "TO_AFFINE_BN254")?; - - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::to_affine_bn254(p, &mut hints); - - Ok(hints) -} - -/// Processes an `ADD_BN254` hint. -#[inline] -pub fn bn254_add_hint(data: &[u64]) -> Result> { - hint_fields![P1: 8, P2: 8]; - - validate_hint_length(data, EXPECTED_LEN, "ADD_BN254")?; - - let p1: &[u64; P1_SIZE] = data[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); - let p2: &[u64; P2_SIZE] = data[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::add_bn254(p1, p2, &mut hints); - - Ok(hints) -} - -/// Processes a `MUL_BN254` hint. -#[inline] -pub fn bn254_mul_hint(data: &[u64]) -> Result> { - hint_fields![P: 8, K: 4]; - - validate_hint_length(data, EXPECTED_LEN, "MUL_BN254")?; - - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - let k: &[u64; K_SIZE] = data[K_OFFSET..K_OFFSET + K_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::mul_bn254(p, k, &mut hints); - - Ok(hints) -} + if data.is_empty() { + anyhow::bail!("BN254_PAIRING_CHECK: data is empty"); + } -/// Processes a `TO_AFFINE_TWIST_BN254` hint. -#[inline] -pub fn bn254_to_affine_twist_hint(data: &[u64]) -> Result> { - hint_fields![P: 24]; + let num_pairs = data[0] as usize; - validate_hint_length(data, EXPECTED_LEN, "TO_AFFINE_TWIST_BN254")?; + // Prevent absurd sizes early (optional but defensive) + if num_pairs == 0 { + anyhow::bail!("BN254_PAIRING_CHECK: num_pairs is zero"); + } - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + println!("num_pairs: {}", num_pairs); - let mut hints = Vec::new(); - zisklib::to_affine_twist_bn254(p, &mut hints); + let expected_len = 1 + num_pairs * (G1_WORDS + G2_WORDS); - Ok(hints) -} - -/// Processes an `IS_ON_CURVE_TWIST_BN254` hint. -#[inline] -pub fn bn254_is_on_curve_twist_hint(data: &[u64]) -> Result> { - hint_fields![P: 16]; - - validate_hint_length(data, EXPECTED_LEN, "IS_ON_CURVE_TWIST_BN254")?; + validate_hint_length(data, expected_len, "PAIRING_BATCH_BN254")?; - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + // Split input buffer + // --------------------------------------------------------------------- + // Reinterpret &[u64] as grouped byte slices (zero-copy) + // + // SAFETY INVARIANTS: + // - Length has been validated exactly + // - &[u64] memory is contiguous + // - We only create immutable views + // - Alignment is safe (u8 alignment = 1) + // - Endianness is intentionally native + // --------------------------------------------------------------------- - let mut hints = Vec::new(); - zisklib::is_on_curve_twist_bn254(p, &mut hints); + let g1_start = 1; + let g1_end = g1_start + num_pairs * G1_WORDS; + let g2_start = g1_end; + let g2_end = g2_start + num_pairs * G2_WORDS; - Ok(hints) -} + let g1_words = &data[g1_start..g1_end]; + let g2_words = &data[g2_start..g2_end]; -/// Processes an `IS_ON_SUBGROUP_TWIST_BN254` hint. -#[inline] -pub fn bn254_is_on_subgroup_twist_hint(data: &[u64]) -> Result> { - hint_fields![P: 16]; + let g1_points: &[[u8; G1_WORDS * 8]] = + unsafe { reinterpret_u64_as_bytes::<{ G1_WORDS * 8 }>(g1_words, num_pairs) }; - validate_hint_length(data, EXPECTED_LEN, "IS_ON_SUBGROUP_TWIST_BN254")?; + let g2_points: &[[u8; G2_WORDS * 8]] = + unsafe { reinterpret_u64_as_bytes::<{ G2_WORDS * 8 }>(g2_words, num_pairs) }; - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); + // Build arrays of raw pointers for the FFI call + let g1_ptrs: Vec<*const u8> = g1_points.iter().map(|p| p.as_ptr()).collect(); + let g2_ptrs: Vec<*const u8> = g2_points.iter().map(|p| p.as_ptr()).collect(); let mut hints = Vec::new(); - zisklib::is_on_subgroup_twist_bn254(p, &mut hints); + unsafe { + zisklib::bn254_pairing_check_c(g1_ptrs.as_ptr(), g2_ptrs.as_ptr(), num_pairs, &mut hints); + } Ok(hints) } -/// Processes a `PAIRING_BATCH_BN254` hint. -/// Format: [num_points:u64][g1_points:&[u64]][g2_points:&[u64]] -/// where g1_points has length num_points * 8 and g2_points has length num_points * 16 #[inline] -pub fn bn254_pairing_batch_hint(data: &[u64]) -> Result> { - if data.is_empty() { - anyhow::bail!("PAIRING_BATCH_BN254: data is empty"); - } - - let num_points = data[0] as usize; - - const G1_POINT_SIZE: usize = 8; - const G2_POINT_SIZE: usize = 16; - - let expected_len = 1 + num_points * G1_POINT_SIZE + num_points * G2_POINT_SIZE; - - validate_hint_length(data, expected_len, "PAIRING_BATCH_BN254")?; - - let g1_start = 1; - let g1_end = g1_start + num_points * G1_POINT_SIZE; - let g2_start = g1_end; - let g2_end = g2_start + num_points * G2_POINT_SIZE; - - let g1_points_slice = &data[g1_start..g1_end]; - let g2_points_slice = &data[g2_start..g2_end]; - - // SAFETY: We've validated the length, and the memory layout of &[u64] with length num_points * 8 - // is identical to &[[u64; 8]] with length num_points - let g1_points: &[[u64; G1_POINT_SIZE]] = unsafe { - std::slice::from_raw_parts( - g1_points_slice.as_ptr() as *const [u64; G1_POINT_SIZE], - num_points, - ) - }; - - // SAFETY: We've validated the length, and the memory layout of &[u64] with length num_points * 16 - // is identical to &[[u64; 16]] with length num_points - let g2_points: &[[u64; G2_POINT_SIZE]] = unsafe { - std::slice::from_raw_parts( - g2_points_slice.as_ptr() as *const [u64; G2_POINT_SIZE], - num_points, - ) - }; - - let mut hints = Vec::new(); - zisklib::pairing_batch_bn254(g1_points, g2_points, &mut hints); - - Ok(hints) +unsafe fn reinterpret_u64_as_bytes( + slice: &[u64], + count: usize, +) -> &[[u8; BYTES]] { + debug_assert_eq!(slice.len(), count * (BYTES / 8)); + std::slice::from_raw_parts(slice.as_ptr().cast(), count) } diff --git a/ziskos-hints/src/handlers/kzg.rs b/ziskos-hints/src/handlers/kzg.rs new file mode 100644 index 000000000..6e27c6d19 --- /dev/null +++ b/ziskos-hints/src/handlers/kzg.rs @@ -0,0 +1,33 @@ +use crate::{handlers::validate_hint_min_length, hint_fields, zisklib}; + +use anyhow::Result; + +/// Processes an `HINT_VERIFY_KZG_PROOF` hint. +#[inline] +pub fn verify_kzg_proof_hint(data: &[u64]) -> Result> { + hint_fields![Z: 32, Y: 32, COMMITMENT: 48, PROOF: 48]; + + validate_hint_min_length(data, EXPECTED_LEN, "HINT_VERIFY_KZG_PROOF")?; + + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + + let z: &[u8; Z_SIZE] = bytes[Z_OFFSET..Z_OFFSET + Z_SIZE].try_into().unwrap(); + let y: &[u8; Y_SIZE] = bytes[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); + let commitment: &[u8; COMMITMENT_SIZE] = + bytes[COMMITMENT_OFFSET..COMMITMENT_OFFSET + COMMITMENT_SIZE].try_into().unwrap(); + let proof: &[u8; PROOF_SIZE] = + bytes[PROOF_OFFSET..PROOF_OFFSET + PROOF_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + unsafe { + zisklib::verify_kzg_proof_c( + z.as_ptr(), + y.as_ptr(), + commitment.as_ptr(), + proof.as_ptr(), + &mut hints, + ); + } + + Ok(hints) +} diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index ec4a5c139..8b3300808 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -3,6 +3,7 @@ pub mod bn254; pub mod modexp; pub mod secp256k1; pub mod sha256; +pub mod kzg; /// Macro to generate size, offset, and expected length constants for hint data fields. /// @@ -29,7 +30,7 @@ macro_rules! hint_fields { #[allow(dead_code)] const EXPECTED_LEN: usize = hint_fields!(@sum $($size),+); #[allow(dead_code)] - const EXPECTED_LEN_U64: usize = (EXPECTED_LEN + 7) / 8; + const EXPECTED_LEN_U64: usize = EXPECTED_LEN.div_ceil(8); }; (@offsets $offset:expr, $name:ident: $size:expr) => { diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 491962110..8c410efe6 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -1,4 +1,3 @@ -use crate::handlers::validate_hint_length; use crate::handlers::validate_hint_min_length; use crate::hint_fields; use crate::zisklib; @@ -34,242 +33,3 @@ pub fn secp256k1_ecrecover_hint(data: &[u64]) -> Result> { Ok(hints) } - -// Processes a `SECP256K1_FN_REDUCE`` hint. -#[inline] -pub fn secp256k1_fn_reduce_hint(data: &[u64]) -> Result> { - hint_fields![X: 4]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_REDUCE")?; - - let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_fn_reduce(x, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_FN_ADD` hint. -#[inline] -pub fn secp256k1_fn_add_hint(data: &[u64]) -> Result> { - hint_fields![X: 4, Y: 4]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_ADD")?; - - let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_fn_add(x, y, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_FN_NEG`` hint. -#[inline] -pub fn secp256k1_fn_neg_hint(data: &[u64]) -> Result> { - hint_fields![X: 4]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_NEG")?; - - let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_fn_neg(x, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_FN_SUB`` hint. -#[inline] -pub fn secp256k1_fn_sub_hint(data: &[u64]) -> Result> { - hint_fields![X: 4, Y: 4]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_SUB")?; - - let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_fn_sub(x, y, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_FN_MUL`` hint. -#[inline] -pub fn secp256k1_fn_mul_hint(data: &[u64]) -> Result> { - hint_fields![X: 4, Y: 4]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_MUL")?; - - let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_fn_mul(x, y, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_FN_INV`` hint. -#[inline] -pub fn secp256k1_fn_inv_hint(data: &[u64]) -> Result> { - hint_fields![X: 4]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FN_INV")?; - - let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_fn_inv(x, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_FP_REDUCE`` hint. -#[inline] -pub fn secp256k1_fp_reduce_hint(data: &[u64]) -> Result> { - hint_fields![X: 4]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_REDUCE")?; - - let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_fp_reduce(x, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_FP_ADD` hint. -#[inline] -pub fn secp256k1_fp_add_hint(data: &[u64]) -> Result> { - hint_fields![X: 4, Y: 4]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_ADD")?; - - let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_fp_add(x, y, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_FP_NEGATE` hint. -#[inline] -pub fn secp256k1_fp_negate_hint(data: &[u64]) -> Result> { - hint_fields![X: 4]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_NEGATE")?; - - let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_fp_negate(x, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_FP_MUL`` hint. -#[inline] -pub fn secp256k1_fp_mul_hint(data: &[u64]) -> Result> { - hint_fields![X: 4, Y: 4]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_MUL")?; - - let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - let y: &[u64; Y_SIZE] = data[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_fp_mul(x, y, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_FP_MUL_SCALAR` hint. -#[inline] -pub fn secp256k1_fp_mul_scalar_hint(data: &[u64]) -> Result> { - hint_fields![X: 4, SCALAR: 1]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_FP_MUL_SCALAR")?; - - let x: &[u64; X_SIZE] = data[X_OFFSET..X_OFFSET + X_SIZE].try_into().unwrap(); - let scalar: u64 = data[SCALAR_OFFSET]; - - let mut hints = Vec::new(); - zisklib::secp256k1_fp_mul_scalar(x, scalar, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_TO_AFFINE` hint. -#[inline] -pub fn secp256k1_to_affine_hint(data: &[u64]) -> Result> { - hint_fields![P: 12]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_TO_AFFINE")?; - - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_to_affine(p, &mut hints); - - Ok(hints) -} - -// Processes a `SECP256K1_DECOMPRESS` hint. -#[inline] -pub fn secp256k1_decompress_hint(data: &[u64]) -> Result> { - hint_fields![X_BYTES: 4, Y_IS_ODD: 1]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DECOMPRESS")?; - - let x: &[u64; X_BYTES_SIZE] = - data[X_BYTES_OFFSET..X_BYTES_OFFSET + X_BYTES_SIZE].try_into().unwrap(); - let x: [u64; X_BYTES_SIZE] = [x[3].to_be(), x[2].to_be(), x[1].to_be(), x[0].to_be()]; - let y_is_odd = data[Y_IS_ODD_OFFSET] != 0; - - let mut hints = Vec::new(); - zisklib::secp256k1_decompress(&x, y_is_odd, &mut hints).map_err(anyhow::Error::msg)?; - - Ok(hints) -} - -// Processes a `SECP256K1_DOUBLE_SCALAR_MUL_WITH_G` hint. -#[inline] -pub fn secp256k1_double_scalar_mul_with_g_hint(data: &[u64]) -> Result> { - hint_fields![K1: 4, K2: 4, P: 8]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_DOUBLE_SCALAR_MUL_WITH_G")?; - - let k1: &[u64; K1_SIZE] = data[K1_OFFSET..K1_OFFSET + K1_SIZE].try_into().unwrap(); - let k2: &[u64; K2_SIZE] = data[K2_OFFSET..K2_OFFSET + K2_SIZE].try_into().unwrap(); - let p: &[u64; P_SIZE] = data[P_OFFSET..P_OFFSET + P_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_double_scalar_mul_with_g(k1, k2, p, &mut hints); - - Ok(hints) -} - -/// Processes an `SECP256K1_ECDSA_VERIFY` hint. -#[inline] -pub fn secp256k1_ecdsa_verify_hint(data: &[u64]) -> Result> { - hint_fields![PK: 8, Z: 4, R: 4, S: 4]; - - validate_hint_length(data, EXPECTED_LEN, "SECP256K1_ECDSA_VERIFY")?; - - let pk: &[u64; PK_SIZE] = data[PK_OFFSET..PK_OFFSET + PK_SIZE].try_into().unwrap(); - let z: &[u64; Z_SIZE] = data[Z_OFFSET..Z_OFFSET + Z_SIZE].try_into().unwrap(); - let r: &[u64; R_SIZE] = data[R_OFFSET..R_OFFSET + R_SIZE].try_into().unwrap(); - let s: &[u64; S_SIZE] = data[S_OFFSET..S_OFFSET + S_SIZE].try_into().unwrap(); - - let mut hints = Vec::new(); - zisklib::secp256k1_ecdsa_verify(pk, z, r, s, &mut hints); - - Ok(hints) -} diff --git a/ziskos-hints/src/handlers/sha256.rs b/ziskos-hints/src/handlers/sha256.rs index 24152b654..b6a596a85 100644 --- a/ziskos-hints/src/handlers/sha256.rs +++ b/ziskos-hints/src/handlers/sha256.rs @@ -5,7 +5,7 @@ use anyhow::Result; /// Processes an `HINT_SHA256` hint. #[inline] pub fn sha256_hint(data: &[u64], data_len_bytes: usize) -> Result> { - let data_len_u64 = (data_len_bytes + 7) / 8; // convert bytes to u64 length + let data_len_u64 = (data_len_bytes + 7) / 8; validate_hint_min_length(data, data_len_u64, "HINT_SHA256")?; From 4fe336c74717ef14a63055d271b57327b0e58a26 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:36:50 +0000 Subject: [PATCH 312/782] cargo fmt & clippy --- ziskos-hints/src/handlers/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 8b3300808..a6f55a177 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -1,9 +1,9 @@ pub mod bls381; pub mod bn254; +pub mod kzg; pub mod modexp; pub mod secp256k1; pub mod sha256; -pub mod kzg; /// Macro to generate size, offset, and expected length constants for hint data fields. /// From 3be20c36c3f20b97fe2a78def5d14047f5b8a597 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:37:33 +0000 Subject: [PATCH 313/782] cargo fmt & clippy --- ziskos-hints/src/handlers/sha256.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ziskos-hints/src/handlers/sha256.rs b/ziskos-hints/src/handlers/sha256.rs index b6a596a85..8aa2bc808 100644 --- a/ziskos-hints/src/handlers/sha256.rs +++ b/ziskos-hints/src/handlers/sha256.rs @@ -5,7 +5,7 @@ use anyhow::Result; /// Processes an `HINT_SHA256` hint. #[inline] pub fn sha256_hint(data: &[u64], data_len_bytes: usize) -> Result> { - let data_len_u64 = (data_len_bytes + 7) / 8; + let data_len_u64 = data_len_bytes.div_ceil(8); validate_hint_min_length(data, data_len_u64, "HINT_SHA256")?; From c5db1ec2d764fc5f39fd6cee0732dc43cf511f9f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 23 Jan 2026 07:42:08 +0000 Subject: [PATCH 314/782] adapting to new crypto trait --- common/src/hints.rs | 3 ++- common/src/io/stream/zisk_stream.rs | 2 +- common/src/utils.rs | 24 ++++++++++++------- precompiles/hints/src/hints_processor.rs | 4 ++-- ziskos-hints/src/handlers/bn254.rs | 2 -- ziskos-hints/src/handlers/kzg.rs | 2 +- ziskos-hints/src/handlers/mod.rs | 30 ++++++++++++++++++++++++ ziskos-hints/src/handlers/modexp.rs | 27 ++++++++++++++------- 8 files changed, 70 insertions(+), 24 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index ffb880baa..339f411b8 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -376,7 +376,8 @@ impl PrecompileHint { anyhow::ensure!( slice.len() >= idx + 1 + num_u64s, - "Slice too short for hint data: expected {} u64s, got {}", + "Slice too short for hint data {}: expected {} u64s, got {}", + (header >> 32) as u32, num_u64s, slice.len() - idx - 1 ); diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index 96e7a5a8f..74cc87023 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -106,7 +106,7 @@ impl ZiskStream { ) { while let Ok(ThreadCommand::Process) = rx.recv() { if let Err(e) = Self::process_stream(&mut stream, &*hints_processor) { - tracing::error!("Error processing hints in background thread: {:?}", e); + panic!("Error processing hints in background thread: {:?}", e); } } // Loop exits when Shutdown is received or channel is closed diff --git a/common/src/utils.rs b/common/src/utils.rs index d095e5c71..53973ec7c 100644 --- a/common/src/utils.rs +++ b/common/src/utils.rs @@ -93,17 +93,25 @@ pub fn init_tracing(log_path: &str) { /// # Type Parameters /// * `T` - Source element type /// * `U` - Destination element type -pub fn reinterpret_vec(v: Vec) -> anyhow::Result> { +pub fn reinterpret_vec(mut v: Vec) -> anyhow::Result> { let size_t = std::mem::size_of::(); let size_u = std::mem::size_of::(); - // Check that total byte size is compatible - if (v.len() * size_t) % size_u != 0 { - return Err(anyhow::anyhow!( - "Total byte size {} is not divisible by target type size {}", - v.len() * size_t, - size_u - )); + // Total bytes in Vec + let total_bytes = v.len() * size_t; + + // Compute remainder to see if we need padding + let rem = total_bytes % size_u; + + // If remainder exists, pad with zeroed T elements + if rem != 0 { + // Number of extra bytes needed + let pad_bytes = size_u - rem; + + // Number of T elements to pad (round up) + let pad_t = (pad_bytes + size_t - 1) / size_t; + + v.extend(std::iter::repeat(T::default()).take(pad_t)); } // Check that the pointer is properly aligned for U diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index dda78cc27..adfbedaf2 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -239,8 +239,8 @@ impl HintsProcessor { if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to previous error")); } - let hint = PrecompileHint::from_u64_slice(hints, idx, true)?; + self.num_hint.fetch_add(1, Ordering::Relaxed); // Check if custom handler is registered for custom hints (skip pass-through) @@ -388,7 +388,7 @@ impl HintsProcessor { return; } - println!("Processing Hint => {:?}:", hint); + // println!("Processing Hint => {:?}:", hint); // Check if we should stop due to error - but still need to fill the slot let result = if state.error_flag.load(Ordering::Acquire) { diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 83d01f7ae..9fbc0a938 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -79,8 +79,6 @@ pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { anyhow::bail!("BN254_PAIRING_CHECK: num_pairs is zero"); } - println!("num_pairs: {}", num_pairs); - let expected_len = 1 + num_pairs * (G1_WORDS + G2_WORDS); validate_hint_length(data, expected_len, "PAIRING_BATCH_BN254")?; diff --git a/ziskos-hints/src/handlers/kzg.rs b/ziskos-hints/src/handlers/kzg.rs index 6e27c6d19..c18dccedb 100644 --- a/ziskos-hints/src/handlers/kzg.rs +++ b/ziskos-hints/src/handlers/kzg.rs @@ -5,7 +5,7 @@ use anyhow::Result; /// Processes an `HINT_VERIFY_KZG_PROOF` hint. #[inline] pub fn verify_kzg_proof_hint(data: &[u64]) -> Result> { - hint_fields![Z: 32, Y: 32, COMMITMENT: 48, PROOF: 48]; + hint_fields![Z: 4, Y: 4, COMMITMENT: 6, PROOF: 6]; validate_hint_min_length(data, EXPECTED_LEN, "HINT_VERIFY_KZG_PROOF")?; diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index a6f55a177..13eea02b9 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -66,6 +66,36 @@ fn read_field<'a>(data: &'a [u64], pos: &mut usize) -> anyhow::Result<&'a [u64]> Ok(field) } +#[inline] +fn read_field_bytes<'a>(data: &'a [u64], pos: &mut usize) -> anyhow::Result<(&'a [u8], usize)> { + // Treat the entire u64 slice as bytes + let byte_data: &[u8] = unsafe { + std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * size_of::()) + }; + + // Make sure we have at least 8 bytes for the length header + if *pos + 8 > byte_data.len() { + anyhow::bail!("MODEXP hint data too short to read length"); + } + + // Read length as native-endian u64 + let len_bytes = + u64::from_ne_bytes(byte_data[*pos..*pos + 8].try_into().expect("slice length checked")) + as usize; + *pos += 8; + + // Ensure there are enough bytes for the field + if *pos + len_bytes > byte_data.len() { + anyhow::bail!("MODEXP hint data too short for field"); + } + + // Get the slice + let field = &byte_data[*pos..*pos + len_bytes]; + *pos += len_bytes; + + Ok((field, len_bytes)) +} + /// Validates that the hint data has the expected length. /// /// # Arguments diff --git a/ziskos-hints/src/handlers/modexp.rs b/ziskos-hints/src/handlers/modexp.rs index bf16eaac5..d0ecffd99 100644 --- a/ziskos-hints/src/handlers/modexp.rs +++ b/ziskos-hints/src/handlers/modexp.rs @@ -1,7 +1,4 @@ -use crate::{ - handlers::{read_field, validate_hint_length}, - zisklib, -}; +use crate::{handlers::read_field_bytes, zisklib}; use anyhow::Result; @@ -9,14 +6,26 @@ use anyhow::Result; #[inline] pub fn modexp_hint(data: &[u64]) -> Result> { let mut pos = 0; - let base = read_field(data, &mut pos)?; - let exp = read_field(data, &mut pos)?; - let modulus = read_field(data, &mut pos)?; + let (base, base_len) = read_field_bytes(data, &mut pos)?; + let (exp, exp_len) = read_field_bytes(data, &mut pos)?; + let (modulus, modulus_len) = read_field_bytes(data, &mut pos)?; - validate_hint_length(data, pos, "MODEXP")?; + // validate_hint_length(data, pos, "MODEXP")?; let mut hints = Vec::new(); - zisklib::modexp_u64(base, exp, modulus, &mut hints); + let mut result = vec![0u8; modulus_len]; + unsafe { + zisklib::modexp_bytes_c( + base.as_ptr(), + base_len, + exp.as_ptr(), + exp_len, + modulus.as_ptr(), + modulus_len, + result.as_mut_ptr(), + &mut hints, + ); + } Ok(hints) } From eafcc16a3aa4a6570893cf646f09767f8b2cf772 Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 23 Jan 2026 09:57:27 +0100 Subject: [PATCH 315/782] Add poseidon2 hints support to syscall_poseidon2() --- ziskos-hints/Cargo.toml | 1 + ziskos/entrypoint/Cargo.toml | 1 + ziskos/entrypoint/src/syscalls/poseidon2.rs | 21 ++++++++++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index e6002a67d..86e7f9aee 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -33,6 +33,7 @@ serde = { workspace = true, features = ["derive"] } bincode = "2.0" paste = "1.0" sha2 = { workspace = true } +fields = { workspace = true } # zisk-os specific dependencies elliptic-curve = "0.13.8" diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index a486f9e54..ab5842f3b 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -25,6 +25,7 @@ tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } bincode = "2.0" sha2 = { workspace = true } +fields = { workspace = true } [features] default = [] diff --git a/ziskos/entrypoint/src/syscalls/poseidon2.rs b/ziskos/entrypoint/src/syscalls/poseidon2.rs index db1ce5c8f..ab64e83b4 100644 --- a/ziskos/entrypoint/src/syscalls/poseidon2.rs +++ b/ziskos/entrypoint/src/syscalls/poseidon2.rs @@ -6,6 +6,9 @@ use core::arch::asm; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] use crate::ziskos_syscall; +#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] +use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; + /// Executes the Poseidon2 permutation on the given state. /// /// The `Poseidon2` system call executes a CSR set on a custom port. When transpiling from RISC-V to Zisk, @@ -27,5 +30,21 @@ pub extern "C" fn syscall_poseidon2( #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x812, state); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + // Get a mutable reference to the state + let state: &mut [u64; 16] = unsafe { &mut *(state) }; + + // Call poseidon2, mapping u64 to Goldilocks elements + let state_gl = state.map(Goldilocks::new); + let new_state_gl = poseidon2_hash::(&state_gl); + for (i, d) in state.iter_mut().enumerate() { + *d = new_state_gl[i].as_canonical_u64(); + } + + #[cfg(feature = "hints")] + { + // For hints, we store the new state in the hints vector + hints.extend_from_slice(state); + } + } } From 9195fa2d06a254252039b7132381bebc40e40092 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:13:45 +0000 Subject: [PATCH 316/782] fixed bn254 pairing check --- common/src/hints.rs | 13 ++++++ precompiles/hints/src/hints_processor.rs | 4 ++ ziskos-hints/src/handlers/bn254.rs | 53 ++++++++++++++++-------- ziskos-hints/src/handlers/keccak256.rs | 18 ++++++++ ziskos-hints/src/handlers/kzg.rs | 7 ++-- ziskos-hints/src/handlers/mod.rs | 1 + 6 files changed, 75 insertions(+), 21 deletions(-) create mode 100644 ziskos-hints/src/handlers/keccak256.rs diff --git a/common/src/hints.rs b/common/src/hints.rs index 339f411b8..50b6e8b0c 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -86,6 +86,9 @@ const HINT_MODEXP: u32 = 0x0500; // KZG hint codes const HINT_VERIFY_KZG_PROOF: u32 = 0x0600; +// Keccak256 hint codes +const HINT_KECCAK256: u32 = 0x0700; + /// Control code variants for stream control. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u32)] @@ -171,6 +174,10 @@ pub enum BuiltInHint { // KZG hint types. /// Verify KZG proof. VerifyKzgProof = HINT_VERIFY_KZG_PROOF, + + // Keccak256 hint types. + /// Compute Keccak-256 hash. + Keccak256 = HINT_KECCAK256, } impl Display for BuiltInHint { @@ -197,6 +204,8 @@ impl Display for BuiltInHint { BuiltInHint::ModExp => "MODEXP", // KZG Hint BuiltInHint::VerifyKzgProof => "VERIFY_KZG_PROOF", + // Keccak256 Hint + BuiltInHint::Keccak256 => "KECCAK256", }; write!(f, "{} ({:#x})", name, *self as u32) @@ -229,6 +238,8 @@ impl TryFrom for BuiltInHint { HINT_MODEXP => Ok(Self::ModExp), // KZG Hint HINT_VERIFY_KZG_PROOF => Ok(Self::VerifyKzgProof), + // Keccak256 Hint + HINT_KECCAK256 => Ok(Self::Keccak256), _ => Err(anyhow::anyhow!("Invalid built-in hint code: {:#x}", value)), } } @@ -308,6 +319,8 @@ impl HintCode { HintCode::BuiltIn(BuiltInHint::ModExp) => HINT_MODEXP, // KZG Hint HintCode::BuiltIn(BuiltInHint::VerifyKzgProof) => HINT_VERIFY_KZG_PROOF, + // Keccak256 Hint + HintCode::BuiltIn(BuiltInHint::Keccak256) => HINT_KECCAK256, // Custom Hints HintCode::Custom(code) => code, diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index adfbedaf2..679a4a769 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -20,6 +20,7 @@ use ziskos_hints::handlers::bls381::{ use ziskos_hints::handlers::bn254::{ bn254_g1_add_hint, bn254_g1_mul_hint, bn254_pairing_check_hint, }; +use ziskos_hints::handlers::keccak256::keccak256_hint; use ziskos_hints::handlers::kzg::verify_kzg_proof_hint; use ziskos_hints::handlers::modexp::modexp_hint; use ziskos_hints::handlers::secp256k1::secp256k1_ecrecover_hint; @@ -616,6 +617,9 @@ impl HintsProcessor { // KZG Hint Codes BuiltInHint::VerifyKzgProof => verify_kzg_proof_hint(&data), // TODO: check + + // Keccak256 Hint Codes + BuiltInHint::Keccak256 => keccak256_hint(&data, data_len_bytes), } } } diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 9fbc0a938..2a5950c8c 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -53,20 +53,21 @@ pub fn bn254_g1_mul_hint(data: &[u64]) -> Result> { #[inline] pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { // --------------------------------------------------------------------- - // Input format: + // Input format (INTERLEAVED): // // data[0] = num_pairs - // data[1 ..] = G1 points (num_pairs * 8 u64s) - // data[1 + g1_len ..] = G2 points (num_pairs * 16 u64s) + // data[1 ..] = Interleaved pairs: [G1[0], G2[0], G1[1], G2[1], ...] // // Each G1 point: 8 u64 = 64 bytes // Each G2 point: 16 u64 = 128 bytes + // Each pair: 24 u64 = 192 bytes // // All data is interpreted in *native-endian* u64 layout. // --------------------------------------------------------------------- const G1_WORDS: usize = 8; const G2_WORDS: usize = 16; + const PAIR_WORDS: usize = G1_WORDS + G2_WORDS; if data.is_empty() { anyhow::bail!("BN254_PAIRING_CHECK: data is empty"); @@ -79,14 +80,12 @@ pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { anyhow::bail!("BN254_PAIRING_CHECK: num_pairs is zero"); } - let expected_len = 1 + num_pairs * (G1_WORDS + G2_WORDS); + let expected_len = 1 + num_pairs * PAIR_WORDS; validate_hint_length(data, expected_len, "PAIRING_BATCH_BN254")?; - // Split input buffer + // Extract interleaved pairs // --------------------------------------------------------------------- - // Reinterpret &[u64] as grouped byte slices (zero-copy) - // // SAFETY INVARIANTS: // - Length has been validated exactly // - &[u64] memory is contiguous @@ -95,19 +94,30 @@ pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { // - Endianness is intentionally native // --------------------------------------------------------------------- - let g1_start = 1; - let g1_end = g1_start + num_pairs * G1_WORDS; - let g2_start = g1_end; - let g2_end = g2_start + num_pairs * G2_WORDS; + let pairs_data = &data[1..]; + let mut g1_points = Vec::with_capacity(num_pairs); + let mut g2_points = Vec::with_capacity(num_pairs); + + for i in 0..num_pairs { + let pair_start = i * PAIR_WORDS; + let g1_start = pair_start; + let g2_start = pair_start + G1_WORDS; - let g1_words = &data[g1_start..g1_end]; - let g2_words = &data[g2_start..g2_end]; + let g1_words = &pairs_data[g1_start..g1_start + G1_WORDS]; + let g2_words = &pairs_data[g2_start..g2_start + G2_WORDS]; - let g1_points: &[[u8; G1_WORDS * 8]] = - unsafe { reinterpret_u64_as_bytes::<{ G1_WORDS * 8 }>(g1_words, num_pairs) }; + let g1_bytes = + unsafe { std::slice::from_raw_parts(g1_words.as_ptr() as *const u8, G1_WORDS * 8) }; + let g2_bytes = + unsafe { std::slice::from_raw_parts(g2_words.as_ptr() as *const u8, G2_WORDS * 8) }; - let g2_points: &[[u8; G2_WORDS * 8]] = - unsafe { reinterpret_u64_as_bytes::<{ G2_WORDS * 8 }>(g2_words, num_pairs) }; + g1_points.push(g1_bytes); + g2_points.push(g2_bytes); + } + + for i in 0..num_pairs { + println!("[{}] G1 Point: {:x?}\n G2 Point: {:x?}", i, g1_points[i], g2_points[i]); + } // Build arrays of raw pointers for the FFI call let g1_ptrs: Vec<*const u8> = g1_points.iter().map(|p| p.as_ptr()).collect(); @@ -115,7 +125,14 @@ pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { let mut hints = Vec::new(); unsafe { - zisklib::bn254_pairing_check_c(g1_ptrs.as_ptr(), g2_ptrs.as_ptr(), num_pairs, &mut hints); + let result = zisklib::bn254_pairing_check_c( + g1_ptrs.as_ptr(), + g2_ptrs.as_ptr(), + num_pairs, + &mut hints, + ); + + println!("BN254_PAIRING_CHECK: result = {}", result); } Ok(hints) diff --git a/ziskos-hints/src/handlers/keccak256.rs b/ziskos-hints/src/handlers/keccak256.rs new file mode 100644 index 000000000..e89446ec8 --- /dev/null +++ b/ziskos-hints/src/handlers/keccak256.rs @@ -0,0 +1,18 @@ +use crate::{handlers::validate_hint_min_length, zisklib}; + +use anyhow::Result; + +/// Processes an `HINT_KECCAK256` hint. +#[inline] +pub fn keccak256_hint(data: &[u64], data_len_bytes: usize) -> Result> { + let data_len_u64 = data_len_bytes.div_ceil(8); + + validate_hint_min_length(data, data_len_u64, "HINT_KECCAK256")?; + + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data_len_bytes) }; + + let mut hints = Vec::new(); + zisklib::keccak256(bytes, &mut hints); + + Ok(hints) +} diff --git a/ziskos-hints/src/handlers/kzg.rs b/ziskos-hints/src/handlers/kzg.rs index c18dccedb..cd60a4539 100644 --- a/ziskos-hints/src/handlers/kzg.rs +++ b/ziskos-hints/src/handlers/kzg.rs @@ -19,15 +19,16 @@ pub fn verify_kzg_proof_hint(data: &[u64]) -> Result> { bytes[PROOF_OFFSET..PROOF_OFFSET + PROOF_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - unsafe { + let result = unsafe { zisklib::verify_kzg_proof_c( z.as_ptr(), y.as_ptr(), commitment.as_ptr(), proof.as_ptr(), &mut hints, - ); - } + ) + }; + println!("KZG result: {}", result); Ok(hints) } diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 13eea02b9..9f36c04c5 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -1,5 +1,6 @@ pub mod bls381; pub mod bn254; +pub mod keccak256; pub mod kzg; pub mod modexp; pub mod secp256k1; From 3f2430761c83dd10df800a501c7a323bc4cfc7c4 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:47:42 +0000 Subject: [PATCH 317/782] added either aligned or unaligned hints parsing fn --- Cargo.lock | 2 + common/src/hints.rs | 76 ++++++++++++++++++++++++ precompiles/hints/src/hints_processor.rs | 24 +++++--- 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30af4a5bd..8a65e4503 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6316,6 +6316,7 @@ version = "0.16.0" dependencies = [ "bincode", "cfg-if", + "fields", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -6338,6 +6339,7 @@ dependencies = [ "bincode", "cfg-if", "elliptic-curve", + "fields", "getrandom 0.2.17", "k256", "lazy_static", diff --git a/common/src/hints.rs b/common/src/hints.rs index 50b6e8b0c..244dff07c 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -413,4 +413,80 @@ impl PrecompileHint { Ok(PrecompileHint { hint_code, is_passthrough, data, data_len_bytes: length as usize }) } + + /// Parses a [`PrecompileHint`] from a slice of `u64` values at the given byte index. + /// + /// # Arguments + /// + /// * `slice` - The source slice containing concatenated hints + /// * `idx` - The **byte index** where the hint header starts + /// * `allow_custom` - If true, unknown codes create Custom variant; if false, return error + /// + /// # Returns + /// + /// * `Ok(PrecompileHint)` - Successfully parsed hint + /// * `Err` - If the slice is too short or the index is out of bounds + #[inline(always)] + pub fn from_unaligned_u64_slice(slice: &[u64], idx: usize, allow_custom: bool) -> Result { + const HEADER_SIZE: usize = 8; + + // Convert u64 slice to byte slice + let byte_slice = + unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const u8, slice.len() * 8) }; + + if idx + HEADER_SIZE > byte_slice.len() { + return Err(anyhow::anyhow!("Slice too short for header at byte index {}", idx)); + } + + // Read 8-byte header as u64 (little-endian) + let header = u64::from_le_bytes(byte_slice[idx..idx + HEADER_SIZE].try_into().unwrap()); + + // Extract length from lower 32 bits (length is in bytes) + let length = (header & 0xFFFFFFFF) as usize; + + // Calculate how many u64s are needed to hold length bytes + let num_u64s = length.div_ceil(8); + println!("Header: {:#x}, Length: {}, Num u64s: {}", header, length, num_u64s); + anyhow::ensure!( + idx + HEADER_SIZE + length <= byte_slice.len(), + "Slice too short for hint data {}: expected {} bytes, got {}", + (header >> 32) as u32, + length, + byte_slice.len() - idx - HEADER_SIZE + ); + + // Extract hint code from upper 32 bits + let hint_code_32 = (header >> 32) as u32; + // Extract pass-through flag from bit 31 (MSB) - shift is faster than mask + let is_passthrough = hint_code_32 >> 31 != 0; + // Extract the actual hint code from bits 0-30 - mask is optimal + let hint_code_value = hint_code_32 & 0x7FFFFFFF; + + let hint_code = if allow_custom { + HintCode::try_from(hint_code_value).unwrap_or(HintCode::Custom(hint_code_value)) + } else { + HintCode::try_from(hint_code_value)? + }; + + // Extract data bytes and convert to u64 with optimal performance + let data_bytes = &byte_slice[idx + HEADER_SIZE..idx + HEADER_SIZE + length]; + let mut data = Vec::with_capacity(num_u64s); + + let mut offset = 0; + // Process full u64s with direct unaligned reads + while offset + 8 <= data_bytes.len() { + let value = unsafe { (data_bytes.as_ptr().add(offset) as *const u64).read_unaligned() }; + data.push(u64::from_le(value)); + offset += 8; + } + + // Handle last partial u64 if any + if offset < data_bytes.len() { + let mut bytes = [0u8; 8]; + bytes[..data_bytes.len() - offset].copy_from_slice(&data_bytes[offset..]); + data.push(u64::from_le_bytes(bytes)); + } + + Ok(PrecompileHint { hint_code, is_passthrough, data, data_len_bytes: length }) + } } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 679a4a769..192842a3e 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -231,16 +231,25 @@ impl HintsProcessor { /// * `Ok(false)` - Batch processed successfully, no CTRL_END /// * `Err` - If a previous error occurred or hints are malformed pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { + const ALLOW_UNALIGNED: bool = false; + const HEADER_SIZE: usize = if ALLOW_UNALIGNED { 8 } else { 1 }; + let mut has_ctrl_end = false; // Parse hints and dispatch to pool let mut idx = 0; - while idx < hints.len() { + while idx < hints.len() * HEADER_SIZE { // Check for error before processing each hint if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to previous error")); } - let hint = PrecompileHint::from_u64_slice(hints, idx, true)?; + let hint = if ALLOW_UNALIGNED { + PrecompileHint::from_unaligned_u64_slice(hints, idx, true)? + } else { + PrecompileHint::from_u64_slice(hints, idx, true)? + }; + + // println!("Received Hint <= {:?}:", hint); self.num_hint.fetch_add(1, Ordering::Relaxed); @@ -256,7 +265,8 @@ impl HintsProcessor { } } - let length = hint.data.len(); + let length = + if ALLOW_UNALIGNED { hint.data_len_bytes } else { hint.data.len() } + HEADER_SIZE; if let Some(stats) = &self.stats { stats.lock().unwrap().entry(hint.hint_code).and_modify(|c| *c += 1).or_insert(1); @@ -280,14 +290,14 @@ impl HintsProcessor { // Reset global sequence and buffer at stream start self.reset(); // Control hint only; skip processing - idx += length + 1; + idx += length; continue; } HintCode::Ctrl(CtrlHint::End) => { // Control hint only; wait for completion then set flag self.wait_for_completion()?; has_ctrl_end = true; - idx += length + 1; + idx += length; debug!("CTRL_END received, all hints processed"); @@ -332,7 +342,7 @@ impl HintsProcessor { // Use notify_all since wait_for_completion also waits on this condvar self.state.drain_signal.notify_all(); // Continue to next hint without spawning worker - idx += length + 1; + idx += length; continue; } else { queue.buffer.push_back(None); @@ -348,7 +358,7 @@ impl HintsProcessor { Self::worker_thread(state, hint, generation, seq_id, custom_handlers); }); - idx += length + 1; + idx += length; } if has_ctrl_end { From 37e4b121622d86cf42b8872f4739b5024086948a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Fri, 23 Jan 2026 08:56:09 +0000 Subject: [PATCH 318/782] BLS errors fixed --- .../src/zisklib/lib/array_lib/modexp.rs | 317 +++++++------ .../src/zisklib/lib/bls12_381/constants.rs | 49 +- .../src/zisklib/lib/bls12_381/curve.rs | 436 ++++++++++-------- .../src/zisklib/lib/bls12_381/fr.rs | 12 + .../src/zisklib/lib/bls12_381/kzg.rs | 209 +++++---- .../src/zisklib/lib/bls12_381/pairing.rs | 223 +++++---- .../src/zisklib/lib/bls12_381/twist.rs | 394 +++++++++------- 7 files changed, 954 insertions(+), 686 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs index 38baff414..5b58d607d 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs @@ -41,6 +41,11 @@ pub fn modexp( } } + // If modulus == 0, return zeros + if len_m == 1 && modulus[0].is_zero() { + return vec![U256::ZERO]; + } + // If modulus == 1, then base^exp (mod 1) is always 0 if len_m == 1 && modulus[0].is_one() { return vec![U256::ZERO]; @@ -64,182 +69,178 @@ pub fn modexp( } // We can assume from now on that base,modulus > 1 and exp > 0 - - // There are two versions: - // - If len(modulus) == 1, we can use short reductions - // - If len(modulus) > 1, we must use long reductions if len_m == 1 { - let modulus = &modulus[0]; - - // Compute base = base (mod modulus) - let base = rem_short_init( + modexp_short( base, - modulus, + exp, + &modulus[0], #[cfg(feature = "hints")] hints, - ); - - // Hint exponent bits - let (len, bits) = fcall_bin_decomp( + ) + } else { + modexp_long( + base, exp, + modulus, #[cfg(feature = "hints")] hints, - ); + ) + } +} + +/// Short modexp when modulus fits in a single U256 +fn modexp_short( + base: &[U256], + exp: &[u64], + modulus: &U256, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Vec { + let len_e = exp.len(); - // We should recompose the exponent from bits to verify correctness - let mut rec_exp = vec![0u64; len_e]; + // Compute base = base (mod modulus) + let base = rem_short_init( + base, + modulus, + #[cfg(feature = "hints")] + hints, + ); - // Recompose the MSB - let bits_pos = len - 1; - let limb_idx = bits_pos / 64; - let bit_in_limb = bits_pos % 64; - rec_exp[limb_idx] = 1u64 << bit_in_limb; + // Hint exponent bits + let (len, bits) = fcall_bin_decomp( + exp, + #[cfg(feature = "hints")] + hints, + ); - // Scratch space - let mut scratch = ShortScratch::new(); + // We should recompose the exponent from bits to verify correctness + let mut rec_exp = vec![0u64; len_e]; - // Initialize out = base - let mut out = base; - for (bit_idx, &bit) in bits.iter().enumerate().skip(1) { - if out.is_zero() { - return vec![U256::ZERO]; - } + // Recompose the MSB + let bits_pos = len - 1; + let limb_idx = bits_pos / 64; + let bit_in_limb = bits_pos % 64; + rec_exp[limb_idx] = 1u64 << bit_in_limb; - // Compute out = out² (mod modulus) - out = square_and_reduce_short( - &out, - modulus, - &mut scratch, - #[cfg(feature = "hints")] - hints, - ); + // Scratch space + let mut scratch = ShortScratch::new(); - if bit == 1 { - // Compute out = (out * base) (mod modulus); - out = mul_and_reduce_short( - &out, - &base, - modulus, - &mut scratch, - #[cfg(feature = "hints")] - hints, - ); - // Recompose the exponent - let bits_pos = len - 1 - bit_idx; - let limb_idx = bits_pos / 64; - let bit_in_limb = bits_pos % 64; - rec_exp[limb_idx] |= 1u64 << bit_in_limb; - } + // Initialize out = base + let mut out = base; + for (bit_idx, &bit) in bits.iter().enumerate().skip(1) { + if out.is_zero() { + return vec![U256::ZERO]; } - assert_eq!(rec_exp[..], *exp, "Exponent decomposition mismatch"); - - vec![out] - } else { - // Compute base = base (mod modulus) - let base = rem_long_init( - base, + // Compute out = out² (mod modulus) + out = square_and_reduce_short( + &out, modulus, + &mut scratch, #[cfg(feature = "hints")] hints, ); - // Hint exponent bits - let (len, bits) = fcall_bin_decomp( - exp, - #[cfg(feature = "hints")] - hints, - ); - - // We should recompose the exponent from bits to verify correctness - let mut rec_exp = vec![0u64; len_e]; - - // Recompose the MSB - let bits_pos = len - 1; - let limb_idx = bits_pos / 64; - let bit_in_limb = bits_pos % 64; - rec_exp[limb_idx] = 1u64 << bit_in_limb; - - // Scratch space - let mut scratch = LongScratch::new(len_m); - - // Initialize out = base - let mut out = base.clone(); - for (bit_idx, &bit) in bits.iter().enumerate().skip(1) { - if out.len() == 1 && out[0].is_zero() { - return vec![U256::ZERO]; - } - - // Compute out = out² (mod modulus) - out = square_and_reduce_long( + if bit == 1 { + // Compute out = (out * base) (mod modulus) + out = mul_and_reduce_short( &out, + &base, modulus, &mut scratch, #[cfg(feature = "hints")] hints, ); - - if bit == 1 { - // Compute out = (out * base) (mod modulus); - out = mul_and_reduce_long( - &out, - &base, - modulus, - &mut scratch, - #[cfg(feature = "hints")] - hints, - ); - // Recompose the exponent - let bits_pos = len - 1 - bit_idx; - let limb_idx = bits_pos / 64; - let bit_in_limb = bits_pos % 64; - rec_exp[limb_idx] |= 1u64 << bit_in_limb; - } + // Recompose the exponent + let bits_pos = len - 1 - bit_idx; + let limb_idx = bits_pos / 64; + let bit_in_limb = bits_pos % 64; + rec_exp[limb_idx] |= 1u64 << bit_in_limb; } + } - assert_eq!(rec_exp[..], *exp, "Exponent decomposition mismatch"); + assert_eq!(rec_exp[..], *exp, "Exponent decomposition mismatch"); - out - } + vec![out] } -pub fn modexp_u64( - base: &[u64], +/// Long modexp when modulus requires multiple U256 limbs +fn modexp_long( + base: &[U256], exp: &[u64], - modulus: &[u64], + modulus: &[U256], #[cfg(feature = "hints")] hints: &mut Vec, -) -> Vec { - // Round up to multiple of 4 - let base_len = (base.len() + 3) & !3; - let modulus_len = (modulus.len() + 3) & !3; - - let mut base_padded = vec![0u64; base_len]; - let mut modulus_padded = vec![0u64; modulus_len]; - - base_padded[..base.len()].copy_from_slice(base); - modulus_padded[..modulus.len()].copy_from_slice(modulus); +) -> Vec { + let len_e = exp.len(); + let len_m = modulus.len(); - // Convert u64 arrays to U256 chunks - let base_u256 = U256::flat_to_slice(&base_padded); - let modulus_u256 = U256::flat_to_slice(&modulus_padded); + // Compute base = base (mod modulus) + let base = rem_long_init( + base, + modulus, + #[cfg(feature = "hints")] + hints, + ); - // Call the main modexp function - let result_u256 = modexp( - base_u256, + // Hint exponent bits + let (len, bits) = fcall_bin_decomp( exp, - modulus_u256, #[cfg(feature = "hints")] hints, ); - // Convert result back to u64 array - U256::slice_to_flat(&result_u256).to_vec() + // We should recompose the exponent from bits to verify correctness + let mut rec_exp = vec![0u64; len_e]; + + // Recompose the MSB + let bits_pos = len - 1; + let limb_idx = bits_pos / 64; + let bit_in_limb = bits_pos % 64; + rec_exp[limb_idx] = 1u64 << bit_in_limb; + + // Scratch space + let mut scratch = LongScratch::new(len_m); + + // Initialize out = base + let mut out = base.clone(); + for (bit_idx, &bit) in bits.iter().enumerate().skip(1) { + if out.len() == 1 && out[0].is_zero() { + return vec![U256::ZERO]; + } + + // Compute out = out² (mod modulus) + out = square_and_reduce_long( + &out, + modulus, + &mut scratch, + #[cfg(feature = "hints")] + hints, + ); + + if bit == 1 { + // Compute out = (out * base) (mod modulus) + out = mul_and_reduce_long( + &out, + &base, + modulus, + &mut scratch, + #[cfg(feature = "hints")] + hints, + ); + // Recompose the exponent + let bits_pos = len - 1 - bit_idx; + let limb_idx = bits_pos / 64; + let bit_in_limb = bits_pos % 64; + rec_exp[limb_idx] |= 1u64 << bit_in_limb; + } + } + + assert_eq!(rec_exp[..], *exp, "Exponent decomposition mismatch"); + + out } /// Compute modular exponentiation from big-endian byte arrays /// -/// This function is designed to patch `fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Vec` -/// /// ### Safety /// /// The caller must ensure that: @@ -265,31 +266,22 @@ pub unsafe extern "C" fn modexp_bytes_c( let exp_bytes = std::slice::from_raw_parts(exp_ptr, exp_len); let modulus_bytes = std::slice::from_raw_parts(modulus_ptr, modulus_len); - // Convert big-endian bytes to little-endian u64 arrays - let base_u64 = bytes_be_to_u64_le(base_bytes); + // Convert from big-endian bytes to little-endian u64/U256 arrays + let base_u256 = bytes_be_to_u256_le(base_bytes); let exp_u64 = bytes_be_to_u64_le(exp_bytes); - let modulus_u64 = bytes_be_to_u64_le(modulus_bytes); - - // Handle empty/zero cases - if modulus_u64.is_empty() || (modulus_u64.len() == 1 && modulus_u64[0] == 0) { - // modulus == 0: return all zeros - let result = std::slice::from_raw_parts_mut(result_ptr, modulus_len); - result.fill(0); - return modulus_len; - } + let modulus_u256 = bytes_be_to_u256_le(modulus_bytes); - // Call the u64 version - let result_u64 = modexp_u64( - &base_u64, + let result_u256 = modexp( + &base_u256, &exp_u64, - &modulus_u64, + &modulus_u256, #[cfg(feature = "hints")] hints, ); // Convert result back to big-endian bytes with proper length let result = std::slice::from_raw_parts_mut(result_ptr, modulus_len); - u64_le_to_bytes_be(&result_u64, result); + u256_le_to_bytes_be(&result_u256, result); modulus_len } @@ -308,11 +300,9 @@ fn bytes_be_to_u64_le(bytes: &[u8]) -> Vec { return vec![0]; } - // Calculate number of u64 limbs needed - let num_limbs = (bytes.len() + 7) / 8; + // Process bytes into u64 limbs + let num_limbs = bytes.len().div_ceil(8); let mut result = vec![0u64; num_limbs]; - - // Process bytes from the end (least significant) to the beginning for (i, &byte) in bytes.iter().rev().enumerate() { let limb_idx = i / 8; let byte_idx = i % 8; @@ -322,18 +312,27 @@ fn bytes_be_to_u64_le(bytes: &[u8]) -> Vec { result } -/// Convert little-endian u64 array to big-endian bytes with specified length -fn u64_le_to_bytes_be(limbs: &[u64], output: &mut [u8]) { +/// Convert big-endian bytes to little-endian U256 array +fn bytes_be_to_u256_le(bytes: &[u8]) -> Vec { + let u64_le = bytes_be_to_u64_le(bytes); + + // Pad to multiple of 4 u64s + let padded_len = u64_le.len().next_multiple_of(4); + let mut padded = vec![0u64; padded_len]; + padded[..u64_le.len()].copy_from_slice(&u64_le); + + U256::flat_to_slice(&padded).to_vec() +} + +/// Convert little-endian U256 array to big-endian bytes +fn u256_le_to_bytes_be(limbs: &[U256], output: &mut [u8]) { + let flat = U256::slice_to_flat(limbs); let out_len = output.len(); output.fill(0); - // Calculate how many bytes the result actually has - let result_bytes = limbs.len() * 8; - - for (i, &limb) in limbs.iter().enumerate() { + for (i, &limb) in flat.iter().enumerate() { for j in 0..8 { let byte_val = ((limb >> (j * 8)) & 0xFF) as u8; - // Position from the end of the result let pos_from_end = i * 8 + j; if pos_from_end < out_len { output[out_len - 1 - pos_from_end] = byte_val; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs index eeea66734..83e7cb2b1 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs @@ -13,18 +13,10 @@ pub const E_B: [u64; 6] = [0x4, 0, 0, 0, 0, 0]; pub const ETWISTED_B: [u64; 12] = [0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0]; /// Identity element in G1 -pub const G1_IDENTITY: [u64; 12] = { - let mut tmp = [0u64; 12]; - tmp[6] = 1; - tmp -}; +pub const G1_IDENTITY: [u64; 12] = [0; 12]; /// Identity element in G2 -pub const G2_IDENTITY: [u64; 24] = { - let mut tmp = [0u64; 24]; - tmp[12] = 1; - tmp -}; +pub const G2_IDENTITY: [u64; 24] = [0; 24]; /// G1 generator point for BLS12-381 pub const G1_GENERATOR: [u64; 12] = [ @@ -255,14 +247,31 @@ pub const FROBENIUS_GAMMA25: [u64; 6] = [ 0x1A01_11EA_397F_E699, ]; -/// Trusted setup G2 point `[τ]₂` from the Ethereum KZG ceremony (compressed format) -/// Hex: b5bfd7dd8cdeb128843bc287230af38926187075cbfbefa81009a2ce615ac53d2914e5870cb452d2afaaab24f3499f72185cbfee53492714734429b7b38608e23926c911cceceac9a36851477ba4c60b087041de621000edc98edada20c1def2 -// TODO: I can save computations by decompressing this -pub const TRUSTED_SETUP_TAU_G2_COMPRESSED: [u8; 96] = [ - 0xb5, 0xbf, 0xd7, 0xdd, 0x8c, 0xde, 0xb1, 0x28, 0x84, 0x3b, 0xc2, 0x87, 0x23, 0x0a, 0xf3, 0x89, - 0x26, 0x18, 0x70, 0x75, 0xcb, 0xfb, 0xef, 0xa8, 0x10, 0x09, 0xa2, 0xce, 0x61, 0x5a, 0xc5, 0x3d, - 0x29, 0x14, 0xe5, 0x87, 0x0c, 0xb4, 0x52, 0xd2, 0xaf, 0xaa, 0xab, 0x24, 0xf3, 0x49, 0x9f, 0x72, - 0x18, 0x5c, 0xbf, 0xee, 0x53, 0x49, 0x27, 0x14, 0x73, 0x44, 0x29, 0xb7, 0xb3, 0x86, 0x08, 0xe2, - 0x39, 0x26, 0xc9, 0x11, 0xcc, 0xec, 0xea, 0xc9, 0xa3, 0x68, 0x51, 0x47, 0x7b, 0xa4, 0xc6, 0x0b, - 0x08, 0x70, 0x41, 0xde, 0x62, 0x10, 0x00, 0xed, 0xc9, 0x8e, 0xda, 0xda, 0x20, 0xc1, 0xde, 0xf2, +/// Trusted setup G2 point `[τ]₂ := τ·G2` from the Ethereum KZG ceremony (uncompressed format) +/// For reference, see: https://github.com/ethereum/kzg-ceremony +pub const TRUSTED_SETUP_TAU_G2: [u64; 24] = [ + 0xc98edada20c1def2, + 0x087041de621000ed, + 0xa36851477ba4c60b, + 0x3926c911cceceac9, + 0x734429b7b38608e2, + 0x185cbfee53492714, + 0xafaaab24f3499f72, + 0x2914e5870cb452d2, + 0x1009a2ce615ac53d, + 0x26187075cbfbefa8, + 0x843bc287230af389, + 0x15bfd7dd8cdeb128, + 0xee689bfbbb832a99, + 0x4ce26d105941f383, + 0xe82451a496a9c979, + 0x131569490e28de18, + 0xd7d5ee8599d1fca2, + 0x014353bdb96b626d, + 0x23048ef30d0a154f, + 0x9495346f3d7ac9cd, + 0xda5ed1ba9bfa0789, + 0xef79de09fc63671f, + 0x03432fcae0181b4b, + 0x1666c54b0a325295, ]; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs index 647b387eb..8f0b41979 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs @@ -14,9 +14,23 @@ use super::{ add_fp_bls12_381, mul_fp_bls12_381, neg_fp_bls12_381, sqrt_fp_bls12_381, square_fp_bls12_381, }, + fr::scalar_bytes_be_to_u64_le, }; -/// Decompresses a G1 point on the BLS12-381 curve from 48 bytes (compressed format). +// TODO: Check what happens if scalar or ecc coordinates are bigger than the field size + +/// G1 add result codes +pub const G1_ADD_SUCCESS: u8 = 0; +pub const G1_ADD_SUCCESS_INFINITY: u8 = 1; +pub const G1_ADD_ERR_NOT_ON_CURVE: u8 = 2; + +/// G1 MSM result codes +pub const G1_MSM_SUCCESS: u8 = 0; +pub const G1_MSM_SUCCESS_INFINITY: u8 = 1; +pub const G1_MSM_ERR_NOT_ON_CURVE: u8 = 2; +pub const G1_MSM_ERR_NOT_IN_SUBGROUP: u8 = 3; + +/// Decompresses a point on the BLS12-381 curve from 48 bytes /// /// Format: Big-endian x-coordinate with flag bits in the top 3 bits of the first byte: /// - Bit 7 (0x80): Compression flag (must be 1 for compressed) @@ -25,12 +39,12 @@ use super::{ pub fn decompress_bls12_381( input: &[u8; 48], #[cfg(feature = "hints")] hints: &mut Vec, -) -> Result<([u64; 12], bool), &'static str> { +) -> Result<[u64; 12], &'static str> { let flags = input[0]; // Check compression bit if (flags & 0x80) == 0 { - return Err("decompress_bls12_381: Expected compressed point (0x80 flag not set)"); + return Err("Expected compressed point"); } // Check infinity bit @@ -44,7 +58,7 @@ pub fn decompress_bls12_381( return Err("Invalid infinity encoding"); } } - return Ok((G1_IDENTITY, true)); + return Ok(G1_IDENTITY); } // Extract sign bit @@ -111,7 +125,7 @@ pub fn decompress_bls12_381( let mut result = [0u64; 12]; result[0..6].copy_from_slice(&x); result[6..12].copy_from_slice(&final_y); - Ok((result, false)) + Ok(result) } /// Check if a non-zero point `p` is on the BLS12-381 curve @@ -239,6 +253,66 @@ pub fn add_bls12_381( result } +/// Adds two points `p1` and `p2` on the BLS12-381 curve +pub fn add_complete_bls12_381( + p1: &[u64; 12], + p2: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Result<[u64; 12], u8> { + let p1_is_inf = *p1 == G1_IDENTITY; + let p2_is_inf = *p2 == G1_IDENTITY; + + // Handle identity cases + if p1_is_inf && p2_is_inf { + return Ok(G1_IDENTITY); + } + if p1_is_inf { + if !is_on_curve_bls12_381( + p2, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G1_ADD_ERR_NOT_ON_CURVE); + } + return Ok(*p2); + } + + if p2_is_inf { + if !is_on_curve_bls12_381( + p1, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G1_ADD_ERR_NOT_ON_CURVE); + } + return Ok(*p1); + } + + // Both points are non-identity, validate both are on curve + if !is_on_curve_bls12_381( + p1, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G1_ADD_ERR_NOT_ON_CURVE); + } + if !is_on_curve_bls12_381( + p2, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G1_ADD_ERR_NOT_ON_CURVE); + } + + // Otherwise, perform regular addition + Ok(add_bls12_381( + p1, + p2, + #[cfg(feature = "hints")] + hints, + )) +} + /// Negation of a non-zero point `p` on the BLS12-381 curve pub fn neg_bls12_381(p: &[u64; 12], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 12] { let x: [u64; 6] = p[0..6].try_into().unwrap(); @@ -298,23 +372,41 @@ pub fn sub_bls12_381( ) } -// pub fn sub_complete_bls12_381(p1: &[u64; 12], p2: &[u64; 12]) -> [u64; 12] { -// if p1 == G1_IDENTITY { -// return neg_bls12_381(p2); -// } - -// let x2: [u64; 6] = p2[0..6].try_into().unwrap(); -// let y2: [u64; 6] = p2[6..12].try_into().unwrap(); - -// // P1 - P2 = P1 + (-P2) -// let y2_neg = neg_fp_bls12_381(&y2); +/// Subtraction of two points `p1` and `p2` on the BLS12-381 curve +pub fn sub_complete_bls12_381( + p1: &[u64; 12], + p2: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { + let p1_is_inf = *p1 == G1_IDENTITY; + let p2_is_inf = *p2 == G1_IDENTITY; -// let mut p2_neg = [0u64; 12]; -// p2_neg[0..6].copy_from_slice(&x2); -// p2_neg[6..12].copy_from_slice(&y2_neg); + // Handle identity cases + if p1_is_inf && p2_is_inf { + // O - O = O + return G1_IDENTITY; + } + if p1_is_inf { + // O - P2 = -P2 + return neg_bls12_381( + p2, + #[cfg(feature = "hints")] + hints, + ); + } + if p2_is_inf { + // P1 - O = P1 + return *p1; + } -// add_bls12_381(p1, &p2_neg) -// } + // Perform regular subtraction: P1 - P2 = P1 + (-P2) + sub_bls12_381( + p1, + p2, + #[cfg(feature = "hints")] + hints, + ) +} /// Multiplies a non-zero point `p` on the BLS12-381 curve by a scalar `k` on the BLS12-381 scalar field pub fn scalar_mul_bls12_381( @@ -473,6 +565,78 @@ pub fn scalar_mul_by_x2div3_bls12_381( ) } +/// Multi-Scalar Multiplication (MSM) for BLS12-381 G1 points +/// It computes k1·P1 + k2·P2 + ... + kn·Pn +// TODO: This is a naive implementation, one can improve it by using, e.g., a windowed strategies! +pub fn msm_complete_bls12_381( + points: &[[u64; 12]], + scalars: &[[u64; 4]], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Result<[u64; 12], u8> { + assert_eq!(points.len(), scalars.len()); + + let mut acc = G1_IDENTITY; + let mut acc_is_inf = true; + for (point, scalar) in points.iter().zip(scalars.iter()) { + // Skip infinity points + if *point == G1_IDENTITY { + continue; + } + + // Skip zero scalars + if *scalar == [0, 0, 0, 0] { + continue; + } + + // Verify point is on curve + if !is_on_curve_bls12_381( + point, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G1_MSM_ERR_NOT_ON_CURVE); + } + + // Verify point is in subgroup + if !is_on_subgroup_bls12_381( + point, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G1_MSM_ERR_NOT_IN_SUBGROUP); + } + + // Compute P * k + let product = scalar_mul_bls12_381( + point, + scalar, + #[cfg(feature = "hints")] + hints, + ); + + // Skip if product is infinity + if product == G1_IDENTITY { + continue; + } + + // Add to accumulator + if acc_is_inf { + acc = product; + acc_is_inf = false; + } else { + acc = add_bls12_381( + &acc, + &product, + #[cfg(feature = "hints")] + hints, + ); + acc_is_inf = acc == G1_IDENTITY; + } + } + + Ok(acc) +} + /// Compute the sigma endomorphism σ of a non-zero point `p`, defined as: /// σ : E(Fp) -> E(Fp) /// (x,y) |-> (ɣ·x,y) @@ -495,7 +659,7 @@ pub fn sigma_endomorphism_bls12_381( result } -/// G1 point addition for uncompressed 96-byte points (big-endian format) +/// G1 point addition for uncompressed 96-byte points /// /// Input format: 96 bytes per point = 48 bytes x-coordinate + 48 bytes y-coordinate (big-endian) /// Output format: Same as input @@ -508,7 +672,7 @@ pub fn sigma_endomorphism_bls12_381( /// Returns: /// - 0 = success (regular point) /// - 1 = success (point at infinity) -/// - 2 = error (point not on curve or invalid) +/// - 2 = error (point not on curve) #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_bls12_381_g1_add_c")] pub unsafe extern "C" fn bls12_381_g1_add_c( @@ -517,114 +681,35 @@ pub unsafe extern "C" fn bls12_381_g1_add_c( b: *const u8, #[cfg(feature = "hints")] hints: &mut Vec, ) -> u8 { - // TODO: Check we can remove code using assumptions! - let a_bytes: &[u8; 96] = &*(a as *const [u8; 96]); let b_bytes: &[u8; 96] = &*(b as *const [u8; 96]); let ret_bytes: &mut [u8; 96] = &mut *(ret as *mut [u8; 96]); - // Check for infinity points (all zeros) - let a_is_inf = a_bytes.iter().all(|&x| x == 0); - let b_is_inf = b_bytes.iter().all(|&x| x == 0); - - if a_is_inf && b_is_inf { - // 𝒪 + 𝒪 = 𝒪 - ret_bytes.fill(0); - return 1; - } - - if a_is_inf { - // 𝒪 + B = B - ret_bytes.copy_from_slice(b_bytes); - return 0; - } - - if b_is_inf { - // A + 𝒪 = A - ret_bytes.copy_from_slice(a_bytes); - return 0; - } - - // Convert big-endian bytes to little-endian u64 limbs + // Parse points let a_u64 = g1_bytes_be_to_u64_le(a_bytes); let b_u64 = g1_bytes_be_to_u64_le(b_bytes); - // Verify points are on curve - if !is_on_curve_bls12_381( - &a_u64, - #[cfg(feature = "hints")] - hints, - ) || !is_on_curve_bls12_381( - &b_u64, - #[cfg(feature = "hints")] - hints, - ) { - return 2; - } - // Perform addition - let result = add_bls12_381( + let result = match add_complete_bls12_381( &a_u64, &b_u64, #[cfg(feature = "hints")] hints, - ); + ) { + Ok(r) => r, + Err(code) => return code, + }; - // Check if result is identity + // Encode result if result == G1_IDENTITY { - ret_bytes.fill(0); - return 1; - } - - // Convert result back to big-endian bytes - g1_u64_le_to_bytes_be(&result, ret_bytes); - 0 -} - -/// Convert 96-byte big-endian G1 point to [u64; 12] little-endian -pub fn g1_bytes_be_to_u64_le(bytes: &[u8; 96]) -> [u64; 12] { - let mut result = [0u64; 12]; - - // x-coordinate (first 48 bytes) - for i in 0..6 { - for j in 0..8 { - result[5 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); - } - } - - // y-coordinate (next 48 bytes) - for i in 0..6 { - for j in 0..8 { - result[11 - i] |= (bytes[48 + i * 8 + j] as u64) << (8 * (7 - j)); - } - } - - result -} - -/// Convert [u64; 12] little-endian G1 point to 96-byte big-endian -fn g1_u64_le_to_bytes_be(limbs: &[u64; 12], bytes: &mut [u8; 96]) { - // x-coordinate (first 48 bytes) - for i in 0..6 { - let limb = limbs[5 - i]; - for j in 0..8 { - bytes[i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; - } - } - - // y-coordinate (next 48 bytes) - for i in 0..6 { - let limb = limbs[11 - i]; - for j in 0..8 { - bytes[48 + i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; - } + G1_ADD_SUCCESS_INFINITY + } else { + g1_u64_le_to_bytes_be(&result, ret_bytes); + G1_ADD_SUCCESS } } -/// G1 Multi-Scalar Multiplication (MSM) for uncompressed points (big-endian format) -/// -/// This function is designed to patch: -/// `fn bls12_381_g1_msm(&self, pairs: &mut dyn Iterator>) -> Result<[u8; 96]>` +/// G1 Multi-Scalar Multiplication (MSM) for uncompressed points /// /// Input format per pair: 128 bytes = 96 bytes G1 point (x || y big-endian) + 32 bytes scalar (big-endian) /// Output format: 96 bytes G1 point (x || y big-endian) @@ -636,7 +721,8 @@ fn g1_u64_le_to_bytes_be(limbs: &[u64; 12], bytes: &mut [u8; 96]) { /// Returns: /// - 0 = success (regular point) /// - 1 = success (point at infinity) -/// - 2 = error (point not on curve or invalid) +/// - 2 = error (point not on curve) +/// - 3 = error (point not in subgroup) #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_bls12_381_g1_msm_c")] pub unsafe extern "C" fn bls12_381_g1_msm_c( @@ -647,96 +733,78 @@ pub unsafe extern "C" fn bls12_381_g1_msm_c( ) -> u8 { let ret_bytes: &mut [u8; 96] = &mut *(ret as *mut [u8; 96]); - // Handle empty input - if num_pairs == 0 { - ret_bytes.fill(0); - return 1; // Point at infinity - } - - // Accumulator starts at infinity - let mut acc = G1_IDENTITY; - let mut acc_is_inf = true; - + // Parse all pairs + let mut points = Vec::with_capacity(num_pairs); + let mut scalars = Vec::with_capacity(num_pairs); for i in 0..num_pairs { let pair_ptr = pairs.add(i * 128); - - // Extract point (96 bytes) and scalar (32 bytes) let point_bytes: &[u8; 96] = &*(pair_ptr as *const [u8; 96]); let scalar_bytes: &[u8; 32] = &*(pair_ptr.add(96) as *const [u8; 32]); - // Check if point is infinity - let point_is_inf = point_bytes.iter().all(|&x| x == 0); - if point_is_inf { - continue; // 𝒪 * k = 𝒪, skip - } - - // Check if scalar is zero - let scalar_is_zero = scalar_bytes.iter().all(|&x| x == 0); - if scalar_is_zero { - continue; // P * 0 = 𝒪, skip - } - - // Convert point from big-endian bytes to u64 limbs + // Parse point and scalar let point_u64 = g1_bytes_be_to_u64_le(point_bytes); + let scalar_u64 = scalar_bytes_be_to_u64_le(scalar_bytes); - // Verify point is on curve - if !is_on_curve_bls12_381( - &point_u64, - #[cfg(feature = "hints")] - hints, - ) { - return 2; - } + points.push(point_u64); + scalars.push(scalar_u64); + } - // Convert scalar from big-endian bytes to u64 limbs (only need 4 limbs for 256-bit field) - let scalar_u64 = scalar_bytes_be_to_u64_le(scalar_bytes); + // Perform MSM with validation + let result = match msm_complete_bls12_381( + &points, + &scalars, + #[cfg(feature = "hints")] + hints, + ) { + Ok(r) => r, + Err(code) => return code, + }; - // Compute P * k - let product = scalar_mul_bls12_381( - &point_u64, - &scalar_u64, - #[cfg(feature = "hints")] - hints, - ); + // Encode result + if result == G1_IDENTITY { + G1_MSM_SUCCESS_INFINITY + } else { + g1_u64_le_to_bytes_be(&result, ret_bytes); + G1_MSM_SUCCESS + } +} - // Check if product is infinity - if product == G1_IDENTITY { - continue; - } +/// Convert 96-byte big-endian G1 point to [u64; 12] little-endian +pub fn g1_bytes_be_to_u64_le(bytes: &[u8; 96]) -> [u64; 12] { + let mut result = [0u64; 12]; - // Add to accumulator - if acc_is_inf { - acc = product; - acc_is_inf = false; - } else { - acc = add_bls12_381( - &acc, - &product, - #[cfg(feature = "hints")] - hints, - ); - acc_is_inf = acc == G1_IDENTITY; + // x-coordinate (first 48 bytes) + for i in 0..6 { + for j in 0..8 { + result[5 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); } } - // Write result - if acc_is_inf { - ret_bytes.fill(0); - return 1; + // y-coordinate (next 48 bytes) + for i in 0..6 { + for j in 0..8 { + result[11 - i] |= (bytes[48 + i * 8 + j] as u64) << (8 * (7 - j)); + } } - g1_u64_le_to_bytes_be(&acc, ret_bytes); - 0 + result } -/// Convert 32-byte big-endian scalar to [u64; 4] little-endian (zero-padded for 256-bit) -fn scalar_bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { - let mut result = [0u64; 4]; - for i in 0..4 { +/// Convert [u64; 12] little-endian G1 point to 96-byte big-endian +fn g1_u64_le_to_bytes_be(limbs: &[u64; 12], bytes: &mut [u8; 96]) { + // x-coordinate (first 48 bytes) + for i in 0..6 { + let limb = limbs[5 - i]; for j in 0..8 { - result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + bytes[i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; } } - result + // y-coordinate (next 48 bytes) + for i in 0..6 { + let limb = limbs[11 - i]; + for j in 0..8 { + bytes[48 + i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; + } + } } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs index 8aaff5025..52320296a 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs @@ -112,3 +112,15 @@ pub fn square_fr_bls12_381( ); *params.d } + +/// Convert 32-byte big-endian scalar to [u64; 4] little-endian +pub fn scalar_bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { + let mut result = [0u64; 4]; + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + result +} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs index 4d80f2bfc..348a5d2d6 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/kzg.rs @@ -1,96 +1,94 @@ -use crate::zisklib::lib::utils::{is_one, lt}; +use crate::zisklib::{ + is_on_subgroup_bls12_381, + lib::utils::{eq, is_one, lt}, +}; use super::{ - constants::{ - G1_GENERATOR, G1_IDENTITY, G2_GENERATOR, G2_IDENTITY, R, TRUSTED_SETUP_TAU_G2_COMPRESSED, - }, - curve::{decompress_bls12_381, scalar_mul_bls12_381, sub_bls12_381}, + constants::{G1_GENERATOR, G1_IDENTITY, G2_GENERATOR, G2_IDENTITY, R, TRUSTED_SETUP_TAU_G2}, + curve::{decompress_bls12_381, scalar_mul_bls12_381, sub_bls12_381, sub_complete_bls12_381}, pairing::pairing_batch_bls12_381, twist::{ decompress_twist_bls12_381, neg_twist_bls12_381, scalar_mul_twist_bls12_381, - sub_twist_bls12_381, + sub_complete_twist_bls12_381, sub_twist_bls12_381, }, }; /// Verify KZG proof using BLS12-381 implementation. -/// -/// # Arguments -/// * `z` - 32 bytes big-endian scalar (evaluation point) -/// * `y` - 32 bytes big-endian scalar (claimed evaluation) -/// * `commitment` - 48 bytes compressed G1 point (polynomial commitment) -/// * `proof` - 48 bytes compressed G1 point (KZG proof) -/// -/// # Safety -/// All pointers must be valid and properly aligned. -/// -/// # Returns -/// * 1 if the proof is valid -/// * 0 if the proof is invalid -/// * 2 if there was a parsing error (invalid input) -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_verify_kzg_proof_c")] -pub unsafe extern "C" fn verify_kzg_proof_c( - z: *const u8, - y: *const u8, - commitment: *const u8, - proof: *const u8, +pub fn verify_kzg_proof( + z_bytes: &[u8; 32], + y_bytes: &[u8; 32], + commitment_bytes: &[u8; 48], + proof_bytes: &[u8; 48], #[cfg(feature = "hints")] hints: &mut Vec, -) -> u8 { - let z_bytes: &[u8; 32] = &*(z as *const [u8; 32]); - let y_bytes: &[u8; 32] = &*(y as *const [u8; 32]); - let commitment_bytes: &[u8; 48] = &*(commitment as *const [u8; 48]); - let proof_bytes: &[u8; 48] = &*(proof as *const [u8; 48]); - - // Parse the commitment (G1 point, compressed) - let commitment_point = match decompress_bls12_381( +) -> bool { + // Parse the commitment + let commitment = match decompress_bls12_381( commitment_bytes, #[cfg(feature = "hints")] hints, ) { - Ok((point, _is_inf)) => point, - Err(_) => return 2, // Invalid commitment + Ok(result) => result, + Err(_) => return false, }; + if !eq(&commitment, &G1_IDENTITY) + && !is_on_subgroup_bls12_381( + &commitment, + #[cfg(feature = "hints")] + hints, + ) + { + return false; + } - // Parse the proof (G1 point, compressed) - let proof_point = match decompress_bls12_381( + // Parse the proof + let proof = match decompress_bls12_381( proof_bytes, #[cfg(feature = "hints")] hints, ) { - Ok((point, _is_inf)) => point, - Err(_) => return 2, // Invalid proof + Ok(result) => result, + Err(_) => return false, }; + if !eq(&proof, &G1_IDENTITY) + && !is_on_subgroup_bls12_381( + &proof, + #[cfg(feature = "hints")] + hints, + ) + { + return false; + } - // Parse z and y as scalar field elements (must be canonical < R) - let z_scalar = match read_scalar_canonical(z_bytes) { + // Parse z and y as scalar field elements + let z = match scalar_bytes_be_to_u64_le_canonical(z_bytes) { Some(s) => s, - None => return 2, // z not canonical + None => return false, }; - let y_scalar = match read_scalar_canonical(y_bytes) { + let y = match scalar_bytes_be_to_u64_le_canonical(y_bytes) { Some(s) => s, - None => return 2, // y not canonical + None => return false, }; + // The verification equation is: + // e(C - [y]G₁, G₂) = e(π, [τ]₂ - [z]G₂) + // Get the trusted setup G2 point [τ]₂ - let tau_g2 = get_trusted_setup_g2( - #[cfg(feature = "hints")] - hints, - ); + let tau_g2 = TRUSTED_SETUP_TAU_G2; // Get generators let g1 = G1_GENERATOR; let g2 = G2_GENERATOR; - // Compute c_minus_y = commitment - [y]G₁ + // Compute c_minus_y = C - [y]G₁ let y_g1 = scalar_mul_bls12_381( &g1, - &y_scalar, + &y, #[cfg(feature = "hints")] hints, ); - let c_minus_y = sub_bls12_381( - &commitment_point, + let c_minus_y = sub_complete_bls12_381( + &commitment, &y_g1, #[cfg(feature = "hints")] hints, @@ -99,22 +97,41 @@ pub unsafe extern "C" fn verify_kzg_proof_c( // Compute t_minus_z = [τ]₂ - [z]G₂ let z_g2 = scalar_mul_twist_bls12_381( &g2, - &z_scalar, + &z, #[cfg(feature = "hints")] hints, ); - let t_minus_z = sub_twist_bls12_381( + let t_minus_z = sub_complete_twist_bls12_381( &tau_g2, &z_g2, #[cfg(feature = "hints")] hints, ); - // The verification equation is: - // e(commitment - [y]G₁, G₂) = e(proof, [τ]₂ - [z]G₂) - // - // Which is equivalent to checking: - // e(commitment - [y]G₁, -G₂) · e(proof, [τ]₂ - [z]G₂) = 1 + // LHS: e(C - [y]G₁, G₂) - G₂ is never infinity + // RHS: e(π, [τ]₂ - [z]G₂) + let c_minus_y_is_inf = eq(&c_minus_y, &G1_IDENTITY); + let proof_is_inf = eq(&proof, &G1_IDENTITY); + let t_minus_z_is_inf = eq(&t_minus_z, &G2_IDENTITY); + + // If c_minus_y = O: LHS = e(O, G₂) = 1 + // => RHS must equal 1, i.e., e(π, [τ]₂ - [z]G₂) = 1 + // => π = O or [τ]₂ - [z]G₂ = O + if c_minus_y_is_inf { + return proof_is_inf || t_minus_z_is_inf; + } + + // If π = O or [τ]₂ - [z]G₂ = O: RHS = 1 + // => LHS must equal 1, i.e., e(C - [y]G₁, G₂) = 1 + // => C - [y]G₁ = O (but we already handled that above) + // => This means c_minus_y ≠ O but RHS = 1, so verification fails + if proof_is_inf || t_minus_z_is_inf { + return false; + } + + // General case: no infinities, proceed with pairing check + // The check is equivalent to: + // e(C - [y]G₁, -G₂) · e(π, [τ]₂ - [z]G₂) = 1 let neg_g2 = neg_twist_bls12_381( &g2, #[cfg(feature = "hints")] @@ -122,25 +139,60 @@ pub unsafe extern "C" fn verify_kzg_proof_c( ); // Batch pairing check - let g1_points = [c_minus_y, proof_point]; + let g1_points = [c_minus_y, proof]; let g2_points = [neg_g2, t_minus_z]; // Check if the pairing result equals 1 - if is_one(&pairing_batch_bls12_381( + is_one(&pairing_batch_bls12_381( &g1_points, &g2_points, #[cfg(feature = "hints")] hints, - )) { - 1 // Valid proof - } else { - 0 // Invalid proof - } + )) } -/// Read a scalar from 32 big-endian bytes and check if it's canonical (< R) -/// Returns None if the scalar is not canonical -fn read_scalar_canonical(bytes: &[u8; 32]) -> Option<[u64; 4]> { +/// Verify KZG proof using BLS12-381 implementation. +/// +/// # Arguments +/// * `z` - 32 bytes big-endian scalar (evaluation point) +/// * `y` - 32 bytes big-endian scalar (claimed evaluation) +/// * `commitment` - 48 bytes compressed G1 point (polynomial commitment) +/// * `proof` - 48 bytes compressed G1 point (KZG proof) +/// +/// # Safety +/// All pointers must be valid and properly aligned. +/// +/// # Returns +/// * 1 if the proof is valid +/// * 0 if the proof is invalid +/// * 2 if there was a parsing error (invalid input) +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_verify_kzg_proof_c")] +pub unsafe extern "C" fn verify_kzg_proof_c( + z: *const u8, + y: *const u8, + commitment: *const u8, + proof: *const u8, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { + let z_bytes: &[u8; 32] = &*(z as *const [u8; 32]); + let y_bytes: &[u8; 32] = &*(y as *const [u8; 32]); + let commitment_bytes: &[u8; 48] = &*(commitment as *const [u8; 48]); + let proof_bytes: &[u8; 48] = &*(proof as *const [u8; 48]); + + verify_kzg_proof( + z_bytes, + y_bytes, + commitment_bytes, + proof_bytes, + #[cfg(feature = "hints")] + hints, + ) +} + +/// Convert 32-byte big-endian scalar to [u64; 4] little-endian, checking canonicity +/// Returns None if the scalar is not canonical (>= R) +fn scalar_bytes_be_to_u64_le_canonical(bytes: &[u8; 32]) -> Option<[u64; 4]> { // Convert big-endian bytes to little-endian u64 limbs let mut scalar = [0u64; 4]; for i in 0..4 { @@ -149,21 +201,10 @@ fn read_scalar_canonical(bytes: &[u8; 32]) -> Option<[u64; 4]> { } } - // Check if scalar < R (scalar field order) + // Check if scalar < R if !lt(&scalar, &R) { - return None; // scalar >= R, not canonical + return None; } Some(scalar) } - -/// Get the trusted setup G2 point `[τ]₂` -fn get_trusted_setup_g2(#[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 24] { - decompress_twist_bls12_381( - &TRUSTED_SETUP_TAU_G2_COMPRESSED, - #[cfg(feature = "hints")] - hints, - ) - .expect("Failed to decompress trusted setup G2") - .0 -} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs index 2ef20f30c..8e37e6387 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs @@ -1,6 +1,6 @@ //! Pairing over BLS12-381 curve -use crate::zisklib::lib::utils::{gt, is_one}; +use crate::zisklib::lib::utils::{eq, gt, is_one}; use super::{ constants::{G1_IDENTITY, G2_IDENTITY, P_MINUS_ONE}, @@ -12,6 +12,14 @@ use super::{ twist::{g2_bytes_be_to_u64_le, is_on_curve_twist_bls12_381, is_on_subgroup_twist_bls12_381}, }; +/// Pairing check result codes +pub const PAIRING_CHECK_SUCCESS: u8 = 0; +pub const PAIRING_CHECK_FAILED: u8 = 1; +pub const PAIRING_CHECK_ERR_G1_NOT_ON_CURVE: u8 = 2; +pub const PAIRING_CHECK_ERR_G1_NOT_IN_SUBGROUP: u8 = 3; +pub const PAIRING_CHECK_ERR_G2_NOT_ON_CURVE: u8 = 4; +pub const PAIRING_CHECK_ERR_G2_NOT_IN_SUBGROUP: u8 = 5; + /// Optimal Ate Pairing e: G1 x G2 -> GT over the BLS12-381 curve /// where G1 = E(Fp)[r] = E(Fp), G2 = E'(Fp2)[r] and GT = μ_r (the r-th roots of unity over Fp12*) /// the involved curves are E/Fp: y² = x³ + 4 and E'/Fp2: y² = x³ + 4·(1+u) @@ -46,9 +54,11 @@ pub fn pairing_bls12_381( ) } -/// Computes the optimal Ate pairing for a batch of G1 and G2 points over the BN254 curve -/// and multiplies the results together, i.e.: +/// Computes the optimal Ate pairing for a batch of G1 and G2 points over the BLS12-381 curve +/// and multiplies the results together: /// e(P₁, Q₁) · e(P₂, Q₂) · ... · e(Pₙ, Qₙ) ∈ GT +/// +/// Assumes all points are non-infinity and already validated (on curve and in subgroup). pub fn pairing_batch_bls12_381( g1_points: &[[u64; 12]], g2_points: &[[u64; 24]], @@ -63,33 +73,20 @@ pub fn pairing_batch_bls12_381( let n = g1_points.len(); assert_eq!(n, g2_points.len(), "Number of G1 and G2 points must be equal"); - // Miller loop and multiplication - let mut g1_points_ml = Vec::with_capacity(n); - let mut g2_points_ml = Vec::with_capacity(n); - for (p, q) in g1_points.iter().zip(g2_points.iter()) { - // If p = 𝒪 or q = 𝒪 => MillerLoop(P, 𝒪) = MillerLoop(𝒪, Q) = 1; we can skip - if *p != G1_IDENTITY && *q != G2_IDENTITY { - g1_points_ml.push(*p); - g2_points_ml.push(*q); - } - } - - if g1_points_ml.is_empty() { - // If all pairing computations were skipped, return 1 + if n == 0 { + // Empty input returns 1 let mut one = [0; 72]; one[0] = 1; return one; } - // Miller loop let miller_loop = miller_loop_batch_bls12_381( - &g1_points_ml, - &g2_points_ml, + g1_points, + g2_points, #[cfg(feature = "hints")] hints, ); - // Final exponentiation final_exp_bls12_381( &miller_loop, #[cfg(feature = "hints")] @@ -97,110 +94,166 @@ pub fn pairing_batch_bls12_381( ) } -/// BLS12-381 pairing check for big-endian byte format +/// BLS12-381 pairing check with validation. /// -/// This function is designed to patch: -/// `fn bls12_381_pairing_check(&self, pairs: &[(G1Point, G2Point)]) -> Result` +/// Validates all points are on curve and in subgroup. /// -/// Input format per pair: 288 bytes = 96 bytes G1 point + 192 bytes G2 point (big-endian) -/// - G1 point: 48 bytes x + 48 bytes y -/// - G2 point: 48 bytes x_i + 48 bytes x_r + 48 bytes y_i + 48 bytes y_r +/// # Arguments +/// * `g1_points` - Slice of G1 points as [u64; 12] +/// * `g2_points` - Slice of G2 points as [u64; 24] /// -/// ### Safety -/// - `pairs` must point to an array of `num_pairs * 288` bytes -/// -/// Returns: -/// - true if the pairing check passes (the product of pairings is equal to 1 in GT) -/// - false otherwise -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_bls12_381_pairing_check_c")] -pub unsafe extern "C" fn bls12_381_pairing_check_c( - pairs: *const u8, - num_pairs: usize, +/// # Returns +/// * `Ok(true)` - Pairing check passed +/// * `Ok(false)` - Pairing check failed +/// * `Err(PAIRING_CHECK_ERR_G1_NOT_ON_CURVE)` - G1 point not on curve +/// * `Err(PAIRING_CHECK_ERR_G1_NOT_IN_SUBGROUP)` - G1 point not in subgroup +/// * `Err(PAIRING_CHECK_ERR_G2_NOT_ON_CURVE)` - G2 point not on curve +/// * `Err(PAIRING_CHECK_ERR_G2_NOT_IN_SUBGROUP)` - G2 point not in subgroup +pub fn pairing_check_bls12_381( + g1_points: &[[u64; 12]], + g2_points: &[[u64; 24]], #[cfg(feature = "hints")] hints: &mut Vec, -) -> bool { - // Handle empty input - empty product is 1, so pairing check passes - if num_pairs == 0 { - return true; - } +) -> Result { + assert_eq!(g1_points.len(), g2_points.len(), "Number of G1 and G2 points must be equal"); - let mut g1_points: Vec<[u64; 12]> = Vec::with_capacity(num_pairs); - let mut g2_points: Vec<[u64; 24]> = Vec::with_capacity(num_pairs); + // Collect valid pairs + let mut valid_g1: Vec<[u64; 12]> = Vec::with_capacity(g1_points.len()); + let mut valid_g2: Vec<[u64; 24]> = Vec::with_capacity(g2_points.len()); + for (g1, g2) in g1_points.iter().zip(g2_points.iter()) { + let g1_is_inf = eq(g1, &G1_IDENTITY); + let g2_is_inf = eq(g2, &G2_IDENTITY); - for i in 0..num_pairs { - let pair_ptr = pairs.add(i * 288); - - // Extract G1 point (96 bytes) and G2 point (192 bytes) - let g1_bytes: &[u8; 96] = &*(pair_ptr as *const [u8; 96]); - let g2_bytes: &[u8; 192] = &*(pair_ptr.add(96) as *const [u8; 192]); - - // Check if G1 point is infinity - let g1_is_inf = g1_bytes.iter().all(|&x| x == 0); - - // Check if G2 point is infinity - let g2_is_inf = g2_bytes.iter().all(|&x| x == 0); - - // If either point is infinity, skip this pair (contributes 1 to product) - if g1_is_inf || g2_is_inf { + // If p = 𝒪 or q = 𝒪 => MillerLoop(P, 𝒪) = MillerLoop(𝒪, Q) = 1; we can skip + if g2_is_inf { + if !g1_is_inf { + if !is_on_curve_bls12_381( + g1, + #[cfg(feature = "hints")] + hints, + ) { + return Err(PAIRING_CHECK_ERR_G1_NOT_ON_CURVE); + } + if !is_on_subgroup_bls12_381( + g1, + #[cfg(feature = "hints")] + hints, + ) { + return Err(PAIRING_CHECK_ERR_G1_NOT_IN_SUBGROUP); + } + } continue; } - // Convert G1 from big-endian bytes to u64 limbs - let g1_u64 = g1_bytes_be_to_u64_le(g1_bytes); + if g1_is_inf { + if !is_on_curve_twist_bls12_381( + g2, + #[cfg(feature = "hints")] + hints, + ) { + return Err(PAIRING_CHECK_ERR_G2_NOT_ON_CURVE); + } + if !is_on_subgroup_twist_bls12_381( + g2, + #[cfg(feature = "hints")] + hints, + ) { + return Err(PAIRING_CHECK_ERR_G2_NOT_IN_SUBGROUP); + } + continue; + } - // Verify G1 point is on curve if !is_on_curve_bls12_381( - &g1_u64, + g1, #[cfg(feature = "hints")] hints, ) { - return false; + return Err(PAIRING_CHECK_ERR_G1_NOT_ON_CURVE); } - - // Verify G1 point is in subgroup if !is_on_subgroup_bls12_381( - &g1_u64, + g1, #[cfg(feature = "hints")] hints, ) { - return false; + return Err(PAIRING_CHECK_ERR_G1_NOT_IN_SUBGROUP); } - // Convert G2 from big-endian bytes to u64 limbs - let g2_u64 = g2_bytes_be_to_u64_le(g2_bytes); - - // Verify G2 point is on twist curve if !is_on_curve_twist_bls12_381( - &g2_u64, + g2, #[cfg(feature = "hints")] hints, ) { - return false; + return Err(PAIRING_CHECK_ERR_G2_NOT_ON_CURVE); } - - // Verify G2 point is in subgroup if !is_on_subgroup_twist_bls12_381( - &g2_u64, + g2, #[cfg(feature = "hints")] hints, ) { - return false; + return Err(PAIRING_CHECK_ERR_G2_NOT_IN_SUBGROUP); } - g1_points.push(g1_u64); - g2_points.push(g2_u64); + valid_g1.push(*g1); + valid_g2.push(*g2); } - // If all pairs were skipped (all infinities), result is 1 - if g1_points.is_empty() { - return true; + // If all pairs were skipped, result is 1 + if valid_g1.is_empty() { + return Ok(true); } // Compute batch pairing and check if result is 1 - is_one(&pairing_batch_bls12_381( + Ok(is_one(&pairing_batch_bls12_381( + &valid_g1, + &valid_g2, + #[cfg(feature = "hints")] + hints, + ))) +} + +/// BLS12-381 pairing check for big-endian byte format. +/// +/// # Input format +/// Per pair: 288 bytes = 96 bytes G1 point + 192 bytes G2 point (big-endian) +/// - G1 point: 48 bytes x + 48 bytes y +/// - G2 point: 48 bytes x_i + 48 bytes x_r + 48 bytes y_i + 48 bytes y_r +/// +/// # Safety +/// `pairs` must point to an array of `num_pairs * 288` bytes +/// +/// # Returns +/// - 0 = pairing check passed +/// - 1 = pairing check failed +/// - 2 = error: G1 point not on curve +/// - 3 = error: G1 point not in subgroup +/// - 4 = error: G2 point not on curve +/// - 5 = error: G2 point not in subgroup +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_bls12_381_pairing_check_c")] +pub unsafe extern "C" fn bls12_381_pairing_check_c( + pairs: *const u8, + num_pairs: usize, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> u8 { + // Parse all pairs + let mut g1_points: Vec<[u64; 12]> = Vec::with_capacity(num_pairs); + let mut g2_points: Vec<[u64; 24]> = Vec::with_capacity(num_pairs); + for i in 0..num_pairs { + let pair_ptr = pairs.add(i * 288); + let g1_bytes: &[u8; 96] = &*(pair_ptr as *const [u8; 96]); + let g2_bytes: &[u8; 192] = &*(pair_ptr.add(96) as *const [u8; 192]); + + g1_points.push(g1_bytes_be_to_u64_le(g1_bytes)); + g2_points.push(g2_bytes_be_to_u64_le(g2_bytes)); + } + + match pairing_check_bls12_381( &g1_points, &g2_points, #[cfg(feature = "hints")] hints, - )) + ) { + Ok(true) => PAIRING_CHECK_SUCCESS, + Ok(false) => PAIRING_CHECK_FAILED, + Err(code) => code, + } } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs index 1b505590a..e2c6d669f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs @@ -12,8 +12,22 @@ use super::{ mul_fp2_bls12_381, neg_fp2_bls12_381, scalar_mul_fp2_bls12_381, sqrt_fp2_bls12_381, square_fp2_bls12_381, sub_fp2_bls12_381, }, + fr::scalar_bytes_be_to_u64_le, }; +// TODO: Check what happens if scalar or ecc coordinates are bigger than the field size + +/// G2 add result codes +pub const G2_ADD_SUCCESS: u8 = 0; +pub const G2_ADD_SUCCESS_INFINITY: u8 = 1; +pub const G2_ADD_ERR_NOT_ON_CURVE: u8 = 2; + +/// G2 MSM result codes +pub const G2_MSM_SUCCESS: u8 = 0; +pub const G2_MSM_SUCCESS_INFINITY: u8 = 1; +pub const G2_MSM_ERR_NOT_ON_CURVE: u8 = 2; +pub const G2_MSM_ERR_NOT_IN_SUBGROUP: u8 = 3; + /// Decompresses a G2 point on the BLS12-381 twist from 96 bytes (compressed format). /// /// Format: Big-endian x-coordinate (in Fp2) with flag bits in the top 3 bits of the first byte: @@ -319,6 +333,69 @@ pub fn add_twist_bls12_381( result } +/// Addition of two points +pub fn add_complete_twist_bls12_381( + p1: &[u64; 24], + p2: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Result<[u64; 24], u8> { + let p1_is_inf = eq(p1, &G2_IDENTITY); + let p2_is_inf = eq(p2, &G2_IDENTITY); + + // Handle identity cases + if p1_is_inf && p2_is_inf { + return Ok(G2_IDENTITY); + } + + if p1_is_inf { + // Validate p2 is on curve + if !is_on_curve_twist_bls12_381( + p2, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G2_ADD_ERR_NOT_ON_CURVE); + } + return Ok(*p2); + } + + if p2_is_inf { + // Validate p1 is on curve + if !is_on_curve_twist_bls12_381( + p1, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G2_ADD_ERR_NOT_ON_CURVE); + } + return Ok(*p1); + } + + // Both points are non-identity, validate both are on curve + if !is_on_curve_twist_bls12_381( + p1, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G2_ADD_ERR_NOT_ON_CURVE); + } + if !is_on_curve_twist_bls12_381( + p2, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G2_ADD_ERR_NOT_ON_CURVE); + } + + // Perform addition + Ok(add_twist_bls12_381( + p1, + p2, + #[cfg(feature = "hints")] + hints, + )) +} + /// Doubling of a non-zero point pub fn dbl_twist_bls12_381( p: &[u64; 24], @@ -428,6 +505,42 @@ pub fn sub_twist_bls12_381( ) } +/// Subtraction of two points `p1` and `p2` on the BLS12-381 curve +pub fn sub_complete_twist_bls12_381( + p1: &[u64; 24], + p2: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { + let p1_is_inf = *p1 == G2_IDENTITY; + let p2_is_inf = *p2 == G2_IDENTITY; + + // Handle identity cases + if p1_is_inf && p2_is_inf { + // O - O = O + return G2_IDENTITY; + } + if p1_is_inf { + // O - P2 = -P2 + return neg_twist_bls12_381( + p2, + #[cfg(feature = "hints")] + hints, + ); + } + if p2_is_inf { + // P1 - O = P1 + return *p1; + } + + // Perform regular subtraction: P1 - P2 = P1 + (-P2) + sub_twist_bls12_381( + p1, + p2, + #[cfg(feature = "hints")] + hints, + ) +} + /// Negation of a point pub fn neg_twist_bls12_381( p: &[u64; 24], @@ -583,6 +696,79 @@ pub fn scalar_mul_by_abs_x_twist_bls12_381( ) } +/// Multi-Scalar Multiplication (MSM) for BLS12-381 G2 points +/// It computes k1·P1 + k2·P2 + ... + kn·Pn +// TODO: This is a naive implementation, one can improve it by using, e.g., a windowed strategies! +pub fn msm_complete_twist_bls12_381( + points: &[[u64; 24]], + scalars: &[[u64; 4]], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Result<[u64; 24], u8> { + debug_assert_eq!(points.len(), scalars.len()); + + let mut acc = G2_IDENTITY; + let mut acc_is_inf = true; + + for (point, scalar) in points.iter().zip(scalars.iter()) { + // Skip infinity points + if *point == G2_IDENTITY { + continue; + } + + // Skip zero scalars + if *scalar == [0, 0, 0, 0] { + continue; + } + + // Verify point is on curve + if !is_on_curve_twist_bls12_381( + point, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G2_MSM_ERR_NOT_ON_CURVE); + } + + // Verify point is in subgroup (required for MSM per EIP-2537) + if !is_on_subgroup_twist_bls12_381( + point, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G2_MSM_ERR_NOT_IN_SUBGROUP); + } + + // Compute P * k + let product = scalar_mul_twist_bls12_381( + point, + scalar, + #[cfg(feature = "hints")] + hints, + ); + + // Skip if product is infinity + if product == G2_IDENTITY { + continue; + } + + // Add to accumulator + if acc_is_inf { + acc = product; + acc_is_inf = false; + } else { + acc = add_twist_bls12_381( + &acc, + &product, + #[cfg(feature = "hints")] + hints, + ); + acc_is_inf = acc == G2_IDENTITY; + } + } + + Ok(acc) +} + /// Compute the untwist-frobenius-twist (utf) endomorphism ψ := 𝜑⁻¹𝜋ₚ𝜑 of a non-zero point `p`, where: /// 𝜑 : E'(Fp2) -> E(Fp12) defined by 𝜑(x,y) = (x/ω²,y/ω³) is the untwist map /// 𝜋ₚ : E(Fp12) -> E(Fp12) defined by 𝜋ₚ(x,y) = (xᵖ,yᵖ) is the Frobenius map @@ -657,8 +843,6 @@ pub fn utf_endomorphism_twist_bls12_381( /// G2 point addition for uncompressed 192-byte points (big-endian format) /// -/// This function is designed to patch `fn bls12_381_g2_add(&self, a: G2Point, b: G2Point) -> Result<[u8; 192]>` -/// /// Input format: 192 bytes per point = 96 bytes x-coordinate (Fp2) + 96 bytes y-coordinate (Fp2) /// Each Fp2 element: 48 bytes imaginary + 48 bytes real (big-endian) /// Output format: Same as input @@ -671,7 +855,7 @@ pub fn utf_endomorphism_twist_bls12_381( /// Returns: /// - 0 = success (regular point) /// - 1 = success (point at infinity) -/// - 2 = error (point not on curve or invalid) +/// - 2 = error (point not on curve) #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_bls12_381_g2_add_c")] pub unsafe extern "C" fn bls12_381_g2_add_c( @@ -684,69 +868,32 @@ pub unsafe extern "C" fn bls12_381_g2_add_c( let b_bytes: &[u8; 192] = &*(b as *const [u8; 192]); let ret_bytes: &mut [u8; 192] = &mut *(ret as *mut [u8; 192]); - // Check for infinity points (all zeros) - let a_is_inf = a_bytes.iter().all(|&x| x == 0); - let b_is_inf = b_bytes.iter().all(|&x| x == 0); - - if a_is_inf && b_is_inf { - // 𝒪 + 𝒪 = 𝒪 - ret_bytes.fill(0); - return 1; - } - - if a_is_inf { - // 𝒪 + B = B - ret_bytes.copy_from_slice(b_bytes); - return 0; - } - - if b_is_inf { - // A + 𝒪 = A - ret_bytes.copy_from_slice(a_bytes); - return 0; - } - - // Convert big-endian bytes to little-endian u64 limbs + // Parse points let a_u64 = g2_bytes_be_to_u64_le(a_bytes); let b_u64 = g2_bytes_be_to_u64_le(b_bytes); - // Verify points are on curve - if !is_on_curve_twist_bls12_381( - &a_u64, - #[cfg(feature = "hints")] - hints, - ) || !is_on_curve_twist_bls12_381( - &b_u64, - #[cfg(feature = "hints")] - hints, - ) { - return 2; - } - // Perform addition - let result = add_twist_bls12_381( + let result = match add_complete_twist_bls12_381( &a_u64, &b_u64, #[cfg(feature = "hints")] hints, - ); + ) { + Ok(r) => r, + Err(code) => return code, + }; - // Check if result is identity + // Encode result if result == G2_IDENTITY { - ret_bytes.fill(0); - return 1; + G2_ADD_SUCCESS_INFINITY + } else { + g2_u64_le_to_bytes_be(&result, ret_bytes); + G2_ADD_SUCCESS } - - // Convert result back to big-endian bytes - g2_u64_le_to_bytes_be(&result, ret_bytes); - 0 } /// G2 Multi-Scalar Multiplication (MSM) for uncompressed points (big-endian format) /// -/// This function is designed to patch: -/// `fn bls12_381_g2_msm(&self, pairs: &mut dyn Iterator>) -> Result<[u8; 192]>` -/// /// Input format per pair: 224 bytes = 192 bytes G2 point + 32 bytes scalar (big-endian) /// Output format: 192 bytes G2 point /// @@ -757,7 +904,8 @@ pub unsafe extern "C" fn bls12_381_g2_add_c( /// Returns: /// - 0 = success (regular point) /// - 1 = success (point at infinity) -/// - 2 = error (point not on curve or invalid) +/// - 2 = error (point not on curve) +/// - 3 = error (point not in subgroup) #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_bls12_381_g2_msm_c")] pub unsafe extern "C" fn bls12_381_g2_msm_c( @@ -768,118 +916,71 @@ pub unsafe extern "C" fn bls12_381_g2_msm_c( ) -> u8 { let ret_bytes: &mut [u8; 192] = &mut *(ret as *mut [u8; 192]); - // Handle empty input - if num_pairs == 0 { - ret_bytes.fill(0); - return 1; // Point at infinity - } - - // Accumulator starts at infinity - let mut acc = G2_IDENTITY; - let mut acc_is_inf = true; - + // Parse all pairs + let mut points = Vec::with_capacity(num_pairs); + let mut scalars = Vec::with_capacity(num_pairs); for i in 0..num_pairs { let pair_ptr = pairs.add(i * 224); - - // Extract point (192 bytes) and scalar (32 bytes) let point_bytes: &[u8; 192] = &*(pair_ptr as *const [u8; 192]); let scalar_bytes: &[u8; 32] = &*(pair_ptr.add(192) as *const [u8; 32]); - // Check if point is infinity - let point_is_inf = point_bytes.iter().all(|&x| x == 0); - if point_is_inf { - continue; // 𝒪 * k = 𝒪, skip - } - - // Check if scalar is zero - let scalar_is_zero = scalar_bytes.iter().all(|&x| x == 0); - if scalar_is_zero { - continue; // P * 0 = 𝒪, skip - } - - // Convert point from big-endian bytes to u64 limbs + // Parse point and scalar let point_u64 = g2_bytes_be_to_u64_le(point_bytes); + let scalar_u64 = scalar_bytes_be_to_u64_le(scalar_bytes); - // Verify point is on curve - if !is_on_curve_twist_bls12_381( - &point_u64, - #[cfg(feature = "hints")] - hints, - ) { - return 2; - } - - // Convert scalar from big-endian bytes to u64 limbs - let scalar_u64 = scalar_bytes_be_to_u64_le_g2(scalar_bytes); - - // Compute P * k - let product = scalar_mul_twist_bls12_381( - &point_u64, - &scalar_u64, - #[cfg(feature = "hints")] - hints, - ); - - // Check if product is infinity - if product == G2_IDENTITY { - continue; - } - - // Add to accumulator - if acc_is_inf { - acc = product; - acc_is_inf = false; - } else { - acc = add_twist_bls12_381( - &acc, - &product, - #[cfg(feature = "hints")] - hints, - ); - acc_is_inf = acc == G2_IDENTITY; - } + points.push(point_u64); + scalars.push(scalar_u64); } - // Write result - if acc_is_inf { - ret_bytes.fill(0); - return 1; - } + // Perform MSM with validation + let result = match msm_complete_twist_bls12_381( + &points, + &scalars, + #[cfg(feature = "hints")] + hints, + ) { + Ok(r) => r, + Err(code) => return code, + }; - g2_u64_le_to_bytes_be(&acc, ret_bytes); - 0 + // Encode result + if result == G2_IDENTITY { + G2_MSM_SUCCESS_INFINITY + } else { + g2_u64_le_to_bytes_be(&result, ret_bytes); + G2_MSM_SUCCESS + } } /// Convert 192-byte big-endian G2 point to [u64; 24] little-endian -/// Format: x_i (48 bytes) || x_r (48 bytes) || y_i (48 bytes) || y_r (48 bytes) pub fn g2_bytes_be_to_u64_le(bytes: &[u8; 192]) -> [u64; 24] { let mut result = [0u64; 24]; - // x_r (bytes 48-95) -> result[0..6] + // x_r (bytes 0-47) -> result[0..6] for i in 0..6 { for j in 0..8 { - result[5 - i] |= (bytes[48 + i * 8 + j] as u64) << (8 * (7 - j)); + result[5 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); } } - // x_i (bytes 0-47) -> result[6..12] + // x_i (bytes 48-95) -> result[6..12] for i in 0..6 { for j in 0..8 { - result[11 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + result[11 - i] |= (bytes[48 + i * 8 + j] as u64) << (8 * (7 - j)); } } - // y_r (bytes 144-191) -> result[12..18] + // y_r (bytes 96-143) -> result[12..18] for i in 0..6 { for j in 0..8 { - result[17 - i] |= (bytes[144 + i * 8 + j] as u64) << (8 * (7 - j)); + result[17 - i] |= (bytes[96 + i * 8 + j] as u64) << (8 * (7 - j)); } } - // y_i (bytes 96-143) -> result[18..24] + // y_i (bytes 144-191) -> result[18..24] for i in 0..6 { for j in 0..8 { - result[23 - i] |= (bytes[96 + i * 8 + j] as u64) << (8 * (7 - j)); + result[23 - i] |= (bytes[144 + i * 8 + j] as u64) << (8 * (7 - j)); } } @@ -887,51 +988,36 @@ pub fn g2_bytes_be_to_u64_le(bytes: &[u8; 192]) -> [u64; 24] { } /// Convert [u64; 24] little-endian G2 point to 192-byte big-endian -/// Format: x_i (48 bytes) || x_r (48 bytes) || y_i (48 bytes) || y_r (48 bytes) fn g2_u64_le_to_bytes_be(limbs: &[u64; 24], bytes: &mut [u8; 192]) { - // x_i (result[6..12]) -> bytes 0-47 + // x_r (limbs[0..6]) -> bytes 0-47 for i in 0..6 { - let limb = limbs[11 - i]; + let limb = limbs[5 - i]; for j in 0..8 { bytes[i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; } } - // x_r (result[0..6]) -> bytes 48-95 + // x_i (limbs[6..12]) -> bytes 48-95 for i in 0..6 { - let limb = limbs[5 - i]; + let limb = limbs[11 - i]; for j in 0..8 { bytes[48 + i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; } } - // y_i (result[18..24]) -> bytes 96-143 + // y_r (limbs[12..18]) -> bytes 96-143 for i in 0..6 { - let limb = limbs[23 - i]; + let limb = limbs[17 - i]; for j in 0..8 { bytes[96 + i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; } } - // y_r (result[12..18]) -> bytes 144-191 + // y_i (limbs[18..24]) -> bytes 144-191 for i in 0..6 { - let limb = limbs[17 - i]; + let limb = limbs[23 - i]; for j in 0..8 { bytes[144 + i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xFF) as u8; } } } - -/// Convert 32-byte big-endian scalar to [u64; 4] little-endian -fn scalar_bytes_be_to_u64_le_g2(bytes: &[u8; 32]) -> [u64; 4] { - let mut result = [0u64; 4]; - - // 32 bytes = 4 u64 limbs, remaining 2 limbs are zero - for i in 0..4 { - for j in 0..8 { - result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); - } - } - - result -} From 9bdcd588a928ef5b62dd4ffd30b4a74a473340d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:03:11 +0000 Subject: [PATCH 319/782] BN254 errors fixed --- .../src/zisklib/lib/bls12_381/curve.rs | 32 +- .../src/zisklib/lib/bls12_381/fr.rs | 2 +- .../src/zisklib/lib/bls12_381/pairing.rs | 25 +- .../src/zisklib/lib/bls12_381/twist.rs | 18 +- .../src/zisklib/lib/bn254/constants.rs | 16 +- .../entrypoint/src/zisklib/lib/bn254/curve.rs | 454 ++++++++---------- ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs | 8 +- ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs | 22 + .../entrypoint/src/zisklib/lib/bn254/mod.rs | 2 + .../src/zisklib/lib/bn254/pairing.rs | 308 ++++++------ .../entrypoint/src/zisklib/lib/bn254/twist.rs | 41 +- 11 files changed, 470 insertions(+), 458 deletions(-) create mode 100644 ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs index 8f0b41979..153c086be 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs @@ -14,21 +14,21 @@ use super::{ add_fp_bls12_381, mul_fp_bls12_381, neg_fp_bls12_381, sqrt_fp_bls12_381, square_fp_bls12_381, }, - fr::scalar_bytes_be_to_u64_le, + fr::scalar_bytes_be_to_u64_le_bls12_381, }; // TODO: Check what happens if scalar or ecc coordinates are bigger than the field size /// G1 add result codes -pub const G1_ADD_SUCCESS: u8 = 0; -pub const G1_ADD_SUCCESS_INFINITY: u8 = 1; -pub const G1_ADD_ERR_NOT_ON_CURVE: u8 = 2; +const G1_ADD_SUCCESS: u8 = 0; +const G1_ADD_SUCCESS_INFINITY: u8 = 1; +const G1_ADD_ERR_NOT_ON_CURVE: u8 = 2; /// G1 MSM result codes -pub const G1_MSM_SUCCESS: u8 = 0; -pub const G1_MSM_SUCCESS_INFINITY: u8 = 1; -pub const G1_MSM_ERR_NOT_ON_CURVE: u8 = 2; -pub const G1_MSM_ERR_NOT_IN_SUBGROUP: u8 = 3; +const G1_MSM_SUCCESS: u8 = 0; +const G1_MSM_SUCCESS_INFINITY: u8 = 1; +const G1_MSM_ERR_NOT_ON_CURVE: u8 = 2; +const G1_MSM_ERR_NOT_IN_SUBGROUP: u8 = 3; /// Decompresses a point on the BLS12-381 curve from 48 bytes /// @@ -686,8 +686,8 @@ pub unsafe extern "C" fn bls12_381_g1_add_c( let ret_bytes: &mut [u8; 96] = &mut *(ret as *mut [u8; 96]); // Parse points - let a_u64 = g1_bytes_be_to_u64_le(a_bytes); - let b_u64 = g1_bytes_be_to_u64_le(b_bytes); + let a_u64 = g1_bytes_be_to_u64_le_bls12_381(a_bytes); + let b_u64 = g1_bytes_be_to_u64_le_bls12_381(b_bytes); // Perform addition let result = match add_complete_bls12_381( @@ -704,7 +704,7 @@ pub unsafe extern "C" fn bls12_381_g1_add_c( if result == G1_IDENTITY { G1_ADD_SUCCESS_INFINITY } else { - g1_u64_le_to_bytes_be(&result, ret_bytes); + g1_u64_le_to_bytes_be_bls12_381(&result, ret_bytes); G1_ADD_SUCCESS } } @@ -742,8 +742,8 @@ pub unsafe extern "C" fn bls12_381_g1_msm_c( let scalar_bytes: &[u8; 32] = &*(pair_ptr.add(96) as *const [u8; 32]); // Parse point and scalar - let point_u64 = g1_bytes_be_to_u64_le(point_bytes); - let scalar_u64 = scalar_bytes_be_to_u64_le(scalar_bytes); + let point_u64 = g1_bytes_be_to_u64_le_bls12_381(point_bytes); + let scalar_u64 = scalar_bytes_be_to_u64_le_bls12_381(scalar_bytes); points.push(point_u64); scalars.push(scalar_u64); @@ -764,13 +764,13 @@ pub unsafe extern "C" fn bls12_381_g1_msm_c( if result == G1_IDENTITY { G1_MSM_SUCCESS_INFINITY } else { - g1_u64_le_to_bytes_be(&result, ret_bytes); + g1_u64_le_to_bytes_be_bls12_381(&result, ret_bytes); G1_MSM_SUCCESS } } /// Convert 96-byte big-endian G1 point to [u64; 12] little-endian -pub fn g1_bytes_be_to_u64_le(bytes: &[u8; 96]) -> [u64; 12] { +pub fn g1_bytes_be_to_u64_le_bls12_381(bytes: &[u8; 96]) -> [u64; 12] { let mut result = [0u64; 12]; // x-coordinate (first 48 bytes) @@ -791,7 +791,7 @@ pub fn g1_bytes_be_to_u64_le(bytes: &[u8; 96]) -> [u64; 12] { } /// Convert [u64; 12] little-endian G1 point to 96-byte big-endian -fn g1_u64_le_to_bytes_be(limbs: &[u64; 12], bytes: &mut [u8; 96]) { +fn g1_u64_le_to_bytes_be_bls12_381(limbs: &[u64; 12], bytes: &mut [u8; 96]) { // x-coordinate (first 48 bytes) for i in 0..6 { let limb = limbs[5 - i]; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs index 52320296a..f7d05fb79 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs @@ -114,7 +114,7 @@ pub fn square_fr_bls12_381( } /// Convert 32-byte big-endian scalar to [u64; 4] little-endian -pub fn scalar_bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { +pub fn scalar_bytes_be_to_u64_le_bls12_381(bytes: &[u8; 32]) -> [u64; 4] { let mut result = [0u64; 4]; for i in 0..4 { for j in 0..8 { diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs index 8e37e6387..876c1a08f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/pairing.rs @@ -5,20 +5,24 @@ use crate::zisklib::lib::utils::{eq, gt, is_one}; use super::{ constants::{G1_IDENTITY, G2_IDENTITY, P_MINUS_ONE}, curve::{ - g1_bytes_be_to_u64_le, is_on_curve_bls12_381, is_on_subgroup_bls12_381, neg_bls12_381, + g1_bytes_be_to_u64_le_bls12_381, is_on_curve_bls12_381, is_on_subgroup_bls12_381, + neg_bls12_381, }, final_exp::final_exp_bls12_381, miller_loop::{miller_loop_batch_bls12_381, miller_loop_bls12_381}, - twist::{g2_bytes_be_to_u64_le, is_on_curve_twist_bls12_381, is_on_subgroup_twist_bls12_381}, + twist::{ + g2_bytes_be_to_u64_le_bls12_381, is_on_curve_twist_bls12_381, + is_on_subgroup_twist_bls12_381, + }, }; /// Pairing check result codes -pub const PAIRING_CHECK_SUCCESS: u8 = 0; -pub const PAIRING_CHECK_FAILED: u8 = 1; -pub const PAIRING_CHECK_ERR_G1_NOT_ON_CURVE: u8 = 2; -pub const PAIRING_CHECK_ERR_G1_NOT_IN_SUBGROUP: u8 = 3; -pub const PAIRING_CHECK_ERR_G2_NOT_ON_CURVE: u8 = 4; -pub const PAIRING_CHECK_ERR_G2_NOT_IN_SUBGROUP: u8 = 5; +const PAIRING_CHECK_SUCCESS: u8 = 0; +const PAIRING_CHECK_FAILED: u8 = 1; +const PAIRING_CHECK_ERR_G1_NOT_ON_CURVE: u8 = 2; +const PAIRING_CHECK_ERR_G1_NOT_IN_SUBGROUP: u8 = 3; +const PAIRING_CHECK_ERR_G2_NOT_ON_CURVE: u8 = 4; +const PAIRING_CHECK_ERR_G2_NOT_IN_SUBGROUP: u8 = 5; /// Optimal Ate Pairing e: G1 x G2 -> GT over the BLS12-381 curve /// where G1 = E(Fp)[r] = E(Fp), G2 = E'(Fp2)[r] and GT = μ_r (the r-th roots of unity over Fp12*) @@ -239,11 +243,12 @@ pub unsafe extern "C" fn bls12_381_pairing_check_c( let mut g2_points: Vec<[u64; 24]> = Vec::with_capacity(num_pairs); for i in 0..num_pairs { let pair_ptr = pairs.add(i * 288); + let g1_bytes: &[u8; 96] = &*(pair_ptr as *const [u8; 96]); let g2_bytes: &[u8; 192] = &*(pair_ptr.add(96) as *const [u8; 192]); - g1_points.push(g1_bytes_be_to_u64_le(g1_bytes)); - g2_points.push(g2_bytes_be_to_u64_le(g2_bytes)); + g1_points.push(g1_bytes_be_to_u64_le_bls12_381(g1_bytes)); + g2_points.push(g2_bytes_be_to_u64_le_bls12_381(g2_bytes)); } match pairing_check_bls12_381( diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs index e2c6d669f..f23a4017a 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs @@ -12,7 +12,7 @@ use super::{ mul_fp2_bls12_381, neg_fp2_bls12_381, scalar_mul_fp2_bls12_381, sqrt_fp2_bls12_381, square_fp2_bls12_381, sub_fp2_bls12_381, }, - fr::scalar_bytes_be_to_u64_le, + fr::scalar_bytes_be_to_u64_le_bls12_381, }; // TODO: Check what happens if scalar or ecc coordinates are bigger than the field size @@ -869,8 +869,8 @@ pub unsafe extern "C" fn bls12_381_g2_add_c( let ret_bytes: &mut [u8; 192] = &mut *(ret as *mut [u8; 192]); // Parse points - let a_u64 = g2_bytes_be_to_u64_le(a_bytes); - let b_u64 = g2_bytes_be_to_u64_le(b_bytes); + let a_u64 = g2_bytes_be_to_u64_le_bls12_381(a_bytes); + let b_u64 = g2_bytes_be_to_u64_le_bls12_381(b_bytes); // Perform addition let result = match add_complete_twist_bls12_381( @@ -887,7 +887,7 @@ pub unsafe extern "C" fn bls12_381_g2_add_c( if result == G2_IDENTITY { G2_ADD_SUCCESS_INFINITY } else { - g2_u64_le_to_bytes_be(&result, ret_bytes); + g2_u64_le_to_bytes_be_bls12_381(&result, ret_bytes); G2_ADD_SUCCESS } } @@ -925,8 +925,8 @@ pub unsafe extern "C" fn bls12_381_g2_msm_c( let scalar_bytes: &[u8; 32] = &*(pair_ptr.add(192) as *const [u8; 32]); // Parse point and scalar - let point_u64 = g2_bytes_be_to_u64_le(point_bytes); - let scalar_u64 = scalar_bytes_be_to_u64_le(scalar_bytes); + let point_u64 = g2_bytes_be_to_u64_le_bls12_381(point_bytes); + let scalar_u64 = scalar_bytes_be_to_u64_le_bls12_381(scalar_bytes); points.push(point_u64); scalars.push(scalar_u64); @@ -947,13 +947,13 @@ pub unsafe extern "C" fn bls12_381_g2_msm_c( if result == G2_IDENTITY { G2_MSM_SUCCESS_INFINITY } else { - g2_u64_le_to_bytes_be(&result, ret_bytes); + g2_u64_le_to_bytes_be_bls12_381(&result, ret_bytes); G2_MSM_SUCCESS } } /// Convert 192-byte big-endian G2 point to [u64; 24] little-endian -pub fn g2_bytes_be_to_u64_le(bytes: &[u8; 192]) -> [u64; 24] { +pub fn g2_bytes_be_to_u64_le_bls12_381(bytes: &[u8; 192]) -> [u64; 24] { let mut result = [0u64; 24]; // x_r (bytes 0-47) -> result[0..6] @@ -988,7 +988,7 @@ pub fn g2_bytes_be_to_u64_le(bytes: &[u8; 192]) -> [u64; 24] { } /// Convert [u64; 24] little-endian G2 point to 192-byte big-endian -fn g2_u64_le_to_bytes_be(limbs: &[u64; 24], bytes: &mut [u8; 192]) { +fn g2_u64_le_to_bytes_be_bls12_381(limbs: &[u64; 24], bytes: &mut [u8; 192]) { // x_r (limbs[0..6]) -> bytes 0-47 for i in 0..6 { let limb = limbs[5 - i]; diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/constants.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/constants.rs index 10ffae89f..fe2c092e4 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/constants.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/constants.rs @@ -16,18 +16,10 @@ pub const ETWISTED_B: [u64; 8] = [ ]; /// Identity element in G1 -pub const IDENTITY_G1: [u64; 8] = { - let mut tmp = [0u64; 8]; - tmp[4] = 1; - tmp -}; +pub const G1_IDENTITY: [u64; 8] = [0u64; 8]; /// Identity element in G2 -pub const IDENTITY_G2: [u64; 16] = { - let mut tmp = [0u64; 16]; - tmp[8] = 1; - tmp -}; +pub const G2_IDENTITY: [u64; 16] = [0u64; 16]; /// Base field size pub const P: [u64; 4] = @@ -36,6 +28,10 @@ pub const P: [u64; 4] = /// Base field size minus one pub const P_MINUS_ONE: [u64; 4] = [P[0] - 1, P[1], P[2], P[3]]; +/// Scalar field size +pub const R: [u64; 4] = + [0x43E1F593F0000001, 0x2833E84879B97091, 0xB85045B68181585D, 0x30644E72E131A029]; + /// Frobenius operator constant 𝛾₁₁ := (9 + u)^((p-1)/6) pub const FROBENIUS_GAMMA11: [u64; 8] = [ 0xD60B35DADCC9E470, diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs index f35a2fbdc..250be861f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs @@ -1,18 +1,33 @@ //! Operations on the BN254 curve E: y² = x³ + 3 +use num_traits::ops::bytes; + use crate::{ syscalls::{ syscall_bn254_curve_add, syscall_bn254_curve_dbl, SyscallBn254CurveAddParams, SyscallPoint256, }, - zisklib::{eq, fcall_msb_pos_256}, + zisklib::{eq, fcall_msb_pos_256, is_zero}, }; use super::{ - constants::{E_B, IDENTITY_G1, P}, - fp::{add_fp_bn254, inv_fp_bn254, mul_fp_bn254, square_fp_bn254}, + constants::{E_B, G1_IDENTITY, P}, + fp::{add_fp_bn254, inv_fp_bn254, is_canonical_fp_bn254, mul_fp_bn254, square_fp_bn254}, + fr::{is_canonical_fr_bn254, scalar_bytes_be_to_u64_le_bn254}, }; +/// G1 add result codes +const G1_ADD_SUCCESS: u8 = 0; +const G1_ADD_SUCCESS_INFINITY: u8 = 1; +const G1_ADD_ERR_INVALID: u8 = 2; +const G1_ADD_ERR_NOT_ON_CURVE: u8 = 3; + +/// G1 MSM result codes +const G1_MUL_SUCCESS: u8 = 0; +const G1_MUL_SUCCESS_INFINITY: u8 = 1; +const G1_MUL_ERR_NOT_IN_FIELD: u8 = 2; +const G1_MUL_ERR_NOT_ON_CURVE: u8 = 3; + /// Check if a non-zero point `p` is on the BN254 curve pub fn is_on_curve_bn254(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> bool { let x: [u64; 4] = p[0..4].try_into().unwrap(); @@ -44,63 +59,12 @@ pub fn is_on_curve_bn254(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec eq(&lhs, &rhs) } -/// Converts a point `p` on the BN254 curve from Jacobian coordinates to affine coordinates -pub fn to_affine_bn254(p: &[u64; 12], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 8] { - let z: [u64; 4] = p[8..12].try_into().unwrap(); - - if z == [0u64; 4] { - return IDENTITY_G1; - } else if z == [1u64, 0, 0, 0] { - return [p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]]; - } - - let x: [u64; 4] = p[0..4].try_into().unwrap(); - let y: [u64; 4] = p[4..8].try_into().unwrap(); - - let zinv = inv_fp_bn254( - &z, - #[cfg(feature = "hints")] - hints, - ); - let zinv_sq = square_fp_bn254( - &zinv, - #[cfg(feature = "hints")] - hints, - ); - - let x_res = mul_fp_bn254( - &x, - &zinv_sq, - #[cfg(feature = "hints")] - hints, - ); - let mut y_res = mul_fp_bn254( - &y, - &zinv_sq, - #[cfg(feature = "hints")] - hints, - ); - y_res = mul_fp_bn254( - &y_res, - &zinv, - #[cfg(feature = "hints")] - hints, - ); - [x_res[0], x_res[1], x_res[2], x_res[3], y_res[0], y_res[1], y_res[2], y_res[3]] -} - -/// Adds two points `p1` and `p2` on the BN254 curve +/// Adds two non-zero points `p1` and `p2` on the BN254 curve pub fn add_bn254( p1: &[u64; 8], p2: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 8] { - if *p1 == IDENTITY_G1 { - return *p2; - } else if *p2 == IDENTITY_G1 { - return *p1; - } - let x1: [u64; 4] = p1[0..4].try_into().unwrap(); let y1: [u64; 4] = p1[4..8].try_into().unwrap(); let x2: [u64; 4] = p2[0..4].try_into().unwrap(); @@ -118,7 +82,7 @@ pub fn add_bn254( ); } else { // Return 𝒪 - return IDENTITY_G1; + return G1_IDENTITY; } } @@ -142,6 +106,91 @@ pub fn add_bn254( [x3[0], x3[1], x3[2], x3[3], y3[0], y3[1], y3[2], y3[3]] } +/// Addition of two points with validation and identity handling +pub fn add_complete_bn254( + p1: &[u64; 8], + p2: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Result<[u64; 8], u8> { + let p1_is_inf = eq(p1, &G1_IDENTITY); + let p2_is_inf = eq(p2, &G1_IDENTITY); + + // Handle identity cases + if p1_is_inf && p2_is_inf { + return Ok(G1_IDENTITY); + } + + if p1_is_inf { + // Validate p2 field elements and curve membership + let x2: [u64; 4] = p2[0..4].try_into().unwrap(); + let y2: [u64; 4] = p2[4..8].try_into().unwrap(); + if !is_canonical_fp_bn254(&x2) || !is_canonical_fp_bn254(&y2) { + return Err(G1_ADD_ERR_INVALID); + } + if !is_on_curve_bn254( + p2, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G1_ADD_ERR_NOT_ON_CURVE); + } + return Ok(*p2); + } + + if p2_is_inf { + // Validate p1 field elements and curve membership + let x1: [u64; 4] = p1[0..4].try_into().unwrap(); + let y1: [u64; 4] = p1[4..8].try_into().unwrap(); + if !is_canonical_fp_bn254(&x1) || !is_canonical_fp_bn254(&y1) { + return Err(G1_ADD_ERR_INVALID); + } + if !is_on_curve_bn254( + p1, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G1_ADD_ERR_NOT_ON_CURVE); + } + return Ok(*p1); + } + + // Both points are non-identity, validate both + let x1: [u64; 4] = p1[0..4].try_into().unwrap(); + let y1: [u64; 4] = p1[4..8].try_into().unwrap(); + if !is_canonical_fp_bn254(&x1) || !is_canonical_fp_bn254(&y1) { + return Err(G1_ADD_ERR_INVALID); + } + if !is_on_curve_bn254( + p1, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G1_ADD_ERR_NOT_ON_CURVE); + } + + let x2: [u64; 4] = p2[0..4].try_into().unwrap(); + let y2: [u64; 4] = p2[4..8].try_into().unwrap(); + if !is_canonical_fp_bn254(&x2) || !is_canonical_fp_bn254(&y2) { + return Err(G1_ADD_ERR_INVALID); + } + if !is_on_curve_bn254( + p2, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G1_ADD_ERR_NOT_ON_CURVE); + } + + // Perform addition + Ok(add_bn254( + p1, + p2, + #[cfg(feature = "hints")] + hints, + )) +} + +/// Doubles a non-zero point `p` on the BN254 curve pub fn dbl_bn254(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 8] { let mut p1 = SyscallPoint256 { x: p[0..4].try_into().unwrap(), y: p[4..8].try_into().unwrap() }; syscall_bn254_curve_dbl( @@ -152,21 +201,17 @@ pub fn dbl_bn254(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) - [p1.x[0], p1.x[1], p1.x[2], p1.x[3], p1.y[0], p1.y[1], p1.y[2], p1.y[3]] } -/// Multiplies a point `p` on the BN254 curve by a scalar `k` on the BN254 scalar field -pub fn mul_bn254( +/// Multiplies a non-zero point `p` on the BN254 curve by a scalar `k` on the BN254 scalar field +pub fn scalar_mul_bn254( p: &[u64; 8], k: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 8] { - if *p == IDENTITY_G1 { - return IDENTITY_G1; - } - // Direct cases: k = 0, k = 1, k = 2 match k { [0, 0, 0, 0] => { // Return 𝒪 - return IDENTITY_G1; + return G1_IDENTITY; } [1, 0, 0, 0] => { // Return p @@ -257,85 +302,48 @@ pub fn mul_bn254( [x3[0], x3[1], x3[2], x3[3], y3[0], y3[1], y3[2], y3[3]] } -/// Convert big-endian bytes to little-endian u64 limbs for a G1 point (64 bytes -> [u64; 8]) -/// Format: 32 bytes x (big-endian) + 32 bytes y (big-endian) -fn g1_bytes_be_to_u64_le(bytes: &[u8; 64]) -> [u64; 8] { - let mut result = [0u64; 8]; - - // Parse x coordinate (first 32 bytes, big-endian) - for i in 0..4 { - for j in 0..8 { - result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); - } +/// Scalar multiplication with validation and identity handling +pub fn mul_complete_bn254( + p: &[u64; 8], + k: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Result<[u64; 8], u8> { + if !is_canonical_fr_bn254(k) { + return Err(G1_MUL_ERR_NOT_IN_FIELD); } - // Parse y coordinate (next 32 bytes, big-endian) - for i in 0..4 { - for j in 0..8 { - result[7 - i] |= (bytes[32 + i * 8 + j] as u64) << (8 * (7 - j)); - } + // If point is infinity, result is infinity + if eq(p, &G1_IDENTITY) { + return Ok(G1_IDENTITY); } - result -} - -/// Convert little-endian u64 limbs to big-endian bytes for a G1 point ([u64; 8] -> 64 bytes) -fn g1_u64_le_to_bytes_be(point: &[u64; 8]) -> [u8; 64] { - let mut result = [0u8; 64]; - - // Encode x coordinate (first 32 bytes, big-endian) - for i in 0..4 { - for j in 0..8 { - result[i * 8 + j] = ((point[3 - i] >> (8 * (7 - j))) & 0xff) as u8; - } - } + // Point is not infinity, validate field elements and curve membership + let x: [u64; 4] = p[0..4].try_into().unwrap(); + let y: [u64; 4] = p[4..8].try_into().unwrap(); - // Encode y coordinate (next 32 bytes, big-endian) - for i in 0..4 { - for j in 0..8 { - result[32 + i * 8 + j] = ((point[7 - i] >> (8 * (7 - j))) & 0xff) as u8; - } + if !is_canonical_fp_bn254(&x) || !is_canonical_fp_bn254(&y) { + return Err(G1_MUL_ERR_NOT_IN_FIELD); } - result -} - -/// Convert big-endian bytes to little-endian u64 limbs for a scalar (32 bytes -> [u64; 4]) -fn scalar_bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { - let mut result = [0u64; 4]; - - for i in 0..4 { - for j in 0..8 { - result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); - } + if !is_on_curve_bn254( + p, + #[cfg(feature = "hints")] + hints, + ) { + return Err(G1_MUL_ERR_NOT_ON_CURVE); } - result -} - -/// Check if a field element is valid (< P) -fn is_valid_field_element(x: &[u64; 4]) -> bool { - // Compare from most significant limb - for i in (0..4).rev() { - if x[i] > P[i] { - return false; - } - if x[i] < P[i] { - return true; - } - } - // x == P, which is not valid - false + // Perform scalar multiplication + Ok(scalar_mul_bn254( + p, + k, + #[cfg(feature = "hints")] + hints, + )) } /// BN254 G1 point addition with big-endian byte format /// -/// This function is designed to patch: -/// `fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileError>` -/// -/// Input format: 64 bytes per point = 32 bytes x + 32 bytes y (big-endian) -/// Output format: 64 bytes = 32 bytes x + 32 bytes y (big-endian) -/// /// # Safety /// - `p1` must point to at least 64 bytes /// - `p2` must point to at least 64 bytes @@ -350,94 +358,39 @@ fn is_valid_field_element(x: &[u64; 4]) -> bool { pub unsafe extern "C" fn bn254_g1_add_c( p1: *const u8, p2: *const u8, - result: *mut u8, + ret: *mut u8, #[cfg(feature = "hints")] hints: &mut Vec, ) -> u8 { let p1_bytes: &[u8; 64] = &*(p1 as *const [u8; 64]); let p2_bytes: &[u8; 64] = &*(p2 as *const [u8; 64]); - - // Check if p1 is infinity (all zeros) - let p1_is_inf = p1_bytes.iter().all(|&x| x == 0); - - // Check if p2 is infinity (all zeros) - let p2_is_inf = p2_bytes.iter().all(|&x| x == 0); + let ret_bytes: &mut [u8; 64] = &mut *(ret as *mut [u8; 64]); // Convert to internal format - let p1_u64 = g1_bytes_be_to_u64_le(p1_bytes); - let p2_u64 = g1_bytes_be_to_u64_le(p2_bytes); - - // Validate field elements and curve membership for non-infinity points - if !p1_is_inf { - let x1: [u64; 4] = p1_u64[0..4].try_into().unwrap(); - let y1: [u64; 4] = p1_u64[4..8].try_into().unwrap(); - - if !is_valid_field_element(&x1) || !is_valid_field_element(&y1) { - return 1; // Invalid field element - } - - if !is_on_curve_bn254( - &p1_u64, - #[cfg(feature = "hints")] - hints, - ) { - return 1; // Not on curve - } - } - - if !p2_is_inf { - let x2: [u64; 4] = p2_u64[0..4].try_into().unwrap(); - let y2: [u64; 4] = p2_u64[4..8].try_into().unwrap(); + let p1_u64 = g1_bytes_be_to_u64_le_bn254(p1_bytes); + let p2_u64 = g1_bytes_be_to_u64_le_bn254(p2_bytes); - if !is_valid_field_element(&x2) || !is_valid_field_element(&y2) { - return 2; // Invalid field element - } - - if !is_on_curve_bn254( - &p2_u64, - #[cfg(feature = "hints")] - hints, - ) { - return 2; // Not on curve - } - } - - // Handle infinity cases - let sum = if p1_is_inf && p2_is_inf { - IDENTITY_G1 - } else if p1_is_inf { - p2_u64 - } else if p2_is_inf { - p1_u64 - } else { - add_bn254( - &p1_u64, - &p2_u64, - #[cfg(feature = "hints")] - hints, - ) + // Perform addition with validation + let result = match add_complete_bn254( + &p1_u64, + &p2_u64, + #[cfg(feature = "hints")] + hints, + ) { + Ok(r) => r, + Err(code) => return code, }; - // Convert result to big-endian bytes - let result_bytes = if sum == IDENTITY_G1 { [0u8; 64] } else { g1_u64_le_to_bytes_be(&sum) }; - - // Write result - let result_slice = core::slice::from_raw_parts_mut(result, 64); - result_slice.copy_from_slice(&result_bytes); - - 0 // Success + // Encode result + if result == G1_IDENTITY { + G1_ADD_SUCCESS_INFINITY + } else { + g1_u64_le_to_bytes_be_bn254(&result, ret_bytes); + G1_ADD_SUCCESS + } } /// BN254 G1 scalar multiplication with big-endian byte format /// -/// This function is designed to patch: -/// `fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileError>` -/// -/// Input format: -/// - point: 64 bytes = 32 bytes x + 32 bytes y (big-endian) -/// - scalar: 32 bytes (big-endian), does NOT need to be canonical -/// -/// Output format: 64 bytes = 32 bytes x + 32 bytes y (big-endian) -/// /// # Safety /// - `point` must point to at least 64 bytes /// - `scalar` must point to at least 32 bytes @@ -451,58 +404,73 @@ pub unsafe extern "C" fn bn254_g1_add_c( pub unsafe extern "C" fn bn254_g1_mul_c( point: *const u8, scalar: *const u8, - result: *mut u8, + ret: *mut u8, #[cfg(feature = "hints")] hints: &mut Vec, ) -> u8 { let point_bytes: &[u8; 64] = &*(point as *const [u8; 64]); let scalar_bytes: &[u8; 32] = &*(scalar as *const [u8; 32]); + let ret_bytes: &mut [u8; 64] = &mut *(ret as *mut [u8; 64]); - // Check if point is infinity (all zeros) - let point_is_inf = point_bytes.iter().all(|&x| x == 0); + // Convert to internal format + let point_u64 = g1_bytes_be_to_u64_le_bn254(point_bytes); + let scalar_u64 = scalar_bytes_be_to_u64_le_bn254(scalar_bytes); - // Convert point to internal format - let point_u64 = g1_bytes_be_to_u64_le(point_bytes); + // Perform scalar multiplication with validation + let product = match mul_complete_bn254( + &point_u64, + &scalar_u64, + #[cfg(feature = "hints")] + hints, + ) { + Ok(r) => r, + Err(code) => return code, + }; - // Validate field elements and curve membership for non-infinity point - if !point_is_inf { - let x: [u64; 4] = point_u64[0..4].try_into().unwrap(); - let y: [u64; 4] = point_u64[4..8].try_into().unwrap(); + // Encode result + if product == G1_IDENTITY { + G1_MUL_SUCCESS_INFINITY + } else { + g1_u64_le_to_bytes_be_bn254(&product, ret_bytes); + G1_MUL_SUCCESS + } +} - if !is_valid_field_element(&x) || !is_valid_field_element(&y) { - return 1; // Invalid field element - } +/// Convert 64-byte big-endian G1 point to [u64; 8] little-endian +pub fn g1_bytes_be_to_u64_le_bn254(bytes: &[u8; 64]) -> [u64; 8] { + let mut result = [0u64; 8]; - if !is_on_curve_bn254( - &point_u64, - #[cfg(feature = "hints")] - hints, - ) { - return 1; // Not on curve + // x-coordinate (first 32 bytes) + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); } } - // Convert scalar to internal format - let scalar_u64 = scalar_bytes_be_to_u64_le(scalar_bytes); - - // Perform scalar multiplication - let product = if point_is_inf || scalar_u64 == [0, 0, 0, 0] { - IDENTITY_G1 - } else { - mul_bn254( - &point_u64, - &scalar_u64, - #[cfg(feature = "hints")] - hints, - ) - }; + // y-coordinate (next 32 bytes) + for i in 0..4 { + for j in 0..8 { + result[7 - i] |= (bytes[32 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } - // Convert result to big-endian bytes - let result_bytes = - if product == IDENTITY_G1 { [0u8; 64] } else { g1_u64_le_to_bytes_be(&product) }; + result +} - // Write result - let result_slice = core::slice::from_raw_parts_mut(result, 64); - result_slice.copy_from_slice(&result_bytes); +/// Convert little-endian u64 limbs to big-endian bytes for a G1 point ([u64; 8] -> 64 bytes) +fn g1_u64_le_to_bytes_be_bn254(limbs: &[u64; 8], bytes: &mut [u8; 64]) { + // Encode x coordinate (first 32 bytes, big-endian) + for i in 0..4 { + let limb = limbs[3 - i]; + for j in 0..8 { + bytes[i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xff) as u8; + } + } - 0 // Success + // Encode y coordinate (next 32 bytes, big-endian) + for i in 0..4 { + let limb = limbs[7 - i]; + for j in 0..8 { + bytes[32 + i * 8 + j] = ((limb >> (8 * (7 - j))) & 0xff) as u8; + } + } } diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs index b35e4732b..08883b3c4 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs @@ -2,11 +2,17 @@ use crate::{ syscalls::{syscall_arith256_mod, SyscallArith256ModParams}, - zisklib::{eq, fcall_bn254_fp_inv}, + zisklib::{eq, fcall_bn254_fp_inv, lt}, }; use super::constants::{P, P_MINUS_ONE}; +// Checks if a 256-bit integer `x` is in canonical form +#[inline] +pub fn is_canonical_fp_bn254(x: &[u64; 4]) -> bool { + lt(x, &P) +} + /// Addition in the base field of the BN254 curve #[inline] pub fn add_fp_bn254( diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs new file mode 100644 index 000000000..ffec9a266 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs @@ -0,0 +1,22 @@ +use crate::zisklib::lt; + +use super::constants::R; + +// Checks if a 256-bit integer `x` is in canonical form +#[inline] +pub fn is_canonical_fr_bn254(x: &[u64; 4]) -> bool { + lt(x, &R) +} + +/// Convert big-endian bytes to little-endian u64 limbs for a scalar (32 bytes -> [u64; 4]) +pub fn scalar_bytes_be_to_u64_le_bn254(bytes: &[u8; 32]) -> [u64; 4] { + let mut result = [0u64; 4]; + + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + result +} diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/mod.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/mod.rs index f180eb90b..f7cf0a85b 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/mod.rs @@ -6,6 +6,7 @@ mod fp; mod fp12; mod fp2; mod fp6; +mod fr; mod miller_loop; mod pairing; mod twist; @@ -17,5 +18,6 @@ pub use fp::*; pub use fp12::*; pub use fp2::*; pub use fp6::*; +pub use fr::*; pub use pairing::*; pub use twist::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs index d97e0cde1..ca36180ea 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs @@ -1,15 +1,25 @@ //! Pairing over BN254 -use crate::zisklib::lib::utils::{gt, is_one}; +use crate::zisklib::lib::utils::{eq, gt, is_one}; use super::{ - constants::{IDENTITY_G1, IDENTITY_G2, P, P_MINUS_ONE}, - curve::is_on_curve_bn254, + constants::{G1_IDENTITY, G2_IDENTITY, P, P_MINUS_ONE}, + curve::{g1_bytes_be_to_u64_le_bn254, is_on_curve_bn254}, final_exp::final_exp_bn254, + fp::is_canonical_fp_bn254, miller_loop::{miller_loop_batch_bn254, miller_loop_bn254}, - twist::{is_on_curve_twist_bn254, is_on_subgroup_twist_bn254}, + twist::{g2_bytes_be_to_u64_le_bn254, is_on_curve_twist_bn254, is_on_subgroup_twist_bn254}, }; +/// Pairing check result codes +const PAIRING_CHECK_SUCCESS: u8 = 0; +const PAIRING_CHECK_FAILED: u8 = 1; +const PAIRING_CHECK_ERR_G1_INVALID: u8 = 2; +const PAIRING_CHECK_ERR_G1_NOT_ON_CURVE: u8 = 3; +const PAIRING_CHECK_ERR_G2_INVALID: u8 = 4; +const PAIRING_CHECK_ERR_G2_NOT_ON_CURVE: u8 = 5; +const PAIRING_CHECK_ERR_G2_NOT_IN_SUBGROUP: u8 = 6; + /// Optimal Ate Pairing e: G1 x G2 -> GT over the BN254 curve /// where G1 = E(Fp)[r] = E(Fp), G2 = E'(Fp2)[r] and GT = μ_r (the r-th roots of unity over Fp12* /// the involved curves are E/Fp: y² = x³ + 3 and E'/Fp2: y² = x³ + 3/(9+u) @@ -23,7 +33,7 @@ pub fn pairing_bn254( #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 48] { // Is p = 𝒪? - if *p == IDENTITY_G1 || *q == IDENTITY_G2 { + if *p == G1_IDENTITY || *q == G2_IDENTITY { // e(P, 𝒪) = e(𝒪, Q) = 1; let mut one = [0; 48]; one[0] = 1; @@ -68,7 +78,7 @@ pub fn pairing_batch_bn254( let mut g2_points_ml = Vec::with_capacity(num_points); for (p, q) in g1_points.iter().zip(g2_points.iter()) { // Is p = 𝒪 or q = 𝒪? - if *p == IDENTITY_G1 || *q == IDENTITY_G2 { + if *p == G1_IDENTITY || *q == G2_IDENTITY { // MillerLoop(P, 𝒪) = MillerLoop(𝒪, Q) = 1; we can skip continue; } @@ -100,208 +110,176 @@ pub fn pairing_batch_bn254( ) } -/// Check if a field element is valid (< P) -fn is_valid_field_element(x: &[u64; 4]) -> bool { - // Compare from most significant limb - for i in (0..4).rev() { - if x[i] > P[i] { - return false; - } - if x[i] < P[i] { - return true; - } - } - // x == P, which is not valid - false -} - -/// Convert big-endian bytes to little-endian u64 limbs for a G1 point (64 bytes -> [u64; 8]) -/// Format: 32 bytes x (big-endian) + 32 bytes y (big-endian) -fn g1_bytes_be_to_u64_le(bytes: &[u8; 64]) -> [u64; 8] { - let mut result = [0u64; 8]; - - // Parse x coordinate (first 32 bytes, big-endian) - for i in 0..4 { - for j in 0..8 { - result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); - } - } - - // Parse y coordinate (next 32 bytes, big-endian) - for i in 0..4 { - for j in 0..8 { - result[7 - i] |= (bytes[32 + i * 8 + j] as u64) << (8 * (7 - j)); - } - } - - result -} - -/// Convert big-endian bytes to little-endian u64 limbs for a G2 point (128 bytes -> [u64; 16]) -/// Format: 64 bytes x (32 bytes x_i + 32 bytes x_r) + 64 bytes y (32 bytes y_i + 32 bytes y_r) -/// Note: In BN254, Fq2 elements are encoded as (imaginary, real), i.e., y + x*u -fn g2_bytes_be_to_u64_le(bytes: &[u8; 128]) -> [u64; 16] { - let mut result = [0u64; 16]; - - // Parse x coordinate (Fq2: first 32 bytes = x_i, next 32 bytes = x_r) - // Internal format: x_r at [0..4], x_i at [4..8] - - // x_i (imaginary part, first 32 bytes) - for i in 0..4 { - for j in 0..8 { - result[7 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); - } - } - - // x_r (real part, next 32 bytes) - for i in 0..4 { - for j in 0..8 { - result[3 - i] |= (bytes[32 + i * 8 + j] as u64) << (8 * (7 - j)); - } - } - - // Parse y coordinate (Fq2: next 32 bytes = y_i, final 32 bytes = y_r) - // Internal format: y_r at [8..12], y_i at [12..16] - - // y_i (imaginary part) - for i in 0..4 { - for j in 0..8 { - result[15 - i] |= (bytes[64 + i * 8 + j] as u64) << (8 * (7 - j)); - } - } - - // y_r (real part) - for i in 0..4 { - for j in 0..8 { - result[11 - i] |= (bytes[96 + i * 8 + j] as u64) << (8 * (7 - j)); - } - } - - result -} - -/// BN254 pairing check with big-endian byte format +/// BN254 pairing check with validation. /// -/// This function is designed to patch: -/// `fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result` +/// Validates all points have canonical field elements, are on curve, and G2 points are in subgroup. /// -/// Input format per pair: -/// - G1 point: 64 bytes = 32 bytes x + 32 bytes y (big-endian) -/// - G2 point: 128 bytes = 64 bytes x (32 x_i + 32 x_r) + 64 bytes y (32 y_i + 32 y_r) (big-endian) -/// -/// # Safety -/// - `pairs` must point to an array of `num_pairs` pair structures -/// - Each pair is: 8 bytes g1_ptr + 8 bytes g2_ptr (pointers to the actual data) +/// # Arguments +/// * `g1_points` - Slice of G1 points as [u64; 8] +/// * `g2_points` - Slice of G2 points as [u64; 16] /// /// # Returns -/// - 1 if the pairing check passes (the product of pairings equals 1 in GT) -/// - 0 if the pairing check fails -/// - 2 if there was a parsing error (invalid input) -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_bn254_pairing_check_c")] -pub unsafe extern "C" fn bn254_pairing_check_c( - g1_ptrs: *const *const u8, - g2_ptrs: *const *const u8, - num_pairs: usize, +/// * `Ok(true)` - Pairing check passed (product of pairings == 1) +/// * `Ok(false)` - Pairing check failed (product of pairings != 1) +/// * `Err(PAIRING_CHECK_ERR_G1_INVALID)` - G1 field element not canonical (>= P) +/// * `Err(PAIRING_CHECK_ERR_G1_NOT_ON_CURVE)` - G1 point not on curve +/// * `Err(PAIRING_CHECK_ERR_G2_INVALID)` - G2 field element not canonical (>= P) +/// * `Err(PAIRING_CHECK_ERR_G2_NOT_ON_CURVE)` - G2 point not on twist curve +/// * `Err(PAIRING_CHECK_ERR_G2_NOT_IN_SUBGROUP)` - G2 point not in subgroup +pub fn pairing_check_bn254( + g1_points: &[[u64; 8]], + g2_points: &[[u64; 16]], #[cfg(feature = "hints")] hints: &mut Vec, -) -> u8 { - // Handle empty input - empty product is 1, so pairing check passes - if num_pairs == 0 { - return 1; - } - - let mut g1_points: Vec<[u64; 8]> = Vec::with_capacity(num_pairs); - let mut g2_points: Vec<[u64; 16]> = Vec::with_capacity(num_pairs); - - for i in 0..num_pairs { - let g1_ptr = *g1_ptrs.add(i); - let g2_ptr = *g2_ptrs.add(i); - - let g1_bytes: &[u8; 64] = &*(g1_ptr as *const [u8; 64]); - let g2_bytes: &[u8; 128] = &*(g2_ptr as *const [u8; 128]); - - // Check if G1 point is infinity - let g1_is_inf = g1_bytes.iter().all(|&x| x == 0); - - // Check if G2 point is infinity - let g2_is_inf = g2_bytes.iter().all(|&x| x == 0); - - // If either point is infinity, skip this pair (contributes 1 to product) - if g1_is_inf || g2_is_inf { +) -> Result { + assert_eq!(g1_points.len(), g2_points.len(), "Number of G1 and G2 points must be equal"); + + // Collect valid pairs + let mut g1_valid = Vec::with_capacity(g1_points.len()); + let mut g2_valid = Vec::with_capacity(g2_points.len()); + for (g1, g2) in g1_points.iter().zip(g2_points.iter()) { + let g1_is_inf = eq(g1, &G1_IDENTITY); + let g2_is_inf = eq(g2, &G2_IDENTITY); + + // If p = 𝒪 or q = 𝒪 => MillerLoop(P, 𝒪) = MillerLoop(𝒪, Q) = 1; we can skip + if g2_is_inf { + if !g1_is_inf { + if !is_on_curve_bn254( + g1, + #[cfg(feature = "hints")] + hints, + ) { + return Err(PAIRING_CHECK_ERR_G1_NOT_ON_CURVE); + } + } continue; } - // Convert G1 from big-endian bytes to u64 limbs - let g1_u64 = g1_bytes_be_to_u64_le(g1_bytes); + if g1_is_inf { + if !is_on_curve_twist_bn254( + g2, + #[cfg(feature = "hints")] + hints, + ) { + return Err(PAIRING_CHECK_ERR_G2_NOT_ON_CURVE); + } + if !is_on_subgroup_twist_bn254( + g2, + #[cfg(feature = "hints")] + hints, + ) { + return Err(PAIRING_CHECK_ERR_G2_NOT_IN_SUBGROUP); + } + continue; + } - // Validate G1 field elements - let x1: [u64; 4] = g1_u64[0..4].try_into().unwrap(); - let y1: [u64; 4] = g1_u64[4..8].try_into().unwrap(); - if !is_valid_field_element(&x1) || !is_valid_field_element(&y1) { - return 2; // Invalid field element + // Validate G1 point field elements + let x1: [u64; 4] = g1[0..4].try_into().unwrap(); + let y1: [u64; 4] = g1[4..8].try_into().unwrap(); + if !is_canonical_fp_bn254(&x1) || !is_canonical_fp_bn254(&y1) { + return Err(PAIRING_CHECK_ERR_G1_INVALID); } // Verify G1 point is on curve if !is_on_curve_bn254( - &g1_u64, + g1, #[cfg(feature = "hints")] hints, ) { - return 2; + return Err(PAIRING_CHECK_ERR_G1_NOT_ON_CURVE); } - // Convert G2 from big-endian bytes to u64 limbs - let g2_u64 = g2_bytes_be_to_u64_le(g2_bytes); - - // Validate G2 field elements (4 field elements for Fq2) - let x2_r: [u64; 4] = g2_u64[0..4].try_into().unwrap(); - let x2_i: [u64; 4] = g2_u64[4..8].try_into().unwrap(); - let y2_r: [u64; 4] = g2_u64[8..12].try_into().unwrap(); - let y2_i: [u64; 4] = g2_u64[12..16].try_into().unwrap(); - if !is_valid_field_element(&x2_r) - || !is_valid_field_element(&x2_i) - || !is_valid_field_element(&y2_r) - || !is_valid_field_element(&y2_i) + // Validate G2 point field elements + let x2_r: [u64; 4] = g2[0..4].try_into().unwrap(); + let x2_i: [u64; 4] = g2[4..8].try_into().unwrap(); + let y2_r: [u64; 4] = g2[8..12].try_into().unwrap(); + let y2_i: [u64; 4] = g2[12..16].try_into().unwrap(); + if !is_canonical_fp_bn254(&x2_r) + || !is_canonical_fp_bn254(&x2_i) + || !is_canonical_fp_bn254(&y2_r) + || !is_canonical_fp_bn254(&y2_i) { - return 2; // Invalid field element + return Err(PAIRING_CHECK_ERR_G2_INVALID); } // Verify G2 point is on twist curve if !is_on_curve_twist_bn254( - &g2_u64, + g2, #[cfg(feature = "hints")] hints, ) { - return 2; + return Err(PAIRING_CHECK_ERR_G2_NOT_ON_CURVE); } // Verify G2 point is in subgroup if !is_on_subgroup_twist_bn254( - &g2_u64, + g2, #[cfg(feature = "hints")] hints, ) { - return 2; + return Err(PAIRING_CHECK_ERR_G2_NOT_IN_SUBGROUP); } - g1_points.push(g1_u64); - g2_points.push(g2_u64); + g1_valid.push(*g1); + g2_valid.push(*g2); } - // If all pairs were skipped (all infinities), result is 1 - if g1_points.is_empty() { - return 1; + // If all pairs were skipped, result is 1 + if g1_valid.is_empty() { + return Ok(true); } // Compute batch pairing and check if result is 1 - if is_one(&pairing_batch_bn254( + Ok(is_one(&pairing_batch_bn254( + &g1_valid, + &g2_valid, + #[cfg(feature = "hints")] + hints, + ))) +} + +/// BN254 pairing check with big-endian byte format +/// +/// # Safety +/// - `pairs` must point to an array of `num_pairs * 192` bytes +/// Each pair is: 64 bytes G1 point + 128 bytes G2 point +/// +/// # Returns +/// - 0 = pairing check passed +/// - 1 = pairing check failed +/// - 2 = G1 field element invalid +/// - 3 = G1 point not on curve +/// - 4 = G2 field element invalid +/// - 5 = G2 point not on curve +/// - 6 = G2 point not in subgroup +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_bn254_pairing_check_c")] +pub unsafe extern "C" fn bn254_pairing_check_c( + pairs: *const u8, + num_pairs: usize, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> u8 { + // Parse all pairs + let mut g1_points: Vec<[u64; 8]> = Vec::with_capacity(num_pairs); + let mut g2_points: Vec<[u64; 16]> = Vec::with_capacity(num_pairs); + for i in 0..num_pairs { + let pair_ptr = pairs.add(i * 192); + + let g1_bytes: &[u8; 64] = &*(pair_ptr as *const [u8; 64]); + let g2_bytes: &[u8; 128] = &*(pair_ptr.add(64) as *const [u8; 128]); + + g1_points.push(g1_bytes_be_to_u64_le_bn254(g1_bytes)); + g2_points.push(g2_bytes_be_to_u64_le_bn254(g2_bytes)); + } + + // Perform pairing check with validation + match pairing_check_bn254( &g1_points, &g2_points, #[cfg(feature = "hints")] hints, - )) { - 1 // Pairing check passed - } else { - 0 // Pairing check failed + ) { + Ok(true) => PAIRING_CHECK_SUCCESS, + Ok(false) => PAIRING_CHECK_FAILED, + Err(code) => code, } } diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs index 0e904d1a8..ef619e571 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/twist.rs @@ -3,7 +3,7 @@ use crate::zisklib::lib::utils::eq; use super::{ - constants::{ETWISTED_B, E_B, FROBENIUS_GAMMA12, FROBENIUS_GAMMA13, IDENTITY_G2}, + constants::{ETWISTED_B, E_B, FROBENIUS_GAMMA12, FROBENIUS_GAMMA13, G2_IDENTITY}, fp2::{ add_fp2_bn254, conjugate_fp2_bn254, dbl_fp2_bn254, inv_fp2_bn254, mul_fp2_bn254, neg_fp2_bn254, scalar_mul_fp2_bn254, square_fp2_bn254, sub_fp2_bn254, @@ -117,7 +117,7 @@ pub fn to_affine_twist_bn254( let z: [u64; 8] = p[16..24].try_into().unwrap(); if z == [0u64; 8] { - return IDENTITY_G2; + return G2_IDENTITY; } else if z == [1u64, 0, 0, 0, 0, 0, 0, 0] { return [ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], @@ -186,7 +186,7 @@ pub fn add_twist_bn254( ); } else { // Points are the inverse of each other, return the point at infinity - return IDENTITY_G2; + return G2_IDENTITY; } } @@ -422,3 +422,38 @@ pub fn utf_endomorphism_twist_bn254( qy[5], qy[6], qy[7], ] } + +/// Convert 128-byte big-endian G2 point to [u64; 16] little-endian +pub fn g2_bytes_be_to_u64_le_bn254(bytes: &[u8; 128]) -> [u64; 16] { + let mut result = [0u64; 16]; + + // x_i (bytes 0-31) -> result[4..8] + for i in 0..4 { + for j in 0..8 { + result[7 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // x_r (bytes 32-63) -> result[0..4] + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[32 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // y_i (bytes 64-95) -> result[12..16] + for i in 0..4 { + for j in 0..8 { + result[15 - i] |= (bytes[64 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // y_r (bytes 96-127) -> result[8..12] + for i in 0..4 { + for j in 0..8 { + result[11 - i] |= (bytes[96 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + result +} From c6fb7200a84b7c6c230e9b9ac816134ab0825cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:39:37 +0000 Subject: [PATCH 320/782] Secp256k1 errors fixed --- .../src/zisklib/lib/bn254/pairing.rs | 10 +- .../src/zisklib/lib/secp256k1/constants.rs | 3 + .../src/zisklib/lib/secp256k1/curve.rs | 77 +---- .../src/zisklib/lib/secp256k1/ecrecover.rs | 323 +++++++++++------- 4 files changed, 216 insertions(+), 197 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs index ca36180ea..b966c381d 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs @@ -142,14 +142,14 @@ pub fn pairing_check_bn254( // If p = 𝒪 or q = 𝒪 => MillerLoop(P, 𝒪) = MillerLoop(𝒪, Q) = 1; we can skip if g2_is_inf { - if !g1_is_inf { - if !is_on_curve_bn254( + if !g1_is_inf + && !is_on_curve_bn254( g1, #[cfg(feature = "hints")] hints, - ) { - return Err(PAIRING_CHECK_ERR_G1_NOT_ON_CURVE); - } + ) + { + return Err(PAIRING_CHECK_ERR_G1_NOT_ON_CURVE); } continue; } diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs index 6bbe4197c..cbd2cde63 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs @@ -15,6 +15,9 @@ pub const NQR: [u64; 4] = [3, 0, 0, 0]; pub const N: [u64; 4] = [0xBFD25E8CD0364141, 0xBAAEDCE6AF48A03B, 0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFF]; pub const N_MINUS_ONE: [u64; 4] = [N[0] - 1, N[1], N[2], N[3]]; +pub const N_HALF: [u64; 4] = + [0xDFE92F46681B20A0, 0x5D576E7357A4501D, 0xFFFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF]; +pub const N_HALF_PLUS_ONE: [u64; 4] = [N_HALF[0] + 1, N_HALF[1], N_HALF[2], N_HALF[3]]; /// Secp256k1 group identity point pub const IDENTITY_X: [u64; 4] = [0; 4]; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index 3d17b0175..d65fd1293 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -63,49 +63,36 @@ pub fn secp256k1_decompress( Ok((*x, y)) } -/// Converts a non-infinity point `p` on the Secp256k1 curve from projective coordinates to affine coordinates -pub fn secp256k1_to_affine( - p: &[u64; 12], - #[cfg(feature = "hints")] hints: &mut Vec, -) -> [u64; 8] { - let z: [u64; 4] = [p[8], p[9], p[10], p[11]]; - - // Point at infinity cannot be converted to affine - debug_assert!(z != ZERO_256, "Cannot convert point at infinity to affine"); +/// Checks whether the given point `p` is on the Secp256k1 curve. +/// It assumes that `p` is not the point at infinity. +pub fn secp256k1_is_on_curve(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> bool { + let x: [u64; 4] = p[0..4].try_into().unwrap(); + let y: [u64; 4] = p[4..8].try_into().unwrap(); - let zinv = secp256k1_fp_inv( - &z, + // p in E iff y² == x³ + 7 + let lhs = secp256k1_fp_square( + &y, #[cfg(feature = "hints")] hints, ); - let zinv_sq = secp256k1_fp_square( - &zinv, + let mut rhs = secp256k1_fp_square( + &x, #[cfg(feature = "hints")] hints, ); - - let x: [u64; 4] = [p[0], p[1], p[2], p[3]]; - let y: [u64; 4] = [p[4], p[5], p[6], p[7]]; - - let x_res = secp256k1_fp_mul( + rhs = secp256k1_fp_mul( + &rhs, &x, - &zinv_sq, #[cfg(feature = "hints")] hints, ); - let y_res = secp256k1_fp_mul( - &secp256k1_fp_mul( - &y, - &zinv_sq, - #[cfg(feature = "hints")] - hints, - ), - &zinv, + rhs = secp256k1_fp_add( + &rhs, + &E_B, #[cfg(feature = "hints")] hints, ); - - [x_res[0], x_res[1], x_res[2], x_res[3], y_res[0], y_res[1], y_res[2], y_res[3]] + eq(&lhs, &rhs) } /// Given points `p1` and `p2`, performs the point addition `p1 + p2` and assigns the result to `p1`. @@ -138,38 +125,6 @@ fn secp256k1_add_non_infinity_points( } } -/// Checks whether the given point `p` is on the Secp256k1 curve. -/// It assumes that `p` is not the point at infinity. -pub fn secp256k1_is_on_curve(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> bool { - let x: [u64; 4] = p[0..4].try_into().unwrap(); - let y: [u64; 4] = p[4..8].try_into().unwrap(); - - // p in E iff y² == x³ + 7 - let lhs = secp256k1_fp_square( - &y, - #[cfg(feature = "hints")] - hints, - ); - let mut rhs = secp256k1_fp_square( - &x, - #[cfg(feature = "hints")] - hints, - ); - rhs = secp256k1_fp_mul( - &rhs, - &x, - #[cfg(feature = "hints")] - hints, - ); - rhs = secp256k1_fp_add( - &rhs, - &E_B, - #[cfg(feature = "hints")] - hints, - ); - eq(&lhs, &rhs) -} - /// Given a non-infinity point `p` and a scalar `k`, computes the scalar multiplication `k·p` /// /// Note: There are no (non-infinity) points of order 2 in Secp256k1. diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs index 0f445c4a1..d48e6d4a6 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs @@ -3,12 +3,12 @@ use crate::{ syscall_secp256k1_add, syscall_secp256k1_dbl, SyscallPoint256, SyscallSecp256k1AddParams, }, zisklib::{ - eq, fcall_msb_pos_256, fcall_secp256k1_ecdsa_verify, is_one, ONE_256, TWO_256, ZERO_256, + eq, fcall_msb_pos_256, fcall_secp256k1_ecdsa_verify, is_one, lt, ONE_256, TWO_256, ZERO_256, }, }; use super::{ - constants::{E_B, G, G_X, G_Y, IDENTITY_X, IDENTITY_Y, N, P}, + constants::{E_B, G, G_X, G_Y, IDENTITY_X, IDENTITY_Y, N, N_HALF_PLUS_ONE, P}, curve::{ secp256k1_decompress, secp256k1_double_scalar_mul_with_g, secp256k1_is_on_curve, secp256k1_scalar_mul, secp256k1_triple_scalar_mul_with_g, @@ -25,104 +25,61 @@ use super::{ use tiny_keccak::{Hasher, Keccak}; -/// Convert big-endian bytes to little-endian u64 limbs (32 bytes -> [u64; 4]) -fn bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { - let mut result = [0u64; 4]; - for i in 0..4 { - for j in 0..8 { - result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); - } - } - result -} +/// Ecrecover result codes +pub const ECRECOVER_SUCCESS: u8 = 0; +pub const ECRECOVER_ERR_INVALID_R: u8 = 1; +pub const ECRECOVER_ERR_INVALID_S: u8 = 2; +pub const ECRECOVER_ERR_INVALID_RECID: u8 = 3; +pub const ECRECOVER_ERR_POINT_NOT_ON_CURVE: u8 = 4; +pub const ECRECOVER_ERR_RECOVERY_FAILED: u8 = 5; -/// Convert little-endian u64 limbs to big-endian bytes ([u64; 4] -> 32 bytes) -fn u64_le_to_bytes_be(limbs: &[u64; 4]) -> [u8; 32] { - let mut result = [0u8; 32]; - for i in 0..4 { - for j in 0..8 { - result[i * 8 + j] = ((limbs[3 - i] >> (8 * (7 - j))) & 0xff) as u8; - } - } - result -} - -/// Check if a scalar is valid (0 < x < N) -fn is_valid_scalar(x: &[u64; 4]) -> bool { - // Must be non-zero - if *x == ZERO_256 { - return false; - } - // Must be less than N - for i in (0..4).rev() { - if x[i] > N[i] { - return false; - } - if x[i] < N[i] { - return true; - } - } - // x == N, not valid - false -} - -/// Check if r is valid for ecrecover (0 < r < P for the base case) -/// When recid >= 2, we need r + N < P -fn is_valid_r(r: &[u64; 4], recid: u8) -> bool { - // Must be non-zero - if *r == ZERO_256 { - return false; - } - - if recid >= 2 { - // Need to check r + N < P (which is practically never true for secp256k1) - // Since N is very close to P, r + N will almost always exceed P - // This case is extremely rare in practice - // For simplicity, we can add r + N and check if it's < P - // But practically this never happens, so we can return false - return false; - } - - // Must be less than P (field modulus) for recovery - for i in (0..4).rev() { - if r[i] > P[i] { - return false; - } - if r[i] < P[i] { - return true; - } - } - // r == P, not valid - false -} - -/// Recover the public key from an ECDSA signature +/// Recover the public key point from an ECDSA signature /// /// The recovery formula is: /// R = (r, y) where y is recovered from r using the curve equation -/// PK = r⁻¹ * (s*R - z*G) +/// PK = r⁻¹ * (s*R - z*G) = u1*G + u2*R where u1 = -z*r⁻¹ and u2 = s*r⁻¹ /// -/// Returns the recovered public key as [u64; 8] (x, y coordinates) or None if recovery fails +/// # Arguments +/// * `r` - Signature r value as [u64; 4] little-endian +/// * `s` - Signature s value as [u64; 4] little-endian +/// * `z` - Message hash as [u64; 4] little-endian +/// * `recid` - Recovery ID (0, 1, 2, or 3) +/// +/// # Returns +/// * `Ok([u64; 8])` - Recovered public key (x, y coordinates) +/// * `Err(u8)` - Error code +// TODO: Use triple scalar mul! pub fn secp256k1_ecrecover_point( r: &[u64; 4], s: &[u64; 4], z: &[u64; 4], recid: u8, + require_low_s: bool, #[cfg(feature = "hints")] hints: &mut Vec, -) -> Option<[u64; 8]> { - // Validate r and s - if !is_valid_scalar(r) || !is_valid_scalar(s) { - return None; +) -> Result<[u64; 8], u8> { + // Validate recid + // The recid is a value in the range [0, 3] + // However, the upper two values (2 and 3) are, representing infinity values, invalid + if recid > 1 { + return Err(ECRECOVER_ERR_INVALID_RECID); + } + + // Validate r + if !is_valid_r(r) { + return Err(ECRECOVER_ERR_INVALID_R); + } + + // Validate s + if require_low_s { + if !is_valid_s_low(s) { + return Err(ECRECOVER_ERR_INVALID_S); + } + } else if !is_valid_s(s) { + return Err(ECRECOVER_ERR_INVALID_S); } // Determine the x-coordinate of R - // If recid >= 2, x = r + N (but this is extremely rare and usually invalid) - let x = if recid >= 2 { - // r + N would need to be < P, which is practically never true - return None; - } else { - *r - }; + let x = *r; // Recover the y-coordinate from x // y² = x³ + 7 @@ -133,7 +90,7 @@ pub fn secp256k1_ecrecover_point( #[cfg(feature = "hints")] hints, ) - .ok()?; + .map_err(|_| ECRECOVER_ERR_POINT_NOT_ON_CURVE)?; let r_point = [rx[0], rx[1], rx[2], rx[3], ry[0], ry[1], ry[2], ry[3]]; @@ -172,57 +129,85 @@ pub fn secp256k1_ecrecover_point( &r_point, #[cfg(feature = "hints")] hints, - )?; + ) + .ok_or(ECRECOVER_ERR_RECOVERY_FAILED)?; - Some(pk) + Ok(pk) } -/// Recover the Ethereum address from an ECDSA signature -/// -/// This function is designed to patch: -/// `fn secp256k1_ecrecover(&self, sig: &[u8; 64], recid: u8, msg: &[u8; 32]) -> Result<[u8; 32], PrecompileError>` +/// Recover the Ethereum address from an ECDSA signature (for precompile) /// /// # Arguments -/// * `sig` - 64 bytes: r (32 bytes) || s (32 bytes), big-endian -/// * `recid` - Recovery ID (0, 1, 2, or 3) -/// * `msg` - 32 bytes message hash (big-endian) +/// * `r` - Signature r value as [u64; 4] little-endian +/// * `s` - Signature s value as [u64; 4] little-endian +/// * `z` - Message hash as [u64; 4] little-endian +/// * `recid` - Recovery ID (0 or 1) /// /// # Returns -/// * 32 bytes where the first 12 bytes are 0 and the last 20 bytes are the Ethereum address -/// * Returns all zeros if recovery fails +/// * `Ok([u8; 32])` - First 12 bytes are 0, last 20 bytes are the Ethereum address +/// * `Err(u8)` - Error code pub fn secp256k1_ecrecover( - sig: &[u8; 64], + r: &[u64; 4], + s: &[u64; 4], + z: &[u64; 4], recid: u8, - msg: &[u8; 32], #[cfg(feature = "hints")] hints: &mut Vec, -) -> Option<[u8; 32]> { - // Parse r and s from signature - let r_bytes: [u8; 32] = sig[0..32].try_into().unwrap(); - let s_bytes: [u8; 32] = sig[32..64].try_into().unwrap(); +) -> Result<[u8; 32], u8> { + // Recover the public key point + let pk = secp256k1_ecrecover_point( + r, + s, + z, + recid, + false, + #[cfg(feature = "hints")] + hints, + )?; - let r = bytes_be_to_u64_le(&r_bytes); - let s = bytes_be_to_u64_le(&s_bytes); - let z = bytes_be_to_u64_le(msg); + Ok(pubkey_to_address(&pk)) +} +/// Recover the Ethereum address from an ECDSA signature (for tx recovery with low S) +/// +/// # Arguments +/// * `r` - Signature r value as [u64; 4] little-endian +/// * `s` - Signature s value as [u64; 4] little-endian +/// * `z` - Message hash as [u64; 4] little-endian +/// * `recid` - Recovery ID (0 or 1) +/// +/// # Returns +/// * `Ok([u8; 32])` - First 12 bytes are 0, last 20 bytes are the Ethereum address +/// * `Err(u8)` - Error code +pub fn secp256k1_ecrecover_tx( + r: &[u64; 4], + s: &[u64; 4], + z: &[u64; 4], + recid: u8, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Result<[u8; 32], u8> { // Recover the public key point let pk = secp256k1_ecrecover_point( - &r, - &s, - &z, + r, + s, + z, recid, + true, #[cfg(feature = "hints")] hints, )?; - // Convert public key to uncompressed format (65 bytes: 0x04 || x || y) - // But for keccak hashing, we only use x || y (64 bytes) + Ok(pubkey_to_address(&pk)) +} + +/// Convert a public key point to an Ethereum address +fn pubkey_to_address(pk: &[u64; 8]) -> [u8; 32] { let x = [pk[0], pk[1], pk[2], pk[3]]; let y = [pk[4], pk[5], pk[6], pk[7]]; let x_bytes = u64_le_to_bytes_be(&x); let y_bytes = u64_le_to_bytes_be(&y); - // Concatenate x and y for hashing + // Concatenate x and y for hashing (64 bytes) let mut pk_bytes = [0u8; 64]; pk_bytes[0..32].copy_from_slice(&x_bytes); pk_bytes[32..64].copy_from_slice(&y_bytes); @@ -237,20 +222,30 @@ pub fn secp256k1_ecrecover( let mut result = [0u8; 32]; result[12..32].copy_from_slice(&hash[12..32]); - Some(result) + result } /// C-compatible wrapper for secp256k1_ecrecover /// /// # Safety -/// - `sig` must point to at least 64 bytes -/// - `msg` must point to at least 32 bytes +/// - `sig` must point to at least 64 bytes (r || s, big-endian) +/// - `msg` must point to at least 32 bytes (message hash, big-endian) /// - `output` must point to a writable buffer of at least 32 bytes /// +/// # Arguments +/// - `sig` - 64 bytes: r (32 bytes) || s (32 bytes), big-endian +/// - `recid` - Recovery ID (0 or 1) +/// - `msg` - 32 bytes message hash, big-endian +/// - `output` - Output buffer for the recovered address (32 bytes) +/// - `require_low_s` - If true, require s <= N/2 (for tx recovery); if false, allow s < N (for precompile) +/// /// # Returns -/// - 0 if recovery succeeded -/// - 1 if recovery failed (invalid signature or point not on curve) -// TODO: This function has two modes: tx recovery and precompile. Check it is correct +/// - 0 = success +/// - 1 = invalid r (not in [1, N)) +/// - 2 = invalid s (not in [1, N) or [1, N/2] depending on require_low_s) +/// - 3 = invalid recid (not 0 or 1) +/// - 4 = point not on curve (no valid y for given r) +/// - 5 = recovery failed (result is point at infinity) #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_secp256k1_ecrecover_c")] pub unsafe extern "C" fn secp256k1_ecrecover_c( @@ -258,28 +253,94 @@ pub unsafe extern "C" fn secp256k1_ecrecover_c( recid: u8, msg: *const u8, output: *mut u8, + require_low_s: bool, #[cfg(feature = "hints")] hints: &mut Vec, ) -> u8 { let sig_bytes: &[u8; 64] = &*(sig as *const [u8; 64]); let msg_bytes: &[u8; 32] = &*(msg as *const [u8; 32]); + let output_bytes: &mut [u8; 32] = &mut *(output as *mut [u8; 32]); - match secp256k1_ecrecover( - sig_bytes, + // Parse r, s, z from big-endian bytes + let r_bytes: [u8; 32] = sig_bytes[0..32].try_into().unwrap(); + let s_bytes: [u8; 32] = sig_bytes[32..64].try_into().unwrap(); + + let r = bytes_be_to_u64_le(&r_bytes); + let s = bytes_be_to_u64_le(&s_bytes); + let z = bytes_be_to_u64_le(msg_bytes); + + // Perform ecrecover + match secp256k1_ecrecover_point( + &r, + &s, + &z, recid, - msg_bytes, + require_low_s, #[cfg(feature = "hints")] hints, ) { - Some(result) => { - let output_slice = core::slice::from_raw_parts_mut(output, 32); - output_slice.copy_from_slice(&result); - 0 // Success + Ok(pk) => { + let result = pubkey_to_address(&pk); + output_bytes.copy_from_slice(&result); + ECRECOVER_SUCCESS } - None => { - // Zero out output on failure - let output_slice = core::slice::from_raw_parts_mut(output, 32); - output_slice.fill(0); - 1 // Failure + Err(code) => { + output_bytes.fill(0); + code } } } + +/// Convert big-endian bytes to little-endian u64 limbs (32 bytes -> [u64; 4]) +fn bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { + let mut result = [0u64; 4]; + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + result +} + +/// Convert little-endian u64 limbs to big-endian bytes ([u64; 4] -> 32 bytes) +fn u64_le_to_bytes_be(limbs: &[u64; 4]) -> [u8; 32] { + let mut result = [0u8; 32]; + for i in 0..4 { + for j in 0..8 { + result[i * 8 + j] = ((limbs[3 - i] >> (8 * (7 - j))) & 0xff) as u8; + } + } + result +} + +/// Check if r is valid: 0 < r < N +fn is_valid_r(r: &[u64; 4]) -> bool { + if *r == ZERO_256 { + return false; + } + if lt(r, &N) { + return true; + } + false +} + +/// Check if s is valid for precompile: 0 < s < N +fn is_valid_s(s: &[u64; 4]) -> bool { + if *s == ZERO_256 { + return false; + } + if lt(s, &N) { + return true; + } + false +} + +/// Check if s is valid for tx recovery (low S): 0 < s <= N/2 +fn is_valid_s_low(s: &[u64; 4]) -> bool { + if *s == ZERO_256 { + return false; + } + if lt(s, &N_HALF_PLUS_ONE) { + return true; + } + false +} From 4d83488efb445edf15005632c43d2723c6dd2493 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 23 Jan 2026 12:01:06 +0000 Subject: [PATCH 321/782] adapt hint handlers to new secp256k1_ecrecover_c and bn254_pairing_check_c fn API --- ziskos-hints/src/handlers/bn254.rs | 68 +------------------------- ziskos-hints/src/handlers/mod.rs | 1 + ziskos-hints/src/handlers/secp256k1.rs | 5 +- 3 files changed, 6 insertions(+), 68 deletions(-) diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 2a5950c8c..07eafa340 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -52,19 +52,6 @@ pub fn bn254_g1_mul_hint(data: &[u64]) -> Result> { /// Processes an `HINT_BN254_PAIRING_CHECK` hint. #[inline] pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { - // --------------------------------------------------------------------- - // Input format (INTERLEAVED): - // - // data[0] = num_pairs - // data[1 ..] = Interleaved pairs: [G1[0], G2[0], G1[1], G2[1], ...] - // - // Each G1 point: 8 u64 = 64 bytes - // Each G2 point: 16 u64 = 128 bytes - // Each pair: 24 u64 = 192 bytes - // - // All data is interpreted in *native-endian* u64 layout. - // --------------------------------------------------------------------- - const G1_WORDS: usize = 8; const G2_WORDS: usize = 16; const PAIR_WORDS: usize = G1_WORDS + G2_WORDS; @@ -84,65 +71,12 @@ pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { validate_hint_length(data, expected_len, "PAIRING_BATCH_BN254")?; - // Extract interleaved pairs - // --------------------------------------------------------------------- - // SAFETY INVARIANTS: - // - Length has been validated exactly - // - &[u64] memory is contiguous - // - We only create immutable views - // - Alignment is safe (u8 alignment = 1) - // - Endianness is intentionally native - // --------------------------------------------------------------------- - let pairs_data = &data[1..]; - let mut g1_points = Vec::with_capacity(num_pairs); - let mut g2_points = Vec::with_capacity(num_pairs); - - for i in 0..num_pairs { - let pair_start = i * PAIR_WORDS; - let g1_start = pair_start; - let g2_start = pair_start + G1_WORDS; - - let g1_words = &pairs_data[g1_start..g1_start + G1_WORDS]; - let g2_words = &pairs_data[g2_start..g2_start + G2_WORDS]; - - let g1_bytes = - unsafe { std::slice::from_raw_parts(g1_words.as_ptr() as *const u8, G1_WORDS * 8) }; - let g2_bytes = - unsafe { std::slice::from_raw_parts(g2_words.as_ptr() as *const u8, G2_WORDS * 8) }; - - g1_points.push(g1_bytes); - g2_points.push(g2_bytes); - } - - for i in 0..num_pairs { - println!("[{}] G1 Point: {:x?}\n G2 Point: {:x?}", i, g1_points[i], g2_points[i]); - } - - // Build arrays of raw pointers for the FFI call - let g1_ptrs: Vec<*const u8> = g1_points.iter().map(|p| p.as_ptr()).collect(); - let g2_ptrs: Vec<*const u8> = g2_points.iter().map(|p| p.as_ptr()).collect(); let mut hints = Vec::new(); unsafe { - let result = zisklib::bn254_pairing_check_c( - g1_ptrs.as_ptr(), - g2_ptrs.as_ptr(), - num_pairs, - &mut hints, - ); - - println!("BN254_PAIRING_CHECK: result = {}", result); + zisklib::bn254_pairing_check_c(pairs_data.as_ptr() as *const u8, num_pairs, &mut hints); } Ok(hints) } - -#[inline] -unsafe fn reinterpret_u64_as_bytes( - slice: &[u64], - count: usize, -) -> &[[u8; BYTES]] { - debug_assert_eq!(slice.len(), count * (BYTES / 8)); - std::slice::from_raw_parts(slice.as_ptr().cast(), count) -} diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 9f36c04c5..9665f0733 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -54,6 +54,7 @@ macro_rules! hint_fields { } /// Read a length-prefixed field from hint data +#[allow(unused)] #[inline] fn read_field<'a>(data: &'a [u64], pos: &mut usize) -> anyhow::Result<&'a [u64]> { let len = diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 8c410efe6..bd8d0ad0f 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -7,7 +7,7 @@ use anyhow::Result; /// Processes an `HINT_SECP256K1_ECRECOVER` hint. #[inline] pub fn secp256k1_ecrecover_hint(data: &[u64]) -> Result> { - hint_fields![SIG: 64, RECID: 8, MSG: 32]; + hint_fields![SIG: 64, RECID: 8, MSG: 32, LO_S: 1]; validate_hint_min_length(data, EXPECTED_LEN_U64, "HINT_SECP256K1_ECRECOVER")?; @@ -18,6 +18,8 @@ pub fn secp256k1_ecrecover_hint(data: &[u64]) -> Result> { bytes[RECID_OFFSET..RECID_OFFSET + RECID_SIZE].try_into().unwrap(); let recid: u8 = u64::from_le_bytes(*recid) as u8; let msg: &[u8; MSG_SIZE] = bytes[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); + let low_s: bool = + u64::from_le_bytes(bytes[LO_S_OFFSET..LO_S_OFFSET + LO_S_SIZE].try_into().unwrap()) != 0; let mut hints = Vec::new(); let result: &mut [u8; 32] = &mut [0u8; 32]; @@ -27,6 +29,7 @@ pub fn secp256k1_ecrecover_hint(data: &[u64]) -> Result> { recid, msg.as_ptr(), result.as_mut_ptr(), + low_s, &mut hints, ); } From 65e32fa0f89d2668f357c011202bfd2d8dd10957 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:19:23 +0000 Subject: [PATCH 322/782] remove debugging info --- ziskos-hints/src/handlers/bls381.rs | 4 ++-- ziskos-hints/src/handlers/bn254.rs | 15 --------------- ziskos-hints/src/handlers/kzg.rs | 3 +-- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/ziskos-hints/src/handlers/bls381.rs b/ziskos-hints/src/handlers/bls381.rs index f3c25c539..77d83fc78 100644 --- a/ziskos-hints/src/handlers/bls381.rs +++ b/ziskos-hints/src/handlers/bls381.rs @@ -127,13 +127,13 @@ pub fn bls12_381_pairing_check_hint(data: &[u64]) -> Result> { validate_hint_length(data, expected_len, "HINT_BLS12_381_PAIRING_CHECK")?; - let bytes = unsafe { + let pairs = unsafe { std::slice::from_raw_parts(data.as_ptr().add(1) as *const u8, num_pairs * PAIR_SIZE_BYTES) }; let mut hints = Vec::new(); unsafe { - zisklib::bls12_381_pairing_check_c(bytes.as_ptr(), num_pairs, &mut hints); + zisklib::bls12_381_pairing_check_c(pairs.as_ptr(), num_pairs, &mut hints); } Ok(hints) diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 2a5950c8c..8eb658531 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -115,10 +115,6 @@ pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { g2_points.push(g2_bytes); } - for i in 0..num_pairs { - println!("[{}] G1 Point: {:x?}\n G2 Point: {:x?}", i, g1_points[i], g2_points[i]); - } - // Build arrays of raw pointers for the FFI call let g1_ptrs: Vec<*const u8> = g1_points.iter().map(|p| p.as_ptr()).collect(); let g2_ptrs: Vec<*const u8> = g2_points.iter().map(|p| p.as_ptr()).collect(); @@ -131,18 +127,7 @@ pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { num_pairs, &mut hints, ); - - println!("BN254_PAIRING_CHECK: result = {}", result); } Ok(hints) } - -#[inline] -unsafe fn reinterpret_u64_as_bytes( - slice: &[u64], - count: usize, -) -> &[[u8; BYTES]] { - debug_assert_eq!(slice.len(), count * (BYTES / 8)); - std::slice::from_raw_parts(slice.as_ptr().cast(), count) -} diff --git a/ziskos-hints/src/handlers/kzg.rs b/ziskos-hints/src/handlers/kzg.rs index cd60a4539..bf4f2d380 100644 --- a/ziskos-hints/src/handlers/kzg.rs +++ b/ziskos-hints/src/handlers/kzg.rs @@ -19,7 +19,7 @@ pub fn verify_kzg_proof_hint(data: &[u64]) -> Result> { bytes[PROOF_OFFSET..PROOF_OFFSET + PROOF_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - let result = unsafe { + unsafe { zisklib::verify_kzg_proof_c( z.as_ptr(), y.as_ptr(), @@ -28,7 +28,6 @@ pub fn verify_kzg_proof_hint(data: &[u64]) -> Result> { &mut hints, ) }; - println!("KZG result: {}", result); Ok(hints) } From 4bc601421f6e6169eaa4c370b3bb546a6c11ca12 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:42:44 +0000 Subject: [PATCH 323/782] remove unused var --- ziskos-hints/src/handlers/bn254.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 8eb658531..3b5b7088e 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -121,12 +121,7 @@ pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { let mut hints = Vec::new(); unsafe { - let result = zisklib::bn254_pairing_check_c( - g1_ptrs.as_ptr(), - g2_ptrs.as_ptr(), - num_pairs, - &mut hints, - ); + zisklib::bn254_pairing_check_c(g1_ptrs.as_ptr(), g2_ptrs.as_ptr(), num_pairs, &mut hints); } Ok(hints) From dbdaabb44238842294e37e93cd46e155ec899321 Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 23 Jan 2026 15:01:18 +0100 Subject: [PATCH 324/782] Disable poseidon 2 hints --- ziskos/entrypoint/Cargo.toml | 1 - ziskos/entrypoint/src/syscalls/poseidon2.rs | 32 +++++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index ab5842f3b..a486f9e54 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -25,7 +25,6 @@ tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } bincode = "2.0" sha2 = { workspace = true } -fields = { workspace = true } [features] default = [] diff --git a/ziskos/entrypoint/src/syscalls/poseidon2.rs b/ziskos/entrypoint/src/syscalls/poseidon2.rs index ab64e83b4..680cbb8c7 100644 --- a/ziskos/entrypoint/src/syscalls/poseidon2.rs +++ b/ziskos/entrypoint/src/syscalls/poseidon2.rs @@ -6,8 +6,8 @@ use core::arch::asm; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] use crate::ziskos_syscall; -#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] -use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; +// #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] +// use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; /// Executes the Poseidon2 permutation on the given state. /// @@ -31,20 +31,22 @@ pub extern "C" fn syscall_poseidon2( ziskos_syscall!(0x812, state); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { - // Get a mutable reference to the state - let state: &mut [u64; 16] = unsafe { &mut *(state) }; + // // Get a mutable reference to the state + // let state: &mut [u64; 16] = unsafe { &mut *(state) }; - // Call poseidon2, mapping u64 to Goldilocks elements - let state_gl = state.map(Goldilocks::new); - let new_state_gl = poseidon2_hash::(&state_gl); - for (i, d) in state.iter_mut().enumerate() { - *d = new_state_gl[i].as_canonical_u64(); - } + // // Call poseidon2, mapping u64 to Goldilocks elements + // let state_gl = state.map(Goldilocks::new); + // let new_state_gl = poseidon2_hash::(&state_gl); + // for (i, d) in state.iter_mut().enumerate() { + // *d = new_state_gl[i].as_canonical_u64(); + // } - #[cfg(feature = "hints")] - { - // For hints, we store the new state in the hints vector - hints.extend_from_slice(state); - } + // #[cfg(feature = "hints")] + // { + // // For hints, we store the new state in the hints vector + // hints.extend_from_slice(state); + // } + + unreachable!(); } } From 4670e9224dc4519d75256c085a3c53bccf2cfe12 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 23 Jan 2026 14:24:23 +0000 Subject: [PATCH 325/782] update Cargo.lock --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8a65e4503..62a4d6456 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6316,7 +6316,6 @@ version = "0.16.0" dependencies = [ "bincode", "cfg-if", - "fields", "getrandom 0.2.17", "lazy_static", "lib-c", From 1acaed2103351ecce45b2460f3e558a244ddc1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Fri, 23 Jan 2026 15:25:25 +0100 Subject: [PATCH 326/782] Merge pull request #721 from 0xPolygonHermez/feature/stdin-update Feature/stdin update --- Cargo.lock | 54 ++++++++++----------------------- Cargo.toml | 1 + common/Cargo.toml | 1 + common/src/io/file_stdin.rs | 5 +-- common/src/io/memory_stdin.rs | 13 +++++--- common/src/io/null_stdin.rs | 5 +-- common/src/io/zisk_stdin.rs | 40 +++++++++++++++--------- ziskos/entrypoint/Cargo.toml | 2 +- ziskos/entrypoint/src/io.rs | 57 +++++++++++++++++++++++++++++++++++ ziskos/entrypoint/src/lib.rs | 2 ++ 10 files changed, 119 insertions(+), 61 deletions(-) create mode 100644 ziskos/entrypoint/src/io.rs diff --git a/Cargo.lock b/Cargo.lock index d86cc3407..0f743d869 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -431,22 +431,11 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bincode" -version = "2.0.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "bincode_derive", "serde", - "unty", -] - -[[package]] -name = "bincode_derive" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" -dependencies = [ - "virtue", ] [[package]] @@ -1040,7 +1029,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" dependencies = [ "fields", "num-bigint", @@ -1385,7 +1374,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" dependencies = [ "cfg-if", "num-bigint", @@ -2705,7 +2694,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" dependencies = [ "colored", "fields", @@ -3091,9 +3080,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -3101,7 +3090,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" dependencies = [ "blake3", "borsh", @@ -3136,7 +3125,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" dependencies = [ "borsh", "colored", @@ -3167,7 +3156,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" dependencies = [ "fields", "itoa", @@ -3180,7 +3169,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" dependencies = [ "proc-macro2", "quote", @@ -3191,7 +3180,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" dependencies = [ "crossbeam-channel", "tracing", @@ -3200,7 +3189,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" dependencies = [ "colored", "fields", @@ -3211,7 +3200,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" dependencies = [ "bytemuck", "fields", @@ -4826,12 +4815,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "unty" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" - [[package]] name = "ureq" version = "3.1.4" @@ -4934,12 +4917,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "virtue" -version = "0.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" - [[package]] name = "walkdir" version = "2.5.0" @@ -5437,7 +5414,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" dependencies = [ "colored", "fields", @@ -5603,6 +5580,7 @@ name = "zisk-common" version = "0.16.0" dependencies = [ "anyhow", + "bincode", "bytemuck", "fields", "libc", diff --git a/Cargo.toml b/Cargo.toml index 73ac8d527..b17b63981 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -155,6 +155,7 @@ clap = { version = "4", features = ["derive", "env"] } futures = { version = "0.3" } thiserror = { version = "2" } bytes = "1.0" +bincode = "1.3.3" tokio = { version = "1", features = ["full"] } tokio-stream = "0.1" diff --git a/common/Cargo.toml b/common/Cargo.toml index e2dd40cf3..704598511 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -24,6 +24,7 @@ serde_json = { workspace = true } anyhow = { workspace = true } bytemuck = { workspace = true } zstd = { workspace = true } +bincode = { workspace = true } # Distributed mode (mpi) is only supported on Linux x86_64 [target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] diff --git a/common/src/io/file_stdin.rs b/common/src/io/file_stdin.rs index ff51be586..26fd13eb7 100644 --- a/common/src/io/file_stdin.rs +++ b/common/src/io/file_stdin.rs @@ -1,6 +1,7 @@ //! A file-based implementation of ZiskStdin. //! This module provides functionality to read input data from a file. +use serde::Serialize; use std::fs::{self, File}; use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; @@ -38,13 +39,13 @@ impl ZiskIO for ZiskFileStdin { self.reader.read_exact(buffer).expect("Failed to read into buffer"); } - fn write_serialized(&mut self, _data: &[u8]) { + fn write(&mut self, _data: &T) { // This is a read-only stdin implementation // Writing is not supported for file-based stdin panic!("Write operations are not supported for FileStdin"); } - fn write_bytes(&mut self, _data: &[u8]) { + fn write_slice(&mut self, _data: &[u8]) { // This is a read-only stdin implementation // Writing is not supported for file-based stdin panic!("Write operations are not supported for FileStdin"); diff --git a/common/src/io/memory_stdin.rs b/common/src/io/memory_stdin.rs index d3ad9ce52..92faa840c 100644 --- a/common/src/io/memory_stdin.rs +++ b/common/src/io/memory_stdin.rs @@ -1,3 +1,4 @@ +use serde::Serialize; use std::io::{Cursor, Read}; use crate::io::ZiskIO; @@ -40,11 +41,15 @@ impl ZiskIO for ZiskMemoryStdin { self.cursor.read_exact(buffer).expect("Failed to read into buffer from memory"); } - fn write_serialized(&mut self, _data: &[u8]) { - panic!("Write operations are not supported for ZiskMemoryStdin"); + fn write(&mut self, data: &T) { + let mut tmp = Vec::new(); + bincode::serialize_into(&mut tmp, data).expect("Failed to serialize data into memory"); + self.data.extend_from_slice(&tmp); + self.cursor.get_mut().extend_from_slice(&tmp); } - fn write_bytes(&mut self, _data: &[u8]) { - panic!("Write operations are not supported for ZiskMemoryStdin"); + fn write_slice(&mut self, data: &[u8]) { + self.data.extend_from_slice(data); + self.cursor.get_mut().extend_from_slice(data); } } diff --git a/common/src/io/null_stdin.rs b/common/src/io/null_stdin.rs index 4348ebbc3..80b262ded 100644 --- a/common/src/io/null_stdin.rs +++ b/common/src/io/null_stdin.rs @@ -1,6 +1,7 @@ use tracing::warn; use crate::io::ZiskIO; +use serde::Serialize; pub struct ZiskNullStdin; @@ -10,10 +11,10 @@ impl ZiskIO for ZiskNullStdin { } fn read_slice(&mut self, _slice: &mut [u8]) {} fn read_into(&mut self, _buffer: &mut [u8]) {} - fn write_serialized(&mut self, _data: &[u8]) { + fn write(&mut self, _data: &T) { warn!("NullStdin does not support writing"); } - fn write_bytes(&mut self, _data: &[u8]) { + fn write_slice(&mut self, _data: &[u8]) { warn!("NullStdin does not support writing"); } } diff --git a/common/src/io/zisk_stdin.rs b/common/src/io/zisk_stdin.rs index 8f5b14006..2001e0a63 100644 --- a/common/src/io/zisk_stdin.rs +++ b/common/src/io/zisk_stdin.rs @@ -1,4 +1,5 @@ use crate::io::{ZiskFileStdin, ZiskMemoryStdin, ZiskNullStdin}; +use serde::Serialize; use std::path::Path; use anyhow::Result; @@ -14,10 +15,10 @@ pub trait ZiskIO: Send + Sync { fn read_into(&mut self, buffer: &mut [u8]); /// Write a serialized value to the buffer. - fn write_serialized(&mut self, data: &[u8]); + fn write(&mut self, data: &T); /// Write a slice of bytes to the buffer. - fn write_bytes(&mut self, data: &[u8]); + fn write_slice(&mut self, data: &[u8]); } pub enum ZiskIOVariant { @@ -51,19 +52,19 @@ impl ZiskIO for ZiskIOVariant { } } - fn write_serialized(&mut self, data: &[u8]) { + fn write(&mut self, data: &T) { match self { - ZiskIOVariant::File(file_stdin) => file_stdin.write_serialized(data), - ZiskIOVariant::Null(null_stdin) => null_stdin.write_serialized(data), - ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write_serialized(data), + ZiskIOVariant::File(file_stdin) => file_stdin.write(data), + ZiskIOVariant::Null(null_stdin) => null_stdin.write(data), + ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write(data), } } - fn write_bytes(&mut self, data: &[u8]) { + fn write_slice(&mut self, data: &[u8]) { match self { - ZiskIOVariant::File(file_stdin) => file_stdin.write_bytes(data), - ZiskIOVariant::Null(null_stdin) => null_stdin.write_bytes(data), - ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write_bytes(data), + ZiskIOVariant::File(file_stdin) => file_stdin.write_slice(data), + ZiskIOVariant::Null(null_stdin) => null_stdin.write_slice(data), + ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write_slice(data), } } } @@ -85,16 +86,27 @@ impl ZiskIO for ZiskStdin { self.io.read_into(buffer) } - fn write_serialized(&mut self, data: &[u8]) { - self.io.write_serialized(data) + fn write(&mut self, data: &T) { + self.io.write(data) } - fn write_bytes(&mut self, data: &[u8]) { - self.io.write_bytes(data) + fn write_slice(&mut self, data: &[u8]) { + self.io.write_slice(data) + } +} + +impl Default for ZiskStdin { + fn default() -> Self { + Self::new() } } impl ZiskStdin { + /// Create new memory-based stdin + pub fn new() -> Self { + Self { io: ZiskIOVariant::Memory(ZiskMemoryStdin::new(Vec::new())) } + } + /// Create a null stdin (no input) pub fn null() -> Self { Self { io: ZiskIOVariant::Null(ZiskNullStdin) } diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index 5974a5968..e9824b4c7 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -21,4 +21,4 @@ getrandom = { version = "0.2", features = ["custom"] } cfg-if = "1.0" tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } -bincode = "2.0" \ No newline at end of file +bincode = { workspace = true } \ No newline at end of file diff --git a/ziskos/entrypoint/src/io.rs b/ziskos/entrypoint/src/io.rs new file mode 100644 index 000000000..f376274f5 --- /dev/null +++ b/ziskos/entrypoint/src/io.rs @@ -0,0 +1,57 @@ +//! I/O utilities for Zisk zkVM programs. +//! +//! This module provides a high-level API for reading inputs and committing public outputs. + +use crate::{read_input, set_output}; +use serde::{de::DeserializeOwned, Serialize}; + +/// Read a deserializable object from the input stream. +/// +/// ### Examples +/// ```ignore +/// use serde::{Deserialize, Serialize}; +/// +/// #[derive(Serialize, Deserialize)] +/// struct MyStruct { +/// a: u32, +/// b: u32, +/// } +/// +/// let data: MyStruct = ziskos::io::read(); +/// ``` +pub fn read() -> T { + let bytes = read_input(); + bincode::deserialize(&bytes).expect("Deserialization failed") +} + +/// Read raw bytes from the input stream. +/// +/// ### Examples +/// ```ignore +/// let data: Vec = ziskos::io::read_vec(); +/// ``` +pub fn read_vec() -> Vec { + read_input() +} + +/// Commit a serializable value to public outputs. +/// The value is serialized with bincode and written as 32-bit chunks. +pub fn commit(value: &T) { + let bytes = bincode::serialize(value).expect("Serialization failed"); + write(&bytes); +} + +/// Write raw bytes to public outputs. +/// Bytes are written as 32-bit little-endian values. +pub fn write(buf: &[u8]) { + let chunks = buf.len().div_ceil(4); + + for i in 0..chunks { + let start = i * 4; + let end = (start + 4).min(buf.len()); + let mut bytes = [0u8; 4]; + bytes[..end - start].copy_from_slice(&buf[start..end]); + let val = u32::from_le_bytes(bytes); + set_output(i, val); + } +} diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 530a898ab..fa4f3d047 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -14,6 +14,8 @@ pub mod zisklib; pub mod syscalls; +pub mod io; + pub mod ziskos_definitions; #[macro_export] From 02707d911eb4b25159c5ba85c79c07872445cd7a Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 23 Jan 2026 16:15:06 +0100 Subject: [PATCH 327/782] Activate poseidon 2 hints, again --- ziskos/entrypoint/Cargo.toml | 1 + ziskos/entrypoint/src/syscalls/poseidon2.rs | 32 ++++++++++----------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index a486f9e54..a2fbc043a 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -25,6 +25,7 @@ tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } bincode = "2.0" sha2 = { workspace = true } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0", features = ["verify"] } [features] default = [] diff --git a/ziskos/entrypoint/src/syscalls/poseidon2.rs b/ziskos/entrypoint/src/syscalls/poseidon2.rs index 680cbb8c7..ab64e83b4 100644 --- a/ziskos/entrypoint/src/syscalls/poseidon2.rs +++ b/ziskos/entrypoint/src/syscalls/poseidon2.rs @@ -6,8 +6,8 @@ use core::arch::asm; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] use crate::ziskos_syscall; -// #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] -// use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; +#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] +use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; /// Executes the Poseidon2 permutation on the given state. /// @@ -31,22 +31,20 @@ pub extern "C" fn syscall_poseidon2( ziskos_syscall!(0x812, state); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { - // // Get a mutable reference to the state - // let state: &mut [u64; 16] = unsafe { &mut *(state) }; + // Get a mutable reference to the state + let state: &mut [u64; 16] = unsafe { &mut *(state) }; - // // Call poseidon2, mapping u64 to Goldilocks elements - // let state_gl = state.map(Goldilocks::new); - // let new_state_gl = poseidon2_hash::(&state_gl); - // for (i, d) in state.iter_mut().enumerate() { - // *d = new_state_gl[i].as_canonical_u64(); - // } + // Call poseidon2, mapping u64 to Goldilocks elements + let state_gl = state.map(Goldilocks::new); + let new_state_gl = poseidon2_hash::(&state_gl); + for (i, d) in state.iter_mut().enumerate() { + *d = new_state_gl[i].as_canonical_u64(); + } - // #[cfg(feature = "hints")] - // { - // // For hints, we store the new state in the hints vector - // hints.extend_from_slice(state); - // } - - unreachable!(); + #[cfg(feature = "hints")] + { + // For hints, we store the new state in the hints vector + hints.extend_from_slice(state); + } } } From f136b80be6d90d6971b09993e1f918265352b091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Sat, 24 Jan 2026 11:12:08 +0000 Subject: [PATCH 328/782] Minor fixes in bn --- .../entrypoint/src/zisklib/lib/bn254/curve.rs | 19 ++++++++++----- ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs | 23 ++++++++++++++++++- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs index 250be861f..751a3ef43 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs @@ -13,7 +13,7 @@ use crate::{ use super::{ constants::{E_B, G1_IDENTITY, P}, fp::{add_fp_bn254, inv_fp_bn254, is_canonical_fp_bn254, mul_fp_bn254, square_fp_bn254}, - fr::{is_canonical_fr_bn254, scalar_bytes_be_to_u64_le_bn254}, + fr::{is_canonical_fr_bn254, reduce_fr_bn254, scalar_bytes_be_to_u64_le_bn254}, }; /// G1 add result codes @@ -308,10 +308,6 @@ pub fn mul_complete_bn254( k: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec, ) -> Result<[u64; 8], u8> { - if !is_canonical_fr_bn254(k) { - return Err(G1_MUL_ERR_NOT_IN_FIELD); - } - // If point is infinity, result is infinity if eq(p, &G1_IDENTITY) { return Ok(G1_IDENTITY); @@ -333,10 +329,21 @@ pub fn mul_complete_bn254( return Err(G1_MUL_ERR_NOT_ON_CURVE); } + // Reduce the scalar if not canonical + let k = if !is_canonical_fr_bn254(k) { + reduce_fr_bn254( + k, + #[cfg(feature = "hints")] + hints, + ) + } else { + *k + }; + // Perform scalar multiplication Ok(scalar_mul_bn254( p, - k, + &k, #[cfg(feature = "hints")] hints, )) diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs index ffec9a266..86826efb1 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs @@ -1,4 +1,7 @@ -use crate::zisklib::lt; +use crate::{ + syscalls::{syscall_arith256_mod, SyscallArith256ModParams}, + zisklib::{eq, lt}, +}; use super::constants::R; @@ -8,6 +11,24 @@ pub fn is_canonical_fr_bn254(x: &[u64; 4]) -> bool { lt(x, &R) } +pub fn reduce_fr_bn254(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { + // x·1 + 0 + let mut params = SyscallArith256ModParams { + a: x, + b: &[1, 0, 0, 0], + c: &[0, 0, 0, 0], + module: &R, + d: &mut [0, 0, 0, 0], + }; + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); + + *params.d +} + /// Convert big-endian bytes to little-endian u64 limbs for a scalar (32 bytes -> [u64; 4]) pub fn scalar_bytes_be_to_u64_le_bn254(bytes: &[u8; 32]) -> [u64; 4] { let mut result = [0u64; 4]; From 3c9c101409828abad84e7215799dbb0b8aace380 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sun, 25 Jan 2026 09:21:55 +0000 Subject: [PATCH 329/782] fix nice command output --- .../asm-runner/src/asm_services/services.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 5efbf068f..414a81458 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -166,10 +166,19 @@ impl AsmServices { // Prepare command let command_path = trimmed_path.to_string() + &format!("-{asm_service}.bin"); - let mut command = Command::new("nice"); - command.arg("-n"); - command.arg("-5"); - command.arg(command_path); + let mut command = Command::new(command_path); + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + { + use std::os::unix::process::CommandExt; + + unsafe { + command.pre_exec(|| { + // Ignore failure silently (matches nice behavior) + libc::setpriority(libc::PRIO_PROCESS, 0, -5); + Ok(()) + }); + } + } options.apply_to_command(&mut command, asm_service); From 3bf4979130092cdcec6b9ea9c925586427ab4261 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sun, 25 Jan 2026 09:24:00 +0000 Subject: [PATCH 330/782] changed writer initialization message from info to debug --- executor/src/emu_asm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index b5755ee9b..d841d882a 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -233,7 +233,7 @@ impl EmulatorAsm { let shmem_input_name = AsmSharedMemory::::shmem_input_name(port, *service, self.local_rank); - tracing::info!( + tracing::debug!( "Initializing SharedMemoryWriter for service {:?} at '{}'", service, shmem_input_name From 61f948f3f1643d9ef53359866577df814067a3ac Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 26 Jan 2026 07:16:13 +0000 Subject: [PATCH 331/782] fix kzg hint handler --- ziskos-hints/src/handlers/kzg.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ziskos-hints/src/handlers/kzg.rs b/ziskos-hints/src/handlers/kzg.rs index bf4f2d380..a0fd9d30e 100644 --- a/ziskos-hints/src/handlers/kzg.rs +++ b/ziskos-hints/src/handlers/kzg.rs @@ -1,16 +1,16 @@ -use crate::{handlers::validate_hint_min_length, hint_fields, zisklib}; +use crate::{handlers::validate_hint_length, hint_fields, zisklib}; use anyhow::Result; /// Processes an `HINT_VERIFY_KZG_PROOF` hint. #[inline] pub fn verify_kzg_proof_hint(data: &[u64]) -> Result> { - hint_fields![Z: 4, Y: 4, COMMITMENT: 6, PROOF: 6]; - - validate_hint_min_length(data, EXPECTED_LEN, "HINT_VERIFY_KZG_PROOF")?; + hint_fields![Z: 32, Y: 32, COMMITMENT: 48, PROOF: 48]; let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + validate_hint_length(bytes, EXPECTED_LEN, "HINT_VERIFY_KZG_PROOF")?; + let z: &[u8; Z_SIZE] = bytes[Z_OFFSET..Z_OFFSET + Z_SIZE].try_into().unwrap(); let y: &[u8; Y_SIZE] = bytes[Y_OFFSET..Y_OFFSET + Y_SIZE].try_into().unwrap(); let commitment: &[u8; COMMITMENT_SIZE] = From 044f5472fbcceabcbb9b442a6dc0a16e4c1f7399 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 26 Jan 2026 08:08:21 +0000 Subject: [PATCH 332/782] hint handlers improvements --- Cargo.lock | 1 + ziskos-hints/src/handlers/bls381.rs | 8 ++++---- ziskos-hints/src/handlers/bn254.rs | 8 ++++---- ziskos-hints/src/handlers/kzg.rs | 2 +- ziskos-hints/src/handlers/mod.rs | 3 --- ziskos-hints/src/handlers/secp256k1.rs | 6 +++--- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62a4d6456..8a65e4503 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6316,6 +6316,7 @@ version = "0.16.0" dependencies = [ "bincode", "cfg-if", + "fields", "getrandom 0.2.17", "lazy_static", "lib-c", diff --git a/ziskos-hints/src/handlers/bls381.rs b/ziskos-hints/src/handlers/bls381.rs index 77d83fc78..3d4c27050 100644 --- a/ziskos-hints/src/handlers/bls381.rs +++ b/ziskos-hints/src/handlers/bls381.rs @@ -10,9 +10,9 @@ use anyhow::Result; pub fn bls12_381_g1_add_hint(data: &[u64]) -> Result> { hint_fields![A: 96, B: 96]; - validate_hint_min_length(data, EXPECTED_LEN_U64, "HINT_BLS12_381_G1_ADD")?; + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; - let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + validate_hint_min_length(bytes, EXPECTED_LEN, "HINT_BLS12_381_G1_ADD")?; let a: &[u8; A_SIZE] = bytes[A_OFFSET..A_OFFSET + A_SIZE].try_into().unwrap(); let b: &[u8; B_SIZE] = bytes[B_OFFSET..B_OFFSET + B_SIZE].try_into().unwrap(); @@ -62,9 +62,9 @@ pub fn bls12_381_g1_msm_hint(data: &[u64]) -> Result> { pub fn bls12_381_g2_add_hint(data: &[u64]) -> Result> { hint_fields![A: 192, B: 192]; - validate_hint_min_length(data, EXPECTED_LEN_U64, "HINT_BLS12_381_G2_ADD")?; + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; - let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + validate_hint_min_length(bytes, EXPECTED_LEN, "HINT_BLS12_381_G2_ADD")?; let a: &[u8; A_SIZE] = bytes[A_OFFSET..A_OFFSET + A_SIZE].try_into().unwrap(); let b: &[u8; B_SIZE] = bytes[B_OFFSET..B_OFFSET + B_SIZE].try_into().unwrap(); diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 3b5b7088e..059532e62 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -10,9 +10,9 @@ use anyhow::Result; pub fn bn254_g1_add_hint(data: &[u64]) -> Result> { hint_fields![P1: 64, P2: 64]; - validate_hint_min_length(data, EXPECTED_LEN_U64, "HINT_BN254_G1_ADD")?; + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; - let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + validate_hint_min_length(bytes, EXPECTED_LEN, "HINT_BN254_G1_ADD")?; let p1: &[u8; P1_SIZE] = bytes[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); let p2: &[u8; P2_SIZE] = bytes[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); @@ -31,9 +31,9 @@ pub fn bn254_g1_add_hint(data: &[u64]) -> Result> { pub fn bn254_g1_mul_hint(data: &[u64]) -> Result> { hint_fields![POINT: 64, SCALAR: 32]; - validate_hint_min_length(data, EXPECTED_LEN_U64, "HINT_BN254_G1_MUL")?; + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; - let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + validate_hint_min_length(bytes, EXPECTED_LEN, "HINT_BN254_G1_MUL")?; let point: &[u8; POINT_SIZE] = bytes[POINT_OFFSET..POINT_OFFSET + POINT_SIZE].try_into().unwrap(); diff --git a/ziskos-hints/src/handlers/kzg.rs b/ziskos-hints/src/handlers/kzg.rs index a0fd9d30e..ebf7407c5 100644 --- a/ziskos-hints/src/handlers/kzg.rs +++ b/ziskos-hints/src/handlers/kzg.rs @@ -7,7 +7,7 @@ use anyhow::Result; pub fn verify_kzg_proof_hint(data: &[u64]) -> Result> { hint_fields![Z: 32, Y: 32, COMMITMENT: 48, PROOF: 48]; - let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; validate_hint_length(bytes, EXPECTED_LEN, "HINT_VERIFY_KZG_PROOF")?; diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 9f36c04c5..7144fc64d 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -28,10 +28,7 @@ macro_rules! hint_fields { hint_fields!(@offsets 0, $($name: $size),+); - #[allow(dead_code)] const EXPECTED_LEN: usize = hint_fields!(@sum $($size),+); - #[allow(dead_code)] - const EXPECTED_LEN_U64: usize = EXPECTED_LEN.div_ceil(8); }; (@offsets $offset:expr, $name:ident: $size:expr) => { diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 8c410efe6..c2e5d37fd 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -1,4 +1,4 @@ -use crate::handlers::validate_hint_min_length; +use crate::handlers::validate_hint_length; use crate::hint_fields; use crate::zisklib; @@ -9,9 +9,9 @@ use anyhow::Result; pub fn secp256k1_ecrecover_hint(data: &[u64]) -> Result> { hint_fields![SIG: 64, RECID: 8, MSG: 32]; - validate_hint_min_length(data, EXPECTED_LEN_U64, "HINT_SECP256K1_ECRECOVER")?; + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; - let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, EXPECTED_LEN) }; + validate_hint_length(bytes, EXPECTED_LEN, "HINT_SECP256K1_ECRECOVER")?; let sig: &[u8; SIG_SIZE] = bytes[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); let recid: &[u8; RECID_SIZE] = From c8e45ccebc9a997451271335f4bcef1083615079 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 26 Jan 2026 08:15:36 +0000 Subject: [PATCH 333/782] improva sha256 and keccack hint handler --- ziskos-hints/src/handlers/keccak256.rs | 15 ++++++++++++--- ziskos-hints/src/handlers/sha256.rs | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ziskos-hints/src/handlers/keccak256.rs b/ziskos-hints/src/handlers/keccak256.rs index e89446ec8..d3d3c335d 100644 --- a/ziskos-hints/src/handlers/keccak256.rs +++ b/ziskos-hints/src/handlers/keccak256.rs @@ -1,16 +1,25 @@ -use crate::{handlers::validate_hint_min_length, zisklib}; +use crate::{handlers::validate_hint_length, zisklib}; use anyhow::Result; /// Processes an `HINT_KECCAK256` hint. #[inline] pub fn keccak256_hint(data: &[u64], data_len_bytes: usize) -> Result> { - let data_len_u64 = data_len_bytes.div_ceil(8); + let data_len_words = data_len_bytes.div_ceil(8); - validate_hint_min_length(data, data_len_u64, "HINT_KECCAK256")?; + if data.len() != data_len_words { + anyhow::bail!( + "HINT_KECCAK256: expected data length of {} bytes ({} words), got {} words", + data_len_bytes, + data_len_words, + data.len() + ); + } let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data_len_bytes) }; + validate_hint_length(bytes, data_len_bytes, "HINT_KECCAK256")?; + let mut hints = Vec::new(); zisklib::keccak256(bytes, &mut hints); diff --git a/ziskos-hints/src/handlers/sha256.rs b/ziskos-hints/src/handlers/sha256.rs index 8aa2bc808..58a45ad90 100644 --- a/ziskos-hints/src/handlers/sha256.rs +++ b/ziskos-hints/src/handlers/sha256.rs @@ -1,16 +1,25 @@ -use crate::{handlers::validate_hint_min_length, zisklib}; +use crate::{handlers::validate_hint_length, zisklib}; use anyhow::Result; /// Processes an `HINT_SHA256` hint. #[inline] pub fn sha256_hint(data: &[u64], data_len_bytes: usize) -> Result> { - let data_len_u64 = data_len_bytes.div_ceil(8); + let data_len_words = data_len_bytes.div_ceil(8); - validate_hint_min_length(data, data_len_u64, "HINT_SHA256")?; + if data.len() != data_len_words { + anyhow::bail!( + "HINT_SHA256: expected data length of {} bytes ({} words), got {} words", + data_len_bytes, + data_len_words, + data.len() + ); + } let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data_len_bytes) }; + validate_hint_length(bytes, data_len_bytes, "HINT_SHA256")?; + let mut hints = Vec::new(); zisklib::sha256(bytes, &mut hints); From 76a4d20abf5e83df63605e210bd780e2f632e32c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 26 Jan 2026 08:23:48 +0000 Subject: [PATCH 334/782] improvements in hint handlers bls381 and bn254 --- ziskos-hints/src/handlers/bls381.rs | 9 ++---- ziskos-hints/src/handlers/bn254.rs | 9 ++---- ziskos-hints/src/handlers/mod.rs | 49 +++++++---------------------- 3 files changed, 18 insertions(+), 49 deletions(-) diff --git a/ziskos-hints/src/handlers/bls381.rs b/ziskos-hints/src/handlers/bls381.rs index 3d4c27050..d220d82c1 100644 --- a/ziskos-hints/src/handlers/bls381.rs +++ b/ziskos-hints/src/handlers/bls381.rs @@ -1,7 +1,4 @@ -use crate::{ - handlers::{validate_hint_length, validate_hint_min_length}, - hint_fields, zisklib, -}; +use crate::{handlers::validate_hint_length, hint_fields, zisklib}; use anyhow::Result; @@ -12,7 +9,7 @@ pub fn bls12_381_g1_add_hint(data: &[u64]) -> Result> { let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; - validate_hint_min_length(bytes, EXPECTED_LEN, "HINT_BLS12_381_G1_ADD")?; + validate_hint_length(bytes, EXPECTED_LEN, "HINT_BLS12_381_G1_ADD")?; let a: &[u8; A_SIZE] = bytes[A_OFFSET..A_OFFSET + A_SIZE].try_into().unwrap(); let b: &[u8; B_SIZE] = bytes[B_OFFSET..B_OFFSET + B_SIZE].try_into().unwrap(); @@ -64,7 +61,7 @@ pub fn bls12_381_g2_add_hint(data: &[u64]) -> Result> { let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; - validate_hint_min_length(bytes, EXPECTED_LEN, "HINT_BLS12_381_G2_ADD")?; + validate_hint_length(bytes, EXPECTED_LEN, "HINT_BLS12_381_G2_ADD")?; let a: &[u8; A_SIZE] = bytes[A_OFFSET..A_OFFSET + A_SIZE].try_into().unwrap(); let b: &[u8; B_SIZE] = bytes[B_OFFSET..B_OFFSET + B_SIZE].try_into().unwrap(); diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 059532e62..ab7ac02e4 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -1,7 +1,4 @@ -use crate::{ - handlers::{validate_hint_length, validate_hint_min_length}, - hint_fields, zisklib, -}; +use crate::{handlers::validate_hint_length, hint_fields, zisklib}; use anyhow::Result; @@ -12,7 +9,7 @@ pub fn bn254_g1_add_hint(data: &[u64]) -> Result> { let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; - validate_hint_min_length(bytes, EXPECTED_LEN, "HINT_BN254_G1_ADD")?; + validate_hint_length(bytes, EXPECTED_LEN, "HINT_BN254_G1_ADD")?; let p1: &[u8; P1_SIZE] = bytes[P1_OFFSET..P1_OFFSET + P1_SIZE].try_into().unwrap(); let p2: &[u8; P2_SIZE] = bytes[P2_OFFSET..P2_OFFSET + P2_SIZE].try_into().unwrap(); @@ -33,7 +30,7 @@ pub fn bn254_g1_mul_hint(data: &[u64]) -> Result> { let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; - validate_hint_min_length(bytes, EXPECTED_LEN, "HINT_BN254_G1_MUL")?; + validate_hint_length(bytes, EXPECTED_LEN, "HINT_BN254_G1_MUL")?; let point: &[u8; POINT_SIZE] = bytes[POINT_OFFSET..POINT_OFFSET + POINT_SIZE].try_into().unwrap(); diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 7144fc64d..6c2dfceaa 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -51,18 +51,18 @@ macro_rules! hint_fields { } /// Read a length-prefixed field from hint data -#[inline] -fn read_field<'a>(data: &'a [u64], pos: &mut usize) -> anyhow::Result<&'a [u64]> { - let len = - *data.get(*pos).ok_or("MODEXP hint data too short").map_err(anyhow::Error::msg)? as usize; - *pos += 1; - let field = data - .get(*pos..*pos + len) - .ok_or("MODEXP hint data too short") - .map_err(anyhow::Error::msg)?; - *pos += len; - Ok(field) -} +// #[inline] +// fn read_field<'a>(data: &'a [u64], pos: &mut usize) -> anyhow::Result<&'a [u64]> { +// let len = +// *data.get(*pos).ok_or("MODEXP hint data too short").map_err(anyhow::Error::msg)? as usize; +// *pos += 1; +// let field = data +// .get(*pos..*pos + len) +// .ok_or("MODEXP hint data too short") +// .map_err(anyhow::Error::msg)?; +// *pos += len; +// Ok(field) +// } #[inline] fn read_field_bytes<'a>(data: &'a [u64], pos: &mut usize) -> anyhow::Result<(&'a [u8], usize)> { @@ -118,28 +118,3 @@ fn validate_hint_length(data: &[T], expected_len: usize, hint_name: &str) -> } Ok(()) } - -/// Validates that the hint data has at least the minimum required length. -/// -/// # Arguments -/// -/// * `data` - The hint data to validate -/// * `min_len` - The minimum required length -/// * `hint_name` - The name of the hint type for error messages -/// -/// # Returns -/// -/// * `Ok(())` - If the length is sufficient -/// * `Err(anyhow::Error)` - If the length is too short -#[inline] -fn validate_hint_min_length(data: &[T], min_len: usize, hint_name: &str) -> anyhow::Result<()> { - if data.len() < min_len { - anyhow::bail!( - "Invalid {} hint length: expected at least {}, got {}", - hint_name, - min_len, - data.len(), - ); - } - Ok(()) -} From c67ad9ec397790223c72e54d73ae81250e2fe609 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 26 Jan 2026 11:48:20 +0100 Subject: [PATCH 335/782] Fix secp256k1_ecrecover_hint() LO_S length --- ziskos-hints/src/handlers/secp256k1.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 95db375de..b159fb861 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -7,7 +7,7 @@ use anyhow::Result; /// Processes an `HINT_SECP256K1_ECRECOVER` hint. #[inline] pub fn secp256k1_ecrecover_hint(data: &[u64]) -> Result> { - hint_fields![SIG: 64, RECID: 8, MSG: 32, LO_S: 1]; + hint_fields![SIG: 64, RECID: 8, MSG: 32, LO_S: 8]; let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; From 790d075b6b281a3de79e18b64cab106975e99bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Mon, 26 Jan 2026 17:17:19 +0100 Subject: [PATCH 336/782] Fix clippy with latest rust version (#724) --- cli/src/commands/prove.rs | 12 ++++++------ cli/src/commands/run.rs | 6 +++--- distributed/crates/worker/src/cli/main.rs | 4 ++-- distributed/crates/worker/src/worker.rs | 14 ++++++-------- emulator-asm/asm-runner/src/asm_runner.rs | 4 ++-- emulator/src/emu.rs | 4 ++-- emulator/src/emulator.rs | 5 ++--- emulator/src/regions_of_interest.rs | 7 +++---- 8 files changed, 26 insertions(+), 30 deletions(-) diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 721f4739e..0958ee6ed 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -112,14 +112,14 @@ impl ZiskProve { let mut gpu_params = ParamsGPU::new(self.preallocate); - if self.max_streams.is_some() { - gpu_params.with_max_number_streams(self.max_streams.unwrap()); + if let Some(max_streams) = self.max_streams { + gpu_params.with_max_number_streams(max_streams); } - if self.number_threads_witness.is_some() { - gpu_params.with_number_threads_pools_witness(self.number_threads_witness.unwrap()); + if let Some(number_threads_witness) = self.number_threads_witness { + gpu_params.with_number_threads_pools_witness(number_threads_witness); } - if self.max_witness_stored.is_some() { - gpu_params.with_max_witness_stored(self.max_witness_stored.unwrap()); + if let Some(max_witness_stored) = self.max_witness_stored { + gpu_params.with_max_witness_stored(max_witness_stored); } let stdin = self.create_stdin()?; diff --git a/cli/src/commands/run.rs b/cli/src/commands/run.rs index 8ad7f9709..d98e8ee2d 100644 --- a/cli/src/commands/run.rs +++ b/cli/src/commands/run.rs @@ -72,12 +72,12 @@ impl ZiskRun { if self.metrics { extra_command += " -m "; } - if self.input.is_some() { - let path = Path::new(self.input.as_ref().unwrap()); + if let Some(input) = &self.input { + let path = Path::new(input); if !path.exists() { return Err(anyhow!("Input file does not exist at path: {}", path.display())); } - input_command = format!("-i {}", self.input.as_ref().unwrap()); + input_command = format!("-i {}", input); } runner_command = format!("ziskemu {input_command} {extra_command} -e"); } else { diff --git a/distributed/crates/worker/src/cli/main.rs b/distributed/crates/worker/src/cli/main.rs index c3ac5667c..239115399 100644 --- a/distributed/crates/worker/src/cli/main.rs +++ b/distributed/crates/worker/src/cli/main.rs @@ -210,11 +210,11 @@ fn print_command_info( ); println!("{: >12} {}", "Elf".bright_green().bold(), prover_config.elf.display()); - if prover_config.asm.is_some() { + if let Some(asm) = &prover_config.asm { if let Some(asm_port) = prover_config.asm_port.as_ref() { println!("{: >12} {}", "Asm port".bright_green().bold(), asm_port); } - let asm_path = prover_config.asm.as_ref().unwrap().display(); + let asm_path = asm.display(); println!("{: >12} {}", "ASM runner".bright_green().bold(), asm_path); } else { println!( diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 43b9011f9..fd366f781 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -198,16 +198,14 @@ impl ProverConfig { let mut custom_commits_map: HashMap = HashMap::new(); custom_commits_map.insert("rom".to_string(), rom_bin_path); let mut gpu_params = ParamsGPU::new(prover_service_config.preallocate); - if prover_service_config.max_streams.is_some() { - gpu_params.with_max_number_streams(prover_service_config.max_streams.unwrap()); + if let Some(max_streams) = prover_service_config.max_streams { + gpu_params.with_max_number_streams(max_streams); } - if prover_service_config.number_threads_witness.is_some() { - gpu_params.with_number_threads_pools_witness( - prover_service_config.number_threads_witness.unwrap(), - ); + if let Some(number_threads_witness) = prover_service_config.number_threads_witness { + gpu_params.with_number_threads_pools_witness(number_threads_witness); } - if prover_service_config.max_witness_stored.is_some() { - gpu_params.with_max_witness_stored(prover_service_config.max_witness_stored.unwrap()); + if let Some(max_witness_stored) = prover_service_config.max_witness_stored { + gpu_params.with_max_witness_stored(max_witness_stored); } Ok(ProverConfig { diff --git a/emulator-asm/asm-runner/src/asm_runner.rs b/emulator-asm/asm-runner/src/asm_runner.rs index 71d704036..1be6fbadc 100644 --- a/emulator-asm/asm-runner/src/asm_runner.rs +++ b/emulator-asm/asm-runner/src/asm_runner.rs @@ -119,8 +119,8 @@ impl AsmRunnerOptions { /// # Arguments /// * `command` - A mutable reference to the `Command` to be modified. pub fn apply_to_command(&self, command: &mut Command, asm_service: &AsmService) { - let port = if self.base_port.is_some() { - AsmServices::port_for(asm_service, self.base_port.unwrap(), self.local_rank) + let port = if let Some(base_port) = self.base_port { + AsmServices::port_for(asm_service, base_port, self.local_rank) } else { AsmServices::default_port(asm_service, self.local_rank) }; diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index b040fb8ce..12f5c0079 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -1569,7 +1569,7 @@ impl<'a> Emu<'a> { self.ctx.stats.set_store_ops(options.store_op_output.is_some()); // Check that callback is provided if chunk size is specified - if options.chunk_size.is_some() { + if let Some(chunk_size) = options.chunk_size { // Check callback consistency if callback.is_none() { panic!("Emu::run() called with chunk size but no callback"); @@ -1577,7 +1577,7 @@ impl<'a> Emu<'a> { // Record callback into context self.ctx.do_callback = true; - self.ctx.callback_steps = options.chunk_size.unwrap(); + self.ctx.callback_steps = chunk_size; // Check steps value if self.ctx.callback_steps == 0 { diff --git a/emulator/src/emulator.rs b/emulator/src/emulator.rs index 0369af1e3..c8ad2c86b 100644 --- a/emulator/src/emulator.rs +++ b/emulator/src/emulator.rs @@ -153,9 +153,8 @@ impl ZiskEmulator { // OUTPUT: // Save output to a file if requested - if options.output.is_some() { - fs::write(options.output.as_ref().unwrap(), &output) - .map_err(|e| ZiskEmulatorErr::Unknown(e.to_string()))? + if let Some(output_path) = &options.output { + fs::write(output_path, &output).map_err(|e| ZiskEmulatorErr::Unknown(e.to_string()))? } // Log output to console if requested diff --git a/emulator/src/regions_of_interest.rs b/emulator/src/regions_of_interest.rs index f1a60f941..f63b18eb5 100644 --- a/emulator/src/regions_of_interest.rs +++ b/emulator/src/regions_of_interest.rs @@ -42,11 +42,10 @@ impl RegionsOfInterest { self.call_stack_rc += 1; } pub fn update_call_depth(&mut self, call_stack_depth: usize) { - if self.call_stack_depth.is_none() { - self.call_stack_depth = Some(call_stack_depth); + if let Some(depth) = self.call_stack_depth { + self.call_stack_depth = Some(std::cmp::min(depth, call_stack_depth)); } else { - self.call_stack_depth = - Some(std::cmp::min(self.call_stack_depth.unwrap(), call_stack_depth)); + self.call_stack_depth = Some(call_stack_depth); } } pub fn call(&mut self, caller: Option, call_stack_depth: usize) { From 06f813fca81c3160f81394165760cfabc190f2a6 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:45:32 +0000 Subject: [PATCH 337/782] added a check in the modexp hint handler --- ziskos-hints/src/handlers/modexp.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ziskos-hints/src/handlers/modexp.rs b/ziskos-hints/src/handlers/modexp.rs index d0ecffd99..02f87c019 100644 --- a/ziskos-hints/src/handlers/modexp.rs +++ b/ziskos-hints/src/handlers/modexp.rs @@ -10,7 +10,12 @@ pub fn modexp_hint(data: &[u64]) -> Result> { let (exp, exp_len) = read_field_bytes(data, &mut pos)?; let (modulus, modulus_len) = read_field_bytes(data, &mut pos)?; - // validate_hint_length(data, pos, "MODEXP")?; + // Check that the data length fits the expected length. + // Each length prefix is 8 bytes (1 u64), so total prefix size is 3 * 8 = 24 bytes. + // The total expected length in bytes is 24 + base_len + exp_len + modulus_len. + if (24 + base_len + exp_len + modulus_len).div_ceil(8) > data.len() * 8 { + anyhow::bail!("MODEXP hint data too short"); + } let mut hints = Vec::new(); let mut result = vec![0u8; modulus_len]; From 26fc76c1bab2b38762b3ee070118fff7a19fbf36 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:46:02 +0000 Subject: [PATCH 338/782] cargo fmt clippy --- emulator-asm/asm-runner/src/hints_shmem.rs | 1 - ziskos-hints/src/handlers/mod.rs | 1 + ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index 5d3215058..4d1a54720 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -60,7 +60,6 @@ unsafe impl Sync for HintsShmem {} impl HintsShmem { const CONTROL_PRECOMPILE_SIZE: u64 = 0x1000; // 4KB const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB - // const MAX_PRECOMPILE_SIZE: u64 = 0x100000; // 1MB const BUFFER_CAPACITY_U64: u64 = Self::MAX_PRECOMPILE_SIZE >> 3; // Capacity in u64 elements /// Create a new HintsShmem with the given shared memory names and unlock option. diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 6c2dfceaa..c7d7304b4 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -28,6 +28,7 @@ macro_rules! hint_fields { hint_fields!(@offsets 0, $($name: $size),+); + #[allow(unused)] const EXPECTED_LEN: usize = hint_fields!(@sum $($size),+); }; diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs index b966c381d..915ebfa27 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs @@ -261,6 +261,7 @@ pub unsafe extern "C" fn bn254_pairing_check_c( // Parse all pairs let mut g1_points: Vec<[u64; 8]> = Vec::with_capacity(num_pairs); let mut g2_points: Vec<[u64; 16]> = Vec::with_capacity(num_pairs); + for i in 0..num_pairs { let pair_ptr = pairs.add(i * 192); From 4291ac5e2a4d0773ff372dcc381197c0171a5f66 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:46:40 +0000 Subject: [PATCH 339/782] set default num hint threads to 32 --- precompiles/hints/src/hints_processor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 192842a3e..e11a939e6 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -178,7 +178,7 @@ pub struct HintsProcessor { } impl HintsProcessor { - const DEFAULT_NUM_THREADS: usize = 1; + const DEFAULT_NUM_THREADS: usize = 32; /// Creates a builder for configuring a [`HintsProcessor`]. /// From 7979657c96171120d768dcee968b1c38feb94145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Mon, 26 Jan 2026 23:14:41 +0100 Subject: [PATCH 340/782] Coordinator decides whether vadcop final proof should be compressed (#725) --- Cargo.lock | 66 +++++++++---------- distributed/crates/common/src/dto.rs | 8 --- distributed/crates/common/src/types.rs | 9 --- .../src/cli/handler_coordinator.rs | 10 ++- .../crates/coordinator/src/cli/main.rs | 4 ++ distributed/crates/coordinator/src/config.rs | 5 +- .../crates/coordinator/src/coordinator.rs | 10 +-- .../grpc-api/proto/zisk_distributed_api.proto | 10 +-- .../crates/grpc-api/src/conversions.rs | 8 --- distributed/crates/worker/src/cli/main.rs | 5 -- distributed/crates/worker/src/config.rs | 2 - distributed/crates/worker/src/worker.rs | 54 ++++----------- distributed/crates/worker/src/worker_node.rs | 9 --- 13 files changed, 63 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f743d869..65bf63054 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -627,9 +627,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.53" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "jobserver", @@ -1029,7 +1029,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" dependencies = [ "fields", "num-bigint", @@ -1374,7 +1374,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" dependencies = [ "cfg-if", "num-bigint", @@ -2460,9 +2460,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-format" @@ -2694,7 +2694,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" dependencies = [ "colored", "fields", @@ -3090,7 +3090,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" dependencies = [ "blake3", "borsh", @@ -3125,7 +3125,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" dependencies = [ "borsh", "colored", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" dependencies = [ "fields", "itoa", @@ -3169,7 +3169,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" dependencies = [ "proc-macro2", "quote", @@ -3180,7 +3180,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" dependencies = [ "crossbeam-channel", "tracing", @@ -3189,7 +3189,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" dependencies = [ "colored", "fields", @@ -3200,7 +3200,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" dependencies = [ "bytemuck", "fields", @@ -3347,9 +3347,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -4061,9 +4061,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -4304,9 +4304,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.45" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" dependencies = [ "deranged", "itoa", @@ -4321,15 +4321,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" dependencies = [ "num-conv", "time-core", @@ -4876,9 +4876,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -5414,7 +5414,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2de91805a77cb3cbf90b05ab5a67f8fb54e3261e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" dependencies = [ "colored", "fields", @@ -5473,18 +5473,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" dependencies = [ "proc-macro2", "quote", @@ -5853,9 +5853,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" [[package]] name = "zstd" diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index d31d13dbf..5d991c70d 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -181,15 +181,7 @@ pub struct AggParamsDto { pub agg_proofs: Vec, pub last_proof: bool, pub final_proof: bool, - pub verify_constraints: bool, - pub aggregation: bool, - pub rma: bool, pub compressed: bool, - pub verify_proofs: bool, - pub save_proofs: bool, - pub test_mode: bool, - pub output_dir_path: String, - pub minimal_memory: bool, } pub struct ProofDto { diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index 0a41e48df..1bf543d9d 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -12,7 +12,6 @@ use std::{ collections::HashMap, fmt::{self, Debug, Display}, ops::Range, - path::PathBuf, }; use tracing::error; @@ -441,13 +440,5 @@ pub struct AggregationParams { pub agg_proofs: Vec, pub last_proof: bool, pub final_proof: bool, - pub verify_constraints: bool, - pub aggregation: bool, - pub rma: bool, pub compressed: bool, - pub verify_proofs: bool, - pub save_proofs: bool, - pub test_mode: bool, - pub output_dir_path: PathBuf, - pub minimal_memory: bool, } diff --git a/distributed/crates/coordinator/src/cli/handler_coordinator.rs b/distributed/crates/coordinator/src/cli/handler_coordinator.rs index 9f32c6895..7d47f0572 100644 --- a/distributed/crates/coordinator/src/cli/handler_coordinator.rs +++ b/distributed/crates/coordinator/src/cli/handler_coordinator.rs @@ -14,13 +14,21 @@ pub async fn handle( port: Option, proofs_dir: Option, no_save_proofs: bool, + compressed_proofs: bool, webhook_url: Option, ) -> Result<()> { // Config file is now optional - if not provided, defaults will be used let config_file = config_file.or_else(|| std::env::var("ZISK_COORDINATOR_CONFIG_PATH").ok()); // Load configuration - let config = Config::load(config_file, port, proofs_dir, no_save_proofs, webhook_url)?; + let config = Config::load( + config_file, + port, + proofs_dir, + no_save_proofs, + compressed_proofs, + webhook_url, + )?; // Initialize tracing - keep guard alive for application lifetime let _log_guard = zisk_distributed_common::tracing::init(Some(&config.logging), None)?; diff --git a/distributed/crates/coordinator/src/cli/main.rs b/distributed/crates/coordinator/src/cli/main.rs index 62cb5be60..8b24410b0 100644 --- a/distributed/crates/coordinator/src/cli/main.rs +++ b/distributed/crates/coordinator/src/cli/main.rs @@ -34,6 +34,9 @@ struct ZiskCoordinatorArgs { )] no_save_proofs: bool, + #[arg(short = 'c', long, help = "Generate compressed proofs", default_value_t = false)] + compressed_proofs: bool, + /// Webhook URL to notify when a job finishes. /// /// The placeholder `{$job_id}` can be used in the URL and will be @@ -119,6 +122,7 @@ async fn main() -> Result<()> { args.port, args.proofs_dir, args.no_save_proofs, + args.compressed_proofs, args.webhook_url, ) .await diff --git a/distributed/crates/coordinator/src/config.rs b/distributed/crates/coordinator/src/config.rs index 7c18e7439..d374248ea 100644 --- a/distributed/crates/coordinator/src/config.rs +++ b/distributed/crates/coordinator/src/config.rs @@ -37,6 +37,7 @@ pub struct CoordinatorConfig { pub phase1_timeout_seconds: u64, pub phase2_timeout_seconds: u64, pub webhook_url: Option, + pub compressed_proofs: bool, } impl Config { @@ -50,6 +51,7 @@ impl Config { port: Option, proofs_dir: Option, no_save_proofs: bool, + compressed_proofs: bool, webhook_url: Option, ) -> Result { // Create proofs directory if it doesn't exist @@ -75,7 +77,8 @@ impl Config { .set_default("coordinator.max_workers_per_job", 10)? .set_default("coordinator.max_total_workers", 1000)? .set_default("coordinator.phase1_timeout_seconds", 300)? - .set_default("coordinator.phase2_timeout_seconds", 600)?; + .set_default("coordinator.phase2_timeout_seconds", 600)? + .set_default("coordinator.compressed_proofs", compressed_proofs)?; if let Some(path) = config_file { builder = builder.add_source(config::File::with_name(&path)); diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index affb0aca4..4e4eb5af4 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -1692,15 +1692,7 @@ impl Coordinator { agg_proofs: proofs, last_proof: all_done, final_proof: all_done, - verify_constraints: true, - aggregation: true, - rma: true, - compressed: true, - verify_proofs: true, - save_proofs: false, - test_mode: false, - output_dir_path: "".to_string(), - minimal_memory: false, + compressed: self.config.coordinator.compressed_proofs, }), }; diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index ca421892d..68eccf2a1 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -249,15 +249,7 @@ message AggParams { ProofList agg_proofs = 1; bool last_proof = 2; bool final_proof = 3; - bool verify_constraints = 4; - bool aggregation = 5; - bool rma = 6; - bool compressed = 7; - bool verify_proofs = 8; - bool save_proofs = 9; - bool test_mode = 10; - string output_dir_path = 11; - bool minimal_memory = 12; + bool compressed = 4; } message ExecuteTaskResponse { diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index 74406abc3..b50711fed 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -372,15 +372,7 @@ impl From for AggParams { agg_proofs: Some(ProofList { proofs: agg_proofs }), last_proof: dto.last_proof, final_proof: dto.final_proof, - verify_constraints: dto.verify_constraints, - aggregation: dto.aggregation, - rma: dto.rma, compressed: dto.compressed, - verify_proofs: dto.verify_proofs, - save_proofs: dto.save_proofs, - test_mode: dto.test_mode, - output_dir_path: dto.output_dir_path, - minimal_memory: dto.minimal_memory, } } } diff --git a/distributed/crates/worker/src/cli/main.rs b/distributed/crates/worker/src/cli/main.rs index 239115399..3185edd66 100644 --- a/distributed/crates/worker/src/cli/main.rs +++ b/distributed/crates/worker/src/cli/main.rs @@ -98,10 +98,6 @@ struct Cli { #[clap(long, default_value_t = false)] pub verify_constraints: bool, - /// Whether to generate the final vadcop proof compressed - #[clap(short = 'c', long, default_value_t = false)] - pub compressed: bool, - /// GPU parameters #[clap(short = 'z', long, default_value_t = false)] pub preallocate: bool, @@ -150,7 +146,6 @@ async fn main() -> Result<()> { debug: cli.debug.clone(), verify_constraints: cli.verify_constraints, aggregation: true, // we always aggregate - compressed: cli.compressed, preallocate: cli.preallocate, max_streams: cli.max_streams, number_threads_witness: cli.number_threads_witness, diff --git a/distributed/crates/worker/src/config.rs b/distributed/crates/worker/src/config.rs index 09b1be093..06d135c64 100644 --- a/distributed/crates/worker/src/config.rs +++ b/distributed/crates/worker/src/config.rs @@ -155,7 +155,6 @@ pub struct ProverServiceConfigDto { pub debug: Option>, pub verify_constraints: bool, pub aggregation: bool, - pub compressed: bool, pub preallocate: bool, pub max_streams: Option, pub number_threads_witness: Option, @@ -179,7 +178,6 @@ impl Default for ProverServiceConfigDto { debug: None, verify_constraints: false, aggregation: false, - compressed: false, preallocate: false, max_streams: None, number_threads_witness: None, diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index fd366f781..93eae46db 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -89,9 +89,6 @@ pub struct ProverConfig { /// Flag to enable aggregation pub aggregation: bool, - /// Flag to enable vadcop final compressed proof - pub compressed: bool, - /// Preallocate resources pub gpu_params: ParamsGPU, @@ -222,7 +219,6 @@ impl ProverConfig { unlock_mapped_memory: prover_service_config.unlock_mapped_memory, verify_constraints: prover_service_config.verify_constraints, aggregation: prover_service_config.aggregation, - compressed: prover_service_config.compressed, gpu_params, shared_tables: prover_service_config.shared_tables, rma: prover_service_config.rma, @@ -416,7 +412,7 @@ impl Worker { ); let phase_inputs = proofman::ProvePhaseInputs::Contributions(proof_info); - let options = self.get_proof_options_partial_contribution(); + let options = self.get_proof_options(false); let mut serialized = borsh::to_vec(&( JobPhase::Contributions, @@ -450,7 +446,7 @@ impl Worker { let phase_inputs = proofman::ProvePhaseInputs::Internal(challenges); - let options = self.get_proof_options_prove(); + let options = self.get_proof_options(false); let mut serialized = borsh::to_vec(&(JobPhase::Prove, job_id, phase_inputs, options)).unwrap(); @@ -474,7 +470,7 @@ impl Worker { ) -> JoinHandle<()> { let prover = self.prover.clone(); - let options = self.get_proof_options_partial_contribution(); + let options = self.get_proof_options(false); tokio::spawn(async move { let mut job = job.lock().await; @@ -573,7 +569,7 @@ impl Worker { ) -> JoinHandle<()> { let prover = self.prover.clone(); - let options = self.get_proof_options_prove(); + let options = self.get_proof_options(false); tokio::spawn(async move { let job = job.lock().await; @@ -643,6 +639,8 @@ impl Worker { ) -> JoinHandle<()> { let prover = self.prover.clone(); + let options = self.get_proof_options(agg_params.compressed); + tokio::spawn(async move { let job = job.lock().await; let job_id = job.job_id.clone(); @@ -659,8 +657,6 @@ impl Worker { }) .collect(); - let options = Self::get_proof_options_aggregation(&agg_params); - let result = prover.aggregate_proofs( agg_proofs, agg_params.last_proof, @@ -693,45 +689,17 @@ impl Worker { }) } - fn get_proof_options_partial_contribution(&self) -> ProofOptions { - ProofOptions { - verify_constraints: false, - aggregation: false, - compressed: self.prover_config.compressed, - verify_proofs: true, - save_proofs: true, - test_mode: false, - output_dir_path: PathBuf::from("."), - rma: self.prover_config.rma, - minimal_memory: self.prover_config.minimal_memory, - } - } - - fn get_proof_options_prove(&self) -> ProofOptions { + fn get_proof_options(&self, compressed: bool) -> ProofOptions { ProofOptions { - verify_constraints: false, - aggregation: true, - compressed: self.prover_config.compressed, + verify_constraints: self.prover_config.verify_constraints, + aggregation: self.prover_config.aggregation, verify_proofs: false, save_proofs: false, test_mode: false, - output_dir_path: PathBuf::default(), + output_dir_path: PathBuf::from("."), rma: self.prover_config.rma, minimal_memory: self.prover_config.minimal_memory, - } - } - - fn get_proof_options_aggregation(agg_params: &AggregationParams) -> ProofOptions { - ProofOptions { - verify_constraints: agg_params.verify_constraints, - aggregation: agg_params.aggregation, - rma: agg_params.rma, - compressed: agg_params.compressed, - verify_proofs: agg_params.verify_proofs, - save_proofs: agg_params.save_proofs, - test_mode: agg_params.test_mode, - output_dir_path: agg_params.output_dir_path.clone(), - minimal_memory: agg_params.minimal_memory, + compressed, } } diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 6b0db138e..3012ac2be 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -666,17 +666,8 @@ impl WorkerNodeGrpc { agg_proofs, last_proof: agg_params.last_proof, final_proof: agg_params.final_proof, - verify_constraints: agg_params.verify_constraints, - aggregation: agg_params.aggregation, - rma: agg_params.rma, compressed: agg_params.compressed, - verify_proofs: agg_params.verify_proofs, - save_proofs: agg_params.save_proofs, - test_mode: agg_params.test_mode, - output_dir_path: PathBuf::from(agg_params.output_dir_path), - minimal_memory: agg_params.minimal_memory, }; - self.worker.set_current_computation( self.worker.handle_aggregate(job, agg_params, computation_tx.clone()).await, ); From c150176f963768e32f0248c5bd4d55c973dbae6f Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 27 Jan 2026 09:05:04 +0100 Subject: [PATCH 341/782] Add ctx.precompile_results to save_to_asm() trace --- core/src/zisk_rom_2_asm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 0111ffde1..b5b8ddde6 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -3691,8 +3691,7 @@ impl ZiskRom2Asm { } println!( - "ZiskRom2Asm::save_to_asm() {} bytes, {} instructions, {:02} bytes/inst, {} map lines, {} label lines, {} comment lines, {} code lines, {:02} code lines/inst", - code.len(), + "ZiskRom2Asm::save_to_asm() {} bytes, {} instructions, {:02} bytes/inst, {} map lines, {} label lines, {} comment lines, {} code lines, {:02} code lines/inst, precompile_results={:?}", code.len(), rom.sorted_pc_list.len(), code.len() as f64 / rom.sorted_pc_list.len() as f64, map_label_lines_counter, @@ -3700,6 +3699,7 @@ impl ZiskRom2Asm { comment_lines_counter, code_lines_counter, code_lines_counter as f64 / rom.sorted_pc_list.len() as f64, + ctx.precompile_results ); } } From 35048e9eae3e18b98067bc2c1e7f82eeed52575e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 27 Jan 2026 08:52:01 +0000 Subject: [PATCH 342/782] changed false as default value in rom-setup hints parameter --- cli/src/commands/rom_setup.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index 34e413193..266f8f446 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -31,7 +31,7 @@ pub struct ZiskRomSetup { pub output_dir: Option, /// Enable precompile hints in assembly generation - #[clap(long, default_value_t = true)] + #[clap(short = 'h', long, default_value_t = false)] pub hints: bool, #[clap(short = 'v', long, default_value_t = false)] From ec0288cb3079e164b1cd7d47d6529271ded52d14 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 27 Jan 2026 08:57:18 +0000 Subject: [PATCH 343/782] improving sha256 and keccak hint handlers --- ziskos-hints/src/handlers/keccak256.rs | 4 +--- ziskos-hints/src/handlers/sha256.rs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/ziskos-hints/src/handlers/keccak256.rs b/ziskos-hints/src/handlers/keccak256.rs index d3d3c335d..c4b054dc8 100644 --- a/ziskos-hints/src/handlers/keccak256.rs +++ b/ziskos-hints/src/handlers/keccak256.rs @@ -1,4 +1,4 @@ -use crate::{handlers::validate_hint_length, zisklib}; +use crate::zisklib; use anyhow::Result; @@ -18,8 +18,6 @@ pub fn keccak256_hint(data: &[u64], data_len_bytes: usize) -> Result> { let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data_len_bytes) }; - validate_hint_length(bytes, data_len_bytes, "HINT_KECCAK256")?; - let mut hints = Vec::new(); zisklib::keccak256(bytes, &mut hints); diff --git a/ziskos-hints/src/handlers/sha256.rs b/ziskos-hints/src/handlers/sha256.rs index 58a45ad90..1971563ea 100644 --- a/ziskos-hints/src/handlers/sha256.rs +++ b/ziskos-hints/src/handlers/sha256.rs @@ -1,4 +1,4 @@ -use crate::{handlers::validate_hint_length, zisklib}; +use crate::zisklib; use anyhow::Result; @@ -18,8 +18,6 @@ pub fn sha256_hint(data: &[u64], data_len_bytes: usize) -> Result> { let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data_len_bytes) }; - validate_hint_length(bytes, data_len_bytes, "HINT_SHA256")?; - let mut hints = Vec::new(); zisklib::sha256(bytes, &mut hints); From cbf18264b40582f4e9c063667b1fda2d82f72bf0 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:05:52 +0000 Subject: [PATCH 344/782] improving secp256k1 hint handler --- ziskos-hints/src/handlers/mod.rs | 11 ++++++++ ziskos-hints/src/handlers/secp256k1.rs | 37 +++++++++++--------------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index c7d7304b4..513697b7d 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -119,3 +119,14 @@ fn validate_hint_length(data: &[T], expected_len: usize, hint_name: &str) -> } Ok(()) } + +/// Converts a big-endian u64 array to little-endian u64 array. +/// Reverses both the array order and the byte order within each u64. +#[inline] +fn u64_be_to_u64_le(input: &[u64; N]) -> [u64; N] { + let mut result = [0u64; N]; + for i in 0..N { + result[i] = input[N - 1 - i].swap_bytes(); + } + result +} diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index b159fb861..00cea9443 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -1,3 +1,4 @@ +use crate::handlers::u64_be_to_u64_le; use crate::handlers::validate_hint_length; use crate::hint_fields; use crate::zisklib; @@ -7,32 +8,24 @@ use anyhow::Result; /// Processes an `HINT_SECP256K1_ECRECOVER` hint. #[inline] pub fn secp256k1_ecrecover_hint(data: &[u64]) -> Result> { - hint_fields![SIG: 64, RECID: 8, MSG: 32, LO_S: 8]; + hint_fields![R: 4, S: 4, RECID: 1, MSG: 4, LO_S: 1]; - let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; + validate_hint_length(data, EXPECTED_LEN, "HINT_SECP256K1_ECRECOVER")?; - validate_hint_length(bytes, EXPECTED_LEN, "HINT_SECP256K1_ECRECOVER")?; - - let sig: &[u8; SIG_SIZE] = bytes[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); - let recid: &[u8; RECID_SIZE] = - bytes[RECID_OFFSET..RECID_OFFSET + RECID_SIZE].try_into().unwrap(); - let recid: u8 = u64::from_le_bytes(*recid) as u8; - let msg: &[u8; MSG_SIZE] = bytes[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); - let low_s: bool = - u64::from_le_bytes(bytes[LO_S_OFFSET..LO_S_OFFSET + LO_S_SIZE].try_into().unwrap()) != 0; + let r: &[u64; R_SIZE] = data[R_OFFSET..R_OFFSET + R_SIZE].try_into().unwrap(); + let s: &[u64; S_SIZE] = data[S_OFFSET..S_OFFSET + S_SIZE].try_into().unwrap(); + let recid = u64::from_le(data[RECID_OFFSET]) as u8; + let msg: &[u64; MSG_SIZE] = data[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); + let low_s: bool = u64::from_le(data[LO_S_OFFSET]) != 0; let mut hints = Vec::new(); - let result: &mut [u8; 32] = &mut [0u8; 32]; - unsafe { - zisklib::secp256k1_ecrecover_c( - sig.as_ptr(), - recid, - msg.as_ptr(), - result.as_mut_ptr(), - low_s, - &mut hints, - ); - } + + let r = u64_be_to_u64_le(r); + let s = u64_be_to_u64_le(s); + let msg = u64_be_to_u64_le(msg); + + zisklib::secp256k1_ecrecover_point(&r, &s, &msg, recid, low_s, &mut hints) + .map_err(|e: u8| anyhow::anyhow!("HINT_SECP256K1_ECRECOVER: {}", e))?; Ok(hints) } From f6a29f0c0bb909c1e26c566d9bbc02fb3c929078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Tue, 27 Jan 2026 11:07:47 +0100 Subject: [PATCH 345/782] Witness Lib simplification (#726) * Witness lib simplification * Remove witness lib calls * Minor modification wcm * Cargo fmt --- .github/workflows/release.yml | 1 - .vscode/launch.json | 2 - Cargo.lock | 23 +++++----- book/getting_started/installation.md | 3 +- book/getting_started/proof.md | 4 +- cli/src/commands/common.rs | 14 ------ cli/src/commands/execute.rs | 8 +--- cli/src/commands/prove.rs | 6 --- cli/src/commands/stats.rs | 6 --- cli/src/commands/verify_constraints.rs | 6 --- common/src/lib.rs | 2 - common/src/types.rs | 28 ++++++++++++ common/src/zisk_lib_init.rs | 56 ----------------------- distributed/Dockerfile | 2 - distributed/README.md | 1 - distributed/crates/worker/src/cli/main.rs | 15 +----- distributed/crates/worker/src/config.rs | 2 - distributed/crates/worker/src/worker.rs | 8 +--- sdk/Cargo.toml | 1 + sdk/src/builder.rs | 29 +----------- sdk/src/prover/asm.rs | 10 ++-- sdk/src/prover/backend.rs | 5 +- sdk/src/prover/emu.rs | 10 ++-- sdk/src/utils.rs | 14 ------ sdk/src/zisk_lib_loader.rs | 31 ++++--------- tools/test-env/build_zisk.sh | 1 - tools/verify_all.sh | 4 -- witness-computation/Cargo.toml | 3 -- witness-computation/src/lib.rs | 1 + witness-computation/src/zisk_lib.rs | 24 +++++----- 30 files changed, 77 insertions(+), 243 deletions(-) delete mode 100644 common/src/zisk_lib_init.rs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba5216afd..dc9ccc284 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -117,7 +117,6 @@ jobs: cp ./target/${TARGET}/release/riscv2zisk zisk-dist/bin/ cp ./target/${TARGET}/release/zisk-coordinator zisk-dist/bin/ cp ./target/${TARGET}/release/zisk-worker zisk-dist/bin/ - cp ./target/${TARGET}/release/libzisk_witness.${LIB_EXT} zisk-dist/bin/ cp ./ziskup/ziskup zisk-dist/bin/ cp ./target/${TARGET}/release/libziskclib.a zisk-dist/bin/ diff --git a/.vscode/launch.json b/.vscode/launch.json index c1f559e01..40f42d394 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,8 +15,6 @@ //"--bin", //"proofman-cli", "verify-constraints", - "--witness-lib", - "../zisk/target/debug/libzisk_witness.so", "--elf", "../zisk-testvectors/pessimistic-proof/program/pessimistic-proof-program-keccak.elf", "-i", diff --git a/Cargo.lock b/Cargo.lock index 65bf63054..0f2a36164 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1029,7 +1029,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "fields", "num-bigint", @@ -1374,7 +1374,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "cfg-if", "num-bigint", @@ -2694,7 +2694,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "colored", "fields", @@ -3090,7 +3090,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "blake3", "borsh", @@ -3125,7 +3125,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "borsh", "colored", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "fields", "itoa", @@ -3169,7 +3169,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "proc-macro2", "quote", @@ -3180,7 +3180,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "crossbeam-channel", "tracing", @@ -3189,7 +3189,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "colored", "fields", @@ -3200,7 +3200,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "bytemuck", "fields", @@ -5414,7 +5414,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#881721d8be0ca34361222cbfc1adabdaeefb12a2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "colored", "fields", @@ -5750,6 +5750,7 @@ dependencies = [ "tracing", "zisk-common", "zisk-distributed-common", + "zisk-witness", "zstd", ] diff --git a/book/getting_started/installation.md b/book/getting_started/installation.md index 3a4e20346..96ad408f4 100644 --- a/book/getting_started/installation.md +++ b/book/getting_started/installation.md @@ -123,8 +123,7 @@ You can use the flags `--provingkey`, `--verifykey` or `--nokey` to specify the 3. Copy the tools to `~/.zisk/bin` directory: ```bash mkdir -p $HOME/.zisk/bin - LIB_EXT=$([[ "$(uname)" == "Darwin" ]] && echo "dylib" || echo "so") - cp target/release/cargo-zisk target/release/ziskemu target/release/riscv2zisk target/release/zisk-coordinator target/release/zisk-worker target/release/libzisk_witness.$LIB_EXT target/release/libziskclib.a $HOME/.zisk/bin + cp target/release/cargo-zisk target/release/ziskemu target/release/riscv2zisk target/release/zisk-coordinator target/release/zisk-worker target/release/libziskclib.a $HOME/.zisk/bin ``` 4. Copy required files for assembly rom setup: diff --git a/book/getting_started/proof.md b/book/getting_started/proof.md index e566c9795..fa508315b 100644 --- a/book/getting_started/proof.md +++ b/book/getting_started/proof.md @@ -26,10 +26,10 @@ In the following steps to verify constraints or generate prove, select one of th - input_two_segments.bin: 512 shas To **verify constraints** use: -`(cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --witness-lib ../zisk/target/release/libzisk_witness.so --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey)` +`(cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey)` To **generate proof** use: -`(cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --witness-lib ../zisk/target/release/libzisk_witness.so --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey)` +`(cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey)` ## Steps to compile a verifiable rust program diff --git a/cli/src/commands/common.rs b/cli/src/commands/common.rs index cb5890265..b97ef04fd 100644 --- a/cli/src/commands/common.rs +++ b/cli/src/commands/common.rs @@ -6,14 +6,6 @@ pub fn get_home_dir() -> String { env::var("HOME").expect("get_home_dir() failed to get HOME environment variable") } -/// Gets the default witness computation library file location in the home installation directory. -pub fn get_default_witness_computation_lib() -> PathBuf { - let extension = if cfg!(target_os = "macos") { "dylib" } else { "so" }; - let witness_computation_lib = - format!("{}/.zisk/bin/libzisk_witness.{}", get_home_dir(), extension); - PathBuf::from(witness_computation_lib) -} - /// Gets the default proving key file location in the home installation directory. pub fn get_default_proving_key() -> PathBuf { let proving_key = format!("{}/.zisk/provingKey", get_home_dir()); @@ -70,12 +62,6 @@ pub fn cli_fail_if_macos() -> anyhow::Result<()> { } } -/// Gets the witness computation library file location. -/// Uses the default one if not specified by user. -pub fn get_witness_computation_lib(witness_lib: Option<&PathBuf>) -> PathBuf { - witness_lib.cloned().unwrap_or_else(get_default_witness_computation_lib) -} - /// Gets the proving key file location. /// Uses the default one if not specified by user. pub fn get_proving_key(proving_key: Option<&PathBuf>) -> PathBuf { diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index a89f44d02..20a4b7429 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -18,10 +18,6 @@ use zisk_common::io::ZiskStdin; .required(false) ))] pub struct ZiskExecute { - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - /// ROM file path /// This is the path to the ROM file that the witness computation dynamic library will use /// to generate the witness. @@ -102,7 +98,6 @@ impl ZiskExecute { let prover = ProverClient::builder() .emu() .witness() - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) @@ -116,8 +111,7 @@ impl ZiskExecute { pub fn run_asm(&mut self, stdin: ZiskStdin) -> Result { let prover = ProverClient::builder() .asm() - .verify_constraints() - .witness_lib_path_opt(self.witness_lib.clone()) + .witness() .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 0958ee6ed..a280b4349 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -21,10 +21,6 @@ use zisk_sdk::{ProverClient, ZiskProveResult}; .required(false) ))] pub struct ZiskProve { - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - /// ELF file path /// This is the path to the ROM file that the witness computation dynamic library will use /// to generate the witness. @@ -176,7 +172,6 @@ impl ZiskProve { .aggregation(self.aggregation) .compressed(self.compressed) .rma(self.rma) - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) @@ -206,7 +201,6 @@ impl ZiskProve { .aggregation(self.aggregation) .compressed(self.compressed) .rma(self.rma) - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index d18f25e07..867630675 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -20,10 +20,6 @@ use crate::ux::print_banner; .required(false) ))] pub struct ZiskStats { - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - /// ROM file path /// This is the path to the ROM file that the witness computation dynamic library will use /// to generate the witness. @@ -131,7 +127,6 @@ impl ZiskStats { let prover = ProverClient::builder() .emu() .witness() - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) @@ -146,7 +141,6 @@ impl ZiskStats { let prover = ProverClient::builder() .asm() .witness() - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 7a7d8061b..56a6840d5 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -20,10 +20,6 @@ use zisk_sdk::{ProverClient, ZiskVerifyConstraintsResult}; .required(false) ))] pub struct ZiskVerifyConstraints { - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - /// ROM file path /// This is the path to the ROM file that the witness computation dynamic library will use /// to generate the witness. @@ -114,7 +110,6 @@ impl ZiskVerifyConstraints { let prover = ProverClient::builder() .emu() .verify_constraints() - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) @@ -129,7 +124,6 @@ impl ZiskVerifyConstraints { let prover = ProverClient::builder() .asm() .verify_constraints() - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) diff --git a/common/src/lib.rs b/common/src/lib.rs index e2f28a94e..7ce32868f 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -12,7 +12,6 @@ mod regular_counters; mod regular_planner; mod types; mod utils; -mod zisk_lib_init; pub use bus::*; pub use component::*; @@ -27,4 +26,3 @@ pub use regular_counters::*; pub use regular_planner::*; pub use types::*; pub use utils::*; -pub use zisk_lib_init::*; diff --git a/common/src/types.rs b/common/src/types.rs index 4bc9c8291..b0eadfc4d 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::time::Instant; /// Type representing a chunk identifier. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -63,3 +64,30 @@ impl fmt::Display for SegmentId { write!(f, "{}", self.0) } } + +#[derive(Debug, Default, Clone)] +pub struct ZiskExecutionResult { + pub executed_steps: u64, +} + +impl ZiskExecutionResult { + pub fn new(executed_steps: u64) -> Self { + Self { executed_steps } + } +} + +#[derive(Debug, Clone)] +pub struct Stats { + pub airgroup_id: usize, + pub air_id: usize, + /// Collect start time + pub collect_start_time: Instant, + /// Collect duration in microseconds + pub collect_duration: u64, + /// Witness start time + pub witness_start_time: Instant, + /// Witness duration in microseconds + pub witness_duration: u128, + /// Number of chunks + pub num_chunks: usize, +} diff --git a/common/src/zisk_lib_init.rs b/common/src/zisk_lib_init.rs deleted file mode 100644 index 0ec1d50b9..000000000 --- a/common/src/zisk_lib_init.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::{path::PathBuf, time::Instant}; - -use fields::PrimeField64; -use proofman_common::VerboseMode; -use witness::WitnessLibrary; - -use crate::{io::ZiskStdin, ExecutorStats}; - -#[derive(Debug, Default, Clone)] -pub struct ZiskExecutionResult { - pub executed_steps: u64, -} - -impl ZiskExecutionResult { - pub fn new(executed_steps: u64) -> Self { - Self { executed_steps } - } -} - -#[derive(Debug, Clone)] -pub struct Stats { - pub airgroup_id: usize, - pub air_id: usize, - /// Collect start time - pub collect_start_time: Instant, - /// Collect duration in microseconds - pub collect_duration: u64, - /// Witness start time - pub witness_start_time: Instant, - /// Witness duration in microseconds - pub witness_duration: u128, - /// Number of chunks - pub num_chunks: usize, -} - -/// Extension trait that provides execution result access without Any boxing -pub trait ZiskWitnessLibrary { - fn set_stdin(&self, stdin: ZiskStdin); - fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)>; -} - -// SUpertrait for ZiskWitnessLibrary and WitnessLibrary -pub trait ZiskLib: - WitnessLibrary + ZiskWitnessLibrary + Send + Sync -{ -} - -pub type ZiskLibInitFn = fn( - VerboseMode, - PathBuf, // Rom path - Option, // Asm path - Option, // Asm ROM path - Option, // Base port for the ASM microservices - bool, // Unlock_mapped_memory - bool, // Shared_tables -) -> Result>, Box>; diff --git a/distributed/Dockerfile b/distributed/Dockerfile index f2bc3b3e9..05deb53d3 100644 --- a/distributed/Dockerfile +++ b/distributed/Dockerfile @@ -75,8 +75,6 @@ RUN mkdir -p bin config/coordinator config/worker /app/proofs /var/log/distribut # Copy binaries from builder stage COPY --from=builder --chown=zisk:zisk /app/target/release/zisk-coordinator ./bin/ COPY --from=builder --chown=zisk:zisk /app/target/release/zisk-worker ./bin/ -# Copy the witness library -COPY --from=builder --chown=zisk:zisk /app/target/release/libzisk_witness.so ./bin/ # Copy configuration files with proper ownership COPY --chown=zisk:zisk distributed/crates/coordinator/config/ ./config/coordinator/ diff --git a/distributed/README.md b/distributed/README.md index 3b715a708..bc7a264a8 100644 --- a/distributed/README.md +++ b/distributed/README.md @@ -365,7 +365,6 @@ The table below lists the available configuration options for the Worker: | `logging.level` | - | RUST_LOG | String | debug | Logging level (error, warn, info, debug, trace) | | `logging.format` | - | - | String | pretty | Logging format (pretty, json, compact) | | `logging.file_path` | - | - | String | - | *Optional*. Log file path (enables file logging) | -| - | `--witness-lib` | - | String | ~/.zisk/bin/libzisk_witness.so | Path to witness computation dynamic library | | - | `--proving-key` | - | String | ~/.zisk/provingKey | Path to setup folder | | - | `--elf` | - | String | - | Path to ELF file | | - | `--asm` | - | String | ~/.zisk/cache | Path to ASM file (mutually exclusive with `--emulator`) | diff --git a/distributed/crates/worker/src/cli/main.rs b/distributed/crates/worker/src/cli/main.rs index 3185edd66..5c6d6078a 100644 --- a/distributed/crates/worker/src/cli/main.rs +++ b/distributed/crates/worker/src/cli/main.rs @@ -1,8 +1,5 @@ use anyhow::Result; -use cargo_zisk::{ - commands::{get_proving_key, get_witness_computation_lib}, - ux::print_banner, -}; +use cargo_zisk::{commands::get_proving_key, ux::print_banner}; use clap::Parser; use colored::Colorize; use std::path::PathBuf; @@ -48,10 +45,6 @@ struct Cli { )] config: Option, - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - /// ELF file path /// This is the path to the ROM file that the witness computation dynamic library will use /// to generate the witness. @@ -136,7 +129,6 @@ async fn main() -> Result<()> { let prover_config_dto = ProverServiceConfigDto { elf: cli.elf.clone(), - witness_lib: cli.witness_lib.clone(), asm: cli.asm.clone(), emulator: cli.emulator, proving_key: cli.proving_key.clone(), @@ -198,11 +190,6 @@ fn print_command_info( .map(|p| format!("(log file: {})", p).bright_black().to_string()) .unwrap_or_default() ); - println!( - "{: >12} {}", - "Witness Lib".bright_green().bold(), - get_witness_computation_lib(Some(&prover_config.witness_lib)).display() - ); println!("{: >12} {}", "Elf".bright_green().bold(), prover_config.elf.display()); if let Some(asm) = &prover_config.asm { diff --git a/distributed/crates/worker/src/config.rs b/distributed/crates/worker/src/config.rs index 06d135c64..8ff243122 100644 --- a/distributed/crates/worker/src/config.rs +++ b/distributed/crates/worker/src/config.rs @@ -145,7 +145,6 @@ impl WorkerServiceConfig { #[derive(Debug, Clone)] pub struct ProverServiceConfigDto { pub elf: PathBuf, - pub witness_lib: Option, pub asm: Option, pub emulator: bool, pub proving_key: Option, @@ -168,7 +167,6 @@ impl Default for ProverServiceConfigDto { fn default() -> Self { Self { elf: PathBuf::new(), - witness_lib: None, asm: None, emulator: false, proving_key: None, diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 93eae46db..0a9cedb20 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use cargo_zisk::commands::{get_proving_key, get_witness_computation_lib}; +use cargo_zisk::commands::get_proving_key; use proofman::{AggProofs, ContributionsInfo}; use rom_setup::{ gen_elf_hash, get_elf_bin_file_path, get_elf_data_hash, get_rom_blowup_factor_and_arity, @@ -50,9 +50,6 @@ pub struct ProverConfig { /// Path to the ELF file pub elf: PathBuf, - /// Path to the witness computation dynamic library - pub witness_lib: PathBuf, - /// Path to the ASM file (optional) pub asm: Option, @@ -207,7 +204,6 @@ impl ProverConfig { Ok(ProverConfig { elf: prover_service_config.elf.clone(), - witness_lib: get_witness_computation_lib(prover_service_config.witness_lib.as_ref()), asm: prover_service_config.asm.clone(), asm_rom, custom_commits_map, @@ -263,7 +259,6 @@ impl Worker { .prove() .aggregation(true) .rma(true) - .witness_lib_path(prover_config.witness_lib.clone()) .proving_key_path(prover_config.proving_key.clone()) .elf_path(prover_config.elf.clone()) .verbose(prover_config.verbose) @@ -295,7 +290,6 @@ impl Worker { .prove() .aggregation(true) .rma(true) - .witness_lib_path(prover_config.witness_lib.clone()) .proving_key_path(prover_config.proving_key.clone()) .elf_path(prover_config.elf.clone()) .verbose(prover_config.verbose) diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 8b9a9e66c..839296374 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -22,6 +22,7 @@ tracing = { workspace = true } zstd = { workspace = true } bytemuck = { workspace = true } zisk-distributed-common = { workspace = true } +zisk-witness = { workspace = true } [features] default = [] diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 399fa73f0..b72d3feff 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use crate::{ - get_asm_paths, get_proving_key, get_witness_computation_lib, + get_asm_paths, get_proving_key, prover::{Asm, AsmProver, Emu, EmuProver, ZiskProver}, }; use colored::Colorize; @@ -52,7 +52,6 @@ pub struct ProverClientBuilder { aggregation: bool, rma: bool, compressed: bool, - witness_lib: Option, proving_key: Option, proving_key_snark: Option, elf: Option, @@ -150,18 +149,6 @@ impl ProverClientBuilder { self } - #[must_use] - pub fn witness_lib_path(mut self, witness_lib: PathBuf) -> Self { - self.witness_lib = Some(witness_lib); - self - } - - #[must_use] - pub fn witness_lib_path_opt(mut self, witness_lib: Option) -> Self { - self.witness_lib = witness_lib; - self - } - #[must_use] pub fn proving_key_path(mut self, proving_key: PathBuf) -> Self { self.proving_key = Some(proving_key); @@ -326,7 +313,6 @@ impl ProverClientBuilder { impl ProverClientBuilder { fn build_emu(self) -> Result> { - let witness_lib = get_witness_computation_lib(self.witness_lib.as_ref()); let proving_key = get_proving_key(self.proving_key.as_ref()); let proving_key_snark = None; let elf = self.elf.ok_or_else(|| anyhow::anyhow!("ELF path is required"))?; @@ -341,7 +327,6 @@ impl ProverClientBuilder { Self::print_emu_command_info( self.witness, self.verify_constraints, - &witness_lib, &proving_key, &proving_key_snark, &elf, @@ -354,7 +339,6 @@ impl ProverClientBuilder { self.aggregation, self.rma, self.compressed, - witness_lib, proving_key, proving_key_snark, elf, @@ -374,7 +358,6 @@ impl ProverClientBuilder { fn print_emu_command_info( witness: bool, verify_constraints: bool, - witness_lib: &Path, proving_key: &Path, proving_key_snark: &Option, elf: &Path, @@ -388,7 +371,6 @@ impl ProverClientBuilder { println!("{: >12} Prove", "Command".bright_green().bold()); } - println!("{: >12} {}", "Witness Lib".bright_green().bold(), witness_lib.display()); println!("{: >12} {}", "Elf".bright_green().bold(), elf.display()); println!( "{: >12} {}", @@ -468,7 +450,6 @@ impl ProverClientBuilder { F: PrimeField64, GoldilocksQuinticExtension: ExtensionField, { - let witness_lib = get_witness_computation_lib(self.witness_lib.as_ref()); let proving_key = get_proving_key(self.proving_key.as_ref()); let proving_key_snark = None; let elf = self.elf.ok_or_else(|| anyhow::anyhow!("ELF path is required"))?; @@ -485,7 +466,6 @@ impl ProverClientBuilder { Self::print_asm_command_info( self.witness, self.verify_constraints, - &witness_lib, &proving_key, &proving_key_snark, &elf, @@ -498,7 +478,6 @@ impl ProverClientBuilder { self.aggregation, self.rma, self.compressed, - witness_lib, proving_key, proving_key_snark, elf, @@ -522,7 +501,6 @@ impl ProverClientBuilder { fn print_asm_command_info( witness: bool, verify_constraints: bool, - witness_lib: &Path, proving_key: &Path, proving_key_snark: &Option, elf: &Path, @@ -536,7 +514,6 @@ impl ProverClientBuilder { println!("{: >12} Prove", "Command".bright_green().bold()); } - println!("{: >12} {}", "Witness Lib".bright_green().bold(), witness_lib.display()); println!("{: >12} {}", "Elf".bright_green().bold(), elf.display()); println!("{: >12} {}", "Proving key".bright_green().bold(), proving_key.display()); @@ -565,7 +542,6 @@ impl From> for ProverClientBuilder { witness: builder.witness, rma: builder.rma, compressed: builder.compressed, - witness_lib: builder.witness_lib, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, @@ -601,7 +577,6 @@ impl From> for ProverClientBuilder { witness: builder.witness, rma: builder.rma, compressed: builder.compressed, - witness_lib: builder.witness_lib, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, @@ -639,7 +614,6 @@ impl From> witness: builder.witness, rma: builder.rma, compressed: builder.compressed, - witness_lib: builder.witness_lib, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, @@ -675,7 +649,6 @@ impl From> for ProverClientBuilder, elf: PathBuf, @@ -56,7 +56,6 @@ impl AsmProver { aggregation, rma, compressed, - witness_lib, proving_key, proving_key_snark, elf, @@ -186,7 +185,6 @@ impl AsmCoreProver { aggregation: bool, rma: bool, compressed: bool, - witness_lib: PathBuf, proving_key: PathBuf, _proving_key_snark: Option, elf: PathBuf, @@ -214,14 +212,12 @@ impl AsmCoreProver { let asm_mt_path = default_cache_path.join(asm_mt_filename); let asm_rh_path = default_cache_path.join(asm_rh_filename); - check_paths_exist(&witness_lib)?; check_paths_exist(&proving_key)?; check_paths_exist(&elf)?; check_paths_exist(&asm_mt_path)?; check_paths_exist(&asm_rh_path)?; - let (library, mut witness_lib) = ZiskLibLoader::load_asm( - witness_lib, + let mut witness_lib = ZiskLibLoader::load_asm( elf, verbose.into(), shared_tables, @@ -264,7 +260,7 @@ impl AsmCoreProver { asm_services.start_asm_services(&asm_mt_path, asm_runner_options)?; timer_stop_and_log_info!(STARTING_ASM_MICROSERVICES); - proofman.register_witness(&mut *witness_lib, library)?; + witness_lib.register_witness(&proofman.get_wcm())?; proofman.set_barrier(); diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 0dd397dbd..b2a490b57 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -9,14 +9,15 @@ use fields::Goldilocks; use proofman::{AggProofs, ProofInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult}; use proofman_common::{DebugInfo, ProofOptions}; use std::{fs::File, io::Write, path::PathBuf}; -use zisk_common::{io::ZiskStdin, ExecutorStats, ProofLog, ZiskExecutionResult, ZiskLib}; +use zisk_common::{io::ZiskStdin, ExecutorStats, ProofLog, ZiskExecutionResult}; +use zisk_witness::WitnessLib; pub(crate) struct ProverBackend { pub verify_constraints: bool, pub aggregation: bool, pub rma: bool, pub compressed: bool, - pub witness_lib: Box>, + pub witness_lib: WitnessLib, pub proving_key: PathBuf, pub verify_proofs: bool, pub minimal_memory: bool, diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index ce66d3a71..388c8ae16 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -10,6 +10,7 @@ use std::path::PathBuf; use zisk_common::io::ZiskStdin; use zisk_common::ExecutorStats; use zisk_distributed_common::LoggingConfig; +use zisk_witness::WitnessLibrary; use anyhow::Result; @@ -30,7 +31,6 @@ impl EmuProver { aggregation: bool, rma: bool, compressed: bool, - witness_lib: PathBuf, proving_key: PathBuf, proving_key_snark: Option, elf: PathBuf, @@ -48,7 +48,6 @@ impl EmuProver { aggregation, rma, compressed, - witness_lib, proving_key, proving_key_snark, elf, @@ -159,7 +158,6 @@ impl EmuCoreProver { aggregation: bool, rma: bool, compressed: bool, - witness_lib: PathBuf, proving_key: PathBuf, _proving_key_snark: Option, elf: PathBuf, @@ -174,13 +172,11 @@ impl EmuCoreProver { ) -> Result { let custom_commits_map = get_custom_commits_map(&proving_key, &elf)?; - check_paths_exist(&witness_lib)?; check_paths_exist(&proving_key)?; check_paths_exist(&elf)?; // Build emulator library - let (library, mut witness_lib) = - ZiskLibLoader::load_emu(witness_lib, elf, verbose.into(), shared_tables)?; + let mut witness_lib = ZiskLibLoader::load_emu(elf, verbose.into(), shared_tables)?; let proofman = ProofMan::new( proving_key.clone(), @@ -202,7 +198,7 @@ impl EmuCoreProver { initialize_logger(verbose.into(), Some(world_rank)); } - proofman.register_witness(&mut *witness_lib, library)?; + witness_lib.register_witness(&proofman.get_wcm())?; proofman.set_barrier(); diff --git a/sdk/src/utils.rs b/sdk/src/utils.rs index f128284ed..bc6512224 100644 --- a/sdk/src/utils.rs +++ b/sdk/src/utils.rs @@ -15,14 +15,6 @@ pub fn get_home_dir() -> String { env::var("HOME").expect("get_home_dir() failed to get HOME environment variable") } -/// Gets the default witness computation library file location in the home installation directory. -pub fn get_default_witness_computation_lib() -> PathBuf { - let extension = if cfg!(target_os = "macos") { "dylib" } else { "so" }; - let witness_computation_lib = - format!("{}/.zisk/bin/libzisk_witness.{}", get_home_dir(), extension); - PathBuf::from(witness_computation_lib) -} - /// Gets the default proving key file location in the home installation directory. pub fn get_default_proving_key() -> PathBuf { let proving_key = format!("{}/.zisk/provingKey", get_home_dir()); @@ -79,12 +71,6 @@ pub fn cli_fail_if_macos() -> anyhow::Result<()> { } } -/// Gets the witness computation library file location. -/// Uses the default one if not specified by user. -pub fn get_witness_computation_lib(witness_lib: Option<&PathBuf>) -> PathBuf { - witness_lib.cloned().unwrap_or_else(get_default_witness_computation_lib) -} - /// Gets the proving key file location. /// Uses the default one if not specified by user. pub fn get_proving_key(proving_key: Option<&PathBuf>) -> PathBuf { diff --git a/sdk/src/zisk_lib_loader.rs b/sdk/src/zisk_lib_loader.rs index d62243abe..67f46dfa3 100644 --- a/sdk/src/zisk_lib_loader.rs +++ b/sdk/src/zisk_lib_loader.rs @@ -1,21 +1,17 @@ use std::path::PathBuf; use fields::PrimeField64; -use libloading::{Library, Symbol}; use proofman_common::VerboseMode; -use zisk_common::{ZiskLib, ZiskLibInitFn}; +use zisk_witness::{init_zisk_lib, WitnessLib}; use anyhow::Result; -use crate::get_witness_computation_lib; - #[derive(Default)] pub struct ZiskLibLoader; impl ZiskLibLoader { #[allow(clippy::too_many_arguments)] fn load_library( - witness_lib: PathBuf, elf: PathBuf, verbose: VerboseMode, shared_tables: bool, @@ -23,15 +19,8 @@ impl ZiskLibLoader { asm_rh_filename: Option, base_port: Option, unlock_mapped_memory: Option, - ) -> Result<(Library, Box>)> { - let lib_path = get_witness_computation_lib(Some(&witness_lib)); - let library = unsafe { Library::new(lib_path) }?; - - tracing::info!("Loading witness library from {:?}", witness_lib); - let witness_lib_constructor: Symbol> = - unsafe { library.get(b"init_library")? }; - - let witness_lib = witness_lib_constructor( + ) -> Result> { + let witness_lib = init_zisk_lib( verbose, elf, asm_mt_filename, @@ -39,24 +28,21 @@ impl ZiskLibLoader { base_port, unlock_mapped_memory.unwrap_or(false), shared_tables, - ) - .expect("Failed to initialize witness library"); + ); - Ok((library, witness_lib)) + Ok(witness_lib) } pub fn load_emu( - witness_lib: PathBuf, elf: PathBuf, verbose: VerboseMode, shared_tables: bool, - ) -> Result<(Library, Box>)> { - Self::load_library(witness_lib, elf, verbose, shared_tables, None, None, None, None) + ) -> Result> { + Self::load_library(elf, verbose, shared_tables, None, None, None, None) } #[allow(clippy::too_many_arguments)] pub fn load_asm( - witness_lib: PathBuf, elf: PathBuf, verbose: VerboseMode, shared_tables: bool, @@ -64,9 +50,8 @@ impl ZiskLibLoader { asm_rh_filename: PathBuf, base_port: Option, unlock_mapped_memory: bool, - ) -> Result<(Library, Box>)> { + ) -> Result> { Self::load_library( - witness_lib, elf, verbose, shared_tables, diff --git a/tools/test-env/build_zisk.sh b/tools/test-env/build_zisk.sh index 954b9d7b3..3d85549b5 100755 --- a/tools/test-env/build_zisk.sh +++ b/tools/test-env/build_zisk.sh @@ -162,7 +162,6 @@ main() { LIB_EXT="dylib" fi - ensure cp target/${TARGET}/release/libzisk_witness.${LIB_EXT} "${ZISK_BIN_DIR}" || return 1 ensure cp ziskup/ziskup "${ZISK_BIN_DIR}" || return 1 ensure cp target/${TARGET}/release/libziskclib.a "${ZISK_BIN_DIR}" || return 1 diff --git a/tools/verify_all.sh b/tools/verify_all.sh index 6e3f5f97d..b7fa6019d 100755 --- a/tools/verify_all.sh +++ b/tools/verify_all.sh @@ -224,7 +224,6 @@ if [[ $elf_mode -eq 0 ]]; then if (cargo run --release --bin cargo-zisk verify-constraints \ --emulator \ - --witness-lib target/release/libzisk_witness.so \ --elf "$elf_file" \ --proving-key "$proving_key"); then record_result "$elf_file" "PASSED" "$counter" @@ -248,7 +247,6 @@ else if (cargo run --release --bin cargo-zisk verify-constraints \ --emulator \ - --witness-lib target/release/libzisk_witness.so \ --elf "$elf_file" \ --proving-key "$proving_key"); then record_result "$elf_file" "PASSED" @@ -262,7 +260,6 @@ else if (cargo run --release --bin cargo-zisk verify-constraints \ --emulator \ - --witness-lib target/release/libzisk_witness.so \ --elf "$elf_file" \ --input "$input_path" \ --proving-key "$proving_key"); then @@ -295,7 +292,6 @@ else if (cargo run --release --bin cargo-zisk verify-constraints \ --emulator \ - --witness-lib target/release/libzisk_witness.so \ --elf "$elf_file" \ --input "$input_file" \ --proving-key "$proving_key"); then diff --git a/witness-computation/Cargo.toml b/witness-computation/Cargo.toml index e932d32f9..596a4da4d 100644 --- a/witness-computation/Cargo.toml +++ b/witness-computation/Cargo.toml @@ -7,9 +7,6 @@ keywords = { workspace = true } repository = { workspace = true } categories = { workspace = true } -[lib] -crate-type = ["dylib"] - [dependencies] executor = { workspace = true } sm-arith = { workspace = true } diff --git a/witness-computation/src/lib.rs b/witness-computation/src/lib.rs index 39ef75d65..f92509468 100644 --- a/witness-computation/src/lib.rs +++ b/witness-computation/src/lib.rs @@ -1,3 +1,4 @@ mod zisk_lib; +pub use witness::WitnessLibrary; pub use zisk_lib::*; diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 9fb5831ce..03d7eec7b 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -24,7 +24,7 @@ use sm_mem::Mem; use sm_rom::RomSM; use std::{collections::HashMap, path::PathBuf, sync::Arc}; use witness::{WitnessLibrary, WitnessManager}; -use zisk_common::{io::ZiskStdin, ExecutorStats, ZiskExecutionResult, ZiskLib, ZiskWitnessLibrary}; +use zisk_common::{io::ZiskStdin, ExecutorStats, ZiskExecutionResult}; use zisk_core::{Riscv2zisk, CHUNK_SIZE}; #[cfg(feature = "packed")] use zisk_pil::PACKED_INFO; @@ -49,9 +49,8 @@ pub struct WitnessLib { verbose_mode: proofman_common::VerboseMode, } -#[no_mangle] #[allow(clippy::too_many_arguments)] -fn init_library( +pub fn init_zisk_lib( verbose_mode: proofman_common::VerboseMode, elf_path: PathBuf, asm_mt_path: Option, @@ -59,10 +58,10 @@ fn init_library( base_port: Option, unlock_mapped_memory: bool, shared_tables: bool, -) -> Result>, Box> { +) -> WitnessLib { let chunk_size = CHUNK_SIZE; - let result = Box::new(WitnessLib { + WitnessLib { elf_path, asm_mt_path, asm_rh_path, @@ -72,9 +71,7 @@ fn init_library( unlock_mapped_memory, shared_tables, verbose_mode, - }); - - Ok(result) + } } impl WitnessLibrary for WitnessLib { @@ -208,6 +205,9 @@ impl WitnessLibrary for WitnessLib { wcm.register_component(executor.clone()); self.executor = Some(executor); + + wcm.set_witness_initialized(); + Ok(()) } @@ -230,8 +230,8 @@ impl WitnessLibrary for WitnessLib { } } -impl ZiskWitnessLibrary for WitnessLib { - fn set_stdin(&self, stdin: ZiskStdin) { +impl WitnessLib { + pub fn set_stdin(&self, stdin: ZiskStdin) { if let Some(executor) = &self.executor { executor.set_stdin(stdin); } @@ -241,9 +241,7 @@ impl ZiskWitnessLibrary for WitnessLib { /// /// # Returns /// * `u16` - The execution result code. - fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)> { + pub fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)> { self.executor.as_ref().map(|executor| executor.get_execution_result()) } } - -impl ZiskLib for WitnessLib {} From 27840bf73c2f05764f8c84839a34e450c59e0fa3 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:20:58 +0000 Subject: [PATCH 346/782] improving kzg hint handler --- ziskos-hints/src/handlers/kzg.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/ziskos-hints/src/handlers/kzg.rs b/ziskos-hints/src/handlers/kzg.rs index ebf7407c5..158885d09 100644 --- a/ziskos-hints/src/handlers/kzg.rs +++ b/ziskos-hints/src/handlers/kzg.rs @@ -1,4 +1,4 @@ -use crate::{handlers::validate_hint_length, hint_fields, zisklib}; +use crate::{handlers::validate_hint_length, hint_fields, zisklib::verify_kzg_proof}; use anyhow::Result; @@ -19,15 +19,8 @@ pub fn verify_kzg_proof_hint(data: &[u64]) -> Result> { bytes[PROOF_OFFSET..PROOF_OFFSET + PROOF_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - unsafe { - zisklib::verify_kzg_proof_c( - z.as_ptr(), - y.as_ptr(), - commitment.as_ptr(), - proof.as_ptr(), - &mut hints, - ) - }; + + verify_kzg_proof(z, y, commitment, proof, &mut hints); Ok(hints) } From 9305ee7deca17bccce94ed20822b09b386339cdf Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 27 Jan 2026 11:22:00 +0000 Subject: [PATCH 347/782] Fix mem inputs asm --- common/src/bus/bus_device_metrics.rs | 1 + executor/src/sm_static_bundle.rs | 37 ++++++++++++++----- .../arith_eq/src/arith_eq_bus_device.rs | 15 ++++++-- precompiles/arith_eq/src/arith_eq_manager.rs | 7 +++- .../src/arith_eq_384_bus_device.rs | 15 ++++++-- .../arith_eq_384/src/arith_eq_384_manager.rs | 7 +++- precompiles/big_int/src/add256_bus_device.rs | 16 +++++--- precompiles/big_int/src/add256_manager.rs | 7 +++- precompiles/dma/src/dma_bus_device.rs | 15 ++++++-- precompiles/dma/src/dma_manager.rs | 7 +++- precompiles/keccakf/src/keccakf_bus_device.rs | 16 +++++--- precompiles/keccakf/src/keccakf_manager.rs | 7 +++- .../poseidon2/src/poseidon2_bus_device.rs | 16 +++++--- .../poseidon2/src/poseidon2_manager.rs | 7 +++- precompiles/sha256f/src/sha256f_bus_device.rs | 16 +++++--- precompiles/sha256f/src/sha256f_manager.rs | 7 +++- 16 files changed, 141 insertions(+), 55 deletions(-) diff --git a/common/src/bus/bus_device_metrics.rs b/common/src/bus/bus_device_metrics.rs index 160f6856a..86894cb24 100644 --- a/common/src/bus/bus_device_metrics.rs +++ b/common/src/bus/bus_device_metrics.rs @@ -12,6 +12,7 @@ use crate::Metrics; #[derive(Debug, PartialEq)] pub enum BusDeviceMode { Counter, + CounterAsm, InputGenerator, } diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index 88f76a978..8c92a5257 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -225,27 +225,46 @@ impl StaticSMBundle { arith_counter = Some((sm.type_id(), arith_sm.build_arith_counter())); } StateMachines::KeccakfManager(keccak_sm) => { - keccakf_counter = Some((sm.type_id(), keccak_sm.build_keccakf_counter())); + keccakf_counter = Some(( + sm.type_id(), + keccak_sm.build_keccakf_counter(self.process_only_operation_bus), + )); } StateMachines::Sha256fManager(sha256_sm) => { - sha256f_counter = Some((sm.type_id(), sha256_sm.build_sha256f_counter())); + sha256f_counter = Some(( + sm.type_id(), + sha256_sm.build_sha256f_counter(self.process_only_operation_bus), + )); } StateMachines::Poseidon2Manager(poseidon2_sm) => { - poseidon2_counter = - Some((sm.type_id(), poseidon2_sm.build_poseidon2_counter())); + poseidon2_counter = Some(( + sm.type_id(), + poseidon2_sm.build_poseidon2_counter(self.process_only_operation_bus), + )); } StateMachines::ArithEqManager(arith_eq_sm) => { - arith_eq_counter = Some((sm.type_id(), arith_eq_sm.build_arith_eq_counter())); + arith_eq_counter = Some(( + sm.type_id(), + arith_eq_sm.build_arith_eq_counter(self.process_only_operation_bus), + )); } StateMachines::ArithEq384Manager(arith_eq_384_sm) => { - arith_eq_384_counter = - Some((sm.type_id(), arith_eq_384_sm.build_arith_eq_384_counter())); + arith_eq_384_counter = Some(( + sm.type_id(), + arith_eq_384_sm.build_arith_eq_384_counter(self.process_only_operation_bus), + )); } StateMachines::Add256Manager(add256_sm) => { - add256_counter = Some((sm.type_id(), add256_sm.build_add256_counter())); + add256_counter = Some(( + sm.type_id(), + add256_sm.build_add256_counter(self.process_only_operation_bus), + )); } StateMachines::DmaManager(dma_sm) => { - dma_counter = Some((sm.type_id(), dma_sm.build_dma_counter())); + dma_counter = Some(( + sm.type_id(), + dma_sm.build_dma_counter(self.process_only_operation_bus), + )); } StateMachines::RomSM(_) => {} } diff --git a/precompiles/arith_eq/src/arith_eq_bus_device.rs b/precompiles/arith_eq/src/arith_eq_bus_device.rs index 6a77fde87..19dc40dd8 100644 --- a/precompiles/arith_eq/src/arith_eq_bus_device.rs +++ b/precompiles/arith_eq/src/arith_eq_bus_device.rs @@ -179,10 +179,17 @@ impl BusDevice for ArithEqCounterInputGen { let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); - } + let only_counters = match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + true + } + BusDeviceMode::CounterAsm => { + self.measure(data); + return true; + } + BusDeviceMode::InputGenerator => false, + }; match op { ARITH256_OP => { diff --git a/precompiles/arith_eq/src/arith_eq_manager.rs b/precompiles/arith_eq/src/arith_eq_manager.rs index 2d8835a68..9b3890cef 100644 --- a/precompiles/arith_eq/src/arith_eq_manager.rs +++ b/precompiles/arith_eq/src/arith_eq_manager.rs @@ -31,8 +31,11 @@ impl ArithEqManager { Arc::new(Self { arith_eq_sm }) } - pub fn build_arith_eq_counter(&self) -> ArithEqCounterInputGen { - ArithEqCounterInputGen::new(BusDeviceMode::Counter) + pub fn build_arith_eq_counter(&self, asm_execution: bool) -> ArithEqCounterInputGen { + match asm_execution { + true => ArithEqCounterInputGen::new(BusDeviceMode::CounterAsm), + false => ArithEqCounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_arith_eq_input_generator(&self) -> ArithEqCounterInputGen { diff --git a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs index 438f2d045..534a72f1b 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs @@ -167,10 +167,17 @@ impl BusDevice for ArithEq384CounterInputGen { let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); - } + let only_counters = match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + true + } + BusDeviceMode::CounterAsm => { + self.measure(data); + return true; + } + BusDeviceMode::InputGenerator => false, + }; match op { ARITH384_MOD_OP => { diff --git a/precompiles/arith_eq_384/src/arith_eq_384_manager.rs b/precompiles/arith_eq_384/src/arith_eq_384_manager.rs index 033c8d158..ad5f58c39 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_manager.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_manager.rs @@ -31,8 +31,11 @@ impl ArithEq384Manager { Arc::new(Self { arith_eq_384_sm }) } - pub fn build_arith_eq_384_counter(&self) -> ArithEq384CounterInputGen { - ArithEq384CounterInputGen::new(BusDeviceMode::Counter) + pub fn build_arith_eq_384_counter(&self, asm_execution: bool) -> ArithEq384CounterInputGen { + match asm_execution { + true => ArithEq384CounterInputGen::new(BusDeviceMode::CounterAsm), + false => ArithEq384CounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_arith_eq_384_input_generator(&self) -> ArithEq384CounterInputGen { diff --git a/precompiles/big_int/src/add256_bus_device.rs b/precompiles/big_int/src/add256_bus_device.rs index 00fee3170..364ca7b69 100644 --- a/precompiles/big_int/src/add256_bus_device.rs +++ b/precompiles/big_int/src/add256_bus_device.rs @@ -124,13 +124,19 @@ impl BusDevice for Add256CounterInputGen { let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_add256_mem_inputs(addr_main, step_main, data, true, pending); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + generate_add256_mem_inputs(addr_main, step_main, data, false, pending); + } } - generate_add256_mem_inputs(addr_main, step_main, data, only_counters, pending); - true } diff --git a/precompiles/big_int/src/add256_manager.rs b/precompiles/big_int/src/add256_manager.rs index 99a471749..0ff2c8d86 100644 --- a/precompiles/big_int/src/add256_manager.rs +++ b/precompiles/big_int/src/add256_manager.rs @@ -38,8 +38,11 @@ impl Add256Manager { Arc::new(Self { add256_sm }) } - pub fn build_add256_counter(&self) -> Add256CounterInputGen { - Add256CounterInputGen::new(BusDeviceMode::Counter) + pub fn build_add256_counter(&self, asm_execution: bool) -> Add256CounterInputGen { + match asm_execution { + true => Add256CounterInputGen::new(BusDeviceMode::CounterAsm), + false => Add256CounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_add256_input_generator(&self) -> Add256CounterInputGen { diff --git a/precompiles/dma/src/dma_bus_device.rs b/precompiles/dma/src/dma_bus_device.rs index c8199fc0e..a96cf20d8 100644 --- a/precompiles/dma/src/dma_bus_device.rs +++ b/precompiles/dma/src/dma_bus_device.rs @@ -176,12 +176,19 @@ impl BusDevice for DmaCounterInputGen { } } - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_dma_mem_inputs(data, data_ext, true, pending); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + generate_dma_mem_inputs(data, data_ext, false, pending); + } } - generate_dma_mem_inputs(data, data_ext, only_counters, pending); true } diff --git a/precompiles/dma/src/dma_manager.rs b/precompiles/dma/src/dma_manager.rs index 2194b7686..15bd934d5 100644 --- a/precompiles/dma/src/dma_manager.rs +++ b/precompiles/dma/src/dma_manager.rs @@ -39,8 +39,11 @@ impl DmaManager { Arc::new(Self { dma_sm, dma_pre_post_sm, dma_64_aligned_sm, dma_unaligned_sm }) } - pub fn build_dma_counter(&self) -> DmaCounterInputGen { - DmaCounterInputGen::new(BusDeviceMode::Counter) + pub fn build_dma_counter(&self, asm_execution: bool) -> DmaCounterInputGen { + match asm_execution { + true => DmaCounterInputGen::new(BusDeviceMode::CounterAsm), + false => DmaCounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_dma_input_generator(&self) -> DmaCounterInputGen { diff --git a/precompiles/keccakf/src/keccakf_bus_device.rs b/precompiles/keccakf/src/keccakf_bus_device.rs index 7bae908f2..7d190a2db 100644 --- a/precompiles/keccakf/src/keccakf_bus_device.rs +++ b/precompiles/keccakf/src/keccakf_bus_device.rs @@ -124,13 +124,19 @@ impl BusDevice for KeccakfCounterInputGen { let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_keccakf_mem_inputs(addr_main, step_main, data, true, pending); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + generate_keccakf_mem_inputs(addr_main, step_main, data, false, pending); + } } - generate_keccakf_mem_inputs(addr_main, step_main, data, only_counters, pending); - true } diff --git a/precompiles/keccakf/src/keccakf_manager.rs b/precompiles/keccakf/src/keccakf_manager.rs index e3a206cdf..fab4a338a 100644 --- a/precompiles/keccakf/src/keccakf_manager.rs +++ b/precompiles/keccakf/src/keccakf_manager.rs @@ -31,8 +31,11 @@ impl KeccakfManager { Arc::new(Self { keccakf_sm }) } - pub fn build_keccakf_counter(&self) -> KeccakfCounterInputGen { - KeccakfCounterInputGen::new(BusDeviceMode::Counter) + pub fn build_keccakf_counter(&self, asm_execution: bool) -> KeccakfCounterInputGen { + match asm_execution { + true => KeccakfCounterInputGen::new(BusDeviceMode::CounterAsm), + false => KeccakfCounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_keccakf_input_generator(&self) -> KeccakfCounterInputGen { diff --git a/precompiles/poseidon2/src/poseidon2_bus_device.rs b/precompiles/poseidon2/src/poseidon2_bus_device.rs index 38e38a89c..df35893e1 100644 --- a/precompiles/poseidon2/src/poseidon2_bus_device.rs +++ b/precompiles/poseidon2/src/poseidon2_bus_device.rs @@ -124,13 +124,19 @@ impl BusDevice for Poseidon2CounterInputGen { let step_main = data[A]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_poseidon2_mem_inputs(addr_main, step_main, data, true, pending); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + generate_poseidon2_mem_inputs(addr_main, step_main, data, false, pending); + } } - generate_poseidon2_mem_inputs(addr_main, step_main, data, only_counters, pending); - true } diff --git a/precompiles/poseidon2/src/poseidon2_manager.rs b/precompiles/poseidon2/src/poseidon2_manager.rs index 77ed48897..10ac169ec 100644 --- a/precompiles/poseidon2/src/poseidon2_manager.rs +++ b/precompiles/poseidon2/src/poseidon2_manager.rs @@ -29,8 +29,11 @@ impl Poseidon2Manager { Arc::new(Self { poseidon2_sm }) } - pub fn build_poseidon2_counter(&self) -> Poseidon2CounterInputGen { - Poseidon2CounterInputGen::new(BusDeviceMode::Counter) + pub fn build_poseidon2_counter(&self, asm_execution: bool) -> Poseidon2CounterInputGen { + match asm_execution { + true => Poseidon2CounterInputGen::new(BusDeviceMode::CounterAsm), + false => Poseidon2CounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_poseidon2_input_generator(&self) -> Poseidon2CounterInputGen { diff --git a/precompiles/sha256f/src/sha256f_bus_device.rs b/precompiles/sha256f/src/sha256f_bus_device.rs index bf6b72ce9..aa9120595 100644 --- a/precompiles/sha256f/src/sha256f_bus_device.rs +++ b/precompiles/sha256f/src/sha256f_bus_device.rs @@ -124,13 +124,19 @@ impl BusDevice for Sha256fCounterInputGen { let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_sha256f_mem_inputs(addr_main, step_main, data, true, pending); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + generate_sha256f_mem_inputs(addr_main, step_main, data, false, pending); + } } - generate_sha256f_mem_inputs(addr_main, step_main, data, only_counters, pending); - true } diff --git a/precompiles/sha256f/src/sha256f_manager.rs b/precompiles/sha256f/src/sha256f_manager.rs index 658c6201f..1c55ec19a 100644 --- a/precompiles/sha256f/src/sha256f_manager.rs +++ b/precompiles/sha256f/src/sha256f_manager.rs @@ -30,8 +30,11 @@ impl Sha256fManager { Arc::new(Self { sha256f_sm }) } - pub fn build_sha256f_counter(&self) -> Sha256fCounterInputGen { - Sha256fCounterInputGen::new(BusDeviceMode::Counter) + pub fn build_sha256f_counter(&self, asm_execution: bool) -> Sha256fCounterInputGen { + match asm_execution { + true => Sha256fCounterInputGen::new(BusDeviceMode::CounterAsm), + false => Sha256fCounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_sha256f_input_generator(&self) -> Sha256fCounterInputGen { From 703357bf3997b40067fd39fcbf97a24cb3a4ef36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:06:55 +0000 Subject: [PATCH 348/782] Adding maps to G1 and G2 --- .../src/zisklib/lib/bls12_381/constants.rs | 761 +++++++++++++++++- .../src/zisklib/lib/bls12_381/curve.rs | 2 +- .../src/zisklib/lib/bls12_381/fp.rs | 17 + .../src/zisklib/lib/bls12_381/fp2.rs | 33 +- .../src/zisklib/lib/bls12_381/map_to_curve.rs | 711 ++++++++++++++++ .../src/zisklib/lib/bls12_381/mod.rs | 2 + .../src/zisklib/lib/bls12_381/twist.rs | 138 +++- 7 files changed, 1651 insertions(+), 13 deletions(-) create mode 100644 ziskos/entrypoint/src/zisklib/lib/bls12_381/map_to_curve.rs diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs index 83e7cb2b1..0175754d2 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/constants.rs @@ -64,12 +64,12 @@ pub const G2_GENERATOR: [u64; 24] = [ /// Base field size pub const P: [u64; 6] = [ - 0xB9FEFFFFFFFFAAAB, - 0x1EABFFFEB153FFFF, - 0x6730D2A0F6B0F624, - 0x64774B84F38512BF, - 0x4B1BA7B6434BACD7, - 0x1A0111EA397FE69A, + 0xB9FE_FFFF_FFFF_AAAB, + 0x1EAB_FFFE_B153_FFFF, + 0x6730_D2A0_F6B0_F624, + 0x6477_4B84_F385_12BF, + 0x4B1B_A7B6_434B_ACD7, + 0x1A01_11EA_397F_E69A, ]; /// Base field size minus one @@ -77,7 +77,7 @@ pub const P_MINUS_ONE: [u64; 6] = [P[0] - 1, P[1], P[2], P[3], P[4], P[5]]; /// Scalar field size pub const R: [u64; 4] = - [0xFFFFFFFF00000001, 0x53BDA402FFFE5BFE, 0x3339D80809A1D805, 0x73EDA753299D7D48]; + [0xFFFF_FFFF_0000_0001, 0x53BD_A402_FFFE_5BFE, 0x3339_D808_09A1_D805, 0x73ED_A753_299D_7D48]; /// Scalar field size minus one pub const R_MINUS_ONE: [u64; 4] = [R[0] - 1, R[1], R[2], R[3]]; @@ -275,3 +275,750 @@ pub const TRUSTED_SETUP_TAU_G2: [u64; 24] = [ 0x03432fcae0181b4b, 0x1666c54b0a325295, ]; + +// ============================================================================ +// Constants for G1 mapping (11-isogenous curve E': y² = x³ + A'x + B') +// ============================================================================ + +/// A' coefficient of the isogenous curve E' for G1 +/// A' = 0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d +pub const ISO_A_G1: [u64; 6] = [ + 0x5CF4_2808_2D58_4C1D, + 0x9893_6F8D_A0E0_F97F, + 0xD8E8_981A_EFD8_81AC, + 0xB0EA_9853_83EE_66A8, + 0x3D69_3A02_C96D_4982, + 0x0014_4698_A3B8_E943, +]; + +/// B' coefficient of the isogenous curve E' for G1 +/// B' = 0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0 +pub const ISO_B_G1: [u64; 6] = [ + 0xD1CC_48E9_8E17_2BE0, + 0x5A23_215A_316C_EAA5, + 0xA0B9_C14F_CEF3_5EF5, + 0x2016_C1F0_F24F_4070, + 0x018B_12E8_753E_EE3B, + 0x12E2_908D_1168_8030, +]; + +/// Z constant for G1 SWU: Z = 11 +pub const SWU_Z_G1: [u64; 6] = [0x0B, 0, 0, 0, 0, 0]; +pub const SWU_Z2_G1: [u64; 6] = [0x79, 0, 0, 0, 0, 0]; // 0x0B^2 + +/// Cofactor for G1 +pub const COFACTOR_G1: [u64; 4] = [0xD201000000010001, 0x0, 0x0, 0x0]; + +// ============================================================================ +// G1 Isogeny Map Coefficients (11-isogeny from E' to E) +// ============================================================================ + +/// Isogeny map x-numerator coefficients for G1 +pub const ISO_X_NUM_G1: [[u64; 6]; 12] = [ + [ + 0xAEAC_1662_7346_49B7, + 0x5610_C2D5_F2E6_2D6E, + 0xF262_7B56_CDB4_E2C8, + 0x6B30_3E88_A2D7_005F, + 0xB809_101D_D998_1585, + 0x11A0_5F2B_1E83_3340, + ], + [ + 0xE834_EEF1_B3CB_83BB, + 0x4838_F2A6_F318_C356, + 0xF565_E33C_70D1_E86B, + 0x7C17_E75B_2F6A_8417, + 0x0588_BAB2_2147_A81C, + 0x1729_4ED3_E943_AB2F, + ], + [ + 0xE017_9F9D_AC9E_DCB0, + 0x958C_3E3D_2A09_729F, + 0x6878_E501_EC68_E25C, + 0xCE03_2473_2959_83E5, + 0x1D10_48C5_D10A_9A1B, + 0x0D54_005D_B976_78EC, + ], + [ + 0xC5B3_8864_1D9B_6861, + 0x5336_E25C_E310_7193, + 0xF1B3_3289_F1B3_3083, + 0xD7F5_E465_6A8D_BF25, + 0x4E06_09D3_07E5_5412, + 0x1778_E716_6FCC_6DB7, + ], + [ + 0x5115_4CE9_AC88_95D9, + 0x985A_286F_301E_77C4, + 0x086E_EB65_982F_AC18, + 0x99DB_995A_1257_FB3F, + 0x6642_B4B3_E411_8E54, + 0x0E99_726A_3199_F443, + ], + [ + 0xCD13_C1C6_6F65_2983, + 0xA087_0D2D_CAE7_3D19, + 0x9ED3_AB90_97E6_8F90, + 0xDB3C_B17D_D952_799B, + 0x01D1_201B_F7A7_4AB5, + 0x1630_C325_0D73_13FF, + ], + [ + 0xDDD7_F225_A139_ED84, + 0x8DA2_5128_C105_2ECA, + 0x9008_E218_F9C8_6B2A, + 0xB115_8626_4F0F_8CE1, + 0x6A37_26C3_8AE6_52BF, + 0x0D6E_D655_3FE4_4D29, + ], + [ + 0x9CCB_5618_E3F0_C88E, + 0x39B7_C8F8_C8F4_75AF, + 0xA682_C62E_F0F2_7533, + 0x356D_E5AB_275B_4DB1, + 0xE874_3884_D111_7E53, + 0x17B8_1E77_01AB_DBE2, + ], + [ + 0x6D71_986A_8497_E317, + 0x4FA2_95F2_96B7_4E95, + 0xA2C5_96C9_28C5_D1DE, + 0xC43B_756C_E79F_5574, + 0x7B90_B335_63BE_990D, + 0x080D_3CF1_F9A7_8FC4, + ], + [ + 0x7F24_1067_BE39_0C9E, + 0xA319_0B2E_DC03_2779, + 0x6763_14BA_F4BB_1B7F, + 0xDD2E_CB80_3A0C_5C99, + 0x2E0C_3751_5D13_8F22, + 0x169B_1F8E_1BCF_A7C4, + ], + [ + 0xCA67_DF3F_1605_FB7B, + 0xF69B_771F_8C28_5DEC, + 0xD50A_F360_03B1_4866, + 0xFA7D_CCDD_E678_7F96, + 0x72D8_EC09_D256_5B0D, + 0x1032_1DA0_79CE_07E2, + ], + [ + 0xA9C8_BA2E_8BA2_D229, + 0xC24B_1B80_B64D_391F, + 0x23C0_BF1B_C24C_6B68, + 0x31D7_9D7E_22C8_37BC, + 0xBD1E_9623_81ED_EE3D, + 0x06E0_8C24_8E26_0E70, + ], +]; + +/// Isogeny map x-denominator coefficients for G1 +pub const ISO_X_DEN_G1: [[u64; 6]; 11] = [ + [ + 0x993C_F9FA_40D2_1B1C, + 0xB558_D681_BE34_3DF8, + 0x9C95_8861_7FC8_AC62, + 0x01D5_EF4B_A35B_48BA, + 0x18B2_E62F_4BD3_FA6F, + 0x08CA_8D54_8CFF_19AE, + ], + [ + 0xE5C8_276E_C82B_3BFF, + 0x13DA_A884_6CB0_26E9, + 0x0126_C258_8C48_BF57, + 0x7041_E8CA_0CF0_800C, + 0x48B4_7112_98E5_3636, + 0x1256_1A5D_EB55_9C43, + ], + [ + 0xFCC2_39BA_5CB8_3E19, + 0xD6A3_D096_7C94_FEDC, + 0xFCA6_4E00_B11A_CEAC, + 0x6F89_416F_5A71_8CD1, + 0x8137_E629_BFF2_991F, + 0x0B29_62FE_57A3_225E, + ], + [ + 0x130D_E893_8DC6_2CD8, + 0x4976_D524_3EEC_F5C4, + 0x54CC_A8AB_C28D_6FD0, + 0x5B08_243F_16B1_6551, + 0xC83A_AFEF_7C40_EB54, + 0x0342_5581_A58A_E2FE, + ], + [ + 0x539D_395B_3532_A21E, + 0x9BD2_9BA8_1F35_781D, + 0x8D6B_44E8_33B3_06DA, + 0xFFDF_C759_A120_62BB, + 0x0A6F_1D5F_43E7_A07D, + 0x13A8_E162_0229_14A8, + ], + [ + 0xC02D_F9A2_9F63_04A5, + 0x7400_D24B_C422_8F11, + 0x0A43_BCEF_24B8_982F, + 0x3957_35E9_CE9C_AD4D, + 0x5539_0F7F_0506_C6E9, + 0x0E73_55F8_E4E6_67B9, + ], + [ + 0xEC25_7449_6EE8_4A3A, + 0xEA73_B353_8F0D_E06C, + 0x4E2E_0730_62AE_DE9C, + 0x570F_5799_AF53_A189, + 0x0F3E_0C63_E059_6721, + 0x0772_CAAC_F169_3619, + ], + [ + 0x11F7_D99B_BDCC_5A5E, + 0x0FA5_B948_9D11_E2D3, + 0x1996_E1CD_F982_2C58, + 0x6E7F_63C2_1BCA_68A8, + 0x30B3_F5B0_74CF_0199, + 0x14A7_AC2A_9D64_A8B2, + ], + [ + 0x4776_EC3A_79A1_D641, + 0x0382_6692_ABBA_4370, + 0x7410_0DA6_7F39_8835, + 0xE07F_8D1D_7161_366B, + 0x5E92_0B3D_AFC7_A3CC, + 0x0A10_ECF6_ADA5_4F82, + ], + [ + 0x2D63_84D1_68EC_DD0A, + 0x9317_4E4B_4B78_6500, + 0x76DF_5339_78F3_1C15, + 0xF682_B4EE_96F7_D037, + 0x476D_6E3E_B3A5_6680, + 0x095F_C13A_B9E9_2AD4, + ], + [0x1, 0x0, 0x0, 0x0, 0x0, 0x0], +]; + +/// Isogeny map y-numerator coefficients for G1 +pub const ISO_Y_NUM_G1: [[u64; 6]; 16] = [ + [ + 0xBE98_4571_9707_BB33, + 0xCD0C_7AEE_9B3B_A3C2, + 0x2B52_AF6C_9565_43D3, + 0x11AD_138E_48A8_6952, + 0x259D_1F09_4980_DCFA, + 0x090D_97C8_1BA2_4EE0, + ], + [ + 0xE097_E75A_2E41_C696, + 0xD6C5_6711_962F_A8BF, + 0x0F90_6343_EB67_AD34, + 0x1223_E96C_254F_383D, + 0xD510_36D7_76FB_4683, + 0x1349_96A1_04EE_5811, + ], + [ + 0xB8DF_E240_C72D_E1F6, + 0xD26D_5216_28B0_0523, + 0xC344_BE4B_9140_0DA7, + 0x2552_E2D6_58A3_1CE2, + 0xF4A3_84C8_6A3B_4994, + 0x00CC_786B_AA96_6E66, + ], + [ + 0xA635_5C77_B0E5_F4CB, + 0xDE40_5ABA_9EC6_1DEC, + 0x09E4_A3EC_0325_1CF9, + 0xD42A_A7B9_0EEB_791C, + 0x7898_751A_D874_6757, + 0x01F8_6376_E898_1C21, + ], + [ + 0x41B6_DAEC_F2E8_FEDB, + 0x2EE7_F8DC_0990_40A8, + 0x7983_3FD2_2135_1ADC, + 0x1955_36FB_E3CE_50B8, + 0x5CAF_4FE2_A215_29C4, + 0x08CC_03FD_EFE0_FF13, + ], + [ + 0x99B2_3AB1_3633_A5F0, + 0x203F_6326_C95A_8072, + 0x7650_5C3D_3AD5_544E, + 0x74A7_D0D4_AFAD_B7BD, + 0x2211_E11D_B8F0_A6A0, + 0x1660_3FCA_4063_4B6A, + ], + [ + 0xC961_F885_5FE9_D6F2, + 0x47A8_7AC2_460F_415E, + 0x5231_413C_4D63_4F37, + 0xE75B_B8CA_2BE1_84CB, + 0xB2C9_77D0_2779_6B3C, + 0x04AB_0B9B_CFAC_1BBC, + ], + [ + 0xA15E_4CA3_1870_FB29, + 0x42F6_4550_FEDF_E935, + 0xFD03_8DA6_C26C_8426, + 0x170A_05BF_E3BD_D81F, + 0xDE99_26BD_2CA6_C674, + 0x0987_C8D5_333A_B86F, + ], + [ + 0x6037_0E57_7BDB_A587, + 0x69D6_5201_C786_07A3, + 0x1E8B_6E6A_1F20_CABE, + 0x8F3A_BD16_679D_C26C, + 0xE88C_9E22_1E4D_A1BB, + 0x09FC_4018_BD96_684B, + ], + [ + 0x2BAF_AAEB_CA73_1C30, + 0x9B3F_7055_DD4E_BA6F, + 0x0698_5E7E_D1E4_D43B, + 0xC42A_0CA7_915A_F6FE, + 0x223A_BDE7_ADA1_4A23, + 0x0E1B_BA7A_1186_BDB5, + ], + [ + 0xE813_711A_D011_C132, + 0x31BF_3A5C_CE3F_BAFC, + 0xD118_3E41_6389_E610, + 0xCD2F_CBCB_6CAF_493F, + 0x0DFD_0B8F_1D43_FB93, + 0x1971_3E47_937C_D1BE, + ], + [ + 0xCE07_C8A4_D007_4D8E, + 0x49D9_CDF4_1B44_D606, + 0x2E6B_FE7F_911F_6432, + 0x5235_59B8_AAF0_C246, + 0xB918_C143_FED2_EDCC, + 0x18B4_6A90_8F36_F6DE, + ], + [ + 0x0D4C_04F0_0B97_1EF8, + 0x06C8_51C1_9192_11F2, + 0xC027_10E8_07B4_633F, + 0x7AA7_B12A_3426_B08E, + 0xD155_0960_04F5_3F44, + 0x0B18_2CAC_101B_9399, + ], + [ + 0x42D9_D3F5_DB98_0133, + 0xC6CF_90AD_1C23_2A64, + 0x13E6_632D_3C40_659C, + 0x757B_3B08_0D4C_1580, + 0x72FC_00AE_7BE3_15DC, + 0x0245_A394_AD1E_CA9B, + ], + [ + 0x866B_1E71_5475_224B, + 0x6BA1_049B_6579_AFB7, + 0xD9AB_0F5D_396A_7CE4, + 0x5E67_3D81_D7E8_6568, + 0x02A1_59F7_48C4_A3FC, + 0x05C1_2964_5E44_CF11, + ], + [ + 0x04B4_56BE_69C8_B604, + 0xB665_027E_FEC0_1C77, + 0x57AD_D4FA_95AF_01B2, + 0xCB18_1D8F_8496_5A39, + 0x4EA5_0B3B_42DF_2EB5, + 0x15E6_BE4E_990F_03CE, + ], +]; + +/// Isogeny map y-denominator coefficients for G1 +pub const ISO_Y_DEN_G1: [[u64; 6]; 16] = [ + [ + 0x0147_9253_B036_63C1, + 0x07F3_688E_F60C_206D, + 0xEEC3_232B_5BE7_2E7A, + 0x601A_6DE5_7898_0BE6, + 0x5218_1140_FAD0_EAE9, + 0x1611_2C4C_3A9C_98B2, + ], + [ + 0x32F6_102C_2E49_A03D, + 0x78A4_2607_6352_9E35, + 0xA4A1_0356_F453_E01F, + 0x85C8_4FF7_31C4_D59C, + 0x1A0C_BD6C_43C3_48B8, + 0x1962_D75C_2381_201E, + ], + [ + 0x1E25_38B5_3DBF_67F2, + 0xA675_7CD6_36F9_6F89, + 0x0C35_A5DD_279C_D2EC, + 0x78C4_8555_51AE_7F31, + 0x6FAA_AE7D_6E8E_B157, + 0x058D_F330_6640_DA27, + ], + [ + 0xA8D2_6D98_445F_5416, + 0x7273_64F2_C282_97AD, + 0x123D_A489_E726_AF41, + 0xD115_C5DB_DDBC_D30E, + 0xF20D_23BF_89ED_B4D1, + 0x16B7_D288_798E_5395, + ], + [ + 0xDA39_1423_11A5_001D, + 0xA20B_15DC_0FD2_EDED, + 0x542E_DA0F_C9DE_C916, + 0xC6D1_9C9F_0F69_BBB0, + 0xB00C_C912_F822_8DDC, + 0x0BE0_E079_545F_43E4, + ], + [ + 0x02C6_477F_AAF9_B7AC, + 0x49F3_8DB9_DFA9_CCE2, + 0xC5EC_D87B_6F0F_5A64, + 0xB701_52C6_5550_D881, + 0x9FB2_66EA_AC78_3182, + 0x08D9_E529_7186_DB2D, + ], + [ + 0x3D1A_1399_126A_775C, + 0xD5FA_9C01_A58B_1FB9, + 0x5DD3_65BC_400A_0051, + 0x5EEC_FDFA_8D0C_F8EF, + 0xC3BA_8734_ACE9_824B, + 0x1660_07C0_8A99_DB2F, + ], + [ + 0x60EE_415A_1581_2ED9, + 0xB920_F5B0_0801_DEE4, + 0xFEB3_4FD2_0635_7132, + 0xE5A4_375E_FA1F_4FD7, + 0x03BC_DDFA_BBA6_FF6E, + 0x16A3_EF08_BE3E_A7EA, + ], + [ + 0x6B23_3D9D_5553_5D4A, + 0x52CF_E2F7_BB92_4883, + 0xABC5_750C_4BF3_9B48, + 0xF9FB_0CE4_C6AF_5920, + 0x1A1B_E54F_D1D7_4CC4, + 0x1866_C8ED_336C_6123, + ], + [ + 0x346E_F48B_B891_3F55, + 0xC738_5EA3_D529_B35E, + 0x5308_592E_7EA7_D4FB, + 0x3216_F763_E13D_87BB, + 0xEA82_0597_D94A_8490, + 0x167A_55CD_A70A_6E1C, + ], + [ + 0x00F8_B49C_BA8F_6AA8, + 0x71A5_C29F_4F83_0604, + 0x0E59_1B36_E636_A5C8, + 0x9C6D_D039_BB61_A629, + 0x48F0_10A0_1AD2_911D, + 0x04D2_F259_EEA4_05BD, + ], + [ + 0x9684_B529_E256_1092, + 0x16F9_6898_6F7E_BBEA, + 0x8C0F_9A88_CEA7_9135, + 0x7F94_FF8A_EFCE_42D2, + 0xF585_2C1E_48C5_0C47, + 0x0ACC_BB67_481D_033F, + ], + [ + 0x1E99_B138_5733_45CC, + 0x9300_0763_E3B9_0AC1, + 0x7D5C_EEF9_A00D_9B86, + 0x5433_46D9_8ADF_0226, + 0xC361_3144_B45F_1496, + 0x0AD6_B951_4C76_7FE3, + ], + [ + 0xD1FA_DC13_26ED_06F7, + 0x4205_17BD_8714_CC80, + 0xCB74_8DF2_7942_480E, + 0xBF56_5B94_E729_27C1, + 0x628B_DD0D_53CD_76F2, + 0x0266_0400_EB2E_4F3B, + ], + [ + 0x4415_473A_1D63_4B8F, + 0x5CA2_F570_F134_9780, + 0x324E_FCD6_356C_AA20, + 0x71C4_0F65_E273_B853, + 0x6B24_255E_0D78_19C1, + 0x0E0F_A1D8_16DD_C03E, + ], + [0x1, 0x0, 0x0, 0x0, 0x0, 0x0], +]; + +// ============================================================================ +// Constants for G2 mapping (3-isogenous curve E': y² = x³ + A'x + B') +// ============================================================================ + +/// A' coefficient of the isogenous curve E' for G2 +/// A' = 0xF0 * I +pub const ISO_A_G2: [u64; 12] = + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00]; + +/// B' coefficient of the isogenous curve E' for G2 +/// B' = 0x03F4 * (1 + I) +pub const ISO_B_G2: [u64; 12] = [ + 0x03F4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x03F4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +]; + +/// Z constant for G2 SWU: Z = -(2 + I) +pub const SWU_Z_G2: [u64; 12] = + [P[0] - 2, P[1], P[2], P[3], P[4], P[5], P[0] - 1, P[1], P[2], P[3], P[4], P[5]]; + +// ============================================================================ +// G2 Isogeny Map Coefficients (3-isogeny from E' to E) +// ============================================================================ + +/// Isogeny map x-numerator coefficients for G2 +pub const ISO_X_NUM_G2: [[u64; 12]; 4] = [ + [ + 0x6238_AAAA_AAAA_97D6, + 0x5C26_38E3_43D9_C71C, + 0x88B5_8423_C50A_E15D, + 0x32C5_2D39_FD3A_042A, + 0xBB5B_7A9A_47D7_ED85, + 0x05C7_5950_7E8E_333E, + 0x6238_AAAA_AAAA_97D6, + 0x5C26_38E3_43D9_C71C, + 0x88B5_8423_C50A_E15D, + 0x32C5_2D39_FD3A_042A, + 0xBB5B_7A9A_47D7_ED85, + 0x05C7_5950_7E8E_333E, + ], + [ + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x26A9_FFFF_FFFF_C71A, + 0x1472_AAA9_CB8D_5555, + 0x9A20_8C6B_4F20_A418, + 0x984F_87AD_F7AE_0C7F, + 0x3212_6FCE_D787_C88F, + 0x1156_0BF1_7BAA_99BC, + ], + [ + 0x26A9_FFFF_FFFF_C71E, + 0x1472_AAA9_CB8D_5555, + 0x9A20_8C6B_4F20_A418, + 0x984F_87AD_F7AE_0C7F, + 0x3212_6FCE_D787_C88F, + 0x1156_0BF1_7BAA_99BC, + 0x9354_FFFF_FFFF_E38D, + 0x0A39_5554_E5C6_AAAA, + 0xCD10_4635_A790_520C, + 0xCC27_C3D6_FBD7_063F, + 0x1909_37E7_6BC3_E447, + 0x08AB_05F8_BDD5_4CDE, + ], + [ + 0x88E2_AAAA_AAAA_5ED1, + 0x7098_E38D_0F67_1C71, + 0x22D6_108F_142B_8575, + 0xCB14_B4E7_F4E8_10AA, + 0xED6D_EA69_1F5F_B614, + 0x171D_6541_FA38_CCFA, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ], +]; + +/// Isogeny map x-denominator coefficients for G2 +pub const ISO_X_DEN_G2: [[u64; 12]; 3] = [ + [ + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0xB9FE_FFFF_FFFF_AA63, + 0x1EAB_FFFE_B153_FFFF, + 0x6730_D2A0_F6B0_F624, + 0x6477_4B84_F385_12BF, + 0x4B1B_A7B6_434B_ACD7, + 0x1A01_11EA_397F_E69A, + ], + [ + 0x0000_0000_0000_000C, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0xB9FE_FFFF_FFFF_AA9F, + 0x1EAB_FFFE_B153_FFFF, + 0x6730_D2A0_F6B0_F624, + 0x6477_4B84_F385_12BF, + 0x4B1B_A7B6_434B_ACD7, + 0x1A01_11EA_397F_E69A, + ], + [0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], +]; + +/// Isogeny map y-numerator coefficients for G2 +pub const ISO_Y_NUM_G2: [[u64; 12]; 4] = [ + [ + 0x12CF_C71C_71C6_D706, + 0xFC8C_25EB_F8C9_2F68, + 0xF544_39D8_7D27_E500, + 0x0F7D_A5D4_A07F_649B, + 0x59A4_C18B_076D_1193, + 0x1530_477C_7AB4_113B, + 0x12CF_C71C_71C6_D706, + 0xFC8C_25EB_F8C9_2F68, + 0xF544_39D8_7D27_E500, + 0x0F7D_A5D4_A07F_649B, + 0x59A4_C18B_076D_1193, + 0x1530_477C_7AB4_113B, + ], + [ + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x6238_AAAA_AAAA_97BE, + 0x5C26_38E3_43D9_C71C, + 0x88B5_8423_C50A_E15D, + 0x32C5_2D39_FD3A_042A, + 0xBB5B_7A9A_47D7_ED85, + 0x05C7_5950_7E8E_333E, + ], + [ + 0x26A9_FFFF_FFFF_C71C, + 0x1472_AAA9_CB8D_5555, + 0x9A20_8C6B_4F20_A418, + 0x984F_87AD_F7AE_0C7F, + 0x3212_6FCE_D787_C88F, + 0x1156_0BF1_7BAA_99BC, + 0x9354_FFFF_FFFF_E38F, + 0x0A39_5554_E5C6_AAAA, + 0xCD10_4635_A790_520C, + 0xCC27_C3D6_FBD7_063F, + 0x1909_37E7_6BC3_E447, + 0x08AB_05F8_BDD5_4CDE, + ], + [ + 0xE1B3_71C7_1C71_8B10, + 0x4E79_097A_56DC_4BD9, + 0xB0E9_77C6_9AA2_7452, + 0x761B_0F37_A1E2_6286, + 0xFBF7_043D_E381_1AD0, + 0x124C_9AD4_3B6C_F79B, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ], +]; + +/// Isogeny map y-denominator coefficients for G2 +pub const ISO_Y_DEN_G2: [[u64; 12]; 4] = [ + [ + 0xB9FE_FFFF_FFFF_A8FB, + 0x1EAB_FFFE_B153_FFFF, + 0x6730_D2A0_F6B0_F624, + 0x6477_4B84_F385_12BF, + 0x4B1B_A7B6_434B_ACD7, + 0x1A01_11EA_397F_E69A, + 0xB9FE_FFFF_FFFF_A8FB, + 0x1EAB_FFFE_B153_FFFF, + 0x6730_D2A0_F6B0_F624, + 0x6477_4B84_F385_12BF, + 0x4B1B_A7B6_434B_ACD7, + 0x1A01_11EA_397F_E69A, + ], + [ + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0xB9FE_FFFF_FFFF_A9D3, + 0x1EAB_FFFE_B153_FFFF, + 0x6730_D2A0_F6B0_F624, + 0x6477_4B84_F385_12BF, + 0x4B1B_A7B6_434B_ACD7, + 0x1A01_11EA_397F_E69A, + ], + [ + 0x0000_0000_0000_0012, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0xB9FE_FFFF_FFFF_AA99, + 0x1EAB_FFFE_B153_FFFF, + 0x6730_D2A0_F6B0_F624, + 0x6477_4B84_F385_12BF, + 0x4B1B_A7B6_434B_ACD7, + 0x1A01_11EA_397F_E69A, + ], + [0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], +]; + +pub const PSI_C1: [u64; 12] = [ + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x8BFD_0000_0000_AAAD, + 0x4094_27EB_4F49_FFFD, + 0x897D_2965_0FB8_5F9B, + 0xAA0D_857D_8975_9AD4, + 0xEC02_4086_63D4_DE85, + 0x1A01_11EA_397F_E699, +]; + +pub const PSI_C2: [u64; 12] = [ + 0xF1EE_7B04_121B_DEA2, + 0x3044_66CF_3E67_FA0A, + 0xEF39_6489_F61E_B45E, + 0x1C3D_EDD9_30B1_CF60, + 0xE2E9_C448_D77A_2CD9, + 0x1352_03E6_0180_A68E, + 0xC810_84FB_EDE3_CC09, + 0xEE67_992F_72EC_05F4, + 0x77F7_6E17_0092_41C5, + 0x4839_5DAB_C2D3_435E, + 0x6831_E36D_6BD1_7FFE, + 0x06AF_0E04_37FF_400B, +]; + +pub const PSI2_C1: [u64; 12] = [ + 0x8BFD_0000_0000_AAAC, + 0x4094_27EB_4F49_FFFD, + 0x897D_2965_0FB8_5F9B, + 0xAA0D_857D_8975_9AD4, + 0xEC02_4086_63D4_DE85, + 0x1A01_11EA_397F_E699, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, +]; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs index 153c086be..99af22090 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs @@ -791,7 +791,7 @@ pub fn g1_bytes_be_to_u64_le_bls12_381(bytes: &[u8; 96]) -> [u64; 12] { } /// Convert [u64; 12] little-endian G1 point to 96-byte big-endian -fn g1_u64_le_to_bytes_be_bls12_381(limbs: &[u64; 12], bytes: &mut [u8; 96]) { +pub fn g1_u64_le_to_bytes_be_bls12_381(limbs: &[u64; 12], bytes: &mut [u8; 96]) { // x-coordinate (first 48 bytes) for i in 0..6 { let limb = limbs[5 - i]; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs index f722e4fe5..9654dcc0e 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp.rs @@ -7,6 +7,12 @@ use crate::{ use super::constants::{NQR_FP, P, P_MINUS_ONE}; +/// Sign function in Fp +#[inline] +pub fn sgn0_fp_bls12_381(x: &[u64; 6]) -> u64 { + x[0] & 1 +} + /// Addition in Fp #[inline] pub fn add_fp_bls12_381( @@ -217,3 +223,14 @@ pub fn inv_fp_bls12_381(x: &[u64; 6], #[cfg(feature = "hints")] hints: &mut Vec< inv } + +/// Convert 48-byte big-endian field element to [u64; 6] little-endian +pub fn bytes_be_to_u64_le_fp_bls12_381(bytes: &[u8; 48]) -> [u64; 6] { + let mut result = [0u64; 6]; + for i in 0..6 { + for j in 0..8 { + result[5 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + result +} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs index c30f61e27..93fc3a25e 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fp2.rs @@ -6,7 +6,7 @@ use crate::{ syscall_bls12_381_complex_sub, SyscallBls12_381ComplexAddParams, SyscallBls12_381ComplexMulParams, SyscallBls12_381ComplexSubParams, SyscallComplex384, }, - zisklib::{eq, fcall_bls12_381_fp2_inv, fcall_bls12_381_fp2_sqrt}, + zisklib::{eq, fcall_bls12_381_fp2_inv, fcall_bls12_381_fp2_sqrt, is_zero}, }; use super::constants::{NQR_FP2, P_MINUS_ONE}; @@ -31,6 +31,14 @@ fn from_syscall_complex(complex: &SyscallComplex384) -> [u64; 12] { result } +/// Sign function in Fp2 +pub fn sgn0_fp2_bls12_381(x: &[u64; 12]) -> u64 { + let sign_0 = x[0] & 1; + let zero_0 = is_zero(&x[0..6]) as u64; + let sign_1 = x[6] & 1; + sign_0 | (zero_0 & sign_1) +} + /// Addition in Fp2 #[inline] pub fn add_fp2_bls12_381( @@ -246,3 +254,26 @@ pub fn conjugate_fp2_bls12_381( ); from_syscall_complex(&f1) } + +/// Convert 96-byte big-endian Fp2 element to [u64; 12] little-endian +/// Format: fp2 = (c0, c1) where c0 is real, c1 is imaginary +/// Bytes: c0 (48 bytes) || c1 (48 bytes) +pub fn bytes_be_to_u64_le_fp2_bls12_381(bytes: &[u8; 96]) -> [u64; 12] { + let mut result = [0u64; 12]; + + // c0 (real part, bytes 0-47) -> result[0..6] + for i in 0..6 { + for j in 0..8 { + result[5 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + // c1 (imaginary part, bytes 48-95) -> result[6..12] + for i in 0..6 { + for j in 0..8 { + result[11 - i] |= (bytes[48 + i * 8 + j] as u64) << (8 * (7 - j)); + } + } + + result +} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/map_to_curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/map_to_curve.rs new file mode 100644 index 000000000..ee3c8d7f5 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/map_to_curve.rs @@ -0,0 +1,711 @@ +use crate::zisklib::{eq, is_zero, lt}; + +use super::{ + constants::{ + COFACTOR_G1, ISO_A_G1, ISO_A_G2, ISO_B_G1, ISO_B_G2, ISO_X_DEN_G1, ISO_X_DEN_G2, + ISO_X_NUM_G1, ISO_X_NUM_G2, ISO_Y_DEN_G1, ISO_Y_DEN_G2, ISO_Y_NUM_G1, ISO_Y_NUM_G2, + SWU_Z2_G1, SWU_Z_G1, SWU_Z_G2, + }, + curve::{g1_u64_le_to_bytes_be_bls12_381, scalar_mul_bls12_381}, + fp::{ + add_fp_bls12_381, bytes_be_to_u64_le_fp_bls12_381, inv_fp_bls12_381, mul_fp_bls12_381, + neg_fp_bls12_381, sgn0_fp_bls12_381, sqrt_fp_bls12_381, square_fp_bls12_381, + }, + fp2::{ + add_fp2_bls12_381, bytes_be_to_u64_le_fp2_bls12_381, inv_fp2_bls12_381, mul_fp2_bls12_381, + neg_fp2_bls12_381, sgn0_fp2_bls12_381, sqrt_fp2_bls12_381, square_fp2_bls12_381, + }, + twist::{ + clear_cofactor_twist_bls12_381, g2_u64_le_to_bytes_be_bls12_381, scalar_mul_twist_bls12_381, + }, +}; + +/// Maps a field element to a point on the BLS12-381 G1 curve +pub fn map_to_curve_g1_bls12_381( + u: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { + // Step 1: Map to isogenous curve E' using simplified SWU + let p_prime = map_to_curve_simple_swu_g1_bls12_381( + u, + #[cfg(feature = "hints")] + hints, + ); + + // Step 2: Apply isogeny map from E' to E + let p = isogeny_map_g1_bls12_381( + &p_prime, + #[cfg(feature = "hints")] + hints, + ); + + // Step 3: Clear cofactor + scalar_mul_bls12_381( + &p, + &COFACTOR_G1, + #[cfg(feature = "hints")] + hints, + ) +} + +/// Maps a field element in Fp2 to a point on the BLS12-381 G2 curve +pub fn map_to_curve_g2_bls12_381( + u: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { + // Step 1: Map to isogenous curve E' using simplified SWU + let p_prime = map_to_curve_simple_swu_g2_bls12_381( + u, + #[cfg(feature = "hints")] + hints, + ); + + // Step 2: Apply isogeny map from E' to E + let p = isogeny_map_g2_bls12_381( + &p_prime, + #[cfg(feature = "hints")] + hints, + ); + + // Step 3: Clear cofactor + clear_cofactor_twist_bls12_381( + &p, + #[cfg(feature = "hints")] + hints, + ) +} + +/// Maps a field element u ∈ Fp to a point on the isogenous curve E' +/// using the simplified Shallue-van de Woestijne-Ulas (SWU) method for AB != 0 +fn map_to_curve_simple_swu_g1_bls12_381( + u: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { + // 1. tv1 = inv0(Z^2 * u^4 + Z * u^2) + let u2 = square_fp_bls12_381( + u, + #[cfg(feature = "hints")] + hints, + ); + let u4 = square_fp_bls12_381( + &u2, + #[cfg(feature = "hints")] + hints, + ); + let z_u2 = mul_fp_bls12_381( + &SWU_Z_G1, + &u2, + #[cfg(feature = "hints")] + hints, + ); + let z2_u4 = mul_fp_bls12_381( + &SWU_Z2_G1, + &u4, + #[cfg(feature = "hints")] + hints, + ); + let tv1_denom = add_fp_bls12_381( + &z2_u4, + &z_u2, + #[cfg(feature = "hints")] + hints, + ); + let tv1 = inv_fp_bls12_381( + &tv1_denom, + #[cfg(feature = "hints")] + hints, + ); + + // 2. x1 = (-B / A) * (1 + tv1) + let neg_b = neg_fp_bls12_381( + &ISO_B_G1, + #[cfg(feature = "hints")] + hints, + ); + let a_inv = inv_fp_bls12_381( + &ISO_A_G1, + #[cfg(feature = "hints")] + hints, + ); + let neg_b_over_a = mul_fp_bls12_381( + &neg_b, + &a_inv, + #[cfg(feature = "hints")] + hints, + ); + let one = [1u64, 0, 0, 0, 0, 0]; + let one_plus_tv1 = add_fp_bls12_381( + &one, + &tv1, + #[cfg(feature = "hints")] + hints, + ); + let mut x1 = mul_fp_bls12_381( + &neg_b_over_a, + &one_plus_tv1, + #[cfg(feature = "hints")] + hints, + ); + + // 3. If tv1 == 0, set x1 = B / (Z * A) + if is_zero(&tv1) { + let z_a = mul_fp_bls12_381( + &SWU_Z_G1, + &ISO_A_G1, + #[cfg(feature = "hints")] + hints, + ); + let z_a_inv = inv_fp_bls12_381( + &z_a, + #[cfg(feature = "hints")] + hints, + ); + x1 = mul_fp_bls12_381( + &ISO_B_G1, + &z_a_inv, + #[cfg(feature = "hints")] + hints, + ); + } + + // 4. gx1 = x1^3 + A * x1 + B + let gx1 = compute_y2_iso_g1_bls12_381( + &x1, + #[cfg(feature = "hints")] + hints, + ); + + // 5. x2 = Z * u^2 * x1 (computed lazily below if needed) + + // 6. gx2 = x2^3 + A * x2 + B (computed lazily below if needed) + + // 7-8. Select x and y based on whether gx1 is square + let (y1, gx1_is_qr) = sqrt_fp_bls12_381( + &gx1, + #[cfg(feature = "hints")] + hints, + ); + let (x, mut y) = if gx1_is_qr { + (x1, y1) + } else { + let x2 = mul_fp_bls12_381( + &z_u2, + &x1, + #[cfg(feature = "hints")] + hints, + ); + let gx2 = compute_y2_iso_g1_bls12_381( + &x2, + #[cfg(feature = "hints")] + hints, + ); + let (y2, _) = sqrt_fp_bls12_381( + &gx2, + #[cfg(feature = "hints")] + hints, + ); + (x2, y2) + }; + + // 9. If sgn0(u) != sgn0(y), set y = -y + if sgn0_fp_bls12_381(u) != sgn0_fp_bls12_381(&y) { + y = neg_fp_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); + } + + // Return point (x, y) on E' + let mut point = [0u64; 12]; + point[0..6].copy_from_slice(&x); + point[6..12].copy_from_slice(&y); + point +} + +/// Maps a field element u ∈ Fp2 to a point on the isogenous curve E' +/// using the simplified Shallue-van de Woestijne-Ulas (SWU) method for AB != 0 +fn map_to_curve_simple_swu_g2_bls12_381( + u: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { + // 1. tv1 = inv0(Z^2 * u^4 + Z * u^2) + let u2 = square_fp2_bls12_381( + u, + #[cfg(feature = "hints")] + hints, + ); + let u4 = square_fp2_bls12_381( + &u2, + #[cfg(feature = "hints")] + hints, + ); + let z_u2 = mul_fp2_bls12_381( + &SWU_Z_G2, + &u2, + #[cfg(feature = "hints")] + hints, + ); + let z2 = square_fp2_bls12_381( + &SWU_Z_G2, + #[cfg(feature = "hints")] + hints, + ); + let z2_u4 = mul_fp2_bls12_381( + &z2, + &u4, + #[cfg(feature = "hints")] + hints, + ); + let tv1_denom = add_fp2_bls12_381( + &z2_u4, + &z_u2, + #[cfg(feature = "hints")] + hints, + ); + let tv1 = inv_fp2_bls12_381( + &tv1_denom, + #[cfg(feature = "hints")] + hints, + ); + + // 2. x1 = (-B / A) * (1 + tv1) + let neg_b = neg_fp2_bls12_381( + &ISO_B_G2, + #[cfg(feature = "hints")] + hints, + ); + let a_inv = inv_fp2_bls12_381( + &ISO_A_G2, + #[cfg(feature = "hints")] + hints, + ); + let neg_b_over_a = mul_fp2_bls12_381( + &neg_b, + &a_inv, + #[cfg(feature = "hints")] + hints, + ); + let one: [u64; 12] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let one_plus_tv1 = add_fp2_bls12_381( + &one, + &tv1, + #[cfg(feature = "hints")] + hints, + ); + let mut x1 = mul_fp2_bls12_381( + &neg_b_over_a, + &one_plus_tv1, + #[cfg(feature = "hints")] + hints, + ); + + // 3. If tv1 == 0, set x1 = B / (Z * A) + if is_zero(&tv1) { + let z_a = mul_fp2_bls12_381( + &SWU_Z_G2, + &ISO_A_G2, + #[cfg(feature = "hints")] + hints, + ); + let z_a_inv = inv_fp2_bls12_381( + &z_a, + #[cfg(feature = "hints")] + hints, + ); + x1 = mul_fp2_bls12_381( + &ISO_B_G2, + &z_a_inv, + #[cfg(feature = "hints")] + hints, + ); + } + + // 4. gx1 = x1^3 + A * x1 + B + let gx1 = compute_y2_iso_g2_bls12_381( + &x1, + #[cfg(feature = "hints")] + hints, + ); + + // 7-8. Select x and y based on whether gx1 is square + let (y1, gx1_is_qr) = sqrt_fp2_bls12_381( + &gx1, + #[cfg(feature = "hints")] + hints, + ); + let (x, mut y) = if gx1_is_qr { + (x1, y1) + } else { + // 5. x2 = Z * u^2 * x1 + let x2 = mul_fp2_bls12_381( + &z_u2, + &x1, + #[cfg(feature = "hints")] + hints, + ); + // 6. gx2 = x2^3 + A * x2 + B + let gx2 = compute_y2_iso_g2_bls12_381( + &x2, + #[cfg(feature = "hints")] + hints, + ); + let (y2, _) = sqrt_fp2_bls12_381( + &gx2, + #[cfg(feature = "hints")] + hints, + ); + (x2, y2) + }; + + // 9. If sgn0(u) != sgn0(y), set y = -y + if sgn0_fp2_bls12_381(u) != sgn0_fp2_bls12_381(&y) { + y = neg_fp2_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); + } + + // Return point (x, y) on E' + let mut point = [0u64; 24]; + point[0..12].copy_from_slice(&x); + point[12..24].copy_from_slice(&y); + point +} + +/// Compute y² = x³ + A'x + B' for the isogenous curve E' (G1) +fn compute_y2_iso_g1_bls12_381( + x: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 6] { + let x2 = square_fp_bls12_381( + x, + #[cfg(feature = "hints")] + hints, + ); + let x3 = mul_fp_bls12_381( + &x2, + x, + #[cfg(feature = "hints")] + hints, + ); + let ax = mul_fp_bls12_381( + &ISO_A_G1, + x, + #[cfg(feature = "hints")] + hints, + ); + let x3_ax = add_fp_bls12_381( + &x3, + &ax, + #[cfg(feature = "hints")] + hints, + ); + add_fp_bls12_381( + &x3_ax, + &ISO_B_G1, + #[cfg(feature = "hints")] + hints, + ) +} + +/// Compute y² = x³ + A'x + B' for the isogenous curve E' (G2) +fn compute_y2_iso_g2_bls12_381( + x: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { + let x2 = square_fp2_bls12_381( + x, + #[cfg(feature = "hints")] + hints, + ); + let x3 = mul_fp2_bls12_381( + &x2, + x, + #[cfg(feature = "hints")] + hints, + ); + let ax = mul_fp2_bls12_381( + &ISO_A_G2, + x, + #[cfg(feature = "hints")] + hints, + ); + let x3_ax = add_fp2_bls12_381( + &x3, + &ax, + #[cfg(feature = "hints")] + hints, + ); + add_fp2_bls12_381( + &x3_ax, + &ISO_B_G2, + #[cfg(feature = "hints")] + hints, + ) +} + +/// Apply the 11-isogeny map from E' to E for G1 +fn isogeny_map_g1_bls12_381( + p: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { + let x: [u64; 6] = p[0..6].try_into().unwrap(); + let y: [u64; 6] = p[6..12].try_into().unwrap(); + + // Compute x-coordinate: x_num / x_den + let x_num = eval_poly_fp( + &ISO_X_NUM_G1, + &x, + #[cfg(feature = "hints")] + hints, + ); + let x_den = eval_poly_fp( + &ISO_X_DEN_G1, + &x, + #[cfg(feature = "hints")] + hints, + ); + let x_den_inv = inv_fp_bls12_381( + &x_den, + #[cfg(feature = "hints")] + hints, + ); + let x_out = mul_fp_bls12_381( + &x_num, + &x_den_inv, + #[cfg(feature = "hints")] + hints, + ); + + // Compute y-coordinate: y' * y_num / y_den + let y_num = eval_poly_fp( + &ISO_Y_NUM_G1, + &x, + #[cfg(feature = "hints")] + hints, + ); + let y_den = eval_poly_fp( + &ISO_Y_DEN_G1, + &x, + #[cfg(feature = "hints")] + hints, + ); + let y_den_inv = inv_fp_bls12_381( + &y_den, + #[cfg(feature = "hints")] + hints, + ); + let y_frac = mul_fp_bls12_381( + &y_num, + &y_den_inv, + #[cfg(feature = "hints")] + hints, + ); + let y_out = mul_fp_bls12_381( + &y, + &y_frac, + #[cfg(feature = "hints")] + hints, + ); + + let mut result = [0u64; 12]; + result[0..6].copy_from_slice(&x_out); + result[6..12].copy_from_slice(&y_out); + result +} + +/// Apply the 3-isogeny map from E' to E for G2 +fn isogeny_map_g2_bls12_381( + p: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { + let x: [u64; 12] = p[0..12].try_into().unwrap(); + let y: [u64; 12] = p[12..24].try_into().unwrap(); + + // Compute x-coordinate: x_num / x_den + let x_num = eval_poly_fp2( + &ISO_X_NUM_G2, + &x, + #[cfg(feature = "hints")] + hints, + ); + let x_den = eval_poly_fp2( + &ISO_X_DEN_G2, + &x, + #[cfg(feature = "hints")] + hints, + ); + let x_den_inv = inv_fp2_bls12_381( + &x_den, + #[cfg(feature = "hints")] + hints, + ); + let x_out = mul_fp2_bls12_381( + &x_num, + &x_den_inv, + #[cfg(feature = "hints")] + hints, + ); + + // Compute y-coordinate: y' * y_num / y_den + let y_num = eval_poly_fp2( + &ISO_Y_NUM_G2, + &x, + #[cfg(feature = "hints")] + hints, + ); + let y_den = eval_poly_fp2( + &ISO_Y_DEN_G2, + &x, + #[cfg(feature = "hints")] + hints, + ); + let y_den_inv = inv_fp2_bls12_381( + &y_den, + #[cfg(feature = "hints")] + hints, + ); + let y_frac = mul_fp2_bls12_381( + &y_num, + &y_den_inv, + #[cfg(feature = "hints")] + hints, + ); + let y_out = mul_fp2_bls12_381( + &y, + &y_frac, + #[cfg(feature = "hints")] + hints, + ); + + let mut result = [0u64; 24]; + result[0..12].copy_from_slice(&x_out); + result[12..24].copy_from_slice(&y_out); + result +} + +/// Evaluate a polynomial at x +fn eval_poly_fp( + coeffs: &[[u64; 6]; N], + x: &[u64; 6], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 6] { + // Use Horner's method + let mut result = coeffs[N - 1]; + for i in (0..N - 1).rev() { + result = mul_fp_bls12_381( + &result, + x, + #[cfg(feature = "hints")] + hints, + ); + result = add_fp_bls12_381( + &result, + &coeffs[i], + #[cfg(feature = "hints")] + hints, + ); + } + result +} + +/// Evaluate a polynomial at x over Fp2 +fn eval_poly_fp2( + coeffs: &[[u64; 12]; N], + x: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 12] { + // Use Horner's method + let mut result = coeffs[N - 1]; + for i in (0..N - 1).rev() { + result = mul_fp2_bls12_381( + &result, + x, + #[cfg(feature = "hints")] + hints, + ); + result = add_fp2_bls12_381( + &result, + &coeffs[i], + #[cfg(feature = "hints")] + hints, + ); + } + result +} + +/// BLS12-381 map Fp field element to G1 point +/// +/// Input format: 48 bytes field element (big-endian) +/// Output format: 96 bytes G1 point (x || y big-endian) +/// +/// ### Safety +/// - `fp` must point to a valid `[u8; 48]` +/// - `ret` must point to a valid `[u8; 96]` for the output +/// +/// Returns: +/// - 0 = success +/// - 1 = error (input not in field) +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_bls12_381_fp_to_g1_c")] +pub unsafe extern "C" fn bls12_381_fp_to_g1_c( + ret: *mut u8, + fp: *const u8, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> u8 { + let fp_bytes: &[u8; 48] = &*(fp as *const [u8; 48]); + let ret_bytes: &mut [u8; 96] = &mut *(ret as *mut [u8; 96]); + + // Parse field element + let u = bytes_be_to_u64_le_fp_bls12_381(fp_bytes); + + // Map to curve + let result = map_to_curve_g1_bls12_381( + &u, + #[cfg(feature = "hints")] + hints, + ); + + // Encode result + g1_u64_le_to_bytes_be_bls12_381(&result, ret_bytes); + 0 +} + +/// BLS12-381 map Fp2 field element to G2 point +/// +/// Input format: 96 bytes Fp2 element (c0 || c1, each 48 bytes big-endian) +/// Output format: 192 bytes G2 point (x_r || x_i || y_r || y_i, each 48 bytes big-endian) +/// +/// ### Safety +/// - `fp2` must point to a valid `[u8; 96]` +/// - `ret` must point to a valid `[u8; 192]` for the output +/// +/// Returns: +/// - 0 = success +/// - 1 = error (input not in field) +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_bls12_381_fp2_to_g2_c")] +pub unsafe extern "C" fn bls12_381_fp2_to_g2_c( + ret: *mut u8, + fp2: *const u8, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> u8 { + let fp2_bytes: &[u8; 96] = &*(fp2 as *const [u8; 96]); + let ret_bytes: &mut [u8; 192] = &mut *(ret as *mut [u8; 192]); + + // Parse Fp2 element + let u = bytes_be_to_u64_le_fp2_bls12_381(fp2_bytes); + + // Map to curve + let result = map_to_curve_g2_bls12_381( + &u, + #[cfg(feature = "hints")] + hints, + ); + + // Encode result + g2_u64_le_to_bytes_be_bls12_381(&result, ret_bytes); + 0 +} diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs index 852eed18b..42a80c231 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/mod.rs @@ -8,6 +8,7 @@ mod fp2; mod fp6; mod fr; mod kzg; +mod map_to_curve; mod miller_loop; mod pairing; mod twist; @@ -21,5 +22,6 @@ pub use fp2::*; pub use fp6::*; pub use fr::*; pub use kzg::*; +pub use map_to_curve::*; pub use pairing::*; pub use twist::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs index f23a4017a..713087ad7 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs @@ -5,7 +5,7 @@ use crate::zisklib::{eq, fcall_msb_pos_256, lt}; use super::{ constants::{ ETWISTED_B, EXT_U, EXT_U_INV, FROBENIUS_GAMMA13, FROBENIUS_GAMMA14, G2_IDENTITY, P, - X_ABS_BIN_BE, + PSI2_C1, PSI_C1, PSI_C2, X_ABS_BIN_BE, }, fp2::{ add_fp2_bls12_381, conjugate_fp2_bls12_381, dbl_fp2_bls12_381, inv_fp2_bls12_381, @@ -15,8 +15,6 @@ use super::{ fr::scalar_bytes_be_to_u64_le_bls12_381, }; -// TODO: Check what happens if scalar or ecc coordinates are bigger than the field size - /// G2 add result codes pub const G2_ADD_SUCCESS: u8 = 0; pub const G2_ADD_SUCCESS_INFINITY: u8 = 1; @@ -239,6 +237,138 @@ pub fn is_on_subgroup_twist_bls12_381( eq(&lhs, &rhs) } +fn psi_twist_bls12_381(p: &[u64; 24], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 24] { + let x: [u64; 12] = p[0..12].try_into().unwrap(); + let y: [u64; 12] = p[12..24].try_into().unwrap(); + + let mut frobx = conjugate_fp2_bls12_381( + &x, + #[cfg(feature = "hints")] + hints, + ); + frobx = mul_fp2_bls12_381( + &frobx, + &PSI_C1, + #[cfg(feature = "hints")] + hints, + ); + + let mut froby = conjugate_fp2_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); + froby = mul_fp2_bls12_381( + &froby, + &PSI_C2, + #[cfg(feature = "hints")] + hints, + ); + + let mut result = [0u64; 24]; + result[0..12].copy_from_slice(&frobx); + result[12..24].copy_from_slice(&froby); + result +} + +fn psi2_twist_bls12_381( + p: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { + let x: [u64; 12] = p[0..12].try_into().unwrap(); + let y: [u64; 12] = p[12..24].try_into().unwrap(); + + let xa = mul_fp2_bls12_381( + &x, + &PSI2_C1, + #[cfg(feature = "hints")] + hints, + ); + let ya = neg_fp2_bls12_381( + &y, + #[cfg(feature = "hints")] + hints, + ); + + let mut result = [0u64; 24]; + result[0..12].copy_from_slice(&xa); + result[12..24].copy_from_slice(&ya); + result +} + +/// Efficient cofactor clearing for G2 using endomorphisms +/// Implements: h_eff * P where h_eff is the effective cofactor +pub fn clear_cofactor_twist_bls12_381( + p: &[u64; 24], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 24] { + let mut t1 = scalar_mul_by_abs_x_twist_bls12_381( + p, + #[cfg(feature = "hints")] + hints, + ); + t1 = neg_twist_bls12_381( + &t1, + #[cfg(feature = "hints")] + hints, + ); + let mut t2 = psi_twist_bls12_381( + p, + #[cfg(feature = "hints")] + hints, + ); + let mut t3 = dbl_twist_bls12_381( + p, + #[cfg(feature = "hints")] + hints, + ); + t3 = psi2_twist_bls12_381( + &t3, + #[cfg(feature = "hints")] + hints, + ); + t3 = sub_twist_bls12_381( + &t3, + &t2, + #[cfg(feature = "hints")] + hints, + ); + t2 = add_twist_bls12_381( + &t1, + &t2, + #[cfg(feature = "hints")] + hints, + ); + t2 = scalar_mul_by_abs_x_twist_bls12_381( + &t2, + #[cfg(feature = "hints")] + hints, + ); + t2 = neg_twist_bls12_381( + &t2, + #[cfg(feature = "hints")] + hints, + ); + t3 = add_twist_bls12_381( + &t3, + &t2, + #[cfg(feature = "hints")] + hints, + ); + t3 = sub_twist_bls12_381( + &t3, + &t1, + #[cfg(feature = "hints")] + hints, + ); + sub_twist_bls12_381( + &t3, + p, + #[cfg(feature = "hints")] + hints, + ) +} + /// Addition of two non-zero points pub fn add_twist_bls12_381( p1: &[u64; 24], @@ -988,7 +1118,7 @@ pub fn g2_bytes_be_to_u64_le_bls12_381(bytes: &[u8; 192]) -> [u64; 24] { } /// Convert [u64; 24] little-endian G2 point to 192-byte big-endian -fn g2_u64_le_to_bytes_be_bls12_381(limbs: &[u64; 24], bytes: &mut [u8; 192]) { +pub fn g2_u64_le_to_bytes_be_bls12_381(limbs: &[u64; 24], bytes: &mut [u8; 192]) { // x_r (limbs[0..6]) -> bytes 0-47 for i in 0..6 { let limb = limbs[5 - i]; From facd93de08dfa2f641bb18746c230a5d97bcf342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Tue, 27 Jan 2026 14:57:27 +0100 Subject: [PATCH 349/782] Fix mem inputs asm (#727) * Fix mem inputs asm * Setting proper timer * Adding minimal memory to verify-constraints * Minimal memory verify constraints fix --- Cargo.lock | 22 +++++------ cli/src/commands/verify_constraints.rs | 5 +++ common/src/bus/bus_device_metrics.rs | 1 + emulator-asm/asm-runner/src/asm_mt_runner.rs | 8 ++-- executor/src/sm_static_bundle.rs | 37 ++++++++++++++----- .../arith_eq/src/arith_eq_bus_device.rs | 15 ++++++-- precompiles/arith_eq/src/arith_eq_manager.rs | 7 +++- .../src/arith_eq_384_bus_device.rs | 15 ++++++-- .../arith_eq_384/src/arith_eq_384_manager.rs | 7 +++- precompiles/big_int/src/add256_bus_device.rs | 16 +++++--- precompiles/big_int/src/add256_manager.rs | 7 +++- precompiles/dma/src/dma_bus_device.rs | 15 ++++++-- precompiles/dma/src/dma_manager.rs | 7 +++- precompiles/keccakf/src/keccakf_bus_device.rs | 16 +++++--- precompiles/keccakf/src/keccakf_manager.rs | 7 +++- .../poseidon2/src/poseidon2_bus_device.rs | 16 +++++--- .../poseidon2/src/poseidon2_manager.rs | 7 +++- precompiles/sha256f/src/sha256f_bus_device.rs | 16 +++++--- precompiles/sha256f/src/sha256f_manager.rs | 7 +++- sdk/src/builder.rs | 12 +++--- sdk/src/prover/backend.rs | 2 +- 21 files changed, 168 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f2a36164..97bd7d25b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1029,7 +1029,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" dependencies = [ "fields", "num-bigint", @@ -1374,7 +1374,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" dependencies = [ "cfg-if", "num-bigint", @@ -2694,7 +2694,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" dependencies = [ "colored", "fields", @@ -3090,7 +3090,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" dependencies = [ "blake3", "borsh", @@ -3125,7 +3125,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" dependencies = [ "borsh", "colored", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" dependencies = [ "fields", "itoa", @@ -3169,7 +3169,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" dependencies = [ "proc-macro2", "quote", @@ -3180,7 +3180,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" dependencies = [ "crossbeam-channel", "tracing", @@ -3189,7 +3189,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" dependencies = [ "colored", "fields", @@ -3200,7 +3200,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" dependencies = [ "bytemuck", "fields", @@ -5414,7 +5414,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" dependencies = [ "colored", "fields", diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 56a6840d5..d9841cb22 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -68,6 +68,9 @@ pub struct ZiskVerifyConstraints { #[clap(short = 'j', long, default_value_t = false)] pub shared_tables: bool, + + #[clap(short = 'm', long, default_value_t = false)] + pub minimal_memory: bool, } impl ZiskVerifyConstraints { @@ -114,6 +117,7 @@ impl ZiskVerifyConstraints { .elf_path(self.elf.clone()) .verbose(self.verbose) .shared_tables(self.shared_tables) + .minimal_memory(self.minimal_memory) .print_command_info() .build()?; @@ -131,6 +135,7 @@ impl ZiskVerifyConstraints { .asm_path_opt(self.asm.clone()) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) + .minimal_memory(self.minimal_memory) .print_command_info() .build()?; diff --git a/common/src/bus/bus_device_metrics.rs b/common/src/bus/bus_device_metrics.rs index 160f6856a..86894cb24 100644 --- a/common/src/bus/bus_device_metrics.rs +++ b/common/src/bus/bus_device_metrics.rs @@ -12,6 +12,7 @@ use crate::Metrics; #[derive(Debug, PartialEq)] pub enum BusDeviceMode { Counter, + CounterAsm, InputGenerator, } diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index d3fbcbe8b..320a3b222 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -197,16 +197,16 @@ impl AsmRunnerMT { .context("Child process returned error"); } + let total_steps = emu_traces.iter().map(|x| x.steps).sum::(); + let mhz = (total_steps as f64 / start_time.elapsed().as_secs_f64()) / 1_000_000.0; + info!("··· Assembly execution speed: {:.2} MHz", mhz); + // Collect results let mut tasks = Vec::new(); for handle in handles { tasks.push(handle.join().expect("Task panicked")); } - let total_steps = emu_traces.iter().map(|x| x.steps).sum::(); - let mhz = (total_steps as f64 / start_time.elapsed().as_secs_f64()) / 1_000_000.0; - info!("··· Assembly execution speed: {:.2} MHz", mhz); - // Wait for the assembly emulator to complete writing the trace let response = handle .join() diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index 88f76a978..8c92a5257 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -225,27 +225,46 @@ impl StaticSMBundle { arith_counter = Some((sm.type_id(), arith_sm.build_arith_counter())); } StateMachines::KeccakfManager(keccak_sm) => { - keccakf_counter = Some((sm.type_id(), keccak_sm.build_keccakf_counter())); + keccakf_counter = Some(( + sm.type_id(), + keccak_sm.build_keccakf_counter(self.process_only_operation_bus), + )); } StateMachines::Sha256fManager(sha256_sm) => { - sha256f_counter = Some((sm.type_id(), sha256_sm.build_sha256f_counter())); + sha256f_counter = Some(( + sm.type_id(), + sha256_sm.build_sha256f_counter(self.process_only_operation_bus), + )); } StateMachines::Poseidon2Manager(poseidon2_sm) => { - poseidon2_counter = - Some((sm.type_id(), poseidon2_sm.build_poseidon2_counter())); + poseidon2_counter = Some(( + sm.type_id(), + poseidon2_sm.build_poseidon2_counter(self.process_only_operation_bus), + )); } StateMachines::ArithEqManager(arith_eq_sm) => { - arith_eq_counter = Some((sm.type_id(), arith_eq_sm.build_arith_eq_counter())); + arith_eq_counter = Some(( + sm.type_id(), + arith_eq_sm.build_arith_eq_counter(self.process_only_operation_bus), + )); } StateMachines::ArithEq384Manager(arith_eq_384_sm) => { - arith_eq_384_counter = - Some((sm.type_id(), arith_eq_384_sm.build_arith_eq_384_counter())); + arith_eq_384_counter = Some(( + sm.type_id(), + arith_eq_384_sm.build_arith_eq_384_counter(self.process_only_operation_bus), + )); } StateMachines::Add256Manager(add256_sm) => { - add256_counter = Some((sm.type_id(), add256_sm.build_add256_counter())); + add256_counter = Some(( + sm.type_id(), + add256_sm.build_add256_counter(self.process_only_operation_bus), + )); } StateMachines::DmaManager(dma_sm) => { - dma_counter = Some((sm.type_id(), dma_sm.build_dma_counter())); + dma_counter = Some(( + sm.type_id(), + dma_sm.build_dma_counter(self.process_only_operation_bus), + )); } StateMachines::RomSM(_) => {} } diff --git a/precompiles/arith_eq/src/arith_eq_bus_device.rs b/precompiles/arith_eq/src/arith_eq_bus_device.rs index 6a77fde87..19dc40dd8 100644 --- a/precompiles/arith_eq/src/arith_eq_bus_device.rs +++ b/precompiles/arith_eq/src/arith_eq_bus_device.rs @@ -179,10 +179,17 @@ impl BusDevice for ArithEqCounterInputGen { let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); - } + let only_counters = match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + true + } + BusDeviceMode::CounterAsm => { + self.measure(data); + return true; + } + BusDeviceMode::InputGenerator => false, + }; match op { ARITH256_OP => { diff --git a/precompiles/arith_eq/src/arith_eq_manager.rs b/precompiles/arith_eq/src/arith_eq_manager.rs index 2d8835a68..9b3890cef 100644 --- a/precompiles/arith_eq/src/arith_eq_manager.rs +++ b/precompiles/arith_eq/src/arith_eq_manager.rs @@ -31,8 +31,11 @@ impl ArithEqManager { Arc::new(Self { arith_eq_sm }) } - pub fn build_arith_eq_counter(&self) -> ArithEqCounterInputGen { - ArithEqCounterInputGen::new(BusDeviceMode::Counter) + pub fn build_arith_eq_counter(&self, asm_execution: bool) -> ArithEqCounterInputGen { + match asm_execution { + true => ArithEqCounterInputGen::new(BusDeviceMode::CounterAsm), + false => ArithEqCounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_arith_eq_input_generator(&self) -> ArithEqCounterInputGen { diff --git a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs index 438f2d045..534a72f1b 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs @@ -167,10 +167,17 @@ impl BusDevice for ArithEq384CounterInputGen { let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); - } + let only_counters = match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + true + } + BusDeviceMode::CounterAsm => { + self.measure(data); + return true; + } + BusDeviceMode::InputGenerator => false, + }; match op { ARITH384_MOD_OP => { diff --git a/precompiles/arith_eq_384/src/arith_eq_384_manager.rs b/precompiles/arith_eq_384/src/arith_eq_384_manager.rs index 033c8d158..ad5f58c39 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_manager.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_manager.rs @@ -31,8 +31,11 @@ impl ArithEq384Manager { Arc::new(Self { arith_eq_384_sm }) } - pub fn build_arith_eq_384_counter(&self) -> ArithEq384CounterInputGen { - ArithEq384CounterInputGen::new(BusDeviceMode::Counter) + pub fn build_arith_eq_384_counter(&self, asm_execution: bool) -> ArithEq384CounterInputGen { + match asm_execution { + true => ArithEq384CounterInputGen::new(BusDeviceMode::CounterAsm), + false => ArithEq384CounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_arith_eq_384_input_generator(&self) -> ArithEq384CounterInputGen { diff --git a/precompiles/big_int/src/add256_bus_device.rs b/precompiles/big_int/src/add256_bus_device.rs index 00fee3170..364ca7b69 100644 --- a/precompiles/big_int/src/add256_bus_device.rs +++ b/precompiles/big_int/src/add256_bus_device.rs @@ -124,13 +124,19 @@ impl BusDevice for Add256CounterInputGen { let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_add256_mem_inputs(addr_main, step_main, data, true, pending); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + generate_add256_mem_inputs(addr_main, step_main, data, false, pending); + } } - generate_add256_mem_inputs(addr_main, step_main, data, only_counters, pending); - true } diff --git a/precompiles/big_int/src/add256_manager.rs b/precompiles/big_int/src/add256_manager.rs index 99a471749..0ff2c8d86 100644 --- a/precompiles/big_int/src/add256_manager.rs +++ b/precompiles/big_int/src/add256_manager.rs @@ -38,8 +38,11 @@ impl Add256Manager { Arc::new(Self { add256_sm }) } - pub fn build_add256_counter(&self) -> Add256CounterInputGen { - Add256CounterInputGen::new(BusDeviceMode::Counter) + pub fn build_add256_counter(&self, asm_execution: bool) -> Add256CounterInputGen { + match asm_execution { + true => Add256CounterInputGen::new(BusDeviceMode::CounterAsm), + false => Add256CounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_add256_input_generator(&self) -> Add256CounterInputGen { diff --git a/precompiles/dma/src/dma_bus_device.rs b/precompiles/dma/src/dma_bus_device.rs index c8199fc0e..a96cf20d8 100644 --- a/precompiles/dma/src/dma_bus_device.rs +++ b/precompiles/dma/src/dma_bus_device.rs @@ -176,12 +176,19 @@ impl BusDevice for DmaCounterInputGen { } } - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_dma_mem_inputs(data, data_ext, true, pending); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + generate_dma_mem_inputs(data, data_ext, false, pending); + } } - generate_dma_mem_inputs(data, data_ext, only_counters, pending); true } diff --git a/precompiles/dma/src/dma_manager.rs b/precompiles/dma/src/dma_manager.rs index 2194b7686..15bd934d5 100644 --- a/precompiles/dma/src/dma_manager.rs +++ b/precompiles/dma/src/dma_manager.rs @@ -39,8 +39,11 @@ impl DmaManager { Arc::new(Self { dma_sm, dma_pre_post_sm, dma_64_aligned_sm, dma_unaligned_sm }) } - pub fn build_dma_counter(&self) -> DmaCounterInputGen { - DmaCounterInputGen::new(BusDeviceMode::Counter) + pub fn build_dma_counter(&self, asm_execution: bool) -> DmaCounterInputGen { + match asm_execution { + true => DmaCounterInputGen::new(BusDeviceMode::CounterAsm), + false => DmaCounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_dma_input_generator(&self) -> DmaCounterInputGen { diff --git a/precompiles/keccakf/src/keccakf_bus_device.rs b/precompiles/keccakf/src/keccakf_bus_device.rs index 7bae908f2..7d190a2db 100644 --- a/precompiles/keccakf/src/keccakf_bus_device.rs +++ b/precompiles/keccakf/src/keccakf_bus_device.rs @@ -124,13 +124,19 @@ impl BusDevice for KeccakfCounterInputGen { let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_keccakf_mem_inputs(addr_main, step_main, data, true, pending); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + generate_keccakf_mem_inputs(addr_main, step_main, data, false, pending); + } } - generate_keccakf_mem_inputs(addr_main, step_main, data, only_counters, pending); - true } diff --git a/precompiles/keccakf/src/keccakf_manager.rs b/precompiles/keccakf/src/keccakf_manager.rs index e3a206cdf..fab4a338a 100644 --- a/precompiles/keccakf/src/keccakf_manager.rs +++ b/precompiles/keccakf/src/keccakf_manager.rs @@ -31,8 +31,11 @@ impl KeccakfManager { Arc::new(Self { keccakf_sm }) } - pub fn build_keccakf_counter(&self) -> KeccakfCounterInputGen { - KeccakfCounterInputGen::new(BusDeviceMode::Counter) + pub fn build_keccakf_counter(&self, asm_execution: bool) -> KeccakfCounterInputGen { + match asm_execution { + true => KeccakfCounterInputGen::new(BusDeviceMode::CounterAsm), + false => KeccakfCounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_keccakf_input_generator(&self) -> KeccakfCounterInputGen { diff --git a/precompiles/poseidon2/src/poseidon2_bus_device.rs b/precompiles/poseidon2/src/poseidon2_bus_device.rs index 38e38a89c..df35893e1 100644 --- a/precompiles/poseidon2/src/poseidon2_bus_device.rs +++ b/precompiles/poseidon2/src/poseidon2_bus_device.rs @@ -124,13 +124,19 @@ impl BusDevice for Poseidon2CounterInputGen { let step_main = data[A]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_poseidon2_mem_inputs(addr_main, step_main, data, true, pending); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + generate_poseidon2_mem_inputs(addr_main, step_main, data, false, pending); + } } - generate_poseidon2_mem_inputs(addr_main, step_main, data, only_counters, pending); - true } diff --git a/precompiles/poseidon2/src/poseidon2_manager.rs b/precompiles/poseidon2/src/poseidon2_manager.rs index 77ed48897..10ac169ec 100644 --- a/precompiles/poseidon2/src/poseidon2_manager.rs +++ b/precompiles/poseidon2/src/poseidon2_manager.rs @@ -29,8 +29,11 @@ impl Poseidon2Manager { Arc::new(Self { poseidon2_sm }) } - pub fn build_poseidon2_counter(&self) -> Poseidon2CounterInputGen { - Poseidon2CounterInputGen::new(BusDeviceMode::Counter) + pub fn build_poseidon2_counter(&self, asm_execution: bool) -> Poseidon2CounterInputGen { + match asm_execution { + true => Poseidon2CounterInputGen::new(BusDeviceMode::CounterAsm), + false => Poseidon2CounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_poseidon2_input_generator(&self) -> Poseidon2CounterInputGen { diff --git a/precompiles/sha256f/src/sha256f_bus_device.rs b/precompiles/sha256f/src/sha256f_bus_device.rs index bf6b72ce9..aa9120595 100644 --- a/precompiles/sha256f/src/sha256f_bus_device.rs +++ b/precompiles/sha256f/src/sha256f_bus_device.rs @@ -124,13 +124,19 @@ impl BusDevice for Sha256fCounterInputGen { let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_sha256f_mem_inputs(addr_main, step_main, data, true, pending); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + generate_sha256f_mem_inputs(addr_main, step_main, data, false, pending); + } } - generate_sha256f_mem_inputs(addr_main, step_main, data, only_counters, pending); - true } diff --git a/precompiles/sha256f/src/sha256f_manager.rs b/precompiles/sha256f/src/sha256f_manager.rs index 658c6201f..1c55ec19a 100644 --- a/precompiles/sha256f/src/sha256f_manager.rs +++ b/precompiles/sha256f/src/sha256f_manager.rs @@ -30,8 +30,11 @@ impl Sha256fManager { Arc::new(Self { sha256f_sm }) } - pub fn build_sha256f_counter(&self) -> Sha256fCounterInputGen { - Sha256fCounterInputGen::new(BusDeviceMode::Counter) + pub fn build_sha256f_counter(&self, asm_execution: bool) -> Sha256fCounterInputGen { + match asm_execution { + true => Sha256fCounterInputGen::new(BusDeviceMode::CounterAsm), + false => Sha256fCounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_sha256f_input_generator(&self) -> Sha256fCounterInputGen { diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index b72d3feff..b1fca5573 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -202,6 +202,12 @@ impl ProverClientBuilder { self.print_command_info = true; self } + + #[must_use] + pub fn minimal_memory(mut self, minimal: bool) -> Self { + self.minimal_memory = minimal; + self + } } // ASM-specific methods @@ -257,12 +263,6 @@ impl ProverClientBuilder { self } - #[must_use] - pub fn minimal_memory(mut self, minimal: bool) -> Self { - self.minimal_memory = minimal; - self - } - #[must_use] pub fn gpu(mut self, gpu_params: ParamsGPU) -> Self { self.gpu_params = Some(gpu_params); diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index b2a490b57..4ffdd41d0 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -120,7 +120,7 @@ impl ProverBackend { self.witness_lib.set_stdin(stdin); self.proofman - .verify_proof_constraints_from_lib(&debug_info, false) + .verify_proof_constraints_from_lib(&debug_info, self.minimal_memory, false) .map_err(|e| anyhow::anyhow!("Error generating proof: {}", e))?; let elapsed = start.elapsed(); From af2fcfb74f9cd4643de2f4135043bcef30f235e6 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 27 Jan 2026 15:59:04 +0100 Subject: [PATCH 350/782] Fix some precompile results code in assembly generator, when not in precompile results mode --- core/src/zisk_rom_2_asm.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index b5b8ddde6..265f697c1 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -520,10 +520,12 @@ impl ZiskRom2Asm { ctx.mem_chunk_mask = format!("qword {}[chunk_mask]", ctx.ptr); ctx.mem_rsp = format!("qword {}[MEM_RSP]", ctx.ptr); ctx.mem_free_input = format!("qword {}[MEM_FREE_INPUT]", ctx.ptr); - ctx.mem_precompile_results_address = - format!("qword {}[MEM_PRECOMPILE_RESULTS_ADDRESS]", ctx.ptr); - ctx.mem_precompile_written_address = format!("qword {}[0x70000000]", ctx.ptr); - ctx.mem_precompile_read_address = format!("qword {}[0x70001000]", ctx.ptr); + if ctx.precompile_results() { + ctx.mem_precompile_results_address = + format!("qword {}[MEM_PRECOMPILE_RESULTS_ADDRESS]", ctx.ptr); + ctx.mem_precompile_written_address = format!("qword {}[0x70000000]", ctx.ptr); + ctx.mem_precompile_read_address = format!("qword {}[0x70001000]", ctx.ptr); + } // Preamble *code += ".intel_syntax noprefix\n"; @@ -541,7 +543,9 @@ impl ZiskRom2Asm { *code += ".comm MEM_CHUNK_START_STEP, 8, 8\n"; *code += ".comm MEM_RSP, 8, 8\n"; *code += ".comm MEM_FREE_INPUT, 8, 8\n"; - *code += ".comm MEM_PRECOMPILE_RESULTS_ADDRESS, 8, 8\n"; + if ctx.precompile_results() { + *code += ".comm MEM_PRECOMPILE_RESULTS_ADDRESS, 8, 8\n"; + } if ctx.zip() { *code += ".comm MEM_CHUNK_ID, 8, 8\n"; } @@ -614,7 +618,9 @@ impl ZiskRom2Asm { *code += ".extern print_fcall_ctx\n"; *code += ".extern print_pc\n"; *code += ".extern realloc_trace\n"; - *code += ".extern wait_for_prec_avail\n\n"; + if ctx.precompile_results() { + *code += ".extern wait_for_prec_avail\n\n"; + } if ctx.minimal_trace() || ctx.main_trace() From 8c1177623e840c35a9abcceb38475ba36edb65ec Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 27 Jan 2026 16:38:20 +0000 Subject: [PATCH 351/782] Verify constraints minimal memory only --- Cargo.lock | 22 +++++++++++----------- cli/src/commands/verify_constraints.rs | 5 ----- sdk/src/builder.rs | 12 ++++++------ sdk/src/prover/backend.rs | 2 +- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97bd7d25b..0f2a36164 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1029,7 +1029,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "fields", "num-bigint", @@ -1374,7 +1374,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "cfg-if", "num-bigint", @@ -2694,7 +2694,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "colored", "fields", @@ -3090,7 +3090,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "blake3", "borsh", @@ -3125,7 +3125,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "borsh", "colored", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "fields", "itoa", @@ -3169,7 +3169,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "proc-macro2", "quote", @@ -3180,7 +3180,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "crossbeam-channel", "tracing", @@ -3189,7 +3189,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "colored", "fields", @@ -3200,7 +3200,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "bytemuck", "fields", @@ -5414,7 +5414,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#7616c4b2df3f27a7b24139549c57eda67b1f9fe2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a01ae94f7d21736e4dd4a739f775bb5f3bae93fb" dependencies = [ "colored", "fields", diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index d9841cb22..56a6840d5 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -68,9 +68,6 @@ pub struct ZiskVerifyConstraints { #[clap(short = 'j', long, default_value_t = false)] pub shared_tables: bool, - - #[clap(short = 'm', long, default_value_t = false)] - pub minimal_memory: bool, } impl ZiskVerifyConstraints { @@ -117,7 +114,6 @@ impl ZiskVerifyConstraints { .elf_path(self.elf.clone()) .verbose(self.verbose) .shared_tables(self.shared_tables) - .minimal_memory(self.minimal_memory) .print_command_info() .build()?; @@ -135,7 +131,6 @@ impl ZiskVerifyConstraints { .asm_path_opt(self.asm.clone()) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) - .minimal_memory(self.minimal_memory) .print_command_info() .build()?; diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index b1fca5573..b72d3feff 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -202,12 +202,6 @@ impl ProverClientBuilder { self.print_command_info = true; self } - - #[must_use] - pub fn minimal_memory(mut self, minimal: bool) -> Self { - self.minimal_memory = minimal; - self - } } // ASM-specific methods @@ -263,6 +257,12 @@ impl ProverClientBuilder { self } + #[must_use] + pub fn minimal_memory(mut self, minimal: bool) -> Self { + self.minimal_memory = minimal; + self + } + #[must_use] pub fn gpu(mut self, gpu_params: ParamsGPU) -> Self { self.gpu_params = Some(gpu_params); diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 4ffdd41d0..b2a490b57 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -120,7 +120,7 @@ impl ProverBackend { self.witness_lib.set_stdin(stdin); self.proofman - .verify_proof_constraints_from_lib(&debug_info, self.minimal_memory, false) + .verify_proof_constraints_from_lib(&debug_info, false) .map_err(|e| anyhow::anyhow!("Error generating proof: {}", e))?; let elapsed = start.elapsed(); From 012940d98f9070cecd90c076403897cdb6c83684 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:24:18 +0000 Subject: [PATCH 352/782] rollback improvements in kzg and secp256k1 hint handlers --- ziskos-hints/src/handlers/kzg.rs | 13 ++++++--- ziskos-hints/src/handlers/mod.rs | 11 -------- ziskos-hints/src/handlers/secp256k1.rs | 37 +++++++++++++++----------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/ziskos-hints/src/handlers/kzg.rs b/ziskos-hints/src/handlers/kzg.rs index 158885d09..ebf7407c5 100644 --- a/ziskos-hints/src/handlers/kzg.rs +++ b/ziskos-hints/src/handlers/kzg.rs @@ -1,4 +1,4 @@ -use crate::{handlers::validate_hint_length, hint_fields, zisklib::verify_kzg_proof}; +use crate::{handlers::validate_hint_length, hint_fields, zisklib}; use anyhow::Result; @@ -19,8 +19,15 @@ pub fn verify_kzg_proof_hint(data: &[u64]) -> Result> { bytes[PROOF_OFFSET..PROOF_OFFSET + PROOF_SIZE].try_into().unwrap(); let mut hints = Vec::new(); - - verify_kzg_proof(z, y, commitment, proof, &mut hints); + unsafe { + zisklib::verify_kzg_proof_c( + z.as_ptr(), + y.as_ptr(), + commitment.as_ptr(), + proof.as_ptr(), + &mut hints, + ) + }; Ok(hints) } diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 513697b7d..c7d7304b4 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -119,14 +119,3 @@ fn validate_hint_length(data: &[T], expected_len: usize, hint_name: &str) -> } Ok(()) } - -/// Converts a big-endian u64 array to little-endian u64 array. -/// Reverses both the array order and the byte order within each u64. -#[inline] -fn u64_be_to_u64_le(input: &[u64; N]) -> [u64; N] { - let mut result = [0u64; N]; - for i in 0..N { - result[i] = input[N - 1 - i].swap_bytes(); - } - result -} diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 00cea9443..b159fb861 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -1,4 +1,3 @@ -use crate::handlers::u64_be_to_u64_le; use crate::handlers::validate_hint_length; use crate::hint_fields; use crate::zisklib; @@ -8,24 +7,32 @@ use anyhow::Result; /// Processes an `HINT_SECP256K1_ECRECOVER` hint. #[inline] pub fn secp256k1_ecrecover_hint(data: &[u64]) -> Result> { - hint_fields![R: 4, S: 4, RECID: 1, MSG: 4, LO_S: 1]; + hint_fields![SIG: 64, RECID: 8, MSG: 32, LO_S: 8]; - validate_hint_length(data, EXPECTED_LEN, "HINT_SECP256K1_ECRECOVER")?; + let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; - let r: &[u64; R_SIZE] = data[R_OFFSET..R_OFFSET + R_SIZE].try_into().unwrap(); - let s: &[u64; S_SIZE] = data[S_OFFSET..S_OFFSET + S_SIZE].try_into().unwrap(); - let recid = u64::from_le(data[RECID_OFFSET]) as u8; - let msg: &[u64; MSG_SIZE] = data[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); - let low_s: bool = u64::from_le(data[LO_S_OFFSET]) != 0; + validate_hint_length(bytes, EXPECTED_LEN, "HINT_SECP256K1_ECRECOVER")?; - let mut hints = Vec::new(); - - let r = u64_be_to_u64_le(r); - let s = u64_be_to_u64_le(s); - let msg = u64_be_to_u64_le(msg); + let sig: &[u8; SIG_SIZE] = bytes[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); + let recid: &[u8; RECID_SIZE] = + bytes[RECID_OFFSET..RECID_OFFSET + RECID_SIZE].try_into().unwrap(); + let recid: u8 = u64::from_le_bytes(*recid) as u8; + let msg: &[u8; MSG_SIZE] = bytes[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); + let low_s: bool = + u64::from_le_bytes(bytes[LO_S_OFFSET..LO_S_OFFSET + LO_S_SIZE].try_into().unwrap()) != 0; - zisklib::secp256k1_ecrecover_point(&r, &s, &msg, recid, low_s, &mut hints) - .map_err(|e: u8| anyhow::anyhow!("HINT_SECP256K1_ECRECOVER: {}", e))?; + let mut hints = Vec::new(); + let result: &mut [u8; 32] = &mut [0u8; 32]; + unsafe { + zisklib::secp256k1_ecrecover_c( + sig.as_ptr(), + recid, + msg.as_ptr(), + result.as_mut_ptr(), + low_s, + &mut hints, + ); + } Ok(hints) } From da37aafbadf0b7bbcdafe26d2be06656e7376d45 Mon Sep 17 00:00:00 2001 From: agnusmor <100322135+agnusmor@users.noreply.github.com> Date: Tue, 27 Jan 2026 23:46:16 +0100 Subject: [PATCH 353/782] Add hints functions library for guest/host programs (#729) * Add support for new crypto hints (draft) * Refactor hint definitions * Refactor hint definitions and add is_result flag * Refactor SHA256 hint function * Add new hints for modexp, verify_kzg_proof and bn254_pairing_check * Add new hints for BLS12-381 and BN254, and fix hint data * Implement Keccak-256 hint * Align hint data to 8 bytes * Update Cargo.lock * Fix function signature for hints in bn254 and secp256k1 * Add check for paused HINT_QUEUE and main thread validation in hint functions * Update dependencies in Cargo.lock * Fix hint_bn254_pairing_check * Refactor hint handling by introducing HintBuffer * Add hints for FP to G1 and FP2 to G2 conversions * Add single-threaded hint checks and improve metrics reporting * Remove unused log dependency from Cargo.toml * Fix HintBuffer reset method. Fix macro define_hint * Add dependencies for zisk_hints and zisk_hints_debug configs. Fix header type in write_hint_header * Remove unused dependencies once_cell and paste from Cargo.toml * Add is_enabled method to HintBuffer and update macros to use it for hint checks * Fix visibility modifier for HINTS_METRICS in metrics.rs * Refactor is_enabled method in HintBuffer to check inner state directly --- Cargo.lock | 95 ++++---- ziskos/entrypoint/Cargo.toml | 7 + ziskos/entrypoint/src/hints/bls12_381.rs | 65 ++++++ ziskos/entrypoint/src/hints/bn254.rs | 29 +++ ziskos/entrypoint/src/hints/hint_buffer.rs | 207 +++++++++++++++++ ziskos/entrypoint/src/hints/keccak256.rs | 11 + ziskos/entrypoint/src/hints/kzg.rs | 11 + ziskos/entrypoint/src/hints/macros.rs | 176 +++++++++++++++ ziskos/entrypoint/src/hints/metrics.rs | 32 +++ ziskos/entrypoint/src/hints/mod.rs | 211 ++++++++++++++++++ ziskos/entrypoint/src/hints/modexp.rs | 11 + ziskos/entrypoint/src/hints/secp256k1.rs | 11 + ziskos/entrypoint/src/hints/sha256f.rs | 11 + ziskos/entrypoint/src/lib.rs | 3 + .../entrypoint/src/zisklib/lib/keccak256.rs | 43 +++- 15 files changed, 874 insertions(+), 49 deletions(-) create mode 100644 ziskos/entrypoint/src/hints/bls12_381.rs create mode 100644 ziskos/entrypoint/src/hints/bn254.rs create mode 100644 ziskos/entrypoint/src/hints/hint_buffer.rs create mode 100644 ziskos/entrypoint/src/hints/keccak256.rs create mode 100644 ziskos/entrypoint/src/hints/kzg.rs create mode 100644 ziskos/entrypoint/src/hints/macros.rs create mode 100644 ziskos/entrypoint/src/hints/metrics.rs create mode 100644 ziskos/entrypoint/src/hints/mod.rs create mode 100644 ziskos/entrypoint/src/hints/modexp.rs create mode 100644 ziskos/entrypoint/src/hints/secp256k1.rs create mode 100644 ziskos/entrypoint/src/hints/sha256f.rs diff --git a/Cargo.lock b/Cargo.lock index 8a65e4503..687cebf54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,9 +403,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.3" +version = "1.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e84ce723ab67259cfeb9877c6a639ee9eb7a27b28123abd71db7f0d5d0cc9d86" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" dependencies = [ "aws-lc-sys", "zeroize", @@ -413,9 +413,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a442ece363113bd4bd4c8b18977a7798dd4d3c3383f34fb61936960e8f4ad8" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" dependencies = [ "cc", "cmake", @@ -708,9 +708,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.53" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "jobserver", @@ -1177,10 +1177,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.114", +] + [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" dependencies = [ "fields", "num-bigint", @@ -1565,7 +1575,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" dependencies = [ "cfg-if", "num-bigint", @@ -2426,9 +2436,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" @@ -2685,9 +2695,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-format" @@ -2954,7 +2964,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" dependencies = [ "colored", "fields", @@ -3332,9 +3342,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -3342,7 +3352,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" dependencies = [ "blake3", "borsh", @@ -3377,7 +3387,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" dependencies = [ "borsh", "colored", @@ -3408,7 +3418,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" dependencies = [ "fields", "itoa", @@ -3421,7 +3431,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" dependencies = [ "proc-macro2", "quote", @@ -3432,7 +3442,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" dependencies = [ "crossbeam-channel", "tracing", @@ -3441,7 +3451,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" dependencies = [ "colored", "fields", @@ -3452,7 +3462,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" dependencies = [ "bytemuck", "fields", @@ -3601,9 +3611,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -4417,9 +4427,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -4660,9 +4670,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.45" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" dependencies = [ "deranged", "itoa", @@ -4677,15 +4687,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" dependencies = [ "num-conv", "time-core", @@ -5239,9 +5249,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -5858,7 +5868,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e574800c1cffa2cf9ca65d63b6b40e7377e87ed1" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" dependencies = [ "colored", "fields", @@ -5944,18 +5954,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" dependencies = [ "proc-macro2", "quote", @@ -6316,6 +6326,7 @@ version = "0.16.0" dependencies = [ "bincode", "cfg-if", + "ctor", "fields", "getrandom 0.2.17", "lazy_static", @@ -6323,6 +6334,8 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", + "once_cell", + "paste", "precompiles-helpers", "rand 0.8.5", "serde", @@ -6358,9 +6371,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" [[package]] name = "zstd" diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index a2fbc043a..2ea6dc099 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -27,6 +27,13 @@ bincode = "2.0" sha2 = { workspace = true } fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0", features = ["verify"] } +[target.'cfg(any(zisk_hints, zisk_hints_debug))'.dependencies] +once_cell = "1.21.3" +paste = "1.0" + +[target.'cfg(zisk_hints_metrics)'.dependencies] +ctor = "0.2" + [features] default = [] hints = [] diff --git a/ziskos/entrypoint/src/hints/bls12_381.rs b/ziskos/entrypoint/src/hints/bls12_381.rs new file mode 100644 index 000000000..fe55c49eb --- /dev/null +++ b/ziskos/entrypoint/src/hints/bls12_381.rs @@ -0,0 +1,65 @@ +use crate::hints::macros::{define_hint, define_hint_pairs}; + +const BLS12_381_G1_ADD_HINT_ID: u32 = 0x0400; +const BLS12_381_G1_MSM_HINT_ID: u32 = 0x0401; +const BLS12_381_G2_ADD_HINT_ID: u32 = 0x0405; +const BLS12_381_G2_MSM_HINT_ID: u32 = 0x0406; +const BLS12_381_PAIRING_CHECK_HINT_ID: u32 = 0x040A; +const BLS12_381_FP_TO_G1_HINT_ID: u32 = 0x0410; +const BLS12_381_FP2_TO_G2_HINT_ID: u32 = 0x0411; + +define_hint! { + bls12_381_g1_add => { + hint_id: BLS12_381_G1_ADD_HINT_ID, + params: (a: 96, b: 96), + is_result: false, + } +} + +define_hint_pairs! { + bls12_381_g1_msm => { + hint_id: BLS12_381_G1_MSM_HINT_ID, + pair_len: 96 + 32, + is_result: false, + } +} + +define_hint! { + bls12_381_g2_add => { + hint_id: BLS12_381_G2_ADD_HINT_ID, + params: (a: 192, b: 192), + is_result: false, + } +} + +define_hint_pairs! { + bls12_381_g2_msm => { + hint_id: BLS12_381_G2_MSM_HINT_ID, + pair_len: 192 + 32, + is_result: false, + } +} + +define_hint_pairs! { + bls12_381_pairing_check => { + hint_id: BLS12_381_PAIRING_CHECK_HINT_ID, + pair_len: 96 + 192, + is_result: false, + } +} + +define_hint! { + bls12_381_fp_to_g1 => { + hint_id: BLS12_381_FP_TO_G1_HINT_ID, + params: (fp: 48), + is_result: false, + } +} + +define_hint! { + bls12_381_fp2_to_g2 => { + hint_id: BLS12_381_FP2_TO_G2_HINT_ID, + params: (fp2: 96), + is_result: false, + } +} diff --git a/ziskos/entrypoint/src/hints/bn254.rs b/ziskos/entrypoint/src/hints/bn254.rs new file mode 100644 index 000000000..4a8a058e0 --- /dev/null +++ b/ziskos/entrypoint/src/hints/bn254.rs @@ -0,0 +1,29 @@ +use crate::hints::{macros::{define_hint, define_hint_pairs}}; + +const BN254_G1_ADD_HINT_ID: u32 = 0x0200; +const BN254_G1_MUL_HINT_ID: u32 = 0x0201; +const BN254_PAIRING_CHECK_HINT_ID: u32 = 0x0205; + +define_hint! { + bn254_g1_add => { + hint_id: BN254_G1_ADD_HINT_ID, + params: (p1: 64, p2: 64), + is_result: false, + } +} + +define_hint! { + bn254_g1_mul => { + hint_id: BN254_G1_MUL_HINT_ID, + params: (point: 64, scalar: 32), + is_result: false, + } +} + +define_hint_pairs! { + bn254_pairing_check => { + hint_id: BN254_PAIRING_CHECK_HINT_ID, + pair_len: 64 + 128, + is_result: false, + } +} diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs new file mode 100644 index 000000000..8a817bdbe --- /dev/null +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -0,0 +1,207 @@ +use std::io::{self, Write}; +use std::sync::{Arc, Condvar, Mutex}; + +pub const MAX_HINT_BUFFER_LEN: usize = 1 << 20; // 1 MiB +pub const HEADER_LEN: usize = 8; + +pub struct HintBuffer { + inner: Mutex, + not_empty: Condvar, +} + +struct HintBufferInner { + buf: [u8; MAX_HINT_BUFFER_LEN], + head: usize, + tail: usize, + len: usize, + closed: bool, + paused: bool, +} + +pub fn build_hint_buffer() -> Arc { + Arc::new(HintBuffer { + inner: Mutex::new(HintBufferInner { + buf: [0u8; MAX_HINT_BUFFER_LEN], + head: 0, + tail: 0, + len: 0, + closed: false, + paused: false, + }), + not_empty: Condvar::new(), + }) +} + +impl HintBufferInner { + #[inline] + fn free_space(&self) -> usize { + MAX_HINT_BUFFER_LEN - self.len + } + + #[inline] + fn write_bytes(&mut self, src: &[u8]) { + let mut remaining = src; + while !remaining.is_empty() { + let end_space = MAX_HINT_BUFFER_LEN - self.tail; + let chunk = remaining.len().min(end_space); + + self.buf[self.tail..self.tail + chunk].copy_from_slice(&remaining[..chunk]); + self.tail = (self.tail + chunk) % MAX_HINT_BUFFER_LEN; + self.len += chunk; + + remaining = &remaining[chunk..]; + } + } + + #[inline] + fn read_bytes(&mut self, dst: &mut [u8]) -> usize { + let to_read = dst.len().min(self.len); + if to_read == 0 { + return 0; + } + + let mut out = &mut dst[..to_read]; + while !out.is_empty() { + let end_space = MAX_HINT_BUFFER_LEN - self.head; + let chunk = out.len().min(end_space); + + out[..chunk].copy_from_slice(&self.buf[self.head..self.head + chunk]); + self.head = (self.head + chunk) % MAX_HINT_BUFFER_LEN; + self.len -= chunk; + + out = &mut out[chunk..]; + } + + to_read + } +} + +impl HintBuffer { + pub fn close(&self) { + let mut g = self.inner.lock().unwrap(); + g.closed = true; + self.not_empty.notify_all(); + } + + pub fn reset(&self) { + let mut g = self.inner.lock().unwrap(); + g.head = 0; + g.tail = 0; + g.len = 0; + g.closed = false; + g.paused = false; + + self.not_empty.notify_all(); + } + + #[inline(always)] + pub fn pause(&self) { + let mut g = self.inner.lock().unwrap(); + g.paused = true; + } + + #[inline(always)] + pub fn resume(&self) { + let mut g = self.inner.lock().unwrap(); + g.paused = false; + } + + #[inline(always)] + pub fn is_closed(&self) -> bool { + let g = self.inner.lock().unwrap(); + g.closed + } + + #[inline(always)] + pub fn is_paused(&self) -> bool { + let g = self.inner.lock().unwrap(); + g.paused + } + + #[inline(always)] + pub fn is_enabled(&self) -> bool { + let g = self.inner.lock().unwrap(); + !g.paused && !g.closed + } + + #[inline(always)] + pub fn write_hint_header(&self, hint_id: u32, len: usize, is_result: bool) { + let header = ((((if is_result { 0x8000_0000u64 } else { 0 }) | hint_id as u64) << 32) + | (len as u64)) + .to_le_bytes(); + + let mut g = self.inner.lock().unwrap(); + + if HEADER_LEN > g.free_space() { + panic!( + "Hint buffer overflow: capacity={} used={} free={} trying_to_write={}", + MAX_HINT_BUFFER_LEN, + g.len, + g.free_space(), + HEADER_LEN + ); + } + + #[cfg(zisk_hints_metrics)] + crate::hints::metrics::inc_hint_count(hint_id); + + g.write_bytes(&header); + self.not_empty.notify_one(); + } + + #[inline(always)] + pub fn write_hint_data(&self, data: *const u8, len: usize) { + if len > MAX_HINT_BUFFER_LEN { + panic!("Hint data too large: {} bytes (max buffer {})", len, MAX_HINT_BUFFER_LEN); + } + + let mut g = self.inner.lock().unwrap(); + + let payload: &[u8] = unsafe { std::slice::from_raw_parts(data, len) }; + + if payload.len() > g.free_space() { + panic!( + "Hint buffer overflow: capacity={} used={} free={} trying_to_write={}", + MAX_HINT_BUFFER_LEN, + g.len, + g.free_space(), + payload.len() + ); + } + + g.write_bytes(payload); + self.not_empty.notify_one(); + } + + fn read_blocking(&self, dst: &mut [u8]) -> io::Result { + if dst.is_empty() { + return Ok(0); + } + + let mut g = self.inner.lock().unwrap(); + while g.len == 0 && !g.closed { + g = self.not_empty.wait(g).unwrap(); + } + + if g.len == 0 && g.closed { + return Ok(0); + } + + Ok(g.read_bytes(dst)) + } + + pub fn drain_to_writer(&self, writer: &mut W) -> io::Result<()> { + let mut buffer = vec![0u8; 64 * 1024]; + + loop { + let n = self.read_blocking(&mut buffer)?; + if n == 0 { + break; // closed and empty + } + + writer.write_all(&buffer[..n])?; + } + + Ok(()) + } +} diff --git a/ziskos/entrypoint/src/hints/keccak256.rs b/ziskos/entrypoint/src/hints/keccak256.rs new file mode 100644 index 000000000..5807626c4 --- /dev/null +++ b/ziskos/entrypoint/src/hints/keccak256.rs @@ -0,0 +1,11 @@ +use crate::hints::macros::define_hint_ptr; + +const KECCAK256_HINT_ID: u32 = 0x0700; + +define_hint_ptr! { + keccak256 => { + hint_id: KECCAK256_HINT_ID, + param: input, + is_result: false, + } +} diff --git a/ziskos/entrypoint/src/hints/kzg.rs b/ziskos/entrypoint/src/hints/kzg.rs new file mode 100644 index 000000000..9d322f28c --- /dev/null +++ b/ziskos/entrypoint/src/hints/kzg.rs @@ -0,0 +1,11 @@ +use crate::hints::macros::define_hint; + +const KZG_VERIFY_PROOF_HINT_ID: u32 = 0x0600; + +define_hint! { + verify_kzg_proof => { + hint_id: KZG_VERIFY_PROOF_HINT_ID, + params: (z: 32, y: 32, commitment: 48, proof: 48), + is_result: false, + } +} diff --git a/ziskos/entrypoint/src/hints/macros.rs b/ziskos/entrypoint/src/hints/macros.rs new file mode 100644 index 000000000..066af2014 --- /dev/null +++ b/ziskos/entrypoint/src/hints/macros.rs @@ -0,0 +1,176 @@ +macro_rules! define_hint { + ( + $name:ident => { + hint_id: $hint_id:expr, + params: ( $( $arg:ident : $len:literal ),+ $(,)? ), + is_result: $is_result:expr, + } + ) => { + paste::paste! { + #[no_mangle] + pub unsafe extern "C" fn []($( $arg: *const u8 ),+) { + if !crate::hints::HINT_BUFFER.is_enabled() { + return; + } + + #[cfg(zisk_hints_single_thread)] + crate::hints::check_main_thread(); + + let mut total_len = 0; + $( + total_len += $len; + )+ + + crate::hints::HINT_BUFFER.write_hint_header( + $hint_id, + total_len, + $is_result, + ); + + $( + $crate::hints::HINT_BUFFER.write_hint_data($arg, $len); + )+ + } + + $crate::hints::macros::register_hint_meta!($name, $hint_id); + } + }; +} + +macro_rules! define_hint_pairs { + ( + $name:ident => { + hint_id: $hint_id:expr, + pair_len: $pair_len:expr, + is_result: $is_result:expr, + } + ) => { + paste::paste! { + #[no_mangle] + pub unsafe extern "C" fn []( pairs: *const u8, num_pairs: usize) { + if !crate::hints::HINT_BUFFER.is_enabled() { + return; + } + + #[cfg(zisk_hints_single_thread)] + crate::hints::check_main_thread(); + + crate::hints::HINT_BUFFER.write_hint_header( + $hint_id, + 8 + (num_pairs * $pair_len), + false, + ); + + let num_pairs_bytes: [u8; 8] = (num_pairs as u64).to_le_bytes(); + crate::hints::HINT_BUFFER.write_hint_data(num_pairs_bytes.as_ptr(), num_pairs_bytes.len()); + + crate::hints::HINT_BUFFER.write_hint_data(pairs, num_pairs * $pair_len); + } + + $crate::hints::macros::register_hint_meta!($name, $hint_id); + } + }; +} + +macro_rules! define_hint_ptr { + ( + $name:ident => { + hint_id: $hint_id:expr, + param: $arg:ident, + is_result: $is_result:expr, + } + ) => { + paste::paste! { + #[no_mangle] + pub unsafe extern "C" fn []([<$arg _ptr>]: *const u8, [<$arg _len>]: usize) { + if !crate::hints::HINT_BUFFER.is_enabled() { + return; + } + + #[cfg(zisk_hints_single_thread)] + crate::hints::check_main_thread(); + + let pad = (8 - ([<$arg _len>] & 7)) & 7; + + crate::hints::HINT_BUFFER.write_hint_header( + $hint_id, + [<$arg _len>], + $is_result, + ); + + crate::hints::HINT_BUFFER.write_hint_data([<$arg _ptr>], [<$arg _len>]); + if pad > 0 { + const ZERO_PAD: [u8; 8] = [0; 8]; + crate::hints::HINT_BUFFER.write_hint_data(ZERO_PAD.as_ptr(), pad); + } + } + + $crate::hints::macros::register_hint_meta!($name, $hint_id); + } + }; + ( + $name:ident => { + hint_id: $hint_id:expr, + params: ( $( $arg:ident ),+ $(,)? ), + is_result: $is_result:expr, + } + ) => { + paste::paste! { + #[no_mangle] + pub unsafe extern "C" fn []($( [<$arg _ptr>]: *const u8, [<$arg _len>]: usize ),+ + ) { + if !crate::hints::HINT_BUFFER.is_enabled() { + return; + } + + #[cfg(zisk_hints_single_thread)] + crate::hints::check_main_thread(); + + let mut total_len = 0; + $( + total_len += 8 + [<$arg _len>]; + )+ + + let pad = (8 - (total_len & 7)) & 7; + + crate::hints::HINT_BUFFER.write_hint_header( + $hint_id, + total_len, + $is_result, + ); + + $( + { + let len_bytes: [u8; 8] = ([<$arg _len>] as u64).to_le_bytes(); + crate::hints::HINT_BUFFER.write_hint_data(len_bytes.as_ptr(), len_bytes.len()); + crate::hints::HINT_BUFFER.write_hint_data([<$arg _ptr>], [<$arg _len>]); + } + )+ + + if pad > 0 { + const ZERO_PAD: [u8; 8] = [0; 8]; + crate::hints::HINT_BUFFER.write_hint_data(ZERO_PAD.as_ptr(), pad); + } + } + + $crate::hints::macros::register_hint_meta!($name, $hint_id); + } + }; +} + +macro_rules! register_hint_meta { + ($name:ident, $hint_id:expr) => { + paste::paste! { + #[cfg(zisk_hints_metrics)] + #[ctor::ctor] + fn [<$name _register_meta>]() { + $crate::hints::metrics::register_hint($hint_id, stringify!($name).to_string()); + } + } + }; +} + +pub(crate) use define_hint; +pub(crate) use define_hint_pairs; +pub(crate) use define_hint_ptr; +pub(crate) use register_hint_meta; \ No newline at end of file diff --git a/ziskos/entrypoint/src/hints/metrics.rs b/ziskos/entrypoint/src/hints/metrics.rs new file mode 100644 index 000000000..7a190e6e8 --- /dev/null +++ b/ziskos/entrypoint/src/hints/metrics.rs @@ -0,0 +1,32 @@ +use std::{collections::HashMap, sync::RwLock}; +use once_cell::sync::Lazy; + +pub(crate) static HINTS_METRICS: Lazy>> = Lazy::new(|| RwLock::new(HashMap::new())); + +#[derive(Clone, Debug)] +pub(crate) struct HintRegisterInfo { + pub hint_name: String, + pub count: u64, +} + +pub(crate) fn register_hint(hint_id: u32, hint_name: String) { + HINTS_METRICS.write().expect("HINTS_METRICS poisoned").insert(hint_id, HintRegisterInfo { hint_name, count: 0 }); +} + +pub(crate) fn inc_hint_count(hint_id: u32) { + if let Ok(mut hints) = HINTS_METRICS.write() { + if let Some(info) = hints.get_mut(&hint_id) { + info.count += 1; + } + } +} + +pub(crate) fn print_metrics() { + let hints = crate::hints::metrics::HINTS_METRICS.read().expect("HINTS_METRICS poisoned"); + println!("Hints usage summary:"); + for (_, info) in hints.iter() { + if info.count > 0 { + println!(" {}: {}", info.hint_name, info.count); + } + } +} \ No newline at end of file diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs new file mode 100644 index 000000000..6721a15bd --- /dev/null +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -0,0 +1,211 @@ +mod hint_buffer; +mod macros; +mod bls12_381; +mod bn254; +mod keccak256; +mod kzg; +mod modexp; +mod secp256k1; +mod sha256f; + +#[cfg(zisk_hints_metrics)] +mod metrics; + +use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer}; +use once_cell::sync::Lazy; +use std::{io::{self, BufWriter, Write}, sync::Arc}; +use std::path::PathBuf; +use std::thread::{self, JoinHandle, ThreadId}; +use std::{ffi::CStr, os::raw::c_char}; +use std::cell::UnsafeCell; + +#[cfg(zisk_hints_single_thread)] +use once_cell::sync::OnceCell; + +pub use bls12_381::*; +pub use bn254::*; +pub use keccak256::*; +pub use kzg::*; +pub use modexp::*; +pub use secp256k1::*; +pub use sha256f::*; + +pub const HINT_START: u32 = 0; +pub const HINT_END: u32 = 1; + +static HINT_BUFFER: Lazy> = Lazy::new(|| build_hint_buffer()); +static HINT_FILE_WRITER_HANDLE: Lazy = Lazy::new(HintFileWriterHandleCell::new); + +pub struct HintFileWriterHandleCell { + inner: UnsafeCell>>>, +} + +unsafe impl Sync for HintFileWriterHandleCell {} + +impl HintFileWriterHandleCell { + pub const fn new() -> Self { + Self { + inner: UnsafeCell::new(None), + } + } + + pub fn take(&self) -> Option>> { + unsafe { (*self.inner.get()).take() } + } + + pub fn store(&self, handle: JoinHandle>) { + // Safety: caller guarantees single-threaded access when mutating the handle. + unsafe { + *self.inner.get() = Some(handle); + } + } +} + +pub fn init_precompile_hints(hints_file_path: PathBuf) -> io::Result<()> { + // Record the main thread id to validate single-threaded calls later + #[cfg(zisk_hints_single_thread)] + let _ = MAIN_TID.set(std::thread::current().id()); + + if let Some(handle) = HINT_FILE_WRITER_HANDLE.take() { + HINT_BUFFER.close(); + match handle.join() { + Ok(result) => { + if let Err(err) = result { + return Err(err); + } + } + Err(e) => { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("Failed precompile hints writer thread, error: {:?}", e), + )) + } + } + } + + HINT_BUFFER.reset(); + + let handle = thread::spawn(move || write_precompile_hints(hints_file_path)); + HINT_FILE_WRITER_HANDLE.store(handle); + + Ok(()) +} + +pub fn close_precompile_hints() -> io::Result<()> { + HINT_BUFFER.close(); + + let handle = HINT_FILE_WRITER_HANDLE.take(); + if let Some(handle) = handle { + match handle.join() { + Ok(result) => { + match result { + Ok(()) => Ok(()), + Err(e) => return Err(e), + } + } + Err(e) => Err(io::Error::new( + io::ErrorKind::Other, + format!("Failed precompile hints writer thread, error: {:?}", e), + )), + } + } else { + Ok(()) + } +} + +fn write_precompile_hints(path: PathBuf) -> io::Result<()> { + debug_assert!(cfg!(target_endian = "little")); + + let file = std::fs::File::create(path)?; + let mut file_writer = BufWriter::with_capacity(1 << 20, file); + let disable_prefix = std::env::var("HINTS_DISABLE_PREFIX").unwrap_or_default() == "1"; + + // Write HINT_START + if !disable_prefix { + let start_header: u64 = ((HINT_START as u64) << 32) | 0u64; + let start_bytes = start_header.to_le_bytes(); + file_writer.write_all(&start_bytes)?; + } + + // Write hints from the buffer + HINT_BUFFER.drain_to_writer(&mut file_writer)?; + file_writer.flush()?; + + // Write HINT_END + if !disable_prefix { + let end_header: u64 = ((HINT_END as u64) << 32) | 0u64; + let end_bytes = end_header.to_le_bytes(); + file_writer.write_all(&end_bytes)?; + } + + file_writer.flush()?; + + #[cfg(zisk_hints_metrics)] + crate::hints::metrics::print_metrics(); + + Ok(()) +} + +#[cfg(zisk_hints_single_thread)] +static MAIN_TID: OnceCell = OnceCell::new(); + +#[cfg(zisk_hints_single_thread)] +#[inline(always)] +pub(crate) fn check_main_thread() { + // Panic on calls from a different thread + let tid = std::thread::current().id(); + match MAIN_TID.get() { + Some(main) => { + if *main != tid { + panic!( + "Precompile hint function called from non-main thread, main={:?}, current={:?}", + main, tid + ); + } + } + None => { + // If not initialized yet, record the first caller thread as main + let _ = MAIN_TID.set(tid); + } + } +} + +// Logs hint message; gated by `hints_enabled()` on non-Zisk targets and always-on for Zisk +#[inline(always)] +pub fn hint_log>(msg: S) { + // We check if hints are enable only for non-zisk targets, since in zisk targets hints are not used + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + if !HINT_BUFFER.is_enabled() { + return; + } + + println!("{}", msg.as_ref()); +} + +// Extern functions for C interface + +#[no_mangle] +pub extern "C" fn pause_hints() -> bool { + let already_paused = HINT_BUFFER.is_paused(); + HINT_BUFFER.pause(); + already_paused +} + +#[no_mangle] +pub extern "C" fn resume_hints() { + HINT_BUFFER.resume(); +} + +#[no_mangle] +pub unsafe extern "C" fn hint_log_c(msg: *const c_char) { + if msg.is_null() { + return; + } + + let c_str = unsafe { CStr::from_ptr(msg) }; + + match c_str.to_str() { + Ok(s) => hint_log(s), + Err(_) => return, + } +} \ No newline at end of file diff --git a/ziskos/entrypoint/src/hints/modexp.rs b/ziskos/entrypoint/src/hints/modexp.rs new file mode 100644 index 000000000..617d3251b --- /dev/null +++ b/ziskos/entrypoint/src/hints/modexp.rs @@ -0,0 +1,11 @@ +use crate::hints::macros::define_hint_ptr; + +const MODEXP_HINT_ID: u32 = 0x0500; + +define_hint_ptr! { + modexp_bytes => { + hint_id: MODEXP_HINT_ID, + params: (base, exp, modulus), + is_result: false, + } +} diff --git a/ziskos/entrypoint/src/hints/secp256k1.rs b/ziskos/entrypoint/src/hints/secp256k1.rs new file mode 100644 index 000000000..560a8cfc7 --- /dev/null +++ b/ziskos/entrypoint/src/hints/secp256k1.rs @@ -0,0 +1,11 @@ +use crate::hints::macros::define_hint; + +const SECP256K1_ECRECOVER_HINT_ID: u32 = 0x0300; + +define_hint! { + secp256k1_ecrecover => { + hint_id: SECP256K1_ECRECOVER_HINT_ID, + params: (sig: 64, recid: 8, msg: 32, require_low_s: 8), + is_result: false, + } +} diff --git a/ziskos/entrypoint/src/hints/sha256f.rs b/ziskos/entrypoint/src/hints/sha256f.rs new file mode 100644 index 000000000..cfd6b805a --- /dev/null +++ b/ziskos/entrypoint/src/hints/sha256f.rs @@ -0,0 +1,11 @@ +use crate::hints::macros::define_hint_ptr; + +const SHA256_HINT_ID: u32 = 0x0100; + +define_hint_ptr! { + sha256 => { + hint_id: SHA256_HINT_ID, + param: f, + is_result: false, + } +} diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index ae6ae0903..259e8425e 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -16,6 +16,9 @@ pub mod syscalls; pub mod ziskos_definitions; +#[cfg(any(zisk_hints, zisk_hints_debug))] +pub mod hints; + #[macro_export] macro_rules! entrypoint { ($path:path) => { diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index 5386a751d..f4e527f9e 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -7,7 +7,7 @@ const KECCAK256_RATE: usize = 136; /// /// This implements the Keccak sponge construction with: /// - Rate: 1088 bits (136 bytes) -/// - Capacity: 512 bits (64 bytes) +/// - Capacity: 512 bits (64 bytes) /// - Output: 256 bits (32 bytes) /// - Padding: Keccak padding (0x01...0x80) pub fn keccak256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u8; 32] { @@ -106,11 +106,38 @@ pub unsafe extern "C" fn native_keccak256( output: *mut u8, #[cfg(feature = "hints")] hints: &mut Vec, ) { - keccak256_c( - bytes, - len, - output, - #[cfg(feature = "hints")] - hints, - ); + #[cfg(zisk_hints)] + crate::hints::hint_keccak256(bytes, len); + + #[cfg(zisk_hints_debug)] + { + let input_bytes = unsafe { core::slice::from_raw_parts(bytes, len) }; + crate::hints::hint_log(format!("hint_keccak256 (bytes: {:?}, len: {})", input_bytes, len)); + } + + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + keccak256_c( + bytes, + len, + output, + #[cfg(feature = "hints")] + hints, + ); + } + + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + { + use tiny_keccak::{Hasher, Keccak}; + + let (input_bytes, out) = unsafe { + let input_bytes = core::slice::from_raw_parts(bytes, len); + let out = core::slice::from_raw_parts_mut(output, 32); + (input_bytes, out) + }; + + let mut hasher = Keccak::v256(); + hasher.update(input_bytes); + hasher.finalize(out); + } } From 30c1f53f4876c3784c49ed822b2bd1ec2a2fce8d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 28 Jan 2026 07:54:49 +0000 Subject: [PATCH 354/782] remove ZiskStreamNull and pass a new with_hints parameter to zisk_lib --- cli/src/commands/execute.rs | 17 ++++--- cli/src/commands/prove.rs | 2 +- cli/src/commands/stats.rs | 2 +- cli/src/commands/verify_constraints.rs | 17 ++++--- common/src/io/stream/mod.rs | 2 - common/src/io/stream/null.rs | 44 ------------------- common/src/io/stream/stream_reader.rs | 20 ++------- common/src/zisk_lib_init.rs | 1 + .../crates/coordinator/src/coordinator.rs | 2 +- distributed/crates/worker/src/worker.rs | 2 +- sdk/src/builder.rs | 12 +++++ sdk/src/prover/asm.rs | 4 ++ sdk/src/zisk_lib_loader.rs | 6 ++- witness-computation/src/zisk_lib.rs | 43 +++++++++++------- 14 files changed, 79 insertions(+), 95 deletions(-) delete mode 100644 common/src/io/stream/null.rs diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 6d9aac77d..bc91ddf5f 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -93,11 +93,17 @@ impl ZiskExecute { let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; + let hints_stream = if self.hints.is_some() { + let hints_stream = StreamSource::from_uri(self.hints.as_ref().unwrap())?; - if matches!(hints_stream, StreamSource::Quic(_)) { - return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); - } + if matches!(hints_stream, StreamSource::Quic(_)) { + return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); + } + + Some(hints_stream) + } else { + None + }; let emulator = if cfg!(target_os = "macos") { if !self.emulator { @@ -109,7 +115,7 @@ impl ZiskExecute { }; let result = - if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, Some(hints_stream))? }; + if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, hints_stream)? }; info!("Execution completed in {:.2?}, steps: {}", result.duration, result.execution.steps); @@ -147,6 +153,7 @@ impl ZiskExecute { .asm_path_opt(self.asm.clone()) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) + .with_hints(hints_stream.is_some()) .print_command_info() .build()?; diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index bc32098b3..d091186d3 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -142,7 +142,7 @@ impl ZiskProve { let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; + let hints_stream = StreamSource::from_uri(self.hints.as_ref().unwrap())?; if matches!(hints_stream, StreamSource::Quic(_)) { return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index 55ed2c5fd..fa6bd89ad 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -112,7 +112,7 @@ impl ZiskStats { let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; + let hints_stream = StreamSource::from_uri(self.hints.as_ref().unwrap())?; if matches!(hints_stream, StreamSource::Quic(_)) { return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index b2d3702a6..ed5138ec2 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -98,11 +98,17 @@ impl ZiskVerifyConstraints { let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.hints.as_deref())?; + let hints_stream = if self.hints.is_some() { + let hints_stream = StreamSource::from_uri(self.hints.as_ref().unwrap())?; - if matches!(hints_stream, StreamSource::Quic(_)) { - return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); - } + if matches!(hints_stream, StreamSource::Quic(_)) { + return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); + } + + Some(hints_stream) + } else { + None + }; let emulator = if cfg!(target_os = "macos") { if !self.emulator { @@ -114,7 +120,7 @@ impl ZiskVerifyConstraints { }; let result = - if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, Some(hints_stream))? }; + if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, hints_stream)? }; tracing::info!(""); tracing::info!( @@ -162,6 +168,7 @@ impl ZiskVerifyConstraints { .asm_path_opt(self.asm.clone()) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) + .with_hints(hints_stream.is_some()) .print_command_info() .build()?; diff --git a/common/src/io/stream/mod.rs b/common/src/io/stream/mod.rs index f35168583..ffdc645c3 100644 --- a/common/src/io/stream/mod.rs +++ b/common/src/io/stream/mod.rs @@ -1,6 +1,5 @@ mod file; mod memory; -mod null; mod quic; mod stream_reader; mod stream_writer; @@ -11,7 +10,6 @@ mod unix_socket; pub use file::{FileStreamReader, FileStreamWriter}; pub use memory::MemoryStreamReader; -pub use null::NullStreamReader; pub use quic::{QuicStreamReader, QuicStreamWriter}; pub use stream_reader::*; pub use stream_writer::*; diff --git a/common/src/io/stream/null.rs b/common/src/io/stream/null.rs deleted file mode 100644 index c12933f17..000000000 --- a/common/src/io/stream/null.rs +++ /dev/null @@ -1,44 +0,0 @@ -use super::StreamRead; - -use anyhow::Result; - -pub struct NullStreamReader { - active: bool, -} - -impl Default for NullStreamReader { - fn default() -> Self { - NullStreamReader::new() - } -} - -impl NullStreamReader { - /// Create a new NullStreamReader - pub fn new() -> Self { - NullStreamReader { active: false } - } -} - -impl StreamRead for NullStreamReader { - /// Open/initialize the stream for reading - fn open(&mut self) -> Result<()> { - self.active = true; - Ok(()) - } - - /// Read the next item from the stream - fn next(&mut self) -> Result>> { - Ok(None) - } - - /// Close the stream - fn close(&mut self) -> Result<()> { - self.active = false; - Ok(()) - } - - /// Check if the stream is currently active - fn is_active(&self) -> bool { - self.active - } -} diff --git a/common/src/io/stream/stream_reader.rs b/common/src/io/stream/stream_reader.rs index 6b764f14b..dcb2ce028 100644 --- a/common/src/io/stream/stream_reader.rs +++ b/common/src/io/stream/stream_reader.rs @@ -1,6 +1,6 @@ use crate::io::{MemoryStreamReader, QuicStreamReader, UnixSocketStreamReader}; -use super::{FileStreamReader, NullStreamReader}; +use super::FileStreamReader; use anyhow::Result; @@ -22,18 +22,12 @@ pub trait StreamRead: Send + 'static { pub enum StreamSource { File(FileStreamReader), - Null(NullStreamReader), UnixSocket(UnixSocketStreamReader), Quic(QuicStreamReader), Memory(MemoryStreamReader), } impl StreamSource { - /// Create a null stdin - pub fn null() -> Self { - StreamSource::Null(NullStreamReader::new()) - } - /// Create a file-based stdin pub fn from_file>(path: P) -> Result { Ok(StreamSource::File(FileStreamReader::new(path)?)) @@ -65,12 +59,8 @@ impl StreamSource { /// - `file://path/to/file` → File-based stream /// - `unix://path/to/socket` → Unix domain socket stream /// - `quic://host:port` → QUIC network stream (e.g., `quic://127.0.0.1:8080`) - pub fn from_uri>(hints_uri: Option) -> Result { - if hints_uri.is_none() { - return Ok(Self::null()); - } - - let uri_str = hints_uri.unwrap().into(); + pub fn from_uri>(hints_uri: S) -> Result { + let uri_str = hints_uri.into(); // Check if URI contains "://" separator if let Some(pos) = uri_str.find("://") { @@ -96,7 +86,6 @@ impl StreamRead for StreamSource { fn open(&mut self) -> Result<()> { match self { StreamSource::File(file_stream) => file_stream.open(), - StreamSource::Null(null_stream) => null_stream.open(), StreamSource::UnixSocket(unix_stream) => unix_stream.open(), StreamSource::Quic(quic_stream) => quic_stream.open(), StreamSource::Memory(memory_stream) => memory_stream.open(), @@ -107,7 +96,6 @@ impl StreamRead for StreamSource { fn next(&mut self) -> Result>> { match self { StreamSource::File(file_stream) => file_stream.next(), - StreamSource::Null(null_stream) => null_stream.next(), StreamSource::UnixSocket(unix_stream) => unix_stream.next(), StreamSource::Quic(quic_stream) => quic_stream.next(), StreamSource::Memory(memory_stream) => memory_stream.next(), @@ -118,7 +106,6 @@ impl StreamRead for StreamSource { fn close(&mut self) -> Result<()> { match self { StreamSource::File(file_stream) => file_stream.close(), - StreamSource::Null(null_stream) => null_stream.close(), StreamSource::UnixSocket(unix_stream) => unix_stream.close(), StreamSource::Quic(quic_stream) => quic_stream.close(), StreamSource::Memory(memory_stream) => memory_stream.close(), @@ -129,7 +116,6 @@ impl StreamRead for StreamSource { fn is_active(&self) -> bool { match self { StreamSource::File(file_stream) => file_stream.is_active(), - StreamSource::Null(null_stream) => null_stream.is_active(), StreamSource::UnixSocket(unix_stream) => unix_stream.is_active(), StreamSource::Quic(quic_stream) => quic_stream.is_active(), StreamSource::Memory(memory_stream) => memory_stream.is_active(), diff --git a/common/src/zisk_lib_init.rs b/common/src/zisk_lib_init.rs index 96d7411c8..74be3de93 100644 --- a/common/src/zisk_lib_init.rs +++ b/common/src/zisk_lib_init.rs @@ -59,4 +59,5 @@ pub type ZiskLibInitFn = fn( Option, // Base port for the ASM microservices bool, // Unlock_mapped_memory bool, // Shared_tables + bool, // With_hints ) -> Result>, Box>; diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 686356c68..f8cc286d9 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -759,7 +759,7 @@ impl Coordinator { }; let hints_relay = PrecompileHintsRelay::new(dispatcher); let mut stream = ZiskStream::new(hints_relay); - let stream_reader = StreamSource::from_uri(Some(hints_uri)).map_err(|e| { + let stream_reader = StreamSource::from_uri(hints_uri).map_err(|e| { CoordinatorError::Internal(format!( "Failed to create hints stream reader for job {}: {}", job.job_id, e diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 31e674610..ca4e724d3 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -572,7 +572,7 @@ impl Worker { match hints_source { HintsSourceDto::HintsPath(hints_uri) => { - let hints_stream = StreamSource::from_uri(hints_uri.into())?; + let hints_stream = StreamSource::from_uri(hints_uri)?; prover.set_hints_stream(hints_stream)?; } HintsSourceDto::HintsStream(_hints_uri) => { diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 34168ee57..090aaf258 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -67,6 +67,7 @@ pub struct ProverClientBuilder { asm_path: Option, base_port: Option, unlock_mapped_memory: bool, + with_hints: bool, // Prove-specific fields (only available when Operation = Prove) save_proofs: bool, @@ -248,6 +249,12 @@ impl ProverClientBuilder { self.unlock_mapped_memory = unlock; self } + + #[must_use] + pub fn with_hints(mut self, with_hints: bool) -> Self { + self.with_hints = with_hints; + self + } } // Prove-specific methods (available for both backends when operation is Prove) @@ -508,6 +515,7 @@ impl ProverClientBuilder { asm_rh_filename, self.base_port, self.unlock_mapped_memory, + self.with_hints, self.gpu_params.filter(|_| !self.verify_constraints).unwrap_or_default(), self.verify_proofs, self.minimal_memory, @@ -579,6 +587,7 @@ impl From> for ProverClientBuilder { asm_path: None, base_port: None, unlock_mapped_memory: false, + with_hints: false, // Reset prove-specific fields (will be set when choosing operation) save_proofs: false, @@ -615,6 +624,7 @@ impl From> for ProverClientBuilder { asm_path: builder.asm_path, base_port: builder.base_port, unlock_mapped_memory: builder.unlock_mapped_memory, + with_hints: builder.with_hints, // Reset prove-specific fields (will be set when choosing operation) save_proofs: false, @@ -653,6 +663,7 @@ impl From> asm_path: builder.asm_path, base_port: builder.base_port, unlock_mapped_memory: builder.unlock_mapped_memory, + with_hints: builder.with_hints, // Initialize prove-specific fields to defaults for verify_constraints mode save_proofs: false, // Not relevant for constraint verification @@ -689,6 +700,7 @@ impl From> for ProverClientBuilder, unlock_mapped_memory: bool, + with_hints: bool, gpu_params: ParamsGPU, verify_proofs: bool, minimal_memory: bool, @@ -66,6 +67,7 @@ impl AsmProver { asm_rh_filename, base_port, unlock_mapped_memory, + with_hints, gpu_params, verify_proofs, minimal_memory, @@ -215,6 +217,7 @@ impl AsmCoreProver { asm_rh_filename: String, base_port: Option, unlock_mapped_memory: bool, + with_hints: bool, gpu_params: ParamsGPU, verify_proofs: bool, minimal_memory: bool, @@ -248,6 +251,7 @@ impl AsmCoreProver { asm_rh_path, base_port, unlock_mapped_memory, + with_hints, )?; let proofman = ProofMan::new( diff --git a/sdk/src/zisk_lib_loader.rs b/sdk/src/zisk_lib_loader.rs index d62243abe..1e18eed09 100644 --- a/sdk/src/zisk_lib_loader.rs +++ b/sdk/src/zisk_lib_loader.rs @@ -23,6 +23,7 @@ impl ZiskLibLoader { asm_rh_filename: Option, base_port: Option, unlock_mapped_memory: Option, + with_hints: bool, ) -> Result<(Library, Box>)> { let lib_path = get_witness_computation_lib(Some(&witness_lib)); let library = unsafe { Library::new(lib_path) }?; @@ -39,6 +40,7 @@ impl ZiskLibLoader { base_port, unlock_mapped_memory.unwrap_or(false), shared_tables, + with_hints, ) .expect("Failed to initialize witness library"); @@ -51,7 +53,7 @@ impl ZiskLibLoader { verbose: VerboseMode, shared_tables: bool, ) -> Result<(Library, Box>)> { - Self::load_library(witness_lib, elf, verbose, shared_tables, None, None, None, None) + Self::load_library(witness_lib, elf, verbose, shared_tables, None, None, None, None, false) } #[allow(clippy::too_many_arguments)] @@ -64,6 +66,7 @@ impl ZiskLibLoader { asm_rh_filename: PathBuf, base_port: Option, unlock_mapped_memory: bool, + with_hints: bool, ) -> Result<(Library, Box>)> { Self::load_library( witness_lib, @@ -74,6 +77,7 @@ impl ZiskLibLoader { Some(asm_rh_filename), base_port, Some(unlock_mapped_memory), + with_hints, ) } } diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 81eaab3cc..f82d1d2aa 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -53,6 +53,7 @@ pub struct WitnessLib { unlock_mapped_memory: bool, shared_tables: bool, verbose_mode: proofman_common::VerboseMode, + with_hints: bool, } #[no_mangle] @@ -65,6 +66,7 @@ fn init_library( base_port: Option, unlock_mapped_memory: bool, shared_tables: bool, + with_hints: bool, ) -> Result>, Box> { let chunk_size = CHUNK_SIZE; @@ -77,6 +79,7 @@ fn init_library( base_port, unlock_mapped_memory, shared_tables, + with_hints, verbose_mode, }); @@ -201,26 +204,32 @@ impl WitnessLibrary for WitnessLib { // Create hints pipeline with null hints stream initially. // Debug flag: true = HintsShmem (shared memory), false = HintsFile (file output) - const USE_SHARED_MEMORY_HINTS: bool = true; + let hints_stream = if self.with_hints { + println!("Initializing zisk_lib with hints stream."); + const USE_SHARED_MEMORY_HINTS: bool = true; - let hints_processor = if USE_SHARED_MEMORY_HINTS { - let hints_shmem = - HintsShmem::new(self.base_port, local_rank, self.unlock_mapped_memory) - .expect("zisk_lib: Failed to create HintsShmem"); + let hints_processor = if USE_SHARED_MEMORY_HINTS { + let hints_shmem = + HintsShmem::new(self.base_port, local_rank, self.unlock_mapped_memory) + .expect("zisk_lib: Failed to create HintsShmem"); - HintsProcessor::builder(hints_shmem) - .build() - .expect("zisk_lib: Failed to create PrecompileHintsProcessor") - } else { - let hints_file = HintsFile::new(format!("hints_results_{}.bin", local_rank)) - .expect("zisk_lib: Failed to create HintsFile"); + HintsProcessor::builder(hints_shmem) + .build() + .expect("zisk_lib: Failed to create PrecompileHintsProcessor") + } else { + let hints_file = HintsFile::new(format!("hints_results_{}.bin", local_rank)) + .expect("zisk_lib: Failed to create HintsFile"); - HintsProcessor::builder(hints_file) - .build() - .expect("zisk_lib: Failed to create PrecompileHintsProcessor") - }; + HintsProcessor::builder(hints_file) + .build() + .expect("zisk_lib: Failed to create PrecompileHintsProcessor") + }; - let hints_stream = ZiskStream::new(hints_processor); + Some(ZiskStream::new(hints_processor)) + } else { + println!("Initializing zisk_lib without hints stream."); + None + }; let executor = Arc::new(ZiskExecutor::new( zisk_rom, @@ -228,7 +237,7 @@ impl WitnessLibrary for WitnessLib { sm_bundle, self.chunk_size, emulator, - Some(hints_stream), + hints_stream, )); // Step 7: Register the executor as a component in the Witness Manager From 6849a13eacc8f7ab3dcef3b8a1784e50194815ce Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 28 Jan 2026 07:57:51 +0000 Subject: [PATCH 355/782] fix keccakck256 zisklib fn --- ziskos/entrypoint/src/zisklib/lib/keccak256.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index f4e527f9e..a348a3ecb 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -129,15 +129,24 @@ pub unsafe extern "C" fn native_keccak256( #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { use tiny_keccak::{Hasher, Keccak}; + const OUT_LEN: usize = 32; let (input_bytes, out) = unsafe { let input_bytes = core::slice::from_raw_parts(bytes, len); - let out = core::slice::from_raw_parts_mut(output, 32); + let out = core::slice::from_raw_parts_mut(output, OUT_LEN); (input_bytes, out) }; let mut hasher = Keccak::v256(); hasher.update(input_bytes); hasher.finalize(out); + + #[cfg(feature = "hints")] + { + const OUT_LEN_WORDS: usize = OUT_LEN / std::mem::size_of::(); + let out_u64: &[u64] = + unsafe { core::slice::from_raw_parts(out.as_ptr() as *const u64, OUT_LEN_WORDS) }; + hints.extend_from_slice(out_u64); + } } } From a8d1a74159dd6908f51c09352ebecc8d78330455 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 28 Jan 2026 08:02:33 +0000 Subject: [PATCH 356/782] improved hitns stats --- precompiles/hints/src/hints_processor.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index e11a939e6..7d3bab7bf 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -10,7 +10,7 @@ use std::collections::{HashMap, VecDeque}; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; -use tracing::debug; +use tracing::{debug, info}; use zisk_common::io::{StreamProcessor, StreamSink}; use zisk_common::{BuiltInHint, CtrlHint, HintCode, PrecompileHint}; use ziskos_hints::handlers::bls381::{ @@ -269,7 +269,14 @@ impl HintsProcessor { if ALLOW_UNALIGNED { hint.data_len_bytes } else { hint.data.len() } + HEADER_SIZE; if let Some(stats) = &self.stats { - stats.lock().unwrap().entry(hint.hint_code).and_modify(|c| *c += 1).or_insert(1); + if !matches!(hint.hint_code, HintCode::Ctrl(_)) { + stats + .lock() + .unwrap() + .entry(hint.hint_code) + .and_modify(|c| *c += 1) + .or_insert(1); + } } // Check if this is a control code @@ -363,12 +370,12 @@ impl HintsProcessor { if has_ctrl_end { if let Some(stats) = &self.stats { - debug!("Processed hints stats:"); + info!("Hints stats:"); let stats = stats.lock().unwrap(); let mut sorted_stats: Vec<_> = stats.iter().collect(); sorted_stats.sort_by_key(|(&hint_code, _)| hint_code.to_u32()); for (hint_code, count) in sorted_stats { - debug!("Hint type {}: {}", hint_code, count); + info!(" {}: {}", hint_code, count); } } } From 8fb67fb859152ea7a8a0850ce135131afab870dc Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 28 Jan 2026 08:03:09 +0000 Subject: [PATCH 357/782] added processed hints info --- precompiles/hints/src/hints_processor.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 7d3bab7bf..20c66ccaf 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -10,6 +10,7 @@ use std::collections::{HashMap, VecDeque}; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; +use std::time::Instant; use tracing::{debug, info}; use zisk_common::io::{StreamProcessor, StreamSink}; use zisk_common::{BuiltInHint, CtrlHint, HintCode, PrecompileHint}; @@ -145,6 +146,7 @@ impl HintsProcessorBuilder { hints_sink, drainer_thread: ManuallyDrop::new(drainer_thread), custom_handlers: Arc::new(self.custom_handlers), + instant: Mutex::new(None), }) } } @@ -175,6 +177,8 @@ pub struct HintsProcessor { /// Custom hint handlers registered by the user custom_handlers: Arc>, + + instant: Mutex>, } impl HintsProcessor { @@ -298,6 +302,7 @@ impl HintsProcessor { self.reset(); // Control hint only; skip processing idx += length; + *self.instant.lock().unwrap() = Some(Instant::now()); continue; } HintCode::Ctrl(CtrlHint::End) => { @@ -315,6 +320,21 @@ impl HintsProcessor { hints.len() - idx )); } + + let num_hints = self.num_hint.load(Ordering::Relaxed); + let elapsed = self.instant.lock().as_ref().unwrap().unwrap().elapsed(); + let rate = num_hints as f64 / elapsed.as_secs_f64(); + + let (value, unit) = if rate >= 1_000_000.0 { + (rate / 1_000_000.0, "MHz") + } else if rate >= 1_000.0 { + (rate / 1_000.0, "kHz") + } else { + (rate, "Hz") + }; + + info!("Processed {} hints in {:?} ({:.2} {})", num_hints, elapsed, value, unit); + break; } HintCode::Ctrl(CtrlHint::Cancel) => { From 78965b81ecb8044f19c985cff1f414d5a3577a8f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 28 Jan 2026 08:04:59 +0000 Subject: [PATCH 358/782] cargo fmt --- ziskos/entrypoint/src/hints/bn254.rs | 2 +- ziskos/entrypoint/src/hints/macros.rs | 2 +- ziskos/entrypoint/src/hints/metrics.rs | 12 ++++--- ziskos/entrypoint/src/hints/mod.rs | 32 +++++++++---------- .../src/zisklib/lib/bn254/pairing.rs | 2 +- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/ziskos/entrypoint/src/hints/bn254.rs b/ziskos/entrypoint/src/hints/bn254.rs index 4a8a058e0..5d438e8dd 100644 --- a/ziskos/entrypoint/src/hints/bn254.rs +++ b/ziskos/entrypoint/src/hints/bn254.rs @@ -1,4 +1,4 @@ -use crate::hints::{macros::{define_hint, define_hint_pairs}}; +use crate::hints::macros::{define_hint, define_hint_pairs}; const BN254_G1_ADD_HINT_ID: u32 = 0x0200; const BN254_G1_MUL_HINT_ID: u32 = 0x0201; diff --git a/ziskos/entrypoint/src/hints/macros.rs b/ziskos/entrypoint/src/hints/macros.rs index 066af2014..634296d09 100644 --- a/ziskos/entrypoint/src/hints/macros.rs +++ b/ziskos/entrypoint/src/hints/macros.rs @@ -173,4 +173,4 @@ macro_rules! register_hint_meta { pub(crate) use define_hint; pub(crate) use define_hint_pairs; pub(crate) use define_hint_ptr; -pub(crate) use register_hint_meta; \ No newline at end of file +pub(crate) use register_hint_meta; diff --git a/ziskos/entrypoint/src/hints/metrics.rs b/ziskos/entrypoint/src/hints/metrics.rs index 7a190e6e8..06d436cdf 100644 --- a/ziskos/entrypoint/src/hints/metrics.rs +++ b/ziskos/entrypoint/src/hints/metrics.rs @@ -1,7 +1,8 @@ -use std::{collections::HashMap, sync::RwLock}; use once_cell::sync::Lazy; +use std::{collections::HashMap, sync::RwLock}; -pub(crate) static HINTS_METRICS: Lazy>> = Lazy::new(|| RwLock::new(HashMap::new())); +pub(crate) static HINTS_METRICS: Lazy>> = + Lazy::new(|| RwLock::new(HashMap::new())); #[derive(Clone, Debug)] pub(crate) struct HintRegisterInfo { @@ -10,7 +11,10 @@ pub(crate) struct HintRegisterInfo { } pub(crate) fn register_hint(hint_id: u32, hint_name: String) { - HINTS_METRICS.write().expect("HINTS_METRICS poisoned").insert(hint_id, HintRegisterInfo { hint_name, count: 0 }); + HINTS_METRICS + .write() + .expect("HINTS_METRICS poisoned") + .insert(hint_id, HintRegisterInfo { hint_name, count: 0 }); } pub(crate) fn inc_hint_count(hint_id: u32) { @@ -29,4 +33,4 @@ pub(crate) fn print_metrics() { println!(" {}: {}", info.hint_name, info.count); } } -} \ No newline at end of file +} diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 6721a15bd..cf7308ec8 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -1,9 +1,9 @@ -mod hint_buffer; -mod macros; mod bls12_381; mod bn254; +mod hint_buffer; mod keccak256; mod kzg; +mod macros; mod modexp; mod secp256k1; mod sha256f; @@ -11,13 +11,16 @@ mod sha256f; #[cfg(zisk_hints_metrics)] mod metrics; -use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer}; +use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer}; use once_cell::sync::Lazy; -use std::{io::{self, BufWriter, Write}, sync::Arc}; +use std::cell::UnsafeCell; use std::path::PathBuf; use std::thread::{self, JoinHandle, ThreadId}; use std::{ffi::CStr, os::raw::c_char}; -use std::cell::UnsafeCell; +use std::{ + io::{self, BufWriter, Write}, + sync::Arc, +}; #[cfg(zisk_hints_single_thread)] use once_cell::sync::OnceCell; @@ -34,7 +37,8 @@ pub const HINT_START: u32 = 0; pub const HINT_END: u32 = 1; static HINT_BUFFER: Lazy> = Lazy::new(|| build_hint_buffer()); -static HINT_FILE_WRITER_HANDLE: Lazy = Lazy::new(HintFileWriterHandleCell::new); +static HINT_FILE_WRITER_HANDLE: Lazy = + Lazy::new(HintFileWriterHandleCell::new); pub struct HintFileWriterHandleCell { inner: UnsafeCell>>>, @@ -44,9 +48,7 @@ unsafe impl Sync for HintFileWriterHandleCell {} impl HintFileWriterHandleCell { pub const fn new() -> Self { - Self { - inner: UnsafeCell::new(None), - } + Self { inner: UnsafeCell::new(None) } } pub fn take(&self) -> Option>> { @@ -97,12 +99,10 @@ pub fn close_precompile_hints() -> io::Result<()> { let handle = HINT_FILE_WRITER_HANDLE.take(); if let Some(handle) = handle { match handle.join() { - Ok(result) => { - match result { - Ok(()) => Ok(()), - Err(e) => return Err(e), - } - } + Ok(result) => match result { + Ok(()) => Ok(()), + Err(e) => return Err(e), + }, Err(e) => Err(io::Error::new( io::ErrorKind::Other, format!("Failed precompile hints writer thread, error: {:?}", e), @@ -208,4 +208,4 @@ pub unsafe extern "C" fn hint_log_c(msg: *const c_char) { Ok(s) => hint_log(s), Err(_) => return, } -} \ No newline at end of file +} diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs index 915ebfa27..037a1ff50 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs @@ -261,7 +261,7 @@ pub unsafe extern "C" fn bn254_pairing_check_c( // Parse all pairs let mut g1_points: Vec<[u64; 8]> = Vec::with_capacity(num_pairs); let mut g2_points: Vec<[u64; 16]> = Vec::with_capacity(num_pairs); - + for i in 0..num_pairs { let pair_ptr = pairs.add(i * 192); From 0742578623c6fada201cb77bd0d0ec38d479a02c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:30:28 +0000 Subject: [PATCH 359/782] added bls12_381_fp_to_g1_hint and bls12_381_fp2_to_g2_hint hint handlers --- precompiles/hints/src/hints_processor.rs | 7 +++-- ziskos-hints/src/handlers/bls381.rs | 36 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 20c66ccaf..85ed89e0a 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -15,7 +15,8 @@ use tracing::{debug, info}; use zisk_common::io::{StreamProcessor, StreamSink}; use zisk_common::{BuiltInHint, CtrlHint, HintCode, PrecompileHint}; use ziskos_hints::handlers::bls381::{ - bls12_381_g1_add_hint, bls12_381_g1_msm_hint, bls12_381_g2_add_hint, bls12_381_g2_msm_hint, + bls12_381_fp2_to_g2_hint, bls12_381_fp_to_g1_hint, bls12_381_g1_add_hint, + bls12_381_g1_msm_hint, bls12_381_g2_add_hint, bls12_381_g2_msm_hint, bls12_381_pairing_check_hint, }; use ziskos_hints::handlers::bn254::{ @@ -646,8 +647,8 @@ impl HintsProcessor { BuiltInHint::Bls12_381G2Add => bls12_381_g2_add_hint(&data), // TODO: check BuiltInHint::Bls12_381G2Msm => bls12_381_g2_msm_hint(&data), // TODO: check BuiltInHint::Bls12_381PairingCheck => bls12_381_pairing_check_hint(&data), // TODO: check - BuiltInHint::Bls12_381FpToG1 => unimplemented!(), - BuiltInHint::Bls12_381Fp2ToG2 => unimplemented!(), + BuiltInHint::Bls12_381FpToG1 => bls12_381_fp_to_g1_hint(&data), + BuiltInHint::Bls12_381Fp2ToG2 => bls12_381_fp2_to_g2_hint(&data), // Modular Exponentiation Hint Codes BuiltInHint::ModExp => modexp_hint(&data), // TODO: check diff --git a/ziskos-hints/src/handlers/bls381.rs b/ziskos-hints/src/handlers/bls381.rs index d220d82c1..113309730 100644 --- a/ziskos-hints/src/handlers/bls381.rs +++ b/ziskos-hints/src/handlers/bls381.rs @@ -135,3 +135,39 @@ pub fn bls12_381_pairing_check_hint(data: &[u64]) -> Result> { Ok(hints) } + +/// Processes an `HINT_BLS12_381_FP_TO_G1` hint. +#[inline] +pub fn bls12_381_fp_to_g1_hint(data: &[u64]) -> Result> { + hint_fields![FP: 6]; + + validate_hint_length(data, EXPECTED_LEN, "HINT_BLS12_381_FP_TO_G1")?; + + let fp: &[u64; FP_SIZE] = data[FP_OFFSET..FP_OFFSET + FP_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + let result: &mut [u8; 96] = &mut [0u8; 96]; + unsafe { + zisklib::bls12_381_fp_to_g1_c(result.as_mut_ptr(), fp.as_ptr() as *const u8, &mut hints); + } + + Ok(hints) +} + +/// Processes an `HINT_BLS12_381_FP2_TO_G2` hint. +#[inline] +pub fn bls12_381_fp2_to_g2_hint(data: &[u64]) -> Result> { + hint_fields![FP2: 12]; + + validate_hint_length(data, EXPECTED_LEN, "HINT_BLS12_381_FP2_TO_G2")?; + + let fp2: &[u64; FP2_SIZE] = data[FP2_OFFSET..FP2_OFFSET + FP2_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + let result: &mut [u8; 192] = &mut [0u8; 192]; + unsafe { + zisklib::bls12_381_fp2_to_g2_c(result.as_mut_ptr(), fp2.as_ptr() as *const u8, &mut hints); + } + + Ok(hints) +} From 4587d88b2f1a9b857c92ba2bf6e9eb048f1623c0 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:45:15 +0000 Subject: [PATCH 360/782] improve hint stream creation --- cli/src/commands/execute.rs | 17 ++++++++--------- cli/src/commands/prove.rs | 18 ++++++++++++------ cli/src/commands/stats.rs | 18 ++++++++++++------ cli/src/commands/verify_constraints.rs | 17 ++++++++--------- 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index bc91ddf5f..a2f4749b8 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -93,16 +93,15 @@ impl ZiskExecute { let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = if self.hints.is_some() { - let hints_stream = StreamSource::from_uri(self.hints.as_ref().unwrap())?; - - if matches!(hints_stream, StreamSource::Quic(_)) { - return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); + let hints_stream = match self.hints.as_ref() { + Some(uri) => { + let stream = StreamSource::from_uri(uri)?; + if matches!(stream, StreamSource::Quic(_)) { + anyhow::bail!("QUIC hints source is not supported for execution."); + } + Some(stream) } - - Some(hints_stream) - } else { - None + None => None, }; let emulator = if cfg!(target_os = "macos") { diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index d091186d3..535effb18 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -142,11 +142,16 @@ impl ZiskProve { let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.hints.as_ref().unwrap())?; - - if matches!(hints_stream, StreamSource::Quic(_)) { - return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); - } + let hints_stream = match self.hints.as_ref() { + Some(uri) => { + let stream = StreamSource::from_uri(uri)?; + if matches!(stream, StreamSource::Quic(_)) { + anyhow::bail!("QUIC hints source is not supported for execution."); + } + Some(stream) + } + None => None, + }; let emulator = if cfg!(target_os = "macos") { if !self.emulator { @@ -160,7 +165,7 @@ impl ZiskProve { let (result, world_rank) = if emulator { self.run_emu(stdin, gpu_params)? } else { - self.run_asm(stdin, Some(hints_stream), gpu_params)? + self.run_asm(stdin, hints_stream, gpu_params)? }; if world_rank == 0 { @@ -235,6 +240,7 @@ impl ZiskProve { .verify_proofs(self.verify_proofs) .minimal_memory(self.minimal_memory) .gpu(gpu_params) + .with_hints(hints_stream.is_some()) .print_command_info() .build()?; diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index fa6bd89ad..d06745598 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -112,11 +112,16 @@ impl ZiskStats { let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = StreamSource::from_uri(self.hints.as_ref().unwrap())?; - - if matches!(hints_stream, StreamSource::Quic(_)) { - return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); - } + let hints_stream = match self.hints.as_ref() { + Some(uri) => { + let stream = StreamSource::from_uri(uri)?; + if matches!(stream, StreamSource::Quic(_)) { + anyhow::bail!("QUIC hints source is not supported for execution."); + } + Some(stream) + } + None => None, + }; let emulator = if cfg!(target_os = "macos") { if !self.emulator { @@ -128,7 +133,7 @@ impl ZiskStats { }; let (world_rank, n_processes, stats) = - if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, Some(hints_stream))? }; + if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, hints_stream)? }; if world_rank % 2 == 1 { std::thread::sleep(std::time::Duration::from_millis(2000)); @@ -179,6 +184,7 @@ impl ZiskStats { .asm_path_opt(self.asm.clone()) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) + .with_hints(hints_stream.is_some()) .print_command_info() .build()?; diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index ed5138ec2..158f5578e 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -98,16 +98,15 @@ impl ZiskVerifyConstraints { let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; - let hints_stream = if self.hints.is_some() { - let hints_stream = StreamSource::from_uri(self.hints.as_ref().unwrap())?; - - if matches!(hints_stream, StreamSource::Quic(_)) { - return Err(anyhow::anyhow!("QUIC hints source is not supported for execution.")); + let hints_stream = match self.hints.as_ref() { + Some(uri) => { + let stream = StreamSource::from_uri(uri)?; + if matches!(stream, StreamSource::Quic(_)) { + anyhow::bail!("QUIC hints source is not supported for execution."); + } + Some(stream) } - - Some(hints_stream) - } else { - None + None => None, }; let emulator = if cfg!(target_os = "macos") { From 387ea043c96d7aaea494f3f924677221ad06fdb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Wed, 28 Jan 2026 16:28:44 +0100 Subject: [PATCH 361/782] Fix/process data mem (#730) * Improving process_data for precompileds with memory * Removing MemCollectorsInfo * Suggestions copilot --- Cargo.lock | 3 + common/src/bus/bus_device.rs | 58 +---- common/src/bus/bus_device_metrics.rs | 30 +-- common/src/bus/data_bus_mem.rs | 20 -- common/src/component/component_builder.rs | 20 +- common/src/component/component_instance.rs | 35 +-- common/src/regular_counters.rs | 55 ++--- data-bus/src/data_bus.rs | 174 +------------- data-bus/src/data_bus_file.rs | 140 ----------- data-bus/src/data_bus_player.rs | 47 ---- data-bus/src/lib.rs | 4 - executor/Cargo.toml | 1 + executor/src/dummy_counter.rs | 30 +-- executor/src/static_data_bus.rs | 78 ++---- executor/src/static_data_bus_collect.rs | 211 ++++------------ .../arith_eq/src/arith_eq_bus_device.rs | 156 ++++++------ precompiles/arith_eq/src/arith_eq_instance.rs | 24 +- precompiles/arith_eq/src/arith_eq_manager.rs | 17 +- .../arith_eq/src/mem_inputs/arith256.rs | 16 +- .../arith_eq/src/mem_inputs/arith256_mod.rs | 16 +- .../src/mem_inputs/bn254_complex_add.rs | 16 +- .../src/mem_inputs/bn254_complex_mul.rs | 16 +- .../src/mem_inputs/bn254_complex_sub.rs | 16 +- .../src/mem_inputs/bn254_curve_add.rs | 17 +- .../src/mem_inputs/bn254_curve_dbl.rs | 17 +- .../src/mem_inputs/generate_mem_inputs.rs | 30 +-- .../arith_eq/src/mem_inputs/secp256k1_add.rs | 16 +- .../arith_eq/src/mem_inputs/secp256k1_dbl.rs | 16 +- .../src/arith_eq_384_bus_device.rs | 135 +++++------ .../arith_eq_384/src/arith_eq_384_instance.rs | 24 +- .../arith_eq_384/src/arith_eq_384_manager.rs | 17 +- .../src/mem_inputs/arith384_mod.rs | 15 +- .../src/mem_inputs/bls12_381_complex_add.rs | 15 +- .../src/mem_inputs/bls12_381_complex_mul.rs | 15 +- .../src/mem_inputs/bls12_381_complex_sub.rs | 15 +- .../src/mem_inputs/bls12_381_curve_add.rs | 15 +- .../src/mem_inputs/bls12_381_curve_dbl.rs | 15 +- .../src/mem_inputs/generate_mem_inputs.rs | 28 +-- precompiles/big_int/src/add256_bus_device.rs | 108 ++++----- .../big_int/src/add256_gen_mem_inputs.rs | 38 ++- precompiles/big_int/src/add256_instance.rs | 24 +- precompiles/big_int/src/add256_manager.rs | 17 +- precompiles/common/Cargo.toml | 2 + precompiles/common/src/lib.rs | 226 ++++++++++++------ precompiles/dma/src/dma/dma_collector.rs | 27 +-- .../dma_64_aligned_collector.rs | 24 +- precompiles/dma/src/dma_bus_device.rs | 104 ++++---- precompiles/dma/src/dma_gen_mem_inputs.rs | 70 +++--- precompiles/dma/src/dma_manager.rs | 17 +- .../dma_pre_post/dma_pre_post_collector.rs | 25 +- .../dma_unaligned/dma_unaligned_collector.rs | 24 +- precompiles/keccakf/src/keccakf_bus_device.rs | 108 ++++----- .../keccakf/src/keccakf_gen_mem_inputs.rs | 19 +- precompiles/keccakf/src/keccakf_instance.rs | 29 +-- precompiles/keccakf/src/keccakf_manager.rs | 17 +- .../poseidon2/src/poseidon2_bus_device.rs | 108 ++++----- .../poseidon2/src/poseidon2_gen_mem_inputs.rs | 20 +- .../poseidon2/src/poseidon2_instance.rs | 24 +- .../poseidon2/src/poseidon2_manager.rs | 17 +- precompiles/sha256f/src/sha256f_bus_device.rs | 108 ++++----- .../sha256f/src/sha256f_gen_mem_inputs.rs | 30 +-- precompiles/sha256f/src/sha256f_instance.rs | 24 +- precompiles/sha256f/src/sha256f_manager.rs | 17 +- state-machines/arith/src/arith.rs | 21 +- state-machines/arith/src/arith_bus_device.rs | 55 ++--- .../arith/src/arith_full_instance.rs | 29 +-- state-machines/binary/src/binary.rs | 11 +- .../binary/src/binary_add_collector.rs | 26 +- .../binary/src/binary_basic_collector.rs | 27 +-- state-machines/binary/src/binary_counter.rs | 58 ++--- .../binary/src/binary_extension_collector.rs | 27 +-- state-machines/main/src/main_counter.rs | 42 +--- state-machines/main/src/main_sm.rs | 7 +- state-machines/mem-common/src/mem_counters.rs | 49 ++-- state-machines/mem/src/mem.rs | 6 +- state-machines/mem/src/mem_align_collector.rs | 22 +- .../mem/src/mem_module_collector.rs | 35 ++- state-machines/mem/src/mem_test.rs | 64 +---- state-machines/rom/src/rom.rs | 11 +- state-machines/rom/src/rom_instance.rs | 24 +- 80 files changed, 1023 insertions(+), 2291 deletions(-) delete mode 100644 data-bus/src/data_bus_file.rs delete mode 100644 data-bus/src/data_bus_player.rs diff --git a/Cargo.lock b/Cargo.lock index 0f2a36164..09814d1a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1336,6 +1336,7 @@ dependencies = [ "precomp-keccakf", "precomp-poseidon2", "precomp-sha256f", + "precompiles-common", "proofman", "proofman-common", "proofman-util", @@ -3039,6 +3040,8 @@ name = "precompiles-common" version = "0.16.0" dependencies = [ "fields", + "mem-common", + "sm-mem", "zisk-common", "zisk-core", ] diff --git a/common/src/bus/bus_device.rs b/common/src/bus/bus_device.rs index 1a66430a4..26eef3841 100644 --- a/common/src/bus/bus_device.rs +++ b/common/src/bus/bus_device.rs @@ -1,7 +1,4 @@ -use std::{any::Any, collections::VecDeque}; - -use super::BusId; -use crate::MemCollectorInfo; +use std::any::Any; /// Represents a subscriber in the `DataBus` system. /// @@ -11,59 +8,6 @@ use crate::MemCollectorInfo; /// # Associated Type /// * `D` - The type of data handled by the `BusDevice`. pub trait BusDevice: Any + Send + Sync { - /// Processes incoming data sent to the device. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus that sent the data. - /// * `data` - A reference to the data payload being processed. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - fn process_data( - &mut self, - bus_id: &BusId, - data: &[D], - data_ext: &[D], - pending: &mut VecDeque<(BusId, Vec, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool; - - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec; - /// Converts the device to a generic `Any` type. fn as_any(self: Box) -> Box; - - /// Performs any necessary cleanup or finalization when the metrics instance is closed. - fn on_close(&mut self) {} -} - -impl BusDevice for Box> { - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - data_ext: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - (**self).process_data(bus_id, data, data_ext, pending, mem_collector_info) - } - - fn bus_id(&self) -> Vec { - (**self).bus_id() - } - - fn as_any(self: Box) -> Box { - (*self).as_any() - } - - fn on_close(&mut self) { - (**self).on_close() - } } diff --git a/common/src/bus/bus_device_metrics.rs b/common/src/bus/bus_device_metrics.rs index 86894cb24..d9bcea5cf 100644 --- a/common/src/bus/bus_device_metrics.rs +++ b/common/src/bus/bus_device_metrics.rs @@ -2,11 +2,8 @@ //! of `BusDevice` and `Metrics`, providing a unified interface for monitoring and managing //! bus operations with associated metrics. -use std::{any::Any, collections::VecDeque}; +use super::BusDevice; -use super::{BusDevice, BusId}; - -use crate::MemCollectorInfo; use crate::Metrics; #[derive(Debug, PartialEq)] @@ -23,31 +20,6 @@ pub enum BusDeviceMode { /// maintaining compatibility with `Metrics` functionality. pub trait BusDeviceMetrics: BusDevice + Metrics + std::any::Any {} -impl BusDevice for Box { - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - data_ext: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - (**self).process_data(bus_id, data, data_ext, pending, mem_collector_info) - } - - fn bus_id(&self) -> Vec { - (**self).bus_id() - } - - fn as_any(self: Box) -> Box { - (*self).as_any() - } - - fn on_close(&mut self) { - (**self).on_close() - } -} - /// Blanket implementation of `BusDeviceMetrics` for any type implementing `BusDevice`, /// `Metrics`, and `std::any::Any`. impl + Metrics + std::any::Any> BusDeviceMetrics for T {} diff --git a/common/src/bus/data_bus_mem.rs b/common/src/bus/data_bus_mem.rs index 0d6c7467d..13064a936 100644 --- a/common/src/bus/data_bus_mem.rs +++ b/common/src/bus/data_bus_mem.rs @@ -50,23 +50,3 @@ impl MemBusData { [data[MEM_VALUE_0], data[MEM_VALUE_1]] } } - -pub struct MemCollectorInfo { - pub from_addr: u32, - pub to_addr: u32, -} - -impl MemCollectorInfo { - pub fn skip_addr(&self, addr: u32) -> bool { - if addr > self.to_addr || addr < self.from_addr { - return true; - } - false - } - pub fn skip_addr_range(&self, addr_from: u32, addr_to: u32) -> bool { - if addr_from > self.to_addr || addr_to < self.from_addr { - return true; - } - false - } -} diff --git a/common/src/component/component_builder.rs b/common/src/component/component_builder.rs index 83c6da267..7ad5fc3bd 100644 --- a/common/src/component/component_builder.rs +++ b/common/src/component/component_builder.rs @@ -4,7 +4,7 @@ //! This trait provides methods to create counters, planners, input collectors, and optional //! input generators, enabling flexible and modular integration of components. -use crate::{BusDevice, BusDeviceMetrics, Instance, InstanceCtx, PayloadType, Plan, Planner}; +use crate::{Instance, InstanceCtx, Plan, Planner}; use fields::PrimeField64; use proofman_common::ProofCtx; @@ -15,12 +15,6 @@ use proofman_common::ProofCtx; /// * `F` - A type that implements the `PrimeField64` trait, representing the field over which /// operations are performed. pub trait ComponentBuilder: Send + Sync { - /// Builds and returns a bus device counter for monitoring metrics. - /// - /// # Returns - /// A boxed implementation of `BusDeviceMetrics`, capable of tracking bus data. - fn build_counter(&self) -> Option>; - /// Builds a planner for planning execution instances. /// /// # Returns @@ -40,16 +34,4 @@ pub trait ComponentBuilder: Send + Sync { /// # Arguments /// * `ictx` - The instance context used to create the instance. fn build_instance(&self, ictx: InstanceCtx) -> Box>; - - /// Optionally creates an input generator for producing inputs to be sent back to the bus. - /// - /// # Returns - /// An `Option` containing a boxed implementation of `BusDevice`, or `None` if the component - /// does not support input generation. - /// - /// # Default Implementation - /// Returns `None` by default, indicating no input generator is provided. - fn build_inputs_generator(&self) -> Option>> { - None - } } diff --git a/common/src/component/component_instance.rs b/common/src/component/component_instance.rs index 0d00bf3a9..13f601ceb 100644 --- a/common/src/component/component_instance.rs +++ b/common/src/component/component_instance.rs @@ -129,6 +129,10 @@ macro_rules! table_instance { pub fn new(table_sm: Arc<$TableSM>, ictx: InstanceCtx, bus_id: BusId) -> Self { Self { table_sm, ictx, bus_id } } + + pub fn process_data(&mut self, _bus_id: &BusId, _data: &[u64]) -> bool { + true + } } impl Instance for $InstanceName { @@ -180,19 +184,6 @@ macro_rules! table_instance { } impl BusDevice for $InstanceName { - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - true - } - fn bus_id(&self) -> Vec { - vec![self.bus_id] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self @@ -247,6 +238,10 @@ macro_rules! table_instance_array { pub fn new(table_sm: Arc<$TableSM>, ictx: InstanceCtx, bus_id: BusId) -> Self { Self { table_sm, ictx, bus_id } } + + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { + true + } } impl Instance for $InstanceName { @@ -307,20 +302,6 @@ macro_rules! table_instance_array { } impl BusDevice for $InstanceName { - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - true - } - - fn bus_id(&self) -> Vec { - vec![self.bus_id] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/common/src/regular_counters.rs b/common/src/regular_counters.rs index 5382bb9d7..6561ee33c 100644 --- a/common/src/regular_counters.rs +++ b/common/src/regular_counters.rs @@ -2,9 +2,8 @@ //! sent over the data bus. It is designed to be reusable across multiple state machines //! and collects metrics for specified `ZiskOperationType` instructions. -use crate::MemCollectorInfo; use crate::{BusDevice, BusId, Counter, ExtOperationData, Metrics, OperationBusData}; -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; use zisk_core::ZiskOperationType; /// The `RegularCounters` struct represents a generic counter that monitors and measures @@ -50,6 +49,24 @@ impl RegularCounters { } None } + + /// Processes data received on the bus, updating counters. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { + debug_assert!(*bus_id == self.bus_id); + + self.measure(data); + + true + } } impl Metrics for RegularCounters { @@ -111,40 +128,6 @@ impl Add for RegularCounters { } impl BusDevice for RegularCounters { - /// Processes data received on the bus, updating counters. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus sending the data. - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - debug_assert!(*bus_id == self.bus_id); - - self.measure(data); - - true - } - - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![self.bus_id] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/data-bus/src/data_bus.rs b/data-bus/src/data_bus.rs index d06a6c1a7..71c44c8d1 100644 --- a/data-bus/src/data_bus.rs +++ b/data-bus/src/data_bus.rs @@ -3,9 +3,7 @@ //! omnipresent devices that process all data sent to the bus. This module provides mechanisms to //! send data, route it to the appropriate subscribers, and manage device connections. -use std::collections::VecDeque; - -use zisk_common::{BusDevice, BusId}; +use zisk_common::BusId; pub trait DataBusTrait { /// Writes data to the bus and processes it through the registered devices. @@ -24,173 +22,3 @@ pub trait DataBusTrait { fn into_devices(self, execute_on_close: bool) -> Vec<(Option, Option)>; } - -/// A bus system facilitating communication between multiple publishers and subscribers. -/// -/// The `DataBus` allows devices to register for specific bus IDs or act as global (omni) devices. -/// It routes payloads to registered devices and handles data transfers efficiently. -/// -/// # Type Parameters -/// * `D` - The type of data payloads handled by the bus. -/// * `BD` - The type of devices (subscribers) connected to the bus, implementing the `BusDevice` -/// trait. -pub struct DataBus> { - /// List of devices connected to the bus. - devices: Vec<(Option, BD)>, - - /// Mapping from `BusId` to indices of devices listening to that ID. - devices_bus_id_map: Vec>, - - /// Queue of pending data transfers to be processed. - pending_transfers: VecDeque<(BusId, Vec, Vec)>, - - /// Indices of devices that are connected to the bus but without a specific instance. - none_devices: Vec, - - /// The number of active devices currently connected to the bus. - active_devices: usize, -} - -impl> Default for DataBus { - /// Creates a new `DataBus` with default settings. - fn default() -> Self { - Self::new() - } -} - -impl> DataBus { - /// Creates a new `DataBus` instance. - pub fn new() -> Self { - Self { - devices: Vec::new(), - devices_bus_id_map: vec![vec![], vec![], vec![]], - pending_transfers: VecDeque::new(), - none_devices: vec![], - active_devices: 0, - } - } - - /// Connects a device to the bus with specific `BusId` subscriptions. - /// - /// # Arguments - /// * `instance_idx` - An optional index for the device instance. - /// * `bus_device` - The device to be added to the bus. - pub fn connect_device(&mut self, instance_idx: Option, bus_device: Option) { - if let Some(bus_device) = bus_device { - let bus_ids = bus_device.bus_id(); - - self.devices.push((instance_idx, bus_device)); - let device_idx = self.devices.len() - 1; - - for bus_id in bus_ids { - self.devices_bus_id_map[*bus_id].push(device_idx); - } - self.active_devices += 1; - } else { - self.none_devices.push(self.devices.len()); - } - } - - /// Routes data to the devices subscribed to a specific bus ID or global devices. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus to route the data to. - /// * `payload` - A reference to the data payload being routed. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn route_data(&mut self, bus_id: BusId, data: &[D], data_ext: &[D]) { - let devices_idx = &mut self.devices_bus_id_map[*bus_id]; - let mut i = 0; - - while i < devices_idx.len() { - let device_idx = devices_idx[i]; - // When a device returns false, it indicates that it has finished its work and should be disabled. - if !self.devices[device_idx].1.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ) { - // Remove the device from the bus and update the mapping. - devices_idx.swap_remove(i); - self.active_devices -= 1; - } else { - i += 1; - } - } - } - - /// Outputs the current state of the bus for debugging purposes. - pub fn debug_state(&self) { - println!("Devices: {:?}", self.devices.len()); - println!("Devices by bus ID: {:?}", self.devices_bus_id_map); - println!("Pending Transfers: {:?}", self.pending_transfers.len()); - } -} - -impl> DataBusTrait for DataBus { - /// Writes data to the bus and processes it through the registered devices. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus receiving the data. - /// * `payload` - The data payload to be sent. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn write_to_bus(&mut self, bus_id: BusId, data: &[D], data_ext: &[D]) -> bool { - self.route_data(bus_id, data, data_ext); - - while let Some((bus_id, data, data_ext)) = self.pending_transfers.pop_front() { - self.route_data(bus_id, &data, &data_ext); - } - - self.active_devices > 0 - } - - /// Called when the bus is closed, allowing devices to perform any necessary cleanup. - fn on_close(&mut self) { - for device in &mut self.devices { - device.1.on_close(); - } - } - - /// Converts the bus into a vector of devices, optionally executing their close operations. - /// - /// # Arguments - /// * `execute_on_close` - If true, calls the `on_close` method on each device. - /// - //// # Returns - /// A vector of tuples containing the device instance index and the device itself. - fn into_devices(self, execute_on_close: bool) -> Vec<(Option, Option)> { - let total_len = self.devices.len() + self.none_devices.len(); - let mut result = Vec::with_capacity(total_len); - - let mut dev_iter = self.devices.into_iter(); - let mut none_iter = self.none_devices.iter().copied().peekable(); - - for idx in 0..total_len { - if Some(&idx) == none_iter.peek() { - result.push((None, None)); - none_iter.next(); - } else { - let mut device = - dev_iter.next().expect("Mismatch between device and none-device count"); - - if execute_on_close { - device.1.on_close(); - } - - result.push((device.0, Some(device.1))); - } - } - - result - } -} diff --git a/data-bus/src/data_bus_file.rs b/data-bus/src/data_bus_file.rs deleted file mode 100644 index 620adc031..000000000 --- a/data-bus/src/data_bus_file.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! A module for reading and writing DataBus information to a file. -//! -//! The `DataBusFileReader` struct provides a utility for reading DataBus information from a plain -//! text file. The `DataBusFileWriter` struct provides a utility for writing DataBus information to -//! a file. - -use std::{ - fs::File, - io::{self, Read, Write}, - str::FromStr, -}; - -use zisk_common::BusId; - -pub type ReadFromFileData = Vec<(BusId, Vec, Vec)>; -pub struct DataBusFileReader; - -impl DataBusFileReader { - /// Reads data from a plain text file and returns a vector of `(BusId, Payload)` tuples. - /// - /// # File Format - /// Each line in the file should be formatted as: - /// ```text - /// ... - /// ``` - /// - ``: A 16-bit unsigned integer representing the bus ID. - /// - ``: A list of payload values convertible to the type `D`. - /// - /// # Arguments - /// * `file_path` - The path to the plain text file. - /// - /// # Returns - /// * `Result)>, io::Error>`: A vector of `(BusId, Payload)` tuples or an error - /// if the file cannot be read or the data format is invalid. - /// - /// # Errors - /// - Returns an error if the file cannot be opened or read. - /// - Returns an error if any line is malformed (missing `BusId` or invalid payload values). - pub fn read_from_file(file_path: &str) -> Result, io::Error> - where - D::Err: std::fmt::Display, - { - let mut file = File::open(file_path)?; - let mut content = String::new(); - file.read_to_string(&mut content)?; - - // Estimate the number of lines for pre-allocation - let estimated_lines = content.lines().count(); - let mut data = Vec::with_capacity(estimated_lines); - - for (line_number, line) in content.lines().enumerate() { - let mut parts = line.split_whitespace(); - - // Parse the BusId (first token) - let bus_id = parts - .next() - .ok_or_else(|| { - io::Error::new( - io::ErrorKind::InvalidData, - format!("Missing BusId on line {}", line_number + 1), - ) - })? - .parse::() - .map_err(|err| { - io::Error::new( - io::ErrorKind::InvalidData, - format!("Invalid BusId on line {}: {}", line_number + 1, err), - ) - })?; - - // Pre-allocate payload size if possible - let mut payload = Vec::with_capacity(parts.clone().count()); - - for token in parts { - let value = token.parse::().map_err(|err| { - io::Error::new( - io::ErrorKind::InvalidData, - format!("Invalid payload on line {}: {}", line_number + 1, err), - ) - })?; - payload.push(value); - } - - // Push the parsed data into the pre-allocated vector - data.push((BusId(bus_id), payload, vec![])); - } - - Ok(data) - } -} - -/// A utility struct for writing DataBus information to a file. -pub struct DataBusFileWriter { - file: Option, -} - -impl DataBusFileWriter { - /// Creates a new `DataBusFileWriter` and opens the specified file for writing. - /// - /// # Arguments - /// * `file_path` - The path to the file where data will be written. - /// - /// # Returns - /// A new instance of `DataBusFileWriter`. - pub fn new(file_path: &str) -> Result { - let file = File::create(file_path)?; - Ok(Self { file: Some(file) }) - } - - /// Writes a single `(BusId, Payload)` line to the file. - /// - /// # Arguments - /// * `bus_id` - The BusId to write. - /// * `payload` - A vector of payload items to write. - pub fn write(&mut self, bus_id: u16, payload: &[D]) -> Result<(), io::Error> { - if let Some(file) = self.file.as_mut() { - let payload_str: String = - payload.iter().map(|item| item.to_string()).collect::>().join(" "); - writeln!(file, "{bus_id} {payload_str}")?; - Ok(()) - } else { - Err(io::Error::other("Attempted to write to a closed file.")) - } - } - - /// Closes the file, ensuring all data is flushed to disk. - pub fn close(&mut self) -> Result<(), io::Error> { - if let Some(mut file) = self.file.take() { - file.flush()?; // Ensure all buffered data is written - } - Ok(()) - } -} - -impl Drop for DataBusFileWriter { - /// Ensures the file is closed when the `DataBusFileWriter` is dropped. - fn drop(&mut self) { - let _ = self.close(); // Silently ignore any errors during drop - } -} diff --git a/data-bus/src/data_bus_player.rs b/data-bus/src/data_bus_player.rs deleted file mode 100644 index 2a997388c..000000000 --- a/data-bus/src/data_bus_player.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! A player for replaying data on the `DataBus`. - -use std::{io, str::FromStr}; - -use zisk_common::{BusDevice, BusId}; - -use crate::{DataBus, DataBusFileReader, DataBusTrait}; - -pub struct DataBusPlayer; - -impl DataBusPlayer { - /// Plays data on the `DataBus` from a provided data vector. - /// - /// # Arguments - /// * `data_bus` - The `DataBus` to which the data is sent. - /// * `data` - A vector of `(BusId, Payload)` tuples. - pub fn play>( - data_bus: &mut DataBus, - data: Vec<(BusId, Vec, Vec)>, - ) { - for (bus_id, data, data_ext) in data { - as DataBusTrait>::write_to_bus( - data_bus, bus_id, &data, &data_ext, - ); - } - } - - /// Plays data on the `DataBus` from a file using `DataBusFileReader`. - /// - /// # Arguments - /// * `file_path` - The path to the file containing the data. - /// * `data_bus` - The `DataBus` to which the data is sent. - /// - /// # Returns - /// * `Result<(), io::Error>` indicating success or failure during file reading and playing. - pub fn play_from_file>( - data_bus: &mut DataBus, - file_path: &str, - ) -> Result<(), io::Error> - where - D::Err: std::fmt::Display, - { - let data = DataBusFileReader::read_from_file::(file_path)?; - Self::play(data_bus, data); - Ok(()) - } -} diff --git a/data-bus/src/lib.rs b/data-bus/src/lib.rs index 4ff1159d1..f4f8ec56f 100644 --- a/data-bus/src/lib.rs +++ b/data-bus/src/lib.rs @@ -1,7 +1,3 @@ mod data_bus; -mod data_bus_file; -mod data_bus_player; pub use data_bus::*; -pub use data_bus_file::*; -pub use data_bus_player::*; diff --git a/executor/Cargo.toml b/executor/Cargo.toml index bb959d1d3..3b14088a1 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -31,6 +31,7 @@ rayon = { workspace = true } pil-std-lib = { workspace = true } crossbeam = "0.8.4" +precompiles-common = { workspace = true } precomp-keccakf = { workspace = true } precomp-sha256f = { workspace = true } precomp-poseidon2 = { workspace = true } diff --git a/executor/src/dummy_counter.rs b/executor/src/dummy_counter.rs index 4cf41795c..4be2099a5 100644 --- a/executor/src/dummy_counter.rs +++ b/executor/src/dummy_counter.rs @@ -3,9 +3,9 @@ //! This counter is used as a default implementation when no actual counting or metrics //! collection is required. -use std::{any::Any, collections::VecDeque}; +use std::any::Any; -use zisk_common::{BusDevice, BusId, MemCollectorInfo, Metrics}; +use zisk_common::{BusDevice, Metrics}; /// The `DummyCounter` struct serves as a placeholder counter that performs no actions /// when connected to the data bus. @@ -15,6 +15,12 @@ use zisk_common::{BusDevice, BusId, MemCollectorInfo, Metrics}; #[derive(Default)] pub struct DummyCounter {} +impl DummyCounter { + #[inline(always)] + pub fn process_data(&mut self) -> bool { + true + } +} impl Metrics for DummyCounter { /// Does nothing when tracking activity on the bus. /// @@ -36,26 +42,6 @@ impl Metrics for DummyCounter { } impl BusDevice for DummyCounter { - #[inline(always)] - fn process_data( - &mut self, - _bus_id: &BusId, - _data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - true - } - - /// Returns an empty vector as this counter is not associated with any bus IDs. - /// - /// # Returns - /// An empty vector of bus IDs. - fn bus_id(&self) -> Vec { - vec![] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/executor/src/static_data_bus.rs b/executor/src/static_data_bus.rs index e510481a1..a1a6e4468 100644 --- a/executor/src/static_data_bus.rs +++ b/executor/src/static_data_bus.rs @@ -14,10 +14,11 @@ use precomp_dma::DmaCounterInputGen; use precomp_keccakf::KeccakfCounterInputGen; use precomp_poseidon2::Poseidon2CounterInputGen; use precomp_sha256f::Sha256fCounterInputGen; +use precompiles_common::MemCounterProcessor; use sm_arith::ArithCounterInputGen; use sm_binary::BinaryCounter; use sm_main::MainCounter; -use zisk_common::{BusDevice, BusDeviceMetrics, BusId, PayloadType, MEM_BUS_ID, OPERATION_BUS_ID}; +use zisk_common::{BusDeviceMetrics, BusId, PayloadType, MEM_BUS_ID, OPERATION_BUS_ID}; use zisk_core::{ ARITH_EQ_384_OP_TYPE_ID, ARITH_EQ_OP_TYPE_ID, ARITH_OP_TYPE_ID, BIG_INT_OP_TYPE_ID, BINARY_E_OP_TYPE_ID, BINARY_OP_TYPE_ID, DMA_OP_TYPE_ID, KECCAK_OP_TYPE_ID, @@ -112,87 +113,54 @@ impl StaticDataBus { if !self.process_only_operation_bus { if let Some(mem_counter) = self.mem_counter.1.as_mut() { // If we are not processing only operation bus, we process memory bus data. - _continue &= mem_counter.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + _continue &= mem_counter.process_data(&bus_id, data); } } _continue } OPERATION_BUS_ID => match data[1] as u32 { - PUB_OUT_OP_TYPE_ID => self.main_counter.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ), - BINARY_OP_TYPE_ID | BINARY_E_OP_TYPE_ID => self.binary_counter.1.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ), - ARITH_OP_TYPE_ID => self.arith_counter.1.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ), + PUB_OUT_OP_TYPE_ID => self.main_counter.process_data(&bus_id, data), + BINARY_OP_TYPE_ID | BINARY_E_OP_TYPE_ID => { + self.binary_counter.1.process_data(&bus_id, data) + } + ARITH_OP_TYPE_ID => { + self.arith_counter.1.process_data(&bus_id, data, &mut self.pending_transfers) + } KECCAK_OP_TYPE_ID => self.keccakf_counter.1.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - None, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), SHA256_OP_TYPE_ID => self.sha256f_counter.1.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - None, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), POSEIDON2_OP_TYPE_ID => self.poseidon2_counter.1.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - None, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), ARITH_EQ_OP_TYPE_ID => self.arith_eq_counter.1.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - None, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), ARITH_EQ_384_OP_TYPE_ID => self.arith_eq_384_counter.1.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - None, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), BIG_INT_OP_TYPE_ID => self.add_256_counter.1.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - None, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), DMA_OP_TYPE_ID => self.dma_counter.1.process_data( &bus_id, data, data_ext, - &mut self.pending_transfers, - None, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), _ => true, }, @@ -219,19 +187,9 @@ impl DataBusTrait> for StaticDataBus { /// Queue of pending data transfers to be processed. pending_transfers: VecDeque<(BusId, Vec, Vec)>, - - mem_collectors_info: Vec, } const BINARY_TYPE: u64 = ZiskOperationType::Binary as u64; @@ -134,9 +132,6 @@ impl StaticDataBusCollect { add256_inputs_generator: Add256CounterInputGen, dma_inputs_generator: DmaCounterInputGen, ) -> Self { - let mem_collectors_info: Vec = - mem_collector.iter().map(|(_, collector)| collector.get_mem_collector_info()).collect(); - Self { mem_collector, mem_align_collector, @@ -164,7 +159,6 @@ impl StaticDataBusCollect { add256_inputs_generator, dma_inputs_generator, pending_transfers: VecDeque::with_capacity(64), - mem_collectors_info, } } @@ -182,250 +176,147 @@ impl StaticDataBusCollect { fn route_data(&mut self, bus_id: BusId, data: &[PayloadType], data_ext: &[PayloadType]) { match bus_id { MEM_BUS_ID => { - // Process mem collectors - inverted condition to avoid continue - for (_, mem_collector) in &mut self.mem_collector { - mem_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); - } - - // Only process align collectors if needed - for (_, mem_align_collector) in &mut self.mem_align_collector { - mem_align_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); - } + MemCollectorProcessor::new(&mut self.mem_collector, &mut self.mem_align_collector) + .process_mem_data(&data.try_into().unwrap()); } OPERATION_BUS_ID => match data[OP_TYPE] { BINARY_TYPE => { for (_, binary_add_collector) in &mut self.binary_add_collector { - binary_add_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + binary_add_collector.process_data(&bus_id, data); } for (_, binary_basic_collector) in &mut self.binary_basic_collector { - binary_basic_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + binary_basic_collector.process_data(&bus_id, data); } } BINARY_E_TYPE => { for (_, binary_extension_collector) in &mut self.binary_extension_collector { - binary_extension_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + binary_extension_collector.process_data(&bus_id, data); } } ARITH_TYPE => { for (_, arith_collector) in &mut self.arith_collector { - arith_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + arith_collector.process_data(&bus_id, data); } self.arith_inputs_generator.process_data( &bus_id, data, - data_ext, &mut self.pending_transfers, - None, ); } KECCAK_TYPE => { for (_, keccakf_collector) in &mut self.keccakf_collector { - keccakf_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + keccakf_collector.process_data(&bus_id, data); } self.keccakf_inputs_generator.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } SHA256_TYPE => { for (_, sha256f_collector) in &mut self.sha256f_collector { - sha256f_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + sha256f_collector.process_data(&bus_id, data); } self.sha256f_inputs_generator.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } POSEIDON2_TYPE => { for (_, poseidon2_collector) in &mut self.poseidon2_collector { - poseidon2_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + poseidon2_collector.process_data(&bus_id, data); } self.poseidon2_inputs_generator.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } ARITH_EQ_TYPE => { for (_, arith_eq_collector) in &mut self.arith_eq_collector { - arith_eq_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + arith_eq_collector.process_data(&bus_id, data); } self.arith_eq_inputs_generator.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } ARITH_EQ_384_TYPE => { for (_, arith_eq_384_collector) in &mut self.arith_eq_384_collector { - arith_eq_384_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + arith_eq_384_collector.process_data(&bus_id, data); } self.arith_eq_384_inputs_generator.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } BIG_INT_OP_TYPE_ID => { for (_, add256_collector) in &mut self.add256_collector { - add256_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + add256_collector.process_data(&bus_id, data); } self.add256_inputs_generator.process_data( &bus_id, data, - data_ext, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } DMA_OP_TYPE_ID => { for (_, dma_collector) in &mut self.dma_collector { - dma_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + dma_collector.process_data(&bus_id, data, data_ext); } for (_, dma_pre_post_collector) in &mut self.dma_pre_post_collector { - dma_pre_post_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + dma_pre_post_collector.process_data(&bus_id, data, data_ext); } for (_, dma_64_aligned_collector) in &mut self.dma_64_aligned_collector { - dma_64_aligned_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + dma_64_aligned_collector.process_data(&bus_id, data, data_ext); } for (_, dma_unaligned_collector) in &mut self.dma_unaligned_collector { - dma_unaligned_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + dma_unaligned_collector.process_data(&bus_id, data, data_ext); } self.dma_inputs_generator.process_data( &bus_id, data, data_ext, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } _ => {} }, ROM_BUS_ID => { for (_, rom_collector) in &mut self.rom_collector { - rom_collector.process_data( - &bus_id, - data, - data_ext, - &mut self.pending_transfers, - None, - ); + rom_collector.process_data(&bus_id, data); } } _ => {} @@ -458,13 +349,9 @@ impl DataBusTrait>> fn on_close(&mut self) {} fn into_devices( - mut self, - execute_on_close: bool, + self, + _execute_on_close: bool, ) -> Vec<(Option, Option>>)> { - if execute_on_close { - self.on_close(); - } - let mut result = Vec::new(); // Add all collectors to the result diff --git a/precompiles/arith_eq/src/arith_eq_bus_device.rs b/precompiles/arith_eq/src/arith_eq_bus_device.rs index 19dc40dd8..b88921012 100644 --- a/precompiles/arith_eq/src/arith_eq_bus_device.rs +++ b/precompiles/arith_eq/src/arith_eq_bus_device.rs @@ -2,12 +2,13 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::ArithEq` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; +use precompiles_common::MemProcessor; +use zisk_common::STEP; use zisk_common::{ BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OP, OPERATION_BUS_ID, OP_TYPE, }; -use zisk_common::{MemCollectorInfo, STEP}; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; use crate::mem_inputs::{ @@ -72,94 +73,47 @@ impl ArithEqCounterInputGen { (op_type == ZiskOperationType::ArithEq).then_some(self.counter.inst_count) } - fn skip_data(&self, data: &[u64], mem_collectors_info: &[MemCollectorInfo]) -> bool { + fn skip_data(&self, data: &[u64], mem_processors: &mut P) -> bool { let addr_main = data[B] as u32; match data[OP] as u8 { - ARITH256_OP => skip_arith256_mem_inputs(addr_main, data, mem_collectors_info), - ARITH256_MOD_OP => skip_arith256_mod_mem_inputs(addr_main, data, mem_collectors_info), - SECP256K1_ADD_OP => skip_secp256k1_add_mem_inputs(addr_main, data, mem_collectors_info), - SECP256K1_DBL_OP => skip_secp256k1_dbl_mem_inputs(addr_main, data, mem_collectors_info), - BN254_CURVE_ADD_OP => { - skip_bn254_curve_add_mem_inputs(addr_main, data, mem_collectors_info) - } - BN254_CURVE_DBL_OP => { - skip_bn254_curve_dbl_mem_inputs(addr_main, data, mem_collectors_info) - } + ARITH256_OP => skip_arith256_mem_inputs(addr_main, data, mem_processors), + ARITH256_MOD_OP => skip_arith256_mod_mem_inputs(addr_main, data, mem_processors), + SECP256K1_ADD_OP => skip_secp256k1_add_mem_inputs(addr_main, data, mem_processors), + SECP256K1_DBL_OP => skip_secp256k1_dbl_mem_inputs(addr_main, data, mem_processors), + BN254_CURVE_ADD_OP => skip_bn254_curve_add_mem_inputs(addr_main, data, mem_processors), + BN254_CURVE_DBL_OP => skip_bn254_curve_dbl_mem_inputs(addr_main, data, mem_processors), BN254_COMPLEX_ADD_OP => { - skip_bn254_complex_add_mem_inputs(addr_main, data, mem_collectors_info) + skip_bn254_complex_add_mem_inputs(addr_main, data, mem_processors) } BN254_COMPLEX_SUB_OP => { - skip_bn254_complex_sub_mem_inputs(addr_main, data, mem_collectors_info) + skip_bn254_complex_sub_mem_inputs(addr_main, data, mem_processors) } BN254_COMPLEX_MUL_OP => { - skip_bn254_complex_mul_mem_inputs(addr_main, data, mem_collectors_info) + skip_bn254_complex_mul_mem_inputs(addr_main, data, mem_processors) } _ => { panic!("ArithEqCounterInputGen: Unsupported data length {}", data.len(),); } } } -} - -impl Metrics for ArithEqCounterInputGen { - /// Tracks activity on the connected bus and updates counters for recognized operations. - /// - /// # Arguments - /// * `_bus_id` - The ID of the bus (unused in this implementation). - /// * `_data` - The data received from the bus. - /// - /// # Returns - /// An empty vector, as this implementation does not produce any derived inputs for the bus. - #[inline(always)] - fn measure(&mut self, _data: &[u64]) { - self.counter.update(1); - } - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl Add for ArithEqCounterInputGen { - type Output = ArithEqCounterInputGen; - /// Combines two `Arith256Counter` instances by summing their counters. - /// - /// # Arguments - /// * `self` - The first `Arith256Counter` instance. - /// * `other` - The second `Arith256Counter` instance. - /// - /// # Returns - /// A new `Arith256Counter` with combined counters. - fn add(self, other: Self) -> ArithEqCounterInputGen { - ArithEqCounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } - } -} - -impl BusDevice for ArithEqCounterInputGen { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments /// * `bus_id` - The ID of the bus sending the data. /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// * `mem_processors` – A collection of memory processors used to send derived inputs. /// /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( + pub fn process_data( &mut self, bus_id: &BusId, data: &[u64], - _data_ext: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, + mem_processors: &mut P, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); @@ -169,12 +123,6 @@ impl BusDevice for ArithEqCounterInputGen { return true; } - if let Some(mem_collectors_info) = mem_collector_info { - if self.skip_data(data, mem_collectors_info) { - return true; - } - } - let op = data[OP] as u8; let step_main = data[STEP]; let addr_main = data[B] as u32; @@ -188,12 +136,23 @@ impl BusDevice for ArithEqCounterInputGen { self.measure(data); return true; } - BusDeviceMode::InputGenerator => false, + BusDeviceMode::InputGenerator => { + if self.skip_data(data, mem_processors) { + return true; + } + false + } }; match op { ARITH256_OP => { - generate_arith256_mem_inputs(addr_main, step_main, data, only_counters, pending); + generate_arith256_mem_inputs( + addr_main, + step_main, + data, + only_counters, + mem_processors, + ); } ARITH256_MOD_OP => { generate_arith256_mod_mem_inputs( @@ -201,7 +160,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } SECP256K1_ADD_OP => { @@ -210,7 +169,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } SECP256K1_DBL_OP => { @@ -219,7 +178,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BN254_CURVE_ADD_OP => { @@ -228,7 +187,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BN254_CURVE_DBL_OP => { @@ -237,7 +196,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BN254_COMPLEX_ADD_OP => { @@ -246,7 +205,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BN254_COMPLEX_SUB_OP => { @@ -255,7 +214,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BN254_COMPLEX_MUL_OP => { @@ -264,7 +223,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } @@ -275,15 +234,48 @@ impl BusDevice for ArithEqCounterInputGen { true } +} + +impl Metrics for ArithEqCounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. + /// + /// # Returns + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); + } - /// Returns the bus IDs associated with this counter. + /// Provides a dynamic reference for downcasting purposes. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self } +} +impl Add for ArithEqCounterInputGen { + type Output = ArithEqCounterInputGen; + + /// Combines two `Arith256Counter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `Arith256Counter` instance. + /// * `other` - The second `Arith256Counter` instance. + /// + /// # Returns + /// A new `Arith256Counter` with combined counters. + fn add(self, other: Self) -> ArithEqCounterInputGen { + ArithEqCounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } + } +} + +impl BusDevice for ArithEqCounterInputGen { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/arith_eq/src/arith_eq_instance.rs b/precompiles/arith_eq/src/arith_eq_instance.rs index 4e0b2127a..9226085ad 100644 --- a/precompiles/arith_eq/src/arith_eq_instance.rs +++ b/precompiles/arith_eq/src/arith_eq_instance.rs @@ -11,12 +11,11 @@ use crate::{ }; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::collections::VecDeque; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, OperationBusData, PayloadType, OPERATION_BUS_ID, + InstanceType, OperationBusData, PayloadType, OPERATION_BUS_ID, }; use zisk_core::ZiskOperationType; @@ -159,9 +158,7 @@ impl ArithEqCollector { collect_skipper, } } -} -impl BusDevice for ArithEqCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -173,14 +170,7 @@ impl BusDevice for ArithEqCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -232,15 +222,9 @@ impl BusDevice for ArithEqCollector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for ArithEqCollector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/arith_eq/src/arith_eq_manager.rs b/precompiles/arith_eq/src/arith_eq_manager.rs index 9b3890cef..86bc95250 100644 --- a/precompiles/arith_eq/src/arith_eq_manager.rs +++ b/precompiles/arith_eq/src/arith_eq_manager.rs @@ -2,11 +2,8 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{BusDevice, PayloadType}; -use zisk_common::{ - BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::ArithEqTrace; @@ -44,14 +41,6 @@ impl ArithEqManager { } impl ComponentBuilder for ArithEqManager { - /// Builds and returns a new counter for monitoring arith256 operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for arith256 operations. - fn build_counter(&self) -> Option> { - Some(Box::new(ArithEqCounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan arith256-related instances. /// /// # Returns @@ -89,8 +78,4 @@ impl ComponentBuilder for ArithEqManager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(ArithEqCounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/precompiles/arith_eq/src/mem_inputs/arith256.rs b/precompiles/arith_eq/src/mem_inputs/arith256.rs index e00c95932..e47286031 100644 --- a/precompiles/arith_eq/src/mem_inputs/arith256.rs +++ b/precompiles/arith_eq/src/mem_inputs/arith256.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Arith256; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const ARITH_256_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 5, @@ -12,12 +10,12 @@ pub const ARITH_256_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { chunks_per_param: 4, }; -pub fn generate_arith256_mem_inputs( +pub fn generate_arith256_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[5],... let a: &[u64; 4] = &data[10..14].try_into().unwrap(); @@ -38,15 +36,15 @@ pub fn generate_arith256_mem_inputs( data, Some(&d), only_counters, - pending, + mem_processors, &ARITH_256_MEM_CONFIG, ); } -pub fn skip_arith256_mem_inputs( +pub fn skip_arith256_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &ARITH_256_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &ARITH_256_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs b/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs index 43ab8f740..04aa76153 100644 --- a/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs +++ b/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Arith256Mod; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const ARITH_256_MOD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 5, @@ -11,12 +9,12 @@ pub const ARITH_256_MOD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfi write_params: 1, chunks_per_param: 4, }; -pub fn generate_arith256_mod_mem_inputs( +pub fn generate_arith256_mod_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[5],... let a: &[u64; 4] = &data[10..14].try_into().unwrap(); @@ -32,15 +30,15 @@ pub fn generate_arith256_mod_mem_inputs( data, Some(&d), only_counters, - pending, + mem_processors, &ARITH_256_MOD_MEM_CONFIG, ); } -pub fn skip_arith256_mod_mem_inputs( +pub fn skip_arith256_mod_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &ARITH_256_MOD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &ARITH_256_MOD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs index 5e0c6c337..3e4e97826 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Bn254Complex; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const BN254_COMPLEX_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 2, @@ -12,12 +10,12 @@ pub const BN254_COMPLEX_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputC chunks_per_param: 8, }; -pub fn generate_bn254_complex_add_mem_inputs( +pub fn generate_bn254_complex_add_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... let f1: &[u64; 8] = &data[7..15].try_into().unwrap(); @@ -31,15 +29,15 @@ pub fn generate_bn254_complex_add_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BN254_COMPLEX_ADD_MEM_CONFIG, ); } -pub fn skip_bn254_complex_add_mem_inputs( +pub fn skip_bn254_complex_add_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_ADD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_ADD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs index b6bf49b9d..4ee73bc18 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Bn254Complex; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const BN254_COMPLEX_MUL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 2, @@ -12,12 +10,12 @@ pub const BN254_COMPLEX_MUL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputC chunks_per_param: 8, }; -pub fn generate_bn254_complex_mul_mem_inputs( +pub fn generate_bn254_complex_mul_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... let f1: &[u64; 8] = &data[7..15].try_into().unwrap(); @@ -31,15 +29,15 @@ pub fn generate_bn254_complex_mul_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BN254_COMPLEX_MUL_MEM_CONFIG, ); } -pub fn skip_bn254_complex_mul_mem_inputs( +pub fn skip_bn254_complex_mul_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_MUL_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_MUL_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs index f528bceba..11f1e7573 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Bn254Complex; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const BN254_COMPLEX_SUB_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 2, @@ -12,12 +10,12 @@ pub const BN254_COMPLEX_SUB_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputC chunks_per_param: 8, }; -pub fn generate_bn254_complex_sub_mem_inputs( +pub fn generate_bn254_complex_sub_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... let f1: &[u64; 8] = &data[7..15].try_into().unwrap(); @@ -31,15 +29,15 @@ pub fn generate_bn254_complex_sub_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BN254_COMPLEX_SUB_MEM_CONFIG, ); } -pub fn skip_bn254_complex_sub_mem_inputs( +pub fn skip_bn254_complex_sub_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_SUB_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_SUB_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs b/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs index f50638927..b43bc2828 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs @@ -1,8 +1,7 @@ use super::ArithEqMemInputConfig; use crate::executors::Bn254Curve; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; + use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; pub const BN254_CURVE_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { @@ -13,12 +12,12 @@ pub const BN254_CURVE_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputCon chunks_per_param: 8, }; -pub fn generate_bn254_curve_add_mem_inputs( +pub fn generate_bn254_curve_add_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... let p1_start = OPERATION_PRECOMPILED_BUS_DATA_SIZE + BN254_CURVE_ADD_MEM_CONFIG.indirect_params; @@ -36,15 +35,15 @@ pub fn generate_bn254_curve_add_mem_inputs( data, Some(&p3), only_counters, - pending, + mem_processors, &BN254_CURVE_ADD_MEM_CONFIG, ); } -pub fn skip_bn254_curve_add_mem_inputs( +pub fn skip_bn254_curve_add_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BN254_CURVE_ADD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BN254_CURVE_ADD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs b/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs index 37eeb5802..0273c3aa0 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs @@ -1,8 +1,7 @@ use super::ArithEqMemInputConfig; use crate::executors::Bn254Curve; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; + use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; pub const BN254_CURVE_DBL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { @@ -13,12 +12,12 @@ pub const BN254_CURVE_DBL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputCon chunks_per_param: 8, }; -pub fn generate_bn254_curve_dbl_mem_inputs( +pub fn generate_bn254_curve_dbl_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... let p1: &[u64; 8] = &data @@ -34,15 +33,15 @@ pub fn generate_bn254_curve_dbl_mem_inputs( data, Some(&p3), only_counters, - pending, + mem_processors, &BN254_CURVE_DBL_MEM_CONFIG, ); } -pub fn skip_bn254_curve_dbl_mem_inputs( +pub fn skip_bn254_curve_dbl_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BN254_CURVE_DBL_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BN254_CURVE_DBL_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs b/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs index 58ad9c63e..480ea57f8 100644 --- a/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs +++ b/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs @@ -1,7 +1,7 @@ use precompiles_common::MemBusHelpers; -use std::collections::VecDeque; -use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; +use precompiles_common::MemProcessor; + +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; #[derive(Debug)] pub struct ArithEqMemInputConfig { @@ -11,13 +11,13 @@ pub struct ArithEqMemInputConfig { pub write_params: usize, pub chunks_per_param: usize, } -pub fn generate_mem_inputs( +pub fn generate_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], write_data: Option<&[u64]>, only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, config: &ArithEqMemInputConfig, ) { let params_count = config.read_params + config.write_params; @@ -28,7 +28,7 @@ pub fn generate_mem_inputs( addr_main + iparam as u32 * 8, step_main, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], - pending, + mem_processors, ) } for iparam in 0..params_count { @@ -66,27 +66,25 @@ pub fn generate_mem_inputs( step_main, chunk_data, is_write, - pending, + mem_processors, ) } } } -pub fn skip_mem_inputs( +pub fn skip_mem_inputs( addr_main: u32, data: &[u64], config: &ArithEqMemInputConfig, - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { let params_count = config.read_params + config.write_params; // Check indirect loads for iparam in 0..config.indirect_params { let addr = addr_main + iparam as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } @@ -103,10 +101,8 @@ pub fn skip_mem_inputs( }; for ichunk in 0..config.chunks_per_param { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } diff --git a/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs b/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs index 8ae70b6c4..bdd2ff7aa 100644 --- a/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Secp256k1; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const SECP256K1_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 2, @@ -12,12 +10,12 @@ pub const SECP256K1_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfi chunks_per_param: 8, }; -pub fn generate_secp256k1_add_mem_inputs( +pub fn generate_secp256k1_add_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... let p1: &[u64; 8] = &data[7..15].try_into().unwrap(); @@ -31,15 +29,15 @@ pub fn generate_secp256k1_add_mem_inputs( data, Some(&p3), only_counters, - pending, + mem_processors, &SECP256K1_ADD_MEM_CONFIG, ); } -pub fn skip_secp256k1_add_mem_inputs( +pub fn skip_secp256k1_add_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &SECP256K1_ADD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &SECP256K1_ADD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs b/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs index cef9a6294..451e4e065 100644 --- a/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs +++ b/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Secp256k1; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const SECP256K1_DBL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 0, @@ -12,12 +10,12 @@ pub const SECP256K1_DBL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfi chunks_per_param: 8, }; -pub fn generate_secp256k1_dbl_mem_inputs( +pub fn generate_secp256k1_dbl_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + processor: &mut P, ) { // op,op_type,a,b,... let p1: &[u64; 8] = &data[5..13].try_into().unwrap(); @@ -30,15 +28,15 @@ pub fn generate_secp256k1_dbl_mem_inputs( data, Some(&p3), only_counters, - pending, + processor, &SECP256K1_DBL_MEM_CONFIG, ); } -pub fn skip_secp256k1_dbl_mem_inputs( +pub fn skip_secp256k1_dbl_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &SECP256K1_DBL_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &SECP256K1_DBL_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs index 534a72f1b..c30fc1293 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs @@ -2,11 +2,11 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::ArithEq384` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; +use precompiles_common::MemProcessor; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, MemCollectorInfo, Metrics, B, OP, OPERATION_BUS_ID, - OP_TYPE, STEP, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OP, OPERATION_BUS_ID, OP_TYPE, STEP, }; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; @@ -63,91 +63,48 @@ impl ArithEq384CounterInputGen { (op_type == ZiskOperationType::ArithEq384).then_some(self.counter.inst_count) } - fn skip_data(&self, data: &[u64], mem_collectors_info: &[MemCollectorInfo]) -> bool { + fn skip_data(&self, data: &[u64], mem_processors: &mut P) -> bool { let addr_main = data[B] as u32; match data[OP] as u8 { - ARITH384_MOD_OP => skip_arith384_mod_mem_inputs(addr_main, data, mem_collectors_info), + ARITH384_MOD_OP => skip_arith384_mod_mem_inputs(addr_main, data, mem_processors), BLS12_381_CURVE_ADD_OP => { - skip_bls12_381_curve_add_mem_inputs(addr_main, data, mem_collectors_info) + skip_bls12_381_curve_add_mem_inputs(addr_main, data, mem_processors) } BLS12_381_CURVE_DBL_OP => { - skip_bls12_381_curve_dbl_mem_inputs(addr_main, data, mem_collectors_info) + skip_bls12_381_curve_dbl_mem_inputs(addr_main, data, mem_processors) } BLS12_381_COMPLEX_ADD_OP => { - skip_bls12_381_complex_add_mem_inputs(addr_main, data, mem_collectors_info) + skip_bls12_381_complex_add_mem_inputs(addr_main, data, mem_processors) } BLS12_381_COMPLEX_SUB_OP => { - skip_bls12_381_complex_sub_mem_inputs(addr_main, data, mem_collectors_info) + skip_bls12_381_complex_sub_mem_inputs(addr_main, data, mem_processors) } BLS12_381_COMPLEX_MUL_OP => { - skip_bls12_381_complex_mul_mem_inputs(addr_main, data, mem_collectors_info) + skip_bls12_381_complex_mul_mem_inputs(addr_main, data, mem_processors) } _ => { panic!("ArithEq384CounterInputGen: Unsupported data length {}", data.len()); } } } -} - -impl Metrics for ArithEq384CounterInputGen { - /// Tracks activity on the connected bus and updates counters for recognized operations. - /// - /// # Arguments - /// * `_bus_id` - The ID of the bus (unused in this implementation). - /// * `_data` - The data received from the bus. - /// - /// # Returns - /// An empty vector, as this implementation does not produce any derived inputs for the bus. - #[inline(always)] - fn measure(&mut self, _data: &[u64]) { - self.counter.update(1); - } - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl Add for ArithEq384CounterInputGen { - type Output = ArithEq384CounterInputGen; - /// Combines two `Arith384Counter` instances by summing their counters. - /// - /// # Arguments - /// * `self` - The first `Arith384Counter` instance. - /// * `other` - The second `Arith384Counter` instance. - /// - /// # Returns - /// A new `Arith384Counter` with combined counters. - fn add(self, other: Self) -> ArithEq384CounterInputGen { - ArithEq384CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } - } -} - -impl BusDevice for ArithEq384CounterInputGen { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments /// * `bus_id` - The ID of the bus sending the data. /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. /// /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( + pub fn process_data( &mut self, bus_id: &BusId, data: &[u64], - _data_ext: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, + mem_processors: &mut P, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); @@ -157,12 +114,6 @@ impl BusDevice for ArithEq384CounterInputGen { return true; } - if let Some(mem_collectors_info) = mem_collector_info { - if self.skip_data(data, mem_collectors_info) { - return true; - } - } - let op = data[OP] as u8; let step_main = data[STEP]; let addr_main = data[B] as u32; @@ -176,7 +127,12 @@ impl BusDevice for ArithEq384CounterInputGen { self.measure(data); return true; } - BusDeviceMode::InputGenerator => false, + BusDeviceMode::InputGenerator => { + if self.skip_data(data, mem_processors) { + return true; + } + false + } }; match op { @@ -186,7 +142,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BLS12_381_CURVE_ADD_OP => { @@ -195,7 +151,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BLS12_381_CURVE_DBL_OP => { @@ -204,7 +160,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BLS12_381_COMPLEX_ADD_OP => { @@ -213,7 +169,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BLS12_381_COMPLEX_SUB_OP => { @@ -222,7 +178,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BLS12_381_COMPLEX_MUL_OP => { @@ -231,7 +187,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } _ => { @@ -241,15 +197,48 @@ impl BusDevice for ArithEq384CounterInputGen { true } +} - /// Returns the bus IDs associated with this counter. +impl Metrics for ArithEq384CounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); } + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for ArithEq384CounterInputGen { + type Output = ArithEq384CounterInputGen; + + /// Combines two `Arith384Counter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `Arith384Counter` instance. + /// * `other` - The second `Arith384Counter` instance. + /// + /// # Returns + /// A new `Arith384Counter` with combined counters. + fn add(self, other: Self) -> ArithEq384CounterInputGen { + ArithEq384CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } + } +} + +impl BusDevice for ArithEq384CounterInputGen { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/arith_eq_384/src/arith_eq_384_instance.rs b/precompiles/arith_eq_384/src/arith_eq_384_instance.rs index 137a3fa86..be5aa91e1 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_instance.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_instance.rs @@ -6,14 +6,13 @@ use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::collections::VecDeque; use std::{any::Any, collections::HashMap, sync::Arc}; +use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, InstanceType, OperationBusData, PayloadType, OPERATION_BUS_ID, }; -use zisk_common::{ChunkId, MemCollectorInfo}; use zisk_core::ZiskOperationType; use zisk_pil::ArithEq384Trace; @@ -158,9 +157,7 @@ impl ArithEq384Collector { pub fn new(num_operations: u64, collect_skipper: CollectSkipper) -> Self { Self { inputs: Vec::new(), num_operations, collect_skipper } } -} -impl BusDevice for ArithEq384Collector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -171,14 +168,7 @@ impl BusDevice for ArithEq384Collector { /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -221,15 +211,9 @@ impl BusDevice for ArithEq384Collector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for ArithEq384Collector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/arith_eq_384/src/arith_eq_384_manager.rs b/precompiles/arith_eq_384/src/arith_eq_384_manager.rs index ad5f58c39..7d79ea4d4 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_manager.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_manager.rs @@ -2,11 +2,8 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{BusDevice, PayloadType}; -use zisk_common::{ - BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::ArithEq384Trace; @@ -44,14 +41,6 @@ impl ArithEq384Manager { } impl ComponentBuilder for ArithEq384Manager { - /// Builds and returns a new counter for monitoring arith256 operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for arith256 operations. - fn build_counter(&self) -> Option> { - Some(Box::new(ArithEq384CounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan arith256-related instances. /// /// # Returns @@ -92,8 +81,4 @@ impl ComponentBuilder for ArithEq384Manager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(ArithEq384CounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs b/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs index 9e997b14b..e7e4b8f3a 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Arith384Mod, ARITH_EQ_384_U64S}; @@ -12,12 +11,12 @@ pub const ARITH_384_MOD_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq384MemInpu chunks_per_param: ARITH_EQ_384_U64S, }; -pub fn generate_arith384_mod_mem_inputs( +pub fn generate_arith384_mod_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { let mut pos_offset: usize = 10; // op,op_type,a,b,addr[5],... let a: &[u64; ARITH_EQ_384_U64S] = @@ -40,15 +39,15 @@ pub fn generate_arith384_mod_mem_inputs( data, Some(&d), only_counters, - pending, + mem_processors, &ARITH_384_MOD_MEM_CONFIG, ); } -pub fn skip_arith384_mod_mem_inputs( +pub fn skip_arith384_mod_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &ARITH_384_MOD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &ARITH_384_MOD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs index 444a3a844..d2c88f709 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Bls12_381Complex, ARITH_EQ_384_U64S_DOUBLE}; @@ -12,12 +11,12 @@ pub const BLS12_381_COMPLEX_ADD_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq38 chunks_per_param: ARITH_EQ_384_U64S_DOUBLE, }; -pub fn generate_bls12_381_complex_add_mem_inputs( +pub fn generate_bls12_381_complex_add_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = @@ -34,15 +33,15 @@ pub fn generate_bls12_381_complex_add_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BLS12_381_COMPLEX_ADD_MEM_CONFIG, ); } -pub fn skip_bls12_381_complex_add_mem_inputs( +pub fn skip_bls12_381_complex_add_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_ADD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_ADD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs index 7c6ff890d..bd444258c 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Bls12_381Complex, ARITH_EQ_384_U64S_DOUBLE}; @@ -12,12 +11,12 @@ pub const BLS12_381_COMPLEX_MUL_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq38 chunks_per_param: ARITH_EQ_384_U64S_DOUBLE, }; -pub fn generate_bls12_381_complex_mul_mem_inputs( +pub fn generate_bls12_381_complex_mul_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = @@ -34,15 +33,15 @@ pub fn generate_bls12_381_complex_mul_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BLS12_381_COMPLEX_MUL_MEM_CONFIG, ); } -pub fn skip_bls12_381_complex_mul_mem_inputs( +pub fn skip_bls12_381_complex_mul_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_MUL_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_MUL_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs index 4e1b901ef..d3e7b4fe4 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Bls12_381Complex, ARITH_EQ_384_U64S_DOUBLE}; @@ -12,12 +11,12 @@ pub const BLS12_381_COMPLEX_SUB_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq38 chunks_per_param: ARITH_EQ_384_U64S_DOUBLE, }; -pub fn generate_bls12_381_complex_sub_mem_inputs( +pub fn generate_bls12_381_complex_sub_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = @@ -34,15 +33,15 @@ pub fn generate_bls12_381_complex_sub_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BLS12_381_COMPLEX_SUB_MEM_CONFIG, ); } -pub fn skip_bls12_381_complex_sub_mem_inputs( +pub fn skip_bls12_381_complex_sub_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_SUB_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_SUB_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs index 2f0f8404c..9fa1ece0a 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Bls12_381Curve, ARITH_EQ_384_U64S_DOUBLE}; @@ -12,12 +11,12 @@ pub const BLS12_381_CURVE_ADD_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq384M chunks_per_param: ARITH_EQ_384_U64S_DOUBLE, }; -pub fn generate_bls12_381_curve_add_mem_inputs( +pub fn generate_bls12_381_curve_add_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let p1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = @@ -34,15 +33,15 @@ pub fn generate_bls12_381_curve_add_mem_inputs( data, Some(&p3), only_counters, - pending, + mem_processors, &BLS12_381_CURVE_ADD_MEM_CONFIG, ); } -pub fn skip_bls12_381_curve_add_mem_inputs( +pub fn skip_bls12_381_curve_add_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BLS12_381_CURVE_ADD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BLS12_381_CURVE_ADD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs index c3f0359b6..d2ee68ce0 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Bls12_381Curve, ARITH_EQ_384_U64S_DOUBLE}; @@ -12,12 +11,12 @@ pub const BLS12_381_CURVE_DBL_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq384M chunks_per_param: ARITH_EQ_384_U64S_DOUBLE, }; -pub fn generate_bls12_381_curve_dbl_mem_inputs( +pub fn generate_bls12_381_curve_dbl_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { let pos_offset: usize = 5; // op,op_type,a,b,... let p1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = @@ -31,15 +30,15 @@ pub fn generate_bls12_381_curve_dbl_mem_inputs( data, Some(&p3), only_counters, - pending, + mem_processors, &BLS12_381_CURVE_DBL_MEM_CONFIG, ); } -pub fn skip_bls12_381_curve_dbl_mem_inputs( +pub fn skip_bls12_381_curve_dbl_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BLS12_381_CURVE_DBL_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BLS12_381_CURVE_DBL_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs b/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs index fc526f3cb..4d890e408 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs @@ -1,6 +1,6 @@ use precompiles_common::MemBusHelpers; -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; +use precompiles_common::MemProcessor; +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; #[derive(Debug)] pub struct ArithEq384MemInputConfig { @@ -10,13 +10,13 @@ pub struct ArithEq384MemInputConfig { pub write_params: usize, pub chunks_per_param: usize, } -pub fn generate_mem_inputs( +pub fn generate_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], write_data: Option<&[u64]>, only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, config: &ArithEq384MemInputConfig, ) { let params_count = config.read_params + config.write_params; @@ -27,7 +27,7 @@ pub fn generate_mem_inputs( addr_main + iparam as u32 * 8, step_main, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], - pending, + mem_processors, ) } for iparam in 0..params_count { @@ -65,27 +65,25 @@ pub fn generate_mem_inputs( step_main, chunk_data, is_write, - pending, + mem_processors, ) } } } -pub fn skip_mem_inputs( +pub fn skip_mem_inputs( addr_main: u32, data: &[u64], config: &ArithEq384MemInputConfig, - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { let params_count = config.read_params + config.write_params; // Check indirect loads for iparam in 0..config.indirect_params { let addr = addr_main + iparam as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } @@ -102,10 +100,8 @@ pub fn skip_mem_inputs( }; for ichunk in 0..config.chunks_per_param { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } diff --git a/precompiles/big_int/src/add256_bus_device.rs b/precompiles/big_int/src/add256_bus_device.rs index 364ca7b69..71cf20ec5 100644 --- a/precompiles/big_int/src/add256_bus_device.rs +++ b/precompiles/big_int/src/add256_bus_device.rs @@ -2,9 +2,10 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::Add256` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; + +use precompiles_common::MemProcessor; -use zisk_common::MemCollectorInfo; use zisk_common::{ BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, STEP, }; @@ -48,66 +49,23 @@ impl Add256CounterInputGen { pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { (op_type == ZiskOperationType::BigInt).then_some(self.counter.inst_count) } -} - -impl Metrics for Add256CounterInputGen { - /// Tracks activity on the connected bus and updates counters for recognized operations. - /// - /// # Arguments - /// * `_bus_id` - The ID of the bus (unused in this implementation). - /// * `_data` - The data received from the bus. - /// - /// # Returns - /// An empty vector, as this implementation does not produce any derived inputs for the bus. - #[inline(always)] - fn measure(&mut self, _data: &[u64]) { - self.counter.update(1); - } - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl Add for Add256CounterInputGen { - type Output = Add256CounterInputGen; - - /// Combines two `Add256Counter` instances by summing their counters. - /// - /// # Arguments - /// * `self` - The first `Add256Counter` instance. - /// * `other` - The second `Add256Counter` instance. - /// - /// # Returns - /// A new `Add256Counter` with combined counters. - fn add(self, other: Self) -> Add256CounterInputGen { - Add256CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } - } -} -impl BusDevice for Add256CounterInputGen { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments /// * `bus_id` - The ID of the bus sending the data. /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. /// /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( + pub fn process_data( &mut self, bus_id: &BusId, data: &[u64], - _data_ext: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, + mem_processors: &mut P, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); @@ -115,39 +73,69 @@ impl BusDevice for Add256CounterInputGen { return true; } - if let Some(mem_collectors_info) = mem_collector_info { - if skip_add256_mem_inputs(data[B] as u32, data, mem_collectors_info) { - return true; - } - } - let step_main = data[STEP]; let addr_main = data[B] as u32; match self.mode { BusDeviceMode::Counter => { self.measure(data); - generate_add256_mem_inputs(addr_main, step_main, data, true, pending); + generate_add256_mem_inputs(addr_main, step_main, data, true, mem_processors); } BusDeviceMode::CounterAsm => { self.measure(data); } BusDeviceMode::InputGenerator => { - generate_add256_mem_inputs(addr_main, step_main, data, false, pending); + if skip_add256_mem_inputs(addr_main, data, mem_processors) { + return true; + } + generate_add256_mem_inputs(addr_main, step_main, data, false, mem_processors); } } true } +} - /// Returns the bus IDs associated with this counter. +impl Metrics for Add256CounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); } + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for Add256CounterInputGen { + type Output = Add256CounterInputGen; + + /// Combines two `Add256Counter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `Add256Counter` instance. + /// * `other` - The second `Add256Counter` instance. + /// + /// # Returns + /// A new `Add256Counter` with combined counters. + fn add(self, other: Self) -> Add256CounterInputGen { + Add256CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } + } +} + +impl BusDevice for Add256CounterInputGen { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/big_int/src/add256_gen_mem_inputs.rs b/precompiles/big_int/src/add256_gen_mem_inputs.rs index 6006c8c49..db7d9faea 100644 --- a/precompiles/big_int/src/add256_gen_mem_inputs.rs +++ b/precompiles/big_int/src/add256_gen_mem_inputs.rs @@ -2,9 +2,9 @@ use lib_c::add256; use crate::add256_constants::*; use precompiles_common::MemBusHelpers; -use std::collections::VecDeque; -use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; +use precompiles_common::MemProcessor; + +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; #[derive(Debug)] pub struct Add256MemInputConfig { @@ -15,12 +15,12 @@ pub struct Add256MemInputConfig { pub chunks_per_param: usize, } -pub fn generate_add256_mem_inputs( +pub fn generate_add256_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // Start by generating the params (indirection read, direct, indirection write) for iparam in 0..PARAMS { @@ -28,7 +28,7 @@ pub fn generate_add256_mem_inputs( addr_main + iparam as u32 * 8, step_main, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], - pending, + mem_processors, ); } @@ -40,7 +40,7 @@ pub fn generate_add256_mem_inputs( param_addr + ichunk as u32 * 8, step_main, data[START_READ_PARAMS + iparam * PARAM_CHUNKS + ichunk], - pending, + mem_processors, ); } } @@ -60,7 +60,7 @@ pub fn generate_add256_mem_inputs( let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; for (ichunk, write_data) in write_data.iter().enumerate().take(PARAM_CHUNKS) { let param_addr = write_addr + ichunk as u32 * 8; - MemBusHelpers::mem_aligned_write(param_addr, step_main, *write_data, pending); + MemBusHelpers::mem_aligned_write(param_addr, step_main, *write_data, mem_processors); } } @@ -68,18 +68,16 @@ pub fn generate_add256_mem_inputs( // op_b = addr_main // mem_trace: @a, @b, cin, @c, a[0..3], b[0..3], cout, [ c[0..3] ] -pub fn skip_add256_mem_inputs( +pub fn skip_add256_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { // verify main params "struct" of indirections for iparam in 0..PARAMS { let addr = addr_main + iparam as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } @@ -88,10 +86,8 @@ pub fn skip_add256_mem_inputs( let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; for ichunk in 0..PARAM_CHUNKS { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } @@ -100,10 +96,8 @@ pub fn skip_add256_mem_inputs( let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; for ichunk in 0..PARAM_CHUNKS { let addr = write_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } diff --git a/precompiles/big_int/src/add256_instance.rs b/precompiles/big_int/src/add256_instance.rs index 8d538652c..20d441bc8 100644 --- a/precompiles/big_int/src/add256_instance.rs +++ b/precompiles/big_int/src/add256_instance.rs @@ -7,12 +7,11 @@ use crate::{Add256Input, Add256SM}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::collections::VecDeque; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, + InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; use zisk_pil::Add256Trace; @@ -149,9 +148,7 @@ impl Add256Collector { collect_skipper, } } -} -impl BusDevice for Add256Collector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -164,14 +161,7 @@ impl BusDevice for Add256Collector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -196,15 +186,9 @@ impl BusDevice for Add256Collector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for Add256Collector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/big_int/src/add256_manager.rs b/precompiles/big_int/src/add256_manager.rs index 0ff2c8d86..bd3b99fc9 100644 --- a/precompiles/big_int/src/add256_manager.rs +++ b/precompiles/big_int/src/add256_manager.rs @@ -2,10 +2,7 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{ - BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, - InstanceInfo, PayloadType, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; #[cfg(not(feature = "packed"))] use zisk_pil::Add256Trace; @@ -51,14 +48,6 @@ impl Add256Manager { } impl ComponentBuilder for Add256Manager { - /// Builds and returns a new counter for monitoring Add256 operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for Add256 operations. - fn build_counter(&self) -> Option> { - Some(Box::new(Add256CounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan Add256-related instances. /// /// # Returns @@ -96,8 +85,4 @@ impl ComponentBuilder for Add256Manager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(Add256CounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/precompiles/common/Cargo.toml b/precompiles/common/Cargo.toml index dd772a536..755e6e90a 100644 --- a/precompiles/common/Cargo.toml +++ b/precompiles/common/Cargo.toml @@ -10,5 +10,7 @@ categories = { workspace = true } [dependencies] zisk-core = { workspace = true } zisk-common = { workspace = true } +sm-mem = { workspace = true } +mem-common = { workspace = true } fields = { workspace = true } \ No newline at end of file diff --git a/precompiles/common/src/lib.rs b/precompiles/common/src/lib.rs index 7a00db84f..b1b2819a7 100644 --- a/precompiles/common/src/lib.rs +++ b/precompiles/common/src/lib.rs @@ -4,8 +4,9 @@ mod goldilocks_constants; pub use goldilocks_constants::{get_ks, GOLDILOCKS_GEN, GOLDILOCKS_K}; -use std::collections::VecDeque; -use zisk_common::{BusId, MEM_BUS_ID}; +use mem_common::MemCounters; +use sm_mem::{MemAlignCollector, MemModuleCollector}; +use zisk_common::MEM_BUS_ID; use zisk_core::InstContext; /// Represents a precompile operation code. @@ -59,110 +60,187 @@ const MEM_STEP_BASE: u64 = 1; /// Maximum number of memory operations per main step. const MAX_MEM_OPS_BY_MAIN_STEP: u64 = 4; +/// Trait for processing memory operations - allows static dispatch +pub trait MemProcessor { + fn process_mem_data(&mut self, data: &[u64; 7]); + fn skip_addr(&mut self, addr: u32) -> bool; + fn skip_addr_range(&mut self, addr_from: u32, addr_to: u32) -> bool; +} + +/// Collector-based memory mem_processor +pub struct MemCollectorProcessor<'a> { + pub mem: &'a mut [(usize, MemModuleCollector)], + pub align: &'a mut [(usize, MemAlignCollector)], +} + +impl<'a> MemCollectorProcessor<'a> { + #[inline(always)] + pub fn new( + mem: &'a mut [(usize, MemModuleCollector)], + align: &'a mut [(usize, MemAlignCollector)], + ) -> Self { + Self { mem, align } + } +} + +impl MemProcessor for MemCollectorProcessor<'_> { + #[inline(always)] + fn process_mem_data(&mut self, data: &[u64; 7]) { + for collector in self.mem.iter_mut() { + collector.1.process_data(&MEM_BUS_ID, data); + } + for collector in self.align.iter_mut() { + collector.1.process_data(&MEM_BUS_ID, data); + } + } + + #[inline(always)] + fn skip_addr(&mut self, addr: u32) -> bool { + for collector in self.mem.iter_mut() { + if !collector.1.skip_addr(addr) { + return false; + } + } + true + } + + #[inline(always)] + fn skip_addr_range(&mut self, addr_from: u32, addr_to: u32) -> bool { + for collector in self.mem.iter_mut() { + if !collector.1.skip_addr_range(addr_from, addr_to) { + return false; + } + } + true + } +} + +/// Counter-based memory mem_processor +pub struct MemCounterProcessor<'a> { + pub counters: Option<&'a mut MemCounters>, +} + +impl<'a> MemCounterProcessor<'a> { + #[inline(always)] + pub fn new(counters: Option<&'a mut MemCounters>) -> Self { + Self { counters } + } +} + +impl MemProcessor for MemCounterProcessor<'_> { + #[inline(always)] + fn process_mem_data(&mut self, data: &[u64; 7]) { + if let Some(counters) = &mut self.counters { + counters.process_data(&MEM_BUS_ID, data); + } + } + + fn skip_addr(&mut self, _addr: u32) -> bool { + false + } + + fn skip_addr_range(&mut self, _addr_from: u32, _addr_to: u32) -> bool { + false + } +} + impl MemBusHelpers { /// Generates an aligned memory load operation. /// The address must be 8-byte aligned. - pub fn mem_aligned_load( + pub fn mem_aligned_load( addr: u32, step: u64, mem_value: u64, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processor: &mut P, ) { - assert!(addr % 8 == 0); - pending.push_back(( - MEM_BUS_ID, - vec![ - MEMORY_LOAD_OP, - addr as u64, - MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2, - 8, - mem_value, - 0, - 0, - ], - vec![], - )); + debug_assert!(addr % 8 == 0); + let data: [u64; 7] = [ + MEMORY_LOAD_OP, + addr as u64, + MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2, + 8, + mem_value, + 0, + 0, + ]; + mem_processor.process_mem_data(&data); } + /// Generates an aligned memory write operation. /// The address must be 8-byte aligned. - pub fn mem_aligned_write( + pub fn mem_aligned_write( addr: u32, step: u64, value: u64, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processor: &mut P, ) { - assert!(addr % 8 == 0); - pending.push_back(( - MEM_BUS_ID, - vec![ - MEMORY_STORE_OP, - addr as u64, - MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3, - 8, - 0, - 0, - value, - ], - vec![], - )); + debug_assert!(addr % 8 == 0); + let data: [u64; 7] = [ + MEMORY_STORE_OP, + addr as u64, + MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3, + 8, + 0, + 0, + value, + ]; + mem_processor.process_mem_data(&data); } + /// Generates an aligned memory operation (load or write). /// The address must be 8-byte aligned. - pub fn mem_aligned_op( + pub fn mem_aligned_op( addr: u32, step: u64, value: u64, is_write: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processor: &mut P, ) { - pending.push_back(( - MEM_BUS_ID, - vec![ - if is_write { MEMORY_STORE_OP } else { MEMORY_LOAD_OP }, - addr as u64, - MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + if is_write { 3 } else { 2 }, - 8, - if is_write { 0 } else { value }, - 0, - if is_write { value } else { 0 }, - ], - vec![], - )); + let data: [u64; 7] = [ + if is_write { MEMORY_STORE_OP } else { MEMORY_LOAD_OP }, + addr as u64, + MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + if is_write { 3 } else { 2 }, + 8, + if is_write { 0 } else { value }, + 0, + if is_write { value } else { 0 }, + ]; + + mem_processor.process_mem_data(&data); } + /// Generates multiple aligned memory load operations from a slice of values. /// The address must be 8-byte aligned. - pub fn mem_aligned_load_from_slice( + pub fn mem_aligned_load_from_slice( addr: u32, step: u64, values: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processor: &mut P, ) { assert!(addr % 8 == 0); let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2; for (i, &value) in values.iter().enumerate() { - pending.push_back(( - MEM_BUS_ID, - vec![MEMORY_LOAD_OP, (addr as usize + i * 8) as u64, mem_step, 8, value, 0, 0], - vec![], - )); + let data: [u64; 7] = + [MEMORY_LOAD_OP, (addr as usize + i * 8) as u64, mem_step, 8, value, 0, 0]; + + mem_processor.process_mem_data(&data); } } /// Generates multiple aligned memory write operations from a slice of values. /// The address must be 8-byte aligned. - pub fn mem_aligned_write_from_slice( + pub fn mem_aligned_write_from_slice( addr: u32, step: u64, values: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processor: &mut P, ) { assert!(addr % 8 == 0); let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3; for (i, &value) in values.iter().enumerate() { - pending.push_back(( - MEM_BUS_ID, - vec![MEMORY_STORE_OP, (addr as usize + i * 8) as u64, mem_step, 8, 0, 0, value], - vec![], - )); + let data: [u64; 7] = + [MEMORY_STORE_OP, (addr as usize + i * 8) as u64, mem_step, 8, 0, 0, value]; + + mem_processor.process_mem_data(&data); } } /// Generates aligned memory writes from an unaligned read slice using the specified source offset. @@ -170,12 +248,12 @@ impl MemBusHelpers { /// create a full 8-byte write. This function is useful to use the same slice of values to generate /// first aligned reads and then aligned writes. /// The address must be 8-byte aligned. - pub fn mem_aligned_write_from_read_unaligned_slice( + pub fn mem_aligned_write_from_read_unaligned_slice( addr: u32, step: u64, src_offset: u8, values: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processor: &mut P, ) { assert!(addr % 8 == 0); let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3; @@ -191,21 +269,13 @@ impl MemBusHelpers { 7 => (values[i] >> 56) | (values[i + 1] << 8), _ => panic!("invalid src_offset {src_offset} on DmaUnaligned"), }; - pending.push_back(( - MEM_BUS_ID, - vec![ - MEMORY_STORE_OP, - (addr as usize + i * 8) as u64, - mem_step, - 8, - 0, - 0, - write_value, - ], - vec![], - )); + let data: [u64; 7] = + [MEMORY_STORE_OP, (addr as usize + i * 8) as u64, mem_step, 8, 0, 0, write_value]; + + mem_processor.process_mem_data(&data); } } + /// Returns the memory read step for the given step number. pub fn get_mem_read_step(step: u64) -> u64 { MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2 diff --git a/precompiles/dma/src/dma/dma_collector.rs b/precompiles/dma/src/dma/dma_collector.rs index 89787947f..95294baa3 100644 --- a/precompiles/dma/src/dma/dma_collector.rs +++ b/precompiles/dma/src/dma/dma_collector.rs @@ -1,11 +1,9 @@ //! The `DmaCollector` module defines an collector to calculate all inputs of an instance //! for the Dma State Machine. -use std::{any::Any, collections::VecDeque}; +use std::any::Any; -use zisk_common::{ - BusDevice, BusId, CollectCounter, ExtOperationData, MemCollectorInfo, OPERATION_BUS_ID, OP_TYPE, -}; +use zisk_common::{BusDevice, BusId, CollectCounter, ExtOperationData, OPERATION_BUS_ID, OP_TYPE}; use zisk_core::ZiskOperationType; use crate::DmaInput; @@ -39,9 +37,7 @@ impl DmaCollector { collect_counter, } } -} -impl BusDevice for DmaCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -54,14 +50,7 @@ impl BusDevice for DmaCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -88,15 +77,9 @@ impl BusDevice for DmaCollector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for DmaCollector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs index 3b6177e07..c25df966d 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs @@ -6,8 +6,7 @@ use crate::Dma64AlignedInput; use std::any::Any; -use std::collections::VecDeque; -use zisk_common::{BusDevice, BusId, CollectCounter, MemCollectorInfo, OPERATION_BUS_ID, OP_TYPE}; +use zisk_common::{BusDevice, BusId, CollectCounter, OPERATION_BUS_ID, OP_TYPE}; use zisk_core::ZiskOperationType; pub struct Dma64AlignedCollector { @@ -48,9 +47,7 @@ impl Dma64AlignedCollector { last_segment_collector, } } -} -impl BusDevice for Dma64AlignedCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -63,14 +60,7 @@ impl BusDevice for Dma64AlignedCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_inputs as usize { @@ -100,15 +90,9 @@ impl BusDevice for Dma64AlignedCollector { self.inputs.len() < self.num_inputs as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for Dma64AlignedCollector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/dma/src/dma_bus_device.rs b/precompiles/dma/src/dma_bus_device.rs index a96cf20d8..4194cd82e 100644 --- a/precompiles/dma/src/dma_bus_device.rs +++ b/precompiles/dma/src/dma_bus_device.rs @@ -2,11 +2,11 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::Dma` instructions. -use std::{collections::VecDeque, ops::Add}; - +use precompiles_common::MemProcessor; use precompiles_helpers::DmaInfo; +use std::ops::Add; use zisk_common::{BusDevice, BusDeviceMode, BusId, Metrics, B, OPERATION_BUS_ID, OP_TYPE}; -use zisk_common::{MemCollectorInfo, A, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; +use zisk_common::{A, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; use zisk_core::ZiskOperationType; use crate::{generate_dma_mem_inputs, skip_dma_mem_inputs, DMA_64_ALIGNED_OPS_BY_ROW}; @@ -92,6 +92,49 @@ impl DmaCounterInputGen { } self.dma_ops += 1; } + + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + data_ext: &[u64], + mem_processors: &mut P, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if data[OP_TYPE] as u32 != ZiskOperationType::Dma as u32 { + return true; + } + + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_dma_mem_inputs(data, data_ext, true, mem_processors); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + if skip_dma_mem_inputs(data, data_ext, mem_processors) { + return true; + } + generate_dma_mem_inputs(data, data_ext, false, mem_processors); + } + } + + true + } } impl Metrics for DmaCounterInputGen { @@ -145,61 +188,6 @@ impl Add for DmaCounterInputGen { } impl BusDevice for DmaCounterInputGen { - /// Processes data received on the bus, updating counters and generating inputs when applicable. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus sending the data. - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - data_ext: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - debug_assert!(*bus_id == OPERATION_BUS_ID); - - if data[OP_TYPE] as u32 != ZiskOperationType::Dma as u32 { - return true; - } - - if let Some(mem_collectors_info) = mem_collector_info { - if skip_dma_mem_inputs(data, data_ext, mem_collectors_info) { - return true; - } - } - - match self.mode { - BusDeviceMode::Counter => { - self.measure(data); - generate_dma_mem_inputs(data, data_ext, true, pending); - } - BusDeviceMode::CounterAsm => { - self.measure(data); - } - BusDeviceMode::InputGenerator => { - generate_dma_mem_inputs(data, data_ext, false, pending); - } - } - - true - } - - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/dma/src/dma_gen_mem_inputs.rs b/precompiles/dma/src/dma_gen_mem_inputs.rs index bc4e72de4..35a62564e 100644 --- a/precompiles/dma/src/dma_gen_mem_inputs.rs +++ b/precompiles/dma/src/dma_gen_mem_inputs.rs @@ -1,16 +1,14 @@ use precompiles_common::MemBusHelpers; +use precompiles_common::MemProcessor; use precompiles_helpers::{DmaHelpers, DmaInfo}; -use std::collections::VecDeque; -use zisk_common::{ - BusId, MemCollectorInfo, A, B, DMA_ENCODED, OP, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP, -}; +use zisk_common::{A, B, DMA_ENCODED, OP, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; use zisk_core::{zisk_ops::ZiskOp, EXTRA_PARAMS}; -pub fn generate_dma_mem_inputs( +pub fn generate_dma_mem_inputs( data: &[u64], data_ext: &[u64], _only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { let dst = data[A]; let src = data[B]; @@ -31,10 +29,9 @@ pub fn generate_dma_mem_inputs( EXTRA_PARAMS as u32, main_step, DmaInfo::get_count(encoded) as u64, - pending, + mem_processors, ); - let mut wr_pending = VecDeque::new(); if pre_count > 0 { let pre_data_offset = DmaInfo::get_pre_data_offset(encoded); let read_value = data_ext[pre_data_offset]; @@ -42,7 +39,7 @@ pub fn generate_dma_mem_inputs( #[cfg(feature = "debug_dma")] println!("DMA: mem_aligned_load@pre 0x{src64:08X} S:{main_step} V:{read_value} (0x{read_value:016X})"); - MemBusHelpers::mem_aligned_load(src64, main_step, read_value, pending); + MemBusHelpers::mem_aligned_load(src64, main_step, read_value, mem_processors); // pre-load of write address before unaligned write let pre_value = data_ext[DmaInfo::get_pre_write_offset(encoded)]; @@ -50,7 +47,7 @@ pub fn generate_dma_mem_inputs( #[cfg(feature = "debug_dma")] println!("DMA: mem_aligned_load@pre-p 0x{dst64:08X} S:{main_step} V:{pre_value} (0x{pre_value:016X})"); - MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, pending); + MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, mem_processors); let write_value = if DmaInfo::is_double_read_pre(encoded) { let second_read_value = data_ext[pre_data_offset + 1]; @@ -59,7 +56,12 @@ pub fn generate_dma_mem_inputs( "DMA: mem_aligned_load@pre2 0x{:08X} S:{main_step} V:{second_read_value} (0x{second_read_value:016X})", src64 + 8 ); - MemBusHelpers::mem_aligned_load(src64 + 8, main_step, second_read_value, pending); + MemBusHelpers::mem_aligned_load( + src64 + 8, + main_step, + second_read_value, + mem_processors, + ); DmaHelpers::calculate_write_value( dst_offset, src_offset, @@ -79,7 +81,7 @@ pub fn generate_dma_mem_inputs( #[cfg(feature = "debug_dma")] println!("DMA: mem_aligned_write@pre 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); - MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, &mut wr_pending); + MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, mem_processors); } // this is part of words loop @@ -101,13 +103,13 @@ pub fn generate_dma_mem_inputs( #[cfg(feature = "debug_dma")] println!("DMA: mem_aligned_load_from_slice 0x{src64:08X} S:{main_step} V:{values:?}"); - MemBusHelpers::mem_aligned_load_from_slice(src64, main_step, values, pending); + MemBusHelpers::mem_aligned_load_from_slice(src64, main_step, values, mem_processors); let src_offset = (src_offset + pre_count) & 0x07; if aligned { #[cfg(feature = "debug_dma")] println!("DMA: mem_aligned_write_from_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); - MemBusHelpers::mem_aligned_write_from_slice(dst64, main_step, values, &mut wr_pending); + MemBusHelpers::mem_aligned_write_from_slice(dst64, main_step, values, mem_processors); } else { #[cfg(feature = "debug_dma")] println!("DMA: mem_aligned_write_from_read_unaligned_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); @@ -116,7 +118,7 @@ pub fn generate_dma_mem_inputs( main_step, src_offset as u8, values, - &mut wr_pending, + mem_processors, ); } } @@ -131,7 +133,7 @@ pub fn generate_dma_mem_inputs( #[cfg(feature = "debug_dma")] println!("DMA: mem_aligned_load@post 0x{src64:08X} S:{main_step} V:{read_value} (0x{read_value:016X})"); - MemBusHelpers::mem_aligned_load(src64, main_step, read_value, pending); + MemBusHelpers::mem_aligned_load(src64, main_step, read_value, mem_processors); // pre-load of write address before unaligned write let pre_value = data_ext[DmaInfo::get_post_write_offset(encoded)]; @@ -139,7 +141,7 @@ pub fn generate_dma_mem_inputs( #[cfg(feature = "debug_dma")] println!("DMA: mem_aligned_load@post-p 0x{dst64:08X} S:{main_step} V:{pre_value} (0x{pre_value:016X})"); - MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, pending); + MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, mem_processors); let write_value = if DmaInfo::is_double_read_post(encoded) { let second_read_value = data_ext[post_data_offset + 1]; @@ -148,7 +150,12 @@ pub fn generate_dma_mem_inputs( "DMA: mem_aligned_load@post2 0x{:08X} S:{main_step} V:{second_read_value} (0x{second_read_value:016X})", src64 + 8 ); - MemBusHelpers::mem_aligned_load(src64 + 8, main_step, second_read_value, pending); + MemBusHelpers::mem_aligned_load( + src64 + 8, + main_step, + second_read_value, + mem_processors, + ); DmaHelpers::calculate_write_value( 0, // in post offset it's 0 (src_offset + pre_count) & 0x07, // src_offset it's modified by pre, aligned/unaligned no change offset @@ -168,15 +175,14 @@ pub fn generate_dma_mem_inputs( #[cfg(feature = "debug_dma")] println!("DMA: mem_aligned_write@post 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); - MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, &mut wr_pending); + MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, mem_processors); } - pending.extend(wr_pending); } -pub fn skip_dma_mem_inputs( +pub fn skip_dma_mem_inputs( data: &[u64], _data_ext: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { let dst = data[A]; let src = data[B]; @@ -202,16 +208,16 @@ pub fn skip_dma_mem_inputs( let dst64_to = (dst + use_count + 7) as u32 & !0x07; let src64_to = (src + use_count + 7) as u32 & !0x07; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(EXTRA_PARAMS as u32) { - return false; - } - if !mem_collector.skip_addr_range(dst64_from, dst64_to) { - return false; - } - if !mem_collector.skip_addr_range(src64_from, src64_to) { - return false; - } + if !mem_processors.skip_addr(EXTRA_PARAMS as u32) { + return false; + } + + if !mem_processors.skip_addr_range(dst64_from, dst64_to) { + return false; + } + + if !mem_processors.skip_addr_range(src64_from, src64_to) { + return false; } // If any mem_collector includes this addresses we could skip this precompiles diff --git a/precompiles/dma/src/dma_manager.rs b/precompiles/dma/src/dma_manager.rs index 15bd934d5..57ff3add2 100644 --- a/precompiles/dma/src/dma_manager.rs +++ b/precompiles/dma/src/dma_manager.rs @@ -3,10 +3,7 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::ProofCtx; -use zisk_common::{ - BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, - PayloadType, Plan, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, Plan, Planner}; use zisk_pil::{Dma64AlignedTrace, DmaPrePostTrace, DmaTrace, DmaUnalignedTrace, ZiskProofValues}; use crate::{ @@ -52,14 +49,6 @@ impl DmaManager { } impl ComponentBuilder for DmaManager { - /// Builds and returns a new counter for monitoring Dma operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for Dma operations. - fn build_counter(&self) -> Option> { - Some(Box::new(DmaCounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan Dma-related instances. /// /// # Returns @@ -98,10 +87,6 @@ impl ComponentBuilder for DmaManager { } } - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(DmaCounterInputGen::new(BusDeviceMode::InputGenerator))) - } - fn configure_instances(&self, pctx: &ProofCtx, plannings: &[Plan]) { let enable_dma_64_aligned = plannings.iter().any(|p| p.air_id == Dma64AlignedTrace::::AIR_ID); diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs index 69e59103a..2f5d2344a 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs @@ -1,9 +1,9 @@ //! The `DmaPrePostCollector` module defines an collector to calculate all inputs of an instance //! for the DmaPrePost State Machine. -use std::{any::Any, collections::VecDeque}; +use std::any::Any; -use zisk_common::{BusDevice, BusId, CollectCounter, MemCollectorInfo, OPERATION_BUS_ID, OP_TYPE}; +use zisk_common::{BusDevice, BusId, CollectCounter, OPERATION_BUS_ID, OP_TYPE}; use zisk_core::ZiskOperationType; use crate::DmaPrePostInput; @@ -37,9 +37,7 @@ impl DmaPrePostCollector { collect_counter, } } -} -impl BusDevice for DmaPrePostCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -52,14 +50,7 @@ impl BusDevice for DmaPrePostCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -84,15 +75,9 @@ impl BusDevice for DmaPrePostCollector { // println!("DmaPrePostCollector::process_data3 input.len()={}", self.inputs.len()); self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for DmaPrePostCollector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs index 7208f5b57..b80ff0f63 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs @@ -6,8 +6,7 @@ use crate::DmaUnalignedInput; use std::any::Any; -use std::collections::VecDeque; -use zisk_common::{BusDevice, BusId, CollectCounter, MemCollectorInfo, OPERATION_BUS_ID, OP_TYPE}; +use zisk_common::{BusDevice, BusId, CollectCounter, OPERATION_BUS_ID, OP_TYPE}; use zisk_core::ZiskOperationType; pub struct DmaUnalignedCollector { @@ -48,9 +47,7 @@ impl DmaUnalignedCollector { last_segment_collector, } } -} -impl BusDevice for DmaUnalignedCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -63,14 +60,7 @@ impl BusDevice for DmaUnalignedCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.collect_counter.is_final_skip() { @@ -100,15 +90,9 @@ impl BusDevice for DmaUnalignedCollector { !self.collect_counter.is_final_skip() } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for DmaUnalignedCollector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/keccakf/src/keccakf_bus_device.rs b/precompiles/keccakf/src/keccakf_bus_device.rs index 7d190a2db..87ca467b2 100644 --- a/precompiles/keccakf/src/keccakf_bus_device.rs +++ b/precompiles/keccakf/src/keccakf_bus_device.rs @@ -2,12 +2,13 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::Keccakf` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; +use precompiles_common::MemProcessor; +use zisk_common::STEP; use zisk_common::{ BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, }; -use zisk_common::{MemCollectorInfo, STEP}; use zisk_core::ZiskOperationType; use crate::{generate_keccakf_mem_inputs, skip_keccakf_mem_inputs}; @@ -48,66 +49,23 @@ impl KeccakfCounterInputGen { pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { (op_type == ZiskOperationType::Keccak).then_some(self.counter.inst_count) } -} -impl Metrics for KeccakfCounterInputGen { - /// Tracks activity on the connected bus and updates counters for recognized operations. - /// - /// # Arguments - /// * `_bus_id` - The ID of the bus (unused in this implementation). - /// * `_data` - The data received from the bus. - /// - /// # Returns - /// An empty vector, as this implementation does not produce any derived inputs for the bus. - #[inline(always)] - fn measure(&mut self, _data: &[u64]) { - self.counter.update(1); - } - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl Add for KeccakfCounterInputGen { - type Output = KeccakfCounterInputGen; - - /// Combines two `KeccakfCounter` instances by summing their counters. - /// - /// # Arguments - /// * `self` - The first `KeccakfCounter` instance. - /// * `other` - The second `KeccakfCounter` instance. - /// - /// # Returns - /// A new `KeccakfCounter` with combined counters. - fn add(self, other: Self) -> KeccakfCounterInputGen { - KeccakfCounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } - } -} - -impl BusDevice for KeccakfCounterInputGen { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments /// * `bus_id` - The ID of the bus sending the data. /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. /// /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( + pub fn process_data( &mut self, bus_id: &BusId, data: &[u64], - _data_ext: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, + mem_processors: &mut P, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); @@ -115,39 +73,69 @@ impl BusDevice for KeccakfCounterInputGen { return true; } - if let Some(mem_collectors_info) = mem_collector_info { - if skip_keccakf_mem_inputs(data[B] as u32, mem_collectors_info) { - return true; - } - } - let step_main = data[STEP]; let addr_main = data[B] as u32; match self.mode { BusDeviceMode::Counter => { self.measure(data); - generate_keccakf_mem_inputs(addr_main, step_main, data, true, pending); + generate_keccakf_mem_inputs(addr_main, step_main, data, true, mem_processors); } BusDeviceMode::CounterAsm => { self.measure(data); } BusDeviceMode::InputGenerator => { - generate_keccakf_mem_inputs(addr_main, step_main, data, false, pending); + if skip_keccakf_mem_inputs(addr_main, mem_processors) { + return true; + } + generate_keccakf_mem_inputs(addr_main, step_main, data, false, mem_processors); } } true } +} + +impl Metrics for KeccakfCounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. + /// + /// # Returns + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); + } + + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for KeccakfCounterInputGen { + type Output = KeccakfCounterInputGen; - /// Returns the bus IDs associated with this counter. + /// Combines two `KeccakfCounter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `KeccakfCounter` instance. + /// * `other` - The second `KeccakfCounter` instance. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// A new `KeccakfCounter` with combined counters. + fn add(self, other: Self) -> KeccakfCounterInputGen { + KeccakfCounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } } +} +impl BusDevice for KeccakfCounterInputGen { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs b/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs index a7a6ea917..a35672ca7 100644 --- a/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs +++ b/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs @@ -1,10 +1,9 @@ -use std::collections::VecDeque; +use precompiles_common::MemProcessor; use tiny_keccak::keccakf; use precompiles_common::MemBusHelpers; -use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; #[derive(Debug)] pub struct KeccakfMemInputConfig { @@ -14,12 +13,12 @@ pub struct KeccakfMemInputConfig { pub chunks_per_param: usize, } -pub fn generate_keccakf_mem_inputs( +pub fn generate_keccakf_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // Get the basic data from the input // op,op_type,a,b,... @@ -59,23 +58,21 @@ pub fn generate_keccakf_mem_inputs( step_main, chunk_data, is_write, - pending, + mem_processors, ); } } } -pub fn skip_keccakf_mem_inputs(addr_main: u32, mem_collectors_info: &[MemCollectorInfo]) -> bool { +pub fn skip_keccakf_mem_inputs(addr_main: u32, mem_processors: &mut P) -> bool { let write_params = 1; let chunks_per_param = 25; for param_index in 0..write_params { let param_addr = addr_main + (param_index * 8 * chunks_per_param) as u32; for ichunk in 0..chunks_per_param { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } diff --git a/precompiles/keccakf/src/keccakf_instance.rs b/precompiles/keccakf/src/keccakf_instance.rs index 40badaee6..ba901967b 100644 --- a/precompiles/keccakf/src/keccakf_instance.rs +++ b/precompiles/keccakf/src/keccakf_instance.rs @@ -6,14 +6,10 @@ use crate::{KeccakfInput, KeccakfSM}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::{ - any::Any, - collections::{HashMap, VecDeque}, - sync::Arc, -}; +use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::{ BusDevice, BusId, CheckPoint, ChunkId, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, + InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; use zisk_pil::KeccakfTrace; @@ -162,9 +158,7 @@ impl KeccakfCollector { collect_skipper, } } -} -impl BusDevice for KeccakfCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -177,14 +171,7 @@ impl BusDevice for KeccakfCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -209,15 +196,9 @@ impl BusDevice for KeccakfCollector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for KeccakfCollector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/keccakf/src/keccakf_manager.rs b/precompiles/keccakf/src/keccakf_manager.rs index fab4a338a..28f9e97d0 100644 --- a/precompiles/keccakf/src/keccakf_manager.rs +++ b/precompiles/keccakf/src/keccakf_manager.rs @@ -2,11 +2,8 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{BusDevice, PayloadType}; -use zisk_common::{ - BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::KeccakfTrace; @@ -44,14 +41,6 @@ impl KeccakfManager { } impl ComponentBuilder for KeccakfManager { - /// Builds and returns a new counter for monitoring keccakf operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for keccakf operations. - fn build_counter(&self) -> Option> { - Some(Box::new(KeccakfCounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan keccakf-related instances. /// /// # Returns @@ -89,8 +78,4 @@ impl ComponentBuilder for KeccakfManager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(KeccakfCounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/precompiles/poseidon2/src/poseidon2_bus_device.rs b/precompiles/poseidon2/src/poseidon2_bus_device.rs index df35893e1..e206184c2 100644 --- a/precompiles/poseidon2/src/poseidon2_bus_device.rs +++ b/precompiles/poseidon2/src/poseidon2_bus_device.rs @@ -2,9 +2,10 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::Poseidon2` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; + +use precompiles_common::MemProcessor; -use zisk_common::MemCollectorInfo; use zisk_common::{ BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE, }; @@ -48,66 +49,23 @@ impl Poseidon2CounterInputGen { pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { (op_type == ZiskOperationType::Poseidon2).then_some(self.counter.inst_count) } -} - -impl Metrics for Poseidon2CounterInputGen { - /// Tracks activity on the connected bus and updates counters for recognized operations. - /// - /// # Arguments - /// * `_bus_id` - The ID of the bus (unused in this implementation). - /// * `_data` - The data received from the bus. - /// - /// # Returns - /// An empty vector, as this implementation does not produce any derived inputs for the bus. - #[inline(always)] - fn measure(&mut self, _data: &[u64]) { - self.counter.update(1); - } - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl Add for Poseidon2CounterInputGen { - type Output = Poseidon2CounterInputGen; - - /// Combines two `Poseidon2Counter` instances by summing their counters. - /// - /// # Arguments - /// * `self` - The first `Poseidon2Counter` instance. - /// * `other` - The second `Poseidon2Counter` instance. - /// - /// # Returns - /// A new `Poseidon2Counter` with combined counters. - fn add(self, other: Self) -> Poseidon2CounterInputGen { - Poseidon2CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } - } -} -impl BusDevice for Poseidon2CounterInputGen { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments /// * `bus_id` - The ID of the bus sending the data. /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. /// /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( + pub fn process_data( &mut self, bus_id: &BusId, data: &[u64], - _data_ext: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, + mem_processors: &mut P, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); @@ -115,39 +73,69 @@ impl BusDevice for Poseidon2CounterInputGen { return true; } - if let Some(mem_collectors_info) = mem_collector_info { - if skip_poseidon2_mem_inputs(data[B] as u32, mem_collectors_info) { - return true; - } - } - let step_main = data[A]; let addr_main = data[B] as u32; match self.mode { BusDeviceMode::Counter => { self.measure(data); - generate_poseidon2_mem_inputs(addr_main, step_main, data, true, pending); + generate_poseidon2_mem_inputs(addr_main, step_main, data, true, mem_processors); } BusDeviceMode::CounterAsm => { self.measure(data); } BusDeviceMode::InputGenerator => { - generate_poseidon2_mem_inputs(addr_main, step_main, data, false, pending); + if skip_poseidon2_mem_inputs(addr_main, mem_processors) { + return true; + } + generate_poseidon2_mem_inputs(addr_main, step_main, data, false, mem_processors); } } true } +} - /// Returns the bus IDs associated with this counter. +impl Metrics for Poseidon2CounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); } + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for Poseidon2CounterInputGen { + type Output = Poseidon2CounterInputGen; + + /// Combines two `Poseidon2Counter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `Poseidon2Counter` instance. + /// * `other` - The second `Poseidon2Counter` instance. + /// + /// # Returns + /// A new `Poseidon2Counter` with combined counters. + fn add(self, other: Self) -> Poseidon2CounterInputGen { + Poseidon2CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } + } +} + +impl BusDevice for Poseidon2CounterInputGen { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs b/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs index 525f037f4..9996be979 100644 --- a/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs +++ b/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs @@ -1,8 +1,8 @@ use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; use precompiles_common::MemBusHelpers; -use std::collections::VecDeque; -use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; +use precompiles_common::MemProcessor; + +use zisk_common::OPERATION_BUS_DATA_SIZE; #[derive(Debug)] pub struct Poseidon2MemInputConfig { @@ -12,12 +12,12 @@ pub struct Poseidon2MemInputConfig { pub chunks_per_param: usize, } -pub fn generate_poseidon2_mem_inputs( +pub fn generate_poseidon2_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // Get the basic data from the input // op,op_type,a,b,... @@ -60,23 +60,21 @@ pub fn generate_poseidon2_mem_inputs( step_main, chunk_data, is_write, - pending, + mem_processors, ); } } } -pub fn skip_poseidon2_mem_inputs(addr_main: u32, mem_collectors_info: &[MemCollectorInfo]) -> bool { +pub fn skip_poseidon2_mem_inputs(addr_main: u32, mem_processors: &mut P) -> bool { let write_params = 1; let chunks_per_param = 16; for param_index in 0..write_params { let param_addr = addr_main + (param_index * 8 * chunks_per_param) as u32; for ichunk in 0..chunks_per_param { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } diff --git a/precompiles/poseidon2/src/poseidon2_instance.rs b/precompiles/poseidon2/src/poseidon2_instance.rs index df1b5c35a..90b6882a2 100644 --- a/precompiles/poseidon2/src/poseidon2_instance.rs +++ b/precompiles/poseidon2/src/poseidon2_instance.rs @@ -7,12 +7,11 @@ use crate::{Poseidon2Input, Poseidon2SM}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::collections::VecDeque; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, + InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; use zisk_pil::Poseidon2Trace; @@ -151,9 +150,7 @@ impl Poseidon2Collector { collect_skipper, } } -} -impl BusDevice for Poseidon2Collector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -166,14 +163,7 @@ impl BusDevice for Poseidon2Collector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -198,15 +188,9 @@ impl BusDevice for Poseidon2Collector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for Poseidon2Collector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/poseidon2/src/poseidon2_manager.rs b/precompiles/poseidon2/src/poseidon2_manager.rs index 10ac169ec..a025941c8 100644 --- a/precompiles/poseidon2/src/poseidon2_manager.rs +++ b/precompiles/poseidon2/src/poseidon2_manager.rs @@ -1,10 +1,7 @@ use std::sync::Arc; use fields::PrimeField64; -use zisk_common::{ - BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, - InstanceInfo, PayloadType, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::Poseidon2Trace; @@ -42,14 +39,6 @@ impl Poseidon2Manager { } impl ComponentBuilder for Poseidon2Manager { - /// Builds and returns a new counter for monitoring poseidon2 operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for poseidon2 operations. - fn build_counter(&self) -> Option> { - Some(Box::new(Poseidon2CounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan poseidon2-related instances. /// /// # Returns @@ -90,8 +79,4 @@ impl ComponentBuilder for Poseidon2Manager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(Poseidon2CounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/precompiles/sha256f/src/sha256f_bus_device.rs b/precompiles/sha256f/src/sha256f_bus_device.rs index aa9120595..a9814a51c 100644 --- a/precompiles/sha256f/src/sha256f_bus_device.rs +++ b/precompiles/sha256f/src/sha256f_bus_device.rs @@ -2,12 +2,13 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::Sha256f` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; +use precompiles_common::MemProcessor; +use zisk_common::STEP; use zisk_common::{ BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, }; -use zisk_common::{MemCollectorInfo, STEP}; use zisk_core::ZiskOperationType; use crate::{generate_sha256f_mem_inputs, skip_sha256f_mem_inputs}; @@ -48,66 +49,23 @@ impl Sha256fCounterInputGen { pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { (op_type == ZiskOperationType::Sha256).then_some(self.counter.inst_count) } -} -impl Metrics for Sha256fCounterInputGen { - /// Tracks activity on the connected bus and updates counters for recognized operations. - /// - /// # Arguments - /// * `_bus_id` - The ID of the bus (unused in this implementation). - /// * `_data` - The data received from the bus. - /// - /// # Returns - /// An empty vector, as this implementation does not produce any derived inputs for the bus. - #[inline(always)] - fn measure(&mut self, _data: &[u64]) { - self.counter.update(1); - } - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl Add for Sha256fCounterInputGen { - type Output = Sha256fCounterInputGen; - - /// Combines two `Sha256fCounter` instances by summing their counters. - /// - /// # Arguments - /// * `self` - The first `Sha256fCounter` instance. - /// * `other` - The second `Sha256fCounter` instance. - /// - /// # Returns - /// A new `Sha256fCounter` with combined counters. - fn add(self, other: Self) -> Sha256fCounterInputGen { - Sha256fCounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } - } -} - -impl BusDevice for Sha256fCounterInputGen { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments /// * `bus_id` - The ID of the bus sending the data. /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. /// /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( + pub fn process_data( &mut self, bus_id: &BusId, data: &[u64], - _data_ext: &[u64], - pending: &mut VecDeque<(BusId, Vec, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, + mem_processors: &mut P, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); @@ -115,39 +73,69 @@ impl BusDevice for Sha256fCounterInputGen { return true; } - if let Some(mem_collectors_info) = mem_collector_info { - if skip_sha256f_mem_inputs(data[B] as u32, data, mem_collectors_info) { - return true; - } - } - let step_main = data[STEP]; let addr_main = data[B] as u32; match self.mode { BusDeviceMode::Counter => { self.measure(data); - generate_sha256f_mem_inputs(addr_main, step_main, data, true, pending); + generate_sha256f_mem_inputs(addr_main, step_main, data, true, mem_processors); } BusDeviceMode::CounterAsm => { self.measure(data); } BusDeviceMode::InputGenerator => { - generate_sha256f_mem_inputs(addr_main, step_main, data, false, pending); + if skip_sha256f_mem_inputs(addr_main, data, mem_processors) { + return true; + } + generate_sha256f_mem_inputs(addr_main, step_main, data, false, mem_processors); } } true } +} + +impl Metrics for Sha256fCounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. + /// + /// # Returns + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); + } + + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for Sha256fCounterInputGen { + type Output = Sha256fCounterInputGen; - /// Returns the bus IDs associated with this counter. + /// Combines two `Sha256fCounter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `Sha256fCounter` instance. + /// * `other` - The second `Sha256fCounter` instance. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// A new `Sha256fCounter` with combined counters. + fn add(self, other: Self) -> Sha256fCounterInputGen { + Sha256fCounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } } +} +impl BusDevice for Sha256fCounterInputGen { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs index ae030aa60..38998b8d7 100644 --- a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs +++ b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs @@ -1,7 +1,7 @@ use precompiles_common::MemBusHelpers; -use std::collections::VecDeque; -use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; +use precompiles_common::MemProcessor; + +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; use zisk_core::sha256f; #[derive(Debug)] @@ -13,12 +13,12 @@ pub struct Sha256MemInputConfig { pub chunks_per_param: usize, } -pub fn generate_sha256f_mem_inputs( +pub fn generate_sha256f_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec, Vec)>, + mem_processors: &mut P, ) { // Get the basic data from the input // op,op_type,a,b,addr[2],... @@ -37,7 +37,7 @@ pub fn generate_sha256f_mem_inputs( addr_main + iparam as u32 * 8, step_main, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], - pending, + mem_processors, ); } @@ -75,16 +75,16 @@ pub fn generate_sha256f_mem_inputs( step_main, chunk_data, is_write, - pending, + mem_processors, ); } } } -pub fn skip_sha256f_mem_inputs( +pub fn skip_sha256f_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { let indirect_params = 2; let read_params = 2; @@ -93,10 +93,8 @@ pub fn skip_sha256f_mem_inputs( for iparam in 0..indirect_params { let addr = addr_main + iparam as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } @@ -107,10 +105,8 @@ pub fn skip_sha256f_mem_inputs( for ichunk in 0..chunks { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } diff --git a/precompiles/sha256f/src/sha256f_instance.rs b/precompiles/sha256f/src/sha256f_instance.rs index f6b9f8f53..4da9f97c8 100644 --- a/precompiles/sha256f/src/sha256f_instance.rs +++ b/precompiles/sha256f/src/sha256f_instance.rs @@ -7,12 +7,11 @@ use crate::{Sha256fInput, Sha256fSM}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::collections::VecDeque; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, + InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; use zisk_pil::Sha256fTrace; @@ -149,9 +148,7 @@ impl Sha256fCollector { collect_skipper, } } -} -impl BusDevice for Sha256fCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -164,14 +161,7 @@ impl BusDevice for Sha256fCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -196,15 +186,9 @@ impl BusDevice for Sha256fCollector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for Sha256fCollector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/sha256f/src/sha256f_manager.rs b/precompiles/sha256f/src/sha256f_manager.rs index 1c55ec19a..03312d2e1 100644 --- a/precompiles/sha256f/src/sha256f_manager.rs +++ b/precompiles/sha256f/src/sha256f_manager.rs @@ -2,10 +2,7 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{ - BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, - InstanceInfo, PayloadType, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::Sha256fTrace; @@ -43,14 +40,6 @@ impl Sha256fManager { } impl ComponentBuilder for Sha256fManager { - /// Builds and returns a new counter for monitoring sha256f operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for sha256f operations. - fn build_counter(&self) -> Option> { - Some(Box::new(Sha256fCounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan sha256f-related instances. /// /// # Returns @@ -88,8 +77,4 @@ impl ComponentBuilder for Sha256fManager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(Sha256fCounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/state-machines/arith/src/arith.rs b/state-machines/arith/src/arith.rs index bf41af61f..103f3bb85 100644 --- a/state-machines/arith/src/arith.rs +++ b/state-machines/arith/src/arith.rs @@ -10,10 +10,7 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{ - BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, - InstanceInfo, PayloadType, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::ArithTrace; @@ -50,14 +47,6 @@ impl ArithSM { } impl ComponentBuilder for ArithSM { - /// Builds and returns a new counter for monitoring arithmetic operations. - /// - /// # Returns - /// A boxed implementation of `ArithCounter`. - fn build_counter(&self) -> Option> { - Some(Box::new(ArithCounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan arithmetic-related instances. /// /// # Returns @@ -86,12 +75,4 @@ impl ComponentBuilder for ArithSM { _ => panic!("BinarySM::get_instance() Unsupported air_id: {:?}", ictx.plan.air_id), } } - - /// Creates and returns an input generator for arithmetic state machine computations. - /// - /// # Returns - /// A boxed implementation of `ArithInputGenerator`. - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(ArithCounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/state-machines/arith/src/arith_bus_device.rs b/state-machines/arith/src/arith_bus_device.rs index d298b52d3..f59970ce4 100644 --- a/state-machines/arith/src/arith_bus_device.rs +++ b/state-machines/arith/src/arith_bus_device.rs @@ -9,8 +9,7 @@ use fields::Goldilocks; use std::collections::VecDeque; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, MemCollectorInfo, Metrics, A, B, OP, - OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OP, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; @@ -55,31 +54,7 @@ impl ArithCounterInputGen { pub fn frops_count(&self, op_type: ZiskOperationType) -> Option { (op_type == ZiskOperationType::Arith).then_some(self.counter.frops_count) } -} - -impl Metrics for ArithCounterInputGen { - /// Tracks activity on the connected bus and updates counters for recognized operations. - /// - /// # Arguments - /// * `data` - The data received from the bus. - /// - /// # Returns - /// An empty vector, as this implementation does not produce any derived inputs for the bus. - #[inline(always)] - fn measure(&mut self, _data: &[u64]) { - self.counter.update(1); - } - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} -impl BusDevice for ArithCounterInputGen { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments @@ -91,13 +66,11 @@ impl BusDevice for ArithCounterInputGen { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( + pub fn process_data( &mut self, bus_id: &BusId, data: &[u64], - _data_ext: &[u64], pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); @@ -127,15 +100,31 @@ impl BusDevice for ArithCounterInputGen { true } +} + +impl Metrics for ArithCounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `data` - The data received from the bus. + /// + /// # Returns + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); + } - /// Returns the bus IDs associated with this counter. + /// Provides a dynamic reference for downcasting purposes. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self } +} +impl BusDevice for ArithCounterInputGen { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/arith/src/arith_full_instance.rs b/state-machines/arith/src/arith_full_instance.rs index b833d1f40..c84cf545c 100644 --- a/state-machines/arith/src/arith_full_instance.rs +++ b/state-machines/arith/src/arith_full_instance.rs @@ -8,14 +8,10 @@ use crate::{ArithFrops, ArithFullSM}; use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::{ - collections::{HashMap, VecDeque}, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; use zisk_common::{ BusDevice, BusId, CheckPoint, ChunkId, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, OperationData, PayloadType, A, B, OP, OPERATION_BUS_ID, - OP_TYPE, + InstanceType, OperationData, PayloadType, A, B, OP, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; use zisk_pil::ArithTrace; @@ -198,9 +194,7 @@ impl ArithInstanceCollector { frops_table_id, } } -} -impl BusDevice for ArithInstanceCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -212,14 +206,7 @@ impl BusDevice for ArithInstanceCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); let instance_complete = self.inputs.len() == self.num_operations as usize; @@ -255,15 +242,9 @@ impl BusDevice for ArithInstanceCollector { self.inputs.len() < self.num_operations as usize || self.force_execute_to_end } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for ArithInstanceCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/binary/src/binary.rs b/state-machines/binary/src/binary.rs index cc3c0c6e6..4d6798af8 100644 --- a/state-machines/binary/src/binary.rs +++ b/state-machines/binary/src/binary.rs @@ -15,7 +15,7 @@ use crate::{ }; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{BusDeviceMetrics, ComponentBuilder, Instance, InstanceCtx, Planner}; +use zisk_common::{ComponentBuilder, Instance, InstanceCtx, Planner}; use zisk_pil::{BinaryAddTrace, BinaryExtensionTrace, BinaryTrace}; /// The `BinarySM` struct represents the Binary State Machine, @@ -58,15 +58,6 @@ impl BinarySM { } impl ComponentBuilder for BinarySM { - /// Builds and returns a new counter for monitoring binary operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for binary and extension binary - /// operations. - fn build_counter(&self) -> Option> { - Some(Box::new(BinaryCounter::new())) - } - /// Builds a planner to plan binary-related instances. /// /// # Returns diff --git a/state-machines/binary/src/binary_add_collector.rs b/state-machines/binary/src/binary_add_collector.rs index 18024e297..6827464d9 100644 --- a/state-machines/binary/src/binary_add_collector.rs +++ b/state-machines/binary/src/binary_add_collector.rs @@ -1,10 +1,9 @@ //! The `BinaryAddCollector` struct represents an input collector for binary add operations. use crate::BinaryBasicFrops; -use std::collections::VecDeque; use zisk_common::{ - BusDevice, BusId, CollectSkipper, ExtOperationData, MemCollectorInfo, OperationBusData, A, B, - OP, OPERATION_BUS_ID, + BusDevice, BusId, CollectSkipper, ExtOperationData, OperationBusData, A, B, OP, + OPERATION_BUS_ID, }; use zisk_core::zisk_ops::ZiskOp; @@ -57,9 +56,7 @@ impl BinaryAddCollector { std, } } -} -impl BusDevice for BinaryAddCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -71,14 +68,7 @@ impl BusDevice for BinaryAddCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); let instance_complete = self.inputs.len() == self.num_operations; @@ -115,15 +105,9 @@ impl BusDevice for BinaryAddCollector { self.inputs.len() < self.num_operations || self.force_execute_to_end } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for BinaryAddCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/binary/src/binary_basic_collector.rs b/state-machines/binary/src/binary_basic_collector.rs index c2a0e5def..841cf5cbc 100644 --- a/state-machines/binary/src/binary_basic_collector.rs +++ b/state-machines/binary/src/binary_basic_collector.rs @@ -2,12 +2,10 @@ //! //! It manages collected inputs for the `BinaryExtensionSM` to compute witnesses -use std::collections::VecDeque; - use crate::{BinaryBasicFrops, BinaryInput}; use zisk_common::{ - BusDevice, BusId, CollectSkipper, ExtOperationData, MemCollectorInfo, OperationBusData, A, B, - OP, OPERATION_BUS_ID, + BusDevice, BusId, CollectSkipper, ExtOperationData, OperationBusData, A, B, OP, + OPERATION_BUS_ID, }; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; @@ -67,9 +65,7 @@ impl BinaryBasicCollector { std, } } -} -impl BusDevice for BinaryBasicCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -81,14 +77,7 @@ impl BusDevice for BinaryBasicCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); let instance_complete = self.inputs.len() == self.num_operations; @@ -128,15 +117,9 @@ impl BusDevice for BinaryBasicCollector { self.inputs.len() < self.num_operations || self.force_execute_to_end } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for BinaryBasicCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/binary/src/binary_counter.rs b/state-machines/binary/src/binary_counter.rs index 036cfb77e..3d00b8264 100644 --- a/state-machines/binary/src/binary_counter.rs +++ b/state-machines/binary/src/binary_counter.rs @@ -6,10 +6,7 @@ //! the system bus for both monitoring and input generation. use crate::{BinaryBasicFrops, BinaryExtensionFrops}; -use std::collections::VecDeque; -use zisk_common::{ - BusDevice, BusId, Counter, MemCollectorInfo, Metrics, A, B, OP, OPERATION_BUS_ID, OP_TYPE, -}; +use zisk_common::{BusDevice, BusId, Counter, Metrics, A, B, OP, OPERATION_BUS_ID, OP_TYPE}; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; /// The `BinaryCounter` struct represents a counter that monitors and measures @@ -40,6 +37,25 @@ impl BinaryCounter { pub fn new() -> Self { Self::default() } + + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + self.measure(data); + + true + } } impl Metrics for BinaryCounter { @@ -91,40 +107,6 @@ impl Metrics for BinaryCounter { } impl BusDevice for BinaryCounter { - /// Processes data received on the bus, updating counters and generating inputs when applicable. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus sending the data. - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - debug_assert!(*bus_id == OPERATION_BUS_ID); - - self.measure(data); - - true - } - - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/binary/src/binary_extension_collector.rs b/state-machines/binary/src/binary_extension_collector.rs index d85ee1590..733f65232 100644 --- a/state-machines/binary/src/binary_extension_collector.rs +++ b/state-machines/binary/src/binary_extension_collector.rs @@ -2,12 +2,10 @@ //! //! It manages collected inputs for the `BinaryExtensionSM` to compute witnesses -use std::collections::VecDeque; - use crate::{BinaryExtensionFrops, BinaryInput}; use zisk_common::{ - BusDevice, BusId, CollectSkipper, ExtOperationData, MemCollectorInfo, OperationBusData, A, B, - OP, OPERATION_BUS_ID, + BusDevice, BusId, CollectSkipper, ExtOperationData, OperationBusData, A, B, OP, + OPERATION_BUS_ID, }; use fields::PrimeField64; @@ -53,9 +51,7 @@ impl BinaryExtensionCollector { std, } } -} -impl BusDevice for BinaryExtensionCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -67,14 +63,7 @@ impl BusDevice for BinaryExtensionCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); let instance_complete = self.inputs.len() == self.num_operations; @@ -111,15 +100,9 @@ impl BusDevice for BinaryExtensionCollector { self.inputs.len() < self.num_operations || self.force_execute_to_end } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for BinaryExtensionCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/main/src/main_counter.rs b/state-machines/main/src/main_counter.rs index 14418d60b..f939569b4 100644 --- a/state-machines/main/src/main_counter.rs +++ b/state-machines/main/src/main_counter.rs @@ -2,8 +2,7 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::PubOut` instructions. -use std::collections::VecDeque; -use zisk_common::{BusDevice, BusId, MemCollectorInfo, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE}; +use zisk_common::{BusDevice, BusId, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE}; use zisk_core::ZiskOperationType; /// The `MainCounter` struct represents a counter that monitors and measures @@ -33,22 +32,7 @@ impl MainCounter { pub fn new() -> Self { Self { publics: Vec::new() } } -} - -impl Metrics for MainCounter { - #[inline(always)] - fn measure(&mut self, _data: &[u64]) {} - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} -impl BusDevice for MainCounter { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments @@ -60,14 +44,7 @@ impl BusDevice for MainCounter { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); const PUBOUT: u64 = ZiskOperationType::PubOut as u64; @@ -84,15 +61,22 @@ impl BusDevice for MainCounter { true } +} + +impl Metrics for MainCounter { + #[inline(always)] + fn measure(&mut self, _data: &[u64]) {} - /// Returns the bus IDs associated with this counter. + /// Provides a dynamic reference for downcasting purposes. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self } +} +impl BusDevice for MainCounter { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/main/src/main_sm.rs b/state-machines/main/src/main_sm.rs index 5f442045d..ec1506ba7 100644 --- a/state-machines/main/src/main_sm.rs +++ b/state-machines/main/src/main_sm.rs @@ -9,13 +9,12 @@ use std::sync::Arc; -use crate::MainCounter; use fields::PrimeField64; use mem_common::{MemHelpers, MEM_REGS_MAX_DIFF, MEM_STEPS_BY_MAIN_STEP}; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofCtx, ProofmanResult, SetupCtx}; use rayon::prelude::*; -use zisk_common::{BusDeviceMetrics, EmuTrace, InstanceCtx, SegmentId}; +use zisk_common::{EmuTrace, InstanceCtx, SegmentId}; use zisk_core::{ZiskRom, DEFAULT_MAX_STEPS, REGS_IN_MAIN, REGS_IN_MAIN_FROM, REGS_IN_MAIN_TO}; use zisk_pil::MainAirValues; use ziskemu::{Emu, EmuRegTrace}; @@ -373,8 +372,4 @@ impl MainSM { pub fn debug(_pctx: &ProofCtx, _sctx: &SetupCtx) { // No debug information to display } - - pub fn build_counter() -> Box { - Box::new(MainCounter::new()) - } } diff --git a/state-machines/mem-common/src/mem_counters.rs b/state-machines/mem-common/src/mem_counters.rs index d76c972af..030928a06 100644 --- a/state-machines/mem-common/src/mem_counters.rs +++ b/state-machines/mem-common/src/mem_counters.rs @@ -2,18 +2,12 @@ use rayon::prelude::*; #[cfg(feature = "save_mem_bus_data")] use std::{env, io::Write, slice}; -use std::{ - collections::{HashMap, VecDeque}, - fs::File, - io::Read, -}; +use std::{collections::HashMap, fs::File, io::Read}; use zisk_common::ChunkId; use crate::{MemAlignCounters, MemHelpers}; use std::fmt; -use zisk_common::{ - BusDevice, BusId, MemBusData, MemCollectorInfo, Metrics, MEM_BUS_DATA_SIZE, MEM_BUS_ID, -}; +use zisk_common::{BusDevice, BusId, MemBusData, Metrics, MEM_BUS_DATA_SIZE, MEM_BUS_ID}; const ST_BITS_OFFSET: u32 = 30; const ST_INI: u8 = 0; @@ -309,29 +303,9 @@ impl MemCounters { self.mem_measure(data); } } -} - -impl Metrics for MemCounters { - #[inline(always)] - fn measure(&mut self, data: &[u64]) { - self.mem_measure(data); - } - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl BusDevice for MemCounters { #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(bus_id == &MEM_BUS_ID); #[cfg(feature = "save_mem_bus_data")] @@ -344,17 +318,22 @@ impl BusDevice for MemCounters { true } +} - fn bus_id(&self) -> Vec { - vec![MEM_BUS_ID] +impl Metrics for MemCounters { + #[inline(always)] + fn measure(&mut self, data: &[u64]) { + self.mem_measure(data); } - /// Provides a dynamic reference for downcasting purposes. - fn as_any(self: Box) -> Box { + fn as_any(&self) -> &dyn std::any::Any { self } +} - fn on_close(&mut self) { - self.close(); +impl BusDevice for MemCounters { + /// Provides a dynamic reference for downcasting purposes. + fn as_any(self: Box) -> Box { + self } } diff --git a/state-machines/mem/src/mem.rs b/state-machines/mem/src/mem.rs index 510b02b35..0cc8d0102 100644 --- a/state-machines/mem/src/mem.rs +++ b/state-machines/mem/src/mem.rs @@ -9,7 +9,7 @@ use fields::PrimeField64; use mem_common::MemCounters; use pil_std_lib::Std; use proofman_common::ProofCtx; -use zisk_common::{BusDeviceMetrics, ComponentBuilder, Instance, InstanceCtx, Plan, Planner}; +use zisk_common::{ComponentBuilder, Instance, InstanceCtx, Plan, Planner}; use zisk_pil::{ InputDataTrace, MemAlignByteTrace, MemAlignReadByteTrace, MemAlignTrace, MemAlignWriteByteTrace, MemTrace, RomDataTrace, ZiskProofValues, @@ -46,10 +46,6 @@ impl Mem { } impl ComponentBuilder for Mem { - fn build_counter(&self) -> Option> { - Some(Box::new(MemCounters::new())) - } - fn build_planner(&self) -> Box { Box::new(MemPlanner::new()) } diff --git a/state-machines/mem/src/mem_align_collector.rs b/state-machines/mem/src/mem_align_collector.rs index f5db472d4..018902cdb 100644 --- a/state-machines/mem/src/mem_align_collector.rs +++ b/state-machines/mem/src/mem_align_collector.rs @@ -1,10 +1,7 @@ use crate::MemAlignInput; use mem_common::{MemAlignCheckPoint, MemHelpers}; -use std::collections::VecDeque; -use zisk_common::{ - BusDevice, BusId, ChunkId, CollectCounter, MemBusData, MemCollectorInfo, MEM_BUS_ID, -}; +use zisk_common::{BusDevice, BusId, ChunkId, CollectCounter, MemBusData, MEM_BUS_ID}; pub struct MemAlignCollector { /// Collected inputs @@ -65,18 +62,9 @@ impl MemAlignCollector { + self.read_byte.count() + self.write_byte.count() } -} -impl BusDevice for MemAlignCollector { #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == MEM_BUS_ID); let bytes = MemBusData::get_bytes(data); @@ -130,11 +118,9 @@ impl BusDevice for MemAlignCollector { }; true } +} - fn bus_id(&self) -> Vec { - vec![MEM_BUS_ID] - } - +impl BusDevice for MemAlignCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/mem/src/mem_module_collector.rs b/state-machines/mem/src/mem_module_collector.rs index f8eddf80f..1575a0e1a 100644 --- a/state-machines/mem/src/mem_module_collector.rs +++ b/state-machines/mem/src/mem_module_collector.rs @@ -1,8 +1,6 @@ -use std::collections::VecDeque; - use crate::{MemInput, MemPreviousSegment}; use mem_common::{MemHelpers, MemModuleCheckPoint, MEM_BYTES, MEM_BYTES_BITS}; -use zisk_common::{BusDevice, BusId, MemBusData, MemCollectorInfo, SegmentId, MEM_BUS_ID}; +use zisk_common::{BusDevice, BusId, MemBusData, SegmentId, MEM_BUS_ID}; #[derive(Debug, PartialEq, Eq)] enum InputAction { @@ -469,21 +467,22 @@ impl MemModuleCollector { } } - pub fn get_mem_collector_info(&self) -> MemCollectorInfo { - MemCollectorInfo { from_addr: self.filter_min_addr, to_addr: self.filter_max_addr } + pub fn skip_addr(&self, addr: u32) -> bool { + if addr > self.filter_max_addr || addr < self.filter_min_addr { + return true; + } + false + } + + pub fn skip_addr_range(&self, addr_from: u32, addr_to: u32) -> bool { + if addr_from > self.filter_max_addr || addr_to < self.filter_min_addr { + return true; + } + false } -} -impl BusDevice for MemModuleCollector { #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == MEM_BUS_ID); let addr = MemBusData::get_addr(data); @@ -493,11 +492,9 @@ impl BusDevice for MemModuleCollector { } true } +} - fn bus_id(&self) -> Vec { - vec![MEM_BUS_ID] - } - +impl BusDevice for MemModuleCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/mem/src/mem_test.rs b/state-machines/mem/src/mem_test.rs index e649e54c1..97a803c14 100644 --- a/state-machines/mem/src/mem_test.rs +++ b/state-machines/mem/src/mem_test.rs @@ -1,9 +1,9 @@ #![cfg(test)] -use std::{collections::VecDeque, sync::Arc}; +use std::sync::Arc; use crate::{MemModulePlanner, MemModulePlannerConfig, MemPlanCalculator}; use mem_common::{MemCounters, MEMORY_LOAD_OP, MEMORY_STORE_OP}; -use zisk_common::{BusDevice, ChunkId, Plan, MEM_BUS_ID}; +use zisk_common::{ChunkId, Plan, MEM_BUS_ID}; fn generate_test_plans( from_addr: u32, @@ -44,9 +44,6 @@ fn add_test_aligned_mem_reads( counter.process_data( &MEM_BUS_ID, &[MEMORY_LOAD_OP as u64, addr as u64, step + i * step_delta, 8, value], - &[], - &mut VecDeque::new(), - None, ); } } @@ -73,13 +70,7 @@ fn add_mem_data( let mut step = step; let op = if is_write { MEMORY_STORE_OP } else { MEMORY_LOAD_OP } as u64; for i in 0..count { - counter.process_data( - &MEM_BUS_ID, - &[op, addr, step, width, value], - &[], - &mut VecDeque::new(), - None, - ); + counter.process_data(&MEM_BUS_ID, &[op, addr, step, width, value]); if config.step_cycle > 0 { if i > 0 && (config.step_cycle % i) == 0 { step += config.step_delta; @@ -106,23 +97,11 @@ fn add_mem_data( // } fn add_mem_read64(counter: &mut MemCounters, addr: u32, step: u64, value: u64) { - counter.process_data( - &MEM_BUS_ID, - &[MEMORY_LOAD_OP as u64, addr as u64, step, 8, value], - &[], - &mut VecDeque::new(), - None, - ); + counter.process_data(&MEM_BUS_ID, &[MEMORY_LOAD_OP as u64, addr as u64, step, 8, value]); } fn add_mem_write64(counter: &mut MemCounters, addr: u32, step: u64, value: u64) { - counter.process_data( - &MEM_BUS_ID, - &[MEMORY_STORE_OP as u64, addr as u64, step, 8, value], - &[], - &mut VecDeque::new(), - None, - ); + counter.process_data(&MEM_BUS_ID, &[MEMORY_STORE_OP as u64, addr as u64, step, 8, value]); } #[test] @@ -174,38 +153,5 @@ fn test_counters() { assert_eq!(format!("{counter:?}"), "[MEM_0,#:10 => 0x80000000:2 0x80000008:2 0x80000010:2 0x80000018:2 0x80000020:2 0x80000028:2 0x80000030:2 0x80000038:2 0x80000040:2 0x80000048:2][MEM_1,#:10 => 0x90000000:1 0x90000008:1 0x90000010:1 0x90000018:1 0x90000020:1 0x90000028:1 0x90000030:1 0x90000038:1 0x90000040:1 0x90000048:1][MEM_2,#:4 => 0xA0000000:7 0xA0000008:2 0xA0000010:1 0xA0000018:1]"); } -/* -#[test] -fn test_mem() { - let mem_sm = MemSM::new(); - let std_sm = - - let mem_bus_device = >::build_counter(&mem_sm); - - let mut data_bus = DataBus::::new(); - data_bus.connect_device( - vec![OPERATION_BUS_ID], - Box::new(BusDeviceMetricsWrapper::new(arith_bus_device, false)), - ); - - let data = vec![ - (OPERATION_BUS_ID, OperationBusData::from_values(Mul as u8, Arith as u64, 1, 2).into()), - (OPERATION_BUS_ID, OperationBusData::from_values(Div as u8, Arith as u64, 1, 2).into()), - (OPERATION_BUS_ID, OperationBusData::from_values(Add as u8, Binary as u64, 1, 2).into()), - (OPERATION_BUS_ID, OperationBusData::from_values(Sub as u8, Binary as u64, 1, 2).into()), - ]; - - DataBusPlayer::play(&mut data_bus, data); - - let arith_counter = data_bus.devices.remove(0).inner; - - let arith_planner = - >::build_planner(&arith_sm); - - let plan = arith_planner.plan(vec![(0, arith_counter)]); - - println!("Plan: {:?}", plan); -} -*/ #[test] fn full() {} diff --git a/state-machines/rom/src/rom.rs b/state-machines/rom/src/rom.rs index c7c230bfd..20ba0a190 100644 --- a/state-machines/rom/src/rom.rs +++ b/state-machines/rom/src/rom.rs @@ -23,8 +23,7 @@ use fields::PrimeField64; use itertools::Itertools; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use zisk_common::{ - create_atomic_vec, BusDeviceMetrics, ComponentBuilder, CounterStats, Instance, InstanceCtx, - Planner, + create_atomic_vec, ComponentBuilder, CounterStats, Instance, InstanceCtx, Planner, }; use zisk_core::{ zisk_ops::ZiskOp, Riscv2zisk, ZiskRom, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, ROM_EXIT, SRC_IMM, @@ -307,14 +306,6 @@ impl RomSM { } impl ComponentBuilder for RomSM { - /// Builds and returns a new counter for monitoring ROM operations. - /// - /// # Returns - /// A boxed implementation of `RomCounter`. - fn build_counter(&self) -> Option> { - None - } - /// Builds a planner for ROM-related instances. /// /// # Returns diff --git a/state-machines/rom/src/rom_instance.rs b/state-machines/rom/src/rom_instance.rs index b32a99215..66c9320a0 100644 --- a/state-machines/rom/src/rom_instance.rs +++ b/state-machines/rom/src/rom_instance.rs @@ -3,7 +3,6 @@ //! It is responsible for computing witnesses for ROM-related execution plans, use std::{ - collections::VecDeque, sync::{ atomic::{AtomicBool, AtomicU64}, Arc, @@ -18,7 +17,7 @@ use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::sync::Mutex; use zisk_common::{ create_atomic_vec, BusDevice, BusId, CheckPoint, ChunkId, CounterStats, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, Metrics, PayloadType, ROM_BUS_ID, + InstanceType, Metrics, PayloadType, ROM_BUS_ID, }; use zisk_core::ZiskRom; @@ -246,9 +245,7 @@ impl RomCollector { let rom_counter = RomCounter::new(bios_inst_count, prog_inst_count); Self { already_computed: computed, rom_counter } } -} -impl BusDevice for RomCollector { /// Processes data received on the bus, updating ROM metrics. /// /// # Arguments @@ -260,14 +257,7 @@ impl BusDevice for RomCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _data_ext: &[u64], - _pending: &mut VecDeque<(BusId, Vec, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == ROM_BUS_ID); if !self.already_computed { @@ -276,15 +266,9 @@ impl BusDevice for RomCollector { true } +} - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![ROM_BUS_ID] - } - +impl BusDevice for RomCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self From c73200f8eab4f985f86d254dd6540fe6864c386d Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 28 Jan 2026 16:42:38 +0100 Subject: [PATCH 362/782] fix bugs with trace reallocation and detection of this situation, add support to different inital trace sizes. --- Cargo.lock | 1 + emulator-asm/Makefile | 13 +++++ emulator-asm/asm-runner/Cargo.toml | 1 + emulator-asm/asm-runner/src/asm_mt_runner.rs | 23 ++------ emulator-asm/asm-runner/src/shmem_utils.rs | 15 +++-- emulator-asm/src/dma/direct_memcpy_mtrace.asm | 34 ++++++++++- emulator-asm/src/main.c | 57 ++++++++++++++----- rom-setup/src/asm_setup.rs | 9 +-- 8 files changed, 109 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f2a36164..337a7ff16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,6 +300,7 @@ dependencies = [ "mem-common", "mem-planner-cpp", "named-sem", + "proofman-common", "rayon", "thiserror 2.0.18", "tracing", diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 218ad9824..c8859ffdb 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -1,3 +1,5 @@ +TRACE_TARGET=NONE + # Debug build flags ifeq ($(dbg),1) # CFLAGS = -O0 -g -D DEBUG -no-pie -ggdb -fno-inline @@ -8,6 +10,17 @@ else ASMFLAGS = --noexecstack endif +ifeq ($(TRACE_TARGET),MT) + CFLAGS += -DTRACE_TARGET_MT + ASMFLAGS += --defsym TRACE_TARGET=1 +else ifeq ($(TRACE_TARGET),MO) + CFLAGS += -DTRACE_TARGET_MO + ASMFLAGS += --defsym TRACE_TARGET=7 +else ifeq ($(TRACE_TARGET),RH) + CFLAGS += -DTRACE_TARGET_RH + ASMFLAGS += --defsym TRACE_TARGET=2 +endif + # Default EMU_PATH and OUT_PATH EMU_PATH ?= src/emu.asm OUT_PATH ?= build/ziskemuasm diff --git a/emulator-asm/asm-runner/Cargo.toml b/emulator-asm/asm-runner/Cargo.toml index f1c494afd..5269e8bab 100644 --- a/emulator-asm/asm-runner/Cargo.toml +++ b/emulator-asm/asm-runner/Cargo.toml @@ -16,6 +16,7 @@ zisk-common = { workspace = true } zisk-core = { workspace = true } mem-planner-cpp = { workspace = true } mem-common = { workspace = true } +proofman-common = { workspace = true } tracing = { workspace = true} rayon = { workspace = true} diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index d3fbcbe8b..943b284cf 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -123,14 +123,6 @@ impl AsmRunnerMT { let __stats = _stats.clone(); - // Threshold (in bytes) used to detect when the shared memory region size has changed. - // Computed to optimize the common case where minor size fluctuations are ignored. - // It is based on the worst-case scenario of memory usage. - let threshold_bytes = (chunk_size as usize * 200) + (44 * 8) + 32; - let mut threshold = unsafe { - preloaded.output_shmem.mapped_ptr().add(threshold_bytes) as *const AsmMTChunk - }; - let exit_code = loop { match sem_chunk_done.timed_wait(Duration::from_secs(10)) { Ok(()) => { @@ -150,17 +142,10 @@ impl AsmRunnerMT { fence(Ordering::Acquire); // Check if we need to remap the shared memory - if data_ptr >= threshold - && preloaded - .output_shmem - .check_size_changed(&mut data_ptr) - .context("Failed to check and remap shared memory for MO trace")? - { - threshold = unsafe { - preloaded.output_shmem.mapped_ptr().add(threshold_bytes) - as *const AsmMTChunk - }; - } + preloaded + .output_shmem + .check_size_changed(&mut data_ptr) + .context("Failed to check and remap shared memory for MT trace")?; let emu_trace = Arc::new(AsmMTChunk::to_emu_trace(&mut data_ptr)); let should_exit = emu_trace.end; diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 84acfefbb..f0a3959c6 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -1,7 +1,10 @@ +use anyhow::anyhow; +use anyhow::Result; use libc::{ c_uint, close, mmap, munmap, shm_open, shm_unlink, MAP_FAILED, MAP_SHARED, PROT_READ, S_IRUSR, S_IWUSR, }; +use proofman_common::format_bytes; use std::{ ffi::CString, fmt::Debug, @@ -10,12 +13,9 @@ use std::{ ptr, sync::atomic::{fence, Ordering}, }; -use tracing::debug; +use tracing::info; use zisk_common::io::{ZiskIO, ZiskStdin}; -use anyhow::anyhow; -use anyhow::Result; - use crate::{AsmInputC2, AsmService, AsmServices, SharedMemoryWriter}; pub enum AsmSharedMemoryMode { @@ -216,7 +216,12 @@ impl AsmSharedMemory { return Ok(false); } - debug!("Remapping shared memory {} to new size: {}", self.shmem_name, read_mapped_size); + info!( + "Remapping shared memory {}: {} => {}", + self.shmem_name, + format_bytes(self.mapped_size as f64), + format_bytes(read_mapped_size as f64) + ); let offset = (*current_read_ptr as usize).wrapping_sub(self.mapped_ptr as usize); diff --git a/emulator-asm/src/dma/direct_memcpy_mtrace.asm b/emulator-asm/src/dma/direct_memcpy_mtrace.asm index b906f00f3..3dfb4ad79 100644 --- a/emulator-asm/src/dma/direct_memcpy_mtrace.asm +++ b/emulator-asm/src/dma/direct_memcpy_mtrace.asm @@ -57,7 +57,16 @@ .extern fast_dma_encode .extern trace_address_threshold +.extern trace_resize_request +.ifdef DEBUG +.section .data +.align 8 + dma_check_case: .quad 0 + dma_check_step: .quad 0 + dma_check_aux: .quad 0 + dma_check_threshold: .quad 0 +.endif .include "dma_constants.inc" @@ -65,7 +74,7 @@ .set R_MT_INDEX, r13 .set R_MT_ADDR, r12 -.set R_STEP, r15 +.set R_STEP, r14 .set R_AUX, r9 .set R_AUX2, rcx # NOTE: used by rep .set R_SRC, rsi # NOTE: used by rep @@ -98,6 +107,10 @@ dma_memcpy_mtrace: # calculate bytes of mtrace used and verify if throw the limit +.ifdef DEBUG + mov qword ptr [dma_check_case], 1 +.endif + lea R_AUX, [R_MT_ADDR + 8 * R_MT_INDEX] # 1 cycle - calculate address mtrace lea R_AUX, [R_AUX + R_COUNT + MAX_DMA_MT_MARGIN] # 1 cycle - calculate current mtrace bytes usage sub R_AUX, [trace_address_threshold] # ~4 cycles - bytes over threshold (can be negative) @@ -106,13 +119,28 @@ dma_memcpy_mtrace: # check if bytes over threshold are usual for current situation on inside chunk # R_STEP contain number the steps to end of chunk, we need number the steps consumed +.ifdef DEBUG + mov qword ptr [dma_check_case], 2 + mov [dma_check_step], R_STEP + mov [dma_check_aux], R_AUX +.endif + mov R_AUX2, CHUNK_SIZE # 1 cycle - load chunk size constant sub R_AUX2, R_STEP # 1 cycle - calculate steps consumed in chunk imul R_AUX2, MAX_BYTES_MTRACE_STEP # ~3 cycles - bytes expected for consumed steps cmp R_AUX2, R_AUX # 1 cycle - compare expected vs actual jae .L_memcpy_mtrace_continue # 2 cycles (predicted) - expected >= actual, ok + # at this point we need to increase trace, registers R_ENCODE, R_AUX no need to save. +.ifdef DEBUG + mov qword ptr [dma_check_case], 3 + mov R_AUX, [trace_address_threshold] + mov [dma_check_threshold], R_AUX +.endif + + mov qword ptr [trace_resize_request], R_COUNT + push R_COUNT # ~3 cycles - save general purpose registers push r8 # ~3 cycles push r10 # ~3 cycles @@ -177,8 +205,8 @@ direct_dma_memcpy_mtrace_with_count_check: # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count # Result will be returned in R_ENCODE (encoded value) - cmp R_COUNT, MAX_BYTES_DIRECT_MTRACE # 1 cycle - check if count exceeds direct threshold - ja .L_memcpy_check_mtrace_available # 2 cycles (not taken usually) - large count, check trace space + cmp R_COUNT, MAX_DMA_BYTES_DIRECT_MTRACE # 1 cycle - check if count exceeds direct threshold + ja .L_memcpy_check_mtrace_available # 2 cycles (not taken usually) - large count, check trace space .L_memcpy_mtrace_continue: direct_dma_memcpy_mtrace: diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 67589308f..53ff952d2 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -36,15 +36,27 @@ uint64_t get_gen_method(void); #define INPUT_ADDR (uint64_t)0x90000000 #define MAX_INPUT_SIZE (uint64_t)0x08000000 // 128MB -#define RAM_ADDR (uint64_t)0xa0000000 +#define RAM_ADDR (uint64_t)0xA0000000 #define RAM_SIZE (uint64_t)0x20000000 // 512MB #define SYS_ADDR RAM_ADDR #define SYS_SIZE (uint64_t)0x10000 #define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) -#define TRACE_ADDR (uint64_t)0xc0000000 -#define INITIAL_TRACE_SIZE (uint64_t)0x180000000 // 6GB -#define DELTA_TRACE_SIZE (uint64_t)0x080000000 // 2GB +#ifdef TRACE_TARGET_MO + #pragma message "TRACE_TARGET = MO" + #define INITIAL_TRACE_SIZE (uint64_t)0x100000000 /* 4GB */ + #define DELTA_TRACE_SIZE (uint64_t)0x040000000 /* 1GB */ +#elif defined(TRACE_TARGET_RH) + #pragma message "TRACE_TARGET = RH" + #define INITIAL_TRACE_SIZE (uint64_t)0x004000000 /* 64MB */ + #define DELTA_TRACE_SIZE (uint64_t)0x004000000 /* 64MB */ +#else + #pragma message "TRACE_TARGET = DEFAULT" + #define INITIAL_TRACE_SIZE (uint64_t)0x180000000 /* 6GB */ + #define DELTA_TRACE_SIZE (uint64_t)0x080000000 /* 2GB */ +#endif + +#define TRACE_ADDR (uint64_t)0xC0000000 #define REG_ADDR (uint64_t)0x70000000 #define REG_SIZE (uint64_t)0x1000 // 4kB @@ -200,17 +212,29 @@ uint64_t trace_used_size = 0; // Worst case: every chunk instruction is a keccak operation, with an input data of 256 bytes -#define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) -#define MAX_TRACE_CHUNK_INFO ((44*8) + 32) -#define MAX_BYTES_DIRECT_MTRACE 256 -#define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) -#define MAX_CHUNK_TRACE_SIZE ((INITIAL_CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) - +#if defined(TRACE_TARGET_MO) + #define MAX_MTRACE_REGS_ACCESS_SIZE (3 * 8) + #define MAX_BYTES_DIRECT_MTRACE 80 // PRE/POST = ((1 PRE + 2 SRC + 1 WR DST) * 2 + BLOCK_READ + BLOCK_WRITE) * 8 + #define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) + #define MAX_CHUNK_TRACE_SIZE (INITIAL_CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) +#elif defined(TRACE_TARGET_RH) + #define MAX_CHUNK_TRACE_SIZE 0 +#else + #define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) + #define MAX_TRACE_CHUNK_INFO ((44*8) + 32) + #define MAX_BYTES_DIRECT_MTRACE 256 + #define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) + #define MAX_CHUNK_TRACE_SIZE ((INITIAL_CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) +#endif +uint64_t trace_resize_request = 0; uint64_t trace_address_threshold = TRACE_ADDR + INITIAL_TRACE_SIZE - MAX_CHUNK_TRACE_SIZE; uint64_t print_pc_counter = 0; int map_locked_flag = MAP_LOCKED; +// Log name +char log_name[128]; + #ifdef ASM_PRECOMPILE_CACHE bool precompile_cache_enabled = false; #endif @@ -232,6 +256,9 @@ void set_chunk_size (uint64_t new_chunk_size) void set_trace_size (uint64_t new_trace_size) { // Update trace global variables + printf("%s trace resize (trace_resize_request: %ld): %ld MB => %ld MB\n", log_name, trace_resize_request, trace_size >> 20, new_trace_size >> 20); + + trace_resize_request = 0; trace_size = new_trace_size; trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; pOutputTrace[2] = trace_size; @@ -312,9 +339,6 @@ sem_t * sem_shutdown_done = NULL; char file_lock_name[128]; int file_lock_fd = -1; -// Log name -char log_name[128]; - int process_id = 0; uint64_t input_size = 0; @@ -2687,11 +2711,16 @@ void server_setup (void) flags |= MAP_FIXED; } void * pTrace = mmap(requested_address, trace_size, PROT_READ | PROT_WRITE, flags, shmem_output_fd, 0); + if (verbose) { gettimeofday(&stop_time, NULL); duration = TimeDiff(start_time, stop_time); } + + printf("%s trace mapping on %p with %ld MB\n", log_name, pTrace, trace_size >> 20); + fflush(stdout); + if (pTrace == MAP_FAILED) { printf("ERROR: Failed calling mmap(pTrace) name=%s errno=%d=%s\n", shmem_output_name, errno, strerror(errno)); @@ -2738,6 +2767,8 @@ void server_setup (void) gettimeofday(&stop_time, NULL); duration = TimeDiff(start_time, stop_time); #endif + printf("%s trace mapping on %p with %ld MB\n", log_name, pTrace, chunk_player_mt_size >> 20); + fflush(stdout); if (pTrace == MAP_FAILED) { printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); diff --git a/rom-setup/src/asm_setup.rs b/rom-setup/src/asm_setup.rs index 525827c17..58b650afd 100644 --- a/rom-setup/src/asm_setup.rs +++ b/rom-setup/src/asm_setup.rs @@ -36,12 +36,12 @@ pub fn generate_assembly( let bin_mo_file = base_path.with_file_name(bin_mo_file); [ - (bin_mt_file, AsmGenerationMethod::AsmMinimalTraces), - (bin_rh_file, AsmGenerationMethod::AsmRomHistogram), - (bin_mo_file, AsmGenerationMethod::AsmMemOp), + (bin_mt_file, AsmGenerationMethod::AsmMinimalTraces, "MT"), + (bin_rh_file, AsmGenerationMethod::AsmRomHistogram, "RH"), + (bin_mo_file, AsmGenerationMethod::AsmMemOp, "MO"), ] .iter() - .for_each(|(file, gen_method)| { + .for_each(|(file, gen_method, trace_target)| { let asm_file = file.with_extension("asm"); // Convert the ELF file to Zisk format and generates an assembly file let rv2zk = Riscv2zisk::new(elf_file_path.to_str().unwrap().to_string()); @@ -69,6 +69,7 @@ pub fn generate_assembly( let status = Command::new("make") .arg(format!("EMU_PATH={}", asm_file.to_str().unwrap())) .arg(format!("OUT_PATH={}", file.to_str().unwrap())) + .arg(format!("TRACE_TARGET={trace_target}")) .current_dir(emulator_asm_path) .stdout(if verbose { Stdio::inherit() } else { Stdio::null() }) .stderr(if verbose { Stdio::inherit() } else { Stdio::null() }) From d15713d5148ea3c2965b8665fa35f40b0451331a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 29 Jan 2026 05:22:19 +0000 Subject: [PATCH 363/782] fix when printing Assembly execution speed --- emulator-asm/asm-runner/src/asm_mt_runner.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 74830e946..41f1243a4 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -200,16 +200,16 @@ impl AsmRunnerMT { .context("Child process returned error"); } + let total_steps = emu_traces.iter().map(|x| x.steps).sum::(); + let mhz = (total_steps as f64 / start_time.elapsed().as_secs_f64()) / 1_000_000.0; + info!("··· Assembly execution speed: {}MHz", mhz.round()); + // Collect results let mut tasks = Vec::new(); for handle in handles { tasks.push(handle.join().expect("Task panicked")); } - let total_steps = emu_traces.iter().map(|x| x.steps).sum::(); - let mhz = (total_steps as f64 / start_time.elapsed().as_secs_f64()) / 1_000_000.0; - info!("··· Assembly execution speed: {:.2} MHz", mhz); - // Wait for the assembly emulator to complete writing the trace let response = handle .join() From 888fa9993859c01b85c6fa96adbd1d3def64c93d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 29 Jan 2026 05:59:14 +0000 Subject: [PATCH 364/782] fix build.rs in lib-c --- lib-c/build.rs | 130 +++--------------------------- lib-c/c/Makefile | 205 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 173 insertions(+), 162 deletions(-) diff --git a/lib-c/build.rs b/lib-c/build.rs index b96388738..3652ef56c 100644 --- a/lib-c/build.rs +++ b/lib-c/build.rs @@ -1,8 +1,5 @@ -use std::env; -use std::fs; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::process::Command; -use std::time::UNIX_EPOCH; fn main() { if cfg!(target_os = "macos") { @@ -10,12 +7,6 @@ fn main() { return; } - // // **Check if the `no_lib_link` feature is enabled** - // if env::var("CARGO_FEATURE_NO_LIB_LINK").is_ok() { - // println!("Skipping linking because `no_lib_link` feature is enabled."); - // return; - // } - // Paths let c_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("c"); if !c_path.exists() { @@ -25,24 +16,23 @@ fn main() { let library_name = "ziskc"; let lib_file = library_folder.join(format!("lib{library_name}.a")); - // Check if the C++ library exists before recompiling - if !lib_file.exists() { - println!("`{}` not found! Compiling...", lib_file.display()); - run_command("make", &["clean"], &c_path); - run_command("make", &[], &c_path); - } else { - println!("C++ library already compiled, skipping rebuild."); - } + // Run make (incremental build - only recompiles changed files) + let status = Command::new("make") + .current_dir(&c_path) + .status() + .unwrap_or_else(|e| panic!("Failed to execute `make`: {e}")); - // Absolute path to the library - let abs_lib_path = library_folder.canonicalize().unwrap_or_else(|_| library_folder.clone()); + if !status.success() { + panic!("Command `make` failed with exit code {:?}", status.code()); + } + // Verify the library exists after build if !lib_file.exists() { - panic!("`{}` was not found", lib_file.display()); + panic!("`{}` was not found after build", lib_file.display()); } - // Ensure Rust triggers a rebuild if the C++ source code changes - track_cpp_changes(&c_path); + // Absolute path to the library + let abs_lib_path = library_folder.canonicalize().unwrap_or_else(|_| library_folder.clone()); // Link the static library println!("cargo:rustc-link-search=native={}", abs_lib_path.display()); @@ -53,97 +43,3 @@ fn main() { println!("cargo:rustc-link-lib={lib}"); } } - -// /// Runs an external command and checks for errors -fn run_command(cmd: &str, args: &[&str], dir: &Path) { - let status = Command::new(cmd) - .args(args) - .current_dir(dir) - .status() - .unwrap_or_else(|e| panic!("Failed to execute `{cmd}`: {e}")); - - if !status.success() { - panic!("Command `{}` failed with exit code {:?}", cmd, status.code()); - } -} - -/// Tracks changes in the `pil2-stark` directory to trigger recompilation only when needed -fn track_cpp_changes(c_path: &Path) { - let cpp_files = find_cpp_files(c_path); - let lib_file = c_path.join("lib/libziskc.a"); - - // Print tracked files for debugging - eprintln!("Tracking {} C++ source files:", cpp_files.len()); - for file in &cpp_files { - eprintln!(" - {}", file.display()); - println!("cargo:rerun-if-changed={}", file.display()); - } - - // If any C++ source file changed, force a rebuild - if cpp_files_have_changed(&cpp_files, &lib_file) { - eprintln!("Changes detected! Running `make clean` and recompiling..."); - run_command("make", &["clean"], c_path); - run_command("make", &[], c_path); - } else { - println!("No C++ source changes detected, skipping rebuild."); - } -} -/// Checks if any `.cpp`, `.h`, or `.hpp` file has changed since the last build -fn cpp_files_have_changed(cpp_files: &[PathBuf], lib_file: &Path) -> bool { - let mut modified_files: Vec = Vec::new(); - - // Get the modification time of `libstarks.a` - let lib_modified_time = match fs::metadata(lib_file) { - Ok(metadata) => { - let modified = metadata.modified().unwrap_or(UNIX_EPOCH); - eprintln!("`{}` last modified: {:?}", lib_file.display(), modified); - modified - } - Err(_) => { - eprintln!("Library `{}` does not exist, triggering rebuild.", lib_file.display()); - return true; // If `libstarks.a` is missing, we must rebuild. - } - }; - - // Check if any `.cpp`, `.h`, or `.hpp` file has been modified after `libstarks.a` - for file in cpp_files { - if let Ok(metadata) = fs::metadata(file) { - if let Ok(modified_time) = metadata.modified() { - if modified_time > lib_modified_time { - modified_files.push(file.clone()); - } - } - } - } - - // Print the list of modified files (if any) - if !modified_files.is_empty() { - eprintln!("Modified files detected:"); - for file in &modified_files { - eprintln!(" - {}", file.display()); - } - return true; - } - - false // No changes detected -} - -/// Finds all `.cpp`, `.h`, and `.hpp` files in `pil2-stark` (recursive search) -fn find_cpp_files(dir: &Path) -> Vec { - let mut cpp_files = Vec::new(); - if let Ok(entries) = fs::read_dir(dir) { - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { - cpp_files.extend(find_cpp_files(&path)); - } else if let Some(ext) = path.extension() { - if (ext == "cpp" || ext == "h" || ext == "hpp") - && path.file_name() != Some(std::ffi::OsStr::new("starks_lib.h")) - { - cpp_files.push(path); - } - } - } - } - cpp_files -} diff --git a/lib-c/c/Makefile b/lib-c/c/Makefile index ab3686ff6..6234c5f4b 100644 --- a/lib-c/c/Makefile +++ b/lib-c/c/Makefile @@ -6,50 +6,165 @@ else CFLAGS = -O3 -fPIC endif -all: - mkdir -p build - nasm -felf64 src/ffiasm/fec.asm -o build/fec.o - nasm -felf64 src/ffiasm/fnec.asm -o build/fnec.o - nasm -felf64 src/ffiasm/fq.asm -o build/fq.o - nasm -felf64 src/ffiasm/bls12_381_384.asm -o build/bls12_381_384.o - gcc $(CFLAGS) -c src/ffiasm/fec.cpp -o build/fecc.o - gcc $(CFLAGS) -c src/ffiasm/fnec.cpp -o build/fnecc.o - gcc $(CFLAGS) -c src/ffiasm/fq.cpp -o build/fqc.o - gcc $(CFLAGS) -c src/ffiasm/bls12_381_384.cpp -o build/bls12_381_384c.o - gcc $(CFLAGS) -c src/ec/ec.cpp -o build/ec.o - gcc $(CFLAGS) -c src/bn254/bn254.cpp -o build/bn254.o - gcc $(CFLAGS) -c src/bls12_381/bls12_381.cpp -o build/bls12_381.o - gcc $(CFLAGS) -c src/fcall/fcall.cpp -o build/fcall.o - gcc $(CFLAGS) -c src/arith256/arith256.cpp -o build/arith256.o - gcc $(CFLAGS) -c src/arith384/arith384.cpp -o build/arith384.o - gcc $(CFLAGS) -c src/bigint/add256.cpp -o build/add256.o - gcc $(CFLAGS) -c src/common/globals.cpp -o build/globals.o - gcc $(CFLAGS) -c src/poseidon2/goldilocks_base_field.cpp -o build/goldilocks_base_field.o - gcc $(CFLAGS) -c src/poseidon2/poseidon2_goldilocks.cpp -o build/poseidon2_goldilocks.o - ar rcs\ - build/libziskc.a\ - build/fec.o\ - build/fnec.o\ - build/fq.o\ - build/bls12_381_384.o\ - build/ec.o\ - build/bn254.o\ - build/bls12_381.o\ - build/fecc.o\ - build/fnecc.o\ - build/fqc.o\ - build/bls12_381_384c.o\ - build/fcall.o\ - build/arith256.o\ - build/arith384.o\ - build/add256.o\ - build/globals.o\ - build/goldilocks_base_field.o\ - build/poseidon2_goldilocks.o - gcc $(CFLAGS) src/main.cpp -lc build/libziskc.a -o build/clib -lgmp -lstdc++ -lgmpxx - mkdir -p lib - cp build/libziskc.a lib/ +# Directories +BUILD_DIR = build +LIB_DIR = lib +SRC_DIR = src + +# Output library +LIB_NAME = libziskc.a +LIB_FILE = $(LIB_DIR)/$(LIB_NAME) + +# Object files +ASM_OBJS = $(BUILD_DIR)/fec.o \ + $(BUILD_DIR)/fnec.o \ + $(BUILD_DIR)/fq.o \ + $(BUILD_DIR)/fr.o \ + $(BUILD_DIR)/bls12_381_384.o \ + $(BUILD_DIR)/bls12_381_asm.o \ + $(BUILD_DIR)/nsecp256r1.o \ + $(BUILD_DIR)/psecp256r1.o + +CPP_OBJS = $(BUILD_DIR)/fecc.o \ + $(BUILD_DIR)/fnecc.o \ + $(BUILD_DIR)/fqc.o \ + $(BUILD_DIR)/frc.o \ + $(BUILD_DIR)/bls12_381_384c.o \ + $(BUILD_DIR)/bls12_381c.o \ + $(BUILD_DIR)/alt_bn128.o \ + $(BUILD_DIR)/nsecp256r1c.o \ + $(BUILD_DIR)/psecp256r1c.o \ + $(BUILD_DIR)/misc.o \ + $(BUILD_DIR)/naf.o \ + $(BUILD_DIR)/splitparstr.o \ + $(BUILD_DIR)/ec.o \ + $(BUILD_DIR)/bn254.o \ + $(BUILD_DIR)/bls12_381.o \ + $(BUILD_DIR)/fcall.o \ + $(BUILD_DIR)/arith256.o \ + $(BUILD_DIR)/arith384.o \ + $(BUILD_DIR)/add256.o \ + $(BUILD_DIR)/globals.o \ + $(BUILD_DIR)/goldilocks_base_field.o \ + $(BUILD_DIR)/poseidon2_goldilocks.o + +ALL_OBJS = $(ASM_OBJS) $(CPP_OBJS) + +# Header directories for include path +INCLUDES = -I$(SRC_DIR) + +# Default target +all: $(LIB_FILE) $(BUILD_DIR)/clib + +# Create directories +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +$(LIB_DIR): + mkdir -p $(LIB_DIR) + +# Library target +$(LIB_FILE): $(ALL_OBJS) | $(LIB_DIR) + ar rcs $@ $(ALL_OBJS) + +# Test binary +$(BUILD_DIR)/clib: $(SRC_DIR)/main.cpp $(LIB_FILE) + gcc $(CFLAGS) $(INCLUDES) $< -lc $(LIB_FILE) -o $@ -lgmp -lstdc++ -lgmpxx + +# Assembly rules +$(BUILD_DIR)/fec.o: $(SRC_DIR)/ffiasm/fec.asm | $(BUILD_DIR) + nasm -felf64 $< -o $@ + +$(BUILD_DIR)/fnec.o: $(SRC_DIR)/ffiasm/fnec.asm | $(BUILD_DIR) + nasm -felf64 $< -o $@ + +$(BUILD_DIR)/fq.o: $(SRC_DIR)/ffiasm/fq.asm | $(BUILD_DIR) + nasm -felf64 $< -o $@ + +$(BUILD_DIR)/fr.o: $(SRC_DIR)/ffiasm/fr.asm | $(BUILD_DIR) + nasm -felf64 $< -o $@ + +$(BUILD_DIR)/bls12_381_384.o: $(SRC_DIR)/ffiasm/bls12_381_384.asm | $(BUILD_DIR) + nasm -felf64 $< -o $@ + +$(BUILD_DIR)/bls12_381_asm.o: $(SRC_DIR)/ffiasm/bls12_381.asm | $(BUILD_DIR) + nasm -felf64 $< -o $@ + +$(BUILD_DIR)/nsecp256r1.o: $(SRC_DIR)/ffiasm/nsecp256r1.asm | $(BUILD_DIR) + nasm -felf64 $< -o $@ + +$(BUILD_DIR)/psecp256r1.o: $(SRC_DIR)/ffiasm/psecp256r1.asm | $(BUILD_DIR) + nasm -felf64 $< -o $@ + +# C++ compilation rules +$(BUILD_DIR)/fecc.o: $(SRC_DIR)/ffiasm/fec.cpp $(SRC_DIR)/ffiasm/fec.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/fnecc.o: $(SRC_DIR)/ffiasm/fnec.cpp $(SRC_DIR)/ffiasm/fnec.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/fqc.o: $(SRC_DIR)/ffiasm/fq.cpp $(SRC_DIR)/ffiasm/fq.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/frc.o: $(SRC_DIR)/ffiasm/fr.cpp $(SRC_DIR)/ffiasm/fr.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/bls12_381_384c.o: $(SRC_DIR)/ffiasm/bls12_381_384.cpp $(SRC_DIR)/ffiasm/bls12_381_384.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/bls12_381c.o: $(SRC_DIR)/ffiasm/bls12_381.cpp $(SRC_DIR)/ffiasm/bls12_381.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/alt_bn128.o: $(SRC_DIR)/ffiasm/alt_bn128.cpp $(SRC_DIR)/ffiasm/alt_bn128.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/nsecp256r1c.o: $(SRC_DIR)/ffiasm/nsecp256r1.cpp $(SRC_DIR)/ffiasm/nsecp256r1.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/psecp256r1c.o: $(SRC_DIR)/ffiasm/psecp256r1.cpp $(SRC_DIR)/ffiasm/psecp256r1.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/misc.o: $(SRC_DIR)/ffiasm/misc.cpp $(SRC_DIR)/ffiasm/misc.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/naf.o: $(SRC_DIR)/ffiasm/naf.cpp $(SRC_DIR)/ffiasm/naf.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/splitparstr.o: $(SRC_DIR)/ffiasm/splitparstr.cpp $(SRC_DIR)/ffiasm/splitparstr.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/ec.o: $(SRC_DIR)/ec/ec.cpp $(SRC_DIR)/ec/ec.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/bn254.o: $(SRC_DIR)/bn254/bn254.cpp $(SRC_DIR)/bn254/bn254.hpp $(SRC_DIR)/bn254/bn254_fe.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/bls12_381.o: $(SRC_DIR)/bls12_381/bls12_381.cpp $(SRC_DIR)/bls12_381/bls12_381.hpp $(SRC_DIR)/bls12_381/bls12_381_fe.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/fcall.o: $(SRC_DIR)/fcall/fcall.cpp $(SRC_DIR)/fcall/fcall.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/arith256.o: $(SRC_DIR)/arith256/arith256.cpp $(SRC_DIR)/arith256/arith256.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/arith384.o: $(SRC_DIR)/arith384/arith384.cpp $(SRC_DIR)/arith384/arith384.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/add256.o: $(SRC_DIR)/bigint/add256.cpp $(SRC_DIR)/bigint/add256.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/globals.o: $(SRC_DIR)/common/globals.cpp $(SRC_DIR)/common/globals.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/goldilocks_base_field.o: $(SRC_DIR)/poseidon2/goldilocks_base_field.cpp $(SRC_DIR)/poseidon2/goldilocks_base_field.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILD_DIR)/poseidon2_goldilocks.o: $(SRC_DIR)/poseidon2/poseidon2_goldilocks.cpp $(SRC_DIR)/poseidon2/poseidon2_goldilocks.hpp $(SRC_DIR)/poseidon2/poseidon2_goldilocks_constants.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ clean: - rm -rf build - rm -rf lib + rm -rf $(BUILD_DIR) + rm -rf $(LIB_DIR) + +.PHONY: all clean From 61cf1596b79ba851efc495efcf7ffea18c1cf606 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 29 Jan 2026 06:00:10 +0000 Subject: [PATCH 365/782] improve message --- precompiles/hints/src/hints_processor.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 85ed89e0a..7567937c4 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -334,7 +334,13 @@ impl HintsProcessor { (rate, "Hz") }; - info!("Processed {} hints in {:?} ({:.2} {})", num_hints, elapsed, value, unit); + info!( + "Processed {} hints in {:.0?} ({}{})", + num_hints, + elapsed, + value.round(), + unit + ); break; } From 45a951a177004fee48e5b7809d406154b7e0d2e0 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 29 Jan 2026 06:42:45 +0000 Subject: [PATCH 366/782] remove unnecessary println --- witness-computation/src/zisk_lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index f82d1d2aa..b5c4b8a30 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -205,7 +205,6 @@ impl WitnessLibrary for WitnessLib { // Create hints pipeline with null hints stream initially. // Debug flag: true = HintsShmem (shared memory), false = HintsFile (file output) let hints_stream = if self.with_hints { - println!("Initializing zisk_lib with hints stream."); const USE_SHARED_MEMORY_HINTS: bool = true; let hints_processor = if USE_SHARED_MEMORY_HINTS { @@ -227,7 +226,6 @@ impl WitnessLibrary for WitnessLib { Some(ZiskStream::new(hints_processor)) } else { - println!("Initializing zisk_lib without hints stream."); None }; From 52848d317c368f0095a27a9be9e8467c605906b0 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 29 Jan 2026 10:59:27 +0100 Subject: [PATCH 367/782] Fix ZiskRomSetup short option for hints --- cli/src/commands/rom_setup.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index 266f8f446..1cd51a0d4 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -31,7 +31,7 @@ pub struct ZiskRomSetup { pub output_dir: Option, /// Enable precompile hints in assembly generation - #[clap(short = 'h', long, default_value_t = false)] + #[clap(short = 'n', long, default_value_t = false)] pub hints: bool, #[clap(short = 'v', long, default_value_t = false)] From 30d3957d981d602512164896ead417ecff292180 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 29 Jan 2026 11:02:43 +0100 Subject: [PATCH 368/782] Add wait_counter to main.c assembly --- emulator-asm/src/main.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 2358d21f3..10919ddd6 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -136,6 +136,7 @@ extern uint64_t MEM_CHUNK_ADDRESS; extern uint64_t MEM_CHUNK_START_STEP; uint64_t realloc_counter = 0; +uint64_t wait_counter = 0; extern void zisk_keccakf(uint64_t state[25]); /* Used for debugging @@ -3834,9 +3835,10 @@ void server_run (void) uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; - printf("Duration = %lu us, realloc counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%%), end=%lu, error=%lu, max steps=%lu, chunk size=%lu\n", + printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%%), end=%lu, error=%lu, max steps=%lu, chunk size=%lu\n", duration, realloc_counter, + wait_counter, steps, step_duration_ns, step_tp_sec, @@ -4950,6 +4952,9 @@ void file_lock(void) int _wait_for_prec_avail (void) { + // Increment wait counter + wait_counter++; + // Sync precompile shared memory if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { printf("ERROR: 1 msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); From 3942360203a4faa93d31d521bdd66ea1f0e834d2 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:39:50 +0000 Subject: [PATCH 369/782] fix build.rs in lib-c --- lib-c/build.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib-c/build.rs b/lib-c/build.rs index 3652ef56c..e9d74b601 100644 --- a/lib-c/build.rs +++ b/lib-c/build.rs @@ -13,9 +13,16 @@ fn main() { panic!("Missing c_path = {}", c_path.display()); } let library_folder = c_path.join("lib"); + let build_folder = c_path.join("build"); let library_name = "ziskc"; let lib_file = library_folder.join(format!("lib{library_name}.a")); + // Ensure build and lib directories exist before running make + std::fs::create_dir_all(&build_folder) + .unwrap_or_else(|e| panic!("Failed to create build directory: {e}")); + std::fs::create_dir_all(&library_folder) + .unwrap_or_else(|e| panic!("Failed to create lib directory: {e}")); + // Run make (incremental build - only recompiles changed files) let status = Command::new("make") .current_dir(&c_path) From f13b5df8ea95cd613da6fd63a4ab713f2544f589 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:37:29 +0000 Subject: [PATCH 370/782] minor fix in main.c --- emulator-asm/src/main.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 10919ddd6..a26235723 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -2453,6 +2453,11 @@ void client_run (void) request[3] = 0; request[4] = 0; + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + client_write_precompile_results(); + } + // Send data to server result = send(socket_fd, request, sizeof(request), 0); if (result < 0) @@ -2463,11 +2468,6 @@ void client_run (void) exit(-1); } - if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - client_write_precompile_results(); - } - // Read server response bytes_received = recv(socket_fd, response, sizeof(response), MSG_WAITALL); if (bytes_received < 0) From 3adc485bbcc94c33edb8966dafdb480533dd026b Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:41:53 +0000 Subject: [PATCH 371/782] enable hints processor stats with -v flag --- witness-computation/src/zisk_lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index b5c4b8a30..dbdb516be 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -213,6 +213,7 @@ impl WitnessLibrary for WitnessLib { .expect("zisk_lib: Failed to create HintsShmem"); HintsProcessor::builder(hints_shmem) + .enable_stats(self.verbose_mode != proofman_common::VerboseMode::Info) .build() .expect("zisk_lib: Failed to create PrecompileHintsProcessor") } else { @@ -220,6 +221,7 @@ impl WitnessLibrary for WitnessLib { .expect("zisk_lib: Failed to create HintsFile"); HintsProcessor::builder(hints_file) + .enable_stats(self.verbose_mode != proofman_common::VerboseMode::Info) .build() .expect("zisk_lib: Failed to create PrecompileHintsProcessor") }; From 4facc6b3c09941c9033449977b2e696517735b35 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 29 Jan 2026 20:54:13 +0100 Subject: [PATCH 372/782] Implement share_input_shm and open_input_shm in main.c --- emulator-asm/src/main.c | 336 ++++++++++++++++++++++++++-------------- 1 file changed, 221 insertions(+), 115 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 10919ddd6..47f5cd486 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -276,6 +276,8 @@ bool trace = false; bool trace_trace = false; bool verbose = false; bool save_to_file = false; +bool share_input_shm = false; // Shares input shared memories: input, precompile results and control input, using a common name +bool open_input_shm = false; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) // ROM histogram uint64_t histogram_size = 0; @@ -884,6 +886,8 @@ void print_usage (void) printf("\t-a chunk_address\n"); printf("\t-v verbose on\n"); printf("\t-u unlock physical memory in mmap\n"); + printf("\t--share_input_shm share input shared memories\n"); + printf("\t--open_input_shm open existing input shared memories\n"); #ifdef ASM_PRECOMPILE_CACHE printf("\t--precompile-cache-store store precompile results in cache file\n"); printf("\t--precompile-cache-load load precompile results from cache file\n"); @@ -1200,6 +1204,16 @@ void parse_arguments(int argc, char *argv[]) } continue; } + if (strcmp(argv[i], "--share_input_shm") == 0) + { + share_input_shm = true; + continue; + } + if (strcmp(argv[i], "--open_input_shm") == 0) + { + open_input_shm = true; + continue; + } #ifdef ASM_PRECOMPILE_CACHE if (strcmp(argv[i], "--precompile-cache-store") == 0) { @@ -1311,15 +1325,24 @@ void configure (void) case Fast: { strcpy(shmem_control_input_name, shm_prefix); - strcat(shmem_control_input_name, "_FT_control_input"); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_FT_control_input"); strcpy(shmem_control_output_name, shm_prefix); strcat(shmem_control_output_name, "_FT_control_output"); strcpy(shmem_input_name, shm_prefix); - strcat(shmem_input_name, "_FT_input"); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_FT_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_FT_precompile"); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_FT_precompile"); strcpy(sem_prec_avail_name, shm_prefix); strcat(sem_prec_avail_name, "_FT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); @@ -1347,15 +1370,24 @@ void configure (void) case MinimalTrace: { strcpy(shmem_control_input_name, shm_prefix); - strcat(shmem_control_input_name, "_MT_control_input"); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MT_control_input"); strcpy(shmem_control_output_name, shm_prefix); strcat(shmem_control_output_name, "_MT_control_output"); strcpy(shmem_input_name, shm_prefix); - strcat(shmem_input_name, "_MT_input"); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MT_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_MT_precompile"); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MT_precompile"); strcpy(sem_prec_avail_name, shm_prefix); strcat(sem_prec_avail_name, "_MT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); @@ -1386,15 +1418,24 @@ void configure (void) case RomHistogram: { strcpy(shmem_control_input_name, shm_prefix); - strcat(shmem_control_input_name, "_RH_control_input"); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_RH_control_input"); strcpy(shmem_control_output_name, shm_prefix); strcat(shmem_control_output_name, "_RH_control_output"); strcpy(shmem_input_name, shm_prefix); - strcat(shmem_input_name, "_RH_input"); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_RH_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_RH_precompile"); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_RH_precompile"); strcpy(sem_prec_avail_name, shm_prefix); strcat(sem_prec_avail_name, "_RH_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); @@ -1425,15 +1466,24 @@ void configure (void) case MainTrace: { strcpy(shmem_control_input_name, shm_prefix); - strcat(shmem_control_input_name, "_MA_control_input"); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MA_control_input"); strcpy(shmem_control_output_name, shm_prefix); strcat(shmem_control_output_name, "_MA_control_output"); strcpy(shmem_input_name, shm_prefix); - strcat(shmem_input_name, "_MA_input"); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MA_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_MA_precompile"); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MA_precompile"); strcpy(sem_prec_avail_name, shm_prefix); strcat(sem_prec_avail_name, "_MA_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); @@ -1464,11 +1514,17 @@ void configure (void) case ChunksOnly: { strcpy(shmem_control_input_name, shm_prefix); - strcat(shmem_control_input_name, "_CH_control_input"); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CH_control_input"); strcpy(shmem_control_output_name, shm_prefix); strcat(shmem_control_output_name, "_CH_control_output"); strcpy(shmem_input_name, shm_prefix); - strcat(shmem_input_name, "_CH_input"); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_CH_input"); strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); @@ -1500,15 +1556,24 @@ void configure (void) case Zip: { strcpy(shmem_control_input_name, shm_prefix); - strcat(shmem_control_input_name, "_ZP_control_input"); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_ZP_control_input"); strcpy(shmem_control_output_name, shm_prefix); strcat(shmem_control_output_name, "_ZP_control_output"); strcpy(shmem_input_name, shm_prefix); - strcat(shmem_input_name, "_ZP_input"); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_ZP_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_ZP_precompile"); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_ZP_precompile"); strcpy(sem_prec_avail_name, shm_prefix); strcat(sem_prec_avail_name, "_ZP_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); @@ -1539,15 +1604,24 @@ void configure (void) case MemOp: { strcpy(shmem_control_input_name, shm_prefix); - strcat(shmem_control_input_name, "_MO_control_input"); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MO_control_input"); strcpy(shmem_control_output_name, shm_prefix); strcat(shmem_control_output_name, "_MO_control_output"); strcpy(shmem_input_name, shm_prefix); - strcat(shmem_input_name, "_MO_input"); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MO_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_MO_precompile"); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MO_precompile"); strcpy(sem_prec_avail_name, shm_prefix); strcat(sem_prec_avail_name, "_MO_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); @@ -1578,7 +1652,10 @@ void configure (void) case ChunkPlayerMTCollectMem: { strcpy(shmem_control_input_name, shm_prefix); - strcat(shmem_control_input_name, "_CM_control_input"); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CM_control_input"); strcpy(shmem_control_output_name, shm_prefix); strcat(shmem_control_output_name, "_CM_control_output"); strcpy(shmem_input_name, ""); @@ -1603,15 +1680,24 @@ void configure (void) case MemReads: { strcpy(shmem_control_input_name, shm_prefix); - strcat(shmem_control_input_name, "_MT_control_input"); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MT_control_input"); strcpy(shmem_control_output_name, shm_prefix); strcat(shmem_control_output_name, "_MT_control_output"); strcpy(shmem_input_name, shm_prefix); - strcat(shmem_input_name, "_MT_input"); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MT_input"); if (precompile_results_enabled) { strcpy(shmem_precompile_name, shm_prefix); - strcat(shmem_precompile_name, "_MT_precompile"); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MT_precompile"); strcpy(sem_prec_avail_name, shm_prefix); strcat(sem_prec_avail_name, "_MT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); @@ -1642,7 +1728,10 @@ void configure (void) case ChunkPlayerMemReadsCollectMain: { strcpy(shmem_control_input_name, shm_prefix); - strcat(shmem_control_input_name, "_CA_control_input"); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CA_control_input"); strcpy(shmem_control_output_name, shm_prefix); strcat(shmem_control_output_name, "_CA_control_output"); strcpy(shmem_input_name, ""); @@ -3224,39 +3313,42 @@ void server_setup (void) if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) { - // Make sure the input shared memory is deleted - shm_unlink(shmem_input_name); - - // Create the input shared memory - shmem_input_fd = shm_open(shmem_input_name, O_RDWR | O_CREAT | O_EXCL, 0666); - if (shmem_input_fd < 0) + if (!open_input_shm) { - printf("ERROR: Failed calling input RW shm_open(%s) as read-write errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + // Make sure the input shared memory is deleted + shm_unlink(shmem_input_name); - // Size it - result = ftruncate(shmem_input_fd, MAX_INPUT_SIZE); - if (result != 0) - { - printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + // Create the input shared memory + shmem_input_fd = shm_open(shmem_input_name, O_RDWR | O_CREAT | O_EXCL, 0666); + if (shmem_input_fd < 0) + { + printf("ERROR: Failed calling input RW shm_open(%s) as read-write errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - // Sync - fsync(shmem_input_fd); + // Size it + result = ftruncate(shmem_input_fd, MAX_INPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - // Close the descriptor - if (close(shmem_input_fd) != 0) - { - printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); + // Sync + fsync(shmem_input_fd); + + // Close the descriptor + if (close(shmem_input_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } } // Open the input shared memory as read-only @@ -3304,39 +3396,42 @@ void server_setup (void) /* PRECOMPILE */ /**************/ - // Make sure the precompile results shared memory is deleted - shm_unlink(shmem_precompile_name); - - // Create the precompile results shared memory - shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR | O_CREAT, 0666); - if (shmem_precompile_fd < 0) + if (!open_input_shm) { - printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_precompile_name); - // Size it - result = ftruncate(shmem_precompile_fd, MAX_PRECOMPILE_SIZE); - if (result != 0) - { - printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + // Create the precompile results shared memory + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR | O_CREAT, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - // Sync - fsync(shmem_precompile_fd); + // Size it + result = ftruncate(shmem_precompile_fd, MAX_PRECOMPILE_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - // Close the descriptor - if (close(shmem_precompile_fd) != 0) - { - printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); + // Sync + fsync(shmem_precompile_fd); + + // Close the descriptor + if (close(shmem_precompile_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } } // Open the precompile shared memory as read-only @@ -3372,39 +3467,42 @@ void server_setup (void) /* CONTROL INPUT */ /*****************/ - // Make sure the precompile results shared memory is deleted - shm_unlink(shmem_control_input_name); - - // Create the control shared memory - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR | O_CREAT, 0666); - if (shmem_control_input_fd < 0) + if (!open_input_shm) { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_input_name); - // Size it - result = ftruncate(shmem_control_input_fd, CONTROL_INPUT_SIZE); - if (result != 0) - { - printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + // Create the control shared memory + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - // Sync - fsync(shmem_control_input_fd); + // Size it + result = ftruncate(shmem_control_input_fd, CONTROL_INPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - // Close the descriptor - if (close(shmem_control_input_fd) != 0) - { - printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); + // Sync + fsync(shmem_control_input_fd); + + // Close the descriptor + if (close(shmem_control_input_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } } // Open the control input shared memory as read-only @@ -4203,13 +4301,21 @@ extern int _print_pc (uint64_t pc, uint64_t c) print_pc_counter++; } -//uint64_t chunk_done_counter = 0; +// uint64_t chunk_done_counter = 0; +// struct timeval chunk_done_tv; // struct timeval sync_start, sync_stop; // uint64_t sync_duration = 0; extern void _chunk_done() { - //chunk_done_counter++; - //printf("chunk_done() counter=%lu\n", chunk_done_counter); + // chunk_done_counter++; + // if ((chunk_done_counter & 0xFF) == 0) + // { + // struct timeval tv; + // gettimeofday(&tv, NULL); + // uint64_t duration = TimeDiff(chunk_done_tv, tv); + // chunk_done_tv = tv; + // printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); + // } //gettimeofday(&sync_start, NULL); __sync_synchronize(); // gettimeofday(&sync_stop, NULL); From 6ac7b12dadde45021ec3b2b217952395a2296ce4 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 30 Jan 2026 13:51:57 +0000 Subject: [PATCH 373/782] Activating execution stats again --- cli/src/commands/stats.rs | 20 ++++++++++++++++---- cli/src/commands/verify_constraints.rs | 2 -- common/src/executor_stats.rs | 4 ++-- executor/src/executor.rs | 8 ++++---- sdk/src/prover/asm.rs | 4 ++-- sdk/src/prover/backend.rs | 20 +++++++++++--------- sdk/src/prover/emu.rs | 4 ++-- sdk/src/prover/mod.rs | 10 +++++----- witness-computation/src/zisk_lib.rs | 4 ++-- 9 files changed, 44 insertions(+), 32 deletions(-) diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index d06745598..dbfdd5680 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -6,7 +6,7 @@ use std::{collections::HashMap, fs, path::PathBuf, time::Instant}; use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; -use zisk_common::{ExecutorStats, Stats}; +use zisk_common::{ExecutorStatsHandle, Stats}; use zisk_pil::*; use zisk_sdk::ProverClient; @@ -146,14 +146,26 @@ impl ZiskStats { ); if let Some(stats) = &stats { - Self::print_stats(&stats.witness_stats); + Self::print_stats(&stats.get_inner().lock().unwrap().witness_stats); stats.print_stats(); } Ok(()) } - pub fn run_emu(&mut self, stdin: ZiskStdin) -> Result<(i32, i32, Option)> { + fn create_stdin(&mut self) -> Result { + let stdin = if let Some(input) = &self.input { + if !input.exists() { + return Err(anyhow::anyhow!("Input file not found at {:?}", input.display())); + } + ZiskStdin::from_file(input)? + } else { + ZiskStdin::null() + }; + Ok(stdin) + } + + pub fn run_emu(&mut self, stdin: ZiskStdin) -> Result<(i32, i32, Option)> { let prover = ProverClient::builder() .emu() .witness() @@ -172,7 +184,7 @@ impl ZiskStats { &mut self, stdin: ZiskStdin, hints_stream: Option, - ) -> Result<(i32, i32, Option)> { + ) -> Result<(i32, i32, Option)> { let prover = ProverClient::builder() .asm() .witness() diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 158f5578e..f8f1c8fe6 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -7,8 +7,6 @@ use std::path::PathBuf; use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; use zisk_sdk::{ProverClient, ZiskVerifyConstraintsResult}; #[derive(Parser)] diff --git a/common/src/executor_stats.rs b/common/src/executor_stats.rs index 0cb477e5a..4f893a588 100644 --- a/common/src/executor_stats.rs +++ b/common/src/executor_stats.rs @@ -237,8 +237,8 @@ impl ExecutorStatsHandle { self.inner.lock().unwrap().print_stats(); } - pub fn get_inner(&self) -> ExecutorStats { - self.inner.lock().unwrap().clone() + pub fn get_inner(&self) -> Arc> { + self.inner.clone() } pub fn insert_witness_stats(&self, airgroup_id: usize, stats: Stats) { diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 070c6efbf..44a32e96f 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -33,8 +33,8 @@ use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; use zisk_common::ChunkId; use zisk_common::{ - BusDevice, BusDeviceMetrics, CheckPoint, ExecutorStats, ExecutorStatsHandle, Instance, - InstanceCtx, InstanceType, Plan, Stats, ZiskExecutionResult, + BusDevice, BusDeviceMetrics, CheckPoint, ExecutorStatsHandle, Instance, InstanceCtx, + InstanceType, Plan, Stats, ZiskExecutionResult, }; use zisk_pil::{ ZiskPublicValues, INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, @@ -165,8 +165,8 @@ impl ZiskExecutor { } #[allow(clippy::type_complexity)] - pub fn get_execution_result(&self) -> (ZiskExecutionResult, ExecutorStats) { - (self.execution_result.lock().unwrap().clone(), self.stats.get_inner()) + pub fn get_execution_result(&self) -> (ZiskExecutionResult, ExecutorStatsHandle) { + (self.execution_result.lock().unwrap().clone(), self.stats.clone()) } pub fn store_stats(&self) { diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index bc5660ea7..9ffe341a0 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -12,7 +12,7 @@ use rom_setup::DEFAULT_CACHE_PATH; use std::{collections::HashMap, path::PathBuf}; use tracing::info; use zisk_common::io::{StreamSource, ZiskStdin}; -use zisk_common::ExecutorStats; +use zisk_common::ExecutorStatsHandle; use zisk_distributed_common::LoggingConfig; use anyhow::Result; @@ -121,7 +121,7 @@ impl ProverEngine for AsmProver { hints_stream: Option, debug_info: Option>, mpi_node: Option, - ) -> Result<(i32, i32, Option)> { + ) -> Result<(i32, i32, Option)> { let debug_info = create_debug_info(debug_info, self.core_prover.backend.proving_key.clone())?; diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index e8382717c..c53e042c2 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -11,7 +11,7 @@ use proofman_common::{DebugInfo, ProofOptions}; use std::{fs::File, io::Write, path::PathBuf}; use zisk_common::{ io::{StreamSource, ZiskStdin}, - ExecutorStats, ProofLog, ZiskExecutionResult, ZiskLib, + ExecutorStatsHandle, ProofLog, ZiskExecutionResult, ZiskLib, }; pub(crate) struct ProverBackend { @@ -64,7 +64,7 @@ impl ProverBackend { hints_stream: Option, debug_info: DebugInfo, _mpi_node: Option, - ) -> Result<(i32, i32, Option)> { + ) -> Result<(i32, i32, Option)> { self.witness_lib.set_stdin(stdin); if let Some(stream) = hints_stream { self.witness_lib @@ -112,7 +112,7 @@ impl ProverBackend { ) .map_err(|e| anyhow::anyhow!("Error generating execution: {}", e))?; - let (_, stats): (ZiskExecutionResult, ExecutorStats) = + let (_, stats): (ZiskExecutionResult, ExecutorStatsHandle) = self.witness_lib.execution_result().ok_or_else(|| { anyhow::anyhow!("Failed to get execution result from emulator prover") })?; @@ -151,9 +151,10 @@ impl ProverBackend { // Store the stats in stats.json #[cfg(feature = "stats")] { - let stats_id = _stats.lock().unwrap().get_id(); - _stats.lock().unwrap().add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); - _stats.lock().unwrap().store_stats(); + let mut _stats = stats.get_inner().lock().unwrap(); + let stats_id = _stats.next_id(); + _stats.add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); + _stats.store_stats(); } Ok(ZiskVerifyConstraintsResult { execution: result, duration: elapsed, stats }) @@ -241,9 +242,10 @@ impl ProverBackend { // Store the stats in stats.json #[cfg(feature = "stats")] { - let stats_id = _stats.lock().unwrap().get_id(); - _stats.lock().unwrap().add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); - _stats.lock().unwrap().store_stats(); + let mut _stats = stats.get_inner().lock().unwrap(); + let stats_id = _stats.next_id(); + _stats.add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); + _stats.store_stats(); } self.proofman.set_barrier(); diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 588fe4b40..3e48249f8 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -8,7 +8,7 @@ use proofman::{AggProofs, ProofMan, ProvePhase, ProvePhaseInputs}; use proofman_common::{initialize_logger, ParamsGPU, ProofOptions}; use std::path::PathBuf; use zisk_common::io::{StreamSource, ZiskStdin}; -use zisk_common::ExecutorStats; +use zisk_common::ExecutorStatsHandle; use zisk_distributed_common::LoggingConfig; use anyhow::Result; @@ -110,7 +110,7 @@ impl ProverEngine for EmuProver { hints_stream: Option, debug_info: Option>, mpi_node: Option, - ) -> Result<(i32, i32, Option)> { + ) -> Result<(i32, i32, Option)> { let debug_info = create_debug_info(debug_info, self.core_prover.backend.proving_key.clone())?; diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 962a334a7..334fb1815 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -13,7 +13,7 @@ use anyhow::Result; use std::{path::PathBuf, time::Duration}; use zisk_common::{ io::{StreamSource, ZiskStdin}, - ExecutorStats, ZiskExecutionResult, + ExecutorStatsHandle, ZiskExecutionResult, }; pub struct ZiskExecuteResult { @@ -24,13 +24,13 @@ pub struct ZiskExecuteResult { pub struct ZiskVerifyConstraintsResult { pub execution: ZiskExecutionResult, pub duration: Duration, - pub stats: ExecutorStats, + pub stats: ExecutorStatsHandle, } pub struct ZiskProveResult { pub execution: ZiskExecutionResult, pub duration: Duration, - pub stats: ExecutorStats, + pub stats: ExecutorStatsHandle, pub proof: Proof, } @@ -64,7 +64,7 @@ pub trait ProverEngine { hints_stream: Option, debug_info: Option>, mpi_node: Option, - ) -> Result<(i32, i32, Option)>; + ) -> Result<(i32, i32, Option)>; fn verify_constraints_debug( &self, @@ -161,7 +161,7 @@ impl ZiskProver { hints_stream: Option, debug_info: Option>, mpi_node: Option, - ) -> Result<(i32, i32, Option)> { + ) -> Result<(i32, i32, Option)> { self.prover.stats(stdin, hints_stream, debug_info, mpi_node) } diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index dbdb516be..1431dcd71 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -28,7 +28,7 @@ use tracing::debug; use witness::{WitnessLibrary, WitnessManager}; use zisk_common::{ io::{ZiskStdin, ZiskStream}, - ExecutorStats, ZiskExecutionResult, ZiskLib, ZiskWitnessLibrary, + ExecutorStatsHandle, ZiskExecutionResult, ZiskLib, ZiskWitnessLibrary, }; use zisk_core::{Riscv2zisk, CHUNK_SIZE}; #[cfg(feature = "packed")] @@ -285,7 +285,7 @@ impl ZiskWitnessLibrary for WitnessLib { /// /// # Returns /// * `u16` - The execution result code. - fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)> { + fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStatsHandle)> { self.executor.as_ref().map(|executor| executor.get_execution_result()) } } From fa827788c36c0a2b1ee2e8ad8a60d3918a3fb5a8 Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 30 Jan 2026 17:21:50 +0100 Subject: [PATCH 374/782] Fix stats (thanks Roger) --- cli/src/commands/prove.rs | 2 -- cli/src/commands/stats.rs | 12 ------------ common/src/zisk_lib_init.rs | 4 ++-- sdk/src/prover/backend.rs | 28 ++++++++++++++++++++-------- 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 535effb18..6a2c7f36f 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -7,8 +7,6 @@ use std::path::PathBuf; use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; use zisk_sdk::{ProverClient, ZiskProveResult}; // Structure representing the 'prove' subcommand of cargo. diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index dbfdd5680..9573c4cb5 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -153,18 +153,6 @@ impl ZiskStats { Ok(()) } - fn create_stdin(&mut self) -> Result { - let stdin = if let Some(input) = &self.input { - if !input.exists() { - return Err(anyhow::anyhow!("Input file not found at {:?}", input.display())); - } - ZiskStdin::from_file(input)? - } else { - ZiskStdin::null() - }; - Ok(stdin) - } - pub fn run_emu(&mut self, stdin: ZiskStdin) -> Result<(i32, i32, Option)> { let prover = ProverClient::builder() .emu() diff --git a/common/src/zisk_lib_init.rs b/common/src/zisk_lib_init.rs index 74be3de93..677f456f9 100644 --- a/common/src/zisk_lib_init.rs +++ b/common/src/zisk_lib_init.rs @@ -6,7 +6,7 @@ use witness::WitnessLibrary; use crate::{ io::{StreamSource, ZiskStdin}, - ExecutorStats, + ExecutorStatsHandle, }; use anyhow::Result; @@ -42,7 +42,7 @@ pub struct Stats { pub trait ZiskWitnessLibrary { fn set_stdin(&self, stdin: ZiskStdin); fn set_hints_stream(&self, stream: StreamSource) -> Result<()>; - fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)>; + fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStatsHandle)>; } // SUpertrait for ZiskWitnessLibrary and WitnessLibrary diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index c53e042c2..debe63ba6 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -9,6 +9,8 @@ use fields::Goldilocks; use proofman::{AggProofs, ProofInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult}; use proofman_common::{DebugInfo, ProofOptions}; use std::{fs::File, io::Write, path::PathBuf}; +#[cfg(feature = "stats")] +use zisk_common::ExecutorStatsEvent; use zisk_common::{ io::{StreamSource, ZiskStdin}, ExecutorStatsHandle, ProofLog, ZiskExecutionResult, ZiskLib, @@ -151,10 +153,15 @@ impl ProverBackend { // Store the stats in stats.json #[cfg(feature = "stats")] { - let mut _stats = stats.get_inner().lock().unwrap(); - let stats_id = _stats.next_id(); - _stats.add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); - _stats.store_stats(); + let stats_id = stats.get_inner().lock().unwrap().next_id(); + stats.get_inner().lock().unwrap().add_stat( + 0, + stats_id, + "END", + 0, + ExecutorStatsEvent::Mark, + ); + stats.get_inner().lock().unwrap().store_stats(); } Ok(ZiskVerifyConstraintsResult { execution: result, duration: elapsed, stats }) @@ -242,10 +249,15 @@ impl ProverBackend { // Store the stats in stats.json #[cfg(feature = "stats")] { - let mut _stats = stats.get_inner().lock().unwrap(); - let stats_id = _stats.next_id(); - _stats.add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); - _stats.store_stats(); + let stats_id = stats.get_inner().lock().unwrap().next_id(); + stats.get_inner().lock().unwrap().add_stat( + 0, + stats_id, + "END", + 0, + ExecutorStatsEvent::Mark, + ); + stats.get_inner().lock().unwrap().store_stats(); } self.proofman.set_barrier(); From 38d8ddb50e4242e5db0f219c2103f8c400c932e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:32:31 +0000 Subject: [PATCH 375/782] Adding the secp256r1 precompile --- Cargo.lock | 13 + Cargo.toml | 1 + book/getting_started/precompiles.md | 10 +- common/src/bus/data_bus_operation.rs | 66 ++ core/src/riscv2zisk_context.rs | 4 +- core/src/zisk_ops.rs | 77 +- core/src/zisk_rom_2_asm.rs | 179 ++++ pil/operations.pil | 6 +- pil/opids.pil | 1 - pil/src/pil_helpers/traces.rs | 6 +- precompiles/arith_eq/Cargo.toml | 9 +- precompiles/arith_eq/pil/arith_eq.pil | 94 ++- .../arith_eq/pil/equations/secp256r1_add.pil | 777 ++++++++++++++++++ .../arith_eq/pil/equations/secp256r1_dbl.pil | 757 +++++++++++++++++ .../arith_eq/pil/equations/secp256r1_x3.pil | 537 ++++++++++++ .../arith_eq/pil/equations/secp256r1_y3.pil | 777 ++++++++++++++++++ precompiles/arith_eq/src/arith_eq.rs | 81 +- .../arith_eq/src/arith_eq_bus_device.rs | 28 +- .../arith_eq/src/arith_eq_constants.rs | 9 +- .../arith_eq/src/arith_eq_generator.rs | 71 ++ precompiles/arith_eq/src/arith_eq_input.rs | 39 + precompiles/arith_eq/src/arith_eq_instance.rs | 8 +- .../arith_eq/src/arith_eq_test_bigint.rs | 23 +- .../arith_eq/src/arith_eq_test_generator.rs | 64 +- .../arith_eq/src/arith_eq_test_secp256r1.rs | 67 ++ precompiles/arith_eq/src/equations/mod.rs | 8 + .../arith_eq/src/equations/secp256r1_add.rs | 755 +++++++++++++++++ .../arith_eq/src/equations/secp256r1_dbl.rs | 741 +++++++++++++++++ .../arith_eq/src/equations/secp256r1_x3.rs | 502 +++++++++++ .../arith_eq/src/equations/secp256r1_y3.rs | 753 +++++++++++++++++ precompiles/arith_eq/src/executors/mod.rs | 3 + .../arith_eq/src/executors/secp256r1.rs | 182 ++++ .../arith_eq/src/generator/equation.rs | 15 +- precompiles/arith_eq/src/mem_inputs/mod.rs | 4 + .../arith_eq/src/mem_inputs/secp256r1_add.rs | 43 + .../arith_eq/src/mem_inputs/secp256r1_dbl.rs | 42 + precompiles/arith_eq/src/test_data/mod.rs | 4 + .../src/test_data/secp256r1_add_test_data.rs | 428 ++++++++++ .../src/test_data/secp256r1_dbl_test_data.rs | 327 ++++++++ precompiles/helpers/Cargo.toml | 1 + precompiles/helpers/src/arith_eq/mod.rs | 2 + precompiles/helpers/src/arith_eq/secp256r1.rs | 30 + precompiles/helpers/src/common.rs | 12 + ziskos/entrypoint/src/syscalls/mod.rs | 4 + .../entrypoint/src/syscalls/secp256r1_add.rs | 42 + .../entrypoint/src/syscalls/secp256r1_dbl.rs | 34 + ziskos/entrypoint/src/syscalls/syscall.rs | 2 + 47 files changed, 7582 insertions(+), 56 deletions(-) create mode 100644 precompiles/arith_eq/pil/equations/secp256r1_add.pil create mode 100644 precompiles/arith_eq/pil/equations/secp256r1_dbl.pil create mode 100644 precompiles/arith_eq/pil/equations/secp256r1_x3.pil create mode 100644 precompiles/arith_eq/pil/equations/secp256r1_y3.pil create mode 100644 precompiles/arith_eq/src/arith_eq_test_secp256r1.rs create mode 100644 precompiles/arith_eq/src/equations/secp256r1_add.rs create mode 100644 precompiles/arith_eq/src/equations/secp256r1_dbl.rs create mode 100644 precompiles/arith_eq/src/equations/secp256r1_x3.rs create mode 100644 precompiles/arith_eq/src/equations/secp256r1_y3.rs create mode 100644 precompiles/arith_eq/src/executors/secp256r1.rs create mode 100644 precompiles/arith_eq/src/mem_inputs/secp256r1_add.rs create mode 100644 precompiles/arith_eq/src/mem_inputs/secp256r1_dbl.rs create mode 100644 precompiles/arith_eq/src/test_data/secp256r1_add_test_data.rs create mode 100644 precompiles/arith_eq/src/test_data/secp256r1_dbl_test_data.rs create mode 100644 precompiles/helpers/src/arith_eq/secp256r1.rs create mode 100644 ziskos/entrypoint/src/syscalls/secp256r1_add.rs create mode 100644 ziskos/entrypoint/src/syscalls/secp256r1_dbl.rs diff --git a/Cargo.lock b/Cargo.lock index 09814d1a0..03918229c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,6 +238,17 @@ dependencies = [ "ark-std", ] +[[package]] +name = "ark-secp256r1" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf8be5820de567729bfa73a410ddd07cec8ad102d9a4bf61fd6b2e60db264e8" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-serialize" version = "0.5.0" @@ -2857,6 +2868,7 @@ dependencies = [ "ark-bn254", "ark-ff", "ark-secp256k1", + "ark-secp256r1", "ark-std", "fields", "k256", @@ -3054,6 +3066,7 @@ dependencies = [ "ark-bn254", "ark-ff", "ark-secp256k1", + "ark-secp256r1", "ark-std", "cfg-if", "circuit", diff --git a/Cargo.toml b/Cargo.toml index b17b63981..58d191229 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,6 +133,7 @@ colored = "3" ark-ff = "0.5.0" ark-std = "0.5.0" ark-secp256k1 = "0.5" +ark-secp256r1 = "0.5" ark-bn254 = "0.5.0" ark-bls12-381 = "0.5.0" sysinfo = "0.37" diff --git a/book/getting_started/precompiles.md b/book/getting_started/precompiles.md index 0dc7444a8..f6c501c09 100644 --- a/book/getting_started/precompiles.md +++ b/book/getting_started/precompiles.md @@ -15,12 +15,17 @@ You can see [here](https://github.com/0xPolygonHermez/zisk-patch-tiny-keccak/tre ### Available Precompiles in ZisK Below is a summary of the precompiles currently available in ZisK: -- [syscall_arith256_mod](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/arith256_mod.rs): Modular multiplication followed by addition over 256-bit non-negative integers. +- [syscall_add256](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/add256.rs): Addition over 256-bit non-negative integers. - [syscall_arith256](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/arith256.rs): Multiplication followed by addition over 256-bit non-negative integers. +- [syscall_arith256_mod](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/arith256_mod.rs): Modular multiplication followed by addition over 256-bit non-negative integers. +- [syscall_arith384_mod](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/arith384_mod.rs): Modular multiplication followed by addition over 256-bit non-negative integers. - [syscall_keccak_f](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/keccakf.rs): Keccak-f[1600] permutation function from the [Keccak](https://keccak.team/files/Keccak-reference-3.0.pdf) cryptographic sponge construction. - [syscall_sha256_f](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/sha256f.rs): Extend and compress function of the [SHA-256](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) cryptographic hash algorithm. +- [syscall_syscall_poseidon2](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/poseidon2.rs): Compression function of the [Poseidon2](https://eprint.iacr.org/2023/323.pdf) cryptographic hash algorithm. - [syscall_secp256k1_add](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/secp256k1_add.rs): Elliptic curve point addition over the [Secp256k1](https://en.bitcoin.it/wiki/Secp256k1) curve. - [syscall_secp256k1_dbl](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/secp256k1_dbl.rs): Elliptic curve point doubling over the [Secp256k1](https://en.bitcoin.it/wiki/Secp256k1) curve. +- [syscall_secp256r1_add](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/secp256r1_add.rs): Elliptic curve point addition over the [Secp256r1](https://csrc.nist.gov/pubs/sp/800/186/final) curve. +- [syscall_secp256r1_dbl](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/secp256r1_dbl.rs): Elliptic curve point doubling over the [Secp256r1](https://csrc.nist.gov/pubs/sp/800/186/final) curve. - [syscall_bn254_curve_add](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/bn254_curve_add.rs): Elliptic curve point addition over the [Bn254](https://hackmd.io/kcEJAWISQ56eE6YpBnurgw) curve. - [syscall_bn254_curve_dbl](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/bn254_curve_dbl.rs): Elliptic curve point doubling over the [Bn254](https://hackmd.io/kcEJAWISQ56eE6YpBnurgw) curve. - [syscall_bn254_complex_add](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/bn254_complex_add.rs): Complex addition within the quadratic extension built over the base field of the [Bn254](https://hackmd.io/kcEJAWISQ56eE6YpBnurgw) curve. @@ -31,5 +36,4 @@ Below is a summary of the precompiles currently available in ZisK: - [syscall_bls12_381_curve_dbl](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/bls12_381_curve_dbl.rs): Elliptic curve point doubling over the BLS12-381 curve. - [syscall_bls12_381_complex_add](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs): Complex addition within the quadratic extension built over the base field of the BLS12-381 curve. - [syscall_bls12_381_complex_sub](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs): Complex subtraction within the quadratic extension built over the base field of the BLS12-381 curve. -- [syscall_bls12_381_complex_mul](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs): Complex multiplication within the quadratic extension built over the base field of the BLS12-381 curve. -- [syscall_add256](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/add256.rs): 256 bits addition with one carry in bit and carry out bit. \ No newline at end of file +- [syscall_bls12_381_complex_mul](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/bls12_381_complex_add.rs): Complex multiplication within the quadratic extension built over the base field of the BLS12-381 curve. \ No newline at end of file diff --git a/common/src/bus/data_bus_operation.rs b/common/src/bus/data_bus_operation.rs index 4906a64be..f087d7903 100644 --- a/common/src/bus/data_bus_operation.rs +++ b/common/src/bus/data_bus_operation.rs @@ -70,6 +70,10 @@ pub const OPERATION_BUS_BLS12_381_COMPLEX_SUB_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; pub const OPERATION_BUS_BLS12_381_COMPLEX_MUL_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; +pub const OPERATION_BUS_SECP256R1_ADD_DATA_SIZE: usize = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_256_BITS_SIZE; +pub const OPERATION_BUS_SECP256R1_DBL_DATA_SIZE: usize = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + POINT_256_BITS_SIZE; // bus_data_size + 4 params (&a, &b, cin, &c, a, b) pub const OPERATION_BUS_ADD_256_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE @@ -126,6 +130,8 @@ pub type OperationBls12_381ComplexMulData = [D; OPERATION_BUS_BLS12_381_COMPL pub type OperationAdd256Data = [D; OPERATION_BUS_ADD_256_DATA_SIZE]; pub type OperationDmaMemCpyData = [D; OPERATION_BUS_DMA_MEMCPY_DATA_SIZE]; pub type OperationDmaMemCmpData = [D; OPERATION_BUS_DMA_MEMCMP_DATA_SIZE]; +pub type OperationSecp256r1AddData = [D; OPERATION_BUS_SECP256R1_ADD_DATA_SIZE]; +pub type OperationSecp256r1DblData = [D; OPERATION_BUS_SECP256R1_DBL_DATA_SIZE]; pub enum ExtOperationData { OperationData(OperationData), @@ -150,6 +156,8 @@ pub enum ExtOperationData { OperationAdd256Data(OperationAdd256Data), OperationDmaMemCpyData(OperationDmaMemCpyData), OperationDmaMemCmpData(OperationDmaMemCmpData), + OperationSecp256r1AddData(OperationSecp256r1AddData), + OperationSecp256r1DblData(OperationSecp256r1DblData), } const KECCAK_OP: u8 = ZiskOp::Keccak.code(); @@ -173,6 +181,8 @@ const BLS12_381_COMPLEX_MUL_OP: u8 = ZiskOp::Bls12_381ComplexMul.code(); const ADD256_OP: u8 = ZiskOp::Add256.code(); const DMA_MEMCPY_OP: u8 = ZiskOp::DmaMemCpy.code(); const DMA_MEMCMP_OP: u8 = ZiskOp::DmaMemCmp.code(); +const SECP256R1_ADD_OP: u8 = ZiskOp::Secp256r1Add.code(); +const SECP256R1_DBL_OP: u8 = ZiskOp::Secp256r1Dbl.code(); // impl> TryFrom<&[D]> for ExtOperationData { impl> TryFrom<&[D]> for ExtOperationData { @@ -289,6 +299,16 @@ impl> TryFrom<&[D]> for ExtOperationData { data.try_into().map_err(|_| "Invalid OperationDmaMemCmpData size")?; Ok(ExtOperationData::OperationDmaMemCmpData(array)) } + SECP256R1_ADD_OP => { + let array: OperationSecp256r1AddData = + data.try_into().map_err(|_| "Invalid OperationSecp256r1AddData size")?; + Ok(ExtOperationData::OperationSecp256r1AddData(array)) + } + SECP256R1_DBL_OP => { + let array: OperationSecp256r1DblData = + data.try_into().map_err(|_| "Invalid OperationSecp256r1DblData size")?; + Ok(ExtOperationData::OperationSecp256r1DblData(array)) + } _ => { let array: OperationData = data.try_into().map_err(|_| "Invalid OperationData size")?; @@ -463,6 +483,26 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254ComplexMulData(data) } + SECP256R1_ADD_OP => { + let mut data = unsafe { + uninit_array::().assume_init() + }; + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationSecp256r1AddData(data) + } + SECP256R1_DBL_OP => { + let mut data = unsafe { + uninit_array::().assume_init() + }; + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationSecp256r1DblData(data) + } _ => ExtOperationData::OperationData([op, op_type, a, b]), }, @@ -694,6 +734,24 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } + SECP256R1_ADD_OP => { + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] + .copy_from_slice(&ctx.precompiled.input_data); + &buffer[..len] + } + SECP256R1_DBL_OP => { + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] + .copy_from_slice(&ctx.precompiled.input_data); + &buffer[..len] + } _ => { buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); &buffer[..OPERATION_BUS_DATA_SIZE] @@ -830,6 +888,8 @@ impl OperationBusData { ExtOperationData::OperationAdd256Data(d) => d[OP] as u8, ExtOperationData::OperationDmaMemCpyData(d) => d[OP] as u8, ExtOperationData::OperationDmaMemCmpData(d) => d[OP] as u8, + ExtOperationData::OperationSecp256r1AddData(d) => d[OP] as u8, + ExtOperationData::OperationSecp256r1DblData(d) => d[OP] as u8, } } @@ -865,6 +925,8 @@ impl OperationBusData { ExtOperationData::OperationAdd256Data(d) => d[OP_TYPE], ExtOperationData::OperationDmaMemCpyData(d) => d[OP_TYPE], ExtOperationData::OperationDmaMemCmpData(d) => d[OP_TYPE], + ExtOperationData::OperationSecp256r1AddData(d) => d[OP_TYPE], + ExtOperationData::OperationSecp256r1DblData(d) => d[OP_TYPE], } } @@ -900,6 +962,8 @@ impl OperationBusData { ExtOperationData::OperationAdd256Data(d) => d[A], ExtOperationData::OperationDmaMemCpyData(d) => d[A], ExtOperationData::OperationDmaMemCmpData(d) => d[A], + ExtOperationData::OperationSecp256r1AddData(d) => d[A], + ExtOperationData::OperationSecp256r1DblData(d) => d[A], } } @@ -935,6 +999,8 @@ impl OperationBusData { ExtOperationData::OperationAdd256Data(d) => d[B], ExtOperationData::OperationDmaMemCpyData(d) => d[B], ExtOperationData::OperationDmaMemCmpData(d) => d[B], + ExtOperationData::OperationSecp256r1AddData(d) => d[B], + ExtOperationData::OperationSecp256r1DblData(d) => d[B], } } } diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 2a22683a4..01ef952a8 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -14,7 +14,7 @@ use std::collections::HashMap; // The CSR precompiled addresses are defined in the `ZiskOS` `ziskos/entrypoint/src` files // because legacy versions of Rust do not support constant parameters in `asm!` macros. -const CSR_PRECOMPILED: [&str; 21] = [ +const CSR_PRECOMPILED: [&str; 23] = [ "keccak", "arith256", "arith256_mod", @@ -36,6 +36,8 @@ const CSR_PRECOMPILED: [&str; 21] = [ "dma_memcpy", "dma_memcmp", "poseidon2", + "secp256r1_add", + "secp256r1_dbl", ]; const CSR_PRECOMPILED_ADDR_START: u32 = 0x800; const CSR_PRECOMPILED_ADDR_END: u32 = CSR_PRECOMPILED_ADDR_START + CSR_PRECOMPILED.len() as u32; diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 67e5f5d94..331ba0b33 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -440,7 +440,7 @@ define_ops! { (DmaPost, "_dma_post", Dma, DMA_PRE_POST_COST, 0xdd, 8, 0, opc_virtual, op_virtual, ops_virtual), (DmaCmpByte, "_dma_cmp_byte", Dma, DMA_PRE_POST_COST, 0xde, 8, 0, opc_virtual, op_virtual, ops_virtual), // opcodes 0xda-0xdf reserved for dma extra operations (costs) - // opcodes 0xe0,0xe1 are available + // opcodes 0xe0 is available (Arith384Mod, "arith384_mod", ArithEq384, ARITH_EQ_384_COST, 0xe2, 232, 48, opc_arith384_mod, op_arith384_mod, ops_arith384_mod), (Bls12_381CurveAdd, "bls12_381_curve_add", ArithEq384, ARITH_EQ_384_COST, 0xe3, 208, 96, opc_bls12_381_curve_add, op_bls12_381_curve_add, ops_bls12_381_curve_add), (Bls12_381CurveDbl, "bls12_381_curve_dbl", ArithEq384, ARITH_EQ_384_COST, 0xe4, 96, 96, opc_bls12_381_curve_dbl, op_bls12_381_curve_dbl, ops_bls12_381_curve_dbl), @@ -453,6 +453,8 @@ define_ops! { (Arith256Mod, "arith256_mod", ArithEq, ARITH_EQ_COST, 0xf3, 168, 32, opc_arith256_mod, op_arith256_mod, ops_arith256_mod), (Secp256k1Add, "secp256k1_add", ArithEq, ARITH_EQ_COST, 0xf4, 144, 64, opc_secp256k1_add, op_secp256k1_add, ops_secp256k1_add), (Secp256k1Dbl, "secp256k1_dbl", ArithEq, ARITH_EQ_COST, 0xf5, 64, 64, opc_secp256k1_dbl, op_secp256k1_add, ops_secp256k1_dbl), + (Secp256r1Add, "secp256r1_add", ArithEq, ARITH_EQ_COST, 0xe8, 144, 64, opc_secp256r1_add, op_secp256r1_add, ops_secp256r1_add), + (Secp256r1Dbl, "secp256r1_dbl", ArithEq, ARITH_EQ_COST, 0xe9, 64, 64, opc_secp256r1_dbl, op_secp256r1_dbl, ops_secp256r1_dbl), (FcallParam, "fcall_param", Fcall, FCALL_COST, 0xf6, 0, 0, opc_fcall_param, op_fcall_param, ops_none), (Fcall, "fcall", Fcall, FCALL_COST, 0xf7, 0, 0, opc_fcall, op_fcall, ops_none), (FcallGet, "fcall_get", Fcall, FCALL_COST, 0xf8, 0, 0, opc_fcall_get, op_fcall_get, ops_none), @@ -1823,6 +1825,79 @@ pub fn ops_secp256k1_dbl(ctx: &InstContext, stats: &mut dyn OpStats) { precompiled_stats_direct_data(ctx, stats, 8, 8); } +#[inline(always)] +pub fn opc_secp256r1_add(ctx: &mut InstContext) { + const WORDS: usize = 2 + 2 * 8; + let mut data = [0u64; WORDS]; + + precompiled_load_data(ctx, 2, 2, 8, 0, &mut data, "secp256r1_add"); + + if ctx.emulation_mode != EmulationMode::ConsumeMemReads { + // ignore 2 indirections + let (_, rest) = data.split_at(2); + let (p1, p2) = rest.split_at(8); + + let p1: &[u64; 8] = p1.try_into().expect("opc_secp256r1_add: p1.len != 8"); + let p2: &[u64; 8] = p2.try_into().expect("opc_secp256r1_add: p2.len != 8"); + let mut p3 = [0u64; 8]; + + precompiles_helpers::secp256r1_add(p1, p2, &mut p3); + + // [0:p1,p2] + for (i, d) in p3.iter().enumerate() { + ctx.mem.write(data[0] + (8 * i as u64), *d, 8); + } + } + ctx.c = 0; + ctx.flag = false; +} + +/// Unimplemented. Secp256r1Add can only be called from the system call context via InstContext. +/// This is provided just for completeness. +#[inline(always)] +pub fn op_secp256r1_add(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_secp256r1_add() is not implemented"); +} + +#[inline(always)] +pub fn ops_secp256r1_add(ctx: &InstContext, stats: &mut dyn OpStats) { + precompiled_stats_data(ctx, stats, &[8, 8], &[], 1); +} + +#[inline(always)] +pub fn opc_secp256r1_dbl(ctx: &mut InstContext) { + const WORDS: usize = 8; // one input of 8 64-bit words + let mut data = [0u64; WORDS]; + + precompiled_load_data(ctx, 0, 1, 8, 0, &mut data, "secp256r1_dbl"); + + if ctx.emulation_mode != EmulationMode::ConsumeMemReads { + let p1: &[u64; 8] = &data; + let mut p3 = [0u64; 8]; + + precompiles_helpers::secp256r1_dbl(p1, &mut p3); + + for (i, d) in p3.iter().enumerate() { + ctx.mem.write(ctx.b + (8 * i as u64), *d, 8); + } + } + + ctx.c = 0; + ctx.flag = false; +} + +/// Unimplemented. Secp256r1Dbl can only be called from the system call context via InstContext. +/// This is provided just for completeness. +#[inline(always)] +pub fn op_secp256r1_dbl(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_secp256r1_dbl() is not implemented"); +} + +#[inline(always)] +pub fn ops_secp256r1_dbl(ctx: &InstContext, stats: &mut dyn OpStats) { + precompiled_stats_direct_data(ctx, stats, 8, 8); +} + #[inline(always)] pub fn opc_bn254_curve_add(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 8; diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 3fe26b6ab..b9004bb54 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -232,6 +232,8 @@ impl ZiskAsmContext { | ZiskOp::Bls12_381ComplexSub | ZiskOp::Bls12_381ComplexMul | ZiskOp::Add256 + | ZiskOp::Secp256r1Add + | ZiskOp::Secp256r1Dbl ) } } @@ -528,6 +530,8 @@ impl ZiskRom2Asm { *code += ".extern opcode_arith256_mod\n"; *code += ".extern opcode_secp256k1_add\n"; *code += ".extern opcode_secp256k1_dbl\n"; + *code += ".extern opcode_secp256r1_add\n"; + *code += ".extern opcode_secp256r1_dbl\n"; *code += ".extern opcode_fcall\n"; *code += ".extern opcode_bn254_curve_add\n"; *code += ".extern opcode_bn254_curve_dbl\n"; @@ -5614,6 +5618,181 @@ impl ZiskRom2Asm { ctx.c.is_saved = true; ctx.flag_is_always_zero = true; } + ZiskOp::Secp256r1Add => { + *code += &ctx.full_line_comment("Secp256r1Add".to_string()); + + // Use the memory address as the first and unique parameter + if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() + { + *code += &format!( + "\tmov rdi, {} {}\n", + ctx.b.string_value, + ctx.comment_str("rdi = b = address") + ); + } + + // Save data into mem_reads + if ctx.minimal_trace() || ctx.zip() || ctx.mem_reads() { + // If zip, check if chunk is active + if ctx.zip() { + *code += &format!( + "\ttest {}, 1 {}\n", + REG_ACTIVE_CHUNK, + ctx.comment_str("active_chunk == 1 ?") + ); + *code += &format!("\tjnz pc_{:x}_secp256r1add_active_chunk\n", ctx.pc); + *code += &format!("\tjmp pc_{:x}_secp256r1add_active_chunk_done\n", ctx.pc); + *code += &format!("pc_{:x}_secp256r1add_active_chunk:\n", ctx.pc); + } + Self::precompiled_save_mem_reads(ctx, code, 2, &[8, 8]); + if ctx.zip() { + *code += &format!("pc_{:x}_secp256r1add_active_chunk_done:\n", ctx.pc); + } + } + + // Save memory operations into mem_reads + if ctx.mem_op() { + Self::mem_op_precompiled_read_and_write(ctx, code, 2, &[8, 8], 0, 0, 8); + } + + if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() + { + // Call the secp256r1_add function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_secp256r1_add\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } + + // Consume mem reads + if ctx.chunk_player_mem_reads_collect_main() { + *code += &format!( + "\tmov [{} + {}*8], {} {}\n", + REG_MEM_READS_ADDRESS, + REG_MEM_READS_SIZE, + REG_CHUNK_PLAYER_ADDRESS, + ctx.comment_str("Main[4] = precompiler data address") + ); + *code += &format!( + "\tinc {} {}\n", + REG_MEM_READS_SIZE, + ctx.comment_str("mem_reads_size++") + ); + } + if ctx.chunk_player_mt_collect_mem() || ctx.chunk_player_mem_reads_collect_main() { + *code += &format!( + "\tadd {}, 18*8 {}\n", + REG_CHUNK_PLAYER_ADDRESS, + ctx.comment_str("chunk_address += 18*8") + ); + } + + // Set result + *code += &format!("\txor {}, {} {}\n", REG_C, REG_C, ctx.comment_str("c = 0")); + ctx.c.is_saved = true; + ctx.flag_is_always_zero = true; + } + ZiskOp::Secp256r1Dbl => { + *code += &ctx.full_line_comment("Secp256r1Dbl".to_string()); + + // Use the memory address as the first and unique parameter + if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() + { + *code += &format!( + "\tmov rdi, {} {}\n", + ctx.b.string_value, + ctx.comment_str("rdi = b = address") + ); + } + + // Copy read data into mem_reads + if ctx.minimal_trace() || ctx.zip() || ctx.mem_reads() { + // If zip, check if chunk is active + if ctx.zip() { + *code += &format!( + "\ttest {}, 1 {}\n", + REG_ACTIVE_CHUNK, + ctx.comment_str("active_chunk == 1 ?") + ); + *code += &format!("\tjnz pc_{:x}_secp256r1dbl_active_chunk\n", ctx.pc); + *code += &format!("\tjmp pc_{:x}_secp256r1dbl_active_chunk_done\n", ctx.pc); + *code += &format!("pc_{:x}_secp256r1dbl_active_chunk:\n", ctx.pc); + } + *code += &format!("\tmov {REG_ADDRESS}, rdi\n"); + for k in 0..8 { + *code += &format!( + "\tmov {}, [{} + {}] {}\n", + REG_VALUE, + REG_ADDRESS, + k * 8, + ctx.comment(format!("value = mem[address[{k}]]")) + ); + *code += &format!( + "\tmov [{} + {}*8 + {}], {} {}\n", + REG_MEM_READS_ADDRESS, + REG_MEM_READS_SIZE, + k * 8, + REG_VALUE, + ctx.comment(format!("mem_reads[{k}] = value")) + ); + } + + // Increment chunk.steps.mem_reads_size in 8 units + *code += &format!( + "\tadd {}, 8 {}\n", + REG_MEM_READS_SIZE, + ctx.comment_str("mem_reads_size += 8") + ); + if ctx.zip() { + *code += &format!("pc_{:x}_secp256r1dbl_active_chunk_done:\n", ctx.pc); + } + } + + // Save memory operations into mem_reads + if ctx.mem_op() { + Self::mem_op_array(ctx, code, "rdi", false, 8); + Self::mem_op_array(ctx, code, "rdi", true, 8); + } + + if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() + { + // Call the secp256r1_dbl function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_secp256r1_dbl\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } + + // Consume mem reads + if ctx.chunk_player_mem_reads_collect_main() { + *code += &format!( + "\tmov [{} + {}*8], {} {}\n", + REG_MEM_READS_ADDRESS, + REG_MEM_READS_SIZE, + REG_CHUNK_PLAYER_ADDRESS, + ctx.comment_str("Main[4] = precompiler data address") + ); + *code += &format!( + "\tinc {} {}\n", + REG_MEM_READS_SIZE, + ctx.comment_str("mem_reads_size++") + ); + } + if ctx.chunk_player_mt_collect_mem() || ctx.chunk_player_mem_reads_collect_main() { + *code += &format!( + "\tadd {}, 8*8 {}\n", + REG_CHUNK_PLAYER_ADDRESS, + ctx.comment_str("chunk_address += 8*8") + ); + } + + // Set result + *code += &format!("\txor {}, {} {}\n", REG_C, REG_C, ctx.comment_str("c = 0")); + ctx.c.is_saved = true; + ctx.flag_is_always_zero = true; + } ZiskOp::FcallParam => { assert!(ctx.store_b_in_c); assert!(ctx.a.is_constant); diff --git a/pil/operations.pil b/pil/operations.pil index 0aefb95ab..859fca342 100644 --- a/pil/operations.pil +++ b/pil/operations.pil @@ -8,8 +8,10 @@ - 0x21-0x29 - Arithmetic Operations: - 0xB0-0xBF + - DMA Operations: + - 0xDA-0xDF - Precompiles: - - 0xE1-0xE7 + - 0xE1-0xE9 - 0xF0-0xF5 - 0xF9-0xFE - Misc: @@ -95,6 +97,8 @@ const int OP_COMPLEX_ADD_BLS12_381 = 0xE5; const int OP_COMPLEX_SUB_BLS12_381 = 0xE6; const int OP_COMPLEX_MUL_BLS12_381 = 0xE7; +const int OP_EC_ADD_SECP256R1 = 0xE8; +const int OP_EC_DBL_SECP256R1 = 0xE9; const int OP_ADD256 = 0xF0; const int OP_KECCAKF = 0xF1; diff --git a/pil/opids.pil b/pil/opids.pil index c1ee73de8..2509e760b 100644 --- a/pil/opids.pil +++ b/pil/opids.pil @@ -29,7 +29,6 @@ const int BINARY_EXTENSION_FROPS_TABLE_ID = 5012; const int ARITH_EQ_LT_TABLE_ID = 5002; const int KECCAKF_TABLE_ID = 126; - // DMA const int DMA_BUS_ID = 8000; const int DMA_ROM_ID = 8001; diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index eb94603e0..d03c7cfd2 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "28c8185c73aa9a58d122501d67edea0d8dd62d1893c974f4155b3944a7c84bbd"; +pub const PILOUT_HASH: &str = "714c5bc49f0ec815cfed6956d9919523e8fef7142ba6edf74c433e7dedf273b7"; pub const MERKLE_TREE_ARITY: u64 = 4; @@ -375,7 +375,7 @@ trace_row!(ArithEqFixedRow { pub type ArithEqFixed = GenericTrace, 1048576, 0, 18>; trace_row!(ArithEqTraceRow { - x1:u16, y1:u16, x2:u16, y2:u16, x3:u16, y3:u16, q0:ubit(22), q1:ubit(22), q2:ubit(22), s:ubit(22), sel_op:[bit; 9], sel_op_clk0:[bit; 9], x_delta_chunk_inv:u64, x_are_different:bit, x3_lt:bit, y3_lt:bit, carry:[[u64; 2]; 3], step_addr:ubit(40), + x1:u16, y1:u16, x2:u16, y2:u16, x3:u16, y3:u16, q0:ubit(22), q1:ubit(22), q2:ubit(22), s:ubit(22), sel_op:[bit; 11], sel_op_clk0:[bit; 11], x_delta_chunk_inv:u64, x_are_different:bit, x3_lt:bit, y3_lt:bit, carry:[[u64; 2]; 3], step_addr:ubit(40), }); pub type ArithEqTrace = GenericTrace, 1048576, 0, 18>; @@ -719,7 +719,7 @@ pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ (0, 18, PackedInfoConst { is_packed: true, num_packed_words: 11, - unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], + unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], }), (0, 19, PackedInfoConst { is_packed: true, diff --git a/precompiles/arith_eq/Cargo.toml b/precompiles/arith_eq/Cargo.toml index e5711f81d..7a3b81b60 100644 --- a/precompiles/arith_eq/Cargo.toml +++ b/precompiles/arith_eq/Cargo.toml @@ -30,6 +30,11 @@ name = "arith_eq_test_secp256k1" path = "src/arith_eq_test_secp256k1.rs" required-features = ["test_data"] +[[bin]] +name = "arith_eq_test_secp256r1" +path = "src/arith_eq_test_secp256r1.rs" +required-features = ["test_data"] + [dependencies] zisk-core = { workspace = true } zisk-pil = { workspace = true } @@ -53,6 +58,7 @@ rayon = { workspace = true } ark-ff = { workspace = true } ark-std = { workspace = true } ark-secp256k1 = { workspace = true } +ark-secp256r1 = { workspace = true } ark-bn254 = { workspace = true } num-bigint = { workspace = true } num-traits = { workspace = true } @@ -74,4 +80,5 @@ packed = ["proofman-common/packed"] diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] no_lib_link = ["proofman-common/no_lib_link"] test_data = [] -test_data_secp256k1 = [] \ No newline at end of file +test_data_secp256k1 = [] +test_data_secp256r1 = [] \ No newline at end of file diff --git a/precompiles/arith_eq/pil/arith_eq.pil b/precompiles/arith_eq/pil/arith_eq.pil index d75ba7b83..e2514fd9b 100644 --- a/precompiles/arith_eq/pil/arith_eq.pil +++ b/precompiles/arith_eq/pil/arith_eq.pil @@ -27,15 +27,22 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION EQ13: y1 - y2 - y3 + (q2 * p2) y3 COMPLEX_SUB_BN254 EQ14: x1 * x2 - y1 * y2 - x3 + (q1 * p2) x3 COMPLEX_MUL_BN254 EQ15: y1 * x2 + x1 * y2 - y3 + (q2 * p2) y3 COMPLEX_MUL_BN254 + EQ16: s * x2 - s * x1 - y2 + y1 + (q0 * p3) lambda - ADD EC_ADD_SECP256R1 + EQ17: 2 * s * y1 - 3 * x1 * x1 - a + (q0 * p3) lambda - DBL EC_DBL_SECP256R1 + EQ18: s * s - x1 - x2 - x3 + (q1 * p3) x3 EC_ADD_SECP256R1, EC_DBL_SECP256R1 + EQ19: s * x1 - s * x3 - y1 - y3 + (q2 * p3) y3 EC_ADD_SECP256R1, EC_DBL_SECP256R1 where p1 refers to the base field order of: · Secp256k1: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F and p2 refers to the base field order of: · BN254: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD47 + and p3 refers to the base field order of: + · Secp256r1: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF */ const int SECP256K1_PRIME = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F; const int BN254_PRIME = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD47; + const int SECP256R1_PRIME = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; const int CLOCKS = 16; col fixed CLK_0 = [1, 0:(CLOCKS-1)]...; @@ -59,8 +66,8 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION const int CARRY_MIN = -(2**CARRY_BITS - 1); const int CARRY_MAX = 2**CARRY_BITS; - const int EQS = 16; // Number of equations - const int OPS = 9; // Number of operations + const int EQS = 20; // Number of equations + const int OPS = 11; // Number of operations const int MAX_CEQS = 3; // Max concurrent equations const int QS = 3; // Number of quotients @@ -85,17 +92,20 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION const expr sel_bn254_complex_add = sel_op[6]; const expr sel_bn254_complex_sub = sel_op[7]; const expr sel_bn254_complex_mul = sel_op[8]; + const expr sel_secp256r1_add = sel_op[9]; + const expr sel_secp256r1_dbl = sel_op[10]; // groups of selectors const expr sel_any_arith256 = sel_arith256 + sel_arith256_mod; const expr sel_secp256k1 = sel_secp256k1_add + sel_secp256k1_dbl; const expr sel_bn254_curve = sel_bn254_curve_add + sel_bn254_curve_dbl; const expr sel_bn254_complex = sel_bn254_complex_add + sel_bn254_complex_sub + sel_bn254_complex_mul; - const expr sel_check_diff = sel_secp256k1_add + sel_bn254_curve_add; + const expr sel_secp256r1 = sel_secp256r1_add + sel_secp256r1_dbl; + const expr sel_check_diff = sel_secp256k1_add + sel_bn254_curve_add + sel_secp256r1_add; const expr sel_check_x_lt_prime = sel_arith256_mod + sel_secp256k1 + - sel_bn254_curve + sel_bn254_complex; + sel_bn254_curve + sel_bn254_complex + sel_secp256r1; const expr sel_check_y_lt_prime = sel_secp256k1 + sel_bn254_curve + - sel_bn254_complex; + sel_bn254_complex + sel_secp256r1; const expr eq_selectors[EQS] = [sel_arith256, sel_arith256_mod, sel_secp256k1_add, sel_secp256k1_dbl, @@ -104,11 +114,13 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION sel_bn254_curve, sel_bn254_curve, sel_bn254_complex_add, sel_bn254_complex_add, sel_bn254_complex_sub, sel_bn254_complex_sub, - sel_bn254_complex_mul, sel_bn254_complex_mul]; + sel_bn254_complex_mul, sel_bn254_complex_mul, + sel_secp256r1_add, sel_secp256r1_dbl, + sel_secp256r1, sel_secp256r1]; // constraint to set (x1,y1) = (x2,y2) - (sel_secp256k1_dbl + sel_bn254_curve_dbl) * (x1 - x2) === 0; - (sel_secp256k1_dbl + sel_bn254_curve_dbl) * (y1 - y2) === 0; + (sel_secp256k1_dbl + sel_bn254_curve_dbl + sel_secp256r1_dbl) * (x1 - x2) === 0; + (sel_secp256k1_dbl + sel_bn254_curve_dbl + sel_secp256r1_dbl) * (y1 - y2) === 0; const expr chunk_cols[7] = [x1, y1, x2, y2, x3, y3, s]; const expr qs[QS] = [q0, q1, q2]; @@ -131,6 +143,10 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION include "equations/bn254_complex_sub_y3.pil" include "equations/bn254_complex_mul_x3.pil" include "equations/bn254_complex_mul_y3.pil" + include "equations/secp256r1_add.pil" + include "equations/secp256r1_dbl.pil" + include "equations/secp256r1_x3.pil" + include "equations/secp256r1_y3.pil" col witness bits(1) sel_op_clk0[OPS]; @@ -145,6 +161,9 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION const expr bn254_complex_add_clk0 = sel_op_clk0[6]; const expr bn254_complex_sub_clk0 = sel_op_clk0[7]; const expr bn254_complex_mul_clk0 = sel_op_clk0[8]; + const expr secp256r1_add_clk0 = sel_op_clk0[9]; + const expr secp256r1_dbl_clk0 = sel_op_clk0[10]; + const expr secp256r1_clk0 = sel_op_clk0[9] + sel_op_clk0[10]; expr sum_sel_op = 0; expr sum_sel_op_clk0 = 0; @@ -181,17 +200,21 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION + expr_group_by_cbc(secp256k1_add_clk0, eq_secp256k1_add_chunks, i) + expr_group_by_cbc(secp256k1_dbl_clk0, eq_secp256k1_dbl_chunks, i) + expr_group_by_cbc(bn254_curve_add_clk0, eq_bn254_curve_add_chunks, i) - + expr_group_by_cbc(bn254_curve_dbl_clk0, eq_bn254_curve_dbl_chunks, i); + + expr_group_by_cbc(bn254_curve_dbl_clk0, eq_bn254_curve_dbl_chunks, i) + + expr_group_by_cbc(secp256r1_add_clk0, eq_secp256r1_add_chunks, i) + + expr_group_by_cbc(secp256r1_dbl_clk0, eq_secp256r1_dbl_chunks, i); eq[1][i] = expr_group_by_cbc(secp256k1_clk0, eq_secp256k1_x3_chunks, i) + expr_group_by_cbc(bn254_curve_clk0, eq_bn254_curve_x3_chunks, i) + expr_group_by_cbc(bn254_complex_add_clk0, eq_bn254_complex_add_x3_chunks, i) + expr_group_by_cbc(bn254_complex_sub_clk0, eq_bn254_complex_sub_x3_chunks, i) - + expr_group_by_cbc(bn254_complex_mul_clk0, eq_bn254_complex_mul_x3_chunks, i); + + expr_group_by_cbc(bn254_complex_mul_clk0, eq_bn254_complex_mul_x3_chunks, i) + + expr_group_by_cbc(secp256r1_clk0, eq_secp256r1_x3_chunks, i); eq[2][i] = expr_group_by_cbc(secp256k1_clk0, eq_secp256k1_y3_chunks, i) + expr_group_by_cbc(bn254_curve_clk0, eq_bn254_curve_y3_chunks, i) + expr_group_by_cbc(bn254_complex_add_clk0, eq_bn254_complex_add_y3_chunks, i) + expr_group_by_cbc(bn254_complex_sub_clk0, eq_bn254_complex_sub_y3_chunks, i) - + expr_group_by_cbc(bn254_complex_mul_clk0, eq_bn254_complex_mul_y3_chunks, i); + + expr_group_by_cbc(bn254_complex_mul_clk0, eq_bn254_complex_mul_y3_chunks, i) + + expr_group_by_cbc(secp256r1_clk0, eq_secp256r1_y3_chunks, i); } for (int i = 0; i < length(chunk_cols); ++i) { @@ -357,7 +380,8 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION + clk_cte_selector(bn254_curve_clk0, BN254_PRIME) + clk_cte_selector(bn254_complex_add_clk0, BN254_PRIME) + clk_cte_selector(bn254_complex_sub_clk0, BN254_PRIME) - + clk_cte_selector(bn254_complex_mul_clk0, BN254_PRIME); + + clk_cte_selector(bn254_complex_mul_clk0, BN254_PRIME) + + clk_cte_selector(secp256r1_clk0, SECP256R1_PRIME); const expr delta_x3 = x3 - y2 * sel_arith256_mod - lt_cte; const expr delta_y3 = y3 - lt_cte; @@ -412,6 +436,8 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION // bn254_complex_add x1,y1,x2,y2 x3,y3 x1,y1,x2,y2 // bn254_complex_sub x1,y1,x2,y2 x3,y3 x1,y1,x2,y2 // bn254_complex_mul x1,y1,x2,y2 x3,y3 x1,y1,x2,y2 + // secp256r1_add x1,y1,x2,y2 x3,y3 x1,y1,x2,y2 + // secp256r1_dbl x1,y2 x3,y3 x1,y1 const int ADDR_OP = MAIN_STEP + 1; const int ADDR_X1 = ADDR_OP + 1; @@ -448,10 +474,11 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION // 15 --- ---------- ------- ---------- --------- const expr use_x2 = sel_arith256 + sel_arith256_mod + sel_secp256k1_add + sel_bn254_curve_add + - sel_bn254_complex; - const expr use_y2 = sel_arith256_mod + sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex; + sel_bn254_complex + sel_secp256r1_add; + const expr use_y2 = sel_arith256_mod + sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex + + sel_secp256r1_add; const expr use_y3 = sel_arith256 + sel_secp256k1_add + sel_secp256k1_dbl + sel_bn254_curve_add + - sel_bn254_curve_dbl + sel_bn254_complex; + sel_bn254_curve_dbl + sel_bn254_complex + sel_secp256r1_add + sel_secp256r1_dbl; // [any_arith256] ADDR_X1 === ADDR_IND_0 // @@ -474,17 +501,24 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION // [bn254_complex] ADDR_X1 === ADDR_IND_0 // ADDR_Y1 === ADDR_IND_0 + 32 // ADDR_X3 === ADDR_X1, ADDR_Y3 === ADDR_Y1 + // + // [secp256r1_add] ADDR_X1 === ADDR_IND_0 + // ADDR_Y1 === ADDR_IND_0 + 32 + // ADDR_X3 === ADDR_X1, ADDR_Y3 === ADDR_Y1 + // + // [secp256r1_dbl] ADDR_X1 === ADDR_OP, + // ADDR_Y1 === ADDR_OP + 32 + // ADDR_X3 === ADDR_X1, ADDR_Y3 === ADDR_Y1 - const expr use_ind_0 = sel_any_arith256 + sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex; - - (sel_any_arith256 + sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex) * clock_eq(step_addr, ADDR_X1, ADDR_IND_0) === 0; - (sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex) * clock_eq(step_addr, ADDR_Y1, ADDR_IND_0, 32) === 0; + const expr use_ind_0 = sel_any_arith256 + sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex + sel_secp256r1_add; - (sel_secp256k1_dbl + sel_bn254_curve_dbl) * clock_eq(step_addr, ADDR_X1, ADDR_OP) === 0; - (sel_secp256k1_dbl + sel_bn254_curve_dbl) * clock_eq(step_addr, ADDR_Y1, ADDR_OP, 32) === 0; + (sel_any_arith256 + sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex + sel_secp256r1_add) * clock_eq(step_addr, ADDR_X1, ADDR_IND_0) === 0; + (sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex + sel_secp256r1_add) * clock_eq(step_addr, ADDR_Y1, ADDR_IND_0, 32) === 0; - (sel_secp256k1 + sel_bn254_curve + sel_bn254_complex) * clock_eq(step_addr, ADDR_X1, ADDR_X3) === 0; - (sel_secp256k1 + sel_bn254_curve + sel_bn254_complex) * clock_eq(step_addr, ADDR_Y1, ADDR_Y3) === 0; + (sel_secp256k1_dbl + sel_bn254_curve_dbl + sel_secp256r1_dbl) * clock_eq(step_addr, ADDR_X1, ADDR_OP) === 0; + (sel_secp256k1_dbl + sel_bn254_curve_dbl + sel_secp256r1_dbl) * clock_eq(step_addr, ADDR_Y1, ADDR_OP, 32) === 0; + (sel_secp256k1 + sel_bn254_curve + sel_bn254_complex + sel_secp256r1) * clock_eq(step_addr, ADDR_X1, ADDR_X3) === 0; + (sel_secp256k1 + sel_bn254_curve + sel_bn254_complex + sel_secp256r1) * clock_eq(step_addr, ADDR_Y1, ADDR_Y3) === 0; // [any_arith256] ADDR_Y1 === ADDR_IND_1 // @@ -496,13 +530,16 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION // // [bn254_complex] ADDR_X2 === ADDR_IND_1 // ADDR_Y2 === ADDR_IND_1 + 32 + // + // [secp256r1_add] ADDR_X2 === ADDR_IND_1 + // ADDR_Y2 === ADDR_IND_1 + 32 - const expr use_ind_1 = sel_any_arith256 + sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex; + const expr use_ind_1 = sel_any_arith256 + sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex + sel_secp256r1_add; sel_any_arith256 * clock_eq(step_addr, ADDR_Y1, ADDR_IND_1) === 0; - (sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex) * clock_eq(step_addr, ADDR_X2, ADDR_IND_1) === 0; - (sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex) * clock_eq(step_addr, ADDR_Y2, ADDR_IND_1, 32) === 0; + (sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex + sel_secp256r1_add) * clock_eq(step_addr, ADDR_X2, ADDR_IND_1) === 0; + (sel_secp256k1_add + sel_bn254_curve_add + sel_bn254_complex + sel_secp256r1_add) * clock_eq(step_addr, ADDR_Y2, ADDR_IND_1, 32) === 0; // [any_arith256] ADDR_X2 === ADDR_IND_2 @@ -543,7 +580,6 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION const expr main_step = clock_map(step_addr, MAIN_STEP, start: 0, end: 14); - const expr mem_is_write = CLK[8] + CLK[9] + CLK[10] + CLK[11]; const expr mem_value[2][2]; @@ -613,7 +649,9 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION sel_bn254_curve_dbl * OP_EC_DBL_BN254 + sel_bn254_complex_add * OP_COMPLEX_ADD_BN254 + sel_bn254_complex_sub * OP_COMPLEX_SUB_BN254 + - sel_bn254_complex_mul * OP_COMPLEX_MUL_BN254; + sel_bn254_complex_mul * OP_COMPLEX_MUL_BN254 + + sel_secp256r1_add * OP_EC_ADD_SECP256R1 + + sel_secp256r1_dbl * OP_EC_DBL_SECP256R1; lookup_proves(operation_bus_id, [bus_op, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(MAIN_STEP)], mul: in_use_clk0); diff --git a/precompiles/arith_eq/pil/equations/secp256r1_add.pil b/precompiles/arith_eq/pil/equations/secp256r1_add.pil new file mode 100644 index 000000000..6dc8c4b64 --- /dev/null +++ b/precompiles/arith_eq/pil/equations/secp256r1_add.pil @@ -0,0 +1,777 @@ +// code generated +// +// equation: s*x2-s*x1-y2+y1-p*q0+p*offset +// +// p: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF +// offset: 0x20000000000000000000000000000000000000000000000000000000000000000 +// (p*offset): 0x1FFFFFFFE00000002000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFE0000000000000000000000000000000000000000000000000000000000000000 +// +// chunks:16 +// chunk_bits:16 +// terms_by_clock: 2 + + +const expr eq_secp256r1_add_chunks[32]; + +// clock #0 + +eq_secp256r1_add_chunks[ 0] = s * x2 // s[0] * x2[0] + - s * x1 // - s[0] * x1[0] + - y2 // - y2[0] + + y1 // + y1[0] + - 0xFFFF * q0; // - p[0] * q0[0] + +eq_secp256r1_add_chunks[ 1] = s' * x2 // s[1] * x2[0] + + s * x2' // + s[0] * x2[1] + - s' * x1 // - s[1] * x1[0] + - s * x1' // - s[0] * x1[1] + - y2' // - y2[1] + + y1' // + y1[1] + - 0xFFFF * q0 // - p[1] * q0[0] + - 0xFFFF * q0'; // - p[0] * q0[1] + +// clock #1 + +eq_secp256r1_add_chunks[ 2] = s' * 'x2 // s[2] * x2[0] + + s * x2 // + s[1] * x2[1] + + 's * x2' // + s[0] * x2[2] + - s' * 'x1 // - s[2] * x1[0] + - s * x1 // - s[1] * x1[1] + - 's * x1' // - s[0] * x1[2] + - y2' // - y2[2] + + y1' // + y1[2] + - 0xFFFF * 'q0 // - p[2] * q0[0] + - 0xFFFF * q0 // - p[1] * q0[1] + - 0xFFFF * q0'; // - p[0] * q0[2] + +eq_secp256r1_add_chunks[ 3] = s'2 * 'x2 // s[3] * x2[0] + + s' * x2 // + s[2] * x2[1] + + s * x2' // + s[1] * x2[2] + + 's * x2'2 // + s[0] * x2[3] + - s'2 * 'x1 // - s[3] * x1[0] + - s' * x1 // - s[2] * x1[1] + - s * x1' // - s[1] * x1[2] + - 's * x1'2 // - s[0] * x1[3] + - y2'2 // - y2[3] + + y1'2 // + y1[3] + - 0xFFFF * 'q0 // - p[3] * q0[0] + - 0xFFFF * q0 // - p[2] * q0[1] + - 0xFFFF * q0' // - p[1] * q0[2] + - 0xFFFF * q0'2; // - p[0] * q0[3] + +// clock #2 + +eq_secp256r1_add_chunks[ 4] = s'2 * 2'x2 // s[4] * x2[0] + + s' * 'x2 // + s[3] * x2[1] + + s * x2 // + s[2] * x2[2] + + 's * x2' // + s[1] * x2[3] + + 2's * x2'2 // + s[0] * x2[4] + - s'2 * 2'x1 // - s[4] * x1[0] + - s' * 'x1 // - s[3] * x1[1] + - s * x1 // - s[2] * x1[2] + - 's * x1' // - s[1] * x1[3] + - 2's * x1'2 // - s[0] * x1[4] + - y2'2 // - y2[4] + + y1'2 // + y1[4] + - 0xFFFF * 2'q0 // - p[4] * q0[0] + - 0xFFFF * 'q0 // - p[3] * q0[1] + - 0xFFFF * q0 // - p[2] * q0[2] + - 0xFFFF * q0' // - p[1] * q0[3] + - 0xFFFF * q0'2; // - p[0] * q0[4] + +eq_secp256r1_add_chunks[ 5] = s'3 * 2'x2 // s[5] * x2[0] + + s'2 * 'x2 // + s[4] * x2[1] + + s' * x2 // + s[3] * x2[2] + + s * x2' // + s[2] * x2[3] + + 's * x2'2 // + s[1] * x2[4] + + 2's * x2'3 // + s[0] * x2[5] + - s'3 * 2'x1 // - s[5] * x1[0] + - s'2 * 'x1 // - s[4] * x1[1] + - s' * x1 // - s[3] * x1[2] + - s * x1' // - s[2] * x1[3] + - 's * x1'2 // - s[1] * x1[4] + - 2's * x1'3 // - s[0] * x1[5] + - y2'3 // - y2[5] + + y1'3 // + y1[5] + - 0xFFFF * 2'q0 // - p[5] * q0[0] + - 0xFFFF * 'q0 // - p[4] * q0[1] + - 0xFFFF * q0 // - p[3] * q0[2] + - 0xFFFF * q0' // - p[2] * q0[3] + - 0xFFFF * q0'2 // - p[1] * q0[4] + - 0xFFFF * q0'3; // - p[0] * q0[5] + +// clock #3 + +eq_secp256r1_add_chunks[ 6] = s'3 * 3'x2 // s[6] * x2[0] + + s'2 * 2'x2 // + s[5] * x2[1] + + s' * 'x2 // + s[4] * x2[2] + + s * x2 // + s[3] * x2[3] + + 's * x2' // + s[2] * x2[4] + + 2's * x2'2 // + s[1] * x2[5] + + 3's * x2'3 // + s[0] * x2[6] + - s'3 * 3'x1 // - s[6] * x1[0] + - s'2 * 2'x1 // - s[5] * x1[1] + - s' * 'x1 // - s[4] * x1[2] + - s * x1 // - s[3] * x1[3] + - 's * x1' // - s[2] * x1[4] + - 2's * x1'2 // - s[1] * x1[5] + - 3's * x1'3 // - s[0] * x1[6] + - y2'3 // - y2[6] + + y1'3 // + y1[6] + - 0xFFFF * 2'q0 // - p[5] * q0[1] + - 0xFFFF * 'q0 // - p[4] * q0[2] + - 0xFFFF * q0 // - p[3] * q0[3] + - 0xFFFF * q0' // - p[2] * q0[4] + - 0xFFFF * q0'2 // - p[1] * q0[5] + - 0xFFFF * q0'3; // - p[0] * q0[6] + +eq_secp256r1_add_chunks[ 7] = s'4 * 3'x2 // s[7] * x2[0] + + s'3 * 2'x2 // + s[6] * x2[1] + + s'2 * 'x2 // + s[5] * x2[2] + + s' * x2 // + s[4] * x2[3] + + s * x2' // + s[3] * x2[4] + + 's * x2'2 // + s[2] * x2[5] + + 2's * x2'3 // + s[1] * x2[6] + + 3's * x2'4 // + s[0] * x2[7] + - s'4 * 3'x1 // - s[7] * x1[0] + - s'3 * 2'x1 // - s[6] * x1[1] + - s'2 * 'x1 // - s[5] * x1[2] + - s' * x1 // - s[4] * x1[3] + - s * x1' // - s[3] * x1[4] + - 's * x1'2 // - s[2] * x1[5] + - 2's * x1'3 // - s[1] * x1[6] + - 3's * x1'4 // - s[0] * x1[7] + - y2'4 // - y2[7] + + y1'4 // + y1[7] + - 0xFFFF * 'q0 // - p[5] * q0[2] + - 0xFFFF * q0 // - p[4] * q0[3] + - 0xFFFF * q0' // - p[3] * q0[4] + - 0xFFFF * q0'2 // - p[2] * q0[5] + - 0xFFFF * q0'3 // - p[1] * q0[6] + - 0xFFFF * q0'4; // - p[0] * q0[7] + +// clock #4 + +eq_secp256r1_add_chunks[ 8] = s'4 * 4'x2 // s[8] * x2[0] + + s'3 * 3'x2 // + s[7] * x2[1] + + s'2 * 2'x2 // + s[6] * x2[2] + + s' * 'x2 // + s[5] * x2[3] + + s * x2 // + s[4] * x2[4] + + 's * x2' // + s[3] * x2[5] + + 2's * x2'2 // + s[2] * x2[6] + + 3's * x2'3 // + s[1] * x2[7] + + 4's * x2'4 // + s[0] * x2[8] + - s'4 * 4'x1 // - s[8] * x1[0] + - s'3 * 3'x1 // - s[7] * x1[1] + - s'2 * 2'x1 // - s[6] * x1[2] + - s' * 'x1 // - s[5] * x1[3] + - s * x1 // - s[4] * x1[4] + - 's * x1' // - s[3] * x1[5] + - 2's * x1'2 // - s[2] * x1[6] + - 3's * x1'3 // - s[1] * x1[7] + - 4's * x1'4 // - s[0] * x1[8] + - y2'4 // - y2[8] + + y1'4 // + y1[8] + - 0xFFFF * 'q0 // - p[5] * q0[3] + - 0xFFFF * q0 // - p[4] * q0[4] + - 0xFFFF * q0' // - p[3] * q0[5] + - 0xFFFF * q0'2 // - p[2] * q0[6] + - 0xFFFF * q0'3 // - p[1] * q0[7] + - 0xFFFF * q0'4; // - p[0] * q0[8] + +eq_secp256r1_add_chunks[ 9] = s'5 * 4'x2 // s[9] * x2[0] + + s'4 * 3'x2 // + s[8] * x2[1] + + s'3 * 2'x2 // + s[7] * x2[2] + + s'2 * 'x2 // + s[6] * x2[3] + + s' * x2 // + s[5] * x2[4] + + s * x2' // + s[4] * x2[5] + + 's * x2'2 // + s[3] * x2[6] + + 2's * x2'3 // + s[2] * x2[7] + + 3's * x2'4 // + s[1] * x2[8] + + 4's * x2'5 // + s[0] * x2[9] + - s'5 * 4'x1 // - s[9] * x1[0] + - s'4 * 3'x1 // - s[8] * x1[1] + - s'3 * 2'x1 // - s[7] * x1[2] + - s'2 * 'x1 // - s[6] * x1[3] + - s' * x1 // - s[5] * x1[4] + - s * x1' // - s[4] * x1[5] + - 's * x1'2 // - s[3] * x1[6] + - 2's * x1'3 // - s[2] * x1[7] + - 3's * x1'4 // - s[1] * x1[8] + - 4's * x1'5 // - s[0] * x1[9] + - y2'5 // - y2[9] + + y1'5 // + y1[9] + - 0xFFFF * q0 // - p[5] * q0[4] + - 0xFFFF * q0' // - p[4] * q0[5] + - 0xFFFF * q0'2 // - p[3] * q0[6] + - 0xFFFF * q0'3 // - p[2] * q0[7] + - 0xFFFF * q0'4 // - p[1] * q0[8] + - 0xFFFF * q0'5; // - p[0] * q0[9] + +// clock #5 + +eq_secp256r1_add_chunks[10] = s'5 * 5'x2 // s[10] * x2[0] + + s'4 * 4'x2 // + s[9] * x2[1] + + s'3 * 3'x2 // + s[8] * x2[2] + + s'2 * 2'x2 // + s[7] * x2[3] + + s' * 'x2 // + s[6] * x2[4] + + s * x2 // + s[5] * x2[5] + + 's * x2' // + s[4] * x2[6] + + 2's * x2'2 // + s[3] * x2[7] + + 3's * x2'3 // + s[2] * x2[8] + + 4's * x2'4 // + s[1] * x2[9] + + 5's * x2'5 // + s[0] * x2[10] + - s'5 * 5'x1 // - s[10] * x1[0] + - s'4 * 4'x1 // - s[9] * x1[1] + - s'3 * 3'x1 // - s[8] * x1[2] + - s'2 * 2'x1 // - s[7] * x1[3] + - s' * 'x1 // - s[6] * x1[4] + - s * x1 // - s[5] * x1[5] + - 's * x1' // - s[4] * x1[6] + - 2's * x1'2 // - s[3] * x1[7] + - 3's * x1'3 // - s[2] * x1[8] + - 4's * x1'4 // - s[1] * x1[9] + - 5's * x1'5 // - s[0] * x1[10] + - y2'5 // - y2[10] + + y1'5 // + y1[10] + - 0xFFFF * q0 // - p[5] * q0[5] + - 0xFFFF * q0' // - p[4] * q0[6] + - 0xFFFF * q0'2 // - p[3] * q0[7] + - 0xFFFF * q0'3 // - p[2] * q0[8] + - 0xFFFF * q0'4 // - p[1] * q0[9] + - 0xFFFF * q0'5; // - p[0] * q0[10] + +eq_secp256r1_add_chunks[11] = s'6 * 5'x2 // s[11] * x2[0] + + s'5 * 4'x2 // + s[10] * x2[1] + + s'4 * 3'x2 // + s[9] * x2[2] + + s'3 * 2'x2 // + s[8] * x2[3] + + s'2 * 'x2 // + s[7] * x2[4] + + s' * x2 // + s[6] * x2[5] + + s * x2' // + s[5] * x2[6] + + 's * x2'2 // + s[4] * x2[7] + + 2's * x2'3 // + s[3] * x2[8] + + 3's * x2'4 // + s[2] * x2[9] + + 4's * x2'5 // + s[1] * x2[10] + + 5's * x2'6 // + s[0] * x2[11] + - s'6 * 5'x1 // - s[11] * x1[0] + - s'5 * 4'x1 // - s[10] * x1[1] + - s'4 * 3'x1 // - s[9] * x1[2] + - s'3 * 2'x1 // - s[8] * x1[3] + - s'2 * 'x1 // - s[7] * x1[4] + - s' * x1 // - s[6] * x1[5] + - s * x1' // - s[5] * x1[6] + - 's * x1'2 // - s[4] * x1[7] + - 2's * x1'3 // - s[3] * x1[8] + - 3's * x1'4 // - s[2] * x1[9] + - 4's * x1'5 // - s[1] * x1[10] + - 5's * x1'6 // - s[0] * x1[11] + - y2'6 // - y2[11] + + y1'6 // + y1[11] + - 0xFFFF * q0' // - p[5] * q0[6] + - 0xFFFF * q0'2 // - p[4] * q0[7] + - 0xFFFF * q0'3 // - p[3] * q0[8] + - 0xFFFF * q0'4 // - p[2] * q0[9] + - 0xFFFF * q0'5 // - p[1] * q0[10] + - 0xFFFF * q0'6; // - p[0] * q0[11] + +// clock #6 + +eq_secp256r1_add_chunks[12] = s'6 * 6'x2 // s[12] * x2[0] + + s'5 * 5'x2 // + s[11] * x2[1] + + s'4 * 4'x2 // + s[10] * x2[2] + + s'3 * 3'x2 // + s[9] * x2[3] + + s'2 * 2'x2 // + s[8] * x2[4] + + s' * 'x2 // + s[7] * x2[5] + + s * x2 // + s[6] * x2[6] + + 's * x2' // + s[5] * x2[7] + + 2's * x2'2 // + s[4] * x2[8] + + 3's * x2'3 // + s[3] * x2[9] + + 4's * x2'4 // + s[2] * x2[10] + + 5's * x2'5 // + s[1] * x2[11] + + 6's * x2'6 // + s[0] * x2[12] + - s'6 * 6'x1 // - s[12] * x1[0] + - s'5 * 5'x1 // - s[11] * x1[1] + - s'4 * 4'x1 // - s[10] * x1[2] + - s'3 * 3'x1 // - s[9] * x1[3] + - s'2 * 2'x1 // - s[8] * x1[4] + - s' * 'x1 // - s[7] * x1[5] + - s * x1 // - s[6] * x1[6] + - 's * x1' // - s[5] * x1[7] + - 2's * x1'2 // - s[4] * x1[8] + - 3's * x1'3 // - s[3] * x1[9] + - 4's * x1'4 // - s[2] * x1[10] + - 5's * x1'5 // - s[1] * x1[11] + - 6's * x1'6 // - s[0] * x1[12] + - y2'6 // - y2[12] + + y1'6 // + y1[12] + - 6'q0 // - q0[0] + - 0xFFFF * q0' // - p[5] * q0[7] + - 0xFFFF * q0'2 // - p[4] * q0[8] + - 0xFFFF * q0'3 // - p[3] * q0[9] + - 0xFFFF * q0'4 // - p[2] * q0[10] + - 0xFFFF * q0'5 // - p[1] * q0[11] + - 0xFFFF * q0'6; // - p[0] * q0[12] + +eq_secp256r1_add_chunks[13] = s'7 * 6'x2 // s[13] * x2[0] + + s'6 * 5'x2 // + s[12] * x2[1] + + s'5 * 4'x2 // + s[11] * x2[2] + + s'4 * 3'x2 // + s[10] * x2[3] + + s'3 * 2'x2 // + s[9] * x2[4] + + s'2 * 'x2 // + s[8] * x2[5] + + s' * x2 // + s[7] * x2[6] + + s * x2' // + s[6] * x2[7] + + 's * x2'2 // + s[5] * x2[8] + + 2's * x2'3 // + s[4] * x2[9] + + 3's * x2'4 // + s[3] * x2[10] + + 4's * x2'5 // + s[2] * x2[11] + + 5's * x2'6 // + s[1] * x2[12] + + 6's * x2'7 // + s[0] * x2[13] + - s'7 * 6'x1 // - s[13] * x1[0] + - s'6 * 5'x1 // - s[12] * x1[1] + - s'5 * 4'x1 // - s[11] * x1[2] + - s'4 * 3'x1 // - s[10] * x1[3] + - s'3 * 2'x1 // - s[9] * x1[4] + - s'2 * 'x1 // - s[8] * x1[5] + - s' * x1 // - s[7] * x1[6] + - s * x1' // - s[6] * x1[7] + - 's * x1'2 // - s[5] * x1[8] + - 2's * x1'3 // - s[4] * x1[9] + - 3's * x1'4 // - s[3] * x1[10] + - 4's * x1'5 // - s[2] * x1[11] + - 5's * x1'6 // - s[1] * x1[12] + - 6's * x1'7 // - s[0] * x1[13] + - y2'7 // - y2[13] + + y1'7 // + y1[13] + - 5'q0 // - q0[1] + - 0xFFFF * q0'2 // - p[5] * q0[8] + - 0xFFFF * q0'3 // - p[4] * q0[9] + - 0xFFFF * q0'4 // - p[3] * q0[10] + - 0xFFFF * q0'5 // - p[2] * q0[11] + - 0xFFFF * q0'6 // - p[1] * q0[12] + - 0xFFFF * q0'7; // - p[0] * q0[13] + +// clock #7 + +eq_secp256r1_add_chunks[14] = s'7 * 7'x2 // s[14] * x2[0] + + s'6 * 6'x2 // + s[13] * x2[1] + + s'5 * 5'x2 // + s[12] * x2[2] + + s'4 * 4'x2 // + s[11] * x2[3] + + s'3 * 3'x2 // + s[10] * x2[4] + + s'2 * 2'x2 // + s[9] * x2[5] + + s' * 'x2 // + s[8] * x2[6] + + s * x2 // + s[7] * x2[7] + + 's * x2' // + s[6] * x2[8] + + 2's * x2'2 // + s[5] * x2[9] + + 3's * x2'3 // + s[4] * x2[10] + + 4's * x2'4 // + s[3] * x2[11] + + 5's * x2'5 // + s[2] * x2[12] + + 6's * x2'6 // + s[1] * x2[13] + + 7's * x2'7 // + s[0] * x2[14] + - s'7 * 7'x1 // - s[14] * x1[0] + - s'6 * 6'x1 // - s[13] * x1[1] + - s'5 * 5'x1 // - s[12] * x1[2] + - s'4 * 4'x1 // - s[11] * x1[3] + - s'3 * 3'x1 // - s[10] * x1[4] + - s'2 * 2'x1 // - s[9] * x1[5] + - s' * 'x1 // - s[8] * x1[6] + - s * x1 // - s[7] * x1[7] + - 's * x1' // - s[6] * x1[8] + - 2's * x1'2 // - s[5] * x1[9] + - 3's * x1'3 // - s[4] * x1[10] + - 4's * x1'4 // - s[3] * x1[11] + - 5's * x1'5 // - s[2] * x1[12] + - 6's * x1'6 // - s[1] * x1[13] + - 7's * x1'7 // - s[0] * x1[14] + - y2'7 // - y2[14] + + y1'7 // + y1[14] + - 0xFFFF * 7'q0 // - p[14] * q0[0] + - 5'q0 // - q0[2] + - 0xFFFF * q0'2 // - p[5] * q0[9] + - 0xFFFF * q0'3 // - p[4] * q0[10] + - 0xFFFF * q0'4 // - p[3] * q0[11] + - 0xFFFF * q0'5 // - p[2] * q0[12] + - 0xFFFF * q0'6 // - p[1] * q0[13] + - 0xFFFF * q0'7; // - p[0] * q0[14] + +eq_secp256r1_add_chunks[15] = s'8 * 7'x2 // s[15] * x2[0] + + s'7 * 6'x2 // + s[14] * x2[1] + + s'6 * 5'x2 // + s[13] * x2[2] + + s'5 * 4'x2 // + s[12] * x2[3] + + s'4 * 3'x2 // + s[11] * x2[4] + + s'3 * 2'x2 // + s[10] * x2[5] + + s'2 * 'x2 // + s[9] * x2[6] + + s' * x2 // + s[8] * x2[7] + + s * x2' // + s[7] * x2[8] + + 's * x2'2 // + s[6] * x2[9] + + 2's * x2'3 // + s[5] * x2[10] + + 3's * x2'4 // + s[4] * x2[11] + + 4's * x2'5 // + s[3] * x2[12] + + 5's * x2'6 // + s[2] * x2[13] + + 6's * x2'7 // + s[1] * x2[14] + + 7's * x2'8 // + s[0] * x2[15] + - s'8 * 7'x1 // - s[15] * x1[0] + - s'7 * 6'x1 // - s[14] * x1[1] + - s'6 * 5'x1 // - s[13] * x1[2] + - s'5 * 4'x1 // - s[12] * x1[3] + - s'4 * 3'x1 // - s[11] * x1[4] + - s'3 * 2'x1 // - s[10] * x1[5] + - s'2 * 'x1 // - s[9] * x1[6] + - s' * x1 // - s[8] * x1[7] + - s * x1' // - s[7] * x1[8] + - 's * x1'2 // - s[6] * x1[9] + - 2's * x1'3 // - s[5] * x1[10] + - 3's * x1'4 // - s[4] * x1[11] + - 4's * x1'5 // - s[3] * x1[12] + - 5's * x1'6 // - s[2] * x1[13] + - 6's * x1'7 // - s[1] * x1[14] + - 7's * x1'8 // - s[0] * x1[15] + - y2'8 // - y2[15] + + y1'8 // + y1[15] + - 0xFFFF * 7'q0 // - p[15] * q0[0] + - 0xFFFF * 6'q0 // - p[14] * q0[1] + - 4'q0 // - q0[3] + - 0xFFFF * q0'3 // - p[5] * q0[10] + - 0xFFFF * q0'4 // - p[4] * q0[11] + - 0xFFFF * q0'5 // - p[3] * q0[12] + - 0xFFFF * q0'6 // - p[2] * q0[13] + - 0xFFFF * q0'7 // - p[1] * q0[14] + - 0xFFFF * q0'8; // - p[0] * q0[15] + +// clock #8 + +eq_secp256r1_add_chunks[16] = s'7 * 7'x2 // s[15] * x2[1] + + s'6 * 6'x2 // + s[14] * x2[2] + + s'5 * 5'x2 // + s[13] * x2[3] + + s'4 * 4'x2 // + s[12] * x2[4] + + s'3 * 3'x2 // + s[11] * x2[5] + + s'2 * 2'x2 // + s[10] * x2[6] + + s' * 'x2 // + s[9] * x2[7] + + s * x2 // + s[8] * x2[8] + + 's * x2' // + s[7] * x2[9] + + 2's * x2'2 // + s[6] * x2[10] + + 3's * x2'3 // + s[5] * x2[11] + + 4's * x2'4 // + s[4] * x2[12] + + 5's * x2'5 // + s[3] * x2[13] + + 6's * x2'6 // + s[2] * x2[14] + + 7's * x2'7 // + s[1] * x2[15] + - s'7 * 7'x1 // - s[15] * x1[1] + - s'6 * 6'x1 // - s[14] * x1[2] + - s'5 * 5'x1 // - s[13] * x1[3] + - s'4 * 4'x1 // - s[12] * x1[4] + - s'3 * 3'x1 // - s[11] * x1[5] + - s'2 * 2'x1 // - s[10] * x1[6] + - s' * 'x1 // - s[9] * x1[7] + - s * x1 // - s[8] * x1[8] + - 's * x1' // - s[7] * x1[9] + - 2's * x1'2 // - s[6] * x1[10] + - 3's * x1'3 // - s[5] * x1[11] + - 4's * x1'4 // - s[4] * x1[12] + - 5's * x1'5 // - s[3] * x1[13] + - 6's * x1'6 // - s[2] * x1[14] + - 7's * x1'7 // - s[1] * x1[15] + - 0xFFFF * 7'q0 // - p[15] * q0[1] + - 0xFFFF * 6'q0 // - p[14] * q0[2] + - 4'q0 // - q0[4] + - 0xFFFF * q0'3 // - p[5] * q0[11] + - 0xFFFF * q0'4 // - p[4] * q0[12] + - 0xFFFF * q0'5 // - p[3] * q0[13] + - 0xFFFF * q0'6 // - p[2] * q0[14] + - 0xFFFF * q0'7 // - p[1] * q0[15] + + 0xFFFE; // + (p*offset)[16] + +eq_secp256r1_add_chunks[17] = s'7 * 6'x2 // s[15] * x2[2] + + s'6 * 5'x2 // + s[14] * x2[3] + + s'5 * 4'x2 // + s[13] * x2[4] + + s'4 * 3'x2 // + s[12] * x2[5] + + s'3 * 2'x2 // + s[11] * x2[6] + + s'2 * 'x2 // + s[10] * x2[7] + + s' * x2 // + s[9] * x2[8] + + s * x2' // + s[8] * x2[9] + + 's * x2'2 // + s[7] * x2[10] + + 2's * x2'3 // + s[6] * x2[11] + + 3's * x2'4 // + s[5] * x2[12] + + 4's * x2'5 // + s[4] * x2[13] + + 5's * x2'6 // + s[3] * x2[14] + + 6's * x2'7 // + s[2] * x2[15] + - s'7 * 6'x1 // - s[15] * x1[2] + - s'6 * 5'x1 // - s[14] * x1[3] + - s'5 * 4'x1 // - s[13] * x1[4] + - s'4 * 3'x1 // - s[12] * x1[5] + - s'3 * 2'x1 // - s[11] * x1[6] + - s'2 * 'x1 // - s[10] * x1[7] + - s' * x1 // - s[9] * x1[8] + - s * x1' // - s[8] * x1[9] + - 's * x1'2 // - s[7] * x1[10] + - 2's * x1'3 // - s[6] * x1[11] + - 3's * x1'4 // - s[5] * x1[12] + - 4's * x1'5 // - s[4] * x1[13] + - 5's * x1'6 // - s[3] * x1[14] + - 6's * x1'7 // - s[2] * x1[15] + - 0xFFFF * 6'q0 // - p[15] * q0[2] + - 0xFFFF * 5'q0 // - p[14] * q0[3] + - 3'q0 // - q0[5] + - 0xFFFF * q0'4 // - p[5] * q0[12] + - 0xFFFF * q0'5 // - p[4] * q0[13] + - 0xFFFF * q0'6 // - p[3] * q0[14] + - 0xFFFF * q0'7 // - p[2] * q0[15] + + 0xFFFF; // + (p*offset)[17] + +// clock #9 + +eq_secp256r1_add_chunks[18] = s'6 * 6'x2 // s[15] * x2[3] + + s'5 * 5'x2 // + s[14] * x2[4] + + s'4 * 4'x2 // + s[13] * x2[5] + + s'3 * 3'x2 // + s[12] * x2[6] + + s'2 * 2'x2 // + s[11] * x2[7] + + s' * 'x2 // + s[10] * x2[8] + + s * x2 // + s[9] * x2[9] + + 's * x2' // + s[8] * x2[10] + + 2's * x2'2 // + s[7] * x2[11] + + 3's * x2'3 // + s[6] * x2[12] + + 4's * x2'4 // + s[5] * x2[13] + + 5's * x2'5 // + s[4] * x2[14] + + 6's * x2'6 // + s[3] * x2[15] + - s'6 * 6'x1 // - s[15] * x1[3] + - s'5 * 5'x1 // - s[14] * x1[4] + - s'4 * 4'x1 // - s[13] * x1[5] + - s'3 * 3'x1 // - s[12] * x1[6] + - s'2 * 2'x1 // - s[11] * x1[7] + - s' * 'x1 // - s[10] * x1[8] + - s * x1 // - s[9] * x1[9] + - 's * x1' // - s[8] * x1[10] + - 2's * x1'2 // - s[7] * x1[11] + - 3's * x1'3 // - s[6] * x1[12] + - 4's * x1'4 // - s[5] * x1[13] + - 5's * x1'5 // - s[4] * x1[14] + - 6's * x1'6 // - s[3] * x1[15] + - 0xFFFF * 6'q0 // - p[15] * q0[3] + - 0xFFFF * 5'q0 // - p[14] * q0[4] + - 3'q0 // - q0[6] + - 0xFFFF * q0'4 // - p[5] * q0[13] + - 0xFFFF * q0'5 // - p[4] * q0[14] + - 0xFFFF * q0'6 // - p[3] * q0[15] + + 0xFFFF; // + (p*offset)[18] + +eq_secp256r1_add_chunks[19] = s'6 * 5'x2 // s[15] * x2[4] + + s'5 * 4'x2 // + s[14] * x2[5] + + s'4 * 3'x2 // + s[13] * x2[6] + + s'3 * 2'x2 // + s[12] * x2[7] + + s'2 * 'x2 // + s[11] * x2[8] + + s' * x2 // + s[10] * x2[9] + + s * x2' // + s[9] * x2[10] + + 's * x2'2 // + s[8] * x2[11] + + 2's * x2'3 // + s[7] * x2[12] + + 3's * x2'4 // + s[6] * x2[13] + + 4's * x2'5 // + s[5] * x2[14] + + 5's * x2'6 // + s[4] * x2[15] + - s'6 * 5'x1 // - s[15] * x1[4] + - s'5 * 4'x1 // - s[14] * x1[5] + - s'4 * 3'x1 // - s[13] * x1[6] + - s'3 * 2'x1 // - s[12] * x1[7] + - s'2 * 'x1 // - s[11] * x1[8] + - s' * x1 // - s[10] * x1[9] + - s * x1' // - s[9] * x1[10] + - 's * x1'2 // - s[8] * x1[11] + - 2's * x1'3 // - s[7] * x1[12] + - 3's * x1'4 // - s[6] * x1[13] + - 4's * x1'5 // - s[5] * x1[14] + - 5's * x1'6 // - s[4] * x1[15] + - 0xFFFF * 5'q0 // - p[15] * q0[4] + - 0xFFFF * 4'q0 // - p[14] * q0[5] + - 2'q0 // - q0[7] + - 0xFFFF * q0'5 // - p[5] * q0[14] + - 0xFFFF * q0'6 // - p[4] * q0[15] + + 0xFFFF; // + (p*offset)[19] + +// clock #10 + +eq_secp256r1_add_chunks[20] = s'5 * 5'x2 // s[15] * x2[5] + + s'4 * 4'x2 // + s[14] * x2[6] + + s'3 * 3'x2 // + s[13] * x2[7] + + s'2 * 2'x2 // + s[12] * x2[8] + + s' * 'x2 // + s[11] * x2[9] + + s * x2 // + s[10] * x2[10] + + 's * x2' // + s[9] * x2[11] + + 2's * x2'2 // + s[8] * x2[12] + + 3's * x2'3 // + s[7] * x2[13] + + 4's * x2'4 // + s[6] * x2[14] + + 5's * x2'5 // + s[5] * x2[15] + - s'5 * 5'x1 // - s[15] * x1[5] + - s'4 * 4'x1 // - s[14] * x1[6] + - s'3 * 3'x1 // - s[13] * x1[7] + - s'2 * 2'x1 // - s[12] * x1[8] + - s' * 'x1 // - s[11] * x1[9] + - s * x1 // - s[10] * x1[10] + - 's * x1' // - s[9] * x1[11] + - 2's * x1'2 // - s[8] * x1[12] + - 3's * x1'3 // - s[7] * x1[13] + - 4's * x1'4 // - s[6] * x1[14] + - 5's * x1'5 // - s[5] * x1[15] + - 0xFFFF * 5'q0 // - p[15] * q0[5] + - 0xFFFF * 4'q0 // - p[14] * q0[6] + - 2'q0 // - q0[8] + - 0xFFFF * q0'5 // - p[5] * q0[15] + + 0xFFFF; // + (p*offset)[20] + +eq_secp256r1_add_chunks[21] = s'5 * 4'x2 // s[15] * x2[6] + + s'4 * 3'x2 // + s[14] * x2[7] + + s'3 * 2'x2 // + s[13] * x2[8] + + s'2 * 'x2 // + s[12] * x2[9] + + s' * x2 // + s[11] * x2[10] + + s * x2' // + s[10] * x2[11] + + 's * x2'2 // + s[9] * x2[12] + + 2's * x2'3 // + s[8] * x2[13] + + 3's * x2'4 // + s[7] * x2[14] + + 4's * x2'5 // + s[6] * x2[15] + - s'5 * 4'x1 // - s[15] * x1[6] + - s'4 * 3'x1 // - s[14] * x1[7] + - s'3 * 2'x1 // - s[13] * x1[8] + - s'2 * 'x1 // - s[12] * x1[9] + - s' * x1 // - s[11] * x1[10] + - s * x1' // - s[10] * x1[11] + - 's * x1'2 // - s[9] * x1[12] + - 2's * x1'3 // - s[8] * x1[13] + - 3's * x1'4 // - s[7] * x1[14] + - 4's * x1'5 // - s[6] * x1[15] + - 0xFFFF * 4'q0 // - p[15] * q0[6] + - 0xFFFF * 3'q0 // - p[14] * q0[7] + - 'q0 // - q0[9] + + 0xFFFF; // + (p*offset)[21] + +// clock #11 + +eq_secp256r1_add_chunks[22] = s'4 * 4'x2 // s[15] * x2[7] + + s'3 * 3'x2 // + s[14] * x2[8] + + s'2 * 2'x2 // + s[13] * x2[9] + + s' * 'x2 // + s[12] * x2[10] + + s * x2 // + s[11] * x2[11] + + 's * x2' // + s[10] * x2[12] + + 2's * x2'2 // + s[9] * x2[13] + + 3's * x2'3 // + s[8] * x2[14] + + 4's * x2'4 // + s[7] * x2[15] + - s'4 * 4'x1 // - s[15] * x1[7] + - s'3 * 3'x1 // - s[14] * x1[8] + - s'2 * 2'x1 // - s[13] * x1[9] + - s' * 'x1 // - s[12] * x1[10] + - s * x1 // - s[11] * x1[11] + - 's * x1' // - s[10] * x1[12] + - 2's * x1'2 // - s[9] * x1[13] + - 3's * x1'3 // - s[8] * x1[14] + - 4's * x1'4 // - s[7] * x1[15] + - 0xFFFF * 4'q0 // - p[15] * q0[7] + - 0xFFFF * 3'q0 // - p[14] * q0[8] + - 'q0 // - q0[10] + + 0x1; // + (p*offset)[22] + +eq_secp256r1_add_chunks[23] = s'4 * 3'x2 // s[15] * x2[8] + + s'3 * 2'x2 // + s[14] * x2[9] + + s'2 * 'x2 // + s[13] * x2[10] + + s' * x2 // + s[12] * x2[11] + + s * x2' // + s[11] * x2[12] + + 's * x2'2 // + s[10] * x2[13] + + 2's * x2'3 // + s[9] * x2[14] + + 3's * x2'4 // + s[8] * x2[15] + - s'4 * 3'x1 // - s[15] * x1[8] + - s'3 * 2'x1 // - s[14] * x1[9] + - s'2 * 'x1 // - s[13] * x1[10] + - s' * x1 // - s[12] * x1[11] + - s * x1' // - s[11] * x1[12] + - 's * x1'2 // - s[10] * x1[13] + - 2's * x1'3 // - s[9] * x1[14] + - 3's * x1'4 // - s[8] * x1[15] + - 0xFFFF * 3'q0 // - p[15] * q0[8] + - 0xFFFF * 2'q0 // - p[14] * q0[9] + - q0; // - q0[11] + +// clock #12 + +eq_secp256r1_add_chunks[24] = s'3 * 3'x2 // s[15] * x2[9] + + s'2 * 2'x2 // + s[14] * x2[10] + + s' * 'x2 // + s[13] * x2[11] + + s * x2 // + s[12] * x2[12] + + 's * x2' // + s[11] * x2[13] + + 2's * x2'2 // + s[10] * x2[14] + + 3's * x2'3 // + s[9] * x2[15] + - s'3 * 3'x1 // - s[15] * x1[9] + - s'2 * 2'x1 // - s[14] * x1[10] + - s' * 'x1 // - s[13] * x1[11] + - s * x1 // - s[12] * x1[12] + - 's * x1' // - s[11] * x1[13] + - 2's * x1'2 // - s[10] * x1[14] + - 3's * x1'3 // - s[9] * x1[15] + - 0xFFFF * 3'q0 // - p[15] * q0[9] + - 0xFFFF * 2'q0 // - p[14] * q0[10] + - q0; // - q0[12] + +eq_secp256r1_add_chunks[25] = s'3 * 2'x2 // s[15] * x2[10] + + s'2 * 'x2 // + s[14] * x2[11] + + s' * x2 // + s[13] * x2[12] + + s * x2' // + s[12] * x2[13] + + 's * x2'2 // + s[11] * x2[14] + + 2's * x2'3 // + s[10] * x2[15] + - s'3 * 2'x1 // - s[15] * x1[10] + - s'2 * 'x1 // - s[14] * x1[11] + - s' * x1 // - s[13] * x1[12] + - s * x1' // - s[12] * x1[13] + - 's * x1'2 // - s[11] * x1[14] + - 2's * x1'3 // - s[10] * x1[15] + - 0xFFFF * 2'q0 // - p[15] * q0[10] + - 0xFFFF * 'q0 // - p[14] * q0[11] + - q0'; // - q0[13] + +// clock #13 + +eq_secp256r1_add_chunks[26] = s'2 * 2'x2 // s[15] * x2[11] + + s' * 'x2 // + s[14] * x2[12] + + s * x2 // + s[13] * x2[13] + + 's * x2' // + s[12] * x2[14] + + 2's * x2'2 // + s[11] * x2[15] + - s'2 * 2'x1 // - s[15] * x1[11] + - s' * 'x1 // - s[14] * x1[12] + - s * x1 // - s[13] * x1[13] + - 's * x1' // - s[12] * x1[14] + - 2's * x1'2 // - s[11] * x1[15] + - 0xFFFF * 2'q0 // - p[15] * q0[11] + - 0xFFFF * 'q0 // - p[14] * q0[12] + - q0'; // - q0[14] + +eq_secp256r1_add_chunks[27] = s'2 * 'x2 // s[15] * x2[12] + + s' * x2 // + s[14] * x2[13] + + s * x2' // + s[13] * x2[14] + + 's * x2'2 // + s[12] * x2[15] + - s'2 * 'x1 // - s[15] * x1[12] + - s' * x1 // - s[14] * x1[13] + - s * x1' // - s[13] * x1[14] + - 's * x1'2 // - s[12] * x1[15] + - 0xFFFF * 'q0 // - p[15] * q0[12] + - 0xFFFF * q0 // - p[14] * q0[13] + - q0'2; // - q0[15] + +// clock #14 + +eq_secp256r1_add_chunks[28] = s' * 'x2 // s[15] * x2[13] + + s * x2 // + s[14] * x2[14] + + 's * x2' // + s[13] * x2[15] + - s' * 'x1 // - s[15] * x1[13] + - s * x1 // - s[14] * x1[14] + - 's * x1' // - s[13] * x1[15] + - 0xFFFF * 'q0 // - p[15] * q0[13] + - 0xFFFF * q0 // - p[14] * q0[14] + + 0x2; // + (p*offset)[28] + +eq_secp256r1_add_chunks[29] = s' * x2 // s[15] * x2[14] + + s * x2' // + s[14] * x2[15] + - s' * x1 // - s[15] * x1[14] + - s * x1' // - s[14] * x1[15] + - 0xFFFF * q0 // - p[15] * q0[14] + - 0xFFFF * q0'; // - p[14] * q0[15] + +// clock #15 + +eq_secp256r1_add_chunks[30] = s * x2 // s[15] * x2[15] + - s * x1 // - s[15] * x1[15] + - 0xFFFF * q0 // - p[15] * q0[15] + + 0xFFFE; // + (p*offset)[30] + +eq_secp256r1_add_chunks[31] = 0x1FFFF; // (p*offset)[31] + diff --git a/precompiles/arith_eq/pil/equations/secp256r1_dbl.pil b/precompiles/arith_eq/pil/equations/secp256r1_dbl.pil new file mode 100644 index 000000000..7b3041d1c --- /dev/null +++ b/precompiles/arith_eq/pil/equations/secp256r1_dbl.pil @@ -0,0 +1,757 @@ +// code generated +// +// equation: 2*s*y1-3*x1*x1-a+p*q0-p*offset +// +// a: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC +// p: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF +// offset: 0x40000000000000000000000000000000000000000000000000000000000000000 +// 2: 2 +// 3: 3 +// (p*offset): 0x3FFFFFFFC00000004000000000000000000000003FFFFFFFFFFFFFFFFFFFFFFFC0000000000000000000000000000000000000000000000000000000000000000 +// +// chunks:16 +// chunk_bits:16 +// terms_by_clock: 2 + + +const expr eq_secp256r1_dbl_chunks[32]; + +// clock #0 + +eq_secp256r1_dbl_chunks[ 0] = 2 * s * y1 // 2[0] * s[0] * y1[0] + - 3 * x1 * x1 // - 3[0] * x1[0] * x1[0] + - 0xFFFC // - a[0] + + 0xFFFF * q0; // + p[0] * q0[0] + +eq_secp256r1_dbl_chunks[ 1] = 2 * s' * y1 // 2[0] * s[1] * y1[0] + + 2 * s * y1' // + 2[0] * s[0] * y1[1] + - 3 * x1' * x1 // - 3[0] * x1[1] * x1[0] + - 3 * x1 * x1' // - 3[0] * x1[0] * x1[1] + - 0xFFFF // - a[1] + + 0xFFFF * q0 // + p[1] * q0[0] + + 0xFFFF * q0'; // + p[0] * q0[1] + +// clock #1 + +eq_secp256r1_dbl_chunks[ 2] = 2 * s' * 'y1 // 2[0] * s[2] * y1[0] + + 2 * s * y1 // + 2[0] * s[1] * y1[1] + + 2 * 's * y1' // + 2[0] * s[0] * y1[2] + - 3 * x1' * 'x1 // - 3[0] * x1[2] * x1[0] + - 3 * x1 * x1 // - 3[0] * x1[1] * x1[1] + - 3 * 'x1 * x1' // - 3[0] * x1[0] * x1[2] + - 0xFFFF // - a[2] + + 0xFFFF * 'q0 // + p[2] * q0[0] + + 0xFFFF * q0 // + p[1] * q0[1] + + 0xFFFF * q0'; // + p[0] * q0[2] + +eq_secp256r1_dbl_chunks[ 3] = 2 * s'2 * 'y1 // 2[0] * s[3] * y1[0] + + 2 * s' * y1 // + 2[0] * s[2] * y1[1] + + 2 * s * y1' // + 2[0] * s[1] * y1[2] + + 2 * 's * y1'2 // + 2[0] * s[0] * y1[3] + - 3 * x1'2 * 'x1 // - 3[0] * x1[3] * x1[0] + - 3 * x1' * x1 // - 3[0] * x1[2] * x1[1] + - 3 * x1 * x1' // - 3[0] * x1[1] * x1[2] + - 3 * 'x1 * x1'2 // - 3[0] * x1[0] * x1[3] + - 0xFFFF // - a[3] + + 0xFFFF * 'q0 // + p[3] * q0[0] + + 0xFFFF * q0 // + p[2] * q0[1] + + 0xFFFF * q0' // + p[1] * q0[2] + + 0xFFFF * q0'2; // + p[0] * q0[3] + +// clock #2 + +eq_secp256r1_dbl_chunks[ 4] = 2 * s'2 * 2'y1 // 2[0] * s[4] * y1[0] + + 2 * s' * 'y1 // + 2[0] * s[3] * y1[1] + + 2 * s * y1 // + 2[0] * s[2] * y1[2] + + 2 * 's * y1' // + 2[0] * s[1] * y1[3] + + 2 * 2's * y1'2 // + 2[0] * s[0] * y1[4] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[4] * x1[0] + - 3 * x1' * 'x1 // - 3[0] * x1[3] * x1[1] + - 3 * x1 * x1 // - 3[0] * x1[2] * x1[2] + - 3 * 'x1 * x1' // - 3[0] * x1[1] * x1[3] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[0] * x1[4] + - 0xFFFF // - a[4] + + 0xFFFF * 2'q0 // + p[4] * q0[0] + + 0xFFFF * 'q0 // + p[3] * q0[1] + + 0xFFFF * q0 // + p[2] * q0[2] + + 0xFFFF * q0' // + p[1] * q0[3] + + 0xFFFF * q0'2; // + p[0] * q0[4] + +eq_secp256r1_dbl_chunks[ 5] = 2 * s'3 * 2'y1 // 2[0] * s[5] * y1[0] + + 2 * s'2 * 'y1 // + 2[0] * s[4] * y1[1] + + 2 * s' * y1 // + 2[0] * s[3] * y1[2] + + 2 * s * y1' // + 2[0] * s[2] * y1[3] + + 2 * 's * y1'2 // + 2[0] * s[1] * y1[4] + + 2 * 2's * y1'3 // + 2[0] * s[0] * y1[5] + - 3 * x1'3 * 2'x1 // - 3[0] * x1[5] * x1[0] + - 3 * x1'2 * 'x1 // - 3[0] * x1[4] * x1[1] + - 3 * x1' * x1 // - 3[0] * x1[3] * x1[2] + - 3 * x1 * x1' // - 3[0] * x1[2] * x1[3] + - 3 * 'x1 * x1'2 // - 3[0] * x1[1] * x1[4] + - 3 * 2'x1 * x1'3 // - 3[0] * x1[0] * x1[5] + - 0xFFFF // - a[5] + + 0xFFFF * 2'q0 // + p[5] * q0[0] + + 0xFFFF * 'q0 // + p[4] * q0[1] + + 0xFFFF * q0 // + p[3] * q0[2] + + 0xFFFF * q0' // + p[2] * q0[3] + + 0xFFFF * q0'2 // + p[1] * q0[4] + + 0xFFFF * q0'3; // + p[0] * q0[5] + +// clock #3 + +eq_secp256r1_dbl_chunks[ 6] = 2 * s'3 * 3'y1 // 2[0] * s[6] * y1[0] + + 2 * s'2 * 2'y1 // + 2[0] * s[5] * y1[1] + + 2 * s' * 'y1 // + 2[0] * s[4] * y1[2] + + 2 * s * y1 // + 2[0] * s[3] * y1[3] + + 2 * 's * y1' // + 2[0] * s[2] * y1[4] + + 2 * 2's * y1'2 // + 2[0] * s[1] * y1[5] + + 2 * 3's * y1'3 // + 2[0] * s[0] * y1[6] + - 3 * x1'3 * 3'x1 // - 3[0] * x1[6] * x1[0] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[5] * x1[1] + - 3 * x1' * 'x1 // - 3[0] * x1[4] * x1[2] + - 3 * x1 * x1 // - 3[0] * x1[3] * x1[3] + - 3 * 'x1 * x1' // - 3[0] * x1[2] * x1[4] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[1] * x1[5] + - 3 * 3'x1 * x1'3 // - 3[0] * x1[0] * x1[6] + + 0xFFFF * 2'q0 // + p[5] * q0[1] + + 0xFFFF * 'q0 // + p[4] * q0[2] + + 0xFFFF * q0 // + p[3] * q0[3] + + 0xFFFF * q0' // + p[2] * q0[4] + + 0xFFFF * q0'2 // + p[1] * q0[5] + + 0xFFFF * q0'3; // + p[0] * q0[6] + +eq_secp256r1_dbl_chunks[ 7] = 2 * s'4 * 3'y1 // 2[0] * s[7] * y1[0] + + 2 * s'3 * 2'y1 // + 2[0] * s[6] * y1[1] + + 2 * s'2 * 'y1 // + 2[0] * s[5] * y1[2] + + 2 * s' * y1 // + 2[0] * s[4] * y1[3] + + 2 * s * y1' // + 2[0] * s[3] * y1[4] + + 2 * 's * y1'2 // + 2[0] * s[2] * y1[5] + + 2 * 2's * y1'3 // + 2[0] * s[1] * y1[6] + + 2 * 3's * y1'4 // + 2[0] * s[0] * y1[7] + - 3 * x1'4 * 3'x1 // - 3[0] * x1[7] * x1[0] + - 3 * x1'3 * 2'x1 // - 3[0] * x1[6] * x1[1] + - 3 * x1'2 * 'x1 // - 3[0] * x1[5] * x1[2] + - 3 * x1' * x1 // - 3[0] * x1[4] * x1[3] + - 3 * x1 * x1' // - 3[0] * x1[3] * x1[4] + - 3 * 'x1 * x1'2 // - 3[0] * x1[2] * x1[5] + - 3 * 2'x1 * x1'3 // - 3[0] * x1[1] * x1[6] + - 3 * 3'x1 * x1'4 // - 3[0] * x1[0] * x1[7] + + 0xFFFF * 'q0 // + p[5] * q0[2] + + 0xFFFF * q0 // + p[4] * q0[3] + + 0xFFFF * q0' // + p[3] * q0[4] + + 0xFFFF * q0'2 // + p[2] * q0[5] + + 0xFFFF * q0'3 // + p[1] * q0[6] + + 0xFFFF * q0'4; // + p[0] * q0[7] + +// clock #4 + +eq_secp256r1_dbl_chunks[ 8] = 2 * s'4 * 4'y1 // 2[0] * s[8] * y1[0] + + 2 * s'3 * 3'y1 // + 2[0] * s[7] * y1[1] + + 2 * s'2 * 2'y1 // + 2[0] * s[6] * y1[2] + + 2 * s' * 'y1 // + 2[0] * s[5] * y1[3] + + 2 * s * y1 // + 2[0] * s[4] * y1[4] + + 2 * 's * y1' // + 2[0] * s[3] * y1[5] + + 2 * 2's * y1'2 // + 2[0] * s[2] * y1[6] + + 2 * 3's * y1'3 // + 2[0] * s[1] * y1[7] + + 2 * 4's * y1'4 // + 2[0] * s[0] * y1[8] + - 3 * x1'4 * 4'x1 // - 3[0] * x1[8] * x1[0] + - 3 * x1'3 * 3'x1 // - 3[0] * x1[7] * x1[1] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[6] * x1[2] + - 3 * x1' * 'x1 // - 3[0] * x1[5] * x1[3] + - 3 * x1 * x1 // - 3[0] * x1[4] * x1[4] + - 3 * 'x1 * x1' // - 3[0] * x1[3] * x1[5] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[2] * x1[6] + - 3 * 3'x1 * x1'3 // - 3[0] * x1[1] * x1[7] + - 3 * 4'x1 * x1'4 // - 3[0] * x1[0] * x1[8] + + 0xFFFF * 'q0 // + p[5] * q0[3] + + 0xFFFF * q0 // + p[4] * q0[4] + + 0xFFFF * q0' // + p[3] * q0[5] + + 0xFFFF * q0'2 // + p[2] * q0[6] + + 0xFFFF * q0'3 // + p[1] * q0[7] + + 0xFFFF * q0'4; // + p[0] * q0[8] + +eq_secp256r1_dbl_chunks[ 9] = 2 * s'5 * 4'y1 // 2[0] * s[9] * y1[0] + + 2 * s'4 * 3'y1 // + 2[0] * s[8] * y1[1] + + 2 * s'3 * 2'y1 // + 2[0] * s[7] * y1[2] + + 2 * s'2 * 'y1 // + 2[0] * s[6] * y1[3] + + 2 * s' * y1 // + 2[0] * s[5] * y1[4] + + 2 * s * y1' // + 2[0] * s[4] * y1[5] + + 2 * 's * y1'2 // + 2[0] * s[3] * y1[6] + + 2 * 2's * y1'3 // + 2[0] * s[2] * y1[7] + + 2 * 3's * y1'4 // + 2[0] * s[1] * y1[8] + + 2 * 4's * y1'5 // + 2[0] * s[0] * y1[9] + - 3 * x1'5 * 4'x1 // - 3[0] * x1[9] * x1[0] + - 3 * x1'4 * 3'x1 // - 3[0] * x1[8] * x1[1] + - 3 * x1'3 * 2'x1 // - 3[0] * x1[7] * x1[2] + - 3 * x1'2 * 'x1 // - 3[0] * x1[6] * x1[3] + - 3 * x1' * x1 // - 3[0] * x1[5] * x1[4] + - 3 * x1 * x1' // - 3[0] * x1[4] * x1[5] + - 3 * 'x1 * x1'2 // - 3[0] * x1[3] * x1[6] + - 3 * 2'x1 * x1'3 // - 3[0] * x1[2] * x1[7] + - 3 * 3'x1 * x1'4 // - 3[0] * x1[1] * x1[8] + - 3 * 4'x1 * x1'5 // - 3[0] * x1[0] * x1[9] + + 0xFFFF * q0 // + p[5] * q0[4] + + 0xFFFF * q0' // + p[4] * q0[5] + + 0xFFFF * q0'2 // + p[3] * q0[6] + + 0xFFFF * q0'3 // + p[2] * q0[7] + + 0xFFFF * q0'4 // + p[1] * q0[8] + + 0xFFFF * q0'5; // + p[0] * q0[9] + +// clock #5 + +eq_secp256r1_dbl_chunks[10] = 2 * s'5 * 5'y1 // 2[0] * s[10] * y1[0] + + 2 * s'4 * 4'y1 // + 2[0] * s[9] * y1[1] + + 2 * s'3 * 3'y1 // + 2[0] * s[8] * y1[2] + + 2 * s'2 * 2'y1 // + 2[0] * s[7] * y1[3] + + 2 * s' * 'y1 // + 2[0] * s[6] * y1[4] + + 2 * s * y1 // + 2[0] * s[5] * y1[5] + + 2 * 's * y1' // + 2[0] * s[4] * y1[6] + + 2 * 2's * y1'2 // + 2[0] * s[3] * y1[7] + + 2 * 3's * y1'3 // + 2[0] * s[2] * y1[8] + + 2 * 4's * y1'4 // + 2[0] * s[1] * y1[9] + + 2 * 5's * y1'5 // + 2[0] * s[0] * y1[10] + - 3 * x1'5 * 5'x1 // - 3[0] * x1[10] * x1[0] + - 3 * x1'4 * 4'x1 // - 3[0] * x1[9] * x1[1] + - 3 * x1'3 * 3'x1 // - 3[0] * x1[8] * x1[2] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[7] * x1[3] + - 3 * x1' * 'x1 // - 3[0] * x1[6] * x1[4] + - 3 * x1 * x1 // - 3[0] * x1[5] * x1[5] + - 3 * 'x1 * x1' // - 3[0] * x1[4] * x1[6] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[3] * x1[7] + - 3 * 3'x1 * x1'3 // - 3[0] * x1[2] * x1[8] + - 3 * 4'x1 * x1'4 // - 3[0] * x1[1] * x1[9] + - 3 * 5'x1 * x1'5 // - 3[0] * x1[0] * x1[10] + + 0xFFFF * q0 // + p[5] * q0[5] + + 0xFFFF * q0' // + p[4] * q0[6] + + 0xFFFF * q0'2 // + p[3] * q0[7] + + 0xFFFF * q0'3 // + p[2] * q0[8] + + 0xFFFF * q0'4 // + p[1] * q0[9] + + 0xFFFF * q0'5; // + p[0] * q0[10] + +eq_secp256r1_dbl_chunks[11] = 2 * s'6 * 5'y1 // 2[0] * s[11] * y1[0] + + 2 * s'5 * 4'y1 // + 2[0] * s[10] * y1[1] + + 2 * s'4 * 3'y1 // + 2[0] * s[9] * y1[2] + + 2 * s'3 * 2'y1 // + 2[0] * s[8] * y1[3] + + 2 * s'2 * 'y1 // + 2[0] * s[7] * y1[4] + + 2 * s' * y1 // + 2[0] * s[6] * y1[5] + + 2 * s * y1' // + 2[0] * s[5] * y1[6] + + 2 * 's * y1'2 // + 2[0] * s[4] * y1[7] + + 2 * 2's * y1'3 // + 2[0] * s[3] * y1[8] + + 2 * 3's * y1'4 // + 2[0] * s[2] * y1[9] + + 2 * 4's * y1'5 // + 2[0] * s[1] * y1[10] + + 2 * 5's * y1'6 // + 2[0] * s[0] * y1[11] + - 3 * x1'6 * 5'x1 // - 3[0] * x1[11] * x1[0] + - 3 * x1'5 * 4'x1 // - 3[0] * x1[10] * x1[1] + - 3 * x1'4 * 3'x1 // - 3[0] * x1[9] * x1[2] + - 3 * x1'3 * 2'x1 // - 3[0] * x1[8] * x1[3] + - 3 * x1'2 * 'x1 // - 3[0] * x1[7] * x1[4] + - 3 * x1' * x1 // - 3[0] * x1[6] * x1[5] + - 3 * x1 * x1' // - 3[0] * x1[5] * x1[6] + - 3 * 'x1 * x1'2 // - 3[0] * x1[4] * x1[7] + - 3 * 2'x1 * x1'3 // - 3[0] * x1[3] * x1[8] + - 3 * 3'x1 * x1'4 // - 3[0] * x1[2] * x1[9] + - 3 * 4'x1 * x1'5 // - 3[0] * x1[1] * x1[10] + - 3 * 5'x1 * x1'6 // - 3[0] * x1[0] * x1[11] + + 0xFFFF * q0' // + p[5] * q0[6] + + 0xFFFF * q0'2 // + p[4] * q0[7] + + 0xFFFF * q0'3 // + p[3] * q0[8] + + 0xFFFF * q0'4 // + p[2] * q0[9] + + 0xFFFF * q0'5 // + p[1] * q0[10] + + 0xFFFF * q0'6; // + p[0] * q0[11] + +// clock #6 + +eq_secp256r1_dbl_chunks[12] = 2 * s'6 * 6'y1 // 2[0] * s[12] * y1[0] + + 2 * s'5 * 5'y1 // + 2[0] * s[11] * y1[1] + + 2 * s'4 * 4'y1 // + 2[0] * s[10] * y1[2] + + 2 * s'3 * 3'y1 // + 2[0] * s[9] * y1[3] + + 2 * s'2 * 2'y1 // + 2[0] * s[8] * y1[4] + + 2 * s' * 'y1 // + 2[0] * s[7] * y1[5] + + 2 * s * y1 // + 2[0] * s[6] * y1[6] + + 2 * 's * y1' // + 2[0] * s[5] * y1[7] + + 2 * 2's * y1'2 // + 2[0] * s[4] * y1[8] + + 2 * 3's * y1'3 // + 2[0] * s[3] * y1[9] + + 2 * 4's * y1'4 // + 2[0] * s[2] * y1[10] + + 2 * 5's * y1'5 // + 2[0] * s[1] * y1[11] + + 2 * 6's * y1'6 // + 2[0] * s[0] * y1[12] + - 3 * x1'6 * 6'x1 // - 3[0] * x1[12] * x1[0] + - 3 * x1'5 * 5'x1 // - 3[0] * x1[11] * x1[1] + - 3 * x1'4 * 4'x1 // - 3[0] * x1[10] * x1[2] + - 3 * x1'3 * 3'x1 // - 3[0] * x1[9] * x1[3] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[8] * x1[4] + - 3 * x1' * 'x1 // - 3[0] * x1[7] * x1[5] + - 3 * x1 * x1 // - 3[0] * x1[6] * x1[6] + - 3 * 'x1 * x1' // - 3[0] * x1[5] * x1[7] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[4] * x1[8] + - 3 * 3'x1 * x1'3 // - 3[0] * x1[3] * x1[9] + - 3 * 4'x1 * x1'4 // - 3[0] * x1[2] * x1[10] + - 3 * 5'x1 * x1'5 // - 3[0] * x1[1] * x1[11] + - 3 * 6'x1 * x1'6 // - 3[0] * x1[0] * x1[12] + - 0x1 // - a[12] + + 6'q0 // + q0[0] + + 0xFFFF * q0' // + p[5] * q0[7] + + 0xFFFF * q0'2 // + p[4] * q0[8] + + 0xFFFF * q0'3 // + p[3] * q0[9] + + 0xFFFF * q0'4 // + p[2] * q0[10] + + 0xFFFF * q0'5 // + p[1] * q0[11] + + 0xFFFF * q0'6; // + p[0] * q0[12] + +eq_secp256r1_dbl_chunks[13] = 2 * s'7 * 6'y1 // 2[0] * s[13] * y1[0] + + 2 * s'6 * 5'y1 // + 2[0] * s[12] * y1[1] + + 2 * s'5 * 4'y1 // + 2[0] * s[11] * y1[2] + + 2 * s'4 * 3'y1 // + 2[0] * s[10] * y1[3] + + 2 * s'3 * 2'y1 // + 2[0] * s[9] * y1[4] + + 2 * s'2 * 'y1 // + 2[0] * s[8] * y1[5] + + 2 * s' * y1 // + 2[0] * s[7] * y1[6] + + 2 * s * y1' // + 2[0] * s[6] * y1[7] + + 2 * 's * y1'2 // + 2[0] * s[5] * y1[8] + + 2 * 2's * y1'3 // + 2[0] * s[4] * y1[9] + + 2 * 3's * y1'4 // + 2[0] * s[3] * y1[10] + + 2 * 4's * y1'5 // + 2[0] * s[2] * y1[11] + + 2 * 5's * y1'6 // + 2[0] * s[1] * y1[12] + + 2 * 6's * y1'7 // + 2[0] * s[0] * y1[13] + - 3 * x1'7 * 6'x1 // - 3[0] * x1[13] * x1[0] + - 3 * x1'6 * 5'x1 // - 3[0] * x1[12] * x1[1] + - 3 * x1'5 * 4'x1 // - 3[0] * x1[11] * x1[2] + - 3 * x1'4 * 3'x1 // - 3[0] * x1[10] * x1[3] + - 3 * x1'3 * 2'x1 // - 3[0] * x1[9] * x1[4] + - 3 * x1'2 * 'x1 // - 3[0] * x1[8] * x1[5] + - 3 * x1' * x1 // - 3[0] * x1[7] * x1[6] + - 3 * x1 * x1' // - 3[0] * x1[6] * x1[7] + - 3 * 'x1 * x1'2 // - 3[0] * x1[5] * x1[8] + - 3 * 2'x1 * x1'3 // - 3[0] * x1[4] * x1[9] + - 3 * 3'x1 * x1'4 // - 3[0] * x1[3] * x1[10] + - 3 * 4'x1 * x1'5 // - 3[0] * x1[2] * x1[11] + - 3 * 5'x1 * x1'6 // - 3[0] * x1[1] * x1[12] + - 3 * 6'x1 * x1'7 // - 3[0] * x1[0] * x1[13] + + 5'q0 // + q0[1] + + 0xFFFF * q0'2 // + p[5] * q0[8] + + 0xFFFF * q0'3 // + p[4] * q0[9] + + 0xFFFF * q0'4 // + p[3] * q0[10] + + 0xFFFF * q0'5 // + p[2] * q0[11] + + 0xFFFF * q0'6 // + p[1] * q0[12] + + 0xFFFF * q0'7; // + p[0] * q0[13] + +// clock #7 + +eq_secp256r1_dbl_chunks[14] = 2 * s'7 * 7'y1 // 2[0] * s[14] * y1[0] + + 2 * s'6 * 6'y1 // + 2[0] * s[13] * y1[1] + + 2 * s'5 * 5'y1 // + 2[0] * s[12] * y1[2] + + 2 * s'4 * 4'y1 // + 2[0] * s[11] * y1[3] + + 2 * s'3 * 3'y1 // + 2[0] * s[10] * y1[4] + + 2 * s'2 * 2'y1 // + 2[0] * s[9] * y1[5] + + 2 * s' * 'y1 // + 2[0] * s[8] * y1[6] + + 2 * s * y1 // + 2[0] * s[7] * y1[7] + + 2 * 's * y1' // + 2[0] * s[6] * y1[8] + + 2 * 2's * y1'2 // + 2[0] * s[5] * y1[9] + + 2 * 3's * y1'3 // + 2[0] * s[4] * y1[10] + + 2 * 4's * y1'4 // + 2[0] * s[3] * y1[11] + + 2 * 5's * y1'5 // + 2[0] * s[2] * y1[12] + + 2 * 6's * y1'6 // + 2[0] * s[1] * y1[13] + + 2 * 7's * y1'7 // + 2[0] * s[0] * y1[14] + - 3 * x1'7 * 7'x1 // - 3[0] * x1[14] * x1[0] + - 3 * x1'6 * 6'x1 // - 3[0] * x1[13] * x1[1] + - 3 * x1'5 * 5'x1 // - 3[0] * x1[12] * x1[2] + - 3 * x1'4 * 4'x1 // - 3[0] * x1[11] * x1[3] + - 3 * x1'3 * 3'x1 // - 3[0] * x1[10] * x1[4] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[9] * x1[5] + - 3 * x1' * 'x1 // - 3[0] * x1[8] * x1[6] + - 3 * x1 * x1 // - 3[0] * x1[7] * x1[7] + - 3 * 'x1 * x1' // - 3[0] * x1[6] * x1[8] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[5] * x1[9] + - 3 * 3'x1 * x1'3 // - 3[0] * x1[4] * x1[10] + - 3 * 4'x1 * x1'4 // - 3[0] * x1[3] * x1[11] + - 3 * 5'x1 * x1'5 // - 3[0] * x1[2] * x1[12] + - 3 * 6'x1 * x1'6 // - 3[0] * x1[1] * x1[13] + - 3 * 7'x1 * x1'7 // - 3[0] * x1[0] * x1[14] + - 0xFFFF // - a[14] + + 0xFFFF * 7'q0 // + p[14] * q0[0] + + 5'q0 // + q0[2] + + 0xFFFF * q0'2 // + p[5] * q0[9] + + 0xFFFF * q0'3 // + p[4] * q0[10] + + 0xFFFF * q0'4 // + p[3] * q0[11] + + 0xFFFF * q0'5 // + p[2] * q0[12] + + 0xFFFF * q0'6 // + p[1] * q0[13] + + 0xFFFF * q0'7; // + p[0] * q0[14] + +eq_secp256r1_dbl_chunks[15] = 2 * s'8 * 7'y1 // 2[0] * s[15] * y1[0] + + 2 * s'7 * 6'y1 // + 2[0] * s[14] * y1[1] + + 2 * s'6 * 5'y1 // + 2[0] * s[13] * y1[2] + + 2 * s'5 * 4'y1 // + 2[0] * s[12] * y1[3] + + 2 * s'4 * 3'y1 // + 2[0] * s[11] * y1[4] + + 2 * s'3 * 2'y1 // + 2[0] * s[10] * y1[5] + + 2 * s'2 * 'y1 // + 2[0] * s[9] * y1[6] + + 2 * s' * y1 // + 2[0] * s[8] * y1[7] + + 2 * s * y1' // + 2[0] * s[7] * y1[8] + + 2 * 's * y1'2 // + 2[0] * s[6] * y1[9] + + 2 * 2's * y1'3 // + 2[0] * s[5] * y1[10] + + 2 * 3's * y1'4 // + 2[0] * s[4] * y1[11] + + 2 * 4's * y1'5 // + 2[0] * s[3] * y1[12] + + 2 * 5's * y1'6 // + 2[0] * s[2] * y1[13] + + 2 * 6's * y1'7 // + 2[0] * s[1] * y1[14] + + 2 * 7's * y1'8 // + 2[0] * s[0] * y1[15] + - 3 * x1'8 * 7'x1 // - 3[0] * x1[15] * x1[0] + - 3 * x1'7 * 6'x1 // - 3[0] * x1[14] * x1[1] + - 3 * x1'6 * 5'x1 // - 3[0] * x1[13] * x1[2] + - 3 * x1'5 * 4'x1 // - 3[0] * x1[12] * x1[3] + - 3 * x1'4 * 3'x1 // - 3[0] * x1[11] * x1[4] + - 3 * x1'3 * 2'x1 // - 3[0] * x1[10] * x1[5] + - 3 * x1'2 * 'x1 // - 3[0] * x1[9] * x1[6] + - 3 * x1' * x1 // - 3[0] * x1[8] * x1[7] + - 3 * x1 * x1' // - 3[0] * x1[7] * x1[8] + - 3 * 'x1 * x1'2 // - 3[0] * x1[6] * x1[9] + - 3 * 2'x1 * x1'3 // - 3[0] * x1[5] * x1[10] + - 3 * 3'x1 * x1'4 // - 3[0] * x1[4] * x1[11] + - 3 * 4'x1 * x1'5 // - 3[0] * x1[3] * x1[12] + - 3 * 5'x1 * x1'6 // - 3[0] * x1[2] * x1[13] + - 3 * 6'x1 * x1'7 // - 3[0] * x1[1] * x1[14] + - 3 * 7'x1 * x1'8 // - 3[0] * x1[0] * x1[15] + - 0xFFFF // - a[15] + + 0xFFFF * 7'q0 // + p[15] * q0[0] + + 0xFFFF * 6'q0 // + p[14] * q0[1] + + 4'q0 // + q0[3] + + 0xFFFF * q0'3 // + p[5] * q0[10] + + 0xFFFF * q0'4 // + p[4] * q0[11] + + 0xFFFF * q0'5 // + p[3] * q0[12] + + 0xFFFF * q0'6 // + p[2] * q0[13] + + 0xFFFF * q0'7 // + p[1] * q0[14] + + 0xFFFF * q0'8; // + p[0] * q0[15] + +// clock #8 + +eq_secp256r1_dbl_chunks[16] = 2 * s'7 * 7'y1 // 2[0] * s[15] * y1[1] + + 2 * s'6 * 6'y1 // + 2[0] * s[14] * y1[2] + + 2 * s'5 * 5'y1 // + 2[0] * s[13] * y1[3] + + 2 * s'4 * 4'y1 // + 2[0] * s[12] * y1[4] + + 2 * s'3 * 3'y1 // + 2[0] * s[11] * y1[5] + + 2 * s'2 * 2'y1 // + 2[0] * s[10] * y1[6] + + 2 * s' * 'y1 // + 2[0] * s[9] * y1[7] + + 2 * s * y1 // + 2[0] * s[8] * y1[8] + + 2 * 's * y1' // + 2[0] * s[7] * y1[9] + + 2 * 2's * y1'2 // + 2[0] * s[6] * y1[10] + + 2 * 3's * y1'3 // + 2[0] * s[5] * y1[11] + + 2 * 4's * y1'4 // + 2[0] * s[4] * y1[12] + + 2 * 5's * y1'5 // + 2[0] * s[3] * y1[13] + + 2 * 6's * y1'6 // + 2[0] * s[2] * y1[14] + + 2 * 7's * y1'7 // + 2[0] * s[1] * y1[15] + - 3 * x1'7 * 7'x1 // - 3[0] * x1[15] * x1[1] + - 3 * x1'6 * 6'x1 // - 3[0] * x1[14] * x1[2] + - 3 * x1'5 * 5'x1 // - 3[0] * x1[13] * x1[3] + - 3 * x1'4 * 4'x1 // - 3[0] * x1[12] * x1[4] + - 3 * x1'3 * 3'x1 // - 3[0] * x1[11] * x1[5] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[10] * x1[6] + - 3 * x1' * 'x1 // - 3[0] * x1[9] * x1[7] + - 3 * x1 * x1 // - 3[0] * x1[8] * x1[8] + - 3 * 'x1 * x1' // - 3[0] * x1[7] * x1[9] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[6] * x1[10] + - 3 * 3'x1 * x1'3 // - 3[0] * x1[5] * x1[11] + - 3 * 4'x1 * x1'4 // - 3[0] * x1[4] * x1[12] + - 3 * 5'x1 * x1'5 // - 3[0] * x1[3] * x1[13] + - 3 * 6'x1 * x1'6 // - 3[0] * x1[2] * x1[14] + - 3 * 7'x1 * x1'7 // - 3[0] * x1[1] * x1[15] + + 0xFFFF * 7'q0 // + p[15] * q0[1] + + 0xFFFF * 6'q0 // + p[14] * q0[2] + + 4'q0 // + q0[4] + + 0xFFFF * q0'3 // + p[5] * q0[11] + + 0xFFFF * q0'4 // + p[4] * q0[12] + + 0xFFFF * q0'5 // + p[3] * q0[13] + + 0xFFFF * q0'6 // + p[2] * q0[14] + + 0xFFFF * q0'7 // + p[1] * q0[15] + - 0xFFFC; // - (p*offset)[16] + +eq_secp256r1_dbl_chunks[17] = 2 * s'7 * 6'y1 // 2[0] * s[15] * y1[2] + + 2 * s'6 * 5'y1 // + 2[0] * s[14] * y1[3] + + 2 * s'5 * 4'y1 // + 2[0] * s[13] * y1[4] + + 2 * s'4 * 3'y1 // + 2[0] * s[12] * y1[5] + + 2 * s'3 * 2'y1 // + 2[0] * s[11] * y1[6] + + 2 * s'2 * 'y1 // + 2[0] * s[10] * y1[7] + + 2 * s' * y1 // + 2[0] * s[9] * y1[8] + + 2 * s * y1' // + 2[0] * s[8] * y1[9] + + 2 * 's * y1'2 // + 2[0] * s[7] * y1[10] + + 2 * 2's * y1'3 // + 2[0] * s[6] * y1[11] + + 2 * 3's * y1'4 // + 2[0] * s[5] * y1[12] + + 2 * 4's * y1'5 // + 2[0] * s[4] * y1[13] + + 2 * 5's * y1'6 // + 2[0] * s[3] * y1[14] + + 2 * 6's * y1'7 // + 2[0] * s[2] * y1[15] + - 3 * x1'7 * 6'x1 // - 3[0] * x1[15] * x1[2] + - 3 * x1'6 * 5'x1 // - 3[0] * x1[14] * x1[3] + - 3 * x1'5 * 4'x1 // - 3[0] * x1[13] * x1[4] + - 3 * x1'4 * 3'x1 // - 3[0] * x1[12] * x1[5] + - 3 * x1'3 * 2'x1 // - 3[0] * x1[11] * x1[6] + - 3 * x1'2 * 'x1 // - 3[0] * x1[10] * x1[7] + - 3 * x1' * x1 // - 3[0] * x1[9] * x1[8] + - 3 * x1 * x1' // - 3[0] * x1[8] * x1[9] + - 3 * 'x1 * x1'2 // - 3[0] * x1[7] * x1[10] + - 3 * 2'x1 * x1'3 // - 3[0] * x1[6] * x1[11] + - 3 * 3'x1 * x1'4 // - 3[0] * x1[5] * x1[12] + - 3 * 4'x1 * x1'5 // - 3[0] * x1[4] * x1[13] + - 3 * 5'x1 * x1'6 // - 3[0] * x1[3] * x1[14] + - 3 * 6'x1 * x1'7 // - 3[0] * x1[2] * x1[15] + + 0xFFFF * 6'q0 // + p[15] * q0[2] + + 0xFFFF * 5'q0 // + p[14] * q0[3] + + 3'q0 // + q0[5] + + 0xFFFF * q0'4 // + p[5] * q0[12] + + 0xFFFF * q0'5 // + p[4] * q0[13] + + 0xFFFF * q0'6 // + p[3] * q0[14] + + 0xFFFF * q0'7 // + p[2] * q0[15] + - 0xFFFF; // - (p*offset)[17] + +// clock #9 + +eq_secp256r1_dbl_chunks[18] = 2 * s'6 * 6'y1 // 2[0] * s[15] * y1[3] + + 2 * s'5 * 5'y1 // + 2[0] * s[14] * y1[4] + + 2 * s'4 * 4'y1 // + 2[0] * s[13] * y1[5] + + 2 * s'3 * 3'y1 // + 2[0] * s[12] * y1[6] + + 2 * s'2 * 2'y1 // + 2[0] * s[11] * y1[7] + + 2 * s' * 'y1 // + 2[0] * s[10] * y1[8] + + 2 * s * y1 // + 2[0] * s[9] * y1[9] + + 2 * 's * y1' // + 2[0] * s[8] * y1[10] + + 2 * 2's * y1'2 // + 2[0] * s[7] * y1[11] + + 2 * 3's * y1'3 // + 2[0] * s[6] * y1[12] + + 2 * 4's * y1'4 // + 2[0] * s[5] * y1[13] + + 2 * 5's * y1'5 // + 2[0] * s[4] * y1[14] + + 2 * 6's * y1'6 // + 2[0] * s[3] * y1[15] + - 3 * x1'6 * 6'x1 // - 3[0] * x1[15] * x1[3] + - 3 * x1'5 * 5'x1 // - 3[0] * x1[14] * x1[4] + - 3 * x1'4 * 4'x1 // - 3[0] * x1[13] * x1[5] + - 3 * x1'3 * 3'x1 // - 3[0] * x1[12] * x1[6] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[11] * x1[7] + - 3 * x1' * 'x1 // - 3[0] * x1[10] * x1[8] + - 3 * x1 * x1 // - 3[0] * x1[9] * x1[9] + - 3 * 'x1 * x1' // - 3[0] * x1[8] * x1[10] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[7] * x1[11] + - 3 * 3'x1 * x1'3 // - 3[0] * x1[6] * x1[12] + - 3 * 4'x1 * x1'4 // - 3[0] * x1[5] * x1[13] + - 3 * 5'x1 * x1'5 // - 3[0] * x1[4] * x1[14] + - 3 * 6'x1 * x1'6 // - 3[0] * x1[3] * x1[15] + + 0xFFFF * 6'q0 // + p[15] * q0[3] + + 0xFFFF * 5'q0 // + p[14] * q0[4] + + 3'q0 // + q0[6] + + 0xFFFF * q0'4 // + p[5] * q0[13] + + 0xFFFF * q0'5 // + p[4] * q0[14] + + 0xFFFF * q0'6 // + p[3] * q0[15] + - 0xFFFF; // - (p*offset)[18] + +eq_secp256r1_dbl_chunks[19] = 2 * s'6 * 5'y1 // 2[0] * s[15] * y1[4] + + 2 * s'5 * 4'y1 // + 2[0] * s[14] * y1[5] + + 2 * s'4 * 3'y1 // + 2[0] * s[13] * y1[6] + + 2 * s'3 * 2'y1 // + 2[0] * s[12] * y1[7] + + 2 * s'2 * 'y1 // + 2[0] * s[11] * y1[8] + + 2 * s' * y1 // + 2[0] * s[10] * y1[9] + + 2 * s * y1' // + 2[0] * s[9] * y1[10] + + 2 * 's * y1'2 // + 2[0] * s[8] * y1[11] + + 2 * 2's * y1'3 // + 2[0] * s[7] * y1[12] + + 2 * 3's * y1'4 // + 2[0] * s[6] * y1[13] + + 2 * 4's * y1'5 // + 2[0] * s[5] * y1[14] + + 2 * 5's * y1'6 // + 2[0] * s[4] * y1[15] + - 3 * x1'6 * 5'x1 // - 3[0] * x1[15] * x1[4] + - 3 * x1'5 * 4'x1 // - 3[0] * x1[14] * x1[5] + - 3 * x1'4 * 3'x1 // - 3[0] * x1[13] * x1[6] + - 3 * x1'3 * 2'x1 // - 3[0] * x1[12] * x1[7] + - 3 * x1'2 * 'x1 // - 3[0] * x1[11] * x1[8] + - 3 * x1' * x1 // - 3[0] * x1[10] * x1[9] + - 3 * x1 * x1' // - 3[0] * x1[9] * x1[10] + - 3 * 'x1 * x1'2 // - 3[0] * x1[8] * x1[11] + - 3 * 2'x1 * x1'3 // - 3[0] * x1[7] * x1[12] + - 3 * 3'x1 * x1'4 // - 3[0] * x1[6] * x1[13] + - 3 * 4'x1 * x1'5 // - 3[0] * x1[5] * x1[14] + - 3 * 5'x1 * x1'6 // - 3[0] * x1[4] * x1[15] + + 0xFFFF * 5'q0 // + p[15] * q0[4] + + 0xFFFF * 4'q0 // + p[14] * q0[5] + + 2'q0 // + q0[7] + + 0xFFFF * q0'5 // + p[5] * q0[14] + + 0xFFFF * q0'6 // + p[4] * q0[15] + - 0xFFFF; // - (p*offset)[19] + +// clock #10 + +eq_secp256r1_dbl_chunks[20] = 2 * s'5 * 5'y1 // 2[0] * s[15] * y1[5] + + 2 * s'4 * 4'y1 // + 2[0] * s[14] * y1[6] + + 2 * s'3 * 3'y1 // + 2[0] * s[13] * y1[7] + + 2 * s'2 * 2'y1 // + 2[0] * s[12] * y1[8] + + 2 * s' * 'y1 // + 2[0] * s[11] * y1[9] + + 2 * s * y1 // + 2[0] * s[10] * y1[10] + + 2 * 's * y1' // + 2[0] * s[9] * y1[11] + + 2 * 2's * y1'2 // + 2[0] * s[8] * y1[12] + + 2 * 3's * y1'3 // + 2[0] * s[7] * y1[13] + + 2 * 4's * y1'4 // + 2[0] * s[6] * y1[14] + + 2 * 5's * y1'5 // + 2[0] * s[5] * y1[15] + - 3 * x1'5 * 5'x1 // - 3[0] * x1[15] * x1[5] + - 3 * x1'4 * 4'x1 // - 3[0] * x1[14] * x1[6] + - 3 * x1'3 * 3'x1 // - 3[0] * x1[13] * x1[7] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[12] * x1[8] + - 3 * x1' * 'x1 // - 3[0] * x1[11] * x1[9] + - 3 * x1 * x1 // - 3[0] * x1[10] * x1[10] + - 3 * 'x1 * x1' // - 3[0] * x1[9] * x1[11] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[8] * x1[12] + - 3 * 3'x1 * x1'3 // - 3[0] * x1[7] * x1[13] + - 3 * 4'x1 * x1'4 // - 3[0] * x1[6] * x1[14] + - 3 * 5'x1 * x1'5 // - 3[0] * x1[5] * x1[15] + + 0xFFFF * 5'q0 // + p[15] * q0[5] + + 0xFFFF * 4'q0 // + p[14] * q0[6] + + 2'q0 // + q0[8] + + 0xFFFF * q0'5 // + p[5] * q0[15] + - 0xFFFF; // - (p*offset)[20] + +eq_secp256r1_dbl_chunks[21] = 2 * s'5 * 4'y1 // 2[0] * s[15] * y1[6] + + 2 * s'4 * 3'y1 // + 2[0] * s[14] * y1[7] + + 2 * s'3 * 2'y1 // + 2[0] * s[13] * y1[8] + + 2 * s'2 * 'y1 // + 2[0] * s[12] * y1[9] + + 2 * s' * y1 // + 2[0] * s[11] * y1[10] + + 2 * s * y1' // + 2[0] * s[10] * y1[11] + + 2 * 's * y1'2 // + 2[0] * s[9] * y1[12] + + 2 * 2's * y1'3 // + 2[0] * s[8] * y1[13] + + 2 * 3's * y1'4 // + 2[0] * s[7] * y1[14] + + 2 * 4's * y1'5 // + 2[0] * s[6] * y1[15] + - 3 * x1'5 * 4'x1 // - 3[0] * x1[15] * x1[6] + - 3 * x1'4 * 3'x1 // - 3[0] * x1[14] * x1[7] + - 3 * x1'3 * 2'x1 // - 3[0] * x1[13] * x1[8] + - 3 * x1'2 * 'x1 // - 3[0] * x1[12] * x1[9] + - 3 * x1' * x1 // - 3[0] * x1[11] * x1[10] + - 3 * x1 * x1' // - 3[0] * x1[10] * x1[11] + - 3 * 'x1 * x1'2 // - 3[0] * x1[9] * x1[12] + - 3 * 2'x1 * x1'3 // - 3[0] * x1[8] * x1[13] + - 3 * 3'x1 * x1'4 // - 3[0] * x1[7] * x1[14] + - 3 * 4'x1 * x1'5 // - 3[0] * x1[6] * x1[15] + + 0xFFFF * 4'q0 // + p[15] * q0[6] + + 0xFFFF * 3'q0 // + p[14] * q0[7] + + 'q0 // + q0[9] + - 0xFFFF; // - (p*offset)[21] + +// clock #11 + +eq_secp256r1_dbl_chunks[22] = 2 * s'4 * 4'y1 // 2[0] * s[15] * y1[7] + + 2 * s'3 * 3'y1 // + 2[0] * s[14] * y1[8] + + 2 * s'2 * 2'y1 // + 2[0] * s[13] * y1[9] + + 2 * s' * 'y1 // + 2[0] * s[12] * y1[10] + + 2 * s * y1 // + 2[0] * s[11] * y1[11] + + 2 * 's * y1' // + 2[0] * s[10] * y1[12] + + 2 * 2's * y1'2 // + 2[0] * s[9] * y1[13] + + 2 * 3's * y1'3 // + 2[0] * s[8] * y1[14] + + 2 * 4's * y1'4 // + 2[0] * s[7] * y1[15] + - 3 * x1'4 * 4'x1 // - 3[0] * x1[15] * x1[7] + - 3 * x1'3 * 3'x1 // - 3[0] * x1[14] * x1[8] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[13] * x1[9] + - 3 * x1' * 'x1 // - 3[0] * x1[12] * x1[10] + - 3 * x1 * x1 // - 3[0] * x1[11] * x1[11] + - 3 * 'x1 * x1' // - 3[0] * x1[10] * x1[12] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[9] * x1[13] + - 3 * 3'x1 * x1'3 // - 3[0] * x1[8] * x1[14] + - 3 * 4'x1 * x1'4 // - 3[0] * x1[7] * x1[15] + + 0xFFFF * 4'q0 // + p[15] * q0[7] + + 0xFFFF * 3'q0 // + p[14] * q0[8] + + 'q0 // + q0[10] + - 0x3; // - (p*offset)[22] + +eq_secp256r1_dbl_chunks[23] = 2 * s'4 * 3'y1 // 2[0] * s[15] * y1[8] + + 2 * s'3 * 2'y1 // + 2[0] * s[14] * y1[9] + + 2 * s'2 * 'y1 // + 2[0] * s[13] * y1[10] + + 2 * s' * y1 // + 2[0] * s[12] * y1[11] + + 2 * s * y1' // + 2[0] * s[11] * y1[12] + + 2 * 's * y1'2 // + 2[0] * s[10] * y1[13] + + 2 * 2's * y1'3 // + 2[0] * s[9] * y1[14] + + 2 * 3's * y1'4 // + 2[0] * s[8] * y1[15] + - 3 * x1'4 * 3'x1 // - 3[0] * x1[15] * x1[8] + - 3 * x1'3 * 2'x1 // - 3[0] * x1[14] * x1[9] + - 3 * x1'2 * 'x1 // - 3[0] * x1[13] * x1[10] + - 3 * x1' * x1 // - 3[0] * x1[12] * x1[11] + - 3 * x1 * x1' // - 3[0] * x1[11] * x1[12] + - 3 * 'x1 * x1'2 // - 3[0] * x1[10] * x1[13] + - 3 * 2'x1 * x1'3 // - 3[0] * x1[9] * x1[14] + - 3 * 3'x1 * x1'4 // - 3[0] * x1[8] * x1[15] + + 0xFFFF * 3'q0 // + p[15] * q0[8] + + 0xFFFF * 2'q0 // + p[14] * q0[9] + + q0; // + q0[11] + +// clock #12 + +eq_secp256r1_dbl_chunks[24] = 2 * s'3 * 3'y1 // 2[0] * s[15] * y1[9] + + 2 * s'2 * 2'y1 // + 2[0] * s[14] * y1[10] + + 2 * s' * 'y1 // + 2[0] * s[13] * y1[11] + + 2 * s * y1 // + 2[0] * s[12] * y1[12] + + 2 * 's * y1' // + 2[0] * s[11] * y1[13] + + 2 * 2's * y1'2 // + 2[0] * s[10] * y1[14] + + 2 * 3's * y1'3 // + 2[0] * s[9] * y1[15] + - 3 * x1'3 * 3'x1 // - 3[0] * x1[15] * x1[9] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[14] * x1[10] + - 3 * x1' * 'x1 // - 3[0] * x1[13] * x1[11] + - 3 * x1 * x1 // - 3[0] * x1[12] * x1[12] + - 3 * 'x1 * x1' // - 3[0] * x1[11] * x1[13] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[10] * x1[14] + - 3 * 3'x1 * x1'3 // - 3[0] * x1[9] * x1[15] + + 0xFFFF * 3'q0 // + p[15] * q0[9] + + 0xFFFF * 2'q0 // + p[14] * q0[10] + + q0; // + q0[12] + +eq_secp256r1_dbl_chunks[25] = 2 * s'3 * 2'y1 // 2[0] * s[15] * y1[10] + + 2 * s'2 * 'y1 // + 2[0] * s[14] * y1[11] + + 2 * s' * y1 // + 2[0] * s[13] * y1[12] + + 2 * s * y1' // + 2[0] * s[12] * y1[13] + + 2 * 's * y1'2 // + 2[0] * s[11] * y1[14] + + 2 * 2's * y1'3 // + 2[0] * s[10] * y1[15] + - 3 * x1'3 * 2'x1 // - 3[0] * x1[15] * x1[10] + - 3 * x1'2 * 'x1 // - 3[0] * x1[14] * x1[11] + - 3 * x1' * x1 // - 3[0] * x1[13] * x1[12] + - 3 * x1 * x1' // - 3[0] * x1[12] * x1[13] + - 3 * 'x1 * x1'2 // - 3[0] * x1[11] * x1[14] + - 3 * 2'x1 * x1'3 // - 3[0] * x1[10] * x1[15] + + 0xFFFF * 2'q0 // + p[15] * q0[10] + + 0xFFFF * 'q0 // + p[14] * q0[11] + + q0'; // + q0[13] + +// clock #13 + +eq_secp256r1_dbl_chunks[26] = 2 * s'2 * 2'y1 // 2[0] * s[15] * y1[11] + + 2 * s' * 'y1 // + 2[0] * s[14] * y1[12] + + 2 * s * y1 // + 2[0] * s[13] * y1[13] + + 2 * 's * y1' // + 2[0] * s[12] * y1[14] + + 2 * 2's * y1'2 // + 2[0] * s[11] * y1[15] + - 3 * x1'2 * 2'x1 // - 3[0] * x1[15] * x1[11] + - 3 * x1' * 'x1 // - 3[0] * x1[14] * x1[12] + - 3 * x1 * x1 // - 3[0] * x1[13] * x1[13] + - 3 * 'x1 * x1' // - 3[0] * x1[12] * x1[14] + - 3 * 2'x1 * x1'2 // - 3[0] * x1[11] * x1[15] + + 0xFFFF * 2'q0 // + p[15] * q0[11] + + 0xFFFF * 'q0 // + p[14] * q0[12] + + q0'; // + q0[14] + +eq_secp256r1_dbl_chunks[27] = 2 * s'2 * 'y1 // 2[0] * s[15] * y1[12] + + 2 * s' * y1 // + 2[0] * s[14] * y1[13] + + 2 * s * y1' // + 2[0] * s[13] * y1[14] + + 2 * 's * y1'2 // + 2[0] * s[12] * y1[15] + - 3 * x1'2 * 'x1 // - 3[0] * x1[15] * x1[12] + - 3 * x1' * x1 // - 3[0] * x1[14] * x1[13] + - 3 * x1 * x1' // - 3[0] * x1[13] * x1[14] + - 3 * 'x1 * x1'2 // - 3[0] * x1[12] * x1[15] + + 0xFFFF * 'q0 // + p[15] * q0[12] + + 0xFFFF * q0 // + p[14] * q0[13] + + q0'2; // + q0[15] + +// clock #14 + +eq_secp256r1_dbl_chunks[28] = 2 * s' * 'y1 // 2[0] * s[15] * y1[13] + + 2 * s * y1 // + 2[0] * s[14] * y1[14] + + 2 * 's * y1' // + 2[0] * s[13] * y1[15] + - 3 * x1' * 'x1 // - 3[0] * x1[15] * x1[13] + - 3 * x1 * x1 // - 3[0] * x1[14] * x1[14] + - 3 * 'x1 * x1' // - 3[0] * x1[13] * x1[15] + + 0xFFFF * 'q0 // + p[15] * q0[13] + + 0xFFFF * q0 // + p[14] * q0[14] + - 0x4; // - (p*offset)[28] + +eq_secp256r1_dbl_chunks[29] = 2 * s' * y1 // 2[0] * s[15] * y1[14] + + 2 * s * y1' // + 2[0] * s[14] * y1[15] + - 3 * x1' * x1 // - 3[0] * x1[15] * x1[14] + - 3 * x1 * x1' // - 3[0] * x1[14] * x1[15] + + 0xFFFF * q0 // + p[15] * q0[14] + + 0xFFFF * q0'; // + p[14] * q0[15] + +// clock #15 + +eq_secp256r1_dbl_chunks[30] = 2 * s * y1 // 2[0] * s[15] * y1[15] + - 3 * x1 * x1 // - 3[0] * x1[15] * x1[15] + + 0xFFFF * q0 // + p[15] * q0[15] + - 0xFFFC; // - (p*offset)[30] + +eq_secp256r1_dbl_chunks[31] = - 0x3FFFF; // - (p*offset)[31] + diff --git a/precompiles/arith_eq/pil/equations/secp256r1_x3.pil b/precompiles/arith_eq/pil/equations/secp256r1_x3.pil new file mode 100644 index 000000000..8a5baf6ad --- /dev/null +++ b/precompiles/arith_eq/pil/equations/secp256r1_x3.pil @@ -0,0 +1,537 @@ +// code generated +// +// equation: s*s-x1-x2-x3-p*q1+p*offset +// +// p: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF +// offset: 0x4 +// (p*offset): 0x3FFFFFFFC00000004000000000000000000000003FFFFFFFFFFFFFFFFFFFFFFFC +// +// chunks:16 +// chunk_bits:16 +// terms_by_clock: 2 + + +const expr eq_secp256r1_x3_chunks[31]; + +// clock #0 + +eq_secp256r1_x3_chunks[ 0] = s * s // s[0] * s[0] + - x1 // - x1[0] + - x2 // - x2[0] + - x3 // - x3[0] + - 0xFFFF * q1 // - p[0] * q1[0] + + 0xFFFC; // + (p*offset)[0] + +eq_secp256r1_x3_chunks[ 1] = s' * s // s[1] * s[0] + + s * s' // + s[0] * s[1] + - x1' // - x1[1] + - x2' // - x2[1] + - x3' // - x3[1] + - 0xFFFF * q1 // - p[1] * q1[0] + - 0xFFFF * q1' // - p[0] * q1[1] + + 0xFFFF; // + (p*offset)[1] + +// clock #1 + +eq_secp256r1_x3_chunks[ 2] = s' * 's // s[2] * s[0] + + s * s // + s[1] * s[1] + + 's * s' // + s[0] * s[2] + - x1' // - x1[2] + - x2' // - x2[2] + - x3' // - x3[2] + - 0xFFFF * 'q1 // - p[2] * q1[0] + - 0xFFFF * q1 // - p[1] * q1[1] + - 0xFFFF * q1' // - p[0] * q1[2] + + 0xFFFF; // + (p*offset)[2] + +eq_secp256r1_x3_chunks[ 3] = s'2 * 's // s[3] * s[0] + + s' * s // + s[2] * s[1] + + s * s' // + s[1] * s[2] + + 's * s'2 // + s[0] * s[3] + - x1'2 // - x1[3] + - x2'2 // - x2[3] + - x3'2 // - x3[3] + - 0xFFFF * 'q1 // - p[3] * q1[0] + - 0xFFFF * q1 // - p[2] * q1[1] + - 0xFFFF * q1' // - p[1] * q1[2] + - 0xFFFF * q1'2 // - p[0] * q1[3] + + 0xFFFF; // + (p*offset)[3] + +// clock #2 + +eq_secp256r1_x3_chunks[ 4] = s'2 * 2's // s[4] * s[0] + + s' * 's // + s[3] * s[1] + + s * s // + s[2] * s[2] + + 's * s' // + s[1] * s[3] + + 2's * s'2 // + s[0] * s[4] + - x1'2 // - x1[4] + - x2'2 // - x2[4] + - x3'2 // - x3[4] + - 0xFFFF * 2'q1 // - p[4] * q1[0] + - 0xFFFF * 'q1 // - p[3] * q1[1] + - 0xFFFF * q1 // - p[2] * q1[2] + - 0xFFFF * q1' // - p[1] * q1[3] + - 0xFFFF * q1'2 // - p[0] * q1[4] + + 0xFFFF; // + (p*offset)[4] + +eq_secp256r1_x3_chunks[ 5] = s'3 * 2's // s[5] * s[0] + + s'2 * 's // + s[4] * s[1] + + s' * s // + s[3] * s[2] + + s * s' // + s[2] * s[3] + + 's * s'2 // + s[1] * s[4] + + 2's * s'3 // + s[0] * s[5] + - x1'3 // - x1[5] + - x2'3 // - x2[5] + - x3'3 // - x3[5] + - 0xFFFF * 2'q1 // - p[5] * q1[0] + - 0xFFFF * 'q1 // - p[4] * q1[1] + - 0xFFFF * q1 // - p[3] * q1[2] + - 0xFFFF * q1' // - p[2] * q1[3] + - 0xFFFF * q1'2 // - p[1] * q1[4] + - 0xFFFF * q1'3 // - p[0] * q1[5] + + 0xFFFF; // + (p*offset)[5] + +// clock #3 + +eq_secp256r1_x3_chunks[ 6] = s'3 * 3's // s[6] * s[0] + + s'2 * 2's // + s[5] * s[1] + + s' * 's // + s[4] * s[2] + + s * s // + s[3] * s[3] + + 's * s' // + s[2] * s[4] + + 2's * s'2 // + s[1] * s[5] + + 3's * s'3 // + s[0] * s[6] + - x1'3 // - x1[6] + - x2'3 // - x2[6] + - x3'3 // - x3[6] + - 0xFFFF * 2'q1 // - p[5] * q1[1] + - 0xFFFF * 'q1 // - p[4] * q1[2] + - 0xFFFF * q1 // - p[3] * q1[3] + - 0xFFFF * q1' // - p[2] * q1[4] + - 0xFFFF * q1'2 // - p[1] * q1[5] + - 0xFFFF * q1'3 // - p[0] * q1[6] + + 0x3; // + (p*offset)[6] + +eq_secp256r1_x3_chunks[ 7] = s'4 * 3's // s[7] * s[0] + + s'3 * 2's // + s[6] * s[1] + + s'2 * 's // + s[5] * s[2] + + s' * s // + s[4] * s[3] + + s * s' // + s[3] * s[4] + + 's * s'2 // + s[2] * s[5] + + 2's * s'3 // + s[1] * s[6] + + 3's * s'4 // + s[0] * s[7] + - x1'4 // - x1[7] + - x2'4 // - x2[7] + - x3'4 // - x3[7] + - 0xFFFF * 'q1 // - p[5] * q1[2] + - 0xFFFF * q1 // - p[4] * q1[3] + - 0xFFFF * q1' // - p[3] * q1[4] + - 0xFFFF * q1'2 // - p[2] * q1[5] + - 0xFFFF * q1'3 // - p[1] * q1[6] + - 0xFFFF * q1'4; // - p[0] * q1[7] + +// clock #4 + +eq_secp256r1_x3_chunks[ 8] = s'4 * 4's // s[8] * s[0] + + s'3 * 3's // + s[7] * s[1] + + s'2 * 2's // + s[6] * s[2] + + s' * 's // + s[5] * s[3] + + s * s // + s[4] * s[4] + + 's * s' // + s[3] * s[5] + + 2's * s'2 // + s[2] * s[6] + + 3's * s'3 // + s[1] * s[7] + + 4's * s'4 // + s[0] * s[8] + - x1'4 // - x1[8] + - x2'4 // - x2[8] + - x3'4 // - x3[8] + - 0xFFFF * 'q1 // - p[5] * q1[3] + - 0xFFFF * q1 // - p[4] * q1[4] + - 0xFFFF * q1' // - p[3] * q1[5] + - 0xFFFF * q1'2 // - p[2] * q1[6] + - 0xFFFF * q1'3 // - p[1] * q1[7] + - 0xFFFF * q1'4; // - p[0] * q1[8] + +eq_secp256r1_x3_chunks[ 9] = s'5 * 4's // s[9] * s[0] + + s'4 * 3's // + s[8] * s[1] + + s'3 * 2's // + s[7] * s[2] + + s'2 * 's // + s[6] * s[3] + + s' * s // + s[5] * s[4] + + s * s' // + s[4] * s[5] + + 's * s'2 // + s[3] * s[6] + + 2's * s'3 // + s[2] * s[7] + + 3's * s'4 // + s[1] * s[8] + + 4's * s'5 // + s[0] * s[9] + - x1'5 // - x1[9] + - x2'5 // - x2[9] + - x3'5 // - x3[9] + - 0xFFFF * q1 // - p[5] * q1[4] + - 0xFFFF * q1' // - p[4] * q1[5] + - 0xFFFF * q1'2 // - p[3] * q1[6] + - 0xFFFF * q1'3 // - p[2] * q1[7] + - 0xFFFF * q1'4 // - p[1] * q1[8] + - 0xFFFF * q1'5; // - p[0] * q1[9] + +// clock #5 + +eq_secp256r1_x3_chunks[10] = s'5 * 5's // s[10] * s[0] + + s'4 * 4's // + s[9] * s[1] + + s'3 * 3's // + s[8] * s[2] + + s'2 * 2's // + s[7] * s[3] + + s' * 's // + s[6] * s[4] + + s * s // + s[5] * s[5] + + 's * s' // + s[4] * s[6] + + 2's * s'2 // + s[3] * s[7] + + 3's * s'3 // + s[2] * s[8] + + 4's * s'4 // + s[1] * s[9] + + 5's * s'5 // + s[0] * s[10] + - x1'5 // - x1[10] + - x2'5 // - x2[10] + - x3'5 // - x3[10] + - 0xFFFF * q1 // - p[5] * q1[5] + - 0xFFFF * q1' // - p[4] * q1[6] + - 0xFFFF * q1'2 // - p[3] * q1[7] + - 0xFFFF * q1'3 // - p[2] * q1[8] + - 0xFFFF * q1'4 // - p[1] * q1[9] + - 0xFFFF * q1'5; // - p[0] * q1[10] + +eq_secp256r1_x3_chunks[11] = s'6 * 5's // s[11] * s[0] + + s'5 * 4's // + s[10] * s[1] + + s'4 * 3's // + s[9] * s[2] + + s'3 * 2's // + s[8] * s[3] + + s'2 * 's // + s[7] * s[4] + + s' * s // + s[6] * s[5] + + s * s' // + s[5] * s[6] + + 's * s'2 // + s[4] * s[7] + + 2's * s'3 // + s[3] * s[8] + + 3's * s'4 // + s[2] * s[9] + + 4's * s'5 // + s[1] * s[10] + + 5's * s'6 // + s[0] * s[11] + - x1'6 // - x1[11] + - x2'6 // - x2[11] + - x3'6 // - x3[11] + - 0xFFFF * q1' // - p[5] * q1[6] + - 0xFFFF * q1'2 // - p[4] * q1[7] + - 0xFFFF * q1'3 // - p[3] * q1[8] + - 0xFFFF * q1'4 // - p[2] * q1[9] + - 0xFFFF * q1'5 // - p[1] * q1[10] + - 0xFFFF * q1'6; // - p[0] * q1[11] + +// clock #6 + +eq_secp256r1_x3_chunks[12] = s'6 * 6's // s[12] * s[0] + + s'5 * 5's // + s[11] * s[1] + + s'4 * 4's // + s[10] * s[2] + + s'3 * 3's // + s[9] * s[3] + + s'2 * 2's // + s[8] * s[4] + + s' * 's // + s[7] * s[5] + + s * s // + s[6] * s[6] + + 's * s' // + s[5] * s[7] + + 2's * s'2 // + s[4] * s[8] + + 3's * s'3 // + s[3] * s[9] + + 4's * s'4 // + s[2] * s[10] + + 5's * s'5 // + s[1] * s[11] + + 6's * s'6 // + s[0] * s[12] + - x1'6 // - x1[12] + - x2'6 // - x2[12] + - x3'6 // - x3[12] + - 6'q1 // - q1[0] + - 0xFFFF * q1' // - p[5] * q1[7] + - 0xFFFF * q1'2 // - p[4] * q1[8] + - 0xFFFF * q1'3 // - p[3] * q1[9] + - 0xFFFF * q1'4 // - p[2] * q1[10] + - 0xFFFF * q1'5 // - p[1] * q1[11] + - 0xFFFF * q1'6 // - p[0] * q1[12] + + 0x4; // + (p*offset)[12] + +eq_secp256r1_x3_chunks[13] = s'7 * 6's // s[13] * s[0] + + s'6 * 5's // + s[12] * s[1] + + s'5 * 4's // + s[11] * s[2] + + s'4 * 3's // + s[10] * s[3] + + s'3 * 2's // + s[9] * s[4] + + s'2 * 's // + s[8] * s[5] + + s' * s // + s[7] * s[6] + + s * s' // + s[6] * s[7] + + 's * s'2 // + s[5] * s[8] + + 2's * s'3 // + s[4] * s[9] + + 3's * s'4 // + s[3] * s[10] + + 4's * s'5 // + s[2] * s[11] + + 5's * s'6 // + s[1] * s[12] + + 6's * s'7 // + s[0] * s[13] + - x1'7 // - x1[13] + - x2'7 // - x2[13] + - x3'7 // - x3[13] + - 5'q1 // - q1[1] + - 0xFFFF * q1'2 // - p[5] * q1[8] + - 0xFFFF * q1'3 // - p[4] * q1[9] + - 0xFFFF * q1'4 // - p[3] * q1[10] + - 0xFFFF * q1'5 // - p[2] * q1[11] + - 0xFFFF * q1'6 // - p[1] * q1[12] + - 0xFFFF * q1'7; // - p[0] * q1[13] + +// clock #7 + +eq_secp256r1_x3_chunks[14] = s'7 * 7's // s[14] * s[0] + + s'6 * 6's // + s[13] * s[1] + + s'5 * 5's // + s[12] * s[2] + + s'4 * 4's // + s[11] * s[3] + + s'3 * 3's // + s[10] * s[4] + + s'2 * 2's // + s[9] * s[5] + + s' * 's // + s[8] * s[6] + + s * s // + s[7] * s[7] + + 's * s' // + s[6] * s[8] + + 2's * s'2 // + s[5] * s[9] + + 3's * s'3 // + s[4] * s[10] + + 4's * s'4 // + s[3] * s[11] + + 5's * s'5 // + s[2] * s[12] + + 6's * s'6 // + s[1] * s[13] + + 7's * s'7 // + s[0] * s[14] + - x1'7 // - x1[14] + - x2'7 // - x2[14] + - x3'7 // - x3[14] + - 0xFFFF * 7'q1 // - p[14] * q1[0] + - 5'q1 // - q1[2] + - 0xFFFF * q1'2 // - p[5] * q1[9] + - 0xFFFF * q1'3 // - p[4] * q1[10] + - 0xFFFF * q1'4 // - p[3] * q1[11] + - 0xFFFF * q1'5 // - p[2] * q1[12] + - 0xFFFF * q1'6 // - p[1] * q1[13] + - 0xFFFF * q1'7 // - p[0] * q1[14] + + 0xFFFC; // + (p*offset)[14] + +eq_secp256r1_x3_chunks[15] = s'8 * 7's // s[15] * s[0] + + s'7 * 6's // + s[14] * s[1] + + s'6 * 5's // + s[13] * s[2] + + s'5 * 4's // + s[12] * s[3] + + s'4 * 3's // + s[11] * s[4] + + s'3 * 2's // + s[10] * s[5] + + s'2 * 's // + s[9] * s[6] + + s' * s // + s[8] * s[7] + + s * s' // + s[7] * s[8] + + 's * s'2 // + s[6] * s[9] + + 2's * s'3 // + s[5] * s[10] + + 3's * s'4 // + s[4] * s[11] + + 4's * s'5 // + s[3] * s[12] + + 5's * s'6 // + s[2] * s[13] + + 6's * s'7 // + s[1] * s[14] + + 7's * s'8 // + s[0] * s[15] + - x1'8 // - x1[15] + - x2'8 // - x2[15] + - x3'8 // - x3[15] + - 0xFFFF * 7'q1 // - p[15] * q1[0] + - 0xFFFF * 6'q1 // - p[14] * q1[1] + - 4'q1 // - q1[3] + - 0xFFFF * q1'3 // - p[5] * q1[10] + - 0xFFFF * q1'4 // - p[4] * q1[11] + - 0xFFFF * q1'5 // - p[3] * q1[12] + - 0xFFFF * q1'6 // - p[2] * q1[13] + - 0xFFFF * q1'7 // - p[1] * q1[14] + - 0xFFFF * q1'8 // - p[0] * q1[15] + + 0xFFFF; // + (p*offset)[15] + +// clock #8 + +eq_secp256r1_x3_chunks[16] = s'7 * 7's // s[15] * s[1] + + s'6 * 6's // + s[14] * s[2] + + s'5 * 5's // + s[13] * s[3] + + s'4 * 4's // + s[12] * s[4] + + s'3 * 3's // + s[11] * s[5] + + s'2 * 2's // + s[10] * s[6] + + s' * 's // + s[9] * s[7] + + s * s // + s[8] * s[8] + + 's * s' // + s[7] * s[9] + + 2's * s'2 // + s[6] * s[10] + + 3's * s'3 // + s[5] * s[11] + + 4's * s'4 // + s[4] * s[12] + + 5's * s'5 // + s[3] * s[13] + + 6's * s'6 // + s[2] * s[14] + + 7's * s'7 // + s[1] * s[15] + - 0xFFFF * 7'q1 // - p[15] * q1[1] + - 0xFFFF * 6'q1 // - p[14] * q1[2] + - 4'q1 // - q1[4] + - 0xFFFF * q1'3 // - p[5] * q1[11] + - 0xFFFF * q1'4 // - p[4] * q1[12] + - 0xFFFF * q1'5 // - p[3] * q1[13] + - 0xFFFF * q1'6 // - p[2] * q1[14] + - 0xFFFF * q1'7 // - p[1] * q1[15] + + 0x3; // + (p*offset)[16] + +eq_secp256r1_x3_chunks[17] = s'7 * 6's // s[15] * s[2] + + s'6 * 5's // + s[14] * s[3] + + s'5 * 4's // + s[13] * s[4] + + s'4 * 3's // + s[12] * s[5] + + s'3 * 2's // + s[11] * s[6] + + s'2 * 's // + s[10] * s[7] + + s' * s // + s[9] * s[8] + + s * s' // + s[8] * s[9] + + 's * s'2 // + s[7] * s[10] + + 2's * s'3 // + s[6] * s[11] + + 3's * s'4 // + s[5] * s[12] + + 4's * s'5 // + s[4] * s[13] + + 5's * s'6 // + s[3] * s[14] + + 6's * s'7 // + s[2] * s[15] + - 0xFFFF * 6'q1 // - p[15] * q1[2] + - 0xFFFF * 5'q1 // - p[14] * q1[3] + - 3'q1 // - q1[5] + - 0xFFFF * q1'4 // - p[5] * q1[12] + - 0xFFFF * q1'5 // - p[4] * q1[13] + - 0xFFFF * q1'6 // - p[3] * q1[14] + - 0xFFFF * q1'7; // - p[2] * q1[15] + +// clock #9 + +eq_secp256r1_x3_chunks[18] = s'6 * 6's // s[15] * s[3] + + s'5 * 5's // + s[14] * s[4] + + s'4 * 4's // + s[13] * s[5] + + s'3 * 3's // + s[12] * s[6] + + s'2 * 2's // + s[11] * s[7] + + s' * 's // + s[10] * s[8] + + s * s // + s[9] * s[9] + + 's * s' // + s[8] * s[10] + + 2's * s'2 // + s[7] * s[11] + + 3's * s'3 // + s[6] * s[12] + + 4's * s'4 // + s[5] * s[13] + + 5's * s'5 // + s[4] * s[14] + + 6's * s'6 // + s[3] * s[15] + - 0xFFFF * 6'q1 // - p[15] * q1[3] + - 0xFFFF * 5'q1 // - p[14] * q1[4] + - 3'q1 // - q1[6] + - 0xFFFF * q1'4 // - p[5] * q1[13] + - 0xFFFF * q1'5 // - p[4] * q1[14] + - 0xFFFF * q1'6; // - p[3] * q1[15] + +eq_secp256r1_x3_chunks[19] = s'6 * 5's // s[15] * s[4] + + s'5 * 4's // + s[14] * s[5] + + s'4 * 3's // + s[13] * s[6] + + s'3 * 2's // + s[12] * s[7] + + s'2 * 's // + s[11] * s[8] + + s' * s // + s[10] * s[9] + + s * s' // + s[9] * s[10] + + 's * s'2 // + s[8] * s[11] + + 2's * s'3 // + s[7] * s[12] + + 3's * s'4 // + s[6] * s[13] + + 4's * s'5 // + s[5] * s[14] + + 5's * s'6 // + s[4] * s[15] + - 0xFFFF * 5'q1 // - p[15] * q1[4] + - 0xFFFF * 4'q1 // - p[14] * q1[5] + - 2'q1 // - q1[7] + - 0xFFFF * q1'5 // - p[5] * q1[14] + - 0xFFFF * q1'6; // - p[4] * q1[15] + +// clock #10 + +eq_secp256r1_x3_chunks[20] = s'5 * 5's // s[15] * s[5] + + s'4 * 4's // + s[14] * s[6] + + s'3 * 3's // + s[13] * s[7] + + s'2 * 2's // + s[12] * s[8] + + s' * 's // + s[11] * s[9] + + s * s // + s[10] * s[10] + + 's * s' // + s[9] * s[11] + + 2's * s'2 // + s[8] * s[12] + + 3's * s'3 // + s[7] * s[13] + + 4's * s'4 // + s[6] * s[14] + + 5's * s'5 // + s[5] * s[15] + - 0xFFFF * 5'q1 // - p[15] * q1[5] + - 0xFFFF * 4'q1 // - p[14] * q1[6] + - 2'q1 // - q1[8] + - 0xFFFF * q1'5; // - p[5] * q1[15] + +eq_secp256r1_x3_chunks[21] = s'5 * 4's // s[15] * s[6] + + s'4 * 3's // + s[14] * s[7] + + s'3 * 2's // + s[13] * s[8] + + s'2 * 's // + s[12] * s[9] + + s' * s // + s[11] * s[10] + + s * s' // + s[10] * s[11] + + 's * s'2 // + s[9] * s[12] + + 2's * s'3 // + s[8] * s[13] + + 3's * s'4 // + s[7] * s[14] + + 4's * s'5 // + s[6] * s[15] + - 0xFFFF * 4'q1 // - p[15] * q1[6] + - 0xFFFF * 3'q1 // - p[14] * q1[7] + - 'q1; // - q1[9] + +// clock #11 + +eq_secp256r1_x3_chunks[22] = s'4 * 4's // s[15] * s[7] + + s'3 * 3's // + s[14] * s[8] + + s'2 * 2's // + s[13] * s[9] + + s' * 's // + s[12] * s[10] + + s * s // + s[11] * s[11] + + 's * s' // + s[10] * s[12] + + 2's * s'2 // + s[9] * s[13] + + 3's * s'3 // + s[8] * s[14] + + 4's * s'4 // + s[7] * s[15] + - 0xFFFF * 4'q1 // - p[15] * q1[7] + - 0xFFFF * 3'q1 // - p[14] * q1[8] + - 'q1; // - q1[10] + +eq_secp256r1_x3_chunks[23] = s'4 * 3's // s[15] * s[8] + + s'3 * 2's // + s[14] * s[9] + + s'2 * 's // + s[13] * s[10] + + s' * s // + s[12] * s[11] + + s * s' // + s[11] * s[12] + + 's * s'2 // + s[10] * s[13] + + 2's * s'3 // + s[9] * s[14] + + 3's * s'4 // + s[8] * s[15] + - 0xFFFF * 3'q1 // - p[15] * q1[8] + - 0xFFFF * 2'q1 // - p[14] * q1[9] + - q1; // - q1[11] + +// clock #12 + +eq_secp256r1_x3_chunks[24] = s'3 * 3's // s[15] * s[9] + + s'2 * 2's // + s[14] * s[10] + + s' * 's // + s[13] * s[11] + + s * s // + s[12] * s[12] + + 's * s' // + s[11] * s[13] + + 2's * s'2 // + s[10] * s[14] + + 3's * s'3 // + s[9] * s[15] + - 0xFFFF * 3'q1 // - p[15] * q1[9] + - 0xFFFF * 2'q1 // - p[14] * q1[10] + - q1; // - q1[12] + +eq_secp256r1_x3_chunks[25] = s'3 * 2's // s[15] * s[10] + + s'2 * 's // + s[14] * s[11] + + s' * s // + s[13] * s[12] + + s * s' // + s[12] * s[13] + + 's * s'2 // + s[11] * s[14] + + 2's * s'3 // + s[10] * s[15] + - 0xFFFF * 2'q1 // - p[15] * q1[10] + - 0xFFFF * 'q1 // - p[14] * q1[11] + - q1'; // - q1[13] + +// clock #13 + +eq_secp256r1_x3_chunks[26] = s'2 * 2's // s[15] * s[11] + + s' * 's // + s[14] * s[12] + + s * s // + s[13] * s[13] + + 's * s' // + s[12] * s[14] + + 2's * s'2 // + s[11] * s[15] + - 0xFFFF * 2'q1 // - p[15] * q1[11] + - 0xFFFF * 'q1 // - p[14] * q1[12] + - q1'; // - q1[14] + +eq_secp256r1_x3_chunks[27] = s'2 * 's // s[15] * s[12] + + s' * s // + s[14] * s[13] + + s * s' // + s[13] * s[14] + + 's * s'2 // + s[12] * s[15] + - 0xFFFF * 'q1 // - p[15] * q1[12] + - 0xFFFF * q1 // - p[14] * q1[13] + - q1'2; // - q1[15] + +// clock #14 + +eq_secp256r1_x3_chunks[28] = s' * 's // s[15] * s[13] + + s * s // + s[14] * s[14] + + 's * s' // + s[13] * s[15] + - 0xFFFF * 'q1 // - p[15] * q1[13] + - 0xFFFF * q1; // - p[14] * q1[14] + +eq_secp256r1_x3_chunks[29] = s' * s // s[15] * s[14] + + s * s' // + s[14] * s[15] + - 0xFFFF * q1 // - p[15] * q1[14] + - 0xFFFF * q1'; // - p[14] * q1[15] + +// clock #15 + +eq_secp256r1_x3_chunks[30] = s * s // s[15] * s[15] + - 0xFFFF * q1; // - p[15] * q1[15] + diff --git a/precompiles/arith_eq/pil/equations/secp256r1_y3.pil b/precompiles/arith_eq/pil/equations/secp256r1_y3.pil new file mode 100644 index 000000000..a5e356d34 --- /dev/null +++ b/precompiles/arith_eq/pil/equations/secp256r1_y3.pil @@ -0,0 +1,777 @@ +// code generated +// +// equation: s*x1-s*x3-y1-y3+p*q2-p*offset +// +// p: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF +// offset: 0x20000000000000000000000000000000000000000000000000000000000000000 +// (p*offset): 0x1FFFFFFFE00000002000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFE0000000000000000000000000000000000000000000000000000000000000000 +// +// chunks:16 +// chunk_bits:16 +// terms_by_clock: 2 + + +const expr eq_secp256r1_y3_chunks[32]; + +// clock #0 + +eq_secp256r1_y3_chunks[ 0] = s * x1 // s[0] * x1[0] + - s * x3 // - s[0] * x3[0] + - y1 // - y1[0] + - y3 // - y3[0] + + 0xFFFF * q2; // + p[0] * q2[0] + +eq_secp256r1_y3_chunks[ 1] = s' * x1 // s[1] * x1[0] + + s * x1' // + s[0] * x1[1] + - s' * x3 // - s[1] * x3[0] + - s * x3' // - s[0] * x3[1] + - y1' // - y1[1] + - y3' // - y3[1] + + 0xFFFF * q2 // + p[1] * q2[0] + + 0xFFFF * q2'; // + p[0] * q2[1] + +// clock #1 + +eq_secp256r1_y3_chunks[ 2] = s' * 'x1 // s[2] * x1[0] + + s * x1 // + s[1] * x1[1] + + 's * x1' // + s[0] * x1[2] + - s' * 'x3 // - s[2] * x3[0] + - s * x3 // - s[1] * x3[1] + - 's * x3' // - s[0] * x3[2] + - y1' // - y1[2] + - y3' // - y3[2] + + 0xFFFF * 'q2 // + p[2] * q2[0] + + 0xFFFF * q2 // + p[1] * q2[1] + + 0xFFFF * q2'; // + p[0] * q2[2] + +eq_secp256r1_y3_chunks[ 3] = s'2 * 'x1 // s[3] * x1[0] + + s' * x1 // + s[2] * x1[1] + + s * x1' // + s[1] * x1[2] + + 's * x1'2 // + s[0] * x1[3] + - s'2 * 'x3 // - s[3] * x3[0] + - s' * x3 // - s[2] * x3[1] + - s * x3' // - s[1] * x3[2] + - 's * x3'2 // - s[0] * x3[3] + - y1'2 // - y1[3] + - y3'2 // - y3[3] + + 0xFFFF * 'q2 // + p[3] * q2[0] + + 0xFFFF * q2 // + p[2] * q2[1] + + 0xFFFF * q2' // + p[1] * q2[2] + + 0xFFFF * q2'2; // + p[0] * q2[3] + +// clock #2 + +eq_secp256r1_y3_chunks[ 4] = s'2 * 2'x1 // s[4] * x1[0] + + s' * 'x1 // + s[3] * x1[1] + + s * x1 // + s[2] * x1[2] + + 's * x1' // + s[1] * x1[3] + + 2's * x1'2 // + s[0] * x1[4] + - s'2 * 2'x3 // - s[4] * x3[0] + - s' * 'x3 // - s[3] * x3[1] + - s * x3 // - s[2] * x3[2] + - 's * x3' // - s[1] * x3[3] + - 2's * x3'2 // - s[0] * x3[4] + - y1'2 // - y1[4] + - y3'2 // - y3[4] + + 0xFFFF * 2'q2 // + p[4] * q2[0] + + 0xFFFF * 'q2 // + p[3] * q2[1] + + 0xFFFF * q2 // + p[2] * q2[2] + + 0xFFFF * q2' // + p[1] * q2[3] + + 0xFFFF * q2'2; // + p[0] * q2[4] + +eq_secp256r1_y3_chunks[ 5] = s'3 * 2'x1 // s[5] * x1[0] + + s'2 * 'x1 // + s[4] * x1[1] + + s' * x1 // + s[3] * x1[2] + + s * x1' // + s[2] * x1[3] + + 's * x1'2 // + s[1] * x1[4] + + 2's * x1'3 // + s[0] * x1[5] + - s'3 * 2'x3 // - s[5] * x3[0] + - s'2 * 'x3 // - s[4] * x3[1] + - s' * x3 // - s[3] * x3[2] + - s * x3' // - s[2] * x3[3] + - 's * x3'2 // - s[1] * x3[4] + - 2's * x3'3 // - s[0] * x3[5] + - y1'3 // - y1[5] + - y3'3 // - y3[5] + + 0xFFFF * 2'q2 // + p[5] * q2[0] + + 0xFFFF * 'q2 // + p[4] * q2[1] + + 0xFFFF * q2 // + p[3] * q2[2] + + 0xFFFF * q2' // + p[2] * q2[3] + + 0xFFFF * q2'2 // + p[1] * q2[4] + + 0xFFFF * q2'3; // + p[0] * q2[5] + +// clock #3 + +eq_secp256r1_y3_chunks[ 6] = s'3 * 3'x1 // s[6] * x1[0] + + s'2 * 2'x1 // + s[5] * x1[1] + + s' * 'x1 // + s[4] * x1[2] + + s * x1 // + s[3] * x1[3] + + 's * x1' // + s[2] * x1[4] + + 2's * x1'2 // + s[1] * x1[5] + + 3's * x1'3 // + s[0] * x1[6] + - s'3 * 3'x3 // - s[6] * x3[0] + - s'2 * 2'x3 // - s[5] * x3[1] + - s' * 'x3 // - s[4] * x3[2] + - s * x3 // - s[3] * x3[3] + - 's * x3' // - s[2] * x3[4] + - 2's * x3'2 // - s[1] * x3[5] + - 3's * x3'3 // - s[0] * x3[6] + - y1'3 // - y1[6] + - y3'3 // - y3[6] + + 0xFFFF * 2'q2 // + p[5] * q2[1] + + 0xFFFF * 'q2 // + p[4] * q2[2] + + 0xFFFF * q2 // + p[3] * q2[3] + + 0xFFFF * q2' // + p[2] * q2[4] + + 0xFFFF * q2'2 // + p[1] * q2[5] + + 0xFFFF * q2'3; // + p[0] * q2[6] + +eq_secp256r1_y3_chunks[ 7] = s'4 * 3'x1 // s[7] * x1[0] + + s'3 * 2'x1 // + s[6] * x1[1] + + s'2 * 'x1 // + s[5] * x1[2] + + s' * x1 // + s[4] * x1[3] + + s * x1' // + s[3] * x1[4] + + 's * x1'2 // + s[2] * x1[5] + + 2's * x1'3 // + s[1] * x1[6] + + 3's * x1'4 // + s[0] * x1[7] + - s'4 * 3'x3 // - s[7] * x3[0] + - s'3 * 2'x3 // - s[6] * x3[1] + - s'2 * 'x3 // - s[5] * x3[2] + - s' * x3 // - s[4] * x3[3] + - s * x3' // - s[3] * x3[4] + - 's * x3'2 // - s[2] * x3[5] + - 2's * x3'3 // - s[1] * x3[6] + - 3's * x3'4 // - s[0] * x3[7] + - y1'4 // - y1[7] + - y3'4 // - y3[7] + + 0xFFFF * 'q2 // + p[5] * q2[2] + + 0xFFFF * q2 // + p[4] * q2[3] + + 0xFFFF * q2' // + p[3] * q2[4] + + 0xFFFF * q2'2 // + p[2] * q2[5] + + 0xFFFF * q2'3 // + p[1] * q2[6] + + 0xFFFF * q2'4; // + p[0] * q2[7] + +// clock #4 + +eq_secp256r1_y3_chunks[ 8] = s'4 * 4'x1 // s[8] * x1[0] + + s'3 * 3'x1 // + s[7] * x1[1] + + s'2 * 2'x1 // + s[6] * x1[2] + + s' * 'x1 // + s[5] * x1[3] + + s * x1 // + s[4] * x1[4] + + 's * x1' // + s[3] * x1[5] + + 2's * x1'2 // + s[2] * x1[6] + + 3's * x1'3 // + s[1] * x1[7] + + 4's * x1'4 // + s[0] * x1[8] + - s'4 * 4'x3 // - s[8] * x3[0] + - s'3 * 3'x3 // - s[7] * x3[1] + - s'2 * 2'x3 // - s[6] * x3[2] + - s' * 'x3 // - s[5] * x3[3] + - s * x3 // - s[4] * x3[4] + - 's * x3' // - s[3] * x3[5] + - 2's * x3'2 // - s[2] * x3[6] + - 3's * x3'3 // - s[1] * x3[7] + - 4's * x3'4 // - s[0] * x3[8] + - y1'4 // - y1[8] + - y3'4 // - y3[8] + + 0xFFFF * 'q2 // + p[5] * q2[3] + + 0xFFFF * q2 // + p[4] * q2[4] + + 0xFFFF * q2' // + p[3] * q2[5] + + 0xFFFF * q2'2 // + p[2] * q2[6] + + 0xFFFF * q2'3 // + p[1] * q2[7] + + 0xFFFF * q2'4; // + p[0] * q2[8] + +eq_secp256r1_y3_chunks[ 9] = s'5 * 4'x1 // s[9] * x1[0] + + s'4 * 3'x1 // + s[8] * x1[1] + + s'3 * 2'x1 // + s[7] * x1[2] + + s'2 * 'x1 // + s[6] * x1[3] + + s' * x1 // + s[5] * x1[4] + + s * x1' // + s[4] * x1[5] + + 's * x1'2 // + s[3] * x1[6] + + 2's * x1'3 // + s[2] * x1[7] + + 3's * x1'4 // + s[1] * x1[8] + + 4's * x1'5 // + s[0] * x1[9] + - s'5 * 4'x3 // - s[9] * x3[0] + - s'4 * 3'x3 // - s[8] * x3[1] + - s'3 * 2'x3 // - s[7] * x3[2] + - s'2 * 'x3 // - s[6] * x3[3] + - s' * x3 // - s[5] * x3[4] + - s * x3' // - s[4] * x3[5] + - 's * x3'2 // - s[3] * x3[6] + - 2's * x3'3 // - s[2] * x3[7] + - 3's * x3'4 // - s[1] * x3[8] + - 4's * x3'5 // - s[0] * x3[9] + - y1'5 // - y1[9] + - y3'5 // - y3[9] + + 0xFFFF * q2 // + p[5] * q2[4] + + 0xFFFF * q2' // + p[4] * q2[5] + + 0xFFFF * q2'2 // + p[3] * q2[6] + + 0xFFFF * q2'3 // + p[2] * q2[7] + + 0xFFFF * q2'4 // + p[1] * q2[8] + + 0xFFFF * q2'5; // + p[0] * q2[9] + +// clock #5 + +eq_secp256r1_y3_chunks[10] = s'5 * 5'x1 // s[10] * x1[0] + + s'4 * 4'x1 // + s[9] * x1[1] + + s'3 * 3'x1 // + s[8] * x1[2] + + s'2 * 2'x1 // + s[7] * x1[3] + + s' * 'x1 // + s[6] * x1[4] + + s * x1 // + s[5] * x1[5] + + 's * x1' // + s[4] * x1[6] + + 2's * x1'2 // + s[3] * x1[7] + + 3's * x1'3 // + s[2] * x1[8] + + 4's * x1'4 // + s[1] * x1[9] + + 5's * x1'5 // + s[0] * x1[10] + - s'5 * 5'x3 // - s[10] * x3[0] + - s'4 * 4'x3 // - s[9] * x3[1] + - s'3 * 3'x3 // - s[8] * x3[2] + - s'2 * 2'x3 // - s[7] * x3[3] + - s' * 'x3 // - s[6] * x3[4] + - s * x3 // - s[5] * x3[5] + - 's * x3' // - s[4] * x3[6] + - 2's * x3'2 // - s[3] * x3[7] + - 3's * x3'3 // - s[2] * x3[8] + - 4's * x3'4 // - s[1] * x3[9] + - 5's * x3'5 // - s[0] * x3[10] + - y1'5 // - y1[10] + - y3'5 // - y3[10] + + 0xFFFF * q2 // + p[5] * q2[5] + + 0xFFFF * q2' // + p[4] * q2[6] + + 0xFFFF * q2'2 // + p[3] * q2[7] + + 0xFFFF * q2'3 // + p[2] * q2[8] + + 0xFFFF * q2'4 // + p[1] * q2[9] + + 0xFFFF * q2'5; // + p[0] * q2[10] + +eq_secp256r1_y3_chunks[11] = s'6 * 5'x1 // s[11] * x1[0] + + s'5 * 4'x1 // + s[10] * x1[1] + + s'4 * 3'x1 // + s[9] * x1[2] + + s'3 * 2'x1 // + s[8] * x1[3] + + s'2 * 'x1 // + s[7] * x1[4] + + s' * x1 // + s[6] * x1[5] + + s * x1' // + s[5] * x1[6] + + 's * x1'2 // + s[4] * x1[7] + + 2's * x1'3 // + s[3] * x1[8] + + 3's * x1'4 // + s[2] * x1[9] + + 4's * x1'5 // + s[1] * x1[10] + + 5's * x1'6 // + s[0] * x1[11] + - s'6 * 5'x3 // - s[11] * x3[0] + - s'5 * 4'x3 // - s[10] * x3[1] + - s'4 * 3'x3 // - s[9] * x3[2] + - s'3 * 2'x3 // - s[8] * x3[3] + - s'2 * 'x3 // - s[7] * x3[4] + - s' * x3 // - s[6] * x3[5] + - s * x3' // - s[5] * x3[6] + - 's * x3'2 // - s[4] * x3[7] + - 2's * x3'3 // - s[3] * x3[8] + - 3's * x3'4 // - s[2] * x3[9] + - 4's * x3'5 // - s[1] * x3[10] + - 5's * x3'6 // - s[0] * x3[11] + - y1'6 // - y1[11] + - y3'6 // - y3[11] + + 0xFFFF * q2' // + p[5] * q2[6] + + 0xFFFF * q2'2 // + p[4] * q2[7] + + 0xFFFF * q2'3 // + p[3] * q2[8] + + 0xFFFF * q2'4 // + p[2] * q2[9] + + 0xFFFF * q2'5 // + p[1] * q2[10] + + 0xFFFF * q2'6; // + p[0] * q2[11] + +// clock #6 + +eq_secp256r1_y3_chunks[12] = s'6 * 6'x1 // s[12] * x1[0] + + s'5 * 5'x1 // + s[11] * x1[1] + + s'4 * 4'x1 // + s[10] * x1[2] + + s'3 * 3'x1 // + s[9] * x1[3] + + s'2 * 2'x1 // + s[8] * x1[4] + + s' * 'x1 // + s[7] * x1[5] + + s * x1 // + s[6] * x1[6] + + 's * x1' // + s[5] * x1[7] + + 2's * x1'2 // + s[4] * x1[8] + + 3's * x1'3 // + s[3] * x1[9] + + 4's * x1'4 // + s[2] * x1[10] + + 5's * x1'5 // + s[1] * x1[11] + + 6's * x1'6 // + s[0] * x1[12] + - s'6 * 6'x3 // - s[12] * x3[0] + - s'5 * 5'x3 // - s[11] * x3[1] + - s'4 * 4'x3 // - s[10] * x3[2] + - s'3 * 3'x3 // - s[9] * x3[3] + - s'2 * 2'x3 // - s[8] * x3[4] + - s' * 'x3 // - s[7] * x3[5] + - s * x3 // - s[6] * x3[6] + - 's * x3' // - s[5] * x3[7] + - 2's * x3'2 // - s[4] * x3[8] + - 3's * x3'3 // - s[3] * x3[9] + - 4's * x3'4 // - s[2] * x3[10] + - 5's * x3'5 // - s[1] * x3[11] + - 6's * x3'6 // - s[0] * x3[12] + - y1'6 // - y1[12] + - y3'6 // - y3[12] + + 6'q2 // + q2[0] + + 0xFFFF * q2' // + p[5] * q2[7] + + 0xFFFF * q2'2 // + p[4] * q2[8] + + 0xFFFF * q2'3 // + p[3] * q2[9] + + 0xFFFF * q2'4 // + p[2] * q2[10] + + 0xFFFF * q2'5 // + p[1] * q2[11] + + 0xFFFF * q2'6; // + p[0] * q2[12] + +eq_secp256r1_y3_chunks[13] = s'7 * 6'x1 // s[13] * x1[0] + + s'6 * 5'x1 // + s[12] * x1[1] + + s'5 * 4'x1 // + s[11] * x1[2] + + s'4 * 3'x1 // + s[10] * x1[3] + + s'3 * 2'x1 // + s[9] * x1[4] + + s'2 * 'x1 // + s[8] * x1[5] + + s' * x1 // + s[7] * x1[6] + + s * x1' // + s[6] * x1[7] + + 's * x1'2 // + s[5] * x1[8] + + 2's * x1'3 // + s[4] * x1[9] + + 3's * x1'4 // + s[3] * x1[10] + + 4's * x1'5 // + s[2] * x1[11] + + 5's * x1'6 // + s[1] * x1[12] + + 6's * x1'7 // + s[0] * x1[13] + - s'7 * 6'x3 // - s[13] * x3[0] + - s'6 * 5'x3 // - s[12] * x3[1] + - s'5 * 4'x3 // - s[11] * x3[2] + - s'4 * 3'x3 // - s[10] * x3[3] + - s'3 * 2'x3 // - s[9] * x3[4] + - s'2 * 'x3 // - s[8] * x3[5] + - s' * x3 // - s[7] * x3[6] + - s * x3' // - s[6] * x3[7] + - 's * x3'2 // - s[5] * x3[8] + - 2's * x3'3 // - s[4] * x3[9] + - 3's * x3'4 // - s[3] * x3[10] + - 4's * x3'5 // - s[2] * x3[11] + - 5's * x3'6 // - s[1] * x3[12] + - 6's * x3'7 // - s[0] * x3[13] + - y1'7 // - y1[13] + - y3'7 // - y3[13] + + 5'q2 // + q2[1] + + 0xFFFF * q2'2 // + p[5] * q2[8] + + 0xFFFF * q2'3 // + p[4] * q2[9] + + 0xFFFF * q2'4 // + p[3] * q2[10] + + 0xFFFF * q2'5 // + p[2] * q2[11] + + 0xFFFF * q2'6 // + p[1] * q2[12] + + 0xFFFF * q2'7; // + p[0] * q2[13] + +// clock #7 + +eq_secp256r1_y3_chunks[14] = s'7 * 7'x1 // s[14] * x1[0] + + s'6 * 6'x1 // + s[13] * x1[1] + + s'5 * 5'x1 // + s[12] * x1[2] + + s'4 * 4'x1 // + s[11] * x1[3] + + s'3 * 3'x1 // + s[10] * x1[4] + + s'2 * 2'x1 // + s[9] * x1[5] + + s' * 'x1 // + s[8] * x1[6] + + s * x1 // + s[7] * x1[7] + + 's * x1' // + s[6] * x1[8] + + 2's * x1'2 // + s[5] * x1[9] + + 3's * x1'3 // + s[4] * x1[10] + + 4's * x1'4 // + s[3] * x1[11] + + 5's * x1'5 // + s[2] * x1[12] + + 6's * x1'6 // + s[1] * x1[13] + + 7's * x1'7 // + s[0] * x1[14] + - s'7 * 7'x3 // - s[14] * x3[0] + - s'6 * 6'x3 // - s[13] * x3[1] + - s'5 * 5'x3 // - s[12] * x3[2] + - s'4 * 4'x3 // - s[11] * x3[3] + - s'3 * 3'x3 // - s[10] * x3[4] + - s'2 * 2'x3 // - s[9] * x3[5] + - s' * 'x3 // - s[8] * x3[6] + - s * x3 // - s[7] * x3[7] + - 's * x3' // - s[6] * x3[8] + - 2's * x3'2 // - s[5] * x3[9] + - 3's * x3'3 // - s[4] * x3[10] + - 4's * x3'4 // - s[3] * x3[11] + - 5's * x3'5 // - s[2] * x3[12] + - 6's * x3'6 // - s[1] * x3[13] + - 7's * x3'7 // - s[0] * x3[14] + - y1'7 // - y1[14] + - y3'7 // - y3[14] + + 0xFFFF * 7'q2 // + p[14] * q2[0] + + 5'q2 // + q2[2] + + 0xFFFF * q2'2 // + p[5] * q2[9] + + 0xFFFF * q2'3 // + p[4] * q2[10] + + 0xFFFF * q2'4 // + p[3] * q2[11] + + 0xFFFF * q2'5 // + p[2] * q2[12] + + 0xFFFF * q2'6 // + p[1] * q2[13] + + 0xFFFF * q2'7; // + p[0] * q2[14] + +eq_secp256r1_y3_chunks[15] = s'8 * 7'x1 // s[15] * x1[0] + + s'7 * 6'x1 // + s[14] * x1[1] + + s'6 * 5'x1 // + s[13] * x1[2] + + s'5 * 4'x1 // + s[12] * x1[3] + + s'4 * 3'x1 // + s[11] * x1[4] + + s'3 * 2'x1 // + s[10] * x1[5] + + s'2 * 'x1 // + s[9] * x1[6] + + s' * x1 // + s[8] * x1[7] + + s * x1' // + s[7] * x1[8] + + 's * x1'2 // + s[6] * x1[9] + + 2's * x1'3 // + s[5] * x1[10] + + 3's * x1'4 // + s[4] * x1[11] + + 4's * x1'5 // + s[3] * x1[12] + + 5's * x1'6 // + s[2] * x1[13] + + 6's * x1'7 // + s[1] * x1[14] + + 7's * x1'8 // + s[0] * x1[15] + - s'8 * 7'x3 // - s[15] * x3[0] + - s'7 * 6'x3 // - s[14] * x3[1] + - s'6 * 5'x3 // - s[13] * x3[2] + - s'5 * 4'x3 // - s[12] * x3[3] + - s'4 * 3'x3 // - s[11] * x3[4] + - s'3 * 2'x3 // - s[10] * x3[5] + - s'2 * 'x3 // - s[9] * x3[6] + - s' * x3 // - s[8] * x3[7] + - s * x3' // - s[7] * x3[8] + - 's * x3'2 // - s[6] * x3[9] + - 2's * x3'3 // - s[5] * x3[10] + - 3's * x3'4 // - s[4] * x3[11] + - 4's * x3'5 // - s[3] * x3[12] + - 5's * x3'6 // - s[2] * x3[13] + - 6's * x3'7 // - s[1] * x3[14] + - 7's * x3'8 // - s[0] * x3[15] + - y1'8 // - y1[15] + - y3'8 // - y3[15] + + 0xFFFF * 7'q2 // + p[15] * q2[0] + + 0xFFFF * 6'q2 // + p[14] * q2[1] + + 4'q2 // + q2[3] + + 0xFFFF * q2'3 // + p[5] * q2[10] + + 0xFFFF * q2'4 // + p[4] * q2[11] + + 0xFFFF * q2'5 // + p[3] * q2[12] + + 0xFFFF * q2'6 // + p[2] * q2[13] + + 0xFFFF * q2'7 // + p[1] * q2[14] + + 0xFFFF * q2'8; // + p[0] * q2[15] + +// clock #8 + +eq_secp256r1_y3_chunks[16] = s'7 * 7'x1 // s[15] * x1[1] + + s'6 * 6'x1 // + s[14] * x1[2] + + s'5 * 5'x1 // + s[13] * x1[3] + + s'4 * 4'x1 // + s[12] * x1[4] + + s'3 * 3'x1 // + s[11] * x1[5] + + s'2 * 2'x1 // + s[10] * x1[6] + + s' * 'x1 // + s[9] * x1[7] + + s * x1 // + s[8] * x1[8] + + 's * x1' // + s[7] * x1[9] + + 2's * x1'2 // + s[6] * x1[10] + + 3's * x1'3 // + s[5] * x1[11] + + 4's * x1'4 // + s[4] * x1[12] + + 5's * x1'5 // + s[3] * x1[13] + + 6's * x1'6 // + s[2] * x1[14] + + 7's * x1'7 // + s[1] * x1[15] + - s'7 * 7'x3 // - s[15] * x3[1] + - s'6 * 6'x3 // - s[14] * x3[2] + - s'5 * 5'x3 // - s[13] * x3[3] + - s'4 * 4'x3 // - s[12] * x3[4] + - s'3 * 3'x3 // - s[11] * x3[5] + - s'2 * 2'x3 // - s[10] * x3[6] + - s' * 'x3 // - s[9] * x3[7] + - s * x3 // - s[8] * x3[8] + - 's * x3' // - s[7] * x3[9] + - 2's * x3'2 // - s[6] * x3[10] + - 3's * x3'3 // - s[5] * x3[11] + - 4's * x3'4 // - s[4] * x3[12] + - 5's * x3'5 // - s[3] * x3[13] + - 6's * x3'6 // - s[2] * x3[14] + - 7's * x3'7 // - s[1] * x3[15] + + 0xFFFF * 7'q2 // + p[15] * q2[1] + + 0xFFFF * 6'q2 // + p[14] * q2[2] + + 4'q2 // + q2[4] + + 0xFFFF * q2'3 // + p[5] * q2[11] + + 0xFFFF * q2'4 // + p[4] * q2[12] + + 0xFFFF * q2'5 // + p[3] * q2[13] + + 0xFFFF * q2'6 // + p[2] * q2[14] + + 0xFFFF * q2'7 // + p[1] * q2[15] + - 0xFFFE; // - (p*offset)[16] + +eq_secp256r1_y3_chunks[17] = s'7 * 6'x1 // s[15] * x1[2] + + s'6 * 5'x1 // + s[14] * x1[3] + + s'5 * 4'x1 // + s[13] * x1[4] + + s'4 * 3'x1 // + s[12] * x1[5] + + s'3 * 2'x1 // + s[11] * x1[6] + + s'2 * 'x1 // + s[10] * x1[7] + + s' * x1 // + s[9] * x1[8] + + s * x1' // + s[8] * x1[9] + + 's * x1'2 // + s[7] * x1[10] + + 2's * x1'3 // + s[6] * x1[11] + + 3's * x1'4 // + s[5] * x1[12] + + 4's * x1'5 // + s[4] * x1[13] + + 5's * x1'6 // + s[3] * x1[14] + + 6's * x1'7 // + s[2] * x1[15] + - s'7 * 6'x3 // - s[15] * x3[2] + - s'6 * 5'x3 // - s[14] * x3[3] + - s'5 * 4'x3 // - s[13] * x3[4] + - s'4 * 3'x3 // - s[12] * x3[5] + - s'3 * 2'x3 // - s[11] * x3[6] + - s'2 * 'x3 // - s[10] * x3[7] + - s' * x3 // - s[9] * x3[8] + - s * x3' // - s[8] * x3[9] + - 's * x3'2 // - s[7] * x3[10] + - 2's * x3'3 // - s[6] * x3[11] + - 3's * x3'4 // - s[5] * x3[12] + - 4's * x3'5 // - s[4] * x3[13] + - 5's * x3'6 // - s[3] * x3[14] + - 6's * x3'7 // - s[2] * x3[15] + + 0xFFFF * 6'q2 // + p[15] * q2[2] + + 0xFFFF * 5'q2 // + p[14] * q2[3] + + 3'q2 // + q2[5] + + 0xFFFF * q2'4 // + p[5] * q2[12] + + 0xFFFF * q2'5 // + p[4] * q2[13] + + 0xFFFF * q2'6 // + p[3] * q2[14] + + 0xFFFF * q2'7 // + p[2] * q2[15] + - 0xFFFF; // - (p*offset)[17] + +// clock #9 + +eq_secp256r1_y3_chunks[18] = s'6 * 6'x1 // s[15] * x1[3] + + s'5 * 5'x1 // + s[14] * x1[4] + + s'4 * 4'x1 // + s[13] * x1[5] + + s'3 * 3'x1 // + s[12] * x1[6] + + s'2 * 2'x1 // + s[11] * x1[7] + + s' * 'x1 // + s[10] * x1[8] + + s * x1 // + s[9] * x1[9] + + 's * x1' // + s[8] * x1[10] + + 2's * x1'2 // + s[7] * x1[11] + + 3's * x1'3 // + s[6] * x1[12] + + 4's * x1'4 // + s[5] * x1[13] + + 5's * x1'5 // + s[4] * x1[14] + + 6's * x1'6 // + s[3] * x1[15] + - s'6 * 6'x3 // - s[15] * x3[3] + - s'5 * 5'x3 // - s[14] * x3[4] + - s'4 * 4'x3 // - s[13] * x3[5] + - s'3 * 3'x3 // - s[12] * x3[6] + - s'2 * 2'x3 // - s[11] * x3[7] + - s' * 'x3 // - s[10] * x3[8] + - s * x3 // - s[9] * x3[9] + - 's * x3' // - s[8] * x3[10] + - 2's * x3'2 // - s[7] * x3[11] + - 3's * x3'3 // - s[6] * x3[12] + - 4's * x3'4 // - s[5] * x3[13] + - 5's * x3'5 // - s[4] * x3[14] + - 6's * x3'6 // - s[3] * x3[15] + + 0xFFFF * 6'q2 // + p[15] * q2[3] + + 0xFFFF * 5'q2 // + p[14] * q2[4] + + 3'q2 // + q2[6] + + 0xFFFF * q2'4 // + p[5] * q2[13] + + 0xFFFF * q2'5 // + p[4] * q2[14] + + 0xFFFF * q2'6 // + p[3] * q2[15] + - 0xFFFF; // - (p*offset)[18] + +eq_secp256r1_y3_chunks[19] = s'6 * 5'x1 // s[15] * x1[4] + + s'5 * 4'x1 // + s[14] * x1[5] + + s'4 * 3'x1 // + s[13] * x1[6] + + s'3 * 2'x1 // + s[12] * x1[7] + + s'2 * 'x1 // + s[11] * x1[8] + + s' * x1 // + s[10] * x1[9] + + s * x1' // + s[9] * x1[10] + + 's * x1'2 // + s[8] * x1[11] + + 2's * x1'3 // + s[7] * x1[12] + + 3's * x1'4 // + s[6] * x1[13] + + 4's * x1'5 // + s[5] * x1[14] + + 5's * x1'6 // + s[4] * x1[15] + - s'6 * 5'x3 // - s[15] * x3[4] + - s'5 * 4'x3 // - s[14] * x3[5] + - s'4 * 3'x3 // - s[13] * x3[6] + - s'3 * 2'x3 // - s[12] * x3[7] + - s'2 * 'x3 // - s[11] * x3[8] + - s' * x3 // - s[10] * x3[9] + - s * x3' // - s[9] * x3[10] + - 's * x3'2 // - s[8] * x3[11] + - 2's * x3'3 // - s[7] * x3[12] + - 3's * x3'4 // - s[6] * x3[13] + - 4's * x3'5 // - s[5] * x3[14] + - 5's * x3'6 // - s[4] * x3[15] + + 0xFFFF * 5'q2 // + p[15] * q2[4] + + 0xFFFF * 4'q2 // + p[14] * q2[5] + + 2'q2 // + q2[7] + + 0xFFFF * q2'5 // + p[5] * q2[14] + + 0xFFFF * q2'6 // + p[4] * q2[15] + - 0xFFFF; // - (p*offset)[19] + +// clock #10 + +eq_secp256r1_y3_chunks[20] = s'5 * 5'x1 // s[15] * x1[5] + + s'4 * 4'x1 // + s[14] * x1[6] + + s'3 * 3'x1 // + s[13] * x1[7] + + s'2 * 2'x1 // + s[12] * x1[8] + + s' * 'x1 // + s[11] * x1[9] + + s * x1 // + s[10] * x1[10] + + 's * x1' // + s[9] * x1[11] + + 2's * x1'2 // + s[8] * x1[12] + + 3's * x1'3 // + s[7] * x1[13] + + 4's * x1'4 // + s[6] * x1[14] + + 5's * x1'5 // + s[5] * x1[15] + - s'5 * 5'x3 // - s[15] * x3[5] + - s'4 * 4'x3 // - s[14] * x3[6] + - s'3 * 3'x3 // - s[13] * x3[7] + - s'2 * 2'x3 // - s[12] * x3[8] + - s' * 'x3 // - s[11] * x3[9] + - s * x3 // - s[10] * x3[10] + - 's * x3' // - s[9] * x3[11] + - 2's * x3'2 // - s[8] * x3[12] + - 3's * x3'3 // - s[7] * x3[13] + - 4's * x3'4 // - s[6] * x3[14] + - 5's * x3'5 // - s[5] * x3[15] + + 0xFFFF * 5'q2 // + p[15] * q2[5] + + 0xFFFF * 4'q2 // + p[14] * q2[6] + + 2'q2 // + q2[8] + + 0xFFFF * q2'5 // + p[5] * q2[15] + - 0xFFFF; // - (p*offset)[20] + +eq_secp256r1_y3_chunks[21] = s'5 * 4'x1 // s[15] * x1[6] + + s'4 * 3'x1 // + s[14] * x1[7] + + s'3 * 2'x1 // + s[13] * x1[8] + + s'2 * 'x1 // + s[12] * x1[9] + + s' * x1 // + s[11] * x1[10] + + s * x1' // + s[10] * x1[11] + + 's * x1'2 // + s[9] * x1[12] + + 2's * x1'3 // + s[8] * x1[13] + + 3's * x1'4 // + s[7] * x1[14] + + 4's * x1'5 // + s[6] * x1[15] + - s'5 * 4'x3 // - s[15] * x3[6] + - s'4 * 3'x3 // - s[14] * x3[7] + - s'3 * 2'x3 // - s[13] * x3[8] + - s'2 * 'x3 // - s[12] * x3[9] + - s' * x3 // - s[11] * x3[10] + - s * x3' // - s[10] * x3[11] + - 's * x3'2 // - s[9] * x3[12] + - 2's * x3'3 // - s[8] * x3[13] + - 3's * x3'4 // - s[7] * x3[14] + - 4's * x3'5 // - s[6] * x3[15] + + 0xFFFF * 4'q2 // + p[15] * q2[6] + + 0xFFFF * 3'q2 // + p[14] * q2[7] + + 'q2 // + q2[9] + - 0xFFFF; // - (p*offset)[21] + +// clock #11 + +eq_secp256r1_y3_chunks[22] = s'4 * 4'x1 // s[15] * x1[7] + + s'3 * 3'x1 // + s[14] * x1[8] + + s'2 * 2'x1 // + s[13] * x1[9] + + s' * 'x1 // + s[12] * x1[10] + + s * x1 // + s[11] * x1[11] + + 's * x1' // + s[10] * x1[12] + + 2's * x1'2 // + s[9] * x1[13] + + 3's * x1'3 // + s[8] * x1[14] + + 4's * x1'4 // + s[7] * x1[15] + - s'4 * 4'x3 // - s[15] * x3[7] + - s'3 * 3'x3 // - s[14] * x3[8] + - s'2 * 2'x3 // - s[13] * x3[9] + - s' * 'x3 // - s[12] * x3[10] + - s * x3 // - s[11] * x3[11] + - 's * x3' // - s[10] * x3[12] + - 2's * x3'2 // - s[9] * x3[13] + - 3's * x3'3 // - s[8] * x3[14] + - 4's * x3'4 // - s[7] * x3[15] + + 0xFFFF * 4'q2 // + p[15] * q2[7] + + 0xFFFF * 3'q2 // + p[14] * q2[8] + + 'q2 // + q2[10] + - 0x1; // - (p*offset)[22] + +eq_secp256r1_y3_chunks[23] = s'4 * 3'x1 // s[15] * x1[8] + + s'3 * 2'x1 // + s[14] * x1[9] + + s'2 * 'x1 // + s[13] * x1[10] + + s' * x1 // + s[12] * x1[11] + + s * x1' // + s[11] * x1[12] + + 's * x1'2 // + s[10] * x1[13] + + 2's * x1'3 // + s[9] * x1[14] + + 3's * x1'4 // + s[8] * x1[15] + - s'4 * 3'x3 // - s[15] * x3[8] + - s'3 * 2'x3 // - s[14] * x3[9] + - s'2 * 'x3 // - s[13] * x3[10] + - s' * x3 // - s[12] * x3[11] + - s * x3' // - s[11] * x3[12] + - 's * x3'2 // - s[10] * x3[13] + - 2's * x3'3 // - s[9] * x3[14] + - 3's * x3'4 // - s[8] * x3[15] + + 0xFFFF * 3'q2 // + p[15] * q2[8] + + 0xFFFF * 2'q2 // + p[14] * q2[9] + + q2; // + q2[11] + +// clock #12 + +eq_secp256r1_y3_chunks[24] = s'3 * 3'x1 // s[15] * x1[9] + + s'2 * 2'x1 // + s[14] * x1[10] + + s' * 'x1 // + s[13] * x1[11] + + s * x1 // + s[12] * x1[12] + + 's * x1' // + s[11] * x1[13] + + 2's * x1'2 // + s[10] * x1[14] + + 3's * x1'3 // + s[9] * x1[15] + - s'3 * 3'x3 // - s[15] * x3[9] + - s'2 * 2'x3 // - s[14] * x3[10] + - s' * 'x3 // - s[13] * x3[11] + - s * x3 // - s[12] * x3[12] + - 's * x3' // - s[11] * x3[13] + - 2's * x3'2 // - s[10] * x3[14] + - 3's * x3'3 // - s[9] * x3[15] + + 0xFFFF * 3'q2 // + p[15] * q2[9] + + 0xFFFF * 2'q2 // + p[14] * q2[10] + + q2; // + q2[12] + +eq_secp256r1_y3_chunks[25] = s'3 * 2'x1 // s[15] * x1[10] + + s'2 * 'x1 // + s[14] * x1[11] + + s' * x1 // + s[13] * x1[12] + + s * x1' // + s[12] * x1[13] + + 's * x1'2 // + s[11] * x1[14] + + 2's * x1'3 // + s[10] * x1[15] + - s'3 * 2'x3 // - s[15] * x3[10] + - s'2 * 'x3 // - s[14] * x3[11] + - s' * x3 // - s[13] * x3[12] + - s * x3' // - s[12] * x3[13] + - 's * x3'2 // - s[11] * x3[14] + - 2's * x3'3 // - s[10] * x3[15] + + 0xFFFF * 2'q2 // + p[15] * q2[10] + + 0xFFFF * 'q2 // + p[14] * q2[11] + + q2'; // + q2[13] + +// clock #13 + +eq_secp256r1_y3_chunks[26] = s'2 * 2'x1 // s[15] * x1[11] + + s' * 'x1 // + s[14] * x1[12] + + s * x1 // + s[13] * x1[13] + + 's * x1' // + s[12] * x1[14] + + 2's * x1'2 // + s[11] * x1[15] + - s'2 * 2'x3 // - s[15] * x3[11] + - s' * 'x3 // - s[14] * x3[12] + - s * x3 // - s[13] * x3[13] + - 's * x3' // - s[12] * x3[14] + - 2's * x3'2 // - s[11] * x3[15] + + 0xFFFF * 2'q2 // + p[15] * q2[11] + + 0xFFFF * 'q2 // + p[14] * q2[12] + + q2'; // + q2[14] + +eq_secp256r1_y3_chunks[27] = s'2 * 'x1 // s[15] * x1[12] + + s' * x1 // + s[14] * x1[13] + + s * x1' // + s[13] * x1[14] + + 's * x1'2 // + s[12] * x1[15] + - s'2 * 'x3 // - s[15] * x3[12] + - s' * x3 // - s[14] * x3[13] + - s * x3' // - s[13] * x3[14] + - 's * x3'2 // - s[12] * x3[15] + + 0xFFFF * 'q2 // + p[15] * q2[12] + + 0xFFFF * q2 // + p[14] * q2[13] + + q2'2; // + q2[15] + +// clock #14 + +eq_secp256r1_y3_chunks[28] = s' * 'x1 // s[15] * x1[13] + + s * x1 // + s[14] * x1[14] + + 's * x1' // + s[13] * x1[15] + - s' * 'x3 // - s[15] * x3[13] + - s * x3 // - s[14] * x3[14] + - 's * x3' // - s[13] * x3[15] + + 0xFFFF * 'q2 // + p[15] * q2[13] + + 0xFFFF * q2 // + p[14] * q2[14] + - 0x2; // - (p*offset)[28] + +eq_secp256r1_y3_chunks[29] = s' * x1 // s[15] * x1[14] + + s * x1' // + s[14] * x1[15] + - s' * x3 // - s[15] * x3[14] + - s * x3' // - s[14] * x3[15] + + 0xFFFF * q2 // + p[15] * q2[14] + + 0xFFFF * q2'; // + p[14] * q2[15] + +// clock #15 + +eq_secp256r1_y3_chunks[30] = s * x1 // s[15] * x1[15] + - s * x3 // - s[15] * x3[15] + + 0xFFFF * q2 // + p[15] * q2[15] + - 0xFFFE; // - (p*offset)[30] + +eq_secp256r1_y3_chunks[31] = - 0x1FFFF; // - (p*offset)[31] + diff --git a/precompiles/arith_eq/src/arith_eq.rs b/precompiles/arith_eq/src/arith_eq.rs index 82e9c12d1..92e7fc573 100644 --- a/precompiles/arith_eq/src/arith_eq.rs +++ b/precompiles/arith_eq/src/arith_eq.rs @@ -23,8 +23,9 @@ use crate::{ arith_eq_constants::*, executors, Arith256Input, Arith256ModInput, ArithEqInput, ArithEqLtTableSM, Bn254ComplexAddInput, Bn254ComplexMulInput, Bn254ComplexSubInput, Bn254CurveAddInput, Bn254CurveDblInput, Secp256k1AddInput, Secp256k1DblInput, - SECP256K1_PRIME_CHUNKS, SEL_OP_ARITH256, SEL_OP_ARITH256_MOD, SEL_OP_SECP256K1_ADD, - SEL_OP_SECP256K1_DBL, + Secp256r1AddInput, Secp256r1DblInput, SECP256K1_PRIME_CHUNKS, SECP256R1_PRIME_CHUNKS, + SEL_OP_ARITH256, SEL_OP_ARITH256_MOD, SEL_OP_SECP256K1_ADD, SEL_OP_SECP256K1_DBL, + SEL_OP_SECP256R1_ADD, SEL_OP_SECP256R1_DBL, }; use rayon::prelude::*; @@ -303,6 +304,52 @@ impl ArithEqSM { ); } + fn process_secp256r1_add( + &self, + input: &Secp256r1AddInput, + trace: &mut [ArithEqTraceRowType], + ) { + let data = executors::Secp256r1::execute_add(&input.p1, &input.p2); + self.expand_data_on_trace(&data, trace, SEL_OP_SECP256R1_ADD); + Self::expand_addr_step_on_trace( + &ArithEqStepAddr { + main_step: input.step, + addr_op: input.addr, + addr_x1: input.p1_addr, + addr_y1: input.p1_addr + 32, + addr_x2: input.p2_addr, + addr_y2: input.p2_addr + 32, + addr_x3: input.p1_addr, + addr_y3: input.p1_addr + 32, + addr_ind: [input.p1_addr, input.p2_addr, 0, 0, 0], + }, + trace, + ); + } + + fn process_secp256r1_dbl( + &self, + input: &Secp256r1DblInput, + trace: &mut [ArithEqTraceRowType], + ) { + let data = executors::Secp256r1::execute_dbl(&input.p1); + self.expand_data_on_trace(&data, trace, SEL_OP_SECP256R1_DBL); + Self::expand_addr_step_on_trace( + &ArithEqStepAddr { + main_step: input.step, + addr_op: input.addr, + addr_x1: input.addr, + addr_y1: input.addr + 32, + addr_x2: input.addr, + addr_y2: input.addr + 32, + addr_x3: input.addr, + addr_y3: input.addr + 32, + addr_ind: [0, 0, 0, 0, 0], + }, + trace, + ); + } + #[inline(always)] fn to_ranged_field(&self, value: i64, range_id: usize) -> u64 { self.std.range_check(range_id, value, 1); @@ -425,12 +472,38 @@ impl ArithEqSM { self.std.inc_virtual_row(self.table_id, row as u64, 1); prev_y3_lt = y3_lt; } + SEL_OP_SECP256R1_ADD | SEL_OP_SECP256R1_DBL => { + let x3_lt = data.x3[i] < SECP256R1_PRIME_CHUNKS[i] + || (data.x3[i] == SECP256R1_PRIME_CHUNKS[i] && prev_x3_lt); + trace[i].set_x3_lt(x3_lt); + let row = ArithEqLtTableSM::calculate_table_row( + prev_x3_lt, + x3_lt, + data.x3[i] - SECP256R1_PRIME_CHUNKS[i], + ); + self.std.inc_virtual_row(self.table_id, row as u64, 1); + prev_x3_lt = x3_lt; + + let y3_lt = data.y3[i] < SECP256R1_PRIME_CHUNKS[i] + || (data.y3[i] == SECP256R1_PRIME_CHUNKS[i] && prev_y3_lt); + trace[i].set_y3_lt(y3_lt); + let row = ArithEqLtTableSM::calculate_table_row( + prev_y3_lt, + y3_lt, + data.y3[i] - SECP256R1_PRIME_CHUNKS[i], + ); + self.std.inc_virtual_row(self.table_id, row as u64, 1); + prev_y3_lt = y3_lt; + } _ => { trace[i].set_x3_lt(false); trace[i].set_y3_lt(false); } } - if (sel_op == SEL_OP_SECP256K1_ADD) || (sel_op == SEL_OP_BN254_CURVE_ADD) { + if (sel_op == SEL_OP_SECP256K1_ADD) + || (sel_op == SEL_OP_BN254_CURVE_ADD) + || (sel_op == SEL_OP_SECP256R1_ADD) + { if x1_x2_different { trace[i].set_x_are_different(true); trace[i].set_x_delta_chunk_inv(0); @@ -510,6 +583,8 @@ impl ArithEqSM { ArithEqInput::Bn254ComplexMul(idata) => { self.process_bn254_complex_mul(idata, trace); } + ArithEqInput::Secp256r1Add(idata) => self.process_secp256r1_add(idata, trace), + ArithEqInput::Secp256r1Dbl(idata) => self.process_secp256r1_dbl(idata, trace), } }); diff --git a/precompiles/arith_eq/src/arith_eq_bus_device.rs b/precompiles/arith_eq/src/arith_eq_bus_device.rs index b88921012..b3b125a37 100644 --- a/precompiles/arith_eq/src/arith_eq_bus_device.rs +++ b/precompiles/arith_eq/src/arith_eq_bus_device.rs @@ -16,14 +16,16 @@ use crate::mem_inputs::{ generate_bn254_complex_add_mem_inputs, generate_bn254_complex_mul_mem_inputs, generate_bn254_complex_sub_mem_inputs, generate_bn254_curve_add_mem_inputs, generate_bn254_curve_dbl_mem_inputs, generate_secp256k1_add_mem_inputs, - generate_secp256k1_dbl_mem_inputs, + generate_secp256k1_dbl_mem_inputs, generate_secp256r1_add_mem_inputs, + generate_secp256r1_dbl_mem_inputs, }; use crate::mem_inputs::{ skip_arith256_mem_inputs, skip_arith256_mod_mem_inputs, skip_bn254_complex_add_mem_inputs, skip_bn254_complex_mul_mem_inputs, skip_bn254_complex_sub_mem_inputs, skip_bn254_curve_add_mem_inputs, skip_bn254_curve_dbl_mem_inputs, - skip_secp256k1_add_mem_inputs, skip_secp256k1_dbl_mem_inputs, + skip_secp256k1_add_mem_inputs, skip_secp256k1_dbl_mem_inputs, skip_secp256r1_add_mem_inputs, + skip_secp256r1_dbl_mem_inputs, }; const ARITH256_OP: u8 = ZiskOp::Arith256.code(); @@ -35,6 +37,8 @@ const BN254_CURVE_DBL_OP: u8 = ZiskOp::Bn254CurveDbl.code(); const BN254_COMPLEX_ADD_OP: u8 = ZiskOp::Bn254ComplexAdd.code(); const BN254_COMPLEX_SUB_OP: u8 = ZiskOp::Bn254ComplexSub.code(); const BN254_COMPLEX_MUL_OP: u8 = ZiskOp::Bn254ComplexMul.code(); +const SECP256R1_ADD_OP: u8 = ZiskOp::Secp256r1Add.code(); +const SECP256R1_DBL_OP: u8 = ZiskOp::Secp256r1Dbl.code(); /// The `ArithEqCounter` struct represents a counter that monitors and measures /// arith_eq-related operations on the data bus. @@ -92,6 +96,8 @@ impl ArithEqCounterInputGen { BN254_COMPLEX_MUL_OP => { skip_bn254_complex_mul_mem_inputs(addr_main, data, mem_processors) } + SECP256R1_ADD_OP => skip_secp256r1_add_mem_inputs(addr_main, data, mem_processors), + SECP256R1_DBL_OP => skip_secp256r1_dbl_mem_inputs(addr_main, data, mem_processors), _ => { panic!("ArithEqCounterInputGen: Unsupported data length {}", data.len(),); } @@ -226,6 +232,24 @@ impl ArithEqCounterInputGen { mem_processors, ); } + SECP256R1_ADD_OP => { + generate_secp256r1_add_mem_inputs( + addr_main, + step_main, + data, + only_counters, + mem_processors, + ); + } + SECP256R1_DBL_OP => { + generate_secp256r1_dbl_mem_inputs( + addr_main, + step_main, + data, + only_counters, + mem_processors, + ); + } _ => { panic!("ArithEqCounterInputGen: Unsupported data length {}", data.len(),); diff --git a/precompiles/arith_eq/src/arith_eq_constants.rs b/precompiles/arith_eq/src/arith_eq_constants.rs index cfb8ec0e0..ea5469cbf 100644 --- a/precompiles/arith_eq/src/arith_eq_constants.rs +++ b/precompiles/arith_eq/src/arith_eq_constants.rs @@ -7,7 +7,7 @@ pub const ARITH_EQ_CHUNKS: usize = 16; pub const ARITH_EQ_CHUNK_BITS: usize = 16; pub const ARITH_EQ_CHUNK_SIZE: usize = 1 << ARITH_EQ_CHUNK_BITS; pub const ARITH_EQ_CHUNK_BASE_MAX: usize = ARITH_EQ_CHUNK_SIZE - 1; -pub const ARITH_EQ_OP_NUM: usize = 9; +pub const ARITH_EQ_OP_NUM: usize = 11; pub const SEL_OP_ARITH256: usize = 0; pub const SEL_OP_ARITH256_MOD: usize = 1; @@ -18,6 +18,8 @@ pub const SEL_OP_BN254_CURVE_DBL: usize = 5; pub const SEL_OP_BN254_COMPLEX_ADD: usize = 6; pub const SEL_OP_BN254_COMPLEX_SUB: usize = 7; pub const SEL_OP_BN254_COMPLEX_MUL: usize = 8; +pub const SEL_OP_SECP256R1_ADD: usize = 9; +pub const SEL_OP_SECP256R1_DBL: usize = 10; pub const SECP256K1_PRIME_CHUNKS: [i64; 16] = [ 0xFC2F, 0xFFFF, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, @@ -28,3 +30,8 @@ pub const BN254_PRIME_CHUNKS: [i64; 16] = [ 0xFD47, 0xD87C, 0x8C16, 0x3C20, 0xCA8D, 0x6871, 0x6A91, 0x9781, 0x585D, 0x8181, 0x45B6, 0xB850, 0xA029, 0xE131, 0x4E72, 0x3064, ]; + +pub const SECP256R1_PRIME_CHUNKS: [i64; 16] = [ + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0001, 0x0000, 0xFFFF, 0xFFFF, +]; diff --git a/precompiles/arith_eq/src/arith_eq_generator.rs b/precompiles/arith_eq/src/arith_eq_generator.rs index faf67ca13..b45a0fc8f 100644 --- a/precompiles/arith_eq/src/arith_eq_generator.rs +++ b/precompiles/arith_eq/src/arith_eq_generator.rs @@ -296,4 +296,75 @@ fn main() { let pil_file = pil_code_path.join("bn254_complex_mul_y3.pil"); eq.generate_pil_code_to_file("eq_bn254_complex_mul_y3", pil_file.to_str().unwrap()); + + // SECP256R1 + + // s - different points + + let mut eq = Equation::new(&config); + eq.parse( + "s*x2-s*x1-y2+y1-p*q0+p*offset", + &[ + ("p", "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"), + ("offset", "0x20000000000000000000000000000000000000000000000000000000000000000"), + ], + ); + + let rust_file = rust_code_path.join("secp256r1_add.rs"); + eq.generate_rust_code_to_file("Secp256r1Add", "x1,y1,x2,y2,s,q0", rust_file.to_str().unwrap()); + + let pil_file = pil_code_path.join("secp256r1_add.pil"); + eq.generate_pil_code_to_file("eq_secp256r1_add", pil_file.to_str().unwrap()); + + // s - duplicate points + + let mut eq = Equation::new(&config); + eq.parse( + "2*s*y1-3*x1*x1-a+p*q0-p*offset", + &[ + ("a", "0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc"), + ("p", "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"), + ("offset", "0x40000000000000000000000000000000000000000000000000000000000000000"), + ], + ); + + let rust_file = rust_code_path.join("secp256r1_dbl.rs"); + eq.generate_rust_code_to_file("Secp256r1Dbl", "x1,y1,s,q0", rust_file.to_str().unwrap()); + + let pil_file = pil_code_path.join("secp256r1_dbl.pil"); + eq.generate_pil_code_to_file("eq_secp256r1_dbl", pil_file.to_str().unwrap()); + + // x3 + + let mut eq = Equation::new(&config); + eq.parse( + "s*s-x1-x2-x3-p*q1+p*offset", + &[ + ("p", "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"), + ("offset", "0x4"), + ], + ); + + let rust_file = rust_code_path.join("secp256r1_x3.rs"); + eq.generate_rust_code_to_file("Secp256r1X3", "x1,x2,x3,s,q1", rust_file.to_str().unwrap()); + + let pil_file = pil_code_path.join("secp256r1_x3.pil"); + eq.generate_pil_code_to_file("eq_secp256r1_x3", pil_file.to_str().unwrap()); + + // y3 + + let mut eq = Equation::new(&config); + eq.parse( + "s*x1-s*x3-y1-y3+p*q2-p*offset", + &[ + ("p", "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"), + ("offset", "0x20000000000000000000000000000000000000000000000000000000000000000"), + ], + ); + + let rust_file = rust_code_path.join("secp256r1_y3.rs"); + eq.generate_rust_code_to_file("Secp256r1Y3", "x1,y1,x3,y3,s,q2", rust_file.to_str().unwrap()); + + let pil_file = pil_code_path.join("secp256r1_y3.pil"); + eq.generate_pil_code_to_file("eq_secp256r1_y3", pil_file.to_str().unwrap()); } diff --git a/precompiles/arith_eq/src/arith_eq_input.rs b/precompiles/arith_eq/src/arith_eq_input.rs index ed227896a..568151aa8 100644 --- a/precompiles/arith_eq/src/arith_eq_input.rs +++ b/precompiles/arith_eq/src/arith_eq_input.rs @@ -2,6 +2,7 @@ use zisk_common::{ OperationArith256Data, OperationArith256ModData, OperationBn254ComplexAddData, OperationBn254ComplexMulData, OperationBn254ComplexSubData, OperationBn254CurveAddData, OperationBn254CurveDblData, OperationSecp256k1AddData, OperationSecp256k1DblData, + OperationSecp256r1AddData, OperationSecp256r1DblData, }; #[derive(Debug)] @@ -15,6 +16,8 @@ pub enum ArithEqInput { Bn254ComplexAdd(Bn254ComplexAddInput), Bn254ComplexSub(Bn254ComplexSubInput), Bn254ComplexMul(Bn254ComplexMulInput), + Secp256r1Add(Secp256r1AddInput), + Secp256r1Dbl(Secp256r1DblInput), } #[derive(Debug)] @@ -221,3 +224,39 @@ impl Bn254ComplexMulInput { } } } + +#[derive(Debug)] +pub struct Secp256r1AddInput { + pub addr: u32, + pub p1_addr: u32, + pub p2_addr: u32, + pub step: u64, + pub p1: [u64; 8], + pub p2: [u64; 8], +} + +impl Secp256r1AddInput { + pub fn from(values: &OperationSecp256r1AddData) -> Self { + Self { + addr: values[3] as u32, + p1_addr: values[5] as u32, + p2_addr: values[6] as u32, + step: values[4], + p1: values[7..15].try_into().unwrap(), + p2: values[15..23].try_into().unwrap(), + } + } +} + +#[derive(Debug)] +pub struct Secp256r1DblInput { + pub addr: u32, + pub step: u64, + pub p1: [u64; 8], +} + +impl Secp256r1DblInput { + pub fn from(values: &OperationSecp256r1DblData) -> Self { + Self { addr: values[3] as u32, step: values[4], p1: values[5..13].try_into().unwrap() } + } +} diff --git a/precompiles/arith_eq/src/arith_eq_instance.rs b/precompiles/arith_eq/src/arith_eq_instance.rs index 9226085ad..2e184df80 100644 --- a/precompiles/arith_eq/src/arith_eq_instance.rs +++ b/precompiles/arith_eq/src/arith_eq_instance.rs @@ -7,7 +7,7 @@ use crate::{ Arith256Input, Arith256ModInput, ArithEqInput, ArithEqSM, Bn254ComplexAddInput, Bn254ComplexMulInput, Bn254ComplexSubInput, Bn254CurveAddInput, Bn254CurveDblInput, - Secp256k1AddInput, Secp256k1DblInput, + Secp256k1AddInput, Secp256k1DblInput, Secp256r1AddInput, Secp256r1DblInput, }; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; @@ -216,6 +216,12 @@ impl ArithEqCollector { ExtOperationData::OperationBn254ComplexMulData(bus_data) => { ArithEqInput::Bn254ComplexMul(Bn254ComplexMulInput::from(&bus_data)) } + ExtOperationData::OperationSecp256r1AddData(bus_data) => { + ArithEqInput::Secp256r1Add(Secp256r1AddInput::from(&bus_data)) + } + ExtOperationData::OperationSecp256r1DblData(bus_data) => { + ArithEqInput::Secp256r1Dbl(Secp256r1DblInput::from(&bus_data)) + } // Add here new operations _ => panic!("Expected ExtOperationData::OperationData"), }); diff --git a/precompiles/arith_eq/src/arith_eq_test_bigint.rs b/precompiles/arith_eq/src/arith_eq_test_bigint.rs index 9b24bfe67..e24be9f27 100644 --- a/precompiles/arith_eq/src/arith_eq_test_bigint.rs +++ b/precompiles/arith_eq/src/arith_eq_test_bigint.rs @@ -1,13 +1,15 @@ mod test_data; use test_data::{ get_arith256_mod_test_data, get_arith256_test_data, get_bn254_curve_add_test_data, - get_secp256k1_add_test_data, get_secp256k1_dbl_test_data, + get_secp256k1_add_test_data, get_secp256k1_dbl_test_data, get_secp256r1_add_test_data, + get_secp256r1_dbl_test_data, }; mod equations; mod executors; use executors::{ arith256::Arith256, arith256_mod::Arith256Mod, bn254_curve::Bn254Curve, secp256k1::Secp256k1, + secp256r1::Secp256r1, }; // cargo run --release --features="test_data" --bin arith_eq_test_bigint @@ -63,4 +65,23 @@ fn main() { // Bn254Curve::verify_add(&p1, &p2, &p3); // index += 1; // } + + index = 0; + while let Some((p1, p2, p3)) = get_secp256r1_add_test_data(index) { + println!("testing index secp256r1_add #{} ....", index); + if verbose { + println!("SECP256R1_ADD\n p1: {:?},\n p2: {:?},\n p3: {:?}", p1, p2, p3); + } + Secp256r1::verify_add(&p1, &p2, &p3); + index += 1; + } + index = 0; + while let Some((p1, p3)) = get_secp256r1_dbl_test_data(index) { + println!("testing index secp256r1_dbl #{} ....", index); + if verbose { + println!("SECP256R1_DBL\n p1: {:?},\n p3: {:?}", p1, p3); + } + Secp256r1::verify_dbl(&p1, &p3); + index += 1; + } } diff --git a/precompiles/arith_eq/src/arith_eq_test_generator.rs b/precompiles/arith_eq/src/arith_eq_test_generator.rs index 422a29f68..6866583b7 100644 --- a/precompiles/arith_eq/src/arith_eq_test_generator.rs +++ b/precompiles/arith_eq/src/arith_eq_test_generator.rs @@ -4,24 +4,19 @@ use test_data::{ get_arith256_mod_test_data, get_arith256_test_data, get_bn254_complex_add_test_data, get_bn254_complex_mul_test_data, get_bn254_complex_sub_test_data, get_bn254_curve_add_test_data, get_bn254_curve_dbl_test_data, get_secp256k1_add_test_data, - get_secp256k1_dbl_test_data, + get_secp256k1_dbl_test_data, get_secp256r1_add_test_data, get_secp256r1_dbl_test_data, }; mod arith_eq_constants; use arith_eq_constants::ARITH_EQ_ROWS_BY_OP; -// cargo run --release --features="test_data" --bin arith_eq_test_generator +// cargo run --release --features="test_data" --bin arith_eq_test_generator > ../zisk-testvectors/zisk-programs/arith_eq_gen/program/src/main.rs fn main() { let mut code = String::new(); code += "#![no_main]\n"; - code += "#![cfg(all(target_os = \"zkvm\", target_vendor = \"zisk\"))]\n"; code += "ziskos::entrypoint!(main);\n\n"; - code += "use ziskos::{\n"; - code += "\tarith256::*, arith256_mod::*, bn254_complex_add::*, bn254_complex_mul::*, bn254_complex_sub::*,\n"; - code += "\tbn254_curve_add::*, bn254_curve_dbl::*, complex::*, point::*, secp256k1_add::*,\n"; - code += "\tsecp256k1_dbl::*,\n"; - code += "};\n\n"; + code += "use ziskos::syscalls::*;\n\n"; code += "fn main() {\n"; code += "\tlet mut a: [u64;4] = [0,0,0,0];\n"; code += "\tlet mut b: [u64;4] = [0,0,0,0];\n"; @@ -139,6 +134,59 @@ fn main() { index += 1; } + code += "\tlet mut params = SyscallSecp256r1AddParams { p1: &mut p1, p2: &p2 };\n"; + + let initial_index = index; + while let Some((p1, p2, p3)) = get_secp256r1_add_test_data(index - initial_index) { + code += &format!( + "\t// secp256r1_add test rows: {}-{}\n\n", + index * ARITH_EQ_ROWS_BY_OP, + (index + 1) * ARITH_EQ_ROWS_BY_OP - 1 + ); + let p1_x: [u64; 4] = p1[0..4].try_into().unwrap(); + let p1_y: [u64; 4] = p1[4..8].try_into().unwrap(); + code += &format!( + "\tlet mut p1 = SyscallPoint256 {{\n\t\tx: {p1_x:?},\n\t\ty: {p1_y:?}\n\t}};\n" + ); + let p2_x: [u64; 4] = p2[0..4].try_into().unwrap(); + let p2_y: [u64; 4] = p2[4..8].try_into().unwrap(); + code += + &format!("\tlet p2 = SyscallPoint256 {{\n\t\tx: {p2_x:?},\n\t\ty: {p2_y:?}\n\t}};\n"); + code += "\tparams.p1 = &mut p1;\n"; + code += "\tparams.p2 = &p2;\n"; + code += "\tsyscall_secp256r1_add(&mut params);\n"; + + let p3_x: [u64; 4] = p3[0..4].try_into().unwrap(); + let p3_y: [u64; 4] = p3[4..8].try_into().unwrap(); + code += + &format!("\tlet p3 = SyscallPoint256 {{\n\t\tx: {p3_x:?},\n\t\ty: {p3_y:?}\n\t}};\n"); + code += "\tassert_eq!(params.p1.x, p3.x);\n"; + code += "\tassert_eq!(params.p1.y, p3.y);\n\n"; + index += 1; + } + + let initial_index = index; + while let Some((p1, p3)) = get_secp256r1_dbl_test_data(index - initial_index) { + code += &format!( + "\t// secp256r1_dbl test rows: {}-{}\n\n", + index * ARITH_EQ_ROWS_BY_OP, + (index + 1) * ARITH_EQ_ROWS_BY_OP - 1 + ); + let p1_x: [u64; 4] = p1[0..4].try_into().unwrap(); + let p1_y: [u64; 4] = p1[4..8].try_into().unwrap(); + code += &format!( + "\tlet mut p1 = SyscallPoint256 {{\n\t\tx: {p1_x:?},\n\t\ty: {p1_y:?}\n\t}};\n" + ); + code += "\tsyscall_secp256r1_dbl(&mut p1);\n"; + let p3_x: [u64; 4] = p3[0..4].try_into().unwrap(); + let p3_y: [u64; 4] = p3[4..8].try_into().unwrap(); + code += + &format!("\tlet p3 = SyscallPoint256 {{\n\t\tx: {p3_x:?},\n\t\ty: {p3_y:?}\n\t}};\n"); + code += "\tassert_eq!(&p1.x, &p3.x);\n"; + code += "\tassert_eq!(&p1.y, &p3.y);\n\n"; + index += 1; + } + code += "\tlet mut params = SyscallBn254CurveAddParams { p1: &mut p1, p2: &p2 };\n"; let initial_index = index; while let Some((p1, p2, p3)) = get_bn254_curve_add_test_data(index - initial_index) { diff --git a/precompiles/arith_eq/src/arith_eq_test_secp256r1.rs b/precompiles/arith_eq/src/arith_eq_test_secp256r1.rs new file mode 100644 index 000000000..e34981fd5 --- /dev/null +++ b/precompiles/arith_eq/src/arith_eq_test_secp256r1.rs @@ -0,0 +1,67 @@ +use ark_ff::BigInt; +use ark_secp256r1::Fq as Secp256r1Field; +use ark_std::{One, Zero}; +use std::time::Instant; +#[cfg(any(feature = "test_data", feature = "test_data_secp256r1"))] +mod test_data; +#[cfg(any(feature = "test_data", feature = "test_data_secp256r1"))] +use precompiles_helpers::{secp256r1_add, secp256r1_dbl}; +#[cfg(any(feature = "test_data", feature = "test_data_secp256r1"))] +use test_data::{get_secp256r1_add_test_data, get_secp256r1_dbl_test_data}; + +fn verify_secp256r1_add(test_id: usize, p1: &[u64; 8], p2: &[u64; 8], p: &mut [u64; 8]) { + let mut _p = [0u64; 8]; + secp256r1_add(p1, p2, &mut _p); + assert_eq!(&p[..], &_p[..8], "fail test {}", test_id); + println!("Test #{} (secp256r1_add) .... [\x1B[32mOK\x1B[0m]", test_id) +} + +fn verify_secp256r1_dbl(test_id: usize, p1: &[u64; 8], p: &mut [u64; 8]) { + let mut _p = [0u64; 8]; + secp256r1_dbl(p1, &mut _p); + assert_eq!(&p[..], &_p[..8], "fail test {}", test_id); + println!("Test #{} (secp256r1_dbl) .... [\x1B[32mOK\x1B[0m]", test_id) +} + +#[cfg(any(feature = "test_data", feature = "test_data_secp256r1"))] +fn test() { + let mut index = 0; + while let Some((p1, p2, mut p3)) = get_secp256r1_add_test_data(index) { + verify_secp256r1_add(index, &p1, &p2, &mut p3); + index += 1; + } + index = 0; + while let Some((p1, mut p3)) = get_secp256r1_dbl_test_data(index) { + verify_secp256r1_dbl(index, &p1, &mut p3); + index += 1; + } + + // Run the first test a million times to measure performance + if let Some((p1, p2, mut p3)) = get_secp256r1_add_test_data(0) { + let start = Instant::now(); + for _ in 0..1000000 { + secp256r1_add(&p1, &p2, &mut p3); + } + let duration = start.elapsed(); + let secs = duration.as_secs_f64(); + let tp = if secs == 0.0 { 1_f64 } else { 1_f64 / secs }; + println!("Duration = {:.4} sec, TP = {:.4} M/sec", secs, tp); + } +} + +fn main() { + let arr = BigInt::<4>([ + 0xfffffffffffffffe, + 0x00000000ffffffff, + 0x0000000000000000, + 0xffffffff00000001, + ]); + + let element = Secp256r1Field::from(arr); + println!("Element: {:?}", element); + let one = Secp256r1Field::one(); + let zero = Secp256r1Field::zero(); + let sum = zero - one; + println!("0-1: {:?}", sum); + test(); +} diff --git a/precompiles/arith_eq/src/equations/mod.rs b/precompiles/arith_eq/src/equations/mod.rs index 9e6e56faf..e97a10437 100644 --- a/precompiles/arith_eq/src/equations/mod.rs +++ b/precompiles/arith_eq/src/equations/mod.rs @@ -14,6 +14,10 @@ mod secp256k1_add; mod secp256k1_dbl; mod secp256k1_x3; mod secp256k1_y3; +mod secp256r1_add; +mod secp256r1_dbl; +mod secp256r1_x3; +mod secp256r1_y3; pub use arith256::*; pub use arith256_mod::*; @@ -31,3 +35,7 @@ pub use secp256k1_add::*; pub use secp256k1_dbl::*; pub use secp256k1_x3::*; pub use secp256k1_y3::*; +pub use secp256r1_add::*; +pub use secp256r1_dbl::*; +pub use secp256r1_x3::*; +pub use secp256r1_y3::*; diff --git a/precompiles/arith_eq/src/equations/secp256r1_add.rs b/precompiles/arith_eq/src/equations/secp256r1_add.rs new file mode 100644 index 000000000..f53775ac8 --- /dev/null +++ b/precompiles/arith_eq/src/equations/secp256r1_add.rs @@ -0,0 +1,755 @@ +// code generated +// +// equation: s*x2-s*x1-y2+y1-p*q0+p*offset +// +// p: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF +// offset: 0x20000000000000000000000000000000000000000000000000000000000000000 +// (p*offset): 0x1FFFFFFFE00000002000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFE0000000000000000000000000000000000000000000000000000000000000000 +// +// chunks:16 +// chunk_bits:16 +// terms_by_clock: 2 + +pub struct Secp256r1Add {} + +impl Secp256r1Add { + #[allow(clippy::too_many_arguments)] + pub fn calculate( + icol: u8, + x1: &[i64; 16], + y1: &[i64; 16], + x2: &[i64; 16], + y2: &[i64; 16], + s: &[i64; 16], + q0: &[i64; 16], + ) -> i64 { + match icol { + 0 => s[0] * x2[0] - s[0] * x1[0] - y2[0] + y1[0] - 0xFFFF * q0[0], + 1 => { + s[1] * x2[0] + s[0] * x2[1] - s[1] * x1[0] - s[0] * x1[1] - y2[1] + y1[1] + - 0xFFFF * q0[0] + - 0xFFFF * q0[1] + } + 2 => { + s[2] * x2[0] + s[1] * x2[1] + s[0] * x2[2] + - s[2] * x1[0] + - s[1] * x1[1] + - s[0] * x1[2] + - y2[2] + + y1[2] + - 0xFFFF * q0[0] + - 0xFFFF * q0[1] + - 0xFFFF * q0[2] + } + 3 => { + s[3] * x2[0] + s[2] * x2[1] + s[1] * x2[2] + s[0] * x2[3] + - s[3] * x1[0] + - s[2] * x1[1] + - s[1] * x1[2] + - s[0] * x1[3] + - y2[3] + + y1[3] + - 0xFFFF * q0[0] + - 0xFFFF * q0[1] + - 0xFFFF * q0[2] + - 0xFFFF * q0[3] + } + 4 => { + s[4] * x2[0] + s[3] * x2[1] + s[2] * x2[2] + s[1] * x2[3] + s[0] * x2[4] + - s[4] * x1[0] + - s[3] * x1[1] + - s[2] * x1[2] + - s[1] * x1[3] + - s[0] * x1[4] + - y2[4] + + y1[4] + - 0xFFFF * q0[0] + - 0xFFFF * q0[1] + - 0xFFFF * q0[2] + - 0xFFFF * q0[3] + - 0xFFFF * q0[4] + } + 5 => { + s[5] * x2[0] + + s[4] * x2[1] + + s[3] * x2[2] + + s[2] * x2[3] + + s[1] * x2[4] + + s[0] * x2[5] + - s[5] * x1[0] + - s[4] * x1[1] + - s[3] * x1[2] + - s[2] * x1[3] + - s[1] * x1[4] + - s[0] * x1[5] + - y2[5] + + y1[5] + - 0xFFFF * q0[0] + - 0xFFFF * q0[1] + - 0xFFFF * q0[2] + - 0xFFFF * q0[3] + - 0xFFFF * q0[4] + - 0xFFFF * q0[5] + } + 6 => { + s[6] * x2[0] + + s[5] * x2[1] + + s[4] * x2[2] + + s[3] * x2[3] + + s[2] * x2[4] + + s[1] * x2[5] + + s[0] * x2[6] + - s[6] * x1[0] + - s[5] * x1[1] + - s[4] * x1[2] + - s[3] * x1[3] + - s[2] * x1[4] + - s[1] * x1[5] + - s[0] * x1[6] + - y2[6] + + y1[6] + - 0xFFFF * q0[1] + - 0xFFFF * q0[2] + - 0xFFFF * q0[3] + - 0xFFFF * q0[4] + - 0xFFFF * q0[5] + - 0xFFFF * q0[6] + } + 7 => { + s[7] * x2[0] + + s[6] * x2[1] + + s[5] * x2[2] + + s[4] * x2[3] + + s[3] * x2[4] + + s[2] * x2[5] + + s[1] * x2[6] + + s[0] * x2[7] + - s[7] * x1[0] + - s[6] * x1[1] + - s[5] * x1[2] + - s[4] * x1[3] + - s[3] * x1[4] + - s[2] * x1[5] + - s[1] * x1[6] + - s[0] * x1[7] + - y2[7] + + y1[7] + - 0xFFFF * q0[2] + - 0xFFFF * q0[3] + - 0xFFFF * q0[4] + - 0xFFFF * q0[5] + - 0xFFFF * q0[6] + - 0xFFFF * q0[7] + } + 8 => { + s[8] * x2[0] + + s[7] * x2[1] + + s[6] * x2[2] + + s[5] * x2[3] + + s[4] * x2[4] + + s[3] * x2[5] + + s[2] * x2[6] + + s[1] * x2[7] + + s[0] * x2[8] + - s[8] * x1[0] + - s[7] * x1[1] + - s[6] * x1[2] + - s[5] * x1[3] + - s[4] * x1[4] + - s[3] * x1[5] + - s[2] * x1[6] + - s[1] * x1[7] + - s[0] * x1[8] + - y2[8] + + y1[8] + - 0xFFFF * q0[3] + - 0xFFFF * q0[4] + - 0xFFFF * q0[5] + - 0xFFFF * q0[6] + - 0xFFFF * q0[7] + - 0xFFFF * q0[8] + } + 9 => { + s[9] * x2[0] + + s[8] * x2[1] + + s[7] * x2[2] + + s[6] * x2[3] + + s[5] * x2[4] + + s[4] * x2[5] + + s[3] * x2[6] + + s[2] * x2[7] + + s[1] * x2[8] + + s[0] * x2[9] + - s[9] * x1[0] + - s[8] * x1[1] + - s[7] * x1[2] + - s[6] * x1[3] + - s[5] * x1[4] + - s[4] * x1[5] + - s[3] * x1[6] + - s[2] * x1[7] + - s[1] * x1[8] + - s[0] * x1[9] + - y2[9] + + y1[9] + - 0xFFFF * q0[4] + - 0xFFFF * q0[5] + - 0xFFFF * q0[6] + - 0xFFFF * q0[7] + - 0xFFFF * q0[8] + - 0xFFFF * q0[9] + } + 10 => { + s[10] * x2[0] + + s[9] * x2[1] + + s[8] * x2[2] + + s[7] * x2[3] + + s[6] * x2[4] + + s[5] * x2[5] + + s[4] * x2[6] + + s[3] * x2[7] + + s[2] * x2[8] + + s[1] * x2[9] + + s[0] * x2[10] + - s[10] * x1[0] + - s[9] * x1[1] + - s[8] * x1[2] + - s[7] * x1[3] + - s[6] * x1[4] + - s[5] * x1[5] + - s[4] * x1[6] + - s[3] * x1[7] + - s[2] * x1[8] + - s[1] * x1[9] + - s[0] * x1[10] + - y2[10] + + y1[10] + - 0xFFFF * q0[5] + - 0xFFFF * q0[6] + - 0xFFFF * q0[7] + - 0xFFFF * q0[8] + - 0xFFFF * q0[9] + - 0xFFFF * q0[10] + } + 11 => { + s[11] * x2[0] + + s[10] * x2[1] + + s[9] * x2[2] + + s[8] * x2[3] + + s[7] * x2[4] + + s[6] * x2[5] + + s[5] * x2[6] + + s[4] * x2[7] + + s[3] * x2[8] + + s[2] * x2[9] + + s[1] * x2[10] + + s[0] * x2[11] + - s[11] * x1[0] + - s[10] * x1[1] + - s[9] * x1[2] + - s[8] * x1[3] + - s[7] * x1[4] + - s[6] * x1[5] + - s[5] * x1[6] + - s[4] * x1[7] + - s[3] * x1[8] + - s[2] * x1[9] + - s[1] * x1[10] + - s[0] * x1[11] + - y2[11] + + y1[11] + - 0xFFFF * q0[6] + - 0xFFFF * q0[7] + - 0xFFFF * q0[8] + - 0xFFFF * q0[9] + - 0xFFFF * q0[10] + - 0xFFFF * q0[11] + } + 12 => { + s[12] * x2[0] + + s[11] * x2[1] + + s[10] * x2[2] + + s[9] * x2[3] + + s[8] * x2[4] + + s[7] * x2[5] + + s[6] * x2[6] + + s[5] * x2[7] + + s[4] * x2[8] + + s[3] * x2[9] + + s[2] * x2[10] + + s[1] * x2[11] + + s[0] * x2[12] + - s[12] * x1[0] + - s[11] * x1[1] + - s[10] * x1[2] + - s[9] * x1[3] + - s[8] * x1[4] + - s[7] * x1[5] + - s[6] * x1[6] + - s[5] * x1[7] + - s[4] * x1[8] + - s[3] * x1[9] + - s[2] * x1[10] + - s[1] * x1[11] + - s[0] * x1[12] + - y2[12] + + y1[12] + - q0[0] + - 0xFFFF * q0[7] + - 0xFFFF * q0[8] + - 0xFFFF * q0[9] + - 0xFFFF * q0[10] + - 0xFFFF * q0[11] + - 0xFFFF * q0[12] + } + 13 => { + s[13] * x2[0] + + s[12] * x2[1] + + s[11] * x2[2] + + s[10] * x2[3] + + s[9] * x2[4] + + s[8] * x2[5] + + s[7] * x2[6] + + s[6] * x2[7] + + s[5] * x2[8] + + s[4] * x2[9] + + s[3] * x2[10] + + s[2] * x2[11] + + s[1] * x2[12] + + s[0] * x2[13] + - s[13] * x1[0] + - s[12] * x1[1] + - s[11] * x1[2] + - s[10] * x1[3] + - s[9] * x1[4] + - s[8] * x1[5] + - s[7] * x1[6] + - s[6] * x1[7] + - s[5] * x1[8] + - s[4] * x1[9] + - s[3] * x1[10] + - s[2] * x1[11] + - s[1] * x1[12] + - s[0] * x1[13] + - y2[13] + + y1[13] + - q0[1] + - 0xFFFF * q0[8] + - 0xFFFF * q0[9] + - 0xFFFF * q0[10] + - 0xFFFF * q0[11] + - 0xFFFF * q0[12] + - 0xFFFF * q0[13] + } + 14 => { + s[14] * x2[0] + + s[13] * x2[1] + + s[12] * x2[2] + + s[11] * x2[3] + + s[10] * x2[4] + + s[9] * x2[5] + + s[8] * x2[6] + + s[7] * x2[7] + + s[6] * x2[8] + + s[5] * x2[9] + + s[4] * x2[10] + + s[3] * x2[11] + + s[2] * x2[12] + + s[1] * x2[13] + + s[0] * x2[14] + - s[14] * x1[0] + - s[13] * x1[1] + - s[12] * x1[2] + - s[11] * x1[3] + - s[10] * x1[4] + - s[9] * x1[5] + - s[8] * x1[6] + - s[7] * x1[7] + - s[6] * x1[8] + - s[5] * x1[9] + - s[4] * x1[10] + - s[3] * x1[11] + - s[2] * x1[12] + - s[1] * x1[13] + - s[0] * x1[14] + - y2[14] + + y1[14] + - 0xFFFF * q0[0] + - q0[2] + - 0xFFFF * q0[9] + - 0xFFFF * q0[10] + - 0xFFFF * q0[11] + - 0xFFFF * q0[12] + - 0xFFFF * q0[13] + - 0xFFFF * q0[14] + } + 15 => { + s[15] * x2[0] + + s[14] * x2[1] + + s[13] * x2[2] + + s[12] * x2[3] + + s[11] * x2[4] + + s[10] * x2[5] + + s[9] * x2[6] + + s[8] * x2[7] + + s[7] * x2[8] + + s[6] * x2[9] + + s[5] * x2[10] + + s[4] * x2[11] + + s[3] * x2[12] + + s[2] * x2[13] + + s[1] * x2[14] + + s[0] * x2[15] + - s[15] * x1[0] + - s[14] * x1[1] + - s[13] * x1[2] + - s[12] * x1[3] + - s[11] * x1[4] + - s[10] * x1[5] + - s[9] * x1[6] + - s[8] * x1[7] + - s[7] * x1[8] + - s[6] * x1[9] + - s[5] * x1[10] + - s[4] * x1[11] + - s[3] * x1[12] + - s[2] * x1[13] + - s[1] * x1[14] + - s[0] * x1[15] + - y2[15] + + y1[15] + - 0xFFFF * q0[0] + - 0xFFFF * q0[1] + - q0[3] + - 0xFFFF * q0[10] + - 0xFFFF * q0[11] + - 0xFFFF * q0[12] + - 0xFFFF * q0[13] + - 0xFFFF * q0[14] + - 0xFFFF * q0[15] + } + 16 => { + s[15] * x2[1] + + s[14] * x2[2] + + s[13] * x2[3] + + s[12] * x2[4] + + s[11] * x2[5] + + s[10] * x2[6] + + s[9] * x2[7] + + s[8] * x2[8] + + s[7] * x2[9] + + s[6] * x2[10] + + s[5] * x2[11] + + s[4] * x2[12] + + s[3] * x2[13] + + s[2] * x2[14] + + s[1] * x2[15] + - s[15] * x1[1] + - s[14] * x1[2] + - s[13] * x1[3] + - s[12] * x1[4] + - s[11] * x1[5] + - s[10] * x1[6] + - s[9] * x1[7] + - s[8] * x1[8] + - s[7] * x1[9] + - s[6] * x1[10] + - s[5] * x1[11] + - s[4] * x1[12] + - s[3] * x1[13] + - s[2] * x1[14] + - s[1] * x1[15] + - 0xFFFF * q0[1] + - 0xFFFF * q0[2] + - q0[4] + - 0xFFFF * q0[11] + - 0xFFFF * q0[12] + - 0xFFFF * q0[13] + - 0xFFFF * q0[14] + - 0xFFFF * q0[15] + + 0xFFFE + } + 17 => { + s[15] * x2[2] + + s[14] * x2[3] + + s[13] * x2[4] + + s[12] * x2[5] + + s[11] * x2[6] + + s[10] * x2[7] + + s[9] * x2[8] + + s[8] * x2[9] + + s[7] * x2[10] + + s[6] * x2[11] + + s[5] * x2[12] + + s[4] * x2[13] + + s[3] * x2[14] + + s[2] * x2[15] + - s[15] * x1[2] + - s[14] * x1[3] + - s[13] * x1[4] + - s[12] * x1[5] + - s[11] * x1[6] + - s[10] * x1[7] + - s[9] * x1[8] + - s[8] * x1[9] + - s[7] * x1[10] + - s[6] * x1[11] + - s[5] * x1[12] + - s[4] * x1[13] + - s[3] * x1[14] + - s[2] * x1[15] + - 0xFFFF * q0[2] + - 0xFFFF * q0[3] + - q0[5] + - 0xFFFF * q0[12] + - 0xFFFF * q0[13] + - 0xFFFF * q0[14] + - 0xFFFF * q0[15] + + 0xFFFF + } + 18 => { + s[15] * x2[3] + + s[14] * x2[4] + + s[13] * x2[5] + + s[12] * x2[6] + + s[11] * x2[7] + + s[10] * x2[8] + + s[9] * x2[9] + + s[8] * x2[10] + + s[7] * x2[11] + + s[6] * x2[12] + + s[5] * x2[13] + + s[4] * x2[14] + + s[3] * x2[15] + - s[15] * x1[3] + - s[14] * x1[4] + - s[13] * x1[5] + - s[12] * x1[6] + - s[11] * x1[7] + - s[10] * x1[8] + - s[9] * x1[9] + - s[8] * x1[10] + - s[7] * x1[11] + - s[6] * x1[12] + - s[5] * x1[13] + - s[4] * x1[14] + - s[3] * x1[15] + - 0xFFFF * q0[3] + - 0xFFFF * q0[4] + - q0[6] + - 0xFFFF * q0[13] + - 0xFFFF * q0[14] + - 0xFFFF * q0[15] + + 0xFFFF + } + 19 => { + s[15] * x2[4] + + s[14] * x2[5] + + s[13] * x2[6] + + s[12] * x2[7] + + s[11] * x2[8] + + s[10] * x2[9] + + s[9] * x2[10] + + s[8] * x2[11] + + s[7] * x2[12] + + s[6] * x2[13] + + s[5] * x2[14] + + s[4] * x2[15] + - s[15] * x1[4] + - s[14] * x1[5] + - s[13] * x1[6] + - s[12] * x1[7] + - s[11] * x1[8] + - s[10] * x1[9] + - s[9] * x1[10] + - s[8] * x1[11] + - s[7] * x1[12] + - s[6] * x1[13] + - s[5] * x1[14] + - s[4] * x1[15] + - 0xFFFF * q0[4] + - 0xFFFF * q0[5] + - q0[7] + - 0xFFFF * q0[14] + - 0xFFFF * q0[15] + + 0xFFFF + } + 20 => { + s[15] * x2[5] + + s[14] * x2[6] + + s[13] * x2[7] + + s[12] * x2[8] + + s[11] * x2[9] + + s[10] * x2[10] + + s[9] * x2[11] + + s[8] * x2[12] + + s[7] * x2[13] + + s[6] * x2[14] + + s[5] * x2[15] + - s[15] * x1[5] + - s[14] * x1[6] + - s[13] * x1[7] + - s[12] * x1[8] + - s[11] * x1[9] + - s[10] * x1[10] + - s[9] * x1[11] + - s[8] * x1[12] + - s[7] * x1[13] + - s[6] * x1[14] + - s[5] * x1[15] + - 0xFFFF * q0[5] + - 0xFFFF * q0[6] + - q0[8] + - 0xFFFF * q0[15] + + 0xFFFF + } + 21 => { + s[15] * x2[6] + + s[14] * x2[7] + + s[13] * x2[8] + + s[12] * x2[9] + + s[11] * x2[10] + + s[10] * x2[11] + + s[9] * x2[12] + + s[8] * x2[13] + + s[7] * x2[14] + + s[6] * x2[15] + - s[15] * x1[6] + - s[14] * x1[7] + - s[13] * x1[8] + - s[12] * x1[9] + - s[11] * x1[10] + - s[10] * x1[11] + - s[9] * x1[12] + - s[8] * x1[13] + - s[7] * x1[14] + - s[6] * x1[15] + - 0xFFFF * q0[6] + - 0xFFFF * q0[7] + - q0[9] + + 0xFFFF + } + 22 => { + s[15] * x2[7] + + s[14] * x2[8] + + s[13] * x2[9] + + s[12] * x2[10] + + s[11] * x2[11] + + s[10] * x2[12] + + s[9] * x2[13] + + s[8] * x2[14] + + s[7] * x2[15] + - s[15] * x1[7] + - s[14] * x1[8] + - s[13] * x1[9] + - s[12] * x1[10] + - s[11] * x1[11] + - s[10] * x1[12] + - s[9] * x1[13] + - s[8] * x1[14] + - s[7] * x1[15] + - 0xFFFF * q0[7] + - 0xFFFF * q0[8] + - q0[10] + + 0x1 + } + 23 => { + s[15] * x2[8] + + s[14] * x2[9] + + s[13] * x2[10] + + s[12] * x2[11] + + s[11] * x2[12] + + s[10] * x2[13] + + s[9] * x2[14] + + s[8] * x2[15] + - s[15] * x1[8] + - s[14] * x1[9] + - s[13] * x1[10] + - s[12] * x1[11] + - s[11] * x1[12] + - s[10] * x1[13] + - s[9] * x1[14] + - s[8] * x1[15] + - 0xFFFF * q0[8] + - 0xFFFF * q0[9] + - q0[11] + } + 24 => { + s[15] * x2[9] + + s[14] * x2[10] + + s[13] * x2[11] + + s[12] * x2[12] + + s[11] * x2[13] + + s[10] * x2[14] + + s[9] * x2[15] + - s[15] * x1[9] + - s[14] * x1[10] + - s[13] * x1[11] + - s[12] * x1[12] + - s[11] * x1[13] + - s[10] * x1[14] + - s[9] * x1[15] + - 0xFFFF * q0[9] + - 0xFFFF * q0[10] + - q0[12] + } + 25 => { + s[15] * x2[10] + + s[14] * x2[11] + + s[13] * x2[12] + + s[12] * x2[13] + + s[11] * x2[14] + + s[10] * x2[15] + - s[15] * x1[10] + - s[14] * x1[11] + - s[13] * x1[12] + - s[12] * x1[13] + - s[11] * x1[14] + - s[10] * x1[15] + - 0xFFFF * q0[10] + - 0xFFFF * q0[11] + - q0[13] + } + 26 => { + s[15] * x2[11] + s[14] * x2[12] + s[13] * x2[13] + s[12] * x2[14] + s[11] * x2[15] + - s[15] * x1[11] + - s[14] * x1[12] + - s[13] * x1[13] + - s[12] * x1[14] + - s[11] * x1[15] + - 0xFFFF * q0[11] + - 0xFFFF * q0[12] + - q0[14] + } + 27 => { + s[15] * x2[12] + s[14] * x2[13] + s[13] * x2[14] + s[12] * x2[15] + - s[15] * x1[12] + - s[14] * x1[13] + - s[13] * x1[14] + - s[12] * x1[15] + - 0xFFFF * q0[12] + - 0xFFFF * q0[13] + - q0[15] + } + 28 => { + s[15] * x2[13] + s[14] * x2[14] + s[13] * x2[15] + - s[15] * x1[13] + - s[14] * x1[14] + - s[13] * x1[15] + - 0xFFFF * q0[13] + - 0xFFFF * q0[14] + + 0x2 + } + 29 => { + s[15] * x2[14] + s[14] * x2[15] + - s[15] * x1[14] + - s[14] * x1[15] + - 0xFFFF * q0[14] + - 0xFFFF * q0[15] + } + 30 => s[15] * x2[15] - s[15] * x1[15] - 0xFFFF * q0[15] + 0xFFFE, + 31 => 0x1FFFF, + _ => 0, + } + } +} diff --git a/precompiles/arith_eq/src/equations/secp256r1_dbl.rs b/precompiles/arith_eq/src/equations/secp256r1_dbl.rs new file mode 100644 index 000000000..65395b8c8 --- /dev/null +++ b/precompiles/arith_eq/src/equations/secp256r1_dbl.rs @@ -0,0 +1,741 @@ +// code generated +// +// equation: 2*s*y1-3*x1*x1-a+p*q0-p*offset +// +// a: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC +// p: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF +// offset: 0x40000000000000000000000000000000000000000000000000000000000000000 +// 2: 2 +// 3: 3 +// (p*offset): 0x3FFFFFFFC00000004000000000000000000000003FFFFFFFFFFFFFFFFFFFFFFFC0000000000000000000000000000000000000000000000000000000000000000 +// +// chunks:16 +// chunk_bits:16 +// terms_by_clock: 2 + +pub struct Secp256r1Dbl {} + +impl Secp256r1Dbl { + #[allow(clippy::too_many_arguments)] + pub fn calculate( + icol: u8, + x1: &[i64; 16], + y1: &[i64; 16], + s: &[i64; 16], + q0: &[i64; 16], + ) -> i64 { + match icol { + 0 => 2 * s[0] * y1[0] - 3 * x1[0] * x1[0] - 0xFFFC + 0xFFFF * q0[0], + 1 => { + 2 * s[1] * y1[0] + 2 * s[0] * y1[1] - 3 * x1[1] * x1[0] - 3 * x1[0] * x1[1] - 0xFFFF + + 0xFFFF * q0[0] + + 0xFFFF * q0[1] + } + 2 => { + 2 * s[2] * y1[0] + 2 * s[1] * y1[1] + 2 * s[0] * y1[2] + - 3 * x1[2] * x1[0] + - 3 * x1[1] * x1[1] + - 3 * x1[0] * x1[2] + - 0xFFFF + + 0xFFFF * q0[0] + + 0xFFFF * q0[1] + + 0xFFFF * q0[2] + } + 3 => { + 2 * s[3] * y1[0] + 2 * s[2] * y1[1] + 2 * s[1] * y1[2] + 2 * s[0] * y1[3] + - 3 * x1[3] * x1[0] + - 3 * x1[2] * x1[1] + - 3 * x1[1] * x1[2] + - 3 * x1[0] * x1[3] + - 0xFFFF + + 0xFFFF * q0[0] + + 0xFFFF * q0[1] + + 0xFFFF * q0[2] + + 0xFFFF * q0[3] + } + 4 => { + 2 * s[4] * y1[0] + + 2 * s[3] * y1[1] + + 2 * s[2] * y1[2] + + 2 * s[1] * y1[3] + + 2 * s[0] * y1[4] + - 3 * x1[4] * x1[0] + - 3 * x1[3] * x1[1] + - 3 * x1[2] * x1[2] + - 3 * x1[1] * x1[3] + - 3 * x1[0] * x1[4] + - 0xFFFF + + 0xFFFF * q0[0] + + 0xFFFF * q0[1] + + 0xFFFF * q0[2] + + 0xFFFF * q0[3] + + 0xFFFF * q0[4] + } + 5 => { + 2 * s[5] * y1[0] + + 2 * s[4] * y1[1] + + 2 * s[3] * y1[2] + + 2 * s[2] * y1[3] + + 2 * s[1] * y1[4] + + 2 * s[0] * y1[5] + - 3 * x1[5] * x1[0] + - 3 * x1[4] * x1[1] + - 3 * x1[3] * x1[2] + - 3 * x1[2] * x1[3] + - 3 * x1[1] * x1[4] + - 3 * x1[0] * x1[5] + - 0xFFFF + + 0xFFFF * q0[0] + + 0xFFFF * q0[1] + + 0xFFFF * q0[2] + + 0xFFFF * q0[3] + + 0xFFFF * q0[4] + + 0xFFFF * q0[5] + } + 6 => { + 2 * s[6] * y1[0] + + 2 * s[5] * y1[1] + + 2 * s[4] * y1[2] + + 2 * s[3] * y1[3] + + 2 * s[2] * y1[4] + + 2 * s[1] * y1[5] + + 2 * s[0] * y1[6] + - 3 * x1[6] * x1[0] + - 3 * x1[5] * x1[1] + - 3 * x1[4] * x1[2] + - 3 * x1[3] * x1[3] + - 3 * x1[2] * x1[4] + - 3 * x1[1] * x1[5] + - 3 * x1[0] * x1[6] + + 0xFFFF * q0[1] + + 0xFFFF * q0[2] + + 0xFFFF * q0[3] + + 0xFFFF * q0[4] + + 0xFFFF * q0[5] + + 0xFFFF * q0[6] + } + 7 => { + 2 * s[7] * y1[0] + + 2 * s[6] * y1[1] + + 2 * s[5] * y1[2] + + 2 * s[4] * y1[3] + + 2 * s[3] * y1[4] + + 2 * s[2] * y1[5] + + 2 * s[1] * y1[6] + + 2 * s[0] * y1[7] + - 3 * x1[7] * x1[0] + - 3 * x1[6] * x1[1] + - 3 * x1[5] * x1[2] + - 3 * x1[4] * x1[3] + - 3 * x1[3] * x1[4] + - 3 * x1[2] * x1[5] + - 3 * x1[1] * x1[6] + - 3 * x1[0] * x1[7] + + 0xFFFF * q0[2] + + 0xFFFF * q0[3] + + 0xFFFF * q0[4] + + 0xFFFF * q0[5] + + 0xFFFF * q0[6] + + 0xFFFF * q0[7] + } + 8 => { + 2 * s[8] * y1[0] + + 2 * s[7] * y1[1] + + 2 * s[6] * y1[2] + + 2 * s[5] * y1[3] + + 2 * s[4] * y1[4] + + 2 * s[3] * y1[5] + + 2 * s[2] * y1[6] + + 2 * s[1] * y1[7] + + 2 * s[0] * y1[8] + - 3 * x1[8] * x1[0] + - 3 * x1[7] * x1[1] + - 3 * x1[6] * x1[2] + - 3 * x1[5] * x1[3] + - 3 * x1[4] * x1[4] + - 3 * x1[3] * x1[5] + - 3 * x1[2] * x1[6] + - 3 * x1[1] * x1[7] + - 3 * x1[0] * x1[8] + + 0xFFFF * q0[3] + + 0xFFFF * q0[4] + + 0xFFFF * q0[5] + + 0xFFFF * q0[6] + + 0xFFFF * q0[7] + + 0xFFFF * q0[8] + } + 9 => { + 2 * s[9] * y1[0] + + 2 * s[8] * y1[1] + + 2 * s[7] * y1[2] + + 2 * s[6] * y1[3] + + 2 * s[5] * y1[4] + + 2 * s[4] * y1[5] + + 2 * s[3] * y1[6] + + 2 * s[2] * y1[7] + + 2 * s[1] * y1[8] + + 2 * s[0] * y1[9] + - 3 * x1[9] * x1[0] + - 3 * x1[8] * x1[1] + - 3 * x1[7] * x1[2] + - 3 * x1[6] * x1[3] + - 3 * x1[5] * x1[4] + - 3 * x1[4] * x1[5] + - 3 * x1[3] * x1[6] + - 3 * x1[2] * x1[7] + - 3 * x1[1] * x1[8] + - 3 * x1[0] * x1[9] + + 0xFFFF * q0[4] + + 0xFFFF * q0[5] + + 0xFFFF * q0[6] + + 0xFFFF * q0[7] + + 0xFFFF * q0[8] + + 0xFFFF * q0[9] + } + 10 => { + 2 * s[10] * y1[0] + + 2 * s[9] * y1[1] + + 2 * s[8] * y1[2] + + 2 * s[7] * y1[3] + + 2 * s[6] * y1[4] + + 2 * s[5] * y1[5] + + 2 * s[4] * y1[6] + + 2 * s[3] * y1[7] + + 2 * s[2] * y1[8] + + 2 * s[1] * y1[9] + + 2 * s[0] * y1[10] + - 3 * x1[10] * x1[0] + - 3 * x1[9] * x1[1] + - 3 * x1[8] * x1[2] + - 3 * x1[7] * x1[3] + - 3 * x1[6] * x1[4] + - 3 * x1[5] * x1[5] + - 3 * x1[4] * x1[6] + - 3 * x1[3] * x1[7] + - 3 * x1[2] * x1[8] + - 3 * x1[1] * x1[9] + - 3 * x1[0] * x1[10] + + 0xFFFF * q0[5] + + 0xFFFF * q0[6] + + 0xFFFF * q0[7] + + 0xFFFF * q0[8] + + 0xFFFF * q0[9] + + 0xFFFF * q0[10] + } + 11 => { + 2 * s[11] * y1[0] + + 2 * s[10] * y1[1] + + 2 * s[9] * y1[2] + + 2 * s[8] * y1[3] + + 2 * s[7] * y1[4] + + 2 * s[6] * y1[5] + + 2 * s[5] * y1[6] + + 2 * s[4] * y1[7] + + 2 * s[3] * y1[8] + + 2 * s[2] * y1[9] + + 2 * s[1] * y1[10] + + 2 * s[0] * y1[11] + - 3 * x1[11] * x1[0] + - 3 * x1[10] * x1[1] + - 3 * x1[9] * x1[2] + - 3 * x1[8] * x1[3] + - 3 * x1[7] * x1[4] + - 3 * x1[6] * x1[5] + - 3 * x1[5] * x1[6] + - 3 * x1[4] * x1[7] + - 3 * x1[3] * x1[8] + - 3 * x1[2] * x1[9] + - 3 * x1[1] * x1[10] + - 3 * x1[0] * x1[11] + + 0xFFFF * q0[6] + + 0xFFFF * q0[7] + + 0xFFFF * q0[8] + + 0xFFFF * q0[9] + + 0xFFFF * q0[10] + + 0xFFFF * q0[11] + } + 12 => { + 2 * s[12] * y1[0] + + 2 * s[11] * y1[1] + + 2 * s[10] * y1[2] + + 2 * s[9] * y1[3] + + 2 * s[8] * y1[4] + + 2 * s[7] * y1[5] + + 2 * s[6] * y1[6] + + 2 * s[5] * y1[7] + + 2 * s[4] * y1[8] + + 2 * s[3] * y1[9] + + 2 * s[2] * y1[10] + + 2 * s[1] * y1[11] + + 2 * s[0] * y1[12] + - 3 * x1[12] * x1[0] + - 3 * x1[11] * x1[1] + - 3 * x1[10] * x1[2] + - 3 * x1[9] * x1[3] + - 3 * x1[8] * x1[4] + - 3 * x1[7] * x1[5] + - 3 * x1[6] * x1[6] + - 3 * x1[5] * x1[7] + - 3 * x1[4] * x1[8] + - 3 * x1[3] * x1[9] + - 3 * x1[2] * x1[10] + - 3 * x1[1] * x1[11] + - 3 * x1[0] * x1[12] + - 0x1 + + q0[0] + + 0xFFFF * q0[7] + + 0xFFFF * q0[8] + + 0xFFFF * q0[9] + + 0xFFFF * q0[10] + + 0xFFFF * q0[11] + + 0xFFFF * q0[12] + } + 13 => { + 2 * s[13] * y1[0] + + 2 * s[12] * y1[1] + + 2 * s[11] * y1[2] + + 2 * s[10] * y1[3] + + 2 * s[9] * y1[4] + + 2 * s[8] * y1[5] + + 2 * s[7] * y1[6] + + 2 * s[6] * y1[7] + + 2 * s[5] * y1[8] + + 2 * s[4] * y1[9] + + 2 * s[3] * y1[10] + + 2 * s[2] * y1[11] + + 2 * s[1] * y1[12] + + 2 * s[0] * y1[13] + - 3 * x1[13] * x1[0] + - 3 * x1[12] * x1[1] + - 3 * x1[11] * x1[2] + - 3 * x1[10] * x1[3] + - 3 * x1[9] * x1[4] + - 3 * x1[8] * x1[5] + - 3 * x1[7] * x1[6] + - 3 * x1[6] * x1[7] + - 3 * x1[5] * x1[8] + - 3 * x1[4] * x1[9] + - 3 * x1[3] * x1[10] + - 3 * x1[2] * x1[11] + - 3 * x1[1] * x1[12] + - 3 * x1[0] * x1[13] + + q0[1] + + 0xFFFF * q0[8] + + 0xFFFF * q0[9] + + 0xFFFF * q0[10] + + 0xFFFF * q0[11] + + 0xFFFF * q0[12] + + 0xFFFF * q0[13] + } + 14 => { + 2 * s[14] * y1[0] + + 2 * s[13] * y1[1] + + 2 * s[12] * y1[2] + + 2 * s[11] * y1[3] + + 2 * s[10] * y1[4] + + 2 * s[9] * y1[5] + + 2 * s[8] * y1[6] + + 2 * s[7] * y1[7] + + 2 * s[6] * y1[8] + + 2 * s[5] * y1[9] + + 2 * s[4] * y1[10] + + 2 * s[3] * y1[11] + + 2 * s[2] * y1[12] + + 2 * s[1] * y1[13] + + 2 * s[0] * y1[14] + - 3 * x1[14] * x1[0] + - 3 * x1[13] * x1[1] + - 3 * x1[12] * x1[2] + - 3 * x1[11] * x1[3] + - 3 * x1[10] * x1[4] + - 3 * x1[9] * x1[5] + - 3 * x1[8] * x1[6] + - 3 * x1[7] * x1[7] + - 3 * x1[6] * x1[8] + - 3 * x1[5] * x1[9] + - 3 * x1[4] * x1[10] + - 3 * x1[3] * x1[11] + - 3 * x1[2] * x1[12] + - 3 * x1[1] * x1[13] + - 3 * x1[0] * x1[14] + - 0xFFFF + + 0xFFFF * q0[0] + + q0[2] + + 0xFFFF * q0[9] + + 0xFFFF * q0[10] + + 0xFFFF * q0[11] + + 0xFFFF * q0[12] + + 0xFFFF * q0[13] + + 0xFFFF * q0[14] + } + 15 => { + 2 * s[15] * y1[0] + + 2 * s[14] * y1[1] + + 2 * s[13] * y1[2] + + 2 * s[12] * y1[3] + + 2 * s[11] * y1[4] + + 2 * s[10] * y1[5] + + 2 * s[9] * y1[6] + + 2 * s[8] * y1[7] + + 2 * s[7] * y1[8] + + 2 * s[6] * y1[9] + + 2 * s[5] * y1[10] + + 2 * s[4] * y1[11] + + 2 * s[3] * y1[12] + + 2 * s[2] * y1[13] + + 2 * s[1] * y1[14] + + 2 * s[0] * y1[15] + - 3 * x1[15] * x1[0] + - 3 * x1[14] * x1[1] + - 3 * x1[13] * x1[2] + - 3 * x1[12] * x1[3] + - 3 * x1[11] * x1[4] + - 3 * x1[10] * x1[5] + - 3 * x1[9] * x1[6] + - 3 * x1[8] * x1[7] + - 3 * x1[7] * x1[8] + - 3 * x1[6] * x1[9] + - 3 * x1[5] * x1[10] + - 3 * x1[4] * x1[11] + - 3 * x1[3] * x1[12] + - 3 * x1[2] * x1[13] + - 3 * x1[1] * x1[14] + - 3 * x1[0] * x1[15] + - 0xFFFF + + 0xFFFF * q0[0] + + 0xFFFF * q0[1] + + q0[3] + + 0xFFFF * q0[10] + + 0xFFFF * q0[11] + + 0xFFFF * q0[12] + + 0xFFFF * q0[13] + + 0xFFFF * q0[14] + + 0xFFFF * q0[15] + } + 16 => { + 2 * s[15] * y1[1] + + 2 * s[14] * y1[2] + + 2 * s[13] * y1[3] + + 2 * s[12] * y1[4] + + 2 * s[11] * y1[5] + + 2 * s[10] * y1[6] + + 2 * s[9] * y1[7] + + 2 * s[8] * y1[8] + + 2 * s[7] * y1[9] + + 2 * s[6] * y1[10] + + 2 * s[5] * y1[11] + + 2 * s[4] * y1[12] + + 2 * s[3] * y1[13] + + 2 * s[2] * y1[14] + + 2 * s[1] * y1[15] + - 3 * x1[15] * x1[1] + - 3 * x1[14] * x1[2] + - 3 * x1[13] * x1[3] + - 3 * x1[12] * x1[4] + - 3 * x1[11] * x1[5] + - 3 * x1[10] * x1[6] + - 3 * x1[9] * x1[7] + - 3 * x1[8] * x1[8] + - 3 * x1[7] * x1[9] + - 3 * x1[6] * x1[10] + - 3 * x1[5] * x1[11] + - 3 * x1[4] * x1[12] + - 3 * x1[3] * x1[13] + - 3 * x1[2] * x1[14] + - 3 * x1[1] * x1[15] + + 0xFFFF * q0[1] + + 0xFFFF * q0[2] + + q0[4] + + 0xFFFF * q0[11] + + 0xFFFF * q0[12] + + 0xFFFF * q0[13] + + 0xFFFF * q0[14] + + 0xFFFF * q0[15] + - 0xFFFC + } + 17 => { + 2 * s[15] * y1[2] + + 2 * s[14] * y1[3] + + 2 * s[13] * y1[4] + + 2 * s[12] * y1[5] + + 2 * s[11] * y1[6] + + 2 * s[10] * y1[7] + + 2 * s[9] * y1[8] + + 2 * s[8] * y1[9] + + 2 * s[7] * y1[10] + + 2 * s[6] * y1[11] + + 2 * s[5] * y1[12] + + 2 * s[4] * y1[13] + + 2 * s[3] * y1[14] + + 2 * s[2] * y1[15] + - 3 * x1[15] * x1[2] + - 3 * x1[14] * x1[3] + - 3 * x1[13] * x1[4] + - 3 * x1[12] * x1[5] + - 3 * x1[11] * x1[6] + - 3 * x1[10] * x1[7] + - 3 * x1[9] * x1[8] + - 3 * x1[8] * x1[9] + - 3 * x1[7] * x1[10] + - 3 * x1[6] * x1[11] + - 3 * x1[5] * x1[12] + - 3 * x1[4] * x1[13] + - 3 * x1[3] * x1[14] + - 3 * x1[2] * x1[15] + + 0xFFFF * q0[2] + + 0xFFFF * q0[3] + + q0[5] + + 0xFFFF * q0[12] + + 0xFFFF * q0[13] + + 0xFFFF * q0[14] + + 0xFFFF * q0[15] + - 0xFFFF + } + 18 => { + 2 * s[15] * y1[3] + + 2 * s[14] * y1[4] + + 2 * s[13] * y1[5] + + 2 * s[12] * y1[6] + + 2 * s[11] * y1[7] + + 2 * s[10] * y1[8] + + 2 * s[9] * y1[9] + + 2 * s[8] * y1[10] + + 2 * s[7] * y1[11] + + 2 * s[6] * y1[12] + + 2 * s[5] * y1[13] + + 2 * s[4] * y1[14] + + 2 * s[3] * y1[15] + - 3 * x1[15] * x1[3] + - 3 * x1[14] * x1[4] + - 3 * x1[13] * x1[5] + - 3 * x1[12] * x1[6] + - 3 * x1[11] * x1[7] + - 3 * x1[10] * x1[8] + - 3 * x1[9] * x1[9] + - 3 * x1[8] * x1[10] + - 3 * x1[7] * x1[11] + - 3 * x1[6] * x1[12] + - 3 * x1[5] * x1[13] + - 3 * x1[4] * x1[14] + - 3 * x1[3] * x1[15] + + 0xFFFF * q0[3] + + 0xFFFF * q0[4] + + q0[6] + + 0xFFFF * q0[13] + + 0xFFFF * q0[14] + + 0xFFFF * q0[15] + - 0xFFFF + } + 19 => { + 2 * s[15] * y1[4] + + 2 * s[14] * y1[5] + + 2 * s[13] * y1[6] + + 2 * s[12] * y1[7] + + 2 * s[11] * y1[8] + + 2 * s[10] * y1[9] + + 2 * s[9] * y1[10] + + 2 * s[8] * y1[11] + + 2 * s[7] * y1[12] + + 2 * s[6] * y1[13] + + 2 * s[5] * y1[14] + + 2 * s[4] * y1[15] + - 3 * x1[15] * x1[4] + - 3 * x1[14] * x1[5] + - 3 * x1[13] * x1[6] + - 3 * x1[12] * x1[7] + - 3 * x1[11] * x1[8] + - 3 * x1[10] * x1[9] + - 3 * x1[9] * x1[10] + - 3 * x1[8] * x1[11] + - 3 * x1[7] * x1[12] + - 3 * x1[6] * x1[13] + - 3 * x1[5] * x1[14] + - 3 * x1[4] * x1[15] + + 0xFFFF * q0[4] + + 0xFFFF * q0[5] + + q0[7] + + 0xFFFF * q0[14] + + 0xFFFF * q0[15] + - 0xFFFF + } + 20 => { + 2 * s[15] * y1[5] + + 2 * s[14] * y1[6] + + 2 * s[13] * y1[7] + + 2 * s[12] * y1[8] + + 2 * s[11] * y1[9] + + 2 * s[10] * y1[10] + + 2 * s[9] * y1[11] + + 2 * s[8] * y1[12] + + 2 * s[7] * y1[13] + + 2 * s[6] * y1[14] + + 2 * s[5] * y1[15] + - 3 * x1[15] * x1[5] + - 3 * x1[14] * x1[6] + - 3 * x1[13] * x1[7] + - 3 * x1[12] * x1[8] + - 3 * x1[11] * x1[9] + - 3 * x1[10] * x1[10] + - 3 * x1[9] * x1[11] + - 3 * x1[8] * x1[12] + - 3 * x1[7] * x1[13] + - 3 * x1[6] * x1[14] + - 3 * x1[5] * x1[15] + + 0xFFFF * q0[5] + + 0xFFFF * q0[6] + + q0[8] + + 0xFFFF * q0[15] + - 0xFFFF + } + 21 => { + 2 * s[15] * y1[6] + + 2 * s[14] * y1[7] + + 2 * s[13] * y1[8] + + 2 * s[12] * y1[9] + + 2 * s[11] * y1[10] + + 2 * s[10] * y1[11] + + 2 * s[9] * y1[12] + + 2 * s[8] * y1[13] + + 2 * s[7] * y1[14] + + 2 * s[6] * y1[15] + - 3 * x1[15] * x1[6] + - 3 * x1[14] * x1[7] + - 3 * x1[13] * x1[8] + - 3 * x1[12] * x1[9] + - 3 * x1[11] * x1[10] + - 3 * x1[10] * x1[11] + - 3 * x1[9] * x1[12] + - 3 * x1[8] * x1[13] + - 3 * x1[7] * x1[14] + - 3 * x1[6] * x1[15] + + 0xFFFF * q0[6] + + 0xFFFF * q0[7] + + q0[9] + - 0xFFFF + } + 22 => { + 2 * s[15] * y1[7] + + 2 * s[14] * y1[8] + + 2 * s[13] * y1[9] + + 2 * s[12] * y1[10] + + 2 * s[11] * y1[11] + + 2 * s[10] * y1[12] + + 2 * s[9] * y1[13] + + 2 * s[8] * y1[14] + + 2 * s[7] * y1[15] + - 3 * x1[15] * x1[7] + - 3 * x1[14] * x1[8] + - 3 * x1[13] * x1[9] + - 3 * x1[12] * x1[10] + - 3 * x1[11] * x1[11] + - 3 * x1[10] * x1[12] + - 3 * x1[9] * x1[13] + - 3 * x1[8] * x1[14] + - 3 * x1[7] * x1[15] + + 0xFFFF * q0[7] + + 0xFFFF * q0[8] + + q0[10] + - 0x3 + } + 23 => { + 2 * s[15] * y1[8] + + 2 * s[14] * y1[9] + + 2 * s[13] * y1[10] + + 2 * s[12] * y1[11] + + 2 * s[11] * y1[12] + + 2 * s[10] * y1[13] + + 2 * s[9] * y1[14] + + 2 * s[8] * y1[15] + - 3 * x1[15] * x1[8] + - 3 * x1[14] * x1[9] + - 3 * x1[13] * x1[10] + - 3 * x1[12] * x1[11] + - 3 * x1[11] * x1[12] + - 3 * x1[10] * x1[13] + - 3 * x1[9] * x1[14] + - 3 * x1[8] * x1[15] + + 0xFFFF * q0[8] + + 0xFFFF * q0[9] + + q0[11] + } + 24 => { + 2 * s[15] * y1[9] + + 2 * s[14] * y1[10] + + 2 * s[13] * y1[11] + + 2 * s[12] * y1[12] + + 2 * s[11] * y1[13] + + 2 * s[10] * y1[14] + + 2 * s[9] * y1[15] + - 3 * x1[15] * x1[9] + - 3 * x1[14] * x1[10] + - 3 * x1[13] * x1[11] + - 3 * x1[12] * x1[12] + - 3 * x1[11] * x1[13] + - 3 * x1[10] * x1[14] + - 3 * x1[9] * x1[15] + + 0xFFFF * q0[9] + + 0xFFFF * q0[10] + + q0[12] + } + 25 => { + 2 * s[15] * y1[10] + + 2 * s[14] * y1[11] + + 2 * s[13] * y1[12] + + 2 * s[12] * y1[13] + + 2 * s[11] * y1[14] + + 2 * s[10] * y1[15] + - 3 * x1[15] * x1[10] + - 3 * x1[14] * x1[11] + - 3 * x1[13] * x1[12] + - 3 * x1[12] * x1[13] + - 3 * x1[11] * x1[14] + - 3 * x1[10] * x1[15] + + 0xFFFF * q0[10] + + 0xFFFF * q0[11] + + q0[13] + } + 26 => { + 2 * s[15] * y1[11] + + 2 * s[14] * y1[12] + + 2 * s[13] * y1[13] + + 2 * s[12] * y1[14] + + 2 * s[11] * y1[15] + - 3 * x1[15] * x1[11] + - 3 * x1[14] * x1[12] + - 3 * x1[13] * x1[13] + - 3 * x1[12] * x1[14] + - 3 * x1[11] * x1[15] + + 0xFFFF * q0[11] + + 0xFFFF * q0[12] + + q0[14] + } + 27 => { + 2 * s[15] * y1[12] + 2 * s[14] * y1[13] + 2 * s[13] * y1[14] + 2 * s[12] * y1[15] + - 3 * x1[15] * x1[12] + - 3 * x1[14] * x1[13] + - 3 * x1[13] * x1[14] + - 3 * x1[12] * x1[15] + + 0xFFFF * q0[12] + + 0xFFFF * q0[13] + + q0[15] + } + 28 => { + 2 * s[15] * y1[13] + 2 * s[14] * y1[14] + 2 * s[13] * y1[15] + - 3 * x1[15] * x1[13] + - 3 * x1[14] * x1[14] + - 3 * x1[13] * x1[15] + + 0xFFFF * q0[13] + + 0xFFFF * q0[14] + - 0x4 + } + 29 => { + 2 * s[15] * y1[14] + 2 * s[14] * y1[15] - 3 * x1[15] * x1[14] - 3 * x1[14] * x1[15] + + 0xFFFF * q0[14] + + 0xFFFF * q0[15] + } + 30 => 2 * s[15] * y1[15] - 3 * x1[15] * x1[15] + 0xFFFF * q0[15] - 0xFFFC, + 31 => -0x3FFFF, + _ => 0, + } + } +} diff --git a/precompiles/arith_eq/src/equations/secp256r1_x3.rs b/precompiles/arith_eq/src/equations/secp256r1_x3.rs new file mode 100644 index 000000000..8a884346f --- /dev/null +++ b/precompiles/arith_eq/src/equations/secp256r1_x3.rs @@ -0,0 +1,502 @@ +// code generated +// +// equation: s*s-x1-x2-x3-p*q1+p*offset +// +// p: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF +// offset: 0x4 +// (p*offset): 0x3FFFFFFFC00000004000000000000000000000003FFFFFFFFFFFFFFFFFFFFFFFC +// +// chunks:16 +// chunk_bits:16 +// terms_by_clock: 2 + +pub struct Secp256r1X3 {} + +impl Secp256r1X3 { + #[allow(clippy::too_many_arguments)] + pub fn calculate( + icol: u8, + x1: &[i64; 16], + x2: &[i64; 16], + x3: &[i64; 16], + s: &[i64; 16], + q1: &[i64; 16], + ) -> i64 { + match icol { + 0 => s[0] * s[0] - x1[0] - x2[0] - x3[0] - 0xFFFF * q1[0] + 0xFFFC, + 1 => { + s[1] * s[0] + s[0] * s[1] - x1[1] - x2[1] - x3[1] - 0xFFFF * q1[0] - 0xFFFF * q1[1] + + 0xFFFF + } + 2 => { + s[2] * s[0] + s[1] * s[1] + s[0] * s[2] + - x1[2] + - x2[2] + - x3[2] + - 0xFFFF * q1[0] + - 0xFFFF * q1[1] + - 0xFFFF * q1[2] + + 0xFFFF + } + 3 => { + s[3] * s[0] + s[2] * s[1] + s[1] * s[2] + s[0] * s[3] + - x1[3] + - x2[3] + - x3[3] + - 0xFFFF * q1[0] + - 0xFFFF * q1[1] + - 0xFFFF * q1[2] + - 0xFFFF * q1[3] + + 0xFFFF + } + 4 => { + s[4] * s[0] + s[3] * s[1] + s[2] * s[2] + s[1] * s[3] + s[0] * s[4] + - x1[4] + - x2[4] + - x3[4] + - 0xFFFF * q1[0] + - 0xFFFF * q1[1] + - 0xFFFF * q1[2] + - 0xFFFF * q1[3] + - 0xFFFF * q1[4] + + 0xFFFF + } + 5 => { + s[5] * s[0] + s[4] * s[1] + s[3] * s[2] + s[2] * s[3] + s[1] * s[4] + s[0] * s[5] + - x1[5] + - x2[5] + - x3[5] + - 0xFFFF * q1[0] + - 0xFFFF * q1[1] + - 0xFFFF * q1[2] + - 0xFFFF * q1[3] + - 0xFFFF * q1[4] + - 0xFFFF * q1[5] + + 0xFFFF + } + 6 => { + s[6] * s[0] + + s[5] * s[1] + + s[4] * s[2] + + s[3] * s[3] + + s[2] * s[4] + + s[1] * s[5] + + s[0] * s[6] + - x1[6] + - x2[6] + - x3[6] + - 0xFFFF * q1[1] + - 0xFFFF * q1[2] + - 0xFFFF * q1[3] + - 0xFFFF * q1[4] + - 0xFFFF * q1[5] + - 0xFFFF * q1[6] + + 0x3 + } + 7 => { + s[7] * s[0] + + s[6] * s[1] + + s[5] * s[2] + + s[4] * s[3] + + s[3] * s[4] + + s[2] * s[5] + + s[1] * s[6] + + s[0] * s[7] + - x1[7] + - x2[7] + - x3[7] + - 0xFFFF * q1[2] + - 0xFFFF * q1[3] + - 0xFFFF * q1[4] + - 0xFFFF * q1[5] + - 0xFFFF * q1[6] + - 0xFFFF * q1[7] + } + 8 => { + s[8] * s[0] + + s[7] * s[1] + + s[6] * s[2] + + s[5] * s[3] + + s[4] * s[4] + + s[3] * s[5] + + s[2] * s[6] + + s[1] * s[7] + + s[0] * s[8] + - x1[8] + - x2[8] + - x3[8] + - 0xFFFF * q1[3] + - 0xFFFF * q1[4] + - 0xFFFF * q1[5] + - 0xFFFF * q1[6] + - 0xFFFF * q1[7] + - 0xFFFF * q1[8] + } + 9 => { + s[9] * s[0] + + s[8] * s[1] + + s[7] * s[2] + + s[6] * s[3] + + s[5] * s[4] + + s[4] * s[5] + + s[3] * s[6] + + s[2] * s[7] + + s[1] * s[8] + + s[0] * s[9] + - x1[9] + - x2[9] + - x3[9] + - 0xFFFF * q1[4] + - 0xFFFF * q1[5] + - 0xFFFF * q1[6] + - 0xFFFF * q1[7] + - 0xFFFF * q1[8] + - 0xFFFF * q1[9] + } + 10 => { + s[10] * s[0] + + s[9] * s[1] + + s[8] * s[2] + + s[7] * s[3] + + s[6] * s[4] + + s[5] * s[5] + + s[4] * s[6] + + s[3] * s[7] + + s[2] * s[8] + + s[1] * s[9] + + s[0] * s[10] + - x1[10] + - x2[10] + - x3[10] + - 0xFFFF * q1[5] + - 0xFFFF * q1[6] + - 0xFFFF * q1[7] + - 0xFFFF * q1[8] + - 0xFFFF * q1[9] + - 0xFFFF * q1[10] + } + 11 => { + s[11] * s[0] + + s[10] * s[1] + + s[9] * s[2] + + s[8] * s[3] + + s[7] * s[4] + + s[6] * s[5] + + s[5] * s[6] + + s[4] * s[7] + + s[3] * s[8] + + s[2] * s[9] + + s[1] * s[10] + + s[0] * s[11] + - x1[11] + - x2[11] + - x3[11] + - 0xFFFF * q1[6] + - 0xFFFF * q1[7] + - 0xFFFF * q1[8] + - 0xFFFF * q1[9] + - 0xFFFF * q1[10] + - 0xFFFF * q1[11] + } + 12 => { + s[12] * s[0] + + s[11] * s[1] + + s[10] * s[2] + + s[9] * s[3] + + s[8] * s[4] + + s[7] * s[5] + + s[6] * s[6] + + s[5] * s[7] + + s[4] * s[8] + + s[3] * s[9] + + s[2] * s[10] + + s[1] * s[11] + + s[0] * s[12] + - x1[12] + - x2[12] + - x3[12] + - q1[0] + - 0xFFFF * q1[7] + - 0xFFFF * q1[8] + - 0xFFFF * q1[9] + - 0xFFFF * q1[10] + - 0xFFFF * q1[11] + - 0xFFFF * q1[12] + + 0x4 + } + 13 => { + s[13] * s[0] + + s[12] * s[1] + + s[11] * s[2] + + s[10] * s[3] + + s[9] * s[4] + + s[8] * s[5] + + s[7] * s[6] + + s[6] * s[7] + + s[5] * s[8] + + s[4] * s[9] + + s[3] * s[10] + + s[2] * s[11] + + s[1] * s[12] + + s[0] * s[13] + - x1[13] + - x2[13] + - x3[13] + - q1[1] + - 0xFFFF * q1[8] + - 0xFFFF * q1[9] + - 0xFFFF * q1[10] + - 0xFFFF * q1[11] + - 0xFFFF * q1[12] + - 0xFFFF * q1[13] + } + 14 => { + s[14] * s[0] + + s[13] * s[1] + + s[12] * s[2] + + s[11] * s[3] + + s[10] * s[4] + + s[9] * s[5] + + s[8] * s[6] + + s[7] * s[7] + + s[6] * s[8] + + s[5] * s[9] + + s[4] * s[10] + + s[3] * s[11] + + s[2] * s[12] + + s[1] * s[13] + + s[0] * s[14] + - x1[14] + - x2[14] + - x3[14] + - 0xFFFF * q1[0] + - q1[2] + - 0xFFFF * q1[9] + - 0xFFFF * q1[10] + - 0xFFFF * q1[11] + - 0xFFFF * q1[12] + - 0xFFFF * q1[13] + - 0xFFFF * q1[14] + + 0xFFFC + } + 15 => { + s[15] * s[0] + + s[14] * s[1] + + s[13] * s[2] + + s[12] * s[3] + + s[11] * s[4] + + s[10] * s[5] + + s[9] * s[6] + + s[8] * s[7] + + s[7] * s[8] + + s[6] * s[9] + + s[5] * s[10] + + s[4] * s[11] + + s[3] * s[12] + + s[2] * s[13] + + s[1] * s[14] + + s[0] * s[15] + - x1[15] + - x2[15] + - x3[15] + - 0xFFFF * q1[0] + - 0xFFFF * q1[1] + - q1[3] + - 0xFFFF * q1[10] + - 0xFFFF * q1[11] + - 0xFFFF * q1[12] + - 0xFFFF * q1[13] + - 0xFFFF * q1[14] + - 0xFFFF * q1[15] + + 0xFFFF + } + 16 => { + s[15] * s[1] + + s[14] * s[2] + + s[13] * s[3] + + s[12] * s[4] + + s[11] * s[5] + + s[10] * s[6] + + s[9] * s[7] + + s[8] * s[8] + + s[7] * s[9] + + s[6] * s[10] + + s[5] * s[11] + + s[4] * s[12] + + s[3] * s[13] + + s[2] * s[14] + + s[1] * s[15] + - 0xFFFF * q1[1] + - 0xFFFF * q1[2] + - q1[4] + - 0xFFFF * q1[11] + - 0xFFFF * q1[12] + - 0xFFFF * q1[13] + - 0xFFFF * q1[14] + - 0xFFFF * q1[15] + + 0x3 + } + 17 => { + s[15] * s[2] + + s[14] * s[3] + + s[13] * s[4] + + s[12] * s[5] + + s[11] * s[6] + + s[10] * s[7] + + s[9] * s[8] + + s[8] * s[9] + + s[7] * s[10] + + s[6] * s[11] + + s[5] * s[12] + + s[4] * s[13] + + s[3] * s[14] + + s[2] * s[15] + - 0xFFFF * q1[2] + - 0xFFFF * q1[3] + - q1[5] + - 0xFFFF * q1[12] + - 0xFFFF * q1[13] + - 0xFFFF * q1[14] + - 0xFFFF * q1[15] + } + 18 => { + s[15] * s[3] + + s[14] * s[4] + + s[13] * s[5] + + s[12] * s[6] + + s[11] * s[7] + + s[10] * s[8] + + s[9] * s[9] + + s[8] * s[10] + + s[7] * s[11] + + s[6] * s[12] + + s[5] * s[13] + + s[4] * s[14] + + s[3] * s[15] + - 0xFFFF * q1[3] + - 0xFFFF * q1[4] + - q1[6] + - 0xFFFF * q1[13] + - 0xFFFF * q1[14] + - 0xFFFF * q1[15] + } + 19 => { + s[15] * s[4] + + s[14] * s[5] + + s[13] * s[6] + + s[12] * s[7] + + s[11] * s[8] + + s[10] * s[9] + + s[9] * s[10] + + s[8] * s[11] + + s[7] * s[12] + + s[6] * s[13] + + s[5] * s[14] + + s[4] * s[15] + - 0xFFFF * q1[4] + - 0xFFFF * q1[5] + - q1[7] + - 0xFFFF * q1[14] + - 0xFFFF * q1[15] + } + 20 => { + s[15] * s[5] + + s[14] * s[6] + + s[13] * s[7] + + s[12] * s[8] + + s[11] * s[9] + + s[10] * s[10] + + s[9] * s[11] + + s[8] * s[12] + + s[7] * s[13] + + s[6] * s[14] + + s[5] * s[15] + - 0xFFFF * q1[5] + - 0xFFFF * q1[6] + - q1[8] + - 0xFFFF * q1[15] + } + 21 => { + s[15] * s[6] + + s[14] * s[7] + + s[13] * s[8] + + s[12] * s[9] + + s[11] * s[10] + + s[10] * s[11] + + s[9] * s[12] + + s[8] * s[13] + + s[7] * s[14] + + s[6] * s[15] + - 0xFFFF * q1[6] + - 0xFFFF * q1[7] + - q1[9] + } + 22 => { + s[15] * s[7] + + s[14] * s[8] + + s[13] * s[9] + + s[12] * s[10] + + s[11] * s[11] + + s[10] * s[12] + + s[9] * s[13] + + s[8] * s[14] + + s[7] * s[15] + - 0xFFFF * q1[7] + - 0xFFFF * q1[8] + - q1[10] + } + 23 => { + s[15] * s[8] + + s[14] * s[9] + + s[13] * s[10] + + s[12] * s[11] + + s[11] * s[12] + + s[10] * s[13] + + s[9] * s[14] + + s[8] * s[15] + - 0xFFFF * q1[8] + - 0xFFFF * q1[9] + - q1[11] + } + 24 => { + s[15] * s[9] + + s[14] * s[10] + + s[13] * s[11] + + s[12] * s[12] + + s[11] * s[13] + + s[10] * s[14] + + s[9] * s[15] + - 0xFFFF * q1[9] + - 0xFFFF * q1[10] + - q1[12] + } + 25 => { + s[15] * s[10] + + s[14] * s[11] + + s[13] * s[12] + + s[12] * s[13] + + s[11] * s[14] + + s[10] * s[15] + - 0xFFFF * q1[10] + - 0xFFFF * q1[11] + - q1[13] + } + 26 => { + s[15] * s[11] + s[14] * s[12] + s[13] * s[13] + s[12] * s[14] + s[11] * s[15] + - 0xFFFF * q1[11] + - 0xFFFF * q1[12] + - q1[14] + } + 27 => { + s[15] * s[12] + s[14] * s[13] + s[13] * s[14] + s[12] * s[15] + - 0xFFFF * q1[12] + - 0xFFFF * q1[13] + - q1[15] + } + 28 => s[15] * s[13] + s[14] * s[14] + s[13] * s[15] - 0xFFFF * q1[13] - 0xFFFF * q1[14], + 29 => s[15] * s[14] + s[14] * s[15] - 0xFFFF * q1[14] - 0xFFFF * q1[15], + 30 => s[15] * s[15] - 0xFFFF * q1[15], + _ => 0, + } + } +} diff --git a/precompiles/arith_eq/src/equations/secp256r1_y3.rs b/precompiles/arith_eq/src/equations/secp256r1_y3.rs new file mode 100644 index 000000000..fbfdb4add --- /dev/null +++ b/precompiles/arith_eq/src/equations/secp256r1_y3.rs @@ -0,0 +1,753 @@ +// code generated +// +// equation: s*x1-s*x3-y1-y3+p*q2-p*offset +// +// p: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF +// offset: 0x20000000000000000000000000000000000000000000000000000000000000000 +// (p*offset): 0x1FFFFFFFE00000002000000000000000000000001FFFFFFFFFFFFFFFFFFFFFFFE0000000000000000000000000000000000000000000000000000000000000000 +// +// chunks:16 +// chunk_bits:16 +// terms_by_clock: 2 + +pub struct Secp256r1Y3 {} + +impl Secp256r1Y3 { + #[allow(clippy::too_many_arguments)] + pub fn calculate( + icol: u8, + x1: &[i64; 16], + y1: &[i64; 16], + x3: &[i64; 16], + y3: &[i64; 16], + s: &[i64; 16], + q2: &[i64; 16], + ) -> i64 { + match icol { + 0 => s[0] * x1[0] - s[0] * x3[0] - y1[0] - y3[0] + 0xFFFF * q2[0], + 1 => { + s[1] * x1[0] + s[0] * x1[1] - s[1] * x3[0] - s[0] * x3[1] - y1[1] - y3[1] + + 0xFFFF * q2[0] + + 0xFFFF * q2[1] + } + 2 => { + s[2] * x1[0] + s[1] * x1[1] + s[0] * x1[2] + - s[2] * x3[0] + - s[1] * x3[1] + - s[0] * x3[2] + - y1[2] + - y3[2] + + 0xFFFF * q2[0] + + 0xFFFF * q2[1] + + 0xFFFF * q2[2] + } + 3 => { + s[3] * x1[0] + s[2] * x1[1] + s[1] * x1[2] + s[0] * x1[3] + - s[3] * x3[0] + - s[2] * x3[1] + - s[1] * x3[2] + - s[0] * x3[3] + - y1[3] + - y3[3] + + 0xFFFF * q2[0] + + 0xFFFF * q2[1] + + 0xFFFF * q2[2] + + 0xFFFF * q2[3] + } + 4 => { + s[4] * x1[0] + s[3] * x1[1] + s[2] * x1[2] + s[1] * x1[3] + s[0] * x1[4] + - s[4] * x3[0] + - s[3] * x3[1] + - s[2] * x3[2] + - s[1] * x3[3] + - s[0] * x3[4] + - y1[4] + - y3[4] + + 0xFFFF * q2[0] + + 0xFFFF * q2[1] + + 0xFFFF * q2[2] + + 0xFFFF * q2[3] + + 0xFFFF * q2[4] + } + 5 => { + s[5] * x1[0] + + s[4] * x1[1] + + s[3] * x1[2] + + s[2] * x1[3] + + s[1] * x1[4] + + s[0] * x1[5] + - s[5] * x3[0] + - s[4] * x3[1] + - s[3] * x3[2] + - s[2] * x3[3] + - s[1] * x3[4] + - s[0] * x3[5] + - y1[5] + - y3[5] + + 0xFFFF * q2[0] + + 0xFFFF * q2[1] + + 0xFFFF * q2[2] + + 0xFFFF * q2[3] + + 0xFFFF * q2[4] + + 0xFFFF * q2[5] + } + 6 => { + s[6] * x1[0] + + s[5] * x1[1] + + s[4] * x1[2] + + s[3] * x1[3] + + s[2] * x1[4] + + s[1] * x1[5] + + s[0] * x1[6] + - s[6] * x3[0] + - s[5] * x3[1] + - s[4] * x3[2] + - s[3] * x3[3] + - s[2] * x3[4] + - s[1] * x3[5] + - s[0] * x3[6] + - y1[6] + - y3[6] + + 0xFFFF * q2[1] + + 0xFFFF * q2[2] + + 0xFFFF * q2[3] + + 0xFFFF * q2[4] + + 0xFFFF * q2[5] + + 0xFFFF * q2[6] + } + 7 => { + s[7] * x1[0] + + s[6] * x1[1] + + s[5] * x1[2] + + s[4] * x1[3] + + s[3] * x1[4] + + s[2] * x1[5] + + s[1] * x1[6] + + s[0] * x1[7] + - s[7] * x3[0] + - s[6] * x3[1] + - s[5] * x3[2] + - s[4] * x3[3] + - s[3] * x3[4] + - s[2] * x3[5] + - s[1] * x3[6] + - s[0] * x3[7] + - y1[7] + - y3[7] + + 0xFFFF * q2[2] + + 0xFFFF * q2[3] + + 0xFFFF * q2[4] + + 0xFFFF * q2[5] + + 0xFFFF * q2[6] + + 0xFFFF * q2[7] + } + 8 => { + s[8] * x1[0] + + s[7] * x1[1] + + s[6] * x1[2] + + s[5] * x1[3] + + s[4] * x1[4] + + s[3] * x1[5] + + s[2] * x1[6] + + s[1] * x1[7] + + s[0] * x1[8] + - s[8] * x3[0] + - s[7] * x3[1] + - s[6] * x3[2] + - s[5] * x3[3] + - s[4] * x3[4] + - s[3] * x3[5] + - s[2] * x3[6] + - s[1] * x3[7] + - s[0] * x3[8] + - y1[8] + - y3[8] + + 0xFFFF * q2[3] + + 0xFFFF * q2[4] + + 0xFFFF * q2[5] + + 0xFFFF * q2[6] + + 0xFFFF * q2[7] + + 0xFFFF * q2[8] + } + 9 => { + s[9] * x1[0] + + s[8] * x1[1] + + s[7] * x1[2] + + s[6] * x1[3] + + s[5] * x1[4] + + s[4] * x1[5] + + s[3] * x1[6] + + s[2] * x1[7] + + s[1] * x1[8] + + s[0] * x1[9] + - s[9] * x3[0] + - s[8] * x3[1] + - s[7] * x3[2] + - s[6] * x3[3] + - s[5] * x3[4] + - s[4] * x3[5] + - s[3] * x3[6] + - s[2] * x3[7] + - s[1] * x3[8] + - s[0] * x3[9] + - y1[9] + - y3[9] + + 0xFFFF * q2[4] + + 0xFFFF * q2[5] + + 0xFFFF * q2[6] + + 0xFFFF * q2[7] + + 0xFFFF * q2[8] + + 0xFFFF * q2[9] + } + 10 => { + s[10] * x1[0] + + s[9] * x1[1] + + s[8] * x1[2] + + s[7] * x1[3] + + s[6] * x1[4] + + s[5] * x1[5] + + s[4] * x1[6] + + s[3] * x1[7] + + s[2] * x1[8] + + s[1] * x1[9] + + s[0] * x1[10] + - s[10] * x3[0] + - s[9] * x3[1] + - s[8] * x3[2] + - s[7] * x3[3] + - s[6] * x3[4] + - s[5] * x3[5] + - s[4] * x3[6] + - s[3] * x3[7] + - s[2] * x3[8] + - s[1] * x3[9] + - s[0] * x3[10] + - y1[10] + - y3[10] + + 0xFFFF * q2[5] + + 0xFFFF * q2[6] + + 0xFFFF * q2[7] + + 0xFFFF * q2[8] + + 0xFFFF * q2[9] + + 0xFFFF * q2[10] + } + 11 => { + s[11] * x1[0] + + s[10] * x1[1] + + s[9] * x1[2] + + s[8] * x1[3] + + s[7] * x1[4] + + s[6] * x1[5] + + s[5] * x1[6] + + s[4] * x1[7] + + s[3] * x1[8] + + s[2] * x1[9] + + s[1] * x1[10] + + s[0] * x1[11] + - s[11] * x3[0] + - s[10] * x3[1] + - s[9] * x3[2] + - s[8] * x3[3] + - s[7] * x3[4] + - s[6] * x3[5] + - s[5] * x3[6] + - s[4] * x3[7] + - s[3] * x3[8] + - s[2] * x3[9] + - s[1] * x3[10] + - s[0] * x3[11] + - y1[11] + - y3[11] + + 0xFFFF * q2[6] + + 0xFFFF * q2[7] + + 0xFFFF * q2[8] + + 0xFFFF * q2[9] + + 0xFFFF * q2[10] + + 0xFFFF * q2[11] + } + 12 => { + s[12] * x1[0] + + s[11] * x1[1] + + s[10] * x1[2] + + s[9] * x1[3] + + s[8] * x1[4] + + s[7] * x1[5] + + s[6] * x1[6] + + s[5] * x1[7] + + s[4] * x1[8] + + s[3] * x1[9] + + s[2] * x1[10] + + s[1] * x1[11] + + s[0] * x1[12] + - s[12] * x3[0] + - s[11] * x3[1] + - s[10] * x3[2] + - s[9] * x3[3] + - s[8] * x3[4] + - s[7] * x3[5] + - s[6] * x3[6] + - s[5] * x3[7] + - s[4] * x3[8] + - s[3] * x3[9] + - s[2] * x3[10] + - s[1] * x3[11] + - s[0] * x3[12] + - y1[12] + - y3[12] + + q2[0] + + 0xFFFF * q2[7] + + 0xFFFF * q2[8] + + 0xFFFF * q2[9] + + 0xFFFF * q2[10] + + 0xFFFF * q2[11] + + 0xFFFF * q2[12] + } + 13 => { + s[13] * x1[0] + + s[12] * x1[1] + + s[11] * x1[2] + + s[10] * x1[3] + + s[9] * x1[4] + + s[8] * x1[5] + + s[7] * x1[6] + + s[6] * x1[7] + + s[5] * x1[8] + + s[4] * x1[9] + + s[3] * x1[10] + + s[2] * x1[11] + + s[1] * x1[12] + + s[0] * x1[13] + - s[13] * x3[0] + - s[12] * x3[1] + - s[11] * x3[2] + - s[10] * x3[3] + - s[9] * x3[4] + - s[8] * x3[5] + - s[7] * x3[6] + - s[6] * x3[7] + - s[5] * x3[8] + - s[4] * x3[9] + - s[3] * x3[10] + - s[2] * x3[11] + - s[1] * x3[12] + - s[0] * x3[13] + - y1[13] + - y3[13] + + q2[1] + + 0xFFFF * q2[8] + + 0xFFFF * q2[9] + + 0xFFFF * q2[10] + + 0xFFFF * q2[11] + + 0xFFFF * q2[12] + + 0xFFFF * q2[13] + } + 14 => { + s[14] * x1[0] + + s[13] * x1[1] + + s[12] * x1[2] + + s[11] * x1[3] + + s[10] * x1[4] + + s[9] * x1[5] + + s[8] * x1[6] + + s[7] * x1[7] + + s[6] * x1[8] + + s[5] * x1[9] + + s[4] * x1[10] + + s[3] * x1[11] + + s[2] * x1[12] + + s[1] * x1[13] + + s[0] * x1[14] + - s[14] * x3[0] + - s[13] * x3[1] + - s[12] * x3[2] + - s[11] * x3[3] + - s[10] * x3[4] + - s[9] * x3[5] + - s[8] * x3[6] + - s[7] * x3[7] + - s[6] * x3[8] + - s[5] * x3[9] + - s[4] * x3[10] + - s[3] * x3[11] + - s[2] * x3[12] + - s[1] * x3[13] + - s[0] * x3[14] + - y1[14] + - y3[14] + + 0xFFFF * q2[0] + + q2[2] + + 0xFFFF * q2[9] + + 0xFFFF * q2[10] + + 0xFFFF * q2[11] + + 0xFFFF * q2[12] + + 0xFFFF * q2[13] + + 0xFFFF * q2[14] + } + 15 => { + s[15] * x1[0] + + s[14] * x1[1] + + s[13] * x1[2] + + s[12] * x1[3] + + s[11] * x1[4] + + s[10] * x1[5] + + s[9] * x1[6] + + s[8] * x1[7] + + s[7] * x1[8] + + s[6] * x1[9] + + s[5] * x1[10] + + s[4] * x1[11] + + s[3] * x1[12] + + s[2] * x1[13] + + s[1] * x1[14] + + s[0] * x1[15] + - s[15] * x3[0] + - s[14] * x3[1] + - s[13] * x3[2] + - s[12] * x3[3] + - s[11] * x3[4] + - s[10] * x3[5] + - s[9] * x3[6] + - s[8] * x3[7] + - s[7] * x3[8] + - s[6] * x3[9] + - s[5] * x3[10] + - s[4] * x3[11] + - s[3] * x3[12] + - s[2] * x3[13] + - s[1] * x3[14] + - s[0] * x3[15] + - y1[15] + - y3[15] + + 0xFFFF * q2[0] + + 0xFFFF * q2[1] + + q2[3] + + 0xFFFF * q2[10] + + 0xFFFF * q2[11] + + 0xFFFF * q2[12] + + 0xFFFF * q2[13] + + 0xFFFF * q2[14] + + 0xFFFF * q2[15] + } + 16 => { + s[15] * x1[1] + + s[14] * x1[2] + + s[13] * x1[3] + + s[12] * x1[4] + + s[11] * x1[5] + + s[10] * x1[6] + + s[9] * x1[7] + + s[8] * x1[8] + + s[7] * x1[9] + + s[6] * x1[10] + + s[5] * x1[11] + + s[4] * x1[12] + + s[3] * x1[13] + + s[2] * x1[14] + + s[1] * x1[15] + - s[15] * x3[1] + - s[14] * x3[2] + - s[13] * x3[3] + - s[12] * x3[4] + - s[11] * x3[5] + - s[10] * x3[6] + - s[9] * x3[7] + - s[8] * x3[8] + - s[7] * x3[9] + - s[6] * x3[10] + - s[5] * x3[11] + - s[4] * x3[12] + - s[3] * x3[13] + - s[2] * x3[14] + - s[1] * x3[15] + + 0xFFFF * q2[1] + + 0xFFFF * q2[2] + + q2[4] + + 0xFFFF * q2[11] + + 0xFFFF * q2[12] + + 0xFFFF * q2[13] + + 0xFFFF * q2[14] + + 0xFFFF * q2[15] + - 0xFFFE + } + 17 => { + s[15] * x1[2] + + s[14] * x1[3] + + s[13] * x1[4] + + s[12] * x1[5] + + s[11] * x1[6] + + s[10] * x1[7] + + s[9] * x1[8] + + s[8] * x1[9] + + s[7] * x1[10] + + s[6] * x1[11] + + s[5] * x1[12] + + s[4] * x1[13] + + s[3] * x1[14] + + s[2] * x1[15] + - s[15] * x3[2] + - s[14] * x3[3] + - s[13] * x3[4] + - s[12] * x3[5] + - s[11] * x3[6] + - s[10] * x3[7] + - s[9] * x3[8] + - s[8] * x3[9] + - s[7] * x3[10] + - s[6] * x3[11] + - s[5] * x3[12] + - s[4] * x3[13] + - s[3] * x3[14] + - s[2] * x3[15] + + 0xFFFF * q2[2] + + 0xFFFF * q2[3] + + q2[5] + + 0xFFFF * q2[12] + + 0xFFFF * q2[13] + + 0xFFFF * q2[14] + + 0xFFFF * q2[15] + - 0xFFFF + } + 18 => { + s[15] * x1[3] + + s[14] * x1[4] + + s[13] * x1[5] + + s[12] * x1[6] + + s[11] * x1[7] + + s[10] * x1[8] + + s[9] * x1[9] + + s[8] * x1[10] + + s[7] * x1[11] + + s[6] * x1[12] + + s[5] * x1[13] + + s[4] * x1[14] + + s[3] * x1[15] + - s[15] * x3[3] + - s[14] * x3[4] + - s[13] * x3[5] + - s[12] * x3[6] + - s[11] * x3[7] + - s[10] * x3[8] + - s[9] * x3[9] + - s[8] * x3[10] + - s[7] * x3[11] + - s[6] * x3[12] + - s[5] * x3[13] + - s[4] * x3[14] + - s[3] * x3[15] + + 0xFFFF * q2[3] + + 0xFFFF * q2[4] + + q2[6] + + 0xFFFF * q2[13] + + 0xFFFF * q2[14] + + 0xFFFF * q2[15] + - 0xFFFF + } + 19 => { + s[15] * x1[4] + + s[14] * x1[5] + + s[13] * x1[6] + + s[12] * x1[7] + + s[11] * x1[8] + + s[10] * x1[9] + + s[9] * x1[10] + + s[8] * x1[11] + + s[7] * x1[12] + + s[6] * x1[13] + + s[5] * x1[14] + + s[4] * x1[15] + - s[15] * x3[4] + - s[14] * x3[5] + - s[13] * x3[6] + - s[12] * x3[7] + - s[11] * x3[8] + - s[10] * x3[9] + - s[9] * x3[10] + - s[8] * x3[11] + - s[7] * x3[12] + - s[6] * x3[13] + - s[5] * x3[14] + - s[4] * x3[15] + + 0xFFFF * q2[4] + + 0xFFFF * q2[5] + + q2[7] + + 0xFFFF * q2[14] + + 0xFFFF * q2[15] + - 0xFFFF + } + 20 => { + s[15] * x1[5] + + s[14] * x1[6] + + s[13] * x1[7] + + s[12] * x1[8] + + s[11] * x1[9] + + s[10] * x1[10] + + s[9] * x1[11] + + s[8] * x1[12] + + s[7] * x1[13] + + s[6] * x1[14] + + s[5] * x1[15] + - s[15] * x3[5] + - s[14] * x3[6] + - s[13] * x3[7] + - s[12] * x3[8] + - s[11] * x3[9] + - s[10] * x3[10] + - s[9] * x3[11] + - s[8] * x3[12] + - s[7] * x3[13] + - s[6] * x3[14] + - s[5] * x3[15] + + 0xFFFF * q2[5] + + 0xFFFF * q2[6] + + q2[8] + + 0xFFFF * q2[15] + - 0xFFFF + } + 21 => { + s[15] * x1[6] + + s[14] * x1[7] + + s[13] * x1[8] + + s[12] * x1[9] + + s[11] * x1[10] + + s[10] * x1[11] + + s[9] * x1[12] + + s[8] * x1[13] + + s[7] * x1[14] + + s[6] * x1[15] + - s[15] * x3[6] + - s[14] * x3[7] + - s[13] * x3[8] + - s[12] * x3[9] + - s[11] * x3[10] + - s[10] * x3[11] + - s[9] * x3[12] + - s[8] * x3[13] + - s[7] * x3[14] + - s[6] * x3[15] + + 0xFFFF * q2[6] + + 0xFFFF * q2[7] + + q2[9] + - 0xFFFF + } + 22 => { + s[15] * x1[7] + + s[14] * x1[8] + + s[13] * x1[9] + + s[12] * x1[10] + + s[11] * x1[11] + + s[10] * x1[12] + + s[9] * x1[13] + + s[8] * x1[14] + + s[7] * x1[15] + - s[15] * x3[7] + - s[14] * x3[8] + - s[13] * x3[9] + - s[12] * x3[10] + - s[11] * x3[11] + - s[10] * x3[12] + - s[9] * x3[13] + - s[8] * x3[14] + - s[7] * x3[15] + + 0xFFFF * q2[7] + + 0xFFFF * q2[8] + + q2[10] + - 0x1 + } + 23 => { + s[15] * x1[8] + + s[14] * x1[9] + + s[13] * x1[10] + + s[12] * x1[11] + + s[11] * x1[12] + + s[10] * x1[13] + + s[9] * x1[14] + + s[8] * x1[15] + - s[15] * x3[8] + - s[14] * x3[9] + - s[13] * x3[10] + - s[12] * x3[11] + - s[11] * x3[12] + - s[10] * x3[13] + - s[9] * x3[14] + - s[8] * x3[15] + + 0xFFFF * q2[8] + + 0xFFFF * q2[9] + + q2[11] + } + 24 => { + s[15] * x1[9] + + s[14] * x1[10] + + s[13] * x1[11] + + s[12] * x1[12] + + s[11] * x1[13] + + s[10] * x1[14] + + s[9] * x1[15] + - s[15] * x3[9] + - s[14] * x3[10] + - s[13] * x3[11] + - s[12] * x3[12] + - s[11] * x3[13] + - s[10] * x3[14] + - s[9] * x3[15] + + 0xFFFF * q2[9] + + 0xFFFF * q2[10] + + q2[12] + } + 25 => { + s[15] * x1[10] + + s[14] * x1[11] + + s[13] * x1[12] + + s[12] * x1[13] + + s[11] * x1[14] + + s[10] * x1[15] + - s[15] * x3[10] + - s[14] * x3[11] + - s[13] * x3[12] + - s[12] * x3[13] + - s[11] * x3[14] + - s[10] * x3[15] + + 0xFFFF * q2[10] + + 0xFFFF * q2[11] + + q2[13] + } + 26 => { + s[15] * x1[11] + s[14] * x1[12] + s[13] * x1[13] + s[12] * x1[14] + s[11] * x1[15] + - s[15] * x3[11] + - s[14] * x3[12] + - s[13] * x3[13] + - s[12] * x3[14] + - s[11] * x3[15] + + 0xFFFF * q2[11] + + 0xFFFF * q2[12] + + q2[14] + } + 27 => { + s[15] * x1[12] + s[14] * x1[13] + s[13] * x1[14] + s[12] * x1[15] + - s[15] * x3[12] + - s[14] * x3[13] + - s[13] * x3[14] + - s[12] * x3[15] + + 0xFFFF * q2[12] + + 0xFFFF * q2[13] + + q2[15] + } + 28 => { + s[15] * x1[13] + s[14] * x1[14] + s[13] * x1[15] + - s[15] * x3[13] + - s[14] * x3[14] + - s[13] * x3[15] + + 0xFFFF * q2[13] + + 0xFFFF * q2[14] + - 0x2 + } + 29 => { + s[15] * x1[14] + s[14] * x1[15] - s[15] * x3[14] - s[14] * x3[15] + + 0xFFFF * q2[14] + + 0xFFFF * q2[15] + } + 30 => s[15] * x1[15] - s[15] * x3[15] + 0xFFFF * q2[15] - 0xFFFE, + 31 => -0x1FFFF, + _ => 0, + } + } +} diff --git a/precompiles/arith_eq/src/executors/mod.rs b/precompiles/arith_eq/src/executors/mod.rs index 2d114fbf7..52a9df04e 100644 --- a/precompiles/arith_eq/src/executors/mod.rs +++ b/precompiles/arith_eq/src/executors/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod arith_eq_data; pub(crate) mod bn254_complex; pub(crate) mod bn254_curve; pub(crate) mod secp256k1; +pub(crate) mod secp256r1; #[allow(unused_imports)] pub use arith256::*; @@ -15,5 +16,7 @@ pub use bn254_complex::*; pub use bn254_curve::*; #[allow(unused_imports)] pub use secp256k1::*; +#[allow(unused_imports)] +pub use secp256r1::*; pub use arith_eq_data::*; diff --git a/precompiles/arith_eq/src/executors/secp256r1.rs b/precompiles/arith_eq/src/executors/secp256r1.rs new file mode 100644 index 000000000..08db6b884 --- /dev/null +++ b/precompiles/arith_eq/src/executors/secp256r1.rs @@ -0,0 +1,182 @@ +use super::ArithEqData; +use lazy_static::lazy_static; +use num_bigint::BigInt; +use num_traits::Zero; +use precompiles_helpers::{bigint2_to_8_u64, bigint_from_field, bigint_to_16_chunks}; + +use crate::equations; +use ark_secp256r1::Fq as Secp256r1Field; + +const COLS: u8 = 32; + +lazy_static! { + pub static ref SECP256R1_PRIME: BigInt = BigInt::parse_bytes( + b"ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + 16 + ) + .unwrap(); + pub static ref SECP256R1_A: BigInt = BigInt::parse_bytes( + b"ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", + 16 + ) + .unwrap(); + pub static ref SECP256R1_ADD_Q0_OFFSET: BigInt = BigInt::from(1) << 257; + pub static ref SECP256R1_DBL_Q0_OFFSET: BigInt = BigInt::from(1) << 258; + pub static ref SECP256R1_Q1_OFFSET: BigInt = BigInt::from(4); + pub static ref SECP256R1_Q2_OFFSET: BigInt = BigInt::from(1) << 257; +} + +pub struct Secp256r1 {} + +impl Secp256r1 { + #[allow(dead_code)] + pub fn calculate_add(p1: &[u64; 8], p2: &[u64; 8], p3: &mut [u64; 8]) { + Self::prepare(false, p1, p2, Some(p3)); + } + #[allow(dead_code)] + pub fn calculate_dbl(p1: &[u64; 8], p3: &mut [u64; 8]) { + Self::prepare(true, p1, p1, Some(p3)); + } + + fn point_from_8x64(p: &[u64; 8]) -> (Secp256r1Field, Secp256r1Field) { + ( + Secp256r1Field::from(ark_ff::BigInt::<4>(p[0..4].try_into().unwrap())), + Secp256r1Field::from(ark_ff::BigInt::<4>(p[4..8].try_into().unwrap())), + ) + } + fn prepare( + is_dbl: bool, + p1: &[u64; 8], + p2: &[u64; 8], + p3: Option<&mut [u64; 8]>, + ) -> Option { + let (x1, y1) = Self::point_from_8x64(p1); + let (x2, y2) = if is_dbl { (x1, y1) } else { Self::point_from_8x64(p2) }; + + let s = if is_dbl { + (Secp256r1Field::from(3u64) * x1 * x1 + Secp256r1Field::from(-3)) / (y1 + y1) + } else { + (y2 - y1) / (x2 - x1) + }; + let x3 = s * s - (x1 + x2); + let y3 = s * (x1 - x3) - y1; + + let s = bigint_from_field(&s); + let x1 = bigint_from_field(&x1); + let y1 = bigint_from_field(&y1); + let x2 = bigint_from_field(&x2); + let y2 = bigint_from_field(&y2); + let x3 = bigint_from_field(&x3); + let y3 = bigint_from_field(&y3); + + let q0 = if is_dbl { + let _q0: BigInt = 2 * &s * &y1 - 3 * &x1 * &x1 - &*SECP256R1_A; + assert!((&_q0 % &*SECP256R1_PRIME).is_zero()); + &*SECP256R1_DBL_Q0_OFFSET - (&_q0 / &*SECP256R1_PRIME) + } else { + let _q0: BigInt = &s * (&x2 - &x1) - &y2 + &y1; + assert!((&_q0 % &*SECP256R1_PRIME).is_zero()); + (&_q0 / &*SECP256R1_PRIME) + &*SECP256R1_ADD_Q0_OFFSET + }; + + let _q1 = &s * &s - &x1 - &x2 - &x3; + assert!((&_q1 % &*SECP256R1_PRIME).is_zero()); + let q1 = (&_q1 / &*SECP256R1_PRIME) + &*SECP256R1_Q1_OFFSET; + + let _q2 = &s * &x1 - &s * &x3 - &y1 - &y3; + assert!((&_q2 % &*SECP256R1_PRIME).is_zero()); + let q2 = &*SECP256R1_Q2_OFFSET - (&_q2 / &*SECP256R1_PRIME); + + if let Some(p3) = p3 { + bigint2_to_8_u64(&x3, &y3, p3); + return None; + } + + let mut data = ArithEqData::default(); + bigint_to_16_chunks(&q0, &mut data.q0); + bigint_to_16_chunks(&q1, &mut data.q1); + bigint_to_16_chunks(&q2, &mut data.q2); + bigint_to_16_chunks(&s, &mut data.s); + bigint_to_16_chunks(&x1, &mut data.x1); + bigint_to_16_chunks(&y1, &mut data.y1); + bigint_to_16_chunks(&x2, &mut data.x2); + bigint_to_16_chunks(&y2, &mut data.y2); + bigint_to_16_chunks(&x3, &mut data.x3); + bigint_to_16_chunks(&y3, &mut data.y3); + Some(data) + } + #[inline(always)] + #[allow(dead_code)] + pub fn execute_add(p1: &[u64; 8], p2: &[u64; 8]) -> ArithEqData { + Self::execute_add_dbl(false, p1, p2) + } + + #[inline(always)] + #[allow(dead_code)] + pub fn execute_dbl(p1: &[u64; 8]) -> ArithEqData { + Self::execute_add_dbl(true, p1, p1) + } + pub fn execute_add_dbl(is_dbl: bool, p1: &[u64; 8], p2: &[u64; 8]) -> ArithEqData { + let mut data = Self::prepare(is_dbl, p1, p2, None).unwrap(); + for icol in 0..COLS { + let index = icol as usize; + data.eq[index] = [ + if is_dbl { + equations::Secp256r1Dbl::calculate(icol, &data.x1, &data.y1, &data.s, &data.q0) + } else { + equations::Secp256r1Add::calculate( + icol, &data.x1, &data.y1, &data.x2, &data.y2, &data.s, &data.q0, + ) + }, + equations::Secp256r1X3::calculate( + icol, &data.x1, &data.x2, &data.x3, &data.s, &data.q1, + ), + equations::Secp256r1Y3::calculate( + icol, &data.x1, &data.y1, &data.x3, &data.y3, &data.s, &data.q2, + ), + ]; + for ieq in 0..3 { + let cin = if index > 0 { data.cout[index - 1][ieq] } else { 0 }; + let value = data.eq[index][ieq] + cin; + if icol != COLS - 1 { + data.cout[index][ieq] = value / 0x10000; + } + debug_assert!( + 0 == if icol == COLS - 1 { value } else { value % 0x10000 }, + "EqSecp256r1 residue eq{ieq} ({index}) #:{value} cin:{cin}" + ); + } + } + data + } + #[cfg(feature = "test_data")] + #[allow(dead_code)] + pub fn verify_add_dbl(is_dbl: bool, p1: &[u64; 8], p2: &[u64; 8], p: &[u64; 8]) { + let data = Self::execute_add_dbl(is_dbl, p1, p2); + data.check_ranges(); + let op = if is_dbl { "Secp256r1Dbl" } else { "Secp256r1Add" }; + for i in 0..2 { + let offset = (i + 1) * 4 - 1; + let mut x3 = data.x3[offset] as u64; + let mut y3 = data.y3[offset] as u64; + for j in 1..4 { + x3 <<= 16; + y3 <<= 16; + x3 += data.x3[offset - j] as u64; + y3 += data.y3[offset - j] as u64; + } + assert!(p[i] == x3, "{} p[{}]:{} not match with x3:{}", op, i, p[i], x3); + assert!(p[i + 4] == y3, "{} p[{}]:{} not match with y3:{}", op, i + 4, p[i + 4], y3); + } + } + #[cfg(feature = "test_data")] + #[allow(dead_code)] + pub fn verify_add(p1: &[u64; 8], p2: &[u64; 8], p: &[u64; 8]) { + Self::verify_add_dbl(false, p1, p2, p); + } + #[cfg(feature = "test_data")] + #[allow(dead_code)] + pub fn verify_dbl(p1: &[u64; 8], p: &[u64; 8]) { + Self::verify_add_dbl(true, p1, p1, p); + } +} diff --git a/precompiles/arith_eq/src/generator/equation.rs b/precompiles/arith_eq/src/generator/equation.rs index 00180a795..7b895c0e2 100644 --- a/precompiles/arith_eq/src/generator/equation.rs +++ b/precompiles/arith_eq/src/generator/equation.rs @@ -382,7 +382,20 @@ impl Equation { } else { " " }); - for (i, term) in addt.terms.iter().enumerate() { + + // Filter out BigInt terms with value 1 when there are other terms + let terms_to_output: Vec<_> = if addt.terms.len() > 1 { + addt.terms + .iter() + .filter(|t| { + !matches!(t, ProductTerm::BigInt { value, .. } if *value == BigInt::one()) + }) + .collect() + } else { + addt.terms.iter().collect() + }; + + for (i, term) in terms_to_output.iter().enumerate() { if i > 0 { line.append(" * "); } diff --git a/precompiles/arith_eq/src/mem_inputs/mod.rs b/precompiles/arith_eq/src/mem_inputs/mod.rs index 0701a1db3..665159f2a 100644 --- a/precompiles/arith_eq/src/mem_inputs/mod.rs +++ b/precompiles/arith_eq/src/mem_inputs/mod.rs @@ -8,6 +8,8 @@ mod bn254_curve_dbl; mod generate_mem_inputs; mod secp256k1_add; mod secp256k1_dbl; +mod secp256r1_add; +mod secp256r1_dbl; pub use arith256::*; pub use arith256_mod::*; @@ -19,3 +21,5 @@ pub use bn254_curve_dbl::*; pub use generate_mem_inputs::*; pub use secp256k1_add::*; pub use secp256k1_dbl::*; +pub use secp256r1_add::*; +pub use secp256r1_dbl::*; diff --git a/precompiles/arith_eq/src/mem_inputs/secp256r1_add.rs b/precompiles/arith_eq/src/mem_inputs/secp256r1_add.rs new file mode 100644 index 000000000..c76966895 --- /dev/null +++ b/precompiles/arith_eq/src/mem_inputs/secp256r1_add.rs @@ -0,0 +1,43 @@ +use super::ArithEqMemInputConfig; +use crate::executors::Secp256r1; +use precompiles_common::MemProcessor; + +pub const SECP256R1_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { + indirect_params: 2, + rewrite_params: true, + read_params: 2, + write_params: 1, + chunks_per_param: 8, +}; + +pub fn generate_secp256r1_add_mem_inputs( + addr_main: u32, + step_main: u64, + data: &[u64], + only_counters: bool, + mem_processors: &mut P, +) { + // op,op_type,a,b,addr[2],... + let p1: &[u64; 8] = &data[7..15].try_into().unwrap(); + let p2: &[u64; 8] = &data[15..23].try_into().unwrap(); + let mut p3 = [0u64; 8]; + + Secp256r1::calculate_add(p1, p2, &mut p3); + super::generate_mem_inputs( + addr_main, + step_main, + data, + Some(&p3), + only_counters, + mem_processors, + &SECP256R1_ADD_MEM_CONFIG, + ); +} + +pub fn skip_secp256r1_add_mem_inputs( + addr_main: u32, + data: &[u64], + mem_processors: &mut P, +) -> bool { + super::skip_mem_inputs(addr_main, data, &SECP256R1_ADD_MEM_CONFIG, mem_processors) +} diff --git a/precompiles/arith_eq/src/mem_inputs/secp256r1_dbl.rs b/precompiles/arith_eq/src/mem_inputs/secp256r1_dbl.rs new file mode 100644 index 000000000..b7c48897d --- /dev/null +++ b/precompiles/arith_eq/src/mem_inputs/secp256r1_dbl.rs @@ -0,0 +1,42 @@ +use super::ArithEqMemInputConfig; +use crate::executors::Secp256r1; +use precompiles_common::MemProcessor; + +pub const SECP256R1_DBL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { + indirect_params: 0, + rewrite_params: true, + read_params: 1, + write_params: 1, + chunks_per_param: 8, +}; + +pub fn generate_secp256r1_dbl_mem_inputs( + addr_main: u32, + step_main: u64, + data: &[u64], + only_counters: bool, + processor: &mut P, +) { + // op,op_type,a,b,... + let p1: &[u64; 8] = &data[5..13].try_into().unwrap(); + let mut p3 = [0u64; 8]; + + Secp256r1::calculate_dbl(p1, &mut p3); + super::generate_mem_inputs( + addr_main, + step_main, + data, + Some(&p3), + only_counters, + processor, + &SECP256R1_DBL_MEM_CONFIG, + ); +} + +pub fn skip_secp256r1_dbl_mem_inputs( + addr_main: u32, + data: &[u64], + mem_processors: &mut P, +) -> bool { + super::skip_mem_inputs(addr_main, data, &SECP256R1_DBL_MEM_CONFIG, mem_processors) +} diff --git a/precompiles/arith_eq/src/test_data/mod.rs b/precompiles/arith_eq/src/test_data/mod.rs index 7056634f2..12a785ddc 100644 --- a/precompiles/arith_eq/src/test_data/mod.rs +++ b/precompiles/arith_eq/src/test_data/mod.rs @@ -8,6 +8,8 @@ mod bn254_curve_add_test_data; mod bn254_curve_dbl_test_data; mod secp256k1_add_test_data; mod secp256k1_dbl_test_data; +mod secp256r1_add_test_data; +mod secp256r1_dbl_test_data; mod str_test_data; pub use arith256_mod_test_data::*; @@ -19,4 +21,6 @@ pub use bn254_curve_add_test_data::*; pub use bn254_curve_dbl_test_data::*; pub use secp256k1_add_test_data::*; pub use secp256k1_dbl_test_data::*; +pub use secp256r1_add_test_data::*; +pub use secp256r1_dbl_test_data::*; pub use str_test_data::*; diff --git a/precompiles/arith_eq/src/test_data/secp256r1_add_test_data.rs b/precompiles/arith_eq/src/test_data/secp256r1_add_test_data.rs new file mode 100644 index 000000000..5dda8b76c --- /dev/null +++ b/precompiles/arith_eq/src/test_data/secp256r1_add_test_data.rs @@ -0,0 +1,428 @@ +use super::str_test_data; + +pub fn get_secp256r1_add_test_data(index: usize) -> Option<([u64; 8], [u64; 8], [u64; 8])> { + if let Some(sdata) = get_secp256r1_add_test_str_data(index) { + let bdata = str_test_data::<6, 8>(index, "secp256r1_add_test", sdata); + Some((bdata[0], bdata[1], bdata[2])) + } else { + None + } +} + +/* +p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff +a = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc +b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b +F = GF(p) +E = EllipticCurve(F, (a, b)) +for i in range(50): + P = E.random_point(); + Q = E.random_point(); + R = P + Q + print(f"{i} => Some([\"{P[0]}\",\"{P[1]}\",\"{Q[0]}\",\"{Q[1]}\",\"{R[0]}\",\"{R[1]}\"]),"); +*/ +pub fn get_secp256r1_add_test_str_data(index: usize) -> Option<[&'static str; 6]> { + match index { + 0 => Some([ + "17684782685806207390954577263779598973526381175946291755799324209262768957344", + "92167691407581242143601595837485822613797650653636052564997068795998294585315", + "73984533821977080713866179493522290598614194355644276021965064794957344172596", + "25220164086070734051904195238061809059634781413542833942170478608760396143401", + "112171174882501884528082106717813190259999700649843659774819863553444790677722", + "38217399076316596903440542314150866607868328433182787710786920250876662996546", + ]), + 1 => Some([ + "82918273272589823868042929503759319670759008984275041506509079652257071194521", + "61224585167429861024102734372621389555494697000978287314851561976642958452244", + "67310915831906821858103204863888318834564940463611273649349677923602187616233", + "51814378521375348755702907304644887862192343269383631658543556746676469157830", + "77203751959144651428250626283649437330076229869597141272893263683006993559929", + "67344331379487595029094632279940506068248676066019697791912043900620046856013", + ]), + 2 => Some([ + "93111698684678093939605103683080290397897724258093264284317661355069057630808", + "35675818774711895574491415838927782765793396943357279529533888204119443078802", + "96544594863230548499306260104755349234540376563330938978557078346969342385220", + "110188329108817311801109847817685558648859748784587130029696651128198391411170", + "55406363010319599318791981827531471269195981263250380326076602674363183979546", + "80187187215393328353937446610893129036121989978504756983389948399116563153899", + ]), + 3 => Some([ + "109792441664755217847584901718687659931344458985210623403156605941433490235562", + "13672769320709405707218937931583674538845507455548724277425218320749068446824", + "38687032845773596096569999620157205944238436689184055065468202061825530140413", + "85394043624262560205157547608063947866391005887241800198754175377855117746319", + "53842541175280800529597949933301239418747219522402878122636476673093344807096", + "85028463323583941565197628685128161692305398288598561754474353670768548890109", + ]), + 4 => Some([ + "111046606926526837876522959178243244999552433237100111673240488583131028784991", + "30865955838698820275812672384269028129284981326041433797245757418940757931288", + "25115970043795004262482663636387713014533227375501854235219685135338149402223", + "58677217951910880232368327840967535372058919419693130862518037877021177730467", + "51835429297154079647474400111135195081670903668751067082006513570458550334753", + "49963073480457359940942527575761352637668977353404502946418927921633348201017", + ]), + 5 => Some([ + "56716310841055871819822533195423997545886613427643009335772467969509251528982", + "64774184185872237889875447604630858017971380400746453022020026633771596864715", + "67440228314388245033149888721824888298468314735195926940350696944539969178383", + "38253130939000485854773109995881400131962639704062457685413726276301316660839", + "110008225213432446344888784765089079768890827616417156232722879918666196039391", + "68455554716316449349213273103787842483668147082745949068312805763810897213318", + ]), + 6 => Some([ + "10711371137774951565075017512938527972196596954031454094456874742414877754922", + "10979325994147291644193898262920514822778310295654136614674184725119802042368", + "103118243427658940385528043976310308098606077756589810400151657401414766130507", + "93782498337492415246158407900656434766812279585430391592239869365183866375555", + "51788787192610256325779347918095837410829899638556998910764153476027741992579", + "66827727791564345726359173612923110597811907765712045298521380068116711548260", + ]), + 7 => Some([ + "37878582505801285302116644905259385594851471580997806424818245790989358942746", + "3954867695159406499019530537199039649186077483484343374511573856022884343885", + "15954034272742718634920131612614825254413138500513377939178226732036408341417", + "78294798114425485839393414355181903431219656006283736777111163420095295683942", + "114507089246636857174017359524370096126339431136935841344681560435492754667849", + "63142883043554587034959529206801513237434737868484377801071458081196270035831", + ]), + 8 => Some([ + "33550053298798125312683244411504391660388321336315520983909658171623560514091", + "31582360259010038393481741519828217163955429470960819312510044046349274020609", + "75176112042124483069595148862822617085802089111830682967463865996787645016048", + "2932340845413258164776964798617768268605887815062269192858510956466958935973", + "89599095453192155946840139555393621753171863523719103613729148019210406414939", + "69562167672104032160611733500691587785973814928241336686794033626566330555264", + ]), + 9 => Some([ + "94823042128298259500654179528002304592138382315135147006121846477324829311273", + "99906940534375788963644081843967870473312301256166670894408207440018140151479", + "85613919116770679219788316084323512693744412000122326672150148641564001318398", + "103146823225288187202733820965146317648710151494325480234705786985988275268655", + "96791634910682562993887711843304952493943431947706491565722374279494742752228", + "113144214650399993038874400932041418225203539779667359872130127433295594801204", + ]), + 10 => Some([ + "3924293741150057366064332225528023499563338199182095965555578837053505998259", + "66873024892857422435221094361651117209003246442158284596207674673367578708949", + "109298453923049928929026883598539553189485119784740188785061901690209373576555", + "83480763728226906765148281399893822950673958852988467138695175156078878982994", + "9585465302163048573719869770885934779171894251260901734741813172711790573552", + "36580229740635493024586893283432496581936112516452384421778917660278670379205", + ]), + 11 => Some([ + "18310530620295480468572934814176808997048007995002733256546647990508177628495", + "73840740082159408202648255163410263148570126042167268033205813190171840460729", + "110524227115255716942075764723157634370474802155898135889569164884230304931574", + "110768478803701575395251932670901325946021336657137380316423865518882701453077", + "86848462668105028049027273369135648339749859252554207169657766605664687038235", + "103951338529650365319959162200642544339524568925410996102416607861607493173359", + ]), + 12 => Some([ + "99026000812720516094198217504658807704529606677040629671690224871721994009866", + "102283167815738585734079225269834039547914441148344541189360146911122240529378", + "47030337388076660274220789896103266916807259984607814932407577130242858996530", + "13988063588848201446959776861162160594895507686034814948277302761786467365367", + "68963083552307892403665499897704699081845623816023270639734553736595722927729", + "88516918828671556877288187103380179464665131773376519067341033009089322289871", + ]), + 13 => Some([ + "38487995441962658887226186976171889915668616244117603539058056811075303395665", + "30729290185158340402230787453296886837781643907390297912367958322597124855735", + "46377664604144300952370182291547699817924119708455288990002188270179749941596", + "78011506084475784490315938908345250336210574034788332544774927562237068135115", + "44849552197990575942636252844937561490512552613659834659176260937609879733076", + "52667203167598901110147452523952598762344006520849334796894159504486191385966", + ]), + 14 => Some([ + "107323871096141655023648133824881321033974511825709097974777326668514404430186", + "25876968499913150169795244747395736904102969467863417392071209018709682667871", + "14987872774801053572489943273001700127722261309709505847450248474445802930149", + "113761216433816835891945223129831698304508251814618734494593960340282523326599", + "45652559185939258244295551039101331351471636935897381037972987043202864089589", + "109846613246848823431335575806177887162399192846740045730679001049149689360781", + ]), + 15 => Some([ + "89498624753157943574848869547714395176575531584463061175358098074964274548237", + "54610500846976780115167326368939014701633330820351986760119188118026446396172", + "95140673971388669355462722845028729563858704443940420306818524437258583643764", + "33372883152878338678481880215734848164890267885445926530505844306681822831192", + "58052714241912431298421833412422781913243185039601912297088735600684521051387", + "101430933770040428979875496205448661542353192781356287757602374631579200560473", + ]), + 16 => Some([ + "20076577295121672562412962777148154728710138141363032581329189442228756250980", + "84275991458571559479283216723081968429043855843498809147798329351941749450622", + "17925867131735381996928284478794942700284969337102328149800785605282738098817", + "37621115907253832086334801875641895037364746119996579693954641922285954197272", + "31758381054468008065966754331296926026063698834207480276589462782231723619324", + "96280061408269851056578038229643687160614513133720649050290010088707768986190", + ]), + 17 => Some([ + "37833252873912169524349932076511664774170190212025289576052077155435451536339", + "53444662663525260248083762828343868780347220955549460221713254441547310615471", + "43864924226398863070516914810877092088739179987027735292280176204228288218844", + "61005705831629982989436785681121304807121444435528518201484013535773956527579", + "99480240365553947808422113855848188966386992208895265013295687188736307649082", + "87536301283601900687312639272033287940222528700243293560604854292813186922404", + ]), + 18 => Some([ + "82262089720121462595945763654356490365121875935383936536996893316797471892471", + "43505406937748654235816434495553014184099200289777144288995445971344568638598", + "37928360437627000803527366093122380390125981680530416956692770746909699333363", + "96927347055660015057982480998533256325068043525049747439124102704436442998964", + "16212424997607839624955014540613147385929385816592333594424024375317443919449", + "37691497548960493344162615415721531757555993305083789143947631329012108542126", + ]), + 19 => Some([ + "1367365622087344939351945828053936856061499931539283999396846816508794452252", + "83799125362740311566530398272339378559428930014331061571738445063846734504951", + "105919145000244084049580642211428333984193142445263769646392623126960508956661", + "52886877527805697672283964864216990888753423817134777213153377256726324598249", + "115760698842416184204790826679138312305753824704381087052900277549322444920156", + "75240750283097166896899089057864412206714386510014748200266404108094097511380", + ]), + 20 => Some([ + "12654877640585584941390969918023113422749533710886242612568395102294595144458", + "87880387757558380320173739872126572094227539260928934411743227331433368901774", + "98036551135651205790432805807164301015456219642410417085929765389200972441773", + "77546413552649808426580209521162971565591754878695040582777056092346246556907", + "48942679803473092458570336426447518800299566137794197814519683760932791314077", + "58187128405356536097790573896414569139095436177480649375592124810025437524565", + ]), + 21 => Some([ + "34724128525181436934184252551946588572272360650226861914547682412204499271442", + "85078812506403798249947331221949111629861910300409971103013469709968384966134", + "68780544494574478086432450902260402510058643791491361069254750999420272923028", + "709822679066441527422428232812964558258976246726291156594716899010966528392", + "51246220245483583339618360572710688775594049285038923222741388176973453648219", + "40080380480941938145513302461059815248501148539318074581535780765762362205841", + ]), + 22 => Some([ + "70431055459072866888593600666475469825668392770979273745039573158458682324701", + "86867137214653338771212863237013126068915160634255968731936950104374121720364", + "98139461395927289302484436275575259197661915413598596910410433324419277413072", + "9623318446644888671815849599669450078797425124502623626155673010743177752348", + "76929609807364453541418579982904016607090782495571035444607895649037963729123", + "73463845371689733045095247304740596879796165941396449430049816696271139550850", + ]), + 23 => Some([ + "9441554039850447067436133580199088375433916940157917376785533450201617198990", + "110899367047838333158229392583362187984010486558832854496925755036415811046678", + "106052240812411914734793733638511395589573453787088042497837472345934028779997", + "21286873478661319893957432565714681230654086426508335597590975998514450145775", + "110284032788425371286216948717330028002551389199813251716120143779532408427756", + "94039413261401628026497136029100643968966679998651852077901395000726685057459", + ]), + 24 => Some([ + "75969892884820301220070407837486168482129896286078105470831958068154011066253", + "68029562791644718983404506086125821645287589528436959429914264532568861147313", + "48041522956484263320739131189084347731880804634345989375732322670139185566988", + "55862608788643415931350866922256321607280397147632577024468873036849092308964", + "50544743925708136174630587392895274130726065884144829401401852483613075929571", + "47660039615364704328901816741870550256933370358893928106796995393361194601147", + ]), + 25 => Some([ + "93672567375930934807356337490671551016358128651604941091082401597864881914654", + "97107591870001992228553911690339053410231905825769475984890055827634100572832", + "14511678543903603372701087598500786602321721366666782193112090490791630698480", + "3995161265837882039098595643598119671851511259014710566285491863571630337214", + "110646106285265692147884703939174854914286298305591431361676422578809859575333", + "77400618114030994900310583895910478803534286430821025347359253744610751022915", + ]), + 26 => Some([ + "102703062322189224954472739851094742674716572505363993033934707090358928153093", + "29423914759435986269535995913517418616500690926863780423277331742056718682877", + "63734164515543282401013686266437391979973894861124524260778180483339482872550", + "103912386011641552388391244838421172011186150106027961659334909684801444205548", + "9693306655266152369632858065548028931695448445687527765165665993196642740014", + "30015251212531507543313524860541630284887571290440586894903412149823191925774", + ]), + 27 => Some([ + "2210111107897660734573797030186664834182221613246114325746835133127115459259", + "52143443862739299051820998984417230645937564079376260657234806900694798367995", + "63171057060175692913217361114292263068839516853085376463019791345857695343995", + "103381239874046770219850656826381869813584200270849218618004252520493488821519", + "43500770603565630307579686262694316821021686465090697669357203951794872643183", + "56590132770472356844155242547622282714504842705857656373738368510015788734163", + ]), + 28 => Some([ + "14157835229576069119792407133151679287854718553245428114052281219203289750083", + "84647122890378799262817713401375547836089036902220927224570978018804126730131", + "86845608676718187144879739102363207118010952841099531790143952234149577367876", + "74581012233007728408593077758601562949593508594705092935811810499627280008504", + "77117088110901940204182947123917770397208857951115824769198059648021292443535", + "58731842680597213121773459196104380619451164668845205911288220541490845806391", + ]), + 29 => Some([ + "69928742639936344048598373230638130201775271938079774940079796375546123270292", + "94813290892602790142920344449639250364723227256486132066787437258110718616780", + "77180544937058093189876662268912659635354078759523772855870632487132794629922", + "12063256824651236274306611963240579663713326075914535867495818160095664576629", + "110734189220580761880219761041494779496266289937646949824468951110217803643967", + "100048470574657061403918312748922531118841950091365014201468116991291064139737", + ]), + 30 => Some([ + "40833647432394108188067079585841213557652279160464787563924201568269653962383", + "12061143888578525077744469124109987156016597738239013281079135363586294808505", + "53896309055379180089376690242838975277819315715656788790307075477000125239526", + "24617509855908834659320618573662804335655054140987591902549923110640938653241", + "30003163742531165339899480376456992857407863515231096726747580583988780269787", + "18528065410133402654463440343628206298821836924916229509718822212123758991988", + ]), + 31 => Some([ + "115377147796467875644599880350836837934363458179313277312050652191531974714817", + "40892464273221617347382496161869344706374780758075574308796670465355451631291", + "66466553840427100072823726111186038661942090484840976109514020353762816170942", + "54906531779533059685610440115844619484422316722425159575704549897586736791773", + "13444311971109807431434466220145694942603035874708503110793914589176019332459", + "13849728853253979615348616092492843774889838749033582601557952492541966545529", + ]), + 32 => Some([ + "105203689550274114040945430032566499276588290839350226077864208823595393864902", + "96120821386051133717286482581846213136576051605105852796000322925693714768175", + "43263202689208777345594739741906244922053437154232280819157161529579539262633", + "107625820720206849148100722855677738125317433569995332059467331772729734734412", + "23985402459150634182847873136793777576882033323594566363227924602177596119989", + "45362220531791375766178258142092197795394535110240251315180322128700354409444", + ]), + 33 => Some([ + "75632894459563536446672852762250993526049205374450388805777735713895520961799", + "114393201063217157751647165476340953918159290026566225269962531299510195930148", + "59686045302710549367350964181911697640000727020287489066619430590943247600091", + "27528136108182499296332864648142601033691769630039974559954879016698731510816", + "92212711138896264019328128182078619374143813809018818264414395399464232290423", + "44356685718472702247422606172339888465290994667798888501447111057059912924226", + ]), + 34 => Some([ + "39138368728492398486954793309659662296785514207706895315010445591098773185577", + "107091543455417075768966137778406791232503450034180530824166635869852107342299", + "94924517089109757011197781535262382902947734688053865564187390231352986750529", + "113506473115036544550546281248450205770893710891082588438658485146634651080985", + "56249288162224753278326598155200248778345417526366519397502878285299712606719", + "93901626464075017486934112989826223091809007341422383612245573547092478088734", + ]), + 35 => Some([ + "62977172685037110948072903218410698141451968449389910589615614867943361358085", + "45257362169644012289441951793513371860466242621255659286272104001422567870713", + "112704413806640622877558248130034421586972967616046023010121610541929192747523", + "51239398917791240207839425730936680501197530002225723654384928811755031874311", + "30405018237698908524092926703827606961814334376436030834082402529535815206925", + "91600553196968549458095806412166321658673718577831723438748164982747038192587", + ]), + 36 => Some([ + "37316781919864858250508825820799252762236093548215309526063240983115388307002", + "92065293952034728177596775770123547035123140295906654822791835417523439805136", + "3784716451994793383674369635777476753196300164268017025023308099900391616990", + "24877455512103777870127143674243127569045061301086789275010282989523175224471", + "21668122133833805213728598021274639736092880226692011951677383211964753973887", + "87341549546719749904482695414117510401381125508536981042745723373587776523983", + ]), + 37 => Some([ + "41733557441873379557853633089158052587664975289193675916822158555184062363973", + "54829584665559693213380027669915792369192340992508720502846759571214860960700", + "112288755455850118760736937917607296859127879751229035687737477602207927993339", + "81461988233585863252395036489492268043973570005091746944122953476172359896066", + "110064749350856327858286213250464823407601673454335142087056018527058836366529", + "42162432163526560057171976138878928948333534297779587982656976644155978523222", + ]), + 38 => Some([ + "108601842112476324003732869565410732658138086604590138648292170312970050187474", + "91033881143222487565964973929748982039663649368694920620769113163005584815170", + "72305020943925161946048359754747104510479968176883671535827791972643243772790", + "31825568583101363325459876750009019926418630861622657747104220196492974157481", + "56729930381091281268466561575890153768124925626415802731035270515413722258728", + "6382192644419839591991134610005356538565265137776538430306489893072434829381", + ]), + 39 => Some([ + "110057956689440083657668611755060721791990236478131128473407034349282568815310", + "35946149523420923935145040155586965807313521928838670446369440195608124681621", + "40909871195474608839942711765943092130092405565416192851992151970259936012416", + "106870661082258282632444857881509209670126926118079384308670768736318476719941", + "22315267136428264121532618884559001934721851805546727456223809866918874321148", + "44547522409324652743586736865842067699639592608216711736422643721509254995900", + ]), + 40 => Some([ + "107485325347796407671388015359823178242451996312768816574123323416026044370106", + "50839488651363674558135671194854888566328967053910495912509821129252169140659", + "63335002634940318370439611952914952237204048637686196443287115507597016766648", + "38672283700544518322945051533075923533755916907891482757544393899631898169224", + "108662579882817052805050986004143032723168511241431143745184654575629630513704", + "19649199210347160877422315697683791643136727231603438115232511244704017341461", + ]), + 41 => Some([ + "25411496175355141746890415235431503068530373142155374862823924034395005790002", + "91068968875633819382539516229780881778723670173045281627457761054560271763868", + "76085561714047110662465771018526765363077780214846971696956444592221164383037", + "86731160596420773154191635324679096016311659217151045383265708010546536090949", + "96725493169835689858956240877643845116166095991989735780879551374356814859610", + "79376344051414450246732447941609138972322414996041037311858151646373314061375", + ]), + 42 => Some([ + "56102996560984849487069966786194128890511072505522656184665103355112974294435", + "51553998015901583108872218227013316539828964340037843751423430863367725141022", + "15998429368760911518905700713406934848181272864292598503817390491845521965866", + "2310508821683633536668724705994946078848602036239474768940008735486350225159", + "37769289690297247294534085860166450383987445341376560287512976563613610591887", + "5296357534529156265415504441491928300888550971621159139157948012711877767344", + ]), + 43 => Some([ + "96719930252444052597642041345793011228619227459711070098526834248460214041996", + "23865165653542290858733630831761052913038817763220581878899324862888507530700", + "2946311160093038764289336502321462775322681633833175557230625591532725229883", + "30069956481863829211278337776074372612030845987890649118170772744927706473743", + "46915285708143942297479393945222755020998491702106394728671025707096919993996", + "69266120427482287576712223686333054664226987014112019366422528680014576960606", + ]), + 44 => Some([ + "65051462413891671129225867179546758143726585398078388607814068124960649447940", + "31381833805609080618489036414733779351330041681173105828422676953587093339016", + "51011745484506694561450015429988019077819035803612110543366161322884819534285", + "113844475431868304872090350127567971455785020925147288644866668506265982255165", + "84705462504278282272851002088982770927677824734985070660159553684882397510753", + "73457374232190677143927780263693416700558585140264548669718573638669710850199", + ]), + 45 => Some([ + "112522999038853079314988559604351880524612924077818163227902186734640579219556", + "5619492670117547164920188890999129789310713295821655768403557553886101368976", + "79942246241615802867477105465475440646383212201742028570681477707358654705198", + "29634807148720351364631869355763087983721300756064618344987903470208344524872", + "29762887570221357868960666817455326077050302536085512351138128019976126994517", + "8917888557981321787427029090591731315024561091113350452915823603832748755969", + ]), + 46 => Some([ + "104207887061711157445362963152034955840923990059437458918209951756591677376977", + "74006063057168174946440710863673448132292493904672628703260856685806444692900", + "40640613915236513486536299056279097059754020679320172628435226138772383138453", + "92619313530806343161465449209945726767501192538038081070281908092420852750145", + "64816721658334116711561117572944084882733804453039892851067376443016627555702", + "19142518236398157846109356659205888879891198776238436777826236827013759329687", + ]), + 47 => Some([ + "32702286579289147034182559797231313755360054223391478073573037828909117062528", + "50865582199050470556560603675346151929078007930284746451564776344678109518855", + "65736118316401465497403098908912532899029906668895970931675340839692704831107", + "65864822934892145754006315762328140452231287739130167705959543074012029001215", + "45531241932389678988757631100611223553088514341465278158877033343241235790421", + "106758084880369418117102272879151094010617932285830471190124604391958674331368", + ]), + 48 => Some([ + "9274515152587056471384582364877717622404425981251102336564216988502165066424", + "80580737068393435827809175626846409127867474156780974360780328085412111192569", + "58466884242639961077864176140504526297662814780252177865746636700182117203484", + "89461614052461727522178669535051318181492096248574725014848740966256864813471", + "64500294620894692026409882776417842379823140219092683029132615428962645192231", + "35875911528397224238602620074464252981730926403820622552293133236459887760472", + ]), + 49 => Some([ + "51821397846560117243251460672909334643040296891791662155544247214090802464509", + "67925887814568610026304093613646998768677837897685270659066718846290586647576", + "105832849288959482777670265488407868460057738392187111231133654125954176337272", + "97687616214762711968100756989214763066786608857690239294623507469853299138420", + "100144853711840929258130164913346820143407027068659221904542527171658184022281", + "87692150256066340963075598693539984014822637248252508403877581559194736715297", + ]), + _ => None, + } +} diff --git a/precompiles/arith_eq/src/test_data/secp256r1_dbl_test_data.rs b/precompiles/arith_eq/src/test_data/secp256r1_dbl_test_data.rs new file mode 100644 index 000000000..fb127f7ed --- /dev/null +++ b/precompiles/arith_eq/src/test_data/secp256r1_dbl_test_data.rs @@ -0,0 +1,327 @@ +use super::str_test_data; + +pub fn get_secp256r1_dbl_test_data(index: usize) -> Option<([u64; 8], [u64; 8])> { + if let Some(sdata) = get_secp256r1_dbl_test_str_data(index) { + let bdata = str_test_data::<4, 8>(index, "secp256r1_dbl_test", sdata); + Some((bdata[0], bdata[1])) + } else { + None + } +} + +/* +p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff +a = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc +b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b +F = GF(p) +E = EllipticCurve(F, (a, b)) +for i in range(50): + P = E.random_point(); + R = 2*P + print(f"{i} => Some([\"{P[0]}\",\"{P[1]}\",\"{R[0]}\",\"{R[1]}\"]),"); +*/ +pub fn get_secp256r1_dbl_test_str_data(index: usize) -> Option<[&'static str; 4]> { + match index { + 0 => Some([ + "27675493881189608185000019896557472664993584052492607765862010472799068084386", + "7352244569642907517516338217145955072413095899750742887468663360455917814611", + "79037361267200367168785815871744887643164632725736514592166483490619230121075", + "4077933234470343714276241883240920906139062404301557560973844876092588323988", + ]), + 1 => Some([ + "3360376272603723971624701532604720898676706339789378925682489753859349836709", + "43974730703370341091550222085681175563853832276728365259120521493842901894638", + "52853259462882411969640510256518235858172147991597135999425041457772314775863", + "68030551993853624445614927051536516109504529419190077317254253330368193311403", + ]), + 2 => Some([ + "88676035452794063749057713437014174922784706808285202738350243652079149130848", + "50824802278369076535825198791773071013146180029413341048526379677412529985812", + "72997795821879687104072496376499913353753964526499672523434804669196718469500", + "53762373387355522063466547667122148388804866346281354009917298229100535229898", + ]), + 3 => Some([ + "71372090362814190117397057916958324888323655186932282733112070467895401464045", + "111078344811409713697956617838312642415112432605771182890795220317205222879342", + "107192017635467963612225109436024041761976193253449255543057737880777787424394", + "66432532050590218574089936703773699841012078667162567282180177066526754582860", + ]), + 4 => Some([ + "31794533443528416677323423022454608423413062407393976650457453651471044087593", + "10825871155769179903674149509522662127600696279366454156648831938944439227339", + "61562171207971025081273194822135660173739778880688146968647561830202311477043", + "68513288579896028646973003634202406296825969838469972129649526467266918162572", + ]), + 5 => Some([ + "91060368462428685546946494344746410879554238660838080424175100582724711970328", + "44618099832436814463676281645434359880211089763096176914976894141915094186022", + "48042260235037936576812642413787606194822455404860397369779429598327736582433", + "55591103925453110525440241481629898238706089664546814584468937596549986619793", + ]), + 6 => Some([ + "103965886319419460503461474648787158389315813863288110577385007120529004326270", + "92527803764805458617665276672768135155565125500566011891297680408757047050103", + "96987638986373391379724462307346631952435654568241522815748739193441366499518", + "47374945241639497121296667606175204211725572815381139171523661194929544012544", + ]), + 7 => Some([ + "65219326653226218931128974159670307413494269555373006268070592938984912873583", + "28760857951737509711442707692992902113718450640177089688234917623278702478945", + "65888248906982506067834341692105543875511022545617620335574067809473067919741", + "93711731565602791746384191345707242822637247270636674067520708184331360428596", + ]), + 8 => Some([ + "106210426944164244332900103221252827801696290984990191607033822921403699315591", + "66033879474684143638864677646736962638245492768184401626396449385215185079527", + "97001551816916045057440501215891575169935415297206405924580499588082170702215", + "15313907143426807302847678555561409196571233221759673790380019028238856536708", + ]), + 9 => Some([ + "81563981051532074510470434314933288696795114080113201674765829984492109486247", + "94674495020699996024474089925556673141693997390438745495314754098592636485900", + "70579020275693309056763353367363433388874088838321890677499690050146010172181", + "67532799562931560324535916150414138770617059854724626133688711653168011828562", + ]), + 10 => Some([ + "105926744943182775251389926603443779857488256819460597607587603328742557603551", + "89577085024073186154133911806520967358216088648102277366282334438354735657829", + "36969790594839896046009511659892773317207198219378886190623842770738386817863", + "3176710786499933388496208045770416504923970016417545654543582917258692384339", + ]), + 11 => Some([ + "82952801040030046591653359323115156132588706551236277788806789798511802077341", + "67742512949797851642969519083149394508321283247151086013805783326991120608730", + "18839059680713021002945713993319317691522923507949439596538311131459950100096", + "91749667201212490259065072726948349028602535295787384286392321911710656353203", + ]), + 12 => Some([ + "22249079430472750171094101149699481011007467462254290937028389796347578947965", + "110451857460863006329308647518239512245462245343091310286371274298354769356867", + "51018571064862945609971891259190336795507255409361316715413486315339248406194", + "13348224299011701169985097248159921338180531951451600523286675222818843730914", + ]), + 13 => Some([ + "20484629867944456759671199726804749562668465495566813175379512349152466919404", + "69903748127630776090713848568240576795730885931070138238857012507383688147507", + "111580365324163467359918940152662651124553825640652378688252993921053010482487", + "64388698672712248371625002437296025913342008627987120257603958806391456496624", + ]), + 14 => Some([ + "96537501690400388864304028251258137628846976823895141796583036867993993579900", + "98827348280558554936402817469868835961178438953500929397002088255878035202936", + "107043732074358154753389140602001488371796425269635314156202806901221637108811", + "25720630990896256983664489172451501236091949689965543387711136596715013493811", + ]), + 15 => Some([ + "82964332720597251906863047406034107806204475340942193345880545959223161824757", + "2535705325786516421399361925342224235740784167765863940836092666238884145878", + "74975954962092129827075131486493034123049767154498679732752780100130495721780", + "37486673765435147797773619302439219485825392891339272513129446812828990307073", + ]), + 16 => Some([ + "80882199979706522570998083928753031262041303012506640217916233556544354791914", + "39227900293525267745120623191429459615976130195792597481572786788050036079078", + "80157914499418311940685319482509009427116245762577395716883422644319491876401", + "1129448055158863327588719303480731505190665335377534896973127904371693995043", + ]), + 17 => Some([ + "34641704536088193550079102190647247315424978742846182992457402433345347219295", + "90544308526668958962250641732315596397992903264864385620881118500444134086491", + "74069842740832456785103147548357508233091392981149031026712944888912932864868", + "38595262063118161000564729964611435602495437057846194302717818225702965593488", + ]), + 18 => Some([ + "38041273931034400682131345618719147367267652239101263153549641389166797025805", + "8576277969817328064421002238004850377503021104554593250727903538068307018853", + "7644947780884249836683427700565231870908265110028398427877728739930650676562", + "328209809804861647965920601674391453752039760419779127506949862375352123044", + ]), + 19 => Some([ + "8807143131588724563187546356572887144829343259549349928752248026524571566309", + "71764049905439050593922539421751972431097533016322006535867892917433028764473", + "23458790796645888403187291802917321680007079214960418481959483134884565534763", + "111924358597078312991433457376953377492712605551203682978487745838169539088610", + ]), + 20 => Some([ + "63046303122414836857224945991520306458421317653897286450867387616910328505302", + "68321673072516662136375058424032953039446318778956851743367881469345795571488", + "105609519772040867633323682815853052290089931431224707019153475958466586956217", + "48018313219458211946382006202812789635140218122120147934600538758473417440637", + ]), + 21 => Some([ + "102850103546686443020777382132243955419052591850564364137968313810076658648496", + "1254184153782947799803255341743511148898695450011429568204904192310559388673", + "34701454034912545367190186926154646880963419792730319801590280911209128145083", + "5393716746037739427030194481726031199116389264008785647551233681919420827579", + ]), + 22 => Some([ + "62125292866235572735647937972691049010068228868522712378077493442262158281702", + "99503402427857905262229444564080614638102331332799423074576486003566320336076", + "75941257376561286389079890532683795873852315520311718401442439244384069352630", + "43410723719737593730678318955332439501752729276328362873620500901115161656950", + ]), + 23 => Some([ + "64002322064408361853299819563502381355621924280333335533869684528122027965061", + "84495687164229915249162290734988559477746746377164916107694314118818232962524", + "42898088288940134681766700375196999964990414107254048963459932412730534424485", + "29567662893995207927990509632456334189154106280859269336843413690718319464856", + ]), + 24 => Some([ + "12230674786972538120739240328212053191294726317188869883061405966970746595705", + "31516260071695450510879817050566422129794141579982164039599932703606874916061", + "89724391829108235533785922249105481431224544947348739771699403896925352102088", + "86059397811547285278094778759838953930413515056327197861905859368936505580885", + ]), + 25 => Some([ + "58911865631286240286286539642126777192947789330399378943775821308950784285059", + "106618713328730944840192222484431068598783463090223974086782442308115955545190", + "27119460803600800453434644673040121454455038496677588224477119999976355504530", + "111350586119120342537866962125681396060167593324927256000078238332947571537442", + ]), + 26 => Some([ + "17516364029844442641337075032263298201873493073042943428522943752637722956884", + "99780914298702666179771354652927927952146668873384802596545252230428779009011", + "82358107210864043857952883304094862908976337621695822836748665056314939088447", + "13715968981053881371721835537936146409767904603911336360276385845766049746422", + ]), + 27 => Some([ + "74676228708033920986476364862754304451632046939799453186857911444661407501891", + "5927547383987894337822663704444138312583278465380838092641381201302317593589", + "98502616564885478487235974276837215120346349879844786094914300546915810617728", + "75885107513472011330622269146806285453016887708274925865578929439188084362800", + ]), + 28 => Some([ + "24157278883353118388239010875288245752318985749430363083628571624681771367032", + "84961028276250472053794415497313644852366248374320110870723180287329394555999", + "3330552996162391742832531233626085633537169937294466797389676474135741805139", + "81503738788380421890123773740525891797551407910254861173192877911391987693810", + ]), + 29 => Some([ + "17655551802530837699364385806086954367370263699138951734258195446283051940946", + "98236955804386266924558244073713230219746501710518799298106066085008964705579", + "57373187902213099141557276910127760835812365635279458399636976452428314017903", + "9636580600280637726687443346287348575766474677970441092861343995111248178274", + ]), + 30 => Some([ + "92022461209860697440521006840256200564038622301844592762370643753640651534991", + "60558887757685951339215977663629837935727625839126000879651344761701232882130", + "91068447104669134346553826252864994723246512397081355308357996049153969413034", + "114436257797237415761486717557225249123444379935578269119188334930600216061100", + ]), + 31 => Some([ + "40735563338348272209711080311152397013729625167694629904889796587190384159878", + "103394991638383348741611031375232586820926278961195153765593574672767509666946", + "7767882644474144882688122335458137792113104721225153739076142233830789110880", + "21420242927622805275896998550026136085544516609245995982558646703074913955120", + ]), + 32 => Some([ + "87037739712144167008889348413849026685553472861758898069048907403689426969264", + "69811747734495700577564216321023837046241672496286717669840584631876922566008", + "4408995650263083241075343365037204209717714840832666019099705245657855977893", + "77519099661396457842203008707072557749026719773815850614662057747366057170371", + ]), + 33 => Some([ + "102307323108974896425869759287684534597753535918441079037597093081066898905523", + "42264319808047447998438717210540350015751262088840890747437915648429111651182", + "67100401758586308051929785437996646296320231818129368260495465036591455823021", + "107194883327677412413837098740699799247735320308927394536601938897972894830923", + ]), + 34 => Some([ + "102929584491481651932553963305865068019795484110342028010262347695551448209928", + "104315708275376527763160005390439347397781353204748765719436768555321906257952", + "10403471979638198626278497210025626524860064482229064684808713578165907868298", + "49336784843051988726052384190907456418108387966940856140766606450916396674854", + ]), + 35 => Some([ + "38473870230989310668264274838632438522446363421372475860190301222545472809780", + "97345650640090374352059605999075195801309291071431682904319809615819123624985", + "18746312099851846921879737588510086104615122710877875369690874313180242176304", + "63852631640261386482515242207657436643396006625142718652569168015656644612958", + ]), + 36 => Some([ + "111172365718777275261553049424428529843132810242786988596766731423856070474763", + "32894689523743969696950044240462150378016563604261509322210792502679911101598", + "21001578149873794620866081274221900471032662007616757226205252751061799172978", + "83491776935661227994808240284899338462513069457427691158188743983077458455237", + ]), + 37 => Some([ + "96946461112700250258584173246125407860519408199485590262165671530140251267253", + "63833705914298844770895413071881165278384966242232556079275216948879800758898", + "53313054758482710952584912174011882402989680261576083898969060616217867673777", + "69935351470681056441519422734336191119513717666082220696982519579791724292977", + ]), + 38 => Some([ + "42340520195350827366266644689985470833831335294106115895278918186457640808106", + "34161995772621826684243404258534800600675860756541900224421043769840774351397", + "27688047487626059001558633704427386371949511389832012274777469895652170986654", + "69879424116441455155756606426803509348361249825720648607126586477708296240444", + ]), + 39 => Some([ + "95995450170096461230228761655365532900866210643271944071491176089373079855005", + "37223595982737666989884270765650773530495097325417815961173155837912976958883", + "5454086141833025197796219662821159066398337835428881252109615110578757165363", + "81614047057994862372520469438146729029679722278625644677073368093893804430747", + ]), + 40 => Some([ + "22805561457752636094721086973193770193044553761898008645735770860067309849542", + "76122302100612426549304461560020749139367314089832284841993225181404923861170", + "40338732334467043512259459638567318527155753186418690373091973991852276338236", + "88977652408711377264669794973375958023041300003319958641864909530050057384069", + ]), + 41 => Some([ + "73884916476412840787432457274089533668123750611740988703793990873479668341267", + "95375953396453830641479439091149343986838296783284410546155159111696077466671", + "13278794831264317878740049962945340832281608095403907754296647279357981691070", + "93479632508352372310355930177768467644813000592911557048584067102644563839360", + ]), + 42 => Some([ + "94941193142043240036059928178683880901688999529090111670381882971844837503037", + "114371587718742372061688208831213894214366851291820397231992995224259969622625", + "72777706257975327042753004132486596699962560376026966469545399199160716463074", + "114540614316974213053899847712189585924251711944228773093364280994383190154957", + ]), + 43 => Some([ + "4253189979396770887746703699548781354801499301957512157857615543224980036648", + "58877397710734925298226969980530754719555255196436220096277790458128792906250", + "50483388696246046430618742841874776760005205365821788982791359957721720516986", + "59495465481421930390441071589159802068466910095526901052457542972222451310945", + ]), + 44 => Some([ + "10394902551382846129674861822500670503694149627817912602881936017490906790392", + "72341768474514678671359875990601473484533978345834760046963654129453630085782", + "102812627892202637177795752111556094509063075418756313273952952148617094876122", + "28626981733687699848179882938274423067928389533757862828457083548325085613961", + ]), + 45 => Some([ + "16215521976833204209050248497110404538897337563635192783616736445368048831192", + "44161797662527352894621865841326779649853068942784278731876887905470170816256", + "96701684562904329883895684844683627284420234618267449701968521327399111835811", + "86467583438555269372399863894802164298514241124505016282687302672455382615999", + ]), + 46 => Some([ + "77304614138727580166467654224182477075304448916355029313519480279527026362685", + "4097294974765126542919353376388476485070593452805769406824288484172598351160", + "50768358693567732372714579880181823673916620610363234297674956719343270986769", + "112104006189746323710806096486930425242587209629621022190465721944855454525692", + ]), + 47 => Some([ + "46630979645136848407503076566838422657349763976789907884318182230480133505164", + "50039112218205956891168840689072767346876077125226222747266575979778032021694", + "61247552980881057576144354591383932614484085471246070341570683599992494819988", + "68136838446751300763551527780117141304059332794759369321508784589076628277655", + ]), + 48 => Some([ + "79492169635876824167413866720993449454663528481176426987777389681340468912177", + "106977025309353131750871442464209539184863629176593136980629258721474651776580", + "112345830952717412914379261907614526402048162012954114300835209619748870016011", + "107001196952579848275877468070801422289566544783405231404190478281531942515168", + ]), + 49 => Some([ + "83848287480453503544287194869475671289089650965884747542818004494192759435413", + "14423946942137591196388014316114088651825974510710115056815066159144287107799", + "18171416013064719205219973633800359850758400149900498404718542060411164329311", + "25249779374936489305192546248778458932225984848949286977981267947408203584264", + ]), + _ => None, + } +} diff --git a/precompiles/helpers/Cargo.toml b/precompiles/helpers/Cargo.toml index b768c194f..6839a050e 100644 --- a/precompiles/helpers/Cargo.toml +++ b/precompiles/helpers/Cargo.toml @@ -14,6 +14,7 @@ circuit = { workspace = true } ark-ff = { workspace = true } ark-std = { workspace = true } ark-secp256k1 = { workspace = true } +ark-secp256r1 = { workspace = true } ark-bn254 = { workspace = true } ark-bls12-381 = { workspace = true } num-bigint = { workspace = true } diff --git a/precompiles/helpers/src/arith_eq/mod.rs b/precompiles/helpers/src/arith_eq/mod.rs index ce92bb1a8..8bfcd8522 100644 --- a/precompiles/helpers/src/arith_eq/mod.rs +++ b/precompiles/helpers/src/arith_eq/mod.rs @@ -2,8 +2,10 @@ mod arith256; mod bn254complex; mod bn254curve; mod secp256k1; +mod secp256r1; pub use arith256::*; pub use bn254complex::*; pub use bn254curve::*; pub use secp256k1::*; +pub use secp256r1::*; diff --git a/precompiles/helpers/src/arith_eq/secp256r1.rs b/precompiles/helpers/src/arith_eq/secp256r1.rs new file mode 100644 index 000000000..fa10fba4c --- /dev/null +++ b/precompiles/helpers/src/arith_eq/secp256r1.rs @@ -0,0 +1,30 @@ +// TODO: Implement these functions in assembly to speed things up! + +use ark_ff::{BigInt, PrimeField}; +use ark_secp256r1::Fq as Secp256r1Field; + +pub fn secp256r1_add(p1: &[u64; 8], p2: &[u64; 8], p: &mut [u64; 8]) { + let x1 = Secp256r1Field::from(BigInt::<4>(p1[0..4].try_into().unwrap())); + let y1 = Secp256r1Field::from(BigInt::<4>(p1[4..8].try_into().unwrap())); + let x2 = Secp256r1Field::from(BigInt::<4>(p2[0..4].try_into().unwrap())); + let y2 = Secp256r1Field::from(BigInt::<4>(p2[4..8].try_into().unwrap())); + + let s = (y2 - y1) / (x2 - x1); + let x3 = s * s - (x1 + x2); + let y3 = s * (x1 - x3) - y1; + + p[..4].copy_from_slice(&x3.into_bigint().0); + p[4..].copy_from_slice(&y3.into_bigint().0); +} + +pub fn secp256r1_dbl(p1: &[u64; 8], p: &mut [u64; 8]) { + let x1 = Secp256r1Field::from(BigInt::<4>(p1[0..4].try_into().unwrap())); + let y1 = Secp256r1Field::from(BigInt::<4>(p1[4..8].try_into().unwrap())); + + let s = (Secp256r1Field::from(3u64) * x1 * x1 + Secp256r1Field::from(-3)) / (y1 + y1); + let x3 = s * s - (x1 + x1); + let y3 = s * (x1 - x3) - y1; + + p[..4].copy_from_slice(&x3.into_bigint().0); + p[4..].copy_from_slice(&y3.into_bigint().0); +} diff --git a/precompiles/helpers/src/common.rs b/precompiles/helpers/src/common.rs index 61a28cd2c..b979f9da1 100644 --- a/precompiles/helpers/src/common.rs +++ b/precompiles/helpers/src/common.rs @@ -2,6 +2,7 @@ use ark_bls12_381::Fq as Bls12_381Field; use ark_bn254::Fq as Bn254Field; use ark_ff::PrimeField; use ark_secp256k1::Fq as Secp256k1Field; +use ark_secp256r1::Fq as Secp256r1Field; use num_bigint::{BigInt, Sign}; use num_traits::Zero; @@ -183,6 +184,17 @@ impl FieldToBigInt for Bls12_381Field { } } +impl FieldToBigInt for Secp256r1Field { + fn to_bigint(&self) -> BigInt { + let mut result = BigInt::zero(); + for &word in self.into_bigint().0.iter().rev() { + result <<= 64; + result += word; + } + result + } +} + pub fn bigint_from_field(value: &F) -> BigInt { value.to_bigint() } diff --git a/ziskos/entrypoint/src/syscalls/mod.rs b/ziskos/entrypoint/src/syscalls/mod.rs index b43dd3c18..8e3b54a4a 100644 --- a/ziskos/entrypoint/src/syscalls/mod.rs +++ b/ziskos/entrypoint/src/syscalls/mod.rs @@ -18,6 +18,8 @@ mod point; mod poseidon2; mod secp256k1_add; mod secp256k1_dbl; +mod secp256r1_add; +mod secp256r1_dbl; mod sha256f; mod syscall; @@ -41,6 +43,8 @@ pub use point::*; pub use poseidon2::*; pub use secp256k1_add::*; pub use secp256k1_dbl::*; +pub use secp256r1_add::*; +pub use secp256r1_dbl::*; pub use sha256f::*; pub use syscall::*; diff --git a/ziskos/entrypoint/src/syscalls/secp256r1_add.rs b/ziskos/entrypoint/src/syscalls/secp256r1_add.rs new file mode 100644 index 000000000..00d83afea --- /dev/null +++ b/ziskos/entrypoint/src/syscalls/secp256r1_add.rs @@ -0,0 +1,42 @@ +//! Secp256r1Add system call interception + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +use crate::ziskos_syscall; + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +use core::arch::asm; + +use super::point::SyscallPoint256; + +#[derive(Debug)] +#[repr(C)] +pub struct SyscallSecp256r1AddParams<'a> { + pub p1: &'a mut SyscallPoint256, + pub p2: &'a SyscallPoint256, +} + +/// Performs the addition of two points on the Secp256r1 curve, storing the result in the first point. +/// +/// The `Secp256r1Add` system call executes a CSR set on a custom port. When transpiling from RISC-V to Zisk, +/// this instruction is replaced with a precompiled operation—specifically, `Secp256r1Add`. +/// +/// `Secp256r1Add` operates on two points, each with two coordinates of 256 bits. +/// Each coordinate is represented as an array of four `u64` elements. +/// The syscall takes as a parameter the address of a structure containing points `p1` and `p2`. +/// The result of the addition is stored in `p1`. +/// +/// ### Safety +/// +/// The caller must ensure that `p1` is a valid pointer to data that is aligned to an eight-byte boundary. +/// +/// The caller must ensure that both `p1` and `p2` coordinates are within the range of the Secp256r1 base field. +/// +/// The resulting point will have both coordinates in the range of the Secp256r1 base field. +#[allow(unused_variables)] +#[no_mangle] +pub extern "C" fn syscall_secp256r1_add(params: &mut SyscallSecp256r1AddParams) { + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + ziskos_syscall!(0x815, params); + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!() +} diff --git a/ziskos/entrypoint/src/syscalls/secp256r1_dbl.rs b/ziskos/entrypoint/src/syscalls/secp256r1_dbl.rs new file mode 100644 index 000000000..b8514c1b6 --- /dev/null +++ b/ziskos/entrypoint/src/syscalls/secp256r1_dbl.rs @@ -0,0 +1,34 @@ +//! syscall_secp256r1_dbl system call interception + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +use crate::ziskos_syscall; + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +use core::arch::asm; + +use super::point::SyscallPoint256; + +/// Executes the doubling of a point on the Secp256r1 curve. +/// +/// The `syscall_secp256r1_dbl` system call executes a CSR set on a custom port. When transpiling from RISC-V to Zisk, +/// this instruction is replaced with a precompiled operation—specifically, `Secp256r1Dbl`. +/// +/// `syscall_secp256r1_dbl` operates on a point with two coordinates, each consisting of 256 bits. +/// Each coordinate is represented as an array of four `u64` elements. The syscall takes as a parameter +/// the address of the point, and the result of the doubling operation is stored at the same location. +/// +/// ### Safety +/// +/// The caller must ensure that `p1` is a valid pointer to data that is aligned to an eight-byte boundary. +/// +/// The caller must ensure that `p1` coordinates are within the range of the Secp256r1 base field. +/// +/// The resulting point will have both coordinates in the range of the Secp256r1 base field. +#[allow(unused_variables)] +#[no_mangle] +pub extern "C" fn syscall_secp256r1_dbl(p1: &mut SyscallPoint256) { + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + ziskos_syscall!(0x816, p1); + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!() +} diff --git a/ziskos/entrypoint/src/syscalls/syscall.rs b/ziskos/entrypoint/src/syscalls/syscall.rs index be2b32703..fbead90e8 100644 --- a/ziskos/entrypoint/src/syscalls/syscall.rs +++ b/ziskos/entrypoint/src/syscalls/syscall.rs @@ -21,3 +21,5 @@ pub const SYSCALL_ADD256_ID: u16 = 0x811; pub const SYSCALL_POSEIDON2_ID: u16 = 0x812; pub const SYSCALL_DMA_MEMCPY_ID: u16 = 0x813; pub const SYSCALL_DMA_MEMCMP_ID: u16 = 0x814; +pub const SYSCALL_SECP256R1_ADD_ID: u16 = 0x815; +pub const SYSCALL_SECP256R1_DBL_ID: u16 = 0x816; From 3cc1229725eec631945beb961ff38ee8a6b5ad41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:29:13 +0000 Subject: [PATCH 376/782] ECDSA verify --- ziskos/entrypoint/src/zisklib/fcalls/mod.rs | 3 + .../src/zisklib/fcalls/secp256r1/ecdsa.rs | 66 ++++ .../src/zisklib/fcalls/secp256r1/mod.rs | 3 + .../entrypoint/src/zisklib/fcalls_impl/mod.rs | 1 + .../src/zisklib/fcalls_impl/proxy.rs | 5 +- .../fcalls_impl/secp256r1/constants.rs | 31 ++ .../zisklib/fcalls_impl/secp256r1/ecdsa.rs | 297 +++++++++++++++ .../src/zisklib/fcalls_impl/secp256r1/mod.rs | 5 + ziskos/entrypoint/src/zisklib/lib/mod.rs | 2 + .../src/zisklib/lib/secp256r1/constants.rs | 26 ++ .../src/zisklib/lib/secp256r1/curve.rs | 350 ++++++++++++++++++ .../src/zisklib/lib/secp256r1/ecdsa.rs | 57 +++ .../src/zisklib/lib/secp256r1/field.rs | 29 ++ .../src/zisklib/lib/secp256r1/mod.rs | 10 + .../src/zisklib/lib/secp256r1/scalar.rs | 17 + 15 files changed, 900 insertions(+), 2 deletions(-) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs create mode 100644 ziskos/entrypoint/src/zisklib/fcalls/secp256r1/mod.rs create mode 100644 ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/constants.rs create mode 100644 ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/ecdsa.rs create mode 100644 ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/mod.rs create mode 100644 ziskos/entrypoint/src/zisklib/lib/secp256r1/constants.rs create mode 100644 ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs create mode 100644 ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs create mode 100644 ziskos/entrypoint/src/zisklib/lib/secp256r1/field.rs create mode 100644 ziskos/entrypoint/src/zisklib/lib/secp256r1/mod.rs create mode 100644 ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs diff --git a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs index 2de781ae3..35cf72e47 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs @@ -20,6 +20,7 @@ pub const FCALL_BIG_INT_DIV_ID: u16 = 17; pub const FCALL_BIN_DECOMP_ID: u16 = 18; pub const FCALL_BLS12_381_FP2_SQRT_ID: u16 = 19; pub const FCALL_SECP256K1_ECDSA_VERIFY_ID: u16 = 20; +pub const FCALL_SECP256R1_ECDSA_VERIFY_ID: u16 = 21; mod big_int256_div; mod big_int_div; @@ -29,6 +30,7 @@ mod bn254; mod msb_pos_256; mod msb_pos_384; mod secp256k1; +mod secp256r1; pub use big_int256_div::*; pub use big_int_div::*; @@ -38,3 +40,4 @@ pub use bn254::*; pub use msb_pos_256::*; pub use msb_pos_384::*; pub use secp256k1::*; +pub use secp256r1::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs new file mode 100644 index 000000000..d2757d58b --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs @@ -0,0 +1,66 @@ +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { + use core::arch::asm; + use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, zisklib::FCALL_SECP256R1_ECDSA_VERIFY_ID}; + } +} + +/// Hints the ECDSA recovery computation over the `secp256r1` curve. +/// +/// Given the public key `PK`, a message hash `z`, and signature components `(r, s)`, +/// this function hints a curve point `P` such that: +/// +/// ```text +/// P = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]PK +/// ``` +/// +/// ### Parameters +/// +/// - `pk_value`: The public key `PK = (x, y)`, +/// represented as 8 `u64` limbs in little-endian order: `[x₀, x₁, x₂, x₃, y₀, y₁, y₂, y₃]` +/// - `z_value`: The message hash (prehash), represented as 4 `u64` limbs in little-endian order +/// - `r_value`: The signature `r` component, represented as 4 `u64` limbs in little-endian order +/// - `s_value`: The signature `s` component, represented as 4 `u64` limbs in little-endian order +/// +/// ### Returns +/// +/// The curve point `P = (x, y)` as 8 `u64` limbs in little-endian order: +/// `[x₀, x₁, x₂, x₃, y₀, y₁, y₂, y₃]` +/// +/// ### Safety +/// +/// The caller must ensure that all input pointers (`pk_value`, `z_value`, `r_value`, `s_value`) are +/// valid and aligned to an 8-byte boundary. +/// +/// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness +/// of the result. It is the caller's responsibility to ensure it. +#[allow(unused_variables)] +pub fn fcall_secp256r1_ecdsa_verify( + pk_value: &[u64; 8], + z_value: &[u64; 4], + r_value: &[u64; 4], + s_value: &[u64; 4], +) -> [u64; 8] { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!(); + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + ziskos_fcall_param!(pk_value, 8); + ziskos_fcall_param!(z_value, 4); + ziskos_fcall_param!(r_value, 4); + ziskos_fcall_param!(s_value, 4); + ziskos_fcall!(FCALL_SECP256R1_ECDSA_VERIFY_ID); + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] + } +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/mod.rs new file mode 100644 index 000000000..c39dd2576 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/mod.rs @@ -0,0 +1,3 @@ +mod ecdsa; + +pub use ecdsa::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs index ca9ea9138..f5a520496 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/mod.rs @@ -7,6 +7,7 @@ mod msb_pos_256; mod msb_pos_384; mod proxy; mod secp256k1; +mod secp256r1; mod utils; pub use proxy::*; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs index 1697f91db..407235cc5 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/proxy.rs @@ -5,12 +5,12 @@ use crate::zisklib::{ FCALL_BN254_FP2_INV_ID, FCALL_BN254_FP_INV_ID, FCALL_BN254_TWIST_ADD_LINE_COEFFS_ID, FCALL_BN254_TWIST_DBL_LINE_COEFFS_ID, FCALL_MSB_POS_256_ID, FCALL_MSB_POS_384_ID, FCALL_SECP256K1_ECDSA_VERIFY_ID, FCALL_SECP256K1_FN_INV_ID, FCALL_SECP256K1_FP_INV_ID, - FCALL_SECP256K1_FP_SQRT_ID, + FCALL_SECP256K1_FP_SQRT_ID, FCALL_SECP256R1_ECDSA_VERIFY_ID, }; use super::{ big_int256_div::*, big_int_div::*, bin_decomp::*, bls12_381::*, bn254::*, msb_pos_256::*, - msb_pos_384::*, secp256k1::*, + msb_pos_384::*, secp256k1::*, secp256r1::*, }; pub fn fcall_proxy(id: u64, params: &[u64], results: &mut [u64]) -> i64 { @@ -38,6 +38,7 @@ pub fn fcall_proxy(id: u64, params: &[u64], results: &mut [u64]) -> i64 { FCALL_BIG_INT256_DIV_ID => fcall_big_int256_div(params, results), FCALL_BIG_INT_DIV_ID => fcall_big_int_div(params, results), FCALL_BIN_DECOMP_ID => fcall_bin_decomp(params, results), + FCALL_SECP256R1_ECDSA_VERIFY_ID => fcall_secp256r1_ecdsa_verify(params, results), _ => panic!("Unsupported fcall ID {id}"), } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/constants.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/constants.rs new file mode 100644 index 000000000..000521681 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/constants.rs @@ -0,0 +1,31 @@ +use lazy_static::lazy_static; +use num_bigint::BigUint; + +lazy_static! { + pub static ref P: BigUint = BigUint::parse_bytes( + b"ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + 16 + ) + .unwrap(); + pub static ref N: BigUint = BigUint::parse_bytes( + b"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + 16 + ) + .unwrap(); +} + +pub const E_A: [u64; 4] = + [0xFFFF_FFFF_FFFF_FFFC, 0x0000_0000_FFFF_FFFF, 0x0000_0000_0000_0000, 0xFFFF_FFFF_0000_0001]; + +pub const IDENTITY: [u64; 8] = [0u64; 8]; + +pub const G: [u64; 8] = [ + 0xF4A1_3945_D898_C296, + 0x7703_7D81_2DEB_33A0, + 0xF8BC_E6E5_63A4_40F2, + 0x6B17_D1F2_E12C_4247, + 0xCBB6_4068_37BF_51F5, + 0x2BCE_3357_6B31_5ECE, + 0x8EE7_EB4A_7C0F_9E16, + 0x4FE3_42E2_FE1A_7F9B, +]; diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/ecdsa.rs new file mode 100644 index 000000000..1cd2b3145 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/ecdsa.rs @@ -0,0 +1,297 @@ +use num_traits::Zero; + +use crate::zisklib::fcalls_impl::utils::{ + biguint_from_u64, biguint_from_u64_digits, n_u64_digits_from_biguint, +}; + +use super::constants::{E_A, G, IDENTITY, N, P}; + +pub fn fcall_secp256r1_ecdsa_verify(params: &[u64], results: &mut [u64]) -> i64 { + // Get the input + let pk: &[u64; 8] = ¶ms[0..8].try_into().unwrap(); + let z: &[u64; 4] = ¶ms[8..12].try_into().unwrap(); + let r: &[u64; 4] = ¶ms[12..16].try_into().unwrap(); + let s: &[u64; 4] = ¶ms[16..20].try_into().unwrap(); + + // Get the curve point P + let p = secp256r1_ecdsa_verify(pk, z, r, s); + + // Store the result + results[0..8].copy_from_slice(&p); + + 8 +} + +fn secp256r1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u64; 4]) -> [u64; 8] { + // Given the public key pk and the signature (r, s) over the message hash z: + // 1. Computes s_inv = s⁻¹ mod n + // 2. Computes u1 = z·s_inv mod n + // 3. Computes u2 = r·s_inv mod n + // 4. Computes and returns the curve point p = u1·G + u2·PK + let s_inv = secp256r1_fn_inv(s); + let u1 = secp256r1_fn_mul(z, &s_inv); + let u2 = secp256r1_fn_mul(r, &s_inv); + secp256r1_curve_dbl_scalar_mul(&u1, &G, &u2, pk) +} + +fn secp256r1_fn_mul(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let b_big = biguint_from_u64_digits(b); + let product = (a_big * b_big) % &*N; + n_u64_digits_from_biguint(&product) +} + +fn secp256r1_fn_inv(a: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let inv = a_big.modinv(&N); + match inv { + Some(inverse) => n_u64_digits_from_biguint(&inverse), + None => panic!("Inverse does not exist"), + } +} + +fn secp256r1_fp_add(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let b_big = biguint_from_u64_digits(b); + let sum = (a_big + b_big) % &*P; + n_u64_digits_from_biguint(&sum) +} + +fn secp256r1_fp_sub(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let b_big = biguint_from_u64_digits(b); + let diff = if a_big >= b_big { a_big - b_big } else { (a_big + &*P) - b_big }; + n_u64_digits_from_biguint(&diff) +} + +fn secp256r1_fp_scalar_mul(a: &[u64; 4], scalar: u64) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let scalar_big = biguint_from_u64(scalar); + let product = (a_big * scalar_big) % &*P; + n_u64_digits_from_biguint(&product) +} + +fn secp256r1_fp_mul(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let b_big = biguint_from_u64_digits(b); + let product = (a_big * b_big) % &*P; + n_u64_digits_from_biguint(&product) +} + +fn secp256r1_fp_square(a: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let square = (a_big.clone() * a_big) % &*P; + n_u64_digits_from_biguint(&square) +} + +fn secp256r1_fp_inv(a: &[u64; 4]) -> [u64; 4] { + let a_big = biguint_from_u64_digits(a); + let inv = a_big.modinv(&P); + match inv { + Some(inverse) => n_u64_digits_from_biguint(&inverse), + None => panic!("Inverse does not exist"), + } +} + +fn secp256r1_curve_add(p: &[u64; 8], q: &[u64; 8]) -> [u64; 8] { + let x1: &[u64; 4] = &p[0..4].try_into().unwrap(); + let y1: &[u64; 4] = &p[4..8].try_into().unwrap(); + let x2: &[u64; 4] = &q[0..4].try_into().unwrap(); + let y2: &[u64; 4] = &q[4..8].try_into().unwrap(); + + if x1 == x2 { + if y1 == y2 { + return secp256r1_curve_dbl(p); + } else { + return IDENTITY; + } + } + + if p == &IDENTITY { + return *q; + } else if q == &IDENTITY { + return *p; + } + + let lambda = { + let y2_minus_y1 = secp256r1_fp_sub(y2, y1); + let x2_minus_x1 = secp256r1_fp_sub(x2, x1); + let x2_minus_x1_inv = secp256r1_fp_inv(&x2_minus_x1); + secp256r1_fp_mul(&y2_minus_y1, &x2_minus_x1_inv) + }; + + let x3 = { + let lambda_sq = secp256r1_fp_square(&lambda); + let x1_plus_x2 = secp256r1_fp_add(x1, x2); + secp256r1_fp_sub(&lambda_sq, &x1_plus_x2) + }; + + let y3 = { + let lambda_x1_minus_x3 = { + let x1_minus_x3 = secp256r1_fp_sub(x1, &x3); + secp256r1_fp_mul(&lambda, &x1_minus_x3) + }; + secp256r1_fp_sub(&lambda_x1_minus_x3, y1) + }; + + let mut result = [0u64; 8]; + result[0..4].copy_from_slice(&x3); + result[4..8].copy_from_slice(&y3); + result +} + +fn secp256r1_curve_dbl(p: &[u64; 8]) -> [u64; 8] { + if p == &IDENTITY { + return *p; + } + + let x: &[u64; 4] = &p[0..4].try_into().unwrap(); + let y: &[u64; 4] = &p[4..8].try_into().unwrap(); + + let lambda = { + let three_x1_sq = { + let x1_sq = secp256r1_fp_square(x); + secp256r1_fp_scalar_mul(&x1_sq, 3) + }; + let num = secp256r1_fp_add(&three_x1_sq, &E_A); + + let two_y1 = secp256r1_fp_scalar_mul(y, 2); + let den = secp256r1_fp_inv(&two_y1); + + secp256r1_fp_mul(&num, &den) + }; + + let x3 = { + let lambda_sq = secp256r1_fp_square(&lambda); + let two_x1 = secp256r1_fp_scalar_mul(x, 2); + secp256r1_fp_sub(&lambda_sq, &two_x1) + }; + + let y3 = { + let lambda_x1_minus_x3 = { + let x1_minus_x3 = secp256r1_fp_sub(x, &x3); + secp256r1_fp_mul(&lambda, &x1_minus_x3) + }; + secp256r1_fp_sub(&lambda_x1_minus_x3, y) + }; + + let mut result = [0u64; 8]; + result[0..4].copy_from_slice(&x3); + result[4..8].copy_from_slice(&y3); + result +} + +fn secp256r1_curve_dbl_scalar_mul( + k1: &[u64; 4], + p1: &[u64; 8], + k2: &[u64; 4], + p2: &[u64; 8], +) -> [u64; 8] { + let mut r = IDENTITY; + for i in (0..256).rev() { + r = secp256r1_curve_dbl(&r); + + let k1_bit = (k1[i / 64] >> (i % 64)) & 1; + let k2_bit = (k2[i / 64] >> (i % 64)) & 1; + + if k1_bit == 1 { + r = secp256r1_curve_add(&r, p1); + } + if k2_bit == 1 { + r = secp256r1_curve_add(&r, p2); + } + } + + r +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_dbl_scalar_mul() { + // 0 * IDENTITY + 0 * IDENTITY = IDENTITY + let k1 = [0u64; 4]; + let p1 = IDENTITY; + let k2 = [0u64; 4]; + let p2 = IDENTITY; + + let result = secp256r1_curve_dbl_scalar_mul(&k1, &p1, &k2, &p2); + assert_eq!(result, IDENTITY); + + // 1 * G + 0 * IDENTITY = G + let k1 = [1u64, 0, 0, 0]; + let p1 = G; + let k2 = [0u64; 4]; + let p2 = IDENTITY; + + let result = secp256r1_curve_dbl_scalar_mul(&k1, &p1, &k2, &p2); + assert_eq!(result, G); + + // 0 * IDENTITY + 1 * G = G + let k1 = [0u64; 4]; + let p1 = IDENTITY; + let k2 = [1u64, 0, 0, 0]; + let p2 = G; + + let result = secp256r1_curve_dbl_scalar_mul(&k1, &p1, &k2, &p2); + assert_eq!(result, G); + + // 2 * G + 3 * G = 5 * G + let k1 = [2u64, 0, 0, 0]; + let p1 = G; + let k2 = [3u64, 0, 0, 0]; + let p2 = G; + + let result = secp256r1_curve_dbl_scalar_mul(&k1, &p1, &k2, &p2); + let expected = [ + 0x21554a0dc3d033ed, + 0xef8c82fd1f5be524, + 0xd784c85608668fdf, + 0x51590b7a515140d2, + 0xd1d0bb44fda16da4, + 0xd012f00d4d80888, + 0x8ae1bf36bf8a7926, + 0xe0c17da8904a727d, + ]; + assert_eq!(result, expected); + + // Random test + let k1 = [0xc4bed2f1f47f9a54, 0x9cd109ce498a9b95, 0xd9d5232066758816, 0xf3b0020b50fafcfe]; + let p1 = [ + 0x3c86442bafe51c41, + 0xa709f983d1ad2017, + 0x503d3c4c7699e29f, + 0x51f730041a088667, + 0xb4c365119c4d3bfc, + 0x41f620cca7b9001f, + 0xeb5025341faef867, + 0xf55cbe6ac6ff94ce, + ]; + let k2 = [0xb652a5b177426eaa, 0xe44bcf080ef8aaf7, 0x3966826b0d4eb5f5, 0xe33606d47d23f70a]; + let p2 = [ + 0x8ba8cddeb162e15b, + 0xb33b65b9a6c8945c, + 0x7480c2cff5cea8e0, + 0x3393c7d67a51330d, + 0xf1d29bdb9ed24e90, + 0x5da65af891bf0b50, + 0x99cc7a2be908e44e, + 0x8de4594f14dc559d, + ]; + + let result = secp256r1_curve_dbl_scalar_mul(&k1, &p1, &k2, &p2); + let expected = [ + 0xa57ec301274eaa5c, + 0x7f2d4f49c426a01b, + 0x910612a4889b8c13, + 0x4436050010e76a1e, + 0x2cd45c4320036102, + 0xc2d5e53a2316da0a, + 0x76355a97180de3fe, + 0xd15d039ba7950631, + ]; + assert_eq!(result, expected); + } +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/mod.rs new file mode 100644 index 000000000..4799a59ba --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls_impl/secp256r1/mod.rs @@ -0,0 +1,5 @@ +mod constants; +mod ecdsa; + +use constants::*; +pub use ecdsa::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/mod.rs b/ziskos/entrypoint/src/zisklib/lib/mod.rs index 6ed327c62..c1aacc644 100644 --- a/ziskos/entrypoint/src/zisklib/lib/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/mod.rs @@ -4,6 +4,7 @@ mod bls12_381; mod bn254; mod constants; mod secp256k1; +mod secp256r1; mod sha256f_compress; mod utils; @@ -14,5 +15,6 @@ pub use bls12_381::*; pub use bn254::*; pub use constants::*; pub use secp256k1::*; +pub use secp256r1::*; pub use sha256f_compress::*; pub use utils::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/constants.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/constants.rs new file mode 100644 index 000000000..cd3e3df1a --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/constants.rs @@ -0,0 +1,26 @@ +//! Constants for the [Secp256r1](https://csrc.nist.gov/pubs/sp/800/186/final) elliptic curve + +/// B parameter of the curve E: y² = x³ + a·x + b +pub const E_A: [u64; 4] = + [0xFFFF_FFFF_FFFF_FFFC, 0x0000_0000_FFFF_FFFF, 0x0000_0000_0000_0000, 0xFFFF_FFFF_0000_0001]; +pub const E_B: [u64; 4] = + [0x3BCE_3C3E_27D2_604B, 0x651D_06B0_CC53_B0F6, 0xB3EB_BD55_7698_86BC, 0x5AC6_35D8_AA3A_93E7]; + +/// Secp256r1 base field size +pub const P: [u64; 4] = + [0xFFFF_FFFF_FFFF_FFFF, 0x0000_0000_FFFF_FFFF, 0x0000_0000_0000_0000, 0xFFFF_FFFF_0000_0001]; + +/// Secp256r1 scalar field size +pub const N: [u64; 4] = + [0xF3B9_CAC2_FC63_2551, 0xBCE6_FAAD_A717_9E84, 0xFFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_0000_0000]; +pub const N_MINUS_ONE: [u64; 4] = [N[0] - 1, N[1], N[2], N[3]]; + +/// Secp256r1 group identity point +pub const IDENTITY_X: [u64; 4] = [0; 4]; +pub const IDENTITY_Y: [u64; 4] = [0; 4]; + +/// Secp256r1 group of points generator +pub const G_X: [u64; 4] = + [0xF4A1_3945_D898_C296, 0x7703_7D81_2DEB_33A0, 0xF8BC_E6E5_63A4_40F2, 0x6B17_D1F2_E12C_4247]; +pub const G_Y: [u64; 4] = + [0xCBB6_4068_37BF_51F5, 0x2BCE_3357_6B31_5ECE, 0x8EE7_EB4A_7C0F_9E16, 0x4FE3_42E2_FE1A_7F9B]; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs new file mode 100644 index 000000000..8e7281c34 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs @@ -0,0 +1,350 @@ +use crate::{ + syscalls::{ + syscall_secp256r1_add, syscall_secp256r1_dbl, SyscallPoint256, SyscallSecp256r1AddParams, + }, + zisklib::{eq, fcall_msb_pos_256, fcall_msb_pos_256_3, is_one, ONE_256, TWO_256, ZERO_256}, +}; + +use super::{ + constants::{E_A, E_B, G_X, G_Y, IDENTITY_X, IDENTITY_Y}, + field::{secp256r1_fp_add, secp256r1_fp_mul, secp256r1_fp_square}, +}; + +const IDENTITY_POINT256: SyscallPoint256 = SyscallPoint256 { x: IDENTITY_X, y: IDENTITY_Y }; + +const G_POINT256: SyscallPoint256 = SyscallPoint256 { x: G_X, y: G_Y }; + +/// Given points `p1` and `p2`, performs the point addition `p1 + p2` and assigns the result to `p1`. +/// It assumes that `p1` and `p2` are from the Secp256r1 curve, that `p1,p2 != 𝒪` +/// Returns true if the result is the point at infinity. +#[inline] +fn secp256r1_add_non_infinity_points(p1: &mut SyscallPoint256, p2: &SyscallPoint256) -> bool { + if p1.x != p2.x { + let mut params = SyscallSecp256r1AddParams { p1, p2 }; + syscall_secp256r1_add(&mut params); + false + } else if p1.y == p2.y { + syscall_secp256r1_dbl(p1); + false + } else { + // p1 + (-p1) = 𝒪 + true + } +} + +/// Checks whether the given point `p` is on the Secp256r1 curve. +/// It assumes that `p` is not the point at infinity. +pub fn secp256r1_is_on_curve(p: &[u64; 8]) -> bool { + let x: [u64; 4] = p[0..4].try_into().unwrap(); + let y: [u64; 4] = p[4..8].try_into().unwrap(); + + // p in E iff y² == x³ + a·x + b + let lhs = secp256r1_fp_square(&y); + let mut rhs = secp256r1_fp_square(&x); + rhs = secp256r1_fp_mul(&rhs, &x); + rhs = secp256r1_fp_add(&rhs, &secp256r1_fp_mul(&x, &E_A)); + rhs = secp256r1_fp_add(&rhs, &E_B); + eq(&lhs, &rhs) +} + +/// Given two points `p` and `q` and scalars `r`, `s`, and `t`, computes the triple scalar multiplication `r·g + s·p + t·q` +/// It assumes that `r,s,t ∈ [1, N-1]` and that `p,q != 𝒪` +pub fn secp256r1_triple_scalar_mul_with_g( + r: &[u64; 4], + s: &[u64; 4], + t: &[u64; 4], + p: &[u64; 8], + q: &[u64; 8], +) -> Option<[u64; 8]> { + let p = SyscallPoint256 { x: [p[0], p[1], p[2], p[3]], y: [p[4], p[5], p[6], p[7]] }; + let q = SyscallPoint256 { x: [q[0], q[1], q[2], q[3]], y: [q[4], q[5], q[6], q[7]] }; + + // Precompute g + p, g + q, p + q, g + p + q + let mut gp = G_POINT256; + let gp_is_infinity = secp256r1_add_non_infinity_points(&mut gp, &p); + + let mut gq = G_POINT256; + let gq_is_infinity = secp256r1_add_non_infinity_points(&mut gq, &q); + + let mut pq = SyscallPoint256 { x: p.x, y: p.y }; + let pq_is_infinity = secp256r1_add_non_infinity_points(&mut pq, &q); + + let mut gpq = SyscallPoint256 { x: gp.x, y: gp.y }; + let gpq_is_infinity = secp256r1_add_non_infinity_points(&mut gpq, &q); + + if is_one(r) && is_one(s) && is_one(t) { + // Return g + p + q + if gpq_is_infinity { + return None; + } else { + return Some([ + gpq.x[0], gpq.x[1], gpq.x[2], gpq.x[3], gpq.y[0], gpq.y[1], gpq.y[2], gpq.y[3], + ]); + } + } + // From here on, at least one of r,s,t is greater than 1 + + // Hint the maximum length between the binary representations of r,s and t + let (max_limb, max_bit) = fcall_msb_pos_256_3(r, s, t); + + // Perform the loop, based on the binary representation of r,s and t + + // We do the first iteration separately + let max_limb = max_limb as usize; + let max_bit = max_bit as usize; + + // At least one of the scalars should have the first received bit as 1 + let r_bit = (r[max_limb] >> max_bit) & 1; + let s_bit = (s[max_limb] >> max_bit) & 1; + let t_bit = (t[max_limb] >> max_bit) & 1; + assert!(r_bit == 1 || s_bit == 1 || t_bit == 1); + + // Start at 𝒪 + let mut res = IDENTITY_POINT256; + let mut res_is_infinity = true; + let mut r_rec = ZERO_256; + let mut s_rec = ZERO_256; + let mut t_rec = ZERO_256; + + // Eight cases based on the bits of r,s and t + match (r_bit, s_bit, t_bit) { + (0, 0, 1) => { + // Set res = q + res.x = q.x; + res.y = q.y; + res_is_infinity = false; + + // Update t_rec + t_rec[max_limb] = 1 << max_bit; + } + (0, 1, 0) => { + // Set res = p + res.x = p.x; + res.y = p.y; + res_is_infinity = false; + + // Update s_rec + s_rec[max_limb] = 1 << max_bit; + } + (0, 1, 1) => { + // Set res = p + q if not infinity + if !pq_is_infinity { + res.x = pq.x; + res.y = pq.y; + res_is_infinity = false; + } + + // Update s_rec and t_rec + s_rec[max_limb] = 1 << max_bit; + t_rec[max_limb] = 1 << max_bit; + } + (1, 0, 0) => { + // Set res = g + res.x = G_POINT256.x; + res.y = G_POINT256.y; + res_is_infinity = false; + + // Update r_rec + r_rec[max_limb] = 1 << max_bit; + } + (1, 0, 1) => { + // Set res = g + q if not infinity + if !gq_is_infinity { + res.x = gq.x; + res.y = gq.y; + res_is_infinity = false; + } + + // Update r_rec and t_rec + r_rec[max_limb] = 1 << max_bit; + t_rec[max_limb] = 1 << max_bit; + } + (1, 1, 0) => { + // Set res = g + p if not infinity + if !gp_is_infinity { + res.x = gp.x; + res.y = gp.y; + res_is_infinity = false; + } + + // Update r_rec and s_rec + r_rec[max_limb] = 1 << max_bit; + s_rec[max_limb] = 1 << max_bit; + } + (1, 1, 1) => { + // Set res = g + p + q if not infinity + if !gpq_is_infinity { + res.x = gpq.x; + res.y = gpq.y; + res_is_infinity = false; + } + + // Update r_rec, s_rec and t_rec + r_rec[max_limb] = 1 << max_bit; + s_rec[max_limb] = 1 << max_bit; + t_rec[max_limb] = 1 << max_bit; + } + _ => unreachable!(), + } + + // Determine starting limb/bit for the loop + let mut limb = max_limb; + let mut bit = if max_bit == 0 { + // If max_bit is 0 then limb > 0; otherwise r,s,t = 1, which is excluded here + limb -= 1; + 63 + } else { + max_bit - 1 + }; + + // Perform the rest of the loop + for i in (0..=limb).rev() { + for j in (0..=bit).rev() { + let r_bit = (r[i] >> j) & 1; + let s_bit = (s[i] >> j) & 1; + let t_bit = (t[i] >> j) & 1; + + // Eight cases based on the bits of r,s and t + match (r_bit, s_bit, t_bit) { + (0, 0, 0) => { + // If res is 𝒪, do nothing; otherwise, double + if !res_is_infinity { + syscall_secp256r1_dbl(&mut res); + } + } + (0, 0, 1) => { + // If res is 𝒪, set res = q; otherwise, double res and add q + if res_is_infinity { + res.x = q.x; + res.y = q.y; + res_is_infinity = false; + } else { + syscall_secp256r1_dbl(&mut res); + res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &q); + } + + // Update t_rec + t_rec[i] |= 1 << j; + } + (0, 1, 0) => { + // If res is 𝒪, set res = p; otherwise, double res and add p + if res_is_infinity { + res.x = p.x; + res.y = p.y; + res_is_infinity = false; + } else { + syscall_secp256r1_dbl(&mut res); + res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &p); + } + + // Update s_rec + s_rec[i] |= 1 << j; + } + (0, 1, 1) => { + // If res is 𝒪, set res = p + q if not infinity; otherwise, double res and add (p + q) + if res_is_infinity { + if !pq_is_infinity { + res.x = pq.x; + res.y = pq.y; + res_is_infinity = false; + } + } else { + syscall_secp256r1_dbl(&mut res); + if !pq_is_infinity { + res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &pq); + } + } + + // Update s_rec and t_rec + s_rec[i] |= 1 << j; + t_rec[i] |= 1 << j; + } + (1, 0, 0) => { + // If res is 𝒪, set res = g; otherwise, double res and add g + if res_is_infinity { + res.x = G_POINT256.x; + res.y = G_POINT256.y; + res_is_infinity = false; + } else { + syscall_secp256r1_dbl(&mut res); + res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &G_POINT256); + } + + // Update r_rec + r_rec[i] |= 1 << j; + } + (1, 0, 1) => { + // If res is 𝒪, set res = g + q if not infinity; otherwise, double res and add (g + q) + if res_is_infinity { + if !gq_is_infinity { + res.x = gq.x; + res.y = gq.y; + res_is_infinity = false; + } + } else { + syscall_secp256r1_dbl(&mut res); + if !gq_is_infinity { + res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &gq); + } + } + + // Update r_rec and t_rec + r_rec[i] |= 1 << j; + t_rec[i] |= 1 << j; + } + (1, 1, 0) => { + // If res is 𝒪, set res = g + p if not infinity + if res_is_infinity { + if !gp_is_infinity { + res.x = gp.x; + res.y = gp.y; + res_is_infinity = false; + } + } else { + syscall_secp256r1_dbl(&mut res); + if !gp_is_infinity { + res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &gp); + } + } + + // Update r_rec and s_rec + r_rec[i] |= 1 << j; + s_rec[i] |= 1 << j; + } + (1, 1, 1) => { + // If res is 𝒪, set res = g + p + q if not infinity; otherwise, double res and add (g + p + q) + if res_is_infinity { + if !gpq_is_infinity { + res.x = gpq.x; + res.y = gpq.y; + res_is_infinity = false; + } + } else { + syscall_secp256r1_dbl(&mut res); + if !gpq_is_infinity { + res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &gpq); + } + } + + // Update r_rec, s_rec and t_rec + r_rec[i] |= 1 << j; + s_rec[i] |= 1 << j; + t_rec[i] |= 1 << j; + } + _ => unreachable!(), + } + } + bit = 63; + } + + // Check that the recomposed scalars are the same as the received scalars + assert!(eq(&r_rec, r)); + assert!(eq(&s_rec, s)); + assert!(eq(&t_rec, t)); + + if res_is_infinity { + None + } else { + Some([res.x[0], res.x[1], res.x[2], res.x[3], res.y[0], res.y[1], res.y[2], res.y[3]]) + } +} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs new file mode 100644 index 000000000..59aa5d84b --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs @@ -0,0 +1,57 @@ +use crate::{ + syscalls::{ + syscall_secp256r1_add, syscall_secp256r1_dbl, SyscallPoint256, SyscallSecp256r1AddParams, + }, + zisklib::{ + eq, fcall_msb_pos_256, fcall_secp256r1_ecdsa_verify, is_one, ONE_256, TWO_256, ZERO_256, + }, +}; + +use super::{ + constants::{E_B, G_X, G_Y, IDENTITY_X, IDENTITY_Y}, + curve::{secp256r1_is_on_curve, secp256r1_triple_scalar_mul_with_g}, + field::{secp256r1_fp_add, secp256r1_fp_mul, secp256r1_fp_square}, + scalar::secp256r1_fn_neg, +}; + +/// Verifies the signature (r, s) over the message hash z using the public key pk +/// Returns true if the signature is valid, false otherwise +pub fn secp256r1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u64; 4]) -> bool { + // Ecdsa verification computes (x, y) = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]PK + // and checks that x ≡ r (mod n) + // We can equivalently hint y, and verify that + // [z]G + [r]PK + [-s](r,y) == 𝒪, + // saving us from expensive fn arithmetic + + // Hint the result + let coords = fcall_secp256r1_ecdsa_verify(pk, z, r, s); + let point = [r[0], r[1], r[2], r[3], coords[4], coords[5], coords[6], coords[7]]; + + // Check the recovered point is valid + assert!(secp256r1_is_on_curve(&point)); // Note: Identity point would be raised here + + // Check that [z]G + [r]PK + [-s](r,y) == 𝒪 + let neg_s = secp256r1_fn_neg(s); + secp256r1_triple_scalar_mul_with_g(z, r, &neg_s, pk, &point).is_none() +} + +// ==================== C FFI Functions ==================== + +/// # Safety +/// - `pk_ptr` must point to 8 u64s +/// - `z_ptr`, `r_ptr`, `s_ptr` must point to 4 u64s each +/// +/// Returns true if signature is valid +#[no_mangle] +pub unsafe extern "C" fn secp256r1_ecdsa_verify_c( + pk_ptr: *const u64, + z_ptr: *const u64, + r_ptr: *const u64, + s_ptr: *const u64, +) -> bool { + let pk: &[u64; 8] = &*(pk_ptr as *const [u64; 8]); + let z: &[u64; 4] = &*(z_ptr as *const [u64; 4]); + let r: &[u64; 4] = &*(r_ptr as *const [u64; 4]); + let s: &[u64; 4] = &*(s_ptr as *const [u64; 4]); + secp256r1_ecdsa_verify(pk, z, r, s) +} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/field.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/field.rs new file mode 100644 index 000000000..2a2a6b9e2 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/field.rs @@ -0,0 +1,29 @@ +use crate::syscalls::{syscall_arith256_mod, SyscallArith256ModParams}; + +use super::constants::P; + +pub fn secp256r1_fp_add(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { + // x·1 + y + let mut params = + SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &P, d: &mut [0, 0, 0, 0] }; + syscall_arith256_mod(&mut params); + *params.d +} + +pub fn secp256r1_fp_mul(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { + // x·y + 0 + let mut params = + SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; + syscall_arith256_mod(&mut params); + + *params.d +} + +pub fn secp256r1_fp_square(x: &[u64; 4]) -> [u64; 4] { + // x·x + 0 + let mut params = + SyscallArith256ModParams { a: x, b: x, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; + syscall_arith256_mod(&mut params); + + *params.d +} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/mod.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/mod.rs new file mode 100644 index 000000000..794d231fd --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/mod.rs @@ -0,0 +1,10 @@ +mod constants; +mod curve; +mod ecdsa; +mod field; +mod scalar; + +pub use curve::*; +pub use ecdsa::*; +pub use field::*; +pub use scalar::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs new file mode 100644 index 000000000..0da1add5a --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs @@ -0,0 +1,17 @@ +use crate::syscalls::{syscall_arith256_mod, SyscallArith256ModParams}; + +use super::constants::{N, N_MINUS_ONE}; + +pub fn secp256r1_fn_neg(x: &[u64; 4]) -> [u64; 4] { + // x·(-1) + 0 + let mut params = SyscallArith256ModParams { + a: x, + b: &N_MINUS_ONE, + c: &[0, 0, 0, 0], + module: &N, + d: &mut [0, 0, 0, 0], + }; + syscall_arith256_mod(&mut params); + + *params.d +} From 9e049a90e2f26e7cd4ea9af2e76f7ce0f339f746 Mon Sep 17 00:00:00 2001 From: fractasy Date: Sat, 31 Jan 2026 18:55:51 +0100 Subject: [PATCH 377/782] Fix jalr address mask issue --- core/src/riscv2zisk_context.rs | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 2a22683a4..407341eb9 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -905,9 +905,38 @@ impl Riscv2ZiskContext<'_> { pub fn jalr(&mut self, i: &RiscvInstruction, inst_size: u64) { assert!(inst_size == 4 || inst_size == 2); let mut rom_address = i.rom_address; + + // Thanks to https://github.com/codygunton for reporting the issue with JALR alignment! + + // JALR target address mask per RISC-V ISA spec Section 2.5. + // Must clear only bit 0 (0xfffffffffffffffe) for 2-byte alignment. + // + // BUG: Using 0xfffffffffffffffc (4-byte alignment) breaks zksync-os at _start. + // The startup code (zksync-airbender/riscv_common/src/asm/start64.s) is: + // _start: + // la ra, _abs_start # auipc + addi (8 bytes) + // jr ra # c.jr ra (2 bytes, compressed) + // _abs_start: # offset 10 = 0x8000000a + // + // The assembler uses compressed `c.jr` (2 bytes), placing _abs_start at + // 0x8000000a - valid for C extension but not 4-byte aligned. We could change the start + // file but we leave as-is to document the issue. + // + // With mask 0xfc: 0x8000000a & 0xfc = 0x80000008 (jumps back to `jr ra`!) + // With mask 0xfe: 0x8000000a & 0xfe = 0x8000000a (correct target) + // + // The wrong mask causes an infinite self-loop at the first instruction, + // terminating after 16k steps instead of 1.6B. + // + // Note that this change fixes the misalign2-jalr-01.S test, which is part of the privilege + // architecture test suite but which seeems to test requirements of other parts of the + // spec. + + const JALR_MASK: u64 = 0xfffffffffffffffe; + if (i.imm % 4) == 0 { let mut zib = ZiskInstBuilder::new_from_riscv(rom_address, i.inst.clone()); - zib.src_a("imm", 0xfffffffffffffffc, false); + zib.src_a("imm", JALR_MASK, false); zib.src_b("reg", i.rs1 as u64, false); zib.op("and").unwrap(); zib.set_pc(); @@ -930,7 +959,7 @@ impl Riscv2ZiskContext<'_> { } { let mut zib = ZiskInstBuilder::new(rom_address); - zib.src_a("imm", 0xfffffffffffffffc, false); + zib.src_a("imm", JALR_MASK, false); zib.src_b("lastc", 0, false); zib.op("and").unwrap(); zib.set_pc(); From ddf0a790e8f42289b4cae49e920a1ccfdf3e41b0 Mon Sep 17 00:00:00 2001 From: fractasy Date: Sun, 1 Feb 2026 21:58:53 +0100 Subject: [PATCH 378/782] Add SECP256R1 support to lib-c and assembly --- emulator-asm/src/emu.c | 154 ++++++++++ lib-c/c/Makefile | 10 + lib-c/c/src/common/globals.cpp | 2 + lib-c/c/src/common/globals.hpp | 4 + lib-c/c/src/common/utils.hpp | 32 ++ lib-c/c/src/fcall/fcall.cpp | 15 + lib-c/c/src/fcall/fcall.hpp | 4 + lib-c/c/src/secp256r1/secp256r1.cpp | 444 ++++++++++++++++++++++++++++ lib-c/c/src/secp256r1/secp256r1.hpp | 58 ++++ 9 files changed, 723 insertions(+) create mode 100644 lib-c/c/src/secp256r1/secp256r1.cpp create mode 100644 lib-c/c/src/secp256r1/secp256r1.hpp diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index a5f20ebc0..c7d92082f 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -10,6 +10,7 @@ #include "emu.hpp" #include "../../lib-c/c/src/bigint/add256.hpp" #include "../../lib-c/c/src/ec/ec.hpp" +#include "../../lib-c/c/src/secp256r1/secp256r1.hpp" #include "../../lib-c/c/src/fcall/fcall.hpp" #include "../../lib-c/c/src/arith256/arith256.hpp" #include "../../lib-c/c/src/arith384/arith384.hpp" @@ -48,6 +49,10 @@ void reset_asm_call_metrics (void) asm_call_metrics.secp256k1_add_duration = 0; asm_call_metrics.secp256k1_dbl_counter = 0; asm_call_metrics.secp256k1_dbl_duration = 0; + asm_call_metrics.secp256r1_add_counter = 0; + asm_call_metrics.secp256r1_add_duration = 0; + asm_call_metrics.secp256r1_dbl_counter = 0; + asm_call_metrics.secp256r1_dbl_duration = 0; asm_call_metrics.fcall_counter = 0; asm_call_metrics.fcall_duration = 0; asm_call_metrics.inverse_fp_ec_counter = 0; @@ -156,6 +161,26 @@ void print_asm_call_metrics (uint64_t total_duration) duration, percentage); + // Print secp256r1_add metrics + percentage = total_duration == 0 ? 0 : (asm_call_metrics.secp256r1_add_duration * 1000) / total_duration; + duration = asm_call_metrics.secp256r1_add_counter == 0 ? 0 : (asm_call_metrics.secp256r1_add_duration * 1000) / asm_call_metrics.secp256r1_add_counter; + asm_call_total_duration += asm_call_metrics.secp256r1_add_duration; + printf("secp256r1_add: counter = %lu, duration = %lu us, single duration = %lu ns, per thousand = %lu \n", + asm_call_metrics.secp256r1_add_counter, + asm_call_metrics.secp256r1_add_duration, + duration, + percentage); + + // Print secp256r1_dbl metrics + percentage = total_duration == 0 ? 0 : (asm_call_metrics.secp256r1_dbl_duration * 1000) / total_duration; + duration = asm_call_metrics.secp256r1_dbl_counter == 0 ? 0 : (asm_call_metrics.secp256r1_dbl_duration * 1000) / asm_call_metrics.secp256r1_dbl_counter; + asm_call_total_duration += asm_call_metrics.secp256r1_dbl_duration; + printf("secp256r1_dbl: counter = %lu, duration = %lu us, single duration = %lu ns, per thousand = %lu \n", + asm_call_metrics.secp256r1_dbl_counter, + asm_call_metrics.secp256r1_dbl_duration, + duration, + percentage); + // Print fcall metrics percentage = total_duration == 0 ? 0 : (asm_call_metrics.fcall_duration * 1000) / total_duration; duration = asm_call_metrics.fcall_counter == 0 ? 0 : (asm_call_metrics.fcall_duration * 1000) / asm_call_metrics.fcall_counter; @@ -903,6 +928,135 @@ extern int _opcode_secp256k1_dbl(uint64_t * address) return 0; } +extern int _opcode_secp256r1_add(uint64_t * address) +{ +#ifdef ASM_CALL_METRICS + gettimeofday(&asm_call_start, NULL); +#endif + + uint64_t * p1 = (uint64_t *)address[0]; + uint64_t * p2 = (uint64_t *)address[1]; +#ifdef DEBUG + if (emu_verbose) + { +#ifdef ASM_CALL_METRICS + printf("opcode_secp256r1_add() calling AddPointEcP() counter=%lu address=%p p1_address=%p p2_address=%p\n", asm_call_metrics.secp256r1_add_counter, address, p1, p2); +#else + printf("opcode_secp256r1_add() calling AddPointEcP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); +#endif + printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + printf("p2.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[3], p2[2], p2[1], p2[0], p2[3], p2[2], p2[1], p2[0]); + printf("p2.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[7], p2[6], p2[5], p2[4], p2[7], p2[6], p2[5], p2[4]); + } +#endif + +#ifdef ASM_PRECOMPILE_CACHE + if (precompile_cache_storing) + { +#endif + // Call point addition function + int result = secp256r1_add_point_ecp ( + 0, + p1, // p1 = [x1, y1] = 8x64bits + p2, // p2 = [x2, y2] = 8x64bits + p1 // p3 = [x3, y3] = 8x64bits + ); + if (result != 0) + { + printf("_opcode_secp256r1_add() failed callilng AddPointEcP() result=%d;", result); + exit(-1); + } + +#ifdef ASM_PRECOMPILE_CACHE + // Store result in cache + precompile_cache_store((uint8_t *)p1, 8*8); + } + else if (precompile_cache_loading) + { + // Load result from cache + precompile_cache_load((uint8_t *)p1, 8*8); + } +#endif + +#ifdef DEBUG + if (emu_verbose) + { + printf("p3 = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); + } +#endif +#ifdef ASM_CALL_METRICS + asm_call_metrics.secp256r1_add_counter++; + gettimeofday(&asm_call_stop, NULL); + asm_call_metrics.secp256r1_add_duration += TimeDiff(asm_call_start, asm_call_stop); +#endif + return 0; +} + +extern int _opcode_secp256r1_dbl(uint64_t * address) +{ +#ifdef ASM_CALL_METRICS + gettimeofday(&asm_call_start, NULL); +#endif + + uint64_t * p1 = address; + +#ifdef DEBUG + if (emu_verbose) + { +#ifdef ASM_CALL_METRICS + printf("opcode_secp256r1_dbl() calling AddPointEcP() counter=%lu address=%p\n", asm_call_metrics.secp256r1_dbl_counter, address); +#else + printf("opcode_secp256r1_dbl() calling AddPointEcP() address=%p\n", address); +#endif + printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + } +#endif + +#ifdef ASM_PRECOMPILE_CACHE + if (precompile_cache_storing) + { +#endif + int result = secp256r1_add_point_ecp ( + 1, + p1, // p1 = [x1, y1] = 8x64bits + NULL, // p2 = [x2, y2] = 8x64bits + p1 // p3 = [x3, y3] = 8x64bits + ); + if (result != 0) + { + printf("_opcode_secp256r1_dbl() failed callilng secp256r1_add_point_ecp() result=%d;", result); + exit(-1); + } + +#ifdef ASM_PRECOMPILE_CACHE + // Store result in cache + precompile_cache_store((uint8_t *)p1, 8*8); + } + else if (precompile_cache_loading) + { + // Load result from cache + precompile_cache_load((uint8_t *)p1, 8*8); + } +#endif + +#ifdef DEBUG + if (emu_verbose) printf("opcode_secp256r1_dbl() called secp256r1_add_point_ecp()\n"); + if (emu_verbose) + { + printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + } +#endif +#ifdef ASM_CALL_METRICS + asm_call_metrics.secp256r1_dbl_counter++; + gettimeofday(&asm_call_stop, NULL); + asm_call_metrics.secp256r1_dbl_duration += TimeDiff(asm_call_start, asm_call_stop); +#endif + return 0; +} + extern uint64_t MEM_TRACE_ADDRESS; extern uint64_t fcall_ctx; uint64_t print_fcall_ctx_counter = 0; diff --git a/lib-c/c/Makefile b/lib-c/c/Makefile index ab3686ff6..20313b3b5 100644 --- a/lib-c/c/Makefile +++ b/lib-c/c/Makefile @@ -12,11 +12,16 @@ all: nasm -felf64 src/ffiasm/fnec.asm -o build/fnec.o nasm -felf64 src/ffiasm/fq.asm -o build/fq.o nasm -felf64 src/ffiasm/bls12_381_384.asm -o build/bls12_381_384.o + nasm -felf64 src/ffiasm/psecp256r1.asm -o build/psecp256r1.o + nasm -felf64 src/ffiasm/nsecp256r1.asm -o build/nsecp256r1.o gcc $(CFLAGS) -c src/ffiasm/fec.cpp -o build/fecc.o gcc $(CFLAGS) -c src/ffiasm/fnec.cpp -o build/fnecc.o gcc $(CFLAGS) -c src/ffiasm/fq.cpp -o build/fqc.o gcc $(CFLAGS) -c src/ffiasm/bls12_381_384.cpp -o build/bls12_381_384c.o + gcc $(CFLAGS) -c src/ffiasm/psecp256r1.cpp -o build/psecp256r1c.o + gcc $(CFLAGS) -c src/ffiasm/nsecp256r1.cpp -o build/nsecp256r1c.o gcc $(CFLAGS) -c src/ec/ec.cpp -o build/ec.o + gcc $(CFLAGS) -c src/secp256r1/secp256r1.cpp -o build/secp256r1.o gcc $(CFLAGS) -c src/bn254/bn254.cpp -o build/bn254.o gcc $(CFLAGS) -c src/bls12_381/bls12_381.cpp -o build/bls12_381.o gcc $(CFLAGS) -c src/fcall/fcall.cpp -o build/fcall.o @@ -32,13 +37,18 @@ all: build/fnec.o\ build/fq.o\ build/bls12_381_384.o\ + build/psecp256r1.o\ + build/nsecp256r1.o\ build/ec.o\ + build/secp256r1.o\ build/bn254.o\ build/bls12_381.o\ build/fecc.o\ build/fnecc.o\ build/fqc.o\ build/bls12_381_384c.o\ + build/psecp256r1c.o\ + build/nsecp256r1c.o\ build/fcall.o\ build/arith256.o\ build/arith384.o\ diff --git a/lib-c/c/src/common/globals.cpp b/lib-c/c/src/common/globals.cpp index d242ee558..a4cb36e6f 100644 --- a/lib-c/c/src/common/globals.cpp +++ b/lib-c/c/src/common/globals.cpp @@ -4,6 +4,8 @@ RawFec fec; RawFnec fnec; RawFq bn254; RawBLS12_381_384 bls12_381; +RawpSecp256r1 secp256r1; +RawnSecp256r1 secp256r1n; mpz_class ScalarMask256 ("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16); mpz_class ScalarMask384 ("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16); diff --git a/lib-c/c/src/common/globals.hpp b/lib-c/c/src/common/globals.hpp index e9939409a..b939d6ddf 100644 --- a/lib-c/c/src/common/globals.hpp +++ b/lib-c/c/src/common/globals.hpp @@ -6,11 +6,15 @@ #include "../ffiasm/fnec.hpp" #include "../ffiasm/fq.hpp" #include "../ffiasm/bls12_381_384.hpp" +#include "../ffiasm/psecp256r1.hpp" +#include "../ffiasm/nsecp256r1.hpp" extern RawFec fec; extern RawFnec fnec; extern RawFq bn254; extern RawBLS12_381_384 bls12_381; +extern RawpSecp256r1 secp256r1; +extern RawnSecp256r1 secp256r1n; extern mpz_class ScalarMask256; extern mpz_class ScalarMask384; diff --git a/lib-c/c/src/common/utils.hpp b/lib-c/c/src/common/utils.hpp index cf52af1fd..baa018e28 100644 --- a/lib-c/c/src/common/utils.hpp +++ b/lib-c/c/src/common/utils.hpp @@ -107,4 +107,36 @@ inline void fe2array (const RawBLS12_381_384::Element &fe, uint64_t * a) scalar2array6(s, a); } +// Converts an array of 4 u64 LE to a Fq (Secp256r1) element +inline void array2fe (const uint64_t * a, RawpSecp256r1::Element &fe) +{ + mpz_class s; + array2scalar(a, s); + secp256r1.fromMpz(fe, s.get_mpz_t()); +} + +// Converts a Fq (Secp256r1) element to an array of 4 u64 LE +inline void fe2array (const RawpSecp256r1::Element &fe, uint64_t * a) +{ + mpz_class s; + secp256r1.toMpz(s.get_mpz_t(), fe); + scalar2array(s, a); +} + +// Converts an array of 4 u64 LE to a Fq (nSecp256r1) element +inline void array2fe (const uint64_t * a, RawnSecp256r1::Element &fe) +{ + mpz_class s; + array2scalar(a, s); + secp256r1n.fromMpz(fe, s.get_mpz_t()); +} + +// Converts a Fq (nSecp256r1) element to an array of 4 u64 LE +inline void fe2array (const RawnSecp256r1::Element &fe, uint64_t * a) +{ + mpz_class s; + secp256r1n.toMpz(s.get_mpz_t(), fe); + scalar2array(s, a); +} + #endif \ No newline at end of file diff --git a/lib-c/c/src/fcall/fcall.cpp b/lib-c/c/src/fcall/fcall.cpp index 7410334e3..55f6dd354 100644 --- a/lib-c/c/src/fcall/fcall.cpp +++ b/lib-c/c/src/fcall/fcall.cpp @@ -4,6 +4,7 @@ #include "../bls12_381/bls12_381_fe.hpp" #include "../bls12_381/bls12_381.hpp" #include "../ec/ec.hpp" +#include "../secp256r1/secp256r1.hpp" #include #include @@ -110,6 +111,11 @@ int Fcall ( iresult = Secp256k1EcdsaVerifyCtx(ctx); break; } + case FCALL_SECP256R1_ECDSA_VERIFY_ID: + { + iresult = Secp256r1EcdsaVerifyCtx(ctx); + break; + } default: { printf("Fcall() found unsupported function_id=%lu\n", ctx->function_id); @@ -1048,4 +1054,13 @@ int Secp256k1EcdsaVerifyCtx( secp256k1_ecdsa_verify( &ctx->params[0], &ctx->params[8], &ctx->params[12], &ctx->params[16], &ctx->result[0]); ctx->result_size = 8; return 0; +} + +int Secp256r1EcdsaVerifyCtx( + struct FcallContext * ctx // fcall context +) +{ + secp256r1_ecdsa_verify( &ctx->params[0], &ctx->params[8], &ctx->params[12], &ctx->params[16], &ctx->result[0]); + ctx->result_size = 8; + return 0; } \ No newline at end of file diff --git a/lib-c/c/src/fcall/fcall.hpp b/lib-c/c/src/fcall/fcall.hpp index 5464bde09..b2509745d 100644 --- a/lib-c/c/src/fcall/fcall.hpp +++ b/lib-c/c/src/fcall/fcall.hpp @@ -27,6 +27,7 @@ extern "C" { #define FCALL_BIN_DECOMP_ID 18 #define FCALL_BLS12_381_FP2_SQRT_ID 19 #define FCALL_SECP256K1_ECDSA_VERIFY_ID 20 +#define FCALL_SECP256R1_ECDSA_VERIFY_ID 21 #define FCALL_PARAMS_MAX_SIZE 386 #define FCALL_RESULT_MAX_SIZE 8193 @@ -106,6 +107,9 @@ int BLS12_381Fp2SqrtCtx ( int Secp256k1EcdsaVerifyCtx ( struct FcallContext * ctx // fcall context ); +int Secp256r1EcdsaVerifyCtx ( + struct FcallContext * ctx // fcall context +); // Functions supported by fcall, in u64 array format int InverseFpEc ( diff --git a/lib-c/c/src/secp256r1/secp256r1.cpp b/lib-c/c/src/secp256r1/secp256r1.cpp new file mode 100644 index 000000000..adeb28a73 --- /dev/null +++ b/lib-c/c/src/secp256r1/secp256r1.cpp @@ -0,0 +1,444 @@ + +#include +#include "secp256r1.hpp" +#include "../ffiasm/psecp256r1.hpp" +#include "../ffiasm/nsecp256r1.hpp" +#include "../common/utils.hpp" +#include "../common/globals.hpp" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int inline secp256r1_add_point_ec_fe (bool dbl, const RawpSecp256r1::Element &x1, const RawpSecp256r1::Element &y1, const RawpSecp256r1::Element &x2, const RawpSecp256r1::Element &y2, RawpSecp256r1::Element &x3, RawpSecp256r1::Element &y3) +{ + // Check if results are buffered +#ifdef ENABLE_EXPERIMENTAL_CODE + if(ctx.ecRecoverPrecalcBuffer.filled == true){ + if(ctx.ecRecoverPrecalcBuffer.pos < 2){ + zklog.error("ecRecoverPrecalcBuffer.buffer buffer is not filled, but pos < 2 (pos=" + to_string(ctx.ecRecoverPrecalcBuffer.pos) + ")"); + exitProcess(); + } + x3 = ctx.ecRecoverPrecalcBuffer.buffer[ctx.ecRecoverPrecalcBuffer.pos-2]; + y3 = ctx.ecRecoverPrecalcBuffer.buffer[ctx.ecRecoverPrecalcBuffer.pos-1]; + return ZKR_SUCCESS; + } +#endif + + RawpSecp256r1::Element aux1, aux2, s; + + if (dbl) + { + // s = 3*x1*x1/2*y1 + secp256r1.mul(aux1, x1, x1); + secp256r1.fromUI(aux2, 3); + secp256r1.mul(aux1, aux1, aux2); + secp256r1.add(aux2, y1, y1); + if (secp256r1.isZero(aux2)) + { + printf("secp256r1_add_point_ec_fe() got denominator=0 1\n"); + return -1; + } + secp256r1.div(s, aux1, aux2); + + // Required for x3 calculation + secp256r1.add(aux2, x1, x1); + } + else + { + // s = (y2-y1)/(x2-x1) + secp256r1.sub(aux1, y2, y1); + secp256r1.sub(aux2, x2, x1); + if (secp256r1.isZero(aux2)) + { + printf("secp256r1_add_point_ec_fe() got denominator=0 2\n"); + return -1; + } + secp256r1.div(s, aux1, aux2); + + // Required for x3 calculation + secp256r1.add(aux2, x1, x2); + } + + // x3 = s*s - (x1+x2) + secp256r1.mul(aux1, s, s); + // aux2 was calculated before + secp256r1.sub(x3, aux1, aux2); + + // y3 = s*(x1-x3) - y1 + secp256r1.sub(aux1, x1, x3);; + secp256r1.mul(aux1, aux1, s); + secp256r1.sub(y3, aux1, y1); + + return 0; +} + +int inline secp256r1_add_point_ec_dbl_fe (RawpSecp256r1::Element &x1, RawpSecp256r1::Element &y1) +{ + // Check if results are buffered +#ifdef ENABLE_EXPERIMENTAL_CODE + if(ctx.ecRecoverPrecalcBuffer.filled == true){ + if(ctx.ecRecoverPrecalcBuffer.pos < 2){ + zklog.error("ecRecoverPrecalcBuffer.buffer buffer is not filled, but pos < 2 (pos=" + to_string(ctx.ecRecoverPrecalcBuffer.pos) + ")"); + exitProcess(); + } + x3 = ctx.ecRecoverPrecalcBuffer.buffer[ctx.ecRecoverPrecalcBuffer.pos-2]; + y3 = ctx.ecRecoverPrecalcBuffer.buffer[ctx.ecRecoverPrecalcBuffer.pos-1]; + return ZKR_SUCCESS; + } +#endif + + RawpSecp256r1::Element aux1, aux2, aux3, s; + + // s = 3*x1*x1/2*y1 + secp256r1.mul(aux1, x1, x1); + secp256r1.fromUI(aux2, 3); + secp256r1.mul(aux1, aux1, aux2); + secp256r1.add(aux2, y1, y1); + if (secp256r1.isZero(aux2)) + { + printf("secp256r1_add_point_ec_dbl_fe() got denominator=0 1\n"); + return -1; + } + secp256r1.div(s, aux1, aux2); + + // Required for x3 calculation + secp256r1.add(aux2, x1, x1); + + // x3 = s*s - (x1+x2) + secp256r1.mul(aux1, s, s); + // aux2 was calculated before + + secp256r1.sub(aux3, aux1, aux2); + + // y3 = s*(x1-x3) - y1 + secp256r1.sub(aux1, x1, aux3); + x1 = aux3; + secp256r1.mul(aux1, aux1, s); + secp256r1.sub(y1, aux1, y1); + + return 0; +} + +int secp256r1_add_point_ec (uint64_t _dbl, const uint64_t * _x1, const uint64_t * _y1, const uint64_t * _x2, const uint64_t * _y2, uint64_t * _x3, uint64_t * _y3) +{ + bool dbl = _dbl; + + RawpSecp256r1::Element x1, y1, x2, y2, x3, y3; + array2fe(_x1, x1); + array2fe(_y1, y1); + if (!dbl) + { + array2fe(_x2, x2); + array2fe(_y2, y2); + } + + int result = secp256r1_add_point_ec_fe (dbl, x1, y1, x2, y2, x3, y3); + + fe2array(x3, _x3); + fe2array(y3, _y3); + + return result; +} + +int secp256r1_add_point_ec_dbl (uint64_t * _x1, uint64_t * _y1) +{ + RawpSecp256r1::Element x1, y1; + array2fe(_x1, x1); + array2fe(_y1, y1); + + int result = secp256r1_add_point_ec_dbl_fe (x1, y1); + + fe2array(x1, _x1); + fe2array(y1, _y1); + + return result; +} + +int secp256r1_add_point_ecp (uint64_t _dbl, const uint64_t * p1, const uint64_t * p2, uint64_t * p3) +{ + bool dbl = _dbl; + + RawpSecp256r1::Element x1, y1, x2, y2, x3, y3; + array2fe(p1, x1); + array2fe(p1 + 4, y1); + if (!dbl) + { + array2fe(p2, x2); + array2fe(p2 + 4, y2); + } + + // printf("secp256r1_add_point_ecp() x1=%s\n", secp256r1.toString(x1, 16).c_str()); + // printf("secp256r1_add_point_ecp() y1=%s\n", secp256r1.toString(y1, 16).c_str()); + // printf("secp256r1_add_point_ecp() x2=%s\n", secp256r1.toString(x2, 16).c_str()); + // printf("secp256r1_add_point_ecp() y2=%s\n", secp256r1.toString(y2, 16).c_str()); + + int result = secp256r1_add_point_ec_fe (dbl, x1, y1, x2, y2, x3, y3); + + fe2array(x3, p3); + fe2array(y3, p3 + 4); + + return result; +} + +uint64_t SECP256R1_G[8] = { + 0xF4A13945D898C296, + 0x77037D812DEB33A0, + 0xF8BCE6E563A440F2, + 0x6B17D1F2E12C4247, + 0xCBB6406837BF51F5, + 0x2BCE33576B315ECE, + 0x8EE7EB4A7C0F9E16, + 0x4FE342E2FE1A7F9B +}; + +int secp256r1_ecdsa_verify ( + const uint64_t * pk, // 8 x 64 bits + const uint64_t * _z, // 4 x 64 bits + const uint64_t * _r, // 4 x 64 bits + const uint64_t * _s, // 4 x 64 bits + uint64_t * result // 8 x 64 bits +) +{ + // Convert z, r, s inputs to field elements + RawnSecp256r1::Element z, r, s; + array2fe(_z, z); + array2fe(_r, r); + array2fe(_s, s); + + // Given the public key pk and the signature (r, s) over the message hash z: + // 1. Computes s_inv = s⁻¹ mod n + // 2. Computes u1 = z·s_inv mod n + // 3. Computes u2 = r·s_inv mod n + // 4. Computes and returns the curve point p = u1·G + u2·PK + + // s_inv = s⁻¹ mod n + RawnSecp256r1::Element s_inv; + secp256r1n.inv(s_inv, s); + + // u1 = z·s_inv mod n + RawnSecp256r1::Element u1; + secp256r1n.mul(u1, z, s_inv); + + // u2 = r·s_inv mod n + RawnSecp256r1::Element u2; + secp256r1n.mul(u2, r, s_inv); + uint64_t u1_array[4]; + uint64_t u2_array[4]; + fe2array(u1, u1_array); + fe2array(u2, u2_array); + + secp256r1_curve_dbl_scalar_mul(u1_array, SECP256R1_G, u2_array, pk, result); + + return 0; +} + +const uint64_t SECP256R1_IDENTITY[8] = {0,0,0,0,0,0,0,0}; + +void secp256r1_curve_add( + const uint64_t * p, // 8 x 64 bits + const uint64_t * q, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +) +{ + // Get the 2 points coordinates + const uint64_t * x1 = &p[0]; + const uint64_t * y1 = &p[4]; + const uint64_t * x2 = &q[0]; + const uint64_t * y2 = &q[4]; + + // If p==q return dbl(p) + if (x1[0] == x2[0] && + x1[1] == x2[1] && + x1[2] == x2[2] && + x1[3] == x2[3]) + { + if (y1[0] == y2[0] && + y1[1] == y2[1] && + y1[2] == y2[2] && + y1[3] == y2[3]) { + secp256r1_curve_dbl(p, r); + return; + } else { + for (int i = 0; i < 8; i++) { + r[i] = SECP256R1_IDENTITY[i]; + } + return; + } + } + + // If p==0 return q + if ( p[0] == SECP256R1_IDENTITY[0] && + p[1] == SECP256R1_IDENTITY[1] && + p[2] == SECP256R1_IDENTITY[2] && + p[3] == SECP256R1_IDENTITY[3] && + p[4] == SECP256R1_IDENTITY[4] && + p[5] == SECP256R1_IDENTITY[5] && + p[6] == SECP256R1_IDENTITY[6] && + p[7] == SECP256R1_IDENTITY[7] ) + { + for (int i = 0; i < 8; i++) + { + r[i] = q[i]; + } + return; + } + // if q == 0 return p + else if ( q[0] == SECP256R1_IDENTITY[0] && + q[1] == SECP256R1_IDENTITY[1] && + q[2] == SECP256R1_IDENTITY[2] && + q[3] == SECP256R1_IDENTITY[3] && + q[4] == SECP256R1_IDENTITY[4] && + q[5] == SECP256R1_IDENTITY[5] && + q[6] == SECP256R1_IDENTITY[6] && + q[7] == SECP256R1_IDENTITY[7] ) + { + for (int i = 0; i < 8; i++) + { + r[i] = p[i]; + } + return; + } + + // Convert coordinates to field elements + RawpSecp256r1::Element x1_fe, y1_fe, x2_fe, y2_fe; + array2fe(x1, x1_fe); + array2fe(y1, y1_fe); + array2fe(x2, x2_fe); + array2fe(y2, y2_fe); + + // Calculate lambda = (y2 - y1) / (x2 - x1) + RawpSecp256r1::Element y2_minus_y1; + secp256r1.sub(y2_minus_y1, y2_fe, y1_fe); + RawpSecp256r1::Element x2_minus_x1; + secp256r1.sub(x2_minus_x1, x2_fe, x1_fe); + RawpSecp256r1::Element x2_minus_x1_inv; + secp256r1.inv(x2_minus_x1_inv, x2_minus_x1); + RawpSecp256r1::Element lambda; + secp256r1.mul(lambda, y2_minus_y1, x2_minus_x1_inv); + + // Calculate x3 = lambda^2 - (x1 + x2) + RawpSecp256r1::Element x3_fe; + RawpSecp256r1::Element lambda_sq; + secp256r1.square(lambda_sq, lambda); + RawpSecp256r1::Element x1_plus_x2; + secp256r1.add(x1_plus_x2, x1_fe, x2_fe); + secp256r1.sub(x3_fe, lambda_sq, x1_plus_x2); + + // Calculate y3 = lambda * (x1 - x3) - y1 + RawpSecp256r1::Element y3_fe; + RawpSecp256r1::Element x1_minus_x3; + secp256r1.sub(x1_minus_x3, x1_fe, x3_fe); + RawpSecp256r1::Element lambda_x1_minus_x3; + secp256r1.mul(lambda_x1_minus_x3, lambda, x1_minus_x3); + secp256r1.sub(y3_fe, lambda_x1_minus_x3, y1_fe); + + // Convert to result + fe2array(x3_fe, r); + fe2array(y3_fe, r + 4); +} + +void secp256r1_curve_dbl( + const uint64_t * p, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +) +{ + // If p==0 return p + if ( p[0] == SECP256R1_IDENTITY[0] && + p[1] == SECP256R1_IDENTITY[1] && + p[2] == SECP256R1_IDENTITY[2] && + p[3] == SECP256R1_IDENTITY[3] && + p[4] == SECP256R1_IDENTITY[4] && + p[5] == SECP256R1_IDENTITY[5] && + p[6] == SECP256R1_IDENTITY[6] && + p[7] == SECP256R1_IDENTITY[7] ) + { + for (int i = 0; i < 8; i++) + { + r[i] = p[i]; + } + return; + } + + // Convert coordinates to field elements + uint64_t * x = (uint64_t *)&p[0]; + uint64_t * y = (uint64_t *)&p[4]; + RawpSecp256r1::Element x_fe, y_fe; + array2fe(x, x_fe); + array2fe(y, y_fe); + + // Calculate lambda = (3*x1^2) / (2*y1) + RawpSecp256r1::Element x1_sq; + secp256r1.square(x1_sq, x_fe); + RawpSecp256r1::Element three; + secp256r1.fromUI(three, 3); + RawpSecp256r1::Element three_x1_sq; + secp256r1.mul(three_x1_sq, x1_sq, three); + RawpSecp256r1::Element two_y1; + secp256r1.add(two_y1, y_fe, y_fe); + RawpSecp256r1::Element two_y1_inv; + secp256r1.inv(two_y1_inv, two_y1); + RawpSecp256r1::Element lambda; + secp256r1.mul(lambda, three_x1_sq, two_y1_inv); + + // Calculate x3 = lambda^2 - 2*x1 + RawpSecp256r1::Element lambda_sq; + secp256r1.square(lambda_sq, lambda); + RawpSecp256r1::Element two_x1; + secp256r1.add(two_x1, x_fe, x_fe); + RawpSecp256r1::Element x3_fe; + secp256r1.sub(x3_fe, lambda_sq, two_x1); + + // Calculate y3 = lambda * (x1 - x3) - y1 + RawpSecp256r1::Element x1_minus_x3; + secp256r1.sub(x1_minus_x3, x_fe, x3_fe); + RawpSecp256r1::Element lambda_x1_minus_x3; + secp256r1.mul(lambda_x1_minus_x3, lambda, x1_minus_x3); + RawpSecp256r1::Element y3_fe; + secp256r1.sub(y3_fe, lambda_x1_minus_x3, y_fe); + + // Convert to result + fe2array(x3_fe, r); + fe2array(y3_fe, r + 4); +} + +int secp256r1_curve_dbl_scalar_mul( + const uint64_t * k1, // 4 x 64 bits + const uint64_t * p1, // 8 x 64 bits + const uint64_t * k2, // 4 x 64 bits + const uint64_t * p2, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +) +{ + for (uint64_t i = 0; i < 8; i++) { + r[i] = 0; + } + + for (int64_t ii=255; ii>=0; ii--) { + uint64_t i = ii; + + // r = r + r + secp256r1_curve_dbl(r, r); + + // If k1[i] == 1 then r = r + p1 + uint64_t k1_bit = (k1[i / 64] >> (i % 64)) & 1; + if (k1_bit == 1) + { + secp256r1_curve_add(r, p1, r); + } + + // If k2[i] == 1 then r = r + p2 + uint64_t k2_bit = (k2[i / 64] >> (i % 64)) & 1; + if (k2_bit == 1) + { + secp256r1_curve_add(r, p2, r); + } + } + + return 0; +} + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/lib-c/c/src/secp256r1/secp256r1.hpp b/lib-c/c/src/secp256r1/secp256r1.hpp new file mode 100644 index 000000000..56e37e010 --- /dev/null +++ b/lib-c/c/src/secp256r1/secp256r1.hpp @@ -0,0 +1,58 @@ +#ifndef SECP256R1_HPP +#define SECP256R1_HPP + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int secp256r1_add_point_ec ( + uint64_t dbl, + const uint64_t * x1, // 4 x 64 bits + const uint64_t * y1, // 4 x 64 bits + const uint64_t * x2, // 4 x 64 bits + const uint64_t * y2, // 4 x 64 bits + uint64_t * x3, // 4 x 64 bits + uint64_t * y3 // 4 x 64 bits +); + +int secp256r1_add_point_ecp ( + const uint64_t dbl, + const uint64_t * p1, // 8 x 64 bits + const uint64_t * p2, // 8 x 64 bits + uint64_t * p3 // 8 x 64 bits +); + +int secp256r1_ecdsa_verify ( + const uint64_t * pk, // 8 x 64 bits + const uint64_t * z, // 4 x 64 bits + const uint64_t * r, // 4 x 64 bits + const uint64_t * s, // 4 x 64 bits + uint64_t * result // 8 x 64 bits +); + +void secp256r1_curve_add( + const uint64_t * p, // 8 x 64 bits + const uint64_t * q, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +); + +void secp256r1_curve_dbl( + const uint64_t * p, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +); + +int secp256r1_curve_dbl_scalar_mul( + const uint64_t * k1, // 4 x 64 bits + const uint64_t * p1, // 8 x 64 bits + const uint64_t * k2, // 4 x 64 bits + const uint64_t * p2, // 8 x 64 bits + uint64_t * r // 8 x 64 bits +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif From 824e79ad4024223149c0ba1ce58271bf50c84768 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:59:53 +0000 Subject: [PATCH 379/782] merging pre-develop-0.16.0 to feature/precompile_results_feat_hints --- .github/workflows/pr.yml | 2 +- .github/workflows/release.yml | 1 - .gitignore | 2 + .vscode/launch.json | 2 - Cargo.lock | 124 +-- Cargo.toml | 6 +- book/getting_started/installation.md | 3 +- book/getting_started/proof.md | 4 +- cli/src/commands/common.rs | 14 - cli/src/commands/execute.rs | 8 +- cli/src/commands/prove.rs | 18 +- cli/src/commands/run.rs | 6 +- cli/src/commands/stats.rs | 6 - cli/src/commands/verify_constraints.rs | 6 - common/Cargo.toml | 2 + common/src/bus/bus_device.rs | 56 +- common/src/bus/bus_device_metrics.rs | 30 +- common/src/bus/data_bus_mem.rs | 14 - common/src/bus/data_bus_operation.rs | 365 +++++-- common/src/component/component_builder.rs | 20 +- common/src/component/component_counter.rs | 10 +- common/src/component/component_instance.rs | 35 +- common/src/component/component_planner.rs | 86 ++ common/src/io/stdin/file.rs | 5 +- common/src/io/stdin/memory.rs | 13 +- common/src/io/stdin/null.rs | 5 +- common/src/io/stdin/zisk_stdin.rs | 40 +- common/src/lib.rs | 2 - common/src/regular_counters.rs | 54 +- common/src/types.rs | 28 + common/src/zisk_lib_init.rs | 63 -- core/Cargo.toml | 3 + core/src/inst_context.rs | 20 +- core/src/mem.rs | 186 ++++ core/src/riscv2zisk_context.rs | 109 +- core/src/zisk_definitions.rs | 4 +- core/src/zisk_inst.rs | 20 +- core/src/zisk_inst_builder.rs | 12 +- core/src/zisk_ops.rs | 323 +++++- core/src/zisk_rom_2_asm.rs | 619 ++++++----- data-bus/src/data_bus.rs | 175 +--- data-bus/src/data_bus_file.rs | 139 --- data-bus/src/data_bus_player.rs | 42 - data-bus/src/lib.rs | 4 - distributed/Dockerfile | 2 - distributed/README.md | 1 - distributed/crates/common/src/dto.rs | 9 +- distributed/crates/common/src/types.rs | 12 +- .../src/cli/handler_coordinator.rs | 10 +- .../coordinator/src/cli/handler_prove.rs | 12 + .../crates/coordinator/src/cli/main.rs | 9 + distributed/crates/coordinator/src/config.rs | 5 +- .../crates/coordinator/src/coordinator.rs | 27 +- .../crates/coordinator/src/workers_pool.rs | 12 +- .../grpc-api/proto/zisk_distributed_api.proto | 21 +- .../crates/grpc-api/src/conversions.rs | 10 +- distributed/crates/worker/src/cli/main.rs | 26 +- distributed/crates/worker/src/config.rs | 4 - distributed/crates/worker/src/worker.rs | 76 +- distributed/crates/worker/src/worker_node.rs | 9 - emulator-asm/Makefile | 12 +- emulator-asm/asm-runner/src/asm_mo_runner.rs | 1 - emulator-asm/asm-runner/src/asm_runner.rs | 4 +- .../asm-runner/src/asm_services/services.rs | 39 +- emulator-asm/src/dma/Makefile | 33 + emulator-asm/src/dma/direct_memcpy_mops.asm | 330 ++++++ emulator-asm/src/dma/direct_memcpy_mtrace.asm | 354 +++++++ emulator-asm/src/dma/dma_constants.inc | 71 ++ emulator-asm/src/dma/fast_dma_encode.asm | 74 ++ .../src/dma/fast_dma_encode_table.asm | 264 +++++ emulator-asm/src/dma/memcpy_fast.asm | 97 ++ emulator-asm/src/dma/test_dma.cpp | 978 ++++++++++++++++++ emulator-asm/src/main.c | 42 +- emulator/Cargo.toml | 1 + emulator/src/emu.rs | 170 ++- emulator/src/emu_context.rs | 20 +- emulator/src/emu_options.rs | 4 +- emulator/src/emulator.rs | 5 +- emulator/src/regions_of_interest.rs | 7 +- emulator/src/stats.rs | 13 +- executor/Cargo.toml | 2 + executor/src/dummy_counter.rs | 29 +- executor/src/executor.rs | 9 +- executor/src/sm_static_bundle.rs | 101 +- executor/src/static_data_bus.rs | 115 +- executor/src/static_data_bus_collect.rs | 241 ++--- lib-c/c/src/ffiasm/bls12_381.asm | 3 + lib-c/c/src/ffiasm/bls12_381_384.asm | 3 + lib-c/c/src/ffiasm/fec.asm | 3 + lib-c/c/src/ffiasm/fnec.asm | 3 + lib-c/c/src/ffiasm/fq.asm | 3 + lib-c/c/src/ffiasm/fr.asm | 3 + lib-c/c/src/ffiasm/nsecp256r1.asm | 3 + lib-c/c/src/ffiasm/psecp256r1.asm | 3 + pil/config.pil | 6 + pil/operations.pil | 3 + pil/opids.pil | 13 +- pil/src/constants.rs | 5 + pil/src/lib.rs | 2 + pil/src/pil_helpers/traces.rs | 338 ++++-- pil/src/pil_helpers/traces_dev.rs | 2 +- pil/zisk.pil | 41 +- precompiles/arith_eq/pil/arith_eq.pil | 2 +- .../arith_eq/src/arith_eq_bus_device.rs | 172 +-- precompiles/arith_eq/src/arith_eq_input.rs | 92 +- precompiles/arith_eq/src/arith_eq_instance.rs | 23 +- precompiles/arith_eq/src/arith_eq_manager.rs | 24 +- .../arith_eq/src/mem_inputs/arith256.rs | 22 +- .../arith_eq/src/mem_inputs/arith256_mod.rs | 24 +- .../src/mem_inputs/bn254_complex_add.rs | 20 +- .../src/mem_inputs/bn254_complex_mul.rs | 20 +- .../src/mem_inputs/bn254_complex_sub.rs | 20 +- .../src/mem_inputs/bn254_curve_add.rs | 26 +- .../src/mem_inputs/bn254_curve_dbl.rs | 23 +- .../src/mem_inputs/generate_mem_inputs.rs | 38 +- .../arith_eq/src/mem_inputs/secp256k1_add.rs | 20 +- .../arith_eq/src/mem_inputs/secp256k1_dbl.rs | 18 +- precompiles/arith_eq_384/pil/arith_eq_384.pil | 2 +- .../src/arith_eq_384_bus_device.rs | 149 ++- .../arith_eq_384/src/arith_eq_384_input.rs | 62 +- .../arith_eq_384/src/arith_eq_384_instance.rs | 23 +- .../arith_eq_384/src/arith_eq_384_manager.rs | 24 +- .../src/mem_inputs/arith384_mod.rs | 17 +- .../src/mem_inputs/bls12_381_complex_add.rs | 17 +- .../src/mem_inputs/bls12_381_complex_mul.rs | 17 +- .../src/mem_inputs/bls12_381_complex_sub.rs | 17 +- .../src/mem_inputs/bls12_381_curve_add.rs | 17 +- .../src/mem_inputs/bls12_381_curve_dbl.rs | 17 +- .../src/mem_inputs/generate_mem_inputs.rs | 36 +- precompiles/big_int/pil/big_int_add.pil | 2 +- precompiles/big_int/src/add256.rs | 14 +- precompiles/big_int/src/add256_bus_device.rs | 103 +- precompiles/big_int/src/add256_constants.rs | 4 +- .../big_int/src/add256_gen_mem_inputs.rs | 50 +- precompiles/big_int/src/add256_input.rs | 12 +- precompiles/big_int/src/add256_instance.rs | 23 +- precompiles/big_int/src/add256_manager.rs | 24 +- precompiles/common/Cargo.toml | 2 + precompiles/common/src/lib.rs | 275 ++++- precompiles/dma/Cargo.toml | 38 + precompiles/dma/dma.md | 14 + precompiles/dma/pil/dma.pil | 205 ++++ precompiles/dma/pil/dma_64_aligned.pil | 237 +++++ precompiles/dma/pil/dma_pre_post.pil | 179 ++++ precompiles/dma/pil/dma_pre_post_table.pil | 71 ++ precompiles/dma/pil/dma_rom.pil | 52 + precompiles/dma/pil/dma_unaligned.pil | 266 +++++ precompiles/dma/pil/dual_range.pil | 17 + precompiles/dma/pil/tools.pil | 13 + precompiles/dma/src/dma/dma.rs | 305 ++++++ precompiles/dma/src/dma/dma_collector.rs | 86 ++ precompiles/dma/src/dma/dma_input.rs | 54 + precompiles/dma/src/dma/dma_instance.rs | 117 +++ precompiles/dma/src/dma/dma_rom.rs | 20 + precompiles/dma/src/dma/mod.rs | 11 + .../dma/src/dma_64_aligned/dma_64_aligned.rs | 283 +++++ .../dma_64_aligned_collector.rs | 99 ++ .../dma_64_aligned/dma_64_aligned_input.rs | 72 ++ .../dma_64_aligned/dma_64_aligned_instance.rs | 141 +++ precompiles/dma/src/dma_64_aligned/mod.rs | 10 + precompiles/dma/src/dma_bus_device.rs | 195 ++++ precompiles/dma/src/dma_constants.rs | 13 + precompiles/dma/src/dma_gen_mem_inputs.rs | 226 ++++ precompiles/dma/src/dma_manager.rs | 99 ++ precompiles/dma/src/dma_planner.rs | 242 +++++ .../dma/src/dma_pre_post/dma_pre_post.rs | 350 +++++++ .../dma_pre_post/dma_pre_post_collector.rs | 84 ++ .../src/dma_pre_post/dma_pre_post_input.rs | 79 ++ .../src/dma_pre_post/dma_pre_post_instance.rs | 118 +++ .../dma/src/dma_pre_post/dma_pre_post_rom.rs | 23 + precompiles/dma/src/dma_pre_post/mod.rs | 12 + .../dma/src/dma_unaligned/dma_unaligned.rs | 367 +++++++ .../dma_unaligned/dma_unaligned_collector.rs | 99 ++ .../src/dma_unaligned/dma_unaligned_input.rs | 85 ++ .../dma_unaligned/dma_unaligned_instance.rs | 141 +++ precompiles/dma/src/dma_unaligned/mod.rs | 10 + precompiles/dma/src/lib.rs | 19 + precompiles/helpers/Cargo.toml | 3 +- precompiles/helpers/src/dma.rs | 590 +++++++++++ precompiles/helpers/src/lib.rs | 2 + precompiles/keccakf/pil/keccakf.pil | 2 +- precompiles/keccakf/src/keccakf_bus_device.rs | 103 +- .../keccakf/src/keccakf_gen_mem_inputs.rs | 23 +- precompiles/keccakf/src/keccakf_input.rs | 4 +- precompiles/keccakf/src/keccakf_instance.rs | 28 +- precompiles/keccakf/src/keccakf_manager.rs | 24 +- precompiles/poseidon2/pil/poseidon2.pil | 2 +- .../poseidon2/src/poseidon2_bus_device.rs | 101 +- .../poseidon2/src/poseidon2_gen_mem_inputs.rs | 20 +- .../poseidon2/src/poseidon2_instance.rs | 23 +- .../poseidon2/src/poseidon2_manager.rs | 24 +- precompiles/sha256f/pil/sha256f.pil | 3 +- precompiles/sha256f/src/sha256f_bus_device.rs | 103 +- .../sha256f/src/sha256f_gen_mem_inputs.rs | 42 +- precompiles/sha256f/src/sha256f_input.rs | 10 +- precompiles/sha256f/src/sha256f_instance.rs | 23 +- precompiles/sha256f/src/sha256f_manager.rs | 24 +- riscv/src/riscv_inst.rs | 2 +- sdk/Cargo.toml | 1 + sdk/src/builder.rs | 29 +- sdk/src/prover/asm.rs | 10 +- sdk/src/prover/backend.rs | 6 +- sdk/src/prover/emu.rs | 10 +- sdk/src/utils.rs | 14 - sdk/src/zisk_lib_loader.rs | 31 +- state-machines/arith/pil/arith.pil | 4 +- state-machines/arith/pil/arith_mul64.pil | 2 +- state-machines/arith/src/arith.rs | 21 +- state-machines/arith/src/arith_bus_device.rs | 56 +- state-machines/arith/src/arith_full.rs | 5 +- .../arith/src/arith_full_instance.rs | 28 +- state-machines/binary/pil/binary.pil | 4 +- state-machines/binary/pil/binary_add.pil | 4 +- .../binary/pil/binary_extension.pil | 4 +- state-machines/binary/src/binary.rs | 11 +- .../binary/src/binary_add_collector.rs | 25 +- .../binary/src/binary_basic_collector.rs | 26 +- state-machines/binary/src/binary_counter.rs | 57 +- .../binary/src/binary_extension_collector.rs | 26 +- .../frequent-ops/pil/frequent_ops.pil | 5 +- state-machines/main/pil/main.pil | 44 +- state-machines/main/pil/registers.pil | 67 ++ state-machines/main/src/main_counter.rs | 41 +- state-machines/main/src/main_sm.rs | 12 +- .../mem-common/src/mem_constants.rs | 5 - state-machines/mem-common/src/mem_counters.rs | 48 +- .../mem-cpp/cpp/mem_align_counter.cpp | 68 +- state-machines/mem-cpp/cpp/mem_config.hpp | 42 +- state-machines/mem-cpp/cpp/mem_counter.cpp | 151 ++- state-machines/mem-cpp/cpp/tools.hpp | 2 +- state-machines/mem/pil/dual_byte.pil | 12 - state-machines/mem/pil/mem.pil | 30 +- state-machines/mem/pil/mem_align_byte.pil | 11 +- state-machines/mem/src/mem.rs | 6 +- state-machines/mem/src/mem_align_byte_sm.rs | 8 +- state-machines/mem/src/mem_align_collector.rs | 21 +- state-machines/mem/src/mem_counters_cursor.rs | 2 +- .../mem/src/mem_module_collector.rs | 34 +- state-machines/mem/src/mem_sm.rs | 93 +- state-machines/mem/src/mem_test.rs | 60 +- state-machines/mem/src/rom_data_sm.rs | 2 +- state-machines/rom/src/rom.rs | 25 +- state-machines/rom/src/rom_counter.rs | 4 +- state-machines/rom/src/rom_instance.rs | 37 +- tools/test-env/build_zisk.sh | 1 - tools/verify_all.sh | 4 - witness-computation/Cargo.toml | 4 +- witness-computation/src/lib.rs | 1 + witness-computation/src/zisk_lib.rs | 39 +- ziskos-hints/Cargo.toml | 2 +- ziskos/entrypoint/Cargo.toml | 7 +- ziskos/entrypoint/src/dma/memcmp.s | 14 + ziskos/entrypoint/src/dma/memcpy.s | 14 + ziskos/entrypoint/src/dma/memmove.s | 13 + ziskos/entrypoint/src/io.rs | 57 + ziskos/entrypoint/src/lib.rs | 5 + ziskos/entrypoint/src/memcpy_test.rs | 489 +++++++++ ziskos/entrypoint/src/syscalls/mod.rs | 12 + ziskos/entrypoint/src/syscalls/syscall.rs | 2 + 259 files changed, 11811 insertions(+), 3497 deletions(-) delete mode 100644 common/src/zisk_lib_init.rs delete mode 100644 data-bus/src/data_bus_file.rs delete mode 100644 data-bus/src/data_bus_player.rs create mode 100644 emulator-asm/src/dma/Makefile create mode 100644 emulator-asm/src/dma/direct_memcpy_mops.asm create mode 100644 emulator-asm/src/dma/direct_memcpy_mtrace.asm create mode 100644 emulator-asm/src/dma/dma_constants.inc create mode 100644 emulator-asm/src/dma/fast_dma_encode.asm create mode 100644 emulator-asm/src/dma/fast_dma_encode_table.asm create mode 100644 emulator-asm/src/dma/memcpy_fast.asm create mode 100644 emulator-asm/src/dma/test_dma.cpp create mode 100644 pil/config.pil create mode 100644 pil/src/constants.rs create mode 100644 precompiles/dma/Cargo.toml create mode 100644 precompiles/dma/dma.md create mode 100644 precompiles/dma/pil/dma.pil create mode 100644 precompiles/dma/pil/dma_64_aligned.pil create mode 100644 precompiles/dma/pil/dma_pre_post.pil create mode 100644 precompiles/dma/pil/dma_pre_post_table.pil create mode 100644 precompiles/dma/pil/dma_rom.pil create mode 100644 precompiles/dma/pil/dma_unaligned.pil create mode 100644 precompiles/dma/pil/dual_range.pil create mode 100644 precompiles/dma/pil/tools.pil create mode 100644 precompiles/dma/src/dma/dma.rs create mode 100644 precompiles/dma/src/dma/dma_collector.rs create mode 100644 precompiles/dma/src/dma/dma_input.rs create mode 100644 precompiles/dma/src/dma/dma_instance.rs create mode 100644 precompiles/dma/src/dma/dma_rom.rs create mode 100644 precompiles/dma/src/dma/mod.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs create mode 100644 precompiles/dma/src/dma_64_aligned/mod.rs create mode 100644 precompiles/dma/src/dma_bus_device.rs create mode 100644 precompiles/dma/src/dma_constants.rs create mode 100644 precompiles/dma/src/dma_gen_mem_inputs.rs create mode 100644 precompiles/dma/src/dma_manager.rs create mode 100644 precompiles/dma/src/dma_planner.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs create mode 100644 precompiles/dma/src/dma_pre_post/mod.rs create mode 100644 precompiles/dma/src/dma_unaligned/dma_unaligned.rs create mode 100644 precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs create mode 100644 precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs create mode 100644 precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs create mode 100644 precompiles/dma/src/dma_unaligned/mod.rs create mode 100644 precompiles/dma/src/lib.rs create mode 100644 precompiles/helpers/src/dma.rs create mode 100644 state-machines/main/pil/registers.pil delete mode 100644 state-machines/mem/pil/dual_byte.pil create mode 100644 ziskos/entrypoint/src/dma/memcmp.s create mode 100644 ziskos/entrypoint/src/dma/memcpy.s create mode 100644 ziskos/entrypoint/src/dma/memmove.s create mode 100644 ziskos/entrypoint/src/io.rs create mode 100644 ziskos/entrypoint/src/memcpy_test.rs diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 738c7290a..6cfbc7cc9 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -47,7 +47,7 @@ jobs: test-x86_64: name: Test on Ubuntu x86_64 runs-on: self-hosted - timeout-minutes: 120 + timeout-minutes: 180 container: image: ubuntu:22.04 options: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba5216afd..dc9ccc284 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -117,7 +117,6 @@ jobs: cp ./target/${TARGET}/release/riscv2zisk zisk-dist/bin/ cp ./target/${TARGET}/release/zisk-coordinator zisk-dist/bin/ cp ./target/${TARGET}/release/zisk-worker zisk-dist/bin/ - cp ./target/${TARGET}/release/libzisk_witness.${LIB_EXT} zisk-dist/bin/ cp ./ziskup/ziskup zisk-dist/bin/ cp ./target/${TARGET}/release/libziskclib.a zisk-dist/bin/ diff --git a/.gitignore b/.gitignore index 6739405b2..992799b46 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ /tmp *.log /emulator-asm/build* +/emulator-asm/src/dma/*.o +/emulator-asm/src/dma/test_dma /emulator-asm/src/emu.asm /cache /lib-c/c/build diff --git a/.vscode/launch.json b/.vscode/launch.json index c1f559e01..40f42d394 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,8 +15,6 @@ //"--bin", //"proofman-cli", "verify-constraints", - "--witness-lib", - "../zisk/target/debug/libzisk_witness.so", "--elf", "../zisk-testvectors/pessimistic-proof/program/pessimistic-proof-program-keccak.elf", "-i", diff --git a/Cargo.lock b/Cargo.lock index 687cebf54..cfb3919a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -501,22 +501,11 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bincode" -version = "2.0.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "bincode_derive", "serde", - "unty", -] - -[[package]] -name = "bincode_derive" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" -dependencies = [ - "virtue", ] [[package]] @@ -803,9 +792,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785" dependencies = [ "clap_builder", "clap_derive", @@ -813,9 +802,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61" dependencies = [ "anstream", "anstyle", @@ -825,9 +814,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -1190,7 +1179,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" dependencies = [ "fields", "num-bigint", @@ -1521,9 +1510,11 @@ dependencies = [ "precomp-arith-eq", "precomp-arith-eq-384", "precomp-big-int", + "precomp-dma", "precomp-keccakf", "precomp-poseidon2", "precomp-sha256f", + "precompiles-common", "precompiles-hints", "proofman", "proofman-common", @@ -1575,7 +1566,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" dependencies = [ "cfg-if", "num-bigint", @@ -2028,9 +2019,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2964,7 +2955,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" dependencies = [ "colored", "fields", @@ -3218,6 +3209,29 @@ dependencies = [ "zisk-pil", ] +[[package]] +name = "precomp-dma" +version = "0.16.0" +dependencies = [ + "fields", + "generic-array", + "lib-c", + "mem-common", + "pil-std-lib", + "precompiles-common", + "precompiles-helpers", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sm-mem", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + [[package]] name = "precomp-keccakf" version = "0.16.0" @@ -3286,6 +3300,8 @@ name = "precompiles-common" version = "0.16.0" dependencies = [ "fields", + "mem-common", + "sm-mem", "zisk-common", "zisk-core", ] @@ -3352,7 +3368,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" dependencies = [ "blake3", "borsh", @@ -3387,7 +3403,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" dependencies = [ "borsh", "colored", @@ -3418,7 +3434,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" dependencies = [ "fields", "itoa", @@ -3431,7 +3447,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" dependencies = [ "proc-macro2", "quote", @@ -3442,7 +3458,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" dependencies = [ "crossbeam-channel", "tracing", @@ -3451,7 +3467,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" dependencies = [ "colored", "fields", @@ -3462,7 +3478,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" dependencies = [ "bytemuck", "fields", @@ -4286,9 +4302,9 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" @@ -4901,9 +4917,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" +checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a" dependencies = [ "async-trait", "axum", @@ -4930,9 +4946,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40aaccc9f9eccf2cd82ebc111adc13030d23e887244bc9cfa5d1d636049de3" +checksum = "27aac809edf60b741e2d7db6367214d078856b8a5bff0087e94ff330fb97b6fc" dependencies = [ "prettyplease", "proc-macro2", @@ -4942,9 +4958,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" +checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0" dependencies = [ "bytes", "prost", @@ -4953,9 +4969,9 @@ dependencies = [ [[package]] name = "tonic-prost-build" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a16cba4043dc3ff43fcb3f96b4c5c154c64cbd18ca8dce2ab2c6a451d058a2" +checksum = "a4556786613791cfef4ed134aa670b61a85cfcacf71543ef33e8d801abae988f" dependencies = [ "prettyplease", "proc-macro2", @@ -5182,12 +5198,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "unty" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" - [[package]] name = "ureq" version = "3.1.4" @@ -5290,12 +5300,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "virtue" -version = "0.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" - [[package]] name = "walkdir" version = "2.5.0" @@ -5868,7 +5872,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#74b7c04f96386fc312d09f43530084bd04fd3d5d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" dependencies = [ "colored", "fields", @@ -5954,18 +5958,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" +checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" +checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22" dependencies = [ "proc-macro2", "quote", @@ -6061,6 +6065,7 @@ name = "zisk-common" version = "0.16.0" dependencies = [ "anyhow", + "bincode", "bytemuck", "fields", "libc", @@ -6091,6 +6096,7 @@ dependencies = [ "indexmap", "json", "lib-c", + "paste", "precompiles-helpers", "rayon", "riscv", @@ -6235,6 +6241,7 @@ dependencies = [ "tracing", "zisk-common", "zisk-distributed-common", + "zisk-witness", "zstd", ] @@ -6261,6 +6268,7 @@ dependencies = [ "precomp-arith-eq", "precomp-arith-eq-384", "precomp-big-int", + "precomp-dma", "precomp-keccakf", "precomp-poseidon2", "precomp-sha256f", diff --git a/Cargo.toml b/Cargo.toml index 8e55e384e..164bba4bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ categories = ["cryptography"] [workspace.metadata] gha_pil2_proofman_js_branch = "pre-develop-0.16.0" -gha_pil2_compiler_branch = "tags/v0.8.0" +gha_pil2_compiler_branch = "develop-0.9.0" [workspace] members = [ @@ -36,6 +36,7 @@ members = [ "precompiles/keccakf", "precompiles/sha256f", "precompiles/big_int", + "precompiles/dma", "lib-c", "lib-float", "emulator-asm/asm-runner", @@ -80,6 +81,7 @@ precomp-keccakf = { path = "precompiles/keccakf" } precomp-sha256f = { path = "precompiles/sha256f" } precomp-poseidon2 = { path = "precompiles/poseidon2" } precomp-big-int = { path = "precompiles/big_int" } +precomp-dma = { path = "precompiles/dma" } riscv = { path = "riscv" } rom-setup = { path = "rom-setup" } sm-arith = { path = "state-machines/arith" } @@ -157,6 +159,7 @@ clap = { version = "4", features = ["derive", "env"] } futures = { version = "0.3" } thiserror = { version = "2" } bytes = "1.0" +bincode = "1.3.3" tokio = { version = "1", features = ["full"] } tokio-stream = "0.1" @@ -164,6 +167,7 @@ futures-util = "0.3" uuid = { version = "1.0", features = ["serde", "v4"] } chrono = { version = "0.4", features = ["serde"] } sha2 = { version = "0.10.9", features = ["compress"] } +paste = "1.0" # gRPC dependencies tonic = "0.14" diff --git a/book/getting_started/installation.md b/book/getting_started/installation.md index 3a4e20346..96ad408f4 100644 --- a/book/getting_started/installation.md +++ b/book/getting_started/installation.md @@ -123,8 +123,7 @@ You can use the flags `--provingkey`, `--verifykey` or `--nokey` to specify the 3. Copy the tools to `~/.zisk/bin` directory: ```bash mkdir -p $HOME/.zisk/bin - LIB_EXT=$([[ "$(uname)" == "Darwin" ]] && echo "dylib" || echo "so") - cp target/release/cargo-zisk target/release/ziskemu target/release/riscv2zisk target/release/zisk-coordinator target/release/zisk-worker target/release/libzisk_witness.$LIB_EXT target/release/libziskclib.a $HOME/.zisk/bin + cp target/release/cargo-zisk target/release/ziskemu target/release/riscv2zisk target/release/zisk-coordinator target/release/zisk-worker target/release/libziskclib.a $HOME/.zisk/bin ``` 4. Copy required files for assembly rom setup: diff --git a/book/getting_started/proof.md b/book/getting_started/proof.md index e566c9795..fa508315b 100644 --- a/book/getting_started/proof.md +++ b/book/getting_started/proof.md @@ -26,10 +26,10 @@ In the following steps to verify constraints or generate prove, select one of th - input_two_segments.bin: 512 shas To **verify constraints** use: -`(cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --witness-lib ../zisk/target/release/libzisk_witness.so --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey)` +`(cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey)` To **generate proof** use: -`(cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --witness-lib ../zisk/target/release/libzisk_witness.so --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey)` +`(cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey)` ## Steps to compile a verifiable rust program diff --git a/cli/src/commands/common.rs b/cli/src/commands/common.rs index cb5890265..b97ef04fd 100644 --- a/cli/src/commands/common.rs +++ b/cli/src/commands/common.rs @@ -6,14 +6,6 @@ pub fn get_home_dir() -> String { env::var("HOME").expect("get_home_dir() failed to get HOME environment variable") } -/// Gets the default witness computation library file location in the home installation directory. -pub fn get_default_witness_computation_lib() -> PathBuf { - let extension = if cfg!(target_os = "macos") { "dylib" } else { "so" }; - let witness_computation_lib = - format!("{}/.zisk/bin/libzisk_witness.{}", get_home_dir(), extension); - PathBuf::from(witness_computation_lib) -} - /// Gets the default proving key file location in the home installation directory. pub fn get_default_proving_key() -> PathBuf { let proving_key = format!("{}/.zisk/provingKey", get_home_dir()); @@ -70,12 +62,6 @@ pub fn cli_fail_if_macos() -> anyhow::Result<()> { } } -/// Gets the witness computation library file location. -/// Uses the default one if not specified by user. -pub fn get_witness_computation_lib(witness_lib: Option<&PathBuf>) -> PathBuf { - witness_lib.cloned().unwrap_or_else(get_default_witness_computation_lib) -} - /// Gets the proving key file location. /// Uses the default one if not specified by user. pub fn get_proving_key(proving_key: Option<&PathBuf>) -> PathBuf { diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index a2f4749b8..a03df4bb9 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -19,10 +19,6 @@ use zisk_common::io::{StreamSource, ZiskStdin}; .required(false) ))] pub struct ZiskExecute { - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - /// ROM file path /// This is the path to the ROM file that the witness computation dynamic library will use /// to generate the witness. @@ -125,7 +121,6 @@ impl ZiskExecute { let prover = ProverClient::builder() .emu() .witness() - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) @@ -143,8 +138,7 @@ impl ZiskExecute { ) -> Result { let prover = ProverClient::builder() .asm() - .verify_constraints() - .witness_lib_path_opt(self.witness_lib.clone()) + .witness() .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 535effb18..5097e7b34 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -22,10 +22,6 @@ use zisk_sdk::{ProverClient, ZiskProveResult}; .required(false) ))] pub struct ZiskProve { - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - /// ELF file path /// This is the path to the ROM file that the witness computation dynamic library will use /// to generate the witness. @@ -130,14 +126,14 @@ impl ZiskProve { let mut gpu_params = ParamsGPU::new(self.preallocate); - if self.max_streams.is_some() { - gpu_params.with_max_number_streams(self.max_streams.unwrap()); + if let Some(max_streams) = self.max_streams { + gpu_params.with_max_number_streams(max_streams); } - if self.number_threads_witness.is_some() { - gpu_params.with_number_threads_pools_witness(self.number_threads_witness.unwrap()); + if let Some(number_threads_witness) = self.number_threads_witness { + gpu_params.with_number_threads_pools_witness(number_threads_witness); } - if self.max_witness_stored.is_some() { - gpu_params.with_max_witness_stored(self.max_witness_stored.unwrap()); + if let Some(max_witness_stored) = self.max_witness_stored { + gpu_params.with_max_witness_stored(max_witness_stored); } let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; @@ -196,7 +192,6 @@ impl ZiskProve { .aggregation(self.aggregation) .compressed(self.compressed) .rma(self.rma) - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) @@ -227,7 +222,6 @@ impl ZiskProve { .aggregation(self.aggregation) .compressed(self.compressed) .rma(self.rma) - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) diff --git a/cli/src/commands/run.rs b/cli/src/commands/run.rs index 8ad7f9709..d98e8ee2d 100644 --- a/cli/src/commands/run.rs +++ b/cli/src/commands/run.rs @@ -72,12 +72,12 @@ impl ZiskRun { if self.metrics { extra_command += " -m "; } - if self.input.is_some() { - let path = Path::new(self.input.as_ref().unwrap()); + if let Some(input) = &self.input { + let path = Path::new(input); if !path.exists() { return Err(anyhow!("Input file does not exist at path: {}", path.display())); } - input_command = format!("-i {}", self.input.as_ref().unwrap()); + input_command = format!("-i {}", input); } runner_command = format!("ziskemu {input_command} {extra_command} -e"); } else { diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index d06745598..9076850fc 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -22,10 +22,6 @@ use crate::ux::{print_banner, print_banner_field}; .required(false) ))] pub struct ZiskStats { - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - /// ROM file path /// This is the path to the ROM file that the witness computation dynamic library will use /// to generate the witness. @@ -157,7 +153,6 @@ impl ZiskStats { let prover = ProverClient::builder() .emu() .witness() - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) @@ -176,7 +171,6 @@ impl ZiskStats { let prover = ProverClient::builder() .asm() .witness() - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 158f5578e..b3f2f5853 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -21,10 +21,6 @@ use zisk_sdk::{ProverClient, ZiskVerifyConstraintsResult}; .required(false) ))] pub struct ZiskVerifyConstraints { - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - /// ROM file path /// This is the path to the ROM file that the witness computation dynamic library will use /// to generate the witness. @@ -140,7 +136,6 @@ impl ZiskVerifyConstraints { let prover = ProverClient::builder() .emu() .verify_constraints() - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) @@ -159,7 +154,6 @@ impl ZiskVerifyConstraints { let prover = ProverClient::builder() .asm() .verify_constraints() - .witness_lib_path_opt(self.witness_lib.clone()) .proving_key_path_opt(self.proving_key.clone()) .elf_path(self.elf.clone()) .verbose(self.verbose) diff --git a/common/Cargo.toml b/common/Cargo.toml index bd3890a00..7efce79ae 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -33,6 +33,8 @@ quinn = "0.11" rustls = { version = "0.23", features = ["ring"] } rcgen = "0.14" +bincode = { workspace = true } + # Distributed mode (mpi) is only supported on Linux x86_64 [target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] mpi = { workspace = true } diff --git a/common/src/bus/bus_device.rs b/common/src/bus/bus_device.rs index 4184286c2..26eef3841 100644 --- a/common/src/bus/bus_device.rs +++ b/common/src/bus/bus_device.rs @@ -1,7 +1,4 @@ -use std::{any::Any, collections::VecDeque}; - -use super::BusId; -use crate::MemCollectorInfo; +use std::any::Any; /// Represents a subscriber in the `DataBus` system. /// @@ -11,57 +8,6 @@ use crate::MemCollectorInfo; /// # Associated Type /// * `D` - The type of data handled by the `BusDevice`. pub trait BusDevice: Any + Send + Sync { - /// Processes incoming data sent to the device. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus that sent the data. - /// * `data` - A reference to the data payload being processed. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - fn process_data( - &mut self, - bus_id: &BusId, - data: &[D], - pending: &mut VecDeque<(BusId, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool; - - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec; - /// Converts the device to a generic `Any` type. fn as_any(self: Box) -> Box; - - /// Performs any necessary cleanup or finalization when the metrics instance is closed. - fn on_close(&mut self) {} -} - -impl BusDevice for Box> { - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - (**self).process_data(bus_id, data, pending, mem_collector_info) - } - - fn bus_id(&self) -> Vec { - (**self).bus_id() - } - - fn as_any(self: Box) -> Box { - (*self).as_any() - } - - fn on_close(&mut self) { - (**self).on_close() - } } diff --git a/common/src/bus/bus_device_metrics.rs b/common/src/bus/bus_device_metrics.rs index e1a2e44cc..d9bcea5cf 100644 --- a/common/src/bus/bus_device_metrics.rs +++ b/common/src/bus/bus_device_metrics.rs @@ -2,16 +2,14 @@ //! of `BusDevice` and `Metrics`, providing a unified interface for monitoring and managing //! bus operations with associated metrics. -use std::{any::Any, collections::VecDeque}; +use super::BusDevice; -use super::{BusDevice, BusId}; - -use crate::MemCollectorInfo; use crate::Metrics; #[derive(Debug, PartialEq)] pub enum BusDeviceMode { Counter, + CounterAsm, InputGenerator, } @@ -22,30 +20,6 @@ pub enum BusDeviceMode { /// maintaining compatibility with `Metrics` functionality. pub trait BusDeviceMetrics: BusDevice + Metrics + std::any::Any {} -impl BusDevice for Box { - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - (**self).process_data(bus_id, data, pending, mem_collector_info) - } - - fn bus_id(&self) -> Vec { - (**self).bus_id() - } - - fn as_any(self: Box) -> Box { - (*self).as_any() - } - - fn on_close(&mut self) { - (**self).on_close() - } -} - /// Blanket implementation of `BusDeviceMetrics` for any type implementing `BusDevice`, /// `Metrics`, and `std::any::Any`. impl + Metrics + std::any::Any> BusDeviceMetrics for T {} diff --git a/common/src/bus/data_bus_mem.rs b/common/src/bus/data_bus_mem.rs index b42fa663e..13064a936 100644 --- a/common/src/bus/data_bus_mem.rs +++ b/common/src/bus/data_bus_mem.rs @@ -50,17 +50,3 @@ impl MemBusData { [data[MEM_VALUE_0], data[MEM_VALUE_1]] } } - -pub struct MemCollectorInfo { - pub from_addr: u32, - pub to_addr: u32, -} - -impl MemCollectorInfo { - pub fn skip_addr(&self, addr: u32) -> bool { - if addr > self.to_addr || addr < self.from_addr { - return true; - } - false - } -} diff --git a/common/src/bus/data_bus_operation.rs b/common/src/bus/data_bus_operation.rs index a5d120917..4906a64be 100644 --- a/common/src/bus/data_bus_operation.rs +++ b/common/src/bus/data_bus_operation.rs @@ -12,6 +12,7 @@ pub const OPERATION_BUS_ID: BusId = BusId(0); /// The size of the operation data payload. pub const OPERATION_BUS_DATA_SIZE: usize = 4; // op,op_type,a,b +pub const OPERATION_PRECOMPILED_BUS_DATA_SIZE: usize = 5; // op,op_type,a,b, step // worst case: // arith_256: 3 x 256 + 2 addr = 3 * 4 + 2 = 14 @@ -34,44 +35,53 @@ const POINT_384_BITS_SIZE: usize = 2 * DATA_384_BITS_SIZE; const COMPLEX_OVER_384_BITS_SIZE: usize = 2 * DATA_384_BITS_SIZE; // use OPERATION_BUS_DATA_SIZE because a = step, b = addr -pub const OPERATION_BUS_KECCAKF_DATA_SIZE: usize = OPERATION_BUS_DATA_SIZE + 25; -pub const OPERATION_BUS_POSEIDON2_DATA_SIZE: usize = OPERATION_BUS_DATA_SIZE + 16; + +pub const OPERATION_BUS_KECCAKF_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 25; +pub const OPERATION_BUS_POSEIDON2_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 16; pub const OPERATION_BUS_SHA256F_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 3 * DATA_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 3 * DATA_256_BITS_SIZE; pub const OPERATION_BUS_ARITH_256_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 3 * DATA_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 3 * DATA_256_BITS_SIZE; pub const OPERATION_BUS_ARITH_256_MOD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 4 * DATA_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 4 * DATA_256_BITS_SIZE; pub const OPERATION_BUS_SECP256K1_ADD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_256_BITS_SIZE; pub const OPERATION_BUS_SECP256K1_DBL_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + POINT_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + POINT_256_BITS_SIZE; pub const OPERATION_BUS_BN254_CURVE_ADD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_256_BITS_SIZE; pub const OPERATION_BUS_BN254_CURVE_DBL_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + POINT_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + POINT_256_BITS_SIZE; pub const OPERATION_BUS_BN254_COMPLEX_ADD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; pub const OPERATION_BUS_BN254_COMPLEX_SUB_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; pub const OPERATION_BUS_BN254_COMPLEX_MUL_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_256_BITS_SIZE; pub const OPERATION_BUS_ARITH_384_MOD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 4 * DATA_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 5 * INDIRECTION_SIZE + 4 * DATA_384_BITS_SIZE; pub const OPERATION_BUS_BLS12_381_CURVE_ADD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_384_BITS_SIZE; pub const OPERATION_BUS_BLS12_381_CURVE_DBL_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + POINT_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + POINT_384_BITS_SIZE; pub const OPERATION_BUS_BLS12_381_COMPLEX_ADD_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; pub const OPERATION_BUS_BLS12_381_COMPLEX_SUB_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; pub const OPERATION_BUS_BLS12_381_COMPLEX_MUL_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * COMPLEX_OVER_384_BITS_SIZE; // bus_data_size + 4 params (&a, &b, cin, &c, a, b) -pub const OPERATION_BUS_ADD_256_DATA_SIZE: usize = - OPERATION_BUS_DATA_SIZE + 4 * PARAMS_SIZE + 2 * DATA_256_BITS_SIZE + SINGLE_RESULT_SIZE; +pub const OPERATION_BUS_ADD_256_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + + 4 * PARAMS_SIZE + + 2 * DATA_256_BITS_SIZE + + SINGLE_RESULT_SIZE; + +pub const DMA_ENCODED: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE; +// 5 bus_precompiled_data + encoded +pub const OPERATION_BUS_DMA_MEMCPY_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1; +// 5 bus_precompiled_data + encoded + count_eq +pub const OPERATION_BUS_DMA_MEMCMP_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2; // 4 bus_data + 5 addr + 4 x 384 = 4 + 5 + 4 * 6 = 33 pub const MAX_OPERATION_DATA_SIZE: usize = OPERATION_BUS_ARITH_384_MOD_DATA_SIZE; @@ -88,6 +98,9 @@ pub const A: usize = 2; /// Index of the `b` value in the operation data payload. pub const B: usize = 3; +/// Index of the `STEP` value in the operation data payload (only for precompiled operations). +pub const STEP: usize = 4; + /// Type alias for operation data payload. pub type OperationData = [D; OPERATION_BUS_DATA_SIZE]; @@ -111,6 +124,8 @@ pub type OperationBls12_381ComplexAddData = [D; OPERATION_BUS_BLS12_381_COMPL pub type OperationBls12_381ComplexSubData = [D; OPERATION_BUS_BLS12_381_COMPLEX_SUB_DATA_SIZE]; pub type OperationBls12_381ComplexMulData = [D; OPERATION_BUS_BLS12_381_COMPLEX_MUL_DATA_SIZE]; pub type OperationAdd256Data = [D; OPERATION_BUS_ADD_256_DATA_SIZE]; +pub type OperationDmaMemCpyData = [D; OPERATION_BUS_DMA_MEMCPY_DATA_SIZE]; +pub type OperationDmaMemCmpData = [D; OPERATION_BUS_DMA_MEMCMP_DATA_SIZE]; pub enum ExtOperationData { OperationData(OperationData), @@ -133,6 +148,8 @@ pub enum ExtOperationData { OperationBls12_381ComplexSubData(OperationBls12_381ComplexSubData), OperationBls12_381ComplexMulData(OperationBls12_381ComplexMulData), OperationAdd256Data(OperationAdd256Data), + OperationDmaMemCpyData(OperationDmaMemCpyData), + OperationDmaMemCmpData(OperationDmaMemCmpData), } const KECCAK_OP: u8 = ZiskOp::Keccak.code(); @@ -154,6 +171,8 @@ const BLS12_381_COMPLEX_ADD_OP: u8 = ZiskOp::Bls12_381ComplexAdd.code(); const BLS12_381_COMPLEX_SUB_OP: u8 = ZiskOp::Bls12_381ComplexSub.code(); const BLS12_381_COMPLEX_MUL_OP: u8 = ZiskOp::Bls12_381ComplexMul.code(); const ADD256_OP: u8 = ZiskOp::Add256.code(); +const DMA_MEMCPY_OP: u8 = ZiskOp::DmaMemCpy.code(); +const DMA_MEMCMP_OP: u8 = ZiskOp::DmaMemCmp.code(); // impl> TryFrom<&[D]> for ExtOperationData { impl> TryFrom<&[D]> for ExtOperationData { @@ -260,6 +279,16 @@ impl> TryFrom<&[D]> for ExtOperationData { data.try_into().map_err(|_| "Invalid OperationAdd256Data size")?; Ok(ExtOperationData::OperationAdd256Data(array)) } + DMA_MEMCPY_OP => { + let array: OperationDmaMemCpyData = + data.try_into().map_err(|_| "Invalid OperationDmaMemCpyData size")?; + Ok(ExtOperationData::OperationDmaMemCpyData(array)) + } + DMA_MEMCMP_OP => { + let array: OperationDmaMemCmpData = + data.try_into().map_err(|_| "Invalid OperationDmaMemCmpData size")?; + Ok(ExtOperationData::OperationDmaMemCmpData(array)) + } _ => { let array: OperationData = data.try_into().map_err(|_| "Invalid OperationData size")?; @@ -293,9 +322,9 @@ impl OperationBusData { op_type: PayloadType, a: u64, b: u64, - pending: &mut VecDeque<(BusId, Vec)>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) { - pending.push_back((OPERATION_BUS_ID, vec![op as u64, op_type, a, b])); + pending.push_back((OPERATION_BUS_ID, vec![op as u64, op_type, a, b], Vec::new())); } /// Creates operation data from a `ZiskInst` instruction and its context. @@ -312,21 +341,26 @@ impl OperationBusData { let b = if inst.m32 { ctx.b & 0xffff_ffff } else { ctx.b }; let op = inst.op as u64; let op_type = inst.op_type as u64; + let step = ctx.step; match inst.op_type { ZiskOperationType::Keccak => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationKeccakData(data) } ZiskOperationType::Sha256 => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationSha256Data(data) } @@ -343,72 +377,90 @@ impl OperationBusData { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationArith256Data(data) } ARITH256_MOD_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationArith256ModData(data) } SECP256K1_ADD_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationSecp256k1AddData(data) } SECP256K1_DBL_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationSecp256k1DblData(data) } BN254_CURVE_ADD_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254CurveAddData(data) } BN254_CURVE_DBL_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254CurveDblData(data) } BN254_COMPLEX_ADD_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254ComplexAddData(data) } BN254_COMPLEX_SUB_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254ComplexSubData(data) } BN254_COMPLEX_MUL_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254ComplexMulData(data) } _ => ExtOperationData::OperationData([op, op_type, a, b]), @@ -419,24 +471,30 @@ impl OperationBusData { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationArith384ModData(data) } BLS12_381_CURVE_ADD_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381CurveAddData(data) } BLS12_381_CURVE_DBL_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381CurveDblData(data) } BLS12_381_COMPLEX_ADD_OP => { @@ -444,8 +502,10 @@ impl OperationBusData { uninit_array::() .assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381ComplexAddData(data) } BLS12_381_COMPLEX_SUB_OP => { @@ -453,8 +513,10 @@ impl OperationBusData { uninit_array::() .assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381ComplexSubData(data) } BLS12_381_COMPLEX_MUL_OP => { @@ -462,8 +524,10 @@ impl OperationBusData { uninit_array::() .assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381ComplexMulData(data) } _ => ExtOperationData::OperationData([op, op_type, a, b]), @@ -473,12 +537,37 @@ impl OperationBusData { ADD256_OP => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationAdd256Data(data) } _ => ExtOperationData::OperationData([op, op_type, a, b]), }, + ZiskOperationType::Dma => match inst.op { + DMA_MEMCPY_OP => { + let mut data = unsafe { + uninit_array::().assume_init() + }; + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationDmaMemCpyData(data) + } + DMA_MEMCMP_OP => { + let mut data = unsafe { + uninit_array::().assume_init() + }; + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationDmaMemCmpData(data) + } + _ => ExtOperationData::OperationData([op, op_type, a, b]), + }, _ => ExtOperationData::OperationData([op, op_type, a, b]), } @@ -494,20 +583,23 @@ impl OperationBusData { let b = if inst.m32 { ctx.b & 0xffff_ffff } else { ctx.b }; let op = inst.op as u64; let op_type = inst.op_type as u64; + let step = ctx.step; match inst.op_type { ZiskOperationType::Keccak => { debug_assert_eq!(ctx.precompiled.input_data.len(), 25); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..OPERATION_BUS_KECCAKF_DATA_SIZE] + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..OPERATION_BUS_KECCAKF_DATA_SIZE] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..OPERATION_BUS_KECCAKF_DATA_SIZE] } ZiskOperationType::Sha256 => { debug_assert_eq!(ctx.precompiled.input_data.len(), 14); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..OPERATION_BUS_SHA256F_DATA_SIZE] + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..OPERATION_BUS_SHA256F_DATA_SIZE] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..OPERATION_BUS_SHA256F_DATA_SIZE] } @@ -522,65 +614,83 @@ impl OperationBusData { ZiskOperationType::ArithEq => match inst.op { ARITH256_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } ARITH256_MOD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } SECP256K1_ADD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } SECP256K1_DBL_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BN254_CURVE_ADD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BN254_CURVE_DBL_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BN254_COMPLEX_ADD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BN254_COMPLEX_SUB_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BN254_COMPLEX_MUL_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } @@ -592,44 +702,56 @@ impl OperationBusData { ZiskOperationType::ArithEq384 => match inst.op { ARITH384_MOD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BLS12_381_CURVE_ADD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BLS12_381_CURVE_DBL_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BLS12_381_COMPLEX_ADD_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BLS12_381_COMPLEX_SUB_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } BLS12_381_COMPLEX_MUL_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } @@ -640,9 +762,26 @@ impl OperationBusData { }, ZiskOperationType::BigInt => match inst.op { ADD256_OP => { - let len = OPERATION_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] + .copy_from_slice(&ctx.precompiled.input_data); + &buffer[..len] + } + _ => { buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..len] + &buffer[..OPERATION_BUS_DATA_SIZE] + } + }, + ZiskOperationType::Dma => match inst.op { + DMA_MEMCPY_OP | DMA_MEMCMP_OP => { + let len = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..len] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } @@ -689,6 +828,8 @@ impl OperationBusData { ExtOperationData::OperationBls12_381ComplexSubData(d) => d[OP] as u8, ExtOperationData::OperationBls12_381ComplexMulData(d) => d[OP] as u8, ExtOperationData::OperationAdd256Data(d) => d[OP] as u8, + ExtOperationData::OperationDmaMemCpyData(d) => d[OP] as u8, + ExtOperationData::OperationDmaMemCmpData(d) => d[OP] as u8, } } @@ -722,6 +863,8 @@ impl OperationBusData { ExtOperationData::OperationBls12_381ComplexSubData(d) => d[OP_TYPE], ExtOperationData::OperationBls12_381ComplexMulData(d) => d[OP_TYPE], ExtOperationData::OperationAdd256Data(d) => d[OP_TYPE], + ExtOperationData::OperationDmaMemCpyData(d) => d[OP_TYPE], + ExtOperationData::OperationDmaMemCmpData(d) => d[OP_TYPE], } } @@ -755,6 +898,8 @@ impl OperationBusData { ExtOperationData::OperationBls12_381ComplexSubData(d) => d[A], ExtOperationData::OperationBls12_381ComplexMulData(d) => d[A], ExtOperationData::OperationAdd256Data(d) => d[A], + ExtOperationData::OperationDmaMemCpyData(d) => d[A], + ExtOperationData::OperationDmaMemCmpData(d) => d[A], } } @@ -788,6 +933,8 @@ impl OperationBusData { ExtOperationData::OperationBls12_381ComplexSubData(d) => d[B], ExtOperationData::OperationBls12_381ComplexMulData(d) => d[B], ExtOperationData::OperationAdd256Data(d) => d[B], + ExtOperationData::OperationDmaMemCpyData(d) => d[B], + ExtOperationData::OperationDmaMemCmpData(d) => d[B], } } } diff --git a/common/src/component/component_builder.rs b/common/src/component/component_builder.rs index 83c6da267..7ad5fc3bd 100644 --- a/common/src/component/component_builder.rs +++ b/common/src/component/component_builder.rs @@ -4,7 +4,7 @@ //! This trait provides methods to create counters, planners, input collectors, and optional //! input generators, enabling flexible and modular integration of components. -use crate::{BusDevice, BusDeviceMetrics, Instance, InstanceCtx, PayloadType, Plan, Planner}; +use crate::{Instance, InstanceCtx, Plan, Planner}; use fields::PrimeField64; use proofman_common::ProofCtx; @@ -15,12 +15,6 @@ use proofman_common::ProofCtx; /// * `F` - A type that implements the `PrimeField64` trait, representing the field over which /// operations are performed. pub trait ComponentBuilder: Send + Sync { - /// Builds and returns a bus device counter for monitoring metrics. - /// - /// # Returns - /// A boxed implementation of `BusDeviceMetrics`, capable of tracking bus data. - fn build_counter(&self) -> Option>; - /// Builds a planner for planning execution instances. /// /// # Returns @@ -40,16 +34,4 @@ pub trait ComponentBuilder: Send + Sync { /// # Arguments /// * `ictx` - The instance context used to create the instance. fn build_instance(&self, ictx: InstanceCtx) -> Box>; - - /// Optionally creates an input generator for producing inputs to be sent back to the bus. - /// - /// # Returns - /// An `Option` containing a boxed implementation of `BusDevice`, or `None` if the component - /// does not support input generation. - /// - /// # Default Implementation - /// Returns `None` by default, indicating no input generator is provided. - fn build_inputs_generator(&self) -> Option>> { - None - } } diff --git a/common/src/component/component_counter.rs b/common/src/component/component_counter.rs index 133494f1a..5bfbede94 100644 --- a/common/src/component/component_counter.rs +++ b/common/src/component/component_counter.rs @@ -6,7 +6,7 @@ use std::{ any::Any, fmt::Debug, ops::{Add, AddAssign}, - sync::{atomic::AtomicU32, Arc}, + sync::{atomic::AtomicU64, Arc}, }; use zisk_core::{ROM_ADDR, ROM_ENTRY}; @@ -94,10 +94,10 @@ impl AddAssign<&Counter> for Counter { #[derive(Debug)] pub struct CounterStats { /// Shared biod instruction counter for monitoring ROM operations. - pub bios_inst_count: Arc>, + pub bios_inst_count: Arc>, /// Shared program instruction counter for monitoring ROM operations. - pub prog_inst_count: Arc>, + pub prog_inst_count: Arc>, /// The PC of the last executed instruction. pub end_pc: u64, @@ -107,7 +107,7 @@ pub struct CounterStats { } impl CounterStats { - pub fn new(entry_inst_count: Arc>, inst_count: Arc>) -> Self { + pub fn new(entry_inst_count: Arc>, inst_count: Arc>) -> Self { CounterStats { bios_inst_count: entry_inst_count, prog_inst_count: inst_count, @@ -124,7 +124,7 @@ impl CounterStats { /// * `num` - The number of instructions executed at the given PC. /// * `end` - A flag indicating if this is the final instruction in the execution. #[inline(always)] - pub fn update(&mut self, pc: u64, step: u64, num: u32, end: bool) { + pub fn update(&mut self, pc: u64, step: u64, num: u64, end: bool) { if pc < ROM_ADDR { let addr = ((pc - ROM_ENTRY) as usize) >> 2; self.bios_inst_count[addr].fetch_add(num, std::sync::atomic::Ordering::Relaxed); diff --git a/common/src/component/component_instance.rs b/common/src/component/component_instance.rs index 0d00bf3a9..13f601ceb 100644 --- a/common/src/component/component_instance.rs +++ b/common/src/component/component_instance.rs @@ -129,6 +129,10 @@ macro_rules! table_instance { pub fn new(table_sm: Arc<$TableSM>, ictx: InstanceCtx, bus_id: BusId) -> Self { Self { table_sm, ictx, bus_id } } + + pub fn process_data(&mut self, _bus_id: &BusId, _data: &[u64]) -> bool { + true + } } impl Instance for $InstanceName { @@ -180,19 +184,6 @@ macro_rules! table_instance { } impl BusDevice for $InstanceName { - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - true - } - fn bus_id(&self) -> Vec { - vec![self.bus_id] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self @@ -247,6 +238,10 @@ macro_rules! table_instance_array { pub fn new(table_sm: Arc<$TableSM>, ictx: InstanceCtx, bus_id: BusId) -> Self { Self { table_sm, ictx, bus_id } } + + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { + true + } } impl Instance for $InstanceName { @@ -307,20 +302,6 @@ macro_rules! table_instance_array { } impl BusDevice for $InstanceName { - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - true - } - - fn bus_id(&self) -> Vec { - vec![self.bus_id] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/common/src/component/component_planner.rs b/common/src/component/component_planner.rs index 03a14879c..e23fd3e99 100644 --- a/common/src/component/component_planner.rs +++ b/common/src/component/component_planner.rs @@ -53,6 +53,33 @@ impl CollectSkipper { true } + /// Determines how many rows of the current instruction should be skipped. This method is useful + /// when an instruction spans multiple rows. + /// + /// # Returns + /// number of rows to skip if the instruction should be skipped, `0` otherwise. + #[inline(always)] + pub fn rows_to_skip(&mut self, rows: u64) -> u64 { + if !self.skipping { + return 0; + } + + if self.skip == 0 || self.skipped >= self.skip { + self.skipping = false; + return 0; + } + + if (self.skipped + rows) >= self.skip { + let result = self.skip - self.skipped; + self.skipped = self.skip; + self.skipping = false; + return result; + } + + self.skipped += rows; + rows + } + #[inline(always)] pub fn should_skip_query(&mut self, apply: bool) -> bool { if !self.skipping { @@ -146,6 +173,65 @@ impl CollectCounter { true } + /// Determines whether the current instruction should be skipped. + /// + /// Behavior: + /// 1. Skip first `initial_skip` elements + /// 2. Don't skip next `collect_count` elements + /// 3. Skip all remaining elements + /// + /// Arguments: + /// * `rows` - Number of rows in the current instruction + /// + /// # Returns + /// `Some((skip, count))` where: + /// - `skip` is the number of rows to skip + /// - `count` is the number of rows to collect + /// `None` if all rows should be skipped. + #[inline(always)] + pub fn should_process(&mut self, rows: u32) -> Option<(u32, u32)> { + // Phase 1: Initial skipping + let mut skip = 0; + let mut rows = rows; + if self.initial_skipping { + if self.initial_skip == 0 { + self.initial_skipping = false; + } else if (self.initial_skipped + rows) >= self.initial_skip { + skip = self.initial_skip - self.initial_skipped; + rows -= skip; + self.initial_skipped = self.initial_skip; + self.initial_skipping = false; + // skip only a part of rows, at this point need + // to calculate count of rows not skipped + if rows == 0 { + return None; + } + } else { + self.initial_skipped += rows; + // skip all rows + return None; + } + } + + if self.final_skip_phase { + // Phase 3: Skip all remaining elements + None + } else if (self.collected + rows) >= self.collect_count { + // Phase 2: Collecting (not skipping) + let rows_to_collect = self.collect_count - self.collected; + self.final_skip_phase = true; + self.collected = self.collect_count; + if rows_to_collect == 0 { + None + } else { + Some((skip, rows_to_collect)) + } + } else { + self.collected += rows; + Some((skip, rows)) + } + } + /// Reset to initial state with new parameters pub fn reset(&mut self, initial_skip: u32, collect_count: u32) { self.initial_skip = initial_skip; diff --git a/common/src/io/stdin/file.rs b/common/src/io/stdin/file.rs index df9f77267..56e78a120 100644 --- a/common/src/io/stdin/file.rs +++ b/common/src/io/stdin/file.rs @@ -1,6 +1,7 @@ //! A file-based implementation of ZiskStdin. //! This module provides functionality to read input data from a file. +use serde::Serialize; use std::fs::{self, File}; use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; @@ -45,13 +46,13 @@ impl ZiskIO for ZiskFileStdin { self.reader.read_exact(buffer).expect("Failed to read into buffer"); } - fn write_serialized(&mut self, _data: &[u8]) { + fn write(&mut self, _data: &T) { // This is a read-only stdin implementation // Writing is not supported for file-based stdin panic!("Write operations are not supported for FileStdin"); } - fn write_bytes(&mut self, _data: &[u8]) { + fn write_slice(&mut self, _data: &[u8]) { // This is a read-only stdin implementation // Writing is not supported for file-based stdin panic!("Write operations are not supported for FileStdin"); diff --git a/common/src/io/stdin/memory.rs b/common/src/io/stdin/memory.rs index d3ad9ce52..92faa840c 100644 --- a/common/src/io/stdin/memory.rs +++ b/common/src/io/stdin/memory.rs @@ -1,3 +1,4 @@ +use serde::Serialize; use std::io::{Cursor, Read}; use crate::io::ZiskIO; @@ -40,11 +41,15 @@ impl ZiskIO for ZiskMemoryStdin { self.cursor.read_exact(buffer).expect("Failed to read into buffer from memory"); } - fn write_serialized(&mut self, _data: &[u8]) { - panic!("Write operations are not supported for ZiskMemoryStdin"); + fn write(&mut self, data: &T) { + let mut tmp = Vec::new(); + bincode::serialize_into(&mut tmp, data).expect("Failed to serialize data into memory"); + self.data.extend_from_slice(&tmp); + self.cursor.get_mut().extend_from_slice(&tmp); } - fn write_bytes(&mut self, _data: &[u8]) { - panic!("Write operations are not supported for ZiskMemoryStdin"); + fn write_slice(&mut self, data: &[u8]) { + self.data.extend_from_slice(data); + self.cursor.get_mut().extend_from_slice(data); } } diff --git a/common/src/io/stdin/null.rs b/common/src/io/stdin/null.rs index 4348ebbc3..80b262ded 100644 --- a/common/src/io/stdin/null.rs +++ b/common/src/io/stdin/null.rs @@ -1,6 +1,7 @@ use tracing::warn; use crate::io::ZiskIO; +use serde::Serialize; pub struct ZiskNullStdin; @@ -10,10 +11,10 @@ impl ZiskIO for ZiskNullStdin { } fn read_slice(&mut self, _slice: &mut [u8]) {} fn read_into(&mut self, _buffer: &mut [u8]) {} - fn write_serialized(&mut self, _data: &[u8]) { + fn write(&mut self, _data: &T) { warn!("NullStdin does not support writing"); } - fn write_bytes(&mut self, _data: &[u8]) { + fn write_slice(&mut self, _data: &[u8]) { warn!("NullStdin does not support writing"); } } diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index ef5ec9a82..28c632ec7 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -1,4 +1,5 @@ use crate::io::{ZiskFileStdin, ZiskMemoryStdin, ZiskNullStdin}; +use serde::Serialize; use std::path::Path; use anyhow::Result; @@ -14,10 +15,10 @@ pub trait ZiskIO: Send + Sync { fn read_into(&mut self, buffer: &mut [u8]); /// Write a serialized value to the buffer. - fn write_serialized(&mut self, data: &[u8]); + fn write(&mut self, data: &T); /// Write a slice of bytes to the buffer. - fn write_bytes(&mut self, data: &[u8]); + fn write_slice(&mut self, data: &[u8]); } pub enum ZiskIOVariant { @@ -51,19 +52,19 @@ impl ZiskIO for ZiskIOVariant { } } - fn write_serialized(&mut self, data: &[u8]) { + fn write(&mut self, data: &T) { match self { - ZiskIOVariant::File(file_stdin) => file_stdin.write_serialized(data), - ZiskIOVariant::Null(null_stdin) => null_stdin.write_serialized(data), - ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write_serialized(data), + ZiskIOVariant::File(file_stdin) => file_stdin.write(data), + ZiskIOVariant::Null(null_stdin) => null_stdin.write(data), + ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write(data), } } - fn write_bytes(&mut self, data: &[u8]) { + fn write_slice(&mut self, data: &[u8]) { match self { - ZiskIOVariant::File(file_stdin) => file_stdin.write_bytes(data), - ZiskIOVariant::Null(null_stdin) => null_stdin.write_bytes(data), - ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write_bytes(data), + ZiskIOVariant::File(file_stdin) => file_stdin.write_slice(data), + ZiskIOVariant::Null(null_stdin) => null_stdin.write_slice(data), + ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write_slice(data), } } } @@ -85,16 +86,27 @@ impl ZiskIO for ZiskStdin { self.io.read_into(buffer) } - fn write_serialized(&mut self, data: &[u8]) { - self.io.write_serialized(data) + fn write(&mut self, data: &T) { + self.io.write(data) } - fn write_bytes(&mut self, data: &[u8]) { - self.io.write_bytes(data) + fn write_slice(&mut self, data: &[u8]) { + self.io.write_slice(data) + } +} + +impl Default for ZiskStdin { + fn default() -> Self { + Self::new() } } impl ZiskStdin { + /// Create new memory-based stdin + pub fn new() -> Self { + Self { io: ZiskIOVariant::Memory(ZiskMemoryStdin::new(Vec::new())) } + } + /// Create a null stdin (no input) pub fn null() -> Self { Self { io: ZiskIOVariant::Null(ZiskNullStdin) } diff --git a/common/src/lib.rs b/common/src/lib.rs index 1d8f3634a..4e517df75 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -13,7 +13,6 @@ mod regular_counters; mod regular_planner; mod types; mod utils; -mod zisk_lib_init; pub use bus::*; pub use component::*; @@ -29,4 +28,3 @@ pub use regular_counters::*; pub use regular_planner::*; pub use types::*; pub use utils::*; -pub use zisk_lib_init::*; diff --git a/common/src/regular_counters.rs b/common/src/regular_counters.rs index 8e43e3a48..6561ee33c 100644 --- a/common/src/regular_counters.rs +++ b/common/src/regular_counters.rs @@ -2,9 +2,8 @@ //! sent over the data bus. It is designed to be reusable across multiple state machines //! and collects metrics for specified `ZiskOperationType` instructions. -use crate::MemCollectorInfo; use crate::{BusDevice, BusId, Counter, ExtOperationData, Metrics, OperationBusData}; -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; use zisk_core::ZiskOperationType; /// The `RegularCounters` struct represents a generic counter that monitors and measures @@ -50,6 +49,24 @@ impl RegularCounters { } None } + + /// Processes data received on the bus, updating counters. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { + debug_assert!(*bus_id == self.bus_id); + + self.measure(data); + + true + } } impl Metrics for RegularCounters { @@ -111,39 +128,6 @@ impl Add for RegularCounters { } impl BusDevice for RegularCounters { - /// Processes data received on the bus, updating counters. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus sending the data. - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - debug_assert!(*bus_id == self.bus_id); - - self.measure(data); - - true - } - - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![self.bus_id] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/common/src/types.rs b/common/src/types.rs index 4bc9c8291..89aa18db9 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::time::Instant; /// Type representing a chunk identifier. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -63,3 +64,30 @@ impl fmt::Display for SegmentId { write!(f, "{}", self.0) } } + +#[derive(Debug, Default, Clone)] +pub struct ZiskExecutionResult { + pub steps: u64, +} + +impl ZiskExecutionResult { + pub fn new(executed_steps: u64) -> Self { + Self { steps: executed_steps } + } +} + +#[derive(Debug, Clone)] +pub struct Stats { + pub airgroup_id: usize, + pub air_id: usize, + /// Collect start time + pub collect_start_time: Instant, + /// Collect duration in microseconds + pub collect_duration: u64, + /// Witness start time + pub witness_start_time: Instant, + /// Witness duration in microseconds + pub witness_duration: u128, + /// Number of chunks + pub num_chunks: usize, +} diff --git a/common/src/zisk_lib_init.rs b/common/src/zisk_lib_init.rs deleted file mode 100644 index 74be3de93..000000000 --- a/common/src/zisk_lib_init.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::{path::PathBuf, time::Instant}; - -use fields::PrimeField64; -use proofman_common::VerboseMode; -use witness::WitnessLibrary; - -use crate::{ - io::{StreamSource, ZiskStdin}, - ExecutorStats, -}; - -use anyhow::Result; - -#[derive(Debug, Default, Clone)] -pub struct ZiskExecutionResult { - pub steps: u64, -} - -impl ZiskExecutionResult { - pub fn new(steps: u64) -> Self { - Self { steps } - } -} - -#[derive(Debug, Clone)] -pub struct Stats { - pub airgroup_id: usize, - pub air_id: usize, - /// Collect start time - pub collect_start_time: Instant, - /// Collect duration in microseconds - pub collect_duration: u64, - /// Witness start time - pub witness_start_time: Instant, - /// Witness duration in microseconds - pub witness_duration: u128, - /// Number of chunks - pub num_chunks: usize, -} - -/// Extension trait that provides execution result access without Any boxing -pub trait ZiskWitnessLibrary { - fn set_stdin(&self, stdin: ZiskStdin); - fn set_hints_stream(&self, stream: StreamSource) -> Result<()>; - fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)>; -} - -// SUpertrait for ZiskWitnessLibrary and WitnessLibrary -pub trait ZiskLib: - WitnessLibrary + ZiskWitnessLibrary + Send + Sync -{ -} - -pub type ZiskLibInitFn = fn( - VerboseMode, - PathBuf, // Rom path - Option, // Asm path - Option, // Asm ROM path - Option, // Base port for the ASM microservices - bool, // Unlock_mapped_memory - bool, // Shared_tables - bool, // With_hints -) -> Result>, Box>; diff --git a/core/Cargo.toml b/core/Cargo.toml index cf3b890c2..5e26391c5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -27,6 +27,8 @@ serde = { workspace = true } serde_json = { workspace = true } fields = { workspace = true } sha2 = { workspace = true } +paste = { workspace = true } + indexmap = { version = "2.2.6", features = ["serde"] } json = "0.12.4" @@ -36,4 +38,5 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] } [features] default = [] +debug_dma = [] # sp = [] diff --git a/core/src/inst_context.rs b/core/src/inst_context.rs index 9b82dba6a..381c23d54 100644 --- a/core/src/inst_context.rs +++ b/core/src/inst_context.rs @@ -8,7 +8,7 @@ use crate::{ Mem, FCALL_PARAMS_MAX_SIZE, FCALL_RESULT_MAX_SIZE, REGS_IN_MAIN_TOTAL_NUMBER, ROM_ENTRY, }; -/// Zisk precompiled +/// Zisk precompiled emulation mode #[derive(Debug, Default, PartialEq, Eq)] pub enum EmulationMode { #[default] @@ -42,27 +42,27 @@ pub struct PrecompiledInstContext { #[derive(Debug)] pub struct FcallInstContext { /// Fcall parameters data - /// Maximum size is FCALL_PARAMS_MAX_SIZE u64's + /// Maximum size is FCALL_PARAMS_MAX_SIZE u64s pub parameters: [u64; FCALL_PARAMS_MAX_SIZE], - /// Indicates how many parameters u64's contain valid data + /// Indicates how many parameter u64s contain valid data pub parameters_size: u64, /// Fcall result data - /// Maximum size is FCALL_RESULT_MAX_SIZE u64's + /// Maximum size is FCALL_RESULT_MAX_SIZE u64s pub result: [u64; FCALL_RESULT_MAX_SIZE], - /// Indicates how many result u64's contain valid data + /// Indicates how many result u64s contain valid data pub result_size: u64, - /// Indicates how many result u64's have been read using fcall_get() + /// Indicates how many result u64s have been read using fcall_get() pub result_got: u64, } impl Default for FcallInstContext { /// Default fcall instruction context constructor fn default() -> Self { - FcallInstContext { + Self { parameters: [0; FCALL_PARAMS_MAX_SIZE], parameters_size: 0, result: [0; FCALL_RESULT_MAX_SIZE], @@ -71,7 +71,6 @@ impl Default for FcallInstContext { } } } - #[derive(Debug)] /// ZisK instruction context data container, storing the state of the execution pub struct InstContext { @@ -117,6 +116,10 @@ pub struct InstContext { /// Fcall data pub fcall: FcallInstContext, + + /// DataExt 64 bytes size. With this information it is possible to specify which variable part of the minimal trace + /// is associated with the current instruction. Used by DMA precompile. + pub data_ext_len: usize, } /// RisK instruction context implementation @@ -138,6 +141,7 @@ impl InstContext { emulation_mode: EmulationMode::default(), precompiled: PrecompiledInstContext::default(), fcall: FcallInstContext::default(), + data_ext_len: 0, } } diff --git a/core/src/mem.rs b/core/src/mem.rs index 0d4f7d711..ed91c39f5 100644 --- a/core/src/mem.rs +++ b/core/src/mem.rs @@ -141,6 +141,8 @@ pub const FLOAT_LIB_SP: u64 = 0xc0000000 - 16; // 0xbffffff0 pub const ARCH_ID_ZISK: u64 = 0xFFFEEEE; /// UART memory address; single bytes written here will be copied to the standard output pub const UART_ADDR: u64 = SYS_ADDR + 0x200; +/// Extra parameters of repcompiles are stored in fixed memory area (256 bytes => 32 parameters) +pub const EXTRA_PARAMS: u64 = SYS_ADDR + 0x0F00; /// Float registers first address pub const FREG_FIRST: u64 = SYS_ADDR + 0x1000; /// CSR memory address; contains control and status registers @@ -795,5 +797,189 @@ impl Mem { } } + #[inline(always)] + pub fn get_writeable_section(&mut self, addr: u64, count: u64) -> &mut MemSection { + if let Ok(section) = self.read_sections.binary_search_by(|section| { + if addr < section.start { + std::cmp::Ordering::Greater + } else if addr > (section.end - count) { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Equal + } + }) { + panic!( + "Mem::get_write_section() invalid addr={addr}={addr:x},count={count} write section start={:x} end={:x} is read only section", + self.read_sections[section].start, self.read_sections[section].end); + }; + + // If not found in read sections, try write section + let section = &mut self.write_section; + + // Check that the address and count fall into this section address range + if (addr < section.start) || ((addr + count) > section.end) { + panic!( + "Mem::get_section() invalid addr={addr}={addr:x},count={count} write section start={:x} end={:x}", + section.start, section.end + ); + } + section + } + + #[inline(always)] + pub fn get_readable_section(&self, addr: u64, count: u64) -> &MemSection { + let section = if let Ok(section) = self.read_sections.binary_search_by(|section| { + if addr < section.start { + std::cmp::Ordering::Greater + } else if addr > (section.end - count) { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Equal + } + }) { + &self.read_sections[section] + } else { + &self.write_section + }; + if (addr < section.start) || ((addr + count) > section.end) { + panic!( + "Mem::get_read_section() invalid addr={addr}={addr:x},count={count} read section start={:x} end={:x}", + section.start, section.end + ); + } + section + } + + #[inline(always)] + pub fn memcpy(&mut self, dst: u64, src: u64, count: u64) { + // Early return if source and destination are the same or count is zero + if dst == src || count == 0 { + return; + } + + let dst_end = dst + count; + let src_end = src + count; + let count_usize = count as usize; + + // Check if there is an overlap between source and destination + let overlaps = (dst < src_end) && (src < dst_end); + + if overlaps { + // Overlapping case: use temporary buffer to avoid data corruption + let temp_buffer: Vec = { + let src_section = self.get_readable_section(src, count); + let src_offset: usize = (src - src_section.start) as usize; + src_section.buffer[src_offset..src_offset + count_usize].to_vec() + }; + + let dst_section = self.get_writeable_section(dst, count); + let dst_offset: usize = (dst - dst_section.start) as usize; + dst_section.buffer[dst_offset..dst_offset + count_usize].copy_from_slice(&temp_buffer); + } else { + // Non-overlapping case: direct copy + // First, get a copy of the source data + let data_to_copy: Vec = { + let src_section = self.get_readable_section(src, count); + let src_offset: usize = (src - src_section.start) as usize; + src_section.buffer[src_offset..src_offset + count_usize].to_vec() + }; + + // Then, write to destination + let dst_section = self.get_writeable_section(dst, count); + let dst_offset: usize = (dst - dst_section.start) as usize; + dst_section.buffer[dst_offset..dst_offset + count_usize].copy_from_slice(&data_to_copy); + } + } + + pub fn memcpy_from_data(&mut self, dst: u64, count: u64, data: &[u64], data_offset: usize) { + // Early return if source and destination are the same or count is zero + if count == 0 { + return; + } + + let data_bytes: &[u8] = + unsafe { core::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; + + // Then, write to destination + let dst_section = self.get_writeable_section(dst, count); + let dst_offset: usize = (dst - dst_section.start) as usize; + + let count = count as usize; + let bytes = &data_bytes[data_offset..data_offset + count]; + dst_section.buffer[dst_offset..dst_offset + count].copy_from_slice(bytes); + } + + /// Reads `count` bytes from memory starting at `addr` and appends them as u64 values to `data`. + /// The data is read in 64-bit aligned chunks and pushed to the vector. + pub fn push_from_mem(&mut self, data: &mut Vec, addr: u64, count: u64) { + if count == 0 { + return; + } + + let section = self.get_readable_section(addr, count); + let addr64 = addr >> 3; + let to_addr64 = (addr + count - 1) >> 3; + let count64 = (to_addr64 - addr64 + 1) as usize; + let addr_offset: usize = (addr - section.start) as usize & !0x07; + let addr_offset64: usize = addr_offset >> 3; + + let mem64: &[u64] = unsafe { + core::slice::from_raw_parts( + section.buffer.as_ptr() as *const u64, + section.buffer.len() / 8, + ) + }; + data.extend_from_slice(&mem64[addr_offset64..addr_offset64 + count64]); + } + + pub fn memcmp(&self, a: u64, b: u64, count: u64) -> (u64, usize) { + if count == 0 { + return (0, 0); + } + + let count_usize = count as usize; + + // Get sections for both addresses + let a_section = self.get_readable_section(a, count); + let b_section = self.get_readable_section(b, count); + + let a_offset: usize = (a - a_section.start) as usize; + let b_offset: usize = (b - b_section.start) as usize; + + // Compare byte by byte + for i in 0..count_usize { + let byte_a = a_section.buffer[a_offset + i] as i8; + let byte_b = b_section.buffer[b_offset + i] as i8; + + if byte_a != byte_b { + // Sign extend the difference to 64 bits + let diff = (byte_a as i64) - (byte_b as i64); + return (diff as u64, i); + } + } + + // All bytes are equal + (0, count_usize) + } + + pub fn memdump(&self, addr: u64, count: u64) -> String { + if count == 0 { + return String::new(); + } + + let count_usize = count as usize; + + // Get section for the address range + let section = self.get_readable_section(addr, count); + let offset: usize = (addr - section.start) as usize; + + // Convert bytes to hex string + section.buffer[offset..offset + count_usize] + .iter() + .map(|byte| format!("{:02x}", byte)) + .collect::>() + .join("") + } + //pub fn get_non_aligned_data_from_required(address: u64, width: u8,) } diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 0d50c3180..2a22683a4 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -6,15 +6,15 @@ use riscv::{riscv_interpreter, RiscvInstruction}; use crate::{ convert_vector, ZiskInstBuilder, ZiskRom, ARCH_ID_CSR_ADDR, ARCH_ID_ZISK, CSR_ADDR, - FLOAT_LIB_ROM_ADDR, FLOAT_LIB_SP, FREG_F0, FREG_INST, FREG_RA, FREG_X0, INPUT_ADDR, MTVEC, - OUTPUT_ADDR, REG_X0, ROM_ENTRY, ROM_EXIT, + EXTRA_PARAMS, FLOAT_LIB_ROM_ADDR, FLOAT_LIB_SP, FREG_F0, FREG_INST, FREG_RA, FREG_X0, + INPUT_ADDR, MTVEC, OUTPUT_ADDR, REG_X0, ROM_ENTRY, ROM_EXIT, }; use std::collections::HashMap; // The CSR precompiled addresses are defined in the `ZiskOS` `ziskos/entrypoint/src` files // because legacy versions of Rust do not support constant parameters in `asm!` macros. -const CSR_PRECOMPILED: [&str; 19] = [ +const CSR_PRECOMPILED: [&str; 21] = [ "keccak", "arith256", "arith256_mod", @@ -33,6 +33,8 @@ const CSR_PRECOMPILED: [&str; 19] = [ "bls12_381_complex_sub", "bls12_381_complex_mul", "add256", + "dma_memcpy", + "dma_memcmp", "poseidon2", ]; const CSR_PRECOMPILED_ADDR_START: u32 = 0x800; @@ -56,13 +58,23 @@ const FLOAT_HANDLER_RETURN_ADDR: u64 = FLOAT_HANDLER_ADDR + 4 * 34; // 31 regs + pub struct Riscv2ZiskContext<'a> { /// Map of program address to ZisK instructions pub insts: &'a mut HashMap, + pub input_precompile: Option, + pub output_precompile: Option, } impl Riscv2ZiskContext<'_> { /// Converts an input RISCV instruction into a ZisK instruction and stores it into the internal /// map. C instrucions are already expanded into their equivalent RISCV instructions, so we /// only have to map them to their corresponding IMA 32-bits equivalent instructions. - pub fn convert(&mut self, riscv_instruction: &RiscvInstruction) { + /// + /// # Parameters + /// * `riscv_instruction` - The current instruction to convert + /// * `next_instructions` - Slice of the remaining instructions after the current one + pub fn convert( + &mut self, + riscv_instruction: &RiscvInstruction, + next_instructions: &[RiscvInstruction], + ) { // ZisK supports the IMAC RISC-V instruction set match riscv_instruction.inst.as_str() { // I: Base Integer Instruction Set @@ -70,9 +82,18 @@ impl Riscv2ZiskContext<'_> { // I.1. Integer Computational (Register-Register) "add" => { - if riscv_instruction.rs1 == 0 { - // rd = rs1(0) + rs2 = rs2 - self.copyb(riscv_instruction, 4, 2); + if riscv_instruction.rd == 0 && self.input_precompile == Some(0x813) { + self.create_register_op(riscv_instruction, "dma_memcpy", 4); + } else if riscv_instruction.rd == 10 && self.input_precompile == Some(0x814) { + self.create_register_op(riscv_instruction, "dma_memcmp", 4); + } else if riscv_instruction.rs1 == 0 { + if !next_instructions.is_empty() { + // rd = rs1(0) + rs2 = rs2 followed by ret + self.copyb(riscv_instruction, 4, 2); + } else { + // rd = rs1(0) + rs2 = rs2 + self.copyb(riscv_instruction, 4, 2); + } } else if riscv_instruction.rs2 == 0 { // rd = rs1 + rs2(0) = rs1 self.copyb(riscv_instruction, 4, 1); @@ -768,7 +789,7 @@ impl Riscv2ZiskContext<'_> { zib.src_a("imm", 0, false); zib.src_b("imm", 0, false); zib.op("flag").unwrap(); - zib.store_ra("reg", i.rd as i64, false); + zib.store_pc("reg", i.rd as i64, false); zib.j(4, i.imm as i64); zib.verbose(&format!("auipc r{}, 0x{:x}", i.rd, i.imm)); zib.build(); @@ -890,7 +911,7 @@ impl Riscv2ZiskContext<'_> { zib.src_b("reg", i.rs1 as u64, false); zib.op("and").unwrap(); zib.set_pc(); - zib.store_ra("reg", i.rd as i64, false); + zib.store_pc("reg", i.rd as i64, false); zib.j(i.imm as i64, inst_size as i64); zib.verbose(&format!("jalr r{}, r{}, 0x{:x}", i.rd, i.rs1, i.imm)); zib.build(); @@ -913,7 +934,7 @@ impl Riscv2ZiskContext<'_> { zib.src_b("lastc", 0, false); zib.op("and").unwrap(); zib.set_pc(); - zib.store_ra("reg", i.rd as i64, false); + zib.store_pc("reg", i.rd as i64, false); zib.j(0, inst_size as i64 - 1); zib.verbose(&format!("jalr r{}, r{}, 0x{:x} ; 2/2", i.rd, i.rs1, i.imm)); zib.build(); @@ -931,7 +952,7 @@ impl Riscv2ZiskContext<'_> { zib.src_a("imm", 0, false); zib.src_b("imm", 0, false); zib.op("flag").unwrap(); - zib.store_ra("reg", i.rd as i64, false); + zib.store_pc("reg", i.rd as i64, false); zib.j(i.imm as i64, inst_size as i64); zib.verbose(&format!("jal r{}, 0x{:x}", i.rd, i.imm)); zib.build(); @@ -944,7 +965,7 @@ impl Riscv2ZiskContext<'_> { zib.src_a("imm", 0, false); zib.src_b("mem", MTVEC, false); zib.op("copyb").unwrap(); - zib.store_ra("reg", 1, false); + zib.store_pc("reg", 1, false); zib.set_pc(); zib.j(0, 4); zib.verbose("ecall"); @@ -1153,10 +1174,22 @@ impl Riscv2ZiskContext<'_> { zib.src_b("reg", i.rs1 as u64, false); zib.j(4, 4); if (CSR_PRECOMPILED_ADDR_START..=CSR_PRECOMPILED_ADDR_END).contains(&i.csr) { - zib.src_a("step", 0, false); - let precompiled = CSR_PRECOMPILED[(i.csr - CSR_PRECOMPILED_ADDR_START) as usize]; - zib.op(precompiled).unwrap(); - zib.verbose(precompiled); + match i.csr { + 0x813 | 0x814 => { + self.output_precompile = Some(i.csr); + zib.src_a("imm", 0, false); + zib.op("copyb").unwrap(); + zib.store("mem", EXTRA_PARAMS as i64, false, false); + zib.verbose("param"); + } + _ => { + let precompiled = + CSR_PRECOMPILED[(i.csr - CSR_PRECOMPILED_ADDR_START) as usize]; + zib.src_a("imm", 0, false); + zib.op(precompiled).unwrap(); + zib.verbose(precompiled); + } + } } else if (CSR_FCALL_PARAM_ADDR_START..=CSR_FCALL_PARAM_ADDR_END).contains(&i.csr) { let words = CSR_FCALL_PARAM_OFFSET_TO_WORDS[(i.csr - CSR_FCALL_PARAM_ADDR_START) as usize]; @@ -1195,7 +1228,7 @@ impl Riscv2ZiskContext<'_> { self.insts.insert(rom_address, zib); } else if i.csr == CSR_PRECOMPILED_ADD256 { let mut zib = ZiskInstBuilder::new_from_riscv(rom_address, i.inst.clone()); - zib.src_a("step", 0, false); + zib.src_a("imm", 0, false); zib.src_b("reg", i.rs1 as u64, false); zib.op("add256").unwrap(); zib.verbose("add256"); @@ -1697,15 +1730,49 @@ pub fn add_zisk_code(rom: &mut ZiskRom, addr: u64, data: &[u8]) { let riscv_instructions = riscv_interpreter(addr, &code_vector); // Create a context to convert RISCV instructions to ZisK instructions, using rom.insts - let mut ctx = Riscv2ZiskContext { insts: &mut rom.insts }; + let mut ctx = Riscv2ZiskContext { + insts: &mut rom.insts, + input_precompile: None, + output_precompile: None, + }; + + // for (i, riscv_instruction) in riscv_instructions.iter().enumerate() { + // println!("RISCV#{i} 0x{:08X}", riscv_instruction.rom_address); + // } + // let zisk_memcmp_index = + // riscv_instructions.iter().position(|inst| inst.rom_address == 0x80236edc); + // let zisk_memcmp_index: Option = None; // For all RISCV instructions - for riscv_instruction in riscv_instructions { + for (i, riscv_instruction) in riscv_instructions.iter().enumerate() { //print!("add_zisk_code() converting RISCV instruction={}\n", // riscv_instruction.to_string()); + // if riscv_instructions[i].rom_address >= 0x80267b28 + // && riscv_instructions[i].rom_address <= 0x80267b30 + // { + // if let Some(zisk_memcmp_index) = zisk_memcmp_index { + // // Get slice of remaining instructions after current one + // let index_offset = (riscv_instructions[i].rom_address - 0x80267b28) as usize >> 2; + // let next_instructions = + // &riscv_instructions[(zisk_memcmp_index + index_offset + 1)..]; + + // let mut instruction = riscv_instructions[zisk_memcmp_index + index_offset].clone(); + // instruction.rom_address = riscv_instructions[i].rom_address; + + // // Convert RICV instruction to ZisK instruction and store it in rom.insts + // ctx.convert(&instruction, next_instructions); + // continue; + // //print!(" to: {}", ctx.insts.iter().last().) + // } + // } + + // Get slice of remaining instructions after current one + let next_instructions = &riscv_instructions[(i + 1)..]; // Convert RICV instruction to ZisK instruction and store it in rom.insts - ctx.convert(&riscv_instruction); + ctx.input_precompile = ctx.output_precompile; + ctx.output_precompile = None; + ctx.convert(riscv_instruction, next_instructions); //print!(" to: {}", ctx.insts.iter().last().) } } @@ -1902,7 +1969,7 @@ pub fn add_entry_exit_jmp(rom: &mut ZiskRom, addr: u64) { zib.src_b("imm", addr, false); zib.op("copyb").unwrap(); zib.set_pc(); - zib.store_ra("reg", 1, false); + zib.store_pc("reg", 1, false); zib.j(0, 4); zib.verbose(&format!("CALL to entry: 0x{addr:08x}")); zib.build(); diff --git a/core/src/zisk_definitions.rs b/core/src/zisk_definitions.rs index 02bf67b3a..2c232d29b 100644 --- a/core/src/zisk_definitions.rs +++ b/core/src/zisk_definitions.rs @@ -1,7 +1,7 @@ //! This module contains constant definitions used by other modules and crates. -pub const DEFAULT_MAX_STEPS: u64 = 0xffffffff; -pub const DEFAULT_MAX_STEPS_STR: &str = "4294967295"; // 2^32 - 1 +pub const DEFAULT_MAX_STEPS: u64 = 0xF_FFFF_FFFF; +pub const DEFAULT_MAX_STEPS_STR: &str = "68719476735"; // 2^36 - 1 pub const CHUNK_SIZE_BITS: usize = 18; pub const CHUNK_SIZE: u64 = 1 << CHUNK_SIZE_BITS; diff --git a/core/src/zisk_inst.rs b/core/src/zisk_inst.rs index 155d55124..765352cbc 100644 --- a/core/src/zisk_inst.rs +++ b/core/src/zisk_inst.rs @@ -83,6 +83,7 @@ pub enum ZiskOperationType { ArithEq, ArithEq384, BigInt, // Note: Add new core operations here + Dma, // Note: To add extra params to precompiles calls // ZisK Free Input Operations FcallParam, Fcall, @@ -103,7 +104,7 @@ pub const ARITH_EQ_384_OP_TYPE_ID: u32 = ZiskOperationType::ArithEq384 as u32; pub const BIG_INT_OP_TYPE_ID: u32 = ZiskOperationType::BigInt as u32; pub const FCALL_PARAM_OP_TYPE_ID: u32 = ZiskOperationType::FcallParam as u32; pub const FCALL_OP_TYPE_ID: u32 = ZiskOperationType::Fcall as u32; -pub const FCALL_GET_OP_TYPE_ID: u32 = ZiskOperationType::FcallGet as u32; +pub const DMA_OP_TYPE_ID: u32 = ZiskOperationType::Dma as u32; /// ZisK instruction definition /// @@ -115,11 +116,12 @@ pub const FCALL_GET_OP_TYPE_ID: u32 = ZiskOperationType::FcallGet as u32; #[derive(Debug, Clone)] pub struct ZiskInst { pub paddr: u64, - pub store_ra: bool, + pub store_pc: bool, pub store_use_sp: bool, pub store: u64, pub store_offset: i64, pub set_pc: bool, + pub op_with_step: bool, // #[cfg(feature = "sp")] // pub set_sp: bool, pub ind_width: u64, @@ -152,11 +154,12 @@ impl Default for ZiskInst { fn default() -> Self { Self { paddr: 0, - store_ra: false, + store_pc: false, store_use_sp: false, store: 0, store_offset: 0, set_pc: false, + op_with_step: false, // #[cfg(feature = "sp")] // set_sp: false, ind_width: 0, @@ -222,8 +225,8 @@ impl ZiskInst { if self.store_offset != 0 { s += &format!(" store_offset=0x{:x}", self.store_offset as u64); } - if self.store_ra { - s += &format!(" store_ra={}", self.store_ra); + if self.store_pc { + s += &format!(" store_pc={}", self.store_pc); } if self.store_use_sp { s += &format!(" store_use_sp={}", self.store_use_sp); @@ -231,6 +234,9 @@ impl ZiskInst { if self.set_pc { s += &format!(" set_pc={}", self.set_pc); } + if self.op_with_step { + s += &format!(" op_with_step={}", self.op_with_step); + } if self.jmp_offset1 != 0 { s += &format!(" jmp_offset1={}", self.jmp_offset1); } @@ -263,11 +269,11 @@ impl ZiskInst { let flags: u64 = 1 | (((self.a_src == SRC_IMM) as u64) << 1) | (((self.a_src == SRC_MEM) as u64) << 2) - | (((self.a_src == SRC_STEP) as u64) << 3) + | ((self.op_with_step as u64) << 3) | (((self.b_src == SRC_IMM) as u64) << 4) | (((self.b_src == SRC_MEM) as u64) << 5) | ((self.is_external_op as u64) << 6) - | ((self.store_ra as u64) << 7) + | ((self.store_pc as u64) << 7) | (((self.store == STORE_MEM) as u64) << 8) | (((self.store == STORE_IND) as u64) << 9) | ((self.set_pc as u64) << 10) diff --git a/core/src/zisk_inst_builder.rs b/core/src/zisk_inst_builder.rs index 2d6edbc3d..615e5dc69 100644 --- a/core/src/zisk_inst_builder.rs +++ b/core/src/zisk_inst_builder.rs @@ -5,7 +5,7 @@ use crate::{ zisk_ops::{InvalidNameError, OpType, ZiskOp}, ZiskInst, REGS_IN_MAIN_FROM, REGS_IN_MAIN_TO, REG_FIRST, SRC_C, SRC_IMM, SRC_IND, SRC_MEM, - SRC_REG, SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, + SRC_REG, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, }; // #[cfg(feature = "sp")] @@ -44,7 +44,7 @@ impl ZiskInstBuilder { "lastc" => SRC_C, // #[cfg(feature = "sp")] // "sp" => SRC_SP, - "step" => SRC_STEP, + // "step" => SRC_STEP, _ => panic!("ZiskInstBuilder::a_src() called with invalid src={src}"), } } @@ -158,7 +158,7 @@ impl ZiskInstBuilder { } /// Sets the c store instruction attributes - pub fn store(&mut self, dst_input: &str, offset_input: i64, use_sp: bool, store_ra: bool) { + pub fn store(&mut self, dst_input: &str, offset_input: i64, use_sp: bool, store_pc: bool) { let mut dst = dst_input; let mut offset = offset_input; if dst == "reg" { @@ -170,7 +170,7 @@ impl ZiskInstBuilder { } } - self.i.store_ra = store_ra; + self.i.store_pc = store_pc; self.i.store = self.c_store(dst); if self.i.store == STORE_REG || self.i.store == STORE_MEM || self.i.store == STORE_IND { @@ -183,7 +183,7 @@ impl ZiskInstBuilder { } /// Set the store as a store ra - pub fn store_ra(&mut self, dst: &str, offset: i64, use_sp: bool) { + pub fn store_pc(&mut self, dst: &str, offset: i64, use_sp: bool) { self.store(dst, offset, use_sp, true); } @@ -207,6 +207,8 @@ impl ZiskInstBuilder { self.i.func = op.get_call_function(); self.i.op_type = op.op_type().into(); self.i.input_size = op.input_size(); + // assume that input_size > 0 implies a precompiled, and precompiled uses step on operations + self.i.op_with_step = op.input_size() > 0; Ok(()) } diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 41e12c58c..d44887723 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -9,9 +9,11 @@ #![allow(unused)] +use precompiles_helpers::DmaInfo; use ziskos::zisklib::fcall_proxy; use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; +use paste::paste; use std::{ collections::HashMap, fmt::{Debug, Display}, @@ -21,8 +23,8 @@ use std::{ use tiny_keccak::keccakf; use crate::{ - sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, M64, - REG_A0, SYS_ADDR, + sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, + EXTRA_PARAMS, M64, REG_A0, SYS_ADDR, }; use lib_c::{inverse_fn_ec_c, inverse_fp_ec_c, sqrt_fp_ec_parity_c, Fcall, FcallContext}; @@ -53,6 +55,7 @@ pub enum OpType { Fcall, ArithEq384, BigInt, + Dma, } impl From for ZiskOperationType { @@ -70,6 +73,7 @@ impl From for ZiskOperationType { OpType::Fcall => ZiskOperationType::Fcall, OpType::ArithEq384 => ZiskOperationType::ArithEq384, OpType::BigInt => ZiskOperationType::BigInt, + OpType::Dma => ZiskOperationType::Dma, } } } @@ -91,6 +95,7 @@ impl Display for OpType { Self::Fcall => write!(f, "Fcall"), Self::ArithEq384 => write!(f, "Arith384"), Self::BigInt => write!(f, "BigInt"), + Self::Dma => write!(f, "Dma"), } } } @@ -113,6 +118,7 @@ impl FromStr for OpType { "fcall" => Ok(Self::Fcall), "aeq384" => Ok(Self::ArithEq384), "bint" => Ok(Self::BigInt), + "dma" => Ok(Self::Dma), _ => Err(InvalidOpTypeError), } } @@ -148,6 +154,7 @@ impl Display for InvalidCodeError { pub trait OpStats { fn mem_align_read(&mut self, addr: u64, count: usize); fn mem_align_write(&mut self, addr: u64, count: usize); + fn add_extras(&mut self, extras: &[(u8, usize)]); } /// Stats gathering function that does nothing (used as default) @@ -156,6 +163,21 @@ pub fn ops_none(_ctx: &InstContext, _stats: &mut dyn OpStats) { // No-op implementation } +#[inline(always)] +pub fn opc_virtual(ctx: &mut InstContext) { + unimplemented!("opc_virtual: virtual operation") +} + +#[inline(always)] +pub fn op_virtual(a: u64, b: u64) -> (u64, bool) { + unimplemented!("op_virtual: virtual operation") +} + +#[inline(always)] +pub fn ops_virtual(_ctx: &InstContext, _stats: &mut dyn OpStats) { + unimplemented!("ops_virtual: virtual operation") +} + /// Internal macro used to define all ops in the [`ZiskOp`] enum macro_rules! define_ops { ( $( ($name:ident, $str_name:expr, $type:ident, $steps:expr, $code:expr, $input_size:expr, $output_size:expr, $call_fn:ident, $call_ab_fn:ident, $call_stats_fn:ident ) ),* $(,)? ) => { @@ -172,6 +194,11 @@ macro_rules! define_ops { } impl ZiskOp { + $( + paste! { + pub const [<$str_name:upper>]: u8 = $code; + } + )* /// Returns the (string) name of the operation pub const fn name(&self) -> &'static str { match self { @@ -314,6 +341,8 @@ macro_rules! define_ops { }; } +const DMA_64_ALIGNED_OPS_BY_ROW: usize = 4; + // Cost definitions: Area x Op const INTERNAL_COST: u64 = 0; const BINARY_COST: u64 = 75; @@ -328,6 +357,17 @@ const ARITH_EQ_COST: u64 = 85 * 16; const FCALL_COST: u64 = INTERNAL_COST; const ARITH_EQ_384_COST: u64 = 79 * 24; const ADD256_COST: u64 = 104; +const DMA_COST: u64 = 39; + +const DMA_64_ALIGNED_COST: u64 = 40; +const DMA_UNALIGNED_COST: u64 = 42; +const DMA_PRE_POST_COST: u64 = 84; + +// const OP_DMA_64_ALIGNED: u8 = 0xda; +// const OP_DMA_UNALIGNED: u8 = 0xdb; +// const OP_DMA_PRE: u8 = 0xdc; +// const OP_DMA_POST: u8 = 0xdd; +// const OP_DMA_CMP_BYTE: u8 = 0xde; /// Table of Zisk opcode definitions: enum, name, type, cost, code and implementation functions /// This table is the backbone of the Zisk processor, it determines what functionality is supported, @@ -390,6 +430,17 @@ define_ops! { (RemuW, "remu_w", ArithA32, ARITHA32_COST, 0xbd, 0, 0, opc_remu_w, op_remu_w, ops_none), (DivW, "div_w", ArithA32, ARITHA32_COST, 0xbe, 0, 0, opc_div_w, op_div_w, ops_none), (RemW, "rem_w", ArithA32, ARITHA32_COST, 0xbf, 0, 0, opc_rem_w, op_rem_w, ops_none), + // opcpdes 0xc0-0xcf are available + (DmaMemCpy, "dma_memcpy", Dma, DMA_COST, 0xd0, 8, 0, opc_dma_memcpy, op_dma_memcpy, ops_dma_memcpy), + (DmaMemCmp, "dma_memcmp", Dma, DMA_COST, 0xd1, 16, 0, opc_dma_memcmp, op_dma_memcmp, ops_dma_memcmp), + // opcodes 0xd2-0xd9 future reserved for dma operations (memset, memcpy256, memcmp256) + (Dma64Aligned, "_dma_64_aligned", Dma, DMA_64_ALIGNED_COST, 0xda, 8, 0, opc_virtual, op_virtual, ops_virtual), + (DmaUnaligned, "_dma_unaligned", Dma, DMA_UNALIGNED_COST, 0xdb, 8, 0, opc_virtual, op_virtual, ops_virtual), + (DmaPre, "_dma_pre", Dma, DMA_PRE_POST_COST, 0xdc, 8, 0, opc_virtual, op_virtual, ops_virtual), + (DmaPost, "_dma_post", Dma, DMA_PRE_POST_COST, 0xdd, 8, 0, opc_virtual, op_virtual, ops_virtual), + (DmaCmpByte, "_dma_cmp_byte", Dma, DMA_PRE_POST_COST, 0xde, 8, 0, opc_virtual, op_virtual, ops_virtual), + // opcodes 0xda-0xdf reserved for dma extra operations (costs) + // opcodes 0xe0,0xe1 are available (Arith384Mod, "arith384_mod", ArithEq384, ARITH_EQ_384_COST, 0xe2, 232, 48, opc_arith384_mod, op_arith384_mod, ops_arith384_mod), (Bls12_381CurveAdd, "bls12_381_curve_add", ArithEq384, ARITH_EQ_384_COST, 0xe3, 208, 96, opc_bls12_381_curve_add, op_bls12_381_curve_add, ops_bls12_381_curve_add), (Bls12_381CurveDbl, "bls12_381_curve_dbl", ArithEq384, ARITH_EQ_384_COST, 0xe4, 96, 96, opc_bls12_381_curve_dbl, op_bls12_381_curve_dbl, ops_bls12_381_curve_dbl), @@ -2365,3 +2416,271 @@ pub fn opc_halt(ctx: &mut InstContext) { ctx.c = 0; ctx.flag = false; } + +pub fn opc_dma_memcpy(ctx: &mut InstContext) { + let dst = ctx.a; + let src = ctx.b; + + match ctx.emulation_mode { + EmulationMode::Mem => { + let count = ctx.mem.read(EXTRA_PARAMS, 8); + ctx.mem.memcpy(dst, src, count); + } + EmulationMode::GenerateMemReads => { + // In generate mode we need to populate precompiled.input_data with + // information needed + let count = ctx.mem.read(EXTRA_PARAMS, 8); + ctx.precompiled.input_data.clear(); + + #[cfg(feature = "debug_dma")] + println!( + "opc_dma_memcpy 0x{dst:08X} 0x{src:08X} {count} GMR STEP:{}", + ctx.emulation_mode, ctx.step + ); + + let encoded = DmaInfo::fast_encode_memcpy(dst, src, count as usize); + ctx.precompiled.input_data.push(encoded); + + if count > 0 { + // read first dst unaligned part for dma-pre + let mut data_len = 0; + let dst64 = dst & !0x07; + // if dst64 != dst { + if DmaInfo::get_pre_count(encoded) > 0 { + let pre_data = ctx.mem.read(dst64, 8); + data_len += 1; + ctx.precompiled.input_data.push(pre_data); + } + + // read last dst unaligned part for dma-post + let to_dst = dst + count - 1; + // if to_dst & 0x07 != 0x07 { + if DmaInfo::get_post_count(encoded) > 0 { + let post_data = ctx.mem.read(to_dst & !0x07, 8); + data_len += 1; + // println!("ADDING_POST_DATA 0x{:08X} 0x{post_data:016X}", to_dst & !0x07); + ctx.precompiled.input_data.push(post_data); + } + + // read all source 64-words + let src64 = src & !0x07; + let to_src64 = (src + count - 1) & !0x07; + + let src64_count = (to_src64 - src64 + 8) >> 3; + ctx.mem.push_from_mem(&mut ctx.precompiled.input_data, src64, src64_count * 8); + data_len += src64_count; + #[cfg(feature = "debug_dma")] + println!( + "PRECOMPILED.INPUT_DATA: [{}] data_len:{data_len}", + ctx.precompiled + .input_data + .iter() + .map(|x| format!("0x{x:016X}")) + .collect::>() + .join(",") + ); + assert_eq!(data_len as usize, DmaInfo::get_data_size(encoded)); + + ctx.mem.memcpy(dst, src, count); + } + ctx.precompiled.output_data.clear(); + + ctx.precompiled.step = ctx.step; + } + EmulationMode::ConsumeMemReads => { + let encoded = ctx.precompiled.input_data[0]; + let count = DmaInfo::get_count(encoded); + #[cfg(feature = "debug_dma")] + println!( + "opc_dma_memcpy 0x{dst:08X} 0x{src:08X} {count} CMR STEP:{} DATA_EXT_LEN:{}", + ctx.step, + DmaInfo::get_data_size(encoded) + ); + ctx.data_ext_len = DmaInfo::get_data_size(encoded); + } + } + ctx.c = 0; + ctx.flag = false; +} + +/// Unimplemented. Arith256 can only be called from the system call context via InstContext. +/// This is provided just for completeness. +#[inline(always)] +pub fn op_dma_memcpy(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_dma_memcpy() is not implemented"); +} + +#[inline(always)] +pub fn ops_dma_memcpy(ctx: &InstContext, stats: &mut dyn OpStats) { + let dst = ctx.a; + let src = ctx.b; + let count = ctx.mem.read(EXTRA_PARAMS, 8); + + // pre, post, dma_align, dma_unalign + if count == 0 { + return; + } + + let pre = dst & 0x07 != 0; + let dst64 = dst & !0x07; + let src64 = src & !0x07; + + if pre { + stats.mem_align_read(dst64, 1); + stats.mem_align_read(src64, 2); + stats.mem_align_write(dst64, 1); + } + + let dst64_end = (dst + count - 1) & !0x07; + let src64_end = (src + count - 1) & !0x07; + let post = dst64_end > dst64 && (dst + count - 1) & 0x07 != 7; + if post { + stats.mem_align_read(dst64_end, 1); + stats.mem_align_read(src64_end - 8, 2); + stats.mem_align_write(dst64_end, 1); + } + + if count < 8 { + stats.add_extras(&[(ZiskOp::_DMA_PRE, pre as usize), (ZiskOp::_DMA_POST, post as usize)]); + } else { + let first_loop_dst64 = (dst + 7) >> 3; + let first_loop_src64 = (src + 7) >> 3; + let last_loop_dst64 = (dst + count - 8) >> 3; + let loop_count = (last_loop_dst64 + 1 - first_loop_dst64) as usize; + + // same alignment + if dst & 0x07 == src & 0x07 { + stats.mem_align_read(first_loop_src64, loop_count); + stats.mem_align_write(first_loop_dst64, loop_count); + let units = loop_count.div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, pre as usize), + (ZiskOp::_DMA_POST, post as usize), + (ZiskOp::_DMA_64_ALIGNED, units), + ]); + } else { + stats.mem_align_read(first_loop_src64, loop_count + 1); + stats.mem_align_write(first_loop_dst64, loop_count); + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, pre as usize), + (ZiskOp::_DMA_POST, post as usize), + (ZiskOp::_DMA_UNALIGNED, loop_count + 1), + ]); + } + } +} + +pub fn opc_dma_memcmp(ctx: &mut InstContext) { + let addr_a = ctx.a; + let addr_b = ctx.b; + let count = ctx.mem.read(EXTRA_PARAMS, 8); + + println!("opc_dma_memcmp 0x{addr_a:08X} 0x{addr_b:08X} {count} {:?}", ctx.emulation_mode); + + if ctx.emulation_mode == EmulationMode::ConsumeMemReads { + ctx.c = ctx.precompiled.input_data[0]; + ctx.flag = false; + return; + } + let (op_result, count_eq) = ctx.mem.memcmp(addr_a, addr_b, count); + + if let EmulationMode::GenerateMemReads = ctx.emulation_mode { + // In generate mode we need to populate precompiled.input_data with + // information needed + ctx.precompiled.input_data.clear(); + + // first element was the result of operation + ctx.precompiled.input_data.push(op_result); + ctx.precompiled.input_data.push(count_eq as u64); + + let count_used = std::cmp::min(count, count_eq as u64 + 1); + ctx.mem.push_from_mem(&mut ctx.precompiled.input_data, addr_a, count_used); + ctx.mem.push_from_mem(&mut ctx.precompiled.input_data, addr_b, count_used); + + // read full source data + ctx.precompiled.step = ctx.step; + } + + ctx.c = op_result; + ctx.flag = false; +} + +/// Unimplemented. Arith256 can only be called from the system call context via InstContext. +/// This is provided just for completeness. +#[inline(always)] +pub fn op_dma_memcmp(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_dma_memcmp() is not implemented"); +} + +#[inline(always)] +pub fn ops_dma_memcmp(ctx: &InstContext, stats: &mut dyn OpStats) { + let addr_a = ctx.a; + let addr_b = ctx.b; + let count = ctx.mem.read(EXTRA_PARAMS, 8); + + // pre, post, dma_align, dma_unalign + if count == 0 { + return; + } + + let (res, count_eq) = ctx.mem.memcmp(addr_a, addr_b, count); + let pre = addr_a & 0x07 != 0; + let addr64_a = addr_a & !0x07; + let addr64_b = addr_b & !0x07; + + if pre { + stats.mem_align_read(addr64_a, 1); + stats.mem_align_read(addr64_b, 2); + stats.mem_align_read(addr64_a, 1); + } + + let addr64_a_end = (addr_a + count - 1) & !0x07; + let addr64_b_end = (addr_b + count - 1) & !0x07; + let post = addr64_a_end > addr64_a && (addr_a + count - 1) & 0x07 != 7; + if post { + stats.mem_align_read(addr64_a_end, 1); + stats.mem_align_read(addr64_b_end - 8, 2); + stats.mem_align_read(addr64_a_end, 1); + } + + if count < 8 { + // with count < 8, there aren't 64-bits loops. + stats.add_extras(&[(ZiskOp::_DMA_PRE, pre as usize), (ZiskOp::_DMA_POST, post as usize)]); + } else { + // calculate the resources used by 64-bits loop. + // count used are number of bytes read to demostrate memcmp(), usually count_eq + 1, + // but if all bytes are equal count = count_eq, no need extra reads + let count_used = std::cmp::min(count, count_eq as u64 + 1); + let first_loop_dst64 = (addr_a + 7) >> 3; + let first_loop_src64 = (addr_b + 7) >> 3; + let last_loop_dst64 = (addr_a + count_used - 8) >> 3; + let loop_count = (last_loop_dst64 + 1 - first_loop_dst64) as usize; + + // need a machine to compare one byte + let compare_byte = count == count_eq as u64; + + // same alignment + if addr_a & 0x07 == addr_b & 0x07 { + stats.mem_align_read(first_loop_src64, loop_count); + stats.mem_align_read(first_loop_dst64, loop_count); + // add information about other machines to demostrate operation + let units = loop_count.div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, pre as usize), + (ZiskOp::_DMA_POST, post as usize), + (ZiskOp::_DMA_64_ALIGNED, units), + (ZiskOp::_DMA_CMP_BYTE, compare_byte as usize), + ]); + } else { + stats.mem_align_read(first_loop_src64, loop_count + 1); + stats.mem_align_read(first_loop_dst64, loop_count); + // add information about other machines to demostrate operation + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, pre as usize), + (ZiskOp::_DMA_POST, post as usize), + (ZiskOp::_DMA_64_ALIGNED, loop_count + 1), + (ZiskOp::_DMA_CMP_BYTE, compare_byte as usize), + ]); + } + } +} diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 265f697c1..f2c01716b 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -4,9 +4,9 @@ use std::path::Path; use crate::{ - zisk_ops::ZiskOp, AsmGenerationMethod, ZiskInst, ZiskRom, FLOAT_LIB_ROM_ADDR, FREE_INPUT_ADDR, - M64, P2_32, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, SRC_C, SRC_IMM, SRC_IND, SRC_MEM, SRC_REG, - SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, + zisk_ops::ZiskOp, AsmGenerationMethod, ZiskInst, ZiskRom, EXTRA_PARAMS, FLOAT_LIB_ROM_ADDR, + FREE_INPUT_ADDR, M64, P2_32, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, SRC_C, SRC_IMM, SRC_IND, + SRC_MEM, SRC_REG, SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, }; // Regs rax, rcx, rdx, rdi, rsi, rsp, and r8-r11 are caller-save, not saved across function calls. @@ -71,10 +71,26 @@ const FCALL_LENGTH: u64 = FCALL_RESULT_GOT + 1; const XMM_MAPPED_REGS: [u64; 16] = [1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]; //const XMM_MAPPED_REGS: [u64; 0] = []; // Used for debugging -const F_MEM_CLEAR_WRITE_BYTE: u64 = 1 << 37; -const F_MEM_WRITE_SHIFT: u64 = 36; -const F_MEM_WRITE: u64 = 1 << F_MEM_WRITE_SHIFT; -const F_MEM_WIDTH_SHIFT: u64 = 32; +const F_MOPS_CLEAR_WRITE_BYTE: u64 = 1 << 37; + +const F_MOPS_BLOCK_READ: u64 = 0x0000_000A_0000_0000; +const F_MOPS_BLOCK_WRITE: u64 = 0x0000_000B_0000_0000; + +const F_MOPS_READ_8: u64 = 0x0000_0008_0000_0000; +const F_MOPS_READ_4: u64 = 0x0000_0004_0000_0000; +const F_MOPS_READ_2: u64 = 0x0000_0002_0000_0000; +const F_MOPS_READ_1: u64 = 0x0000_0001_0000_0000; + +const F_MOPS_WRITE_8: u64 = 0x0000_0018_0000_0000; +const F_MOPS_WRITE_4: u64 = 0x0000_0014_0000_0000; +const F_MOPS_WRITE_2: u64 = 0x0000_0012_0000_0000; +const F_MOPS_WRITE_1: u64 = 0x0000_0011_0000_0000; + +const F_MOPS_ALIGNED_READ: u64 = 0x0000_000C_0000_0000; +const F_MOPS_ALIGNED_WRITE: u64 = 0x0000_000D_0000_0000; +// const F_MOPS_ALIGNED_BLOCK_READ: u64 = 0x0000_000E_0000_0000; +// const F_MOPS_ALIGNED_BLOCK_WRITE: u64 = 0x0000_000F_0000_0000; +const F_MOPS_BLOCK_LENGTH_SHIFT: u64 = 36; // const PRECOMPILE_BUFFER_SIZE_IN_BYTES: u64 = 0x100000; // 1MB const PRECOMPILE_BUFFER_SIZE_IN_BYTES: u64 = 0x10000000; // 256MB @@ -1316,7 +1332,7 @@ impl ZiskRom2Asm { } if ctx.mem_op() { - Self::a_src_mem_op(&mut ctx, code); + Self::src_read_mops(&mut ctx, code); } ctx.a.is_saved = true; @@ -1700,7 +1716,7 @@ impl ZiskRom2Asm { } if ctx.mem_op() { - Self::b_src_mem_op(&mut ctx, code); + Self::src_read_mops(&mut ctx, code); } } SRC_IMM => { @@ -2444,7 +2460,7 @@ impl ZiskRom2Asm { } if ctx.mem_op() { - Self::b_src_ind_mem_op(&mut ctx, code, reg_address, instruction.ind_width); + Self::b_src_ind_mops(&mut ctx, code, reg_address, instruction.ind_width); } } _ => panic!( @@ -2539,13 +2555,12 @@ impl ZiskRom2Asm { /*************/ // Execute operation, storing result is registers c and flag - Self::operation_to_asm(&mut ctx, instruction.op, code, &mut unusual_code); - - // At this point, REG_C must contain the value of c - assert!(ctx.c.is_saved); + Self::operation_to_asm(&mut ctx, instruction, code, &mut unusual_code); // Copy c value to main trace if ctx.main_trace() { + // At this point, REG_C must contain the value of c + assert!(ctx.c.is_saved); *code += &ctx.full_line_comment("Main[3]=c".to_string()); *code += &format!( "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8 + 3*8], {REG_C}\n" @@ -2566,6 +2581,7 @@ impl ZiskRom2Asm { } } STORE_REG => { + assert!(ctx.c.is_saved); assert!(instruction.store_offset >= 0); assert!(instruction.store_offset <= 34); @@ -2593,7 +2609,7 @@ impl ZiskRom2Asm { .full_line_comment(format!("STORE_REG reg={}", instruction.store_offset)); // Store in mem[address] - if instruction.store_ra { + if instruction.store_pc { let value = (ctx.pc as i64 + instruction.jmp_offset2) as u64; Self::write_riscv_reg_constant( &mut ctx, @@ -2617,6 +2633,7 @@ impl ZiskRom2Asm { } } STORE_MEM => { + assert!(ctx.c.is_saved); *code += &ctx.full_line_comment("STORE_MEM".to_string()); // Calculate memory address and store it in REG_ADDRESS @@ -2717,7 +2734,7 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov {}, 0x{:x} {}\n", REG_VALUE, @@ -2749,6 +2766,7 @@ impl ZiskRom2Asm { } } STORE_IND => { + assert!(ctx.c.is_saved); *code += &ctx .full_line_comment(format!("STORE_IND width={}", instruction.ind_width)); @@ -3139,7 +3157,7 @@ impl ZiskRom2Asm { { match instruction.ind_width { 8 => { - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov qword {}[{}], {} {}\n", ctx.ptr, @@ -3157,7 +3175,7 @@ impl ZiskRom2Asm { } } 4 => { - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov dword {}[{}], {} {}\n", ctx.ptr, @@ -3175,7 +3193,7 @@ impl ZiskRom2Asm { } } 2 => { - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov word {}[{}], {} {}\n", ctx.ptr, @@ -3193,7 +3211,7 @@ impl ZiskRom2Asm { } } 1 => { - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov word {}[{}], {} {}\n", ctx.ptr, @@ -3228,7 +3246,7 @@ impl ZiskRom2Asm { ctx.pc, ctx.comment_str("width=1: continue") ); - if instruction.store_ra { + if instruction.store_pc { *code += &format!( "\tmov dil, 0x{:x} {}\n", (ctx.pc as i64 + instruction.jmp_offset2) as u64 as u8, @@ -3712,10 +3730,11 @@ impl ZiskRom2Asm { fn operation_to_asm( ctx: &mut ZiskAsmContext, - opcode: u8, + inst: &ZiskInst, code: &mut String, unusual_code: &mut String, ) { + let opcode = inst.op; // Set flags to false, by default ctx.flag_is_always_one = false; ctx.flag_is_always_zero = false; @@ -5180,8 +5199,8 @@ impl ZiskRom2Asm { // Trace 25 memory read operations if ctx.mem_op() { *code += &format!("\tmov {REG_ADDRESS}, rdi\n"); - Self::mem_op_array(ctx, code, REG_ADDRESS, false, 8, 25); - Self::mem_op_array(ctx, code, REG_ADDRESS, true, 8, 25); + Self::mem_op_array(ctx, code, REG_ADDRESS, false, 25); + Self::mem_op_array(ctx, code, REG_ADDRESS, true, 25); } // Get result from precompile results data @@ -5368,8 +5387,8 @@ impl ZiskRom2Asm { // Trace 16 memory read operations if ctx.mem_op() { *code += &format!("\tmov {REG_ADDRESS}, rdi\n"); - Self::mem_op_array(ctx, code, REG_ADDRESS, false, 8, 16); - Self::mem_op_array(ctx, code, REG_ADDRESS, true, 8, 16); + Self::mem_op_array(ctx, code, REG_ADDRESS, false, 16); + Self::mem_op_array(ctx, code, REG_ADDRESS, true, 16); } // Call the poseidon2 function @@ -5724,8 +5743,8 @@ impl ZiskRom2Asm { // Save memory operations into mem_reads if ctx.mem_op() { - Self::mem_op_array(ctx, code, "rdi", false, 8, 8); - Self::mem_op_array(ctx, code, "rdi", true, 8, 8); + Self::mem_op_array(ctx, code, "rdi", false, 8); + Self::mem_op_array(ctx, code, "rdi", true, 8); } if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() @@ -6132,8 +6151,8 @@ impl ZiskRom2Asm { // Save memory operations into mem_reads if ctx.mem_op() { - Self::mem_op_array(ctx, code, "rdi", false, 8, 8); - Self::mem_op_array(ctx, code, "rdi", true, 8, 8); + Self::mem_op_array(ctx, code, "rdi", false, 8); + Self::mem_op_array(ctx, code, "rdi", true, 8); } if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() @@ -6656,8 +6675,8 @@ impl ZiskRom2Asm { // Save memory operations into mem_reads if ctx.mem_op() { - Self::mem_op_array(ctx, code, "rdi", false, 8, 12); - Self::mem_op_array(ctx, code, "rdi", true, 8, 12); + Self::mem_op_array(ctx, code, "rdi", false, 12); + Self::mem_op_array(ctx, code, "rdi", true, 12); } if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() @@ -7057,6 +7076,75 @@ impl ZiskRom2Asm { ctx.c.is_saved = true; ctx.flag_is_always_zero = false; } + ZiskOp::DmaMemCpy => { + assert_eq!(inst.store, STORE_NONE); + // Use the memory address as the first and unique parameter + *code += &ctx.full_line_comment("DmaMemCpy".to_string()); + + *code += &format!( + "\tmov rdi, {} {}\n", + ctx.a.string_value, + ctx.comment_str("rdi = a = destination") + ); + *code += &format!( + "\tmov rsi, {} {}\n", + ctx.b.string_value, + ctx.comment_str("rsi = b = source") + ); + *code += &format!( + "\tmov rdx, 0x{:08x} {}\n", + EXTRA_PARAMS, + ctx.comment_str("rdx = @EXTERN_PARAM") + ); + *code += &format!("\tmov rdx, [rdx] {}\n", ctx.comment_str("rdx = [EXTERN_PARAM]")); + + assert_eq!(REG_MEM_READS_ADDRESS, "r12"); + assert_eq!(REG_MEM_READS_SIZE, "r13"); + + match ctx.mode { + AsmGenerationMethod::AsmMinimalTraces => { + // the number of mem_reads of trace used by memcpy could be + // large, need to control the count of each operation, and + // if it's necessary call to increase minimal trace + *code += "\tcall direct_dma_memcpy_mtrace_with_count_check\n"; + } + AsmGenerationMethod::AsmRomHistogram => { + // ROM hasn't a variable trace, only multiplicities + *code += "\tcall dma_memcpy_fast\n"; + } + AsmGenerationMethod::AsmMemOp => { + // the maximum number of mops of memcpy is limited because in case of range + // of address only send one address as block. The maximum number of mops + // generated by memcpy is 6, means that no need check size. + // 2 pre-reads + 2 src-reads + 1 src-loop + 1 write block = 6 mops + *code += "\tcall direct_dma_memcpy_mops\n"; + } + _ => unimplemented!("dma_memcpy not implemented for method {:?}", ctx.mode), + } + + // Set result + *code += &format!("\txor {}, {} {}\n", REG_C, REG_C, ctx.comment_str("c = 0")); + ctx.c.is_saved = true; + ctx.flag_is_always_zero = true; + } + ZiskOp::DmaMemCmp => { + unimplemented!(); + } + ZiskOp::Dma64Aligned => { + unimplemented!("Internal opcode Dma64Aligned"); + } + ZiskOp::DmaUnaligned => { + unimplemented!("Internal opcode DmaUnaligned"); + } + ZiskOp::DmaPre => { + unimplemented!("Internal opcode DmaPre"); + } + ZiskOp::DmaPost => { + unimplemented!("Internal opcode DmaPost"); + } + ZiskOp::DmaCmpByte => { + unimplemented!("Internal opcode DmaCmpByte"); + } } } @@ -7558,37 +7646,32 @@ impl ZiskRom2Asm { /* MEMORY OPERATIONS */ /*********************/ - fn a_src_mem_op(ctx: &mut ZiskAsmContext, code: &mut String) { + fn src_read_mops(ctx: &mut ZiskAsmContext, code: &mut String) { // Calculate the trace value on top of the address - const WIDTH: u64 = 8; if ctx.address_is_constant { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_ADDRESS, - (WIDTH << F_MEM_WIDTH_SHIFT) | ctx.address_constant_value, + "\tmov {REG_ADDRESS}, 0x{:x} {}\n", + if ctx.address_constant_value & 0x07 == 0 { + F_MOPS_ALIGNED_READ + } else { + F_MOPS_READ_8 + } + ctx.address_constant_value, ctx.comment_str("aux = constant mem op") ); } else { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - WIDTH << F_MEM_WIDTH_SHIFT, + "\tmov {REG_AUX}, 0x{F_MOPS_READ_8:x} {}\n", ctx.comment_str("aux = mem op mask") ); *code += &format!( - "\tor {}, {} {}\n", - REG_ADDRESS, - REG_AUX, + "\tor {REG_ADDRESS}, {REG_AUX} {}\n", ctx.comment_str("address |= mem op mask") ); } // Copy read data into mem_reads_address and increment it *code += &format!( - "\tmov [{} + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - REG_ADDRESS, + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8], {REG_ADDRESS} {}\n", ctx.comment_str("mem_reads[@+size*8] = mem op") ); @@ -7596,126 +7679,83 @@ impl ZiskRom2Asm { *code += &format!("\tinc {} {}\n", REG_MEM_READS_SIZE, ctx.comment_str("mem_reads_size++")); } - fn b_src_mem_op(ctx: &mut ZiskAsmContext, code: &mut String) { - // Calculate the trace value on top of the address - const WIDTH: u64 = 8; + fn b_src_ind_mops(ctx: &mut ZiskAsmContext, code: &mut String, reg_address: &str, width: u64) { if ctx.address_is_constant { + let mops = if width == 8 && ctx.address_constant_value & 0x07 == 0 { + F_MOPS_ALIGNED_READ + ctx.address_constant_value + } else { + ctx.address_constant_value + + match width { + 1 => F_MOPS_READ_1, + 2 => F_MOPS_READ_2, + 4 => F_MOPS_READ_4, + 8 => F_MOPS_READ_8, + _ => panic!("Invalid width"), + } + }; *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_ADDRESS, - (WIDTH << F_MEM_WIDTH_SHIFT) | ctx.address_constant_value, + "\tmov {reg_address}, 0x{mops:x} {}\n", ctx.comment_str("aux = constant mem op") ); } else { // Calculate the trace value on top of the address - *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - WIDTH << F_MEM_WIDTH_SHIFT, - ctx.comment_str("aux = mem op mask") - ); - *code += &format!( - "\tor {}, {} {}\n", - REG_ADDRESS, - REG_AUX, - ctx.comment_str("address |= mem op mask") - ); - } - - // Copy read data into mem_reads_address and increment it - *code += &format!( - "\tmov [{} + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - REG_ADDRESS, - ctx.comment_str("mem_reads[@+size*8] = mem op") - ); - - // Increment chunk.steps.mem_reads_size - *code += &format!("\tinc {} {}\n", REG_MEM_READS_SIZE, ctx.comment_str("mem_reads_size++")); - } + let mops = match width { + 1 => F_MOPS_READ_1, + 2 => F_MOPS_READ_2, + 4 => F_MOPS_READ_4, + 8 => F_MOPS_READ_8, + _ => panic!("Invalid width"), + }; - fn b_src_ind_mem_op( - ctx: &mut ZiskAsmContext, - code: &mut String, - reg_address: &str, - width: u64, - ) { - if ctx.address_is_constant { - *code += &format!( - "\tmov {}, 0x{:x} {}\n", - reg_address, - (width << F_MEM_WIDTH_SHIFT) + ctx.address_constant_value, - ctx.comment_str("aux = constant mem op") - ); - } else { - // Calculate the trace value on top of the address - *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - width << F_MEM_WIDTH_SHIFT, - ctx.comment_str("aux = mem op mask") - ); + *code += + &format!("\tmov {REG_AUX}, 0x{mops:x} {}\n", ctx.comment_str("aux = mem op mask")); *code += &format!( - "\tor {}, {} {}\n", - reg_address, - REG_AUX, + "\tor {reg_address}, {REG_AUX} {}\n", ctx.comment_str("address |= mem op mask") ); } // Copy read data into mem_reads_address and increment it *code += &format!( - "\tmov [{} + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - reg_address, + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8], {reg_address} {}\n", ctx.comment_str("mem_reads[@+size*8] = mem op") ); // Increment chunk.steps.mem_reads_size - *code += &format!("\tinc {} {}\n", REG_MEM_READS_SIZE, ctx.comment_str("mem_reads_size++")); + *code += &format!("\tinc {REG_MEM_READS_SIZE} {}\n", ctx.comment_str("mem_reads_size++")); } fn c_store_mem_mem_op(ctx: &mut ZiskAsmContext, code: &mut String) { // Calculate the trace value on top of the address - const WRITE: u64 = 1; - const WIDTH: u64 = 8; if ctx.address_is_constant { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_ADDRESS, - (WRITE << F_MEM_WRITE_SHIFT) - + (WIDTH << F_MEM_WIDTH_SHIFT) - + ctx.address_constant_value, + "\tmov {REG_ADDRESS}, 0x{:x} {}\n", + if ctx.address_constant_value & 0x07 == 0 { + F_MOPS_ALIGNED_WRITE + } else { + F_MOPS_WRITE_8 + } + ctx.address_constant_value, ctx.comment_str("aux = constant mem op") ); } else { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - (WRITE << F_MEM_WRITE_SHIFT) + (WIDTH << F_MEM_WIDTH_SHIFT), + "\tmov {REG_AUX}, 0x{F_MOPS_WRITE_8:x} {}\n", ctx.comment_str("aux = mem op mask") ); *code += &format!( - "\tor {}, {} {}\n", - REG_ADDRESS, - REG_AUX, + "\tor {REG_ADDRESS}, {REG_AUX} {}\n", ctx.comment_str("address |= mem op mask") ); } // Copy read data into mem_reads_address and increment it *code += &format!( - "\tmov [{} + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - REG_ADDRESS, + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8], {REG_ADDRESS} {}\n", ctx.comment_str("mem_reads[@+size*8] = mem op") ); // Increment chunk.steps.mem_reads_size - *code += &format!("\tinc {} {}\n", REG_MEM_READS_SIZE, ctx.comment_str("mem_reads_size++")); + *code += &format!("\tinc {REG_MEM_READS_SIZE} {}\n", ctx.comment_str("mem_reads_size++")); } fn c_store_ind_mem_op(ctx: &mut ZiskAsmContext, code: &mut String, width: u64) { @@ -7723,55 +7763,59 @@ impl ZiskRom2Asm { // With this information, the mem_planner can use a specific state machine for // this kind of byte writes if width == 1 { - *code += &format!("\tmov {}, {} {}\n", REG_VALUE, REG_C, ctx.comment_str("value = c")); + *code += &format!("\tmov {REG_VALUE}, {REG_C} {}\n", ctx.comment_str("value = c")); } // Calculate the fixed trace value adding write (bit 36) and width (bits 32-35) on top of // the address if ctx.address_is_constant { + let mops = if width == 8 && ctx.address_constant_value & 0x07 == 0 { + F_MOPS_ALIGNED_WRITE + ctx.address_constant_value + } else { + ctx.address_constant_value + + match width { + 1 => F_MOPS_WRITE_1, + 2 => F_MOPS_WRITE_2, + 4 => F_MOPS_WRITE_4, + 8 => F_MOPS_WRITE_8, + _ => panic!("Invalid width"), + } + }; *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_ADDRESS, - F_MEM_WRITE | (width << F_MEM_WIDTH_SHIFT) | ctx.address_constant_value, + "\tmov {REG_ADDRESS}, 0x{mops:x} {}\n", ctx.comment_str("aux = constant mem op") ); } else { + let mops = match width { + 1 => F_MOPS_WRITE_1, + 2 => F_MOPS_WRITE_2, + 4 => F_MOPS_WRITE_4, + 8 => F_MOPS_WRITE_8, + _ => panic!("Invalid width"), + }; + *code += + &format!("\tmov {REG_AUX}, 0x{mops:x} {}\n", ctx.comment_str("aux = mem op mask")); *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - F_MEM_WRITE | (width << F_MEM_WIDTH_SHIFT), - ctx.comment_str("aux = mem op mask") - ); - *code += &format!( - "\tor {}, {} {}\n", - REG_ADDRESS, - REG_AUX, + "\tor {REG_ADDRESS}, {REG_AUX} {}\n", ctx.comment_str("address |= mem op mask") ); } // Dynamic trace value: if rest of bytes were zero, set flag on bit F_MEM_CLEAR_WRITE_BYTE if width == 1 { - *code += &format!( - "\tshr {}, 8 {}\n", - REG_VALUE, - ctx.comment_str("value & 0xFFFFFF00 == 0 ?") - ); + *code += + &format!("\tshr {REG_VALUE}, 8 {}\n", ctx.comment_str("value & 0xFFFFFF00 == 0 ?")); *code += &format!( "\tjnz pc_{}_rest_of_bytes_not_zero {}\n", ctx.pc, ctx.comment_str("aux & 0xFFFFFF00 != 0 ?") ); *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - F_MEM_CLEAR_WRITE_BYTE, + "\tmov {REG_AUX}, 0x{F_MOPS_CLEAR_WRITE_BYTE:x} {}\n", ctx.comment_str("aux = F_MEM_CLEAR_WRITE_BYTE") ); *code += &format!( - "\tor {}, {} {}\n", - REG_ADDRESS, - REG_AUX, + "\tor {REG_ADDRESS}, {REG_AUX} {}\n", ctx.comment_str("address |= F_MEM_CLEAR_WRITE_BYTE") ); *code += &format!("\npc_{}_rest_of_bytes_not_zero:\n", ctx.pc); @@ -7779,76 +7823,49 @@ impl ZiskRom2Asm { // Copy read data into mem_reads_address and increment it *code += &format!( - "\tmov [{} + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - REG_ADDRESS, + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8], {REG_ADDRESS} {}\n", ctx.comment_str("mem_reads[@+size*8] = mem op") ); // Increment chunk.steps.mem_reads_size - *code += &format!("\tinc {} {}\n", REG_MEM_READS_SIZE, ctx.comment_str("mem_reads_size++")); + *code += &format!("\tinc {REG_MEM_READS_SIZE} {}\n", ctx.comment_str("mem_reads_size++")); } fn mem_op_array( ctx: &mut ZiskAsmContext, code: &mut String, reg_address: &str, - _write: bool, - width: u64, + write: bool, length: u64, ) { - let write: u64 = if _write { 1 } else { 0 }; - let mem_op_mask: u64 = (write << F_MEM_WRITE_SHIFT) | (width << F_MEM_WIDTH_SHIFT); + let mops_mask: u64 = if length > 1 { + // compress operation in one single block + (if write { F_MOPS_BLOCK_WRITE } else { F_MOPS_BLOCK_READ }) + | (length << F_MOPS_BLOCK_LENGTH_SHIFT) + } else if write { + F_MOPS_WRITE_8 + } else { + F_MOPS_READ_8 + }; - // Get a copy of the address register + // Load mask the mask *code += &format!( - "\tmov {}, {} {}\n", - REG_VALUE, - reg_address, - ctx.comment_str("value = address") + "\tmov {REG_VALUE}, 0x{mops_mask:x} {}\n", + ctx.comment_str("value = mem op mask") ); - // Calculate the mask - *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - mem_op_mask, - ctx.comment_str("aux = mem op mask + offset") - ); + // Get a copy of the address register + *code += + &format!("\tadd {REG_VALUE}, {reg_address} {}\n", ctx.comment_str("value = address")); - // Add the mask to the address *code += &format!( - "\tadd {}, {} {}\n", - REG_VALUE, - REG_AUX, - ctx.comment_str("value |= mem op mask") + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8], {REG_VALUE} {}\n", + ctx.comment_str("mem_reads[@+size*8] = mem op") ); - // Iterate for all memory operations - for i in 0..length { - // Copy read data into mem_reads_address and increment it - *code += &format!( - "\tmov [{} + {}*8 + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - i, - REG_VALUE, - ctx.comment_str("mem_reads[@+size*8] = mem op") - ); - - if i != (length - 1) { - // Get a copy of the address register - *code += &format!("\tadd {}, 8 {}\n", REG_VALUE, ctx.comment_str("value += 8")); - } - } // Increment chunk.steps.mem_reads_size - *code += &format!( - "\tadd {}, {} {}\n", - REG_MEM_READS_SIZE, - length, - ctx.comment_str("mem_reads_size += length") - ); + *code += + &format!("\tinc {REG_MEM_READS_SIZE} {}\n", ctx.comment_str("mem_reads_size += 1")); } fn internal_mem_op_precompiled_read( @@ -7858,105 +7875,81 @@ impl ZiskRom2Asm { load_sizes: &[usize], update_index: bool, ) -> u64 { - // Calculate the mask - let mem_op_mask: u64 = 8u64 << 32; - // This index will be incremented as we insert data into mem_reads let mut mem_reads_index: u64 = 0; // We get a copy of the precompiled data address - *code += &format!("\tmov {}, rdi {}\n", REG_ADDRESS, ctx.comment_str("address = rdi")); - - for i in 0..params_count { - // Store next aligned address value in mem_reads, and advance it - *code += &format!( - "\tmov {}, [{} + {}*8] {}\n", - REG_VALUE, - REG_ADDRESS, - i, - ctx.comment(format!("value = mem[address+{i}]")) - ); + *code += &format!("\tmov {REG_ADDRESS}, rdi {}\n", ctx.comment_str("address = rdi")); - // Load the mask + offset + if params_count > 0 { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - mem_op_mask + 8 * i, - ctx.comment_str("aux = mem op mask + offset") + "\tmov {REG_AUX}, 0x{:x} {}\n", + F_MOPS_BLOCK_READ | (params_count << F_MOPS_BLOCK_LENGTH_SHIFT), + ctx.comment_str(&format!("aux = MOPS_BLOCK_READ({})", params_count)) ); // Add the address - *code += &format!( - "\tadd {}, {} {}\n", - REG_AUX, - REG_ADDRESS, - ctx.comment_str("aux += address") - ); + *code += + &format!("\tadd {REG_AUX}, {REG_ADDRESS} {}\n", ctx.comment_str("aux += address")); // Store it in the trace *code += &format!( - "\tmov [{} + {}*8 + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - mem_reads_index, - REG_AUX, + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8 + {mem_reads_index}*8], {REG_AUX} {}\n", ctx.comment_str("mem_reads[@+size*8+ind*8] = mem_op") ); mem_reads_index += 1; } + let mut previous_size = 0; + for (i, size) in load_sizes.iter().enumerate() { // Store next aligned address value in mem_reads, and advance it *code += &format!( - "\tmov {}, [{} + {}*8] {}\n", - REG_VALUE, - REG_ADDRESS, - i, + "\tmov {REG_VALUE}, [{REG_ADDRESS} + {i}*8] {}\n", ctx.comment(format!("value = mem[address+{i}]")) ); - // Store the first load_count iterations - // load_size elements in mem_reads - - // For each chunk of the indirection, store it in mem_reads - for l in 0..*size { - // Load the mask + offset + // if previous_size = size, means that REG_AUX has the correct value + // and not need to generate again + if previous_size != *size { *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - mem_op_mask + 8 * (l as u64), - ctx.comment_str("aux = mem op mask + offset") + "\tmov {REG_AUX}, 0x{:x} {}\n", + F_MOPS_BLOCK_READ | ((*size as u64) << F_MOPS_BLOCK_LENGTH_SHIFT), + ctx.comment(format!("aux = MOPS_BLOCK_READ({})", size)) ); + previous_size = *size; + } + + // Store a block with all consecutive mem_reads + + // Add the mask over the reg_value to reuse mops_mask (reg_aux) if width is the + // same of last previous parameter + + *code += + &format!("\tadd {REG_VALUE}, {REG_AUX} {}\n", ctx.comment_str("value += aux ")); + + // Store it in the trace + *code += &format!( + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8 + {mem_reads_index}*8], {REG_VALUE} {}\n", + ctx.comment_str("mem_reads[@+size*8+ind*8] = mops") + ); - // Add the address + mem_reads_index += 1; + } + if update_index && mem_reads_index > 0 { + // Increment chunk.steps.mem_reads_size + if mem_reads_index == 1 { *code += &format!( - "\tadd {}, {} {}\n", - REG_AUX, - REG_VALUE, - ctx.comment_str("aux += address") + "\tinc {REG_MEM_READS_SIZE}, {}\n", + ctx.comment_str("mem_reads_size+=1") ); - - // Store it in the trace + } else { *code += &format!( - "\tmov [{} + {}*8 + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - mem_reads_index, - REG_AUX, - ctx.comment_str("mem_reads[@+size*8+ind*8] = mem_op") + "\tadd {REG_MEM_READS_SIZE}, {mem_reads_index} {}\n", + ctx.comment(format!("mem_reads_size+={mem_reads_index}")) ); - mem_reads_index += 1; } } - if update_index { - // Increment chunk.steps.mem_reads_size - *code += &format!( - "\tadd {}, {} {}\n", - REG_MEM_READS_SIZE, - mem_reads_index, - ctx.comment(format!("mem_reads_size+={mem_reads_index}")) - ); - } mem_reads_index } @@ -7983,80 +7976,68 @@ impl ZiskRom2Asm { load_size: u64, initial_mem_reads_index: u64, ) { - // Calculate the mask - let mem_op_mask: u64 = F_MEM_WRITE + (8u64 << F_MEM_WIDTH_SHIFT); - // This index will be incremented as we insert data into mem_reads let mut mem_reads_index: u64 = initial_mem_reads_index; if initial_mem_reads_index == 0 { // We get a copy of the precompiled data address - *code += &format!("\tmov {}, rdi {}\n", REG_ADDRESS, ctx.comment_str("address = rdi")); + *code += &format!("\tmov {REG_ADDRESS}, rdi {}\n", ctx.comment_str("address = rdi")); + } + if begin <= end { + // Load the mask + offset + *code += &format!( + "\tmov {REG_AUX}, 0x{:x} {}\n", + F_MOPS_BLOCK_WRITE | (load_size << F_MOPS_BLOCK_LENGTH_SHIFT), + ctx.comment(format!("aux = BLOCK_WRITE({})", load_size)) + ); } + // For every parameter for i in begin..=end { // Store next aligned address value in mem_reads, and advance it *code += &format!( - "\tmov {}, [{} + {}*8] {}\n", - REG_VALUE, - REG_ADDRESS, - i, + "\tmov {REG_VALUE}, [{REG_ADDRESS} + {i}*8] {}\n", ctx.comment(format!("value = mem[address+{i}]")) ); - // For each of the indirection parameter, store it in mem_reads - for l in 0..load_size { - // Load the mask + offset - *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_AUX, - mem_op_mask + 8 * l, - ctx.comment_str("aux = mem op mask + offset") + // Add the address + *code += + &format!("\tadd {REG_VALUE}, {REG_AUX} {}\n", ctx.comment_str("value += address")); + + // Store it in the trace + *code += &format!( + "\tmov [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8 + {mem_reads_index}*8], {REG_VALUE} {}\n", + ctx.comment_str("mem_reads[@+size*8+ind*8] = value (mops)") ); + mem_reads_index += 1; + } - // Add the address + // Increment chunk.steps.mem_reads_size + if mem_reads_index > 0 { + if mem_reads_index == 1 { *code += &format!( - "\tadd {}, {} {}\n", - REG_AUX, - REG_VALUE, - ctx.comment_str("aux += address") + "\tinc {REG_MEM_READS_SIZE} {}\n", + ctx.comment_str("mem_reads_size+=1") ); - - // Store it in the trace + } else { *code += &format!( - "\tmov [{} + {}*8 + {}*8], {} {}\n", - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, - mem_reads_index, - REG_AUX, - ctx.comment_str("mem_reads[@+size*8+ind*8] = mem_op") + "\tadd {REG_MEM_READS_SIZE}, {mem_reads_index} {}\n", + ctx.comment(format!("mem_reads_size+={mem_reads_index}")) ); - mem_reads_index += 1; } } - - // Increment chunk.steps.mem_reads_size - *code += &format!( - "\tadd {}, {} {}\n", - REG_MEM_READS_SIZE, - mem_reads_index, - ctx.comment(format!("mem_reads_size+={mem_reads_index}")) - ); } fn mem_op_precompiled_restore_c_and_flags(ctx: &mut ZiskAsmContext, code: &mut String) { // We get a copy of the precompiled data address - *code += &format!("\tmov {}, rdi {}\n", REG_ADDRESS, ctx.comment_str("address = rdi")); + *code += &format!("\tmov {REG_ADDRESS}, rdi {}\n", ctx.comment_str("address = rdi")); // read last mem_read into c *code += &format!( - "\tmov {}, [{} + {}*8 - 8] {}\n", - REG_C, - REG_MEM_READS_ADDRESS, - REG_MEM_READS_SIZE, + "\tmov {REG_C}, [{REG_MEM_READS_ADDRESS} + {REG_MEM_READS_SIZE}*8 - 8] {}\n", ctx.comment_str("c = mem_reads[@+size*8+ind*8]") ); - *code += &format!("\tmov {}, {} {}\n", REG_FLAG, REG_C, ctx.comment_str("flag = c")); + *code += &format!("\tmov {REG_FLAG}, {REG_C} {}\n", ctx.comment_str("flag = c")); } /**********************/ @@ -9380,10 +9361,7 @@ impl ZiskRom2Asm { /////////////// // Build the mask for this case - const WIDTH: u64 = 8; - const WRITE: u64 = 0; - let addr_step_mask: u64 = - (WIDTH << F_MEM_WIDTH_SHIFT) + (WRITE << F_MEM_WRITE_SHIFT) + (micro_step << 38); + let addr_step_mask: u64 = F_MOPS_READ_8 + (micro_step << 38); // Add mask to address *code += &format!( @@ -9562,10 +9540,15 @@ impl ZiskRom2Asm { /////////////// // Build the mask for this case - const WRITE: u64 = 1; const MICRO_STEP: u64 = 3; - let addr_step_mask: u64 = - (width << F_MEM_WIDTH_SHIFT) + (WRITE << F_MEM_WRITE_SHIFT) + (MICRO_STEP << 40); + let addr_step_mask: u64 = (MICRO_STEP << 40) + + match width { + 1 => F_MOPS_WRITE_1, + 2 => F_MOPS_WRITE_2, + 4 => F_MOPS_WRITE_4, + 8 => F_MOPS_WRITE_8, + _ => panic!("Invalid width {width}"), + }; // Add mask to address *code += &format!( @@ -9675,7 +9658,7 @@ impl ZiskRom2Asm { const WIDTH: u64 = 8; const MICRO_STEP: u64 = 2; let addr_step_mask: u64 = - (WIDTH << F_MEM_WIDTH_SHIFT) + (write << F_MEM_WRITE_SHIFT) + (MICRO_STEP << 40); + if write == 0 { F_MOPS_READ_8 } else { F_MOPS_WRITE_8 } + (MICRO_STEP << 40); // For every element for i in 0..buffer_size { diff --git a/data-bus/src/data_bus.rs b/data-bus/src/data_bus.rs index 8783c1731..71c44c8d1 100644 --- a/data-bus/src/data_bus.rs +++ b/data-bus/src/data_bus.rs @@ -3,9 +3,7 @@ //! omnipresent devices that process all data sent to the bus. This module provides mechanisms to //! send data, route it to the appropriate subscribers, and manage device connections. -use std::collections::VecDeque; - -use zisk_common::{BusDevice, BusId}; +use zisk_common::BusId; pub trait DataBusTrait { /// Writes data to the bus and processes it through the registered devices. @@ -18,178 +16,9 @@ pub trait DataBusTrait { /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. - fn write_to_bus(&mut self, bus_id: BusId, payload: &[D]) -> bool; + fn write_to_bus(&mut self, bus_id: BusId, data: &[D], data_ext: &[D]) -> bool; fn on_close(&mut self); fn into_devices(self, execute_on_close: bool) -> Vec<(Option, Option)>; } - -/// A bus system facilitating communication between multiple publishers and subscribers. -/// -/// The `DataBus` allows devices to register for specific bus IDs or act as global (omni) devices. -/// It routes payloads to registered devices and handles data transfers efficiently. -/// -/// # Type Parameters -/// * `D` - The type of data payloads handled by the bus. -/// * `BD` - The type of devices (subscribers) connected to the bus, implementing the `BusDevice` -/// trait. -pub struct DataBus> { - /// List of devices connected to the bus. - devices: Vec<(Option, BD)>, - - /// Mapping from `BusId` to indices of devices listening to that ID. - devices_bus_id_map: Vec>, - - /// Queue of pending data transfers to be processed. - pending_transfers: VecDeque<(BusId, Vec)>, - - /// Indices of devices that are connected to the bus but without a specific instance. - none_devices: Vec, - - /// The number of active devices currently connected to the bus. - active_devices: usize, -} - -impl> Default for DataBus { - /// Creates a new `DataBus` with default settings. - fn default() -> Self { - Self::new() - } -} - -impl> DataBus { - /// Creates a new `DataBus` instance. - pub fn new() -> Self { - Self { - devices: Vec::new(), - devices_bus_id_map: vec![vec![], vec![], vec![]], - pending_transfers: VecDeque::new(), - none_devices: vec![], - active_devices: 0, - } - } - - /// Connects a device to the bus with specific `BusId` subscriptions. - /// - /// # Arguments - /// * `instance_idx` - An optional index for the device instance. - /// * `bus_device` - The device to be added to the bus. - pub fn connect_device(&mut self, instance_idx: Option, bus_device: Option) { - if let Some(bus_device) = bus_device { - let bus_ids = bus_device.bus_id(); - - self.devices.push((instance_idx, bus_device)); - let device_idx = self.devices.len() - 1; - - for bus_id in bus_ids { - self.devices_bus_id_map[*bus_id].push(device_idx); - } - self.active_devices += 1; - } else { - self.none_devices.push(self.devices.len()); - } - } - - /// Routes data to the devices subscribed to a specific bus ID or global devices. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus to route the data to. - /// * `payload` - A reference to the data payload being routed. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn route_data(&mut self, bus_id: BusId, payload: &[D]) { - let devices_idx = &mut self.devices_bus_id_map[*bus_id]; - let mut i = 0; - - while i < devices_idx.len() { - let device_idx = devices_idx[i]; - // When a device returns false, it indicates that it has finished its work and should be disabled. - if !self.devices[device_idx].1.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ) { - // Remove the device from the bus and update the mapping. - devices_idx.swap_remove(i); - self.active_devices -= 1; - } else { - i += 1; - } - } - } - - /// Outputs the current state of the bus for debugging purposes. - pub fn debug_state(&self) { - println!("Devices: {:?}", self.devices.len()); - println!("Devices by bus ID: {:?}", self.devices_bus_id_map); - println!("Pending Transfers: {:?}", self.pending_transfers.len()); - } -} - -impl> DataBusTrait for DataBus { - /// Writes data to the bus and processes it through the registered devices. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus receiving the data. - /// * `payload` - The data payload to be sent. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn write_to_bus(&mut self, bus_id: BusId, payload: &[D]) -> bool { - self.route_data(bus_id, payload); - - while let Some((bus_id, payload)) = self.pending_transfers.pop_front() { - self.route_data(bus_id, &payload); - } - - self.active_devices > 0 - } - - /// Called when the bus is closed, allowing devices to perform any necessary cleanup. - fn on_close(&mut self) { - for device in &mut self.devices { - device.1.on_close(); - } - } - - /// Converts the bus into a vector of devices, optionally executing their close operations. - /// - /// # Arguments - /// * `execute_on_close` - If true, calls the `on_close` method on each device. - /// - //// # Returns - /// A vector of tuples containing the device instance index and the device itself. - fn into_devices(self, execute_on_close: bool) -> Vec<(Option, Option)> { - let total_len = self.devices.len() + self.none_devices.len(); - let mut result = Vec::with_capacity(total_len); - - let mut dev_iter = self.devices.into_iter(); - let mut none_iter = self.none_devices.iter().copied().peekable(); - - for idx in 0..total_len { - if Some(&idx) == none_iter.peek() { - result.push((None, None)); - none_iter.next(); - } else { - let mut device = - dev_iter.next().expect("Mismatch between device and none-device count"); - - if execute_on_close { - device.1.on_close(); - } - - result.push((device.0, Some(device.1))); - } - } - - result - } -} diff --git a/data-bus/src/data_bus_file.rs b/data-bus/src/data_bus_file.rs deleted file mode 100644 index bbc270496..000000000 --- a/data-bus/src/data_bus_file.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! A module for reading and writing DataBus information to a file. -//! -//! The `DataBusFileReader` struct provides a utility for reading DataBus information from a plain -//! text file. The `DataBusFileWriter` struct provides a utility for writing DataBus information to -//! a file. - -use std::{ - fs::File, - io::{self, Read, Write}, - str::FromStr, -}; - -use zisk_common::BusId; - -pub struct DataBusFileReader; - -impl DataBusFileReader { - /// Reads data from a plain text file and returns a vector of `(BusId, Payload)` tuples. - /// - /// # File Format - /// Each line in the file should be formatted as: - /// ```text - /// ... - /// ``` - /// - ``: A 16-bit unsigned integer representing the bus ID. - /// - ``: A list of payload values convertible to the type `D`. - /// - /// # Arguments - /// * `file_path` - The path to the plain text file. - /// - /// # Returns - /// * `Result)>, io::Error>`: A vector of `(BusId, Payload)` tuples or an error - /// if the file cannot be read or the data format is invalid. - /// - /// # Errors - /// - Returns an error if the file cannot be opened or read. - /// - Returns an error if any line is malformed (missing `BusId` or invalid payload values). - pub fn read_from_file(file_path: &str) -> Result)>, io::Error> - where - D::Err: std::fmt::Display, - { - let mut file = File::open(file_path)?; - let mut content = String::new(); - file.read_to_string(&mut content)?; - - // Estimate the number of lines for pre-allocation - let estimated_lines = content.lines().count(); - let mut data = Vec::with_capacity(estimated_lines); - - for (line_number, line) in content.lines().enumerate() { - let mut parts = line.split_whitespace(); - - // Parse the BusId (first token) - let bus_id = parts - .next() - .ok_or_else(|| { - io::Error::new( - io::ErrorKind::InvalidData, - format!("Missing BusId on line {}", line_number + 1), - ) - })? - .parse::() - .map_err(|err| { - io::Error::new( - io::ErrorKind::InvalidData, - format!("Invalid BusId on line {}: {}", line_number + 1, err), - ) - })?; - - // Pre-allocate payload size if possible - let mut payload = Vec::with_capacity(parts.clone().count()); - - for token in parts { - let value = token.parse::().map_err(|err| { - io::Error::new( - io::ErrorKind::InvalidData, - format!("Invalid payload on line {}: {}", line_number + 1, err), - ) - })?; - payload.push(value); - } - - // Push the parsed data into the pre-allocated vector - data.push((BusId(bus_id), payload)); - } - - Ok(data) - } -} - -/// A utility struct for writing DataBus information to a file. -pub struct DataBusFileWriter { - file: Option, -} - -impl DataBusFileWriter { - /// Creates a new `DataBusFileWriter` and opens the specified file for writing. - /// - /// # Arguments - /// * `file_path` - The path to the file where data will be written. - /// - /// # Returns - /// A new instance of `DataBusFileWriter`. - pub fn new(file_path: &str) -> Result { - let file = File::create(file_path)?; - Ok(Self { file: Some(file) }) - } - - /// Writes a single `(BusId, Payload)` line to the file. - /// - /// # Arguments - /// * `bus_id` - The BusId to write. - /// * `payload` - A vector of payload items to write. - pub fn write(&mut self, bus_id: u16, payload: &[D]) -> Result<(), io::Error> { - if let Some(file) = self.file.as_mut() { - let payload_str: String = - payload.iter().map(|item| item.to_string()).collect::>().join(" "); - writeln!(file, "{bus_id} {payload_str}")?; - Ok(()) - } else { - Err(io::Error::other("Attempted to write to a closed file.")) - } - } - - /// Closes the file, ensuring all data is flushed to disk. - pub fn close(&mut self) -> Result<(), io::Error> { - if let Some(mut file) = self.file.take() { - file.flush()?; // Ensure all buffered data is written - } - Ok(()) - } -} - -impl Drop for DataBusFileWriter { - /// Ensures the file is closed when the `DataBusFileWriter` is dropped. - fn drop(&mut self) { - let _ = self.close(); // Silently ignore any errors during drop - } -} diff --git a/data-bus/src/data_bus_player.rs b/data-bus/src/data_bus_player.rs deleted file mode 100644 index 25ee2834a..000000000 --- a/data-bus/src/data_bus_player.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! A player for replaying data on the `DataBus`. - -use std::{io, str::FromStr}; - -use zisk_common::{BusDevice, BusId}; - -use crate::{DataBus, DataBusFileReader, DataBusTrait}; - -pub struct DataBusPlayer; - -impl DataBusPlayer { - /// Plays data on the `DataBus` from a provided data vector. - /// - /// # Arguments - /// * `data_bus` - The `DataBus` to which the data is sent. - /// * `data` - A vector of `(BusId, Payload)` tuples. - pub fn play>(data_bus: &mut DataBus, data: Vec<(BusId, Vec)>) { - for (bus_id, payload) in data { - as DataBusTrait>::write_to_bus(data_bus, bus_id, &payload); - } - } - - /// Plays data on the `DataBus` from a file using `DataBusFileReader`. - /// - /// # Arguments - /// * `file_path` - The path to the file containing the data. - /// * `data_bus` - The `DataBus` to which the data is sent. - /// - /// # Returns - /// * `Result<(), io::Error>` indicating success or failure during file reading and playing. - pub fn play_from_file>( - data_bus: &mut DataBus, - file_path: &str, - ) -> Result<(), io::Error> - where - D::Err: std::fmt::Display, - { - let data = DataBusFileReader::read_from_file::(file_path)?; - Self::play(data_bus, data); - Ok(()) - } -} diff --git a/data-bus/src/lib.rs b/data-bus/src/lib.rs index 4ff1159d1..f4f8ec56f 100644 --- a/data-bus/src/lib.rs +++ b/data-bus/src/lib.rs @@ -1,7 +1,3 @@ mod data_bus; -mod data_bus_file; -mod data_bus_player; pub use data_bus::*; -pub use data_bus_file::*; -pub use data_bus_player::*; diff --git a/distributed/Dockerfile b/distributed/Dockerfile index f2bc3b3e9..05deb53d3 100644 --- a/distributed/Dockerfile +++ b/distributed/Dockerfile @@ -75,8 +75,6 @@ RUN mkdir -p bin config/coordinator config/worker /app/proofs /var/log/distribut # Copy binaries from builder stage COPY --from=builder --chown=zisk:zisk /app/target/release/zisk-coordinator ./bin/ COPY --from=builder --chown=zisk:zisk /app/target/release/zisk-worker ./bin/ -# Copy the witness library -COPY --from=builder --chown=zisk:zisk /app/target/release/libzisk_witness.so ./bin/ # Copy configuration files with proper ownership COPY --chown=zisk:zisk distributed/crates/coordinator/config/ ./config/coordinator/ diff --git a/distributed/README.md b/distributed/README.md index 3b715a708..bc7a264a8 100644 --- a/distributed/README.md +++ b/distributed/README.md @@ -365,7 +365,6 @@ The table below lists the available configuration options for the Worker: | `logging.level` | - | RUST_LOG | String | debug | Logging level (error, warn, info, debug, trace) | | `logging.format` | - | - | String | pretty | Logging format (pretty, json, compact) | | `logging.file_path` | - | - | String | - | *Optional*. Log file path (enables file logging) | -| - | `--witness-lib` | - | String | ~/.zisk/bin/libzisk_witness.so | Path to witness computation dynamic library | | - | `--proving-key` | - | String | ~/.zisk/provingKey | Path to setup folder | | - | `--elf` | - | String | - | Path to ELF file | | - | `--asm` | - | String | ~/.zisk/cache | Path to ASM file (mutually exclusive with `--emulator`) | diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 85f1bee75..5214d78c8 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -86,6 +86,7 @@ pub enum HintsModeDto { pub struct LaunchProofRequestDto { pub data_id: DataId, pub compute_capacity: u32, + pub minimal_compute_capacity: u32, pub inputs_mode: InputsModeDto, pub hints_mode: HintsModeDto, pub simulated_node: Option, @@ -213,15 +214,7 @@ pub struct AggParamsDto { pub agg_proofs: Vec, pub last_proof: bool, pub final_proof: bool, - pub verify_constraints: bool, - pub aggregation: bool, - pub rma: bool, pub compressed: bool, - pub verify_proofs: bool, - pub save_proofs: bool, - pub test_mode: bool, - pub output_dir_path: String, - pub minimal_memory: bool, } pub struct ProofDto { diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index b908a78de..010ca92f0 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -12,7 +12,6 @@ use std::{ collections::HashMap, fmt::{self, Debug, Display}, ops::Range, - path::PathBuf, }; use tracing::error; @@ -246,6 +245,7 @@ pub struct Job { pub inputs_mode: InputsModeDto, pub hints_mode: HintsModeDto, pub compute_capacity: ComputeCapacity, + pub minimal_compute_capacity: ComputeCapacity, pub workers: Vec, pub agg_worker_id: Option, pub partitions: Vec>, @@ -263,6 +263,7 @@ impl Job { inputs_mode: InputsModeDto, hints_mode: HintsModeDto, compute_capacity: ComputeCapacity, + minimal_compute_capacity: ComputeCapacity, selected_workers: Vec, partitions: Vec>, execution_mode: JobExecutionMode, @@ -276,6 +277,7 @@ impl Job { inputs_mode, hints_mode, compute_capacity, + minimal_compute_capacity, workers: selected_workers, agg_worker_id: None, partitions, @@ -442,13 +444,5 @@ pub struct AggregationParams { pub agg_proofs: Vec, pub last_proof: bool, pub final_proof: bool, - pub verify_constraints: bool, - pub aggregation: bool, - pub rma: bool, pub compressed: bool, - pub verify_proofs: bool, - pub save_proofs: bool, - pub test_mode: bool, - pub output_dir_path: PathBuf, - pub minimal_memory: bool, } diff --git a/distributed/crates/coordinator/src/cli/handler_coordinator.rs b/distributed/crates/coordinator/src/cli/handler_coordinator.rs index 9f32c6895..7d47f0572 100644 --- a/distributed/crates/coordinator/src/cli/handler_coordinator.rs +++ b/distributed/crates/coordinator/src/cli/handler_coordinator.rs @@ -14,13 +14,21 @@ pub async fn handle( port: Option, proofs_dir: Option, no_save_proofs: bool, + compressed_proofs: bool, webhook_url: Option, ) -> Result<()> { // Config file is now optional - if not provided, defaults will be used let config_file = config_file.or_else(|| std::env::var("ZISK_COORDINATOR_CONFIG_PATH").ok()); // Load configuration - let config = Config::load(config_file, port, proofs_dir, no_save_proofs, webhook_url)?; + let config = Config::load( + config_file, + port, + proofs_dir, + no_save_proofs, + compressed_proofs, + webhook_url, + )?; // Initialize tracing - keep guard alive for application lifetime let _log_guard = zisk_distributed_common::tracing::init(Some(&config.logging), None)?; diff --git a/distributed/crates/coordinator/src/cli/handler_prove.rs b/distributed/crates/coordinator/src/cli/handler_prove.rs index 5e9a726a9..ec8750903 100644 --- a/distributed/crates/coordinator/src/cli/handler_prove.rs +++ b/distributed/crates/coordinator/src/cli/handler_prove.rs @@ -18,6 +18,7 @@ pub async fn handle( direct_inputs: bool, stream_hints: bool, compute_capacity: u32, + minimal_compute_capacity: Option, simulated_node: Option, ) -> Result<()> { // Initialize tracing - keep guard alive for application lifetime @@ -52,9 +53,20 @@ pub async fn handle( uuid::Uuid::new_v4().to_string() }; + // Check compute capacity + let minimal_compute_capacity = minimal_compute_capacity.unwrap_or(compute_capacity); + if minimal_compute_capacity > compute_capacity { + return Err(anyhow::anyhow!( + "Minimal compute capacity ({}) cannot be greater than compute capacity ({})", + minimal_compute_capacity, + compute_capacity + )); + } + let launch_proof_request = LaunchProofRequest { data_id, compute_capacity, + minimal_compute_capacity, inputs_mode: inputs_mode.into(), inputs_uri, hints_mode: hints_mode.into(), diff --git a/distributed/crates/coordinator/src/cli/main.rs b/distributed/crates/coordinator/src/cli/main.rs index 9c67047a9..1020e5e05 100644 --- a/distributed/crates/coordinator/src/cli/main.rs +++ b/distributed/crates/coordinator/src/cli/main.rs @@ -34,6 +34,9 @@ struct ZiskCoordinatorArgs { )] no_save_proofs: bool, + #[arg(short = 'c', long, help = "Generate compressed proofs", default_value_t = false)] + compressed_proofs: bool, + /// Webhook URL to notify when a job finishes. /// /// The placeholder `{$job_id}` can be used in the URL and will be @@ -85,6 +88,9 @@ enum ZiskCoordinatorCommands { #[arg(long, short, help = "Compute capacity needed to generate the proof")] compute_capacity: u32, + #[arg(long, short, help = "Minimal compute capacity needed to generate the proof")] + minimal_compute_capacity: Option, + #[arg(long, help = "Simulated node ID")] simulated_node: Option, }, @@ -104,6 +110,7 @@ async fn main() -> Result<()> { direct_inputs, stream_hints, compute_capacity, + minimal_compute_capacity, simulated_node, }) => { // Run the "prove" subcommand @@ -115,6 +122,7 @@ async fn main() -> Result<()> { direct_inputs, stream_hints, compute_capacity, + minimal_compute_capacity, simulated_node, ) .await @@ -126,6 +134,7 @@ async fn main() -> Result<()> { args.port, args.proofs_dir, args.no_save_proofs, + args.compressed_proofs, args.webhook_url, ) .await diff --git a/distributed/crates/coordinator/src/config.rs b/distributed/crates/coordinator/src/config.rs index 7c18e7439..d374248ea 100644 --- a/distributed/crates/coordinator/src/config.rs +++ b/distributed/crates/coordinator/src/config.rs @@ -37,6 +37,7 @@ pub struct CoordinatorConfig { pub phase1_timeout_seconds: u64, pub phase2_timeout_seconds: u64, pub webhook_url: Option, + pub compressed_proofs: bool, } impl Config { @@ -50,6 +51,7 @@ impl Config { port: Option, proofs_dir: Option, no_save_proofs: bool, + compressed_proofs: bool, webhook_url: Option, ) -> Result { // Create proofs directory if it doesn't exist @@ -75,7 +77,8 @@ impl Config { .set_default("coordinator.max_workers_per_job", 10)? .set_default("coordinator.max_total_workers", 1000)? .set_default("coordinator.phase1_timeout_seconds", 300)? - .set_default("coordinator.phase2_timeout_seconds", 600)?; + .set_default("coordinator.phase2_timeout_seconds", 600)? + .set_default("coordinator.compressed_proofs", compressed_proofs)?; if let Some(path) = config_file { builder = builder.add_source(config::File::with_name(&path)); diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index f8cc286d9..b56b185ea 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -286,6 +286,13 @@ impl Coordinator { )); } + if request.minimal_compute_capacity > request.compute_capacity { + error!("Invalid requested minimal compute capacity"); + return Err(CoordinatorError::InvalidArgument( + "Minimal compute capacity must not exceed compute capacity".to_string(), + )); + } + // Check if we have enough capacity to compute the proof is already checked // in create_job > partition_and_allocate_by_capacity @@ -330,12 +337,14 @@ impl Coordinator { self.pre_launch_proof(&request)?; let required_compute_capacity = ComputeCapacity::from(request.compute_capacity); + let minimal_compute_capacity = ComputeCapacity::from(request.minimal_compute_capacity); // Create and configure a new job let mut job = self .create_job( request.data_id.clone(), required_compute_capacity, + minimal_compute_capacity, request.inputs_mode, request.hints_mode, request.simulated_node, @@ -531,6 +540,7 @@ impl Coordinator { &self, data_id: DataId, required_compute_capacity: ComputeCapacity, + minimal_compute_capacity: ComputeCapacity, inputs_mode: InputsModeDto, hints_mode: HintsModeDto, simulated_node: Option, @@ -543,7 +553,11 @@ impl Coordinator { let (selected_workers, mut partitions) = self .workers_pool - .partition_and_allocate_by_capacity(required_compute_capacity, execution_mode) + .partition_and_allocate_by_capacity( + required_compute_capacity, + minimal_compute_capacity, + execution_mode, + ) .await?; if let Some(simulated_node) = simulated_node { @@ -555,6 +569,7 @@ impl Coordinator { inputs_mode, hints_mode, required_compute_capacity, + minimal_compute_capacity, selected_workers, partitions, execution_mode, @@ -1774,15 +1789,7 @@ impl Coordinator { agg_proofs: proofs, last_proof: all_done, final_proof: all_done, - verify_constraints: true, - aggregation: true, - rma: true, - compressed: true, - verify_proofs: true, - save_proofs: false, - test_mode: false, - output_dir_path: "".to_string(), - minimal_memory: false, + compressed: self.config.coordinator.compressed_proofs, }), }; diff --git a/distributed/crates/coordinator/src/workers_pool.rs b/distributed/crates/coordinator/src/workers_pool.rs index 293d931a8..67827999f 100644 --- a/distributed/crates/coordinator/src/workers_pool.rs +++ b/distributed/crates/coordinator/src/workers_pool.rs @@ -427,6 +427,7 @@ impl WorkersPool { pub async fn partition_and_allocate_by_capacity( &self, required_compute_capacity: ComputeCapacity, + minimal_compute_capacity: ComputeCapacity, execution_mode: JobExecutionMode, ) -> CoordinatorResult<(Vec, Vec>)> { // Simulation mode requires exactly one worker @@ -445,6 +446,12 @@ impl WorkersPool { )); } + if minimal_compute_capacity.compute_units > required_compute_capacity.compute_units { + return Err(CoordinatorError::InvalidArgument( + "Minimal compute capacity cannot exceed required capacity".to_string(), + )); + } + let workers = self.workers.write().await; // For simulation mode, replicate single worker multiple times @@ -470,7 +477,7 @@ impl WorkersPool { available_workers.iter().map(|(_, p)| p.compute_capacity.compute_units).sum(); // Check if we have enough total capacity - if required_compute_capacity.compute_units > available_capacity { + if minimal_compute_capacity.compute_units > available_capacity { return Err(CoordinatorError::InsufficientCapacity); } @@ -496,11 +503,10 @@ impl WorkersPool { // Step 2: Distribute work units using round-robin allocation let num_workers = selected_workers.len(); - let total_units = required_compute_capacity.compute_units; let mut worker_allocations = vec![Vec::new(); num_workers]; // Round-robin assignment of compute units - for unit in 0..total_units { + for unit in 0..total_capacity { let worker_idx = (unit as usize) % num_workers; // Check if this worker still has capacity diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index ef8737147..ba615687c 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -55,11 +55,12 @@ message WorkersListRequest { message LaunchProofRequest { string data_id = 1; uint32 compute_capacity = 2; - InputMode inputs_mode = 3; - optional string inputs_uri = 4; - HintsMode hints_mode = 5; - optional string hints_uri = 6; - optional uint32 simulated_node = 7; // If set, indicates this is a simulated worker + uint32 minimal_compute_capacity = 3; + InputMode inputs_mode = 4; + optional string inputs_uri = 5; + HintsMode hints_mode = 6; + optional string hints_uri = 7; + optional uint32 simulated_node = 8; // If set, indicates this is a simulated worker } enum InputMode { @@ -260,15 +261,7 @@ message AggParams { ProofList agg_proofs = 1; bool last_proof = 2; bool final_proof = 3; - bool verify_constraints = 4; - bool aggregation = 5; - bool rma = 6; - bool compressed = 7; - bool verify_proofs = 8; - bool save_proofs = 9; - bool test_mode = 10; - string output_dir_path = 11; - bool minimal_memory = 12; + bool compressed = 4; } // Stream type enumeration diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index 81fc70b0f..84e49f267 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -169,6 +169,7 @@ impl From for LaunchProofRequest { LaunchProofRequest { data_id: dto.data_id.into(), compute_capacity: dto.compute_capacity, + minimal_compute_capacity: dto.minimal_compute_capacity, inputs_mode: inputs_mode.into(), inputs_uri, hints_mode: hints_mode.into(), @@ -187,6 +188,7 @@ impl TryFrom for LaunchProofRequestDto { Ok(LaunchProofRequestDto { data_id: req.data_id.into(), compute_capacity: req.compute_capacity, + minimal_compute_capacity: req.minimal_compute_capacity, inputs_mode: match InputMode::try_from(req.inputs_mode).unwrap_or(InputMode::None) { InputMode::None => InputsModeDto::InputsNone, InputMode::Path => { @@ -393,15 +395,7 @@ impl From for AggParams { agg_proofs: Some(ProofList { proofs: agg_proofs }), last_proof: dto.last_proof, final_proof: dto.final_proof, - verify_constraints: dto.verify_constraints, - aggregation: dto.aggregation, - rma: dto.rma, compressed: dto.compressed, - verify_proofs: dto.verify_proofs, - save_proofs: dto.save_proofs, - test_mode: dto.test_mode, - output_dir_path: dto.output_dir_path, - minimal_memory: dto.minimal_memory, } } } diff --git a/distributed/crates/worker/src/cli/main.rs b/distributed/crates/worker/src/cli/main.rs index 03c2fea27..7187e2cb0 100644 --- a/distributed/crates/worker/src/cli/main.rs +++ b/distributed/crates/worker/src/cli/main.rs @@ -1,8 +1,5 @@ use anyhow::Result; -use cargo_zisk::{ - commands::{get_proving_key, get_witness_computation_lib}, - ux::print_banner, -}; +use cargo_zisk::{commands::get_proving_key, ux::print_banner}; use clap::Parser; use colored::Colorize; use std::path::PathBuf; @@ -48,10 +45,6 @@ struct Cli { )] config: Option, - /// Witness computation dynamic library path - #[clap(short = 'w', long)] - pub witness_lib: Option, - /// ELF file path /// This is the path to the ROM file that the witness computation dynamic library will use /// to generate the witness. @@ -98,10 +91,6 @@ struct Cli { #[clap(long, default_value_t = false)] pub verify_constraints: bool, - /// Whether to generate the final vadcop proof compressed - #[clap(short = 'c', long, default_value_t = false)] - pub compressed: bool, - /// GPU parameters #[clap(short = 'z', long, default_value_t = false)] pub preallocate: bool, @@ -140,7 +129,6 @@ async fn main() -> Result<()> { let prover_config_dto = ProverServiceConfigDto { elf: cli.elf.clone(), - witness_lib: cli.witness_lib.clone(), asm: cli.asm.clone(), emulator: cli.emulator, proving_key: cli.proving_key.clone(), @@ -150,7 +138,6 @@ async fn main() -> Result<()> { debug: cli.debug.clone(), verify_constraints: cli.verify_constraints, aggregation: true, // we always aggregate - compressed: cli.compressed, preallocate: cli.preallocate, max_streams: cli.max_streams, number_threads_witness: cli.number_threads_witness, @@ -203,18 +190,13 @@ fn print_command_info( .map(|p| format!("(log file: {})", p).bright_black().to_string()) .unwrap_or_default() ); - println!( - "{: >12} {}", - "Witness Lib".bright_green().bold(), - get_witness_computation_lib(Some(&prover_config.witness_lib)).display() - ); - println!("{: >12} {}", "ELF".bright_green().bold(), prover_config.elf.display()); - if prover_config.asm.is_some() { + println!("{: >12} {}", "Elf".bright_green().bold(), prover_config.elf.display()); + if let Some(asm) = &prover_config.asm { if let Some(asm_port) = prover_config.asm_port.as_ref() { println!("{: >12} {}", "Asm port".bright_green().bold(), asm_port); } - let asm_path = prover_config.asm.as_ref().unwrap().display(); + let asm_path = asm.display(); println!("{: >12} {}", "ASM runner".bright_green().bold(), asm_path); } else { println!( diff --git a/distributed/crates/worker/src/config.rs b/distributed/crates/worker/src/config.rs index 6f94f5c37..8ff243122 100644 --- a/distributed/crates/worker/src/config.rs +++ b/distributed/crates/worker/src/config.rs @@ -145,7 +145,6 @@ impl WorkerServiceConfig { #[derive(Debug, Clone)] pub struct ProverServiceConfigDto { pub elf: PathBuf, - pub witness_lib: Option, pub asm: Option, pub emulator: bool, pub proving_key: Option, @@ -155,7 +154,6 @@ pub struct ProverServiceConfigDto { pub debug: Option>, pub verify_constraints: bool, pub aggregation: bool, - pub compressed: bool, pub preallocate: bool, pub max_streams: Option, pub number_threads_witness: Option, @@ -169,7 +167,6 @@ impl Default for ProverServiceConfigDto { fn default() -> Self { Self { elf: PathBuf::new(), - witness_lib: None, asm: None, emulator: false, proving_key: None, @@ -179,7 +176,6 @@ impl Default for ProverServiceConfigDto { debug: None, verify_constraints: false, aggregation: false, - compressed: true, preallocate: false, max_streams: None, number_threads_witness: None, diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index ca4e724d3..61a081893 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,6 +1,6 @@ use anyhow::Result; use asm_runner::HintsShmem; -use cargo_zisk::commands::{get_proving_key, get_witness_computation_lib}; +use cargo_zisk::commands::get_proving_key; use precompiles_hints::HintsProcessor; use proofman::{AggProofs, ContributionsInfo}; use rom_setup::{ @@ -57,9 +57,6 @@ pub struct ProverConfig { /// Path to the ELF file pub elf: PathBuf, - /// Path to the witness computation dynamic library - pub witness_lib: PathBuf, - /// Path to the ASM file (optional) pub asm: Option, @@ -96,9 +93,6 @@ pub struct ProverConfig { /// Flag to enable aggregation pub aggregation: bool, - /// Flag to enable vadcop final compressed proof - pub compressed: bool, - /// Preallocate resources pub gpu_params: ParamsGPU, @@ -205,21 +199,18 @@ impl ProverConfig { let mut custom_commits_map: HashMap = HashMap::new(); custom_commits_map.insert("rom".to_string(), rom_bin_path); let mut gpu_params = ParamsGPU::new(prover_service_config.preallocate); - if prover_service_config.max_streams.is_some() { - gpu_params.with_max_number_streams(prover_service_config.max_streams.unwrap()); + if let Some(max_streams) = prover_service_config.max_streams { + gpu_params.with_max_number_streams(max_streams); } - if prover_service_config.number_threads_witness.is_some() { - gpu_params.with_number_threads_pools_witness( - prover_service_config.number_threads_witness.unwrap(), - ); + if let Some(number_threads_witness) = prover_service_config.number_threads_witness { + gpu_params.with_number_threads_pools_witness(number_threads_witness); } - if prover_service_config.max_witness_stored.is_some() { - gpu_params.with_max_witness_stored(prover_service_config.max_witness_stored.unwrap()); + if let Some(max_witness_stored) = prover_service_config.max_witness_stored { + gpu_params.with_max_witness_stored(max_witness_stored); } Ok(ProverConfig { elf: prover_service_config.elf.clone(), - witness_lib: get_witness_computation_lib(prover_service_config.witness_lib.as_ref()), asm: prover_service_config.asm.clone(), asm_rom, custom_commits_map, @@ -231,7 +222,6 @@ impl ProverConfig { unlock_mapped_memory: prover_service_config.unlock_mapped_memory, verify_constraints: prover_service_config.verify_constraints, aggregation: prover_service_config.aggregation, - compressed: prover_service_config.compressed, gpu_params, shared_tables: prover_service_config.shared_tables, rma: prover_service_config.rma, @@ -279,7 +269,6 @@ impl Worker { .prove() .aggregation(true) .rma(true) - .witness_lib_path(prover_config.witness_lib.clone()) .proving_key_path(prover_config.proving_key.clone()) .elf_path(prover_config.elf.clone()) .verbose(prover_config.verbose) @@ -312,7 +301,6 @@ impl Worker { .prove() .aggregation(true) .rma(true) - .witness_lib_path(prover_config.witness_lib.clone()) .proving_key_path(prover_config.proving_key.clone()) .elf_path(prover_config.elf.clone()) .verbose(prover_config.verbose) @@ -430,7 +418,7 @@ impl Worker { ); let phase_inputs = proofman::ProvePhaseInputs::Contributions(proof_info); - let options = self.get_proof_options_partial_contribution(); + let options = self.get_proof_options(false); let mut serialized = borsh::to_vec(&( JobPhase::Contributions, @@ -464,7 +452,7 @@ impl Worker { let phase_inputs = proofman::ProvePhaseInputs::Internal(challenges); - let options = self.get_proof_options_prove(); + let options = self.get_proof_options(false); let mut serialized = borsh::to_vec(&(JobPhase::Prove, job_id, phase_inputs, options)).unwrap(); @@ -488,7 +476,7 @@ impl Worker { ) -> JoinHandle<()> { let prover = self.prover.clone(); - let options = self.get_proof_options_partial_contribution(); + let options = self.get_proof_options(false); tokio::spawn(async move { let guard = job.lock().await; @@ -699,7 +687,7 @@ impl Worker { ) -> JoinHandle<()> { let prover = self.prover.clone(); - let options = self.get_proof_options_prove(); + let options = self.get_proof_options(false); tokio::spawn(async move { let job = job.lock().await; @@ -769,6 +757,8 @@ impl Worker { ) -> JoinHandle<()> { let prover = self.prover.clone(); + let options = self.get_proof_options(agg_params.compressed); + tokio::spawn(async move { let job = job.lock().await; let job_id = job.job_id.clone(); @@ -785,8 +775,6 @@ impl Worker { }) .collect(); - let options = Self::get_proof_options_aggregation(&agg_params); - let result = prover.aggregate_proofs( agg_proofs, agg_params.last_proof, @@ -819,45 +807,17 @@ impl Worker { }) } - fn get_proof_options_partial_contribution(&self) -> ProofOptions { - ProofOptions { - verify_constraints: false, - aggregation: false, - compressed: false, - verify_proofs: true, - save_proofs: true, - test_mode: false, - output_dir_path: PathBuf::from("."), - rma: self.prover_config.rma, - minimal_memory: self.prover_config.minimal_memory, - } - } - - fn get_proof_options_prove(&self) -> ProofOptions { + fn get_proof_options(&self, compressed: bool) -> ProofOptions { ProofOptions { - verify_constraints: false, - aggregation: true, - compressed: true, + verify_constraints: self.prover_config.verify_constraints, + aggregation: self.prover_config.aggregation, verify_proofs: false, save_proofs: false, test_mode: false, - output_dir_path: PathBuf::default(), + output_dir_path: PathBuf::from("."), rma: self.prover_config.rma, minimal_memory: self.prover_config.minimal_memory, - } - } - - fn get_proof_options_aggregation(agg_params: &AggregationParams) -> ProofOptions { - ProofOptions { - verify_constraints: agg_params.verify_constraints, - aggregation: agg_params.aggregation, - rma: agg_params.rma, - compressed: agg_params.compressed, - verify_proofs: agg_params.verify_proofs, - save_proofs: agg_params.save_proofs, - test_mode: agg_params.test_mode, - output_dir_path: agg_params.output_dir_path.clone(), - minimal_memory: agg_params.minimal_memory, + compressed, } } diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 58fd29b73..2a3ac748b 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -719,17 +719,8 @@ impl WorkerNodeGrpc { agg_proofs, last_proof: agg_params.last_proof, final_proof: agg_params.final_proof, - verify_constraints: agg_params.verify_constraints, - aggregation: agg_params.aggregation, - rma: agg_params.rma, compressed: agg_params.compressed, - verify_proofs: agg_params.verify_proofs, - save_proofs: agg_params.save_proofs, - test_mode: agg_params.test_mode, - output_dir_path: PathBuf::from(agg_params.output_dir_path), - minimal_memory: agg_params.minimal_memory, }; - self.worker.set_current_computation( self.worker.handle_aggregate(job, agg_params, computation_tx.clone()).await, ); diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 2d006d7ae..218ad9824 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -11,6 +11,7 @@ endif # Default EMU_PATH and OUT_PATH EMU_PATH ?= src/emu.asm OUT_PATH ?= build/ziskemuasm +DMA_OBJS = build/dma/fast_dma_encode.o build/dma/memcpy_fast.o build/dma/direct_memcpy_mtrace.o build/dma/direct_memcpy_mops.o # Ensure the output directory exists OUT_DIR := $(dir $(OUT_PATH)) @@ -22,10 +23,17 @@ build/emu.o: $(EMU_PATH) mkdir -p build as $(ASMFLAGS) -o build/emu.o $< +build/dma/%.o: src/dma/%.asm src/dma/dma_constants.inc + mkdir -p build/dma + as $(ASMFLAGS) -I./src/dma -o $@ $< + +build/dma.o: $(DMA_OBJS) + ld -r $(DMA_OBJS) -o $@ + # Compile the final executable -$(OUT_PATH): build/emu.o src/main.c src/emu.c src/chfast/keccak.c +$(OUT_PATH): build/emu.o src/main.c src/emu.c src/chfast/keccak.c build/dma.o mkdir -p $(OUT_DIR) - gcc $(CFLAGS) src/main.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ + gcc $(CFLAGS) src/main.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ clean: rm -rf build diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index c632e08ba..98e4a390b 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -263,7 +263,6 @@ impl AsmRunnerMO { // _stats.add_stat(i); // } // } - preloaded.handle_mo = Some(std::thread::spawn(move || { drop(mem_planner); MemPlanner::new() diff --git a/emulator-asm/asm-runner/src/asm_runner.rs b/emulator-asm/asm-runner/src/asm_runner.rs index 71d704036..1be6fbadc 100644 --- a/emulator-asm/asm-runner/src/asm_runner.rs +++ b/emulator-asm/asm-runner/src/asm_runner.rs @@ -119,8 +119,8 @@ impl AsmRunnerOptions { /// # Arguments /// * `command` - A mutable reference to the `Command` to be modified. pub fn apply_to_command(&self, command: &mut Command, asm_service: &AsmService) { - let port = if self.base_port.is_some() { - AsmServices::port_for(asm_service, self.base_port.unwrap(), self.local_rank) + let port = if let Some(base_port) = self.base_port { + AsmServices::port_for(asm_service, base_port, self.local_rank) } else { AsmServices::default_port(asm_service, self.local_rank) }; diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 414a81458..c9845776d 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -272,26 +272,29 @@ impl AsmServices { )); } - // read_exact will block until all 40 bytes are available or timeout/error occurs - let mut in_buffer = [0u8; 40]; - let mut total_read = 0; + let total_timeout = Duration::from_secs(120); + let start = Instant::now(); - if let Err(e) = stream.read_exact(&mut in_buffer) { - // Try to read what's available to show partial data - let mut partial_buffer = vec![0u8; 1024]; - if let Ok(n) = stream.read(&mut partial_buffer) { - total_read += n; + // Read exactly 40 bytes + let mut in_buffer = [0u8; 40]; + loop { + if start.elapsed() >= total_timeout { + return Err(anyhow::anyhow!("Total timeout exceeded")); } - return Err(anyhow::anyhow!( - "Failed to read full response payload (expected 40 bytes, got {} bytes) \ - from service {} on {}: {} (error kind: {:?})", - total_read, - service, - addr, - e, - e.kind(), - )); + match stream.read_exact(&mut in_buffer) { + Ok(_) => break, + Err(e) + if e.kind() == std::io::ErrorKind::TimedOut + || e.kind() == std::io::ErrorKind::WouldBlock => + { + tracing::debug!("Read timeout after {:?}, retrying...", start.elapsed()); + continue; + } + Err(e) => { + return Err(e.into()); + } + } } // Decode bytes into ResponseData @@ -326,7 +329,7 @@ impl AsmServices { // Wait for the shutdown signal (up to 30s) loop { - match sem.timed_wait(Duration::from_secs(30)) { + match sem.timed_wait(Duration::from_secs(60)) { Ok(_) => break, Err(named_sem::Error::WaitFailed(e)) if e.kind() == std::io::ErrorKind::Interrupted => diff --git a/emulator-asm/src/dma/Makefile b/emulator-asm/src/dma/Makefile new file mode 100644 index 000000000..0f82efa4d --- /dev/null +++ b/emulator-asm/src/dma/Makefile @@ -0,0 +1,33 @@ +# Makefile for encode_memcpy_inline test + +CXX = g++ +CXXFLAGS = -std=c++17 -O0 -Wall -Wextra -g -no-pie +# CXXFLAGS = -std=c++17 -O2 -Wall -Wextra -g -no-pie +LDFLAGS = -no-pie +ASMFLAGS = -g --noexecstack -I$(CURDIR) + +SRCS_ASM = direct_memcpy_mtrace.asm memcpy_fast.asm fast_dma_encode.asm direct_memcpy_mops.asm +SRCS_CPP = test_dma.cpp +OBJS_ASM = direct_memcpy_mtrace.o memcpy_fast.o fast_dma_encode.o direct_memcpy_mops.o +OBJS_CPP = $(SRCS_CPP:.cpp=.o) +OBJS = $(OBJS_ASM) $(OBJS_CPP) +TARGET = test_dma + +all: $(TARGET) + +%.o: %.asm + as $(ASMFLAGS) -o $@ $< + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +$(TARGET): $(OBJS) + $(CXX) $(LDFLAGS) -o $@ $^ + +test: $(TARGET) + ./$(TARGET) + +clean: + rm -f $(OBJS) $(TARGET) + +.PHONY: all test_dma clean diff --git a/emulator-asm/src/dma/direct_memcpy_mops.asm b/emulator-asm/src/dma/direct_memcpy_mops.asm new file mode 100644 index 000000000..d256096a4 --- /dev/null +++ b/emulator-asm/src/dma/direct_memcpy_mops.asm @@ -0,0 +1,330 @@ +.intel_syntax noprefix +.code64 + +################################################################################ +# memcpy_mops - Optimized version with memory ops tracing and actual copy +# +# This function performs two main tasks: +# 1. Records all addresses of memory operations (read and write addresses) +# 2. Performs the actual memory copy from src to dst (with overlap handling) +# +# REGISTER USAGE: +# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r9, r11, r12, r13 +# Does NOT use XMM registers (caller doesn't need to save them) +# Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) +# +# PARAMETERS (NON System V AMD64 ABI): +# rdi -> rbx = dst (u64) - Destination address +# rsi -> rax = src (u64) - Source address +# rdx -> count (usize) - Number of bytes to copy +# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) +# +# MEMORY COPY BEHAVIOR: +# - Handles overlapping src/dst correctly (like memmove) +# - For non-overlapping: optimized copy using pre_count/loop_count/post_count +# - For overlapping: backward byte-by-byte copy to avoid corruption +################################################################################ + +.global direct_dma_memcpy_mops +.global dma_memcpy_mops +.extern fast_dma_encode + +.include "dma_constants.inc" +.equ MOPS_ALIGNED_READ_2W, ((2 << MOPS_BLOCK_WORDS_SBITS) + MOPS_ALIGNED_BLOCK_READ) +.equ LOOP_COUNT_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_SBITS - LOOP_COUNT_SBITS) +.equ PRE_WRITES_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_SBITS - PRE_WRITES_SBITS) + +.section .text + +# mov rdx, [0xA000_0F00] # save count before call +# mov rdi, rbx # rdi = dst (rbx) +# mov rsi, rax # rsi = src (rax) + +# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) + +# call function with standard ABI call +dma_memcpy_mops: + + # save registers used + push r12 # register used as mops base address + push r13 # register used as mops index + push rbx # + + mov r12, rcx + xor r13, r13 + call direct_dma_memcpy_mops + + mov rax, r13 + pop rbx + pop r13 + pop r12 + + ret + +# call directly from assembly without standard ABI call +# more eficient + +direct_dma_memcpy_mops: + + # updated registers: + # r9 = no save (value_reg) + # rcx = no save (available from asm) + # rdi = no save (available from asm) + # rsi = no save (available from asm) + # r13 = with new mops index (output) + # rax = encoded + + # Call fast_dma_encode to calculate encoding + # Parameters already in correct registers: rdi=dst, rsi=src, rdx=count + # Result will be returned in rax (encoded value) + + call fast_dma_encode # ~15-20 cycles - table lookup encoding + + # add read parameter count to mops + + mov r9, (MOPS_ALIGNED_READ + EXTRA_PARAMETER_ADDR) + mov [r12 + r13 * 8], r9 + inc r13 + + # check if count is zero + test rdx, rdx # compare count + jz .L_done # jump if zero + +.L_pre_dst_to_mops: + # If pre_count > 0, write aligned dst value to trace + test rax, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_post_dst_to_mops # 2 cycles (predicted taken) + +.L_pre_is_active: + # Branch with pre_count > 0: save original dst value before it's overwritten + mov r9, MOPS_ALIGNED_READ # r9 = flags aligned read + add r9, rdi # 1 cycle - get original dst + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov [r12 + r13 * 8], r9 # ~4 cycles - write dst pre-address to trace + + test rax, DOUBLE_SRC_PRE_MASK + jnz .L_pre_double_src_to_mops + +.L_pre_single_src_to_mops: + + mov r9, MOPS_ALIGNED_READ # r9 = flags aligned read + add r9, rsi # 1 cycle - get original src + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + jmp .L_pre_src_inc_mops_index + +.L_pre_double_src_to_mops: + + mov r9, MOPS_ALIGNED_READ_2W # r9 = flags double read + add r9, rsi # 1 cycle - get original src + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + +.L_pre_src_inc_mops_index: + add r13, 2 # add 2 (pre-write, block single/dual src) + +.L_post_dst_to_mops: + + # If post_count > 0, write aligned (dst+count) value to trace + test rax, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_src_to_mops # 2 cycles (predicted taken) - skip to src copy + +.L_post_is_active: + mov rcx, MOPS_ALIGNED_READ # rcx = flags aligned read + lea r9, [rdi + rdx - 1] # 1 cycle - r9 = dst + count - 1 (last dst byte) + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + add r9, rcx # 1 cycle - r9 mops with dst aligned address + mov [r12 + r13 * 8], r9 # ~4 cycles - write dst post-value to trace + + mov r9, rax + shr r9, PRE_AND_LOOP_BYTES_SBITS + add r9, rsi + and r9, ALIGN_MASK + + test rax, DOUBLE_SRC_POST_MASK + jnz .L_post_double_src_to_mops + +.L_post_single_src_to_mops: + + mov rcx, MOPS_ALIGNED_READ # r9 = flags aligned read + add r9, rcx # 1 cycle - get original src + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + jmp .L_post_src_inc_mops_index + +.L_post_double_src_to_mops: + + mov rcx, MOPS_ALIGNED_READ_2W # r9 = flags aligned read + add r9, rcx # 1 cycle - get original src + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + +.L_post_src_inc_mops_index: + add r13, 2 # add 2 (pre-write, block single/dual src) + +.L_src_to_mops: + mov rcx, rax # 1 cycle - rcx = encoded + shr rcx, LOOP_COUNT_SBITS # 1 cycle - rcx = loop (32 bits) + jz .L_save_dst_with_loop_count_zero + shl rcx, MOPS_BLOCK_WORDS_SBITS # 1 cycle - rcx = loop | 0 (4 bits) | (32 bits) + + test rax, UNALIGNED_DST_SRC_MASK + jnz .L_src_extra_for_unaligned_loop + + mov r9, MOPS_ALIGNED_BLOCK_READ # 1 cycle - r9 = read block + jmp .L_src_block_before_address + +.L_src_extra_for_unaligned_loop: + mov r9, MOPS_ALIGNED_BLOCK_READ + MOPS_BLOCK_ONE_WORD + +.L_src_block_before_address: + add r9, rcx + test rax, SRC64_INC_BY_PRE_MASK + jnz .L_src_incr_by_pre + add r9, rsi # 1 cycle - rcx = first block src address + jmp .L_src_to_mops_ready + +.L_src_incr_by_pre: + lea r9, [rsi + r9 + 8] + +.L_src_to_mops_ready: + and r9, ALIGN_MASK + mov [r12 + r13 * 8], r9 # ~4 cycles - write first block src read address + inc r13 + +.L_save_dst_addr_reusing_rcx: + + mov r9, rax # rcx = encoded + and r9, PRE_WRITES_MASK # rcx = pre_writes mask + shl r9, PRE_WRITES_TO_MOPS_BLOCK # rcx = pre_writes offset (with correct shift) + add r9, rcx + add r9, rdi + + mov rcx, MOPS_ALIGNED_BLOCK_WRITE + add r9, rcx + and r9, ALIGN_MASK + + mov [r12 + r13 * 8], r9 + inc r13 + jmp .L_mops_done + +.L_save_dst_with_loop_count_zero: + mov r9, rax # rcx = encoded + and r9, PRE_WRITES_MASK # rcx = pre_writes mask + shl r9, PRE_WRITES_TO_MOPS_BLOCK # rcx = pre_writes offset (with correct shift) + add r9, rdi + + mov rcx, MOPS_ALIGNED_BLOCK_WRITE + add r9, rcx + and r9, ALIGN_MASK + + mov [r12 + r13 * 8], r9 + inc r13 + +.L_mops_done: + + # Check for memory overlap to decide copy direction + # NOTE: rdi and rsi contain their ORIGINAL values (not modified in mops section) + # Overlap exists if: src < dst < src+count (forward overlap) + cmp rdi, rsi # 1 cycle - compare dst with src + jb .L_copy_forward # 2 cycles (predicted) - dst < src, no overlap + lea r9, [rsi + rdx] # 1 cycle - r9 = src + count + cmp rdi, r9 # 1 cycle - compare dst with (src+count) + jae .L_copy_forward # 2 cycles (predicted) - dst >= src+count, no overlap + + # Overlap detected (src < dst < src+count), must copy backward + # Setup: rsi = src+count-1, rdi = dst+count-1, rcx = count + # Uses ORIGINAL rsi and rdi values (not modified during mops recording) + + lea rsi, [rsi + rdx - 1] # 1 cycle - rsi = src + count - 1 (from original) + lea rdi, [rdi + rdx - 1] # 1 cycle - rdi = dst + count - 1 (from original) + mov rcx, rdx # 1 cycle - rcx = count + + std # ~20-50 cycles - set DF (serializing, pipeline flush) + rep movsb # ~3-5 cycles per byte (backward copy, slower than forward) + cld # ~20-50 cycles - clear DF (serializing, pipeline flush) + + ret # ~5 cycles + +.L_copy_forward: + # No overlap detected, perform optimized forward copy + cmp rdx, 16 # 1 cycle - check if count >= 16 (worth alignment) + jae .L_copy_forward_pre # 2 cycles (predicted) - use 3-phase aligned copy + + # Small copy (count < 16): copy all bytes directly + mov rcx, rdx # 1 cycle - rcx = count + rep movsb # ~3-5 cycles per byte (unaligned small copy) + + ret # ~5 cycles + +.L_copy_forward_pre: + # Copy in 3 phases: pre-alignment bytes, aligned qwords, post-alignment bytes + # If pre_count > 0, copy unaligned prefix bytes + test rax, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_copy_forward_loop # 2 cycles (predicted) + + # Extract and copy pre_count bytes (1-7 bytes to reach alignment) + mov rcx, rax # 1 cycle + and rcx, PRE_COUNT_MASK # 1 cycle - rcx = pre_count (bits 0-3) + + rep movsb # ~3-5 cycles per byte + # rsi, rdi now 8-byte aligned + +.L_copy_forward_loop: + # Copy aligned qwords (main bulk of data) + mov rcx, rax # 1 cycle + shr rcx, LOOP_COUNT_SBITS # 1 cycle - rcx = loop_count (bits 32-63) + rep movsq # ~1.5-2 cycles per qword (aligned, optimized) + # rsi, rdi advanced by loop_count * 8 + +.L_check_forward_post: + + # If post_count > 0, copy remaining unaligned suffix bytes + test rax, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_done # 2 cycles (predicted) + + # Extract and copy post_count bytes (1-7 bytes after aligned data) + mov rcx, rax # 1 cycle + shr rcx, POST_COUNT_SBITS # 1 cycle - shift post_count to position + and rcx, 0x07 # 1 cycle - rcx = post_count (bits 43-45) + + rep movsb # ~3-5 cycles per byte + # rsi, rdi now point past end of data + +.L_done: + ret # ~5 cycles + +# Performance estimate (Modern x86-64, L1 cache hits): +# +# NON-OVERLAPPING FORWARD COPY PATH: +# - fast_dma_encode call: ~15-20 cycles (function call + table lookup) +# - Write mops entries: ~4-6 cycles per entry +# - Pre-read mops (conditional): ~12 cycles (if pre_count > 0) +# - Post-read mops (conditional): ~12 cycles (if post_count > 0) +# - Block src read mops: ~8-12 cycles (address calculation + write) +# - Pre-bytes copy: ~3-5 cycles per byte (if pre_count > 0, max 7 bytes) +# - Aligned qwords copy: ~1.5-2 cycles per qword (rep movsq, main data) +# - Post-bytes copy: ~3-5 cycles per byte (if post_count > 0, max 7 bytes) +# - Function overhead: ~10 cycles (push/pop, branches, return) +# +# TOTAL (best case, aligned, no pre/post): +# ~30 cycles base + ~2 cycles per qword (trace + copy) +# +# TOTAL (typical case, some alignment): +# ~50 cycles base + ~2 cycles per qword + ~4 cycles per pre/post byte +# +# OVERLAPPING BACKWARD COPY PATH: +# - Same mops overhead: ~30-50 cycles +# - std instruction: ~20-50 cycles (serializing, causes pipeline flush) +# - Backward byte copy: ~3-5 cycles per byte (rep movsb backward) +# - cld instruction: ~20-50 cycles (serializing, causes pipeline flush) +# +# TOTAL (overlap, worst case): +# ~100-150 cycles base + ~4-5 cycles per byte +# +# NOTES: +# - Assumes L1 cache hits for all memory accesses +# - rep movsq/movsb performance varies by microarchitecture +# - Actual cycles may vary ±20% depending on CPU model and memory alignment +# - Fast path (aligned, no overlap) is ~2-3x faster than overlap path + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/direct_memcpy_mtrace.asm b/emulator-asm/src/dma/direct_memcpy_mtrace.asm new file mode 100644 index 000000000..b906f00f3 --- /dev/null +++ b/emulator-asm/src/dma/direct_memcpy_mtrace.asm @@ -0,0 +1,354 @@ +.intel_syntax noprefix +.code64 + +# +# memcpy_mtrace - Optimized version with memory tracing and actual copy +# +# This function performs three main tasks: +# 1. Encodes memcpy metadata (offsets, counts, flags) using fast_dma_encode +# 2. Records memory trace (pre-values and src data for verification/rollback) +# 3. Performs the actual memory copy from src to dst (with overlap handling) +# +# REGISTER USAGE: +# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r9, r12, r13 +# Does NOT use XMM registers (caller doesn't need to save them) +# Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) +# +# PARAMETERS (NON System V AMD64 ABI): +# rdi = dst (u64) - Destination address +# rsi = src (u64) - Source address +# rdx = count (usize) - Number of bytes to copy +# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) +# +# RETURN VALUE: +# RAX = Number of 64-bit words written to trace buffer +# +# MEMORY TRACE FORMAT (written to trace buffer sequentially): +# [0] = Encoded metadata (64-bit value with offsets, counts, flags) +# [1] = Pre-write value at aligned(dst) IF pre_count > 0 +# [1 or 2] = Post-write value at aligned(dst+count) IF post_count > 0 +# [...] = All aligned qwords from aligned(src) to aligned(src+count) +# +# The trace buffer captures: +# - Original destination values (for undo/verification) +# - Source data (for verification) +# - Metadata needed to reconstruct the operation +# +# MEMORY COPY BEHAVIOR: +# - Handles overlapping src/dst correctly (like memmove) +# - For non-overlapping: optimized copy using pre_count/loop_count/post_count +# - For overlapping: backward byte-by-byte copy to avoid corruption +# +# PERFORMANCE: +# - Encoding: ~15-20 cycles (function call to fast_dma_encode, table lookup) +# - Trace writes: ~4 cycles per qword write +# - Src data copy to trace: ~1.5-2 cycles per qword (rep movsq) +# - Final memcpy (non-overlap): ~1.5-2 cycles per qword (rep movsq aligned) +# - Final memcpy (overlap): ~100-150 cycles overhead + ~4-5 cycles per byte (std/rep movsb/cld) +# +# SIDE EFFECTS: +# - Modifies memory at dst (count bytes) +# - Modifies trace buffer (variable size depending on pre/post counts) +# - Preserves direction flag (cld called after any std) + +.global direct_dma_memcpy_mtrace +.global dma_memcpy_mtrace +.global direct_dma_memcpy_mtrace_with_count_check + +.extern fast_dma_encode +.extern trace_address_threshold + + +.include "dma_constants.inc" + +.section .text + +.set R_MT_INDEX, r13 +.set R_MT_ADDR, r12 +.set R_STEP, r15 +.set R_AUX, r9 +.set R_AUX2, rcx # NOTE: used by rep +.set R_SRC, rsi # NOTE: used by rep +.set R_DST, rdi # NOTE: used by rep +.set R_COUNT, rdx +.set R_ENCODE, rax + +dma_memcpy_mtrace: + push R_MT_ADDR # ~3 cycles - save callee-saved register + push R_MT_INDEX # ~3 cycles - save callee-saved register + push R_AUX # ~3 cycles - save callee-saved register + push rbx # ~3 cycles - save callee-saved register + + mov R_MT_ADDR, R_COUNT # 1 cycle - setup trace address from count + xor R_MT_INDEX, R_MT_INDEX # 1 cycle - initialize trace index to 0 + call direct_dma_memcpy_mtrace # ~5 cycles + function cost + + mov R_ENCODE, R_MT_INDEX # 1 cycle - return trace index in R_ENCODE + pop rbx # ~3 cycles - restore register + pop R_AUX # ~3 cycles - restore register + pop R_MT_INDEX # ~3 cycles - restore register + pop R_MT_ADDR # ~3 cycles - restore register + + ret # ~5 cycles + +.L_memcpy_check_mtrace_available: + + # trace_address_threshold containt the address "limit" before call _realloc_trace + # trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE + + # calculate bytes of mtrace used and verify if throw the limit + + lea R_AUX, [R_MT_ADDR + 8 * R_MT_INDEX] # 1 cycle - calculate address mtrace + lea R_AUX, [R_AUX + R_COUNT + MAX_DMA_MT_MARGIN] # 1 cycle - calculate current mtrace bytes usage + sub R_AUX, [trace_address_threshold] # ~4 cycles - bytes over threshold (can be negative) + jc .L_memcpy_mtrace_continue # 2 cycles (predicted) - negative means space available + + # check if bytes over threshold are usual for current situation on inside chunk + # R_STEP contain number the steps to end of chunk, we need number the steps consumed + + mov R_AUX2, CHUNK_SIZE # 1 cycle - load chunk size constant + sub R_AUX2, R_STEP # 1 cycle - calculate steps consumed in chunk + imul R_AUX2, MAX_BYTES_MTRACE_STEP # ~3 cycles - bytes expected for consumed steps + cmp R_AUX2, R_AUX # 1 cycle - compare expected vs actual + jae .L_memcpy_mtrace_continue # 2 cycles (predicted) - expected >= actual, ok + + # at this point we need to increase trace, registers R_ENCODE, R_AUX no need to save. + push R_COUNT # ~3 cycles - save general purpose registers + push r8 # ~3 cycles + push r10 # ~3 cycles + push r11 # ~3 cycles + push R_SRC # ~3 cycles + push R_DST # ~3 cycles + + # IMPORTANT: inside call means unaligned to 16 bits + + sub rsp, 16*16 + 8 # 1 cycle - allocate stack space for 16 XMM registers + + movaps [rsp + 0*16], xmm0 # ~4 cycles - save XMM registers (aligned stores) + movaps [rsp + 1*16], xmm1 # ~4 cycles + movaps [rsp + 2*16], xmm2 # ~4 cycles + movaps [rsp + 3*16], xmm3 # ~4 cycles + movaps [rsp + 4*16], xmm4 # ~4 cycles + movaps [rsp + 5*16], xmm5 # ~4 cycles + movaps [rsp + 6*16], xmm6 # ~4 cycles + movaps [rsp + 7*16], xmm7 # ~4 cycles + movaps [rsp + 8*16], xmm8 # ~4 cycles + movaps [rsp + 9*16], xmm9 # ~4 cycles + movaps [rsp + 10*16], xmm10 # ~4 cycles + movaps [rsp + 11*16], xmm11 # ~4 cycles + movaps [rsp + 12*16], xmm12 # ~4 cycles + movaps [rsp + 13*16], xmm13 # ~4 cycles + movaps [rsp + 14*16], xmm14 # ~4 cycles + movaps [rsp + 15*16], xmm15 # ~4 cycles + + call _realloc_trace # ~5 cycles + function cost (~100-500 cycles) + + movaps xmm0, [rsp + 0*16] # ~4 cycles - restore XMM registers (aligned loads) + movaps xmm1, [rsp + 1*16] # ~4 cycles + movaps xmm2, [rsp + 2*16] # ~4 cycles + movaps xmm3, [rsp + 3*16] # ~4 cycles + movaps xmm4, [rsp + 4*16] # ~4 cycles + movaps xmm5, [rsp + 5*16] # ~4 cycles + movaps xmm6, [rsp + 6*16] # ~4 cycles + movaps xmm7, [rsp + 7*16] # ~4 cycles + movaps xmm8, [rsp + 8*16] # ~4 cycles + movaps xmm9, [rsp + 9*16] # ~4 cycles + movaps xmm10, [rsp + 10*16] # ~4 cycles + movaps xmm11, [rsp + 11*16] # ~4 cycles + movaps xmm12, [rsp + 12*16] # ~4 cycles + movaps xmm13, [rsp + 13*16] # ~4 cycles + movaps xmm14, [rsp + 14*16] # ~4 cycles + movaps xmm15, [rsp + 15*16] # ~4 cycles + + add rsp, 16*16 +8 # 1 cycle - deallocate stack space + + pop R_DST # ~3 cycles - restore general purpose registers + pop R_SRC # ~3 cycles + pop r11 # ~3 cycles + pop r10 # ~3 cycles + pop r8 # ~3 cycles + pop R_COUNT # ~3 cycles + + jmp .L_memcpy_mtrace_continue + +direct_dma_memcpy_mtrace_with_count_check: + + # Call fast_dma_encode to calculate encoding + # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count + # Result will be returned in R_ENCODE (encoded value) + + cmp R_COUNT, MAX_BYTES_DIRECT_MTRACE # 1 cycle - check if count exceeds direct threshold + ja .L_memcpy_check_mtrace_available # 2 cycles (not taken usually) - large count, check trace space + +.L_memcpy_mtrace_continue: +direct_dma_memcpy_mtrace: + + # Call fast_dma_encode to calculate encoding + # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count + # Result will be returned in R_ENCODE (encoded value) + + call fast_dma_encode # ~15-20 cycles - table lookup encoding + + mov [R_MT_ADDR + R_MT_INDEX * 8], R_ENCODE # ~4 cycles - write encoded result to mem trace + inc R_MT_INDEX # 1 cycle - advance R_MT_INDEX (mem trace index) + +.L_pre_dst_to_mtrace: + # If pre_count > 0, write aligned dst value to trace + test R_ENCODE, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_post_dst_to_mtrace # 2 cycles (predicted taken) + + # Branch with pre_count > 0: save original dst value before it's overwritten + mov R_AUX, R_DST # 1 cycle - get original dst + and R_AUX, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov R_AUX, [R_AUX] # ~4 cycles - read qword from aligned dst + mov [R_MT_ADDR + R_MT_INDEX * 8], R_AUX # ~4 cycles - write dst pre-value to trace + inc R_MT_INDEX # 1 cycle - advance trace index + +.L_post_dst_to_mtrace: + + # If post_count > 0, write aligned (dst+count) value to trace + test R_ENCODE, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_src_to_mtrace # 2 cycles (predicted taken) - skip to src copy + + lea R_AUX, [R_DST + R_COUNT - 1] # 1 cycle - R_AUX = dst + count - 1 (last dst byte) + and R_AUX, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov R_AUX, [R_AUX] # ~4 cycles - read qword at (dst+count) aligned + mov [R_MT_ADDR + R_MT_INDEX * 8], R_AUX # ~4 cycles - write dst post-value to trace + inc R_MT_INDEX # 1 cycle - advance trace index + +.L_src_to_mtrace: + # Copy source data to trace buffer + # Total qwords = loop_count (bits 0-31) + extra_src_reads (bits 48-50) + + mov R_AUX2, R_ENCODE # 1 cycle - R_AUX2 = encoded + shr R_AUX2, LOOP_COUNT_SBITS # 1 cycle - R_AUX2 = loop_count (bits 32-63) + + mov R_AUX, R_ENCODE # 1 cycle - R_AUX = encoded + shr R_AUX, EXTRA_SRC_READS_SBITS # 1 cycle - shift extra_src_reads to position + and R_AUX, 0x03 # 1 cycle - R_AUX = extra_src_reads (bits 48-50) + add R_AUX2, R_AUX # 1 cycle - R_AUX2 = total qwords to copy + + # Setup for rep movsq: copy from aligned src to trace buffer + mov R_AUX, R_SRC # 1 cycle - preserve original src pointer + and R_SRC, ALIGN_MASK # 1 cycle - R_SRC = src aligned to 8 bytes + + push R_DST # ~3 cycles - save dst pointer + lea R_DST, [R_MT_ADDR + R_MT_INDEX * 8] # 1 cycle - R_DST = trace buffer destination + add R_MT_INDEX, R_AUX2 # 1 cycle - advance trace index by qwords copied + + rep movsq # ~1.5-2 cycles per qword (hardware optimized) + + pop R_DST # ~3 cycles - restore dst pointer + mov R_SRC, R_AUX # 1 cycle - restore original src pointer + +.L_mtrace_done: + # Check for memory overlap to decide copy direction + # NOTE: R_DST and R_SRC now contain their ORIGINAL values (restored above) + # Overlap exists if: src < dst < src+count (forward overlap) + cmp R_DST, R_SRC # 1 cycle - compare dst with src + jb .L_copy_forward # 2 cycles (predicted) - dst < src, no overlap + lea R_AUX, [R_SRC + R_COUNT] # 1 cycle - R_AUX = src + count + cmp R_DST, R_AUX # 1 cycle - compare dst with (src+count) + jae .L_copy_forward # 2 cycles (predicted) - dst >= src+count, no overlap + + # Overlap detected (src < dst < src+count), must copy backward + # Setup: R_SRC = src+count-1, R_DST = dst+count-1, R_AUX2 = count + # Uses ORIGINAL R_SRC and R_DST values (restored from R_AUX and stack) + + lea R_SRC, [R_SRC + R_COUNT - 1] # 1 cycle - R_SRC = src + count - 1 (from original) + lea R_DST, [R_DST + R_COUNT - 1] # 1 cycle - R_DST = dst + count - 1 (from original) + mov R_AUX2, R_COUNT # 1 cycle - R_AUX2 = count + + std # ~20-50 cycles - set DF (serializing, pipeline flush) + rep movsb # ~3-5 cycles per byte (backward copy, slower than forward) + cld # ~20-50 cycles - clear DF (serializing, pipeline flush) + + ret # ~5 cycles + +.L_copy_forward: + # No overlap detected, perform optimized forward copy + cmp R_COUNT, 16 # 1 cycle - check if count >= 16 (worth alignment) + jae .L_copy_forward_pre # 2 cycles (predicted) - use 3-phase aligned copy + + # Small copy (count < 16): copy all bytes directly + mov R_AUX2, R_COUNT # 1 cycle - R_AUX2 = count + rep movsb # ~3-5 cycles per byte (unaligned small copy) + + ret # ~5 cycles + +.L_copy_forward_pre: + # Copy in 3 phases: pre-alignment bytes, aligned qwords, post-alignment bytes + + # If pre_count > 0, copy unaligned prefix bytes + + test R_ENCODE, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_copy_forward_loop # 2 cycles (predicted) + + # Extract and copy pre_count bytes (1-7 bytes to reach alignment) + + mov R_AUX2, R_ENCODE # 1 cycle + and R_AUX2, PRE_COUNT_MASK # 1 cycle - R_AUX2 = pre_count (bits 0-3) + + rep movsb # ~3-5 cycles per byte + # R_SRC, R_DST now 8-byte aligned + +.L_copy_forward_loop: + # Copy aligned qwords (main bulk of data) + mov R_AUX2, R_ENCODE # 1 cycle + shr R_AUX2, LOOP_COUNT_SBITS # 1 cycle - R_AUX2 = loop_count (bits 32-63) + rep movsq # ~1.5-2 cycles per qword (aligned, optimized) + # R_SRC, R_DST advanced by loop_count * 8 + +.L_check_forward_post: + + # If post_count > 0, copy remaining unaligned suffix bytes + test R_ENCODE, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_done # 2 cycles (predicted) + + # Extract and copy post_count bytes (1-7 bytes after aligned data) + mov R_AUX2, R_ENCODE # 1 cycle + shr R_AUX2, POST_COUNT_SBITS # 1 cycle - shift post_count to position + and R_AUX2, 0x07 # 1 cycle - R_AUX2 = post_count (bits 43-45) + + rep movsb # ~3-5 cycles per byte + # R_SRC, R_DST now point past end of data + +.L_done: + ret # ~5 cycles + +# Performance estimate (Modern x86-64, L1 cache hits): +# +# NON-OVERLAPPING FORWARD COPY PATH: +# - fast_dma_encode call: ~15-20 cycles (function call + table lookup) +# - Write encoding to trace: ~4 cycles +# - Pre-value trace (conditional): ~12 cycles (if pre_count > 0) +# - Post-value trace (conditional): ~12 cycles (if post_count > 0) +# - Source data to trace: ~1.5-2 cycles per qword (rep movsq) +# - Pre-bytes copy: ~3-5 cycles per byte (if pre_count > 0, max 7 bytes) +# - Aligned qwords copy: ~1.5-2 cycles per qword (rep movsq, main data) +# - Post-bytes copy: ~3-5 cycles per byte (if post_count > 0, max 7 bytes) +# - Function overhead: ~10 cycles (push/pop, branches, return) +# +# TOTAL (best case, aligned, no pre/post): +# ~30 cycles base + ~2 cycles per qword (trace + copy) +# +# TOTAL (typical case, some alignment): +# ~50 cycles base + ~2 cycles per qword + ~4 cycles per pre/post byte +# +# OVERLAPPING BACKWARD COPY PATH: +# - Same trace overhead: ~30-50 cycles +# - std instruction: ~20-50 cycles (serializing, causes pipeline flush) +# - Backward byte copy: ~3-5 cycles per byte (rep movsb backward) +# - cld instruction: ~20-50 cycles (serializing, causes pipeline flush) +# +# TOTAL (overlap, worst case): +# ~100-150 cycles base + ~4-5 cycles per byte +# +# NOTES: +# - Assumes L1 cache hits for all memory accesses +# - rep movsq/movsb performance varies by microarchitecture +# - Actual cycles may vary ±20% depending on CPU model and memory alignment +# - Fast path (aligned, no overlap) is ~2-3x faster than overlap path + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/dma_constants.inc b/emulator-asm/src/dma/dma_constants.inc new file mode 100644 index 000000000..058285e5f --- /dev/null +++ b/emulator-asm/src/dma/dma_constants.inc @@ -0,0 +1,71 @@ +.intel_syntax noprefix + +# GENERAL CONSTANTS + +.equ MAX_MTRACE_REGS_ACCESS_SIZE, (2 + 2 + 3) * 8 +.equ CHUNK_SIZE, (1 << 18) +.equ MAX_TRACE_CHUNK_INFO, ((44*8) + 32) +.equ MAX_BYTES_DIRECT_MTRACE, 256 +.equ MAX_BYTES_MTRACE_STEP, (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) +.equ MAX_CHUNK_TRACE_SIZE, CHUNK_SIZE * MAX_BYTES_MTRACE_STEP + MAX_TRACE_CHUNK_INFO + +# 1 encoded + 2 prewrites + 2 src reads +.equ MAX_DMA_EXTRA_BYTES, (2 + 2 + 1) * 8 +.equ MAX_DMA_BYTES_DIRECT_MTRACE, (MAX_BYTES_DIRECT_MTRACE - MAX_DMA_EXTRA_BYTES) +.equ MAX_DMA_MT_MARGIN, (MAX_DMA_BYTES_DIRECT_MTRACE + MAX_DMA_EXTRA_BYTES) + +.equ EXTRA_PARAMETER_ADDR, 0xA0000F00 + +# ENCODE CONSTANTS +# +# bits offset mask +# ---- ------ -------- +# pre_count: 0-7 3 0 0x0000_0000_0000_0007 +# post_count: 0-7 3 3 0x0000_0000_0000_0038 +# pre_writes: 0,1,2 2 6 0x0000_0000_0000_00C0 +# dst_offset: 0-7 3 8 0x0000_0000_0000_0700 +# src_offset: 0-7 3 11 0x0000_0000_0000_3800 +# double_src_pre: 0,1 1 14 0x0000_0000_0000_4000 +# double_src_post: 0,1 1 15 0x0000_0000_0000_8000 +# extra_src_reads: 0-3 2 16 0x0000_0000_0003_0000 +# src64_inc_by_pre: 1 18 0x0000_0000_0004_0000 +# unaligned_dst_src: 1 19 0x0000_0000_0008_0000 +# pre_count: 0-7 3 29 only for optimization PRE_AND_LOOP_BYTES +# loop_count 32 0 0xFFFF_FFFF_0000_0000 + +# BITS 31,32,32 + +.equ PRE_COUNT_MASK, 0x0000000000000007 +.equ POST_COUNT_MASK, 0x0000000000000038 +.equ PRE_WRITES_MASK, 0x00000000000000C0 +.equ DST_OFFSET_MASK, 0x0000000000000700 +.equ SRC_OFFSET_MASK, 0x0000000000003800 +.equ DOUBLE_SRC_PRE_MASK, 0x0000000000004000 +.equ DOUBLE_SRC_POST_MASK, 0x0000000000008000 +.equ EXTRA_SRC_READS_MASK, 0x0000000000030000 +.equ SRC64_INC_BY_PRE_MASK, 0x0000000000040000 +.equ UNALIGNED_DST_SRC_MASK, 0x0000000000080000 +.equ LOOP_COUNT_MASK, 0xFFFFFFFF00000000 +.equ ONLY_LOOP_COUNT_MASK, 0xFFFFFFFF00000000 + +# PRE_COUNT_SBITS 0 +.equ POST_COUNT_SBITS, 3 +.equ PRE_WRITES_SBITS, 6 +.equ DST_OFFSET_SBITS, 8 +.equ SRC_OFFSET_SBITS, 11 +.equ DOUBLE_SRC_PRE_SBITS, 14 +.equ DOUBLE_SRC_POST_SBITS, 15 +.equ EXTRA_SRC_READS_SBITS, 16 +.equ SRC64_INC_BY_PRE_SBITS, 18 +.equ UNALIGNED_DST_SRC_SBITS, 19 +.equ LOOP_COUNT_SBITS, 32 +.equ PRE_AND_LOOP_BYTES_SBITS, 29 +.equ ALIGN_MASK, 0xFFFFFFFFFFFFFFF8 +.equ MOPS_BLOCK_WORDS_SBITS, 36 +.equ MOPS_BLOCK_ONE_WORD, 0x0000001000000000 +.equ MOPS_BLOCK_READ, 0x0000000A00000000 +.equ MOPS_BLOCK_WRITE, 0x0000000B00000000 +.equ MOPS_ALIGNED_READ, 0x0000000C00000000 +.equ MOPS_ALIGNED_WRITE, 0x0000000D00000000 +.equ MOPS_ALIGNED_BLOCK_READ, 0x0000000E00000000 +.equ MOPS_ALIGNED_BLOCK_WRITE, 0x0000000F00000000 diff --git a/emulator-asm/src/dma/fast_dma_encode.asm b/emulator-asm/src/dma/fast_dma_encode.asm new file mode 100644 index 000000000..533fba3d2 --- /dev/null +++ b/emulator-asm/src/dma/fast_dma_encode.asm @@ -0,0 +1,74 @@ +.intel_syntax noprefix +.code64 + +################################################################################ +# fast_dma_encode - Optimized function to encode dma information +# +# REGISTER USAGE: +# Modified registers: rax, r8 +# Does NOT use XMM registers (caller doesn't need to save them) +# +# PARAMETERS (System V AMD64 ABI): +# rdi = dst (u64) - Destination address +# rsi = src (u64) - Source address +# rdx = count (usize) - Number of bytes to copy +# +# RETURN VALUE: +# rax = encoded value +# +# ENCODED METADATA (bits): +# 0-2: pre_count - Bytes to copy before alignment (0-7) +# 3-5: post_count - Bytes to copy after aligned chunks (0-7) +# 6-7: pre_writes - Number of pre/post partial writes (0, 1, or 2) +# 8-10: dst_offset - Byte offset within dst qword (0-7) +# 11-13: src_offset - Byte offset within src qword (0-7) +# 14: double_src_pre - Flag: pre-read spans two src qwords +# 15: double_src_post - Flag: post-read spans two src qwords +# 16-17: extra_src_reads - Additional src qword reads needed (0-3) +# 18: src64_inc_by_pre - Flag: indicate loop use src64 + 8 +# 19: unaligned_dst_src - Flag: dst and src has diferent alignement +# 32-63: loop_count - Number of 8-byte chunks in main copy loop +################################################################################ + +.global fast_dma_encode + +.section .text + +fast_dma_encode: + mov rax, rdi + and rax, 0x07 # dst_offset (0-7) + shl rax, 7 # dst_offset << 7 + + mov r8, rsi + and r8, 0x07 # src_offset (0-7) + shl r8, 4 # src_offset << 4 + + or rax, r8 # combine dst and src offsets + + # Calculate table_count + mov r8, rdx + cmp r8, 16 + jb .L_count_lt_16 + + # count >= 16: table_count = (count & 0x07) | 0x08 + and r8, 0x07 + or r8, 0x08 + +.L_count_lt_16: + or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count + + # Look up encoded value in table (direct access since it's in the same file) + mov rax, [fast_dma_encode_table + rax * 8] + + # Add (count >> 3) to result + mov r8, rdx + shl r8, 29 # r8 = count << 29 + add rax, r8 # result += (count << 29) + + ret + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits + +# Include the lookup table in the .rodata section +.include "fast_dma_encode_table.asm" diff --git a/emulator-asm/src/dma/fast_dma_encode_table.asm b/emulator-asm/src/dma/fast_dma_encode_table.asm new file mode 100644 index 000000000..86f837ccc --- /dev/null +++ b/emulator-asm/src/dma/fast_dma_encode_table.asm @@ -0,0 +1,264 @@ +.intel_syntax noprefix +.code64 + +.section .data +# generated with precompiles/helpers/src/dma +# asm_fast_encode_table test + +fast_dma_encode_table: + .quad 0x0000000000000000, 0xFFFFFFFFE0010048, 0xFFFFFFFFC0010050, 0xFFFFFFFFA0010058 # 0 - 3 D0 S0 C0 + .quad 0xffffffff80010060, 0xFFFFFFFF60010068, 0xFFFFFFFF40010070, 0xFFFFFFFF20010078 # 4 - 7 D0 S0 C4 + .quad 0x0000000000000000, 0xFFFFFFFFE0010048, 0xFFFFFFFFC0010050, 0xFFFFFFFFA0010058 # 8 - 11 D0 S0 C8 + .quad 0xffffffff80010060, 0xFFFFFFFF60010068, 0xFFFFFFFF40010070, 0xFFFFFFFF20010078 # 12 - 15 D0 S0 C12 + .quad 0x0000000000080800, 0xFFFFFFFFE0090848, 0xFFFFFFFFC0090850, 0xFFFFFFFFA0090858 # 16 - 19 D0 S1 C0 + .quad 0xffffffff80090860, 0xFFFFFFFF60090868, 0xFFFFFFFF40090870, 0xFFFFFFFF20090878 # 20 - 23 D0 S1 C4 + .quad 0x0000000000090800, 0xFFFFFFFFE0090848, 0xFFFFFFFFC0090850, 0xFFFFFFFFA0090858 # 24 - 27 D0 S1 C8 + .quad 0xffffffff80090860, 0xFFFFFFFF60090868, 0xFFFFFFFF40090870, 0xFFFFFFFF20090878 # 28 - 31 D0 S1 C12 + .quad 0x0000000000081000, 0xFFFFFFFFE0091048, 0xFFFFFFFFC0091050, 0xFFFFFFFFA0091058 # 32 - 35 D0 S2 C0 + .quad 0xffffffff80091060, 0xFFFFFFFF60091068, 0xFFFFFFFF40091070, 0xFFFFFFFF200A9078 # 36 - 39 D0 S2 C4 + .quad 0x0000000000091000, 0xFFFFFFFFE0091048, 0xFFFFFFFFC0091050, 0xFFFFFFFFA0091058 # 40 - 43 D0 S2 C8 + .quad 0xffffffff80091060, 0xFFFFFFFF60091068, 0xFFFFFFFF40091070, 0xFFFFFFFF200A9078 # 44 - 47 D0 S2 C12 + .quad 0x0000000000081800, 0xFFFFFFFFE0091848, 0xFFFFFFFFC0091850, 0xFFFFFFFFA0091858 # 48 - 51 D0 S3 C0 + .quad 0xffffffff80091860, 0xFFFFFFFF60091868, 0xFFFFFFFF400A9870, 0xFFFFFFFF200A9878 # 52 - 55 D0 S3 C4 + .quad 0x0000000000091800, 0xFFFFFFFFE0091848, 0xFFFFFFFFC0091850, 0xFFFFFFFFA0091858 # 56 - 59 D0 S3 C8 + .quad 0xffffffff80091860, 0xFFFFFFFF60091868, 0xFFFFFFFF400A9870, 0xFFFFFFFF200A9878 # 60 - 63 D0 S3 C12 + .quad 0x0000000000082000, 0xFFFFFFFFE0092048, 0xFFFFFFFFC0092050, 0xFFFFFFFFA0092058 # 64 - 67 D0 S4 C0 + .quad 0xffffffff80092060, 0xFFFFFFFF600AA068, 0xFFFFFFFF400AA070, 0xFFFFFFFF200AA078 # 68 - 71 D0 S4 C4 + .quad 0x0000000000092000, 0xFFFFFFFFE0092048, 0xFFFFFFFFC0092050, 0xFFFFFFFFA0092058 # 72 - 75 D0 S4 C8 + .quad 0xffffffff80092060, 0xFFFFFFFF600AA068, 0xFFFFFFFF400AA070, 0xFFFFFFFF200AA078 # 76 - 79 D0 S4 C12 + .quad 0x0000000000082800, 0xFFFFFFFFE0092848, 0xFFFFFFFFC0092850, 0xFFFFFFFFA0092858 # 80 - 83 D0 S5 C0 + .quad 0xffffffff800aa860, 0xFFFFFFFF600AA868, 0xFFFFFFFF400AA870, 0xFFFFFFFF200AA878 # 84 - 87 D0 S5 C4 + .quad 0x0000000000092800, 0xFFFFFFFFE0092848, 0xFFFFFFFFC0092850, 0xFFFFFFFFA0092858 # 88 - 91 D0 S5 C8 + .quad 0xffffffff800aa860, 0xFFFFFFFF600AA868, 0xFFFFFFFF400AA870, 0xFFFFFFFF200AA878 # 92 - 95 D0 S5 C12 + .quad 0x0000000000083000, 0xFFFFFFFFE0093048, 0xFFFFFFFFC0093050, 0xFFFFFFFFA00AB058 # 96 - 99 D0 S6 C0 + .quad 0xffffffff800ab060, 0xFFFFFFFF600AB068, 0xFFFFFFFF400AB070, 0xFFFFFFFF200AB078 # 100 - 103 D0 S6 C4 + .quad 0x0000000000093000, 0xFFFFFFFFE0093048, 0xFFFFFFFFC0093050, 0xFFFFFFFFA00AB058 # 104 - 107 D0 S6 C8 + .quad 0xffffffff800ab060, 0xFFFFFFFF600AB068, 0xFFFFFFFF400AB070, 0xFFFFFFFF200AB078 # 108 - 111 D0 S6 C12 + .quad 0x0000000000083800, 0xFFFFFFFFE0093848, 0xFFFFFFFFC00AB850, 0xFFFFFFFFA00AB858 # 112 - 115 D0 S7 C0 + .quad 0xffffffff800ab860, 0xFFFFFFFF600AB868, 0xFFFFFFFF400AB870, 0xFFFFFFFF200AB878 # 116 - 119 D0 S7 C4 + .quad 0x0000000000093800, 0xFFFFFFFFE0093848, 0xFFFFFFFFC00AB850, 0xFFFFFFFFA00AB858 # 120 - 123 D0 S7 C8 + .quad 0xffffffff800ab860, 0xFFFFFFFF600AB868, 0xFFFFFFFF400AB870, 0xFFFFFFFF200AB878 # 124 - 127 D0 S7 C12 + .quad 0x0000000000080100, 0x0000000000090141, 0x0000000000090142, 0x0000000000090143 # 128 - 131 D1 S0 C0 + .quad 0x0000000000090144, 0x0000000000090145, 0x0000000000090146, 0x0000000000090147 # 132 - 135 D1 S0 C4 + .quad 0xffffffffe009018f, 0xFFFFFFFFC00A8197, 0xFFFFFFFFA00A819F, 0xFFFFFFFF800A81A7 # 136 - 139 D1 S0 C8 + .quad 0xffffffff600a81af, 0xFFFFFFFF400A81B7, 0xFFFFFFFF200A81BF, 0x0000000000090147 # 140 - 143 D1 S0 C12 + .quad 0x0000000000000900, 0x0000000000010941, 0x0000000000010942, 0x0000000000010943 # 144 - 147 D1 S1 C0 + .quad 0x0000000000010944, 0x0000000000010945, 0x0000000000010946, 0x0000000000050947 # 148 - 151 D1 S1 C4 + .quad 0xffffffffe006098f, 0xFFFFFFFFC0060997, 0xFFFFFFFFA006099F, 0xFFFFFFFF800609A7 # 152 - 155 D1 S1 C8 + .quad 0xffffffff600609af, 0xFFFFFFFF400609B7, 0xFFFFFFFF200609BF, 0x0000000000050947 # 156 - 159 D1 S1 C12 + .quad 0x0000000000081100, 0x0000000000091141, 0x0000000000091142, 0x0000000000091143 # 160 - 163 D1 S2 C0 + .quad 0x0000000000091144, 0x0000000000091145, 0x00000000000D1146, 0x00000000000E5147 # 164 - 167 D1 S2 C4 + .quad 0xffffffffe00e518f, 0xFFFFFFFFC00E5197, 0xFFFFFFFFA00E519F, 0xFFFFFFFF800E51A7 # 168 - 171 D1 S2 C8 + .quad 0xffffffff600e51af, 0xFFFFFFFF400E51B7, 0xFFFFFFFF200E51BF, 0x00000000000E5147 # 172 - 175 D1 S2 C12 + .quad 0x0000000000081900, 0x0000000000091941, 0x0000000000091942, 0x0000000000091943 # 176 - 179 D1 S3 C0 + .quad 0x0000000000091944, 0x00000000000D1945, 0x00000000000E5946, 0x00000000000E5947 # 180 - 183 D1 S3 C4 + .quad 0xffffffffe00e598f, 0xFFFFFFFFC00E5997, 0xFFFFFFFFA00E599F, 0xFFFFFFFF800E59A7 # 184 - 187 D1 S3 C8 + .quad 0xffffffff600e59af, 0xFFFFFFFF400E59B7, 0xFFFFFFFF200FD9BF, 0x00000000000E5947 # 188 - 191 D1 S3 C12 + .quad 0x0000000000082100, 0x0000000000092141, 0x0000000000092142, 0x0000000000092143 # 192 - 195 D1 S4 C0 + .quad 0x00000000000d2144, 0x00000000000E6145, 0x00000000000E6146, 0x00000000000E6147 # 196 - 199 D1 S4 C4 + .quad 0xffffffffe00e618f, 0xFFFFFFFFC00E6197, 0xFFFFFFFFA00E619F, 0xFFFFFFFF800E61A7 # 200 - 203 D1 S4 C8 + .quad 0xffffffff600e61af, 0xFFFFFFFF400FE1B7, 0xFFFFFFFF200FE1BF, 0x00000000000E6147 # 204 - 207 D1 S4 C12 + .quad 0x0000000000082900, 0x0000000000092941, 0x0000000000092942, 0x00000000000D2943 # 208 - 211 D1 S5 C0 + .quad 0x00000000000e6944, 0x00000000000E6945, 0x00000000000E6946, 0x00000000000E6947 # 212 - 215 D1 S5 C4 + .quad 0xffffffffe00e698f, 0xFFFFFFFFC00E6997, 0xFFFFFFFFA00E699F, 0xFFFFFFFF800E69A7 # 216 - 219 D1 S5 C8 + .quad 0xffffffff600fe9af, 0xFFFFFFFF400FE9B7, 0xFFFFFFFF200FE9BF, 0x00000000000E6947 # 220 - 223 D1 S5 C12 + .quad 0x0000000000083100, 0x0000000000093141, 0x00000000000D3142, 0x00000000000E7143 # 224 - 227 D1 S6 C0 + .quad 0x00000000000e7144, 0x00000000000E7145, 0x00000000000E7146, 0x00000000000E7147 # 228 - 231 D1 S6 C4 + .quad 0xffffffffe00e718f, 0xFFFFFFFFC00E7197, 0xFFFFFFFFA00E719F, 0xFFFFFFFF800FF1A7 # 232 - 235 D1 S6 C8 + .quad 0xffffffff600ff1af, 0xFFFFFFFF400FF1B7, 0xFFFFFFFF200FF1BF, 0x00000000000E7147 # 236 - 239 D1 S6 C12 + .quad 0x0000000000083900, 0x00000000000D3941, 0x00000000000E7942, 0x00000000000E7943 # 240 - 243 D1 S7 C0 + .quad 0x00000000000e7944, 0x00000000000E7945, 0x00000000000E7946, 0x00000000000E7947 # 244 - 247 D1 S7 C4 + .quad 0xffffffffe00e798f, 0xFFFFFFFFC00E7997, 0xFFFFFFFFA00FF99F, 0xFFFFFFFF800FF9A7 # 248 - 251 D1 S7 C8 + .quad 0xffffffff600ff9af, 0xFFFFFFFF400FF9B7, 0xFFFFFFFF200FF9BF, 0x00000000000E7947 # 252 - 255 D1 S7 C12 + .quad 0x0000000000080200, 0x0000000000090241, 0x0000000000090242, 0x0000000000090243 # 256 - 259 D2 S0 C0 + .quad 0x0000000000090244, 0x0000000000090245, 0x0000000000090246, 0xFFFFFFFFE009028E # 260 - 263 D2 S0 C4 + .quad 0xffffffffc0090296, 0xFFFFFFFFA00A829E, 0xFFFFFFFF800A82A6, 0xFFFFFFFF600A82AE # 264 - 267 D2 S0 C8 + .quad 0xffffffff400a82b6, 0xFFFFFFFF200A82BE, 0x0000000000090246, 0xFFFFFFFFE009028E # 268 - 271 D2 S0 C12 + .quad 0x0000000000080a00, 0x0000000000090A41, 0x0000000000090A42, 0x0000000000090A43 # 272 - 275 D2 S1 C0 + .quad 0x0000000000090a44, 0x0000000000090A45, 0x0000000000090A46, 0xFFFFFFFFE0090A8E # 276 - 279 D2 S1 C4 + .quad 0xffffffffc00a8a96, 0xFFFFFFFFA00A8A9E, 0xFFFFFFFF800A8AA6, 0xFFFFFFFF600A8AAE # 280 - 283 D2 S1 C8 + .quad 0xffffffff400a8ab6, 0xFFFFFFFF200A8ABE, 0x0000000000090A46, 0xFFFFFFFFE0090A8E # 284 - 287 D2 S1 C12 + .quad 0x0000000000001200, 0x0000000000011241, 0x0000000000011242, 0x0000000000011243 # 288 - 291 D2 S2 C0 + .quad 0x0000000000011244, 0x0000000000011245, 0x0000000000051246, 0xFFFFFFFFE006128E # 292 - 295 D2 S2 C4 + .quad 0xffffffffc0061296, 0xFFFFFFFFA006129E, 0xFFFFFFFF800612A6, 0xFFFFFFFF600612AE # 296 - 299 D2 S2 C8 + .quad 0xffffffff400612b6, 0xFFFFFFFF200612BE, 0x0000000000051246, 0xFFFFFFFFE006128E # 300 - 303 D2 S2 C12 + .quad 0x0000000000081a00, 0x0000000000091A41, 0x0000000000091A42, 0x0000000000091A43 # 304 - 307 D2 S3 C0 + .quad 0x0000000000091a44, 0x00000000000D1A45, 0x00000000000E5A46, 0xFFFFFFFFE00E5A8E # 308 - 311 D2 S3 C4 + .quad 0xffffffffc00e5a96, 0xFFFFFFFFA00E5A9E, 0xFFFFFFFF800E5AA6, 0xFFFFFFFF600E5AAE # 312 - 315 D2 S3 C8 + .quad 0xffffffff400e5ab6, 0xFFFFFFFF200E5ABE, 0x00000000000E5A46, 0xFFFFFFFFE00E5A8E # 316 - 319 D2 S3 C12 + .quad 0x0000000000082200, 0x0000000000092241, 0x0000000000092242, 0x0000000000092243 # 320 - 323 D2 S4 C0 + .quad 0x00000000000d2244, 0x00000000000E6245, 0x00000000000E6246, 0xFFFFFFFFE00E628E # 324 - 327 D2 S4 C4 + .quad 0xffffffffc00e6296, 0xFFFFFFFFA00E629E, 0xFFFFFFFF800E62A6, 0xFFFFFFFF600E62AE # 328 - 331 D2 S4 C8 + .quad 0xffffffff400e62b6, 0xFFFFFFFF200FE2BE, 0x00000000000E6246, 0xFFFFFFFFE00E628E # 332 - 335 D2 S4 C12 + .quad 0x0000000000082a00, 0x0000000000092A41, 0x0000000000092A42, 0x00000000000D2A43 # 336 - 339 D2 S5 C0 + .quad 0x00000000000e6a44, 0x00000000000E6A45, 0x00000000000E6A46, 0xFFFFFFFFE00E6A8E # 340 - 343 D2 S5 C4 + .quad 0xffffffffc00e6a96, 0xFFFFFFFFA00E6A9E, 0xFFFFFFFF800E6AA6, 0xFFFFFFFF600E6AAE # 344 - 347 D2 S5 C8 + .quad 0xffffffff400feab6, 0xFFFFFFFF200FEABE, 0x00000000000E6A46, 0xFFFFFFFFE00E6A8E # 348 - 351 D2 S5 C12 + .quad 0x0000000000083200, 0x0000000000093241, 0x00000000000D3242, 0x00000000000E7243 # 352 - 355 D2 S6 C0 + .quad 0x00000000000e7244, 0x00000000000E7245, 0x00000000000E7246, 0xFFFFFFFFE00E728E # 356 - 359 D2 S6 C4 + .quad 0xffffffffc00e7296, 0xFFFFFFFFA00E729E, 0xFFFFFFFF800E72A6, 0xFFFFFFFF600FF2AE # 360 - 363 D2 S6 C8 + .quad 0xffffffff400ff2b6, 0xFFFFFFFF200FF2BE, 0x00000000000E7246, 0xFFFFFFFFE00E728E # 364 - 367 D2 S6 C12 + .quad 0x0000000000083a00, 0x00000000000D3A41, 0x00000000000E7A42, 0x00000000000E7A43 # 368 - 371 D2 S7 C0 + .quad 0x00000000000e7a44, 0x00000000000E7A45, 0x00000000000E7A46, 0xFFFFFFFFE00E7A8E # 372 - 375 D2 S7 C4 + .quad 0xffffffffc00e7a96, 0xFFFFFFFFA00E7A9E, 0xFFFFFFFF800FFAA6, 0xFFFFFFFF600FFAAE # 376 - 379 D2 S7 C8 + .quad 0xffffffff400ffab6, 0xFFFFFFFF200FFABE, 0x00000000000E7A46, 0xFFFFFFFFE00E7A8E # 380 - 383 D2 S7 C12 + .quad 0x0000000000080300, 0x0000000000090341, 0x0000000000090342, 0x0000000000090343 # 384 - 387 D3 S0 C0 + .quad 0x0000000000090344, 0x0000000000090345, 0xFFFFFFFFE009038D, 0xFFFFFFFFC0090395 # 388 - 391 D3 S0 C4 + .quad 0xffffffffa009039d, 0xFFFFFFFF800A83A5, 0xFFFFFFFF600A83AD, 0xFFFFFFFF400A83B5 # 392 - 395 D3 S0 C8 + .quad 0xffffffff200a83bd, 0x0000000000090345, 0xFFFFFFFFE009038D, 0xFFFFFFFFC0090395 # 396 - 399 D3 S0 C12 + .quad 0x0000000000080b00, 0x0000000000090B41, 0x0000000000090B42, 0x0000000000090B43 # 400 - 403 D3 S1 C0 + .quad 0x0000000000090b44, 0x0000000000090B45, 0xFFFFFFFFE0090B8D, 0xFFFFFFFFC0090B95 # 404 - 407 D3 S1 C4 + .quad 0xffffffffa00a8b9d, 0xFFFFFFFF800A8BA5, 0xFFFFFFFF600A8BAD, 0xFFFFFFFF400A8BB5 # 408 - 411 D3 S1 C8 + .quad 0xffffffff200a8bbd, 0x0000000000090B45, 0xFFFFFFFFE0090B8D, 0xFFFFFFFFC0090B95 # 412 - 415 D3 S1 C12 + .quad 0x0000000000081300, 0x0000000000091341, 0x0000000000091342, 0x0000000000091343 # 416 - 419 D3 S2 C0 + .quad 0x0000000000091344, 0x0000000000091345, 0xFFFFFFFFE009138D, 0xFFFFFFFFC00A9395 # 420 - 423 D3 S2 C4 + .quad 0xffffffffa00a939d, 0xFFFFFFFF800A93A5, 0xFFFFFFFF600A93AD, 0xFFFFFFFF400A93B5 # 424 - 427 D3 S2 C8 + .quad 0xffffffff200a93bd, 0x0000000000091345, 0xFFFFFFFFE009138D, 0xFFFFFFFFC00A9395 # 428 - 431 D3 S2 C12 + .quad 0x0000000000001b00, 0x0000000000011B41, 0x0000000000011B42, 0x0000000000011B43 # 432 - 435 D3 S3 C0 + .quad 0x0000000000011b44, 0x0000000000051B45, 0xFFFFFFFFE0061B8D, 0xFFFFFFFFC0061B95 # 436 - 439 D3 S3 C4 + .quad 0xffffffffa0061b9d, 0xFFFFFFFF80061BA5, 0xFFFFFFFF60061BAD, 0xFFFFFFFF40061BB5 # 440 - 443 D3 S3 C8 + .quad 0xffffffff20061bbd, 0x0000000000051B45, 0xFFFFFFFFE0061B8D, 0xFFFFFFFFC0061B95 # 444 - 447 D3 S3 C12 + .quad 0x0000000000082300, 0x0000000000092341, 0x0000000000092342, 0x0000000000092343 # 448 - 451 D3 S4 C0 + .quad 0x00000000000d2344, 0x00000000000E6345, 0xFFFFFFFFE00E638D, 0xFFFFFFFFC00E6395 # 452 - 455 D3 S4 C4 + .quad 0xffffffffa00e639d, 0xFFFFFFFF800E63A5, 0xFFFFFFFF600E63AD, 0xFFFFFFFF400E63B5 # 456 - 459 D3 S4 C8 + .quad 0xffffffff200e63bd, 0x00000000000E6345, 0xFFFFFFFFE00E638D, 0xFFFFFFFFC00E6395 # 460 - 463 D3 S4 C12 + .quad 0x0000000000082b00, 0x0000000000092B41, 0x0000000000092B42, 0x00000000000D2B43 # 464 - 467 D3 S5 C0 + .quad 0x00000000000e6b44, 0x00000000000E6B45, 0xFFFFFFFFE00E6B8D, 0xFFFFFFFFC00E6B95 # 468 - 471 D3 S5 C4 + .quad 0xffffffffa00e6b9d, 0xFFFFFFFF800E6BA5, 0xFFFFFFFF600E6BAD, 0xFFFFFFFF400E6BB5 # 472 - 475 D3 S5 C8 + .quad 0xffffffff200febbd, 0x00000000000E6B45, 0xFFFFFFFFE00E6B8D, 0xFFFFFFFFC00E6B95 # 476 - 479 D3 S5 C12 + .quad 0x0000000000083300, 0x0000000000093341, 0x00000000000D3342, 0x00000000000E7343 # 480 - 483 D3 S6 C0 + .quad 0x00000000000e7344, 0x00000000000E7345, 0xFFFFFFFFE00E738D, 0xFFFFFFFFC00E7395 # 484 - 487 D3 S6 C4 + .quad 0xffffffffa00e739d, 0xFFFFFFFF800E73A5, 0xFFFFFFFF600E73AD, 0xFFFFFFFF400FF3B5 # 488 - 491 D3 S6 C8 + .quad 0xffffffff200ff3bd, 0x00000000000E7345, 0xFFFFFFFFE00E738D, 0xFFFFFFFFC00E7395 # 492 - 495 D3 S6 C12 + .quad 0x0000000000083b00, 0x00000000000D3B41, 0x00000000000E7B42, 0x00000000000E7B43 # 496 - 499 D3 S7 C0 + .quad 0x00000000000e7b44, 0x00000000000E7B45, 0xFFFFFFFFE00E7B8D, 0xFFFFFFFFC00E7B95 # 500 - 503 D3 S7 C4 + .quad 0xffffffffa00e7b9d, 0xFFFFFFFF800E7BA5, 0xFFFFFFFF600FFBAD, 0xFFFFFFFF400FFBB5 # 504 - 507 D3 S7 C8 + .quad 0xffffffff200ffbbd, 0x00000000000E7B45, 0xFFFFFFFFE00E7B8D, 0xFFFFFFFFC00E7B95 # 508 - 511 D3 S7 C12 + .quad 0x0000000000080400, 0x0000000000090441, 0x0000000000090442, 0x0000000000090443 # 512 - 515 D4 S0 C0 + .quad 0x0000000000090444, 0xFFFFFFFFE009048C, 0xFFFFFFFFC0090494, 0xFFFFFFFFA009049C # 516 - 519 D4 S0 C4 + .quad 0xffffffff800904a4, 0xFFFFFFFF600A84AC, 0xFFFFFFFF400A84B4, 0xFFFFFFFF200A84BC # 520 - 523 D4 S0 C8 + .quad 0x0000000000090444, 0xFFFFFFFFE009048C, 0xFFFFFFFFC0090494, 0xFFFFFFFFA009049C # 524 - 527 D4 S0 C12 + .quad 0x0000000000080c00, 0x0000000000090C41, 0x0000000000090C42, 0x0000000000090C43 # 528 - 531 D4 S1 C0 + .quad 0x0000000000090c44, 0xFFFFFFFFE0090C8C, 0xFFFFFFFFC0090C94, 0xFFFFFFFFA0090C9C # 532 - 535 D4 S1 C4 + .quad 0xffffffff800a8ca4, 0xFFFFFFFF600A8CAC, 0xFFFFFFFF400A8CB4, 0xFFFFFFFF200A8CBC # 536 - 539 D4 S1 C8 + .quad 0x0000000000090c44, 0xFFFFFFFFE0090C8C, 0xFFFFFFFFC0090C94, 0xFFFFFFFFA0090C9C # 540 - 543 D4 S1 C12 + .quad 0x0000000000081400, 0x0000000000091441, 0x0000000000091442, 0x0000000000091443 # 544 - 547 D4 S2 C0 + .quad 0x0000000000091444, 0xFFFFFFFFE009148C, 0xFFFFFFFFC0091494, 0xFFFFFFFFA00A949C # 548 - 551 D4 S2 C4 + .quad 0xffffffff800a94a4, 0xFFFFFFFF600A94AC, 0xFFFFFFFF400A94B4, 0xFFFFFFFF200A94BC # 552 - 555 D4 S2 C8 + .quad 0x0000000000091444, 0xFFFFFFFFE009148C, 0xFFFFFFFFC0091494, 0xFFFFFFFFA00A949C # 556 - 559 D4 S2 C12 + .quad 0x0000000000081c00, 0x0000000000091C41, 0x0000000000091C42, 0x0000000000091C43 # 560 - 563 D4 S3 C0 + .quad 0x0000000000091c44, 0xFFFFFFFFE0091C8C, 0xFFFFFFFFC00A9C94, 0xFFFFFFFFA00A9C9C # 564 - 567 D4 S3 C4 + .quad 0xffffffff800a9ca4, 0xFFFFFFFF600A9CAC, 0xFFFFFFFF400A9CB4, 0xFFFFFFFF200A9CBC # 568 - 571 D4 S3 C8 + .quad 0x0000000000091c44, 0xFFFFFFFFE0091C8C, 0xFFFFFFFFC00A9C94, 0xFFFFFFFFA00A9C9C # 572 - 575 D4 S3 C12 + .quad 0x0000000000002400, 0x0000000000012441, 0x0000000000012442, 0x0000000000012443 # 576 - 579 D4 S4 C0 + .quad 0x0000000000052444, 0xFFFFFFFFE006248C, 0xFFFFFFFFC0062494, 0xFFFFFFFFA006249C # 580 - 583 D4 S4 C4 + .quad 0xffffffff800624a4, 0xFFFFFFFF600624AC, 0xFFFFFFFF400624B4, 0xFFFFFFFF200624BC # 584 - 587 D4 S4 C8 + .quad 0x0000000000052444, 0xFFFFFFFFE006248C, 0xFFFFFFFFC0062494, 0xFFFFFFFFA006249C # 588 - 591 D4 S4 C12 + .quad 0x0000000000082c00, 0x0000000000092C41, 0x0000000000092C42, 0x00000000000D2C43 # 592 - 595 D4 S5 C0 + .quad 0x00000000000e6c44, 0xFFFFFFFFE00E6C8C, 0xFFFFFFFFC00E6C94, 0xFFFFFFFFA00E6C9C # 596 - 599 D4 S5 C4 + .quad 0xffffffff800e6ca4, 0xFFFFFFFF600E6CAC, 0xFFFFFFFF400E6CB4, 0xFFFFFFFF200E6CBC # 600 - 603 D4 S5 C8 + .quad 0x00000000000e6c44, 0xFFFFFFFFE00E6C8C, 0xFFFFFFFFC00E6C94, 0xFFFFFFFFA00E6C9C # 604 - 607 D4 S5 C12 + .quad 0x0000000000083400, 0x0000000000093441, 0x00000000000D3442, 0x00000000000E7443 # 608 - 611 D4 S6 C0 + .quad 0x00000000000e7444, 0xFFFFFFFFE00E748C, 0xFFFFFFFFC00E7494, 0xFFFFFFFFA00E749C # 612 - 615 D4 S6 C4 + .quad 0xffffffff800e74a4, 0xFFFFFFFF600E74AC, 0xFFFFFFFF400E74B4, 0xFFFFFFFF200FF4BC # 616 - 619 D4 S6 C8 + .quad 0x00000000000e7444, 0xFFFFFFFFE00E748C, 0xFFFFFFFFC00E7494, 0xFFFFFFFFA00E749C # 620 - 623 D4 S6 C12 + .quad 0x0000000000083c00, 0x00000000000D3C41, 0x00000000000E7C42, 0x00000000000E7C43 # 624 - 627 D4 S7 C0 + .quad 0x00000000000e7c44, 0xFFFFFFFFE00E7C8C, 0xFFFFFFFFC00E7C94, 0xFFFFFFFFA00E7C9C # 628 - 631 D4 S7 C4 + .quad 0xffffffff800e7ca4, 0xFFFFFFFF600E7CAC, 0xFFFFFFFF400FFCB4, 0xFFFFFFFF200FFCBC # 632 - 635 D4 S7 C8 + .quad 0x00000000000e7c44, 0xFFFFFFFFE00E7C8C, 0xFFFFFFFFC00E7C94, 0xFFFFFFFFA00E7C9C # 636 - 639 D4 S7 C12 + .quad 0x0000000000080500, 0x0000000000090541, 0x0000000000090542, 0x0000000000090543 # 640 - 643 D5 S0 C0 + .quad 0xffffffffe009058b, 0xFFFFFFFFC0090593, 0xFFFFFFFFA009059B, 0xFFFFFFFF800905A3 # 644 - 647 D5 S0 C4 + .quad 0xffffffff600905ab, 0xFFFFFFFF400A85B3, 0xFFFFFFFF200A85BB, 0x0000000000090543 # 648 - 651 D5 S0 C8 + .quad 0xffffffffe009058b, 0xFFFFFFFFC0090593, 0xFFFFFFFFA009059B, 0xFFFFFFFF800905A3 # 652 - 655 D5 S0 C12 + .quad 0x0000000000080d00, 0x0000000000090D41, 0x0000000000090D42, 0x0000000000090D43 # 656 - 659 D5 S1 C0 + .quad 0xffffffffe0090d8b, 0xFFFFFFFFC0090D93, 0xFFFFFFFFA0090D9B, 0xFFFFFFFF80090DA3 # 660 - 663 D5 S1 C4 + .quad 0xffffffff600a8dab, 0xFFFFFFFF400A8DB3, 0xFFFFFFFF200A8DBB, 0x0000000000090D43 # 664 - 667 D5 S1 C8 + .quad 0xffffffffe0090d8b, 0xFFFFFFFFC0090D93, 0xFFFFFFFFA0090D9B, 0xFFFFFFFF80090DA3 # 668 - 671 D5 S1 C12 + .quad 0x0000000000081500, 0x0000000000091541, 0x0000000000091542, 0x0000000000091543 # 672 - 675 D5 S2 C0 + .quad 0xffffffffe009158b, 0xFFFFFFFFC0091593, 0xFFFFFFFFA009159B, 0xFFFFFFFF800A95A3 # 676 - 679 D5 S2 C4 + .quad 0xffffffff600a95ab, 0xFFFFFFFF400A95B3, 0xFFFFFFFF200A95BB, 0x0000000000091543 # 680 - 683 D5 S2 C8 + .quad 0xffffffffe009158b, 0xFFFFFFFFC0091593, 0xFFFFFFFFA009159B, 0xFFFFFFFF800A95A3 # 684 - 687 D5 S2 C12 + .quad 0x0000000000081d00, 0x0000000000091D41, 0x0000000000091D42, 0x0000000000091D43 # 688 - 691 D5 S3 C0 + .quad 0xffffffffe0091d8b, 0xFFFFFFFFC0091D93, 0xFFFFFFFFA00A9D9B, 0xFFFFFFFF800A9DA3 # 692 - 695 D5 S3 C4 + .quad 0xffffffff600a9dab, 0xFFFFFFFF400A9DB3, 0xFFFFFFFF200A9DBB, 0x0000000000091D43 # 696 - 699 D5 S3 C8 + .quad 0xffffffffe0091d8b, 0xFFFFFFFFC0091D93, 0xFFFFFFFFA00A9D9B, 0xFFFFFFFF800A9DA3 # 700 - 703 D5 S3 C12 + .quad 0x0000000000082500, 0x0000000000092541, 0x0000000000092542, 0x0000000000092543 # 704 - 707 D5 S4 C0 + .quad 0xffffffffe009258b, 0xFFFFFFFFC00AA593, 0xFFFFFFFFA00AA59B, 0xFFFFFFFF800AA5A3 # 708 - 711 D5 S4 C4 + .quad 0xffffffff600aa5ab, 0xFFFFFFFF400AA5B3, 0xFFFFFFFF200AA5BB, 0x0000000000092543 # 712 - 715 D5 S4 C8 + .quad 0xffffffffe009258b, 0xFFFFFFFFC00AA593, 0xFFFFFFFFA00AA59B, 0xFFFFFFFF800AA5A3 # 716 - 719 D5 S4 C12 + .quad 0x0000000000002d00, 0x0000000000012D41, 0x0000000000012D42, 0x0000000000052D43 # 720 - 723 D5 S5 C0 + .quad 0xffffffffe0062d8b, 0xFFFFFFFFC0062D93, 0xFFFFFFFFA0062D9B, 0xFFFFFFFF80062DA3 # 724 - 727 D5 S5 C4 + .quad 0xffffffff60062dab, 0xFFFFFFFF40062DB3, 0xFFFFFFFF20062DBB, 0x0000000000052D43 # 728 - 731 D5 S5 C8 + .quad 0xffffffffe0062d8b, 0xFFFFFFFFC0062D93, 0xFFFFFFFFA0062D9B, 0xFFFFFFFF80062DA3 # 732 - 735 D5 S5 C12 + .quad 0x0000000000083500, 0x0000000000093541, 0x00000000000D3542, 0x00000000000E7543 # 736 - 739 D5 S6 C0 + .quad 0xffffffffe00e758b, 0xFFFFFFFFC00E7593, 0xFFFFFFFFA00E759B, 0xFFFFFFFF800E75A3 # 740 - 743 D5 S6 C4 + .quad 0xffffffff600e75ab, 0xFFFFFFFF400E75B3, 0xFFFFFFFF200E75BB, 0x00000000000E7543 # 744 - 747 D5 S6 C8 + .quad 0xffffffffe00e758b, 0xFFFFFFFFC00E7593, 0xFFFFFFFFA00E759B, 0xFFFFFFFF800E75A3 # 748 - 751 D5 S6 C12 + .quad 0x0000000000083d00, 0x00000000000D3D41, 0x00000000000E7D42, 0x00000000000E7D43 # 752 - 755 D5 S7 C0 + .quad 0xffffffffe00e7d8b, 0xFFFFFFFFC00E7D93, 0xFFFFFFFFA00E7D9B, 0xFFFFFFFF800E7DA3 # 756 - 759 D5 S7 C4 + .quad 0xffffffff600e7dab, 0xFFFFFFFF400E7DB3, 0xFFFFFFFF200FFDBB, 0x00000000000E7D43 # 760 - 763 D5 S7 C8 + .quad 0xffffffffe00e7d8b, 0xFFFFFFFFC00E7D93, 0xFFFFFFFFA00E7D9B, 0xFFFFFFFF800E7DA3 # 764 - 767 D5 S7 C12 + .quad 0x0000000000080600, 0x0000000000090641, 0x0000000000090642, 0xFFFFFFFFE009068A # 768 - 771 D6 S0 C0 + .quad 0xffffffffc0090692, 0xFFFFFFFFA009069A, 0xFFFFFFFF800906A2, 0xFFFFFFFF600906AA # 772 - 775 D6 S0 C4 + .quad 0xffffffff400906b2, 0xFFFFFFFF200A86BA, 0x0000000000090642, 0xFFFFFFFFE009068A # 776 - 779 D6 S0 C8 + .quad 0xffffffffc0090692, 0xFFFFFFFFA009069A, 0xFFFFFFFF800906A2, 0xFFFFFFFF600906AA # 780 - 783 D6 S0 C12 + .quad 0x0000000000080e00, 0x0000000000090E41, 0x0000000000090E42, 0xFFFFFFFFE0090E8A # 784 - 787 D6 S1 C0 + .quad 0xffffffffc0090e92, 0xFFFFFFFFA0090E9A, 0xFFFFFFFF80090EA2, 0xFFFFFFFF60090EAA # 788 - 791 D6 S1 C4 + .quad 0xffffffff400a8eb2, 0xFFFFFFFF200A8EBA, 0x0000000000090E42, 0xFFFFFFFFE0090E8A # 792 - 795 D6 S1 C8 + .quad 0xffffffffc0090e92, 0xFFFFFFFFA0090E9A, 0xFFFFFFFF80090EA2, 0xFFFFFFFF60090EAA # 796 - 799 D6 S1 C12 + .quad 0x0000000000081600, 0x0000000000091641, 0x0000000000091642, 0xFFFFFFFFE009168A # 800 - 803 D6 S2 C0 + .quad 0xffffffffc0091692, 0xFFFFFFFFA009169A, 0xFFFFFFFF800916A2, 0xFFFFFFFF600A96AA # 804 - 807 D6 S2 C4 + .quad 0xffffffff400a96b2, 0xFFFFFFFF200A96BA, 0x0000000000091642, 0xFFFFFFFFE009168A # 808 - 811 D6 S2 C8 + .quad 0xffffffffc0091692, 0xFFFFFFFFA009169A, 0xFFFFFFFF800916A2, 0xFFFFFFFF600A96AA # 812 - 815 D6 S2 C12 + .quad 0x0000000000081e00, 0x0000000000091E41, 0x0000000000091E42, 0xFFFFFFFFE0091E8A # 816 - 819 D6 S3 C0 + .quad 0xffffffffc0091e92, 0xFFFFFFFFA0091E9A, 0xFFFFFFFF800A9EA2, 0xFFFFFFFF600A9EAA # 820 - 823 D6 S3 C4 + .quad 0xffffffff400a9eb2, 0xFFFFFFFF200A9EBA, 0x0000000000091E42, 0xFFFFFFFFE0091E8A # 824 - 827 D6 S3 C8 + .quad 0xffffffffc0091e92, 0xFFFFFFFFA0091E9A, 0xFFFFFFFF800A9EA2, 0xFFFFFFFF600A9EAA # 828 - 831 D6 S3 C12 + .quad 0x0000000000082600, 0x0000000000092641, 0x0000000000092642, 0xFFFFFFFFE009268A # 832 - 835 D6 S4 C0 + .quad 0xffffffffc0092692, 0xFFFFFFFFA00AA69A, 0xFFFFFFFF800AA6A2, 0xFFFFFFFF600AA6AA # 836 - 839 D6 S4 C4 + .quad 0xffffffff400aa6b2, 0xFFFFFFFF200AA6BA, 0x0000000000092642, 0xFFFFFFFFE009268A # 840 - 843 D6 S4 C8 + .quad 0xffffffffc0092692, 0xFFFFFFFFA00AA69A, 0xFFFFFFFF800AA6A2, 0xFFFFFFFF600AA6AA # 844 - 847 D6 S4 C12 + .quad 0x0000000000082e00, 0x0000000000092E41, 0x0000000000092E42, 0xFFFFFFFFE0092E8A # 848 - 851 D6 S5 C0 + .quad 0xffffffffc00aae92, 0xFFFFFFFFA00AAE9A, 0xFFFFFFFF800AAEA2, 0xFFFFFFFF600AAEAA # 852 - 855 D6 S5 C4 + .quad 0xffffffff400aaeb2, 0xFFFFFFFF200AAEBA, 0x0000000000092E42, 0xFFFFFFFFE0092E8A # 856 - 859 D6 S5 C8 + .quad 0xffffffffc00aae92, 0xFFFFFFFFA00AAE9A, 0xFFFFFFFF800AAEA2, 0xFFFFFFFF600AAEAA # 860 - 863 D6 S5 C12 + .quad 0x0000000000003600, 0x0000000000013641, 0x0000000000053642, 0xFFFFFFFFE006368A # 864 - 867 D6 S6 C0 + .quad 0xffffffffc0063692, 0xFFFFFFFFA006369A, 0xFFFFFFFF800636A2, 0xFFFFFFFF600636AA # 868 - 871 D6 S6 C4 + .quad 0xffffffff400636b2, 0xFFFFFFFF200636BA, 0x0000000000053642, 0xFFFFFFFFE006368A # 872 - 875 D6 S6 C8 + .quad 0xffffffffc0063692, 0xFFFFFFFFA006369A, 0xFFFFFFFF800636A2, 0xFFFFFFFF600636AA # 876 - 879 D6 S6 C12 + .quad 0x0000000000083e00, 0x00000000000D3E41, 0x00000000000E7E42, 0xFFFFFFFFE00E7E8A # 880 - 883 D6 S7 C0 + .quad 0xffffffffc00e7e92, 0xFFFFFFFFA00E7E9A, 0xFFFFFFFF800E7EA2, 0xFFFFFFFF600E7EAA # 884 - 887 D6 S7 C4 + .quad 0xffffffff400e7eb2, 0xFFFFFFFF200E7EBA, 0x00000000000E7E42, 0xFFFFFFFFE00E7E8A # 888 - 891 D6 S7 C8 + .quad 0xffffffffc00e7e92, 0xFFFFFFFFA00E7E9A, 0xFFFFFFFF800E7EA2, 0xFFFFFFFF600E7EAA # 892 - 895 D6 S7 C12 + .quad 0x0000000000080700, 0x0000000000090741, 0xFFFFFFFFE0090789, 0xFFFFFFFFC0090791 # 896 - 899 D7 S0 C0 + .quad 0xffffffffa0090799, 0xFFFFFFFF800907A1, 0xFFFFFFFF600907A9, 0xFFFFFFFF400907B1 # 900 - 903 D7 S0 C4 + .quad 0xffffffff200907b9, 0x0000000000090741, 0xFFFFFFFFE0090789, 0xFFFFFFFFC0090791 # 904 - 907 D7 S0 C8 + .quad 0xffffffffa0090799, 0xFFFFFFFF800907A1, 0xFFFFFFFF600907A9, 0xFFFFFFFF400907B1 # 908 - 911 D7 S0 C12 + .quad 0x0000000000080f00, 0x0000000000090F41, 0xFFFFFFFFE0090F89, 0xFFFFFFFFC0090F91 # 912 - 915 D7 S1 C0 + .quad 0xffffffffa0090f99, 0xFFFFFFFF80090FA1, 0xFFFFFFFF60090FA9, 0xFFFFFFFF40090FB1 # 916 - 919 D7 S1 C4 + .quad 0xffffffff200a8fb9, 0x0000000000090F41, 0xFFFFFFFFE0090F89, 0xFFFFFFFFC0090F91 # 920 - 923 D7 S1 C8 + .quad 0xffffffffa0090f99, 0xFFFFFFFF80090FA1, 0xFFFFFFFF60090FA9, 0xFFFFFFFF40090FB1 # 924 - 927 D7 S1 C12 + .quad 0x0000000000081700, 0x0000000000091741, 0xFFFFFFFFE0091789, 0xFFFFFFFFC0091791 # 928 - 931 D7 S2 C0 + .quad 0xffffffffa0091799, 0xFFFFFFFF800917A1, 0xFFFFFFFF600917A9, 0xFFFFFFFF400A97B1 # 932 - 935 D7 S2 C4 + .quad 0xffffffff200a97b9, 0x0000000000091741, 0xFFFFFFFFE0091789, 0xFFFFFFFFC0091791 # 936 - 939 D7 S2 C8 + .quad 0xffffffffa0091799, 0xFFFFFFFF800917A1, 0xFFFFFFFF600917A9, 0xFFFFFFFF400A97B1 # 940 - 943 D7 S2 C12 + .quad 0x0000000000081f00, 0x0000000000091F41, 0xFFFFFFFFE0091F89, 0xFFFFFFFFC0091F91 # 944 - 947 D7 S3 C0 + .quad 0xffffffffa0091f99, 0xFFFFFFFF80091FA1, 0xFFFFFFFF600A9FA9, 0xFFFFFFFF400A9FB1 # 948 - 951 D7 S3 C4 + .quad 0xffffffff200a9fb9, 0x0000000000091F41, 0xFFFFFFFFE0091F89, 0xFFFFFFFFC0091F91 # 952 - 955 D7 S3 C8 + .quad 0xffffffffa0091f99, 0xFFFFFFFF80091FA1, 0xFFFFFFFF600A9FA9, 0xFFFFFFFF400A9FB1 # 956 - 959 D7 S3 C12 + .quad 0x0000000000082700, 0x0000000000092741, 0xFFFFFFFFE0092789, 0xFFFFFFFFC0092791 # 960 - 963 D7 S4 C0 + .quad 0xffffffffa0092799, 0xFFFFFFFF800AA7A1, 0xFFFFFFFF600AA7A9, 0xFFFFFFFF400AA7B1 # 964 - 967 D7 S4 C4 + .quad 0xffffffff200aa7b9, 0x0000000000092741, 0xFFFFFFFFE0092789, 0xFFFFFFFFC0092791 # 968 - 971 D7 S4 C8 + .quad 0xffffffffa0092799, 0xFFFFFFFF800AA7A1, 0xFFFFFFFF600AA7A9, 0xFFFFFFFF400AA7B1 # 972 - 975 D7 S4 C12 + .quad 0x0000000000082f00, 0x0000000000092F41, 0xFFFFFFFFE0092F89, 0xFFFFFFFFC0092F91 # 976 - 979 D7 S5 C0 + .quad 0xffffffffa00aaf99, 0xFFFFFFFF800AAFA1, 0xFFFFFFFF600AAFA9, 0xFFFFFFFF400AAFB1 # 980 - 983 D7 S5 C4 + .quad 0xffffffff200aafb9, 0x0000000000092F41, 0xFFFFFFFFE0092F89, 0xFFFFFFFFC0092F91 # 984 - 987 D7 S5 C8 + .quad 0xffffffffa00aaf99, 0xFFFFFFFF800AAFA1, 0xFFFFFFFF600AAFA9, 0xFFFFFFFF400AAFB1 # 988 - 991 D7 S5 C12 + .quad 0x0000000000083700, 0x0000000000093741, 0xFFFFFFFFE0093789, 0xFFFFFFFFC00AB791 # 992 - 995 D7 S6 C0 + .quad 0xffffffffa00ab799, 0xFFFFFFFF800AB7A1, 0xFFFFFFFF600AB7A9, 0xFFFFFFFF400AB7B1 # 996 - 999 D7 S6 C4 + .quad 0xffffffff200ab7b9, 0x0000000000093741, 0xFFFFFFFFE0093789, 0xFFFFFFFFC00AB791 # 1000 - 1003 D7 S6 C8 + .quad 0xffffffffa00ab799, 0xFFFFFFFF800AB7A1, 0xFFFFFFFF600AB7A9, 0xFFFFFFFF400AB7B1 # 1004 - 1007 D7 S6 C12 + .quad 0x0000000000003f00, 0x0000000000053F41, 0xFFFFFFFFE0063F89, 0xFFFFFFFFC0063F91 # 1008 - 1011 D7 S7 C0 + .quad 0xffffffffa0063f99, 0xFFFFFFFF80063FA1, 0xFFFFFFFF60063FA9, 0xFFFFFFFF40063FB1 # 1012 - 1015 D7 S7 C4 + .quad 0xffffffff20063fb9, 0x0000000000053F41, 0xFFFFFFFFE0063F89, 0xFFFFFFFFC0063F91 # 1016 - 1019 D7 S7 C8 + .quad 0xffffffffa0063f99, 0xFFFFFFFF80063FA1, 0xFFFFFFFF60063FA9, 0xFFFFFFFF40063FB1 # 1020 - 1023 D7 S7 C12 diff --git a/emulator-asm/src/dma/memcpy_fast.asm b/emulator-asm/src/dma/memcpy_fast.asm new file mode 100644 index 000000000..0ad97dae1 --- /dev/null +++ b/emulator-asm/src/dma/memcpy_fast.asm @@ -0,0 +1,97 @@ +.intel_syntax noprefix +.code64 + +################################################################################ +# dma_memcpy_fast - Optimized memcpy using rep movsq (no tracing) +# +# Fast memory copy function optimized for performance using hardware-accelerated +# instructions. Handles overlapping memory regions correctly (like memmove). +# +# PARAMETERS (System V AMD64 ABI): +# rdi = dst (u64) - Destination address +# rsi = src (u64) - Source address +# rdx = count (usize) - Number of bytes to copy +# +# RETURN VALUE: None +# +# STRATEGY: +# For non-overlapping regions: +# 1. Copy pre_count unaligned bytes (0-7 bytes to reach alignment) +# 2. Copy aligned qwords using rep movsq (~1-2 cycles/qword) +# 3. Copy post_count remaining bytes (0-7 bytes) +# +# For overlapping regions (dst between src and src+count): +# - Copy backward byte-by-byte using rep movsb with std flag +# +# PERFORMANCE: ~10-20 cycles overhead + ~1-2 cycles per qword +# +# REGISTERS USED: rax, rcx, rdi, rsi, rdx, r8, r9 +################################################################################ + +.global dma_memcpy_fast + +.section .text + +dma_memcpy_fast: + # Check if count is 0 + test rdx, rdx # 1 cycle + jz .L_fast_done # nothing to copy + + # Save original values + mov r8, rdi # r8 = dst + mov r9, rsi # r9 = src + + # Check for overlap: if dst < src or dst >= src+count, no overlap + lea rax, [rsi + rdx] # rax = src + count + cmp rdi, rsi # compare dst with src + jb .L_fast_forward # dst < src, copy forward + cmp rdi, rax # compare dst with src+count + jae .L_fast_forward # dst >= src+count, no overlap + + # Overlap detected: copy backward + lea rsi, [r9 + rdx - 1] # rsi = src + count - 1 (use r9, original src) + lea rdi, [r8 + rdx - 1] # rdi = dst + count - 1 (use r8, original dst) + mov rcx, rdx # rcx = count + std # set direction flag (backward) + rep movsb # copy backward + cld # clear direction flag + jmp .L_fast_done + +.L_fast_forward: + # No overlap: optimized 3-phase copy + # Calculate dst_offset and pre_count + mov rax, r8 # rax = dst + and rax, 0x07 # rax = dst_offset + test rax, rax # check if already aligned + jz .L_fast_aligned # skip pre-copy if aligned + + # Copy pre_count bytes to align dst + mov rcx, 8 # rcx = 8 + sub rcx, rax # rcx = 8 - dst_offset = pre_count + cmp rcx, rdx # check if pre_count > count + jbe .L_fast_pre_ok # pre_count <= count + mov rcx, rdx # pre_count = count (copy all) +.L_fast_pre_ok: + sub rdx, rcx # count -= pre_count + rep movsb # copy pre_count bytes + # rsi and rdi are now advanced and rdi is aligned + +.L_fast_aligned: + # Copy aligned qwords using rep movsq + mov rcx, rdx # rcx = remaining count + shr rcx, 3 # rcx = count / 8 (qword count) + jz .L_fast_post # skip if no qwords to copy + rep movsq # copy qwords (~1-2 cycles each) + +.L_fast_post: + # Copy remaining bytes (0-7) + mov rcx, rdx # rcx = original count + and rcx, 0x07 # rcx = count % 8 (post_count) + jz .L_fast_done # skip if no remaining bytes + rep movsb # copy remaining bytes + +.L_fast_done: + ret + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/test_dma.cpp b/emulator-asm/src/dma/test_dma.cpp new file mode 100644 index 000000000..dc7005b44 --- /dev/null +++ b/emulator-asm/src/dma/test_dma.cpp @@ -0,0 +1,978 @@ +#include +#include +#include +#include +#include +#include + +#define NO_OVERLAPPING 0xFFFFFFFF + +// External assembly function declarations +extern "C" { + uint64_t dma_memcpy_mtrace(uint64_t dst, uint64_t src, uint64_t count, uint64_t* trace_ptr); + uint64_t dma_memcpy_mops(uint64_t dst, uint64_t src, uint64_t count, uint64_t* mops_ptr); + void dma_memcpy_fast(uint64_t dst, uint64_t src, uint64_t count); + uint64_t fast_dma_encode(uint64_t dst, uint64_t src, uint64_t count); +} +const char *mops_labels[16] = {"NOP", "CWR1", "RD1", "WR1", "RD2", "WR2", "RD4", "WR4", "RD8", "WR8", + "ARD", "AWR", "BR", "BW", "ABR", "ABW"}; + +// MOPS constants from dma_constants.inc +const uint64_t EXTRA_PARAMETER_ADDR = 0xA000'0F00; +const uint64_t MOPS_ALIGNED_READ = 0x0000'000C'0000'0000ULL; +const uint64_t MOPS_ALIGNED_BLOCK_READ = 0x0000'000E'0000'0000ULL; +const uint64_t MOPS_ALIGNED_BLOCK_WRITE = 0x0000'000F'0000'0000ULL; +const uint64_t MOPS_BLOCK_WORDS_SBITS = 36; + +class Memory { +protected: + uint8_t *original_bytes; +public: + uint8_t *bytes; + size_t size; + Memory (size_t size): size(size) { + bytes = (uint8_t *) aligned_alloc(8, size); + if (bytes == NULL) { + printf("Failed to allocate bytes\n"); + exit(1); + } + if ((((uint64_t) bytes) & 0x07) != 0) { + printf("Invalid allocation %p\n", bytes); + exit(1); + } + original_bytes = (uint8_t *) aligned_alloc(8, size); + if (original_bytes == NULL) { + printf("Failed to allocate original_bytes\n"); + free(bytes); + exit(1); + } + } + Memory(const Memory&) = delete; + Memory& operator=(const Memory&) = delete; + ~Memory() { + if (original_bytes) free(original_bytes); + if (bytes) free(bytes); + original_bytes = NULL; + bytes = NULL; + } + + const uint8_t *get_original_bytes(uint8_t *reference) { + return original_bytes + (reference - bytes); + } + void fill_pattern(uint8_t start = 0) { + for (size_t i = 0; i < size; ++i) { + bytes[i] = start + i; + } + memcpy(original_bytes, bytes, size); + } + + bool verify_pattern(uint8_t start = 0, const char *title = "") { + for (size_t i = 0; i < size; ++i) { + uint8_t expected = start + i; + if (bytes[i] != expected) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", + title, expected, i, bytes[i]); + return false; + } + } + return true; + } + + bool verify_pattern_except(uint8_t start, size_t addr, size_t count, const char *title = "") { + size_t from = addr - (size_t)bytes; + size_t to = from + count - 1; + for (size_t i = 0; i < size; ++i) { + if (i >= from && i <= to) continue; + uint8_t expected = start + i; + if (bytes[i] != expected) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", + title, expected, i, bytes[i]); + return false; + } + } + return true; + } +}; + +// Helper class to manage aligned test buffers +class AlignedBuffer { +public: + std::vector data; + + AlignedBuffer(size_t size) : data(size, 0) {} + + uint64_t* aligned_ptr() { + return reinterpret_cast(data.data()); + } + + uint8_t* byte_ptr() { + return data.data(); + } + + void fill_pattern(uint8_t start = 0) { + for (size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast(start + i); + } + } + + bool verify_pattern(uint8_t start = 0, const char *title = "") { + for (size_t i = 0; i < data.size(); ++i) { + uint8_t expected = static_cast(start + i); + if (data[i] != expected) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", + title, expected, i, data[i]); + return false; + } + } + return true; + } + + bool verify_pattern_except(uint8_t start, size_t from, size_t count, const char *title = "") { + size_t to = from + count - 1; + for (size_t i = 0; i < data.size(); ++i) { + if (i >= from && i <= to) continue; + uint8_t expected = static_cast(start + i); + if (data[i] != expected) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", + title, expected, i, data[i]); + return false; + } + } + return true; + } + + bool verify_fill(uint8_t value, const char *title = "") { + for (size_t i = 0; i < data.size(); ++i) { + if (data[i] != value) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]:0x%02X\n", + title, value, i, data[i]); + return false; + } + } + return true; + } + + bool verify_fill_except(uint8_t value, size_t from, size_t count, const char *title = "") { + size_t to = from + count - 1; + for (size_t i = 0; i < data.size(); ++i) { + if (i >= from && i <= to) continue; + if (data[i] != value) { + printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]:0x%02X\n", + title, value, i, data[i]); + return false; + } + } + return true; + } + + void fill_value(uint8_t value) { + std::fill(data.begin(), data.end(), value); + } +}; + +#define TRACE_EQ(EXPECTED, CALCULATED, MSG) \ + if (EXPECTED != CALCULATED) { \ + fprintf(stderr, "❌ FAIL: Trace comparation on %s (E: 0x%016lX vs 0x%016lX) \n", MSG, EXPECTED, CALCULATED); \ + exit(1); \ + } + +// Reference implementation for encode_memcpy from Rust +uint64_t encode_memcpy_reference(uint64_t dst, uint64_t src, uint64_t count) { + uint64_t dst_offset = dst & 0x07; + uint64_t src_offset = src & 0x07; + + uint64_t pre_count, loop_count, post_count; + + if (dst_offset > 0) { + uint64_t _pre_count = 8 - dst_offset; + if (_pre_count >= count) { + pre_count = count; + loop_count = 0; + post_count = 0; + } else { + uint64_t pending = count - _pre_count; + pre_count = _pre_count; + loop_count = pending >> 3; + post_count = pending & 0x07; + } + } else { + pre_count = 0; + loop_count = count >> 3; + post_count = count & 0x07; + } + + uint64_t pre_writes = (pre_count > 0) + (post_count > 0); + uint64_t src_offset_pos = (src_offset + pre_count) & 0x07; + uint64_t double_src_post = (src_offset_pos + post_count) > 8; + uint64_t double_src_pre = (src_offset + pre_count) > 8; + uint64_t extra_src_reads = + (count == 0) ? 0 : ((((src + count - 1) >> 3) - (src >> 3) + 1) - loop_count); + uint64_t src64_inc_by_pre = (pre_count > 0 && (src_offset + pre_count) >= 8); + uint64_t unaligned_dst_src = (src_offset != dst_offset); + + return pre_count + | (post_count << 3) + | (pre_writes << 6) + | (dst_offset << 8) + | (src_offset << 11) + | (double_src_pre << 14) + | (double_src_post << 15) + | (extra_src_reads << 16) + | (src64_inc_by_pre << 18) + | (unaligned_dst_src << 19) + | (pre_count << 29) + | (loop_count << 32); +} + +// Extract fields from encoded value +struct EncodedInfo { + uint64_t loop_count; + uint64_t pre_writes; + uint64_t dst_offset; + uint64_t src_offset; + uint64_t pre_count; + uint64_t post_count; + bool double_src_pre; + bool double_src_post; + uint64_t extra_src_reads; + uint64_t src64_inc_by_pre; + uint64_t unaligned_dst_src; + + EncodedInfo(uint64_t encoded) { + loop_count = encoded >> 32; + pre_count = encoded & 0x07; + post_count = (encoded >> 3) & 0x07; + pre_writes = (encoded >> 6) & 0x03; + dst_offset = (encoded >> 8) & 0x07; + src_offset = (encoded >> 11) & 0x07; + double_src_pre = (encoded >> 14) & 0x01; + double_src_post = (encoded >> 15) & 0x01; + extra_src_reads = (encoded >> 16) & 0x03; + src64_inc_by_pre = (encoded >> 18) & 0x01; + unaligned_dst_src = (encoded >> 19) & 0x01; + } + + void print() const { + std::cout << " loop_count: " << loop_count << "\n" + << " pre_writes: " << pre_writes << "\n" + << " dst_offset: " << dst_offset << "\n" + << " src_offset: " << src_offset << "\n" + << " pre_count: " << pre_count << "\n" + << " post_count: " << post_count << "\n" + << " double_src_pre: " << double_src_pre << "\n" + << " double_src_post: " << double_src_post << "\n" + << " extra_src_reads: " << extra_src_reads << "\n" + << " src64_inc_by_pre: " << src64_inc_by_pre << "\n" + << " unaligned_dst_src: " << unaligned_dst_src << "\n"; + } +}; + +void print_hex_dump(const char* label, const uint8_t* data, size_t size) { + std::cout << label << " (" << size << " bytes):\n"; + for (size_t i = 0; i < size; i += 16) { + std::cout << " " << std::hex << std::setw(4) << std::setfill('0') << i << ": "; + for (size_t j = 0; j < 16 && i + j < size; ++j) { + std::cout << std::hex << std::setw(2) << std::setfill('0') + << static_cast(data[i + j]) << " "; + } + std::cout << "\n"; + } + std::cout << std::dec; +} + +bool test_memcpy_mtrace(Memory &mem, uint64_t dst_offset, uint64_t src_offset, size_t count, + const char* description, int64_t overlapping = NO_OVERLAPPING) { + + + bool is_overlapping = overlapping != NO_OVERLAPPING; + if (is_overlapping) { + printf("\n\x1b[1;36m##### test_memcpy_mtrace(%ld, %ld, %ld,\"%s\", overlapping:%ld) #####\x1b[0m\n", + dst_offset, src_offset, count, description, overlapping); + } else { + printf("\n\x1b[1;36m##### test_memcpy_mtrace(%ld, %ld, %ld,\"%s\") #####\x1b[0m\n", + dst_offset, src_offset, count, description); + } + mem.fill_pattern(0x10); + + uint8_t *src; + uint8_t *dst; + + if (is_overlapping) { + src = mem.bytes + 1024; + dst = src + overlapping; + } else { + src = mem.bytes + 1024; + dst = src + ((count + 7) & ~0x07) + 1024; + } + AlignedBuffer trace_buf(4096); + + // Clear trace buffer + trace_buf.fill_value(0); + + if (!mem.verify_pattern(0x10) || !trace_buf.verify_fill(0, "trace_buff")) { + return false; + } + + uint64_t src_addr = ((uint64_t) src) + src_offset; + uint64_t dst_addr = ((uint64_t) dst) + dst_offset; + uint64_t* trace_ptr = trace_buf.aligned_ptr(); + + printf("TEST dst:0x%08lX src:0x%08lX count:%ld trace:%p\n", dst_addr, src_addr, count, trace_ptr); + + // Calculate reference encoding BEFORE assembly call to avoid register corruption + uint64_t encoded_ref = encode_memcpy_reference(dst_addr, src_addr, count); + + // Call assembly function and capture return value + uint64_t qwords_written = dma_memcpy_mtrace(dst_addr, src_addr, count, trace_ptr); + + // Get encoded value from trace + uint64_t encoded_asm = trace_ptr[0]; + + if (encoded_asm != encoded_ref) { + printf("Encoded (ASM): 0x%016lX\n", encoded_asm); + printf("Encoded (REF): 0x%016lX\n", encoded_ref); + + std::cerr << "❌ FAIL: Encoded value mismatch!\n"; + EncodedInfo info_asm(encoded_asm); + EncodedInfo info_ref(encoded_ref); + std::cout << "ASM info:\n"; + info_asm.print(); + std::cout << "REF info:\n"; + info_ref.print(); + return false; + } + + // Verify the actual memcpy was performed + const uint8_t* src_bytes; + if (is_overlapping) { + src_bytes = mem.get_original_bytes((uint8_t *)src_addr); + } else { + src_bytes = (uint8_t *)src_addr; + } + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + + bool copy_ok = true; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != src_bytes[i]) { + std::cerr << "❌ FAIL: Memory copy mismatch at byte " << i << "\n"; + std::cerr << " Expected: 0x" << std::hex << static_cast(src_bytes[i]) + << ", Got: 0x" << static_cast(dst_bytes[i]) << std::dec << "\n"; + copy_ok = false; + break; + } + } + + if (!copy_ok) { + print_hex_dump("Source", src_bytes, std::min(count, size_t(64))); + print_hex_dump("Destination", dst_bytes, std::min(count, size_t(64))); + return false; + } + + std::cout << "✅ PASS: Encoding and copy correct\n"; + + // Print trace buffer summary + EncodedInfo info(encoded_asm); + size_t trace_idx = 1; + std::cout << "Trace buffer:\n"; + std::cout << " [0] Encoded: 0x" << std::hex << trace_ptr[0] << std::dec << "\n"; + + uint64_t *dst_original = (uint64_t*) mem.get_original_bytes((uint8_t *) (dst_addr & ~0x07)); + if (info.pre_count > 0) { + TRACE_EQ(dst_original[0], trace_ptr[trace_idx], "PRE pre-write value not match"); + trace_idx++; + } + + if (info.post_count > 0) { + size_t last_dst_index = (dst_offset + count - 1) >> 3; + TRACE_EQ(dst_original[last_dst_index], trace_ptr[trace_idx], "POST pre-write value not match"); + trace_idx++; + } + + size_t expected_src_qwords = info.loop_count + info.extra_src_reads; + // Verify that the function returned the correct number of qwords written + size_t expected_total_qwords = trace_idx + expected_src_qwords; + + if (qwords_written != expected_total_qwords) { + std::cerr << "❌ FAIL: Incorrect number of qwords returned!\n"; + std::cerr << " Expected: " << expected_total_qwords << " qwords\n"; + std::cerr << " Got: " << qwords_written << " qwords\n"; + return false; + } + + uint64_t *src_original = (uint64_t*) mem.get_original_bytes((uint8_t *) (src_addr & ~0x07)); + for (size_t index = 0; index < expected_src_qwords; ++index) { + TRACE_EQ(src_original[index], trace_ptr[trace_idx], "SRC values not match"); + trace_idx++; + } + if (!mem.verify_pattern_except(0x10, dst_addr, count, "mem (out)") || + !trace_buf.verify_fill_except(0, 0, qwords_written * 8, "trace_buff (out)")) { + return false; + } + std::cout << "✅ Returned correct qword count: " << qwords_written << " qwords\n"; + + return true; +} + +bool test_memcpy_mops(Memory &mem, uint64_t dst_offset, uint64_t src_offset, size_t count, + const char* description, int64_t overlapping = NO_OVERLAPPING) { + + + bool is_overlapping = overlapping != NO_OVERLAPPING; + if (is_overlapping) { + printf("\n\x1b[1;35m##### test_memcpy_mops(%ld, %ld, %ld,\"%s\", overlapping:%ld) #####\x1b[0m\n", + dst_offset, src_offset, count, description, overlapping); + } else { + printf("\n\x1b[1;35m##### test_memcpy_mops(%ld, %ld, %ld,\"%s\") #####\x1b[0m\n", + dst_offset, src_offset, count, description); + } + mem.fill_pattern(0x10); + + uint8_t *src; + uint8_t *dst; + + if (is_overlapping) { + src = mem.bytes + 1024; + dst = src + overlapping; + } else { + src = mem.bytes + 1024; + dst = src + ((count + 7) & ~0x07) + 1024; + } + AlignedBuffer mops_buf(4096); + + // Clear mops buffer + mops_buf.fill_value(0); + + if (!mem.verify_pattern(0x10) || !mops_buf.verify_fill(0, "mops_buff")) { + return false; + } + + uint64_t src_addr = ((uint64_t) src) + src_offset; + uint64_t dst_addr = ((uint64_t) dst) + dst_offset; + uint64_t* mops_ptr = mops_buf.aligned_ptr(); + + printf("TEST dst:0x%08lX src:0x%08lX count:%ld mops:%p\n", dst_addr, src_addr, count, mops_ptr); + + // Calculate reference encoding to know expected structure + uint64_t encoded_ref = encode_memcpy_reference(dst_addr, src_addr, count); + EncodedInfo info(encoded_ref); + printf("INFO pre:%ld%s post:%ld%s loop:%ld sibp:%ld\n", info.pre_count, info.double_src_pre ? "+D":"", + info.post_count, info.double_src_post ? "+D":"", info.loop_count, info.src64_inc_by_pre); + + // Call assembly function and capture return value + uint64_t mops_entries = dma_memcpy_mops(dst_addr, src_addr, count, mops_ptr); + + // Verify the actual memcpy was performed + const uint8_t* src_bytes; + if (is_overlapping) { + src_bytes = mem.get_original_bytes((uint8_t *)src_addr); + } else { + src_bytes = (uint8_t *)src_addr; + } + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + + bool copy_ok = true; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != src_bytes[i]) { + std::cerr << "❌ FAIL: Memory copy mismatch at byte " << i << "\n"; + std::cerr << " Expected: 0x" << std::hex << static_cast(src_bytes[i]) + << ", Got: 0x" << static_cast(dst_bytes[i]) << std::dec << "\n"; + copy_ok = false; + break; + } + } + + if (!copy_ok) { + print_hex_dump("Source", src_bytes, std::min(count, size_t(64))); + print_hex_dump("Destination", dst_bytes, std::min(count, size_t(64))); + return false; + } + + std::vector> expected; + + expected.emplace_back(MOPS_ALIGNED_READ + EXTRA_PARAMETER_ADDR, "PARAM count"); + + if (info.pre_count > 0) { + expected.emplace_back(MOPS_ALIGNED_READ + (dst_addr & ~0x07ULL), "PRE preread dst"); + expected.emplace_back((info.double_src_pre ? (MOPS_ALIGNED_BLOCK_READ + (2ULL << MOPS_BLOCK_WORDS_SBITS)): MOPS_ALIGNED_READ) + + (src_addr & ~0x07ULL), "PRE src read"); + } + + if (info.post_count > 0) { + expected.emplace_back(MOPS_ALIGNED_READ + ((dst_addr + count - 1) & ~0x07ULL), "POST preread dst"); + expected.emplace_back((info.double_src_post ? (MOPS_ALIGNED_BLOCK_READ + (2ULL << MOPS_BLOCK_WORDS_SBITS)): MOPS_ALIGNED_READ) + + ((src_addr + info.pre_count + info.loop_count * 8) & ~0x07ULL), "POST src read"); + } + + if (info.loop_count > 0) { + expected.emplace_back(MOPS_ALIGNED_BLOCK_READ + ((info.loop_count + (info.unaligned_dst_src)) << MOPS_BLOCK_WORDS_SBITS) + + ((src_addr + info.pre_count) & ~0x07ULL), "LOOP read src"); + } + + if (count > 0) { + expected.emplace_back(MOPS_ALIGNED_BLOCK_WRITE + ((info.loop_count + info.pre_writes) << MOPS_BLOCK_WORDS_SBITS) + + (dst_addr & ~0x07ULL), "write dst"); + } + + size_t max_entries = std::max(mops_entries, expected.size()); + bool errors = false; + for (size_t i = 0; i < max_entries; ++i) { + printf("MOPS[%2ld] = ", i); + if (i < mops_entries) { + printf("%3ld %3s 0x%08lX # ", mops_ptr[i] >> MOPS_BLOCK_WORDS_SBITS, + mops_labels[(mops_ptr[i] >> 32) & 0x0F], mops_ptr[i] & 0xFFFF'FFFF); + } else { + printf("--- --- ---------- #"); + } + if (i < expected.size()) { + printf("%3ld %3s 0x%08lX %s", expected[i].first >> MOPS_BLOCK_WORDS_SBITS, + mops_labels[(expected[i].first >> 32) & 0x0F], expected[i].first & 0xFFFF'FFFF, expected[i].second.c_str()); + } + if (i >= mops_entries || i >= expected.size()) { + printf(" \x1B[31;1mFAIL\x1B[0m\n"); + errors = true; + } else if (mops_ptr[i] != expected[i].first) { + printf(" \x1B[31;1mNOT MATCH\x1B[0m\n"); + errors = true; + } else { + printf("\n"); + } + } + + // Verify total mops entries count + if (mops_entries != expected.size()) { + printf("FAIL: Incorrect number of mops entries (E:%ld vs %ld)", expected.size(), mops_entries); + return false; + } + + if (errors) { + return false; + } + std::cout << "✅ PASS: MOPS entries and copy correct (" << mops_entries << " entries)\n"; + + if (!mem.verify_pattern_except(0x10, dst_addr, count, "mem (out)") || + !mops_buf.verify_fill_except(0, 0, mops_entries * 8, "mops_buff (out)")) { + return false; + } + + return true; +} + +bool test_overlapping_copy(const char* description, int64_t offset) { + std::cout << "\n=== Test: " << description << " ===\n"; + std::cout << "Offset: " << offset << " bytes\n"; + + AlignedBuffer buf(2048); + AlignedBuffer trace_buf(2048); + + // Fill with pattern + buf.fill_pattern(0x20); + trace_buf.fill_value(0); + + size_t count = 32; + uint64_t src_addr = reinterpret_cast(buf.byte_ptr() + 64); + uint64_t dst_addr = src_addr + offset; + + // Validate bounds + if (dst_addr < reinterpret_cast(buf.byte_ptr()) || + dst_addr + count > reinterpret_cast(buf.byte_ptr() + buf.data.size())) { + std::cerr << "❌ FAIL: dst_addr out of bounds\n"; + return false; + } + uint64_t* trace_ptr = trace_buf.aligned_ptr(); + + // Save original data + std::vector original_src(count); + std::memcpy(original_src.data(), reinterpret_cast(src_addr), count); + + // Call function + dma_memcpy_mtrace(dst_addr, src_addr, count, trace_ptr); + + // Verify copy + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + bool ok = true; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != original_src[i]) { + std::cerr << "❌ FAIL: Overlapping copy mismatch at byte " << i << "\n"; + ok = false; + break; + } + } + + if (ok) { + std::cout << "✅ PASS: Overlapping copy correct\n"; + } + + return ok; +} + +bool test_fast_memcpy(uint64_t dst_offset, uint64_t src_offset, size_t count, + const char* description) { + std::cout << "\n=== Test Fast: " << description << " ===\n"; + std::cout << "dst_offset=" << dst_offset << ", src_offset=" << src_offset + << ", count=" << count << "\n"; + + // Allocate buffers + AlignedBuffer src_buf(2048); + AlignedBuffer dst_buf(2048); + + // Fill source with pattern + src_buf.fill_pattern(0x10); + // Fill destination with different pattern + dst_buf.fill_pattern(0xA0); + + // Calculate actual addresses with offsets + uint64_t src_addr = reinterpret_cast(src_buf.byte_ptr() + 64) + src_offset; + uint64_t dst_addr = reinterpret_cast(dst_buf.byte_ptr() + 64) + dst_offset; + + // Call assembly function (no trace) + dma_memcpy_fast(dst_addr, src_addr, count); + + // Verify the memcpy was performed + const uint8_t* src_bytes = reinterpret_cast(src_addr); + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + + bool copy_ok = true; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != src_bytes[i]) { + std::cout << "❌ FAIL: Mismatch at byte " << i << "\n"; + std::cout << " Expected: 0x" << std::hex << (int)src_bytes[i] + << " Got: 0x" << (int)dst_bytes[i] << std::dec << "\n"; + copy_ok = false; + break; + } + } + + if (!copy_ok) { + return false; + } + + std::cout << "✅ PASS: Fast copy correct\n"; + return true; +} + +bool test_fast_overlapping_heap(const char* description, int64_t offset) { + std::cout << "\n=== Test Fast Overlap (HEAP): " << description << " ===\n"; + std::cout << "Offset: " << offset << " bytes\n"; + + size_t count = 32; + size_t buffer_size = 4096; + + // Allocate with aligned_alloc to get proper alignment and avoid vector overhead + uint8_t* buf = (uint8_t*)aligned_alloc(8, buffer_size); + if (!buf) { + std::cerr << "❌ FAIL: allocation failed\n"; + return false; + } + + // Fill with pattern + for (size_t i = 0; i < buffer_size; ++i) { + buf[i] = 0x20 + (i & 0xFF); + } + + uint64_t src_addr = reinterpret_cast(buf + 1024); + uint64_t dst_addr = src_addr + offset; + + // Validate bounds + if (dst_addr < reinterpret_cast(buf) || + dst_addr + count > reinterpret_cast(buf + buffer_size)) { + std::cerr << "❌ FAIL: dst_addr out of bounds\n"; + free(buf); + return false; + } + + // Set canaries + size_t min_addr = std::min(src_addr, dst_addr) - reinterpret_cast(buf); + size_t max_addr = std::max(src_addr + count, dst_addr + count) - reinterpret_cast(buf); + + const uint8_t CANARY = 0xCA; + for (size_t i = min_addr - 8; i < min_addr; ++i) { + buf[i] = CANARY; + } + for (size_t i = max_addr; i < max_addr + 8; ++i) { + buf[i] = CANARY; + } + + // Save original data + uint8_t original_src[32]; + const uint8_t* src_bytes = reinterpret_cast(src_addr); + for (size_t i = 0; i < count; ++i) { + original_src[i] = src_bytes[i]; + } + + // Call function + printf("dma_memcpy_fast(0x%016lx,0x%016lx,%ld)\n", dst_addr, src_addr, count); + dma_memcpy_fast(dst_addr, src_addr, count); + printf("dma_memcpy_fast-END\n"); + + // Check canaries + bool canaries_ok = true; + for (size_t i = min_addr - 8; i < min_addr; ++i) { + if (buf[i] != CANARY) { + std::cerr << "❌ FAIL: Canary corrupted BEFORE at offset " << i << "\n"; + canaries_ok = false; + } + } + for (size_t i = max_addr; i < max_addr + 8; ++i) { + if (buf[i] != CANARY) { + std::cerr << "❌ FAIL: Canary corrupted AFTER at offset " << i << "\n"; + canaries_ok = false; + } + } + + // Verify result + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + bool ok = canaries_ok; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != original_src[i]) { + std::cout << "❌ FAIL: Mismatch at byte " << i << "\n"; + ok = false; + break; + } + } + + free(buf); + + if (ok) { + std::cout << "✅ PASS: Fast overlapping copy correct (heap)\n"; + } + + return ok; +} + +bool test_fast_overlapping(const char* description, int64_t offset) { + std::cout << "\n=== Test Fast Overlap: " << description << " ===\n"; + std::cout << "Offset: " << offset << " bytes\n"; + + size_t count = 32; + + // Use statically allocated aligned buffer with canaries + static uint8_t static_buf[4096] __attribute__((aligned(8))); + + // Fill with pattern + for (size_t i = 0; i < sizeof(static_buf); ++i) { + static_buf[i] = 0x20 + (i & 0xFF); + } + + // Calculate addresses with safety margins + size_t guard_size = 64; // 64 bytes before and after + uint64_t src_addr = reinterpret_cast(static_buf + 1024); + uint64_t dst_addr = src_addr + offset; + + // Validate bounds + if (dst_addr < reinterpret_cast(static_buf + guard_size) || + dst_addr + count > reinterpret_cast(static_buf + sizeof(static_buf) - guard_size)) { + std::cerr << "❌ FAIL: dst_addr out of bounds\n"; + return false; + } + + // Set canary values around the operation zone + size_t min_addr = std::min(src_addr, dst_addr) - reinterpret_cast(static_buf); + size_t max_addr = std::max(src_addr + count, dst_addr + count) - reinterpret_cast(static_buf); + + // Fill canaries before and after + const uint8_t CANARY = 0xCA; + for (size_t i = min_addr - 8; i < min_addr; ++i) { + static_buf[i] = CANARY; + } + for (size_t i = max_addr; i < max_addr + 8; ++i) { + static_buf[i] = CANARY; + } + + // Save original data using simple array + uint8_t original_src[32]; + const uint8_t* src_bytes = reinterpret_cast(src_addr); + for (size_t i = 0; i < count; ++i) { + original_src[i] = src_bytes[i]; + } + // std::memcpy(original_src.data(), reinterpret_cast(src_addr), count); + + // Call fast function + printf("dma_memcpy_fast(0x%016lx,0x%016lx,%ld)\n", dst_addr, src_addr, count); + dma_memcpy_fast(dst_addr, src_addr, count); + printf("dma_memcpy_fast-END\n"); + + // Check canaries for buffer overflow + bool canaries_ok = true; + for (size_t i = min_addr - 8; i < min_addr; ++i) { + if (static_buf[i] != CANARY) { + std::cerr << "❌ FAIL: Canary corrupted BEFORE region at offset " << i + << " (expected 0x" << std::hex << (int)CANARY + << ", got 0x" << (int)static_buf[i] << std::dec << ")\n"; + canaries_ok = false; + } + } + for (size_t i = max_addr; i < max_addr + 8; ++i) { + if (static_buf[i] != CANARY) { + std::cerr << "❌ FAIL: Canary corrupted AFTER region at offset " << i + << " (expected 0x" << std::hex << (int)CANARY + << ", got 0x" << (int)static_buf[i] << std::dec << ")\n"; + canaries_ok = false; + } + } + + if (!canaries_ok) { + std::cerr << "❌ BUFFER OVERFLOW DETECTED!\n"; + std::cerr << " src_addr: 0x" << std::hex << src_addr << std::dec << "\n"; + std::cerr << " dst_addr: 0x" << std::hex << dst_addr << std::dec << "\n"; + std::cerr << " count: " << count << "\n"; + std::cerr << " offset: " << offset << "\n"; + return false; + } + + // Verify result + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + bool ok = true; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != original_src[i]) { + std::cout << "❌ FAIL: Mismatch at byte " << i << "\n"; + ok = false; + break; + } + } + + if (!ok) { + return false; + } + + std::cout << "✅ PASS: Fast overlapping copy correct\n"; + return true; +} + +int main() { + Memory mem (8192); + std::cout << "Testing DMA memory operations assembly implementation\n"; + std::cout << "=====================================================\n"; + + int passed = 0; + int total = 0; + + // Test mtrace (memory trace with full data) + std::cout << "\n\x1b[1;33m=== MTRACE Tests (Full Memory Trace) ===\x1b[0m\n"; + auto run_mtrace_test = [&](uint64_t dst_off, uint64_t src_off, size_t count, const char* desc) { + total++; + if (test_memcpy_mtrace(mem, dst_off, src_off, count, desc)) { + passed++; + } + }; + + run_mtrace_test(0, 0, 0, "Zero count"); + run_mtrace_test(0, 0, 1, "Single byte, aligned"); + run_mtrace_test(0, 0, 8, "One qword, aligned"); + run_mtrace_test(0, 0, 16, "Two qwords, aligned"); + run_mtrace_test(1, 0, 7, "dst_offset=1, count=7"); + run_mtrace_test(7, 0, 1, "dst_offset=7, count=1"); + run_mtrace_test(7, 0, 2, "dst_offset=7, count=2"); + run_mtrace_test(3, 5, 10, "dst_offset=3, src_offset=5, count=10"); + run_mtrace_test(0, 0, 100, "Large aligned copy"); + run_mtrace_test(3, 5, 100, "Large unaligned copy"); + + // Test mops (memory operations - addresses only) + std::cout << "\n\x1b[1;33m=== MOPS Tests (Memory Operations) ===\x1b[0m\n"; + auto run_mops_test = [&](uint64_t dst_off, uint64_t src_off, size_t count, const char* desc) { + total++; + if (test_memcpy_mops(mem, dst_off, src_off, count, desc)) { + passed++; + } else { + exit(1); + } + }; + + run_mops_test(0, 0, 0, "Zero count"); + run_mops_test(0, 0, 1, "Single byte, aligned"); + run_mops_test(0, 0, 8, "One qword, aligned"); + run_mops_test(0, 0, 16, "Two qwords, aligned"); + run_mops_test(1, 0, 7, "dst_offset=1, count=7"); + run_mops_test(7, 0, 1, "dst_offset=7, count=1"); + run_mops_test(7, 0, 2, "dst_offset=7, count=2"); + run_mops_test(3, 5, 10, "dst_offset=3, src_offset=5, count=10"); + run_mops_test(0, 0, 100, "Large aligned copy"); + run_mops_test(3, 5, 100, "Large unaligned copy"); + + // Comprehensive test + std::cout << "\n=== Comprehensive Test ===\n"; + for (uint64_t dst_off = 0; dst_off < 8; ++dst_off) { + for (uint64_t src_off = 0; src_off < 8; ++src_off) { + for (size_t count = 0; count < 128; ++count) { + total++; + if (test_memcpy_mtrace(mem, dst_off, src_off, count, "Comprehensive")) { + passed++; + } + total++; + if (test_memcpy_mtrace(mem, dst_off, src_off, count, "Comprehensive overlapping 0", 0)) { + passed++; + } + // total++; + // if (count > 4) { + // if (test_encode_memcpy(mem, dst_off, src_off, count, "Comprehensive overlapping -4", -4)) { + // passed++; + // } + // total++; + // if (test_encode_memcpy(mem, dst_off, src_off, count, "Comprehensive overlapping 4", 4)) { + // passed++; + // } + // total++; + // if (test_encode_memcpy(mem, dst_off, src_off, count, "Comprehensive overlapping 1 byte", count-1)) { + // passed++; + // } + // } + } + } + } + // Overlapping tests + total++; + if (test_overlapping_copy("Forward overlap (dst > src)", 8)) passed++; + + total++; + if (test_overlapping_copy("Backward overlap (dst < src)", -8)) passed++; + + total++; + if (test_overlapping_copy("No overlap (large gap)", 100)) passed++; + + // Fast memcpy tests + std::cout << "\n=== Fast Memcpy Tests ===\n"; + auto run_fast_test = [&](uint64_t dst_off, uint64_t src_off, size_t count, const char* desc) { + total++; + if (test_fast_memcpy(dst_off, src_off, count, desc)) { + passed++; + } + }; + + run_fast_test(0, 0, 0, "Zero count"); + run_fast_test(0, 0, 1, "Single byte, aligned"); + run_fast_test(0, 0, 8, "One qword, aligned"); + run_fast_test(0, 0, 16, "Two qwords, aligned"); + run_fast_test(1, 0, 7, "dst_offset=1, count=7"); + run_fast_test(7, 0, 1, "dst_offset=7, count=1"); + run_fast_test(3, 5, 10, "dst_offset=3, src_offset=5, count=10"); + run_fast_test(0, 0, 100, "Large aligned copy"); + run_fast_test(3, 5, 100, "Large unaligned copy"); + run_fast_test(1, 2, 1000, "Very large copy"); + + // Fast overlapping tests + total++; + if (test_fast_overlapping("Forward overlap (dst > src)", 8)) passed++; + + // Test with heap allocation + total++; + if (test_fast_overlapping_heap("Forward overlap (dst > src) HEAP", 8)) passed++; + + + total++; + if (test_fast_overlapping("Backward overlap (dst < src)", -8)) passed++; + + total++; + if (test_fast_overlapping("No overlap (large gap)", 100)) passed++; + + // Summary + std::cout << "\n=== Test Summary ===\n"; + std::cout << "Passed: " << passed << "/" << total << " tests\n"; + std::cout << "Success rate: " << (100.0 * passed / total) << "%\n"; + + if (passed == total) { + std::cout << "\n✅ All tests passed!\n"; + return 0; + } else { + std::cout << "\n❌ Some tests failed!\n"; + return 1; + } +} diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 6b02362dd..de7966959 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -46,7 +46,8 @@ uint64_t get_precompile_results(void); #define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) #define TRACE_ADDR (uint64_t)0xc0000000 -#define INITIAL_TRACE_SIZE (uint64_t)0x100000000 // 4GB +#define INITIAL_TRACE_SIZE (uint64_t)0x180000000 // 6GB +#define DELTA_TRACE_SIZE (uint64_t)0x080000000 // 2GB #define CONTROL_INPUT_ADDR (uint64_t)0x70000000 #define CONTROL_INPUT_SIZE (uint64_t)0x1000 // 4kB @@ -55,6 +56,8 @@ uint64_t get_precompile_results(void); #define CONTROL_RETRY_DELAY_US 1000 // 1ms #define CONTROL_NUMBER_OF_RETRIES 1000 // 1s max total +#define MAX_STEPS (1ULL << 36) + uint8_t * pInput = (uint8_t *)INPUT_ADDR; uint8_t * pInputLast = (uint8_t *)(INPUT_ADDR + 10440504 - 64); uint8_t * pRam = (uint8_t *)RAM_ADDR; @@ -204,10 +207,15 @@ uint64_t trace_address = TRACE_ADDR; uint64_t trace_size = INITIAL_TRACE_SIZE; uint64_t trace_used_size = 0; -// Worst case: every chunk instruction is a keccak operation, with an input data of 200 bytes -#define MAX_CHUNK_TRACE_SIZE (INITIAL_CHUNK_SIZE * 200) + (44 * 8) + 32 -uint64_t trace_address_threshold = TRACE_ADDR + INITIAL_TRACE_SIZE - MAX_CHUNK_TRACE_SIZE; +// Worst case: every chunk instruction is a keccak operation, with an input data of 256 bytes + +#define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) +#define MAX_TRACE_CHUNK_INFO ((44*8) + 32) +#define MAX_BYTES_DIRECT_MTRACE 256 +#define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) +#define MAX_CHUNK_TRACE_SIZE ((INITIAL_CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) +uint64_t trace_address_threshold = TRACE_ADDR + INITIAL_TRACE_SIZE - MAX_CHUNK_TRACE_SIZE; uint64_t print_pc_counter = 0; int map_locked_flag = MAP_LOCKED; @@ -230,7 +238,7 @@ void set_chunk_size (uint64_t new_chunk_size) } chunk_size = new_chunk_size; chunk_size_mask = chunk_size - 1; - trace_address_threshold = TRACE_ADDR + trace_size - ((chunk_size*200) + (44*8) + 32); + trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; } void set_trace_size (uint64_t new_trace_size) @@ -2537,7 +2545,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_MT_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = 0; request[4] = 0; @@ -2603,7 +2611,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_RH_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 0; request[3] = 0; request[4] = 0; @@ -2669,7 +2677,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_MO_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = 0; request[4] = 0; @@ -2735,7 +2743,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_MA_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = 0; request[4] = 0; @@ -2798,7 +2806,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_CM_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = chunk_player_address; request[4] = 0; @@ -2872,7 +2880,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_CM_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = chunk_player_address; request[4] = 0; @@ -2933,7 +2941,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_FA_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = 0; request[4] = 0; @@ -2994,7 +3002,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_MR_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = 0; request[4] = 0; @@ -3057,7 +3065,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_CA_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = chunk_player_address; request[4] = 0; @@ -3131,7 +3139,7 @@ void client_run (void) // Prepare message to send request[0] = TYPE_CA_REQUEST; - request[1] = 1ULL << 32; // max_steps + request[1] = MAX_STEPS; request[2] = 1ULL << 18; // chunk_len request[3] = chunk_player_address; request[4] = 0; @@ -4339,7 +4347,7 @@ extern void _realloc_trace (void) realloc_counter++; // Calculate new trace size - uint64_t new_trace_size = trace_size * 2; + uint64_t new_trace_size = trace_size + DELTA_TRACE_SIZE; // Extend the underlying file to the new size int result = ftruncate(shmem_output_fd, new_trace_size); @@ -5150,4 +5158,4 @@ int _wait_for_prec_avail (void) void post_prec_read (void) { sem_post(sem_prec_read); -} \ No newline at end of file +} diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml index 481b9b2a9..90c8015c7 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -55,6 +55,7 @@ harness = false [features] default = [] debug_stats_trace = [] +minimal_trace_index_debug = [] debug_call_stack = [] gpu = ["proofman-common/gpu", "packed"] packed = ["proofman-common/packed"] diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index dcdf07352..48fd4266c 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -320,7 +320,7 @@ impl<'a> Emu<'a> { 8, [self.ctx.inst_ctx.a, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { let (required_address_1, required_address_2) = Mem::required_addresses(address, 8); @@ -340,7 +340,7 @@ impl<'a> Emu<'a> { 8, [raw_data_1, raw_data_2], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } /*println!( "Emu::source_a_mem_reads_consume() mem_leads_index={} value={:x}", @@ -728,7 +728,7 @@ impl<'a> Emu<'a> { 8, [self.ctx.inst_ctx.b, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { let (required_address_1, required_address_2) = Mem::required_addresses(address, 8); @@ -745,7 +745,7 @@ impl<'a> Emu<'a> { 8, [raw_data, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { assert!(*mem_reads_index < mem_reads.len()); let raw_data_1 = mem_reads[*mem_reads_index]; @@ -762,7 +762,7 @@ impl<'a> Emu<'a> { 8, [raw_data_1, raw_data_2], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } } /*println!( @@ -797,8 +797,14 @@ impl<'a> Emu<'a> { 8, [self.ctx.inst_ctx.b, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { + if instruction.ind_width == 0 || address > 0xFFFF_FFFF { + println!( + "ILLEGAL INSTRUCTION/ADDRESS 0x{:08X} {} S:{} {:?}", + address, instruction.ind_width, self.ctx.inst_ctx.step, instruction + ); + } let (required_address_1, required_address_2) = Mem::required_addresses(address, instruction.ind_width); if required_address_1 == required_address_2 { @@ -817,7 +823,7 @@ impl<'a> Emu<'a> { instruction.ind_width as u8, [raw_data, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { assert!(*mem_reads_index < mem_reads.len()); let raw_data_1 = mem_reads[*mem_reads_index]; @@ -838,7 +844,7 @@ impl<'a> Emu<'a> { 8, [raw_data_1, raw_data_2], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } } /*println!( @@ -1015,7 +1021,7 @@ impl<'a> Emu<'a> { } STORE_IND => { // Calculate value - let val: i64 = if instruction.store_ra { + let val: i64 = if instruction.store_pc { self.ctx.inst_ctx.pc as i64 + instruction.jmp_offset2 } else { self.ctx.inst_ctx.c as i64 @@ -1237,7 +1243,7 @@ impl<'a> Emu<'a> { value, [value, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } // Otherwise, if not aligned, get old raw data from memory, then write it else { @@ -1256,7 +1262,7 @@ impl<'a> Emu<'a> { value, [raw_data, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { assert!(*mem_reads_index < mem_reads.len()); let raw_data_1 = mem_reads[*mem_reads_index]; @@ -1273,7 +1279,7 @@ impl<'a> Emu<'a> { value, [raw_data_1, raw_data_2], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } } } @@ -1300,7 +1306,7 @@ impl<'a> Emu<'a> { value, [value, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } // Otherwise, if not aligned, get old raw data from memory, then write it else { @@ -1319,7 +1325,7 @@ impl<'a> Emu<'a> { value, [raw_data, 0], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } else { assert!(*mem_reads_index < mem_reads.len()); let raw_data_1 = mem_reads[*mem_reads_index]; @@ -1336,7 +1342,7 @@ impl<'a> Emu<'a> { value, [raw_data_1, raw_data_2], ); - data_bus.write_to_bus(MEM_BUS_ID, &payload); + data_bus.write_to_bus(MEM_BUS_ID, &payload, &[]); } } } @@ -1563,7 +1569,7 @@ impl<'a> Emu<'a> { self.ctx.stats.set_store_ops(options.store_op_output.is_some()); // Check that callback is provided if chunk size is specified - if options.chunk_size.is_some() { + if let Some(chunk_size) = options.chunk_size { // Check callback consistency if callback.is_none() { panic!("Emu::run() called with chunk size but no callback"); @@ -1571,7 +1577,7 @@ impl<'a> Emu<'a> { // Record callback into context self.ctx.do_callback = true; - self.ctx.callback_steps = options.chunk_size.unwrap(); + self.ctx.callback_steps = chunk_size; // Check steps value if self.ctx.callback_steps == 0 { @@ -1624,6 +1630,10 @@ impl<'a> Emu<'a> { // While not done while !self.ctx.inst_ctx.end { + // println!( + // "DEBUG_TRACE {:09} 0x{:08x} {:?}", + // self.ctx.inst_ctx.step, self.ctx.inst_ctx.pc, self.ctx.inst_ctx.regs + // ); if options.verbose { println!( "Emu::run() step={} ctx.pc={}", @@ -1823,13 +1833,7 @@ impl<'a> Emu<'a> { let pc = self.ctx.inst_ctx.pc; let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); - // println!( - // "Emu::step() executing step={} pc={:x} inst={}", - // self.ctx.inst_ctx.step, - // self.ctx.inst_ctx.pc, - // instruction.to_text() - // ); - + let pc = self.ctx.inst_ctx.pc; //println!("PCLOG={}", instruction.to_text()); // Build the 'a' register value based on the source specified by the current instruction @@ -1943,6 +1947,14 @@ impl<'a> Emu<'a> { #[inline(always)] pub fn par_step_my_block(&mut self, emu_full_trace_vec: &mut EmuTrace) { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + + #[cfg(feature = "minimal_trace_index_debug")] + println!( + "MINIMAL_TRACE par_step_my_block {} {}", + self.ctx.inst_ctx.step, + emu_full_trace_vec.mem_reads.len() + ); + // Build the 'a' register value based on the source specified by the current instruction self.source_a_mem_reads_generate(instruction, &mut emu_full_trace_vec.mem_reads); @@ -1959,7 +1971,22 @@ impl<'a> Emu<'a> { (instruction.func)(&mut self.ctx.inst_ctx); // If this is a precompiled, copy input data generated by precompile call to mem_reads. + // when generate mem traces the input data containts also data_ext. if instruction.input_size > 0 { + #[cfg(feature = "minimal_trace_index_debug")] + { + let input_data_bytes = self.ctx.inst_ctx.precompiled.input_data.len() * 8; + if input_data_bytes > instruction.input_size as usize { + println!( + "MINIMAL_TRACE data_ext_len:{} input_data:{} input_size:{} mem_reads[{}..{}]", + input_data_bytes - instruction.input_size as usize, + input_data_bytes, + instruction.input_size, + emu_full_trace_vec.mem_reads.len(), + emu_full_trace_vec.mem_reads.len() + (input_data_bytes >> 3) + ); + } + } emu_full_trace_vec.mem_reads.append(&mut self.ctx.inst_ctx.precompiled.input_data); } @@ -2025,6 +2052,16 @@ impl<'a> Emu<'a> { ) -> bool { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + #[cfg(feature = "minimal_trace_index_debug")] + println!( + "MINIMAL_TRACE step_emu_trace {} {}", + self.ctx.inst_ctx.step, mem_reads_index + ); + // println!( + // "DEBUG_TRACE {:09} 0x{:08x} {:?}", + // self.ctx.inst_ctx.step, self.ctx.inst_ctx.pc, self.ctx.inst_ctx.regs + // ); + self.source_a_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); self.source_b_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); // If this is a precompiled, get the required input data from mem_reads @@ -2041,6 +2078,7 @@ impl<'a> Emu<'a> { } } + self.ctx.inst_ctx.data_ext_len = 0; (instruction.func)(&mut self.ctx.inst_ctx); self.store_c_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); @@ -2054,7 +2092,22 @@ impl<'a> Emu<'a> { &self.ctx.inst_ctx, &mut self.static_array, ); - data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload); + if self.ctx.inst_ctx.data_ext_len > 0 { + if mem_reads.len() < *mem_reads_index + self.ctx.inst_ctx.data_ext_len { + println!( + "OUT_OF_DATA_EXT({}) S:{}", + self.ctx.inst_ctx.data_ext_len, self.ctx.inst_ctx.step + ); + } + data_bus.write_to_bus( + OPERATION_BUS_ID, + operation_payload, + &mem_reads[*mem_reads_index..*mem_reads_index + self.ctx.inst_ctx.data_ext_len], + ); + *mem_reads_index += self.ctx.inst_ctx.data_ext_len; + } else { + data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); + } } // #[cfg(feature = "sp")] @@ -2078,6 +2131,12 @@ impl<'a> Emu<'a> { ) -> bool { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + #[cfg(feature = "minimal_trace_index_debug")] + println!( + "MINIMAL_TRACE step_emu_trace_no_mem_ops {} {}", + self.ctx.inst_ctx.step, mem_reads_index + ); + self.source_a_mem_reads_consume_no_mem_ops(instruction, mem_reads, mem_reads_index); self.source_b_mem_reads_consume_no_mem_ops(instruction, mem_reads, mem_reads_index); // If this is a precompiled, get the required input data from mem_reads @@ -2094,6 +2153,7 @@ impl<'a> Emu<'a> { } } + self.ctx.inst_ctx.data_ext_len = 0; (instruction.func)(&mut self.ctx.inst_ctx); self.store_c_mem_reads_consume_no_mem_ops(instruction, mem_reads, mem_reads_index); @@ -2107,7 +2167,16 @@ impl<'a> Emu<'a> { &self.ctx.inst_ctx, &mut self.static_array, ); - data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload); + if self.ctx.inst_ctx.data_ext_len > 0 { + data_bus.write_to_bus( + OPERATION_BUS_ID, + operation_payload, + &mem_reads[*mem_reads_index..*mem_reads_index + self.ctx.inst_ctx.data_ext_len], + ); + *mem_reads_index += self.ctx.inst_ctx.data_ext_len; + } else { + data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]); + } } // #[cfg(feature = "sp")] @@ -2201,6 +2270,13 @@ impl<'a> Emu<'a> { ) -> bool { let mut _continue = true; let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + + #[cfg(feature = "minimal_trace_index_debug")] + println!( + "MINIMAL_TRACE step_emu_traces {} {} 0x{:08x}", + self.ctx.inst_ctx.step, mem_reads_index, self.ctx.inst_ctx.pc + ); + self.source_a_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); self.source_b_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); // If this is a precompiled, get the required input data from mem_reads @@ -2214,6 +2290,8 @@ impl<'a> Emu<'a> { self.ctx.inst_ctx.precompiled.input_data.push(mem_read); } } + + self.ctx.inst_ctx.data_ext_len = 0; (instruction.func)(&mut self.ctx.inst_ctx); self.store_c_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); @@ -2226,14 +2304,24 @@ impl<'a> Emu<'a> { &self.ctx.inst_ctx, &mut self.static_array, ); - _continue = data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload); + _continue = if self.ctx.inst_ctx.data_ext_len > 0 { + let data_ext_index = *mem_reads_index; + *mem_reads_index += self.ctx.inst_ctx.data_ext_len; + data_bus.write_to_bus( + OPERATION_BUS_ID, + operation_payload, + &mem_reads[data_ext_index..*mem_reads_index], + ) + } else { + data_bus.write_to_bus(OPERATION_BUS_ID, operation_payload, &[]) + } } // Get rom bus data let rom_payload = RomBusData::from_instruction(instruction, &self.ctx.inst_ctx); // Write rom bus data to rom bus - data_bus.write_to_bus(ROM_BUS_ID, &rom_payload); + data_bus.write_to_bus(ROM_BUS_ID, &rom_payload, &[]); // #[cfg(feature = "sp")] // self.set_sp(instruction); @@ -2245,6 +2333,18 @@ impl<'a> Emu<'a> { _continue } + #[allow(dead_code)] + fn get_slice_from_mem_reads<'b>( + &mut self, + mem_reads: &'b [u64], + mem_reads_index: &mut usize, + len: usize, + ) -> &'b [u64] { + let slice = &mem_reads[*mem_reads_index..*mem_reads_index + len]; + *mem_reads_index += len; + slice + } + /// Performs one single step of the emulation #[inline(always)] pub fn step_slice_full_trace( @@ -2259,6 +2359,12 @@ impl<'a> Emu<'a> { } let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + #[cfg(feature = "minimal_trace_index_debug")] + println!( + "MINIMAL_TRACE step_slice_full_trace {} {} 0x{:08x}", + self.ctx.inst_ctx.step, mem_reads_index, self.ctx.inst_ctx.pc + ); + reg_trace.clear_reg_step_ranges(); self.source_a_mem_reads_consume(instruction, mem_reads, mem_reads_index, reg_trace); @@ -2276,6 +2382,7 @@ impl<'a> Emu<'a> { } } + self.ctx.inst_ctx.data_ext_len = 0; (instruction.func)(&mut self.ctx.inst_ctx); self.store_c_mem_reads_consume(instruction, mem_reads, mem_reads_index, reg_trace); @@ -2292,6 +2399,7 @@ impl<'a> Emu<'a> { let full_trace_step = Self::build_full_trace_step(instruction, &self.ctx.inst_ctx, reg_trace); + *mem_reads_index += self.ctx.inst_ctx.data_ext_len; self.ctx.inst_ctx.step += 1; full_trace_step @@ -2372,7 +2480,7 @@ impl<'a> Emu<'a> { // trace.set_a_src_sp(inst.a_src == SRC_SP), // #[cfg(feature = "sp")] // trace.set_a_use_sp_imm1(inst.a_use_sp_imm1), - trace.set_a_src_step(inst.a_src == SRC_STEP); + trace.set_op_with_step(inst.op_with_step); trace.set_b_src_imm(inst.b_src == SRC_IMM); trace.set_b_src_mem(inst.b_src == SRC_MEM); trace.set_b_src_reg(inst.b_src == SRC_REG); @@ -2396,7 +2504,7 @@ impl<'a> Emu<'a> { inst.op }, ); - trace.set_store_ra(inst.store_ra); + trace.set_store_pc(inst.store_pc); trace.set_store_mem(inst.store == STORE_MEM); trace.set_store_reg(inst.store == STORE_REG); trace.set_store_ind(inst.store == STORE_IND); @@ -2520,7 +2628,7 @@ impl<'a> Emu<'a> { #[inline(always)] pub fn get_value_to_store(&self, instruction: &ZiskInst) -> u64 { - if instruction.store_ra { + if instruction.store_pc { (self.ctx.inst_ctx.pc as i64 + instruction.jmp_offset2) as u64 } else { self.ctx.inst_ctx.c diff --git a/emulator/src/emu_context.rs b/emulator/src/emu_context.rs index d5487768d..8bb7dacdb 100644 --- a/emulator/src/emu_context.rs +++ b/emulator/src/emu_context.rs @@ -1,8 +1,7 @@ use crate::Stats; use zisk_common::EmuTrace; use zisk_core::{ - EmulationMode, FcallInstContext, InstContext, Mem, PrecompiledInstContext, INPUT_ADDR, - MAX_INPUT_SIZE, RAM_ADDR, RAM_SIZE, REGS_IN_MAIN_TOTAL_NUMBER, ROM_ENTRY, + InstContext, INPUT_ADDR, MAX_INPUT_SIZE, RAM_ADDR, RAM_SIZE, REGS_IN_MAIN_TOTAL_NUMBER, }; /// ZisK emulator context data container, storing the state of the emulation @@ -26,22 +25,7 @@ impl EmuContext { /// RisK emulator context constructor pub fn new(input: Vec) -> EmuContext { let mut ctx = EmuContext { - inst_ctx: InstContext { - mem: Mem::default(), - a: 0, - b: 0, - c: 0, - flag: false, - sp: 0, - pc: ROM_ENTRY, - step: 0, - end: false, - error: false, - regs: [0; REGS_IN_MAIN_TOTAL_NUMBER], - emulation_mode: EmulationMode::default(), - precompiled: PrecompiledInstContext::default(), - fcall: FcallInstContext::default(), - }, + inst_ctx: InstContext::default(), tracerv: Vec::new(), tracerv_step: 0, tracerv_current_regs: [0; REGS_IN_MAIN_TOTAL_NUMBER], diff --git a/emulator/src/emu_options.rs b/emulator/src/emu_options.rs index 1d13b1eed..ab1056ef4 100644 --- a/emulator/src/emu_options.rs +++ b/emulator/src/emu_options.rs @@ -2,7 +2,7 @@ use clap::Parser; use std::fmt; -use zisk_core::DEFAULT_MAX_STEPS_STR; +use zisk_core::{DEFAULT_MAX_STEPS, DEFAULT_MAX_STEPS_STR}; pub const ZISK_VERSION_MESSAGE: &str = concat!( env!("CARGO_PKG_VERSION"), @@ -103,7 +103,7 @@ impl Default for EmuOptions { elf: None, inputs: None, output: None, - max_steps: 0xFFFFFFFFFFFFFFFF, + max_steps: DEFAULT_MAX_STEPS, print_step: None, trace: None, verbose: false, diff --git a/emulator/src/emulator.rs b/emulator/src/emulator.rs index 0369af1e3..c8ad2c86b 100644 --- a/emulator/src/emulator.rs +++ b/emulator/src/emulator.rs @@ -153,9 +153,8 @@ impl ZiskEmulator { // OUTPUT: // Save output to a file if requested - if options.output.is_some() { - fs::write(options.output.as_ref().unwrap(), &output) - .map_err(|e| ZiskEmulatorErr::Unknown(e.to_string()))? + if let Some(output_path) = &options.output { + fs::write(output_path, &output).map_err(|e| ZiskEmulatorErr::Unknown(e.to_string()))? } // Log output to console if requested diff --git a/emulator/src/regions_of_interest.rs b/emulator/src/regions_of_interest.rs index f1a60f941..f63b18eb5 100644 --- a/emulator/src/regions_of_interest.rs +++ b/emulator/src/regions_of_interest.rs @@ -42,11 +42,10 @@ impl RegionsOfInterest { self.call_stack_rc += 1; } pub fn update_call_depth(&mut self, call_stack_depth: usize) { - if self.call_stack_depth.is_none() { - self.call_stack_depth = Some(call_stack_depth); + if let Some(depth) = self.call_stack_depth { + self.call_stack_depth = Some(std::cmp::min(depth, call_stack_depth)); } else { - self.call_stack_depth = - Some(std::cmp::min(self.call_stack_depth.unwrap(), call_stack_depth)); + self.call_stack_depth = Some(call_stack_depth); } } pub fn call(&mut self, caller: Option, call_stack_depth: usize) { diff --git a/emulator/src/stats.rs b/emulator/src/stats.rs index 1b340f54b..7e775eff0 100644 --- a/emulator/src/stats.rs +++ b/emulator/src/stats.rs @@ -493,12 +493,12 @@ impl Stats { if is_jmp { // CALL: set_pc=true, store_ra=true, store_offset=1 (stores PC+4 or PC+2 in ra) // self.is_call = instruction.store_ra && instruction.store_offset == 1; - self.is_call = instruction.store_ra; + self.is_call = instruction.store_pc; self.call_return_reg = if self.is_call { instruction.store_offset as u8 } else { 0 }; - // RETURN: set_pc=true, store_ra=false (no stores RA), b_src=SRC_REG, b_offset_imm0=1 (jumps to ra/x1) + // RETURN: set_pc=true, store_pc=false (no stores RA), b_src=SRC_REG, b_offset_imm0=1 (jumps to ra/x1) // Additionally, verify that the target PC matches the expected return address from the call stack - let is_jalr_ra = !instruction.store_ra + let is_jalr_ra = !instruction.store_pc && instruction.set_pc && instruction.b_src == SRC_REG && instruction.b_offset_imm0 == 1; @@ -513,7 +513,7 @@ impl Stats { self.is_return = false; } } else if let Some(top) = self.call_stack.last() { - self.is_return = !instruction.store_ra + self.is_return = !instruction.store_pc && instruction.b_src == SRC_REG && instruction.b_offset_imm0 == top.return_reg as u64; } else { @@ -920,4 +920,9 @@ impl OpStats for Stats { self.on_memory_write(addr + 8 * index as u64, 8, 0); } } + fn add_extras(&mut self, extras: &[(u8, usize)]) { + for (opcode, count) in extras { + self.costs.ops[*opcode as usize] += *count as u64; + } + } } diff --git a/executor/Cargo.toml b/executor/Cargo.toml index 88e686425..d4cc55f6f 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -33,12 +33,14 @@ precompiles-hints = { workspace = true } anyhow = { workspace = true } crossbeam = "0.8.4" +precompiles-common = { workspace = true } precomp-keccakf = { workspace = true } precomp-sha256f = { workspace = true } precomp-poseidon2 = { workspace = true } precomp-arith-eq = { workspace = true } precomp-arith-eq-384 = { workspace = true } precomp-big-int = { workspace = true } +precomp-dma = { workspace = true } sm-arith = { workspace = true } sm-binary = { workspace = true } diff --git a/executor/src/dummy_counter.rs b/executor/src/dummy_counter.rs index ea3c3400b..4be2099a5 100644 --- a/executor/src/dummy_counter.rs +++ b/executor/src/dummy_counter.rs @@ -3,9 +3,9 @@ //! This counter is used as a default implementation when no actual counting or metrics //! collection is required. -use std::{any::Any, collections::VecDeque}; +use std::any::Any; -use zisk_common::{BusDevice, BusId, MemCollectorInfo, Metrics}; +use zisk_common::{BusDevice, Metrics}; /// The `DummyCounter` struct serves as a placeholder counter that performs no actions /// when connected to the data bus. @@ -15,6 +15,12 @@ use zisk_common::{BusDevice, BusId, MemCollectorInfo, Metrics}; #[derive(Default)] pub struct DummyCounter {} +impl DummyCounter { + #[inline(always)] + pub fn process_data(&mut self) -> bool { + true + } +} impl Metrics for DummyCounter { /// Does nothing when tracking activity on the bus. /// @@ -36,25 +42,6 @@ impl Metrics for DummyCounter { } impl BusDevice for DummyCounter { - #[inline(always)] - fn process_data( - &mut self, - _bus_id: &BusId, - _data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - true - } - - /// Returns an empty vector as this counter is not associated with any bus IDs. - /// - /// # Returns - /// An empty vector of bus IDs. - fn bus_id(&self) -> Vec { - vec![] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 070c6efbf..e27a2a594 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -68,7 +68,7 @@ enum MinimalTraceExecutionMode { } /// The maximum number of steps to execute in the emulator or assembly runner. -pub const MAX_NUM_STEPS: u64 = 1 << 32; +pub const MAX_NUM_STEPS: u64 = 1 << 36; /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. @@ -380,7 +380,12 @@ impl ZiskExecutor { .remove(&global_id) .expect("Missing collectors for given global_id") .into_iter() - .map(Option::unwrap) // All are guaranteed to be Some + .enumerate() + .map(|(idx, opt)| { + opt.unwrap_or_else(|| { + panic!("Collector at index {} for global_id {} is None", idx, global_id) + }) + }) .collect() }; diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index adcf0f123..8c92a5257 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -7,6 +7,11 @@ use precomp_arith_eq::{ArithEqInstance, ArithEqManager}; use precomp_arith_eq_384::ArithEq384Instance; use precomp_arith_eq_384::ArithEq384Manager; use precomp_big_int::{Add256Instance, Add256Manager}; +use precomp_dma::Dma64AlignedInstance; +use precomp_dma::DmaInstance; +use precomp_dma::DmaManager; +use precomp_dma::DmaPrePostInstance; +use precomp_dma::DmaUnalignedInstance; use precomp_keccakf::{KeccakfInstance, KeccakfManager}; use precomp_poseidon2::{Poseidon2Instance, Poseidon2Manager}; use precomp_sha256f::{Sha256fInstance, Sha256fManager}; @@ -21,6 +26,10 @@ use sm_rom::{RomInstance, RomSM}; use std::collections::{BTreeMap, HashMap}; use zisk_common::{BusDeviceMetrics, ChunkId, ComponentBuilder, Instance, InstanceCtx, Plan}; use zisk_pil::ADD_256_AIR_IDS; +use zisk_pil::DMA_64_ALIGNED_AIR_IDS; +use zisk_pil::DMA_AIR_IDS; +use zisk_pil::DMA_PRE_POST_AIR_IDS; +use zisk_pil::DMA_UNALIGNED_AIR_IDS; use zisk_pil::{ ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, MEM_ALIGN_AIR_IDS, @@ -45,6 +54,7 @@ pub enum StateMachines { ArithEqManager(Arc>), ArithEq384Manager(Arc>), Add256Manager(Arc>), + DmaManager(Arc>), } impl StateMachines { @@ -60,6 +70,7 @@ impl StateMachines { StateMachines::ArithEqManager(_) => 7, StateMachines::ArithEq384Manager(_) => 8, StateMachines::Add256Manager(_) => 9, + StateMachines::DmaManager(_) => 10, } } @@ -81,6 +92,7 @@ impl StateMachines { StateMachines::ArithEqManager(sm) => (**sm).build_planner(), StateMachines::ArithEq384Manager(sm) => (**sm).build_planner(), StateMachines::Add256Manager(sm) => (**sm).build_planner(), + StateMachines::DmaManager(sm) => (**sm).build_planner(), } } @@ -98,6 +110,7 @@ impl StateMachines { StateMachines::ArithEqManager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::ArithEq384Manager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::Add256Manager(sm) => (**sm).configure_instances(pctx, plans), + StateMachines::DmaManager(sm) => (**sm).configure_instances(pctx, plans), } } @@ -113,6 +126,7 @@ impl StateMachines { StateMachines::ArithEqManager(sm) => (**sm).build_instance(ictx), StateMachines::ArithEq384Manager(sm) => (**sm).build_instance(ictx), StateMachines::Add256Manager(sm) => (**sm).build_instance(ictx), + StateMachines::DmaManager(sm) => (**sm).build_instance(ictx), } } } @@ -193,6 +207,7 @@ impl StaticSMBundle { let mut arith_eq_counter = None; let mut arith_eq_384_counter = None; let mut add256_counter = None; + let mut dma_counter = None; for (_, sm) in self.sm.values() { match sm { @@ -210,24 +225,46 @@ impl StaticSMBundle { arith_counter = Some((sm.type_id(), arith_sm.build_arith_counter())); } StateMachines::KeccakfManager(keccak_sm) => { - keccakf_counter = Some((sm.type_id(), keccak_sm.build_keccakf_counter())); + keccakf_counter = Some(( + sm.type_id(), + keccak_sm.build_keccakf_counter(self.process_only_operation_bus), + )); } StateMachines::Sha256fManager(sha256_sm) => { - sha256f_counter = Some((sm.type_id(), sha256_sm.build_sha256f_counter())); + sha256f_counter = Some(( + sm.type_id(), + sha256_sm.build_sha256f_counter(self.process_only_operation_bus), + )); } StateMachines::Poseidon2Manager(poseidon2_sm) => { - poseidon2_counter = - Some((sm.type_id(), poseidon2_sm.build_poseidon2_counter())); + poseidon2_counter = Some(( + sm.type_id(), + poseidon2_sm.build_poseidon2_counter(self.process_only_operation_bus), + )); } StateMachines::ArithEqManager(arith_eq_sm) => { - arith_eq_counter = Some((sm.type_id(), arith_eq_sm.build_arith_eq_counter())); + arith_eq_counter = Some(( + sm.type_id(), + arith_eq_sm.build_arith_eq_counter(self.process_only_operation_bus), + )); } StateMachines::ArithEq384Manager(arith_eq_384_sm) => { - arith_eq_384_counter = - Some((sm.type_id(), arith_eq_384_sm.build_arith_eq_384_counter())); + arith_eq_384_counter = Some(( + sm.type_id(), + arith_eq_384_sm.build_arith_eq_384_counter(self.process_only_operation_bus), + )); } StateMachines::Add256Manager(add256_sm) => { - add256_counter = Some((sm.type_id(), add256_sm.build_add256_counter())); + add256_counter = Some(( + sm.type_id(), + add256_sm.build_add256_counter(self.process_only_operation_bus), + )); + } + StateMachines::DmaManager(dma_sm) => { + dma_counter = Some(( + sm.type_id(), + dma_sm.build_dma_counter(self.process_only_operation_bus), + )); } StateMachines::RomSM(_) => {} } @@ -244,6 +281,7 @@ impl StaticSMBundle { arith_eq_counter.expect("ArithEq counter not found"), arith_eq_384_counter.expect("ArithEq384 counter not found"), add256_counter.expect("Add256 counter not found"), + dma_counter.expect("Dma counter not found"), Some(0), ) } @@ -276,6 +314,10 @@ impl StaticSMBundle { let mut arith_eq_384_collectors = Vec::new(); let mut add256_collectors = Vec::new(); let mut rom_collectors = Vec::new(); + let mut dma_collectors = Vec::new(); + let mut dma_pre_post_collectors = Vec::new(); + let mut dma_64_aligned_collectors = Vec::new(); + let mut dma_unaligned_collectors = Vec::new(); for global_idx in global_idxs { let secn_instance = secn_instances.get(global_idx).unwrap(); @@ -420,6 +462,40 @@ impl StaticSMBundle { add256_instance.build_add256_collector(ChunkId(chunk_id)); add256_collectors.push((*global_idx, add256_collector)); } + // DMA AIRS + air_id if air_id == DMA_AIR_IDS[0] => { + let dma_instance = + secn_instance.as_any().downcast_ref::>().unwrap(); + let dma_collector = dma_instance.build_dma_collector(ChunkId(chunk_id)); + dma_collectors.push((*global_idx, dma_collector)); + } + air_id if air_id == DMA_PRE_POST_AIR_IDS[0] => { + let dma_pre_post_instance = secn_instance + .as_any() + .downcast_ref::>() + .unwrap(); + let dma_pre_post_collector = + dma_pre_post_instance.build_dma_collector(ChunkId(chunk_id)); + dma_pre_post_collectors.push((*global_idx, dma_pre_post_collector)); + } + air_id if air_id == DMA_64_ALIGNED_AIR_IDS[0] => { + let dma_64_aligned_instance = secn_instance + .as_any() + .downcast_ref::>() + .unwrap(); + let dma_64_aligned_collector = + dma_64_aligned_instance.build_dma_collector(ChunkId(chunk_id)); + dma_64_aligned_collectors.push((*global_idx, dma_64_aligned_collector)); + } + air_id if air_id == DMA_UNALIGNED_AIR_IDS[0] => { + let dma_unaligned_instance = secn_instance + .as_any() + .downcast_ref::>() + .unwrap(); + let dma_unaligned_collector = + dma_unaligned_instance.build_dma_collector(ChunkId(chunk_id)); + dma_unaligned_collectors.push((*global_idx, dma_unaligned_collector)); + } air_id if air_id == ROM_AIR_IDS[0] => { let rom_instance = secn_instance.as_any().downcast_ref::().unwrap(); @@ -441,6 +517,7 @@ impl StaticSMBundle { let mut poseidon2_inputs_generator = None; let mut arith_inputs_generator = None; let mut add256_inputs_generator = None; + let mut dma_inputs_generator = None; for (_, sm) in self.sm.values() { match sm { StateMachines::ArithSM(arith_sm) => { @@ -470,6 +547,9 @@ impl StaticSMBundle { add256_inputs_generator = Some(add256_sm.build_add256_input_generator()); } + StateMachines::DmaManager(dma_sm) => { + dma_inputs_generator = Some(dma_sm.build_dma_input_generator()); + } _ => {} } } @@ -487,6 +567,10 @@ impl StaticSMBundle { arith_eq_collectors, arith_eq_384_collectors, add256_collectors, + dma_collectors, + dma_pre_post_collectors, + dma_64_aligned_collectors, + dma_unaligned_collectors, rom_collectors, arith_eq_inputs_generator.expect("ArithEq input generator not found"), arith_eq_384_inputs_generator.expect("ArithEq384 input generator not found"), @@ -495,6 +579,7 @@ impl StaticSMBundle { poseidon2_inputs_generator.expect("Poseidon2 input generator not found"), arith_inputs_generator.expect("Arith input generator not found"), add256_inputs_generator.expect("Add256 input generator not found"), + dma_inputs_generator.expect("Dma input generator not found"), ); Some(data_bus) diff --git a/executor/src/static_data_bus.rs b/executor/src/static_data_bus.rs index e55dde6eb..a1a6e4468 100644 --- a/executor/src/static_data_bus.rs +++ b/executor/src/static_data_bus.rs @@ -10,17 +10,19 @@ use mem_common::MemCounters; use precomp_arith_eq::ArithEqCounterInputGen; use precomp_arith_eq_384::ArithEq384CounterInputGen; use precomp_big_int::Add256CounterInputGen; +use precomp_dma::DmaCounterInputGen; use precomp_keccakf::KeccakfCounterInputGen; use precomp_poseidon2::Poseidon2CounterInputGen; use precomp_sha256f::Sha256fCounterInputGen; +use precompiles_common::MemCounterProcessor; use sm_arith::ArithCounterInputGen; use sm_binary::BinaryCounter; use sm_main::MainCounter; -use zisk_common::{BusDevice, BusDeviceMetrics, BusId, PayloadType, MEM_BUS_ID, OPERATION_BUS_ID}; +use zisk_common::{BusDeviceMetrics, BusId, PayloadType, MEM_BUS_ID, OPERATION_BUS_ID}; use zisk_core::{ ARITH_EQ_384_OP_TYPE_ID, ARITH_EQ_OP_TYPE_ID, ARITH_OP_TYPE_ID, BIG_INT_OP_TYPE_ID, - BINARY_E_OP_TYPE_ID, BINARY_OP_TYPE_ID, KECCAK_OP_TYPE_ID, POSEIDON2_OP_TYPE_ID, - PUB_OUT_OP_TYPE_ID, SHA256_OP_TYPE_ID, + BINARY_E_OP_TYPE_ID, BINARY_OP_TYPE_ID, DMA_OP_TYPE_ID, KECCAK_OP_TYPE_ID, + POSEIDON2_OP_TYPE_ID, PUB_OUT_OP_TYPE_ID, SHA256_OP_TYPE_ID, }; /// A bus system facilitating communication between multiple publishers and subscribers. @@ -47,9 +49,10 @@ pub struct StaticDataBus { pub arith_eq_counter: (usize, ArithEqCounterInputGen), pub arith_eq_384_counter: (usize, ArithEq384CounterInputGen), pub add_256_counter: (usize, Add256CounterInputGen), + pub dma_counter: (usize, DmaCounterInputGen), pub rom_counter_id: Option, /// Queue of pending data transfers to be processed. - pending_transfers: VecDeque<(BusId, Vec)>, + pending_transfers: VecDeque<(BusId, Vec, Vec)>, } impl StaticDataBus { @@ -66,6 +69,7 @@ impl StaticDataBus { arith_eq_counter: (usize, ArithEqCounterInputGen), arith_eq_384_counter: (usize, ArithEq384CounterInputGen), add_256_counter: (usize, Add256CounterInputGen), + dma_counter: (usize, DmaCounterInputGen), rom_counter_id: Option, ) -> Self { Self { @@ -80,6 +84,7 @@ impl StaticDataBus { arith_eq_counter, arith_eq_384_counter, add_256_counter, + dma_counter, rom_counter_id, pending_transfers: VecDeque::new(), } @@ -96,77 +101,66 @@ impl StaticDataBus { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn route_data(&mut self, bus_id: BusId, payload: &[PayloadType]) -> bool { + fn route_data( + &mut self, + bus_id: BusId, + data: &[PayloadType], + data_ext: &[PayloadType], + ) -> bool { match bus_id { MEM_BUS_ID => { let mut _continue = true; if !self.process_only_operation_bus { if let Some(mem_counter) = self.mem_counter.1.as_mut() { // If we are not processing only operation bus, we process memory bus data. - _continue &= mem_counter.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); + _continue &= mem_counter.process_data(&bus_id, data); } } _continue } - OPERATION_BUS_ID => match payload[1] as u32 { - PUB_OUT_OP_TYPE_ID => self.main_counter.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ), - BINARY_OP_TYPE_ID | BINARY_E_OP_TYPE_ID => self.binary_counter.1.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ), - ARITH_OP_TYPE_ID => self.arith_counter.1.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ), + OPERATION_BUS_ID => match data[1] as u32 { + PUB_OUT_OP_TYPE_ID => self.main_counter.process_data(&bus_id, data), + BINARY_OP_TYPE_ID | BINARY_E_OP_TYPE_ID => { + self.binary_counter.1.process_data(&bus_id, data) + } + ARITH_OP_TYPE_ID => { + self.arith_counter.1.process_data(&bus_id, data, &mut self.pending_transfers) + } KECCAK_OP_TYPE_ID => self.keccakf_counter.1.process_data( &bus_id, - payload, - &mut self.pending_transfers, - None, + data, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), SHA256_OP_TYPE_ID => self.sha256f_counter.1.process_data( &bus_id, - payload, - &mut self.pending_transfers, - None, + data, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), POSEIDON2_OP_TYPE_ID => self.poseidon2_counter.1.process_data( &bus_id, - payload, - &mut self.pending_transfers, - None, + data, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), ARITH_EQ_OP_TYPE_ID => self.arith_eq_counter.1.process_data( &bus_id, - payload, - &mut self.pending_transfers, - None, + data, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), ARITH_EQ_384_OP_TYPE_ID => self.arith_eq_384_counter.1.process_data( &bus_id, - payload, - &mut self.pending_transfers, - None, + data, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), BIG_INT_OP_TYPE_ID => self.add_256_counter.1.process_data( &bus_id, - payload, - &mut self.pending_transfers, - None, + data, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), + ), + DMA_OP_TYPE_ID => self.dma_counter.1.process_data( + &bus_id, + data, + data_ext, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), _ => true, }, @@ -177,29 +171,25 @@ impl StaticDataBus { impl DataBusTrait> for StaticDataBus { #[inline(always)] - fn write_to_bus(&mut self, bus_id: BusId, payload: &[PayloadType]) -> bool { - let mut _continue = self.route_data(bus_id, payload); + fn write_to_bus( + &mut self, + bus_id: BusId, + data: &[PayloadType], + data_ext: &[PayloadType], + ) -> bool { + let mut _continue = self.route_data(bus_id, data, data_ext); - while let Some((bus_id, payload)) = self.pending_transfers.pop_front() { - _continue &= self.route_data(bus_id, &payload); + while let Some((bus_id, data, data_ext)) = self.pending_transfers.pop_front() { + _continue &= self.route_data(bus_id, &data, &data_ext); } _continue } fn on_close(&mut self) { - self.main_counter.on_close(); if let Some(mem_counter) = self.mem_counter.1.as_mut() { - mem_counter.on_close(); + mem_counter.close(); } - self.binary_counter.1.on_close(); - self.arith_counter.1.on_close(); - self.keccakf_counter.1.on_close(); - self.sha256f_counter.1.on_close(); - self.poseidon2_counter.1.on_close(); - self.arith_eq_counter.1.on_close(); - self.arith_eq_384_counter.1.on_close(); - self.add_256_counter.1.on_close(); } fn into_devices( @@ -222,6 +212,7 @@ impl DataBusTrait> for StaticDataBus { pub add256_collector: Vec<(usize, Add256Collector)>, pub add256_inputs_generator: Add256CounterInputGen, + /// Dma collectors + pub dma_collector: Vec<(usize, DmaCollector)>, + pub dma_pre_post_collector: Vec<(usize, DmaPrePostCollector)>, + pub dma_64_aligned_collector: Vec<(usize, Dma64AlignedCollector)>, + pub dma_unaligned_collector: Vec<(usize, DmaUnalignedCollector)>, + pub dma_inputs_generator: DmaCounterInputGen, + /// ROM collector pub rom_collector: Vec<(usize, RomCollector)>, /// Queue of pending data transfers to be processed. - pending_transfers: VecDeque<(BusId, Vec)>, - - mem_collectors_info: Vec, + pending_transfers: VecDeque<(BusId, Vec, Vec)>, } const BINARY_TYPE: u64 = ZiskOperationType::Binary as u64; @@ -90,6 +100,7 @@ const POSEIDON2_TYPE: u64 = ZiskOperationType::Poseidon2 as u64; const ARITH_EQ_TYPE: u64 = ZiskOperationType::ArithEq as u64; const ARITH_EQ_384_TYPE: u64 = ZiskOperationType::ArithEq384 as u64; const BIG_INT_OP_TYPE_ID: u64 = ZiskOperationType::BigInt as u64; +const DMA_OP_TYPE_ID: u64 = ZiskOperationType::Dma as u64; impl StaticDataBusCollect { /// Creates a new `DataBus` instance. @@ -107,6 +118,10 @@ impl StaticDataBusCollect { arith_eq_collector: Vec<(usize, ArithEqCollector)>, arith_eq_384_collector: Vec<(usize, ArithEq384Collector)>, add256_collector: Vec<(usize, Add256Collector)>, + dma_collector: Vec<(usize, DmaCollector)>, + dma_pre_post_collector: Vec<(usize, DmaPrePostCollector)>, + dma_64_aligned_collector: Vec<(usize, Dma64AlignedCollector)>, + dma_unaligned_collector: Vec<(usize, DmaUnalignedCollector)>, rom_collector: Vec<(usize, RomCollector)>, arith_eq_inputs_generator: ArithEqCounterInputGen, arith_eq_384_inputs_generator: ArithEq384CounterInputGen, @@ -115,10 +130,8 @@ impl StaticDataBusCollect { poseidon2_inputs_generator: Poseidon2CounterInputGen, arith_inputs_generator: ArithCounterInputGen, add256_inputs_generator: Add256CounterInputGen, + dma_inputs_generator: DmaCounterInputGen, ) -> Self { - let mem_collectors_info: Vec = - mem_collector.iter().map(|(_, collector)| collector.get_mem_collector_info()).collect(); - Self { mem_collector, mem_align_collector, @@ -132,6 +145,10 @@ impl StaticDataBusCollect { arith_eq_collector, arith_eq_384_collector, add256_collector, + dma_collector, + dma_pre_post_collector, + dma_64_aligned_collector, + dma_unaligned_collector, rom_collector, arith_eq_inputs_generator, arith_eq_384_inputs_generator, @@ -140,8 +157,8 @@ impl StaticDataBusCollect { poseidon2_inputs_generator, arith_inputs_generator, add256_inputs_generator, + dma_inputs_generator, pending_transfers: VecDeque::with_capacity(64), - mem_collectors_info, } } @@ -156,177 +173,150 @@ impl StaticDataBusCollect { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn route_data(&mut self, bus_id: BusId, payload: &[PayloadType]) { + fn route_data(&mut self, bus_id: BusId, data: &[PayloadType], data_ext: &[PayloadType]) { match bus_id { MEM_BUS_ID => { - // Process mem collectors - inverted condition to avoid continue - for (_, mem_collector) in &mut self.mem_collector { - mem_collector.process_data(&bus_id, payload, &mut self.pending_transfers, None); - } - - // Only process align collectors if needed - for (_, mem_align_collector) in &mut self.mem_align_collector { - mem_align_collector.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); - } + MemCollectorProcessor::new(&mut self.mem_collector, &mut self.mem_align_collector) + .process_mem_data(&data.try_into().unwrap()); } - OPERATION_BUS_ID => match payload[OP_TYPE] { + OPERATION_BUS_ID => match data[OP_TYPE] { BINARY_TYPE => { for (_, binary_add_collector) in &mut self.binary_add_collector { - binary_add_collector.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); + binary_add_collector.process_data(&bus_id, data); } for (_, binary_basic_collector) in &mut self.binary_basic_collector { - binary_basic_collector.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); + binary_basic_collector.process_data(&bus_id, data); } } BINARY_E_TYPE => { for (_, binary_extension_collector) in &mut self.binary_extension_collector { - binary_extension_collector.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); + binary_extension_collector.process_data(&bus_id, data); } } ARITH_TYPE => { for (_, arith_collector) in &mut self.arith_collector { - arith_collector.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); + arith_collector.process_data(&bus_id, data); } self.arith_inputs_generator.process_data( &bus_id, - payload, + data, &mut self.pending_transfers, - None, ); } KECCAK_TYPE => { for (_, keccakf_collector) in &mut self.keccakf_collector { - keccakf_collector.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); + keccakf_collector.process_data(&bus_id, data); } self.keccakf_inputs_generator.process_data( &bus_id, - payload, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + data, + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } SHA256_TYPE => { for (_, sha256f_collector) in &mut self.sha256f_collector { - sha256f_collector.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); + sha256f_collector.process_data(&bus_id, data); } self.sha256f_inputs_generator.process_data( &bus_id, - payload, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + data, + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } POSEIDON2_TYPE => { for (_, poseidon2_collector) in &mut self.poseidon2_collector { - poseidon2_collector.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); + poseidon2_collector.process_data(&bus_id, data); } self.poseidon2_inputs_generator.process_data( &bus_id, - payload, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + data, + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } ARITH_EQ_TYPE => { for (_, arith_eq_collector) in &mut self.arith_eq_collector { - arith_eq_collector.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); + arith_eq_collector.process_data(&bus_id, data); } self.arith_eq_inputs_generator.process_data( &bus_id, - payload, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + data, + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } ARITH_EQ_384_TYPE => { for (_, arith_eq_384_collector) in &mut self.arith_eq_384_collector { - arith_eq_384_collector.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); + arith_eq_384_collector.process_data(&bus_id, data); } self.arith_eq_384_inputs_generator.process_data( &bus_id, - payload, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + data, + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } BIG_INT_OP_TYPE_ID => { for (_, add256_collector) in &mut self.add256_collector { - add256_collector.process_data( - &bus_id, - payload, - &mut self.pending_transfers, - None, - ); + add256_collector.process_data(&bus_id, data); } self.add256_inputs_generator.process_data( &bus_id, - payload, - &mut self.pending_transfers, - Some(&self.mem_collectors_info), + data, + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), + ); + } + DMA_OP_TYPE_ID => { + for (_, dma_collector) in &mut self.dma_collector { + dma_collector.process_data(&bus_id, data, data_ext); + } + for (_, dma_pre_post_collector) in &mut self.dma_pre_post_collector { + dma_pre_post_collector.process_data(&bus_id, data, data_ext); + } + for (_, dma_64_aligned_collector) in &mut self.dma_64_aligned_collector { + dma_64_aligned_collector.process_data(&bus_id, data, data_ext); + } + for (_, dma_unaligned_collector) in &mut self.dma_unaligned_collector { + dma_unaligned_collector.process_data(&bus_id, data, data_ext); + } + + self.dma_inputs_generator.process_data( + &bus_id, + data, + data_ext, + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), ); } _ => {} }, ROM_BUS_ID => { for (_, rom_collector) in &mut self.rom_collector { - rom_collector.process_data(&bus_id, payload, &mut self.pending_transfers, None); + rom_collector.process_data(&bus_id, data); } } _ => {} @@ -338,12 +328,19 @@ impl DataBusTrait>> for StaticDataBusCollect { #[inline(always)] - fn write_to_bus(&mut self, bus_id: BusId, payload: &[PayloadType]) -> bool { - self.route_data(bus_id, payload); + fn write_to_bus( + &mut self, + bus_id: BusId, + data: &[PayloadType], + data_ext: &[PayloadType], + ) -> bool { + self.route_data(bus_id, data, data_ext); // Process all pending transfers in a batch to improve cache locality - while let Some((pending_bus_id, pending_payload)) = self.pending_transfers.pop_front() { - self.route_data(pending_bus_id, &pending_payload); + while let Some((pending_bus_id, pending_payload, pending_data_ext)) = + self.pending_transfers.pop_front() + { + self.route_data(pending_bus_id, &pending_payload, &pending_data_ext); } true @@ -352,13 +349,9 @@ impl DataBusTrait>> fn on_close(&mut self) {} fn into_devices( - mut self, - execute_on_close: bool, + self, + _execute_on_close: bool, ) -> Vec<(Option, Option>>)> { - if execute_on_close { - self.on_close(); - } - let mut result = Vec::new(); // Add all collectors to the result @@ -410,6 +403,22 @@ impl DataBusTrait>> result.push((Some(id), Some(Box::new(collector) as Box>))); } + for (id, collector) in self.dma_collector { + result.push((Some(id), Some(Box::new(collector) as Box>))); + } + + for (id, collector) in self.dma_pre_post_collector { + result.push((Some(id), Some(Box::new(collector) as Box>))); + } + + for (id, collector) in self.dma_64_aligned_collector { + result.push((Some(id), Some(Box::new(collector) as Box>))); + } + + for (id, collector) in self.dma_unaligned_collector { + result.push((Some(id), Some(Box::new(collector) as Box>))); + } + for (id, collector) in self.rom_collector { result.push((Some(id), Some(Box::new(collector) as Box>))); } diff --git a/lib-c/c/src/ffiasm/bls12_381.asm b/lib-c/c/src/ffiasm/bls12_381.asm index aceee87d4..04e85b56f 100644 --- a/lib-c/c/src/ffiasm/bls12_381.asm +++ b/lib-c/c/src/ffiasm/bls12_381.asm @@ -8792,3 +8792,6 @@ R3 dq 0xc62c1807439b73af,0x1b3e0d188cf06990,0x73d13c71c7b5f418,0x6e2a5 lboMask dq 0x7fffffffffffffff np dq 0xfffffffeffffffff + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/bls12_381_384.asm b/lib-c/c/src/ffiasm/bls12_381_384.asm index 7f065aec7..b737e40f5 100644 --- a/lib-c/c/src/ffiasm/bls12_381_384.asm +++ b/lib-c/c/src/ffiasm/bls12_381_384.asm @@ -10910,3 +10910,6 @@ R3 dq 0xed48ac6bd94ca1e0,0x315f831e03a7adf8,0x9a53352a615e29dd,0x34c04 lboMask dq 0x1fffffffffffffff np dq 0x89f3fffcfffcfffd + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/fec.asm b/lib-c/c/src/ffiasm/fec.asm index 4d1465e25..21b8e1d2c 100644 --- a/lib-c/c/src/ffiasm/fec.asm +++ b/lib-c/c/src/ffiasm/fec.asm @@ -8874,3 +8874,6 @@ R3 dq 0x002bb1e33795f671,0x0000000100000b73,0x0000000000000000,0x00000 lboMask dq 0xffffffffffffffff np dq 0xd838091dd2253531 + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/fnec.asm b/lib-c/c/src/ffiasm/fnec.asm index 3b6b36d3d..fe8abd610 100644 --- a/lib-c/c/src/ffiasm/fnec.asm +++ b/lib-c/c/src/ffiasm/fnec.asm @@ -8874,3 +8874,6 @@ R3 dq 0x7bc0cfe0e9ff41ed,0x0017648444d4322c,0xb1b31347f1d0b2da,0x555d8 lboMask dq 0xffffffffffffffff np dq 0x4b0dff665588b13f + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/fq.asm b/lib-c/c/src/ffiasm/fq.asm index 34d7bd01a..93c36ba05 100644 --- a/lib-c/c/src/ffiasm/fq.asm +++ b/lib-c/c/src/ffiasm/fq.asm @@ -8791,3 +8791,6 @@ R3 dq 0xb1cd6dafda1530df,0x62f210e6a7283db6,0xef7f0b0c0ada0afb,0x20fd6 lboMask dq 0x3fffffffffffffff np dq 0x87d20782e4866389 + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/fr.asm b/lib-c/c/src/ffiasm/fr.asm index 806afaac0..0cac981ae 100644 --- a/lib-c/c/src/ffiasm/fr.asm +++ b/lib-c/c/src/ffiasm/fr.asm @@ -8791,3 +8791,6 @@ R3 dq 0x5e94d8e1b4bf0040,0x2a489cbe1cfbb6b8,0x893cc664a19fcfed,0x0cf85 lboMask dq 0x3fffffffffffffff np dq 0xc2e1f593efffffff + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/nsecp256r1.asm b/lib-c/c/src/ffiasm/nsecp256r1.asm index 963327487..e7f3e4693 100644 --- a/lib-c/c/src/ffiasm/nsecp256r1.asm +++ b/lib-c/c/src/ffiasm/nsecp256r1.asm @@ -8875,3 +8875,6 @@ R3 dq 0xac8ebec90b65a624,0x111f28ae0c0555c9,0x2543b9246ba5e93f,0x503a5 lboMask dq 0xffffffffffffffff np dq 0xccd1c8aaee00bc4f + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/lib-c/c/src/ffiasm/psecp256r1.asm b/lib-c/c/src/ffiasm/psecp256r1.asm index ba7233133..a97b6e8f0 100644 --- a/lib-c/c/src/ffiasm/psecp256r1.asm +++ b/lib-c/c/src/ffiasm/psecp256r1.asm @@ -8875,3 +8875,6 @@ R3 dq 0xfffffffd0000000a,0xffffffedfffffff7,0x00000005fffffffc,0x00000 lboMask dq 0xffffffffffffffff np dq 0x1 + +; Mark stack as non-executable +section .note.GNU-stack noalloc noexec nowrite progbits diff --git a/pil/config.pil b/pil/config.pil new file mode 100644 index 000000000..954a45829 --- /dev/null +++ b/pil/config.pil @@ -0,0 +1,6 @@ +const int MAIN_STEP_BITS = 36; +const int MAX_STEPS = 1 << MAIN_STEP_BITS; +const int MEM_STEP_BITS = MAIN_STEP_BITS + 2; +const int REG_STEP_BITS = MAIN_STEP_BITS + 2; +const int ADDR_BITS = 32; +const int ADDR_W_BITS = ADDR_BITS - 3; diff --git a/pil/operations.pil b/pil/operations.pil index f873069a5..0aefb95ab 100644 --- a/pil/operations.pil +++ b/pil/operations.pil @@ -83,6 +83,9 @@ const int OP_REMU_W = 0xBD; const int OP_DIV_W = 0xBE; const int OP_REM_W = 0xBF; +const int OP_DMA_MEMCPY = 0xD0; +const int OP_DMA_MEMCMP = 0xD1; + const int OP_POSEIDON2 = 0xE1; const int OP_ARITH_384_MOD = 0xE2; diff --git a/pil/opids.pil b/pil/opids.pil index 1378e1529..c1ee73de8 100644 --- a/pil/opids.pil +++ b/pil/opids.pil @@ -11,7 +11,6 @@ const int ROM_BUS_ID = 7890; // Memory ids const int MEMORY_ID = 10; const int MEMORY_ALIGN_ROM_ID = 133; -const int DUAL_BYTE_TABLE_ID = 88; // Arith table ids const int ARITH_TABLE_ID = 331; @@ -28,4 +27,14 @@ const int BINARY_EXTENSION_FROPS_TABLE_ID = 5012; // Precompiles const int ARITH_EQ_LT_TABLE_ID = 5002; -const int KECCAKF_TABLE_ID = 126; \ No newline at end of file +const int KECCAKF_TABLE_ID = 126; + + +// DMA +const int DMA_BUS_ID = 8000; +const int DMA_ROM_ID = 8001; +const int DMA_PRE_POST_TABLE_ID = 8002; + +// Ranges +const int DUAL_RANGE_7_BITS_ID = 77; +const int DUAL_RANGE_BYTE_ID = 88; diff --git a/pil/src/constants.rs b/pil/src/constants.rs new file mode 100644 index 000000000..5fcb5657b --- /dev/null +++ b/pil/src/constants.rs @@ -0,0 +1,5 @@ +pub const DUAL_RANGE_BYTE_ID: usize = 88; +pub const DUAL_RANGE_7_BITS_ID: usize = 77; +pub const DMA_ROM_ID: usize = 8001; +pub const DMA_PRE_POST_TABLE_ID: usize = 8002; +pub const DMA_PRE_POST_TABLE_SIZE: usize = 280; diff --git a/pil/src/lib.rs b/pil/src/lib.rs index 77de853a8..92c1ba497 100644 --- a/pil/src/lib.rs +++ b/pil/src/lib.rs @@ -1,3 +1,5 @@ +mod constants; mod pil_helpers; +pub use constants::*; pub use pil_helpers::*; diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 7e65ecf58..eb94603e0 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "92d0e6393f156ecee7a7d087ce5b417d708dd0023de05bcdf569ff0daed5c287"; +pub const PILOUT_HASH: &str = "28c8185c73aa9a58d122501d67edea0d8dd62d1893c974f4155b3944a7c84bbd"; pub const MERKLE_TREE_ARITY: u64 = 4; @@ -26,49 +26,57 @@ pub const ZISK_AIRGROUP_ID: usize = 0; //AIR CONSTANTS -pub const MAIN_AIR_IDS: &[usize] = &[0]; +pub const DMA_AIR_IDS: &[usize] = &[0]; -pub const ROM_AIR_IDS: &[usize] = &[1]; +pub const DMA_64_ALIGNED_AIR_IDS: &[usize] = &[1]; -pub const MEM_AIR_IDS: &[usize] = &[2]; +pub const DMA_UNALIGNED_AIR_IDS: &[usize] = &[2]; -pub const ROM_DATA_AIR_IDS: &[usize] = &[3]; +pub const DMA_PRE_POST_AIR_IDS: &[usize] = &[3]; -pub const INPUT_DATA_AIR_IDS: &[usize] = &[4]; +pub const MAIN_AIR_IDS: &[usize] = &[4]; -pub const MEM_ALIGN_AIR_IDS: &[usize] = &[5]; +pub const ROM_AIR_IDS: &[usize] = &[5]; -pub const MEM_ALIGN_BYTE_AIR_IDS: &[usize] = &[6]; +pub const MEM_AIR_IDS: &[usize] = &[6]; -pub const MEM_ALIGN_READ_BYTE_AIR_IDS: &[usize] = &[7]; +pub const ROM_DATA_AIR_IDS: &[usize] = &[7]; -pub const MEM_ALIGN_WRITE_BYTE_AIR_IDS: &[usize] = &[8]; +pub const INPUT_DATA_AIR_IDS: &[usize] = &[8]; -pub const ARITH_AIR_IDS: &[usize] = &[9]; +pub const MEM_ALIGN_AIR_IDS: &[usize] = &[9]; -pub const BINARY_AIR_IDS: &[usize] = &[10]; +pub const MEM_ALIGN_BYTE_AIR_IDS: &[usize] = &[10]; -pub const BINARY_ADD_AIR_IDS: &[usize] = &[11]; +pub const MEM_ALIGN_READ_BYTE_AIR_IDS: &[usize] = &[11]; -pub const BINARY_EXTENSION_AIR_IDS: &[usize] = &[12]; +pub const MEM_ALIGN_WRITE_BYTE_AIR_IDS: &[usize] = &[12]; -pub const ADD_256_AIR_IDS: &[usize] = &[13]; +pub const ARITH_AIR_IDS: &[usize] = &[13]; -pub const ARITH_EQ_AIR_IDS: &[usize] = &[14]; +pub const BINARY_AIR_IDS: &[usize] = &[14]; -pub const ARITH_EQ_384_AIR_IDS: &[usize] = &[15]; +pub const BINARY_ADD_AIR_IDS: &[usize] = &[15]; -pub const KECCAKF_AIR_IDS: &[usize] = &[16]; +pub const BINARY_EXTENSION_AIR_IDS: &[usize] = &[16]; -pub const SHA_256_F_AIR_IDS: &[usize] = &[17]; +pub const ADD_256_AIR_IDS: &[usize] = &[17]; -pub const POSEIDON_2_AIR_IDS: &[usize] = &[18]; +pub const ARITH_EQ_AIR_IDS: &[usize] = &[18]; -pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[19]; +pub const ARITH_EQ_384_AIR_IDS: &[usize] = &[19]; -pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[20]; +pub const KECCAKF_AIR_IDS: &[usize] = &[20]; -pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[21]; +pub const SHA_256_F_AIR_IDS: &[usize] = &[21]; + +pub const POSEIDON_2_AIR_IDS: &[usize] = &[22]; + +pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[23]; + +pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[24]; + +pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[25]; //PUBLICS @@ -109,311 +117,375 @@ values!(ZiskPublicValues { }); values!(ZiskProofValues { - enable_input_data: F, enable_rom_data: F, + enable_input_data: F, enable_rom_data: F, enable_dma_64_aligned: F, enable_dma_64_aligned_input: F, enable_dma_unaligned: F, }); +trace_row!(DmaFixedRow { + __L1__: F, +}); +pub type DmaFixed = GenericTrace, 2097152, 0, 0>; + +trace_row!(DmaTraceRow { + sel:bit, h_count:ubit(24), count_lt_256:bit, l_count:ubit(9), h_src64:ubit(22), l_src64:ubit(7), src_offset:ubit(3), h_dst64:ubit(22), l_dst64:ubit(7), dst_offset:ubit(3), main_step:ubit(36), use_pre:bit, use_memcpy:bit, use_post:bit, src64_inc_by_pre:bit, pre_count:ubit(3), l_count64:ubit(9), src_offset_after_pre:ubit(3), +}); +pub type DmaTrace = GenericTrace, 2097152, 0, 0>; + + +pub type DmaTracePacked = GenericTrace, 2097152, 0, 0>; + + +trace_row!(Dma64AlignedFixedRow { + __L1__: F, +}); +pub type Dma64AlignedFixed = GenericTrace, 2097152, 0, 1>; + +trace_row!(Dma64AlignedTraceRow { + main_step:ubit(36), dst64:ubit(29), count:u32, sel_op:[bit; 4], seq_end:bit, is_mem_eq:bit, value:[[u32; 2]; 4], src64:ubit(29), previous_seq_end:bit, +}); +pub type Dma64AlignedTrace = GenericTrace, 2097152, 0, 1>; + + +pub type Dma64AlignedTracePacked = GenericTrace, 2097152, 0, 1>; + + +trace_row!(DmaUnalignedFixedRow { + __L1__: F, +}); +pub type DmaUnalignedFixed = GenericTrace, 2097152, 0, 2>; + +trace_row!(DmaUnalignedTraceRow { + main_step:ubit(36), src64:ubit(29), dst64:ubit(29), count:u32, seq_end:bit, previous_seq_end:bit, is_mem_eq:bit, offset_7:bit, offset_6:bit, offset_5:bit, offset_4:bit, offset_3:bit, offset_2:bit, read_bytes:[u8; 8], no_last_no_seq_end:bit, write_value:[u32; 2], +}); +pub type DmaUnalignedTrace = GenericTrace, 2097152, 0, 2>; + + +pub type DmaUnalignedTracePacked = GenericTrace, 2097152, 0, 2>; + + +trace_row!(DmaPrePostFixedRow { + __L1__: F, +}); +pub type DmaPrePostFixed = GenericTrace, 2097152, 0, 3>; + +trace_row!(DmaPrePostTraceRow { + main_step:ubit(36), src64:ubit(29), dst64:ubit(29), dst_offset:ubit(3), src_offset:ubit(3), count:ubit(3), selread:[bit; 7], dst_offset_gt_src_offset:bit, enabled:bit, enabled_second_read:bit, bytes:[u8; 24], selb:[bit; 8], write_value:[u32; 4], +}); +pub type DmaPrePostTrace = GenericTrace, 2097152, 0, 3>; + + +pub type DmaPrePostTracePacked = GenericTrace, 2097152, 0, 3>; + + trace_row!(MainFixedRow { SEGMENT_L1: F, SEGMENT_STEP: F, __L1__: F, }); -pub type MainFixed = GenericTrace, 4194304, 0, 0>; +pub type MainFixed = GenericTrace, 4194304, 0, 4>; trace_row!(MainTraceRow { - a:[u32; 2], b:[u32; 2], c:[u32; 2], flag:bit, pc:u32, a_src_imm:bit, a_src_mem:bit, a_offset_imm0:u64, a_imm1:u32, a_src_step:bit, b_src_imm:bit, b_src_mem:bit, b_offset_imm0:u64, b_imm1:u32, b_src_ind:bit, ind_width:ubit(4), is_external_op:bit, op:u8, store_ra:bit, store_mem:bit, store_ind:bit, store_offset:u64, set_pc:bit, jmp_offset1:u64, jmp_offset2:u64, m32:bit, addr1:u32, a_reg_prev_mem_step:ubit(40), b_reg_prev_mem_step:ubit(40), store_reg_prev_mem_step:ubit(40), store_reg_prev_value:[u32; 2], a_src_reg:bit, b_src_reg:bit, store_reg:bit, + a:[u32; 2], b:[u32; 2], c:[u32; 2], flag:bit, pc:u32, a_src_imm:bit, a_src_mem:bit, a_offset_imm0:u64, a_imm1:u32, op_with_step:bit, b_src_imm:bit, b_src_mem:bit, b_offset_imm0:u64, b_imm1:u32, b_src_ind:bit, ind_width:ubit(4), is_external_op:bit, op:u8, store_pc:bit, store_mem:bit, store_ind:bit, store_offset:u64, set_pc:bit, jmp_offset1:u64, jmp_offset2:u64, m32:bit, addr1:u32, a_reg_prev_mem_step:ubit(38), b_reg_prev_mem_step:ubit(38), store_reg_prev_mem_step:ubit(38), store_reg_prev_value:[u32; 2], a_src_reg:bit, b_src_reg:bit, store_reg:bit, }); -pub type MainTrace = GenericTrace, 4194304, 0, 0>; +pub type MainTrace = GenericTrace, 4194304, 0, 4>; -pub type MainTracePacked = GenericTrace, 4194304, 0, 0>; +pub type MainTracePacked = GenericTrace, 4194304, 0, 4>; trace_row!(RomFixedRow { __L1__: F, }); -pub type RomFixed = GenericTrace, 4194304, 0, 1>; +pub type RomFixed = GenericTrace, 4194304, 0, 5>; trace_row!(RomTraceRow { multiplicity:F, }); -pub type RomTrace = GenericTrace, 4194304, 0, 1>; +pub type RomTrace = GenericTrace, 4194304, 0, 5>; trace_row!(MemFixedRow { SEGMENT_L1: F, __L1__: F, }); -pub type MemFixed = GenericTrace, 4194304, 0, 2>; +pub type MemFixed = GenericTrace, 4194304, 0, 6>; trace_row!(MemTraceRow { - addr:ubit(29), step:ubit(40), sel:bit, addr_changes:bit, step_dual:ubit(40), sel_dual:bit, value:[u32; 2], wr:bit, previous_step:ubit(40), increment:[ubit(18); 2], read_same_addr:bit, + addr:ubit(29), step:ubit(38), sel:bit, addr_changes:bit, step_dual:ubit(38), sel_dual:bit, value:[u32; 2], wr:bit, previous_step:ubit(40), l_increment:ubit(22), h_increment:u16, read_same_addr:bit, }); -pub type MemTrace = GenericTrace, 4194304, 0, 2>; +pub type MemTrace = GenericTrace, 4194304, 0, 6>; -pub type MemTracePacked = GenericTrace, 4194304, 0, 2>; +pub type MemTracePacked = GenericTrace, 4194304, 0, 6>; trace_row!(RomDataFixedRow { SEGMENT_L1: F, __L1__: F, }); -pub type RomDataFixed = GenericTrace, 2097152, 0, 3>; +pub type RomDataFixed = GenericTrace, 2097152, 0, 7>; trace_row!(RomDataTraceRow { - addr:ubit(29), step:ubit(40), sel:bit, addr_changes:bit, value:[u32; 2], + addr:ubit(29), step:ubit(38), sel:bit, addr_changes:bit, value:[u32; 2], }); -pub type RomDataTrace = GenericTrace, 2097152, 0, 3>; +pub type RomDataTrace = GenericTrace, 2097152, 0, 7>; -pub type RomDataTracePacked = GenericTrace, 2097152, 0, 3>; +pub type RomDataTracePacked = GenericTrace, 2097152, 0, 7>; trace_row!(InputDataFixedRow { SEGMENT_L1: F, __L1__: F, }); -pub type InputDataFixed = GenericTrace, 2097152, 0, 4>; +pub type InputDataFixed = GenericTrace, 2097152, 0, 8>; trace_row!(InputDataTraceRow { - addr:ubit(29), step:ubit(40), sel:bit, addr_changes:bit, value_word:[u16; 4], is_free_read:bit, + addr:ubit(29), step:ubit(38), sel:bit, addr_changes:bit, value_word:[u16; 4], is_free_read:bit, }); -pub type InputDataTrace = GenericTrace, 2097152, 0, 4>; +pub type InputDataTrace = GenericTrace, 2097152, 0, 8>; -pub type InputDataTracePacked = GenericTrace, 2097152, 0, 4>; +pub type InputDataTracePacked = GenericTrace, 2097152, 0, 8>; trace_row!(MemAlignFixedRow { L1: F, __L1__: F, }); -pub type MemAlignFixed = GenericTrace, 2097152, 0, 5>; +pub type MemAlignFixed = GenericTrace, 2097152, 0, 9>; trace_row!(MemAlignTraceRow { addr:ubit(29), offset:ubit(3), width:ubit(4), wr:bit, pc:u8, reset:bit, sel_up_to_down:bit, sel_down_to_up:bit, reg:[u8; 8], sel:[bit; 8], step:ubit(40), delta_addr:u64, sel_prove:bit, value:[u32; 2], }); -pub type MemAlignTrace = GenericTrace, 2097152, 0, 5>; +pub type MemAlignTrace = GenericTrace, 2097152, 0, 9>; -pub type MemAlignTracePacked = GenericTrace, 2097152, 0, 5>; +pub type MemAlignTracePacked = GenericTrace, 2097152, 0, 9>; trace_row!(MemAlignByteFixedRow { __L1__: F, }); -pub type MemAlignByteFixed = GenericTrace, 4194304, 0, 6>; +pub type MemAlignByteFixed = GenericTrace, 4194304, 0, 10>; trace_row!(MemAlignByteTraceRow { sel_high_4b:bit, sel_high_2b:bit, sel_high_b:bit, direct_value:u32, composed_value:u32, written_composed_value:u32, written_byte_value:u8, value_16b:u16, value_8b:u8, byte_value:u8, addr_w:ubit(29), step:ubit(40), is_write:bit, mem_write_values:[u32; 2], bus_byte:u8, }); -pub type MemAlignByteTrace = GenericTrace, 4194304, 0, 6>; +pub type MemAlignByteTrace = GenericTrace, 4194304, 0, 10>; -pub type MemAlignByteTracePacked = GenericTrace, 4194304, 0, 6>; +pub type MemAlignByteTracePacked = GenericTrace, 4194304, 0, 10>; trace_row!(MemAlignReadByteFixedRow { __L1__: F, }); -pub type MemAlignReadByteFixed = GenericTrace, 4194304, 0, 7>; +pub type MemAlignReadByteFixed = GenericTrace, 4194304, 0, 11>; trace_row!(MemAlignReadByteTraceRow { sel_high_4b:bit, sel_high_2b:bit, sel_high_b:bit, direct_value:u32, composed_value:u32, value_16b:u16, value_8b:u8, byte_value:u8, addr_w:ubit(29), step:ubit(40), }); -pub type MemAlignReadByteTrace = GenericTrace, 4194304, 0, 7>; +pub type MemAlignReadByteTrace = GenericTrace, 4194304, 0, 11>; -pub type MemAlignReadByteTracePacked = GenericTrace, 4194304, 0, 7>; +pub type MemAlignReadByteTracePacked = GenericTrace, 4194304, 0, 11>; trace_row!(MemAlignWriteByteFixedRow { __L1__: F, }); -pub type MemAlignWriteByteFixed = GenericTrace, 4194304, 0, 8>; +pub type MemAlignWriteByteFixed = GenericTrace, 4194304, 0, 12>; trace_row!(MemAlignWriteByteTraceRow { sel_high_4b:bit, sel_high_2b:bit, sel_high_b:bit, direct_value:u32, composed_value:u32, written_composed_value:u32, written_byte_value:u8, value_16b:u16, value_8b:u8, byte_value:u8, addr_w:ubit(29), step:ubit(40), mem_write_values:[u32; 2], }); -pub type MemAlignWriteByteTrace = GenericTrace, 4194304, 0, 8>; +pub type MemAlignWriteByteTrace = GenericTrace, 4194304, 0, 12>; -pub type MemAlignWriteByteTracePacked = GenericTrace, 4194304, 0, 8>; +pub type MemAlignWriteByteTracePacked = GenericTrace, 4194304, 0, 12>; trace_row!(ArithFixedRow { __L1__: F, }); -pub type ArithFixed = GenericTrace, 2097152, 0, 9>; +pub type ArithFixed = GenericTrace, 2097152, 0, 13>; trace_row!(ArithTraceRow { carry:[u64; 7], a:[u16; 4], b:[u16; 4], c:[u16; 4], d:[u16; 4], na:bit, nb:bit, nr:bit, np:bit, sext:bit, m32:bit, div:bit, fab:u64, na_fb:u64, nb_fa:u64, main_div:bit, main_mul:bit, signed:bit, div_by_zero:bit, div_overflow:bit, inv_sum_all_bs:u64, op:u8, bus_res1:u32, multiplicity:bit, range_ab:ubit(7), range_cd:ubit(7), }); -pub type ArithTrace = GenericTrace, 2097152, 0, 9>; +pub type ArithTrace = GenericTrace, 2097152, 0, 13>; -pub type ArithTracePacked = GenericTrace, 2097152, 0, 9>; +pub type ArithTracePacked = GenericTrace, 2097152, 0, 13>; trace_row!(BinaryFixedRow { __L1__: F, }); -pub type BinaryFixed = GenericTrace, 4194304, 0, 10>; +pub type BinaryFixed = GenericTrace, 4194304, 0, 14>; trace_row!(BinaryTraceRow { b_op:ubit(7), free_in_a:[u8; 8], free_in_b:[u8; 8], free_in_c:[u8; 8], carry:[bit; 8], mode32:bit, result_is_a:bit, use_first_byte:bit, c_is_signed:bit, b_op_or_sext:ubit(10), mode32_and_c_is_signed:bit, }); -pub type BinaryTrace = GenericTrace, 4194304, 0, 10>; +pub type BinaryTrace = GenericTrace, 4194304, 0, 14>; -pub type BinaryTracePacked = GenericTrace, 4194304, 0, 10>; +pub type BinaryTracePacked = GenericTrace, 4194304, 0, 14>; trace_row!(BinaryAddFixedRow { __L1__: F, }); -pub type BinaryAddFixed = GenericTrace, 4194304, 0, 11>; +pub type BinaryAddFixed = GenericTrace, 4194304, 0, 15>; trace_row!(BinaryAddTraceRow { a:[u32; 2], b:[u32; 2], c_chunks:[u16; 4], cout:[bit; 2], }); -pub type BinaryAddTrace = GenericTrace, 4194304, 0, 11>; +pub type BinaryAddTrace = GenericTrace, 4194304, 0, 15>; -pub type BinaryAddTracePacked = GenericTrace, 4194304, 0, 11>; +pub type BinaryAddTracePacked = GenericTrace, 4194304, 0, 15>; trace_row!(BinaryExtensionFixedRow { __L1__: F, }); -pub type BinaryExtensionFixed = GenericTrace, 4194304, 0, 12>; +pub type BinaryExtensionFixed = GenericTrace, 4194304, 0, 16>; trace_row!(BinaryExtensionTraceRow { op:ubit(6), free_in_a:[u8; 8], free_in_b:u8, free_in_c:[[u32; 2]; 8], op_is_shift:bit, b:[u32; 2], }); -pub type BinaryExtensionTrace = GenericTrace, 4194304, 0, 12>; +pub type BinaryExtensionTrace = GenericTrace, 4194304, 0, 16>; -pub type BinaryExtensionTracePacked = GenericTrace, 4194304, 0, 12>; +pub type BinaryExtensionTracePacked = GenericTrace, 4194304, 0, 16>; trace_row!(Add256FixedRow { __L1__: F, }); -pub type Add256Fixed = GenericTrace, 1048576, 0, 13>; +pub type Add256Fixed = GenericTrace, 1048576, 0, 17>; trace_row!(Add256TraceRow { a:[[u32; 2]; 4], b:[[u32; 2]; 4], c_chunks:[[u16; 4]; 4], cout:[[bit; 2]; 4], addr_params:u32, addr_a:u32, addr_b:u32, addr_c:u32, step:ubit(40), cin:bit, sel:bit, }); -pub type Add256Trace = GenericTrace, 1048576, 0, 13>; +pub type Add256Trace = GenericTrace, 1048576, 0, 17>; -pub type Add256TracePacked = GenericTrace, 1048576, 0, 13>; +pub type Add256TracePacked = GenericTrace, 1048576, 0, 17>; trace_row!(ArithEqFixedRow { CLK_0: F, __L1__: F, }); -pub type ArithEqFixed = GenericTrace, 1048576, 0, 14>; +pub type ArithEqFixed = GenericTrace, 1048576, 0, 18>; trace_row!(ArithEqTraceRow { x1:u16, y1:u16, x2:u16, y2:u16, x3:u16, y3:u16, q0:ubit(22), q1:ubit(22), q2:ubit(22), s:ubit(22), sel_op:[bit; 9], sel_op_clk0:[bit; 9], x_delta_chunk_inv:u64, x_are_different:bit, x3_lt:bit, y3_lt:bit, carry:[[u64; 2]; 3], step_addr:ubit(40), }); -pub type ArithEqTrace = GenericTrace, 1048576, 0, 14>; +pub type ArithEqTrace = GenericTrace, 1048576, 0, 18>; -pub type ArithEqTracePacked = GenericTrace, 1048576, 0, 14>; +pub type ArithEqTracePacked = GenericTrace, 1048576, 0, 18>; trace_row!(ArithEq384FixedRow { CLK_0: F, __L1__: F, }); -pub type ArithEq384Fixed = GenericTrace, 1048576, 0, 15>; +pub type ArithEq384Fixed = GenericTrace, 1048576, 0, 19>; trace_row!(ArithEq384TraceRow { x1:u16, y1:u16, x2:u16, y2:u16, x3:u16, y3:u16, q0:ubit(22), q1:ubit(22), q2:ubit(22), s:ubit(22), sel_op:[bit; 6], sel_op_clk0:[bit; 6], x_delta_chunk_inv:u64, x_are_different:bit, x3_lt:bit, y3_lt:bit, carry:[[u64; 2]; 3], step_addr:ubit(40), }); -pub type ArithEq384Trace = GenericTrace, 1048576, 0, 15>; +pub type ArithEq384Trace = GenericTrace, 1048576, 0, 19>; -pub type ArithEq384TracePacked = GenericTrace, 1048576, 0, 15>; +pub type ArithEq384TracePacked = GenericTrace, 1048576, 0, 19>; trace_row!(KeccakfFixedRow { CLK_0: F, __L1__: F, }); -pub type KeccakfFixed = GenericTrace, 131072, 0, 16>; +pub type KeccakfFixed = GenericTrace, 131072, 0, 20>; trace_row!(KeccakfTraceRow { in_use_clk_0:bit, in_use:bit, state:[bit; 1600], chunk_acc:[ubit(22); 533], rem_acc:u8, step_addr:ubit(40), }); -pub type KeccakfTrace = GenericTrace, 131072, 0, 16>; +pub type KeccakfTrace = GenericTrace, 131072, 0, 20>; -pub type KeccakfTracePacked = GenericTrace, 131072, 0, 16>; +pub type KeccakfTracePacked = GenericTrace, 131072, 0, 20>; trace_row!(Sha256fFixedRow { CLK_0: F, __L1__: F, }); -pub type Sha256fFixed = GenericTrace, 262144, 0, 17>; +pub type Sha256fFixed = GenericTrace, 262144, 0, 21>; trace_row!(Sha256fTraceRow { a:[bit; 32], e:[bit; 32], w:[bit; 32], new_a_carry_bits:u8, new_e_carry_bits:u8, new_w_carry_bits:ubit(4), step_addr:ubit(40), in_use_clk_0:bit, in_use:bit, }); -pub type Sha256fTrace = GenericTrace, 262144, 0, 17>; +pub type Sha256fTrace = GenericTrace, 262144, 0, 21>; -pub type Sha256fTracePacked = GenericTrace, 262144, 0, 17>; +pub type Sha256fTracePacked = GenericTrace, 262144, 0, 21>; trace_row!(Poseidon2FixedRow { CLK_0: F, __L1__: F, }); -pub type Poseidon2Fixed = GenericTrace, 131072, 0, 18>; +pub type Poseidon2Fixed = GenericTrace, 131072, 0, 22>; trace_row!(Poseidon2TraceRow { in_use_clk_0:bit, in_use:bit, chunks:[[u32; 2]; 16], step_addr:ubit(40), }); -pub type Poseidon2Trace = GenericTrace, 131072, 0, 18>; +pub type Poseidon2Trace = GenericTrace, 131072, 0, 22>; -pub type Poseidon2TracePacked = GenericTrace, 131072, 0, 18>; +pub type Poseidon2TracePacked = GenericTrace, 131072, 0, 22>; trace_row!(SpecifiedRangesFixedRow { - RANGE: [F; 33], __L1__: F, + OPID: [F; 29], VALS: [F; 29], __L1__: F, }); -pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 19>; +pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 23>; trace_row!(SpecifiedRangesTraceRow { - mul:[F; 33], + mul:[F; 29], }); -pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 19>; +pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 23>; trace_row!(VirtualTable0FixedRow { UID: [F; 8], column: [F; 43], __L1__: F, }); -pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 20>; +pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 24>; trace_row!(VirtualTable0TraceRow { multiplicity:[F; 8], }); -pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 20>; +pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 24>; trace_row!(VirtualTable1FixedRow { - UID: [F; 8], column: [F; 64], __L1__: F, + UID: [F; 8], column: [F; 72], __L1__: F, }); -pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 21>; +pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 25>; trace_row!(VirtualTable1TraceRow { multiplicity:[F; 8], }); -pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 21>; +pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 25>; trace_row!(RomRomTraceRow { line: F, a_offset_imm0: F, a_imm1: F, b_offset_imm0: F, b_imm1: F, ind_width: F, op: F, store_offset: F, jmp_offset1: F, jmp_offset2: F, flags: F, }); -pub type RomRomTrace = GenericTrace, 4194304, 0, 1, 0>; +pub type RomRomTrace = GenericTrace, 4194304, 0, 5, 0>; +values!(Dma64AlignedAirValues { + segment_id: F, segment_previous_seq_end: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count: F, segment_previous_is_mem_eq: F, segment_last_seq_end: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count: F, segment_last_is_mem_eq: F, is_last_segment: F, segment_previous_src64: F, segment_last_src64: F, last_count_chunk: [F; 2], padding_size: F, im_direct: [FieldExtension; 5], +}); + +values!(DmaUnalignedAirValues { + segment_id: F, segment_previous_seq_end: F, segment_previous_src64: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_offset: F, segment_previous_count: F, segment_previous_is_mem_eq: F, segment_first_bytes: [F; 8], segment_last_seq_end: F, segment_last_src64: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_offset: F, segment_last_count: F, segment_last_is_mem_eq: F, segment_next_bytes: [F; 8], is_last_segment: F, last_count_chunk: [F; 2], padding_rows: F, im_direct: [FieldExtension; 6], +}); + values!(MainAirValues { main_last_segment: F, main_segment: F, segment_initial_pc: F, segment_previous_c: [F; 2], segment_next_pc: F, segment_last_c: [F; 2], last_reg_value: [[F; 2]; 31], last_reg_mem_step: [F; 31], im_direct: [FieldExtension; 96], }); @@ -454,6 +526,22 @@ values!(BinaryExtensionAirValues { padding_size: F, im_direct: [FieldExtension; 1], }); +values!(DmaAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(Dma64AlignedAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(DmaUnalignedAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(DmaPrePostAirGroupValues { + gsum_result: FieldExtension, +}); + values!(MainAirGroupValues { gsum_result: FieldExtension, }); @@ -545,90 +633,110 @@ values!(VirtualTable1AirGroupValues { pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ (0, 0, PackedInfoConst { is_packed: true, - num_packed_words: 14, - unpack_info: &[32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, 64, 1, 64, 64, 1, 32, 40, 40, 40, 32, 32, 1, 1, 1], + num_packed_words: 3, + unpack_info: &[1, 24, 1, 9, 22, 7, 3, 22, 7, 3, 36, 1, 1, 1, 1, 3, 9, 3], + }), + (0, 1, PackedInfoConst { + is_packed: true, + num_packed_words: 7, + unpack_info: &[36, 29, 32, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 29, 1], }), (0, 2, PackedInfoConst { is_packed: true, - num_packed_words: 4, - unpack_info: &[29, 40, 1, 1, 40, 1, 32, 32, 1, 40, 18, 18, 1], + num_packed_words: 5, + unpack_info: &[36, 29, 29, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 32, 32], }), (0, 3, PackedInfoConst { is_packed: true, - num_packed_words: 3, - unpack_info: &[29, 40, 1, 1, 32, 32], + num_packed_words: 7, + unpack_info: &[36, 29, 29, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32], }), (0, 4, PackedInfoConst { + is_packed: true, + num_packed_words: 14, + unpack_info: &[32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, 64, 1, 64, 64, 1, 32, 38, 38, 38, 32, 32, 1, 1, 1], + }), + (0, 6, PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[29, 38, 1, 1, 38, 1, 32, 32, 1, 40, 22, 16, 1], + }), + (0, 7, PackedInfoConst { is_packed: true, num_packed_words: 3, - unpack_info: &[29, 40, 1, 1, 16, 16, 16, 16, 1], + unpack_info: &[29, 38, 1, 1, 32, 32], }), - (0, 5, PackedInfoConst { + (0, 8, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[29, 38, 1, 1, 16, 16, 16, 16, 1], + }), + (0, 9, PackedInfoConst { is_packed: true, num_packed_words: 5, unpack_info: &[29, 3, 4, 1, 8, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 40, 64, 1, 32, 32], }), - (0, 6, PackedInfoConst { + (0, 10, PackedInfoConst { is_packed: true, num_packed_words: 5, unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 1, 32, 32, 8], }), - (0, 7, PackedInfoConst { + (0, 11, PackedInfoConst { is_packed: true, num_packed_words: 3, unpack_info: &[1, 1, 1, 32, 32, 16, 8, 8, 29, 40], }), - (0, 8, PackedInfoConst { + (0, 12, PackedInfoConst { is_packed: true, num_packed_words: 5, unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 32, 32], }), - (0, 9, PackedInfoConst { + (0, 13, PackedInfoConst { is_packed: true, num_packed_words: 17, unpack_info: &[64, 64, 64, 64, 64, 64, 64, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 64, 64, 64, 1, 1, 1, 1, 1, 64, 8, 32, 1, 7, 7], }), - (0, 10, PackedInfoConst { + (0, 14, PackedInfoConst { is_packed: true, num_packed_words: 4, unpack_info: &[7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1], }), - (0, 11, PackedInfoConst { + (0, 15, PackedInfoConst { is_packed: true, num_packed_words: 4, unpack_info: &[32, 32, 32, 32, 16, 16, 16, 16, 1, 1], }), - (0, 12, PackedInfoConst { + (0, 16, PackedInfoConst { is_packed: true, num_packed_words: 11, unpack_info: &[6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 1, 32, 32], }), - (0, 13, PackedInfoConst { + (0, 17, PackedInfoConst { is_packed: true, num_packed_words: 15, unpack_info: &[32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 40, 1, 1], }), - (0, 14, PackedInfoConst { + (0, 18, PackedInfoConst { is_packed: true, num_packed_words: 11, unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], }), - (0, 15, PackedInfoConst { + (0, 19, PackedInfoConst { is_packed: true, num_packed_words: 11, unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], }), - (0, 16, PackedInfoConst { + (0, 20, PackedInfoConst { is_packed: true, num_packed_words: 209, unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 8, 40], }), - (0, 17, PackedInfoConst { + (0, 21, PackedInfoConst { is_packed: true, num_packed_words: 3, unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 40, 1, 1], }), - (0, 18, PackedInfoConst { + (0, 22, PackedInfoConst { is_packed: true, num_packed_words: 17, unpack_info: &[1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40], diff --git a/pil/src/pil_helpers/traces_dev.rs b/pil/src/pil_helpers/traces_dev.rs index 3754c7955..a04f6d83f 100644 --- a/pil/src/pil_helpers/traces_dev.rs +++ b/pil/src/pil_helpers/traces_dev.rs @@ -108,7 +108,7 @@ trace!(MainFixed { }, 0, 0, 4194304 ); trace!(MainTrace { - a: [F; 2], b: [F; 2], c: [F; 2], flag: F, pc: F, a_src_imm: F, a_src_mem: F, a_offset_imm0: F, a_imm1: F, a_src_step: F, b_src_imm: F, b_src_mem: F, b_offset_imm0: F, b_imm1: F, b_src_ind: F, ind_width: F, is_external_op: F, op: F, store_ra: F, store_mem: F, store_ind: F, store_offset: F, set_pc: F, jmp_offset1: F, jmp_offset2: F, m32: F, addr1: F, a_reg_prev_mem_step: F, b_reg_prev_mem_step: F, store_reg_prev_mem_step: F, store_reg_prev_value: [F; 2], a_src_reg: F, b_src_reg: F, store_reg: F, + a: [F; 2], b: [F; 2], c: [F; 2], flag: F, pc: F, a_src_imm: F, a_src_mem: F, a_offset_imm0: F, a_imm1: F, a_src_step: F, b_src_imm: F, b_src_mem: F, b_offset_imm0: F, b_imm1: F, b_src_ind: F, ind_width: F, is_external_op: F, op: F, store_pc: F, store_mem: F, store_ind: F, store_offset: F, set_pc: F, jmp_offset1: F, jmp_offset2: F, m32: F, addr1: F, a_reg_prev_mem_step: F, b_reg_prev_mem_step: F, store_reg_prev_mem_step: F, store_reg_prev_value: [F; 2], a_src_reg: F, b_src_reg: F, store_reg: F, }, 0, 0, 4194304 ); trace!(RomFixed { diff --git a/pil/zisk.pil b/pil/zisk.pil index ddb451838..f9eed8b35 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -2,7 +2,9 @@ require "std_direct.pil" require "operations.pil" require "opids.pil" +require "config.pil" +require "dma/pil/dual_range.pil" require "rom/pil/rom.pil" require "main/pil/main.pil" require "mem/pil/mem.pil" @@ -18,25 +20,57 @@ require "arith_eq/pil/arith_eq.pil" require "arith_eq_384/pil/arith_eq_384.pil" require "keccakf/pil/keccakf.pil" require "sha256f/pil/sha256f.pil" +require "dma/pil/dma.pil" +require "dma/pil/dma_rom.pil" +require "dma/pil/dma_pre_post.pil" +require "dma/pil/dma_pre_post_table.pil" +require "dma/pil/dma_64_aligned.pil" +require "dma/pil/dma_unaligned.pil" require "poseidon2/pil/poseidon2.pil" +enable_range_stats(); + proofval enable_input_data; enable_input_data * (1 - enable_input_data); proofval enable_rom_data; enable_rom_data * (1 - enable_rom_data); +proofval enable_dma_64_aligned; +enable_dma_64_aligned * (1 - enable_dma_64_aligned); + +proofval enable_dma_64_aligned_input; +enable_dma_64_aligned_input * (1 - enable_dma_64_aligned_input); + +proofval enable_dma_unaligned; +enable_dma_unaligned * (1 - enable_dma_unaligned); + const int PUBLIC_INPUTS_64_BITS = 32; // 32 x 64 bits = 2048 bits public inputs[PUBLIC_INPUTS_64_BITS * 2]; // 2 x 32-bits = 64 bits +function range_dual_byte(expr byte_a, expr byte_b, expr sel = 1) { + lookup_assumes(DUAL_RANGE_BYTE_ID, expressions: [byte_a, byte_b], sel: sel); +} + airgroup Zisk { // Virtual Tables Configuration set_max_std_tables_bits(20); set_max_num_rows_virtual(1 << 21); // Set the maximum rows for virtual tables set_max_num_virtual_tables(2); - set_group_virtual_tables(table_ids: [ARITH_TABLE_ID, ARITH_RANGE_TABLE_ID, ARITH_EQ_LT_TABLE_ID, BINARY_EXTENSION_TABLE_ID, BINARY_TABLE_ID, MEMORY_ALIGN_ROM_ID, KECCAKF_TABLE_ID]); + set_group_virtual_tables(table_ids: [ARITH_TABLE_ID, ARITH_RANGE_TABLE_ID, ARITH_EQ_LT_TABLE_ID, BINARY_EXTENSION_TABLE_ID, + BINARY_TABLE_ID, MEMORY_ALIGN_ROM_ID, KECCAKF_TABLE_ID, DMA_ROM_ID, DMA_PRE_POST_TABLE_ID]); set_group_virtual_tables(table_ids: [BINARY_EXTENSION_FROPS_TABLE_ID, BINARY_FROPS_TABLE_ID, ARITH_FROPS_TABLE_ID]); + virtual DualRange(id: DUAL_RANGE_7_BITS_ID, min1: 0, max1: P2_7-1, min2: 0, max2: P2_7-1) alias DualRange7Bits; + virtual DualRange(id: DUAL_RANGE_BYTE_ID, min1: 0, max1: P2_8-1, min2: 0, max2: P2_8-1) alias DualByte; + + Dma(); + virtual DmaRom(); + Dma64Aligned(enable: enable_dma_64_aligned); + // Dma64Aligned(enable: enable_dma_64_aligned_input, src_is_free_input: 1) alias Dma64AlignedInput; + DmaUnaligned(enable: enable_dma_unaligned); + DmaPrePost(); + virtual DmaPrePostTable(); // Main Program Main(N: 2**22); Rom(N: 2**22); @@ -51,8 +85,7 @@ airgroup Zisk { MemAlignByte(N: 2**22, read: 1, write: 0) alias MemAlignReadByte; MemAlignByte(N: 2**22, read: 0, write: 1) alias MemAlignWriteByte; virtual MemAlignRom(); - virtual DualByte(); - + // Standard operations Arith(N: 2**21); virtual ArithTable(); @@ -86,6 +119,6 @@ airgroup Zisk { // Public Inputs for (int i = 0; i < PUBLIC_INPUTS_64_BITS; i++) { - direct_global_update_proves(OPERATION_BUS_ID, [OP_PUBOUT, i, 0, inputs[i*2], inputs[i*2 + 1], inputs[i*2], inputs[i*2 + 1], 0]); + direct_global_update_proves(OPERATION_BUS_ID, [OP_PUBOUT, i, 0, inputs[i*2], inputs[i*2 + 1], inputs[i*2], inputs[i*2 + 1], 0, 0]); } } diff --git a/precompiles/arith_eq/pil/arith_eq.pil b/precompiles/arith_eq/pil/arith_eq.pil index d8b36984e..d75ba7b83 100644 --- a/precompiles/arith_eq/pil/arith_eq.pil +++ b/precompiles/arith_eq/pil/arith_eq.pil @@ -615,7 +615,7 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION sel_bn254_complex_sub * OP_COMPLEX_SUB_BN254 + sel_bn254_complex_mul * OP_COMPLEX_MUL_BN254; - lookup_proves(operation_bus_id, [bus_op, step_addr'(MAIN_STEP), 0, step_addr'(ADDR_OP), 0, 0, 0, 0], mul: in_use_clk0); + lookup_proves(operation_bus_id, [bus_op, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(MAIN_STEP)], mul: in_use_clk0); // selclk0 is the clock 0 for dedicated to one operation function expr_group_by_cbc(const expr selclk0, const expr chunks[], const int index ): const expr { diff --git a/precompiles/arith_eq/src/arith_eq_bus_device.rs b/precompiles/arith_eq/src/arith_eq_bus_device.rs index 3774e69f0..b88921012 100644 --- a/precompiles/arith_eq/src/arith_eq_bus_device.rs +++ b/precompiles/arith_eq/src/arith_eq_bus_device.rs @@ -2,11 +2,12 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::ArithEq` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; +use zisk_common::STEP; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OP, OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OP, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; @@ -72,93 +73,47 @@ impl ArithEqCounterInputGen { (op_type == ZiskOperationType::ArithEq).then_some(self.counter.inst_count) } - fn skip_data(&self, data: &[u64], mem_collectors_info: &[MemCollectorInfo]) -> bool { + fn skip_data(&self, data: &[u64], mem_processors: &mut P) -> bool { let addr_main = data[B] as u32; match data[OP] as u8 { - ARITH256_OP => skip_arith256_mem_inputs(addr_main, data, mem_collectors_info), - ARITH256_MOD_OP => skip_arith256_mod_mem_inputs(addr_main, data, mem_collectors_info), - SECP256K1_ADD_OP => skip_secp256k1_add_mem_inputs(addr_main, data, mem_collectors_info), - SECP256K1_DBL_OP => skip_secp256k1_dbl_mem_inputs(addr_main, data, mem_collectors_info), - BN254_CURVE_ADD_OP => { - skip_bn254_curve_add_mem_inputs(addr_main, data, mem_collectors_info) - } - BN254_CURVE_DBL_OP => { - skip_bn254_curve_dbl_mem_inputs(addr_main, data, mem_collectors_info) - } + ARITH256_OP => skip_arith256_mem_inputs(addr_main, data, mem_processors), + ARITH256_MOD_OP => skip_arith256_mod_mem_inputs(addr_main, data, mem_processors), + SECP256K1_ADD_OP => skip_secp256k1_add_mem_inputs(addr_main, data, mem_processors), + SECP256K1_DBL_OP => skip_secp256k1_dbl_mem_inputs(addr_main, data, mem_processors), + BN254_CURVE_ADD_OP => skip_bn254_curve_add_mem_inputs(addr_main, data, mem_processors), + BN254_CURVE_DBL_OP => skip_bn254_curve_dbl_mem_inputs(addr_main, data, mem_processors), BN254_COMPLEX_ADD_OP => { - skip_bn254_complex_add_mem_inputs(addr_main, data, mem_collectors_info) + skip_bn254_complex_add_mem_inputs(addr_main, data, mem_processors) } BN254_COMPLEX_SUB_OP => { - skip_bn254_complex_sub_mem_inputs(addr_main, data, mem_collectors_info) + skip_bn254_complex_sub_mem_inputs(addr_main, data, mem_processors) } BN254_COMPLEX_MUL_OP => { - skip_bn254_complex_mul_mem_inputs(addr_main, data, mem_collectors_info) + skip_bn254_complex_mul_mem_inputs(addr_main, data, mem_processors) } _ => { panic!("ArithEqCounterInputGen: Unsupported data length {}", data.len(),); } } } -} - -impl Metrics for ArithEqCounterInputGen { - /// Tracks activity on the connected bus and updates counters for recognized operations. - /// - /// # Arguments - /// * `_bus_id` - The ID of the bus (unused in this implementation). - /// * `_data` - The data received from the bus. - /// - /// # Returns - /// An empty vector, as this implementation does not produce any derived inputs for the bus. - #[inline(always)] - fn measure(&mut self, _data: &[u64]) { - self.counter.update(1); - } - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl Add for ArithEqCounterInputGen { - type Output = ArithEqCounterInputGen; - - /// Combines two `Arith256Counter` instances by summing their counters. - /// - /// # Arguments - /// * `self` - The first `Arith256Counter` instance. - /// * `other` - The second `Arith256Counter` instance. - /// - /// # Returns - /// A new `Arith256Counter` with combined counters. - fn add(self, other: Self) -> ArithEqCounterInputGen { - ArithEqCounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } - } -} -impl BusDevice for ArithEqCounterInputGen { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments /// * `bus_id` - The ID of the bus sending the data. /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// * `mem_processors` – A collection of memory processors used to send derived inputs. /// /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( + pub fn process_data( &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, + mem_processors: &mut P, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); @@ -168,24 +123,36 @@ impl BusDevice for ArithEqCounterInputGen { return true; } - if let Some(mem_collectors_info) = mem_collector_info { - if self.skip_data(data, mem_collectors_info) { - return true; - } - } - let op = data[OP] as u8; - let step_main = data[A]; + let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); - } + let only_counters = match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + true + } + BusDeviceMode::CounterAsm => { + self.measure(data); + return true; + } + BusDeviceMode::InputGenerator => { + if self.skip_data(data, mem_processors) { + return true; + } + false + } + }; match op { ARITH256_OP => { - generate_arith256_mem_inputs(addr_main, step_main, data, only_counters, pending); + generate_arith256_mem_inputs( + addr_main, + step_main, + data, + only_counters, + mem_processors, + ); } ARITH256_MOD_OP => { generate_arith256_mod_mem_inputs( @@ -193,7 +160,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } SECP256K1_ADD_OP => { @@ -202,7 +169,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } SECP256K1_DBL_OP => { @@ -211,7 +178,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BN254_CURVE_ADD_OP => { @@ -220,7 +187,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BN254_CURVE_DBL_OP => { @@ -229,7 +196,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BN254_COMPLEX_ADD_OP => { @@ -238,7 +205,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BN254_COMPLEX_SUB_OP => { @@ -247,7 +214,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BN254_COMPLEX_MUL_OP => { @@ -256,7 +223,7 @@ impl BusDevice for ArithEqCounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } @@ -267,15 +234,48 @@ impl BusDevice for ArithEqCounterInputGen { true } +} + +impl Metrics for ArithEqCounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. + /// + /// # Returns + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); + } + + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for ArithEqCounterInputGen { + type Output = ArithEqCounterInputGen; - /// Returns the bus IDs associated with this counter. + /// Combines two `Arith256Counter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `Arith256Counter` instance. + /// * `other` - The second `Arith256Counter` instance. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// A new `Arith256Counter` with combined counters. + fn add(self, other: Self) -> ArithEqCounterInputGen { + ArithEqCounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } } +} +impl BusDevice for ArithEqCounterInputGen { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/arith_eq/src/arith_eq_input.rs b/precompiles/arith_eq/src/arith_eq_input.rs index 648f43fb8..ed227896a 100644 --- a/precompiles/arith_eq/src/arith_eq_input.rs +++ b/precompiles/arith_eq/src/arith_eq_input.rs @@ -35,15 +35,15 @@ impl Arith256Input { pub fn from(values: &OperationArith256Data) -> Self { Self { addr: values[3] as u32, - a_addr: values[4] as u32, - b_addr: values[5] as u32, - c_addr: values[6] as u32, - dl_addr: values[7] as u32, - dh_addr: values[8] as u32, - step: values[2], - a: values[9..13].try_into().unwrap(), - b: values[13..17].try_into().unwrap(), - c: values[17..21].try_into().unwrap(), + a_addr: values[5] as u32, + b_addr: values[6] as u32, + c_addr: values[7] as u32, + dl_addr: values[8] as u32, + dh_addr: values[9] as u32, + step: values[4], + a: values[10..14].try_into().unwrap(), + b: values[14..18].try_into().unwrap(), + c: values[18..22].try_into().unwrap(), } } } @@ -67,16 +67,16 @@ impl Arith256ModInput { pub fn from(values: &OperationArith256ModData) -> Self { Self { addr: values[3] as u32, - a_addr: values[4] as u32, - b_addr: values[5] as u32, - c_addr: values[6] as u32, - module_addr: values[7] as u32, - d_addr: values[8] as u32, - step: values[2], - a: values[9..13].try_into().unwrap(), - b: values[13..17].try_into().unwrap(), - c: values[17..21].try_into().unwrap(), - module: values[21..25].try_into().unwrap(), + a_addr: values[5] as u32, + b_addr: values[6] as u32, + c_addr: values[7] as u32, + module_addr: values[8] as u32, + d_addr: values[9] as u32, + step: values[4], + a: values[10..14].try_into().unwrap(), + b: values[14..18].try_into().unwrap(), + c: values[18..22].try_into().unwrap(), + module: values[22..26].try_into().unwrap(), } } } @@ -95,11 +95,11 @@ impl Secp256k1AddInput { pub fn from(values: &OperationSecp256k1AddData) -> Self { Self { addr: values[3] as u32, - p1_addr: values[4] as u32, - p2_addr: values[5] as u32, - step: values[2], - p1: values[6..14].try_into().unwrap(), - p2: values[14..22].try_into().unwrap(), + p1_addr: values[5] as u32, + p2_addr: values[6] as u32, + step: values[4], + p1: values[7..15].try_into().unwrap(), + p2: values[15..23].try_into().unwrap(), } } } @@ -113,7 +113,7 @@ pub struct Secp256k1DblInput { impl Secp256k1DblInput { pub fn from(values: &OperationSecp256k1DblData) -> Self { - Self { addr: values[3] as u32, step: values[2], p1: values[4..12].try_into().unwrap() } + Self { addr: values[3] as u32, step: values[4], p1: values[5..13].try_into().unwrap() } } } @@ -131,11 +131,11 @@ impl Bn254CurveAddInput { pub fn from(values: &OperationBn254CurveAddData) -> Self { Self { addr: values[3] as u32, - p1_addr: values[4] as u32, - p2_addr: values[5] as u32, - step: values[2], - p1: values[6..14].try_into().unwrap(), - p2: values[14..22].try_into().unwrap(), + p1_addr: values[5] as u32, + p2_addr: values[6] as u32, + step: values[4], + p1: values[7..15].try_into().unwrap(), + p2: values[15..23].try_into().unwrap(), } } } @@ -149,7 +149,7 @@ pub struct Bn254CurveDblInput { impl Bn254CurveDblInput { pub fn from(values: &OperationBn254CurveDblData) -> Self { - Self { addr: values[3] as u32, step: values[2], p1: values[4..12].try_into().unwrap() } + Self { addr: values[3] as u32, step: values[4], p1: values[5..13].try_into().unwrap() } } } @@ -167,11 +167,11 @@ impl Bn254ComplexAddInput { pub fn from(values: &OperationBn254ComplexAddData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..14].try_into().unwrap(), - f2: values[14..22].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..15].try_into().unwrap(), + f2: values[15..23].try_into().unwrap(), } } } @@ -190,11 +190,11 @@ impl Bn254ComplexSubInput { pub fn from(values: &OperationBn254ComplexSubData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..14].try_into().unwrap(), - f2: values[14..22].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..15].try_into().unwrap(), + f2: values[15..23].try_into().unwrap(), } } } @@ -213,11 +213,11 @@ impl Bn254ComplexMulInput { pub fn from(values: &OperationBn254ComplexMulData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..14].try_into().unwrap(), - f2: values[14..22].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..15].try_into().unwrap(), + f2: values[15..23].try_into().unwrap(), } } } diff --git a/precompiles/arith_eq/src/arith_eq_instance.rs b/precompiles/arith_eq/src/arith_eq_instance.rs index 6d15e9800..9226085ad 100644 --- a/precompiles/arith_eq/src/arith_eq_instance.rs +++ b/precompiles/arith_eq/src/arith_eq_instance.rs @@ -11,12 +11,11 @@ use crate::{ }; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::collections::VecDeque; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, OperationBusData, PayloadType, OPERATION_BUS_ID, + InstanceType, OperationBusData, PayloadType, OPERATION_BUS_ID, }; use zisk_core::ZiskOperationType; @@ -159,9 +158,7 @@ impl ArithEqCollector { collect_skipper, } } -} -impl BusDevice for ArithEqCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -173,13 +170,7 @@ impl BusDevice for ArithEqCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -231,15 +222,9 @@ impl BusDevice for ArithEqCollector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for ArithEqCollector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/arith_eq/src/arith_eq_manager.rs b/precompiles/arith_eq/src/arith_eq_manager.rs index 2d8835a68..86bc95250 100644 --- a/precompiles/arith_eq/src/arith_eq_manager.rs +++ b/precompiles/arith_eq/src/arith_eq_manager.rs @@ -2,11 +2,8 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{BusDevice, PayloadType}; -use zisk_common::{ - BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::ArithEqTrace; @@ -31,8 +28,11 @@ impl ArithEqManager { Arc::new(Self { arith_eq_sm }) } - pub fn build_arith_eq_counter(&self) -> ArithEqCounterInputGen { - ArithEqCounterInputGen::new(BusDeviceMode::Counter) + pub fn build_arith_eq_counter(&self, asm_execution: bool) -> ArithEqCounterInputGen { + match asm_execution { + true => ArithEqCounterInputGen::new(BusDeviceMode::CounterAsm), + false => ArithEqCounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_arith_eq_input_generator(&self) -> ArithEqCounterInputGen { @@ -41,14 +41,6 @@ impl ArithEqManager { } impl ComponentBuilder for ArithEqManager { - /// Builds and returns a new counter for monitoring arith256 operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for arith256 operations. - fn build_counter(&self) -> Option> { - Some(Box::new(ArithEqCounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan arith256-related instances. /// /// # Returns @@ -86,8 +78,4 @@ impl ComponentBuilder for ArithEqManager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(ArithEqCounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/precompiles/arith_eq/src/mem_inputs/arith256.rs b/precompiles/arith_eq/src/mem_inputs/arith256.rs index dc6dc5084..e47286031 100644 --- a/precompiles/arith_eq/src/mem_inputs/arith256.rs +++ b/precompiles/arith_eq/src/mem_inputs/arith256.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Arith256; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const ARITH_256_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 5, @@ -12,17 +10,17 @@ pub const ARITH_256_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { chunks_per_param: 4, }; -pub fn generate_arith256_mem_inputs( +pub fn generate_arith256_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[5],... - let a: &[u64; 4] = &data[9..13].try_into().unwrap(); - let b: &[u64; 4] = &data[13..17].try_into().unwrap(); - let c: &[u64; 4] = &data[17..21].try_into().unwrap(); + let a: &[u64; 4] = &data[10..14].try_into().unwrap(); + let b: &[u64; 4] = &data[14..18].try_into().unwrap(); + let c: &[u64; 4] = &data[18..22].try_into().unwrap(); // let mut dh = [0u64; 4]; // let mut dl = [0u64; 4]; let mut d: [u64; 8] = [0u64; 8]; @@ -38,15 +36,15 @@ pub fn generate_arith256_mem_inputs( data, Some(&d), only_counters, - pending, + mem_processors, &ARITH_256_MEM_CONFIG, ); } -pub fn skip_arith256_mem_inputs( +pub fn skip_arith256_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &ARITH_256_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &ARITH_256_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs b/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs index a8c0b8d45..04aa76153 100644 --- a/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs +++ b/precompiles/arith_eq/src/mem_inputs/arith256_mod.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Arith256Mod; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const ARITH_256_MOD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 5, @@ -11,18 +9,18 @@ pub const ARITH_256_MOD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfi write_params: 1, chunks_per_param: 4, }; -pub fn generate_arith256_mod_mem_inputs( +pub fn generate_arith256_mod_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[5],... - let a: &[u64; 4] = &data[9..13].try_into().unwrap(); - let b: &[u64; 4] = &data[13..17].try_into().unwrap(); - let c: &[u64; 4] = &data[17..21].try_into().unwrap(); - let module: &[u64; 4] = &data[21..25].try_into().unwrap(); + let a: &[u64; 4] = &data[10..14].try_into().unwrap(); + let b: &[u64; 4] = &data[14..18].try_into().unwrap(); + let c: &[u64; 4] = &data[18..22].try_into().unwrap(); + let module: &[u64; 4] = &data[22..26].try_into().unwrap(); let mut d: [u64; 4] = [0u64; 4]; Arith256Mod::calculate(a, b, c, module, &mut d); @@ -32,15 +30,15 @@ pub fn generate_arith256_mod_mem_inputs( data, Some(&d), only_counters, - pending, + mem_processors, &ARITH_256_MOD_MEM_CONFIG, ); } -pub fn skip_arith256_mod_mem_inputs( +pub fn skip_arith256_mod_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &ARITH_256_MOD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &ARITH_256_MOD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs index fca141932..3e4e97826 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_add.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Bn254Complex; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const BN254_COMPLEX_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 2, @@ -12,16 +10,16 @@ pub const BN254_COMPLEX_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputC chunks_per_param: 8, }; -pub fn generate_bn254_complex_add_mem_inputs( +pub fn generate_bn254_complex_add_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... - let f1: &[u64; 8] = &data[6..14].try_into().unwrap(); - let f2: &[u64; 8] = &data[14..22].try_into().unwrap(); + let f1: &[u64; 8] = &data[7..15].try_into().unwrap(); + let f2: &[u64; 8] = &data[15..23].try_into().unwrap(); let mut f3 = [0u64; 8]; Bn254Complex::calculate_add(f1, f2, &mut f3); @@ -31,15 +29,15 @@ pub fn generate_bn254_complex_add_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BN254_COMPLEX_ADD_MEM_CONFIG, ); } -pub fn skip_bn254_complex_add_mem_inputs( +pub fn skip_bn254_complex_add_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_ADD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_ADD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs index 6c70af05b..4ee73bc18 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_mul.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Bn254Complex; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const BN254_COMPLEX_MUL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 2, @@ -12,16 +10,16 @@ pub const BN254_COMPLEX_MUL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputC chunks_per_param: 8, }; -pub fn generate_bn254_complex_mul_mem_inputs( +pub fn generate_bn254_complex_mul_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... - let f1: &[u64; 8] = &data[6..14].try_into().unwrap(); - let f2: &[u64; 8] = &data[14..22].try_into().unwrap(); + let f1: &[u64; 8] = &data[7..15].try_into().unwrap(); + let f2: &[u64; 8] = &data[15..23].try_into().unwrap(); let mut f3 = [0u64; 8]; Bn254Complex::calculate_mul(f1, f2, &mut f3); @@ -31,15 +29,15 @@ pub fn generate_bn254_complex_mul_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BN254_COMPLEX_MUL_MEM_CONFIG, ); } -pub fn skip_bn254_complex_mul_mem_inputs( +pub fn skip_bn254_complex_mul_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_MUL_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_MUL_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs b/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs index df7d18f56..11f1e7573 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_complex_sub.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Bn254Complex; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const BN254_COMPLEX_SUB_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 2, @@ -12,16 +10,16 @@ pub const BN254_COMPLEX_SUB_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputC chunks_per_param: 8, }; -pub fn generate_bn254_complex_sub_mem_inputs( +pub fn generate_bn254_complex_sub_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... - let f1: &[u64; 8] = &data[6..14].try_into().unwrap(); - let f2: &[u64; 8] = &data[14..22].try_into().unwrap(); + let f1: &[u64; 8] = &data[7..15].try_into().unwrap(); + let f2: &[u64; 8] = &data[15..23].try_into().unwrap(); let mut f3 = [0u64; 8]; Bn254Complex::calculate_sub(f1, f2, &mut f3); @@ -31,15 +29,15 @@ pub fn generate_bn254_complex_sub_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BN254_COMPLEX_SUB_MEM_CONFIG, ); } -pub fn skip_bn254_complex_sub_mem_inputs( +pub fn skip_bn254_complex_sub_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_SUB_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BN254_COMPLEX_SUB_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs b/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs index e160843ee..b43bc2828 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_curve_add.rs @@ -1,8 +1,8 @@ use super::ArithEqMemInputConfig; use crate::executors::Bn254Curve; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; + +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; pub const BN254_CURVE_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 2, @@ -12,16 +12,20 @@ pub const BN254_CURVE_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputCon chunks_per_param: 8, }; -pub fn generate_bn254_curve_add_mem_inputs( +pub fn generate_bn254_curve_add_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... - let p1: &[u64; 8] = &data[6..14].try_into().unwrap(); - let p2: &[u64; 8] = &data[14..22].try_into().unwrap(); + let p1_start = OPERATION_PRECOMPILED_BUS_DATA_SIZE + BN254_CURVE_ADD_MEM_CONFIG.indirect_params; + let p1: &[u64; 8] = + &data[p1_start..p1_start + BN254_CURVE_ADD_MEM_CONFIG.chunks_per_param].try_into().unwrap(); + let p2_start = p1_start + BN254_CURVE_ADD_MEM_CONFIG.chunks_per_param; + let p2: &[u64; 8] = + &data[p2_start..p2_start + BN254_CURVE_ADD_MEM_CONFIG.chunks_per_param].try_into().unwrap(); let mut p3 = [0u64; 8]; Bn254Curve::calculate_add(p1, p2, &mut p3); @@ -31,15 +35,15 @@ pub fn generate_bn254_curve_add_mem_inputs( data, Some(&p3), only_counters, - pending, + mem_processors, &BN254_CURVE_ADD_MEM_CONFIG, ); } -pub fn skip_bn254_curve_add_mem_inputs( +pub fn skip_bn254_curve_add_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BN254_CURVE_ADD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BN254_CURVE_ADD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs b/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs index 1c683f1f7..0273c3aa0 100644 --- a/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs +++ b/precompiles/arith_eq/src/mem_inputs/bn254_curve_dbl.rs @@ -1,8 +1,8 @@ use super::ArithEqMemInputConfig; use crate::executors::Bn254Curve; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; + +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; pub const BN254_CURVE_DBL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 0, @@ -12,15 +12,18 @@ pub const BN254_CURVE_DBL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputCon chunks_per_param: 8, }; -pub fn generate_bn254_curve_dbl_mem_inputs( +pub fn generate_bn254_curve_dbl_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... - let p1: &[u64; 8] = &data[4..12].try_into().unwrap(); + let p1: &[u64; 8] = &data + [OPERATION_PRECOMPILED_BUS_DATA_SIZE..OPERATION_PRECOMPILED_BUS_DATA_SIZE + 8] + .try_into() + .unwrap(); let mut p3 = [0u64; 8]; Bn254Curve::calculate_dbl(p1, &mut p3); @@ -30,15 +33,15 @@ pub fn generate_bn254_curve_dbl_mem_inputs( data, Some(&p3), only_counters, - pending, + mem_processors, &BN254_CURVE_DBL_MEM_CONFIG, ); } -pub fn skip_bn254_curve_dbl_mem_inputs( +pub fn skip_bn254_curve_dbl_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BN254_CURVE_DBL_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BN254_CURVE_DBL_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs b/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs index 2b1b7d943..480ea57f8 100644 --- a/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs +++ b/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs @@ -1,7 +1,7 @@ use precompiles_common::MemBusHelpers; -use std::collections::VecDeque; -use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; +use precompiles_common::MemProcessor; + +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; #[derive(Debug)] pub struct ArithEqMemInputConfig { @@ -11,24 +11,24 @@ pub struct ArithEqMemInputConfig { pub write_params: usize, pub chunks_per_param: usize, } -pub fn generate_mem_inputs( +pub fn generate_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], write_data: Option<&[u64]>, only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, config: &ArithEqMemInputConfig, ) { let params_count = config.read_params + config.write_params; - let params_offset = OPERATION_BUS_DATA_SIZE + config.indirect_params; + let params_offset = OPERATION_PRECOMPILED_BUS_DATA_SIZE + config.indirect_params; for iparam in 0..config.indirect_params { MemBusHelpers::mem_aligned_load( addr_main + iparam as u32 * 8, step_main, - data[OPERATION_BUS_DATA_SIZE + iparam], - pending, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], + mem_processors, ) } for iparam in 0..params_count { @@ -39,7 +39,7 @@ pub fn generate_mem_inputs( }; let param_addr = if config.indirect_params > 0 { // read indirect parameters, means stored the address of parameter - data[OPERATION_BUS_DATA_SIZE + param_index] as u32 + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32 } else { addr_main + (param_index * 8 * config.chunks_per_param) as u32 }; @@ -66,27 +66,25 @@ pub fn generate_mem_inputs( step_main, chunk_data, is_write, - pending, + mem_processors, ) } } } -pub fn skip_mem_inputs( +pub fn skip_mem_inputs( addr_main: u32, data: &[u64], config: &ArithEqMemInputConfig, - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { let params_count = config.read_params + config.write_params; // Check indirect loads for iparam in 0..config.indirect_params { let addr = addr_main + iparam as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } @@ -97,16 +95,14 @@ pub fn skip_mem_inputs( iparam }; let param_addr = if config.indirect_params > 0 { - data[OPERATION_BUS_DATA_SIZE + param_index] as u32 + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32 } else { addr_main + (param_index * 8 * config.chunks_per_param) as u32 }; for ichunk in 0..config.chunks_per_param { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } diff --git a/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs b/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs index 0c08a0ba0..bdd2ff7aa 100644 --- a/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs +++ b/precompiles/arith_eq/src/mem_inputs/secp256k1_add.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Secp256k1; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const SECP256K1_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 2, @@ -12,16 +10,16 @@ pub const SECP256K1_ADD_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfi chunks_per_param: 8, }; -pub fn generate_secp256k1_add_mem_inputs( +pub fn generate_secp256k1_add_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // op,op_type,a,b,addr[2],... - let p1: &[u64; 8] = &data[6..14].try_into().unwrap(); - let p2: &[u64; 8] = &data[14..22].try_into().unwrap(); + let p1: &[u64; 8] = &data[7..15].try_into().unwrap(); + let p2: &[u64; 8] = &data[15..23].try_into().unwrap(); let mut p3 = [0u64; 8]; Secp256k1::calculate_add(p1, p2, &mut p3); @@ -31,15 +29,15 @@ pub fn generate_secp256k1_add_mem_inputs( data, Some(&p3), only_counters, - pending, + mem_processors, &SECP256K1_ADD_MEM_CONFIG, ); } -pub fn skip_secp256k1_add_mem_inputs( +pub fn skip_secp256k1_add_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &SECP256K1_ADD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &SECP256K1_ADD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs b/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs index b86bf868a..451e4e065 100644 --- a/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs +++ b/precompiles/arith_eq/src/mem_inputs/secp256k1_dbl.rs @@ -1,8 +1,6 @@ use super::ArithEqMemInputConfig; use crate::executors::Secp256k1; -use std::collections::VecDeque; -use zisk_common::BusId; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; pub const SECP256K1_DBL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfig { indirect_params: 0, @@ -12,15 +10,15 @@ pub const SECP256K1_DBL_MEM_CONFIG: ArithEqMemInputConfig = ArithEqMemInputConfi chunks_per_param: 8, }; -pub fn generate_secp256k1_dbl_mem_inputs( +pub fn generate_secp256k1_dbl_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + processor: &mut P, ) { // op,op_type,a,b,... - let p1: &[u64; 8] = &data[4..12].try_into().unwrap(); + let p1: &[u64; 8] = &data[5..13].try_into().unwrap(); let mut p3 = [0u64; 8]; Secp256k1::calculate_dbl(p1, &mut p3); @@ -30,15 +28,15 @@ pub fn generate_secp256k1_dbl_mem_inputs( data, Some(&p3), only_counters, - pending, + processor, &SECP256K1_DBL_MEM_CONFIG, ); } -pub fn skip_secp256k1_dbl_mem_inputs( +pub fn skip_secp256k1_dbl_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &SECP256K1_DBL_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &SECP256K1_DBL_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/pil/arith_eq_384.pil b/precompiles/arith_eq_384/pil/arith_eq_384.pil index 7a621320c..3a561fe77 100644 --- a/precompiles/arith_eq_384/pil/arith_eq_384.pil +++ b/precompiles/arith_eq_384/pil/arith_eq_384.pil @@ -567,7 +567,7 @@ airtemplate ArithEq384(const int N, const int operation_bus_id = OPERATION_BUS_I sel_bls12_381_complex_sub * OP_COMPLEX_SUB_BLS12_381 + sel_bls12_381_complex_mul * OP_COMPLEX_MUL_BLS12_381; - lookup_proves(operation_bus_id, [bus_op, step_addr'(MAIN_STEP), 0, step_addr'(ADDR_OP), 0, 0, 0, 0], mul: in_use_clk0); + lookup_proves(operation_bus_id, [bus_op, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(MAIN_STEP)], mul: in_use_clk0); // selclk0 is the clock 0 for dedicated to one operation function expr_group_by_cbc(const expr selclk0, const expr chunks[], const int index): const expr { diff --git a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs index 5f06e8f25..c30fc1293 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_bus_device.rs @@ -2,11 +2,11 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::ArithEq384` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; +use precompiles_common::MemProcessor; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, MemCollectorInfo, Metrics, A, B, OP, - OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OP, OPERATION_BUS_ID, OP_TYPE, STEP, }; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; @@ -63,90 +63,48 @@ impl ArithEq384CounterInputGen { (op_type == ZiskOperationType::ArithEq384).then_some(self.counter.inst_count) } - fn skip_data(&self, data: &[u64], mem_collectors_info: &[MemCollectorInfo]) -> bool { + fn skip_data(&self, data: &[u64], mem_processors: &mut P) -> bool { let addr_main = data[B] as u32; match data[OP] as u8 { - ARITH384_MOD_OP => skip_arith384_mod_mem_inputs(addr_main, data, mem_collectors_info), + ARITH384_MOD_OP => skip_arith384_mod_mem_inputs(addr_main, data, mem_processors), BLS12_381_CURVE_ADD_OP => { - skip_bls12_381_curve_add_mem_inputs(addr_main, data, mem_collectors_info) + skip_bls12_381_curve_add_mem_inputs(addr_main, data, mem_processors) } BLS12_381_CURVE_DBL_OP => { - skip_bls12_381_curve_dbl_mem_inputs(addr_main, data, mem_collectors_info) + skip_bls12_381_curve_dbl_mem_inputs(addr_main, data, mem_processors) } BLS12_381_COMPLEX_ADD_OP => { - skip_bls12_381_complex_add_mem_inputs(addr_main, data, mem_collectors_info) + skip_bls12_381_complex_add_mem_inputs(addr_main, data, mem_processors) } BLS12_381_COMPLEX_SUB_OP => { - skip_bls12_381_complex_sub_mem_inputs(addr_main, data, mem_collectors_info) + skip_bls12_381_complex_sub_mem_inputs(addr_main, data, mem_processors) } BLS12_381_COMPLEX_MUL_OP => { - skip_bls12_381_complex_mul_mem_inputs(addr_main, data, mem_collectors_info) + skip_bls12_381_complex_mul_mem_inputs(addr_main, data, mem_processors) } _ => { panic!("ArithEq384CounterInputGen: Unsupported data length {}", data.len()); } } } -} - -impl Metrics for ArithEq384CounterInputGen { - /// Tracks activity on the connected bus and updates counters for recognized operations. - /// - /// # Arguments - /// * `_bus_id` - The ID of the bus (unused in this implementation). - /// * `_data` - The data received from the bus. - /// - /// # Returns - /// An empty vector, as this implementation does not produce any derived inputs for the bus. - #[inline(always)] - fn measure(&mut self, _data: &[u64]) { - self.counter.update(1); - } - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl Add for ArithEq384CounterInputGen { - type Output = ArithEq384CounterInputGen; - - /// Combines two `Arith384Counter` instances by summing their counters. - /// - /// # Arguments - /// * `self` - The first `Arith384Counter` instance. - /// * `other` - The second `Arith384Counter` instance. - /// - /// # Returns - /// A new `Arith384Counter` with combined counters. - fn add(self, other: Self) -> ArithEq384CounterInputGen { - ArithEq384CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } - } -} -impl BusDevice for ArithEq384CounterInputGen { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments /// * `bus_id` - The ID of the bus sending the data. /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. /// /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( + pub fn process_data( &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, + mem_processors: &mut P, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); @@ -156,20 +114,26 @@ impl BusDevice for ArithEq384CounterInputGen { return true; } - if let Some(mem_collectors_info) = mem_collector_info { - if self.skip_data(data, mem_collectors_info) { - return true; - } - } - let op = data[OP] as u8; - let step_main = data[A]; + let step_main = data[STEP]; let addr_main = data[B] as u32; - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); - } + let only_counters = match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + true + } + BusDeviceMode::CounterAsm => { + self.measure(data); + return true; + } + BusDeviceMode::InputGenerator => { + if self.skip_data(data, mem_processors) { + return true; + } + false + } + }; match op { ARITH384_MOD_OP => { @@ -178,7 +142,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BLS12_381_CURVE_ADD_OP => { @@ -187,7 +151,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BLS12_381_CURVE_DBL_OP => { @@ -196,7 +160,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BLS12_381_COMPLEX_ADD_OP => { @@ -205,7 +169,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BLS12_381_COMPLEX_SUB_OP => { @@ -214,7 +178,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } BLS12_381_COMPLEX_MUL_OP => { @@ -223,7 +187,7 @@ impl BusDevice for ArithEq384CounterInputGen { step_main, data, only_counters, - pending, + mem_processors, ); } _ => { @@ -233,15 +197,48 @@ impl BusDevice for ArithEq384CounterInputGen { true } +} - /// Returns the bus IDs associated with this counter. +impl Metrics for ArithEq384CounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); } + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for ArithEq384CounterInputGen { + type Output = ArithEq384CounterInputGen; + + /// Combines two `Arith384Counter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `Arith384Counter` instance. + /// * `other` - The second `Arith384Counter` instance. + /// + /// # Returns + /// A new `Arith384Counter` with combined counters. + fn add(self, other: Self) -> ArithEq384CounterInputGen { + ArithEq384CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } + } +} + +impl BusDevice for ArithEq384CounterInputGen { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/arith_eq_384/src/arith_eq_384_input.rs b/precompiles/arith_eq_384/src/arith_eq_384_input.rs index c5f8dbfbc..ba3ce8a59 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_input.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_input.rs @@ -35,16 +35,16 @@ impl Arith384ModInput { pub fn from(values: &OperationArith384ModData) -> Self { Self { addr: values[3] as u32, - a_addr: values[4] as u32, - b_addr: values[5] as u32, - c_addr: values[6] as u32, - module_addr: values[7] as u32, - d_addr: values[8] as u32, - step: values[2], - a: values[9..15].try_into().unwrap(), - b: values[15..21].try_into().unwrap(), - c: values[21..27].try_into().unwrap(), - module: values[27..33].try_into().unwrap(), + a_addr: values[5] as u32, + b_addr: values[6] as u32, + c_addr: values[7] as u32, + module_addr: values[8] as u32, + d_addr: values[9] as u32, + step: values[4], + a: values[10..16].try_into().unwrap(), + b: values[16..22].try_into().unwrap(), + c: values[22..28].try_into().unwrap(), + module: values[28..34].try_into().unwrap(), } } } @@ -63,11 +63,11 @@ impl Bls12_381CurveAddInput { pub fn from(values: &OperationBls12_381CurveAddData) -> Self { Self { addr: values[3] as u32, - p1_addr: values[4] as u32, - p2_addr: values[5] as u32, - step: values[2], - p1: values[6..18].try_into().unwrap(), - p2: values[18..30].try_into().unwrap(), + p1_addr: values[5] as u32, + p2_addr: values[6] as u32, + step: values[4], + p1: values[7..19].try_into().unwrap(), + p2: values[19..31].try_into().unwrap(), } } } @@ -81,7 +81,7 @@ pub struct Bls12_381CurveDblInput { impl Bls12_381CurveDblInput { pub fn from(values: &OperationBls12_381CurveDblData) -> Self { - Self { addr: values[3] as u32, step: values[2], p1: values[4..16].try_into().unwrap() } + Self { addr: values[3] as u32, step: values[4], p1: values[5..17].try_into().unwrap() } } } @@ -99,11 +99,11 @@ impl Bls12_381ComplexAddInput { pub fn from(values: &OperationBls12_381ComplexAddData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..18].try_into().unwrap(), - f2: values[18..30].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..19].try_into().unwrap(), + f2: values[19..31].try_into().unwrap(), } } } @@ -122,11 +122,11 @@ impl Bls12_381ComplexSubInput { pub fn from(values: &OperationBls12_381ComplexSubData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..18].try_into().unwrap(), - f2: values[18..30].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..19].try_into().unwrap(), + f2: values[19..31].try_into().unwrap(), } } } @@ -145,11 +145,11 @@ impl Bls12_381ComplexMulInput { pub fn from(values: &OperationBls12_381ComplexMulData) -> Self { Self { addr: values[3] as u32, - f1_addr: values[4] as u32, - f2_addr: values[5] as u32, - step: values[2], - f1: values[6..18].try_into().unwrap(), - f2: values[18..30].try_into().unwrap(), + f1_addr: values[5] as u32, + f2_addr: values[6] as u32, + step: values[4], + f1: values[7..19].try_into().unwrap(), + f2: values[19..31].try_into().unwrap(), } } } diff --git a/precompiles/arith_eq_384/src/arith_eq_384_instance.rs b/precompiles/arith_eq_384/src/arith_eq_384_instance.rs index 81ebc371e..be5aa91e1 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_instance.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_instance.rs @@ -6,14 +6,13 @@ use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::collections::VecDeque; use std::{any::Any, collections::HashMap, sync::Arc}; +use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, InstanceType, OperationBusData, PayloadType, OPERATION_BUS_ID, }; -use zisk_common::{ChunkId, MemCollectorInfo}; use zisk_core::ZiskOperationType; use zisk_pil::ArithEq384Trace; @@ -158,9 +157,7 @@ impl ArithEq384Collector { pub fn new(num_operations: u64, collect_skipper: CollectSkipper) -> Self { Self { inputs: Vec::new(), num_operations, collect_skipper } } -} -impl BusDevice for ArithEq384Collector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -171,13 +168,7 @@ impl BusDevice for ArithEq384Collector { /// # Returns /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -220,15 +211,9 @@ impl BusDevice for ArithEq384Collector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for ArithEq384Collector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/arith_eq_384/src/arith_eq_384_manager.rs b/precompiles/arith_eq_384/src/arith_eq_384_manager.rs index 033c8d158..7d79ea4d4 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_manager.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_manager.rs @@ -2,11 +2,8 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{BusDevice, PayloadType}; -use zisk_common::{ - BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::ArithEq384Trace; @@ -31,8 +28,11 @@ impl ArithEq384Manager { Arc::new(Self { arith_eq_384_sm }) } - pub fn build_arith_eq_384_counter(&self) -> ArithEq384CounterInputGen { - ArithEq384CounterInputGen::new(BusDeviceMode::Counter) + pub fn build_arith_eq_384_counter(&self, asm_execution: bool) -> ArithEq384CounterInputGen { + match asm_execution { + true => ArithEq384CounterInputGen::new(BusDeviceMode::CounterAsm), + false => ArithEq384CounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_arith_eq_384_input_generator(&self) -> ArithEq384CounterInputGen { @@ -41,14 +41,6 @@ impl ArithEq384Manager { } impl ComponentBuilder for ArithEq384Manager { - /// Builds and returns a new counter for monitoring arith256 operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for arith256 operations. - fn build_counter(&self) -> Option> { - Some(Box::new(ArithEq384CounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan arith256-related instances. /// /// # Returns @@ -89,8 +81,4 @@ impl ComponentBuilder for ArithEq384Manager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(ArithEq384CounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs b/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs index a73fa3d93..e7e4b8f3a 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/arith384_mod.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Arith384Mod, ARITH_EQ_384_U64S}; @@ -12,14 +11,14 @@ pub const ARITH_384_MOD_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq384MemInpu chunks_per_param: ARITH_EQ_384_U64S, }; -pub fn generate_arith384_mod_mem_inputs( +pub fn generate_arith384_mod_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { - let mut pos_offset: usize = 9; // op,op_type,a,b,addr[5],... + let mut pos_offset: usize = 10; // op,op_type,a,b,addr[5],... let a: &[u64; ARITH_EQ_384_U64S] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S)].try_into().unwrap(); pos_offset += ARITH_EQ_384_U64S; @@ -40,15 +39,15 @@ pub fn generate_arith384_mod_mem_inputs( data, Some(&d), only_counters, - pending, + mem_processors, &ARITH_384_MOD_MEM_CONFIG, ); } -pub fn skip_arith384_mod_mem_inputs( +pub fn skip_arith384_mod_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &ARITH_384_MOD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &ARITH_384_MOD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs index c10a6e3c7..d2c88f709 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_add.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Bls12_381Complex, ARITH_EQ_384_U64S_DOUBLE}; @@ -12,14 +11,14 @@ pub const BLS12_381_COMPLEX_ADD_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq38 chunks_per_param: ARITH_EQ_384_U64S_DOUBLE, }; -pub fn generate_bls12_381_complex_add_mem_inputs( +pub fn generate_bls12_381_complex_add_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { - let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... + let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S_DOUBLE)].try_into().unwrap(); pos_offset += ARITH_EQ_384_U64S_DOUBLE; @@ -34,15 +33,15 @@ pub fn generate_bls12_381_complex_add_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BLS12_381_COMPLEX_ADD_MEM_CONFIG, ); } -pub fn skip_bls12_381_complex_add_mem_inputs( +pub fn skip_bls12_381_complex_add_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_ADD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_ADD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs index fec7de46d..bd444258c 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_mul.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Bls12_381Complex, ARITH_EQ_384_U64S_DOUBLE}; @@ -12,14 +11,14 @@ pub const BLS12_381_COMPLEX_MUL_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq38 chunks_per_param: ARITH_EQ_384_U64S_DOUBLE, }; -pub fn generate_bls12_381_complex_mul_mem_inputs( +pub fn generate_bls12_381_complex_mul_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { - let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... + let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S_DOUBLE)].try_into().unwrap(); pos_offset += ARITH_EQ_384_U64S_DOUBLE; @@ -34,15 +33,15 @@ pub fn generate_bls12_381_complex_mul_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BLS12_381_COMPLEX_MUL_MEM_CONFIG, ); } -pub fn skip_bls12_381_complex_mul_mem_inputs( +pub fn skip_bls12_381_complex_mul_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_MUL_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_MUL_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs index 5d15fbc29..d3e7b4fe4 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_complex_sub.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Bls12_381Complex, ARITH_EQ_384_U64S_DOUBLE}; @@ -12,14 +11,14 @@ pub const BLS12_381_COMPLEX_SUB_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq38 chunks_per_param: ARITH_EQ_384_U64S_DOUBLE, }; -pub fn generate_bls12_381_complex_sub_mem_inputs( +pub fn generate_bls12_381_complex_sub_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { - let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... + let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let f1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S_DOUBLE)].try_into().unwrap(); pos_offset += ARITH_EQ_384_U64S_DOUBLE; @@ -34,15 +33,15 @@ pub fn generate_bls12_381_complex_sub_mem_inputs( data, Some(&f3), only_counters, - pending, + mem_processors, &BLS12_381_COMPLEX_SUB_MEM_CONFIG, ); } -pub fn skip_bls12_381_complex_sub_mem_inputs( +pub fn skip_bls12_381_complex_sub_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_SUB_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BLS12_381_COMPLEX_SUB_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs index 983b3c7f6..9fa1ece0a 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_add.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Bls12_381Curve, ARITH_EQ_384_U64S_DOUBLE}; @@ -12,14 +11,14 @@ pub const BLS12_381_CURVE_ADD_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq384M chunks_per_param: ARITH_EQ_384_U64S_DOUBLE, }; -pub fn generate_bls12_381_curve_add_mem_inputs( +pub fn generate_bls12_381_curve_add_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { - let mut pos_offset: usize = 6; // op,op_type,a,b,addr[2],... + let mut pos_offset: usize = 7; // op,op_type,a,b,addr[2],... let p1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S_DOUBLE)].try_into().unwrap(); pos_offset += ARITH_EQ_384_U64S_DOUBLE; @@ -34,15 +33,15 @@ pub fn generate_bls12_381_curve_add_mem_inputs( data, Some(&p3), only_counters, - pending, + mem_processors, &BLS12_381_CURVE_ADD_MEM_CONFIG, ); } -pub fn skip_bls12_381_curve_add_mem_inputs( +pub fn skip_bls12_381_curve_add_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BLS12_381_CURVE_ADD_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BLS12_381_CURVE_ADD_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs index b61f8da15..d2ee68ce0 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/bls12_381_curve_dbl.rs @@ -1,5 +1,4 @@ -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo}; +use precompiles_common::MemProcessor; use super::ArithEq384MemInputConfig; use crate::{executors::Bls12_381Curve, ARITH_EQ_384_U64S_DOUBLE}; @@ -12,14 +11,14 @@ pub const BLS12_381_CURVE_DBL_MEM_CONFIG: ArithEq384MemInputConfig = ArithEq384M chunks_per_param: ARITH_EQ_384_U64S_DOUBLE, }; -pub fn generate_bls12_381_curve_dbl_mem_inputs( +pub fn generate_bls12_381_curve_dbl_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { - let pos_offset: usize = 4; // op,op_type,a,b,... + let pos_offset: usize = 5; // op,op_type,a,b,... let p1: &[u64; ARITH_EQ_384_U64S_DOUBLE] = &data[pos_offset..(pos_offset + ARITH_EQ_384_U64S_DOUBLE)].try_into().unwrap(); let mut p3 = [0u64; ARITH_EQ_384_U64S_DOUBLE]; @@ -31,15 +30,15 @@ pub fn generate_bls12_381_curve_dbl_mem_inputs( data, Some(&p3), only_counters, - pending, + mem_processors, &BLS12_381_CURVE_DBL_MEM_CONFIG, ); } -pub fn skip_bls12_381_curve_dbl_mem_inputs( +pub fn skip_bls12_381_curve_dbl_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { - super::skip_mem_inputs(addr_main, data, &BLS12_381_CURVE_DBL_MEM_CONFIG, mem_collectors_info) + super::skip_mem_inputs(addr_main, data, &BLS12_381_CURVE_DBL_MEM_CONFIG, mem_processors) } diff --git a/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs b/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs index 2b1602d29..4d890e408 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs @@ -1,6 +1,6 @@ use precompiles_common::MemBusHelpers; -use std::collections::VecDeque; -use zisk_common::{BusId, MemCollectorInfo, OPERATION_BUS_DATA_SIZE}; +use precompiles_common::MemProcessor; +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; #[derive(Debug)] pub struct ArithEq384MemInputConfig { @@ -10,24 +10,24 @@ pub struct ArithEq384MemInputConfig { pub write_params: usize, pub chunks_per_param: usize, } -pub fn generate_mem_inputs( +pub fn generate_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], write_data: Option<&[u64]>, only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, config: &ArithEq384MemInputConfig, ) { let params_count = config.read_params + config.write_params; - let params_offset = OPERATION_BUS_DATA_SIZE + config.indirect_params; + let params_offset = OPERATION_PRECOMPILED_BUS_DATA_SIZE + config.indirect_params; for iparam in 0..config.indirect_params { MemBusHelpers::mem_aligned_load( addr_main + iparam as u32 * 8, step_main, - data[OPERATION_BUS_DATA_SIZE + iparam], - pending, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], + mem_processors, ) } for iparam in 0..params_count { @@ -38,7 +38,7 @@ pub fn generate_mem_inputs( }; let param_addr = if config.indirect_params > 0 { // read indirect parameters, means stored the address of parameter - data[OPERATION_BUS_DATA_SIZE + param_index] as u32 + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32 } else { addr_main + (param_index * 8 * config.chunks_per_param) as u32 }; @@ -65,27 +65,25 @@ pub fn generate_mem_inputs( step_main, chunk_data, is_write, - pending, + mem_processors, ) } } } -pub fn skip_mem_inputs( +pub fn skip_mem_inputs( addr_main: u32, data: &[u64], config: &ArithEq384MemInputConfig, - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { let params_count = config.read_params + config.write_params; // Check indirect loads for iparam in 0..config.indirect_params { let addr = addr_main + iparam as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } @@ -96,16 +94,14 @@ pub fn skip_mem_inputs( iparam }; let param_addr = if config.indirect_params > 0 { - data[OPERATION_BUS_DATA_SIZE + param_index] as u32 + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32 } else { addr_main + (param_index * 8 * config.chunks_per_param) as u32 }; for ichunk in 0..config.chunks_per_param { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } diff --git a/precompiles/big_int/pil/big_int_add.pil b/precompiles/big_int/pil/big_int_add.pil index 0e6f93af4..9bd34e80a 100644 --- a/precompiles/big_int/pil/big_int_add.pil +++ b/precompiles/big_int/pil/big_int_add.pil @@ -134,5 +134,5 @@ airtemplate BigIntAdd(const int N = 2**21, const int bits = 256, const int opera const expr final_cout = cout[N_RC-1][RC-1]; // proves the operation lauched by main, c = flag = carry_out - lookup_proves(operation_bus_id, [operation_code, step, 0, addr_params, 0, final_cout, 0, final_cout], sel); + lookup_proves(operation_bus_id, [operation_code, 0, 0, addr_params, 0, final_cout, 0, final_cout, step], sel); } \ No newline at end of file diff --git a/precompiles/big_int/src/add256.rs b/precompiles/big_int/src/add256.rs index acea6702d..c339491d7 100644 --- a/precompiles/big_int/src/add256.rs +++ b/precompiles/big_int/src/add256.rs @@ -62,9 +62,9 @@ impl Add256SM { trace: &mut Add256TraceRowType, multiplicities: &mut [u32], ) { + debug_assert!(input.cin < 2); trace.set_cin(input.cin != 0); let mut cout_2 = input.cin as u32; - for i in 0..4 { let al = input.a[i] as u32; let ah = (input.a[i] >> 32) as u32; @@ -140,19 +140,19 @@ impl Add256SM { let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); let trace_rows = trace.buffer.as_mut_slice(); - // Determinar tamaño óptimo de chunks + // Calculate optimal chunk size let num_threads = rayon::current_num_threads(); let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); - // Procesar en chunks para compartir arrays locales de multiplicities + // Process in chunks to allow per-chunk local multiplicities arrays let local_multiplicities_vec: Vec> = flat_inputs .par_chunks(chunk_size) .zip(trace_rows.par_chunks_mut(chunk_size)) .map(|(input_chunk, trace_chunk)| { - // Array local compartido por este chunk + // Local array shared by this chunk let mut local_multiplicities = vec![0u32; 1 << 16]; - // Procesar todos los inputs del chunk + // Sum all local arrays into a global one for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { self.process_slice(input, trace_row, &mut local_multiplicities); } @@ -161,7 +161,7 @@ impl Add256SM { }) .collect(); - // Sumar todos los arrays locales en uno global + // Sum all local arrays into a global one let mut global_multiplicities = vec![0u32; 1 << 16]; for local_multiplicities in local_multiplicities_vec { for (i, count) in local_multiplicities.iter().enumerate() { @@ -169,7 +169,7 @@ impl Add256SM { } } - // Enviar el resultado final al std + // Send final result to std self.std.range_checks(self.range_id, global_multiplicities); timer_stop_and_log_trace!(ADD256_TRACE); diff --git a/precompiles/big_int/src/add256_bus_device.rs b/precompiles/big_int/src/add256_bus_device.rs index d65dcff9f..71cf20ec5 100644 --- a/precompiles/big_int/src/add256_bus_device.rs +++ b/precompiles/big_int/src/add256_bus_device.rs @@ -2,11 +2,12 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::Add256` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; + +use precompiles_common::MemProcessor; -use zisk_common::MemCollectorInfo; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, STEP, }; use zisk_core::ZiskOperationType; @@ -48,6 +49,51 @@ impl Add256CounterInputGen { pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { (op_type == ZiskOperationType::BigInt).then_some(self.counter.inst_count) } + + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + mem_processors: &mut P, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if data[OP_TYPE] as u32 != ZiskOperationType::BigInt as u32 { + return true; + } + + let step_main = data[STEP]; + let addr_main = data[B] as u32; + + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_add256_mem_inputs(addr_main, step_main, data, true, mem_processors); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + if skip_add256_mem_inputs(addr_main, data, mem_processors) { + return true; + } + generate_add256_mem_inputs(addr_main, step_main, data, false, mem_processors); + } + } + + true + } } impl Metrics for Add256CounterInputGen { @@ -90,57 +136,6 @@ impl Add for Add256CounterInputGen { } impl BusDevice for Add256CounterInputGen { - /// Processes data received on the bus, updating counters and generating inputs when applicable. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus sending the data. - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - debug_assert!(*bus_id == OPERATION_BUS_ID); - - if data[OP_TYPE] as u32 != ZiskOperationType::BigInt as u32 { - return true; - } - - if let Some(mem_collectors_info) = mem_collector_info { - if skip_add256_mem_inputs(data[B] as u32, data, mem_collectors_info) { - return true; - } - } - - let step_main = data[A]; - let addr_main = data[B] as u32; - - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); - } - - generate_add256_mem_inputs(addr_main, step_main, data, only_counters, pending); - - true - } - - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/big_int/src/add256_constants.rs b/precompiles/big_int/src/add256_constants.rs index 4b51efd21..6b358c74b 100644 --- a/precompiles/big_int/src/add256_constants.rs +++ b/precompiles/big_int/src/add256_constants.rs @@ -1,4 +1,4 @@ -use zisk_common::OPERATION_BUS_DATA_SIZE; +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; pub const PARAMS: usize = 4; pub const READ_PARAMS: usize = 2; @@ -6,7 +6,7 @@ pub const DIRECT_READ_PARAMS: usize = 1; pub const WRITE_PARAMS: usize = 1; pub const RESULT_PARAMS: usize = 1; pub const PARAM_CHUNKS: usize = 4; -pub const START_READ_PARAMS: usize = OPERATION_BUS_DATA_SIZE + PARAMS; +pub const START_READ_PARAMS: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + PARAMS; pub const START_WRITE_PARAMS: usize = START_READ_PARAMS + READ_PARAMS * PARAM_CHUNKS + RESULT_PARAMS; pub const WRITE_ADDR_PARAM: usize = READ_PARAMS + DIRECT_READ_PARAMS; diff --git a/precompiles/big_int/src/add256_gen_mem_inputs.rs b/precompiles/big_int/src/add256_gen_mem_inputs.rs index 4f26b6c69..db7d9faea 100644 --- a/precompiles/big_int/src/add256_gen_mem_inputs.rs +++ b/precompiles/big_int/src/add256_gen_mem_inputs.rs @@ -2,9 +2,9 @@ use lib_c::add256; use crate::add256_constants::*; use precompiles_common::MemBusHelpers; -use std::collections::VecDeque; -use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; +use precompiles_common::MemProcessor; + +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; #[derive(Debug)] pub struct Add256MemInputConfig { @@ -15,32 +15,32 @@ pub struct Add256MemInputConfig { pub chunks_per_param: usize, } -pub fn generate_add256_mem_inputs( +pub fn generate_add256_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // Start by generating the params (indirection read, direct, indirection write) for iparam in 0..PARAMS { MemBusHelpers::mem_aligned_load( addr_main + iparam as u32 * 8, step_main, - data[OPERATION_BUS_DATA_SIZE + iparam], - pending, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], + mem_processors, ); } // generate load params for iparam in 0..READ_PARAMS { - let param_addr = data[OPERATION_BUS_DATA_SIZE + iparam] as u32; + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; for ichunk in 0..PARAM_CHUNKS { MemBusHelpers::mem_aligned_load( param_addr + ichunk as u32 * 8, step_main, data[START_READ_PARAMS + iparam * PARAM_CHUNKS + ichunk], - pending, + mem_processors, ); } } @@ -53,14 +53,14 @@ pub fn generate_add256_mem_inputs( [START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] .try_into() .unwrap(); - add256(&a, &b, data[OPERATION_BUS_DATA_SIZE + READ_PARAMS], &mut write_data); + add256(&a, &b, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + READ_PARAMS], &mut write_data); } // verify write param - let write_addr = data[OPERATION_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; + let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; for (ichunk, write_data) in write_data.iter().enumerate().take(PARAM_CHUNKS) { let param_addr = write_addr + ichunk as u32 * 8; - MemBusHelpers::mem_aligned_write(param_addr, step_main, *write_data, pending); + MemBusHelpers::mem_aligned_write(param_addr, step_main, *write_data, mem_processors); } } @@ -68,42 +68,36 @@ pub fn generate_add256_mem_inputs( // op_b = addr_main // mem_trace: @a, @b, cin, @c, a[0..3], b[0..3], cout, [ c[0..3] ] -pub fn skip_add256_mem_inputs( +pub fn skip_add256_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { // verify main params "struct" of indirections for iparam in 0..PARAMS { let addr = addr_main + iparam as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } // verify read params for iparam in 0..READ_PARAMS { - let param_addr = data[OPERATION_BUS_DATA_SIZE + iparam] as u32; + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; for ichunk in 0..PARAM_CHUNKS { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } // verify write param - let write_addr = data[OPERATION_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; + let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + WRITE_ADDR_PARAM] as u32; for ichunk in 0..PARAM_CHUNKS { let addr = write_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } diff --git a/precompiles/big_int/src/add256_input.rs b/precompiles/big_int/src/add256_input.rs index 71e7bc755..746b58593 100644 --- a/precompiles/big_int/src/add256_input.rs +++ b/precompiles/big_int/src/add256_input.rs @@ -1,6 +1,6 @@ use crate::add256_constants::*; use zisk_common::OperationAdd256Data; -use zisk_common::{A, B, OPERATION_BUS_DATA_SIZE}; +use zisk_common::{B, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; #[derive(Debug)] pub struct Add256Input { @@ -17,12 +17,12 @@ pub struct Add256Input { impl Add256Input { pub fn from(values: &OperationAdd256Data) -> Self { Self { - step_main: values[A], + step_main: values[STEP], addr_main: values[B] as u32, - addr_a: values[OPERATION_BUS_DATA_SIZE] as u32, - addr_b: values[OPERATION_BUS_DATA_SIZE + 1] as u32, - addr_c: values[OPERATION_BUS_DATA_SIZE + READ_PARAMS + DIRECT_READ_PARAMS] as u32, - cin: values[OPERATION_BUS_DATA_SIZE + READ_PARAMS], + addr_a: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE] as u32, + addr_b: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] as u32, + cin: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2], + addr_c: values[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 3] as u32, a: values[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(), b: values[START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] .try_into() diff --git a/precompiles/big_int/src/add256_instance.rs b/precompiles/big_int/src/add256_instance.rs index 48799c65d..20d441bc8 100644 --- a/precompiles/big_int/src/add256_instance.rs +++ b/precompiles/big_int/src/add256_instance.rs @@ -7,12 +7,11 @@ use crate::{Add256Input, Add256SM}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::collections::VecDeque; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, + InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; use zisk_pil::Add256Trace; @@ -149,9 +148,7 @@ impl Add256Collector { collect_skipper, } } -} -impl BusDevice for Add256Collector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -164,13 +161,7 @@ impl BusDevice for Add256Collector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -195,15 +186,9 @@ impl BusDevice for Add256Collector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for Add256Collector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/big_int/src/add256_manager.rs b/precompiles/big_int/src/add256_manager.rs index 99a471749..bd3b99fc9 100644 --- a/precompiles/big_int/src/add256_manager.rs +++ b/precompiles/big_int/src/add256_manager.rs @@ -2,10 +2,7 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{ - BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, - InstanceInfo, PayloadType, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; #[cfg(not(feature = "packed"))] use zisk_pil::Add256Trace; @@ -38,8 +35,11 @@ impl Add256Manager { Arc::new(Self { add256_sm }) } - pub fn build_add256_counter(&self) -> Add256CounterInputGen { - Add256CounterInputGen::new(BusDeviceMode::Counter) + pub fn build_add256_counter(&self, asm_execution: bool) -> Add256CounterInputGen { + match asm_execution { + true => Add256CounterInputGen::new(BusDeviceMode::CounterAsm), + false => Add256CounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_add256_input_generator(&self) -> Add256CounterInputGen { @@ -48,14 +48,6 @@ impl Add256Manager { } impl ComponentBuilder for Add256Manager { - /// Builds and returns a new counter for monitoring Add256 operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for Add256 operations. - fn build_counter(&self) -> Option> { - Some(Box::new(Add256CounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan Add256-related instances. /// /// # Returns @@ -93,8 +85,4 @@ impl ComponentBuilder for Add256Manager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(Add256CounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/precompiles/common/Cargo.toml b/precompiles/common/Cargo.toml index 9bf5decaf..aee745b1b 100644 --- a/precompiles/common/Cargo.toml +++ b/precompiles/common/Cargo.toml @@ -10,5 +10,7 @@ categories = { workspace = true } [dependencies] zisk-core = { workspace = true } zisk-common = { workspace = true } +sm-mem = { workspace = true } +mem-common = { workspace = true } fields = { workspace = true } diff --git a/precompiles/common/src/lib.rs b/precompiles/common/src/lib.rs index f37f9081a..b1b2819a7 100644 --- a/precompiles/common/src/lib.rs +++ b/precompiles/common/src/lib.rs @@ -1,19 +1,25 @@ +//! Common utilities and helpers for Zisk precompiles. + mod goldilocks_constants; pub use goldilocks_constants::{get_ks, GOLDILOCKS_GEN, GOLDILOCKS_K}; -use std::collections::VecDeque; -use zisk_common::{BusId, MEM_BUS_ID}; +use mem_common::MemCounters; +use sm_mem::{MemAlignCollector, MemModuleCollector}; +use zisk_common::MEM_BUS_ID; use zisk_core::InstContext; +/// Represents a precompile operation code. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct PrecompileCode(u16); impl PrecompileCode { + /// Creates a new precompile code from a u16 value. pub fn new(value: u16) -> Self { PrecompileCode(value) } + /// Returns the underlying u16 value of the precompile code. pub fn value(&self) -> u16 { self.0 } @@ -31,83 +37,256 @@ impl From for u16 { } } +/// Context for precompile execution. pub struct PrecompileContext {} +/// Trait for implementing precompile calls. pub trait PrecompileCall: Send + Sync { + /// Executes the precompile operation with the given opcode and instruction context. + /// Returns an optional tuple containing the result value and a boolean flag. fn execute(&self, opcode: PrecompileCode, ctx: &mut InstContext) -> Option<(u64, bool)>; } +/// Helper functions for memory bus operations. pub struct MemBusHelpers {} +/// Memory load operation code. const MEMORY_LOAD_OP: u64 = 1; +/// Memory store operation code. const MEMORY_STORE_OP: u64 = 2; +/// Base step for memory operations. const MEM_STEP_BASE: u64 = 1; +/// Maximum number of memory operations per main step. const MAX_MEM_OPS_BY_MAIN_STEP: u64 = 4; +/// Trait for processing memory operations - allows static dispatch +pub trait MemProcessor { + fn process_mem_data(&mut self, data: &[u64; 7]); + fn skip_addr(&mut self, addr: u32) -> bool; + fn skip_addr_range(&mut self, addr_from: u32, addr_to: u32) -> bool; +} + +/// Collector-based memory mem_processor +pub struct MemCollectorProcessor<'a> { + pub mem: &'a mut [(usize, MemModuleCollector)], + pub align: &'a mut [(usize, MemAlignCollector)], +} + +impl<'a> MemCollectorProcessor<'a> { + #[inline(always)] + pub fn new( + mem: &'a mut [(usize, MemModuleCollector)], + align: &'a mut [(usize, MemAlignCollector)], + ) -> Self { + Self { mem, align } + } +} + +impl MemProcessor for MemCollectorProcessor<'_> { + #[inline(always)] + fn process_mem_data(&mut self, data: &[u64; 7]) { + for collector in self.mem.iter_mut() { + collector.1.process_data(&MEM_BUS_ID, data); + } + for collector in self.align.iter_mut() { + collector.1.process_data(&MEM_BUS_ID, data); + } + } + + #[inline(always)] + fn skip_addr(&mut self, addr: u32) -> bool { + for collector in self.mem.iter_mut() { + if !collector.1.skip_addr(addr) { + return false; + } + } + true + } + + #[inline(always)] + fn skip_addr_range(&mut self, addr_from: u32, addr_to: u32) -> bool { + for collector in self.mem.iter_mut() { + if !collector.1.skip_addr_range(addr_from, addr_to) { + return false; + } + } + true + } +} + +/// Counter-based memory mem_processor +pub struct MemCounterProcessor<'a> { + pub counters: Option<&'a mut MemCounters>, +} + +impl<'a> MemCounterProcessor<'a> { + #[inline(always)] + pub fn new(counters: Option<&'a mut MemCounters>) -> Self { + Self { counters } + } +} + +impl MemProcessor for MemCounterProcessor<'_> { + #[inline(always)] + fn process_mem_data(&mut self, data: &[u64; 7]) { + if let Some(counters) = &mut self.counters { + counters.process_data(&MEM_BUS_ID, data); + } + } + + fn skip_addr(&mut self, _addr: u32) -> bool { + false + } + + fn skip_addr_range(&mut self, _addr_from: u32, _addr_to: u32) -> bool { + false + } +} + impl MemBusHelpers { - pub fn mem_aligned_load( + /// Generates an aligned memory load operation. + /// The address must be 8-byte aligned. + pub fn mem_aligned_load( addr: u32, step: u64, mem_value: u64, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processor: &mut P, ) { - assert!(addr % 8 == 0); - pending.push_back(( - MEM_BUS_ID, - vec![ - MEMORY_LOAD_OP, - addr as u64, - MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2, - 8, - mem_value, - 0, - 0, - ], - )); - } - pub fn mem_aligned_write( + debug_assert!(addr % 8 == 0); + let data: [u64; 7] = [ + MEMORY_LOAD_OP, + addr as u64, + MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2, + 8, + mem_value, + 0, + 0, + ]; + mem_processor.process_mem_data(&data); + } + + /// Generates an aligned memory write operation. + /// The address must be 8-byte aligned. + pub fn mem_aligned_write( addr: u32, step: u64, value: u64, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processor: &mut P, ) { - assert!(addr % 8 == 0); - pending.push_back(( - MEM_BUS_ID, - vec![ - MEMORY_STORE_OP, - addr as u64, - MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3, - 8, - 0, - 0, - value, - ], - )); - } - pub fn mem_aligned_op( + debug_assert!(addr % 8 == 0); + let data: [u64; 7] = [ + MEMORY_STORE_OP, + addr as u64, + MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3, + 8, + 0, + 0, + value, + ]; + mem_processor.process_mem_data(&data); + } + + /// Generates an aligned memory operation (load or write). + /// The address must be 8-byte aligned. + pub fn mem_aligned_op( addr: u32, step: u64, value: u64, is_write: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processor: &mut P, + ) { + let data: [u64; 7] = [ + if is_write { MEMORY_STORE_OP } else { MEMORY_LOAD_OP }, + addr as u64, + MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + if is_write { 3 } else { 2 }, + 8, + if is_write { 0 } else { value }, + 0, + if is_write { value } else { 0 }, + ]; + + mem_processor.process_mem_data(&data); + } + + /// Generates multiple aligned memory load operations from a slice of values. + /// The address must be 8-byte aligned. + pub fn mem_aligned_load_from_slice( + addr: u32, + step: u64, + values: &[u64], + mem_processor: &mut P, + ) { + assert!(addr % 8 == 0); + let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2; + for (i, &value) in values.iter().enumerate() { + let data: [u64; 7] = + [MEMORY_LOAD_OP, (addr as usize + i * 8) as u64, mem_step, 8, value, 0, 0]; + + mem_processor.process_mem_data(&data); + } + } + /// Generates multiple aligned memory write operations from a slice of values. + /// The address must be 8-byte aligned. + pub fn mem_aligned_write_from_slice( + addr: u32, + step: u64, + values: &[u64], + mem_processor: &mut P, + ) { + assert!(addr % 8 == 0); + let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3; + for (i, &value) in values.iter().enumerate() { + let data: [u64; 7] = + [MEMORY_STORE_OP, (addr as usize + i * 8) as u64, mem_step, 8, 0, 0, value]; + + mem_processor.process_mem_data(&data); + } + } + /// Generates aligned memory writes from an unaligned read slice using the specified source offset. + /// The number of writes generated is `values.len() - 1` because the last value is not enough to + /// create a full 8-byte write. This function is useful to use the same slice of values to generate + /// first aligned reads and then aligned writes. + /// The address must be 8-byte aligned. + pub fn mem_aligned_write_from_read_unaligned_slice( + addr: u32, + step: u64, + src_offset: u8, + values: &[u64], + mem_processor: &mut P, ) { - pending.push_back(( - MEM_BUS_ID, - vec![ - if is_write { MEMORY_STORE_OP } else { MEMORY_LOAD_OP }, - addr as u64, - MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + if is_write { 3 } else { 2 }, - 8, - if is_write { 0 } else { value }, - 0, - if is_write { value } else { 0 }, - ], - )); + assert!(addr % 8 == 0); + let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3; + let write_count = values.len() - 1; + for i in 0..write_count { + let write_value = match src_offset { + 1 => (values[i] >> 8) | (values[i + 1] << 56), + 2 => (values[i] >> 16) | (values[i + 1] << 48), + 3 => (values[i] >> 24) | (values[i + 1] << 40), + 4 => (values[i] >> 32) | (values[i + 1] << 32), + 5 => (values[i] >> 40) | (values[i + 1] << 24), + 6 => (values[i] >> 48) | (values[i + 1] << 16), + 7 => (values[i] >> 56) | (values[i + 1] << 8), + _ => panic!("invalid src_offset {src_offset} on DmaUnaligned"), + }; + let data: [u64; 7] = + [MEMORY_STORE_OP, (addr as usize + i * 8) as u64, mem_step, 8, 0, 0, write_value]; + + mem_processor.process_mem_data(&data); + } + } + + /// Returns the memory read step for the given step number. + pub fn get_mem_read_step(step: u64) -> u64 { + MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 2 + } + /// Returns the memory write step for the given step number. + pub fn get_mem_write_step(step: u64) -> u64 { + MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3 } } +/// Calculates the base-2 logarithm of n (floor). pub fn log2(n: usize) -> usize { let mut res = 0; let mut n = n; diff --git a/precompiles/dma/Cargo.toml b/precompiles/dma/Cargo.toml new file mode 100644 index 000000000..7c197b12b --- /dev/null +++ b/precompiles/dma/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "precomp-dma" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +keywords = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } + +[dependencies] +zisk-core = { workspace = true } +zisk-common = { workspace = true } +zisk-pil = { workspace = true } +precompiles-common = { workspace = true } +precompiles-helpers = { workspace = true } +sm-mem = { workspace = true } +mem-common = { workspace = true } +lib-c = { workspace = true } + +proofman = { workspace = true } +proofman-common = { workspace = true } +proofman-macros = { workspace = true } +proofman-util = { workspace = true } +pil-std-lib = { workspace = true } +fields = { workspace=true } +tracing = { workspace = true } +rayon = { workspace = true } +generic-array = "0.14" + +[features] +default = [] +dma_memcmp = [] +debug_dma = [] +gpu = ["proofman-common/gpu", "packed"] +packed = ["proofman-common/packed"] +no_lib_link = ["proofman-common/no_lib_link"] +diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] +disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file diff --git a/precompiles/dma/dma.md b/precompiles/dma/dma.md new file mode 100644 index 000000000..4824d297f --- /dev/null +++ b/precompiles/dma/dma.md @@ -0,0 +1,14 @@ +## Translate + +ELF + +ADD X0, DST, SRC ==> DMA_MEMCPY DST, SRC (JMP +8) +CSRW XXX, COUNT ==> FLAG COUNT + + +DST_PRE_DATA +DST_POST_DATA +SRC_DATA ..... (without redundancy) + + +extra param count \ No newline at end of file diff --git a/precompiles/dma/pil/dma.pil b/precompiles/dma/pil/dma.pil new file mode 100644 index 000000000..5103a493d --- /dev/null +++ b/precompiles/dma/pil/dma.pil @@ -0,0 +1,205 @@ +const int DMA_MEM_CPY = 1; +const int DMA_MEM_PRE_POST = 2; +const int DMA_MEM_EQ = 3; + +const int MEM_CPY_BYTE_CONT_ID = 8200; +const int MEM_CPY_CONT_ID = 8201; + +const int DMA_MEM_CPY_COUNT_ADDR = 0xA0000F00; + + +airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const int selectors = 1, + const int operation_bus_id = OPERATION_BUS_ID) { + + assert(selectors == 0 || selectors == 1); + assert(op_x_row > 1); + + // DMA: MEMCPY + // + // Unlike a CPU memcpy, the DMA memcpy is instantaneous from a temporal point of view all reads + // occur in the same step and all writes in the next step. In the case of reads there are no + // problems in performing the same read several times in the same step because the result is + // always the same in this moment. In the case of writes, this is not the case, because it would + // create an ambiguity to know the written value. Therefore, we must guarantee that each 64-bit + // value is written only once. + // + // The strategy that DMA follows in the case of memcpy is to divide the operation into 3 phases: + // + // 1. Pre-copy: initial bytes are copied to achieve at least destination alignment, to avoid + // double writes. + // + // 2. Fully or partially aligned copy. It's a loop where 64-bit values are copied. There are + // two variants: + // a) Aligned 64-bit copy: both source and destination addresses are aligned to 8 bytes. + // b) Unaligned 64-bit copy: the source address is not aligned to 8 bytes, but it's read in + // bytes and 64-bit values are reassembled. + // + // 3. Post-copy: final bytes that don't complete a 64-bit word are copied. + // + // Not all phases are necessary, they depend on the copy parameters. + // + // Example of fully aligned memcpy operation: + // + // PRE MEMCPY POST + // ┌──────────┐ ┌──────────────────────────────┐ ┌──────────┐ + // + // ┌──────┐ ┌────────┬────────┐ ┌────────┐ ┌────┐ + // src │ 0 │ │ 1 │ 2 │...│ n │ │ n+1│ + // └──────┘ └────────┴────────┘ └────────┘ └────┘ + // ↓ ↓ ↓ ↓ ↓ + // ┌───┬──────┐ ┌────────┬────────┐ ┌────────┐ ┌────┬─────┐ + // dst │ 0 │ │ 1 │ 2 │...│ n │ │ n+1 │ + // └───┴──────┘ └────────┴────────┘ └────────┘ └────┴─────┘ + // ^dst64 ^dst64+1 ^dst64+2 ^dst64+n ^dst64+n+1 <-- each dst64 address is + // written only once + // + // + // Example of partially aligned memcpy operation: + // + // PRE MEMCPY POST + // ┌──────────┐ ┌────────────────────────────────────────┐ ┌──────────┐ + // ┌────┐┌─ ┌────────┬────────┐ ┌────────┐┌─┬──────┐ ┌─┬───┐ + // src │ 0 ││1 │ 1 │ 2 │...│ n ││ n+1 │ │ n+1│ + // └────┘└─ └────────┴────────┘ └────────┘└─┴──────┘ └─┴───┘ + // ↓ ↓ ↓ ↙ ↓ ↙ ↓ ↙ ↙ + // ┌───┬──────┐ ┌────────┬────────┐ ┌────────┐ ┌────┬─────┐ + // dst │ 0 │ │ 1 │ 2 │...│ n │ ↑ │ n+1 │ + // └───┴──────┘ └────────┴────────┘ └────────┘ │ └────┴─────┘ + // ^dst64 ^dst64+1 ^dst64+2 ^dst64+n │ ^dst64+n+1 <-- each dst64 address is + // without written only once + // write + // + + col witness bits(1) sel; + sel * (1 - sel) === 0; + + // read count from register directly + + // assuming max memory of 4GB (32 bits) + // count: 24 bits RC + 8 bits T + col witness bits(24) h_count; + + // What is count_lt_256 used for? + // + // If count were split in the traditional way into h_count|l_count there would be a problem + // because the DMA ROM has to check whether the count is small. + // + // But what would happen if the bits of l_count were 0 while h_count was not? + // The DMA ROM could not distinguish whether count is 0 or, for example, 1024 = 4|0 + // + // To avoid this, was defined that in such a case one is subtracted from h_count and 256 is added + // to l_count so that the overall count value remains the same and so that for any k up to 511, + // if l_count > k ==> count > k. + // + // To ensure this condition a flag count_lt_256 must be enforced to verify that h_count is actually + // 0 when l_count < 256, and the ROM verifies the consistency between count_lt_256 and l_count. + + col witness bits(1) count_lt_256; + col witness bits(9) l_count; // 0..255 + 256 = 9 bits + const expr count = h_count * 256 + l_count; + + count_lt_256 * (1 - count_lt_256) === 0; + + // constraint (1) + count_lt_256 * h_count === 0; + + // count count count cons Range h_count * 256 + // 31..8 7..0 h_count lt_256 (1) l_count Check Rom + l_count + // ----- ----- ------- ------ ---- ------- ----- ---- ------------- + // 0 x 0 1 OK x OK OK 0 | x OK + // 0 x 0 0 OK x OK FAIL 0 | x OK <== DETECTED + // 1 x 0 0 OK 256 + x OK OK 1 | x OK + // 1 x 0 1 OK 256 + x OK FAIL 1 | x OK <== DETECTED + // 1 x 1 1 FAIL x OK OK 1 | x OK <== DETECTED + // y > 1 x y - 1 0 OK 256 + x OK y | x OK + // MAX x MAX - 1 0 OK 256 + x OK MAX | x OK + // 0 x -1 0 OK 256 + x FAIL 0 | x OK <== DETECTED + // MAX x MAX 0 OK 256 + x FAIL MAX+1 | x FAIL <== DETECTED + // - - - - - x < 0 - FAIL - | - - <== DETECTED + // - - - - - x >=512 - FAIL - | - - <== DETECTED + + range_check(expression: h_count, min: 0, max: 2**24-1, sel: sel); + + // 32 bit address + // 22 bits RC + 5 bits RC + 3 bits T + col witness bits(22) h_src64; + col witness bits(7) l_src64; + col witness bits(3) src_offset; + const expr src = h_src64 * 2**10 + l_src64 * 2**3 + src_offset; + const expr src64 = h_src64 * 2**10 + l_src64 * 2**3; + // src_offset range verified with dma_rom table + + + // 32 bit address + // 22 bits RC + 7 bits RC + 3 bits T + col witness bits(22) h_dst64; + col witness bits(7) l_dst64; + col witness bits(3) dst_offset; + const expr dst = h_dst64 * 2**10 + l_dst64 * 2**3 + dst_offset; + const expr dst64 = h_dst64 * 2**10 + l_dst64 * 2**3; + // dst_offset range verified with dma_rom table + + col witness bits(MAIN_STEP_BITS) main_step; + + range_check(expression: h_src64, min: 0, max: 2**22-1, sel: sel); + range_check(expression: h_dst64, min: 0, max: 2**22-1, sel: sel); + + lookup_assumes(DUAL_RANGE_7_BITS_ID, expressions: [l_src64, l_dst64], sel: sel); + + col witness bits(1) use_pre; // use memcpy_pre operation + col witness bits(1) use_memcpy; // use memcpy operation + col witness bits(1) use_post; // use memcpy_post operation + col witness bits(1) src64_inc_by_pre; // src64 increment after apply memcpy_pre operation + + use_pre * (1 - use_pre) === 0; + use_memcpy * (1 - use_memcpy) === 0; + use_post * (1 - use_post) === 0; + src64_inc_by_pre * (1 - src64_inc_by_pre) === 0; + + const expr flags = use_pre + use_memcpy * 2 + use_post * 4 + src64_inc_by_pre * 8 + count_lt_256 * 16; + + col witness bits(3) pre_count; // number of bytes of memcpy_pre operation + col witness bits(9) l_count64; // number of 64 bits words in l_count after substract pre_count and post_count + col witness bits(3) src_offset_after_pre; // src_offset after apply memcpy_pre operation + + const expr post_count = count - pre_count - h_count * 256 - l_count64 * 8; + + lookup_assumes(DMA_ROM_ID, expressions: [dst_offset, src_offset, l_count, flags, pre_count, src_offset_after_pre, l_count64], sel: sel); + + // if operation not selected then other selectors must be 0. + (1 - sel) * use_memcpy === 0; + (1 - sel) * use_pre === 0; + (1 - sel) * use_post === 0; + + permutation_assumes(DMA_BUS_ID, [DMA_MEM_CPY, + (dst64 + use_pre * 8), + (src64 + src64_inc_by_pre * 8), + 0, + src_offset_after_pre, + h_count * 256 + l_count64 * 8, // count64 = h_count * (256/8 = 32) + l_count64 + main_step], sel: use_memcpy); + + permutation_assumes(DMA_BUS_ID, [DMA_MEM_PRE_POST, + dst64, + src64, + dst_offset, + src_offset, + pre_count, + main_step], sel: use_pre); + + permutation_assumes(DMA_BUS_ID, [DMA_MEM_PRE_POST, + dst64 + use_pre * 8 + l_count64 * 8 + h_count * 256, + src64 + src64_inc_by_pre * 8 + l_count64 * 8 + h_count * 256, + 0, + src_offset_after_pre, + post_count, + main_step], sel: use_post); + + // TODO: memcmp + + // used to link operation with main + lookup_proves(operation_bus_id, [OP_DMA_MEMCPY, dst, 0, src, 0, 0, 0, 0, main_step], mul: sel); + + // lookup_proves(operation_bus_id, [OP_PARAM, 0, 0, count, 0, 0, 0, 0, main_step-1], mul: sel); + precompiled_mem_load(sel: sel, main_step: main_step, addr: DMA_MEM_CPY_COUNT_ADDR, value: [count, 0]); +} \ No newline at end of file diff --git a/precompiles/dma/pil/dma_64_aligned.pil b/precompiles/dma/pil/dma_64_aligned.pil new file mode 100644 index 000000000..973bab77d --- /dev/null +++ b/precompiles/dma/pil/dma_64_aligned.pil @@ -0,0 +1,237 @@ +airtemplate Dma64Aligned(int N = 2**21, const int RC = 2, const int op_x_row = 4, + const expr enable = 1, const int src_is_free_input = 0, + const int operation_bus_id = OPERATION_BUS_ID) { + assert(RC == 2); + + const expr L1 = get_L1(); + const expr LAST = L1'; + + assert(op_x_row > 1); + + // dst64 aligned, word address + // src64 aligned, word address + // count number of words + + airval segment_id; // Id of current segment + airval segment_previous_seq_end; // Last value of `seq_end` in previous segment. + airval segment_previous_dst64; // Last value of `dst64` in previous segment. + airval segment_previous_main_step; // Last value of `main_step` in previous segment. + airval segment_previous_count; // Last value of `count` in previous segment. + airval segment_previous_is_mem_eq; // Last value of `is_mem_eq' in previous segment. + + airval segment_last_seq_end; // Last value of `seq_end` in current segment. + airval segment_last_dst64; // Last value of `dst64` in current segment. + airval segment_last_main_step; // Last value of `main_step` in current segment. + airval segment_last_count; // Last value of `count` in current segment. + airval segment_last_is_mem_eq; // Last value of `is_mem_eq' in current segment. + + airval is_last_segment; // 1 if this is the last segment, 0 otherwise. + + is_last_segment * (1 - is_last_segment) === 0; + segment_previous_seq_end * (1 - segment_previous_seq_end) === 0; + segment_last_seq_end * (1 - segment_last_seq_end) === 0; + + segment_previous_is_mem_eq * (1 - segment_previous_is_mem_eq) === 0; + segment_last_is_mem_eq * (1 - segment_last_is_mem_eq) === 0; + + col witness bits(MAIN_STEP_BITS) main_step; + col witness bits(ADDR_W_BITS) dst64; + col witness bits(32) count; + + col witness bits(1) sel_op[op_x_row]; + const expr sel = sel_op[0]; + col witness bits(1) seq_end; + col witness bits(1) is_mem_eq; + + if (src_is_free_input) { + const expr air.value[op_x_row][RC]; + col witness bits(16) value_chunks[op_x_row][RC][2]; + for (int i = 0; i < op_x_row; i++) { + for (int rc = 0; rc < RC; ++rc) { + value[i][rc] = value_chunks[i][rc][0] + value_chunks[i][rc][1] * P2_16; + range_check(value_chunks[i][rc][0], 0, P2_16 - 1); + range_check(value_chunks[i][rc][1], 0, P2_16 - 1); + } + } + + const int air.segment_previous_src64 = 0; + const int air.segment_last_src64 = 0; + } else { + col witness bits(32) air.value[op_x_row][RC]; + col witness bits(ADDR_W_BITS) air.src64; + airval air.segment_previous_src64; // Last value of `src64` in previous segment. + airval air.segment_last_src64; // Last value of `src64` in current segment. + } + + seq_end * (1 - seq_end) === 0; + is_mem_eq * (1 - is_mem_eq) === 0; + + col witness bits(1) previous_seq_end; + previous_seq_end === L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; + + LAST * (seq_end - segment_last_seq_end) === 0; + LAST * (1 - seq_end) * (dst64 - segment_last_dst64) === 0; + LAST * (1 - seq_end) * (main_step - segment_last_main_step) === 0; + LAST * (1 - seq_end) * (count - segment_last_count) === 0; + LAST * (1 - seq_end) * (is_mem_eq - segment_last_is_mem_eq) === 0; + + // when segment_last_seq_end = 1, means that the next segment starts with new input, no need to + // compare with previous, to simplify WC of continuations, zeros are sent to bus + // these constraints aren't strictly necessary, because these values are used only when seq_end = 0 + + segment_last_dst64 * segment_last_seq_end === 0; + segment_last_count * segment_last_seq_end === 0; + segment_last_main_step * segment_last_seq_end === 0; + segment_last_is_mem_eq * segment_last_seq_end === 0; + + // when segment_previous_seq_end = 1, means that this segment starts with new input, no need to + // compare with previous, to simplify WC of continuations, zeros are sent to bus + // these constraints aren't strictly necessary, because these values are used only when seq_end = 0 + + segment_previous_dst64 * segment_previous_seq_end === 0; + segment_previous_count * segment_previous_seq_end === 0; + segment_previous_main_step * segment_previous_seq_end === 0; + segment_previous_is_mem_eq * segment_previous_seq_end === 0; + + if (!src_is_free_input) { + LAST * (1 - seq_end) * (src64 - segment_last_src64) === 0; + const expr air.previous_src64 = L1 * (segment_previous_src64 - 'src64) + 'src64; + segment_previous_src64 * segment_previous_seq_end === 0; + segment_last_src64 * segment_last_seq_end === 0; + } + + // Continuations + + // AIR_ID, segment_id, seq_end, src64, dst64, count, main_step + + direct_global_update_proves(MEM_CPY_CONT_ID, [0, + 0, + src_is_free_input, + 1, // initial seq_end + 0, // initial src64 + 0, // initial dst64 + 0, // initial count + 0, // initial main_step + 0], // initial is_mem_eq + sel: enable); + + + direct_update_assumes(MEM_CPY_CONT_ID, [segment_id, + 0, + src_is_free_input, + segment_previous_seq_end, + segment_previous_src64, + segment_previous_dst64, + segment_previous_count, + segment_previous_main_step, + segment_previous_is_mem_eq]); + + direct_update_proves(MEM_CPY_CONT_ID, [segment_id + 1, + is_last_segment, + src_is_free_input, + segment_last_seq_end, + segment_last_src64, + segment_last_dst64, + segment_last_count, + segment_last_main_step, + segment_last_is_mem_eq] + , sel: 1 - is_last_segment); + + + + expr _ops_in_row = 0; + for (int i = 0; i < op_x_row; i++) { + sel_op[i] * (1 - sel_op[i]) === 0; + _ops_in_row += sel_op[i]; + + if (!src_is_free_input) { + precompiled_mem_load( + sel: sel_op[i], + main_step: main_step, + addr: src64 * 8 + 8 * i, + value: value[i] + ); + } + + precompiled_mem_op( + is_write: 1 - is_mem_eq, + sel: sel_op[i], + main_step: main_step, + addr: dst64 * 8 + 8 * i, + value: value[i] + ); + if (i > 0) { + // current selector only can be 1 if previous is 1 + sel_op[i] * (1 - sel_op[i - 1]) === 0; + } + } + const expr ops_in_row = _ops_in_row; + + const expr continue_seq_on_l1 = L1 * (1 - segment_previous_seq_end); + const expr continue_seq_on_no_l1 = (1 - L1) * (1 - 'seq_end); + + // const expr new_seq_on_l1 = L1 * segment_previous_seq_end; + // const expr new_seq_on_no_l1 = (1 - L1) * 'seq_end; + + // TRANSITIONS: + // + // If not finish sequence in previous row, means current it's a continuation of previous sequence + // count match with previous value plus ops_in_row. + continue_seq_on_l1 * (count - (segment_previous_count - op_x_row)) == 0; + continue_seq_on_no_l1 * (count - ('count - op_x_row)) === 0; + + // If finish sequence count match with ops_in_row, because final count must be 0. + // new_seq_on_l1 * (count - ops_in_row) === 0; + // new_seq_on_no_l1 * (count - ops_in_row) === 0; + seq_end * (count - ops_in_row) === 0; + + // If previous row was a seq_end, start with new dst/src addresses, else increment by ops_in_row + continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + op_x_row)) === 0; + continue_seq_on_no_l1 * (dst64 - ('dst64 + op_x_row)) === 0; + + // NOTE: transition of src64 is defined only + + // LATCHS: + // + // is_mem_eq = 'is_mem_eq + // main_step = 'main_step + + continue_seq_on_l1 * (is_mem_eq - segment_previous_is_mem_eq) === 0; + continue_seq_on_no_l1 * (is_mem_eq - 'is_mem_eq) === 0; + + continue_seq_on_l1 * (main_step - segment_previous_main_step) === 0; + continue_seq_on_no_l1 * (main_step - 'main_step) === 0; + + // SECURITY: control count no negative + // + // if the seq_end it isn't active when count = 0, in each rows continues decreasing 8 x op_x_row + // units, means in 2^22 rows * 2^3 * 2^2 = 2^27. It's secure, because if any row lies, at end of + // instance this constraint fails. + + airval last_count_chunk[2]; + range_check(expression: last_count_chunk[0], min: 0, max: 2**16-1); + range_check(expression: last_count_chunk[1], min: 0, max: 2**16-1); + last_count_chunk[0] + last_count_chunk[1] * P2_16 === segment_last_count; + + airval padding_size; + + if (src_is_free_input) { + const int DMA_MEM_INPUT_CPY_OP = 0xD3; + const expr dma_op = DMA_MEM_INPUT_CPY_OP; + permutation_proves(operation_bus_id, [dma_op, dst64 * 8, 0, count * 8, 0, 0, 0, 0, main_step], sel: previous_seq_end); + + // Used to cancel padding operations + direct_update_assumes(operation_bus_id, [dma_op, 0, 0, 0, 0, 0, 0, 0, 0], sel: adding_size); + } else { + // TRANSITION src64: + continue_seq_on_l1 * (src64 - (segment_previous_src64 + op_x_row)) === 0; + continue_seq_on_no_l1 * (src64 - ('src64 + op_x_row)) === 0; + + // At beginning of memcpy blocks we send two address + const expr dma_op = is_mem_eq * (DMA_MEM_EQ - DMA_MEM_CPY) + DMA_MEM_CPY; + permutation_proves(DMA_BUS_ID, [dma_op, dst64 * 8, src64 * 8, 0, 0, count * 8, main_step], sel: previous_seq_end); + + // Used to cancel padding operations + direct_update_assumes(DMA_BUS_ID, [DMA_MEM_CPY, 0, 0, 0, 0, 0, 0], sel: padding_size); + } +} \ No newline at end of file diff --git a/precompiles/dma/pil/dma_pre_post.pil b/precompiles/dma/pil/dma_pre_post.pil new file mode 100644 index 000000000..a61bcdc87 --- /dev/null +++ b/precompiles/dma/pil/dma_pre_post.pil @@ -0,0 +1,179 @@ +// src_addr, src_offset +// dst_addr, dst_offset, count + +airtemplate DmaPrePost(int N = 2**21) { + + col witness bits(MAIN_STEP_BITS) main_step; + col witness bits(ADDR_W_BITS) src64; + col witness bits(ADDR_W_BITS) dst64; + col witness bits(3) dst_offset; + col witness bits(3) src_offset; + col witness bits(3) count; + + col witness bits(1) selread[7]; + const expr selr[8]; + expr _selr_0to6 = 0; + expr _selr_value = 0; + for (int i = 0; i < 7; ++i) { + selread[i] * (1 - selread[i]) === 0; + selr[i] = selread[i]; + _selr_0to6 = _selr_0to6 + selread[i]; + if (i > 0) { + _selr_value = _selr_value + i * selr[i]; + } + } + const expr selr_0to6 = _selr_0to6; + _selr_value = _selr_value + 7 * selr[7]; + + const expr selr_value = _selr_value; + + selr_0to6 * (1 - selr_0to6) === 0; + selr[7] = (1 - selr_0to6); + + col witness bits(1) dst_offset_gt_src_offset; + dst_offset_gt_src_offset * (1 - dst_offset_gt_src_offset) === 0; + + col witness bits(1) enabled; + col witness bits(1) enabled_second_read; + + enabled * (1 - enabled) === 0; + enabled_second_read * (1 - enabled_second_read) === 0; + + // to force use table with enable when enabled_second_read is 1. If enable is 1, with table + // it's verified the correcte value of enabled_second_read using offset an count + enabled_second_read * (1 - enabled) === 0; + + const int V2R = 2 * 3; // Values To Read (2 x 32 bits = 64 bits x (R,R+8,PRE-W)) + const int B2R = V2R * 4; // Bytes To Read (4 bytes = value 32 bits) + + col witness bits(8) bytes[B2R]; + const expr values[V2R]; + + for (int i = 0; i < B2R; i+=2) { + range_dual_byte(bytes[i+1], bytes[i], enabled); + } + + for (int i = 0; i < V2R; ++i) { + values[i] = bytes[i*4] + P2_8 * bytes[i*4+1] + P2_16 * bytes[i*4+2] + P2_24 * bytes[i*4+3]; + } + col witness bits(1) selb[8]; + expr selectors_mask = 0; + for (int i = 0; i < 8; ++i) { + selb[i] * (1 - selb[i]) === 0; + selectors_mask += selb[i] * (1 << (7-i)); + } + + col witness bits(32) write_value[4]; + + // byte_0, byte_1, byte_2, byte_3, byte_4, byte_5, byte_6, byte_7, byte_0', byte_1' ... + + // src_offset 0 1 2 3 4 5 6 7 + // dst_offset 0 0 1 2 3 4 5 6 7 + // dst_offset 1 -1 + + + + // │ src_offset: + // selr_value │ 0 1 2 3 4 5 6 7 + // ──────────────┼─────────────────────── + // dst_offset: 0 │ 0 1 2 3 4 5 6 7 + // 1 │ 1 0 1 2 3 4 5 6 + // 2 │ 2 1 0 1 2 3 4 5 + // 3 │ 3 2 1 0 1 2 3 4 + // 4 │ 4 3 2 1 0 1 2 3 + // 5 │ 5 4 3 2 1 0 1 2 + // 6 │ 6 5 4 3 2 1 0 1 + // 7 │ 7 6 5 4 3 2 1 0 + // + // NOTE: selr_value = ABS(src_offset - dst_offset) + + // ┌────── src_offset - dst_offset + // │ ┌── abs(src_offset - dst_offset) + // │ │ + // │ │ 0 1 2 3 4 5 6 7 + // ┌─────────────────┬─────────────────┐ + // -7 7 │ │ R0 │ + // -6 6 │ │ R0 R1 │ + // -5 5 │ │ R0 R1 R2 │ + // -4 4 │ │ R0 R1 R2 R3 │ + // -3 3 │ R0 │ R1 R2 R3 R4 │ + // -2 2 │ R0 R1 │ R2 R3 R4 R5 │ + // -1 1 │ R0 R1 R2 │ R3 R4 R5 R6 │ + // ├─────────────────┼─────────────────┤ + // 0 0 │ R0 R1 R2 R3 │ R4 R5 R6 R7 │ + // 1 1 │ R1 R2 R3 R4 │ R5 R6 R7 R8 │ + // 2 2 │ R2 R3 R4 R5 │ R6 R7 R8 R9 │ + // 3 3 │ R3 R4 R5 R6 │ R7 R8 R9 R10 │ + // 4 4 │ R4 R5 R6 R7 │ R8 R9 R10 R11 │ + // 5 5 │ R5 R6 R7 R8 │ R9 R10 R11 R12 │ + // 6 6 │ R6 R7 R8 R9 │ R10 R11 R12 R13 │ + // 7 7 │ R7 R8 R9 R10 │ R11 R12 R13 R14 │ + // └─────────────────┴─────────────────┘ + + // dst_offset <= src_offset + + write_value[0] === selr[0] * (selb[0] * bytes[0] + selb[1] * P2_8 * bytes[1] + selb[2] * P2_16 * bytes[2] + selb[3] * P2_24 * bytes[3]) + + selr[1] * (selb[0] * bytes[1] + selb[1] * P2_8 * bytes[2] + selb[2] * P2_16 * bytes[3] + selb[3] * P2_24 * bytes[4]) + + selr[2] * (selb[0] * bytes[2] + selb[1] * P2_8 * bytes[3] + selb[2] * P2_16 * bytes[4] + selb[3] * P2_24 * bytes[5]) + + selr[3] * (selb[0] * bytes[3] + selb[1] * P2_8 * bytes[4] + selb[2] * P2_16 * bytes[5] + selb[3] * P2_24 * bytes[6]) + + selr[4] * (selb[0] * bytes[4] + selb[1] * P2_8 * bytes[5] + selb[2] * P2_16 * bytes[6] + selb[3] * P2_24 * bytes[7]) + + selr[5] * (selb[0] * bytes[5] + selb[1] * P2_8 * bytes[6] + selb[2] * P2_16 * bytes[7] + selb[3] * P2_24 * bytes[8]) + + selr[6] * (selb[0] * bytes[6] + selb[1] * P2_8 * bytes[7] + selb[2] * P2_16 * bytes[8] + selb[3] * P2_24 * bytes[9]) + + selr[7] * (selb[0] * bytes[7] + selb[1] * P2_8 * bytes[8] + selb[2] * P2_16 * bytes[9] + selb[3] * P2_24 * bytes[10]) + + (1 - selb[0]) * bytes[16] + (1 - selb[1]) * P2_8 * bytes[17] + (1 - selb[2]) * P2_16 * bytes[18] + (1 - selb[3]) * P2_24 * bytes[19]; + + write_value[1] === selr[0] * (selb[4] * bytes[4] + selb[5] * P2_8 * bytes[5] + selb[6] * P2_16 * bytes[6] + selb[7] * P2_24 * bytes[7]) + + selr[1] * (selb[4] * bytes[5] + selb[5] * P2_8 * bytes[6] + selb[6] * P2_16 * bytes[7] + selb[7] * P2_24 * bytes[8]) + + selr[2] * (selb[4] * bytes[6] + selb[5] * P2_8 * bytes[7] + selb[6] * P2_16 * bytes[8] + selb[7] * P2_24 * bytes[9]) + + selr[3] * (selb[4] * bytes[7] + selb[5] * P2_8 * bytes[8] + selb[6] * P2_16 * bytes[9] + selb[7] * P2_24 * bytes[10]) + + selr[4] * (selb[4] * bytes[8] + selb[5] * P2_8 * bytes[9] + selb[6] * P2_16 * bytes[10] + selb[7] * P2_24 * bytes[11]) + + selr[5] * (selb[4] * bytes[9] + selb[5] * P2_8 * bytes[10] + selb[6] * P2_16 * bytes[11] + selb[7] * P2_24 * bytes[12]) + + selr[6] * (selb[4] * bytes[10] + selb[5] * P2_8 * bytes[11] + selb[6] * P2_16 * bytes[12] + selb[7] * P2_24 * bytes[13]) + + selr[7] * (selb[4] * bytes[11] + selb[5] * P2_8 * bytes[12] + selb[6] * P2_16 * bytes[13] + selb[7] * P2_24 * bytes[14]) + + (1 - selb[4]) * bytes[20] + (1 - selb[5]) * P2_8 * bytes[21] + (1 - selb[6]) * P2_16 * bytes[22] + (1 - selb[7]) * P2_24 * bytes[23]; + + // dst_offset > src_offset + + write_value[2] === selr[3] * ( selb[3] * P2_24 * bytes[0]) + + selr[2] * ( selb[2] * P2_16 * bytes[0] + selb[3] * P2_24 * bytes[1]) + + selr[1] * ( selb[1] * P2_8 * bytes[0] + selb[2] * P2_16 * bytes[1] + selb[3] * P2_24 * bytes[2]) + + (1 - selb[0]) * bytes[16] + (1 - selb[1]) * P2_8 * bytes[17] + (1 - selb[2]) * P2_16 * bytes[18] + (1 - selb[3]) * P2_24 * bytes[19]; + + write_value[3] === selr[7] * ( + selb[7] * P2_24 * bytes[0]) + + selr[6] * ( + selb[6] * P2_16 * bytes[0] + selb[7] * P2_24 * bytes[1]) + + selr[5] * ( + selb[5] * P2_8 * bytes[0] + selb[6] * P2_16 * bytes[1] + selb[7] * P2_24 * bytes[2]) + + selr[4] * (selb[4] * bytes[0] + selb[5] * P2_8 * bytes[1] + selb[6] * P2_16 * bytes[2] + selb[7] * P2_24 * bytes[3]) + + selr[3] * (selb[4] * bytes[1] + selb[5] * P2_8 * bytes[2] + selb[6] * P2_16 * bytes[3] + selb[7] * P2_24 * bytes[4]) + + selr[2] * (selb[4] * bytes[2] + selb[5] * P2_8 * bytes[3] + selb[6] * P2_16 * bytes[4] + selb[7] * P2_24 * bytes[5]) + + selr[1] * (selb[4] * bytes[3] + selb[5] * P2_8 * bytes[4] + selb[6] * P2_16 * bytes[5] + selb[7] * P2_24 * bytes[6]) + + (1 - selb[4]) * bytes[20] + (1 - selb[5]) * P2_8 * bytes[21] + (1 - selb[6]) * P2_16 * bytes[22] + (1 - selb[7]) * P2_24 * bytes[23]; + + + + const expr bus_write_value[2]; + + bus_write_value[0] = dst_offset_gt_src_offset * (write_value[2] - write_value[0]) + write_value[0]; + bus_write_value[1] = dst_offset_gt_src_offset * (write_value[3] - write_value[1]) + write_value[1]; + + // MEMORY ACCESS + // + // Read first 64 bits + // Read next 64 bits (optional, only if enabled_second_read) + // Read previous value before write + // Write final value + + precompiled_mem_load(sel: enabled, main_step: main_step, addr: src64 * 8, value: [values[0], values[1]]); + precompiled_mem_load(sel: enabled_second_read, main_step: main_step, addr: src64 * 8 + 8, value: [values[2], values[3]]); + precompiled_mem_load(sel: enabled, main_step: main_step, addr: dst64 * 8, value: [values[4], values[5]]); + precompiled_mem_store(sel: enabled, main_step: main_step, addr: dst64 * 8, value: bus_write_value); + + // Send operation to bus + permutation_proves(DMA_BUS_ID, [DMA_MEM_PRE_POST, dst64 * 8, src64 * 8, dst_offset, src_offset, count, main_step], sel: enabled); + + // selectors_mask (8 bits) + // enabled_second_read (1 bit) + // dst_offset_gt_src_offset (1 bit) + // selr_value (0-7) (3 bits) + const expr flags = selectors_mask + enabled_second_read * P2_8 + dst_offset_gt_src_offset * P2_9 + selr_value * P2_10; + lookup_assumes(DMA_PRE_POST_TABLE_ID, [flags, dst_offset, src_offset, count], sel: enabled); +} \ No newline at end of file diff --git a/precompiles/dma/pil/dma_pre_post_table.pil b/precompiles/dma/pil/dma_pre_post_table.pil new file mode 100644 index 000000000..803af229b --- /dev/null +++ b/precompiles/dma/pil/dma_pre_post_table.pil @@ -0,0 +1,71 @@ +require "std_lookup.pil" + +const int DMA_PRE_POST_TABLE_SIZE = 280; + +airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { + col fixed FLAGS; + col fixed DST_OFFSET; + col fixed SRC_OFFSET; + col fixed COUNT; + + int index = 0; + + // POST OPERATION objective execute the last incomplete "write": + // dst_offset = 0 + // count ∈ [1,7] + + int table_offsets[64]; + + for (int src_offset = 0; src_offset < 8; ++src_offset) { + table_offsets[src_offset] = index; + for (int count = 1; count < 8; ++ count) { + SRC_OFFSET[index] = src_offset; + DST_OFFSET[index] = 0; + COUNT[index] = count; + + const int selectors = (0xFF << (8 - count)) & 0xFF; + assert(selectors != 0); + assert(selectors <= 0xFF); + + const int enabled_second_read = (src_offset + count) > 8 ? 1:0; + // selr_value = src_offset + // dst_offset_gt_src_offset = 0 + FLAGS[index] = selectors + enabled_second_read * P2_8 + P2_10 * src_offset; + index += 1; + } + } + + // PRE OPERATION objective execute the "first" incomplete "write" + // dst_offset > 0 + // count = 8 - dst_offset + + for (int dst_offset = 1; dst_offset < 8; ++dst_offset) { + const int mask = 0xFF >> dst_offset; + for (int src_offset = 0; src_offset < 8; ++src_offset) { + table_offsets[dst_offset * 8 + src_offset] = index; + for (int count = 1; count < (9 - dst_offset); ++ count) { + SRC_OFFSET[index] = src_offset; + DST_OFFSET[index] = dst_offset; + COUNT[index] = count; + + const int selectors = mask & (0xFF << (8 - (dst_offset + count))); + assert(selectors != 0); + assert(selectors <= 0xFF); + + const int enabled_second_read = (src_offset + count) > 8 ? 1:0; + const int dst_offset_gt_src_offset = dst_offset > src_offset ? 1:0; + const int selr_value = dst_offset > src_offset ? dst_offset - src_offset: src_offset - dst_offset; + // selr_value = src_offset + // dst_offset_gt_src_offset = 0 + FLAGS[index] = selectors + enabled_second_read * P2_8 + dst_offset_gt_src_offset * P2_9 + selr_value * P2_10; + index += 1; + } + } + } + println(`TABLE_OFFSETS[${length(table_offsets)}] = [` ,table_offsets, "]"); + println(`DMA_PRE_POST_TABLE_SIZE=${DMA_PRE_POST_TABLE_SIZE}`); + assert_eq(index, DMA_PRE_POST_TABLE_SIZE); + + col witness multiplicity; + lookup_proves(DMA_PRE_POST_TABLE_ID, mul: multiplicity, expressions: [FLAGS, DST_OFFSET, SRC_OFFSET, COUNT]); +} \ No newline at end of file diff --git a/precompiles/dma/pil/dma_rom.pil b/precompiles/dma/pil/dma_rom.pil new file mode 100644 index 000000000..c21f7b55b --- /dev/null +++ b/precompiles/dma/pil/dma_rom.pil @@ -0,0 +1,52 @@ +require "std_lookup.pil" + +const int DMA_ROM_TABLE_SIZE = 8 * 8 * P2_9; + +airtemplate DmaRom(int N = DMA_ROM_TABLE_SIZE) { + + col fixed DST_OFFSET; + col fixed SRC_OFFSET; + col fixed L_COUNT; + col fixed FLAGS; + col fixed PRE_COUNT; + col fixed SRC_OFFSET_AFTER_PRE; + col fixed L_COUNT64; + + int i = 0; + for (int dst_offset = 0; dst_offset < 8; ++dst_offset) { + for (int src_offset = 0; src_offset < 8; ++src_offset) { + for (int l_count = 0; l_count < P2_9; ++l_count) { + DST_OFFSET[i] = dst_offset; + SRC_OFFSET[i] = src_offset; + + if (l_count == 0) { + L_COUNT[i] = 0; + FLAGS[i] = 16; + PRE_COUNT[i] = 0; + SRC_OFFSET_AFTER_PRE[i] = src_offset; + L_COUNT64[i] = 0; + } else { + const int use_pre = dst_offset > 0; + const int pre_count = use_pre ? (((8 - dst_offset) < l_count) ? (8 - dst_offset) : l_count) : 0; + const int post_count = (l_count - pre_count) % 8; + const int use_post = post_count > 0; + const int memcpy_count = l_count - pre_count - post_count; + const int use_memcpy = memcpy_count > 0; + const int src64_inc_by_pre = (use_pre && (src_offset + pre_count) >= 8) ? 1 : 0; + const int count_lt_256 = l_count < 256 ? 1 : 0; + + L_COUNT[i] = l_count; + FLAGS[i] = use_pre + use_memcpy * 2 + use_post * 4 + src64_inc_by_pre * 8 + count_lt_256 * 16; + PRE_COUNT[i] = pre_count; + SRC_OFFSET_AFTER_PRE[i] = (src_offset + pre_count) % 8; + L_COUNT64[i] = (l_count - pre_count) / 8; + } + i += 1; + } + } + } + println(`${i} rows`); + + col witness multiplicity; + lookup_proves(DMA_ROM_ID, [DST_OFFSET, SRC_OFFSET, L_COUNT, FLAGS, PRE_COUNT, SRC_OFFSET_AFTER_PRE, L_COUNT64], multiplicity); +} \ No newline at end of file diff --git a/precompiles/dma/pil/dma_unaligned.pil b/precompiles/dma/pil/dma_unaligned.pil new file mode 100644 index 000000000..c2159613e --- /dev/null +++ b/precompiles/dma/pil/dma_unaligned.pil @@ -0,0 +1,266 @@ + +// TODO: Optimization Dma32AlignedOp + +airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) { + + const expr L1 = get_L1(); + const expr LAST = L1'; + + airval segment_id; // Id of current segment + airval segment_previous_seq_end; // Last value of `seq_end` in previous segment. + airval segment_previous_src64; // Last value of `src64` in previous segment. + airval segment_previous_dst64; // Last value of `dst64` in previous segment. + airval segment_previous_main_step; // Last value of `main_step` in previous segment. + airval segment_previous_offset; // Last value of `offset` in previous segment. + airval segment_previous_count; // Last value of `count` in previous segment. + airval segment_previous_is_mem_eq; // Last value of `is_mem_eq' in previous segment. + + + airval segment_first_bytes[8]; // bytes of next block + + airval segment_last_seq_end; // Last value of `seq_end` in current segment. + airval segment_last_src64; // Last value of `src64` in current segment. + airval segment_last_dst64; // Last value of `dst64` in current segment. + airval segment_last_main_step; // Last value of `main_step` in current segment. + airval segment_last_offset; // Last value of `offset` in current segment. + airval segment_last_count; // Last value of `count` in current segment. + airval segment_last_is_mem_eq; // Last value of `is_mem_eq' in previous segment. + + airval segment_next_bytes[8]; // bytes of next block + + airval is_last_segment; // 1 if this is the last segment, 0 otherwise. + + segment_previous_seq_end * (1 - segment_previous_seq_end) === 0; + segment_last_seq_end * (1 - segment_last_seq_end) === 0; + + is_last_segment * (1 - is_last_segment) === 0; + + // if it's last segment must be the end of sequence or padding that use end of sequence + is_last_segment * (1 - segment_last_seq_end) === 0; + + col witness bits(MAIN_STEP_BITS) main_step; + col witness bits(ADDR_W_BITS) src64; + col witness bits(ADDR_W_BITS) dst64; + col witness bits(32) count; // number of words + + col witness bits(1) seq_end; + seq_end * (1 - seq_end) === 0; + + col witness bits(1) previous_seq_end; + previous_seq_end === L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; + + col witness bits(1) is_mem_eq; // Last value of `is_mem_eq' in previous segment. + is_mem_eq * (1 - is_mem_eq) === 0; + + LAST * (seq_end - segment_last_seq_end) === 0; + LAST * (1 - seq_end) * (src64 - segment_last_src64) === 0; + LAST * (1 - seq_end) * (dst64 - segment_last_dst64) === 0; + LAST * (1 - seq_end) * (main_step - segment_last_main_step) === 0; + LAST * (1 - seq_end) * (count - segment_last_count) === 0; + LAST * (1 - seq_end) * (is_mem_eq - segment_last_is_mem_eq) === 0; + + col witness bits(1) offset_7; + col witness bits(1) offset_6; + col witness bits(1) offset_5; + col witness bits(1) offset_4; + col witness bits(1) offset_3; + col witness bits(1) offset_2; + + // To save one witness column, define offset_1 using other columns, because no offset_0 supported + // offset = 0 means aligned memcpy. + // CONSIDERATION: support offset_0 to prove also aligned memcpy. + const expr offset_7to2 = offset_7 + offset_6 + offset_5 + offset_4 + offset_3 + offset_2; + const expr offset_1 = (1 - offset_7to2); + + offset_7to2 * (offset_7to2 - 1) === 0; + const expr offset = offset_1 + offset_2 * 2 + offset_3 * 3 + offset_4 * 4 + offset_5 * 5 + + offset_6 * 6 + offset_7 * 7; + + offset_7 * (1 - offset_7) === 0; + offset_6 * (1 - offset_6) === 0; + offset_5 * (1 - offset_5) === 0; + offset_4 * (1 - offset_4) === 0; + offset_3 * (1 - offset_3) === 0; + offset_2 * (1 - offset_2) === 0; + + const expr previous_offset = L1 * (segment_previous_offset - 'offset) + 'offset; + LAST * (1 - seq_end) * (offset - segment_last_offset) === 0; + + col witness bits(8) read_bytes[8]; + + range_dual_byte(read_bytes[1], read_bytes[0]); + range_dual_byte(read_bytes[3], read_bytes[2]); + range_dual_byte(read_bytes[5], read_bytes[4]); + range_dual_byte(read_bytes[7], read_bytes[6]); + + const expr read_value[2]; + + read_value[0] = read_bytes[0] + P2_8 * read_bytes[1] + P2_16 * read_bytes[2] + P2_24 * read_bytes[3]; + read_value[1] = read_bytes[4] + P2_8 * read_bytes[5] + P2_16 * read_bytes[6] + P2_24 * read_bytes[7]; + + // byte_0, byte_1, byte_2, byte_3, byte_4, byte_5, byte_6, byte_7, byte_0', byte_1' ... + + const expr next_bytes[8]; + + // when an instance last row has seq_end = 1, means that next_bytes don't need to match + // with first read_bytes, because it's new dma operation, in this situation set value + // of segment_next_bytes to 0. + + col witness bits(1) no_last_no_seq_end; + no_last_no_seq_end === (1 - LAST) * (1 - seq_end); + + for (int i = 0; i < 8; ++i) { + // if LAST next_bytes are bytes sent to bus, if no LAST but is a seq_end, no sense + // use bytes of the next input, in this case next_bytes = 0, otherwise next_bytes are + // the next read (src64 + 1) * 8 + next_bytes[i] = LAST * segment_next_bytes[i] + no_last_no_seq_end * read_bytes[i]'; + + // if last row of previous instance aren't the the last row of an input, segment_first_bytes + // must match with bytes "received" from bus. Otherwise, zeros was sent to bus and them not + // need to match with read_bytes because are bytes of other input. + (1 - segment_previous_seq_end) * L1 * (segment_first_bytes[i] - read_bytes[i]) === 0; + + // force segment_next_bytes to zero, when it's finish of input + segment_last_seq_end * segment_next_bytes[i] == 0; + + // to force the "received" values from the bus are 0 when the last row of the previous segment + // is final of input (seq_end). + segment_previous_seq_end * L1 * segment_first_bytes[i] === 0; + } + + col witness bits(32) write_value[2]; + + write_value[0] === offset_1 * (read_bytes[1] + P2_8 * read_bytes[2] + P2_16 * read_bytes[3] + P2_24 * read_bytes[4]) + + offset_2 * (read_bytes[2] + P2_8 * read_bytes[3] + P2_16 * read_bytes[4] + P2_24 * read_bytes[5]) + + offset_3 * (read_bytes[3] + P2_8 * read_bytes[4] + P2_16 * read_bytes[5] + P2_24 * read_bytes[6]) + + offset_4 * (read_bytes[4] + P2_8 * read_bytes[5] + P2_16 * read_bytes[6] + P2_24 * read_bytes[7]) + + offset_5 * (read_bytes[5] + P2_8 * read_bytes[6] + P2_16 * read_bytes[7] + P2_24 * next_bytes[0]) + + offset_6 * (read_bytes[6] + P2_8 * read_bytes[7] + P2_16 * next_bytes[0] + P2_24 * next_bytes[1]) + + offset_7 * (read_bytes[7] + P2_8 * next_bytes[0] + P2_16 * next_bytes[1] + P2_24 * next_bytes[2]); + + write_value[1] === offset_1 * (read_bytes[5] + P2_8 * read_bytes[6] + P2_16 * read_bytes[7] + P2_24 * next_bytes[0]) + + offset_2 * (read_bytes[6] + P2_8 * read_bytes[7] + P2_16 * next_bytes[0] + P2_24 * next_bytes[1]) + + offset_3 * (read_bytes[7] + P2_8 * next_bytes[0] + P2_16 * next_bytes[1] + P2_24 * next_bytes[2]) + + offset_4 * (next_bytes[0] + P2_8 * next_bytes[1] + P2_16 * next_bytes[2] + P2_24 * next_bytes[3]) + + offset_5 * (next_bytes[1] + P2_8 * next_bytes[2] + P2_16 * next_bytes[3] + P2_24 * next_bytes[4]) + + offset_6 * (next_bytes[2] + P2_8 * next_bytes[3] + P2_16 * next_bytes[4] + P2_24 * next_bytes[5]) + + offset_7 * (next_bytes[3] + P2_8 * next_bytes[4] + P2_16 * next_bytes[5] + P2_24 * next_bytes[6]); + + // memory access + precompiled_mem_load( + sel: 1, + main_step: main_step, + addr: src64 * 8, + value: read_value + ); + + precompiled_mem_store( + sel: (1 - seq_end), + main_step: main_step, + addr: dst64 * 8, + value: write_value + ); + + // At begining of sequence + // DMA BUS [op, dst64, src64, dst_offset, src_offset, bytes, main_step] + permutation_proves(DMA_BUS_ID, [DMA_MEM_CPY, dst64 * 8, src64 * 8, 0, offset, count * 8, main_step], sel: previous_seq_end); + + const expr continue_seq_on_l1 = L1 * (1 - segment_previous_seq_end); + const expr continue_seq_on_no_l1 = (1 - L1) * (1 - 'seq_end); + + const expr new_seq_on_l1 = L1 * segment_previous_seq_end; + const expr new_seq_on_no_l1 = (1 - L1) * 'seq_end; + + // TRANSITIONS: + // + // After first element of sequence + // count = 'count - 1 + // src64 = 'src64 + 1 + // dst64 = 'dst64 + 1 + + continue_seq_on_l1 * (count - (segment_previous_count - 1)) == 0; + continue_seq_on_no_l1 * (count - ('count - 1)) === 0; + + continue_seq_on_l1 * (src64 - (segment_previous_src64 + 1)) == 0; + continue_seq_on_no_l1 * (src64 - ('src64 + 1)) === 0; + + continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + 1)) == 0; + continue_seq_on_no_l1 * (dst64 - ('dst64 + 1)) === 0; + + // LATCHS: + // + // offset = 'offset + // is_mem_eq = 'is_mem_eq + // main_step = 'main_step + continue_seq_on_l1 * (offset - segment_previous_offset) == 0; + continue_seq_on_no_l1 * (offset - 'offset) === 0; + + continue_seq_on_l1 * (is_mem_eq - segment_previous_is_mem_eq) == 0; + continue_seq_on_no_l1 * (is_mem_eq - 'is_mem_eq) === 0; + + continue_seq_on_l1 * (main_step - segment_previous_main_step) == 0; + continue_seq_on_no_l1 * (main_step - 'main_step) === 0; + + // At end of sequence + // count must be 0 at end of sequence + count * seq_end === 0; + + // SECURITY: control count no negative + // + // if the seq_end it isn't active when count = 0, in each rows continues decreasing 8 units, + // means in 2^22 rows * 2^3 = 2^25. It's secure, because if any row lies, at end of instance + // this constraint fails. + + airval last_count_chunk[2]; + range_check(expression: last_count_chunk[0], min: 0, max: 2**16-1); + range_check(expression: last_count_chunk[1], min: 0, max: 2**16-1); + last_count_chunk[0] + last_count_chunk[1] * P2_16 === segment_last_count; + + + // PADDING + // + // cancel operation sent to bus src=0, dst=0, offset=1, count=0, main_step=0 + // precompiled_mem_load_padding demostrate read addr=0 width=8 main_step=0 value=0 + // in padding rows seq_end is active, means no precompiled_mem_store + + airval padding_rows; + permutation_assumes(DMA_BUS_ID, [DMA_MEM_CPY, 0, 0, 0, 1, 0, 0], sel: padding_rows); + precompiled_mem_load_padding(padding: padding_rows); + + // CONTINUATIONS + + // AIR_ID, segment_id, seq_end, src64, dst64, count, main_step + + direct_global_update_proves(MEM_CPY_BYTE_CONT_ID, [0, // initial segment_id + 0, + 1, // initial seq_end + 0, // initial src64 + 0, // initial dst64 + 0, // initial offset + 0, // initial count + 0, + 0,0,0,0,0,0,0,0], // next bytes + sel: enable); + + + direct_update_assumes(MEM_CPY_BYTE_CONT_ID, [ segment_id, + 0, + segment_previous_seq_end, + segment_previous_src64, + segment_previous_dst64, + segment_previous_offset, + segment_previous_count, + segment_previous_main_step, + ...segment_first_bytes]); + + direct_update_proves(MEM_CPY_BYTE_CONT_ID, [ segment_id + 1, + is_last_segment, + segment_last_seq_end, + segment_last_src64, + segment_last_dst64, + segment_last_offset, + segment_last_count, + segment_last_main_step, + ...segment_next_bytes], sel: 1 - is_last_segment); + +} \ No newline at end of file diff --git a/precompiles/dma/pil/dual_range.pil b/precompiles/dma/pil/dual_range.pil new file mode 100644 index 000000000..c8dd82f55 --- /dev/null +++ b/precompiles/dma/pil/dual_range.pil @@ -0,0 +1,17 @@ +require "std_lookup.pil" + +// Template to verify two small ranges of values in the same lookup (typically used for bytes). +// The lookup table size is limited by (range1_size * range2_size). + +airtemplate DualRange(int N = 0, const int id, const int min1, const int max1, const int min2, const int max2 ) { + // Number of elements in B + const int COUNT_B = (max2 - min2 + 1); + N = (max1 - min1 + 1) * COUNT_B; + + // Repeat each A element COUNT_B times so every A value pairs with every B value + col fixed A = [min1:COUNT_B..max1:COUNT_B]...; + col fixed B = [min2..max2]...; + + col witness multiplicity; + lookup_proves(id, mul: multiplicity, expressions: [A, B]); +} \ No newline at end of file diff --git a/precompiles/dma/pil/tools.pil b/precompiles/dma/pil/tools.pil new file mode 100644 index 000000000..57c6325ff --- /dev/null +++ b/precompiles/dma/pil/tools.pil @@ -0,0 +1,13 @@ + +function get_continuation_id(const int base_cont_id, const int max_cont): int { + container proof.continuation.`${AIRTEMPLATE}` alias continuation { + int id = base_cont_id; + } + const int id = continuation.id; + assert(id < base_cont_id); + continuation.id += continuation.id; + return id; +} + +// const int continuation_id = get_new_continuation_id(MEM_CPY_BYTE_CONT_ID, 1); + diff --git a/precompiles/dma/src/dma/dma.rs b/precompiles/dma/src/dma/dma.rs new file mode 100644 index 000000000..cffd8cbd6 --- /dev/null +++ b/precompiles/dma/src/dma/dma.rs @@ -0,0 +1,305 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use rayon::prelude::*; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use zisk_pil::{DMA_ROM_ID, DUAL_RANGE_7_BITS_ID}; + +use crate::{dma::dma_rom::DmaRom, DmaInput}; +use precompiles_helpers::DmaInfo; + +#[cfg(feature = "packed")] +pub use zisk_pil::{DmaTracePacked, DmaTraceRowPacked}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{DmaTrace, DmaTraceRow}; + +#[cfg(feature = "packed")] +type DmaTraceRowType = DmaTraceRowPacked; +#[cfg(feature = "packed")] +type DmaTraceType = DmaTracePacked; + +#[cfg(not(feature = "packed"))] +type DmaTraceRowType = DmaTraceRow; +#[cfg(not(feature = "packed"))] +type DmaTraceType = DmaTrace; + +/// The `DmaSM` struct encapsulates the logic of the Dma State Machine. +pub struct DmaSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + pub rom_table_id: usize, + pub dual_range_7_bits_id: usize, + pub range_22_bits_id: usize, + pub range_24_bits_id: usize, +} + +impl DmaSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaSM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + rom_table_id: std.get_virtual_table_id(DMA_ROM_ID).expect("Failed to get dma rom ID"), + dual_range_7_bits_id: std + .get_virtual_table_id(DUAL_RANGE_7_BITS_ID) + .expect("Failed to get dual 7-bits table ID"), + range_22_bits_id: std + .get_range_id(0, 0x3F_FFFF, None) + .expect("Failed to get 22b table ID"), + range_24_bits_id: std + .get_range_id(0, 0xFF_FFFF, None) + .expect("Failed to get 24b table ID"), + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[allow(clippy::too_many_arguments)] + #[inline(always)] + pub fn process_slice( + &self, + input: &DmaInput, + trace: &mut DmaTraceRowType, + local_dual_7_bits_multiplicities: &mut [u64], + local_22_bits_values: &mut Vec, + local_24_bits_values: &mut Vec, + local_24_bits_low_values: &mut [u32], + local_rom_multiplicities: &mut [u64], + ) { + let count = DmaInfo::get_count(input.encoded); + let count_lt_256 = count < 256; + let count_ge_256 = 1 - count_lt_256 as usize; + let h_count = ((count >> 8) - count_ge_256) as u32; + trace.set_count_lt_256(count_lt_256); + trace.set_h_count(h_count); + let l_count = (count & 0xFF) as u16 + 256 * count_ge_256 as u16; + trace.set_l_count(l_count); + + // to increase performance because the 99.99% of count is < 64K => h_count < 256 + if h_count < 256 { + local_24_bits_low_values[h_count as usize] += 1; + } else { + local_24_bits_values.push(h_count); + } + + let h_src64 = input.src >> 10; + let h_dst64 = input.dst >> 10; + let l_src64 = (input.src >> 3) as u8 & 0x7F; + let l_dst64 = (input.dst >> 3) as u8 & 0x7F; + + trace.set_h_src64(h_src64); + trace.set_l_src64(l_src64); + let src_offset = input.src as u8 & 0x07; + trace.set_src_offset(src_offset); + + trace.set_h_dst64(h_dst64); + trace.set_l_dst64(l_dst64); + trace.set_dst_offset(input.dst as u8 & 0x07); + + local_22_bits_values.push(h_src64); + local_22_bits_values.push(h_dst64); + let dual_7_bits_row = ((l_src64 as usize) << 7) | l_dst64 as usize; + local_dual_7_bits_multiplicities[dual_7_bits_row] += 1; + // println!( + // "local_dual_7_bits_multiplicities[{dual_7_bits_row} ({l_src64}|{l_dst64})] = {}", + // local_dual_7_bits_multiplicities[dual_7_bits_row] + // ); + + local_rom_multiplicities[DmaRom::get_row(input.dst & 0x07, input.src & 0x07, count)] += 1; + + trace.set_main_step(input.step); + + let pre_count = DmaInfo::get_pre_count(input.encoded) as u8; + let loop_count = DmaInfo::get_loop_count(input.encoded); + let post_count = DmaInfo::get_post_count(input.encoded); + trace.set_use_pre(pre_count > 0); + trace.set_use_memcpy(loop_count > 0); + trace.set_use_post(post_count > 0); + + trace.set_src64_inc_by_pre(DmaInfo::get_src64_inc_by_pre(input.encoded) > 0); + + trace.set_pre_count(pre_count); + trace.set_l_count64((l_count - pre_count as u16 - post_count as u16) >> 3); + trace.set_src_offset_after_pre((src_offset + pre_count) % 8); + + trace.set_sel(true); + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut DmaTraceRowType) { + trace.set_count_lt_256(true); + trace.set_h_count(0); + trace.set_l_count(0); + + // to increase performance because the 99.99% of count is < 64K => h_count < 256 + trace.set_h_src64(0); + trace.set_l_src64(0); + trace.set_src_offset(0); + + trace.set_h_dst64(0); + trace.set_l_dst64(0); + trace.set_dst_offset(0); + + trace.set_main_step(0); + + trace.set_use_pre(false); + trace.set_use_memcpy(false); + trace.set_use_post(false); + + trace.set_src64_inc_by_pre(false); + + trace.set_pre_count(0); + trace.set_l_count64(0); + trace.set_src_offset_after_pre(0); + + trace.set_sel(false); + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = DmaTraceType::::new_from_vec(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); + assert!(total_inputs <= num_rows); + + tracing::debug!( + "··· Creating Dma instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + // Calculate optimal chunk size + let num_threads = rayon::current_num_threads(); + let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); + + // TODO: add new interface with u32 to std to be used with global_rom_multiplicities + // Split the add256_trace.buffer into slices matching each inner vector’s length. + let ( + global_dual_7_bits_multiplicities, + global_22_bits_values, + global_24_bits_values, + global_24_bits_low_values, + global_rom_multiplicities, + ) = flat_inputs + .par_chunks(chunk_size) + .zip(trace_rows.par_chunks_mut(chunk_size)) + .map(|(input_chunk, trace_chunk)| { + // Local array shared by this chunk + let mut local_dual_7_bits_multiplicities = vec![0u64; 1 << 14]; + let mut local_22_bits_values = Vec::::with_capacity(inputs.len() * 2); + let mut local_24_bits_values = Vec::::new(); + let mut local_24_bits_low_values = vec![0u32; 256]; + let mut local_rom_multiplicities = vec![0u64; 1 << 15]; + // Sum all local arrays into a global one + for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { + self.process_slice( + input, + trace_row, + &mut local_dual_7_bits_multiplicities, + &mut local_22_bits_values, + &mut local_24_bits_values, + &mut local_24_bits_low_values, + &mut local_rom_multiplicities, + ); + } + ( + local_dual_7_bits_multiplicities, + local_22_bits_values, + local_24_bits_values, + local_24_bits_low_values, + local_rom_multiplicities, + ) + }) + .reduce( + // Identity: create empty accumulators + || { + ( + vec![0u64; 1 << 14], + Vec::new(), + Vec::new(), + vec![0u32; 256], + vec![0u64; 1 << 15], + ) + }, + // Combine two results + |mut acc, local| { + // Merge multiplicities (element-wise addition) + for (i, &val) in local.0.iter().enumerate() { + acc.0[i] += val; + } + // Concatenate value vectors + acc.1.extend(local.1); + acc.2.extend(local.2); + // Merge low values (element-wise addition) + for (i, &val) in local.3.iter().enumerate() { + acc.3[i] += val; + } + for (i, &val) in local.4.iter().enumerate() { + acc.4[i] += val; + } + acc + }, + ); + + // for (index, value) in global_dual_7_bits_multiplicities.iter().enumerate() { + // if *value != 0 { + // println!("DUAL_7_BITS[{index}]={value}") + // } + // } + self.std + .inc_virtual_rows_ranged(self.dual_range_7_bits_id, &global_dual_7_bits_multiplicities); + self.std.range_checks(self.range_24_bits_id, global_24_bits_low_values); + self.std.inc_virtual_rows_ranged(self.rom_table_id, &global_rom_multiplicities); + + for value in global_22_bits_values { + self.std.range_check(self.range_22_bits_id, value as i64, 1); + } + for value in global_24_bits_values { + self.std.range_check(self.range_24_bits_id, value as i64, 1); + } + + if total_inputs < num_rows { + self.process_empty_slice(&mut trace_rows[total_inputs]); + let empty_row = trace_rows[total_inputs]; + trace_rows[total_inputs + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + } + + timer_stop_and_log_trace!(DMA_TRACE); + let from_trace = FromTrace::new(&mut trace); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma/dma_collector.rs b/precompiles/dma/src/dma/dma_collector.rs new file mode 100644 index 000000000..95294baa3 --- /dev/null +++ b/precompiles/dma/src/dma/dma_collector.rs @@ -0,0 +1,86 @@ +//! The `DmaCollector` module defines an collector to calculate all inputs of an instance +//! for the Dma State Machine. + +use std::any::Any; + +use zisk_common::{BusDevice, BusId, CollectCounter, ExtOperationData, OPERATION_BUS_ID, OP_TYPE}; +use zisk_core::ZiskOperationType; + +use crate::DmaInput; + +pub struct DmaCollector { + /// Collected inputs for witness computation. + pub inputs: Vec, + + /// The number of operations to collect. + pub num_operations: u64, + + /// Helper to skip instructions based on the plan's configuration. + pub collect_counter: CollectCounter, +} + +impl DmaCollector { + /// Creates a new `DmaCollector`. + /// + /// # Arguments + /// + /// * `bus_id` - The connected bus ID. + /// * `num_operations` - The number of operations to collect. + /// * `collect_skipper` - The helper to skip instructions based on the plan's configuration. + /// + /// # Returns + /// A new `ArithInstanceCollector` instance initialized with the provided parameters. + pub fn new(num_operations: u64, collect_counter: CollectCounter) -> Self { + Self { + inputs: Vec::with_capacity(num_operations as usize), + num_operations, + collect_counter, + } + } + + /// Processes data received on the bus, collecting the inputs necessary for witness computation. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A tuple where: + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if self.inputs.len() == self.num_operations as usize { + return false; + } + + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + return true; + } + + if self.collect_counter.should_skip() { + return true; + } + + let data: ExtOperationData = + data.try_into().expect("Regular Metrics: Failed to convert data"); + if let ExtOperationData::OperationDmaMemCpyData(data) = data { + self.inputs.push(DmaInput::from_memcpy(&data, data_ext)); + } else if let ExtOperationData::OperationDmaMemCmpData(data) = data { + self.inputs.push(DmaInput::from_memcmp(&data, data_ext)); + } else { + panic!("Expected ExtOperationData::OperationDmaData"); + } + + self.inputs.len() < self.num_operations as usize + } +} + +impl BusDevice for DmaCollector { + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/dma/dma_input.rs b/precompiles/dma/src/dma/dma_input.rs new file mode 100644 index 000000000..5dd7aba3a --- /dev/null +++ b/precompiles/dma/src/dma/dma_input.rs @@ -0,0 +1,54 @@ +use zisk_common::{ + OperationDmaMemCmpData, OperationDmaMemCpyData, A, B, OPERATION_BUS_DMA_MEMCMP_DATA_SIZE, + OPERATION_BUS_DMA_MEMCPY_DATA_SIZE, STEP, +}; + +#[derive(Debug)] +pub enum DmaOperation { + MemCpy, + MemCmp, + InputCpy, + MemSet, + MemCpy256, +} +#[derive(Debug)] +pub struct DmaInput { + pub src: u32, + pub dst: u32, + pub operation: DmaOperation, + pub encoded: u64, + pub count_eq: u32, + pub result: i32, + pub step: u64, // main step +} + +impl DmaInput { + pub fn from_memcpy(data: &OperationDmaMemCpyData, _data_ext: &[u64]) -> Self { + let encoded = data[OPERATION_BUS_DMA_MEMCPY_DATA_SIZE - 1]; + Self { + dst: data[A] as u32, + src: data[B] as u32, + step: data[STEP], + encoded, + count_eq: 0, + result: 0, + operation: DmaOperation::MemCpy, + } + } + + pub fn from_memcmp(data: &OperationDmaMemCmpData, _data_ext: &[u64]) -> Self { + let encoded = data[OPERATION_BUS_DMA_MEMCMP_DATA_SIZE - 2]; + let count_eq = data[OPERATION_BUS_DMA_MEMCMP_DATA_SIZE - 1] as u32; + let result = (data[OPERATION_BUS_DMA_MEMCMP_DATA_SIZE - 1] >> 32) as i32; + + Self { + dst: data[A] as u32, + src: data[B] as u32, + step: data[STEP], + encoded, + count_eq, + result, + operation: DmaOperation::MemCmp, + } + } +} diff --git a/precompiles/dma/src/dma/dma_instance.rs b/precompiles/dma/src/dma/dma_instance.rs new file mode 100644 index 000000000..9d37c60a8 --- /dev/null +++ b/precompiles/dma/src/dma/dma_instance.rs @@ -0,0 +1,117 @@ +//! The `DmaInstance` module defines an instance to perform the witness computation +//! for the Dma State Machine. +//! +//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for +//! execution plans. + +use crate::dma::dma_collector::DmaCollector; +use crate::{DmaCheckPoint, DmaSM}; +use fields::PrimeField64; +use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; +use std::sync::Arc; +use zisk_common::ChunkId; +use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; +use zisk_pil::DmaTrace; + +/// The `DmaInstance` struct represents an instance for the Dma State Machine. +/// +/// It encapsulates the `DmaSM` and its associated context, and it processes input data +/// to compute witnesses for the Dma State Machine. +pub struct DmaInstance { + /// Dma state machine. + dma_sm: Arc>, + + /// Instance context. + ictx: InstanceCtx, +} + +impl DmaInstance { + /// Creates a new `DmaInstance`. + /// + /// # Arguments + /// * `dma_sm` - An `Arc`-wrapped reference to the Dma State Machine. + /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. + /// * `bus_id` - The bus ID associated with this instance. + /// + /// # Returns + /// A new `DmaInstance` instance initialized with the provided state machine and + /// context. + pub fn new(dma_sm: Arc>, ictx: InstanceCtx) -> Self { + Self { dma_sm, ictx } + } + + pub fn build_dma_collector(&self, chunk_id: ChunkId) -> DmaCollector { + assert_eq!( + self.ictx.plan.air_id, + DmaTrace::::AIR_ID, + "DmaInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; + DmaCollector::new(num_ops, collect_counter) + } +} + +impl Instance for DmaInstance { + /// Computes the witness for the Dma execution plan. + /// + /// This method leverages the `DmaSM` to generate an `AirInstance` using the collected + /// inputs. + /// + /// # Arguments + /// * `_pctx` - The proof context, unused in this implementation. + /// + /// # Returns + /// An `Option` containing the computed `AirInstance`. + fn compute_witness( + &self, + _pctx: &ProofCtx, + _sctx: &SetupCtx, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + ) -> ProofmanResult>> { + let inputs: Vec<_> = collectors + .into_iter() + .map(|(_, collector)| collector.as_any().downcast::().unwrap().inputs) + .collect(); + + Ok(Some(self.dma_sm.compute_witness(&inputs, trace_buffer)?)) + } + + /// Retrieves the checkpoint associated with this instance. + /// + /// # Returns + /// A `CheckPoint` object representing the checkpoint of the execution plan. + fn check_point(&self) -> &CheckPoint { + &self.ictx.plan.check_point + } + + /// Retrieves the type of this instance. + /// + /// # Returns + /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). + fn instance_type(&self) -> InstanceType { + InstanceType::Instance + } + + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { + assert_eq!( + self.ictx.plan.air_id, + DmaTrace::::AIR_ID, + "DmaInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; + Some(Box::new(DmaCollector::new(num_ops, collect_counter))) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/precompiles/dma/src/dma/dma_rom.rs b/precompiles/dma/src/dma/dma_rom.rs new file mode 100644 index 000000000..b68d88c69 --- /dev/null +++ b/precompiles/dma/src/dma/dma_rom.rs @@ -0,0 +1,20 @@ +use precompiles_helpers::DmaInfo; + +pub enum DmaRom {} + +impl DmaRom { + #[allow(dead_code)] + pub fn get_row_from_encoded(encoded: u64) -> usize { + let src_offset = DmaInfo::get_src_offset(encoded); + let dst_offset = DmaInfo::get_dst_offset(encoded); + let count = DmaInfo::get_count(encoded); + Self::get_row(dst_offset as u32, src_offset as u32, count) + } + pub fn get_row(dst_offset: u32, src_offset: u32, count: usize) -> usize { + assert!(dst_offset < 8, "dst_offset too big {dst_offset}"); + assert!(src_offset < 8, "src_offset too big {src_offset}"); + assert!(count < u32::MAX as usize, "count too big {count}"); + let count = if count >= 256 { (count & 0xFF) + 256 } else { count & 0xFF }; + (dst_offset as usize * 8 + src_offset as usize) * 512 + count + } +} diff --git a/precompiles/dma/src/dma/mod.rs b/precompiles/dma/src/dma/mod.rs new file mode 100644 index 000000000..4a85a16a5 --- /dev/null +++ b/precompiles/dma/src/dma/mod.rs @@ -0,0 +1,11 @@ +#[allow(clippy::module_inception)] +mod dma; +mod dma_collector; +mod dma_input; +mod dma_instance; +mod dma_rom; + +pub use dma::*; +pub use dma_collector::*; +pub use dma_input::*; +pub use dma_instance::*; diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs new file mode 100644 index 000000000..e883d2c10 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs @@ -0,0 +1,283 @@ +use std::sync::Arc; + +use fields::PrimeField64; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; +use zisk_common::SegmentId; +use zisk_pil::Dma64AlignedAirValues; + +#[cfg(feature = "packed")] +pub use zisk_pil::{Dma64AlignedTracePacked, Dma64AlignedTraceRowPacked}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{Dma64AlignedTrace, Dma64AlignedTraceRow}; + +#[cfg(feature = "packed")] +type Dma64AlignedTraceRowType = Dma64AlignedTraceRowPacked; +#[cfg(feature = "packed")] +type Dma64AlignedTraceType = Dma64AlignedTracePacked; + +#[cfg(not(feature = "packed"))] +type Dma64AlignedTraceRowType = Dma64AlignedTraceRow; +#[cfg(not(feature = "packed"))] +type Dma64AlignedTraceType = Dma64AlignedTrace; + +use crate::{Dma64AlignedInput, DMA_64_ALIGNED_OPS_BY_ROW}; +use precompiles_helpers::DmaInfo; + +/// The `Dma64AlignedSM` struct encapsulates the logic of the Dma64Aligned State Machine. +pub struct Dma64AlignedSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + range_16_bits_id: usize, + op_x_rows: usize, +} + +impl Dma64AlignedSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `Dma64AlignedSM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + range_16_bits_id: std + .get_range_id(0, 0xFFFF, None) + .expect("Failed to get 16b table ID"), + op_x_rows: DMA_64_ALIGNED_OPS_BY_ROW, + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_input( + &self, + input: &Dma64AlignedInput, + trace: &mut [Dma64AlignedTraceRowType], + _local_16_bits_table: &mut [u32], // for input_cpy + air_values: &mut Dma64AlignedAirValues, + ) -> usize { + let rows = input.rows as usize; + let skip_count = input.skip_rows as usize * self.op_x_rows; + let initial_count = DmaInfo::get_loop_count(input.encoded) - skip_count; + let mut count64 = initial_count; + // println!( + // "DMA_64_ALIGNED INPUT {input:?} count:{count64} rows:{rows} dma_info:{}", + // DmaInfo::to_string(input.encoded) + // ); + let mut src_values_index = 0; + let mut dst64 = ((input.dst + 7) >> 3) + skip_count as u32; + let mut src64 = ((input.src + 7) >> 3) + skip_count as u32; + let mut seq_end = false; + let addr_incr_by_row = self.op_x_rows as u32; + for (irow, row) in trace.iter_mut().enumerate().take(rows) { + row.set_main_step(input.step); + row.set_is_mem_eq(input.is_mem_eq); + row.set_previous_seq_end(irow == 0 && input.skip_rows == 0); + + // calculate the first aligned address + // if dst is aligned is same address if not it's addr + 8 + row.set_dst64(dst64); + row.set_src64(src64); + dst64 += addr_incr_by_row; + src64 += addr_incr_by_row; + + row.set_count(count64 as u32); + let use_count = if count64 <= self.op_x_rows { + seq_end = true; + for index in count64..self.op_x_rows { + row.set_sel_op(index, false); + row.set_value(index, 0, 0); + row.set_value(index, 1, 0); + } + count64 + } else { + count64 -= self.op_x_rows; + self.op_x_rows + }; + row.set_seq_end(seq_end); + for index in 0..use_count { + row.set_sel_op(index, true); + let value = input.src_values[src_values_index]; + src_values_index += 1; + row.set_value(index, 0, value as u32); + row.set_value(index, 1, (value >> 32) as u32); + } + } + + if input.is_last_instance_input { + if seq_end { + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_is_mem_eq = F::ZERO; + } else { + air_values.segment_last_seq_end = F::ZERO; + air_values.segment_last_src64 = F::from_u32(src64 - addr_incr_by_row); + air_values.segment_last_dst64 = F::from_u32(dst64 - addr_incr_by_row); + air_values.segment_last_main_step = F::from_u64(input.step); + let last_count = initial_count - (rows - 1) * self.op_x_rows; + air_values.segment_last_count = F::from_u32(last_count as u32); + air_values.last_count_chunk[0] = F::from_u16(last_count as u16); + air_values.last_count_chunk[1] = F::from_u16((last_count >> 16) as u16); + air_values.segment_last_is_mem_eq = F::ZERO; + } + } + rows + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut Dma64AlignedTraceRowType) { + trace.set_main_step(0); + trace.set_is_mem_eq(false); + trace.set_dst64(0); + trace.set_src64(0); + trace.set_count(0); + for index in 0..self.op_x_rows { + trace.set_sel_op(index, false); + trace.set_value(index, 0, 0); + trace.set_value(index, 1, 0); + } + trace.set_seq_end(true); + trace.set_previous_seq_end(true); + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + segment_id: SegmentId, + is_last_segment: bool, + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = Dma64AlignedTraceType::::new_from_vec(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs + .iter() + .map(|inputs| inputs.iter().map(|input| input.rows as usize).sum::()) + .sum(); + + assert!(total_inputs > 0); + // println!("LAST INPUT: {:?}", inputs.last().unwrap()); + // println!("DMA_64_ALIGNED TOTALS total_inputs:{total_inputs} num_rows:{num_rows}"); + assert!( + total_inputs <= num_rows, + "Too many inputs, total_inputs:{total_inputs} num_rows:{num_rows}" + ); + + tracing::debug!( + "··· Creating Dma64Aligned instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_64_ALIGNED_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + let mut local_16_bits_table = vec![0u32; 1 << 16]; + let mut air_values = Dma64AlignedAirValues::::new(); + + // TODO: inputs between instances + let mut row_offset = 0; + for input in flat_inputs.iter() { + let rows_used = self.process_input( + input, + &mut trace_rows[row_offset..], + &mut local_16_bits_table, + &mut air_values, + ); + row_offset += rows_used; + } + + // padding + if row_offset < num_rows { + air_values.padding_size = F::from_u32((num_rows - row_offset) as u32); + self.process_empty_slice(&mut trace_rows[row_offset]); + let empty_row = trace_rows[row_offset]; + trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_is_mem_eq = F::ZERO; + } + + // add range check of count to check that it's a positive 32-bits number + let last_count = air_values.segment_last_count.as_canonical_u64(); + local_16_bits_table[(last_count & 0xFFFF) as usize] += 1; + local_16_bits_table[((last_count >> 16) & 0xFFFF) as usize] += 1; + + self.std.range_checks(self.range_16_bits_id, local_16_bits_table); + + let segment_id = segment_id.into(); + air_values.segment_id = F::from_usize(segment_id); + air_values.is_last_segment = F::from_bool(is_last_segment); + + let first_input = flat_inputs.first().unwrap(); + if first_input.skip_rows == 0 { + air_values.segment_previous_seq_end = F::ONE; + air_values.segment_previous_dst64 = F::from_u32(0); + air_values.segment_previous_src64 = F::from_u32(0); + air_values.segment_previous_main_step = F::from_u64(0); + air_values.segment_previous_count = F::from_u32(0); + air_values.segment_previous_is_mem_eq = F::from_bool(false); + } else { + assert!(segment_id > 0); + air_values.segment_previous_seq_end = F::ZERO; + air_values.segment_previous_dst64 = + F::from_u32(trace_rows[0].get_dst64() - self.op_x_rows as u32); + air_values.segment_previous_src64 = + F::from_u32(trace_rows[0].get_src64() - self.op_x_rows as u32); + air_values.segment_previous_main_step = F::from_u64(trace_rows[0].get_main_step()); + air_values.segment_previous_count = + F::from_u32(trace_rows[0].get_count() + self.op_x_rows as u32); + air_values.segment_previous_is_mem_eq = F::from_bool(trace_rows[0].get_is_mem_eq()); + } + #[cfg(feature = "debug_dma")] + { + println!("TRACE Dma64AlignedSM @{segment_id} [0] {:?}", trace[0]); + println!( + "TRACE Dma64AlignedSM @{segment_id} [{}] {:?}", + num_rows - 1, + trace[num_rows - 1] + ); + println!("TRACE Dma64AlignedSM AIR_VALUES {:?}", air_values); + } + timer_stop_and_log_trace!(DMA_64_ALIGNED_TRACE); + let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs new file mode 100644 index 000000000..c25df966d --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs @@ -0,0 +1,99 @@ +//! The `Dma64AlignedInstance` module defines an instance to perform the witness computation +//! for the Dma State Machine. +//! +//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for +//! execution plans. + +use crate::Dma64AlignedInput; +use std::any::Any; +use zisk_common::{BusDevice, BusId, CollectCounter, OPERATION_BUS_ID, OP_TYPE}; +use zisk_core::ZiskOperationType; + +pub struct Dma64AlignedCollector { + /// Collected inputs for witness computation. + pub inputs: Vec, + + /// The number of inputs to collect. + pub num_inputs: u64, + + /// Helper to skip instructions based on the plan's configuration. + pub collect_counter: CollectCounter, + + pub trace_offset: usize, + pub last_segment_collector: bool, +} + +impl Dma64AlignedCollector { + /// Creates a new `Dma64AlignedCollector`. + /// + /// # Arguments + /// + /// * `bus_id` - The connected bus ID. + /// * `num_inputs` - The number of inputs to collect. + /// * `collect_counter` - The helper to skip instructions based on the plan's configuration. + /// + /// # Returns + /// A new `Dma64AlignedCollector` instance initialized with the provided parameters. + pub fn new( + num_inputs: u64, + collect_counter: CollectCounter, + last_segment_collector: bool, + ) -> Self { + Self { + inputs: Vec::with_capacity(num_inputs as usize), + num_inputs, + collect_counter, + trace_offset: 0, + last_segment_collector, + } + } + + /// Processes data received on the bus, collecting the inputs necessary for witness computation. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A tuple where: + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if self.inputs.len() == self.num_inputs as usize { + return false; + } + + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + return true; + } + + let rows = Dma64AlignedInput::get_rows(data) as u32; + if rows == 0 { + return true; + } + + if let Some((skip, max_count)) = self.collect_counter.should_process(rows) { + self.inputs.push(Dma64AlignedInput::from( + data, + data_ext, + self.trace_offset, + skip as usize, + max_count as usize, + self.last_segment_collector && self.collect_counter.is_final_skip(), + )); + self.trace_offset += max_count as usize; + } + + self.inputs.len() < self.num_inputs as usize + } +} + +impl BusDevice for Dma64AlignedCollector { + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs new file mode 100644 index 000000000..b1d159ac6 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs @@ -0,0 +1,72 @@ +use precompiles_helpers::DmaInfo; +use zisk_common::{A, B, DMA_ENCODED, STEP}; + +use crate::DMA_64_ALIGNED_OPS_BY_ROW; + +#[derive(Debug)] +pub struct Dma64AlignedInput { + pub src: u32, + pub dst: u32, + pub is_first_instance_input: bool, + pub is_last_instance_input: bool, + pub is_mem_eq: bool, + pub trace_offset: u32, // offset inside trace to paralelize + pub skip_rows: u32, // inside input how many rows skip + pub rows: u32, // number of rows used + pub step: u64, + pub encoded: u64, + pub src_values: Vec, +} + +impl Dma64AlignedInput { + pub fn get_rows(data: &[u64]) -> usize { + let encoded = data[DMA_ENCODED]; + if DmaInfo::get_dst_offset(encoded) == DmaInfo::get_src_offset(encoded) { + let count = DmaInfo::get_loop_count(encoded); + if count > 0 { + count.div_ceil(DMA_64_ALIGNED_OPS_BY_ROW) + } else { + 0 + } + } else { + 0 + } + } + pub fn get_count(data: &[u64]) -> usize { + let encoded = data[DMA_ENCODED]; + if DmaInfo::get_dst_offset(encoded) == DmaInfo::get_src_offset(encoded) { + DmaInfo::get_loop_count(encoded) + } else { + 0 + } + } + pub fn from( + data: &[u64], + data_ext: &[u64], + trace_offset: usize, + skip_rows: usize, + max_rows: usize, + is_last_instance_input: bool, + ) -> Self { + let encoded = data[DMA_ENCODED]; + let pre_count = DmaInfo::get_pre_count(encoded) as u32; + let skip_count = skip_rows * DMA_64_ALIGNED_OPS_BY_ROW; + let data_offset = DmaInfo::get_loop_data_offset(encoded) + skip_count; + let count = DmaInfo::get_loop_count(encoded) - skip_count; + let total_rows = DmaInfo::get_loop_count(encoded).div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); + let rows = std::cmp::min(total_rows - skip_rows, max_rows) as u32; + Self { + dst: data[A] as u32 + pre_count, + src: data[B] as u32 + pre_count, + trace_offset: trace_offset as u32, + is_first_instance_input: trace_offset == 0, + is_last_instance_input, + step: data[STEP], + skip_rows: skip_rows as u32, + rows, + encoded, + src_values: data_ext[data_offset..data_offset + count].to_vec(), + is_mem_eq: false, + } + } +} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs new file mode 100644 index 000000000..054d5c847 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs @@ -0,0 +1,141 @@ +//! The `Dma64AlignedInstance` module defines an instance to perform the witness computation +//! for the Dma State Machine. +//! +//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for +//! execution plans. + +use crate::{Dma64AlignedCollector, Dma64AlignedSM, DmaCheckPoint}; +use fields::PrimeField64; +use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; +use std::sync::Arc; +use zisk_common::ChunkId; +use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; +use zisk_pil::Dma64AlignedTrace; + +/// The `Dma64AlignedInstance` struct represents an instance for the Dma State Machine. +/// +/// It encapsulates the `Dma64AlignedSM` and its associated context, and it processes input data +/// to compute witnesses for the Dma64Aligned State Machine. +pub struct Dma64AlignedInstance { + /// Dma state machine. + dma_64_aligned_sm: Arc>, + + /// Instance context. + ictx: InstanceCtx, + + /// Flag to define that it's last segment + is_last_segment: bool, +} + +impl Dma64AlignedInstance { + /// Creates a new `Dma64AlignedInstance`. + /// + /// # Arguments + /// * `dma_64_aligned_sm` - An `Arc`-wrapped reference to the Dma 64 Aligned State Machine. + /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. + /// * `bus_id` - The bus ID associated with this instance. + /// + /// # Returns + /// A new `Dma64AlignedInstance` instance initialized with the provided state machine and + /// context. + pub fn new(dma_64_aligned_sm: Arc>, ictx: InstanceCtx) -> Self { + let is_last_segment = { + let meta = ictx.plan.meta.as_ref().unwrap(); + let checkpoint = meta.downcast_ref::().unwrap(); + checkpoint.is_last_segment + }; + Self { dma_64_aligned_sm, ictx, is_last_segment } + } + + pub fn build_dma_collector(&self, chunk_id: ChunkId) -> Dma64AlignedCollector { + assert_eq!( + self.ictx.plan.air_id, + Dma64AlignedTrace::::AIR_ID, + "Dma64AlignedInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; + Dma64AlignedCollector::new( + num_inputs, + collect_counter, + Some(chunk_id) == collect_info.last_chunk, + ) + } +} + +impl Instance for Dma64AlignedInstance { + /// Computes the witness for the Dma execution plan. + /// + /// This method leverages the `Dma64AlignedSM` to generate an `AirInstance` using the collected + /// inputs. + /// + /// # Arguments + /// * `_pctx` - The proof context, unused in this implementation. + /// + /// # Returns + /// An `Option` containing the computed `AirInstance`. + fn compute_witness( + &self, + _pctx: &ProofCtx, + _sctx: &SetupCtx, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + ) -> ProofmanResult>> { + let inputs: Vec<_> = collectors + .into_iter() + .map(|(_, collector)| { + collector.as_any().downcast::().unwrap().inputs + }) + .collect(); + // Extract segment id from instance context + let segment_id = self.ictx.plan.segment_id.unwrap(); + + Ok(Some(self.dma_64_aligned_sm.compute_witness( + &inputs, + segment_id, + self.is_last_segment, + trace_buffer, + )?)) + } + + /// Retrieves the checkpoint associated with this instance. + /// + /// # Returns + /// A `CheckPoint` object representing the checkpoint of the execution plan. + fn check_point(&self) -> &CheckPoint { + &self.ictx.plan.check_point + } + + /// Retrieves the type of this instance. + /// + /// # Returns + /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). + fn instance_type(&self) -> InstanceType { + InstanceType::Instance + } + + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { + assert_eq!( + self.ictx.plan.air_id, + Dma64AlignedTrace::::AIR_ID, + "Dma64AlignedInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; + Some(Box::new(Dma64AlignedCollector::new( + num_inputs, + collect_counter, + Some(chunk_id) == collect_info.last_chunk, + ))) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/precompiles/dma/src/dma_64_aligned/mod.rs b/precompiles/dma/src/dma_64_aligned/mod.rs new file mode 100644 index 000000000..9f48724d5 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/mod.rs @@ -0,0 +1,10 @@ +#[allow(clippy::module_inception)] +mod dma_64_aligned; +mod dma_64_aligned_collector; +mod dma_64_aligned_input; +mod dma_64_aligned_instance; + +pub use dma_64_aligned::*; +pub use dma_64_aligned_collector::*; +pub use dma_64_aligned_input::*; +pub use dma_64_aligned_instance::*; diff --git a/precompiles/dma/src/dma_bus_device.rs b/precompiles/dma/src/dma_bus_device.rs new file mode 100644 index 000000000..4194cd82e --- /dev/null +++ b/precompiles/dma/src/dma_bus_device.rs @@ -0,0 +1,195 @@ +//! The `DmaCounter` module defines a counter for tracking dma-related operations +//! sent over the data bus. It connects to the bus and gathers metrics for specific +//! `ZiskOperationType::Dma` instructions. + +use precompiles_common::MemProcessor; +use precompiles_helpers::DmaInfo; +use std::ops::Add; +use zisk_common::{BusDevice, BusDeviceMode, BusId, Metrics, B, OPERATION_BUS_ID, OP_TYPE}; +use zisk_common::{A, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; +use zisk_core::ZiskOperationType; + +use crate::{generate_dma_mem_inputs, skip_dma_mem_inputs, DMA_64_ALIGNED_OPS_BY_ROW}; + +/// The `DmaCounter` struct represents a counter that monitors and measures +/// dma-related operations on the data bus. +/// +/// It tracks specific operation types (`ZiskOperationType`) and updates counters for each +/// accepted operation type whenever data is processed on the bus. +#[derive(Debug)] +pub struct DmaCounterInputGen { + /// sizes of memcpy + pub dma_pre_post_ops: usize, + pub dma_ops: usize, + pub dma_unaligned_inputs: usize, + pub dma_unaligned_rows: usize, + pub dma_64_aligned_inputs: usize, + pub dma_64_aligned_rows: usize, + + /// Bus device mode (counter or input generator). + pub mode: BusDeviceMode, +} + +impl DmaCounterInputGen { + /// Creates a new instance of `DmaCounter`. + /// + /// # Arguments + /// * `mode` - The ID of the bus to which this counter is connected. + /// + /// # Returns + /// A new `DmaCounter` instance. + pub fn new(mode: BusDeviceMode) -> Self { + Self { + dma_pre_post_ops: 0, + dma_ops: 0, + dma_unaligned_inputs: 0, + dma_64_aligned_inputs: 0, + dma_unaligned_rows: 0, + dma_64_aligned_rows: 0, + mode, + } + } + + /// Retrieves the count of instructions for a specific `ZiskOperationType`. + /// + /// # Arguments + /// * `dst` - The destination address of operation. + /// * `src` - The source address of operation. + /// * `count` - The bytes of operation. + pub fn inst_count_memcpy(&mut self, dst: u64, src: u64, count: usize) { + let dst_offset = dst & 0x07; + let src_offset = src & 0x07; + + // offset => max bytes is 8 - offset + if count > 0 { + let remaining = if dst_offset > 0 { + self.dma_pre_post_ops += 1; + count - std::cmp::min(8 - dst_offset as usize, count) + } else { + count + }; + + if (remaining % 8) > 0 { + // adding a post because last write isn't full (8-bytes) + self.dma_pre_post_ops += 1; + } + if dst_offset == src_offset { + // println!( + // "count: {count} remaining: {remaining} self.dma_64_aligned_ops: {}", + // self.dma_64_aligned_rows + // ); + let rows = (remaining >> 3).div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); + self.dma_64_aligned_rows += rows; + self.dma_64_aligned_inputs += 1; + } else if remaining > 7 { + // check remaining because unaligned add an extra row for each unaligned, means + // if remaming >> 3 is 0, add extra row. + // on unalignmed_ops, each dst write use its src read and next src read also. + // the last src read don't have write. + self.dma_unaligned_rows += (remaining >> 3) + 1; + self.dma_unaligned_inputs += 1; + } + } + self.dma_ops += 1; + } + + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + data_ext: &[u64], + mem_processors: &mut P, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if data[OP_TYPE] as u32 != ZiskOperationType::Dma as u32 { + return true; + } + + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_dma_mem_inputs(data, data_ext, true, mem_processors); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + if skip_dma_mem_inputs(data, data_ext, mem_processors) { + return true; + } + generate_dma_mem_inputs(data, data_ext, false, mem_processors); + } + } + + true + } +} + +impl Metrics for DmaCounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. + /// + /// # Returns + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, data: &[u64]) { + let dst = data[A]; + let src = data[B]; + let count = DmaInfo::get_count(data[OPERATION_PRECOMPILED_BUS_DATA_SIZE]); + self.inst_count_memcpy(dst, src, count); + } + + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for DmaCounterInputGen { + type Output = DmaCounterInputGen; + + /// Combines two `DmaCounter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `DmaCounter` instance. + /// * `other` - The second `DmaCounter` instance. + /// + /// # Returns + /// A new `DmaCounter` with combined counters. + fn add(self, other: Self) -> DmaCounterInputGen { + DmaCounterInputGen { + dma_pre_post_ops: self.dma_pre_post_ops + other.dma_pre_post_ops, + dma_ops: self.dma_ops + other.dma_ops, + dma_unaligned_inputs: self.dma_unaligned_inputs + other.dma_unaligned_inputs, + dma_64_aligned_inputs: self.dma_64_aligned_inputs + other.dma_64_aligned_inputs, + dma_unaligned_rows: self.dma_unaligned_rows + other.dma_unaligned_rows, + dma_64_aligned_rows: self.dma_64_aligned_rows + other.dma_64_aligned_rows, + mode: self.mode, + } + } +} + +impl BusDevice for DmaCounterInputGen { + /// Provides a dynamic reference for downcasting purposes. + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/dma_constants.rs b/precompiles/dma/src/dma_constants.rs new file mode 100644 index 000000000..d85d443a3 --- /dev/null +++ b/precompiles/dma/src/dma_constants.rs @@ -0,0 +1,13 @@ +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; + +pub const PARAMS: usize = 4; +pub const READ_PARAMS: usize = 2; +pub const DIRECT_READ_PARAMS: usize = 1; +pub const WRITE_PARAMS: usize = 1; +pub const RESULT_PARAMS: usize = 1; +pub const PARAM_CHUNKS: usize = 4; +pub const START_READ_PARAMS: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + PARAMS; +pub const START_WRITE_PARAMS: usize = + START_READ_PARAMS + READ_PARAMS * PARAM_CHUNKS + RESULT_PARAMS; +pub const WRITE_ADDR_PARAM: usize = READ_PARAMS + DIRECT_READ_PARAMS; +pub const DMA_64_ALIGNED_OPS_BY_ROW: usize = 4; diff --git a/precompiles/dma/src/dma_gen_mem_inputs.rs b/precompiles/dma/src/dma_gen_mem_inputs.rs new file mode 100644 index 000000000..35a62564e --- /dev/null +++ b/precompiles/dma/src/dma_gen_mem_inputs.rs @@ -0,0 +1,226 @@ +use precompiles_common::MemBusHelpers; +use precompiles_common::MemProcessor; +use precompiles_helpers::{DmaHelpers, DmaInfo}; +use zisk_common::{A, B, DMA_ENCODED, OP, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; +use zisk_core::{zisk_ops::ZiskOp, EXTRA_PARAMS}; + +pub fn generate_dma_mem_inputs( + data: &[u64], + data_ext: &[u64], + _only_counters: bool, + mem_processors: &mut P, +) { + let dst = data[A]; + let src = data[B]; + let encoded = data[DMA_ENCODED]; + + let dst64 = (dst & !0x07) as u32; + let src64 = (src & !0x07) as u32; + let main_step = data[STEP]; + let pre_count = DmaInfo::get_pre_count(encoded) as u64; + let dst_offset = dst & 0x07; + let src_offset = src & 0x07; + let aligned = dst_offset == src_offset; + + // NOTE: for dual memories it's very important to keep the order of loads and stores because + // stores happend after loads. + + MemBusHelpers::mem_aligned_load( + EXTRA_PARAMS as u32, + main_step, + DmaInfo::get_count(encoded) as u64, + mem_processors, + ); + + if pre_count > 0 { + let pre_data_offset = DmaInfo::get_pre_data_offset(encoded); + let read_value = data_ext[pre_data_offset]; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@pre 0x{src64:08X} S:{main_step} V:{read_value} (0x{read_value:016X})"); + + MemBusHelpers::mem_aligned_load(src64, main_step, read_value, mem_processors); + + // pre-load of write address before unaligned write + let pre_value = data_ext[DmaInfo::get_pre_write_offset(encoded)]; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@pre-p 0x{dst64:08X} S:{main_step} V:{pre_value} (0x{pre_value:016X})"); + + MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, mem_processors); + + let write_value = if DmaInfo::is_double_read_pre(encoded) { + let second_read_value = data_ext[pre_data_offset + 1]; + #[cfg(feature = "debug_dma")] + println!( + "DMA: mem_aligned_load@pre2 0x{:08X} S:{main_step} V:{second_read_value} (0x{second_read_value:016X})", + src64 + 8 + ); + MemBusHelpers::mem_aligned_load( + src64 + 8, + main_step, + second_read_value, + mem_processors, + ); + DmaHelpers::calculate_write_value( + dst_offset, + src_offset, + pre_count, + pre_value, + &[read_value, second_read_value], + ) + } else { + DmaHelpers::calculate_write_value( + dst_offset, + src_offset, + pre_count, + pre_value, + &[read_value], + ) + }; + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write@pre 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); + + MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, mem_processors); + } + + // this is part of words loop + let post_count = DmaInfo::get_post_count(encoded) as u64; + let loop_count = DmaInfo::get_loop_count(encoded); + if loop_count > 0 { + let loop_src = src as u32 + pre_count as u32; + let dst64 = (dst as u32 + pre_count as u32) & !0x07; + let src64 = loop_src & !0x07; + let loop_data_offset = DmaInfo::get_loop_data_offset(encoded); + let loop_data_count = DmaInfo::get_loop_count(encoded); + let loop_src_data_end = + loop_data_offset + loop_data_count + ((loop_src & 0x07) > 0) as usize; + if data_ext.len() <= loop_data_offset || data_ext.len() < loop_src_data_end { + println!("PRE-CRASH data_ext[{loop_data_offset}..{loop_src_data_end}] data_ext.len() = {} DATA={data:?} INFO{}", data_ext.len(), DmaInfo::to_string(encoded)); + } + let values = &data_ext[loop_data_offset..loop_src_data_end]; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load_from_slice 0x{src64:08X} S:{main_step} V:{values:?}"); + + MemBusHelpers::mem_aligned_load_from_slice(src64, main_step, values, mem_processors); + + let src_offset = (src_offset + pre_count) & 0x07; + if aligned { + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write_from_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); + MemBusHelpers::mem_aligned_write_from_slice(dst64, main_step, values, mem_processors); + } else { + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write_from_read_unaligned_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); + MemBusHelpers::mem_aligned_write_from_read_unaligned_slice( + dst64, + main_step, + src_offset as u8, + values, + mem_processors, + ); + } + } + if post_count > 0 { + let src_offset = src & 0x07; + + let post_data_offset = DmaInfo::get_post_data_offset(encoded); + let src64 = (src as u32 + pre_count as u32 + loop_count as u32 * 8) & !0x07; + let dst64 = dst as u32 + pre_count as u32 + loop_count as u32 * 8; + let read_value = data_ext[post_data_offset]; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@post 0x{src64:08X} S:{main_step} V:{read_value} (0x{read_value:016X})"); + + MemBusHelpers::mem_aligned_load(src64, main_step, read_value, mem_processors); + + // pre-load of write address before unaligned write + let pre_value = data_ext[DmaInfo::get_post_write_offset(encoded)]; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@post-p 0x{dst64:08X} S:{main_step} V:{pre_value} (0x{pre_value:016X})"); + + MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, mem_processors); + + let write_value = if DmaInfo::is_double_read_post(encoded) { + let second_read_value = data_ext[post_data_offset + 1]; + #[cfg(feature = "debug_dma")] + println!( + "DMA: mem_aligned_load@post2 0x{:08X} S:{main_step} V:{second_read_value} (0x{second_read_value:016X})", + src64 + 8 + ); + MemBusHelpers::mem_aligned_load( + src64 + 8, + main_step, + second_read_value, + mem_processors, + ); + DmaHelpers::calculate_write_value( + 0, // in post offset it's 0 + (src_offset + pre_count) & 0x07, // src_offset it's modified by pre, aligned/unaligned no change offset + post_count, + pre_value, + &[read_value, second_read_value], + ) + } else { + DmaHelpers::calculate_write_value( + 0, // in post offset it's 0 + (src_offset + pre_count) & 0x07, // src_offset it's modified by pre, aligned/unaligned no change offset + post_count, + pre_value, + &[read_value], + ) + }; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write@post 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); + MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, mem_processors); + } +} + +pub fn skip_dma_mem_inputs( + data: &[u64], + _data_ext: &[u64], + mem_processors: &mut P, +) -> bool { + let dst = data[A]; + let src = data[B]; + let op = data[OP] as u8; + + // A memcmp operation has two parts, any of them could be empty. + // - equal part, means that bytes of src and dst are the same (count_eq) + // - different part, at maximum one byte, to obtain difference (count - count_eq) + let count = DmaInfo::get_count(data[DMA_ENCODED]) as u64; + let use_count = match op { + ZiskOp::DMA_MEMCPY => count, + ZiskOp::DMA_MEMCMP => std::cmp::min( + count, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] + 1, // count_eq + 1 (different byte) + ), + _ => panic!("Invalid operation inside skip_dma_mem_inputs (op:{op})"), + }; + // calculate range for dst and src to verify if any of them are included + // in the memcollector addresses. + + let dst64_from = dst as u32 & !0x07; + let src64_from = src as u32 & !0x07; + let dst64_to = (dst + use_count + 7) as u32 & !0x07; + let src64_to = (src + use_count + 7) as u32 & !0x07; + + if !mem_processors.skip_addr(EXTRA_PARAMS as u32) { + return false; + } + + if !mem_processors.skip_addr_range(dst64_from, dst64_to) { + return false; + } + + if !mem_processors.skip_addr_range(src64_from, src64_to) { + return false; + } + + // If any mem_collector includes this addresses we could skip this precompiles + // at mem input data generation. + true +} diff --git a/precompiles/dma/src/dma_manager.rs b/precompiles/dma/src/dma_manager.rs new file mode 100644 index 000000000..57ff3add2 --- /dev/null +++ b/precompiles/dma/src/dma_manager.rs @@ -0,0 +1,99 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use pil_std_lib::Std; +use proofman_common::ProofCtx; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, Plan, Planner}; +use zisk_pil::{Dma64AlignedTrace, DmaPrePostTrace, DmaTrace, DmaUnalignedTrace, ZiskProofValues}; + +use crate::{ + Dma64AlignedInstance, Dma64AlignedSM, DmaCounterInputGen, DmaInstance, DmaPlanner, + DmaPrePostInstance, DmaPrePostSM, DmaSM, DmaUnalignedInstance, DmaUnalignedSM, +}; + +/// The `DmaManager` struct represents the Dma manager, +/// which is responsible for managing the Dma state machine and its table state machine. +#[allow(dead_code)] +pub struct DmaManager { + /// Dma state machine + dma_sm: Arc>, + dma_pre_post_sm: Arc>, + dma_64_aligned_sm: Arc>, + dma_unaligned_sm: Arc>, +} + +impl DmaManager { + /// Creates a new instance of `DmaManager`. + /// + /// # Returns + /// An `Arc`-wrapped instance of `DmaManager`. + pub fn new(std: Arc>) -> Arc { + let dma_sm = DmaSM::new(std.clone()); + let dma_pre_post_sm = DmaPrePostSM::new(std.clone()); + let dma_64_aligned_sm = Dma64AlignedSM::new(std.clone()); + let dma_unaligned_sm = DmaUnalignedSM::new(std); + + Arc::new(Self { dma_sm, dma_pre_post_sm, dma_64_aligned_sm, dma_unaligned_sm }) + } + + pub fn build_dma_counter(&self, asm_execution: bool) -> DmaCounterInputGen { + match asm_execution { + true => DmaCounterInputGen::new(BusDeviceMode::CounterAsm), + false => DmaCounterInputGen::new(BusDeviceMode::Counter), + } + } + + pub fn build_dma_input_generator(&self) -> DmaCounterInputGen { + DmaCounterInputGen::new(BusDeviceMode::InputGenerator) + } +} + +impl ComponentBuilder for DmaManager { + /// Builds a planner to plan Dma-related instances. + /// + /// # Returns + /// A boxed implementation of `RegularPlanner`. + fn build_planner(&self) -> Box { + // Get the number of Dmas that a single Dma instance can handle + Box::new(DmaPlanner::::new()) + } + + /// Builds an inputs data collector for Dma operations. + /// + /// # Arguments + /// * `ictx` - The context of the instance, containing the plan and its associated + /// configurations. + /// + /// # Returns + /// A boxed implementation of `BusDeviceInstance` specific to the requested `air_id` instance. + /// + /// # Panics + /// Panics if the provided `air_id` is not supported. + fn build_instance(&self, ictx: InstanceCtx) -> Box> { + match ictx.plan.air_id { + DmaTrace::::AIR_ID => Box::new(DmaInstance::new(self.dma_sm.clone(), ictx)), + DmaPrePostTrace::::AIR_ID => { + Box::new(DmaPrePostInstance::new(self.dma_pre_post_sm.clone(), ictx)) + } + Dma64AlignedTrace::::AIR_ID => { + Box::new(Dma64AlignedInstance::new(self.dma_64_aligned_sm.clone(), ictx)) + } + DmaUnalignedTrace::::AIR_ID => { + Box::new(DmaUnalignedInstance::new(self.dma_unaligned_sm.clone(), ictx)) + } + _ => { + panic!("DmaBuilder::get_instance() Unsupported air_id: {:?}", ictx.plan.air_id) + } + } + } + + fn configure_instances(&self, pctx: &ProofCtx, plannings: &[Plan]) { + let enable_dma_64_aligned = + plannings.iter().any(|p| p.air_id == Dma64AlignedTrace::::AIR_ID); + let enable_dma_unaligned = + plannings.iter().any(|p| p.air_id == DmaUnalignedTrace::::AIR_ID); + let mut proof_values = ZiskProofValues::from_vec_guard(pctx.get_proof_values()); + proof_values.enable_dma_64_aligned = F::from_bool(enable_dma_64_aligned); + proof_values.enable_dma_unaligned = F::from_bool(enable_dma_unaligned); + } +} diff --git a/precompiles/dma/src/dma_planner.rs b/precompiles/dma/src/dma_planner.rs new file mode 100644 index 000000000..e031bd161 --- /dev/null +++ b/precompiles/dma/src/dma_planner.rs @@ -0,0 +1,242 @@ +//! The `DmaPlanner` module defines a planner for generating execution plans specific to +//! arithmetic operations. +//! +//! It organizes execution plans for both regular instances and table instances, +//! leveraging arithmetic operation counts and metadata to construct detailed plans. + +use crate::DmaCounterInputGen; +use std::any::Any; +use std::collections::HashMap; + +use fields::PrimeField64; +use zisk_common::{ + BusDeviceMetrics, CheckPoint, ChunkId, CollectCounter, InstanceType, Plan, Planner, SegmentId, +}; +use zisk_pil::{Dma64AlignedTrace, DmaPrePostTrace, DmaTrace, DmaUnalignedTrace}; + +/// The `DmaPlanner` struct organizes execution plans for arithmetic instances and tables. +/// +/// It allows adding metadata about instances and tables and generates plans +/// based on the provided counters. +#[derive(Default)] +pub struct DmaPlanner { + _marker: std::marker::PhantomData, +} + +#[derive(Default)] +pub struct DmaCheckPoint { + pub chunks: HashMap, + pub last_chunk: Option, + pub is_last_segment: bool, +} + +/// Macro to generate a plan function for a specific field of a struct. +/// +/// This macro creates a function that generates checkpoints from counts across multiple chunks, +/// allowing you to specify which field of the struct to use for counting. +/// +/// # Macro Arguments +/// * `$fn_name` - The name of the generated function. +/// * `$type` - The struct type containing the count field (must have a `chunk_id: ChunkId` field). +/// * `$field` - The field name to use as the count value (must be `u64`). +/// +/// # Generated Function +/// The generated function has the signature: +/// ```ignore +/// pub fn $fn_name( +/// counts: &[$type], +/// size: u64, +/// ) -> Vec<(CheckPoint, , bool>)> +/// ``` +/// +/// # Example +/// ```ignore +/// define_plan_for_field!(plan_by_inst_count, InstFropsCount, inst_count); +/// define_plan_for_field!(plan_by_frops_count, InstFropsCount, frops_count); +/// ``` +macro_rules! define_plan_for_field { + ($fn_name:ident, $type:ty, $field:ident) => { + define_plan_for_field!($fn_name, $type, $field, false, $field); + }; + ($fn_name:ident, $type:ty, $field:ident, $field_inputs: ident) => { + define_plan_for_field!($fn_name, $type, $field, true, $field_inputs); + }; + ($fn_name:ident, $type:ty, $field:ident, $has_input_counter: literal, $field_inputs: ident) => { + pub fn $fn_name( + counts: &Vec<(ChunkId, Box)>, + size: u64, + ) -> Vec<(CheckPoint, DmaCheckPoint)> { + if counts.is_empty() || size == 0 { + return vec![]; + } + // let tag = stringify!($field); + // let total = counts.len(); + // println!("plan_{tag} counts:{total} size:{size}"); + let mut checkpoints = Vec::new(); + let mut current_scope = + DmaCheckPoint { chunks: HashMap::new(), last_chunk: None, is_last_segment: false }; + let mut remaining_size = size; // Remaining size for the current scope. + + for (current_chunk, dyn_counter) in counts.iter() { + let counter = (**dyn_counter).as_any().downcast_ref::<$type>().unwrap(); + // println!("counter: {:?}", counter); + let mut inst_count = counter.$field as u64; + let mut cumulative_offset = 0u64; // Reset cumulative offset for each chunk. + + while inst_count > 0 { + let checkpoint_size = remaining_size.min(inst_count); + // println!("plan_{tag} C:{}/{total} #{current_chunk} I:{remaining_size}/{size} +{checkpoint_size}/{inst_count} skip:{cumulative_offset} count:{checkpoint_size}", index+1); + + current_scope.chunks.insert( + *current_chunk, + ( + // Use input counter to calculate the capacity of collector inputs vector, used + // for state machines that has different number of rows by input. + if $has_input_counter { + counter.$field_inputs as u64 + } else { + checkpoint_size + }, + CollectCounter::new(cumulative_offset as u32, checkpoint_size as u32), + ), + ); + current_scope.last_chunk = Some(*current_chunk); + + cumulative_offset += checkpoint_size; + inst_count -= checkpoint_size; + remaining_size -= checkpoint_size; + + if remaining_size == 0 { + // println!("plan_{tag} adding instance .... inst_count = {inst_count}"); + let keys = current_scope.chunks.keys().cloned().collect::>(); + checkpoints + .push((CheckPoint::Multiple(keys), std::mem::take(&mut current_scope))); + remaining_size = size; + } + } + } + // println!("plan_{tag} final counters interation"); + // Push any remaining checkpoints into the result. + if !current_scope.chunks.is_empty() { + let keys = current_scope.chunks.keys().cloned().collect::>(); + current_scope.is_last_segment = true; + checkpoints.push((CheckPoint::Multiple(keys), std::mem::take(&mut current_scope))); + } else if let Some(last) = checkpoints.last_mut() { + last.1.is_last_segment = true; + } + + checkpoints + } + }; +} + +impl DmaPlanner { + /// Creates a new `DmaPlanner`. + /// + /// # Returns + /// A new `DmaPlanner` instance with no preconfigured instances or tables. + pub fn new() -> Self { + Self { _marker: std::marker::PhantomData } + } + + define_plan_for_field!(plan_dma_controller, DmaCounterInputGen, dma_ops); + define_plan_for_field!(plan_dma_pre_post, DmaCounterInputGen, dma_pre_post_ops); + define_plan_for_field!( + plan_dma_unaligned, + DmaCounterInputGen, + dma_unaligned_rows, + dma_unaligned_inputs + ); + define_plan_for_field!( + plan_dma_64_aligned, + DmaCounterInputGen, + dma_64_aligned_rows, + dma_64_aligned_inputs + ); +} + +impl Planner for DmaPlanner { + /// Generates execution plans for Dma instances. + /// + /// # Arguments + /// * `counters` - A vector of counters, each associated with a `ChunkId` and `DmaCounter` + /// metrics data. + /// + /// # Returns + /// A vector of `Plan` instances representing execution configurations for the instances + /// + /// # Panics + /// Panics if any counter cannot be downcasted to an `DmaCounter`. + fn plan(&self, counters: Vec<(ChunkId, Box)>) -> Vec { + let mut dma_plans: Vec = + Self::plan_dma_controller(&counters, DmaTrace::::NUM_ROWS as u64) + .into_iter() + .map(|(check_point, collect_info)| { + let converted: Box = Box::new(collect_info); + Plan::new( + DmaTrace::::AIRGROUP_ID, + DmaTrace::::AIR_ID, + None, + InstanceType::Instance, + check_point, + Some(converted), + ) + }) + .collect(); + + let pre_post_plans: Vec = + Self::plan_dma_pre_post(&counters, DmaPrePostTrace::::NUM_ROWS as u64) + .into_iter() + .map(|(check_point, collect_info)| { + let converted: Box = Box::new(collect_info); + Plan::new( + DmaPrePostTrace::::AIRGROUP_ID, + DmaPrePostTrace::::AIR_ID, + None, + InstanceType::Instance, + check_point, + Some(converted), + ) + }) + .collect(); + dma_plans.extend(pre_post_plans); + + let aligned_plans: Vec = + Self::plan_dma_64_aligned(&counters, Dma64AlignedTrace::::NUM_ROWS as u64) + .into_iter() + .enumerate() + .map(|(segment_id, (check_point, collect_info))| { + let converted: Box = Box::new(collect_info); + Plan::new( + Dma64AlignedTrace::::AIRGROUP_ID, + Dma64AlignedTrace::::AIR_ID, + Some(SegmentId(segment_id)), + InstanceType::Instance, + check_point, + Some(converted), + ) + }) + .collect(); + dma_plans.extend(aligned_plans); + + let unaligned_plans: Vec = + Self::plan_dma_unaligned(&counters, DmaUnalignedTrace::::NUM_ROWS as u64) + .into_iter() + .enumerate() + .map(|(segment_id, (check_point, collect_info))| { + let converted: Box = Box::new(collect_info); + Plan::new( + DmaUnalignedTrace::::AIRGROUP_ID, + DmaUnalignedTrace::::AIR_ID, + Some(SegmentId(segment_id)), + InstanceType::Instance, + check_point, + Some(converted), + ) + }) + .collect(); + dma_plans.extend(unaligned_plans); + + dma_plans + } +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs new file mode 100644 index 000000000..3b04aa360 --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs @@ -0,0 +1,350 @@ +use std::sync::Arc; + +use fields::PrimeField64; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use rayon::{ + iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}, + slice::{ParallelSlice, ParallelSliceMut}, +}; +use zisk_pil::{DMA_PRE_POST_TABLE_ID, DMA_PRE_POST_TABLE_SIZE, DUAL_RANGE_BYTE_ID}; + +#[cfg(feature = "packed")] +pub use zisk_pil::{DmaPrePostTracePacked, DmaPrePostTraceRowPacked}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{DmaPrePostTrace, DmaPrePostTraceRow}; + +#[cfg(feature = "packed")] +type DmaPrePostTraceRowType = DmaPrePostTraceRowPacked; +#[cfg(feature = "packed")] +type DmaPrePostTraceType = DmaPrePostTracePacked; + +#[cfg(not(feature = "packed"))] +type DmaPrePostTraceRowType = DmaPrePostTraceRow; +#[cfg(not(feature = "packed"))] +type DmaPrePostTraceType = DmaPrePostTrace; + +use crate::{DmaPrePostInput, DmaPrePostRom}; +use precompiles_helpers::DmaInfo; + +/// The `DmaPrePostSM` struct encapsulates the logic of the DmaPrePost State Machine. +pub struct DmaPrePostSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + pre_post_table_id: usize, + + /// Dual Byte Range checks + dual_range_byte_id: usize, +} + +impl DmaPrePostSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaPrePostSM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + dual_range_byte_id: std + .get_virtual_table_id(DUAL_RANGE_BYTE_ID) + .expect("Failed to get tabl eDUAL_RANGE_BYTE ID ID"), + pre_post_table_id: std + .get_virtual_table_id(DMA_PRE_POST_TABLE_ID) + .expect("Failed to get table DMA_PRE_POST_TABLE_ID ID ID"), + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_slice( + &self, + input: &DmaPrePostInput, + trace: &mut DmaPrePostTraceRowType, + pre_post_table_mul: &mut [u64], + local_dual_range_byte_mul: &mut [u64], + ) { + let dst_offset = input.dst & 0x07; + let src_offset = input.src & 0x07; + let is_pre = dst_offset > 0; + + let dst64 = input.dst >> 3; + let src64 = input.src >> 3; + + trace.set_main_step(input.step); + trace.set_dst64(dst64); + trace.set_src64(src64); + trace.set_dst_offset(dst_offset as u8); + trace.set_src_offset(src_offset as u8); + + let count = if is_pre { + DmaInfo::get_pre_count(input.encoded) + } else { + DmaInfo::get_post_count(input.encoded) + }; + + trace.set_count(count as u8); + trace.set_enabled(true); + let second_read = (src_offset as usize + count) > 8; + //println!("SECOND_READ: {second_read}"); + trace.set_enabled_second_read(second_read); + + let mut value = input.src_values[0]; + let mut bytes = [0u8; 24]; + + bytes[0] = value as u8; + bytes[1] = (value >> 8) as u8; + bytes[2] = (value >> 16) as u8; + bytes[3] = (value >> 24) as u8; + bytes[4] = (value >> 32) as u8; + bytes[5] = (value >> 40) as u8; + bytes[6] = (value >> 48) as u8; + bytes[7] = (value >> 56) as u8; + + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + + if second_read { + value = input.src_values[1]; + bytes[8] = value as u8; + bytes[9] = (value >> 8) as u8; + bytes[10] = (value >> 16) as u8; + bytes[11] = (value >> 24) as u8; + bytes[12] = (value >> 32) as u8; + bytes[13] = (value >> 40) as u8; + bytes[14] = (value >> 48) as u8; + bytes[15] = (value >> 56) as u8; + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + } else { + local_dual_range_byte_mul[0] += 4; + } + + value = input.dst_pre_value; + bytes[16] = value as u8; + bytes[17] = (value >> 8) as u8; + bytes[18] = (value >> 16) as u8; + bytes[19] = (value >> 24) as u8; + bytes[20] = (value >> 32) as u8; + bytes[21] = (value >> 40) as u8; + bytes[22] = (value >> 48) as u8; + bytes[23] = (value >> 56) as u8; + + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + + let selr_value = if dst_offset > src_offset { + trace.set_dst_offset_gt_src_offset(true); + dst_offset - src_offset + } else { + trace.set_dst_offset_gt_src_offset(false); + src_offset - dst_offset + }; + + let read_value_23 = + if selr_value > 0 { input.src_values[0] << (selr_value * 8) } else { 0 }; + let read_value_01 = (input.src_values[0] >> (selr_value * 8)) + | if selr_value > 0 { input.src_values[1] << (64 - selr_value * 8) } else { 0 }; + + let _mask = 0xFFFF_FFFF_FFFF_FFFFu64 << (dst_offset * 8); + let mask = _mask ^ (_mask << (count * 8)); + + let write_value_01 = (read_value_01 & mask) | (input.dst_pre_value & !mask); + let write_value_23 = (read_value_23 & mask) | (input.dst_pre_value & !mask); + + trace.set_write_value(0, write_value_01 as u32); + trace.set_write_value(1, (write_value_01 >> 32) as u32); + trace.set_write_value(2, write_value_23 as u32); + trace.set_write_value(3, (write_value_23 >> 32) as u32); + + trace.set_selb(0, (mask & 0x0000_0000_0000_00FF) != 0); + trace.set_selb(1, (mask & 0x0000_0000_0000_FF00) != 0); + trace.set_selb(2, (mask & 0x0000_0000_00FF_0000) != 0); + trace.set_selb(3, (mask & 0x0000_0000_FF00_0000) != 0); + trace.set_selb(4, (mask & 0x0000_00FF_0000_0000) != 0); + trace.set_selb(5, (mask & 0x0000_FF00_0000_0000) != 0); + trace.set_selb(6, (mask & 0x00FF_0000_0000_0000) != 0); + trace.set_selb(7, (mask & 0xFF00_0000_0000_0000) != 0); + + for (index, byte) in bytes.iter().enumerate() { + // println!("PRE-POST bytes[{index}]: 0x{byte:02X}"); + trace.set_bytes(index, *byte); + } + + trace.set_selread(0, selr_value == 0); + trace.set_selread(1, selr_value == 1); + trace.set_selread(2, selr_value == 2); + trace.set_selread(3, selr_value == 3); + trace.set_selread(4, selr_value == 4); + trace.set_selread(5, selr_value == 5); + trace.set_selread(6, selr_value == 6); + + // println!("PRE-POST write_value: 0x{write_value_01:016X} 0x{write_value_23:016X}"); + + let table_row = DmaPrePostRom::get_row(dst_offset as usize, src_offset as usize, count); + // println!("PRE-POST-ROM [{table_row}] dst_offset: {dst_offset} src_offset: {src_offset} count: {count}"); + pre_post_table_mul[table_row] += 1; + + // println!("DMA_PRE_POST: bytes={bytes:?} selr_value={selr_value} mask=0x{mask:016X}"); + // println!( + // "DMA_PRE_POST: read_value_01=0x{read_value_01:016X} read_value_23=0x{read_value_23:016X}" + // ); + // println!("DMA_PRE_POST: write_value_xx=[0x{write_value_01:016X},0x{write_value_23:016X}] dst_offset={dst_offset} src_offset={src_offset}"); + // println!( + // "DMA_PRE_POST: selb={:?}", + // [ + // ((mask & 0x0000_0000_0000_00FF) != 0) as u8, + // ((mask & 0x0000_0000_0000_FF00) != 0) as u8, + // ((mask & 0x0000_0000_00FF_0000) != 0) as u8, + // ((mask & 0x0000_0000_FF00_0000) != 0) as u8, + // ((mask & 0x0000_00FF_0000_0000) != 0) as u8, + // ((mask & 0x0000_FF00_0000_0000) != 0) as u8, + // ((mask & 0x00FF_0000_0000_0000) != 0) as u8, + // ((mask & 0xFF00_0000_0000_0000) != 0) as u8 + // ] + // ); + // println!( + // "DMA_PRE_POST: selread={:?}", + // [ + // (selr_value == 0) as u8, + // (selr_value == 1) as u8, + // (selr_value == 2) as u8, + // (selr_value == 3) as u8, + // (selr_value == 4) as u8, + // (selr_value == 5) as u8, + // (selr_value == 6) as u8, + // (selr_value == 7) as u8 + // ] + // ); + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut DmaPrePostTraceRowType) { + trace.set_main_step(0); + trace.set_dst64(0); + trace.set_src64(0); + trace.set_dst_offset(0); + trace.set_src_offset(0); + for index in 0..7 { + trace.set_selread(index, false); + } + + trace.set_dst_offset_gt_src_offset(false); + trace.set_count(0); + trace.set_enabled(false); + trace.set_enabled_second_read(false); + + for index in 0..24 { + trace.set_bytes(index, 0); + } + for index in 0..8 { + trace.set_selb(index, false); + } + trace.set_write_value(0, 0); + trace.set_write_value(1, 0); + trace.set_write_value(2, 0); + trace.set_write_value(3, 0); + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = DmaPrePostTraceType::::new_from_vec(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|inputs| inputs.len()).sum(); + + assert!(total_inputs <= num_rows); + assert!(total_inputs > 0); + + tracing::debug!( + "··· Creating DmaPrePost instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_PRE_POST_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + // Calculate optimal chunk size + let num_threads = rayon::current_num_threads(); + let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); + + // Process in chunks to allow per-chunk local multiplicities arrays + let (global_pre_post_table_mul, global_dual_range_byte_mul): ( + Vec>, + Vec>, + ) = flat_inputs + .par_chunks(chunk_size) + .zip(trace_rows.par_chunks_mut(chunk_size)) + .map(|(input_chunk, trace_chunk)| { + // Local array shared by this chunk + let mut local_pre_post_table_mul = vec![0u64; DMA_PRE_POST_TABLE_SIZE]; + let mut local_dual_range_byte_mul = vec![0u64; 1 << 16]; + + // Sum all local arrays into a global one + for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { + self.process_slice( + input, + trace_row, + &mut local_pre_post_table_mul, + &mut local_dual_range_byte_mul, + ); + } + + (local_pre_post_table_mul, local_dual_range_byte_mul) + }) + .collect(); + + for pre_post_table_mul in global_pre_post_table_mul.iter() { + // println!("PRE_POST_TABLE_MUL {:?}", pre_post_table_mul); + self.std.inc_virtual_rows_ranged(self.pre_post_table_id, pre_post_table_mul); + } + + for dual_range_byte_mul in global_dual_range_byte_mul.iter() { + self.std.inc_virtual_rows_ranged(self.dual_range_byte_id, dual_range_byte_mul); + } + + if total_inputs < num_rows { + self.process_empty_slice(&mut trace_rows[total_inputs]); + let empty_row = trace_rows[total_inputs]; + trace_rows[total_inputs + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + } + let from_trace = FromTrace::new(&mut trace); + timer_stop_and_log_trace!(DMA_PRE_POST_TRACE); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs new file mode 100644 index 000000000..2f5d2344a --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs @@ -0,0 +1,84 @@ +//! The `DmaPrePostCollector` module defines an collector to calculate all inputs of an instance +//! for the DmaPrePost State Machine. + +use std::any::Any; + +use zisk_common::{BusDevice, BusId, CollectCounter, OPERATION_BUS_ID, OP_TYPE}; +use zisk_core::ZiskOperationType; + +use crate::DmaPrePostInput; + +pub struct DmaPrePostCollector { + /// Collected inputs for witness computation. + pub inputs: Vec, + + /// The number of operations to collect. + pub num_operations: u64, + + /// Helper to skip instructions based on the plan's configuration. + pub collect_counter: CollectCounter, +} + +impl DmaPrePostCollector { + /// Creates a new `DmaPrePostCollector`. + /// + /// # Arguments + /// + /// * `bus_id` - The connected bus ID. + /// * `num_operations` - The number of operations to collect. + /// * `collect_skipper` - The helper to skip instructions based on the plan's configuration. + /// + /// # Returns + /// A new `ArithInstanceCollector` instance initialized with the provided parameters. + pub fn new(num_operations: u64, collect_counter: CollectCounter) -> Self { + Self { + inputs: Vec::with_capacity(num_operations as usize), + num_operations, + collect_counter, + } + } + + /// Processes data received on the bus, collecting the inputs necessary for witness computation. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A tuple where: + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if self.inputs.len() == self.num_operations as usize { + return false; + } + + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + return true; + } + + // println!( + // "DmaPrePostCollector::process_data {} {:?}", + // DmaInfo::to_string(data[DMA_ENCODED]), + // self.collect_counter + // ); + let rows = DmaPrePostInput::get_count(data); + let res = self.collect_counter.should_process(rows as u32); + // println!("DmaPrePostCollector::process_data2 {} {:?}", rows, res); + if let Some((skip, max_count)) = res { + self.inputs.extend(DmaPrePostInput::from(data, data_ext, skip, max_count)); + } + // println!("DmaPrePostCollector::process_data3 input.len()={}", self.inputs.len()); + self.inputs.len() < self.num_operations as usize + } +} + +impl BusDevice for DmaPrePostCollector { + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs new file mode 100644 index 000000000..51f7fef69 --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs @@ -0,0 +1,79 @@ +use precompiles_helpers::DmaInfo; +use zisk_common::{A, B, DMA_ENCODED, STEP}; + +#[derive(Debug)] +pub struct DmaPrePostInput { + pub src: u32, + pub dst: u32, + pub step: u64, + pub encoded: u64, + pub src_values: [u64; 2], + pub dst_pre_value: u64, +} +impl std::fmt::Display for DmaPrePostInput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "DmaPrePostInput {{ src: 0x{:08x}, dst: 0x{:08x}, step: {}, encoded: 0x{:016x} ({}), src_values: [0x{:016x}, 0x{:016x}], dst_pre_value: 0x{:016x} }}", + self.src, self.dst, self.step, self.encoded, DmaInfo::to_string(self.encoded), self.src_values[0], self.src_values[1], self.dst_pre_value + ) + } +} +impl DmaPrePostInput { + pub fn get_count(data: &[u64]) -> usize { + let encoded = data[DMA_ENCODED]; + (DmaInfo::get_pre_count(encoded) > 0) as usize + + (DmaInfo::get_post_count(encoded) > 0) as usize + } + pub fn from(data: &[u64], data_ext: &[u64], skip: u32, max_count: u32) -> Vec { + let encoded = data[DMA_ENCODED]; + let mut inputs = Vec::new(); + let pre_count = DmaInfo::get_pre_count(encoded); + let mut skipped = 0; + if pre_count > 0 { + if skipped < skip { + skipped += 1; + } else { + let src_offset = DmaInfo::get_pre_data_offset(encoded); + let input = Self { + dst: data[A] as u32, + src: data[B] as u32, + step: data[STEP], + encoded, + src_values: [ + data_ext[src_offset], + if DmaInfo::is_double_read_pre(encoded) { + data_ext[src_offset + 1] + } else { + 0 + }, + ], + dst_pre_value: data_ext[DmaInfo::get_pre_write_offset(encoded)], + }; + inputs.push(input); + } + } + let post_count = DmaInfo::get_post_count(encoded); + if post_count > 0 && skipped >= skip && max_count as usize > inputs.len() { + let src_offset = DmaInfo::get_post_data_offset(encoded); + let loop_count = DmaInfo::get_loop_count(encoded); + let input = Self { + dst: data[A] as u32 + pre_count as u32 + loop_count as u32 * 8, + src: data[B] as u32 + pre_count as u32 + loop_count as u32 * 8, + step: data[STEP], + encoded, + src_values: [ + data_ext[src_offset], + if DmaInfo::is_double_read_post(encoded) { + data_ext[src_offset + 1] + } else { + 0 + }, + ], + dst_pre_value: data_ext[DmaInfo::get_post_write_offset(encoded)], + }; + inputs.push(input); + } + inputs + } +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs new file mode 100644 index 000000000..2d5dc5090 --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs @@ -0,0 +1,118 @@ +//! The `DmaPrePostInstance` module defines an instance to perform the witness computation +//! for the DmaPrePost State Machine. +//! +//! It manages collected inputs and interacts with the `DmaPrePostSM` to compute witnesses for +//! execution plans. + +use crate::{DmaCheckPoint, DmaPrePostCollector, DmaPrePostSM}; +use fields::PrimeField64; +use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; +use std::sync::Arc; +use zisk_common::ChunkId; +use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; +use zisk_pil::DmaPrePostTrace; + +/// The `DmaPrePostInstance` struct represents an instance for the DmaPrePost State Machine. +/// +/// It encapsulates the `DmaPrePostSM` and its associated context, and it processes input data +/// to compute witnesses for the DmaPrePost State Machine. +pub struct DmaPrePostInstance { + /// DmaPrePost State machine. + dma_sm: Arc>, + + /// Instance context. + ictx: InstanceCtx, +} + +impl DmaPrePostInstance { + /// Creates a new `DmaPrePostInstance`. + /// + /// # Arguments + /// * `dma_sm` - An `Arc`-wrapped reference to the DmaPrePost State Machine. + /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. + /// * `bus_id` - The bus ID associated with this instance. + /// + /// # Returns + /// A new `DmaPrePostInstance` instance initialized with the provided state machine and + /// context. + pub fn new(dma_sm: Arc>, ictx: InstanceCtx) -> Self { + Self { dma_sm, ictx } + } + + pub fn build_dma_collector(&self, chunk_id: ChunkId) -> DmaPrePostCollector { + assert_eq!( + self.ictx.plan.air_id, + DmaPrePostTrace::::AIR_ID, + "DmaPrePostInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info: &DmaCheckPoint = meta.downcast_ref::().unwrap(); + let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; + DmaPrePostCollector::new(num_ops, collect_counter) + } +} + +impl Instance for DmaPrePostInstance { + /// Computes the witness for the Dma execution plan. + /// + /// This method leverages the `DmaPrePostSM` to generate an `AirInstance` using the collected + /// inputs. + /// + /// # Arguments + /// * `_pctx` - The proof context, unused in this implementation. + /// + /// # Returns + /// An `Option` containing the computed `AirInstance`. + fn compute_witness( + &self, + _pctx: &ProofCtx, + _sctx: &SetupCtx, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + ) -> ProofmanResult>> { + let inputs: Vec<_> = collectors + .into_iter() + .map(|(_, collector)| { + collector.as_any().downcast::().unwrap().inputs + }) + .collect(); + + Ok(Some(self.dma_sm.compute_witness(&inputs, trace_buffer)?)) + } + + /// Retrieves the checkpoint associated with this instance. + /// + /// # Returns + /// A `CheckPoint` object representing the checkpoint of the execution plan. + fn check_point(&self) -> &CheckPoint { + &self.ictx.plan.check_point + } + + /// Retrieves the type of this instance. + /// + /// # Returns + /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). + fn instance_type(&self) -> InstanceType { + InstanceType::Instance + } + + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { + assert_eq!( + self.ictx.plan.air_id, + DmaPrePostTrace::::AIR_ID, + "DmaPrePostInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; + Some(Box::new(DmaPrePostCollector::new(num_ops, collect_counter))) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs new file mode 100644 index 000000000..0ca2bf430 --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs @@ -0,0 +1,23 @@ +use precompiles_helpers::DmaInfo; + +pub enum DmaPrePostRom {} + +impl DmaPrePostRom { + // Table generated from pil + const TABLE_OFFSETS: [usize; 64] = [ + 0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98, 105, 112, 118, 124, 130, 136, + 142, 148, 154, 160, 165, 170, 175, 180, 185, 190, 195, 200, 204, 208, 212, 216, 220, 224, + 228, 232, 235, 238, 241, 244, 247, 250, 253, 256, 258, 260, 262, 264, 266, 268, 270, 272, + 273, 274, 275, 276, 277, 278, 279, + ]; + #[allow(dead_code)] + pub fn get_row_from_encoded(encoded: u64) -> usize { + let src_offset = DmaInfo::get_src_offset(encoded); + let dst_offset = DmaInfo::get_dst_offset(encoded); + let count = DmaInfo::get_count(encoded); + Self::get_row(dst_offset, src_offset, count) + } + pub fn get_row(dst_offset: usize, src_offset: usize, count: usize) -> usize { + Self::TABLE_OFFSETS[dst_offset * 8 + src_offset] + count - 1 + } +} diff --git a/precompiles/dma/src/dma_pre_post/mod.rs b/precompiles/dma/src/dma_pre_post/mod.rs new file mode 100644 index 000000000..9e8132ecc --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/mod.rs @@ -0,0 +1,12 @@ +#[allow(clippy::module_inception)] +mod dma_pre_post; +mod dma_pre_post_collector; +mod dma_pre_post_input; +mod dma_pre_post_instance; +mod dma_pre_post_rom; + +pub use dma_pre_post::*; +pub use dma_pre_post_collector::*; +pub use dma_pre_post_input::*; +pub use dma_pre_post_instance::*; +pub use dma_pre_post_rom::*; diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs new file mode 100644 index 000000000..43e7a8b02 --- /dev/null +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs @@ -0,0 +1,367 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator as _}; + +use crate::DmaUnalignedInput; +use pil_std_lib::Std; +use precompiles_helpers::DmaInfo; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use zisk_common::SegmentId; +use zisk_pil::{DmaUnalignedAirValues, DUAL_RANGE_BYTE_ID}; + +#[cfg(feature = "packed")] +pub use zisk_pil::{DmaUnalignedTracePacked, DmaUnalignedTraceRowPacked}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{DmaUnalignedTrace, DmaUnalignedTraceRow}; + +#[cfg(feature = "packed")] +type DmaUnalignedTraceRowType = DmaUnalignedTraceRowPacked; +#[cfg(feature = "packed")] +type DmaUnalignedTraceType = DmaUnalignedTracePacked; + +#[cfg(not(feature = "packed"))] +type DmaUnalignedTraceRowType = DmaUnalignedTraceRow; +#[cfg(not(feature = "packed"))] +type DmaUnalignedTraceType = DmaUnalignedTrace; + +pub struct DmaUnalignedPrevSegment { + pub seq_end: bool, + pub dst64: u32, + pub src64: u32, + pub src_offset: u8, + pub main_step: u64, + pub count: u32, + pub is_mem_eq: bool, +} + +/// The `DmaUnalignedSM` struct encapsulates the logic of the DmaUnaligned State Machine. +pub struct DmaUnalignedSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + range_16_bits_id: usize, + dual_range_byte_id: usize, +} + +impl DmaUnalignedSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaUnalignedSM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + dual_range_byte_id: std + .get_virtual_table_id(DUAL_RANGE_BYTE_ID) + .expect("Failed to get tabl eDUAL_RANGE_BYTE ID ID"), + range_16_bits_id: std + .get_range_id(0, 0xFFFF, None) + .expect("Failed to get 16b table ID"), + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_input( + &self, + input: &DmaUnalignedInput, + trace: &mut [DmaUnalignedTraceRowType], + local_dual_byte_table: &mut [u64], + air_values: &mut DmaUnalignedAirValues, + ) -> usize { + let rows = input.count as usize; + let initial_count = DmaInfo::get_loop_count(input.encoded) - input.skip as usize; + let mut count = initial_count; + let src_offset = DmaInfo::get_loop_src_offset(input.encoded); + let mut dst64 = (input.dst >> 3) + input.skip; + let mut src64 = (input.src >> 3) + input.skip; + + let mut src_values_index = 0; + let mut seq_end = false; + let mut next_value = 0; + // println!( + // "DMA_UNALIGNED INPUT {input:?} count:{count} rows:{rows} dma_info:{}", + // DmaInfo::to_string(input.encoded) + // ); + assert!(rows > 0); + for (irow, row) in trace.iter_mut().enumerate().take(rows) { + row.set_main_step(input.step); + row.set_is_mem_eq(false); + row.set_no_last_no_seq_end(count != 0); + row.set_previous_seq_end(input.skip == 0 && irow == 0); + + row.set_dst64(dst64); + row.set_src64(src64); + dst64 += 1; + src64 += 1; + + row.set_offset_2(src_offset == 2); + row.set_offset_3(src_offset == 3); + row.set_offset_4(src_offset == 4); + row.set_offset_5(src_offset == 5); + row.set_offset_6(src_offset == 6); + row.set_offset_7(src_offset == 7); + + row.set_count(count as u32); + // println!("DMA_UNALIGNED: trace[{irow}] count:{count}"); + row.set_seq_end(count == 0); + + let value = input.src_values[src_values_index]; + src_values_index += 1; + let write_value = if count == 0 { + seq_end = true; + next_value = 0; + match src_offset { + 1 => value >> 8, + 2 => value >> 16, + 3 => value >> 24, + 4 => value >> 32, + 5 => value >> 40, + 6 => value >> 48, + 7 => value >> 56, + _ => panic!("invalid src_offset {src_offset} on DmaUnaligned"), + } + } else { + count -= 1; + if src_values_index >= input.src_values.len() { + println!( + "DMA_UNALIGNED INPUT src_values_index out of bounds {} / {} count:{count} irow:{irow} INPUT:{:?}", + src_values_index, + input.src_values.len(), + input + ); + } + next_value = input.src_values[src_values_index]; + match src_offset { + 1 => (value >> 8) | (next_value << 56), + 2 => (value >> 16) | (next_value << 48), + 3 => (value >> 24) | (next_value << 40), + 4 => (value >> 32) | (next_value << 32), + 5 => (value >> 40) | (next_value << 24), + 6 => (value >> 48) | (next_value << 16), + 7 => (value >> 56) | (next_value << 8), + _ => panic!("invalid src_offset {src_offset} on DmaUnaligned"), + } + }; + + row.set_read_bytes(0, value as u8); + row.set_read_bytes(1, (value >> 8) as u8); + row.set_read_bytes(2, (value >> 16) as u8); + row.set_read_bytes(3, (value >> 24) as u8); + row.set_read_bytes(4, (value >> 32) as u8); + row.set_read_bytes(5, (value >> 40) as u8); + row.set_read_bytes(6, (value >> 48) as u8); + row.set_read_bytes(7, (value >> 56) as u8); + + row.set_write_value(0, write_value as u32); + row.set_write_value(1, (write_value >> 32) as u32); + + let value = value as usize; + local_dual_byte_table[value & 0xFFFF] += 1; + local_dual_byte_table[(value >> 16) & 0xFFFF] += 1; + local_dual_byte_table[(value >> 32) & 0xFFFF] += 1; + local_dual_byte_table[(value >> 48) & 0xFFFF] += 1; + } + + if input.is_last_instance_input { + if seq_end { + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count = F::ZERO; + air_values.segment_last_count = F::ZERO; + air_values.segment_last_offset = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_is_mem_eq = F::ZERO; + air_values.segment_next_bytes = [F::ZERO; 8]; + } else { + let last_row = rows - 1; + air_values.segment_last_seq_end = F::ZERO; + air_values.segment_last_src64 = F::from_u32(trace[last_row].get_src64()); + air_values.segment_last_dst64 = F::from_u32(trace[last_row].get_dst64()); + air_values.segment_last_main_step = F::from_u64(trace[last_row].get_main_step()); + air_values.segment_last_count = F::from_u32(trace[last_row].get_count()); + air_values.segment_last_offset = F::from_u8(src_offset); + let count = trace[last_row].get_count(); + air_values.last_count_chunk[0] = F::from_u16(count as u16); + air_values.last_count_chunk[1] = F::from_u16((count >> 16) as u16); + air_values.segment_last_is_mem_eq = F::from_bool(trace[last_row].get_is_mem_eq()); + for (index, byte) in air_values.segment_next_bytes.iter_mut().enumerate() { + *byte = F::from_u8((next_value >> (index * 8)) as u8); + } + } + } + rows + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut DmaUnalignedTraceRowType) { + trace.set_main_step(0); + trace.set_is_mem_eq(false); + trace.set_no_last_no_seq_end(false); + trace.set_previous_seq_end(true); + + trace.set_dst64(0); + trace.set_src64(0); + + trace.set_offset_2(false); + trace.set_offset_3(false); + trace.set_offset_4(false); + trace.set_offset_5(false); + trace.set_offset_6(false); + trace.set_offset_7(false); + + trace.set_count(0); + trace.set_seq_end(true); + + trace.set_read_bytes(0, 0); + trace.set_read_bytes(1, 0); + trace.set_read_bytes(2, 0); + trace.set_read_bytes(3, 0); + trace.set_read_bytes(4, 0); + trace.set_read_bytes(5, 0); + trace.set_read_bytes(6, 0); + trace.set_read_bytes(7, 0); + + trace.set_write_value(0, 0); + trace.set_write_value(1, 0); + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + segment_id: SegmentId, + is_last_segment: bool, + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = DmaUnalignedTraceType::::new_from_vec(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs + .iter() + .map(|inputs| inputs.iter().map(|input| input.count as usize).sum::()) + .sum(); + + assert!(total_inputs <= num_rows); + assert!(total_inputs > 0); + + tracing::debug!( + "··· Creating DmaUnaligned instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_UNALIGNED_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + // TODO: add std method to used short table, no sense with instances around 2^22 use 64 bits, need more space. + let mut local_dual_byte_table = vec![0u64; 1 << 16]; + let mut air_values = DmaUnalignedAirValues::::new(); + let mut row_offset = 0; + for input in flat_inputs.iter() { + let rows_used = self.process_input( + input, + &mut trace_rows[row_offset..], + &mut local_dual_byte_table, + &mut air_values, + ); + row_offset += rows_used; + } + + let padding_rows = num_rows - row_offset; + let last_count = if padding_rows == 0 && !trace_rows[num_rows - 1].get_seq_end() { + trace_rows[num_rows - 1].get_count() + } else { + 0 + }; + self.std.range_check(self.range_16_bits_id, (last_count & 0xFFFF) as i64, 1); + self.std.range_check(self.range_16_bits_id, ((last_count >> 16) & 0xFFFF) as i64, 1); + + local_dual_byte_table[0] += (padding_rows * 4) as u64; + self.std.inc_virtual_rows_ranged(self.dual_range_byte_id, &local_dual_byte_table); + + air_values.segment_id = F::from_usize(segment_id.into()); + air_values.is_last_segment = F::from_bool(is_last_segment); + + let first_input = flat_inputs.first().unwrap(); + if first_input.skip == 0 { + air_values.segment_previous_seq_end = F::ONE; + air_values.segment_previous_dst64 = F::ZERO; + air_values.segment_previous_src64 = F::ZERO; + air_values.segment_previous_main_step = F::ZERO; + air_values.segment_previous_count = F::ZERO; + air_values.segment_previous_is_mem_eq = F::ZERO; + air_values.segment_previous_offset = F::ZERO; + air_values.segment_first_bytes = [F::ZERO; 8]; + } else { + air_values.segment_previous_seq_end = F::ZERO; + air_values.segment_previous_dst64 = F::from_u32(trace_rows[0].get_dst64() - 1); + air_values.segment_previous_src64 = F::from_u32(trace_rows[0].get_src64() - 1); + air_values.segment_previous_main_step = F::from_u64(trace_rows[0].get_main_step()); + air_values.segment_previous_count = F::from_u32(trace_rows[0].get_count() + 1); + air_values.segment_previous_is_mem_eq = F::from_bool(trace_rows[0].get_is_mem_eq()); + air_values.segment_previous_offset = + F::from_u8(DmaInfo::get_loop_src_offset(first_input.encoded)); + for (index, byte) in air_values.segment_first_bytes.iter_mut().enumerate() { + *byte = F::from_u8(trace_rows[0].get_read_bytes(index)); + } + } + + // padding + if padding_rows > 0 { + air_values.padding_rows = F::from_u32(padding_rows as u32); + self.process_empty_slice(&mut trace_rows[row_offset]); + let empty_row = trace_rows[row_offset]; + trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count = F::ZERO; + air_values.segment_last_is_mem_eq = F::ZERO; + air_values.segment_next_bytes = [F::ZERO; 8]; + } else { + trace[num_rows - 1].set_no_last_no_seq_end(false); + } + #[cfg(feature = "debug_dma")] + { + println!("TRACE DmaUnalignedSM @{segment_id} [0] {:?}", trace[0]); + println!( + "TRACE DmaUnalignedSM @{segment_id} [{}] {:?}", + num_rows - 1, + trace[num_rows - 1] + ); + println!("TRACE DmaUnalignedSM AIR_VALUES {:?}", air_values); + } + timer_stop_and_log_trace!(DMA_UNALIGNED_TRACE); + let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs new file mode 100644 index 000000000..b80ff0f63 --- /dev/null +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs @@ -0,0 +1,99 @@ +//! The `DmaUnalignedInstance` module defines an instance to perform the witness computation +//! for the Dma State Machine. +//! +//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for +//! execution plans. + +use crate::DmaUnalignedInput; +use std::any::Any; +use zisk_common::{BusDevice, BusId, CollectCounter, OPERATION_BUS_ID, OP_TYPE}; +use zisk_core::ZiskOperationType; + +pub struct DmaUnalignedCollector { + /// Collected inputs for witness computation. + pub inputs: Vec, + + /// The number of operations to collect. + pub num_inputs: u64, + + /// Helper to skip instructions based on the plan's configuration. + pub collect_counter: CollectCounter, + + pub trace_offset: usize, + pub last_segment_collector: bool, +} + +impl DmaUnalignedCollector { + /// Creates a new `DmaUnalignedCollector`. + /// + /// # Arguments + /// + /// * `bus_id` - The connected bus ID. + /// * `num_inputs` - The number of inputs to collect. + /// * `collect_counter` - The helper to skip instructions based on the plan's configuration. + /// + /// # Returns + /// A new `DmaUnalignedCollector` instance initialized with the provided parameters. + pub fn new( + num_inputs: u64, + collect_counter: CollectCounter, + last_segment_collector: bool, + ) -> Self { + Self { + inputs: Vec::with_capacity(num_inputs as usize), + num_inputs, + collect_counter, + trace_offset: 0, + last_segment_collector, + } + } + + /// Processes data received on the bus, collecting the inputs necessary for witness computation. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A tuple where: + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if self.collect_counter.is_final_skip() { + return false; + } + + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + return true; + } + + let rows = DmaUnalignedInput::get_count(data) as u32; + if rows == 0 { + return true; + } + + if let Some((skip, max_count)) = self.collect_counter.should_process(rows) { + self.inputs.push(DmaUnalignedInput::from( + data, + data_ext, + self.trace_offset, + skip as usize, + max_count as usize, + self.last_segment_collector && self.collect_counter.is_final_skip(), + )); + self.trace_offset += max_count as usize; + } + + !self.collect_counter.is_final_skip() + } +} + +impl BusDevice for DmaUnalignedCollector { + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs new file mode 100644 index 000000000..ce1761a65 --- /dev/null +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs @@ -0,0 +1,85 @@ +use precompiles_helpers::DmaInfo; +use zisk_common::{A, B, DMA_ENCODED, STEP}; + +#[derive(Debug)] +pub struct DmaUnalignedInput { + pub src: u32, + pub dst: u32, + pub is_first_instance_input: bool, + pub is_last_instance_input: bool, + pub trace_offset: u32, // offset inside trace to paralelize + pub skip: u32, // inside input how many rows skip + pub count: u32, // number of rows used + pub step: u64, + pub encoded: u64, + pub src_values: Vec, +} + +impl DmaUnalignedInput { + pub fn get_count(data: &[u64]) -> usize { + let encoded = data[DMA_ENCODED]; + if DmaInfo::get_dst_offset(encoded) == DmaInfo::get_src_offset(encoded) { + 0 + } else { + let count = DmaInfo::get_loop_count(encoded); + if count > 0 { + count + 1 + } else { + 0 + } + } + } + pub fn get_last_count(&self) -> usize { + let rows = self.count as usize; + let initial_count = self.get_initial_count(); + initial_count - rows + 1 + } + pub fn get_initial_count(&self) -> usize { + DmaInfo::get_count(self.encoded) - self.skip as usize + } + pub fn from( + data: &[u64], + data_ext: &[u64], + trace_offset: usize, + skip: usize, + max_count: usize, + is_last_instance_input: bool, + ) -> Self { + let encoded = data[DMA_ENCODED]; + + let pre_count = DmaInfo::get_pre_count(encoded) as u32; + let data_offset = DmaInfo::get_loop_data_offset(encoded) + skip; + + // unaligned need an extra row to read part of next bytes + let pending_count = DmaInfo::get_loop_count(encoded) + 1 - skip; + let count = std::cmp::min(pending_count, max_count); + if data_offset >= data_ext.len() || (data_offset + count) > data_ext.len() { + println!( + "PROBLEM ON INPUT GENERATION STEP:{} data_ext.len={} src_values[{data_offset}..{}] {}", + data[STEP], + data_ext.len(), + data_offset + count, + DmaInfo::to_string(encoded) + ); + } + // if count not enough to finish unaligned memcpy, add extra source because one row + // use next source value + let src_values_count = if count < pending_count { count + 1 } else { count }; + assert!(DmaInfo::get_loop_count(encoded) > 0); + Self { + dst: data[A] as u32 + pre_count, + src: data[B] as u32 + DmaInfo::get_src64_inc_by_pre(encoded) as u32 * 8, + trace_offset: trace_offset as u32, + is_first_instance_input: trace_offset == 0, + is_last_instance_input, + step: data[STEP], + skip: skip as u32, + count: count as u32, + encoded, + src_values: data_ext[data_offset..data_offset + src_values_count].to_vec(), + } + } + pub fn get_rows(&self) -> usize { + DmaInfo::get_loop_count(self.encoded) + } +} diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs new file mode 100644 index 000000000..174a893d0 --- /dev/null +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs @@ -0,0 +1,141 @@ +//! The `DmaUnalignedInstance` module defines an instance to perform the witness computation +//! for the Dma State Machine. +//! +//! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for +//! execution plans. + +use crate::{DmaCheckPoint, DmaUnalignedCollector, DmaUnalignedSM}; +use fields::PrimeField64; +use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; +use std::sync::Arc; +use zisk_common::ChunkId; +use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; +use zisk_pil::DmaUnalignedTrace; + +/// The `DmaUnalignedInstance` struct represents an instance for the Dma State Machine. +/// +/// It encapsulates the `DmaUnalignedSM` and its associated context, and it processes input data +/// to compute witnesses for the DmaUnaligned State Machine. +pub struct DmaUnalignedInstance { + /// Dma state machine. + dma_64_aligned_sm: Arc>, + + /// Instance context. + ictx: InstanceCtx, + + /// Flag to define that it's last segment + is_last_segment: bool, +} + +impl DmaUnalignedInstance { + /// Creates a new `DmaUnalignedInstance`. + /// + /// # Arguments + /// * `dma_64_aligned_sm` - An `Arc`-wrapped reference to the Dma 64 Aligned State Machine. + /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. + /// * `bus_id` - The bus ID associated with this instance. + /// + /// # Returns + /// A new `DmaUnalignedInstance` instance initialized with the provided state machine and + /// context. + pub fn new(dma_64_aligned_sm: Arc>, ictx: InstanceCtx) -> Self { + let is_last_segment = { + let meta = ictx.plan.meta.as_ref().unwrap(); + let checkpoint = meta.downcast_ref::().unwrap(); + checkpoint.is_last_segment + }; + Self { dma_64_aligned_sm, ictx, is_last_segment } + } + + pub fn build_dma_collector(&self, chunk_id: ChunkId) -> DmaUnalignedCollector { + assert_eq!( + self.ictx.plan.air_id, + DmaUnalignedTrace::::AIR_ID, + "DmaUnalignedInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; + DmaUnalignedCollector::new( + num_inputs, + collect_counter, + Some(chunk_id) == collect_info.last_chunk, + ) + } +} + +impl Instance for DmaUnalignedInstance { + /// Computes the witness for the Dma execution plan. + /// + /// This method leverages the `DmaUnalignedSM` to generate an `AirInstance` using the collected + /// inputs. + /// + /// # Arguments + /// * `_pctx` - The proof context, unused in this implementation. + /// + /// # Returns + /// An `Option` containing the computed `AirInstance`. + fn compute_witness( + &self, + _pctx: &ProofCtx, + _sctx: &SetupCtx, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + ) -> ProofmanResult>> { + let inputs: Vec<_> = collectors + .into_iter() + .map(|(_, collector)| { + collector.as_any().downcast::().unwrap().inputs + }) + .collect(); + // Extract segment id from instance context + let segment_id = self.ictx.plan.segment_id.unwrap(); + + Ok(Some(self.dma_64_aligned_sm.compute_witness( + &inputs, + segment_id, + self.is_last_segment, + trace_buffer, + )?)) + } + + /// Retrieves the checkpoint associated with this instance. + /// + /// # Returns + /// A `CheckPoint` object representing the checkpoint of the execution plan. + fn check_point(&self) -> &CheckPoint { + &self.ictx.plan.check_point + } + + /// Retrieves the type of this instance. + /// + /// # Returns + /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). + fn instance_type(&self) -> InstanceType { + InstanceType::Instance + } + + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { + assert_eq!( + self.ictx.plan.air_id, + DmaUnalignedTrace::::AIR_ID, + "DmaUnalignedInstance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::().unwrap(); + let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; + Some(Box::new(DmaUnalignedCollector::new( + num_inputs, + collect_counter, + Some(chunk_id) == collect_info.last_chunk, + ))) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/precompiles/dma/src/dma_unaligned/mod.rs b/precompiles/dma/src/dma_unaligned/mod.rs new file mode 100644 index 000000000..4949c3c18 --- /dev/null +++ b/precompiles/dma/src/dma_unaligned/mod.rs @@ -0,0 +1,10 @@ +#[allow(clippy::module_inception)] +mod dma_unaligned; +mod dma_unaligned_collector; +mod dma_unaligned_input; +mod dma_unaligned_instance; + +pub use dma_unaligned::*; +pub use dma_unaligned_collector::*; +pub use dma_unaligned_input::*; +pub use dma_unaligned_instance::*; diff --git a/precompiles/dma/src/lib.rs b/precompiles/dma/src/lib.rs new file mode 100644 index 000000000..26d62ace9 --- /dev/null +++ b/precompiles/dma/src/lib.rs @@ -0,0 +1,19 @@ +mod dma; +mod dma_64_aligned; +mod dma_bus_device; +mod dma_constants; +mod dma_gen_mem_inputs; +mod dma_manager; +mod dma_planner; +mod dma_pre_post; +mod dma_unaligned; + +pub use dma::*; +pub use dma_64_aligned::*; +pub use dma_bus_device::*; +pub use dma_constants::*; +pub use dma_gen_mem_inputs::*; +pub use dma_manager::*; +pub use dma_planner::*; +pub use dma_pre_post::*; +pub use dma_unaligned::*; diff --git a/precompiles/helpers/Cargo.toml b/precompiles/helpers/Cargo.toml index 65a7e9764..b768c194f 100644 --- a/precompiles/helpers/Cargo.toml +++ b/precompiles/helpers/Cargo.toml @@ -22,4 +22,5 @@ num-traits = { workspace = true } cfg-if = "1.0" [features] -default = [] \ No newline at end of file +default = [] +debug_dma = [] \ No newline at end of file diff --git a/precompiles/helpers/src/dma.rs b/precompiles/helpers/src/dma.rs new file mode 100644 index 000000000..a96ada11c --- /dev/null +++ b/precompiles/helpers/src/dma.rs @@ -0,0 +1,590 @@ +// use static_assertions::const_assert; +// const_assert!(CHUNK_MEM_STEP_BITS <= 24); + +pub struct DmaHelpers {} + +pub struct DmaValues { + pub dst64: u64, + pub src64: u64, + pub src_offset: u64, + pub dst_offset: u64, + pub pre_count: u64, + pub post_count: u64, + pub memcpy_count: u64, + pub src64_inc_by_pre: u64, + pub src_offset_after_pre: u64, +} + +const FAST_ENCODE_TABLE_SIZE: usize = 8 * 8 * 16; +const FAST_ENCODE_TABLE: [u64; FAST_ENCODE_TABLE_SIZE] = generate_fast_encode_table(); + +const fn generate_fast_encode_table() -> [u64; FAST_ENCODE_TABLE_SIZE] { + let mut table = [0u64; FAST_ENCODE_TABLE_SIZE]; + // fill table + let mut dst_offset: u64 = 0; + while dst_offset < 8 { + let base_index = dst_offset << 7; + let mut src_offset: u64 = 0; + while src_offset < 8 { + let index = (base_index + (src_offset << 4)) as usize; + let mut count: usize = 0; + while count < 16 { + let value = DmaInfo::encode_memcpy(dst_offset, src_offset, count); + let loop_count = DmaInfo::get_loop_count(value) as u64; + // The table is create to add directly de loop count and after all values + // are correct, for this reason substract de count, because we need diference + // between loop_count (shifted 32) and count (shifted 29) + table[index + count] = ((value & 0x0000_0000_FFFF_FFFF) + (loop_count << 32)) + .wrapping_sub((count as u64) << 29); + count += 1; + } + src_offset += 1; + } + dst_offset += 1; + } + table +} + +pub struct DmaInfo {} + +impl DmaInfo { + #[inline(always)] + pub fn to_string(encoded: u64) -> String { + format!("loop_count: {}|pre_writes: {}|dst_offset: {}|src_offset: {}|pre_count: {}|post_count: {}|extra_src_reads: {}", + Self::get_loop_count(encoded), + Self::get_pre_writes(encoded), + Self::get_dst_offset(encoded), + Self::get_src_offset(encoded), + Self::get_pre_count(encoded), + Self::get_post_count(encoded), + Self::get_extra_src_reads(encoded)) + } + #[inline(always)] + pub const fn fast_encode_memcpy(dst: u64, src: u64, count: usize) -> u64 { + let table_count = if count >= 16 { count & 0x07 | 0x08 } else { count }; + FAST_ENCODE_TABLE[(((dst & 0x07) << 7) + ((src & 0x07) << 4)) as usize + table_count] + .wrapping_add((count as u64) << 29) + } + + pub const fn encode_memcpy(dst: u64, src: u64, count: usize) -> u64 { + // #bits bits + // pre_count: 0-7 3 0-2 + // post_count: 0-7 3 3-5 + // pre_writes: 0,1,2 2 6-7 + // dst_offset: 0-7 3 8-10 + // src_offset: 0-7 3 11-13 + // double_src_pre: 0,1 1 14 + // double_src_post: 0,1 1 15 + // extra_src_reads: 0-3 2 16-17 + // src64_inc_by_pre: 1 18 + // unaligned_dst_src: 1 20 + // loop_count 32 32-63 + + let dst_offset = dst & 0x07; + let src_offset = src & 0x07; + + let count = count as u64; + let (pre_count, loop_count, post_count) = if dst_offset > 0 { + let _pre_count = 8 - dst_offset; + if _pre_count >= count { + (count, 0, 0) + } else { + let pending = count - _pre_count; + (_pre_count, pending >> 3, pending & 0x07) + } + } else { + (0, count >> 3, count & 0x07) + }; + let pre_writes = (pre_count > 0) as u64 + (post_count > 0) as u64; + // let to_src_offset = (src + count - 1) & 0x07; + let src_offset_pos = (src_offset + pre_count) & 0x07; + let double_src_post = (src_offset_pos + post_count) > 8; + let double_src_pre = (src_offset + pre_count) > 8; + let extra_src_reads = + if count == 0 { 0 } else { (((src + count - 1) >> 3) - (src >> 3) + 1) - loop_count }; + + let src64_inc_by_pre = (pre_count > 0 && (src_offset + pre_count) >= 8) as u64; + let unaligned_dst_src = (src_offset != dst_offset) as u64; + + pre_count + | (post_count << 3) + | (pre_writes << 6) + | (dst_offset << 8) + | (src_offset << 11) + | ((double_src_pre as u64) << 14) + | ((double_src_post as u64) << 15) + | (extra_src_reads << 16) + | (src64_inc_by_pre << 18) + | (unaligned_dst_src << 19) + | (pre_count << 29) // optimization to read loop_count * 8 + pre_count + | (loop_count << 32) + } + + pub const fn get_extra_src_reads(encoded: u64) -> usize { + (encoded as usize) >> 16 & 0x03 + } + pub const fn get_count(encoded: u64) -> usize { + Self::get_loop_count(encoded) * 8 + + Self::get_pre_count(encoded) + + Self::get_post_count(encoded) + } + pub const fn get_dst_offset(encoded: u64) -> usize { + (encoded as usize >> 8) & 0x07 + } + + pub const fn get_src_offset(encoded: u64) -> usize { + (encoded as usize >> 11) & 0x07 + } + + pub const fn get_loop_count(encoded: u64) -> usize { + (encoded >> 32) as usize + } + + pub const fn get_pre_writes(encoded: u64) -> usize { + (encoded as usize >> 6) & 0x03 + } + + pub const fn is_double_read_pre(encoded: u64) -> bool { + encoded & (1 << 14) != 0 + } + + pub const fn is_double_read_post(encoded: u64) -> bool { + encoded & (1 << 15) != 0 + } + + pub const fn get_pre_count(encoded: u64) -> usize { + (encoded as usize) & 0x07 + } + + pub const fn get_post_count(encoded: u64) -> usize { + (encoded as usize >> 3) & 0x07 + } + + pub const fn get_pre(encoded: u64) -> u8 { + (Self::get_pre_count(encoded) > 0) as u8 + Self::is_double_read_pre(encoded) as u8 + } + + pub const fn get_post(encoded: u64) -> u8 { + (Self::get_post_count(encoded) > 0) as u8 + Self::is_double_read_post(encoded) as u8 + } + + pub const fn get_src64_inc_by_pre(encoded: u64) -> usize { + (encoded as usize >> 18) & 0x01 + } + + pub const fn get_loop_data_offset(encoded: u64) -> usize { + let pre_count = Self::get_pre_count(encoded); + Self::get_pre_writes(encoded) + + (pre_count > 0 && (Self::get_src_offset(encoded) + pre_count) >= 8) as usize + } + + pub const fn get_loop_src_offset(encoded: u64) -> u8 { + (Self::get_src_offset(encoded) + Self::get_pre_count(encoded)) as u8 & 0x07 + } + + pub const fn get_src_size(encoded: u64) -> usize { + Self::get_loop_count(encoded) + Self::get_extra_src_reads(encoded) + } + pub const fn get_data_size(encoded: u64) -> usize { + Self::get_pre_writes(encoded) + Self::get_src_size(encoded) + } + pub const fn get_post_data_offset(encoded: u64) -> usize { + Self::get_pre_writes(encoded) + Self::get_src_size(encoded) + - (Self::is_double_read_post(encoded) as usize + 1) + } + pub const fn get_pre_write_offset(_encoded: u64) -> usize { + 0 + } + pub const fn get_post_write_offset(encoded: u64) -> usize { + (Self::get_pre_count(encoded) != 0) as usize + } + pub const fn get_pre_data_offset(encoded: u64) -> usize { + Self::get_pre_writes(encoded) + } +} + +impl DmaHelpers { + pub fn calculate_write_value( + dst_offset: u64, + src_offset: u64, + count: u64, + pre_value: u64, + src_values: &[u64], + ) -> u64 { + let write_mask = + (0xFFFF_FFFF_FFFF_FFFF << ((8 - count) * 8)) >> ((8 - dst_offset - count) * 8); + let value = if dst_offset <= src_offset { + (src_values[0] >> ((src_offset - dst_offset) * 8)) + | if dst_offset == src_offset { + 0 + } else if (src_offset + count) > 8 { + if src_values.len() < 2 { + panic!("ERROR src_values: {:?} dst_offset: {dst_offset} src_offset: {src_offset} count: {count}", src_values); + } + src_values[1] << ((8 - src_offset + dst_offset) * 8) + } else { + 0 + } + } else if dst_offset > src_offset { + src_values[0] << ((dst_offset - src_offset) * 8) + } else { + // dst_offset = src_offset + src_values[0] + }; + #[cfg(feature = "debug_dma")] + println!( + "WRITE_MASK 0x{write_mask:016X} VALUE 0x{value:016X} SRC_VALUES 0x{:016X},0x{:016X} PRE_VALUE:{pre_value:016X} DST_OFFSET:{dst_offset} SRC_OFFSET:{src_offset} COUNT:{count}", + src_values[0], if src_values.len() > 1 { src_values[1] } else { 0 } + ); + (pre_value & !write_mask) | (value & write_mask) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Helper function to compute expected value using byte-by-byte copy + fn expected_write_value( + dst_offset: u64, + src_offset: u64, + count: u64, + pre_value: u64, + src_values: &[u64], + ) -> u64 { + // Convert pre_value to bytes (big-endian layout as used in the function) + let mut result_bytes = pre_value.to_le_bytes(); + + // Convert src_values to a contiguous byte array (big-endian) + let mut src_bytes = Vec::new(); + for &val in src_values { + src_bytes.extend_from_slice(&val.to_le_bytes()); + } + + // Copy count bytes from src_bytes[src_offset..] to result_bytes[dst_offset..] + for i in 0..count as usize { + result_bytes[dst_offset as usize + i] = src_bytes[src_offset as usize + i]; + } + + u64::from_le_bytes(result_bytes) + } + + #[test] + fn test_calculate_write_value_all_combinations() { + // Test patterns for src_values + let src0: u64 = 0x0102030405060708; + let src1: u64 = 0x1112131415161718; + + // Test pattern for pre_value + let pre_value: u64 = 0xAABBCCDDEEFF0011; + + // Iterate over all dst_offset values (0..8) + for dst_offset in 0..8 { + // For each dst_offset, count can be 1 to (8 - dst_offset) + for count in 1..=(8 - dst_offset) { + // For each valid (dst_offset, count), test all src_offset values + // src_offset can be 0..8, but we need to ensure we have enough src data + for src_offset in 0..8 { + // Determine if we need one or two src values + // We need two src values if (src_offset + count) > 8 + let needs_two_src = (src_offset + count) > 8; + let src_values: Vec = if needs_two_src { + vec![src0, src1] + } else { + vec![src0, 0] // Always provide both for safety + }; + + let result = DmaHelpers::calculate_write_value( + dst_offset, + src_offset, + count, + pre_value, + &src_values, + ); + + let expected = + expected_write_value(dst_offset, src_offset, count, pre_value, &src_values); + + assert_eq!( + result, expected, + "Failed for dst_offset={}, src_offset={}, count={}\n\ + pre_value: 0x{:016X}\n\ + src[0]: 0x{:016X}\n\ + src[1]: 0x{:016X}\n\ + expected: 0x{:016X}\n\ + got: 0x{:016X}", + dst_offset, src_offset, count, pre_value, src0, src1, expected, result + ); + } + } + } + } + + #[test] + fn test_calculate_write_value_edge_cases() { + let src0: u64 = 0x0102030405060708; + let src1: u64 = 0x1112131415161718; + let pre_value: u64 = 0xAABBCCDDEEFF0011; + + // Test case: dst_offset=0, count=8 (full overwrite) + let result = DmaHelpers::calculate_write_value(0, 0, 8, pre_value, &[src0, src1]); + assert_eq!(result, src0, "Full overwrite with aligned offsets failed"); + + // Test case: dst_offset=0, count=1 (single byte at start) + let result = DmaHelpers::calculate_write_value(0, 0, 1, pre_value, &[src0, src1]); + let expected = 0xAABBCCDDEEFF0008u64; + assert_eq!(result, expected, "Single byte at start failed"); + + // Test case: dst_offset=7, count=1 (single byte at end) + let result = DmaHelpers::calculate_write_value(7, 0, 1, pre_value, &[src0, src1]); + let expected = 0x08BBCCDDEEFF0011; + assert_eq!(result, expected, "Single byte at end failed"); + + // Test case: src spans two values (src_offset=7, count=2) + let result = DmaHelpers::calculate_write_value(0, 7, 2, pre_value, &[src0, src1]); + let expected = 0xAABBCCDDEEFF1801; + assert_eq!(result, expected, "Src spanning two values failed"); + } + + #[test] + fn test_calculate_write_value_zero_patterns() { + let src0: u64 = 0x0000000000000000; + let src1: u64 = 0x0000000000000000; + let pre_value: u64 = 0xFFFFFFFFFFFFFFFF; + + // Writing zeros should clear the appropriate bytes + for dst_offset in 0..8 { + for count in 1..=(8 - dst_offset) { + let result = DmaHelpers::calculate_write_value( + dst_offset, + 0, + count, + pre_value, + &[src0, src1], + ); + let expected = expected_write_value(dst_offset, 0, count, pre_value, &[src0, src1]); + assert_eq!( + result, expected, + "Zero pattern failed for dst_offset={}, count={}", + dst_offset, count + ); + } + } + } + + #[test] + fn test_calculate_write_value_ff_patterns() { + let src0: u64 = 0xFFFFFFFFFFFFFFFF; + let src1: u64 = 0xFFFFFFFFFFFFFFFF; + let pre_value: u64 = 0x0000000000000000; + + // Writing 0xFF should set the appropriate bytes + for dst_offset in 0..8 { + for count in 1..=(8 - dst_offset) { + let result = DmaHelpers::calculate_write_value( + dst_offset, + 0, + count, + pre_value, + &[src0, src1], + ); + let expected = expected_write_value(dst_offset, 0, count, pre_value, &[src0, src1]); + assert_eq!( + result, expected, + "FF pattern failed for dst_offset={}, count={}", + dst_offset, count + ); + } + } + } + + /// Byte-based implementation for comparison + #[inline(always)] + fn calculate_write_value_bytes( + dst_offset: usize, + src_offset: usize, + count: usize, + pre_value: u64, + src_values: &[u64], + ) -> u64 { + let mut result_bytes = pre_value.to_le_bytes(); + let src0_bytes = src_values[0].to_le_bytes(); + let src1_bytes = src_values[1].to_le_bytes(); + + for i in 0..count { + let src_idx = src_offset + i; + result_bytes[dst_offset + i] = + if src_idx < 8 { src0_bytes[src_idx] } else { src1_bytes[src_idx - 8] }; + } + + u64::from_le_bytes(result_bytes) + } + + #[test] + fn benchmark_calculate_write_value() { + use std::time::Instant; + + let src0: u64 = 0x0102030405060708; + let src1: u64 = 0x1112131415161718; + let pre_value: u64 = 0xAABBCCDDEEFF0011; + let src_values = [src0, src1]; + + const ITERATIONS: usize = 1_000_000; + + // Warm up + let mut sum_bitwise: u64 = 0; + let mut sum_bytes: u64 = 0; + + // Benchmark bitwise implementation + let start = Instant::now(); + for _ in 0..ITERATIONS { + for dst_offset in 0..8 { + for count in 1..=(8 - dst_offset) { + for src_offset in 0..8 { + sum_bitwise = sum_bitwise.wrapping_add(DmaHelpers::calculate_write_value( + dst_offset, + src_offset, + count, + pre_value, + &src_values, + )); + } + } + } + } + let bitwise_duration = start.elapsed(); + + // Benchmark byte-based implementation + let start = Instant::now(); + for _ in 0..ITERATIONS { + for dst_offset in 0..8 { + for count in 1..=(8 - dst_offset) { + for src_offset in 0..8 { + sum_bytes = sum_bytes.wrapping_add(calculate_write_value_bytes( + dst_offset, + src_offset, + count, + pre_value, + &src_values, + )); + } + } + } + } + let bytes_duration = start.elapsed(); + + // Verify both produce same results + assert_eq!(sum_bitwise, sum_bytes, "Results differ!"); + + // 288 combinations per iteration (8 dst * varying count * 8 src) + let total_ops = ITERATIONS * 288; + + println!("\n=== Benchmark Results ==="); + println!("Iterations: {} ({} total operations)", ITERATIONS, total_ops); + println!("Bitwise implementation: {:?}", bitwise_duration); + println!("Byte-based implementation: {:?}", bytes_duration); + println!( + "Bitwise ops/sec: {:.2}M", + total_ops as f64 / bitwise_duration.as_secs_f64() / 1_000_000.0 + ); + println!( + "Bytes ops/sec: {:.2}M", + total_ops as f64 / bytes_duration.as_secs_f64() / 1_000_000.0 + ); + println!( + "Speedup (bitwise vs bytes): {:.2}x", + bytes_duration.as_secs_f64() / bitwise_duration.as_secs_f64() + ); + println!("Checksum (to prevent optimization): {}", sum_bitwise); + } + + #[test] + fn asm_fast_encode_table() { + let table = generate_fast_encode_table(); + for i in 0..256 { + let dst_offset = i >> 5; + let src_offset = (i >> 2) & 0x7; + println!( + "\t.quad 0x{:016x}, 0x{:016X}, 0x{:016X}, 0x{:016X} # {:4} - {:4} D{dst_offset} S{src_offset} C{}", + table[i * 4], + table[i * 4 + 1], + table[i * 4 + 2], + table[i * 4 + 3], + i * 4, + i * 4 + 3, + (i * 4) & 0xF + ); + } + assert!(table.len() == 1024); + } + #[test] + fn test_fast_encode_table() { + for dst in 0..256 { + for src in 0..256 { + for count in 0..256 { + let encode = DmaInfo::encode_memcpy(dst, src, count); + let fast_encode = DmaInfo::fast_encode_memcpy(dst, src, count); + assert_eq!( + encode, + fast_encode, + "testing dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}" + ); + } + } + } + } + + #[allow(dead_code)] + fn benchmark_fast_encode_vs_encode_memcpy() { + use std::time::Instant; + + const ITERATIONS: usize = 10_000_000; + let mut checksum1 = DmaInfo::encode_memcpy(0, 0, 8); + let mut checksum2 = DmaInfo::fast_encode_memcpy(0, 0, 8); + + // Benchmark encode_memcpy (original) + let start = Instant::now(); + for i in 0..ITERATIONS { + let dst = (i & 0xFF) as u64; + let src = ((i >> 8) & 0xFF) as u64; + let count = (i >> 16) & 0xFF; + checksum1 ^= DmaInfo::encode_memcpy(dst, src, count); + } + let duration_encode = start.elapsed(); + + // Benchmark fast_encode (table-based) + let start = Instant::now(); + for i in 0..ITERATIONS { + let dst = (i & 0xFF) as u64; + let src = ((i >> 8) & 0xFF) as u64; + let count = (i >> 16) & 0xFF; + checksum2 ^= DmaInfo::fast_encode_memcpy(dst, src, count); + } + let duration_fast = start.elapsed(); + + // Verify both produce same results + assert_eq!(checksum1, checksum2, "Checksums must match!"); + + // Print results + println!("\n═══════════════════════════════════════════════════════"); + println!(" DMA Encoding Benchmark ({} iterations)", ITERATIONS); + println!("═══════════════════════════════════════════════════════"); + println!( + " encode_memcpy: {:?} ({:.2} ns/op)", + duration_encode, + duration_encode.as_nanos() as f64 / ITERATIONS as f64 + ); + println!( + " fast_encode: {:?} ({:.2} ns/op)", + duration_fast, + duration_fast.as_nanos() as f64 / ITERATIONS as f64 + ); + println!("───────────────────────────────────────────────────────"); + let speedup = duration_encode.as_nanos() as f64 / duration_fast.as_nanos() as f64; + println!(" Speedup: {:.2}x faster", speedup); + println!("═══════════════════════════════════════════════════════\n"); + + // Assert that fast_encode is actually faster + assert!(duration_fast < duration_encode, "fast_encode should be faster than encode_memcpy"); + } +} diff --git a/precompiles/helpers/src/lib.rs b/precompiles/helpers/src/lib.rs index 5c2c38842..2e090138a 100644 --- a/precompiles/helpers/src/lib.rs +++ b/precompiles/helpers/src/lib.rs @@ -2,12 +2,14 @@ mod arith_eq; mod arith_eq_384; mod big_int; mod common; +mod dma; mod keccak; pub use arith_eq::*; pub use arith_eq_384::*; pub use big_int::*; pub use common::*; +pub use dma::*; pub use keccak::{ keccak_f, keccak_f_expr, keccak_f_round_states, keccak_f_rounds, keccak_f_state, keccakf_idx_pos, keccakf_state_from_linear, keccakf_state_to_linear, diff --git a/precompiles/keccakf/pil/keccakf.pil b/precompiles/keccakf/pil/keccakf.pil index cccb54c01..8e0c7c4bd 100644 --- a/precompiles/keccakf/pil/keccakf.pil +++ b/precompiles/keccakf/pil/keccakf.pil @@ -181,7 +181,7 @@ airtemplate Keccakf(const int N = 2**17, const int operation_bus_id = OPERATION_ } // --> Constraints to make sure that this coprocessor is called from the main processor - lookup_proves(operation_bus_id, [OP_KECCAKF, step_addr'(STEP_MAIN), 0, step_addr'(ADDR_STATE), 0, 0, 0, 0], mul: in_use_clk_0); + lookup_proves(operation_bus_id, [OP_KECCAKF, 0, 0, step_addr'(ADDR_STATE), 0, 0, 0, 0, step_addr'(STEP_MAIN)], mul: in_use_clk_0); function add(const expr b[]): const expr { const int len = length(b); diff --git a/precompiles/keccakf/src/keccakf_bus_device.rs b/precompiles/keccakf/src/keccakf_bus_device.rs index 2a1884298..87ca467b2 100644 --- a/precompiles/keccakf/src/keccakf_bus_device.rs +++ b/precompiles/keccakf/src/keccakf_bus_device.rs @@ -2,11 +2,12 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::Keccakf` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; +use zisk_common::STEP; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; @@ -48,6 +49,51 @@ impl KeccakfCounterInputGen { pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { (op_type == ZiskOperationType::Keccak).then_some(self.counter.inst_count) } + + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + mem_processors: &mut P, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if data[OP_TYPE] as u32 != ZiskOperationType::Keccak as u32 { + return true; + } + + let step_main = data[STEP]; + let addr_main = data[B] as u32; + + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_keccakf_mem_inputs(addr_main, step_main, data, true, mem_processors); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + if skip_keccakf_mem_inputs(addr_main, mem_processors) { + return true; + } + generate_keccakf_mem_inputs(addr_main, step_main, data, false, mem_processors); + } + } + + true + } } impl Metrics for KeccakfCounterInputGen { @@ -90,57 +136,6 @@ impl Add for KeccakfCounterInputGen { } impl BusDevice for KeccakfCounterInputGen { - /// Processes data received on the bus, updating counters and generating inputs when applicable. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus sending the data. - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - debug_assert!(*bus_id == OPERATION_BUS_ID); - - if data[OP_TYPE] as u32 != ZiskOperationType::Keccak as u32 { - return true; - } - - if let Some(mem_collectors_info) = mem_collector_info { - if skip_keccakf_mem_inputs(data[B] as u32, mem_collectors_info) { - return true; - } - } - - let step_main = data[A]; - let addr_main = data[B] as u32; - - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); - } - - generate_keccakf_mem_inputs(addr_main, step_main, data, only_counters, pending); - - true - } - - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs b/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs index 3c648530d..a35672ca7 100644 --- a/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs +++ b/precompiles/keccakf/src/keccakf_gen_mem_inputs.rs @@ -1,10 +1,9 @@ -use std::collections::VecDeque; +use precompiles_common::MemProcessor; use tiny_keccak::keccakf; use precompiles_common::MemBusHelpers; -use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; #[derive(Debug)] pub struct KeccakfMemInputConfig { @@ -14,16 +13,16 @@ pub struct KeccakfMemInputConfig { pub chunks_per_param: usize, } -pub fn generate_keccakf_mem_inputs( +pub fn generate_keccakf_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // Get the basic data from the input // op,op_type,a,b,... - let state: &mut [u64; 25] = &mut data[4..29].try_into().unwrap(); + let state: &mut [u64; 25] = &mut data[5..30].try_into().unwrap(); // Apply the keccakf function keccakf(state); @@ -33,7 +32,7 @@ pub fn generate_keccakf_mem_inputs( let write_params = 1; let chunks_per_param = 25; let params_count = read_params + write_params; - let params_offset = OPERATION_BUS_DATA_SIZE; + let params_offset = OPERATION_PRECOMPILED_BUS_DATA_SIZE; for iparam in 0..params_count { let is_write = iparam >= read_params; let param_index = if is_write { iparam - read_params } else { iparam }; @@ -59,23 +58,21 @@ pub fn generate_keccakf_mem_inputs( step_main, chunk_data, is_write, - pending, + mem_processors, ); } } } -pub fn skip_keccakf_mem_inputs(addr_main: u32, mem_collectors_info: &[MemCollectorInfo]) -> bool { +pub fn skip_keccakf_mem_inputs(addr_main: u32, mem_processors: &mut P) -> bool { let write_params = 1; let chunks_per_param = 25; for param_index in 0..write_params { let param_addr = addr_main + (param_index * 8 * chunks_per_param) as u32; for ichunk in 0..chunks_per_param { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } diff --git a/precompiles/keccakf/src/keccakf_input.rs b/precompiles/keccakf/src/keccakf_input.rs index ebbb38dc3..daeb20f76 100644 --- a/precompiles/keccakf/src/keccakf_input.rs +++ b/precompiles/keccakf/src/keccakf_input.rs @@ -10,9 +10,9 @@ pub struct KeccakfInput { impl KeccakfInput { pub fn from(values: &OperationKeccakData) -> Self { Self { - step_main: values[2], + step_main: values[4], addr_main: values[3] as u32, - state: values[4..29].try_into().unwrap(), + state: values[5..30].try_into().unwrap(), } } } diff --git a/precompiles/keccakf/src/keccakf_instance.rs b/precompiles/keccakf/src/keccakf_instance.rs index 13f2187cc..ba901967b 100644 --- a/precompiles/keccakf/src/keccakf_instance.rs +++ b/precompiles/keccakf/src/keccakf_instance.rs @@ -6,14 +6,10 @@ use crate::{KeccakfInput, KeccakfSM}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::{ - any::Any, - collections::{HashMap, VecDeque}, - sync::Arc, -}; +use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::{ BusDevice, BusId, CheckPoint, ChunkId, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, + InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; use zisk_pil::KeccakfTrace; @@ -162,9 +158,7 @@ impl KeccakfCollector { collect_skipper, } } -} -impl BusDevice for KeccakfCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -177,13 +171,7 @@ impl BusDevice for KeccakfCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -208,15 +196,9 @@ impl BusDevice for KeccakfCollector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for KeccakfCollector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/keccakf/src/keccakf_manager.rs b/precompiles/keccakf/src/keccakf_manager.rs index e3a206cdf..28f9e97d0 100644 --- a/precompiles/keccakf/src/keccakf_manager.rs +++ b/precompiles/keccakf/src/keccakf_manager.rs @@ -2,11 +2,8 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{BusDevice, PayloadType}; -use zisk_common::{ - BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::KeccakfTrace; @@ -31,8 +28,11 @@ impl KeccakfManager { Arc::new(Self { keccakf_sm }) } - pub fn build_keccakf_counter(&self) -> KeccakfCounterInputGen { - KeccakfCounterInputGen::new(BusDeviceMode::Counter) + pub fn build_keccakf_counter(&self, asm_execution: bool) -> KeccakfCounterInputGen { + match asm_execution { + true => KeccakfCounterInputGen::new(BusDeviceMode::CounterAsm), + false => KeccakfCounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_keccakf_input_generator(&self) -> KeccakfCounterInputGen { @@ -41,14 +41,6 @@ impl KeccakfManager { } impl ComponentBuilder for KeccakfManager { - /// Builds and returns a new counter for monitoring keccakf operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for keccakf operations. - fn build_counter(&self) -> Option> { - Some(Box::new(KeccakfCounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan keccakf-related instances. /// /// # Returns @@ -86,8 +78,4 @@ impl ComponentBuilder for KeccakfManager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(KeccakfCounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/precompiles/poseidon2/pil/poseidon2.pil b/precompiles/poseidon2/pil/poseidon2.pil index a9a3522d7..f9a25b379 100644 --- a/precompiles/poseidon2/pil/poseidon2.pil +++ b/precompiles/poseidon2/pil/poseidon2.pil @@ -161,7 +161,7 @@ airtemplate Poseidon2(const int N = 2**17, const int operation_bus_id = OPERATIO // --> Constraints to make sure that this coprocessor is called from the main processor - lookup_proves(operation_bus_id, [OP_POSEIDON2, step_addr'(STEP_MAIN), 0, step_addr'(ADDR_STATE), 0, 0, 0, 0], mul: in_use_clk_0); + lookup_proves(operation_bus_id, [OP_POSEIDON2, 0, 0, step_addr'(ADDR_STATE), 0, 0, 0, 0, step_addr'(STEP_MAIN)], mul: in_use_clk_0); function clock_set(const expr mvcol = 1, const int start = 0, int end = -1): const expr { expr result = 0; diff --git a/precompiles/poseidon2/src/poseidon2_bus_device.rs b/precompiles/poseidon2/src/poseidon2_bus_device.rs index d3c65ca33..e206184c2 100644 --- a/precompiles/poseidon2/src/poseidon2_bus_device.rs +++ b/precompiles/poseidon2/src/poseidon2_bus_device.rs @@ -2,9 +2,10 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::Poseidon2` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; + +use precompiles_common::MemProcessor; -use zisk_common::MemCollectorInfo; use zisk_common::{ BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE, }; @@ -48,6 +49,51 @@ impl Poseidon2CounterInputGen { pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { (op_type == ZiskOperationType::Poseidon2).then_some(self.counter.inst_count) } + + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + mem_processors: &mut P, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if data[OP_TYPE] as u32 != ZiskOperationType::Poseidon2 as u32 { + return true; + } + + let step_main = data[A]; + let addr_main = data[B] as u32; + + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_poseidon2_mem_inputs(addr_main, step_main, data, true, mem_processors); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + if skip_poseidon2_mem_inputs(addr_main, mem_processors) { + return true; + } + generate_poseidon2_mem_inputs(addr_main, step_main, data, false, mem_processors); + } + } + + true + } } impl Metrics for Poseidon2CounterInputGen { @@ -90,57 +136,6 @@ impl Add for Poseidon2CounterInputGen { } impl BusDevice for Poseidon2CounterInputGen { - /// Processes data received on the bus, updating counters and generating inputs when applicable. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus sending the data. - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - debug_assert!(*bus_id == OPERATION_BUS_ID); - - if data[OP_TYPE] as u32 != ZiskOperationType::Poseidon2 as u32 { - return true; - } - - if let Some(mem_collectors_info) = mem_collector_info { - if skip_poseidon2_mem_inputs(data[B] as u32, mem_collectors_info) { - return true; - } - } - - let step_main = data[A]; - let addr_main = data[B] as u32; - - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); - } - - generate_poseidon2_mem_inputs(addr_main, step_main, data, only_counters, pending); - - true - } - - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs b/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs index 4cb07bfe3..9996be979 100644 --- a/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs +++ b/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs @@ -1,8 +1,8 @@ use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; use precompiles_common::MemBusHelpers; -use std::collections::VecDeque; -use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; +use precompiles_common::MemProcessor; + +use zisk_common::OPERATION_BUS_DATA_SIZE; #[derive(Debug)] pub struct Poseidon2MemInputConfig { @@ -12,12 +12,12 @@ pub struct Poseidon2MemInputConfig { pub chunks_per_param: usize, } -pub fn generate_poseidon2_mem_inputs( +pub fn generate_poseidon2_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // Get the basic data from the input // op,op_type,a,b,... @@ -60,23 +60,21 @@ pub fn generate_poseidon2_mem_inputs( step_main, chunk_data, is_write, - pending, + mem_processors, ); } } } -pub fn skip_poseidon2_mem_inputs(addr_main: u32, mem_collectors_info: &[MemCollectorInfo]) -> bool { +pub fn skip_poseidon2_mem_inputs(addr_main: u32, mem_processors: &mut P) -> bool { let write_params = 1; let chunks_per_param = 16; for param_index in 0..write_params { let param_addr = addr_main + (param_index * 8 * chunks_per_param) as u32; for ichunk in 0..chunks_per_param { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } diff --git a/precompiles/poseidon2/src/poseidon2_instance.rs b/precompiles/poseidon2/src/poseidon2_instance.rs index a0ba4f6ed..90b6882a2 100644 --- a/precompiles/poseidon2/src/poseidon2_instance.rs +++ b/precompiles/poseidon2/src/poseidon2_instance.rs @@ -7,12 +7,11 @@ use crate::{Poseidon2Input, Poseidon2SM}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::collections::VecDeque; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, + InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; use zisk_pil::Poseidon2Trace; @@ -151,9 +150,7 @@ impl Poseidon2Collector { collect_skipper, } } -} -impl BusDevice for Poseidon2Collector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -166,13 +163,7 @@ impl BusDevice for Poseidon2Collector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -197,15 +188,9 @@ impl BusDevice for Poseidon2Collector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for Poseidon2Collector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/poseidon2/src/poseidon2_manager.rs b/precompiles/poseidon2/src/poseidon2_manager.rs index 77ed48897..a025941c8 100644 --- a/precompiles/poseidon2/src/poseidon2_manager.rs +++ b/precompiles/poseidon2/src/poseidon2_manager.rs @@ -1,10 +1,7 @@ use std::sync::Arc; use fields::PrimeField64; -use zisk_common::{ - BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, - InstanceInfo, PayloadType, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::Poseidon2Trace; @@ -29,8 +26,11 @@ impl Poseidon2Manager { Arc::new(Self { poseidon2_sm }) } - pub fn build_poseidon2_counter(&self) -> Poseidon2CounterInputGen { - Poseidon2CounterInputGen::new(BusDeviceMode::Counter) + pub fn build_poseidon2_counter(&self, asm_execution: bool) -> Poseidon2CounterInputGen { + match asm_execution { + true => Poseidon2CounterInputGen::new(BusDeviceMode::CounterAsm), + false => Poseidon2CounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_poseidon2_input_generator(&self) -> Poseidon2CounterInputGen { @@ -39,14 +39,6 @@ impl Poseidon2Manager { } impl ComponentBuilder for Poseidon2Manager { - /// Builds and returns a new counter for monitoring poseidon2 operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for poseidon2 operations. - fn build_counter(&self) -> Option> { - Some(Box::new(Poseidon2CounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan poseidon2-related instances. /// /// # Returns @@ -87,8 +79,4 @@ impl ComponentBuilder for Poseidon2Manager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(Poseidon2CounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/precompiles/sha256f/pil/sha256f.pil b/precompiles/sha256f/pil/sha256f.pil index 3aab7e46b..1dd677969 100644 --- a/precompiles/sha256f/pil/sha256f.pil +++ b/precompiles/sha256f/pil/sha256f.pil @@ -249,7 +249,8 @@ airtemplate Sha256f(const int N = 2**22, const int operation_bus_id = OPERATION_ ); // --> Constraints to make sure that this coprocessor is called from the main processor - lookup_proves(operation_bus_id, [OP_SHA256F, step_addr'(STEP_MAIN), 0, step_addr'(ADDR_OP), 0, 0, 0, 0], mul: in_use_clk_0); + + lookup_proves(operation_bus_id, [OP_SHA256F, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(STEP_MAIN)], mul: in_use_clk_0); function pack(const expr a[]): expr { const int len = length(a); diff --git a/precompiles/sha256f/src/sha256f_bus_device.rs b/precompiles/sha256f/src/sha256f_bus_device.rs index e232b50b3..a9814a51c 100644 --- a/precompiles/sha256f/src/sha256f_bus_device.rs +++ b/precompiles/sha256f/src/sha256f_bus_device.rs @@ -2,11 +2,12 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::Sha256f` instructions. -use std::{collections::VecDeque, ops::Add}; +use std::ops::Add; -use zisk_common::MemCollectorInfo; +use precompiles_common::MemProcessor; +use zisk_common::STEP; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; @@ -48,6 +49,51 @@ impl Sha256fCounterInputGen { pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { (op_type == ZiskOperationType::Sha256).then_some(self.counter.inst_count) } + + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + mem_processors: &mut P, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if data[OP_TYPE] as u32 != ZiskOperationType::Sha256 as u32 { + return true; + } + + let step_main = data[STEP]; + let addr_main = data[B] as u32; + + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_sha256f_mem_inputs(addr_main, step_main, data, true, mem_processors); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + if skip_sha256f_mem_inputs(addr_main, data, mem_processors) { + return true; + } + generate_sha256f_mem_inputs(addr_main, step_main, data, false, mem_processors); + } + } + + true + } } impl Metrics for Sha256fCounterInputGen { @@ -90,57 +136,6 @@ impl Add for Sha256fCounterInputGen { } impl BusDevice for Sha256fCounterInputGen { - /// Processes data received on the bus, updating counters and generating inputs when applicable. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus sending the data. - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, - mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - debug_assert!(*bus_id == OPERATION_BUS_ID); - - if data[OP_TYPE] as u32 != ZiskOperationType::Sha256 as u32 { - return true; - } - - if let Some(mem_collectors_info) = mem_collector_info { - if skip_sha256f_mem_inputs(data[B] as u32, data, mem_collectors_info) { - return true; - } - } - - let step_main = data[A]; - let addr_main = data[B] as u32; - - let only_counters = self.mode == BusDeviceMode::Counter; - if only_counters { - self.measure(data); - } - - generate_sha256f_mem_inputs(addr_main, step_main, data, only_counters, pending); - - true - } - - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs index a5ad78347..38998b8d7 100644 --- a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs +++ b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs @@ -1,7 +1,7 @@ use precompiles_common::MemBusHelpers; -use std::collections::VecDeque; -use zisk_common::MemCollectorInfo; -use zisk_common::{BusId, OPERATION_BUS_DATA_SIZE}; +use precompiles_common::MemProcessor; + +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; use zisk_core::sha256f; #[derive(Debug)] @@ -13,17 +13,17 @@ pub struct Sha256MemInputConfig { pub chunks_per_param: usize, } -pub fn generate_sha256f_mem_inputs( +pub fn generate_sha256f_mem_inputs( addr_main: u32, step_main: u64, data: &[u64], only_counters: bool, - pending: &mut VecDeque<(BusId, Vec)>, + mem_processors: &mut P, ) { // Get the basic data from the input // op,op_type,a,b,addr[2],... - let state: &mut [u64; 4] = &mut data[6..10].try_into().unwrap(); - let input: &[u64; 8] = &data[10..18].try_into().unwrap(); + let state: &mut [u64; 4] = &mut data[7..11].try_into().unwrap(); + let input: &[u64; 8] = &data[11..19].try_into().unwrap(); // Apply the sha256f function and get the output sha256f(state, input); @@ -36,8 +36,8 @@ pub fn generate_sha256f_mem_inputs( MemBusHelpers::mem_aligned_load( addr_main + iparam as u32 * 8, step_main, - data[OPERATION_BUS_DATA_SIZE + iparam], - pending, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], + mem_processors, ); } @@ -46,12 +46,12 @@ pub fn generate_sha256f_mem_inputs( let write_params = 1; let chunks_per_param = [4usize, 8, 4]; let params_count = read_params + write_params; - let params_offset = OPERATION_BUS_DATA_SIZE + indirect_params; + let params_offset = OPERATION_PRECOMPILED_BUS_DATA_SIZE + indirect_params; let mut read_chunks = 0; for (iparam, &chunks) in chunks_per_param.iter().enumerate().take(params_count) { let is_write = iparam >= read_params; let param_index = if is_write { iparam - read_params } else { iparam }; - let param_addr = data[OPERATION_BUS_DATA_SIZE + param_index] as u32; + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32; // read/write all chunks of the iparam parameter let current_param_offset = if is_write { // if write calculate index over write_data @@ -75,16 +75,16 @@ pub fn generate_sha256f_mem_inputs( step_main, chunk_data, is_write, - pending, + mem_processors, ); } } } -pub fn skip_sha256f_mem_inputs( +pub fn skip_sha256f_mem_inputs( addr_main: u32, data: &[u64], - mem_collectors_info: &[MemCollectorInfo], + mem_processors: &mut P, ) -> bool { let indirect_params = 2; let read_params = 2; @@ -93,24 +93,20 @@ pub fn skip_sha256f_mem_inputs( for iparam in 0..indirect_params { let addr = addr_main + iparam as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } for (iparam, &chunks) in chunks_per_param.iter().enumerate().take(read_params + write_params) { let is_write = iparam >= read_params; let param_index = if is_write { iparam - read_params } else { iparam }; - let param_addr = data[OPERATION_BUS_DATA_SIZE + param_index] as u32; + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32; for ichunk in 0..chunks { let addr = param_addr + ichunk as u32 * 8; - for mem_collector in mem_collectors_info { - if !mem_collector.skip_addr(addr) { - return false; - } + if !mem_processors.skip_addr(addr) { + return false; } } } diff --git a/precompiles/sha256f/src/sha256f_input.rs b/precompiles/sha256f/src/sha256f_input.rs index 9e2ba78e3..f3e8229c3 100644 --- a/precompiles/sha256f/src/sha256f_input.rs +++ b/precompiles/sha256f/src/sha256f_input.rs @@ -13,12 +13,12 @@ pub struct Sha256fInput { impl Sha256fInput { pub fn from(values: &OperationSha256Data) -> Self { Self { - step_main: values[2], + step_main: values[4], addr_main: values[3] as u32, - state_addr: values[4] as u32, - input_addr: values[5] as u32, - state: values[6..10].try_into().unwrap(), - input: values[10..18].try_into().unwrap(), + state_addr: values[5] as u32, + input_addr: values[6] as u32, + state: values[7..11].try_into().unwrap(), + input: values[11..19].try_into().unwrap(), } } } diff --git a/precompiles/sha256f/src/sha256f_instance.rs b/precompiles/sha256f/src/sha256f_instance.rs index 891e79425..4da9f97c8 100644 --- a/precompiles/sha256f/src/sha256f_instance.rs +++ b/precompiles/sha256f/src/sha256f_instance.rs @@ -7,12 +7,11 @@ use crate::{Sha256fInput, Sha256fSM}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::collections::VecDeque; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, PayloadType, OPERATION_BUS_ID, OP_TYPE, + InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; use zisk_pil::Sha256fTrace; @@ -149,9 +148,7 @@ impl Sha256fCollector { collect_skipper, } } -} -impl BusDevice for Sha256fCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -164,13 +161,7 @@ impl BusDevice for Sha256fCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[PayloadType], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); if self.inputs.len() == self.num_operations as usize { @@ -195,15 +186,9 @@ impl BusDevice for Sha256fCollector { self.inputs.len() < self.num_operations as usize } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for Sha256fCollector { fn as_any(self: Box) -> Box { self } diff --git a/precompiles/sha256f/src/sha256f_manager.rs b/precompiles/sha256f/src/sha256f_manager.rs index 658c6201f..03312d2e1 100644 --- a/precompiles/sha256f/src/sha256f_manager.rs +++ b/precompiles/sha256f/src/sha256f_manager.rs @@ -2,10 +2,7 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{ - BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, - InstanceInfo, PayloadType, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::Sha256fTrace; @@ -30,8 +27,11 @@ impl Sha256fManager { Arc::new(Self { sha256f_sm }) } - pub fn build_sha256f_counter(&self) -> Sha256fCounterInputGen { - Sha256fCounterInputGen::new(BusDeviceMode::Counter) + pub fn build_sha256f_counter(&self, asm_execution: bool) -> Sha256fCounterInputGen { + match asm_execution { + true => Sha256fCounterInputGen::new(BusDeviceMode::CounterAsm), + false => Sha256fCounterInputGen::new(BusDeviceMode::Counter), + } } pub fn build_sha256f_input_generator(&self) -> Sha256fCounterInputGen { @@ -40,14 +40,6 @@ impl Sha256fManager { } impl ComponentBuilder for Sha256fManager { - /// Builds and returns a new counter for monitoring sha256f operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for sha256f operations. - fn build_counter(&self) -> Option> { - Some(Box::new(Sha256fCounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan sha256f-related instances. /// /// # Returns @@ -85,8 +77,4 @@ impl ComponentBuilder for Sha256fManager { } } } - - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(Sha256fCounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/riscv/src/riscv_inst.rs b/riscv/src/riscv_inst.rs index 6fdacdafd..679ea33dd 100644 --- a/riscv/src/riscv_inst.rs +++ b/riscv/src/riscv_inst.rs @@ -33,7 +33,7 @@ //! See /// RISC-V instruction data -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct RiscvInstruction { /// Instruction ROM address, i.e. program counter value pub rom_address: u64, diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 8b9a9e66c..839296374 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -22,6 +22,7 @@ tracing = { workspace = true } zstd = { workspace = true } bytemuck = { workspace = true } zisk-distributed-common = { workspace = true } +zisk-witness = { workspace = true } [features] default = [] diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 090aaf258..282db02f8 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use crate::{ - get_asm_paths, get_proving_key, get_witness_computation_lib, + get_asm_paths, get_proving_key, prover::{Asm, AsmProver, Emu, EmuProver, ZiskProver}, }; use colored::Colorize; @@ -52,7 +52,6 @@ pub struct ProverClientBuilder { aggregation: bool, rma: bool, compressed: bool, - witness_lib: Option, proving_key: Option, proving_key_snark: Option, elf: Option, @@ -151,18 +150,6 @@ impl ProverClientBuilder { self } - #[must_use] - pub fn witness_lib_path(mut self, witness_lib: PathBuf) -> Self { - self.witness_lib = Some(witness_lib); - self - } - - #[must_use] - pub fn witness_lib_path_opt(mut self, witness_lib: Option) -> Self { - self.witness_lib = witness_lib; - self - } - #[must_use] pub fn proving_key_path(mut self, proving_key: PathBuf) -> Self { self.proving_key = Some(proving_key); @@ -333,7 +320,6 @@ impl ProverClientBuilder { impl ProverClientBuilder { fn build_emu(self) -> Result> { - let witness_lib = get_witness_computation_lib(self.witness_lib.as_ref()); let proving_key = get_proving_key(self.proving_key.as_ref()); let proving_key_snark = None; let elf = self.elf.ok_or_else(|| anyhow::anyhow!("ELF path is required"))?; @@ -348,7 +334,6 @@ impl ProverClientBuilder { Self::print_emu_command_info( self.witness, self.verify_constraints, - &witness_lib, &proving_key, &proving_key_snark, &elf, @@ -361,7 +346,6 @@ impl ProverClientBuilder { self.aggregation, self.rma, self.compressed, - witness_lib, proving_key, proving_key_snark, elf, @@ -381,7 +365,6 @@ impl ProverClientBuilder { fn print_emu_command_info( witness: bool, verify_constraints: bool, - witness_lib: &Path, proving_key: &Path, proving_key_snark: &Option, elf: &Path, @@ -395,7 +378,6 @@ impl ProverClientBuilder { println!("{: >12} Prove", "Command".bright_green().bold()); } - println!("{: >12} {}", "Witness Lib".bright_green().bold(), witness_lib.display()); println!("{: >12} {}", "ELF".bright_green().bold(), elf.display()); println!( "{: >12} {}", @@ -475,7 +457,6 @@ impl ProverClientBuilder { F: PrimeField64, GoldilocksQuinticExtension: ExtensionField, { - let witness_lib = get_witness_computation_lib(self.witness_lib.as_ref()); let proving_key = get_proving_key(self.proving_key.as_ref()); let proving_key_snark = None; let elf = self.elf.ok_or_else(|| anyhow::anyhow!("ELF path is required"))?; @@ -492,7 +473,6 @@ impl ProverClientBuilder { Self::print_asm_command_info( self.witness, self.verify_constraints, - &witness_lib, &proving_key, &proving_key_snark, &elf, @@ -505,7 +485,6 @@ impl ProverClientBuilder { self.aggregation, self.rma, self.compressed, - witness_lib, proving_key, proving_key_snark, elf, @@ -530,7 +509,6 @@ impl ProverClientBuilder { fn print_asm_command_info( witness: bool, verify_constraints: bool, - witness_lib: &Path, proving_key: &Path, proving_key_snark: &Option, elf: &Path, @@ -544,7 +522,6 @@ impl ProverClientBuilder { println!("{: >12} Prove", "Command".bright_green().bold()); } - println!("{: >12} {}", "Witness Lib".bright_green().bold(), witness_lib.display()); println!("{: >12} {}", "ELF".bright_green().bold(), elf.display()); println!("{: >12} {}", "Proving Key".bright_green().bold(), proving_key.display()); @@ -573,7 +550,6 @@ impl From> for ProverClientBuilder { witness: builder.witness, rma: builder.rma, compressed: builder.compressed, - witness_lib: builder.witness_lib, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, @@ -610,7 +586,6 @@ impl From> for ProverClientBuilder { witness: builder.witness, rma: builder.rma, compressed: builder.compressed, - witness_lib: builder.witness_lib, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, @@ -649,7 +624,6 @@ impl From> witness: builder.witness, rma: builder.rma, compressed: builder.compressed, - witness_lib: builder.witness_lib, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, @@ -686,7 +660,6 @@ impl From> for ProverClientBuilder, elf: PathBuf, @@ -57,7 +57,6 @@ impl AsmProver { aggregation, rma, compressed, - witness_lib, proving_key, proving_key_snark, elf, @@ -207,7 +206,6 @@ impl AsmCoreProver { aggregation: bool, rma: bool, compressed: bool, - witness_lib: PathBuf, proving_key: PathBuf, _proving_key_snark: Option, elf: PathBuf, @@ -236,14 +234,12 @@ impl AsmCoreProver { let asm_mt_path = default_cache_path.join(asm_mt_filename); let asm_rh_path = default_cache_path.join(asm_rh_filename); - check_paths_exist(&witness_lib)?; check_paths_exist(&proving_key)?; check_paths_exist(&elf)?; check_paths_exist(&asm_mt_path)?; check_paths_exist(&asm_rh_path)?; - let (library, mut witness_lib) = ZiskLibLoader::load_asm( - witness_lib, + let mut witness_lib = ZiskLibLoader::load_asm( elf, verbose.into(), shared_tables, @@ -287,7 +283,7 @@ impl AsmCoreProver { asm_services.start_asm_services(&asm_mt_path, asm_runner_options)?; timer_stop_and_log_info!(STARTING_ASM_MICROSERVICES); - proofman.register_witness(&mut *witness_lib, library)?; + witness_lib.register_witness(&proofman.get_wcm())?; proofman.set_barrier(); diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index e8382717c..d7e444f53 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -9,17 +9,19 @@ use fields::Goldilocks; use proofman::{AggProofs, ProofInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult}; use proofman_common::{DebugInfo, ProofOptions}; use std::{fs::File, io::Write, path::PathBuf}; + use zisk_common::{ io::{StreamSource, ZiskStdin}, - ExecutorStats, ProofLog, ZiskExecutionResult, ZiskLib, + ExecutorStats, ProofLog, ZiskExecutionResult, }; +use zisk_witness::WitnessLib; pub(crate) struct ProverBackend { pub verify_constraints: bool, pub aggregation: bool, pub rma: bool, pub compressed: bool, - pub witness_lib: Box>, + pub witness_lib: WitnessLib, pub proving_key: PathBuf, pub verify_proofs: bool, pub minimal_memory: bool, diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 588fe4b40..b21b27b9b 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -10,6 +10,7 @@ use std::path::PathBuf; use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ExecutorStats; use zisk_distributed_common::LoggingConfig; +use zisk_witness::WitnessLibrary; use anyhow::Result; @@ -30,7 +31,6 @@ impl EmuProver { aggregation: bool, rma: bool, compressed: bool, - witness_lib: PathBuf, proving_key: PathBuf, proving_key_snark: Option, elf: PathBuf, @@ -48,7 +48,6 @@ impl EmuProver { aggregation, rma, compressed, - witness_lib, proving_key, proving_key_snark, elf, @@ -181,7 +180,6 @@ impl EmuCoreProver { aggregation: bool, rma: bool, compressed: bool, - witness_lib: PathBuf, proving_key: PathBuf, _proving_key_snark: Option, elf: PathBuf, @@ -196,13 +194,11 @@ impl EmuCoreProver { ) -> Result { let custom_commits_map = get_custom_commits_map(&proving_key, &elf)?; - check_paths_exist(&witness_lib)?; check_paths_exist(&proving_key)?; check_paths_exist(&elf)?; // Build emulator library - let (library, mut witness_lib) = - ZiskLibLoader::load_emu(witness_lib, elf, verbose.into(), shared_tables)?; + let mut witness_lib = ZiskLibLoader::load_emu(elf, verbose.into(), shared_tables)?; let proofman = ProofMan::new( proving_key.clone(), @@ -224,7 +220,7 @@ impl EmuCoreProver { initialize_logger(verbose.into(), Some(world_rank)); } - proofman.register_witness(&mut *witness_lib, library)?; + witness_lib.register_witness(&proofman.get_wcm())?; proofman.set_barrier(); diff --git a/sdk/src/utils.rs b/sdk/src/utils.rs index f128284ed..bc6512224 100644 --- a/sdk/src/utils.rs +++ b/sdk/src/utils.rs @@ -15,14 +15,6 @@ pub fn get_home_dir() -> String { env::var("HOME").expect("get_home_dir() failed to get HOME environment variable") } -/// Gets the default witness computation library file location in the home installation directory. -pub fn get_default_witness_computation_lib() -> PathBuf { - let extension = if cfg!(target_os = "macos") { "dylib" } else { "so" }; - let witness_computation_lib = - format!("{}/.zisk/bin/libzisk_witness.{}", get_home_dir(), extension); - PathBuf::from(witness_computation_lib) -} - /// Gets the default proving key file location in the home installation directory. pub fn get_default_proving_key() -> PathBuf { let proving_key = format!("{}/.zisk/provingKey", get_home_dir()); @@ -79,12 +71,6 @@ pub fn cli_fail_if_macos() -> anyhow::Result<()> { } } -/// Gets the witness computation library file location. -/// Uses the default one if not specified by user. -pub fn get_witness_computation_lib(witness_lib: Option<&PathBuf>) -> PathBuf { - witness_lib.cloned().unwrap_or_else(get_default_witness_computation_lib) -} - /// Gets the proving key file location. /// Uses the default one if not specified by user. pub fn get_proving_key(proving_key: Option<&PathBuf>) -> PathBuf { diff --git a/sdk/src/zisk_lib_loader.rs b/sdk/src/zisk_lib_loader.rs index 1e18eed09..c74c35f3b 100644 --- a/sdk/src/zisk_lib_loader.rs +++ b/sdk/src/zisk_lib_loader.rs @@ -1,21 +1,17 @@ use std::path::PathBuf; use fields::PrimeField64; -use libloading::{Library, Symbol}; use proofman_common::VerboseMode; -use zisk_common::{ZiskLib, ZiskLibInitFn}; +use zisk_witness::{init_zisk_lib, WitnessLib}; use anyhow::Result; -use crate::get_witness_computation_lib; - #[derive(Default)] pub struct ZiskLibLoader; impl ZiskLibLoader { #[allow(clippy::too_many_arguments)] fn load_library( - witness_lib: PathBuf, elf: PathBuf, verbose: VerboseMode, shared_tables: bool, @@ -24,15 +20,8 @@ impl ZiskLibLoader { base_port: Option, unlock_mapped_memory: Option, with_hints: bool, - ) -> Result<(Library, Box>)> { - let lib_path = get_witness_computation_lib(Some(&witness_lib)); - let library = unsafe { Library::new(lib_path) }?; - - tracing::info!("Loading witness library from {:?}", witness_lib); - let witness_lib_constructor: Symbol> = - unsafe { library.get(b"init_library")? }; - - let witness_lib = witness_lib_constructor( + ) -> Result> { + let witness_lib = init_zisk_lib( verbose, elf, asm_mt_filename, @@ -41,24 +30,21 @@ impl ZiskLibLoader { unlock_mapped_memory.unwrap_or(false), shared_tables, with_hints, - ) - .expect("Failed to initialize witness library"); + ); - Ok((library, witness_lib)) + Ok(witness_lib) } pub fn load_emu( - witness_lib: PathBuf, elf: PathBuf, verbose: VerboseMode, shared_tables: bool, - ) -> Result<(Library, Box>)> { - Self::load_library(witness_lib, elf, verbose, shared_tables, None, None, None, None, false) + ) -> Result> { + Self::load_library(elf, verbose, shared_tables, None, None, None, None, false) } #[allow(clippy::too_many_arguments)] pub fn load_asm( - witness_lib: PathBuf, elf: PathBuf, verbose: VerboseMode, shared_tables: bool, @@ -67,9 +53,8 @@ impl ZiskLibLoader { base_port: Option, unlock_mapped_memory: bool, with_hints: bool, - ) -> Result<(Library, Box>)> { + ) -> Result> { Self::load_library( - witness_lib, elf, verbose, shared_tables, diff --git a/state-machines/arith/pil/arith.pil b/state-machines/arith/pil/arith.pil index 4bc4e49b7..127c816cf 100644 --- a/state-machines/arith/pil/arith.pil +++ b/state-machines/arith/pil/arith.pil @@ -270,7 +270,7 @@ airtemplate Arith(const int N = 2**18, const int operation_bus_id = OPERATION_BU bus_a0, bus_a1, bus_b0, bus_b1, bus_res0, bus_res1, - div_by_zero /*+ div_overflow*/], mul: multiplicity); + div_by_zero /*+ div_overflow*/, 0], mul: multiplicity); // Check that remainder (d) is lower than divisor (b) when division is performed // Specifically, we ensure that 0 <= |d| < |b| @@ -278,7 +278,7 @@ airtemplate Arith(const int N = 2**18, const int operation_bus_id = OPERATION_BU (d[0] + CHUNK_SIZE * d[1]), (d[2] + CHUNK_SIZE * d[3]) + m32 * nr * 0xFFFFFFFF, // remainder (b[0] + CHUNK_SIZE * b[1]), (b[2] + CHUNK_SIZE * b[3]) + m32 * nb * 0xFFFFFFFF, // divisor 1, 0, - 1], sel: div * (1 - div_by_zero)); + 1, 0], sel: div * (1 - div_by_zero)); for (int index = 0; index < length(carry); ++index) { arith_range_table_assumes(ARITH_RANGE_CARRY, carry[index]); // TODO: review carry range diff --git a/state-machines/arith/pil/arith_mul64.pil b/state-machines/arith/pil/arith_mul64.pil index 57372a4ed..76a234cd9 100644 --- a/state-machines/arith/pil/arith_mul64.pil +++ b/state-machines/arith/pil/arith_mul64.pil @@ -210,7 +210,7 @@ airtemplate ArithMul64(const int N = 2**18, const int operation_bus_id = OPERATI (d[0] + CHUNK_SIZE * d[1]), (d[2] + CHUNK_SIZE * d[3]) + m32 * nr * 0xFFFFFFFF, // remainder (b[0] + CHUNK_SIZE * b[1]), (b[2] + CHUNK_SIZE * b[3]) + m32 * nb * 0xFFFFFFFF, // divisor 1, 0, - 1], sel: div * (1 - div_by_zero)); + 1, 0], sel: div * (1 - div_by_zero)); for (int index = 0; index < length(carry); ++index) { arith_range_table_assumes(ARITH_RANGE_CARRY, carry[index]); // TODO: review carry range diff --git a/state-machines/arith/src/arith.rs b/state-machines/arith/src/arith.rs index bf41af61f..103f3bb85 100644 --- a/state-machines/arith/src/arith.rs +++ b/state-machines/arith/src/arith.rs @@ -10,10 +10,7 @@ use std::sync::Arc; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{ - BusDevice, BusDeviceMetrics, BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, - InstanceInfo, PayloadType, Planner, -}; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; use zisk_core::ZiskOperationType; use zisk_pil::ArithTrace; @@ -50,14 +47,6 @@ impl ArithSM { } impl ComponentBuilder for ArithSM { - /// Builds and returns a new counter for monitoring arithmetic operations. - /// - /// # Returns - /// A boxed implementation of `ArithCounter`. - fn build_counter(&self) -> Option> { - Some(Box::new(ArithCounterInputGen::new(BusDeviceMode::Counter))) - } - /// Builds a planner to plan arithmetic-related instances. /// /// # Returns @@ -86,12 +75,4 @@ impl ComponentBuilder for ArithSM { _ => panic!("BinarySM::get_instance() Unsupported air_id: {:?}", ictx.plan.air_id), } } - - /// Creates and returns an input generator for arithmetic state machine computations. - /// - /// # Returns - /// A boxed implementation of `ArithInputGenerator`. - fn build_inputs_generator(&self) -> Option>> { - Some(Box::new(ArithCounterInputGen::new(BusDeviceMode::InputGenerator))) - } } diff --git a/state-machines/arith/src/arith_bus_device.rs b/state-machines/arith/src/arith_bus_device.rs index c4b4349ff..f59970ce4 100644 --- a/state-machines/arith/src/arith_bus_device.rs +++ b/state-machines/arith/src/arith_bus_device.rs @@ -9,8 +9,7 @@ use fields::Goldilocks; use std::collections::VecDeque; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, MemCollectorInfo, Metrics, A, B, OP, - OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OP, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; @@ -55,31 +54,7 @@ impl ArithCounterInputGen { pub fn frops_count(&self, op_type: ZiskOperationType) -> Option { (op_type == ZiskOperationType::Arith).then_some(self.counter.frops_count) } -} - -impl Metrics for ArithCounterInputGen { - /// Tracks activity on the connected bus and updates counters for recognized operations. - /// - /// # Arguments - /// * `data` - The data received from the bus. - /// - /// # Returns - /// An empty vector, as this implementation does not produce any derived inputs for the bus. - #[inline(always)] - fn measure(&mut self, _data: &[u64]) { - self.counter.update(1); - } - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} -impl BusDevice for ArithCounterInputGen { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments @@ -91,12 +66,11 @@ impl BusDevice for ArithCounterInputGen { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( + pub fn process_data( &mut self, bus_id: &BusId, data: &[u64], - pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, + pending: &mut VecDeque<(BusId, Vec, Vec)>, ) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); @@ -126,15 +100,31 @@ impl BusDevice for ArithCounterInputGen { true } +} + +impl Metrics for ArithCounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `data` - The data received from the bus. + /// + /// # Returns + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); + } - /// Returns the bus IDs associated with this counter. + /// Provides a dynamic reference for downcasting purposes. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self } +} +impl BusDevice for ArithCounterInputGen { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/arith/src/arith_full.rs b/state-machines/arith/src/arith_full.rs index 35b1a986c..0cdea3e04 100644 --- a/state-machines/arith/src/arith_full.rs +++ b/state-machines/arith/src/arith_full.rs @@ -171,7 +171,10 @@ impl ArithFullSM { /// Generates binary inputs for operations requiring additional validation (e.g., division). #[inline(always)] - pub fn generate_inputs(input: &OperationData, pending: &mut VecDeque<(BusId, Vec)>) { + pub fn generate_inputs( + input: &OperationData, + pending: &mut VecDeque<(BusId, Vec, Vec)>, + ) { let mut aop = ArithOperation::new(); let input_data = ExtOperationData::OperationData(*input); diff --git a/state-machines/arith/src/arith_full_instance.rs b/state-machines/arith/src/arith_full_instance.rs index 9569e3bf6..c84cf545c 100644 --- a/state-machines/arith/src/arith_full_instance.rs +++ b/state-machines/arith/src/arith_full_instance.rs @@ -8,14 +8,10 @@ use crate::{ArithFrops, ArithFullSM}; use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; -use std::{ - collections::{HashMap, VecDeque}, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; use zisk_common::{ BusDevice, BusId, CheckPoint, ChunkId, CollectSkipper, ExtOperationData, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, OperationData, PayloadType, A, B, OP, OPERATION_BUS_ID, - OP_TYPE, + InstanceType, OperationData, PayloadType, A, B, OP, OPERATION_BUS_ID, OP_TYPE, }; use zisk_core::ZiskOperationType; use zisk_pil::ArithTrace; @@ -198,9 +194,7 @@ impl ArithInstanceCollector { frops_table_id, } } -} -impl BusDevice for ArithInstanceCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -212,13 +206,7 @@ impl BusDevice for ArithInstanceCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); let instance_complete = self.inputs.len() == self.num_operations as usize; @@ -254,15 +242,9 @@ impl BusDevice for ArithInstanceCollector { self.inputs.len() < self.num_operations as usize || self.force_execute_to_end } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for ArithInstanceCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/binary/pil/binary.pil b/state-machines/binary/pil/binary.pil index 121176f6b..64107e66a 100644 --- a/state-machines/binary/pil/binary.pil +++ b/state-machines/binary/pil/binary.pil @@ -153,8 +153,8 @@ airtemplate Binary(const int N = 2**21, const int RC = 2, const int bits = 64, c // Otherwise, the result is simply c c[0] += cout; - lookup_proves(operation_bus_id, [b_op + 0x10 * mode32, ...a, ...b, ...c, cout]); + lookup_proves(operation_bus_id, [b_op + 0x10 * mode32, ...a, ...b, ...c, cout, 0]); airval padding_size; - direct_update_assumes(operation_bus_id, [OP_ADD, 0, 0, 0, 0, 0, 0, 0], sel: padding_size); + direct_update_assumes(operation_bus_id, [OP_ADD, 0, 0, 0, 0, 0, 0, 0, 0], sel: padding_size); } diff --git a/state-machines/binary/pil/binary_add.pil b/state-machines/binary/pil/binary_add.pil index ef564dfbb..90d909061 100644 --- a/state-machines/binary/pil/binary_add.pil +++ b/state-machines/binary/pil/binary_add.pil @@ -22,8 +22,8 @@ airtemplate BinaryAdd(const int N = 2**21, const int operation_bus_id = OPERATIO range_check(expression: c_chunks[i * 2 + 1], min: 0, max: 2**16 - 1); } - lookup_proves(operation_bus_id, [OP_ADD, ...a, ...b, ...c, 0]); + lookup_proves(operation_bus_id, [OP_ADD, ...a, ...b, ...c, 0, 0]); airval padding_size; - direct_update_assumes(operation_bus_id, [OP_ADD, 0, 0, 0, 0, 0, 0, 0], sel: padding_size); + direct_update_assumes(operation_bus_id, [OP_ADD, 0, 0, 0, 0, 0, 0, 0, 0], sel: padding_size); } \ No newline at end of file diff --git a/state-machines/binary/pil/binary_extension.pil b/state-machines/binary/pil/binary_extension.pil index 4a1e013b1..3538406d5 100644 --- a/state-machines/binary/pil/binary_extension.pil +++ b/state-machines/binary/pil/binary_extension.pil @@ -125,10 +125,10 @@ airtemplate BinaryExtension(const int N = 2**18, const int bits = 64, const int op_is_shift * (b[1] - a[1]) + a[1], c[0], c[1], - 0 + 0, 0 ] ); airval padding_size; - direct_update_assumes(operation_bus_id, [OP_SEXT_B, 0, 0, 0, 0, 0, 0, 0], sel: padding_size); + direct_update_assumes(operation_bus_id, [OP_SEXT_B, 0, 0, 0, 0, 0, 0, 0, 0], sel: padding_size); } diff --git a/state-machines/binary/src/binary.rs b/state-machines/binary/src/binary.rs index cc3c0c6e6..4d6798af8 100644 --- a/state-machines/binary/src/binary.rs +++ b/state-machines/binary/src/binary.rs @@ -15,7 +15,7 @@ use crate::{ }; use fields::PrimeField64; use pil_std_lib::Std; -use zisk_common::{BusDeviceMetrics, ComponentBuilder, Instance, InstanceCtx, Planner}; +use zisk_common::{ComponentBuilder, Instance, InstanceCtx, Planner}; use zisk_pil::{BinaryAddTrace, BinaryExtensionTrace, BinaryTrace}; /// The `BinarySM` struct represents the Binary State Machine, @@ -58,15 +58,6 @@ impl BinarySM { } impl ComponentBuilder for BinarySM { - /// Builds and returns a new counter for monitoring binary operations. - /// - /// # Returns - /// A boxed implementation of `RegularCounters` configured for binary and extension binary - /// operations. - fn build_counter(&self) -> Option> { - Some(Box::new(BinaryCounter::new())) - } - /// Builds a planner to plan binary-related instances. /// /// # Returns diff --git a/state-machines/binary/src/binary_add_collector.rs b/state-machines/binary/src/binary_add_collector.rs index 2e5982a85..6827464d9 100644 --- a/state-machines/binary/src/binary_add_collector.rs +++ b/state-machines/binary/src/binary_add_collector.rs @@ -1,10 +1,9 @@ //! The `BinaryAddCollector` struct represents an input collector for binary add operations. use crate::BinaryBasicFrops; -use std::collections::VecDeque; use zisk_common::{ - BusDevice, BusId, CollectSkipper, ExtOperationData, MemCollectorInfo, OperationBusData, A, B, - OP, OPERATION_BUS_ID, + BusDevice, BusId, CollectSkipper, ExtOperationData, OperationBusData, A, B, OP, + OPERATION_BUS_ID, }; use zisk_core::zisk_ops::ZiskOp; @@ -57,9 +56,7 @@ impl BinaryAddCollector { std, } } -} -impl BusDevice for BinaryAddCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -71,13 +68,7 @@ impl BusDevice for BinaryAddCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); let instance_complete = self.inputs.len() == self.num_operations; @@ -114,15 +105,9 @@ impl BusDevice for BinaryAddCollector { self.inputs.len() < self.num_operations || self.force_execute_to_end } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for BinaryAddCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/binary/src/binary_basic_collector.rs b/state-machines/binary/src/binary_basic_collector.rs index 2a33e0fd8..841cf5cbc 100644 --- a/state-machines/binary/src/binary_basic_collector.rs +++ b/state-machines/binary/src/binary_basic_collector.rs @@ -2,12 +2,10 @@ //! //! It manages collected inputs for the `BinaryExtensionSM` to compute witnesses -use std::collections::VecDeque; - use crate::{BinaryBasicFrops, BinaryInput}; use zisk_common::{ - BusDevice, BusId, CollectSkipper, ExtOperationData, MemCollectorInfo, OperationBusData, A, B, - OP, OPERATION_BUS_ID, + BusDevice, BusId, CollectSkipper, ExtOperationData, OperationBusData, A, B, OP, + OPERATION_BUS_ID, }; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; @@ -67,9 +65,7 @@ impl BinaryBasicCollector { std, } } -} -impl BusDevice for BinaryBasicCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -81,13 +77,7 @@ impl BusDevice for BinaryBasicCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); let instance_complete = self.inputs.len() == self.num_operations; @@ -127,15 +117,9 @@ impl BusDevice for BinaryBasicCollector { self.inputs.len() < self.num_operations || self.force_execute_to_end } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for BinaryBasicCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/binary/src/binary_counter.rs b/state-machines/binary/src/binary_counter.rs index 2c2b789ce..3d00b8264 100644 --- a/state-machines/binary/src/binary_counter.rs +++ b/state-machines/binary/src/binary_counter.rs @@ -6,10 +6,7 @@ //! the system bus for both monitoring and input generation. use crate::{BinaryBasicFrops, BinaryExtensionFrops}; -use std::collections::VecDeque; -use zisk_common::{ - BusDevice, BusId, Counter, MemCollectorInfo, Metrics, A, B, OP, OPERATION_BUS_ID, OP_TYPE, -}; +use zisk_common::{BusDevice, BusId, Counter, Metrics, A, B, OP, OPERATION_BUS_ID, OP_TYPE}; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; /// The `BinaryCounter` struct represents a counter that monitors and measures @@ -40,6 +37,25 @@ impl BinaryCounter { pub fn new() -> Self { Self::default() } + + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + self.measure(data); + + true + } } impl Metrics for BinaryCounter { @@ -91,39 +107,6 @@ impl Metrics for BinaryCounter { } impl BusDevice for BinaryCounter { - /// Processes data received on the bus, updating counters and generating inputs when applicable. - /// - /// # Arguments - /// * `bus_id` - The ID of the bus sending the data. - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. - /// - /// # Returns - /// A boolean indicating whether the program should continue execution or terminate. - /// Returns `true` to continue execution, `false` to stop. - #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { - debug_assert!(*bus_id == OPERATION_BUS_ID); - - self.measure(data); - - true - } - - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/binary/src/binary_extension_collector.rs b/state-machines/binary/src/binary_extension_collector.rs index deeb14269..733f65232 100644 --- a/state-machines/binary/src/binary_extension_collector.rs +++ b/state-machines/binary/src/binary_extension_collector.rs @@ -2,12 +2,10 @@ //! //! It manages collected inputs for the `BinaryExtensionSM` to compute witnesses -use std::collections::VecDeque; - use crate::{BinaryExtensionFrops, BinaryInput}; use zisk_common::{ - BusDevice, BusId, CollectSkipper, ExtOperationData, MemCollectorInfo, OperationBusData, A, B, - OP, OPERATION_BUS_ID, + BusDevice, BusId, CollectSkipper, ExtOperationData, OperationBusData, A, B, OP, + OPERATION_BUS_ID, }; use fields::PrimeField64; @@ -53,9 +51,7 @@ impl BinaryExtensionCollector { std, } } -} -impl BusDevice for BinaryExtensionCollector { /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -67,13 +63,7 @@ impl BusDevice for BinaryExtensionCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); let instance_complete = self.inputs.len() == self.num_operations; @@ -110,15 +100,9 @@ impl BusDevice for BinaryExtensionCollector { self.inputs.len() < self.num_operations || self.force_execute_to_end } +} - /// Returns the bus IDs associated with this instance. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] - } - +impl BusDevice for BinaryExtensionCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/frequent-ops/pil/frequent_ops.pil b/state-machines/frequent-ops/pil/frequent_ops.pil index 65cb005fe..d95bf327c 100644 --- a/state-machines/frequent-ops/pil/frequent_ops.pil +++ b/state-machines/frequent-ops/pil/frequent_ops.pil @@ -14,5 +14,8 @@ airtemplate FrequentOps(const int N = 2**24, int RC = 2, const int operation_bus col fixed C[RC]; col fixed FLAG; - lookup_proves(operation_bus_id, [OP, ...A, ...B, ...C, FLAG], mul: multiplicity, table_id: table_id); + // REVIEW: REPLACE BY CONSTANT + col fixed __ZERO__ = [0...]; + + lookup_proves(operation_bus_id, [OP, ...A, ...B, ...C, FLAG, __ZERO__], mul: multiplicity, table_id: table_id); } \ No newline at end of file diff --git a/state-machines/main/pil/main.pil b/state-machines/main/pil/main.pil index 9f4054d1d..107140b22 100644 --- a/state-machines/main/pil/main.pil +++ b/state-machines/main/pil/main.pil @@ -1,7 +1,8 @@ -require "std_lookup.pil" -require "std_permutation.pil" -require "std_direct.pil" -require "opids.pil" +require "std_lookup.pil"; +require "std_permutation.pil"; +require "std_direct.pil"; +require "registers.pil" +require "opids.pil"; const int BOOT_ADDR = 0x1000; const int END_PC_ADDR = 0x1004; @@ -111,7 +112,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, } else { col witness bits(32) air.a_imm1; } - col witness bits(1) a_src_step; + col witness bits(1) op_with_step; // Selector to include step on operation bus // Source B @@ -139,7 +140,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, // Destination C - col witness bits(1) store_ra; + col witness bits(1) store_pc; col witness bits(1) store_mem; @@ -256,9 +257,9 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, // region. As a result, no memory state machine can validate memory access in this area; // only the main state machine can 'prove' such memory access. - col witness bits(40) a_reg_prev_mem_step; - col witness bits(40) b_reg_prev_mem_step; - col witness bits(40) store_reg_prev_mem_step; + col witness bits(REG_STEP_BITS) a_reg_prev_mem_step; + col witness bits(REG_STEP_BITS) b_reg_prev_mem_step; + col witness bits(REG_STEP_BITS) store_reg_prev_mem_step; col witness bits(32) store_reg_prev_value[RC]; col witness bits(1) a_src_reg; col witness bits(1) b_src_reg; @@ -306,8 +307,8 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, const expr store_value[2]; - store_value[0] = store_ra*(pc + jmp_offset2 - c[0]) + c[0]; - store_value[1] = (1 - store_ra) * c[1]; + store_value[0] = store_pc*(pc + jmp_offset2 - c[0]) + c[0]; + store_value[1] = (1 - store_pc) * c[1]; // Memory function to prove that previous register store_offset access. @@ -330,11 +331,11 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, range_check(expression: a_mem_step - a_reg_prev_mem_step - 1, min: 0, max: MAX_RANGE, sel: a_src_reg); range_check(expression: b_mem_step - b_reg_prev_mem_step - 1, min: 0, max: MAX_RANGE, sel: b_src_reg); - range_check(expression: store_mem_step - store_reg_prev_mem_step -1 , min: 0, max: MAX_RANGE, sel: store_reg); + range_check(expression: store_mem_step - store_reg_prev_mem_step - 1 , min: 0, max: MAX_RANGE, sel: store_reg); // Sent to bus the external operation - lookup_assumes(operation_bus_id, [op, a[0], (1 - m32) * a[1], b[0], (1 - m32) * b[1], ...c, flag], sel: is_external_op); + lookup_assumes(operation_bus_id, [op, a[0], (1 - m32) * a[1], b[0], (1 - m32) * b[1], ...c, flag, STEP * op_with_step], sel: is_external_op); const expr a_src_c; const expr b_src_c; @@ -348,12 +349,12 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, // Optimization to avoid use extra columns when c is source for a or b. if (stack_enabled) { - a_src_c = 1 - a_src_step - a_src_mem - a_src_imm - a_src_sp; + a_src_c = 1 - a_src_mem - a_src_imm - a_src_sp; b_src_c = 1 - b_src_mem - b_src_imm - b_src_ind - b_src_reg; a_imm[1] = a_use_sp_imm1; b_imm[1] = b_use_sp_imm1; } else { - a_src_c = 1 - a_src_step - a_src_mem - a_src_imm - a_src_reg; + a_src_c = 1 - a_src_mem - a_src_imm - a_src_reg; b_src_c = 1 - b_src_mem - b_src_imm - b_src_ind - b_src_reg; a_imm[1] = a_imm1; b_imm[1] = b_imm1; @@ -368,9 +369,6 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, a_src_sp * (a[index] - (index == 0 ? sp: 0 )) === 0; } - // If source is step, value must be same as STEP - a_src_step * (a[index] - (index == 0 ? STEP : 0)) === 0; - // If source is c, value must be same as previous_c a_src_c * (a[index] - previous_c) === 0; b_src_c * (b[index] - previous_c) === 0; @@ -456,11 +454,11 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, a_src_imm * (1 - a_src_imm) === 0; a_src_mem * (1 - a_src_mem) === 0; - a_src_step * (1 - a_src_step) === 0; + op_with_step * (1 - op_with_step) === 0; b_src_imm * (1 - b_src_imm) === 0; b_src_mem * (1 - b_src_mem) === 0; is_external_op * (1 - is_external_op) === 0; - store_ra * (1 - store_ra) === 0; + store_pc * (1 - store_pc) === 0; store_mem * (1 - store_mem) === 0; store_ind * (1 - store_ind) === 0; set_pc * (1 - set_pc) === 0; @@ -470,8 +468,8 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, b_src_reg * (1 - b_src_reg) === 0; store_reg * (1 - store_reg) === 0; - const expr rom_flags = 1 + 2 * a_src_imm + 4 * a_src_mem + 8 * a_src_step + 16 * b_src_imm - + 32 * b_src_mem + 64 * is_external_op + 128 * store_ra + 256 * store_mem + const expr rom_flags = 1 + 2 * a_src_imm + 4 * a_src_mem + 8 * op_with_step + 16 * b_src_imm + + 32 * b_src_mem + 64 * is_external_op + 128 * store_pc + 256 * store_mem + 512 * store_ind + 1024 * set_pc + 2048 * m32 + 4096 * b_src_ind + 8192 * a_src_reg + 16384 * b_src_reg + 32768 * store_reg; @@ -528,5 +526,5 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, // Main Segment constraint - range_check(main_segment, min: 0, max: ((2**32)/N) - 1); + range_check(main_segment, min: 0, max: ((2**MAIN_STEP_BITS)/N) - 1); } \ No newline at end of file diff --git a/state-machines/main/pil/registers.pil b/state-machines/main/pil/registers.pil new file mode 100644 index 000000000..6488dea12 --- /dev/null +++ b/state-machines/main/pil/registers.pil @@ -0,0 +1,67 @@ +const int REG_ZE = 0; +const int REG_RA = 1; +const int REG_SP = 2; +const int REG_GP = 3; +const int REG_TP = 4; +const int REG_T0 = 5; +const int REG_T1 = 6; +const int REG_T2 = 7; +const int REG_S0 = 8; +const int REG_S1 = 9; +const int REG_A0 = 10; +const int REG_A1 = 11; +const int REG_A2 = 12; +const int REG_A3 = 13; +const int REG_A4 = 14; +const int REG_A5 = 15; +const int REG_A6 = 16; +const int REG_A7 = 17; +const int REG_S2 = 18; +const int REG_S3 = 19; +const int REG_S4 = 20; +const int REG_S5 = 21; +const int REG_S6 = 22; +const int REG_S7 = 23; +const int REG_S8 = 24; +const int REG_S9 = 25; +const int REG_S10 = 26; +const int REG_S11 = 27; +const int REG_T3 = 28; +const int REG_T4 = 29; +const int REG_T5 = 30; +const int REG_T6 = 31; + +const int REG_FP = 8; + +const int REG_X0 = 0; +const int REG_X1 = 1; +const int REG_X2 = 2; +const int REG_X3 = 3; +const int REG_X4 = 4; +const int REG_X5 = 5; +const int REG_X6 = 6; +const int REG_X7 = 7; +const int REG_X8 = 8; +const int REG_X9 = 9; +const int REG_X10 = 10; +const int REG_X11 = 11; +const int REG_X12 = 12; +const int REG_X13 = 13; +const int REG_X14 = 14; +const int REG_X15 = 15; +const int REG_X16 = 16; +const int REG_X17 = 17; +const int REG_X18 = 18; +const int REG_X19 = 19; +const int REG_X20 = 20; +const int REG_X21 = 21; +const int REG_X22 = 22; +const int REG_X23 = 23; +const int REG_X24 = 24; +const int REG_X25 = 25; +const int REG_X26 = 26; +const int REG_X27 = 27; +const int REG_X28 = 28; +const int REG_X29 = 29; +const int REG_X30 = 30; +const int REG_X31 = 31; \ No newline at end of file diff --git a/state-machines/main/src/main_counter.rs b/state-machines/main/src/main_counter.rs index 6f055358d..f939569b4 100644 --- a/state-machines/main/src/main_counter.rs +++ b/state-machines/main/src/main_counter.rs @@ -2,8 +2,7 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::PubOut` instructions. -use std::collections::VecDeque; -use zisk_common::{BusDevice, BusId, MemCollectorInfo, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE}; +use zisk_common::{BusDevice, BusId, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE}; use zisk_core::ZiskOperationType; /// The `MainCounter` struct represents a counter that monitors and measures @@ -33,22 +32,7 @@ impl MainCounter { pub fn new() -> Self { Self { publics: Vec::new() } } -} - -impl Metrics for MainCounter { - #[inline(always)] - fn measure(&mut self, _data: &[u64]) {} - - /// Provides a dynamic reference for downcasting purposes. - /// - /// # Returns - /// A reference to `self` as `dyn std::any::Any`. - fn as_any(&self) -> &dyn std::any::Any { - self - } -} -impl BusDevice for MainCounter { /// Processes data received on the bus, updating counters and generating inputs when applicable. /// /// # Arguments @@ -60,13 +44,7 @@ impl BusDevice for MainCounter { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); const PUBOUT: u64 = ZiskOperationType::PubOut as u64; @@ -83,15 +61,22 @@ impl BusDevice for MainCounter { true } +} + +impl Metrics for MainCounter { + #[inline(always)] + fn measure(&mut self, _data: &[u64]) {} - /// Returns the bus IDs associated with this counter. + /// Provides a dynamic reference for downcasting purposes. /// /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![OPERATION_BUS_ID] + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self } +} +impl BusDevice for MainCounter { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/main/src/main_sm.rs b/state-machines/main/src/main_sm.rs index 280f1a8f8..ec1506ba7 100644 --- a/state-machines/main/src/main_sm.rs +++ b/state-machines/main/src/main_sm.rs @@ -9,14 +9,13 @@ use std::sync::Arc; -use crate::MainCounter; use fields::PrimeField64; use mem_common::{MemHelpers, MEM_REGS_MAX_DIFF, MEM_STEPS_BY_MAIN_STEP}; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofCtx, ProofmanResult, SetupCtx}; use rayon::prelude::*; -use zisk_common::{BusDeviceMetrics, EmuTrace, InstanceCtx, SegmentId}; -use zisk_core::{ZiskRom, REGS_IN_MAIN, REGS_IN_MAIN_FROM, REGS_IN_MAIN_TO}; +use zisk_common::{EmuTrace, InstanceCtx, SegmentId}; +use zisk_core::{ZiskRom, DEFAULT_MAX_STEPS, REGS_IN_MAIN, REGS_IN_MAIN_FROM, REGS_IN_MAIN_TO}; use zisk_pil::MainAirValues; use ziskemu::{Emu, EmuRegTrace}; @@ -45,7 +44,8 @@ pub struct MainInstance { } impl MainInstance { - const MAX_SEGMENT_ID: usize = ((1 << 32) / MainTraceType::::NUM_ROWS) - 1; + const MAX_SEGMENT_ID: usize = + ((DEFAULT_MAX_STEPS + 1) as usize / MainTraceType::::NUM_ROWS) - 1; /// Creates a new `MainInstance`. /// @@ -372,8 +372,4 @@ impl MainSM { pub fn debug(_pctx: &ProofCtx, _sctx: &SetupCtx) { // No debug information to display } - - pub fn build_counter() -> Box { - Box::new(MainCounter::new()) - } } diff --git a/state-machines/mem-common/src/mem_constants.rs b/state-machines/mem-common/src/mem_constants.rs index 8fd746c85..0920d6a2f 100644 --- a/state-machines/mem-common/src/mem_constants.rs +++ b/state-machines/mem-common/src/mem_constants.rs @@ -32,8 +32,3 @@ pub const MAX_MEM_ADDR: u64 = 0xFFFF_FFFF; pub const SEGMENT_ADDR_MAX_RANGE: usize = (1 << 24) - 1; pub const SEGMENT_LARGE_ADDR_C_MAX_RANGE: usize = (1 << 16) - 1; - -pub const MEM_INC_C_BITS: usize = 18; -pub const MEM_INC_C_SIZE: usize = 1 << MEM_INC_C_BITS; -pub const MEM_INC_C_MAX_RANGE: usize = MEM_INC_C_SIZE - 1; -pub const MEM_INC_C_MASK: usize = MEM_INC_C_SIZE - 1; diff --git a/state-machines/mem-common/src/mem_counters.rs b/state-machines/mem-common/src/mem_counters.rs index 31cc51e3c..030928a06 100644 --- a/state-machines/mem-common/src/mem_counters.rs +++ b/state-machines/mem-common/src/mem_counters.rs @@ -2,18 +2,12 @@ use rayon::prelude::*; #[cfg(feature = "save_mem_bus_data")] use std::{env, io::Write, slice}; -use std::{ - collections::{HashMap, VecDeque}, - fs::File, - io::Read, -}; +use std::{collections::HashMap, fs::File, io::Read}; use zisk_common::ChunkId; use crate::{MemAlignCounters, MemHelpers}; use std::fmt; -use zisk_common::{ - BusDevice, BusId, MemBusData, MemCollectorInfo, Metrics, MEM_BUS_DATA_SIZE, MEM_BUS_ID, -}; +use zisk_common::{BusDevice, BusId, MemBusData, Metrics, MEM_BUS_DATA_SIZE, MEM_BUS_ID}; const ST_BITS_OFFSET: u32 = 30; const ST_INI: u8 = 0; @@ -309,28 +303,9 @@ impl MemCounters { self.mem_measure(data); } } -} - -impl Metrics for MemCounters { - #[inline(always)] - fn measure(&mut self, data: &[u64]) { - self.mem_measure(data); - } - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl BusDevice for MemCounters { #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(bus_id == &MEM_BUS_ID); #[cfg(feature = "save_mem_bus_data")] @@ -343,17 +318,22 @@ impl BusDevice for MemCounters { true } +} - fn bus_id(&self) -> Vec { - vec![MEM_BUS_ID] +impl Metrics for MemCounters { + #[inline(always)] + fn measure(&mut self, data: &[u64]) { + self.mem_measure(data); } - /// Provides a dynamic reference for downcasting purposes. - fn as_any(self: Box) -> Box { + fn as_any(&self) -> &dyn std::any::Any { self } +} - fn on_close(&mut self) { - self.close(); +impl BusDevice for MemCounters { + /// Provides a dynamic reference for downcasting purposes. + fn as_any(self: Box) -> Box { + self } } diff --git a/state-machines/mem-cpp/cpp/mem_align_counter.cpp b/state-machines/mem-cpp/cpp/mem_align_counter.cpp index 103b9b455..08923c01e 100644 --- a/state-machines/mem-cpp/cpp/mem_align_counter.cpp +++ b/state-machines/mem-cpp/cpp/mem_align_counter.cpp @@ -6,17 +6,6 @@ #include #include - -#define FLAGS_1_BYTE_READ 1 -#define FLAGS_2_BYTES_READ 2 -#define FLAGS_4_BYTES_READ 4 -#define FLAGS_8_BYTES_READ 8 -#define FLAGS_1_BYTE_CLEAR_WRITE (MEM_WRITE_FLAG + MEM_WRITE_BYTE_CLEAR_FLAG + 1) -#define FLAGS_1_BYTE_WRITE (MEM_WRITE_FLAG + 1) -#define FLAGS_2_BYTES_WRITE (MEM_WRITE_FLAG + 2) -#define FLAGS_4_BYTES_WRITE (MEM_WRITE_FLAG + 4) -#define FLAGS_8_BYTES_WRITE (MEM_WRITE_FLAG + 8) - MemAlignCounter::MemAlignCounter(std::shared_ptr context) :context(context) { total_counters.chunk_id = 0xFFFFFFFF; total_counters.full_5 = 0; @@ -59,13 +48,13 @@ void MemAlignCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData uint32_t write_byte = 0; for (uint32_t i = 0; i < chunk_size; i++) { - switch (chunk_data[i].flags & 0xFF) { + switch (chunk_data[i].flags & 0x3F) { // 1 byte read - case FLAGS_1_BYTE_READ: + case MOPS_READ_1: read_byte += 1; break; // 2 bytes read - case FLAGS_2_BYTES_READ: + case MOPS_READ_2: if ((chunk_data[i].addr & 0x07) > 6) { full_3 += 1; } else { @@ -73,7 +62,7 @@ void MemAlignCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData } break; // 4 bytes read - case FLAGS_4_BYTES_READ: + case MOPS_READ_4: if ((chunk_data[i].addr & 0x07) > 4) { full_3 += 1; } else { @@ -81,23 +70,22 @@ void MemAlignCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData } break; // 8 bytes read - case FLAGS_8_BYTES_READ: + case MOPS_READ_8: if ((chunk_data[i].addr & 0x07) > 0) { full_3 += 1; } // if chunk_data[i].addr & 0x07 == 0 ==> aligned read break; // 1 byte write (clear) - case FLAGS_1_BYTE_CLEAR_WRITE: + case MOPS_CWRITE_1: write_byte += 1; break; - // 1 byte write - case FLAGS_1_BYTE_WRITE: + case MOPS_WRITE_1: full_3 += 1; break; // 2 bytes write - case FLAGS_2_BYTES_WRITE: + case MOPS_WRITE_2: if ((chunk_data[i].addr & 0x07) > 6) { full_5 += 1; } else { @@ -105,7 +93,7 @@ void MemAlignCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData } break; // 4 bytes write - case FLAGS_4_BYTES_WRITE: + case MOPS_WRITE_4: if ((chunk_data[i].addr & 0x07) > 4) { full_5 += 1; } else { @@ -113,12 +101,48 @@ void MemAlignCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData } break; // 8 bytes write - case FLAGS_8_BYTES_WRITE: + case MOPS_WRITE_8: if ((chunk_data[i].addr & 0x07) > 0) { full_5 += 1; } // if chunk_data[i].addr & 0x07 == 0 ==> aligned write break; + case MOPS_BLOCK_READ + 0x00: + case MOPS_BLOCK_READ + 0x10: + case MOPS_BLOCK_READ + 0x20: + case MOPS_BLOCK_READ + 0x30: + if ((chunk_data[i].addr & 0x07) > 0) { + const uint32_t count = chunk_data[i].flags >> MOPS_BLOCK_COUNT_SBITS; + full_5 += count; + } + break; + case MOPS_BLOCK_WRITE + 0x00: + case MOPS_BLOCK_WRITE + 0x10: + case MOPS_BLOCK_WRITE + 0x20: + case MOPS_BLOCK_WRITE + 0x30: + if ((chunk_data[i].addr & 0x07) > 0) { + const uint32_t count = chunk_data[i].flags >> MOPS_BLOCK_COUNT_SBITS; + full_5 += count; + } + break; + + case MOPS_ALIGNED_READ + 0x00: + case MOPS_ALIGNED_READ + 0x10: + case MOPS_ALIGNED_READ + 0x20: + case MOPS_ALIGNED_READ + 0x30: + case MOPS_ALIGNED_WRITE + 0x00: + case MOPS_ALIGNED_WRITE + 0x10: + case MOPS_ALIGNED_WRITE + 0x20: + case MOPS_ALIGNED_WRITE + 0x30: + case MOPS_ALIGNED_BLOCK_READ + 0x00: + case MOPS_ALIGNED_BLOCK_READ + 0x10: + case MOPS_ALIGNED_BLOCK_READ + 0x20: + case MOPS_ALIGNED_BLOCK_READ + 0x30: + case MOPS_ALIGNED_BLOCK_WRITE + 0x00: + case MOPS_ALIGNED_BLOCK_WRITE + 0x10: + case MOPS_ALIGNED_BLOCK_WRITE + 0x20: + case MOPS_ALIGNED_BLOCK_WRITE + 0x30: + break; default: printf("MemAlignCounter: Unknown flags: 0x%X\n", chunk_data[i].flags); assert(false && "Unknown flags in MemAlignCounter"); diff --git a/state-machines/mem-cpp/cpp/mem_config.hpp b/state-machines/mem-cpp/cpp/mem_config.hpp index 9e60f471b..d198a850c 100644 --- a/state-machines/mem-cpp/cpp/mem_config.hpp +++ b/state-machines/mem-cpp/cpp/mem_config.hpp @@ -28,7 +28,7 @@ #define ROM_ROWS (1 << 21) #define INPUT_ROWS (1 << 21) #define MEM_ROWS (1 << 22) -#define MAX_CHUNKS 8192 // 2^13 * 2^18 = 2^31 +#define MAX_CHUNKS (1 << 18) // 2^36 / 2^18 = 2^18 // THREAD_BITS >= 1 #define THREAD_BITS 2 @@ -47,7 +47,7 @@ #define ADDR_SLOT_BITS 5 #define ADDR_SLOT_SIZE (1 << ADDR_SLOT_BITS) #define ADDR_SLOT_MASK (0xFFFFFFFF << ADDR_SLOT_BITS) -#define ADDR_SLOTS ((1024 * 1024 * 32) / MAX_THREADS) +#define ADDR_SLOTS ((1024 * 1024 * 64) / MAX_THREADS) #define ADDR_SLOTS_SIZE (ADDR_SLOT_SIZE * ADDR_SLOTS) #define TIME_US_BY_CHUNK 173 @@ -55,7 +55,41 @@ #define NO_CHUNK_ID 0xFFFFFFFF #define EMPTY_PAGE 0xFFFFFFFF -#define MEM_WRITE_FLAG 0x10 -#define MEM_WRITE_BYTE_CLEAR_FLAG 0x20 + +// SINGLE WRITE FLAGS +// bits +// bytes(4) 0-3 (values 1,2,4,8) +// write_flag (1) 4 +// clear_flag (1) 8 + +// ALIGNED WRITE BLOCKS FLAGS +// bits +// bytes(4) 0-3 (14 read block/15 write block) +// word_count(28) 4-31 2^28 * 2^3 = 2^31 bytes = 2GB MAX_MEMCPY_SIZE + + +#define MOPS_WRITE_FLAG 0x10 +#define MOPS_WRITE_BYTE_CLEAR_FLAG 0x20 + +#define MOPS_READ_8 0x08 +#define MOPS_READ_4 0x04 +#define MOPS_READ_2 0x02 +#define MOPS_READ_1 0x01 + +#define MOPS_WRITE_8 0x18 +#define MOPS_WRITE_4 0x14 +#define MOPS_WRITE_2 0x12 +#define MOPS_WRITE_1 0x11 + +#define MOPS_CWRITE_1 0x31 + +#define MOPS_BLOCK_READ 0x0A +#define MOPS_BLOCK_WRITE 0x0B +#define MOPS_ALIGNED_READ 0x0C +#define MOPS_ALIGNED_WRITE 0x0D +#define MOPS_ALIGNED_BLOCK_READ 0x0E +#define MOPS_ALIGNED_BLOCK_WRITE 0x0F + +#define MOPS_BLOCK_COUNT_SBITS 4 #endif diff --git a/state-machines/mem-cpp/cpp/mem_counter.cpp b/state-machines/mem-cpp/cpp/mem_counter.cpp index cc03b8254..c13d839ac 100644 --- a/state-machines/mem-cpp/cpp/mem_counter.cpp +++ b/state-machines/mem-cpp/cpp/mem_counter.cpp @@ -1,6 +1,9 @@ #include "mem_counter.hpp" +#include +#include #include #include +#include #define ST_INI 0 #define ST_READ 1 @@ -10,6 +13,8 @@ #define ST_READ_TO_WRITE ((ST_WRITE - ST_READ) << ST_BITS_OFFSET) #define ST_X_TO_INI_MASK (0xFFFFFFFF >> (32 - ST_BITS_OFFSET)) +#define ALIGN_MASK 0xFFFF'FFFF'FFFF'FFF8ULL + MemCounter::MemCounter(uint32_t id, std::shared_ptr context) :id(id), context(context), addr_mask(id * 8) { count = 0; @@ -100,32 +105,140 @@ void MemCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData *chun const uint8_t bytes = chunk_data->flags & 0x0F; const uint32_t addr = chunk_data->addr; switch (bytes) { - case 1: // byte - case 2: // half word - case 4: // word - case 8: // double word + // byte + case 1: + if ((addr & ADDR_MASK) != addr_mask) { + continue; + } + incr_counter(addr & ALIGN_MASK, chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + break; + + // half word + case 2: + if ((addr & ADDR_MASK) == addr_mask) { + incr_counter(addr & ALIGN_MASK, chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + else if (((addr + 1) & ADDR_MASK) == addr_mask) { + incr_counter((addr & ALIGN_MASK) + 8 , chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + break; + + // word + case 4: + if ((addr & ADDR_MASK) == addr_mask) { + incr_counter(addr & ALIGN_MASK, chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + else if (((addr + 3) & ADDR_MASK) == addr_mask) { + incr_counter((addr & ALIGN_MASK) + 8, chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + break; + + // double word + case 8: + if ((addr & 0x07) == 0) { + // aligned access + if ((addr & ADDR_MASK) != addr_mask) { + continue; + } + incr_counter(addr, chunk_id, true, chunk_data->flags & MOPS_WRITE_FLAG); + } else { + const uint32_t aligned_addr = addr & ALIGN_MASK; + + if ((aligned_addr & ADDR_MASK) == addr_mask) { + incr_counter(aligned_addr, chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + else if (((aligned_addr + 7) & ADDR_MASK) == addr_mask) { + incr_counter(aligned_addr + 8 , chunk_id, false, chunk_data->flags & MOPS_WRITE_FLAG); + } + } + break; + + case MOPS_ALIGNED_READ: { + assert((addr & 0x07) == 0); + if ((addr & ADDR_MASK) == addr_mask) { + incr_counter(addr , chunk_id, true, false); + } + break; + } + + case MOPS_ALIGNED_WRITE: { + assert((addr & 0x07) == 0); + if ((addr & ADDR_MASK) == addr_mask) { + incr_counter(addr , chunk_id, true, true); + } break; + } + + case MOPS_BLOCK_READ: + case MOPS_BLOCK_WRITE: { + bool write = bytes == MOPS_BLOCK_WRITE; + const uint32_t count = chunk_data->flags >> MOPS_BLOCK_COUNT_SBITS; + if ((addr & 0x07) == 0) { + uint32_t to_addr = addr + count * 8; + uint32_t c_addr = (addr & ~ADDR_MASK) + addr_mask; + if (c_addr < addr) { + c_addr += (MAX_THREADS * 8); + } + while (c_addr < to_addr) { + incr_counter(c_addr , chunk_id, true, write); + c_addr += (MAX_THREADS * 8); + } + } else { + // increase range, because if width = 8 and not aligned means + // each access is double, addr and addr + 8 + const uint32_t from_addr = (addr & ~0x07); + const uint32_t to_addr = from_addr + (count + 1) * 8; + uint32_t c_addr = (from_addr & ~ADDR_MASK) + addr_mask; + if (c_addr < from_addr) { + c_addr += (MAX_THREADS * 8); + } + while (c_addr < to_addr) { + incr_counter(c_addr , chunk_id, false, write); + c_addr += (MAX_THREADS * 8); + } + } + break; + } + + case MOPS_ALIGNED_BLOCK_READ: + case MOPS_ALIGNED_BLOCK_WRITE: { + assert((addr & 0x07) == 0); + bool write = bytes == MOPS_ALIGNED_BLOCK_WRITE; + uint32_t count = chunk_data->flags >> 4; + uint32_t to_addr = addr + count * 8; + uint32_t c_addr = (addr & ~ADDR_MASK) + addr_mask; + if (c_addr < addr) { + c_addr += (MAX_THREADS * 8); + } + while (c_addr < to_addr) { + incr_counter(c_addr , chunk_id, true, write); + c_addr += (MAX_THREADS * 8); + } + break; + } + + default: std::ostringstream msg; msg << "ERROR: MemCounter execute_chunk: invalid bytes size " << bytes << " at chunk_id " << chunk_id << " addr 0x" << std::hex << addr; throw std::runtime_error(msg.str()); } - if (bytes == 8 && (addr & 0x07) == 0) { - // aligned access - if ((addr & ADDR_MASK) != addr_mask) { - continue; - } - incr_counter(addr, chunk_id, true, chunk_data->flags & MEM_WRITE_FLAG); - } else { - const uint32_t aligned_addr = addr & 0xFFFFFFF8; + // if (bytes == 8 && (addr & 0x07) == 0) { + // // aligned access + // if ((addr & ADDR_MASK) != addr_mask) { + // continue; + // } + // incr_counter(addr, chunk_id, true, chunk_data->flags & MEM_WRITE_FLAG); + // } else { + // const uint32_t aligned_addr = addr & 0xFFFFFFF8; - if ((aligned_addr & ADDR_MASK) == addr_mask) { - incr_counter(aligned_addr, chunk_id, false, chunk_data->flags & MEM_WRITE_FLAG); - } - else if ((bytes + (addr & 0x07)) > 8 && ((aligned_addr + 8) & ADDR_MASK) == addr_mask) { - incr_counter(aligned_addr + 8 , chunk_id, false, chunk_data->flags & MEM_WRITE_FLAG); - } - } + // if ((aligned_addr & ADDR_MASK) == addr_mask) { + // incr_counter(aligned_addr, chunk_id, false, chunk_data->flags & MEM_WRITE_FLAG); + // } + // else if ((bytes + (addr & 0x07)) > 8 && ((aligned_addr + 8) & ADDR_MASK) == addr_mask) { + // incr_counter(aligned_addr + 8 , chunk_id, false, chunk_data->flags & MEM_WRITE_FLAG); + // } + // } } #ifdef MEM_STATS_ACTIVE diff --git a/state-machines/mem-cpp/cpp/tools.hpp b/state-machines/mem-cpp/cpp/tools.hpp index be1313c72..af166d9ac 100644 --- a/state-machines/mem-cpp/cpp/tools.hpp +++ b/state-machines/mem-cpp/cpp/tools.hpp @@ -60,7 +60,7 @@ inline uint32_t count_operations(MemCountersBusData *chunk_data, int count) { for (int i = 0; i < count; ++i) { const uint32_t bytes = chunk_data[i].flags & 0x0F; const uint32_t offset = chunk_data[i].addr & 0x07; - const bool wr = (chunk_data[i].flags & MEM_WRITE_FLAG) != 0; + const bool wr = (chunk_data[i].flags & MOPS_WRITE_FLAG) != 0; if (offset == 0 && bytes == 8) { cops = 1; } else if (offset + bytes > 8) { diff --git a/state-machines/mem/pil/dual_byte.pil b/state-machines/mem/pil/dual_byte.pil deleted file mode 100644 index 693a651b5..000000000 --- a/state-machines/mem/pil/dual_byte.pil +++ /dev/null @@ -1,12 +0,0 @@ -require "std_lookup.pil" -require "opids.pil" - -airtemplate DualByte(const int N = 2**16) { - - col fixed BYTE_A = [0:256..255:256]...; - col fixed BYTE_B = [0..255]...; - - col witness multiplicity; - - lookup_proves(DUAL_BYTE_TABLE_ID, mul: multiplicity, expressions: [BYTE_A, BYTE_B]); -} \ No newline at end of file diff --git a/state-machines/mem/pil/mem.pil b/state-machines/mem/pil/mem.pil index 429d10a7e..d7baa9076 100644 --- a/state-machines/mem/pil/mem.pil +++ b/state-machines/mem/pil/mem.pil @@ -107,7 +107,7 @@ airtemplate Mem(const int N = 2**21, const int id = MEMORY_ID, const int RC = 2, is_first_segment * segment_id === 0; col witness bits(29) addr; - col witness bits(40) step; + col witness bits(MEM_STEP_BITS) step; col witness bits(1) sel; col witness bits(1) addr_changes; @@ -119,7 +119,7 @@ airtemplate Mem(const int N = 2**21, const int id = MEMORY_ID, const int RC = 2, // Two columns, one for step (timestap) of second operation, and other // to enable dual operation in the row. - col witness bits(40) air.step_dual; + col witness bits(MEM_STEP_BITS) air.step_dual; col witness bits(1) air.sel_dual; sel_dual * (1 - sel_dual) === 0; @@ -370,17 +370,19 @@ airtemplate Mem(const int N = 2**21, const int id = MEMORY_ID, const int RC = 2, } const expr delta_step = step - previous_step + (1 - wr); - col witness bits(18) air.increment[2]; - increment[0] + 2**18 * increment[1] + 1 === addr_changes * (delta_addr - delta_step) + delta_step; + col witness bits(22) air.l_increment; + col witness bits(16) air.h_increment; + l_increment + 2**22 * h_increment + 1 === addr_changes * (delta_addr - delta_step) + delta_step; is_first_segment * SEGMENT_L1 * (1 - addr_changes) === 0; - // addr_change == 1 => [0,2^18-1] => 18 + 18 = 36 bits + 3 => 39 bits => 512 GB - // addr_change == 0 => [0,2^18-1] => 18 + 18 = 36 bits => STEP upto 2^34 => 16 GB steps - // 36 bits x 2^22 = 2^58 + 2^32 address = 2^59 secure. + // addr_change == 1 => 22 + 16 = 38 bits + 3 => 41 bits => 2 TB (limit address 32 bits) + // addr_change == 0 => 22 + 16 = 38 bits => 4 slots => 2^36 => 32 GB steps (16K main instances) + // 38 bits x 2^22 = 2^60 + 2^32 address = 2^61 secure. + // using control in the middle of instance, reduce to 2^60 - range_check(expression: increment[0], min: 0, max: 2**18 - 1); - range_check(expression: increment[1], min: 0, max: 2**18 - 1); + range_check(expression: l_increment, min: 0, max: 2**22 - 1); + range_check(expression: h_increment, min: 0, max: 2**16 - 1); // to avoid intermediate column col witness bits(1) air.read_same_addr; @@ -477,6 +479,10 @@ function precompiled_mem_load(int id = MEMORY_ID, expr addr, expr main_step, exp mem_assumes(id, MEMORY_LOAD_OP, addr, main_step_to_precompiled_mem_step(main_step), 8, value, sel, name); } +function precompiled_mem_load_padding(int id = MEMORY_ID, expr padding = 0, int name = PIOP_NAME_DEFAULT) { + mem_proves(id, MEMORY_LOAD_OP, 0, main_step_to_precompiled_mem_step(0), [0, 0], padding, name); +} + function precompiled_mem_store(int id = MEMORY_ID, expr addr, expr main_step, expr value[], expr sel = 1, int name = PIOP_NAME_DEFAULT ) { mem_assumes(id, MEMORY_STORE_OP, addr, main_step_to_precompiled_mem_step(main_step, 1), 8, value, sel, name); } @@ -487,6 +493,12 @@ function reg_pre_load(int id = MEMORY_ID, expr addr, expr prev_mem_step, expr va mem_proves(id, MEMORY_REG_OP, addr, prev_mem_step, value, sel, name: name); } +function precompiled_reg_load(int id = MEMORY_ID, expr reg, expr prev_mem_step, expr main_step, expr value[], expr sel = 1, int name = PIOP_NAME_DEFAULT) { + mem_proves(id, MEMORY_REG_OP, reg, prev_mem_step, value, sel, name: name); + range_check(expression: main_step_to_precompiled_mem_step(main_step) - prev_mem_step, min: 0, max: MAX_RANGE); + mem_assumes(id, MEMORY_REG_OP, reg, main_step_to_precompiled_mem_step(main_step), 8, value, sel, name: name); +} + function reg_pre_store(int id = MEMORY_ID, expr addr, expr prev_mem_step, expr value[], expr sel = 1, int name = PIOP_NAME_DEFAULT) { mem_proves(id, MEMORY_REG_OP, addr, prev_mem_step, value, sel, name); } diff --git a/state-machines/mem/pil/mem_align_byte.pil b/state-machines/mem/pil/mem_align_byte.pil index 4e2964461..9915376c1 100644 --- a/state-machines/mem/pil/mem_align_byte.pil +++ b/state-machines/mem/pil/mem_align_byte.pil @@ -1,8 +1,7 @@ -require "std_permutation.pil" -require "std_lookup.pil" -require "std_range_check.pil" -require "opids.pil" -require "dual_byte.pil" +require "std_permutation.pil"; +require "std_lookup.pil"; +require "std_range_check.pil"; +require "opids.pil"; // Specific low cost machine for specific byte unaligned memory access. @@ -102,7 +101,7 @@ airtemplate MemAlignByte(const int N = 2**10, const int read = 1, const int writ } range_check(min: 0, max: 0xFFFF, expression: value_16b); - lookup_assumes(DUAL_BYTE_TABLE_ID, [byte_value, value_8b]); + range_dual_byte(byte_value, value_8b); airval padding_size; direct_update_proves(MEMORY_ID, [MEMORY_LOAD_OP, 0, 0, 8, 0, 0], sel: padding_size); diff --git a/state-machines/mem/src/mem.rs b/state-machines/mem/src/mem.rs index 510b02b35..0cc8d0102 100644 --- a/state-machines/mem/src/mem.rs +++ b/state-machines/mem/src/mem.rs @@ -9,7 +9,7 @@ use fields::PrimeField64; use mem_common::MemCounters; use pil_std_lib::Std; use proofman_common::ProofCtx; -use zisk_common::{BusDeviceMetrics, ComponentBuilder, Instance, InstanceCtx, Plan, Planner}; +use zisk_common::{ComponentBuilder, Instance, InstanceCtx, Plan, Planner}; use zisk_pil::{ InputDataTrace, MemAlignByteTrace, MemAlignReadByteTrace, MemAlignTrace, MemAlignWriteByteTrace, MemTrace, RomDataTrace, ZiskProofValues, @@ -46,10 +46,6 @@ impl Mem { } impl ComponentBuilder for Mem { - fn build_counter(&self) -> Option> { - Some(Box::new(MemCounters::new())) - } - fn build_planner(&self) -> Box { Box::new(MemPlanner::new()) } diff --git a/state-machines/mem/src/mem_align_byte_sm.rs b/state-machines/mem/src/mem_align_byte_sm.rs index 542b0e915..3cb653675 100644 --- a/state-machines/mem/src/mem_align_byte_sm.rs +++ b/state-machines/mem/src/mem_align_byte_sm.rs @@ -9,7 +9,10 @@ use rayon::prelude::*; use crate::MemAlignInput; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; -use zisk_pil::{MemAlignByteAirValues, MemAlignReadByteAirValues, MemAlignWriteByteAirValues}; +use zisk_pil::{ + MemAlignByteAirValues, MemAlignReadByteAirValues, MemAlignWriteByteAirValues, + DUAL_RANGE_BYTE_ID, +}; #[cfg(not(feature = "packed"))] use zisk_pil::{ @@ -356,7 +359,6 @@ impl MemAlignByteRow> const OFFSET_MASK: u32 = 0x07; const OFFSET_BITS: u32 = 3; -const DUAL_BYTE_TABLE_ID: usize = 88; pub struct MemAlignByteSM { /// PIL2 standard library @@ -378,7 +380,7 @@ impl MemAlignByteSM { Arc::new(Self { std: std.clone(), table_dual_byte_id: std - .get_virtual_table_id(DUAL_BYTE_TABLE_ID) + .get_virtual_table_id(DUAL_RANGE_BYTE_ID) .expect("Failed to get dual byte table ID"), table_16b_id: std.get_range_id(0, 0xFFFF, None).expect("Failed to get 16b table ID"), table_8b_id: std.get_range_id(0, 0xFF, None).expect("Failed to get 8b table ID"), diff --git a/state-machines/mem/src/mem_align_collector.rs b/state-machines/mem/src/mem_align_collector.rs index 7e8097629..018902cdb 100644 --- a/state-machines/mem/src/mem_align_collector.rs +++ b/state-machines/mem/src/mem_align_collector.rs @@ -1,10 +1,7 @@ use crate::MemAlignInput; use mem_common::{MemAlignCheckPoint, MemHelpers}; -use std::collections::VecDeque; -use zisk_common::{ - BusDevice, BusId, ChunkId, CollectCounter, MemBusData, MemCollectorInfo, MEM_BUS_ID, -}; +use zisk_common::{BusDevice, BusId, ChunkId, CollectCounter, MemBusData, MEM_BUS_ID}; pub struct MemAlignCollector { /// Collected inputs @@ -65,17 +62,9 @@ impl MemAlignCollector { + self.read_byte.count() + self.write_byte.count() } -} -impl BusDevice for MemAlignCollector { #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == MEM_BUS_ID); let bytes = MemBusData::get_bytes(data); @@ -129,11 +118,9 @@ impl BusDevice for MemAlignCollector { }; true } +} - fn bus_id(&self) -> Vec { - vec![MEM_BUS_ID] - } - +impl BusDevice for MemAlignCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/mem/src/mem_counters_cursor.rs b/state-machines/mem/src/mem_counters_cursor.rs index fe04e756d..403291f55 100644 --- a/state-machines/mem/src/mem_counters_cursor.rs +++ b/state-machines/mem/src/mem_counters_cursor.rs @@ -81,7 +81,7 @@ impl MemCountersCursor { return sorted_boxes.first().cloned().unwrap_or_default(); } let total_size: usize = sorted_boxes.iter().map(|b| b.len()).sum(); - let target_size: usize = arity * (total_size / sorted_boxes.len()); + let target_size: usize = std::cmp::max(arity * (total_size / sorted_boxes.len()), 1); let mut groups: Vec<&[Vec]> = Vec::new(); let mut group_weight = 0; diff --git a/state-machines/mem/src/mem_module_collector.rs b/state-machines/mem/src/mem_module_collector.rs index c5dc3392e..1575a0e1a 100644 --- a/state-machines/mem/src/mem_module_collector.rs +++ b/state-machines/mem/src/mem_module_collector.rs @@ -1,8 +1,6 @@ -use std::collections::VecDeque; - use crate::{MemInput, MemPreviousSegment}; use mem_common::{MemHelpers, MemModuleCheckPoint, MEM_BYTES, MEM_BYTES_BITS}; -use zisk_common::{BusDevice, BusId, MemBusData, MemCollectorInfo, SegmentId, MEM_BUS_ID}; +use zisk_common::{BusDevice, BusId, MemBusData, SegmentId, MEM_BUS_ID}; #[derive(Debug, PartialEq, Eq)] enum InputAction { @@ -469,20 +467,22 @@ impl MemModuleCollector { } } - pub fn get_mem_collector_info(&self) -> MemCollectorInfo { - MemCollectorInfo { from_addr: self.filter_min_addr, to_addr: self.filter_max_addr } + pub fn skip_addr(&self, addr: u32) -> bool { + if addr > self.filter_max_addr || addr < self.filter_min_addr { + return true; + } + false + } + + pub fn skip_addr_range(&self, addr_from: u32, addr_to: u32) -> bool { + if addr_from > self.filter_max_addr || addr_to < self.filter_min_addr { + return true; + } + false } -} -impl BusDevice for MemModuleCollector { #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == MEM_BUS_ID); let addr = MemBusData::get_addr(data); @@ -492,11 +492,9 @@ impl BusDevice for MemModuleCollector { } true } +} - fn bus_id(&self) -> Vec { - vec![MEM_BUS_ID] - } - +impl BusDevice for MemModuleCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/state-machines/mem/src/mem_sm.rs b/state-machines/mem/src/mem_sm.rs index 8d001fd75..fed056328 100644 --- a/state-machines/mem/src/mem_sm.rs +++ b/state-machines/mem/src/mem_sm.rs @@ -12,21 +12,15 @@ type MemTraceType = MemTracePacked; #[cfg(not(feature = "packed"))] type MemTraceType = MemTrace; #[cfg(feature = "debug_mem")] -use { - num_bigint::ToBigInt, - std::{ - env, - fs::File, - io::{BufWriter, Write}, - }, +use std::{ + env, + fs::File, + io::{BufWriter, Write}, }; use crate::{MemInput, MemModule}; use fields::PrimeField64; -use mem_common::{ - MemHelpers, MEM_INC_C_BITS, MEM_INC_C_MASK, MEM_INC_C_MAX_RANGE, MEM_INC_C_SIZE, - RAM_W_ADDR_END, RAM_W_ADDR_INIT, -}; +use mem_common::{MemHelpers, RAM_W_ADDR_END, RAM_W_ADDR_INIT}; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use zisk_core::{RAM_ADDR, RAM_SIZE}; @@ -38,10 +32,8 @@ pub struct MemSM { /// PIL2 standard library std: Arc>, - range_id: usize, - + range_22bits_id: usize, dual_range_id: usize, - range_16bits_id: usize, } #[derive(Debug, Default)] @@ -54,14 +46,14 @@ pub struct MemPreviousSegment { #[allow(unused, unused_variables)] impl MemSM { pub fn new(std: Arc>) -> Arc { - let range_id = - std.get_range_id(0, MEM_INC_C_MAX_RANGE as i64, None).expect("Failed to get range ID"); + let range_22bits_id = + std.get_range_id(0, (1 << 22) - 1, None).expect("Failed to get 22 bits range ID"); let dual_range_id = std.get_range_id(0, DUAL_RANGE_MAX as i64, None).expect("Failed to get dual range ID"); let range_16bits_id = - std.get_range_id(0, 0xFFFF, None).expect("Failed to get 16 bits range ID"); + std.get_range_id(0, (1 << 16) - 1, None).expect("Failed to get 16 bits range ID"); - Arc::new(Self { range_id, dual_range_id, range_16bits_id, std: std.clone() }) + Arc::new(Self { range_22bits_id, dual_range_id, range_16bits_id, std: std.clone() }) } pub fn get_to_addr() -> u32 { @@ -72,17 +64,22 @@ impl MemSM { println!("[MemDebug] writing information {} .....", file_name); let file = File::create(file_name).unwrap(); let mut writer = BufWriter::new(file); - let num_rows = MemTrace::NUM_ROWS; + let num_rows = MemTrace::::NUM_ROWS; for i in 0..num_rows { - let addr = trace[i].addr.as_canonical_biguint().to_bigint().unwrap() * 8; - let step = trace[i].step.as_canonical_biguint().to_bigint().unwrap(); - writeln!( - writer, - "{:#010X} {} {} {:?}", - addr, trace[i].step, trace[i].wr, trace[i].value - ) - .unwrap(); + let addr = trace[i].addr.as_canonical_u64() * 8; + let step = trace[i].step.as_canonical_u64(); + let op = if trace[i].wr.is_zero() { 'R' } else { 'W' }; + let values = + [trace[i].value[0].as_canonical_u64(), trace[i].value[1].as_canonical_u64()]; + let value = values[0] | (values[1] << 32); + writeln!(writer, "{i:<8} {addr:#010X} {step} {op} {values:?} 0x{value:016X}").unwrap(); + let dual = !trace[i].sel_dual.is_zero(); + if dual { + let step = trace[i].step_dual.as_canonical_u64(); + writeln!(writer, "{i:<8} {addr:#010X} {step} R {values:?} 0x{value:016X} DUAL") + .unwrap(); + } } println!("[MemDebug] done"); } @@ -112,7 +109,8 @@ impl MemModule for MemSM { ) -> ProofmanResult> { let mut trace = MemTraceType::::new_from_vec(trace_buffer)?; - let mut range_check_data: Vec = vec![0; MEM_INC_C_SIZE]; + let mut range_22bits: Vec = vec![0; 1 << 22]; + let mut range_16bits: Vec = vec![0; 1 << 16]; // 2^20 * 2 = 2^21 = 2MB let mut dual_partial_range: Vec = vec![0; DUAL_PARTIAL_RANGE_MAX]; @@ -131,13 +129,6 @@ impl MemModule for MemSM { for index in 0..mem_op_count { let mem_op = &mem_ops[index]; step = mem_op.step; - // if step >= 28184622 && step <= 28184624 { - // println!( - // "@@@@@@@@@@ 0x{:08X} {step} 8 OP:{}", - // mem_op.addr * 8, - // if mem_op.is_write { 2 } else { 1 } - // ); - // } let addr_changes = last_addr != mem_op.addr; if dual_candidate { @@ -222,10 +213,10 @@ impl MemModule for MemSM { } else { trace[i].set_read_same_addr(true); } - let lsb_increment = increment & MEM_INC_C_MASK; - let msb_increment = increment >> MEM_INC_C_BITS; - trace[i].set_increment(0, lsb_increment as u32); - trace[i].set_increment(1, msb_increment as u32); + let l_increment = increment & ((1 << 22) - 1); + let h_increment = increment >> 22; + trace[i].set_l_increment(l_increment as u32); + trace[i].set_h_increment(h_increment as u16); trace[i].set_wr(mem_op.is_write); #[cfg(feature = "debug_mem")] @@ -234,8 +225,8 @@ impl MemModule for MemSM { increment, i, addr_changes as u8, mem_op.addr, last_addr, mem_op.step, last_step); } - range_check_data[lsb_increment] += 1; - range_check_data[msb_increment] += 1; + range_22bits[l_increment] += 1; + range_16bits[h_increment] += 1; last_addr = mem_op.addr; last_value = mem_op.value; @@ -273,8 +264,8 @@ impl MemModule for MemSM { trace[i].set_value(1, high_value); trace[i].set_addr_changes(false); - trace[i].set_increment(0, 0); - trace[i].set_increment(1, 0); + trace[i].set_h_increment(0); + trace[i].set_l_increment(0); trace[i].set_read_same_addr(true); trace[i].set_sel_dual(false); trace[i].set_step_dual(0); @@ -282,15 +273,14 @@ impl MemModule for MemSM { if padding_size > 0 { // Store the padding range checks - range_check_data[0] += (2 * padding_size) as u32; + range_16bits[0] += padding_size as u32; + range_22bits[0] += padding_size as u32; } // no add extra +1 because index = value - 1 // RAM_W_ADDR_END - last_addr + 1 - 1 = RAM_W_ADDR_END - last_addr let distance_end = RAM_W_ADDR_END - last_addr; - self.std.range_checks(self.range_id, range_check_data); - // Add one in range_check_data_max because it's used by intermediate reads, and reads // add one to distance to allow same step on read operations. @@ -318,10 +308,13 @@ impl MemModule for MemSM { air_values.distance_end[0] = F::from_u16(distance_end[0]); air_values.distance_end[1] = F::from_u16(distance_end[1]); - self.std.range_check(self.range_16bits_id, distance_base[0] as i64, 1); - self.std.range_check(self.range_16bits_id, distance_base[1] as i64, 1); - self.std.range_check(self.range_16bits_id, distance_end[0] as i64, 1); - self.std.range_check(self.range_16bits_id, distance_end[1] as i64, 1); + range_16bits[distance_base[0] as usize] += 1; + range_16bits[distance_base[1] as usize] += 1; + range_16bits[distance_end[0] as usize] += 1; + range_16bits[distance_end[1] as usize] += 1; + + self.std.range_checks(self.range_22bits_id, range_22bits); + self.std.range_checks(self.range_16bits_id, range_16bits); for (value, count) in dual_partial_range.iter().enumerate() { if *count == 0 { diff --git a/state-machines/mem/src/mem_test.rs b/state-machines/mem/src/mem_test.rs index 6cf625d6b..97a803c14 100644 --- a/state-machines/mem/src/mem_test.rs +++ b/state-machines/mem/src/mem_test.rs @@ -1,9 +1,9 @@ #![cfg(test)] -use std::{collections::VecDeque, sync::Arc}; +use std::sync::Arc; use crate::{MemModulePlanner, MemModulePlannerConfig, MemPlanCalculator}; use mem_common::{MemCounters, MEMORY_LOAD_OP, MEMORY_STORE_OP}; -use zisk_common::{BusDevice, ChunkId, Plan, MEM_BUS_ID}; +use zisk_common::{ChunkId, Plan, MEM_BUS_ID}; fn generate_test_plans( from_addr: u32, @@ -44,8 +44,6 @@ fn add_test_aligned_mem_reads( counter.process_data( &MEM_BUS_ID, &[MEMORY_LOAD_OP as u64, addr as u64, step + i * step_delta, 8, value], - &mut VecDeque::new(), - None, ); } } @@ -72,12 +70,7 @@ fn add_mem_data( let mut step = step; let op = if is_write { MEMORY_STORE_OP } else { MEMORY_LOAD_OP } as u64; for i in 0..count { - counter.process_data( - &MEM_BUS_ID, - &[op, addr, step, width, value], - &mut VecDeque::new(), - None, - ); + counter.process_data(&MEM_BUS_ID, &[op, addr, step, width, value]); if config.step_cycle > 0 { if i > 0 && (config.step_cycle % i) == 0 { step += config.step_delta; @@ -104,21 +97,11 @@ fn add_mem_data( // } fn add_mem_read64(counter: &mut MemCounters, addr: u32, step: u64, value: u64) { - counter.process_data( - &MEM_BUS_ID, - &[MEMORY_LOAD_OP as u64, addr as u64, step, 8, value], - &mut VecDeque::new(), - None, - ); + counter.process_data(&MEM_BUS_ID, &[MEMORY_LOAD_OP as u64, addr as u64, step, 8, value]); } fn add_mem_write64(counter: &mut MemCounters, addr: u32, step: u64, value: u64) { - counter.process_data( - &MEM_BUS_ID, - &[MEMORY_STORE_OP as u64, addr as u64, step, 8, value], - &mut VecDeque::new(), - None, - ); + counter.process_data(&MEM_BUS_ID, &[MEMORY_STORE_OP as u64, addr as u64, step, 8, value]); } #[test] @@ -170,38 +153,5 @@ fn test_counters() { assert_eq!(format!("{counter:?}"), "[MEM_0,#:10 => 0x80000000:2 0x80000008:2 0x80000010:2 0x80000018:2 0x80000020:2 0x80000028:2 0x80000030:2 0x80000038:2 0x80000040:2 0x80000048:2][MEM_1,#:10 => 0x90000000:1 0x90000008:1 0x90000010:1 0x90000018:1 0x90000020:1 0x90000028:1 0x90000030:1 0x90000038:1 0x90000040:1 0x90000048:1][MEM_2,#:4 => 0xA0000000:7 0xA0000008:2 0xA0000010:1 0xA0000018:1]"); } -/* -#[test] -fn test_mem() { - let mem_sm = MemSM::new(); - let std_sm = - - let mem_bus_device = >::build_counter(&mem_sm); - - let mut data_bus = DataBus::::new(); - data_bus.connect_device( - vec![OPERATION_BUS_ID], - Box::new(BusDeviceMetricsWrapper::new(arith_bus_device, false)), - ); - - let data = vec![ - (OPERATION_BUS_ID, OperationBusData::from_values(Mul as u8, Arith as u64, 1, 2).into()), - (OPERATION_BUS_ID, OperationBusData::from_values(Div as u8, Arith as u64, 1, 2).into()), - (OPERATION_BUS_ID, OperationBusData::from_values(Add as u8, Binary as u64, 1, 2).into()), - (OPERATION_BUS_ID, OperationBusData::from_values(Sub as u8, Binary as u64, 1, 2).into()), - ]; - - DataBusPlayer::play(&mut data_bus, data); - - let arith_counter = data_bus.devices.remove(0).inner; - - let arith_planner = - >::build_planner(&arith_sm); - - let plan = arith_planner.plan(vec![(0, arith_counter)]); - - println!("Plan: {:?}", plan); -} -*/ #[test] fn full() {} diff --git a/state-machines/mem/src/rom_data_sm.rs b/state-machines/mem/src/rom_data_sm.rs index 315d120f8..6233588d7 100644 --- a/state-machines/mem/src/rom_data_sm.rs +++ b/state-machines/mem/src/rom_data_sm.rs @@ -65,7 +65,7 @@ impl RomDataSM { pub fn save_to_file(trace: &RomDataTrace, file_name: &str) { let file = File::create(file_name).unwrap(); let mut writer = BufWriter::new(file); - let num_rows = RomDataTrace::NUM_ROWS; + let num_rows = RomDataTrace::::NUM_ROWS; for i in 0..num_rows { let addr = trace[i].get_addr() * 8; diff --git a/state-machines/rom/src/rom.rs b/state-machines/rom/src/rom.rs index 8858f3114..20ba0a190 100644 --- a/state-machines/rom/src/rom.rs +++ b/state-machines/rom/src/rom.rs @@ -11,7 +11,7 @@ use std::{ path::PathBuf, sync::{ - atomic::{AtomicBool, AtomicU32}, + atomic::{AtomicBool, AtomicU64}, Arc, Mutex, }, thread::JoinHandle, @@ -23,8 +23,7 @@ use fields::PrimeField64; use itertools::Itertools; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use zisk_common::{ - create_atomic_vec, BusDeviceMetrics, ComponentBuilder, CounterStats, Instance, InstanceCtx, - Planner, + create_atomic_vec, ComponentBuilder, CounterStats, Instance, InstanceCtx, Planner, }; use zisk_core::{ zisk_ops::ZiskOp, Riscv2zisk, ZiskRom, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, ROM_EXIT, SRC_IMM, @@ -37,10 +36,10 @@ pub struct RomSM { zisk_rom: Arc, /// Shared biod instruction counter for monitoring ROM operations. - bios_inst_count: Arc>, + bios_inst_count: Arc>, /// Shared program instruction counter for monitoring ROM operations. - prog_inst_count: Arc>, + prog_inst_count: Arc>, asm_runner_handler: Mutex>>, } @@ -111,14 +110,12 @@ impl RomSM { true => { multiplicity = counter_stats.bios_inst_count [((inst.paddr - ROM_ENTRY) as usize) >> 2] - .swap(0, std::sync::atomic::Ordering::Relaxed) - as u64; + .swap(0, std::sync::atomic::Ordering::Relaxed); } false => { multiplicity = counter_stats.bios_inst_count [((inst.paddr - ROM_ENTRY) as usize) >> 2] - .load(std::sync::atomic::Ordering::Relaxed) - as u64; + .load(std::sync::atomic::Ordering::Relaxed); } } @@ -135,13 +132,11 @@ impl RomSM { multiplicity = counter_stats.prog_inst_count [(inst.paddr - ROM_ADDR) as usize] .swap(0, std::sync::atomic::Ordering::Relaxed) - as u64 } false => { multiplicity = counter_stats.prog_inst_count [(inst.paddr - ROM_ADDR) as usize] .load(std::sync::atomic::Ordering::Relaxed) - as u64 } } if multiplicity == 0 { @@ -311,14 +306,6 @@ impl RomSM { } impl ComponentBuilder for RomSM { - /// Builds and returns a new counter for monitoring ROM operations. - /// - /// # Returns - /// A boxed implementation of `RomCounter`. - fn build_counter(&self) -> Option> { - None - } - /// Builds a planner for ROM-related instances. /// /// # Returns diff --git a/state-machines/rom/src/rom_counter.rs b/state-machines/rom/src/rom_counter.rs index 8c5a033ff..1f47d2a9b 100644 --- a/state-machines/rom/src/rom_counter.rs +++ b/state-machines/rom/src/rom_counter.rs @@ -4,7 +4,7 @@ use std::{ any::Any, - sync::{atomic::AtomicU32, Arc}, + sync::{atomic::AtomicU64, Arc}, }; use zisk_common::{CounterStats, Metrics, RomBusData, RomData}; @@ -24,7 +24,7 @@ impl RomCounter { /// /// # Returns /// A new `RomCounter` instance. - pub fn new(bios_inst_count: Arc>, prog_inst_count: Arc>) -> Self { + pub fn new(bios_inst_count: Arc>, prog_inst_count: Arc>) -> Self { let counter_stats = CounterStats::new(bios_inst_count, prog_inst_count); Self { counter_stats } } diff --git a/state-machines/rom/src/rom_instance.rs b/state-machines/rom/src/rom_instance.rs index 8a3489d71..66c9320a0 100644 --- a/state-machines/rom/src/rom_instance.rs +++ b/state-machines/rom/src/rom_instance.rs @@ -3,9 +3,8 @@ //! It is responsible for computing witnesses for ROM-related execution plans, use std::{ - collections::VecDeque, sync::{ - atomic::{AtomicBool, AtomicU32}, + atomic::{AtomicBool, AtomicU64}, Arc, }, thread::JoinHandle, @@ -18,7 +17,7 @@ use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::sync::Mutex; use zisk_common::{ create_atomic_vec, BusDevice, BusId, CheckPoint, ChunkId, CounterStats, Instance, InstanceCtx, - InstanceType, MemCollectorInfo, Metrics, PayloadType, ROM_BUS_ID, + InstanceType, Metrics, PayloadType, ROM_BUS_ID, }; use zisk_core::ZiskRom; @@ -35,10 +34,10 @@ pub struct RomInstance { ictx: InstanceCtx, /// Shared biod instruction counter for monitoring ROM operations. - bios_inst_count: Mutex>>, + bios_inst_count: Mutex>>, /// Shared program instruction counter for monitoring ROM operations. - prog_inst_count: Mutex>>, + prog_inst_count: Mutex>>, /// Execution statistics counter for ROM instructions. counter_stats: Mutex>, @@ -64,8 +63,8 @@ impl RomInstance { pub fn new( zisk_rom: Arc, ictx: InstanceCtx, - bios_inst_count: Arc>, - prog_inst_count: Arc>, + bios_inst_count: Arc>, + prog_inst_count: Arc>, handle_rh: Option>, ) -> Self { Self { @@ -240,15 +239,13 @@ impl RomCollector { /// A new `RomCounter` instance. pub fn new( computed: bool, - bios_inst_count: Arc>, - prog_inst_count: Arc>, + bios_inst_count: Arc>, + prog_inst_count: Arc>, ) -> Self { let rom_counter = RomCounter::new(bios_inst_count, prog_inst_count); Self { already_computed: computed, rom_counter } } -} -impl BusDevice for RomCollector { /// Processes data received on the bus, updating ROM metrics. /// /// # Arguments @@ -260,13 +257,7 @@ impl BusDevice for RomCollector { /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] - fn process_data( - &mut self, - bus_id: &BusId, - data: &[u64], - _pending: &mut VecDeque<(BusId, Vec)>, - _mem_collector_info: Option<&[MemCollectorInfo]>, - ) -> bool { + pub fn process_data(&mut self, bus_id: &BusId, data: &[u64]) -> bool { debug_assert!(*bus_id == ROM_BUS_ID); if !self.already_computed { @@ -275,15 +266,9 @@ impl BusDevice for RomCollector { true } +} - /// Returns the bus IDs associated with this counter. - /// - /// # Returns - /// A vector containing the connected bus ID. - fn bus_id(&self) -> Vec { - vec![ROM_BUS_ID] - } - +impl BusDevice for RomCollector { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { self diff --git a/tools/test-env/build_zisk.sh b/tools/test-env/build_zisk.sh index 954b9d7b3..3d85549b5 100755 --- a/tools/test-env/build_zisk.sh +++ b/tools/test-env/build_zisk.sh @@ -162,7 +162,6 @@ main() { LIB_EXT="dylib" fi - ensure cp target/${TARGET}/release/libzisk_witness.${LIB_EXT} "${ZISK_BIN_DIR}" || return 1 ensure cp ziskup/ziskup "${ZISK_BIN_DIR}" || return 1 ensure cp target/${TARGET}/release/libziskclib.a "${ZISK_BIN_DIR}" || return 1 diff --git a/tools/verify_all.sh b/tools/verify_all.sh index 6e3f5f97d..b7fa6019d 100755 --- a/tools/verify_all.sh +++ b/tools/verify_all.sh @@ -224,7 +224,6 @@ if [[ $elf_mode -eq 0 ]]; then if (cargo run --release --bin cargo-zisk verify-constraints \ --emulator \ - --witness-lib target/release/libzisk_witness.so \ --elf "$elf_file" \ --proving-key "$proving_key"); then record_result "$elf_file" "PASSED" "$counter" @@ -248,7 +247,6 @@ else if (cargo run --release --bin cargo-zisk verify-constraints \ --emulator \ - --witness-lib target/release/libzisk_witness.so \ --elf "$elf_file" \ --proving-key "$proving_key"); then record_result "$elf_file" "PASSED" @@ -262,7 +260,6 @@ else if (cargo run --release --bin cargo-zisk verify-constraints \ --emulator \ - --witness-lib target/release/libzisk_witness.so \ --elf "$elf_file" \ --input "$input_path" \ --proving-key "$proving_key"); then @@ -295,7 +292,6 @@ else if (cargo run --release --bin cargo-zisk verify-constraints \ --emulator \ - --witness-lib target/release/libzisk_witness.so \ --elf "$elf_file" \ --input "$input_file" \ --proving-key "$proving_key"); then diff --git a/witness-computation/Cargo.toml b/witness-computation/Cargo.toml index 574eb7e4d..ab311177f 100644 --- a/witness-computation/Cargo.toml +++ b/witness-computation/Cargo.toml @@ -7,9 +7,6 @@ keywords = { workspace = true } repository = { workspace = true } categories = { workspace = true } -[lib] -crate-type = ["dylib"] - [dependencies] executor = { workspace = true } asm-runner = { workspace = true } @@ -28,6 +25,7 @@ precomp-poseidon2 = { workspace = true } precomp-big-int = { workspace = true } precomp-arith-eq = { workspace = true } precomp-arith-eq-384 = { workspace = true } +precomp-dma = { workspace = true } zisk-pil = { workspace = true } ziskemu = { workspace = true } zisk-core = { workspace = true } diff --git a/witness-computation/src/lib.rs b/witness-computation/src/lib.rs index 39ef75d65..f92509468 100644 --- a/witness-computation/src/lib.rs +++ b/witness-computation/src/lib.rs @@ -1,3 +1,4 @@ mod zisk_lib; +pub use witness::WitnessLibrary; pub use zisk_lib::*; diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index dbdb516be..4a7e183e3 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -13,6 +13,7 @@ use pil_std_lib::Std; use precomp_arith_eq::ArithEqManager; use precomp_arith_eq_384::ArithEq384Manager; use precomp_big_int::Add256Manager; +use precomp_dma::DmaManager; use precomp_keccakf::KeccakfManager; use precomp_poseidon2::Poseidon2Manager; use precomp_sha256f::Sha256fManager; @@ -28,14 +29,15 @@ use tracing::debug; use witness::{WitnessLibrary, WitnessManager}; use zisk_common::{ io::{ZiskStdin, ZiskStream}, - ExecutorStats, ZiskExecutionResult, ZiskLib, ZiskWitnessLibrary, + ExecutorStats, ZiskExecutionResult, }; use zisk_core::{Riscv2zisk, CHUNK_SIZE}; #[cfg(feature = "packed")] use zisk_pil::PACKED_INFO; use zisk_pil::{ ADD_256_AIR_IDS, ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, - BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, + BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, DMA_64_ALIGNED_AIR_IDS, DMA_AIR_IDS, + DMA_PRE_POST_AIR_IDS, DMA_UNALIGNED_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, MEM_ALIGN_AIR_IDS, MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, MEM_ALIGN_WRITE_BYTE_AIR_IDS, POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, @@ -56,9 +58,8 @@ pub struct WitnessLib { with_hints: bool, } -#[no_mangle] #[allow(clippy::too_many_arguments)] -fn init_library( +pub fn init_zisk_lib( verbose_mode: proofman_common::VerboseMode, elf_path: PathBuf, asm_mt_path: Option, @@ -67,10 +68,10 @@ fn init_library( unlock_mapped_memory: bool, shared_tables: bool, with_hints: bool, -) -> Result>, Box> { +) -> WitnessLib { let chunk_size = CHUNK_SIZE; - let result = Box::new(WitnessLib { + WitnessLib { elf_path, asm_mt_path, asm_rh_path, @@ -81,9 +82,7 @@ fn init_library( shared_tables, with_hints, verbose_mode, - }); - - Ok(result) + } } impl WitnessLibrary for WitnessLib { @@ -130,6 +129,7 @@ impl WitnessLibrary for WitnessLib { let arith_eq_sm = ArithEqManager::new(std.clone()); let arith_eq_384_sm = ArithEq384Manager::new(std.clone()); let add256_sm = Add256Manager::new(std.clone()); + let dma_sm = DmaManager::new(std.clone()); let mem_instances = vec![ (ZISK_AIRGROUP_ID, MEM_AIR_IDS[0]), @@ -147,6 +147,13 @@ impl WitnessLibrary for WitnessLib { (ZISK_AIRGROUP_ID, BINARY_EXTENSION_AIR_IDS[0]), ]; + let dma_instances = vec![ + (ZISK_AIRGROUP_ID, DMA_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_PRE_POST_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_UNALIGNED_AIR_IDS[0]), + ]; + let sm_bundle = StaticSMBundle::new( self.asm_mt_path.is_some(), vec![ @@ -182,6 +189,7 @@ impl WitnessLibrary for WitnessLib { vec![(ZISK_AIRGROUP_ID, ADD_256_AIR_IDS[0])], StateMachines::Add256Manager(add256_sm.clone()), ), + (dma_instances, StateMachines::DmaManager(dma_sm.clone())), ], ); @@ -244,6 +252,9 @@ impl WitnessLibrary for WitnessLib { wcm.register_component(executor.clone()); self.executor = Some(executor); + + wcm.set_witness_initialized(); + Ok(()) } @@ -266,14 +277,14 @@ impl WitnessLibrary for WitnessLib { } } -impl ZiskWitnessLibrary for WitnessLib { - fn set_stdin(&self, stdin: ZiskStdin) { +impl WitnessLib { + pub fn set_stdin(&self, stdin: ZiskStdin) { if let Some(executor) = &self.executor { executor.set_stdin(stdin); } } - fn set_hints_stream(&self, hints_stream: zisk_common::io::StreamSource) -> Result<()> { + pub fn set_hints_stream(&self, hints_stream: zisk_common::io::StreamSource) -> Result<()> { if let Some(executor) = &self.executor { executor.set_hints_stream_src(hints_stream) } else { @@ -285,9 +296,7 @@ impl ZiskWitnessLibrary for WitnessLib { /// /// # Returns /// * `u16` - The execution result code. - fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)> { + pub fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStats)> { self.executor.as_ref().map(|executor| executor.get_execution_result()) } } - -impl ZiskLib for WitnessLib {} diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index 86e7f9aee..cbe97d41e 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -30,7 +30,7 @@ getrandom = { version = "0.2", features = ["custom"] } cfg-if = "1.0" tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } -bincode = "2.0" +bincode = { workspace = true } paste = "1.0" sha2 = { workspace = true } fields = { workspace = true } diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index 2ea6dc099..c6db8a664 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -23,9 +23,12 @@ getrandom = { version = "0.2", features = ["custom"] } cfg-if = "1.0" tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } -bincode = "2.0" + +bincode = { workspace = true } sha2 = { workspace = true } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0", features = ["verify"] } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0", features = [ + "verify", +] } [target.'cfg(any(zisk_hints, zisk_hints_debug))'.dependencies] once_cell = "1.21.3" diff --git a/ziskos/entrypoint/src/dma/memcmp.s b/ziskos/entrypoint/src/dma/memcmp.s new file mode 100644 index 000000000..d49cd21fc --- /dev/null +++ b/ziskos/entrypoint/src/dma/memcmp.s @@ -0,0 +1,14 @@ + .section ".note.GNU-stack","",@progbits + .text + .attribute 4, 16 + .attribute 5, "rv64im" + .globl memcmp + .p2align 4 + .type memcmp,@function +memcmp: + csrs 0x814, a2 # Marker: Write count (a2) to CSR 0x814 + add a0,a0,a1 + ret + + .size memcmp, .-memcmp + .section .text.hot,"ax",@progbits \ No newline at end of file diff --git a/ziskos/entrypoint/src/dma/memcpy.s b/ziskos/entrypoint/src/dma/memcpy.s new file mode 100644 index 000000000..610b94ac5 --- /dev/null +++ b/ziskos/entrypoint/src/dma/memcpy.s @@ -0,0 +1,14 @@ + .section ".note.GNU-stack","",@progbits + .text + .attribute 4, 16 + .attribute 5, "rv64im" + .globl memcpy + .p2align 4 + .type memcpy,@function +memcpy: + csrs 0x813, a2 # Marker: Write count (a2) to CSR 0x813 + add x0,a0,a1 + ret + + .size memcpy, .-memcpy + .section .text.hot,"ax",@progbits \ No newline at end of file diff --git a/ziskos/entrypoint/src/dma/memmove.s b/ziskos/entrypoint/src/dma/memmove.s new file mode 100644 index 000000000..f95b81222 --- /dev/null +++ b/ziskos/entrypoint/src/dma/memmove.s @@ -0,0 +1,13 @@ + .section ".note.GNU-stack","",@progbits + .text + .attribute 4, 16 + .attribute 5, "rv64im" + .globl memmove + .p2align 4 + .type memmove,@function +memmove: + csrs 0x813, a2 # Marker: Write count (a2) to CSR 0x813 + add x0,a0,a1 + ret + .size memmove, .-memmove + .section .text.hot,"ax",@progbits \ No newline at end of file diff --git a/ziskos/entrypoint/src/io.rs b/ziskos/entrypoint/src/io.rs new file mode 100644 index 000000000..f376274f5 --- /dev/null +++ b/ziskos/entrypoint/src/io.rs @@ -0,0 +1,57 @@ +//! I/O utilities for Zisk zkVM programs. +//! +//! This module provides a high-level API for reading inputs and committing public outputs. + +use crate::{read_input, set_output}; +use serde::{de::DeserializeOwned, Serialize}; + +/// Read a deserializable object from the input stream. +/// +/// ### Examples +/// ```ignore +/// use serde::{Deserialize, Serialize}; +/// +/// #[derive(Serialize, Deserialize)] +/// struct MyStruct { +/// a: u32, +/// b: u32, +/// } +/// +/// let data: MyStruct = ziskos::io::read(); +/// ``` +pub fn read() -> T { + let bytes = read_input(); + bincode::deserialize(&bytes).expect("Deserialization failed") +} + +/// Read raw bytes from the input stream. +/// +/// ### Examples +/// ```ignore +/// let data: Vec = ziskos::io::read_vec(); +/// ``` +pub fn read_vec() -> Vec { + read_input() +} + +/// Commit a serializable value to public outputs. +/// The value is serialized with bincode and written as 32-bit chunks. +pub fn commit(value: &T) { + let bytes = bincode::serialize(value).expect("Serialization failed"); + write(&bytes); +} + +/// Write raw bytes to public outputs. +/// Bytes are written as 32-bit little-endian values. +pub fn write(buf: &[u8]) { + let chunks = buf.len().div_ceil(4); + + for i in 0..chunks { + let start = i * 4; + let end = (start + 4).min(buf.len()); + let mut bytes = [0u8; 4]; + bytes[..end - start].copy_from_slice(&buf[start..end]); + let val = u32::from_le_bytes(bytes); + set_output(i, val); + } +} diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 259e8425e..2f0b485ad 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -14,6 +14,8 @@ pub mod zisklib; pub mod syscalls; +pub mod io; + pub mod ziskos_definitions; #[cfg(any(zisk_hints, zisk_hints_debug))] @@ -289,4 +291,7 @@ mod ziskos { ptr } + core::arch::global_asm!(include_str!("dma/memcpy.s")); + core::arch::global_asm!(include_str!("dma/memmove.s")); + // core::arch::global_asm!(include_str!("dma/memcmp.s")); } diff --git a/ziskos/entrypoint/src/memcpy_test.rs b/ziskos/entrypoint/src/memcpy_test.rs new file mode 100644 index 000000000..074a7c635 --- /dev/null +++ b/ziskos/entrypoint/src/memcpy_test.rs @@ -0,0 +1,489 @@ +#[cfg(test)] +mod memcpy_tests { + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + use super::ziskos::memcpy; + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + fn memcpy(dst: *mut u8, src: *const u8, len: usize) -> *mut u8 { + unsafe { + std::ptr::copy(src, dst, len); + } + dst + } + use std::alloc::{alloc, dealloc, Layout}; + + // Helper function to create aligned memory + unsafe fn alloc_aligned(size: usize, align: usize) -> *mut u8 { + let layout = Layout::from_size_align(size + align, align).unwrap(); + let ptr = alloc(layout); + if ptr.is_null() { + panic!("Failed to allocate memory"); + } + // Align the pointer + let aligned = (ptr as usize + align - 1) & !(align - 1); + aligned as *mut u8 + } + + // Helper function to deallocate aligned memory + unsafe fn dealloc_aligned(ptr: *mut u8, size: usize, align: usize) { + let layout = Layout::from_size_align(size + align, align).unwrap(); + // We need to get back to the original pointer, but for simplicity in tests, + // we'll use a different approach + dealloc(ptr, layout); + } + + #[test] + fn test_memcpy_zero_length() { + unsafe { + let src = [1u8, 2, 3, 4]; + let mut dst = [0u8; 4]; + + let result = memcpy(dst.as_mut_ptr(), src.as_ptr(), 0); + + assert_eq!(result, dst.as_mut_ptr()); + assert_eq!(dst, [0, 0, 0, 0]); // Should remain unchanged + } + } + + #[test] + fn test_memcpy_single_byte() { + unsafe { + let src = [0x42u8]; + let mut dst = [0u8; 1]; + + memcpy(dst.as_mut_ptr(), src.as_ptr(), 1); + + assert_eq!(dst[0], 0x42); + } + } + + #[test] + fn test_memcpy_aligned_8_small() { + unsafe { + // Test 8-byte aligned pointers with small copy (< 32 bytes) + let src = alloc_aligned(64, 8); + let dst = alloc_aligned(64, 8); + + // Initialize source data + for i in 0..16 { + *src.add(i) = (i + 1) as u8; + } + + memcpy(dst, src, 16); + + // Verify copy + for i in 0..16 { + assert_eq!(*dst.add(i), (i + 1) as u8, "Mismatch at byte {}", i); + } + + dealloc_aligned(src, 64, 8); + dealloc_aligned(dst, 64, 8); + } + } + + #[test] + fn test_memcpy_aligned_8_large() { + unsafe { + // Test 8-byte aligned pointers with large copy (> 32 bytes) + let src = alloc_aligned(128, 8); + let dst = alloc_aligned(128, 8); + + // Initialize source data + for i in 0..64 { + *src.add(i) = (i % 256) as u8; + } + + memcpy(dst, src, 64); + + // Verify copy + for i in 0..64 { + assert_eq!(*dst.add(i), (i % 256) as u8, "Mismatch at byte {}", i); + } + + dealloc_aligned(src, 128, 8); + dealloc_aligned(dst, 128, 8); + } + } + + #[test] + fn test_memcpy_src_unaligned() { + unsafe { + // Test unaligned source pointer + let src_base = alloc_aligned(64, 8); + let dst = alloc_aligned(64, 8); + let src = src_base.add(3); // Unaligned by 3 bytes + + // Initialize source data + for i in 0..20 { + *src.add(i) = (i + 0x10) as u8; + } + + memcpy(dst, src, 20); + + // Verify copy + for i in 0..20 { + assert_eq!(*dst.add(i), (i + 0x10) as u8, "Mismatch at byte {}", i); + } + + dealloc_aligned(src_base, 64, 8); + dealloc_aligned(dst, 64, 8); + } + } + + #[test] + fn test_memcpy_dst_unaligned() { + unsafe { + // Test unaligned destination pointer + let src = alloc_aligned(64, 8); + let dst_base = alloc_aligned(64, 8); + let dst = dst_base.add(5); // Unaligned by 5 bytes + + // Initialize source data + for i in 0..20 { + *src.add(i) = (i + 0x20) as u8; + } + + memcpy(dst, src, 20); + + // Verify copy + for i in 0..20 { + assert_eq!(*dst.add(i), (i + 0x20) as u8, "Mismatch at byte {}", i); + } + + dealloc_aligned(src, 64, 8); + dealloc_aligned(dst_base, 64, 8); + } + } + + #[test] + fn test_memcpy_both_unaligned() { + unsafe { + // Test both pointers unaligned + let src_base = alloc_aligned(64, 8); + let dst_base = alloc_aligned(64, 8); + let src = src_base.add(2); // Unaligned by 2 bytes + let dst = dst_base.add(6); // Unaligned by 6 bytes + + // Initialize source data + for i in 0..25 { + *src.add(i) = (i + 0x30) as u8; + } + + memcpy(dst, src, 25); + + // Verify copy + for i in 0..25 { + assert_eq!(*dst.add(i), (i + 0x30) as u8, "Mismatch at byte {}", i); + } + + dealloc_aligned(src_base, 64, 8); + dealloc_aligned(dst_base, 64, 8); + } + } + + #[test] + fn test_memcpy_edge_sizes() { + unsafe { + let sizes = [1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65]; + + for &size in &sizes { + let src = alloc_aligned(128, 8); + let dst = alloc_aligned(128, 8); + + // Initialize source data + for i in 0..size { + *src.add(i) = ((i * 3 + 7) % 256) as u8; + } + + memcpy(dst, src, size); + + // Verify copy + for i in 0..size { + assert_eq!( + *dst.add(i), + ((i * 3 + 7) % 256) as u8, + "Size {} mismatch at byte {}", + size, + i + ); + } + + dealloc_aligned(src, 128, 8); + dealloc_aligned(dst, 128, 8); + } + } + } + + #[test] + fn test_memcpy_overlapping_forward() { + unsafe { + // Test overlapping memory (src before dst) - should work + let mut buffer = [0u8; 20]; + + // Initialize data + for i in 0..10 { + buffer[i] = (i + 0x40) as u8; + } + + // Copy from buffer[0..10] to buffer[5..15] + memcpy(buffer.as_mut_ptr().add(5), buffer.as_ptr(), 10); + + // Verify - first 5 bytes unchanged, next 10 are the copy + for i in 0..5 { + assert_eq!(buffer[i], (i + 0x40) as u8); + } + for i in 5..15 { + assert_eq!(buffer[i], (i - 5 + 0x40) as u8); + } + } + } + + #[test] + fn test_memcpy_return_value() { + unsafe { + let src = [1u8, 2, 3, 4]; + let mut dst = [0u8; 4]; + let dst_ptr = dst.as_mut_ptr(); + + let result = memcpy(dst_ptr, src.as_ptr(), 4); + + assert_eq!(result, dst_ptr, "Return value should be original dst pointer"); + } + } + + #[test] + fn test_memcpy_large_unaligned() { + unsafe { + // Test large copy with unaligned pointers + let src_base = alloc_aligned(256, 8); + let dst_base = alloc_aligned(256, 8); + let src = src_base.add(3); // Unaligned + let dst = dst_base.add(1); // Unaligned + + // Initialize source data with pattern + for i in 0..200 { + *src.add(i) = ((i * 7 + 13) % 256) as u8; + } + + memcpy(dst, src, 200); + + // Verify copy + for i in 0..200 { + assert_eq!( + *dst.add(i), + ((i * 7 + 13) % 256) as u8, + "Large unaligned mismatch at byte {}", + i + ); + } + + dealloc_aligned(src_base, 256, 8); + dealloc_aligned(dst_base, 256, 8); + } + } + + #[test] + fn test_memcpy_debug_print() { + unsafe { + let src = [0x12u8, 0x34, 0x56, 0x78]; + let mut dst = [0u8; 4]; + + println!("Before memcpy:"); + println!(" src: {:p} = {:02x?}", src.as_ptr(), src); + println!(" dst: {:p} = {:02x?}", dst.as_ptr(), dst); + + memcpy(dst.as_mut_ptr(), src.as_ptr(), 4); + + println!("After memcpy:"); + println!(" dst: {:p} = {:02x?}", dst.as_ptr(), dst); + + assert_eq!(dst, src); + } + } + + #[test] + fn test_pointer_printing_formats() { + unsafe { + let data = [0xDEu8, 0xAD, 0xBE, 0xEF]; + let ptr = data.as_ptr(); + + println!("\nDifferent ways to print pointers:"); + println!("Standard format: {:p}", ptr); + println!("Hex lowercase: 0x{:x}", ptr as usize); + println!("Hex UPPERCASE: 0x{:X}", ptr as usize); + println!("With padding: 0x{:016x}", ptr as usize); + println!("Auto-prefixed: {:#x}", ptr as usize); + println!("Debug format: {:?}", ptr); + + // También mostrar como imprimir la data + println!("\nData at pointer:"); + println!("Hex bytes: {:02x?}", data); + println!("Hex UPPER bytes: {:02X?}", data); + println!("Pretty debug: {:#02x?}", data); + } + } + + #[test] + fn test_memcpy_large_aligned_568699() { + unsafe { + const SIZE: usize = 568699; + + // Allocate aligned memory for both source and destination + let src = alloc_aligned(SIZE + 64, 8); + let dst = alloc_aligned(SIZE + 64, 8); + + println!("\nTest memcpy with aligned pointers and size: {}", SIZE); + println!("Source pointer: {:p} (0x{:016x})", src, src as usize); + println!("Destination pointer: {:p} (0x{:016x})", dst, dst as usize); + println!("Alignment check src: {} (should be 0)", (src as usize) & 7); + println!("Alignment check dst: {} (should be 0)", (dst as usize) & 7); + + // Initialize source data with a predictable pattern + for i in 0..SIZE { + *src.add(i) = ((i * 73 + 127) % 256) as u8; + } + + // Perform the copy + let start = std::time::Instant::now(); + let result = memcpy(dst, src, SIZE); + let elapsed = start.elapsed(); + + println!("Copy completed in: {:?}", elapsed); + println!( + "Throughput: {:.2} MB/s", + (SIZE as f64) / (1024.0 * 1024.0) / elapsed.as_secs_f64() + ); + + // Verify the return value + assert_eq!(result, dst, "Return value should be original dst pointer"); + + // Verify the copy by checking every byte + let mut mismatches = 0; + for i in 0..SIZE { + let expected = ((i * 73 + 127) % 256) as u8; + let actual = *dst.add(i); + if actual != expected { + if mismatches < 10 { + // Only print first 10 mismatches + println!( + "Mismatch at byte {}: expected 0x{:02x}, got 0x{:02x}", + i, expected, actual + ); + } + mismatches += 1; + } + } + + if mismatches > 0 { + println!("Total mismatches: {}", mismatches); + panic!("Copy verification failed with {} mismatches", mismatches); + } + + println!("✓ All {} bytes copied correctly", SIZE); + + // Test some specific boundary checks + println!("\nBoundary checks:"); + println!("First byte: src[0]=0x{:02x}, dst[0]=0x{:02x}", *src, *dst); + let last_idx = SIZE - 1; + println!( + "Last byte: src[{}]=0x{:02x}, dst[{}]=0x{:02x}", + last_idx, + *src.add(last_idx), + last_idx, + *dst.add(last_idx) + ); + + // Check bytes at key positions (32-byte boundaries, etc.) + let check_positions = [31, 32, 63, 64, 127, 128, 255, 256, 511, 512, 1023, 1024]; + for &pos in &check_positions { + if pos < SIZE { + let expected = ((pos * 73 + 127) % 256) as u8; + let actual = *dst.add(pos); + println!( + "Position {}: expected=0x{:02x}, actual=0x{:02x} {}", + pos, + expected, + actual, + if expected == actual { "✓" } else { "✗" } + ); + assert_eq!(actual, expected, "Mismatch at position {}", pos); + } + } + + dealloc_aligned(src, SIZE + 64, 8); + dealloc_aligned(dst, SIZE + 64, 8); + } + } + + #[test] + fn test_memcpy_various_large_sizes() { + // Test various large sizes to ensure robustness + let test_sizes = [ + 568699, // Original request + 568700, // Just one more + 568698, // Just one less + 1048576, // 1 MB + 524288, // 512 KB + 131072, // 128 KB + 65536, // 64 KB + 32768, // 32 KB + 16384, // 16 KB + ]; + + for &size in &test_sizes { + unsafe { + println!("\nTesting size: {} bytes", size); + + let src = alloc_aligned(size + 64, 8); + let dst = alloc_aligned(size + 64, 8); + + // Simple pattern for faster initialization + for i in 0..size { + *src.add(i) = (i % 256) as u8; + } + + let start = std::time::Instant::now(); + memcpy(dst, src, size); + let elapsed = start.elapsed(); + + println!( + " Time: {:?}, Throughput: {:.2} MB/s", + elapsed, + (size as f64) / (1024.0 * 1024.0) / elapsed.as_secs_f64() + ); + + // Quick verification - check first, last, and some middle bytes + assert_eq!(*dst, 0, "First byte mismatch for size {}", size); + if size > 1 { + let last_idx = size - 1; + let expected_last = (last_idx % 256) as u8; + assert_eq!( + *dst.add(last_idx), + expected_last, + "Last byte mismatch for size {}", + size + ); + } + + // Check a few strategic positions + let check_positions = [size / 4, size / 2, 3 * size / 4]; + for &pos in &check_positions { + if pos < size { + let expected = (pos % 256) as u8; + assert_eq!( + *dst.add(pos), + expected, + "Mismatch at position {} for size {}", + pos, + size + ); + } + } + + dealloc_aligned(src, size + 64, 8); + dealloc_aligned(dst, size + 64, 8); + } + } + + println!("\n✓ All large size tests passed!"); + } +} diff --git a/ziskos/entrypoint/src/syscalls/mod.rs b/ziskos/entrypoint/src/syscalls/mod.rs index 236c001f3..b43dd3c18 100644 --- a/ziskos/entrypoint/src/syscalls/mod.rs +++ b/ziskos/entrypoint/src/syscalls/mod.rs @@ -54,6 +54,18 @@ macro_rules! ziskos_syscall { ); } }}; + ($csr_addr:literal, $arg0:expr, $arg1:expr, $arg2: expr) => {{ + unsafe { + asm!( + concat!("csrs ", stringify!($csr_addr), ", {0}"), + "add x0, {1}, {2}", + in(reg) $arg0, // {0} + in(reg) $arg1, // {1} + in(reg) $arg2, // {2} + options(nostack) + ); + } + }}; } #[macro_export] diff --git a/ziskos/entrypoint/src/syscalls/syscall.rs b/ziskos/entrypoint/src/syscalls/syscall.rs index 2efada2ed..be2b32703 100644 --- a/ziskos/entrypoint/src/syscalls/syscall.rs +++ b/ziskos/entrypoint/src/syscalls/syscall.rs @@ -19,3 +19,5 @@ pub const SYSCALL_BLS12_381_COMPLEX_SUB_ID: u16 = 0x80F; pub const SYSCALL_BLS12_381_COMPLEX_MUL_ID: u16 = 0x810; pub const SYSCALL_ADD256_ID: u16 = 0x811; pub const SYSCALL_POSEIDON2_ID: u16 = 0x812; +pub const SYSCALL_DMA_MEMCPY_ID: u16 = 0x813; +pub const SYSCALL_DMA_MEMCMP_ID: u16 = 0x814; From 3987d48ea077bae96261f22dbf8240f963d0955e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 31 Jan 2026 07:06:35 +0000 Subject: [PATCH 380/782] fix ziskos Cargo.toml removing unnecessary hints feature --- ziskos/entrypoint/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index c6db8a664..8bae1c8d1 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -39,4 +39,3 @@ ctor = "0.2" [features] default = [] -hints = [] From a117f4bd0394147b2cd074b778834c4acb03782d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 31 Jan 2026 07:22:58 +0000 Subject: [PATCH 381/782] improvement during MT computation --- emulator-asm/asm-runner/src/asm_mt_runner.rs | 32 +--- .../asm-runner/src/asm_mt_runner_stub.rs | 6 +- executor/src/emu_asm.rs | 148 ++++++++---------- 3 files changed, 71 insertions(+), 115 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 41f1243a4..9f471aba6 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -19,13 +19,6 @@ use anyhow::{Context, Result}; #[cfg(feature = "stats")] use zisk_common::ExecutorStatsEvent; -pub trait Task: Send + Sync + 'static { - type Output: Send + 'static; - fn execute(self) -> Self::Output; -} - -pub type TaskFactory<'a, T> = Box) -> T + Send + Sync + 'a>; - pub enum MinimalTraces { None, EmuTrace(Vec), @@ -69,16 +62,16 @@ impl AsmRunnerMT { } #[allow(clippy::too_many_arguments)] - pub fn run_and_count( + pub fn run_and_count)>( preloaded: &mut PreloadedMT, max_steps: u64, chunk_size: u64, - task_factory: TaskFactory, + mut on_chunk: F, world_rank: i32, local_rank: i32, base_port: Option, _stats: ExecutorStatsHandle, - ) -> Result<(AsmRunnerMT, Vec)> { + ) -> Result>> { let __stats = _stats.clone(); #[cfg(feature = "stats")] @@ -122,7 +115,6 @@ impl AsmRunnerMT { let mut data_ptr = preloaded.output_shmem.data_ptr() as *const AsmMTChunk; let mut emu_traces = Vec::new(); - let mut handles = Vec::new(); let __stats = _stats.clone(); @@ -168,11 +160,9 @@ impl AsmRunnerMT { let emu_trace = Arc::new(AsmMTChunk::to_emu_trace(&mut data_ptr)); let should_exit = emu_trace.end; - let task = task_factory(chunk_id, emu_trace.clone()); + on_chunk(chunk_id.0 as usize, emu_trace.clone()); emu_traces.push(emu_trace); - handles.push(std::thread::spawn(move || task.execute())); - if should_exit { break 0; } @@ -204,12 +194,6 @@ impl AsmRunnerMT { let mhz = (total_steps as f64 / start_time.elapsed().as_secs_f64()) / 1_000_000.0; info!("··· Assembly execution speed: {}MHz", mhz.round()); - // Collect results - let mut tasks = Vec::new(); - for handle in handles { - tasks.push(handle.join().expect("Task panicked")); - } - // Wait for the assembly emulator to complete writing the trace let response = handle .join() @@ -220,15 +204,9 @@ impl AsmRunnerMT { assert!(response.trace_len > 0); assert!(response.trace_len <= response.allocated_len); - // Unwrap the Arc pointers - let emu_traces: Vec = emu_traces - .into_iter() - .map(|arc| Arc::try_unwrap(arc).map_err(|_| AsmRunError::ArcUnwrap)) - .collect::>()?; - #[cfg(feature = "stats")] _stats.add_stat(0, parent_stats_id, "ASM_MT_RUNNER", 0, ExecutorStatsEvent::End); - Ok((AsmRunnerMT::new(emu_traces), tasks)) + Ok(emu_traces) } } diff --git a/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs b/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs index 762315198..9b0329b62 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs @@ -38,16 +38,16 @@ impl AsmRunnerMT { } #[allow(clippy::too_many_arguments)] - pub fn run_and_count( + pub fn run_and_count)>( _: &mut PreloadedMT, _: u64, _: u64, - _: TaskFactory, + _: F, _: i32, _: i32, _: Option, _: ExecutorStatsHandle, - ) -> Result<(AsmRunnerMT, Vec)> { + ) -> Result>> { Err(anyhow::anyhow!("AsmRunnerMT::run_and_count() is not supported on this platform. Only Linux x86_64 is supported.")) } } diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index d841d882a..d46468246 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -9,7 +9,7 @@ use crate::{ }; use asm_runner::{ write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, - MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, Task, TaskFactory, + MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, }; use data_bus::DataBusTrait; use fields::PrimeField64; @@ -18,10 +18,7 @@ use rayon::prelude::*; use sm_rom::RomSM; #[cfg(feature = "stats")] use zisk_common::ExecutorStatsEvent; -use zisk_common::{ - io::ZiskStdin, BusDeviceMetrics, ChunkId, EmuTrace, ExecutorStatsHandle, PayloadType, - ZiskExecutionResult, -}; +use zisk_common::{io::ZiskStdin, ChunkId, EmuTrace, ExecutorStatsHandle, ZiskExecutionResult}; use zisk_core::{ZiskRom, MAX_INPUT_SIZE}; use ziskemu::ZiskEmulator; @@ -257,89 +254,70 @@ impl EmulatorAsm { #[cfg(feature = "stats")] stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::Begin); - struct CounterTask - where - DB: DataBusTrait>, - { - chunk_id: ChunkId, - emu_trace: Arc, - data_bus: DB, - zisk_rom: Arc, - _phantom: std::marker::PhantomData, - _stats: ExecutorStatsHandle, - _parent_stats_id: u64, - } + let results_mu: Mutex> = Mutex::new(Vec::new()); - impl Task for CounterTask - where - F: PrimeField64, - DB: DataBusTrait> + Send + Sync + 'static, - { - type Output = (ChunkId, DB); - - fn execute(mut self) -> Self::Output { - #[cfg(feature = "stats")] - let stats_id = self._stats.next_id(); - #[cfg(feature = "stats")] - self._stats.add_stat( - self._parent_stats_id, - stats_id, - "MT_CHUNK_PLAYER", - 0, - ExecutorStatsEvent::Begin, - ); - - ZiskEmulator::process_emu_trace::( - &self.zisk_rom, - &self.emu_trace, - &mut self.data_bus, - false, - ); - - self.data_bus.on_close(); - - // Add to executor stats - #[cfg(feature = "stats")] - self._stats.add_stat( - self._parent_stats_id, - stats_id, - "MT_CHUNK_PLAYER", - 0, - ExecutorStatsEvent::End, - ); - - (self.chunk_id, self.data_bus) - } - } + let emu_traces = rayon::in_place_scope(|scope| { + let on_chunk = |idx: usize, emu_trace: std::sync::Arc| { + let chunk_id = ChunkId(idx); + let zisk_rom = &self.zisk_rom; + let results_ref = &results_mu; + scope.spawn(move |_| { + #[cfg(feature = "stats")] + let stats_id = stats.next_id(); + #[cfg(feature = "stats")] + stats.add_stat( + parent_stats_id, + stats_id, + "MT_CHUNK_PLAYER", + 0, + ExecutorStatsEvent::Begin, + ); + + let mut data_bus = sm_bundle.build_data_bus_counters(); + + ZiskEmulator::process_emu_trace::( + zisk_rom, + &emu_trace, + &mut data_bus, + false, + ); + + data_bus.on_close(); - let task_factory: TaskFactory<_> = - Box::new(|chunk_id: ChunkId, emu_trace: Arc| { - let data_bus = sm_bundle.build_data_bus_counters(); - CounterTask { - chunk_id, - emu_trace, - data_bus, - zisk_rom: self.zisk_rom.clone(), - _phantom: std::marker::PhantomData::, - _stats: stats.clone(), #[cfg(feature = "stats")] - _parent_stats_id: parent_stats_id, - #[cfg(not(feature = "stats"))] - _parent_stats_id: 0, - } - }); - - let (asm_runner_mt, mut data_buses) = AsmRunnerMT::run_and_count( - &mut self.asm_shmem_mt.lock().unwrap(), - MAX_NUM_STEPS, - self.chunk_size, - task_factory, - self.world_rank, - self.local_rank, - self.base_port, - stats.clone(), - ) - .expect("Error during ASM execution"); + stats.add_stat( + parent_stats_id, + stats_id, + "MT_CHUNK_PLAYER", + 0, + ExecutorStatsEvent::End, + ); + + results_ref.lock().unwrap().push((chunk_id, data_bus)); + }); + }; + + AsmRunnerMT::run_and_count( + &mut self.asm_shmem_mt.lock().unwrap(), + MAX_NUM_STEPS, + self.chunk_size, + on_chunk, + self.world_rank, + self.local_rank, + self.base_port, + stats.clone(), + ) + .expect("Error during ASM execution") + }); + + // Unwrap the Arc pointers now that all rayon tasks have completed + let emu_traces = emu_traces + .into_iter() + .map(|arc| Arc::try_unwrap(arc).expect("Arc should have single owner after scope")) + .collect(); + let asm_runner_mt = AsmRunnerMT::new(emu_traces); + + let mut data_buses = results_mu.into_inner().unwrap(); data_buses.sort_by_key(|(chunk_id, _)| chunk_id.0); From 2e3cb7a2872dcc515426a68ea6c97ae10793f287 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 2 Feb 2026 05:18:55 +0000 Subject: [PATCH 382/782] use RefCell instead of Mutex in hints_shmem.rs --- emulator-asm/asm-runner/src/hints_shmem.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index 4d1a54720..f6b543be3 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -9,7 +9,7 @@ use crate::{ }; use anyhow::Result; use named_sem::NamedSemaphore; -use std::sync::Mutex; +use std::cell::RefCell; use tracing::debug; use zisk_common::io::StreamSink; @@ -51,7 +51,7 @@ struct ServiceResources { /// HintsShmem struct manages the writing of processed precompile hints to shared memory. pub struct HintsShmem { /// Service resources combining shared memory writers and semaphores - resources: Mutex>, + resources: RefCell>, } unsafe impl Send for HintsShmem {} @@ -94,7 +94,7 @@ impl HintsShmem { resource.control_writer.write_u64_at(0, 0); } - Ok(Self { resources: Mutex::new(resources) }) + Ok(Self { resources: RefCell::new(resources) }) } /// Initialize the shared memory writers for the pipeline. @@ -166,7 +166,7 @@ impl StreamSink for HintsShmem { Self::BUFFER_CAPACITY_U64 ); - let mut resources = self.resources.lock().unwrap(); + let mut resources = self.resources.borrow_mut(); for resource in resources.iter_mut() { // Read current write position once (we're the only writer) From 51408b1d3c1ad14a0fa8de094a694137a520a19b Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 2 Feb 2026 07:51:52 +0000 Subject: [PATCH 383/782] improve executor.rs --- executor/src/executor.rs | 156 +++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 80 deletions(-) diff --git a/executor/src/executor.rs b/executor/src/executor.rs index a5e7e2702..2ee34a500 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -480,10 +480,8 @@ impl ZiskExecutor { let ordered_chunks = self.order_chunks(&chunks_to_execute, &global_id_chunks); let global_ids: Vec = secn_instances.keys().copied().collect(); - let collect_start_times: Vec>>> = - global_ids.iter().map(|_| Arc::new(AtomicCell::new(None))).collect(); - - let chunks_to_execute_clone = chunks_to_execute.clone(); + let collect_start_times: Vec>> = + global_ids.iter().map(|_| AtomicCell::new(None)).collect(); let global_ids_map: HashMap = global_ids.iter().enumerate().map(|(idx, &id)| (id, idx)).collect(); @@ -493,12 +491,12 @@ impl ZiskExecutor { .sm_bundle .build_data_bus_collectors(&pctx, &secn_instances, &chunks_to_execute) .into_iter() - .map(|db| Arc::new(Mutex::new(db))) + .map(Mutex::new) .collect::>(); - let n_chunks_left: Vec> = global_ids + let n_chunks_left: Vec = global_ids .iter() - .map(|global_id| Arc::new(AtomicUsize::new(global_id_chunks[global_id].len()))) + .map(|global_id| AtomicUsize::new(global_id_chunks[global_id].len())) .collect(); for global_id in global_ids.iter() { @@ -521,41 +519,30 @@ impl ZiskExecutor { self.stats.insert_witness_stats(*global_id, stats); } - let next_chunk = Arc::new(AtomicUsize::new(0)); - let n_threads = rayon::current_num_threads(); - - let mut handles = Vec::with_capacity(n_threads); - for _ in 0..n_threads { - let next_chunk = Arc::clone(&next_chunk); - let min_traces_lock = Arc::clone(&self.min_traces); - let data_buses = data_buses.clone(); - let zisk_rom = self.zisk_rom.clone(); - let n_chunks_left = n_chunks_left.clone(); - let global_ids_map = global_ids_map.clone(); - let global_id_chunks = global_id_chunks.clone(); - let collectors_by_instance = self.collectors_by_instance.clone(); - let ordered_chunks_clone = ordered_chunks.clone(); - - let pctx_clone = pctx.clone(); - - let chunks_to_execute = chunks_to_execute_clone.clone(); - - let collect_start_times = collect_start_times.clone(); - - let _stats = self.stats.clone(); - handles.push(std::thread::spawn(move || { - let guard = min_traces_lock.read().unwrap(); - let min_traces = match &*guard { - MinimalTraces::EmuTrace(v) => v, - MinimalTraces::AsmEmuTrace(a) => &a.vec_chunks, - _ => unreachable!(), - }; - loop { - let next_chunk_id = next_chunk.fetch_add(1, Ordering::SeqCst); - if next_chunk_id >= ordered_chunks_clone.len() { + let next_chunk = AtomicUsize::new(0); + + rayon::in_place_scope(|scope| { + for _ in 0..rayon::current_num_threads() { + let next_chunk = &next_chunk; + let n_chunks_left = &n_chunks_left; + let collectors_by_instance = &self.collectors_by_instance; + let collect_start_times = &collect_start_times; + let _stats = &self.stats; + let min_traces = &min_traces; + let data_buses = &data_buses; + let zisk_rom = &self.zisk_rom; + let global_ids_map = &global_ids_map; + let global_id_chunks = &global_id_chunks; + let ordered_chunks = &ordered_chunks; + let chunks_to_execute = &chunks_to_execute; + let pctx = &pctx; + + scope.spawn(move |_| loop { + let next_chunk_id = next_chunk.fetch_add(1, Ordering::Relaxed); + if next_chunk_id >= ordered_chunks.len() { break; } - let chunk_id = ordered_chunks_clone[next_chunk_id]; + let chunk_id = ordered_chunks[next_chunk_id]; if let Some(mut data_bus) = data_buses[chunk_id].lock().unwrap().take() { for global_id in chunks_to_execute[chunk_id].iter() { @@ -566,15 +553,20 @@ impl ZiskExecutor { } ZiskEmulator::process_emu_traces::( - &zisk_rom, + zisk_rom, min_traces, chunk_id, &mut data_bus, ); - for (global_id, collector) in data_bus.into_devices(false) { + // Collect all device results locally to minimize lock acquisitions + let devices = data_bus.into_devices(false); + let mut entries: Vec<(usize, usize, Option<(usize, Box>)>)> = Vec::new(); + let mut affected_globals: Vec<(usize, usize)> = Vec::new(); + + for (global_id, collector) in devices { if let Some(global_id) = global_id { - let global_id_idx = global_ids_map + let global_id_idx = *global_ids_map .get(&global_id) .expect("Global ID not found in map"); @@ -584,47 +576,51 @@ impl ZiskExecutor { .position(|&id| id == chunk_id) .expect("Chunk ID not found in order"); - collectors_by_instance - .write() - .unwrap() - .get_mut(&global_id) - .unwrap()[position] = Some((chunk_id, collector.unwrap())); - - if n_chunks_left[*global_id_idx].fetch_sub(1, Ordering::SeqCst) == 1 - { - pctx_clone.set_witness_ready(global_id, true); - - let collect_start_time = collect_start_times[*global_id_idx] - .load() - .expect("Collect start time was not set"); - let collect_duration = - collect_start_time.elapsed().as_millis() as u64; - - let (airgroup_id, air_id) = pctx_clone - .dctx_get_instance_info(global_id) - .expect("Failed to get instance info"); - let stats = Stats { - airgroup_id, - air_id, - collect_start_time, - collect_duration, - witness_start_time: Instant::now(), - witness_duration: 0, - num_chunks: global_id_chunks[&global_id].len(), - }; + entries.push((global_id, position, Some((chunk_id, collector.unwrap())))); + affected_globals.push((global_id, global_id_idx)); + } + } - _stats.insert_witness_stats(global_id, stats); - } + // Single write-lock acquisition to flush all results from this chunk + { + let mut guard = collectors_by_instance.write().unwrap(); + for (global_id, position, entry) in entries.iter_mut() { + guard.get_mut(global_id).unwrap()[*position] = entry.take(); } } - } - } - })); - } - for handle in handles { - handle.join().unwrap(); - } + // Update atomic counters and mark ready instances (no lock needed) + for (global_id, global_id_idx) in affected_globals { + if n_chunks_left[global_id_idx].fetch_sub(1, Ordering::SeqCst) == 1 + { + pctx.set_witness_ready(global_id, true); + + let collect_start_time = collect_start_times[global_id_idx] + .load() + .expect("Collect start time was not set"); + let collect_duration = + collect_start_time.elapsed().as_millis() as u64; + + let (airgroup_id, air_id) = pctx + .dctx_get_instance_info(global_id) + .expect("Failed to get instance info"); + let stats = Stats { + airgroup_id, + air_id, + collect_start_time, + collect_duration, + witness_start_time: Instant::now(), + witness_duration: 0, + num_chunks: global_id_chunks[&global_id].len(), + }; + + _stats.insert_witness_stats(global_id, stats); + } + } + } + }); + } + }); } /// Computes and generates witness for secondary state machine instance of type `Table`. From b4b17c903e7067de9dc0ce60316976b5354e2505 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 2 Feb 2026 09:13:34 +0000 Subject: [PATCH 384/782] cargo clippy --- ziskos-hints/src/handlers/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index c7d7304b4..186044880 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -51,7 +51,6 @@ macro_rules! hint_fields { }; } -/// Read a length-prefixed field from hint data // #[inline] // fn read_field<'a>(data: &'a [u64], pos: &mut usize) -> anyhow::Result<&'a [u64]> { // let len = From c0b1cf6253a1393f8d25a08433ffa968d40e9a1e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 2 Feb 2026 09:41:07 +0000 Subject: [PATCH 385/782] cargo clippy --- common/src/hints.rs | 2 +- common/src/utils.rs | 2 +- distributed/crates/common/src/types.rs | 1 + distributed/crates/worker/src/worker_node.rs | 6 ++--- emulator-asm/asm-runner/src/asm_mt_runner.rs | 2 +- executor/src/executor.rs | 16 +++++++------ .../arith/src/arith_table_helpers.rs | 1 + ziskos-hints/src/handlers/mod.rs | 2 +- ziskos/entrypoint/src/syscalls/keccakf.rs | 2 +- ziskos/entrypoint/src/syscalls/poseidon2.rs | 2 +- .../entrypoint/src/zisklib/lib/keccak256.rs | 24 +++++++++++-------- 11 files changed, 34 insertions(+), 26 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 244dff07c..5c73c8484 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -385,7 +385,7 @@ impl PrecompileHint { let length = header & 0xFFFFFFFF; // Calculate how many u64s are needed to hold length - let num_u64s = ((length + 7) / 8) as usize; + let num_u64s = length.div_ceil(8) as usize; anyhow::ensure!( slice.len() >= idx + 1 + num_u64s, diff --git a/common/src/utils.rs b/common/src/utils.rs index 53973ec7c..938f367b0 100644 --- a/common/src/utils.rs +++ b/common/src/utils.rs @@ -109,7 +109,7 @@ pub fn reinterpret_vec(mut v: Vec) -> anyhow::Result WorkerNodeGrpc { None => InputSourceDto::InputNull, }; - let hints_source = if params.hints_path.is_some() { + let hints_source = if let Some(hints_path) = ¶ms.hints_path { if params.hints_stream { // Hints will be streamed - use placeholder, will be updated when stream completes - HintsSourceDto::HintsStream(params.hints_path.as_ref().unwrap().clone()) + HintsSourceDto::HintsStream(hints_path.clone()) } else { // Validate and get the full path let hints_uri = Self::validate_subdir( &self.worker_config.worker.inputs_folder, - &PathBuf::from(params.hints_path.as_ref().unwrap()), + &PathBuf::from(hints_path), ) .await?; diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 9f471aba6..156fdc5d2 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -160,7 +160,7 @@ impl AsmRunnerMT { let emu_trace = Arc::new(AsmMTChunk::to_emu_trace(&mut data_ptr)); let should_exit = emu_trace.end; - on_chunk(chunk_id.0 as usize, emu_trace.clone()); + on_chunk(chunk_id.0, emu_trace.clone()); emu_traces.push(emu_trace); if should_exit { diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 2ee34a500..12ba8af3a 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -60,6 +60,7 @@ use crate::{Emulator, EmulatorKind, StaticSMBundle}; use anyhow::Result; pub type DeviceMetricsByChunk = (ChunkId, Box); // (chunk_id, metrics) +type ChunkCollector = (usize, Box>); #[allow(dead_code)] enum MinimalTraceExecutionMode { @@ -110,9 +111,7 @@ pub struct ZiskExecutor { sm_bundle: StaticSMBundle, /// Collectors by instance, storing statistics and collectors for each instance. - #[allow(clippy::type_complexity)] - collectors_by_instance: - Arc>)>>>>>, + collectors_by_instance: Arc>>>>, /// Statistics collected during the execution, including time taken for collection and witness computation. stats: ExecutorStatsHandle, @@ -561,7 +560,7 @@ impl ZiskExecutor { // Collect all device results locally to minimize lock acquisitions let devices = data_bus.into_devices(false); - let mut entries: Vec<(usize, usize, Option<(usize, Box>)>)> = Vec::new(); + let mut entries: Vec<(usize, usize, Option)> = Vec::new(); let mut affected_globals: Vec<(usize, usize)> = Vec::new(); for (global_id, collector) in devices { @@ -576,7 +575,11 @@ impl ZiskExecutor { .position(|&id| id == chunk_id) .expect("Chunk ID not found in order"); - entries.push((global_id, position, Some((chunk_id, collector.unwrap())))); + entries.push(( + global_id, + position, + Some((chunk_id, collector.unwrap())), + )); affected_globals.push((global_id, global_id_idx)); } } @@ -591,8 +594,7 @@ impl ZiskExecutor { // Update atomic counters and mark ready instances (no lock needed) for (global_id, global_id_idx) in affected_globals { - if n_chunks_left[global_id_idx].fetch_sub(1, Ordering::SeqCst) == 1 - { + if n_chunks_left[global_id_idx].fetch_sub(1, Ordering::SeqCst) == 1 { pctx.set_witness_ready(global_id, true); let collect_start_time = collect_start_times[global_id_idx] diff --git a/state-machines/arith/src/arith_table_helpers.rs b/state-machines/arith/src/arith_table_helpers.rs index 336d9ab14..d9dda0c4f 100644 --- a/state-machines/arith/src/arith_table_helpers.rs +++ b/state-machines/arith/src/arith_table_helpers.rs @@ -73,6 +73,7 @@ impl ArithTableHelpers { /// Retrieves the row index during testing (optimized for release mode). #[cfg(not(debug_assertions))] #[cfg(test)] + #[allow(clippy::too_many_arguments)] pub fn get_row( op: u8, na: bool, diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 186044880..b7534f9c9 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -68,7 +68,7 @@ macro_rules! hint_fields { fn read_field_bytes<'a>(data: &'a [u64], pos: &mut usize) -> anyhow::Result<(&'a [u8], usize)> { // Treat the entire u64 slice as bytes let byte_data: &[u8] = unsafe { - std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * size_of::()) + std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) }; // Make sure we have at least 8 bytes for the length header diff --git a/ziskos/entrypoint/src/syscalls/keccakf.rs b/ziskos/entrypoint/src/syscalls/keccakf.rs index 2addd2fa6..2a1ee848f 100644 --- a/ziskos/entrypoint/src/syscalls/keccakf.rs +++ b/ziskos/entrypoint/src/syscalls/keccakf.rs @@ -23,7 +23,7 @@ use tiny_keccak::keccakf; #[allow(unused_variables)] #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_syscall_keccak_f")] -pub extern "C" fn syscall_keccak_f( +pub unsafe extern "C" fn syscall_keccak_f( state: *mut [u64; 25], #[cfg(feature = "hints")] hints: &mut Vec, ) { diff --git a/ziskos/entrypoint/src/syscalls/poseidon2.rs b/ziskos/entrypoint/src/syscalls/poseidon2.rs index ab64e83b4..63c93a8f8 100644 --- a/ziskos/entrypoint/src/syscalls/poseidon2.rs +++ b/ziskos/entrypoint/src/syscalls/poseidon2.rs @@ -23,7 +23,7 @@ use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; #[allow(unused_variables)] #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_syscall_poseidon2")] -pub extern "C" fn syscall_poseidon2( +pub unsafe extern "C" fn syscall_poseidon2( state: *mut [u64; 16], #[cfg(feature = "hints")] hints: &mut Vec, ) { diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index a348a3ecb..a929cb1be 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -20,11 +20,13 @@ pub fn keccak256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) - // XOR block into state xor_block_into_state(&mut state, &input[offset..offset + KECCAK256_RATE]); // Apply Keccak-f permutation - syscall_keccak_f( - &mut state, - #[cfg(feature = "hints")] - hints, - ); + unsafe { + syscall_keccak_f( + &mut state, + #[cfg(feature = "hints")] + hints, + ); + } offset += KECCAK256_RATE; } @@ -44,11 +46,13 @@ pub fn keccak256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) - xor_block_into_state(&mut state, &final_block); // Final permutation - syscall_keccak_f( - &mut state, - #[cfg(feature = "hints")] - hints, - ); + unsafe { + syscall_keccak_f( + &mut state, + #[cfg(feature = "hints")] + hints, + ); + } // Squeeze phase: extract first 32 bytes (256 bits) from state let mut result = [0u8; 32]; From 4fc1f437a7244ca700cac34fcc91435dc31ecd95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:00:50 +0000 Subject: [PATCH 386/782] Fixing minor bugs in ecdsa --- .../src/zisklib/lib/secp256r1/constants.rs | 11 +++ .../src/zisklib/lib/secp256r1/ecdsa.rs | 78 ++++++++++++------- .../src/zisklib/lib/secp256r1/scalar.rs | 23 +++++- 3 files changed, 84 insertions(+), 28 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/constants.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/constants.rs index cd3e3df1a..c30b324cc 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256r1/constants.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/constants.rs @@ -9,6 +9,7 @@ pub const E_B: [u64; 4] = /// Secp256r1 base field size pub const P: [u64; 4] = [0xFFFF_FFFF_FFFF_FFFF, 0x0000_0000_FFFF_FFFF, 0x0000_0000_0000_0000, 0xFFFF_FFFF_0000_0001]; +pub const P_MINUS_ONE: [u64; 4] = [P[0] - 1, P[1], P[2], P[3]]; /// Secp256r1 scalar field size pub const N: [u64; 4] = @@ -18,6 +19,16 @@ pub const N_MINUS_ONE: [u64; 4] = [N[0] - 1, N[1], N[2], N[3]]; /// Secp256r1 group identity point pub const IDENTITY_X: [u64; 4] = [0; 4]; pub const IDENTITY_Y: [u64; 4] = [0; 4]; +pub const IDENTITY: [u64; 8] = [ + IDENTITY_X[0], + IDENTITY_X[1], + IDENTITY_X[2], + IDENTITY_X[3], + IDENTITY_Y[0], + IDENTITY_Y[1], + IDENTITY_Y[2], + IDENTITY_Y[3], +]; /// Secp256r1 group of points generator pub const G_X: [u64; 4] = diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs index 59aa5d84b..3efd039b9 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs @@ -1,57 +1,81 @@ -use crate::{ - syscalls::{ - syscall_secp256r1_add, syscall_secp256r1_dbl, SyscallPoint256, SyscallSecp256r1AddParams, - }, - zisklib::{ - eq, fcall_msb_pos_256, fcall_secp256r1_ecdsa_verify, is_one, ONE_256, TWO_256, ZERO_256, - }, -}; +use crate::zisklib::{eq, fcall_secp256r1_ecdsa_verify, gt, is_zero}; use super::{ - constants::{E_B, G_X, G_Y, IDENTITY_X, IDENTITY_Y}, + constants::{IDENTITY, N_MINUS_ONE, P_MINUS_ONE}, curve::{secp256r1_is_on_curve, secp256r1_triple_scalar_mul_with_g}, - field::{secp256r1_fp_add, secp256r1_fp_mul, secp256r1_fp_square}, - scalar::secp256r1_fn_neg, + scalar::{secp256r1_fn_neg, secp256r1_fn_reduce}, }; /// Verifies the signature (r, s) over the message hash z using the public key pk /// Returns true if the signature is valid, false otherwise pub fn secp256r1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u64; 4]) -> bool { + // r and s must be in the range [1, n-1] + if is_zero(r) || gt(r, &N_MINUS_ONE) { + return false; + } + if is_zero(s) || gt(s, &N_MINUS_ONE) { + return false; + } + + // pk must not be the identity point + if eq(pk, &IDENTITY) { + return false; + } + + // pk must be a valid curve point + let pk_x: [u64; 4] = [pk[0], pk[1], pk[2], pk[3]]; + let pk_y: [u64; 4] = [pk[4], pk[5], pk[6], pk[7]]; + if gt(&pk_x, &P_MINUS_ONE) || gt(&pk_y, &P_MINUS_ONE) { + return false; + } + if !secp256r1_is_on_curve(pk) { + return false; + } + // Ecdsa verification computes (x, y) = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]PK // and checks that x ≡ r (mod n) - // We can equivalently hint y, and verify that - // [z]G + [r]PK + [-s](r,y) == 𝒪, - // saving us from expensive fn arithmetic + // We can equivalently hint (x,y), verify that + // [z]G + [r]PK + [-s](x,y) == 𝒪, + // and ensure that x ≡ r (mod n), saving us from expensive fn arithmetic // Hint the result - let coords = fcall_secp256r1_ecdsa_verify(pk, z, r, s); - let point = [r[0], r[1], r[2], r[3], coords[4], coords[5], coords[6], coords[7]]; + let point = fcall_secp256r1_ecdsa_verify(pk, z, r, s); // Check the recovered point is valid - assert!(secp256r1_is_on_curve(&point)); // Note: Identity point would be raised here + // Note: Identity point would be raised here + if !secp256r1_is_on_curve(&point) { + return false; + } - // Check that [z]G + [r]PK + [-s](r,y) == 𝒪 + // Check that [z]G + [r]PK + [-s](x,y) == 𝒪 let neg_s = secp256r1_fn_neg(s); - secp256r1_triple_scalar_mul_with_g(z, r, &neg_s, pk, &point).is_none() + if secp256r1_triple_scalar_mul_with_g(z, r, &neg_s, pk, &point).is_some() { + return false; + } + + // Check that x ≡ r (mod n) + let point_x: [u64; 4] = [point[0], point[1], point[2], point[3]]; + eq(&secp256r1_fn_reduce(&point_x), r) } // ==================== C FFI Functions ==================== /// # Safety +/// - `msg_ptr` must point to 4 u64s +/// - `sig_ptr` must point to 8 u64s /// - `pk_ptr` must point to 8 u64s -/// - `z_ptr`, `r_ptr`, `s_ptr` must point to 4 u64s each /// /// Returns true if signature is valid #[no_mangle] pub unsafe extern "C" fn secp256r1_ecdsa_verify_c( + msg_ptr: *const u64, + sig_ptr: *const u64, pk_ptr: *const u64, - z_ptr: *const u64, - r_ptr: *const u64, - s_ptr: *const u64, ) -> bool { + let msg: &[u64; 4] = &*(msg_ptr as *const [u64; 4]); + let sig: &[u64; 8] = &*(sig_ptr as *const [u64; 8]); let pk: &[u64; 8] = &*(pk_ptr as *const [u64; 8]); - let z: &[u64; 4] = &*(z_ptr as *const [u64; 4]); - let r: &[u64; 4] = &*(r_ptr as *const [u64; 4]); - let s: &[u64; 4] = &*(s_ptr as *const [u64; 4]); - secp256r1_ecdsa_verify(pk, z, r, s) + let r: &[u64; 4] = &sig[0..4].try_into().unwrap(); + let s: &[u64; 4] = &sig[4..8].try_into().unwrap(); + secp256r1_ecdsa_verify(pk, msg, r, s) } diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs index 0da1add5a..46bf8e5ca 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs @@ -1,7 +1,28 @@ -use crate::syscalls::{syscall_arith256_mod, SyscallArith256ModParams}; +use crate::{ + syscalls::{syscall_arith256_mod, SyscallArith256ModParams}, + zisklib::lt, +}; use super::constants::{N, N_MINUS_ONE}; +pub fn secp256r1_fn_reduce(x: &[u64; 4]) -> [u64; 4] { + if lt(x, &N) { + return *x; + } + + // x·1 + 0 + let mut params = SyscallArith256ModParams { + a: x, + b: &[1, 0, 0, 0], + c: &[0, 0, 0, 0], + module: &N, + d: &mut [0, 0, 0, 0], + }; + syscall_arith256_mod(&mut params); + + *params.d +} + pub fn secp256r1_fn_neg(x: &[u64; 4]) -> [u64; 4] { // x·(-1) + 0 let mut params = SyscallArith256ModParams { From 1257214e4aff669a4ce07ca35091085c4494ec52 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:17:22 +0000 Subject: [PATCH 387/782] fix modexp length check --- ziskos-hints/src/handlers/modexp.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ziskos-hints/src/handlers/modexp.rs b/ziskos-hints/src/handlers/modexp.rs index 02f87c019..c7578576f 100644 --- a/ziskos-hints/src/handlers/modexp.rs +++ b/ziskos-hints/src/handlers/modexp.rs @@ -10,11 +10,15 @@ pub fn modexp_hint(data: &[u64]) -> Result> { let (exp, exp_len) = read_field_bytes(data, &mut pos)?; let (modulus, modulus_len) = read_field_bytes(data, &mut pos)?; - // Check that the data length fits the expected length. - // Each length prefix is 8 bytes (1 u64), so total prefix size is 3 * 8 = 24 bytes. - // The total expected length in bytes is 24 + base_len + exp_len + modulus_len. - if (24 + base_len + exp_len + modulus_len).div_ceil(8) > data.len() * 8 { - anyhow::bail!("MODEXP hint data too short"); + // Verify the data length matches: 3 length prefixes (8 bytes each) + field data, + // converted to u64 words (rounded up for alignment). + let expected_words = (24 + base_len + exp_len + modulus_len).div_ceil(8); + if expected_words != data.len() { + anyhow::bail!( + "MODEXP hint data length mismatch: expected {} words, got {} words", + expected_words, + data.len() + ); } let mut hints = Vec::new(); From da5ae2d22cf200a137a0601335aac1634090a413 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 2 Feb 2026 12:02:37 +0100 Subject: [PATCH 388/782] Fix secp256r1 double function in lib-c --- lib-c/c/src/secp256r1/secp256r1.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib-c/c/src/secp256r1/secp256r1.cpp b/lib-c/c/src/secp256r1/secp256r1.cpp index adeb28a73..3eacc7a2d 100644 --- a/lib-c/c/src/secp256r1/secp256r1.cpp +++ b/lib-c/c/src/secp256r1/secp256r1.cpp @@ -30,9 +30,10 @@ int inline secp256r1_add_point_ec_fe (bool dbl, const RawpSecp256r1::Element &x1 if (dbl) { - // s = 3*x1*x1/2*y1 + // s = (3*x1*x1 + (p-3))/2*y1 = 3*(x1^2 - 1)/2*y1 secp256r1.mul(aux1, x1, x1); secp256r1.fromUI(aux2, 3); + secp256r1.add(aux1, aux1, secp256r1.negOne()); secp256r1.mul(aux1, aux1, aux2); secp256r1.add(aux2, y1, y1); if (secp256r1.isZero(aux2)) From c69f636a46d155fccf9a6a3d1c1865c015cf2fbb Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 2 Feb 2026 12:19:36 +0100 Subject: [PATCH 389/782] Fix secp256r1 fcall --- lib-c/c/src/secp256r1/secp256r1.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib-c/c/src/secp256r1/secp256r1.cpp b/lib-c/c/src/secp256r1/secp256r1.cpp index 3eacc7a2d..2d4d22b31 100644 --- a/lib-c/c/src/secp256r1/secp256r1.cpp +++ b/lib-c/c/src/secp256r1/secp256r1.cpp @@ -372,6 +372,7 @@ void secp256r1_curve_dbl( // Calculate lambda = (3*x1^2) / (2*y1) RawpSecp256r1::Element x1_sq; secp256r1.square(x1_sq, x_fe); + secp256r1.add(x1_sq, x1_sq, secp256r1.negOne()); RawpSecp256r1::Element three; secp256r1.fromUI(three, 3); RawpSecp256r1::Element three_x1_sq; From eed31f1e4a293691bafe8e38392abc9ca524327a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:26:19 +0000 Subject: [PATCH 390/782] fix tests --- common/src/io/stream/quic.rs | 124 +++++++++++++++++------ precompiles/hints/src/hints_processor.rs | 84 +++++++++------ 2 files changed, 149 insertions(+), 59 deletions(-) diff --git a/common/src/io/stream/quic.rs b/common/src/io/stream/quic.rs index eb2810f99..84aba2ca5 100644 --- a/common/src/io/stream/quic.rs +++ b/common/src/io/stream/quic.rs @@ -361,6 +361,7 @@ impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { #[cfg(test)] mod tests { use super::*; + use std::sync::mpsc; use std::thread; use std::time::Duration; @@ -373,26 +374,35 @@ mod tests { }); } - #[test] - fn test_single_message() { + #[tokio::test(flavor = "multi_thread")] + async fn test_single_message() { init_crypto(); let server_addr: SocketAddr = "127.0.0.1:15001".parse().unwrap(); + // Channel to signal when writer has written data + let (tx, rx) = mpsc::channel(); + // Spawn writer (server) thread let writer_thread = thread::spawn(move || { let mut writer = QuicStreamWriter::new(server_addr).unwrap(); writer.write(b"Hello, QUIC!").unwrap(); + tx.send(()).unwrap(); // Signal that data is written // Wait for reader to finish before closing - thread::sleep(Duration::from_millis(200)); + thread::sleep(Duration::from_millis(500)); writer.close().unwrap(); }); // Give writer time to start listening - thread::sleep(Duration::from_millis(500)); + thread::sleep(Duration::from_millis(100)); - // Reader connects and reads message + // Reader connects (this triggers the writer's connection accept) let mut reader = QuicStreamReader::new(server_addr).unwrap(); + reader.open().unwrap(); // Explicitly connect + + // Wait for writer to have written data + rx.recv_timeout(Duration::from_secs(5)).unwrap(); + let message = reader.next().unwrap().unwrap(); assert_eq!(message, b"Hello, QUIC!"); reader.close().unwrap(); @@ -400,26 +410,35 @@ mod tests { writer_thread.join().unwrap(); } - #[test] - fn test_multiple_messages() { + #[tokio::test(flavor = "multi_thread")] + async fn test_multiple_messages() { init_crypto(); let server_addr: SocketAddr = "127.0.0.1:15002".parse().unwrap(); + // Channel to signal when writer has written data + let (tx, rx) = mpsc::channel(); + // Spawn writer (server) thread let writer_thread = thread::spawn(move || { let mut writer = QuicStreamWriter::new(server_addr).unwrap(); writer.write(b"First").unwrap(); writer.write(b"Second message").unwrap(); writer.write(b"Third message with more data!").unwrap(); + tx.send(()).unwrap(); // Signal that data is written thread::sleep(Duration::from_millis(200)); writer.close().unwrap(); }); - thread::sleep(Duration::from_millis(500)); + thread::sleep(Duration::from_millis(100)); - // Reader connects and reads messages + // Reader connects let mut reader = QuicStreamReader::new(server_addr).unwrap(); + reader.open().unwrap(); // Explicitly connect + + // Wait for writer to have written data + rx.recv_timeout(Duration::from_secs(5)).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); assert_eq!(msg1, b"First"); let msg2 = reader.next().unwrap().unwrap(); @@ -431,25 +450,34 @@ mod tests { writer_thread.join().unwrap(); } - #[test] - fn test_message_boundaries() { + #[tokio::test(flavor = "multi_thread")] + async fn test_message_boundaries() { init_crypto(); let server_addr: SocketAddr = "127.0.0.1:15003".parse().unwrap(); + // Channel to signal when writer has written data + let (tx, rx) = mpsc::channel(); + // Spawn writer (server) thread let writer_thread = thread::spawn(move || { let mut writer = QuicStreamWriter::new(server_addr).unwrap(); writer.write(b"ABC").unwrap(); writer.write(b"DEF").unwrap(); + tx.send(()).unwrap(); // Signal that data is written thread::sleep(Duration::from_millis(200)); writer.close().unwrap(); }); - thread::sleep(Duration::from_millis(500)); + thread::sleep(Duration::from_millis(100)); - // Reader should receive each message as discrete unit + // Reader connects let mut reader = QuicStreamReader::new(server_addr).unwrap(); + reader.open().unwrap(); // Explicitly connect + + // Wait for writer to have written data + rx.recv_timeout(Duration::from_secs(5)).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); assert_eq!(msg1, b"ABC"); let msg2 = reader.next().unwrap().unwrap(); @@ -460,8 +488,8 @@ mod tests { writer_thread.join().unwrap(); } - #[test] - fn test_large_message() { + #[tokio::test(flavor = "multi_thread")] + async fn test_large_message() { init_crypto(); let server_addr: SocketAddr = "127.0.0.1:15004".parse().unwrap(); @@ -469,19 +497,28 @@ mod tests { let large_data: Vec = (0..1024 * 1024).map(|i| (i % 256) as u8).collect(); let large_data_clone = large_data.clone(); + // Channel to signal when writer has written data + let (tx, rx) = mpsc::channel(); + // Spawn writer (server) thread let writer_thread = thread::spawn(move || { let mut writer = QuicStreamWriter::new(server_addr).unwrap(); writer.write(&large_data).unwrap(); + tx.send(()).unwrap(); // Signal that data is written thread::sleep(Duration::from_millis(200)); writer.close().unwrap(); }); - thread::sleep(Duration::from_millis(500)); + thread::sleep(Duration::from_millis(100)); - // Reader receives large message + // Reader connects let mut reader = QuicStreamReader::new(server_addr).unwrap(); + reader.open().unwrap(); // Explicitly connect + + // Wait for writer to have written data + rx.recv_timeout(Duration::from_secs(5)).unwrap(); + let message = reader.next().unwrap().unwrap(); assert_eq!(message, large_data_clone); reader.close().unwrap(); @@ -489,24 +526,33 @@ mod tests { writer_thread.join().unwrap(); } - #[test] - fn test_connection_close() { + #[tokio::test(flavor = "multi_thread")] + async fn test_connection_close() { init_crypto(); let server_addr: SocketAddr = "127.0.0.1:15005".parse().unwrap(); + // Channel to signal when writer has written data + let (tx, rx) = mpsc::channel(); + // Spawn writer (server) thread let writer_thread = thread::spawn(move || { let mut writer = QuicStreamWriter::new(server_addr).unwrap(); writer.write(b"Message").unwrap(); + tx.send(()).unwrap(); // Signal that data is written thread::sleep(Duration::from_millis(200)); writer.close().unwrap(); }); - thread::sleep(Duration::from_millis(500)); + thread::sleep(Duration::from_millis(100)); - // Reader receives message and closes + // Reader connects let mut reader = QuicStreamReader::new(server_addr).unwrap(); + reader.open().unwrap(); // Explicitly connect + + // Wait for writer to have written data + rx.recv_timeout(Duration::from_secs(5)).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); assert_eq!(msg1, b"Message"); reader.close().unwrap(); @@ -514,26 +560,35 @@ mod tests { writer_thread.join().unwrap(); } - #[test] - fn test_multiple_concurrent_messages() { + #[tokio::test(flavor = "multi_thread")] + async fn test_multiple_concurrent_messages() { init_crypto(); let server_addr: SocketAddr = "127.0.0.1:15006".parse().unwrap(); + // Channel to signal when writer has written data + let (tx, rx) = mpsc::channel(); + // Spawn writer (server) thread let writer_thread = thread::spawn(move || { let mut writer = QuicStreamWriter::new(server_addr).unwrap(); for i in 0..10 { writer.write(format!("Message {}", i).as_bytes()).unwrap(); } + tx.send(()).unwrap(); // Signal that data is written thread::sleep(Duration::from_millis(200)); writer.close().unwrap(); }); - thread::sleep(Duration::from_millis(500)); + thread::sleep(Duration::from_millis(100)); - // Reader receives 10 messages in quick succession + // Reader connects let mut reader = QuicStreamReader::new(server_addr).unwrap(); + reader.open().unwrap(); // Explicitly connect + + // Wait for writer to have written data + rx.recv_timeout(Duration::from_secs(5)).unwrap(); + for i in 0..10 { let msg = reader.next().unwrap().unwrap(); assert_eq!(msg, format!("Message {}", i).as_bytes()); @@ -543,25 +598,34 @@ mod tests { writer_thread.join().unwrap(); } - #[test] - fn test_writer_closes_early() { + #[tokio::test(flavor = "multi_thread")] + async fn test_writer_closes_early() { init_crypto(); let server_addr: SocketAddr = "127.0.0.1:15007".parse().unwrap(); + // Channel to signal when writer has written data + let (tx, rx) = mpsc::channel(); + // Spawn writer (server) thread that closes after writing one message let writer_thread = thread::spawn(move || { let mut writer = QuicStreamWriter::new(server_addr).unwrap(); writer.write(b"First").unwrap(); + tx.send(()).unwrap(); // Signal that data is written - // Writer closes immediately + // Writer closes after a short delay thread::sleep(Duration::from_millis(100)); writer.close().unwrap(); }); - thread::sleep(Duration::from_millis(500)); + thread::sleep(Duration::from_millis(100)); - // Reader receives first message successfully + // Reader connects let mut reader = QuicStreamReader::new(server_addr).unwrap(); + reader.open().unwrap(); // Explicitly connect + + // Wait for writer to have written data + rx.recv_timeout(Duration::from_secs(5)).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); assert_eq!(msg1, b"First"); diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 7567937c4..792fc2d16 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -147,6 +147,7 @@ impl HintsProcessorBuilder { hints_sink, drainer_thread: ManuallyDrop::new(drainer_thread), custom_handlers: Arc::new(self.custom_handlers), + stream_active: AtomicBool::new(false), instant: Mutex::new(None), }) } @@ -179,6 +180,10 @@ pub struct HintsProcessor { /// Custom hint handlers registered by the user custom_handlers: Arc>, + /// Tracks whether a stream is currently active (between CTRL_START and CTRL_END) + stream_active: AtomicBool, + + /// Timestamp of when the current stream started (for performance metrics) instant: Mutex>, } @@ -301,12 +306,21 @@ impl HintsProcessor { } // Reset global sequence and buffer at stream start self.reset(); + // Mark stream as active + self.stream_active.store(true, Ordering::Release); // Control hint only; skip processing idx += length; *self.instant.lock().unwrap() = Some(Instant::now()); continue; } HintCode::Ctrl(CtrlHint::End) => { + // CTRL_END requires a prior CTRL_START + if !self.stream_active.swap(false, Ordering::AcqRel) { + return Err(anyhow::anyhow!( + "CTRL_END received without a prior CTRL_START" + )); + } + // Control hint only; wait for completion then set flag self.wait_for_completion()?; has_ctrl_end = true; @@ -731,7 +745,8 @@ mod tests { #[test] fn test_single_result_hint_non_blocking() { let p = processor(); - let data = vec![make_header(TEST_PASSTHROUGH_HINT, 2), 0x111, 0x222]; + // length=16 means 16 bytes = 2 u64s of data + let data = vec![make_header(TEST_PASSTHROUGH_HINT, 16), 0x111, 0x222]; // Dispatch should succeed and be non-blocking assert!(p.process_hints(&data, false).is_ok()); @@ -747,12 +762,13 @@ mod tests { #[test] fn test_multiple_hints_ordered_output() { let p = processor(); + // length is in bytes: 8 bytes = 1 u64 let data = vec![ - make_header(TEST_PASSTHROUGH_HINT, 1), + make_header(TEST_PASSTHROUGH_HINT, 8), 0x111, - make_header(TEST_PASSTHROUGH_HINT, 1), + make_header(TEST_PASSTHROUGH_HINT, 8), 0x222, - make_header(TEST_PASSTHROUGH_HINT, 1), + make_header(TEST_PASSTHROUGH_HINT, 8), 0x333, ]; assert!(p.process_hints(&data, false).is_ok()); @@ -767,8 +783,9 @@ mod tests { #[test] fn test_multiple_calls_global_sequence() { let p = processor(); - let data1 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0xAAA]; - let data2 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0xBBB]; + // length is in bytes: 8 bytes = 1 u64 + let data1 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0xAAA]; + let data2 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0xBBB]; assert!(p.process_hints(&data1, false).is_ok()); assert!(p.process_hints(&data2, false).is_ok()); @@ -796,7 +813,8 @@ mod tests { #[test] fn test_unknown_hint_type_returns_error() { let p = processor(); - let data = vec![make_header(999, 1), 0x1234]; + // length is in bytes: 8 bytes = 1 u64 + let data = vec![make_header(999, 8), 0x1234]; // Should return error immediately during validation let result = p.process_hints(&data, false); @@ -807,8 +825,8 @@ mod tests { #[test] fn test_error_stops_wait() { let p = processor(); - // First valid, then invalid type - let data = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x111, make_header(999, 0)]; + // First valid (8 bytes = 1 u64), then invalid type with 0 bytes + let data = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x111, make_header(999, 0)]; // Should error immediately when encountering invalid hint type let result = p.process_hints(&data, false); @@ -830,8 +848,8 @@ mod tests { p.reset(); assert!(!p.state.error_flag.load(Ordering::Acquire)); - // Should be able to process new hints after reset - let good = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x42]; + // Should be able to process new hints after reset (8 bytes = 1 u64) + let good = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x42]; assert!(p.process_hints(&good, false).is_ok()); assert!(p.wait_for_completion().is_ok()); @@ -844,8 +862,8 @@ mod tests { fn test_stream_start_resets_state() { let p = processor(); - // First batch increments sequence - let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x01]; + // First batch increments sequence (8 bytes = 1 u64) + let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x01]; p.process_hints(&batch1, false).unwrap(); p.wait_for_completion().unwrap(); @@ -866,8 +884,8 @@ mod tests { assert!(queue.buffer.is_empty()); } - // Process new batch - let batch2 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x02]; + // Process new batch (8 bytes = 1 u64) + let batch2 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x02]; p.process_hints(&batch2, false).unwrap(); let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::End).to_u32(), 0)]; @@ -882,11 +900,15 @@ mod tests { fn test_stream_end_waits_until_completion() { let p = processor(); - // Dispatch hints + // Send CTRL_START first (required before CTRL_END) + let start = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::Start).to_u32(), 0)]; + p.process_hints(&start, true).unwrap(); + + // Dispatch hints (8 bytes = 1 u64 each) let data = vec![ - make_header(TEST_PASSTHROUGH_HINT, 1), + make_header(TEST_PASSTHROUGH_HINT, 8), 0x10, - make_header(TEST_PASSTHROUGH_HINT, 1), + make_header(TEST_PASSTHROUGH_HINT, 8), 0x20, ]; p.process_hints(&data, false).unwrap(); @@ -936,9 +958,9 @@ mod tests { fn test_ctrl_start_must_be_first_in_batch() { let p = processor(); - // CTRL_START not at position 0 should fail + // CTRL_START not at position 0 should fail (8 bytes = 1 u64) let data = vec![ - make_header(TEST_PASSTHROUGH_HINT, 1), + make_header(TEST_PASSTHROUGH_HINT, 8), 0x42, make_ctrl_header(HintCode::Ctrl(CtrlHint::Start).to_u32(), 0), ]; @@ -952,8 +974,8 @@ mod tests { fn test_ctrl_start_only_in_first_batch() { let p = processor(); - // First batch is ok - let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x01]; + // First batch is ok (8 bytes = 1 u64) + let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x01]; p.process_hints(&batch1, false).unwrap(); // CTRL_START in non-first batch should fail @@ -967,10 +989,14 @@ mod tests { fn test_ctrl_end_must_be_last() { let p = processor(); - // CTRL_END not at end should fail + // Send CTRL_START first (required before CTRL_END) + let start = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::Start).to_u32(), 0)]; + p.process_hints(&start, true).unwrap(); + + // CTRL_END not at end should fail (8 bytes = 1 u64) let data = vec![ make_ctrl_header(HintCode::Ctrl(CtrlHint::End).to_u32(), 0), - make_header(TEST_PASSTHROUGH_HINT, 1), + make_header(TEST_PASSTHROUGH_HINT, 8), 0x42, ]; @@ -998,12 +1024,12 @@ mod tests { let sink = RecordingSink { received: Arc::clone(&received) }; let p = HintsProcessor::builder(sink).num_threads(2).build().unwrap(); - // Send some data + // Send some data (16 bytes = 2 u64s, 8 bytes = 1 u64) let data = vec![ - make_header(TEST_PASSTHROUGH_HINT, 2), + make_header(TEST_PASSTHROUGH_HINT, 16), 0xAAA, 0xBBB, - make_header(TEST_PASSTHROUGH_HINT, 1), + make_header(TEST_PASSTHROUGH_HINT, 8), 0xCCC, ]; @@ -1040,8 +1066,8 @@ mod tests { let sink = FailingSink { should_fail: Arc::clone(&should_fail) }; let p = HintsProcessor::builder(sink).num_threads(2).build().unwrap(); - // First batch succeeds - let data1 = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x01]; + // First batch succeeds (8 bytes = 1 u64) + let data1 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x01]; assert!(p.process_hints(&data1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); From 88a59de0b301ee4d4a125fe178d28423b8e076b4 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:36:11 +0000 Subject: [PATCH 391/782] minor fixes --- .../grpc-api/proto/zisk_distributed_api.proto | 16 ++++++++-------- ziskos/entrypoint/src/zisklib/lib/keccak256.rs | 9 --------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index ba615687c..a06f28c5b 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -63,16 +63,16 @@ message LaunchProofRequest { optional uint32 simulated_node = 8; // If set, indicates this is a simulated worker } - enum InputMode { - INPUT_MODE_NONE = 0; // No input provided - INPUT_MODE_PATH = 1; // Input will be provided as a PATH - INPUT_MODE_DATA = 2; // Input data will be sent directly +enum InputMode { + INPUT_MODE_NONE = 0; // No input provided + INPUT_MODE_PATH = 1; // Input will be provided as a PATH + INPUT_MODE_DATA = 2; // Input data will be sent directly } - enum HintsMode { - HINTS_MODE_NONE = 0; // No hints provided - HINTS_MODE_PATH = 1; // Hints will be provided as a PATH - HINTS_MODE_STREAM = 2; // Hints will be sent as a stream +enum HintsMode { + HINTS_MODE_NONE = 0; // No hints provided + HINTS_MODE_PATH = 1; // Hints will be provided as a PATH + HINTS_MODE_STREAM = 2; // Hints will be sent as a stream } diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index a929cb1be..4654f0145 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -108,7 +108,6 @@ pub unsafe extern "C" fn native_keccak256( bytes: *const u8, len: usize, output: *mut u8, - #[cfg(feature = "hints")] hints: &mut Vec, ) { #[cfg(zisk_hints)] crate::hints::hint_keccak256(bytes, len); @@ -144,13 +143,5 @@ pub unsafe extern "C" fn native_keccak256( let mut hasher = Keccak::v256(); hasher.update(input_bytes); hasher.finalize(out); - - #[cfg(feature = "hints")] - { - const OUT_LEN_WORDS: usize = OUT_LEN / std::mem::size_of::(); - let out_u64: &[u64] = - unsafe { core::slice::from_raw_parts(out.as_ptr() as *const u64, OUT_LEN_WORDS) }; - hints.extend_from_slice(out_u64); - } } } From 036d10588833f85c743316394666c001ab027158 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:52:25 +0000 Subject: [PATCH 392/782] cargo fmt --- ziskos/entrypoint/src/zisklib/lib/keccak256.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index 4654f0145..fddf5d6da 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -104,11 +104,7 @@ pub unsafe extern "C" fn keccak256_c( /// - `output` must point to a writable buffer of at least 32 bytes #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_native_keccak256_c")] -pub unsafe extern "C" fn native_keccak256( - bytes: *const u8, - len: usize, - output: *mut u8, -) { +pub unsafe extern "C" fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8) { #[cfg(zisk_hints)] crate::hints::hint_keccak256(bytes, len); From f57f4b0d30bf015c0fc09e9730fdd45c23858b25 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:06:18 +0000 Subject: [PATCH 393/782] minor fix in native_keccak256 --- ziskos/entrypoint/src/zisklib/lib/keccak256.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index fddf5d6da..739941c5b 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -116,13 +116,7 @@ pub unsafe extern "C" fn native_keccak256(bytes: *const u8, len: usize, output: #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { - keccak256_c( - bytes, - len, - output, - #[cfg(feature = "hints")] - hints, - ); + keccak256_c(bytes, len, output); } #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] From d19678bb2518063a6c66b6e43c8b6188cdf5caea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:30:40 +0000 Subject: [PATCH 394/782] Integrating with thints --- .../entrypoint/src/syscalls/secp256r1_add.rs | 21 +- .../entrypoint/src/syscalls/secp256r1_dbl.rs | 20 +- .../src/zisklib/fcalls/secp256r1/ecdsa.rs | 25 ++- .../entrypoint/src/zisklib/lib/keccak256.rs | 6 +- .../src/zisklib/lib/secp256r1/curve.rs | 192 +++++++++++++++--- .../src/zisklib/lib/secp256r1/ecdsa.rs | 69 ++++++- .../src/zisklib/lib/secp256r1/field.rs | 37 +++- .../src/zisklib/lib/secp256r1/scalar.rs | 19 +- 8 files changed, 327 insertions(+), 62 deletions(-) diff --git a/ziskos/entrypoint/src/syscalls/secp256r1_add.rs b/ziskos/entrypoint/src/syscalls/secp256r1_add.rs index 00d83afea..10d3d6361 100644 --- a/ziskos/entrypoint/src/syscalls/secp256r1_add.rs +++ b/ziskos/entrypoint/src/syscalls/secp256r1_add.rs @@ -33,10 +33,25 @@ pub struct SyscallSecp256r1AddParams<'a> { /// /// The resulting point will have both coordinates in the range of the Secp256r1 base field. #[allow(unused_variables)] -#[no_mangle] -pub extern "C" fn syscall_secp256r1_add(params: &mut SyscallSecp256r1AddParams) { +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_secp256r1_add")] +pub extern "C" fn syscall_secp256r1_add( + params: &mut SyscallSecp256r1AddParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x815, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let p1 = [params.p1.x, params.p1.y].concat().try_into().unwrap(); + let p2 = [params.p2.x, params.p2.y].concat().try_into().unwrap(); + let mut p3: [u64; 8] = [0; 8]; + precompiles_helpers::secp256r1_add(&p1, &p2, &mut p3); + params.p1.x.copy_from_slice(&p3[0..4]); + params.p1.y.copy_from_slice(&p3[4..8]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&p3); + } + } } diff --git a/ziskos/entrypoint/src/syscalls/secp256r1_dbl.rs b/ziskos/entrypoint/src/syscalls/secp256r1_dbl.rs index b8514c1b6..8f492cb1d 100644 --- a/ziskos/entrypoint/src/syscalls/secp256r1_dbl.rs +++ b/ziskos/entrypoint/src/syscalls/secp256r1_dbl.rs @@ -25,10 +25,24 @@ use super::point::SyscallPoint256; /// /// The resulting point will have both coordinates in the range of the Secp256r1 base field. #[allow(unused_variables)] -#[no_mangle] -pub extern "C" fn syscall_secp256r1_dbl(p1: &mut SyscallPoint256) { +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_secp256r1_dbl")] +pub extern "C" fn syscall_secp256r1_dbl( + p1: &mut SyscallPoint256, + #[cfg(feature = "hints")] hints: &mut Vec, +) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] ziskos_syscall!(0x816, p1); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!() + { + let _p1 = [p1.x, p1.y].concat().try_into().unwrap(); + let mut p3: [u64; 8] = [0; 8]; + precompiles_helpers::secp256r1_dbl(&_p1, &mut p3); + p1.x.copy_from_slice(&p3[0..4]); + p1.y.copy_from_slice(&p3[4..8]); + #[cfg(feature = "hints")] + { + hints.extend_from_slice(&p3); + } + } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs index d2757d58b..947a9eb2a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs @@ -42,9 +42,32 @@ pub fn fcall_secp256r1_ecdsa_verify( z_value: &[u64; 4], r_value: &[u64; 4], s_value: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> [u64; 8] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); + { + use crate::zisklib::fcalls_impl; + + // Convert inputs into a single params array + let mut params: [u64; 20] = [0u64; 20]; + params[0..8].copy_from_slice(pk_value); + params[8..12].copy_from_slice(z_value); + params[12..16].copy_from_slice(r_value); + params[16..20].copy_from_slice(s_value); + + // Call the implementation + let mut results = [0u64; 8]; + fcalls_impl::secp256r1::fcall_secp256r1_ecdsa_verify(¶ms, &mut results); + + // Hint the result + #[cfg(feature = "hints")] + { + hints.push(results.len() as u64); + hints.extend_from_slice(&results); + } + + results + } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { ziskos_fcall_param!(pk_value, 8); diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index 4654f0145..fddf5d6da 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -104,11 +104,7 @@ pub unsafe extern "C" fn keccak256_c( /// - `output` must point to a writable buffer of at least 32 bytes #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_native_keccak256_c")] -pub unsafe extern "C" fn native_keccak256( - bytes: *const u8, - len: usize, - output: *mut u8, -) { +pub unsafe extern "C" fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8) { #[cfg(zisk_hints)] crate::hints::hint_keccak256(bytes, len); diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs index 8e7281c34..38ad31614 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs @@ -18,13 +18,25 @@ const G_POINT256: SyscallPoint256 = SyscallPoint256 { x: G_X, y: G_Y }; /// It assumes that `p1` and `p2` are from the Secp256r1 curve, that `p1,p2 != 𝒪` /// Returns true if the result is the point at infinity. #[inline] -fn secp256r1_add_non_infinity_points(p1: &mut SyscallPoint256, p2: &SyscallPoint256) -> bool { +fn secp256r1_add_non_infinity_points( + p1: &mut SyscallPoint256, + p2: &SyscallPoint256, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { if p1.x != p2.x { let mut params = SyscallSecp256r1AddParams { p1, p2 }; - syscall_secp256r1_add(&mut params); + syscall_secp256r1_add( + &mut params, + #[cfg(feature = "hints")] + hints, + ); false } else if p1.y == p2.y { - syscall_secp256r1_dbl(p1); + syscall_secp256r1_dbl( + p1, + #[cfg(feature = "hints")] + hints, + ); false } else { // p1 + (-p1) = 𝒪 @@ -34,16 +46,44 @@ fn secp256r1_add_non_infinity_points(p1: &mut SyscallPoint256, p2: &SyscallPoint /// Checks whether the given point `p` is on the Secp256r1 curve. /// It assumes that `p` is not the point at infinity. -pub fn secp256r1_is_on_curve(p: &[u64; 8]) -> bool { +pub fn secp256r1_is_on_curve(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> bool { let x: [u64; 4] = p[0..4].try_into().unwrap(); let y: [u64; 4] = p[4..8].try_into().unwrap(); // p in E iff y² == x³ + a·x + b - let lhs = secp256r1_fp_square(&y); - let mut rhs = secp256r1_fp_square(&x); - rhs = secp256r1_fp_mul(&rhs, &x); - rhs = secp256r1_fp_add(&rhs, &secp256r1_fp_mul(&x, &E_A)); - rhs = secp256r1_fp_add(&rhs, &E_B); + let lhs = secp256r1_fp_square( + &y, + #[cfg(feature = "hints")] + hints, + ); + let mut rhs = secp256r1_fp_square( + &x, + #[cfg(feature = "hints")] + hints, + ); + rhs = secp256r1_fp_mul( + &rhs, + &x, + #[cfg(feature = "hints")] + hints, + ); + rhs = secp256r1_fp_add( + &rhs, + &secp256r1_fp_mul( + &x, + &E_A, + #[cfg(feature = "hints")] + hints, + ), + #[cfg(feature = "hints")] + hints, + ); + rhs = secp256r1_fp_add( + &rhs, + &E_B, + #[cfg(feature = "hints")] + hints, + ); eq(&lhs, &rhs) } @@ -55,22 +95,43 @@ pub fn secp256r1_triple_scalar_mul_with_g( t: &[u64; 4], p: &[u64; 8], q: &[u64; 8], + #[cfg(feature = "hints")] hints: &mut Vec, ) -> Option<[u64; 8]> { let p = SyscallPoint256 { x: [p[0], p[1], p[2], p[3]], y: [p[4], p[5], p[6], p[7]] }; let q = SyscallPoint256 { x: [q[0], q[1], q[2], q[3]], y: [q[4], q[5], q[6], q[7]] }; // Precompute g + p, g + q, p + q, g + p + q let mut gp = G_POINT256; - let gp_is_infinity = secp256r1_add_non_infinity_points(&mut gp, &p); + let gp_is_infinity = secp256r1_add_non_infinity_points( + &mut gp, + &p, + #[cfg(feature = "hints")] + hints, + ); let mut gq = G_POINT256; - let gq_is_infinity = secp256r1_add_non_infinity_points(&mut gq, &q); + let gq_is_infinity = secp256r1_add_non_infinity_points( + &mut gq, + &q, + #[cfg(feature = "hints")] + hints, + ); let mut pq = SyscallPoint256 { x: p.x, y: p.y }; - let pq_is_infinity = secp256r1_add_non_infinity_points(&mut pq, &q); + let pq_is_infinity = secp256r1_add_non_infinity_points( + &mut pq, + &q, + #[cfg(feature = "hints")] + hints, + ); let mut gpq = SyscallPoint256 { x: gp.x, y: gp.y }; - let gpq_is_infinity = secp256r1_add_non_infinity_points(&mut gpq, &q); + let gpq_is_infinity = secp256r1_add_non_infinity_points( + &mut gpq, + &q, + #[cfg(feature = "hints")] + hints, + ); if is_one(r) && is_one(s) && is_one(t) { // Return g + p + q @@ -85,7 +146,13 @@ pub fn secp256r1_triple_scalar_mul_with_g( // From here on, at least one of r,s,t is greater than 1 // Hint the maximum length between the binary representations of r,s and t - let (max_limb, max_bit) = fcall_msb_pos_256_3(r, s, t); + let (max_limb, max_bit) = fcall_msb_pos_256_3( + r, + s, + t, + #[cfg(feature = "hints")] + hints, + ); // Perform the loop, based on the binary representation of r,s and t @@ -209,7 +276,11 @@ pub fn secp256r1_triple_scalar_mul_with_g( (0, 0, 0) => { // If res is 𝒪, do nothing; otherwise, double if !res_is_infinity { - syscall_secp256r1_dbl(&mut res); + syscall_secp256r1_dbl( + &mut res, + #[cfg(feature = "hints")] + hints, + ); } } (0, 0, 1) => { @@ -219,8 +290,17 @@ pub fn secp256r1_triple_scalar_mul_with_g( res.y = q.y; res_is_infinity = false; } else { - syscall_secp256r1_dbl(&mut res); - res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &q); + syscall_secp256r1_dbl( + &mut res, + #[cfg(feature = "hints")] + hints, + ); + res_is_infinity = secp256r1_add_non_infinity_points( + &mut res, + &q, + #[cfg(feature = "hints")] + hints, + ); } // Update t_rec @@ -233,8 +313,17 @@ pub fn secp256r1_triple_scalar_mul_with_g( res.y = p.y; res_is_infinity = false; } else { - syscall_secp256r1_dbl(&mut res); - res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &p); + syscall_secp256r1_dbl( + &mut res, + #[cfg(feature = "hints")] + hints, + ); + res_is_infinity = secp256r1_add_non_infinity_points( + &mut res, + &p, + #[cfg(feature = "hints")] + hints, + ); } // Update s_rec @@ -249,9 +338,18 @@ pub fn secp256r1_triple_scalar_mul_with_g( res_is_infinity = false; } } else { - syscall_secp256r1_dbl(&mut res); + syscall_secp256r1_dbl( + &mut res, + #[cfg(feature = "hints")] + hints, + ); if !pq_is_infinity { - res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &pq); + res_is_infinity = secp256r1_add_non_infinity_points( + &mut res, + &pq, + #[cfg(feature = "hints")] + hints, + ); } } @@ -266,8 +364,17 @@ pub fn secp256r1_triple_scalar_mul_with_g( res.y = G_POINT256.y; res_is_infinity = false; } else { - syscall_secp256r1_dbl(&mut res); - res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &G_POINT256); + syscall_secp256r1_dbl( + &mut res, + #[cfg(feature = "hints")] + hints, + ); + res_is_infinity = secp256r1_add_non_infinity_points( + &mut res, + &G_POINT256, + #[cfg(feature = "hints")] + hints, + ); } // Update r_rec @@ -282,9 +389,18 @@ pub fn secp256r1_triple_scalar_mul_with_g( res_is_infinity = false; } } else { - syscall_secp256r1_dbl(&mut res); + syscall_secp256r1_dbl( + &mut res, + #[cfg(feature = "hints")] + hints, + ); if !gq_is_infinity { - res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &gq); + res_is_infinity = secp256r1_add_non_infinity_points( + &mut res, + &gq, + #[cfg(feature = "hints")] + hints, + ); } } @@ -301,9 +417,18 @@ pub fn secp256r1_triple_scalar_mul_with_g( res_is_infinity = false; } } else { - syscall_secp256r1_dbl(&mut res); + syscall_secp256r1_dbl( + &mut res, + #[cfg(feature = "hints")] + hints, + ); if !gp_is_infinity { - res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &gp); + res_is_infinity = secp256r1_add_non_infinity_points( + &mut res, + &gp, + #[cfg(feature = "hints")] + hints, + ); } } @@ -320,9 +445,18 @@ pub fn secp256r1_triple_scalar_mul_with_g( res_is_infinity = false; } } else { - syscall_secp256r1_dbl(&mut res); + syscall_secp256r1_dbl( + &mut res, + #[cfg(feature = "hints")] + hints, + ); if !gpq_is_infinity { - res_is_infinity = secp256r1_add_non_infinity_points(&mut res, &gpq); + res_is_infinity = secp256r1_add_non_infinity_points( + &mut res, + &gpq, + #[cfg(feature = "hints")] + hints, + ); } } diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs index 3efd039b9..cc4842883 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs @@ -8,7 +8,13 @@ use super::{ /// Verifies the signature (r, s) over the message hash z using the public key pk /// Returns true if the signature is valid, false otherwise -pub fn secp256r1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u64; 4]) -> bool { +pub fn secp256r1_ecdsa_verify( + pk: &[u64; 8], + z: &[u64; 4], + r: &[u64; 4], + s: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { // r and s must be in the range [1, n-1] if is_zero(r) || gt(r, &N_MINUS_ONE) { return false; @@ -28,7 +34,11 @@ pub fn secp256r1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u6 if gt(&pk_x, &P_MINUS_ONE) || gt(&pk_y, &P_MINUS_ONE) { return false; } - if !secp256r1_is_on_curve(pk) { + if !secp256r1_is_on_curve( + pk, + #[cfg(feature = "hints")] + hints, + ) { return false; } @@ -39,23 +49,55 @@ pub fn secp256r1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u6 // and ensure that x ≡ r (mod n), saving us from expensive fn arithmetic // Hint the result - let point = fcall_secp256r1_ecdsa_verify(pk, z, r, s); + let point = fcall_secp256r1_ecdsa_verify( + pk, + z, + r, + s, + #[cfg(feature = "hints")] + hints, + ); // Check the recovered point is valid // Note: Identity point would be raised here - if !secp256r1_is_on_curve(&point) { + if !secp256r1_is_on_curve( + &point, + #[cfg(feature = "hints")] + hints, + ) { return false; } // Check that [z]G + [r]PK + [-s](x,y) == 𝒪 - let neg_s = secp256r1_fn_neg(s); - if secp256r1_triple_scalar_mul_with_g(z, r, &neg_s, pk, &point).is_some() { + let neg_s = secp256r1_fn_neg( + s, + #[cfg(feature = "hints")] + hints, + ); + if secp256r1_triple_scalar_mul_with_g( + z, + r, + &neg_s, + pk, + &point, + #[cfg(feature = "hints")] + hints, + ) + .is_some() + { return false; } // Check that x ≡ r (mod n) let point_x: [u64; 4] = [point[0], point[1], point[2], point[3]]; - eq(&secp256r1_fn_reduce(&point_x), r) + eq( + &secp256r1_fn_reduce( + &point_x, + #[cfg(feature = "hints")] + hints, + ), + r, + ) } // ==================== C FFI Functions ==================== @@ -66,16 +108,25 @@ pub fn secp256r1_ecdsa_verify(pk: &[u64; 8], z: &[u64; 4], r: &[u64; 4], s: &[u6 /// - `pk_ptr` must point to 8 u64s /// /// Returns true if signature is valid -#[no_mangle] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256r1_ecdsa_verify_c")] pub unsafe extern "C" fn secp256r1_ecdsa_verify_c( msg_ptr: *const u64, sig_ptr: *const u64, pk_ptr: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, ) -> bool { let msg: &[u64; 4] = &*(msg_ptr as *const [u64; 4]); let sig: &[u64; 8] = &*(sig_ptr as *const [u64; 8]); let pk: &[u64; 8] = &*(pk_ptr as *const [u64; 8]); let r: &[u64; 4] = &sig[0..4].try_into().unwrap(); let s: &[u64; 4] = &sig[4..8].try_into().unwrap(); - secp256r1_ecdsa_verify(pk, msg, r, s) + secp256r1_ecdsa_verify( + pk, + msg, + r, + s, + #[cfg(feature = "hints")] + hints, + ) } diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/field.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/field.rs index 2a2a6b9e2..934be0b84 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256r1/field.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/field.rs @@ -2,28 +2,49 @@ use crate::syscalls::{syscall_arith256_mod, SyscallArith256ModParams}; use super::constants::P; -pub fn secp256r1_fp_add(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn secp256r1_fp_add( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·1 + y let mut params = SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256r1_fp_mul(x: &[u64; 4], y: &[u64; 4]) -> [u64; 4] { +pub fn secp256r1_fp_mul( + x: &[u64; 4], + y: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·y + 0 let mut params = SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); - + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256r1_fp_square(x: &[u64; 4]) -> [u64; 4] { +pub fn secp256r1_fp_square( + x: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { // x·x + 0 let mut params = SyscallArith256ModParams { a: x, b: x, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params); - + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs index 46bf8e5ca..f5b73ca34 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/scalar.rs @@ -5,7 +5,10 @@ use crate::{ use super::constants::{N, N_MINUS_ONE}; -pub fn secp256r1_fn_reduce(x: &[u64; 4]) -> [u64; 4] { +pub fn secp256r1_fn_reduce( + x: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { if lt(x, &N) { return *x; } @@ -18,12 +21,16 @@ pub fn secp256r1_fn_reduce(x: &[u64; 4]) -> [u64; 4] { module: &N, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } -pub fn secp256r1_fn_neg(x: &[u64; 4]) -> [u64; 4] { +pub fn secp256r1_fn_neg(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { // x·(-1) + 0 let mut params = SyscallArith256ModParams { a: x, @@ -32,7 +39,11 @@ pub fn secp256r1_fn_neg(x: &[u64; 4]) -> [u64; 4] { module: &N, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *params.d } From bacf6c9eac3dc7505704e13364567e887a3eeb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:17:32 +0000 Subject: [PATCH 395/782] Fixing ecdsa API --- .../src/zisklib/lib/secp256r1/ecdsa.rs | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs index cc4842883..f1995d1d5 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/ecdsa.rs @@ -111,22 +111,48 @@ pub fn secp256r1_ecdsa_verify( #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_secp256r1_ecdsa_verify_c")] pub unsafe extern "C" fn secp256r1_ecdsa_verify_c( - msg_ptr: *const u64, - sig_ptr: *const u64, - pk_ptr: *const u64, + msg: *const u8, + sig: *const u8, + pk: *const u8, #[cfg(feature = "hints")] hints: &mut Vec, ) -> bool { - let msg: &[u64; 4] = &*(msg_ptr as *const [u64; 4]); - let sig: &[u64; 8] = &*(sig_ptr as *const [u64; 8]); - let pk: &[u64; 8] = &*(pk_ptr as *const [u64; 8]); - let r: &[u64; 4] = &sig[0..4].try_into().unwrap(); - let s: &[u64; 4] = &sig[4..8].try_into().unwrap(); + let msg_bytes: &[u8; 32] = &*(msg as *const [u8; 32]); + let sig_bytes: &[u8; 64] = &*(sig as *const [u8; 64]); + let pk_bytes: &[u8; 64] = &*(pk as *const [u8; 64]); + + // Parse r, s from big-endian bytes + let r_bytes: [u8; 32] = sig_bytes[0..32].try_into().unwrap(); + let s_bytes: [u8; 32] = sig_bytes[32..64].try_into().unwrap(); + + // Parse pk_x, pk_y from big-endian bytes + let pk_x_bytes: [u8; 32] = pk_bytes[0..32].try_into().unwrap(); + let pk_y_bytes: [u8; 32] = pk_bytes[32..64].try_into().unwrap(); + + // Convert to little-endian u64 limbs + let z = bytes_be_to_u64_le(msg_bytes); + let r = bytes_be_to_u64_le(&r_bytes); + let s = bytes_be_to_u64_le(&s_bytes); + let pk_x = bytes_be_to_u64_le(&pk_x_bytes); + let pk_y = bytes_be_to_u64_le(&pk_y_bytes); + + let pk: [u64; 8] = [pk_x[0], pk_x[1], pk_x[2], pk_x[3], pk_y[0], pk_y[1], pk_y[2], pk_y[3]]; secp256r1_ecdsa_verify( - pk, - msg, - r, - s, + &pk, + &z, + &r, + &s, #[cfg(feature = "hints")] hints, ) } + +/// Convert big-endian bytes to little-endian u64 limbs (32 bytes -> [u64; 4]) +fn bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { + let mut result = [0u64; 4]; + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + result +} From c9c216ccfe3758f91419bc7df07b6005959fcf66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Mon, 2 Feb 2026 18:52:12 +0100 Subject: [PATCH 396/782] Cargo update (#736) * Cargo update * Build setup to include starkstruct json file * Pointing to proofman 0.16.0 * Cargo update --- Cargo.lock | 27 +++++++++++++-------------- state-machines/starkstructs.json | 4 +++- tools/test-env/build_setup.sh | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0170e99ba..3b567ba8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1179,7 +1179,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" dependencies = [ "fields", "num-bigint", @@ -1566,7 +1566,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" dependencies = [ "cfg-if", "num-bigint", @@ -1995,14 +1995,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -2955,7 +2954,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" dependencies = [ "colored", "fields", @@ -3368,7 +3367,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" dependencies = [ "blake3", "borsh", @@ -3403,7 +3402,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" dependencies = [ "borsh", "colored", @@ -3434,7 +3433,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" dependencies = [ "fields", "itoa", @@ -3447,7 +3446,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" dependencies = [ "proc-macro2", "quote", @@ -3458,7 +3457,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" dependencies = [ "crossbeam-channel", "tracing", @@ -3467,7 +3466,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" dependencies = [ "colored", "fields", @@ -3478,7 +3477,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" dependencies = [ "bytemuck", "fields", @@ -5872,7 +5871,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3d123228f798a16e7a870dfd348d407db88bf295" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" dependencies = [ "colored", "fields", diff --git a/state-machines/starkstructs.json b/state-machines/starkstructs.json index 6976d5404..3e4636601 100644 --- a/state-machines/starkstructs.json +++ b/state-machines/starkstructs.json @@ -5,5 +5,7 @@ "ArithEq384": { "hasCompressor": true }, "VirtualTable0": { "lastLevelVerification": 1 }, "VirtualTable1": { "lastLevelVerification": 1 }, - "Poseidon2": { "blowupFactor": 2 } + "SpecifiedRanges": { "lastLevelVerification": 1 }, + "Poseidon2": { "blowupFactor": 2 }, + "Rom": { "powBits": 20 } } \ No newline at end of file diff --git a/tools/test-env/build_setup.sh b/tools/test-env/build_setup.sh index bba13bc9b..a3368f081 100755 --- a/tools/test-env/build_setup.sh +++ b/tools/test-env/build_setup.sh @@ -83,7 +83,7 @@ main() { cached=0 if [[ "${USE_CACHE_SETUP}" == "1" ]]; then # Compute setup hash - HASH_SUM=$(sha256sum pil/zisk.pilout tmp/fixed/*.fixed \ + HASH_SUM=$(sha256sum pil/zisk.pilout tmp/fixed/*.fixed state-machines/starkstructs.json \ | sort -k2 \ | sha256sum \ | awk '{print $1}' \ From 85266ef449bbc4dec904fe45ba81ee0209e5cc7b Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 2 Feb 2026 19:03:58 +0100 Subject: [PATCH 397/782] Add memory gap between RAM and trace --- emulator-asm/src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index de7966959..c80abf2bc 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -45,7 +45,7 @@ uint64_t get_precompile_results(void); #define SYS_SIZE (uint64_t)0x10000 #define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) -#define TRACE_ADDR (uint64_t)0xc0000000 +#define TRACE_ADDR (uint64_t)0xd0000000 #define INITIAL_TRACE_SIZE (uint64_t)0x180000000 // 6GB #define DELTA_TRACE_SIZE (uint64_t)0x080000000 // 2GB From c818f723de67917810ab7d8b3fb8745bad8948b1 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:30:03 +0000 Subject: [PATCH 398/782] minor simplification --- sdk/src/zisk_lib_loader.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk/src/zisk_lib_loader.rs b/sdk/src/zisk_lib_loader.rs index c74c35f3b..45f67851b 100644 --- a/sdk/src/zisk_lib_loader.rs +++ b/sdk/src/zisk_lib_loader.rs @@ -21,7 +21,7 @@ impl ZiskLibLoader { unlock_mapped_memory: Option, with_hints: bool, ) -> Result> { - let witness_lib = init_zisk_lib( + Ok(init_zisk_lib( verbose, elf, asm_mt_filename, @@ -30,9 +30,7 @@ impl ZiskLibLoader { unlock_mapped_memory.unwrap_or(false), shared_tables, with_hints, - ); - - Ok(witness_lib) + )) } pub fn load_emu( From 76108e8f6a00f35db7eb51a54a813e1a4329b80f Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Mon, 2 Feb 2026 19:45:57 +0100 Subject: [PATCH 399/782] dma operations memcmp, inputcpy and memset. Improvements on ziskemu to trace calls, and dissamble execution trace. --- core/src/mem.rs | 2 +- core/src/riscv2zisk_context.rs | 4 +- core/src/zisk_ops.rs | 12 +- core/src/zisk_rom_2_asm.rs | 8 +- emulator/Cargo.toml | 1 + emulator/src/disasm.rs | 400 +++++++++++ emulator/src/elf_symbol_reader.rs | 28 +- emulator/src/emu.rs | 86 ++- emulator/src/emu_options.rs | 47 ++ emulator/src/lib.rs | 2 + emulator/src/regions_of_interest.rs | 77 ++- emulator/src/stats.rs | 180 ++++- emulator/src/stats_report.rs | 90 ++- pil/config.pil | 1 + pil/operations.pil | 44 ++ pil/opids.pil | 1 + pil/src/pil_helpers/traces.rs | 505 +++++++++----- pil/zisk.pil | 24 +- precompiles/arith_eq/pil/arith_eq.pil | 5 +- precompiles/arith_eq_384/pil/arith_eq_384.pil | 5 +- precompiles/big_int/pil/big_int_add.pil | 8 +- precompiles/dma/pil/dma.pil | 456 +++++++++++-- precompiles/dma/pil/dma_64_aligned.pil | 627 ++++++++++++++---- precompiles/dma/pil/dma_byte_cmp_table.pil | 43 ++ precompiles/dma/pil/dma_pre_post.pil | 551 +++++++++++---- precompiles/dma/pil/dma_pre_post_table.pil | 99 ++- precompiles/dma/pil/dma_unaligned.pil | 285 +++++--- precompiles/dma/src/dma_gen_mem_inputs.rs | 6 +- precompiles/keccakf/pil/keccakf.pil | 5 +- precompiles/poseidon2/pil/poseidon2.pil | 4 +- precompiles/sha256f/pil/sha256f.pil | 4 +- state-machines/arith/pil/arith.pil | 18 +- state-machines/arith/pil/arith_mul64.pil | 13 +- state-machines/binary/pil/binary.pil | 6 +- state-machines/binary/pil/binary_add.pil | 6 +- .../binary/pil/binary_extension.pil | 20 +- .../frequent-ops/pil/frequent_ops.pil | 9 +- state-machines/main/pil/main.pil | 34 +- 38 files changed, 2982 insertions(+), 734 deletions(-) create mode 100644 emulator/src/disasm.rs create mode 100644 precompiles/dma/pil/dma_byte_cmp_table.pil diff --git a/core/src/mem.rs b/core/src/mem.rs index ed91c39f5..26c6003f0 100644 --- a/core/src/mem.rs +++ b/core/src/mem.rs @@ -142,7 +142,7 @@ pub const ARCH_ID_ZISK: u64 = 0xFFFEEEE; /// UART memory address; single bytes written here will be copied to the standard output pub const UART_ADDR: u64 = SYS_ADDR + 0x200; /// Extra parameters of repcompiles are stored in fixed memory area (256 bytes => 32 parameters) -pub const EXTRA_PARAMS: u64 = SYS_ADDR + 0x0F00; +pub const EXTRA_PARAMS_ADDR: u64 = SYS_ADDR + 0x0F00; /// Float registers first address pub const FREG_FIRST: u64 = SYS_ADDR + 0x1000; /// CSR memory address; contains control and status registers diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 2a22683a4..6fd32082c 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -6,7 +6,7 @@ use riscv::{riscv_interpreter, RiscvInstruction}; use crate::{ convert_vector, ZiskInstBuilder, ZiskRom, ARCH_ID_CSR_ADDR, ARCH_ID_ZISK, CSR_ADDR, - EXTRA_PARAMS, FLOAT_LIB_ROM_ADDR, FLOAT_LIB_SP, FREG_F0, FREG_INST, FREG_RA, FREG_X0, + EXTRA_PARAMS_ADDR, FLOAT_LIB_ROM_ADDR, FLOAT_LIB_SP, FREG_F0, FREG_INST, FREG_RA, FREG_X0, INPUT_ADDR, MTVEC, OUTPUT_ADDR, REG_X0, ROM_ENTRY, ROM_EXIT, }; @@ -1179,7 +1179,7 @@ impl Riscv2ZiskContext<'_> { self.output_precompile = Some(i.csr); zib.src_a("imm", 0, false); zib.op("copyb").unwrap(); - zib.store("mem", EXTRA_PARAMS as i64, false, false); + zib.store("mem", EXTRA_PARAMS_ADDR as i64, false, false); zib.verbose("param"); } _ => { diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 67e5f5d94..dbc6d93ed 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -24,7 +24,7 @@ use tiny_keccak::keccakf; use crate::{ sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, - EXTRA_PARAMS, M64, REG_A0, SYS_ADDR, + EXTRA_PARAMS_ADDR, M64, REG_A0, SYS_ADDR, }; use lib_c::{inverse_fn_ec_c, inverse_fp_ec_c, sqrt_fp_ec_parity_c, Fcall, FcallContext}; @@ -2423,13 +2423,13 @@ pub fn opc_dma_memcpy(ctx: &mut InstContext) { match ctx.emulation_mode { EmulationMode::Mem => { - let count = ctx.mem.read(EXTRA_PARAMS, 8); + let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8); ctx.mem.memcpy(dst, src, count); } EmulationMode::GenerateMemReads => { // In generate mode we need to populate precompiled.input_data with // information needed - let count = ctx.mem.read(EXTRA_PARAMS, 8); + let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8); ctx.precompiled.input_data.clear(); #[cfg(feature = "debug_dma")] @@ -2514,7 +2514,7 @@ pub fn op_dma_memcpy(_a: u64, _b: u64) -> (u64, bool) { pub fn ops_dma_memcpy(ctx: &InstContext, stats: &mut dyn OpStats) { let dst = ctx.a; let src = ctx.b; - let count = ctx.mem.read(EXTRA_PARAMS, 8); + let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8); // pre, post, dma_align, dma_unalign if count == 0 { @@ -2573,7 +2573,7 @@ pub fn ops_dma_memcpy(ctx: &InstContext, stats: &mut dyn OpStats) { pub fn opc_dma_memcmp(ctx: &mut InstContext) { let addr_a = ctx.a; let addr_b = ctx.b; - let count = ctx.mem.read(EXTRA_PARAMS, 8); + let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8); println!("opc_dma_memcmp 0x{addr_a:08X} 0x{addr_b:08X} {count} {:?}", ctx.emulation_mode); @@ -2616,7 +2616,7 @@ pub fn op_dma_memcmp(_a: u64, _b: u64) -> (u64, bool) { pub fn ops_dma_memcmp(ctx: &InstContext, stats: &mut dyn OpStats) { let addr_a = ctx.a; let addr_b = ctx.b; - let count = ctx.mem.read(EXTRA_PARAMS, 8); + let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8); // pre, post, dma_align, dma_unalign if count == 0 { diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 3fe26b6ab..190ec909a 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -4,9 +4,9 @@ use std::path::Path; use crate::{ - zisk_ops::ZiskOp, AsmGenerationMethod, ZiskInst, ZiskRom, EXTRA_PARAMS, FLOAT_LIB_ROM_ADDR, - FREE_INPUT_ADDR, M64, P2_32, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, SRC_C, SRC_IMM, SRC_IND, - SRC_MEM, SRC_REG, SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, + zisk_ops::ZiskOp, AsmGenerationMethod, ZiskInst, ZiskRom, EXTRA_PARAMS_ADDR, + FLOAT_LIB_ROM_ADDR, FREE_INPUT_ADDR, M64, P2_32, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, SRC_C, + SRC_IMM, SRC_IND, SRC_MEM, SRC_REG, SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, }; // Regs rax, rcx, rdx, rdi, rsi, rsp, and r8-r11 are caller-save, not saved across function calls. @@ -6837,7 +6837,7 @@ impl ZiskRom2Asm { ); *code += &format!( "\tmov rdx, 0x{:08x} {}\n", - EXTRA_PARAMS, + EXTRA_PARAMS_ADDR, ctx.comment_str("rdx = @EXTERN_PARAM") ); *code += &format!("\tmov rdx, [rdx] {}\n", ctx.comment_str("rdx = [EXTERN_PARAM]")); diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml index 90c8015c7..a209095ab 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -36,6 +36,7 @@ memmap2 = "0.9.8" num-format = "0.4" symbolic-demangle = { version = "12.16", features = ["rust", "cpp"] } symbolic-common = "12.16" +regex = "1.11.1" [build-dependencies] vergen = { version = "8", default-features = false, features = [ diff --git a/emulator/src/disasm.rs b/emulator/src/disasm.rs new file mode 100644 index 000000000..8ddedaf99 --- /dev/null +++ b/emulator/src/disasm.rs @@ -0,0 +1,400 @@ +//! Disassembly writer module +//! Generates objdump-like output with execution counts + +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufWriter, Result, Write}; + +use crate::ElfSymbolReader; +use zisk_core::{ZiskInst, ZiskRom}; + +pub struct DisasmWriter { + file: BufWriter, + pc_histogram: HashMap, + symbols: Option, +} + +impl DisasmWriter { + pub fn new(path: &str) -> Result { + let file = File::create(path)?; + Ok(Self { file: BufWriter::new(file), pc_histogram: HashMap::new(), symbols: None }) + } + + pub fn set_pc_histogram(&mut self, histogram: HashMap) { + self.pc_histogram = histogram; + } + + pub fn set_symbols(&mut self, symbols: ElfSymbolReader) { + self.symbols = Some(symbols); + } + + pub fn write_header(&mut self, title: &str) -> Result<()> { + writeln!(&mut self.file)?; + writeln!(&mut self.file, "Disassembly with execution counts:")?; + writeln!(&mut self.file, "{}", title)?; + writeln!(&mut self.file)?; + Ok(()) + } + + pub fn write_disassembly(&mut self, rom: &ZiskRom) -> Result<()> { + let mut local_labels: HashMap = HashMap::new(); + + // First pass: identify all jump targets to generate labels + for (idx, pc) in rom.sorted_pc_list.iter().enumerate() { + let inst = rom.get_instruction(*pc); + // Get next PC for detecting fall-through + let next_pc = if idx + 1 < rom.sorted_pc_list.len() { + Some(rom.sorted_pc_list[idx + 1]) + } else { + None + }; + + // Check for jumps to generate labels + if inst.set_pc { + let target1 = (*pc as i64 + inst.jmp_offset1) as u64; + // Only create label if it's not the next instruction (not fall-through) + if Some(target1) != next_pc + && !local_labels.contains_key(&target1) + && rom.sorted_pc_list.binary_search(&target1).is_ok() + { + if let Some(ref symbols) = self.symbols { + if let Some(sym) = symbols.get_symbol_at_address(target1) { + local_labels.insert(target1, sym.name.clone()); + } else { + let label = format!(".L{}", local_labels.len()); + local_labels.insert(target1, label); + } + } else { + let label = format!(".L{}", local_labels.len()); + local_labels.insert(target1, label); + } + } + + if inst.jmp_offset2 != 0 { + let target2 = (*pc as i64 + inst.jmp_offset2) as u64; + // Only create label if it's not the next instruction (not fall-through) + if Some(target2) != next_pc + && !local_labels.contains_key(&target2) + && rom.sorted_pc_list.binary_search(&target2).is_ok() + { + if let Some(ref symbols) = self.symbols { + if let Some(sym) = symbols.get_symbol_at_address(target2) { + local_labels.insert(target2, sym.name.clone()); + } else { + let label = format!(".L{}", local_labels.len()); + local_labels.insert(target2, label); + } + } else { + let label = format!(".L{}", local_labels.len()); + local_labels.insert(target2, label); + } + } + } + } + } + + // Second pass: generate disassembly + for (idx, pc) in rom.sorted_pc_list.iter().enumerate() { + // Check if this PC is a function entry point + if let Some(ref symbols) = self.symbols { + if let Some(sym) = symbols.get_symbol_at_address(*pc) { + // Write function header + writeln!(&mut self.file)?; + writeln!(&mut self.file, "{:016x} <{}>:", pc, sym.name)?; + } + } + + // Check if this PC has a label (jump target) + if let Some(label) = local_labels.get(pc) { + if let Some(ref symbols) = self.symbols { + if symbols.get_symbol_at_address(*pc).is_none() { + writeln!(&mut self.file)?; + writeln!(&mut self.file, "{:016x} <{}>:", pc, label)?; + } + } else { + writeln!(&mut self.file)?; + writeln!(&mut self.file, "{:016x} <{}>:", pc, label)?; + } + } + + let inst = rom.get_instruction(*pc); + let exec_count = self.pc_histogram.get(pc).unwrap_or(&0); + + // Get next PC for detecting fall-through jumps + let next_pc = if idx + 1 < rom.sorted_pc_list.len() { + Some(rom.sorted_pc_list[idx + 1]) + } else { + None + }; + + // Determine if this is the first Zisk instruction for a RISC-V instruction + let is_first_zisk_for_riscv = if idx > 0 { + let prev_pc = rom.sorted_pc_list[idx - 1]; + let prev_inst = rom.get_instruction(prev_pc); + prev_inst.riscv_inst != inst.riscv_inst + } else { + true + }; + + // Format: PC | EXEC_COUNT | RISCV_INST | ZISK_INST + if is_first_zisk_for_riscv { + if let Some(ref riscv_inst) = inst.riscv_inst { + writeln!( + &mut self.file, + " {:08x}: {:12} {:30} {}", + pc, + exec_count, + riscv_inst, + inst_to_asm(inst, &local_labels, next_pc) + )?; + } else { + // Zisk instruction without RISC-V source (initialization) + writeln!( + &mut self.file, + " {:08x}: {:12} {:30} {}", + pc, + exec_count, + "", + inst_to_asm(inst, &local_labels, next_pc) + )?; + } + } else { + // Additional Zisk instruction from same RISC-V instruction + writeln!( + &mut self.file, + " {:08x}: {:12} {:30} {}", + pc, + exec_count, + "", + inst_to_asm(inst, &local_labels, next_pc) + )?; + } + } + + Ok(()) + } + + pub fn flush(&mut self) -> Result<()> { + self.file.flush() + } +} + +/// Convert a ZiskInst to assembly-like string representation +/// Format: operation dest, a, b (RISC-V like syntax) +fn inst_to_asm(inst: &ZiskInst, labels: &HashMap, next_pc: Option) -> String { + use zisk_core::{ + SRC_C, SRC_IMM, SRC_IND, SRC_MEM, SRC_REG, SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, + STORE_REG, + }; + + let mut asm = String::new(); + + // Operation name + asm.push_str(inst.op_str); + + let mut operands = Vec::new(); + + // 1. Destination (c register - where result is stored) + if inst.store != STORE_NONE { + let dest = match inst.store { + STORE_REG => { + format!("x{}", inst.store_offset) + } + STORE_MEM => { + if inst.store_use_sp { + if inst.store_offset >= 0 { + format!("[sp+{}]", inst.store_offset) + } else { + format!("[sp{}]", inst.store_offset) + } + } else { + format!("[0x{:x}]", inst.store_offset) + } + } + STORE_IND => { + if inst.store_offset >= 0 { + format!("[a+{}]", inst.store_offset) + } else { + format!("[a{}]", inst.store_offset) + } + } + _ => "?".to_string(), + }; + operands.push(dest); + } + + // 2. Source A + let src_a = match inst.a_src { + SRC_C => "c".to_string(), + SRC_REG => { + format!("x{}", inst.a_offset_imm0) + } + SRC_MEM => { + if inst.a_use_sp_imm1 != 0 { + let offset = inst.a_offset_imm0 as i64; + if offset >= 0 { + format!("[sp+{}]", offset) + } else { + format!("[sp{}]", offset) + } + } else { + format!("[0x{:x}]", inst.a_offset_imm0) + } + } + SRC_IMM => { + let imm = (inst.a_offset_imm0 as i64 | ((inst.a_use_sp_imm1 as i64) << 32)) as i64; + if imm >= 0 && imm <= 9 { + format!("{}", imm) + } else { + format!("0x{:x}", imm as u64) + } + } + SRC_STEP => "step".to_string(), + _ => "?".to_string(), + }; + operands.push(src_a); + + // 3. Source B (if used) + if inst.b_src != 0 { + let src_b = match inst.b_src { + SRC_C => "c".to_string(), + SRC_REG => { + format!("x{}", inst.b_offset_imm0) + } + SRC_MEM => { + if inst.b_use_sp_imm1 != 0 { + let offset = inst.b_offset_imm0 as i64; + if offset >= 0 { + format!("[sp+{}]", offset) + } else { + format!("[sp{}]", offset) + } + } else { + format!("[0x{:x}]", inst.b_offset_imm0) + } + } + SRC_IMM => { + let imm = (inst.b_offset_imm0 as i64 | ((inst.b_use_sp_imm1 as i64) << 32)) as i64; + if imm >= 0 && imm <= 9 { + format!("{}", imm) + } else { + format!("0x{:x}", imm as u64) + } + } + SRC_IND => { + let offset = inst.b_offset_imm0 as i64; + let width = match inst.ind_width { + 1 => "b", + 2 => "h", + 4 => "w", + 8 => "d", + _ => "", + }; + if inst.b_use_sp_imm1 != 0 { + if offset >= 0 { + format!("[a+sp+{}]{}", offset, width) + } else { + format!("[a+sp{}]{}", offset, width) + } + } else { + if offset >= 0 { + format!("[a+{}]{}", offset, width) + } else { + format!("[a{}]{}", offset, width) + } + } + } + _ => "?".to_string(), + }; + operands.push(src_b); + } + + // Format operands + if !operands.is_empty() { + asm.push(' '); + asm.push_str(&operands.join(", ")); + } + + // 4. Jump targets (Zisk peculiarity: two jump offsets) + // jmp_offset1: used if flag is active + // jmp_offset2: used as default jump + // Don't show jumps to next instruction (fall-through) to be more RISC-V like + if inst.set_pc { + let mut jump_targets = Vec::new(); + + let target1_is_next = + inst.jmp_offset1 != 0 && Some((inst.paddr as i64 + inst.jmp_offset1) as u64) == next_pc; + let target2_is_next = + inst.jmp_offset2 != 0 && Some((inst.paddr as i64 + inst.jmp_offset2) as u64) == next_pc; + + if inst.jmp_offset1 != 0 && !target1_is_next { + let target = (inst.paddr as i64 + inst.jmp_offset1) as u64; + if let Some(label) = labels.get(&target) { + jump_targets.push((true, label.clone())); + } else { + jump_targets.push((true, format!("0x{:x}", target))); + } + } + + if inst.jmp_offset2 != 0 && !target2_is_next { + let target = (inst.paddr as i64 + inst.jmp_offset2) as u64; + if let Some(label) = labels.get(&target) { + jump_targets.push((false, label.clone())); + } else { + jump_targets.push((false, format!("0x{:x}", target))); + } + } + + if !jump_targets.is_empty() { + if operands.is_empty() { + asm.push(' '); + } else { + asm.push_str(", "); + } + + // If only one target, don't use prefix (it's implicit) + // If both targets, use true:/false: prefix to distinguish + if jump_targets.len() == 1 { + asm.push_str(&jump_targets[0].1); + } else { + let formatted: Vec = jump_targets + .iter() + .map(|(is_true, label)| { + if *is_true { + format!("true:{}", label) + } else { + format!("false:{}", label) + } + }) + .collect(); + asm.push_str(&formatted.join(", ")); + } + } + } + + // 5. Additional flags/modifiers as comments + let mut comments = Vec::new(); + + if inst.m32 { + comments.push("32-bit"); + } + if inst.end { + comments.push("END"); + } + if inst.is_external_op { + comments.push("external"); + } + if inst.store_pc { + comments.push("store_pc"); + } + if inst.op_with_step { + comments.push("with_step"); + } + + if !comments.is_empty() { + asm.push_str(" ; "); + asm.push_str(&comments.join(", ")); + } + + asm +} diff --git a/emulator/src/elf_symbol_reader.rs b/emulator/src/elf_symbol_reader.rs index 09563f0ef..4ad04dbbe 100644 --- a/emulator/src/elf_symbol_reader.rs +++ b/emulator/src/elf_symbol_reader.rs @@ -1,5 +1,6 @@ use memmap2::Mmap; use object::{elf::STT_FUNC, Object, ObjectSymbol, Symbol, SymbolFlags, SymbolKind}; +use regex::Regex; use std::fs::File; use std::io::Result; @@ -9,11 +10,13 @@ pub struct SymbolInfo { pub name: String, pub address: u64, pub size: u64, + pub is_selected_roi: bool, } pub struct ElfSymbolReader { functions: Vec, profile_tags: Vec<(u16, String)>, + roi_filter: Option, } impl Default for ElfSymbolReader { @@ -23,7 +26,13 @@ impl Default for ElfSymbolReader { } impl ElfSymbolReader { pub fn new() -> Self { - Self { functions: Vec::new(), profile_tags: Vec::new() } + Self { functions: Vec::new(), profile_tags: Vec::new(), roi_filter: None } + } + + /// Sets a regex filter to mark matching symbols as ROI + pub fn set_roi_filter(&mut self, pattern: &str) -> std::result::Result<(), regex::Error> { + self.roi_filter = Some(Regex::new(pattern)?); + Ok(()) } pub fn load_from_file(&mut self, path: &str) -> Result<()> { @@ -52,7 +61,12 @@ impl ElfSymbolReader { let name = self.demangle_name(name); let address = symbol.address(); let size = symbol.size(); - let symbol_info = SymbolInfo { name, address, size }; + let is_selected_roi = self + .roi_filter + .as_ref() + .map(|re| re.is_match(&name)) + .unwrap_or(false); + let symbol_info = SymbolInfo { name, address, size, is_selected_roi }; self.functions.push(symbol_info); } } @@ -105,4 +119,14 @@ impl ElfSymbolReader { pub fn functions(&self) -> impl Iterator { self.functions.iter() } + + /// Returns an iterator over all ROI functions (those matching the filter) + pub fn roi_functions(&self) -> impl Iterator { + self.functions.iter().filter(|s| s.is_selected_roi) + } + + /// Returns the symbol at the given address, if it exists + pub fn get_symbol_at_address(&self, address: u64) -> Option<&SymbolInfo> { + self.functions.iter().find(|s| s.address == address) + } } diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 12f5c0079..c27c46ae1 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -1537,8 +1537,20 @@ impl<'a> Emu<'a> { if options.read_symbols { if let Some(elf_file) = &options.elf { println!("Loading symbols from ELF file: {elf_file}"); + + // Set ROI filter if provided + if let Some(roi_filter) = &options.roi_filter { + match elf.set_roi_filter(roi_filter) { + Ok(_) => println!("ROI filter applied: {}", roi_filter), + Err(e) => eprintln!("Invalid ROI filter regex '{}': {}", roi_filter, e), + } + } + elf.load_from_file(elf_file).unwrap(); let mut count = 0; + let mut roi_count = 0; + + // First pass: add all ROIs for symbol in elf.functions() { count += 1; self.ctx.stats.add_roi( @@ -1547,7 +1559,46 @@ impl<'a> Emu<'a> { &symbol.name, ); } - println!("Loaded {} function symbols", count); + + // Second pass: mark selected ROIs for tracking + for symbol in elf.functions() { + if symbol.is_selected_roi { + roi_count += 1; + println!(" [Selected ROI] {}", symbol.name); + self.ctx + .stats + .mark_roi_as_selected(symbol.address as u32, options.track_calls); + } + } + + println!( + "Loaded {} function symbols ({} marked as selected ROI)", + count, roi_count + ); + + // Setup call tracking if requested + if options.track_calls > 0 { + if roi_count > 0 { + println!( + "Call tracking enabled for {} ROI function(s) (tracking {} parameters)", + roi_count, options.track_calls + ); + println!("Output path: {}", options.track_output_path); + println!("Separator: '{}'", options.track_separator); + + // Initialize tracking files + if let Err(e) = self + .ctx + .stats + .init_roi_tracking(&options.track_output_path, &options.track_separator) + { + eprintln!("Error initializing ROI tracking: {}", e); + } + } else { + eprintln!("Warning: --track-calls specified but no ROI symbols found"); + } + } + count = 0; for (id, tag) in elf.profile_tags() { count += 1; @@ -1558,11 +1609,18 @@ impl<'a> Emu<'a> { self.ctx.stats.set_roi_callers(options.roi_callers); self.ctx.stats.set_top_roi_detail(options.top_roi_detail); self.ctx.stats.set_main_name(options.main_name.clone()); + self.ctx.stats.set_use_thousands_sep(!options.no_thousands_sep); + self.ctx.stats.set_top_rois_filter(options.top_roi_filter); } } if options.coverage && !options.stats { panic!("Coverage feature needs at least stats option"); } + if options.top_histogram > 0 && !options.stats { + panic!("Top Histogram feature needs at least stats option"); + } + + self.ctx.stats.set_top_histogram(options.top_histogram); self.ctx.stats.set_coverage(options.coverage); self.ctx.stats.set_legacy_stats(options.legacy_stats); @@ -1713,6 +1771,32 @@ impl<'a> Emu<'a> { if let Some(store_op_output_file) = &options.store_op_output { self.ctx.stats.flush_op_data_to_file(store_op_output_file).unwrap(); } + + // Generate disassembly if requested + if let Some(disasm_file) = &options.disasm { + println!("Writing disassembly to: {}", disasm_file); + // Try to load symbols if not already loaded + let symbols = if options.read_symbols { + if let Some(elf_file) = &options.elf { + let mut elf = ElfSymbolReader::new(); + if let Some(roi_filter) = &options.roi_filter { + let _ = elf.set_roi_filter(roi_filter); + } + elf.load_from_file(elf_file).ok(); + Some(elf) + } else { + None + } + } else { + None + }; + + if let Err(e) = self.ctx.stats.write_disassembly(self.rom, disasm_file, symbols) { + eprintln!("Error writing disassembly: {}", e); + } else { + println!("Disassembly written successfully"); + } + } } } diff --git a/emulator/src/emu_options.rs b/emulator/src/emu_options.rs index ab1056ef4..71c860c39 100644 --- a/emulator/src/emu_options.rs +++ b/emulator/src/emu_options.rs @@ -77,6 +77,10 @@ pub struct EmuOptions { /// Requires options: -S -X #[clap(short = 'T', long, value_name = "TOP_ROI", default_value = "25")] pub top_roi: usize, + /// Set the number of top frequent instructions (histogram) + /// Requires options: -X + #[clap(short = 'H', long, value_name = "TOP_HISTOGRAM", default_value = "0")] + pub top_histogram: usize, /// Set the number of top caller functions to show for each top ROI. /// Requires options: -S -X -D #[clap(short = 'C', long, value_name = "ROI_CALLERS", default_value = "10")] @@ -93,6 +97,33 @@ pub struct EmuOptions { /// Requires option: -X #[clap(long, value_name = "COVERAGE", default_value = "false")] pub coverage: bool, + /// Filter symbols using regular expression to mark as special ROI. + /// Requires option: -S + #[clap(long, value_name = "ROI_FILTER")] + pub roi_filter: Option, + /// Track function calls to filtered symbols, specifying number of parameters to log. + /// Requires options: -S --roi-filter + #[clap(long, value_name = "TRACK_CALLS", default_value = "0")] + pub track_calls: usize, + /// Separator for tracked call parameters in output files. + /// Requires option: --track-calls + #[clap(long, value_name = "TRACK_SEPARATOR", default_value = ";")] + pub track_separator: String, + /// Output directory path for tracked call files. + /// Requires option: --track-calls + #[clap(long, value_name = "TRACK_OUTPUT_PATH", default_value = ".")] + pub track_output_path: String, + /// Disable thousands separator in statistics reports. + #[clap(long, value_name = "NO_THOUSANDS_SEP", default_value = "false")] + pub no_thousands_sep: bool, + /// Consider only filtered ROIs when calculating top ROI statistics. + /// Requires options: -S -X --roi-filter + #[clap(long, value_name = "TOP_ROI_FILTER", default_value = "false")] + pub top_roi_filter: bool, + /// Generate disassembly file with execution counts (objdump-like format). + /// Requires options: -S -X + #[clap(long, value_name = "DISASM_FILE")] + pub disasm: Option, } impl Default for EmuOptions { @@ -121,7 +152,15 @@ impl Default for EmuOptions { top_roi_detail: false, legacy_stats: false, coverage: false, + top_histogram: 0, main_name: "main".to_string(), + roi_filter: None, + track_calls: 0, + track_separator: ";".to_string(), + track_output_path: ".".to_string(), + no_thousands_sep: false, + top_roi_filter: false, + disasm: None, } } } @@ -149,6 +188,14 @@ impl fmt::Display for EmuOptions { writeln!(f, "TOP_ROI: {:?}", self.top_roi)?; writeln!(f, "ROI_CALLERS: {:?}", self.roi_callers)?; writeln!(f, "TOP_ROI_DETAIL: {:?}", self.top_roi_detail)?; + writeln!(f, "TOP_HISTOGRAM: {:?}", self.top_histogram)?; + writeln!(f, "ROI_FILTER: {:?}", self.roi_filter)?; + writeln!(f, "TRACK_CALLS: {:?}", self.track_calls)?; + writeln!(f, "TRACK_SEPARATOR: {:?}", self.track_separator)?; + writeln!(f, "TRACK_OUTPUT_PATH: {:?}", self.track_output_path)?; + writeln!(f, "NO_THOUSANDS_SEP: {:?}", self.no_thousands_sep)?; + writeln!(f, "TOP_ROI_FILTER: {:?}", self.top_roi_filter)?; + writeln!(f, "DISASM: {:?}", self.disasm)?; Ok(()) } } diff --git a/emulator/src/lib.rs b/emulator/src/lib.rs index 2663497e8..f56e79e7e 100644 --- a/emulator/src/lib.rs +++ b/emulator/src/lib.rs @@ -30,6 +30,7 @@ mod stats_cost_mark; mod stats_costs; pub mod stats_coverage_report; pub mod stats_report; +mod disasm; pub use elf_symbol_reader::*; pub use emu::*; @@ -48,4 +49,5 @@ pub use stats::*; pub use stats_cost_mark::*; pub use stats_costs::*; pub use stats_coverage_report::*; +pub use disasm::*; pub use stats_report::*; diff --git a/emulator/src/regions_of_interest.rs b/emulator/src/regions_of_interest.rs index f63b18eb5..43cf2425f 100644 --- a/emulator/src/regions_of_interest.rs +++ b/emulator/src/regions_of_interest.rs @@ -1,4 +1,6 @@ use std::collections::BTreeMap; +use std::fs::{self, File}; +use std::io::{BufWriter, Write}; use crate::{get_ops_costs, StatsCosts, MAIN_COST}; @@ -8,7 +10,7 @@ pub struct CallerInfo { pub steps: usize, } -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct RegionsOfInterest { pub id: usize, pub from_pc: u32, @@ -19,6 +21,10 @@ pub struct RegionsOfInterest { pub callers: BTreeMap, pub call_stack_rc: usize, call_stack_depth: Option, + pub is_selected_roi: bool, + pub track_calls: usize, + tracked_calls: Vec>, + track_file: Option>, } impl RegionsOfInterest { @@ -33,7 +39,76 @@ impl RegionsOfInterest { callers: BTreeMap::new(), call_stack_rc: 0, call_stack_depth: None, + is_selected_roi: false, + track_calls: 0, + tracked_calls: Vec::new(), + track_file: None, + } + } + + pub fn set_selected_roi(&mut self, track_calls: usize) { + self.is_selected_roi = true; + self.track_calls = track_calls; + } + + pub fn init_tracking( + &mut self, + output_path: &str, + separator: &str, + filename: &str, + ) -> std::io::Result<()> { + if self.track_calls == 0 { + return Ok(()); + } + + // Create output directory if it doesn't exist + fs::create_dir_all(output_path)?; + + let filepath = format!("{}/{}.txt", output_path, filename); + let file = File::create(&filepath)?; + let mut writer = BufWriter::new(file); + + // Write header + writeln!(writer, "# ROI: {} (PC: 0x{:08x}-0x{:08x})", self.name, self.from_pc, self.to_pc)?; + writeln!(writer, "# Separator: '{}'", separator)?; + writeln!(writer, "# Parameters: a0-a{}", self.track_calls.min(8) - 1)?; + + self.track_file = Some(writer); + Ok(()) + } + + pub fn track_call_parameters(&mut self, registers: &[u64], separator: &str, caller: &str) { + if self.track_calls == 0 { + return; + } + + // RISC-V registers a0-a7 are at indices 10-17 + let num_params = self.track_calls.min(8); + let mut params = Vec::with_capacity(num_params); + + for i in 0..num_params { + if 10 + i < registers.len() { + params.push(registers[10 + i]); + } else { + params.push(0); + } } + + // Write to file if available + if let Some(ref mut file) = self.track_file { + let line = params.iter().map(|p| p.to_string()).collect::>().join(separator); + if caller.is_empty() { + let _ = writeln!(file, "{line}"); + } else { + let _ = writeln!(file, "{line};{caller}"); + } + } + + self.tracked_calls.push(params); + } + + pub fn get_tracked_calls(&self) -> &[Vec] { + &self.tracked_calls } pub fn contains(&self, pc: u32) -> bool { pc >= self.from_pc && pc <= self.to_pc diff --git a/emulator/src/stats.rs b/emulator/src/stats.rs index 7e775eff0..84ddb3f1b 100644 --- a/emulator/src/stats.rs +++ b/emulator/src/stats.rs @@ -40,7 +40,7 @@ const OP_DATA_BUFFER_DEFAULT_CAPACITY: usize = 128 * 1024 * 1024; const REG_RA_IDX: usize = 1; /// Keeps statistics of the emulator operations -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Stats { /// Counter of FROPS (FRequentOPs) frops: u64, @@ -65,6 +65,7 @@ pub struct Stats { roi_callers: usize, top_rois_detail: bool, coverage: bool, + top_histogram: usize, legacy_stats: bool, /// PC histogram, i.e. number of times each PC was executed pc_histogram: HashMap, @@ -78,6 +79,9 @@ pub struct Stats { individual_cost_marks: bool, main_name: String, profile_tags: HashMap, + track_separator: String, + use_thousands_sep: bool, + top_rois_filter: bool, #[cfg(feature = "debug_stats_trace")] debug_step_stack: Vec, #[cfg(feature = "debug_stats_trace")] @@ -116,6 +120,10 @@ impl Default for Stats { individual_cost_marks: false, main_name: "main".to_string(), profile_tags: HashMap::new(), + top_histogram: 0, + track_separator: ";".to_string(), + use_thousands_sep: true, + top_rois_filter: false, #[cfg(feature = "debug_stats_trace")] debug_step_stack: Vec::new(), #[cfg(feature = "debug_stats_trace")] @@ -317,9 +325,12 @@ impl Stats { if pc >= self.rois[roi_index].from_pc && pc <= self.rois[roi_index].to_pc { if self.is_call { assert!(!self.is_return); - if let Some(previous_roi_index) = previous_roi_index { + let caller_name = if let Some(previous_roi_index) = previous_roi_index { self.rois[previous_roi_index].caller_call(); - } + &self.rois[previous_roi_index].name.clone() + } else { + "" + }; #[cfg(feature = "debug_call_stack")] println!( "CALL_STACK_DEBUG: CALL P_PC:0x{:08x} => PC:0x{pc:08x} CALLER_ROI:{} CALLED_ROI:{}", @@ -338,6 +349,16 @@ impl Stats { self.call_return_reg = 0; self.rois[roi_index].call(previous_roi_index, self.call_stack.len()); + + // Track call parameters for selected ROIs + if self.rois[roi_index].is_selected_roi && self.rois[roi_index].track_calls > 0 + { + self.rois[roi_index].track_call_parameters( + regs, + &self.track_separator, + caller_name, + ); + } } else if !self.is_return { // JMP: This is a tail call. Replace the top of the call stack if it exists if let Some(top) = self.call_stack.last_mut() { @@ -588,6 +609,7 @@ impl Stats { .rois .iter() .enumerate() + .filter(|(_, roi)| !self.top_rois_filter || roi.is_selected_roi) .map(|(index, roi)| (index, if by_step { roi.get_steps() } else { roi.get_cost() })) .collect(); top_rois.sort_by(|a, b| b.1.cmp(&a.1)); @@ -612,7 +634,13 @@ impl Stats { self.ops_cost = ops_cost; self.precompiled_cost = precompiled_cost; } - pub fn report_opcodes(&self, report: &mut StatsReport, ops: &[u64], title: &str) { + pub fn report_opcodes( + &self, + report: &mut StatsReport, + ops: &[u64], + title: &str, + steps_perc: bool, + ) { let ranks = get_ops_ranks(ops); for (opcode, op_count) in ops.iter().enumerate() { if opcode > 1 && *op_count > 0 { @@ -622,12 +650,21 @@ impl Stats { } else { String::new() }; - report.add_count_cost_perc( - &format!("{title} {:}", inst.name()), - *op_count, - *op_count * inst.steps(), - &rank, - ); + if steps_perc { + report.add_count_cost_perc2( + &format!("{title} {:}", inst.name()), + *op_count, + *op_count * inst.steps(), + &rank, + ); + } else { + report.add_count_cost_perc( + &format!("{title} {:}", inst.name()), + *op_count, + *op_count * inst.steps(), + &rank, + ); + } } } } @@ -696,6 +733,7 @@ impl Stats { let base_cost = BASE_COST as u64; let total_cost = base_cost + mem_cost + main_cost + ops_cost + precompiled_cost; let mut report = StatsReport::new(); + report.use_thousands_sep = self.use_thousands_sep; report.set_total_cost(total_cost); report.set_steps(self.costs.steps); report.title_cost("REPORT", ""); @@ -712,8 +750,8 @@ impl Stats { report.ln(); report.add_cost_perc("FROPS", self.frops_cost); report.add_perc("RAM USAGE", self.costs.mops.get_max_ram_address() - RAM_ADDR + 1, 1 << 29); - report.title_count_cost_perc("COST BY OPCODE", "COUNT", "COST", " RANK"); - self.report_opcodes(&mut report, &self.costs.ops, "OP"); + report.title_count_cost_perc2("COST BY OPCODE", "COUNT", "COST", " RANK"); + self.report_opcodes(&mut report, &self.costs.ops, "OP", true); report.title_count_perc_cost_perc("FROPS BY OPCODE", "COUNT", "HIT", "COST", " RANK"); self.report_opcodes_hit(&mut report, &self.costs.frops_ops, &self.costs.ops, "FROP"); @@ -761,6 +799,7 @@ impl Stats { for index in final_top_cost_rois.iter() { let roi = &self.rois[*index]; let mut roi_report = StatsReport::new(); + roi_report.use_thousands_sep = self.use_thousands_sep; roi_report.set_total_cost(roi.get_cost()); roi_report.set_steps(roi.get_steps()); roi_report.title(&format!("DETAIL FUNCTION {}", roi.name)); @@ -769,7 +808,7 @@ impl Stats { roi_report.set_identation(1); roi_report.title_count_cost_perc("COST BY OPCODE", "COUNT", "COST", " RANK"); - self.report_opcodes(&mut roi_report, roi.get_ops_costs(), "OP"); + self.report_opcodes(&mut roi_report, roi.get_ops_costs(), "OP", false); roi_report.title_top_count_perc("TOP STEP CALLERS (calls, steps)"); let mut callers: Vec<_> = roi.get_callers().collect(); @@ -845,6 +884,54 @@ impl Stats { } } } + if self.top_histogram > 0 { + report.title_autowidth("TOP PC HISTOGRAM (EXECUTIONS, % EXECUTIONS, PC)"); + + // Convert HashMap to Vec and sort by execution count (descending), then by PC (ascending) + let mut pc_vec: Vec<_> = self.pc_histogram.iter().collect(); + pc_vec.sort_by(|a, b| b.1.cmp(a.1).then_with(|| a.0.cmp(b.0))); + + // Show only top N entries + let mut previous_count = 0; + let mut initial_address = 0; + let mut block_count = 0; + let mut block_label = ""; + let last_index = std::cmp::min(self.top_histogram, pc_vec.len()) - 1; + for (index, (pc, count)) in pc_vec.iter().take(self.top_histogram).enumerate() { + let is_same_block = previous_count == **count + && **pc > initial_address + && (**pc - initial_address) < 512; + + if is_same_block { + block_count += **count; + } else { + if block_count > 0 { + report.add_top_step_perc( + &format!(" ----------- {block_label}\n"), + block_count, + ); + } + previous_count = **count; + initial_address = **pc; + block_count = **count; + block_label = if let Some((_, index)) = + self.rois_by_address.range(..=initial_address as u32).next_back() + { + &self.rois[*index as usize].name + } else { + &"" + }; + } + let instruction = rom.get_instruction(**pc); + let pc_str = format!(" 0x{pc:08x}: {}", instruction.verbose); + report.add_top_step_perc(&pc_str, **count); + if index == last_index { + report + .add_top_step_perc(&format!(" ----------- {block_label}\n"), block_count); + } + } + } + report.output } pub fn add_profile_tag(&mut self, id: u16, name: &str) { @@ -856,9 +943,51 @@ impl Stats { self.rois.push(roi); self.rois_by_address.insert(from_pc, index); } + pub fn mark_roi_as_selected(&mut self, from_pc: u32, track_calls: usize) { + if let Some(&index) = self.rois_by_address.get(&from_pc) { + if let Some(roi) = self.rois.get_mut(index as usize) { + roi.set_selected_roi(track_calls); + } + } + } + pub fn init_roi_tracking(&mut self, output_path: &str, separator: &str) -> std::io::Result<()> { + self.track_separator = separator.to_string(); + + // Track used filenames to detect collisions + let mut used_filenames = std::collections::HashSet::new(); + + for roi in &mut self.rois { + if roi.is_selected_roi && roi.track_calls > 0 { + // Clean function name: keep only alphanumeric and underscore + let clean_name: String = + roi.name.chars().filter(|c| c.is_alphanumeric() || *c == '_').collect(); + + // Check for collision + let filename = if used_filenames.contains(&clean_name) { + // Collision detected, add ROI id + format!("{}_roi_{}", clean_name, roi.id) + } else { + clean_name.clone() + }; + + used_filenames.insert(clean_name); + roi.init_tracking(output_path, separator, &filename)?; + } + } + Ok(()) + } + pub fn set_track_separator(&mut self, separator: String) { + self.track_separator = separator; + } + pub fn set_use_thousands_sep(&mut self, value: bool) { + self.use_thousands_sep = value; + } pub fn set_top_rois(&mut self, value: usize) { self.top_rois = value; } + pub fn set_top_histogram(&mut self, value: usize) { + self.top_histogram = value; + } pub fn set_legacy_stats(&mut self, value: bool) { self.legacy_stats = value; } @@ -874,6 +1003,31 @@ impl Stats { pub fn set_main_name(&mut self, value: String) { self.main_name = value; } + pub fn set_top_rois_filter(&mut self, value: bool) { + self.top_rois_filter = value; + } + + /// Write disassembly to file with execution counts + pub fn write_disassembly( + &self, + rom: &ZiskRom, + path: &str, + symbols: Option, + ) -> std::io::Result<()> { + use crate::DisasmWriter; + + let mut disasm_writer = DisasmWriter::new(path)?; + disasm_writer.set_pc_histogram(self.pc_histogram.clone()); + if let Some(syms) = symbols { + disasm_writer.set_symbols(syms); + } + disasm_writer.write_header("ZisK Disassembly")?; + disasm_writer.write_disassembly(rom)?; + disasm_writer.flush()?; + + Ok(()) + } + #[cfg(feature = "debug_stats_trace")] pub fn debug_stats_trace(&mut self, pc: u64) { if self.costs.steps == 1 || self.previous_roi != self.current_roi { diff --git a/emulator/src/stats_report.rs b/emulator/src/stats_report.rs index 3f5f44207..f081ac953 100644 --- a/emulator/src/stats_report.rs +++ b/emulator/src/stats_report.rs @@ -9,6 +9,7 @@ pub struct StatsReport { pub label_width: usize, pub short_label_width: usize, pub label_width_stack: Vec, + pub use_thousands_sep: bool, } impl Default for StatsReport { fn default() -> Self { @@ -26,6 +27,7 @@ impl StatsReport { label_width: 24, short_label_width: 10, label_width_stack: Vec::new(), + use_thousands_sep: true, } } @@ -54,6 +56,14 @@ impl StatsReport { } } + fn format_number(&self, num: u64) -> String { + if self.use_thousands_sep { + num.to_formatted_string(&Locale::en) + } else { + num.to_string() + } + } + pub fn add(&mut self, text: &str) { self.output += text; } @@ -62,7 +72,7 @@ impl StatsReport { "{}{:15}\n", self.identation, label, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), label_width = self.label_width ); } @@ -97,7 +107,7 @@ impl StatsReport { self.output += &format!( "{}{label:15} {:6.2}%\n", self.identation, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), (cost as f64 * 100.0) / total as f64, label_width = self.label_width, ); @@ -107,7 +117,7 @@ impl StatsReport { self.output += &format!( "{}{label:15} {:6.2}%\n", self.identation, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), cost as f64 / self.cost_divisor, label_width = self.label_width, ); @@ -137,7 +147,7 @@ impl StatsReport { self.output += &format!( "{}{:>15} {:6.2}% {label}\n", self.identation, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), cost as f64 / self.cost_divisor ); } @@ -147,7 +157,7 @@ impl StatsReport { self.output += &format!( "{}{:>15} {:6.2}% {depth:2} {label}\n", self.identation, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), cost as f64 / self.cost_divisor ); return; @@ -155,7 +165,7 @@ impl StatsReport { self.output += &format!( "{}{:>15} {:6.2}% {label}\n", self.identation, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), cost as f64 / self.cost_divisor ); } @@ -164,9 +174,9 @@ impl StatsReport { self.output += &format!( "{}{:>15} {:6.2}% {:>10} {label}\n", self.identation, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), cost as f64 / self.cost_divisor, - calls.to_formatted_string(&Locale::en) + self.format_number(calls as u64) ); } @@ -174,9 +184,9 @@ impl StatsReport { self.output += &format!( "{}{:>15} {:6.2}% {:>10} {label}\n", self.identation, - steps.to_formatted_string(&Locale::en), + self.format_number(steps), steps as f64 / self.step_divisor, - calls.to_formatted_string(&Locale::en) + self.format_number(calls as u64) ); } @@ -184,7 +194,7 @@ impl StatsReport { self.output += &format!( "{}{:>15} {:6.2}% {label}\n", self.identation, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), cost as f64 / self.step_divisor ); } @@ -194,7 +204,7 @@ impl StatsReport { self.output += &format!( "{}{:>15} {:6.2}% {depth:2} {label}\n", self.identation, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), cost as f64 / self.step_divisor ); return; @@ -202,7 +212,7 @@ impl StatsReport { self.output += &format!( "{}{:>15} {:6.2}% {label}\n", self.identation, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), cost as f64 / self.step_divisor ); } @@ -219,8 +229,8 @@ impl StatsReport { self.output += &format!( "{}{:>15} {:>15} {:6.2}% {label}\n", self.identation, - count.to_formatted_string(&Locale::en), - step.to_formatted_string(&Locale::en), + self.format_number(count), + self.format_number(step), step as f64 / self.step_divisor ); } @@ -243,8 +253,34 @@ impl StatsReport { "{}{:15} {:>15} {:6.2}%{comment}\n", self.identation, label, - count.to_formatted_string(&Locale::en), - cost.to_formatted_string(&Locale::en), + self.format_number(count), + self.format_number(cost), + cost as f64 / self.cost_divisor, + label_width = self.label_width, + ); + } + + pub fn title_count_cost_perc2( + &mut self, + label: &str, + count_label: &str, + cost_label: &str, + comment: &str, + ) { + self.line_from_title(&format!( + "{label:15} % {cost_label:>15} %{comment}", + label_width = self.label_width, + )); + } + + pub fn add_count_cost_perc2(&mut self, label: &str, count: u64, cost: u64, comment: &str) { + self.output += &format!( + "{}{:15} {:6.2}% {:>15} {:6.2}%{comment}\n", + self.identation, + label, + self.format_number(count), + count as f64 / self.step_divisor, + self.format_number(cost), cost as f64 / self.cost_divisor, label_width = self.label_width, ); @@ -276,9 +312,9 @@ impl StatsReport { "{}{:15} {:6.2}% {:>15} {:6.2}%{comment}\n", self.identation, label, - count.to_formatted_string(&Locale::en), + self.format_number(count), perc, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), cost as f64 / self.cost_divisor, label_width = self.label_width, ); @@ -320,16 +356,16 @@ impl StatsReport { "{}{:10} {:>10} {:>15} {:6.2}% {:>15} {:6.2}% {:>15} {:>15} {:>15} {:>15}{comment}\n", self.identation, label, - index.to_formatted_string(&Locale::en), - count.to_formatted_string(&Locale::en), - step.to_formatted_string(&Locale::en), + self.format_number(index as u64), + self.format_number(count), + self.format_number(step), step as f64 / self.step_divisor, - cost.to_formatted_string(&Locale::en), + self.format_number(cost), cost as f64 / self.cost_divisor, - cost_main.to_formatted_string(&Locale::en), - cost_ops.to_formatted_string(&Locale::en), - cost_precomp.to_formatted_string(&Locale::en), - cost_mem.to_formatted_string(&Locale::en), + self.format_number(cost_main), + self.format_number(cost_ops), + self.format_number(cost_precomp), + self.format_number(cost_mem), label_width = self.label_width, ); } diff --git a/pil/config.pil b/pil/config.pil index 954a45829..0ac39215d 100644 --- a/pil/config.pil +++ b/pil/config.pil @@ -4,3 +4,4 @@ const int MEM_STEP_BITS = MAIN_STEP_BITS + 2; const int REG_STEP_BITS = MAIN_STEP_BITS + 2; const int ADDR_BITS = 32; const int ADDR_W_BITS = ADDR_BITS - 3; +const int EXTRA_PARAMS_ADDR = 0xA0000F00; \ No newline at end of file diff --git a/pil/operations.pil b/pil/operations.pil index 0aefb95ab..de2ef90e6 100644 --- a/pil/operations.pil +++ b/pil/operations.pil @@ -85,6 +85,17 @@ const int OP_REM_W = 0xBF; const int OP_DMA_MEMCPY = 0xD0; const int OP_DMA_MEMCMP = 0xD1; +const int OP_DMA_INPUTCPY = 0xD2; +const int __OP_DMA_MEMSET__ = 0xD3; // not implemented, unsual and too expensive +const int __OP_DMA_MEMEQ__ = 0xD4; // main don't known in compilation time + +const int OP_DMA_X_OFFSET = 6; + +const int OP_DMA_XMEMCPY = OP_DMA_MEMCPY + OP_DMA_X_OFFSET; +const int OP_DMA_XMEMCMP = OP_DMA_MEMCMP + OP_DMA_X_OFFSET; +const int OP_DMA_XMEMSET = __OP_DMA_MEMSET__ + OP_DMA_X_OFFSET; +const int OP_DMA_XMEMEQ = __OP_DMA_MEMEQ__ + OP_DMA_X_OFFSET; + const int OP_POSEIDON2 = 0xE1; @@ -116,3 +127,36 @@ const int OP_COMPLEX_SUB_BN254 = 0xFD; const int OP_COMPLEX_MUL_BN254 = 0xFE; const int OP_HALT = 0xFF; + +function assumes_operation (const expr op, const expr a[] = [0,0], const expr b[] = [0,0], const expr c[] = [0,0], + const expr flag = 0, const expr main_step = 0, + const expr extended_arg = 0, + const expr sel = 1, + const expr extra_args[] = [0]) +{ + assert(length(extra_args) == 1); + lookup_assumes(OPERATION_BUS_ID, [op, ...a, ...b, ...c, flag, main_step, extended_arg, ...extra_args], + sel:); +} + +function proves_operation (const expr op, const expr a[] = [0, 0], const expr b[] = [0, 0], const expr c[] = [0, 0], + const expr flag = 0, const expr main_step = 0, + const expr extended_arg = 0, + const expr mul = 1, + const expr extra_args[] = [0], + const int table_id = -1) +{ + assert(length(extra_args) == 1); + lookup_proves(OPERATION_BUS_ID, [op, ...a, ...b, ...c, flag, main_step, extended_arg, ...extra_args], table_id:, mul:); +} + + +function assumes_padding_operation (const expr op, const expr a[] = [0,0], const expr b[] = [0,0], const expr c[] = [0,0], + const expr flag = 0, const expr main_step = 0, + const expr extended_arg = 0, + const expr extra_args[] = [0], + const expr padding_size = 1) +{ + assert(length(extra_args) == 1); + direct_update_assumes(OPERATION_BUS_ID, [op, ...a, ...b, ...c, flag, main_step, extended_arg, ...extra_args], sel:padding_size); +} diff --git a/pil/opids.pil b/pil/opids.pil index c1ee73de8..d7c68cec5 100644 --- a/pil/opids.pil +++ b/pil/opids.pil @@ -34,6 +34,7 @@ const int KECCAKF_TABLE_ID = 126; const int DMA_BUS_ID = 8000; const int DMA_ROM_ID = 8001; const int DMA_PRE_POST_TABLE_ID = 8002; +const int DMA_BYTE_CMP_TABLE_ID = 8003; // Ranges const int DUAL_RANGE_7_BITS_ID = 77; diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index eb94603e0..145d4081f 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -5,12 +5,12 @@ #![allow(non_upper_case_globals)] #![allow(dead_code)] +use fields::PrimeField64; use proofman_common as common; use proofman_common::GenericTrace; use proofman_common::PackedInfoConst; pub use proofman_macros::trace_row; pub use proofman_macros::values; -use fields::PrimeField64; use std::fmt; #[allow(dead_code)] @@ -78,13 +78,11 @@ pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[24]; pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[25]; - //PUBLICS use serde::Deserialize; use serde::Serialize; use serde_arrays; - fn default_array_rom_root() -> [u64; 4] { [0; 4] } @@ -93,33 +91,28 @@ fn default_array_inputs() -> [u64; 64] { [0; 64] } - #[derive(Debug, Serialize, Deserialize)] pub struct ZiskPublics { #[serde(default = "default_array_rom_root", with = "serde_arrays")] pub rom_root: [u64; 4], #[serde(default = "default_array_inputs", with = "serde_arrays")] pub inputs: [u64; 64], - } impl Default for ZiskPublics { fn default() -> Self { - Self { - rom_root: [0; 4], - inputs: [0; 64], - } + Self { rom_root: [0; 4], inputs: [0; 64] } } } values!(ZiskPublicValues { rom_root: [F; 4], inputs: [F; 64], }); - + values!(ZiskProofValues { enable_input_data: F, enable_rom_data: F, enable_dma_64_aligned: F, enable_dma_64_aligned_input: F, enable_dma_unaligned: F, }); - + trace_row!(DmaFixedRow { __L1__: F, }); @@ -130,10 +123,8 @@ trace_row!(DmaTraceRow { }); pub type DmaTrace = GenericTrace, 2097152, 0, 0>; - pub type DmaTracePacked = GenericTrace, 2097152, 0, 0>; - trace_row!(Dma64AlignedFixedRow { __L1__: F, }); @@ -144,10 +135,8 @@ trace_row!(Dma64AlignedTraceRow { }); pub type Dma64AlignedTrace = GenericTrace, 2097152, 0, 1>; - pub type Dma64AlignedTracePacked = GenericTrace, 2097152, 0, 1>; - trace_row!(DmaUnalignedFixedRow { __L1__: F, }); @@ -158,10 +147,8 @@ trace_row!(DmaUnalignedTraceRow { }); pub type DmaUnalignedTrace = GenericTrace, 2097152, 0, 2>; - pub type DmaUnalignedTracePacked = GenericTrace, 2097152, 0, 2>; - trace_row!(DmaPrePostFixedRow { __L1__: F, }); @@ -172,10 +159,8 @@ trace_row!(DmaPrePostTraceRow { }); pub type DmaPrePostTrace = GenericTrace, 2097152, 0, 3>; - pub type DmaPrePostTracePacked = GenericTrace, 2097152, 0, 3>; - trace_row!(MainFixedRow { SEGMENT_L1: F, SEGMENT_STEP: F, __L1__: F, }); @@ -186,10 +171,8 @@ trace_row!(MainTraceRow { }); pub type MainTrace = GenericTrace, 4194304, 0, 4>; - pub type MainTracePacked = GenericTrace, 4194304, 0, 4>; - trace_row!(RomFixedRow { __L1__: F, }); @@ -200,7 +183,6 @@ trace_row!(RomTraceRow { }); pub type RomTrace = GenericTrace, 4194304, 0, 5>; - trace_row!(MemFixedRow { SEGMENT_L1: F, __L1__: F, }); @@ -211,10 +193,8 @@ trace_row!(MemTraceRow { }); pub type MemTrace = GenericTrace, 4194304, 0, 6>; - pub type MemTracePacked = GenericTrace, 4194304, 0, 6>; - trace_row!(RomDataFixedRow { SEGMENT_L1: F, __L1__: F, }); @@ -225,10 +205,8 @@ trace_row!(RomDataTraceRow { }); pub type RomDataTrace = GenericTrace, 2097152, 0, 7>; - pub type RomDataTracePacked = GenericTrace, 2097152, 0, 7>; - trace_row!(InputDataFixedRow { SEGMENT_L1: F, __L1__: F, }); @@ -239,10 +217,8 @@ trace_row!(InputDataTraceRow { }); pub type InputDataTrace = GenericTrace, 2097152, 0, 8>; - pub type InputDataTracePacked = GenericTrace, 2097152, 0, 8>; - trace_row!(MemAlignFixedRow { L1: F, __L1__: F, }); @@ -253,10 +229,8 @@ trace_row!(MemAlignTraceRow { }); pub type MemAlignTrace = GenericTrace, 2097152, 0, 9>; - pub type MemAlignTracePacked = GenericTrace, 2097152, 0, 9>; - trace_row!(MemAlignByteFixedRow { __L1__: F, }); @@ -267,10 +241,8 @@ trace_row!(MemAlignByteTraceRow { }); pub type MemAlignByteTrace = GenericTrace, 4194304, 0, 10>; - pub type MemAlignByteTracePacked = GenericTrace, 4194304, 0, 10>; - trace_row!(MemAlignReadByteFixedRow { __L1__: F, }); @@ -281,9 +253,8 @@ trace_row!(MemAlignReadByteTraceRow { }); pub type MemAlignReadByteTrace = GenericTrace, 4194304, 0, 11>; - -pub type MemAlignReadByteTracePacked = GenericTrace, 4194304, 0, 11>; - +pub type MemAlignReadByteTracePacked = + GenericTrace, 4194304, 0, 11>; trace_row!(MemAlignWriteByteFixedRow { __L1__: F, @@ -295,9 +266,8 @@ trace_row!(MemAlignWriteByteTraceRow { }); pub type MemAlignWriteByteTrace = GenericTrace, 4194304, 0, 12>; - -pub type MemAlignWriteByteTracePacked = GenericTrace, 4194304, 0, 12>; - +pub type MemAlignWriteByteTracePacked = + GenericTrace, 4194304, 0, 12>; trace_row!(ArithFixedRow { __L1__: F, @@ -309,10 +279,8 @@ trace_row!(ArithTraceRow { }); pub type ArithTrace = GenericTrace, 2097152, 0, 13>; - pub type ArithTracePacked = GenericTrace, 2097152, 0, 13>; - trace_row!(BinaryFixedRow { __L1__: F, }); @@ -323,10 +291,8 @@ trace_row!(BinaryTraceRow { }); pub type BinaryTrace = GenericTrace, 4194304, 0, 14>; - pub type BinaryTracePacked = GenericTrace, 4194304, 0, 14>; - trace_row!(BinaryAddFixedRow { __L1__: F, }); @@ -337,10 +303,8 @@ trace_row!(BinaryAddTraceRow { }); pub type BinaryAddTrace = GenericTrace, 4194304, 0, 15>; - pub type BinaryAddTracePacked = GenericTrace, 4194304, 0, 15>; - trace_row!(BinaryExtensionFixedRow { __L1__: F, }); @@ -351,9 +315,8 @@ trace_row!(BinaryExtensionTraceRow { }); pub type BinaryExtensionTrace = GenericTrace, 4194304, 0, 16>; - -pub type BinaryExtensionTracePacked = GenericTrace, 4194304, 0, 16>; - +pub type BinaryExtensionTracePacked = + GenericTrace, 4194304, 0, 16>; trace_row!(Add256FixedRow { __L1__: F, @@ -365,10 +328,8 @@ trace_row!(Add256TraceRow { }); pub type Add256Trace = GenericTrace, 1048576, 0, 17>; - pub type Add256TracePacked = GenericTrace, 1048576, 0, 17>; - trace_row!(ArithEqFixedRow { CLK_0: F, __L1__: F, }); @@ -379,10 +340,8 @@ trace_row!(ArithEqTraceRow { }); pub type ArithEqTrace = GenericTrace, 1048576, 0, 18>; - pub type ArithEqTracePacked = GenericTrace, 1048576, 0, 18>; - trace_row!(ArithEq384FixedRow { CLK_0: F, __L1__: F, }); @@ -393,10 +352,8 @@ trace_row!(ArithEq384TraceRow { }); pub type ArithEq384Trace = GenericTrace, 1048576, 0, 19>; - pub type ArithEq384TracePacked = GenericTrace, 1048576, 0, 19>; - trace_row!(KeccakfFixedRow { CLK_0: F, __L1__: F, }); @@ -407,10 +364,8 @@ trace_row!(KeccakfTraceRow { }); pub type KeccakfTrace = GenericTrace, 131072, 0, 20>; - pub type KeccakfTracePacked = GenericTrace, 131072, 0, 20>; - trace_row!(Sha256fFixedRow { CLK_0: F, __L1__: F, }); @@ -421,10 +376,8 @@ trace_row!(Sha256fTraceRow { }); pub type Sha256fTrace = GenericTrace, 262144, 0, 21>; - pub type Sha256fTracePacked = GenericTrace, 262144, 0, 21>; - trace_row!(Poseidon2FixedRow { CLK_0: F, __L1__: F, }); @@ -435,10 +388,8 @@ trace_row!(Poseidon2TraceRow { }); pub type Poseidon2Trace = GenericTrace, 131072, 0, 22>; - pub type Poseidon2TracePacked = GenericTrace, 131072, 0, 22>; - trace_row!(SpecifiedRangesFixedRow { OPID: [F; 29], VALS: [F; 29], __L1__: F, }); @@ -449,7 +400,6 @@ trace_row!(SpecifiedRangesTraceRow { }); pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 23>; - trace_row!(VirtualTable0FixedRow { UID: [F; 8], column: [F; 43], __L1__: F, }); @@ -460,7 +410,6 @@ trace_row!(VirtualTable0TraceRow { }); pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 24>; - trace_row!(VirtualTable1FixedRow { UID: [F; 8], column: [F; 72], __L1__: F, }); @@ -471,13 +420,11 @@ trace_row!(VirtualTable1TraceRow { }); pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 25>; - trace_row!(RomRomTraceRow { line: F, a_offset_imm0: F, a_imm1: F, b_offset_imm0: F, b_imm1: F, ind_width: F, op: F, store_offset: F, jmp_offset1: F, jmp_offset2: F, flags: F, }); pub type RomRomTrace = GenericTrace, 4194304, 0, 5, 0>; - values!(Dma64AlignedAirValues { segment_id: F, segment_previous_seq_end: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count: F, segment_previous_is_mem_eq: F, segment_last_seq_end: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count: F, segment_last_is_mem_eq: F, is_last_segment: F, segment_previous_src64: F, segment_last_src64: F, last_count_chunk: [F; 2], padding_size: F, im_direct: [FieldExtension; 5], }); @@ -631,114 +578,324 @@ values!(VirtualTable1AirGroupValues { }); pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ - (0, 0, PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[1, 24, 1, 9, 22, 7, 3, 22, 7, 3, 36, 1, 1, 1, 1, 3, 9, 3], - }), - (0, 1, PackedInfoConst { - is_packed: true, - num_packed_words: 7, - unpack_info: &[36, 29, 32, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 29, 1], - }), - (0, 2, PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[36, 29, 29, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 32, 32], - }), - (0, 3, PackedInfoConst { - is_packed: true, - num_packed_words: 7, - unpack_info: &[36, 29, 29, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32], - }), - (0, 4, PackedInfoConst { - is_packed: true, - num_packed_words: 14, - unpack_info: &[32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, 64, 1, 64, 64, 1, 32, 38, 38, 38, 32, 32, 1, 1, 1], - }), - (0, 6, PackedInfoConst { - is_packed: true, - num_packed_words: 4, - unpack_info: &[29, 38, 1, 1, 38, 1, 32, 32, 1, 40, 22, 16, 1], - }), - (0, 7, PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[29, 38, 1, 1, 32, 32], - }), - (0, 8, PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[29, 38, 1, 1, 16, 16, 16, 16, 1], - }), - (0, 9, PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[29, 3, 4, 1, 8, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 40, 64, 1, 32, 32], - }), - (0, 10, PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 1, 32, 32, 8], - }), - (0, 11, PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[1, 1, 1, 32, 32, 16, 8, 8, 29, 40], - }), - (0, 12, PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 32, 32], - }), - (0, 13, PackedInfoConst { - is_packed: true, - num_packed_words: 17, - unpack_info: &[64, 64, 64, 64, 64, 64, 64, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 64, 64, 64, 1, 1, 1, 1, 1, 64, 8, 32, 1, 7, 7], - }), - (0, 14, PackedInfoConst { - is_packed: true, - num_packed_words: 4, - unpack_info: &[7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1], - }), - (0, 15, PackedInfoConst { - is_packed: true, - num_packed_words: 4, - unpack_info: &[32, 32, 32, 32, 16, 16, 16, 16, 1, 1], - }), - (0, 16, PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 1, 32, 32], - }), - (0, 17, PackedInfoConst { - is_packed: true, - num_packed_words: 15, - unpack_info: &[32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 40, 1, 1], - }), - (0, 18, PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], - }), - (0, 19, PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], - }), - (0, 20, PackedInfoConst { - is_packed: true, - num_packed_words: 209, - unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 8, 40], - }), - (0, 21, PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 40, 1, 1], - }), - (0, 22, PackedInfoConst { - is_packed: true, - num_packed_words: 17, - unpack_info: &[1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40], - }), -]; \ No newline at end of file + ( + 0, + 0, + PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[1, 24, 1, 9, 22, 7, 3, 22, 7, 3, 36, 1, 1, 1, 1, 3, 9, 3], + }, + ), + ( + 0, + 1, + PackedInfoConst { + is_packed: true, + num_packed_words: 7, + unpack_info: &[36, 29, 32, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 29, 1], + }, + ), + ( + 0, + 2, + PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[ + 36, 29, 29, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 32, 32, + ], + }, + ), + ( + 0, + 3, + PackedInfoConst { + is_packed: true, + num_packed_words: 7, + unpack_info: &[ + 36, 29, 29, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, + ], + }, + ), + ( + 0, + 4, + PackedInfoConst { + is_packed: true, + num_packed_words: 14, + unpack_info: &[ + 32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, + 64, 1, 64, 64, 1, 32, 38, 38, 38, 32, 32, 1, 1, 1, + ], + }, + ), + ( + 0, + 6, + PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[29, 38, 1, 1, 38, 1, 32, 32, 1, 40, 22, 16, 1], + }, + ), + ( + 0, + 7, + PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[29, 38, 1, 1, 32, 32], + }, + ), + ( + 0, + 8, + PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[29, 38, 1, 1, 16, 16, 16, 16, 1], + }, + ), + ( + 0, + 9, + PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[ + 29, 3, 4, 1, 8, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 40, 64, 1, + 32, 32, + ], + }, + ), + ( + 0, + 10, + PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 1, 32, 32, 8], + }, + ), + ( + 0, + 11, + PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[1, 1, 1, 32, 32, 16, 8, 8, 29, 40], + }, + ), + ( + 0, + 12, + PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 32, 32], + }, + ), + ( + 0, + 13, + PackedInfoConst { + is_packed: true, + num_packed_words: 17, + unpack_info: &[ + 64, 64, 64, 64, 64, 64, 64, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 1, 1, 1, 1, 1, 1, 1, 64, 64, 64, 1, 1, 1, 1, 1, 64, 8, 32, 1, 7, 7, + ], + }, + ), + ( + 0, + 14, + PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[ + 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, + ], + }, + ), + ( + 0, + 15, + PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[32, 32, 32, 32, 16, 16, 16, 16, 1, 1], + }, + ), + ( + 0, + 16, + PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[ + 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 1, 32, 32, + ], + }, + ), + ( + 0, + 17, + PackedInfoConst { + is_packed: true, + num_packed_words: 15, + unpack_info: &[ + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, + 40, 1, 1, + ], + }, + ), + ( + 0, + 18, + PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[ + 16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40, + ], + }, + ), + ( + 0, + 19, + PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[ + 16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, + 1, 1, 64, 64, 64, 64, 64, 64, 40, + ], + }, + ), + ( + 0, + 20, + PackedInfoConst { + is_packed: true, + num_packed_words: 209, + unpack_info: &[ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 8, 40, + ], + }, + ), + ( + 0, + 21, + PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 40, 1, 1, + ], + }, + ), + ( + 0, + 22, + PackedInfoConst { + is_packed: true, + num_packed_words: 17, + unpack_info: &[ + 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40, + ], + }, + ), +]; diff --git a/pil/zisk.pil b/pil/zisk.pil index f9eed8b35..bc9b7f129 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -1,3 +1,8 @@ +// #pragma arg -I pil,../pil2-proofman/pil2-components/lib/std/pil,state-machines,precompiles +// #pragma arg -o pil/zisk.pilout +// #pragma arg -O fixed-to-file +// #pragma arg -u tmp/fixed + require "std_direct.pil" require "operations.pil" @@ -24,6 +29,7 @@ require "dma/pil/dma.pil" require "dma/pil/dma_rom.pil" require "dma/pil/dma_pre_post.pil" require "dma/pil/dma_pre_post_table.pil" +require "dma/pil/dma_byte_cmp_table.pil" require "dma/pil/dma_64_aligned.pil" require "dma/pil/dma_unaligned.pil" require "poseidon2/pil/poseidon2.pil" @@ -42,6 +48,9 @@ enable_dma_64_aligned * (1 - enable_dma_64_aligned); proofval enable_dma_64_aligned_input; enable_dma_64_aligned_input * (1 - enable_dma_64_aligned_input); +proofval enable_dma_64_aligned_mem; +enable_dma_64_aligned_mem * (1 - enable_dma_64_aligned_mem); + proofval enable_dma_unaligned; enable_dma_unaligned * (1 - enable_dma_unaligned); @@ -65,11 +74,16 @@ airgroup Zisk { virtual DualRange(id: DUAL_RANGE_BYTE_ID, min1: 0, max1: P2_8-1, min2: 0, max2: P2_8-1) alias DualByte; Dma(); - virtual DmaRom(); - Dma64Aligned(enable: enable_dma_64_aligned); - // Dma64Aligned(enable: enable_dma_64_aligned_input, src_is_free_input: 1) alias Dma64AlignedInput; - DmaUnaligned(enable: enable_dma_unaligned); + + Dma64Aligned(enable_flag: enable_dma_64_aligned); + Dma64Aligned(enable: E_DMA_INPUTCPY, enable_flag: enable_dma_64_aligned_input) alias Dma64AlignmentInput; + Dma64Aligned(enable: E_DMA_MEMCPY|E_DMA_MEMCMP|E_DMA_MEMSET, enable_flag: enable_dma_64_aligned_mem) alias Dma64AlignmentMem; + + DmaUnaligned(enable_flag: enable_dma_unaligned); DmaPrePost(); + + virtual DmaByteCmpTable(); + virtual DmaRom(); virtual DmaPrePostTable(); // Main Program Main(N: 2**22); @@ -119,6 +133,6 @@ airgroup Zisk { // Public Inputs for (int i = 0; i < PUBLIC_INPUTS_64_BITS; i++) { - direct_global_update_proves(OPERATION_BUS_ID, [OP_PUBOUT, i, 0, inputs[i*2], inputs[i*2 + 1], inputs[i*2], inputs[i*2 + 1], 0, 0]); + direct_global_update_proves(OPERATION_BUS_ID, [OP_PUBOUT, i, 0, inputs[i*2], inputs[i*2 + 1], inputs[i*2], inputs[i*2 + 1]], surname: PIOP_SURNAME_DYNAMIC); } } diff --git a/precompiles/arith_eq/pil/arith_eq.pil b/precompiles/arith_eq/pil/arith_eq.pil index d75ba7b83..8b4720783 100644 --- a/precompiles/arith_eq/pil/arith_eq.pil +++ b/precompiles/arith_eq/pil/arith_eq.pil @@ -4,7 +4,7 @@ require "operations.pil" require "opids.pil" require "arith_eq_lt_table.pil" -airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION_BUS_ID) { +airtemplate ArithEq (const int N = 2**18) { // TODO: introduction, map // TODO: explain concept of q @@ -615,7 +615,8 @@ airtemplate ArithEq (const int N = 2**18, const int operation_bus_id = OPERATION sel_bn254_complex_sub * OP_COMPLEX_SUB_BN254 + sel_bn254_complex_mul * OP_COMPLEX_MUL_BN254; - lookup_proves(operation_bus_id, [bus_op, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(MAIN_STEP)], mul: in_use_clk0); + proves_operation(op: bus_op, a:[0, 0], b:[step_addr'(ADDR_OP), 0], c:[0, 0], flag:0, + main_step: step_addr'(MAIN_STEP), mul: in_use_clk0); // selclk0 is the clock 0 for dedicated to one operation function expr_group_by_cbc(const expr selclk0, const expr chunks[], const int index ): const expr { diff --git a/precompiles/arith_eq_384/pil/arith_eq_384.pil b/precompiles/arith_eq_384/pil/arith_eq_384.pil index 3a561fe77..efce94aeb 100644 --- a/precompiles/arith_eq_384/pil/arith_eq_384.pil +++ b/precompiles/arith_eq_384/pil/arith_eq_384.pil @@ -4,7 +4,7 @@ require "operations.pil" require "opids.pil" require "arith_eq_lt_table.pil" -airtemplate ArithEq384(const int N, const int operation_bus_id = OPERATION_BUS_ID) { +airtemplate ArithEq384(const int N) { /* EQ0 : x1 * y1 + x2 - x3 - q1 * y2 * p2_384 - q0 * y2 modular arith ARITH_384_MOD x3 = mod(x1*y1+x2, y2) EQ1 : s * x2 - s * x1 - y2 + y1 + (q0 * p) lambda - ADD EC_ADD_BLS12_381 @@ -567,7 +567,8 @@ airtemplate ArithEq384(const int N, const int operation_bus_id = OPERATION_BUS_I sel_bls12_381_complex_sub * OP_COMPLEX_SUB_BLS12_381 + sel_bls12_381_complex_mul * OP_COMPLEX_MUL_BLS12_381; - lookup_proves(operation_bus_id, [bus_op, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(MAIN_STEP)], mul: in_use_clk0); + proves_operation(op: bus_op, a:[0, 0], b:[step_addr'(ADDR_OP), 0], c:[0, 0], flag:0, + main_step: step_addr'(MAIN_STEP), mul: in_use_clk0); // selclk0 is the clock 0 for dedicated to one operation function expr_group_by_cbc(const expr selclk0, const expr chunks[], const int index): const expr { diff --git a/precompiles/big_int/pil/big_int_add.pil b/precompiles/big_int/pil/big_int_add.pil index 9bd34e80a..e463856e5 100644 --- a/precompiles/big_int/pil/big_int_add.pil +++ b/precompiles/big_int/pil/big_int_add.pil @@ -12,14 +12,13 @@ require "opids.pil" /// Parameters: /// /// - N: number of rows by instance -/// - operation_bus_id: the opid of bus used by precompile /// - bits: number of bits of the addition (N_RC * RC * 32) /// - RC: number of chunks of 32 bits defined in native architecture /// - operation_code: the operation code used to identify the precompile /// /// Integration: /// -/// - main assumes an operation on operation_bus_id: +/// - main assumes an operation: /// [ operation_code, a: [step, 0], b: [addr_params, 0], c: [carry_out, 0], flag: carry_out ] /// /// - precompiles "load" params from memory: @@ -45,7 +44,7 @@ require "opids.pil" /// addr_c + 8 * (N_RC - 1) <- c[N_RC-1] (c_chunks[N_RC-1][0] + c_chunks[N_RC-1][1] << 16, /// c_chunks[N_RC-1][2] + c_chunks[N_RC-1][3] << 16) -airtemplate BigIntAdd(const int N = 2**21, const int bits = 256, const int operation_code, const int operation_bus_id = OPERATION_BUS_ID) { +airtemplate BigIntAdd(const int N = 2**21, const int bits = 256, const int operation_code) { const int RC = 2; const int N_RC = bits / (RC * 32); @@ -134,5 +133,6 @@ airtemplate BigIntAdd(const int N = 2**21, const int bits = 256, const int opera const expr final_cout = cout[N_RC-1][RC-1]; // proves the operation lauched by main, c = flag = carry_out - lookup_proves(operation_bus_id, [operation_code, 0, 0, addr_params, 0, final_cout, 0, final_cout, step], sel); + proves_operation(op: operation_code, a:[0, 0], b:[addr_params, 0], c:[final_cout, 0], + flag:final_cout, main_step: step, mul:sel); } \ No newline at end of file diff --git a/precompiles/dma/pil/dma.pil b/precompiles/dma/pil/dma.pil index 5103a493d..212414481 100644 --- a/precompiles/dma/pil/dma.pil +++ b/precompiles/dma/pil/dma.pil @@ -2,17 +2,48 @@ const int DMA_MEM_CPY = 1; const int DMA_MEM_PRE_POST = 2; const int DMA_MEM_EQ = 3; -const int MEM_CPY_BYTE_CONT_ID = 8200; -const int MEM_CPY_CONT_ID = 8201; +const int E_DMA_MEMCPY = 0x01; +const int E_DMA_MEMCMP = 0x02; +const int E_DMA_INPUTCPY = 0x04; +const int E_DMA_MEMSET = 0x08; -const int DMA_MEM_CPY_COUNT_ADDR = 0xA0000F00; +const int E_DMA_ALL = E_DMA_MEMCPY | E_DMA_MEMCMP | E_DMA_INPUTCPY | E_DMA_MEMSET; +const int DMA_64_ALIGNED_CONT_ID = 8200; +const int DMA_UNALIGNED_CONT_ID = 8201; -airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const int selectors = 1, - const int operation_bus_id = OPERATION_BUS_ID) { +const int OP_DMA_PRE_XMEMCPY = 1; +const int OP_DMA_PRE_INPUTCPY = 2; +const int OP_DMA_PRE_XMEMEQ = 3; +const int OP_DMA_PRE_XMEMCMP = 4; +const int OP_DMA_PRE_XMEMSET = 5; - assert(selectors == 0 || selectors == 1); - assert(op_x_row > 1); +const int OP_DMA_POST_OFFSET = 10; + +const int OP_DMA_POST_XMEMCPY = OP_DMA_PRE_XMEMCPY + OP_DMA_POST_OFFSET; +const int OP_DMA_POST_INPUTCPY = OP_DMA_PRE_INPUTCPY + OP_DMA_POST_OFFSET; +const int OP_DMA_POST_XMEMEQ = OP_DMA_PRE_XMEMEQ + OP_DMA_POST_OFFSET; +const int OP_DMA_POST_XMEMCMP = OP_DMA_PRE_XMEMCMP + OP_DMA_POST_OFFSET; +const int OP_DMA_POST_XMEMSET = OP_DMA_PRE_XMEMSET + OP_DMA_POST_OFFSET; + +airtemplate Dma(int N = 2**21, + const int RC = 2, + const int op_x_row = 4, + const int selectors = 1, + const int enable = E_DMA_ALL, + const int enable_extended = 1) { + + const int enable_memcpy = (enable & E_DMA_MEMCPY) ? 1 : 0; + const int enable_memcmp = (enable & E_DMA_MEMCMP) ? 1 : 0; + const int enable_inputcpy = (enable & E_DMA_INPUTCPY) ? 1 : 0; + const int enable_memset = (enable & E_DMA_MEMSET) ? 1 : 0; + + assert(enable_extended == 0 || enable_extended == 1); + assert((enable_memcpy + enable_memcmp + enable_inputcpy + enable_memset) > 0); + assert(op_x_row >= 1); + assert(RC == 2); + + const int has_src = (enable_memcpy || enable_memcmp); // DMA: MEMCPY // @@ -70,10 +101,124 @@ airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const i // write // - col witness bits(1) sel; - sel * (1 - sel) === 0; - // read count from register directly + // main_step MAIN_STEP_BITS + // fill_byte 8 + // sel_memcpy 1 + // sel_memcmp 1 + // sel_memset 1 + // sel_extended 1 + // sel_inputcpy 1 + + // count_lt_256 1 + // h_count 24 + // l_count 9 + // count_diff_chunks 16 <=== + + // h_dst64 22 + // l_dst64 7 + // dst_offset 3 + + // h_src64 22 + // l_src64 7 + // src_offset 3 + // src_offset_after_pre 3 + // src64_inc_by_pre 1 + + // use_pre 1 + // use_loop 1 + // use_post 1 + // pre_count 3 + // l_count64 9 + + // pre_result_nz 1 + // post_result_nz 1 + // bus_pre_result[2] 32 + // bus_post_result[2] 32 + + // INTERMEDIATES + // loop_b0 32 + // loop_extended_arg 32 + // static_count 32 + // b0 32 + // extended_arg 32 + + + // temporal building expression for selector of any operation + expr _sel = 0; + + // temporal building expressions for operations codes of loop, pre and op_bus + expr _bus_op = 0; + expr _loop_op = 0; + expr _pre_op = 0; + if (enable_memcpy) { + col witness bits(1) air.sel_memcpy; + sel_memcpy * (1 - sel_memcpy) === 0; + _sel += sel_memcpy; + _bus_op += sel_memcpy * OP_DMA_MEMCPY; + _loop_op += sel_memcpy * OP_DMA_XMEMCPY; + _pre_op += sel_memcpy * OP_DMA_PRE_XMEMCPY; + } else { + const expr air.sel_memcpy = 0; + } + + if (enable_memcmp) { + col witness bits(1) air.sel_memcmp; + sel_memcmp * (1 - sel_memcmp) === 0; + _sel += sel_memcmp; + _bus_op += sel_memcmp * OP_DMA_MEMCMP; + _loop_op += sel_memcmp * OP_DMA_XMEMEQ; + _pre_op += sel_memcmp * OP_DMA_PRE_XMEMCMP; + } else { + const expr air.sel_memcmp = 0; + } + + if (enable_memset) { + col witness bits(1) air.sel_memset; + sel_memset * (1 - sel_memset) === 0; + + // fill_byte is send from main in extended_arg, this argument is static, known in + // compilation time, for this reason we don't need to validate its range check. + + col witness bits(8) air.fill_byte; + + _sel += sel_memset; + _bus_op += sel_memset * __OP_DMA_MEMSET__; + _loop_op += sel_memset * OP_DMA_XMEMSET; + _pre_op += sel_memset * OP_DMA_PRE_XMEMSET; + } else { + const int air.sel_memset = 0; + const int air.fill_byte = 0; + } + + if (enable_extended) { + col witness bits(1) air.sel_extended; + sel_extended * (1 - sel_extended) === 0; + _bus_op += sel_extended * OP_DMA_X_OFFSET; + } else { + const int air.sel_extended = 0; + } + + const expr sel_load_count_from_mem = _sel; + + if (enable_inputcpy) { + col witness bits(1) air.sel_inputcpy; + sel_inputcpy * (1 - sel_inputcpy) === 0; + + _sel += sel_inputcpy; + _bus_op += sel_inputcpy * OP_DMA_INPUTCPY; + _loop_op += sel_inputcpy * OP_DMA_INPUTCPY; + _pre_op += sel_inputcpy * OP_DMA_PRE_INPUTCPY; + } else { + const expr air.sel_inputcpy = 0; + } + + const expr bus_op = _bus_op; + const expr loop_op = _loop_op; + const expr pre_op = _pre_op; + const expr post_op = _pre_op + OP_DMA_POST_OFFSET; + const expr sel = _sel; + sel * (1 - sel) === 0; // assuming max memory of 4GB (32 bits) // count: 24 bits RC + 8 bits T @@ -120,13 +265,25 @@ airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const i range_check(expression: h_count, min: 0, max: 2**24-1, sel: sel); - // 32 bit address - // 22 bits RC + 5 bits RC + 3 bits T - col witness bits(22) h_src64; - col witness bits(7) l_src64; - col witness bits(3) src_offset; - const expr src = h_src64 * 2**10 + l_src64 * 2**3 + src_offset; - const expr src64 = h_src64 * 2**10 + l_src64 * 2**3; + // count_bus (well formed) + // count (chunks divided) + // count_bus - count === count_diff (chunks divided) + + const expr count_bus; + const expr count_diff; + if (enable_memcmp) { + count_bus = count + count_diff; + col witness bits(16) air.count_diff_chunks[2]; + count_diff = count_diff_chunks[0] + count_diff_chunks[1] * P2_16; + + range_check(expression: count_diff_chunks[0], min: 0, max: 0xFFFF, sel: sel_memcmp); + range_check(expression: count_diff_chunks[1], min: 0, max: 0xFFFF, sel: sel_memcmp); + + (sel_memcpy + sel_inputcpy + sel_memset) * count_diff === 0; + } else { + count_bus = count; + count_diff = 0; + } // src_offset range verified with dma_rom table @@ -141,65 +298,264 @@ airtemplate Dma(int N = 2**21, const int RC = 2, const int op_x_row = 4, const i col witness bits(MAIN_STEP_BITS) main_step; - range_check(expression: h_src64, min: 0, max: 2**22-1, sel: sel); range_check(expression: h_dst64, min: 0, max: 2**22-1, sel: sel); - - lookup_assumes(DUAL_RANGE_7_BITS_ID, expressions: [l_src64, l_dst64], sel: sel); - col witness bits(1) use_pre; // use memcpy_pre operation - col witness bits(1) use_memcpy; // use memcpy operation - col witness bits(1) use_post; // use memcpy_post operation - col witness bits(1) src64_inc_by_pre; // src64 increment after apply memcpy_pre operation + // 32 bit address + // 22 bits RC + 5 bits RC + 3 bits T + if (has_src) { + col witness bits(22) air.h_src64; + col witness bits(7) air.l_src64; + col witness bits(3) air.src_offset; + col witness bits(3) air.src_offset_after_pre; + + col witness bits(1) air.src64_inc_by_pre; // src64 increment after apply memcpy_pre operation + src64_inc_by_pre * (1 - src64_inc_by_pre) === 0; + + const expr air.src = h_src64 * 2**10 + l_src64 * 2**3 + src_offset; + const expr air.src64 = h_src64 * 2**10 + l_src64 * 2**3; + + range_check(expression: h_src64, min: 0, max: 2**22-1, sel: sel); + lookup_assumes(DUAL_RANGE_7_BITS_ID, expressions: [l_src64, l_dst64], sel: sel); + } else { + const expr air.src = 0; + const expr air.src64 = 0; + const expr air.src_offset = 0; + const expr air.src64_inc_by_pre = 0; + const expr air.src_offset_after_pre = 0; + range_check(expression: l_dst64, min: 0, max: 0x7F, sel: sel); + } + + col witness bits(1) use_pre; // use pre operation + col witness bits(1) use_loop; // use loop (aligned, unaligned) operation + col witness bits(1) use_post; // use post operation use_pre * (1 - use_pre) === 0; - use_memcpy * (1 - use_memcpy) === 0; + use_loop * (1 - use_loop) === 0; use_post * (1 - use_post) === 0; - src64_inc_by_pre * (1 - src64_inc_by_pre) === 0; - - const expr flags = use_pre + use_memcpy * 2 + use_post * 4 + src64_inc_by_pre * 8 + count_lt_256 * 16; col witness bits(3) pre_count; // number of bytes of memcpy_pre operation col witness bits(9) l_count64; // number of 64 bits words in l_count after substract pre_count and post_count - col witness bits(3) src_offset_after_pre; // src_offset after apply memcpy_pre operation const expr post_count = count - pre_count - h_count * 256 - l_count64 * 8; - lookup_assumes(DMA_ROM_ID, expressions: [dst_offset, src_offset, l_count, flags, pre_count, src_offset_after_pre, l_count64], sel: sel); - // if operation not selected then other selectors must be 0. - (1 - sel) * use_memcpy === 0; + (1 - sel) * use_loop === 0; (1 - sel) * use_pre === 0; (1 - sel) * use_post === 0; - permutation_assumes(DMA_BUS_ID, [DMA_MEM_CPY, - (dst64 + use_pre * 8), - (src64 + src64_inc_by_pre * 8), - 0, - src_offset_after_pre, - h_count * 256 + l_count64 * 8, // count64 = h_count * (256/8 = 32) + l_count64 - main_step], sel: use_memcpy); + const expr result_nz; + const expr bus_result[2]; + + if (enable_memcmp) { + col witness bits(1) air.pre_result_nz; + col witness bits(1) air.post_result_nz; + + pre_result_nz * (1 - pre_result_nz) === 0; + post_result_nz * (1 - post_result_nz) === 0; + + pre_result_nz * post_result_nz === 0; + + result_nz = pre_result_nz + post_result_nz; + + col witness bits(32) air.bus_pre_result[2]; + col witness bits(32) air.bus_post_result[2]; + + bus_result[0] = bus_pre_result[0] + bus_post_result[0]; + bus_result[1] = bus_pre_result[1] + bus_post_result[1]; + + (bus_pre_result[0] + bus_pre_result[1]) * (bus_post_result[0] + bus_post_result[1]) === 0; + + // if use_pre == 0 the pre result must be zero + bus_pre_result[0] * (1 - use_pre) === 0; + bus_pre_result[1] * (1 - use_pre) === 0; + pre_result_nz * (1 - use_pre) === 0; + + // if use_post == 0 the post result must be zero + bus_post_result[0] * (1 - use_post) === 0; + bus_post_result[1] * (1 - use_post) === 0; + post_result_nz * (1 - use_post) === 0; + + // if use_post == 1 the result must come from post ==> bus_result_pre must be zero + bus_pre_result[0] * use_post === 0; + bus_pre_result[1] * use_post === 0; + pre_result_nz * use_post === 0; + + // if memcmp_result_is_zero means that bus_result must be zero + (1 - pre_result_nz) * bus_pre_result[0] === 0; + (1 - pre_result_nz) * bus_pre_result[1] === 0; + (1 - post_result_nz) * bus_post_result[0] === 0; + (1 - post_result_nz) * bus_post_result[1] === 0; + + // if memcmp_result_is_zero means count and count_eq are equals + (1 - result_nz) * count_diff === 0; + result_nz * (1 - sel_memcmp) === 0; + + // if operation it isn't a memcmp, the result must be 0 + bus_result[0] * (1 - sel_memcmp) === 0; + bus_result[1] * (1 - sel_memcmp) === 0; + } else { + result_nz = 0; + bus_result[0] = 0; + bus_result[1] = 0; + const int air.post_result_nz = 0; + const int air.pre_result_nz = 0; + const int air.bus_pre_result[2] = [0, 0]; + const int air.bus_post_result[2] = [0, 0]; + } + + const expr flags = use_pre + use_loop * 2 + use_post * 4 + src64_inc_by_pre * 8 + count_lt_256 * 16 + result_nz * 32; + lookup_assumes(DMA_ROM_ID, expressions: [dst_offset, src_offset, l_count, flags, pre_count, src_offset_after_pre, l_count64], sel: sel); - permutation_assumes(DMA_BUS_ID, [DMA_MEM_PRE_POST, + // OP_DMA_MEMSET isn't implemented because usually memset used with static value known + // in compilation time, and for this case we could use OP_DMA_XMEMSET. For a OP_DMA_MEMSET + // we will need divide two extra columns for garbage in value to send to bus, value bits 8-31 and + // bits 32-63, and two additional range checks (8 bits, 24 bits) is too expensive for a few + // values. + + // OPCODES + // --------------------------------------------- + // OP_DMA_MEMCPY 0xD0 OP_DMA_XMEMCPY 0xD6 + // OP_DMA_MEMCMP 0xD1 OP_DMA_XMEMCMP 0xD7 + // OP_DMA_INPUTCPY 0xD2 + // OP_DMA_XMEMSET 0xD9 + // OP_DMA_XMEMEQ 0xDA + + + // BUS: DMA ===> DMA_64_ALIGNED (Definitions) + // BUS: DMA ===> DMA_UNALIGNED (Definitions) + + const expr loop_count = h_count * 256 + l_count64 * 8; + const expr loop_dst = dst64 + use_pre * 8; + const expr loop_src = src64 + src64_inc_by_pre * 8; + + expr _loop_b0 = loop_src * (sel_memcpy + sel_memcmp) + + loop_count * (sel_inputcpy + sel_memset); + + if (degree(_loop_b0) > 1) { + col witness bits(32) air.loop_b0; + loop_b0 <== _loop_b0; + } else { + const expr air.loop_b0 = _loop_b0; + } + + expr _loop_extended_arg = loop_count * (sel_memcpy + sel_memcmp) + fill_byte * sel_memset; + + if (degree(_loop_extended_arg) > 1) { + col witness bits(32) air.loop_extended_arg; + loop_extended_arg <== _loop_extended_arg; + } else { + const expr air.loop_extended_arg = _loop_extended_arg; + } + + // DMA ===> DMA_64_ALIGNED + // DMA ===> DMA_UNALIGNED + // -------------------------------------------------------------------------------- + // OP_DMA_XMEMCPY (dst, 0, src, 0, 0, 0, 0, main_step, count, src_offset_after_pre) + // OP_DMA_INPUTCPY (dst, 0, count, 0, 0, 0, 0, main_step, 0 , src_offset_after_pre) + // OP_DMA_XMEMEQ (dst, 0, src, 0, 0, 0, 0, main_step, count, src_offset_after_pre) + // OP_DMA_XMEMSET (dst, 0, count, 0, 0, 0, 0, main_step, value, src_offset_after_pre) + + assumes_operation(op: loop_op, a: [loop_dst, 0], + b: [loop_b0, 0], + main_step:, + extended_arg: loop_extended_arg, + extra_args: [src_offset_after_pre], + sel: use_loop); + + // DMA ===> DMA_PRE_POST (PRE) + // -------------------------------------------------------------------------------- + // OP_DMA_PRE_XMEMCPY (dst, src, dst_offset, src_offset, pre_count, main_step) + // OP_DMA_PRE_INPUTCPY (dst, 0, dst_offset, 0, pre_count, main_step) + // OP_DMA_PRE_XMEMEQ (dst, src, dst_offset, src_offset, pre_count, main_step) + // OP_DMA_PRE_XMEMCMP (dst, src, dst_offset, src_offset, pre_count, main_step, 0, r_nz, R0, R1) + // OP_DMA_PRE_XMEMSET (dst, 0, dst_offset, 0, pre_count, main_step, value) + + permutation_assumes(DMA_BUS_ID, [pre_op, dst64, src64, dst_offset, src_offset, pre_count, - main_step], sel: use_pre); + main_step, + fill_byte, + pre_result_nz, + ...bus_pre_result + ], sel: use_pre); - permutation_assumes(DMA_BUS_ID, [DMA_MEM_PRE_POST, - dst64 + use_pre * 8 + l_count64 * 8 + h_count * 256, - src64 + src64_inc_by_pre * 8 + l_count64 * 8 + h_count * 256, + // DMA ===> DMA_PRE_POST (POST) + // -------------------------------------------------------------------------------- + // OP_DMA_POST_XMEMCPY (dst, src, 0, src_offset, post_count, main_step) + // OP_DMA_POST_INPUTCPY (dst, 0, 0, 0, post_count, main_step) + // OP_DMA_POST_XMEMEQ (dst, src, 0, src_offset, post_count, main_step) + // OP_DMA_POST_XMEMCMP (dst, src, 0, src_offset, post_count, main_step, r_nz, R0, R1) + // OP_DMA_POST_XMEMSET (dst, 0, 0, 0, post_count, main_step, value) + + const expr post_dst64 = dst64 + use_pre * 8 + l_count64 * 8 + h_count * 256; + const expr post_src64 = src64 + src64_inc_by_pre * 8 + l_count64 * 8 + h_count * 256; + permutation_assumes(DMA_BUS_ID, [post_op, + post_dst64, + post_src64, 0, src_offset_after_pre, post_count, - main_step], sel: use_post); + main_step, + fill_byte, + post_result_nz, + ...bus_post_result], sel: use_post); + + // BUS: DMA ===> MAIN (Definitions) + + if (enable_extended) { + col witness bits(32) air.static_count; + static_count <== count_bus * sel_extended; + } else { + const expr air.static_count = 0; + } + + expr _b0 = src * (sel_memcpy + sel_memcmp) + + count * (sel_inputcpy + sel_memset); + + if (degree(_b0) > 1) { + col witness bits(32) air.b0; + b0 <== _b0; + } else { + const expr air.b0 = _b0; + } + + expr _extended_arg = static_count * (sel_memcpy + sel_memcmp) + fill_byte * sel_memset; + + if (degree(_extended_arg) > 1) { + col witness bits(32) air.extended_arg; + extended_arg <== _extended_arg; + } else { + const expr air.extended_arg = _extended_arg; + } + + // BUS: DMA ===> MAIN + // -------------------------------------------------------------------------------- + // OP_DMA_MEMCPY (dst, 0, src, 0, 0, 0, 0, main_step) + MEM(count) + // OP_DMA_XMEMCPY (dst, 0, src, 0, 0, 0, 0, main_step, count) + // OP_DMA_INPUTCPY (dst, 0, count, 0, 0, 0, 0, main_step) + // OP_DMA_MEMCMP (dst, 0, src, 0, 0, R0, R1, main_step) + MEM(count) + // OP_DMA_XMEMCMP (dst, 0, src, 0, 0, R0, R1, main_step, count) + // OP_DMA_XMEMSET (dst, 0, count, 0, 0, 0, 0, main_step, value) - // TODO: memcmp + proves_operation(op: bus_op, + a: [dst, 0], + b: [b0, 0], + c: bus_result, + flag: 0, + main_step:, + extended_arg:, + mul: sel); - // used to link operation with main - lookup_proves(operation_bus_id, [OP_DMA_MEMCPY, dst, 0, src, 0, 0, 0, 0, main_step], mul: sel); + // BUS: DMA ===> MEM + // + // For OP_DMA_MEMCPY and OP_DMA_MEMCMP we need read the third parameter (count), to do it + // we read a specific position on RAM, EXTRA_PARAM_ADDR. - // lookup_proves(operation_bus_id, [OP_PARAM, 0, 0, count, 0, 0, 0, 0, main_step-1], mul: sel); - precompiled_mem_load(sel: sel, main_step: main_step, addr: DMA_MEM_CPY_COUNT_ADDR, value: [count, 0]); + precompiled_mem_load(sel: sel_load_count_from_mem - sel_extended, + main_step:, + addr: EXTRA_PARAMS_ADDR, + value: [count_bus, 0]); } \ No newline at end of file diff --git a/precompiles/dma/pil/dma_64_aligned.pil b/precompiles/dma/pil/dma_64_aligned.pil index 973bab77d..5379fa949 100644 --- a/precompiles/dma/pil/dma_64_aligned.pil +++ b/precompiles/dma/pil/dma_64_aligned.pil @@ -1,99 +1,322 @@ -airtemplate Dma64Aligned(int N = 2**21, const int RC = 2, const int op_x_row = 4, - const expr enable = 1, const int src_is_free_input = 0, - const int operation_bus_id = OPERATION_BUS_ID) { +// The Dma64Aligned state-machine is used to verify full aligned 64-bit operations, for operations +// that use a mem_read + + +airtemplate Dma64Aligned(int N = 2**21, // Rows of instance + const int RC = 2, // Number of chunks for native value + const int op_x_row = 4, // Number of operations by rows + const expr enable_flag = 1, // Expression to active the beggining of + // continuations, used for executions that + // not generates instances. + // + const int enable = E_DMA_ALL, // Enable inputcpy operations + int enable_count_load = -1, // Enable that dma_64_align prove directly + // Not extendend memcpy where need load count + // From memory position + // + int cont_subid = 0) { // Force subid used in continuations + + const int enable_memcpy = (enable & E_DMA_MEMCPY) ? 1 : 0; + const int enable_memcmp = (enable & E_DMA_MEMCMP) ? 1 : 0; + const int enable_inputcpy = (enable & E_DMA_INPUTCPY) ? 1 : 0; + const int enable_memset = (enable & E_DMA_MEMSET) ? 1 : 0; + + if (enable_count_load == -1) { + enable_count_load = enable_memcpy; + } + // parameters verification + + assert(enable_count_load == 0 || enable_count_load == 1); + assert(cont_subid >= 0); + + // the enable_count_load to load count from memory it's used only by direct from main + // memcpy operation + + assert(enable_count_load == 0 || enable_memcpy == 1); + assert((enable_memcpy + enable_memcmp + enable_inputcpy + enable_memset) > 0); + assert(op_x_row >= 1); assert(RC == 2); + const int has_src = (enable_memcpy || enable_memcmp); + + // continuation control, used to create a new continuation subid to avoid collision + // between Dma64Aligned continuations. If you instance diferent airs with same template + // need diferenciate them. If parameter cont_subid = 0 means that the subid is auto-generated + + container proof.dma_64_aligned { + int cont_subids_count = 0; + int cont_subids[64]; + } + use proof.dma_64_aligned; + + if (cont_subid == 0) { + cont_subid = cont_subids_count + 1; + for (int i = 0; i < cont_subids_count; ++i) { + if (cont_subids[i] >= cont_subid) { + cont_subid = cont_subids[i] + 1; + } + } + } else { + for (int i = 0; i < cont_subids_count; ++i) { + assert(cont_subids[i] != cont_subid, `duplicated cont_subid ${cont_subid}`); + } + } + cont_subids[cont_subids_count] = cont_subid; + cont_subids_count += 1; + + // get reference to L1 fixed column and create LAST from L1. + const expr L1 = get_L1(); const expr LAST = L1'; - assert(op_x_row > 1); - - // dst64 aligned, word address - // src64 aligned, word address - // count number of words + // WITNESS + // ───────────────────────────────────────────────────────────────────────────────────────────── + // dst64 ADDR_W_BITS → 64-bit address memory dst + // continuations (increment + ops_selected) @[dst64] + // ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ + // src64 ADDR_W_BITS → 64-bit address memory src + // continuations (increment + ops_selected) @[src64] + // ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ + // count64 32 → 64-bit remain count + // continuations (increment - ops_selected) @[count64] + // no uses inversa, only check pass to zero, and in the segment + // was verified that no negative @[count64_zero_check] + // ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ + // seq_end 1 → indicator that it's last row of the sequence + // continuations, if seq_end at last row, the flags, and other + // values throw segments are zero. + // binary contraint @[seq_end_binary] + // ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ + // fill_byte 8 → continuations (latch) + // ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ + // main_step MAIN_STEP_BITS → continuations (latch) + // ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ + // sel_op_from_1 1 → row + // ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ + // l_value_chunks 8 → Use asymethric range check to reduce number of range checks + // h_value_chunks 24 → for 64-bits, 2 x 24 bits + dual 8 bits = 3 range checks + // ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ + // value 32 → row + // ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ + // sel_xmemcpy 1 ┐ + // sel_memeq 1 │ + // sel_memset 1 ├ @[flags] → continuations (latch) + // sel_memcpy_count_load 1 │ + // sel_inputcpy 1 ┘ + // ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ + // b0 32 intermediate + // extended_arg 32 intermediate + // previous_seq_end 1 intermediate airval segment_id; // Id of current segment - airval segment_previous_seq_end; // Last value of `seq_end` in previous segment. - airval segment_previous_dst64; // Last value of `dst64` in previous segment. - airval segment_previous_main_step; // Last value of `main_step` in previous segment. - airval segment_previous_count; // Last value of `count` in previous segment. - airval segment_previous_is_mem_eq; // Last value of `is_mem_eq' in previous segment. - - airval segment_last_seq_end; // Last value of `seq_end` in current segment. - airval segment_last_dst64; // Last value of `dst64` in current segment. - airval segment_last_main_step; // Last value of `main_step` in current segment. - airval segment_last_count; // Last value of `count` in current segment. - airval segment_last_is_mem_eq; // Last value of `is_mem_eq' in current segment. + airval segment_previous_seq_end; // Last value of @[seq_end] in previous segment. + segment_previous_seq_end * (1 - segment_previous_seq_end) === 0; - airval is_last_segment; // 1 if this is the last segment, 0 otherwise. + airval segment_previous_dst64; // Last value of @[dst64] in previous segment. + airval segment_previous_main_step; // Last value of @[main_step] in previous segment. + airval segment_previous_count64; // Last value of @[count64] in previous segment. + airval segment_previous_flags; // Last value of @[flags] in previous segment. - is_last_segment * (1 - is_last_segment) === 0; - segment_previous_seq_end * (1 - segment_previous_seq_end) === 0; + airval segment_last_seq_end; // Last value of @[seq_end] in current segment. segment_last_seq_end * (1 - segment_last_seq_end) === 0; - segment_previous_is_mem_eq * (1 - segment_previous_is_mem_eq) === 0; - segment_last_is_mem_eq * (1 - segment_last_is_mem_eq) === 0; - - col witness bits(MAIN_STEP_BITS) main_step; - col witness bits(ADDR_W_BITS) dst64; - col witness bits(32) count; + airval segment_last_dst64; // Last value of @[dst64] in current segment. + airval segment_last_main_step; // Last value of @[main_step] in current segment. + airval segment_last_count64; // Last value of @[count64] in current segment. + airval segment_last_flags; // Last value of @[flags] in current segment. - col witness bits(1) sel_op[op_x_row]; - const expr sel = sel_op[0]; - col witness bits(1) seq_end; - col witness bits(1) is_mem_eq; + airval is_last_segment; // 1 if this is the last segment, 0 otherwise. + is_last_segment * (1 - is_last_segment) === 0; - if (src_is_free_input) { - const expr air.value[op_x_row][RC]; - col witness bits(16) value_chunks[op_x_row][RC][2]; - for (int i = 0; i < op_x_row; i++) { - for (int rc = 0; rc < RC; ++rc) { - value[i][rc] = value_chunks[i][rc][0] + value_chunks[i][rc][1] * P2_16; - range_check(value_chunks[i][rc][0], 0, P2_16 - 1); - range_check(value_chunks[i][rc][1], 0, P2_16 - 1); - } - } - const int air.segment_previous_src64 = 0; - const int air.segment_last_src64 = 0; - } else { - col witness bits(32) air.value[op_x_row][RC]; + if (has_src) { col witness bits(ADDR_W_BITS) air.src64; - airval air.segment_previous_src64; // Last value of `src64` in previous segment. - airval air.segment_last_src64; // Last value of `src64` in current segment. + airval air.segment_previous_src64; // Last value of @[src64] in previous segment. + airval air.segment_last_src64; // Last value of @[src64] in current segment. + } else { + const int air.segment_previous_src64 = 0; + const int air.segment_last_src64 = 0; } + col witness bits(1) seq_end; seq_end * (1 - seq_end) === 0; - is_mem_eq * (1 - is_mem_eq) === 0; col witness bits(1) previous_seq_end; - previous_seq_end === L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; + previous_seq_end <== L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; + + // temporal building expression for selector of any operation + expr _sel = 0; + + // temporal building expression for operation code + expr _op = 0; + + // temporal building expression for segment flags + expr _flags = 0; + + const int enable_full = enable_memcpy && enable_memcmp && enable_memset && enable_inputcpy && + enable_count_load; + + if (enable_memcpy) { + col witness bits(1) air.sel_xmemcpy; + sel_xmemcpy * (1 - sel_xmemcpy) === 0; + _sel += sel_xmemcpy; + _op += sel_xmemcpy * OP_DMA_XMEMCPY; + _flags += sel_xmemcpy; + } else { + const expr air.sel_xmemcpy = 0; + } + + if (enable_memcmp) { + col witness bits(1) air.sel_memeq; + sel_memeq * (1 - sel_memeq) === 0; + _sel += sel_memeq; + _op += sel_memeq * OP_DMA_XMEMEQ; + _flags += 2 * sel_memeq; + } else { + const expr air.sel_memeq = 0; + } + + if (enable_memset) { + col witness bits(1) air.sel_memset; + sel_memset * (1 - sel_memset) === 0; + + // fill_byte is send from main in extended_arg, this argument is static, known in + // compilation time, for this reason we don't need to validate its range check. + + col witness bits(8) air.fill_byte; + const expr air.fill_dword = fill_byte + fill_byte * P2_8 + fill_byte * P2_16 + fill_byte * P2_24; + + airval air.segment_previous_fill_byte; // Last value of `fill_byte` in previous segment. + airval air.segment_last_fill_byte; // Last value of `fill_byte` in current segment. + + LAST * (1 - seq_end) * (fill_byte - segment_last_fill_byte) === 0; + + // if sel_memset is disabled fill_byte must be zero, it's an optimization for extended_arg + (1 - sel_memset) * fill_byte === 0; + + // when segment_last_seq_end = 1, means that the next segment starts with new input, no need to + // compare with previous, to simplify WC of continuations, zeros are sent to bus + // these constraints aren't strictly necessary, because these values are used only when seq_end = 0 + + segment_last_fill_byte * segment_last_seq_end === 0; + segment_previous_fill_byte * segment_previous_seq_end === 0; + + _sel += sel_memset; + _op += sel_memset * OP_DMA_XMEMSET; + _flags += 2 * sel_memset; + } else { + const int air.sel_memset = 0; + const int air.fill_byte = 0; + const int air.fill_dword = 0; + const int air.segment_previous_fill_byte = 0; // Last value of `fill_byte` in previous segment. + const int air.segment_last_fill_byte = 0; // Last value of `fill_byte` in current segment. + } + + if (enable_count_load) { + col witness bits(1) air.sel_memcpy_count_load; + sel_memcpy_count_load * (1 - sel_memcpy_count_load) === 0; + _sel += sel_memcpy_count_load; + _op += sel_memcpy_count_load * OP_DMA_MEMCPY; + _flags += 4 * sel_memcpy_count_load; + } else { + const int air.sel_memcpy_count_load = 0; + } + + if (enable_inputcpy) { + col witness bits(1) air.sel_inputcpy; + sel_inputcpy * (1 - sel_inputcpy) === 0; + + _sel += sel_inputcpy; + _op += sel_inputcpy * OP_DMA_INPUTCPY; + _flags += 8 * sel_memset; + } else { + const expr air.sel_inputcpy = 0; + } + + const expr sel_memcpy = sel_xmemcpy + sel_memcpy_count_load; + + const expr op = _op; + const expr flags = _flags; + const expr sel = _sel; + sel * (1 - sel) === 0; + + // Binary constraints + + // main step is "time" when operation is done + + col witness bits(MAIN_STEP_BITS) main_step; // @[main_step] timestamp of operation + col witness bits(ADDR_W_BITS) dst64; // @[dst64] 64-bit destination address + col witness bits(32) count64; // @[count64] number of operation 64-bits words + + // We have the column sel to indicate when row is used, and if row is used means at least + // sel_op[0] must be 1, for this reason if we have multiple operations for rows define an + // array of columns (sel_op_from_1) defined from 1 to op_x_row-1 + + const expr sel_op[op_x_row]; + sel_op[0] = sel; + + if (op_x_row > 1) { + col witness bits(1) air.sel_op_from_1[op_x_row-1]; + expr _ops_selected = sel_op[0]; + for (int i = 1; i < op_x_row; ++i) { + sel_op_from_1[i-1] * (1 - sel_op_from_1[i-1]) === 0; + sel_op[i] = sel_op_from_1[i-1]; + // if previous sel_op it's zero, the rest must be zero. + (1 - sel_op[i-1]) * sel_op[i] === 0; + _ops_selected += sel_op[i]; + } + const expr air.ops_selected = _ops_selected; + } else { + const expr air.ops_selected = sel; + } + + // The flag is_mem_eq is used to mark and memcmp equal operation, means that the operations was + // read - read and value it's the same to match + + // To enable that this machine could be used to operations without dma controller, for these + // reason used a operation bus like rest of operations. If this machine was used on non aligned + // address, there are two posibilities + // - count64 ∈ [0,1] ⇒ it's correct, too expensive (use mem_aligns) but correct. + // - count64 > 1 ⇒ has two write in same "timestamp" and address, memory can't demostrait it. + // + // This machine has three modes to demostrate operations: + // - secondary of dma, in this case all parameters are sent to bus in same assume/prove (@[sel_xmemcpy]) + // - direct using memory, loading the count64 from memory (@[sel_memcpy_load_count]) + // - direct using bus (extended argument with static value) (@[sel_xmemcpy]) + // + + if (enable_count_load) { + precompiled_mem_load(addr: EXTRA_PARAMS_ADDR, + value: [count64 * 8, 0], + main_step:, + sel: sel_memcpy_count_load); + } LAST * (seq_end - segment_last_seq_end) === 0; - LAST * (1 - seq_end) * (dst64 - segment_last_dst64) === 0; + LAST * (1 - seq_end) * (dst64 - segment_last_dst64) === 0; // continuation: last row @[dst64_cont] LAST * (1 - seq_end) * (main_step - segment_last_main_step) === 0; - LAST * (1 - seq_end) * (count - segment_last_count) === 0; - LAST * (1 - seq_end) * (is_mem_eq - segment_last_is_mem_eq) === 0; + LAST * (1 - seq_end) * (count64 - segment_last_count64) === 0; + LAST * (1 - seq_end) * (flags - segment_last_flags) === 0; // when segment_last_seq_end = 1, means that the next segment starts with new input, no need to // compare with previous, to simplify WC of continuations, zeros are sent to bus // these constraints aren't strictly necessary, because these values are used only when seq_end = 0 - segment_last_dst64 * segment_last_seq_end === 0; - segment_last_count * segment_last_seq_end === 0; + segment_last_dst64 * segment_last_seq_end === 0; // continuation: reset by seq end @[dst64_cont] + segment_last_count64 * segment_last_seq_end === 0; segment_last_main_step * segment_last_seq_end === 0; - segment_last_is_mem_eq * segment_last_seq_end === 0; + segment_last_flags * segment_last_seq_end === 0; // when segment_previous_seq_end = 1, means that this segment starts with new input, no need to // compare with previous, to simplify WC of continuations, zeros are sent to bus // these constraints aren't strictly necessary, because these values are used only when seq_end = 0 - segment_previous_dst64 * segment_previous_seq_end === 0; - segment_previous_count * segment_previous_seq_end === 0; + segment_previous_dst64 * segment_previous_seq_end === 0; + segment_previous_count64 * segment_previous_seq_end === 0; segment_previous_main_step * segment_previous_seq_end === 0; - segment_previous_is_mem_eq * segment_previous_seq_end === 0; + segment_previous_flags * segment_previous_seq_end === 0; - if (!src_is_free_input) { + if (has_src) { LAST * (1 - seq_end) * (src64 - segment_last_src64) === 0; const expr air.previous_src64 = L1 * (segment_previous_src64 - 'src64) + 'src64; segment_previous_src64 * segment_previous_seq_end === 0; @@ -102,70 +325,91 @@ airtemplate Dma64Aligned(int N = 2**21, const int RC = 2, const int op_x_row = 4 // Continuations - // AIR_ID, segment_id, seq_end, src64, dst64, count, main_step - - direct_global_update_proves(MEM_CPY_CONT_ID, [0, - 0, - src_is_free_input, - 1, // initial seq_end - 0, // initial src64 - 0, // initial dst64 - 0, // initial count - 0, // initial main_step - 0], // initial is_mem_eq - sel: enable); - - - direct_update_assumes(MEM_CPY_CONT_ID, [segment_id, - 0, - src_is_free_input, - segment_previous_seq_end, - segment_previous_src64, - segment_previous_dst64, - segment_previous_count, - segment_previous_main_step, - segment_previous_is_mem_eq]); - - direct_update_proves(MEM_CPY_CONT_ID, [segment_id + 1, - is_last_segment, - src_is_free_input, - segment_last_seq_end, - segment_last_src64, - segment_last_dst64, - segment_last_count, - segment_last_main_step, - segment_last_is_mem_eq] - , sel: 1 - is_last_segment); - - - - expr _ops_in_row = 0; - for (int i = 0; i < op_x_row; i++) { - sel_op[i] * (1 - sel_op[i]) === 0; - _ops_in_row += sel_op[i]; + // AIR_ID, segment_id, seq_end, src64, dst64, count64, main_step + + direct_global_update_proves(DMA_64_ALIGNED_CONT_ID, + [0, + 0, + cont_subid, // continuations sub id + 1, // initial seq_end + 0, // initial src64 + 0, // initial dst64 + 0, // initial count64 + 0, // initial main_step + 0, // initial fill byte + 0], // initial flags + sel: enable_flag); + + + direct_update_assumes(DMA_64_ALIGNED_CONT_ID, + [segment_id, + 0, + cont_subid, + segment_previous_seq_end, + segment_previous_src64, + segment_previous_dst64, + segment_previous_count64, + segment_previous_main_step, + segment_previous_fill_byte, + segment_previous_flags]); + + direct_update_proves(DMA_64_ALIGNED_CONT_ID, [ + segment_id + 1, + is_last_segment, + cont_subid, + segment_last_seq_end, + segment_last_src64, + segment_last_dst64, + segment_last_count64, + segment_last_main_step, + segment_last_fill_byte, + segment_last_flags] + , sel: 1 - is_last_segment); + + + if (enable_inputcpy) { + const expr air.value[op_x_row][RC]; + col witness bits(8) l_value_chunks[op_x_row][RC]; + col witness bits(24) h_value_chunks[op_x_row][RC]; + assert(RC == 2); + for (int i = 0; i < op_x_row; i++) { + value[i][0] = l_value_chunks[i][0] + h_value_chunks[i][0] * P2_8; + value[i][1] = l_value_chunks[i][1] + h_value_chunks[i][1] * P2_8; + range_check(h_value_chunks[i][0], 0, P2_24-1); + range_check(h_value_chunks[i][1], 0, P2_24-1); + range_dual_byte(l_value_chunks[i][0], l_value_chunks[i][1], sel_op[i]); + } + } else if (has_src) { + col witness bits(32) air.value[op_x_row][RC]; + } else if (enable_memset) { + const expr air.value[op_x_row][RC]; + for (int i = 0; i < op_x_row; i++) { + for (int irc = 0; irc < RC; ++ irc) { + value[i][irc] = fill_dword; + } + } + } else { + assert(false, "Invalid case"); + } - if (!src_is_free_input) { + for (int i = 0; i < op_x_row; i++) { + if (has_src) { precompiled_mem_load( sel: sel_op[i], - main_step: main_step, + main_step:, addr: src64 * 8 + 8 * i, value: value[i] ); } precompiled_mem_op( - is_write: 1 - is_mem_eq, + is_write: sel_memcpy + sel_memset + sel_inputcpy, sel: sel_op[i], - main_step: main_step, + main_step:, addr: dst64 * 8 + 8 * i, value: value[i] ); - if (i > 0) { - // current selector only can be 1 if previous is 1 - sel_op[i] * (1 - sel_op[i - 1]) === 0; - } } - const expr ops_in_row = _ops_in_row; const expr continue_seq_on_l1 = L1 * (1 - segment_previous_seq_end); const expr continue_seq_on_no_l1 = (1 - L1) * (1 - 'seq_end); @@ -176,62 +420,159 @@ airtemplate Dma64Aligned(int N = 2**21, const int RC = 2, const int op_x_row = 4 // TRANSITIONS: // // If not finish sequence in previous row, means current it's a continuation of previous sequence - // count match with previous value plus ops_in_row. - continue_seq_on_l1 * (count - (segment_previous_count - op_x_row)) == 0; - continue_seq_on_no_l1 * (count - ('count - op_x_row)) === 0; - - // If finish sequence count match with ops_in_row, because final count must be 0. - // new_seq_on_l1 * (count - ops_in_row) === 0; - // new_seq_on_no_l1 * (count - ops_in_row) === 0; - seq_end * (count - ops_in_row) === 0; + // count64 match with previous value plus ops_selected. + continue_seq_on_l1 * (count64 - (segment_previous_count64 - ops_selected)) == 0; + continue_seq_on_no_l1 * (count64 - ('count64 - ops_selected)) === 0; + + // If finish sequence count64 match with ops_selected, because final count64 must be 0. + // new_seq_on_l1 * (count64 - ops_selected) === 0; + // new_seq_on_no_l1 * (count64 - ops_selected) === 0; + seq_end * (count64 - ops_selected) === 0; - // If previous row was a seq_end, start with new dst/src addresses, else increment by ops_in_row - continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + op_x_row)) === 0; - continue_seq_on_no_l1 * (dst64 - ('dst64 + op_x_row)) === 0; + // If previous row was a seq_end, start with new dst/src addresses, else increment by ops_selected + continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + ops_selected)) === 0; // continuation: L1 @[dst64_cont] + continue_seq_on_no_l1 * (dst64 - ('dst64 + ops_selected)) === 0; // continuation: !L1 @[dst64_cont] // NOTE: transition of src64 is defined only // LATCHS: // - // is_mem_eq = 'is_mem_eq + // flags = 'flags // main_step = 'main_step + // fill_byte = 'fill_byte - continue_seq_on_l1 * (is_mem_eq - segment_previous_is_mem_eq) === 0; - continue_seq_on_no_l1 * (is_mem_eq - 'is_mem_eq) === 0; + continue_seq_on_l1 * (flags - segment_previous_flags) === 0; + continue_seq_on_no_l1 * (flags - 'flags) === 0; continue_seq_on_l1 * (main_step - segment_previous_main_step) === 0; continue_seq_on_no_l1 * (main_step - 'main_step) === 0; - // SECURITY: control count no negative + if (enable_memset) { + continue_seq_on_l1 * (fill_byte - segment_previous_fill_byte) === 0; + continue_seq_on_no_l1 * (fill_byte - 'fill_byte) === 0; + } + + // SECURITY: control count64 no negative @[count64_zero_check] // - // if the seq_end it isn't active when count = 0, in each rows continues decreasing 8 x op_x_row + // if the seq_end it isn't active when count64 = 0, in each rows continues decreasing 8 x op_x_row // units, means in 2^22 rows * 2^3 * 2^2 = 2^27. It's secure, because if any row lies, at end of // instance this constraint fails. airval last_count_chunk[2]; range_check(expression: last_count_chunk[0], min: 0, max: 2**16-1); range_check(expression: last_count_chunk[1], min: 0, max: 2**16-1); - last_count_chunk[0] + last_count_chunk[1] * P2_16 === segment_last_count; + last_count_chunk[0] + last_count_chunk[1] * P2_16 === segment_last_count64; - airval padding_size; + // BUS + // + // enable_memcpy: 2x mem + 1 op = 2x + 1 + // enable_memset: x mem + 1 op = x + 1 + // enable_memcmp: 2x mem + 1 op = 2x + 1 + // enable_memcpy + enable_memcmp + enable_memset: 2x + 1 + // enable_memcpy + enable_memcmp + enable_memset + enable_count_load: 2x + 2 + // + // enable_count_load: 2x mem + 1 op + 1 mem = 2x + 2 + // enable_input: x mem + 4x rc16 + 1 op = 5x + 1 + // enable_input: x mem + 3x rc16 + 1 op = 4x + 1 (24+D8)*2 + // + // full: 2x mem + 4x rc16 + 1 op + 1 mem = 6x + 2 + // full: 2x mem + 3x rc16 + 1 op + 1 mem = 5x + 2 (24+D8)*2 + + const int bus_count = 1 + op_x_row + + ((enable_memcpy || enable_memcmp) ? op_x_row : 0) + + (enable_inputcpy ? 3 * op_x_row: 0) + + (enable_count_load ? 1 : 0); + + const int max_bus_op_degree = (bus_count & 0x01) == 0 ? 2 : 1; + + println(`bus_count: ${bus_count}`); + println(`max_bus_op_degree: ${max_bus_op_degree}`); + + // DMA_64_ALIGNED ==> DMA + // DMA_64_ALIGNED ==> MAIN + // -------------------------------------------------------------------------------- + // OP_DMA_MEMCPY (dst, 0, src, 0, 0, 0, 0, main_step, 0) + MEM(count) (*) ¿? + // OP_DMA_XMEMCPY (dst, 0, src, 0, 0, 0, 0, main_step, count) + // OP_DMA_INPUTCPY (dst, 0, count, 0, 0, 0, 0, main_step, 0) + // OP_DMA_XMEMSET (dst, 0, count, 0, 0, 0, 0, main_step, value) + // OP_DMA_XMEMEQ (dst, 0, src, 0, 0, 0, 0, main_step, count) + + const int b0_could_be_count = enable_inputcpy || enable_memset; + expr _b0 = 0; + + if (has_src && !b0_could_be_count) { + _b0 = src64 * 8; + } else if (!has_src && b0_could_be_count) { + _b0 = count64 * 8; + } else { + _b0 = count64 * 8 * (sel_inputcpy + sel_memset) + + src64 * 8 * (sel_memcpy + sel_memeq); + } + + if (degree(_b0) > max_bus_op_degree) { + col witness bits(32) air.b0; + b0 <== _b0; + } else { + const expr air.b0 = _b0; + } + + const int extended_arg_could_be_count = enable_memcpy || (enable_memcmp && !enable_count_load); + const int extended_arg_could_be_zero = enable_count_load || enable_inputcpy; - if (src_is_free_input) { - const int DMA_MEM_INPUT_CPY_OP = 0xD3; - const expr dma_op = DMA_MEM_INPUT_CPY_OP; - permutation_proves(operation_bus_id, [dma_op, dst64 * 8, 0, count * 8, 0, 0, 0, 0, main_step], sel: previous_seq_end); + expr _extended_arg; - // Used to cancel padding operations - direct_update_assumes(operation_bus_id, [dma_op, 0, 0, 0, 0, 0, 0, 0, 0], sel: adding_size); + if (extended_arg_could_be_count && !enable_memset && !extended_arg_could_be_zero) { + _extended_arg = count64 * 8; + } else if (!extended_arg_could_be_count) { + _extended_arg = fill_byte; } else { - // TRANSITION src64: - continue_seq_on_l1 * (src64 - (segment_previous_src64 + op_x_row)) === 0; - continue_seq_on_no_l1 * (src64 - ('src64 + op_x_row)) === 0; + _extended_arg = count64 * 8 * (sel_xmemcpy + sel_memeq) + fill_byte; + } + + if (degree(_extended_arg) > max_bus_op_degree) { + col witness bits(32) air.extended_arg; + extended_arg <== _extended_arg; + } else { + const expr air.extended_arg = _extended_arg; + } + + proves_operation(op:, + a: [dst64 * 8, 0], + b: [b0, 0], + c: [0, 0], + flag: 0, + main_step:, + extended_arg:, + mul: previous_seq_end); + + airval padding_size; - // At beginning of memcpy blocks we send two address - const expr dma_op = is_mem_eq * (DMA_MEM_EQ - DMA_MEM_CPY) + DMA_MEM_CPY; - permutation_proves(DMA_BUS_ID, [dma_op, dst64 * 8, src64 * 8, 0, 0, count * 8, main_step], sel: previous_seq_end); + // Used to cancel padding operations + assumes_padding_operation(op: 0, padding_size: padding_size); - // Used to cancel padding operations - direct_update_assumes(DMA_BUS_ID, [DMA_MEM_CPY, 0, 0, 0, 0, 0, 0], sel: padding_size); + // TRANSITIONS + // dst64 + // src64 + // count64 + // + if (has_src) { + continue_seq_on_l1 * (src64 - (segment_previous_src64 + op_x_row)) === 0; + continue_seq_on_no_l1 * (src64 - ('src64 + ops_selected)) === 0; } -} \ No newline at end of file + + continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + op_x_row)) === 0; + continue_seq_on_no_l1 * (dst64 - ('dst64 + op_x_row)) === 0; + + continue_seq_on_l1 * (count64 - (segment_previous_count64 + op_x_row)) === 0; + continue_seq_on_no_l1 * (count64 - ('count64 + ops_selected)) === 0; + + (1 - seq_end) * (ops_selected - op_x_row) === 0; + + // FUTURE IMPROVEMENTS + // + // Delegate/Split a operation, if a operation uses more than 1 row, the final row could + // be used to send to bus as assume the "rest" of operation. This is special usefull if + // instance large and cheap instances, without selectors, that only admins full rows, and + // the remaing part is done by other small instantation that has selectors. With this design + // the cost of one memcpy could be near 6-7 columns + costs of memory. +} \ No newline at end of file diff --git a/precompiles/dma/pil/dma_byte_cmp_table.pil b/precompiles/dma/pil/dma_byte_cmp_table.pil new file mode 100644 index 000000000..ba5f45762 --- /dev/null +++ b/precompiles/dma/pil/dma_byte_cmp_table.pil @@ -0,0 +1,43 @@ +require "std_lookup.pil" + +// this table not includes the equal values +const int DMA_BYTE_CMP_TABLE_SIZE = P2_8 * (P2_8 - 1); + +airtemplate DmaByteCmpTable(int N = DMA_BYTE_CMP_TABLE_SIZE) { + col fixed BYTE = [0:255..255:255]; + col fixed IS_NEG; + col fixed BYTE_DIFF; + + int index = 0; + #pragma transpile + for (int byte = 0; byte < P2_8; ++byte) { + for (int byte2 = 0; byte2 < byte; ++byte2) { + IS_NEG[index] = 0; + BYTE_DIFF[index] = byte - byte2; + ++index; + } + for (int byte2 = byte+1; byte2 < P2_8; ++byte2) { + IS_NEG[index] = 1; + BYTE_DIFF[index] = byte2 - byte; + ++index; + } + } + + assert_eq(index, N); + + // for (int byte = 0; byte < P2_8; ++byte) { + // for (int byte2 = 0; byte2 < P2_8; ++byte2) { + // if (byte == byte2) continue; + // const int is_neg = byte2 > byte ? 1 : 0; + // const int row = byte * 255 + byte2 - is_neg; + // const int byte_diff = byte2 > byte ? (byte2 - byte) : (byte - byte2); + // assert(BYTE[row] == byte, `FAIL BYTE[${row}] byte:${byte} byte2:${byte2}`); + // assert_eq(BYTE[row], byte, `FAIL BYTE[${row}] byte:${byte} byte2:${byte2}`); + // assert_eq(IS_NEG[row], is_neg, `FAIL IS_NEG[${row}] byte:${byte} byte2:${byte2}`); + // assert_eq(BYTE_DIFF[row], byte_diff, `FAIL BYTE_DIFF[${row}] byte:${byte} byte2:${byte2}`); + // } + // } + + col witness multiplicity; + lookup_proves(DMA_BYTE_CMP_TABLE_ID, mul: multiplicity, expressions: [BYTE, IS_NEG, BYTE_DIFF]); +} \ No newline at end of file diff --git a/precompiles/dma/pil/dma_pre_post.pil b/precompiles/dma/pil/dma_pre_post.pil index a61bcdc87..7333d7363 100644 --- a/precompiles/dma/pil/dma_pre_post.pil +++ b/precompiles/dma/pil/dma_pre_post.pil @@ -1,179 +1,442 @@ -// src_addr, src_offset -// dst_addr, dst_offset, count +// This machine is used for DMA operations performed at the beginning, to align +// writes and at the end in case aligned writes need to work with less than +// 8 bytes. The part that handles alignment of writes is known as PRE, while +// the part that handles the final part is known as POST. +// +// This machine needs to read one or two 64-bit words from src to be able to write or compare +// the destination. +// +// Another feature this machine has is to resolve the last different byte of a memcmp. In +// reality a mem_cmp(src, dst, count) = mem_eq(src, dst, count_eq) + cmp_byte(src + count_eq, dst + count_eq) +// where count_eq <= count. -airtemplate DmaPrePost(int N = 2**21) { +airtemplate DmaPrePost(int N = 2**21, // Rows of instance + const int RC = 2, // Number of chunks for native value + const int enable = E_DMA_ALL) // Enable operations +{ + const int enable_memcpy = (enable & E_DMA_MEMCPY) ? 1 : 0; + const int enable_memcmp = (enable & E_DMA_MEMCMP) ? 1 : 0; + const int enable_inputcpy = (enable & E_DMA_INPUTCPY) ? 1 : 0; + const int enable_memset = (enable & E_DMA_MEMSET) ? 1 : 0; + + const int has_src = (enable_memcpy || enable_memcmp); + const int enabled_count = enable_memcpy + enable_memcmp + enable_inputcpy + enable_memset; + const int enabled_all = enabled_count == 4; col witness bits(MAIN_STEP_BITS) main_step; - col witness bits(ADDR_W_BITS) src64; col witness bits(ADDR_W_BITS) dst64; col witness bits(3) dst_offset; - col witness bits(3) src_offset; col witness bits(3) count; - col witness bits(1) selread[7]; - const expr selr[8]; - expr _selr_0to6 = 0; - expr _selr_value = 0; - for (int i = 0; i < 7; ++i) { - selread[i] * (1 - selread[i]) === 0; - selr[i] = selread[i]; - _selr_0to6 = _selr_0to6 + selread[i]; - if (i > 0) { - _selr_value = _selr_value + i * selr[i]; - } + // bytes + // has sel ┌──────────────┐ + // src read rb rb2 pb + // ───────────────────────────────────────────── + // enable_memcpy ✓ ✓ ✓ ✓ ✓ + // enable_memcmp ✓ ✓ ✓ ✓ ✓ + // enable_inputcpy x x ✓ x ✓ + // enable_memset x x x x ✓ + + // prepare operation selectors + + expr _sel = 0; + + // definition @[sel_memcpy] + + if (enable_memcpy) { + col witness bits(1) air.sel_memcpy; + sel_memcpy * (1 - sel_memcpy) === 0; + _sel += sel_memcpy; + } else { + const int air.sel_memcpy = 0; } - const expr selr_0to6 = _selr_0to6; - _selr_value = _selr_value + 7 * selr[7]; - const expr selr_value = _selr_value; + // definition @[sel_memcmp] and @[memcmp_result_nz] - selr_0to6 * (1 - selr_0to6) === 0; - selr[7] = (1 - selr_0to6); + if (enable_memcmp) { + col witness bits(1) air.sel_memcmp; + sel_memcmp * (1 - sel_memcmp) === 0; + _sel += sel_memcmp; - col witness bits(1) dst_offset_gt_src_offset; - dst_offset_gt_src_offset * (1 - dst_offset_gt_src_offset) === 0; + col witness bits(1) air.memcmp_result_nz; + memcmp_result_nz * (1 - memcmp_result_nz) === 0; + // memcmp_result_nz only could be 1 if it's an memcmp operation + memcmp_result_nz * (1 - sel_memcmp) === 0; - col witness bits(1) enabled; - col witness bits(1) enabled_second_read; + col witness bits(32) air.l_memcmp_result; + // memcmp_result_nz is 0 the memcmp_result must be 0 + (1 - memcmp_result_nz) * l_memcmp_result === 0; + } else { + const int air.sel_memcmp = 0; + const int air.memcmp_result[2] = [0, 0]; + } - enabled * (1 - enabled) === 0; - enabled_second_read * (1 - enabled_second_read) === 0; + // definition @[sel_inputcpy] - // to force use table with enable when enabled_second_read is 1. If enable is 1, with table - // it's verified the correcte value of enabled_second_read using offset an count - enabled_second_read * (1 - enabled) === 0; + if (enable_inputcpy) { + col witness bits(1) air.sel_inputcpy; + sel_inputcpy * (1 - sel_inputcpy) === 0; + _sel += sel_inputcpy; + } else { + const int air.sel_inputcpy = 0; + } - const int V2R = 2 * 3; // Values To Read (2 x 32 bits = 64 bits x (R,R+8,PRE-W)) - const int B2R = V2R * 4; // Bytes To Read (4 bytes = value 32 bits) + // definition @[sel_memset] - col witness bits(8) bytes[B2R]; - const expr values[V2R]; - - for (int i = 0; i < B2R; i+=2) { - range_dual_byte(bytes[i+1], bytes[i], enabled); + if (enable_memset) { + col witness bits(1) air.sel_memset; + sel_memset * (1 - sel_memset) === 0; + _sel += sel_memset; + } else { + const int air.sel_memset = 0; + } + + // definition @[sel] + + const expr sel = _sel; + if (enabled_count > 1) { + sel * (1 - sel) === 0; + } + + // Prepare source and flags to selected the "row" + const int RB; + expr _rom_flags = 0; + + if (has_src) { + col witness bits(1) air.selr[7]; + + const expr air.sr[8]; + expr _sr_0to6 = 0; + expr _sr_value = 0; + for (int i = 0; i < 7; ++i) { + selr[i] * (1 - selr[i]) === 0; + sr[i] = selr[i]; + _sr_0to6 = _sr_0to6 + selr[i]; + if (i > 0) { + _sr_value = _sr_value + i * sr[i]; + } + } + const expr air.sr_0to6 = _sr_0to6; + _sr_value = _sr_value + 7 * sr[7]; + + const expr sr_value = _sr_value; + + sr_0to6 * (1 - sr_0to6) === 0; + sr[7] = (1 - sr_0to6); + + col witness bits(1) air.dst_offset_gt_src_offset; + dst_offset_gt_src_offset * (1 - dst_offset_gt_src_offset) === 0; + + + col witness bits(ADDR_W_BITS) air.src64; + col witness bits(3) air.src_offset; + + col witness bits(1) air.enabled_second_read; + enabled_second_read * (1 - enabled_second_read) === 0; + + // to force use table with enable when enabled_second_read is 1. If enable is 1, with table + // it's verified the correct value of enabled_second_read using offset an count + enabled_second_read * (1 - sel) === 0; + + // update rom flags + + _rom_flags += DMA_PRE_POST_DST_OFFSET_GT_SRC_F * dst_offset_gt_src_offset + + DMA_PRE_POST_ENABLED_SECOND_READ_F * enabled_second_read + + DMA_PRE_POST_SR_VALUE_F * sr_value; + + RB = RC * 4 * 2; + } else { + const int air.src64 = 0; + const int air.src_offset = 0; + if (enable_inputcpy) { + RB = RC * 4; + } else { + RB = 0; + } + } + + const int RV = RB / 4; + + if (RB > 0) { + col witness bits(8) air.rb[RB]; + const expr air.r_values[RV]; + for (int i = 0; i < RB; i+=2) { + range_dual_byte(rb[i+1], rb[i], sel); + } + + for (int i = 0; i < RV; ++i) { + r_values[i] = rb[i*4] + P2_8 * rb[i*4+1] + P2_16 * rb[i*4+2] + P2_24 * rb[i*4+3]; + } + if (enable_memset) { + // if memset it's enable, take first rb as fill_free, and define a constraint + // to be sure that all are equals (sr must be 0). + const expr air.fill_byte = rb[0]; + const int _len = RC * 4; + for (int i = 1; i < _len; ++i) { + sel_memset * (rb[i] - rb[0]) === 0; + } + } + } else if (enable_memset) { + // if memset it's enable, take first rb as fill_free, and define a constraint + // to be sure that all are equals (sr must be 0). + col witness bits(8) air.fill_byte; + (1 - sel_memset) * fill_byte === 0; } + + const int PRB = RC * 4; // Pre-write bytes + col witness bits(8) pb[PRB]; + + const expr p_values[RC]; - for (int i = 0; i < V2R; ++i) { - values[i] = bytes[i*4] + P2_8 * bytes[i*4+1] + P2_16 * bytes[i*4+2] + P2_24 * bytes[i*4+3]; + for (int i = 0; i < RC; i+=2) { + range_dual_byte(pb[i+1], pb[i], sel); + } + for (int i = 0; i < RC; ++i) { + p_values[i] = pb[i*4] + P2_8 * pb[i*4+1] + P2_16 * pb[i*4+2] + P2_24 * pb[i*4+3]; } - col witness bits(1) selb[8]; - expr selectors_mask = 0; + + col witness bits(1) sb[8]; for (int i = 0; i < 8; ++i) { - selb[i] * (1 - selb[i]) === 0; - selectors_mask += selb[i] * (1 << (7-i)); + sb[i] * (1 - sb[i]) === 0; + _rom_flags += sb[i] * (1 << (7-i)); } - col witness bits(32) write_value[4]; + if (enable_memcmp) { + // The DMA controller send throw bus, an flag @[memcmp_nz] to indicate that operation + // must detect that last byte of count was different. This flag only could be active + // if @[sel_memcmp] it's active. + // + // The count send by DMA controller includes the different bytes, the idea es generate + // a value that "compenses" this difference. + // + // To detect it, we detect the byte previous to a falling edge of selector of bytes + // read. See @[last_dst_byte] + // + // Using PrePost Rom verifies the factors associated to this position, PrePost sent to + // Rom also if difference is negative @[memcmp_result_is_negative], because if it's + // negative also the factor was negative. + // + // We need other table to verify @[last_dst_byte] with @[abs_diff_dst_src] and + // @[memcmp_result_is_negative]. This table doesn't include the zero for abs_diff_dst_src. + // The lookup to this tables is only active when @[memcmp_result_nz] - // byte_0, byte_1, byte_2, byte_3, byte_4, byte_5, byte_6, byte_7, byte_0', byte_1' ... + // For byte that's first different byte + // write_byte == dst_byte + // write_byte ≠ src_byte + // dst_minus_src = dst_byte - src_byte + // write_byte == dst_minus_src + src_byte - // src_offset 0 1 2 3 4 5 6 7 - // dst_offset 0 0 1 2 3 4 5 6 7 - // dst_offset 1 -1 - + col witness bits(8) air.last_dst_byte; + col witness bits(8) air.abs_diff_dst_src; + col witness bits(1) air.memcmp_result_is_negative; + col witness bits(64, signed) air.diff_factor[2]; + const expr air.diff_dst_src[2]; - - // │ src_offset: - // selr_value │ 0 1 2 3 4 5 6 7 - // ──────────────┼─────────────────────── - // dst_offset: 0 │ 0 1 2 3 4 5 6 7 - // 1 │ 1 0 1 2 3 4 5 6 - // 2 │ 2 1 0 1 2 3 4 5 - // 3 │ 3 2 1 0 1 2 3 4 - // 4 │ 4 3 2 1 0 1 2 3 - // 5 │ 5 4 3 2 1 0 1 2 - // 6 │ 6 5 4 3 2 1 0 1 - // 7 │ 7 6 5 4 3 2 1 0 - // - // NOTE: selr_value = ABS(src_offset - dst_offset) - - // ┌────── src_offset - dst_offset - // │ ┌── abs(src_offset - dst_offset) - // │ │ - // │ │ 0 1 2 3 4 5 6 7 - // ┌─────────────────┬─────────────────┐ - // -7 7 │ │ R0 │ - // -6 6 │ │ R0 R1 │ - // -5 5 │ │ R0 R1 R2 │ - // -4 4 │ │ R0 R1 R2 R3 │ - // -3 3 │ R0 │ R1 R2 R3 R4 │ - // -2 2 │ R0 R1 │ R2 R3 R4 R5 │ - // -1 1 │ R0 R1 R2 │ R3 R4 R5 R6 │ - // ├─────────────────┼─────────────────┤ - // 0 0 │ R0 R1 R2 R3 │ R4 R5 R6 R7 │ - // 1 1 │ R1 R2 R3 R4 │ R5 R6 R7 R8 │ - // 2 2 │ R2 R3 R4 R5 │ R6 R7 R8 R9 │ - // 3 3 │ R3 R4 R5 R6 │ R7 R8 R9 R10 │ - // 4 4 │ R4 R5 R6 R7 │ R8 R9 R10 R11 │ - // 5 5 │ R5 R6 R7 R8 │ R9 R10 R11 R12 │ - // 6 6 │ R6 R7 R8 R9 │ R10 R11 R12 R13 │ - // 7 7 │ R7 R8 R9 R10 │ R11 R12 R13 R14 │ - // └─────────────────┴─────────────────┘ + memcmp_result_is_negative * (1 - memcmp_result_is_negative) === 0; + + last_dst_byte <== sb[0] * (1 - sb[1]) * pb[0] + + sb[1] * (1 - sb[2]) * pb[1] + + sb[2] * (1 - sb[3]) * pb[2] + + sb[3] * (1 - sb[4]) * pb[3] + + sb[4] * (1 - sb[5]) * pb[4] + + sb[5] * (1 - sb[6]) * pb[5] + + sb[6] * (1 - sb[7]) * pb[6] + + sb[7] * pb[7]; + + diff_dst_src[0] = diff_factor[0] * abs_diff_dst_src * memcmp_result_nz; + diff_dst_src[1] = diff_factor[1] * abs_diff_dst_src * memcmp_result_nz; + + l_memcmp_result <== 0x1_0000_0000 * memcmp_result_is_negative + (1 - 2 * memcmp_result_is_negative) * abs_diff_dst_src; + + const expr air.memcmp_result[2]; + memcmp_result[0] = l_memcmp_result; + memcmp_result[1] = 0xFFFF_FFFF * memcmp_result_is_negative; + + _rom_flags += DMA_PRE_POST_MEMCMP_RESULT_IS_NEG_F * memcmp_result_is_negative + + DMA_PRE_POST_MEMCMP_RESULT_NZ_F * memcmp_result_nz; + + lookup_assumes(DMA_BYTE_CMP_TABLE_ID, [last_dst_byte, + memcmp_result_is_negative, + abs_diff_dst_src + ], sel: memcmp_result_nz); + } else { + const int air.diff_dst_src[2] = [0, 0]; + const int air.diff_factor[2] = [0, 0]; + const int air.diff_zero = 0; + const int air.memcmp_result[2] = [0, 0]; + const int air.memcmp_result_nz = 0; + } + if (!enable_memset) { + const int air.fill_byte; + } + + col witness bits(32) bus_write_value[2]; + + if (has_src) { + col witness bits(32) air.write_value[4]; + + // byte_0, byte_1, byte_2, byte_3, byte_4, byte_5, byte_6, byte_7, byte_0', byte_1' ... - // dst_offset <= src_offset - - write_value[0] === selr[0] * (selb[0] * bytes[0] + selb[1] * P2_8 * bytes[1] + selb[2] * P2_16 * bytes[2] + selb[3] * P2_24 * bytes[3]) + - selr[1] * (selb[0] * bytes[1] + selb[1] * P2_8 * bytes[2] + selb[2] * P2_16 * bytes[3] + selb[3] * P2_24 * bytes[4]) + - selr[2] * (selb[0] * bytes[2] + selb[1] * P2_8 * bytes[3] + selb[2] * P2_16 * bytes[4] + selb[3] * P2_24 * bytes[5]) + - selr[3] * (selb[0] * bytes[3] + selb[1] * P2_8 * bytes[4] + selb[2] * P2_16 * bytes[5] + selb[3] * P2_24 * bytes[6]) + - selr[4] * (selb[0] * bytes[4] + selb[1] * P2_8 * bytes[5] + selb[2] * P2_16 * bytes[6] + selb[3] * P2_24 * bytes[7]) + - selr[5] * (selb[0] * bytes[5] + selb[1] * P2_8 * bytes[6] + selb[2] * P2_16 * bytes[7] + selb[3] * P2_24 * bytes[8]) + - selr[6] * (selb[0] * bytes[6] + selb[1] * P2_8 * bytes[7] + selb[2] * P2_16 * bytes[8] + selb[3] * P2_24 * bytes[9]) + - selr[7] * (selb[0] * bytes[7] + selb[1] * P2_8 * bytes[8] + selb[2] * P2_16 * bytes[9] + selb[3] * P2_24 * bytes[10]) + - (1 - selb[0]) * bytes[16] + (1 - selb[1]) * P2_8 * bytes[17] + (1 - selb[2]) * P2_16 * bytes[18] + (1 - selb[3]) * P2_24 * bytes[19]; - - write_value[1] === selr[0] * (selb[4] * bytes[4] + selb[5] * P2_8 * bytes[5] + selb[6] * P2_16 * bytes[6] + selb[7] * P2_24 * bytes[7]) + - selr[1] * (selb[4] * bytes[5] + selb[5] * P2_8 * bytes[6] + selb[6] * P2_16 * bytes[7] + selb[7] * P2_24 * bytes[8]) + - selr[2] * (selb[4] * bytes[6] + selb[5] * P2_8 * bytes[7] + selb[6] * P2_16 * bytes[8] + selb[7] * P2_24 * bytes[9]) + - selr[3] * (selb[4] * bytes[7] + selb[5] * P2_8 * bytes[8] + selb[6] * P2_16 * bytes[9] + selb[7] * P2_24 * bytes[10]) + - selr[4] * (selb[4] * bytes[8] + selb[5] * P2_8 * bytes[9] + selb[6] * P2_16 * bytes[10] + selb[7] * P2_24 * bytes[11]) + - selr[5] * (selb[4] * bytes[9] + selb[5] * P2_8 * bytes[10] + selb[6] * P2_16 * bytes[11] + selb[7] * P2_24 * bytes[12]) + - selr[6] * (selb[4] * bytes[10] + selb[5] * P2_8 * bytes[11] + selb[6] * P2_16 * bytes[12] + selb[7] * P2_24 * bytes[13]) + - selr[7] * (selb[4] * bytes[11] + selb[5] * P2_8 * bytes[12] + selb[6] * P2_16 * bytes[13] + selb[7] * P2_24 * bytes[14]) + - (1 - selb[4]) * bytes[20] + (1 - selb[5]) * P2_8 * bytes[21] + (1 - selb[6]) * P2_16 * bytes[22] + (1 - selb[7]) * P2_24 * bytes[23]; - - // dst_offset > src_offset - - write_value[2] === selr[3] * ( selb[3] * P2_24 * bytes[0]) + - selr[2] * ( selb[2] * P2_16 * bytes[0] + selb[3] * P2_24 * bytes[1]) + - selr[1] * ( selb[1] * P2_8 * bytes[0] + selb[2] * P2_16 * bytes[1] + selb[3] * P2_24 * bytes[2]) + - (1 - selb[0]) * bytes[16] + (1 - selb[1]) * P2_8 * bytes[17] + (1 - selb[2]) * P2_16 * bytes[18] + (1 - selb[3]) * P2_24 * bytes[19]; - - write_value[3] === selr[7] * ( + selb[7] * P2_24 * bytes[0]) + - selr[6] * ( + selb[6] * P2_16 * bytes[0] + selb[7] * P2_24 * bytes[1]) + - selr[5] * ( + selb[5] * P2_8 * bytes[0] + selb[6] * P2_16 * bytes[1] + selb[7] * P2_24 * bytes[2]) + - selr[4] * (selb[4] * bytes[0] + selb[5] * P2_8 * bytes[1] + selb[6] * P2_16 * bytes[2] + selb[7] * P2_24 * bytes[3]) + - selr[3] * (selb[4] * bytes[1] + selb[5] * P2_8 * bytes[2] + selb[6] * P2_16 * bytes[3] + selb[7] * P2_24 * bytes[4]) + - selr[2] * (selb[4] * bytes[2] + selb[5] * P2_8 * bytes[3] + selb[6] * P2_16 * bytes[4] + selb[7] * P2_24 * bytes[5]) + - selr[1] * (selb[4] * bytes[3] + selb[5] * P2_8 * bytes[4] + selb[6] * P2_16 * bytes[5] + selb[7] * P2_24 * bytes[6]) + - (1 - selb[4]) * bytes[20] + (1 - selb[5]) * P2_8 * bytes[21] + (1 - selb[6]) * P2_16 * bytes[22] + (1 - selb[7]) * P2_24 * bytes[23]; - - - - const expr bus_write_value[2]; - - bus_write_value[0] = dst_offset_gt_src_offset * (write_value[2] - write_value[0]) + write_value[0]; - bus_write_value[1] = dst_offset_gt_src_offset * (write_value[3] - write_value[1]) + write_value[1]; + // │ src_offset: + // selr_value │ 0 1 2 3 4 5 6 7 + // ──────────────┼─────────────────────── + // dst_offset: 0 │ 0 1 2 3 4 5 6 7 + // 1 │ 1 0 1 2 3 4 5 6 + // 2 │ 2 1 0 1 2 3 4 5 + // 3 │ 3 2 1 0 1 2 3 4 + // 4 │ 4 3 2 1 0 1 2 3 + // 5 │ 5 4 3 2 1 0 1 2 + // 6 │ 6 5 4 3 2 1 0 1 + // 7 │ 7 6 5 4 3 2 1 0 + // + // NOTE: selr_value = ABS(src_offset - dst_offset) + + // R0..R3,R4..R7 = current row read bytes + // R8..R11,R12..R15 = R0'..R3',R4'..R7' bytes of next row + // + // ┌────── src_offset - dst_offset + // │ + // │ 0 1 2 3 4 5 6 7 + // ┌─────────────────┬─────────────────┐ + // 0 │ R0 R1 R2 R3 │ R4 R5 R6 R7 │ + // 1 │ R1 R2 R3 R4 │ R5 R6 R7 R8 │ + // 2 │ R2 R3 R4 R5 │ R6 R7 R8 R9 │ + // 3 │ R3 R4 R5 R6 │ R7 R8 R9 R10 │ + // 4 │ R4 R5 R6 R7 │ R8 R9 R10 R11 │ + // 5 │ R5 R6 R7 R8 │ R9 R10 R11 R12 │ + // 6 │ R6 R7 R8 R9 │ R10 R11 R12 R13 │ + // 7 │ R7 R8 R9 R10 │ R11 R12 R13 R14 │ + // └─────────────────┴─────────────────┘ + + // dst_offset <= src_offset + + expr _wr_0 = 0; + expr _wr_1 = 0; + for (int j = 0; j < 8; ++j) { + expr _sr_wr_0 = 0; + expr _sr_wr_1 = 0; + for (int i = 0; i < 4; ++i) { + const int factor = 1 << (i * 8); + _sr_wr_0 += factor * sb[i] * rb[i+j]; + _sr_wr_1 += factor * sb[i+4] * rb[i+4+j]; + } + _wr_0 += sr[j] * _sr_wr_0; + _wr_1 += sr[j] * _sr_wr_1; + } + for (int i = 0; i < 4; ++i) { + const int factor = 1 << (i * 8); + _wr_0 += factor * (1 - sb[i]) * pb[i]; + _wr_1 += factor * (1 - sb[i+4]) * pb[i+4]; + } + write_value[0] <== _wr_0; + write_value[1] <== _wr_1; + + + // dst_offset > src_offset + // ┌────── src_offset - dst_offset + // │ ┌── abs(src_offset - dst_offset) + // │ │ + // │ │ 0 1 2 3 4 5 6 7 + // ┌─────────────────┬─────────────────┐ + // -7 7 │ │ R0 │ + // -6 6 │ │ R0 R1 │ + // -5 5 │ │ R0 R1 R2 │ + // -4 4 │ │ R0 R1 R2 R3 │ + // -3 3 │ R0 │ R1 R2 R3 R4 │ + // -2 2 │ R0 R1 │ R2 R3 R4 R5 │ + // -1 1 │ R0 R1 R2 │ R3 R4 R5 R6 │ + // └─────────────────┴─────────────────┘ + + write_value[2] <== sr[3] * ( sb[3] * P2_24 * rb[0]) + + sr[2] * ( sb[2] * P2_16 * rb[0] + sb[3] * P2_24 * rb[1]) + + sr[1] * ( sb[1] * P2_8 * rb[0] + sb[2] * P2_16 * rb[1] + sb[3] * P2_24 * rb[2]) + + (1 - sb[0]) * pb[0] + (1 - sb[1]) * P2_8 * pb[1] + (1 - sb[2]) * P2_16 * pb[2] + (1 - sb[3]) * P2_24 * pb[3]; + + write_value[3] <== sr[7] * ( + sb[7] * P2_24 * rb[0]) + + sr[6] * ( + sb[6] * P2_16 * rb[0] + sb[7] * P2_24 * rb[1]) + + sr[5] * ( + sb[5] * P2_8 * rb[0] + sb[6] * P2_16 * rb[1] + sb[7] * P2_24 * rb[2]) + + sr[4] * (sb[4] * rb[0] + sb[5] * P2_8 * rb[1] + sb[6] * P2_16 * rb[2] + sb[7] * P2_24 * rb[3]) + + sr[3] * (sb[4] * rb[1] + sb[5] * P2_8 * rb[2] + sb[6] * P2_16 * rb[3] + sb[7] * P2_24 * rb[4]) + + sr[2] * (sb[4] * rb[2] + sb[5] * P2_8 * rb[3] + sb[6] * P2_16 * rb[4] + sb[7] * P2_24 * rb[5]) + + sr[1] * (sb[4] * rb[3] + sb[5] * P2_8 * rb[4] + sb[6] * P2_16 * rb[5] + sb[7] * P2_24 * rb[6]) + + (1 - sb[4]) * pb[4] + (1 - sb[5]) * P2_8 * pb[5] + (1 - sb[6]) * P2_16 * pb[6] + (1 - sb[7]) * P2_24 * pb[7]; + + bus_write_value[0] <== dst_offset_gt_src_offset * (write_value[2] - write_value[0]) + write_value[0] + diff_dst_src[0]; + bus_write_value[1] <== dst_offset_gt_src_offset * (write_value[3] - write_value[1]) + write_value[1] + diff_dst_src[1]; + // MEMORY ACCESS + // + // Read first 64 bits + // Read next 64 bits (optional, only if enabled_second_read) + // Read previous value before write + // Write final value + + precompiled_mem_load(sel: sel_memcpy + sel_memcmp, main_step:, addr: src64 * 8, value: [r_values[0], r_values[1]]); + precompiled_mem_load(sel: enabled_second_read, main_step:, addr: src64 * 8 + 8, value: [r_values[2], r_values[3]]); + + + + } else if (enable_inputcpy) { + // at this point memcpy, memcmp are disabled, but enable_memset could be enabled + expr _write_value_0 = 0; + expr _write_value_1 = 0; + for (int i = 0; i < 4; ++i) { + const int factor = 1 << (i * 8); + _write_value_0 += factor * (sb[i] * (rb[i] - pb[i]) + pb[i]); + _write_value_1 += factor * (sb[i+4] * (rb[i+4] - pb[i+4]) + pb[i+4]); + } + bus_write_value[0] <== _write_value_0; + bus_write_value[1] <== _write_value_1; + } else { + expr _write_value_0 = 0; + expr _write_value_1 = 0; + for (int i = 0; i < 4; ++i) { + const int factor = 1 << (i * 8); + _write_value_0 += factor * (sb[i] * (fill_byte - pb[i]) + pb[i]); + _write_value_1 += factor * (sb[i+4] * (fill_byte - pb[i+4]) + pb[i+4]); + } + bus_write_value[0] <== _write_value_0; + bus_write_value[1] <== _write_value_1; + } + + // MEMORY ACCESS // - // Read first 64 bits - // Read next 64 bits (optional, only if enabled_second_read) // Read previous value before write // Write final value - precompiled_mem_load(sel: enabled, main_step: main_step, addr: src64 * 8, value: [values[0], values[1]]); - precompiled_mem_load(sel: enabled_second_read, main_step: main_step, addr: src64 * 8 + 8, value: [values[2], values[3]]); - precompiled_mem_load(sel: enabled, main_step: main_step, addr: dst64 * 8, value: [values[4], values[5]]); - precompiled_mem_store(sel: enabled, main_step: main_step, addr: dst64 * 8, value: bus_write_value); + precompiled_mem_load(sel:, main_step:, addr: dst64 * 8, value: p_values); + precompiled_mem_store(sel:, main_step:, addr: dst64 * 8, value: bus_write_value); + + // DMA ===> DMA_PRE_POST (PRE) + // -------------------------------------------------------------------------------- + // OP_DMA_PRE_XMEMCPY (dst, src, dst_offset, src_offset, pre_count, main_step) + // OP_DMA_PRE_INPUTCPY (dst, 0, dst_offset, 0, pre_count, main_step) + // OP_DMA_PRE_XMEMEQ (dst, src, dst_offset, src_offset, pre_count, main_step) + // OP_DMA_PRE_XMEMCMP (dst, src, dst_offset, src_offset, pre_count, main_step, 0, r_nz, R0, R1) + // OP_DMA_PRE_XMEMSET (dst, 0, dst_offset, 0, pre_count, main_step, value) // Send operation to bus - permutation_proves(DMA_BUS_ID, [DMA_MEM_PRE_POST, dst64 * 8, src64 * 8, dst_offset, src_offset, count, main_step], sel: enabled); - - // selectors_mask (8 bits) - // enabled_second_read (1 bit) - // dst_offset_gt_src_offset (1 bit) - // selr_value (0-7) (3 bits) - const expr flags = selectors_mask + enabled_second_read * P2_8 + dst_offset_gt_src_offset * P2_9 + selr_value * P2_10; - lookup_assumes(DMA_PRE_POST_TABLE_ID, [flags, dst_offset, src_offset, count], sel: enabled); + permutation_proves(DMA_BUS_ID, [DMA_MEM_PRE_POST, + dst64 * 8, + src64 * 8, + dst_offset, + src_offset, + count, + main_step, + fill_byte, + memcmp_result_nz, + ...memcmp_result + ], sel:); + + + // @[selectors_mask] (8 bits) + // DMA_PRE_POST_ENABLED_SECOND_READ_F = P2_8 @[enabled_second_read] (1 bit) + // DMA_PRE_POST_DST_OFFSET_GT_SRC_F = P2_9 @[dst_offset_gt_src_offset] (1 bit) + // DMA_PRE_POST_SR_VALUE_F = P2_10 @[sr_value] (0-7) (3 bits) + // DMA_PRE_POST_MEMCMP_RESULT_IS_NEG_F = P2_13 @[memcmp_result_is_negative] (1 bit) + // DMA_PRE_POST_MEMCMP_RESULT_NZ = P2_14 @[memcmp_result_nz] (1 bit) + + const expr rom_flags = _rom_flags; + lookup_assumes(DMA_PRE_POST_TABLE_ID, [rom_flags, dst_offset, src_offset, count, ...diff_factor], sel: sel); } \ No newline at end of file diff --git a/precompiles/dma/pil/dma_pre_post_table.pil b/precompiles/dma/pil/dma_pre_post_table.pil index 803af229b..5afbcd775 100644 --- a/precompiles/dma/pil/dma_pre_post_table.pil +++ b/precompiles/dma/pil/dma_pre_post_table.pil @@ -1,28 +1,62 @@ require "std_lookup.pil" -const int DMA_PRE_POST_TABLE_SIZE = 280; +const int DMA_PRE_POST_TABLE_SIZE = 280 * 3; + +const int DMA_PRE_POST_ENABLED_SECOND_READ_F = P2_8; +const int DMA_PRE_POST_DST_OFFSET_GT_SRC_F = P2_9; +const int DMA_PRE_POST_SR_VALUE_F = P2_10; +const int DMA_PRE_POST_MEMCMP_RESULT_IS_NEG_F = P2_13; +const int DMA_PRE_POST_MEMCMP_RESULT_NZ_F = P2_14; airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { col fixed FLAGS; col fixed DST_OFFSET; col fixed SRC_OFFSET; col fixed COUNT; + col fixed L_FACTOR; + col fixed H_FACTOR; int index = 0; + // OUT @[selectors_mask] (8 bits) + // DMA_PRE_POST_ENABLED_SECOND_READ_F = P2_8 OUT @[enabled_second_read] (1 bit) + // DMA_PRE_POST_DST_OFFSET_GT_SRC_F = P2_9 OUT @[dst_offset_gt_src_offset] (1 bit) + // DMA_PRE_POST_SR_VALUE_F = P2_10 OUT @[sr_value] (0-7) (3 bits) + // DMA_PRE_POST_MEMCMP_RESULT_IS_NEG_F = P2_13 IN @[memcmp_result_is_negative] (1 bit) + // DMA_PRE_POST_MEMCMP_RESULT_NZ_F = P2_14 IN @[memcmp_result_nz] (1 bit) + // POST OPERATION objective execute the last incomplete "write": // dst_offset = 0 // count ∈ [1,7] int table_offsets[64]; + const int flags_memcmp_result_positive = DMA_PRE_POST_MEMCMP_RESULT_NZ_F; + const int flags_memcmp_result_negative = DMA_PRE_POST_MEMCMP_RESULT_NZ_F + DMA_PRE_POST_MEMCMP_RESULT_IS_NEG_F; + const int l_factors_positive[8]; + const int l_factors_negative[8]; + const int h_factors_positive[8]; + const int h_factors_negative[8]; + + for (int count = 0; count < 8; ++ count) { + const int l_factor = (1 << (8 * count)); + const int h_factor = (1 << (8 * count)); + + l_factors_positive[count] = count < 4 ? l_factor : 0; + l_factors_negative[count] = count < 4 ? -l_factor : 0; + h_factors_positive[count] = count >= 4 ? h_factor : 0; + h_factors_negative[count] = count >= 4 ? -h_factor : 0; + } + for (int src_offset = 0; src_offset < 8; ++src_offset) { table_offsets[src_offset] = index; for (int count = 1; count < 8; ++ count) { - SRC_OFFSET[index] = src_offset; - DST_OFFSET[index] = 0; - COUNT[index] = count; - + for (int k = 0; k < 3; ++k) { + const int lindex = index + k; + SRC_OFFSET[lindex] = src_offset; + DST_OFFSET[lindex] = 0; + COUNT[lindex] = count; + } const int selectors = (0xFF << (8 - count)) & 0xFF; assert(selectors != 0); assert(selectors <= 0xFF); @@ -30,8 +64,21 @@ airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { const int enabled_second_read = (src_offset + count) > 8 ? 1:0; // selr_value = src_offset // dst_offset_gt_src_offset = 0 - FLAGS[index] = selectors + enabled_second_read * P2_8 + P2_10 * src_offset; - index += 1; + expr flags = selectors + DMA_PRE_POST_ENABLED_SECOND_READ_F * enabled_second_read + + DMA_PRE_POST_SR_VALUE_F * src_offset; + FLAGS[index] = flags; + FLAGS[index + 1] = flags + flags_memcmp_result_positive; + FLAGS[index + 2] = flags + flags_memcmp_result_negative; + + const int factor_index = count - 1; + L_FACTOR[index] = 0; + L_FACTOR[index + 1] = l_factors_positive[factor_index]; + L_FACTOR[index + 2] = l_factors_negative[factor_index]; + + H_FACTOR[index] = 0; + H_FACTOR[index + 1] = h_factors_positive[factor_index]; + H_FACTOR[index + 2] = h_factors_negative[factor_index]; + index += 3; } } @@ -44,10 +91,13 @@ airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { for (int src_offset = 0; src_offset < 8; ++src_offset) { table_offsets[dst_offset * 8 + src_offset] = index; for (int count = 1; count < (9 - dst_offset); ++ count) { - SRC_OFFSET[index] = src_offset; - DST_OFFSET[index] = dst_offset; - COUNT[index] = count; - + for (int k = 0; k < 3; ++k) { + const int lindex = index + k; + SRC_OFFSET[lindex] = src_offset; + DST_OFFSET[lindex] = dst_offset; + COUNT[lindex] = count; + } + const int selectors = mask & (0xFF << (8 - (dst_offset + count))); assert(selectors != 0); assert(selectors <= 0xFF); @@ -57,8 +107,24 @@ airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { const int selr_value = dst_offset > src_offset ? dst_offset - src_offset: src_offset - dst_offset; // selr_value = src_offset // dst_offset_gt_src_offset = 0 - FLAGS[index] = selectors + enabled_second_read * P2_8 + dst_offset_gt_src_offset * P2_9 + selr_value * P2_10; - index += 1; + expr flags = selectors + DMA_PRE_POST_ENABLED_SECOND_READ_F * enabled_second_read + + DMA_PRE_POST_DST_OFFSET_GT_SRC_F * dst_offset_gt_src_offset + + DMA_PRE_POST_SR_VALUE_F * selr_value; + + FLAGS[index] = flags; + FLAGS[index + 1] = flags + flags_memcmp_result_positive; + FLAGS[index + 2] = flags + flags_memcmp_result_negative; + + const int factor_index = dst_offset + count - 1; + L_FACTOR[index] = 0; + L_FACTOR[index + 1] = l_factors_positive[factor_index]; + L_FACTOR[index + 2] = l_factors_negative[factor_index]; + + H_FACTOR[index] = 0; + H_FACTOR[index + 1] = h_factors_positive[factor_index]; + H_FACTOR[index + 2] = h_factors_negative[factor_index]; + + index += 3; } } } @@ -67,5 +133,10 @@ airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { assert_eq(index, DMA_PRE_POST_TABLE_SIZE); col witness multiplicity; - lookup_proves(DMA_PRE_POST_TABLE_ID, mul: multiplicity, expressions: [FLAGS, DST_OFFSET, SRC_OFFSET, COUNT]); + lookup_proves(DMA_PRE_POST_TABLE_ID, expressions: [FLAGS, + DST_OFFSET, + SRC_OFFSET, + COUNT, + L_FACTOR, + H_FACTOR], mul: multiplicity ); } \ No newline at end of file diff --git a/precompiles/dma/pil/dma_unaligned.pil b/precompiles/dma/pil/dma_unaligned.pil index c2159613e..b0c0542b8 100644 --- a/precompiles/dma/pil/dma_unaligned.pil +++ b/precompiles/dma/pil/dma_unaligned.pil @@ -1,35 +1,120 @@ -// TODO: Optimization Dma32AlignedOp - -airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) { +// DmaUnaligned +// +// Basically this machine performs a 64-bit read and breaks it down into bytes, +// taking those it needs from the current row or the next row using the row offset +// for this purpose. In the last row, it commits to some bytes that it adds in the +// continuations so that in the first row of the next instance it verifies that it +// matches the read data. +// +// Writes must be aligned, since more than one write can be done at the same instant +// of time. This means that count + 1 rows are needed to prove this unaligned operation. +// +// ROW i ┊ i+1 ... i+count-1 ┊ i+count +// ┊ ┊ +// bytes 0..7 ┊ 8..15 ┊ +// ┌──────────────┐ ┊ ┌──────────────┐ ┊ +// ┊ ┊ +// READ ┌─┬─┬─┬─┐┌─┬─┬─┬─┐ ┊ ┌─┬─┬─┬─┐┌─┬─┬─┬─┐ ┊ ┌─┬─┬─┬─┐┌─┬─┬─┬─┐ +// └─┴─┴─┴─┘└─┴─┴─┴─┘ ┊ └─┴─┴─┴─┘└─┴─┴─┴─┘ ┊ └─┴─┴─┴─┘└─┴─┴─┴─┘ +// ┌───┘ │ │ │ │ │ ┊ │ │ │ ┌────┊──┘ │ +// │ ┌───┘ │ │ │ │ ┊ │ │ │ │ ┌──┊────┘ +// │ │ ┌────┘ │ │ │ ┊ │ │ │ │ │ ┊ +// │ │ │ ┌────┘ │ │ ┊ │ │ │ ...... │ │ ┊ +// │ │ │ │ ┌───┘ │ ┊ │ │ │ │ │ ┊ +// │ │ │ │ │ ┌───┘ ┊ │ │ │ │ │ ┊ +// │ │ │ │ │ │ ┌────┊──┘ │ │ ...... │ │ ┊ +// │ │ │ │ │ │ │ ┌──┊────┘ │ │ │ ┊ +// │ │ │ │ │ │ │ │ ┊ │ │ │ ┊ +// │ │ │ │ │ │ │ │ ┊ ┌───┘ │ │ ┊ +// WRITE ┌─┬─┬─┬─┐┌─┬─┬─┬─┐ ┊ ┌─┬─┬─┬─┐┌─┬─┬─┬─┐ ┊ In the last row there +// └─┴─┴─┴─┘└─┴─┴─┴─┘ ┊ └─┴─┴─┴─┘└─┴─┴─┴─┘ ┊ aren't writes +// ┊ ┊ +// └──────────────┘ ┊ ┊ +// bytes 16..23 ┊ ┊ +// +// NOTE: Data from several programs has been analyzed to see if a special version +// for 32-bit displacements would make sense because then it wouldn't be necessary +// to decompose into bytes since the chunks would be taken directly. It has been seen +// that these operations are not the most frequent. + +airtemplate DmaUnaligned(int N = 2**21, // Rows of instance + const int RC = 2, // Number of chunks for native value + const expr enable_flag = 1, // Expression to active the beggining of + // continuations, used for executions that + // not generates instances. + // + const int enable = E_DMA_ALL, // Enable operations + int cont_subid = 0) { // Force subid used in continuations + + const int enable_memcpy = (enable & E_DMA_MEMCPY) ? 1 : 0; + const int enable_memcmp = (enable & E_DMA_MEMCMP) ? 1 : 0; + assert((enable_memcmp + enable_memcpy) >= 1); + + const int enable_full = enable_memcmp && enable_memcmp; + + // continuation control, used to create a new continuation subid to avoid collision + // between DmaUnaligned continuations. If you instance diferent airs with same template + // need diferenciate them. If parameter cont_subid = 0 means that the subid is auto-generated + + container proof.dma_unaligned { + int cont_subids_count = 0; + int cont_subids[64]; + } + use proof.dma_unaligned; + + if (cont_subid == 0) { + cont_subid = cont_subids_count + 1; + for (int i = 0; i < cont_subids_count; ++i) { + if (cont_subids[i] >= cont_subid) { + cont_subid = cont_subids[i] + 1; + } + } + } else { + for (int i = 0; i < cont_subids_count; ++i) { + assert(cont_subids[i] != cont_subid, `duplicated cont_subid ${cont_subid}`); + } + } + cont_subids[cont_subids_count] = cont_subid; + cont_subids_count += 1; const expr L1 = get_L1(); const expr LAST = L1'; airval segment_id; // Id of current segment - airval segment_previous_seq_end; // Last value of `seq_end` in previous segment. - airval segment_previous_src64; // Last value of `src64` in previous segment. - airval segment_previous_dst64; // Last value of `dst64` in previous segment. - airval segment_previous_main_step; // Last value of `main_step` in previous segment. - airval segment_previous_offset; // Last value of `offset` in previous segment. - airval segment_previous_count; // Last value of `count` in previous segment. - airval segment_previous_is_mem_eq; // Last value of `is_mem_eq' in previous segment. + airval segment_previous_seq_end; // Last value of @[seq_end] in previous segment. + airval segment_previous_src64; // Last value of @[src64] in previous segment. + airval segment_previous_dst64; // Last value of @[dst64] in previous segment. + airval segment_previous_main_step; // Last value of @[main_step] in previous segment. + airval segment_previous_offset; // Last value of @[offset] in previous segment. + airval segment_previous_count; // Last value of @[count] in previous segment. + airval segment_first_bytes[8]; // bytes of next block - airval segment_last_seq_end; // Last value of `seq_end` in current segment. - airval segment_last_src64; // Last value of `src64` in current segment. - airval segment_last_dst64; // Last value of `dst64` in current segment. - airval segment_last_main_step; // Last value of `main_step` in current segment. - airval segment_last_offset; // Last value of `offset` in current segment. - airval segment_last_count; // Last value of `count` in current segment. - airval segment_last_is_mem_eq; // Last value of `is_mem_eq' in previous segment. + airval segment_last_seq_end; // Last value of @[seq_end] in current segment. + airval segment_last_src64; // Last value of @[src64] in current segment. + airval segment_last_dst64; // Last value of @[dst64] in current segment. + airval segment_last_main_step; // Last value of @[main_step] in current segment. + airval segment_last_offset; // Last value of @[offset] in current segment. + airval segment_last_count; // Last value of @[count] in current segment. airval segment_next_bytes[8]; // bytes of next block airval is_last_segment; // 1 if this is the last segment, 0 otherwise. + if (enable_full) { + airval air.segment_previous_is_memeq; // Last value of @[is_memeq] in previous segment. + airval air.segment_last_is_memeq; // Last value of @[is_memeq] in previous segment. + + segment_previous_is_memeq * (1 - segment_previous_is_memeq) === 0; + segment_last_is_memeq * (1 - segment_last_is_memeq) === 0; + } else { + const int air.segment_previous_is_memeq = 0; + const int air.segment_last_is_memeq = 0; + } + segment_previous_seq_end * (1 - segment_previous_seq_end) === 0; segment_last_seq_end * (1 - segment_last_seq_end) === 0; @@ -47,17 +132,19 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) seq_end * (1 - seq_end) === 0; col witness bits(1) previous_seq_end; - previous_seq_end === L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; - - col witness bits(1) is_mem_eq; // Last value of `is_mem_eq' in previous segment. - is_mem_eq * (1 - is_mem_eq) === 0; + previous_seq_end <== L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; LAST * (seq_end - segment_last_seq_end) === 0; LAST * (1 - seq_end) * (src64 - segment_last_src64) === 0; LAST * (1 - seq_end) * (dst64 - segment_last_dst64) === 0; LAST * (1 - seq_end) * (main_step - segment_last_main_step) === 0; LAST * (1 - seq_end) * (count - segment_last_count) === 0; - LAST * (1 - seq_end) * (is_mem_eq - segment_last_is_mem_eq) === 0; + + if (enable_full) { + col witness bits(1) air.is_memeq; // Last value of @[is_memeq] in previous segment. + is_memeq * (1 - is_memeq) === 0; + LAST * (1 - seq_end) * (is_memeq - segment_last_is_memeq) === 0; + } col witness bits(1) offset_7; col witness bits(1) offset_6; @@ -107,7 +194,7 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) // of segment_next_bytes to 0. col witness bits(1) no_last_no_seq_end; - no_last_no_seq_end === (1 - LAST) * (1 - seq_end); + no_last_no_seq_end <== (1 - LAST) * (1 - seq_end); for (int i = 0; i < 8; ++i) { // if LAST next_bytes are bytes sent to bus, if no LAST but is a seq_end, no sense @@ -130,7 +217,7 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) col witness bits(32) write_value[2]; - write_value[0] === offset_1 * (read_bytes[1] + P2_8 * read_bytes[2] + P2_16 * read_bytes[3] + P2_24 * read_bytes[4]) + + write_value[0] <== offset_1 * (read_bytes[1] + P2_8 * read_bytes[2] + P2_16 * read_bytes[3] + P2_24 * read_bytes[4]) + offset_2 * (read_bytes[2] + P2_8 * read_bytes[3] + P2_16 * read_bytes[4] + P2_24 * read_bytes[5]) + offset_3 * (read_bytes[3] + P2_8 * read_bytes[4] + P2_16 * read_bytes[5] + P2_24 * read_bytes[6]) + offset_4 * (read_bytes[4] + P2_8 * read_bytes[5] + P2_16 * read_bytes[6] + P2_24 * read_bytes[7]) + @@ -138,7 +225,7 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) offset_6 * (read_bytes[6] + P2_8 * read_bytes[7] + P2_16 * next_bytes[0] + P2_24 * next_bytes[1]) + offset_7 * (read_bytes[7] + P2_8 * next_bytes[0] + P2_16 * next_bytes[1] + P2_24 * next_bytes[2]); - write_value[1] === offset_1 * (read_bytes[5] + P2_8 * read_bytes[6] + P2_16 * read_bytes[7] + P2_24 * next_bytes[0]) + + write_value[1] <== offset_1 * (read_bytes[5] + P2_8 * read_bytes[6] + P2_16 * read_bytes[7] + P2_24 * next_bytes[0]) + offset_2 * (read_bytes[6] + P2_8 * read_bytes[7] + P2_16 * next_bytes[0] + P2_24 * next_bytes[1]) + offset_3 * (read_bytes[7] + P2_8 * next_bytes[0] + P2_16 * next_bytes[1] + P2_24 * next_bytes[2]) + offset_4 * (next_bytes[0] + P2_8 * next_bytes[1] + P2_16 * next_bytes[2] + P2_24 * next_bytes[3]) + @@ -146,24 +233,67 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) offset_6 * (next_bytes[2] + P2_8 * next_bytes[3] + P2_16 * next_bytes[4] + P2_24 * next_bytes[5]) + offset_7 * (next_bytes[3] + P2_8 * next_bytes[4] + P2_16 * next_bytes[5] + P2_24 * next_bytes[6]); + // At begining of sequence + // DMA BUS [op, dst64, src64, dst_offset, src_offset, bytes, main_step] + + const expr op; + const expr padding_op; + const expr is_write; + + if (enable_full) { + op = is_memeq * (OP_DMA_XMEMEQ - OP_DMA_XMEMCPY) + OP_DMA_XMEMCPY; + padding_op = OP_DMA_XMEMCPY; + is_write = (1 - is_memeq); + } else if (enable_memcpy) { + op = OP_DMA_XMEMCPY; + padding_op = OP_DMA_XMEMCPY; + is_write = 1; + } else { + op = OP_DMA_XMEMEQ; + padding_op = OP_DMA_XMEMEQ; + is_write = 0; + } + + // DMA_UNALIGNED op_bus.prove <== + // -------------------------------------------------------------------------------- + // OP_DMA_XMEMCPY (dst, 0, src, 0, 0, 0, 0, main_step, count, src_offset) + // OP_DMA_XMEMEQ (dst, 0, src, 0, 0, 0, 0, main_step, count, src_offset) + + proves_operation(op:, + a: [dst64 * 8, 0], + b: [src64 * 8, 0], + main_step:, + extended_arg: count, + extra_args: [offset], + mul: previous_seq_end); + // memory access precompiled_mem_load( sel: 1, - main_step: main_step, + main_step: , addr: src64 * 8, value: read_value ); - precompiled_mem_store( + precompiled_mem_op( + is_write:, sel: (1 - seq_end), - main_step: main_step, + main_step:, addr: dst64 * 8, value: write_value ); - // At begining of sequence - // DMA BUS [op, dst64, src64, dst_offset, src_offset, bytes, main_step] - permutation_proves(DMA_BUS_ID, [DMA_MEM_CPY, dst64 * 8, src64 * 8, 0, offset, count * 8, main_step], sel: previous_seq_end); + + // PADDING + // + // cancel operation sent to bus src=0, dst=0, offset=1, count=0, main_step=0 + // precompiled_mem_load_padding demostrate read addr=0 width=8 main_step=0 value=0 + // in padding rows seq_end is active, means no precompiled_mem_store + + airval padding_size; + + precompiled_mem_load_padding(padding: padding_size); + assumes_padding_operation(op: padding_op, extra_args: [1], padding_size:); const expr continue_seq_on_l1 = L1 * (1 - segment_previous_seq_end); const expr continue_seq_on_no_l1 = (1 - L1) * (1 - 'seq_end); @@ -190,14 +320,15 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) // LATCHS: // // offset = 'offset - // is_mem_eq = 'is_mem_eq + // is_memeq = 'is_memeq // main_step = 'main_step continue_seq_on_l1 * (offset - segment_previous_offset) == 0; continue_seq_on_no_l1 * (offset - 'offset) === 0; - continue_seq_on_l1 * (is_mem_eq - segment_previous_is_mem_eq) == 0; - continue_seq_on_no_l1 * (is_mem_eq - 'is_mem_eq) === 0; - + if (enable_full) { + continue_seq_on_l1 * (is_memeq - segment_previous_is_memeq) == 0; + continue_seq_on_no_l1 * (is_memeq - 'is_memeq) === 0; + } continue_seq_on_l1 * (main_step - segment_previous_main_step) == 0; continue_seq_on_no_l1 * (main_step - 'main_step) === 0; @@ -216,51 +347,51 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) range_check(expression: last_count_chunk[1], min: 0, max: 2**16-1); last_count_chunk[0] + last_count_chunk[1] * P2_16 === segment_last_count; - - // PADDING - // - // cancel operation sent to bus src=0, dst=0, offset=1, count=0, main_step=0 - // precompiled_mem_load_padding demostrate read addr=0 width=8 main_step=0 value=0 - // in padding rows seq_end is active, means no precompiled_mem_store - - airval padding_rows; - permutation_assumes(DMA_BUS_ID, [DMA_MEM_CPY, 0, 0, 0, 1, 0, 0], sel: padding_rows); - precompiled_mem_load_padding(padding: padding_rows); - // CONTINUATIONS // AIR_ID, segment_id, seq_end, src64, dst64, count, main_step - direct_global_update_proves(MEM_CPY_BYTE_CONT_ID, [0, // initial segment_id - 0, - 1, // initial seq_end - 0, // initial src64 - 0, // initial dst64 - 0, // initial offset - 0, // initial count - 0, - 0,0,0,0,0,0,0,0], // next bytes - sel: enable); - - - direct_update_assumes(MEM_CPY_BYTE_CONT_ID, [ segment_id, - 0, - segment_previous_seq_end, - segment_previous_src64, - segment_previous_dst64, - segment_previous_offset, - segment_previous_count, - segment_previous_main_step, - ...segment_first_bytes]); - - direct_update_proves(MEM_CPY_BYTE_CONT_ID, [ segment_id + 1, - is_last_segment, - segment_last_seq_end, - segment_last_src64, - segment_last_dst64, - segment_last_offset, - segment_last_count, - segment_last_main_step, - ...segment_next_bytes], sel: 1 - is_last_segment); + direct_global_update_proves(DMA_UNALIGNED_CONT_ID, + [0, // initial segment_id + 0, // security flag no_final @[] indicate that not final + cont_subid, // continuation subid + 1, // initial @[seq_end] + 0, // initial @[src64] + 0, // initial @[dst64] + 0, // initial @[offset] + 0, // initial @[count] + 0, // initial @[main_step] + 0, // initial @[is_memeq] + 0,0,0,0,0,0,0,0], // first bytes + sel: enable_flag); + + + direct_update_assumes(DMA_UNALIGNED_CONT_ID, + [segment_id, // current segment_id + 0, // security flag to indicate that not final + cont_subid, // continuation subid @[cont_subid] + segment_previous_seq_end, // previous @[seq_end] + segment_previous_src64, // previous @[src64] + segment_previous_dst64, // previous @[dst64] + segment_previous_offset, // previous @[offset] + segment_previous_count, // previous @[count] + segment_previous_main_step,// previous @[main_step] + segment_previous_is_memeq, // previous @[is_memeq] + ...segment_first_bytes // first bytes read from memory + ]); + + direct_update_proves(DMA_UNALIGNED_CONT_ID, + [segment_id + 1, // next segment_id + is_last_segment, // security flag to indicate that not final + cont_subid, // continuation subid @[cont_subid] + segment_last_seq_end, // last @[seq_end] + segment_last_src64, // last @[src64] + segment_last_dst64, // last @[dst64] + segment_last_offset, // last @[offset] + segment_last_count, // last @[count] + segment_last_main_step, // last @[main_step] + segment_last_is_memeq, // last @[is_memeq] + ...segment_next_bytes // next bytes read from memory + ], sel: 1 - is_last_segment); } \ No newline at end of file diff --git a/precompiles/dma/src/dma_gen_mem_inputs.rs b/precompiles/dma/src/dma_gen_mem_inputs.rs index 35a62564e..6bbc87d1c 100644 --- a/precompiles/dma/src/dma_gen_mem_inputs.rs +++ b/precompiles/dma/src/dma_gen_mem_inputs.rs @@ -2,7 +2,7 @@ use precompiles_common::MemBusHelpers; use precompiles_common::MemProcessor; use precompiles_helpers::{DmaHelpers, DmaInfo}; use zisk_common::{A, B, DMA_ENCODED, OP, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; -use zisk_core::{zisk_ops::ZiskOp, EXTRA_PARAMS}; +use zisk_core::{zisk_ops::ZiskOp, EXTRA_PARAMS_ADDR}; pub fn generate_dma_mem_inputs( data: &[u64], @@ -26,7 +26,7 @@ pub fn generate_dma_mem_inputs( // stores happend after loads. MemBusHelpers::mem_aligned_load( - EXTRA_PARAMS as u32, + EXTRA_PARAMS_ADDR as u32, main_step, DmaInfo::get_count(encoded) as u64, mem_processors, @@ -208,7 +208,7 @@ pub fn skip_dma_mem_inputs( let dst64_to = (dst + use_count + 7) as u32 & !0x07; let src64_to = (src + use_count + 7) as u32 & !0x07; - if !mem_processors.skip_addr(EXTRA_PARAMS as u32) { + if !mem_processors.skip_addr(EXTRA_PARAMS_ADDR as u32) { return false; } diff --git a/precompiles/keccakf/pil/keccakf.pil b/precompiles/keccakf/pil/keccakf.pil index 8e0c7c4bd..d42eeba3a 100644 --- a/precompiles/keccakf/pil/keccakf.pil +++ b/precompiles/keccakf/pil/keccakf.pil @@ -6,7 +6,7 @@ require "keccakf_table.pil" // Precompile in charge of performing the Keccak-f[1600] permutation. // For reference: https://keccak.team/files/Keccak-reference-3.0.pdf -airtemplate Keccakf(const int N = 2**17, const int operation_bus_id = OPERATION_BUS_ID, const int mem_ops_in_parallel = 25) { +airtemplate Keccakf(const int N = 2**17, const int mem_ops_in_parallel = 25) { // Validate individual inputs const int WIDTH = 1600; const int BITS_PER_LIMB = 32; @@ -181,7 +181,8 @@ airtemplate Keccakf(const int N = 2**17, const int operation_bus_id = OPERATION_ } // --> Constraints to make sure that this coprocessor is called from the main processor - lookup_proves(operation_bus_id, [OP_KECCAKF, 0, 0, step_addr'(ADDR_STATE), 0, 0, 0, 0, step_addr'(STEP_MAIN)], mul: in_use_clk_0); + proves_operation(op: OP_KECCAKF, b: [step_addr'(ADDR_STATE), 0], main_step: step_addr'(STEP_MAIN), + mul: in_use_clk_0); function add(const expr b[]): const expr { const int len = length(b); diff --git a/precompiles/poseidon2/pil/poseidon2.pil b/precompiles/poseidon2/pil/poseidon2.pil index f9a25b379..0a85e5c27 100644 --- a/precompiles/poseidon2/pil/poseidon2.pil +++ b/precompiles/poseidon2/pil/poseidon2.pil @@ -7,7 +7,7 @@ require "poseidon2_constants.pil" // For reference: https://eprint.iacr.org/2023/323.pdf -airtemplate Poseidon2(const int N = 2**17, const int operation_bus_id = OPERATION_BUS_ID) { +airtemplate Poseidon2(const int N = 2**17) { const int n = 16; // Compute some stats @@ -161,7 +161,7 @@ airtemplate Poseidon2(const int N = 2**17, const int operation_bus_id = OPERATIO // --> Constraints to make sure that this coprocessor is called from the main processor - lookup_proves(operation_bus_id, [OP_POSEIDON2, 0, 0, step_addr'(ADDR_STATE), 0, 0, 0, 0, step_addr'(STEP_MAIN)], mul: in_use_clk_0); + proves_operation(op: OP_POSEIDON2, b: [step_addr'(ADDR_STATE), 0], main_step: step_addr'(STEP_MAIN), mul: in_use_clk_0); function clock_set(const expr mvcol = 1, const int start = 0, int end = -1): const expr { expr result = 0; diff --git a/precompiles/sha256f/pil/sha256f.pil b/precompiles/sha256f/pil/sha256f.pil index 1dd677969..d44db4788 100644 --- a/precompiles/sha256f/pil/sha256f.pil +++ b/precompiles/sha256f/pil/sha256f.pil @@ -11,7 +11,7 @@ require "opids.pil" // Note: We use little endian representation. -airtemplate Sha256f(const int N = 2**22, const int operation_bus_id = OPERATION_BUS_ID) { +airtemplate Sha256f(const int N = 2**22) { /* ROW a[0..32] e[0..32] w[0..32] STAGE | 0 | 0bD₁D₂..D₃₂ | 0bH₁H₂..H₃₂ | XXXXXXXXXXXXXXXX | LOAD STATE | @@ -250,7 +250,7 @@ airtemplate Sha256f(const int N = 2**22, const int operation_bus_id = OPERATION_ // --> Constraints to make sure that this coprocessor is called from the main processor - lookup_proves(operation_bus_id, [OP_SHA256F, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(STEP_MAIN)], mul: in_use_clk_0); + proves_operation(op: OP_SHA256F, b: [step_addr'(ADDR_OP), 0], main_step: step_addr'(STEP_MAIN), mul: in_use_clk_0); function pack(const expr a[]): expr { const int len = length(a); diff --git a/state-machines/arith/pil/arith.pil b/state-machines/arith/pil/arith.pil index 127c816cf..6ff146009 100644 --- a/state-machines/arith/pil/arith.pil +++ b/state-machines/arith/pil/arith.pil @@ -8,7 +8,7 @@ require "arith_range_table.pil" // full mul_64 full_32 mul_32 // TOTAL 88 77 57 44 -airtemplate Arith(const int N = 2**18, const int operation_bus_id = OPERATION_BUS_ID) { +airtemplate Arith(const int N = 2**18) { const int CHUNK_SIZE = 2**16; const int CHUNKS_INPUT = 4; @@ -266,19 +266,15 @@ airtemplate Arith(const int N = 2**18, const int operation_bus_id = OPERATION_BU col witness bits(1) multiplicity; - lookup_proves(operation_bus_id, [op, - bus_a0, bus_a1, - bus_b0, bus_b1, - bus_res0, bus_res1, - div_by_zero /*+ div_overflow*/, 0], mul: multiplicity); + proves_operation(op:, a: [bus_a0, bus_a1], b: [bus_b0, bus_b1], c: [bus_res0, bus_res1], + flag: div_by_zero, mul: multiplicity); // Check that remainder (d) is lower than divisor (b) when division is performed // Specifically, we ensure that 0 <= |d| < |b| - lookup_assumes(operation_bus_id, [(1 - nr) * (1 - nb) * OP_LTU + nr * (1 - nb) * OP_LT_ABS_NP + (1 - nr) * nb * OP_LT_ABS_PN + nr * nb * OP_GT, - (d[0] + CHUNK_SIZE * d[1]), (d[2] + CHUNK_SIZE * d[3]) + m32 * nr * 0xFFFFFFFF, // remainder - (b[0] + CHUNK_SIZE * b[1]), (b[2] + CHUNK_SIZE * b[3]) + m32 * nb * 0xFFFFFFFF, // divisor - 1, 0, - 1, 0], sel: div * (1 - div_by_zero)); + assumes_operation(op: (1 - nr) * (1 - nb) * OP_LTU + nr * (1 - nb) * OP_LT_ABS_NP + (1 - nr) * nb * OP_LT_ABS_PN + nr * nb * OP_GT, + a: [(d[0] + CHUNK_SIZE * d[1]), (d[2] + CHUNK_SIZE * d[3]) + m32 * nr * 0xFFFFFFFF], // remainder + b: [(b[0] + CHUNK_SIZE * b[1]), (b[2] + CHUNK_SIZE * b[3]) + m32 * nb * 0xFFFFFFFF], // divisor + c: [1, 0], flag: 1, sel: div * (1 - div_by_zero)); for (int index = 0; index < length(carry); ++index) { arith_range_table_assumes(ARITH_RANGE_CARRY, carry[index]); // TODO: review carry range diff --git a/state-machines/arith/pil/arith_mul64.pil b/state-machines/arith/pil/arith_mul64.pil index 76a234cd9..7d1757e29 100644 --- a/state-machines/arith/pil/arith_mul64.pil +++ b/state-machines/arith/pil/arith_mul64.pil @@ -8,7 +8,7 @@ require "arith_range_table.pil" // full mul_64 full_32 mul_32 // TOTAL 88 77 57 44 -airtemplate ArithMul64(const int N = 2**18, const int operation_bus_id = OPERATION_BUS_ID, const int dual_result = 0) { +airtemplate ArithMul64(const int N = 2**18, const int dual_result = 0) { const int CHUNK_SIZE = 2**16; const int CHUNKS_INPUT = 4; const int CHUNKS_OP = CHUNKS_INPUT * 2; @@ -206,11 +206,12 @@ airtemplate ArithMul64(const int N = 2**18, const int operation_bus_id = OPERATI // Check that remainder (d) is lower than divisor (b) when division is performed // Specifically, we ensure that 0 <= |d| < |b| - lookup_assumes(operation_bus_id, [(1 - nr) * (1 - nb) * OP_LTU + nr * (1 - nb) * OP_LT_ABS_NP + (1 - nr) * nb * OP_LT_ABS_PN + nr * nb * OP_GT, - (d[0] + CHUNK_SIZE * d[1]), (d[2] + CHUNK_SIZE * d[3]) + m32 * nr * 0xFFFFFFFF, // remainder - (b[0] + CHUNK_SIZE * b[1]), (b[2] + CHUNK_SIZE * b[3]) + m32 * nb * 0xFFFFFFFF, // divisor - 1, 0, - 1, 0], sel: div * (1 - div_by_zero)); + assumes_operation(op: [(1 - nr) * (1 - nb) * OP_LTU + nr * (1 - nb) * OP_LT_ABS_NP + (1 - nr) * nb * OP_LT_ABS_PN + nr * nb * OP_GT, + a: [d[0] + CHUNK_SIZE * d[1], (d[2] + CHUNK_SIZE * d[3]) + m32 * nr * 0xFFFFFFFF], // remainder + b: (b[0] + CHUNK_SIZE * b[1]), (b[2] + CHUNK_SIZE * b[3]) + m32 * nb * 0xFFFFFFFF], // divisor + c: [1, 0], + flag: 1, + sel: div * (1 - div_by_zero)); for (int index = 0; index < length(carry); ++index) { arith_range_table_assumes(ARITH_RANGE_CARRY, carry[index]); // TODO: review carry range diff --git a/state-machines/binary/pil/binary.pil b/state-machines/binary/pil/binary.pil index 64107e66a..07a79b1f6 100644 --- a/state-machines/binary/pil/binary.pil +++ b/state-machines/binary/pil/binary.pil @@ -51,7 +51,7 @@ require "binary_table.pil" Note: op = b_op + 0x10*mode32 */ -airtemplate Binary(const int N = 2**21, const int RC = 2, const int bits = 64, const int operation_bus_id = OPERATION_BUS_ID) { +airtemplate Binary(const int N = 2**21, const int RC = 2, const int bits = 64) { if (RC != 2 || bits != 64) { error(`Currently only RC=2 and bits=64 are supported, got RC=${RC}, bits=${bits}`); } @@ -153,8 +153,8 @@ airtemplate Binary(const int N = 2**21, const int RC = 2, const int bits = 64, c // Otherwise, the result is simply c c[0] += cout; - lookup_proves(operation_bus_id, [b_op + 0x10 * mode32, ...a, ...b, ...c, cout, 0]); + proves_operation(op: b_op + 0x10 * mode32, a:, b:, c:, flag:cout); airval padding_size; - direct_update_assumes(operation_bus_id, [OP_ADD, 0, 0, 0, 0, 0, 0, 0, 0], sel: padding_size); + assumes_padding_operation(op: OP_ADD, padding_size:); } diff --git a/state-machines/binary/pil/binary_add.pil b/state-machines/binary/pil/binary_add.pil index 90d909061..2b97eeb1b 100644 --- a/state-machines/binary/pil/binary_add.pil +++ b/state-machines/binary/pil/binary_add.pil @@ -3,7 +3,7 @@ require "std_range_check.pil" require "operations.pil" require "opids.pil" -airtemplate BinaryAdd(const int N = 2**21, const int operation_bus_id = OPERATION_BUS_ID, const int RC = 2) { +airtemplate BinaryAdd(const int N = 2**21, const int RC = 2) { col witness bits(32) a[RC]; col witness bits(32) b[RC]; col witness bits(16) c_chunks[RC*2]; @@ -22,8 +22,8 @@ airtemplate BinaryAdd(const int N = 2**21, const int operation_bus_id = OPERATIO range_check(expression: c_chunks[i * 2 + 1], min: 0, max: 2**16 - 1); } - lookup_proves(operation_bus_id, [OP_ADD, ...a, ...b, ...c, 0, 0]); + proves_operation(op: OP_ADD, a:, b:, c:); airval padding_size; - direct_update_assumes(operation_bus_id, [OP_ADD, 0, 0, 0, 0, 0, 0, 0, 0], sel: padding_size); + assumes_padding_operation(op: OP_ADD, padding_size:); } \ No newline at end of file diff --git a/state-machines/binary/pil/binary_extension.pil b/state-machines/binary/pil/binary_extension.pil index 3538406d5..276e7cd81 100644 --- a/state-machines/binary/pil/binary_extension.pil +++ b/state-machines/binary/pil/binary_extension.pil @@ -68,7 +68,7 @@ x in2[x] out[x][0] out[x][1] Result: 0xFFFF8abc 0xFFFFFFFF */ -airtemplate BinaryExtension(const int N = 2**18, const int bits = 64, const int operation_bus_id = OPERATION_BUS_ID) { +airtemplate BinaryExtension(const int N = 2**18, const int bits = 64) { if (bits != 64) { error(`Currently only bits=64 is supported, got bits=${bits}`); } @@ -115,20 +115,10 @@ airtemplate BinaryExtension(const int N = 2**18, const int bits = 64, const int // if op_is_shift == 1 => [op, a[0], a[1], free_in_b + 256 * b[0], b[1], sum(free_in_c[*][0]), sum(free_in_c[*][1]), 0] // if op_is_shift == 0 => [op, b[0], b[1], a[0], a[1], sum(free_in_c[*][0]), sum(free_in_c[*][1]), 0] - lookup_proves( - operation_bus_id, - [ - op, - op_is_shift * (a[0] - b[0]) + b[0], - op_is_shift * (a[1] - b[1]) + b[1], - op_is_shift * (free_in_b + BYTE_BASE * b[0] - a[0]) + a[0], - op_is_shift * (b[1] - a[1]) + a[1], - c[0], - c[1], - 0, 0 - ] - ); + proves_operation(op:, a: [op_is_shift * (a[0] - b[0]) + b[0], op_is_shift * (a[1] - b[1]) + b[1]], + b: [op_is_shift * (free_in_b + BYTE_BASE * b[0] - a[0]) + a[0], op_is_shift * (b[1] - a[1]) + a[1]], + c:); airval padding_size; - direct_update_assumes(operation_bus_id, [OP_SEXT_B, 0, 0, 0, 0, 0, 0, 0, 0], sel: padding_size); + assumes_padding_operation(op: OP_SEXT_B, padding_size:); } diff --git a/state-machines/frequent-ops/pil/frequent_ops.pil b/state-machines/frequent-ops/pil/frequent_ops.pil index d95bf327c..c4eddd9f4 100644 --- a/state-machines/frequent-ops/pil/frequent_ops.pil +++ b/state-machines/frequent-ops/pil/frequent_ops.pil @@ -1,8 +1,8 @@ require "std_lookup.pil" require "opids.pil" -airtemplate FrequentOps(const int N = 2**24, int RC = 2, const int operation_bus_id = OPERATION_BUS_ID, - const string bin_file = "../src/frequent_ops_fixed.bin", const int table_id = -1) { +airtemplate FrequentOps(const int N = 2**24, int RC = 2, const string bin_file = "../src/frequent_ops_fixed.bin", + const int table_id = -1) { #pragma extern_fixed_file `${bin_file}` @@ -14,8 +14,5 @@ airtemplate FrequentOps(const int N = 2**24, int RC = 2, const int operation_bus col fixed C[RC]; col fixed FLAG; - // REVIEW: REPLACE BY CONSTANT - col fixed __ZERO__ = [0...]; - - lookup_proves(operation_bus_id, [OP, ...A, ...B, ...C, FLAG, __ZERO__], mul: multiplicity, table_id: table_id); + lookup_proves(OPERATION_BUS_ID, [OP, ...A, ...B, ...C, FLAG], table_id:, mul: multiplicity, surname: PIOP_SURNAME_DYNAMIC); } \ No newline at end of file diff --git a/state-machines/main/pil/main.pil b/state-machines/main/pil/main.pil index 107140b22..0cafd2be2 100644 --- a/state-machines/main/pil/main.pil +++ b/state-machines/main/pil/main.pil @@ -12,7 +12,6 @@ const int MAX_RANGE = (1 << 24) - 1; const int REG_BASE_ADDR = 0xA000_0000; airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, - const int operation_bus_id = OPERATION_BUS_ID, const int REGS_IN_MAIN_FROM = 1, const int REGS_IN_MAIN_TO = 31) { const int REGS_IN_MAIN = REGS_IN_MAIN_TO - REGS_IN_MAIN_FROM + 1; @@ -112,7 +111,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, } else { col witness bits(32) air.a_imm1; } - col witness bits(1) op_with_step; // Selector to include step on operation bus + col witness bits(1) is_precompiled; // Selector to include step on operation bus // Source B @@ -156,9 +155,11 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, col witness bits(64, signed) air.inc_sp; } - + // branch jump if flag = 1, jump to pc + jmp_offset1 + // if set_pc = 1, jump to c[0] + jmp_offset1 col witness bits(64, signed) jmp_offset1; + // default jump if flag = 0, jump to pc + jmp_offset2 col witness bits(64, signed) jmp_offset2; col witness bits(1) m32; @@ -333,10 +334,6 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, range_check(expression: b_mem_step - b_reg_prev_mem_step - 1, min: 0, max: MAX_RANGE, sel: b_src_reg); range_check(expression: store_mem_step - store_reg_prev_mem_step - 1 , min: 0, max: MAX_RANGE, sel: store_reg); - // Sent to bus the external operation - - lookup_assumes(operation_bus_id, [op, a[0], (1 - m32) * a[1], b[0], (1 - m32) * b[1], ...c, flag, STEP * op_with_step], sel: is_external_op); - const expr a_src_c; const expr b_src_c; @@ -360,6 +357,25 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, b_imm[1] = b_imm1; } + // Sent to bus the external operation + + // Precompiles are generated throw transpilation because them not exists directly in RISCV + // The traspiler has context about the precompiled and knows that if a and b are 32-bit address + // in this case for specific precompileds you could use a_imm[1] and b_imm[1] to specify an + // offset over the address. Also has the possibility to send and extend static argument known in + // transpilation time. + + // The flag m32 is used to avoid send "garbage" values to bus, to simplify the other state + // state machines and to increase the hits to FROPS (frequent operations). + + assumes_operation(op: , + a: [a[0] + a_src_reg * a_imm[1], (1 - m32) * a[1]], + b: [b[0] + b_src_reg * b_imm[1], (1 - m32) * b[1]], + c: , + flag:, + main_step: STEP * is_precompiled, + extended_arg: jmp_offset1 * is_precompiled, + sel: is_external_op); for (int index = 0; index < RC; ++index) { const expr previous_c = SEGMENT_L1 * (segment_previous_c[index] - 'c[index]) + 'c[index]; @@ -454,7 +470,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, a_src_imm * (1 - a_src_imm) === 0; a_src_mem * (1 - a_src_mem) === 0; - op_with_step * (1 - op_with_step) === 0; + is_precompiled * (1 - is_precompiled) === 0; b_src_imm * (1 - b_src_imm) === 0; b_src_mem * (1 - b_src_mem) === 0; is_external_op * (1 - is_external_op) === 0; @@ -468,7 +484,7 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, b_src_reg * (1 - b_src_reg) === 0; store_reg * (1 - store_reg) === 0; - const expr rom_flags = 1 + 2 * a_src_imm + 4 * a_src_mem + 8 * op_with_step + 16 * b_src_imm + const expr rom_flags = 1 + 2 * a_src_imm + 4 * a_src_mem + 8 * is_precompiled + 16 * b_src_imm + 32 * b_src_mem + 64 * is_external_op + 128 * store_pc + 256 * store_mem + 512 * store_ind + 1024 * set_pc + 2048 * m32 + 4096 * b_src_ind + 8192 * a_src_reg + 16384 * b_src_reg + 32768 * store_reg; From 2e5ff509f6b301be9b034fa1b0780bf5b109e537 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 2 Feb 2026 21:06:39 +0100 Subject: [PATCH 400/782] Fix rom histogram address in asm code generator --- core/src/zisk_rom_2_asm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index f2c01716b..b1664f573 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -51,7 +51,7 @@ const REG_CHUNK_PLAYER_ADDRESS: &str = "rbp"; // Used only in chunk player // - rsp // Only used to calculate histogram position for every rom pc -const TRACE_ADDR_NUMBER: u64 = 0xc0000020; +const TRACE_ADDR_NUMBER: u64 = 0xd0000000 + 0x20; // Fcall params and result lengths const FCALL_PARAMS_LENGTH: u64 = 386; From 557c723f7cc2f658fb4c8f9e18c2f18d49b05eea Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 3 Feb 2026 09:12:57 +0100 Subject: [PATCH 401/782] Refactor usage of registers in hints code generator --- core/src/zisk_rom_2_asm.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index f2c01716b..517f14091 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -8233,13 +8233,13 @@ impl ZiskRom2Asm { // Copy data consuming REG_B u64's starting at REG_A=0, increasing REG_A until REG_A == REG_B // Initialize REG_A to 0 - *code += &format!("\txor {}, {} {}\n", REG_A, REG_A, ctx.comment_str("a = 0")); + *code += &format!("\txor {}, {} {}\n", REG_FLAG, REG_FLAG, ctx.comment_str("flag = 0")); // Loop start *code += &format!("pc_{:x}_fcall_copy_params_loop_start:\n", ctx.pc); // End loop when REG_A == REG_B - *code += &format!("\tcmp {}, {} {}\n", REG_A, REG_B, ctx.comment_str("a =? b")); + *code += &format!("\tcmp {}, {} {}\n", REG_FLAG, REG_B, ctx.comment_str("flag =? b")); *code += &format!("\tje pc_{:x}_fcall_copy_params_loop_end\n", ctx.pc); // Calculate the address of the next precompile u64 value = address + read % buffer_size @@ -8249,7 +8249,12 @@ impl ZiskRom2Asm { ctx.mem_precompile_read_address, ctx.comment_str("address = precompile_read") ); - *code += &format!("\tadd {}, {} {}\n", REG_ADDRESS, REG_A, ctx.comment_str("address += a")); + *code += &format!( + "\tadd {}, {} {}\n", + REG_ADDRESS, + REG_FLAG, + ctx.comment_str("address += flag") + ); *code += &format!( "\tand {}, 0x{:x} {}\n", REG_ADDRESS, @@ -8268,14 +8273,14 @@ impl ZiskRom2Asm { *code += &format!( "\tmov [{} + {}*8 + {}*8], {} {}\n", reg_address, - REG_A, + REG_FLAG, FCALL_RESULT, REG_VALUE, ctx.comment_str("addr[] = value") ); - // Increment REG_A - *code += &format!("\tinc {} {}\n", REG_A, ctx.comment_str("a++")); + // Increment REG_FLAG + *code += &format!("\tinc {} {}\n", REG_FLAG, ctx.comment_str("flag++")); // Jump to loop start *code += &format!("\tjmp pc_{:x}_fcall_copy_params_loop_start\n", ctx.pc); @@ -8287,7 +8292,7 @@ impl ZiskRom2Asm { *code += &format!( "\tadd {}, {} {}\n", ctx.mem_precompile_read_address, - REG_A, + REG_FLAG, ctx.comment_str("precompile_read += fcall_result_size") ); } From 55bad7eee647b457db7b8888231f70e555d53634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Tue, 3 Feb 2026 12:37:26 +0000 Subject: [PATCH 402/782] Adding back k256 patching funcs --- .../src/zisklib/lib/secp256k1/curve.rs | 129 ++++++++++++++ .../src/zisklib/lib/secp256k1/ecdsa.rs | 23 +++ .../src/zisklib/lib/secp256k1/field.rs | 139 +++++++++++++++ .../src/zisklib/lib/secp256k1/scalar.rs | 158 ++++++++++++++++++ 4 files changed, 449 insertions(+) diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index d65fd1293..bfde12550 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -838,3 +838,132 @@ pub fn secp256k1_triple_scalar_mul_with_g( Some([res.x[0], res.x[1], res.x[2], res.x[3], res.y[0], res.y[1], res.y[2], res.y[3]]) } } + +// ==================== C FFI Functions ==================== + +/// Converts a non-infinity point `p` on the Secp256k1 curve from jacobian coordinates to affine coordinates +pub fn secp256k1_to_affine(p: &[u64; 12], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 8] { + let z: [u64; 4] = [p[8], p[9], p[10], p[11]]; + + // Point at infinity cannot be converted to affine + debug_assert!(z != ZERO_256, "Cannot convert point at infinity to affine"); + + let zinv = secp256k1_fp_inv(&z, #[cfg(feature = "hints")] hints); + let zinv_sq = secp256k1_fp_square(&zinv, #[cfg(feature = "hints")] hints); + + let x: [u64; 4] = [p[0], p[1], p[2], p[3]]; + let y: [u64; 4] = [p[4], p[5], p[6], p[7]]; + + let x_res = secp256k1_fp_mul(&x, &zinv_sq, #[cfg(feature = "hints")] hints); + let y_res = secp256k1_fp_mul(&secp256k1_fp_mul(&y, &zinv_sq, #[cfg(feature = "hints")] hints), &zinv, #[cfg(feature = "hints")] hints); + + [x_res[0], x_res[1], x_res[2], x_res[3], y_res[0], y_res[1], y_res[2], y_res[3]] +} + +/// # Safety +/// - `x_bytes_ptr` must point to 32 bytes (big-endian x-coordinate) +/// - `out_ptr` must point to at least 8 u64s +/// +/// Returns 1 on success, 0 if no valid point exists +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_decompress_c")] +pub unsafe extern "C" fn secp256k1_decompress_c( + x_bytes_ptr: *const u8, + y_is_odd: u8, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> u8 { + // Convert the x-coordinate from BEu8 to LEu64 + let x_bytes: &[u8; 32] = &*(x_bytes_ptr as *const [u8; 32]); + let x = bytes_be_to_u64_le(x_bytes); + + let (x, y) = match secp256k1_decompress(&x, y_is_odd != 0, #[cfg(feature = "hints")] hints) { + Ok((x, y)) => (x, y), + Err(_) => return 0, + }; + + *out_ptr.add(0) = x[0]; + *out_ptr.add(1) = x[1]; + *out_ptr.add(2) = x[2]; + *out_ptr.add(3) = x[3]; + *out_ptr.add(4) = y[0]; + *out_ptr.add(5) = y[1]; + *out_ptr.add(6) = y[2]; + *out_ptr.add(7) = y[3]; + + 1 +} + +/// # Safety +/// - `p_ptr` must point to 12 u64s (jacobian point) +/// - `out_ptr` must point to at least 8 u64s +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_to_affine_c")] +pub unsafe extern "C" fn secp256k1_to_affine_c(p_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { + let p: &[u64; 12] = &*(p_ptr as *const [u64; 12]); + let result = secp256k1_to_affine(p, #[cfg(feature = "hints")] hints); + + *out_ptr.add(0) = result[0]; + *out_ptr.add(1) = result[1]; + *out_ptr.add(2) = result[2]; + *out_ptr.add(3) = result[3]; + *out_ptr.add(4) = result[4]; + *out_ptr.add(5) = result[5]; + *out_ptr.add(6) = result[6]; + *out_ptr.add(7) = result[7]; +} + +/// # Safety +/// - `k1_ptr`, `k2_ptr` must point to 4 u64s each +/// - `p_ptr` must point to 8 u64s +/// - `out_ptr` must point to at least 8 u64s +/// +/// Returns true if result is point at infinity +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_double_scalar_mul_with_g_c")] +pub unsafe extern "C" fn secp256k1_double_scalar_mul_with_g_c( + k1_ptr: *const u64, + k2_ptr: *const u64, + p_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { + let k1: &[u64; 4] = &*(k1_ptr as *const [u64; 4]); + let k2: &[u64; 4] = &*(k2_ptr as *const [u64; 4]); + let p: &[u64; 8] = &*(p_ptr as *const [u64; 8]); + + match secp256k1_double_scalar_mul_with_g(k1, k2, p, #[cfg(feature = "hints")] hints) { + None => true, + Some(res) => { + *out_ptr.add(0) = res[0]; + *out_ptr.add(1) = res[1]; + *out_ptr.add(2) = res[2]; + *out_ptr.add(3) = res[3]; + *out_ptr.add(4) = res[4]; + *out_ptr.add(5) = res[5]; + *out_ptr.add(6) = res[6]; + *out_ptr.add(7) = res[7]; + false + } + } +} + +// Helper to convert 32 big-endian bytes to [u64; 4] little-endian limbs +#[inline] +fn bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { + let mut result = [0u64; 4]; + for (i, r) in result.iter_mut().enumerate() { + let offset = 24 - i * 8; + *r = u64::from_be_bytes([ + bytes[offset], + bytes[offset + 1], + bytes[offset + 2], + bytes[offset + 3], + bytes[offset + 4], + bytes[offset + 5], + bytes[offset + 6], + bytes[offset + 7], + ]); + } + result +} \ No newline at end of file diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs index ff395e2be..52fc248cf 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs @@ -72,3 +72,26 @@ pub fn secp256k1_ecdsa_verify( ) .is_none() } + +// ==================== C FFI Functions ==================== + +/// # Safety +/// - `pk_ptr` must point to 8 u64s +/// - `z_ptr`, `r_ptr`, `s_ptr` must point to 4 u64s each +/// +/// Returns true if signature is valid +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_ecdsa_verify_c")] +pub unsafe extern "C" fn secp256k1_ecdsa_verify_c( + pk_ptr: *const u64, + z_ptr: *const u64, + r_ptr: *const u64, + s_ptr: *const u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> bool { + let pk: &[u64; 8] = &*(pk_ptr as *const [u64; 8]); + let z: &[u64; 4] = &*(z_ptr as *const [u64; 4]); + let r: &[u64; 4] = &*(r_ptr as *const [u64; 4]); + let s: &[u64; 4] = &*(s_ptr as *const [u64; 4]); + secp256k1_ecdsa_verify(pk, z, r, s, #[cfg(feature = "hints")] hints) +} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs index 965aff4cb..7d6dcee68 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs @@ -191,3 +191,142 @@ pub fn secp256k1_fp_sqrt( (sqrt, false) } } + +// ==================== C FFI Functions ==================== + +/// # Safety +/// - `x_ptr` must point to 4 u64s +/// - `out_ptr` must point to at least 4 u64s +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_reduce_c")] +pub unsafe extern "C" fn secp256k1_fp_reduce_c(x_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { + let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); + + if lt(x, &P) { + *out_ptr.add(0) = x[0]; + *out_ptr.add(1) = x[1]; + *out_ptr.add(2) = x[2]; + *out_ptr.add(3) = x[3]; + return; + } + + let mut params = SyscallArith256ModParams { + a: x, + b: &[1, 0, 0, 0], + c: &[0, 0, 0, 0], + module: &P, + d: &mut [0, 0, 0, 0], + }; + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); + + *out_ptr.add(0) = params.d[0]; + *out_ptr.add(1) = params.d[1]; + *out_ptr.add(2) = params.d[2]; + *out_ptr.add(3) = params.d[3]; +} + +/// # Safety +/// - `x_ptr` must point to 4 u64s +/// - `y_ptr` must point to 4 u64s +/// - `out_ptr` must point to at least 4 u64s +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_add_c")] +pub unsafe extern "C" fn secp256k1_fp_add_c( + x_ptr: *const u64, + y_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { + let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); + let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); + + let mut params = + SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &P, d: &mut [0, 0, 0, 0] }; + syscall_arith256_mod(&mut params, #[cfg(feature = "hints")] hints, ); + + *out_ptr.add(0) = params.d[0]; + *out_ptr.add(1) = params.d[1]; + *out_ptr.add(2) = params.d[2]; + *out_ptr.add(3) = params.d[3]; +} + +/// # Safety +/// - `x_ptr` must point to 4 u64s +/// - `out_ptr` must point to at least 4 u64s +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_negate_c")] +pub unsafe extern "C" fn secp256k1_fp_negate_c(x_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { + let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); + + let mut params = SyscallArith256ModParams { + a: x, + b: &P_MINUS_ONE, + c: &[0, 0, 0, 0], + module: &P, + d: &mut [0, 0, 0, 0], + }; + syscall_arith256_mod(&mut params, #[cfg(feature = "hints")] hints, ); + + *out_ptr.add(0) = params.d[0]; + *out_ptr.add(1) = params.d[1]; + *out_ptr.add(2) = params.d[2]; + *out_ptr.add(3) = params.d[3]; +} + +/// # Safety +/// - `x_ptr` must point to 4 u64s +/// - `y_ptr` must point to 4 u64s +/// - `out_ptr` must point to at least 4 u64s +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_mul_c")] +pub unsafe extern "C" fn secp256k1_fp_mul_c( + x_ptr: *const u64, + y_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { + let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); + let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); + + let mut params = + SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; + syscall_arith256_mod(&mut params, #[cfg(feature = "hints")] hints, ); + + *out_ptr.add(0) = params.d[0]; + *out_ptr.add(1) = params.d[1]; + *out_ptr.add(2) = params.d[2]; + *out_ptr.add(3) = params.d[3]; +} + +/// # Safety +/// - `x_ptr` must point to 4 u64s +/// - `scalar` is a single u64 value +/// - `out_ptr` must point to at least 4 u64s +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_mul_scalar_c")] +pub unsafe extern "C" fn secp256k1_fp_mul_scalar_c( + x_ptr: *const u64, + scalar: u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { + let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); + + let mut params = SyscallArith256ModParams { + a: x, + b: &[scalar, 0, 0, 0], + c: &[0, 0, 0, 0], + module: &P, + d: &mut [0, 0, 0, 0], + }; + syscall_arith256_mod(&mut params, #[cfg(feature = "hints")] hints, ); + + *out_ptr.add(0) = params.d[0]; + *out_ptr.add(1) = params.d[1]; + *out_ptr.add(2) = params.d[2]; + *out_ptr.add(3) = params.d[3]; +} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs index 78c3cdac4..d94a1fe73 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs @@ -125,3 +125,161 @@ pub fn secp256k1_fn_inv(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec< x_inv } + +// ==================== C FFI Functions ==================== + +/// # Safety +/// - `x_ptr` must point to 4 u64s +/// - `out_ptr` must point to at least 4 u64s +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_reduce_c")] +pub unsafe extern "C" fn secp256k1_fn_reduce_c(x_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { + let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); + + if lt(x, &N) { + *out_ptr.add(0) = x[0]; + *out_ptr.add(1) = x[1]; + *out_ptr.add(2) = x[2]; + *out_ptr.add(3) = x[3]; + return; + } + + let mut params = SyscallArith256ModParams { + a: x, + b: &[1, 0, 0, 0], + c: &[0, 0, 0, 0], + module: &N, + d: &mut [0, 0, 0, 0], + }; + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); + + *out_ptr.add(0) = params.d[0]; + *out_ptr.add(1) = params.d[1]; + *out_ptr.add(2) = params.d[2]; + *out_ptr.add(3) = params.d[3]; +} + +/// # Safety +/// - `x_ptr` must point to 4 u64s +/// - `y_ptr` must point to 4 u64s +/// - `out_ptr` must point to at least 4 u64s +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_add_c")] +pub unsafe extern "C" fn secp256k1_fn_add_c( + x_ptr: *const u64, + y_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { + let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); + let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); + + let mut params = + SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &N, d: &mut [0, 0, 0, 0] }; + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); + + *out_ptr.add(0) = params.d[0]; + *out_ptr.add(1) = params.d[1]; + *out_ptr.add(2) = params.d[2]; + *out_ptr.add(3) = params.d[3]; +} + +/// # Safety +/// - `x_ptr` must point to 4 u64s +/// - `out_ptr` must point to at least 4 u64s +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_neg_c")] +pub unsafe extern "C" fn secp256k1_fn_neg_c(x_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { + let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); + + let mut params = SyscallArith256ModParams { + a: x, + b: &N_MINUS_ONE, + c: &[0, 0, 0, 0], + module: &N, + d: &mut [0, 0, 0, 0], + }; + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); + + *out_ptr.add(0) = params.d[0]; + *out_ptr.add(1) = params.d[1]; + *out_ptr.add(2) = params.d[2]; + *out_ptr.add(3) = params.d[3]; +} + +/// # Safety +/// - `x_ptr` must point to 4 u64s +/// - `y_ptr` must point to 4 u64s +/// - `out_ptr` must point to at least 4 u64s +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_mul_c")] +pub unsafe extern "C" fn secp256k1_fn_mul_c( + x_ptr: *const u64, + y_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { + let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); + let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); + + let mut params = + SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &N, d: &mut [0, 0, 0, 0] }; + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); + + *out_ptr.add(0) = params.d[0]; + *out_ptr.add(1) = params.d[1]; + *out_ptr.add(2) = params.d[2]; + *out_ptr.add(3) = params.d[3]; +} + +/// # Safety +/// - `x_ptr` must point to 4 u64s (non-zero element) +/// - `out_ptr` must point to at least 4 u64s +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_inv_c")] +pub unsafe extern "C" fn secp256k1_fn_inv_c(x_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { + let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); + + // Hint the inverse + let x_inv = fcall_secp256k1_fn_inv( + x, + #[cfg(feature = "hints")] + hints, + ); + + // Check that x·x_inv = 1 (N) + let mut params = SyscallArith256ModParams { + a: x, + b: &x_inv, + c: &[0, 0, 0, 0], + module: &N, + d: &mut [0, 0, 0, 0], + }; + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); + assert_eq!(*params.d, [0x1, 0x0, 0x0, 0x0]); + + *out_ptr.add(0) = x_inv[0]; + *out_ptr.add(1) = x_inv[1]; + *out_ptr.add(2) = x_inv[2]; + *out_ptr.add(3) = x_inv[3]; +} \ No newline at end of file From f3e94c223c15b7dc9428188d1a78425394f5cc03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Tue, 3 Feb 2026 12:42:06 +0000 Subject: [PATCH 403/782] fmt --- .../src/zisklib/lib/secp256k1/curve.rs | 65 ++++++++++++++++--- .../src/zisklib/lib/secp256k1/ecdsa.rs | 9 ++- .../src/zisklib/lib/secp256k1/field.rs | 36 ++++++++-- .../src/zisklib/lib/secp256k1/scalar.rs | 20 ++++-- 4 files changed, 109 insertions(+), 21 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index bfde12550..5d6d8f225 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -842,20 +842,46 @@ pub fn secp256k1_triple_scalar_mul_with_g( // ==================== C FFI Functions ==================== /// Converts a non-infinity point `p` on the Secp256k1 curve from jacobian coordinates to affine coordinates -pub fn secp256k1_to_affine(p: &[u64; 12], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 8] { +pub fn secp256k1_to_affine( + p: &[u64; 12], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 8] { let z: [u64; 4] = [p[8], p[9], p[10], p[11]]; // Point at infinity cannot be converted to affine debug_assert!(z != ZERO_256, "Cannot convert point at infinity to affine"); - let zinv = secp256k1_fp_inv(&z, #[cfg(feature = "hints")] hints); - let zinv_sq = secp256k1_fp_square(&zinv, #[cfg(feature = "hints")] hints); + let zinv = secp256k1_fp_inv( + &z, + #[cfg(feature = "hints")] + hints, + ); + let zinv_sq = secp256k1_fp_square( + &zinv, + #[cfg(feature = "hints")] + hints, + ); let x: [u64; 4] = [p[0], p[1], p[2], p[3]]; let y: [u64; 4] = [p[4], p[5], p[6], p[7]]; - let x_res = secp256k1_fp_mul(&x, &zinv_sq, #[cfg(feature = "hints")] hints); - let y_res = secp256k1_fp_mul(&secp256k1_fp_mul(&y, &zinv_sq, #[cfg(feature = "hints")] hints), &zinv, #[cfg(feature = "hints")] hints); + let x_res = secp256k1_fp_mul( + &x, + &zinv_sq, + #[cfg(feature = "hints")] + hints, + ); + let y_res = secp256k1_fp_mul( + &secp256k1_fp_mul( + &y, + &zinv_sq, + #[cfg(feature = "hints")] + hints, + ), + &zinv, + #[cfg(feature = "hints")] + hints, + ); [x_res[0], x_res[1], x_res[2], x_res[3], y_res[0], y_res[1], y_res[2], y_res[3]] } @@ -877,7 +903,12 @@ pub unsafe extern "C" fn secp256k1_decompress_c( let x_bytes: &[u8; 32] = &*(x_bytes_ptr as *const [u8; 32]); let x = bytes_be_to_u64_le(x_bytes); - let (x, y) = match secp256k1_decompress(&x, y_is_odd != 0, #[cfg(feature = "hints")] hints) { + let (x, y) = match secp256k1_decompress( + &x, + y_is_odd != 0, + #[cfg(feature = "hints")] + hints, + ) { Ok((x, y)) => (x, y), Err(_) => return 0, }; @@ -899,9 +930,17 @@ pub unsafe extern "C" fn secp256k1_decompress_c( /// - `out_ptr` must point to at least 8 u64s #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_secp256k1_to_affine_c")] -pub unsafe extern "C" fn secp256k1_to_affine_c(p_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { +pub unsafe extern "C" fn secp256k1_to_affine_c( + p_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let p: &[u64; 12] = &*(p_ptr as *const [u64; 12]); - let result = secp256k1_to_affine(p, #[cfg(feature = "hints")] hints); + let result = secp256k1_to_affine( + p, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = result[0]; *out_ptr.add(1) = result[1]; @@ -932,7 +971,13 @@ pub unsafe extern "C" fn secp256k1_double_scalar_mul_with_g_c( let k2: &[u64; 4] = &*(k2_ptr as *const [u64; 4]); let p: &[u64; 8] = &*(p_ptr as *const [u64; 8]); - match secp256k1_double_scalar_mul_with_g(k1, k2, p, #[cfg(feature = "hints")] hints) { + match secp256k1_double_scalar_mul_with_g( + k1, + k2, + p, + #[cfg(feature = "hints")] + hints, + ) { None => true, Some(res) => { *out_ptr.add(0) = res[0]; @@ -966,4 +1011,4 @@ fn bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { ]); } result -} \ No newline at end of file +} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs index 52fc248cf..720e5f3a8 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs @@ -93,5 +93,12 @@ pub unsafe extern "C" fn secp256k1_ecdsa_verify_c( let z: &[u64; 4] = &*(z_ptr as *const [u64; 4]); let r: &[u64; 4] = &*(r_ptr as *const [u64; 4]); let s: &[u64; 4] = &*(s_ptr as *const [u64; 4]); - secp256k1_ecdsa_verify(pk, z, r, s, #[cfg(feature = "hints")] hints) + secp256k1_ecdsa_verify( + pk, + z, + r, + s, + #[cfg(feature = "hints")] + hints, + ) } diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs index 7d6dcee68..5b7736c7f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs @@ -199,7 +199,11 @@ pub fn secp256k1_fp_sqrt( /// - `out_ptr` must point to at least 4 u64s #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_reduce_c")] -pub unsafe extern "C" fn secp256k1_fp_reduce_c(x_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { +pub unsafe extern "C" fn secp256k1_fp_reduce_c( + x_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); if lt(x, &P) { @@ -246,7 +250,11 @@ pub unsafe extern "C" fn secp256k1_fp_add_c( let mut params = SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params, #[cfg(feature = "hints")] hints, ); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -259,7 +267,11 @@ pub unsafe extern "C" fn secp256k1_fp_add_c( /// - `out_ptr` must point to at least 4 u64s #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_negate_c")] -pub unsafe extern "C" fn secp256k1_fp_negate_c(x_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { +pub unsafe extern "C" fn secp256k1_fp_negate_c( + x_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); let mut params = SyscallArith256ModParams { @@ -269,7 +281,11 @@ pub unsafe extern "C" fn secp256k1_fp_negate_c(x_ptr: *const u64, out_ptr: *mut module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params, #[cfg(feature = "hints")] hints, ); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -294,7 +310,11 @@ pub unsafe extern "C" fn secp256k1_fp_mul_c( let mut params = SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod(&mut params, #[cfg(feature = "hints")] hints, ); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; @@ -323,7 +343,11 @@ pub unsafe extern "C" fn secp256k1_fp_mul_scalar_c( module: &P, d: &mut [0, 0, 0, 0], }; - syscall_arith256_mod(&mut params, #[cfg(feature = "hints")] hints, ); + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); *out_ptr.add(0) = params.d[0]; *out_ptr.add(1) = params.d[1]; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs index d94a1fe73..4e8023dd3 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs @@ -133,7 +133,11 @@ pub fn secp256k1_fn_inv(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec< /// - `out_ptr` must point to at least 4 u64s #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_reduce_c")] -pub unsafe extern "C" fn secp256k1_fn_reduce_c(x_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { +pub unsafe extern "C" fn secp256k1_fn_reduce_c( + x_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); if lt(x, &N) { @@ -197,7 +201,11 @@ pub unsafe extern "C" fn secp256k1_fn_add_c( /// - `out_ptr` must point to at least 4 u64s #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_neg_c")] -pub unsafe extern "C" fn secp256k1_fn_neg_c(x_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { +pub unsafe extern "C" fn secp256k1_fn_neg_c( + x_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); let mut params = SyscallArith256ModParams { @@ -253,7 +261,11 @@ pub unsafe extern "C" fn secp256k1_fn_mul_c( /// - `out_ptr` must point to at least 4 u64s #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_inv_c")] -pub unsafe extern "C" fn secp256k1_fn_inv_c(x_ptr: *const u64, out_ptr: *mut u64, #[cfg(feature = "hints")] hints: &mut Vec) { +pub unsafe extern "C" fn secp256k1_fn_inv_c( + x_ptr: *const u64, + out_ptr: *mut u64, + #[cfg(feature = "hints")] hints: &mut Vec, +) { let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); // Hint the inverse @@ -282,4 +294,4 @@ pub unsafe extern "C" fn secp256k1_fn_inv_c(x_ptr: *const u64, out_ptr: *mut u64 *out_ptr.add(1) = x_inv[1]; *out_ptr.add(2) = x_inv[2]; *out_ptr.add(3) = x_inv[3]; -} \ No newline at end of file +} From e752d653d3d854e003ba9ff54efda3bb29202bb0 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Tue, 3 Feb 2026 23:10:46 +0100 Subject: [PATCH 404/782] fix bug on precompile alignment control --- core/src/zisk_ops.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index d44887723..715c0339c 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -1500,15 +1500,20 @@ fn internal_precompiled_load_data( ) { let address = ctx.b; if address & 0x7 != 0 { - panic!("precompiled_check_address() found address not aligned to 8 bytes"); + panic!( + "[{title}] precompiled_check_address() found address 0x{address:08X} not aligned \ + to 8 bytes at PC:0x{:08X} STEP:{}", + ctx.pc, ctx.step + ); } if let EmulationMode::ConsumeMemReads = ctx.emulation_mode { // Check input data has the expected length let expected_len = params_count + load_indirections * load_chunks + load_rem + result; if ctx.precompiled.input_data.len() != expected_len { panic!( - "[{title}] ctx.precompiled.input_data.len={} != {expected_len} [{params_count}+{load_indirections}*{load_chunks}+{load_rem}+{result}]", - ctx.precompiled.input_data.len(), + "[{title}] ctx.precompiled.input_data.len={} != {expected_len} \ + [{params_count}+{load_indirections}*{load_chunks}+{load_rem}+{result}] at PC:0x{:08X} STEP:{}", + ctx.precompiled.input_data.len(), ctx.pc, ctx.step, ); } // Read data from the precompiled context @@ -1523,8 +1528,12 @@ fn internal_precompiled_load_data( // Write the indirections to data for (i, data) in data.iter_mut().enumerate().take(params_count) { let indirection = ctx.mem.read(address + (8 * i as u64), 8); - if address & 0x7 != 0 { - panic!("precompiled_check_address() found address[{i}] not aligned to 8 bytes"); + if indirection & 0x7 != 0 { + panic!( + "[{title}] precompiled_check_address() found address_{i} [0x{address:08X}]=0x{indirection:08X} \ + not aligned to 8 bytes at PC:0x{:08X} STEP:{}", + ctx.pc, ctx.step + ); } *data = indirection; } From e1bf882a001f345efeba404db7dee88e8fa92137 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 4 Feb 2026 08:49:16 +0100 Subject: [PATCH 405/782] fix an merge error with precompiles csr --- core/src/riscv2zisk_context.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 407341eb9..f44d55c43 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -33,9 +33,9 @@ const CSR_PRECOMPILED: [&str; 21] = [ "bls12_381_complex_sub", "bls12_381_complex_mul", "add256", + "poseidon2", "dma_memcpy", "dma_memcmp", - "poseidon2", ]; const CSR_PRECOMPILED_ADDR_START: u32 = 0x800; const CSR_PRECOMPILED_ADDR_END: u32 = CSR_PRECOMPILED_ADDR_START + CSR_PRECOMPILED.len() as u32; @@ -1202,7 +1202,7 @@ impl Riscv2ZiskContext<'_> { let mut zib = ZiskInstBuilder::new_from_riscv(rom_address, i.inst.clone()); zib.src_b("reg", i.rs1 as u64, false); zib.j(4, 4); - if (CSR_PRECOMPILED_ADDR_START..=CSR_PRECOMPILED_ADDR_END).contains(&i.csr) { + if (CSR_PRECOMPILED_ADDR_START..CSR_PRECOMPILED_ADDR_END).contains(&i.csr) { match i.csr { 0x813 | 0x814 => { self.output_precompile = Some(i.csr); From af780f34357c13d9bb007b91ac235ad6a5c8821a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:37:59 +0000 Subject: [PATCH 406/782] Removing usuned lib with alignment issues --- .../entrypoint/src/zisklib/lib/bigint256.rs | 480 ------------------ ziskos/entrypoint/src/zisklib/lib/mod.rs | 2 - 2 files changed, 482 deletions(-) delete mode 100644 ziskos/entrypoint/src/zisklib/lib/bigint256.rs diff --git a/ziskos/entrypoint/src/zisklib/lib/bigint256.rs b/ziskos/entrypoint/src/zisklib/lib/bigint256.rs deleted file mode 100644 index 2b54e2793..000000000 --- a/ziskos/entrypoint/src/zisklib/lib/bigint256.rs +++ /dev/null @@ -1,480 +0,0 @@ -use crate::{ - syscalls::{ - syscall_arith256, syscall_arith256_mod, SyscallArith256ModParams, SyscallArith256Params, - }, - zisklib::{eq, fcall_bigint256_div, fcall_msb_pos_256, lt}, -}; - -pub fn mul256( - a: &[u64; 4], - b: &[u64; 4], - #[cfg(feature = "hints")] hints: &mut Vec, -) -> ([u64; 4], [u64; 4]) { - let mut params = - SyscallArith256Params { a, b, c: &[0u64; 4], dl: &mut [0u64; 4], dh: &mut [0u64; 4] }; - syscall_arith256( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - (*params.dl, *params.dh) -} - -pub fn wmul256( - a: &[u64; 4], - b: &[u64; 4], - #[cfg(feature = "hints")] hints: &mut Vec, -) -> [u64; 4] { - let mut params = - SyscallArith256Params { a, b, c: &[0u64; 4], dl: &mut [0u64; 4], dh: &mut [0u64; 4] }; - syscall_arith256( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - *params.dl -} - -pub fn divrem256( - a: &[u64; 4], - b: &[u64; 4], - #[cfg(feature = "hints")] hints: &mut Vec, -) -> ([u64; 4], [u64; 4]) { - // Check for division by zero - assert!(!eq(b, &[0u64; 4]), "Division by zero"); - - // Hint the result of the division - let (quotient, remainder) = fcall_bigint256_div( - a, - b, - #[cfg(feature = "hints")] - hints, - ); - - // Check that a = b * quotient + remainder and remainder < b - assert!(lt(&remainder, b), "Remainder is not less than divisor"); - let mut params = SyscallArith256Params { - a: b, - b: "ient, - c: &remainder, - dl: &mut [0u64; 4], - dh: &mut [0u64; 4], - }; - syscall_arith256( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - assert!(eq(params.dl, a), "Dividend does not equal divisor * quotient + remainder"); - - (quotient, remainder) -} - -/// Raises `x` to (2^power_log) modulo `module` using repeated squaring -pub fn exp_power_of_two( - x: &[u64; 4], - module: &[u64; 4], - power_log: usize, - #[cfg(feature = "hints")] hints: &mut Vec, -) -> [u64; 4] { - // x^1 = x - if power_log == 0 { - return *x; - } - - let mut result = *x; - let zero = [0u64; 4]; - for _ in 0..power_log { - let mut params = SyscallArith256ModParams { - a: &result, - b: &result, - c: &zero, - module, - d: &mut [0u64; 4], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - result = *params.d; - } - - result -} - -/// Raises `x` to (2^power_log) modulo `module` using repeated squaring -pub fn exp_power_of_two_self( - x: &mut [u64; 4], - module: &[u64; 4], - power_log: usize, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - if power_log == 0 { - return; - } - - let zero = [0u64; 4]; - for _ in 0..power_log { - let mut params = - SyscallArith256ModParams { a: x, b: x, c: &zero, module, d: &mut [0u64; 4] }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - *x = *params.d; - } -} - -pub fn wpow256( - a: &[u64; 4], - exp: &[u64; 4], - #[cfg(feature = "hints")] hints: &mut Vec, -) -> [u64; 4] { - // 0^0 = 1 by convention - // 0^n = 0 for n > 0 - if eq(a, &[0u64; 4]) { - return if eq(exp, &[0u64; 4]) { [1, 0, 0, 0] } else { [0u64; 4] }; - } - - // Direct cases: exp = 0,1,2 - match exp { - [0, 0, 0, 0] => { - // Return a^0 = 1 - return [1, 0, 0, 0]; - } - [1, 0, 0, 0] => { - // Return a - return *a; - } - [2, 0, 0, 0] => { - // Return a^2 - let mut dl = [0u64; 4]; - let mut dh = [0u64; 4]; - let mut params = - SyscallArith256Params { a, b: a, c: &[0u64; 4], dl: &mut dl, dh: &mut dh }; - syscall_arith256( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - return dl; - } - _ => {} - } - - // We can assume exp > 2 from now on - // Hint the length the binary representations of exp - // We will verify the output by recomposing exp - let (max_limb, max_bit) = fcall_msb_pos_256( - exp, - &[0, 0, 0, 0], - #[cfg(feature = "hints")] - hints, - ); - - // Perform the loop, based on the binary representation of exp - - // We do the first iteration separately - let _max_limb = max_limb as usize; - let exp_bit = (exp[_max_limb] >> max_bit) & 1; - assert_eq!(exp_bit, 1); // the first received bit should be 1 - - // Start at a - let mut result = *a; - let mut exp_rec = [0, 0, 0, 0]; - exp_rec[_max_limb] = 1 << max_bit; - - // Perform the rest of the loop - let _max_bit = max_bit as usize; - let mut dl = [0u64; 4]; - let mut dh = [0u64; 4]; - for i in (0..=_max_limb).rev() { - let bit_len = if i == _max_limb { _max_bit - 1 } else { 63 }; - for j in (0..=bit_len).rev() { - // Always square - let mut params = SyscallArith256Params { - a: &result, - b: &result, - c: &[0u64; 4], - dl: &mut dl, - dh: &mut dh, - }; - syscall_arith256( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - result = dl; - - // Get the next bit b of exp - // If b == 1, we multiply result by a, otherwise start the next iteration - if ((exp[i] >> j) & 1) == 1 { - let mut params = SyscallArith256Params { - a: &result, - b: a, - c: &[0u64; 4], - dl: &mut dl, - dh: &mut dh, - }; - syscall_arith256( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - result = dl; - - // Reconstruct exp - exp_rec[i] |= 1 << j; - } - } - } - - // Check that the reconstructed exp is equal to the input exp - assert_eq!(exp_rec, *exp); - - result -} - -/// Modular reduction of a 256-bit integer -/// -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes). -/// - `m` must point to a valid `[u64; 4]` (32 bytes). -/// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_redmod256_c")] -pub unsafe extern "C" fn redmod256_c( - a: *const u64, - m: *const u64, - result: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let mut d = [0u64; 4]; - let mut params = SyscallArith256ModParams { - a: &*(a as *const [u64; 4]), - b: &[1, 0, 0, 0], - c: &[0u64; 4], - module: &*(m as *const [u64; 4]), - d: &mut d, - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - core::ptr::copy_nonoverlapping(d.as_ptr(), result, 4); -} - -/// Modular addition of 256-bit integers -/// -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes). -/// - `b` must point to a valid `[u64; 4]` (32 bytes). -/// - `m` must point to a valid `[u64; 4]` (32 bytes). -/// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_addmod256_c")] -pub unsafe extern "C" fn addmod256_c( - a: *const u64, - b: *const u64, - m: *const u64, - result: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let mut d = [0u64; 4]; - let mut params = SyscallArith256ModParams { - a: &*(a as *const [u64; 4]), - b: &[1, 0, 0, 0], - c: &*(b as *const [u64; 4]), - module: &*(m as *const [u64; 4]), - d: &mut d, - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - core::ptr::copy_nonoverlapping(d.as_ptr(), result, 4); -} - -/// Modular multiplication of 256-bit integers -/// -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes). -/// - `b` must point to a valid `[u64; 4]` (32 bytes). -/// - `m` must point to a valid `[u64; 4]` (32 bytes). -/// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_mulmod256_c")] -pub unsafe extern "C" fn mulmod256_c( - a: *const u64, - b: *const u64, - m: *const u64, - result: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let mut d = [0u64; 4]; - let mut params = SyscallArith256ModParams { - a: &*(a as *const [u64; 4]), - b: &*(b as *const [u64; 4]), - c: &[0u64; 4], - module: &*(m as *const [u64; 4]), - d: &mut d, - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - core::ptr::copy_nonoverlapping(d.as_ptr(), result, 4); -} - -/// Wrapping multiplication of 256-bit integers -/// -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes). -/// - `b` must point to a valid `[u64; 4]` (32 bytes). -/// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_wmul256_c")] -pub unsafe extern "C" fn wmul256_c( - a: *const u64, - b: *const u64, - result: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let mut dl = [0u64; 4]; - let mut dh = [0u64; 4]; - let mut params = SyscallArith256Params { - a: &*(a as *const [u64; 4]), - b: &*(b as *const [u64; 4]), - c: &[0u64; 4], - dl: &mut dl, - dh: &mut dh, - }; - syscall_arith256( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - core::ptr::copy_nonoverlapping(dl.as_ptr(), result, 4); -} - -/// Overflowing multiplication of 256-bit integers -/// -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes). -/// - `b` must point to a valid `[u64; 4]` (32 bytes). -/// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. -/// -/// Returns `true` if overflow occurred, `false` otherwise. -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_omul256_c")] -pub unsafe extern "C" fn omul256_c( - a: *const u64, - b: *const u64, - result: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) -> bool { - let mut dl = [0u64; 4]; - let mut dh = [0u64; 4]; - let mut params = SyscallArith256Params { - a: &*(a as *const [u64; 4]), - b: &*(b as *const [u64; 4]), - c: &[0u64; 4], - dl: &mut dl, - dh: &mut dh, - }; - syscall_arith256( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - core::ptr::copy_nonoverlapping(dl.as_ptr(), result, 4); - - // If the high part is non-zero, we have an overflow - !eq(&dh, &[0u64; 4]) -} - -/// Division and remainder of 256-bit integers -/// -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes). -/// - `b` must point to a valid `[u64; 4]` (32 bytes), and must be non-zero. -/// - `q` must point to a valid `[u64; 4]` (32 bytes), used as quotient output. -/// - `r` must point to a valid `[u64; 4]` (32 bytes), used as remainder output. -/// -/// # Panics -/// Panics if `b` is zero. -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_divrem256_c")] -pub unsafe extern "C" fn divrem256_c( - a: *const u64, - b: *const u64, - q: *mut u64, - r: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let a_ref = &*(a as *const [u64; 4]); - let b_ref = &*(b as *const [u64; 4]); - - // Check for division by zero - assert!(!eq(b_ref, &[0u64; 4]), "Division by zero"); - - // Hint the result of the division - let (quotient, remainder) = fcall_bigint256_div( - a_ref, - b_ref, - #[cfg(feature = "hints")] - hints, - ); - - // Check that a = b * quotient + remainder and remainder < b - let mut dl = [0u64; 4]; - let mut dh = [0u64; 4]; - let mut params = - SyscallArith256Params { a: b_ref, b: "ient, c: &remainder, dl: &mut dl, dh: &mut dh }; - syscall_arith256( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - assert!(eq(&dl, a_ref), "Dividend does not equal divisor * quotient + remainder"); - assert!(lt(&remainder, b_ref), "Remainder is not less than divisor"); - - core::ptr::copy_nonoverlapping(quotient.as_ptr(), q, 4); - core::ptr::copy_nonoverlapping(remainder.as_ptr(), r, 4); -} - -/// Wrapping exponentiation of 256-bit integers -/// -/// # Safety -/// - `a` must point to a valid `[u64; 4]` (32 bytes). -/// - `exp` must point to a valid `[u64; 4]` (32 bytes). -/// - `result` must point to a valid `[u64; 4]` (32 bytes), used as output. -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_wpow256_c")] -pub unsafe extern "C" fn wpow256_c( - a: *const u64, - exp: *const u64, - result: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let a_ref = &*(a as *const [u64; 4]); - let exp_ref = &*(exp as *const [u64; 4]); - - let res = wpow256( - a_ref, - exp_ref, - #[cfg(feature = "hints")] - hints, - ); - core::ptr::copy_nonoverlapping(res.as_ptr(), result, 4); -} diff --git a/ziskos/entrypoint/src/zisklib/lib/mod.rs b/ziskos/entrypoint/src/zisklib/lib/mod.rs index 21ac59380..450c9dc28 100644 --- a/ziskos/entrypoint/src/zisklib/lib/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/mod.rs @@ -1,5 +1,4 @@ mod array_lib; -mod bigint256; mod bls12_381; mod bn254; mod constants; @@ -10,7 +9,6 @@ mod utils; // For public consumption pub use array_lib::*; -pub use bigint256::*; pub use bls12_381::*; pub use bn254::*; pub use constants::*; From 9cf835f53357200f228295f7b705e23fdfb43ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:38:38 +0000 Subject: [PATCH 407/782] Fixing alignment problem with sha --- .../entrypoint/src/zisklib/lib/keccak256.rs | 8 +- ziskos/entrypoint/src/zisklib/lib/sha256.rs | 79 ++++++++++++------- ziskos/entrypoint/src/zisklib/lib/utils.rs | 6 ++ 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index 739941c5b..6117ad0f9 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -3,13 +3,7 @@ use crate::syscalls::syscall_keccak_f; /// Keccak-256 rate in bytes (1600 - 2*256) / 8 = 136 bytes const KECCAK256_RATE: usize = 136; -/// Computes the Keccak-256 hash of the input data. -/// -/// This implements the Keccak sponge construction with: -/// - Rate: 1088 bits (136 bytes) -/// - Capacity: 512 bits (64 bytes) -/// - Output: 256 bits (32 bytes) -/// - Padding: Keccak padding (0x01...0x80) +/// Keccak-256 hash function. For reference: https://keccak.team/keccak_specs_summary.html pub fn keccak256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u8; 32] { let mut state = [0u64; 25]; let input_len = input.len(); diff --git a/ziskos/entrypoint/src/zisklib/lib/sha256.rs b/ziskos/entrypoint/src/zisklib/lib/sha256.rs index 7571f2b6c..925d29cba 100644 --- a/ziskos/entrypoint/src/zisklib/lib/sha256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/sha256.rs @@ -1,42 +1,44 @@ use crate::syscalls::{syscall_sha256_f, SyscallSha256Params}; +use super::is_aligned_8; + /// SHA-256 initial hash values const SHA256_INIT: [u32; 8] = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, ]; -/// Compress a single 64-byte block into the state -#[inline] -fn compress_block( - state: &mut [u32; 8], - block: &[u8; 64], - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let state_64: &mut [u64; 4] = unsafe { &mut *(state.as_mut_ptr() as *mut [u64; 4]) }; - let input_u64: &[u64; 8] = unsafe { &*(block.as_ptr() as *const [u64; 8]) }; - let mut sha256_params = SyscallSha256Params { state: state_64, input: input_u64 }; - syscall_sha256_f( - &mut sha256_params, - #[cfg(feature = "hints")] - hints, - ); -} - +/// SHA-256 hash function. For reference: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf pub fn sha256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u8; 32] { let mut state = SHA256_INIT; let input_len = input.len(); // Process complete 64-byte blocks let mut offset = 0; - while offset + 64 <= input_len { - let block: &[u8; 64] = input[offset..offset + 64].try_into().unwrap(); - compress_block( - &mut state, - block, - #[cfg(feature = "hints")] - hints, - ); - offset += 64; + if is_aligned_8(input.as_ptr()) { + // Fast path: input is aligned, use directly + while offset + 64 <= input_len { + let block: &[u8; 64] = input[offset..offset + 64].try_into().unwrap(); + compress_block( + &mut state, + block, + #[cfg(feature = "hints")] + hints, + ); + offset += 64; + } + } else { + // Slow path: input is unaligned, copy each block + let mut aligned_block = [0u8; 64]; + while offset + 64 <= input_len { + aligned_block.copy_from_slice(&input[offset..offset + 64]); + compress_block( + &mut state, + &aligned_block, + #[cfg(feature = "hints")] + hints, + ); + offset += 64; + } } // Handle final block(s) with padding @@ -44,7 +46,6 @@ pub fn sha256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [ let bit_len = (input_len as u64) * 8; // We need: remaining bytes + 1 (0x80) + padding + 8 (length) - // If remaining + 9 > 64, we need 2 blocks let mut final_block = [0u8; 64]; // Copy remaining bytes @@ -53,8 +54,9 @@ pub fn sha256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [ // Append 0x80 final_block[remaining] = 0x80; + // If remaining + 9 > 64, we need 2 blocks if remaining + 9 > 64 { - // Need two blocks: process first block, then second with length + // First block compress_block( &mut state, &final_block, @@ -62,7 +64,7 @@ pub fn sha256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [ hints, ); - // Second block: all zeros except length at the end + // Second block final_block = [0u8; 64]; final_block[56..64].copy_from_slice(&bit_len.to_be_bytes()); compress_block( @@ -72,7 +74,7 @@ pub fn sha256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [ hints, ); } else { - // Single block: append length at the end + // Single final block final_block[56..64].copy_from_slice(&bit_len.to_be_bytes()); compress_block( &mut state, @@ -91,6 +93,23 @@ pub fn sha256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [ result } +/// Compress a single 64-byte block into the state +#[inline] +fn compress_block( + state: &mut [u32; 8], + block: &[u8; 64], + #[cfg(feature = "hints")] hints: &mut Vec, +) { + let state_64: &mut [u64; 4] = unsafe { &mut *(state.as_mut_ptr() as *mut [u64; 4]) }; + let input_u64: &[u64; 8] = unsafe { &*(block.as_ptr() as *const [u64; 8]) }; + let mut sha256_params = SyscallSha256Params { state: state_64, input: input_u64 }; + syscall_sha256_f( + &mut sha256_params, + #[cfg(feature = "hints")] + hints, + ); +} + /// C-compatible wrapper for full SHA-256 hash /// /// # Safety diff --git a/ziskos/entrypoint/src/zisklib/lib/utils.rs b/ziskos/entrypoint/src/zisklib/lib/utils.rs index 759ca505d..b16a7cd13 100644 --- a/ziskos/entrypoint/src/zisklib/lib/utils.rs +++ b/ziskos/entrypoint/src/zisklib/lib/utils.rs @@ -1,3 +1,9 @@ +/// Check if a pointer is 8-byte aligned. +#[inline(always)] +pub fn is_aligned_8(ptr: *const u8) -> bool { + (ptr as usize) & 0x7 == 0 +} + /// Given two n-bit number `x` and `y`, compares them and returns true if `x > y`; otherwise, false. pub fn gt(x: &[u64], y: &[u64]) -> bool { debug_assert_eq!(x.len(), y.len(), "x and y must have the same length"); From 3a3073774bf9c89775be6e0600bdf21fdf352f13 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 4 Feb 2026 12:56:48 +0100 Subject: [PATCH 408/782] fix error on gen_dma_mem_inputs and fix a minor bug on memories debug. --- precompiles/dma/src/dma_gen_mem_inputs.rs | 89 +++++++++++++++-------- state-machines/mem/src/mem_sm.rs | 2 +- 2 files changed, 58 insertions(+), 33 deletions(-) diff --git a/precompiles/dma/src/dma_gen_mem_inputs.rs b/precompiles/dma/src/dma_gen_mem_inputs.rs index 35a62564e..84a0b5f4a 100644 --- a/precompiles/dma/src/dma_gen_mem_inputs.rs +++ b/precompiles/dma/src/dma_gen_mem_inputs.rs @@ -22,6 +22,12 @@ pub fn generate_dma_mem_inputs( let src_offset = src & 0x07; let aligned = dst_offset == src_offset; + let post_count = DmaInfo::get_post_count(encoded) as u64; + let loop_count = DmaInfo::get_loop_count(encoded); + + let loop_dst64 = (dst as u32 + pre_count as u32) & !0x07; + let post_dst64 = dst as u32 + pre_count as u32 + loop_count as u32 * 8; + // NOTE: for dual memories it's very important to keep the order of loads and stores because // stores happend after loads. @@ -32,7 +38,7 @@ pub fn generate_dma_mem_inputs( mem_processors, ); - if pre_count > 0 { + let pre_write_value = if pre_count > 0 { let pre_data_offset = DmaInfo::get_pre_data_offset(encoded); let read_value = data_ext[pre_data_offset]; @@ -78,18 +84,13 @@ pub fn generate_dma_mem_inputs( &[read_value], ) }; - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_write@pre 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); - - MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, mem_processors); - } + write_value + } else { + 0 + }; - // this is part of words loop - let post_count = DmaInfo::get_post_count(encoded) as u64; - let loop_count = DmaInfo::get_loop_count(encoded); - if loop_count > 0 { + let loop_values = if loop_count > 0 { let loop_src = src as u32 + pre_count as u32; - let dst64 = (dst as u32 + pre_count as u32) & !0x07; let src64 = loop_src & !0x07; let loop_data_offset = DmaInfo::get_loop_data_offset(encoded); let loop_data_count = DmaInfo::get_loop_count(encoded); @@ -104,30 +105,16 @@ pub fn generate_dma_mem_inputs( println!("DMA: mem_aligned_load_from_slice 0x{src64:08X} S:{main_step} V:{values:?}"); MemBusHelpers::mem_aligned_load_from_slice(src64, main_step, values, mem_processors); + values + } else { + &[] + }; - let src_offset = (src_offset + pre_count) & 0x07; - if aligned { - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_write_from_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); - MemBusHelpers::mem_aligned_write_from_slice(dst64, main_step, values, mem_processors); - } else { - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_write_from_read_unaligned_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); - MemBusHelpers::mem_aligned_write_from_read_unaligned_slice( - dst64, - main_step, - src_offset as u8, - values, - mem_processors, - ); - } - } - if post_count > 0 { + let post_write_value = if post_count > 0 { let src_offset = src & 0x07; let post_data_offset = DmaInfo::get_post_data_offset(encoded); let src64 = (src as u32 + pre_count as u32 + loop_count as u32 * 8) & !0x07; - let dst64 = dst as u32 + pre_count as u32 + loop_count as u32 * 8; let read_value = data_ext[post_data_offset]; #[cfg(feature = "debug_dma")] @@ -141,7 +128,7 @@ pub fn generate_dma_mem_inputs( #[cfg(feature = "debug_dma")] println!("DMA: mem_aligned_load@post-p 0x{dst64:08X} S:{main_step} V:{pre_value} (0x{pre_value:016X})"); - MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, mem_processors); + MemBusHelpers::mem_aligned_load(post_dst64, main_step, pre_value, mem_processors); let write_value = if DmaInfo::is_double_read_post(encoded) { let second_read_value = data_ext[post_data_offset + 1]; @@ -172,10 +159,48 @@ pub fn generate_dma_mem_inputs( &[read_value], ) }; + write_value + } else { + 0 + }; + + // IMPORTANT: + // Separate reads and writes, because writes happend after the reads on t+1. It's important because dual memories + // asume that inputs arrives ordered by time to decide if compact (dual) or not. + + if pre_count > 0 { + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write@pre 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); + MemBusHelpers::mem_aligned_write(dst64, main_step, pre_write_value, mem_processors); + } + if loop_count > 0 { + let src_offset = (src_offset + pre_count) & 0x07; + if aligned { + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write_from_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); + MemBusHelpers::mem_aligned_write_from_slice( + loop_dst64, + main_step, + loop_values, + mem_processors, + ); + } else { + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write_from_read_unaligned_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); + MemBusHelpers::mem_aligned_write_from_read_unaligned_slice( + loop_dst64, + main_step, + src_offset as u8, + loop_values, + mem_processors, + ); + } + } + if post_count > 0 { #[cfg(feature = "debug_dma")] println!("DMA: mem_aligned_write@post 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); - MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, mem_processors); + MemBusHelpers::mem_aligned_write(post_dst64, main_step, post_write_value, mem_processors); } } diff --git a/state-machines/mem/src/mem_sm.rs b/state-machines/mem/src/mem_sm.rs index fed056328..c1392668c 100644 --- a/state-machines/mem/src/mem_sm.rs +++ b/state-machines/mem/src/mem_sm.rs @@ -220,7 +220,7 @@ impl MemModule for MemSM { trace[i].set_wr(mem_op.is_write); #[cfg(feature = "debug_mem")] - if (lsb_increment >= MEM_INC_C_SIZE) || (msb_increment > MEM_INC_C_SIZE) { + if (l_increment >= (1 << 22)) || (h_increment >= (1 << 16)) { panic!("MemSM: increment's out of range: {} i:{} addr_changes:{} mem_op.addr:0x{:X} last_addr:0x{:X} mem_op.step:{} last_step:{}", increment, i, addr_changes as u8, mem_op.addr, last_addr, mem_op.step, last_step); } From 4d96e7d106bc5365c9bfac536f33d432d9c8b9cf Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 4 Feb 2026 13:10:14 +0100 Subject: [PATCH 409/782] fix clippy errors --- precompiles/dma/src/dma_gen_mem_inputs.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/precompiles/dma/src/dma_gen_mem_inputs.rs b/precompiles/dma/src/dma_gen_mem_inputs.rs index 84a0b5f4a..30a63a6db 100644 --- a/precompiles/dma/src/dma_gen_mem_inputs.rs +++ b/precompiles/dma/src/dma_gen_mem_inputs.rs @@ -55,7 +55,7 @@ pub fn generate_dma_mem_inputs( MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, mem_processors); - let write_value = if DmaInfo::is_double_read_pre(encoded) { + if DmaInfo::is_double_read_pre(encoded) { let second_read_value = data_ext[pre_data_offset + 1]; #[cfg(feature = "debug_dma")] println!( @@ -83,8 +83,7 @@ pub fn generate_dma_mem_inputs( pre_value, &[read_value], ) - }; - write_value + } } else { 0 }; @@ -130,7 +129,7 @@ pub fn generate_dma_mem_inputs( MemBusHelpers::mem_aligned_load(post_dst64, main_step, pre_value, mem_processors); - let write_value = if DmaInfo::is_double_read_post(encoded) { + if DmaInfo::is_double_read_post(encoded) { let second_read_value = data_ext[post_data_offset + 1]; #[cfg(feature = "debug_dma")] println!( @@ -158,8 +157,7 @@ pub fn generate_dma_mem_inputs( pre_value, &[read_value], ) - }; - write_value + } } else { 0 }; From e78c651bd381e6483d854a437b9bd2f25c43f1df Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 4 Feb 2026 14:27:51 +0000 Subject: [PATCH 410/782] Add secp256r1 hint. Refactor hint buffer --- Cargo.lock | 5 +- core/Cargo.toml | 2 +- core/src/zisk_ops.rs | 4 +- ziskos-hints/Cargo.toml | 9 +- ziskos/entrypoint/Cargo.toml | 23 +- ziskos/entrypoint/src/hints/hint_buffer.rs | 196 ++++++++---------- ziskos/entrypoint/src/hints/macros.rs | 8 + ziskos/entrypoint/src/hints/mod.rs | 132 +++++++++--- ziskos/entrypoint/src/hints/secp256r1.rs | 11 + ziskos/entrypoint/src/lib.rs | 6 +- .../entrypoint/src/zisklib/lib/keccak256.rs | 43 +++- 11 files changed, 274 insertions(+), 165 deletions(-) create mode 100644 ziskos/entrypoint/src/hints/secp256r1.rs diff --git a/Cargo.lock b/Cargo.lock index 53bdc5a42..bc1913c08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6117,7 +6117,7 @@ dependencies = [ "sha2", "tiny-keccak", "zisk-pil", - "ziskos", + "ziskos-hints", ] [[package]] @@ -6344,7 +6344,9 @@ dependencies = [ name = "ziskos" version = "0.16.0" dependencies = [ + "anyhow", "bincode", + "bytes", "cfg-if", "ctor", "fields", @@ -6362,6 +6364,7 @@ dependencies = [ "sha2", "static_assertions", "tiny-keccak", + "zisk-common", ] [[package]] diff --git a/core/Cargo.toml b/core/Cargo.toml index 5e26391c5..7a840337e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,7 +18,7 @@ path = "src/bin/riscv2zisk.rs" [dependencies] precompiles-helpers = { workspace = true } lib-c = { workspace = true } -ziskos = { workspace = true } +ziskos-hints = { workspace = true } riscv = { workspace = true } zisk-pil = { workspace = true } diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index c4b6aef27..3a5e036a3 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -10,7 +10,7 @@ #![allow(unused)] use precompiles_helpers::DmaInfo; -use ziskos::zisklib::fcall_proxy; +use ziskos_hints::zisklib::fcall_proxy; use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; use paste::paste; @@ -1514,7 +1514,7 @@ fn internal_precompiled_load_data( if ctx.precompiled.input_data.len() != expected_len { panic!( "[{title}] ctx.precompiled.input_data.len={} != {expected_len} \ - [{params_count}+{load_indirections}*{load_chunks}+{load_rem}+{result}] at PC:0x{:08X} STEP:{}", + [{params_count}+{load_indirections}*{load_chunks}+{load_rem}+{result}] at PC:0x{:08X} STEP:{}", ctx.precompiled.input_data.len(), ctx.pc, ctx.step, ); } diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index cbe97d41e..6dc43f1b7 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -1,5 +1,5 @@ # Wrapper crate around ziskos that compiles with hints feature enabled. -# +# # src/core/ is a symlink to ../../ziskos/entrypoint/src/ - same source, different features. # src/lib.rs wraps the symlinked source and adds hints-specific modules. # Exports C symbols with "hints_" prefix to avoid linker conflicts with ziskos. @@ -43,3 +43,10 @@ anyhow = { workspace = true } [features] default = ["hints"] hints = [] + + [lints.rust] + unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(zisk_hints)', + 'cfg(zisk_hints_metrics)', + 'cfg(zisk_hints_debug)', + 'cfg(zisk_hints_single_thread)'] } diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index 8bae1c8d1..3c1d1cb12 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -30,12 +30,23 @@ fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch "verify", ] } -[target.'cfg(any(zisk_hints, zisk_hints_debug))'.dependencies] -once_cell = "1.21.3" -paste = "1.0" +[target.'cfg(all(not(all(target_os = "zkvm", target_vendor = "zisk")), any(zisk_hints, zisk_hints_debug)))'.dependencies] +bytes = { version = "1.11.0", optional = true } +once_cell = { version = "1.21.3", optional = true } +paste = { version = "1.0", optional = true } +zisk-common = { path = "../../common", optional = true } +anyhow = { workspace = true, optional = true } -[target.'cfg(zisk_hints_metrics)'.dependencies] -ctor = "0.2" +[target.'cfg(all(not(all(target_os = "zkvm", target_vendor = "zisk")), zisk_hints_metrics))'.dependencies] +ctor = { version = "0.2", optional = true } [features] -default = [] +default = ["user-hints"] +user-hints = ["dep:zisk-common", "dep:bytes", "dep:paste", "dep:once_cell", "dep:ctor", "dep:anyhow"] + + [lints.rust] + unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(zisk_hints)', + 'cfg(zisk_hints_metrics)', + 'cfg(zisk_hints_debug)', + 'cfg(zisk_hints_single_thread)'] } diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index 8a817bdbe..d8004130e 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -1,7 +1,9 @@ +use bytes::{Bytes, BytesMut}; use std::io::{self, Write}; use std::sync::{Arc, Condvar, Mutex}; -pub const MAX_HINT_BUFFER_LEN: usize = 1 << 20; // 1 MiB +pub const DEFAULT_BUFFER_LEN: usize = 1 << 20; // 1 MiB +pub const MAX_WRITER_LEN: usize = 128 * 1024; // 128KB is the max write size for Unix sockets pub const HEADER_LEN: usize = 8; pub struct HintBuffer { @@ -10,10 +12,8 @@ pub struct HintBuffer { } struct HintBufferInner { - buf: [u8; MAX_HINT_BUFFER_LEN], - head: usize, - tail: usize, - len: usize, + buf: BytesMut, + commit_pos: usize, closed: bool, paused: bool, } @@ -21,10 +21,8 @@ struct HintBufferInner { pub fn build_hint_buffer() -> Arc { Arc::new(HintBuffer { inner: Mutex::new(HintBufferInner { - buf: [0u8; MAX_HINT_BUFFER_LEN], - head: 0, - tail: 0, - len: 0, + buf: BytesMut::with_capacity(DEFAULT_BUFFER_LEN), + commit_pos: 0, closed: false, paused: false, }), @@ -33,46 +31,14 @@ pub fn build_hint_buffer() -> Arc { } impl HintBufferInner { - #[inline] - fn free_space(&self) -> usize { - MAX_HINT_BUFFER_LEN - self.len - } - - #[inline] + #[inline(always)] fn write_bytes(&mut self, src: &[u8]) { - let mut remaining = src; - while !remaining.is_empty() { - let end_space = MAX_HINT_BUFFER_LEN - self.tail; - let chunk = remaining.len().min(end_space); - - self.buf[self.tail..self.tail + chunk].copy_from_slice(&remaining[..chunk]); - self.tail = (self.tail + chunk) % MAX_HINT_BUFFER_LEN; - self.len += chunk; - - remaining = &remaining[chunk..]; - } + self.buf.extend_from_slice(src); } - #[inline] - fn read_bytes(&mut self, dst: &mut [u8]) -> usize { - let to_read = dst.len().min(self.len); - if to_read == 0 { - return 0; - } - - let mut out = &mut dst[..to_read]; - while !out.is_empty() { - let end_space = MAX_HINT_BUFFER_LEN - self.head; - let chunk = out.len().min(end_space); - - out[..chunk].copy_from_slice(&self.buf[self.head..self.head + chunk]); - self.head = (self.head + chunk) % MAX_HINT_BUFFER_LEN; - self.len -= chunk; - - out = &mut out[chunk..]; - } - - to_read + #[inline(always)] + fn commit(&mut self) { + self.commit_pos = self.buf.len(); } } @@ -85,31 +51,21 @@ impl HintBuffer { pub fn reset(&self) { let mut g = self.inner.lock().unwrap(); - g.head = 0; - g.tail = 0; - g.len = 0; + g.buf.clear(); + g.commit_pos = 0; g.closed = false; g.paused = false; - self.not_empty.notify_all(); } #[inline(always)] pub fn pause(&self) { - let mut g = self.inner.lock().unwrap(); - g.paused = true; + self.inner.lock().unwrap().paused = true; } #[inline(always)] pub fn resume(&self) { - let mut g = self.inner.lock().unwrap(); - g.paused = false; - } - - #[inline(always)] - pub fn is_closed(&self) -> bool { - let g = self.inner.lock().unwrap(); - g.closed + self.inner.lock().unwrap().paused = false; } #[inline(always)] @@ -132,76 +88,90 @@ impl HintBuffer { let mut g = self.inner.lock().unwrap(); - if HEADER_LEN > g.free_space() { - panic!( - "Hint buffer overflow: capacity={} used={} free={} trying_to_write={}", - MAX_HINT_BUFFER_LEN, - g.len, - g.free_space(), - HEADER_LEN - ); - } - #[cfg(zisk_hints_metrics)] crate::hints::metrics::inc_hint_count(hint_id); g.write_bytes(&header); - self.not_empty.notify_one(); } #[inline(always)] pub fn write_hint_data(&self, data: *const u8, len: usize) { - if len > MAX_HINT_BUFFER_LEN { - panic!("Hint data too large: {} bytes (max buffer {})", len, MAX_HINT_BUFFER_LEN); - } - - let mut g = self.inner.lock().unwrap(); - - let payload: &[u8] = unsafe { std::slice::from_raw_parts(data, len) }; - - if payload.len() > g.free_space() { - panic!( - "Hint buffer overflow: capacity={} used={} free={} trying_to_write={}", - MAX_HINT_BUFFER_LEN, - g.len, - g.free_space(), - payload.len() - ); - } - - g.write_bytes(payload); - self.not_empty.notify_one(); + assert!(HEADER_LEN + len <= MAX_WRITER_LEN, "Hint size ({} bytes) exceeds max hint size ({} bytes)", HEADER_LEN + len, MAX_WRITER_LEN); + let payload = unsafe { std::slice::from_raw_parts(data, len) }; + self.inner.lock().unwrap().write_bytes(payload); } - fn read_blocking(&self, dst: &mut [u8]) -> io::Result { - if dst.is_empty() { - return Ok(0); - } - + #[inline(always)] + pub fn commit(&self) { let mut g = self.inner.lock().unwrap(); - while g.len == 0 && !g.closed { - g = self.not_empty.wait(g).unwrap(); - } - - if g.len == 0 && g.closed { - return Ok(0); - } - - Ok(g.read_bytes(dst)) + g.commit(); + self.not_empty.notify_one(); } pub fn drain_to_writer(&self, writer: &mut W) -> io::Result<()> { - let mut buffer = vec![0u8; 64 * 1024]; - loop { - let n = self.read_blocking(&mut buffer)?; - if n == 0 { - break; // closed and empty + // Get chunk of hints to write from HintBuffer(under lock) + let chunk: Bytes = { + let mut g = self.inner.lock().unwrap(); + + while g.commit_pos == 0 && !g.closed { + g = self.not_empty.wait(g).unwrap(); + } + + if g.commit_pos == 0 && g.closed { + return Ok(()); + } + + let n = g.commit_pos; + g.commit_pos = 0; + g.buf.split_to(n).freeze() + }; + + + // Write hints from chunk without holding the lock + let mut chunk_pos = 0usize; + let chunk_len = chunk.len(); + let chunk_base = chunk.as_ptr(); + + // Start and end of the write buffer (MAX_WRITER_LEN) + let mut buf_start = 0usize; + let mut buf_end = 0usize; + + while chunk_pos < chunk_len { + let hint_header = unsafe { + let header_bytes = core::slice::from_raw_parts(chunk_base.add(chunk_pos), 8); + u64::from_le_bytes(header_bytes.try_into().unwrap()) + }; + + let hint_data_len = (hint_header & 0xFFFF_FFFF) as usize; + let pad = (8 - (hint_data_len & 7)) & 7; + let hint_len = HEADER_LEN + hint_data_len + pad; + + // If adding this hint exceeds max write size, flush current write buffer + if buf_end - buf_start + hint_len > MAX_WRITER_LEN { + let buf: &[u8] = unsafe { + core::slice::from_raw_parts(chunk_base.add(buf_start), buf_end - buf_start) + }; + writer.write_all(buf)?; + + // Reset write buffer + buf_start = chunk_pos; + buf_end = chunk_pos; + } + + // Accumulate current hint into write buffer + buf_end += hint_len; + // Advance to next hint + chunk_pos += hint_len; } - writer.write_all(&buffer[..n])?; + // Flush any remaining data in write buffer + if buf_end > buf_start { + let buf: &[u8] = unsafe { + core::slice::from_raw_parts(chunk_base.add(buf_start), buf_end - buf_start) + }; + writer.write_all(buf)?; + } } - - Ok(()) } } diff --git a/ziskos/entrypoint/src/hints/macros.rs b/ziskos/entrypoint/src/hints/macros.rs index 634296d09..aff8654af 100644 --- a/ziskos/entrypoint/src/hints/macros.rs +++ b/ziskos/entrypoint/src/hints/macros.rs @@ -30,6 +30,8 @@ macro_rules! define_hint { $( $crate::hints::HINT_BUFFER.write_hint_data($arg, $len); )+ + + $crate::hints::HINT_BUFFER.commit(); } $crate::hints::macros::register_hint_meta!($name, $hint_id); @@ -65,6 +67,8 @@ macro_rules! define_hint_pairs { crate::hints::HINT_BUFFER.write_hint_data(num_pairs_bytes.as_ptr(), num_pairs_bytes.len()); crate::hints::HINT_BUFFER.write_hint_data(pairs, num_pairs * $pair_len); + + $crate::hints::HINT_BUFFER.commit(); } $crate::hints::macros::register_hint_meta!($name, $hint_id); @@ -103,6 +107,8 @@ macro_rules! define_hint_ptr { const ZERO_PAD: [u8; 8] = [0; 8]; crate::hints::HINT_BUFFER.write_hint_data(ZERO_PAD.as_ptr(), pad); } + + $crate::hints::HINT_BUFFER.commit(); } $crate::hints::macros::register_hint_meta!($name, $hint_id); @@ -151,6 +157,8 @@ macro_rules! define_hint_ptr { const ZERO_PAD: [u8; 8] = [0; 8]; crate::hints::HINT_BUFFER.write_hint_data(ZERO_PAD.as_ptr(), pad); } + + $crate::hints::HINT_BUFFER.commit(); } $crate::hints::macros::register_hint_meta!($name, $hint_id); diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index cf7308ec8..8e2e5f347 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -6,21 +6,21 @@ mod kzg; mod macros; mod modexp; mod secp256k1; +mod secp256r1; mod sha256f; #[cfg(zisk_hints_metrics)] mod metrics; -use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer}; +use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer}; +use anyhow::Result; use once_cell::sync::Lazy; -use std::cell::UnsafeCell; +use std::{io::{self, BufWriter, Write}, sync::Arc}; use std::path::PathBuf; -use std::thread::{self, JoinHandle, ThreadId}; +use std::thread::{self, JoinHandle}; use std::{ffi::CStr, os::raw::c_char}; -use std::{ - io::{self, BufWriter, Write}, - sync::Arc, -}; +use std::cell::UnsafeCell; +use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; #[cfg(zisk_hints_single_thread)] use once_cell::sync::OnceCell; @@ -31,14 +31,14 @@ pub use keccak256::*; pub use kzg::*; pub use modexp::*; pub use secp256k1::*; +pub use secp256r1::*; pub use sha256f::*; pub const HINT_START: u32 = 0; pub const HINT_END: u32 = 1; static HINT_BUFFER: Lazy> = Lazy::new(|| build_hint_buffer()); -static HINT_FILE_WRITER_HANDLE: Lazy = - Lazy::new(HintFileWriterHandleCell::new); +static HINT_WRITER_HANDLE: Lazy = Lazy::new(HintFileWriterHandleCell::new); pub struct HintFileWriterHandleCell { inner: UnsafeCell>>>, @@ -48,7 +48,9 @@ unsafe impl Sync for HintFileWriterHandleCell {} impl HintFileWriterHandleCell { pub const fn new() -> Self { - Self { inner: UnsafeCell::new(None) } + Self { + inner: UnsafeCell::new(None), + } } pub fn take(&self) -> Option>> { @@ -63,12 +65,12 @@ impl HintFileWriterHandleCell { } } -pub fn init_precompile_hints(hints_file_path: PathBuf) -> io::Result<()> { +pub fn init_hints() -> io::Result<()> { // Record the main thread id to validate single-threaded calls later #[cfg(zisk_hints_single_thread)] let _ = MAIN_TID.set(std::thread::current().id()); - if let Some(handle) = HINT_FILE_WRITER_HANDLE.take() { + if let Some(handle) = HINT_WRITER_HANDLE.take() { HINT_BUFFER.close(); match handle.join() { Ok(result) => { @@ -79,7 +81,7 @@ pub fn init_precompile_hints(hints_file_path: PathBuf) -> io::Result<()> { Err(e) => { return Err(io::Error::new( io::ErrorKind::Other, - format!("Failed precompile hints writer thread, error: {:?}", e), + format!("Failed hints writer thread, error: {:?}", e), )) } } @@ -87,22 +89,45 @@ pub fn init_precompile_hints(hints_file_path: PathBuf) -> io::Result<()> { HINT_BUFFER.reset(); - let handle = thread::spawn(move || write_precompile_hints(hints_file_path)); - HINT_FILE_WRITER_HANDLE.store(handle); + Ok(()) +} + +pub fn init_hints_file(hints_file_path: PathBuf) -> io::Result<()> { + init_hints()?; + + let handle = thread::spawn(move || write_hints_to_file(hints_file_path)); + HINT_WRITER_HANDLE.store(handle); Ok(()) } -pub fn close_precompile_hints() -> io::Result<()> { +pub fn init_hints_socket(socket_path: PathBuf) -> io::Result<()> { + init_hints()?; + + // Create the Unix socket writer (server) + let mut socket_writer = UnixSocketWriter::new(&socket_path).map_err(io::Error::other)?; + + // Open the connection (waits for client to connect) + socket_writer.open().map_err(io::Error::other)?; + println!("Client connected to hints socket! Starting hint data transfer..."); + + let handle = thread::spawn(move || write_hints_to_socket(socket_writer)); + HINT_WRITER_HANDLE.store(handle); + + Ok(()) +} +pub fn close_hints() -> io::Result<()> { HINT_BUFFER.close(); - let handle = HINT_FILE_WRITER_HANDLE.take(); + let handle = HINT_WRITER_HANDLE.take(); if let Some(handle) = handle { match handle.join() { - Ok(result) => match result { - Ok(()) => Ok(()), - Err(e) => return Err(e), - }, + Ok(result) => { + match result { + Ok(()) => Ok(()), + Err(e) => return Err(e), + } + } Err(e) => Err(io::Error::new( io::ErrorKind::Other, format!("Failed precompile hints writer thread, error: {:?}", e), @@ -113,32 +138,26 @@ pub fn close_precompile_hints() -> io::Result<()> { } } -fn write_precompile_hints(path: PathBuf) -> io::Result<()> { - debug_assert!(cfg!(target_endian = "little")); - - let file = std::fs::File::create(path)?; - let mut file_writer = BufWriter::with_capacity(1 << 20, file); +pub fn write_hints(writer: &mut W) -> io::Result<()> { let disable_prefix = std::env::var("HINTS_DISABLE_PREFIX").unwrap_or_default() == "1"; // Write HINT_START if !disable_prefix { let start_header: u64 = ((HINT_START as u64) << 32) | 0u64; let start_bytes = start_header.to_le_bytes(); - file_writer.write_all(&start_bytes)?; + writer.write_all(&start_bytes)?; } // Write hints from the buffer - HINT_BUFFER.drain_to_writer(&mut file_writer)?; - file_writer.flush()?; - + HINT_BUFFER.drain_to_writer(writer)?; // Write HINT_END if !disable_prefix { let end_header: u64 = ((HINT_END as u64) << 32) | 0u64; let end_bytes = end_header.to_le_bytes(); - file_writer.write_all(&end_bytes)?; + writer.write_all(&end_bytes)?; } - file_writer.flush()?; + writer.flush()?; #[cfg(zisk_hints_metrics)] crate::hints::metrics::print_metrics(); @@ -146,6 +165,53 @@ fn write_precompile_hints(path: PathBuf) -> io::Result<()> { Ok(()) } +fn write_hints_to_file(path: PathBuf) -> io::Result<()> { + debug_assert!(cfg!(target_endian = "little")); + + let file = std::fs::File::create(path)?; + let mut file_writer = BufWriter::with_capacity(1 << 20, file); + + write_hints(&mut file_writer)?; + + Ok(()) +} + +struct UnixSocketWriter { + inner: UnixSocketStreamWriter, +} + +impl UnixSocketWriter { + pub fn new(path: &PathBuf) -> Result { + let writer = UnixSocketStreamWriter::new(path)?; + Ok(Self { inner: writer }) + } +} + +impl UnixSocketWriter { + pub fn open(&mut self) -> Result<()> { + self.inner.open() + } +} + +// TODO: implement error handling +impl Write for UnixSocketWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf).map_err(io::Error::other) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush().map_err(io::Error::other) + } +} + +fn write_hints_to_socket(mut socket_writer: UnixSocketWriter) -> io::Result<()> { + debug_assert!(cfg!(target_endian = "little")); + + write_hints(&mut socket_writer)?; + + Ok(()) +} + #[cfg(zisk_hints_single_thread)] static MAIN_TID: OnceCell = OnceCell::new(); @@ -208,4 +274,4 @@ pub unsafe extern "C" fn hint_log_c(msg: *const c_char) { Ok(s) => hint_log(s), Err(_) => return, } -} +} \ No newline at end of file diff --git a/ziskos/entrypoint/src/hints/secp256r1.rs b/ziskos/entrypoint/src/hints/secp256r1.rs new file mode 100644 index 000000000..5f67fd580 --- /dev/null +++ b/ziskos/entrypoint/src/hints/secp256r1.rs @@ -0,0 +1,11 @@ +use crate::hints::macros::define_hint; + +const SECP256R1_ECDSA_VERIFY_HINT_ID: u32 = 0x0301; + +define_hint! { + secp256r1_ecdsa_verify => { + hint_id: SECP256R1_ECDSA_VERIFY_HINT_ID, + params: (msg: 32, sig: 64, pk: 64), + is_result: false, + } +} diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 2f0b485ad..e117d91bc 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -9,16 +9,12 @@ mod profile; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] pub use fcall::*; pub use profile::*; - pub mod zisklib; - pub mod syscalls; - pub mod io; - pub mod ziskos_definitions; -#[cfg(any(zisk_hints, zisk_hints_debug))] +#[cfg(all(not(all(target_os = "zkvm", target_vendor = "zisk")), any(zisk_hints, zisk_hints_debug), feature = "user-hints"))] pub mod hints; #[macro_export] diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index 6117ad0f9..c0384131d 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -1,9 +1,46 @@ use crate::syscalls::syscall_keccak_f; +#[cfg(zisk_hints_debug)] +use std::os::raw::c_char; + +#[cfg(all(not(all(target_os = "zkvm", target_vendor = "zisk")), zisk_hints))] +extern "C" { + fn hint_keccak256(input_ptr: *const u8, input_len: usize); +} + +#[cfg(all(not(all(target_os = "zkvm", target_vendor = "zisk")), zisk_hints_debug))] +extern "C" { + fn hint_log_c(msg: *const c_char); +} + +#[cfg(zisk_hints_debug)] +pub fn hint_log>(msg: S) { + // On native we call external C function to log hints, since it controls if hints are paused or not + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + { + use std::ffi::CString; + + if let Ok(c) = CString::new(msg.as_ref()) { + unsafe { hint_log_c(c.as_ptr()) }; + } + } + // On zkvm/zisk, we can just print directly + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + println!("{}", msg.as_ref()); + } +} + /// Keccak-256 rate in bytes (1600 - 2*256) / 8 = 136 bytes const KECCAK256_RATE: usize = 136; -/// Keccak-256 hash function. For reference: https://keccak.team/keccak_specs_summary.html +/// Computes the Keccak-256 hash of the input data. +/// +/// This implements the Keccak sponge construction with: +/// - Rate: 1088 bits (136 bytes) +/// - Capacity: 512 bits (64 bytes) +/// - Output: 256 bits (32 bytes) +/// - Padding: Keccak padding (0x01...0x80) pub fn keccak256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u8; 32] { let mut state = [0u64; 25]; let input_len = input.len(); @@ -100,12 +137,12 @@ pub unsafe extern "C" fn keccak256_c( #[cfg_attr(feature = "hints", export_name = "hints_native_keccak256_c")] pub unsafe extern "C" fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8) { #[cfg(zisk_hints)] - crate::hints::hint_keccak256(bytes, len); + hint_keccak256(bytes, len); #[cfg(zisk_hints_debug)] { let input_bytes = unsafe { core::slice::from_raw_parts(bytes, len) }; - crate::hints::hint_log(format!("hint_keccak256 (bytes: {:?}, len: {})", input_bytes, len)); + hint_log(format!("hint_keccak256 (bytes: {:?}, len: {})", input_bytes, len)); } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] From afba4324561e1e1b4466f0a53ba1557a2b8e57db Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 4 Feb 2026 14:31:56 +0000 Subject: [PATCH 411/782] Remove keccak256 comments --- ziskos/entrypoint/src/zisklib/lib/keccak256.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index c0384131d..76c67a920 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -34,13 +34,7 @@ pub fn hint_log>(msg: S) { /// Keccak-256 rate in bytes (1600 - 2*256) / 8 = 136 bytes const KECCAK256_RATE: usize = 136; -/// Computes the Keccak-256 hash of the input data. -/// -/// This implements the Keccak sponge construction with: -/// - Rate: 1088 bits (136 bytes) -/// - Capacity: 512 bits (64 bytes) -/// - Output: 256 bits (32 bytes) -/// - Padding: Keccak padding (0x01...0x80) +/// Keccak-256 hash function. For reference: https://keccak.team/keccak_specs_summary.html pub fn keccak256(input: &[u8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u8; 32] { let mut state = [0u64; 25]; let input_len = input.len(); From de3c051b0c3e19fbbfa8789cd38d1a42ee74e431 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 4 Feb 2026 15:44:49 +0000 Subject: [PATCH 412/782] Fix clippy --- ziskos/entrypoint/src/hints/hint_buffer.rs | 8 +++++-- ziskos/entrypoint/src/hints/mod.rs | 28 +++++++++++----------- ziskos/entrypoint/src/lib.rs | 10 +++++--- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index d8004130e..d7f3d7df9 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -96,7 +96,12 @@ impl HintBuffer { #[inline(always)] pub fn write_hint_data(&self, data: *const u8, len: usize) { - assert!(HEADER_LEN + len <= MAX_WRITER_LEN, "Hint size ({} bytes) exceeds max hint size ({} bytes)", HEADER_LEN + len, MAX_WRITER_LEN); + assert!( + HEADER_LEN + len <= MAX_WRITER_LEN, + "Hint size ({} bytes) exceeds max hint size ({} bytes)", + HEADER_LEN + len, + MAX_WRITER_LEN + ); let payload = unsafe { std::slice::from_raw_parts(data, len) }; self.inner.lock().unwrap().write_bytes(payload); } @@ -127,7 +132,6 @@ impl HintBuffer { g.buf.split_to(n).freeze() }; - // Write hints from chunk without holding the lock let mut chunk_pos = 0usize; let chunk_len = chunk.len(); diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 8e2e5f347..8c47eb82f 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -12,14 +12,17 @@ mod sha256f; #[cfg(zisk_hints_metrics)] mod metrics; -use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer}; +use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer}; use anyhow::Result; use once_cell::sync::Lazy; -use std::{io::{self, BufWriter, Write}, sync::Arc}; +use std::cell::UnsafeCell; use std::path::PathBuf; use std::thread::{self, JoinHandle}; use std::{ffi::CStr, os::raw::c_char}; -use std::cell::UnsafeCell; +use std::{ + io::{self, BufWriter, Write}, + sync::Arc, +}; use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; #[cfg(zisk_hints_single_thread)] @@ -38,7 +41,8 @@ pub const HINT_START: u32 = 0; pub const HINT_END: u32 = 1; static HINT_BUFFER: Lazy> = Lazy::new(|| build_hint_buffer()); -static HINT_WRITER_HANDLE: Lazy = Lazy::new(HintFileWriterHandleCell::new); +static HINT_WRITER_HANDLE: Lazy = + Lazy::new(HintFileWriterHandleCell::new); pub struct HintFileWriterHandleCell { inner: UnsafeCell>>>, @@ -48,9 +52,7 @@ unsafe impl Sync for HintFileWriterHandleCell {} impl HintFileWriterHandleCell { pub const fn new() -> Self { - Self { - inner: UnsafeCell::new(None), - } + Self { inner: UnsafeCell::new(None) } } pub fn take(&self) -> Option>> { @@ -122,12 +124,10 @@ pub fn close_hints() -> io::Result<()> { let handle = HINT_WRITER_HANDLE.take(); if let Some(handle) = handle { match handle.join() { - Ok(result) => { - match result { - Ok(()) => Ok(()), - Err(e) => return Err(e), - } - } + Ok(result) => match result { + Ok(()) => Ok(()), + Err(e) => return Err(e), + }, Err(e) => Err(io::Error::new( io::ErrorKind::Other, format!("Failed precompile hints writer thread, error: {:?}", e), @@ -274,4 +274,4 @@ pub unsafe extern "C" fn hint_log_c(msg: *const c_char) { Ok(s) => hint_log(s), Err(_) => return, } -} \ No newline at end of file +} diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index e117d91bc..48e90068a 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -8,13 +8,17 @@ mod fcall; mod profile; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] pub use fcall::*; +pub mod io; pub use profile::*; -pub mod zisklib; pub mod syscalls; -pub mod io; +pub mod zisklib; pub mod ziskos_definitions; -#[cfg(all(not(all(target_os = "zkvm", target_vendor = "zisk")), any(zisk_hints, zisk_hints_debug), feature = "user-hints"))] +#[cfg(all( + not(all(target_os = "zkvm", target_vendor = "zisk")), + any(zisk_hints, zisk_hints_debug), + feature = "user-hints" +))] pub mod hints; #[macro_export] From 8540aaf4edeec6cb27a35033600502eff2c2511a Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 4 Feb 2026 15:53:05 +0000 Subject: [PATCH 413/782] Fix use ThreadId --- ziskos/entrypoint/Cargo.toml | 4 ++-- ziskos/entrypoint/src/hints/mod.rs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index 3c1d1cb12..beef2366a 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -44,8 +44,8 @@ ctor = { version = "0.2", optional = true } default = ["user-hints"] user-hints = ["dep:zisk-common", "dep:bytes", "dep:paste", "dep:once_cell", "dep:ctor", "dep:anyhow"] - [lints.rust] - unexpected_cfgs = { level = "warn", check-cfg = [ +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(zisk_hints)', 'cfg(zisk_hints_metrics)', 'cfg(zisk_hints_debug)', diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 8c47eb82f..0563e4d51 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -25,6 +25,9 @@ use std::{ }; use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; +#[cfg(zisk_hints_single_thread)] +use std::thread::ThreadId; + #[cfg(zisk_hints_single_thread)] use once_cell::sync::OnceCell; @@ -130,7 +133,7 @@ pub fn close_hints() -> io::Result<()> { }, Err(e) => Err(io::Error::new( io::ErrorKind::Other, - format!("Failed precompile hints writer thread, error: {:?}", e), + format!("Failed hints writer thread, error: {:?}", e), )), } } else { @@ -185,9 +188,7 @@ impl UnixSocketWriter { let writer = UnixSocketStreamWriter::new(path)?; Ok(Self { inner: writer }) } -} - -impl UnixSocketWriter { + pub fn open(&mut self) -> Result<()> { self.inner.open() } From 0ec629f6c3ecc2219453002995d6024a5034f41a Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 4 Feb 2026 15:56:23 +0000 Subject: [PATCH 414/782] Fix clippy --- ziskos/entrypoint/src/hints/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 0563e4d51..848843b6f 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -188,7 +188,7 @@ impl UnixSocketWriter { let writer = UnixSocketStreamWriter::new(path)?; Ok(Self { inner: writer }) } - + pub fn open(&mut self) -> Result<()> { self.inner.open() } From fdc7cc1df1408e81e12c680d230c7031121e680e Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 4 Feb 2026 16:44:52 +0000 Subject: [PATCH 415/782] Improve error handling in UnixSocketWriter --- ziskos-hints/Cargo.toml | 12 ++++++------ ziskos/entrypoint/Cargo.toml | 8 ++++---- ziskos/entrypoint/src/hints/mod.rs | 10 +++++++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index 6dc43f1b7..c757b5517 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -44,9 +44,9 @@ anyhow = { workspace = true } default = ["hints"] hints = [] - [lints.rust] - unexpected_cfgs = { level = "warn", check-cfg = [ - 'cfg(zisk_hints)', - 'cfg(zisk_hints_metrics)', - 'cfg(zisk_hints_debug)', - 'cfg(zisk_hints_single_thread)'] } +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ +'cfg(zisk_hints)', +'cfg(zisk_hints_metrics)', +'cfg(zisk_hints_debug)', +'cfg(zisk_hints_single_thread)'] } diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index beef2366a..01ba2d19d 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -46,7 +46,7 @@ user-hints = ["dep:zisk-common", "dep:bytes", "dep:paste", "dep:once_cell", "dep [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ - 'cfg(zisk_hints)', - 'cfg(zisk_hints_metrics)', - 'cfg(zisk_hints_debug)', - 'cfg(zisk_hints_single_thread)'] } +'cfg(zisk_hints)', +'cfg(zisk_hints_metrics)', +'cfg(zisk_hints_debug)', +'cfg(zisk_hints_single_thread)'] } diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 848843b6f..b3c7bdf7f 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -113,6 +113,7 @@ pub fn init_hints_socket(socket_path: PathBuf) -> io::Result<()> { let mut socket_writer = UnixSocketWriter::new(&socket_path).map_err(io::Error::other)?; // Open the connection (waits for client to connect) + // TODO: Implement open timeout socket_writer.open().map_err(io::Error::other)?; println!("Client connected to hints socket! Starting hint data transfer..."); @@ -194,14 +195,17 @@ impl UnixSocketWriter { } } -// TODO: implement error handling impl Write for UnixSocketWriter { fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner.write(buf).map_err(io::Error::other) + self.inner + .write(buf) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) } fn flush(&mut self) -> io::Result<()> { - self.inner.flush().map_err(io::Error::other) + self.inner + .flush() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) } } From 8742f07999777a9c64d6657c03963516ce905d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Wed, 4 Feb 2026 17:50:25 +0100 Subject: [PATCH 416/782] SdK improvements (#722) * Updating PlonkVerifier * Working on proof type * Keep working * Keep working on sdk * ProveSnarkCmd change * Working on verification call * Adding verifier only option * Connecting to latest branch proof_type proofman * Minor fixes * Adapting to proofman changes * Fix merge * SDK sha hasher example working. Pending: ASM generation + include_elf * Minor fixes * ASM build problem solved. Pending include elf * Cargo clippy * Cargo clippy * Simplifying vk handle * Adding include_elf with a string for now * Fix * Verification of plonk working * Not pointing to local * Cargo fmt * Improving publics handling SDK * Adding elf name (#732) * Cargo update * Adding another function * Cargo update * Sdk hints working * Removing proving key path * Cargo fmt and clippy * Fix riscof tests due to output log divergence * Increase emulate_asm_all.sh server initialization wait time from 5 to 8 seconds * Fix write method ZiskPublics * New version working * Update Cargo --------- Co-authored-by: fractasy --- Cargo.lock | 118 +- Cargo.toml | 2 - cli/Cargo.toml | 1 - cli/src/bin/cargo-zisk.rs | 12 +- cli/src/commands/build.rs | 11 + cli/src/commands/common.rs | 12 - cli/src/commands/execute.rs | 28 +- cli/src/commands/mod.rs | 4 +- cli/src/commands/prove.rs | 158 +- cli/src/commands/prove_snark.rs | 23 +- cli/src/commands/rom_setup.rs | 38 +- cli/src/commands/rom_vkey.rs | 48 - cli/src/commands/stats.rs | 34 +- cli/src/commands/verify_constraints.rs | 32 +- cli/src/commands/verify_snark.rs | 39 + cli/src/commands/verify_stark.rs | 17 +- common/Cargo.toml | 2 +- common/src/io/stdin/file.rs | 21 +- common/src/io/stdin/memory.rs | 37 +- common/src/io/stdin/null.rs | 10 +- common/src/io/stdin/zisk_stdin.rs | 45 +- common/src/proof.rs | 44 +- common/src/types.rs | 48 + core/src/bin/riscv2zisk.rs | 8 +- core/src/elf2rom.rs | 13 +- core/src/elf_extraction.rs | 11 +- core/src/riscv2zisk.rs | 27 +- distributed/crates/common/Cargo.toml | 1 + distributed/crates/coordinator/Cargo.toml | 1 + .../crates/coordinator/src/coordinator.rs | 24 +- distributed/crates/worker/src/worker.rs | 157 +- distributed/crates/worker/src/worker_node.rs | 4 +- emulator-asm/src/main.c | 24 + emulator/benches/benchmark.rs | 9 +- emulator/src/emulator.rs | 5 +- examples/Cargo.lock | 4823 +++++++++++++++++ examples/Cargo.toml | 19 + examples/sha-hasher/guest/Cargo.lock | 293 + examples/sha-hasher/guest/Cargo.toml | 10 + examples/sha-hasher/guest/src/main.rs | 34 + examples/sha-hasher/host/Cargo.toml | 14 + examples/sha-hasher/host/build.rs | 3 + examples/sha-hasher/host/src/main.rs | 70 + rom-setup/Cargo.toml | 2 +- rom-setup/src/asm_setup.rs | 48 +- rom-setup/src/lib.rs | 4 - rom-setup/src/rom_full_setup.rs | 59 - rom-setup/src/rom_merkle.rs | 69 +- rom-setup/src/rom_vkey.rs | 38 - rom-setup/src/utils.rs | 127 +- sdk/Cargo.toml | 5 +- sdk/src/builder.rs | 314 +- sdk/src/lib.rs | 16 +- sdk/src/proof.rs | 317 -- sdk/src/prover/asm.rs | 288 +- sdk/src/prover/backend.rs | 521 +- sdk/src/prover/emu.rs | 185 +- sdk/src/prover/mod.rs | 575 +- sdk/src/utils.rs | 65 +- sdk/src/zisk_lib_loader.rs | 11 +- state-machines/rom/src/rom.rs | 6 +- tools/emulate_asm_all.sh | 6 +- verifier/Cargo.toml | 1 + verifier/src/verifier.rs | 15 +- witness-computation/src/lib.rs | 1 - witness-computation/src/zisk_lib.rs | 98 +- zisk-contracts/IZiskVerifier.sol | 1 + zisk-contracts/PlonkVerifier.sol | 83 +- zisk-contracts/ZiskVerifier.sol | 13 +- ziskbuild/Cargo.toml | 3 + ziskbuild/src/build.rs | 62 +- ziskbuild/src/lib.rs | 6 + ziskos/entrypoint/src/lib.rs | 21 +- 73 files changed, 7609 insertions(+), 1685 deletions(-) delete mode 100644 cli/src/commands/rom_vkey.rs create mode 100644 cli/src/commands/verify_snark.rs create mode 100644 examples/Cargo.lock create mode 100644 examples/Cargo.toml create mode 100644 examples/sha-hasher/guest/Cargo.lock create mode 100644 examples/sha-hasher/guest/Cargo.toml create mode 100644 examples/sha-hasher/guest/src/main.rs create mode 100644 examples/sha-hasher/host/Cargo.toml create mode 100644 examples/sha-hasher/host/build.rs create mode 100644 examples/sha-hasher/host/src/main.rs delete mode 100644 rom-setup/src/rom_full_setup.rs delete mode 100644 rom-setup/src/rom_vkey.rs delete mode 100644 sdk/src/proof.rs diff --git a/Cargo.lock b/Cargo.lock index 53bdc5a42..8db69f881 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -624,9 +624,9 @@ checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "camino" @@ -653,7 +653,6 @@ version = "0.16.0" dependencies = [ "anyhow", "asm-runner", - "bytemuck", "clap", "colored", "dirs", @@ -803,9 +802,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", "clap_derive", @@ -813,9 +812,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", @@ -976,9 +975,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpp_demangle" -version = "0.4.5" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" dependencies = [ "cfg-if", ] @@ -1029,16 +1028,16 @@ dependencies = [ [[package]] name = "criterion" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ "alloca", "anes", "cast", "ciborium", "clap", - "criterion-plot 0.8.1", + "criterion-plot 0.8.2", "itertools 0.13.0", "num-traits", "oorandom", @@ -1064,9 +1063,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", "itertools 0.13.0", @@ -1190,7 +1189,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" dependencies = [ "fields", "num-bigint", @@ -1577,7 +1576,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" dependencies = [ "cfg-if", "num-bigint", @@ -1611,9 +1610,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -2621,11 +2620,12 @@ dependencies = [ [[package]] name = "msvc-demangler" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4c25a3bb7d880e8eceab4822f3141ad0700d20f025991c1f03bd3d00219a5fc" +checksum = "fbeff6bd154a309b2ada5639b2661ca6ae4599b34e8487dc276d2cd637da2d76" dependencies = [ "bitflags 2.10.0", + "itoa", ] [[package]] @@ -2965,7 +2965,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" dependencies = [ "colored", "fields", @@ -3339,7 +3339,7 @@ name = "precompiles-hints" version = "0.16.0" dependencies = [ "anyhow", - "criterion 0.8.1", + "criterion 0.8.2", "lib-c", "precompiles-helpers", "rayon", @@ -3380,8 +3380,9 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" dependencies = [ + "bincode", "blake3", "borsh", "bytemuck", @@ -3415,9 +3416,11 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" dependencies = [ + "bincode", "borsh", + "bytemuck", "colored", "crossbeam-channel", "crossbeam-queue", @@ -3446,7 +3449,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" dependencies = [ "fields", "itoa", @@ -3459,7 +3462,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" dependencies = [ "proc-macro2", "quote", @@ -3470,7 +3473,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" dependencies = [ "crossbeam-channel", "tracing", @@ -3479,10 +3482,13 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" dependencies = [ + "bincode", + "bytemuck", "colored", "fields", + "serde", "sysinfo 0.35.2", "tracing", ] @@ -3490,10 +3496,11 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" dependencies = [ "bytemuck", "fields", + "proofman-util", "rayon", "tracing", ] @@ -3767,9 +3774,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -3779,9 +3786,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3790,9 +3797,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reqwest" @@ -3881,12 +3888,12 @@ version = "0.16.0" dependencies = [ "anyhow", "blake3", - "bytemuck", "colored", "fields", "proofman-common", "sm-rom", "tracing", + "zisk-common", "zisk-core", "zisk-pil", ] @@ -4514,9 +4521,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.17.1" +version = "12.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520cf51c674f8b93d533f80832babe413214bb766b6d7cb74ee99ad2971f8467" +checksum = "751a2823d606b5d0a7616499e4130a516ebd01a44f39811be2b9600936509c23" dependencies = [ "debugid", "memmap2", @@ -4526,9 +4533,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.17.1" +version = "12.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f0de2ee0ffa2641e17ba715ad51d48b9259778176517979cb38b6aa86fa7425" +checksum = "79b237cfbe320601dd24b4ac817a5b68bb28f5508e33f08d42be0682cadc8ac9" dependencies = [ "cc", "cpp_demangle", @@ -5440,18 +5447,18 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -5884,7 +5891,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" dependencies = [ "colored", "fields", @@ -5970,18 +5977,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.37" +version = "0.8.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.37" +version = "0.8.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" dependencies = [ "proc-macro2", "quote", @@ -6069,7 +6076,10 @@ dependencies = [ "anyhow", "cargo_metadata", "clap", + "rom-setup", + "tracing", "vergen", + "zisk-sdk", ] [[package]] @@ -6078,12 +6088,12 @@ version = "0.16.0" dependencies = [ "anyhow", "bincode", - "bytemuck", "fields", "libc", "mpi", "proofman", "proofman-common", + "proofman-util", "quinn", "rcgen", "rustls", @@ -6129,6 +6139,7 @@ dependencies = [ "chrono", "proofman", "proofman-common", + "proofman-util", "serde", "serde_json", "thiserror 2.0.18", @@ -6154,6 +6165,7 @@ dependencies = [ "futures-util", "humantime", "proofman", + "proofman-util", "prost-types", "reqwest", "serde", @@ -6242,7 +6254,7 @@ version = "0.16.0" dependencies = [ "anyhow", "asm-runner", - "bytemuck", + "bincode", "colored", "fields", "libloading", @@ -6250,9 +6262,12 @@ dependencies = [ "proofman-common", "proofman-util", "rom-setup", + "serde", + "sha2", "tracing", "zisk-common", "zisk-distributed-common", + "zisk-verifier", "zisk-witness", "zstd", ] @@ -6262,6 +6277,7 @@ name = "zisk-verifier" version = "0.16.0" dependencies = [ "anyhow", + "proofman-util", "proofman-verifier", ] diff --git a/Cargo.toml b/Cargo.toml index 702be1e32..970828b10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -159,7 +159,6 @@ static_assertions = "1" clap = { version = "4", features = ["derive", "env"] } futures = { version = "0.3" } thiserror = { version = "2" } -bytes = "1.0" bincode = "1.3.3" tokio = { version = "1", features = ["full"] } @@ -186,5 +185,4 @@ reqwest = { version = "0.12", features = [ "json", "rustls-tls", ], default-features = false } -bytemuck = "1.23" zstd = "0.13" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 6c4bf5013..435756f1e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -42,7 +42,6 @@ zisk-sdk = { workspace = true } zisk-verifier = { workspace = true } zstd = { workspace = true } -bytemuck = { workspace = true } clap = { workspace = true } dirs = "6" rand = "0.9" diff --git a/cli/src/bin/cargo-zisk.rs b/cli/src/bin/cargo-zisk.rs index 22bc5fef8..a45883e63 100644 --- a/cli/src/bin/cargo-zisk.rs +++ b/cli/src/bin/cargo-zisk.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Context, Result}; use cargo_zisk::commands::{ ZiskBuild, ZiskCheckSetup, ZiskClean, ZiskExecute, ZiskProve, ZiskProveSnark, ZiskRomSetup, - ZiskRomVkey, ZiskRun, ZiskSdk, ZiskStats, ZiskVerify, ZiskVerifyConstraints, + ZiskRun, ZiskSdk, ZiskStats, ZiskVerify, ZiskVerifyConstraints, ZiskVerifySnark, }; use clap::Parser; use zisk_build::ZISK_VERSION_MESSAGE; @@ -23,11 +23,11 @@ pub enum Cargo { Prove(ZiskProve), ProveSnark(ZiskProveSnark), RomSetup(ZiskRomSetup), - RomVkey(ZiskRomVkey), Run(ZiskRun), Sdk(ZiskSdk), Stats(ZiskStats), Verify(ZiskVerify), + VerifySnark(ZiskVerifySnark), VerifyConstraints(ZiskVerifyConstraints), } @@ -54,14 +54,11 @@ fn main() -> Result<()> { Cargo::RomSetup(cmd) => { cmd.run().context("Error executing RomSetup command")?; } - Cargo::RomVkey(cmd) => { - cmd.run().context("Error executing RomVkey command")?; - } Cargo::Run(cmd) => { cmd.run().context("Error executing Run command")?; } Cargo::Stats(mut cmd) => { - cmd.run().context("Error executing SDK command")?; + cmd.run().context("Error executing Stats command")?; } Cargo::Execute(mut cmd) => { cmd.run().context("Error executing Execute command")?; @@ -72,6 +69,9 @@ fn main() -> Result<()> { Cargo::Verify(cmd) => { cmd.run().map_err(|e| anyhow!("Error executing Verify command: {}", e))?; } + Cargo::VerifySnark(cmd) => { + cmd.run().context("Error executing VerifySnark command")?; + } Cargo::VerifyConstraints(mut cmd) => { cmd.run().context("Error executing VerifyConstraints command")?; } diff --git a/cli/src/commands/build.rs b/cli/src/commands/build.rs index 7a71c002c..c3d49b138 100644 --- a/cli/src/commands/build.rs +++ b/cli/src/commands/build.rs @@ -17,6 +17,12 @@ pub struct ZiskBuild { #[clap(long)] no_default_features: bool, + + #[clap(short = 'z', long)] + zisk_path: Option, + + #[clap(long)] + hints: bool, } impl ZiskBuild { @@ -40,6 +46,11 @@ impl ZiskBuild { command.args(["--target", ZISK_TARGET]); + // Pass zisk_path to build scripts via environment variable + if let Some(zisk_path) = &self.zisk_path { + command.env("ZISK_PATH", zisk_path); + } + // Set up the command to inherit the parent's stdout and stderr command.stdout(Stdio::inherit()); command.stderr(Stdio::inherit()); diff --git a/cli/src/commands/common.rs b/cli/src/commands/common.rs index b97ef04fd..3bd207e86 100644 --- a/cli/src/commands/common.rs +++ b/cli/src/commands/common.rs @@ -24,12 +24,6 @@ pub fn get_home_zisk_path() -> PathBuf { PathBuf::from(zisk_path) } -/// Gets the default zisk folder location in the home installation directory. -pub fn get_default_zisk_path() -> PathBuf { - let zisk_path = format!("{}/.zisk/zisk", get_home_dir()); - PathBuf::from(zisk_path) -} - /// Gets the default stark info JSON file location in the home installation directory. pub fn get_default_stark_info() -> String { let stark_info = format!( @@ -73,9 +67,3 @@ pub fn get_proving_key(proving_key: Option<&PathBuf>) -> PathBuf { pub fn get_proving_key_snark(proving_key_snark: Option<&PathBuf>) -> PathBuf { proving_key_snark.cloned().unwrap_or_else(get_default_proving_key_snark) } - -/// Gets the zisk folder. -/// Uses the default one if not specified by user. -pub fn get_zisk_path(zisk_path: Option<&PathBuf>) -> PathBuf { - zisk_path.cloned().unwrap_or_else(get_default_zisk_path) -} diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index a03df4bb9..d4155fa85 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -1,9 +1,11 @@ use anyhow::Result; use clap::Parser; use colored::Colorize; +use std::fs; use std::path::PathBuf; use tracing::{info, warn}; use zisk_build::ZISK_VERSION_MESSAGE; +use zisk_common::ElfBinaryOwned; use zisk_sdk::{ProverClient, ZiskExecuteResult}; use crate::ux::{print_banner, print_banner_field}; @@ -122,13 +124,20 @@ impl ZiskExecute { .emu() .witness() .proving_key_path_opt(self.proving_key.clone()) - .elf_path(self.elf.clone()) .verbose(self.verbose) .shared_tables(self.shared_tables) .print_command_info() .build()?; - prover.execute(stdin, None) + let elf_bin = fs::read(&self.elf) + .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; + let elf = ElfBinaryOwned::new( + elf_bin, + self.elf.file_stem().unwrap().to_str().unwrap().to_string(), + false, + ); + prover.setup(&elf)?; + prover.execute(stdin) } pub fn run_asm( @@ -140,16 +149,25 @@ impl ZiskExecute { .asm() .witness() .proving_key_path_opt(self.proving_key.clone()) - .elf_path(self.elf.clone()) .verbose(self.verbose) .shared_tables(self.shared_tables) .asm_path_opt(self.asm.clone()) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) - .with_hints(hints_stream.is_some()) .print_command_info() .build()?; - prover.execute(stdin, hints_stream) + let elf_bin = fs::read(&self.elf) + .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; + let elf = ElfBinaryOwned::new( + elf_bin, + self.elf.file_stem().unwrap().to_str().unwrap().to_string(), + hints_stream.is_some(), + ); + prover.setup(&elf)?; + if let Some(hints_stream) = hints_stream { + prover.set_hints_stream(hints_stream)?; + } + prover.execute(stdin) } } diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index ace972dcc..d366eba74 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -6,11 +6,11 @@ mod execute; mod prove; mod prove_snark; mod rom_setup; -mod rom_vkey; mod run; mod sdk; mod stats; mod verify_constraints; +mod verify_snark; mod verify_stark; pub use build::*; @@ -21,9 +21,9 @@ pub use execute::*; pub use prove::*; pub use prove_snark::*; pub use rom_setup::*; -pub use rom_vkey::*; pub use run::*; pub use sdk::*; pub use stats::*; pub use verify_constraints::*; +pub use verify_snark::*; pub use verify_stark::*; diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 98c9f1f9b..bbd7ef7f7 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -2,12 +2,19 @@ use crate::ux::{print_banner, print_banner_field}; use anyhow::Result; use colored::Colorize; +use proofman::{get_vadcop_final_proof_vkey, SnarkProof, SnarkProtocol}; use proofman_common::ParamsGPU; +use proofman_util::VadcopFinalProof; +use std::fs; use std::path::PathBuf; use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; -use zisk_sdk::{ProverClient, ZiskProveResult}; +use zisk_common::ElfBinaryOwned; +#[cfg(feature = "stats")] +use zisk_common::ExecutorStatsEvent; +use zisk_sdk::ZiskProgramVK; +use zisk_sdk::{get_proving_key, ProofOpts, ProverClient, ZiskProof, ZiskProveResult}; // Structure representing the 'prove' subcommand of cargo. #[derive(clap::Args)] @@ -114,6 +121,19 @@ impl ZiskProve { print_banner(); + let mut gpu_params = None; + if self.preallocate + || self.max_streams.is_some() + || self.number_threads_witness.is_some() + || self.max_witness_stored.is_some() + { + let mut gpu_params_new = ParamsGPU::new(self.preallocate); + if let Some(max_witness_stored) = self.max_witness_stored { + gpu_params_new.with_max_witness_stored(max_witness_stored); + } + gpu_params = Some(gpu_params_new); + } + if let Some(inputs) = &self.inputs { print_banner_field("Input", inputs); } @@ -122,18 +142,6 @@ impl ZiskProve { print_banner_field("Prec. Hints", hints); } - let mut gpu_params = ParamsGPU::new(self.preallocate); - - if let Some(max_streams) = self.max_streams { - gpu_params.with_max_number_streams(max_streams); - } - if let Some(number_threads_witness) = self.number_threads_witness { - gpu_params.with_number_threads_pools_witness(number_threads_witness); - } - if let Some(max_witness_stored) = self.max_witness_stored { - gpu_params.with_max_witness_stored(max_witness_stored); - } - let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; let hints_stream = match self.hints.as_ref() { @@ -156,7 +164,7 @@ impl ZiskProve { self.emulator }; - let (result, world_rank) = if emulator { + let (vk, result, world_rank) = if emulator { self.run_emu(stdin, gpu_params)? } else { self.run_asm(stdin, hints_stream, gpu_params)? @@ -169,7 +177,52 @@ impl ZiskProve { "{}", "--- PROVE SUMMARY ------------------------".bright_green().bold() ); - if let Some(proof_id) = result.proof.id { + + match result.proof { + ZiskProof::VadcopFinal(ref proof) | ZiskProof::VadcopFinalCompressed(ref proof) => { + let compressed = matches!(result.proof, ZiskProof::VadcopFinalCompressed(_)); + let vadcop_final_proof = VadcopFinalProof::new( + proof.clone(), + result.publics.bytes_u64(&vk), + compressed, + ); + vadcop_final_proof + .save(self.output_dir.join("vadcop_final_proof.bin")) + .map_err(|e| { + anyhow::anyhow!( + "Failed to save VadcopFinalProof to output dir {:?}: {}", + self.output_dir.join("vadcop_final_proof.bin").display(), + e + ) + })?; + } + ZiskProof::Plonk(ref proof) | ZiskProof::Fflonk(ref proof) => { + let protocol_id = match result.proof { + ZiskProof::Plonk(_) => SnarkProtocol::Plonk.protocol_id(), + ZiskProof::Fflonk(_) => SnarkProtocol::Fflonk.protocol_id(), + _ => unreachable!(), + }; + let proving_key = get_proving_key(self.proving_key.as_ref()); + let vadcop_verkey = get_vadcop_final_proof_vkey(&proving_key, false)?; + + let snark_proof = SnarkProof { + proof_bytes: proof.clone(), + public_bytes: result.publics.bytes_solidity(&vk, &vadcop_verkey), + public_snark_bytes: result.publics.hash_solidity(&vk, &vadcop_verkey), + protocol_id, + }; + snark_proof.save(self.output_dir.join("snark_proof.bin")).map_err(|e| { + anyhow::anyhow!( + "Failed to save SnarkProof to output dir {:?}: {}", + self.output_dir.join("snark_proof.bin").display(), + e + ) + })?; + } + ZiskProof::Null() => {} + } + + if let Some(proof_id) = &result.proof_id { tracing::info!(" Proof ID: {}", proof_id); } tracing::info!(" ► Statistics"); @@ -182,63 +235,84 @@ impl ZiskProve { pub fn run_emu( &mut self, stdin: ZiskStdin, - gpu_params: ParamsGPU, - ) -> Result<(ZiskProveResult, i32)> { + gpu_params: Option, + ) -> Result<(ZiskProgramVK, ZiskProveResult, i32)> { let prover = ProverClient::builder() - .emu() - .prove() .aggregation(self.aggregation) - .compressed(self.compressed) - .rma(self.rma) .proving_key_path_opt(self.proving_key.clone()) - .elf_path(self.elf.clone()) .verbose(self.verbose) .shared_tables(self.shared_tables) - .save_proofs(self.save_proofs) - .output_dir(self.output_dir.clone()) - .verify_proofs(self.verify_proofs) - .minimal_memory(self.minimal_memory) .gpu(gpu_params) .print_command_info() .build()?; - let result = prover.prove(stdin, None)?; + let elf_bin = fs::read(&self.elf) + .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; + let elf = ElfBinaryOwned::new( + elf_bin, + self.elf.file_stem().unwrap().to_str().unwrap().to_string(), + false, + ); + let vk = prover.setup(&elf)?; + + let proof_options = ProofOpts { + aggregation: self.aggregation, + rma: self.rma, + minimal_memory: self.minimal_memory, + verify_proofs: self.verify_proofs, + save_proofs: self.save_proofs, + output_dir_path: Some(self.output_dir.clone()), + }; + + let result = prover.prove(stdin).with_proof_options(proof_options).run()?; let world_rank = prover.world_rank(); - Ok((result, world_rank)) + Ok((vk, result, world_rank)) } pub fn run_asm( &mut self, stdin: ZiskStdin, hints_stream: Option, - gpu_params: ParamsGPU, - ) -> Result<(ZiskProveResult, i32)> { + gpu_params: Option, + ) -> Result<(ZiskProgramVK, ZiskProveResult, i32)> { let prover = ProverClient::builder() - .asm() - .prove() .aggregation(self.aggregation) - .compressed(self.compressed) - .rma(self.rma) + .asm() .proving_key_path_opt(self.proving_key.clone()) - .elf_path(self.elf.clone()) .verbose(self.verbose) .shared_tables(self.shared_tables) .asm_path_opt(self.asm.clone()) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) - .save_proofs(self.save_proofs) - .output_dir(self.output_dir.clone()) - .verify_proofs(self.verify_proofs) - .minimal_memory(self.minimal_memory) .gpu(gpu_params) - .with_hints(hints_stream.is_some()) .print_command_info() .build()?; - let result = prover.prove(stdin, hints_stream)?; + let elf_bin = fs::read(&self.elf) + .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; + let elf = ElfBinaryOwned::new( + elf_bin, + self.elf.file_stem().unwrap().to_str().unwrap().to_string(), + hints_stream.is_some(), + ); + let vk = prover.setup(&elf)?; + + let proof_options = ProofOpts { + aggregation: self.aggregation, + rma: self.rma, + minimal_memory: self.minimal_memory, + verify_proofs: self.verify_proofs, + save_proofs: self.save_proofs, + output_dir_path: Some(self.output_dir.clone()), + }; + + if let Some(hints_stream) = hints_stream { + prover.set_hints_stream(hints_stream)?; + } + let result = prover.prove(stdin).with_proof_options(proof_options).run()?; let world_rank = prover.world_rank(); - Ok((result, world_rank)) + Ok((vk, result, world_rank)) } } diff --git a/cli/src/commands/prove_snark.rs b/cli/src/commands/prove_snark.rs index 439be89cf..1fa861297 100644 --- a/cli/src/commands/prove_snark.rs +++ b/cli/src/commands/prove_snark.rs @@ -1,15 +1,13 @@ // extern crate env_logger; use anyhow::Result; -use bytemuck::cast_slice; use clap::Parser; use colored::Colorize; use fields::Goldilocks; -use std::io::Read; use std::path::PathBuf; use crate::ux::print_banner; use proofman::SnarkWrapper; -use std::fs::File; +use proofman_util::VadcopFinalProof; #[derive(Parser)] #[command(version, about, long_about = None)] @@ -29,9 +27,6 @@ pub struct ZiskProveSnark { /// Verbosity (-v, -vv) #[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity level")] pub verbose: u8, // Using u8 to hold the number of `-v` - - #[clap(short = 'j', long, default_value_t = false)] - pub save_json: bool, } impl ZiskProveSnark { @@ -41,16 +36,22 @@ impl ZiskProveSnark { print_banner(); - let mut proof_file = File::open(&self.proof)?; - let mut proof_u64 = Vec::new(); - proof_file.read_to_end(&mut proof_u64)?; - let proof = cast_slice::(&proof_u64); + let proof = VadcopFinalProof::load(&self.proof).map_err(|e| { + anyhow::anyhow!("Failed to load VadcopFinalProof from file {}: {}", self.proof, e) + })?; let snark_wrapper: SnarkWrapper = SnarkWrapper::new(&self.proving_key_snark, self.verbose.into())?; let snark_proof = - snark_wrapper.generate_final_snark_proof(proof, &self.output_dir, self.save_json)?; + snark_wrapper.generate_final_snark_proof(&proof, Some(self.output_dir.clone()))?; + snark_proof.save(self.output_dir.join("final_snark_proof.bin")).map_err(|e| { + anyhow::anyhow!( + "Failed to save final SNARK proof to output dir {}: {}", + self.output_dir.join("final_snark_proof.bin").display(), + e + ) + })?; println!( "{} Final SNARK proof generated. Proof: {:?}, Publics: {:?}", "Info:".bright_blue().bold(), diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index 1cd51a0d4..e8855972d 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -2,13 +2,14 @@ use anyhow::Result; use clap::Parser; use std::path::PathBuf; +use crate::{commands::get_proving_key, ux::print_banner}; use colored::Colorize; +use fields::Goldilocks; use proofman_common::initialize_logger; - -use crate::{ - commands::{get_proving_key, get_zisk_path}, - ux::print_banner, -}; +use rom_setup::gen_assembly; +use rom_setup::rom_merkle_setup; +use std::fs; +use zisk_common::ElfBinaryOwned; #[derive(Parser)] #[command(version, about, long_about = None)] @@ -16,7 +17,7 @@ use crate::{ pub struct ZiskRomSetup { /// ELF file path #[clap(short = 'e', long)] - pub elf: PathBuf, + pub elf_path: PathBuf, /// Setup folder path #[clap(short = 'k', long)] @@ -51,15 +52,24 @@ impl ZiskRomSetup { print_banner(); let proving_key = get_proving_key(self.proving_key.as_ref()); - let zisk_path = get_zisk_path(self.zisk_path.as_ref()); - rom_setup::rom_full_setup( - &self.elf, - &proving_key, - &zisk_path, - &self.output_dir, + tracing::info!("Computing setup for ROM {}", self.elf_path.display()); + + tracing::info!("Computing merkle root"); + let elf_bin = fs::read(&self.elf_path).map_err(|e| { + anyhow::anyhow!("Error reading ELF file {}: {}", self.elf_path.display(), e) + })?; + let elf = ElfBinaryOwned::new( + elf_bin, + self.elf_path.file_stem().unwrap().to_str().unwrap().to_string(), self.hints, - self.verbose, - ) + ); + rom_merkle_setup::(&elf, &self.output_dir, &proving_key)?; + + gen_assembly(&self.elf_path, &self.zisk_path, &self.output_dir, self.verbose, self.hints)?; + + println!(); + tracing::info!("{}", "ROM setup successfully completed".bright_green().bold()); + Ok(()) } } diff --git a/cli/src/commands/rom_vkey.rs b/cli/src/commands/rom_vkey.rs deleted file mode 100644 index 39a3b302f..000000000 --- a/cli/src/commands/rom_vkey.rs +++ /dev/null @@ -1,48 +0,0 @@ -use anyhow::Result; -use clap::Parser; -use std::path::PathBuf; - -use colored::Colorize; -use proofman_common::initialize_logger; - -use crate::{commands::get_proving_key, ux::print_banner}; - -#[derive(Parser)] -#[command(version, about, long_about = None)] -#[command(propagate_version = true)] -pub struct ZiskRomVkey { - /// ELF file path - #[clap(short = 'e', long)] - pub elf: PathBuf, - - /// Setup folder path - #[clap(short = 'k', long)] - pub proving_key: Option, - - /// VKey file - #[clap(short = 'o', long)] - pub vkey_file: Option, - - #[clap(short = 'v', long, default_value_t = false)] - pub verbose: bool, -} - -impl ZiskRomVkey { - pub fn run(&self) -> Result<()> { - initialize_logger(proofman_common::VerboseMode::Info, None); - - tracing::info!( - "{}", - format!("{} Rom VKey", format!("{: >12}", "Command").bright_green().bold()) - ); - tracing::info!(""); - - print_banner(); - - let proving_key = get_proving_key(self.proving_key.as_ref()); - - rom_setup::rom_vkey(&self.elf, &self.vkey_file, &proving_key)?; - - Ok(()) - } -} diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index a31c90dbb..92eecd9ca 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -6,6 +6,7 @@ use std::{collections::HashMap, fs, path::PathBuf, time::Instant}; use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; +use zisk_common::ElfBinaryOwned; use zisk_common::{ExecutorStatsHandle, Stats}; use zisk_pil::*; use zisk_sdk::ProverClient; @@ -154,13 +155,26 @@ impl ZiskStats { .emu() .witness() .proving_key_path_opt(self.proving_key.clone()) - .elf_path(self.elf.clone()) .verbose(self.verbose) .shared_tables(self.shared_tables) .print_command_info() .build()?; - prover.stats(stdin, None, self.debug.clone(), self.mpi_node.map(|n| n as u32)) + let elf_bin = fs::read(&self.elf) + .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; + let elf = ElfBinaryOwned::new( + elf_bin, + self.elf.file_stem().unwrap().to_str().unwrap().to_string(), + false, + ); + prover.setup(&elf)?; + + prover.stats( + stdin, + self.debug.clone(), + self.minimal_memory, + self.mpi_node.map(|n| n as u32), + ) } pub fn run_asm( @@ -172,18 +186,28 @@ impl ZiskStats { .asm() .witness() .proving_key_path_opt(self.proving_key.clone()) - .elf_path(self.elf.clone()) .verbose(self.verbose) .shared_tables(self.shared_tables) .asm_path_opt(self.asm.clone()) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) - .with_hints(hints_stream.is_some()) .print_command_info() .build()?; + let elf_bin = fs::read(&self.elf) + .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; + let elf = ElfBinaryOwned::new( + elf_bin, + self.elf.file_stem().unwrap().to_str().unwrap().to_string(), + hints_stream.is_some(), + ); + prover.setup(&elf)?; + + if let Some(hints_stream) = hints_stream { + prover.set_hints_stream(hints_stream)?; + } let mpi_node = self.mpi_node.map(|n| n as u32); - prover.stats(stdin, hints_stream, self.debug.clone(), mpi_node) + prover.stats(stdin, self.debug.clone(), self.minimal_memory, mpi_node) } /// Prints stats individually and grouped, with aligned columns. diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 2aa0f5afe..2cef8d18f 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -3,10 +3,14 @@ use anyhow::Result; use clap::Parser; use colored::Colorize; +use std::fs; use std::path::PathBuf; use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; +use zisk_common::ElfBinaryOwned; +#[cfg(feature = "stats")] +use zisk_common::ExecutorStatsEvent; use zisk_sdk::{ProverClient, ZiskVerifyConstraintsResult}; #[derive(Parser)] @@ -135,13 +139,21 @@ impl ZiskVerifyConstraints { .emu() .verify_constraints() .proving_key_path_opt(self.proving_key.clone()) - .elf_path(self.elf.clone()) .verbose(self.verbose) .shared_tables(self.shared_tables) .print_command_info() .build()?; - prover.verify_constraints_debug(stdin, None, self.debug.clone()) + let elf_bin = fs::read(&self.elf) + .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; + let elf = ElfBinaryOwned::new( + elf_bin, + self.elf.file_stem().unwrap().to_str().unwrap().to_string(), + false, + ); + prover.setup(&elf)?; + + prover.verify_constraints_debug(stdin, self.debug.clone()) } pub fn run_asm( @@ -153,16 +165,26 @@ impl ZiskVerifyConstraints { .asm() .verify_constraints() .proving_key_path_opt(self.proving_key.clone()) - .elf_path(self.elf.clone()) .verbose(self.verbose) .shared_tables(self.shared_tables) .asm_path_opt(self.asm.clone()) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) - .with_hints(hints_stream.is_some()) .print_command_info() .build()?; - prover.verify_constraints_debug(stdin, hints_stream, self.debug.clone()) + let elf_bin = fs::read(&self.elf) + .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; + let elf = ElfBinaryOwned::new( + elf_bin, + self.elf.file_stem().unwrap().to_str().unwrap().to_string(), + hints_stream.is_some(), + ); + prover.setup(&elf)?; + + if let Some(hints_stream) = hints_stream { + prover.set_hints_stream(hints_stream)?; + } + prover.verify_constraints_debug(stdin, self.debug.clone()) } } diff --git a/cli/src/commands/verify_snark.rs b/cli/src/commands/verify_snark.rs new file mode 100644 index 000000000..ac0b62d8f --- /dev/null +++ b/cli/src/commands/verify_snark.rs @@ -0,0 +1,39 @@ +// extern crate env_logger; +use anyhow::Result; +use clap::Parser; +use colored::Colorize; +use proofman::{verify_snark_proof, SnarkProof}; +use proofman_common::initialize_logger; +use std::path::PathBuf; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +#[command(propagate_version = true)] +pub struct ZiskVerifySnark { + #[clap(short = 'p', long)] + pub proof: String, + + #[clap(short = 'k', long)] + pub verkey: PathBuf, + + /// Verbosity (-v, -vv) + #[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity level")] + pub verbose: u8, // Using u8 to hold the number of `-v` +} + +impl ZiskVerifySnark { + pub fn run(&self) -> Result<()> { + println!("{} ZiskVerifySnark", format!("{: >12}", "Command").bright_green().bold()); + println!(); + + initialize_logger(self.verbose.into(), None); + + let proof = SnarkProof::load(&self.proof).map_err(|e| { + anyhow::anyhow!("Failed to load SnarkProof from file {}: {}", self.proof, e) + })?; + + verify_snark_proof(&proof, &self.verkey).map_err(|e| { + anyhow::anyhow!("SNARK proof verification failed for proof {}: {}", self.proof, e) + }) + } +} diff --git a/cli/src/commands/verify_stark.rs b/cli/src/commands/verify_stark.rs index aa26261c4..b8f1c0bbc 100644 --- a/cli/src/commands/verify_stark.rs +++ b/cli/src/commands/verify_stark.rs @@ -2,10 +2,10 @@ use anyhow::Result; use clap::Parser; use colored::Colorize; use proofman_common::initialize_logger; +use proofman_util::VadcopFinalProof; use std::fs; -use zisk_verifier::{verify_zisk_proof, verify_zisk_proof_compressed}; - use zisk_build::ZISK_VERSION_MESSAGE; +use zisk_verifier::verify_zisk_proof; use super::get_default_verkey; @@ -22,9 +22,6 @@ pub struct ZiskVerify { #[clap(short = 'k', long)] pub vk: Option, - - #[clap(short = 'c', long, default_value_t = false)] - pub compressed: bool, } impl ZiskVerify { @@ -39,15 +36,13 @@ impl ZiskVerify { let start = std::time::Instant::now(); - let proof = fs::read(&self.proof)?; + let proof = VadcopFinalProof::load(&self.proof).map_err(|e| { + anyhow::anyhow!("Error loading VADCoP final proof from {}: {}", &self.proof, e) + })?; let vk = &self.get_verkey(); - let result = if self.compressed { - verify_zisk_proof_compressed(&proof, vk) - } else { - verify_zisk_proof(&proof, vk) - }; + let result = verify_zisk_proof(&proof, vk); let elapsed = start.elapsed(); diff --git a/common/Cargo.toml b/common/Cargo.toml index 7efce79ae..3829ed17e 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -15,6 +15,7 @@ zisk-pil = { workspace = true } witness = { workspace = true } proofman-common = { workspace = true } +proofman-util = { workspace = true } proofman = { workspace = true } fields = { workspace = true } tracing = { workspace = true } @@ -23,7 +24,6 @@ serde = { workspace = true } serde_json = { workspace = true } anyhow = { workspace = true } thiserror = { workspace = true } -bytemuck = { workspace = true } zstd = { workspace = true } libc = "0.2" diff --git a/common/src/io/stdin/file.rs b/common/src/io/stdin/file.rs index 56e78a120..366403a6f 100644 --- a/common/src/io/stdin/file.rs +++ b/common/src/io/stdin/file.rs @@ -5,6 +5,7 @@ use serde::Serialize; use std::fs::{self, File}; use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; +use std::sync::Mutex; use crate::io::ZiskIO; @@ -14,7 +15,7 @@ pub struct ZiskFileStdin { path: PathBuf, /// Buffered reader for the file. - reader: BufReader, + reader: Mutex>, } impl ZiskFileStdin { @@ -29,30 +30,32 @@ impl ZiskFileStdin { } let file = File::open(&path_buf)?; - Ok(ZiskFileStdin { path: path_buf, reader: BufReader::new(file) }) + Ok(ZiskFileStdin { path: path_buf, reader: Mutex::new(BufReader::new(file)) }) } } impl ZiskIO for ZiskFileStdin { - fn read(&mut self) -> Vec { + fn read(&self) -> Vec { fs::read(&self.path).expect("Could not read inputs file") } - fn read_slice(&mut self, slice: &mut [u8]) { - self.reader.read_exact(slice).expect("Failed to read slice"); + fn read_slice(&self, slice: &mut [u8]) { + let mut reader = self.reader.lock().unwrap(); + reader.read_exact(slice).expect("Failed to read slice"); } - fn read_into(&mut self, buffer: &mut [u8]) { - self.reader.read_exact(buffer).expect("Failed to read into buffer"); + fn read_into(&self, buffer: &mut [u8]) { + let mut reader = self.reader.lock().unwrap(); + reader.read_exact(buffer).expect("Failed to read into buffer"); } - fn write(&mut self, _data: &T) { + fn write(&self, _data: &T) { // This is a read-only stdin implementation // Writing is not supported for file-based stdin panic!("Write operations are not supported for FileStdin"); } - fn write_slice(&mut self, _data: &[u8]) { + fn write_slice(&self, _data: &[u8]) { // This is a read-only stdin implementation // Writing is not supported for file-based stdin panic!("Write operations are not supported for FileStdin"); diff --git a/common/src/io/stdin/memory.rs b/common/src/io/stdin/memory.rs index 92faa840c..f14d73930 100644 --- a/common/src/io/stdin/memory.rs +++ b/common/src/io/stdin/memory.rs @@ -1,19 +1,20 @@ use serde::Serialize; use std::io::{Cursor, Read}; +use std::sync::Mutex; use crate::io::ZiskIO; /// A memory-based implementation of ZiskStdin that reads from in-memory data. pub struct ZiskMemoryStdin { - data: Vec, - cursor: Cursor>, + data: Mutex>, + cursor: Mutex>>, } impl ZiskMemoryStdin { /// Create a new ZiskMemoryStdin from a vector of bytes. pub fn new(data: Vec) -> Self { - let cursor = Cursor::new(data.clone()); - ZiskMemoryStdin { data, cursor } + let cursor = Mutex::new(Cursor::new(data.clone())); + ZiskMemoryStdin { data: Mutex::new(data), cursor } } /// Create a new ZiskMemoryStdin from a string (UTF-8 encoded). @@ -28,28 +29,32 @@ impl ZiskMemoryStdin { } impl ZiskIO for ZiskMemoryStdin { - fn read(&mut self) -> Vec { + fn read(&self) -> Vec { // Return all the data - self.data.clone() + self.data.lock().unwrap().clone() } - fn read_slice(&mut self, slice: &mut [u8]) { - self.cursor.read_exact(slice).expect("Failed to read slice from memory"); + fn read_slice(&self, slice: &mut [u8]) { + let mut cursor = self.cursor.lock().unwrap(); + cursor.read_exact(slice).expect("Failed to read slice from memory"); } - fn read_into(&mut self, buffer: &mut [u8]) { - self.cursor.read_exact(buffer).expect("Failed to read into buffer from memory"); + fn read_into(&self, buffer: &mut [u8]) { + let mut cursor = self.cursor.lock().unwrap(); + cursor.read_exact(buffer).expect("Failed to read into buffer from memory"); } - fn write(&mut self, data: &T) { + fn write(&self, data: &T) { let mut tmp = Vec::new(); bincode::serialize_into(&mut tmp, data).expect("Failed to serialize data into memory"); - self.data.extend_from_slice(&tmp); - self.cursor.get_mut().extend_from_slice(&tmp); + self.data.lock().unwrap().extend_from_slice(&tmp); + let mut cursor = self.cursor.lock().unwrap(); + cursor.get_mut().extend_from_slice(&tmp); } - fn write_slice(&mut self, data: &[u8]) { - self.data.extend_from_slice(data); - self.cursor.get_mut().extend_from_slice(data); + fn write_slice(&self, data: &[u8]) { + let mut cursor = self.cursor.lock().unwrap(); + self.data.lock().unwrap().extend_from_slice(data); + cursor.get_mut().extend_from_slice(data); } } diff --git a/common/src/io/stdin/null.rs b/common/src/io/stdin/null.rs index 80b262ded..b770f3298 100644 --- a/common/src/io/stdin/null.rs +++ b/common/src/io/stdin/null.rs @@ -6,15 +6,15 @@ use serde::Serialize; pub struct ZiskNullStdin; impl ZiskIO for ZiskNullStdin { - fn read(&mut self) -> Vec { + fn read(&self) -> Vec { Vec::new() } - fn read_slice(&mut self, _slice: &mut [u8]) {} - fn read_into(&mut self, _buffer: &mut [u8]) {} - fn write(&mut self, _data: &T) { + fn read_slice(&self, _slice: &mut [u8]) {} + fn read_into(&self, _buffer: &mut [u8]) {} + fn write(&self, _data: &T) { warn!("NullStdin does not support writing"); } - fn write_slice(&mut self, _data: &[u8]) { + fn write_slice(&self, _data: &[u8]) { warn!("NullStdin does not support writing"); } } diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index 28c632ec7..7f5d77368 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -1,24 +1,24 @@ use crate::io::{ZiskFileStdin, ZiskMemoryStdin, ZiskNullStdin}; +use anyhow::Result; use serde::Serialize; use std::path::Path; - -use anyhow::Result; +use std::sync::Arc; pub trait ZiskIO: Send + Sync { /// Read a value from the buffer. - fn read(&mut self) -> Vec; + fn read(&self) -> Vec; /// Read a slice of bytes from the buffer. - fn read_slice(&mut self, slice: &mut [u8]); + fn read_slice(&self, slice: &mut [u8]); /// Read bytes into the provided buffer. - fn read_into(&mut self, buffer: &mut [u8]); + fn read_into(&self, buffer: &mut [u8]); /// Write a serialized value to the buffer. - fn write(&mut self, data: &T); + fn write(&self, data: &T); /// Write a slice of bytes to the buffer. - fn write_slice(&mut self, data: &[u8]); + fn write_slice(&self, data: &[u8]); } pub enum ZiskIOVariant { @@ -28,7 +28,7 @@ pub enum ZiskIOVariant { } impl ZiskIO for ZiskIOVariant { - fn read(&mut self) -> Vec { + fn read(&self) -> Vec { match self { ZiskIOVariant::File(file_stdin) => file_stdin.read(), ZiskIOVariant::Null(null_stdin) => null_stdin.read(), @@ -36,7 +36,7 @@ impl ZiskIO for ZiskIOVariant { } } - fn read_slice(&mut self, slice: &mut [u8]) { + fn read_slice(&self, slice: &mut [u8]) { match self { ZiskIOVariant::File(file_stdin) => file_stdin.read_slice(slice), ZiskIOVariant::Null(null_stdin) => null_stdin.read_slice(slice), @@ -44,7 +44,7 @@ impl ZiskIO for ZiskIOVariant { } } - fn read_into(&mut self, buffer: &mut [u8]) { + fn read_into(&self, buffer: &mut [u8]) { match self { ZiskIOVariant::File(file_stdin) => file_stdin.read_into(buffer), ZiskIOVariant::Null(null_stdin) => null_stdin.read_into(buffer), @@ -52,7 +52,7 @@ impl ZiskIO for ZiskIOVariant { } } - fn write(&mut self, data: &T) { + fn write(&self, data: &T) { match self { ZiskIOVariant::File(file_stdin) => file_stdin.write(data), ZiskIOVariant::Null(null_stdin) => null_stdin.write(data), @@ -60,7 +60,7 @@ impl ZiskIO for ZiskIOVariant { } } - fn write_slice(&mut self, data: &[u8]) { + fn write_slice(&self, data: &[u8]) { match self { ZiskIOVariant::File(file_stdin) => file_stdin.write_slice(data), ZiskIOVariant::Null(null_stdin) => null_stdin.write_slice(data), @@ -69,28 +69,29 @@ impl ZiskIO for ZiskIOVariant { } } +#[derive(Clone)] pub struct ZiskStdin { - io: ZiskIOVariant, + io: Arc, } impl ZiskIO for ZiskStdin { - fn read(&mut self) -> Vec { + fn read(&self) -> Vec { self.io.read() } - fn read_slice(&mut self, slice: &mut [u8]) { + fn read_slice(&self, slice: &mut [u8]) { self.io.read_slice(slice) } - fn read_into(&mut self, buffer: &mut [u8]) { + fn read_into(&self, buffer: &mut [u8]) { self.io.read_into(buffer) } - fn write(&mut self, data: &T) { + fn write(&self, data: &T) { self.io.write(data) } - fn write_slice(&mut self, data: &[u8]) { + fn write_slice(&self, data: &[u8]) { self.io.write_slice(data) } } @@ -104,21 +105,21 @@ impl Default for ZiskStdin { impl ZiskStdin { /// Create new memory-based stdin pub fn new() -> Self { - Self { io: ZiskIOVariant::Memory(ZiskMemoryStdin::new(Vec::new())) } + Self { io: Arc::new(ZiskIOVariant::Memory(ZiskMemoryStdin::new(Vec::new()))) } } /// Create a null stdin (no input) pub fn null() -> Self { - Self { io: ZiskIOVariant::Null(ZiskNullStdin) } + Self { io: Arc::new(ZiskIOVariant::Null(ZiskNullStdin)) } } /// Create a file-based stdin pub fn from_file>(path: P) -> Result { - Ok(Self { io: ZiskIOVariant::File(ZiskFileStdin::new(path)?) }) + Ok(Self { io: Arc::new(ZiskIOVariant::File(ZiskFileStdin::new(path)?)) }) } pub fn from_vec(data: Vec) -> Self { - Self { io: ZiskIOVariant::Memory(ZiskMemoryStdin::new(data)) } + Self { io: Arc::new(ZiskIOVariant::Memory(ZiskMemoryStdin::new(data))) } } /// Create a ZiskStdin from a URI string diff --git a/common/src/proof.rs b/common/src/proof.rs index e66a39808..d14123260 100644 --- a/common/src/proof.rs +++ b/common/src/proof.rs @@ -1,7 +1,7 @@ use anyhow::Result; +use proofman_util::VadcopFinalProof; use std::io::{Cursor, Write}; use std::{fs, path::PathBuf}; -use tracing::info; use zstd::Encoder; /// Saves a proof data to disk. @@ -13,7 +13,7 @@ use zstd::Encoder; /// /// * `id` - A unique identifier for the proof /// * `proof_folder` - The folder where proofs will be saved -/// * `proof_data` - The proof data as a vector of u64 values +/// * `proof` - The proof data as an optional VadcopFinalProof /// * `with_zip` - Whether to also save a compressed version of the proof /// /// # Returns @@ -22,48 +22,16 @@ use zstd::Encoder; pub fn save_proof( id: &str, proof_folder: PathBuf, - proof_data: &[u64], - with_zip: bool, + proof: &VadcopFinalProof, + _with_zip: bool, ) -> Result<()> { // Ensure the proofs directory exists fs::create_dir_all(&proof_folder)?; // Generate unique filename to avoid overwriting existing files - let mut raw_path = proof_folder.join(format!("proof_{}.fri", id)); - let mut zip_path = raw_path.with_extension("fri.compressed"); - let mut counter = 2; + let raw_path = proof_folder.join(format!("proof_{}.fri", id)); - while fs::exists(&raw_path)? || (with_zip && fs::exists(&zip_path)?) { - raw_path = proof_folder.join(format!("proof_{}_{}.fri", id, counter)); - zip_path = raw_path.with_extension("fri.compressed"); - counter += 1; - } - - // Convert Vec to bytes safely - let proof_bytes = bytemuck::cast_slice::(proof_data); - - // Write raw proof file - fs::write(&raw_path, proof_bytes)?; - - // Calculate compression statistics - let raw_size = proof_bytes.len(); - - info!("[PostJob] Saving proof:"); - info!("[PostJob] Raw: {} ({} bytes)", raw_path.display(), raw_size); - - if with_zip { - // Compress proof data and write to file - let zip_size = save_zip_proof(proof_bytes, &zip_path, 1)?; - - let ratio = zip_size as f64 / raw_size as f64; - - info!( - "[PostJob] Compressed: {} ({} bytes, ratio: {:.2}x)", - zip_path.display(), - zip_size, - ratio - ); - } + proof.save(&raw_path).map_err(|e| anyhow::anyhow!("Failed to save proof: {}", e))?; Ok(()) } diff --git a/common/src/types.rs b/common/src/types.rs index 89aa18db9..5ef014fc0 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -91,3 +91,51 @@ pub struct Stats { /// Number of chunks pub num_chunks: usize, } + +pub trait ElfBinaryLike { + fn elf(&self) -> &[u8]; + fn name(&self) -> &str; + fn with_hints(&self) -> bool; +} + +pub struct ElfBinaryOwned { + pub elf: Vec, + pub name: String, + pub with_hints: bool, +} + +impl ElfBinaryOwned { + pub const fn new(elf: Vec, name: String, with_hints: bool) -> Self { + Self { elf, name, with_hints } + } +} + +impl ElfBinaryLike for ElfBinaryOwned { + fn elf(&self) -> &[u8] { + &self.elf + } + fn name(&self) -> &str { + &self.name + } + fn with_hints(&self) -> bool { + self.with_hints + } +} + +pub struct ElfBinary { + pub elf: &'static [u8], + pub name: &'static str, + pub with_hints: bool, +} + +impl ElfBinaryLike for ElfBinary { + fn elf(&self) -> &[u8] { + self.elf + } + fn name(&self) -> &str { + self.name + } + fn with_hints(&self) -> bool { + self.with_hints + } +} diff --git a/core/src/bin/riscv2zisk.rs b/core/src/bin/riscv2zisk.rs index 75a4906ff..50626db55 100644 --- a/core/src/bin/riscv2zisk.rs +++ b/core/src/bin/riscv2zisk.rs @@ -52,8 +52,14 @@ fn main() { } }; + // Read ELF file bytes + let elf = std::fs::read(elf_file).unwrap_or_else(|e| { + eprintln!("Error reading ELF file: {e}"); + process::exit(1); + }); + // Create an instance of the program converter - let rv2zk = Riscv2zisk::new(elf_file); + let rv2zk = Riscv2zisk::new(&elf); // Convert program if let Err(e) = rv2zk.runfile(asm_file.unwrap(), generation_method, true, true, false) { diff --git a/core/src/elf2rom.rs b/core/src/elf2rom.rs index 7a8e02a8f..6076b5009 100644 --- a/core/src/elf2rom.rs +++ b/core/src/elf2rom.rs @@ -2,9 +2,7 @@ use crate::{ add_end_and_lib, - elf_extraction::{ - collect_elf_payload, collect_elf_payload_from_bytes, merge_adjacent_ro_sections, ElfPayload, - }, + elf_extraction::{collect_elf_payload_from_bytes, merge_adjacent_ro_sections, ElfPayload}, riscv2zisk_context::{add_entry_exit_jmp, add_zisk_code, add_zisk_init_data}, AsmGenerationMethod, RoData, ZiskInst, ZiskRom, ZiskRom2Asm, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, }; @@ -12,13 +10,13 @@ use rayon::prelude::*; use std::{error::Error, path::Path}; /// Executes the ROM transpilation process: from ELF to Zisk -pub fn elf2rom(elf_file: &Path) -> Result> { +pub fn elf2rom(elf: &[u8]) -> Result> { // Load the embedded float library const FLOAT_LIB_DATA: &[u8] = include_bytes!("../../lib-float/c/lib/ziskfloat.elf"); // Extract all relevant sections from the ELF file let payloads: Vec = - vec![collect_elf_payload_from_bytes(FLOAT_LIB_DATA)?, collect_elf_payload(elf_file)?]; + vec![collect_elf_payload_from_bytes(FLOAT_LIB_DATA)?, collect_elf_payload_from_bytes(elf)?]; // Create an empty ZiskRom instance let mut rom: ZiskRom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() }; @@ -216,15 +214,16 @@ fn optimize_instruction_lookup(rom: &mut ZiskRom) -> Result<(), Box> /// Executes the ELF file data transpilation process into a Zisk ROM, and saves the result into a /// file. The file format can be JSON, PIL-based or binary. pub fn elf2romfile( - elf_file: &Path, + elf: &[u8], asm_file: &Path, generation_method: AsmGenerationMethod, log_output: bool, comments: bool, hints: bool, ) -> Result<(), Box> { - let rom = elf2rom(elf_file)?; + let rom = elf2rom(elf)?; ZiskRom2Asm::save_to_asm_file(&rom, asm_file, generation_method, log_output, comments, hints); + Ok(()) } diff --git a/core/src/elf_extraction.rs b/core/src/elf_extraction.rs index 5a9db724c..d9f202413 100644 --- a/core/src/elf_extraction.rs +++ b/core/src/elf_extraction.rs @@ -5,7 +5,7 @@ use elf::{ endian::AnyEndian, ElfBytes, }; -use std::{error::Error, fs, path::Path}; +use std::error::Error; use crate::{is_elf_file, RAM_ADDR, RAM_SIZE}; @@ -32,15 +32,6 @@ pub struct ElfPayload { pub ro: Vec, } -/// Extracts the relevant sections from the ELF file for `ZiskRom` -pub fn collect_elf_payload(elf_path: &Path) -> Result> { - // Read the ELF file - let file_data = - fs::read(elf_path).map_err(|_| format!("Error reading ELF file={}", elf_path.display()))?; - - collect_elf_payload_from_bytes(&file_data) -} - /// Extracts the relevant sections from ELF file bytes for `ZiskRom` pub fn collect_elf_payload_from_bytes(file_data: &[u8]) -> Result> { // Validate it's an ELF file diff --git a/core/src/riscv2zisk.rs b/core/src/riscv2zisk.rs index 8a0aebe1b..57cd06227 100644 --- a/core/src/riscv2zisk.rs +++ b/core/src/riscv2zisk.rs @@ -62,15 +62,15 @@ pub enum AsmGenerationMethod { AsmChunkPlayerMemReadsCollectMain, } /// RISCV-to-ZisK struct containing the input ELF RISCV file name and the output ZISK ASM file name -pub struct Riscv2zisk { - /// ELF RISC-V file name (input) - pub elf_file: PathBuf, +pub struct Riscv2zisk<'a> { + /// ELF RISC-V file bytes (input) + pub elf: &'a [u8], } -impl Riscv2zisk { - /// Creates a new Riscv2zisk struct with the provided input and output file names - pub fn new>(elf_file: P) -> Riscv2zisk { - Riscv2zisk { elf_file: elf_file.into() } +impl<'a> Riscv2zisk<'a> { + /// Creates a new Riscv2zisk struct with the provided ELF bytes + pub fn new(elf: &'a [u8]) -> Riscv2zisk<'a> { + Riscv2zisk { elf } } /// Executes the file conversion process by calling elf2romfile() @@ -82,19 +82,12 @@ impl Riscv2zisk { comments: bool, hints: bool, ) -> Result<(), Box> { - elf2romfile( - &self.elf_file, - &asm_file.into(), - generation_method, - log_output, - comments, - hints, - ) - .map_err(|e| format!("Error converting elf to assembly: {e}").into()) + elf2romfile(self.elf, &asm_file.into(), generation_method, log_output, comments, hints) + .map_err(|e| format!("Error converting elf to assembly: {e}").into()) } /// Executes the file conversion process by calling elf2rom() pub fn run(&self) -> Result> { - elf2rom(&self.elf_file) + elf2rom(self.elf) } } diff --git a/distributed/crates/common/Cargo.toml b/distributed/crates/common/Cargo.toml index cb6ca3f3f..2189ed914 100644 --- a/distributed/crates/common/Cargo.toml +++ b/distributed/crates/common/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] proofman = { workspace = true } proofman-common = { workspace = true } +proofman-util = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } tracing-appender = { workspace = true } diff --git a/distributed/crates/coordinator/Cargo.toml b/distributed/crates/coordinator/Cargo.toml index ee7a02990..438aeebbd 100644 --- a/distributed/crates/coordinator/Cargo.toml +++ b/distributed/crates/coordinator/Cargo.toml @@ -17,6 +17,7 @@ zisk-common = { workspace = true } cargo-zisk = { workspace = true } proofman = { workspace = true } +proofman-util = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index b56b185ea..0bdd1fa0c 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -62,6 +62,8 @@ use zisk_distributed_common::{ WorkerState, WorkersListDto, }; +use proofman_util::VadcopFinalProof; + /// Trait for sending messages to workers through various communication channels. /// /// This trait abstracts the message delivery mechanism, allowing different implementations @@ -415,13 +417,13 @@ impl Coordinator { // Clone job.final_proof and error if does not exist let final_proof = if job.state == JobState::Completed { - job.final_proof.clone().ok_or_else(|| { + Some(job.final_proof.clone().ok_or_else(|| { CoordinatorError::Internal( "Final proof is missing during post-launch processing".to_string(), ) - })? + })?) } else { - Vec::new() + None }; // Check if webhook URL is configured and spawn it in a separate task @@ -436,11 +438,17 @@ impl Coordinator { // Save proof to disk if state == JobState::Completed && !self.config.server.no_save_proofs { let folder = self.config.server.proofs_dir.clone(); - zisk_common::save_proof(job_id.as_str(), folder, &final_proof, false).map_err(|e| { - error!("Failed to save proof for job {}: {}", job_id, e); - job.cleanup(); - CoordinatorError::Internal(e.to_string()) - })?; + let vadcop_proof = VadcopFinalProof::new_from_proof(&final_proof.unwrap(), true) + .map_err(|e| { + CoordinatorError::Internal(format!("Failed to create VadcopFinalProof: {}", e)) + })?; + zisk_common::save_proof(job_id.as_str(), folder, &vadcop_proof, false).map_err( + |e| { + error!("Failed to save proof for job {}: {}", job_id, e); + job.cleanup(); + CoordinatorError::Internal(e.to_string()) + }, + )?; } // Clean up process data for the job diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 61a081893..92ab36a81 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -3,22 +3,19 @@ use asm_runner::HintsShmem; use cargo_zisk::commands::get_proving_key; use precompiles_hints::HintsProcessor; use proofman::{AggProofs, ContributionsInfo}; -use rom_setup::{ - gen_elf_hash, get_elf_bin_file_path, get_elf_data_hash, get_rom_blowup_factor_and_arity, - DEFAULT_CACHE_PATH, -}; +use rom_setup::{get_elf_data_hash, DEFAULT_CACHE_PATH}; use std::collections::hash_map::Entry; +use std::collections::HashMap; use std::fs; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::reinterpret_vec; -use zisk_distributed_common::{ - AggregationParams, DataCtx, HintsSourceDto, InputSourceDto, JobPhase, StreamDataDto, - StreamMessageKind, StreamPayloadDto, WorkerState, -}; +use zisk_common::ElfBinaryOwned; +use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; use zisk_distributed_common::{ComputeCapacity, JobId, WorkerId}; +use zisk_distributed_common::{HintsSourceDto, StreamDataDto, StreamMessageKind, StreamPayloadDto}; use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProver}; use proofman::ProofInfo; @@ -26,7 +23,6 @@ use proofman::ProvePhaseInputs; use proofman_common::ParamsGPU; use proofman_common::ProofOptions; use proofman_common::{json_to_debug_instances_map, DebugInfo}; -use std::collections::HashMap; use std::path::PathBuf; use tracing::{error, info}; @@ -63,9 +59,6 @@ pub struct ProverConfig { /// Path to the ASM ROM file (optional) pub asm_rom: Option, - /// Map of custom commits - pub custom_commits_map: HashMap, - /// Flag indicating whether to use the prebuilt emulator pub emulator: bool, @@ -94,7 +87,7 @@ pub struct ProverConfig { pub aggregation: bool, /// Preallocate resources - pub gpu_params: ParamsGPU, + pub gpu_params: Option, /// Whether to use shared tables in the witness library pub shared_tables: bool, @@ -144,27 +137,23 @@ impl ProverConfig { if emulator { prover_service_config.asm = None; } else if prover_service_config.asm.is_none() { - let stem = prover_service_config - .elf - .file_stem() - .ok_or_else(|| { - anyhow::anyhow!( - "ELF path '{}' does not have a file stem.", - prover_service_config.elf.display() - ) - })? - .to_str() - .ok_or_else(|| { - anyhow::anyhow!( - "ELF file stem for '{}' is not valid UTF-8.", - prover_service_config.elf.display() - ) - })?; - - let hash = get_elf_data_hash(&prover_service_config.elf) + let elf_bin = fs::read(&prover_service_config.elf).map_err(|e| { + anyhow::anyhow!( + "Error reading ELF file {}: {}", + prover_service_config.elf.display(), + e + ) + })?; + + let elf = ElfBinaryOwned::new( + elf_bin, + prover_service_config.elf.file_stem().unwrap().to_str().unwrap().to_string(), + true, + ); + let hash = get_elf_data_hash(&elf) .map_err(|e| anyhow::anyhow!("Error computing ELF hash: {}", e))?; - let new_filename = format!("{stem}-{hash}-mt.bin"); - let asm_rom_filename = format!("{stem}-{hash}-rh.bin"); + let new_filename = format!("{hash}-mt.bin"); + let asm_rom_filename = format!("{hash}-rh.bin"); asm_rom = Some(default_cache_path.join(asm_rom_filename)); prover_service_config.asm = Some(default_cache_path.join(new_filename)); } @@ -179,41 +168,29 @@ impl ProverConfig { return Err(anyhow::anyhow!("ASM file not found at {:?}", asm_rom.display())); } } - let (blowup_factor, merkle_tree_arity) = get_rom_blowup_factor_and_arity(&proving_key); - let rom_bin_path = get_elf_bin_file_path( - &prover_service_config.elf.to_path_buf(), - &default_cache_path, - blowup_factor, - merkle_tree_arity, - )?; - if !rom_bin_path.exists() { - let _ = gen_elf_hash( - &prover_service_config.elf.clone(), - rom_bin_path.as_path(), - blowup_factor, - merkle_tree_arity, - false, - ) - .map_err(|e| anyhow::anyhow!("Error generating elf hash: {}", e)); - } - let mut custom_commits_map: HashMap = HashMap::new(); - custom_commits_map.insert("rom".to_string(), rom_bin_path); - let mut gpu_params = ParamsGPU::new(prover_service_config.preallocate); - if let Some(max_streams) = prover_service_config.max_streams { - gpu_params.with_max_number_streams(max_streams); - } - if let Some(number_threads_witness) = prover_service_config.number_threads_witness { - gpu_params.with_number_threads_pools_witness(number_threads_witness); - } - if let Some(max_witness_stored) = prover_service_config.max_witness_stored { - gpu_params.with_max_witness_stored(max_witness_stored); + let mut gpu_params = None; + if prover_service_config.preallocate + || prover_service_config.max_streams.is_some() + || prover_service_config.number_threads_witness.is_some() + || prover_service_config.max_witness_stored.is_some() + { + let mut gpu_params_new = ParamsGPU::new(prover_service_config.preallocate); + if let Some(max_streams) = prover_service_config.max_streams { + gpu_params_new.with_max_number_streams(max_streams); + } + if let Some(number_threads_witness) = prover_service_config.number_threads_witness { + gpu_params_new.with_number_threads_pools_witness(number_threads_witness); + } + if let Some(max_witness_stored) = prover_service_config.max_witness_stored { + gpu_params_new.with_max_witness_stored(max_witness_stored); + } + gpu_params = Some(gpu_params_new); } Ok(ProverConfig { elf: prover_service_config.elf.clone(), asm: prover_service_config.asm.clone(), asm_rom, - custom_commits_map, emulator, proving_key, verbose: prover_service_config.verbose, @@ -268,15 +245,23 @@ impl Worker { .emu() .prove() .aggregation(true) - .rma(true) .proving_key_path(prover_config.proving_key.clone()) - .elf_path(prover_config.elf.clone()) .verbose(prover_config.verbose) .shared_tables(prover_config.shared_tables) .gpu(prover_config.gpu_params.clone()) .build()?, ); + let elf_bin = fs::read(&prover_config.elf).map_err(|e| { + anyhow::anyhow!("Error reading ELF file {}: {}", prover_config.elf.display(), e) + })?; + let elf = ElfBinaryOwned::new( + elf_bin, + prover_config.elf.file_stem().unwrap().to_str().unwrap().to_string(), + false, + ); + prover.setup(&elf)?; + Ok(Worker:: { _worker_id: worker_id, _compute_capacity: compute_capacity, @@ -300,9 +285,7 @@ impl Worker { .asm() .prove() .aggregation(true) - .rma(true) .proving_key_path(prover_config.proving_key.clone()) - .elf_path(prover_config.elf.clone()) .verbose(prover_config.verbose) .shared_tables(prover_config.shared_tables) .asm_path_opt(prover_config.asm.clone()) @@ -312,6 +295,16 @@ impl Worker { .build()?, ); + let elf_bin = fs::read(&prover_config.elf).map_err(|e| { + anyhow::anyhow!("Error reading ELF file {}: {}", prover_config.elf.display(), e) + })?; + let elf = ElfBinaryOwned::new( + elf_bin, + prover_config.elf.file_stem().unwrap().to_str().unwrap().to_string(), + true, + ); + prover.setup(&elf)?; + Ok(Worker:: { _worker_id: worker_id, _compute_capacity: compute_capacity, @@ -401,12 +394,12 @@ impl Worker { &self, job: Arc>, tx: mpsc::UnboundedSender, - ) -> JoinHandle<()> { - self.partial_contribution_mpi_broadcast(&job).await; - self.partial_contribution(job, tx).await + ) -> Result> { + self.partial_contribution_mpi_broadcast(&job).await?; + Ok(self.partial_contribution(job, tx).await) } - pub async fn partial_contribution_mpi_broadcast(&self, job: &Mutex) { + pub async fn partial_contribution_mpi_broadcast(&self, job: &Mutex) -> Result<()> { let job = job.lock().await; let job_id = job.job_id.clone(); @@ -429,7 +422,8 @@ impl Worker { )) .unwrap(); - self.prover.mpi_broadcast(&mut serialized); + self.prover.mpi_broadcast(&mut serialized)?; + Ok(()) } pub async fn handle_prove( @@ -437,16 +431,16 @@ impl Worker { job: Arc>, challenges: Vec, tx: mpsc::UnboundedSender, - ) -> JoinHandle<()> { - self.prove_mpi_broadcast(&job, challenges.clone()).await; - self.prove(job, challenges, tx).await + ) -> Result> { + self.prove_mpi_broadcast(&job, challenges.clone()).await?; + Ok(self.prove(job, challenges, tx).await) } pub async fn prove_mpi_broadcast( &self, job: &Mutex, challenges: Vec, - ) { + ) -> Result<()> { let job = job.lock().await; let job_id = job.job_id.clone(); @@ -457,7 +451,8 @@ impl Worker { let mut serialized = borsh::to_vec(&(JobPhase::Prove, job_id, phase_inputs, options)).unwrap(); - self.prover.mpi_broadcast(&mut serialized); + self.prover.mpi_broadcast(&mut serialized)?; + Ok(()) } pub async fn handle_aggregate( @@ -546,15 +541,15 @@ impl Worker { InputSourceDto::InputPath(inputs_uri) => { let stdin = ZiskStdin::from_file(inputs_uri)?; - prover.set_stdin(stdin); + prover.set_stdin(stdin)?; } InputSourceDto::InputData(input_data) => { let stdin = ZiskStdin::from_vec(input_data); - prover.set_stdin(stdin); + prover.set_stdin(stdin)?; } InputSourceDto::InputNull => { let stdin = ZiskStdin::null(); - prover.set_stdin(stdin); + prover.set_stdin(stdin)?; } } @@ -814,7 +809,7 @@ impl Worker { verify_proofs: false, save_proofs: false, test_mode: false, - output_dir_path: PathBuf::from("."), + output_dir_path: None, rma: self.prover_config.rma, minimal_memory: self.prover_config.minimal_memory, compressed, @@ -828,7 +823,7 @@ impl Worker { pub async fn handle_mpi_broadcast_request(&self) -> Result<()> { let mut bytes: Vec = Vec::new(); - self.prover.mpi_broadcast(&mut bytes); + self.prover.mpi_broadcast(&mut bytes)?; // extract byte 0 to decide the option let phase = borsh::from_slice(&bytes[0..1]).unwrap(); diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 9d43bcd25..b8f9038b5 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -566,7 +566,7 @@ impl WorkerNodeGrpc { // Start computation in background task self.worker.set_current_computation( - self.worker.handle_partial_contribution(job.clone(), computation_tx.clone()).await, + self.worker.handle_partial_contribution(job.clone(), computation_tx.clone()).await?, ); Ok(()) @@ -674,7 +674,7 @@ impl WorkerNodeGrpc { .collect(); self.worker.set_current_computation( - self.worker.handle_prove(job, cont, computation_tx.clone()).await, + self.worker.handle_prove(job, cont, computation_tx.clone()).await?, ); Ok(()) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index de7966959..631e49106 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -278,6 +278,7 @@ void file_lock(void); // Configuration bool output = false; +bool output_riscof = false; bool silent = false; bool metrics = false; bool trace = false; @@ -885,6 +886,7 @@ void print_usage (void) printf("\t--shutdown\n"); printf("\t--mt \n"); printf("\t-o output on\n"); + printf("\t--output_riscof output riscof on\n"); printf("\t--silent silent on\n"); printf("\t--shm_prefix (default: ZISK)\n"); printf("\t-m metrics on\n"); @@ -990,6 +992,11 @@ void parse_arguments(int argc, char *argv[]) output = true; continue; } + if (strcmp(argv[i], "--output_riscof") == 0) + { + output_riscof = true; + continue; + } if (strcmp(argv[i], "--silent") == 0) { silent = true; @@ -1798,6 +1805,7 @@ void configure (void) printf("\tmap_locked_flag=%d\n", map_locked_flag); printf("\toutput=%u\n", output); printf("\tprecompile_results_enabled=%u\n", precompile_results_enabled); + printf("\toutput_riscof=%u\n", output_riscof); } } @@ -3968,6 +3976,22 @@ void server_run (void) // Log output if (output) + { + unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; + unsigned int output_size = 64; +#ifdef DEBUG + if (verbose) printf("Output size=%d\n", output_size); +#endif + + for (unsigned int i = 0; i < output_size; i++) + { + printf("%08x\n", *pOutput); + pOutput++; + } + } + + // Log output for riscof tests + if (output_riscof) { unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; unsigned int output_size = *pOutput; diff --git a/emulator/benches/benchmark.rs b/emulator/benches/benchmark.rs index 76fe92e7c..3b445e7db 100644 --- a/emulator/benches/benchmark.rs +++ b/emulator/benches/benchmark.rs @@ -81,9 +81,10 @@ fn bench_riscv2zisk(c: &mut Criterion) { b.iter(|| { // Convert the ELF file to ZisK ROM let elf_file = "./benches/data/my.elf".to_string(); + let elf = std::fs::read(elf_file.clone()).unwrap(); let _rom: ZiskRom = { // Create an instance of the RISCV -> ZisK program converter - let rv2zk = Riscv2zisk::new(elf_file.clone()); + let rv2zk = Riscv2zisk::new(&elf); // Convert program to rom let result = rv2zk.run(); @@ -113,9 +114,10 @@ fn bench_process_rom(c: &mut Criterion) { c.bench_function("Process ROM", |b| { // Convert the ELF file to ZisK ROM let elf_file = "./benches/data/my.elf".to_string(); + let elf = std::fs::read(elf_file.clone()).unwrap(); let rom: ZiskRom = { // Create an instance of the RISCV -> ZisK program converter - let rv2zk = Riscv2zisk::new(elf_file.clone()); + let rv2zk = Riscv2zisk::new(&elf); // Convert program to rom let result = rv2zk.run(); @@ -166,9 +168,10 @@ fn bench_process_rom_callback(c: &mut Criterion) { //let elf_file = // "../riscof/riscof_work/rv64i_m/A/src/amoxor.w-01.S/dut/my.elf".to_string(); let elf_file = "./benches/data/my.elf".to_string(); + let elf = std::fs::read(elf_file.clone()).unwrap(); let zisk_rom: ZiskRom = { // Create an instance of the RISCV -> ZisK program converter - let rv2zk = Riscv2zisk::new(elf_file.clone()); + let rv2zk = Riscv2zisk::new(&elf); // Convert program to rom let result = rv2zk.run(); diff --git a/emulator/src/emulator.rs b/emulator/src/emulator.rs index c8ad2c86b..648d64b4c 100644 --- a/emulator/src/emulator.rs +++ b/emulator/src/emulator.rs @@ -76,9 +76,12 @@ impl ZiskEmulator { println!("process_elf_file() elf_file={elf_filename}"); } + let elf = fs::read(&elf_filename) + .map_err(|e| ZiskEmulatorErr::Unknown(format!("Error reading ELF file: {e}")))?; + // Create an instance of the RISC-V -> ZisK program transpiler (Riscv2zisk) with the ELF // file name - let riscv2zisk = Riscv2zisk::new(elf_filename); + let riscv2zisk = Riscv2zisk::new(&elf); // Convert the ELF file to ZisK ROM calling the transpiler run() method let zisk_rom = riscv2zisk.run().map_err(|err| ZiskEmulatorErr::Unknown(err.to_string()))?; diff --git a/examples/Cargo.lock b/examples/Cargo.lock new file mode 100644 index 000000000..bd8e6413d --- /dev/null +++ b/examples/Cargo.lock @@ -0,0 +1,4823 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "arrayvec", + "digest", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.114", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-secp256k1" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bd211c48debd3037b48873a7aa22c3aba034e83388aa4124795c9f220b88c7" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "arrayvec", + "digest", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "asm-runner" +version = "0.16.0" +dependencies = [ + "anyhow", + "clap", + "libc", + "mem-common", + "mem-planner-cpp", + "named-sem", + "rayon", + "thiserror 2.0.18", + "tracing", + "ureq", + "zisk-common", + "zisk-core", +] + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.114", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "build-probe-mpi" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78ace2bb02fc18ad937f1599a853fcf3da2327bc1eb3c8e62b1f2fe4573bfd6" +dependencies = [ + "pkg-config", + "shell-words", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87a0c0e6148f11f01f32650a2ea02d532b2ad4e81d8bd41e6e565b5adc5e6082" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "circuit" +version = "0.16.0" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpp_demangle" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.114", +] + +[[package]] +name = "curves" +version = "0.16.0" +dependencies = [ + "fields 0.16.0", + "num-bigint", + "num-traits", + "rand 0.9.2", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + +[[package]] +name = "data-bus" +version = "0.16.0" +dependencies = [ + "zisk-common", + "zisk-core", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "env" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc95de49ad098572c02d3fbf368c9a020bfff5ae78483685b77f51d8a7e9486d" +dependencies = [ + "num_threads", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "executor" +version = "0.16.0" +dependencies = [ + "anyhow", + "asm-runner", + "crossbeam", + "data-bus", + "fields 0.16.0", + "itertools 0.14.0", + "mem-common", + "mem-planner-cpp", + "named-sem", + "pil-std-lib", + "precomp-arith-eq", + "precomp-arith-eq-384", + "precomp-big-int", + "precomp-dma", + "precomp-keccakf", + "precomp-poseidon2", + "precomp-sha256f", + "precompiles-common", + "precompiles-hints", + "proofman", + "proofman-common", + "proofman-util", + "rayon", + "rom-setup", + "sm-arith", + "sm-binary", + "sm-frequent-ops", + "sm-main", + "sm-mem", + "sm-rom", + "tracing", + "witness", + "zisk-common", + "zisk-core", + "zisk-pil", + "ziskemu", +] + +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.2", + "siphasher", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fields" +version = "0.16.0" +dependencies = [ + "cfg-if", + "num-bigint", + "paste", + "serde", +] + +[[package]] +name = "fields" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +dependencies = [ + "cfg-if", + "num-bigint", + "paste", + "serde", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "git2" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jiff" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "lib-c" +version = "0.16.0" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libffi" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b" +dependencies = [ + "libc", + "libffi-sys", +] + +[[package]] +name = "libffi-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb" +dependencies = [ + "cc", +] + +[[package]] +name = "libgit2-sys" +version = "0.17.0+1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libz-sys" +version = "1.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "mem-common" +version = "0.16.0" +dependencies = [ + "clap", + "fields 0.16.0", + "num-bigint", + "num-traits", + "pil-std-lib", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "static_assertions", + "tracing", + "witness", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "mem-planner-cpp" +version = "0.16.0" +dependencies = [ + "mem-common", + "proofman-common", + "proofman-util", + "tracing", + "zisk-common", + "zisk-pil", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mpi" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41457b69d35846af2fec1877a4f3b866a72b6ab2c9500218f115e65e10993b21" +dependencies = [ + "build-probe-mpi", + "conv", + "libffi", + "mpi-sys", + "once_cell", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "mpi-sys" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f655543f54b263cbc3d2456bf714bd807d66a33eff8f70136687f0776d34f76" +dependencies = [ + "bindgen", + "build-probe-mpi", + "cc", +] + +[[package]] +name = "msvc-demangler" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4c25a3bb7d880e8eceab4822f3141ad0700d20f025991c1f03bd3d00219a5fc" +dependencies = [ + "bitflags", +] + +[[package]] +name = "named-sem" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0875efe1a57a20d0cee7034499aa9d764b3c7525563fa3c3f16a2ccf01ddfa04" +dependencies = [ + "libc", + "thiserror 2.0.18", + "windows", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "ntapi" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "flate2", + "memchr", + "ruzstd", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "papergrid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608b6444acf7f5ea39e8bd06dd6037e34a4b5ddfb29ae840edad49ea798e9e79" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "path-clean" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pil-std-lib" +version = "0.16.0" +dependencies = [ + "colored", + "fields 0.16.0", + "num-bigint", + "num-traits", + "proofman-common", + "proofman-hints", + "proofman-util", + "rayon", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "tracing", + "witness", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomp-arith-eq" +version = "0.16.0" +dependencies = [ + "ark-bn254", + "ark-ff", + "ark-secp256k1", + "ark-std", + "fields 0.16.0", + "k256", + "lazy_static", + "lib-c", + "mem-common", + "nom", + "num-bigint", + "num-traits", + "path-clean", + "pil-std-lib", + "precompiles-common", + "precompiles-helpers", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "regex", + "rustfmt-wrapper", + "serde", + "serde_json", + "sm-mem", + "tracing", + "typenum", + "witness", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "precomp-arith-eq-384" +version = "0.16.0" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ff", + "ark-secp256k1", + "ark-std", + "fields 0.16.0", + "k256", + "lazy_static", + "lib-c", + "mem-common", + "nom", + "num-bigint", + "num-traits", + "path-clean", + "pil-std-lib", + "precomp-arith-eq", + "precompiles-common", + "precompiles-helpers", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "regex", + "rustfmt-wrapper", + "serde", + "serde_json", + "tracing", + "typenum", + "witness", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "precomp-big-int" +version = "0.16.0" +dependencies = [ + "fields 0.16.0", + "generic-array", + "lib-c", + "mem-common", + "pil-std-lib", + "precompiles-common", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sm-mem", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "precomp-dma" +version = "0.16.0" +dependencies = [ + "fields 0.16.0", + "generic-array", + "lib-c", + "mem-common", + "pil-std-lib", + "precompiles-common", + "precompiles-helpers", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sm-mem", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "precomp-keccakf" +version = "0.16.0" +dependencies = [ + "circuit", + "fields 0.16.0", + "path-clean", + "pil-std-lib", + "precompiles-common", + "precompiles-helpers", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "tiny-keccak", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "precomp-poseidon2" +version = "0.16.0" +dependencies = [ + "fields 0.16.0", + "mem-common", + "pil-std-lib", + "precompiles-common", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sha2", + "sm-mem", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "precomp-sha256f" +version = "0.16.0" +dependencies = [ + "fields 0.16.0", + "mem-common", + "pil-std-lib", + "precompiles-common", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sm-mem", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "precompiles-common" +version = "0.16.0" +dependencies = [ + "fields 0.16.0", + "mem-common", + "sm-mem", + "zisk-common", + "zisk-core", +] + +[[package]] +name = "precompiles-helpers" +version = "0.16.0" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ff", + "ark-secp256k1", + "ark-std", + "cfg-if", + "circuit", + "lib-c", + "num-bigint", + "num-traits", +] + +[[package]] +name = "precompiles-hints" +version = "0.16.0" +dependencies = [ + "anyhow", + "lib-c", + "precompiles-helpers", + "rayon", + "rustls", + "tracing", + "zisk-common", + "ziskos-hints", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.10+spec-1.0.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proofman" +version = "0.16.0" +dependencies = [ + "bincode", + "blake3", + "borsh", + "bytemuck", + "chrono", + "colored", + "crossbeam-channel", + "csv", + "curves", + "fields 0.16.0", + "libloading", + "mpi", + "num-bigint", + "num-traits", + "pil-std-lib", + "proofman-common", + "proofman-hints", + "proofman-macros", + "proofman-starks-lib-c", + "proofman-util", + "proofman-verifier", + "rand 0.9.2", + "rayon", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", + "witness", +] + +[[package]] +name = "proofman-common" +version = "0.16.0" +dependencies = [ + "bincode", + "borsh", + "bytemuck", + "colored", + "crossbeam-channel", + "crossbeam-queue", + "csv", + "env", + "fields 0.16.0", + "indexmap", + "lazy_static", + "libloading", + "mpi", + "num_cpus", + "proofman-macros", + "proofman-starks-lib-c", + "proofman-util", + "rayon", + "serde", + "serde_json", + "sysinfo 0.35.2", + "tabled", + "thiserror 2.0.18", + "tracing", + "tracing-subscriber", + "yansi", +] + +[[package]] +name = "proofman-hints" +version = "0.16.0" +dependencies = [ + "fields 0.16.0", + "itoa", + "proofman-common", + "proofman-starks-lib-c", + "proofman-util", + "tracing", +] + +[[package]] +name = "proofman-macros" +version = "0.16.0" +dependencies = [ + "proc-macro2", + "quote", + "rayon", + "syn 2.0.114", +] + +[[package]] +name = "proofman-starks-lib-c" +version = "0.16.0" +dependencies = [ + "crossbeam-channel", + "tracing", +] + +[[package]] +name = "proofman-util" +version = "0.16.0" +dependencies = [ + "bincode", + "bytemuck", + "colored", + "fields 0.16.0", + "serde", + "sysinfo 0.35.2", + "tracing", +] + +[[package]] +name = "proofman-verifier" +version = "0.16.0" +dependencies = [ + "bytemuck", + "fields 0.16.0", + "proofman-util", + "rayon", + "tracing", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "fastbloom", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash 2.1.1", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "riscv" +version = "0.16.0" +dependencies = [ + "elf", +] + +[[package]] +name = "rom-setup" +version = "0.16.0" +dependencies = [ + "anyhow", + "blake3", + "colored", + "fields 0.16.0", + "proofman-common", + "sm-rom", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustfmt-wrapper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1adc9dfed5cc999077978cc7163b9282c5751c8d39827c4ea8c8c220ca5a440" +dependencies = [ + "serde", + "tempfile", + "thiserror 1.0.69", + "toml", + "toolchain_find", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ruzstd" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_arrays" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a16b99c5ea4fe3daccd14853ad260ec00ea043b2708d1fd1da3106dcd8d9df" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "sha-hasher-guest" +version = "0.1.0" +dependencies = [ + "byteorder", + "serde", + "sha2", + "ziskos", +] + +[[package]] +name = "sha-hasher-host" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "sha2", + "zisk-build", + "zisk-common", + "zisk-sdk", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "sm-arith" +version = "0.16.0" +dependencies = [ + "fields 0.16.0", + "num-bigint", + "pil-std-lib", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sm-binary", + "sm-frequent-ops", + "static_assertions", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "sm-binary" +version = "0.16.0" +dependencies = [ + "fields 0.16.0", + "num-bigint", + "pil-std-lib", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sm-frequent-ops", + "static_assertions", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "sm-frequent-ops" +version = "0.16.0" +dependencies = [ + "clap", + "fields 0.16.0", + "num-bigint", + "pil-std-lib", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "static_assertions", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "sm-main" +version = "0.16.0" +dependencies = [ + "asm-runner", + "fields 0.16.0", + "mem-common", + "num-bigint", + "pil-std-lib", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sm-mem", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", + "ziskemu", +] + +[[package]] +name = "sm-mem" +version = "0.16.0" +dependencies = [ + "fields 0.16.0", + "mem-common", + "mem-planner-cpp", + "num-bigint", + "num-traits", + "pil-std-lib", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "tracing", + "witness", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "sm-rom" +version = "0.16.0" +dependencies = [ + "asm-runner", + "fields 0.16.0", + "itertools 0.14.0", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "symbolic-common" +version = "12.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520cf51c674f8b93d533f80832babe413214bb766b6d7cb74ee99ad2971f8467" +dependencies = [ + "debugid", + "memmap2", + "stable_deref_trait", + "uuid", +] + +[[package]] +name = "symbolic-demangle" +version = "12.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0de2ee0ffa2641e17ba715ad51d48b9259778176517979cb38b6aa86fa7425" +dependencies = [ + "cc", + "cpp_demangle", + "msvc-demangler", + "rustc-demangle", + "symbolic-common", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "sysinfo" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "sysinfo" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "tabled" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2407502760ccfd538f2fb1f843dd87b6daf1a17848d57bc5a25617e408ef4c7a" +dependencies = [ + "papergrid", + "tabled_derive", +] + +[[package]] +name = "tabled_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278ea3921cee8c5a69e0542998a089f7a14fa43c9c4e4f9951295da89bd0c943" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toolchain_find" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc8c9a7f0a2966e1acdaf0461023d0b01471eeead645370cf4c3f5cff153f2a" +dependencies = [ + "home", + "once_cell", + "regex", + "semver", + "walkdir", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" +dependencies = [ + "base64", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf-8", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vergen" +version = "8.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" +dependencies = [ + "anyhow", + "cfg-if", + "git2", + "rustversion", + "time", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.114", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "witness" +version = "0.16.0" +dependencies = [ + "colored", + "fields 0.16.0", + "libloading", + "proofman-common", + "proofman-util", + "serde_json", + "tracing", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "x509-parser" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zisk-build" +version = "0.16.0" +dependencies = [ + "anyhow", + "cargo_metadata", + "clap", + "rom-setup", + "tracing", + "vergen", + "zisk-sdk", +] + +[[package]] +name = "zisk-common" +version = "0.16.0" +dependencies = [ + "anyhow", + "bincode", + "fields 0.16.0", + "libc", + "mpi", + "proofman", + "proofman-common", + "proofman-util", + "quinn", + "rcgen", + "rustls", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "tracing-subscriber", + "witness", + "zisk-core", + "zisk-pil", + "zstd", +] + +[[package]] +name = "zisk-core" +version = "0.16.0" +dependencies = [ + "elf", + "fields 0.16.0", + "indexmap", + "json", + "lib-c", + "paste", + "precompiles-helpers", + "rayon", + "riscv", + "serde", + "serde_json", + "sha2", + "tiny-keccak", + "zisk-pil", + "ziskos", +] + +[[package]] +name = "zisk-distributed-common" +version = "0.1.0" +dependencies = [ + "anyhow", + "borsh", + "chrono", + "proofman", + "proofman-common", + "proofman-util", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", + "tracing-appender", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "zisk-pil" +version = "0.16.0" +dependencies = [ + "fields 0.16.0", + "proofman", + "proofman-common", + "proofman-macros", + "rayon", + "serde", + "serde_arrays", +] + +[[package]] +name = "zisk-sdk" +version = "0.16.0" +dependencies = [ + "anyhow", + "asm-runner", + "bincode", + "colored", + "fields 0.16.0", + "libloading", + "proofman", + "proofman-common", + "proofman-util", + "rom-setup", + "serde", + "sha2", + "tracing", + "zisk-common", + "zisk-distributed-common", + "zisk-verifier", + "zisk-witness", + "zstd", +] + +[[package]] +name = "zisk-verifier" +version = "0.16.0" +dependencies = [ + "anyhow", + "proofman-util", + "proofman-verifier", +] + +[[package]] +name = "zisk-witness" +version = "0.16.0" +dependencies = [ + "anyhow", + "asm-runner", + "data-bus", + "env_logger", + "executor", + "fields 0.16.0", + "mem-common", + "pil-std-lib", + "precomp-arith-eq", + "precomp-arith-eq-384", + "precomp-big-int", + "precomp-dma", + "precomp-keccakf", + "precomp-poseidon2", + "precomp-sha256f", + "precompiles-hints", + "proofman", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sm-arith", + "sm-binary", + "sm-frequent-ops", + "sm-main", + "sm-mem", + "sm-rom", + "tracing", + "witness", + "zisk-common", + "zisk-core", + "zisk-pil", + "ziskemu", +] + +[[package]] +name = "ziskemu" +version = "0.16.0" +dependencies = [ + "clap", + "data-bus", + "fields 0.16.0", + "mem-common", + "memmap2", + "num-format", + "object", + "proofman-common", + "rayon", + "riscv", + "sm-arith", + "sm-binary", + "sm-mem", + "symbolic-common", + "symbolic-demangle", + "sysinfo 0.37.2", + "vergen", + "zisk-common", + "zisk-core", + "zisk-pil", +] + +[[package]] +name = "ziskos" +version = "0.16.0" +dependencies = [ + "bincode", + "cfg-if", + "ctor", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", + "getrandom 0.2.17", + "lazy_static", + "lib-c", + "num-bigint", + "num-integer", + "num-traits", + "once_cell", + "paste", + "precompiles-helpers", + "rand 0.8.5", + "serde", + "sha2", + "static_assertions", + "tiny-keccak", +] + +[[package]] +name = "ziskos-hints" +version = "0.16.0" +dependencies = [ + "anyhow", + "bincode", + "cfg-if", + "elliptic-curve", + "fields 0.16.0", + "getrandom 0.2.17", + "k256", + "lazy_static", + "lib-c", + "num-bigint", + "num-integer", + "num-traits", + "paste", + "precompiles-helpers", + "rand 0.8.5", + "serde", + "sha2", + "static_assertions", + "tiny-keccak", +] + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 000000000..9f24f36f1 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,19 @@ +[workspace] +members = ["sha-hasher/host", "sha-hasher/guest"] + +resolver = "2" + +[profile.release] +opt-level = 3 + +[profile.release.package."proofman-verifier"] +opt-level = 0 + +[workspace.dependencies] +# Guest dependencies +ziskos = { path = "../ziskos/entrypoint" } + +# Host dependencies +zisk-sdk = { path = "../sdk" } +zisk-build = { path = "../ziskbuild" } +zisk-common = { path = "../common" } \ No newline at end of file diff --git a/examples/sha-hasher/guest/Cargo.lock b/examples/sha-hasher/guest/Cargo.lock new file mode 100644 index 000000000..a31235f0c --- /dev/null +++ b/examples/sha-hasher/guest/Cargo.lock @@ -0,0 +1,293 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lib-c" +version = "0.13.1" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "sha-hasher-guest" +version = "0.1.0" +dependencies = [ + "byteorder", + "sha2", + "ziskos", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ziskos" +version = "0.13.1" +dependencies = [ + "cfg-if", + "getrandom", + "lazy_static", + "lib-c", + "num-bigint", + "num-integer", + "num-traits", + "rand", + "static_assertions", + "tiny-keccak", +] diff --git a/examples/sha-hasher/guest/Cargo.toml b/examples/sha-hasher/guest/Cargo.toml new file mode 100644 index 000000000..17dbb8710 --- /dev/null +++ b/examples/sha-hasher/guest/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "sha-hasher-guest" +version = "0.1.0" +edition = "2024" + +[dependencies] +byteorder = "1.5.0" +sha2 = "0.10.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } +ziskos = { path = "../../../ziskos/entrypoint" } diff --git a/examples/sha-hasher/guest/src/main.rs b/examples/sha-hasher/guest/src/main.rs new file mode 100644 index 000000000..4d103cf5e --- /dev/null +++ b/examples/sha-hasher/guest/src/main.rs @@ -0,0 +1,34 @@ +// This example program takes a number `n` as input and computes the SHA-256 hash `n` times sequentially. + +// Mark the main function as the entry point for ZisK +#![no_main] +ziskos::entrypoint!(main); + +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +#[derive(Serialize, Deserialize, Debug)] +struct Output { + hash: [u8; 32], + iterations: u32, + magic_number: u32, +} + +fn main() { + // Read the input data + let n: u32 = ziskos::io::read(); + + let mut hash = [0u8; 32]; + + // Compute SHA-256 hashing 'n' times + for _ in 0..n { + let mut hasher = Sha256::new(); + hasher.update(hash); + let digest = &hasher.finalize(); + hash = Into::<[u8; 32]>::into(*digest); + } + + let output = Output { hash, iterations: n, magic_number: 0xDEADBEEF }; + + ziskos::io::commit(&output); +} diff --git a/examples/sha-hasher/host/Cargo.toml b/examples/sha-hasher/host/Cargo.toml new file mode 100644 index 000000000..eac142dbe --- /dev/null +++ b/examples/sha-hasher/host/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "sha-hasher-host" +version = "0.1.0" +edition = "2024" + +[dependencies] +zisk-sdk = { workspace = true } +zisk-common = { workspace = true } +anyhow = "1.0" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sha2 = "0.10.8" + +[build-dependencies] +zisk-build = { workspace = true } diff --git a/examples/sha-hasher/host/build.rs b/examples/sha-hasher/host/build.rs new file mode 100644 index 000000000..c77ea0d8e --- /dev/null +++ b/examples/sha-hasher/host/build.rs @@ -0,0 +1,3 @@ +fn main() { + zisk_build::build_program("../guest"); +} diff --git a/examples/sha-hasher/host/src/main.rs b/examples/sha-hasher/host/src/main.rs new file mode 100644 index 000000000..b713221ca --- /dev/null +++ b/examples/sha-hasher/host/src/main.rs @@ -0,0 +1,70 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use zisk_common::ElfBinary; +use zisk_common::io::ZiskIO; +use zisk_common::io::ZiskStdin; +use zisk_sdk::{ProofOpts, ProverClient, ZiskProof, ZiskPublics, include_elf}; + +pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); + +#[derive(Serialize, Deserialize, Debug)] +struct Output { + hash: [u8; 32], + iterations: u32, + magic_number: u32, +} + +fn main() -> Result<()> { + println!("Starting ZisK Prover Client..."); + + // Create an input stream and write '1000' to it. + let n = 1000u32; + let stdin = ZiskStdin::new(); + stdin.write(&n); + + // Create a `ProverClient` method. + let client = ProverClient::builder().asm().base_port(54321).snark().build().unwrap(); + + let vkey = client.setup(&ELF)?; + + // Execute the program using the `ProverClient.execute` method, without generating a proof. + let result = client.execute(stdin.clone())?; + + println!( + "ZisK has executed program with {} cycles in {:?}", + result.execution.steps, result.duration + ); + + let proof_opts = ProofOpts::default().minimal_memory(); + let vadcop_result = client.prove(stdin).with_proof_options(proof_opts).run()?; + client.verify(&vadcop_result.proof, &vadcop_result.publics, &vkey)?; + + let result = client.prove_snark(&vadcop_result.proof, &vadcop_result.publics, &vkey)?; + client.verify(&result, &vadcop_result.publics, &vkey)?; + + result.save("/tmp/sha_hasher_proof_snark.bin")?; + + let output: Output = vadcop_result.get_publics()?; + println!("Deserialized public outputs: {:?}", output); + println!("Hash: {:02x?}", output.hash); + println!("Iterations: {}", output.iterations); + println!("Magic number: 0x{:08x}", output.magic_number); + + let mut hash = [0u8; 32]; + for _ in 0..n { + let mut hasher = Sha256::new(); + hasher.update(hash); + let digest = &hasher.finalize(); + hash = Into::<[u8; 32]>::into(*digest); + } + let output = Output { hash, iterations: n, magic_number: 0xDEADBEEF }; + let publics = ZiskPublics::write(&output)?; + let proof = ZiskProof::load("/tmp/sha_hasher_proof_snark.bin")?; + let vk = client.vk(&ELF)?; + client.verify(&proof, &publics, &vk)?; + + println!("successfully generated and verified proof for the program!"); + + Ok(()) +} diff --git a/rom-setup/Cargo.toml b/rom-setup/Cargo.toml index cbc095a77..6dc313989 100644 --- a/rom-setup/Cargo.toml +++ b/rom-setup/Cargo.toml @@ -11,13 +11,13 @@ categories = { workspace = true } sm-rom = { workspace = true } zisk-core = { workspace = true } zisk-pil = { workspace = true } +zisk-common = { workspace = true } tracing = { workspace = true } fields = { workspace = true } proofman-common = { workspace = true } colored = { workspace = true } anyhow = { workspace = true } -bytemuck = { workspace = true } blake3 = "1.3.1" [features] diff --git a/rom-setup/src/asm_setup.rs b/rom-setup/src/asm_setup.rs index b7d5d964b..a452783e6 100644 --- a/rom-setup/src/asm_setup.rs +++ b/rom-setup/src/asm_setup.rs @@ -6,7 +6,51 @@ use std::{ use anyhow::Result; use zisk_core::{is_elf_file, AsmGenerationMethod, Riscv2zisk}; -pub fn generate_assembly( +use crate::get_elf_data_hash_from_path; + +/// Check if all assembly binary files exist for a given ELF and output path +pub fn assembly_files_exist(elf: &Path, output_path: &Path) -> Result { + let elf_hash = get_elf_data_hash_from_path(elf)?; + + let stem = elf.file_stem().unwrap().to_str().unwrap(); + let new_filename = format!("{stem}-{elf_hash}.tmp"); + let base_path = output_path.join(new_filename); + let file_stem = base_path.file_stem().unwrap().to_str().unwrap(); + + let bin_mt_file = format!("{file_stem}-mt.bin"); + let bin_mt_file = base_path.with_file_name(bin_mt_file); + + let bin_rh_file = format!("{file_stem}-rh.bin"); + let bin_rh_file = base_path.with_file_name(bin_rh_file); + + let bin_mo_file = format!("{file_stem}-mo.bin"); + let bin_mo_file = base_path.with_file_name(bin_mo_file); + + Ok(bin_mt_file.exists() && bin_rh_file.exists() && bin_mo_file.exists()) +} + +pub fn gen_assembly( + _elf: &Path, + _zisk_path: &Option, + _output_dir: &Option, + _hints: bool, + _verbose: bool, +) -> Result<(), anyhow::Error> { + // Assembly setup is not needed on macOS due to the lack of support for assembly generation. + #[cfg(not(target_os = "macos"))] + { + let output_path = crate::get_output_path(_output_dir)?; + let elf_hash = get_elf_data_hash_from_path(_elf)?; + + tracing::info!("Computing assembly setup"); + let zisk_path = crate::get_zisk_path(_zisk_path.as_ref()); + _generate_assembly(_elf, &elf_hash, &zisk_path, output_path.as_path(), _hints, _verbose)?; + tracing::info!("Assembly setup generated at {}", output_path.display()); + } + Ok(()) +} + +fn _generate_assembly( elf: &Path, elf_hash: &str, zisk_path: &Path, @@ -45,7 +89,7 @@ pub fn generate_assembly( .for_each(|(file, gen_method)| { let asm_file = file.with_extension("asm"); // Convert the ELF file to Zisk format and generates an assembly file - let rv2zk = Riscv2zisk::new(elf_file_path.to_str().unwrap().to_string()); + let rv2zk = Riscv2zisk::new(&file_data); rv2zk .runfile(asm_file.to_str().unwrap().to_string(), *gen_method, false, false, hints) .expect("Error converting elf to assembly"); diff --git a/rom-setup/src/lib.rs b/rom-setup/src/lib.rs index 8ce3a89e0..1b64aea08 100644 --- a/rom-setup/src/lib.rs +++ b/rom-setup/src/lib.rs @@ -1,11 +1,7 @@ mod asm_setup; -mod rom_full_setup; mod rom_merkle; -mod rom_vkey; mod utils; pub use asm_setup::*; -pub use rom_full_setup::*; pub use rom_merkle::*; -pub use rom_vkey::*; pub use utils::*; diff --git a/rom-setup/src/rom_full_setup.rs b/rom-setup/src/rom_full_setup.rs deleted file mode 100644 index 63200a054..000000000 --- a/rom-setup/src/rom_full_setup.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::{ - fs, - path::{Path, PathBuf}, -}; - -use colored::Colorize; - -use crate::{ensure_dir_exists, get_elf_data_hash, DEFAULT_CACHE_PATH}; - -#[allow(unused_variables)] -pub fn rom_full_setup( - elf: &Path, - proving_key: &Path, - zisk_path: &Path, - output_dir: &Option, - hints: bool, - verbose: bool, -) -> std::result::Result<(), anyhow::Error> { - let output_path = if output_dir.is_none() { - let cache_path = std::env::var("HOME") - .map(PathBuf::from) - .map(|home| home.join(DEFAULT_CACHE_PATH)) - .unwrap_or_else(|_| panic!("$HOME environment variable is not set")); - - ensure_dir_exists(&cache_path); - cache_path - } else { - ensure_dir_exists(output_dir.as_ref().unwrap()); - output_dir.clone().unwrap() - }; - - let output_path = fs::canonicalize(&output_path) - .unwrap_or_else(|_| panic!("Failed to get absolute path for {output_path:?}")); - - println!(); - - tracing::info!("Computing setup for ROM {}", elf.display()); - - tracing::info!("Computing ELF hash"); - let elf_hash = get_elf_data_hash(elf)?; - - tracing::info!("Computing merkle root"); - crate::rom_merkle_setup(elf, &elf_hash, output_path.as_path(), proving_key, false)?; - // Assembly setup is not needed on macOS due to the lack of support for assembly generation. - #[cfg(not(target_os = "macos"))] - { - tracing::info!("Computing assembly setup"); - crate::generate_assembly(elf, &elf_hash, zisk_path, output_path.as_path(), hints, verbose)?; - } - - println!(); - tracing::info!( - "{} {}", - "ROM setup successfully completed at".bright_green().bold(), - output_path.display() - ); - - Ok(()) -} diff --git a/rom-setup/src/rom_merkle.rs b/rom-setup/src/rom_merkle.rs index 2a57ebc48..bef1eda63 100644 --- a/rom-setup/src/rom_merkle.rs +++ b/rom-setup/src/rom_merkle.rs @@ -1,37 +1,56 @@ -use std::path::Path; - -use crate::{gen_elf_hash, get_elf_bin_file_path_with_hash, get_rom_blowup_factor_and_arity}; - -pub fn rom_merkle_setup( - elf: &Path, - elf_hash: &str, - output_path: &Path, +use fields::PrimeField64; +use std::path::{Path, PathBuf}; +use zisk_common::ElfBinaryLike; + +use crate::{ + gen_elf_hash, get_elf_bin_file_path_with_hash, get_elf_bin_verkey_file_path_with_hash, + get_elf_data_hash, get_elf_vk, get_output_path, get_rom_info, +}; + +pub fn rom_merkle_setup( + elf: &impl ElfBinaryLike, + output_dir: &Option, proving_key: &Path, - mut check: bool, -) -> Result<(), anyhow::Error> { - // Check if the path is a file and not a directory - if !elf.is_file() { - tracing::error!("Error: The specified ROM path is not a file: {}", elf.display()); - std::process::exit(1); - } +) -> Result<(PathBuf, Vec), anyhow::Error> { + let output_path = get_output_path(output_dir)?; - let (blowup_factor, merkle_tree_arity) = get_rom_blowup_factor_and_arity(proving_key); + let elf_hash = get_elf_data_hash(elf)?; + + let rom_info = get_rom_info(proving_key)?; let elf_bin_path = get_elf_bin_file_path_with_hash( - elf, - elf_hash, - output_path, - blowup_factor, - merkle_tree_arity, + &elf_hash, + &output_path, + rom_info.blowup_factor, + rom_info.merkle_tree_arity, + )?; + + let elf_verkey_bin_path = get_elf_bin_verkey_file_path_with_hash( + &elf_hash, + &output_path, + rom_info.blowup_factor, + rom_info.merkle_tree_arity, )?; - if !elf_bin_path.exists() { - check = false; + if elf_bin_path.exists() && elf_verkey_bin_path.exists() { + let verkey = get_elf_vk(elf_verkey_bin_path.as_path())? + .ok_or_else(|| anyhow::anyhow!("Failed to read existing verkey file"))?; + + return Ok((elf_bin_path, verkey)); } - let root = gen_elf_hash(elf, elf_bin_path.as_path(), blowup_factor, merkle_tree_arity, check)?; + let root = gen_elf_hash::( + elf.elf(), + elf_bin_path.as_path(), + rom_info.blowup_factor, + rom_info.merkle_tree_arity, + )?; tracing::info!("Root hash: {:?}", root); - Ok(()) + let verkey: Vec = root.iter().flat_map(|x| x.as_canonical_u64().to_le_bytes()).collect(); + + std::fs::write(&elf_verkey_bin_path, &verkey)?; + + Ok((elf_bin_path, verkey)) } diff --git a/rom-setup/src/rom_vkey.rs b/rom-setup/src/rom_vkey.rs deleted file mode 100644 index 68476cd38..000000000 --- a/rom-setup/src/rom_vkey.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::path::{Path, PathBuf}; - -use crate::{gen_elf_hash, get_rom_blowup_factor_and_arity}; -use fields::{Goldilocks, PrimeField}; -use std::fs; -use std::fs::File; -use std::io::Write; - -pub fn rom_vkey( - elf: &Path, - verkey_file: &Option, - proving_key: &Path, -) -> Result, anyhow::Error> { - // Check if the path is a file and not a directory - if !elf.is_file() { - tracing::error!("Error: The specified ROM path is not a file: {}", elf.display()); - std::process::exit(1); - } - - let (blowup_factor, merkle_tree_arity) = get_rom_blowup_factor_and_arity(proving_key); - - let root = gen_elf_hash(elf, &PathBuf::new(), blowup_factor, merkle_tree_arity, false)?; - - let verkey: Vec = - root.iter().flat_map(|x| x.as_canonical_biguint().to_bytes_le()).collect(); - - if let Some(verkey_file) = verkey_file { - let parent = Path::new(&verkey_file).parent().unwrap(); - fs::create_dir_all(parent)?; - let mut file = File::create(verkey_file)?; - file.write_all(&verkey)?; - file.flush()?; - } - - tracing::info!("Root hash: {:?}", root); - - Ok(root) -} diff --git a/rom-setup/src/utils.rs b/rom-setup/src/utils.rs index 42cf0fd17..b612c4570 100644 --- a/rom-setup/src/utils.rs +++ b/rom-setup/src/utils.rs @@ -1,40 +1,87 @@ use anyhow::{Context, Result}; -use fields::{Field, Goldilocks}; +use fields::{Goldilocks, PrimeField64}; use proofman_common::{ write_custom_commit_trace, GlobalInfo, ProofType, ProofmanResult, StarkInfo, }; use sm_rom::RomSM; +use std::env; use std::fs; +use std::fs::File; +use std::io::Read; use std::path::{Path, PathBuf}; +use zisk_common::ElfBinaryLike; use zisk_pil::{RomRomTrace, PILOUT_HASH}; pub const DEFAULT_CACHE_PATH: &str = ".zisk/cache"; -pub fn gen_elf_hash( - rom_path: &Path, +/// Gets the user's home directory as specified by the HOME environment variable. +pub fn get_home_dir() -> String { + env::var("HOME").expect("get_home_dir() failed to get HOME environment variable") +} + +/// Gets the default zisk folder location in the home installation directory. +pub fn get_default_zisk_path() -> PathBuf { + let zisk_path = format!("{}/.zisk/zisk", get_home_dir()); + PathBuf::from(zisk_path) +} + +/// Gets the zisk folder. +/// Uses the default one if not specified by user. +pub fn get_zisk_path(zisk_path: Option<&PathBuf>) -> PathBuf { + zisk_path.cloned().unwrap_or_else(get_default_zisk_path) +} + +pub fn get_output_path(output_dir: &Option) -> Result { + let output_path = if output_dir.is_none() { + let cache_path = std::env::var("HOME") + .map(PathBuf::from) + .map(|home| home.join(DEFAULT_CACHE_PATH)) + .unwrap_or_else(|_| panic!("$HOME environment variable is not set")); + + ensure_dir_exists(&cache_path); + cache_path + } else { + ensure_dir_exists(output_dir.as_ref().unwrap()); + output_dir.clone().unwrap() + }; + + let output_path = fs::canonicalize(&output_path) + .unwrap_or_else(|_| panic!("Failed to get absolute path for {output_path:?}")); + + Ok(output_path) +} + +pub fn gen_elf_hash( + elf: &[u8], rom_buffer_path: &Path, blowup_factor: u64, merkle_tree_arity: u64, - check: bool, -) -> ProofmanResult> { - let buffer = vec![ - Goldilocks::ZERO; - RomRomTrace::::NUM_ROWS * RomRomTrace::::ROW_SIZE - ]; - let mut custom_rom_trace: RomRomTrace = RomRomTrace::new_from_vec(buffer)?; +) -> ProofmanResult> { + let buffer = vec![F::ZERO; RomRomTrace::::NUM_ROWS * RomRomTrace::::ROW_SIZE]; + let mut custom_rom_trace: RomRomTrace = RomRomTrace::new_from_vec(buffer)?; - RomSM::compute_custom_trace_rom(rom_path.to_path_buf(), &mut custom_rom_trace); + RomSM::compute_custom_trace_rom(elf, &mut custom_rom_trace); write_custom_commit_trace( &mut custom_rom_trace, blowup_factor, merkle_tree_arity, rom_buffer_path, - check, ) } -pub fn get_elf_data_hash(elf_path: &Path) -> Result { +pub fn get_elf_vk(verkey_path: &Path) -> Result>> { + if !verkey_path.exists() { + return Ok(None); + } + + let mut file = File::open(verkey_path)?; + let mut root_bytes = [0u8; 32]; + file.read_exact(&mut root_bytes)?; + Ok(Some(root_bytes.to_vec())) +} + +pub fn get_elf_data_hash_from_path(elf_path: &Path) -> Result { let elf_data = fs::read(elf_path).with_context(|| format!("Error reading ELF file: {elf_path:?}"))?; @@ -43,40 +90,48 @@ pub fn get_elf_data_hash(elf_path: &Path) -> Result { Ok(hash) } -pub fn get_elf_bin_file_path( - elf_path: &Path, +pub fn get_elf_data_hash(elf: &impl ElfBinaryLike) -> Result { + let hash = blake3::hash(elf.elf()).to_hex().to_string(); + Ok(hash) +} + +pub fn get_elf_bin_file_path_with_hash( + hash: &str, default_cache_path: &Path, blowup_factor: u64, arity: u64, ) -> Result { - let elf_data = - fs::read(elf_path).with_context(|| format!("Error reading ELF file: {elf_path:?}"))?; + let pilout_hash = PILOUT_HASH; - let hash = blake3::hash(&elf_data).to_hex().to_string(); + let n = RomRomTrace::::NUM_ROWS; + + let gpu = if cfg!(feature = "gpu") { "_gpu" } else { "" }; + let rom_cache_file_name = format!( + "{}_{}_{}_{}_{}{}.bin", + hash, + pilout_hash, + &n.to_string(), + &blowup_factor.to_string(), + &arity.to_string(), + gpu + ); - get_elf_bin_file_path_with_hash(elf_path, &hash, default_cache_path, blowup_factor, arity) + Ok(default_cache_path.join(rom_cache_file_name)) } -pub fn get_elf_bin_file_path_with_hash( - elf_path: &Path, +pub fn get_elf_bin_verkey_file_path_with_hash( hash: &str, default_cache_path: &Path, blowup_factor: u64, arity: u64, ) -> Result { - if !elf_path.is_file() { - return Err(anyhow::anyhow!( - "Error: The specified ROM path is not a file: {}", - elf_path.display() - )); - } let pilout_hash = PILOUT_HASH; let n = RomRomTrace::::NUM_ROWS; let gpu = if cfg!(feature = "gpu") { "_gpu" } else { "" }; let rom_cache_file_name = format!( - "{}_{}_{}_{}_{}{}.bin", + "{}_{}_{}_{}_{}{}.verkey.bin", hash, pilout_hash, &n.to_string(), @@ -88,7 +143,12 @@ pub fn get_elf_bin_file_path_with_hash( Ok(default_cache_path.join(rom_cache_file_name)) } -pub fn get_rom_blowup_factor_and_arity(proving_key_path: &Path) -> (u64, u64) { +pub struct RomInfo { + pub blowup_factor: u64, + pub merkle_tree_arity: u64, +} + +pub fn get_rom_info(proving_key_path: &Path) -> ProofmanResult { let global_info = GlobalInfo::new(proving_key_path).expect("Failed to load global info from proving key"); let (airgroup_id, air_id) = global_info.get_air_id("Zisk", "Rom"); @@ -97,11 +157,10 @@ pub fn get_rom_blowup_factor_and_arity(proving_key_path: &Path) -> (u64, u64) { let stark_info_json = std::fs::read_to_string(&stark_info_path) .unwrap_or_else(|_| panic!("Failed to read file {}", &stark_info_path)); let stark_info = StarkInfo::from_json(&stark_info_json); - - ( - 1 << (stark_info.stark_struct.n_bits_ext - stark_info.stark_struct.n_bits), - stark_info.stark_struct.merkle_tree_arity, - ) + Ok(RomInfo { + blowup_factor: 1 << (stark_info.stark_struct.n_bits_ext - stark_info.stark_struct.n_bits), + merkle_tree_arity: stark_info.stark_struct.merkle_tree_arity, + }) } pub fn ensure_dir_exists(path: &PathBuf) { diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 839296374..b2cb6969d 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -20,9 +20,12 @@ asm-runner = { workspace = true } colored = { workspace = true } tracing = { workspace = true } zstd = { workspace = true } -bytemuck = { workspace = true } zisk-distributed-common = { workspace = true } +zisk-verifier = { workspace = true } zisk-witness = { workspace = true } +sha2 = { workspace = true } +bincode = { workspace = true } +serde = { workspace = true } [features] default = [] diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 282db02f8..83c519b68 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use crate::{ - get_asm_paths, get_proving_key, + get_proving_key, get_proving_key_snark, prover::{Asm, AsmProver, Emu, EmuProver, ZiskProver}, }; use colored::Colorize; @@ -28,21 +28,16 @@ pub struct Prove; /// ```rust,no_run /// use zisk_sdk::ProverClientBuilder; /// -/// let elf_path = std::path::PathBuf::from("path/to/program.elf"); /// let output_path = std::path::PathBuf::from("path/to/output"); /// /// let prover_emu = ProverClientBuilder::new() /// .emu() /// .verify_constraints() -/// .elf_path(elf_path.clone()) /// .build(); /// /// let prover_asm = ProverClientBuilder::new() /// .asm() /// .prove() -/// .elf_path(elf_path) -/// .save_proofs(true) -/// .output_dir(output_path) /// .unlock_mapped_memory(true) /// .build(); /// ``` @@ -50,11 +45,9 @@ pub struct Prove; pub struct ProverClientBuilder { // Common fields for both EMU and ASM aggregation: bool, - rma: bool, - compressed: bool, + snark_wrapper: bool, proving_key: Option, proving_key_snark: Option, - elf: Option, verify_constraints: bool, witness: bool, verbose: u8, @@ -66,14 +59,12 @@ pub struct ProverClientBuilder { asm_path: Option, base_port: Option, unlock_mapped_memory: bool, - with_hints: bool, // Prove-specific fields (only available when Operation = Prove) - save_proofs: bool, - output_dir: Option, - verify_proofs: bool, - minimal_memory: bool, - gpu_params: Option, + gpu_params: ParamsGPU, + + // Indicates if building a verifier only + verifier: bool, // Phantom data to track state _backend: std::marker::PhantomData, @@ -83,7 +74,11 @@ pub struct ProverClientBuilder { impl ProverClientBuilder<(), ()> { #[must_use] pub fn new() -> Self { - Self { aggregation: true, rma: true, ..Default::default() } + Self { aggregation: true, snark_wrapper: false, ..Default::default() } + } + + pub fn new_verifier() -> Self { + Self { verifier: true, ..Default::default() } } /// Configure for Emulator backend @@ -97,6 +92,11 @@ impl ProverClientBuilder<(), ()> { pub fn asm(self) -> ProverClientBuilder { self.into() } + + pub fn build(self) -> Result> { + let builder: ProverClientBuilder = self.emu().into(); + builder.build_emu() + } } // Common methods available for any backend @@ -136,17 +136,9 @@ impl ProverClientBuilder { self } - /// Set RMA. - #[must_use] - pub fn rma(mut self, use_rma: bool) -> Self { - self.rma = use_rma; - self - } - - /// Enables final vadcop is compressed proof. #[must_use] - pub fn compressed(mut self, enable: bool) -> Self { - self.compressed = enable; + pub fn snark(mut self) -> Self { + self.snark_wrapper = true; self } @@ -174,12 +166,6 @@ impl ProverClientBuilder { self } - #[must_use] - pub fn elf_path(mut self, elf_path: PathBuf) -> Self { - self.elf = Some(elf_path); - self - } - #[must_use] pub fn verbose(mut self, verbose: u8) -> Self { self.verbose = verbose; @@ -236,43 +222,15 @@ impl ProverClientBuilder { self.unlock_mapped_memory = unlock; self } - - #[must_use] - pub fn with_hints(mut self, with_hints: bool) -> Self { - self.with_hints = with_hints; - self - } } -// Prove-specific methods (available for both backends when operation is Prove) -impl ProverClientBuilder { - #[must_use] - pub fn save_proofs(mut self, save: bool) -> Self { - self.save_proofs = save; - self - } - - #[must_use] - pub fn output_dir(mut self, output_dir: PathBuf) -> Self { - self.output_dir = Some(output_dir); - self - } - - #[must_use] - pub fn verify_proofs(mut self, verify: bool) -> Self { - self.verify_proofs = verify; - self - } - - #[must_use] - pub fn minimal_memory(mut self, minimal: bool) -> Self { - self.minimal_memory = minimal; - self - } - +// Prove-specific methods (available for any operation state - will use defaults if not in Prove mode) +impl ProverClientBuilder { #[must_use] - pub fn gpu(mut self, gpu_params: ParamsGPU) -> Self { - self.gpu_params = Some(gpu_params); + pub fn gpu(mut self, gpu_params: Option) -> Self { + if let Some(gpu_params) = gpu_params { + self.gpu_params = gpu_params; + } self } } @@ -285,12 +243,10 @@ impl ProverClientBuilder { /// ```rust,no_run /// use zisk_sdk::ProverClientBuilder; /// - /// let elf_path = std::path::PathBuf::from("path/to/program.elf"); /// /// let prover = ProverClientBuilder::new() /// .emu() /// .verify_constraints() - /// .elf_path(elf_path) /// .build(); /// ``` pub fn build(self) -> Result> { @@ -298,6 +254,13 @@ impl ProverClientBuilder { } } +impl ProverClientBuilder { + pub fn build(self) -> Result> { + let builder: ProverClientBuilder = self.into(); + builder.build_emu() + } +} + impl ProverClientBuilder { /// Builds an [`EmuProver`] configured for proof generation. /// @@ -305,12 +268,9 @@ impl ProverClientBuilder { /// ```rust,no_run /// use zisk_sdk::ProverClientBuilder; /// - /// let elf_path = std::path::PathBuf::from("path/to/program.elf"); - /// /// let prover = ProverClientBuilder::new() /// .emu() /// .prove() - /// .elf_path(elf_path) /// .build(); /// ``` pub fn build(self) -> Result> { @@ -321,14 +281,7 @@ impl ProverClientBuilder { impl ProverClientBuilder { fn build_emu(self) -> Result> { let proving_key = get_proving_key(self.proving_key.as_ref()); - let proving_key_snark = None; - let elf = self.elf.ok_or_else(|| anyhow::anyhow!("ELF path is required"))?; - - let output_dir = if !self.verify_constraints { - Some(self.output_dir.unwrap_or_else(|| "tmp".into())) - } else { - None - }; + let proving_key_snark = get_proving_key_snark(self.proving_key_snark.as_ref()); if self.print_command_info { Self::print_emu_command_info( @@ -336,28 +289,24 @@ impl ProverClientBuilder { self.verify_constraints, &proving_key, &proving_key_snark, - &elf, - output_dir.as_ref(), ); } - let emu = EmuProver::new( - self.verify_constraints, - self.aggregation, - self.rma, - self.compressed, - proving_key, - proving_key_snark, - elf, - self.verbose, - self.shared_tables, - self.gpu_params.filter(|_| !self.verify_constraints).unwrap_or_default(), - self.verify_proofs, - self.minimal_memory, - self.save_proofs, - output_dir.clone(), - self.logging_config, - )?; + let emu = if self.verifier { + EmuProver::new_verifier(proving_key, proving_key_snark)? + } else { + EmuProver::new( + self.verify_constraints || self.witness, + self.aggregation, + self.snark_wrapper, + proving_key, + proving_key_snark, + self.verbose, + self.shared_tables, + self.gpu_params, + self.logging_config, + )? + }; Ok(ZiskProver::::new(emu)) } @@ -366,9 +315,7 @@ impl ProverClientBuilder { witness: bool, verify_constraints: bool, proving_key: &Path, - proving_key_snark: &Option, - elf: &Path, - output_dir: Option<&PathBuf>, + proving_key_snark: &Path, ) { if witness { println!("{: >12} StatsConstraints", "Command".bright_green().bold()); @@ -378,7 +325,6 @@ impl ProverClientBuilder { println!("{: >12} Prove", "Command".bright_green().bold()); } - println!("{: >12} {}", "ELF".bright_green().bold(), elf.display()); println!( "{: >12} {}", "Emulator".bright_green().bold(), @@ -386,17 +332,11 @@ impl ProverClientBuilder { ); println!("{: >12} {}", "Proving Key".bright_green().bold(), proving_key.display()); - if let Some(proving_key_snark) = proving_key_snark { - println!( - "{: >12} {}", - "Proving key SNARK".bright_green().bold(), - proving_key_snark.display() - ); - } - - if let Some(output_dir) = output_dir { - println!("{: >12} {}", "Output Dir".bright_green().bold(), output_dir.display()); - } + println!( + "{: >12} {}", + "Proving key SNARK".bright_green().bold(), + proving_key_snark.display() + ); println!(); } @@ -410,12 +350,9 @@ impl ProverClientBuilder { /// ```rust,no_run /// use zisk_sdk::ProverClientBuilder; /// - /// let elf_path = std::path::PathBuf::from("path/to/program.elf"); - /// /// let prover = ProverClientBuilder::new() /// .asm() /// .verify_constraints() - /// .elf_path(elf_path) /// .build(); /// ``` pub fn build(self) -> Result> @@ -427,6 +364,17 @@ impl ProverClientBuilder { } } +impl ProverClientBuilder { + pub fn build(self) -> Result> + where + F: PrimeField64, + GoldilocksQuinticExtension: ExtensionField, + { + let builder: ProverClientBuilder = self.into(); + builder.build_asm() + } +} + impl ProverClientBuilder { /// Builds an [`AsmProver`] configured for proof generation. /// @@ -434,12 +382,9 @@ impl ProverClientBuilder { /// ```rust,no_run /// use zisk_sdk::ProverClientBuilder; /// - /// let elf_path = std::path::PathBuf::from("path/to/program.elf"); - /// /// let prover = ProverClientBuilder::new() /// .asm() /// .prove() - /// .elf_path(elf_path) /// .build(); /// ``` pub fn build(self) -> Result> @@ -458,16 +403,7 @@ impl ProverClientBuilder { GoldilocksQuinticExtension: ExtensionField, { let proving_key = get_proving_key(self.proving_key.as_ref()); - let proving_key_snark = None; - let elf = self.elf.ok_or_else(|| anyhow::anyhow!("ELF path is required"))?; - - let output_dir = if !self.verify_constraints { - Some(self.output_dir.unwrap_or_else(|| "tmp".into())) - } else { - None - }; - - let (asm_mt_filename, asm_rh_filename) = get_asm_paths(&elf)?; + let proving_key_snark = get_proving_key_snark(self.proving_key_snark.as_ref()); if self.print_command_info { Self::print_asm_command_info( @@ -475,33 +411,26 @@ impl ProverClientBuilder { self.verify_constraints, &proving_key, &proving_key_snark, - &elf, - output_dir.as_ref(), ); } - let asm = AsmProver::new( - self.verify_constraints, - self.aggregation, - self.rma, - self.compressed, - proving_key, - proving_key_snark, - elf, - self.verbose, - self.shared_tables, - asm_mt_filename, - asm_rh_filename, - self.base_port, - self.unlock_mapped_memory, - self.with_hints, - self.gpu_params.filter(|_| !self.verify_constraints).unwrap_or_default(), - self.verify_proofs, - self.minimal_memory, - self.save_proofs, - output_dir.clone(), - self.logging_config, - )?; + let asm = if self.verifier { + AsmProver::new_verifier(proving_key, proving_key_snark)? + } else { + AsmProver::new( + self.verify_constraints || self.witness, + self.aggregation, + self.snark_wrapper, + proving_key, + proving_key_snark, + self.verbose, + self.shared_tables, + self.base_port, + self.unlock_mapped_memory, + self.gpu_params, + self.logging_config, + )? + }; Ok(ZiskProver::::new(asm)) } @@ -510,9 +439,7 @@ impl ProverClientBuilder { witness: bool, verify_constraints: bool, proving_key: &Path, - proving_key_snark: &Option, - elf: &Path, - output_dir: Option<&PathBuf>, + proving_key_snark: &Path, ) { if witness { println!("{: >12} StatsConstraints", "Command".bright_green().bold()); @@ -522,20 +449,13 @@ impl ProverClientBuilder { println!("{: >12} Prove", "Command".bright_green().bold()); } - println!("{: >12} {}", "ELF".bright_green().bold(), elf.display()); - println!("{: >12} {}", "Proving Key".bright_green().bold(), proving_key.display()); + println!("{: >12} {}", "Proving key".bright_green().bold(), proving_key.display()); - if let Some(proving_key_snark) = proving_key_snark { - println!( - "{: >12} {}", - "Proving key SNARK".bright_green().bold(), - proving_key_snark.display() - ); - } - - if let Some(output_dir) = output_dir { - println!("{: >12} {}", "Output Dir".bright_green().bold(), output_dir.display()); - } + println!( + "{: >12} {}", + "Proving key SNARK".bright_green().bold(), + proving_key_snark.display() + ); println!(); } @@ -546,31 +466,23 @@ impl From> for ProverClientBuilder { fn from(builder: ProverClientBuilder<(), ()>) -> Self { Self { // Preserve common fields + verifier: builder.verifier, aggregation: builder.aggregation, witness: builder.witness, - rma: builder.rma, - compressed: builder.compressed, + snark_wrapper: builder.snark_wrapper, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, - elf: builder.elf, verbose: builder.verbose, shared_tables: builder.shared_tables, print_command_info: builder.print_command_info, logging_config: builder.logging_config, + gpu_params: builder.gpu_params, // Reset ASM-specific fields for EMU backend asm_path: None, base_port: None, unlock_mapped_memory: false, - with_hints: false, - - // Reset prove-specific fields (will be set when choosing operation) - save_proofs: false, - output_dir: None, - verify_proofs: false, - minimal_memory: false, - gpu_params: None, _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -582,31 +494,23 @@ impl From> for ProverClientBuilder { fn from(builder: ProverClientBuilder<(), ()>) -> Self { Self { // Preserve common fields + verifier: builder.verifier, aggregation: builder.aggregation, + snark_wrapper: builder.snark_wrapper, witness: builder.witness, - rma: builder.rma, - compressed: builder.compressed, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, - elf: builder.elf, verbose: builder.verbose, shared_tables: builder.shared_tables, print_command_info: builder.print_command_info, logging_config: builder.logging_config, + gpu_params: builder.gpu_params, // Preserve ASM-specific fields (user may have set defaults) asm_path: builder.asm_path, base_port: builder.base_port, unlock_mapped_memory: builder.unlock_mapped_memory, - with_hints: builder.with_hints, - - // Reset prove-specific fields (will be set when choosing operation) - save_proofs: false, - output_dir: None, - verify_proofs: false, - minimal_memory: false, - gpu_params: None, _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -620,31 +524,23 @@ impl From> fn from(builder: ProverClientBuilder) -> Self { Self { // Preserve common fields + verifier: builder.verifier, aggregation: builder.aggregation, + snark_wrapper: builder.snark_wrapper, witness: builder.witness, - rma: builder.rma, - compressed: builder.compressed, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, verify_constraints: builder.verify_constraints, - elf: builder.elf, verbose: builder.verbose, shared_tables: builder.shared_tables, print_command_info: builder.print_command_info, logging_config: builder.logging_config, + gpu_params: builder.gpu_params, // Preserve backend-specific fields (ASM or EMU) asm_path: builder.asm_path, base_port: builder.base_port, unlock_mapped_memory: builder.unlock_mapped_memory, - with_hints: builder.with_hints, - - // Initialize prove-specific fields to defaults for verify_constraints mode - save_proofs: false, // Not relevant for constraint verification - output_dir: None, // Not needed for constraint verification - verify_proofs: false, // Not applicable for constraint verification - minimal_memory: false, // Not relevant for constraint verification - gpu_params: None, // Not relevant for constraint verification _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -656,14 +552,14 @@ impl From> for ProverClientBuilder) -> Self { Self { // Preserve common fields + verifier: builder.verifier, aggregation: builder.aggregation, + snark_wrapper: builder.snark_wrapper, witness: builder.witness, - rma: builder.rma, - compressed: builder.compressed, proving_key: builder.proving_key, proving_key_snark: builder.proving_key_snark, verify_constraints: false, - elf: builder.elf, + gpu_params: builder.gpu_params, verbose: builder.verbose, shared_tables: builder.shared_tables, print_command_info: builder.print_command_info, @@ -673,14 +569,6 @@ impl From> for ProverClientBuilder, - pub proof: Option>, -} - #[macro_export] macro_rules! include_elf { - ($arg:tt) => {{ - include_bytes!(env!(concat!("ZISK_ELF_", $arg))) + ($arg:literal) => {{ + const WITH_HINTS: bool = option_env!(concat!("ZISK_ELF_", $arg, "_WITH_HINTS")).is_some(); + + ElfBinary { + elf: include_bytes!(env!(concat!("ZISK_ELF_", $arg))), + name: $arg, + with_hints: WITH_HINTS, + } }}; } diff --git a/sdk/src/proof.rs b/sdk/src/proof.rs deleted file mode 100644 index 952430cc9..000000000 --- a/sdk/src/proof.rs +++ /dev/null @@ -1,317 +0,0 @@ -/// Strongly-typed proof formats -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ProofFormat { - /// Raw, unprocessed proof data - Raw(RawProof), - /// Compressed proof using compression algorithms - Compressed(CompressedProof), - /// Wrapped proof with additional metadata - Wrapped(WrappedProof), -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Proof { - pub id: Option, - pub proof: Option>, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RawProof(pub Proof); - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CompressedProof { - pub data: Proof, - pub compression_info: CompressionInfo, -} - -/// Information about compression applied to a proof -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CompressionInfo { - pub algorithm: String, - pub level: u32, - pub original_size: usize, - pub compressed_size: usize, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct WrappedProof { - pub data: Proof, - pub metadata: ProofMetadata, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ProofMetadata { - pub created_at: std::time::SystemTime, - pub version: String, - pub additional_data: std::collections::HashMap, -} - - - - - -use anyhow::{Result, Context}; - -// ...existing code... - -// Utility functions for converting between bytes and u64 vectors -fn bytes_to_u64_vec(bytes: &[u8]) -> Vec { - bytes.chunks(8) - .map(|chunk| { - let mut array = [0u8; 8]; - for (i, &byte) in chunk.iter().enumerate() { - if i < 8 { - array[i] = byte; - } - } - u64::from_le_bytes(array) - }) - .collect() -} - -fn u64_vec_to_bytes(data: &[u64]) -> Vec { - data.iter() - .flat_map(|&num| num.to_le_bytes()) - .collect() -} - -impl CompressedProof { - /// Create a compressed proof from raw proof with default compression level (3) - pub fn from_raw(raw: RawProof) -> Result { - Self::from_raw_with_level(raw, 3) - } - - /// Create a compressed proof from raw proof with specified compression level (1-22) - pub fn from_raw_with_level(raw: RawProof, level: i32) -> Result { - // Serialize the proof to bytes - let original_data = bincode::serialize(&raw.0) - .context("Failed to serialize proof for compression")?; - - let original_size = original_data.len(); - - // Compress using zstd - let compressed_data = zstd::encode_all(original_data.as_slice(), level) - .context("Failed to compress proof with zstd")?; - - let compressed_size = compressed_data.len(); - - // Create a new proof with compressed data - let compressed_proof = Proof { - id: raw.0.id.clone(), - proof: Some(bytes_to_u64_vec(&compressed_data)), - }; - - let compression_info = CompressionInfo { - algorithm: "zstd".to_string(), - level: level as u32, - original_size, - compressed_size, - }; - - Ok(CompressedProof { - data: compressed_proof, - compression_info, - }) - } - - /// Get compression ratio (original_size / compressed_size) - pub fn compression_ratio(&self) -> f64 { - if self.compression_info.compressed_size == 0 { - return 1.0; - } - self.compression_info.original_size as f64 / self.compression_info.compressed_size as f64 - } - - /// Get space saved as percentage - pub fn space_saved_percent(&self) -> f64 { - if self.compression_info.original_size == 0 { - return 0.0; - } - let saved = self.compression_info.original_size.saturating_sub(self.compression_info.compressed_size); - (saved as f64 / self.compression_info.original_size as f64) * 100.0 - } -} - -impl RawProof { - /// Decompress from a compressed proof - pub fn from_compressed(compressed: CompressedProof) -> Result { - if compressed.compression_info.algorithm != "zstd" { - return Err(anyhow::anyhow!( - "Unsupported compression algorithm: {}", - compressed.compression_info.algorithm - )); - } - - // Extract compressed bytes from the proof - let compressed_bytes = match &compressed.data.proof { - Some(data) => u64_vec_to_bytes(data), - None => return Err(anyhow::anyhow!("No proof data found")), - }; - - // Decompress using zstd - let decompressed_data = zstd::decode_all(compressed_bytes.as_slice()) - .context("Failed to decompress proof with zstd")?; - - // Deserialize back to proof - let original_proof: Proof = bincode::deserialize(&decompressed_data) - .context("Failed to deserialize decompressed proof")?; - - Ok(RawProof(original_proof)) - } -} - -// From trait implementations for basic conversions -impl From for RawProof { - fn from(proof: Proof) -> Self { - RawProof(proof) - } -} - -impl From for ProofFormat { - fn from(raw: RawProof) -> Self { - ProofFormat::Raw(raw) - } -} - -impl From for ProofFormat { - fn from(compressed: CompressedProof) -> Self { - ProofFormat::Compressed(compressed) - } -} - -impl From for ProofFormat { - fn from(wrapped: WrappedProof) -> Self { - ProofFormat::Wrapped(wrapped) - } -} - -// TryFrom for fallible conversions (compression/decompression) -impl TryFrom for CompressedProof { - type Error = anyhow::Error; - - fn try_from(raw: RawProof) -> Result { - CompressedProof::from_raw(raw) - } -} - -impl TryFrom for RawProof { - type Error = anyhow::Error; - - fn try_from(compressed: CompressedProof) -> Result { - RawProof::from_compressed(compressed) - } -} - -// Additional From implementations for convenience -impl From<&RawProof> for Result { - fn from(raw: &RawProof) -> Self { - CompressedProof::from_raw(raw.clone()) - } -} - -impl From<&CompressedProof> for Result { - fn from(compressed: &CompressedProof) -> Self { - RawProof::from_compressed(compressed.clone()) - } -} - -// ProofFormat convenience methods -impl ProofFormat { - /// Extract the underlying proof data regardless of format - pub fn proof(&self) -> &Proof { - match self { - ProofFormat::Raw(raw) => &raw.0, - ProofFormat::Compressed(compressed) => &compressed.data, - ProofFormat::Wrapped(wrapped) => &wrapped.data, - } - } - - /// Check if this is a raw proof - pub fn is_raw(&self) -> bool { - matches!(self, ProofFormat::Raw(_)) - } - - /// Check if this is a compressed proof - pub fn is_compressed(&self) -> bool { - matches!(self, ProofFormat::Compressed(_)) - } - - /// Check if this is a wrapped proof - pub fn is_wrapped(&self) -> bool { - matches!(self, ProofFormat::Wrapped(_)) - } - - /// Try to compress this proof format - pub fn try_compress(self) -> Result { - match self { - ProofFormat::Raw(raw) => Ok(ProofFormat::Compressed(raw.try_into()?)), - ProofFormat::Compressed(_) => Ok(self), // Already compressed - ProofFormat::Wrapped(wrapped) => { - let raw = RawProof(wrapped.data); - Ok(ProofFormat::Compressed(raw.try_into()?)) - } - } - } - - /// Try to decompress this proof format - pub fn try_decompress(self) -> Result { - match self { - ProofFormat::Raw(_) => Ok(self), // Already raw - ProofFormat::Compressed(compressed) => Ok(ProofFormat::Raw(compressed.try_into()?)), - ProofFormat::Wrapped(_) => Ok(self), // Keep as wrapped - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_compression_with_from() -> Result<()> { - let original_proof = Proof { - id: Some("test_proof".to_string()), - proof: Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), - }; - - let raw_proof = RawProof(original_proof.clone()); - - // Using From trait - let compressed: CompressedProof = raw_proof.try_into()?; - - // Check compression info - assert_eq!(compressed.compression_info.algorithm, "zstd"); - assert!(compressed.compression_info.original_size > 0); - - // Using From trait to decompress - let decompressed: RawProof = compressed.try_into()?; - - // Should match original - assert_eq!(decompressed.0, original_proof); - - Ok(()) - } - - #[test] - fn test_compression_methods() -> Result<()> { - let proof = Proof { - id: Some("test".to_string()), - proof: Some(vec![42; 100]), // Larger data for better compression - }; - - let raw = RawProof(proof); - - // Test different compression levels - let compressed_default = CompressedProof::from_raw(raw.clone())?; - let compressed_high = CompressedProof::from_raw_with_level(raw.clone(), 9)?; - - // Higher compression should result in smaller size (usually) - assert!(compressed_high.compression_info.level > compressed_default.compression_info.level); - - // Test compression ratio - assert!(compressed_default.compression_ratio() > 1.0); - assert!(compressed_default.space_saved_percent() > 0.0); - - Ok(()) - } -} \ No newline at end of file diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 98aed682f..f2c94289b 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -1,20 +1,24 @@ +use crate::get_asm_paths; use crate::{ - check_paths_exist, create_debug_info, ensure_custom_commits, + check_paths_exist, ensure_custom_commits, prover::{ProverBackend, ProverEngine, ZiskBackend}, - RankInfo, ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, - ZiskProveResult, ZiskVerifyConstraintsResult, + RankInfo, ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, ZiskProgramVK, + ZiskProof, ZiskProveResult, ZiskPublics, ZiskVerifyConstraintsResult, }; +use crate::{ProofMode, ProofOpts}; use asm_runner::{AsmRunnerOptions, AsmServices}; -use proofman::{AggProofs, ProofMan, ProvePhase, ProvePhaseInputs}; -use proofman_common::{initialize_logger, ParamsGPU, ProofOptions}; +use proofman::{AggProofs, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; +use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, VerboseMode}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rom_setup::DEFAULT_CACHE_PATH; +use std::sync::OnceLock; use std::{collections::HashMap, path::PathBuf}; use tracing::info; use zisk_common::io::{StreamSource, ZiskStdin}; +use zisk_common::ElfBinaryLike; use zisk_common::ExecutorStatsHandle; use zisk_distributed_common::LoggingConfig; -use zisk_witness::WitnessLibrary; +use zisk_witness::get_packed_info; use anyhow::Result; @@ -33,50 +37,38 @@ impl AsmProver { pub fn new( verify_constraints: bool, aggregation: bool, - rma: bool, - compressed: bool, + snark_wrapper: bool, proving_key: PathBuf, - proving_key_snark: Option, - elf: PathBuf, + proving_key_snark: PathBuf, verbose: u8, shared_tables: bool, - asm_mt_filename: String, - asm_rh_filename: String, base_port: Option, unlock_mapped_memory: bool, - with_hints: bool, gpu_params: ParamsGPU, - verify_proofs: bool, - minimal_memory: bool, - save_proofs: bool, - output_dir: Option, logging_config: Option, ) -> Result { let core_prover = AsmCoreProver::new( verify_constraints, aggregation, - rma, - compressed, + snark_wrapper, proving_key, proving_key_snark, - elf, verbose, shared_tables, - asm_mt_filename, - asm_rh_filename, base_port, unlock_mapped_memory, - with_hints, gpu_params, - verify_proofs, - minimal_memory, - save_proofs, - output_dir, logging_config, )?; Ok(Self { core_prover }) } + + pub fn new_verifier(proving_key: PathBuf, proving_key_snark: PathBuf) -> Result { + let core_prover = AsmCoreProver::new_verifier(proving_key, proving_key_snark)?; + + Ok(Self { core_prover }) + } } impl ProverEngine for AsmProver { @@ -88,71 +80,131 @@ impl ProverEngine for AsmProver { self.core_prover.rank_info.local_rank } - fn set_stdin(&self, stdin: ZiskStdin) { - self.core_prover.backend.witness_lib.set_stdin(stdin); + fn set_stdin(&self, stdin: ZiskStdin) -> Result<()> { + self.core_prover.backend.set_stdin(stdin) } - fn set_hints_stream(&self, hints_stream: StreamSource) -> anyhow::Result<()> { - self.core_prover.backend.witness_lib.set_hints_stream(hints_stream) + fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()> { + self.core_prover.backend.set_hints_stream(hints_stream) } fn executed_steps(&self) -> u64 { self.core_prover .backend - .witness_lib .execution_result() .map(|(exec_result, _)| exec_result.steps) .unwrap_or(0) } - fn execute( - &self, - stdin: ZiskStdin, - hints_stream: Option, - output_path: Option, - ) -> Result { - self.core_prover.backend.execute(stdin, hints_stream, output_path) + fn setup(&self, elf: &impl ElfBinaryLike) -> Result { + let proving_key = self.core_prover.backend.get_proving_key_path(); + let (rom_bin_path, vk) = ensure_custom_commits(proving_key, elf)?; + let custom_commits_map = HashMap::from([("rom".to_string(), rom_bin_path)]); + + let default_cache_path = std::env::var("HOME") + .map(PathBuf::from) + .map_err(|e| anyhow::anyhow!("Failed to read HOME environment variable: {e}"))? + .join(DEFAULT_CACHE_PATH); + + let (asm_mt_filename, asm_rh_filename) = get_asm_paths(elf)?; + + let asm_mt_path = default_cache_path.join(asm_mt_filename); + let asm_rh_path = default_cache_path.join(asm_rh_filename); + + check_paths_exist(&asm_mt_path)?; + check_paths_exist(&asm_rh_path)?; + + timer_start_info!(STARTING_ASM_MICROSERVICES); + let world_rank = self.core_prover.rank_info.world_rank; + let local_rank = self.core_prover.rank_info.local_rank; + let asm_services = AsmServices::new(world_rank, local_rank, self.core_prover.base_port); + + let asm_runner_options = AsmRunnerOptions::new() + .with_base_port(self.core_prover.base_port) + .with_world_rank(world_rank) + .with_local_rank(local_rank) + .with_unlock_mapped_memory(self.core_prover.unlock_mapped_memory); + + asm_services.start_asm_services(&asm_mt_path, asm_runner_options)?; + timer_stop_and_log_info!(STARTING_ASM_MICROSERVICES); + + let witness_lib = ZiskLibLoader::load_asm( + self.core_prover.verbose, + self.core_prover.shared_tables, + asm_mt_path.clone(), + asm_rh_path, + self.core_prover.base_port, + self.core_prover.unlock_mapped_memory, + elf.with_hints(), + )?; + + self.core_prover + .asm_services + .set(asm_services) + .map_err(|_| anyhow::anyhow!("ASM services have already been initialized."))?; + + self.core_prover.backend.register_witness_lib( + elf.elf(), + witness_lib, + custom_commits_map, + )?; + Ok(ZiskProgramVK { vk }) + } + + fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result { + self.core_prover.backend.execute(stdin, output_path) } fn stats( &self, stdin: ZiskStdin, - hints_stream: Option, debug_info: Option>, + minimal_memory: bool, mpi_node: Option, ) -> Result<(i32, i32, Option)> { - let debug_info = - create_debug_info(debug_info, self.core_prover.backend.proving_key.clone())?; - - self.core_prover.backend.stats(stdin, hints_stream, debug_info, mpi_node) + self.core_prover.backend.stats(stdin, debug_info, minimal_memory, mpi_node) } fn verify_constraints_debug( &self, stdin: ZiskStdin, - hints_stream: Option, debug_info: Option>, ) -> Result { - let debug_info = - create_debug_info(debug_info, self.core_prover.backend.proving_key.clone())?; + self.core_prover.backend.verify_constraints_debug(stdin, debug_info) + } - self.core_prover.backend.verify_constraints_debug(stdin, hints_stream, debug_info) + fn verify_constraints(&self, stdin: ZiskStdin) -> Result { + self.core_prover.backend.verify_constraints(stdin) } - fn verify_constraints( - &self, - stdin: ZiskStdin, - hints_stream: Option, - ) -> Result { - self.core_prover.backend.verify_constraints(stdin, hints_stream) + fn vk(&self, elf: &impl ElfBinaryLike) -> Result { + self.core_prover.backend.vk(elf) + } + + fn verify(&self, proof: &ZiskProof, publics: &ZiskPublics, vk: &ZiskProgramVK) -> Result<()> { + self.core_prover.backend.verify(proof, publics, vk) + } + + fn prove_debug(&self, stdin: ZiskStdin, proof_options: ProofOpts) -> Result { + self.core_prover.backend.prove_debug(stdin, proof_options) } fn prove( &self, stdin: ZiskStdin, - hints_stream: Option, + mode: ProofMode, + proof_options: ProofOpts, ) -> Result { - self.core_prover.backend.prove(stdin, hints_stream) + self.core_prover.backend.prove(stdin, mode, proof_options) + } + + fn prove_snark( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + vk: &ZiskProgramVK, + ) -> Result { + self.core_prover.backend.prove_snark(proof, publics, vk) } fn prove_phase( @@ -174,27 +226,33 @@ impl ProverEngine for AsmProver { self.core_prover.backend.aggregate_proofs(agg_proofs, last_proof, final_proof, options) } - fn mpi_broadcast(&self, data: &mut Vec) { - self.core_prover.backend.mpi_broadcast(data); + fn mpi_broadcast(&self, data: &mut Vec) -> Result<()> { + self.core_prover.backend.mpi_broadcast(data) } } pub struct AsmCoreProver { backend: ProverBackend, - asm_services: AsmServices, + asm_services: OnceLock, rank_info: RankInfo, + verbose: VerboseMode, + shared_tables: bool, + base_port: Option, + unlock_mapped_memory: bool, } impl Drop for AsmCoreProver { fn drop(&mut self) { // Shut down ASM microservices info!(">>> [{}] Stopping ASM microservices.", self.rank_info.world_rank); - if let Err(e) = self.asm_services.stop_asm_services() { - tracing::error!( - ">>> [{}] Failed to stop ASM microservices: {}", - self.rank_info.world_rank, - e - ); + if let Some(asm_services) = &self.asm_services.get() { + if let Err(e) = asm_services.stop_asm_services() { + tracing::error!( + ">>> [{}] Failed to stop ASM microservices: {}", + self.rank_info.world_rank, + e + ); + } } } } @@ -204,60 +262,24 @@ impl AsmCoreProver { pub fn new( verify_constraints: bool, aggregation: bool, - rma: bool, - compressed: bool, + use_snark_wrapper: bool, proving_key: PathBuf, - _proving_key_snark: Option, - elf: PathBuf, + proving_key_snark: PathBuf, verbose: u8, shared_tables: bool, - asm_mt_filename: String, - asm_rh_filename: String, base_port: Option, unlock_mapped_memory: bool, - with_hints: bool, gpu_params: ParamsGPU, - verify_proofs: bool, - minimal_memory: bool, - save_proofs: bool, - output_dir: Option, logging_config: Option, ) -> Result { - let rom_bin_path = ensure_custom_commits(&proving_key, &elf)?; - let custom_commits_map = HashMap::from([("rom".to_string(), rom_bin_path)]); - - let default_cache_path = std::env::var("HOME") - .map(PathBuf::from) - .map_err(|e| anyhow::anyhow!("Failed to read HOME environment variable: {e}"))? - .join(DEFAULT_CACHE_PATH); - - let asm_mt_path = default_cache_path.join(asm_mt_filename); - let asm_rh_path = default_cache_path.join(asm_rh_filename); - check_paths_exist(&proving_key)?; - check_paths_exist(&elf)?; - check_paths_exist(&asm_mt_path)?; - check_paths_exist(&asm_rh_path)?; - - let mut witness_lib = ZiskLibLoader::load_asm( - elf, - verbose.into(), - shared_tables, - asm_mt_path.clone(), - asm_rh_path, - base_port, - unlock_mapped_memory, - with_hints, - )?; - let proofman = ProofMan::new( proving_key.clone(), - custom_commits_map, verify_constraints, aggregation, gpu_params, verbose.into(), - witness_lib.get_packed_info(), + get_packed_info(), ) .map_err(|e| anyhow::anyhow!(e.to_string()))?; @@ -270,38 +292,40 @@ impl AsmCoreProver { initialize_logger(verbose.into(), Some(world_rank)); } - timer_start_info!(STARTING_ASM_MICROSERVICES); - let asm_services = AsmServices::new(world_rank, local_rank, base_port); - - let asm_runner_options = AsmRunnerOptions::new() - .with_verbose(verbose > 0) - .with_base_port(base_port) - .with_world_rank(world_rank) - .with_local_rank(local_rank) - .with_unlock_mapped_memory(unlock_mapped_memory); - - asm_services.start_asm_services(&asm_mt_path, asm_runner_options)?; - timer_stop_and_log_info!(STARTING_ASM_MICROSERVICES); + proofman.set_barrier(); - witness_lib.register_witness(&proofman.get_wcm())?; + let mut snark_wrapper = None; + if use_snark_wrapper { + check_paths_exist(&proving_key_snark)?; + snark_wrapper = Some(SnarkWrapper::new(&proving_key_snark, verbose.into())?); + } - proofman.set_barrier(); + let core = + ProverBackend::new(proofman, snark_wrapper, proving_key, Some(proving_key_snark)); - let core = ProverBackend { - verify_constraints, - aggregation, - rma, - compressed, - witness_lib, - proving_key: proving_key.clone(), - verify_proofs, - minimal_memory, - save_proofs, - output_dir, - proofman, + Ok(Self { + backend: core, + asm_services: OnceLock::new(), rank_info: RankInfo { world_rank, local_rank }, - }; + verbose: verbose.into(), + shared_tables, + base_port, + unlock_mapped_memory, + }) + } - Ok(Self { backend: core, asm_services, rank_info: RankInfo { world_rank, local_rank } }) + #[allow(clippy::too_many_arguments)] + pub fn new_verifier(proving_key: PathBuf, proving_key_snark: PathBuf) -> Result { + let core_prover = ProverBackend::new_verifier(proving_key, Some(proving_key_snark)); + + Ok(Self { + backend: core_prover, + asm_services: OnceLock::new(), + rank_info: RankInfo { world_rank: 0, local_rank: 0 }, + verbose: VerboseMode::Info, + shared_tables: false, + base_port: None, + unlock_mapped_memory: false, + }) } } diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 46d487691..6c716c3bb 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -1,60 +1,144 @@ +use crate::create_debug_info; +use crate::ZiskPublics; use crate::{ - Proof, RankInfo, ZiskAggPhaseResult, ZiskExecuteResult, ZiskPhaseResult, ZiskProveResult, - ZiskVerifyConstraintsResult, + ensure_custom_commits, ZiskAggPhaseResult, ZiskExecuteResult, ZiskPhaseResult, ZiskProgramVK, + ZiskProof, ZiskProveResult, ZiskVerifyConstraintsResult, }; +use crate::{ProofMode, ProofOpts}; use anyhow::Result; -use bytemuck::cast_slice; use colored::Colorize; use fields::Goldilocks; -use proofman::{AggProofs, ProofInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult}; -use proofman_common::{DebugInfo, ProofOptions}; -use std::{fs::File, io::Write, path::PathBuf}; -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; +use proofman::{ + get_vadcop_final_proof_vkey, verify_snark_proof, AggProofs, ProofInfo, ProofMan, ProvePhase, + ProvePhaseInputs, ProvePhaseResult, SnarkProof, SnarkProtocol, SnarkWrapper, +}; +use proofman_common::ProofOptions; +use proofman_util::VadcopFinalProof; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::OnceLock; use zisk_common::{ io::{StreamSource, ZiskStdin}, - ExecutorStatsHandle, ProofLog, ZiskExecutionResult, + ElfBinaryLike, ExecutorStatsHandle, ZiskExecutionResult, }; +use zisk_verifier::verify_zisk_proof; use zisk_witness::WitnessLib; pub(crate) struct ProverBackend { - pub verify_constraints: bool, - pub aggregation: bool, - pub rma: bool, - pub compressed: bool, - pub witness_lib: WitnessLib, - pub proving_key: PathBuf, - pub verify_proofs: bool, - pub minimal_memory: bool, - pub save_proofs: bool, - pub output_dir: Option, - pub proofman: ProofMan, - pub rank_info: RankInfo, + proofman: Option>, + snark_wrapper: Option>, + witness_lib: OnceLock>, + proving_key_path: PathBuf, + proving_key_snark_path: Option, } impl ProverBackend { + pub fn new( + proofman: ProofMan, + snark_wrapper: Option>, + proving_key_path: PathBuf, + proving_key_snark_path: Option, + ) -> Self { + Self { + proofman: Some(proofman), + snark_wrapper, + witness_lib: OnceLock::new(), + proving_key_path, + proving_key_snark_path, + } + } + + pub fn new_verifier( + proving_key_path: PathBuf, + proving_key_snark_path: Option, + ) -> Self { + Self { + proofman: None, + snark_wrapper: None, + witness_lib: OnceLock::new(), + proving_key_path, + proving_key_snark_path, + } + } + + pub fn get_proving_key_path(&self) -> &PathBuf { + &self.proving_key_path + } + + pub fn register_witness_lib( + &self, + elf: &[u8], + mut witness_lib: WitnessLib, + custom_commits_map: HashMap, + ) -> Result<()> { + let proofman = self.proofman.as_ref().ok_or_else(|| { + anyhow::anyhow!("Proofman is not initialized. Please initialize it before use.") + })?; + + witness_lib.register_witness(elf, &proofman.get_wcm())?; + + if self.witness_lib.set(witness_lib).is_err() { + return Err(anyhow::anyhow!("Witness library has already been registered.")); + } + + proofman + .register_custom_commits(custom_commits_map) + .map_err(|e| anyhow::anyhow!(e.to_string())) + } + + pub fn set_stdin(&self, stdin: ZiskStdin) -> Result<()> { + let witness_lib = self.witness_lib.get().ok_or_else(|| { + anyhow::anyhow!("Witness_lib is not initialized. Please initialize it before use.") + })?; + witness_lib.set_stdin(stdin); + Ok(()) + } + + pub fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()> { + let witness_lib = self.witness_lib.get().ok_or_else(|| { + anyhow::anyhow!("Witness_lib is not initialized. Please initialize it before use.") + })?; + witness_lib.set_hints_stream(hints_stream) + } + + pub fn execution_result(&self) -> Result<(ZiskExecutionResult, ExecutorStatsHandle)> { + let witness_lib = self.witness_lib.get().ok_or_else(|| { + anyhow::anyhow!("Witness_lib is not initialized. Please initialize it before use.") + })?; + + let (result, stats) = witness_lib.execution_result().ok_or_else(|| { + anyhow::anyhow!("Failed to get execution result from emulator prover") + })?; + + Ok((result, stats)) + } + pub(crate) fn execute( &self, stdin: ZiskStdin, - hints_stream: Option, output_path: Option, ) -> Result { - self.witness_lib.set_stdin(stdin); - if let Some(stream) = hints_stream { - self.witness_lib - .set_hints_stream(stream) - .map_err(|e| anyhow::anyhow!("Error setting hints stream: {}", e))?; - } + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot execute in verifier mode"))?; + + let witness_lib = self.witness_lib.get().ok_or_else(|| { + anyhow::anyhow!("witness_lib is not initialized. Please initialize it before use.") + })?; + + witness_lib.set_stdin(stdin); let start = std::time::Instant::now(); - self.proofman + proofman .execute_from_lib(output_path) .map_err(|e| anyhow::anyhow!("Error generating execution: {}", e))?; let elapsed = start.elapsed(); - let (result, _) = self.witness_lib.execution_result().ok_or_else(|| { + let (result, _) = witness_lib.execution_result().ok_or_else(|| { anyhow::anyhow!("Failed to get execution result from emulator prover") })?; @@ -64,20 +148,26 @@ impl ProverBackend { pub(crate) fn stats( &self, stdin: ZiskStdin, - hints_stream: Option, - debug_info: DebugInfo, + debug_info: Option>, + minimal_memory: bool, _mpi_node: Option, ) -> Result<(i32, i32, Option)> { - self.witness_lib.set_stdin(stdin); - if let Some(stream) = hints_stream { - self.witness_lib - .set_hints_stream(stream) - .map_err(|e| anyhow::anyhow!("Error setting hints stream: {}", e))?; - } + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot compute stats in verifier mode"))?; - let world_rank = self.proofman.get_world_rank(); - let local_rank = self.proofman.get_local_rank(); - let n_processes = self.proofman.get_n_processes(); + let witness_lib = self.witness_lib.get().ok_or_else(|| { + anyhow::anyhow!("witness_lib is not initialized. Please initialize it before use.") + })?; + + let debug_info = create_debug_info(debug_info, self.proving_key_path.clone())?; + + witness_lib.set_stdin(stdin); + + let world_rank = proofman.get_world_rank(); + let local_rank = proofman.get_local_rank(); + let n_processes = proofman.get_n_processes(); let mut is_active = true; @@ -87,7 +177,7 @@ impl ProverBackend { } } - self.proofman.split_active_processes(is_active); + proofman.split_active_processes(is_active); if !is_active { println!( @@ -99,24 +189,15 @@ impl ProverBackend { return Ok((world_rank, n_processes, None)); } - self.proofman + proofman .compute_witness_from_lib( &debug_info, - ProofOptions::new( - false, - false, - false, - false, - false, - self.minimal_memory, - false, - PathBuf::new(), - ), + ProofOptions::new(false, false, false, false, false, minimal_memory, false, None), ) .map_err(|e| anyhow::anyhow!("Error generating execution: {}", e))?; let (_, stats): (ZiskExecutionResult, ExecutorStatsHandle) = - self.witness_lib.execution_result().ok_or_else(|| { + witness_lib.execution_result().ok_or_else(|| { anyhow::anyhow!("Failed to get execution result from emulator prover") })?; @@ -126,28 +207,29 @@ impl ProverBackend { pub(crate) fn verify_constraints_debug( &self, stdin: ZiskStdin, - hints_stream: Option, - debug_info: DebugInfo, + debug_info: Option>, ) -> Result { - if !self.verify_constraints { - return Err(anyhow::anyhow!("Constraint verification is disabled for this prover.")); - } + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot verify constraints in verifier mode"))?; + + let witness_lib = self.witness_lib.get().ok_or_else(|| { + anyhow::anyhow!("witness_lib is not initialized. Please initialize it before use.") + })?; let start = std::time::Instant::now(); - self.witness_lib.set_stdin(stdin); - if let Some(stream) = hints_stream { - self.witness_lib - .set_hints_stream(stream) - .map_err(|e| anyhow::anyhow!("Error setting hints stream: {}", e))?; - } + let debug_info = create_debug_info(debug_info, self.proving_key_path.clone())?; - self.proofman + witness_lib.set_stdin(stdin); + + proofman .verify_proof_constraints_from_lib(&debug_info, false) .map_err(|e| anyhow::anyhow!("Error generating proof: {}", e))?; let elapsed = start.elapsed(); - let (result, stats) = self.witness_lib.execution_result().ok_or_else(|| { + let (result, stats) = witness_lib.execution_result().ok_or_else(|| { anyhow::anyhow!("Failed to get execution result from emulator prover") })?; @@ -171,45 +253,112 @@ impl ProverBackend { pub(crate) fn verify_constraints( &self, stdin: ZiskStdin, - hints_stream: Option, ) -> Result { - self.verify_constraints_debug(stdin, hints_stream, DebugInfo::default()) + self.verify_constraints_debug(stdin, None) + } + + pub(crate) fn vk(&self, elf: &impl ElfBinaryLike) -> Result { + let proving_key_path = self.proving_key_path.clone(); + let (_, vk) = ensure_custom_commits(&proving_key_path, elf)?; + + Ok(ZiskProgramVK { vk }) + } + + pub(crate) fn prove_debug( + &self, + stdin: ZiskStdin, + proof_options: ProofOpts, + ) -> Result { + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot prove in verifier mode"))?; + + let witness_lib = self.witness_lib.get().ok_or_else(|| { + anyhow::anyhow!("witness_lib is not initialized. Please initialize it before use.") + })?; + + let start = std::time::Instant::now(); + + witness_lib.set_stdin(stdin); + + proofman.set_barrier(); + proofman + .generate_proof_from_lib( + ProvePhaseInputs::Full(ProofInfo::new(None, 1, vec![0], 0)), + ProofOptions::new( + false, + false, + false, + false, + proof_options.verify_proofs, + proof_options.minimal_memory, + proof_options.save_proofs, + proof_options.output_dir_path.clone(), + ), + ProvePhase::Full, + ) + .map_err(|e| anyhow::anyhow!("Error generating proof: {}", e))?; + + let elapsed = start.elapsed(); + + let (execution_result, stats) = witness_lib.execution_result().ok_or_else(|| { + anyhow::anyhow!("Failed to get execution result from emulator prover") + })?; + + // Store the stats in stats.json + #[cfg(feature = "stats")] + { + let stats_id = _stats.lock().unwrap().get_id(); + _stats.lock().unwrap().add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); + _stats.lock().unwrap().store_stats(); + } + + proofman.set_barrier(); + + Ok(ZiskProveResult::new_null(execution_result, elapsed, stats)) } pub(crate) fn prove( &self, stdin: ZiskStdin, - hints_stream: Option, + mode: ProofMode, + proof_options: ProofOpts, ) -> Result { - if self.verify_constraints { + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot prove in verifier mode"))?; + + let witness_lib = self.witness_lib.get().ok_or_else(|| { + anyhow::anyhow!("witness_lib is not initialized. Please initialize it before use.") + })?; + + if mode == ProofMode::Snark && self.snark_wrapper.is_none() { return Err(anyhow::anyhow!( - "Prover initialized with constraint verification enabled. Use `prove` instead." + "Snark wrapper is not initialized. Cannot generate snark proof." )); } let start = std::time::Instant::now(); - self.witness_lib.set_stdin(stdin); - if let Some(stream) = hints_stream { - self.witness_lib - .set_hints_stream(stream) - .map_err(|e| anyhow::anyhow!("Error setting hints stream: {}", e))?; - } + witness_lib.set_stdin(stdin); - self.proofman.set_barrier(); - let proof = self - .proofman + let compressed = matches!(mode, ProofMode::VadcopFinalCompressed); + + proofman.set_barrier(); + let proof = proofman .generate_proof_from_lib( ProvePhaseInputs::Full(ProofInfo::new(None, 1, vec![0], 0)), ProofOptions::new( - self.verify_constraints, - self.aggregation, - self.rma, - self.compressed, - self.verify_proofs, - self.minimal_memory, - self.save_proofs, - self.output_dir.clone().expect("output_dir must be set, unreachable"), + false, + proof_options.aggregation, + proof_options.rma, + compressed, + proof_options.verify_proofs, + proof_options.minimal_memory, + proof_options.save_proofs, + proof_options.output_dir_path.clone(), ), ProvePhase::Full, ) @@ -222,31 +371,10 @@ impl ProverBackend { _ => (None, None), }; - let (execution_result, stats) = self.witness_lib.execution_result().ok_or_else(|| { + let (execution_result, stats) = witness_lib.execution_result().ok_or_else(|| { anyhow::anyhow!("Failed to get execution result from emulator prover") })?; - let proof = Proof { id: proof_id, proof }; - - if let Some(proof_id) = proof.id.clone() { - let output_dir = self.output_dir.as_ref().unwrap(); - - if self.rank_info.local_rank == 0 && !output_dir.exists() { - std::fs::create_dir_all(output_dir)?; - } - - let logs = ProofLog::new(execution_result.steps, proof_id, elapsed.as_secs_f64()); - let log_path = output_dir.join("result.json"); - ProofLog::write_json_log(&log_path, &logs) - .map_err(|e| anyhow::anyhow!("Error generating log: {}", e))?; - - // Save the uncompressed vadcop final proof - let output_file_path = output_dir.join("vadcop_final_proof.bin"); - let vadcop_proof = proof.proof.clone().unwrap(); - let mut file = File::create(output_file_path)?; - file.write_all(cast_slice(&vadcop_proof))?; - } - // Store the stats in stats.json #[cfg(feature = "stats")] { @@ -261,9 +389,99 @@ impl ProverBackend { stats.get_inner().lock().unwrap().store_stats(); } - self.proofman.set_barrier(); + proofman.set_barrier(); + + match (mode, proof) { + (ProofMode::Snark, Some(vadcop_proof)) => { + let snark_proof = self.snark_wrapper.as_ref().unwrap().generate_final_snark_proof( + &vadcop_proof, + proof_options.output_dir_path.clone(), + )?; + + if snark_proof.protocol_id == SnarkProtocol::Plonk.protocol_id() { + let publics = ZiskPublics::new(vadcop_proof.public_values); + Ok(ZiskProveResult::new( + execution_result, + elapsed, + stats, + proof_id, + ZiskProof::Plonk(snark_proof.proof_bytes), + publics, + )) + } else if snark_proof.protocol_id == SnarkProtocol::Fflonk.protocol_id() { + let publics = ZiskPublics::new(vadcop_proof.public_values); + Ok(ZiskProveResult::new( + execution_result, + elapsed, + stats, + proof_id, + ZiskProof::Fflonk(snark_proof.proof_bytes), + publics, + )) + } else { + Err(anyhow::anyhow!( + "Unsupported snark protocol id: {}", + snark_proof.protocol_id + )) + } + } + (_, Some(p)) => { + let proof = if compressed { + ZiskProof::VadcopFinalCompressed(p.proof) + } else { + ZiskProof::VadcopFinal(p.proof) + }; + Ok(ZiskProveResult::new( + execution_result, + elapsed, + stats, + proof_id, + proof, + ZiskPublics::new(p.public_values), + )) + } + (_, None) => Ok(ZiskProveResult::new_null(execution_result, elapsed, stats)), + } + } - Ok(ZiskProveResult { execution: execution_result, duration: elapsed, stats, proof }) + pub(crate) fn prove_snark( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, + ) -> Result { + if self.snark_wrapper.is_none() { + return Err(anyhow::anyhow!( + "Snark wrapper is not initialized. Cannot generate snark proof." + )); + } + + let proof_bytes = match proof { + ZiskProof::VadcopFinal(bytes) => bytes.clone(), + _ => { + return Err(anyhow::anyhow!( + "Cannot generate SNARK proof. Only VadcopFinal proofs can be converted to SNARK proofs.", + )); + } + }; + + let mut pubs = program_vk.vk.clone(); + pubs.extend(publics.public_bytes()); + let vadcop_final_proof = VadcopFinalProof::new(proof_bytes, pubs, false); + + let snark_proof = self + .snark_wrapper + .as_ref() + .unwrap() + .generate_final_snark_proof(&vadcop_final_proof, None)?; + + if snark_proof.protocol_id == SnarkProtocol::Plonk.protocol_id() { + Ok(ZiskProof::Plonk(snark_proof.proof_bytes)) + } else if snark_proof.protocol_id == SnarkProtocol::Fflonk.protocol_id() { + Ok(ZiskProof::Fflonk(snark_proof.proof_bytes)) + } else { + Err(anyhow::anyhow!("Unsupported snark protocol id: {}", snark_proof.protocol_id)) + } } pub(crate) fn prove_phase( @@ -272,7 +490,12 @@ impl ProverBackend { options: ProofOptions, phase: ProvePhase, ) -> Result { - self.proofman + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot prove in verifier mode"))?; + + proofman .generate_proof_from_lib(phase_inputs, options, phase.clone()) .map_err(|e| anyhow::anyhow!("Error generating proof in phase {:?}: {}", phase, e)) } @@ -284,15 +507,79 @@ impl ProverBackend { final_proof: bool, options: &ProofOptions, ) -> Result> { - let result = self + let proofman = self .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot aggregate proofs in verifier mode"))?; + + let result = proofman .receive_aggregated_proofs(agg_proofs, last_proof, final_proof, options) .map_err(|e| anyhow::anyhow!("Error aggregating proofs: {}", e))?; Ok(result.map(|agg| ZiskAggPhaseResult { agg_proofs: agg })) } - pub(crate) fn mpi_broadcast(&self, data: &mut Vec) { - self.proofman.mpi_broadcast(data); + pub(crate) fn mpi_broadcast(&self, data: &mut Vec) -> Result<()> { + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot broadcast in verifier mode"))?; + + proofman.mpi_broadcast(data); + Ok(()) + } + + pub(crate) fn verify( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, + ) -> Result<()> { + match &proof { + ZiskProof::Null() => Err(anyhow::anyhow!("No proof found to verify.")), + ZiskProof::Plonk(proof_bytes) | ZiskProof::Fflonk(proof_bytes) => { + let protocol_id = if let ZiskProof::Plonk(_) = &proof { + SnarkProtocol::Plonk.protocol_id() + } else { + SnarkProtocol::Fflonk.protocol_id() + }; + + let verkey = get_vadcop_final_proof_vkey(&self.proving_key_path, false)?; + + let pubs = publics.bytes_solidity(program_vk, &verkey); + let hash = Sha256::digest(&pubs).to_vec(); + + let snark_proof = SnarkProof { + proof_bytes: proof_bytes.clone(), + public_bytes: pubs, + public_snark_bytes: hash, + protocol_id, + }; + + if self.proving_key_snark_path.is_none() { + return Err(anyhow::anyhow!( + "Proving key snark path is not set, cannot verify Plonk proof." + )); + } + + let verkey_path = PathBuf::from(format!( + "{}/{}/{}.verkey.json", + self.proving_key_snark_path.as_ref().unwrap().display(), + "final", + "final" + )); + Ok(verify_snark_proof(&snark_proof, &verkey_path)?) + } + ZiskProof::VadcopFinal(proof_bytes) | ZiskProof::VadcopFinalCompressed(proof_bytes) => { + let compressed = matches!(proof, ZiskProof::VadcopFinalCompressed(_)); + let mut pubs = program_vk.vk.clone(); + pubs.extend(publics.public_bytes()); + let vadcop_final_proof = + VadcopFinalProof::new(proof_bytes.clone(), pubs, compressed); + + let vk = get_vadcop_final_proof_vkey(&self.proving_key_path, compressed)?; + verify_zisk_proof(&vadcop_final_proof, &vk) + } + } } } diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index cdb702b58..660bfc6a4 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -1,16 +1,19 @@ use crate::{ - check_paths_exist, create_debug_info, get_custom_commits_map, + check_paths_exist, prover::{ProverBackend, ProverEngine, ZiskBackend}, - RankInfo, ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, - ZiskProveResult, ZiskVerifyConstraintsResult, + RankInfo, ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, ZiskProgramVK, + ZiskProof, ZiskProveResult, ZiskPublics, ZiskVerifyConstraintsResult, }; -use proofman::{AggProofs, ProofMan, ProvePhase, ProvePhaseInputs}; -use proofman_common::{initialize_logger, ParamsGPU, ProofOptions}; +use crate::{ensure_custom_commits, ProofMode, ProofOpts}; +use proofman::{AggProofs, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; +use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, VerboseMode}; +use std::collections::HashMap; use std::path::PathBuf; use zisk_common::io::{StreamSource, ZiskStdin}; +use zisk_common::ElfBinaryLike; use zisk_common::ExecutorStatsHandle; use zisk_distributed_common::LoggingConfig; -use zisk_witness::WitnessLibrary; +use zisk_witness::get_packed_info; use anyhow::Result; @@ -29,40 +32,34 @@ impl EmuProver { pub fn new( verify_constraints: bool, aggregation: bool, - rma: bool, - compressed: bool, + snark_wrapper: bool, proving_key: PathBuf, - proving_key_snark: Option, - elf: PathBuf, + proving_key_snark: PathBuf, verbose: u8, shared_tables: bool, gpu_params: ParamsGPU, - verify_proofs: bool, - minimal_memory: bool, - save_proofs: bool, - output_dir: Option, logging_config: Option, ) -> Result { let core_prover = EmuCoreProver::new( verify_constraints, aggregation, - rma, - compressed, + snark_wrapper, proving_key, proving_key_snark, - elf, verbose, shared_tables, gpu_params, - verify_proofs, - minimal_memory, - save_proofs, - output_dir, logging_config, )?; Ok(Self { core_prover }) } + + pub fn new_verifier(proving_key: PathBuf, proving_key_snark: PathBuf) -> Result { + let core_prover = EmuCoreProver::new_verifier(proving_key, proving_key_snark)?; + + Ok(Self { core_prover }) + } } impl ProverEngine for EmuProver { @@ -74,74 +71,94 @@ impl ProverEngine for EmuProver { self.core_prover.rank_info.local_rank } - fn set_stdin(&self, stdin: ZiskStdin) { - self.core_prover.backend.witness_lib.set_stdin(stdin); + fn set_stdin(&self, stdin: ZiskStdin) -> Result<()> { + self.core_prover.backend.set_stdin(stdin) } fn set_hints_stream(&self, _: StreamSource) -> Result<()> { unreachable!("EMU prover does not support precompile hints"); } + fn setup(&self, elf: &impl ElfBinaryLike) -> Result { + let proving_key = self.core_prover.backend.get_proving_key_path(); + + let (rom_bin_path, vk) = ensure_custom_commits(proving_key, elf)?; + let custom_commits_map = HashMap::from([("rom".to_string(), rom_bin_path)]); + + // Build emulator library + let witness_lib = + ZiskLibLoader::load_emu(self.core_prover.verbose, self.core_prover.shared_tables)?; + + self.core_prover.backend.register_witness_lib( + elf.elf(), + witness_lib, + custom_commits_map, + )?; + Ok(ZiskProgramVK { vk }) + } + fn executed_steps(&self) -> u64 { self.core_prover .backend - .witness_lib .execution_result() .map(|(exec_result, _)| exec_result.steps) .unwrap_or(0) } - fn execute( - &self, - stdin: ZiskStdin, - hints_stream: Option, - output_path: Option, - ) -> Result { - if hints_stream.is_some() { - return Err(anyhow::anyhow!("EMU prover does not support precompile hints")); - } - self.core_prover.backend.execute(stdin, None, output_path) + fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result { + self.core_prover.backend.execute(stdin, output_path) } fn stats( &self, stdin: ZiskStdin, - hints_stream: Option, debug_info: Option>, + minimal_memory: bool, mpi_node: Option, ) -> Result<(i32, i32, Option)> { - let debug_info = - create_debug_info(debug_info, self.core_prover.backend.proving_key.clone())?; - - self.core_prover.backend.stats(stdin, hints_stream, debug_info, mpi_node) + self.core_prover.backend.stats(stdin, debug_info, minimal_memory, mpi_node) } fn verify_constraints_debug( &self, stdin: ZiskStdin, - hints_stream: Option, debug_info: Option>, ) -> Result { - let debug_info = - create_debug_info(debug_info, self.core_prover.backend.proving_key.clone())?; + self.core_prover.backend.verify_constraints_debug(stdin, debug_info) + } - self.core_prover.backend.verify_constraints_debug(stdin, hints_stream, debug_info) + fn verify_constraints(&self, stdin: ZiskStdin) -> Result { + self.core_prover.backend.verify_constraints(stdin) } - fn verify_constraints( - &self, - stdin: ZiskStdin, - hints_stream: Option, - ) -> Result { - self.core_prover.backend.verify_constraints(stdin, hints_stream) + fn vk(&self, elf: &impl ElfBinaryLike) -> Result { + self.core_prover.backend.vk(elf) + } + + fn verify(&self, proof: &ZiskProof, publics: &ZiskPublics, vk: &ZiskProgramVK) -> Result<()> { + self.core_prover.backend.verify(proof, publics, vk) + } + + fn prove_debug(&self, stdin: ZiskStdin, proof_options: ProofOpts) -> Result { + self.core_prover.backend.prove_debug(stdin, proof_options) } fn prove( &self, stdin: ZiskStdin, - hints_stream: Option, + mode: ProofMode, + proof_options: ProofOpts, ) -> Result { - self.core_prover.backend.prove(stdin, hints_stream) + self.core_prover.backend.prove(stdin, mode, proof_options) + } + + fn prove_snark( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + vk: &ZiskProgramVK, + ) -> Result { + self.core_prover.backend.prove_snark(proof, publics, vk) } fn prove_phase( @@ -163,14 +180,16 @@ impl ProverEngine for EmuProver { self.core_prover.backend.aggregate_proofs(agg_proofs, last_proof, final_proof, options) } - fn mpi_broadcast(&self, data: &mut Vec) { - self.core_prover.backend.mpi_broadcast(data); + fn mpi_broadcast(&self, data: &mut Vec) -> Result<()> { + self.core_prover.backend.mpi_broadcast(data) } } pub struct EmuCoreProver { backend: ProverBackend, rank_info: RankInfo, + verbose: VerboseMode, + shared_tables: bool, } impl EmuCoreProver { @@ -178,36 +197,23 @@ impl EmuCoreProver { pub fn new( verify_constraints: bool, aggregation: bool, - rma: bool, - compressed: bool, + use_snark_wrapper: bool, proving_key: PathBuf, - _proving_key_snark: Option, - elf: PathBuf, + proving_key_snark: PathBuf, verbose: u8, shared_tables: bool, gpu_params: ParamsGPU, - verify_proofs: bool, - minimal_memory: bool, - save_proofs: bool, - output_dir: Option, logging_config: Option, ) -> Result { - let custom_commits_map = get_custom_commits_map(&proving_key, &elf)?; - check_paths_exist(&proving_key)?; - check_paths_exist(&elf)?; - - // Build emulator library - let mut witness_lib = ZiskLibLoader::load_emu(elf, verbose.into(), shared_tables)?; let proofman = ProofMan::new( proving_key.clone(), - custom_commits_map, verify_constraints, aggregation, gpu_params, verbose.into(), - witness_lib.get_packed_info(), + get_packed_info(), ) .map_err(|e| anyhow::anyhow!(e.to_string()))?; @@ -220,25 +226,34 @@ impl EmuCoreProver { initialize_logger(verbose.into(), Some(world_rank)); } - witness_lib.register_witness(&proofman.get_wcm())?; - proofman.set_barrier(); - let core = ProverBackend { - verify_constraints, - aggregation, - rma, - compressed, - witness_lib, - proving_key: proving_key.clone(), - verify_proofs, - minimal_memory, - save_proofs, - output_dir, - proofman, + let mut snark_wrapper = None; + if use_snark_wrapper { + check_paths_exist(&proving_key_snark)?; + snark_wrapper = Some(SnarkWrapper::new(&proving_key_snark, verbose.into())?); + } + + let core = + ProverBackend::new(proofman, snark_wrapper, proving_key, Some(proving_key_snark)); + + Ok(Self { + backend: core, rank_info: RankInfo { world_rank, local_rank }, - }; + verbose: verbose.into(), + shared_tables, + }) + } - Ok(Self { backend: core, rank_info: RankInfo { world_rank, local_rank } }) + #[allow(clippy::too_many_arguments)] + pub fn new_verifier(proving_key: PathBuf, proving_key_snark: PathBuf) -> Result { + let core_prover = ProverBackend::new_verifier(proving_key, Some(proving_key_snark)); + + Ok(Self { + backend: core_prover, + rank_info: RankInfo { world_rank: 0, local_rank: 0 }, + verbose: VerboseMode::Info, + shared_tables: false, + }) } } diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 334fb1815..98660641c 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -1,16 +1,22 @@ mod asm; mod backend; mod emu; - pub use asm::*; use backend::*; pub use emu::*; -use proofman::{AggProofs, ProvePhase, ProvePhaseInputs, ProvePhaseResult}; +use proofman::{AggProofs, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProtocol}; use proofman_common::ProofOptions; +use sha2::{Digest, Sha256}; -use crate::Proof; use anyhow::Result; -use std::{path::PathBuf, time::Duration}; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::{ + cell::Cell, + path::{Path, PathBuf}, + time::Duration, +}; +use zisk_common::ElfBinaryLike; use zisk_common::{ io::{StreamSource, ZiskStdin}, ExecutorStatsHandle, ZiskExecutionResult, @@ -27,11 +33,417 @@ pub struct ZiskVerifyConstraintsResult { pub stats: ExecutorStatsHandle, } +pub struct ZiskProgramVK { + pub vk: Vec, +} + +#[derive(Debug, Clone)] +pub struct ProofOpts { + pub aggregation: bool, + pub verify_proofs: bool, + pub rma: bool, + pub minimal_memory: bool, + pub output_dir_path: Option, + pub save_proofs: bool, +} + +impl Default for ProofOpts { + fn default() -> Self { + Self { + aggregation: true, + verify_proofs: false, + rma: false, + minimal_memory: false, + output_dir_path: None, + save_proofs: false, + } + } +} + +impl ProofOpts { + pub fn output_dir(mut self, path: PathBuf) -> Self { + self.output_dir_path = Some(path); + self + } + + pub fn save_proofs(mut self) -> Self { + self.save_proofs = true; + self + } + + pub fn verify_proofs(mut self) -> Self { + self.verify_proofs = true; + self + } + + pub fn minimal_memory(mut self) -> Self { + self.minimal_memory = true; + self + } + + pub fn no_aggregation(mut self) -> Self { + self.aggregation = false; + self + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ProofMode { + VadcopFinal, + VadcopFinalCompressed, + Snark, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ZiskProof { + Null(), + VadcopFinal(Vec), + VadcopFinalCompressed(Vec), + Plonk(Vec), + Fflonk(Vec), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ZiskVadcopFinalProof { + pub proof: Vec, + pub compressed: bool, +} + +impl ZiskVadcopFinalProof { + pub fn new(proof: Vec, compressed: bool) -> Self { + Self { proof, compressed } + } + + pub fn save( + &self, + path: impl AsRef, + ) -> Result<(), Box> { + let path = path.as_ref(); + + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + + let file = File::create(path).map_err(|e| { + std::io::Error::new( + e.kind(), + format!( + "Failed to create file for saving Vadcop Final proof: {}: {}", + path.display(), + e + ), + ) + })?; + + bincode::serialize_into(file, self)?; + Ok(()) + } + + pub fn load(path: impl AsRef) -> Result> { + let file = File::open(path.as_ref()).map_err(|e| { + std::io::Error::new( + e.kind(), + format!( + "Failed to open file for loading proof: {}: {}", + path.as_ref().display(), + e + ), + ) + })?; + let proof: ZiskVadcopFinalProof = bincode::deserialize_from(file)?; + Ok(proof) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ZiskSnarkProof { + pub proof: Vec, + pub protocol_id: u64, +} + +impl ZiskSnarkProof { + pub fn new(proof: Vec, protocol_id: u64) -> Self { + Self { proof, protocol_id } + } + + pub fn save( + &self, + path: impl AsRef, + ) -> Result<(), Box> { + let path = path.as_ref(); + + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + + let file = File::create(path).map_err(|e| { + std::io::Error::new( + e.kind(), + format!("Failed to create file for saving SNARK proof: {}: {}", path.display(), e), + ) + })?; + + bincode::serialize_into(file, self)?; + Ok(()) + } + + pub fn load(path: impl AsRef) -> Result> { + let file = File::open(path.as_ref()).map_err(|e| { + std::io::Error::new( + e.kind(), + format!( + "Failed to open file for loading SNARK proof: {}: {}", + path.as_ref().display(), + e + ), + ) + })?; + let proof: ZiskSnarkProof = bincode::deserialize_from(file)?; + Ok(proof) + } +} + +impl ZiskProof { + pub fn save(&self, path: impl AsRef) -> Result<()> { + match self { + ZiskProof::Null() => Err(anyhow::anyhow!("No proof to save")), + ZiskProof::VadcopFinal(proof) | ZiskProof::VadcopFinalCompressed(proof) => { + let compressed = matches!(self, ZiskProof::VadcopFinalCompressed(_)); + let zisk_proof = ZiskVadcopFinalProof::new(proof.clone(), compressed); + zisk_proof.save(path).map_err(|e| anyhow::anyhow!("{}", e)) + } + ZiskProof::Plonk(snark_proof) | ZiskProof::Fflonk(snark_proof) => { + let protocol_id = match self { + ZiskProof::Plonk(_) => SnarkProtocol::Plonk.protocol_id(), + ZiskProof::Fflonk(_) => SnarkProtocol::Fflonk.protocol_id(), + _ => unreachable!(), + }; + let snark_proof = ZiskSnarkProof::new(snark_proof.clone(), protocol_id); + snark_proof.save(path).map_err(|e| anyhow::anyhow!("{}", e)) + } + } + } + + pub fn load(path: impl AsRef) -> Result { + if let Ok(vadcop_proof) = ZiskVadcopFinalProof::load(path.as_ref()) { + let proof = if vadcop_proof.compressed { + ZiskProof::VadcopFinalCompressed(vadcop_proof.proof) + } else { + ZiskProof::VadcopFinal(vadcop_proof.proof) + }; + return Ok(proof); + } + + if let Ok(snark_proof) = ZiskSnarkProof::load(path.as_ref()) { + let proof = match SnarkProtocol::from_protocol_id(snark_proof.protocol_id)? { + SnarkProtocol::Plonk => ZiskProof::Plonk(snark_proof.proof), + SnarkProtocol::Fflonk => ZiskProof::Fflonk(snark_proof.proof), + }; + return Ok(proof); + } + + Err(anyhow::anyhow!("Failed to load proof: unsupported format or corrupted file")) + } +} + +pub const ZISK_PUBLICS: usize = 64; + +pub struct ZiskPublics { + data: Vec, + ptr: Cell, +} + +impl ZiskPublics { + pub fn new(publics_bytes: Vec) -> Self { + assert!( + publics_bytes.len() == ZISK_PUBLICS * 8 + 32, + "Not enough bytes to fill ZiskPublics" + ); + + let mut data = [0u8; ZISK_PUBLICS * 4]; + for (i, chunk) in publics_bytes[32..].chunks_exact(8).enumerate() { + let v32 = u32::from_le_bytes(chunk[0..4].try_into().unwrap()); + data[i * 4..(i + 1) * 4].copy_from_slice(&v32.to_le_bytes()); + } + + Self { data: data.to_vec(), ptr: Cell::new(0) } + } + + pub fn new_empty() -> Self { + Self { data: [0u8; ZISK_PUBLICS * 4].to_vec(), ptr: Cell::new(0) } + } + + /// Create ZiskPublics from a serializable value. + /// The value is serialized with bincode and stored in the public outputs as 64-bit chunks. + pub fn write(value: &T) -> Result { + let serialized = bincode::serialize(value) + .map_err(|e| anyhow::anyhow!("Serialization failed: {}", e))?; + + if serialized.len() > ZISK_PUBLICS * 4 { + return Err(anyhow::anyhow!( + "Serialized data too large: {} bytes (max {} bytes)", + serialized.len(), + ZISK_PUBLICS * 4 + )); + } + + let mut data = [0u8; ZISK_PUBLICS * 4]; + // Chunk into 8-byte (u64) values + for (i, chunk) in serialized.chunks(4).enumerate() { + // copy chunk into 32-bit slot, padding with zeros if chunk < 4 bytes + let mut buf = [0u8; 4]; + buf[..chunk.len()].copy_from_slice(chunk); + data[i * 4..(i + 1) * 4].copy_from_slice(&buf); + } + + Ok(Self { data: data.to_vec(), ptr: Cell::new(0) }) + } + + /// Reset the reading pointer to the beginning. + pub fn head(&self) { + self.ptr.set(0); + } + + /// Read raw bytes from public outputs. + pub fn read_slice(&self, slice: &mut [u8]) { + let ptr = self.ptr.get(); + slice.copy_from_slice(&self.data[ptr..ptr + slice.len()]); + self.ptr.set(ptr + slice.len()); + } + + /// Deserialize a value from public outputs. + /// The value must have been previously written with bincode serialization using `commit()`. + pub fn read(&self) -> Result { + let ptr = self.ptr.get(); + let result: T = bincode::deserialize(&self.data[ptr..]) + .map_err(|e| anyhow::anyhow!("Deserialization failed: {}", e))?; + let nb_bytes = bincode::serialized_size(&result) + .map_err(|e| anyhow::anyhow!("Failed to get serialized size: {}", e))?; + self.ptr.set(ptr + nb_bytes as usize); + Ok(result) + } + + pub fn public_bytes(&self) -> Vec { + let mut bytes = [0u8; ZISK_PUBLICS * 8]; + + // Convert the 256 bytes back to ZISK_PUBLICS u64 values (padding upper 32 bits with zeros) + for i in 0..ZISK_PUBLICS { + let start = i * 4; + let val32 = u32::from_le_bytes([ + self.data[start], + self.data[start + 1], + self.data[start + 2], + self.data[start + 3], + ]); + let val64 = val32 as u64; + bytes[i * 8..(i + 1) * 8].copy_from_slice(&val64.to_le_bytes()); + } + + bytes.to_vec() + } + + pub fn public_bytes_solidity(&self) -> Vec { + let mut bytes = [0u8; ZISK_PUBLICS * 4]; + + for i in 0..ZISK_PUBLICS { + let start = i * 4; + let val32 = u32::from_le_bytes([ + self.data[start], + self.data[start + 1], + self.data[start + 2], + self.data[start + 3], + ]); + bytes[i * 4..(i + 1) * 4].copy_from_slice(&val32.to_be_bytes()); + } + + bytes.to_vec() + } + + pub fn hash_solidity(&self, program_vk: &ZiskProgramVK, vadcop_verkey: &[u8]) -> Vec { + let bytes = self.bytes_solidity(program_vk, vadcop_verkey); + + // SHA-256 + let hash = Sha256::digest(&bytes); + + hash.to_vec() + } +} + +impl ZiskPublics { + pub fn bytes_u64(&self, program_vk: &ZiskProgramVK) -> Vec { + let mut bytes = Vec::with_capacity(program_vk.vk.len() + ZISK_PUBLICS * 8); + + bytes.extend(&program_vk.vk); + bytes.extend(self.public_bytes()); + + bytes + } + + pub fn bytes_solidity(&self, program_vk: &ZiskProgramVK, vadcop_verkey: &[u8]) -> Vec { + let mut prefix = [0u8; 32]; + for (i, chunk) in program_vk.vk.chunks_exact(8).enumerate() { + let val = u64::from_le_bytes(chunk.try_into().unwrap()); + prefix[i * 8..(i + 1) * 8].copy_from_slice(&val.to_be_bytes()); + } + + let mut bytes = prefix.to_vec(); + bytes.extend(self.public_bytes_solidity()); + let mut suffix = [0u8; 32]; + for (i, chunk) in vadcop_verkey.chunks_exact(8).enumerate() { + let val = u64::from_le_bytes(chunk.try_into().unwrap()); + suffix[i * 8..(i + 1) * 8].copy_from_slice(&val.to_be_bytes()); + } + bytes.extend(&suffix); + bytes + } +} + pub struct ZiskProveResult { pub execution: ZiskExecutionResult, pub duration: Duration, pub stats: ExecutorStatsHandle, - pub proof: Proof, + pub proof_id: Option, + pub proof: ZiskProof, + pub publics: ZiskPublics, +} + +impl ZiskProveResult { + pub fn new( + execution: ZiskExecutionResult, + duration: Duration, + stats: ExecutorStatsHandle, + proof_id: Option, + proof: ZiskProof, + publics: ZiskPublics, + ) -> Self { + Self { execution, duration, stats, proof_id, proof, publics } + } + + pub fn new_null( + execution: ZiskExecutionResult, + duration: Duration, + stats: ExecutorStatsHandle, + ) -> Self { + Self { + execution, + duration, + stats, + proof_id: None, + proof: ZiskProof::Null(), + publics: ZiskPublics::new_empty(), + } + } + + /// Deserialize a value from public outputs. + /// The value must have been previously written with bincode serialization using `commit()`. + pub fn get_publics(&self) -> Result { + self.publics.read() + } + + /// Reset the reading pointer to the beginning of public outputs. + pub fn reset_publics(&self) { + self.publics.head(); + } } pub type ZiskPhaseResult = ProvePhaseResult; @@ -41,50 +453,56 @@ pub struct ZiskAggPhaseResult { } pub trait ProverEngine { + fn setup(&self, elf: &impl ElfBinaryLike) -> Result; + fn world_rank(&self) -> i32; fn local_rank(&self) -> i32; - fn set_stdin(&self, stdin: ZiskStdin); + fn set_stdin(&self, stdin: ZiskStdin) -> Result<()>; fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()>; fn executed_steps(&self) -> u64; - fn execute( - &self, - stdin: ZiskStdin, - hints_stream: Option, - output_path: Option, - ) -> Result; + fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result; fn stats( &self, stdin: ZiskStdin, - hints_stream: Option, debug_info: Option>, + minimal_memory: bool, mpi_node: Option, ) -> Result<(i32, i32, Option)>; fn verify_constraints_debug( &self, stdin: ZiskStdin, - hints_stream: Option, debug_info: Option>, ) -> Result; - fn verify_constraints( - &self, - stdin: ZiskStdin, - hints_stream: Option, - ) -> Result; + fn verify_constraints(&self, stdin: ZiskStdin) -> Result; + + fn vk(&self, elf: &impl ElfBinaryLike) -> Result; + + fn verify(&self, proof: &ZiskProof, publics: &ZiskPublics, vk: &ZiskProgramVK) -> Result<()>; + + fn prove_debug(&self, stdin: ZiskStdin, proof_options: ProofOpts) -> Result; fn prove( &self, stdin: ZiskStdin, - hints_stream: Option, + mode: ProofMode, + proof_options: ProofOpts, ) -> Result; + fn prove_snark( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + vk: &ZiskProgramVK, + ) -> Result; + fn prove_phase( &self, phase_inputs: ProvePhaseInputs, @@ -100,7 +518,7 @@ pub trait ProverEngine { options: &ProofOptions, ) -> Result>; - fn mpi_broadcast(&self, data: &mut Vec); + fn mpi_broadcast(&self, data: &mut Vec) -> Result<()>; } pub trait ZiskBackend: Send + Sync { @@ -117,12 +535,15 @@ impl ZiskProver { Self { prover } } + pub fn setup(&self, elf: &impl ElfBinaryLike) -> Result { + self.prover.setup(elf) + } + /// Set the standard input for the current proof. - pub fn set_stdin(&self, stdin: ZiskStdin) { - self.prover.set_stdin(stdin); + pub fn set_stdin(&self, stdin: ZiskStdin) -> Result<()> { + self.prover.set_stdin(stdin) } - /// Set the hints stream for the current proof. pub fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()> { self.prover.set_hints_stream(hints_stream) } @@ -146,51 +567,66 @@ impl ZiskProver { /// Execute the prover with the given standard input and output path. /// It only runs the execution without generating a proof. - pub fn execute( - &self, - stdin: ZiskStdin, - hints_stream: Option, - ) -> Result { - self.prover.execute(stdin, hints_stream, None) + pub fn execute(&self, stdin: ZiskStdin) -> Result { + self.prover.execute(stdin, None) } /// Get the execution statistics with the given standard input and debug information. pub fn stats( &self, stdin: ZiskStdin, - hints_stream: Option, debug_info: Option>, + minimal_memory: bool, mpi_node: Option, ) -> Result<(i32, i32, Option)> { - self.prover.stats(stdin, hints_stream, debug_info, mpi_node) + self.prover.stats(stdin, debug_info, minimal_memory, mpi_node) } /// Verify the constraints with the given standard input and debug information. pub fn verify_constraints_debug( &self, stdin: ZiskStdin, - hints_stream: Option, debug_info: Option>, ) -> Result { - self.prover.verify_constraints_debug(stdin, hints_stream, debug_info) + self.prover.verify_constraints_debug(stdin, debug_info) } /// Verify the constraints with the given standard input. - pub fn verify_constraints( + pub fn verify_constraints(&self, stdin: ZiskStdin) -> Result { + self.prover.verify_constraints(stdin) + } + + pub fn vk(&self, elf: &impl ElfBinaryLike) -> Result { + self.prover.vk(elf) + } + + pub fn verify( &self, - stdin: ZiskStdin, - hints_stream: Option, - ) -> Result { - self.prover.verify_constraints(stdin, hints_stream) + proof: &ZiskProof, + publics: &ZiskPublics, + vk: &ZiskProgramVK, + ) -> Result<()> { + self.prover.verify(proof, publics, vk) } /// Generate a proof with the given standard input. - pub fn prove( + /// Returns a `ProveBuilder` that allows setting per-proof options before running. + /// + /// # Example + /// ```ignore + /// let result = prover.prove(stdin).compressed().run()?; + /// ``` + pub fn prove(&self, stdin: ZiskStdin) -> ProveBuilder<'_, C> { + ProveBuilder::new(&self.prover, stdin) + } + + pub fn prove_snark( &self, - stdin: ZiskStdin, - hints_stream: Option, - ) -> Result { - self.prover.prove(stdin, hints_stream) + proof: &ZiskProof, + publics: &ZiskPublics, + vk: &ZiskProgramVK, + ) -> Result { + self.prover.prove_snark(proof, publics, vk) } pub fn prove_phase( @@ -213,7 +649,54 @@ impl ZiskProver { } /// Broadcast data to all MPI processes. - pub fn mpi_broadcast(&self, data: &mut Vec) { - self.prover.mpi_broadcast(data); + pub fn mpi_broadcast(&self, data: &mut Vec) -> Result<()> { + self.prover.mpi_broadcast(data) + } +} + +/// Builder for configuring and running a proof. +/// +/// This struct provides a fluent API for setting per-proof options +/// before executing the proof generation. +/// +/// # Example +/// ```ignore +/// let result = prover.prove(stdin).compressed().run()?; +/// ``` +pub struct ProveBuilder<'a, C: ZiskBackend> { + prover: &'a C::Prover, + stdin: ZiskStdin, + mode: ProofMode, + proof_options: ProofOpts, +} + +impl<'a, C: ZiskBackend> ProveBuilder<'a, C> { + fn new(prover: &'a C::Prover, stdin: ZiskStdin) -> Self { + Self { prover, stdin, mode: ProofMode::VadcopFinal, proof_options: ProofOpts::default() } + } + + /// Enable compressed proof generation. + pub fn compressed(mut self) -> Self { + self.mode = ProofMode::VadcopFinalCompressed; + self + } + + pub fn plonk(mut self) -> Self { + self.mode = ProofMode::Snark; + self + } + + pub fn with_proof_options(mut self, options: ProofOpts) -> Self { + self.proof_options = options; + self + } + + /// Execute the proof generation with the configured options. + pub fn run(self) -> Result { + self.prover.prove(self.stdin, self.mode, self.proof_options) + } + + pub fn run_debug(self) -> Result { + self.prover.prove_debug(self.stdin, self.proof_options) } } diff --git a/sdk/src/utils.rs b/sdk/src/utils.rs index bc6512224..1ca3df953 100644 --- a/sdk/src/utils.rs +++ b/sdk/src/utils.rs @@ -1,14 +1,13 @@ +use fields::Goldilocks; use std::collections::HashMap; +use std::env; use std::path::{Path, PathBuf}; -use std::{env, fs}; use anyhow::Result; use proofman_common::{json_to_debug_instances_map, DebugInfo, ProofmanResult}; -use rom_setup::{ - gen_elf_hash, get_elf_bin_file_path, get_elf_data_hash, get_rom_blowup_factor_and_arity, - DEFAULT_CACHE_PATH, -}; +use rom_setup::{get_elf_data_hash, rom_merkle_setup}; +use zisk_common::ElfBinaryLike; /// Gets the user's home directory as specified by the HOME environment variable. pub fn get_home_dir() -> String { @@ -33,12 +32,6 @@ pub fn get_home_zisk_path() -> PathBuf { PathBuf::from(zisk_path) } -/// Gets the default zisk folder location in the home installation directory. -pub fn get_default_zisk_path() -> PathBuf { - let zisk_path = format!("{}/.zisk/zisk", get_home_dir()); - PathBuf::from(zisk_path) -} - /// Gets the default stark info JSON file location in the home installation directory. pub fn get_default_stark_info() -> String { let stark_info = format!( @@ -83,49 +76,23 @@ pub fn get_proving_key_snark(proving_key_snark: Option<&PathBuf>) -> PathBuf { proving_key_snark.cloned().unwrap_or_else(get_default_proving_key_snark) } -/// Gets the zisk folder. -/// Uses the default one if not specified by user. -pub fn get_zisk_path(zisk_path: Option<&PathBuf>) -> PathBuf { - zisk_path.cloned().unwrap_or_else(get_default_zisk_path) -} - -pub fn ensure_custom_commits(proving_key: &Path, elf: &Path) -> Result { - // Ensure cache directory exists - let default_cache_path = std::env::var("HOME") - .map(PathBuf::from) - .map_err(|e| anyhow::anyhow!("Failed to read HOME environment variable: {e}"))? - .join(DEFAULT_CACHE_PATH); - - if let Err(e) = fs::create_dir_all(&default_cache_path) { - if e.kind() != std::io::ErrorKind::AlreadyExists { - panic!("Failed to create cache directory: {e:?}"); - } - } - - // Get the blowup factor as the custom commits filename is formed using it - // {ELF_HASH}_{PILOUT_HASH}_{ROM_NUM_ROWS}_{BLOWUP_FACTOR}.bin - let (blowup_factor, merkle_tree_arity) = get_rom_blowup_factor_and_arity(proving_key); - - // Compute the path for the custom commits file - let rom_bin_path = - get_elf_bin_file_path(elf, &default_cache_path, blowup_factor, merkle_tree_arity)?; - - // Check if the custom commits file exists, if not generate it - if !rom_bin_path.exists() { - let _ = gen_elf_hash(elf, rom_bin_path.as_path(), blowup_factor, merkle_tree_arity, false) - .map_err(|e| anyhow::anyhow!("Error generating elf hash: {}", e)); - } - - Ok(rom_bin_path) +pub fn ensure_custom_commits( + proving_key: &Path, + elf: &impl ElfBinaryLike, +) -> Result<(PathBuf, Vec)> { + rom_merkle_setup::(elf, &None, proving_key) } -pub fn get_custom_commits_map(proving_key: &Path, elf: &Path) -> Result> { - let rom_bin_path = ensure_custom_commits(proving_key, elf)?; +pub fn get_custom_commits_map( + proving_key: &Path, + elf: &impl ElfBinaryLike, +) -> Result> { + let (rom_bin_path, _) = ensure_custom_commits(proving_key, elf)?; Ok(HashMap::from([("rom".to_string(), rom_bin_path)])) } -pub fn get_asm_paths(elf: &Path) -> Result<(String, String)> { - let stem = elf.file_stem().unwrap().to_str().unwrap(); +pub fn get_asm_paths(elf: &impl ElfBinaryLike) -> Result<(String, String)> { + let stem = elf.name(); let hash = get_elf_data_hash(elf).map_err(|e| anyhow::anyhow!("Error computing ELF hash: {}", e))?; diff --git a/sdk/src/zisk_lib_loader.rs b/sdk/src/zisk_lib_loader.rs index 45f67851b..85f1316fd 100644 --- a/sdk/src/zisk_lib_loader.rs +++ b/sdk/src/zisk_lib_loader.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use fields::PrimeField64; use proofman_common::VerboseMode; -use zisk_witness::{init_zisk_lib, WitnessLib}; +use zisk_witness::WitnessLib; use anyhow::Result; @@ -12,7 +12,6 @@ pub struct ZiskLibLoader; impl ZiskLibLoader { #[allow(clippy::too_many_arguments)] fn load_library( - elf: PathBuf, verbose: VerboseMode, shared_tables: bool, asm_mt_filename: Option, @@ -21,9 +20,8 @@ impl ZiskLibLoader { unlock_mapped_memory: Option, with_hints: bool, ) -> Result> { - Ok(init_zisk_lib( + Ok(WitnessLib::new( verbose, - elf, asm_mt_filename, asm_rh_filename, base_port, @@ -34,16 +32,14 @@ impl ZiskLibLoader { } pub fn load_emu( - elf: PathBuf, verbose: VerboseMode, shared_tables: bool, ) -> Result> { - Self::load_library(elf, verbose, shared_tables, None, None, None, None, false) + Self::load_library(verbose, shared_tables, None, None, None, None, false) } #[allow(clippy::too_many_arguments)] pub fn load_asm( - elf: PathBuf, verbose: VerboseMode, shared_tables: bool, asm_mt_filename: PathBuf, @@ -53,7 +49,6 @@ impl ZiskLibLoader { with_hints: bool, ) -> Result> { Self::load_library( - elf, verbose, shared_tables, Some(asm_mt_filename), diff --git a/state-machines/rom/src/rom.rs b/state-machines/rom/src/rom.rs index 20ba0a190..8e5906e5c 100644 --- a/state-machines/rom/src/rom.rs +++ b/state-machines/rom/src/rom.rs @@ -277,17 +277,15 @@ impl RomSM { /// * `rom_path` - The path to the ELF file. /// * `rom_custom_trace` - Reference to the custom ROM trace. pub fn compute_custom_trace_rom( - rom_path: PathBuf, + elf: &[u8], rom_custom_trace: &mut RomRomTrace, ) { - // Get the ELF file path as a string - let elf_filename: String = rom_path.to_str().unwrap().into(); tracing::info!("Computing custom trace ROM"); // Load and parse the ELF file, and transpile it into a ZisK ROM using Riscv2zisk // Create an instance of the RISCV -> ZisK program converter - let riscv2zisk = Riscv2zisk::new(elf_filename); + let riscv2zisk = Riscv2zisk::new(elf); // Convert program to rom let rom = riscv2zisk.run().expect("RomSM::prover() failed converting elf to rom"); diff --git a/tools/emulate_asm_all.sh b/tools/emulate_asm_all.sh index 1a87d7115..5f8fb2eed 100755 --- a/tools/emulate_asm_all.sh +++ b/tools/emulate_asm_all.sh @@ -125,12 +125,12 @@ do make # Execute it and save output - build/ziskemuasm -s --gen=1 -o --silent > output 2>&1 & + build/ziskemuasm -s --gen=1 --output_riscof --silent > output 2>&1 & # Store the PID of the background process # BG_PID=$! - echo "Sleeping for 5 seconds to let the emulator server initialize..." - sleep 5 + echo "Sleeping for 8 seconds to let the emulator server initialize..." + sleep 8 build/ziskemuasm -c -i empty_input.bin --gen=1 --shutdown echo "Sleeping for 2 seconds to let the emulator server complete..." sleep 2 diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml index b8fd115a9..9b2fa6363 100644 --- a/verifier/Cargo.toml +++ b/verifier/Cargo.toml @@ -9,6 +9,7 @@ categories = { workspace = true } [dependencies] proofman-verifier = { workspace = true, features = ["verify"] } +proofman-util = { workspace = true } anyhow = { workspace = true } [features] diff --git a/verifier/src/verifier.rs b/verifier/src/verifier.rs index 31d42325e..bad19e455 100644 --- a/verifier/src/verifier.rs +++ b/verifier/src/verifier.rs @@ -1,16 +1,15 @@ use anyhow::{anyhow, Ok, Result}; +use proofman_util::VadcopFinalProof; use proofman_verifier::{verify_vadcop_final, verify_vadcop_final_compressed}; -pub fn verify_zisk_proof(zisk_proof: &[u8], vk: &[u8]) -> Result<()> { - if !verify_vadcop_final(zisk_proof, vk) { - Err(anyhow!("Zisk Proof was not verified")) +pub fn verify_zisk_proof(zisk_proof: &VadcopFinalProof, vk: &[u8]) -> Result<()> { + let is_valid = if zisk_proof.compressed { + verify_vadcop_final_compressed(zisk_proof, vk) } else { - Ok(()) - } -} + verify_vadcop_final(zisk_proof, vk) + }; -pub fn verify_zisk_proof_compressed(zisk_proof: &[u8], vk: &[u8]) -> Result<()> { - if !verify_vadcop_final_compressed(zisk_proof, vk) { + if !is_valid { Err(anyhow!("Zisk Proof was not verified")) } else { Ok(()) diff --git a/witness-computation/src/lib.rs b/witness-computation/src/lib.rs index f92509468..39ef75d65 100644 --- a/witness-computation/src/lib.rs +++ b/witness-computation/src/lib.rs @@ -1,4 +1,3 @@ mod zisk_lib; -pub use witness::WitnessLibrary; pub use zisk_lib::*; diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 482a1635a..b60421c5f 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -1,14 +1,13 @@ //! The `WitnessLib` library defines the core witness computation framework, //! integrating the ZisK execution environment with state machines and witness components. //! -//! This module leverages `WitnessLibrary` to orchestrate the setup of state machines, //! program conversion, and execution pipelines to generate required witnesses. use asm_runner::{HintsFile, HintsShmem}; use executor::{ EmulatorAsm, EmulatorKind, EmulatorRust, StateMachines, StaticSMBundle, ZiskExecutor, }; -use fields::{Goldilocks, PrimeField64}; +use fields::PrimeField64; use pil_std_lib::Std; use precomp_arith_eq::ArithEqManager; use precomp_arith_eq_384::ArithEq384Manager; @@ -26,7 +25,7 @@ use sm_mem::Mem; use sm_rom::RomSM; use std::{collections::HashMap, path::PathBuf, sync::Arc}; use tracing::debug; -use witness::{WitnessLibrary, WitnessManager}; +use witness::WitnessManager; use zisk_common::{ io::{ZiskStdin, ZiskStream}, ExecutorStatsHandle, ZiskExecutionResult, @@ -46,7 +45,6 @@ use zisk_pil::{ use anyhow::Result; pub struct WitnessLib { - elf_path: PathBuf, asm_mt_path: Option, asm_rh_path: Option, executor: Option>>, @@ -58,34 +56,47 @@ pub struct WitnessLib { with_hints: bool, } -#[allow(clippy::too_many_arguments)] -pub fn init_zisk_lib( - verbose_mode: proofman_common::VerboseMode, - elf_path: PathBuf, - asm_mt_path: Option, - asm_rh_path: Option, - base_port: Option, - unlock_mapped_memory: bool, - shared_tables: bool, - with_hints: bool, -) -> WitnessLib { - let chunk_size = CHUNK_SIZE; - - WitnessLib { - elf_path, - asm_mt_path, - asm_rh_path, - executor: None, - chunk_size, - base_port, - unlock_mapped_memory, - shared_tables, - with_hints, - verbose_mode, +pub fn get_packed_info() -> HashMap<(usize, usize), PackedInfo> { + let mut _packed_info = HashMap::new(); + #[cfg(feature = "packed")] + { + for packed_info in PACKED_INFO.iter() { + _packed_info.insert( + (packed_info.0, packed_info.1), + PackedInfo::new( + packed_info.2.is_packed, + packed_info.2.num_packed_words, + packed_info.2.unpack_info.to_vec(), + ), + ); + } } + _packed_info } -impl WitnessLibrary for WitnessLib { +impl WitnessLib { + pub fn new( + verbose_mode: proofman_common::VerboseMode, + asm_mt_path: Option, + asm_rh_path: Option, + base_port: Option, + unlock_mapped_memory: bool, + shared_tables: bool, + with_hints: bool, + ) -> Self { + Self { + asm_mt_path, + asm_rh_path, + executor: None, + chunk_size: CHUNK_SIZE, + base_port, + unlock_mapped_memory, + shared_tables, + verbose_mode, + with_hints, + } + } + /// Registers the witness components and initializes the execution pipeline. /// /// # Arguments @@ -99,7 +110,7 @@ impl WitnessLibrary for WitnessLib { /// /// # Panics /// Panics if the `Riscv2zisk` conversion fails or if required paths cannot be resolved. - fn register_witness(&mut self, wcm: &WitnessManager) -> ProofmanResult<()> { + pub fn register_witness(&mut self, elf: &[u8], wcm: &WitnessManager) -> ProofmanResult<()> { assert_eq!(self.asm_mt_path.is_some(), self.asm_rh_path.is_some()); let world_rank = wcm.get_world_rank(); @@ -108,7 +119,7 @@ impl WitnessLibrary for WitnessLib { proofman_common::initialize_logger(self.verbose_mode, Some(world_rank)); // Step 1: Create an instance of the RISCV -> ZisK program converter - let rv2zk = Riscv2zisk::new(self.elf_path.display().to_string()); + let rv2zk = Riscv2zisk::new(elf); // Step 2: Convert program to ROM let zisk_rom = rv2zk.run().unwrap_or_else(|e| panic!("Application error: {e}")); @@ -258,26 +269,6 @@ impl WitnessLibrary for WitnessLib { Ok(()) } - fn get_packed_info(&self) -> HashMap<(usize, usize), PackedInfo> { - let mut _packed_info = HashMap::new(); - #[cfg(feature = "packed")] - { - for packed_info in PACKED_INFO.iter() { - _packed_info.insert( - (packed_info.0, packed_info.1), - PackedInfo::new( - packed_info.2.is_packed, - packed_info.2.num_packed_words, - packed_info.2.unpack_info.to_vec(), - ), - ); - } - } - _packed_info - } -} - -impl WitnessLib { pub fn set_stdin(&self, stdin: ZiskStdin) { if let Some(executor) = &self.executor { executor.set_stdin(stdin); @@ -285,6 +276,11 @@ impl WitnessLib { } pub fn set_hints_stream(&self, hints_stream: zisk_common::io::StreamSource) -> Result<()> { + if !self.with_hints { + return Err(anyhow::anyhow!( + "Hints stream cannot be set when WitnessLib is initialized without hints" + )); + } if let Some(executor) = &self.executor { executor.set_hints_stream_src(hints_stream) } else { diff --git a/zisk-contracts/IZiskVerifier.sol b/zisk-contracts/IZiskVerifier.sol index ec49975de..88fb1eb1c 100644 --- a/zisk-contracts/IZiskVerifier.sol +++ b/zisk-contracts/IZiskVerifier.sol @@ -11,6 +11,7 @@ interface IZiskVerifier { /// @param proofBytes The proof of the program execution the Zisk zkVM encoded as bytes. function verifySnarkProof( uint64[4] calldata programVK, + uint64[4] calldata rootCVadcopFinal, bytes calldata publicValues, bytes calldata proofBytes ) external view; diff --git a/zisk-contracts/PlonkVerifier.sol b/zisk-contracts/PlonkVerifier.sol index 8dfdfced3..794bbe1c4 100644 --- a/zisk-contracts/PlonkVerifier.sol +++ b/zisk-contracts/PlonkVerifier.sol @@ -43,22 +43,22 @@ contract PlonkVerifier { uint16 constant nPublic = 1; uint16 constant nLagrange = 1; - uint256 constant Qmx = 572058585897115413933308198688680980455081513448360206617674822477027097352; - uint256 constant Qmy = 19520695395476905182400417552180799699716537403076661072544440793833663810330; - uint256 constant Qlx = 9832908426454593805012267200857420955232427230162451920383591798177680906842; - uint256 constant Qly = 6134887858043114941104727813721017175936417585428553547982984062309748590067; - uint256 constant Qrx = 17937669991980819350134444648322386596504213518209816889288337561956505273579; - uint256 constant Qry = 17380198614013544832408943480535610993360041386943658331661438058754652423608; - uint256 constant Qox = 3746383519605994996570654909729447538060398177432538027019038210460830835996; - uint256 constant Qoy = 1024676947590770618963044613714674542516450089379999729241895272070434668163; - uint256 constant Qcx = 19818475363986446083386548432203783753847786675575871159724364841872033339811; - uint256 constant Qcy = 5734908757991782594800656602635866231598638594033863174601859476090728718317; - uint256 constant S1x = 3813951475049395353035144087536163985766398632609172098517655090832064479201; - uint256 constant S1y = 4661041460539871222837848184860991236413816796875875991414335208951982883573; - uint256 constant S2x = 2346297236765228201735212218580864671321819719934943664794817856462628147766; - uint256 constant S2y = 7675333666783282678195557739194219149780835629320882753298243738718972627161; - uint256 constant S3x = 11920762370636635928452991956851650930207813096445461979758461102591469998444; - uint256 constant S3y = 13252580429342734662311430952976723420942336108640176798978862832832900350015; + uint256 constant Qmx = 15728078222621428176160834771923049445267207177815563086756963442982537322555; + uint256 constant Qmy = 10368134109772487360967338421067531937191459049728961159479449769737397414266; + uint256 constant Qlx = 6266903827059262482682470537784423436414327073503210506957823438202715076858; + uint256 constant Qly = 9930975912673080337533028672610162866366123480191667478375977723280338682627; + uint256 constant Qrx = 7123060314877137702038864858686611023352874626900523603313267911196395216007; + uint256 constant Qry = 1770043357969798948108297406468647422219312923587004610833401748122605130485; + uint256 constant Qox = 429670666919728039002263956209270185134416998857239332019860571923582607053; + uint256 constant Qoy = 11024111753296480975480562321760673479457513637396733200807061983645283791758; + uint256 constant Qcx = 4434809406798938738431069472809456595063321363145767360968983463659931159037; + uint256 constant Qcy = 19430845146272042503995416110175245795271603235563329595858119690639500549372; + uint256 constant S1x = 14529212361763788633811869842580421415134713687938570863064659590110934903740; + uint256 constant S1y = 15130539367607292893898013497807485005818775366682240859886811844322272868379; + uint256 constant S2x = 88575883471378427909504541476422256478241201143920420405836063934407519240; + uint256 constant S2y = 3538048320551462152620488185558124425114005176021275627227409411627962175942; + uint256 constant S3x = 18339485453472040659646089143258381971176107908300548337940061504006306000991; + uint256 constant S3y = 401317798322731345836300355016089251665269411090279566075041830406289046834; uint256 constant k1 = 2; uint256 constant k2 = 3; uint256 constant X2x1 = 21831381940315734285607113342023901060522397560371972897001948545212302161822; @@ -197,7 +197,54 @@ contract PlonkVerifier { } } - function checkInput() { + function checkPointBelongsToBN128Curve(p) { + let x := calldataload(p) + let y := calldataload(add(p, 32)) + + // Check that the point is on the curve + // y^2 = x^3 + 3 + let x3_3 := addmod(mulmod(x, mulmod(x, x, qf), qf), 3, qf) + let y2 := mulmod(y, y, qf) + + if iszero(eq(x3_3, y2)) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkProofData() { + // Check proof commitments belong to the bn128 curve + checkPointBelongsToBN128Curve(pA) + checkPointBelongsToBN128Curve(pB) + checkPointBelongsToBN128Curve(pC) + checkPointBelongsToBN128Curve(pZ) + checkPointBelongsToBN128Curve(pT1) + checkPointBelongsToBN128Curve(pT2) + checkPointBelongsToBN128Curve(pT3) + checkPointBelongsToBN128Curve(pWxi) + checkPointBelongsToBN128Curve(pWxiw) + + // Check proof commitments coordinates are in the field + checkField(calldataload(pA)) + checkField(calldataload(add(pA, 32))) + checkField(calldataload(pB)) + checkField(calldataload(add(pB, 32))) + checkField(calldataload(pC)) + checkField(calldataload(add(pC, 32))) + checkField(calldataload(pZ)) + checkField(calldataload(add(pZ, 32))) + checkField(calldataload(pT1)) + checkField(calldataload(add(pT1, 32))) + checkField(calldataload(pT2)) + checkField(calldataload(add(pT2, 32))) + checkField(calldataload(pT3)) + checkField(calldataload(add(pT3, 32))) + checkField(calldataload(pWxi)) + checkField(calldataload(add(pWxi, 32))) + checkField(calldataload(pWxiw)) + checkField(calldataload(add(pWxiw, 32))) + + // Check proof evaluations are in the field checkField(calldataload(pEval_a)) checkField(calldataload(pEval_b)) checkField(calldataload(pEval_c)) @@ -718,7 +765,7 @@ contract PlonkVerifier { let pMem := mload(0x40) mstore(0x40, add(pMem, lastMem)) - checkInput() + checkProofData() calculateChallenges(pMem, _pubSignals) calculateLagrange(pMem) calculatePI(pMem, _pubSignals) diff --git a/zisk-contracts/ZiskVerifier.sol b/zisk-contracts/ZiskVerifier.sol index 4bda76b67..e819834aa 100644 --- a/zisk-contracts/ZiskVerifier.sol +++ b/zisk-contracts/ZiskVerifier.sol @@ -15,7 +15,11 @@ contract ZiskVerifier is PlonkVerifier, IZiskVerifier { error InvalidProof(); function VERSION() external pure returns (string memory) { - return "v0.15.0"; + return "v0.16.0"; + } + + function getRootCVadcopFinal() external pure returns (uint64[4] memory) { + return [uint64(17315851345413577629), uint64(6088251014406292380), uint64(9617272497734844709), uint64(18293967017889666983)]; } // Modulus zkSNARK @@ -26,21 +30,24 @@ contract ZiskVerifier is PlonkVerifier, IZiskVerifier { /// @param publicValues The public values. function hashPublicValues( uint64[4] calldata programVK, + uint64[4] calldata rootCVadcopFinal, bytes calldata publicValues ) public pure returns (uint256) { - return uint256(sha256(abi.encodePacked(bytes8(programVK[0]), bytes8(programVK[1]), bytes8(programVK[2]), bytes8(programVK[3]), publicValues))) % _RFIELD; + return uint256(sha256(abi.encodePacked(bytes8(programVK[0]), bytes8(programVK[1]), bytes8(programVK[2]), bytes8(programVK[3]), publicValues, bytes8(rootCVadcopFinal[0]), bytes8(rootCVadcopFinal[1]), bytes8(rootCVadcopFinal[2]), bytes8(rootCVadcopFinal[3])))) % _RFIELD; } /// @notice Verifies a proof with given public values and vkey. /// @param programVK The verification key for the RISC-V program. + /// @param rootCVadcopFinal The rootC value for the Vadcop final. /// @param publicValues The public values encoded as bytes. /// @param proofBytes The proof of the program execution the Zisk zkVM encoded as bytes. function verifySnarkProof( uint64[4] calldata programVK, + uint64[4] calldata rootCVadcopFinal, bytes calldata publicValues, bytes calldata proofBytes ) external view { - uint256 publicValuesDigest = hashPublicValues(programVK, publicValues); + uint256 publicValuesDigest = hashPublicValues(programVK, rootCVadcopFinal, publicValues); uint256[24] memory proofDecoded = abi.decode(proofBytes, (uint256[24])); diff --git a/ziskbuild/Cargo.toml b/ziskbuild/Cargo.toml index 03320ef23..ba573c511 100644 --- a/ziskbuild/Cargo.toml +++ b/ziskbuild/Cargo.toml @@ -11,6 +11,9 @@ categories.workspace = true clap = { workspace = true } cargo_metadata = "0.23.0" anyhow = { workspace = true } +tracing = { workspace = true } +rom-setup = { workspace = true } +zisk-sdk = { workspace = true } [build-dependencies] vergen = { version = "8", default-features = false, features = [ diff --git a/ziskbuild/src/build.rs b/ziskbuild/src/build.rs index 0bb105c08..fc804925a 100644 --- a/ziskbuild/src/build.rs +++ b/ziskbuild/src/build.rs @@ -3,6 +3,7 @@ use crate::{ ZISK_TARGET, }; use cargo_metadata::camino::Utf8PathBuf; +use rom_setup::{assembly_files_exist, gen_assembly, get_output_path}; use std::{ io::{BufRead, BufReader}, path::PathBuf, @@ -32,8 +33,12 @@ pub(crate) fn build_program_internal(path: &str, args: Option) { if is_clippy_driver { // Still need to set ELF env vars even if build is skipped. let target_elf_paths = generate_elf_paths(&metadata, args.as_ref()); - - print_elf_paths_cargo_directives(&target_elf_paths); + let hints = args + .as_ref() + .and_then(|a| a.hints) + .or_else(|| std::env::var("ZISK_HINTS").ok().and_then(|v| v.parse().ok())) + .unwrap_or(false); + print_elf_paths_cargo_directives(&target_elf_paths, hints); println!("cargo:warning=Skipping build due to clippy invocation."); return; @@ -64,15 +69,28 @@ pub fn execute_build_program( let program_dir: Utf8PathBuf = program_dir.try_into().expect("Failed to convert PathBuf to Utf8PathBuf"); + // Check for ZISK_PATH and ZISK_HINTS environment variables if not set in args + let mut args = args.clone(); + if args.zisk_path.is_none() { + if let Ok(env_path) = std::env::var("ZISK_PATH") { + args.zisk_path = Some(env_path); + } + } + if args.hints.is_none() { + if let Ok(env_hints) = std::env::var("ZISK_HINTS") { + args.hints = env_hints.parse().ok(); + } + } + // Get the program metadata. let program_metadata_file = program_dir.join("Cargo.toml"); let mut program_metadata_cmd = cargo_metadata::MetadataCommand::new(); let program_metadata = program_metadata_cmd.manifest_path(program_metadata_file).exec()?; // Get the command corresponding to Docker or local build. - let cmd = create_command(args, &program_dir, &program_metadata); + let cmd = create_command(&args, &program_dir, &program_metadata); - let target_elf_paths = generate_elf_paths(&program_metadata, Some(args)); + let target_elf_paths = generate_elf_paths(&program_metadata, Some(&args)); if target_elf_paths.len() > 1 && args.elf_name.is_some() { anyhow::bail!("--elf-name is not supported when --output-directory is used and multiple ELFs are built."); @@ -80,6 +98,33 @@ pub fn execute_build_program( execute_command(cmd)?; + // Generate assembly for all ELF files (only if not already generated) + let hints = args.hints.unwrap_or(false); + println!("cargo:rerun-if-env-changed=ZISK_HINTS"); + + let zisk_path_buf = args.zisk_path.as_ref().map(PathBuf::from); + let output_path = get_output_path(&None)?; + for (_, elf_path) in target_elf_paths.iter() { + let elf_path_std = elf_path.as_std_path(); + + let assembly_exists = assembly_files_exist(elf_path_std, &output_path)?; + let hints_marker = output_path.join(format!( + "{}.assembly_hints", + elf_path_std.file_name().unwrap().to_string_lossy() + )); + let new_value = if hints { "on" } else { "off" }; + + let hints_changed = match std::fs::read_to_string(&hints_marker) { + Ok(prev) => prev != new_value, + Err(_) => true, + }; + + if !assembly_exists || hints_changed { + gen_assembly(elf_path_std, &zisk_path_buf, &None, hints, true)?; + std::fs::write(&hints_marker, new_value)?; + } + } + if let Some(output_directory) = &args.output_directory { // The path to the output directory, maybe relative or absolute. let output_directory = PathBuf::from(output_directory); @@ -102,7 +147,7 @@ pub fn execute_build_program( } } - print_elf_paths_cargo_directives(&target_elf_paths); + print_elf_paths_cargo_directives(&target_elf_paths, hints); Ok(target_elf_paths) } @@ -157,8 +202,13 @@ pub fn generate_elf_paths( vec![(bin_target.name.to_owned(), target_elf_path)] } -fn print_elf_paths_cargo_directives(target_elf_paths: &[(String, Utf8PathBuf)]) { +fn print_elf_paths_cargo_directives(target_elf_paths: &[(String, Utf8PathBuf)], hints: bool) { + println!("cargo:rerun-if-env-changed=ZISK_HINTS"); + for (target_name, elf_path) in target_elf_paths.iter() { println!("cargo:rustc-env=ZISK_ELF_{target_name}={elf_path}"); + if hints { + println!("cargo:rustc-env=ZISK_ELF_{target_name}_WITH_HINTS=1"); + } } } diff --git a/ziskbuild/src/lib.rs b/ziskbuild/src/lib.rs index 113af763f..7ec3085c3 100644 --- a/ziskbuild/src/lib.rs +++ b/ziskbuild/src/lib.rs @@ -43,6 +43,12 @@ pub struct BuildArgs { #[clap(long, value_name = "ELF_NAME")] elf_name: Option, + + #[clap(short = 'z', long, value_name = "ZISK_PATH")] + zisk_path: Option, + + #[clap(long, value_name = "HINTS")] + pub hints: Option, } pub fn build_program(path: &str) { diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 2f0b485ad..17028641d 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -77,9 +77,8 @@ pub fn read_input_slice() -> Box<[u8]> { } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -pub fn set_output(id: usize, value: u32) { +pub(crate) fn set_output(id: usize, value: u32) { use std::arch::asm; - let addr_n: *mut u32; let addr_v: *mut u32; let arch_id_zisk: usize; @@ -93,28 +92,16 @@ pub fn set_output(id: usize, value: u32) { assert!(id < 64, "Maximum number of public outputs: 64"); if arch_id_zisk == ARCH_ID_ZISK as usize { - addr_n = OUTPUT_ADDR as *mut u32; - addr_v = (OUTPUT_ADDR + 4 + 4 * (id as u64)) as *mut u32; + addr_v = (OUTPUT_ADDR + 4 * (id as u64)) as *mut u32; } else { - addr_n = 0x1000_0000 as *mut u32; - addr_v = (0x1000_0000 + 4 + 4 * (id as u64)) as *mut u32; - } - - let n; - - unsafe { - n = core::ptr::read(addr_n) as usize; - } - - if id + 1 > n { - unsafe { core::ptr::write_volatile(addr_n, (id + 1) as u32) }; + addr_v = (0x1000_0000 + 4 * (id as u64)) as *mut u32; } unsafe { core::ptr::write_volatile(addr_v, value) }; } #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] -pub fn set_output(id: usize, value: u32) { +pub(crate) fn set_output(id: usize, value: u32) { println!("public {id}: {value:#010x}"); } From a42dfc6ca6c32ad24bf97f3b640a17a3f097d359 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 3 Feb 2026 10:07:47 +0100 Subject: [PATCH 417/782] Make 2 improvements when b address = free input --- core/src/zisk_rom_2_asm.rs | 55 +++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 21a002ca3..d33888d73 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -145,7 +145,7 @@ pub struct ZiskAsmContext { mem_chunk_id: String, // 0, 1, 2, 3, 4... mem_chunk_mask: String, // Module 8 of the chunks we want to activate, e.g. 0x03 mem_rsp: String, // Backup of rsp register value from caller - mem_free_input: String, // Free input address (0x90000000) used in free call operations + mem_free_input: String, // Free input address (0x90000000) used in free call operations, but stored in memory to allow sharing the input shared memory mem_precompile_results_address: String, // Address where precompile results are read from mem_precompile_written_address: String, // Address where precompile written counter is stored mem_precompile_read_address: String, // Address where precompile read counter is stored @@ -1554,28 +1554,34 @@ impl ZiskRom2Asm { SRC_MEM => { *code += &ctx.full_line_comment("b=SRC_MEM".to_string()); - let b_is_free_input = instruction.b_offset_imm0 == FREE_INPUT_ADDR - && instruction.b_use_sp_imm1 == 0; + let b_is_free_input = (instruction.b_offset_imm0 == FREE_INPUT_ADDR) + && (instruction.b_use_sp_imm1 == 0); if !ctx.chunk_player_mem_reads_collect_main() { - // Calculate memory address - *code += &format!( - "\tmov {}, 0x{:x} {}\n", - REG_ADDRESS, - instruction.b_offset_imm0, - ctx.comment_str("address = b_offset_imm0") - ); - if instruction.b_use_sp_imm1 != 0 { + if b_is_free_input { + // No need to write REG_ADDRESS, as we will read from mem_free_input + ctx.address_is_constant = true; + ctx.address_constant_value = instruction.b_offset_imm0; + } else { + // Calculate memory address *code += &format!( - "\tadd {}, {} {}\n", + "\tmov {}, 0x{:x} {}\n", REG_ADDRESS, - ctx.mem_sp, - ctx.comment_str("address += sp") + instruction.b_offset_imm0, + ctx.comment_str("address = b_offset_imm0") ); - ctx.address_is_constant = false; - } else { - ctx.address_is_constant = true; - ctx.address_constant_value = instruction.b_offset_imm0; + if instruction.b_use_sp_imm1 != 0 { + *code += &format!( + "\tadd {}, {} {}\n", + REG_ADDRESS, + ctx.mem_sp, + ctx.comment_str("address += sp") + ); + ctx.address_is_constant = false; + } else { + ctx.address_is_constant = true; + ctx.address_constant_value = instruction.b_offset_imm0; + } } } @@ -6084,15 +6090,7 @@ impl ZiskRom2Asm { //Self::assert_rsp_is_aligned(ctx, code); } - // Get free input address - *code += &format!( - "\tmov {}, {} {}\n", - REG_ADDRESS, - FREE_INPUT_ADDR, - ctx.comment_str("address = free_input") - ); - - // Copy ctx.result[0] or 0 into free input + // If ctx.result_size == 0 => free_input = 0 *code += &format!( "\tmov {}, qword {}[{} + {}*8] {}\n", REG_AUX, @@ -6103,6 +6101,8 @@ impl ZiskRom2Asm { ); *code += &format!("\tcmp {REG_AUX}, 0\n"); *code += &format!("\tjz pc_{:x}_fcall_result_zero\n", ctx.pc); + + // Copy ctx.result[0] to free input address *code += &format!( "\tmov {}, qword {}[{} + {}*8] {}\n", REG_VALUE, @@ -6118,6 +6118,7 @@ impl ZiskRom2Asm { ctx.comment_str("free_input = value") ); *code += &format!("\tjmp pc_{:x}_fcall_result_done\n", ctx.pc); + *code += &format!("pc_{:x}_fcall_result_zero:\n", ctx.pc); *code += &format!( "\tmov {}, 0 {}\n", From e1a10b852438c91e2fed5922f3a3078495b45e46 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 3 Feb 2026 12:03:20 +0000 Subject: [PATCH 418/782] unification working --- emulator-asm/asm-runner/src/asm_mo_runner.rs | 14 +- emulator-asm/asm-runner/src/asm_mt.rs | 38 +--- emulator-asm/asm-runner/src/asm_mt_runner.rs | 14 +- emulator-asm/asm-runner/src/asm_rh_runner.rs | 14 +- emulator-asm/asm-runner/src/asm_runner.rs | 31 ++- .../asm-runner/src/asm_services/services.rs | 46 +++- emulator-asm/asm-runner/src/hints_shmem.rs | 204 +++++++++++------- emulator-asm/asm-runner/src/lib.rs | 16 +- emulator-asm/asm-runner/src/shmem_utils.rs | 18 +- executor/src/emu_asm.rs | 24 +-- 10 files changed, 264 insertions(+), 155 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index 98e4a390b..c15ab58a0 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -37,8 +37,11 @@ impl PreloadedMO { AsmServices::default_port(&AsmService::MO, local_rank) }; - let output_name = - AsmSharedMemory::::shmem_output_name(port, AsmService::MO, local_rank); + let output_name = AsmSharedMemory::::shmem_output_name( + base_port.unwrap(), + AsmService::MO, + local_rank, + ); let output_shared_memory = AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; @@ -98,8 +101,11 @@ impl AsmRunnerMO { AsmServices::default_port(&AsmService::MO, local_rank) }; - let sem_chunk_done_name = - AsmSharedMemory::::shmem_chunk_done_name(port, AsmService::MO, local_rank); + let sem_chunk_done_name = AsmSharedMemory::::shmem_chunk_done_name( + base_port.unwrap(), + AsmService::MO, + local_rank, + ); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; diff --git a/emulator-asm/asm-runner/src/asm_mt.rs b/emulator-asm/asm-runner/src/asm_mt.rs index f5cde7f79..23dbd55e2 100644 --- a/emulator-asm/asm-runner/src/asm_mt.rs +++ b/emulator-asm/asm-runner/src/asm_mt.rs @@ -76,32 +76,12 @@ impl AsmMTChunk { #[repr(C)] #[derive(Debug)] -pub struct AsmInputC { - pub chunk_size: u64, - pub max_steps: u64, - pub initial_trace_size: u64, - pub input_data_size: u64, -} - -impl AsmInputC { - pub fn to_bytes(&self) -> Vec { - let mut bytes = Vec::with_capacity(32); - bytes.extend_from_slice(&self.chunk_size.to_le_bytes()); - bytes.extend_from_slice(&self.max_steps.to_le_bytes()); - bytes.extend_from_slice(&self.initial_trace_size.to_le_bytes()); - bytes.extend_from_slice(&self.input_data_size.to_le_bytes()); - bytes - } -} - -#[repr(C)] -#[derive(Debug)] -pub struct AsmInputC2 { +pub struct AsmInputHeader { pub zero: u64, // Not used pub input_data_size: u64, } -impl AsmInputC2 { +impl AsmInputHeader { pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::with_capacity(32); bytes.extend_from_slice(&0u64.to_le_bytes()); @@ -109,17 +89,3 @@ impl AsmInputC2 { bytes } } - -#[repr(C)] -#[derive(Debug)] -pub struct AsmInputC3 { - pub input_data_size: u64, -} - -impl AsmInputC3 { - pub fn to_bytes(&self) -> Vec { - let mut bytes = Vec::with_capacity(32); - bytes.extend_from_slice(&self.input_data_size.to_le_bytes()); - bytes - } -} diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 156fdc5d2..184eaf4f6 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -41,8 +41,11 @@ impl PreloadedMT { AsmServices::default_port(&AsmService::MT, local_rank) }; - let output_name = - AsmSharedMemory::::shmem_output_name(port, AsmService::MT, local_rank); + let output_name = AsmSharedMemory::::shmem_output_name( + base_port.unwrap(), + AsmService::MT, + local_rank, + ); let output_shared_memory = AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; @@ -85,8 +88,11 @@ impl AsmRunnerMT { AsmServices::default_port(&AsmService::MT, local_rank) }; - let sem_chunk_done_name = - AsmSharedMemory::::shmem_chunk_done_name(port, AsmService::MT, local_rank); + let sem_chunk_done_name = AsmSharedMemory::::shmem_chunk_done_name( + base_port.unwrap(), + AsmService::MT, + local_rank, + ); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; diff --git a/emulator-asm/asm-runner/src/asm_rh_runner.rs b/emulator-asm/asm-runner/src/asm_rh_runner.rs index 4625b7a78..e04ba9f50 100644 --- a/emulator-asm/asm-runner/src/asm_rh_runner.rs +++ b/emulator-asm/asm-runner/src/asm_rh_runner.rs @@ -25,8 +25,11 @@ impl PreloadedRH { AsmServices::default_port(&AsmService::RH, local_rank) }; - let output_name = - AsmSharedMemory::::shmem_output_name(port, AsmService::RH, local_rank); + let output_name = AsmSharedMemory::::shmem_output_name( + base_port.unwrap(), + AsmService::RH, + local_rank, + ); let output_shared_memory = AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; @@ -77,8 +80,11 @@ impl AsmRunnerRH { AsmServices::default_port(&AsmService::RH, local_rank) }; - let sem_chunk_done_name = - AsmSharedMemory::::shmem_chunk_done_name(port, AsmService::RH, local_rank); + let sem_chunk_done_name = AsmSharedMemory::::shmem_chunk_done_name( + base_port.unwrap(), + AsmService::RH, + local_rank, + ); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; diff --git a/emulator-asm/asm-runner/src/asm_runner.rs b/emulator-asm/asm-runner/src/asm_runner.rs index 1be6fbadc..08f8fb417 100644 --- a/emulator-asm/asm-runner/src/asm_runner.rs +++ b/emulator-asm/asm-runner/src/asm_runner.rs @@ -40,6 +40,8 @@ pub struct AsmRunnerOptions { pub local_rank: i32, pub base_port: Option, pub unlock_mapped_memory: bool, + pub share_input_shmem: bool, + pub open_input_shmem: bool, } impl Default for AsmRunnerOptions { @@ -61,6 +63,8 @@ impl AsmRunnerOptions { local_rank: 0, base_port: None, unlock_mapped_memory: false, + share_input_shmem: false, + open_input_shmem: false, } } @@ -114,11 +118,26 @@ impl AsmRunnerOptions { self } + pub fn with_share_input_shmem(mut self, value: bool) -> Self { + self.share_input_shmem = value; + self + } + + pub fn with_open_input_shmem(mut self, value: bool) -> Self { + self.open_input_shmem = value; + self + } + /// Applies the configuration flags to a command-line `Command`. /// /// # Arguments /// * `command` - A mutable reference to the `Command` to be modified. - pub fn apply_to_command(&self, command: &mut Command, asm_service: &AsmService) { + pub fn apply_to_command( + &self, + command: &mut Command, + asm_service: &AsmService, + shm_prefix: &str, + ) { let port = if let Some(base_port) = self.base_port { AsmServices::port_for(asm_service, base_port, self.local_rank) } else { @@ -132,7 +151,7 @@ impl AsmRunnerOptions { command.arg("-u"); } - command.arg("--shm_prefix").arg(AsmServices::shmem_prefix(port, self.local_rank)); + command.arg("--shm_prefix").arg(shm_prefix); match asm_service { AsmService::MT => { @@ -154,6 +173,14 @@ impl AsmRunnerOptions { command.arg("-m"); } + if self.share_input_shmem { + command.arg("--share_input_shm"); + } + + if self.open_input_shmem { + command.arg("--open_input_shm"); + } + if self.verbose { command.arg("-v"); command.stdout(std::process::Stdio::inherit()); diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index c9845776d..98314e9ed 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -69,7 +69,7 @@ impl AsmServices { pub fn start_asm_services( &self, ziskemuasm_path: &Path, - options: AsmRunnerOptions, + mut options: AsmRunnerOptions, ) -> Result<()> { // ! TODO Remove this when we have a proper way to find the path let path_str = ziskemuasm_path.to_string_lossy(); @@ -91,14 +91,49 @@ impl AsmServices { } } - for service in &Self::SERVICES { + let shm_prefix = Self::shmem_prefix( + Self::port_for(&AsmService::MO, self.base_port, self.local_rank), + self.local_rank, + ); + + let service = &Self::SERVICES[0]; + tracing::debug!( + ">>> [{}] Starting ASM service: {} on port {}", + self.world_rank, + service, + Self::port_for(service, self.base_port, self.local_rank) + ); + + // First service has to create the shared memory, the rest can use it + // Es a dir, passar ZISK_15200_0 com a prefix a tots els asm services, + // passar --shared_input_shmem a tots tres, i passar --open_input_shmem al segon i al tercer. + options.share_input_shmem = true; + options.open_input_shmem = false; + self.start_asm_service(service, trimmed_path, &options, &shm_prefix); + + Self::wait_for_service_ready( + service, + Self::port_for(service, self.base_port, self.local_rank), + ); + tracing::debug!( + ">>> [{}] ASM service {} is ready on port {}", + self.world_rank, + service, + Self::port_for(service, self.base_port, self.local_rank) + ); + + for service in &Self::SERVICES[1..] { tracing::debug!( ">>> [{}] Starting ASM service: {} on port {}", self.world_rank, service, Self::port_for(service, self.base_port, self.local_rank) ); - self.start_asm_service(service, trimmed_path, &options); + + options.share_input_shmem = true; + options.open_input_shmem = true; + + self.start_asm_service(service, trimmed_path, &options, &shm_prefix); } for service in &Self::SERVICES { @@ -162,6 +197,7 @@ impl AsmServices { asm_service: &AsmService, trimmed_path: &str, options: &AsmRunnerOptions, + shm_prefix: &str, ) { // Prepare command let command_path = trimmed_path.to_string() + &format!("-{asm_service}.bin"); @@ -180,7 +216,7 @@ impl AsmServices { } } - options.apply_to_command(&mut command, asm_service); + options.apply_to_command(&mut command, asm_service, shm_prefix); if let Err(e) = command.spawn() { tracing::error!("Child process failed: {:?}", e); @@ -312,7 +348,7 @@ impl AsmServices { let sem_name = format!( "/{}_{}_shutdown_done", - Self::shmem_prefix(port, self.local_rank), + Self::shmem_prefix(self.base_port, self.local_rank), service.as_str() ); diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index f6b543be3..781f861a0 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -13,45 +13,47 @@ use std::cell::RefCell; use tracing::debug; use zisk_common::io::StreamSink; -/// Names for a service's shared memory and semaphore resources -struct ServiceResourceNames { - control_writer: String, +/// Names for separate resources (per-service) +struct SeparateResourceNames { control_reader: String, - data_name: String, sem_available_name: String, sem_read_name: String, } -impl ServiceResourceNames { +impl SeparateResourceNames { fn new(service: &AsmService, port: u16, local_rank: i32) -> Self { Self { - control_writer: shmem_control_writer_name(port, *service, local_rank), control_reader: shmem_control_reader_name(port, *service, local_rank), - data_name: shmem_precompile_name(port, *service, local_rank), sem_available_name: sem_available_name(port, *service, local_rank), sem_read_name: sem_read_name(port, *service, local_rank), } } } -/// Represents a service's shared memory and synchronization resources -struct ServiceResources { - /// Control shared memory writer - control_writer: SharedMemoryWriter, - /// Control shared memory reader +/// Separate resources, one per asm service +struct SeparateResources { + /// Control shared memory reader (consumer's read position) control_reader: SharedMemoryReader, - /// Data shared memory writer - data_writer: SharedMemoryWriter, - /// Semaphore to signal data availability + /// Semaphore to signal data availability to this consumer sem_available: NamedSemaphore, - /// Semaphore to wait for data consumption + /// Semaphore to wait for this consumer's data consumption sem_read: NamedSemaphore, } +/// Unified resources shared across all asm services +struct UnifiedResources { + /// Control shared memory writer (single write_pos) + control_writer: SharedMemoryWriter, + /// Data shared memory writer (single data buffer) + data_writer: SharedMemoryWriter, +} + /// HintsShmem struct manages the writing of processed precompile hints to shared memory. pub struct HintsShmem { - /// Service resources combining shared memory writers and semaphores - resources: RefCell>, + /// Unified resources (single data buffer and control writer) + unified: RefCell, + /// Separate resources (control_reader and semaphores for each service) + separate: RefCell>, } unsafe impl Send for HintsShmem {} @@ -76,7 +78,21 @@ impl HintsShmem { local_rank: i32, unlock_mapped_memory: bool, ) -> Result { - let resources_names = AsmServices::SERVICES + // Use the first service's port for shared resources naming + let first_service = &AsmServices::SERVICES[0]; + let shared_port = if let Some(base_port) = base_port { + AsmServices::port_for(first_service, base_port, local_rank) + } else { + AsmServices::default_port(first_service, local_rank) + }; + + // Create unified resources (single data buffer and control writer) + let unified = + Self::create_unified_resources(shared_port, local_rank, unlock_mapped_memory)?; + unified.control_writer.write_u64_at(0, 0); + + // Create separate resources + let separate_names: Vec = AsmServices::SERVICES .iter() .map(|service| { let port = if let Some(base_port) = base_port { @@ -84,46 +100,52 @@ impl HintsShmem { } else { AsmServices::default_port(service, local_rank) }; - ServiceResourceNames::new(service, port, local_rank) + SeparateResourceNames::new(service, base_port.unwrap(), local_rank) }) .collect(); - let mut resources = Self::create_resources(resources_names, unlock_mapped_memory)?; + let separate = Self::create_separate_resources(separate_names)?; - for resource in resources.iter_mut() { - resource.control_writer.write_u64_at(0, 0); - } - - Ok(Self { resources: RefCell::new(resources) }) + Ok(Self { unified: RefCell::new(unified), separate: RefCell::new(separate) }) } - /// Initialize the shared memory writers for the pipeline. - /// - /// This method creates SharedMemoryWriter instances for each shared memory name. - /// If writers are already initialized it logs a warning and does nothing. - fn create_resources( - resources_names: Vec, + /// Create the unified resources (single data buffer and control writer). + fn create_unified_resources( + port: u16, + local_rank: i32, unlock_mapped_memory: bool, - ) -> Result> { - debug!("Initializing resources for precompile hints"); - resources_names + ) -> Result { + debug!("Initializing unified resources for precompile hints"); + let control_name = shmem_control_writer_name(port, local_rank); + let data_name = shmem_precompile_name(port, local_rank); + + Ok(UnifiedResources { + control_writer: SharedMemoryWriter::new( + &control_name, + Self::CONTROL_PRECOMPILE_SIZE as usize, + unlock_mapped_memory, + )?, + data_writer: SharedMemoryWriter::new( + &data_name, + Self::MAX_PRECOMPILE_SIZE as usize, + unlock_mapped_memory, + )?, + }) + } + + /// Create separate resources (control_reader and semaphores for each service). + fn create_separate_resources( + separate_names: Vec, + ) -> Result> { + debug!("Initializing separate resources for precompile hints"); + separate_names .iter() - .map(|names: &ServiceResourceNames| -> Result { - Ok(ServiceResources { - control_writer: SharedMemoryWriter::new( - &names.control_writer, - Self::CONTROL_PRECOMPILE_SIZE as usize, - unlock_mapped_memory, - )?, + .map(|names: &SeparateResourceNames| -> Result { + Ok(SeparateResources { control_reader: SharedMemoryReader::new( &names.control_reader, Self::CONTROL_PRECOMPILE_SIZE as usize, )?, - data_writer: SharedMemoryWriter::new( - &names.data_name, - Self::MAX_PRECOMPILE_SIZE as usize, - unlock_mapped_memory, - )?, sem_available: NamedSemaphore::create(&names.sem_available_name, 0).map_err( |e| { anyhow::anyhow!( @@ -147,55 +169,77 @@ impl HintsShmem { } impl StreamSink for HintsShmem { - /// Writes processed precompile hints to all shared memory writers. + /// Writes processed precompile hints to the shared memory. + /// + /// Data is written ONCE to the shared buffer, then all consumers are notified. + /// Flow control waits for the slowest consumer. /// /// # Arguments /// * `processed` - A vector of processed precompile hints as u64 values. /// /// # Returns - /// * `Ok(())` - If hints were successfully written to all shared memories - /// * `Err` - If writing to any shared memory fails + /// * `Ok(())` - If hints were successfully written to shared memory + /// * `Err` - If writing to shared memory fails #[inline] fn submit(&self, processed: Vec) -> anyhow::Result<()> { let data_size = processed.len() as u64; - debug_assert!( - data_size <= Self::BUFFER_CAPACITY_U64, - "Processed data size ({} u64 elements) exceeds maximum precompile shared memory capacity ({} u64 elements)", - data_size, - Self::BUFFER_CAPACITY_U64 - ); - - let mut resources = self.resources.borrow_mut(); - - for resource in resources.iter_mut() { - // Read current write position once (we're the only writer) - let write_pos = resource.control_writer.read_u64_at(0); - - loop { - // Read current read position (updated by reader) - let read_pos = resource.control_reader.read_u64_at(0); + // Early return for empty data + if data_size == 0 { + return Ok(()); + } - // Calculate occupied space in ring buffer (positions are absolute values in u64 elements) - let occupied_space = write_pos - read_pos; - let available_space = Self::BUFFER_CAPACITY_U64 - occupied_space; + // Validate data size fits in buffer + if data_size > Self::BUFFER_CAPACITY_U64 { + return Err(anyhow::anyhow!( + "Processed data size ({} u64 elements) exceeds buffer capacity ({} u64 elements)", + data_size, + Self::BUFFER_CAPACITY_U64 + )); + } - // Flow control based on buffer occupancy - if available_space >= data_size { - break; - } + let mut unified = self.unified.borrow_mut(); + let mut separate = self.separate.borrow_mut(); + + // Read current write position once + let write_pos = unified.control_writer.read_u64_at(0); + + // Flow control: wait until all consumers have advanced enough + // We need to wait for the slowest consumer (minimum read position) + loop { + // Find the slowest consumer (minimum read position) and its index + let (slowest_idx, min_read_pos) = separate + .iter() + .enumerate() + .map(|(i, res)| (i, res.control_reader.read_u64_at(0))) + .min_by_key(|(_, pos)| *pos) + .unwrap(); + + // Calculate occupied space based on slowest consumer (saturating to avoid underflow) + let occupied_space = write_pos.saturating_sub(min_read_pos); + let available_space = Self::BUFFER_CAPACITY_U64.saturating_sub(occupied_space); + + // Flow control based on buffer occupancy + if available_space >= data_size { + break; + } - // Not enough space - wait for consumption - resource.sem_read.wait()?; + // Not enough space - wait for the SLOWEST consumer to signal progress + // Retry on interrupt (EINTR) + if separate[slowest_idx].sem_read.wait().is_err() { + continue; } + } - // Write data to shared memory with automatic wraparound - resource.data_writer.write_ring_buffer(&processed); + // Write data ONCE to the unified shared memory buffer + unified.data_writer.write_ring_buffer(&processed); - // Update write position in control memory (absolute position, always increases) - resource.control_writer.write_u64_at(0, write_pos + data_size); + // Update write position ONCE in control memory + unified.control_writer.write_u64_at(0, write_pos + data_size); - resource.sem_available.post()?; + // Notify ALL consumers that new data is available + for res in separate.iter_mut() { + res.sem_available.post()?; } Ok(()) diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 48be2bac3..883d2a61e 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -71,10 +71,18 @@ fn build_name( ) } +fn build_name2(prefix: &str, port: u16, local_rank: i32, suffix: &str) -> String { + format!("{}{}_{}", prefix, AsmServices::shmem_prefix(port, local_rank), suffix) +} + fn build_shmem_name(port: u16, asm_service: AsmService, local_rank: i32, suffix: &str) -> String { build_name("", port, asm_service, local_rank, suffix) } +fn build_shmem_name2(port: u16, local_rank: i32, suffix: &str) -> String { + build_name2("", port, local_rank, suffix) +} + fn build_sem_name(port: u16, asm_service: AsmService, local_rank: i32, suffix: &str) -> String { build_name("/", port, asm_service, local_rank, suffix) } @@ -84,8 +92,8 @@ pub fn shmem_input_name(port: u16, asm_service: AsmService, local_rank: i32) -> } /// Shared memory name for precompile hints data -pub fn shmem_precompile_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - build_shmem_name(port, asm_service, local_rank, "precompile") +pub fn shmem_precompile_name(port: u16, local_rank: i32) -> String { + build_shmem_name2(port, local_rank, "precompile") } /// Shared memory name for precompile hints data @@ -99,8 +107,8 @@ pub fn sem_read_name(port: u16, asm_service: AsmService, local_rank: i32) -> Str } /// Shared memory name for precompile hints data control -pub fn shmem_control_writer_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - build_shmem_name(port, asm_service, local_rank, "control_input") +pub fn shmem_control_writer_name(port: u16, local_rank: i32) -> String { + build_shmem_name2(port, local_rank, "control_input") } pub fn shmem_control_reader_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 84acfefbb..8b9f49ba0 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -16,7 +16,7 @@ use zisk_common::io::{ZiskIO, ZiskStdin}; use anyhow::anyhow; use anyhow::Result; -use crate::{AsmInputC2, AsmService, AsmServices, SharedMemoryWriter}; +use crate::{AsmInputHeader, AsmService, AsmServices, SharedMemoryWriter}; pub enum AsmSharedMemoryMode { ReadOnly, @@ -276,6 +276,10 @@ impl AsmSharedMemory { format!("{}_{}_input", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) } + pub fn shmem_input_name2(port: u16, asm_service: AsmService, local_rank: i32) -> String { + format!("{}_input", AsmServices::shmem_prefix(port, local_rank)) + } + pub fn shmem_output_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { format!("{}_{}_output", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) } @@ -346,16 +350,22 @@ pub unsafe fn unmap(ptr: *mut c_void, size: usize) { } pub fn write_input(stdin: &mut ZiskStdin, shmem_input_writer: &SharedMemoryWriter) { + const HEADER_SIZE: usize = size_of::(); + let inputs = stdin.read(); - let asm_input = AsmInputC2 { zero: 0, input_data_size: inputs.len() as u64 }; - let shmem_input_size = (inputs.len() + size_of::() + 7) & !7; + + let shmem_input_size = (HEADER_SIZE + inputs.len() + 7) & !7; let mut full_input = Vec::with_capacity(shmem_input_size); - full_input.extend_from_slice(&asm_input.to_bytes()); + + let header = AsmInputHeader { zero: 0, input_data_size: 0 }; + full_input.extend_from_slice(&header.to_bytes()); full_input.extend_from_slice(&inputs); while full_input.len() < shmem_input_size { full_input.push(0); } shmem_input_writer.write_input(&full_input).expect("Failed to write input to shared memory"); + + shmem_input_writer.write_u64_at(8, inputs.len() as u64); } diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index d46468246..a3fed84cb 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -8,13 +8,12 @@ use crate::{ DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, MAX_NUM_STEPS, }; use asm_runner::{ - write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmServices, AsmSharedMemory, - MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, + write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmService, AsmServices, + AsmSharedMemory, MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, }; use data_bus::DataBusTrait; use fields::PrimeField64; use proofman_common::ProofCtx; -use rayon::prelude::*; use sm_rom::RomSM; #[cfg(feature = "stats")] use zisk_common::ExecutorStatsEvent; @@ -54,7 +53,7 @@ pub struct EmulatorAsm { /// Shared memory writers for each assembly service. #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - shmem_input_writer: [Arc>>; AsmServices::SERVICES.len()], + shmem_input_writer: Arc>>, } impl EmulatorAsm { @@ -90,7 +89,7 @@ impl EmulatorAsm { #[cfg(all(target_os = "linux", target_arch = "x86_64"))] asm_shmem_rh: Arc::new(Mutex::new(None)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - shmem_input_writer: std::array::from_fn(|_| Arc::new(Mutex::new(None))), + shmem_input_writer: Arc::new(Mutex::new(None)), } } @@ -141,12 +140,10 @@ impl EmulatorAsm { #[cfg(feature = "stats")] stats.add_stat(parent_stats_id, stats_id, "ASM_WRITE_INPUT", 0, ExecutorStatsEvent::Begin); - AsmServices::SERVICES.par_iter().enumerate().for_each(|(idx, service)| { - let mut input_writer = self.shmem_input_writer[idx].lock().unwrap(); - input_writer.get_or_insert_with(|| self.create_shmem_writer(service)); + let mut input_writer = self.shmem_input_writer.lock().unwrap(); + input_writer.get_or_insert_with(|| self.create_shmem_writer(&AsmService::MO)); - write_input(&mut stdin.lock().unwrap(), input_writer.as_ref().unwrap()); - }); + write_input(&mut stdin.lock().unwrap(), input_writer.as_ref().unwrap()); #[cfg(feature = "stats")] stats.add_stat(parent_stats_id, stats_id, "ASM_WRITE_INPUT", 0, ExecutorStatsEvent::End); @@ -227,8 +224,11 @@ impl EmulatorAsm { AsmServices::default_port(service, self.local_rank) }; - let shmem_input_name = - AsmSharedMemory::::shmem_input_name(port, *service, self.local_rank); + let shmem_input_name = AsmSharedMemory::::shmem_input_name2( + self.base_port.unwrap(), + *service, + self.local_rank, + ); tracing::debug!( "Initializing SharedMemoryWriter for service {:?} at '{}'", From 11f1810fcc9707045ea2c2dfa00d748f5ed49ddc Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 4 Feb 2026 09:09:13 +0100 Subject: [PATCH 419/782] Avoid trace shm remapping in assembly services --- emulator-asm/src/main.c | 342 +++++++++++++++++++++++++++------------- 1 file changed, 230 insertions(+), 112 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 631e49106..be72fd89e 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -46,8 +46,15 @@ uint64_t get_precompile_results(void); #define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) #define TRACE_ADDR (uint64_t)0xc0000000 -#define INITIAL_TRACE_SIZE (uint64_t)0x180000000 // 6GB -#define DELTA_TRACE_SIZE (uint64_t)0x080000000 // 2GB +#define TRACE_INITIAL_SIZE (uint64_t)0x180000000 // 6GB +#define TRACE_DELTA_SIZE (uint64_t)0x080000000 // 2GB +#define TRACE_MAX_SIZE (uint64_t)0x1000000000 // 64GB +#define TRACE_NUMBER_OF_CHUNKS (((TRACE_MAX_SIZE - TRACE_INITIAL_SIZE) / TRACE_DELTA_SIZE) + 1) + +uint64_t initial_trace_size = TRACE_INITIAL_SIZE; +uint64_t trace_address = TRACE_ADDR; +uint64_t trace_size = TRACE_INITIAL_SIZE; +uint64_t trace_used_size = 0; #define CONTROL_INPUT_ADDR (uint64_t)0x70000000 #define CONTROL_INPUT_SIZE (uint64_t)0x1000 // 4kB @@ -188,7 +195,7 @@ uint64_t max_steps = (1ULL << 32); // Chunk player globals uint64_t chunk_player_address = 0; -uint64_t chunk_player_mt_size = INITIAL_TRACE_SIZE; // TODO +uint64_t chunk_player_mt_size = TRACE_INITIAL_SIZE; // TODO void set_max_steps (uint64_t new_max_steps) { @@ -202,11 +209,6 @@ void set_max_steps (uint64_t new_max_steps) max_steps = new_max_steps; } -uint64_t initial_trace_size = INITIAL_TRACE_SIZE; -uint64_t trace_address = TRACE_ADDR; -uint64_t trace_size = INITIAL_TRACE_SIZE; -uint64_t trace_used_size = 0; - // Worst case: every chunk instruction is a keccak operation, with an input data of 256 bytes #define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) @@ -215,7 +217,7 @@ uint64_t trace_used_size = 0; #define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) #define MAX_CHUNK_TRACE_SIZE ((INITIAL_CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) -uint64_t trace_address_threshold = TRACE_ADDR + INITIAL_TRACE_SIZE - MAX_CHUNK_TRACE_SIZE; +uint64_t trace_address_threshold = TRACE_ADDR + TRACE_INITIAL_SIZE - MAX_CHUNK_TRACE_SIZE; uint64_t print_pc_counter = 0; int map_locked_flag = MAP_LOCKED; @@ -365,6 +367,219 @@ int shmem_control_output_fd = -1; uint64_t * shmem_control_output_address = NULL; volatile uint64_t * precompile_read_address = NULL; +/********************/ +/* TRACE ALLOCATION */ +/********************/ + +void trace_virtual_alloc (void) +{ + void * addr = mmap((void *)TRACE_ADDR, TRACE_MAX_SIZE, PROT_NONE, MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr != (void *)TRACE_ADDR) + { + printf("ERROR: trace_virtual_alloc() failed to reserve trace memory at address 0x%lx\n", TRACE_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +uint64_t next_chunk_id = 0; // Next trace chunk id to be mapped, starting from 0 +int trace_chunk_fd[TRACE_NUMBER_OF_CHUNKS]; // File descriptors for each chunk +uint64_t trace_total_mapped_size = 0; // Total mapped trace size + +void * trace_get_chunk_address (uint64_t chunk_id) +{ + if (chunk_id == 0) + { + return (void *)TRACE_ADDR; + } + else + { + return (void *)(TRACE_ADDR + TRACE_INITIAL_SIZE + ((chunk_id - 1) * TRACE_DELTA_SIZE)); + } +} + +uint64_t trace_get_chunk_size (uint64_t chunk_id) +{ + if (chunk_id == 0) + { + return TRACE_INITIAL_SIZE; + } + else + { + return TRACE_DELTA_SIZE; + } +} + +void trace_generate_shmem_chunk_name(char * shmem_chunk_name, size_t shmem_chunk_name_size, uint64_t chunk_id) +{ + int result = snprintf(shmem_chunk_name, shmem_chunk_name_size, "%s_%lu", shmem_output_name, chunk_id); + if (result < 0 || result >= (int)shmem_chunk_name_size) + { + printf("ERROR: trace_generate_shmem_chunk_name() failed to create chunk shared memory name\n"); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +void trace_cleanup (void) +{ + // Unmap all mapped chunks + for (uint64_t chunk_id = 0; chunk_id < next_chunk_id; chunk_id++) + { + uint64_t chunk_size = trace_get_chunk_size(chunk_id); + void * chunk_address = trace_get_chunk_address(chunk_id); + int result = munmap(chunk_address, chunk_size); + if (result != 0) + { + printf("ERROR: trace_cleanup() failed calling munmap() chunk id=%lu size=%lu B address=0x%lx errno=%d=%s\n", chunk_id, chunk_size, (uint64_t)chunk_address, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Close the chunk shared memory file descriptor + close(trace_chunk_fd[chunk_id]); + trace_chunk_fd[chunk_id] = -1; + + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + shm_unlink(shmem_chunk_name); + } + + // Reset next chunk id + next_chunk_id = 0; +} + +void trace_preventive_cleanup (void) +{ + // Unmap all mapped chunks + for (uint64_t chunk_id = 0; chunk_id < TRACE_NUMBER_OF_CHUNKS; chunk_id++) + { + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + int result = shm_unlink(shmem_chunk_name); + if (result != 0) + { + break; + } + if (verbose) printf("trace_preventive_cleanup() unlinked chunk shared memory %s\n", shmem_chunk_name); + } +} + +void trace_map_next_chunk (void) +{ + // Get the next chunk id, size and address + uint64_t chunk_id = next_chunk_id; + if (chunk_id >= TRACE_NUMBER_OF_CHUNKS) + { + printf("ERROR: trace_map_next_chunk() exceeded maximum number of chunks %lu\n", TRACE_NUMBER_OF_CHUNKS); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t chunk_size = trace_get_chunk_size(chunk_id); + void * chunk_address = trace_get_chunk_address(chunk_id); + + if (verbose) printf("trace_map_next_chunk() mapping chunk id=%lu size=%lu B address=0x%lx\n", chunk_id, chunk_size, (uint64_t)chunk_address); + + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + shm_unlink(shmem_chunk_name); + + // Create the output shared memory + trace_chunk_fd[chunk_id] = shm_open(shmem_chunk_name, O_RDWR | O_CREAT | O_EXCL, 0666); + if (trace_chunk_fd[chunk_id] < 0) + { + printf("ERROR: trace_map_next_chunk() failed calling trace shm_open(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + int result = ftruncate(trace_chunk_fd[chunk_id], chunk_size); + if (result != 0) + { + printf("ERROR: trace_map_next_chunk() failed calling ftruncate(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(trace_chunk_fd[chunk_id]); + + // Map it to the trace address + if (verbose) gettimeofday(&start_time, NULL); + void * requested_address; + if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) + { + requested_address = 0; + } + else + { + requested_address = (void *)chunk_address; + } + int flags = MAP_SHARED | map_locked_flag; + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + flags |= MAP_FIXED; + } + void * pTrace = mmap(requested_address, trace_size, PROT_READ | PROT_WRITE, flags, trace_chunk_fd[chunk_id], 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pTrace == MAP_FAILED) + { + printf("ERROR: trace_map_next_chunk() failed calling mmap(pTrace) name=%s errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && ((uint64_t)pTrace != (uint64_t)requested_address)) + { + printf("ERROR: trace_map_next_chunk() called mmap(trace) but returned address = %p != 0x%lx\n", pTrace, (uint64_t)requested_address); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("trace_map_next_chunk() mapped %lu B to %s and returned address %p in %lu us\n", trace_size, shmem_chunk_name, pTrace, duration); + + // Update total mapped size + trace_total_mapped_size += chunk_size; + + // Increment next chunk id + next_chunk_id++; +} + +void trace_map_initialize (void) +{ + // Perform preventive cleanup of any leftover shared memory chunks + trace_preventive_cleanup(); + + // Reserve the full virtual trace address space + trace_virtual_alloc(); + + // Map the first chunk, i.e. chunk 0 + trace_map_next_chunk(); + + trace_address = TRACE_ADDR; + pOutputTrace = (uint64_t *)TRACE_ADDR; +} + int main(int argc, char *argv[]) { #ifdef DEBUG @@ -3710,72 +3925,7 @@ void server_setup (void) (gen_method == MemReads) || (gen_method == ChunkPlayerMemReadsCollectMain)) { - // Make sure the output shared memory is deleted - shm_unlink(shmem_output_name); - - // Create the output shared memory - shmem_output_fd = shm_open(shmem_output_name, O_RDWR | O_CREAT | O_EXCL, 0666); - if (shmem_output_fd < 0) - { - printf("ERROR: Failed calling trace shm_open(%s) errno=%d=%s\n", shmem_output_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Size it - result = ftruncate(shmem_output_fd, trace_size); - if (result != 0) - { - printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_output_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Sync - fsync(shmem_output_fd); - - // Map it to the trace address - if (verbose) gettimeofday(&start_time, NULL); - void * requested_address; - if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) - { - requested_address = 0; - } - else - { - requested_address = (void *)TRACE_ADDR; - } - int flags = MAP_SHARED | map_locked_flag; - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - flags |= MAP_FIXED; - } - void * pTrace = mmap(requested_address, trace_size, PROT_READ | PROT_WRITE, flags, shmem_output_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pTrace == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(pTrace) name=%s errno=%d=%s\n", shmem_output_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && ((uint64_t)pTrace != TRACE_ADDR)) - { - printf("ERROR: Called mmap(trace) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("mmap(trace) mapped %lu B and returned address %p in %lu us\n", trace_size, pTrace, duration); - - trace_address = (uint64_t)pTrace; - pOutputTrace = pTrace; + trace_map_initialize(); } /***********************/ @@ -3949,7 +4099,7 @@ void server_run (void) uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; - printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%%), end=%lu, error=%lu, max steps=%lu, chunk size=%lu\n", + printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu\n", duration, realloc_counter, wait_counter, @@ -3960,6 +4110,7 @@ void server_run (void) MEM_TRACE_ADDRESS, final_trace_size, final_trace_size_percentage, + trace_size, end, error, max_steps, @@ -4180,16 +4331,7 @@ void server_cleanup (void) } // Cleanup trace - result = munmap((void *)TRACE_ADDR, trace_size); - if (result == -1) - { - printf("ERROR: Failed calling munmap(trace) for size=%lu errno=%d=%s\n", trace_size, errno, strerror(errno)); - } - result = shm_unlink(shmem_output_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_output_name, errno, strerror(errno)); - } + trace_cleanup(); // Cleanup chunk done semaphore if (call_chunk_done) @@ -4370,34 +4512,10 @@ extern void _realloc_trace (void) { realloc_counter++; - // Calculate new trace size - uint64_t new_trace_size = trace_size + DELTA_TRACE_SIZE; - - // Extend the underlying file to the new size - int result = ftruncate(shmem_output_fd, new_trace_size); - if (result != 0) - { - printf("ERROR: realloc_trace() failed calling ftruncate(%s) of new size=%lu errno=%d=%s\n", shmem_output_name, new_trace_size, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Sync - fsync(shmem_output_fd); - - // Remap the memory - void * new_address = mremap((void *)trace_address, trace_size, new_trace_size, 0); - if ((uint64_t)new_address != trace_address) - { - printf("ERROR: realloc_trace() failed calling mremap() from size=%lu to %lu got new_address=%p errno=%d=%s\n", trace_size, new_trace_size, new_address, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + trace_map_next_chunk(); // Update trace global variables - set_trace_size(new_trace_size); + set_trace_size(trace_total_mapped_size); #ifdef DEBUG if (verbose) printf("realloc_trace() realloc counter=%lu trace_address=0x%lx trace_size=%lu=%lx max_address=0x%lx trace_address_threshold=0x%lx chunk_size=%lu\n", realloc_counter, trace_address, trace_size, trace_size, trace_address + trace_size, trace_address_threshold, chunk_size); From 5a1b49a521d42732f3ffbe98232500dcecc6dbeb Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 05:53:19 +0000 Subject: [PATCH 420/782] perf: use zero-copy for EmuTrace mem_reads from shared memory --- common/src/emu_minimal_trace.rs | 5 ++-- emulator-asm/asm-runner/src/asm_mt.rs | 11 ++++++--- emulator-asm/asm-runner/src/asm_mt_runner.rs | 24 +++++++++--------- emulator/src/emu.rs | 26 +++++++++++--------- 4 files changed, 36 insertions(+), 30 deletions(-) diff --git a/common/src/emu_minimal_trace.rs b/common/src/emu_minimal_trace.rs index 19b3c87bc..9d15de021 100644 --- a/common/src/emu_minimal_trace.rs +++ b/common/src/emu_minimal_trace.rs @@ -1,5 +1,6 @@ //! Emulator trace +use std::borrow::Cow; use std::fmt::{Debug, Formatter}; use zisk_core::REGS_IN_MAIN_TOTAL_NUMBER; @@ -37,8 +38,8 @@ pub struct EmuTrace { pub last_c: u64, /// Number of steps executed pub steps: u64, - /// Memory reads - pub mem_reads: Vec, + /// Memory reads (Cow allows zero-copy from shared memory) + pub mem_reads: Cow<'static, [u64]>, /// If the `end` flag is true, the program executed completely. /// This does not mean that the program ended successfully; it could have found an error condition diff --git a/emulator-asm/asm-runner/src/asm_mt.rs b/emulator-asm/asm-runner/src/asm_mt.rs index 23dbd55e2..4caad127d 100644 --- a/emulator-asm/asm-runner/src/asm_mt.rs +++ b/emulator-asm/asm-runner/src/asm_mt.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::fmt::Debug; use zisk_common::EmuTrace; use zisk_common::EmuTraceStart; @@ -45,11 +46,13 @@ impl AsmMTChunk { let chunk = unsafe { std::ptr::read(*mapped_ptr) }; *mapped_ptr = unsafe { mapped_ptr.add(1) }; - // Convert mem_reads into a Vec without copying - let mem_reads_ptr = *mapped_ptr as *mut u64; + // Zero-copy: borrow mem_reads directly from shared memory + // SAFETY: Caller must ensure shared memory outlives EmuTrace usage + let mem_reads_ptr = *mapped_ptr as *const u64; let mem_reads_len = chunk.mem_reads_size as usize; - let mem_reads = - unsafe { std::slice::from_raw_parts(mem_reads_ptr, mem_reads_len).to_vec() }; + let mem_reads: Cow<'static, [u64]> = Cow::Borrowed(unsafe { + std::mem::transmute(std::slice::from_raw_parts(mem_reads_ptr, mem_reads_len)) + }); // Advance the pointer after reading memory reads *mapped_ptr = unsafe { (*mapped_ptr as *mut u64).add(mem_reads_len) as *const AsmMTChunk }; diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 184eaf4f6..9c451c062 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -97,8 +97,6 @@ impl AsmRunnerMT { let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; - let start_time = Instant::now(); - let handle = std::thread::spawn(move || { let asm_services = AsmServices::new(world_rank, local_rank, base_port); @@ -106,13 +104,13 @@ impl AsmRunnerMT { let stats_id = __stats.next_id(); #[cfg(feature = "stats")] __stats.add_stat(parent_stats_id, stats_id, "ASM_MT", 0, ExecutorStatsEvent::Begin); - + let start = Instant::now(); let result = asm_services.send_minimal_trace_request(max_steps, chunk_size); #[cfg(feature = "stats")] __stats.add_stat(parent_stats_id, stats_id, "ASM_MT", 0, ExecutorStatsEvent::End); - result + (result, start.elapsed()) }); let mut chunk_id = ChunkId(0); @@ -120,8 +118,6 @@ impl AsmRunnerMT { // Get the pointer to the data in the shared memory. let mut data_ptr = preloaded.output_shmem.data_ptr() as *const AsmMTChunk; - let mut emu_traces = Vec::new(); - let __stats = _stats.clone(); // Threshold (in bytes) used to detect when the shared memory region size has changed. @@ -132,6 +128,9 @@ impl AsmRunnerMT { preloaded.output_shmem.mapped_ptr().add(threshold_bytes) as *const AsmMTChunk }; + // Pre-allocate reasonable initial capacity to avoid early reallocations + let mut emu_traces: Vec> = Vec::with_capacity(1024); + let exit_code = loop { match sem_chunk_done.timed_wait(SEM_CHUNK_DONE_WAIT_DURATION) { Ok(()) => { @@ -196,15 +195,14 @@ impl AsmRunnerMT { .context("Child process returned error"); } + // Wait for the assembly emulator to complete writing the trace + let (handle, elapsed) = handle.join().map_err(|_| AsmRunError::JoinPanic)?; + let total_steps = emu_traces.iter().map(|x| x.steps).sum::(); - let mhz = (total_steps as f64 / start_time.elapsed().as_secs_f64()) / 1_000_000.0; - info!("··· Assembly execution speed: {}MHz", mhz.round()); + let mhz = (total_steps as f64 / elapsed.as_secs_f64()) / 1_000_000.0; + info!("··· Assembly execution speed: {}MHz ({:?})", mhz.round(), elapsed); - // Wait for the assembly emulator to complete writing the trace - let response = handle - .join() - .map_err(|_| AsmRunError::JoinPanic)? - .map_err(AsmRunError::ServiceError)?; + let response = handle.map_err(AsmRunError::ServiceError)?; assert_eq!(response.result, 0); assert!(response.trace_len > 0); diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 48fd4266c..ab32a9bc8 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::mem; use crate::{ @@ -1585,7 +1586,7 @@ impl<'a> Emu<'a> { } // Reserve enough entries for all the requested steps between callbacks - self.ctx.trace.mem_reads.reserve(self.ctx.callback_steps as usize); + self.ctx.trace.mem_reads.to_mut().reserve(self.ctx.callback_steps as usize); // Init pc to the rom entry address self.ctx.trace.start_state.pc = ROM_ENTRY; @@ -1757,7 +1758,7 @@ impl<'a> Emu<'a> { }, last_c: 0, steps: 0, - mem_reads: Vec::with_capacity(par_options.num_steps), + mem_reads: Cow::Owned(Vec::with_capacity(par_options.num_steps)), end: false, }); } @@ -1811,7 +1812,7 @@ impl<'a> Emu<'a> { }, last_c: 0, steps: 0, - mem_reads: Vec::with_capacity(par_options.num_steps), + mem_reads: Cow::Owned(Vec::with_capacity(par_options.num_steps)), end: false, }); } @@ -1923,7 +1924,7 @@ impl<'a> Emu<'a> { // Swap the emulator trace to avoid memory copies let mut trace = EmuTrace::default(); - trace.mem_reads.reserve(self.ctx.callback_steps as usize); + trace.mem_reads.to_mut().reserve(self.ctx.callback_steps as usize); mem::swap(&mut self.ctx.trace, &mut trace); (callback)(trace); @@ -1948,18 +1949,21 @@ impl<'a> Emu<'a> { pub fn par_step_my_block(&mut self, emu_full_trace_vec: &mut EmuTrace) { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + // Extract the Vec once for all mem_reads operations + let mem_reads = emu_full_trace_vec.mem_reads.to_mut(); + #[cfg(feature = "minimal_trace_index_debug")] println!( "MINIMAL_TRACE par_step_my_block {} {}", self.ctx.inst_ctx.step, - emu_full_trace_vec.mem_reads.len() + mem_reads.len() ); // Build the 'a' register value based on the source specified by the current instruction - self.source_a_mem_reads_generate(instruction, &mut emu_full_trace_vec.mem_reads); + self.source_a_mem_reads_generate(instruction, mem_reads); // Build the 'b' register value based on the source specified by the current instruction - self.source_b_mem_reads_generate(instruction, &mut emu_full_trace_vec.mem_reads); + self.source_b_mem_reads_generate(instruction, mem_reads); // If this is a precompiled, get the required input data to copy it to mem_reads if instruction.input_size > 0 { @@ -1982,16 +1986,16 @@ impl<'a> Emu<'a> { input_data_bytes - instruction.input_size as usize, input_data_bytes, instruction.input_size, - emu_full_trace_vec.mem_reads.len(), - emu_full_trace_vec.mem_reads.len() + (input_data_bytes >> 3) + mem_reads.len(), + mem_reads.len() + (input_data_bytes >> 3) ); } } - emu_full_trace_vec.mem_reads.append(&mut self.ctx.inst_ctx.precompiled.input_data); + mem_reads.append(&mut self.ctx.inst_ctx.precompiled.input_data); } // Store the 'c' register value based on the storage specified by the current instruction - self.store_c_mem_reads_generate(instruction, &mut emu_full_trace_vec.mem_reads); + self.store_c_mem_reads_generate(instruction, mem_reads); // Set SP, if specified by the current instruction // #[cfg(feature = "sp")] From 2a1bc857fe7672a8424eb04abd55d54888919703 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 4 Feb 2026 09:29:04 +0100 Subject: [PATCH 421/782] Leave only hexa values in emu.c precompile traces --- emulator-asm/src/emu.c | 176 ++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index dffd60d31..808293a07 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -582,9 +582,9 @@ extern int _opcode_poseidon2(uint64_t address) #endif #ifdef DEBUG #ifdef ASM_CALL_METRICS - if (emu_verbose) printf("opcode_poseidon2() calling Poseidon2() counter=%lu address=%08lx\n", asm_call_metrics.poseidon2_counter, address); + if (emu_verbose) printf("opcode_poseidon2() calling poseidon2_hash() counter=%lu address=%08lx\n", asm_call_metrics.poseidon2_counter, address); #else - if (emu_verbose) printf("opcode_poseidon2() calling Poseidon2() address=%08lx\n", address); + if (emu_verbose) printf("opcode_poseidon2() calling poseidon2_hash() address=%08lx\n", address); #endif #endif @@ -607,7 +607,7 @@ extern int _opcode_poseidon2(uint64_t address) #endif #ifdef DEBUG - if (emu_verbose) printf("opcode_poseidon2() called Poseidon2()\n"); + if (emu_verbose) printf("opcode_poseidon2() called poseidon2_hash()\n"); #endif #ifdef ASM_CALL_METRICS asm_call_metrics.poseidon2_counter++; @@ -637,9 +637,9 @@ extern int _opcode_arith256(uint64_t * address) #else printf("opcode_arith256() calling Arith256() address=%p\n", address); #endif - printf("a = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", a[3], a[2], a[1], a[0], a[3], a[2], a[1], a[0]); - printf("b = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", b[3], b[2], b[1], b[0], b[3], b[2], b[1], b[0]); - printf("c = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", c[3], c[2], c[1], c[0], c[3], c[2], c[1], c[0]); + printf("a = %lx:%lx:%lx:%lx\n", a[3], a[2], a[1], a[0]); + printf("b = %lx:%lx:%lx:%lx\n", b[3], b[2], b[1], b[0]); + printf("c = %lx:%lx:%lx:%lx\n", c[3], c[2], c[1], c[0]); } #endif @@ -672,8 +672,8 @@ extern int _opcode_arith256(uint64_t * address) if (emu_verbose) printf("opcode_arith256() called Arith256()\n"); if (emu_verbose) { - printf("dl = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", dl[3], dl[2], dl[1], dl[0], dl[3], dl[2], dl[1], dl[0]); - printf("dh = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", dh[3], dh[2], dh[1], dh[0], dh[3], dh[2], dh[1], dh[0]); + printf("dl = %lx:%lx:%lx:%lx\n", dl[3], dl[2], dl[1], dl[0]); + printf("dh = %lx:%lx:%lx:%lx\n", dh[3], dh[2], dh[1], dh[0]); } #endif #ifdef ASM_CALL_METRICS @@ -704,10 +704,10 @@ extern int _opcode_arith256_mod(uint64_t * address) #else printf("opcode_arith256_mod() calling Arith256Mod() address=%p\n", address); #endif - printf("a = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", a[3], a[2], a[1], a[0], a[3], a[2], a[1], a[0]); - printf("b = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", b[3], b[2], b[1], b[0], b[3], b[2], b[1], b[0]); - printf("c = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", c[3], c[2], c[1], c[0], c[3], c[2], c[1], c[0]); - printf("module = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", module[3], module[2], module[1], module[0], module[3], module[2], module[1], module[0]); + printf("a = %lx:%lx:%lx:%lx\n", a[3], a[2], a[1], a[0]); + printf("b = %lx:%lx:%lx:%lx\n", b[3], b[2], b[1], b[0]); + printf("c = %lx:%lx:%lx:%lx\n", c[3], c[2], c[1], c[0]); + printf("module = %lx:%lx:%lx:%lx\n", module[3], module[2], module[1], module[0]); } #endif @@ -738,7 +738,7 @@ extern int _opcode_arith256_mod(uint64_t * address) if (emu_verbose) printf("opcode_arith256_mod() called Arith256Mod()\n"); if (emu_verbose) { - printf("d = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", d[3], d[2], d[1], d[0], d[3], d[2], d[1], d[0]); + printf("d = %lx:%lx:%lx:%lx\n", d[3], d[2], d[1], d[0]); } #endif #ifdef ASM_CALL_METRICS @@ -769,10 +769,10 @@ extern int _opcode_arith384_mod(uint64_t * address) #else printf("opcode_arith384_mod() calling Arith384Mod() address=%p\n", address); #endif - printf("a = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", a[5], a[4], a[3], a[2], a[1], a[0], a[5], a[4], a[3], a[2], a[1], a[0]); - printf("b = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", b[5], b[4], b[3], b[2], b[1], b[0], b[5], b[4], b[3], b[2], b[1], b[0]); - printf("c = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", c[5], c[4], c[3], c[2], c[1], c[0], c[5], c[4], c[3], c[2], c[1], c[0]); - printf("module = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", module[5], module[4], module[3], module[2], module[1], module[0], module[5], module[4], module[3], module[2], module[1], module[0]); + printf("a = %lx:%lx:%lx:%lx:%lx:%lx\n", a[5], a[4], a[3], a[2], a[1], a[0]); + printf("b = %lx:%lx:%lx:%lx:%lx:%lx\n", b[5], b[4], b[3], b[2], b[1], b[0]); + printf("c = %lx:%lx:%lx:%lx:%lx:%lx\n", c[5], c[4], c[3], c[2], c[1], c[0]); + printf("module = %lx:%lx:%lx:%lx:%lx:%lx\n", module[5], module[4], module[3], module[2], module[1], module[0]); } #endif @@ -803,7 +803,7 @@ extern int _opcode_arith384_mod(uint64_t * address) if (emu_verbose) printf("opcode_arith384_mod() called Arith384Mod()\n"); if (emu_verbose) { - printf("d = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", d[5], d[4], d[3], d[2], d[1], d[0], d[5], d[4], d[3], d[2], d[1], d[0]); + printf("d = %lx:%lx:%lx:%lx:%lx:%lx\n", d[5], d[4], d[3], d[2], d[1], d[0]); } #endif #ifdef ASM_CALL_METRICS @@ -830,10 +830,10 @@ extern int _opcode_secp256k1_add(uint64_t * address) #else printf("opcode_secp256k1_add() calling AddPointEcP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); #endif - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); - printf("p2.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[3], p2[2], p2[1], p2[0], p2[3], p2[2], p2[1], p2[0]); - printf("p2.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[7], p2[6], p2[5], p2[4], p2[7], p2[6], p2[5], p2[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); + printf("p2.x = %lx:%lx:%lx:%lx\n", p2[3], p2[2], p2[1], p2[0]); + printf("p2.y = %lx:%lx:%lx:%lx\n", p2[7], p2[6], p2[5], p2[4]); } #endif @@ -868,8 +868,8 @@ extern int _opcode_secp256k1_add(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p3.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p3.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + printf("p3.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p3.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); } #endif #ifdef ASM_CALL_METRICS @@ -896,8 +896,8 @@ extern int _opcode_secp256k1_dbl(uint64_t * address) #else printf("opcode_secp256k1_dbl() calling AddPointEcP() address=%p\n", address); #endif - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); } #endif @@ -932,8 +932,8 @@ extern int _opcode_secp256k1_dbl(uint64_t * address) if (emu_verbose) printf("opcode_secp256k1_dbl() called AddPointEcP()\n"); if (emu_verbose) { - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); } #endif #ifdef ASM_CALL_METRICS @@ -1332,10 +1332,10 @@ extern int _opcode_bn254_curve_add(uint64_t * address) #else printf("_opcode_bn254_curve_add() calling BN254CurveAddP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); #endif - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); - printf("p2.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[3], p2[2], p2[1], p2[0], p2[3], p2[2], p2[1], p2[0]); - printf("p2.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[7], p2[6], p2[5], p2[4], p2[7], p2[6], p2[5], p2[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); + printf("p2.x = %lx:%lx:%lx:%lx\n", p2[3], p2[2], p2[1], p2[0]); + printf("p2.y = %lx:%lx:%lx:%lx\n", p2[7], p2[6], p2[5], p2[4]); } #endif @@ -1369,8 +1369,8 @@ extern int _opcode_bn254_curve_add(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); } #endif #ifdef ASM_CALL_METRICS @@ -1396,8 +1396,8 @@ extern int _opcode_bn254_curve_dbl(uint64_t * address) #else printf("_opcode_bn254_curve_dbl() calling BN254CurveDblP() address=%p p1_address=%p\n", address, p1); #endif - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); } #endif @@ -1430,8 +1430,8 @@ extern int _opcode_bn254_curve_dbl(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); } #endif #ifdef ASM_CALL_METRICS @@ -1458,10 +1458,10 @@ extern int _opcode_bn254_complex_add(uint64_t * address) #else printf("_opcode_bn254_complex_add() calling BN254ComplexAddP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); #endif - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); - printf("p2.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[3], p2[2], p2[1], p2[0], p2[3], p2[2], p2[1], p2[0]); - printf("p2.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[7], p2[6], p2[5], p2[4], p2[7], p2[6], p2[5], p2[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); + printf("p2.x = %lx:%lx:%lx:%lx\n", p2[3], p2[2], p2[1], p2[0]); + printf("p2.y = %lx:%lx:%lx:%lx\n", p2[7], p2[6], p2[5], p2[4]); } #endif @@ -1495,8 +1495,8 @@ extern int _opcode_bn254_complex_add(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); } #endif #ifdef ASM_CALL_METRICS @@ -1523,10 +1523,10 @@ extern int _opcode_bn254_complex_sub(uint64_t * address) #else printf("_opcode_bn254_complex_sub() calling BN254ComplexSubP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); #endif - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); - printf("p2.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[3], p2[2], p2[1], p2[0], p2[3], p2[2], p2[1], p2[0]); - printf("p2.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[7], p2[6], p2[5], p2[4], p2[7], p2[6], p2[5], p2[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); + printf("p2.x = %lx:%lx:%lx:%lx\n", p2[3], p2[2], p2[1], p2[0]); + printf("p2.y = %lx:%lx:%lx:%lx\n", p2[7], p2[6], p2[5], p2[4]); } #endif @@ -1560,8 +1560,8 @@ extern int _opcode_bn254_complex_sub(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); } #endif #ifdef ASM_CALL_METRICS @@ -1588,10 +1588,10 @@ extern int _opcode_bn254_complex_mul(uint64_t * address) #else printf("_opcode_bn254_complex_mul() calling BN254ComplexMulP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); #endif - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); - printf("p2.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[3], p2[2], p2[1], p2[0], p2[3], p2[2], p2[1], p2[0]); - printf("p2.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p2[7], p2[6], p2[5], p2[4], p2[7], p2[6], p2[5], p2[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); + printf("p2.x = %lx:%lx:%lx:%lx\n", p2[3], p2[2], p2[1], p2[0]); + printf("p2.y = %lx:%lx:%lx:%lx\n", p2[7], p2[6], p2[5], p2[4]); } #endif @@ -1625,8 +1625,8 @@ extern int _opcode_bn254_complex_mul(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p1.x = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4], p1[7], p1[6], p1[5], p1[4]); + printf("p1.x = %lx:%lx:%lx:%lx\n", p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx\n", p1[7], p1[6], p1[5], p1[4]); } #endif #ifdef ASM_CALL_METRICS @@ -1657,10 +1657,10 @@ extern int _opcode_bls12_381_curve_add(uint64_t * address) #else printf("_opcode_bls12_381_curve_add() calling BLS12_381CurveAddP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); #endif - printf("p1.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0], p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6], p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); - printf("p2.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[5], p2[4], p2[3], p2[2], p2[1], p2[0], p2[5], p2[4], p2[3], p2[2], p2[1], p2[0]); - printf("p2.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[11], p2[10], p2[9], p2[8], p2[7], p2[6], p2[11], p2[10], p2[9], p2[8], p2[7], p2[6]); + printf("p1.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); + printf("p2.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[5], p2[4], p2[3], p2[2], p2[1], p2[0]); + printf("p2.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[11], p2[10], p2[9], p2[8], p2[7], p2[6]); } #endif @@ -1694,8 +1694,8 @@ extern int _opcode_bls12_381_curve_add(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p1.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0], p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6], p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); + printf("p1.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); } #endif #ifdef ASM_CALL_METRICS @@ -1721,8 +1721,8 @@ extern int _opcode_bls12_381_curve_dbl(uint64_t * address) #else printf("_opcode_bls12_381_curve_dbl() calling BLS12_381CurveDblP() address=%p p1_address=%p\n", address, p1); #endif - printf("p1.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0], p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6], p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); + printf("p1.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); } #endif @@ -1755,8 +1755,8 @@ extern int _opcode_bls12_381_curve_dbl(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p1.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0], p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6], p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); + printf("p1.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); } #endif #ifdef ASM_CALL_METRICS @@ -1783,10 +1783,10 @@ extern int _opcode_bls12_381_complex_add(uint64_t * address) #else printf("_opcode_bls12_381_complex_add() calling BLS12_381ComplexAddP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); #endif - printf("p1.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0], p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6], p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); - printf("p2.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[5], p2[4], p2[3], p2[2], p2[1], p2[0], p2[5], p2[4], p2[3], p2[2], p2[1], p2[0]); - printf("p2.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[11], p2[10], p2[9], p2[8], p2[7], p2[6], p2[11], p2[10], p2[9], p2[8], p2[7], p2[6]); + printf("p1.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); + printf("p2.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[5], p2[4], p2[3], p2[2], p2[1], p2[0]); + printf("p2.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[11], p2[10], p2[9], p2[8], p2[7], p2[6]); } #endif @@ -1820,8 +1820,8 @@ extern int _opcode_bls12_381_complex_add(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p1.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0], p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6], p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); + printf("p1.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); } #endif #ifdef ASM_CALL_METRICS @@ -1848,10 +1848,10 @@ extern int _opcode_bls12_381_complex_sub(uint64_t * address) #else printf("_opcode_bls12_381_complex_sub() calling BLS12_381ComplexSubP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); #endif - printf("p1.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0], p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6], p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); - printf("p2.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[5], p2[4], p2[3], p2[2], p2[1], p2[0], p2[5], p2[4], p2[3], p2[2], p2[1], p2[0]); - printf("p2.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[11], p2[10], p2[9], p2[8], p2[7], p2[6], p2[11], p2[10], p2[9], p2[8], p2[7], p2[6]); + printf("p1.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); + printf("p2.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[5], p2[4], p2[3], p2[2], p2[1], p2[0]); + printf("p2.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[11], p2[10], p2[9], p2[8], p2[7], p2[6]); } #endif @@ -1885,8 +1885,8 @@ extern int _opcode_bls12_381_complex_sub(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p1.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0], p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6], p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); + printf("p1.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); } #endif #ifdef ASM_CALL_METRICS @@ -1913,10 +1913,10 @@ extern int _opcode_bls12_381_complex_mul(uint64_t * address) #else printf("_opcode_bls12_381_complex_mul() calling BLS12_381ComplexMulP() address=%p p1_address=%p p2_address=%p\n", address, p1, p2); #endif - printf("p1.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0], p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6], p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); - printf("p2.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[5], p2[4], p2[3], p2[2], p2[1], p2[0], p2[5], p2[4], p2[3], p2[2], p2[1], p2[0]); - printf("p2.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[11], p2[10], p2[9], p2[8], p2[7], p2[6], p2[11], p2[10], p2[9], p2[8], p2[7], p2[6]); + printf("p1.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); + printf("p2.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[5], p2[4], p2[3], p2[2], p2[1], p2[0]); + printf("p2.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p2[11], p2[10], p2[9], p2[8], p2[7], p2[6]); } #endif @@ -1950,8 +1950,8 @@ extern int _opcode_bls12_381_complex_mul(uint64_t * address) #ifdef DEBUG if (emu_verbose) { - printf("p1.x = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0], p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); - printf("p1.y = %lu:%lu:%lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6], p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); + printf("p1.x = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[5], p1[4], p1[3], p1[2], p1[1], p1[0]); + printf("p1.y = %lx:%lx:%lx:%lx:%lx:%lx\n", p1[11], p1[10], p1[9], p1[8], p1[7], p1[6]); } #endif #ifdef ASM_CALL_METRICS @@ -1982,9 +1982,9 @@ extern uint64_t _opcode_add256(uint64_t * address) #else printf("opcode_add256() calling Add256() address=%p\n", address); #endif - printf("a = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", a[3], a[2], a[1], a[0], a[3], a[2], a[1], a[0]); - printf("b = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", b[3], b[2], b[1], b[0], b[3], b[2], b[1], b[0]); - printf("c = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", c[3], c[2], c[1], c[0], c[3], c[2], c[1], c[0]); + printf("a = %lx:%lx:%lx:%lx\n", a[3], a[2], a[1], a[0]); + printf("b = %lx:%lx:%lx:%lx\n", b[3], b[2], b[1], b[0]); + printf("c = %lx:%lx:%lx:%lx\n", c[3], c[2], c[1], c[0]); } #endif @@ -2022,7 +2022,7 @@ extern uint64_t _opcode_add256(uint64_t * address) if (emu_verbose) { printf("cout = %lu\n", cout); - printf("c = %lu:%lu:%lu:%lu = %lx:%lx:%lx:%lx\n", c[3], c[2], c[1], c[0], c[3], c[2], c[1], c[0]); + printf("c = %lx:%lx:%lx:%lx\n", c[3], c[2], c[1], c[0]); } #endif #ifdef ASM_CALL_METRICS From a05bead0bd6e8cf18f33f31d3cbbff8bf88eb758 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:53:56 +0000 Subject: [PATCH 422/782] set MAX_PRECOMPILE_SIZE to 4MB --- core/src/zisk_rom_2_asm.rs | 2 +- emulator-asm/asm-runner/src/hints_shmem.rs | 2 +- emulator-asm/src/main.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index d33888d73..e217b9832 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -93,7 +93,7 @@ const F_MOPS_ALIGNED_WRITE: u64 = 0x0000_000D_0000_0000; const F_MOPS_BLOCK_LENGTH_SHIFT: u64 = 36; // const PRECOMPILE_BUFFER_SIZE_IN_BYTES: u64 = 0x100000; // 1MB -const PRECOMPILE_BUFFER_SIZE_IN_BYTES: u64 = 0x10000000; // 256MB +const PRECOMPILE_BUFFER_SIZE_IN_BYTES: u64 = 0x400000; // 4MB const PRECOMPILE_BUFFER_SIZE_IN_U64: u64 = PRECOMPILE_BUFFER_SIZE_IN_BYTES / 8; const PRECOMPILE_BUFFER_SIZE_U64_MASK: u64 = PRECOMPILE_BUFFER_SIZE_IN_U64 - 1; diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index 781f861a0..f94c77337 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -61,7 +61,7 @@ unsafe impl Sync for HintsShmem {} impl HintsShmem { const CONTROL_PRECOMPILE_SIZE: u64 = 0x1000; // 4KB - const MAX_PRECOMPILE_SIZE: u64 = 0x10000000; // 256MB + const MAX_PRECOMPILE_SIZE: u64 = 0x400000; // 4MB const BUFFER_CAPACITY_U64: u64 = Self::MAX_PRECOMPILE_SIZE >> 3; // Capacity in u64 elements /// Create a new HintsShmem with the given shared memory names and unlock option. diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index be72fd89e..ad2bf8583 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -336,7 +336,7 @@ int process_id = 0; uint64_t input_size = 0; -#define MAX_PRECOMPILE_SIZE (uint64_t)0x10000000 // 256MB +#define MAX_PRECOMPILE_SIZE (uint64_t)0x400000 // 4MB //#define MAX_PRECOMPILE_SIZE (uint64_t)0x100000 // 1MB // Precompile results shared memory From a73b1634525ee87ac1704bd75c6f64b554d6f380 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:57:39 +0000 Subject: [PATCH 423/782] feat: avoid remapping working --- emulator-asm/asm-runner/src/asm_mo_runner.rs | 64 ++++++++++++++------ emulator-asm/asm-runner/src/asm_mt_runner.rs | 64 ++++++++++++++------ emulator-asm/asm-runner/src/asm_rh_runner.rs | 1 + emulator-asm/asm-runner/src/lib.rs | 8 +++ emulator-asm/asm-runner/src/shmem_utils.rs | 15 ++++- precompiles/hints/src/hints_processor.rs | 2 +- 6 files changed, 115 insertions(+), 39 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index c15ab58a0..538d7ddc3 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -8,7 +8,13 @@ use std::sync::atomic::{fence, Ordering}; use tracing::error; use crate::SEM_CHUNK_DONE_WAIT_DURATION; -use crate::{AsmMOChunk, AsmMOHeader, AsmRunError, AsmService, AsmServices, AsmSharedMemory}; +use crate::TRACE_DELTA_SIZE; +use crate::TRACE_INITIAL_SIZE; +use crate::TRACE_MAX_SIZE; +use crate::{ + AsmMOChunk, AsmMOHeader, AsmMultiSharedMemory, AsmRunError, AsmService, AsmServices, + AsmSharedMemory, +}; use mem_planner_cpp::MemPlanner; use anyhow::{Context, Result}; @@ -20,7 +26,7 @@ use zisk_common::ExecutorStatsEvent; use mem_common::save_plans; pub struct PreloadedMO { - pub output_shmem: AsmSharedMemory, + pub output_shmem: AsmMultiSharedMemory, mem_planner: Option, handle_mo: Option>, } @@ -41,10 +47,16 @@ impl PreloadedMO { base_port.unwrap(), AsmService::MO, local_rank, + None, ); - let output_shared_memory = - AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; + let output_shared_memory = AsmMultiSharedMemory::::open_and_map( + &output_name, + TRACE_INITIAL_SIZE, + TRACE_DELTA_SIZE, + TRACE_MAX_SIZE, + unlock_mapped_memory, + )?; Ok(Self { output_shmem: output_shared_memory, @@ -150,12 +162,26 @@ impl AsmRunnerMO { ExecutorStatsEvent::Begin, ); - // Threshold (in bytes) used to detect when the shared memory region size has changed. - // Computed to optimize the common case where minor size fluctuations are ignored. - // It is based on the worst-case scenario of memory usage. - let threshold_bytes = (chunk_size as usize * 200) + (44 * 8) + 32; + // Threshold (in bytes) used to detect when we need to check for new shared memory files. + // Must match MAX_CHUNK_TRACE_SIZE from main.c to ensure we check before the producer + // reallocates. Constants from main.c: + // MAX_MTRACE_REGS_ACCESS_SIZE = (2 + 2 + 3) * 8 = 56 + // MAX_BYTES_DIRECT_MTRACE = 256 + // MAX_BYTES_MTRACE_STEP = 256 + 56 = 312 + // MAX_TRACE_CHUNK_INFO = (44 * 8) + 32 = 384 + // MAX_CHUNK_TRACE_SIZE = (chunk_size * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO + const MAX_MTRACE_REGS_ACCESS_SIZE: usize = (2 + 2 + 3) * 8; + const MAX_BYTES_DIRECT_MTRACE: usize = 256; + const MAX_BYTES_MTRACE_STEP: usize = MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE; + const MAX_TRACE_CHUNK_INFO: usize = (44 * 8) + 32; + + let threshold_bytes = (chunk_size as usize * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO; let mut threshold = unsafe { - preloaded.output_shmem.mapped_ptr().add(threshold_bytes) as *const AsmMOChunk + preloaded + .output_shmem + .mapped_ptr() + .add(preloaded.output_shmem.total_mapped_size() - threshold_bytes) + as *const AsmMOChunk }; let exit_code = loop { @@ -164,17 +190,19 @@ impl AsmRunnerMO { // Synchronize with memory changes from the C++ side fence(Ordering::Acquire); - // Check if we need to remap the shared memory + // Check if we need to map additional shared memory files. if data_ptr >= threshold - && preloaded - .output_shmem - .check_size_changed(&mut data_ptr) - .context("Failed to check and remap shared memory for MO trace")? + && preloaded.output_shmem.check_size_changed().context( + "Failed to check and map new shared memory files for MO trace", + )? { - threshold = unsafe { - preloaded.output_shmem.mapped_ptr().add(threshold_bytes) - as *const AsmMOChunk - }; + // Update threshold based on new total mapped size + threshold = + unsafe { + preloaded.output_shmem.mapped_ptr().add( + preloaded.output_shmem.total_mapped_size() - threshold_bytes, + ) as *const AsmMOChunk + }; } let chunk = unsafe { std::ptr::read(data_ptr) }; diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 9c451c062..6f6225f80 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -10,8 +10,9 @@ use std::time::Instant; use tracing::{error, info}; use crate::{ - AsmMTChunk, AsmMTHeader, AsmRunError, AsmService, AsmServices, AsmSharedMemory, - SEM_CHUNK_DONE_WAIT_DURATION, + AsmMTChunk, AsmMTHeader, AsmMultiSharedMemory, AsmRunError, AsmService, AsmServices, + AsmSharedMemory, SEM_CHUNK_DONE_WAIT_DURATION, TRACE_DELTA_SIZE, TRACE_INITIAL_SIZE, + TRACE_MAX_SIZE, }; use anyhow::{Context, Result}; @@ -26,7 +27,7 @@ pub enum MinimalTraces { } pub struct PreloadedMT { - pub output_shmem: AsmSharedMemory, + pub output_shmem: AsmMultiSharedMemory, } impl PreloadedMT { @@ -45,10 +46,16 @@ impl PreloadedMT { base_port.unwrap(), AsmService::MT, local_rank, + None, ); - let output_shared_memory = - AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; + let output_shared_memory = AsmMultiSharedMemory::::open_and_map( + &output_name, + TRACE_INITIAL_SIZE, + TRACE_DELTA_SIZE, + TRACE_MAX_SIZE, + unlock_mapped_memory, + )?; Ok(Self { output_shmem: output_shared_memory }) } @@ -120,12 +127,29 @@ impl AsmRunnerMT { let __stats = _stats.clone(); - // Threshold (in bytes) used to detect when the shared memory region size has changed. - // Computed to optimize the common case where minor size fluctuations are ignored. - // It is based on the worst-case scenario of memory usage. - let threshold_bytes = (chunk_size as usize * 200) + (44 * 8) + 32; + // Calculate threshold for detecting when to map additional shared memory files. + // CRITICAL: These constants must match main.c to ensure we check for new files BEFORE + // the C++ producer needs to allocate beyond current mapped region. Mismatch will cause + // the producer to map new files while we still hold Cow::Borrowed references to old + // mappings, creating dangling pointers. + // + // Constants from main.c: + // MAX_MTRACE_REGS_ACCESS_SIZE = (2 + 2 + 3) * 8 // Register access overhead per step + // MAX_BYTES_DIRECT_MTRACE = 256 // Direct memory trace data per step + // MAX_BYTES_MTRACE_STEP = 256 + 56 = 312 // Total per-step overhead + // MAX_TRACE_CHUNK_INFO = (44 * 8) + 32 // Chunk metadata size + const MAX_MTRACE_REGS_ACCESS_SIZE: usize = (2 + 2 + 3) * 8; // 56 bytes + const MAX_BYTES_DIRECT_MTRACE: usize = 256; + const MAX_BYTES_MTRACE_STEP: usize = MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE; + const MAX_TRACE_CHUNK_INFO: usize = (44 * 8) + 32; // 384 bytes + + let threshold_bytes = (chunk_size as usize * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO; let mut threshold = unsafe { - preloaded.output_shmem.mapped_ptr().add(threshold_bytes) as *const AsmMTChunk + preloaded + .output_shmem + .mapped_ptr() + .add(preloaded.output_shmem.total_mapped_size() - threshold_bytes) + as *const AsmMTChunk }; // Pre-allocate reasonable initial capacity to avoid early reallocations @@ -149,17 +173,19 @@ impl AsmRunnerMT { // Synchronize with memory changes from the C++ side fence(Ordering::Acquire); - // Check if we need to remap the shared memory + // Check if we need to map additional shared memory files. if data_ptr >= threshold - && preloaded - .output_shmem - .check_size_changed(&mut data_ptr) - .context("Failed to check and remap shared memory for MO trace")? + && preloaded.output_shmem.check_size_changed().context( + "Failed to check and map new shared memory files for MT trace", + )? { - threshold = unsafe { - preloaded.output_shmem.mapped_ptr().add(threshold_bytes) - as *const AsmMTChunk - }; + // Update threshold based on new total mapped size + threshold = + unsafe { + preloaded.output_shmem.mapped_ptr().add( + preloaded.output_shmem.total_mapped_size() - threshold_bytes, + ) as *const AsmMTChunk + }; } let emu_trace = Arc::new(AsmMTChunk::to_emu_trace(&mut data_ptr)); diff --git a/emulator-asm/asm-runner/src/asm_rh_runner.rs b/emulator-asm/asm-runner/src/asm_rh_runner.rs index e04ba9f50..5ec7301ec 100644 --- a/emulator-asm/asm-runner/src/asm_rh_runner.rs +++ b/emulator-asm/asm-runner/src/asm_rh_runner.rs @@ -29,6 +29,7 @@ impl PreloadedRH { base_port.unwrap(), AsmService::RH, local_rank, + Some(0), ); let output_shared_memory = diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 883d2a61e..9ce72edd2 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -22,6 +22,8 @@ mod hints_file; mod hints_shmem; #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] mod hints_shmem_stub; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +mod multi_shmem; mod shmem_reader; mod shmem_utils; mod shmem_writer; @@ -48,10 +50,16 @@ pub use hints_file::*; pub use hints_shmem::*; #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] pub use hints_shmem_stub::*; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +pub use multi_shmem::*; pub use shmem_reader::*; pub use shmem_utils::*; pub use shmem_writer::*; +pub(crate) const TRACE_INITIAL_SIZE: usize = 0x180000000; // 6GB +pub(crate) const TRACE_DELTA_SIZE: usize = 0x080000000; // 2GB +pub(crate) const TRACE_MAX_SIZE: usize = 0x1000000000; // 64GB + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] const SEM_CHUNK_DONE_WAIT_DURATION: std::time::Duration = std::time::Duration::from_secs(10); diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 8b9f49ba0..e28b62800 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -280,7 +280,20 @@ impl AsmSharedMemory { format!("{}_input", AsmServices::shmem_prefix(port, local_rank)) } - pub fn shmem_output_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + pub fn shmem_output_name( + port: u16, + asm_service: AsmService, + local_rank: i32, + suffix: Option, + ) -> String { + if let Some(suffix) = suffix { + return format!( + "{}_{}_output_{}", + AsmServices::shmem_prefix(port, local_rank), + asm_service.as_str(), + suffix + ); + } format!("{}_{}_output", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 792fc2d16..0024288ad 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -348,7 +348,7 @@ impl HintsProcessor { (rate, "Hz") }; - info!( + debug!( "Processed {} hints in {:.0?} ({}{})", num_hints, elapsed, From 0474f6038d9d206617cb8bb145f3ac8bafe00c9a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:00:05 +0000 Subject: [PATCH 424/782] feat: avoid remapping working --- emulator-asm/asm-runner/src/multi_shmem.rs | 330 +++++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 emulator-asm/asm-runner/src/multi_shmem.rs diff --git a/emulator-asm/asm-runner/src/multi_shmem.rs b/emulator-asm/asm-runner/src/multi_shmem.rs new file mode 100644 index 000000000..627223c83 --- /dev/null +++ b/emulator-asm/asm-runner/src/multi_shmem.rs @@ -0,0 +1,330 @@ +use crate::AsmShmemHeader; +use libc::{ + c_uint, close, mmap, munmap, shm_open, shm_unlink, MAP_FAILED, MAP_SHARED, PROT_READ, S_IRUSR, + S_IWUSR, +}; +use std::{ + ffi::CString, + io, + os::raw::c_void, + ptr, + sync::atomic::{fence, Ordering}, +}; +use tracing::debug; + +use anyhow::anyhow; +use anyhow::Result; + +/// Represents a single mapped shared memory file within the multi-file structure. +struct MappedFile { + fd: i32, + #[allow(dead_code)] // May be useful for debugging/validation + size: usize, +} + +/// A shared memory manager that supports multiple contiguous shared memory files. +/// +/// This struct reserves a large virtual address range upfront and maps multiple +/// shared memory files (`_0`, `_1`, etc.) into contiguous portions of that range. +/// +/// File layout: +/// - `{base_name}_0`: Initial file with size `initial_size`, contains the header +/// - `{base_name}_1`, `_2`, ...: Incremental files with size `incremental_size` +pub struct AsmMultiSharedMemory { + base_name: String, + reserved_ptr: *mut c_void, + reserved_size: usize, + initial_size: usize, + incremental_size: usize, + mapped_files: Vec, + total_mapped_size: usize, + unlock_mapped_memory: bool, + _phantom: std::marker::PhantomData, +} + +unsafe impl Send for AsmMultiSharedMemory {} +unsafe impl Sync for AsmMultiSharedMemory {} + +impl Drop for AsmMultiSharedMemory { + fn drop(&mut self) { + // Close all file descriptors + for mapped_file in &self.mapped_files { + unsafe { close(mapped_file.fd) }; + } + + // Unmap the entire reserved region (this handles all the MAP_FIXED mappings too) + if !self.reserved_ptr.is_null() && self.reserved_size > 0 { + unsafe { + if munmap(self.reserved_ptr, self.reserved_size) != 0 { + tracing::error!( + "munmap failed for multi-shmem '{}': {:?}", + self.base_name, + io::Error::last_os_error() + ); + } + } + } + } +} + +impl AsmMultiSharedMemory { + /// Opens and maps the initial shared memory file, reserving address space for growth. + /// + /// # Arguments + /// * `base_name` - Base name for shared memory files (files will be `{base_name}_0`, `_1`, etc.) + /// * `initial_size` - Size of the first file (`_0`) + /// * `incremental_size` - Size of subsequent files (`_1`, `_2`, ...) + /// * `max_size` - Total virtual address space to reserve + /// * `unlock_mapped_memory` - If true, don't use MAP_LOCKED + pub fn open_and_map( + base_name: &str, + initial_size: usize, + incremental_size: usize, + max_size: usize, + unlock_mapped_memory: bool, + ) -> Result { + if base_name.is_empty() { + return Err(anyhow!("Shared memory base name cannot be empty")); + } + + if max_size < initial_size { + return Err(anyhow!( + "max_size ({}) must be >= initial_size ({})", + max_size, + initial_size + )); + } + + if incremental_size == 0 { + return Err(anyhow!("incremental_size must be > 0")); + } + + // Reserve the entire address range with an anonymous mapping + // MAP_NORESERVE prevents reserving swap space for the entire range + let reserved_ptr = unsafe { + mmap( + ptr::null_mut(), + max_size, + libc::PROT_NONE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_NORESERVE, + -1, + 0, + ) + }; + + if reserved_ptr == MAP_FAILED { + let err = io::Error::last_os_error(); + return Err(anyhow!( + "Failed to reserve {} bytes of address space for '{}': {}", + max_size, + base_name, + err + )); + } + + debug!("Reserved {} bytes at {:p} for multi-shmem '{}'", max_size, reserved_ptr, base_name); + + let mut this = Self { + base_name: base_name.to_string(), + reserved_ptr, + reserved_size: max_size, + initial_size, + incremental_size, + mapped_files: Vec::with_capacity(8), + total_mapped_size: 0, + unlock_mapped_memory, + _phantom: std::marker::PhantomData, + }; + + // Map the initial file (_0) + if let Err(e) = this.map_file(0) { + unsafe { munmap(reserved_ptr, max_size) }; + return Err(e); + } + + this.total_mapped_size = initial_size; + + Ok(this) + } + + /// Checks if the producer has allocated more space and maps any new files. + /// + /// This reads `allocated_size` from the header (always in file `_0`) and maps + /// any new files that have been created by the producer. + /// + /// This does NOT move existing mappings, so pointers and slices to already-mapped data remain valid. + pub fn check_size_changed(&mut self) -> Result { + let allocated_size = self.map_header().allocated_size() as usize; + + if allocated_size <= self.total_mapped_size { + return Ok(false); + } + + // Calculate how many files should exist + let files_needed = if allocated_size <= self.initial_size { + 1 + } else { + 1 + ((allocated_size - self.initial_size + self.incremental_size - 1) + / self.incremental_size) + }; + + let current_files = self.mapped_files.len(); + + if files_needed <= current_files { + // Size increased but within current file - just update total + self.total_mapped_size = allocated_size; + return Ok(true); + } + + debug!( + "Multi-shmem '{}': allocated_size={}, need {} files, have {}", + self.base_name, allocated_size, files_needed, current_files + ); + + // Map all new files + for file_idx in current_files..files_needed { + self.map_file(file_idx)?; + } + + self.total_mapped_size = allocated_size; + + fence(Ordering::Acquire); + + Ok(true) + } + + /// Maps a specific file index into the reserved address space. + fn map_file(&mut self, file_idx: usize) -> Result<()> { + let file_name = format!("{}_{}", self.base_name, file_idx); + + unsafe { + let c_name = CString::new(file_name.clone()) + .map_err(|_| anyhow!("Shared memory name contains null byte"))?; + + let fd = + shm_open(c_name.as_ptr(), libc::O_RDONLY, S_IRUSR as c_uint | S_IWUSR as c_uint); + if fd == -1 { + let err = io::Error::last_os_error(); + return Err(anyhow!("shm_open('{}') failed: {}", file_name, err)); + } + + // Unlink to ensure cleanup + if shm_unlink(c_name.as_ptr()) != 0 { + let err = io::Error::last_os_error(); + close(fd); + return Err(anyhow!("shm_unlink('{}') failed: {}", file_name, err)); + } + + // For _0, validate that the header has a non-zero allocated size + if file_idx == 0 { + let temp_map = mmap(ptr::null_mut(), size_of::(), PROT_READ, MAP_SHARED, fd, 0); + if temp_map == MAP_FAILED { + let err = io::Error::last_os_error(); + close(fd); + return Err(anyhow!("mmap failed for header of '{}': {}", file_name, err)); + } + + let header = (temp_map as *const H).read(); + let allocated_size = header.allocated_size(); + munmap(temp_map, size_of::()); + + if allocated_size == 0 { + close(fd); + return Err(anyhow!("Shared memory '{}' has zero allocated size", file_name)); + } + } + + // Calculate the offset where this file should be mapped + let offset = if file_idx == 0 { + 0 + } else { + self.initial_size + (file_idx - 1) * self.incremental_size + }; + + let file_size = if file_idx == 0 { self.initial_size } else { self.incremental_size }; + + let target_addr = self.reserved_ptr.add(offset); + + let mut flags = MAP_SHARED | libc::MAP_FIXED; + if !self.unlock_mapped_memory { + flags |= libc::MAP_LOCKED; + } + + let mapped_ptr = mmap(target_addr, file_size, PROT_READ, flags, fd, 0); + if mapped_ptr == MAP_FAILED { + let err = io::Error::last_os_error(); + close(fd); + return Err(anyhow!( + "mmap(MAP_FIXED) failed for '{}': {} ({} bytes at {:p})", + file_name, + err, + file_size, + target_addr + )); + } + + debug!( + "Mapped '{}' ({} bytes) at {:p} (offset {})", + file_name, file_size, mapped_ptr, offset + ); + + self.mapped_files.push(MappedFile { fd, size: file_size }); + } + + Ok(()) + } + + /// Reads the header from the shared memory (always from file `_0`). + pub fn map_header(&self) -> H { + if self.mapped_files.is_empty() { + panic!("Multi-shmem '{}' has no mapped files, cannot read header", self.base_name); + } + + unsafe { (self.reserved_ptr as *const H).read() } + } + + /// Returns the base pointer of the mapped region. + pub fn mapped_ptr(&self) -> *mut c_void { + self.reserved_ptr + } + + /// Returns a pointer to the data area (after the header). + pub fn data_ptr(&self) -> *mut c_void { + unsafe { self.reserved_ptr.add(size_of::()) } + } + + /// Returns the total currently mapped size. + pub fn total_mapped_size(&self) -> usize { + self.total_mapped_size + } + + /// Returns the number of currently mapped files. + pub fn num_mapped_files(&self) -> usize { + self.mapped_files.len() + } + + /// Releases incremental shared memory files for a new execution. + /// + /// This closes file descriptors for incremental files (`_1`, `_2`, ...) while + /// keeping `_0` mapped. The reserved address space is preserved. + /// + /// Call this before starting a new execution when reusing the same instance + /// in a distributed context where `_0` remains valid across executions. + pub fn release_incremental(&mut self) { + let files_to_close = self.mapped_files.len().saturating_sub(1); + + // Close file descriptors for incremental files (_1, _2, ...), keep _0 + while self.mapped_files.len() > 1 { + let mapped_file = self.mapped_files.pop().unwrap(); + unsafe { close(mapped_file.fd) }; + } + + // Reset state to initial + self.total_mapped_size = self.initial_size; + + debug!( + "Reset multi-shmem '{}': kept _0, closed {} incremental files, total_mapped_size={}", + self.base_name, files_to_close, self.total_mapped_size + ); + } +} From 88112a8745fa17f7509c1a3308bf433ed55e8e57 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:53:34 +0000 Subject: [PATCH 425/782] refactor: removed MinimalTraces due to a simplification --- Cargo.lock | 1 - emulator-asm/asm-runner/src/asm_mt_runner.rs | 6 ---- .../asm-runner/src/asm_mt_runner_stub.rs | 9 ------ executor/src/emu_asm.rs | 19 +++++------- executor/src/emu_asm_stub.rs | 6 ++-- executor/src/emu_rust.rs | 28 ++++++------------ executor/src/executor.rs | 29 +++++-------------- executor/src/lib.rs | 8 ++--- state-machines/main/Cargo.toml | 1 - state-machines/main/src/main_planner.rs | 15 +++------- 10 files changed, 35 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8db69f881..a48a23eb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4396,7 +4396,6 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "asm-runner", "fields", "mem-common", "num-bigint", diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 6f6225f80..e075bdf32 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -20,12 +20,6 @@ use anyhow::{Context, Result}; #[cfg(feature = "stats")] use zisk_common::ExecutorStatsEvent; -pub enum MinimalTraces { - None, - EmuTrace(Vec), - AsmEmuTrace(AsmRunnerMT), -} - pub struct PreloadedMT { pub output_shmem: AsmMultiSharedMemory, } diff --git a/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs b/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs index 9b0329b62..ac81b94c6 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs @@ -10,15 +10,6 @@ pub trait Task: Send + Sync + 'static { fn execute(self) -> Self::Output; } -pub type TaskFactory<'a, T> = Box) -> T + Send + Sync + 'a>; - -#[derive(Debug)] -pub enum MinimalTraces { - None, - EmuTrace(Vec), - AsmEmuTrace(AsmRunnerMT), -} - pub struct PreloadedMT {} // This struct is used to run the assembly code in a separate process and generate minimal traces. diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index a3fed84cb..72fd2ced2 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -9,7 +9,7 @@ use crate::{ }; use asm_runner::{ write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmService, AsmServices, - AsmSharedMemory, MinimalTraces, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, + AsmSharedMemory, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, }; use data_bus::DataBusTrait; use fields::PrimeField64; @@ -104,7 +104,7 @@ impl EmulatorAsm { /// /// # Returns /// A tuple containing: - /// * `MinimalTraces` - The computed minimal traces. + /// * `Vec` - The computed minimal traces. /// * `DeviceMetricsList` - Flat device metrics collected during execution. /// * `NestedDeviceMetricsList` - Hierarchical device metrics collected during execution. /// * `Option>` - Optional join handle for the memory-only ASM runner. @@ -118,7 +118,7 @@ impl EmulatorAsm { stats: &ExecutorStatsHandle, _caller_stats_id: u64, ) -> ( - MinimalTraces, + Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, @@ -196,11 +196,7 @@ impl EmulatorAsm { let (min_traces, main_count, secn_count) = self.run_mt_assembly(sm_bundle, stats); // Store execute steps - let steps = if let MinimalTraces::AsmEmuTrace(asm_min_traces) = &min_traces { - asm_min_traces.vec_chunks.iter().map(|trace| trace.steps).sum::() - } else { - panic!("Expected AsmEmuTrace, got something else"); - }; + let steps = min_traces.iter().map(|trace| trace.steps).sum::(); let execution_result = ZiskExecutionResult::new(steps); @@ -248,7 +244,7 @@ impl EmulatorAsm { &self, sm_bundle: &StaticSMBundle, stats: &ExecutorStatsHandle, - ) -> (MinimalTraces, DeviceMetricsList, NestedDeviceMetricsList) { + ) -> (Vec, DeviceMetricsList, NestedDeviceMetricsList) { #[cfg(feature = "stats")] let parent_stats_id = stats.next_id(); #[cfg(feature = "stats")] @@ -315,7 +311,6 @@ impl EmulatorAsm { .into_iter() .map(|arc| Arc::try_unwrap(arc).expect("Arc should have single owner after scope")) .collect(); - let asm_runner_mt = AsmRunnerMT::new(emu_traces); let mut data_buses = results_mu.into_inner().unwrap(); @@ -344,7 +339,7 @@ impl EmulatorAsm { #[cfg(feature = "stats")] stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::End); - (MinimalTraces::AsmEmuTrace(asm_runner_mt), main_count, secn_count) + (emu_traces, main_count, secn_count) } } @@ -357,7 +352,7 @@ impl crate::Emulator for EmulatorAsm { stats: &ExecutorStatsHandle, caller_stats_id: u64, ) -> ( - MinimalTraces, + Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, diff --git a/executor/src/emu_asm_stub.rs b/executor/src/emu_asm_stub.rs index 1f1fe8109..d52b83ec0 100644 --- a/executor/src/emu_asm_stub.rs +++ b/executor/src/emu_asm_stub.rs @@ -4,12 +4,12 @@ use std::{ }; use crate::{DeviceMetricsList, NestedDeviceMetricsList, StaticSMBundle}; -use asm_runner::{AsmRunnerMO, MinimalTraces}; +use asm_runner::AsmRunnerMO; use fields::PrimeField64; use proofman_common::ProofCtx; use sm_rom::RomSM; -use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, ZiskExecutionResult}; +use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, ZiskExecutionResult}; use zisk_core::ZiskRom; pub struct EmulatorAsm {} @@ -37,7 +37,7 @@ impl EmulatorAsm { _stats: &ExecutorStatsHandle, _caller_stats_id: u64, ) -> ( - MinimalTraces, + Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index 50115a253..ec4cdd73d 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -4,7 +4,7 @@ use std::{ thread::JoinHandle, }; -use asm_runner::{AsmRunnerMO, MinimalTraces}; +use asm_runner::AsmRunnerMO; use data_bus::DataBusTrait; use fields::PrimeField64; use proofman_common::ProofCtx; @@ -12,7 +12,7 @@ use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rayon::prelude::*; use zisk_common::{ io::{ZiskIO, ZiskStdin}, - ChunkId, ExecutorStatsHandle, ZiskExecutionResult, + ChunkId, EmuTrace, ExecutorStatsHandle, ZiskExecutionResult, }; use zisk_core::ZiskRom; use ziskemu::{EmuOptions, ZiskEmulator}; @@ -48,7 +48,7 @@ impl EmulatorRust { /// /// # Returns /// A tuple containing: - /// * `MinimalTraces` - The minimal traces produced by the emulator. + /// * `Vec` - The minimal traces produced by the emulator. /// * `DeviceMetricsList` - Metrics for primary devices. /// * `NestedDeviceMetricsList` - Metrics for secondary/nested devices. /// * `None`. @@ -61,7 +61,7 @@ impl EmulatorRust { _stats: &ExecutorStatsHandle, _caller_stats_id: u64, ) -> ( - MinimalTraces, + Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, @@ -70,11 +70,7 @@ impl EmulatorRust { let min_traces = self.run_emulator(Self::NUM_THREADS, &mut stdin.lock().unwrap()); // Store execute steps - let steps = if let MinimalTraces::EmuTrace(min_traces) = &min_traces { - min_traces.iter().map(|trace| trace.steps).sum::() - } else { - panic!("Expected EmuTrace, got something else"); - }; + let steps = min_traces.iter().map(|trace| trace.steps).sum::(); let execution_result = ZiskExecutionResult::new(steps); @@ -85,7 +81,7 @@ impl EmulatorRust { (min_traces, main_count, secn_count, None, execution_result) } - fn run_emulator(&self, num_threads: usize, stdin: &mut ZiskStdin) -> MinimalTraces { + fn run_emulator(&self, num_threads: usize, stdin: &mut ZiskStdin) -> Vec { // Call emulate with these options let input_data = stdin.read(); @@ -104,7 +100,7 @@ impl EmulatorRust { ) .expect("Error during emulator execution"); - MinimalTraces::EmuTrace(min_traces) + min_traces } /// Counts metrics for secondary state machines based on minimal traces. @@ -120,15 +116,9 @@ impl EmulatorRust { /// containing the metrics for each chunk. fn count( &self, - min_traces: &MinimalTraces, + min_traces: &[EmuTrace], sm_bundle: &StaticSMBundle, ) -> (DeviceMetricsList, NestedDeviceMetricsList) { - let min_traces = match min_traces { - MinimalTraces::EmuTrace(min_traces) => min_traces, - MinimalTraces::AsmEmuTrace(asm_min_traces) => &asm_min_traces.vec_chunks, - _ => unreachable!(), - }; - let metrics_slices: Vec<_> = min_traces .par_iter() .map(|minimal_trace| { @@ -187,7 +177,7 @@ impl crate::Emulator for EmulatorRust { stats: &ExecutorStatsHandle, caller_stats_id: u64, ) -> ( - MinimalTraces, + Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 12ba8af3a..b31bc8db9 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -19,7 +19,6 @@ //! By structuring these phases, the `ZiskExecutor` ensures high-performance execution while //! maintaining clarity and modularity in the computation process. -use asm_runner::MinimalTraces; use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{create_pool, BufferPool, ProofCtx, ProofmanResult, SetupCtx}; @@ -33,7 +32,7 @@ use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; use zisk_common::ChunkId; use zisk_common::{ - BusDevice, BusDeviceMetrics, CheckPoint, ExecutorStatsHandle, Instance, InstanceCtx, + BusDevice, BusDeviceMetrics, CheckPoint, EmuTrace, ExecutorStatsHandle, Instance, InstanceCtx, InstanceType, Plan, Stats, ZiskExecutionResult, }; use zisk_pil::{ @@ -51,7 +50,6 @@ use zisk_common::ExecutorStatsEvent; use crossbeam::atomic::AtomicCell; -use zisk_common::EmuTrace; use zisk_core::ZiskRom; use ziskemu::ZiskEmulator; @@ -90,7 +88,7 @@ pub struct ZiskExecutor { zisk_rom: Arc, /// Planning information for main state machines. - min_traces: Arc>, + min_traces: Arc>>>, /// Planning information for secondary state machines. secn_planning: RwLock>, @@ -138,7 +136,7 @@ impl ZiskExecutor { chunk_size, hints_stream: Mutex::new(hints_stream), zisk_rom, - min_traces: Arc::new(RwLock::new(MinimalTraces::None)), + min_traces: Arc::new(RwLock::new(None)), secn_planning: RwLock::new(Vec::new()), main_instances: RwLock::new(HashMap::new()), secn_instances: RwLock::new(HashMap::new()), @@ -299,13 +297,7 @@ impl ZiskExecutor { ); let min_traces_guard = self.min_traces.read().unwrap(); - let min_traces = &*min_traces_guard; - - let min_traces = match min_traces { - MinimalTraces::EmuTrace(min_traces) => min_traces, - MinimalTraces::AsmEmuTrace(asm_min_traces) => &asm_min_traces.vec_chunks, - _ => unreachable!(), - }; + let min_traces = min_traces_guard.as_ref().expect("min_traces should not be None"); let air_instance = main_instance.compute_witness( &self.zisk_rom, @@ -464,13 +456,8 @@ impl ZiskExecutor { pctx: Arc>, secn_instances: HashMap>>, ) { - let min_traces = self.min_traces.read().unwrap(); - - let min_traces = match &*min_traces { - MinimalTraces::EmuTrace(min_traces) => min_traces, - MinimalTraces::AsmEmuTrace(asm_min_traces) => &asm_min_traces.vec_chunks, - _ => unreachable!(), - }; + let min_traces_guard = self.min_traces.read().unwrap(); + let min_traces = min_traces_guard.as_ref().expect("min_traces should not be None"); // Group the instances by the chunk they need to process let (chunks_to_execute, global_id_chunks) = @@ -720,7 +707,7 @@ impl ZiskExecutor { fn reset(&self) { // Reset the internal state of the executor *self.execution_result.lock().unwrap() = ZiskExecutionResult::default(); - *self.min_traces.write().unwrap() = MinimalTraces::None; + *self.min_traces.write().unwrap() = None; *self.secn_planning.write().unwrap() = Vec::new(); self.main_instances.write().unwrap().clear(); self.secn_instances.write().unwrap().clear(); @@ -789,7 +776,7 @@ impl WitnessComponent for ZiskExecutor { timer_start_info!(PLAN); let (main_planning, public_values) = MainPlanner::plan::(&min_traces, main_count, self.chunk_size); - *self.min_traces.write().unwrap() = min_traces; + *self.min_traces.write().unwrap() = Some(min_traces); self.assign_main_instances(&pctx, global_ids, main_planning); // Add to executor stats diff --git a/executor/src/lib.rs b/executor/src/lib.rs index cf077defd..9768708f6 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -23,11 +23,11 @@ pub use static_data_bus_collect::*; pub type DeviceMetricsList = Vec; pub type NestedDeviceMetricsList = HashMap; -use asm_runner::{AsmRunnerMO, MinimalTraces}; +use asm_runner::AsmRunnerMO; use fields::PrimeField64; use proofman_common::ProofCtx; use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; -use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, ZiskExecutionResult}; +use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, ZiskExecutionResult}; /// Trait for unified execution across different emulator backends pub trait Emulator: Send + Sync { @@ -40,7 +40,7 @@ pub trait Emulator: Send + Sync { stats: &ExecutorStatsHandle, caller_stats_id: u64, ) -> ( - MinimalTraces, + Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, @@ -70,7 +70,7 @@ impl Emulator for EmulatorKind { stats: &ExecutorStatsHandle, caller_stats_id: u64, ) -> ( - MinimalTraces, + Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, diff --git a/state-machines/main/Cargo.toml b/state-machines/main/Cargo.toml index 506988046..ca06a642d 100644 --- a/state-machines/main/Cargo.toml +++ b/state-machines/main/Cargo.toml @@ -14,7 +14,6 @@ zisk-common = { workspace = true } zisk-pil = { workspace = true } sm-mem = { workspace = true } mem-common = { workspace = true } -asm-runner = { workspace = true } proofman = { workspace = true } proofman-common = { workspace = true } diff --git a/state-machines/main/src/main_planner.rs b/state-machines/main/src/main_planner.rs index d08e43e09..479c0e215 100644 --- a/state-machines/main/src/main_planner.rs +++ b/state-machines/main/src/main_planner.rs @@ -5,9 +5,10 @@ use std::any::Any; -use asm_runner::MinimalTraces; use fields::PrimeField64; -use zisk_common::{BusDeviceMetrics, CheckPoint, ChunkId, InstanceType, Metrics, Plan, SegmentId}; +use zisk_common::{ + BusDeviceMetrics, CheckPoint, ChunkId, EmuTrace, InstanceType, Metrics, Plan, SegmentId, +}; use zisk_pil::{MainTrace, MAIN_AIR_IDS, ZISK_AIRGROUP_ID}; use crate::MainCounter; @@ -32,18 +33,10 @@ impl MainPlanner { /// # Returns /// A vector of `Plan` instances, each corresponding to a segment of the main trace. pub fn plan( - min_traces: &MinimalTraces, + min_traces: &[EmuTrace], main_counters: Vec<(ChunkId, Box)>, min_traces_size: u64, ) -> (Vec, Vec<(u64, u32)>) { - let min_traces = match min_traces { - MinimalTraces::AsmEmuTrace(asm_min_traces) => &asm_min_traces.vec_chunks, - MinimalTraces::EmuTrace(vec_chunks) => vec_chunks, - MinimalTraces::None => { - panic!("Minimal traces are required for planning the main state machine."); - } - }; - let num_rows = MainTrace::::NUM_ROWS as u64; let mut publics = Vec::new(); From 730217466fd5a44a07c914a5f47a455681dc1bca Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:54:24 +0000 Subject: [PATCH 426/782] refactor: renamed Preloaded* structs to *OutputShmem --- emulator-asm/asm-runner/src/asm_mo_runner.rs | 8 ++++---- emulator-asm/asm-runner/src/asm_mt_runner.rs | 6 +++--- emulator-asm/asm-runner/src/asm_rh_runner.rs | 8 ++++---- executor/src/emu_asm.rs | 12 ++++++------ 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index 538d7ddc3..f47609899 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -25,13 +25,13 @@ use zisk_common::ExecutorStatsEvent; #[cfg(feature = "save_mem_plans")] use mem_common::save_plans; -pub struct PreloadedMO { +pub struct MOOutputShmem { pub output_shmem: AsmMultiSharedMemory, mem_planner: Option, handle_mo: Option>, } -impl PreloadedMO { +impl MOOutputShmem { pub fn new( local_rank: i32, base_port: Option, @@ -66,7 +66,7 @@ impl PreloadedMO { } } -impl Drop for PreloadedMO { +impl Drop for MOOutputShmem { fn drop(&mut self) { if let Some(handle_mo) = self.handle_mo.take() { match handle_mo.join() { @@ -94,7 +94,7 @@ impl AsmRunnerMO { #[allow(clippy::too_many_arguments)] pub fn run( - preloaded: &mut PreloadedMO, + preloaded: &mut MOOutputShmem, max_steps: u64, chunk_size: u64, world_rank: i32, diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index e075bdf32..8f38db63b 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -20,11 +20,11 @@ use anyhow::{Context, Result}; #[cfg(feature = "stats")] use zisk_common::ExecutorStatsEvent; -pub struct PreloadedMT { +pub struct MTOutputShmem { pub output_shmem: AsmMultiSharedMemory, } -impl PreloadedMT { +impl MTOutputShmem { pub fn new( local_rank: i32, base_port: Option, @@ -67,7 +67,7 @@ impl AsmRunnerMT { #[allow(clippy::too_many_arguments)] pub fn run_and_count)>( - preloaded: &mut PreloadedMT, + preloaded: &mut MTOutputShmem, max_steps: u64, chunk_size: u64, mut on_chunk: F, diff --git a/emulator-asm/asm-runner/src/asm_rh_runner.rs b/emulator-asm/asm-runner/src/asm_rh_runner.rs index 5ec7301ec..0059a9f6e 100644 --- a/emulator-asm/asm-runner/src/asm_rh_runner.rs +++ b/emulator-asm/asm-runner/src/asm_rh_runner.rs @@ -9,11 +9,11 @@ use anyhow::{Context, Result}; use named_sem::NamedSemaphore; use std::sync::atomic::{fence, Ordering}; -pub struct PreloadedRH { +pub struct RHOutputShmem { pub output_shmem: AsmSharedMemory, } -impl PreloadedRH { +impl RHOutputShmem { pub fn new( local_rank: i32, base_port: Option, @@ -60,7 +60,7 @@ impl AsmRunnerRH { } pub fn run( - asm_shared_memory: &mut Option, + asm_shared_memory: &mut Option, max_steps: u64, world_rank: i32, local_rank: i32, @@ -116,7 +116,7 @@ impl AsmRunnerRH { if asm_shared_memory.is_none() { *asm_shared_memory = - Some(PreloadedRH::new(local_rank, base_port, unlock_mapped_memory)?); + Some(RHOutputShmem::new(local_rank, base_port, unlock_mapped_memory)?); } let asm_rowh_output = diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 72fd2ced2..77502ffc1 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -9,7 +9,7 @@ use crate::{ }; use asm_runner::{ write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmService, AsmServices, - AsmSharedMemory, PreloadedMO, PreloadedMT, PreloadedRH, SharedMemoryWriter, + AsmSharedMemory, MOOutputShmem, MTOutputShmem, RHOutputShmem, SharedMemoryWriter, }; use data_bus::DataBusTrait; use fields::PrimeField64; @@ -45,11 +45,11 @@ pub struct EmulatorAsm { rom_sm: Option>, #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_mt: Arc>, + asm_shmem_mt: Arc>, #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_mo: Arc>, + asm_shmem_mo: Arc>, #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_rh: Arc>>, + asm_shmem_rh: Arc>>, /// Shared memory writers for each assembly service. #[cfg(all(target_os = "linux", target_arch = "x86_64"))] @@ -68,10 +68,10 @@ impl EmulatorAsm { rom_sm: Option>, ) -> Self { #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - let asm_shmem_mt = PreloadedMT::new(local_rank, base_port, unlock_mapped_memory) + let asm_shmem_mt = MTOutputShmem::new(local_rank, base_port, unlock_mapped_memory) .expect("Failed to create PreloadedMT"); #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - let asm_shmem_mo = PreloadedMO::new(local_rank, base_port, unlock_mapped_memory) + let asm_shmem_mo = MOOutputShmem::new(local_rank, base_port, unlock_mapped_memory) .expect("Failed to create PreloadedMO"); Self { From 56695a0c3fdbbe0ad8a5943879dc64ebe5de1271 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:33:16 +0000 Subject: [PATCH 427/782] fix: use default port for each worker in new shared memory naming --- emulator-asm/asm-runner/src/asm_mo_runner.rs | 29 ++++----------- emulator-asm/asm-runner/src/asm_mt_runner.rs | 31 ++++------------ emulator-asm/asm-runner/src/asm_rh_runner.rs | 33 +++++------------ .../asm-runner/src/asm_services/services.rs | 10 ++++-- emulator-asm/asm-runner/src/hints_shmem.rs | 9 ++--- emulator-asm/asm-runner/src/lib.rs | 22 +++++++++--- emulator-asm/asm-runner/src/shmem_utils.rs | 35 +------------------ executor/src/emu_asm.rs | 18 +++------- 8 files changed, 56 insertions(+), 131 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index f47609899..03176c35d 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -12,8 +12,8 @@ use crate::TRACE_DELTA_SIZE; use crate::TRACE_INITIAL_SIZE; use crate::TRACE_MAX_SIZE; use crate::{ - AsmMOChunk, AsmMOHeader, AsmMultiSharedMemory, AsmRunError, AsmService, AsmServices, - AsmSharedMemory, + sem_chunk_done_name, shmem_output_name, AsmMOChunk, AsmMOHeader, AsmMultiSharedMemory, + AsmRunError, AsmService, AsmServices, }; use mem_planner_cpp::MemPlanner; @@ -37,18 +37,9 @@ impl MOOutputShmem { base_port: Option, unlock_mapped_memory: bool, ) -> Result { - let port = if let Some(base_port) = base_port { - AsmServices::port_for(&AsmService::MO, base_port, local_rank) - } else { - AsmServices::default_port(&AsmService::MO, local_rank) - }; + let port = AsmServices::port_base_for(base_port, local_rank); - let output_name = AsmSharedMemory::::shmem_output_name( - base_port.unwrap(), - AsmService::MO, - local_rank, - None, - ); + let output_name = shmem_output_name(port, AsmService::MO, local_rank, None); let output_shared_memory = AsmMultiSharedMemory::::open_and_map( &output_name, @@ -107,17 +98,9 @@ impl AsmRunnerMO { #[cfg(feature = "stats")] _stats.add_stat(0, parent_stats_id, "ASM_MO_RUNNER", 0, ExecutorStatsEvent::Begin); - let port = if let Some(base_port) = base_port { - AsmServices::port_for(&AsmService::MO, base_port, local_rank) - } else { - AsmServices::default_port(&AsmService::MO, local_rank) - }; + let port = AsmServices::port_base_for(base_port, local_rank); - let sem_chunk_done_name = AsmSharedMemory::::shmem_chunk_done_name( - base_port.unwrap(), - AsmService::MO, - local_rank, - ); + let sem_chunk_done_name = sem_chunk_done_name(port, AsmService::MO, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 8f38db63b..a37570ab4 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -10,9 +10,9 @@ use std::time::Instant; use tracing::{error, info}; use crate::{ - AsmMTChunk, AsmMTHeader, AsmMultiSharedMemory, AsmRunError, AsmService, AsmServices, - AsmSharedMemory, SEM_CHUNK_DONE_WAIT_DURATION, TRACE_DELTA_SIZE, TRACE_INITIAL_SIZE, - TRACE_MAX_SIZE, + sem_chunk_done_name, shmem_output_name, AsmMTChunk, AsmMTHeader, AsmMultiSharedMemory, + AsmRunError, AsmService, AsmServices, SEM_CHUNK_DONE_WAIT_DURATION, TRACE_DELTA_SIZE, + TRACE_INITIAL_SIZE, TRACE_MAX_SIZE, }; use anyhow::{Context, Result}; @@ -30,18 +30,9 @@ impl MTOutputShmem { base_port: Option, unlock_mapped_memory: bool, ) -> Result { - let port = if let Some(base_port) = base_port { - AsmServices::port_for(&AsmService::MT, base_port, local_rank) - } else { - AsmServices::default_port(&AsmService::MT, local_rank) - }; + let port = AsmServices::port_base_for(base_port, local_rank); - let output_name = AsmSharedMemory::::shmem_output_name( - base_port.unwrap(), - AsmService::MT, - local_rank, - None, - ); + let output_name = shmem_output_name(port, AsmService::MT, local_rank, None); let output_shared_memory = AsmMultiSharedMemory::::open_and_map( &output_name, @@ -83,17 +74,9 @@ impl AsmRunnerMT { #[cfg(feature = "stats")] _stats.add_stat(0, parent_stats_id, "ASM_MT_RUNNER", 0, ExecutorStatsEvent::Begin); - let port = if let Some(base_port) = base_port { - AsmServices::port_for(&AsmService::MT, base_port, local_rank) - } else { - AsmServices::default_port(&AsmService::MT, local_rank) - }; + let port = AsmServices::port_base_for(base_port, local_rank); - let sem_chunk_done_name = AsmSharedMemory::::shmem_chunk_done_name( - base_port.unwrap(), - AsmService::MT, - local_rank, - ); + let sem_chunk_done_name = sem_chunk_done_name(port, AsmService::MT, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; diff --git a/emulator-asm/asm-runner/src/asm_rh_runner.rs b/emulator-asm/asm-runner/src/asm_rh_runner.rs index 0059a9f6e..de904f9f4 100644 --- a/emulator-asm/asm-runner/src/asm_rh_runner.rs +++ b/emulator-asm/asm-runner/src/asm_rh_runner.rs @@ -2,8 +2,8 @@ use tracing::error; use zisk_common::ExecutorStatsHandle; use crate::{ - AsmRHData, AsmRHHeader, AsmRunError, AsmService, AsmServices, AsmSharedMemory, - SEM_CHUNK_DONE_WAIT_DURATION, + sem_chunk_done_name, shmem_output_name, AsmRHData, AsmRHHeader, AsmRunError, AsmService, + AsmServices, AsmSharedMemory, SEM_CHUNK_DONE_WAIT_DURATION, }; use anyhow::{Context, Result}; use named_sem::NamedSemaphore; @@ -19,18 +19,9 @@ impl RHOutputShmem { base_port: Option, unlock_mapped_memory: bool, ) -> Result { - let port = if let Some(base_port) = base_port { - AsmServices::port_for(&AsmService::RH, base_port, local_rank) - } else { - AsmServices::default_port(&AsmService::RH, local_rank) - }; - - let output_name = AsmSharedMemory::::shmem_output_name( - base_port.unwrap(), - AsmService::RH, - local_rank, - Some(0), - ); + let port = AsmServices::port_base_for(base_port, local_rank); + + let output_name = shmem_output_name(port, AsmService::RH, local_rank, Some(0)); let output_shared_memory = AsmSharedMemory::::open_and_map(&output_name, unlock_mapped_memory)?; @@ -75,17 +66,9 @@ impl AsmRunnerRH { #[cfg(feature = "stats")] _stats.add_stat(0, parent_stats_id, "ASM_RH_RUNNER", 0, ExecutorStatsEvent::Begin); - let port = if let Some(base_port) = base_port { - AsmServices::port_for(&AsmService::RH, base_port, local_rank) - } else { - AsmServices::default_port(&AsmService::RH, local_rank) - }; - - let sem_chunk_done_name = AsmSharedMemory::::shmem_chunk_done_name( - base_port.unwrap(), - AsmService::RH, - local_rank, - ); + let port = AsmServices::port_base_for(base_port, local_rank); + + let sem_chunk_done_name = sem_chunk_done_name(port, AsmService::RH, local_rank); let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 98314e9ed..1a99e4eb3 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -246,6 +246,12 @@ impl AsmServices { base_port + service_offset as u16 + rank_offset } + pub fn port_base_for(base_port: Option, local_rank: i32) -> u16 { + let rank_offset = local_rank as u16 * Self::SERVICES.len() as u16; + + base_port.unwrap_or(ASM_SERVICE_BASE_PORT) + rank_offset + } + pub fn send_status_request(&self, service: &AsmService) -> Result { self.send_request(service, &PingRequest {}) } @@ -344,11 +350,11 @@ impl AsmServices { #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub fn send_shutdown_and_wait(&self, service: &AsmService) -> Result<()> { - let port = AsmServices::port_for(service, self.base_port, self.local_rank); + let port = AsmServices::port_base_for(Some(self.base_port), self.local_rank); let sem_name = format!( "/{}_{}_shutdown_done", - Self::shmem_prefix(self.base_port, self.local_rank), + Self::shmem_prefix(port, self.local_rank), service.as_str() ); diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index f94c77337..602bd1311 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -95,12 +95,9 @@ impl HintsShmem { let separate_names: Vec = AsmServices::SERVICES .iter() .map(|service| { - let port = if let Some(base_port) = base_port { - AsmServices::port_for(service, base_port, local_rank) - } else { - AsmServices::default_port(service, local_rank) - }; - SeparateResourceNames::new(service, base_port.unwrap(), local_rank) + let port = AsmServices::port_base_for(base_port, local_rank); + + SeparateResourceNames::new(service, port, local_rank) }) .collect(); diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 9ce72edd2..8c507104f 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -95,8 +95,8 @@ fn build_sem_name(port: u16, asm_service: AsmService, local_rank: i32, suffix: & build_name("/", port, asm_service, local_rank, suffix) } -pub fn shmem_input_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - build_shmem_name(port, asm_service, local_rank, "input") +pub fn shmem_input_name(port: u16, local_rank: i32) -> String { + build_shmem_name2(port, local_rank, "input") } /// Shared memory name for precompile hints data @@ -123,8 +123,22 @@ pub fn shmem_control_reader_name(port: u16, asm_service: AsmService, local_rank: build_shmem_name(port, asm_service, local_rank, "control_output") } -pub fn shmem_output_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - build_shmem_name(port, asm_service, local_rank, "output") +pub fn shmem_output_name( + port: u16, + asm_service: AsmService, + local_rank: i32, + suffix: Option, +) -> String { + if let Some(suffix) = suffix { + format!( + "{}_{}_output_{}", + AsmServices::shmem_prefix(port, local_rank), + asm_service.as_str(), + suffix + ) + } else { + build_shmem_name(port, asm_service, local_rank, "output") + } } pub fn sem_chunk_done_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index e28b62800..71e9ec509 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -16,7 +16,7 @@ use zisk_common::io::{ZiskIO, ZiskStdin}; use anyhow::anyhow; use anyhow::Result; -use crate::{AsmInputHeader, AsmService, AsmServices, SharedMemoryWriter}; +use crate::{AsmInputHeader, SharedMemoryWriter}; pub enum AsmSharedMemoryMode { ReadOnly, @@ -271,39 +271,6 @@ impl AsmSharedMemory { // Skip the header size to get the data pointer unsafe { self.mapped_ptr.add(size_of::()) } } - - pub fn shmem_input_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - format!("{}_{}_input", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) - } - - pub fn shmem_input_name2(port: u16, asm_service: AsmService, local_rank: i32) -> String { - format!("{}_input", AsmServices::shmem_prefix(port, local_rank)) - } - - pub fn shmem_output_name( - port: u16, - asm_service: AsmService, - local_rank: i32, - suffix: Option, - ) -> String { - if let Some(suffix) = suffix { - return format!( - "{}_{}_output_{}", - AsmServices::shmem_prefix(port, local_rank), - asm_service.as_str(), - suffix - ); - } - format!("{}_{}_output", AsmServices::shmem_prefix(port, local_rank), asm_service.as_str()) - } - - pub fn shmem_chunk_done_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { - format!( - "/{}_{}_chunk_done", - AsmServices::shmem_prefix(port, local_rank), - asm_service.as_str() - ) - } } pub fn open_shmem(name: &str, flags: i32, mode: u32) -> Result { diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 77502ffc1..b06ddb5f4 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -8,8 +8,8 @@ use crate::{ DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, MAX_NUM_STEPS, }; use asm_runner::{ - write_input, AsmMTHeader, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmService, AsmServices, - AsmSharedMemory, MOOutputShmem, MTOutputShmem, RHOutputShmem, SharedMemoryWriter, + shmem_input_name, write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmService, AsmServices, + MOOutputShmem, MTOutputShmem, RHOutputShmem, SharedMemoryWriter, }; use data_bus::DataBusTrait; use fields::PrimeField64; @@ -214,17 +214,9 @@ impl EmulatorAsm { } fn create_shmem_writer(&self, service: &asm_runner::AsmService) -> SharedMemoryWriter { - let port = if let Some(base_port) = self.base_port { - AsmServices::port_for(service, base_port, self.local_rank) - } else { - AsmServices::default_port(service, self.local_rank) - }; - - let shmem_input_name = AsmSharedMemory::::shmem_input_name2( - self.base_port.unwrap(), - *service, - self.local_rank, - ); + let port = AsmServices::port_base_for(self.base_port, self.local_rank); + + let shmem_input_name = shmem_input_name(port, self.local_rank); tracing::debug!( "Initializing SharedMemoryWriter for service {:?} at '{}'", From 937245a7215527c511aa06c3cf8cffd1b6b3710a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:35:33 +0000 Subject: [PATCH 428/782] cargo clippy --- emulator-asm/asm-runner/src/asm_mt.rs | 5 ++++- emulator-asm/asm-runner/src/multi_shmem.rs | 3 +-- executor/src/emu_rust.rs | 11 ++--------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mt.rs b/emulator-asm/asm-runner/src/asm_mt.rs index 4caad127d..2ddeb9525 100644 --- a/emulator-asm/asm-runner/src/asm_mt.rs +++ b/emulator-asm/asm-runner/src/asm_mt.rs @@ -51,7 +51,10 @@ impl AsmMTChunk { let mem_reads_ptr = *mapped_ptr as *const u64; let mem_reads_len = chunk.mem_reads_size as usize; let mem_reads: Cow<'static, [u64]> = Cow::Borrowed(unsafe { - std::mem::transmute(std::slice::from_raw_parts(mem_reads_ptr, mem_reads_len)) + std::mem::transmute::<&[u64], &[u64]>(std::slice::from_raw_parts( + mem_reads_ptr, + mem_reads_len, + )) }); // Advance the pointer after reading memory reads diff --git a/emulator-asm/asm-runner/src/multi_shmem.rs b/emulator-asm/asm-runner/src/multi_shmem.rs index 627223c83..7553fe727 100644 --- a/emulator-asm/asm-runner/src/multi_shmem.rs +++ b/emulator-asm/asm-runner/src/multi_shmem.rs @@ -164,8 +164,7 @@ impl AsmMultiSharedMemory { let files_needed = if allocated_size <= self.initial_size { 1 } else { - 1 + ((allocated_size - self.initial_size + self.incremental_size - 1) - / self.incremental_size) + 1 + (allocated_size - self.initial_size).div_ceil(self.incremental_size) }; let current_files = self.mapped_files.len(); diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index ec4cdd73d..5073086f2 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -92,15 +92,8 @@ impl EmulatorRust { ..EmuOptions::default() }; - let min_traces = ZiskEmulator::compute_minimal_traces( - &self.zisk_rom, - &input_data, - &emu_options, - num_threads, - ) - .expect("Error during emulator execution"); - - min_traces + ZiskEmulator::compute_minimal_traces(&self.zisk_rom, &input_data, &emu_options, num_threads) + .expect("Error during emulator execution") } /// Counts metrics for secondary state machines based on minimal traces. From a93f0799eeeac5374178d216e16cc38b3f2bdfc8 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:12:49 +0100 Subject: [PATCH 429/782] cargo clippy macOS --- emulator-asm/asm-runner/src/asm_mt_runner_stub.rs | 6 +----- emulator-asm/asm-runner/src/lib.rs | 3 +++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs b/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs index ac81b94c6..9c887e830 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner_stub.rs @@ -1,14 +1,10 @@ -use zisk_common::{ChunkId, EmuTrace, ExecutorStatsHandle}; +use zisk_common::{EmuTrace, ExecutorStatsHandle}; use std::ffi::c_void; use std::fmt::Debug; use std::sync::Arc; use anyhow::Result; -pub trait Task: Send + Sync + 'static { - type Output: Send + 'static; - fn execute(self) -> Self::Output; -} pub struct PreloadedMT {} diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 8c507104f..9e743c996 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -56,8 +56,11 @@ pub use shmem_reader::*; pub use shmem_utils::*; pub use shmem_writer::*; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub(crate) const TRACE_INITIAL_SIZE: usize = 0x180000000; // 6GB +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub(crate) const TRACE_DELTA_SIZE: usize = 0x080000000; // 2GB +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub(crate) const TRACE_MAX_SIZE: usize = 0x1000000000; // 64GB #[cfg(all(target_os = "linux", target_arch = "x86_64"))] From 0aed49506cb9d5f5d62bb426cf935987506c1657 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 4 Feb 2026 14:50:57 +0100 Subject: [PATCH 430/782] Fix chunk_size in trace mapping 2 --- emulator-asm/src/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index ad2bf8583..d5f231c1b 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -536,7 +536,7 @@ void trace_map_next_chunk (void) { flags |= MAP_FIXED; } - void * pTrace = mmap(requested_address, trace_size, PROT_READ | PROT_WRITE, flags, trace_chunk_fd[chunk_id], 0); + void * pTrace = mmap(requested_address, chunk_size, PROT_READ | PROT_WRITE, flags, trace_chunk_fd[chunk_id], 0); if (verbose) { gettimeofday(&stop_time, NULL); @@ -556,7 +556,7 @@ void trace_map_next_chunk (void) fflush(stderr); exit(-1); } - if (verbose) printf("trace_map_next_chunk() mapped %lu B to %s and returned address %p in %lu us\n", trace_size, shmem_chunk_name, pTrace, duration); + if (verbose) printf("trace_map_next_chunk() mapped %lu B to %s and returned address %p in %lu us\n", chunk_size, shmem_chunk_name, pTrace, duration); // Update total mapped size trace_total_mapped_size += chunk_size; From 75393a7026c22c0b34c1e0819a8b2fa37cffd682 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:11:44 +0000 Subject: [PATCH 431/782] set timeout to 5' for CI pruposes --- emulator-asm/asm-runner/src/asm_mt_runner.rs | 4 ++-- emulator-asm/asm-runner/src/asm_services/services.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index a37570ab4..50773c1c3 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -34,7 +34,7 @@ impl MTOutputShmem { let output_name = shmem_output_name(port, AsmService::MT, local_rank, None); - let output_shared_memory = AsmMultiSharedMemory::::open_and_map( + let output_shmem = AsmMultiSharedMemory::::open_and_map( &output_name, TRACE_INITIAL_SIZE, TRACE_DELTA_SIZE, @@ -42,7 +42,7 @@ impl MTOutputShmem { unlock_mapped_memory, )?; - Ok(Self { output_shmem: output_shared_memory }) + Ok(Self { output_shmem }) } } diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 1a99e4eb3..ad011ca16 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -176,7 +176,7 @@ impl AsmServices { fn wait_for_service_ready(service: &AsmService, port: u16) { let addr = format!("127.0.0.1:{port}"); - let timeout = Duration::from_secs(60); + let timeout = Duration::from_secs(50 * 5); let retry_delay = Duration::from_millis(100); let start = Instant::now(); From 71233287446199170a06539047bf16aa5a53438f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:28:09 +0000 Subject: [PATCH 432/782] increase shared memory limit in pr.yml --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6cfbc7cc9..ae0d3bfde 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -51,7 +51,7 @@ jobs: container: image: ubuntu:22.04 options: - --shm-size=32g + --shm-size=128g -v /home/gha/cache-setup:/github/home/output:rw defaults: run: From 46cd738b34fe16877f63875cad782f69dd8d892a Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 4 Feb 2026 17:29:54 +0000 Subject: [PATCH 433/782] Fix Clippy --- ziskos/entrypoint/src/hints/mod.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index b3c7bdf7f..957898984 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -197,15 +197,11 @@ impl UnixSocketWriter { impl Write for UnixSocketWriter { fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner - .write(buf) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) + self.inner.write(buf).map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) } fn flush(&mut self) -> io::Result<()> { - self.inner - .flush() - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) + self.inner.flush().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) } } From 241980d4390b7b2bfe06ec3c43d65728cc11b56f Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 4 Feb 2026 18:03:33 +0000 Subject: [PATCH 434/782] Fix hints parameter to native_keccak256 --- ziskos/entrypoint/src/zisklib/lib/keccak256.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs index 76c67a920..2ea327fa8 100644 --- a/ziskos/entrypoint/src/zisklib/lib/keccak256.rs +++ b/ziskos/entrypoint/src/zisklib/lib/keccak256.rs @@ -141,7 +141,13 @@ pub unsafe extern "C" fn native_keccak256(bytes: *const u8, len: usize, output: #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { - keccak256_c(bytes, len, output); + keccak256_c( + bytes, + len, + output, + #[cfg(feature = "hints")] + hints, + ); } #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] From 2cfe46b0856d7c5397b0abc31aed56581dec5e52 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Wed, 4 Feb 2026 18:08:37 +0000 Subject: [PATCH 435/782] Proofman update --- Cargo.lock | 22 +++++++++++----------- sdk/Cargo.toml | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8db69f881..6e48f2ac0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1189,7 +1189,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" dependencies = [ "fields", "num-bigint", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" dependencies = [ "cfg-if", "num-bigint", @@ -2965,7 +2965,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" dependencies = [ "colored", "fields", @@ -3380,7 +3380,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" dependencies = [ "bincode", "blake3", @@ -3416,7 +3416,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" dependencies = [ "bincode", "borsh", @@ -3449,7 +3449,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" dependencies = [ "fields", "itoa", @@ -3462,7 +3462,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" dependencies = [ "proc-macro2", "quote", @@ -3473,7 +3473,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" dependencies = [ "crossbeam-channel", "tracing", @@ -3482,7 +3482,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" dependencies = [ "bincode", "bytemuck", @@ -3496,7 +3496,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" dependencies = [ "bytemuck", "fields", @@ -5891,7 +5891,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1722586c1826b708aa75b868e0d0280564522167" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" dependencies = [ "colored", "fields", diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index b2cb6969d..0ea66b732 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -29,7 +29,8 @@ serde = { workspace = true } [features] default = [] -gpu = [] +gpu = ["proofman-common/gpu", "packed"] +packed = ["proofman-common/packed"] stats = [] disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] From 5909430e9e22abba4287d04b910ee8ec0cdcd697 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Feb 2026 18:16:34 +0000 Subject: [PATCH 436/782] reduce shared memory in pr.yml to 48GB --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ae0d3bfde..84eeb0fda 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -51,7 +51,7 @@ jobs: container: image: ubuntu:22.04 options: - --shm-size=128g + --shm-size=48g -v /home/gha/cache-setup:/github/home/output:rw defaults: run: From e9256424360eaa1485cdc074c8cfabe2cd77b2a9 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 4 Feb 2026 19:45:34 +0100 Subject: [PATCH 437/782] fix on indirection aligment control add exception for direct param --- core/src/zisk_ops.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index c4b6aef27..278d84501 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -1461,11 +1461,13 @@ pub fn precompiled_load_data( load_chunks, load_rem, 0, + None, data, title, ); } +#[allow(clippy::too_many_arguments)] #[inline(always)] pub fn precompiled_load_data_with_result( ctx: &mut InstContext, @@ -1473,6 +1475,7 @@ pub fn precompiled_load_data_with_result( load_indirections: usize, load_chunks: usize, load_rem: usize, + direct_load_param: Option, data: &mut [u64], title: &str, ) { @@ -1483,6 +1486,7 @@ pub fn precompiled_load_data_with_result( load_chunks, load_rem, 1, + direct_load_param, data, title, ); @@ -1497,6 +1501,7 @@ fn internal_precompiled_load_data( load_chunks: usize, load_rem: usize, result: usize, + direct_load_param: Option, // If one of the load parameters isn't an indirection data: &mut [u64], title: &str, ) { @@ -1530,7 +1535,7 @@ fn internal_precompiled_load_data( // Write the indirections to data for (i, data) in data.iter_mut().enumerate().take(params_count) { let indirection = ctx.mem.read(address + (8 * i as u64), 8); - if indirection & 0x7 != 0 { + if indirection & 0x7 != 0 && Some(i) != direct_load_param { panic!( "[{title}] precompiled_check_address() found address_{i} [0x{address:08X}]=0x{indirection:08X} \ not aligned to 8 bytes at PC:0x{:08X} STEP:{}", @@ -1622,7 +1627,7 @@ pub fn opc_add256(ctx: &mut InstContext) { const WORDS: usize = 4 + 1 + 2 * 4; let mut data = [0u64; WORDS]; - precompiled_load_data_with_result(ctx, 4, 2, 4, 0, &mut data, "add256"); + precompiled_load_data_with_result(ctx, 4, 2, 4, 0, Some(2), &mut data, "add256"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 3 indirections From 331519ed153c1c36641192f72fd65c6387234252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Thu, 5 Feb 2026 11:09:01 +0100 Subject: [PATCH 438/782] Fix rom-setup-gpu and read_input_slice no longer exposed (#751) * Fix rom-setup-gpu and read_input_slice no longer exposed * Fix rom setup --- Cargo.lock | 34 +++++++++++++------------- cli/src/commands/rom_setup.rs | 35 +++++++++++++++++++++++---- rom-setup/src/rom_merkle.rs | 41 ++++++++++++++++++++++++++++++-- rom-setup/src/utils.rs | 4 +++- sdk/src/prover/asm.rs | 4 ++-- sdk/src/prover/backend.rs | 20 ++++++++++------ sdk/src/prover/emu.rs | 4 ++-- sdk/src/utils.rs | 18 +++++++------- test-verifier/.gitignore | 2 -- test-verifier/Cargo.toml | 14 ----------- test-verifier/build/input.bin | Bin 341600 -> 0 bytes test-verifier/build/zisk.vk.bin | 1 - test-verifier/src/main.rs | 19 --------------- ziskos/entrypoint/src/lib.rs | 9 +++---- 14 files changed, 120 insertions(+), 85 deletions(-) delete mode 100644 test-verifier/.gitignore delete mode 100644 test-verifier/Cargo.toml delete mode 100644 test-verifier/build/input.bin delete mode 100644 test-verifier/build/zisk.vk.bin delete mode 100644 test-verifier/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 372d50f83..bdbd1b8d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1189,7 +1189,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" dependencies = [ "fields", "num-bigint", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" dependencies = [ "cfg-if", "num-bigint", @@ -2965,7 +2965,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" dependencies = [ "colored", "fields", @@ -3380,7 +3380,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" dependencies = [ "bincode", "blake3", @@ -3416,7 +3416,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" dependencies = [ "bincode", "borsh", @@ -3449,7 +3449,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" dependencies = [ "fields", "itoa", @@ -3462,7 +3462,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" dependencies = [ "proc-macro2", "quote", @@ -3473,7 +3473,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" dependencies = [ "crossbeam-channel", "tracing", @@ -3482,7 +3482,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" dependencies = [ "bincode", "bytemuck", @@ -3496,7 +3496,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" dependencies = [ "bytemuck", "fields", @@ -4704,9 +4704,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.46" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -4727,9 +4727,9 @@ checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -5890,7 +5890,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#38df10734720232d68c62e794706c18e899740a8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" dependencies = [ "colored", "fields", @@ -5909,9 +5909,9 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "x509-parser" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ "asn1-rs", "data-encoding", diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index e8855972d..9dc9b77b5 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -5,10 +5,13 @@ use std::path::PathBuf; use crate::{commands::get_proving_key, ux::print_banner}; use colored::Colorize; use fields::Goldilocks; -use proofman_common::initialize_logger; +use proofman_common::{ + initialize_logger, MpiCtx, ParamsGPU, ProofCtx, ProofType, SetupCtx, SetupsVadcop, +}; use rom_setup::gen_assembly; use rom_setup::rom_merkle_setup; use std::fs; +use std::sync::Arc; use zisk_common::ElfBinaryOwned; #[derive(Parser)] @@ -35,8 +38,8 @@ pub struct ZiskRomSetup { #[clap(short = 'n', long, default_value_t = false)] pub hints: bool, - #[clap(short = 'v', long, default_value_t = false)] - pub verbose: bool, + #[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity level")] + pub verbose: u8, } impl ZiskRomSetup { @@ -53,6 +56,22 @@ impl ZiskRomSetup { let proving_key = get_proving_key(self.proving_key.as_ref()); + let mpi_ctx = Arc::new(MpiCtx::new()); + let mut pctx = ProofCtx::create_ctx(proving_key, false, self.verbose.into(), mpi_ctx)?; + + let params_gpu = ParamsGPU::new(false); + let sctx = Arc::new(SetupCtx::::new( + &pctx.global_info, + &ProofType::Basic, + false, + ¶ms_gpu, + &[], + )); + let setups_vadcop = + Arc::new(SetupsVadcop::new(&pctx.global_info, false, false, ¶ms_gpu, &[])); + pctx.set_device_buffers(&sctx, &setups_vadcop, false, ¶ms_gpu)?; + let pctx = Arc::new(pctx); + tracing::info!("Computing setup for ROM {}", self.elf_path.display()); tracing::info!("Computing merkle root"); @@ -64,9 +83,15 @@ impl ZiskRomSetup { self.elf_path.file_stem().unwrap().to_str().unwrap().to_string(), self.hints, ); - rom_merkle_setup::(&elf, &self.output_dir, &proving_key)?; + rom_merkle_setup::(&pctx, &elf, &self.output_dir)?; - gen_assembly(&self.elf_path, &self.zisk_path, &self.output_dir, self.verbose, self.hints)?; + gen_assembly( + &self.elf_path, + &self.zisk_path, + &self.output_dir, + self.hints, + self.verbose > 0, + )?; println!(); tracing::info!("{}", "ROM setup successfully completed".bright_green().bold()); diff --git a/rom-setup/src/rom_merkle.rs b/rom-setup/src/rom_merkle.rs index bef1eda63..d94db60f0 100644 --- a/rom-setup/src/rom_merkle.rs +++ b/rom-setup/src/rom_merkle.rs @@ -1,4 +1,5 @@ use fields::PrimeField64; +use proofman_common::ProofCtx; use std::path::{Path, PathBuf}; use zisk_common::ElfBinaryLike; @@ -8,15 +9,15 @@ use crate::{ }; pub fn rom_merkle_setup( + pctx: &ProofCtx, elf: &impl ElfBinaryLike, output_dir: &Option, - proving_key: &Path, ) -> Result<(PathBuf, Vec), anyhow::Error> { let output_path = get_output_path(output_dir)?; let elf_hash = get_elf_data_hash(elf)?; - let rom_info = get_rom_info(proving_key)?; + let rom_info = get_rom_info(&pctx.global_info.get_proving_key_path())?; let elf_bin_path = get_elf_bin_file_path_with_hash( &elf_hash, @@ -40,6 +41,7 @@ pub fn rom_merkle_setup( } let root = gen_elf_hash::( + pctx, elf.elf(), elf_bin_path.as_path(), rom_info.blowup_factor, @@ -54,3 +56,38 @@ pub fn rom_merkle_setup( Ok((elf_bin_path, verkey)) } + +pub fn rom_merkle_setup_verkey( + elf: &impl ElfBinaryLike, + output_dir: &Option, + proving_key: &Path, +) -> Result, anyhow::Error> { + let output_path = get_output_path(output_dir)?; + + let elf_hash = get_elf_data_hash(elf)?; + + let rom_info = get_rom_info(proving_key)?; + + let elf_bin_path = get_elf_bin_file_path_with_hash( + &elf_hash, + &output_path, + rom_info.blowup_factor, + rom_info.merkle_tree_arity, + )?; + + let elf_verkey_bin_path = get_elf_bin_verkey_file_path_with_hash( + &elf_hash, + &output_path, + rom_info.blowup_factor, + rom_info.merkle_tree_arity, + )?; + + if elf_bin_path.exists() && elf_verkey_bin_path.exists() { + let verkey = get_elf_vk(elf_verkey_bin_path.as_path())? + .ok_or_else(|| anyhow::anyhow!("Failed to read existing verkey file"))?; + + Ok(verkey) + } else { + Err(anyhow::anyhow!("ROM merkle setup has not been performed yet")) + } +} diff --git a/rom-setup/src/utils.rs b/rom-setup/src/utils.rs index b612c4570..702760263 100644 --- a/rom-setup/src/utils.rs +++ b/rom-setup/src/utils.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use fields::{Goldilocks, PrimeField64}; use proofman_common::{ - write_custom_commit_trace, GlobalInfo, ProofType, ProofmanResult, StarkInfo, + write_custom_commit_trace, GlobalInfo, ProofCtx, ProofType, ProofmanResult, StarkInfo, }; use sm_rom::RomSM; use std::env; @@ -52,6 +52,7 @@ pub fn get_output_path(output_dir: &Option) -> Result { } pub fn gen_elf_hash( + pctx: &ProofCtx, elf: &[u8], rom_buffer_path: &Path, blowup_factor: u64, @@ -63,6 +64,7 @@ pub fn gen_elf_hash( RomSM::compute_custom_trace_rom(elf, &mut custom_rom_trace); write_custom_commit_trace( + pctx, &mut custom_rom_trace, blowup_factor, merkle_tree_arity, diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index f2c94289b..c16f41415 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -97,8 +97,8 @@ impl ProverEngine for AsmProver { } fn setup(&self, elf: &impl ElfBinaryLike) -> Result { - let proving_key = self.core_prover.backend.get_proving_key_path(); - let (rom_bin_path, vk) = ensure_custom_commits(proving_key, elf)?; + let pctx = self.core_prover.backend.get_pctx()?; + let (rom_bin_path, vk) = ensure_custom_commits(&pctx, elf)?; let custom_commits_map = HashMap::from([("rom".to_string(), rom_bin_path)]); let default_cache_path = std::env::var("HOME") diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 6c716c3bb..6ab5bdb96 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -1,10 +1,10 @@ use crate::create_debug_info; use crate::ZiskPublics; +use crate::{ProofMode, ProofOpts}; use crate::{ - ensure_custom_commits, ZiskAggPhaseResult, ZiskExecuteResult, ZiskPhaseResult, ZiskProgramVK, - ZiskProof, ZiskProveResult, ZiskVerifyConstraintsResult, + ZiskAggPhaseResult, ZiskExecuteResult, ZiskPhaseResult, ZiskProgramVK, ZiskProof, + ZiskProveResult, ZiskVerifyConstraintsResult, }; -use crate::{ProofMode, ProofOpts}; use anyhow::Result; use colored::Colorize; use fields::Goldilocks; @@ -12,11 +12,13 @@ use proofman::{ get_vadcop_final_proof_vkey, verify_snark_proof, AggProofs, ProofInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProof, SnarkProtocol, SnarkWrapper, }; -use proofman_common::ProofOptions; +use proofman_common::{ProofCtx, ProofOptions}; use proofman_util::VadcopFinalProof; +use rom_setup::rom_merkle_setup_verkey; use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::path::PathBuf; +use std::sync::Arc; use std::sync::OnceLock; use zisk_common::{ io::{StreamSource, ZiskStdin}, @@ -62,8 +64,11 @@ impl ProverBackend { } } - pub fn get_proving_key_path(&self) -> &PathBuf { - &self.proving_key_path + pub fn get_pctx(&self) -> Result>> { + let proofman = self.proofman.as_ref().ok_or_else(|| { + anyhow::anyhow!("Proofman is not initialized. Please initialize it before use.") + })?; + Ok(proofman.get_wcm().get_pctx()) } pub fn register_witness_lib( @@ -259,7 +264,8 @@ impl ProverBackend { pub(crate) fn vk(&self, elf: &impl ElfBinaryLike) -> Result { let proving_key_path = self.proving_key_path.clone(); - let (_, vk) = ensure_custom_commits(&proving_key_path, elf)?; + + let vk = rom_merkle_setup_verkey(elf, &None, proving_key_path.as_path())?; Ok(ZiskProgramVK { vk }) } diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 660bfc6a4..012afc941 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -80,9 +80,9 @@ impl ProverEngine for EmuProver { } fn setup(&self, elf: &impl ElfBinaryLike) -> Result { - let proving_key = self.core_prover.backend.get_proving_key_path(); + let pctx = self.core_prover.backend.get_pctx()?; - let (rom_bin_path, vk) = ensure_custom_commits(proving_key, elf)?; + let (rom_bin_path, vk) = ensure_custom_commits(&pctx, elf)?; let custom_commits_map = HashMap::from([("rom".to_string(), rom_bin_path)]); // Build emulator library diff --git a/sdk/src/utils.rs b/sdk/src/utils.rs index 1ca3df953..c257a77b1 100644 --- a/sdk/src/utils.rs +++ b/sdk/src/utils.rs @@ -1,11 +1,11 @@ -use fields::Goldilocks; +use fields::PrimeField64; use std::collections::HashMap; use std::env; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use anyhow::Result; -use proofman_common::{json_to_debug_instances_map, DebugInfo, ProofmanResult}; +use proofman_common::{json_to_debug_instances_map, DebugInfo, ProofCtx, ProofmanResult}; use rom_setup::{get_elf_data_hash, rom_merkle_setup}; use zisk_common::ElfBinaryLike; @@ -76,18 +76,18 @@ pub fn get_proving_key_snark(proving_key_snark: Option<&PathBuf>) -> PathBuf { proving_key_snark.cloned().unwrap_or_else(get_default_proving_key_snark) } -pub fn ensure_custom_commits( - proving_key: &Path, +pub fn ensure_custom_commits( + pctx: &ProofCtx, elf: &impl ElfBinaryLike, ) -> Result<(PathBuf, Vec)> { - rom_merkle_setup::(elf, &None, proving_key) + rom_merkle_setup(pctx, elf, &None) } -pub fn get_custom_commits_map( - proving_key: &Path, +pub fn get_custom_commits_map( + pctx: &ProofCtx, elf: &impl ElfBinaryLike, ) -> Result> { - let (rom_bin_path, _) = ensure_custom_commits(proving_key, elf)?; + let (rom_bin_path, _) = ensure_custom_commits(pctx, elf)?; Ok(HashMap::from([("rom".to_string(), rom_bin_path)])) } diff --git a/test-verifier/.gitignore b/test-verifier/.gitignore deleted file mode 100644 index f2f9e58ec..000000000 --- a/test-verifier/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -target -Cargo.lock \ No newline at end of file diff --git a/test-verifier/Cargo.toml b/test-verifier/Cargo.toml deleted file mode 100644 index 7ef9ebebd..000000000 --- a/test-verifier/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "test-zisk-verifier" -version = "0.1.0" -edition = "2021" -default-run = "test-zisk-verifier" - -[dependencies] -ziskos = { path = "../ziskos/entrypoint" } -zisk-verifier = { path = "../verifier" } - -[profile.release.package."proofman-verifier"] -opt-level = 0 - -[workspace] \ No newline at end of file diff --git a/test-verifier/build/input.bin b/test-verifier/build/input.bin deleted file mode 100644 index ed06a2c7d44a8d811c6401343411a25f4bd6e394..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 341600 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 Vec { +pub(crate) fn read_input() -> Vec { read_input_slice().to_vec() } #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] -pub fn read_input() -> Vec { +pub(crate) fn read_input() -> Vec { use std::{fs::File, io::Read}; let mut file = @@ -62,7 +62,7 @@ pub fn read_input() -> Vec { } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -pub fn read_input_slice<'a>() -> &'a [u8] { +fn read_input_slice<'a>() -> &'a [u8] { // Create a slice of the first 8 bytes to get the size let bytes = unsafe { core::slice::from_raw_parts((INPUT_ADDR as *const u8).add(8), 8) }; // Convert the slice to a u64 (little-endian) @@ -71,8 +71,9 @@ pub fn read_input_slice<'a>() -> &'a [u8] { unsafe { core::slice::from_raw_parts((INPUT_ADDR as *const u8).add(16), size as usize) } } +#[allow(unused)] #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] -pub fn read_input_slice() -> Box<[u8]> { +fn read_input_slice() -> Box<[u8]> { read_input().into_boxed_slice() } From a694b1b0a5ee13401edebdf9c85d1b14c89d82f4 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Feb 2026 07:49:39 +0000 Subject: [PATCH 439/782] added secp256r1_ecdsa_verify hint handler --- cli/src/commands/rom_setup.rs | 21 ++++++----------- common/src/hints.rs | 12 ++++------ precompiles/hints/src/hints_processor.rs | 3 ++- ziskos-hints/src/handlers/mod.rs | 1 + ziskos-hints/src/handlers/secp256r1.rs | 29 ++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 ziskos-hints/src/handlers/secp256r1.rs diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index 9dc9b77b5..03b0637e1 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -20,7 +20,7 @@ use zisk_common::ElfBinaryOwned; pub struct ZiskRomSetup { /// ELF file path #[clap(short = 'e', long)] - pub elf_path: PathBuf, + pub elf: PathBuf, /// Setup folder path #[clap(short = 'k', long)] @@ -72,26 +72,19 @@ impl ZiskRomSetup { pctx.set_device_buffers(&sctx, &setups_vadcop, false, ¶ms_gpu)?; let pctx = Arc::new(pctx); - tracing::info!("Computing setup for ROM {}", self.elf_path.display()); + tracing::info!("Computing setup for ROM {}", self.elf.display()); tracing::info!("Computing merkle root"); - let elf_bin = fs::read(&self.elf_path).map_err(|e| { - anyhow::anyhow!("Error reading ELF file {}: {}", self.elf_path.display(), e) - })?; + let elf_bytes = fs::read(&self.elf) + .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; let elf = ElfBinaryOwned::new( - elf_bin, - self.elf_path.file_stem().unwrap().to_str().unwrap().to_string(), + elf_bytes, + self.elf.file_stem().unwrap().to_str().unwrap().to_string(), self.hints, ); rom_merkle_setup::(&pctx, &elf, &self.output_dir)?; - gen_assembly( - &self.elf_path, - &self.zisk_path, - &self.output_dir, - self.hints, - self.verbose > 0, - )?; + gen_assembly(&self.elf, &self.zisk_path, &self.output_dir, self.hints, self.verbose > 0)?; println!(); tracing::info!("{}", "ROM setup successfully completed".bright_green().bold()); diff --git a/common/src/hints.rs b/common/src/hints.rs index 5c73c8484..173e421bf 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -69,7 +69,7 @@ const HINT_BN254_PAIRING_CHECK: u32 = 0x0205; // Secp256k1 hint codes const HINT_SECP256K1_ECRECOVER: u32 = 0x0300; -const HINT_SECP256R1_VERIFY_SIGNATURE: u32 = 0x0301; +const HINT_SECP256R1_ECDSA_VERIFY: u32 = 0x0301; // BLS12-381 hint codes const HINT_BLS12_381_G1_ADD: u32 = 0x0400; @@ -149,7 +149,7 @@ pub enum BuiltInHint { /// secp256k1 ECDSA signature recovery. Secp256k1EcRecover = HINT_SECP256K1_ECRECOVER, /// secp256r1 (P-256) signature verification. - Secp256r1VerifySignature = HINT_SECP256R1_VERIFY_SIGNATURE, + Secp256r1EcdsaVerify = HINT_SECP256R1_ECDSA_VERIFY, // BLS12-381 hint types. /// BLS12-381 G1 addition (returns 96-byte unpadded G1 point) @@ -191,7 +191,7 @@ impl Display for BuiltInHint { BuiltInHint::Bn254PairingCheck => "BN254_PAIRING_CHECK", // Secp256k1 Hints BuiltInHint::Secp256k1EcRecover => "SECP256K1_ECRECOVER", - BuiltInHint::Secp256r1VerifySignature => "SECP256R1_VERIFY_SIGNATURE", + BuiltInHint::Secp256r1EcdsaVerify => "SECP256R1_ECDSA_VERIFY", // BLS12-381 Hints BuiltInHint::Bls12_381G1Add => "BLS12_381_G1_ADD", BuiltInHint::Bls12_381G1Msm => "BLS12_381_G1_MSM", @@ -225,7 +225,7 @@ impl TryFrom for BuiltInHint { HINT_BN254_PAIRING_CHECK => Ok(Self::Bn254PairingCheck), // Secp256k1 Hints HINT_SECP256K1_ECRECOVER => Ok(Self::Secp256k1EcRecover), - HINT_SECP256R1_VERIFY_SIGNATURE => Ok(Self::Secp256r1VerifySignature), + HINT_SECP256R1_ECDSA_VERIFY => Ok(Self::Secp256r1EcdsaVerify), // BLS12-381 Hints HINT_BLS12_381_G1_ADD => Ok(Self::Bls12_381G1Add), HINT_BLS12_381_G1_MSM => Ok(Self::Bls12_381G1Msm), @@ -304,9 +304,7 @@ impl HintCode { HintCode::BuiltIn(BuiltInHint::Bn254PairingCheck) => HINT_BN254_PAIRING_CHECK, // Secp256k1 Hints HintCode::BuiltIn(BuiltInHint::Secp256k1EcRecover) => HINT_SECP256K1_ECRECOVER, - HintCode::BuiltIn(BuiltInHint::Secp256r1VerifySignature) => { - HINT_SECP256R1_VERIFY_SIGNATURE - } + HintCode::BuiltIn(BuiltInHint::Secp256r1EcdsaVerify) => HINT_SECP256R1_ECDSA_VERIFY, // BLS12-381 Hints HintCode::BuiltIn(BuiltInHint::Bls12_381G1Add) => HINT_BLS12_381_G1_ADD, HintCode::BuiltIn(BuiltInHint::Bls12_381G1Msm) => HINT_BLS12_381_G1_MSM, diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 0024288ad..aaf22857e 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -26,6 +26,7 @@ use ziskos_hints::handlers::keccak256::keccak256_hint; use ziskos_hints::handlers::kzg::verify_kzg_proof_hint; use ziskos_hints::handlers::modexp::modexp_hint; use ziskos_hints::handlers::secp256k1::secp256k1_ecrecover_hint; +use ziskos_hints::handlers::secp256r1::secp256r1_ecdsa_verify_hint; use ziskos_hints::handlers::sha256::sha256_hint; /// Ordered result buffer with drain state. @@ -659,7 +660,7 @@ impl HintsProcessor { // Secp256k1 Hints BuiltInHint::Secp256k1EcRecover => secp256k1_ecrecover_hint(&data), - BuiltInHint::Secp256r1VerifySignature => unimplemented!(), + BuiltInHint::Secp256r1EcdsaVerify => secp256r1_ecdsa_verify_hint(&data), // BLS12-381 Hint Codes BuiltInHint::Bls12_381G1Add => bls12_381_g1_add_hint(&data), diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index b7534f9c9..6fb7411f1 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -4,6 +4,7 @@ pub mod keccak256; pub mod kzg; pub mod modexp; pub mod secp256k1; +pub mod secp256r1; pub mod sha256; /// Macro to generate size, offset, and expected length constants for hint data fields. diff --git a/ziskos-hints/src/handlers/secp256r1.rs b/ziskos-hints/src/handlers/secp256r1.rs new file mode 100644 index 000000000..23bc13b4f --- /dev/null +++ b/ziskos-hints/src/handlers/secp256r1.rs @@ -0,0 +1,29 @@ +use crate::handlers::validate_hint_length; +use crate::hint_fields; +use crate::zisklib; + +use anyhow::Result; + +/// Processes an `HINT_SECP256R1_VERIFY_SIGNATURE` hint. +#[inline] +pub fn secp256r1_ecdsa_verify_hint(data: &[u64]) -> Result> { + hint_fields![MSG: 4, SIG: 8, PK: 8]; + + validate_hint_length(data, EXPECTED_LEN, "HINT_SECP256R1_VERIFY_SIGNATURE")?; + + let msg: &[u64; MSG_SIZE] = data[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); + let sig: &[u64; SIG_SIZE] = data[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); + let pk: &[u64; PK_SIZE] = data[PK_OFFSET..PK_OFFSET + PK_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + unsafe { + zisklib::secp256r1_ecdsa_verify_c( + msg.as_ptr() as *const u8, + sig.as_ptr() as *const u8, + pk.as_ptr() as *const u8, + &mut hints, + ); + } + + Ok(hints) +} From 846979197f757f61dc506dcecc33bb386e1ad0f3 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 5 Feb 2026 10:20:54 +0100 Subject: [PATCH 440/782] Add hints to secp256r1 assembly code generator --- core/src/zisk_rom_2_asm.rs | 41 +++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 6464b4388..bd47128f1 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -268,6 +268,12 @@ impl ZiskAsmContext { pub fn precompile_results_secp256k1dbl(&self) -> bool { self.precompile_results() } + pub fn precompile_results_secp256r1add(&self) -> bool { + self.precompile_results() + } + pub fn precompile_results_secp256r1dbl(&self) -> bool { + self.precompile_results() + } pub fn precompile_results_fcall(&self) -> bool { self.precompile_results() } @@ -5839,12 +5845,18 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the secp256r1_add function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_secp256r1_add\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results_secp256r1add() { + *code += "\tmov rdi, [rdi]\n"; + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); + } else { + // Call the secp256r1_add function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_secp256r1_add\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads @@ -5939,12 +5951,17 @@ impl ZiskRom2Asm { if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Call the secp256r1_dbl function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_secp256r1_dbl\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Get result from precompile results data + if ctx.precompile_results_secp256r1dbl() { + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 8); + } else { + // Call the secp256r1_dbl function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_secp256r1_dbl\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // Consume mem reads From 0c58a0ed97f9c7901395ea4013389cbee515c763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Thu, 5 Feb 2026 12:23:10 +0100 Subject: [PATCH 441/782] Assembly names (#754) --- rom-setup/src/asm_setup.rs | 4 +++- sdk/src/utils.rs | 1 + ziskbuild/src/build.rs | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rom-setup/src/asm_setup.rs b/rom-setup/src/asm_setup.rs index a452783e6..6ad504296 100644 --- a/rom-setup/src/asm_setup.rs +++ b/rom-setup/src/asm_setup.rs @@ -9,10 +9,11 @@ use zisk_core::{is_elf_file, AsmGenerationMethod, Riscv2zisk}; use crate::get_elf_data_hash_from_path; /// Check if all assembly binary files exist for a given ELF and output path -pub fn assembly_files_exist(elf: &Path, output_path: &Path) -> Result { +pub fn assembly_files_exist(elf: &Path, output_path: &Path, hints: bool) -> Result { let elf_hash = get_elf_data_hash_from_path(elf)?; let stem = elf.file_stem().unwrap().to_str().unwrap(); + let stem = if hints { format!("{stem}-hints") } else { stem.to_string() }; let new_filename = format!("{stem}-{elf_hash}.tmp"); let base_path = output_path.join(new_filename); let file_stem = base_path.file_stem().unwrap().to_str().unwrap(); @@ -67,6 +68,7 @@ fn _generate_assembly( } let stem = elf.file_stem().unwrap().to_str().unwrap(); + let stem = if hints { format!("{stem}-hints") } else { stem.to_string() }; let new_filename = format!("{stem}-{elf_hash}.tmp"); let base_path = output_path.join(new_filename); let file_stem = base_path.file_stem().unwrap().to_str().unwrap(); diff --git a/sdk/src/utils.rs b/sdk/src/utils.rs index c257a77b1..eeab25582 100644 --- a/sdk/src/utils.rs +++ b/sdk/src/utils.rs @@ -93,6 +93,7 @@ pub fn get_custom_commits_map( pub fn get_asm_paths(elf: &impl ElfBinaryLike) -> Result<(String, String)> { let stem = elf.name(); + let stem = if elf.with_hints() { format!("{stem}-hints") } else { stem.to_string() }; let hash = get_elf_data_hash(elf).map_err(|e| anyhow::anyhow!("Error computing ELF hash: {}", e))?; diff --git a/ziskbuild/src/build.rs b/ziskbuild/src/build.rs index fc804925a..9e1272697 100644 --- a/ziskbuild/src/build.rs +++ b/ziskbuild/src/build.rs @@ -107,7 +107,7 @@ pub fn execute_build_program( for (_, elf_path) in target_elf_paths.iter() { let elf_path_std = elf_path.as_std_path(); - let assembly_exists = assembly_files_exist(elf_path_std, &output_path)?; + let assembly_exists = assembly_files_exist(elf_path_std, &output_path, hints)?; let hints_marker = output_path.join(format!( "{}.assembly_hints", elf_path_std.file_name().unwrap().to_string_lossy() From 49d018bfeaf207e1a623ee13ecdf442f7f4c3f93 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:24:05 +0100 Subject: [PATCH 442/782] Update ziskos-hints/src/handlers/secp256r1.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ziskos-hints/src/handlers/secp256r1.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ziskos-hints/src/handlers/secp256r1.rs b/ziskos-hints/src/handlers/secp256r1.rs index 23bc13b4f..b2973a0b3 100644 --- a/ziskos-hints/src/handlers/secp256r1.rs +++ b/ziskos-hints/src/handlers/secp256r1.rs @@ -4,12 +4,12 @@ use crate::zisklib; use anyhow::Result; -/// Processes an `HINT_SECP256R1_VERIFY_SIGNATURE` hint. +/// Processes an `HINT_SECP256R1_ECDSA_VERIFY` hint. #[inline] pub fn secp256r1_ecdsa_verify_hint(data: &[u64]) -> Result> { hint_fields![MSG: 4, SIG: 8, PK: 8]; - validate_hint_length(data, EXPECTED_LEN, "HINT_SECP256R1_VERIFY_SIGNATURE")?; + validate_hint_length(data, EXPECTED_LEN, "HINT_SECP256R1_ECDSA_VERIFY")?; let msg: &[u64; MSG_SIZE] = data[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); let sig: &[u64; SIG_SIZE] = data[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); From 29b1d8a32701ee9c133bbc4c08cc2aef730e18c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Thu, 5 Feb 2026 08:55:35 +0000 Subject: [PATCH 443/782] Fixing ecdsa over secp256k1 --- common/src/hints.rs | 12 +- precompiles/hints/src/hints_processor.rs | 4 +- ziskos-hints/src/handlers/secp256k1.rs | 11 +- .../src/zisklib/lib/array_lib/modexp.rs | 2 + .../src/zisklib/lib/secp256k1/constants.rs | 4 - .../src/zisklib/lib/secp256k1/curve.rs | 192 +-------- .../src/zisklib/lib/secp256k1/ecdsa.rs | 372 +++++++++++++++--- .../src/zisklib/lib/secp256k1/ecrecover.rs | 346 ---------------- .../src/zisklib/lib/secp256k1/field.rs | 261 +----------- .../src/zisklib/lib/secp256k1/mod.rs | 2 - .../src/zisklib/lib/secp256k1/scalar.rs | 233 +---------- 11 files changed, 343 insertions(+), 1096 deletions(-) delete mode 100644 ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs diff --git a/common/src/hints.rs b/common/src/hints.rs index 173e421bf..c0f7d4e03 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -45,7 +45,7 @@ //! For data hints, the hint code (32 bits) is structured as follows: //! - **Bit 31 (MSB)**: Indicates if the data is pass-through (1) or requires computation (0) //! - **Bits 0-30**: Encode the built-in hint code as defined in the constants -//! (e.g., `HINT_SHA256`, `HINT_BN254_G1_ADD`, `HINT_SECP256K1_ECRECOVER`, etc.) +//! (e.g., `HINT_SHA256`, `HINT_BN254_G1_ADD`, `HINT_SECP256K1_RECOVER`, etc.) //! ``` use std::fmt::Display; @@ -68,7 +68,7 @@ const HINT_BN254_G1_MUL: u32 = 0x0201; const HINT_BN254_PAIRING_CHECK: u32 = 0x0205; // Secp256k1 hint codes -const HINT_SECP256K1_ECRECOVER: u32 = 0x0300; +const HINT_SECP256K1_RECOVER: u32 = 0x0300; const HINT_SECP256R1_ECDSA_VERIFY: u32 = 0x0301; // BLS12-381 hint codes @@ -147,7 +147,7 @@ pub enum BuiltInHint { // Secp256k1 hint types. /// secp256k1 ECDSA signature recovery. - Secp256k1EcRecover = HINT_SECP256K1_ECRECOVER, + Secp256k1Recover = HINT_SECP256K1_RECOVER, /// secp256r1 (P-256) signature verification. Secp256r1EcdsaVerify = HINT_SECP256R1_ECDSA_VERIFY, @@ -190,7 +190,7 @@ impl Display for BuiltInHint { BuiltInHint::Bn254G1Mul => "BN254_G1_MUL", BuiltInHint::Bn254PairingCheck => "BN254_PAIRING_CHECK", // Secp256k1 Hints - BuiltInHint::Secp256k1EcRecover => "SECP256K1_ECRECOVER", + BuiltInHint::Secp256k1Recover => "SECP256K1_RECOVER", BuiltInHint::Secp256r1EcdsaVerify => "SECP256R1_ECDSA_VERIFY", // BLS12-381 Hints BuiltInHint::Bls12_381G1Add => "BLS12_381_G1_ADD", @@ -224,7 +224,7 @@ impl TryFrom for BuiltInHint { HINT_BN254_G1_MUL => Ok(Self::Bn254G1Mul), HINT_BN254_PAIRING_CHECK => Ok(Self::Bn254PairingCheck), // Secp256k1 Hints - HINT_SECP256K1_ECRECOVER => Ok(Self::Secp256k1EcRecover), + HINT_SECP256K1_RECOVER => Ok(Self::Secp256k1Recover), HINT_SECP256R1_ECDSA_VERIFY => Ok(Self::Secp256r1EcdsaVerify), // BLS12-381 Hints HINT_BLS12_381_G1_ADD => Ok(Self::Bls12_381G1Add), @@ -303,7 +303,7 @@ impl HintCode { HintCode::BuiltIn(BuiltInHint::Bn254G1Mul) => HINT_BN254_G1_MUL, HintCode::BuiltIn(BuiltInHint::Bn254PairingCheck) => HINT_BN254_PAIRING_CHECK, // Secp256k1 Hints - HintCode::BuiltIn(BuiltInHint::Secp256k1EcRecover) => HINT_SECP256K1_ECRECOVER, + HintCode::BuiltIn(BuiltInHint::Secp256k1Recover) => HINT_SECP256K1_RECOVER, HintCode::BuiltIn(BuiltInHint::Secp256r1EcdsaVerify) => HINT_SECP256R1_ECDSA_VERIFY, // BLS12-381 Hints HintCode::BuiltIn(BuiltInHint::Bls12_381G1Add) => HINT_BLS12_381_G1_ADD, diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index aaf22857e..8a7e76b48 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -25,7 +25,7 @@ use ziskos_hints::handlers::bn254::{ use ziskos_hints::handlers::keccak256::keccak256_hint; use ziskos_hints::handlers::kzg::verify_kzg_proof_hint; use ziskos_hints::handlers::modexp::modexp_hint; -use ziskos_hints::handlers::secp256k1::secp256k1_ecrecover_hint; +use ziskos_hints::handlers::secp256k1::secp256k1_recover_hint; use ziskos_hints::handlers::secp256r1::secp256r1_ecdsa_verify_hint; use ziskos_hints::handlers::sha256::sha256_hint; @@ -659,7 +659,7 @@ impl HintsProcessor { BuiltInHint::Bn254PairingCheck => bn254_pairing_check_hint(&data), // TODO: check // Secp256k1 Hints - BuiltInHint::Secp256k1EcRecover => secp256k1_ecrecover_hint(&data), + BuiltInHint::Secp256k1Recover => secp256k1_recover_hint(&data), BuiltInHint::Secp256r1EcdsaVerify => secp256r1_ecdsa_verify_hint(&data), // BLS12-381 Hint Codes diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index b159fb861..dea18360b 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -4,32 +4,29 @@ use crate::zisklib; use anyhow::Result; -/// Processes an `HINT_SECP256K1_ECRECOVER` hint. +/// Processes an `HINT_SECP256K1_RECOVER` hint. #[inline] -pub fn secp256k1_ecrecover_hint(data: &[u64]) -> Result> { +pub fn secp256k1_recover_hint(data: &[u64]) -> Result> { hint_fields![SIG: 64, RECID: 8, MSG: 32, LO_S: 8]; let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; - validate_hint_length(bytes, EXPECTED_LEN, "HINT_SECP256K1_ECRECOVER")?; + validate_hint_length(bytes, EXPECTED_LEN, "HINT_SECP256K1_RECOVER")?; let sig: &[u8; SIG_SIZE] = bytes[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); let recid: &[u8; RECID_SIZE] = bytes[RECID_OFFSET..RECID_OFFSET + RECID_SIZE].try_into().unwrap(); let recid: u8 = u64::from_le_bytes(*recid) as u8; let msg: &[u8; MSG_SIZE] = bytes[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); - let low_s: bool = - u64::from_le_bytes(bytes[LO_S_OFFSET..LO_S_OFFSET + LO_S_SIZE].try_into().unwrap()) != 0; let mut hints = Vec::new(); let result: &mut [u8; 32] = &mut [0u8; 32]; unsafe { - zisklib::secp256k1_ecrecover_c( + zisklib::secp256k1_ecdsa_address_recover_c( sig.as_ptr(), recid, msg.as_ptr(), result.as_mut_ptr(), - low_s, &mut hints, ); } diff --git a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs index 5b58d607d..41cdf3e40 100644 --- a/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/array_lib/modexp.rs @@ -150,6 +150,7 @@ fn modexp_short( #[cfg(feature = "hints")] hints, ); + // Recompose the exponent let bits_pos = len - 1 - bit_idx; let limb_idx = bits_pos / 64; @@ -226,6 +227,7 @@ fn modexp_long( #[cfg(feature = "hints")] hints, ); + // Recompose the exponent let bits_pos = len - 1 - bit_idx; let limb_idx = bits_pos / 64; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs index cbd2cde63..99520707f 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/constants.rs @@ -6,7 +6,6 @@ pub const E_B: [u64; 4] = [0x7, 0, 0, 0]; /// Secp256k1 base field size pub const P: [u64; 4] = [0xFFFFFFFEFFFFFC2F, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF]; -pub const P_MINUS_ONE: [u64; 4] = [P[0] - 1, P[1], P[2], P[3]]; /// A known non-quadratic residue in Fp pub const NQR: [u64; 4] = [3, 0, 0, 0]; @@ -15,9 +14,6 @@ pub const NQR: [u64; 4] = [3, 0, 0, 0]; pub const N: [u64; 4] = [0xBFD25E8CD0364141, 0xBAAEDCE6AF48A03B, 0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFFFF]; pub const N_MINUS_ONE: [u64; 4] = [N[0] - 1, N[1], N[2], N[3]]; -pub const N_HALF: [u64; 4] = - [0xDFE92F46681B20A0, 0x5D576E7357A4501D, 0xFFFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF]; -pub const N_HALF_PLUS_ONE: [u64; 4] = [N_HALF[0] + 1, N_HALF[1], N_HALF[2], N_HALF[3]]; /// Secp256k1 group identity point pub const IDENTITY_X: [u64; 4] = [0; 4]; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index 5d6d8f225..3b9e2ce54 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -1,5 +1,3 @@ -use std::hint; - use crate::{ syscalls::{ syscall_secp256k1_add, syscall_secp256k1_dbl, SyscallPoint256, SyscallSecp256k1AddParams, @@ -9,24 +7,20 @@ use crate::{ use super::{ constants::{E_B, G, G_X, G_Y, IDENTITY_X, IDENTITY_Y}, - field::{ - secp256k1_fp_add, secp256k1_fp_inv, secp256k1_fp_mul, secp256k1_fp_sqrt, - secp256k1_fp_square, - }, - scalar::{secp256k1_fn_inv, secp256k1_fn_mul, secp256k1_fn_reduce, secp256k1_fn_sub}, + field::{secp256k1_fp_add, secp256k1_fp_mul, secp256k1_fp_sqrt, secp256k1_fp_square}, + scalar::secp256k1_fn_sub, }; const IDENTITY_POINT256: SyscallPoint256 = SyscallPoint256 { x: IDENTITY_X, y: IDENTITY_Y }; const G_POINT256: SyscallPoint256 = SyscallPoint256 { x: G_X, y: G_Y }; -/// Given a x-coordinate `x_bytes` and a parity `y_is_odd`, -/// this function decompresses the point on the secp256k1 curve. -pub fn secp256k1_decompress( +/// Given a x-coordinate and a parity bit, returns the corresponding point (x, y) on the curve if it exists +pub fn secp256k1_lift_x( x: &[u64; 4], y_is_odd: bool, #[cfg(feature = "hints")] hints: &mut Vec, -) -> Result<([u64; 4], [u64; 4]), bool> { +) -> Result<[u64; 8], bool> { // Calculate the y-coordinate of the point: y = sqrt(x³ + 7) let x_sq = secp256k1_fp_square( x, @@ -60,7 +54,7 @@ pub fn secp256k1_decompress( let parity = (y[0] & 1) != 0; assert_eq!(parity, y_is_odd); - Ok((*x, y)) + Ok([x[0], x[1], x[2], x[3], y[0], y[1], y[2], y[3]]) } /// Checks whether the given point `p` is on the Secp256k1 curve. @@ -838,177 +832,3 @@ pub fn secp256k1_triple_scalar_mul_with_g( Some([res.x[0], res.x[1], res.x[2], res.x[3], res.y[0], res.y[1], res.y[2], res.y[3]]) } } - -// ==================== C FFI Functions ==================== - -/// Converts a non-infinity point `p` on the Secp256k1 curve from jacobian coordinates to affine coordinates -pub fn secp256k1_to_affine( - p: &[u64; 12], - #[cfg(feature = "hints")] hints: &mut Vec, -) -> [u64; 8] { - let z: [u64; 4] = [p[8], p[9], p[10], p[11]]; - - // Point at infinity cannot be converted to affine - debug_assert!(z != ZERO_256, "Cannot convert point at infinity to affine"); - - let zinv = secp256k1_fp_inv( - &z, - #[cfg(feature = "hints")] - hints, - ); - let zinv_sq = secp256k1_fp_square( - &zinv, - #[cfg(feature = "hints")] - hints, - ); - - let x: [u64; 4] = [p[0], p[1], p[2], p[3]]; - let y: [u64; 4] = [p[4], p[5], p[6], p[7]]; - - let x_res = secp256k1_fp_mul( - &x, - &zinv_sq, - #[cfg(feature = "hints")] - hints, - ); - let y_res = secp256k1_fp_mul( - &secp256k1_fp_mul( - &y, - &zinv_sq, - #[cfg(feature = "hints")] - hints, - ), - &zinv, - #[cfg(feature = "hints")] - hints, - ); - - [x_res[0], x_res[1], x_res[2], x_res[3], y_res[0], y_res[1], y_res[2], y_res[3]] -} - -/// # Safety -/// - `x_bytes_ptr` must point to 32 bytes (big-endian x-coordinate) -/// - `out_ptr` must point to at least 8 u64s -/// -/// Returns 1 on success, 0 if no valid point exists -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_decompress_c")] -pub unsafe extern "C" fn secp256k1_decompress_c( - x_bytes_ptr: *const u8, - y_is_odd: u8, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) -> u8 { - // Convert the x-coordinate from BEu8 to LEu64 - let x_bytes: &[u8; 32] = &*(x_bytes_ptr as *const [u8; 32]); - let x = bytes_be_to_u64_le(x_bytes); - - let (x, y) = match secp256k1_decompress( - &x, - y_is_odd != 0, - #[cfg(feature = "hints")] - hints, - ) { - Ok((x, y)) => (x, y), - Err(_) => return 0, - }; - - *out_ptr.add(0) = x[0]; - *out_ptr.add(1) = x[1]; - *out_ptr.add(2) = x[2]; - *out_ptr.add(3) = x[3]; - *out_ptr.add(4) = y[0]; - *out_ptr.add(5) = y[1]; - *out_ptr.add(6) = y[2]; - *out_ptr.add(7) = y[3]; - - 1 -} - -/// # Safety -/// - `p_ptr` must point to 12 u64s (jacobian point) -/// - `out_ptr` must point to at least 8 u64s -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_to_affine_c")] -pub unsafe extern "C" fn secp256k1_to_affine_c( - p_ptr: *const u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let p: &[u64; 12] = &*(p_ptr as *const [u64; 12]); - let result = secp256k1_to_affine( - p, - #[cfg(feature = "hints")] - hints, - ); - - *out_ptr.add(0) = result[0]; - *out_ptr.add(1) = result[1]; - *out_ptr.add(2) = result[2]; - *out_ptr.add(3) = result[3]; - *out_ptr.add(4) = result[4]; - *out_ptr.add(5) = result[5]; - *out_ptr.add(6) = result[6]; - *out_ptr.add(7) = result[7]; -} - -/// # Safety -/// - `k1_ptr`, `k2_ptr` must point to 4 u64s each -/// - `p_ptr` must point to 8 u64s -/// - `out_ptr` must point to at least 8 u64s -/// -/// Returns true if result is point at infinity -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_double_scalar_mul_with_g_c")] -pub unsafe extern "C" fn secp256k1_double_scalar_mul_with_g_c( - k1_ptr: *const u64, - k2_ptr: *const u64, - p_ptr: *const u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) -> bool { - let k1: &[u64; 4] = &*(k1_ptr as *const [u64; 4]); - let k2: &[u64; 4] = &*(k2_ptr as *const [u64; 4]); - let p: &[u64; 8] = &*(p_ptr as *const [u64; 8]); - - match secp256k1_double_scalar_mul_with_g( - k1, - k2, - p, - #[cfg(feature = "hints")] - hints, - ) { - None => true, - Some(res) => { - *out_ptr.add(0) = res[0]; - *out_ptr.add(1) = res[1]; - *out_ptr.add(2) = res[2]; - *out_ptr.add(3) = res[3]; - *out_ptr.add(4) = res[4]; - *out_ptr.add(5) = res[5]; - *out_ptr.add(6) = res[6]; - *out_ptr.add(7) = res[7]; - false - } - } -} - -// Helper to convert 32 big-endian bytes to [u64; 4] little-endian limbs -#[inline] -fn bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { - let mut result = [0u64; 4]; - for (i, r) in result.iter_mut().enumerate() { - let offset = 24 - i * 8; - *r = u64::from_be_bytes([ - bytes[offset], - bytes[offset + 1], - bytes[offset + 2], - bytes[offset + 3], - bytes[offset + 4], - bytes[offset + 5], - bytes[offset + 6], - bytes[offset + 7], - ]); - } - result -} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs index 720e5f3a8..fbbc3819d 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs @@ -1,29 +1,31 @@ -use crate::{ - syscalls::{ - syscall_secp256k1_add, syscall_secp256k1_dbl, SyscallPoint256, SyscallSecp256k1AddParams, - }, - zisklib::{ - eq, fcall_msb_pos_256, fcall_secp256k1_ecdsa_verify, is_one, ONE_256, TWO_256, ZERO_256, - }, -}; +use tiny_keccak::{Hasher, Keccak}; + +use crate::zisklib::{eq, fcall_secp256k1_ecdsa_verify, gt, ZERO_256}; use super::{ - constants::{E_B, G, G_X, G_Y, IDENTITY_X, IDENTITY_Y}, - curve::{ - secp256k1_decompress, secp256k1_double_scalar_mul_with_g, secp256k1_is_on_curve, - secp256k1_triple_scalar_mul_with_g, - }, - field::{ - secp256k1_fp_add, secp256k1_fp_inv, secp256k1_fp_mul, secp256k1_fp_sqrt, - secp256k1_fp_square, - }, - scalar::{ - secp256k1_fn_inv, secp256k1_fn_mul, secp256k1_fn_neg, secp256k1_fn_reduce, secp256k1_fn_sub, - }, + constants::N_MINUS_ONE, + curve::{secp256k1_is_on_curve, secp256k1_lift_x, secp256k1_triple_scalar_mul_with_g}, + scalar::{secp256k1_fn_neg, secp256k1_fn_reduce}, }; +// ECDSA verify result codes +pub const ECDSA_VERIFY_SUCCESS: u8 = 0; +pub const ECDSA_VERIFY_ERROR: u8 = 1; + +/// ECDSA recover result codes +pub const ECDSA_RECOVER_SUCCESS: u8 = 0; +pub const ECDSA_RECOVER_ERR_INVALID_R: u8 = 1; +pub const ECDSA_RECOVER_ERR_INVALID_S: u8 = 2; +pub const ECDSA_RECOVER_ERR_INVALID_RECID: u8 = 3; +pub const ECDSA_RECOVER_ERR_POINT_NOT_ON_CURVE: u8 = 4; +pub const ECDSA_RECOVER_ERR_RECOVERY_FAILED: u8 = 5; + /// Verifies the signature (r, s) over the message hash z using the public key pk -/// Returns true if the signature is valid, false otherwise +/// +/// # Returns +/// - 0 = valid signature +/// - 1 = public key not on curve +/// - 2 = invalid signature pub fn secp256k1_ecdsa_verify( pk: &[u64; 8], z: &[u64; 4], @@ -31,14 +33,23 @@ pub fn secp256k1_ecdsa_verify( s: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec, ) -> bool { - // Ecdsa verification computes (x, y) = [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]PK + // pk must be on the curve + if !secp256k1_is_on_curve( + pk, + #[cfg(feature = "hints")] + hints, + ) { + return false; + } + + // Ecdsa verification computes (x, y) = [z·s⁻¹ (mod n)]G + [r·s⁻¹ (mod n)]PK // and checks that x ≡ r (mod n) - // We can equivalently hint y, and verify that - // [z]G + [r]PK + [-s](r,y) == 𝒪, - // saving us from expensive fn arithmetic + // We can equivalently hint (x,y), verify that + // [z]G + [r]PK + [-s](x,y) == 𝒪, + // and ensure that x ≡ r (mod n), saving us from expensive fn arithmetic // Hint the result - let coords = fcall_secp256k1_ecdsa_verify( + let point = fcall_secp256k1_ecdsa_verify( pk, z, r, @@ -46,22 +57,24 @@ pub fn secp256k1_ecdsa_verify( #[cfg(feature = "hints")] hints, ); - let point = [r[0], r[1], r[2], r[3], coords[4], coords[5], coords[6], coords[7]]; // Check the recovered point is valid - assert!(secp256k1_is_on_curve( + // Note: Identity point would be raised here + if !secp256k1_is_on_curve( &point, #[cfg(feature = "hints")] hints, - )); // Note: Identity point would be raised here + ) { + return false; + } - // Check that [z]G + [r]PK + [-s](r,y) == 𝒪 + // Check that [z]G + [r]PK + [-s](x,y) == 𝒪 let neg_s = secp256k1_fn_neg( s, #[cfg(feature = "hints")] hints, ); - secp256k1_triple_scalar_mul_with_g( + if secp256k1_triple_scalar_mul_with_g( z, r, &neg_s, @@ -70,35 +83,290 @@ pub fn secp256k1_ecdsa_verify( #[cfg(feature = "hints")] hints, ) - .is_none() + .is_some() + { + return false; + } + + // Check that x ≡ r (mod n) + let point_x: [u64; 4] = [point[0], point[1], point[2], point[3]]; + eq( + &secp256k1_fn_reduce( + &point_x, + #[cfg(feature = "hints")] + hints, + ), + r, + ) +} + +/// Recover the public key point from an ECDSA signature (r, s) over the message hash z and recovery id recid +/// +/// # Returns +/// - 0 = success +/// - 1 = invalid r (not in [1, N)) +/// - 2 = invalid s (not in [1, N)) +/// - 3 = invalid recid (not 0 or 1) +/// - 4 = point not on curve +/// - 5 = recovery failed +pub fn secp256k1_ecdsa_recover( + r: &[u64; 4], + s: &[u64; 4], + z: &[u64; 4], + recid: u8, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> Result<[u64; 8], u8> { + // Validate r + if *r == ZERO_256 || gt(r, &N_MINUS_ONE) { + return Err(ECDSA_RECOVER_ERR_INVALID_R); + } + + // Validate s + if *s == ZERO_256 || gt(s, &N_MINUS_ONE) { + return Err(ECDSA_RECOVER_ERR_INVALID_S); + } + + // Validate recid + if recid > 1 { + return Err(ECDSA_RECOVER_ERR_INVALID_RECID); + } + + // Ecdsa recovery computes R = (x,y) and + // (xQ, yQ) = [-z·r⁻¹ (mod n)]G + [s·r⁻¹ (mod n)]R + // We can equivalently compute R, hint (xQ,yQ) and verify that + // [z]G + [-s]R + [r](xQ,yQ) == 𝒪, + // saving us from expensive fn arithmetic + + // Determine the x-coordinate of R + let x = *r; + + // Compute the y-coordinate from x and the parity bit + let y_is_odd = (recid & 1) == 1; + let r_point = secp256k1_lift_x( + &x, + y_is_odd, + #[cfg(feature = "hints")] + hints, + ) + .map_err(|_| ECDSA_RECOVER_ERR_POINT_NOT_ON_CURVE)?; + + // Check that [z]G + [-s]R + [r](xQ,yQ) == 𝒪 + + // Hint the result + // The following functions hints (x,y) satisfying + // [z]G + [r]R + [-s](x,y) == 𝒪 + // We can use it + let neg_s = secp256k1_fn_neg( + s, + #[cfg(feature = "hints")] + hints, + ); + let neg_r = secp256k1_fn_neg( + r, + #[cfg(feature = "hints")] + hints, + ); + let point = fcall_secp256k1_ecdsa_verify( + &r_point, + z, + &neg_s, + &neg_r, + #[cfg(feature = "hints")] + hints, + ); + + // Check the recovered point is valid + // Note: Identity point would be raised here + if !secp256k1_is_on_curve( + &point, + #[cfg(feature = "hints")] + hints, + ) { + return Err(ECDSA_RECOVER_ERR_RECOVERY_FAILED); + } + + // Check that [z]G + [-s]R + [r](xQ,yQ) == 𝒪 + if secp256k1_triple_scalar_mul_with_g( + z, + &neg_s, + r, + &r_point, + &point, + #[cfg(feature = "hints")] + hints, + ) + .is_some() + { + return Err(ECDSA_RECOVER_ERR_RECOVERY_FAILED); + } + + // Return the recovered public key + Ok(point) } // ==================== C FFI Functions ==================== +/// C-compatible wrapper for secp256k1_ecdsa_verify and address recovery +/// /// # Safety -/// - `pk_ptr` must point to 8 u64s -/// - `z_ptr`, `r_ptr`, `s_ptr` must point to 4 u64s each +/// - `sig` must point to at least 64 bytes (r || s, big-endian) +/// - `msg` must point to at least 32 bytes (message hash, big-endian) +/// - `pk` must point to at least 64 bytes (x || y, big-endian) +/// - `output` must point to a writable buffer of at least 32 bytes +/// +/// # Arguments +/// - `sig` - 64 bytes: r (32 bytes) || s (32 bytes), big-endian +/// - `msg` - 32 bytes message hash, big-endian +/// - `pk` - 64 bytes: x (32 bytes) || y (32 bytes), big-endian +/// - `output` - Output buffer for the recovered address (32 bytes) /// -/// Returns true if signature is valid +/// # Returns +/// - `Ok([u8; 32])` - Recovered address if signature is valid +/// - `Err(u8)` - Error code #[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_ecdsa_verify_c")] -pub unsafe extern "C" fn secp256k1_ecdsa_verify_c( - pk_ptr: *const u64, - z_ptr: *const u64, - r_ptr: *const u64, - s_ptr: *const u64, +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_ecdsa_verify_and_address_recover_c")] +pub unsafe extern "C" fn secp256k1_ecdsa_verify_and_address_recover_c( + sig: *const u8, + msg: *const u8, + pk: *const u8, + output: *mut u8, #[cfg(feature = "hints")] hints: &mut Vec, -) -> bool { - let pk: &[u64; 8] = &*(pk_ptr as *const [u64; 8]); - let z: &[u64; 4] = &*(z_ptr as *const [u64; 4]); - let r: &[u64; 4] = &*(r_ptr as *const [u64; 4]); - let s: &[u64; 4] = &*(s_ptr as *const [u64; 4]); - secp256k1_ecdsa_verify( - pk, - z, - r, - s, +) -> u8 { + let sig_bytes: &[u8; 64] = &*(sig as *const [u8; 64]); + let msg_bytes: &[u8; 32] = &*(msg as *const [u8; 32]); + let pk_bytes: &[u8; 64] = &*(pk as *const [u8; 64]); + let output_bytes: &mut [u8; 32] = &mut *(output as *mut [u8; 32]); + + // Parse r, s from big-endian bytes + let r_bytes: [u8; 32] = sig_bytes[0..32].try_into().unwrap(); + let s_bytes: [u8; 32] = sig_bytes[32..64].try_into().unwrap(); + + // Parse pk_x, pk_y from big-endian bytes + let pk_x_bytes: [u8; 32] = pk_bytes[0..32].try_into().unwrap(); + let pk_y_bytes: [u8; 32] = pk_bytes[32..64].try_into().unwrap(); + + // Convert to little-endian u64 limbs + let r = bytes_be_to_u64_le(&r_bytes); + let s = bytes_be_to_u64_le(&s_bytes); + let z = bytes_be_to_u64_le(msg_bytes); + let pk_x = bytes_be_to_u64_le(&pk_x_bytes); + let pk_y = bytes_be_to_u64_le(&pk_y_bytes); + + let pk_arr: [u64; 8] = [pk_x[0], pk_x[1], pk_x[2], pk_x[3], pk_y[0], pk_y[1], pk_y[2], pk_y[3]]; + if secp256k1_ecdsa_verify( + &pk_arr, + &z, + &r, + &s, #[cfg(feature = "hints")] hints, - ) + ) { + // Signature is valid - compute and return the address from the public key + let address = pubkey_to_address(&pk_arr); + output_bytes.copy_from_slice(&address); + ECDSA_VERIFY_SUCCESS + } else { + ECDSA_VERIFY_ERROR + } +} + +/// C-compatible wrapper for secp256k1_ecdsa_recover +/// +/// # Safety +/// - `sig` must point to at least 64 bytes (r || s, big-endian) +/// - `msg` must point to at least 32 bytes (message hash, big-endian) +/// - `output` must point to a writable buffer of at least 32 bytes +/// +/// # Arguments +/// - `sig` - 64 bytes: r (32 bytes) || s (32 bytes), big-endian +/// - `recid` - Recovery ID (0 or 1) +/// - `msg` - 32 bytes message hash, big-endian +/// - `output` - Output buffer for the recovered address (32 bytes) +/// +/// # Returns +/// - `Ok([u32; 8])` - Recovered address if recovery is successful +/// - `Err(u8)` - Error code +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_ecdsa_address_recover_c")] +pub unsafe extern "C" fn secp256k1_ecdsa_address_recover_c( + sig: *const u8, + recid: u8, + msg: *const u8, + output: *mut u8, + #[cfg(feature = "hints")] hints: &mut Vec, +) -> u8 { + let sig_bytes: &[u8; 64] = &*(sig as *const [u8; 64]); + let msg_bytes: &[u8; 32] = &*(msg as *const [u8; 32]); + let output_bytes: &mut [u8; 32] = &mut *(output as *mut [u8; 32]); + + // Parse r, s, z from big-endian bytes + let r_bytes: [u8; 32] = sig_bytes[0..32].try_into().unwrap(); + let s_bytes: [u8; 32] = sig_bytes[32..64].try_into().unwrap(); + + let r = bytes_be_to_u64_le(&r_bytes); + let s = bytes_be_to_u64_le(&s_bytes); + let z = bytes_be_to_u64_le(msg_bytes); + + // Perform ecrecover + match secp256k1_ecdsa_recover( + &r, + &s, + &z, + recid, + #[cfg(feature = "hints")] + hints, + ) { + Ok(pk) => { + let result = pubkey_to_address(&pk); + output_bytes.copy_from_slice(&result); + ECDSA_RECOVER_SUCCESS + } + Err(code) => code, + } +} + +/// Convert big-endian bytes to little-endian u64 limbs (32 bytes -> [u64; 4]) +fn bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { + let mut result = [0u64; 4]; + for i in 0..4 { + for j in 0..8 { + result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); + } + } + result +} + +fn u64_le_to_bytes_be(limbs: &[u64; 4]) -> [u8; 32] { + let mut result = [0u8; 32]; + for i in 0..4 { + for j in 0..8 { + result[i * 8 + j] = ((limbs[3 - i] >> (8 * (7 - j))) & 0xff) as u8; + } + } + result +} + +fn pubkey_to_address(pk: &[u64; 8]) -> [u8; 32] { + let x = [pk[0], pk[1], pk[2], pk[3]]; + let y = [pk[4], pk[5], pk[6], pk[7]]; + + let x_bytes = u64_le_to_bytes_be(&x); + let y_bytes = u64_le_to_bytes_be(&y); + + // Concatenate x and y + let mut pk_bytes = [0u8; 64]; + pk_bytes[0..32].copy_from_slice(&x_bytes); + pk_bytes[32..64].copy_from_slice(&y_bytes); + + // Hash with keccak256 + let mut hasher = Keccak::v256(); + hasher.update(&pk_bytes); + let mut hash = [0u8; 32]; + hasher.finalize(&mut hash); + + // Ethereum address is last 20 bytes + let mut result = [0u8; 32]; + result[12..32].copy_from_slice(&hash[12..32]); + + result } diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs deleted file mode 100644 index d48e6d4a6..000000000 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecrecover.rs +++ /dev/null @@ -1,346 +0,0 @@ -use crate::{ - syscalls::{ - syscall_secp256k1_add, syscall_secp256k1_dbl, SyscallPoint256, SyscallSecp256k1AddParams, - }, - zisklib::{ - eq, fcall_msb_pos_256, fcall_secp256k1_ecdsa_verify, is_one, lt, ONE_256, TWO_256, ZERO_256, - }, -}; - -use super::{ - constants::{E_B, G, G_X, G_Y, IDENTITY_X, IDENTITY_Y, N, N_HALF_PLUS_ONE, P}, - curve::{ - secp256k1_decompress, secp256k1_double_scalar_mul_with_g, secp256k1_is_on_curve, - secp256k1_scalar_mul, secp256k1_triple_scalar_mul_with_g, - }, - field::{ - secp256k1_fp_add, secp256k1_fp_inv, secp256k1_fp_mul, secp256k1_fp_sqrt, - secp256k1_fp_square, - }, - scalar::{ - secp256k1_fn_add, secp256k1_fn_inv, secp256k1_fn_mul, secp256k1_fn_neg, - secp256k1_fn_reduce, secp256k1_fn_sub, - }, -}; - -use tiny_keccak::{Hasher, Keccak}; - -/// Ecrecover result codes -pub const ECRECOVER_SUCCESS: u8 = 0; -pub const ECRECOVER_ERR_INVALID_R: u8 = 1; -pub const ECRECOVER_ERR_INVALID_S: u8 = 2; -pub const ECRECOVER_ERR_INVALID_RECID: u8 = 3; -pub const ECRECOVER_ERR_POINT_NOT_ON_CURVE: u8 = 4; -pub const ECRECOVER_ERR_RECOVERY_FAILED: u8 = 5; - -/// Recover the public key point from an ECDSA signature -/// -/// The recovery formula is: -/// R = (r, y) where y is recovered from r using the curve equation -/// PK = r⁻¹ * (s*R - z*G) = u1*G + u2*R where u1 = -z*r⁻¹ and u2 = s*r⁻¹ -/// -/// # Arguments -/// * `r` - Signature r value as [u64; 4] little-endian -/// * `s` - Signature s value as [u64; 4] little-endian -/// * `z` - Message hash as [u64; 4] little-endian -/// * `recid` - Recovery ID (0, 1, 2, or 3) -/// -/// # Returns -/// * `Ok([u64; 8])` - Recovered public key (x, y coordinates) -/// * `Err(u8)` - Error code -// TODO: Use triple scalar mul! -pub fn secp256k1_ecrecover_point( - r: &[u64; 4], - s: &[u64; 4], - z: &[u64; 4], - recid: u8, - require_low_s: bool, - #[cfg(feature = "hints")] hints: &mut Vec, -) -> Result<[u64; 8], u8> { - // Validate recid - // The recid is a value in the range [0, 3] - // However, the upper two values (2 and 3) are, representing infinity values, invalid - if recid > 1 { - return Err(ECRECOVER_ERR_INVALID_RECID); - } - - // Validate r - if !is_valid_r(r) { - return Err(ECRECOVER_ERR_INVALID_R); - } - - // Validate s - if require_low_s { - if !is_valid_s_low(s) { - return Err(ECRECOVER_ERR_INVALID_S); - } - } else if !is_valid_s(s) { - return Err(ECRECOVER_ERR_INVALID_S); - } - - // Determine the x-coordinate of R - let x = *r; - - // Recover the y-coordinate from x - // y² = x³ + 7 - let y_is_odd = (recid & 1) == 1; - let (rx, ry) = secp256k1_decompress( - &x, - y_is_odd, - #[cfg(feature = "hints")] - hints, - ) - .map_err(|_| ECRECOVER_ERR_POINT_NOT_ON_CURVE)?; - - let r_point = [rx[0], rx[1], rx[2], rx[3], ry[0], ry[1], ry[2], ry[3]]; - - // Compute r_inv = r⁻¹ (mod N) - let r_inv = secp256k1_fn_inv( - r, - #[cfg(feature = "hints")] - hints, - ); - - // Compute u1 = -z * r_inv (mod N) - let neg_z = secp256k1_fn_neg( - z, - #[cfg(feature = "hints")] - hints, - ); - let u1 = secp256k1_fn_mul( - &neg_z, - &r_inv, - #[cfg(feature = "hints")] - hints, - ); - - // Compute u2 = s * r_inv (mod N) - let u2 = secp256k1_fn_mul( - s, - &r_inv, - #[cfg(feature = "hints")] - hints, - ); - - // Compute PK = u1*G + u2*R - let pk = secp256k1_double_scalar_mul_with_g( - &u1, - &u2, - &r_point, - #[cfg(feature = "hints")] - hints, - ) - .ok_or(ECRECOVER_ERR_RECOVERY_FAILED)?; - - Ok(pk) -} - -/// Recover the Ethereum address from an ECDSA signature (for precompile) -/// -/// # Arguments -/// * `r` - Signature r value as [u64; 4] little-endian -/// * `s` - Signature s value as [u64; 4] little-endian -/// * `z` - Message hash as [u64; 4] little-endian -/// * `recid` - Recovery ID (0 or 1) -/// -/// # Returns -/// * `Ok([u8; 32])` - First 12 bytes are 0, last 20 bytes are the Ethereum address -/// * `Err(u8)` - Error code -pub fn secp256k1_ecrecover( - r: &[u64; 4], - s: &[u64; 4], - z: &[u64; 4], - recid: u8, - #[cfg(feature = "hints")] hints: &mut Vec, -) -> Result<[u8; 32], u8> { - // Recover the public key point - let pk = secp256k1_ecrecover_point( - r, - s, - z, - recid, - false, - #[cfg(feature = "hints")] - hints, - )?; - - Ok(pubkey_to_address(&pk)) -} - -/// Recover the Ethereum address from an ECDSA signature (for tx recovery with low S) -/// -/// # Arguments -/// * `r` - Signature r value as [u64; 4] little-endian -/// * `s` - Signature s value as [u64; 4] little-endian -/// * `z` - Message hash as [u64; 4] little-endian -/// * `recid` - Recovery ID (0 or 1) -/// -/// # Returns -/// * `Ok([u8; 32])` - First 12 bytes are 0, last 20 bytes are the Ethereum address -/// * `Err(u8)` - Error code -pub fn secp256k1_ecrecover_tx( - r: &[u64; 4], - s: &[u64; 4], - z: &[u64; 4], - recid: u8, - #[cfg(feature = "hints")] hints: &mut Vec, -) -> Result<[u8; 32], u8> { - // Recover the public key point - let pk = secp256k1_ecrecover_point( - r, - s, - z, - recid, - true, - #[cfg(feature = "hints")] - hints, - )?; - - Ok(pubkey_to_address(&pk)) -} - -/// Convert a public key point to an Ethereum address -fn pubkey_to_address(pk: &[u64; 8]) -> [u8; 32] { - let x = [pk[0], pk[1], pk[2], pk[3]]; - let y = [pk[4], pk[5], pk[6], pk[7]]; - - let x_bytes = u64_le_to_bytes_be(&x); - let y_bytes = u64_le_to_bytes_be(&y); - - // Concatenate x and y for hashing (64 bytes) - let mut pk_bytes = [0u8; 64]; - pk_bytes[0..32].copy_from_slice(&x_bytes); - pk_bytes[32..64].copy_from_slice(&y_bytes); - - // Hash with keccak256 - let mut hasher = Keccak::v256(); - hasher.update(&pk_bytes); - let mut hash = [0u8; 32]; - hasher.finalize(&mut hash); - - // Return with first 12 bytes zeroed (Ethereum address is last 20 bytes) - let mut result = [0u8; 32]; - result[12..32].copy_from_slice(&hash[12..32]); - - result -} - -/// C-compatible wrapper for secp256k1_ecrecover -/// -/// # Safety -/// - `sig` must point to at least 64 bytes (r || s, big-endian) -/// - `msg` must point to at least 32 bytes (message hash, big-endian) -/// - `output` must point to a writable buffer of at least 32 bytes -/// -/// # Arguments -/// - `sig` - 64 bytes: r (32 bytes) || s (32 bytes), big-endian -/// - `recid` - Recovery ID (0 or 1) -/// - `msg` - 32 bytes message hash, big-endian -/// - `output` - Output buffer for the recovered address (32 bytes) -/// - `require_low_s` - If true, require s <= N/2 (for tx recovery); if false, allow s < N (for precompile) -/// -/// # Returns -/// - 0 = success -/// - 1 = invalid r (not in [1, N)) -/// - 2 = invalid s (not in [1, N) or [1, N/2] depending on require_low_s) -/// - 3 = invalid recid (not 0 or 1) -/// - 4 = point not on curve (no valid y for given r) -/// - 5 = recovery failed (result is point at infinity) -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_ecrecover_c")] -pub unsafe extern "C" fn secp256k1_ecrecover_c( - sig: *const u8, - recid: u8, - msg: *const u8, - output: *mut u8, - require_low_s: bool, - #[cfg(feature = "hints")] hints: &mut Vec, -) -> u8 { - let sig_bytes: &[u8; 64] = &*(sig as *const [u8; 64]); - let msg_bytes: &[u8; 32] = &*(msg as *const [u8; 32]); - let output_bytes: &mut [u8; 32] = &mut *(output as *mut [u8; 32]); - - // Parse r, s, z from big-endian bytes - let r_bytes: [u8; 32] = sig_bytes[0..32].try_into().unwrap(); - let s_bytes: [u8; 32] = sig_bytes[32..64].try_into().unwrap(); - - let r = bytes_be_to_u64_le(&r_bytes); - let s = bytes_be_to_u64_le(&s_bytes); - let z = bytes_be_to_u64_le(msg_bytes); - - // Perform ecrecover - match secp256k1_ecrecover_point( - &r, - &s, - &z, - recid, - require_low_s, - #[cfg(feature = "hints")] - hints, - ) { - Ok(pk) => { - let result = pubkey_to_address(&pk); - output_bytes.copy_from_slice(&result); - ECRECOVER_SUCCESS - } - Err(code) => { - output_bytes.fill(0); - code - } - } -} - -/// Convert big-endian bytes to little-endian u64 limbs (32 bytes -> [u64; 4]) -fn bytes_be_to_u64_le(bytes: &[u8; 32]) -> [u64; 4] { - let mut result = [0u64; 4]; - for i in 0..4 { - for j in 0..8 { - result[3 - i] |= (bytes[i * 8 + j] as u64) << (8 * (7 - j)); - } - } - result -} - -/// Convert little-endian u64 limbs to big-endian bytes ([u64; 4] -> 32 bytes) -fn u64_le_to_bytes_be(limbs: &[u64; 4]) -> [u8; 32] { - let mut result = [0u8; 32]; - for i in 0..4 { - for j in 0..8 { - result[i * 8 + j] = ((limbs[3 - i] >> (8 * (7 - j))) & 0xff) as u8; - } - } - result -} - -/// Check if r is valid: 0 < r < N -fn is_valid_r(r: &[u64; 4]) -> bool { - if *r == ZERO_256 { - return false; - } - if lt(r, &N) { - return true; - } - false -} - -/// Check if s is valid for precompile: 0 < s < N -fn is_valid_s(s: &[u64; 4]) -> bool { - if *s == ZERO_256 { - return false; - } - if lt(s, &N) { - return true; - } - false -} - -/// Check if s is valid for tx recovery (low S): 0 < s <= N/2 -fn is_valid_s_low(s: &[u64; 4]) -> bool { - if *s == ZERO_256 { - return false; - } - if lt(s, &N_HALF_PLUS_ONE) { - return true; - } - false -} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs index 5b7736c7f..9681bcdbe 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/field.rs @@ -1,33 +1,9 @@ use crate::{ syscalls::{syscall_arith256_mod, SyscallArith256ModParams}, - zisklib::{fcall_secp256k1_fp_inv, fcall_secp256k1_fp_sqrt, lt}, + zisklib::fcall_secp256k1_fp_sqrt, }; -use super::constants::{NQR, P, P_MINUS_ONE}; - -pub fn secp256k1_fp_reduce( - x: &[u64; 4], - #[cfg(feature = "hints")] hints: &mut Vec, -) -> [u64; 4] { - if lt(x, &P) { - return *x; - } - - // x·1 + 0 - let mut params = SyscallArith256ModParams { - a: x, - b: &[1, 0, 0, 0], - c: &[0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - *params.d -} +use super::constants::{NQR, P}; pub fn secp256k1_fp_add( x: &[u64; 4], @@ -45,27 +21,6 @@ pub fn secp256k1_fp_add( *params.d } -pub fn secp256k1_fp_negate( - x: &[u64; 4], - #[cfg(feature = "hints")] hints: &mut Vec, -) -> [u64; 4] { - // x·(-1) + 0 - let mut params = SyscallArith256ModParams { - a: x, - b: &P_MINUS_ONE, - c: &[0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *params.d -} - pub fn secp256k1_fp_mul( x: &[u64; 4], y: &[u64; 4], @@ -82,28 +37,6 @@ pub fn secp256k1_fp_mul( *params.d } -pub fn secp256k1_fp_mul_scalar( - x: &[u64; 4], - scalar: u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) -> [u64; 4] { - // x·scalar + 0 - let mut params = SyscallArith256ModParams { - a: x, - b: &[scalar, 0, 0, 0], - c: &[0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *params.d -} - pub fn secp256k1_fp_square( x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec, @@ -119,33 +52,6 @@ pub fn secp256k1_fp_square( *params.d } -/// Inverts a non-zero element `x` -pub fn secp256k1_fp_inv(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { - // Hint the inverse - let x_inv = fcall_secp256k1_fp_inv( - x, - #[cfg(feature = "hints")] - hints, - ); - - // Check that x·x_inv = 1 (P) - let mut params = SyscallArith256ModParams { - a: x, - b: &x_inv, - c: &[0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - assert_eq!(*params.d, [0x1, 0x0, 0x0, 0x0]); - - x_inv -} - pub fn secp256k1_fp_sqrt( x: &[u64; 4], parity: u64, @@ -191,166 +97,3 @@ pub fn secp256k1_fp_sqrt( (sqrt, false) } } - -// ==================== C FFI Functions ==================== - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_reduce_c")] -pub unsafe extern "C" fn secp256k1_fp_reduce_c( - x_ptr: *const u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - if lt(x, &P) { - *out_ptr.add(0) = x[0]; - *out_ptr.add(1) = x[1]; - *out_ptr.add(2) = x[2]; - *out_ptr.add(3) = x[3]; - return; - } - - let mut params = SyscallArith256ModParams { - a: x, - b: &[1, 0, 0, 0], - c: &[0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `y_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_add_c")] -pub unsafe extern "C" fn secp256k1_fp_add_c( - x_ptr: *const u64, - y_ptr: *const u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); - - let mut params = - SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_negate_c")] -pub unsafe extern "C" fn secp256k1_fp_negate_c( - x_ptr: *const u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: x, - b: &P_MINUS_ONE, - c: &[0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `y_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_mul_c")] -pub unsafe extern "C" fn secp256k1_fp_mul_c( - x_ptr: *const u64, - y_ptr: *const u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); - - let mut params = - SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &P, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `scalar` is a single u64 value -/// - `out_ptr` must point to at least 4 u64s -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fp_mul_scalar_c")] -pub unsafe extern "C" fn secp256k1_fp_mul_scalar_c( - x_ptr: *const u64, - scalar: u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: x, - b: &[scalar, 0, 0, 0], - c: &[0, 0, 0, 0], - module: &P, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs index d315cf963..794d231fd 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/mod.rs @@ -1,12 +1,10 @@ mod constants; mod curve; mod ecdsa; -mod ecrecover; mod field; mod scalar; pub use curve::*; pub use ecdsa::*; -pub use ecrecover::*; pub use field::*; pub use scalar::*; diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs index 4e8023dd3..0c93c694d 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/scalar.rs @@ -1,6 +1,6 @@ use crate::{ syscalls::{syscall_arith256_mod, SyscallArith256ModParams}, - zisklib::{fcall_secp256k1_fn_inv, lt}, + zisklib::lt, }; use super::constants::{N, N_MINUS_ONE}; @@ -30,23 +30,6 @@ pub fn secp256k1_fn_reduce( *params.d } -pub fn secp256k1_fn_add( - x: &[u64; 4], - y: &[u64; 4], - #[cfg(feature = "hints")] hints: &mut Vec, -) -> [u64; 4] { - // x·1 + y - let mut params = - SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *params.d -} - pub fn secp256k1_fn_neg(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { // x·(-1) + 0 let mut params = SyscallArith256ModParams { @@ -81,217 +64,3 @@ pub fn secp256k1_fn_sub( *params.d } - -pub fn secp256k1_fn_mul( - x: &[u64; 4], - y: &[u64; 4], - #[cfg(feature = "hints")] hints: &mut Vec, -) -> [u64; 4] { - // x·y + 0 - let mut params = - SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *params.d -} - -/// Inverts a non-zero element `x` -pub fn secp256k1_fn_inv(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { - // Hint the inverse - let x_inv = fcall_secp256k1_fn_inv( - x, - #[cfg(feature = "hints")] - hints, - ); - - // Check that x·x_inv = 1 (N) - let mut params = SyscallArith256ModParams { - a: x, - b: &x_inv, - c: &[0, 0, 0, 0], - module: &N, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - assert_eq!(*params.d, [0x1, 0x0, 0x0, 0x0]); - - x_inv -} - -// ==================== C FFI Functions ==================== - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_reduce_c")] -pub unsafe extern "C" fn secp256k1_fn_reduce_c( - x_ptr: *const u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - if lt(x, &N) { - *out_ptr.add(0) = x[0]; - *out_ptr.add(1) = x[1]; - *out_ptr.add(2) = x[2]; - *out_ptr.add(3) = x[3]; - return; - } - - let mut params = SyscallArith256ModParams { - a: x, - b: &[1, 0, 0, 0], - c: &[0, 0, 0, 0], - module: &N, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `y_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_add_c")] -pub unsafe extern "C" fn secp256k1_fn_add_c( - x_ptr: *const u64, - y_ptr: *const u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); - - let mut params = - SyscallArith256ModParams { a: x, b: &[1, 0, 0, 0], c: y, module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_neg_c")] -pub unsafe extern "C" fn secp256k1_fn_neg_c( - x_ptr: *const u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - let mut params = SyscallArith256ModParams { - a: x, - b: &N_MINUS_ONE, - c: &[0, 0, 0, 0], - module: &N, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s -/// - `y_ptr` must point to 4 u64s -/// - `out_ptr` must point to at least 4 u64s -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_mul_c")] -pub unsafe extern "C" fn secp256k1_fn_mul_c( - x_ptr: *const u64, - y_ptr: *const u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - let y: &[u64; 4] = &*(y_ptr as *const [u64; 4]); - - let mut params = - SyscallArith256ModParams { a: x, b: y, c: &[0, 0, 0, 0], module: &N, d: &mut [0, 0, 0, 0] }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - - *out_ptr.add(0) = params.d[0]; - *out_ptr.add(1) = params.d[1]; - *out_ptr.add(2) = params.d[2]; - *out_ptr.add(3) = params.d[3]; -} - -/// # Safety -/// - `x_ptr` must point to 4 u64s (non-zero element) -/// - `out_ptr` must point to at least 4 u64s -#[cfg_attr(not(feature = "hints"), no_mangle)] -#[cfg_attr(feature = "hints", export_name = "hints_secp256k1_fn_inv_c")] -pub unsafe extern "C" fn secp256k1_fn_inv_c( - x_ptr: *const u64, - out_ptr: *mut u64, - #[cfg(feature = "hints")] hints: &mut Vec, -) { - let x: &[u64; 4] = &*(x_ptr as *const [u64; 4]); - - // Hint the inverse - let x_inv = fcall_secp256k1_fn_inv( - x, - #[cfg(feature = "hints")] - hints, - ); - - // Check that x·x_inv = 1 (N) - let mut params = SyscallArith256ModParams { - a: x, - b: &x_inv, - c: &[0, 0, 0, 0], - module: &N, - d: &mut [0, 0, 0, 0], - }; - syscall_arith256_mod( - &mut params, - #[cfg(feature = "hints")] - hints, - ); - assert_eq!(*params.d, [0x1, 0x0, 0x0, 0x0]); - - *out_ptr.add(0) = x_inv[0]; - *out_ptr.add(1) = x_inv[1]; - *out_ptr.add(2) = x_inv[2]; - *out_ptr.add(3) = x_inv[3]; -} From 17f801fb0dfa4f96b775e421d9ecadfb32dc9c7f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:45:11 +0000 Subject: [PATCH 444/782] cargo clippy --- ziskos-hints/src/handlers/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 6fb7411f1..34e2feabe 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -35,6 +35,7 @@ macro_rules! hint_fields { (@offsets $offset:expr, $name:ident: $size:expr) => { paste::paste! { + #[allow(dead_code)] const [<$name _OFFSET>]: usize = $offset; } }; From 6d4ab3f77d6836593c5f5e68303773a97b309d8d Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 5 Feb 2026 12:06:54 +0000 Subject: [PATCH 445/782] Fix worker distributed hints --- distributed/crates/worker/src/cli/main.rs | 4 ++++ distributed/crates/worker/src/config.rs | 2 ++ distributed/crates/worker/src/worker.rs | 27 ++++++++++++++++++++--- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/distributed/crates/worker/src/cli/main.rs b/distributed/crates/worker/src/cli/main.rs index 7187e2cb0..c4c5d38ab 100644 --- a/distributed/crates/worker/src/cli/main.rs +++ b/distributed/crates/worker/src/cli/main.rs @@ -110,6 +110,9 @@ struct Cli { #[clap(short = 'r', long, default_value_t = false)] pub rma: bool, + + #[clap(long, default_value_t = false)] + pub hints: bool, } #[tokio::main] @@ -131,6 +134,7 @@ async fn main() -> Result<()> { elf: cli.elf.clone(), asm: cli.asm.clone(), emulator: cli.emulator, + hints: cli.hints, proving_key: cli.proving_key.clone(), asm_port: cli.asm_port, unlock_mapped_memory: cli.unlock_mapped_memory, diff --git a/distributed/crates/worker/src/config.rs b/distributed/crates/worker/src/config.rs index 8ff243122..cb845ce1b 100644 --- a/distributed/crates/worker/src/config.rs +++ b/distributed/crates/worker/src/config.rs @@ -146,6 +146,7 @@ impl WorkerServiceConfig { pub struct ProverServiceConfigDto { pub elf: PathBuf, pub asm: Option, + pub hints: bool, pub emulator: bool, pub proving_key: Option, pub asm_port: Option, @@ -168,6 +169,7 @@ impl Default for ProverServiceConfigDto { Self { elf: PathBuf::new(), asm: None, + hints: false, emulator: false, proving_key: None, asm_port: None, diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 92ab36a81..6a60a70d7 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -137,6 +137,22 @@ impl ProverConfig { if emulator { prover_service_config.asm = None; } else if prover_service_config.asm.is_none() { + let stem = prover_service_config + .elf + .file_stem() + .ok_or_else(|| { + anyhow::anyhow!( + "ELF path '{}' does not have a file stem.", + prover_service_config.elf.display() + ) + })? + .to_str() + .ok_or_else(|| { + anyhow::anyhow!( + "ELF file stem for '{}' is not valid UTF-8.", + prover_service_config.elf.display() + ) + })?; let elf_bin = fs::read(&prover_service_config.elf).map_err(|e| { anyhow::anyhow!( "Error reading ELF file {}: {}", @@ -148,12 +164,17 @@ impl ProverConfig { let elf = ElfBinaryOwned::new( elf_bin, prover_service_config.elf.file_stem().unwrap().to_str().unwrap().to_string(), - true, + prover_service_config.hints, ); let hash = get_elf_data_hash(&elf) .map_err(|e| anyhow::anyhow!("Error computing ELF hash: {}", e))?; - let new_filename = format!("{hash}-mt.bin"); - let asm_rom_filename = format!("{hash}-rh.bin"); + let stem = if prover_service_config.hints { + format!("{stem}-hints") + } else { + stem.to_string() + }; + let new_filename = format!("{stem}-{hash}-mt.bin"); + let asm_rom_filename = format!("{stem}-{hash}-rh.bin"); asm_rom = Some(default_cache_path.join(asm_rom_filename)); prover_service_config.asm = Some(default_cache_path.join(new_filename)); } From ea2ed7c830e5be249d866b0e13b6e979aca7f28b Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 5 Feb 2026 12:09:58 +0000 Subject: [PATCH 446/782] Fix --- distributed/crates/worker/src/worker.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 6a60a70d7..19bef25c7 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -322,7 +322,7 @@ impl Worker { let elf = ElfBinaryOwned::new( elf_bin, prover_config.elf.file_stem().unwrap().to_str().unwrap().to_string(), - true, + prover_service_config.hints, ); prover.setup(&elf)?; From 9dadb629526d15b81beb06166b31866ec6371c83 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:28:03 +0000 Subject: [PATCH 447/782] added hints flag to ProverConfig --- distributed/crates/worker/src/worker.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 19bef25c7..05623e5cf 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -97,6 +97,9 @@ pub struct ProverConfig { /// Whether to use minimal memory mode pub minimal_memory: bool, + + /// Whether to include precompile hints in the assembly generation + pub hints: bool, } impl ProverConfig { @@ -224,6 +227,7 @@ impl ProverConfig { shared_tables: prover_service_config.shared_tables, rma: prover_service_config.rma, minimal_memory: prover_service_config.minimal_memory, + hints: prover_service_config.hints, }) } } @@ -322,7 +326,7 @@ impl Worker { let elf = ElfBinaryOwned::new( elf_bin, prover_config.elf.file_stem().unwrap().to_str().unwrap().to_string(), - prover_service_config.hints, + prover_config.hints, ); prover.setup(&elf)?; From 2738495fd0d7d9a0ba5814a5f5286ad423a59b89 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:07:57 +0000 Subject: [PATCH 448/782] fix: distributed with hitns now working --- common/src/io/stream/zisk_stream.rs | 8 ++++++++ emulator-asm/asm-runner/src/hints_shmem.rs | 7 +++++++ emulator-asm/asm-runner/src/shmem_writer.rs | 4 ++++ emulator-asm/src/main.c | 2 ++ executor/src/executor.rs | 6 ++++++ precompiles/hints/src/hints_processor.rs | 12 ++++++++++-- 6 files changed, 37 insertions(+), 2 deletions(-) diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index 74cc87023..c44dd73ec 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -15,6 +15,8 @@ pub trait StreamProcessor: Send + Sync + 'static { /// - processed_data: Vec - The processed data /// - has_ctrl_end: bool - True if CTRL_END was found (signals end of batch) fn process(&self, data: &[u64], first_batch: bool) -> anyhow::Result; + + fn reset(&self) {} } /// Trait for submitting processed hints to a sink. @@ -27,6 +29,8 @@ pub trait StreamProcessor: Send + Sync + 'static { /// * `Err` - If submission fails pub trait StreamSink: Send + Sync + 'static { fn submit(&self, processed: Vec) -> anyhow::Result<()>; + + fn reset(&self) {} } enum ThreadCommand { @@ -136,6 +140,10 @@ impl ZiskStream { Ok(()) } + pub fn reset(&mut self) { + self.hints_processor.reset(); + } + /// Trigger the background thread to process hints asynchronously. /// /// This method: diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index 602bd1311..3bf9eb266 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -241,4 +241,11 @@ impl StreamSink for HintsShmem { Ok(()) } + + fn reset(&self) { + // Reset control writer and data writer to initial state for next stream + let mut unified = self.unified.borrow_mut(); + unified.control_writer.write_u64_at(0, 0); + unified.data_writer.reset(); + } } diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index cc3c6f3bd..9723b26d0 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -204,6 +204,10 @@ impl SharedMemoryWriter { (self.ptr.add(offset) as *mut u64).write(value); } } + + pub fn reset(&mut self) { + self.current_ptr = self.ptr; + } } impl Drop for SharedMemoryWriter { diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 789b2f39c..a665c6583 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4013,6 +4013,8 @@ void server_setup (void) void server_reset (void) { + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) *precompile_read_address = 0; + // Reset RAM data for next emulation if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) { diff --git a/executor/src/executor.rs b/executor/src/executor.rs index b31bc8db9..26373bb37 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -910,6 +910,12 @@ impl WitnessComponent for ZiskExecutor { } } + if let Ok(mut hints_stream_guard) = self.hints_stream.lock() { + if let Some(hints_stream) = hints_stream_guard.as_mut() { + hints_stream.reset(); + } + } + // Add to executor stats #[cfg(feature = "stats")] self.stats.add_stat( diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 8a7e76b48..5752bf8cc 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -306,7 +306,7 @@ impl HintsProcessor { )); } // Reset global sequence and buffer at stream start - self.reset(); + self.reset_state(); // Mark stream as active self.stream_active.store(true, Ordering::Release); // Control hint only; skip processing @@ -599,7 +599,7 @@ impl HintsProcessor { /// /// Increments the generation counter to invalidate any in-flight workers /// from the previous session, preventing them from corrupting the new state. - fn reset(&self) { + fn reset_state(&self) { // Clear error flag - use Release to synchronize with Acquire loads in workers self.state.error_flag.store(false, Ordering::Release); // Reset sequence counter - Relaxed is sufficient as it's only used within mutex @@ -681,6 +681,10 @@ impl HintsProcessor { BuiltInHint::Keccak256 => keccak256_hint(&data, data_len_bytes), } } + + fn reset(&self) { + self.hints_sink.reset(); + } } impl Drop for HintsProcessor { @@ -702,6 +706,10 @@ impl StreamProcessor for HintsProcessor { fn process(&self, data: &[u64], first_batch: bool) -> Result { self.process_hints(data, first_batch) } + + fn reset(&self) { + self.reset(); + } } #[cfg(test)] From 87174147b976b59b00ba68044360ab74a6271592 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:11:15 +0000 Subject: [PATCH 449/782] typo --- emulator-asm/asm-runner/src/asm_mt_runner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 50773c1c3..d2c54bdf4 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -203,7 +203,7 @@ impl AsmRunnerMT { let total_steps = emu_traces.iter().map(|x| x.steps).sum::(); let mhz = (total_steps as f64 / elapsed.as_secs_f64()) / 1_000_000.0; - info!("··· Assembly execution speed: {}MHz ({:?})", mhz.round(), elapsed); + info!("··· Assembly execution speed: {}MHz ({:2?})", mhz.round(), elapsed); let response = handle.map_err(AsmRunError::ServiceError)?; From 631611b842580913847a38a97bbd6f1f95b18483 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 5 Feb 2026 16:52:29 +0000 Subject: [PATCH 450/782] Add secp256k1 ecdsa verify hint --- ziskos/entrypoint/src/hints/secp256k1.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ziskos/entrypoint/src/hints/secp256k1.rs b/ziskos/entrypoint/src/hints/secp256k1.rs index 560a8cfc7..af1acfdfa 100644 --- a/ziskos/entrypoint/src/hints/secp256k1.rs +++ b/ziskos/entrypoint/src/hints/secp256k1.rs @@ -1,11 +1,20 @@ use crate::hints::macros::define_hint; -const SECP256K1_ECRECOVER_HINT_ID: u32 = 0x0300; +const SECP256K1_ECDSA_RECOVER_HINT_ID: u32 = 0x0300; +const SECP256K1_ECDSA_VERIFY_SIGNATURE_HINT_ID: u32 = 0x0301; define_hint! { - secp256k1_ecrecover => { - hint_id: SECP256K1_ECRECOVER_HINT_ID, - params: (sig: 64, recid: 8, msg: 32, require_low_s: 8), + secp256k1_ecdsa_address_recover => { + hint_id: SECP256K1_ECDSA_RECOVER_HINT_ID, + params: (sig: 64, recid: 8, msg: 32), + is_result: false, + } +} + +define_hint! { + secp256k1_ecdsa_verify_and_address_recover => { + hint_id: SECP256K1_ECDSA_VERIFY_SIGNATURE_HINT_ID, + params: (sig: 64, msg: 32, pk: 64), is_result: false, } } From 61dc3e9ef86535d4d0143c34b39267644e95fdcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Thu, 5 Feb 2026 18:00:37 +0100 Subject: [PATCH 451/782] Save method stdin (#755) * Save method stdin * Cargo fmt * Cargo clippy --- common/src/io/stdin/file.rs | 7 +++++++ common/src/io/stdin/memory.rs | 7 +++++++ common/src/io/stdin/null.rs | 6 ++++++ common/src/io/stdin/zisk_stdin.rs | 14 ++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/common/src/io/stdin/file.rs b/common/src/io/stdin/file.rs index 366403a6f..f474955b3 100644 --- a/common/src/io/stdin/file.rs +++ b/common/src/io/stdin/file.rs @@ -1,6 +1,7 @@ //! A file-based implementation of ZiskStdin. //! This module provides functionality to read input data from a file. +use anyhow::Result; use serde::Serialize; use std::fs::{self, File}; use std::io::{BufReader, Read}; @@ -60,4 +61,10 @@ impl ZiskIO for ZiskFileStdin { // Writing is not supported for file-based stdin panic!("Write operations are not supported for FileStdin"); } + + fn save(&self, _path: &Path) -> Result<()> { + // This is a read-only stdin implementation + // Saving is not supported for file-based stdin + panic!("Save operations are not supported for FileStdin"); + } } diff --git a/common/src/io/stdin/memory.rs b/common/src/io/stdin/memory.rs index f14d73930..7ea4e868e 100644 --- a/common/src/io/stdin/memory.rs +++ b/common/src/io/stdin/memory.rs @@ -1,5 +1,7 @@ +use anyhow::Result; use serde::Serialize; use std::io::{Cursor, Read}; +use std::path::Path; use std::sync::Mutex; use crate::io::ZiskIO; @@ -57,4 +59,9 @@ impl ZiskIO for ZiskMemoryStdin { self.data.lock().unwrap().extend_from_slice(data); cursor.get_mut().extend_from_slice(data); } + + fn save(&self, path: &Path) -> Result<()> { + std::fs::write(path, self.data.lock().unwrap().as_slice())?; + Ok(()) + } } diff --git a/common/src/io/stdin/null.rs b/common/src/io/stdin/null.rs index b770f3298..d8c80218b 100644 --- a/common/src/io/stdin/null.rs +++ b/common/src/io/stdin/null.rs @@ -1,7 +1,9 @@ use tracing::warn; use crate::io::ZiskIO; +use anyhow::Result; use serde::Serialize; +use std::path::Path; pub struct ZiskNullStdin; @@ -17,4 +19,8 @@ impl ZiskIO for ZiskNullStdin { fn write_slice(&self, _data: &[u8]) { warn!("NullStdin does not support writing"); } + fn save(&self, _path: &Path) -> Result<()> { + warn!("NullStdin does not support saving"); + Ok(()) + } } diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index 7f5d77368..2cd7442d0 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -19,6 +19,8 @@ pub trait ZiskIO: Send + Sync { /// Write a slice of bytes to the buffer. fn write_slice(&self, data: &[u8]); + + fn save(&self, path: &Path) -> Result<()>; } pub enum ZiskIOVariant { @@ -67,6 +69,14 @@ impl ZiskIO for ZiskIOVariant { ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write_slice(data), } } + + fn save(&self, path: &Path) -> Result<()> { + match self { + ZiskIOVariant::File(file_stdin) => file_stdin.save(path), + ZiskIOVariant::Null(null_stdin) => null_stdin.save(path), + ZiskIOVariant::Memory(memory_stdin) => memory_stdin.save(path), + } + } } #[derive(Clone)] @@ -94,6 +104,10 @@ impl ZiskIO for ZiskStdin { fn write_slice(&self, data: &[u8]) { self.io.write_slice(data) } + + fn save(&self, path: &Path) -> Result<()> { + self.io.save(path) + } } impl Default for ZiskStdin { From b8327ec8e380e59c2180de069ae56455bd229b7d Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 5 Feb 2026 19:09:42 +0000 Subject: [PATCH 452/782] Fix ECDSA verify hint ID --- ziskos/entrypoint/src/hints/secp256k1.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ziskos/entrypoint/src/hints/secp256k1.rs b/ziskos/entrypoint/src/hints/secp256k1.rs index af1acfdfa..03172c8bc 100644 --- a/ziskos/entrypoint/src/hints/secp256k1.rs +++ b/ziskos/entrypoint/src/hints/secp256k1.rs @@ -1,7 +1,7 @@ use crate::hints::macros::define_hint; const SECP256K1_ECDSA_RECOVER_HINT_ID: u32 = 0x0300; -const SECP256K1_ECDSA_VERIFY_SIGNATURE_HINT_ID: u32 = 0x0301; +const SECP256K1_ECDSA_VERIFY_HINT_ID: u32 = 0x0302; define_hint! { secp256k1_ecdsa_address_recover => { @@ -13,7 +13,7 @@ define_hint! { define_hint! { secp256k1_ecdsa_verify_and_address_recover => { - hint_id: SECP256K1_ECDSA_VERIFY_SIGNATURE_HINT_ID, + hint_id: SECP256K1_ECDSA_VERIFY_HINT_ID, params: (sig: 64, msg: 32, pk: 64), is_result: false, } From 278742144cf1f59e9604c1a3f811e68087c1dc2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Fri, 6 Feb 2026 07:35:56 +0000 Subject: [PATCH 453/782] Fixing curve multiplication bugs --- .../src/zisklib/lib/bls12_381/curve.rs | 9 +++-- .../src/zisklib/lib/bls12_381/fr.rs | 30 +++++++++++++++- .../src/zisklib/lib/bls12_381/twist.rs | 9 +++-- .../entrypoint/src/zisklib/lib/bn254/curve.rs | 34 ++++++++----------- ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs | 6 ---- ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs | 10 +++--- .../src/zisklib/lib/bn254/pairing.rs | 13 +++---- 7 files changed, 66 insertions(+), 45 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs index 99af22090..0e7298510 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs @@ -14,7 +14,7 @@ use super::{ add_fp_bls12_381, mul_fp_bls12_381, neg_fp_bls12_381, sqrt_fp_bls12_381, square_fp_bls12_381, }, - fr::scalar_bytes_be_to_u64_le_bls12_381, + fr::{reduce_fr_bls12_381, scalar_bytes_be_to_u64_le_bls12_381}, }; // TODO: Check what happens if scalar or ecc coordinates are bigger than the field size @@ -584,7 +584,12 @@ pub fn msm_complete_bls12_381( } // Skip zero scalars - if *scalar == [0, 0, 0, 0] { + if reduce_fr_bls12_381( + scalar, + #[cfg(feature = "hints")] + hints, + ) == [0, 0, 0, 0] + { continue; } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs index f7d05fb79..9bed00563 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/fr.rs @@ -1,9 +1,37 @@ //! Finite field Fr operations for BLS12-381 -use crate::syscalls::{syscall_arith256_mod, SyscallArith256ModParams}; +use crate::{ + syscalls::{syscall_arith256_mod, SyscallArith256ModParams}, + zisklib::lt, +}; use super::constants::{R, R_MINUS_ONE}; +pub fn reduce_fr_bls12_381( + x: &[u64; 4], + #[cfg(feature = "hints")] hints: &mut Vec, +) -> [u64; 4] { + if lt(x, &R) { + return *x; + } + + // x·1 + 0 + let mut params = SyscallArith256ModParams { + a: x, + b: &[1, 0, 0, 0], + c: &[0, 0, 0, 0], + module: &R, + d: &mut [0, 0, 0, 0], + }; + syscall_arith256_mod( + &mut params, + #[cfg(feature = "hints")] + hints, + ); + + *params.d +} + /// Addition in Fr #[inline] pub fn add_fr_bls12_381( diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs index 713087ad7..7ff33d21b 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs @@ -12,7 +12,7 @@ use super::{ mul_fp2_bls12_381, neg_fp2_bls12_381, scalar_mul_fp2_bls12_381, sqrt_fp2_bls12_381, square_fp2_bls12_381, sub_fp2_bls12_381, }, - fr::scalar_bytes_be_to_u64_le_bls12_381, + fr::{reduce_fr_bls12_381, scalar_bytes_be_to_u64_le_bls12_381}, }; /// G2 add result codes @@ -846,7 +846,12 @@ pub fn msm_complete_twist_bls12_381( } // Skip zero scalars - if *scalar == [0, 0, 0, 0] { + if reduce_fr_bls12_381( + scalar, + #[cfg(feature = "hints")] + hints, + ) == [0, 0, 0, 0] + { continue; } diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs index 751a3ef43..428fd32d0 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs @@ -7,13 +7,13 @@ use crate::{ syscall_bn254_curve_add, syscall_bn254_curve_dbl, SyscallBn254CurveAddParams, SyscallPoint256, }, - zisklib::{eq, fcall_msb_pos_256, is_zero}, + zisklib::{eq, fcall_msb_pos_256, is_zero, lt}, }; use super::{ constants::{E_B, G1_IDENTITY, P}, - fp::{add_fp_bn254, inv_fp_bn254, is_canonical_fp_bn254, mul_fp_bn254, square_fp_bn254}, - fr::{is_canonical_fr_bn254, reduce_fr_bn254, scalar_bytes_be_to_u64_le_bn254}, + fp::{add_fp_bn254, inv_fp_bn254, mul_fp_bn254, square_fp_bn254}, + fr::{reduce_fr_bn254, scalar_bytes_be_to_u64_le_bn254}, }; /// G1 add result codes @@ -22,7 +22,7 @@ const G1_ADD_SUCCESS_INFINITY: u8 = 1; const G1_ADD_ERR_INVALID: u8 = 2; const G1_ADD_ERR_NOT_ON_CURVE: u8 = 3; -/// G1 MSM result codes +/// G1 mul result codes const G1_MUL_SUCCESS: u8 = 0; const G1_MUL_SUCCESS_INFINITY: u8 = 1; const G1_MUL_ERR_NOT_IN_FIELD: u8 = 2; @@ -124,7 +124,7 @@ pub fn add_complete_bn254( // Validate p2 field elements and curve membership let x2: [u64; 4] = p2[0..4].try_into().unwrap(); let y2: [u64; 4] = p2[4..8].try_into().unwrap(); - if !is_canonical_fp_bn254(&x2) || !is_canonical_fp_bn254(&y2) { + if !lt(&x2, &P) || !lt(&y2, &P) { return Err(G1_ADD_ERR_INVALID); } if !is_on_curve_bn254( @@ -141,7 +141,7 @@ pub fn add_complete_bn254( // Validate p1 field elements and curve membership let x1: [u64; 4] = p1[0..4].try_into().unwrap(); let y1: [u64; 4] = p1[4..8].try_into().unwrap(); - if !is_canonical_fp_bn254(&x1) || !is_canonical_fp_bn254(&y1) { + if !lt(&x1, &P) || !lt(&y1, &P) { return Err(G1_ADD_ERR_INVALID); } if !is_on_curve_bn254( @@ -157,7 +157,7 @@ pub fn add_complete_bn254( // Both points are non-identity, validate both let x1: [u64; 4] = p1[0..4].try_into().unwrap(); let y1: [u64; 4] = p1[4..8].try_into().unwrap(); - if !is_canonical_fp_bn254(&x1) || !is_canonical_fp_bn254(&y1) { + if !lt(&x1, &P) || !lt(&y1, &P) { return Err(G1_ADD_ERR_INVALID); } if !is_on_curve_bn254( @@ -170,7 +170,7 @@ pub fn add_complete_bn254( let x2: [u64; 4] = p2[0..4].try_into().unwrap(); let y2: [u64; 4] = p2[4..8].try_into().unwrap(); - if !is_canonical_fp_bn254(&x2) || !is_canonical_fp_bn254(&y2) { + if !lt(&x2, &P) || !lt(&y2, &P) { return Err(G1_ADD_ERR_INVALID); } if !is_on_curve_bn254( @@ -317,7 +317,7 @@ pub fn mul_complete_bn254( let x: [u64; 4] = p[0..4].try_into().unwrap(); let y: [u64; 4] = p[4..8].try_into().unwrap(); - if !is_canonical_fp_bn254(&x) || !is_canonical_fp_bn254(&y) { + if !lt(&x, &P) || !lt(&y, &P) { return Err(G1_MUL_ERR_NOT_IN_FIELD); } @@ -329,16 +329,12 @@ pub fn mul_complete_bn254( return Err(G1_MUL_ERR_NOT_ON_CURVE); } - // Reduce the scalar if not canonical - let k = if !is_canonical_fr_bn254(k) { - reduce_fr_bn254( - k, - #[cfg(feature = "hints")] - hints, - ) - } else { - *k - }; + // Reduce the scalar + let k = reduce_fr_bn254( + k, + #[cfg(feature = "hints")] + hints, + ); // Perform scalar multiplication Ok(scalar_mul_bn254( diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs index 08883b3c4..8073bac77 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/fp.rs @@ -7,12 +7,6 @@ use crate::{ use super::constants::{P, P_MINUS_ONE}; -// Checks if a 256-bit integer `x` is in canonical form -#[inline] -pub fn is_canonical_fp_bn254(x: &[u64; 4]) -> bool { - lt(x, &P) -} - /// Addition in the base field of the BN254 curve #[inline] pub fn add_fp_bn254( diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs index 86826efb1..6791350ae 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/fr.rs @@ -5,13 +5,11 @@ use crate::{ use super::constants::R; -// Checks if a 256-bit integer `x` is in canonical form -#[inline] -pub fn is_canonical_fr_bn254(x: &[u64; 4]) -> bool { - lt(x, &R) -} - pub fn reduce_fr_bn254(x: &[u64; 4], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 4] { + if lt(x, &R) { + return *x; + } + // x·1 + 0 let mut params = SyscallArith256ModParams { a: x, diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs index 037a1ff50..646755e73 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/pairing.rs @@ -1,12 +1,11 @@ //! Pairing over BN254 -use crate::zisklib::lib::utils::{eq, gt, is_one}; +use crate::zisklib::lib::utils::{eq, is_one, lt}; use super::{ - constants::{G1_IDENTITY, G2_IDENTITY, P, P_MINUS_ONE}, + constants::{G1_IDENTITY, G2_IDENTITY, P}, curve::{g1_bytes_be_to_u64_le_bn254, is_on_curve_bn254}, final_exp::final_exp_bn254, - fp::is_canonical_fp_bn254, miller_loop::{miller_loop_batch_bn254, miller_loop_bn254}, twist::{g2_bytes_be_to_u64_le_bn254, is_on_curve_twist_bn254, is_on_subgroup_twist_bn254}, }; @@ -175,7 +174,7 @@ pub fn pairing_check_bn254( // Validate G1 point field elements let x1: [u64; 4] = g1[0..4].try_into().unwrap(); let y1: [u64; 4] = g1[4..8].try_into().unwrap(); - if !is_canonical_fp_bn254(&x1) || !is_canonical_fp_bn254(&y1) { + if !lt(&x1, &P) || !lt(&y1, &P) { return Err(PAIRING_CHECK_ERR_G1_INVALID); } @@ -193,11 +192,7 @@ pub fn pairing_check_bn254( let x2_i: [u64; 4] = g2[4..8].try_into().unwrap(); let y2_r: [u64; 4] = g2[8..12].try_into().unwrap(); let y2_i: [u64; 4] = g2[12..16].try_into().unwrap(); - if !is_canonical_fp_bn254(&x2_r) - || !is_canonical_fp_bn254(&x2_i) - || !is_canonical_fp_bn254(&y2_r) - || !is_canonical_fp_bn254(&y2_i) - { + if !lt(&x2_r, &P) || !lt(&x2_i, &P) || !lt(&y2_r, &P) || !lt(&y2_i, &P) { return Err(PAIRING_CHECK_ERR_G2_INVALID); } From 92e49e9fbf8dfc06c70e65794708ed5c4084edd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Fri, 6 Feb 2026 11:58:29 +0100 Subject: [PATCH 454/782] Merge pull request #758 from 0xPolygonHermez/feature/execution_info Feature/execution info --- Cargo.lock | 62 +++++------ distributed/crates/common/src/dto.rs | 15 ++- distributed/crates/common/src/types.rs | 12 ++- .../crates/coordinator/src/coordinator.rs | 102 ++++++++++++++---- .../grpc-api/proto/zisk_distributed_api.proto | 8 ++ .../crates/grpc-api/src/conversions.rs | 12 ++- distributed/crates/worker/src/worker.rs | 8 +- distributed/crates/worker/src/worker_node.rs | 19 +++- sdk/src/prover/asm.rs | 6 +- sdk/src/prover/backend.rs | 12 ++- sdk/src/prover/emu.rs | 6 +- sdk/src/prover/mod.rs | 10 +- 12 files changed, 208 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52a3ca468..3b31db1d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,9 +130,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "ark-bls12-381" @@ -1189,7 +1189,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" dependencies = [ "fields", "num-bigint", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" dependencies = [ "cfg-if", "num-bigint", @@ -2272,9 +2272,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" dependencies = [ "jiff-static", "log", @@ -2285,9 +2285,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" dependencies = [ "proc-macro2", "quote", @@ -2910,9 +2910,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", @@ -2920,9 +2920,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ "pest", "pest_generator", @@ -2930,9 +2930,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ "pest", "pest_meta", @@ -2943,9 +2943,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", "sha2", @@ -2965,7 +2965,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" dependencies = [ "colored", "fields", @@ -3380,7 +3380,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" dependencies = [ "bincode", "blake3", @@ -3416,7 +3416,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" dependencies = [ "bincode", "borsh", @@ -3449,7 +3449,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" dependencies = [ "fields", "itoa", @@ -3462,7 +3462,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" dependencies = [ "proc-macro2", "quote", @@ -3473,7 +3473,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" dependencies = [ "crossbeam-channel", "tracing", @@ -3482,7 +3482,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" dependencies = [ "bincode", "bytemuck", @@ -3496,7 +3496,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" dependencies = [ "bytemuck", "fields", @@ -5218,9 +5218,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.1.4" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" +checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" dependencies = [ "base64", "flate2", @@ -5890,7 +5890,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c5d5b26abbdb2b3ee15583aa198aee4dd4d786d2" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" dependencies = [ "colored", "fields", @@ -5976,18 +5976,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.38" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.38" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 5214d78c8..5f9540e5a 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -203,6 +203,14 @@ pub struct ProveParamsDto { pub challenges: Vec, } +#[derive(Clone)] +pub struct ExecutionInfoDto { + pub execution_time: f32, + pub publics: Vec, + pub proof_values: Vec, + pub summary_info: String, +} + #[derive(Clone)] pub struct ChallengesDto { pub worker_index: u32, @@ -236,8 +244,13 @@ pub struct ExecuteTaskResponseDto { pub result_data: ExecuteTaskResponseResultDataDto, } +pub struct ContributionsResultDataDto { + pub challenges: Vec, + pub execution_info: ExecutionInfoDto, +} + pub enum ExecuteTaskResponseResultDataDto { - Challenges(Vec), + Challenges(ContributionsResultDataDto), Proofs(Vec), FinalProof(FinalProofDto), } diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index 7efca80d3..a0f479610 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -6,7 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use chrono::{DateTime, Utc}; -use proofman::ContributionsInfo; +use proofman::{ContributionsInfo, ExecutionInfo}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -252,6 +252,7 @@ pub struct Job { pub results: HashMap>, pub stats: HashMap, pub challenges: Option>, + pub execution_info: Option, pub execution_mode: JobExecutionMode, pub final_proof: Option>, pub executed_steps: Option, @@ -285,6 +286,7 @@ impl Job { results: HashMap::new(), stats: HashMap::new(), challenges: None, + execution_info: None, execution_mode, final_proof: None, executed_steps: None, @@ -384,9 +386,15 @@ pub struct AggProofData { pub values: Vec, } +#[derive(Debug, Clone)] +pub struct ContributionsResult { + pub challenges: Vec, + pub execution_info: ExecutionInfo, +} + #[derive(Debug, Clone)] pub enum JobResultData { - Challenges(Vec), + Challenges(ContributionsResult), AggProofs(Vec), } diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 0bdd1fa0c..3bb129d59 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -39,7 +39,7 @@ use crate::{ use chrono::{DateTime, Utc}; use colored::Colorize; use dashmap::DashMap; -use proofman::ContributionsInfo; +use proofman::{ContributionsInfo, ExecutionInfo}; use std::{ collections::HashMap, sync::{ @@ -53,13 +53,13 @@ use tracing::{error, info, warn}; use zisk_common::io::{StreamSource, ZiskStream}; use zisk_distributed_common::{ AggParamsDto, AggProofData, ChallengesDto, ComputeCapacity, ContributionParamsDto, - CoordinatorMessageDto, DataId, ExecuteTaskRequestDto, ExecuteTaskRequestTypeDto, - ExecuteTaskResponseDto, ExecuteTaskResponseResultDataDto, HeartbeatAckDto, HintsModeDto, - HintsSourceDto, InputSourceDto, InputsModeDto, Job, JobExecutionMode, JobId, JobPhase, - JobResult, JobResultData, JobState, JobStatusDto, JobsListDto, LaunchProofRequestDto, - LaunchProofResponseDto, MetricsDto, ProofDto, ProveParamsDto, StatusInfoDto, StreamMessageKind, - SystemStatusDto, WorkerErrorDto, WorkerId, WorkerReconnectRequestDto, WorkerRegisterRequestDto, - WorkerState, WorkersListDto, + ContributionsResult, CoordinatorMessageDto, DataId, ExecuteTaskRequestDto, + ExecuteTaskRequestTypeDto, ExecuteTaskResponseDto, ExecuteTaskResponseResultDataDto, + HeartbeatAckDto, HintsModeDto, HintsSourceDto, InputSourceDto, InputsModeDto, Job, + JobExecutionMode, JobId, JobPhase, JobResult, JobResultData, JobState, JobStatusDto, + JobsListDto, LaunchProofRequestDto, LaunchProofResponseDto, MetricsDto, ProofDto, + ProveParamsDto, StatusInfoDto, StreamMessageKind, SystemStatusDto, WorkerErrorDto, WorkerId, + WorkerReconnectRequestDto, WorkerRegisterRequestDto, WorkerState, WorkersListDto, }; use proofman_util::VadcopFinalProof; @@ -1173,6 +1173,9 @@ impl Coordinator { return Ok(()); } + // Print execution summary from Phase 1 completion + self.print_execution_summary(&job); + // Validate and extract challenges in a single operation to minimize lock time let challenges = self.validate_and_extract_challenges(&job).await?; @@ -1241,14 +1244,15 @@ impl Coordinator { result_data: ExecuteTaskResponseResultDataDto, ) -> CoordinatorResult { match result_data { - ExecuteTaskResponseResultDataDto::Challenges(challenges) => { - if challenges.is_empty() { + ExecuteTaskResponseResultDataDto::Challenges(ch_list) => { + if ch_list.challenges.is_empty() { return Err(CoordinatorError::InvalidRequest( "Received empty Challenges result data".to_string(), )); } - let contributions: Vec = challenges + let contributions: Vec = ch_list + .challenges .into_iter() .map(|challenge| ContributionsInfo { worker_index: challenge.worker_index, @@ -1257,7 +1261,17 @@ impl Coordinator { }) .collect(); - Ok(JobResultData::Challenges(contributions)) + let execution_info = ExecutionInfo { + summary_info: ch_list.execution_info.summary_info, + publics: ch_list.execution_info.publics, + proof_values: ch_list.execution_info.proof_values, + execution_time: ch_list.execution_info.execution_time, + }; + + Ok(JobResultData::Challenges(ContributionsResult { + execution_info, + challenges: contributions, + })) } _ => Err(CoordinatorError::InvalidRequest( "Expected Challenges result data for Phase1".to_string(), @@ -1265,6 +1279,28 @@ impl Coordinator { } } + /// Prints execution summary information from Phase 1 completion. + /// + /// Extracts and displays execution information from the first completed worker's + /// contribution results, including timing, summary info, and key metrics. + /// + /// # Parameters + /// + /// * `job` - Reference to the job containing Phase 1 results + fn print_execution_summary(&self, job: &Job) { + // Find the first completed contribution result to extract ExecutionInfo summary + if let Some(contributions_results) = job.results.get(&JobPhase::Contributions) { + if let Some((_worker_id, job_result)) = contributions_results.iter().next() { + if let JobResultData::Challenges(contributions_result) = &job_result.data { + info!( + "Execution Summary: {}", + contributions_result.execution_info.summary_info + ); + } + } + } + } + /// Checks if all workers have completed Phase 1 contributions. /// /// # Parameters @@ -1283,13 +1319,27 @@ impl Coordinator { ); let duration_ms = Duration::from_millis(duration.num_milliseconds() as u64); + // Get execution time from the worker's result + let exec_time_info = job + .results + .get(&JobPhase::Contributions) + .and_then(|results| results.get(worker_id)) + .and_then(|job_result| match &job_result.data { + JobResultData::Challenges(contributions_result) => { + Some(contributions_result.execution_info.execution_time) + } + _ => None, + }) + .unwrap_or(0.0); + info!( - "[Phase1] {} finished phase 1 for {} ({}/{} workers done, {:.3}s)", + "[Phase1] {} finished phase 1 for {} ({}/{} workers done, {:.3}s (execution {:.3}s))", worker_id, job.job_id, phase1_results_len, job.workers.len(), - duration_ms.as_secs_f32() + duration_ms.as_secs_f32(), + exec_time_info ); // Ensure we have results from all assigned workers before proceeding. @@ -1352,7 +1402,7 @@ impl Coordinator { // Simulation mode: replicate single worker's challenges across all expected workers // This maintains algorithm correctness while using minimal computational resources let first_challenges = match phase1_results.values().next().unwrap().data { - JobResultData::Challenges(ref values) => values, + JobResultData::Challenges(ref values) => &values.challenges, _ => unreachable!("Expected Challenges data in Phase1 results"), }; @@ -1364,7 +1414,7 @@ impl Coordinator { let challenges: Vec> = phase1_results .values() .map(|results| match &results.data { - JobResultData::Challenges(values) => values.clone(), + JobResultData::Challenges(values) => values.challenges.clone(), _ => unreachable!("Expected Challenges data in Phase1 results"), }) .collect(); @@ -1900,6 +1950,21 @@ impl Coordinator { let duration = Duration::from_millis(job.duration_ms.unwrap_or(0)); + // Extract execution time from Phase 1 results to split phase1 into execution + contributions + let phase1_execution_time = job + .results + .get(&JobPhase::Contributions) + .and_then(|results| results.values().next()) + .and_then(|job_result| match &job_result.data { + JobResultData::Challenges(contributions_result) => { + Some(contributions_result.execution_info.execution_time) + } + _ => None, + }) + .unwrap_or(0.0); + + let phase1_contributions_time = phase1_duration.as_seconds_f32() - phase1_execution_time; + let header = format!("[Job] Finished {} successfully ✔", job_id).green(); let duration_str = format!("Duration: {:.3}s", duration.as_secs_f32()).bold(); let steps_str = if let Some(executed_steps) = job.executed_steps { @@ -1908,10 +1973,11 @@ impl Coordinator { "Steps: N/A".to_string().red().bold() }; info!( - "{} {} ({:.3}s+{:.3}s+{:.3}s) {} Inputs: {:?}, Capacity: {} ", + "{} {} ({:.3}s+{:.3}s+{:.3}s+{:.3}s) {} Inputs: {:?}, Capacity: {} ", header, duration_str, - phase1_duration.as_seconds_f32(), + phase1_execution_time, + phase1_contributions_time, phase2_duration.as_seconds_f32(), phase3_duration.as_seconds_f32(), steps_str, diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index a06f28c5b..efa788dc1 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -296,8 +296,16 @@ message ExecuteTaskResponse { } } +message ExecuteInfo { + float execution_time = 1; + repeated uint64 publics = 2; + repeated uint64 proof_values = 3; + string summary_info = 4; +} + message ChallengesList { repeated Challenges challenges = 1; + ExecuteInfo execution_info = 2; } message Challenges { diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index 84e49f267..e484909a6 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -419,7 +419,17 @@ impl From for ExecuteTaskResponseDto { challenge: c.challenge, }) .collect(); - Some(ExecuteTaskResponseResultDataDto::Challenges(challenges)) + let exec_info = challenges_list.execution_info.unwrap(); + let execution_info = ExecutionInfoDto { + execution_time: exec_info.execution_time, + publics: exec_info.publics, + proof_values: exec_info.proof_values, + summary_info: exec_info.summary_info, + }; + Some(ExecuteTaskResponseResultDataDto::Challenges(ContributionsResultDataDto { + execution_info, + challenges, + })) } Some(execute_task_response::ResultData::Proofs(proof_list)) => { let proofs: Vec = proof_list diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 05623e5cf..4392ab860 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -18,6 +18,7 @@ use zisk_distributed_common::{ComputeCapacity, JobId, WorkerId}; use zisk_distributed_common::{HintsSourceDto, StreamDataDto, StreamMessageKind, StreamPayloadDto}; use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProver}; +use proofman::ExecutionInfo; use proofman::ProofInfo; use proofman::ProvePhaseInputs; use proofman_common::ParamsGPU; @@ -34,7 +35,7 @@ pub enum ComputationResult { Challenge { job_id: JobId, success: bool, - result: Result>, + result: Result<(ExecutionInfo, Vec)>, }, Proofs { job_id: JobId, @@ -532,12 +533,15 @@ impl Worker { guard.executed_steps = prover.executed_steps(); + let execution_info = + prover.get_execution_info().unwrap_or_else(|_| ExecutionInfo::default()); + match result { Ok(data) => { let _ = tx.send(ComputationResult::Challenge { job_id, success: true, - result: Ok(data), + result: Ok((execution_info, data)), }); } Err(error) => { diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index b8f9038b5..e081a68a0 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -1,6 +1,6 @@ use crate::{worker::ComputationResult, ProverConfig, Worker}; use anyhow::{anyhow, Result}; -use proofman::{AggProofs, ContributionsInfo}; +use proofman::{AggProofs, ContributionsInfo, ExecutionInfo}; use std::path::Path; use std::{path::PathBuf, time::Duration}; use tokio::sync::mpsc; @@ -254,7 +254,7 @@ impl WorkerNodeGrpc { &mut self, job_id: JobId, success: bool, - result: Result>, + result: Result<(ExecutionInfo, Vec)>, message_sender: &mpsc::UnboundedSender, ) -> Result<()> { if let Some(handle) = self.worker.take_current_computation() { @@ -276,11 +276,12 @@ impl WorkerNodeGrpc { "Inconsistent state: operation reported success but returned Err result" )); } - (vec![], e.to_string()) + ((ExecutionInfo::default(), vec![]), e.to_string()) } }; let challenges: Vec = result_data + .1 .into_iter() .map(|cont| Challenges { worker_index: cont.worker_index, @@ -289,13 +290,23 @@ impl WorkerNodeGrpc { }) .collect(); + let execution_info: ExecuteInfo = ExecuteInfo { + execution_time: result_data.0.execution_time, + publics: result_data.0.publics, + proof_values: result_data.0.proof_values, + summary_info: result_data.0.summary_info, + }; + let message = WorkerMessage { payload: Some(worker_message::Payload::ExecuteTaskResponse(ExecuteTaskResponse { worker_id: self.worker_config.worker.worker_id.as_string(), job_id: job_id.as_string(), task_type: TaskType::PartialContribution as i32, success, - result_data: Some(ResultData::Challenges(ChallengesList { challenges })), + result_data: Some(ResultData::Challenges(ChallengesList { + challenges, + execution_info: Some(execution_info), + })), error_message, })), }; diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index c16f41415..f28452c8c 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -7,7 +7,7 @@ use crate::{ }; use crate::{ProofMode, ProofOpts}; use asm_runner::{AsmRunnerOptions, AsmServices}; -use proofman::{AggProofs, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; +use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, VerboseMode}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rom_setup::DEFAULT_CACHE_PATH; @@ -151,6 +151,10 @@ impl ProverEngine for AsmProver { Ok(ZiskProgramVK { vk }) } + fn get_execution_info(&self) -> Result { + self.core_prover.backend.get_execution_info() + } + fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result { self.core_prover.backend.execute(stdin, output_path) } diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 6ab5bdb96..66b4673f1 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -9,8 +9,8 @@ use anyhow::Result; use colored::Colorize; use fields::Goldilocks; use proofman::{ - get_vadcop_final_proof_vkey, verify_snark_proof, AggProofs, ProofInfo, ProofMan, ProvePhase, - ProvePhaseInputs, ProvePhaseResult, SnarkProof, SnarkProtocol, SnarkWrapper, + get_vadcop_final_proof_vkey, verify_snark_proof, AggProofs, ExecutionInfo, ProofInfo, ProofMan, + ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProof, SnarkProtocol, SnarkWrapper, }; use proofman_common::{ProofCtx, ProofOptions}; use proofman_util::VadcopFinalProof; @@ -506,6 +506,14 @@ impl ProverBackend { .map_err(|e| anyhow::anyhow!("Error generating proof in phase {:?}: {}", phase, e)) } + pub(crate) fn get_execution_info(&self) -> Result { + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot get execution info in verifier mode"))?; + Ok(proofman.get_execution_info()) + } + pub(crate) fn aggregate_proofs( &self, agg_proofs: Vec, diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 012afc941..b857e2e32 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -5,7 +5,7 @@ use crate::{ ZiskProof, ZiskProveResult, ZiskPublics, ZiskVerifyConstraintsResult, }; use crate::{ensure_custom_commits, ProofMode, ProofOpts}; -use proofman::{AggProofs, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; +use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, VerboseMode}; use std::collections::HashMap; use std::path::PathBuf; @@ -105,6 +105,10 @@ impl ProverEngine for EmuProver { .unwrap_or(0) } + fn get_execution_info(&self) -> Result { + self.core_prover.backend.get_execution_info() + } + fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result { self.core_prover.backend.execute(stdin, output_path) } diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 98660641c..fceb55bf8 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -4,7 +4,9 @@ mod emu; pub use asm::*; use backend::*; pub use emu::*; -use proofman::{AggProofs, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProtocol}; +use proofman::{ + AggProofs, ExecutionInfo, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProtocol, +}; use proofman_common::ProofOptions; use sha2::{Digest, Sha256}; @@ -465,6 +467,8 @@ pub trait ProverEngine { fn executed_steps(&self) -> u64; + fn get_execution_info(&self) -> Result; + fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result; fn stats( @@ -565,6 +569,10 @@ impl ZiskProver { self.prover.executed_steps() } + pub fn get_execution_info(&self) -> Result { + self.prover.get_execution_info() + } + /// Execute the prover with the given standard input and output path. /// It only runs the execution without generating a proof. pub fn execute(&self, stdin: ZiskStdin) -> Result { From 22be23f7ef39d80b0c1f2ed807f68a73a6e6cf80 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Fri, 6 Feb 2026 13:08:55 +0000 Subject: [PATCH 455/782] Change hints id for secp256k1/r1 hint functions. Use hint id constants from zisk_common crate --- common/src/hints.rs | 49 +++++++++++++----------- ziskos/entrypoint/src/hints/bls12_381.rs | 31 +++++++-------- ziskos/entrypoint/src/hints/bn254.rs | 15 ++++---- ziskos/entrypoint/src/hints/keccak256.rs | 5 +-- ziskos/entrypoint/src/hints/kzg.rs | 5 +-- ziskos/entrypoint/src/hints/modexp.rs | 5 +-- ziskos/entrypoint/src/hints/secp256k1.rs | 8 ++-- ziskos/entrypoint/src/hints/secp256r1.rs | 5 +-- ziskos/entrypoint/src/hints/sha256f.rs | 5 +-- 9 files changed, 63 insertions(+), 65 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index c0f7d4e03..d675532cf 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -53,41 +53,44 @@ use std::fmt::Display; use anyhow::Result; // === CONTROL CODES === -const CTRL_START: u32 = 0x0000; -const CTRL_END: u32 = 0x0001; -const CTRL_CANCEL: u32 = 0x0002; -const CTRL_ERROR: u32 = 0x0003; +pub const CTRL_START: u32 = 0x0000; +pub const CTRL_END: u32 = 0x0001; +pub const CTRL_CANCEL: u32 = 0x0002; +pub const CTRL_ERROR: u32 = 0x0003; // === BUILT-IN HINT CODES === // SHA256 hint codes -const HINT_SHA256: u32 = 0x0100; +pub const HINT_SHA256: u32 = 0x0100; // BN254 hint codes -const HINT_BN254_G1_ADD: u32 = 0x0200; -const HINT_BN254_G1_MUL: u32 = 0x0201; -const HINT_BN254_PAIRING_CHECK: u32 = 0x0205; +pub const HINT_BN254_G1_ADD: u32 = 0x0200; +pub const HINT_BN254_G1_MUL: u32 = 0x0201; +pub const HINT_BN254_PAIRING_CHECK: u32 = 0x0205; // Secp256k1 hint codes -const HINT_SECP256K1_RECOVER: u32 = 0x0300; -const HINT_SECP256R1_ECDSA_VERIFY: u32 = 0x0301; +pub const HINT_SECP256K1_ECDSA_ADDRESS_RECOVER: u32 = 0x0300; +pub const HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER: u32 = 0x0301; + +// Secp256r1 hint codes +pub const HINT_SECP256R1_ECDSA_VERIFY: u32 = 0x0380; // BLS12-381 hint codes -const HINT_BLS12_381_G1_ADD: u32 = 0x0400; -const HINT_BLS12_381_G1_MSM: u32 = 0x0401; -const HINT_BLS12_381_G2_ADD: u32 = 0x0405; -const HINT_BLS12_381_G2_MSM: u32 = 0x0406; -const HINT_BLS12_381_PAIRING_CHECK: u32 = 0x040A; -const HINT_BLS12_381_FP_TO_G1: u32 = 0x0410; -const HINT_BLS12_381_FP2_TO_G2: u32 = 0x0411; +pub const HINT_BLS12_381_G1_ADD: u32 = 0x0400; +pub const HINT_BLS12_381_G1_MSM: u32 = 0x0401; +pub const HINT_BLS12_381_G2_ADD: u32 = 0x0405; +pub const HINT_BLS12_381_G2_MSM: u32 = 0x0406; +pub const HINT_BLS12_381_PAIRING_CHECK: u32 = 0x040A; +pub const HINT_BLS12_381_FP_TO_G1: u32 = 0x0410; +pub const HINT_BLS12_381_FP2_TO_G2: u32 = 0x0411; // Modular exponentiation hint codes -const HINT_MODEXP: u32 = 0x0500; +pub const HINT_MODEXP: u32 = 0x0500; // KZG hint codes -const HINT_VERIFY_KZG_PROOF: u32 = 0x0600; +pub const HINT_VERIFY_KZG_PROOF: u32 = 0x0600; // Keccak256 hint codes -const HINT_KECCAK256: u32 = 0x0700; +pub const HINT_KECCAK256: u32 = 0x0700; /// Control code variants for stream control. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -147,7 +150,7 @@ pub enum BuiltInHint { // Secp256k1 hint types. /// secp256k1 ECDSA signature recovery. - Secp256k1Recover = HINT_SECP256K1_RECOVER, + Secp256k1Recover = HINT_SECP256K1_ECDSA_ADDRESS_RECOVER, /// secp256r1 (P-256) signature verification. Secp256r1EcdsaVerify = HINT_SECP256R1_ECDSA_VERIFY, @@ -224,7 +227,7 @@ impl TryFrom for BuiltInHint { HINT_BN254_G1_MUL => Ok(Self::Bn254G1Mul), HINT_BN254_PAIRING_CHECK => Ok(Self::Bn254PairingCheck), // Secp256k1 Hints - HINT_SECP256K1_RECOVER => Ok(Self::Secp256k1Recover), + HINT_SECP256K1_ECDSA_ADDRESS_RECOVER => Ok(Self::Secp256k1Recover), HINT_SECP256R1_ECDSA_VERIFY => Ok(Self::Secp256r1EcdsaVerify), // BLS12-381 Hints HINT_BLS12_381_G1_ADD => Ok(Self::Bls12_381G1Add), @@ -303,7 +306,7 @@ impl HintCode { HintCode::BuiltIn(BuiltInHint::Bn254G1Mul) => HINT_BN254_G1_MUL, HintCode::BuiltIn(BuiltInHint::Bn254PairingCheck) => HINT_BN254_PAIRING_CHECK, // Secp256k1 Hints - HintCode::BuiltIn(BuiltInHint::Secp256k1Recover) => HINT_SECP256K1_RECOVER, + HintCode::BuiltIn(BuiltInHint::Secp256k1Recover) => HINT_SECP256K1_ECDSA_ADDRESS_RECOVER, HintCode::BuiltIn(BuiltInHint::Secp256r1EcdsaVerify) => HINT_SECP256R1_ECDSA_VERIFY, // BLS12-381 Hints HintCode::BuiltIn(BuiltInHint::Bls12_381G1Add) => HINT_BLS12_381_G1_ADD, diff --git a/ziskos/entrypoint/src/hints/bls12_381.rs b/ziskos/entrypoint/src/hints/bls12_381.rs index fe55c49eb..4d2e05dd6 100644 --- a/ziskos/entrypoint/src/hints/bls12_381.rs +++ b/ziskos/entrypoint/src/hints/bls12_381.rs @@ -1,16 +1,17 @@ use crate::hints::macros::{define_hint, define_hint_pairs}; - -const BLS12_381_G1_ADD_HINT_ID: u32 = 0x0400; -const BLS12_381_G1_MSM_HINT_ID: u32 = 0x0401; -const BLS12_381_G2_ADD_HINT_ID: u32 = 0x0405; -const BLS12_381_G2_MSM_HINT_ID: u32 = 0x0406; -const BLS12_381_PAIRING_CHECK_HINT_ID: u32 = 0x040A; -const BLS12_381_FP_TO_G1_HINT_ID: u32 = 0x0410; -const BLS12_381_FP2_TO_G2_HINT_ID: u32 = 0x0411; +use zisk_common::{ + HINT_BLS12_381_G1_ADD, + HINT_BLS12_381_G1_MSM, + HINT_BLS12_381_G2_ADD, + HINT_BLS12_381_G2_MSM, + HINT_BLS12_381_PAIRING_CHECK, + HINT_BLS12_381_FP_TO_G1, + HINT_BLS12_381_FP2_TO_G2, +}; define_hint! { bls12_381_g1_add => { - hint_id: BLS12_381_G1_ADD_HINT_ID, + hint_id: HINT_BLS12_381_G1_ADD, params: (a: 96, b: 96), is_result: false, } @@ -18,7 +19,7 @@ define_hint! { define_hint_pairs! { bls12_381_g1_msm => { - hint_id: BLS12_381_G1_MSM_HINT_ID, + hint_id: HINT_BLS12_381_G1_MSM, pair_len: 96 + 32, is_result: false, } @@ -26,7 +27,7 @@ define_hint_pairs! { define_hint! { bls12_381_g2_add => { - hint_id: BLS12_381_G2_ADD_HINT_ID, + hint_id: HINT_BLS12_381_G2_ADD, params: (a: 192, b: 192), is_result: false, } @@ -34,7 +35,7 @@ define_hint! { define_hint_pairs! { bls12_381_g2_msm => { - hint_id: BLS12_381_G2_MSM_HINT_ID, + hint_id: HINT_BLS12_381_G2_MSM, pair_len: 192 + 32, is_result: false, } @@ -42,7 +43,7 @@ define_hint_pairs! { define_hint_pairs! { bls12_381_pairing_check => { - hint_id: BLS12_381_PAIRING_CHECK_HINT_ID, + hint_id: HINT_BLS12_381_PAIRING_CHECK, pair_len: 96 + 192, is_result: false, } @@ -50,7 +51,7 @@ define_hint_pairs! { define_hint! { bls12_381_fp_to_g1 => { - hint_id: BLS12_381_FP_TO_G1_HINT_ID, + hint_id: HINT_BLS12_381_FP_TO_G1, params: (fp: 48), is_result: false, } @@ -58,7 +59,7 @@ define_hint! { define_hint! { bls12_381_fp2_to_g2 => { - hint_id: BLS12_381_FP2_TO_G2_HINT_ID, + hint_id: HINT_BLS12_381_FP2_TO_G2, params: (fp2: 96), is_result: false, } diff --git a/ziskos/entrypoint/src/hints/bn254.rs b/ziskos/entrypoint/src/hints/bn254.rs index 5d438e8dd..c280719d9 100644 --- a/ziskos/entrypoint/src/hints/bn254.rs +++ b/ziskos/entrypoint/src/hints/bn254.rs @@ -1,12 +1,13 @@ use crate::hints::macros::{define_hint, define_hint_pairs}; - -const BN254_G1_ADD_HINT_ID: u32 = 0x0200; -const BN254_G1_MUL_HINT_ID: u32 = 0x0201; -const BN254_PAIRING_CHECK_HINT_ID: u32 = 0x0205; +use zisk_common::{ + HINT_BN254_G1_ADD, + HINT_BN254_G1_MUL, + HINT_BN254_PAIRING_CHECK, +}; define_hint! { bn254_g1_add => { - hint_id: BN254_G1_ADD_HINT_ID, + hint_id: HINT_BN254_G1_ADD, params: (p1: 64, p2: 64), is_result: false, } @@ -14,7 +15,7 @@ define_hint! { define_hint! { bn254_g1_mul => { - hint_id: BN254_G1_MUL_HINT_ID, + hint_id: HINT_BN254_G1_MUL, params: (point: 64, scalar: 32), is_result: false, } @@ -22,7 +23,7 @@ define_hint! { define_hint_pairs! { bn254_pairing_check => { - hint_id: BN254_PAIRING_CHECK_HINT_ID, + hint_id: HINT_BN254_PAIRING_CHECK, pair_len: 64 + 128, is_result: false, } diff --git a/ziskos/entrypoint/src/hints/keccak256.rs b/ziskos/entrypoint/src/hints/keccak256.rs index 5807626c4..ebb388ed1 100644 --- a/ziskos/entrypoint/src/hints/keccak256.rs +++ b/ziskos/entrypoint/src/hints/keccak256.rs @@ -1,10 +1,9 @@ use crate::hints::macros::define_hint_ptr; - -const KECCAK256_HINT_ID: u32 = 0x0700; +use zisk_common::HINT_KECCAK256; define_hint_ptr! { keccak256 => { - hint_id: KECCAK256_HINT_ID, + hint_id: HINT_KECCAK256, param: input, is_result: false, } diff --git a/ziskos/entrypoint/src/hints/kzg.rs b/ziskos/entrypoint/src/hints/kzg.rs index 9d322f28c..0985b86d5 100644 --- a/ziskos/entrypoint/src/hints/kzg.rs +++ b/ziskos/entrypoint/src/hints/kzg.rs @@ -1,10 +1,9 @@ use crate::hints::macros::define_hint; - -const KZG_VERIFY_PROOF_HINT_ID: u32 = 0x0600; +use zisk_common::HINT_VERIFY_KZG_PROOF; define_hint! { verify_kzg_proof => { - hint_id: KZG_VERIFY_PROOF_HINT_ID, + hint_id: HINT_VERIFY_KZG_PROOF, params: (z: 32, y: 32, commitment: 48, proof: 48), is_result: false, } diff --git a/ziskos/entrypoint/src/hints/modexp.rs b/ziskos/entrypoint/src/hints/modexp.rs index 617d3251b..e038a81de 100644 --- a/ziskos/entrypoint/src/hints/modexp.rs +++ b/ziskos/entrypoint/src/hints/modexp.rs @@ -1,10 +1,9 @@ use crate::hints::macros::define_hint_ptr; - -const MODEXP_HINT_ID: u32 = 0x0500; +use zisk_common::HINT_MODEXP; define_hint_ptr! { modexp_bytes => { - hint_id: MODEXP_HINT_ID, + hint_id: HINT_MODEXP, params: (base, exp, modulus), is_result: false, } diff --git a/ziskos/entrypoint/src/hints/secp256k1.rs b/ziskos/entrypoint/src/hints/secp256k1.rs index 03172c8bc..6bc045a44 100644 --- a/ziskos/entrypoint/src/hints/secp256k1.rs +++ b/ziskos/entrypoint/src/hints/secp256k1.rs @@ -1,11 +1,9 @@ use crate::hints::macros::define_hint; - -const SECP256K1_ECDSA_RECOVER_HINT_ID: u32 = 0x0300; -const SECP256K1_ECDSA_VERIFY_HINT_ID: u32 = 0x0302; +use zisk_common::{HINT_SECP256K1_ECDSA_ADDRESS_RECOVER, HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER}; define_hint! { secp256k1_ecdsa_address_recover => { - hint_id: SECP256K1_ECDSA_RECOVER_HINT_ID, + hint_id: HINT_SECP256K1_ECDSA_ADDRESS_RECOVER, params: (sig: 64, recid: 8, msg: 32), is_result: false, } @@ -13,7 +11,7 @@ define_hint! { define_hint! { secp256k1_ecdsa_verify_and_address_recover => { - hint_id: SECP256K1_ECDSA_VERIFY_HINT_ID, + hint_id: HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER, params: (sig: 64, msg: 32, pk: 64), is_result: false, } diff --git a/ziskos/entrypoint/src/hints/secp256r1.rs b/ziskos/entrypoint/src/hints/secp256r1.rs index 5f67fd580..41ef46ec7 100644 --- a/ziskos/entrypoint/src/hints/secp256r1.rs +++ b/ziskos/entrypoint/src/hints/secp256r1.rs @@ -1,10 +1,9 @@ use crate::hints::macros::define_hint; - -const SECP256R1_ECDSA_VERIFY_HINT_ID: u32 = 0x0301; +use zisk_common::HINT_SECP256R1_ECDSA_VERIFY; define_hint! { secp256r1_ecdsa_verify => { - hint_id: SECP256R1_ECDSA_VERIFY_HINT_ID, + hint_id: HINT_SECP256R1_ECDSA_VERIFY, params: (msg: 32, sig: 64, pk: 64), is_result: false, } diff --git a/ziskos/entrypoint/src/hints/sha256f.rs b/ziskos/entrypoint/src/hints/sha256f.rs index cfd6b805a..fd0708394 100644 --- a/ziskos/entrypoint/src/hints/sha256f.rs +++ b/ziskos/entrypoint/src/hints/sha256f.rs @@ -1,10 +1,9 @@ use crate::hints::macros::define_hint_ptr; - -const SHA256_HINT_ID: u32 = 0x0100; +use zisk_common::HINT_SHA256; define_hint_ptr! { sha256 => { - hint_id: SHA256_HINT_ID, + hint_id: HINT_SHA256, param: f, is_result: false, } From 36b123aa8491f57e7e3f47e270353acb28521574 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Fri, 6 Feb 2026 13:14:01 +0000 Subject: [PATCH 456/782] Fix Clippy --- common/src/hints.rs | 4 +++- ziskos/entrypoint/src/hints/bls12_381.rs | 8 ++------ ziskos/entrypoint/src/hints/bn254.rs | 6 +----- ziskos/entrypoint/src/hints/secp256k1.rs | 4 +++- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index d675532cf..26d8a4e2b 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -306,7 +306,9 @@ impl HintCode { HintCode::BuiltIn(BuiltInHint::Bn254G1Mul) => HINT_BN254_G1_MUL, HintCode::BuiltIn(BuiltInHint::Bn254PairingCheck) => HINT_BN254_PAIRING_CHECK, // Secp256k1 Hints - HintCode::BuiltIn(BuiltInHint::Secp256k1Recover) => HINT_SECP256K1_ECDSA_ADDRESS_RECOVER, + HintCode::BuiltIn(BuiltInHint::Secp256k1Recover) => { + HINT_SECP256K1_ECDSA_ADDRESS_RECOVER + } HintCode::BuiltIn(BuiltInHint::Secp256r1EcdsaVerify) => HINT_SECP256R1_ECDSA_VERIFY, // BLS12-381 Hints HintCode::BuiltIn(BuiltInHint::Bls12_381G1Add) => HINT_BLS12_381_G1_ADD, diff --git a/ziskos/entrypoint/src/hints/bls12_381.rs b/ziskos/entrypoint/src/hints/bls12_381.rs index 4d2e05dd6..d224ec80a 100644 --- a/ziskos/entrypoint/src/hints/bls12_381.rs +++ b/ziskos/entrypoint/src/hints/bls12_381.rs @@ -1,12 +1,8 @@ use crate::hints::macros::{define_hint, define_hint_pairs}; use zisk_common::{ - HINT_BLS12_381_G1_ADD, - HINT_BLS12_381_G1_MSM, - HINT_BLS12_381_G2_ADD, - HINT_BLS12_381_G2_MSM, + HINT_BLS12_381_FP2_TO_G2, HINT_BLS12_381_FP_TO_G1, HINT_BLS12_381_G1_ADD, + HINT_BLS12_381_G1_MSM, HINT_BLS12_381_G2_ADD, HINT_BLS12_381_G2_MSM, HINT_BLS12_381_PAIRING_CHECK, - HINT_BLS12_381_FP_TO_G1, - HINT_BLS12_381_FP2_TO_G2, }; define_hint! { diff --git a/ziskos/entrypoint/src/hints/bn254.rs b/ziskos/entrypoint/src/hints/bn254.rs index c280719d9..f495248de 100644 --- a/ziskos/entrypoint/src/hints/bn254.rs +++ b/ziskos/entrypoint/src/hints/bn254.rs @@ -1,9 +1,5 @@ use crate::hints::macros::{define_hint, define_hint_pairs}; -use zisk_common::{ - HINT_BN254_G1_ADD, - HINT_BN254_G1_MUL, - HINT_BN254_PAIRING_CHECK, -}; +use zisk_common::{HINT_BN254_G1_ADD, HINT_BN254_G1_MUL, HINT_BN254_PAIRING_CHECK}; define_hint! { bn254_g1_add => { diff --git a/ziskos/entrypoint/src/hints/secp256k1.rs b/ziskos/entrypoint/src/hints/secp256k1.rs index 6bc045a44..4ca4dd3c8 100644 --- a/ziskos/entrypoint/src/hints/secp256k1.rs +++ b/ziskos/entrypoint/src/hints/secp256k1.rs @@ -1,5 +1,7 @@ use crate::hints::macros::define_hint; -use zisk_common::{HINT_SECP256K1_ECDSA_ADDRESS_RECOVER, HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER}; +use zisk_common::{ + HINT_SECP256K1_ECDSA_ADDRESS_RECOVER, HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER, +}; define_hint! { secp256k1_ecdsa_address_recover => { From 9fe4405b556a6266df0c79b082be1d57d7e6ea95 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 6 Feb 2026 07:09:48 +0000 Subject: [PATCH 457/782] feat: add ecdsa verify and address recover hint handler --- common/src/hints.rs | 26 ++++++++++--- precompiles/hints/src/hints_processor.rs | 7 +++- ziskos-hints/src/handlers/secp256k1.rs | 48 +++++++++++++++++++++--- 3 files changed, 69 insertions(+), 12 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 26d8a4e2b..af8f0b782 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -149,8 +149,12 @@ pub enum BuiltInHint { Bn254PairingCheck = HINT_BN254_PAIRING_CHECK, // Secp256k1 hint types. - /// secp256k1 ECDSA signature recovery. - Secp256k1Recover = HINT_SECP256K1_ECDSA_ADDRESS_RECOVER, + /// secp256k1 ECDSA address recovery + Secp256k1EcdsaAddressRecover = HINT_SECP256K1_ECDSA_ADDRESS_RECOVER, + /// secp256k1 ECDSA signature verification and address recovery. + Secp256k1EcdsaVerifyAddressRecover = HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER, + + // Secp256r1 hint types. /// secp256r1 (P-256) signature verification. Secp256r1EcdsaVerify = HINT_SECP256R1_ECDSA_VERIFY, @@ -193,7 +197,11 @@ impl Display for BuiltInHint { BuiltInHint::Bn254G1Mul => "BN254_G1_MUL", BuiltInHint::Bn254PairingCheck => "BN254_PAIRING_CHECK", // Secp256k1 Hints - BuiltInHint::Secp256k1Recover => "SECP256K1_RECOVER", + BuiltInHint::Secp256k1EcdsaAddressRecover => "SECP256K1_ECDSA_ADDRESS_RECOVER", + BuiltInHint::Secp256k1EcdsaVerifyAddressRecover => { + "SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER" + } + // Secp256r1 Hints BuiltInHint::Secp256r1EcdsaVerify => "SECP256R1_ECDSA_VERIFY", // BLS12-381 Hints BuiltInHint::Bls12_381G1Add => "BLS12_381_G1_ADD", @@ -227,7 +235,11 @@ impl TryFrom for BuiltInHint { HINT_BN254_G1_MUL => Ok(Self::Bn254G1Mul), HINT_BN254_PAIRING_CHECK => Ok(Self::Bn254PairingCheck), // Secp256k1 Hints - HINT_SECP256K1_ECDSA_ADDRESS_RECOVER => Ok(Self::Secp256k1Recover), + HINT_SECP256K1_ECDSA_ADDRESS_RECOVER => Ok(Self::Secp256k1EcdsaAddressRecover), + HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER => { + Ok(Self::Secp256k1EcdsaVerifyAddressRecover) + } + // Secp256r1 Hints HINT_SECP256R1_ECDSA_VERIFY => Ok(Self::Secp256r1EcdsaVerify), // BLS12-381 Hints HINT_BLS12_381_G1_ADD => Ok(Self::Bls12_381G1Add), @@ -306,9 +318,13 @@ impl HintCode { HintCode::BuiltIn(BuiltInHint::Bn254G1Mul) => HINT_BN254_G1_MUL, HintCode::BuiltIn(BuiltInHint::Bn254PairingCheck) => HINT_BN254_PAIRING_CHECK, // Secp256k1 Hints - HintCode::BuiltIn(BuiltInHint::Secp256k1Recover) => { + HintCode::BuiltIn(BuiltInHint::Secp256k1EcdsaAddressRecover) => { HINT_SECP256K1_ECDSA_ADDRESS_RECOVER } + HintCode::BuiltIn(BuiltInHint::Secp256k1EcdsaVerifyAddressRecover) => { + HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER + } + // Secp256r1 Hints HintCode::BuiltIn(BuiltInHint::Secp256r1EcdsaVerify) => HINT_SECP256R1_ECDSA_VERIFY, // BLS12-381 Hints HintCode::BuiltIn(BuiltInHint::Bls12_381G1Add) => HINT_BLS12_381_G1_ADD, diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 5752bf8cc..9baaaae30 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -25,7 +25,7 @@ use ziskos_hints::handlers::bn254::{ use ziskos_hints::handlers::keccak256::keccak256_hint; use ziskos_hints::handlers::kzg::verify_kzg_proof_hint; use ziskos_hints::handlers::modexp::modexp_hint; -use ziskos_hints::handlers::secp256k1::secp256k1_recover_hint; +use ziskos_hints::handlers::secp256k1::{secp256k1_ecdsa_address_recover, secp256k1_ecdsa_verify_address_recover}; use ziskos_hints::handlers::secp256r1::secp256r1_ecdsa_verify_hint; use ziskos_hints::handlers::sha256::sha256_hint; @@ -659,7 +659,10 @@ impl HintsProcessor { BuiltInHint::Bn254PairingCheck => bn254_pairing_check_hint(&data), // TODO: check // Secp256k1 Hints - BuiltInHint::Secp256k1Recover => secp256k1_recover_hint(&data), + BuiltInHint::Secp256k1EcdsaAddressRecover => secp256k1_ecdsa_address_recover(&data), + BuiltInHint::Secp256k1EcdsaVerifyAddressRecover => secp256k1_ecdsa_verify_address_recover(&data), + + // Secp256r1 Hints BuiltInHint::Secp256r1EcdsaVerify => secp256r1_ecdsa_verify_hint(&data), // BLS12-381 Hint Codes diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index dea18360b..854a8af4a 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -4,14 +4,19 @@ use crate::zisklib; use anyhow::Result; -/// Processes an `HINT_SECP256K1_RECOVER` hint. +/// Processes an `HINT_SECP256K1_ECDSA_ADDRESS_RECOVER` hint. #[inline] -pub fn secp256k1_recover_hint(data: &[u64]) -> Result> { - hint_fields![SIG: 64, RECID: 8, MSG: 32, LO_S: 8]; +pub fn secp256k1_ecdsa_address_recover(data: &[u64]) -> Result> { + hint_fields![SIG: 64, RECID: 8, MSG: 32]; - let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 8) }; + let bytes = unsafe { + std::slice::from_raw_parts( + data.as_ptr() as *const u8, + data.len() * std::mem::size_of::(), + ) + }; - validate_hint_length(bytes, EXPECTED_LEN, "HINT_SECP256K1_RECOVER")?; + validate_hint_length(bytes, EXPECTED_LEN, "HINT_SECP256K1_ECDSA_ADDRESS_RECOVER")?; let sig: &[u8; SIG_SIZE] = bytes[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); let recid: &[u8; RECID_SIZE] = @@ -33,3 +38,36 @@ pub fn secp256k1_recover_hint(data: &[u64]) -> Result> { Ok(hints) } + +/// Processes an `HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER` hint. +#[inline] +pub fn secp256k1_ecdsa_verify_address_recover(data: &[u64]) -> Result> { + hint_fields![SIG: 64, MSG: 32, PK: 64]; + + let bytes = unsafe { + std::slice::from_raw_parts( + data.as_ptr() as *const u8, + data.len() * std::mem::size_of::(), + ) + }; + + validate_hint_length(bytes, EXPECTED_LEN, "HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER")?; + + let sig: &[u8; SIG_SIZE] = bytes[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); + let msg: &[u8; MSG_SIZE] = bytes[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); + let pk: &[u8; PK_SIZE] = bytes[PK_OFFSET..PK_OFFSET + PK_SIZE].try_into().unwrap(); + + let mut hints = Vec::new(); + let result: &mut [u8; 32] = &mut [0u8; 32]; + unsafe { + zisklib::secp256k1_ecdsa_verify_and_address_recover_c( + sig.as_ptr(), + msg.as_ptr(), + pk.as_ptr(), + result.as_mut_ptr(), + &mut hints, + ); + } + + Ok(hints) +} From 570127212ce6f24c43d38372da1f0c8bb23dae54 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:15:15 +0000 Subject: [PATCH 458/782] improve secp256k1 --- precompiles/hints/src/hints_processor.rs | 8 +++-- ziskos-hints/src/handlers/secp256k1.rs | 46 ++++++++---------------- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 9baaaae30..dd95fa1c6 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -25,7 +25,9 @@ use ziskos_hints::handlers::bn254::{ use ziskos_hints::handlers::keccak256::keccak256_hint; use ziskos_hints::handlers::kzg::verify_kzg_proof_hint; use ziskos_hints::handlers::modexp::modexp_hint; -use ziskos_hints::handlers::secp256k1::{secp256k1_ecdsa_address_recover, secp256k1_ecdsa_verify_address_recover}; +use ziskos_hints::handlers::secp256k1::{ + secp256k1_ecdsa_address_recover, secp256k1_ecdsa_verify_address_recover, +}; use ziskos_hints::handlers::secp256r1::secp256r1_ecdsa_verify_hint; use ziskos_hints::handlers::sha256::sha256_hint; @@ -660,7 +662,9 @@ impl HintsProcessor { // Secp256k1 Hints BuiltInHint::Secp256k1EcdsaAddressRecover => secp256k1_ecdsa_address_recover(&data), - BuiltInHint::Secp256k1EcdsaVerifyAddressRecover => secp256k1_ecdsa_verify_address_recover(&data), + BuiltInHint::Secp256k1EcdsaVerifyAddressRecover => { + secp256k1_ecdsa_verify_address_recover(&data) + } // Secp256r1 Hints BuiltInHint::Secp256r1EcdsaVerify => secp256r1_ecdsa_verify_hint(&data), diff --git a/ziskos-hints/src/handlers/secp256k1.rs b/ziskos-hints/src/handlers/secp256k1.rs index 854a8af4a..e0ca2321d 100644 --- a/ziskos-hints/src/handlers/secp256k1.rs +++ b/ziskos-hints/src/handlers/secp256k1.rs @@ -7,30 +7,21 @@ use anyhow::Result; /// Processes an `HINT_SECP256K1_ECDSA_ADDRESS_RECOVER` hint. #[inline] pub fn secp256k1_ecdsa_address_recover(data: &[u64]) -> Result> { - hint_fields![SIG: 64, RECID: 8, MSG: 32]; + hint_fields![SIG: 8, RECID: 1, MSG: 4]; - let bytes = unsafe { - std::slice::from_raw_parts( - data.as_ptr() as *const u8, - data.len() * std::mem::size_of::(), - ) - }; + validate_hint_length(data, EXPECTED_LEN, "HINT_SECP256K1_ECDSA_ADDRESS_RECOVER")?; - validate_hint_length(bytes, EXPECTED_LEN, "HINT_SECP256K1_ECDSA_ADDRESS_RECOVER")?; - - let sig: &[u8; SIG_SIZE] = bytes[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); - let recid: &[u8; RECID_SIZE] = - bytes[RECID_OFFSET..RECID_OFFSET + RECID_SIZE].try_into().unwrap(); - let recid: u8 = u64::from_le_bytes(*recid) as u8; - let msg: &[u8; MSG_SIZE] = bytes[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); + let sig: &[u64; SIG_SIZE] = data[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); + let recid: u8 = data[RECID_OFFSET] as u8; + let msg: &[u64; MSG_SIZE] = data[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); let mut hints = Vec::new(); let result: &mut [u8; 32] = &mut [0u8; 32]; unsafe { zisklib::secp256k1_ecdsa_address_recover_c( - sig.as_ptr(), + sig.as_ptr() as *const u8, recid, - msg.as_ptr(), + msg.as_ptr() as *const u8, result.as_mut_ptr(), &mut hints, ); @@ -42,28 +33,21 @@ pub fn secp256k1_ecdsa_address_recover(data: &[u64]) -> Result> { /// Processes an `HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER` hint. #[inline] pub fn secp256k1_ecdsa_verify_address_recover(data: &[u64]) -> Result> { - hint_fields![SIG: 64, MSG: 32, PK: 64]; - - let bytes = unsafe { - std::slice::from_raw_parts( - data.as_ptr() as *const u8, - data.len() * std::mem::size_of::(), - ) - }; + hint_fields![SIG: 8, MSG: 4, PK: 8]; - validate_hint_length(bytes, EXPECTED_LEN, "HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER")?; + validate_hint_length(data, EXPECTED_LEN, "HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER")?; - let sig: &[u8; SIG_SIZE] = bytes[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); - let msg: &[u8; MSG_SIZE] = bytes[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); - let pk: &[u8; PK_SIZE] = bytes[PK_OFFSET..PK_OFFSET + PK_SIZE].try_into().unwrap(); + let sig: &[u64; SIG_SIZE] = data[SIG_OFFSET..SIG_OFFSET + SIG_SIZE].try_into().unwrap(); + let msg: &[u64; MSG_SIZE] = data[MSG_OFFSET..MSG_OFFSET + MSG_SIZE].try_into().unwrap(); + let pk: &[u64; PK_SIZE] = data[PK_OFFSET..PK_OFFSET + PK_SIZE].try_into().unwrap(); let mut hints = Vec::new(); let result: &mut [u8; 32] = &mut [0u8; 32]; unsafe { zisklib::secp256k1_ecdsa_verify_and_address_recover_c( - sig.as_ptr(), - msg.as_ptr(), - pk.as_ptr(), + sig.as_ptr() as *const u8, + msg.as_ptr() as *const u8, + pk.as_ptr() as *const u8, result.as_mut_ptr(), &mut hints, ); From bd1885021db874f79edd6ecbe774632cba1ea433 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:41:42 +0000 Subject: [PATCH 459/782] fix typo --- common/src/hints.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index af8f0b782..14a172e82 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -149,7 +149,7 @@ pub enum BuiltInHint { Bn254PairingCheck = HINT_BN254_PAIRING_CHECK, // Secp256k1 hint types. - /// secp256k1 ECDSA address recovery + /// secp256k1 ECDSA address recovery. Secp256k1EcdsaAddressRecover = HINT_SECP256K1_ECDSA_ADDRESS_RECOVER, /// secp256k1 ECDSA signature verification and address recovery. Secp256k1EcdsaVerifyAddressRecover = HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER, From d40b23b12b7e811bbbcc575b8365b981726f1903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Fri, 6 Feb 2026 15:08:29 +0100 Subject: [PATCH 460/782] Feature/proof with publics (#763) * Adding proof with publics struct * Some other changes * Fix elapsed * Minor fixes --- Cargo.lock | 1 + cli/src/commands/prove.rs | 85 +++---- cli/src/commands/prove_snark.rs | 18 +- cli/src/commands/verify_stark.rs | 28 +-- common/src/lib.rs | 2 - common/src/proof.rs | 70 ------ distributed/crates/coordinator/Cargo.toml | 1 + .../crates/coordinator/src/coordinator.rs | 27 ++- examples/Cargo.lock | 21 +- examples/sha-hasher/host/Cargo.toml | 21 ++ examples/sha-hasher/host/bin/compressed.rs | 49 ++++ examples/sha-hasher/host/bin/execute.rs | 50 ++++ examples/sha-hasher/host/bin/plonk.rs | 52 ++++ examples/sha-hasher/host/bin/prove.rs | 82 +++++++ examples/sha-hasher/host/src/main.rs | 39 +-- lib-float/c/lib/libziskfloat.a | Bin 332576 -> 332576 bytes sdk/src/lib.rs | 3 +- sdk/src/prover/asm.rs | 14 +- sdk/src/prover/backend.rs | 164 +++++++------ sdk/src/prover/emu.rs | 14 +- sdk/src/prover/mod.rs | 223 ++++++++++++++++-- sdk/src/verifier.rs | 116 +++++++++ verifier/src/verifier.rs | 9 +- ziskos/entrypoint/src/io.rs | 4 +- ziskos/entrypoint/src/lib.rs | 4 +- 25 files changed, 791 insertions(+), 306 deletions(-) delete mode 100644 common/src/proof.rs create mode 100644 examples/sha-hasher/host/bin/compressed.rs create mode 100644 examples/sha-hasher/host/bin/execute.rs create mode 100644 examples/sha-hasher/host/bin/plonk.rs create mode 100644 examples/sha-hasher/host/bin/prove.rs create mode 100644 sdk/src/verifier.rs diff --git a/Cargo.lock b/Cargo.lock index 3b31db1d3..d74536f62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6180,6 +6180,7 @@ dependencies = [ "zisk-common", "zisk-distributed-common", "zisk-distributed-grpc-api", + "zisk-sdk", ] [[package]] diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index bbd7ef7f7..db6bb14cd 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -2,9 +2,7 @@ use crate::ux::{print_banner, print_banner_field}; use anyhow::Result; use colored::Colorize; -use proofman::{get_vadcop_final_proof_vkey, SnarkProof, SnarkProtocol}; use proofman_common::ParamsGPU; -use proofman_util::VadcopFinalProof; use std::fs; use std::path::PathBuf; use tracing::warn; @@ -13,8 +11,7 @@ use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ElfBinaryOwned; #[cfg(feature = "stats")] use zisk_common::ExecutorStatsEvent; -use zisk_sdk::ZiskProgramVK; -use zisk_sdk::{get_proving_key, ProofOpts, ProverClient, ZiskProof, ZiskProveResult}; +use zisk_sdk::{ProofOpts, ProverClient, ZiskProof, ZiskProveResult}; // Structure representing the 'prove' subcommand of cargo. #[derive(clap::Args)] @@ -164,69 +161,41 @@ impl ZiskProve { self.emulator }; - let (vk, result, world_rank) = if emulator { + let (result, world_rank) = if emulator { self.run_emu(stdin, gpu_params)? } else { self.run_asm(stdin, hints_stream, gpu_params)? }; if world_rank == 0 { - let elapsed = result.duration.as_secs_f64(); + let elapsed = result.get_duration().as_secs_f64(); tracing::info!(""); tracing::info!( "{}", "--- PROVE SUMMARY ------------------------".bright_green().bold() ); - match result.proof { - ZiskProof::VadcopFinal(ref proof) | ZiskProof::VadcopFinalCompressed(ref proof) => { - let compressed = matches!(result.proof, ZiskProof::VadcopFinalCompressed(_)); - let vadcop_final_proof = VadcopFinalProof::new( - proof.clone(), - result.publics.bytes_u64(&vk), - compressed, - ); - vadcop_final_proof - .save(self.output_dir.join("vadcop_final_proof.bin")) - .map_err(|e| { - anyhow::anyhow!( - "Failed to save VadcopFinalProof to output dir {:?}: {}", - self.output_dir.join("vadcop_final_proof.bin").display(), - e - ) - })?; - } - ZiskProof::Plonk(ref proof) | ZiskProof::Fflonk(ref proof) => { - let protocol_id = match result.proof { - ZiskProof::Plonk(_) => SnarkProtocol::Plonk.protocol_id(), - ZiskProof::Fflonk(_) => SnarkProtocol::Fflonk.protocol_id(), - _ => unreachable!(), - }; - let proving_key = get_proving_key(self.proving_key.as_ref()); - let vadcop_verkey = get_vadcop_final_proof_vkey(&proving_key, false)?; - - let snark_proof = SnarkProof { - proof_bytes: proof.clone(), - public_bytes: result.publics.bytes_solidity(&vk, &vadcop_verkey), - public_snark_bytes: result.publics.hash_solidity(&vk, &vadcop_verkey), - protocol_id, - }; - snark_proof.save(self.output_dir.join("snark_proof.bin")).map_err(|e| { - anyhow::anyhow!( - "Failed to save SnarkProof to output dir {:?}: {}", - self.output_dir.join("snark_proof.bin").display(), - e - ) - })?; - } - ZiskProof::Null() => {} - } - - if let Some(proof_id) = &result.proof_id { + if let Some(proof_id) = &result.get_proof_id() { + let output_dir = match result.get_proof() { + ZiskProof::VadcopFinal(_) | ZiskProof::VadcopFinalCompressed(_) => { + self.output_dir.join("vadcop_final_proof.bin") + } + ZiskProof::Plonk(_) | ZiskProof::Fflonk(_) => { + self.output_dir.join("final_snark_proof.bin") + } + _ => { + return Err(anyhow::anyhow!("Unsupported proof type for saving proof file")) + } + }; + result.save_proof_with_publics(output_dir)?; tracing::info!(" Proof ID: {}", proof_id); } tracing::info!(" ► Statistics"); - tracing::info!(" time: {} seconds, steps: {}", elapsed, result.execution.steps); + tracing::info!( + " time: {} seconds, steps: {}", + elapsed, + result.get_execution_steps() + ); } Ok(()) @@ -236,7 +205,7 @@ impl ZiskProve { &mut self, stdin: ZiskStdin, gpu_params: Option, - ) -> Result<(ZiskProgramVK, ZiskProveResult, i32)> { + ) -> Result<(ZiskProveResult, i32)> { let prover = ProverClient::builder() .aggregation(self.aggregation) .proving_key_path_opt(self.proving_key.clone()) @@ -253,7 +222,7 @@ impl ZiskProve { self.elf.file_stem().unwrap().to_str().unwrap().to_string(), false, ); - let vk = prover.setup(&elf)?; + prover.setup(&elf)?; let proof_options = ProofOpts { aggregation: self.aggregation, @@ -267,7 +236,7 @@ impl ZiskProve { let result = prover.prove(stdin).with_proof_options(proof_options).run()?; let world_rank = prover.world_rank(); - Ok((vk, result, world_rank)) + Ok((result, world_rank)) } pub fn run_asm( @@ -275,7 +244,7 @@ impl ZiskProve { stdin: ZiskStdin, hints_stream: Option, gpu_params: Option, - ) -> Result<(ZiskProgramVK, ZiskProveResult, i32)> { + ) -> Result<(ZiskProveResult, i32)> { let prover = ProverClient::builder() .aggregation(self.aggregation) .asm() @@ -296,7 +265,7 @@ impl ZiskProve { self.elf.file_stem().unwrap().to_str().unwrap().to_string(), hints_stream.is_some(), ); - let vk = prover.setup(&elf)?; + prover.setup(&elf)?; let proof_options = ProofOpts { aggregation: self.aggregation, @@ -313,6 +282,6 @@ impl ZiskProve { let result = prover.prove(stdin).with_proof_options(proof_options).run()?; let world_rank = prover.world_rank(); - Ok((vk, result, world_rank)) + Ok((result, world_rank)) } } diff --git a/cli/src/commands/prove_snark.rs b/cli/src/commands/prove_snark.rs index 1fa861297..15862f9cb 100644 --- a/cli/src/commands/prove_snark.rs +++ b/cli/src/commands/prove_snark.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use crate::ux::print_banner; use proofman::SnarkWrapper; -use proofman_util::VadcopFinalProof; +use zisk_sdk::ZiskProofWithPublicValues; #[derive(Parser)] #[command(version, about, long_about = None)] @@ -16,6 +16,12 @@ pub struct ZiskProveSnark { #[clap(short = 'p', long)] pub proof: String, + /// ELF file path + /// This is the path to the ROM file that the witness computation dynamic library will use + /// to generate the witness. + #[clap(short = 'e', long)] + pub elf: PathBuf, + /// Setup folder path #[clap(short = 'k', long)] pub proving_key_snark: PathBuf, @@ -36,13 +42,19 @@ impl ZiskProveSnark { print_banner(); - let proof = VadcopFinalProof::load(&self.proof).map_err(|e| { - anyhow::anyhow!("Failed to load VadcopFinalProof from file {}: {}", self.proof, e) + let zisk_proof = ZiskProofWithPublicValues::load(&self.proof).map_err(|e| { + anyhow::anyhow!( + "Failed to load ZiskProofWithPublicValues from file {}: {}", + self.proof, + e + ) })?; let snark_wrapper: SnarkWrapper = SnarkWrapper::new(&self.proving_key_snark, self.verbose.into())?; + let proof = zisk_proof.get_vadcop_final_proof()?; + let snark_proof = snark_wrapper.generate_final_snark_proof(&proof, Some(self.output_dir.clone()))?; snark_proof.save(self.output_dir.join("final_snark_proof.bin")).map_err(|e| { diff --git a/cli/src/commands/verify_stark.rs b/cli/src/commands/verify_stark.rs index b8f1c0bbc..9cf994c49 100644 --- a/cli/src/commands/verify_stark.rs +++ b/cli/src/commands/verify_stark.rs @@ -2,12 +2,9 @@ use anyhow::Result; use clap::Parser; use colored::Colorize; use proofman_common::initialize_logger; -use proofman_util::VadcopFinalProof; -use std::fs; +use std::path::PathBuf; use zisk_build::ZISK_VERSION_MESSAGE; -use zisk_verifier::verify_zisk_proof; - -use super::get_default_verkey; +use zisk_sdk::{get_proving_key, verify_zisk_proof_with_proving_key, ZiskProofWithPublicValues}; #[derive(Parser)] #[command(author, about, long_about = None, version = ZISK_VERSION_MESSAGE)] @@ -21,7 +18,7 @@ pub struct ZiskVerify { pub verbose: u8, // Using u8 to hold the number of `-v` #[clap(short = 'k', long)] - pub vk: Option, + pub proving_key: Option, } impl ZiskVerify { @@ -36,13 +33,16 @@ impl ZiskVerify { let start = std::time::Instant::now(); - let proof = VadcopFinalProof::load(&self.proof).map_err(|e| { + let proof = ZiskProofWithPublicValues::load(&self.proof).map_err(|e| { anyhow::anyhow!("Error loading VADCoP final proof from {}: {}", &self.proof, e) })?; - let vk = &self.get_verkey(); - - let result = verify_zisk_proof(&proof, vk); + let result = verify_zisk_proof_with_proving_key( + proof.get_proof(), + proof.get_publics(), + proof.get_program_vk(), + get_proving_key(self.proving_key.as_ref()), + ); let elapsed = start.elapsed(); @@ -58,12 +58,4 @@ impl ZiskVerify { result } - - /// Gets the verification key - /// Uses the default one if not specified by user. - pub fn get_verkey(&self) -> Vec { - let vk_file = - if self.vk.is_none() { get_default_verkey() } else { self.vk.clone().unwrap() }; - fs::read(&vk_file).unwrap() - } } diff --git a/common/src/lib.rs b/common/src/lib.rs index 4e517df75..4e002f298 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -7,7 +7,6 @@ mod instance_context; pub mod io; mod mpi_context; mod planner_helpers; -mod proof; mod proof_log; mod regular_counters; mod regular_planner; @@ -22,7 +21,6 @@ pub use hints::*; pub use instance_context::*; pub use mpi_context::*; pub use planner_helpers::*; -pub use proof::*; pub use proof_log::*; pub use regular_counters::*; pub use regular_planner::*; diff --git a/common/src/proof.rs b/common/src/proof.rs deleted file mode 100644 index d14123260..000000000 --- a/common/src/proof.rs +++ /dev/null @@ -1,70 +0,0 @@ -use anyhow::Result; -use proofman_util::VadcopFinalProof; -use std::io::{Cursor, Write}; -use std::{fs, path::PathBuf}; -use zstd::Encoder; - -/// Saves a proof data to disk. -/// -/// Creates a unique filename to avoid overwriting existing proof files by appending -/// a counter suffix (_2, _3, etc.) if the initial filename already exists. -/// -/// # Arguments -/// -/// * `id` - A unique identifier for the proof -/// * `proof_folder` - The folder where proofs will be saved -/// * `proof` - The proof data as an optional VadcopFinalProof -/// * `with_zip` - Whether to also save a compressed version of the proof -/// -/// # Returns -/// -/// Returns `Ok(())` on success, or a `CoordinatorError` on failure -pub fn save_proof( - id: &str, - proof_folder: PathBuf, - proof: &VadcopFinalProof, - _with_zip: bool, -) -> Result<()> { - // Ensure the proofs directory exists - fs::create_dir_all(&proof_folder)?; - - // Generate unique filename to avoid overwriting existing files - let raw_path = proof_folder.join(format!("proof_{}.fri", id)); - - proof.save(&raw_path).map_err(|e| anyhow::anyhow!("Failed to save proof: {}", e))?; - - Ok(()) -} - -/// Compresses data using zstd and writes it to a file. -/// -/// # Arguments -/// -/// * `data` - The raw data to compress -/// * `zip_path` - Path where the compressed file will be written -/// * `compression_level` - Compression level (1 = fastest, 22 = best compression) -/// -/// # Returns -/// -/// Returns the compressed size in bytes -pub fn save_zip_proof( - data: &[u8], - zip_path: &std::path::Path, - compression_level: i32, -) -> Result { - // Compress data in memory using zstd - let mut compressed_buffer = Cursor::new(Vec::new()); - - let mut encoder = Encoder::new(&mut compressed_buffer, compression_level)?; - encoder.write_all(data)?; - encoder.finish()?; - - // Extract compressed data and get size - let compressed_data = compressed_buffer.into_inner(); - let compressed_size = compressed_data.len(); - - // Write compressed data to file - fs::write(zip_path, &compressed_data)?; - - Ok(compressed_size) -} diff --git a/distributed/crates/coordinator/Cargo.toml b/distributed/crates/coordinator/Cargo.toml index 438aeebbd..6584f790d 100644 --- a/distributed/crates/coordinator/Cargo.toml +++ b/distributed/crates/coordinator/Cargo.toml @@ -14,6 +14,7 @@ path = "src/cli/main.rs" zisk-distributed-grpc-api = { workspace = true } zisk-distributed-common = { workspace = true } zisk-common = { workspace = true } +zisk-sdk = { workspace = true } cargo-zisk = { workspace = true } proofman = { workspace = true } diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 3bb129d59..e66f932b9 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -42,6 +42,7 @@ use dashmap::DashMap; use proofman::{ContributionsInfo, ExecutionInfo}; use std::{ collections::HashMap, + fs, sync::{ atomic::{AtomicU64, Ordering}, Arc, @@ -62,7 +63,7 @@ use zisk_distributed_common::{ WorkerReconnectRequestDto, WorkerRegisterRequestDto, WorkerState, WorkersListDto, }; -use proofman_util::VadcopFinalProof; +use zisk_sdk::ZiskProofWithPublicValues; /// Trait for sending messages to workers through various communication channels. /// @@ -438,17 +439,19 @@ impl Coordinator { // Save proof to disk if state == JobState::Completed && !self.config.server.no_save_proofs { let folder = self.config.server.proofs_dir.clone(); - let vadcop_proof = VadcopFinalProof::new_from_proof(&final_proof.unwrap(), true) - .map_err(|e| { - CoordinatorError::Internal(format!("Failed to create VadcopFinalProof: {}", e)) - })?; - zisk_common::save_proof(job_id.as_str(), folder, &vadcop_proof, false).map_err( - |e| { - error!("Failed to save proof for job {}: {}", job_id, e); - job.cleanup(); - CoordinatorError::Internal(e.to_string()) - }, - )?; + + let zisk_proof = ZiskProofWithPublicValues::new_from_vadcop_proof( + &final_proof.unwrap(), + self.config.coordinator.compressed_proofs, + ) + .map_err(|e| CoordinatorError::Internal(format!("Failed to create proof: {}", e)))?; + fs::create_dir_all(&folder).map_err(|e| { + CoordinatorError::Internal(format!("Failed to create proofs directory: {}", e)) + })?; + let raw_path = folder.join(format!("proof_{}.fri", job_id)); + zisk_proof + .save(raw_path) + .map_err(|e| CoordinatorError::Internal(format!("Failed to save proof: {}", e)))?; } // Clean up process data for the job diff --git a/examples/Cargo.lock b/examples/Cargo.lock index bd8e6413d..804dfe24b 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -213,6 +213,17 @@ dependencies = [ "ark-std", ] +[[package]] +name = "ark-secp256r1" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf8be5820de567729bfa73a410ddd07cec8ad102d9a4bf61fd6b2e60db264e8" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-serialize" version = "0.5.0" @@ -1140,7 +1151,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#4836fde21c866747b2575aab03e7a07b80fb4064" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1d992d56cb6d356b3252a2cd93d34495f6640b25" dependencies = [ "cfg-if", "num-bigint", @@ -2129,6 +2140,7 @@ dependencies = [ "ark-bn254", "ark-ff", "ark-secp256k1", + "ark-secp256r1", "ark-std", "fields 0.16.0", "k256", @@ -2326,6 +2338,7 @@ dependencies = [ "ark-bn254", "ark-ff", "ark-secp256k1", + "ark-secp256r1", "ark-std", "cfg-if", "circuit", @@ -3178,7 +3191,6 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "asm-runner", "fields 0.16.0", "mem-common", "num-bigint", @@ -4606,7 +4618,7 @@ dependencies = [ "sha2", "tiny-keccak", "zisk-pil", - "ziskos", + "ziskos-hints", ] [[package]] @@ -4743,7 +4755,9 @@ dependencies = [ name = "ziskos" version = "0.16.0" dependencies = [ + "anyhow", "bincode", + "bytes", "cfg-if", "ctor", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", @@ -4761,6 +4775,7 @@ dependencies = [ "sha2", "static_assertions", "tiny-keccak", + "zisk-common", ] [[package]] diff --git a/examples/sha-hasher/host/Cargo.toml b/examples/sha-hasher/host/Cargo.toml index eac142dbe..29a6fd3cc 100644 --- a/examples/sha-hasher/host/Cargo.toml +++ b/examples/sha-hasher/host/Cargo.toml @@ -12,3 +12,24 @@ sha2 = "0.10.8" [build-dependencies] zisk-build = { workspace = true } + +[features] +default = [] +packed = ["zisk-sdk/packed"] +gpu = ["zisk-sdk/gpu", "packed"] + +[[bin]] +name = "prove" +path = "bin/prove.rs" + +[[bin]] +name = "execute" +path = "bin/execute.rs" + +[[bin]] +name = "plonk" +path = "bin/plonk.rs" + +[[bin]] +name = "compressed" +path = "bin/compressed.rs" \ No newline at end of file diff --git a/examples/sha-hasher/host/bin/compressed.rs b/examples/sha-hasher/host/bin/compressed.rs new file mode 100644 index 000000000..0e8261e85 --- /dev/null +++ b/examples/sha-hasher/host/bin/compressed.rs @@ -0,0 +1,49 @@ +use anyhow::Result; +use zisk_common::ElfBinary; +use zisk_common::io::ZiskIO; +use zisk_common::io::ZiskStdin; +use zisk_sdk::{ProofOpts, ProverClient, include_elf}; + +pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); + +fn main() -> Result<()> { + println!("Starting ZisK Prover Client (Compressed proof mode)..."); + + // Create an input stream and write '1000' to it. + let n = 1000u32; + let stdin = ZiskStdin::new(); + stdin.write(&n); + println!("Input prepared: {} iterations", n); + + // Create a `ProverClient` method. + println!("Building prover client..."); + let client = ProverClient::builder().asm().base_port(54321).build().unwrap(); + + println!("Setting up program..."); + let vkey = client.setup(&ELF)?; + println!("Setup completed successfully"); + + println!("Generating Vadcop proof..."); + let proof_opts = ProofOpts::default().minimal_memory(); + let vadcop_result = client.prove(stdin).with_proof_options(proof_opts).run()?; + println!("Vadcop proof generated in {:?}", vadcop_result.get_duration()); + + println!("Compressing proof (this may take a while)..."); + let compressed_result = + client.compress(&vadcop_result.get_proof(), &vadcop_result.get_publics(), &vkey)?; + + // Alternatively, you can also call `compressed()` on the `ProverClient.prove` method to generate a compressed proof directly. + // let result = client.prove(stdin).with_proof_options(proof_opts).compressed().run()?; + + println!("Verifying compressed proof..."); + client.verify( + &compressed_result.get_proof(), + &compressed_result.get_publics(), + &compressed_result.get_program_vk(), + )?; + println!("Compressed proof verification successful!"); + + println!("\u{2713} Successfully generated and verified compressed proof!"); + + Ok(()) +} diff --git a/examples/sha-hasher/host/bin/execute.rs b/examples/sha-hasher/host/bin/execute.rs new file mode 100644 index 000000000..aa47505b5 --- /dev/null +++ b/examples/sha-hasher/host/bin/execute.rs @@ -0,0 +1,50 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use zisk_common::ElfBinary; +use zisk_common::io::ZiskIO; +use zisk_common::io::ZiskStdin; +use zisk_sdk::{ProverClient, include_elf}; + +pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); + +#[derive(Serialize, Deserialize, Debug)] +struct Output { + hash: [u8; 32], + iterations: u32, + magic_number: u32, +} + +fn main() -> Result<()> { + println!("Starting ZisK Prover Client..."); + + // Create an input stream and write '1000' to it. + let n = 1000u32; + let stdin = ZiskStdin::new(); + stdin.write(&n); + println!("Input prepared: {} iterations", n); + + // Create a `ProverClient` method. + println!("Building prover client..."); + let client = ProverClient::builder().asm().base_port(54321).build().unwrap(); + + println!("Setting up program..."); + client.setup(&ELF)?; + println!("Setup completed successfully"); + + // Execute the program using the `ProverClient.execute` method, without generating a proof. + println!("Executing program (no proof generation)..."); + let result = client.execute(stdin.clone())?; + + println!("\u{2713} Execution completed successfully!"); + println!("Cycles: {}", result.get_execution_steps()); + println!("Duration: {:?}", result.get_duration()); + + println!("Reading public outputs..."); + let output: Output = result.get_public_values()?; + println!("Public outputs:"); + println!(" Hash: {:02x?}", output.hash); + println!(" Iterations: {}", output.iterations); + println!(" Magic number: 0x{:08x}", output.magic_number); + + Ok(()) +} diff --git a/examples/sha-hasher/host/bin/plonk.rs b/examples/sha-hasher/host/bin/plonk.rs new file mode 100644 index 000000000..96e3020bd --- /dev/null +++ b/examples/sha-hasher/host/bin/plonk.rs @@ -0,0 +1,52 @@ +use anyhow::Result; +use zisk_common::ElfBinary; +use zisk_common::io::ZiskIO; +use zisk_common::io::ZiskStdin; +use zisk_sdk::{ProverClient, ZiskProofWithPublicValues, include_elf}; + +pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); + +fn main() -> Result<()> { + println!("Starting ZisK Prover Client (SNARK mode)..."); + + // Create an input stream and write '1000' to it. + let n = 1000u32; + let stdin = ZiskStdin::new(); + stdin.write(&n); + println!("Input prepared: {} iterations", n); + + // Create a `ProverClient` method. + println!("Building prover client with SNARK support..."); + let client = ProverClient::builder().asm().base_port(54321).snark().build().unwrap(); + + println!("Setting up program and generating verification key..."); + let vkey = client.setup(&ELF)?; + println!("Setup completed successfully"); + + println!("Generating PLONK proof (this may take a while)..."); + let snark_proof = client.prove(stdin).plonk().run()?; + println!("PLONK proof generated successfully in {:?}", snark_proof.get_duration()); + println!("Execution steps: {}", snark_proof.get_execution_steps()); + + // Alternatively, it can also be done in two steps + // let vadcop_result = client.prove(stdin).run()?; + // let snark_proof = client.prove_snark(&vadcop_result.get_proof(), &vadcop_result.get_publics(), &vkey)?; + + println!("Verifying PLONK proof..."); + client.verify(snark_proof.get_proof(), snark_proof.get_publics(), &vkey)?; + println!("PLONK proof verification successful!"); + + println!("Saving PLONK proof to disk..."); + snark_proof.save_proof_with_publics("/tmp/sha_hasher_proof_snark_with_publics.bin")?; + println!("Proof saved to /tmp/sha_hasher_proof_snark_with_publics.bin"); + + println!("Loading and verifying saved PLONK proof..."); + let proof = ZiskProofWithPublicValues::load("/tmp/sha_hasher_proof_snark_with_publics.bin")?; + let vk = client.vk(&ELF)?; + client.verify(proof.get_proof(), proof.get_publics(), &vk)?; + println!("Saved PLONK proof verification successful!"); + + println!("\u{2713} Successfully generated and verified PLONK proof!"); + + Ok(()) +} diff --git a/examples/sha-hasher/host/bin/prove.rs b/examples/sha-hasher/host/bin/prove.rs new file mode 100644 index 000000000..310c4c50d --- /dev/null +++ b/examples/sha-hasher/host/bin/prove.rs @@ -0,0 +1,82 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use zisk_common::ElfBinary; +use zisk_common::io::ZiskIO; +use zisk_common::io::ZiskStdin; +use zisk_sdk::{ + ProofOpts, ProverClient, ZiskProof, ZiskProofWithPublicValues, ZiskPublics, include_elf, +}; + +pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); + +#[derive(Serialize, Deserialize, Debug)] +struct Output { + hash: [u8; 32], + iterations: u32, + magic_number: u32, +} + +fn main() -> Result<()> { + println!("Starting ZisK Prover Client..."); + + // Create an input stream and write '1000' to it. + let n = 1000u32; + let stdin = ZiskStdin::new(); + stdin.write(&n); + println!("Input prepared: {} iterations", n); + + // Create a `ProverClient` method. + println!("Building prover client..."); + let client = ProverClient::builder().asm().base_port(54321).build().unwrap(); + + println!("Setting up program..."); + client.setup(&ELF)?; + println!("Setup completed successfully"); + + println!("Generating proof (this may take a while)..."); + let proof_opts = ProofOpts::default().minimal_memory(); + let result = client.prove(stdin).with_proof_options(proof_opts).run()?; + println!("Proof generated successfully in {:?}", result.get_duration()); + println!("Execution steps: {}", result.get_execution_steps()); + + println!("Verifying proof..."); + client.verify(result.get_proof(), result.get_publics(), result.get_program_vk())?; + println!("Proof verification successful!"); + + println!("Saving proof to disk..."); + result.save_proof_with_publics("tmp/sha_hasher_proof_with_publics.bin")?; + result.get_proof().save("tmp/sha_hasher_proof.bin")?; + println!("Proofs saved to tmp/ directory"); + + let mut hash = [0u8; 32]; + for _ in 0..n { + let mut hasher = Sha256::new(); + hasher.update(hash); + let digest = &hasher.finalize(); + hash = Into::<[u8; 32]>::into(*digest); + } + + let output = Output { hash, iterations: n, magic_number: 0xDEADBEEF }; + println!("Expected output hash: {:02x?}", &hash[..8]); + + println!("Verifying saved proofs from disk..."); + let publics = ZiskPublics::write(&output)?; + println!("Loading proof from disk..."); + let proof = ZiskProof::load("tmp/sha_hasher_proof.bin")?; + let vk = client.vk(&ELF)?; + println!("Verifying standalone proof..."); + client.verify(&proof, &publics, &vk)?; + println!("Standalone proof verification successful!"); + + println!("Loading proof with publics from disk..."); + let proof_with_publics = + ZiskProofWithPublicValues::load("tmp/sha_hasher_proof_with_publics.bin")?; + println!("Verifying proof with publics..."); + client.verify(&proof_with_publics.proof, &proof_with_publics.publics, &vk)?; + println!("Proof with publics verification successful!"); + + println!("\u{2713} Successfully generated and verified all proofs!"); + + Ok(()) +} diff --git a/examples/sha-hasher/host/src/main.rs b/examples/sha-hasher/host/src/main.rs index b713221ca..4e9d1188f 100644 --- a/examples/sha-hasher/host/src/main.rs +++ b/examples/sha-hasher/host/src/main.rs @@ -1,20 +1,11 @@ use anyhow::Result; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; use zisk_common::ElfBinary; use zisk_common::io::ZiskIO; use zisk_common::io::ZiskStdin; -use zisk_sdk::{ProofOpts, ProverClient, ZiskProof, ZiskPublics, include_elf}; +use zisk_sdk::{ProofOpts, ProverClient, include_elf}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); -#[derive(Serialize, Deserialize, Debug)] -struct Output { - hash: [u8; 32], - iterations: u32, - magic_number: u32, -} - fn main() -> Result<()> { println!("Starting ZisK Prover Client..."); @@ -24,7 +15,7 @@ fn main() -> Result<()> { stdin.write(&n); // Create a `ProverClient` method. - let client = ProverClient::builder().asm().base_port(54321).snark().build().unwrap(); + let client = ProverClient::builder().build().unwrap(); let vkey = client.setup(&ELF)?; @@ -38,31 +29,7 @@ fn main() -> Result<()> { let proof_opts = ProofOpts::default().minimal_memory(); let vadcop_result = client.prove(stdin).with_proof_options(proof_opts).run()?; - client.verify(&vadcop_result.proof, &vadcop_result.publics, &vkey)?; - - let result = client.prove_snark(&vadcop_result.proof, &vadcop_result.publics, &vkey)?; - client.verify(&result, &vadcop_result.publics, &vkey)?; - - result.save("/tmp/sha_hasher_proof_snark.bin")?; - - let output: Output = vadcop_result.get_publics()?; - println!("Deserialized public outputs: {:?}", output); - println!("Hash: {:02x?}", output.hash); - println!("Iterations: {}", output.iterations); - println!("Magic number: 0x{:08x}", output.magic_number); - - let mut hash = [0u8; 32]; - for _ in 0..n { - let mut hasher = Sha256::new(); - hasher.update(hash); - let digest = &hasher.finalize(); - hash = Into::<[u8; 32]>::into(*digest); - } - let output = Output { hash, iterations: n, magic_number: 0xDEADBEEF }; - let publics = ZiskPublics::write(&output)?; - let proof = ZiskProof::load("/tmp/sha_hasher_proof_snark.bin")?; - let vk = client.vk(&ELF)?; - client.verify(&proof, &publics, &vk)?; + client.verify(vadcop_result.get_proof(), vadcop_result.get_publics(), &vkey)?; println!("successfully generated and verified proof for the program!"); diff --git a/lib-float/c/lib/libziskfloat.a b/lib-float/c/lib/libziskfloat.a index 411c06a9ce07a54e3d63ea741fc5de18de66a672..6f69bf2388d9a8ff8b12484effee9c41bdbaa2e5 100644 GIT binary patch delta 2292 zcmZ{mQD{<87{~FPPcvP%!eIH5ERra=XqK7KkcyCvLiQjQ6xM^2h>(c7F9TPwjf!Ou z-Z5hNGLxcOgLOqHB=!*7h($4C&{#ns1xp4kv4|w?)`jzbxtGiD{J-;^|DXHao6Xs? zIeXe+-s|@I_W1UBAGcR6_U41f>GZhWyBgOkl663`8dS;33PIrtwbsSPO=9${n+*eW z<``5-F{oOIv-~>oUYQFl1kg$!+in!Aqygpz==^n7UjJ9sw4-5)dhRxJ12l1;$t_}4 zM}d{CVicHSezO?$&$1XmwKpsX(9w4+0nqeECIb{%W?6tN-`SF8l-`YBKUqjiS&xo$ zJ_uB+W?lkDO>g2QElPdC(XHIvDm3bD(aXg|eR+`Q+l3+3+>=AFXiIMb_M*$j{;j%Ud^v1j9`5Zu& z_uLOq^8YElukRPDKeoBFY3;W3lRJKD@2FTJy6%vPT3sV(X;D}Ncs9YyV z2590GCB*Tm(pfTt{5mhu95}MnOEK|YO1+J^5;hu0ZMjS~ZIQPzjhi!B>ZiO`T8}Q$$6lmzJ014@VsD)lyclp_!6wbFWnk(!;P0BCr* z_+OCvt?Ovqpx&FJf74jMz%)ZyaRX}c6(s@ccuQ&Rqo6PNVTsD%u#07if`Xl2Q5;JB bhoML9{YD-jeg1<=0A-jl3EFg6jVAMNB`5{_ delta 2292 zcmZ{mUr1A77{=N6y|cC1vM{jSG>k?R95(;VIY>rD45b)ej1?5Ci#Z};s8XU(wHRZWE%IljF0vp%tNm=RS*}tBnH!)BH&}72Ty_2qa|1N>fT^`|RY#T; zZF1zFVLpo-4a~79K$W*F0MN1bEDliN6H@^Sudp;g)*oyc81?%X3xbqSa6SZ7YZjgd zMiqAOJcx2^H@DPDjk=d>=5iul9pafrsYqOl@%&CXI@QOso8)NU5HEpkDZ{)BkpDIh z0H20Oc?hUh@A3pdm&UoeS>7Rg%p2@-q&(vhfJSGzDxU+wwZP{AvVPz`fWlw-2taI& zzm%itC31rIWP2?kGg8~g3F0a=P!J%OlT`CAeGG1#e1EUK-j?cF>mTRMqO^y)^&pzu z8<&dkeoE@e8c@YUK7fw+s1FoeJU~vMvLB`*K-MlQ(ZA3QZP-Ip)-5m~M}*2i(j@NB z(En*{aKC1j(((pG?lmO<>Uc*<{i9$g_;HzvV6#g_ihzQh-%uP{^@pKH^nND~kiPgy Qd4N*PoB(Y)Y~~uvA669!`v3p{ diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index a58fc38ff..486b839ac 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -2,13 +2,14 @@ mod builder; mod client; mod prover; mod utils; +mod verifier; mod zisk_lib_loader; pub use builder::*; pub use client::ProverClient; -pub use proofman_util::VadcopFinalProof; pub use prover::*; pub use utils::*; +pub use verifier::*; pub use zisk_lib_loader::*; pub struct RankInfo { diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index f28452c8c..fdc380a31 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -3,7 +3,8 @@ use crate::{ check_paths_exist, ensure_custom_commits, prover::{ProverBackend, ProverEngine, ZiskBackend}, RankInfo, ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, ZiskProgramVK, - ZiskProof, ZiskProveResult, ZiskPublics, ZiskVerifyConstraintsResult, + ZiskProof, ZiskProofWithPublicValues, ZiskProveResult, ZiskPublics, + ZiskVerifyConstraintsResult, }; use crate::{ProofMode, ProofOpts}; use asm_runner::{AsmRunnerOptions, AsmServices}; @@ -207,10 +208,19 @@ impl ProverEngine for AsmProver { proof: &ZiskProof, publics: &ZiskPublics, vk: &ZiskProgramVK, - ) -> Result { + ) -> Result { self.core_prover.backend.prove_snark(proof, publics, vk) } + fn compress( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + vk: &ZiskProgramVK, + ) -> Result { + self.core_prover.backend.compress(proof, publics, vk) + } + fn prove_phase( &self, phase_inputs: ProvePhaseInputs, diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 66b4673f1..8d02ae6bd 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -1,5 +1,10 @@ use crate::create_debug_info; +use crate::ZiskProofWithPublicValues; use crate::ZiskPublics; +use crate::{ + get_program_vk_with_proving_key, verify_zisk_proof_with_proving_key, + verify_zisk_snark_proof_with_proving_key, +}; use crate::{ProofMode, ProofOpts}; use crate::{ ZiskAggPhaseResult, ZiskExecuteResult, ZiskPhaseResult, ZiskProgramVK, ZiskProof, @@ -9,13 +14,11 @@ use anyhow::Result; use colored::Colorize; use fields::Goldilocks; use proofman::{ - get_vadcop_final_proof_vkey, verify_snark_proof, AggProofs, ExecutionInfo, ProofInfo, ProofMan, - ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProof, SnarkProtocol, SnarkWrapper, + AggProofs, ExecutionInfo, ProofInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult, + SnarkProtocol, SnarkWrapper, }; use proofman_common::{ProofCtx, ProofOptions}; use proofman_util::VadcopFinalProof; -use rom_setup::rom_merkle_setup_verkey; -use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; @@ -24,7 +27,6 @@ use zisk_common::{ io::{StreamSource, ZiskStdin}, ElfBinaryLike, ExecutorStatsHandle, ZiskExecutionResult, }; -use zisk_verifier::verify_zisk_proof; use zisk_witness::WitnessLib; pub(crate) struct ProverBackend { @@ -147,7 +149,9 @@ impl ProverBackend { anyhow::anyhow!("Failed to get execution result from emulator prover") })?; - Ok(ZiskExecuteResult { execution: result, duration: elapsed }) + let publics = proofman.get_publics(); + + Ok(ZiskExecuteResult::new(result, elapsed, &publics)) } pub(crate) fn stats( @@ -263,11 +267,7 @@ impl ProverBackend { } pub(crate) fn vk(&self, elf: &impl ElfBinaryLike) -> Result { - let proving_key_path = self.proving_key_path.clone(); - - let vk = rom_merkle_setup_verkey(elf, &None, proving_key_path.as_path())?; - - Ok(ZiskProgramVK { vk }) + get_program_vk_with_proving_key(elf, self.proving_key_path.clone()) } pub(crate) fn prove_debug( @@ -370,8 +370,6 @@ impl ProverBackend { ) .map_err(|e| anyhow::anyhow!("Error generating proof: {}", e))?; - let elapsed = start.elapsed(); - let (proof_id, proof) = match proof { ProvePhaseResult::Full(proof_id, proof) => (proof_id, proof), _ => (None, None), @@ -404,25 +402,31 @@ impl ProverBackend { proof_options.output_dir_path.clone(), )?; + let publics = ZiskPublics::new(&vadcop_proof.public_values); + let program_vk = ZiskProgramVK::new_from_publics(&vadcop_proof.public_values); if snark_proof.protocol_id == SnarkProtocol::Plonk.protocol_id() { - let publics = ZiskPublics::new(vadcop_proof.public_values); Ok(ZiskProveResult::new( execution_result, - elapsed, + start.elapsed(), stats, proof_id, - ZiskProof::Plonk(snark_proof.proof_bytes), - publics, + ZiskProofWithPublicValues { + proof: ZiskProof::Plonk(snark_proof.proof_bytes), + publics, + program_vk, + }, )) } else if snark_proof.protocol_id == SnarkProtocol::Fflonk.protocol_id() { - let publics = ZiskPublics::new(vadcop_proof.public_values); Ok(ZiskProveResult::new( execution_result, - elapsed, + start.elapsed(), stats, proof_id, - ZiskProof::Fflonk(snark_proof.proof_bytes), - publics, + ZiskProofWithPublicValues { + proof: ZiskProof::Fflonk(snark_proof.proof_bytes), + publics, + program_vk, + }, )) } else { Err(anyhow::anyhow!( @@ -439,23 +443,61 @@ impl ProverBackend { }; Ok(ZiskProveResult::new( execution_result, - elapsed, + start.elapsed(), stats, proof_id, - proof, - ZiskPublics::new(p.public_values), + ZiskProofWithPublicValues { + proof, + publics: ZiskPublics::new(&p.public_values), + program_vk: ZiskProgramVK::new_from_publics(&p.public_values), + }, )) } - (_, None) => Ok(ZiskProveResult::new_null(execution_result, elapsed, stats)), + (_, None) => Ok(ZiskProveResult::new_null(execution_result, start.elapsed(), stats)), } } + pub(crate) fn compress( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, + ) -> Result { + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot compress in verifier mode"))?; + + let proof_bytes = match proof { + ZiskProof::VadcopFinal(bytes) => bytes.clone(), + _ => { + return Err(anyhow::anyhow!( + "Cannot generate SNARK proof. Only VadcopFinal proofs can be converted to SNARK proofs.", + )); + } + }; + + let mut pubs = program_vk.vk.clone(); + pubs.extend(publics.public_bytes()); + let vadcop_final_proof = VadcopFinalProof::new(proof_bytes, pubs, false); + + let compressed_proof = proofman + .generate_vadcop_final_proof_compressed(&vadcop_final_proof, None, false) + .map_err(|e| anyhow::anyhow!("Error generating compressed proof: {}", e))?; + + Ok(ZiskProofWithPublicValues { + proof: ZiskProof::VadcopFinalCompressed(compressed_proof.proof), + publics: ZiskPublics::new(&compressed_proof.public_values), + program_vk: ZiskProgramVK::new_from_publics(&compressed_proof.public_values), + }) + } + pub(crate) fn prove_snark( &self, proof: &ZiskProof, publics: &ZiskPublics, program_vk: &ZiskProgramVK, - ) -> Result { + ) -> Result { if self.snark_wrapper.is_none() { return Err(anyhow::anyhow!( "Snark wrapper is not initialized. Cannot generate snark proof." @@ -482,9 +524,17 @@ impl ProverBackend { .generate_final_snark_proof(&vadcop_final_proof, None)?; if snark_proof.protocol_id == SnarkProtocol::Plonk.protocol_id() { - Ok(ZiskProof::Plonk(snark_proof.proof_bytes)) + Ok(ZiskProofWithPublicValues { + proof: ZiskProof::Plonk(snark_proof.proof_bytes), + publics: publics.clone(), + program_vk: program_vk.clone(), + }) } else if snark_proof.protocol_id == SnarkProtocol::Fflonk.protocol_id() { - Ok(ZiskProof::Fflonk(snark_proof.proof_bytes)) + Ok(ZiskProofWithPublicValues { + proof: ZiskProof::Fflonk(snark_proof.proof_bytes), + publics: publics.clone(), + program_vk: program_vk.clone(), + }) } else { Err(anyhow::anyhow!("Unsupported snark protocol id: {}", snark_proof.protocol_id)) } @@ -551,48 +601,22 @@ impl ProverBackend { ) -> Result<()> { match &proof { ZiskProof::Null() => Err(anyhow::anyhow!("No proof found to verify.")), - ZiskProof::Plonk(proof_bytes) | ZiskProof::Fflonk(proof_bytes) => { - let protocol_id = if let ZiskProof::Plonk(_) = &proof { - SnarkProtocol::Plonk.protocol_id() - } else { - SnarkProtocol::Fflonk.protocol_id() - }; - - let verkey = get_vadcop_final_proof_vkey(&self.proving_key_path, false)?; - - let pubs = publics.bytes_solidity(program_vk, &verkey); - let hash = Sha256::digest(&pubs).to_vec(); - - let snark_proof = SnarkProof { - proof_bytes: proof_bytes.clone(), - public_bytes: pubs, - public_snark_bytes: hash, - protocol_id, - }; - - if self.proving_key_snark_path.is_none() { - return Err(anyhow::anyhow!( - "Proving key snark path is not set, cannot verify Plonk proof." - )); - } - - let verkey_path = PathBuf::from(format!( - "{}/{}/{}.verkey.json", - self.proving_key_snark_path.as_ref().unwrap().display(), - "final", - "final" - )); - Ok(verify_snark_proof(&snark_proof, &verkey_path)?) - } - ZiskProof::VadcopFinal(proof_bytes) | ZiskProof::VadcopFinalCompressed(proof_bytes) => { - let compressed = matches!(proof, ZiskProof::VadcopFinalCompressed(_)); - let mut pubs = program_vk.vk.clone(); - pubs.extend(publics.public_bytes()); - let vadcop_final_proof = - VadcopFinalProof::new(proof_bytes.clone(), pubs, compressed); - - let vk = get_vadcop_final_proof_vkey(&self.proving_key_path, compressed)?; - verify_zisk_proof(&vadcop_final_proof, &vk) + ZiskProof::Plonk(_) | ZiskProof::Fflonk(_) => verify_zisk_snark_proof_with_proving_key( + proof, + publics, + program_vk, + self.proving_key_path.clone(), + self.proving_key_snark_path + .clone() + .expect("Proving key snark path is required for snark proofs"), + ), + ZiskProof::VadcopFinal(_) | ZiskProof::VadcopFinalCompressed(_) => { + verify_zisk_proof_with_proving_key( + proof, + publics, + program_vk, + self.proving_key_path.clone(), + ) } } } diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index b857e2e32..018ebb311 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -2,7 +2,8 @@ use crate::{ check_paths_exist, prover::{ProverBackend, ProverEngine, ZiskBackend}, RankInfo, ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, ZiskProgramVK, - ZiskProof, ZiskProveResult, ZiskPublics, ZiskVerifyConstraintsResult, + ZiskProof, ZiskProofWithPublicValues, ZiskProveResult, ZiskPublics, + ZiskVerifyConstraintsResult, }; use crate::{ensure_custom_commits, ProofMode, ProofOpts}; use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; @@ -161,10 +162,19 @@ impl ProverEngine for EmuProver { proof: &ZiskProof, publics: &ZiskPublics, vk: &ZiskProgramVK, - ) -> Result { + ) -> Result { self.core_prover.backend.prove_snark(proof, publics, vk) } + fn compress( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + vk: &ZiskProgramVK, + ) -> Result { + self.core_prover.backend.compress(proof, publics, vk) + } + fn prove_phase( &self, phase_inputs: ProvePhaseInputs, diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index fceb55bf8..514e89b3a 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -8,9 +8,10 @@ use proofman::{ AggProofs, ExecutionInfo, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProtocol, }; use proofman_common::ProofOptions; +use proofman_util::VadcopFinalProof; use sha2::{Digest, Sha256}; -use anyhow::Result; +use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::fs::File; use std::{ @@ -27,6 +28,31 @@ use zisk_common::{ pub struct ZiskExecuteResult { pub execution: ZiskExecutionResult, pub duration: Duration, + pub publics: ZiskPublics, +} + +impl ZiskExecuteResult { + pub fn new(execution: ZiskExecutionResult, duration: Duration, publics: &[u8]) -> Self { + Self { execution, duration, publics: ZiskPublics::new(publics) } + } + + pub fn get_publics(&self) -> &ZiskPublics { + &self.publics + } + + pub fn get_public_values( + &self, + ) -> Result { + self.publics.read() + } + + pub fn get_execution_steps(&self) -> &u64 { + &self.execution.steps + } + + pub fn get_duration(&self) -> Duration { + self.duration + } } pub struct ZiskVerifyConstraintsResult { @@ -35,10 +61,26 @@ pub struct ZiskVerifyConstraintsResult { pub stats: ExecutorStatsHandle, } +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ZiskProgramVK { pub vk: Vec, } +impl ZiskProgramVK { + pub fn new_from_publics(publics: &[u8]) -> Self { + assert!( + publics.len() >= 32, + "Not enough bytes to extract program VK (expected at least 32 bytes)" + ); + + Self { vk: publics[0..32].to_vec() } + } + + pub fn new_empty() -> Self { + Self { vk: vec![0u8; 32] } + } +} + #[derive(Debug, Clone)] pub struct ProofOpts { pub aggregation: bool, @@ -96,7 +138,7 @@ pub enum ProofMode { Snark, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ZiskProof { Null(), VadcopFinal(Vec), @@ -250,13 +292,14 @@ impl ZiskProof { pub const ZISK_PUBLICS: usize = 64; +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ZiskPublics { data: Vec, ptr: Cell, } impl ZiskPublics { - pub fn new(publics_bytes: Vec) -> Self { + pub fn new(publics_bytes: &[u8]) -> Self { assert!( publics_bytes.len() == ZISK_PUBLICS * 8 + 32, "Not enough bytes to fill ZiskPublics" @@ -400,13 +443,98 @@ impl ZiskPublics { } } -pub struct ZiskProveResult { - pub execution: ZiskExecutionResult, - pub duration: Duration, - pub stats: ExecutorStatsHandle, - pub proof_id: Option, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ZiskProofWithPublicValues { pub proof: ZiskProof, pub publics: ZiskPublics, + pub program_vk: ZiskProgramVK, +} + +impl ZiskProofWithPublicValues { + pub fn new(proof: ZiskProof, publics: ZiskPublics, program_vk: ZiskProgramVK) -> Self { + Self { proof, publics, program_vk } + } + + pub fn save(&self, path: impl AsRef) -> Result<()> { + bincode::serialize_into( + File::create(path.as_ref()).with_context(|| { + format!("failed to create file for saving proof: {}", path.as_ref().display()) + })?, + self, + ) + .map_err(Into::into) + } + + pub fn load(path: impl AsRef) -> Result { + let file = File::open(path.as_ref()).with_context(|| { + format!("failed to open file for loading proof: {}", path.as_ref().display()) + })?; + let proof_with_publics: ZiskProofWithPublicValues = bincode::deserialize_from(file)?; + Ok(proof_with_publics) + } + + pub fn get_vadcop_final_proof(&self) -> Result { + match &self.proof { + ZiskProof::VadcopFinal(proof_bytes) | ZiskProof::VadcopFinalCompressed(proof_bytes) => { + let compressed = matches!(self.proof, ZiskProof::VadcopFinalCompressed(_)); + let mut pubs = self.program_vk.vk.clone(); + pubs.extend(self.publics.public_bytes()); + Ok(VadcopFinalProof::new(proof_bytes.clone(), pubs, compressed)) + } + + _ => Err(anyhow::anyhow!("Proof is not a Vadcop final proof")), + } + } + + pub fn get_proof(&self) -> &ZiskProof { + &self.proof + } + + pub fn get_publics(&self) -> &ZiskPublics { + &self.publics + } + + pub fn get_program_vk(&self) -> &ZiskProgramVK { + &self.program_vk + } + + /// Create ZiskProofWithPublicValues directly from a Vadcop proof byte array. + /// + /// This method parses the proof format (n_publics, publics..., proof...) and extracts + /// the public values and program VK directly, without creating an intermediate VadcopFinalProof. + /// + /// # Parameters + /// + /// * `proof` - The proof as a slice of u64 values + /// * `compressed` - Whether the proof is compressed + /// + /// # Returns + /// + /// A ZiskProofWithPublicValues containing the parsed proof, publics, and program VK + pub fn new_from_vadcop_proof(proof: &[u64], compressed: bool) -> Result { + let vadcop_proof = VadcopFinalProof::new_from_proof(proof, compressed) + .map_err(|e| anyhow::anyhow!("Failed to parse Vadcop proof: {}", e))?; + + let zisk_proof = if compressed { + ZiskProof::VadcopFinalCompressed(vadcop_proof.proof) + } else { + ZiskProof::VadcopFinal(vadcop_proof.proof) + }; + + Ok(Self { + proof: zisk_proof, + publics: ZiskPublics::new(&vadcop_proof.public_values), + program_vk: ZiskProgramVK::new_from_publics(&vadcop_proof.public_values), + }) + } +} + +pub struct ZiskProveResult { + execution: ZiskExecutionResult, + duration: Duration, + stats: ExecutorStatsHandle, + proof_id: Option, + proof_with_publics: ZiskProofWithPublicValues, } impl ZiskProveResult { @@ -415,10 +543,9 @@ impl ZiskProveResult { duration: Duration, stats: ExecutorStatsHandle, proof_id: Option, - proof: ZiskProof, - publics: ZiskPublics, + proof_with_publics: ZiskProofWithPublicValues, ) -> Self { - Self { execution, duration, stats, proof_id, proof, publics } + Self { execution, duration, stats, proof_id, proof_with_publics } } pub fn new_null( @@ -431,20 +558,56 @@ impl ZiskProveResult { duration, stats, proof_id: None, - proof: ZiskProof::Null(), - publics: ZiskPublics::new_empty(), + proof_with_publics: ZiskProofWithPublicValues { + proof: ZiskProof::Null(), + publics: ZiskPublics::new_empty(), + program_vk: ZiskProgramVK::new_empty(), + }, } } - /// Deserialize a value from public outputs. - /// The value must have been previously written with bincode serialization using `commit()`. - pub fn get_publics(&self) -> Result { - self.publics.read() + pub fn get_stats(&self) -> &ExecutorStatsHandle { + &self.stats + } + + pub fn get_duration(&self) -> Duration { + self.duration + } + + pub fn get_execution_steps(&self) -> &u64 { + &self.execution.steps + } + + pub fn get_proof_id(&self) -> Option<&String> { + self.proof_id.as_ref() + } + + pub fn get_proof(&self) -> &ZiskProof { + &self.proof_with_publics.proof } - /// Reset the reading pointer to the beginning of public outputs. - pub fn reset_publics(&self) { - self.publics.head(); + pub fn get_publics(&self) -> &ZiskPublics { + &self.proof_with_publics.publics + } + + pub fn get_program_vk(&self) -> &ZiskProgramVK { + &self.proof_with_publics.program_vk + } + + pub fn get_proof_with_publics(&self) -> &ZiskProofWithPublicValues { + &self.proof_with_publics + } + + pub fn save_proof_with_publics(&self, path: impl AsRef) -> Result<()> { + self.proof_with_publics.save(path) + } + + /// Deserialize a value from public outputs. + /// The value must have been previously written with bincode serialization using `commit()`. + pub fn get_public_values( + &self, + ) -> Result { + self.proof_with_publics.publics.read() } } @@ -505,7 +668,14 @@ pub trait ProverEngine { proof: &ZiskProof, publics: &ZiskPublics, vk: &ZiskProgramVK, - ) -> Result; + ) -> Result; + + fn compress( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + vk: &ZiskProgramVK, + ) -> Result; fn prove_phase( &self, @@ -633,10 +803,19 @@ impl ZiskProver { proof: &ZiskProof, publics: &ZiskPublics, vk: &ZiskProgramVK, - ) -> Result { + ) -> Result { self.prover.prove_snark(proof, publics, vk) } + pub fn compress( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + vk: &ZiskProgramVK, + ) -> Result { + self.prover.compress(proof, publics, vk) + } + pub fn prove_phase( &self, phase_inputs: ProvePhaseInputs, diff --git a/sdk/src/verifier.rs b/sdk/src/verifier.rs new file mode 100644 index 000000000..8abe1c0ba --- /dev/null +++ b/sdk/src/verifier.rs @@ -0,0 +1,116 @@ +use crate::{ + get_default_proving_key, get_default_proving_key_snark, ZiskProgramVK, ZiskProof, ZiskPublics, +}; +use anyhow::{anyhow, Ok, Result}; +use proofman::{get_vadcop_final_proof_vkey, verify_snark_proof, SnarkProof, SnarkProtocol}; +use proofman_util::VadcopFinalProof; +use rom_setup::rom_merkle_setup_verkey; +use sha2::{Digest, Sha256}; +use std::path::PathBuf; +use zisk_common::ElfBinaryLike; +use zisk_verifier::verify_vadcop_final_proof; + +pub fn verify_zisk_snark_proof( + proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, +) -> Result<()> { + let proving_key = get_default_proving_key(); + let proving_key_snark = get_default_proving_key_snark(); + + verify_zisk_snark_proof_with_proving_key( + proof, + publics, + program_vk, + proving_key, + proving_key_snark, + ) +} + +pub fn verify_zisk_proof( + zisk_proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, +) -> Result<()> { + let proving_key = get_default_proving_key(); + verify_zisk_proof_with_proving_key(zisk_proof, publics, program_vk, proving_key) +} + +pub fn get_program_vk(elf: &impl ElfBinaryLike) -> Result { + let proving_key_path = get_default_proving_key(); + get_program_vk_with_proving_key(elf, proving_key_path) +} + +pub fn verify_zisk_snark_proof_with_proving_key( + proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, + proving_key: PathBuf, + proving_key_snark: PathBuf, +) -> Result<()> { + match &proof { + ZiskProof::Plonk(proof_bytes) | ZiskProof::Fflonk(proof_bytes) => { + let protocol_id = if let ZiskProof::Plonk(_) = &proof { + SnarkProtocol::Plonk.protocol_id() + } else { + SnarkProtocol::Fflonk.protocol_id() + }; + + if !proving_key_snark.exists() { + return Err(anyhow!( + "Proving key snark path does not exist: {}", + proving_key_snark.display() + )); + } + + let verkey = get_vadcop_final_proof_vkey(&proving_key, false)?; + + let pubs = publics.bytes_solidity(program_vk, &verkey); + let hash = Sha256::digest(&pubs).to_vec(); + + let snark_proof = SnarkProof { + proof_bytes: proof_bytes.clone(), + public_bytes: pubs, + public_snark_bytes: hash, + protocol_id, + }; + + let verkey_path = PathBuf::from(format!( + "{}/{}/{}.verkey.json", + proving_key_snark.display(), + "final", + "final" + )); + Ok(verify_snark_proof(&snark_proof, &verkey_path)?) + } + _ => Err(anyhow!("Not a snark proof.")), + } +} + +pub fn verify_zisk_proof_with_proving_key( + zisk_proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, + proving_key: PathBuf, +) -> Result<()> { + match &zisk_proof { + ZiskProof::VadcopFinal(proof_bytes) | ZiskProof::VadcopFinalCompressed(proof_bytes) => { + let compressed = matches!(zisk_proof, ZiskProof::VadcopFinalCompressed(_)); + let mut pubs = program_vk.vk.clone(); + pubs.extend(publics.public_bytes()); + let vadcop_final_proof = VadcopFinalProof::new(proof_bytes.clone(), pubs, compressed); + + let vk = get_vadcop_final_proof_vkey(&proving_key, compressed)?; + verify_vadcop_final_proof(&vadcop_final_proof, &vk) + } + _ => Err(anyhow!("Not a Vadcop final proof.")), + } +} + +pub fn get_program_vk_with_proving_key( + elf: &impl ElfBinaryLike, + proving_key_path: PathBuf, +) -> Result { + let vk = rom_merkle_setup_verkey(elf, &None, &proving_key_path)?; + Ok(ZiskProgramVK { vk }) +} diff --git a/verifier/src/verifier.rs b/verifier/src/verifier.rs index bad19e455..a134f2be6 100644 --- a/verifier/src/verifier.rs +++ b/verifier/src/verifier.rs @@ -2,11 +2,14 @@ use anyhow::{anyhow, Ok, Result}; use proofman_util::VadcopFinalProof; use proofman_verifier::{verify_vadcop_final, verify_vadcop_final_compressed}; -pub fn verify_zisk_proof(zisk_proof: &VadcopFinalProof, vk: &[u8]) -> Result<()> { +pub fn verify_vadcop_final_proof( + zisk_proof: &VadcopFinalProof, + vadcop_final_vk: &[u8], +) -> Result<()> { let is_valid = if zisk_proof.compressed { - verify_vadcop_final_compressed(zisk_proof, vk) + verify_vadcop_final_compressed(zisk_proof, vadcop_final_vk) } else { - verify_vadcop_final(zisk_proof, vk) + verify_vadcop_final(zisk_proof, vadcop_final_vk) }; if !is_valid { diff --git a/ziskos/entrypoint/src/io.rs b/ziskos/entrypoint/src/io.rs index f376274f5..7c17956ab 100644 --- a/ziskos/entrypoint/src/io.rs +++ b/ziskos/entrypoint/src/io.rs @@ -2,7 +2,7 @@ //! //! This module provides a high-level API for reading inputs and committing public outputs. -use crate::{read_input, set_output}; +use crate::{read_input, read_input_slice, set_output}; use serde::{de::DeserializeOwned, Serialize}; /// Read a deserializable object from the input stream. @@ -20,7 +20,7 @@ use serde::{de::DeserializeOwned, Serialize}; /// let data: MyStruct = ziskos::io::read(); /// ``` pub fn read() -> T { - let bytes = read_input(); + let bytes = read_input_slice(); bincode::deserialize(&bytes).expect("Deserialization failed") } diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index aa5794027..b6c4844a9 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -62,7 +62,7 @@ pub(crate) fn read_input() -> Vec { } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -fn read_input_slice<'a>() -> &'a [u8] { +pub fn read_input_slice<'a>() -> &'a [u8] { // Create a slice of the first 8 bytes to get the size let bytes = unsafe { core::slice::from_raw_parts((INPUT_ADDR as *const u8).add(8), 8) }; // Convert the slice to a u64 (little-endian) @@ -73,7 +73,7 @@ fn read_input_slice<'a>() -> &'a [u8] { #[allow(unused)] #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] -fn read_input_slice() -> Box<[u8]> { +pub fn read_input_slice() -> Box<[u8]> { read_input().into_boxed_slice() } From 9ec789aaf87539f2edf5b5d0c6e9404c640d95b8 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:21:38 +0000 Subject: [PATCH 461/782] stats calls simplified --- cli/src/commands/prove.rs | 2 - cli/src/commands/verify_constraints.rs | 2 - common/src/executor_stats.rs | 220 +++++++++++++++++- emulator-asm/asm-runner/src/asm_mo_runner.rs | 81 ++----- emulator-asm/asm-runner/src/asm_mt_runner.rs | 40 +--- emulator-asm/asm-runner/src/asm_rh_runner.rs | 23 +- executor/src/emu_asm.rs | 67 ++---- executor/src/emu_rust.rs | 13 +- executor/src/executor.rs | 224 +++---------------- executor/src/lib.rs | 10 +- sdk/src/prover/backend.rs | 39 +--- 11 files changed, 313 insertions(+), 408 deletions(-) diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index db6bb14cd..292d754a9 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -9,8 +9,6 @@ use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ElfBinaryOwned; -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; use zisk_sdk::{ProofOpts, ProverClient, ZiskProof, ZiskProveResult}; // Structure representing the 'prove' subcommand of cargo. diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 2cef8d18f..f0d7c171d 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -9,8 +9,6 @@ use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ElfBinaryOwned; -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; use zisk_sdk::{ProverClient, ZiskVerifyConstraintsResult}; #[derive(Parser)] diff --git a/common/src/executor_stats.rs b/common/src/executor_stats.rs index 4f893a588..2327402fe 100644 --- a/common/src/executor_stats.rs +++ b/common/src/executor_stats.rs @@ -11,6 +11,213 @@ use zisk_pil::*; use crate::Stats; +/// Trait for types that can be converted to a stats ID. +/// Implemented for `u64` (raw ID) and `StatsScope` (extracts ID via `.id()`). +pub trait IntoStatsId { + fn as_stats_id(&self) -> u64; +} + +impl IntoStatsId for u64 { + #[inline] + fn as_stats_id(&self) -> u64 { + *self + } +} + +impl IntoStatsId for i32 { + #[inline] + fn as_stats_id(&self) -> u64 { + *self as u64 + } +} + +impl IntoStatsId for StatsScope { + #[inline] + fn as_stats_id(&self) -> u64 { + self.id() + } +} + +impl IntoStatsId for &T { + #[inline] + fn as_stats_id(&self) -> u64 { + (*self).as_stats_id() + } +} + +/// Creates a new stats scope (StatsScope) and emits a Begin event. +/// When `stats` feature is disabled, creates a zero-sized StatsScope. +/// +/// # Usage +/// ```ignore +/// stats_begin!(self.stats, 0, parent_scope, "PARENT_OP", 0); +/// stats_begin!(self.stats, &parent_scope, child_scope, "CHILD_OP", 0); +/// // ... work ... +/// stats_end!(self.stats, &child_scope, "CHILD_OP", 0); +/// stats_end!(self.stats, &parent_scope, "PARENT_OP", 0); +/// ``` +#[cfg(feature = "stats")] +#[macro_export] +macro_rules! stats_begin { + ($stats:expr, $parent:expr, $scope:ident, $name:expr, $index:expr) => { + let $scope = $crate::StatsScope::new( + $crate::IntoStatsId::as_stats_id(&$parent), + $stats.next_id(), + $name, + $index, + ); + $stats.add_stat( + $scope.parent_id(), + $scope.id(), + $name, + $index, + $crate::ExecutorStatsEvent::Begin, + ); + }; +} + +#[cfg(not(feature = "stats"))] +#[macro_export] +macro_rules! stats_begin { + ($stats:expr, $parent:expr, $scope:ident, $name:expr, $index:expr) => { + let $scope = $crate::StatsScope; + }; +} + +/// Ends a stats scope with an End event. +/// Uses name and index from the scope (passed to stats_begin). +/// When `stats` feature is disabled, this generates no code. +/// +/// # Usage +/// ```ignore +/// stats_begin!(self.stats, &parent_scope, child_scope, "CHILD_OP", 0); +/// // ... work ... +/// stats_end!(self.stats, &child_scope); +/// ``` +#[cfg(feature = "stats")] +#[macro_export] +macro_rules! stats_end { + ($stats:expr, $scope:expr) => { + $stats.add_stat( + $scope.parent_id(), + $scope.id(), + $scope.name(), + $scope.index(), + $crate::ExecutorStatsEvent::End, + ); + }; +} + +#[cfg(not(feature = "stats"))] +#[macro_export] +macro_rules! stats_end { + ($stats:expr, $scope:expr) => {}; +} + +/// Records a stats mark event (single point in time, not a scope). +/// When `stats` feature is disabled, this generates no code. +/// +/// # Usage +/// ```ignore +/// stats_mark!(self.stats, &parent_scope, "CHECKPOINT_NAME", index); +/// ``` +#[cfg(feature = "stats")] +#[macro_export] +macro_rules! stats_mark { + ($stats:expr, $parent:expr, $name:expr, $index:expr) => { + let __mark_id = $stats.next_id(); + $stats.add_stat( + $crate::IntoStatsId::as_stats_id(&$parent), + __mark_id, + $name, + $index, + $crate::ExecutorStatsEvent::Mark, + ); + }; +} + +#[cfg(not(feature = "stats"))] +#[macro_export] +macro_rules! stats_mark { + ($stats:expr, $parent:expr, $name:expr, $index:expr) => {}; +} + +/// Stats scope that holds scope information (parent_id, id, name, index). +/// Created by `stats_begin!` macro, ended by `stats_end!` macro. +/// When `stats` feature is disabled, this is a zero-sized type. +/// +/// # Usage +/// ```ignore +/// stats_begin!(self.stats, 0, parent_scope, "PARENT", 0); +/// stats_begin!(self.stats, &parent_scope, child_scope, "CHILD", 0); +/// // ... work ... +/// stats_end!(self.stats, &child_scope); +/// stats_end!(self.stats, &parent_scope); +/// ``` +#[cfg(feature = "stats")] +pub struct StatsScope { + parent_id: u64, + id: u64, + name: &'static str, + index: usize, +} + +#[cfg(feature = "stats")] +impl StatsScope { + /// Creates a new stats scope. Does NOT emit Begin - use `stats_begin!` macro instead. + #[inline] + pub fn new(parent_id: u64, id: u64, name: &'static str, index: usize) -> Self { + Self { parent_id, id, name, index } + } + + #[inline] + pub fn parent_id(&self) -> u64 { + self.parent_id + } + + #[inline] + pub fn id(&self) -> u64 { + self.id + } + + #[inline] + pub fn name(&self) -> &'static str { + self.name + } + + #[inline] + pub fn index(&self) -> usize { + self.index + } +} + +/// Zero-sized type when stats feature is disabled. All methods are no-ops. +#[cfg(not(feature = "stats"))] +pub struct StatsScope; + +#[cfg(not(feature = "stats"))] +impl StatsScope { + #[inline] + pub fn parent_id(&self) -> u64 { + 0 + } + + #[inline] + pub fn id(&self) -> u64 { + 0 + } + + #[inline] + pub fn name(&self) -> &'static str { + "" + } + + #[inline] + pub fn index(&self) -> usize { + 0 + } +} + #[derive(Debug, Clone)] pub enum ExecutorStatsEvent { Begin, @@ -89,25 +296,22 @@ impl ExecutorStats { val if val == MEM_AIR_IDS[0] => "MEM".to_string(), val if val == ROM_DATA_AIR_IDS[0] => "ROM_DATA".to_string(), val if val == INPUT_DATA_AIR_IDS[0] => "INPUT_DATA".to_string(), + val if val == DMA_PRE_POST_AIR_IDS[0] => "DMA_PRE_POST".to_string(), val if val == MEM_ALIGN_AIR_IDS[0] => "MEM_ALIGN".to_string(), val if val == MEM_ALIGN_BYTE_AIR_IDS[0] => "MEM_ALIGN_BYTE".to_string(), val if val == MEM_ALIGN_READ_BYTE_AIR_IDS[0] => "MEM_ALIGN_READ_BYTE".to_string(), val if val == MEM_ALIGN_WRITE_BYTE_AIR_IDS[0] => "MEM_ALIGN_WRITE_BYTE".to_string(), - // val if val == MEM_ALIGN_ROM_AIR_IDS[0] => "MEM_ALIGN_ROM".to_string(), val if val == ARITH_AIR_IDS[0] => "ARITH".to_string(), - // val if val == ARITH_TABLE_AIR_IDS[0] => "ARITH_TABLE".to_string(), - // val if val == ARITH_RANGE_TABLE_AIR_IDS[0] => "ARITH_RANGE_TABLE".to_string(), val if val == ARITH_EQ_AIR_IDS[0] => "ARITH_EQ".to_string(), - // val if val == ARITH_EQ_LT_TABLE_AIR_IDS[0] => "ARITH_EQ_LT_TABLE".to_string(), + val if val == ARITH_EQ_384_AIR_IDS[0] => "ARITH_EQ_384".to_string(), val if val == BINARY_AIR_IDS[0] => "BINARY".to_string(), val if val == BINARY_ADD_AIR_IDS[0] => "BINARY_ADD".to_string(), - // val if val == BINARY_TABLE_AIR_IDS[0] => "BINARY_TABLE".to_string(), val if val == BINARY_EXTENSION_AIR_IDS[0] => "BINARY_EXTENSION".to_string(), - // val if val == BINARY_EXTENSION_TABLE_AIR_IDS[0] => "BINARY_EXTENSION_TABLE".to_string(), + val if val == ADD_256_AIR_IDS[0] => "ADD_256".to_string(), val if val == KECCAKF_AIR_IDS[0] => "KECCAKF".to_string(), - // val if val == KECCAKF_TABLE_AIR_IDS[0] => "KECCAKF_TABLE".to_string(), val if val == SHA_256_F_AIR_IDS[0] => "SHA_256_F".to_string(), - // val if val == SPECIFIED_RANGES_AIR_IDS[0] => "SPECIFIED_RANGES".to_string(), + val if val == POSEIDON_2_AIR_IDS[0] => "POSEIDON_2".to_string(), + val if val == SPECIFIED_RANGES_AIR_IDS[0] => "SPECIFIED_RANGES".to_string(), _ => format!("Unknown air_id: {air_id}"), } } diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index 03176c35d..fc6792504 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -1,7 +1,6 @@ #[cfg(all(target_os = "linux", target_arch = "x86_64"))] use named_sem::NamedSemaphore; -use zisk_common::ExecutorStatsHandle; -use zisk_common::Plan; +use zisk_common::{stats_begin, stats_end, stats_mark, ExecutorStatsHandle, Plan}; use std::ffi::c_void; use std::sync::atomic::{fence, Ordering}; @@ -19,9 +18,6 @@ use mem_planner_cpp::MemPlanner; use anyhow::{Context, Result}; -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; - #[cfg(feature = "save_mem_plans")] use mem_common::save_plans; @@ -93,10 +89,7 @@ impl AsmRunnerMO { base_port: Option, _stats: ExecutorStatsHandle, ) -> Result { - #[cfg(feature = "stats")] - let parent_stats_id = _stats.next_id(); - #[cfg(feature = "stats")] - _stats.add_stat(0, parent_stats_id, "ASM_MO_RUNNER", 0, ExecutorStatsEvent::Begin); + stats_begin!(_stats, 0, _runner_scope, "ASM_MO_RUNNER", 0); let port = AsmServices::port_base_for(base_port, local_rank); @@ -105,20 +98,17 @@ impl AsmRunnerMO { let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; - let __stats = _stats.clone(); + // Capture parent id for thread (StatsScope is not Send) + let _parent_id = _runner_scope.id(); + let _thread_stats = _stats.clone(); let handle = std::thread::spawn(move || { - #[cfg(feature = "stats")] - let stats_id = __stats.next_id(); - #[cfg(feature = "stats")] - __stats.add_stat(parent_stats_id, stats_id, "ASM_MO", 0, ExecutorStatsEvent::Begin); + stats_begin!(_thread_stats, _parent_id, _mo_scope, "ASM_MO", 0); let asm_services = AsmServices::new(world_rank, local_rank, base_port); let result = asm_services.send_memory_ops_request(max_steps, chunk_size); - // Add to executor stats - #[cfg(feature = "stats")] - __stats.add_stat(parent_stats_id, stats_id, "ASM_MO", 0, ExecutorStatsEvent::End); + stats_end!(_thread_stats, &_mo_scope); result }); @@ -134,16 +124,7 @@ impl AsmRunnerMO { // Initialize C++ memory operations trace mem_planner.execute(); - #[cfg(feature = "stats")] - let stats_id = _stats.next_id(); - #[cfg(feature = "stats")] - _stats.add_stat( - parent_stats_id, - stats_id, - "MO_PROCESS_CHUNKS", - 0, - ExecutorStatsEvent::Begin, - ); + stats_begin!(_stats, &_runner_scope, _process_scope, "MO_PROCESS_CHUNKS", 0); // Threshold (in bytes) used to detect when we need to check for new shared memory files. // Must match MAX_CHUNK_TRACE_SIZE from main.c to ensure we check before the producer @@ -192,18 +173,7 @@ impl AsmRunnerMO { data_ptr = unsafe { data_ptr.add(1) }; - // Add to executor stats - #[cfg(feature = "stats")] - { - let stats_id = _stats.next_id(); - _stats.add_stat( - parent_stats_id, - stats_id, - "MO_CHUNK_DONE", - 0, - ExecutorStatsEvent::Mark, - ); - } + stats_mark!(_stats, &_runner_scope, "MO_CHUNK_DONE", 0); mem_planner.add_chunk(chunk.mem_ops_size, data_ptr as *const c_void); @@ -249,45 +219,22 @@ impl AsmRunnerMO { let mut mem_align_plans = mem_planner.wait_mem_align_plans(); mem_planner.wait(); - // Add to executor stats - #[cfg(feature = "stats")] - _stats.add_stat(parent_stats_id, stats_id, "MO_PROCESS_CHUNKS", 0, ExecutorStatsEvent::End); - - #[cfg(feature = "stats")] - let stats_id = _stats.next_id(); - #[cfg(feature = "stats")] - _stats.add_stat( - parent_stats_id, - stats_id, - "MO_COLLECT_PLANS", - 0, - ExecutorStatsEvent::Begin, - ); + stats_end!(_stats, &_process_scope); + stats_begin!(_stats, &_runner_scope, _collect_scope, "MO_COLLECT_PLANS", 0); let plans = mem_planner.collect_plans(&mut mem_align_plans); #[cfg(feature = "save_mem_plans")] save_plans(&plans, "mem_plans_cpp.txt"); - // Add to executor stats - #[cfg(feature = "stats")] - _stats.add_stat(parent_stats_id, stats_id, "MO_COLLECT_PLANS", 0, ExecutorStatsEvent::End); - - // #[cfg(feature = "stats")] - // { - // let mem_stats = mem_planner.get_mem_stats(); - // for i in mem_stats { - // _stats.add_stat(i); - // } - // } + stats_end!(_stats, &_collect_scope); + preloaded.handle_mo = Some(std::thread::spawn(move || { drop(mem_planner); MemPlanner::new() })); - #[cfg(feature = "stats")] - _stats.add_stat(0, parent_stats_id, "ASM_MO_RUNNER", 0, ExecutorStatsEvent::End); - + stats_end!(_stats, &_runner_scope); Ok(AsmRunnerMO::new(plans)) } } diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index d2c54bdf4..8fafef8b6 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -1,4 +1,5 @@ use named_sem::NamedSemaphore; +use zisk_common::{stats_begin, stats_end, stats_mark}; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] use zisk_common::{ChunkId, EmuTrace, ExecutorStatsHandle}; @@ -17,9 +18,6 @@ use crate::{ use anyhow::{Context, Result}; -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; - pub struct MTOutputShmem { pub output_shmem: AsmMultiSharedMemory, } @@ -67,12 +65,7 @@ impl AsmRunnerMT { base_port: Option, _stats: ExecutorStatsHandle, ) -> Result>> { - let __stats = _stats.clone(); - - #[cfg(feature = "stats")] - let parent_stats_id = __stats.next_id(); - #[cfg(feature = "stats")] - _stats.add_stat(0, parent_stats_id, "ASM_MT_RUNNER", 0, ExecutorStatsEvent::Begin); + stats_begin!(_stats, 0, _runner_scope, "ASM_MT_RUNNER", 0); let port = AsmServices::port_base_for(base_port, local_rank); @@ -81,18 +74,17 @@ impl AsmRunnerMT { let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; + // Capture parent id for thread (StatsScope is not Send) + let _parent_id = _runner_scope.id(); + let _thread_stats = _stats.clone(); let handle = std::thread::spawn(move || { let asm_services = AsmServices::new(world_rank, local_rank, base_port); - #[cfg(feature = "stats")] - let stats_id = __stats.next_id(); - #[cfg(feature = "stats")] - __stats.add_stat(parent_stats_id, stats_id, "ASM_MT", 0, ExecutorStatsEvent::Begin); + stats_begin!(_thread_stats, _parent_id, _mt_scope, "ASM_MT", 0); let start = Instant::now(); let result = asm_services.send_minimal_trace_request(max_steps, chunk_size); - #[cfg(feature = "stats")] - __stats.add_stat(parent_stats_id, stats_id, "ASM_MT", 0, ExecutorStatsEvent::End); + stats_end!(_thread_stats, &_mt_scope); (result, start.elapsed()) }); @@ -102,8 +94,6 @@ impl AsmRunnerMT { // Get the pointer to the data in the shared memory. let mut data_ptr = preloaded.output_shmem.data_ptr() as *const AsmMTChunk; - let __stats = _stats.clone(); - // Calculate threshold for detecting when to map additional shared memory files. // CRITICAL: These constants must match main.c to ensure we check for new files BEFORE // the C++ producer needs to allocate beyond current mapped region. Mismatch will cause @@ -135,17 +125,7 @@ impl AsmRunnerMT { let exit_code = loop { match sem_chunk_done.timed_wait(SEM_CHUNK_DONE_WAIT_DURATION) { Ok(()) => { - #[cfg(feature = "stats")] - { - let stats_id = __stats.next_id(); - __stats.add_stat( - parent_stats_id, - stats_id, - "MT_CHUNK_DONE", - 0, - ExecutorStatsEvent::Mark, - ); - } + stats_mark!(_stats, &_runner_scope, "MT_CHUNK_DONE", 0); // Synchronize with memory changes from the C++ side fence(Ordering::Acquire); @@ -211,9 +191,7 @@ impl AsmRunnerMT { assert!(response.trace_len > 0); assert!(response.trace_len <= response.allocated_len); - #[cfg(feature = "stats")] - _stats.add_stat(0, parent_stats_id, "ASM_MT_RUNNER", 0, ExecutorStatsEvent::End); - + stats_end!(_stats, &_runner_scope); Ok(emu_traces) } } diff --git a/emulator-asm/asm-runner/src/asm_rh_runner.rs b/emulator-asm/asm-runner/src/asm_rh_runner.rs index de904f9f4..4d3f3022a 100644 --- a/emulator-asm/asm-runner/src/asm_rh_runner.rs +++ b/emulator-asm/asm-runner/src/asm_rh_runner.rs @@ -1,13 +1,13 @@ -use tracing::error; -use zisk_common::ExecutorStatsHandle; - use crate::{ sem_chunk_done_name, shmem_output_name, AsmRHData, AsmRHHeader, AsmRunError, AsmService, AsmServices, AsmSharedMemory, SEM_CHUNK_DONE_WAIT_DURATION, }; -use anyhow::{Context, Result}; use named_sem::NamedSemaphore; use std::sync::atomic::{fence, Ordering}; +use tracing::error; +use zisk_common::{stats_begin, stats_end, ExecutorStatsHandle}; + +use anyhow::{Context, Result}; pub struct RHOutputShmem { pub output_shmem: AsmSharedMemory, @@ -30,9 +30,6 @@ impl RHOutputShmem { } } -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; - // This struct is used to run the assembly code in a separate process and generate the ROM histogram. pub struct AsmRunnerRH { pub asm_rowh_output: AsmRHData, @@ -59,12 +56,7 @@ impl AsmRunnerRH { unlock_mapped_memory: bool, _stats: ExecutorStatsHandle, ) -> Result { - let __stats = _stats.clone(); - - #[cfg(feature = "stats")] - let parent_stats_id = __stats.next_id(); - #[cfg(feature = "stats")] - _stats.add_stat(0, parent_stats_id, "ASM_RH_RUNNER", 0, ExecutorStatsEvent::Begin); + stats_begin!(_stats, 0, _runner_scope, "ASM_RH_RUNNER", 0); let port = AsmServices::port_base_for(base_port, local_rank); @@ -105,10 +97,7 @@ impl AsmRunnerRH { let asm_rowh_output = AsmRHData::from_shared_memory(&asm_shared_memory.as_ref().unwrap().output_shmem); - // Add to executor stats - #[cfg(feature = "stats")] - _stats.add_stat(0, parent_stats_id, "ASM_RH_RUNNER", 0, ExecutorStatsEvent::End); - + stats_end!(_stats, &_runner_scope); Ok(AsmRunnerRH::new(asm_rowh_output)) } } diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index b06ddb5f4..3e6981e0a 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -15,9 +15,10 @@ use data_bus::DataBusTrait; use fields::PrimeField64; use proofman_common::ProofCtx; use sm_rom::RomSM; -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; -use zisk_common::{io::ZiskStdin, ChunkId, EmuTrace, ExecutorStatsHandle, ZiskExecutionResult}; +use zisk_common::{ + io::ZiskStdin, stats_begin, stats_end, ChunkId, EmuTrace, ExecutorStatsHandle, StatsScope, + ZiskExecutionResult, +}; use zisk_core::{ZiskRom, MAX_INPUT_SIZE}; use ziskemu::ZiskEmulator; @@ -116,7 +117,7 @@ impl EmulatorAsm { pctx: &ProofCtx, sm_bundle: &StaticSMBundle, stats: &ExecutorStatsHandle, - _caller_stats_id: u64, + _caller_stats_scope: &StatsScope, ) -> ( Vec, DeviceMetricsList, @@ -124,29 +125,16 @@ impl EmulatorAsm { Option>, ZiskExecutionResult, ) { - #[cfg(feature = "stats")] - let parent_stats_id = stats.next_id(); - #[cfg(feature = "stats")] - stats.add_stat( - _caller_stats_id, - parent_stats_id, - "EXECUTE_WITH_ASSEMBLY", - 0, - ExecutorStatsEvent::Begin, - ); + stats_begin!(stats, _caller_stats_scope, _exec_scope, "EXECUTE_WITH_ASSEMBLY", 0); - #[cfg(feature = "stats")] - let stats_id = stats.next_id(); - #[cfg(feature = "stats")] - stats.add_stat(parent_stats_id, stats_id, "ASM_WRITE_INPUT", 0, ExecutorStatsEvent::Begin); + stats_begin!(stats, &_exec_scope, _write_scope, "ASM_WRITE_INPUT", 0); let mut input_writer = self.shmem_input_writer.lock().unwrap(); input_writer.get_or_insert_with(|| self.create_shmem_writer(&AsmService::MO)); write_input(&mut stdin.lock().unwrap(), input_writer.as_ref().unwrap()); - #[cfg(feature = "stats")] - stats.add_stat(parent_stats_id, stats_id, "ASM_WRITE_INPUT", 0, ExecutorStatsEvent::End); + stats_end!(stats, &_write_scope); let chunk_size = self.chunk_size; let (world_rank, local_rank, base_port) = @@ -207,8 +195,7 @@ impl EmulatorAsm { ); } - #[cfg(feature = "stats")] - stats.add_stat(0, parent_stats_id, "EXECUTE_WITH_ASSEMBLY", 0, ExecutorStatsEvent::End); + stats_end!(stats, &_exec_scope); (min_traces, main_count, secn_count, Some(handle_mo), execution_result) } @@ -237,29 +224,21 @@ impl EmulatorAsm { sm_bundle: &StaticSMBundle, stats: &ExecutorStatsHandle, ) -> (Vec, DeviceMetricsList, NestedDeviceMetricsList) { - #[cfg(feature = "stats")] - let parent_stats_id = stats.next_id(); - #[cfg(feature = "stats")] - stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::Begin); + stats_begin!(stats, 0, _mt_scope, "RUN_MT_ASSEMBLY", 0); let results_mu: Mutex> = Mutex::new(Vec::new()); + // Capture the parent scope ID so it can be copied into the closure + #[allow(unused_variables)] + let mt_scope_id = _mt_scope.id(); + let emu_traces = rayon::in_place_scope(|scope| { let on_chunk = |idx: usize, emu_trace: std::sync::Arc| { let chunk_id = ChunkId(idx); let zisk_rom = &self.zisk_rom; let results_ref = &results_mu; scope.spawn(move |_| { - #[cfg(feature = "stats")] - let stats_id = stats.next_id(); - #[cfg(feature = "stats")] - stats.add_stat( - parent_stats_id, - stats_id, - "MT_CHUNK_PLAYER", - 0, - ExecutorStatsEvent::Begin, - ); + stats_begin!(stats, mt_scope_id, _chunk_scope, "MT_CHUNK_PLAYER", 0); let mut data_bus = sm_bundle.build_data_bus_counters(); @@ -272,14 +251,7 @@ impl EmulatorAsm { data_bus.on_close(); - #[cfg(feature = "stats")] - stats.add_stat( - parent_stats_id, - stats_id, - "MT_CHUNK_PLAYER", - 0, - ExecutorStatsEvent::End, - ); + stats_end!(stats, &_chunk_scope); results_ref.lock().unwrap().push((chunk_id, data_bus)); }); @@ -329,8 +301,7 @@ impl EmulatorAsm { } } - #[cfg(feature = "stats")] - stats.add_stat(0, parent_stats_id, "RUN_MT_ASSEMBLY", 0, ExecutorStatsEvent::End); + stats_end!(stats, &_mt_scope); (emu_traces, main_count, secn_count) } } @@ -342,7 +313,7 @@ impl crate::Emulator for EmulatorAsm { pctx: &ProofCtx, sm_bundle: &StaticSMBundle, stats: &ExecutorStatsHandle, - caller_stats_id: u64, + caller_stats_scope: &StatsScope, ) -> ( Vec, DeviceMetricsList, @@ -350,6 +321,6 @@ impl crate::Emulator for EmulatorAsm { Option>, ZiskExecutionResult, ) { - self.execute(stdin, pctx, sm_bundle, stats, caller_stats_id) + self.execute(stdin, pctx, sm_bundle, stats, caller_stats_scope) } } diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index 5073086f2..ffbe7b75c 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -44,7 +44,7 @@ impl EmulatorRust { /// * `_pctx` - Proof context carrying field-parameterized configuration for execution. /// * `sm_bundle` - Static state machine bundle used for counting device metrics. /// * `_stats` - Handle to executor statistics collection. - /// * `_caller_stats_id` - Identifier used to associate collected statistics with the caller. + /// * `_caller_stats_scope` - Stats scope used to associate collected statistics with the caller. /// /// # Returns /// A tuple containing: @@ -56,10 +56,7 @@ impl EmulatorRust { pub fn execute( &self, stdin: &Mutex, - _pctx: &ProofCtx, sm_bundle: &StaticSMBundle, - _stats: &ExecutorStatsHandle, - _caller_stats_id: u64, ) -> ( Vec, DeviceMetricsList, @@ -165,10 +162,10 @@ impl crate::Emulator for EmulatorRust { fn execute( &self, stdin: &Mutex, - pctx: &ProofCtx, + _pctx: &ProofCtx, sm_bundle: &StaticSMBundle, - stats: &ExecutorStatsHandle, - caller_stats_id: u64, + _stats: &ExecutorStatsHandle, + _caller_stats_scope: &zisk_common::StatsScope, ) -> ( Vec, DeviceMetricsList, @@ -176,6 +173,6 @@ impl crate::Emulator for EmulatorRust { Option>, ZiskExecutionResult, ) { - self.execute(stdin, pctx, sm_bundle, stats, caller_stats_id) + self.execute(stdin, sm_bundle) } } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 26373bb37..80bcf6cbc 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -30,10 +30,9 @@ use zisk_common::io::{StreamSource, ZiskStdin, ZiskStream}; use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; -use zisk_common::ChunkId; use zisk_common::{ - BusDevice, BusDeviceMetrics, CheckPoint, EmuTrace, ExecutorStatsHandle, Instance, InstanceCtx, - InstanceType, Plan, Stats, ZiskExecutionResult, + stats_begin, stats_end, BusDevice, BusDeviceMetrics, CheckPoint, ChunkId, EmuTrace, + ExecutorStatsHandle, Instance, InstanceCtx, InstanceType, Plan, Stats, ZiskExecutionResult, }; use zisk_pil::{ ZiskPublicValues, INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, @@ -45,8 +44,6 @@ use std::{ collections::HashMap, sync::{Arc, Mutex, RwLock}, }; -#[cfg(feature = "stats")] -use zisk_common::ExecutorStatsEvent; use crossbeam::atomic::AtomicCell; @@ -285,16 +282,7 @@ impl ZiskExecutor { .expect("Failed to get instance info"); let witness_start_time = Instant::now(); - #[cfg(feature = "stats")] - let stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat( - _caller_stats_id, - stats_id, - "AIR_MAIN_WITNESS", - air_id, - ExecutorStatsEvent::Begin, - ); + stats_begin!(self.stats, _caller_stats_id, _stats_scope, "AIR_MAIN_WITNESS", air_id); let min_traces_guard = self.min_traces.read().unwrap(); let min_traces = min_traces_guard.as_ref().expect("min_traces should not be None"); @@ -309,14 +297,7 @@ impl ZiskExecutor { pctx.add_air_instance(air_instance, main_instance.ictx.global_id); - #[cfg(feature = "stats")] - self.stats.add_stat( - _caller_stats_id, - stats_id, - "AIR_MAIN_WITNESS", - air_id, - ExecutorStatsEvent::End, - ); + stats_end!(self.stats, &_stats_scope); let stats = Stats { airgroup_id, @@ -353,16 +334,7 @@ impl ZiskExecutor { #[cfg(feature = "stats")] let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id)?; - #[cfg(feature = "stats")] - let stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat( - _caller_stats_id, - stats_id, - "AIR_SECN_WITNESS", - air_id, - ExecutorStatsEvent::Begin, - ); + stats_begin!(self.stats, _caller_stats_id, _stats_scope, "AIR_SECN_WITNESS", air_id); let collectors_by_instance = { let mut guard = self.collectors_by_instance.write().unwrap(); @@ -385,16 +357,9 @@ impl ZiskExecutor { { pctx.add_air_instance(air_instance, global_id); } - #[cfg(feature = "stats")] - { - self.stats.add_stat( - _caller_stats_id, - stats_id, - "AIR_SECN_WITNESS", - air_id, - ExecutorStatsEvent::End, - ); - } + + stats_end!(self.stats, &_stats_scope); + self.stats.set_witness_duration(global_id, witness_start_time.elapsed().as_millis()); Ok(()) } @@ -630,16 +595,8 @@ impl ZiskExecutor { ) -> ProofmanResult<()> { #[cfg(feature = "stats")] let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id)?; - #[cfg(feature = "stats")] - let stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat( - _caller_stats_id, - stats_id, - "AIR_WITNESS_TABLE", - air_id, - ExecutorStatsEvent::Begin, - ); + stats_begin!(self.stats, _caller_stats_id, _stats_scope, "AIR_WITNESS_TABLE", air_id); + assert_eq!(table_instance.instance_type(), InstanceType::Table, "Instance is not a table"); if let Some(air_instance) = @@ -653,14 +610,7 @@ impl ZiskExecutor { } } - #[cfg(feature = "stats")] - self.stats.add_stat( - _caller_stats_id, - stats_id, - "AIR_WITNESS_TABLE", - air_id, - ExecutorStatsEvent::Begin, - ); + stats_end!(self.stats, &_stats_scope); Ok(()) } @@ -729,13 +679,10 @@ impl WitnessComponent for ZiskExecutor { pctx: Arc>, global_ids: &RwLock>, ) -> ProofmanResult<()> { - #[cfg(feature = "stats")] - let parent_stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat(0, parent_stats_id, "EXECUTE", 0, ExecutorStatsEvent::Begin); - self.reset(); + stats_begin!(self.stats, 0, _exec_scope, "EXECUTE", 0); + // Set the start time of the current execution self.stats.set_start_time(Instant::now()); @@ -750,16 +697,7 @@ impl WitnessComponent for ZiskExecutor { timer_start_info!(COMPUTE_MINIMAL_TRACE); let (min_traces, main_count, mut secn_count, handle_mo, execution_result) = - self.emulator.execute( - &self.stdin, - &pctx, - &self.sm_bundle, - &self.stats, - #[cfg(feature = "stats")] - parent_stats_id, - #[cfg(not(feature = "stats"))] - 0, - ); + self.emulator.execute(&self.stdin, &pctx, &self.sm_bundle, &self.stats, &_exec_scope); timer_stop_and_log_info!(COMPUTE_MINIMAL_TRACE); @@ -767,11 +705,7 @@ impl WitnessComponent for ZiskExecutor { *self.execution_result.lock().unwrap() = execution_result; // Plan the main and secondary instances using the counted metrics - // stats_begin!(stats_next_id!(), parent_stats_id, "PLAN"); - #[cfg(feature = "stats")] - let stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat(parent_stats_id, stats_id, "MAIN_PLAN", 0, ExecutorStatsEvent::Begin); + stats_begin!(self.stats, &_exec_scope, _main_plan_scope, "MAIN_PLAN", 0); timer_start_info!(PLAN); let (main_planning, public_values) = @@ -779,87 +713,37 @@ impl WitnessComponent for ZiskExecutor { *self.min_traces.write().unwrap() = Some(min_traces); self.assign_main_instances(&pctx, global_ids, main_planning); - // Add to executor stats - #[cfg(feature = "stats")] - self.stats.add_stat(parent_stats_id, stats_id, "MAIN_PLAN", 0, ExecutorStatsEvent::End); - #[cfg(feature = "stats")] - let stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat(parent_stats_id, stats_id, "SECN_PLAN", 0, ExecutorStatsEvent::Begin); + stats_end!(self.stats, &_main_plan_scope); + stats_begin!(self.stats, &_exec_scope, _secn_plan_scope, "SECN_PLAN", 0); let mut secn_planning = self.sm_bundle.plan_sec(&mut secn_count); timer_stop_and_log_info!(PLAN); timer_start_info!(PLAN_MEM_CPP); - // Add to executor stats - #[cfg(feature = "stats")] - self.stats.add_stat(parent_stats_id, stats_id, "SECN_PLAN", 0, ExecutorStatsEvent::End); + stats_end!(self.stats, &_secn_plan_scope); if let Some(handle_mo) = handle_mo { - #[cfg(feature = "stats")] - let stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat( - parent_stats_id, - stats_id, - "MO_PLAN_WAIT", - 0, - ExecutorStatsEvent::Begin, - ); + stats_begin!(self.stats, &_exec_scope, _mo_wait_scope, "MO_PLAN_WAIT", 0); // Wait for the memory operations thread to finish let asm_runner_mo = handle_mo.join().expect("Error during Assembly Memory Operations thread execution"); - // Add to executor stats - #[cfg(feature = "stats")] - self.stats.add_stat( - parent_stats_id, - stats_id, - "MO_PLAN_WAIT", - 0, - ExecutorStatsEvent::End, - ); - #[cfg(feature = "stats")] - let stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat( - parent_stats_id, - stats_id, - "MO_PLAN_ADD", - 0, - ExecutorStatsEvent::Begin, - ); + stats_end!(self.stats, &_mo_wait_scope); + stats_begin!(self.stats, &_exec_scope, _mo_add_scope, "MO_PLAN_ADD", 0); secn_planning .entry(self.sm_bundle.get_mem_sm_id()) .or_default() .extend(asm_runner_mo.plans); - // Add to executor stats - #[cfg(feature = "stats")] - self.stats.add_stat( - parent_stats_id, - stats_id, - "MO_PLAN_ADD", - 0, - ExecutorStatsEvent::End, - ); + stats_end!(self.stats, &_mo_add_scope); } timer_stop_and_log_info!(PLAN_MEM_CPP); - #[cfg(feature = "stats")] - let stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat( - parent_stats_id, - stats_id, - "CONFIGURE_INSTANCES", - 0, - ExecutorStatsEvent::Begin, - ); + stats_begin!(self.stats, &_exec_scope, _config_scope, "CONFIGURE_INSTANCES", 0); // Configure the instances self.sm_bundle.configure_instances(&pctx, &secn_planning); @@ -916,21 +800,11 @@ impl WitnessComponent for ZiskExecutor { } } - // Add to executor stats - #[cfg(feature = "stats")] - self.stats.add_stat( - parent_stats_id, - stats_id, - "CONFIGURE_INSTANCES", - 0, - ExecutorStatsEvent::End, - ); - - #[cfg(feature = "stats")] - self.stats.add_stat(0, parent_stats_id, "EXECUTE", 0, ExecutorStatsEvent::End); + stats_end!(self.stats, &_config_scope); + stats_end!(self.stats, &_exec_scope); // #[cfg(feature = "stats")] - // self.stats.lock().unwrap().store_stats(); + // self.stats.store_stats(); Ok(()) } @@ -955,10 +829,7 @@ impl WitnessComponent for ZiskExecutor { return Ok(()); } - #[cfg(feature = "stats")] - let parent_stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat(0, parent_stats_id, "CALCULATE_WITNESS", 0, ExecutorStatsEvent::Begin); + stats_begin!(self.stats, 0, _witness_scope, "CALCULATE_WITNESS", 0); let is_asm_emulator = self.emulator.is_asm_emulator(); @@ -975,10 +846,7 @@ impl WitnessComponent for ZiskExecutor { &pctx, main_instance, buffer_pool.take_buffer(), - #[cfg(feature = "stats")] - parent_stats_id, - #[cfg(not(feature = "stats"))] - 0, + _witness_scope.id(), )?; } else { let secn_instance = &self.secn_instances.read().unwrap()[&global_id]; @@ -1015,10 +883,7 @@ impl WitnessComponent for ZiskExecutor { global_id, &**secn_instance, buffer_pool.take_buffer(), - #[cfg(feature = "stats")] - parent_stats_id, - #[cfg(not(feature = "stats"))] - 0, + _witness_scope.id(), )?; } InstanceType::Table => self.witness_table( @@ -1027,20 +892,14 @@ impl WitnessComponent for ZiskExecutor { global_id, &**secn_instance, Vec::new(), - #[cfg(feature = "stats")] - parent_stats_id, - #[cfg(not(feature = "stats"))] - 0, + _witness_scope.id(), )?, } } } Ok(()) })?; - - // Add to executor stats - #[cfg(feature = "stats")] - self.stats.add_stat(0, parent_stats_id, "CALCULATE_WITNESS", 0, ExecutorStatsEvent::End); + stats_end!(self.stats, &_witness_scope); Ok(()) } @@ -1054,16 +913,7 @@ impl WitnessComponent for ZiskExecutor { n_cores: usize, _buffer_pool: &dyn BufferPool, ) -> ProofmanResult<()> { - #[cfg(feature = "stats")] - let parent_stats_id = self.stats.next_id(); - #[cfg(feature = "stats")] - self.stats.add_stat( - 0, - parent_stats_id, - "PRE_CALCULATE_WITNESS", - 0, - ExecutorStatsEvent::Begin, - ); + stats_begin!(self.stats, 0, _pre_scope, "PRE_CALCULATE_WITNESS", 0); if stage != 1 { return Ok(()); @@ -1123,15 +973,7 @@ impl WitnessComponent for ZiskExecutor { } }); - // Add to executor stats - #[cfg(feature = "stats")] - self.stats.add_stat( - 0, - parent_stats_id, - "PRE_CALCULATE_WITNESS", - 0, - ExecutorStatsEvent::End, - ); + stats_end!(self.stats, &_pre_scope); Ok(()) } diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 9768708f6..19fb50fc9 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -27,7 +27,7 @@ use asm_runner::AsmRunnerMO; use fields::PrimeField64; use proofman_common::ProofCtx; use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; -use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, ZiskExecutionResult}; +use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope, ZiskExecutionResult}; /// Trait for unified execution across different emulator backends pub trait Emulator: Send + Sync { @@ -38,7 +38,7 @@ pub trait Emulator: Send + Sync { pctx: &ProofCtx, sm_bundle: &StaticSMBundle, stats: &ExecutorStatsHandle, - caller_stats_id: u64, + caller_stats_scope: &StatsScope, ) -> ( Vec, DeviceMetricsList, @@ -68,7 +68,7 @@ impl Emulator for EmulatorKind { pctx: &ProofCtx, sm_bundle: &StaticSMBundle, stats: &ExecutorStatsHandle, - caller_stats_id: u64, + caller_stats_scope: &StatsScope, ) -> ( Vec, DeviceMetricsList, @@ -77,8 +77,8 @@ impl Emulator for EmulatorKind { ZiskExecutionResult, ) { match self { - Self::Asm(e) => e.execute(stdin, pctx, sm_bundle, stats, caller_stats_id), - Self::Rust(e) => e.execute(stdin, pctx, sm_bundle, stats, caller_stats_id), + Self::Asm(e) => e.execute(stdin, pctx, sm_bundle, stats, caller_stats_scope), + Self::Rust(e) => e.execute(stdin, sm_bundle), } } } diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 8d02ae6bd..386ca3a30 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -23,6 +23,7 @@ use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; use std::sync::OnceLock; +use zisk_common::stats_mark; use zisk_common::{ io::{StreamSource, ZiskStdin}, ElfBinaryLike, ExecutorStatsHandle, ZiskExecutionResult, @@ -242,19 +243,10 @@ impl ProverBackend { anyhow::anyhow!("Failed to get execution result from emulator prover") })?; - // Store the stats in stats.json + stats_mark!(stats, 0, "END", 0); + #[cfg(feature = "stats")] - { - let stats_id = stats.get_inner().lock().unwrap().next_id(); - stats.get_inner().lock().unwrap().add_stat( - 0, - stats_id, - "END", - 0, - ExecutorStatsEvent::Mark, - ); - stats.get_inner().lock().unwrap().store_stats(); - } + stats.store_stats(); Ok(ZiskVerifyConstraintsResult { execution: result, duration: elapsed, stats }) } @@ -312,13 +304,10 @@ impl ProverBackend { anyhow::anyhow!("Failed to get execution result from emulator prover") })?; - // Store the stats in stats.json + stats_mark!(stats, 0, "END", 0); + #[cfg(feature = "stats")] - { - let stats_id = _stats.lock().unwrap().get_id(); - _stats.lock().unwrap().add_stat(0, stats_id, "END", 0, ExecutorStatsEvent::Mark); - _stats.lock().unwrap().store_stats(); - } + stats.store_stats(); proofman.set_barrier(); @@ -380,18 +369,10 @@ impl ProverBackend { })?; // Store the stats in stats.json + stats_mark!(stats, 0, "END", 0); + #[cfg(feature = "stats")] - { - let stats_id = stats.get_inner().lock().unwrap().next_id(); - stats.get_inner().lock().unwrap().add_stat( - 0, - stats_id, - "END", - 0, - ExecutorStatsEvent::Mark, - ); - stats.get_inner().lock().unwrap().store_stats(); - } + stats.store_stats(); proofman.set_barrier(); From c3153d0c1a545c79ffece248f6d18fe4c7ef51d1 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:23:24 +0000 Subject: [PATCH 462/782] fix documentation --- common/src/executor_stats.rs | 13 +++---------- emulator-asm/asm-runner/src/asm_mo_runner.rs | 2 +- emulator-asm/asm-runner/src/asm_mt_runner.rs | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/common/src/executor_stats.rs b/common/src/executor_stats.rs index 2327402fe..3a28ead0c 100644 --- a/common/src/executor_stats.rs +++ b/common/src/executor_stats.rs @@ -12,7 +12,7 @@ use zisk_pil::*; use crate::Stats; /// Trait for types that can be converted to a stats ID. -/// Implemented for `u64` (raw ID) and `StatsScope` (extracts ID via `.id()`). +/// Implemented for `u64` (raw ID), `StatsScope`, and references `&T` where `T: IntoStatsId`. pub trait IntoStatsId { fn as_stats_id(&self) -> u64; } @@ -24,13 +24,6 @@ impl IntoStatsId for u64 { } } -impl IntoStatsId for i32 { - #[inline] - fn as_stats_id(&self) -> u64 { - *self as u64 - } -} - impl IntoStatsId for StatsScope { #[inline] fn as_stats_id(&self) -> u64 { @@ -53,8 +46,8 @@ impl IntoStatsId for &T { /// stats_begin!(self.stats, 0, parent_scope, "PARENT_OP", 0); /// stats_begin!(self.stats, &parent_scope, child_scope, "CHILD_OP", 0); /// // ... work ... -/// stats_end!(self.stats, &child_scope, "CHILD_OP", 0); -/// stats_end!(self.stats, &parent_scope, "PARENT_OP", 0); +/// stats_end!(self.stats, &child_scope); +/// stats_end!(self.stats, &parent_scope); /// ``` #[cfg(feature = "stats")] #[macro_export] diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index fc6792504..1f48363e0 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -98,7 +98,7 @@ impl AsmRunnerMO { let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; - // Capture parent id for thread (StatsScope is not Send) + // Capture parent id for thread let _parent_id = _runner_scope.id(); let _thread_stats = _stats.clone(); diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 8fafef8b6..d7c8b7c43 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -74,7 +74,7 @@ impl AsmRunnerMT { let mut sem_chunk_done = NamedSemaphore::create(sem_chunk_done_name.clone(), 0) .map_err(|e| AsmRunError::SemaphoreError(sem_chunk_done_name.clone(), e))?; - // Capture parent id for thread (StatsScope is not Send) + // Capture parent id for thread let _parent_id = _runner_scope.id(); let _thread_stats = _stats.clone(); let handle = std::thread::spawn(move || { From 725fbf484b241da700d81f66dcf2bcfa485cfeee Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:28:32 +0100 Subject: [PATCH 463/782] fix macOS --- executor/src/emu_asm_stub.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/executor/src/emu_asm_stub.rs b/executor/src/emu_asm_stub.rs index d52b83ec0..7eb798c59 100644 --- a/executor/src/emu_asm_stub.rs +++ b/executor/src/emu_asm_stub.rs @@ -9,7 +9,7 @@ use asm_runner::AsmRunnerMO; use fields::PrimeField64; use proofman_common::ProofCtx; use sm_rom::RomSM; -use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, ZiskExecutionResult}; +use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope, ZiskExecutionResult}; use zisk_core::ZiskRom; pub struct EmulatorAsm {} @@ -35,7 +35,7 @@ impl EmulatorAsm { _pctx: &ProofCtx, _sm_bundle: &StaticSMBundle, _stats: &ExecutorStatsHandle, - _caller_stats_id: u64, + _caller_stats_scope: &StatsScope, ) -> ( Vec, DeviceMetricsList, From 693b9c0dea3aa5b191893f8f91f67fb238c8cf34 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:54:11 +0000 Subject: [PATCH 464/782] set timeout wait_for_service_ready to 60'' again --- emulator-asm/asm-runner/src/asm_services/services.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index ad011ca16..1a99e4eb3 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -176,7 +176,7 @@ impl AsmServices { fn wait_for_service_ready(service: &AsmService, port: u16) { let addr = format!("127.0.0.1:{port}"); - let timeout = Duration::from_secs(50 * 5); + let timeout = Duration::from_secs(60); let retry_delay = Duration::from_millis(100); let start = Instant::now(); From 348544585e095b1dff80c10abe4ad8d15dfecfae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Fri, 6 Feb 2026 17:27:30 +0100 Subject: [PATCH 465/782] Feature/recursivef gpu (#767) * RecursiveF GPU * Fix * Adding prove snark in prove method * Extending check setup with recursive final * Fix build --- Cargo.lock | 22 ++++++------ cli/src/commands/check_setup.rs | 19 ++++++++-- cli/src/commands/prove.rs | 37 ++++++++++++++++++-- cli/src/commands/prove_snark.rs | 7 ++-- cli/src/commands/rom_setup.rs | 4 ++- emulator-asm/asm-runner/src/asm_mo_runner.rs | 1 + sdk/src/builder.rs | 6 ++++ sdk/src/prover/backend.rs | 13 ++++--- 8 files changed, 86 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d74536f62..e8a6c7934 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1189,7 +1189,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" dependencies = [ "fields", "num-bigint", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" dependencies = [ "cfg-if", "num-bigint", @@ -2965,7 +2965,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" dependencies = [ "colored", "fields", @@ -3380,7 +3380,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" dependencies = [ "bincode", "blake3", @@ -3416,7 +3416,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" dependencies = [ "bincode", "borsh", @@ -3449,7 +3449,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" dependencies = [ "fields", "itoa", @@ -3462,7 +3462,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" dependencies = [ "proc-macro2", "quote", @@ -3473,7 +3473,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" dependencies = [ "crossbeam-channel", "tracing", @@ -3482,7 +3482,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" dependencies = [ "bincode", "bytemuck", @@ -3496,7 +3496,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" dependencies = [ "bytemuck", "fields", @@ -5890,7 +5890,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b345e73c22ec25923ae12aff0466d95aae7e75ef" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" dependencies = [ "colored", "fields", diff --git a/cli/src/commands/check_setup.rs b/cli/src/commands/check_setup.rs index 26cf8930b..2e177a134 100644 --- a/cli/src/commands/check_setup.rs +++ b/cli/src/commands/check_setup.rs @@ -1,5 +1,5 @@ // extern crate env_logger; -use crate::commands::get_proving_key; +use crate::commands::{get_proving_key, get_proving_key_snark}; use anyhow::Result; use clap::Parser; use colored::Colorize; @@ -7,7 +7,7 @@ use std::path::PathBuf; use fields::Goldilocks; -use proofman::ProofMan; +use proofman::{check_setup_snark, ProofMan}; use proofman_common::initialize_logger; #[derive(Parser)] @@ -18,9 +18,16 @@ pub struct ZiskCheckSetup { #[clap(short = 'k', long)] pub proving_key: Option, + /// Setup folder path + #[clap(short = 'w', long)] + pub proving_key_snark: Option, + #[clap(short = 'a', long, default_value_t = false)] pub aggregation: bool, + #[clap(short = 's', long, default_value_t = false)] + pub snark: bool, + /// Verbosity (-v, -vv) #[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity level")] pub verbose: u8, // Using u8 to hold the number of `-v` @@ -40,6 +47,14 @@ impl ZiskCheckSetup { ) .map_err(|e| anyhow::anyhow!("Error checking setup: {}", e))?; + if self.snark { + check_setup_snark::( + &get_proving_key_snark(self.proving_key_snark.as_ref()), + self.verbose.into(), + ) + .map_err(|e| anyhow::anyhow!("Error checking setup snark: {}", e))? + } + Ok(()) } } diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 292d754a9..b9ac94e32 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -49,6 +49,10 @@ pub struct ZiskProve { #[clap(short = 'k', long)] pub proving_key: Option, + /// Setup folder path for SNARK + #[clap(short = 'w', long)] + pub proving_key_snark: Option, + /// Output dir path #[clap(short = 'o', long, default_value = "tmp")] pub output_dir: PathBuf, @@ -105,6 +109,9 @@ pub struct ZiskProve { #[clap(short = 'r', long, default_value_t = false)] pub rma: bool, + + #[clap(long, default_value_t = false)] + pub snark: bool, } impl ZiskProve { @@ -137,6 +144,10 @@ impl ZiskProve { print_banner_field("Prec. Hints", hints); } + if self.snark && self.compressed { + anyhow::bail!("Compressed proofs are not supported for SNARK generation."); + } + let stdin = ZiskStdin::from_uri(self.inputs.as_ref())?; let hints_stream = match self.hints.as_ref() { @@ -207,8 +218,10 @@ impl ZiskProve { let prover = ProverClient::builder() .aggregation(self.aggregation) .proving_key_path_opt(self.proving_key.clone()) + .proving_key_snark_path_opt(self.proving_key_snark.clone()) .verbose(self.verbose) .shared_tables(self.shared_tables) + .with_snark(self.snark) .gpu(gpu_params) .print_command_info() .build()?; @@ -231,9 +244,17 @@ impl ZiskProve { output_dir_path: Some(self.output_dir.clone()), }; - let result = prover.prove(stdin).with_proof_options(proof_options).run()?; let world_rank = prover.world_rank(); + let mut prover = prover.prove(stdin).with_proof_options(proof_options); + if self.snark { + prover = prover.plonk(); + } + if self.compressed { + prover = prover.compressed(); + } + let result = prover.run()?; + Ok((result, world_rank)) } @@ -247,7 +268,9 @@ impl ZiskProve { .aggregation(self.aggregation) .asm() .proving_key_path_opt(self.proving_key.clone()) + .proving_key_snark_path_opt(self.proving_key_snark.clone()) .verbose(self.verbose) + .with_snark(self.snark) .shared_tables(self.shared_tables) .asm_path_opt(self.asm.clone()) .base_port_opt(self.port) @@ -277,9 +300,19 @@ impl ZiskProve { if let Some(hints_stream) = hints_stream { prover.set_hints_stream(hints_stream)?; } - let result = prover.prove(stdin).with_proof_options(proof_options).run()?; + let world_rank = prover.world_rank(); + let mut prover = prover.prove(stdin).with_proof_options(proof_options); + if self.snark { + prover = prover.plonk(); + } + if self.compressed { + prover = prover.compressed(); + } + + let result = prover.run()?; + Ok((result, world_rank)) } } diff --git a/cli/src/commands/prove_snark.rs b/cli/src/commands/prove_snark.rs index 15862f9cb..1ed1a33ef 100644 --- a/cli/src/commands/prove_snark.rs +++ b/cli/src/commands/prove_snark.rs @@ -55,8 +55,11 @@ impl ZiskProveSnark { let proof = zisk_proof.get_vadcop_final_proof()?; - let snark_proof = - snark_wrapper.generate_final_snark_proof(&proof, Some(self.output_dir.clone()))?; + let snark_proof = snark_wrapper.generate_final_snark_proof( + &proof, + Some(self.output_dir.clone()), + None, + )?; snark_proof.save(self.output_dir.join("final_snark_proof.bin")).map_err(|e| { anyhow::anyhow!( "Failed to save final SNARK proof to output dir {}: {}", diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index 03b0637e1..3a391d19f 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -59,7 +59,9 @@ impl ZiskRomSetup { let mpi_ctx = Arc::new(MpiCtx::new()); let mut pctx = ProofCtx::create_ctx(proving_key, false, self.verbose.into(), mpi_ctx)?; - let params_gpu = ParamsGPU::new(false); + let mut params_gpu = ParamsGPU::new(false); + params_gpu.with_max_number_streams(1); + let sctx = Arc::new(SetupCtx::::new( &pctx.global_info, &ProofType::Basic, diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index 1f48363e0..9049b1496 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -106,6 +106,7 @@ impl AsmRunnerMO { stats_begin!(_thread_stats, _parent_id, _mo_scope, "ASM_MO", 0); let asm_services = AsmServices::new(world_rank, local_rank, base_port); + #[allow(clippy::let_and_return)] let result = asm_services.send_memory_ops_request(max_steps, chunk_size); stats_end!(_thread_stats, &_mo_scope); diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 83c519b68..74e9a3393 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -142,6 +142,12 @@ impl ProverClientBuilder { self } + #[must_use] + pub fn with_snark(mut self, snark: bool) -> Self { + self.snark_wrapper = snark; + self + } + #[must_use] pub fn proving_key_path(mut self, proving_key: PathBuf) -> Self { self.proving_key = Some(proving_key); diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 386ca3a30..271b31b1a 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -381,6 +381,7 @@ impl ProverBackend { let snark_proof = self.snark_wrapper.as_ref().unwrap().generate_final_snark_proof( &vadcop_proof, proof_options.output_dir_path.clone(), + Some(proofman.get_device_buffers_ptr()), )?; let publics = ZiskPublics::new(&vadcop_proof.public_values); @@ -498,11 +499,13 @@ impl ProverBackend { pubs.extend(publics.public_bytes()); let vadcop_final_proof = VadcopFinalProof::new(proof_bytes, pubs, false); - let snark_proof = self - .snark_wrapper - .as_ref() - .unwrap() - .generate_final_snark_proof(&vadcop_final_proof, None)?; + let device_ptr = self.proofman.as_ref().map(|p| p.get_device_buffers_ptr()); + + let snark_proof = self.snark_wrapper.as_ref().unwrap().generate_final_snark_proof( + &vadcop_final_proof, + None, + device_ptr, + )?; if snark_proof.protocol_id == SnarkProtocol::Plonk.protocol_id() { Ok(ZiskProofWithPublicValues { From 56590a456a9b878f75a3421f514e12f5b096c1fb Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Mon, 9 Feb 2026 08:43:52 +0000 Subject: [PATCH 466/782] Adjust print info --- sdk/src/builder.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 74e9a3393..bf80211c7 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -338,11 +338,7 @@ impl ProverClientBuilder { ); println!("{: >12} {}", "Proving Key".bright_green().bold(), proving_key.display()); - println!( - "{: >12} {}", - "Proving key SNARK".bright_green().bold(), - proving_key_snark.display() - ); + println!("{: >12} {}", "SNARK Key".bright_green().bold(), proving_key_snark.display()); println!(); } @@ -455,13 +451,9 @@ impl ProverClientBuilder { println!("{: >12} Prove", "Command".bright_green().bold()); } - println!("{: >12} {}", "Proving key".bright_green().bold(), proving_key.display()); + println!("{: >12} {}", "Proving Key".bright_green().bold(), proving_key.display()); - println!( - "{: >12} {}", - "Proving key SNARK".bright_green().bold(), - proving_key_snark.display() - ); + println!("{: >12} {}", "SNARK Key".bright_green().bold(), proving_key_snark.display()); println!(); } From 5f702c69443d02a38fec635840e1199f0057d1a1 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 9 Feb 2026 11:42:32 +0100 Subject: [PATCH 467/782] Improve riscv2zisk arguments parsing and usability --- core/src/bin/riscv2zisk.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/core/src/bin/riscv2zisk.rs b/core/src/bin/riscv2zisk.rs index 50626db55..24d1b2bbf 100644 --- a/core/src/bin/riscv2zisk.rs +++ b/core/src/bin/riscv2zisk.rs @@ -5,34 +5,31 @@ use std::{env, process}; use zisk_core::Riscv2zisk; /// Performs a transpilation of a RISC-V ELF file to a Zisk ROM file. -/// The binary accepts 2 arguments: the path of the input RISC-V ELF file, and the path of the -/// output Zisk rom file. +/// The binary accepts 3 arguments (4 including the executable name): +/// - the path of the input RISC-V ELF file +/// - the path of the output Zisk rom file +/// - the generation method /// After parsing the arguments, the main function calls Riscv2zisk::runfile to perform the actual /// work. fn main() { - println!("riscv2zisk converts an ELF RISCV file into a ZISK ASM file"); - // Get program arguments let args: Vec = env::args().collect(); // Check program arguments length - if args.len() < 3 || args.len() > 4 { - eprintln!("Error parsing arguments: invalid number of arguments={}. Usage: riscv2zisk [] ", args.len()); + if args.len() != 4 { + eprintln!("Error parsing arguments: invalid number of arguments={}", args.len()); for (i, arg) in args.iter().enumerate() { eprintln!("Argument {i}: {arg}"); } + eprintln!("Usage: riscv2zisk "); process::exit(1); } - // Get the 2 input parameters: ELF (RISCV) file name (input data) and ZisK file name (output - // data) + // Get the 3 arguments: the input ELF file, the output ASM file and the generation method let elf_file = args[1].clone(); - println!("ELF (RISCV) file: {elf_file}"); - let (asm_file, gen_arg) = if args.len() == 4 { - (Some(args[2].clone()), args[3].clone()) - } else { - (None, args[2].clone()) - }; + let asm_file = args[2].clone(); + let gen_arg = args[3].clone(); + println!("riscv2zisk converts a RISCV ELF file ({elf_file}) into a ZISK ASM file ({asm_file}), using generation method {gen_arg}."); let generation_method = match gen_arg.as_str() { "--gen=0" => zisk_core::AsmGenerationMethod::AsmFast, @@ -47,7 +44,7 @@ fn main() { "--gen=9" => zisk_core::AsmGenerationMethod::AsmMemReads, "--gen=10" => zisk_core::AsmGenerationMethod::AsmChunkPlayerMemReadsCollectMain, _ => { - eprintln!("Invalid generation method. Use --gen=0 (fast), =1 (minimal trace), =2 (rom histogram), =3 (main trace), =4 (chunks), =5 (bus op), =6 (zip) or =7 (mem op)."); + eprintln!("Invalid generation method. Use --gen=0 (fast), =1 (minimal trace), =2 (rom histogram), =3 (main trace), =4 (chunks), =5 (bus op), =6 (zip), =7 (mem op), =8 (min trace chunk player), =9 (mem reads), =10 (mem reads chunk player)."); process::exit(1); } }; @@ -62,7 +59,7 @@ fn main() { let rv2zk = Riscv2zisk::new(&elf); // Convert program - if let Err(e) = rv2zk.runfile(asm_file.unwrap(), generation_method, true, true, false) { + if let Err(e) = rv2zk.runfile(asm_file, generation_method, true, true, false) { println!("Application error: {e}"); process::exit(1); } From ca3886f7eda92d711b42bdc88aa13c298b6ddf12 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 9 Feb 2026 11:43:34 +0100 Subject: [PATCH 468/782] Improve parse_function_symbol() in ElfSymbolReader to support STT_GNU_IFUNC --- emulator/src/elf_symbol_reader.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/emulator/src/elf_symbol_reader.rs b/emulator/src/elf_symbol_reader.rs index 09563f0ef..bed0bd202 100644 --- a/emulator/src/elf_symbol_reader.rs +++ b/emulator/src/elf_symbol_reader.rs @@ -1,4 +1,5 @@ use memmap2::Mmap; +use object::elf::STT_GNU_IFUNC; use object::{elf::STT_FUNC, Object, ObjectSymbol, Symbol, SymbolFlags, SymbolKind}; use std::fs::File; @@ -48,7 +49,8 @@ impl ElfSymbolReader { if let Ok(name) = symbol.name() { if !name.is_empty() { if let SymbolFlags::Elf { st_info, .. } = symbol.flags() { - if (st_info & STT_FUNC) != 0 { + let st_type = st_info & 0x0f; + if st_type == STT_FUNC || st_type == STT_GNU_IFUNC { let name = self.demangle_name(name); let address = symbol.address(); let size = symbol.size(); From 81244efa8fc037e032e5dbb6ad53c87046049ef7 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 9 Feb 2026 12:26:36 +0100 Subject: [PATCH 469/782] Limit ELf file section size in collect_elf_payload_from_bytes() --- core/src/elf_extraction.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/elf_extraction.rs b/core/src/elf_extraction.rs index d9f202413..26dd63bca 100644 --- a/core/src/elf_extraction.rs +++ b/core/src/elf_extraction.rs @@ -11,6 +11,7 @@ use crate::{is_elf_file, RAM_ADDR, RAM_SIZE}; const RAM_START_ADDR: u64 = RAM_ADDR; const RAM_END_ADDR: u64 = RAM_ADDR + RAM_SIZE; +const MAX_ELF_SECTION_SIZE: usize = 1024 * 1024 * 1024; // 1 GiB, arbitrary limit to prevent OOM from malformed ELFs /// Raw bytes of `data` that will live at `addr` once the ROM has booted. #[derive(Debug, Clone)] @@ -81,6 +82,12 @@ pub fn collect_elf_payload_from_bytes(file_data: &[u8]) -> Result MAX_ELF_SECTION_SIZE { + return Err(format!( + "ELF section at 0x{:08x} has size {} which exceeds the maximum allowed size of {} bytes.", + sh.sh_addr, size, MAX_ELF_SECTION_SIZE + ).into()); + } // Align size to 4 bytes let aligned_size = (size + 3) & !3; vec![0u8; aligned_size] From eb35edac55d3fb9c104c57def9cd27ae688e1549 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 9 Feb 2026 12:34:45 +0100 Subject: [PATCH 470/782] Fix cargo clippy --- core/src/bin/riscv2zisk.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/bin/riscv2zisk.rs b/core/src/bin/riscv2zisk.rs index 24d1b2bbf..a4472d012 100644 --- a/core/src/bin/riscv2zisk.rs +++ b/core/src/bin/riscv2zisk.rs @@ -9,6 +9,7 @@ use zisk_core::Riscv2zisk; /// - the path of the input RISC-V ELF file /// - the path of the output Zisk rom file /// - the generation method +/// /// After parsing the arguments, the main function calls Riscv2zisk::runfile to perform the actual /// work. fn main() { From fda53bd3390a4c4c467ed0c8b5b3a159733573fd Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Tue, 10 Feb 2026 02:23:17 +0000 Subject: [PATCH 471/782] Set initial state of HintBuffer to closed in build_hint_buffer function --- ziskos/entrypoint/src/hints/hint_buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index d7f3d7df9..377be4868 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -23,7 +23,7 @@ pub fn build_hint_buffer() -> Arc { inner: Mutex::new(HintBufferInner { buf: BytesMut::with_capacity(DEFAULT_BUFFER_LEN), commit_pos: 0, - closed: false, + closed: true, paused: false, }), not_empty: Condvar::new(), From a824c3f34b33b662dbe1d7d6c27246fe1d1c9649 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 10 Feb 2026 08:25:39 +0000 Subject: [PATCH 472/782] Adding verify-constraints example --- Cargo.lock | 22 ++--- examples/Cargo.lock | 88 +++++++++---------- examples/sha-hasher/host/Cargo.toml | 6 +- .../sha-hasher/host/bin/verify-constraints.rs | 49 +++++++++++ sdk/src/prover/backend.rs | 4 +- sdk/src/prover/mod.rs | 30 +++++++ 6 files changed, 142 insertions(+), 57 deletions(-) create mode 100644 examples/sha-hasher/host/bin/verify-constraints.rs diff --git a/Cargo.lock b/Cargo.lock index e8a6c7934..c4b8558e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1189,7 +1189,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "fields", "num-bigint", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "cfg-if", "num-bigint", @@ -2965,7 +2965,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "colored", "fields", @@ -3380,7 +3380,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "bincode", "blake3", @@ -3416,7 +3416,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "bincode", "borsh", @@ -3449,7 +3449,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "fields", "itoa", @@ -3462,7 +3462,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "proc-macro2", "quote", @@ -3473,7 +3473,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "crossbeam-channel", "tracing", @@ -3482,7 +3482,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "bincode", "bytemuck", @@ -3496,7 +3496,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "bytemuck", "fields", @@ -5890,7 +5890,7 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c9502fcb7acea00dc1554b9873f3154bf468cb9a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "colored", "fields", diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 804dfe24b..f3d0a7c11 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -845,8 +845,9 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ - "fields 0.16.0", + "fields", "num-bigint", "num-traits", "rand 0.9.2", @@ -1076,7 +1077,7 @@ dependencies = [ "asm-runner", "crossbeam", "data-bus", - "fields 0.16.0", + "fields", "itertools 0.14.0", "mem-common", "mem-planner-cpp", @@ -1141,17 +1142,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -dependencies = [ - "cfg-if", - "num-bigint", - "paste", - "serde", -] - -[[package]] -name = "fields" -version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1d992d56cb6d356b3252a2cd93d34495f6640b25" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "cfg-if", "num-bigint", @@ -1728,7 +1719,7 @@ name = "mem-common" version = "0.16.0" dependencies = [ "clap", - "fields 0.16.0", + "fields", "num-bigint", "num-traits", "pil-std-lib", @@ -2056,9 +2047,10 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "colored", - "fields 0.16.0", + "fields", "num-bigint", "num-traits", "proofman-common", @@ -2142,7 +2134,7 @@ dependencies = [ "ark-secp256k1", "ark-secp256r1", "ark-std", - "fields 0.16.0", + "fields", "k256", "lazy_static", "lib-c", @@ -2181,7 +2173,7 @@ dependencies = [ "ark-ff", "ark-secp256k1", "ark-std", - "fields 0.16.0", + "fields", "k256", "lazy_static", "lib-c", @@ -2215,7 +2207,7 @@ dependencies = [ name = "precomp-big-int" version = "0.16.0" dependencies = [ - "fields 0.16.0", + "fields", "generic-array", "lib-c", "mem-common", @@ -2237,7 +2229,7 @@ dependencies = [ name = "precomp-dma" version = "0.16.0" dependencies = [ - "fields 0.16.0", + "fields", "generic-array", "lib-c", "mem-common", @@ -2261,7 +2253,7 @@ name = "precomp-keccakf" version = "0.16.0" dependencies = [ "circuit", - "fields 0.16.0", + "fields", "path-clean", "pil-std-lib", "precompiles-common", @@ -2282,7 +2274,7 @@ dependencies = [ name = "precomp-poseidon2" version = "0.16.0" dependencies = [ - "fields 0.16.0", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -2303,7 +2295,7 @@ dependencies = [ name = "precomp-sha256f" version = "0.16.0" dependencies = [ - "fields 0.16.0", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -2323,7 +2315,7 @@ dependencies = [ name = "precompiles-common" version = "0.16.0" dependencies = [ - "fields 0.16.0", + "fields", "mem-common", "sm-mem", "zisk-common", @@ -2382,6 +2374,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "bincode", "blake3", @@ -2392,7 +2385,7 @@ dependencies = [ "crossbeam-channel", "csv", "curves", - "fields 0.16.0", + "fields", "libloading", "mpi", "num-bigint", @@ -2417,6 +2410,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "bincode", "borsh", @@ -2426,7 +2420,7 @@ dependencies = [ "crossbeam-queue", "csv", "env", - "fields 0.16.0", + "fields", "indexmap", "lazy_static", "libloading", @@ -2449,8 +2443,9 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ - "fields 0.16.0", + "fields", "itoa", "proofman-common", "proofman-starks-lib-c", @@ -2461,6 +2456,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "proc-macro2", "quote", @@ -2471,6 +2467,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "crossbeam-channel", "tracing", @@ -2479,11 +2476,12 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "bincode", "bytemuck", "colored", - "fields 0.16.0", + "fields", "serde", "sysinfo 0.35.2", "tracing", @@ -2492,9 +2490,10 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "bytemuck", - "fields 0.16.0", + "fields", "proofman-util", "rayon", "tracing", @@ -2741,7 +2740,7 @@ dependencies = [ "anyhow", "blake3", "colored", - "fields 0.16.0", + "fields", "proofman-common", "sm-rom", "tracing", @@ -3130,7 +3129,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "sm-arith" version = "0.16.0" dependencies = [ - "fields 0.16.0", + "fields", "num-bigint", "pil-std-lib", "proofman", @@ -3151,7 +3150,7 @@ dependencies = [ name = "sm-binary" version = "0.16.0" dependencies = [ - "fields 0.16.0", + "fields", "num-bigint", "pil-std-lib", "proofman", @@ -3172,7 +3171,7 @@ name = "sm-frequent-ops" version = "0.16.0" dependencies = [ "clap", - "fields 0.16.0", + "fields", "num-bigint", "pil-std-lib", "proofman", @@ -3191,7 +3190,7 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "fields 0.16.0", + "fields", "mem-common", "num-bigint", "pil-std-lib", @@ -3212,7 +3211,7 @@ dependencies = [ name = "sm-mem" version = "0.16.0" dependencies = [ - "fields 0.16.0", + "fields", "mem-common", "mem-planner-cpp", "num-bigint", @@ -3235,7 +3234,7 @@ name = "sm-rom" version = "0.16.0" dependencies = [ "asm-runner", - "fields 0.16.0", + "fields", "itertools 0.14.0", "proofman", "proofman-common", @@ -4394,9 +4393,10 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "witness" version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" dependencies = [ "colored", - "fields 0.16.0", + "fields", "libloading", "proofman-common", "proofman-util", @@ -4579,7 +4579,7 @@ version = "0.16.0" dependencies = [ "anyhow", "bincode", - "fields 0.16.0", + "fields", "libc", "mpi", "proofman", @@ -4605,7 +4605,7 @@ name = "zisk-core" version = "0.16.0" dependencies = [ "elf", - "fields 0.16.0", + "fields", "indexmap", "json", "lib-c", @@ -4644,7 +4644,7 @@ dependencies = [ name = "zisk-pil" version = "0.16.0" dependencies = [ - "fields 0.16.0", + "fields", "proofman", "proofman-common", "proofman-macros", @@ -4661,7 +4661,7 @@ dependencies = [ "asm-runner", "bincode", "colored", - "fields 0.16.0", + "fields", "libloading", "proofman", "proofman-common", @@ -4695,7 +4695,7 @@ dependencies = [ "data-bus", "env_logger", "executor", - "fields 0.16.0", + "fields", "mem-common", "pil-std-lib", "precomp-arith-eq", @@ -4731,7 +4731,7 @@ version = "0.16.0" dependencies = [ "clap", "data-bus", - "fields 0.16.0", + "fields", "mem-common", "memmap2", "num-format", @@ -4760,7 +4760,7 @@ dependencies = [ "bytes", "cfg-if", "ctor", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", + "fields", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -4786,7 +4786,7 @@ dependencies = [ "bincode", "cfg-if", "elliptic-curve", - "fields 0.16.0", + "fields", "getrandom 0.2.17", "k256", "lazy_static", diff --git a/examples/sha-hasher/host/Cargo.toml b/examples/sha-hasher/host/Cargo.toml index 29a6fd3cc..aa4402af5 100644 --- a/examples/sha-hasher/host/Cargo.toml +++ b/examples/sha-hasher/host/Cargo.toml @@ -32,4 +32,8 @@ path = "bin/plonk.rs" [[bin]] name = "compressed" -path = "bin/compressed.rs" \ No newline at end of file +path = "bin/compressed.rs" + +[[bin]] +name = "verify-constraints" +path = "bin/verify-constraints.rs" \ No newline at end of file diff --git a/examples/sha-hasher/host/bin/verify-constraints.rs b/examples/sha-hasher/host/bin/verify-constraints.rs new file mode 100644 index 000000000..b77c0527a --- /dev/null +++ b/examples/sha-hasher/host/bin/verify-constraints.rs @@ -0,0 +1,49 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use zisk_common::ElfBinary; +use zisk_common::io::ZiskIO; +use zisk_common::io::ZiskStdin; +use zisk_sdk::{ProverClient, include_elf}; + +pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); + +#[derive(Serialize, Deserialize, Debug)] +struct Output { + hash: [u8; 32], + iterations: u32, + magic_number: u32, +} + +fn main() -> Result<()> { + println!("Starting ZisK Prover Client..."); + + // Create an input stream and write '1000' to it. + let n = 1000u32; + let stdin = ZiskStdin::new(); + stdin.write(&n); + println!("Input prepared: {} iterations", n); + + // Create a `ProverClient` method. + println!("Building prover client..."); + let client = ProverClient::builder().emu().verify_constraints().build().unwrap(); + + println!("Setting up program..."); + client.setup(&ELF)?; + println!("Setup completed successfully"); + + println!("Verifying constraints (no proof generation)..."); + let result = client.verify_constraints(stdin.clone())?; + + println!("\u{2713} VerifyConstraints completed successfully!"); + println!("Cycles: {}", result.get_execution_steps()); + println!("Duration: {:?}", result.get_duration()); + + println!("Reading public outputs..."); + let output: Output = result.get_public_values()?; + println!("Public outputs:"); + println!(" Hash: {:02x?}", output.hash); + println!(" Iterations: {}", output.iterations); + println!(" Magic number: 0x{:08x}", output.magic_number); + + Ok(()) +} diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 271b31b1a..449142377 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -248,7 +248,9 @@ impl ProverBackend { #[cfg(feature = "stats")] stats.store_stats(); - Ok(ZiskVerifyConstraintsResult { execution: result, duration: elapsed, stats }) + let publics = proofman.get_publics(); + + Ok(ZiskVerifyConstraintsResult::new(result, elapsed, stats, &publics)) } pub(crate) fn verify_constraints( diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 514e89b3a..e9c9891ed 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -59,6 +59,36 @@ pub struct ZiskVerifyConstraintsResult { pub execution: ZiskExecutionResult, pub duration: Duration, pub stats: ExecutorStatsHandle, + pub publics: ZiskPublics, +} + +impl ZiskVerifyConstraintsResult { + pub fn new( + execution: ZiskExecutionResult, + duration: Duration, + stats: ExecutorStatsHandle, + publics: &[u8], + ) -> Self { + Self { execution, duration, stats, publics: ZiskPublics::new(publics) } + } + + pub fn get_publics(&self) -> &ZiskPublics { + &self.publics + } + + pub fn get_public_values( + &self, + ) -> Result { + self.publics.read() + } + + pub fn get_execution_steps(&self) -> &u64 { + &self.execution.steps + } + + pub fn get_duration(&self) -> Duration { + self.duration + } } #[derive(Debug, Clone, Serialize, Deserialize)] From a23186ebfccb1cc6afc54ce967594ccd497230c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Tue, 10 Feb 2026 11:00:51 +0100 Subject: [PATCH 473/782] Verify constraints example (#770) * Verify constraints example * Cargo fmt --- .gitignore | 1 + cli/src/commands/execute.rs | 20 +++-------- cli/src/commands/prove.rs | 19 ++--------- cli/src/commands/rom_setup.rs | 11 ++---- cli/src/commands/stats.rs | 18 ++-------- cli/src/commands/verify_constraints.rs | 19 ++--------- common/src/io/stdin/file.rs | 10 ++++-- common/src/io/stdin/memory.rs | 10 ++++-- common/src/io/stdin/null.rs | 7 ++-- common/src/io/stdin/zisk_stdin.rs | 31 ++++++++++++----- common/src/types.rs | 19 ++++++++--- distributed/crates/worker/src/worker.rs | 34 +++---------------- emulator-asm/asm-runner/src/shmem_utils.rs | 2 +- examples/sha-hasher/host/Cargo.toml | 1 + examples/sha-hasher/host/bin/compressed.rs | 8 ++--- .../sha-hasher/host/bin/verify-constraints.rs | 21 +++++++----- examples/sha-hasher/host/build.rs | 7 ++++ executor/src/emu_rust.rs | 2 +- sdk/src/lib.rs | 8 +++++ 19 files changed, 113 insertions(+), 135 deletions(-) diff --git a/.gitignore b/.gitignore index 992799b46..fbc8f88e0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.pilout *.fixed /tmp +**/tmp *.log /emulator-asm/build* /emulator-asm/src/dma/*.o diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index d4155fa85..3693292cb 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -1,11 +1,10 @@ use anyhow::Result; use clap::Parser; use colored::Colorize; -use std::fs; use std::path::PathBuf; use tracing::{info, warn}; use zisk_build::ZISK_VERSION_MESSAGE; -use zisk_common::ElfBinaryOwned; +use zisk_common::ElfBinaryFromFile; use zisk_sdk::{ProverClient, ZiskExecuteResult}; use crate::ux::{print_banner, print_banner_field}; @@ -129,13 +128,8 @@ impl ZiskExecute { .print_command_info() .build()?; - let elf_bin = fs::read(&self.elf) - .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; - let elf = ElfBinaryOwned::new( - elf_bin, - self.elf.file_stem().unwrap().to_str().unwrap().to_string(), - false, - ); + let elf = ElfBinaryFromFile::new(&self.elf, false)?; + prover.setup(&elf)?; prover.execute(stdin) } @@ -157,13 +151,7 @@ impl ZiskExecute { .print_command_info() .build()?; - let elf_bin = fs::read(&self.elf) - .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; - let elf = ElfBinaryOwned::new( - elf_bin, - self.elf.file_stem().unwrap().to_str().unwrap().to_string(), - hints_stream.is_some(), - ); + let elf = ElfBinaryFromFile::new(&self.elf, hints_stream.is_some())?; prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { prover.set_hints_stream(hints_stream)?; diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index b9ac94e32..5b2047e13 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -3,12 +3,11 @@ use anyhow::Result; use colored::Colorize; use proofman_common::ParamsGPU; -use std::fs; use std::path::PathBuf; use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; -use zisk_common::ElfBinaryOwned; +use zisk_common::ElfBinaryFromFile; use zisk_sdk::{ProofOpts, ProverClient, ZiskProof, ZiskProveResult}; // Structure representing the 'prove' subcommand of cargo. @@ -226,13 +225,7 @@ impl ZiskProve { .print_command_info() .build()?; - let elf_bin = fs::read(&self.elf) - .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; - let elf = ElfBinaryOwned::new( - elf_bin, - self.elf.file_stem().unwrap().to_str().unwrap().to_string(), - false, - ); + let elf = ElfBinaryFromFile::new(&self.elf, false)?; prover.setup(&elf)?; let proof_options = ProofOpts { @@ -279,13 +272,7 @@ impl ZiskProve { .print_command_info() .build()?; - let elf_bin = fs::read(&self.elf) - .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; - let elf = ElfBinaryOwned::new( - elf_bin, - self.elf.file_stem().unwrap().to_str().unwrap().to_string(), - hints_stream.is_some(), - ); + let elf = ElfBinaryFromFile::new(&self.elf, hints_stream.is_some())?; prover.setup(&elf)?; let proof_options = ProofOpts { diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index 3a391d19f..a8e3bff46 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -10,9 +10,8 @@ use proofman_common::{ }; use rom_setup::gen_assembly; use rom_setup::rom_merkle_setup; -use std::fs; use std::sync::Arc; -use zisk_common::ElfBinaryOwned; +use zisk_common::ElfBinaryFromFile; #[derive(Parser)] #[command(version, about, long_about = None)] @@ -77,13 +76,7 @@ impl ZiskRomSetup { tracing::info!("Computing setup for ROM {}", self.elf.display()); tracing::info!("Computing merkle root"); - let elf_bytes = fs::read(&self.elf) - .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; - let elf = ElfBinaryOwned::new( - elf_bytes, - self.elf.file_stem().unwrap().to_str().unwrap().to_string(), - self.hints, - ); + let elf = ElfBinaryFromFile::new(&self.elf, self.hints)?; rom_merkle_setup::(&pctx, &elf, &self.output_dir)?; gen_assembly(&self.elf, &self.zisk_path, &self.output_dir, self.hints, self.verbose > 0)?; diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index 92eecd9ca..d374846a5 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -6,7 +6,7 @@ use std::{collections::HashMap, fs, path::PathBuf, time::Instant}; use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; -use zisk_common::ElfBinaryOwned; +use zisk_common::ElfBinaryFromFile; use zisk_common::{ExecutorStatsHandle, Stats}; use zisk_pil::*; use zisk_sdk::ProverClient; @@ -160,13 +160,7 @@ impl ZiskStats { .print_command_info() .build()?; - let elf_bin = fs::read(&self.elf) - .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; - let elf = ElfBinaryOwned::new( - elf_bin, - self.elf.file_stem().unwrap().to_str().unwrap().to_string(), - false, - ); + let elf = ElfBinaryFromFile::new(&self.elf, false)?; prover.setup(&elf)?; prover.stats( @@ -194,13 +188,7 @@ impl ZiskStats { .print_command_info() .build()?; - let elf_bin = fs::read(&self.elf) - .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; - let elf = ElfBinaryOwned::new( - elf_bin, - self.elf.file_stem().unwrap().to_str().unwrap().to_string(), - hints_stream.is_some(), - ); + let elf = ElfBinaryFromFile::new(&self.elf, hints_stream.is_some())?; prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index f0d7c171d..7593570f7 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -3,12 +3,11 @@ use anyhow::Result; use clap::Parser; use colored::Colorize; -use std::fs; use std::path::PathBuf; use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; -use zisk_common::ElfBinaryOwned; +use zisk_common::ElfBinaryFromFile; use zisk_sdk::{ProverClient, ZiskVerifyConstraintsResult}; #[derive(Parser)] @@ -142,13 +141,7 @@ impl ZiskVerifyConstraints { .print_command_info() .build()?; - let elf_bin = fs::read(&self.elf) - .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; - let elf = ElfBinaryOwned::new( - elf_bin, - self.elf.file_stem().unwrap().to_str().unwrap().to_string(), - false, - ); + let elf = ElfBinaryFromFile::new(&self.elf, false)?; prover.setup(&elf)?; prover.verify_constraints_debug(stdin, self.debug.clone()) @@ -171,13 +164,7 @@ impl ZiskVerifyConstraints { .print_command_info() .build()?; - let elf_bin = fs::read(&self.elf) - .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", self.elf.display(), e))?; - let elf = ElfBinaryOwned::new( - elf_bin, - self.elf.file_stem().unwrap().to_str().unwrap().to_string(), - hints_stream.is_some(), - ); + let elf = ElfBinaryFromFile::new(&self.elf, hints_stream.is_some())?; prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { diff --git a/common/src/io/stdin/file.rs b/common/src/io/stdin/file.rs index f474955b3..8ae39c0d0 100644 --- a/common/src/io/stdin/file.rs +++ b/common/src/io/stdin/file.rs @@ -2,7 +2,7 @@ //! This module provides functionality to read input data from a file. use anyhow::Result; -use serde::Serialize; +use serde::{de::DeserializeOwned, Serialize}; use std::fs::{self, File}; use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; @@ -36,7 +36,7 @@ impl ZiskFileStdin { } impl ZiskIO for ZiskFileStdin { - fn read(&self) -> Vec { + fn read_bytes(&self) -> Vec { fs::read(&self.path).expect("Could not read inputs file") } @@ -50,6 +50,12 @@ impl ZiskIO for ZiskFileStdin { reader.read_exact(buffer).expect("Failed to read into buffer"); } + fn read(&self) -> Result { + let mut reader = self.reader.lock().unwrap(); + bincode::deserialize_from(&mut *reader) + .map_err(|e| anyhow::anyhow!("Failed to deserialize from file: {}", e)) + } + fn write(&self, _data: &T) { // This is a read-only stdin implementation // Writing is not supported for file-based stdin diff --git a/common/src/io/stdin/memory.rs b/common/src/io/stdin/memory.rs index 7ea4e868e..89558b3be 100644 --- a/common/src/io/stdin/memory.rs +++ b/common/src/io/stdin/memory.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use serde::Serialize; +use serde::{de::DeserializeOwned, Serialize}; use std::io::{Cursor, Read}; use std::path::Path; use std::sync::Mutex; @@ -31,7 +31,7 @@ impl ZiskMemoryStdin { } impl ZiskIO for ZiskMemoryStdin { - fn read(&self) -> Vec { + fn read_bytes(&self) -> Vec { // Return all the data self.data.lock().unwrap().clone() } @@ -46,6 +46,12 @@ impl ZiskIO for ZiskMemoryStdin { cursor.read_exact(buffer).expect("Failed to read into buffer from memory"); } + fn read(&self) -> Result { + let mut cursor = self.cursor.lock().unwrap(); + bincode::deserialize_from(&mut *cursor) + .map_err(|e| anyhow::anyhow!("Failed to deserialize from memory: {}", e)) + } + fn write(&self, data: &T) { let mut tmp = Vec::new(); bincode::serialize_into(&mut tmp, data).expect("Failed to serialize data into memory"); diff --git a/common/src/io/stdin/null.rs b/common/src/io/stdin/null.rs index d8c80218b..12285b0dd 100644 --- a/common/src/io/stdin/null.rs +++ b/common/src/io/stdin/null.rs @@ -2,17 +2,20 @@ use tracing::warn; use crate::io::ZiskIO; use anyhow::Result; -use serde::Serialize; +use serde::{de::DeserializeOwned, Serialize}; use std::path::Path; pub struct ZiskNullStdin; impl ZiskIO for ZiskNullStdin { - fn read(&self) -> Vec { + fn read_bytes(&self) -> Vec { Vec::new() } fn read_slice(&self, _slice: &mut [u8]) {} fn read_into(&self, _buffer: &mut [u8]) {} + fn read(&self) -> Result { + Err(anyhow::anyhow!("NullStdin does not support reading")) + } fn write(&self, _data: &T) { warn!("NullStdin does not support writing"); } diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index 2cd7442d0..531c5b309 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -1,12 +1,12 @@ use crate::io::{ZiskFileStdin, ZiskMemoryStdin, ZiskNullStdin}; use anyhow::Result; -use serde::Serialize; +use serde::{de::DeserializeOwned, Serialize}; use std::path::Path; use std::sync::Arc; pub trait ZiskIO: Send + Sync { /// Read a value from the buffer. - fn read(&self) -> Vec; + fn read_bytes(&self) -> Vec; /// Read a slice of bytes from the buffer. fn read_slice(&self, slice: &mut [u8]); @@ -14,6 +14,9 @@ pub trait ZiskIO: Send + Sync { /// Read bytes into the provided buffer. fn read_into(&self, buffer: &mut [u8]); + /// Read and deserialize a value from the buffer. + fn read(&self) -> Result; + /// Write a serialized value to the buffer. fn write(&self, data: &T); @@ -30,11 +33,11 @@ pub enum ZiskIOVariant { } impl ZiskIO for ZiskIOVariant { - fn read(&self) -> Vec { + fn read_bytes(&self) -> Vec { match self { - ZiskIOVariant::File(file_stdin) => file_stdin.read(), - ZiskIOVariant::Null(null_stdin) => null_stdin.read(), - ZiskIOVariant::Memory(memory_stdin) => memory_stdin.read(), + ZiskIOVariant::File(file_stdin) => file_stdin.read_bytes(), + ZiskIOVariant::Null(null_stdin) => null_stdin.read_bytes(), + ZiskIOVariant::Memory(memory_stdin) => memory_stdin.read_bytes(), } } @@ -54,6 +57,14 @@ impl ZiskIO for ZiskIOVariant { } } + fn read(&self) -> Result { + match self { + ZiskIOVariant::File(file_stdin) => file_stdin.read(), + ZiskIOVariant::Null(null_stdin) => null_stdin.read(), + ZiskIOVariant::Memory(memory_stdin) => memory_stdin.read(), + } + } + fn write(&self, data: &T) { match self { ZiskIOVariant::File(file_stdin) => file_stdin.write(data), @@ -85,8 +96,8 @@ pub struct ZiskStdin { } impl ZiskIO for ZiskStdin { - fn read(&self) -> Vec { - self.io.read() + fn read_bytes(&self) -> Vec { + self.io.read_bytes() } fn read_slice(&self, slice: &mut [u8]) { @@ -97,6 +108,10 @@ impl ZiskIO for ZiskStdin { self.io.read_into(buffer) } + fn read(&self) -> Result { + self.io.read() + } + fn write(&self, data: &T) { self.io.write(data) } diff --git a/common/src/types.rs b/common/src/types.rs index 5ef014fc0..9d0ec2945 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -1,4 +1,7 @@ +use anyhow::Result; use std::fmt; +use std::fs; +use std::path::Path; use std::time::Instant; /// Type representing a chunk identifier. @@ -98,19 +101,25 @@ pub trait ElfBinaryLike { fn with_hints(&self) -> bool; } -pub struct ElfBinaryOwned { +pub struct ElfBinaryFromFile { pub elf: Vec, pub name: String, pub with_hints: bool, } -impl ElfBinaryOwned { - pub const fn new(elf: Vec, name: String, with_hints: bool) -> Self { - Self { elf, name, with_hints } +impl ElfBinaryFromFile { + pub fn new(elf: &Path, with_hints: bool) -> Result { + let elf_bin = fs::read(elf) + .map_err(|e| anyhow::anyhow!("Error reading ELF file {}: {}", elf.display(), e))?; + Ok(Self { + elf: elf_bin, + name: elf.file_stem().unwrap().to_str().unwrap().to_string(), + with_hints, + }) } } -impl ElfBinaryLike for ElfBinaryOwned { +impl ElfBinaryLike for ElfBinaryFromFile { fn elf(&self) -> &[u8] { &self.elf } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 4392ab860..f8c2b0015 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -12,7 +12,7 @@ use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::reinterpret_vec; -use zisk_common::ElfBinaryOwned; +use zisk_common::ElfBinaryFromFile; use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; use zisk_distributed_common::{ComputeCapacity, JobId, WorkerId}; use zisk_distributed_common::{HintsSourceDto, StreamDataDto, StreamMessageKind, StreamPayloadDto}; @@ -157,19 +157,9 @@ impl ProverConfig { prover_service_config.elf.display() ) })?; - let elf_bin = fs::read(&prover_service_config.elf).map_err(|e| { - anyhow::anyhow!( - "Error reading ELF file {}: {}", - prover_service_config.elf.display(), - e - ) - })?; + let elf = + ElfBinaryFromFile::new(&prover_service_config.elf, prover_service_config.hints)?; - let elf = ElfBinaryOwned::new( - elf_bin, - prover_service_config.elf.file_stem().unwrap().to_str().unwrap().to_string(), - prover_service_config.hints, - ); let hash = get_elf_data_hash(&elf) .map_err(|e| anyhow::anyhow!("Error computing ELF hash: {}", e))?; let stem = if prover_service_config.hints { @@ -278,14 +268,7 @@ impl Worker { .build()?, ); - let elf_bin = fs::read(&prover_config.elf).map_err(|e| { - anyhow::anyhow!("Error reading ELF file {}: {}", prover_config.elf.display(), e) - })?; - let elf = ElfBinaryOwned::new( - elf_bin, - prover_config.elf.file_stem().unwrap().to_str().unwrap().to_string(), - false, - ); + let elf = ElfBinaryFromFile::new(&prover_config.elf, prover_config.hints)?; prover.setup(&elf)?; Ok(Worker:: { @@ -321,14 +304,7 @@ impl Worker { .build()?, ); - let elf_bin = fs::read(&prover_config.elf).map_err(|e| { - anyhow::anyhow!("Error reading ELF file {}: {}", prover_config.elf.display(), e) - })?; - let elf = ElfBinaryOwned::new( - elf_bin, - prover_config.elf.file_stem().unwrap().to_str().unwrap().to_string(), - prover_config.hints, - ); + let elf = ElfBinaryFromFile::new(&prover_config.elf, prover_config.hints)?; prover.setup(&elf)?; Ok(Worker:: { diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 71e9ec509..e02c00853 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -332,7 +332,7 @@ pub unsafe fn unmap(ptr: *mut c_void, size: usize) { pub fn write_input(stdin: &mut ZiskStdin, shmem_input_writer: &SharedMemoryWriter) { const HEADER_SIZE: usize = size_of::(); - let inputs = stdin.read(); + let inputs = stdin.read_bytes(); let shmem_input_size = (HEADER_SIZE + inputs.len() + 7) & !7; diff --git a/examples/sha-hasher/host/Cargo.toml b/examples/sha-hasher/host/Cargo.toml index aa4402af5..e09c575cd 100644 --- a/examples/sha-hasher/host/Cargo.toml +++ b/examples/sha-hasher/host/Cargo.toml @@ -12,6 +12,7 @@ sha2 = "0.10.8" [build-dependencies] zisk-build = { workspace = true } +zisk-common = { workspace = true } [features] default = [] diff --git a/examples/sha-hasher/host/bin/compressed.rs b/examples/sha-hasher/host/bin/compressed.rs index 0e8261e85..398698fad 100644 --- a/examples/sha-hasher/host/bin/compressed.rs +++ b/examples/sha-hasher/host/bin/compressed.rs @@ -30,16 +30,16 @@ fn main() -> Result<()> { println!("Compressing proof (this may take a while)..."); let compressed_result = - client.compress(&vadcop_result.get_proof(), &vadcop_result.get_publics(), &vkey)?; + client.compress(vadcop_result.get_proof(), vadcop_result.get_publics(), &vkey)?; // Alternatively, you can also call `compressed()` on the `ProverClient.prove` method to generate a compressed proof directly. // let result = client.prove(stdin).with_proof_options(proof_opts).compressed().run()?; println!("Verifying compressed proof..."); client.verify( - &compressed_result.get_proof(), - &compressed_result.get_publics(), - &compressed_result.get_program_vk(), + compressed_result.get_proof(), + compressed_result.get_publics(), + compressed_result.get_program_vk(), )?; println!("Compressed proof verification successful!"); diff --git a/examples/sha-hasher/host/bin/verify-constraints.rs b/examples/sha-hasher/host/bin/verify-constraints.rs index b77c0527a..df79efd5c 100644 --- a/examples/sha-hasher/host/bin/verify-constraints.rs +++ b/examples/sha-hasher/host/bin/verify-constraints.rs @@ -1,11 +1,11 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -use zisk_common::ElfBinary; +use std::path::PathBuf; +use zisk_common::ElfBinaryFromFile; use zisk_common::io::ZiskIO; use zisk_common::io::ZiskStdin; -use zisk_sdk::{ProverClient, include_elf}; - -pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); +use zisk_sdk::ProverClient; +use zisk_sdk::elf_path; #[derive(Serialize, Deserialize, Debug)] struct Output { @@ -17,10 +17,13 @@ struct Output { fn main() -> Result<()> { println!("Starting ZisK Prover Client..."); - // Create an input stream and write '1000' to it. - let n = 1000u32; - let stdin = ZiskStdin::new(); - stdin.write(&n); + let elf = ElfBinaryFromFile::new(&PathBuf::from(elf_path!("sha-hasher-guest")), false)?; + + let current_dir = std::env::current_dir()?; + let stdin = + ZiskStdin::from_file(current_dir.join("sha-hasher/host/tmp/verify_constraints_input.bin"))?; + + let n: u32 = stdin.read()?; println!("Input prepared: {} iterations", n); // Create a `ProverClient` method. @@ -28,7 +31,7 @@ fn main() -> Result<()> { let client = ProverClient::builder().emu().verify_constraints().build().unwrap(); println!("Setting up program..."); - client.setup(&ELF)?; + client.setup(&elf)?; println!("Setup completed successfully"); println!("Verifying constraints (no proof generation)..."); diff --git a/examples/sha-hasher/host/build.rs b/examples/sha-hasher/host/build.rs index c77ea0d8e..336b4a51f 100644 --- a/examples/sha-hasher/host/build.rs +++ b/examples/sha-hasher/host/build.rs @@ -1,3 +1,10 @@ +use std::path::PathBuf; +use zisk_common::io::{ZiskIO, ZiskStdin}; + fn main() { zisk_build::build_program("../guest"); + let n = 1000u32; + let stdin_save = ZiskStdin::new(); + stdin_save.write(&n); + stdin_save.save(&PathBuf::from("tmp/verify_constraints_input.bin")).unwrap(); } diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index ffbe7b75c..eb75c53c1 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -80,7 +80,7 @@ impl EmulatorRust { fn run_emulator(&self, num_threads: usize, stdin: &mut ZiskStdin) -> Vec { // Call emulate with these options - let input_data = stdin.read(); + let input_data = stdin.read_bytes(); // Settings for the emulator let emu_options = EmuOptions { diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 486b839ac..651187f53 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -29,3 +29,11 @@ macro_rules! include_elf { } }}; } + +/// Macro to get the ELF file path at compile time +#[macro_export] +macro_rules! elf_path { + ($arg:literal) => {{ + env!(concat!("ZISK_ELF_", $arg)) + }}; +} From 36e9b23c2961d765b8494d833f96a8e527f71901 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:07:39 +0000 Subject: [PATCH 474/782] added precompile hints documentation --- book/SUMMARY.md | 1 + book/getting_started/precompile_hints.md | 251 +++++++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 book/getting_started/precompile_hints.md diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 1badc60bf..b38c5d568 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -8,6 +8,7 @@ - [Quickstart](./getting_started/quickstart.md) - [Writing Programs](./getting_started/writing_programs.md) - [Precompiles](./getting_started/precompiles.md) +- [Precompile Hints](./getting_started/precompile_hints.md) # Developer Guide - [Ziskof](./developer/ziskof.md) diff --git a/book/getting_started/precompile_hints.md b/book/getting_started/precompile_hints.md new file mode 100644 index 000000000..718cf6e0d --- /dev/null +++ b/book/getting_started/precompile_hints.md @@ -0,0 +1,251 @@ +# Precompile Hints System + +Cryptographic precompiles (SHA-256, Keccak-256, elliptic curve operations, pairings, etc.) are computationally expensive inside a zkVM. The precompile hints system accelerates proof generation by offloading these expensive computations outside the zkVM execution, then feeding the results back as verifiable data through a high-performance, parallel pipeline. Hints are preprocessed results that allow cryptographic operations to be handled externally while remaining fully verifiable inside the VM. The system is designed around three core principles: + +1. **Pre-computing results outside the VM**: The guest program emits hint requests describing the operation and its inputs. +2. **Streaming results back**: A dedicated pipeline processes these requests in parallel, maintaining order, and feeds results to the prover via shared memory. +3. **Verifying inside the VM**: The zkVM circuits verify that the precomputed results are correct, avoiding the cost of computing them inside the zkVM. + +```mermaid +flowchart LR + A["Guest program
Emits hints request"] --> B["ZiskStream"] + B --> C["HintsProcessor
Parallel engine"] + C --> D["StreamSink
ASM emulator/file output"] +``` + +--- + +## Table of Contents + +1. [Hint Format and Protocol](#1-hint-format-and-protocol) +2. [Hints in CLI Execution](#2-hints-in-cli-execution) +3. [Hints in Distributed Execution](#3-hints-in-distributed-execution) +4. [Custom Hint Handlers](#4-custom-hint-handlers) + +--- + +## 1. Hint Format and Protocol + +### 1.1. Hint Request Format +Hints are transmitted as a stream of `u64` values. Each hint request consists of a **header** (1 `u64`) followed by **data** (N `u64` values). + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Header (u64) │ +├·····························································┤ +│ Hint Code (32 bits) Length (32 bits). │ +├─────────────────────────────────────────────────────────────┤ +│ Data[0] (u64) │ +├─────────────────────────────────────────────────────────────┤ +│ Data[1] (u64) │ +├─────────────────────────────────────────────────────────────┤ +│ ... │ +├─────────────────────────────────────────────────────────────┤ +│ Data[length-1] (u64) │ +└─────────────────────────────────────────────────────────────┘ +``` +- **Hint Code** (upper 32 bits): Control code or Data Hint Type +- **Length** (lower 32 bits): Payload data size in **bytes**. The last `u64` may contain padding bytes. + +### 1.2. Control Hint Types: +The following control codes are defined: +- `0x00` (START): Start a new hint stream. Resets processor state and sequence counters. Must be the first hint in the first batch. +- `0x01` (END): End the current hint stream. The processor will wait for all pending hints to be processed before returning. Must be the last hint in its batch; only a `CTRL_START` may follow in a subsequent batch. +- `0x02` (CANCEL): **[Reserved for future use]** Cancel current stream and stop processing further hints. +- `0x03` (ERROR): **[Reserved for future use]** Indicate an error has occurred; stop processing further hints. + +Control codes are for control only and do not have any associated data (Length should be zero). + +### 1.3. Data Hint Types +For data hints, the hint code (32 bits) is structured as follows: +- **Bit 31 (MSB)**: Pass-through flag. When set, the data bypasses computation and is forwarded directly to the sink. +- **Bits 0-30**: The hint type identifier (control, built-in, or custom code). + (e.g., `HINT_SHA256`, `HINT_BN254_G1_ADD`, `HINT_SECP256K1_RECOVER`, etc.) + +**Example**: A SHA-256 hint (`0x0100`) with a 32-byte input: +``` +Header: 0x00000100_00000020 +Data[0]: first_8_input_bytes_as_u64 +Data[1]: next_8_input_bytes_as_u64 +Data[2]: next_8_input_bytes_as_u64 +Data[3]: last_8_input_bytes_as_u64 +``` + +The same hint with the **pass-through flag** set (bit 31), forwarding pre-computed data directly to the sink without invoking the SHA-256 handler: +``` +Header: 0x80000100_00000020 +``` + +#### 1.3.1 Stream Batching +The hints protocol supports chunking for individual hints that exceed the transport’s message size limit (currently 128 KB). Each message in the stream contains either a single complete hint or one chunk of a larger hint — hints are never combined in the same message. + +When a hint exceeds the size limit, it must be split into multiple sequential chunks, each sent as a separate message. Each chunk includes a header specifying the total length of the complete hint, allowing the receiver to reassemble all chunks before processing. For example, a hint with a 300 KB payload would be split into three messages: +```Message 1: Header (code + total length), Data[0..N] (first 128 KB chunk) +Message 2: Header (code + total length), Data[0..N] (second 128 KB chunk) +Message 3: Header (code + total length), Data[0..M] (final 44 KB chunk) +``` +The receiver buffers incoming chunks and reassembles them based on the total length specified in the header before invoking the hint handler. This allows the system to handle arbitrarily large hints while respecting transport limitations. + +#### 1.3.2 Pass-Through Hints +When bit 31 of the hint code is set (e.g., `0x8000_0000 | actual_code`), the hint is marked as **pass-through**: + +- The data payload is forwarded directly to the sink without invoking any handler. +- No worker thread is spawned; the data is queued immediately in the reorder buffer. +- This is useful for pre-computed results that don't need processing. + +### 1.4. Hint Code Types +| Category | Code Range | Description | +|--------------|-------------------|-------------------------------------| +| **Control** | `0x0000`-`0x000F` | Stream lifecycle management | +| **Built-in** | `0x0100`-`0x0700` | Cryptographic precompile operations | +| **Custom** | User-defined | Application-specific handlers | + +> **Note:** Custom hint codes can technically use any value not occupied by control or built-in codes. By convention, codes `0xA000`-`0xFFFF` are recommended for custom use to avoid future conflicts as new built-in types are added. The processor does not enforce a range restriction — any unrecognized code is treated as custom. + +#### 1.4.1. Control Codes +Control codes manage the stream lifecycle and do not carry computational data: + +| Code | Name | Description | +|------|------|-------------| +| `0x0000` | `CTRL_START` | Resets processor state. Must be the first hint in the first batch. | +| `0x0001` | `CTRL_END` | Signals end of stream. Blocks until all pending hints complete. Must be the last hint. | +| `0x0002` | `CTRL_CANCEL` | **[Reserved for future use]** Cancels the current stream. Sets error flag and stops processing. | +| `0x0003` | `CTRL_ERROR` | **[Reserved for future use]** External error signal. Sets error flag and stops processing. | + +#### 1.4.2. Built-in Hint Types +| Code | Name | Description | +|------|------|-------------| +| `0x0100` | `Sha256` | SHA-256 hash computation | +| `0x0200` | `Bn254G1Add` | BN254 G1 point addition | +| `0x0201` | `Bn254G1Mul` | BN254 G1 scalar multiplication | +| `0x0205` | `Bn254PairingCheck` | BN254 pairing check | +| `0x0300` | `Secp256k1EcdsaAddressRecover` | Secp256k1 ECDSA address recovery | +| `0x0301` | `Secp256k1EcdsaVerifyAddressRecover` | Secp256k1 ECDSA verify + address recovery | +| `0x0380` | `Secp256r1EcdsaVerify` | Secp256r1 (P-256) ECDSA verification | +| `0x0400` | `Bls12_381G1Add` | BLS12-381 G1 point addition | +| `0x0401` | `Bls12_381G1Msm` | BLS12-381 G1 multi-scalar multiplication | +| `0x0405` | `Bls12_381G2Add` | BLS12-381 G2 point addition | +| `0x0406` | `Bls12_381G2Msm` | BLS12-381 G2 multi-scalar multiplication | +| `0x040A` | `Bls12_381PairingCheck` | BLS12-381 pairing check | +| `0x0410` | `Bls12_381FpToG1` | BLS12-381 map field element to G1 | +| `0x0411` | `Bls12_381Fp2ToG2` | BLS12-381 map field element to G2 | +| `0x0500` | `ModExp` | Modular exponentiation | +| `0x0600` | `VerifyKzgProof` | KZG polynomial commitment proof verification | +| `0x0700` | `Keccak256` | Keccak-256 hash computation | + +#### 1.4.3. Custom Hint Types +Custom hint types allow users to define their own hint handlers for application-specific logic. Users can register custom handlers via the `HintsProcessor` builder API, providing a mapping from hint code to a processing function (see [Custom Hint Handlers](#4-custom-hint-handlers)). By convention, codes in the range `0xA000`-`0xFFFF` are recommended for custom use to avoid conflicts with current and future built-in types. If a data hint is received with an unregistered code, the processor returns an error and stops processing immediately. + +### 1.5. Stream Protocol + +A valid hint stream follows this protocol: + +``` +CTRL_START ← Reset state, begin stream + [Hint_1] [Hint_2] ... [Hint_N] ← Data hints (any order of types) +CTRL_END ← Wait for completion, end stream +``` + +## 2. Hints in CLI Execution + +There are four CLI commands (`execute`, `prove`, `verify-constraints`, `stats`) that support hints stream system by providing a URI via the `--hints` option. The URI determines the input stream source for hints, which can be a file, Unix socket, QUIC stream, or other custom transport. +The supported schemes are: +``` +--hints file://path → File stream reader +--hints unix://path → Unix socket stream reader +--hints quic://host:port → Quic stream reader +--hints (plain path) → File stream reader +``` + +> **Note:** Only ASM mode supports hints. The emulator mode does not use the hints pipeline. + +## 3. Hints in Distributed Execution + +In the distributed proving system, hints are received by the `coordinator` and broadcasted to all **workers** via gRPC. The coordinator runs a relay that validates incoming hint messages, assigns sequence numbers for ordering, and dispatches them to `workers` asynchronously. `Workers` buffer incoming messages and reorder them by sequence number before processing. The processed hints are then submitted to the sink in the correct order. +There is another mode where workers can load hints from a local path/URI instead of streaming from the coordinator, which is useful for debugging. + +### 3.1. Architecture + +```mermaid +flowchart TD + A["Guest program
Emits hints request"] --> B + + subgraph H["Coordinator"] + B["ZiskStream"] + B --> C["Hints Relay
Validates
Broadcast to all workers (async)
"] + end + + C --> E["Worker 1
Stream incoming hints + Reorder"] + C --> F["Worker 2
Stream incoming hints + Reorder"] + C --> G["Worker N
Stream incoming hints + Reorder"] + + E --> E1["HintsProcessor
Parallel engine"] + E1 --> E2["StreamSink
ASM emulator/file output"] + + F --> F1["HintsProcessor
Parallel engine"] + F1 --> F2["StreamSink
ASM emulator/file output"] + + G --> G1["HintsProcessor
Parallel engine"] + G1 --> G2["StreamSink
ASM emulator/file output"] + + style H fill:transparent,stroke-dasharray: 5 5 +``` + +When the `coordinator` receives a hint request from the guest program, it parses the incoming `u64` stream, validates control codes, assigns sequence numbers for ordering, and broadcasts the data to all workers. + +Three message types are sent over gRPC to workers: + +| StreamMessageKind | When | Payload | +|---|---|---| +| `Start` | On `CTRL_START` | None | +| `Data` | For each data batch | Sequence number + raw bytes | +| `End` | On `CTRL_END` | None | + +Each worker receives the stream of hints, buffers them if they arrive out of order, and sends them to the `HintsProcessor` for parallel processing. The `HintsProcessor` ensures that results are submitted to the sink in the original order. + +### 3.2. Hints Mode Configuration + +When starting a worker, if the `--hints` option is provided, the worker prepares to receive hints from the coordinator. +When launching a proof generation job where hints will be provided, the workers must be started to receive and process hints. +A hints stream system can be configured in two ways: +* **Streaming mode**: Workers receive hints from the coordinator via gRPC. This is the default and recommended mode for production, as it allows real-time processing of hints as they are generated. +* **Path mode**: Workers load hints from a local path/URI. This is useful for debugging or when hints are pre-generated and stored in a file. In this mode, the coordinator does not send hints to workers; instead, each worker reads the hints directly from the specified path. + +#### 3.2.1 Coordinator Hints Streaming Mode +To start the coordinator in streaming mode, provide the `--hints-uri` option with a URI that the `coordinator` will connect to, and set `--stream-hints` to enable broadcasting to workers. The URI determines the input stream source for hints. +The supported schemes are: +``` +--hints-uri file://path → File stream reader +--hints-uri unix://path → Unix socket stream reader +--hints-uri quic://host:port → Quic stream reader +--hints-uri (plain path) → File stream reader +``` + +Example to launch a prove command in streaming mode: +``` +zisk-coordinator prove --hints-uri unix:///tmp/hints.sock --stream-hints ... +``` + +#### 3.2.2 Worker Hints non-Streaming Mode +To start a worker in non-streaming mode, provide the `--hints-uri` option with a URI that points to the local workers path where hints are stored, without the `--stream-hints` option. In this mode the worker(s) will load the precompile hints from the specified URI instead of receiving them from the coordinator. This mode is useful for debugging or when hints are pre-generated and stored in a file. + +## 4. Custom Hint Handlers + +Register custom handlers via the builder pattern: + +```rust +let processor = HintsProcessor::builder(my_sink) + .custom_hint(0xA000, |data: &[u64]| -> Result> { + // Custom processing logic + Ok(vec![data[0] * 2]) + }) + .custom_hint(0xA001, |data| { + // Another custom handler + Ok(transform(data)) + }) + .build()?; +``` + +**Requirements:** +- Handler function must be `Fn(&[u64]) -> Result> + Send + Sync + 'static`. +- Custom hint codes should not conflict with built-in codes (`0x0000`-`0x0700`). By convention, use codes in the range `0xA000`-`0xFFFF`. From 6b29605ed0d6dd1f0d5c6622ea496c11ec0a7e69 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:47:57 +0000 Subject: [PATCH 475/782] fix: lib-c/build.rs --- lib-c/build.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lib-c/build.rs b/lib-c/build.rs index e9d74b601..cfbc6f5df 100644 --- a/lib-c/build.rs +++ b/lib-c/build.rs @@ -45,8 +45,46 @@ fn main() { println!("cargo:rustc-link-search=native={}", abs_lib_path.display()); println!("cargo:rustc-link-lib=static={library_name}"); + // Track C source files for recompilation + track_sources(&c_path); + // Link required libraries for lib in &["pthread", "gmp", "stdc++", "gmpxx", "c"] { println!("cargo:rustc-link-lib={lib}"); } } + +/// Tell Cargo to track C source files for changes +fn track_sources(dir: &Path) { + // Track all C/C++ source files and headers recursively + if let Ok(entries) = std::fs::read_dir(dir.join("src")) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + track_sources_recursive(&path); + } else if let Some(ext) = path.extension() { + if ext == "c" || ext == "cpp" || ext == "h" || ext == "hpp" || ext == "asm" { + println!("cargo:rerun-if-changed={}", path.display()); + } + } + } + } + + // Also track the Makefile itself + println!("cargo:rerun-if-changed={}", dir.join("Makefile").display()); +} + +fn track_sources_recursive(dir: &Path) { + if let Ok(entries) = std::fs::read_dir(dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + track_sources_recursive(&path); + } else if let Some(ext) = path.extension() { + if ext == "c" || ext == "cpp" || ext == "h" || ext == "hpp" || ext == "asm" { + println!("cargo:rerun-if-changed={}", path.display()); + } + } + } + } +} From d7c131727204728a3cf9b86b46d0469dc7205af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Tue, 10 Feb 2026 13:02:40 +0100 Subject: [PATCH 476/782] Update vergen (#771) --- Cargo.lock | 120 ++++++++++++++++++++++++--- Cargo.toml | 3 + cli/Cargo.toml | 6 +- cli/build.rs | 12 ++- distributed/crates/worker/Cargo.toml | 6 +- emulator/Cargo.toml | 6 +- emulator/build.rs | 11 ++- ziskbuild/Cargo.toml | 6 +- ziskbuild/build.rs | 12 ++- 9 files changed, 148 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4b8558e7..7d7df753b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -674,7 +674,7 @@ dependencies = [ "target-lexicon", "tokio", "tracing", - "vergen", + "vergen-git2", "yansi", "zisk-build", "zisk-common", @@ -1203,6 +1203,41 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.114", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -1273,6 +1308,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.114", +] + [[package]] name = "digest" version = "0.10.7" @@ -1780,9 +1846,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "git2" -version = "0.19.0" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ "bitflags 2.10.0", "libc", @@ -2131,6 +2197,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -2414,9 +2486,9 @@ dependencies = [ [[package]] name = "libgit2-sys" -version = "0.17.0+1.8.1" +version = "0.18.3+1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" dependencies = [ "cc", "libc", @@ -5301,15 +5373,41 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "8.3.2" +version = "9.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" +checksum = "b849a1f6d8639e8de261e81ee0fc881e3e3620db1af9f2e0da015d4382ceaf75" dependencies = [ "anyhow", - "cfg-if", + "derive_builder", + "rustversion", + "time", + "vergen-lib", +] + +[[package]] +name = "vergen-git2" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51ab55ddf1188c8d679f349775362b0fa9e90bd7a4ac69838b2a087623f0d57" +dependencies = [ + "anyhow", + "derive_builder", "git2", "rustversion", "time", + "vergen", + "vergen-lib", +] + +[[package]] +name = "vergen-lib" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b34a29ba7e9c59e62f229ae1932fb1b8fb8a6fdcc99215a641913f5f5a59a569" +dependencies = [ + "anyhow", + "derive_builder", + "rustversion", ] [[package]] @@ -6077,7 +6175,7 @@ dependencies = [ "clap", "rom-setup", "tracing", - "vergen", + "vergen-git2", "zisk-sdk", ] @@ -6225,7 +6323,7 @@ dependencies = [ "tonic", "tracing", "uuid", - "vergen", + "vergen-git2", "witness", "zisk-common", "zisk-distributed-common", @@ -6350,7 +6448,7 @@ dependencies = [ "symbolic-common", "symbolic-demangle", "sysinfo 0.37.2", - "vergen", + "vergen-git2", "zisk-common", "zisk-core", "zisk-pil", diff --git a/Cargo.toml b/Cargo.toml index 970828b10..4d9ade02b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -186,3 +186,6 @@ reqwest = { version = "0.12", features = [ "rustls-tls", ], default-features = false } zstd = "0.13" +vergen-git2 = { version = "9", default-features = false, features = [ + "build", +] } \ No newline at end of file diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 435756f1e..06a17b975 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -12,11 +12,7 @@ name = "cargo-zisk" path = "src/bin/cargo-zisk.rs" [build-dependencies] -vergen = { version = "8", default-features = false, features = [ - "build", - "git", - "git2", -] } +vergen-git2.workspace = true [dependencies] zisk-common = { workspace = true } diff --git a/cli/build.rs b/cli/build.rs index 0a689d13e..a9af6f870 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -1,5 +1,15 @@ fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); + let mut builder = vergen_git2::Emitter::default(); + builder + .add_instructions( + &vergen_git2::BuildBuilder::default().build_timestamp(true).build().unwrap(), + ) + .unwrap(); + builder + .add_instructions(&vergen_git2::Git2Builder::default().sha(true).build().unwrap()) + .unwrap(); + builder.emit().unwrap(); + let disable_distributed = std::env::vars().any(|(k, _)| k == "CARGO_FEATURE_DISABLE_DISTRIBUTED"); let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); diff --git a/distributed/crates/worker/Cargo.toml b/distributed/crates/worker/Cargo.toml index a346a19dc..62888ae12 100644 --- a/distributed/crates/worker/Cargo.toml +++ b/distributed/crates/worker/Cargo.toml @@ -11,11 +11,7 @@ name = "zisk-worker" path = "src/cli/main.rs" [build-dependencies] -vergen = { version = "8", default-features = false, features = [ - "build", - "git", - "git2", -] } +vergen-git2.workspace = true [dependencies] zisk-distributed-common = { workspace = true } diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml index 90c8015c7..3ed8ad221 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -38,11 +38,7 @@ symbolic-demangle = { version = "12.16", features = ["rust", "cpp"] } symbolic-common = "12.16" [build-dependencies] -vergen = { version = "8", default-features = false, features = [ - "build", - "git", - "git2", -] } +vergen-git2 = { workspace = true } [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } diff --git a/emulator/build.rs b/emulator/build.rs index c2f550fb6..b9a0832eb 100644 --- a/emulator/build.rs +++ b/emulator/build.rs @@ -1,3 +1,12 @@ fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); + let mut builder = vergen_git2::Emitter::default(); + builder + .add_instructions( + &vergen_git2::BuildBuilder::default().build_timestamp(true).build().unwrap(), + ) + .unwrap(); + builder + .add_instructions(&vergen_git2::Git2Builder::default().sha(true).build().unwrap()) + .unwrap(); + builder.emit().unwrap(); } diff --git a/ziskbuild/Cargo.toml b/ziskbuild/Cargo.toml index ba573c511..093826c02 100644 --- a/ziskbuild/Cargo.toml +++ b/ziskbuild/Cargo.toml @@ -16,8 +16,4 @@ rom-setup = { workspace = true } zisk-sdk = { workspace = true } [build-dependencies] -vergen = { version = "8", default-features = false, features = [ - "build", - "git", - "git2", -] } +vergen-git2.workspace = true \ No newline at end of file diff --git a/ziskbuild/build.rs b/ziskbuild/build.rs index 0a689d13e..a9af6f870 100644 --- a/ziskbuild/build.rs +++ b/ziskbuild/build.rs @@ -1,5 +1,15 @@ fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); + let mut builder = vergen_git2::Emitter::default(); + builder + .add_instructions( + &vergen_git2::BuildBuilder::default().build_timestamp(true).build().unwrap(), + ) + .unwrap(); + builder + .add_instructions(&vergen_git2::Git2Builder::default().sha(true).build().unwrap()) + .unwrap(); + builder.emit().unwrap(); + let disable_distributed = std::env::vars().any(|(k, _)| k == "CARGO_FEATURE_DISABLE_DISTRIBUTED"); let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); From cb700dce35e2450d0fd8c81e406d89611d3967de Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:26:05 +0000 Subject: [PATCH 477/782] added -H flag on execute, prove, stats and verify_constraints CLI commands. Fixed message typo --- cli/src/commands/execute.rs | 4 ++-- cli/src/commands/prove.rs | 4 ++-- cli/src/commands/stats.rs | 4 ++-- cli/src/commands/verify_constraints.rs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 3693292cb..ef86c9e02 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -40,7 +40,7 @@ pub struct ZiskExecute { pub inputs: Option, /// Precompiles Hints path - #[clap(long)] + #[clap(short = 'H', long)] pub hints: Option, /// Setup folder path @@ -94,7 +94,7 @@ impl ZiskExecute { Some(uri) => { let stream = StreamSource::from_uri(uri)?; if matches!(stream, StreamSource::Quic(_)) { - anyhow::bail!("QUIC hints source is not supported for execution."); + anyhow::bail!("QUIC hints source is not supported in CLI mode."); } Some(stream) } diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 5b2047e13..c50e216a4 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -41,7 +41,7 @@ pub struct ZiskProve { pub inputs: Option, /// Precompiles Hints path - #[clap(long)] + #[clap(short = 'H', long)] pub hints: Option, /// Setup folder path @@ -153,7 +153,7 @@ impl ZiskProve { Some(uri) => { let stream = StreamSource::from_uri(uri)?; if matches!(stream, StreamSource::Quic(_)) { - anyhow::bail!("QUIC hints source is not supported for execution."); + anyhow::bail!("QUIC hints source is not supported in CLI mode."); } Some(stream) } diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index d374846a5..af2d3fb28 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -43,7 +43,7 @@ pub struct ZiskStats { pub inputs: Option, /// Precompiles Hints path - #[clap(long)] + #[clap(short = 'H', long)] pub hints: Option, /// Setup folder path @@ -113,7 +113,7 @@ impl ZiskStats { Some(uri) => { let stream = StreamSource::from_uri(uri)?; if matches!(stream, StreamSource::Quic(_)) { - anyhow::bail!("QUIC hints source is not supported for execution."); + anyhow::bail!("QUIC hints source is not supported in CLI mode."); } Some(stream) } diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 7593570f7..5a6dbbae8 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -40,7 +40,7 @@ pub struct ZiskVerifyConstraints { pub inputs: Option, /// Precompiles Hints path - #[clap(long)] + #[clap(short = 'H', long)] pub hints: Option, /// Setup folder path @@ -97,7 +97,7 @@ impl ZiskVerifyConstraints { Some(uri) => { let stream = StreamSource::from_uri(uri)?; if matches!(stream, StreamSource::Quic(_)) { - anyhow::bail!("QUIC hints source is not supported for execution."); + anyhow::bail!("QUIC hints source is not supported in CLI mode."); } Some(stream) } From 0307b8d3fbb554cf3e95657e8df6d6fad1b8ec15 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:29:11 +0000 Subject: [PATCH 478/782] docs: added hints documentation to distributed README.md --- distributed/README.md | 65 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/distributed/README.md b/distributed/README.md index bc7a264a8..e1fc9362d 100644 --- a/distributed/README.md +++ b/distributed/README.md @@ -41,7 +41,7 @@ cargo run --release --bin zisk-coordinator cargo run --release --bin zisk-worker -- --elf --inputs-folder # Generate a proof (in another terminal) -cargo run --release --bin zisk-coordinator prove --input --compute-capacity 10 +cargo run --release --bin zisk-coordinator prove --inputs-uri --compute-capacity 10 ``` ### Docker Deployment @@ -92,7 +92,7 @@ docker logs -f zisk-worker-1 # Generate a proof (use filename only, not full path) docker exec -it zisk-coordinator \ - zisk-coordinator prove --input --compute-capacity 10 + zisk-coordinator prove --inputs-uri --compute-capacity 10 # Stop containers docker stop zisk-coordinator zisk-worker-1 @@ -142,7 +142,9 @@ The table below lists the available configuration options for the Coordinator: | `service.environment` | - | - | String | development | Service environment (development, staging, production) | | `server.host` | - | - | String | 0.0.0.0 | Server host | | `server.port` | `--port` | - | Number | 50051 | Server port | -| `server.proofs_dir` | `--proofs-dir` | - | String | proofs | Directory to save generated proofs | +| `server.proofs_dir` | `--proofs-dir` | - | String | proofs | Directory to save generated proofs (conflicts with `--no-save-proofs`) | +| - | `--no-save-proofs` | - | Boolean | false | Disable saving proofs (conflicts with `--proofs-dir`) | +| - | `-c`, `--compressed-proofs` | - | Boolean | false | Generate compressed proofs | | `server.shutdown_timeout_seconds` | - | - | Number | 30 | Graceful shutdown timeout in seconds | | `logging.level` | - | RUST_LOG | String | debug | Logging level (error, warn, info, debug, trace) | | `logging.format` | - | - | String | pretty | Logging format (pretty, json, compact) | @@ -234,7 +236,7 @@ When `success` is `false`, the `error` field contains: } ``` -**Successful Proof Generation Example::** +**Successful Proof Generation Example:** ```json { @@ -348,7 +350,7 @@ Workers need to know where to find input files for proof generation. The `--inpu cargo run --release --bin zisk-worker -- --elf program.elf --inputs-folder /data/inputs/ # Coordinator requests proof for "input.bin" -> Worker looks for "/data/inputs/input.bin" -cargo run --release --bin zisk-coordinator -- prove --input input.bin --compute-capacity 10 +cargo run --release --bin zisk-coordinator -- prove --inputs-uri input.bin --compute-capacity 10 ``` The table below lists the available configuration options for the Worker: @@ -375,9 +377,11 @@ The table below lists the available configuration options for the Worker: | - | `-d`, `--debug` | - | String | - | Enable debug mode with optional component filter | | - | `--verify-constraints` | - | Boolean | false | Whether to verify constraints | | - | `--unlock-mapped-memory` | - | Boolean | false | Unlock memory map for the ROM file (mutually exclusive with `--emulator`) | -| - | `-f`, `--final-snark` | - | Boolean | false | Whether to generate the final SNARK | +| - | `--hints` | - | Boolean | false | Enable precompile hints processing | +| - | `-m`, `--minimal-memory` | - | Boolean | false | Use minimal memory mode | +| - | `-r`, `--rma` | - | Boolean | false | Enable RMA mode | | - | `-z`, `--preallocate` | - | Boolean | false | GPU preallocation flag | -| - | `-t`, `--max-streams` | | - | Number | - | Maximum number of GPU streams | +| - | `-t`, `--max-streams` | - | Number | - | Maximum number of GPU streams | | - | `-n`, `--number-threads-witness` | - | Number | - | Number of threads for witness computation | | - | `-x`, `--max-witness-stored` | - | Number | - | Maximum number of witnesses to store in memory | @@ -417,16 +421,57 @@ format = "pretty" file_path = "/var/log/distributed/worker-001.log" ``` -## Launching a proof +## Launching a Proof -To launch a proof generation request, use the `prove` command of the `zisk-coordinator` binary, specifying the input filename and desired compute capacity. +To launch a proof generation request, use the `prove` subcommand of the `zisk-coordinator` binary. This sends an RPC request to a running coordinator instance. ```bash -cargo run --release --bin zisk-coordinator -- prove --input --compute-capacity 10 +cargo run --release --bin zisk-coordinator -- prove --inputs-uri --compute-capacity 10 ``` The `--compute-capacity` flag indicates the total compute units required to generate a proof. The coordinator will assign one or more workers to meet this capacity, distributing the workload if multiple workers are needed. Requests exceeding the combined capacity of available workers will not be processed and an error will be returned. +### Prove Subcommand Arguments + +| CLI Argument | Short | Type | Default | Description | +|---|---|---|---|---| +| `--inputs-uri` | - | String | - | Path to the input file for proof generation | +| `--compute-capacity` | `-c` | Number | *required* | Total compute units required for the proof | +| `--coordinator-url` | - | String | http://127.0.0.1:50051 | URL of the coordinator to send the request to | +| `--data-id` | - | String | Auto (from filename or UUID) | Custom identifier for the proof job | +| `--hints-uri` | - | String | - | Path/URI to the precompile hints source | +| `--stream-hints` | - | Boolean | false | Stream hints from the coordinator to workers via gRPC (see [Precompile Hints](../book/getting_started/precompile_hints.md)) | +| `--direct-inputs` | `-x` | Boolean | false | Send input data inline via gRPC instead of as a file path | +| `--minimal-compute-capacity` | `-m` | Number | Same as `--compute-capacity` | Minimum acceptable compute capacity (allows partial worker allocation) | +| `--simulated-node` | - | Number | - | Simulated node ID (for testing) | + +### Input and Hints Modes + +The `prove` subcommand supports two modes for delivering inputs and hints to workers: + +**Input modes** (controlled by `--inputs-uri` and `--direct-inputs`): +- **Path mode** (default): The coordinator sends the input file path to workers. Workers must have access to the file at the specified path. +- **Data mode** (`--direct-inputs`): The coordinator reads the input file and sends its contents inline via gRPC. Workers do not need local access to the file. + +**Hints modes** (controlled by `--hints-uri` and `--stream-hints`): +- **Path mode** (default): The coordinator sends the hints URI to workers. Each worker loads hints from the specified path independently. +- **Streaming mode** (`--stream-hints`): The coordinator reads hints from the URI and broadcasts them to all workers in real-time via gRPC. See the [Precompile Hints documentation](../book/getting_started/precompile_hints.md) for details. + +**Examples:** +```bash +# Basic proof with file path inputs +zisk-coordinator prove --inputs-uri /data/inputs/my_input.bin --compute-capacity 10 + +# Send input data directly (workers don't need local file access) +zisk-coordinator prove --inputs-uri /data/inputs/my_input.bin -x --compute-capacity 10 + +# With precompile hints in path mode (workers load hints locally) +zisk-coordinator prove --inputs-uri input.bin --hints-uri /data/hints/hints.bin --compute-capacity 10 + +# With precompile hints in streaming mode (coordinator broadcasts to workers) +zisk-coordinator prove --inputs-uri input.bin --hints-uri unix:///tmp/hints.sock --stream-hints --compute-capacity 10 +``` + ## Administrative Operations ### Health Checks and Monitoring From 1a17483a97dc6a3e4712f6206a20a966042ac7af Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:11:38 +0000 Subject: [PATCH 479/782] remove libloading crate as it is not necessary --- Cargo.lock | 288 +++++++++++++++++++++++---- Cargo.toml | 5 +- cli/Cargo.toml | 1 - distributed/crates/worker/Cargo.toml | 1 - sdk/Cargo.toml | 1 - 5 files changed, 248 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d7df753b..f0f123403 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -660,7 +660,6 @@ dependencies = [ "fields", "futures", "indicatif", - "libloading", "mpi", "proofman", "proofman-common", @@ -670,7 +669,7 @@ dependencies = [ "rom-setup", "serde", "serde_json", - "sysinfo 0.37.2", + "sysinfo 0.38.1", "target-lexicon", "tokio", "tracing", @@ -1189,7 +1188,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" dependencies = [ "fields", "num-bigint", @@ -1642,7 +1641,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" dependencies = [ "cfg-if", "num-bigint", @@ -1838,6 +1837,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "gimli" version = "0.32.3" @@ -2197,6 +2209,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -2451,6 +2469,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lib-c" version = "0.16.0" @@ -2461,9 +2485,9 @@ version = "0.16.0" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" [[package]] name = "libffi" @@ -2618,9 +2642,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" @@ -2714,7 +2738,7 @@ checksum = "0875efe1a57a20d0cee7034499aa9d764b3c7525563fa3c3f16a2ccf01ddfa04" dependencies = [ "libc", "thiserror 2.0.18", - "windows", + "windows 0.61.3", ] [[package]] @@ -2827,18 +2851,18 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "objc2-core-foundation" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ "bitflags 2.10.0", ] [[package]] name = "objc2-io-kit" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" dependencies = [ "libc", "objc2-core-foundation", @@ -3037,7 +3061,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" dependencies = [ "colored", "fields", @@ -3452,7 +3476,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" dependencies = [ "bincode", "blake3", @@ -3488,7 +3512,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" dependencies = [ "bincode", "borsh", @@ -3521,7 +3545,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" dependencies = [ "fields", "itoa", @@ -3534,7 +3558,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" dependencies = [ "proc-macro2", "quote", @@ -3545,7 +3569,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" dependencies = [ "crossbeam-channel", "tracing", @@ -3554,7 +3578,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" dependencies = [ "bincode", "bytemuck", @@ -3568,7 +3592,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" dependencies = [ "bytemuck", "fields", @@ -4141,9 +4165,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -4668,21 +4692,21 @@ dependencies = [ "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows", + "windows 0.61.3", ] [[package]] name = "sysinfo" -version = "0.37.2" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +checksum = "5792d209c2eac902426c0c4a166c9f72147db453af548cf9bf3242644c4d4fe3" dependencies = [ "libc", "memchr", "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows", + "windows 0.62.2", ] [[package]] @@ -4714,12 +4738,12 @@ checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" [[package]] name = "tempfile" -version = "3.24.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.1", "once_cell", "rustix", "windows-sys 0.61.2", @@ -5260,9 +5284,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-segmentation" @@ -5282,6 +5306,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -5450,6 +5480,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.108" @@ -5509,6 +5548,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -5522,6 +5583,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "web-sys" version = "0.3.85" @@ -5597,11 +5670,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", + "windows-collections 0.2.0", "windows-core 0.61.2", - "windows-future", + "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -5613,6 +5698,15 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -5647,7 +5741,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -5694,6 +5799,16 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -5832,6 +5947,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -5984,11 +6108,93 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.114", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.114", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" dependencies = [ "colored", "fields", @@ -6311,7 +6517,6 @@ dependencies = [ "colored", "config", "fields", - "libloading", "precompiles-hints", "proofman", "proofman-common", @@ -6355,7 +6560,6 @@ dependencies = [ "bincode", "colored", "fields", - "libloading", "proofman", "proofman-common", "proofman-util", @@ -6447,7 +6651,7 @@ dependencies = [ "sm-mem", "symbolic-common", "symbolic-demangle", - "sysinfo 0.37.2", + "sysinfo 0.38.1", "vergen-git2", "zisk-common", "zisk-core", @@ -6508,9 +6712,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 4d9ade02b..3f9a566e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -140,11 +140,10 @@ ark-secp256k1 = "0.5" ark-secp256r1 = "0.5" ark-bn254 = "0.5.0" ark-bls12-381 = "0.5.0" -sysinfo = "0.37" +sysinfo = "0.38" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } anyhow = "1.0" -libloading = "0.8" named-sem = "0.2.2" tracing = "0.1" tracing-subscriber = { version = "0.3", features = [ @@ -178,7 +177,7 @@ tonic-prost-build = "0.14" prost = "0.14" prost-types = "0.14" config = "0.15" -toml = "0.9" +toml = "0.9.8" borsh = { version = "1.5", features = ["derive"] } reqwest = { version = "0.12", features = [ "stream", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 06a17b975..ab0eb0804 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -33,7 +33,6 @@ tracing = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } anyhow = { workspace = true } -libloading = { workspace = true } zisk-sdk = { workspace = true } zisk-verifier = { workspace = true } diff --git a/distributed/crates/worker/Cargo.toml b/distributed/crates/worker/Cargo.toml index 62888ae12..316c906e2 100644 --- a/distributed/crates/worker/Cargo.toml +++ b/distributed/crates/worker/Cargo.toml @@ -37,7 +37,6 @@ tokio-stream = { workspace = true } tracing = { workspace = true } anyhow = { workspace = true } serde = { workspace = true } -libloading = { workspace = true } borsh = { workspace = true } clap = { workspace = true } colored = { workspace = true } diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index a156d69bd..3b6644843 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -12,7 +12,6 @@ proofman-common = { workspace = true } proofman-util = { workspace = true } zisk-common = { workspace = true } fields = { workspace = true } -libloading = { workspace = true } anyhow = { workspace = true } proofman = { workspace = true } rom-setup = { workspace = true } From 9d95e40db08d22e4a664e190dd00d5994fa72428 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:23:47 +0000 Subject: [PATCH 480/782] removed outdated comments --- precompiles/hints/src/hints_processor.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index dd95fa1c6..f81acad00 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -658,7 +658,7 @@ impl HintsProcessor { // BN254 Hint Codes BuiltInHint::Bn254G1Add => bn254_g1_add_hint(&data), BuiltInHint::Bn254G1Mul => bn254_g1_mul_hint(&data), - BuiltInHint::Bn254PairingCheck => bn254_pairing_check_hint(&data), // TODO: check + BuiltInHint::Bn254PairingCheck => bn254_pairing_check_hint(&data), // Secp256k1 Hints BuiltInHint::Secp256k1EcdsaAddressRecover => secp256k1_ecdsa_address_recover(&data), @@ -671,18 +671,18 @@ impl HintsProcessor { // BLS12-381 Hint Codes BuiltInHint::Bls12_381G1Add => bls12_381_g1_add_hint(&data), - BuiltInHint::Bls12_381G1Msm => bls12_381_g1_msm_hint(&data), // TODO: check - BuiltInHint::Bls12_381G2Add => bls12_381_g2_add_hint(&data), // TODO: check - BuiltInHint::Bls12_381G2Msm => bls12_381_g2_msm_hint(&data), // TODO: check - BuiltInHint::Bls12_381PairingCheck => bls12_381_pairing_check_hint(&data), // TODO: check + BuiltInHint::Bls12_381G1Msm => bls12_381_g1_msm_hint(&data), + BuiltInHint::Bls12_381G2Add => bls12_381_g2_add_hint(&data), + BuiltInHint::Bls12_381G2Msm => bls12_381_g2_msm_hint(&data), + BuiltInHint::Bls12_381PairingCheck => bls12_381_pairing_check_hint(&data), BuiltInHint::Bls12_381FpToG1 => bls12_381_fp_to_g1_hint(&data), BuiltInHint::Bls12_381Fp2ToG2 => bls12_381_fp2_to_g2_hint(&data), // Modular Exponentiation Hint Codes - BuiltInHint::ModExp => modexp_hint(&data), // TODO: check + BuiltInHint::ModExp => modexp_hint(&data), // KZG Hint Codes - BuiltInHint::VerifyKzgProof => verify_kzg_proof_hint(&data), // TODO: check + BuiltInHint::VerifyKzgProof => verify_kzg_proof_hint(&data), // Keccak256 Hint Codes BuiltInHint::Keccak256 => keccak256_hint(&data, data_len_bytes), From 669b8a057b0cf36c4859b82bf5450fc4fa132b4d Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 10 Feb 2026 13:30:55 +0000 Subject: [PATCH 481/782] Cargo update --- Cargo.lock | 26 +- examples/Cargo.lock | 426 ++++++++++++++++++++++++------ examples/sha-hasher/host/build.rs | 7 +- 3 files changed, 368 insertions(+), 91 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0f123403..5792efb58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1188,7 +1188,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "fields", "num-bigint", @@ -1641,7 +1641,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "cfg-if", "num-bigint", @@ -2764,9 +2764,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -3061,7 +3061,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "colored", "fields", @@ -3476,7 +3476,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "bincode", "blake3", @@ -3512,7 +3512,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "bincode", "borsh", @@ -3545,7 +3545,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "fields", "itoa", @@ -3558,7 +3558,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "proc-macro2", "quote", @@ -3569,7 +3569,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "crossbeam-channel", "tracing", @@ -3578,7 +3578,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "bincode", "bytemuck", @@ -3592,7 +3592,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "bytemuck", "fields", @@ -6194,7 +6194,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2d85384be91342c657fbb295e1b50792d2722817" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "colored", "fields", diff --git a/examples/Cargo.lock b/examples/Cargo.lock index f3d0a7c11..70291cc17 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "ark-bls12-381" @@ -484,9 +484,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "camino" @@ -591,9 +591,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", "clap_derive", @@ -601,9 +601,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", @@ -702,9 +702,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpp_demangle" -version = "0.4.5" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" dependencies = [ "cfg-if", ] @@ -845,7 +845,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "fields", "num-bigint", @@ -859,6 +859,41 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.114", +] + [[package]] name = "data-bus" version = "0.16.0" @@ -915,6 +950,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.114", +] + [[package]] name = "digest" version = "0.10.7" @@ -1142,7 +1208,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "cfg-if", "num-bigint", @@ -1172,6 +1238,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1237,11 +1309,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "git2" -version = "0.19.0" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ "bitflags", "libc", @@ -1274,6 +1359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", + "foldhash", ] [[package]] @@ -1433,6 +1519,18 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1507,9 +1605,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" dependencies = [ "jiff-static", "log", @@ -1520,9 +1618,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" dependencies = [ "proc-macro2", "quote", @@ -1603,15 +1701,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lib-c" version = "0.16.0" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" [[package]] name = "libffi" @@ -1634,9 +1738,9 @@ dependencies = [ [[package]] name = "libgit2-sys" -version = "0.17.0+1.8.1" +version = "0.18.3+1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" dependencies = [ "cc", "libc", @@ -1750,9 +1854,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" @@ -1818,11 +1922,12 @@ dependencies = [ [[package]] name = "msvc-demangler" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4c25a3bb7d880e8eceab4822f3141ad0700d20f025991c1f03bd3d00219a5fc" +checksum = "fbeff6bd154a309b2ada5639b2661ca6ae4599b34e8487dc276d2cd637da2d76" dependencies = [ "bitflags", + "itoa", ] [[package]] @@ -1848,9 +1953,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -2047,7 +2152,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "colored", "fields", @@ -2353,6 +2458,16 @@ dependencies = [ "ziskos-hints", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.114", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -2374,7 +2489,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "bincode", "blake3", @@ -2410,7 +2525,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "bincode", "borsh", @@ -2443,7 +2558,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "fields", "itoa", @@ -2456,7 +2571,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "proc-macro2", "quote", @@ -2467,7 +2582,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "crossbeam-channel", "tracing", @@ -2476,7 +2591,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "bincode", "bytemuck", @@ -2490,7 +2605,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "bytemuck", "fields", @@ -2675,9 +2790,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -2687,9 +2802,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2698,9 +2813,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "rfc6979" @@ -2896,9 +3011,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -3299,9 +3414,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.17.1" +version = "12.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520cf51c674f8b93d533f80832babe413214bb766b6d7cb74ee99ad2971f8467" +checksum = "751a2823d606b5d0a7616499e4130a516ebd01a44f39811be2b9600936509c23" dependencies = [ "debugid", "memmap2", @@ -3311,9 +3426,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.17.1" +version = "12.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f0de2ee0ffa2641e17ba715ad51d48b9259778176517979cb38b6aa86fa7425" +checksum = "79b237cfbe320601dd24b4ac817a5b68bb28f5508e33f08d42be0682cadc8ac9" dependencies = [ "cc", "cpp_demangle", @@ -3406,12 +3521,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.24.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.1", "once_cell", "rustix", "windows-sys 0.61.2", @@ -3468,9 +3583,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.46" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -3491,9 +3606,9 @@ checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -3759,9 +3874,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-width" @@ -3769,6 +3884,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -3777,9 +3898,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.1.4" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" +checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" dependencies = [ "base64", "flate2", @@ -3859,15 +3980,41 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "8.3.2" +version = "9.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" +checksum = "b849a1f6d8639e8de261e81ee0fc881e3e3620db1af9f2e0da015d4382ceaf75" dependencies = [ "anyhow", - "cfg-if", + "derive_builder", + "rustversion", + "time", + "vergen-lib", +] + +[[package]] +name = "vergen-git2" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51ab55ddf1188c8d679f349775362b0fa9e90bd7a4ac69838b2a087623f0d57" +dependencies = [ + "anyhow", + "derive_builder", "git2", "rustversion", "time", + "vergen", + "vergen-lib", +] + +[[package]] +name = "vergen-lib" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b34a29ba7e9c59e62f229ae1932fb1b8fb8a6fdcc99215a641913f5f5a59a569" +dependencies = [ + "anyhow", + "derive_builder", + "rustversion", ] [[package]] @@ -3901,6 +4048,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.108" @@ -3946,6 +4102,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "web-time" version = "1.1.0" @@ -3958,18 +4148,18 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -4389,11 +4579,93 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.114", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.114", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#035079859314f714b14f03bcdff4b358b2817e93" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" dependencies = [ "colored", "fields", @@ -4412,9 +4684,9 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "x509-parser" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ "asn1-rs", "data-encoding", @@ -4468,18 +4740,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -4569,7 +4841,7 @@ dependencies = [ "clap", "rom-setup", "tracing", - "vergen", + "vergen-git2", "zisk-sdk", ] @@ -4745,7 +5017,7 @@ dependencies = [ "symbolic-common", "symbolic-demangle", "sysinfo 0.37.2", - "vergen", + "vergen-git2", "zisk-common", "zisk-core", "zisk-pil", @@ -4805,9 +5077,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" [[package]] name = "zstd" diff --git a/examples/sha-hasher/host/build.rs b/examples/sha-hasher/host/build.rs index 336b4a51f..9de57067b 100644 --- a/examples/sha-hasher/host/build.rs +++ b/examples/sha-hasher/host/build.rs @@ -6,5 +6,10 @@ fn main() { let n = 1000u32; let stdin_save = ZiskStdin::new(); stdin_save.write(&n); - stdin_save.save(&PathBuf::from("tmp/verify_constraints_input.bin")).unwrap(); + // Check if path exists, if not write + let path = PathBuf::from("tmp/verify_constraints_input.bin"); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).unwrap(); + } + stdin_save.save(&path).unwrap(); } From a20dd0a70fcd9762d3e8d1e86b18c4d7d9287a22 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:41:35 +0000 Subject: [PATCH 482/782] docs: update book adding distributed execution --- book/SUMMARY.md | 1 + book/getting_started/distributed_execution.md | 562 ++++++++++++++++++ 2 files changed, 563 insertions(+) create mode 100644 book/getting_started/distributed_execution.md diff --git a/book/SUMMARY.md b/book/SUMMARY.md index b38c5d568..6d7f8df13 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -8,6 +8,7 @@ - [Quickstart](./getting_started/quickstart.md) - [Writing Programs](./getting_started/writing_programs.md) - [Precompiles](./getting_started/precompiles.md) +- [Distributed Execution](./getting_started/distributed_execution.md) - [Precompile Hints](./getting_started/precompile_hints.md) # Developer Guide diff --git a/book/getting_started/distributed_execution.md b/book/getting_started/distributed_execution.md new file mode 100644 index 000000000..1153b75b5 --- /dev/null +++ b/book/getting_started/distributed_execution.md @@ -0,0 +1,562 @@ +# Distributed Proving + +Generating a ZisK proof can be computationally intensive, especially for large programs. The distributed proving system lets you split the workload across multiple machines, reducing proof generation time by parallelizing the work. + +This chapter covers how to set up and run a distributed proving cluster, from launching a coordinator to connecting workers and submitting proof requests. + +## How It Works + +A distributed proving cluster consists of two roles: + +- A **Coordinator** that receives proof requests and orchestrates the work. +- One or more **Workers** that execute the actual proof computation. + +When you submit a proof request, the process unfolds in three phases: + +1. **Partial Contributions** — The coordinator assigns segments of the work to available workers based on their compute capacity. Each worker computes its partial challenges independently. +2. **Prove** — Workers compute the global challenge and generate their respective partial proofs. +3. **Aggregation** — The first worker to finish is selected as the aggregator. It collects all partial proofs and produces the final proof. + +The coordinator returns the final proof to the client once aggregation completes. + +Workers report their compute capacity when they register. The coordinator selects workers sequentially from the available pool until the requested capacity is met. While assigned to a job, a worker is marked as busy and won't receive new tasks. + +## Getting Started + +### Building + +From the project root, build both binaries: + +```bash +cargo build --release --bin zisk-coordinator --bin zisk-worker +``` + +### Running Locally + +**1. Start the coordinator:** + +```bash +cargo run --release --bin zisk-coordinator +``` + +**2. Start a worker** (in a separate terminal): + +```bash +cargo run --release --bin zisk-worker -- --elf --inputs-folder +``` + +**3. Submit a proof request** (in a separate terminal): + +```bash +cargo run --release --bin zisk-coordinator prove --inputs-uri --compute-capacity 10 +``` + +The `--compute-capacity` flag specifies how many compute units the proof requires. The coordinator assigns workers until this capacity is covered. + +### Docker Deployment + +For multi-machine setups, Docker simplifies deployment: + +```bash +# Build the image (CPU-only) +docker build -t zisk-distributed:latest -f distributed/Dockerfile . + +# For GPU support +docker build --build-arg GPU=true -t zisk-distributed:gpu -f distributed/Dockerfile . + +# Create a network for container DNS resolution +docker network create zisk-net || true +``` + +**Start the coordinator:** + +```bash +LOGS_DIR="" +docker run -d --rm --name zisk-coordinator \ + --network zisk-net \ + -v "$LOGS_DIR:/var/log/distributed" \ + -e RUST_LOG=info \ + zisk-distributed:latest \ + zisk-coordinator --config /app/config/coordinator/dev.toml +``` + +**Start a worker:** + +```bash +LOGS_DIR="" +PROVING_KEY_DIR="" +ELF_DIR="" +INPUTS_DIR="" +docker run -d --rm --name zisk-worker-1 \ + --network zisk-net --shm-size=20g \ + -v "$LOGS_DIR:/var/log/distributed" \ + -v "$HOME/.zisk/cache:/app/.zisk/cache:ro" \ + -v "$PROVING_KEY_DIR:/app/proving-keys:ro" \ + -v "$ELF_DIR:/app/elf:ro" \ + -v "$INPUTS_DIR:/app/inputs:ro" \ + -e RUST_LOG=info \ + zisk-distributed:latest zisk-worker --coordinator-url http://zisk-coordinator:50051 \ + --elf /app/elf/zec.elf --proving-key /app/proving-keys --inputs-folder /app/inputs +``` + +**Submit a proof:** + +```bash +docker exec -it zisk-coordinator \ + zisk-coordinator prove --inputs-uri --compute-capacity 10 +``` + +> **Note:** Use the filename only when submitting proofs, not the full path. Workers resolve files relative to their `--inputs-folder`. + +**Container paths reference:** + +| Path | Purpose | +|------|---------| +| `/app/config/{coordinator,worker}/` | Configuration files | +| `/app/bin/` | Binaries | +| `/app/.zisk/cache/` | Cache (mount from host `$HOME/.zisk/cache`) | +| `/var/log/distributed/` | Log files | + +## Coordinator + +The coordinator is responsible for managing the distributed proof generation process. It receives proof requests from clients and assigns work to available workers. + +To start a coordinator instance with default settings: + +```bash +cargo run --release --bin zisk-coordinator +``` + +### Coordinator Configuration + +The coordinator can be configured using either a **TOML configuration file** or **command-line arguments**. +If no configuration file is explicitly provided, the system falls back to the `ZISK_COORDINATOR_CONFIG_PATH` environment variable to locate one. If neither the CLI argument nor environment variable is set, built-in defaults are used. + +**Example:** + +```bash +# You can specify the configuration file path using a command line argument: +cargo run --release --bin zisk-coordinator -- --config /path/to/my-config.toml + +# You can specify the configuration file path using an environment variable: +export ZISK_COORDINATOR_CONFIG_PATH="/path/to/my-config.toml" +cargo run --release --bin zisk-coordinator +``` + +The table below lists the available configuration options for the Coordinator: + +| TOML Key | CLI Argument | Environment Variable| Type | Default | Description | +|-----------------------|--------------|---------------------|------|---------|-------------| +| `service.name` | - | - | String | ZisK Distributed Coordinator | Service name | +| `service.environment` | - | - | String | development | Service environment (development, staging, production) | +| `server.host` | - | - | String | 0.0.0.0 | Server host | +| `server.port` | `--port` | - | Number | 50051 | Server port | +| `server.proofs_dir` | `--proofs-dir` | - | String | proofs | Directory to save generated proofs (conflicts with `--no-save-proofs`) | +| - | `--no-save-proofs` | - | Boolean | false | Disable saving proofs (conflicts with `--proofs-dir`) | +| - | `-c`, `--compressed-proofs` | - | Boolean | false | Generate compressed proofs | +| `server.shutdown_timeout_seconds` | - | - | Number | 30 | Graceful shutdown timeout in seconds | +| `logging.level` | - | RUST_LOG | String | debug | Logging level (error, warn, info, debug, trace) | +| `logging.format` | - | - | String | pretty | Logging format (pretty, json, compact) | +| `logging.file_path` | - | - | String | - | *Optional*. Log file path (enables file logging) | +| `coordinator.max_workers_per_job` | - | - | Number | 10 | Maximum workers per proof job | +| `coordinator.max_total_workers` | - | - | Number | 1000 | Maximum total registered workers | +| `coordinator.phase1_timeout_seconds` | - | - | Number | 300 | Phase 1 timeout in seconds | +| `coordinator.phase2_timeout_seconds` | - | - | Number | 600 | Phase 2 timeout in seconds | +| `coordinator.webhook_url` | `--webhook-url` | - | String | - | *Optional*. Webhook URL to notify on job completion | + + +#### Configuration Files examples + +Example development configuration file: + +```toml +[service] +name = "ZisK Distributed Coordinator" +environment = "development" + +[logging] +level = "debug" +format = "pretty" +``` + +Example production configuration file: + +```toml +[service] +name = "ZisK Distributed Coordinator" +environment = "production" + +[server] +host = "0.0.0.0" +port = 50051 +proofs_dir = "proofs" + +[logging] +level = "info" +format = "json" +file_path = "/var/log/distributed/coordinator.log" + +[coordinator] +max_workers_per_job = 20 # Maximum workers per proof job +max_total_workers = 5000 # Maximum total registered workers +phase1_timeout_seconds = 600 # 10 minutes for phase 1 +phase2_timeout_seconds = 1200 # 20 minutes for phase 2 +webhook_url = "http://webhook.example.com/notify?job_id={$job_id}" +``` + +### Webhook URL + +The Coordinator can notify an external service when a job finishes by sending a request to a configured webhook URL. +The placeholder {$job_id} can be included in the URL and will be replaced with the finished job’s ID. +If no placeholder is provided, the Coordinator automatically appends /{job_id} to the end of the URL. + +All webhook notifications are sent as JSON POST requests with the following structure: + +```json +{ + "job_id": "job_12345", + "success": true, + "duration_ms": 45000, + "proof": , + "timestamp": "2025-10-03T14:30:00Z", + "error": null +} +``` + +##### Fields Description + +| Field | Type | Description | +|-------|------|-------------| +| `job_id` | `string` | Unique identifier for the proof generation job | +| `success` | `boolean` | `true` if proof generation completed successfully, `false` if it failed | +| `duration_ms` | `number` | Total execution time in milliseconds from job start to completion | +| `proof` | `array` \| `null` | Final proof data as array of integers (only present on success) | +| `timestamp` | `string` | ISO 8601 timestamp when the notification was sent | +| `error` | `object` \| `null` | Error details (only present on failure) | + +##### Error Object Structure + +When `success` is `false`, the `error` field contains: + +```json +{ + "code": "WORKER_FAILURE", + "message": "Worker node-003 failed during proof generation: Out of memory" +} +``` + +**Successful Proof Generation Example:** + +```json +{ + "job_id": "job_abc123", + "success": true, + "duration_ms": 32500, + "proof": [1234567890, 9876543210, 1357924680, ...], + "timestamp": "2025-10-03T14:30:25Z", + "error": null +} +``` + +**Failed Job Example:** + +```json +{ + "job_id": "job_def456", + "success": false, + "duration_ms": 15000, + "proof": null, + "timestamp": "2025-10-03T14:31:10Z", + "error": { + "code": "WORKER_ERROR", + "message": "Memory exhaustion during proof generation" + } +} +``` + +#### Webhook Implementation Guidelines + +*HTTP Requirements:* + +- **Method**: POST +- **Content-Type**: `application/json` +- **Timeout**: 10 seconds (configurable) +- **Retry**: Currently no automatic retries (implement idempotency) + +*Recommended Response:* + +Your webhook endpoint should respond with: + +- **Success**: HTTP 200-299 status code +- **Body**: Any valid response (ignored by coordinator) + +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{"received": true, "job_id": "job_abc123"} +``` + +If your webhook endpoint is unavailable or returns an error: + +- The coordinator logs the failure but continues operation +- No automatic retries are performed +- Consider implementing your own retry mechanism or message queue + +### Command Line Arguments + +```bash +# Show help +cargo run --release --bin zisk-coordinator -- --help + +# Run coordinator with custom port +cargo run --release --bin zisk-coordinator -- --port 50051 + +# Run with specific configuration +cargo run --release --bin zisk-coordinator -- --config production.toml + +# Run with webhook URL +cargo run --release --bin zisk-coordinator -- --webhook-url http://webhook.example.com/notify --port 50051 +``` + +## Worker + +The worker is responsible for executing proof generation tasks assigned by the coordinator. It registers with the coordinator, reports its compute capacity, and waits for tasks to be assigned. + +To start a worker instance with default settings: + +```bash +cargo run --release --bin zisk-worker -- --elf --inputs-folder +``` + +### Worker Configuration + +The worker can be configured using either a **TOML configuration file** or **command-line arguments**. +If no configuration file is explicitly provided, the system falls back to the `ZISK_WORKER_CONFIG_PATH` environment variable to locate one. If neither the CLI argument nor environment variable is set, built-in defaults are used. + +**Example:** + +```bash +# You can specify the configuration file path using a command line argument: +cargo run --release --bin zisk-worker -- --config /path/to/my-config.toml + +# You can specify the configuration file path using an environment variable: +export ZISK_WORKER_CONFIG_PATH="/path/to/my-config.toml" +cargo run --release --bin zisk-worker +``` + +### Input Files Handling + +Workers need to know where to find input files for proof generation. The `--inputs-folder` parameter specifies the base directory where input files are stored: + +- **Default**: Current working directory (`.`) if not specified +- **Usage**: When the coordinator sends a prove command with an input filename, the worker combines `--inputs-folder` + `filename` to locate the file +- **Benefits**: Allows input files to be organized in a dedicated directory, separate from the worker executable + +**Example:** +```bash +# Worker with inputs in specific folder +cargo run --release --bin zisk-worker -- --elf program.elf --inputs-folder /data/inputs/ + +# Coordinator requests proof for "input.bin" -> Worker looks for "/data/inputs/input.bin" +cargo run --release --bin zisk-coordinator -- prove --inputs-uri input.bin --compute-capacity 10 +``` + +The table below lists the available configuration options for the Worker: + +| TOML Key | CLI Argument | Environment Variable| Type | Default | Description | +|-----------------------|--------------|---------------------|------|---------|-------------| +| `worker.worker_id` | `--worker-id` | - | String | Auto-generated UUID | Unique worker identifier | +| `worker.compute_capacity.compute_units` | `--compute-capacity` | - | Number | 10 | Worker compute capacity (in compute units) | +| `worker.environment` | - | - | String | development | Service environment (development, staging, production) | +| `worker.inputs_folder` | `--inputs-folder` | - | String | . | Path to folder containing input files | +| `coordinator.url` | `--coordinator-url` | - | String | http://127.0.0.1:50051 | Coordinator server URL | +| `connection.reconnect_interval_seconds` | - | - | Number | 5 | Reconnection interval in seconds | +| `connection.heartbeat_timeout_seconds` | - | - | Number | 30 | Heartbeat timeout in seconds | +| `logging.level` | - | RUST_LOG | String | debug | Logging level (error, warn, info, debug, trace) | +| `logging.format` | - | - | String | pretty | Logging format (pretty, json, compact) | +| `logging.file_path` | - | - | String | - | *Optional*. Log file path (enables file logging) | +| - | `--proving-key` | - | String | ~/.zisk/provingKey | Path to setup folder | +| - | `--elf` | - | String | - | Path to ELF file | +| - | `--asm` | - | String | ~/.zisk/cache | Path to ASM file (mutually exclusive with `--emulator`) | +| - | `--emulator` | - | Boolean | false | Use prebuilt emulator (mutually exclusive with `--asm`) | +| - | `--asm-port` | - | Number | 23115 | Base port for Assembly microservices | +| - | `--shared-tables` | - | Boolean | false | Whether to share tables when worker is running in a cluster | +| - | `-v`, `-vv`, `-vvv`, ... | - | Number | 0 | Verbosity level (0=error, 1=warn, 2=info, 3=debug, 4=trace) | +| - | `-d`, `--debug` | - | String | - | Enable debug mode with optional component filter | +| - | `--verify-constraints` | - | Boolean | false | Whether to verify constraints | +| - | `--unlock-mapped-memory` | - | Boolean | false | Unlock memory map for the ROM file (mutually exclusive with `--emulator`) | +| - | `--hints` | - | Boolean | false | Enable precompile hints processing | +| - | `-m`, `--minimal-memory` | - | Boolean | false | Use minimal memory mode | +| - | `-r`, `--rma` | - | Boolean | false | Enable RMA mode | +| - | `-z`, `--preallocate` | - | Boolean | false | GPU preallocation flag | +| - | `-t`, `--max-streams` | - | Number | - | Maximum number of GPU streams | +| - | `-n`, `--number-threads-witness` | - | Number | - | Number of threads for witness computation | +| - | `-x`, `--max-witness-stored` | - | Number | - | Maximum number of witnesses to store in memory | + +#### Configuration Files examples + +Example development configuration file: + +```toml +[worker] +compute_capacity.compute_units = 10 +environment = "development" + +[logging] +level = "debug" +format = "pretty" +``` + +Example production configuration file: + +```toml +[worker] +worker_id = "my-worker-001" +compute_capacity.compute_units = 10 +environment = "production" +inputs_folder = "/app/inputs" + +[coordinator] +url = "http://127.0.0.1:50051" + +[connection] +reconnect_interval_seconds = 5 +heartbeat_timeout_seconds = 30 + +[logging] +level = "info" +format = "pretty" +file_path = "/var/log/distributed/worker-001.log" +``` + +## Launching a Proof + +To launch a proof generation request, use the `prove` subcommand of the `zisk-coordinator` binary. This sends an RPC request to a running coordinator instance. + +```bash +cargo run --release --bin zisk-coordinator -- prove --inputs-uri --compute-capacity 10 +``` + +The `--compute-capacity` flag indicates the total compute units required to generate a proof. The coordinator will assign one or more workers to meet this capacity, distributing the workload if multiple workers are needed. Requests exceeding the combined capacity of available workers will not be processed and an error will be returned. + +### Prove Subcommand Arguments + +| CLI Argument | Short | Type | Default | Description | +|---|---|---|---|---| +| `--inputs-uri` | - | String | - | Path to the input file for proof generation | +| `--compute-capacity` | `-c` | Number | *required* | Total compute units required for the proof | +| `--coordinator-url` | - | String | http://127.0.0.1:50051 | URL of the coordinator to send the request to | +| `--data-id` | - | String | Auto (from filename or UUID) | Custom identifier for the proof job | +| `--hints-uri` | - | String | - | Path/URI to the precompile hints source | +| `--stream-hints` | - | Boolean | false | Stream hints from the coordinator to workers via gRPC (see [Precompile Hints](../book/getting_started/precompile_hints.md)) | +| `--direct-inputs` | `-x` | Boolean | false | Send input data inline via gRPC instead of as a file path | +| `--minimal-compute-capacity` | `-m` | Number | Same as `--compute-capacity` | Minimum acceptable compute capacity (allows partial worker allocation) | +| `--simulated-node` | - | Number | - | Simulated node ID (for testing) | + +### Input and Hints Modes + +The `prove` subcommand supports two modes for delivering inputs and hints to workers: + +**Input modes** (controlled by `--inputs-uri` and `--direct-inputs`): +- **Path mode** (default): The coordinator sends the input file path to workers. Workers must have access to the file at the specified path. +- **Data mode** (`--direct-inputs`): The coordinator reads the input file and sends its contents inline via gRPC. Workers do not need local access to the file. + +**Hints modes** (controlled by `--hints-uri` and `--stream-hints`): +- **Path mode** (default): The coordinator sends the hints URI to workers. Each worker loads hints from the specified path independently. +- **Streaming mode** (`--stream-hints`): The coordinator reads hints from the URI and broadcasts them to all workers in real-time via gRPC. See the [Precompile Hints documentation](../book/getting_started/precompile_hints.md) for details. + +**Examples:** +```bash +# Basic proof with file path inputs +zisk-coordinator prove --inputs-uri /data/inputs/my_input.bin --compute-capacity 10 + +# Send input data directly (workers don't need local file access) +zisk-coordinator prove --inputs-uri /data/inputs/my_input.bin -x --compute-capacity 10 + +# With precompile hints in path mode (workers load hints locally) +zisk-coordinator prove --inputs-uri input.bin --hints-uri /data/hints/hints.bin --compute-capacity 10 + +# With precompile hints in streaming mode (coordinator broadcasts to workers) +zisk-coordinator prove --inputs-uri input.bin --hints-uri unix:///tmp/hints.sock --stream-hints --compute-capacity 10 +``` + +## Administrative Operations + +### Health Checks and Monitoring + +The coordinator exposes administrative endpoints for monitoring: + +```bash +# Basic health check +grpcurl -plaintext 127.0.0.1:50051 zisk.distributed.api.v1.ZiskDistributedApi/HealthCheck + +# System status +grpcurl -plaintext 127.0.0.1:50051 zisk.distributed.api.v1.ZiskDistributedApi/SystemStatus + +# List active jobs +grpcurl -plaintext -d '{"active_only": true}' \ + 127.0.0.1:50051 zisk.distributed.api.v1.ZiskDistributedApi/JobsList + +# List connected workers +grpcurl -plaintext -d '{"available_only": true}' \ + 127.0.0.1:50051 zisk.distributed.api.v1.ZiskDistributedApi/WorkersList +``` + +## Troubleshooting + +### Common Issues + +**Worker can't connect to coordinator:** +- Verify coordinator is running and accessible on the specified port +- Check firewall settings if coordinator and worker are on different machines +- Ensure correct URL format: `http://host:port` (not `https://` for default setup) + +**Configuration not loading:** +- Verify TOML syntax with a TOML validator +- Check file permissions on configuration files +- Use CLI overrides to test specific values + +**Worker not receiving tasks:** +- Check worker registration in coordinator logs +- Verify compute capacity is appropriate for available tasks +- Ensure worker ID is unique if running multiple workers +- Confirm coordinator has active jobs to distribute + +**Input file not found errors:** +- Verify the input file exists in the worker's `--inputs-folder` directory +- Check file permissions - worker needs read access to input files +- Ensure you're using the filename only (not full path) when launching proofs +- Confirm `--inputs-folder` path is correct and accessible + +**Port conflicts:** +- Use `--port` flag or update configuration file to change ports +- Check for other services using the same ports + +### Debug Mode + +Enable detailed logging for troubleshooting by modifying configuration files or using CLI arguments: + +```bash +# Coordinator with debug logging (via config file) +cargo run --release --bin zisk-coordinator -- --config debug-coordinator.toml + +# Worker with debug logging (via config file) +cargo run --release --bin zisk-worker -- --config debug-worker.toml +``` + +Where `debug-coordinator.toml` or `debug-worker.toml` contains: +```toml +[logging] +level = "debug" +format = "pretty" +``` + +### Log Files + +When file logging is enabled, logs are written into specified paths in the configuration files. Ensure the application has write permissions to these paths. + +```toml +[logging] +file_path = "/var/log/distributed/coordinator.log" +``` From 17d9144c7fcc75e5a6fa091460d1ee0df13a5582 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Tue, 10 Feb 2026 14:08:32 +0000 Subject: [PATCH 483/782] fix: update main thread ID handling for single-threaded assert --- ziskos/entrypoint/src/hints/mod.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 957898984..795b5634c 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -71,9 +71,9 @@ impl HintFileWriterHandleCell { } pub fn init_hints() -> io::Result<()> { - // Record the main thread id to validate single-threaded calls later + // Initialize the main thread ID for single-threaded assert (if enabled) #[cfg(zisk_hints_single_thread)] - let _ = MAIN_TID.set(std::thread::current().id()); + let _ = MAIN_TID.set(None); // Placeholder value to mark uninitialized if let Some(handle) = HINT_WRITER_HANDLE.take() { HINT_BUFFER.close(); @@ -214,7 +214,7 @@ fn write_hints_to_socket(mut socket_writer: UnixSocketWriter) -> io::Result<()> } #[cfg(zisk_hints_single_thread)] -static MAIN_TID: OnceCell = OnceCell::new(); +static MAIN_TID: OnceCell> = OnceCell::new(); #[cfg(zisk_hints_single_thread)] #[inline(always)] @@ -223,16 +223,24 @@ pub(crate) fn check_main_thread() { let tid = std::thread::current().id(); match MAIN_TID.get() { Some(main) => { - if *main != tid { - panic!( - "Precompile hint function called from non-main thread, main={:?}, current={:?}", - main, tid - ); + match main { + Some(main) => { + if *main != tid { + panic!( + "Precompile hint function called from non-main thread, main={:?}, current={:?}", + main, tid + ); + } + } + None => { + // If not initialized yet, record the first caller thread as main + let _ = MAIN_TID.set(Some(tid)); + } } } None => { // If not initialized yet, record the first caller thread as main - let _ = MAIN_TID.set(tid); + let _ = MAIN_TID.set(Some(tid)); } } } From c83b603a546d7e44c0845878562bd6481a95d5a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Wed, 11 Feb 2026 09:20:13 +0100 Subject: [PATCH 484/782] Feature/ziskemu example (#772) * Example ziskemu * Fix * Fix 2 * Fix command * Fix run command * Fix sha hasher --- Cargo.lock | 24 +++--- cli/src/commands/run.rs | 6 ++ emulator/src/emu.rs | 33 ++++---- emulator/src/emu_options.rs | 8 +- emulator/src/emulator.rs | 11 +++ examples/Cargo.lock | 104 ++++++++++++++++++------ examples/sha-hasher/guest/src/main.rs | 3 + examples/sha-hasher/host/Cargo.toml | 6 +- examples/sha-hasher/host/bin/ziskemu.rs | 26 ++++++ sdk/Cargo.toml | 2 + sdk/src/lib.rs | 2 + sdk/src/ziskemu.rs | 40 +++++++++ tools/emulate_all.sh | 2 +- tools/test-env/test_sha_hasher.sh | 4 +- 14 files changed, 210 insertions(+), 61 deletions(-) create mode 100644 examples/sha-hasher/host/bin/ziskemu.rs create mode 100644 sdk/src/ziskemu.rs diff --git a/Cargo.lock b/Cargo.lock index 5792efb58..7e743186e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1188,7 +1188,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "fields", "num-bigint", @@ -1641,7 +1641,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "cfg-if", "num-bigint", @@ -3061,7 +3061,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "colored", "fields", @@ -3476,7 +3476,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "bincode", "blake3", @@ -3512,7 +3512,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "bincode", "borsh", @@ -3545,7 +3545,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "fields", "itoa", @@ -3558,7 +3558,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "proc-macro2", "quote", @@ -3569,7 +3569,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "crossbeam-channel", "tracing", @@ -3578,7 +3578,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "bincode", "bytemuck", @@ -3592,7 +3592,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "bytemuck", "fields", @@ -6194,7 +6194,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "colored", "fields", @@ -6568,9 +6568,11 @@ dependencies = [ "sha2", "tracing", "zisk-common", + "zisk-core", "zisk-distributed-common", "zisk-verifier", "zisk-witness", + "ziskemu", "zstd", ] diff --git a/cli/src/commands/run.rs b/cli/src/commands/run.rs index d98e8ee2d..422ca8772 100644 --- a/cli/src/commands/run.rs +++ b/cli/src/commands/run.rs @@ -38,6 +38,9 @@ pub struct ZiskRun { #[clap(long, short = 'm')] metrics: bool, + #[clap(short = 'f', long)] + riscof: bool, + #[clap(last = true)] args: Vec, } @@ -79,6 +82,9 @@ impl ZiskRun { } input_command = format!("-i {}", input); } + if self.riscof { + extra_command += " -f "; + } runner_command = format!("ziskemu {input_command} {extra_command} -e"); } else { let mut gdb_command = ""; diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index ab32a9bc8..1f5ef7869 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -21,6 +21,8 @@ use zisk_core::{ STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, }; +pub const ZISK_PUBLICS: usize = 64; + /// ZisK emulator structure, containing the ZisK rom, the list of ZisK operations, and the /// execution context pub struct Emu<'a> { @@ -2542,21 +2544,21 @@ impl<'a> Emu<'a> { self.ctx.inst_ctx.step } - /// Get the output as a vector of u64 - pub fn get_output(&self) -> Vec { - let n = self.ctx.inst_ctx.mem.read(OUTPUT_ADDR, 8); - let mut addr = OUTPUT_ADDR + 8; - - let mut output: Vec = Vec::with_capacity(n as usize); + /// Get the output as a vector of u32 + pub fn get_output_32(&self) -> Vec { + let n = ZISK_PUBLICS; + let mut addr = OUTPUT_ADDR; + let mut output: Vec = Vec::with_capacity(n); for _i in 0..n { - output.push(self.ctx.inst_ctx.mem.read(addr, 8)); - addr += 8; + output.push(self.ctx.inst_ctx.mem.read(addr, 4) as u32); + addr += 4; } + output } /// Get the output as a vector of u32 - pub fn get_output_32(&self) -> Vec { + pub fn get_output_riscof_32(&self) -> Vec { let n = self.ctx.inst_ctx.mem.read(OUTPUT_ADDR, 4); let mut addr = OUTPUT_ADDR + 4; let mut output: Vec = Vec::with_capacity(n as usize); @@ -2565,22 +2567,15 @@ impl<'a> Emu<'a> { addr += 4; } - // let mut addr = OUTPUT_ADDR; - // let mut output: Vec = Vec::with_capacity(32); - // for _i in 0..32 { - // output.push(self.ctx.inst_ctx.mem.read(addr, 4) as u32); - // addr += 4; - // } - output } /// Get the output as a vector of u8 pub fn get_output_8(&self) -> Vec { - let n = self.ctx.inst_ctx.mem.read(OUTPUT_ADDR, 4); - let mut addr = OUTPUT_ADDR + 4; + let n = ZISK_PUBLICS; + let mut addr = OUTPUT_ADDR; - let mut output: Vec = Vec::with_capacity(n as usize); + let mut output: Vec = Vec::with_capacity(n); for _i in 0..n { output.push(self.ctx.inst_ctx.mem.read(addr, 1) as u8); output.push(self.ctx.inst_ctx.mem.read(addr + 1, 1) as u8); diff --git a/emulator/src/emu_options.rs b/emulator/src/emu_options.rs index ab1056ef4..d9b0d9e74 100644 --- a/emulator/src/emu_options.rs +++ b/emulator/src/emu_options.rs @@ -48,8 +48,11 @@ pub struct EmuOptions { pub log_step: bool, /// Log the output to console. This option is set by default to true as a requirement to pass /// the riscof GHA tests. Enabled with `-c`. - #[clap(short = 'c', long, value_name = "LOG_OUTPUT", default_value = "true")] + #[clap(short = 'c', long, value_name = "LOG_OUTPUT", default_value = "false")] pub log_output: bool, + /// Log the output to console in riscof format. Enabled with `-f`. + #[clap(short = 'f', long, value_name = "LOG_OUTPUT_RISCOF", default_value = "false")] + pub log_output_riscof: bool, /// Trace every this number of steps. pub chunk_size: Option, /// Log performance metrics. Enabled with `-m`. @@ -109,6 +112,7 @@ impl Default for EmuOptions { verbose: false, log_step: false, log_output: false, + log_output_riscof: false, chunk_size: None, log_metrics: false, tracerv: false, @@ -137,6 +141,7 @@ impl fmt::Display for EmuOptions { writeln!(f, "TRACE: {:?}", self.trace)?; writeln!(f, "OUTPUT: {:?}", self.output)?; writeln!(f, "LOG_OUTPUT: {:?}", self.log_output)?; + writeln!(f, "LOG_OUTPUT_RISCOF: {:?}", self.log_output_riscof)?; writeln!(f, "VERBOSE: {}", self.verbose)?; writeln!(f, "CHUNK_SIZE: {:?}", self.chunk_size)?; writeln!(f, "METRICS: {:?}", self.log_metrics)?; @@ -165,5 +170,6 @@ impl EmuOptions { && !self.stats && !self.generate_minimal_traces && !self.log_output + && !self.log_output_riscof } } diff --git a/emulator/src/emulator.rs b/emulator/src/emulator.rs index 648d64b4c..f9eb41b91 100644 --- a/emulator/src/emulator.rs +++ b/emulator/src/emulator.rs @@ -171,6 +171,17 @@ impl ZiskEmulator { } } + // Log output to console if requested + if options.log_output_riscof { + // Get the emulation output as a u32 vector + let output = emu.get_output_riscof_32(); + + // Log the output to console + for o in &output { + println!("{o:08x}"); + } + } + Ok(output) } diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 70291cc17..6e076d39d 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -845,7 +845,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "fields", "num-bigint", @@ -1208,7 +1208,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "cfg-if", "num-bigint", @@ -1938,7 +1938,7 @@ checksum = "0875efe1a57a20d0cee7034499aa9d764b3c7525563fa3c3f16a2ccf01ddfa04" dependencies = [ "libc", "thiserror 2.0.18", - "windows", + "windows 0.61.3", ] [[package]] @@ -2034,18 +2034,18 @@ dependencies = [ [[package]] name = "objc2-core-foundation" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ "bitflags", ] [[package]] name = "objc2-io-kit" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" dependencies = [ "libc", "objc2-core-foundation", @@ -2152,7 +2152,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "colored", "fields", @@ -2489,7 +2489,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "bincode", "blake3", @@ -2525,7 +2525,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "bincode", "borsh", @@ -2558,7 +2558,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "fields", "itoa", @@ -2571,7 +2571,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "proc-macro2", "quote", @@ -2582,7 +2582,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "crossbeam-channel", "tracing", @@ -2591,7 +2591,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "bincode", "bytemuck", @@ -2605,7 +2605,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "bytemuck", "fields", @@ -3481,21 +3481,21 @@ dependencies = [ "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows", + "windows 0.61.3", ] [[package]] name = "sysinfo" -version = "0.37.2" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +checksum = "5792d209c2eac902426c0c4a166c9f72147db453af548cf9bf3242644c4d4fe3" dependencies = [ "libc", "memchr", "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows", + "windows 0.62.2", ] [[package]] @@ -4201,11 +4201,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", + "windows-collections 0.2.0", "windows-core 0.61.2", - "windows-future", + "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -4217,6 +4229,15 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -4251,7 +4272,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -4298,6 +4330,16 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -4427,6 +4469,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -4665,7 +4716,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ab4f2c46a6c221a55431ae57e25618635224edb6" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" dependencies = [ "colored", "fields", @@ -4934,7 +4985,6 @@ dependencies = [ "bincode", "colored", "fields", - "libloading", "proofman", "proofman-common", "proofman-util", @@ -4943,9 +4993,11 @@ dependencies = [ "sha2", "tracing", "zisk-common", + "zisk-core", "zisk-distributed-common", "zisk-verifier", "zisk-witness", + "ziskemu", "zstd", ] @@ -5016,7 +5068,7 @@ dependencies = [ "sm-mem", "symbolic-common", "symbolic-demangle", - "sysinfo 0.37.2", + "sysinfo 0.38.1", "vergen-git2", "zisk-common", "zisk-core", diff --git a/examples/sha-hasher/guest/src/main.rs b/examples/sha-hasher/guest/src/main.rs index 4d103cf5e..0dba31050 100644 --- a/examples/sha-hasher/guest/src/main.rs +++ b/examples/sha-hasher/guest/src/main.rs @@ -30,5 +30,8 @@ fn main() { let output = Output { hash, iterations: n, magic_number: 0xDEADBEEF }; + println!("Computed hash: {:02x?}", output.hash); + println!("Iterations: {}", output.iterations); + ziskos::io::commit(&output); } diff --git a/examples/sha-hasher/host/Cargo.toml b/examples/sha-hasher/host/Cargo.toml index e09c575cd..e7b4fcd00 100644 --- a/examples/sha-hasher/host/Cargo.toml +++ b/examples/sha-hasher/host/Cargo.toml @@ -37,4 +37,8 @@ path = "bin/compressed.rs" [[bin]] name = "verify-constraints" -path = "bin/verify-constraints.rs" \ No newline at end of file +path = "bin/verify-constraints.rs" + +[[bin]] +name = "ziskemu" +path = "bin/ziskemu.rs" \ No newline at end of file diff --git a/examples/sha-hasher/host/bin/ziskemu.rs b/examples/sha-hasher/host/bin/ziskemu.rs new file mode 100644 index 000000000..4d8154922 --- /dev/null +++ b/examples/sha-hasher/host/bin/ziskemu.rs @@ -0,0 +1,26 @@ +use anyhow::Result; +use std::path::PathBuf; +use zisk_common::ElfBinaryFromFile; +use zisk_common::io::ZiskIO; +use zisk_common::io::ZiskStdin; +use zisk_sdk::{EmuOptions, elf_path, ziskemu}; + +fn main() -> Result<()> { + let elf_path = elf_path!("sha-hasher-guest"); + println!("Loading ELF binary from path: {}", elf_path); + let elf = ElfBinaryFromFile::new(&PathBuf::from(elf_path), false)?; + + let current_dir = std::env::current_dir()?; + let stdin = + ZiskStdin::from_file(current_dir.join("sha-hasher/host/tmp/verify_constraints_input.bin"))?; + + let n: u32 = stdin.read()?; + println!("Input prepared: {} iterations", n); + + println!("Running ZisK Emulator..."); + let emu_options = EmuOptions { stats: true, ..EmuOptions::default() }; + ziskemu(&elf, stdin, &emu_options)?; + println!("ZisK Emulator completed successfully!"); + + Ok(()) +} diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 3b6644843..5d56e2845 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -20,6 +20,8 @@ colored = { workspace = true } tracing = { workspace = true } zstd = { workspace = true } zisk-distributed-common = { workspace = true } +ziskemu = { workspace = true } +zisk-core = { workspace = true } zisk-verifier = { workspace = true } zisk-witness = { workspace = true } sha2 = { workspace = true } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 651187f53..5be49a882 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -4,6 +4,7 @@ mod prover; mod utils; mod verifier; mod zisk_lib_loader; +mod ziskemu; pub use builder::*; pub use client::ProverClient; @@ -11,6 +12,7 @@ pub use prover::*; pub use utils::*; pub use verifier::*; pub use zisk_lib_loader::*; +pub use ziskemu::*; pub struct RankInfo { pub world_rank: i32, diff --git a/sdk/src/ziskemu.rs b/sdk/src/ziskemu.rs new file mode 100644 index 000000000..050a276ce --- /dev/null +++ b/sdk/src/ziskemu.rs @@ -0,0 +1,40 @@ +use std::fmt::Write; +use zisk_common::io::{ZiskIO, ZiskStdin}; +use zisk_common::ElfBinaryLike; +use zisk_core::Riscv2zisk; +pub use ziskemu::EmuOptions; +use ziskemu::ZiskEmulator; + +pub fn ziskemu( + elf: &impl ElfBinaryLike, + stdin: ZiskStdin, + options: &EmuOptions, +) -> anyhow::Result<()> { + let riscv2zisk = Riscv2zisk::new(elf.elf()); + + let zisk_rom = riscv2zisk + .run() + .map_err(|e| anyhow::anyhow!("Failed to convert ELF to ZISK ROM: {e:?}"))?; + + let callback = None::>; + + let inputs = stdin.read_bytes(); + + let options = EmuOptions { log_output: true, ..options.clone() }; + let result = ZiskEmulator::process_rom(&zisk_rom, &inputs, &options, callback); + match result { + Ok(result) => { + // println!("Emulation completed successfully"); + result.iter().fold(String::new(), |mut acc, byte| { + write!(&mut acc, "{byte:02x}").unwrap(); + acc + }); + Ok(()) + // print!("Result: 0x{}", hex_string); + } + Err(e) => { + eprintln!("Error during emulation: {e:?}"); + Err(anyhow::anyhow!("Emulation failed")) + } + } +} diff --git a/tools/emulate_all.sh b/tools/emulate_all.sh index c922a3ccc..8818242f2 100755 --- a/tools/emulate_all.sh +++ b/tools/emulate_all.sh @@ -107,7 +107,7 @@ do echo "Emulating file ${COUNTER} of ${MAX_COUNTER}: ${ELF_FILE}" # Execute it and save output - ./target/debug/ziskemu -e $ELF_FILE -i $INPUT_FILE 2>&1|tee output + ./target/debug/ziskemu -e $ELF_FILE -i $INPUT_FILE -f 2>&1|tee output # Compare output vs reference REFERENCE_FILE=${ELF_FILE%%my.elf}../ref/Reference-sail_c_simulator.signature diff --git a/tools/test-env/test_sha_hasher.sh b/tools/test-env/test_sha_hasher.sh index 12b5c9f27..c185a4bd7 100755 --- a/tools/test-env/test_sha_hasher.sh +++ b/tools/test-env/test_sha_hasher.sh @@ -46,14 +46,14 @@ main() { INPUT_BIN="build/input.bin" step "Running program with ziskemu..." - ensure ziskemu -e "$ELF_PATH" -i "$INPUT_BIN" | tee ziskemu_output.log || return 1 + ensure ziskemu -e "$ELF_PATH" -i "$INPUT_BIN" -f | tee ziskemu_output.log || return 1 if ! grep -qE ${EXPECTED_OUTPUT} ziskemu_output.log; then err "run ziskemu failed" return 1 fi step "Running program with cargo-zisk run..." - ensure cargo-zisk run --release -i build/input.bin | tee run_output.log || return 1 + ensure cargo-zisk run --release -i build/input.bin -f | tee run_output.log || return 1 if ! grep -qE ${EXPECTED_OUTPUT} run_output.log; then err "run program failed" return 1 From ff72dcd45cfda3343ffe26ac4bea2131ba03feaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Wed, 11 Feb 2026 10:45:49 +0100 Subject: [PATCH 485/782] Fix features (#774) --- Cargo.lock | 53 ++++++++++---------------- cli/Cargo.toml | 5 ++- distributed/crates/common/Cargo.toml | 3 -- emulator/Cargo.toml | 4 +- examples/Cargo.lock | 45 ++++++++-------------- examples/sha-hasher/host/Cargo.toml | 4 +- executor/Cargo.toml | 19 ++++++++- pil/Cargo.toml | 4 +- precompiles/arith_eq/Cargo.toml | 7 +--- precompiles/arith_eq_384/Cargo.toml | 7 +--- precompiles/big_int/Cargo.toml | 8 +--- precompiles/dma/Cargo.toml | 8 +--- precompiles/keccakf/Cargo.toml | 8 +--- precompiles/poseidon2/Cargo.toml | 8 +--- precompiles/sha256f/Cargo.toml | 8 +--- rom-setup/Cargo.toml | 5 +-- sdk/Cargo.toml | 8 ++-- state-machines/arith/Cargo.toml | 10 ++--- state-machines/binary/Cargo.toml | 8 +--- state-machines/frequent-ops/Cargo.toml | 8 ++-- state-machines/main/Cargo.toml | 8 +--- state-machines/mem-common/Cargo.toml | 4 -- state-machines/mem/Cargo.toml | 8 +--- state-machines/rom/Cargo.toml | 6 +-- witness-computation/Cargo.toml | 7 +--- 25 files changed, 98 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e743186e..65d2cce18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -888,7 +888,7 @@ dependencies = [ "serde-untagged", "serde_core", "serde_json", - "toml 0.9.11+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", "winnow", "yaml-rust2", ] @@ -1188,7 +1188,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "fields", "num-bigint", @@ -1300,9 +1300,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" dependencies = [ "powerfmt", ] @@ -1641,7 +1641,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "cfg-if", "num-bigint", @@ -2615,7 +2615,6 @@ dependencies = [ "num-bigint", "num-traits", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3061,7 +3060,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "colored", "fields", @@ -3237,7 +3236,6 @@ dependencies = [ "pil-std-lib", "precompiles-common", "precompiles-helpers", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3277,7 +3275,6 @@ dependencies = [ "precomp-arith-eq", "precompiles-common", "precompiles-helpers", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3304,7 +3301,6 @@ dependencies = [ "mem-common", "pil-std-lib", "precompiles-common", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3327,7 +3323,6 @@ dependencies = [ "pil-std-lib", "precompiles-common", "precompiles-helpers", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3349,7 +3344,6 @@ dependencies = [ "pil-std-lib", "precompiles-common", "precompiles-helpers", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3369,7 +3363,6 @@ dependencies = [ "mem-common", "pil-std-lib", "precompiles-common", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3390,7 +3383,6 @@ dependencies = [ "mem-common", "pil-std-lib", "precompiles-common", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3476,7 +3468,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "bincode", "blake3", @@ -3512,7 +3504,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "bincode", "borsh", @@ -3545,7 +3537,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "fields", "itoa", @@ -3558,7 +3550,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "proc-macro2", "quote", @@ -3569,7 +3561,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "crossbeam-channel", "tracing", @@ -3578,7 +3570,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "bincode", "bytemuck", @@ -3592,7 +3584,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "bytemuck", "fields", @@ -4434,7 +4426,6 @@ dependencies = [ "fields", "num-bigint", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -4455,7 +4446,6 @@ dependencies = [ "fields", "num-bigint", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -4476,7 +4466,6 @@ dependencies = [ "fields", "num-bigint", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -4496,7 +4485,6 @@ dependencies = [ "mem-common", "num-bigint", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -4519,7 +4507,6 @@ dependencies = [ "num-bigint", "num-traits", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -4538,7 +4525,6 @@ dependencies = [ "asm-runner", "fields", "itertools 0.14.0", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -4951,9 +4937,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", @@ -5010,9 +4996,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1" dependencies = [ "winnow", ] @@ -6194,7 +6180,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "colored", "fields", @@ -6524,7 +6510,7 @@ dependencies = [ "serde", "tokio", "tokio-stream", - "toml 0.9.11+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", "tonic", "tracing", "uuid", @@ -6559,6 +6545,7 @@ dependencies = [ "asm-runner", "bincode", "colored", + "executor", "fields", "proofman", "proofman-common", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index ab0eb0804..96acb37c8 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -54,8 +54,9 @@ mpi = { workspace = true } [features] default = [] disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] -gpu = ["proofman-common/gpu", "proofman/gpu", "packed"] -packed = ["proofman-common/packed"] +diagnostic = ["proofman/disable_distributed", "proofman-common/disable_distributed"] +gpu = ["zisk-sdk/gpu"] +packed = ["zisk-sdk/packed"] stats = [] verify = ["fields/verify"] diff --git a/distributed/crates/common/Cargo.toml b/distributed/crates/common/Cargo.toml index be6f24ef8..2189ed914 100644 --- a/distributed/crates/common/Cargo.toml +++ b/distributed/crates/common/Cargo.toml @@ -18,6 +18,3 @@ uuid = { version = "1.0", features = ["v4"] } thiserror = "2.0" anyhow = "1.0" chrono = { version = "0.4", features = ["serde"] } - -[features] -gpu = ["proofman/gpu", "proofman-common/gpu"] diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml index 3ed8ad221..fac6e9d2b 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -53,7 +53,7 @@ default = [] debug_stats_trace = [] minimal_trace_index_debug = [] debug_call_stack = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] +gpu = ["packed"] +packed = [] # sp = [] no_lib_link = ["zisk-pil/no_lib_link"] diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 6e076d39d..4e242f932 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -845,7 +845,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "fields", "num-bigint", @@ -943,9 +943,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" dependencies = [ "powerfmt", ] @@ -1208,7 +1208,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "cfg-if", "num-bigint", @@ -1827,7 +1827,6 @@ dependencies = [ "num-bigint", "num-traits", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -2152,7 +2151,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "colored", "fields", @@ -2251,7 +2250,6 @@ dependencies = [ "pil-std-lib", "precompiles-common", "precompiles-helpers", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -2291,7 +2289,6 @@ dependencies = [ "precomp-arith-eq", "precompiles-common", "precompiles-helpers", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -2318,7 +2315,6 @@ dependencies = [ "mem-common", "pil-std-lib", "precompiles-common", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -2341,7 +2337,6 @@ dependencies = [ "pil-std-lib", "precompiles-common", "precompiles-helpers", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -2363,7 +2358,6 @@ dependencies = [ "pil-std-lib", "precompiles-common", "precompiles-helpers", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -2383,7 +2377,6 @@ dependencies = [ "mem-common", "pil-std-lib", "precompiles-common", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -2404,7 +2397,6 @@ dependencies = [ "mem-common", "pil-std-lib", "precompiles-common", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -2489,7 +2481,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "bincode", "blake3", @@ -2525,7 +2517,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "bincode", "borsh", @@ -2558,7 +2550,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "fields", "itoa", @@ -2571,7 +2563,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "proc-macro2", "quote", @@ -2582,7 +2574,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "crossbeam-channel", "tracing", @@ -2591,7 +2583,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "bincode", "bytemuck", @@ -2605,7 +2597,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "bytemuck", "fields", @@ -3247,7 +3239,6 @@ dependencies = [ "fields", "num-bigint", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3268,7 +3259,6 @@ dependencies = [ "fields", "num-bigint", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3289,7 +3279,6 @@ dependencies = [ "fields", "num-bigint", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3309,7 +3298,6 @@ dependencies = [ "mem-common", "num-bigint", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3332,7 +3320,6 @@ dependencies = [ "num-bigint", "num-traits", "pil-std-lib", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3351,7 +3338,6 @@ dependencies = [ "asm-runner", "fields", "itertools 0.14.0", - "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3747,9 +3733,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1" dependencies = [ "winnow", ] @@ -4716,7 +4702,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#dc38c9cc889dff0bcc8e4080c67d0f2b8cd282f8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" dependencies = [ "colored", "fields", @@ -4984,6 +4970,7 @@ dependencies = [ "asm-runner", "bincode", "colored", + "executor", "fields", "proofman", "proofman-common", diff --git a/examples/sha-hasher/host/Cargo.toml b/examples/sha-hasher/host/Cargo.toml index e7b4fcd00..4cfb1092f 100644 --- a/examples/sha-hasher/host/Cargo.toml +++ b/examples/sha-hasher/host/Cargo.toml @@ -16,8 +16,8 @@ zisk-common = { workspace = true } [features] default = [] -packed = ["zisk-sdk/packed"] -gpu = ["zisk-sdk/gpu", "packed"] +packed = [] +gpu = ["zisk-sdk/gpu"] [[bin]] name = "prove" diff --git a/executor/Cargo.toml b/executor/Cargo.toml index d4cc55f6f..dd04a23d9 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -53,6 +53,21 @@ named-sem = { workspace = true } [features] default = [] disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] -gpu = ["proofman-common/gpu", "proofman/gpu" ] -packed = ["proofman-common/packed"] +gpu = ["proofman-common/gpu", "proofman/gpu", "packed"] +packed = [ + "proofman-common/packed", + "precomp-keccakf/packed", + "precomp-sha256f/packed", + "precomp-poseidon2/packed", + "precomp-arith-eq/packed", + "precomp-arith-eq-384/packed", + "precomp-big-int/packed", + "precomp-dma/packed", + "sm-arith/packed", + "sm-binary/packed", + "sm-main/packed", + "sm-mem/packed", + "sm-frequent-ops/packed", + "sm-rom/packed", +] stats = [] diff --git a/pil/Cargo.toml b/pil/Cargo.toml index e386c08db..2182e4ab3 100644 --- a/pil/Cargo.toml +++ b/pil/Cargo.toml @@ -20,6 +20,4 @@ serde_arrays = "0.2" [features] default = [] dev = [] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file +no_lib_link = ["proofman-common/no_lib_link"] \ No newline at end of file diff --git a/precompiles/arith_eq/Cargo.toml b/precompiles/arith_eq/Cargo.toml index 7a3b81b60..be8543698 100644 --- a/precompiles/arith_eq/Cargo.toml +++ b/precompiles/arith_eq/Cargo.toml @@ -43,7 +43,6 @@ precompiles-helpers = { workspace = true } zisk-common = { workspace = true } lib-c = { workspace = true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -75,10 +74,8 @@ nom = "7" [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -no_lib_link = ["proofman-common/no_lib_link"] +gpu = ["packed"] +packed = [] test_data = [] test_data_secp256k1 = [] test_data_secp256r1 = [] \ No newline at end of file diff --git a/precompiles/arith_eq_384/Cargo.toml b/precompiles/arith_eq_384/Cargo.toml index d97145c3c..7f86ce6e1 100644 --- a/precompiles/arith_eq_384/Cargo.toml +++ b/precompiles/arith_eq_384/Cargo.toml @@ -30,7 +30,6 @@ precomp-arith-eq = { workspace = true } zisk-common = { workspace = true } lib-c = { workspace = true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -61,8 +60,6 @@ nom = "7" [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -no_lib_link = ["proofman-common/no_lib_link"] +gpu = ["packed"] +packed = [] test_data = [] \ No newline at end of file diff --git a/precompiles/big_int/Cargo.toml b/precompiles/big_int/Cargo.toml index 9b7aaa0ba..9ddbe5bfd 100644 --- a/precompiles/big_int/Cargo.toml +++ b/precompiles/big_int/Cargo.toml @@ -16,7 +16,6 @@ sm-mem = { workspace = true } mem-common = { workspace = true } lib-c = { workspace = true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -28,8 +27,5 @@ generic-array = "0.14" [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file +gpu = ["packed"] +packed = [] \ No newline at end of file diff --git a/precompiles/dma/Cargo.toml b/precompiles/dma/Cargo.toml index 7c197b12b..772dc2cf8 100644 --- a/precompiles/dma/Cargo.toml +++ b/precompiles/dma/Cargo.toml @@ -17,7 +17,6 @@ sm-mem = { workspace = true } mem-common = { workspace = true } lib-c = { workspace = true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -31,8 +30,5 @@ generic-array = "0.14" default = [] dma_memcmp = [] debug_dma = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file +gpu = ["packed"] +packed = [] \ No newline at end of file diff --git a/precompiles/keccakf/Cargo.toml b/precompiles/keccakf/Cargo.toml index 15f3250c2..731b61df2 100644 --- a/precompiles/keccakf/Cargo.toml +++ b/precompiles/keccakf/Cargo.toml @@ -16,7 +16,6 @@ zisk-core = { workspace = true } zisk-common = { workspace = true } zisk-pil = { workspace = true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -38,8 +37,5 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] } [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] +gpu = ["packed"] +packed = [] \ No newline at end of file diff --git a/precompiles/poseidon2/Cargo.toml b/precompiles/poseidon2/Cargo.toml index 788c87d88..7291a99a9 100644 --- a/precompiles/poseidon2/Cargo.toml +++ b/precompiles/poseidon2/Cargo.toml @@ -15,7 +15,6 @@ precompiles-common = { workspace = true } sm-mem = { workspace = true } mem-common = { workspace = true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -27,8 +26,5 @@ sha2 = { workspace = true } [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file +gpu = ["packed"] +packed = [] \ No newline at end of file diff --git a/precompiles/sha256f/Cargo.toml b/precompiles/sha256f/Cargo.toml index 7e666cb27..205f81f56 100644 --- a/precompiles/sha256f/Cargo.toml +++ b/precompiles/sha256f/Cargo.toml @@ -15,7 +15,6 @@ precompiles-common = { workspace = true } sm-mem = { workspace = true } mem-common = { workspace = true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -26,8 +25,5 @@ rayon = { workspace = true } [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file +gpu = ["packed"] +packed = [] \ No newline at end of file diff --git a/rom-setup/Cargo.toml b/rom-setup/Cargo.toml index 6dc313989..19a546f5c 100644 --- a/rom-setup/Cargo.toml +++ b/rom-setup/Cargo.toml @@ -22,6 +22,5 @@ blake3 = "1.3.1" [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -no_lib_link = ["proofman-common/no_lib_link"] +gpu = ["packed"] +packed = [] diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 5d56e2845..cf14eba21 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -20,6 +20,7 @@ colored = { workspace = true } tracing = { workspace = true } zstd = { workspace = true } zisk-distributed-common = { workspace = true } +executor = { workspace = true } ziskemu = { workspace = true } zisk-core = { workspace = true } zisk-verifier = { workspace = true } @@ -33,11 +34,12 @@ default = [] gpu = [ "proofman/gpu", "proofman-common/gpu", - "zisk-distributed-common/gpu", "rom-setup/gpu", - "packed", + "executor/gpu", + "ziskemu/gpu", + "packed" ] -packed = ["proofman-common/packed"] +packed = ["proofman-common/packed", "executor/packed", "zisk-witness/packed"] stats = [] disable_distributed = [ "proofman/disable_distributed", diff --git a/state-machines/arith/Cargo.toml b/state-machines/arith/Cargo.toml index 3831c52b7..8e89cddfb 100644 --- a/state-machines/arith/Cargo.toml +++ b/state-machines/arith/Cargo.toml @@ -19,7 +19,6 @@ sm-binary = { workspace = true } sm-frequent-ops = { workspace = true } fields = { workspace=true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -33,9 +32,6 @@ static_assertions = { workspace = true } [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -generate_code_arith_range_table = [] -no_lib_link = ["proofman-common/no_lib_link"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file +gpu = ["packed"] +packed = [] +generate_code_arith_range_table = [] \ No newline at end of file diff --git a/state-machines/binary/Cargo.toml b/state-machines/binary/Cargo.toml index e3339a688..a1559fc95 100644 --- a/state-machines/binary/Cargo.toml +++ b/state-machines/binary/Cargo.toml @@ -22,7 +22,6 @@ zisk-pil = { workspace = true } sm-frequent-ops = { workspace = true } fields = { workspace=true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -35,8 +34,5 @@ static_assertions = { workspace = true } [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] +gpu = ["packed"] +packed = [] diff --git a/state-machines/frequent-ops/Cargo.toml b/state-machines/frequent-ops/Cargo.toml index 718b0d92a..aeb69a360 100644 --- a/state-machines/frequent-ops/Cargo.toml +++ b/state-machines/frequent-ops/Cargo.toml @@ -17,7 +17,6 @@ zisk-common = { workspace = true } zisk-pil = { workspace = true } fields = { workspace=true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -31,7 +30,6 @@ static_assertions = { workspace = true } [features] default = [] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -generate_code_arith_range_table = [] -no_lib_link = ["proofman-common/no_lib_link"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file +gpu = ["packed"] +packed = [] +generate_code_arith_range_table = [] \ No newline at end of file diff --git a/state-machines/main/Cargo.toml b/state-machines/main/Cargo.toml index ca06a642d..dcd1f455f 100644 --- a/state-machines/main/Cargo.toml +++ b/state-machines/main/Cargo.toml @@ -15,7 +15,6 @@ zisk-pil = { workspace = true } sm-mem = { workspace = true } mem-common = { workspace = true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-util = { workspace = true } proofman-macros = { workspace = true } @@ -27,8 +26,5 @@ pil-std-lib = { workspace = true } [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] +gpu = ["packed"] +packed = [] diff --git a/state-machines/mem-common/Cargo.toml b/state-machines/mem-common/Cargo.toml index b4703285f..d16d27ba6 100644 --- a/state-machines/mem-common/Cargo.toml +++ b/state-machines/mem-common/Cargo.toml @@ -18,7 +18,6 @@ zisk-pil = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } -proofman = { workspace = true } proofman-util = { workspace = true } witness = { workspace = true } pil-std-lib = { workspace = true } @@ -32,9 +31,6 @@ num-traits = "0.2" [features] default = [] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] debug_mem = [] debug_mem_align = [] save_mem_bus_data = [] diff --git a/state-machines/mem/Cargo.toml b/state-machines/mem/Cargo.toml index 75169f396..f704382cc 100644 --- a/state-machines/mem/Cargo.toml +++ b/state-machines/mem/Cargo.toml @@ -18,7 +18,6 @@ zisk-pil = { workspace = true } mem-planner-cpp = { workspace = true } mem-common = { workspace = true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -34,11 +33,8 @@ num-traits = "0.2" [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] +gpu = ["packed"] +packed = [] debug_mem = [] debug_mem_align = [] save_mem_bus_data = [] diff --git a/state-machines/rom/Cargo.toml b/state-machines/rom/Cargo.toml index ed4653957..597ad69cd 100644 --- a/state-machines/rom/Cargo.toml +++ b/state-machines/rom/Cargo.toml @@ -14,7 +14,6 @@ zisk-pil = { workspace = true } asm-runner = { workspace = true } fields = { workspace=true } -proofman = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } @@ -25,6 +24,5 @@ itertools = { workspace = true } [features] default = [] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] +gpu = ["packed"] +packed = [] \ No newline at end of file diff --git a/witness-computation/Cargo.toml b/witness-computation/Cargo.toml index ab311177f..d9e0b67a0 100644 --- a/witness-computation/Cargo.toml +++ b/witness-computation/Cargo.toml @@ -47,8 +47,5 @@ rayon = { workspace = true } [features] default = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed", "proofman/packed"] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file +gpu = ["packed"] +packed = [] \ No newline at end of file From f49cd8577c07b84642f3aa289c7f1f0cc9e39201 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 11 Feb 2026 12:04:15 +0100 Subject: [PATCH 486/782] fix critical dma typos on pil --- pil/src/pil_helpers/traces.rs | 2 +- precompiles/dma/pil/dma_64_aligned.pil | 2 +- precompiles/dma/pil/dma_unaligned.pil | 17 ++++++++--------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index d03c7cfd2..c3aa3d09a 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "714c5bc49f0ec815cfed6956d9919523e8fef7142ba6edf74c433e7dedf273b7"; +pub const PILOUT_HASH: &str = "1e049cc2bf07c7cb06f032528ca43e3363ca78452f0ed5e01ac03ad760c04a34"; pub const MERKLE_TREE_ARITY: u64 = 4; diff --git a/precompiles/dma/pil/dma_64_aligned.pil b/precompiles/dma/pil/dma_64_aligned.pil index 973bab77d..2d202f19d 100644 --- a/precompiles/dma/pil/dma_64_aligned.pil +++ b/precompiles/dma/pil/dma_64_aligned.pil @@ -177,7 +177,7 @@ airtemplate Dma64Aligned(int N = 2**21, const int RC = 2, const int op_x_row = 4 // // If not finish sequence in previous row, means current it's a continuation of previous sequence // count match with previous value plus ops_in_row. - continue_seq_on_l1 * (count - (segment_previous_count - op_x_row)) == 0; + continue_seq_on_l1 * (count - (segment_previous_count - op_x_row)) === 0; continue_seq_on_no_l1 * (count - ('count - op_x_row)) === 0; // If finish sequence count match with ops_in_row, because final count must be 0. diff --git a/precompiles/dma/pil/dma_unaligned.pil b/precompiles/dma/pil/dma_unaligned.pil index c2159613e..241f099cb 100644 --- a/precompiles/dma/pil/dma_unaligned.pil +++ b/precompiles/dma/pil/dma_unaligned.pil @@ -1,6 +1,4 @@ -// TODO: Optimization Dma32AlignedOp - airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) { const expr L1 = get_L1(); @@ -121,7 +119,7 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) (1 - segment_previous_seq_end) * L1 * (segment_first_bytes[i] - read_bytes[i]) === 0; // force segment_next_bytes to zero, when it's finish of input - segment_last_seq_end * segment_next_bytes[i] == 0; + segment_last_seq_end * segment_next_bytes[i] === 0; // to force the "received" values from the bus are 0 when the last row of the previous segment // is final of input (seq_end). @@ -178,13 +176,13 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) // src64 = 'src64 + 1 // dst64 = 'dst64 + 1 - continue_seq_on_l1 * (count - (segment_previous_count - 1)) == 0; + continue_seq_on_l1 * (count - (segment_previous_count - 1)) === 0; continue_seq_on_no_l1 * (count - ('count - 1)) === 0; - continue_seq_on_l1 * (src64 - (segment_previous_src64 + 1)) == 0; + continue_seq_on_l1 * (src64 - (segment_previous_src64 + 1)) === 0; continue_seq_on_no_l1 * (src64 - ('src64 + 1)) === 0; - continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + 1)) == 0; + continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + 1)) === 0; continue_seq_on_no_l1 * (dst64 - ('dst64 + 1)) === 0; // LATCHS: @@ -192,13 +190,14 @@ airtemplate DmaUnaligned(int N = 2**21, const int RC = 2, const expr enable = 1) // offset = 'offset // is_mem_eq = 'is_mem_eq // main_step = 'main_step - continue_seq_on_l1 * (offset - segment_previous_offset) == 0; + + continue_seq_on_l1 * (offset - segment_previous_offset) === 0; continue_seq_on_no_l1 * (offset - 'offset) === 0; - continue_seq_on_l1 * (is_mem_eq - segment_previous_is_mem_eq) == 0; + continue_seq_on_l1 * (is_mem_eq - segment_previous_is_mem_eq) === 0; continue_seq_on_no_l1 * (is_mem_eq - 'is_mem_eq) === 0; - continue_seq_on_l1 * (main_step - segment_previous_main_step) == 0; + continue_seq_on_l1 * (main_step - segment_previous_main_step) === 0; continue_seq_on_no_l1 * (main_step - 'main_step) === 0; // At end of sequence From f9c0c49e0fde33d55988981ceb608e501254c303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Wed, 11 Feb 2026 12:24:56 +0100 Subject: [PATCH 487/782] Fix logger (#777) --- Cargo.lock | 22 +++++++++++----------- cli/src/commands/check_setup.rs | 4 ++-- cli/src/commands/clean.rs | 5 +++-- cli/src/commands/rom_setup.rs | 7 +++---- cli/src/commands/verify_snark.rs | 4 ++-- cli/src/commands/verify_stark.rs | 7 ++++--- common/src/utils.rs | 24 ------------------------ distributed/crates/common/src/tracing.rs | 8 +++++--- sdk/src/lib.rs | 5 +---- sdk/src/prover/asm.rs | 15 +++++++-------- sdk/src/prover/backend.rs | 12 +++++------- sdk/src/prover/emu.rs | 20 +++++++------------- sdk/src/utils.rs | 9 ++++++++- witness-computation/src/zisk_lib.rs | 23 ++++++++++++----------- 14 files changed, 70 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65d2cce18..95b568436 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1188,7 +1188,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" dependencies = [ "fields", "num-bigint", @@ -1641,7 +1641,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" dependencies = [ "cfg-if", "num-bigint", @@ -3060,7 +3060,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" dependencies = [ "colored", "fields", @@ -3468,7 +3468,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" dependencies = [ "bincode", "blake3", @@ -3504,7 +3504,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" dependencies = [ "bincode", "borsh", @@ -3537,7 +3537,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" dependencies = [ "fields", "itoa", @@ -3550,7 +3550,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" dependencies = [ "proc-macro2", "quote", @@ -3561,7 +3561,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" dependencies = [ "crossbeam-channel", "tracing", @@ -3570,7 +3570,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" dependencies = [ "bincode", "bytemuck", @@ -3584,7 +3584,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" dependencies = [ "bytemuck", "fields", @@ -6180,7 +6180,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" dependencies = [ "colored", "fields", diff --git a/cli/src/commands/check_setup.rs b/cli/src/commands/check_setup.rs index 2e177a134..12f27d134 100644 --- a/cli/src/commands/check_setup.rs +++ b/cli/src/commands/check_setup.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; use fields::Goldilocks; use proofman::{check_setup_snark, ProofMan}; -use proofman_common::initialize_logger; +use zisk_sdk::setup_logger; #[derive(Parser)] #[command(version, about, long_about = None)] @@ -38,7 +38,7 @@ impl ZiskCheckSetup { println!("{} CheckSetup", format!("{: >12}", "Command").bright_green().bold()); println!(); - initialize_logger(self.verbose.into(), None); + setup_logger(self.verbose.into()); ProofMan::::check_setup( get_proving_key(self.proving_key.as_ref()), diff --git a/cli/src/commands/clean.rs b/cli/src/commands/clean.rs index 4595131eb..d4eca382a 100644 --- a/cli/src/commands/clean.rs +++ b/cli/src/commands/clean.rs @@ -4,7 +4,8 @@ use clap::Parser; use colored::Colorize; use anyhow::{Context, Result}; -use proofman_common::initialize_logger; +use proofman_common::VerboseMode; +use zisk_sdk::setup_logger; use crate::{commands::get_home_zisk_path, ux::print_banner}; @@ -15,7 +16,7 @@ pub struct ZiskClean; impl ZiskClean { pub fn run(&self) -> Result<()> { - initialize_logger(proofman_common::VerboseMode::Info, None); + setup_logger(VerboseMode::Info); print_banner(); tracing::info!( diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index a8e3bff46..9418b1299 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -5,13 +5,12 @@ use std::path::PathBuf; use crate::{commands::get_proving_key, ux::print_banner}; use colored::Colorize; use fields::Goldilocks; -use proofman_common::{ - initialize_logger, MpiCtx, ParamsGPU, ProofCtx, ProofType, SetupCtx, SetupsVadcop, -}; +use proofman_common::{MpiCtx, ParamsGPU, ProofCtx, ProofType, SetupCtx, SetupsVadcop}; use rom_setup::gen_assembly; use rom_setup::rom_merkle_setup; use std::sync::Arc; use zisk_common::ElfBinaryFromFile; +use zisk_sdk::setup_logger; #[derive(Parser)] #[command(version, about, long_about = None)] @@ -43,7 +42,7 @@ pub struct ZiskRomSetup { impl ZiskRomSetup { pub fn run(&self) -> Result<()> { - initialize_logger(proofman_common::VerboseMode::Info, None); + setup_logger(self.verbose.into()); tracing::info!( "{}", diff --git a/cli/src/commands/verify_snark.rs b/cli/src/commands/verify_snark.rs index ac0b62d8f..170486bc6 100644 --- a/cli/src/commands/verify_snark.rs +++ b/cli/src/commands/verify_snark.rs @@ -3,8 +3,8 @@ use anyhow::Result; use clap::Parser; use colored::Colorize; use proofman::{verify_snark_proof, SnarkProof}; -use proofman_common::initialize_logger; use std::path::PathBuf; +use zisk_sdk::setup_logger; #[derive(Parser)] #[command(version, about, long_about = None)] @@ -26,7 +26,7 @@ impl ZiskVerifySnark { println!("{} ZiskVerifySnark", format!("{: >12}", "Command").bright_green().bold()); println!(); - initialize_logger(self.verbose.into(), None); + setup_logger(self.verbose.into()); let proof = SnarkProof::load(&self.proof).map_err(|e| { anyhow::anyhow!("Failed to load SnarkProof from file {}: {}", self.proof, e) diff --git a/cli/src/commands/verify_stark.rs b/cli/src/commands/verify_stark.rs index 9cf994c49..485b2b485 100644 --- a/cli/src/commands/verify_stark.rs +++ b/cli/src/commands/verify_stark.rs @@ -1,10 +1,11 @@ use anyhow::Result; use clap::Parser; use colored::Colorize; -use proofman_common::initialize_logger; use std::path::PathBuf; use zisk_build::ZISK_VERSION_MESSAGE; -use zisk_sdk::{get_proving_key, verify_zisk_proof_with_proving_key, ZiskProofWithPublicValues}; +use zisk_sdk::{ + get_proving_key, setup_logger, verify_zisk_proof_with_proving_key, ZiskProofWithPublicValues, +}; #[derive(Parser)] #[command(author, about, long_about = None, version = ZISK_VERSION_MESSAGE)] @@ -23,7 +24,7 @@ pub struct ZiskVerify { impl ZiskVerify { pub fn run(&self) -> Result<()> { - initialize_logger(self.verbose.into(), None); + setup_logger(self.verbose.into()); tracing::info!( "{}", diff --git a/common/src/utils.rs b/common/src/utils.rs index 938f367b0..25a2d4d7c 100644 --- a/common/src/utils.rs +++ b/common/src/utils.rs @@ -1,6 +1,4 @@ -use std::fs::OpenOptions; use std::mem::MaybeUninit; -use tracing_subscriber::{filter::FilterFn, fmt, prelude::*, EnvFilter}; pub fn create_atomic_vec
(size: usize) -> Vec
{ let mut vec: Vec> = Vec::with_capacity(size); @@ -54,28 +52,6 @@ macro_rules! error_file { }; } -pub fn init_tracing(log_path: &str) { - let file = OpenOptions::new() - .append(true) - .create(true) - .open(log_path) - .expect("Failed to open log file"); - - let file_layer = fmt::layer() - .with_writer(file) - .with_ansi(false) // no color in file - .with_target(false) - .with_filter(FilterFn::new(|meta| meta.target() == "screen_and_file")); - - let stdout_layer = fmt::layer().with_writer(std::io::stdout).with_ansi(true).with_target(false); - - tracing_subscriber::registry() - .with(EnvFilter::from_default_env().add_directive("info".parse().unwrap())) - .with(stdout_layer) - .with(file_layer) - .init(); -} - /// Reinterprets a `Vec` as a `Vec` by transmuting the underlying memory. /// /// This function converts between vector types by reinterpreting the raw memory, diff --git a/distributed/crates/common/src/tracing.rs b/distributed/crates/common/src/tracing.rs index 7c1b1db1f..a4aebaf69 100644 --- a/distributed/crates/common/src/tracing.rs +++ b/distributed/crates/common/src/tracing.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use proofman_common::set_global_rank; +use proofman_common::{set_global_rank, RankInfo}; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display}; use tracing_subscriber::{ @@ -68,7 +68,7 @@ impl fmt::Display for LogFormat { /// Returns `Ok(None)` if only console logging is configured. pub fn init( logging_config: Option<&LoggingConfig>, - rank: Option, + rank: Option<&RankInfo>, ) -> Result> { // Prioritize logging_config values over environment variables let log_level = @@ -97,7 +97,9 @@ pub fn init( } if let Some(r) = rank { - set_global_rank(r); + if r.n_processes > 1 { + set_global_rank(r.world_rank); + } } // Apply console logging with optional file logging diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 5be49a882..cd50867f4 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -14,10 +14,7 @@ pub use verifier::*; pub use zisk_lib_loader::*; pub use ziskemu::*; -pub struct RankInfo { - pub world_rank: i32, - pub local_rank: i32, -} +pub use proofman_common::VerboseMode; #[macro_export] macro_rules! include_elf { diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index fdc380a31..eb7ffeef2 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -2,14 +2,14 @@ use crate::get_asm_paths; use crate::{ check_paths_exist, ensure_custom_commits, prover::{ProverBackend, ProverEngine, ZiskBackend}, - RankInfo, ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, ZiskProgramVK, + ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, ZiskProgramVK, ZiskProof, ZiskProofWithPublicValues, ZiskProveResult, ZiskPublics, ZiskVerifyConstraintsResult, }; use crate::{ProofMode, ProofOpts}; use asm_runner::{AsmRunnerOptions, AsmServices}; use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; -use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, VerboseMode}; +use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, VerboseMode}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rom_setup::DEFAULT_CACHE_PATH; use std::sync::OnceLock; @@ -297,13 +297,12 @@ impl AsmCoreProver { ) .map_err(|e| anyhow::anyhow!(e.to_string()))?; - let world_rank = proofman.get_world_rank(); - let local_rank = proofman.get_local_rank(); + let rank_info = proofman.get_rank_info(); if logging_config.is_some() { - zisk_distributed_common::init(logging_config.as_ref(), Some(world_rank))?; + zisk_distributed_common::init(logging_config.as_ref(), Some(&rank_info))?; } else { - initialize_logger(verbose.into(), Some(world_rank)); + initialize_logger(verbose.into(), Some(&rank_info)); } proofman.set_barrier(); @@ -320,7 +319,7 @@ impl AsmCoreProver { Ok(Self { backend: core, asm_services: OnceLock::new(), - rank_info: RankInfo { world_rank, local_rank }, + rank_info, verbose: verbose.into(), shared_tables, base_port, @@ -335,7 +334,7 @@ impl AsmCoreProver { Ok(Self { backend: core_prover, asm_services: OnceLock::new(), - rank_info: RankInfo { world_rank: 0, local_rank: 0 }, + rank_info: RankInfo { world_rank: 0, local_rank: 0, n_processes: 1 }, verbose: VerboseMode::Info, shared_tables: false, base_port: None, diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 449142377..e3c29ce47 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -175,14 +175,12 @@ impl ProverBackend { witness_lib.set_stdin(stdin); - let world_rank = proofman.get_world_rank(); - let local_rank = proofman.get_local_rank(); - let n_processes = proofman.get_n_processes(); + let rank_info = proofman.get_rank_info(); let mut is_active = true; if let Some(mpi_node) = _mpi_node { - if local_rank != mpi_node as i32 { + if rank_info.local_rank != mpi_node as i32 { is_active = false; } } @@ -192,11 +190,11 @@ impl ProverBackend { if !is_active { println!( "{}: {}", - format!("Rank {local_rank}").bright_yellow().bold(), + format!("Rank {}", rank_info.local_rank).bright_yellow().bold(), "Inactive rank, skipping computation.".bright_yellow() ); - return Ok((world_rank, n_processes, None)); + return Ok((rank_info.world_rank, rank_info.n_processes, None)); } proofman @@ -211,7 +209,7 @@ impl ProverBackend { anyhow::anyhow!("Failed to get execution result from emulator prover") })?; - Ok((world_rank, n_processes, Some(stats))) + Ok((rank_info.world_rank, rank_info.n_processes, Some(stats))) } pub(crate) fn verify_constraints_debug( diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 018ebb311..19bb1cb72 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -1,13 +1,13 @@ use crate::{ check_paths_exist, prover::{ProverBackend, ProverEngine, ZiskBackend}, - RankInfo, ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, ZiskProgramVK, + ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, ZiskProgramVK, ZiskProof, ZiskProofWithPublicValues, ZiskProveResult, ZiskPublics, ZiskVerifyConstraintsResult, }; use crate::{ensure_custom_commits, ProofMode, ProofOpts}; use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; -use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, VerboseMode}; +use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, VerboseMode}; use std::collections::HashMap; use std::path::PathBuf; use zisk_common::io::{StreamSource, ZiskStdin}; @@ -231,13 +231,12 @@ impl EmuCoreProver { ) .map_err(|e| anyhow::anyhow!(e.to_string()))?; - let world_rank = proofman.get_world_rank(); - let local_rank = proofman.get_local_rank(); + let rank_info = proofman.get_rank_info(); if logging_config.is_some() { - zisk_distributed_common::init(logging_config.as_ref(), Some(world_rank))?; + zisk_distributed_common::init(logging_config.as_ref(), Some(&rank_info))?; } else { - initialize_logger(verbose.into(), Some(world_rank)); + initialize_logger(verbose.into(), Some(&rank_info)); } proofman.set_barrier(); @@ -251,12 +250,7 @@ impl EmuCoreProver { let core = ProverBackend::new(proofman, snark_wrapper, proving_key, Some(proving_key_snark)); - Ok(Self { - backend: core, - rank_info: RankInfo { world_rank, local_rank }, - verbose: verbose.into(), - shared_tables, - }) + Ok(Self { backend: core, rank_info, verbose: verbose.into(), shared_tables }) } #[allow(clippy::too_many_arguments)] @@ -265,7 +259,7 @@ impl EmuCoreProver { Ok(Self { backend: core_prover, - rank_info: RankInfo { world_rank: 0, local_rank: 0 }, + rank_info: RankInfo { world_rank: 0, local_rank: 0, n_processes: 1 }, verbose: VerboseMode::Info, shared_tables: false, }) diff --git a/sdk/src/utils.rs b/sdk/src/utils.rs index eeab25582..5dfa1f30f 100644 --- a/sdk/src/utils.rs +++ b/sdk/src/utils.rs @@ -5,7 +5,10 @@ use std::path::PathBuf; use anyhow::Result; -use proofman_common::{json_to_debug_instances_map, DebugInfo, ProofCtx, ProofmanResult}; +use proofman_common::{ + initialize_logger, json_to_debug_instances_map, DebugInfo, ProofCtx, ProofmanResult, + VerboseMode, +}; use rom_setup::{get_elf_data_hash, rom_merkle_setup}; use zisk_common::ElfBinaryLike; @@ -117,3 +120,7 @@ pub fn create_debug_info( Some(Some(debug_value)) => json_to_debug_instances_map(proving_key, debug_value.clone()), } } + +pub fn setup_logger(verbose: VerboseMode) { + initialize_logger(verbose, None); +} diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index b60421c5f..0481b30e6 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -113,10 +113,7 @@ impl WitnessLib { pub fn register_witness(&mut self, elf: &[u8], wcm: &WitnessManager) -> ProofmanResult<()> { assert_eq!(self.asm_mt_path.is_some(), self.asm_rh_path.is_some()); - let world_rank = wcm.get_world_rank(); - let local_rank = wcm.get_local_rank(); - - proofman_common::initialize_logger(self.verbose_mode, Some(world_rank)); + let rank_info = wcm.get_rank_info(); // Step 1: Create an instance of the RISCV -> ZisK program converter let rv2zk = Riscv2zisk::new(elf); @@ -209,8 +206,8 @@ impl WitnessLib { debug!("Using ASM emulator"); EmulatorKind::Asm(EmulatorAsm::new( zisk_rom.clone(), - world_rank, - local_rank, + rank_info.world_rank, + rank_info.local_rank, self.base_port, self.unlock_mapped_memory, self.chunk_size, @@ -227,17 +224,21 @@ impl WitnessLib { const USE_SHARED_MEMORY_HINTS: bool = true; let hints_processor = if USE_SHARED_MEMORY_HINTS { - let hints_shmem = - HintsShmem::new(self.base_port, local_rank, self.unlock_mapped_memory) - .expect("zisk_lib: Failed to create HintsShmem"); + let hints_shmem = HintsShmem::new( + self.base_port, + rank_info.local_rank, + self.unlock_mapped_memory, + ) + .expect("zisk_lib: Failed to create HintsShmem"); HintsProcessor::builder(hints_shmem) .enable_stats(self.verbose_mode != proofman_common::VerboseMode::Info) .build() .expect("zisk_lib: Failed to create PrecompileHintsProcessor") } else { - let hints_file = HintsFile::new(format!("hints_results_{}.bin", local_rank)) - .expect("zisk_lib: Failed to create HintsFile"); + let hints_file = + HintsFile::new(format!("hints_results_{}.bin", rank_info.local_rank)) + .expect("zisk_lib: Failed to create HintsFile"); HintsProcessor::builder(hints_file) .enable_stats(self.verbose_mode != proofman_common::VerboseMode::Info) From aac554cb93079b0df48962a180ef78331c5e3b92 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 9 Feb 2026 11:03:02 +0000 Subject: [PATCH 488/782] removed parsing hint from unaligned u64 slice --- precompiles/hints/src/hints_processor.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index f81acad00..55def766a 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -244,23 +244,18 @@ impl HintsProcessor { /// * `Ok(false)` - Batch processed successfully, no CTRL_END /// * `Err` - If a previous error occurred or hints are malformed pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { - const ALLOW_UNALIGNED: bool = false; - const HEADER_SIZE: usize = if ALLOW_UNALIGNED { 8 } else { 1 }; + const HEADER_SIZE: usize = 1; let mut has_ctrl_end = false; // Parse hints and dispatch to pool let mut idx = 0; - while idx < hints.len() * HEADER_SIZE { + while idx < hints.len() { // Check for error before processing each hint if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to previous error")); } - let hint = if ALLOW_UNALIGNED { - PrecompileHint::from_unaligned_u64_slice(hints, idx, true)? - } else { - PrecompileHint::from_u64_slice(hints, idx, true)? - }; + let hint = PrecompileHint::from_u64_slice(hints, idx, true)?; // println!("Received Hint <= {:?}:", hint); @@ -278,8 +273,7 @@ impl HintsProcessor { } } - let length = - if ALLOW_UNALIGNED { hint.data_len_bytes } else { hint.data.len() } + HEADER_SIZE; + let length = hint.data.len() + HEADER_SIZE; if let Some(stats) = &self.stats { if !matches!(hint.hint_code, HintCode::Ctrl(_)) { From ff9714c10af024c740a62f4b9f9b0edbcda37725 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 9 Feb 2026 14:03:14 +0000 Subject: [PATCH 489/782] allowed partial hints in hits_processor and hints_relay --- common/src/hints.rs | 258 ++++++++++++------ .../crates/coordinator/src/hints_relay.rs | 39 ++- precompiles/hints/src/hints_processor.rs | 59 +++- 3 files changed, 256 insertions(+), 100 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 14a172e82..097c3e822 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -347,6 +347,73 @@ impl HintCode { } } +/// Represents a partially received hint when the slice doesn't contain all data. +/// +/// This is returned when the hint header has been parsed but there isn't enough +/// data in the slice to complete the hint. +#[derive(Debug, Clone)] +pub struct PartialPrecompileHint { + /// The type of hint, determining how the data should be processed. + pub hint_code: HintCode, + /// Whether this hint contains pass-through data (true) or requires computation (false). + pub is_passthrough: bool, + /// The partial hint payload data received so far. + pub data: Vec, + /// Total data length in bytes expected for this hint. + pub expected_len_bytes: usize, + /// Number of u64s still needed to complete the hint. + pub remaining_u64s: usize, +} + +impl PartialPrecompileHint { + /// Continues parsing a partial hint by accumulating more data from the slice. + /// + /// # Arguments + /// + /// * `slice` - The new data slice to accumulate + /// + /// # Returns + /// + /// * `Ok((PrecHintParseResult, usize))` - The result and number of u64s consumed from the slice + /// * `PrecHintParseResult::Complete` - If enough data was accumulated to complete the hint + /// * `PrecHintParseResult::Partial` - If more data is still needed + #[inline(always)] + pub fn continue_from_slice(mut self, slice: &[u64]) -> (PrecHintParseResult, usize) { + let available = slice.len(); + + if available >= self.remaining_u64s { + // We have enough data to complete the hint + let consumed = self.remaining_u64s; + self.data.extend_from_slice(&slice[..consumed]); + + ( + PrecHintParseResult::Complete(PrecompileHint { + hint_code: self.hint_code, + is_passthrough: self.is_passthrough, + data: self.data, + data_len_bytes: self.expected_len_bytes, + }), + consumed, + ) + } else { + // Still not enough data, accumulate what we have + self.data.extend_from_slice(slice); + self.remaining_u64s -= available; + + (PrecHintParseResult::Partial(self), available) + } + } +} + +/// Result of parsing a hint from a u64 slice. +#[derive(Debug)] +pub enum PrecHintParseResult { + /// A complete hint was successfully parsed. + Complete(PrecompileHint), + /// A partial hint was received; more data is needed. + Partial(PartialPrecompileHint), +} + /// Represents a single precompile hint parsed from a `u64` slice. /// /// A hint consists of a type identifier and associated data. The hint type @@ -380,21 +447,72 @@ impl std::fmt::Debug for PrecompileHint { } impl PrecompileHint { - /// Parses a [`PrecompileHint`] from a slice of `u64` values at the given index. + /// Parses a [`PrecompileHint`] from a slice of `u64` values at the given index, + /// optionally continuing from a previously received partial hint. /// /// # Arguments /// /// * `slice` - The source slice containing concatenated hints /// * `idx` - The index where the hint header starts /// * `allow_custom` - If true, unknown codes create Custom variant; if false, return error + /// * `partial` - Optional partial hint from a previous call to continue accumulating + /// * `max_buffer_size` - Maximum allowed hint data size in bytes; hints exceeding this return error /// /// # Returns /// - /// * `Ok(PrecompileHint)` - Successfully parsed hint - /// * `Err` - If the slice is too short or the index is out of bounds + /// * `Ok((PrecHintParseResult, usize))` - The parse result and number of u64s consumed + /// * `PrecHintParseResult::Complete` - Successfully parsed a complete hint + /// * `PrecHintParseResult::Partial` - Parsed header but slice doesn't contain all data + /// * `Err` - If the slice is empty, index is out of bounds, hint code is invalid, + /// or hint size exceeds `max_buffer_size` #[inline(always)] - pub fn from_u64_slice(slice: &[u64], idx: usize, allow_custom: bool) -> Result { - if slice.is_empty() || idx >= slice.len() { + pub fn from_u64_slice( + slice: &[u64], + idx: usize, + allow_custom: bool, + partial: Option, + max_buffer_size: usize, + ) -> Result<(PrecHintParseResult, usize)> { + // If we have a partial hint, continue accumulating from it + if let Some(partial_hint) = partial { + let available = slice.len() - idx; + + if available >= partial_hint.remaining_u64s { + // We have enough data to complete the hint + let consumed = partial_hint.remaining_u64s; + let mut data = partial_hint.data; + data.extend_from_slice(&slice[idx..idx + consumed]); + + return Ok(( + PrecHintParseResult::Complete(PrecompileHint { + hint_code: partial_hint.hint_code, + is_passthrough: partial_hint.is_passthrough, + data, + data_len_bytes: partial_hint.expected_len_bytes, + }), + consumed, + )); + } else { + // Still not enough data, accumulate what we have + let mut data = partial_hint.data; + data.extend_from_slice(&slice[idx..]); + let remaining_u64s = partial_hint.remaining_u64s - available; + + return Ok(( + PrecHintParseResult::Partial(PartialPrecompileHint { + hint_code: partial_hint.hint_code, + is_passthrough: partial_hint.is_passthrough, + data, + expected_len_bytes: partial_hint.expected_len_bytes, + remaining_u64s, + }), + available, + )); + } + } + + // No partial hint, parse from scratch + if slice.len() <= idx { return Err(anyhow::anyhow!("Slice too short or index out of bounds")); } @@ -402,18 +520,20 @@ impl PrecompileHint { // Extract length from lower 32 bits let length = header & 0xFFFFFFFF; + let length_bytes = length as usize; + + // Validate against max buffer size + if length_bytes > max_buffer_size { + return Err(anyhow::anyhow!( + "Hint data size {} bytes exceeds max buffer size {} bytes", + length_bytes, + max_buffer_size + )); + } // Calculate how many u64s are needed to hold length let num_u64s = length.div_ceil(8) as usize; - anyhow::ensure!( - slice.len() >= idx + 1 + num_u64s, - "Slice too short for hint data {}: expected {} u64s, got {}", - (header >> 32) as u32, - num_u64s, - slice.len() - idx - 1 - ); - // Extract hint code from upper 32 bits let hint_code_32 = (header >> 32) as u32; // Extract pass-through flag from bit 31 (MSB) - shift is faster than mask @@ -427,85 +547,41 @@ impl PrecompileHint { HintCode::try_from(hint_code_value)? }; - // Create a new Vec with the hint data. - let data = slice[idx + 1..idx + 1 + num_u64s].to_vec(); - - Ok(PrecompileHint { hint_code, is_passthrough, data, data_len_bytes: length as usize }) - } - - /// Parses a [`PrecompileHint`] from a slice of `u64` values at the given byte index. - /// - /// # Arguments - /// - /// * `slice` - The source slice containing concatenated hints - /// * `idx` - The **byte index** where the hint header starts - /// * `allow_custom` - If true, unknown codes create Custom variant; if false, return error - /// - /// # Returns - /// - /// * `Ok(PrecompileHint)` - Successfully parsed hint - /// * `Err` - If the slice is too short or the index is out of bounds - #[inline(always)] - pub fn from_unaligned_u64_slice(slice: &[u64], idx: usize, allow_custom: bool) -> Result { - const HEADER_SIZE: usize = 8; - - // Convert u64 slice to byte slice - let byte_slice = - unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const u8, slice.len() * 8) }; - - if idx + HEADER_SIZE > byte_slice.len() { - return Err(anyhow::anyhow!("Slice too short for header at byte index {}", idx)); + let available_u64s = slice.len() - idx - 1; + + // Check if we have enough data for the complete hint + if available_u64s < num_u64s { + // Return partial hint with whatever data we have + let data = slice[idx + 1..].to_vec(); + let remaining_u64s = num_u64s - available_u64s; + // Consumed: 1 header + all available data + let consumed = 1 + available_u64s; + + return Ok(( + PrecHintParseResult::Partial(PartialPrecompileHint { + hint_code, + is_passthrough, + data, + expected_len_bytes: length_bytes, + remaining_u64s, + }), + consumed, + )); } - // Read 8-byte header as u64 (little-endian) - let header = u64::from_le_bytes(byte_slice[idx..idx + HEADER_SIZE].try_into().unwrap()); - - // Extract length from lower 32 bits (length is in bytes) - let length = (header & 0xFFFFFFFF) as usize; - - // Calculate how many u64s are needed to hold length bytes - let num_u64s = length.div_ceil(8); - println!("Header: {:#x}, Length: {}, Num u64s: {}", header, length, num_u64s); - anyhow::ensure!( - idx + HEADER_SIZE + length <= byte_slice.len(), - "Slice too short for hint data {}: expected {} bytes, got {}", - (header >> 32) as u32, - length, - byte_slice.len() - idx - HEADER_SIZE - ); - - // Extract hint code from upper 32 bits - let hint_code_32 = (header >> 32) as u32; - // Extract pass-through flag from bit 31 (MSB) - shift is faster than mask - let is_passthrough = hint_code_32 >> 31 != 0; - // Extract the actual hint code from bits 0-30 - mask is optimal - let hint_code_value = hint_code_32 & 0x7FFFFFFF; - - let hint_code = if allow_custom { - HintCode::try_from(hint_code_value).unwrap_or(HintCode::Custom(hint_code_value)) - } else { - HintCode::try_from(hint_code_value)? - }; - - // Extract data bytes and convert to u64 with optimal performance - let data_bytes = &byte_slice[idx + HEADER_SIZE..idx + HEADER_SIZE + length]; - let mut data = Vec::with_capacity(num_u64s); - - let mut offset = 0; - // Process full u64s with direct unaligned reads - while offset + 8 <= data_bytes.len() { - let value = unsafe { (data_bytes.as_ptr().add(offset) as *const u64).read_unaligned() }; - data.push(u64::from_le(value)); - offset += 8; - } - - // Handle last partial u64 if any - if offset < data_bytes.len() { - let mut bytes = [0u8; 8]; - bytes[..data_bytes.len() - offset].copy_from_slice(&data_bytes[offset..]); - data.push(u64::from_le_bytes(bytes)); - } - - Ok(PrecompileHint { hint_code, is_passthrough, data, data_len_bytes: length }) + // Create a new Vec with the hint data. + let data = slice[idx + 1..idx + 1 + num_u64s].to_vec(); + // Consumed: 1 header + num_u64s data + let consumed = 1 + num_u64s; + + Ok(( + PrecHintParseResult::Complete(PrecompileHint { + hint_code, + is_passthrough, + data, + data_len_bytes: length_bytes, + }), + consumed, + )) } } diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index 79fe28192..3eeaf29fa 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -4,8 +4,11 @@ use anyhow::Result; use std::future::Future; use std::pin::Pin; use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::Arc; -use zisk_common::{io::StreamProcessor, CtrlHint, HintCode, PrecompileHint}; +use std::sync::{Arc, Mutex}; +use zisk_common::{ + io::StreamProcessor, CtrlHint, HintCode, PartialPrecompileHint, PrecHintParseResult, + PrecompileHint, +}; use zisk_distributed_common::StreamMessageKind; type AsyncDispatcher = Arc< @@ -18,6 +21,12 @@ pub struct PrecompileHintsRelay { sequence_number: Arc, dispatcher: AsyncDispatcher, runtime_handle: tokio::runtime::Handle, + + /// Buffer for incomplete hint data between batches + pending_partial: Mutex>, + + /// Maximum allowed buffer size in bytes (to prevent unbounded growth) + max_buffer_size: usize, } impl PrecompileHintsRelay { @@ -39,18 +48,40 @@ impl PrecompileHintsRelay { sequence_number: Arc::new(AtomicU32::new(0)), dispatcher, runtime_handle: tokio::runtime::Handle::current(), + pending_partial: Mutex::new(None), + max_buffer_size: Self::DEFAULT_MAX_BUFFER_SIZE, } } + /// Default maximum buffer size: 128KB in bytes + const DEFAULT_MAX_BUFFER_SIZE: usize = 128 * 1024; + pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { let mut has_ctrl_start = false; let mut has_ctrl_end = false; + // Take any pending partial hint from previous batch + let mut pending_partial = self.pending_partial.lock().unwrap().take(); + // Parse hints and dispatch to pool let mut idx = 0; while idx < hints.len() { - let hint = PrecompileHint::from_u64_slice(hints, idx, true)?; - let length = hint.data.len(); + let (parsed_hint, consumed) = PrecompileHint::from_u64_slice( + hints, + idx, + true, + pending_partial.take(), + self.max_buffer_size, + )?; + let hint = match parsed_hint { + PrecHintParseResult::Complete(hint) => hint, + PrecHintParseResult::Partial(partial) => { + // Store partial for next batch and exit loop + *self.pending_partial.lock().unwrap() = Some(partial); + break; + } + }; + let length = consumed; // Validate hint type is in valid range before accessing stats array diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 55def766a..69bdb24c2 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -13,7 +13,9 @@ use std::sync::{Arc, Condvar, Mutex}; use std::time::Instant; use tracing::{debug, info}; use zisk_common::io::{StreamProcessor, StreamSink}; -use zisk_common::{BuiltInHint, CtrlHint, HintCode, PrecompileHint}; +use zisk_common::{ + BuiltInHint, CtrlHint, HintCode, PartialPrecompileHint, PrecHintParseResult, PrecompileHint, +}; use ziskos_hints::handlers::bls381::{ bls12_381_fp2_to_g2_hint, bls12_381_fp_to_g1_hint, bls12_381_g1_add_hint, bls12_381_g1_msm_hint, bls12_381_g2_add_hint, bls12_381_g2_msm_hint, @@ -80,6 +82,7 @@ pub struct HintsProcessorBuilder { num_threads: usize, enable_stats: bool, custom_handlers: HashMap, + max_buffer_size: usize, } impl HintsProcessorBuilder { @@ -120,6 +123,24 @@ impl HintsProcessorBuilder { self } + /// Sets the maximum buffer size for partial hint accumulation. + /// + /// When receiving chunked data, incomplete hints are buffered until + /// the next chunk arrives. This limit prevents unbounded memory growth + /// from malformed headers declaring huge data lengths. + /// + /// # Arguments + /// + /// * `size` - Maximum buffer size in number of u64 elements + /// + /// # Default + /// + /// 128KB (16,384 u64 elements) + pub fn max_buffer_size(mut self, size: usize) -> Self { + self.max_buffer_size = size; + self + } + /// Builds the [`HintsProcessor`] with the configured settings. /// /// # Returns @@ -152,6 +173,8 @@ impl HintsProcessorBuilder { custom_handlers: Arc::new(self.custom_handlers), stream_active: AtomicBool::new(false), instant: Mutex::new(None), + pending_partial: Mutex::new(None), + max_buffer_size: self.max_buffer_size, }) } } @@ -188,11 +211,21 @@ pub struct HintsProcessor { /// Timestamp of when the current stream started (for performance metrics) instant: Mutex>, + + /// Buffer for incomplete hint data between batches + pending_partial: Mutex>, + + /// Maximum allowed buffer size in bytes (to prevent unbounded growth) + max_buffer_size: usize, } impl HintsProcessor { + /// Default number of worker threads in the thread pool. const DEFAULT_NUM_THREADS: usize = 32; + /// Default maximum buffer size: 128KB in bytes + const DEFAULT_MAX_BUFFER_SIZE: usize = 128 * 1024; + /// Creates a builder for configuring a [`HintsProcessor`]. /// /// # Arguments @@ -213,6 +246,7 @@ impl HintsProcessor { num_threads: Self::DEFAULT_NUM_THREADS, enable_stats: false, custom_handlers: HashMap::new(), + max_buffer_size: Self::DEFAULT_MAX_BUFFER_SIZE, } } @@ -244,10 +278,11 @@ impl HintsProcessor { /// * `Ok(false)` - Batch processed successfully, no CTRL_END /// * `Err` - If a previous error occurred or hints are malformed pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { - const HEADER_SIZE: usize = 1; - let mut has_ctrl_end = false; + // Take any pending partial hint from previous batch + let mut pending_partial = self.pending_partial.lock().unwrap().take(); + // Parse hints and dispatch to pool let mut idx = 0; while idx < hints.len() { @@ -255,7 +290,21 @@ impl HintsProcessor { if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to previous error")); } - let hint = PrecompileHint::from_u64_slice(hints, idx, true)?; + let (parsed_hint, consumed) = PrecompileHint::from_u64_slice( + hints, + idx, + true, + pending_partial.take(), + self.max_buffer_size, + )?; + let hint = match parsed_hint { + PrecHintParseResult::Complete(hint) => hint, + PrecHintParseResult::Partial(partial) => { + // Store partial for next batch and exit loop + *self.pending_partial.lock().unwrap() = Some(partial); + break; + } + }; // println!("Received Hint <= {:?}:", hint); @@ -273,7 +322,7 @@ impl HintsProcessor { } } - let length = hint.data.len() + HEADER_SIZE; + let length = consumed; if let Some(stats) = &self.stats { if !matches!(hint.hint_code, HintCode::Ctrl(_)) { From 286457b0073940d4c69646890fcde842d4e45448 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:59:36 +0000 Subject: [PATCH 490/782] remove max size in hints since the max size allowed is controlled by the source depending on the used media --- common/src/hints.rs | 10 ----- .../crates/coordinator/src/hints_relay.rs | 17 ++------- precompiles/hints/src/hints_processor.rs | 37 ++----------------- 3 files changed, 6 insertions(+), 58 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 097c3e822..bd383f6f2 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -471,7 +471,6 @@ impl PrecompileHint { idx: usize, allow_custom: bool, partial: Option, - max_buffer_size: usize, ) -> Result<(PrecHintParseResult, usize)> { // If we have a partial hint, continue accumulating from it if let Some(partial_hint) = partial { @@ -522,15 +521,6 @@ impl PrecompileHint { let length = header & 0xFFFFFFFF; let length_bytes = length as usize; - // Validate against max buffer size - if length_bytes > max_buffer_size { - return Err(anyhow::anyhow!( - "Hint data size {} bytes exceeds max buffer size {} bytes", - length_bytes, - max_buffer_size - )); - } - // Calculate how many u64s are needed to hold length let num_u64s = length.div_ceil(8) as usize; diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index 3eeaf29fa..356674b45 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -24,9 +24,6 @@ pub struct PrecompileHintsRelay { /// Buffer for incomplete hint data between batches pending_partial: Mutex>, - - /// Maximum allowed buffer size in bytes (to prevent unbounded growth) - max_buffer_size: usize, } impl PrecompileHintsRelay { @@ -49,13 +46,9 @@ impl PrecompileHintsRelay { dispatcher, runtime_handle: tokio::runtime::Handle::current(), pending_partial: Mutex::new(None), - max_buffer_size: Self::DEFAULT_MAX_BUFFER_SIZE, } } - /// Default maximum buffer size: 128KB in bytes - const DEFAULT_MAX_BUFFER_SIZE: usize = 128 * 1024; - pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { let mut has_ctrl_start = false; let mut has_ctrl_end = false; @@ -66,13 +59,9 @@ impl PrecompileHintsRelay { // Parse hints and dispatch to pool let mut idx = 0; while idx < hints.len() { - let (parsed_hint, consumed) = PrecompileHint::from_u64_slice( - hints, - idx, - true, - pending_partial.take(), - self.max_buffer_size, - )?; + let (parsed_hint, consumed) = + PrecompileHint::from_u64_slice(hints, idx, true, pending_partial.take())?; + let hint = match parsed_hint { PrecHintParseResult::Complete(hint) => hint, PrecHintParseResult::Partial(partial) => { diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 69bdb24c2..3d519e9d4 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -82,7 +82,6 @@ pub struct HintsProcessorBuilder { num_threads: usize, enable_stats: bool, custom_handlers: HashMap, - max_buffer_size: usize, } impl HintsProcessorBuilder { @@ -123,24 +122,6 @@ impl HintsProcessorBuilder { self } - /// Sets the maximum buffer size for partial hint accumulation. - /// - /// When receiving chunked data, incomplete hints are buffered until - /// the next chunk arrives. This limit prevents unbounded memory growth - /// from malformed headers declaring huge data lengths. - /// - /// # Arguments - /// - /// * `size` - Maximum buffer size in number of u64 elements - /// - /// # Default - /// - /// 128KB (16,384 u64 elements) - pub fn max_buffer_size(mut self, size: usize) -> Self { - self.max_buffer_size = size; - self - } - /// Builds the [`HintsProcessor`] with the configured settings. /// /// # Returns @@ -174,7 +155,6 @@ impl HintsProcessorBuilder { stream_active: AtomicBool::new(false), instant: Mutex::new(None), pending_partial: Mutex::new(None), - max_buffer_size: self.max_buffer_size, }) } } @@ -214,18 +194,12 @@ pub struct HintsProcessor { /// Buffer for incomplete hint data between batches pending_partial: Mutex>, - - /// Maximum allowed buffer size in bytes (to prevent unbounded growth) - max_buffer_size: usize, } impl HintsProcessor { /// Default number of worker threads in the thread pool. const DEFAULT_NUM_THREADS: usize = 32; - /// Default maximum buffer size: 128KB in bytes - const DEFAULT_MAX_BUFFER_SIZE: usize = 128 * 1024; - /// Creates a builder for configuring a [`HintsProcessor`]. /// /// # Arguments @@ -246,7 +220,6 @@ impl HintsProcessor { num_threads: Self::DEFAULT_NUM_THREADS, enable_stats: false, custom_handlers: HashMap::new(), - max_buffer_size: Self::DEFAULT_MAX_BUFFER_SIZE, } } @@ -290,13 +263,9 @@ impl HintsProcessor { if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to previous error")); } - let (parsed_hint, consumed) = PrecompileHint::from_u64_slice( - hints, - idx, - true, - pending_partial.take(), - self.max_buffer_size, - )?; + let (parsed_hint, consumed) = + PrecompileHint::from_u64_slice(hints, idx, true, pending_partial.take())?; + let hint = match parsed_hint { PrecHintParseResult::Complete(hint) => hint, PrecHintParseResult::Partial(partial) => { From 978a20186e7fae6b8c2ce110af0807b8e4c84045 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 11 Feb 2026 11:06:13 +0000 Subject: [PATCH 491/782] reset state when streaming in --hints-stream mode --- .../crates/coordinator/src/hints_relay.rs | 10 ++++++++++ distributed/crates/worker/src/worker.rs | 16 +++++++++++++--- precompiles/hints/src/hints_processor.rs | 1 + 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index 356674b45..16a622ea4 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -143,10 +143,20 @@ impl PrecompileHintsRelay { self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamMessageKind::End, vec![])); } + + /// Reset internal state for clean execution + fn reset_state(&self) { + self.sequence_number.store(0, Ordering::SeqCst); + *self.pending_partial.lock().unwrap() = None; + } } impl StreamProcessor for PrecompileHintsRelay { fn process(&self, data: &[u64], first_batch: bool) -> Result { self.process_hints(data, first_batch) } + + fn reset(&self) { + self.reset_state(); + } } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index f8c2b0015..43420131d 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -10,7 +10,7 @@ use std::fs; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; -use zisk_common::io::{StreamSource, ZiskStdin}; +use zisk_common::io::{StreamProcessor, StreamSource, ZiskStdin}; use zisk_common::reinterpret_vec; use zisk_common::ElfBinaryFromFile; use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; @@ -564,8 +564,9 @@ impl Worker { prover.set_hints_stream(hints_stream)?; } HintsSourceDto::HintsStream(_hints_uri) => { - // let hints_stream = StreamSource::from_uri(hints_uri.into())?; - // prover.set_hints_stream(hints_stream)?; + // For HintsStream, the worker will receive hint data via stream_data messages + // The hints will be processed by the hints_processor in process_stream_data + // No need to set hints_stream on prover for this case } HintsSourceDto::HintsNull => { // No hints to set @@ -620,6 +621,12 @@ impl Worker { } } + // Reset hints processor state for the new stream/job + // This is crucial for consecutive proofs to work correctly + if let Some(ref mut hints_processor) = self.hints_processor { + hints_processor.reset(); + } + return Ok(()); } else if stream_type == StreamMessageKind::End { // Ensure buffer exists @@ -631,6 +638,9 @@ impl Worker { )); } + // Clean up the stream buffer for this job + self.stream_buffers.remove(&job_id); + return Ok(()); } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 3d519e9d4..fb67546cc 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -702,6 +702,7 @@ impl HintsProcessor { } fn reset(&self) { + self.pending_partial.lock().unwrap().take(); self.hints_sink.reset(); } } From 98cd4b680c522c1e997a478b29638b6c4a489957 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:28:40 +0000 Subject: [PATCH 492/782] fix in process_hints --- common/src/hints.rs | 20 +- .../crates/coordinator/src/hints_relay.rs | 10 +- precompiles/hints/src/hints_processor.rs | 302 +++++++++++++++++- 3 files changed, 314 insertions(+), 18 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index bd383f6f2..b0711c12e 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -374,11 +374,11 @@ impl PartialPrecompileHint { /// /// # Returns /// - /// * `Ok((PrecHintParseResult, usize))` - The result and number of u64s consumed from the slice + /// * `(PrecHintParseResult, usize)` - The result and number of u64s consumed from the slice /// * `PrecHintParseResult::Complete` - If enough data was accumulated to complete the hint /// * `PrecHintParseResult::Partial` - If more data is still needed #[inline(always)] - pub fn continue_from_slice(mut self, slice: &[u64]) -> (PrecHintParseResult, usize) { + pub fn continue_from_slice(mut self, slice: &[u64]) -> (PrecompileHintParseResult, usize) { let available = slice.len(); if available >= self.remaining_u64s { @@ -387,7 +387,7 @@ impl PartialPrecompileHint { self.data.extend_from_slice(&slice[..consumed]); ( - PrecHintParseResult::Complete(PrecompileHint { + PrecompileHintParseResult::Complete(PrecompileHint { hint_code: self.hint_code, is_passthrough: self.is_passthrough, data: self.data, @@ -400,14 +400,14 @@ impl PartialPrecompileHint { self.data.extend_from_slice(slice); self.remaining_u64s -= available; - (PrecHintParseResult::Partial(self), available) + (PrecompileHintParseResult::Partial(self), available) } } } /// Result of parsing a hint from a u64 slice. #[derive(Debug)] -pub enum PrecHintParseResult { +pub enum PrecompileHintParseResult { /// A complete hint was successfully parsed. Complete(PrecompileHint), /// A partial hint was received; more data is needed. @@ -471,7 +471,7 @@ impl PrecompileHint { idx: usize, allow_custom: bool, partial: Option, - ) -> Result<(PrecHintParseResult, usize)> { + ) -> Result<(PrecompileHintParseResult, usize)> { // If we have a partial hint, continue accumulating from it if let Some(partial_hint) = partial { let available = slice.len() - idx; @@ -483,7 +483,7 @@ impl PrecompileHint { data.extend_from_slice(&slice[idx..idx + consumed]); return Ok(( - PrecHintParseResult::Complete(PrecompileHint { + PrecompileHintParseResult::Complete(PrecompileHint { hint_code: partial_hint.hint_code, is_passthrough: partial_hint.is_passthrough, data, @@ -498,7 +498,7 @@ impl PrecompileHint { let remaining_u64s = partial_hint.remaining_u64s - available; return Ok(( - PrecHintParseResult::Partial(PartialPrecompileHint { + PrecompileHintParseResult::Partial(PartialPrecompileHint { hint_code: partial_hint.hint_code, is_passthrough: partial_hint.is_passthrough, data, @@ -548,7 +548,7 @@ impl PrecompileHint { let consumed = 1 + available_u64s; return Ok(( - PrecHintParseResult::Partial(PartialPrecompileHint { + PrecompileHintParseResult::Partial(PartialPrecompileHint { hint_code, is_passthrough, data, @@ -565,7 +565,7 @@ impl PrecompileHint { let consumed = 1 + num_u64s; Ok(( - PrecHintParseResult::Complete(PrecompileHint { + PrecompileHintParseResult::Complete(PrecompileHint { hint_code, is_passthrough, data, diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index 16a622ea4..f34b56473 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -6,8 +6,8 @@ use std::pin::Pin; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; use zisk_common::{ - io::StreamProcessor, CtrlHint, HintCode, PartialPrecompileHint, PrecHintParseResult, - PrecompileHint, + io::StreamProcessor, CtrlHint, HintCode, PartialPrecompileHint, PrecompileHint, + PrecompileHintParseResult, }; use zisk_distributed_common::StreamMessageKind; @@ -63,8 +63,8 @@ impl PrecompileHintsRelay { PrecompileHint::from_u64_slice(hints, idx, true, pending_partial.take())?; let hint = match parsed_hint { - PrecHintParseResult::Complete(hint) => hint, - PrecHintParseResult::Partial(partial) => { + PrecompileHintParseResult::Complete(hint) => hint, + PrecompileHintParseResult::Partial(partial) => { // Store partial for next batch and exit loop *self.pending_partial.lock().unwrap() = Some(partial); break; @@ -99,7 +99,7 @@ impl PrecompileHintsRelay { } has_ctrl_end = hint.hint_code == HintCode::Ctrl(CtrlHint::End); - idx += length + 1; + idx += length; } if has_ctrl_start { diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index fb67546cc..871e72ac2 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -14,7 +14,8 @@ use std::time::Instant; use tracing::{debug, info}; use zisk_common::io::{StreamProcessor, StreamSink}; use zisk_common::{ - BuiltInHint, CtrlHint, HintCode, PartialPrecompileHint, PrecHintParseResult, PrecompileHint, + BuiltInHint, CtrlHint, HintCode, PartialPrecompileHint, PrecompileHint, + PrecompileHintParseResult, }; use ziskos_hints::handlers::bls381::{ bls12_381_fp2_to_g2_hint, bls12_381_fp_to_g1_hint, bls12_381_g1_add_hint, @@ -267,8 +268,8 @@ impl HintsProcessor { PrecompileHint::from_u64_slice(hints, idx, true, pending_partial.take())?; let hint = match parsed_hint { - PrecHintParseResult::Complete(hint) => hint, - PrecHintParseResult::Partial(partial) => { + PrecompileHintParseResult::Complete(hint) => hint, + PrecompileHintParseResult::Partial(partial) => { // Store partial for next batch and exit loop *self.pending_partial.lock().unwrap() = Some(partial); break; @@ -1334,6 +1335,301 @@ mod tests { assert_eq!(results[4], vec![150]); // SLOW: 50 * 3 } + // Partial hint tests + #[test] + fn test_partial_hint_header_data_split() { + let p = processor(); + + // Hint split exactly at boundary: header in first batch, data in second + // Header indicates 8 bytes (1 u64) of data + let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 8)]; + let batch2 = vec![0x1234]; // The data + + // First batch should succeed but not complete the hint + assert!(p.process_hints(&batch1, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Verify no results yet (hint is partial) + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + assert!(queue.buffer.is_empty()); + } + + // Second batch completes the hint + assert!(p.process_hints(&batch2, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Now we should have the complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } + } + + #[test] + fn test_partial_hint_partial_data_split() { + let p = processor(); + + // Hint with header + some data in first batch, remaining data in second + // Header indicates 16 bytes (2 u64s) of data + let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 16), 0x1111]; // Header + 1 u64 + let batch2 = vec![0x2222]; // Remaining 1 u64 + + // First batch has partial data + assert!(p.process_hints(&batch1, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Verify no results yet + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + } + + // Second batch completes the hint + assert!(p.process_hints(&batch2, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Now we should have the complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } + } + + #[test] + fn test_partial_hint_across_multiple_batches() { + let p = processor(); + + // Large hint split across 3 batches + // Header indicates 32 bytes (4 u64s) of data + let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 32), 0x1111]; // Header + 1 u64 + let batch2 = vec![0x2222, 0x3333]; // 2 more u64s + let batch3 = vec![0x4444]; // Final u64 + + // First batch + assert!(p.process_hints(&batch1, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Second batch + assert!(p.process_hints(&batch2, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Still no complete results + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + } + + // Third batch completes the hint + assert!(p.process_hints(&batch3, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Now we should have the complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } + } + + #[test] + fn test_multiple_partial_hints_interleaved() { + let p = processor(); + + // Start two hints in first batch, complete them in different subsequent batches + let batch1 = vec![ + make_header(TEST_PASSTHROUGH_HINT, 16), + 0x1111, // First hint: header + partial data + make_header(TEST_PASSTHROUGH_HINT, 8), // Second hint: header only + ]; + let batch2 = vec![0x2222]; // Completes first hint + let batch3 = vec![0x3333]; // Completes second hint + + // First batch has two partial hints + assert!(p.process_hints(&batch1, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // No results yet + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + } + + // Complete first hint + assert!(p.process_hints(&batch2, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Should have first result now + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + } + + // Complete second hint + assert!(p.process_hints(&batch3, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Should have both results + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 2); + assert!(queue.buffer.is_empty()); + } + } + + #[test] + fn test_partial_hint_with_complete_hints() { + let p = processor(); + + // Mix partial and complete hints in same batch + let batch1 = vec![ + make_header(TEST_PASSTHROUGH_HINT, 8), + 0x1111, // Complete hint + make_header(TEST_PASSTHROUGH_HINT, 16), + 0x2222, // Partial hint (needs 1 more u64) + ]; + let batch2 = vec![0x3333]; // Completes partial hint + + // First batch processes one complete, one partial + assert!(p.process_hints(&batch1, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Should have result for complete hint + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + } + + // Complete the partial hint + assert!(p.process_hints(&batch2, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Should have both results + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 2); + assert!(queue.buffer.is_empty()); + } + } + + #[test] + fn test_partial_hint_zero_length() { + let p = processor(); + + // Hint with zero data length split across batches + let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 0)]; // Header only, zero data + + // Should complete immediately since no data needed + assert!(p.process_hints(&batch1, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Should have result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } + } + + #[test] + fn test_partial_hint_stream_control() { + let p = processor(); + + // Test partial hints with stream control messages + let start = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::Start).to_u32(), 0)]; + let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 8)]; // Header only + let batch2 = vec![0x1234]; // Complete the hint + let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::End).to_u32(), 0)]; + + // Start stream + assert!(p.process_hints(&start, true).is_ok()); + + // Partial hint + assert!(p.process_hints(&batch1, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Complete hint + assert!(p.process_hints(&batch2, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // End stream should wait for all hints to complete + assert!(p.process_hints(&end, false).is_ok()); + + // Everything should be processed + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } + } + + #[test] + fn test_partial_hint_reset_clears_pending() { + let p = processor(); + + // Start a partial hint + let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 16), 0x1111]; // Needs 1 more u64 + assert!(p.process_hints(&batch1, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Verify partial hint is pending + { + let partial = p.pending_partial.lock().unwrap(); + assert!(partial.is_some()); + } + + // Reset should clear pending partial + p.reset(); + + // Verify pending partial is cleared + { + let partial = p.pending_partial.lock().unwrap(); + assert!(partial.is_none()); + } + + // Should be able to process new hints normally + let batch2 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x2222]; + assert!(p.process_hints(&batch2, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + } + } + + #[test] + fn test_partial_hint_large_data() { + let p = processor(); + + // Test with larger data size (80 bytes = 10 u64s) + let mut batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 80)]; + batch1.extend([0x1111, 0x2222, 0x3333]); // Header + 3 u64s + + let batch2 = vec![0x4444, 0x5555, 0x6666, 0x7777]; // 4 more u64s + let batch3 = vec![0x8888, 0x9999, 0xAAAA]; // Final 3 u64s + + // Process all batches + assert!(p.process_hints(&batch1, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + assert!(p.process_hints(&batch2, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + assert!(p.process_hints(&batch3, false).is_ok()); + assert!(p.wait_for_completion().is_ok()); + + // Should have complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } + } + #[test] fn test_custom_handlers_stress_ordering() { use std::sync::{Arc, Mutex}; From 5eedfc9de1ae793aad53b22c4a31b43656276953 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:14:03 +0000 Subject: [PATCH 493/782] fixes in hints_processor.rs tests --- common/src/hints.rs | 50 ++---------------------- precompiles/hints/src/hints_processor.rs | 11 ++++-- 2 files changed, 11 insertions(+), 50 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index b0711c12e..9b95cb7b8 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -365,46 +365,6 @@ pub struct PartialPrecompileHint { pub remaining_u64s: usize, } -impl PartialPrecompileHint { - /// Continues parsing a partial hint by accumulating more data from the slice. - /// - /// # Arguments - /// - /// * `slice` - The new data slice to accumulate - /// - /// # Returns - /// - /// * `(PrecHintParseResult, usize)` - The result and number of u64s consumed from the slice - /// * `PrecHintParseResult::Complete` - If enough data was accumulated to complete the hint - /// * `PrecHintParseResult::Partial` - If more data is still needed - #[inline(always)] - pub fn continue_from_slice(mut self, slice: &[u64]) -> (PrecompileHintParseResult, usize) { - let available = slice.len(); - - if available >= self.remaining_u64s { - // We have enough data to complete the hint - let consumed = self.remaining_u64s; - self.data.extend_from_slice(&slice[..consumed]); - - ( - PrecompileHintParseResult::Complete(PrecompileHint { - hint_code: self.hint_code, - is_passthrough: self.is_passthrough, - data: self.data, - data_len_bytes: self.expected_len_bytes, - }), - consumed, - ) - } else { - // Still not enough data, accumulate what we have - self.data.extend_from_slice(slice); - self.remaining_u64s -= available; - - (PrecompileHintParseResult::Partial(self), available) - } - } -} - /// Result of parsing a hint from a u64 slice. #[derive(Debug)] pub enum PrecompileHintParseResult { @@ -456,15 +416,13 @@ impl PrecompileHint { /// * `idx` - The index where the hint header starts /// * `allow_custom` - If true, unknown codes create Custom variant; if false, return error /// * `partial` - Optional partial hint from a previous call to continue accumulating - /// * `max_buffer_size` - Maximum allowed hint data size in bytes; hints exceeding this return error /// /// # Returns /// - /// * `Ok((PrecHintParseResult, usize))` - The parse result and number of u64s consumed - /// * `PrecHintParseResult::Complete` - Successfully parsed a complete hint - /// * `PrecHintParseResult::Partial` - Parsed header but slice doesn't contain all data - /// * `Err` - If the slice is empty, index is out of bounds, hint code is invalid, - /// or hint size exceeds `max_buffer_size` + /// * `Ok((PrecompileHintParseResult, usize))` - The parse result and number of u64s consumed + /// * `PrecompileHintParseResult::Complete` - Successfully parsed a complete hint + /// * `PrecompileHintParseResult::Partial` - Parsed header but slice doesn't contain all data + /// * `Err` - If the slice is empty, index is out of bounds or hint code is invalid #[inline(always)] pub fn from_u64_slice( slice: &[u64], diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 871e72ac2..904af4a2d 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -1439,16 +1439,19 @@ mod tests { fn test_multiple_partial_hints_interleaved() { let p = processor(); - // Start two hints in first batch, complete them in different subsequent batches + // Start first hint in first batch, start second hint in second batch, and + // complete them in different subsequent batches. let batch1 = vec![ make_header(TEST_PASSTHROUGH_HINT, 16), - 0x1111, // First hint: header + partial data + 0x1111, // First hint: header + partial data (incomplete) + ]; + let batch2 = vec![ + 0x2222, // Completes first hint make_header(TEST_PASSTHROUGH_HINT, 8), // Second hint: header only ]; - let batch2 = vec![0x2222]; // Completes first hint let batch3 = vec![0x3333]; // Completes second hint - // First batch has two partial hints + // First batch has a single partial hint assert!(p.process_hints(&batch1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); From 4ad6a38df4dba7b0d849604868f87c94b5cc0506 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Wed, 11 Feb 2026 14:21:04 +0000 Subject: [PATCH 494/782] Cargo update --- Cargo.lock | 42 ++++++++++++++++++++-------------------- executor/src/executor.rs | 1 + 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95b568436..d70cbce40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -801,9 +801,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" dependencies = [ "clap_builder", "clap_derive", @@ -811,9 +811,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" dependencies = [ "anstream", "anstyle", @@ -835,9 +835,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "cmake" @@ -1188,7 +1188,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" dependencies = [ "fields", "num-bigint", @@ -1500,9 +1500,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" dependencies = [ "log", "regex", @@ -1510,9 +1510,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ "anstream", "anstyle", @@ -1641,7 +1641,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" dependencies = [ "cfg-if", "num-bigint", @@ -3060,7 +3060,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" dependencies = [ "colored", "fields", @@ -3468,7 +3468,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" dependencies = [ "bincode", "blake3", @@ -3504,7 +3504,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" dependencies = [ "bincode", "borsh", @@ -3537,7 +3537,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" dependencies = [ "fields", "itoa", @@ -3550,7 +3550,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" dependencies = [ "proc-macro2", "quote", @@ -3561,7 +3561,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" dependencies = [ "crossbeam-channel", "tracing", @@ -3570,7 +3570,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" dependencies = [ "bincode", "bytemuck", @@ -3584,7 +3584,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" dependencies = [ "bytemuck", "fields", @@ -6180,7 +6180,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#1e0d92b19a2850d7f3a0cb919a7d0302c049eb4e" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" dependencies = [ "colored", "fields", diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 80bcf6cbc..70424c5db 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -677,6 +677,7 @@ impl WitnessComponent for ZiskExecutor { fn execute( &self, pctx: Arc>, + _sctx: Arc>, global_ids: &RwLock>, ) -> ProofmanResult<()> { self.reset(); From 9d5eb7b24b0bbe771cfe08004f39628f49bdcf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Wed, 11 Feb 2026 16:49:43 +0100 Subject: [PATCH 495/782] Fix ziskemu costs (#779) --- core/src/zisk_ops.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index b3a051aa3..cb1d2af0c 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -345,23 +345,23 @@ const DMA_64_ALIGNED_OPS_BY_ROW: usize = 4; // Cost definitions: Area x Op const INTERNAL_COST: u64 = 0; -const BINARY_COST: u64 = 75; -const BINARY_ADD_COST: u64 = 26; -const BINARY_E_COST: u64 = 54; +const BINARY_COST: u64 = 60; +const BINARY_ADD_COST: u64 = 25; +const BINARY_E_COST: u64 = 53; const ARITHA32_COST: u64 = 95; const ARITHAM32_COST: u64 = 95; -const KECCAK_COST: u64 = (((93846 * 86) - 1) / 63) + 1; +const KECCAK_COST: u64 = 25 * 3022; const SHA256_COST: u64 = 72 * 121; const POSEIDON2_COST: u64 = 14 * 75; -const ARITH_EQ_COST: u64 = 85 * 16; +const ARITH_EQ_COST: u64 = 89 * 16; const FCALL_COST: u64 = INTERNAL_COST; const ARITH_EQ_384_COST: u64 = 79 * 24; const ADD256_COST: u64 = 104; -const DMA_COST: u64 = 39; +const DMA_COST: u64 = 42; const DMA_64_ALIGNED_COST: u64 = 40; const DMA_UNALIGNED_COST: u64 = 42; -const DMA_PRE_POST_COST: u64 = 84; +const DMA_PRE_POST_COST: u64 = 88; // const OP_DMA_64_ALIGNED: u8 = 0xda; // const OP_DMA_UNALIGNED: u8 = 0xdb; From a5b01827108fa0685d9f6d2ef59774654e80b99a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:18:18 +0000 Subject: [PATCH 496/782] fix hint processor messages --- precompiles/hints/src/hints_processor.rs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index f81acad00..a27c61ecc 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -340,24 +340,8 @@ impl HintsProcessor { } let num_hints = self.num_hint.load(Ordering::Relaxed); - let elapsed = self.instant.lock().as_ref().unwrap().unwrap().elapsed(); - let rate = num_hints as f64 / elapsed.as_secs_f64(); - - let (value, unit) = if rate >= 1_000_000.0 { - (rate / 1_000_000.0, "MHz") - } else if rate >= 1_000.0 { - (rate / 1_000.0, "kHz") - } else { - (rate, "Hz") - }; - - debug!( - "Processed {} hints in {:.0?} ({}{})", - num_hints, - elapsed, - value.round(), - unit - ); + + info!("··· Processed {} hints", num_hints); break; } @@ -414,12 +398,12 @@ impl HintsProcessor { if has_ctrl_end { if let Some(stats) = &self.stats { - info!("Hints stats:"); + debug!("Hints stats:"); let stats = stats.lock().unwrap(); let mut sorted_stats: Vec<_> = stats.iter().collect(); sorted_stats.sort_by_key(|(&hint_code, _)| hint_code.to_u32()); for (hint_code, count) in sorted_stats { - info!(" {}: {}", hint_code, count); + debug!(" {}: {}", hint_code, count); } } } From 6513eaa963a9b7b391e1002fb24662fcbbae1967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Wed, 11 Feb 2026 18:35:38 +0100 Subject: [PATCH 497/782] Fix/cost (#778) * Fixing costs * Adding cost per instance type --- common/src/component/component_instance.rs | 6 +- common/src/types.rs | 79 ++++++++++++++++++- executor/src/emu_asm.rs | 11 +-- executor/src/emu_asm_stub.rs | 4 +- executor/src/emu_rust.rs | 12 ++- executor/src/executor.rs | 70 +++++++++++++--- executor/src/lib.rs | 6 +- precompiles/arith_eq/src/arith_eq_instance.rs | 6 +- .../arith_eq_384/src/arith_eq_384_instance.rs | 5 ++ precompiles/big_int/src/add256_instance.rs | 5 ++ precompiles/dma/src/dma/dma_instance.rs | 5 ++ .../dma_64_aligned/dma_64_aligned_instance.rs | 5 ++ .../src/dma_pre_post/dma_pre_post_instance.rs | 5 ++ .../dma_unaligned/dma_unaligned_instance.rs | 5 ++ precompiles/keccakf/src/keccakf_instance.rs | 5 ++ .../poseidon2/src/poseidon2_instance.rs | 5 ++ precompiles/sha256f/src/sha256f_instance.rs | 5 ++ sdk/src/prover/mod.rs | 38 +++++++-- .../arith/src/arith_full_instance.rs | 5 ++ .../binary/src/binary_add_instance.rs | 5 ++ .../binary/src/binary_basic_instance.rs | 5 ++ .../binary/src/binary_extension_instance.rs | 5 ++ .../mem/src/mem_align_byte_instance.rs | 5 ++ state-machines/mem/src/mem_align_instance.rs | 5 ++ .../mem/src/mem_align_read_byte_instance.rs | 5 ++ .../mem/src/mem_align_write_byte_instance.rs | 5 ++ state-machines/mem/src/mem_module_instance.rs | 5 ++ state-machines/rom/src/rom_instance.rs | 5 ++ 28 files changed, 286 insertions(+), 41 deletions(-) diff --git a/common/src/component/component_instance.rs b/common/src/component/component_instance.rs index 13f601ceb..6e02686ca 100644 --- a/common/src/component/component_instance.rs +++ b/common/src/component/component_instance.rs @@ -2,7 +2,7 @@ //! in the context of proof systems. It includes traits and macros for defining instances //! and integrating them with state machines and proofs. -use crate::{BusDevice, CheckPoint, ChunkId, PayloadType}; +use crate::{BusDevice, CheckPoint, ChunkId, PayloadType, StatsType}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::any::Any; @@ -80,6 +80,10 @@ pub trait Instance: Any + Send + Sync { /// A reference to self as `&dyn Any`. fn as_any(&self) -> &dyn Any; + fn stats_type(&self) -> StatsType { + StatsType::Other + } + fn reset(&self) {} } diff --git a/common/src/types.rs b/common/src/types.rs index 9d0ec2945..5fad3c5c8 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -68,14 +68,89 @@ impl fmt::Display for SegmentId { } } +pub enum StatsType { + Main, + Memory, + Opcodes, + Precompiled, + Tables, + Other, +} + +#[derive(Debug, Default, Clone)] +pub struct StatsCostPerType { + pub main_cost: u64, + pub opcode_cost: u64, + pub memory_cost: u64, + pub precompile_cost: u64, + pub tables_cost: u64, + pub other_cost: u64, +} + +impl StatsCostPerType { + pub fn total_cost(&self) -> u64 { + self.main_cost + + self.opcode_cost + + self.memory_cost + + self.precompile_cost + + self.tables_cost + + self.other_cost + } + + pub fn add_cost(&mut self, stats_type: StatsType, cost: u64) { + match stats_type { + StatsType::Main => self.main_cost += cost, + StatsType::Opcodes => self.opcode_cost += cost, + StatsType::Memory => self.memory_cost += cost, + StatsType::Precompiled => self.precompile_cost += cost, + StatsType::Tables => self.tables_cost += cost, + StatsType::Other => self.other_cost += cost, + } + } +} + +impl fmt::Display for StatsCostPerType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let total = self.total_cost(); + if total == 0 { + return write!(f, "total=0"); + } + + let mut parts = Vec::new(); + + let pct = (self.main_cost as f64 / total as f64) * 100.0; + parts.push(format!("main={} ({:.1}%)", self.main_cost, pct)); + + let pct = (self.opcode_cost as f64 / total as f64) * 100.0; + parts.push(format!("opcode={} ({:.1}%)", self.opcode_cost, pct)); + + let pct = (self.memory_cost as f64 / total as f64) * 100.0; + parts.push(format!("memory={} ({:.1}%)", self.memory_cost, pct)); + + let pct = (self.precompile_cost as f64 / total as f64) * 100.0; + parts.push(format!("precompile={} ({:.1}%)", self.precompile_cost, pct)); + + let pct = (self.tables_cost as f64 / total as f64) * 100.0; + parts.push(format!("tables={} ({:.1}%)", self.tables_cost, pct)); + + if self.other_cost > 0 { + let pct = (self.other_cost as f64 / total as f64) * 100.0; + parts.push(format!("other={} ({:.1}%)", self.other_cost, pct)); + } + + write!(f, "total={} [{}]", total, parts.join(", ")) + } +} + #[derive(Debug, Default, Clone)] pub struct ZiskExecutionResult { pub steps: u64, + pub cost_per_type: StatsCostPerType, } impl ZiskExecutionResult { - pub fn new(executed_steps: u64) -> Self { - Self { steps: executed_steps } + pub fn new(executed_steps: u64, cost_per_type: StatsCostPerType) -> Self { + Self { steps: executed_steps, cost_per_type } } } diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 3e6981e0a..88bb04657 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -17,7 +17,6 @@ use proofman_common::ProofCtx; use sm_rom::RomSM; use zisk_common::{ io::ZiskStdin, stats_begin, stats_end, ChunkId, EmuTrace, ExecutorStatsHandle, StatsScope, - ZiskExecutionResult, }; use zisk_core::{ZiskRom, MAX_INPUT_SIZE}; use ziskemu::ZiskEmulator; @@ -109,7 +108,7 @@ impl EmulatorAsm { /// * `DeviceMetricsList` - Flat device metrics collected during execution. /// * `NestedDeviceMetricsList` - Hierarchical device metrics collected during execution. /// * `Option>` - Optional join handle for the memory-only ASM runner. - /// * `ZiskExecutionResult` - The result of executing the ZisK ROM. + /// * `u64` - Total number of steps. #[allow(clippy::type_complexity)] pub fn execute( &self, @@ -123,7 +122,7 @@ impl EmulatorAsm { DeviceMetricsList, NestedDeviceMetricsList, Option>, - ZiskExecutionResult, + u64, ) { stats_begin!(stats, _caller_stats_scope, _exec_scope, "EXECUTE_WITH_ASSEMBLY", 0); @@ -186,8 +185,6 @@ impl EmulatorAsm { // Store execute steps let steps = min_traces.iter().map(|trace| trace.steps).sum::(); - let execution_result = ZiskExecutionResult::new(steps); - // If the world rank is 0, wait for the ROM Histogram thread to finish and set the handler if has_rom_sm { self.rom_sm.as_ref().unwrap().set_asm_runner_handler( @@ -197,7 +194,7 @@ impl EmulatorAsm { stats_end!(stats, &_exec_scope); - (min_traces, main_count, secn_count, Some(handle_mo), execution_result) + (min_traces, main_count, secn_count, Some(handle_mo), steps) } fn create_shmem_writer(&self, service: &asm_runner::AsmService) -> SharedMemoryWriter { @@ -319,7 +316,7 @@ impl crate::Emulator for EmulatorAsm { DeviceMetricsList, NestedDeviceMetricsList, Option>, - ZiskExecutionResult, + u64, ) { self.execute(stdin, pctx, sm_bundle, stats, caller_stats_scope) } diff --git a/executor/src/emu_asm_stub.rs b/executor/src/emu_asm_stub.rs index 7eb798c59..c2d7cd91a 100644 --- a/executor/src/emu_asm_stub.rs +++ b/executor/src/emu_asm_stub.rs @@ -9,7 +9,7 @@ use asm_runner::AsmRunnerMO; use fields::PrimeField64; use proofman_common::ProofCtx; use sm_rom::RomSM; -use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope, ZiskExecutionResult}; +use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; use zisk_core::ZiskRom; pub struct EmulatorAsm {} @@ -41,7 +41,7 @@ impl EmulatorAsm { DeviceMetricsList, NestedDeviceMetricsList, Option>, - ZiskExecutionResult, + u64, ) { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); } diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index eb75c53c1..cd360a833 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -12,7 +12,7 @@ use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rayon::prelude::*; use zisk_common::{ io::{ZiskIO, ZiskStdin}, - ChunkId, EmuTrace, ExecutorStatsHandle, ZiskExecutionResult, + ChunkId, EmuTrace, ExecutorStatsHandle, }; use zisk_core::ZiskRom; use ziskemu::{EmuOptions, ZiskEmulator}; @@ -52,7 +52,7 @@ impl EmulatorRust { /// * `DeviceMetricsList` - Metrics for primary devices. /// * `NestedDeviceMetricsList` - Metrics for secondary/nested devices. /// * `None`. - /// * `ZiskExecutionResult` - Summary of the emulator execution, including the total number of steps. + /// * `u64` - Total number of steps. pub fn execute( &self, stdin: &Mutex, @@ -62,20 +62,18 @@ impl EmulatorRust { DeviceMetricsList, NestedDeviceMetricsList, Option>, - ZiskExecutionResult, + u64, ) { let min_traces = self.run_emulator(Self::NUM_THREADS, &mut stdin.lock().unwrap()); // Store execute steps let steps = min_traces.iter().map(|trace| trace.steps).sum::(); - let execution_result = ZiskExecutionResult::new(steps); - timer_start_info!(COUNT); let (main_count, secn_count) = self.count(&min_traces, sm_bundle); timer_stop_and_log_info!(COUNT); - (min_traces, main_count, secn_count, None, execution_result) + (min_traces, main_count, secn_count, None, steps) } fn run_emulator(&self, num_threads: usize, stdin: &mut ZiskStdin) -> Vec { @@ -171,7 +169,7 @@ impl crate::Emulator for EmulatorRust { DeviceMetricsList, NestedDeviceMetricsList, Option>, - ZiskExecutionResult, + u64, ) { self.execute(stdin, sm_bundle) } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 70424c5db..b7714ed8b 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -32,11 +32,12 @@ use data_bus::DataBusTrait; use sm_main::{MainInstance, MainPlanner, MainSM}; use zisk_common::{ stats_begin, stats_end, BusDevice, BusDeviceMetrics, CheckPoint, ChunkId, EmuTrace, - ExecutorStatsHandle, Instance, InstanceCtx, InstanceType, Plan, Stats, ZiskExecutionResult, + ExecutorStatsHandle, Instance, InstanceCtx, InstanceType, Plan, Stats, StatsType, + ZiskExecutionResult, }; use zisk_pil::{ ZiskPublicValues, INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, - ZISK_AIRGROUP_ID, + SPECIFIED_RANGES_AIR_IDS, VIRTUAL_TABLE_0_AIR_IDS, VIRTUAL_TABLE_1_AIR_IDS, ZISK_AIRGROUP_ID, }; use std::time::Instant; @@ -47,6 +48,7 @@ use std::{ use crossbeam::atomic::AtomicCell; +use zisk_common::StatsCostPerType; use zisk_core::ZiskRom; use ziskemu::ZiskEmulator; @@ -175,11 +177,24 @@ impl ZiskExecutor { fn assign_main_instances( &self, pctx: &ProofCtx, + sctx: &SetupCtx, global_ids: &RwLock>, main_planning: Vec, - ) { + ) -> u64 { let mut main_instances = self.main_instances.write().unwrap(); + let setup_main = sctx.get_setup(ZISK_AIRGROUP_ID, MAIN_AIR_IDS[0]).unwrap(); + let n_bits = setup_main.stark_info.stark_struct.n_bits; + let total_cols: u64 = setup_main + .stark_info + .map_sections_n + .iter() + .filter(|(key, _)| *key != "const") + .map(|(_, value)| *value) + .sum(); + let cost = (1 << n_bits) * total_cols; + let total_cost = cost * main_planning.len() as u64; + for mut plan in main_planning { let global_id = pctx .add_instance_assign(plan.airgroup_id, plan.air_id) @@ -190,6 +205,8 @@ impl ZiskExecutor { .entry(global_id) .or_insert_with(|| self.create_main_instance(plan, global_id)); } + + total_cost } /// Creates main state machine instance based on a main planning. @@ -677,7 +694,7 @@ impl WitnessComponent for ZiskExecutor { fn execute( &self, pctx: Arc>, - _sctx: Arc>, + sctx: Arc>, global_ids: &RwLock>, ) -> ProofmanResult<()> { self.reset(); @@ -697,14 +714,11 @@ impl WitnessComponent for ZiskExecutor { // Process the ROM to collect the Minimal Traces timer_start_info!(COMPUTE_MINIMAL_TRACE); - let (min_traces, main_count, mut secn_count, handle_mo, execution_result) = + let (min_traces, main_count, mut secn_count, handle_mo, steps) = self.emulator.execute(&self.stdin, &pctx, &self.sm_bundle, &self.stats, &_exec_scope); timer_stop_and_log_info!(COMPUTE_MINIMAL_TRACE); - // Store the execution result - *self.execution_result.lock().unwrap() = execution_result; - // Plan the main and secondary instances using the counted metrics stats_begin!(self.stats, &_exec_scope, _main_plan_scope, "MAIN_PLAN", 0); @@ -712,7 +726,7 @@ impl WitnessComponent for ZiskExecutor { let (main_planning, public_values) = MainPlanner::plan::(&min_traces, main_count, self.chunk_size); *self.min_traces.write().unwrap() = Some(min_traces); - self.assign_main_instances(&pctx, global_ids, main_planning); + let cost_main = self.assign_main_instances(&pctx, &sctx, global_ids, main_planning); stats_end!(self.stats, &_main_plan_scope); stats_begin!(self.stats, &_exec_scope, _secn_plan_scope, "SECN_PLAN", 0); @@ -771,12 +785,16 @@ impl WitnessComponent for ZiskExecutor { // Update internal state with the computed minimal traces and planning. *self.secn_planning.write().unwrap() = secn_planning; + let mut cost_per_type = StatsCostPerType::default(); + cost_per_type.add_cost(StatsType::Main, cost_main); + let mut secn_instances = self.secn_instances.write().unwrap(); for global_id in &secn_global_ids_vec { secn_instances .entry(*global_id) .or_insert_with(|| self.create_secn_instance(*global_id)); secn_instances[global_id].reset(); + let (airgroup_id, air_id) = pctx.dctx_get_instance_info(*global_id)?; if secn_instances[global_id].instance_type() == InstanceType::Instance { let checkpoint = secn_instances[global_id].check_point(); let chunks = match checkpoint { @@ -786,13 +804,23 @@ impl WitnessComponent for ZiskExecutor { chunk_ids.iter().map(|id| id.as_usize()).collect() } }; - let (_, air_id) = - pctx.dctx_get_instance_info(*global_id).expect("Failed to get instance info"); let mem_global_id = air_id == MEM_AIR_IDS[0] || air_id == ROM_DATA_AIR_IDS[0] || air_id == INPUT_DATA_AIR_IDS[0]; pctx.dctx_set_chunks(*global_id, chunks, mem_global_id); } + + let setup = sctx.get_setup(airgroup_id, air_id)?; + let n_bits = setup.stark_info.stark_struct.n_bits; + let total_cols: u64 = setup + .stark_info + .map_sections_n + .iter() + .filter(|(key, _)| *key != "const") + .map(|(_, value)| *value) + .sum(); + let cost = (1 << n_bits) * total_cols; + cost_per_type.add_cost(secn_instances[global_id].stats_type(), cost); } if let Ok(mut hints_stream_guard) = self.hints_stream.lock() { @@ -807,6 +835,26 @@ impl WitnessComponent for ZiskExecutor { // #[cfg(feature = "stats")] // self.stats.store_stats(); + let tables_air_ids = + [SPECIFIED_RANGES_AIR_IDS[0], VIRTUAL_TABLE_0_AIR_IDS[0], VIRTUAL_TABLE_1_AIR_IDS[0]]; + for air_id in tables_air_ids { + let setup = sctx.get_setup(ZISK_AIRGROUP_ID, air_id)?; + let n_bits = setup.stark_info.stark_struct.n_bits; + let total_cols: u64 = setup + .stark_info + .map_sections_n + .iter() + .filter(|(key, _)| *key != "const") + .map(|(_, value)| *value) + .sum(); + let cost = (1 << n_bits) * total_cols; + cost_per_type.add_cost(StatsType::Tables, cost); + } + + // Store the execution result + let execution_result = ZiskExecutionResult::new(steps, cost_per_type); + *self.execution_result.lock().unwrap() = execution_result; + Ok(()) } diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 19fb50fc9..e0e0cd9f4 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -27,7 +27,7 @@ use asm_runner::AsmRunnerMO; use fields::PrimeField64; use proofman_common::ProofCtx; use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; -use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope, ZiskExecutionResult}; +use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; /// Trait for unified execution across different emulator backends pub trait Emulator: Send + Sync { @@ -44,7 +44,7 @@ pub trait Emulator: Send + Sync { DeviceMetricsList, NestedDeviceMetricsList, Option>, - ZiskExecutionResult, + u64, ); } @@ -74,7 +74,7 @@ impl Emulator for EmulatorKind { DeviceMetricsList, NestedDeviceMetricsList, Option>, - ZiskExecutionResult, + u64, ) { match self { Self::Asm(e) => e.execute(stdin, pctx, sm_bundle, stats, caller_stats_scope), diff --git a/precompiles/arith_eq/src/arith_eq_instance.rs b/precompiles/arith_eq/src/arith_eq_instance.rs index 2e184df80..fac251ddd 100644 --- a/precompiles/arith_eq/src/arith_eq_instance.rs +++ b/precompiles/arith_eq/src/arith_eq_instance.rs @@ -13,11 +13,11 @@ use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; +use zisk_common::StatsType; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, InstanceType, OperationBusData, PayloadType, OPERATION_BUS_ID, }; - use zisk_core::ZiskOperationType; use zisk_pil::ArithEqTrace; @@ -119,6 +119,10 @@ impl Instance for ArithEqInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Precompiled + } + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { let (num_ops, collect_skipper) = self.collect_info[&chunk_id]; Some(Box::new(ArithEqCollector::new(num_ops, collect_skipper))) diff --git a/precompiles/arith_eq_384/src/arith_eq_384_instance.rs b/precompiles/arith_eq_384/src/arith_eq_384_instance.rs index be5aa91e1..b14626b46 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384_instance.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384_instance.rs @@ -9,6 +9,7 @@ use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; +use zisk_common::StatsType; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, InstanceType, OperationBusData, PayloadType, OPERATION_BUS_ID, @@ -122,6 +123,10 @@ impl Instance for ArithEq384Instance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Precompiled + } + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { let (num_ops, collect_skipper) = self.collect_info[&chunk_id]; Some(Box::new(ArithEq384Collector::new(num_ops, collect_skipper))) diff --git a/precompiles/big_int/src/add256_instance.rs b/precompiles/big_int/src/add256_instance.rs index 20d441bc8..5594fc50d 100644 --- a/precompiles/big_int/src/add256_instance.rs +++ b/precompiles/big_int/src/add256_instance.rs @@ -9,6 +9,7 @@ use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; +use zisk_common::StatsType; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, @@ -100,6 +101,10 @@ impl Instance for Add256Instance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Precompiled + } + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { assert_eq!( self.ictx.plan.air_id, diff --git a/precompiles/dma/src/dma/dma_instance.rs b/precompiles/dma/src/dma/dma_instance.rs index 9d37c60a8..f651df68e 100644 --- a/precompiles/dma/src/dma/dma_instance.rs +++ b/precompiles/dma/src/dma/dma_instance.rs @@ -10,6 +10,7 @@ use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::sync::Arc; use zisk_common::ChunkId; +use zisk_common::StatsType; use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; use zisk_pil::DmaTrace; @@ -97,6 +98,10 @@ impl Instance for DmaInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Precompiled + } + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { assert_eq!( self.ictx.plan.air_id, diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs index 054d5c847..e2596284f 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs @@ -9,6 +9,7 @@ use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::sync::Arc; use zisk_common::ChunkId; +use zisk_common::StatsType; use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; use zisk_pil::Dma64AlignedTrace; @@ -117,6 +118,10 @@ impl Instance for Dma64AlignedInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Precompiled + } + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { assert_eq!( self.ictx.plan.air_id, diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs index 2d5dc5090..1b249da1d 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs @@ -9,6 +9,7 @@ use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::sync::Arc; use zisk_common::ChunkId; +use zisk_common::StatsType; use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; use zisk_pil::DmaPrePostTrace; @@ -98,6 +99,10 @@ impl Instance for DmaPrePostInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Precompiled + } + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { assert_eq!( self.ictx.plan.air_id, diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs index 174a893d0..561a516c0 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs @@ -9,6 +9,7 @@ use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::sync::Arc; use zisk_common::ChunkId; +use zisk_common::StatsType; use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; use zisk_pil::DmaUnalignedTrace; @@ -117,6 +118,10 @@ impl Instance for DmaUnalignedInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Precompiled + } + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { assert_eq!( self.ictx.plan.air_id, diff --git a/precompiles/keccakf/src/keccakf_instance.rs b/precompiles/keccakf/src/keccakf_instance.rs index ba901967b..de7dbc41c 100644 --- a/precompiles/keccakf/src/keccakf_instance.rs +++ b/precompiles/keccakf/src/keccakf_instance.rs @@ -7,6 +7,7 @@ use crate::{KeccakfInput, KeccakfSM}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{any::Any, collections::HashMap, sync::Arc}; +use zisk_common::StatsType; use zisk_common::{ BusDevice, BusId, CheckPoint, ChunkId, CollectSkipper, ExtOperationData, Instance, InstanceCtx, InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, @@ -112,6 +113,10 @@ impl Instance for KeccakfInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Precompiled + } + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { assert_eq!( self.ictx.plan.air_id, diff --git a/precompiles/poseidon2/src/poseidon2_instance.rs b/precompiles/poseidon2/src/poseidon2_instance.rs index 90b6882a2..0955b0f1e 100644 --- a/precompiles/poseidon2/src/poseidon2_instance.rs +++ b/precompiles/poseidon2/src/poseidon2_instance.rs @@ -9,6 +9,7 @@ use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; +use zisk_common::StatsType; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, @@ -102,6 +103,10 @@ impl Instance for Poseidon2Instance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Precompiled + } + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { assert_eq!( self.ictx.plan.air_id, diff --git a/precompiles/sha256f/src/sha256f_instance.rs b/precompiles/sha256f/src/sha256f_instance.rs index 4da9f97c8..b06713aa2 100644 --- a/precompiles/sha256f/src/sha256f_instance.rs +++ b/precompiles/sha256f/src/sha256f_instance.rs @@ -9,6 +9,7 @@ use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{any::Any, collections::HashMap, sync::Arc}; use zisk_common::ChunkId; +use zisk_common::StatsType; use zisk_common::{ BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, @@ -100,6 +101,10 @@ impl Instance for Sha256fInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Precompiled + } + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { assert_eq!( self.ictx.plan.air_id, diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index e9c9891ed..127a6689c 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -22,7 +22,7 @@ use std::{ use zisk_common::ElfBinaryLike; use zisk_common::{ io::{StreamSource, ZiskStdin}, - ExecutorStatsHandle, ZiskExecutionResult, + ExecutorStatsHandle, StatsCostPerType, ZiskExecutionResult, }; pub struct ZiskExecuteResult { @@ -46,8 +46,16 @@ impl ZiskExecuteResult { self.publics.read() } - pub fn get_execution_steps(&self) -> &u64 { - &self.execution.steps + pub fn get_execution_steps(&self) -> u64 { + self.execution.steps + } + + pub fn get_execution_total_cost(&self) -> u64 { + self.execution.cost_per_type.total_cost() + } + + pub fn get_execution_cost_per_type(&self) -> &StatsCostPerType { + &self.execution.cost_per_type } pub fn get_duration(&self) -> Duration { @@ -82,8 +90,16 @@ impl ZiskVerifyConstraintsResult { self.publics.read() } - pub fn get_execution_steps(&self) -> &u64 { - &self.execution.steps + pub fn get_execution_steps(&self) -> u64 { + self.execution.steps + } + + pub fn get_execution_total_cost(&self) -> u64 { + self.execution.cost_per_type.total_cost() + } + + pub fn get_execution_cost_per_type(&self) -> &StatsCostPerType { + &self.execution.cost_per_type } pub fn get_duration(&self) -> Duration { @@ -604,8 +620,16 @@ impl ZiskProveResult { self.duration } - pub fn get_execution_steps(&self) -> &u64 { - &self.execution.steps + pub fn get_execution_steps(&self) -> u64 { + self.execution.steps + } + + pub fn get_execution_total_cost(&self) -> u64 { + self.execution.cost_per_type.total_cost() + } + + pub fn get_execution_cost_per_type(&self) -> &StatsCostPerType { + &self.execution.cost_per_type } pub fn get_proof_id(&self) -> Option<&String> { diff --git a/state-machines/arith/src/arith_full_instance.rs b/state-machines/arith/src/arith_full_instance.rs index c84cf545c..f3a549d85 100644 --- a/state-machines/arith/src/arith_full_instance.rs +++ b/state-machines/arith/src/arith_full_instance.rs @@ -9,6 +9,7 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{collections::HashMap, sync::Arc}; +use zisk_common::StatsType; use zisk_common::{ BusDevice, BusId, CheckPoint, ChunkId, CollectSkipper, ExtOperationData, Instance, InstanceCtx, InstanceType, OperationData, PayloadType, A, B, OP, OPERATION_BUS_ID, OP_TYPE, @@ -123,6 +124,10 @@ impl Instance for ArithFullInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Opcodes + } + /// Builds an input collector for the instance. /// /// # Arguments diff --git a/state-machines/binary/src/binary_add_instance.rs b/state-machines/binary/src/binary_add_instance.rs index 4ce176141..9c05b8f1b 100644 --- a/state-machines/binary/src/binary_add_instance.rs +++ b/state-machines/binary/src/binary_add_instance.rs @@ -9,6 +9,7 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{collections::HashMap, sync::Arc}; +use zisk_common::StatsType; use zisk_common::{ BusDevice, CheckPoint, ChunkId, CollectSkipper, Instance, InstanceCtx, InstanceType, PayloadType, @@ -128,6 +129,10 @@ impl Instance for BinaryAddInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Opcodes + } + /// Builds an input collector for the instance. /// /// # Arguments diff --git a/state-machines/binary/src/binary_basic_instance.rs b/state-machines/binary/src/binary_basic_instance.rs index 1ca42150c..861b36930 100644 --- a/state-machines/binary/src/binary_basic_instance.rs +++ b/state-machines/binary/src/binary_basic_instance.rs @@ -9,6 +9,7 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{collections::HashMap, sync::Arc}; +use zisk_common::StatsType; use zisk_common::{ BusDevice, CheckPoint, ChunkId, CollectSkipper, Instance, InstanceCtx, InstanceType, PayloadType, @@ -126,6 +127,10 @@ impl Instance for BinaryBasicInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Opcodes + } + /// Builds an input collector for the instance. /// /// # Arguments diff --git a/state-machines/binary/src/binary_extension_instance.rs b/state-machines/binary/src/binary_extension_instance.rs index 037d17f91..d01307a49 100644 --- a/state-machines/binary/src/binary_extension_instance.rs +++ b/state-machines/binary/src/binary_extension_instance.rs @@ -9,6 +9,7 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{collections::HashMap, sync::Arc}; +use zisk_common::StatsType; use zisk_common::{ BusDevice, CheckPoint, ChunkId, CollectSkipper, Instance, InstanceCtx, InstanceType, PayloadType, @@ -135,6 +136,10 @@ impl Instance for BinaryExtensionInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Opcodes + } + /// Builds an input collector for the instance. /// /// # Arguments diff --git a/state-machines/mem/src/mem_align_byte_instance.rs b/state-machines/mem/src/mem_align_byte_instance.rs index e7a4fe5f2..e7e66b9a1 100644 --- a/state-machines/mem/src/mem_align_byte_instance.rs +++ b/state-machines/mem/src/mem_align_byte_instance.rs @@ -5,6 +5,7 @@ use crate::mem_align_byte_sm::{MemAlignByteTraceRowType, MemAlignByteTraceType}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{collections::HashMap, sync::Arc}; +use zisk_common::StatsType; use zisk_common::{ BusDevice, CheckPoint, ChunkId, Instance, InstanceCtx, InstanceType, PayloadType, }; @@ -72,6 +73,10 @@ impl Instance for MemAlignByteInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Memory + } + fn as_any(&self) -> &dyn std::any::Any { self } diff --git a/state-machines/mem/src/mem_align_instance.rs b/state-machines/mem/src/mem_align_instance.rs index bedd3756d..08193ce44 100644 --- a/state-machines/mem/src/mem_align_instance.rs +++ b/state-machines/mem/src/mem_align_instance.rs @@ -4,6 +4,7 @@ use mem_common::MemAlignCheckPoint; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{collections::HashMap, sync::Arc}; +use zisk_common::StatsType; use zisk_common::{ BusDevice, CheckPoint, ChunkId, Instance, InstanceCtx, InstanceType, PayloadType, }; @@ -64,6 +65,10 @@ impl Instance for MemAlignInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Memory + } + /// Builds an input collector for the instance. /// /// # Arguments diff --git a/state-machines/mem/src/mem_align_read_byte_instance.rs b/state-machines/mem/src/mem_align_read_byte_instance.rs index 597398498..7b6d7b35e 100644 --- a/state-machines/mem/src/mem_align_read_byte_instance.rs +++ b/state-machines/mem/src/mem_align_read_byte_instance.rs @@ -5,6 +5,7 @@ use crate::mem_align_byte_sm::{MemAlignReadByteTraceRowType, MemAlignReadByteTra use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{collections::HashMap, sync::Arc}; +use zisk_common::StatsType; use zisk_common::{ BusDevice, CheckPoint, ChunkId, Instance, InstanceCtx, InstanceType, PayloadType, }; @@ -72,6 +73,10 @@ impl Instance for MemAlignReadByteInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Memory + } + fn as_any(&self) -> &dyn std::any::Any { self } diff --git a/state-machines/mem/src/mem_align_write_byte_instance.rs b/state-machines/mem/src/mem_align_write_byte_instance.rs index eb497c1b9..d55a06a9e 100644 --- a/state-machines/mem/src/mem_align_write_byte_instance.rs +++ b/state-machines/mem/src/mem_align_write_byte_instance.rs @@ -5,6 +5,7 @@ use crate::mem_align_byte_sm::{MemAlignWriteByteTraceRowType, MemAlignWriteByteT use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::{collections::HashMap, sync::Arc}; +use zisk_common::StatsType; use zisk_common::{ BusDevice, CheckPoint, ChunkId, Instance, InstanceCtx, InstanceType, PayloadType, }; @@ -72,6 +73,10 @@ impl Instance for MemAlignWriteByteInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Memory + } + fn as_any(&self) -> &dyn std::any::Any { self } diff --git a/state-machines/mem/src/mem_module_instance.rs b/state-machines/mem/src/mem_module_instance.rs index cd572277a..23508ebcd 100644 --- a/state-machines/mem/src/mem_module_instance.rs +++ b/state-machines/mem/src/mem_module_instance.rs @@ -5,6 +5,7 @@ use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use proofman_util::{timer_start_debug, timer_stop_and_log_debug}; use rayon::prelude::*; use std::sync::Arc; +use zisk_common::StatsType; use zisk_common::{ BusDevice, CheckPoint, ChunkId, Instance, InstanceCtx, InstanceType, PayloadType, }; @@ -136,6 +137,10 @@ impl Instance for MemModuleInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Memory + } + fn as_any(&self) -> &dyn std::any::Any { self } diff --git a/state-machines/rom/src/rom_instance.rs b/state-machines/rom/src/rom_instance.rs index 66c9320a0..324d67072 100644 --- a/state-machines/rom/src/rom_instance.rs +++ b/state-machines/rom/src/rom_instance.rs @@ -15,6 +15,7 @@ use asm_runner::AsmRunnerRH; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::sync::Mutex; +use zisk_common::StatsType; use zisk_common::{ create_atomic_vec, BusDevice, BusId, CheckPoint, ChunkId, CounterStats, Instance, InstanceCtx, InstanceType, Metrics, PayloadType, ROM_BUS_ID, @@ -200,6 +201,10 @@ impl Instance for RomInstance { InstanceType::Instance } + fn stats_type(&self) -> StatsType { + StatsType::Memory + } + /// Builds an input collector for the instance. /// /// # Arguments From 4a359540f62ae64faee4e7d2f513551dc0c6fc44 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 11 Feb 2026 17:38:11 +0000 Subject: [PATCH 498/782] Support large hints when using unix socket --- Cargo.lock | 1 + .../coordinator/src/coordinator_errors.rs | 2 +- .../hints/src/bin/hints_socket_server.rs | 103 +++++++++--------- tools/test-env/.env | 1 + tools/test-env/build_zisk.sh | 16 ++- ziskos/entrypoint/Cargo.toml | 3 +- ziskos/entrypoint/src/hints/hint_buffer.rs | 33 ++++-- ziskos/entrypoint/src/hints/mod.rs | 24 +++- 8 files changed, 118 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d70cbce40..ea6b660b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6671,6 +6671,7 @@ dependencies = [ "sha2", "static_assertions", "tiny-keccak", + "tokio", "zisk-common", ] diff --git a/distributed/crates/coordinator/src/coordinator_errors.rs b/distributed/crates/coordinator/src/coordinator_errors.rs index 7325ce9ce..617420eac 100644 --- a/distributed/crates/coordinator/src/coordinator_errors.rs +++ b/distributed/crates/coordinator/src/coordinator_errors.rs @@ -17,7 +17,7 @@ pub enum CoordinatorError { InsufficientCapacity, // Internal errors - logged but not exposed to clients - #[error("Internal service error")] + #[error("Internal service error: {0}")] Internal(String), #[error("Worker error: {0}")] diff --git a/precompiles/hints/src/bin/hints_socket_server.rs b/precompiles/hints/src/bin/hints_socket_server.rs index d1c726de8..e933d60c4 100644 --- a/precompiles/hints/src/bin/hints_socket_server.rs +++ b/precompiles/hints/src/bin/hints_socket_server.rs @@ -55,7 +55,7 @@ fn main() -> io::Result<()> { // Open the connection (waits for client to connect) writer.open().map_err(io::Error::other)?; - println!("Client connected! Starting data transfer..."); + println!("Client connected! Starting hint data transfer..."); let shutdown = Arc::new(AtomicBool::new(false)); @@ -75,23 +75,14 @@ fn main() -> io::Result<()> { } }); - // File structure: - // - First 8 bytes: header - // - Middle: batches of hints (each hint = 26 * 8 = 208 bytes) - // - Last 8 bytes: footer - - const HINT_SIZE: usize = 26 * 8; // 208 bytes per hint - const HINTS_PER_BATCH: usize = 100; - const BATCH_SIZE: usize = HINTS_PER_BATCH * HINT_SIZE; // 20,800 bytes - - if file_data.len() < 16 { - eprintln!("Error: File too small (need at least 16 bytes for header+footer)"); - return Ok(()); - } + // Sleep 500ms + thread::sleep(Duration::from_millis(500)); let mut offset = 0; - let mut message_num = 0; + let mut hint_count = 0; + let mut first_hint = true; + let start_time = std::time::Instant::now(); loop { if shutdown.load(Ordering::Relaxed) { println!("\nShutdown requested, exiting..."); @@ -99,53 +90,65 @@ fn main() -> io::Result<()> { } if offset >= file_data.len() { - // All data sent - println!("All data sent successfully!"); - println!("Connection active. Press '0' to close..."); - while !shutdown.load(Ordering::Relaxed) { - thread::sleep(Duration::from_millis(100)); - } - break; + panic!("Reached end of file data unexpectedly!"); + } + + let mut hint_total_len = 8; + + let hint_header = u64::from_le_bytes(file_data[offset..offset + 8].try_into().unwrap()); + let hint_id = (hint_header >> 32) as u32 & 0x7FFF_FFFF; + + if first_hint { + // HINT_START + assert!(hint_id == 0, "Invalid hint file format: first hint must be START"); + println!("Received START hint"); + first_hint = false; + } + + let hint_data_len = hint_header & 0x_FFFF_FFFF; + let pad = (8 - (hint_data_len % 8)) % 8; // Padding to align to 8 bytes + let data_len_with_pad = hint_data_len + pad as u64; + + hint_total_len += data_len_with_pad; + + if (offset + hint_total_len as usize) > file_data.len() { + eprintln!("Error: Hint data length exceeds file ends"); + return Ok(()); } - // Determine what to send in this message - let (start, end) = if offset == 0 { - // First message: 8 bytes header - (0, 8) - } else if offset + 8 >= file_data.len() { - // Last message: final 8 bytes - (file_data.len() - 8, file_data.len()) - } else { - // Middle messages: batches of hints - let data_end = file_data.len() - 8; // Before footer - let remaining_data = data_end - offset; - let batch_size = std::cmp::min(BATCH_SIZE, remaining_data); - (offset, offset + batch_size) - }; - - let chunk = &file_data[start..end]; - - match writer.write(chunk) { + let data_with_pad = &file_data[offset..offset + hint_total_len as usize]; + match writer.write(data_with_pad) { Ok(_) => { - message_num += 1; - println!( - "Message {}: Sent {} bytes (offset {}-{})", - message_num, - chunk.len(), - start, - end - ); - offset = end; + if hint_count % 100 == 0 && hint_id != 0 && hint_id != 1 { + println!("#{} Hint id: 0x{:x}, sent: {} bytes, offset: {}", + hint_count, + hint_id, + hint_total_len, + offset + ); + } } Err(e) => { eprintln!("Error writing to Unix socket: {}", e); break; } } + offset += hint_total_len as usize; + + if hint_id != 1 && hint_id != 0 { + hint_count += 1; + } + + if hint_id == 1 { + // HINT_END + println!("Received END hint. All hints sent, total: {}, time elapsed: {:?}", hint_count, start_time.elapsed()); + break; + } + } println!("Closing connection..."); let _ = writer.close(); println!("Server shutting down..."); Ok(()) -} +} \ No newline at end of file diff --git a/tools/test-env/.env b/tools/test-env/.env index fd4f2f696..d1c5f1bfc 100644 --- a/tools/test-env/.env +++ b/tools/test-env/.env @@ -19,6 +19,7 @@ DISTRIBUTED_THREADS=64 PROVE_FLAGS=-a -y BUILD_GPU=0 +BUILD_HINTS=0 DISABLE_CLONE_REPO=0 DISABLE_RECURSIVE_SETUP=0 diff --git a/tools/test-env/build_zisk.sh b/tools/test-env/build_zisk.sh index 3d85549b5..8dfa5f3f3 100755 --- a/tools/test-env/build_zisk.sh +++ b/tools/test-env/build_zisk.sh @@ -115,11 +115,23 @@ main() { step "Building ZisK tools..." ensure cargo clean || return 1 ensure cargo update || return 1 - BUILD_FEATURES="" + + FEATURES=() if [[ "${BUILD_GPU}" == "1" ]]; then - BUILD_FEATURES="--features gpu" + FEATURES+=("gpu") warn "Building with GPU support..." fi + + if [[ "${BUILD_HINTS}" == "1" ]]; then + FEATURES+=("hints") + warn "Building with hints support..." + fi + + BUILD_FEATURES="" + if (( ${#FEATURES[@]} > 0 )); then + BUILD_FEATURES="--features $(IFS=,; echo "${FEATURES[*]}")" + fi + if ! (cargo build --release --target ${TARGET} ${BUILD_FEATURES}); then warn "Build failed. Trying to fix missing stddef.h..." diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index 01ba2d19d..5e8e86967 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -36,13 +36,14 @@ once_cell = { version = "1.21.3", optional = true } paste = { version = "1.0", optional = true } zisk-common = { path = "../../common", optional = true } anyhow = { workspace = true, optional = true } +tokio = { workspace = true, optional = true} [target.'cfg(all(not(all(target_os = "zkvm", target_vendor = "zisk")), zisk_hints_metrics))'.dependencies] ctor = { version = "0.2", optional = true } [features] default = ["user-hints"] -user-hints = ["dep:zisk-common", "dep:bytes", "dep:paste", "dep:once_cell", "dep:ctor", "dep:anyhow"] +user-hints = ["dep:zisk-common", "dep:bytes", "dep:paste", "dep:once_cell", "dep:ctor", "dep:anyhow", "dep:tokio"] [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index 377be4868..a84441775 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -3,6 +3,7 @@ use std::io::{self, Write}; use std::sync::{Arc, Condvar, Mutex}; pub const DEFAULT_BUFFER_LEN: usize = 1 << 20; // 1 MiB +// TODO: Set MAX_WRITE_LEN based on writer type (file or socket) pub const MAX_WRITER_LEN: usize = 128 * 1024; // 128KB is the max write size for Unix sockets pub const HEADER_LEN: usize = 8; @@ -16,6 +17,7 @@ struct HintBufferInner { commit_pos: usize, closed: bool, paused: bool, + counter: u64, } pub fn build_hint_buffer() -> Arc { @@ -25,6 +27,7 @@ pub fn build_hint_buffer() -> Arc { commit_pos: 0, closed: true, paused: false, + counter: 0, }), not_empty: Condvar::new(), }) @@ -55,6 +58,7 @@ impl HintBuffer { g.commit_pos = 0; g.closed = false; g.paused = false; + g.counter = 0; self.not_empty.notify_all(); } @@ -92,16 +96,16 @@ impl HintBuffer { crate::hints::metrics::inc_hint_count(hint_id); g.write_bytes(&header); + + g.counter += 1; + + // if g.counter == 32672 { + // panic!("Hint counter reached"); + // } } #[inline(always)] pub fn write_hint_data(&self, data: *const u8, len: usize) { - assert!( - HEADER_LEN + len <= MAX_WRITER_LEN, - "Hint size ({} bytes) exceeds max hint size ({} bytes)", - HEADER_LEN + len, - MAX_WRITER_LEN - ); let payload = unsafe { std::slice::from_raw_parts(data, len) }; self.inner.lock().unwrap().write_bytes(payload); } @@ -163,6 +167,21 @@ impl HintBuffer { buf_end = chunk_pos; } + // If single hint exceeds MAX_WRITER_LEN, write it in chunks + if hint_len > MAX_WRITER_LEN { + let mut hint_pos = 0usize; + while hint_pos < hint_len { + let chunk_size = std::cmp::min(MAX_WRITER_LEN, hint_len - hint_pos); + let hint_bytes: &[u8] = unsafe { + core::slice::from_raw_parts(chunk_base.add(chunk_pos + hint_pos), chunk_size) + }; + + writer.write_all(hint_bytes)?; + + hint_pos += chunk_size; + } + } + // Accumulate current hint into write buffer buf_end += hint_len; // Advance to next hint @@ -178,4 +197,4 @@ impl HintBuffer { } } } -} +} \ No newline at end of file diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 795b5634c..549c9e0a9 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -23,6 +23,7 @@ use std::{ io::{self, BufWriter, Write}, sync::Arc, }; +use tokio::sync::oneshot; use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; #[cfg(zisk_hints_single_thread)] @@ -97,25 +98,33 @@ pub fn init_hints() -> io::Result<()> { Ok(()) } -pub fn init_hints_file(hints_file_path: PathBuf) -> io::Result<()> { +pub fn init_hints_file(hints_file_path: PathBuf, ready: Option>) -> io::Result<()> { init_hints()?; + if let Some(tx) = ready { + let _ = tx.send(()); + } + let handle = thread::spawn(move || write_hints_to_file(hints_file_path)); HINT_WRITER_HANDLE.store(handle); Ok(()) } -pub fn init_hints_socket(socket_path: PathBuf) -> io::Result<()> { +pub fn init_hints_socket(socket_path: PathBuf, ready: Option>) -> io::Result<()> { init_hints()?; // Create the Unix socket writer (server) let mut socket_writer = UnixSocketWriter::new(&socket_path).map_err(io::Error::other)?; + // Notify that socket is ready after client connects + if let Some(tx) = ready { + let _ = tx.send(()); + } + // Open the connection (waits for client to connect) // TODO: Implement open timeout socket_writer.open().map_err(io::Error::other)?; - println!("Client connected to hints socket! Starting hint data transfer..."); let handle = thread::spawn(move || write_hints_to_socket(socket_writer)); HINT_WRITER_HANDLE.store(handle); @@ -154,6 +163,7 @@ pub fn write_hints(writer: &mut W) -> io::Result<()> { // Write hints from the buffer HINT_BUFFER.drain_to_writer(writer)?; + // Write HINT_END if !disable_prefix { let end_header: u64 = ((HINT_END as u64) << 32) | 0u64; @@ -193,6 +203,10 @@ impl UnixSocketWriter { pub fn open(&mut self) -> Result<()> { self.inner.open() } + + pub fn close(&mut self) -> Result<()> { + self.inner.close() + } } impl Write for UnixSocketWriter { @@ -210,6 +224,8 @@ fn write_hints_to_socket(mut socket_writer: UnixSocketWriter) -> io::Result<()> write_hints(&mut socket_writer)?; + socket_writer.close().map_err(io::Error::other)?; + Ok(()) } @@ -283,4 +299,4 @@ pub unsafe extern "C" fn hint_log_c(msg: *const c_char) { Ok(s) => hint_log(s), Err(_) => return, } -} +} \ No newline at end of file From ae2a9b781a1f229e7ae6c14a165c61ed6190cf11 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:50:46 +0000 Subject: [PATCH 499/782] fix hint processor messages --- precompiles/hints/src/hints_processor.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 5087225ec..4a0a21d35 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -354,7 +354,28 @@ impl HintsProcessor { let num_hints = self.num_hint.load(Ordering::Relaxed); - info!("··· Processed {} hints", num_hints); + if tracing::enabled!(tracing::Level::DEBUG) { + let elapsed = self.instant.lock().as_ref().unwrap().unwrap().elapsed(); + let rate = num_hints as f64 / elapsed.as_secs_f64(); + + let (value, unit) = if rate >= 1_000_000.0 { + (rate / 1_000_000.0, "MHz") + } else if rate >= 1_000.0 { + (rate / 1_000.0, "kHz") + } else { + (rate, "Hz") + }; + + debug!( + "Processed {} hints in {:.0?} ({}{})", + num_hints, + elapsed, + value.round(), + unit + ); + } else { + info!("··· Processed {} hints", num_hints); + } break; } From eb8fb1d52b64af6e966d064778b3ffbf74abbdd0 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 11 Feb 2026 17:53:15 +0000 Subject: [PATCH 500/782] Adding total hints count in print_metrics function --- ziskos/entrypoint/src/hints/metrics.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ziskos/entrypoint/src/hints/metrics.rs b/ziskos/entrypoint/src/hints/metrics.rs index 06d436cdf..2f6cf9e06 100644 --- a/ziskos/entrypoint/src/hints/metrics.rs +++ b/ziskos/entrypoint/src/hints/metrics.rs @@ -1,3 +1,4 @@ +use num_traits::float::TotalOrder; use once_cell::sync::Lazy; use std::{collections::HashMap, sync::RwLock}; @@ -27,10 +28,13 @@ pub(crate) fn inc_hint_count(hint_id: u32) { pub(crate) fn print_metrics() { let hints = crate::hints::metrics::HINTS_METRICS.read().expect("HINTS_METRICS poisoned"); + let mut total_hints = 0; println!("Hints usage summary:"); for (_, info) in hints.iter() { if info.count > 0 { println!(" {}: {}", info.hint_name, info.count); } + total_hints += info.count; } + println!("Total hints: {}", total_hints); } From b265c906896393106aeb57099373c1ce7f020b83 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 11 Feb 2026 18:49:22 +0000 Subject: [PATCH 501/782] Fix double-write bug for large hints --- ziskos/entrypoint/src/hints/hint_buffer.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index a84441775..293994d0c 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -17,7 +17,7 @@ struct HintBufferInner { commit_pos: usize, closed: bool, paused: bool, - counter: u64, + // counter: u64, } pub fn build_hint_buffer() -> Arc { @@ -27,7 +27,7 @@ pub fn build_hint_buffer() -> Arc { commit_pos: 0, closed: true, paused: false, - counter: 0, + // counter: 0, }), not_empty: Condvar::new(), }) @@ -58,7 +58,7 @@ impl HintBuffer { g.commit_pos = 0; g.closed = false; g.paused = false; - g.counter = 0; + // g.counter = 0; self.not_empty.notify_all(); } @@ -97,7 +97,7 @@ impl HintBuffer { g.write_bytes(&header); - g.counter += 1; + // g.counter += 1; // if g.counter == 32672 { // panic!("Hint counter reached"); @@ -180,10 +180,14 @@ impl HintBuffer { hint_pos += chunk_size; } + // Reset write buffer + buf_start = chunk_pos; + buf_end = chunk_pos; + } else { + // Accumulate current hint into write buffer + buf_end += hint_len; } - // Accumulate current hint into write buffer - buf_end += hint_len; // Advance to next hint chunk_pos += hint_len; } From eed36b854ea909efb3b35ab282d54642a6c11318 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 11 Feb 2026 18:55:49 +0000 Subject: [PATCH 502/782] Refactor hint metrics count --- ziskos/entrypoint/src/hints/hint_buffer.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index 293994d0c..288599bd0 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -92,9 +92,6 @@ impl HintBuffer { let mut g = self.inner.lock().unwrap(); - #[cfg(zisk_hints_metrics)] - crate::hints::metrics::inc_hint_count(hint_id); - g.write_bytes(&header); // g.counter += 1; @@ -151,6 +148,12 @@ impl HintBuffer { u64::from_le_bytes(header_bytes.try_into().unwrap()) }; + #[cfg(zisk_hints_metrics)] + { + let hint_id = (hint_header >> 32) as u32 & 0x7FFF_FFFF; + crate::hints::metrics::inc_hint_count(hint_id); + } + let hint_data_len = (hint_header & 0xFFFF_FFFF) as usize; let pad = (8 - (hint_data_len & 7)) & 7; let hint_len = HEADER_LEN + hint_data_len + pad; From 35bfad0328dadada26d4265d06b3f86bd41a8e55 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 11 Feb 2026 19:01:33 +0000 Subject: [PATCH 503/782] Fix double-write bug for large hints --- ziskos/entrypoint/src/hints/hint_buffer.rs | 4 ++++ ziskos/entrypoint/src/hints/metrics.rs | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index 288599bd0..1a7e3dcad 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -183,12 +183,16 @@ impl HintBuffer { hint_pos += chunk_size; } + // Advance to next hint + chunk_pos += hint_len; // Reset write buffer buf_start = chunk_pos; buf_end = chunk_pos; } else { // Accumulate current hint into write buffer buf_end += hint_len; + // Advance to next hint + chunk_pos += hint_len; } // Advance to next hint diff --git a/ziskos/entrypoint/src/hints/metrics.rs b/ziskos/entrypoint/src/hints/metrics.rs index 2f6cf9e06..654e64284 100644 --- a/ziskos/entrypoint/src/hints/metrics.rs +++ b/ziskos/entrypoint/src/hints/metrics.rs @@ -1,4 +1,3 @@ -use num_traits::float::TotalOrder; use once_cell::sync::Lazy; use std::{collections::HashMap, sync::RwLock}; From bfe1991b394f983f986f67d2a0c2b0a4036de526 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 11 Feb 2026 19:24:52 +0000 Subject: [PATCH 504/782] Remove redundant chunk position advancement in HintBuffer implementation --- ziskos/entrypoint/src/hints/hint_buffer.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index 1a7e3dcad..f8e2eb8ef 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -194,9 +194,6 @@ impl HintBuffer { // Advance to next hint chunk_pos += hint_len; } - - // Advance to next hint - chunk_pos += hint_len; } // Flush any remaining data in write buffer From 8209511bdc6eb91090e7be0ff82681160d55e0c7 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 11 Feb 2026 20:37:00 +0000 Subject: [PATCH 505/782] Add reset_metrics function and call it in init_hints for metrics reset --- ziskos/entrypoint/src/hints/metrics.rs | 9 ++++++++- ziskos/entrypoint/src/hints/mod.rs | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ziskos/entrypoint/src/hints/metrics.rs b/ziskos/entrypoint/src/hints/metrics.rs index 654e64284..1b6ff2a46 100644 --- a/ziskos/entrypoint/src/hints/metrics.rs +++ b/ziskos/entrypoint/src/hints/metrics.rs @@ -26,7 +26,7 @@ pub(crate) fn inc_hint_count(hint_id: u32) { } pub(crate) fn print_metrics() { - let hints = crate::hints::metrics::HINTS_METRICS.read().expect("HINTS_METRICS poisoned"); + let hints = HINTS_METRICS.read().expect("HINTS_METRICS poisoned"); let mut total_hints = 0; println!("Hints usage summary:"); for (_, info) in hints.iter() { @@ -37,3 +37,10 @@ pub(crate) fn print_metrics() { } println!("Total hints: {}", total_hints); } + +pub(crate) fn reset_metrics() { + let mut hints = HINTS_METRICS.write().expect("HINTS_METRICS poisoned"); + for (_, info) in hints.iter_mut() { + info.count = 0; + } +} diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 549c9e0a9..8a88b337a 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -93,6 +93,9 @@ pub fn init_hints() -> io::Result<()> { } } + #[cfg(zisk_hints_metrics)] + crate::hints::metrics::reset_metrics(); + HINT_BUFFER.reset(); Ok(()) From 9d6b7c0f627d380e8d74a184d391ba8ed712f838 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:00:09 +0000 Subject: [PATCH 506/782] improve hints processor reset --- precompiles/hints/src/hints_processor.rs | 38 ++++++++++-------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 4a0a21d35..df98c7347 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -72,6 +72,15 @@ impl HintProcessorState { generation: AtomicUsize::new(0), } } + + fn reset(&self) { + self.error_flag.store(false, Ordering::Release); + self.next_seq.store(0, Ordering::Relaxed); + self.generation.fetch_add(1, Ordering::SeqCst); + let mut queue = self.queue.lock().unwrap(); + queue.buffer.clear(); + queue.next_drain_seq = 0; + } } /// Type alias for custom hint handler functions. @@ -320,8 +329,6 @@ impl HintsProcessor { idx )); } - // Reset global sequence and buffer at stream start - self.reset_state(); // Mark stream as active self.stream_active.store(true, Ordering::Release); // Control hint only; skip processing @@ -612,26 +619,6 @@ impl HintsProcessor { Ok(()) } - /// Resets the processor state, clearing any errors and the reorder buffer. - /// - /// This should be called to start a fresh processing session after an error - /// or when you want to reset the global sequence counter. - /// - /// Increments the generation counter to invalidate any in-flight workers - /// from the previous session, preventing them from corrupting the new state. - fn reset_state(&self) { - // Clear error flag - use Release to synchronize with Acquire loads in workers - self.state.error_flag.store(false, Ordering::Release); - // Reset sequence counter - Relaxed is sufficient as it's only used within mutex - self.state.next_seq.store(0, Ordering::Relaxed); - // Increment generation with SeqCst to invalidate stale workers - // This provides a total ordering fence that synchronizes with worker generation checks - self.state.generation.fetch_add(1, Ordering::SeqCst); - let mut queue = self.state.queue.lock().unwrap(); - queue.buffer.clear(); - queue.next_drain_seq = 0; - } - /// Dispatches a single hint to its appropriate handler based on hint type. /// /// # Arguments @@ -708,8 +695,13 @@ impl HintsProcessor { } fn reset(&self) { - self.pending_partial.lock().unwrap().take(); + self.num_hint.store(0, Ordering::Relaxed); + self.state.reset(); + self.stats.as_ref().map(|s| s.lock().unwrap().clear()); self.hints_sink.reset(); + self.stream_active.store(false, Ordering::Release); + self.instant.lock().unwrap().take(); + self.pending_partial.lock().unwrap().take(); } } From 240904b979ef10225f4422b373f26776721aec3c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:00:52 +0000 Subject: [PATCH 507/782] refactor: move num hints printing to a fn --- precompiles/hints/src/hints_processor.rs | 47 ++++++++++++------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index df98c7347..ea0f8c9ae 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -359,30 +359,7 @@ impl HintsProcessor { )); } - let num_hints = self.num_hint.load(Ordering::Relaxed); - - if tracing::enabled!(tracing::Level::DEBUG) { - let elapsed = self.instant.lock().as_ref().unwrap().unwrap().elapsed(); - let rate = num_hints as f64 / elapsed.as_secs_f64(); - - let (value, unit) = if rate >= 1_000_000.0 { - (rate / 1_000_000.0, "MHz") - } else if rate >= 1_000.0 { - (rate / 1_000.0, "kHz") - } else { - (rate, "Hz") - }; - - debug!( - "Processed {} hints in {:.0?} ({}{})", - num_hints, - elapsed, - value.round(), - unit - ); - } else { - info!("··· Processed {} hints", num_hints); - } + self.print_num_processed_hints(); break; } @@ -452,6 +429,28 @@ impl HintsProcessor { Ok(has_ctrl_end) } + /// Prints the total number of processed hints and processing rate if in debug mode. + fn print_num_processed_hints(&self) { + let num_hints = self.num_hint.load(Ordering::Relaxed); + + if tracing::enabled!(tracing::Level::DEBUG) { + let elapsed = self.instant.lock().as_ref().unwrap().unwrap().elapsed(); + let rate = num_hints as f64 / elapsed.as_secs_f64(); + + let (value, unit) = if rate >= 1_000_000.0 { + (rate / 1_000_000.0, "MHz") + } else if rate >= 1_000.0 { + (rate / 1_000.0, "kHz") + } else { + (rate, "Hz") + }; + + debug!("Processed {} hints in {:.0?} ({}{})", num_hints, elapsed, value.round(), unit); + } else { + info!("··· Processed {} hints", num_hints); + } + } + /// Worker thread that processes a single hint and stores the result. /// /// # Arguments From b3d64564d9d792b4476f45677fd7f727322f2e1d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 12 Feb 2026 08:49:52 +0000 Subject: [PATCH 508/782] added sem draining in hints shared memory --- emulator-asm/asm-runner/src/hints_shmem.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index 3bf9eb266..dbd527a71 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -247,5 +247,12 @@ impl StreamSink for HintsShmem { let mut unified = self.unified.borrow_mut(); unified.control_writer.write_u64_at(0, 0); unified.data_writer.reset(); + + // Drain stale semaphore signals from previous execution + let mut separate = self.separate.borrow_mut(); + for res in separate.iter_mut() { + while res.sem_available.try_wait().is_ok() {} + while res.sem_read.try_wait().is_ok() {} + } } } From df5c6aff7732f5ceb384a7c37326e2de48d10d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Thu, 12 Feb 2026 10:30:39 +0100 Subject: [PATCH 509/782] Preparing for sdk crate (#780) * Preparing for sdk crate * Cargo update * Cargo clippy --- Cargo.lock | 659 +++--------------- Cargo.toml | 1 - cli/Cargo.toml | 5 +- common/Cargo.toml | 8 +- core/Cargo.toml | 4 - emulator-asm/asm-runner/Cargo.toml | 2 - emulator/Cargo.toml | 2 - examples/Cargo.lock | 495 ++----------- examples/Cargo.toml | 4 +- examples/sha-hasher/host/Cargo.toml | 4 +- examples/sha-hasher/host/bin/compressed.rs | 5 +- examples/sha-hasher/host/bin/execute.rs | 5 +- examples/sha-hasher/host/bin/plonk.rs | 5 +- examples/sha-hasher/host/bin/prove.rs | 4 +- .../sha-hasher/host/bin/verify-constraints.rs | 6 +- examples/sha-hasher/host/bin/ziskemu.rs | 5 +- examples/sha-hasher/host/build.rs | 4 +- examples/sha-hasher/host/src/main.rs | 5 +- executor/Cargo.toml | 4 +- pil/Cargo.toml | 1 - precompiles/arith_eq/Cargo.toml | 2 - precompiles/arith_eq_384/Cargo.toml | 2 - precompiles/hints/src/hints_processor.rs | 4 +- riscv/Cargo.toml | 3 - sdk/Cargo.toml | 6 +- sdk/src/lib.rs | 4 + state-machines/frequent-ops/Cargo.toml | 4 - state-machines/main/Cargo.toml | 1 - state-machines/mem-common/Cargo.toml | 2 - state-machines/mem/Cargo.toml | 1 - witness-computation/Cargo.toml | 1 - ziskbuild/Cargo.toml | 6 +- ziskos-hints/Cargo.toml | 4 - ziskos/entrypoint/Cargo.toml | 1 - 34 files changed, 171 insertions(+), 1098 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d70cbce40..39ced623c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -24,7 +15,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -39,15 +29,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aligned-vec" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" -dependencies = [ - "equator", -] - [[package]] name = "alloca" version = "0.4.0" @@ -205,7 +186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -218,7 +199,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -279,7 +260,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -315,7 +296,6 @@ name = "asm-runner" version = "0.16.0" dependencies = [ "anyhow", - "clap", "libc", "mem-common", "mem-planner-cpp", @@ -323,7 +303,6 @@ dependencies = [ "rayon", "thiserror 2.0.18", "tracing", - "ureq", "zisk-common", "zisk-core", ] @@ -352,7 +331,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "synstructure", ] @@ -364,7 +343,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -386,7 +365,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -397,7 +376,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -424,9 +403,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" dependencies = [ "cc", "cmake", @@ -477,39 +456,12 @@ dependencies = [ "tower-service", ] -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link 0.2.1", -] - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "bincode" version = "1.3.3" @@ -525,7 +477,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.10.0", + "bitflags", "cexpr", "clang-sys", "itertools 0.12.1", @@ -536,15 +488,9 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.114", + "syn 2.0.115", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.10.0" @@ -597,7 +543,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -681,7 +627,6 @@ dependencies = [ "zisk-pil", "zisk-sdk", "zisk-verifier", - "zstd", ] [[package]] @@ -830,7 +775,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -906,12 +851,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "const-random" version = "0.1.18" @@ -1132,23 +1071,11 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -1182,18 +1109,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "fields", "num-bigint", "num-traits", - "rand 0.9.2", ] [[package]] @@ -1223,7 +1149,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1234,7 +1160,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1274,16 +1200,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "zeroize", -] - [[package]] name = "der-parser" version = "10.0.0" @@ -1325,7 +1241,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1335,7 +1251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1345,9 +1261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", - "subtle", ] [[package]] @@ -1379,7 +1293,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1397,20 +1311,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - [[package]] name = "educe" version = "0.6.0" @@ -1420,7 +1320,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1435,25 +1335,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "encode_unicode" version = "1.0.0" @@ -1486,7 +1367,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1498,49 +1379,6 @@ dependencies = [ "num_threads", ] -[[package]] -name = "env_filter" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - -[[package]] -name = "equator" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" -dependencies = [ - "equator-macro", -] - -[[package]] -name = "equator-macro" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1590,12 +1428,10 @@ dependencies = [ "precomp-poseidon2", "precomp-sha256f", "precompiles-common", - "precompiles-hints", "proofman", "proofman-common", "proofman-util", "rayon", - "rom-setup", "sm-arith", "sm-binary", "sm-frequent-ops", @@ -1628,20 +1464,10 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "cfg-if", "num-bigint", @@ -1655,18 +1481,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "findshlibs" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" -dependencies = [ - "cc", - "lazy_static", - "libc", - "winapi", -] - [[package]] name = "fixedbitset" version = "0.5.7" @@ -1766,7 +1580,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1801,13 +1615,12 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -1850,19 +1663,13 @@ dependencies = [ "wasip3", ] -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - [[package]] name = "git2" version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.10.0", + "bitflags", "libc", "libgit2-sys", "log", @@ -1875,17 +1682,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "h2" version = "0.4.13" @@ -1959,15 +1755,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - [[package]] name = "home" version = "0.5.12" @@ -2267,24 +2054,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "inferno" -version = "0.11.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" -dependencies = [ - "ahash", - "indexmap", - "is-terminal", - "itoa", - "log", - "num-format", - "once_cell", - "quick-xml", - "rgb", - "str_stack", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -2360,30 +2129,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" -[[package]] -name = "jiff" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-static" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] - [[package]] name = "jni" version = "0.21.1" @@ -2426,12 +2171,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" - [[package]] name = "json5" version = "0.4.1" @@ -2443,20 +2182,6 @@ dependencies = [ "serde", ] -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", - "sha2", - "signature", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -2542,7 +2267,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.10.0", + "bitflags", "libc", ] @@ -2614,14 +2339,12 @@ dependencies = [ "fields", "num-bigint", "num-traits", - "pil-std-lib", "proofman-common", "proofman-macros", "proofman-util", "rayon", "static_assertions", "tracing", - "witness", "zisk-common", "zisk-core", "zisk-pil", @@ -2719,7 +2442,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeff6bd154a309b2ada5639b2661ca6ae4599b34e8487dc276d2cd637da2d76" dependencies = [ - "bitflags 2.10.0", + "bitflags", "itoa", ] @@ -2740,17 +2463,6 @@ dependencies = [ "windows 0.61.3", ] -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -2854,7 +2566,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.10.0", + "bitflags", ] [[package]] @@ -3033,7 +2745,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -3060,7 +2772,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "colored", "fields", @@ -3094,7 +2806,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -3109,16 +2821,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.32" @@ -3159,15 +2861,6 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" -[[package]] -name = "portable-atomic-util" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" -dependencies = [ - "portable-atomic", -] - [[package]] name = "potential_utf" version = "0.1.4" @@ -3183,29 +2876,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "pprof" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afad4d4df7b31280028245f152d5a575083e2abb822d05736f5e47653e77689f" -dependencies = [ - "aligned-vec", - "backtrace", - "cfg-if", - "criterion 0.5.1", - "findshlibs", - "inferno", - "libc", - "log", - "nix", - "once_cell", - "smallvec", - "spin", - "symbolic-demangle", - "tempfile", - "thiserror 1.0.69", -] - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -3225,11 +2895,9 @@ dependencies = [ "ark-secp256r1", "ark-std", "fields", - "k256", "lazy_static", "lib-c", "mem-common", - "nom", "num-bigint", "num-traits", "path-clean", @@ -3263,11 +2931,9 @@ dependencies = [ "ark-secp256k1", "ark-std", "fields", - "k256", "lazy_static", "lib-c", "mem-common", - "nom", "num-bigint", "num-traits", "path-clean", @@ -3444,7 +3110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -3468,7 +3134,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "bincode", "blake3", @@ -3504,7 +3170,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "bincode", "borsh", @@ -3515,7 +3181,6 @@ dependencies = [ "csv", "env", "fields", - "indexmap", "lazy_static", "libloading", "mpi", @@ -3537,7 +3202,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "fields", "itoa", @@ -3550,18 +3215,17 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "proc-macro2", "quote", - "rayon", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "crossbeam-channel", "tracing", @@ -3570,21 +3234,19 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "bincode", "bytemuck", "colored", - "fields", "serde", "sysinfo 0.35.2", - "tracing", ] [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "bytemuck", "fields", @@ -3620,7 +3282,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.114", + "syn 2.0.115", "tempfile", ] @@ -3634,7 +3296,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -3652,7 +3314,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" dependencies = [ - "bitflags 2.10.0", + "bitflags", "memchr", "unicase", ] @@ -3666,15 +3328,6 @@ dependencies = [ "pulldown-cmark", ] -[[package]] -name = "quick-xml" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" -dependencies = [ - "memchr", -] - [[package]] name = "quinn" version = "0.11.9" @@ -3846,7 +3499,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags", ] [[package]] @@ -3930,25 +3583,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "rgb" -version = "0.8.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" -dependencies = [ - "bytemuck", -] - [[package]] name = "ring" version = "0.17.14" @@ -3966,9 +3600,6 @@ dependencies = [ [[package]] name = "riscv" version = "0.16.0" -dependencies = [ - "elf", -] [[package]] name = "rom-setup" @@ -3992,7 +3623,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" dependencies = [ - "bitflags 2.10.0", + "bitflags", "once_cell", "serde", "serde_derive", @@ -4056,7 +3687,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -4185,27 +3816,13 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - [[package]] name = "security-framework" version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.10.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -4280,7 +3897,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -4391,16 +4008,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "simd-adler32" version = "0.3.8" @@ -4465,16 +4072,12 @@ dependencies = [ "clap", "fields", "num-bigint", - "pil-std-lib", "proofman-common", - "proofman-macros", "proofman-util", "rayon", "static_assertions", "tracing", - "zisk-common", "zisk-core", - "zisk-pil", ] [[package]] @@ -4489,7 +4092,6 @@ dependencies = [ "proofman-macros", "proofman-util", "rayon", - "sm-mem", "tracing", "zisk-common", "zisk-core", @@ -4503,7 +4105,6 @@ version = "0.16.0" dependencies = [ "fields", "mem-common", - "mem-planner-cpp", "num-bigint", "num-traits", "pil-std-lib", @@ -4551,25 +4152,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -4582,12 +4164,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "str_stack" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" - [[package]] name = "strsim" version = "0.11.1" @@ -4638,9 +4214,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" dependencies = [ "proc-macro2", "quote", @@ -4664,7 +4240,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -4761,7 +4337,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -4772,7 +4348,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -4886,7 +4462,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -5053,7 +4629,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -5078,7 +4654,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.114", + "syn 2.0.115", "tempfile", "tonic-build", ] @@ -5121,7 +4697,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bytes", "futures-util", "http", @@ -5177,7 +4753,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -5304,35 +4880,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "ureq" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" -dependencies = [ - "base64", - "flate2", - "log", - "percent-encoding", - "rustls", - "rustls-pki-types", - "ureq-proto", - "utf-8", - "webpki-roots", -] - -[[package]] -name = "ureq-proto" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" -dependencies = [ - "base64", - "http", - "httparse", - "log", -] - [[package]] name = "url" version = "2.5.8" @@ -5345,12 +4892,6 @@ dependencies = [ "serde", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -5521,7 +5062,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "wasm-bindgen-shared", ] @@ -5575,7 +5116,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", + "bitflags", "hashbrown 0.15.5", "indexmap", "semver", @@ -5749,7 +5290,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -5760,7 +5301,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -6119,7 +5660,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.114", + "syn 2.0.115", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -6135,7 +5676,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -6147,7 +5688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags", "indexmap", "log", "serde", @@ -6180,7 +5721,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d4d737b299878c7e31112c151279e05a2509819f" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "colored", "fields", @@ -6260,7 +5801,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "synstructure", ] @@ -6281,7 +5822,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -6301,7 +5842,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "synstructure", ] @@ -6322,7 +5863,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -6355,7 +5896,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -6368,7 +5909,6 @@ dependencies = [ "rom-setup", "tracing", "vergen-git2", - "zisk-sdk", ] [[package]] @@ -6392,10 +5932,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "witness", "zisk-core", - "zisk-pil", - "zstd", ] [[package]] @@ -6404,18 +5941,14 @@ version = "0.16.0" dependencies = [ "elf", "fields", - "indexmap", - "json", "lib-c", "paste", "precompiles-helpers", "rayon", "riscv", "serde", - "serde_json", "sha2", "tiny-keccak", - "zisk-pil", "ziskos-hints", ] @@ -6529,7 +6062,6 @@ name = "zisk-pil" version = "0.16.0" dependencies = [ "fields", - "proofman", "proofman-common", "proofman-macros", "rayon", @@ -6554,13 +6086,13 @@ dependencies = [ "serde", "sha2", "tracing", + "zisk-build", "zisk-common", "zisk-core", "zisk-distributed-common", "zisk-verifier", "zisk-witness", "ziskemu", - "zstd", ] [[package]] @@ -6579,7 +6111,6 @@ dependencies = [ "anyhow", "asm-runner", "data-bus", - "env_logger", "executor", "fields", "mem-common", @@ -6631,13 +6162,11 @@ dependencies = [ "memmap2", "num-format", "object", - "pprof", "proofman-common", "rayon", "riscv", "sm-arith", "sm-binary", - "sm-mem", "symbolic-common", "symbolic-demangle", "sysinfo 0.38.1", @@ -6669,7 +6198,6 @@ dependencies = [ "rand 0.8.5", "serde", "sha2", - "static_assertions", "tiny-keccak", "zisk-common", ] @@ -6681,10 +6209,8 @@ dependencies = [ "anyhow", "bincode", "cfg-if", - "elliptic-curve", "fields", "getrandom 0.2.17", - "k256", "lazy_static", "lib-c", "num-bigint", @@ -6695,40 +6221,11 @@ dependencies = [ "rand 0.8.5", "serde", "sha2", - "static_assertions", "tiny-keccak", ] [[package]] name = "zmij" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 3f9a566e4..6830f5ed9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -184,7 +184,6 @@ reqwest = { version = "0.12", features = [ "json", "rustls-tls", ], default-features = false } -zstd = "0.13" vergen-git2 = { version = "9", default-features = false, features = [ "build", ] } \ No newline at end of file diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 96acb37c8..da3001d3c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -36,7 +36,6 @@ anyhow = { workspace = true } zisk-sdk = { workspace = true } zisk-verifier = { workspace = true } -zstd = { workspace = true } clap = { workspace = true } dirs = "6" rand = "0.9" @@ -53,8 +52,8 @@ mpi = { workspace = true } [features] default = [] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] -diagnostic = ["proofman/disable_distributed", "proofman-common/disable_distributed"] +disable_distributed = ["zisk-sdk/disable_distributed"] +diagnostic = ["zisk-sdk/diagnostic"] gpu = ["zisk-sdk/gpu"] packed = ["zisk-sdk/packed"] stats = [] diff --git a/common/Cargo.toml b/common/Cargo.toml index 3829ed17e..7cd718f7f 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -11,9 +11,7 @@ build = "build.rs" [dependencies] zisk-core = { workspace = true } -zisk-pil = { workspace = true } -witness = { workspace = true } proofman-common = { workspace = true } proofman-util = { workspace = true } proofman = { workspace = true } @@ -24,7 +22,6 @@ serde = { workspace = true } serde_json = { workspace = true } anyhow = { workspace = true } thiserror = { workspace = true } -zstd = { workspace = true } libc = "0.2" # QUIC networking @@ -41,10 +38,7 @@ mpi = { workspace = true } [features] default = [] -disable_distributed = [ - "proofman/disable_distributed", - "proofman-common/disable_distributed", -] +disable_distributed = [] stats = [] [lints.rust] diff --git a/core/Cargo.toml b/core/Cargo.toml index 7a840337e..ee31e79aa 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -20,18 +20,14 @@ precompiles-helpers = { workspace = true } lib-c = { workspace = true } ziskos-hints = { workspace = true } riscv = { workspace = true } -zisk-pil = { workspace = true } rayon = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true } fields = { workspace = true } sha2 = { workspace = true } paste = { workspace = true } -indexmap = { version = "2.2.6", features = ["serde"] } -json = "0.12.4" elf = "0.7.4" tiny-keccak = { version = "2.0.2", features = ["keccak"] } diff --git a/emulator-asm/asm-runner/Cargo.toml b/emulator-asm/asm-runner/Cargo.toml index f1c494afd..5cf153e6b 100644 --- a/emulator-asm/asm-runner/Cargo.toml +++ b/emulator-asm/asm-runner/Cargo.toml @@ -21,9 +21,7 @@ tracing = { workspace = true} rayon = { workspace = true} anyhow = { workspace = true} -clap = { workspace = true } libc = "0.2" -ureq = "3" thiserror = { workspace = true } [target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml index fac6e9d2b..55fc1f940 100644 --- a/emulator/Cargo.toml +++ b/emulator/Cargo.toml @@ -22,7 +22,6 @@ zisk-pil = { workspace = true } riscv = { workspace = true } data-bus = { workspace = true } rayon = { workspace = true } -sm-mem = { workspace = true } mem-common = { workspace = true } sm-arith = { workspace = true } sm-binary = { workspace = true } @@ -42,7 +41,6 @@ vergen-git2 = { workspace = true } [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } -pprof = { version = "0.14.0", features = ["flamegraph", "criterion"] } [[bench]] name = "benchmark" diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 4e242f932..4bd2620a4 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -171,7 +171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -184,7 +184,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -245,7 +245,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -275,7 +275,6 @@ name = "asm-runner" version = "0.16.0" dependencies = [ "anyhow", - "clap", "libc", "mem-common", "mem-planner-cpp", @@ -283,7 +282,6 @@ dependencies = [ "rayon", "thiserror 2.0.18", "tracing", - "ureq", "zisk-common", "zisk-core", ] @@ -312,7 +310,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "synstructure", ] @@ -324,7 +322,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -345,9 +343,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" dependencies = [ "cc", "cmake", @@ -355,24 +353,12 @@ dependencies = [ "fs_extra", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "bincode" version = "1.3.3" @@ -399,7 +385,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -451,7 +437,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -591,9 +577,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" dependencies = [ "clap_builder", "clap_derive", @@ -601,9 +587,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" dependencies = [ "anstream", "anstyle", @@ -620,14 +606,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "cmake" @@ -663,12 +649,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "constant_time_eq" version = "0.4.2" @@ -789,23 +769,11 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -839,18 +807,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "fields", "num-bigint", "num-traits", - "rand 0.9.2", ] [[package]] @@ -880,7 +847,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -891,7 +858,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -917,16 +884,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "zeroize", -] - [[package]] name = "der-parser" version = "10.0.0" @@ -968,7 +925,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -978,7 +935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -988,9 +945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", - "subtle", ] [[package]] @@ -1001,7 +956,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1010,20 +965,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - [[package]] name = "educe" version = "0.6.0" @@ -1033,7 +974,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1048,25 +989,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "enum-ordinalize" version = "4.3.2" @@ -1084,7 +1006,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -1096,29 +1018,6 @@ dependencies = [ "num_threads", ] -[[package]] -name = "env_filter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1157,12 +1056,10 @@ dependencies = [ "precomp-poseidon2", "precomp-sha256f", "precompiles-common", - "precompiles-hints", "proofman", "proofman-common", "proofman-util", "rayon", - "rom-setup", "sm-arith", "sm-binary", "sm-frequent-ops", @@ -1195,20 +1092,10 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "cfg-if", "num-bigint", @@ -1273,13 +1160,12 @@ checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -1341,17 +1227,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "hashbrown" version = "0.15.5" @@ -1380,15 +1255,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - [[package]] name = "home" version = "0.5.12" @@ -1398,22 +1264,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - [[package]] name = "iana-time-zone" version = "0.1.65" @@ -1603,30 +1453,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" -[[package]] -name = "jiff" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-static" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] - [[package]] name = "jni" version = "0.21.1" @@ -1669,26 +1495,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" - -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", - "sha2", - "signature", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -1826,14 +1632,12 @@ dependencies = [ "fields", "num-bigint", "num-traits", - "pil-std-lib", "proofman-common", "proofman-macros", "proofman-util", "rayon", "static_assertions", "tracing", - "witness", "zisk-common", "zisk-core", "zisk-pil", @@ -2151,7 +1955,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "colored", "fields", @@ -2174,37 +1978,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "portable-atomic-util" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" -dependencies = [ - "portable-atomic", -] - [[package]] name = "potential_utf" version = "0.1.4" @@ -2239,11 +2018,9 @@ dependencies = [ "ark-secp256r1", "ark-std", "fields", - "k256", "lazy_static", "lib-c", "mem-common", - "nom", "num-bigint", "num-traits", "path-clean", @@ -2277,11 +2054,9 @@ dependencies = [ "ark-secp256k1", "ark-std", "fields", - "k256", "lazy_static", "lib-c", "mem-common", - "nom", "num-bigint", "num-traits", "path-clean", @@ -2457,7 +2232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -2481,7 +2256,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "bincode", "blake3", @@ -2517,7 +2292,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "bincode", "borsh", @@ -2528,7 +2303,6 @@ dependencies = [ "csv", "env", "fields", - "indexmap", "lazy_static", "libloading", "mpi", @@ -2550,7 +2324,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "fields", "itoa", @@ -2563,18 +2337,17 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "proc-macro2", "quote", - "rayon", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "crossbeam-channel", "tracing", @@ -2583,21 +2356,19 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "bincode", "bytemuck", "colored", - "fields", "serde", "sysinfo 0.35.2", - "tracing", ] [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "bytemuck", "fields", @@ -2809,16 +2580,6 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - [[package]] name = "ring" version = "0.17.14" @@ -2836,9 +2597,6 @@ dependencies = [ [[package]] name = "riscv" version = "0.16.0" -dependencies = [ - "elf", -] [[package]] name = "rom-setup" @@ -3031,20 +2789,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - [[package]] name = "security-framework" version = "3.5.1" @@ -3114,7 +2858,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -3157,8 +2901,6 @@ dependencies = [ "anyhow", "serde", "sha2", - "zisk-build", - "zisk-common", "zisk-sdk", ] @@ -3204,16 +2946,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "simd-adler32" version = "0.3.8" @@ -3278,16 +3010,12 @@ dependencies = [ "clap", "fields", "num-bigint", - "pil-std-lib", "proofman-common", - "proofman-macros", "proofman-util", "rayon", "static_assertions", "tracing", - "zisk-common", "zisk-core", - "zisk-pil", ] [[package]] @@ -3302,7 +3030,6 @@ dependencies = [ "proofman-macros", "proofman-util", "rayon", - "sm-mem", "tracing", "zisk-common", "zisk-core", @@ -3316,7 +3043,6 @@ version = "0.16.0" dependencies = [ "fields", "mem-common", - "mem-planner-cpp", "num-bigint", "num-traits", "pil-std-lib", @@ -3364,16 +3090,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3436,9 +3152,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" dependencies = [ "proc-macro2", "quote", @@ -3453,7 +3169,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -3544,7 +3260,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -3555,7 +3271,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -3659,7 +3375,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -3791,7 +3507,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -3882,35 +3598,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "ureq" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" -dependencies = [ - "base64", - "flate2", - "log", - "percent-encoding", - "rustls", - "rustls-pki-types", - "ureq-proto", - "utf-8", - "webpki-roots", -] - -[[package]] -name = "ureq-proto" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" -dependencies = [ - "base64", - "http", - "httparse", - "log", -] - [[package]] name = "url" version = "2.5.8" @@ -3923,12 +3610,6 @@ dependencies = [ "serde", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -4075,7 +3756,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "wasm-bindgen-shared", ] @@ -4141,15 +3822,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "winapi" version = "0.3.9" @@ -4280,7 +3952,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -4291,7 +3963,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -4641,7 +4313,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.114", + "syn 2.0.115", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4657,7 +4329,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4702,7 +4374,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#76130fe678ce7c191c4969458c98955bf6282007" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" dependencies = [ "colored", "fields", @@ -4771,7 +4443,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "synstructure", ] @@ -4792,7 +4464,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -4812,7 +4484,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", "synstructure", ] @@ -4833,7 +4505,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -4866,7 +4538,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.115", ] [[package]] @@ -4879,7 +4551,6 @@ dependencies = [ "rom-setup", "tracing", "vergen-git2", - "zisk-sdk", ] [[package]] @@ -4903,10 +4574,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "witness", "zisk-core", - "zisk-pil", - "zstd", ] [[package]] @@ -4915,18 +4583,14 @@ version = "0.16.0" dependencies = [ "elf", "fields", - "indexmap", - "json", "lib-c", "paste", "precompiles-helpers", "rayon", "riscv", "serde", - "serde_json", "sha2", "tiny-keccak", - "zisk-pil", "ziskos-hints", ] @@ -4954,7 +4618,6 @@ name = "zisk-pil" version = "0.16.0" dependencies = [ "fields", - "proofman", "proofman-common", "proofman-macros", "rayon", @@ -4979,13 +4642,13 @@ dependencies = [ "serde", "sha2", "tracing", + "zisk-build", "zisk-common", "zisk-core", "zisk-distributed-common", "zisk-verifier", "zisk-witness", "ziskemu", - "zstd", ] [[package]] @@ -5004,7 +4667,6 @@ dependencies = [ "anyhow", "asm-runner", "data-bus", - "env_logger", "executor", "fields", "mem-common", @@ -5052,7 +4714,6 @@ dependencies = [ "riscv", "sm-arith", "sm-binary", - "sm-mem", "symbolic-common", "symbolic-demangle", "sysinfo 0.38.1", @@ -5084,7 +4745,6 @@ dependencies = [ "rand 0.8.5", "serde", "sha2", - "static_assertions", "tiny-keccak", "zisk-common", ] @@ -5096,10 +4756,8 @@ dependencies = [ "anyhow", "bincode", "cfg-if", - "elliptic-curve", "fields", "getrandom 0.2.17", - "k256", "lazy_static", "lib-c", "num-bigint", @@ -5110,40 +4768,11 @@ dependencies = [ "rand 0.8.5", "serde", "sha2", - "static_assertions", "tiny-keccak", ] [[package]] name = "zmij" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 9f24f36f1..9bf3cbe84 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -14,6 +14,4 @@ opt-level = 0 ziskos = { path = "../ziskos/entrypoint" } # Host dependencies -zisk-sdk = { path = "../sdk" } -zisk-build = { path = "../ziskbuild" } -zisk-common = { path = "../common" } \ No newline at end of file +zisk-sdk = { path = "../sdk" } \ No newline at end of file diff --git a/examples/sha-hasher/host/Cargo.toml b/examples/sha-hasher/host/Cargo.toml index 4cfb1092f..6ff343636 100644 --- a/examples/sha-hasher/host/Cargo.toml +++ b/examples/sha-hasher/host/Cargo.toml @@ -5,14 +5,12 @@ edition = "2024" [dependencies] zisk-sdk = { workspace = true } -zisk-common = { workspace = true } anyhow = "1.0" serde = { version = "1.0", default-features = false, features = ["derive"] } sha2 = "0.10.8" [build-dependencies] -zisk-build = { workspace = true } -zisk-common = { workspace = true } +zisk-sdk = { workspace = true } [features] default = [] diff --git a/examples/sha-hasher/host/bin/compressed.rs b/examples/sha-hasher/host/bin/compressed.rs index 398698fad..d155284b4 100644 --- a/examples/sha-hasher/host/bin/compressed.rs +++ b/examples/sha-hasher/host/bin/compressed.rs @@ -1,8 +1,5 @@ use anyhow::Result; -use zisk_common::ElfBinary; -use zisk_common::io::ZiskIO; -use zisk_common::io::ZiskStdin; -use zisk_sdk::{ProofOpts, ProverClient, include_elf}; +use zisk_sdk::{ZiskStdin, ZiskIO, ElfBinary, ProofOpts, ProverClient, include_elf}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/execute.rs b/examples/sha-hasher/host/bin/execute.rs index aa47505b5..7a353566a 100644 --- a/examples/sha-hasher/host/bin/execute.rs +++ b/examples/sha-hasher/host/bin/execute.rs @@ -1,9 +1,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -use zisk_common::ElfBinary; -use zisk_common::io::ZiskIO; -use zisk_common::io::ZiskStdin; -use zisk_sdk::{ProverClient, include_elf}; +use zisk_sdk::{ZiskStdin, ZiskIO, ElfBinary, ProverClient, include_elf}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/plonk.rs b/examples/sha-hasher/host/bin/plonk.rs index 96e3020bd..8fb100e93 100644 --- a/examples/sha-hasher/host/bin/plonk.rs +++ b/examples/sha-hasher/host/bin/plonk.rs @@ -1,8 +1,5 @@ use anyhow::Result; -use zisk_common::ElfBinary; -use zisk_common::io::ZiskIO; -use zisk_common::io::ZiskStdin; -use zisk_sdk::{ProverClient, ZiskProofWithPublicValues, include_elf}; +use zisk_sdk::{ZiskStdin, ZiskIO, ElfBinary, ProverClient, ZiskProofWithPublicValues, include_elf}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/prove.rs b/examples/sha-hasher/host/bin/prove.rs index 310c4c50d..96883e8da 100644 --- a/examples/sha-hasher/host/bin/prove.rs +++ b/examples/sha-hasher/host/bin/prove.rs @@ -1,10 +1,8 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use zisk_common::ElfBinary; -use zisk_common::io::ZiskIO; -use zisk_common::io::ZiskStdin; use zisk_sdk::{ + ZiskStdin, ZiskIO, ElfBinary, ProofOpts, ProverClient, ZiskProof, ZiskProofWithPublicValues, ZiskPublics, include_elf, }; diff --git a/examples/sha-hasher/host/bin/verify-constraints.rs b/examples/sha-hasher/host/bin/verify-constraints.rs index df79efd5c..53a8c003b 100644 --- a/examples/sha-hasher/host/bin/verify-constraints.rs +++ b/examples/sha-hasher/host/bin/verify-constraints.rs @@ -1,11 +1,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use zisk_common::ElfBinaryFromFile; -use zisk_common::io::ZiskIO; -use zisk_common::io::ZiskStdin; -use zisk_sdk::ProverClient; -use zisk_sdk::elf_path; +use zisk_sdk::{ProverClient, elf_path, ZiskStdin, ZiskIO, ElfBinaryFromFile}; #[derive(Serialize, Deserialize, Debug)] struct Output { diff --git a/examples/sha-hasher/host/bin/ziskemu.rs b/examples/sha-hasher/host/bin/ziskemu.rs index 4d8154922..80a1c1c1f 100644 --- a/examples/sha-hasher/host/bin/ziskemu.rs +++ b/examples/sha-hasher/host/bin/ziskemu.rs @@ -1,9 +1,6 @@ use anyhow::Result; use std::path::PathBuf; -use zisk_common::ElfBinaryFromFile; -use zisk_common::io::ZiskIO; -use zisk_common::io::ZiskStdin; -use zisk_sdk::{EmuOptions, elf_path, ziskemu}; +use zisk_sdk::{EmuOptions, elf_path, ziskemu, ZiskStdin, ZiskIO, ElfBinaryFromFile}; fn main() -> Result<()> { let elf_path = elf_path!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/build.rs b/examples/sha-hasher/host/build.rs index 9de57067b..da463c8f5 100644 --- a/examples/sha-hasher/host/build.rs +++ b/examples/sha-hasher/host/build.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; -use zisk_common::io::{ZiskIO, ZiskStdin}; +use zisk_sdk::{ZiskIO, ZiskStdin, build_program}; fn main() { - zisk_build::build_program("../guest"); + build_program("../guest"); let n = 1000u32; let stdin_save = ZiskStdin::new(); stdin_save.write(&n); diff --git a/examples/sha-hasher/host/src/main.rs b/examples/sha-hasher/host/src/main.rs index 4e9d1188f..bc0ce6ee3 100644 --- a/examples/sha-hasher/host/src/main.rs +++ b/examples/sha-hasher/host/src/main.rs @@ -1,8 +1,5 @@ use anyhow::Result; -use zisk_common::ElfBinary; -use zisk_common::io::ZiskIO; -use zisk_common::io::ZiskStdin; -use zisk_sdk::{ProofOpts, ProverClient, include_elf}; +use zisk_sdk::{ZiskStdin, ZiskIO, ElfBinary, ProofOpts, ProverClient, include_elf}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/executor/Cargo.toml b/executor/Cargo.toml index dd04a23d9..b36c6222a 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -10,7 +10,6 @@ categories = { workspace = true } [dependencies] sm-main = { workspace = true } sm-rom = { workspace = true } -rom-setup = { workspace = true } zisk-pil = { workspace = true } ziskemu = { workspace = true } zisk-core = { workspace = true } @@ -29,7 +28,6 @@ tracing = { workspace = true } itertools = { workspace = true } rayon = { workspace = true } pil-std-lib = { workspace = true } -precompiles-hints = { workspace = true } anyhow = { workspace = true } crossbeam = "0.8.4" @@ -52,7 +50,7 @@ named-sem = { workspace = true } [features] default = [] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] +disable_distributed = [] gpu = ["proofman-common/gpu", "proofman/gpu", "packed"] packed = [ "proofman-common/packed", diff --git a/pil/Cargo.toml b/pil/Cargo.toml index 2182e4ab3..997e549cd 100644 --- a/pil/Cargo.toml +++ b/pil/Cargo.toml @@ -9,7 +9,6 @@ categories = { workspace = true } [dependencies] proofman-common = { workspace = true } -proofman = { workspace = true } proofman-macros = { workspace = true } fields = { workspace = true } serde = { workspace = true } diff --git a/precompiles/arith_eq/Cargo.toml b/precompiles/arith_eq/Cargo.toml index be8543698..0c9210e87 100644 --- a/precompiles/arith_eq/Cargo.toml +++ b/precompiles/arith_eq/Cargo.toml @@ -66,11 +66,9 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" regex = "1.11.1" rustfmt-wrapper = "0.2.1" -k256 = {version = "0.13", features = ["arithmetic"] } typenum = "1.16" lazy_static = "1.4" path-clean = "1.0" -nom = "7" [features] default = [] diff --git a/precompiles/arith_eq_384/Cargo.toml b/precompiles/arith_eq_384/Cargo.toml index 7f86ce6e1..6c7c29d48 100644 --- a/precompiles/arith_eq_384/Cargo.toml +++ b/precompiles/arith_eq_384/Cargo.toml @@ -52,11 +52,9 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" regex = "1.11.1" rustfmt-wrapper = "0.2.1" -k256 = {version = "0.13", features = ["arithmetic"] } typenum = "1.16" lazy_static = "1.4" path-clean = "1.0" -nom = "7" [features] default = [] diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index ea0f8c9ae..27789049a 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -696,7 +696,9 @@ impl HintsProcessor { fn reset(&self) { self.num_hint.store(0, Ordering::Relaxed); self.state.reset(); - self.stats.as_ref().map(|s| s.lock().unwrap().clear()); + if let Some(stats) = self.stats.as_ref() { + stats.lock().unwrap().clear(); + } self.hints_sink.reset(); self.stream_active.store(false, Ordering::Release); self.instant.lock().unwrap().take(); diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index 10fd17018..a3a27ac98 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -6,6 +6,3 @@ license = { workspace = true } keywords = { workspace = true } repository = { workspace = true } categories = { workspace = true } - -[dependencies] -elf = "0.7.4" diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index cf14eba21..094ec261c 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -18,12 +18,12 @@ rom-setup = { workspace = true } asm-runner = { workspace = true } colored = { workspace = true } tracing = { workspace = true } -zstd = { workspace = true } zisk-distributed-common = { workspace = true } executor = { workspace = true } ziskemu = { workspace = true } zisk-core = { workspace = true } zisk-verifier = { workspace = true } +zisk-build = { workspace = true } zisk-witness = { workspace = true } sha2 = { workspace = true } bincode = { workspace = true } @@ -44,7 +44,11 @@ stats = [] disable_distributed = [ "proofman/disable_distributed", "proofman-common/disable_distributed", + "executor/disable_distributed", + "zisk-common/disable_distributed", + "zisk-build/disable_distributed", ] +diagnostic = ["proofman-common/diagnostic"] [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(distributed)'] } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index cd50867f4..24a33fe46 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -16,6 +16,10 @@ pub use ziskemu::*; pub use proofman_common::VerboseMode; +pub use zisk_common::{io::*, ElfBinary, ElfBinaryFromFile}; + +pub use zisk_build::*; + #[macro_export] macro_rules! include_elf { ($arg:literal) => {{ diff --git a/state-machines/frequent-ops/Cargo.toml b/state-machines/frequent-ops/Cargo.toml index aeb69a360..19ee0e932 100644 --- a/state-machines/frequent-ops/Cargo.toml +++ b/state-machines/frequent-ops/Cargo.toml @@ -13,14 +13,10 @@ path = "src/frequent_ops_test.rs" [dependencies] zisk-core = { workspace = true } -zisk-common = { workspace = true } -zisk-pil = { workspace = true } fields = { workspace=true } proofman-common = { workspace = true } -proofman-macros = { workspace = true } proofman-util = { workspace = true } -pil-std-lib = { workspace = true } tracing = { workspace = true } clap = "4.0" diff --git a/state-machines/main/Cargo.toml b/state-machines/main/Cargo.toml index dcd1f455f..a2e93d7e3 100644 --- a/state-machines/main/Cargo.toml +++ b/state-machines/main/Cargo.toml @@ -12,7 +12,6 @@ ziskemu = { workspace = true } zisk-core = { workspace = true } zisk-common = { workspace = true } zisk-pil = { workspace = true } -sm-mem = { workspace = true } mem-common = { workspace = true } proofman-common = { workspace = true } diff --git a/state-machines/mem-common/Cargo.toml b/state-machines/mem-common/Cargo.toml index d16d27ba6..e65b51e38 100644 --- a/state-machines/mem-common/Cargo.toml +++ b/state-machines/mem-common/Cargo.toml @@ -19,8 +19,6 @@ zisk-pil = { workspace = true } proofman-common = { workspace = true } proofman-macros = { workspace = true } proofman-util = { workspace = true } -witness = { workspace = true } -pil-std-lib = { workspace = true } fields = { workspace=true } tracing = { workspace = true } rayon = { workspace = true } diff --git a/state-machines/mem/Cargo.toml b/state-machines/mem/Cargo.toml index f704382cc..727b6c3d8 100644 --- a/state-machines/mem/Cargo.toml +++ b/state-machines/mem/Cargo.toml @@ -15,7 +15,6 @@ path = "src/mem_sim.rs" zisk-core = { workspace = true } zisk-common = { workspace = true } zisk-pil = { workspace = true } -mem-planner-cpp = { workspace = true } mem-common = { workspace = true } proofman-common = { workspace = true } diff --git a/witness-computation/Cargo.toml b/witness-computation/Cargo.toml index d9e0b67a0..68f40a33a 100644 --- a/witness-computation/Cargo.toml +++ b/witness-computation/Cargo.toml @@ -42,7 +42,6 @@ tracing = { workspace = true } anyhow = { workspace = true } -env_logger = "0.11" rayon = { workspace = true } [features] diff --git a/ziskbuild/Cargo.toml b/ziskbuild/Cargo.toml index 093826c02..de1cdfff4 100644 --- a/ziskbuild/Cargo.toml +++ b/ziskbuild/Cargo.toml @@ -13,7 +13,9 @@ cargo_metadata = "0.23.0" anyhow = { workspace = true } tracing = { workspace = true } rom-setup = { workspace = true } -zisk-sdk = { workspace = true } [build-dependencies] -vergen-git2.workspace = true \ No newline at end of file +vergen-git2.workspace = true + +[features] +disable_distributed = [] \ No newline at end of file diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index c757b5517..0c7b72163 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -24,7 +24,6 @@ num-traits = { workspace = true } precompiles-helpers = { workspace = true } lazy_static = "1.5.0" -static_assertions = "1.1" rand = "0.8.5" getrandom = { version = "0.2", features = ["custom"] } cfg-if = "1.0" @@ -35,9 +34,6 @@ paste = "1.0" sha2 = { workspace = true } fields = { workspace = true } -# zisk-os specific dependencies -elliptic-curve = "0.13.8" -k256 = "0.13.4" anyhow = { workspace = true } [features] diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index 01ba2d19d..88ce977c9 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -17,7 +17,6 @@ num-traits = { workspace = true } precompiles-helpers = { workspace = true } lazy_static = "1.5.0" -static_assertions = "1.1" rand = "0.8.5" getrandom = { version = "0.2", features = ["custom"] } cfg-if = "1.0" From 26a1f6a1c9b965e60436360ab6d1703175706041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:57:03 +0000 Subject: [PATCH 510/782] Fixing an ArithEq384 bug --- precompiles/arith_eq_384/src/arith_eq_384.rs | 14 ++++++++++++-- .../entrypoint/src/zisklib/lib/bls12_381/curve.rs | 6 ++---- .../entrypoint/src/zisklib/lib/bls12_381/twist.rs | 6 ++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/precompiles/arith_eq_384/src/arith_eq_384.rs b/precompiles/arith_eq_384/src/arith_eq_384.rs index cd145a3dc..26f453a83 100644 --- a/precompiles/arith_eq_384/src/arith_eq_384.rs +++ b/precompiles/arith_eq_384/src/arith_eq_384.rs @@ -397,10 +397,11 @@ impl ArithEq384SM { let num_available_ops = self.num_available_ops; let total_inputs: usize = inputs.iter().map(|x| x.len()).sum(); + let all_ops_used = total_inputs == num_available_ops; let num_rows_filled = total_inputs * ARITH_EQ_384_ROWS_BY_OP; let num_rows_needed = if total_inputs < num_available_ops { total_inputs * ARITH_EQ_384_ROWS_BY_OP - } else if total_inputs == num_available_ops { + } else if all_ops_used { num_rows } else { panic!( @@ -468,7 +469,16 @@ impl ArithEq384SM { self.std.range_check(self.chunk_range_id, 0, chunk_range_mult); self.std.range_check(self.carry_range_id, 0, carry_range_mult); - let padding_row = ArithEq384TraceRowType::default(); + let mut padding_row = ArithEq384TraceRowType::default(); + + // In the no-op rows, the first x_are_different val should be the same as the previous one + // To make the constraint `x_are_different === 'x_are_different * (1 - CLK_0) + x_chunk_different;` + // be satisfied + if all_ops_used { + let prev_x_are_different = trace.buffer[num_rows_filled - 1].get_x_are_different(); + + padding_row.set_x_are_different(prev_x_are_different); + } trace.buffer[num_rows_filled..num_rows].par_iter_mut().for_each(|slot| *slot = padding_row); diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs index 0e7298510..137c71343 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/curve.rs @@ -385,16 +385,14 @@ pub fn sub_complete_bls12_381( if p1_is_inf && p2_is_inf { // O - O = O return G1_IDENTITY; - } - if p1_is_inf { + } else if p1_is_inf { // O - P2 = -P2 return neg_bls12_381( p2, #[cfg(feature = "hints")] hints, ); - } - if p2_is_inf { + } else if p2_is_inf { // P1 - O = P1 return *p1; } diff --git a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs index 7ff33d21b..f4a62b8de 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bls12_381/twist.rs @@ -648,16 +648,14 @@ pub fn sub_complete_twist_bls12_381( if p1_is_inf && p2_is_inf { // O - O = O return G2_IDENTITY; - } - if p1_is_inf { + } else if p1_is_inf { // O - P2 = -P2 return neg_twist_bls12_381( p2, #[cfg(feature = "hints")] hints, ); - } - if p2_is_inf { + } else if p2_is_inf { // P1 - O = P1 return *p1; } From 8530f178c0e9d3924517e168674a028c139be13c Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 12 Feb 2026 16:33:51 +0000 Subject: [PATCH 511/782] Fix Clippy --- .../hints/src/bin/hints_socket_server.rs | 17 +++++++++-------- ziskos/entrypoint/src/hints/hint_buffer.rs | 9 ++++++--- ziskos/entrypoint/src/hints/mod.rs | 14 ++++++++++---- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/precompiles/hints/src/bin/hints_socket_server.rs b/precompiles/hints/src/bin/hints_socket_server.rs index e933d60c4..6769bead3 100644 --- a/precompiles/hints/src/bin/hints_socket_server.rs +++ b/precompiles/hints/src/bin/hints_socket_server.rs @@ -120,11 +120,9 @@ fn main() -> io::Result<()> { match writer.write(data_with_pad) { Ok(_) => { if hint_count % 100 == 0 && hint_id != 0 && hint_id != 1 { - println!("#{} Hint id: 0x{:x}, sent: {} bytes, offset: {}", - hint_count, - hint_id, - hint_total_len, - offset + println!( + "#{} Hint id: 0x{:x}, sent: {} bytes, offset: {}", + hint_count, hint_id, hint_total_len, offset ); } } @@ -141,14 +139,17 @@ fn main() -> io::Result<()> { if hint_id == 1 { // HINT_END - println!("Received END hint. All hints sent, total: {}, time elapsed: {:?}", hint_count, start_time.elapsed()); + println!( + "Received END hint. All hints sent, total: {}, time elapsed: {:?}", + hint_count, + start_time.elapsed() + ); break; } - } println!("Closing connection..."); let _ = writer.close(); println!("Server shutting down..."); Ok(()) -} \ No newline at end of file +} diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index f8e2eb8ef..2631f49be 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -3,7 +3,7 @@ use std::io::{self, Write}; use std::sync::{Arc, Condvar, Mutex}; pub const DEFAULT_BUFFER_LEN: usize = 1 << 20; // 1 MiB -// TODO: Set MAX_WRITE_LEN based on writer type (file or socket) + // TODO: Set MAX_WRITE_LEN based on writer type (file or socket) pub const MAX_WRITER_LEN: usize = 128 * 1024; // 128KB is the max write size for Unix sockets pub const HEADER_LEN: usize = 8; @@ -176,7 +176,10 @@ impl HintBuffer { while hint_pos < hint_len { let chunk_size = std::cmp::min(MAX_WRITER_LEN, hint_len - hint_pos); let hint_bytes: &[u8] = unsafe { - core::slice::from_raw_parts(chunk_base.add(chunk_pos + hint_pos), chunk_size) + core::slice::from_raw_parts( + chunk_base.add(chunk_pos + hint_pos), + chunk_size, + ) }; writer.write_all(hint_bytes)?; @@ -205,4 +208,4 @@ impl HintBuffer { } } } -} \ No newline at end of file +} diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 8a88b337a..a7aa86992 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -95,13 +95,16 @@ pub fn init_hints() -> io::Result<()> { #[cfg(zisk_hints_metrics)] crate::hints::metrics::reset_metrics(); - + HINT_BUFFER.reset(); Ok(()) } -pub fn init_hints_file(hints_file_path: PathBuf, ready: Option>) -> io::Result<()> { +pub fn init_hints_file( + hints_file_path: PathBuf, + ready: Option>, +) -> io::Result<()> { init_hints()?; if let Some(tx) = ready { @@ -114,7 +117,10 @@ pub fn init_hints_file(hints_file_path: PathBuf, ready: Option>) -> io::Result<()> { +pub fn init_hints_socket( + socket_path: PathBuf, + ready: Option>, +) -> io::Result<()> { init_hints()?; // Create the Unix socket writer (server) @@ -302,4 +308,4 @@ pub unsafe extern "C" fn hint_log_c(msg: *const c_char) { Ok(s) => hint_log(s), Err(_) => return, } -} \ No newline at end of file +} From 9594298caa18646a109c90204ce92898f029e1bf Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:48:51 +0000 Subject: [PATCH 512/782] removed outdated test --- precompiles/hints/src/hints_processor.rs | 39 ------------------------ 1 file changed, 39 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 27789049a..423203a8e 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -885,45 +885,6 @@ mod tests { assert_eq!(queue.next_drain_seq, 1); } - // Stream control tests - #[test] - fn test_stream_start_resets_state() { - let p = processor(); - - // First batch increments sequence (8 bytes = 1 u64) - let batch1 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x01]; - p.process_hints(&batch1, false).unwrap(); - p.wait_for_completion().unwrap(); - - // Sequence should be at 1 - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - } - - // Send START control - should reset sequence - let start = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::Start).to_u32(), 0)]; - p.process_hints(&start, true).unwrap(); - - // Sequence should be reset to 0 - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 0); - assert!(queue.buffer.is_empty()); - } - - // Process new batch (8 bytes = 1 u64) - let batch2 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x02]; - p.process_hints(&batch2, false).unwrap(); - - let end = vec![make_ctrl_header(HintCode::Ctrl(CtrlHint::End).to_u32(), 0)]; - p.process_hints(&end, false).unwrap(); - - // Should have processed 1 hint (starting from 0 again) - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - } - #[test] fn test_stream_end_waits_until_completion() { let p = processor(); From 6bf0e902bad1509207508f85f18589aa44232374 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Feb 2026 16:55:16 +0000 Subject: [PATCH 513/782] Cargo lock --- Cargo.lock | 26 +++++++++++++------------- examples/Cargo.lock | 26 +++++++++++++------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39ced623c..121771f91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "fields", "num-bigint", @@ -1467,7 +1467,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "cfg-if", "num-bigint", @@ -2772,7 +2772,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "colored", "fields", @@ -3134,7 +3134,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "bincode", "blake3", @@ -3170,7 +3170,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "bincode", "borsh", @@ -3202,7 +3202,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "fields", "itoa", @@ -3215,7 +3215,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "proc-macro2", "quote", @@ -3225,7 +3225,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "crossbeam-channel", "tracing", @@ -3234,7 +3234,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "bincode", "bytemuck", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "bytemuck", "fields", @@ -4572,9 +4572,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.7+spec-1.1.0" +version = "1.0.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" dependencies = [ "winnow", ] @@ -5721,7 +5721,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "colored", "fields", diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 4bd2620a4..296390993 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -813,7 +813,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "fields", "num-bigint", @@ -1095,7 +1095,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "cfg-if", "num-bigint", @@ -1955,7 +1955,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "colored", "fields", @@ -2256,7 +2256,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "bincode", "blake3", @@ -2292,7 +2292,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "bincode", "borsh", @@ -2324,7 +2324,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "fields", "itoa", @@ -2337,7 +2337,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "proc-macro2", "quote", @@ -2347,7 +2347,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "crossbeam-channel", "tracing", @@ -2356,7 +2356,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "bincode", "bytemuck", @@ -2368,7 +2368,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "bytemuck", "fields", @@ -3449,9 +3449,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.7+spec-1.1.0" +version = "1.0.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" dependencies = [ "winnow", ] @@ -4374,7 +4374,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c1007fabaef139e1a2cbcd22ab77a4ce67b3dedb" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" dependencies = [ "colored", "fields", From 90ea3f0ffbb8a83e5dee4016803e94a99d5f3034 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:29:47 +0000 Subject: [PATCH 514/782] enable with_verbose and with_metrics in asm.rs --- sdk/src/prover/asm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index eb7ffeef2..6c7270df7 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -124,6 +124,8 @@ impl ProverEngine for AsmProver { .with_base_port(self.core_prover.base_port) .with_world_rank(world_rank) .with_local_rank(local_rank) + .with_verbose(self.core_prover.verbose == VerboseMode::Debug) + .with_metrics(self.core_prover.verbose == VerboseMode::Debug) .with_unlock_mapped_memory(self.core_prover.unlock_mapped_memory); asm_services.start_asm_services(&asm_mt_path, asm_runner_options)?; From a311a69fae112d73041cdde79b027dc537c72886 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:30:57 +0000 Subject: [PATCH 515/782] avoid double copy in hints relay --- .../crates/coordinator/src/hints_relay.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index f34b56473..732319309 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -107,7 +107,7 @@ impl PrecompileHintsRelay { } // Call async dispatcher - blocks on async work for zero overhead - self.send_hints_data(hints.to_vec()); + self.send_hints_data(hints); if has_ctrl_end { self.send_hints_end(); @@ -122,18 +122,17 @@ impl PrecompileHintsRelay { self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamMessageKind::Start, vec![])); } - fn send_hints_data(&self, hints: Vec) { + fn send_hints_data(&self, hints: &[u64]) { let seq_num = self.sequence_number.fetch_add(1, Ordering::SeqCst); - // Convert Vec to Vec for wire protocol + // Safe conversion: &[u64] → Vec for wire protocol let payload = unsafe { - let mut hints_vec = hints.to_vec(); - let ptr = hints_vec.as_mut_ptr() as *mut u8; - let len = hints_vec.len() * std::mem::size_of::(); - let capacity = hints_vec.capacity() * std::mem::size_of::(); - std::mem::forget(hints_vec); - Vec::from_raw_parts(ptr, len, capacity) - }; + std::slice::from_raw_parts( + hints.as_ptr() as *const u8, + hints.len() * std::mem::size_of::(), + ) + } + .to_vec(); self.runtime_handle.block_on((self.dispatcher)(seq_num, StreamMessageKind::Data, payload)); } From ff651a3e5efcbc13a96ed78cbcd7d7f8f212a137 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:31:14 +0000 Subject: [PATCH 516/782] fix comments --- emulator-asm/asm-runner/src/asm_services/services.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 1a99e4eb3..cf0802d61 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -105,8 +105,6 @@ impl AsmServices { ); // First service has to create the shared memory, the rest can use it - // Es a dir, passar ZISK_15200_0 com a prefix a tots els asm services, - // passar --shared_input_shmem a tots tres, i passar --open_input_shmem al segon i al tercer. options.share_input_shmem = true; options.open_input_shmem = false; self.start_asm_service(service, trimmed_path, &options, &shm_prefix); From b401ab5c51174f3efddf9139475baf976bdccd9e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:31:51 +0000 Subject: [PATCH 517/782] add msync in shmem_writer ring buffer --- emulator-asm/asm-runner/src/shmem_writer.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index 9723b26d0..eab77671c 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -125,7 +125,7 @@ impl SharedMemoryWriter { /// # Arguments /// * `data` - A slice of data to write to shared memory #[inline] - pub fn write_ring_buffer(&mut self, data: &[T]) { + pub fn write_ring_buffer(&mut self, data: &[T]) -> Result<()> { let byte_size = std::mem::size_of_val(data); let data_ptr = data.as_ptr() as *const u8; @@ -159,12 +159,14 @@ impl SharedMemoryWriter { } } - // // Force changes to be flushed to the shared memory - // #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - // if msync(self.ptr as *mut _, self.size, MS_SYNC) != 0 { - // return Err(io::Error::last_os_error()); - // } + // Force changes to be flushed to the shared memory + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + if msync(self.ptr as *mut _, self.size, MS_SYNC) != 0 { + return Err(io::Error::last_os_error()); + } } + + Ok(()) } /// Reads a u64 from shared memory at a specific offset (in bytes) From c45ff3c1e756175f6c88e2df45ebab68e4f49699 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:41:13 +0000 Subject: [PATCH 518/782] fix multiple workers in distributed mode --- common/src/io/stream/zisk_stream.rs | 2 + .../crates/coordinator/src/hints_relay.rs | 5 +-- distributed/crates/worker/src/worker.rs | 38 ++++++++++++++++--- emulator-asm/asm-runner/src/hints_shmem.rs | 37 +++++++++++++++--- precompiles/hints/src/hints_processor.rs | 7 ++++ 5 files changed, 74 insertions(+), 15 deletions(-) diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index c44dd73ec..3894205b8 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -31,6 +31,8 @@ pub trait StreamSink: Send + Sync + 'static { fn submit(&self, processed: Vec) -> anyhow::Result<()>; fn reset(&self) {} + + fn set_has_rom_sm(&self, _has_rom_sm: bool) {} } enum ThreadCommand { diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index 732319309..bc974ab1f 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -127,10 +127,7 @@ impl PrecompileHintsRelay { // Safe conversion: &[u64] → Vec for wire protocol let payload = unsafe { - std::slice::from_raw_parts( - hints.as_ptr() as *const u8, - hints.len() * std::mem::size_of::(), - ) + std::slice::from_raw_parts(hints.as_ptr() as *const u8, std::mem::size_of_val(hints)) } .to_vec(); diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 43420131d..76150eff8 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -602,11 +602,25 @@ impl Worker { let local_rank = self.prover.local_rank(); let unlock_mapped_memory = self.prover_config.unlock_mapped_memory; let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory)?; - self.hints_processor = Some( - HintsProcessor::builder(hints_shmem) - .build() - .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e))?, - ); + let processor = HintsProcessor::builder(hints_shmem) + .build() + .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e))?; + + let worker_idx = self + .current_job + .as_ref() + .ok_or_else(|| { + anyhow::anyhow!( + "Received stream data for job {}, but no current job is set", + job_id + ) + })? + .lock() + .await + .rank_id as usize; + + processor.set_has_rom_sm(worker_idx == 0); + self.hints_processor = Some(processor); } // Check the existence of stream buffer based on stream type @@ -627,6 +641,10 @@ impl Worker { hints_processor.reset(); } + println!( + "Stream START received for job {}, initialized buffer and reset hints processor", + job_id + ); return Ok(()); } else if stream_type == StreamMessageKind::End { // Ensure buffer exists @@ -641,9 +659,13 @@ impl Worker { // Clean up the stream buffer for this job self.stream_buffers.remove(&job_id); + println!("Stream END received for job {}, cleaned up buffer", job_id); return Ok(()); } - + println!( + "Received stream data for job {}, stream type {:?}, processing...", + job_id, stream_type + ); let element = self.stream_buffers.get_mut(&job_id).ok_or_else(|| { anyhow::anyhow!( "Received stream data without START for job {} stream type {:?}", @@ -667,6 +689,10 @@ impl Worker { // Check if this is the expected sequence number // If not, buffer it for later processing if current_seq != *next_seq { + println!( + "Received out-of-order stream data for job {}, expected seq {}, got seq {}, buffering", + job_id, *next_seq, current_seq + ); stream_buffer.insert(current_seq, payload); return Ok(()); } diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index dbd527a71..cdfb1a1f2 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -9,7 +9,7 @@ use crate::{ }; use anyhow::Result; use named_sem::NamedSemaphore; -use std::cell::RefCell; +use std::{cell::RefCell, sync::atomic::AtomicBool}; use tracing::debug; use zisk_common::io::StreamSink; @@ -50,6 +50,7 @@ struct UnifiedResources { /// HintsShmem struct manages the writing of processed precompile hints to shared memory. pub struct HintsShmem { + has_rom_sm: AtomicBool, /// Unified resources (single data buffer and control writer) unified: RefCell, /// Separate resources (control_reader and semaphores for each service) @@ -103,7 +104,11 @@ impl HintsShmem { let separate = Self::create_separate_resources(separate_names)?; - Ok(Self { unified: RefCell::new(unified), separate: RefCell::new(separate) }) + Ok(Self { + unified: RefCell::new(unified), + separate: RefCell::new(separate), + has_rom_sm: AtomicBool::new(true), + }) } /// Create the unified resources (single data buffer and control writer). @@ -198,6 +203,12 @@ impl StreamSink for HintsShmem { let mut unified = self.unified.borrow_mut(); let mut separate = self.separate.borrow_mut(); + let separate = if self.has_rom_sm.load(std::sync::atomic::Ordering::SeqCst) { + &mut separate + } else { + &mut separate[0..2] + }; + // Read current write position once let write_pos = unified.control_writer.read_u64_at(0); @@ -213,8 +224,20 @@ impl StreamSink for HintsShmem { .unwrap(); // Calculate occupied space based on slowest consumer (saturating to avoid underflow) - let occupied_space = write_pos.saturating_sub(min_read_pos); - let available_space = Self::BUFFER_CAPACITY_U64.saturating_sub(occupied_space); + debug_assert!( + write_pos >= min_read_pos, + "Write position ({}) is behind minimum read position ({})", + write_pos, + min_read_pos + ); + let occupied_space = write_pos - min_read_pos; + debug_assert!( + occupied_space <= Self::BUFFER_CAPACITY_U64, + "Occupied space ({}) exceeds buffer capacity ({})", + occupied_space, + Self::BUFFER_CAPACITY_U64 + ); + let available_space = Self::BUFFER_CAPACITY_U64 - occupied_space; // Flow control based on buffer occupancy if available_space >= data_size { @@ -229,7 +252,7 @@ impl StreamSink for HintsShmem { } // Write data ONCE to the unified shared memory buffer - unified.data_writer.write_ring_buffer(&processed); + unified.data_writer.write_ring_buffer(&processed)?; // Update write position ONCE in control memory unified.control_writer.write_u64_at(0, write_pos + data_size); @@ -255,4 +278,8 @@ impl StreamSink for HintsShmem { while res.sem_read.try_wait().is_ok() {} } } + + fn set_has_rom_sm(&self, has_rom_sm: bool) { + self.has_rom_sm.store(has_rom_sm, std::sync::atomic::Ordering::SeqCst); + } } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 423203a8e..1bd6b3cc2 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -329,6 +329,9 @@ impl HintsProcessor { idx )); } + + debug!("CTRL_START received, starting new stream"); + // Mark stream as active self.stream_active.store(true, Ordering::Release); // Control hint only; skip processing @@ -704,6 +707,10 @@ impl HintsProcessor { self.instant.lock().unwrap().take(); self.pending_partial.lock().unwrap().take(); } + + pub fn set_has_rom_sm(&self, has_rom_sm: bool) { + self.hints_sink.set_has_rom_sm(has_rom_sm); + } } impl Drop for HintsProcessor { From 25470f49c0875b601d07a03b7db760a6fe47f33e Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 13 Feb 2026 09:04:55 +0000 Subject: [PATCH 519/782] Minor fix for visualization --- Cargo.lock | 30 +++++++++++++++--------------- sdk/src/prover/asm.rs | 11 ++++++++++- sdk/src/prover/backend.rs | 21 ++++++++++++++++++--- sdk/src/prover/emu.rs | 11 ++++++++++- sdk/src/prover/mod.rs | 30 +++++++++++++++++++++++++++--- 5 files changed, 80 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 121771f91..22994ec9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -651,9 +651,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" dependencies = [ "fields", "num-bigint", @@ -1467,7 +1467,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" dependencies = [ "cfg-if", "num-bigint", @@ -2210,9 +2210,9 @@ version = "0.16.0" [[package]] name = "libc" -version = "0.2.181" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libffi" @@ -2772,7 +2772,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" dependencies = [ "colored", "fields", @@ -3134,7 +3134,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" dependencies = [ "bincode", "blake3", @@ -3170,7 +3170,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" dependencies = [ "bincode", "borsh", @@ -3202,7 +3202,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" dependencies = [ "fields", "itoa", @@ -3215,7 +3215,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" dependencies = [ "proc-macro2", "quote", @@ -3225,7 +3225,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" dependencies = [ "crossbeam-channel", "tracing", @@ -3234,7 +3234,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" dependencies = [ "bincode", "bytemuck", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" dependencies = [ "bytemuck", "fields", @@ -5721,7 +5721,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" dependencies = [ "colored", "fields", diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 6c7270df7..f11c1d96d 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -9,7 +9,7 @@ use crate::{ use crate::{ProofMode, ProofOpts}; use asm_runner::{AsmRunnerOptions, AsmServices}; use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; -use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, VerboseMode}; +use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, RowInfo, VerboseMode}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rom_setup::DEFAULT_CACHE_PATH; use std::sync::OnceLock; @@ -172,6 +172,15 @@ impl ProverEngine for AsmProver { self.core_prover.backend.stats(stdin, debug_info, minimal_memory, mpi_node) } + fn get_instance_trace( + &self, + instance_id: usize, + first_row: usize, + num_rows: usize, + ) -> Result> { + self.core_prover.backend.get_instance_trace(instance_id, first_row, num_rows) + } + fn verify_constraints_debug( &self, stdin: ZiskStdin, diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index e3c29ce47..032da6c07 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -17,7 +17,7 @@ use proofman::{ AggProofs, ExecutionInfo, ProofInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProtocol, SnarkWrapper, }; -use proofman_common::{ProofCtx, ProofOptions}; +use proofman_common::{ProofCtx, ProofOptions, RowInfo}; use proofman_util::VadcopFinalProof; use std::collections::HashMap; use std::path::PathBuf; @@ -140,7 +140,7 @@ impl ProverBackend { let start = std::time::Instant::now(); - proofman + let planning_info = proofman .execute_from_lib(output_path) .map_err(|e| anyhow::anyhow!("Error generating execution: {}", e))?; @@ -152,7 +152,7 @@ impl ProverBackend { let publics = proofman.get_publics(); - Ok(ZiskExecuteResult::new(result, elapsed, &publics)) + Ok(ZiskExecuteResult::new(result, planning_info, elapsed, &publics)) } pub(crate) fn stats( @@ -212,6 +212,21 @@ impl ProverBackend { Ok((rank_info.world_rank, rank_info.n_processes, Some(stats))) } + pub(crate) fn get_instance_trace( + &self, + instance_id: usize, + first_row: usize, + num_rows: usize, + ) -> Result> { + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot get instance trace in verifier mode"))?; + + proofman + .get_instance_trace(instance_id, first_row, num_rows) + .map_err(|e| anyhow::anyhow!("Error getting instance trace: {}", e)) + } pub(crate) fn verify_constraints_debug( &self, stdin: ZiskStdin, diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 19bb1cb72..dca8cd119 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -7,7 +7,7 @@ use crate::{ }; use crate::{ensure_custom_commits, ProofMode, ProofOpts}; use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; -use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, VerboseMode}; +use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, RowInfo, VerboseMode}; use std::collections::HashMap; use std::path::PathBuf; use zisk_common::io::{StreamSource, ZiskStdin}; @@ -124,6 +124,15 @@ impl ProverEngine for EmuProver { self.core_prover.backend.stats(stdin, debug_info, minimal_memory, mpi_node) } + fn get_instance_trace( + &self, + instance_id: usize, + first_row: usize, + num_rows: usize, + ) -> Result> { + self.core_prover.backend.get_instance_trace(instance_id, first_row, num_rows) + } + fn verify_constraints_debug( &self, stdin: ZiskStdin, diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 127a6689c..8e41dbc5f 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -7,11 +7,12 @@ pub use emu::*; use proofman::{ AggProofs, ExecutionInfo, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProtocol, }; -use proofman_common::ProofOptions; +use proofman_common::{ProofOptions, RowInfo}; use proofman_util::VadcopFinalProof; use sha2::{Digest, Sha256}; use anyhow::{Context, Result}; +use proofman::PlanningInfo; use serde::{Deserialize, Serialize}; use std::fs::File; use std::{ @@ -27,13 +28,19 @@ use zisk_common::{ pub struct ZiskExecuteResult { pub execution: ZiskExecutionResult, + pub planning_info: PlanningInfo, pub duration: Duration, pub publics: ZiskPublics, } impl ZiskExecuteResult { - pub fn new(execution: ZiskExecutionResult, duration: Duration, publics: &[u8]) -> Self { - Self { execution, duration, publics: ZiskPublics::new(publics) } + pub fn new( + execution: ZiskExecutionResult, + planning_info: PlanningInfo, + duration: Duration, + publics: &[u8], + ) -> Self { + Self { execution, planning_info, duration, publics: ZiskPublics::new(publics) } } pub fn get_publics(&self) -> &ZiskPublics { @@ -686,6 +693,13 @@ pub trait ProverEngine { fn get_execution_info(&self) -> Result; + fn get_instance_trace( + &self, + instance_id: usize, + first_row: usize, + num_rows: usize, + ) -> Result>; + fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result; fn stats( @@ -814,6 +828,16 @@ impl ZiskProver { self.prover.stats(stdin, debug_info, minimal_memory, mpi_node) } + /// Get the instance trace for a given instance ID and row range. + pub fn get_instance_trace( + &self, + instance_id: usize, + first_row: usize, + num_rows: usize, + ) -> Result> { + self.prover.get_instance_trace(instance_id, first_row, num_rows) + } + /// Verify the constraints with the given standard input and debug information. pub fn verify_constraints_debug( &self, From d47b6267e444f47ccddc59b9f527b368e9ebeeea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Fri, 13 Feb 2026 15:04:43 +0100 Subject: [PATCH 520/782] Fix rom instance (#785) --- state-machines/rom/src/rom.rs | 35 +++++--------------------- state-machines/rom/src/rom_instance.rs | 29 +++++++++++++-------- 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/state-machines/rom/src/rom.rs b/state-machines/rom/src/rom.rs index 8e5906e5c..6b3dc07ab 100644 --- a/state-machines/rom/src/rom.rs +++ b/state-machines/rom/src/rom.rs @@ -10,10 +10,7 @@ use std::{ path::PathBuf, - sync::{ - atomic::{AtomicBool, AtomicU64}, - Arc, Mutex, - }, + sync::{atomic::AtomicU64, Arc, Mutex}, thread::JoinHandle, }; @@ -85,7 +82,6 @@ impl RomSM { pub fn compute_witness( rom: &ZiskRom, counter_stats: &CounterStats, - calculated: &AtomicBool, trace_buffer: Vec, ) -> ProofmanResult> { let mut rom_trace = RomTrace::new_from_vec_zeroes(trace_buffer)?; @@ -106,18 +102,9 @@ impl RomSM { if counter_stats.bios_inst_count.is_empty() { multiplicity = 1; // If the histogram is empty, we use 1 for all pc's } else { - match calculated.load(std::sync::atomic::Ordering::Relaxed) { - true => { - multiplicity = counter_stats.bios_inst_count - [((inst.paddr - ROM_ENTRY) as usize) >> 2] - .swap(0, std::sync::atomic::Ordering::Relaxed); - } - false => { - multiplicity = counter_stats.bios_inst_count - [((inst.paddr - ROM_ENTRY) as usize) >> 2] - .load(std::sync::atomic::Ordering::Relaxed); - } - } + multiplicity = counter_stats.bios_inst_count + [((inst.paddr - ROM_ENTRY) as usize) >> 2] + .load(std::sync::atomic::Ordering::Relaxed); if multiplicity == 0 { continue; @@ -127,18 +114,8 @@ impl RomSM { } } } else { - match calculated.load(std::sync::atomic::Ordering::Relaxed) { - true => { - multiplicity = counter_stats.prog_inst_count - [(inst.paddr - ROM_ADDR) as usize] - .swap(0, std::sync::atomic::Ordering::Relaxed) - } - false => { - multiplicity = counter_stats.prog_inst_count - [(inst.paddr - ROM_ADDR) as usize] - .load(std::sync::atomic::Ordering::Relaxed) - } - } + multiplicity = counter_stats.prog_inst_count[(inst.paddr - ROM_ADDR) as usize] + .load(std::sync::atomic::Ordering::Relaxed); if multiplicity == 0 { continue; } diff --git a/state-machines/rom/src/rom_instance.rs b/state-machines/rom/src/rom_instance.rs index 324d67072..97d013643 100644 --- a/state-machines/rom/src/rom_instance.rs +++ b/state-machines/rom/src/rom_instance.rs @@ -3,10 +3,7 @@ //! It is responsible for computing witnesses for ROM-related execution plans, use std::{ - sync::{ - atomic::{AtomicBool, AtomicU64}, - Arc, - }, + sync::{atomic::AtomicU64, Arc}, thread::JoinHandle, }; @@ -48,8 +45,6 @@ pub struct RomInstance { /// Cached result from the assembly runner thread. asm_result: Mutex>, - - calculated: AtomicBool, } impl RomInstance { @@ -76,7 +71,6 @@ impl RomInstance { counter_stats: Mutex::new(None), handle_rh: Mutex::new(handle_rh), asm_result: Mutex::new(None), - calculated: AtomicBool::new(false), } } @@ -172,17 +166,32 @@ impl Instance for RomInstance { let air_instance = Some(RomSM::compute_witness( &self.zisk_rom, self.counter_stats.lock().unwrap().as_ref().unwrap(), - &self.calculated, trace_buffer, )?); - self.calculated.store(true, std::sync::atomic::Ordering::Relaxed); Ok(air_instance) } fn reset(&self) { *self.counter_stats.lock().unwrap() = None; *self.asm_result.lock().unwrap() = None; - self.calculated.store(false, std::sync::atomic::Ordering::Relaxed); + + let bios_counts = self.bios_inst_count.lock().unwrap().clone(); + let prog_counts = self.prog_inst_count.lock().unwrap().clone(); + + rayon::join( + || { + use rayon::prelude::*; + bios_counts + .par_iter() + .for_each(|i| i.store(0, std::sync::atomic::Ordering::Relaxed)); + }, + || { + use rayon::prelude::*; + prog_counts + .par_iter() + .for_each(|i| i.store(0, std::sync::atomic::Ordering::Relaxed)); + }, + ); } /// Retrieves the checkpoint associated with this instance. From 2044c696a62f94c50a0219f4937d82613e74d14e Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 13 Feb 2026 15:47:47 +0000 Subject: [PATCH 521/782] Adding instances fixed visualization --- Cargo.lock | 22 +++++++++++----------- examples/Cargo.lock | 31 ++++++++++++++++--------------- sdk/src/prover/asm.rs | 13 ++++++++++++- sdk/src/prover/backend.rs | 21 ++++++++++++++++++++- sdk/src/prover/emu.rs | 13 ++++++++++++- sdk/src/prover/mod.rs | 23 ++++++++++++++++++++++- 6 files changed, 93 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67a2324e8..545563469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "fields", "num-bigint", @@ -1467,7 +1467,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "cfg-if", "num-bigint", @@ -2772,7 +2772,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "colored", "fields", @@ -3134,7 +3134,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "bincode", "blake3", @@ -3170,7 +3170,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "bincode", "borsh", @@ -3202,7 +3202,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "fields", "itoa", @@ -3215,7 +3215,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "proc-macro2", "quote", @@ -3225,7 +3225,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "crossbeam-channel", "tracing", @@ -3234,7 +3234,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "bincode", "bytemuck", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "bytemuck", "fields", @@ -5721,7 +5721,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#3b4f2b11c6ce20b15ae95e0a356f2e603e0f7b20" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "colored", "fields", diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 296390993..e9709ef98 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -509,9 +509,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -813,7 +813,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "fields", "num-bigint", @@ -1095,7 +1095,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "cfg-if", "num-bigint", @@ -1519,9 +1519,9 @@ version = "0.16.0" [[package]] name = "libc" -version = "0.2.181" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libffi" @@ -1955,7 +1955,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "colored", "fields", @@ -2256,7 +2256,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "bincode", "blake3", @@ -2292,7 +2292,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "bincode", "borsh", @@ -2324,7 +2324,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "fields", "itoa", @@ -2337,7 +2337,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "proc-macro2", "quote", @@ -2347,7 +2347,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "crossbeam-channel", "tracing", @@ -2356,7 +2356,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "bincode", "bytemuck", @@ -2368,7 +2368,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "bytemuck", "fields", @@ -4374,7 +4374,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a31f6af920a7a228baa5ae8810f7cbc9d45db035" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" dependencies = [ "colored", "fields", @@ -4746,6 +4746,7 @@ dependencies = [ "serde", "sha2", "tiny-keccak", + "tokio", "zisk-common", ] diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index f11c1d96d..cb0eb213c 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -177,8 +177,19 @@ impl ProverEngine for AsmProver { instance_id: usize, first_row: usize, num_rows: usize, + offset: Option, ) -> Result> { - self.core_prover.backend.get_instance_trace(instance_id, first_row, num_rows) + self.core_prover.backend.get_instance_trace(instance_id, first_row, num_rows, offset) + } + + fn get_instance_fixed( + &self, + instance_id: usize, + first_row: usize, + num_rows: usize, + offset: Option, + ) -> Result> { + self.core_prover.backend.get_instance_fixed(instance_id, first_row, num_rows, offset) } fn verify_constraints_debug( diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 032da6c07..92fedbcc8 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -217,6 +217,7 @@ impl ProverBackend { instance_id: usize, first_row: usize, num_rows: usize, + offset: Option, ) -> Result> { let proofman = self .proofman @@ -224,9 +225,27 @@ impl ProverBackend { .ok_or_else(|| anyhow::anyhow!("Cannot get instance trace in verifier mode"))?; proofman - .get_instance_trace(instance_id, first_row, num_rows) + .get_instance_trace(instance_id, first_row, num_rows, offset) .map_err(|e| anyhow::anyhow!("Error getting instance trace: {}", e)) } + + pub(crate) fn get_instance_fixed( + &self, + instance_id: usize, + first_row: usize, + num_rows: usize, + offset: Option, + ) -> Result> { + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot get instance fixed in verifier mode"))?; + + proofman + .get_instance_fixed(instance_id, first_row, num_rows, offset) + .map_err(|e| anyhow::anyhow!("Error getting instance fixed: {}", e)) + } + pub(crate) fn verify_constraints_debug( &self, stdin: ZiskStdin, diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index dca8cd119..8ac1b94bd 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -129,8 +129,19 @@ impl ProverEngine for EmuProver { instance_id: usize, first_row: usize, num_rows: usize, + offset: Option, ) -> Result> { - self.core_prover.backend.get_instance_trace(instance_id, first_row, num_rows) + self.core_prover.backend.get_instance_trace(instance_id, first_row, num_rows, offset) + } + + fn get_instance_fixed( + &self, + instance_id: usize, + first_row: usize, + num_rows: usize, + offset: Option, + ) -> Result> { + self.core_prover.backend.get_instance_fixed(instance_id, first_row, num_rows, offset) } fn verify_constraints_debug( diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 8e41dbc5f..e2a9f71c5 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -698,6 +698,15 @@ pub trait ProverEngine { instance_id: usize, first_row: usize, num_rows: usize, + offset: Option, + ) -> Result>; + + fn get_instance_fixed( + &self, + instance_id: usize, + first_row: usize, + num_rows: usize, + offset: Option, ) -> Result>; fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result; @@ -834,8 +843,20 @@ impl ZiskProver { instance_id: usize, first_row: usize, num_rows: usize, + offset: Option, + ) -> Result> { + self.prover.get_instance_trace(instance_id, first_row, num_rows, offset) + } + + /// Get the instance fixed for a given instance ID and row range. + pub fn get_instance_fixed( + &self, + instance_id: usize, + first_row: usize, + num_rows: usize, + offset: Option, ) -> Result> { - self.prover.get_instance_trace(instance_id, first_row, num_rows) + self.prover.get_instance_fixed(instance_id, first_row, num_rows, offset) } /// Verify the constraints with the given standard input and debug information. From 4ed9cee5ca98e10520ecf454438795a4469d3472 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Fri, 13 Feb 2026 17:24:09 +0000 Subject: [PATCH 522/782] Add documentation for hints generation from guest --- book/getting_started/precompile_hints.md | 142 +++++++++++++++++++++++ ziskos/entrypoint/src/hints/custom.rs | 19 +++ ziskos/entrypoint/src/hints/mod.rs | 12 +- 3 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 ziskos/entrypoint/src/hints/custom.rs diff --git a/book/getting_started/precompile_hints.md b/book/getting_started/precompile_hints.md index 718cf6e0d..0da4135ce 100644 --- a/book/getting_started/precompile_hints.md +++ b/book/getting_started/precompile_hints.md @@ -21,12 +21,14 @@ flowchart LR 2. [Hints in CLI Execution](#2-hints-in-cli-execution) 3. [Hints in Distributed Execution](#3-hints-in-distributed-execution) 4. [Custom Hint Handlers](#4-custom-hint-handlers) +5. [Generating Hints in Guest Programs](#5-generating-hints-in-guest-programs) --- ## 1. Hint Format and Protocol ### 1.1. Hint Request Format + Hints are transmitted as a stream of `u64` values. Each hint request consists of a **header** (1 `u64`) followed by **data** (N `u64` values). ``` @@ -48,6 +50,7 @@ Hints are transmitted as a stream of `u64` values. Each hint request consists of - **Length** (lower 32 bits): Payload data size in **bytes**. The last `u64` may contain padding bytes. ### 1.2. Control Hint Types: + The following control codes are defined: - `0x00` (START): Start a new hint stream. Resets processor state and sequence counters. Must be the first hint in the first batch. - `0x01` (END): End the current hint stream. The processor will wait for all pending hints to be processed before returning. Must be the last hint in its batch; only a `CTRL_START` may follow in a subsequent batch. @@ -57,6 +60,7 @@ The following control codes are defined: Control codes are for control only and do not have any associated data (Length should be zero). ### 1.3. Data Hint Types + For data hints, the hint code (32 bits) is structured as follows: - **Bit 31 (MSB)**: Pass-through flag. When set, the data bypasses computation and is forwarded directly to the sink. - **Bits 0-30**: The hint type identifier (control, built-in, or custom code). @@ -77,6 +81,7 @@ Header: 0x80000100_00000020 ``` #### 1.3.1 Stream Batching + The hints protocol supports chunking for individual hints that exceed the transport’s message size limit (currently 128 KB). Each message in the stream contains either a single complete hint or one chunk of a larger hint — hints are never combined in the same message. When a hint exceeds the size limit, it must be split into multiple sequential chunks, each sent as a separate message. Each chunk includes a header specifying the total length of the complete hint, allowing the receiver to reassemble all chunks before processing. For example, a hint with a 300 KB payload would be split into three messages: @@ -87,6 +92,7 @@ Message 3: Header (code + total length), Data[0..M] (final 44 KB chunk) The receiver buffers incoming chunks and reassembles them based on the total length specified in the header before invoking the hint handler. This allows the system to handle arbitrarily large hints while respecting transport limitations. #### 1.3.2 Pass-Through Hints + When bit 31 of the hint code is set (e.g., `0x8000_0000 | actual_code`), the hint is marked as **pass-through**: - The data payload is forwarded directly to the sink without invoking any handler. @@ -94,6 +100,7 @@ When bit 31 of the hint code is set (e.g., `0x8000_0000 | actual_code`), the hin - This is useful for pre-computed results that don't need processing. ### 1.4. Hint Code Types + | Category | Code Range | Description | |--------------|-------------------|-------------------------------------| | **Control** | `0x0000`-`0x000F` | Stream lifecycle management | @@ -103,6 +110,7 @@ When bit 31 of the hint code is set (e.g., `0x8000_0000 | actual_code`), the hin > **Note:** Custom hint codes can technically use any value not occupied by control or built-in codes. By convention, codes `0xA000`-`0xFFFF` are recommended for custom use to avoid future conflicts as new built-in types are added. The processor does not enforce a range restriction — any unrecognized code is treated as custom. #### 1.4.1. Control Codes + Control codes manage the stream lifecycle and do not carry computational data: | Code | Name | Description | @@ -113,6 +121,7 @@ Control codes manage the stream lifecycle and do not carry computational data: | `0x0003` | `CTRL_ERROR` | **[Reserved for future use]** External error signal. Sets error flag and stops processing. | #### 1.4.2. Built-in Hint Types + | Code | Name | Description | |------|------|-------------| | `0x0100` | `Sha256` | SHA-256 hash computation | @@ -134,6 +143,7 @@ Control codes manage the stream lifecycle and do not carry computational data: | `0x0700` | `Keccak256` | Keccak-256 hash computation | #### 1.4.3. Custom Hint Types + Custom hint types allow users to define their own hint handlers for application-specific logic. Users can register custom handlers via the `HintsProcessor` builder API, providing a mapping from hint code to a processing function (see [Custom Hint Handlers](#4-custom-hint-handlers)). By convention, codes in the range `0xA000`-`0xFFFF` are recommended for custom use to avoid conflicts with current and future built-in types. If a data hint is received with an unregistered code, the processor returns an error and stops processing immediately. ### 1.5. Stream Protocol @@ -212,6 +222,7 @@ A hints stream system can be configured in two ways: * **Path mode**: Workers load hints from a local path/URI. This is useful for debugging or when hints are pre-generated and stored in a file. In this mode, the coordinator does not send hints to workers; instead, each worker reads the hints directly from the specified path. #### 3.2.1 Coordinator Hints Streaming Mode + To start the coordinator in streaming mode, provide the `--hints-uri` option with a URI that the `coordinator` will connect to, and set `--stream-hints` to enable broadcasting to workers. The URI determines the input stream source for hints. The supported schemes are: ``` @@ -227,6 +238,7 @@ zisk-coordinator prove --hints-uri unix:///tmp/hints.sock --stream-hints ... ``` #### 3.2.2 Worker Hints non-Streaming Mode + To start a worker in non-streaming mode, provide the `--hints-uri` option with a URI that points to the local workers path where hints are stored, without the `--stream-hints` option. In this mode the worker(s) will load the precompile hints from the specified URI instead of receiving them from the coordinator. This mode is useful for debugging or when hints are pre-generated and stored in a file. ## 4. Custom Hint Handlers @@ -249,3 +261,133 @@ let processor = HintsProcessor::builder(my_sink) **Requirements:** - Handler function must be `Fn(&[u64]) -> Result> + Send + Sync + 'static`. - Custom hint codes should not conflict with built-in codes (`0x0000`-`0x0700`). By convention, use codes in the range `0xA000`-`0xFFFF`. + +## 5. Generating Hints in Guest Programs + +To generate hints from the guest program you need to follow these steps and requirements: + +1. **Emit hint requests**: Patch your code or dependent crates to call the external FFI Hints helper functions that generate the hints input data required later by the `HintsProcessor`. See [FFI Hints Helper Functions](#55-ffi-hints-helper-functions) for the list of available built-in FFI Hints helper functions, or [Custom Hints Generation](#56-custom-hints-generation) to learn how to generate custom hints from the guest program. +2. **Add the `ziskos` crate** to your guest `Cargo.toml`. +3. **Initialize and finalize the hint stream**: Call the hints init and close functions immediately before and after the section of code that executes precompile logic. +4. **Enable hints at compile time**: Compile your guest program with `RUSTFLAGS='--cfg zisk_hints'` for the native target to activate hint code generation and FFI helper functions in the `ziskos` crate. +5. **Ensure deterministic execution**: Verify that both the native execution that generates hints and the guest compiled for the `zkvm/zisk` target execute deterministically and produce/consume hints in the exact same order. See [Deterministic Execution Requirement](#54-deterministic-execution-requirement). + +To illustrate these steps, consider the `zisk-reth` guest program, which executes and verifies Ethereum Mainnet blocks using the ZisK zkVM: + +https://github.com/0xPolygonHermez/zisk-eth-client/tree/main-reth/bin/guest + +### 5.1 Emit Hint Requests + +`zisk-reth` relies on `reth` crates, which expose a `Crypto` trait that allows a guest program to override precompile implementations. This enables zkVM-optimized implementations while also emitting hints so the computation can be performed outside the zkVM. + +For example, the SHA-256 implementation for the `Crypto` trait can be found here: + +https://github.com/0xPolygonHermez/zisk-eth-client/blob/c855d2fb401648828cc3ac044da2999b4f641917/crates/crypto/src/lib.rs#L164 + +In that file, two target-specific implementations are provided: one for `zkvm/zisk` and one for native (non-zkVM) targets. When compiling with `--cfg zisk_hints` for the native target, the zkVM-specific implementation emits a hint request using the FFI helper: + +```rust +#[cfg(zisk_hints)] +unsafe { + hint_sha256(input.as_ptr(), input.len()); +} +``` + +This call generates the hint input data using the exact input values that will later be used by the ZisK zkVM when executing the `zkvm/zisk` target code. This hint input data is consumed later by the `HintsProcessor`, allowing the SHA-256 computation to be performed outside the zkVM while remaining fully verifiable inside the circuit. + +After the hint generation, execution continues in the native target code to compute the SHA-256 result. + +### 5.2 Initialize/Finalize Hint Stream + +To start hints generation from your guest program you must call one of the following functions from the `ziskos::hints` crate: + +```rust +pub fn init_hints_file(hints_file_path: PathBuf) -> io::Result<()> +``` + +This function stores the generated hints in the file specified by the `hints_file_path` parameter. + +```rust +pub fn init_hints_socket(socket_path: PathBuf,ready: Option>) -> io::Result<()> +``` + +This function sends the hints through the Unix socket specified by the `socket_path` parameter. + +The optional `ready` parameter can be used for synchronization with the host when the guest program is executed in a separate thread to generate hints in parallel. It signals `ready` when the hints generation is ready to start writing hints through the Unix socket. + +To close hints generation you must call: + +```rust +pub fn close_hints() -> io::Result<()> +``` + +You should call these functions only when the guest is compiled for the native target used for hints generation. This can be achieved by placing the code under the following configuration flag: + +```rust +#[cfg(zisk_hints)] +{ + // Initialization/Finalize Hints generation code + ... +} +``` + +You can review how hints generation is initialized and finalized in the `zec-reth` guest here: + +https://github.com/0xPolygonHermez/zisk-eth-client/blob/main-reth/bin/guest/src/main.rs + +### 5.3 Enable Hints at Compile Time + +Once the guest program is set up to generate hints for the native target, it must be compiled with the `zisk_hints` configuration flag enabled: + +```bash +RUSTFLAGS='--cfg zisk_hints' cargo build --release +``` + +After compiling, executing the guest program will generate the hints binary file at the specified location (if `init_hints_file` was used) or start writing hints to the specified Unix socket (if `init_hints_socket` was used). + +If a hints file was generated, it can be consumed using the `--hints` flag in the `cargo-zisk` commands that support hints (as explained in [section 2](#2-hints-in-cli-execution)). + +If you want to display metrics in the console about the number of hints generated during native guest execution, you can additionally compile the guest with the `--cfg zisk_hints_metrics` flag. + +### 5.4 Deterministic Execution Requirement + +An important requirement of the hints generation flow is that the native execution that generates the hints must be fully deterministic and always produce hints in the exact same order. + +Furthermore, the order of hints generated during native execution must match the order in which the guest program compiled for the `zkvm/zisk` target expects to receive them. Since the zkVM execution is also deterministic, any divergence in hint ordering between native execution and zkVM execution will result in incorrect behavior. + +To guarantee deterministic hint generation, the code paths that directly or indirectly generate hints must avoid: + +- The use of threads or parallel execution. +- Data structures such as `HashMap` (or any structure based on randomized hash seeds) when iterated in loops that directly or indirectly call precompile/hint functions. + +Using threads or iterating over non-deterministically ordered data structures may cause the hint generation order to vary between runs, breaking the required alignment between native and zkVM executions. + +### 5.5 FFI Hints Helper Functions + +| Code | Function | +| `0x0100` | `fn hint_sha256(f: *const u8, len: usize);` | +| `0x0200` | `fn hint_bn254_g1_add(p1: *const u8, p2: *const u8);`| +| `0x0201` | `fn hint_bn254_g1_mul(point: *const u8, scalar: *const u8);` | +| `0x0205` | `fn hint_bn254_pairing_check(pairs: *const u8, num_pairs: usize);` | +| `0x0300` | `fn hint_secp256k1_ecdsa_address_recover(sig: *const u8, recid: *const u8, msg: *const u8);` | +| `0x0301` | `fn hint_secp256k1_ecdsa_verify_and_address_recover(sig: *const u8, msg: *const u8, pk: *const u8);` | +| `0x0380` | `fn hint_secp256r1_ecdsa_verify(msg: *const u8, sig: *const u8, pk: *const u8);` | +| `0x0400` | `fn hint_bls12_381_g1_add(a: *const u8, b: *const u8);` | +| `0x0401` | `fn hint_bls12_381_g1_msm(pairs: *const u8, num_pairs: usize);` | +| `0x0405` | `fn hint_bls12_381_g2_add(a: *const u8, b: *const u8);` | +| `0x0406` | `fn hint_bls12_381_g2_msm(pairs: *const u8, num_pairs: usize);` | +| `0x040A` | `fn hint_bls12_381_pairing_check(pairs: *const u8, num_pairs: usize);` | +| `0x0410` | `fn hint_bls12_381_fp_to_g1(fp: *const u8);` | +| `0x0411` | `fn hint_bls12_381_fp2_to_g2(fp2: *const u8);` | +| `0x0500` | `fn hint_modexp_bytes(base_ptr: *const u8, base_len: usize, exp_ptr: *const u8, exp_len: usize, modulus_ptr: *const u8, modulus_len: usize);` | +| `0x0600` | `fn hint_verify_kzg_proof(z: *const u8, y: *const u8, commitment: *const u8, proof: *const u8);` | +| `0x0700` | `fn hint_keccak256(input_ptr: *const u8, input_len: usize);` | + +### 5.6 Custom Hints Generation +To extend the built-in precompile hints, you can generate custom hints for new precompiles. The first step is to register the new hint in the `HintsProcessor`, as explained in section [Custom Hint Handlers](#4-custom-hint-handlers). Once the precompile hint is registered, you can generate hints for it from the guest program using the following FFI function: + +```rust +fn hint_custom(hint_id: u32, data_ptr: *const u8, data_len: usize); +``` + +and following the same guidelines described for the built-in FFI hint helper functions. diff --git a/ziskos/entrypoint/src/hints/custom.rs b/ziskos/entrypoint/src/hints/custom.rs new file mode 100644 index 000000000..ec999e7cb --- /dev/null +++ b/ziskos/entrypoint/src/hints/custom.rs @@ -0,0 +1,19 @@ +use crate::hints::HINT_BUFFER; + +#[no_mangle] +pub unsafe extern "C" fn hint_custom(hint_id: u32, data_ptr: *const u8, data_len: usize) { + if !HINT_BUFFER.is_enabled() { + return; + } + + HINT_BUFFER.write_hint_header(hint_id, data_len, false); + HINT_BUFFER.write_hint_data(data_ptr, data_len); + + let pad = (8 - (data_len & 7)) & 7; + if pad > 0 { + const ZERO_PAD: [u8; 8] = [0; 8]; + HINT_BUFFER.write_hint_data(ZERO_PAD.as_ptr(), pad); + } + + HINT_BUFFER.commit(); +} \ No newline at end of file diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index a7aa86992..79b76b6cf 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -1,5 +1,6 @@ mod bls12_381; mod bn254; +mod custom; mod hint_buffer; mod keccak256; mod kzg; @@ -101,16 +102,9 @@ pub fn init_hints() -> io::Result<()> { Ok(()) } -pub fn init_hints_file( - hints_file_path: PathBuf, - ready: Option>, -) -> io::Result<()> { +pub fn init_hints_file(hints_file_path: PathBuf) -> io::Result<()> { init_hints()?; - if let Some(tx) = ready { - let _ = tx.send(()); - } - let handle = thread::spawn(move || write_hints_to_file(hints_file_path)); HINT_WRITER_HANDLE.store(handle); @@ -160,7 +154,7 @@ pub fn close_hints() -> io::Result<()> { } } -pub fn write_hints(writer: &mut W) -> io::Result<()> { +fn write_hints(writer: &mut W) -> io::Result<()> { let disable_prefix = std::env::var("HINTS_DISABLE_PREFIX").unwrap_or_default() == "1"; // Write HINT_START From ceb9efe1648b6d78d8b6794871361e7e50bc5ede Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Fri, 13 Feb 2026 21:22:08 +0000 Subject: [PATCH 523/782] Fix section reference --- book/getting_started/precompile_hints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/getting_started/precompile_hints.md b/book/getting_started/precompile_hints.md index 0da4135ce..2b2ae0ae3 100644 --- a/book/getting_started/precompile_hints.md +++ b/book/getting_started/precompile_hints.md @@ -345,7 +345,7 @@ RUSTFLAGS='--cfg zisk_hints' cargo build --release After compiling, executing the guest program will generate the hints binary file at the specified location (if `init_hints_file` was used) or start writing hints to the specified Unix socket (if `init_hints_socket` was used). -If a hints file was generated, it can be consumed using the `--hints` flag in the `cargo-zisk` commands that support hints (as explained in [section 2](#2-hints-in-cli-execution)). +If a hints file was generated, it can be consumed using the `--hints` flag in the `cargo-zisk` commands that support hints (as explained in [Hints in CLI Execution](#2-hints-in-cli-execution)). If you want to display metrics in the console about the number of hints generated during native guest execution, you can additionally compile the guest with the `--cfg zisk_hints_metrics` flag. From dd9d2309f3e8ee79ccca40b65093c851874ea771 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Sat, 14 Feb 2026 12:41:11 +0100 Subject: [PATCH 524/782] new dma instructions memcmp, memset, inputcpy --- cli/Cargo.toml | 1 + cli/src/commands/build.rs | 16 +- cli/src/toolchain/build_toolchain.rs | 73 +- cli/src/toolchain/install_toolchain.rs | 10 +- common/src/bus/bus_device_metrics.rs | 2 +- common/src/bus/data_bus_operation.rs | 230 +++-- common/src/component/component_planner.rs | 5 +- core/src/elf2rom.rs | 34 +- core/src/elf_extraction.rs | 33 +- core/src/inst_context.rs | 4 + core/src/mem.rs | 22 +- core/src/riscv2zisk_context.rs | 570 ++++++++++- core/src/zisk_inst.rs | 10 +- core/src/zisk_inst_builder.rs | 2 +- core/src/zisk_ops.rs | 669 +++++++++++-- core/src/zisk_rom_2_asm.rs | 16 +- emulator-asm/src/dma/direct_memcpy_mops.asm | 16 +- emulator-asm/src/dma/direct_memcpy_mtrace.asm | 8 +- emulator-asm/src/dma/dma_constants.inc | 67 +- emulator-asm/src/dma/fast_dma_encode.asm | 110 ++- .../src/dma/fast_dma_encode_table.asm | 768 +++++++++----- emulator/src/disasm.rs | 18 +- emulator/src/emu.rs | 27 +- emulator/src/emu_context.rs | 14 +- emulator/src/emu_options.rs | 6 +- emulator/src/stats.rs | 2 +- executor/src/sm_static_bundle.rs | 28 +- pil/operations.pil | 8 +- pil/src/constants.rs | 3 +- pil/src/pil_helpers/traces.rs | 935 ++++++++++-------- pil/zisk.pil | 29 +- .../src/mem_inputs/generate_mem_inputs.rs | 2 +- .../src/mem_inputs/generate_mem_inputs.rs | 2 +- .../big_int/src/add256_gen_mem_inputs.rs | 4 +- precompiles/common/src/lib.rs | 23 +- precompiles/dma/Cargo.toml | 6 +- precompiles/dma/pil/dma.pil | 20 +- precompiles/dma/pil/dma_64_aligned.pil | 130 ++- precompiles/dma/pil/dma_pre_post.pil | 109 +- precompiles/dma/pil/dma_pre_post_table.pil | 92 +- precompiles/dma/pil/dma_rom.pil | 83 +- precompiles/dma/pil/dma_unaligned.pil | 18 +- precompiles/dma/src/dma/dma.rs | 127 ++- precompiles/dma/src/dma/dma_collector.rs | 103 +- precompiles/dma/src/dma/dma_input.rs | 85 +- precompiles/dma/src/dma/dma_inputcpy.rs | 257 +++++ precompiles/dma/src/dma/dma_instance.rs | 58 +- precompiles/dma/src/dma/dma_memcpy.rs | 289 ++++++ precompiles/dma/src/dma/dma_module.rs | 11 + precompiles/dma/src/dma/dma_rom.rs | 21 +- precompiles/dma/src/dma/mod.rs | 6 + .../dma/src/dma_64_aligned/dma_64_aligned.rs | 136 +-- .../dma_64_aligned_collector.rs | 100 +- .../dma_64_aligned/dma_64_aligned_input.rs | 96 +- .../dma_64_aligned/dma_64_aligned_inputcpy.rs | 277 ++++++ .../dma_64_aligned/dma_64_aligned_instance.rs | 89 +- .../src/dma_64_aligned/dma_64_aligned_mem.rs | 291 ++++++ .../dma_64_aligned/dma_64_aligned_memcpy.rs | 271 +++++ .../dma_64_aligned/dma_64_aligned_memset.rs | 255 +++++ .../dma_64_aligned/dma_64_aligned_module.rs | 14 + precompiles/dma/src/dma_64_aligned/mod.rs | 10 + precompiles/dma/src/dma_bus_device.rs | 259 +++-- precompiles/dma/src/dma_checkpoint.rs | 30 + precompiles/dma/src/dma_collect_counters.rs | 89 ++ .../dma/src/dma_collector_routing_log.rs | 74 ++ precompiles/dma/src/dma_common.rs | 24 + precompiles/dma/src/dma_constants.rs | 6 + .../dma/src/dma_gen_inputcpy_mem_inputs.rs | 101 ++ precompiles/dma/src/dma_gen_mem_inputs.rs | 235 +---- .../dma/src/dma_gen_memcmp_mem_inputs.rs | 166 ++++ .../dma/src/dma_gen_memcpy_mem_inputs.rs | 206 ++++ .../dma/src/dma_gen_memset_mem_inputs.rs | 107 ++ precompiles/dma/src/dma_instance_info.rs | 11 + precompiles/dma/src/dma_instances_builder.rs | 263 +++++ precompiles/dma/src/dma_manager.rs | 84 +- precompiles/dma/src/dma_planner.rs | 227 +---- .../dma/src/dma_pre_post/dma_pre_post.rs | 349 ++++--- .../dma_pre_post/dma_pre_post_collector.rs | 86 +- .../src/dma_pre_post/dma_pre_post_input.rs | 91 +- .../src/dma_pre_post/dma_pre_post_inputcpy.rs | 297 ++++++ .../src/dma_pre_post/dma_pre_post_instance.rs | 72 +- .../src/dma_pre_post/dma_pre_post_memcpy.rs | 304 ++++++ .../src/dma_pre_post/dma_pre_post_module.rs | 11 + .../dma/src/dma_pre_post/dma_pre_post_rom.rs | 40 +- precompiles/dma/src/dma_pre_post/mod.rs | 6 + precompiles/dma/src/dma_strategy.rs | 604 +++++++++++ .../dma/src/dma_unaligned/dma_unaligned.rs | 79 +- .../dma_unaligned/dma_unaligned_collector.rs | 75 +- .../src/dma_unaligned/dma_unaligned_input.rs | 79 +- .../dma_unaligned/dma_unaligned_instance.rs | 36 +- precompiles/dma/src/lib.rs | 22 + precompiles/helpers/src/dma.rs | 477 +++++++-- .../sha256f/src/sha256f_gen_mem_inputs.rs | 2 +- state-machines/main/pil/main.pil | 10 +- .../mem-common/src/mem_align_planner.rs | 79 +- state-machines/mem/src/mem_sm.rs | 11 +- witness-computation/src/zisk_lib.rs | 20 +- ziskos/entrypoint/Cargo.toml | 6 +- ziskos/entrypoint/src/dma.rs | 180 ++++ ziskos/entrypoint/src/dma/inputcpy.s | 14 + ziskos/entrypoint/src/dma/memcmp.s | 4 +- ziskos/entrypoint/src/dma/memcpy.s | 4 +- ziskos/entrypoint/src/dma/memmove.s | 4 +- ziskos/entrypoint/src/dma/memset.s | 51 + ziskos/entrypoint/src/lib.rs | 7 +- ziskos/entrypoint/src/syscalls/syscall.rs | 2 + .../src/zisklib/fcalls/big_int_div.rs | 30 +- .../src/zisklib/fcalls/secp256k1/fp.rs | 41 +- 108 files changed, 9364 insertions(+), 2332 deletions(-) create mode 100644 precompiles/dma/src/dma/dma_inputcpy.rs create mode 100644 precompiles/dma/src/dma/dma_memcpy.rs create mode 100644 precompiles/dma/src/dma/dma_module.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs create mode 100644 precompiles/dma/src/dma_64_aligned/dma_64_aligned_module.rs create mode 100644 precompiles/dma/src/dma_checkpoint.rs create mode 100644 precompiles/dma/src/dma_collect_counters.rs create mode 100644 precompiles/dma/src/dma_collector_routing_log.rs create mode 100644 precompiles/dma/src/dma_common.rs create mode 100644 precompiles/dma/src/dma_gen_inputcpy_mem_inputs.rs create mode 100644 precompiles/dma/src/dma_gen_memcmp_mem_inputs.rs create mode 100644 precompiles/dma/src/dma_gen_memcpy_mem_inputs.rs create mode 100644 precompiles/dma/src/dma_gen_memset_mem_inputs.rs create mode 100644 precompiles/dma/src/dma_instance_info.rs create mode 100644 precompiles/dma/src/dma_instances_builder.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post_memcpy.rs create mode 100644 precompiles/dma/src/dma_pre_post/dma_pre_post_module.rs create mode 100644 precompiles/dma/src/dma_strategy.rs create mode 100644 ziskos/entrypoint/src/dma.rs create mode 100644 ziskos/entrypoint/src/dma/inputcpy.s create mode 100644 ziskos/entrypoint/src/dma/memset.s diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 6c4bf5013..a1840410d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -60,6 +60,7 @@ mpi = { workspace = true } [features] default = [] disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] +custom_rust_llvm = [] gpu = ["proofman-common/gpu", "proofman/gpu", "packed"] packed = ["proofman-common/packed"] stats = [] diff --git a/cli/src/commands/build.rs b/cli/src/commands/build.rs index 7a71c002c..1f450c139 100644 --- a/cli/src/commands/build.rs +++ b/cli/src/commands/build.rs @@ -17,13 +17,27 @@ pub struct ZiskBuild { #[clap(long)] no_default_features: bool, + + #[arg(short, long)] + name: Option, } impl ZiskBuild { pub fn run(&self) -> Result<()> { // Construct the cargo run command + let toolchain_name = if let Some(name) = self.name.as_deref() { + println!("using toolchain_name: {name}"); + name + } else { + "zisk" + }; let mut command = Command::new("cargo"); - command.args(["+zisk", "build"]); + command.args([&format!("+{toolchain_name}"), "build"]); + + // Set RUSTFLAGS for target-cpu=zisk, preserving existing flags + let flags = std::env::var("RUSTFLAGS").unwrap_or_default(); + command.env("RUSTFLAGS", flags.trim()); + // Add the feature selection flags if let Some(features) = &self.features { command.arg("--features").arg(features); diff --git a/cli/src/toolchain/build_toolchain.rs b/cli/src/toolchain/build_toolchain.rs index 35920ff51..8fa1eb63d 100644 --- a/cli/src/toolchain/build_toolchain.rs +++ b/cli/src/toolchain/build_toolchain.rs @@ -6,7 +6,19 @@ use zisk_build::RUSTUP_TOOLCHAIN_NAME; #[derive(Parser)] #[command(name = "build-toolchain", about = "Build the cargo-zisk toolchain.")] -pub struct BuildToolchainCmd {} +pub struct BuildToolchainCmd { + /// Name for the toolchain in rustup + #[arg(short, long)] + name: Option, + + /// Git branch to checkout (default: zisk) + #[arg(short, long)] + branch: Option, + + /// Git tag to checkout (takes precedence over branch) + #[arg(short, long)] + tag: Option, +} impl BuildToolchainCmd { pub fn run(&self) -> Result<()> { @@ -26,7 +38,15 @@ impl BuildToolchainCmd { } println!("No ZISK_BUILD_DIR detected, cloning rust."); - let repo_url = "https://{}@github.com/0xPolygonHermez/rust"; + let repo_url = "https://github.com/0xPolygonHermez/rust"; + + // Determine the ref to checkout: tag takes precedence over branch + let git_ref = self + .tag + .as_ref() + .or(self.branch.as_ref()) + .map(|s| s.as_str()) + .unwrap_or("zisk"); Command::new("git") .args([ @@ -34,12 +54,50 @@ impl BuildToolchainCmd { repo_url, "--depth=1", "--single-branch", - "--branch=zisk", + &format!("--branch={}", git_ref), "zisk-rust", ]) .current_dir(&temp_dir) .run()?; Command::new("git").args(["reset", "--hard"]).current_dir(&dir).run()?; + + #[cfg(feature = "custom_rust_llvm")] + // Initialize submodules EXCEPT llvm-project + // llvm-project will be initialized by the bootstrap system which will also + // apply patches from src/llvm-patches/ automatically + let submodules_to_init = [ + "library/backtrace", + "library/stdarch", + "src/doc/book", + "src/doc/edition-guide", + "src/doc/embedded-book", + "src/doc/nomicon", + "src/doc/reference", + "src/doc/rust-by-example", + "src/gcc", + "src/tools/cargo", + "src/tools/enzyme", + "src/tools/rustc-perf", + ]; + + #[cfg(feature = "custom_rust_llvm")] + for submodule in &submodules_to_init { + println!("Initializing submodule: {}", submodule); + Command::new("git") + .args([ + "submodule", + "update", + "--init", + "--recursive", + "--progress", + "--", + submodule, + ]) + .current_dir(&dir) + .run() + .ok(); // Ignore errors for optional submodules + } + #[cfg(not(feature = "custom_rust_llvm"))] Command::new("git") .args(["submodule", "update", "--init", "--recursive", "--progress"]) .current_dir(&dir) @@ -65,6 +123,10 @@ impl BuildToolchainCmd { || format!("while creating file {temp_dir:?}/riscv64ima-zisk-zkvm-elf.json"), )?; + // Note: LLVM patches are applied automatically by the bootstrap system + // (see src/bootstrap/src/core/build_steps/llvm.rs::apply_llvm_patches) + // The patches are located in src/llvm-patches/*.patch + // Build the toolchain. Command::new("python3") .env("RUST_TARGET_PATH", &temp_dir) @@ -83,8 +145,9 @@ impl BuildToolchainCmd { .run() .with_context(|| "while building the Rust toolchain")?; + let rustup_toolchain_name = self.name.as_deref().unwrap_or(RUSTUP_TOOLCHAIN_NAME); // Remove the existing toolchain from rustup, if it exists. - match Command::new("rustup").args(["toolchain", "remove", RUSTUP_TOOLCHAIN_NAME]).run() { + match Command::new("rustup").args(["toolchain", "remove", rustup_toolchain_name]).run() { Ok(_) => println!("Successfully removed existing toolchain."), Err(_) => println!("No existing toolchain to remove."), } @@ -107,7 +170,7 @@ impl BuildToolchainCmd { // Link the toolchain to rustup. Command::new("rustup") - .args(["toolchain", "link", RUSTUP_TOOLCHAIN_NAME]) + .args(["toolchain", "link", rustup_toolchain_name]) .arg(&toolchain_dir) .run() .with_context(|| "while linking the toolchain to rustup")?; diff --git a/cli/src/toolchain/install_toolchain.rs b/cli/src/toolchain/install_toolchain.rs index ef511c779..f43e627d5 100644 --- a/cli/src/toolchain/install_toolchain.rs +++ b/cli/src/toolchain/install_toolchain.rs @@ -19,7 +19,11 @@ use crate::{get_target, get_toolchain_download_url, is_supported_target}; #[derive(Parser)] #[command(name = "install-toolchain", about = "Install the cargo-zisk toolchain.")] pub struct InstallToolchainCmd { + #[arg(short, long)] version: Option, + + #[arg(short, long)] + name: Option, } impl InstallToolchainCmd { @@ -99,10 +103,12 @@ impl InstallToolchainCmd { } } + let rustup_toolchain_name = self.name.as_deref().unwrap_or(RUSTUP_TOOLCHAIN_NAME); + // Remove the existing toolchain from rustup, if it exists. let mut child = Command::new("rustup") .current_dir(&root_dir) - .args(["toolchain", "remove", RUSTUP_TOOLCHAIN_NAME]) + .args(["toolchain", "remove", rustup_toolchain_name]) .stdout(std::process::Stdio::piped()) .spawn()?; let res = child.wait(); @@ -139,7 +145,7 @@ impl InstallToolchainCmd { .args([ "toolchain", "link", - RUSTUP_TOOLCHAIN_NAME, + rustup_toolchain_name, &new_toolchain_dir.to_string_lossy(), ]) .status()?; diff --git a/common/src/bus/bus_device_metrics.rs b/common/src/bus/bus_device_metrics.rs index d9bcea5cf..88d452e2d 100644 --- a/common/src/bus/bus_device_metrics.rs +++ b/common/src/bus/bus_device_metrics.rs @@ -6,7 +6,7 @@ use super::BusDevice; use crate::Metrics; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum BusDeviceMode { Counter, CounterAsm, diff --git a/common/src/bus/data_bus_operation.rs b/common/src/bus/data_bus_operation.rs index 4906a64be..73af83bb1 100644 --- a/common/src/bus/data_bus_operation.rs +++ b/common/src/bus/data_bus_operation.rs @@ -78,10 +78,17 @@ pub const OPERATION_BUS_ADD_256_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DAT + SINGLE_RESULT_SIZE; pub const DMA_ENCODED: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE; +pub const DMA_MEMCMP_COUNT_BUS: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1; // 5 bus_precompiled_data + encoded pub const OPERATION_BUS_DMA_MEMCPY_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1; +pub const OPERATION_BUS_DMA_XMEMCPY_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1; // 5 bus_precompiled_data + encoded + count_eq pub const OPERATION_BUS_DMA_MEMCMP_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2; +pub const OPERATION_BUS_DMA_XMEMCMP_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2; +// 5 bus_precompiled_data + encoded +pub const OPERATION_BUS_DMA_INPUTCPY_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1; +// 5 bus_precompiled_data + encoded (fill_byte encoded) +pub const OPERATION_BUS_DMA_XMEMSET_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1; // 4 bus_data + 5 addr + 4 x 384 = 4 + 5 + 4 * 6 = 33 pub const MAX_OPERATION_DATA_SIZE: usize = OPERATION_BUS_ARITH_384_MOD_DATA_SIZE; @@ -126,6 +133,10 @@ pub type OperationBls12_381ComplexMulData = [D; OPERATION_BUS_BLS12_381_COMPL pub type OperationAdd256Data = [D; OPERATION_BUS_ADD_256_DATA_SIZE]; pub type OperationDmaMemCpyData = [D; OPERATION_BUS_DMA_MEMCPY_DATA_SIZE]; pub type OperationDmaMemCmpData = [D; OPERATION_BUS_DMA_MEMCMP_DATA_SIZE]; +pub type OperationDmaInputCpyData = [D; OPERATION_BUS_DMA_INPUTCPY_DATA_SIZE]; +pub type OperationDmaXMemCpyData = [D; OPERATION_BUS_DMA_XMEMCPY_DATA_SIZE]; +pub type OperationDmaXMemCmpData = [D; OPERATION_BUS_DMA_XMEMCMP_DATA_SIZE]; +pub type OperationDmaXMemSetData = [D; OPERATION_BUS_DMA_XMEMSET_DATA_SIZE]; pub enum ExtOperationData { OperationData(OperationData), @@ -150,30 +161,12 @@ pub enum ExtOperationData { OperationAdd256Data(OperationAdd256Data), OperationDmaMemCpyData(OperationDmaMemCpyData), OperationDmaMemCmpData(OperationDmaMemCmpData), + OperationDmaInputCpyData(OperationDmaInputCpyData), + OperationDmaXMemSetData(OperationDmaXMemSetData), + OperationDmaXMemCpyData(OperationDmaXMemCpyData), + OperationDmaXMemCmpData(OperationDmaXMemCmpData), } -const KECCAK_OP: u8 = ZiskOp::Keccak.code(); -const SHA256_OP: u8 = ZiskOp::Sha256.code(); -const POSEIDON2_OP: u8 = ZiskOp::Poseidon2.code(); -const ARITH256_OP: u8 = ZiskOp::Arith256.code(); -const ARITH256_MOD_OP: u8 = ZiskOp::Arith256Mod.code(); -const SECP256K1_ADD_OP: u8 = ZiskOp::Secp256k1Add.code(); -const SECP256K1_DBL_OP: u8 = ZiskOp::Secp256k1Dbl.code(); -const BN254_CURVE_ADD_OP: u8 = ZiskOp::Bn254CurveAdd.code(); -const BN254_CURVE_DBL_OP: u8 = ZiskOp::Bn254CurveDbl.code(); -const BN254_COMPLEX_ADD_OP: u8 = ZiskOp::Bn254ComplexAdd.code(); -const BN254_COMPLEX_SUB_OP: u8 = ZiskOp::Bn254ComplexSub.code(); -const BN254_COMPLEX_MUL_OP: u8 = ZiskOp::Bn254ComplexMul.code(); -const ARITH384_MOD_OP: u8 = ZiskOp::Arith384Mod.code(); -const BLS12_381_CURVE_ADD_OP: u8 = ZiskOp::Bls12_381CurveAdd.code(); -const BLS12_381_CURVE_DBL_OP: u8 = ZiskOp::Bls12_381CurveDbl.code(); -const BLS12_381_COMPLEX_ADD_OP: u8 = ZiskOp::Bls12_381ComplexAdd.code(); -const BLS12_381_COMPLEX_SUB_OP: u8 = ZiskOp::Bls12_381ComplexSub.code(); -const BLS12_381_COMPLEX_MUL_OP: u8 = ZiskOp::Bls12_381ComplexMul.code(); -const ADD256_OP: u8 = ZiskOp::Add256.code(); -const DMA_MEMCPY_OP: u8 = ZiskOp::DmaMemCpy.code(); -const DMA_MEMCMP_OP: u8 = ZiskOp::DmaMemCmp.code(); - // impl> TryFrom<&[D]> for ExtOperationData { impl> TryFrom<&[D]> for ExtOperationData { type Error = &'static str; @@ -184,111 +177,131 @@ impl> TryFrom<&[D]> for ExtOperationData { } let op = data[OP].into(); match op as u8 { - KECCAK_OP => { + ZiskOp::KECCAK => { let array: OperationKeccakData = data.try_into().map_err(|_| "Invalid OperationKeccakData size")?; Ok(ExtOperationData::OperationKeccakData(array)) } - SHA256_OP => { + ZiskOp::SHA256 => { let array: OperationSha256Data = data.try_into().map_err(|_| "Invalid OperationSha256Data size")?; Ok(ExtOperationData::OperationSha256Data(array)) } - POSEIDON2_OP => { + ZiskOp::POSEIDON2 => { let array: OperationPoseidon2Data = data.try_into().map_err(|_| "Invalid OperationPoseidon2Data size")?; Ok(ExtOperationData::OperationPoseidon2Data(array)) } - ARITH256_OP => { + ZiskOp::ARITH256 => { let array: OperationArith256Data = data.try_into().map_err(|_| "Invalid OperationArith256Data size")?; Ok(ExtOperationData::OperationArith256Data(array)) } - ARITH256_MOD_OP => { + ZiskOp::ARITH256_MOD => { let array: OperationArith256ModData = data.try_into().map_err(|_| "Invalid OperationArith256ModData size")?; Ok(ExtOperationData::OperationArith256ModData(array)) } - SECP256K1_ADD_OP => { + ZiskOp::SECP256K1_ADD => { let array: OperationSecp256k1AddData = data.try_into().map_err(|_| "Invalid OperationSecp256k1AddData size")?; Ok(ExtOperationData::OperationSecp256k1AddData(array)) } - SECP256K1_DBL_OP => { + ZiskOp::SECP256K1_DBL => { let array: OperationSecp256k1DblData = data.try_into().map_err(|_| "Invalid OperationSecp256k1DblData size")?; Ok(ExtOperationData::OperationSecp256k1DblData(array)) } - BN254_CURVE_ADD_OP => { + ZiskOp::BN254_CURVE_ADD => { let array: OperationBn254CurveAddData = data.try_into().map_err(|_| "Invalid OperationBn254CurveAddData size")?; Ok(ExtOperationData::OperationBn254CurveAddData(array)) } - BN254_CURVE_DBL_OP => { + ZiskOp::BN254_CURVE_DBL => { let array: OperationBn254CurveDblData = data.try_into().map_err(|_| "Invalid OperationBn254CurveDblData size")?; Ok(ExtOperationData::OperationBn254CurveDblData(array)) } - BN254_COMPLEX_ADD_OP => { + ZiskOp::BN254_COMPLEX_ADD => { let array: OperationBn254ComplexAddData = data.try_into().map_err(|_| "Invalid OperationBn254ComplexAddData size")?; Ok(ExtOperationData::OperationBn254ComplexAddData(array)) } - BN254_COMPLEX_SUB_OP => { + ZiskOp::BN254_COMPLEX_SUB => { let array: OperationBn254ComplexSubData = data.try_into().map_err(|_| "Invalid OperationBn254ComplexSubData size")?; Ok(ExtOperationData::OperationBn254ComplexSubData(array)) } - BN254_COMPLEX_MUL_OP => { + ZiskOp::BN254_COMPLEX_MUL => { let array: OperationBn254ComplexMulData = data.try_into().map_err(|_| "Invalid OperationBn254ComplexMulData size")?; Ok(ExtOperationData::OperationBn254ComplexMulData(array)) } - ARITH384_MOD_OP => { + ZiskOp::ARITH384_MOD => { let array: OperationArith384ModData = data.try_into().map_err(|_| "Invalid OperationArith384ModData size")?; Ok(ExtOperationData::OperationArith384ModData(array)) } - BLS12_381_CURVE_ADD_OP => { + ZiskOp::BLS12_381_CURVE_ADD => { let array: OperationBls12_381CurveAddData = data.try_into().map_err(|_| "Invalid OperationBls12_381CurveAddData size")?; Ok(ExtOperationData::OperationBls12_381CurveAddData(array)) } - BLS12_381_CURVE_DBL_OP => { + ZiskOp::BLS12_381_CURVE_DBL => { let array: OperationBls12_381CurveDblData = data.try_into().map_err(|_| "Invalid OperationBls12_381CurveDblData size")?; Ok(ExtOperationData::OperationBls12_381CurveDblData(array)) } - BLS12_381_COMPLEX_ADD_OP => { + ZiskOp::BLS12_381_COMPLEX_ADD => { let array: OperationBls12_381ComplexAddData = data.try_into().map_err(|_| "Invalid OperationBls12_381ComplexAddData size")?; Ok(ExtOperationData::OperationBls12_381ComplexAddData(array)) } - BLS12_381_COMPLEX_SUB_OP => { + ZiskOp::BLS12_381_COMPLEX_SUB => { let array: OperationBls12_381ComplexSubData = data.try_into().map_err(|_| "Invalid OperationBls12_381ComplexSubData size")?; Ok(ExtOperationData::OperationBls12_381ComplexSubData(array)) } - BLS12_381_COMPLEX_MUL_OP => { + ZiskOp::BLS12_381_COMPLEX_MUL => { let array: OperationBls12_381ComplexMulData = data.try_into().map_err(|_| "Invalid OperationBls12_381ComplexMulData size")?; Ok(ExtOperationData::OperationBls12_381ComplexMulData(array)) } - ADD256_OP => { + ZiskOp::ADD256 => { let array: OperationAdd256Data = data.try_into().map_err(|_| "Invalid OperationAdd256Data size")?; Ok(ExtOperationData::OperationAdd256Data(array)) } - DMA_MEMCPY_OP => { + ZiskOp::DMA_MEMCPY => { let array: OperationDmaMemCpyData = data.try_into().map_err(|_| "Invalid OperationDmaMemCpyData size")?; Ok(ExtOperationData::OperationDmaMemCpyData(array)) } - DMA_MEMCMP_OP => { + ZiskOp::DMA_MEMCMP => { let array: OperationDmaMemCmpData = data.try_into().map_err(|_| "Invalid OperationDmaMemCmpData size")?; Ok(ExtOperationData::OperationDmaMemCmpData(array)) } + ZiskOp::DMA_INPUTCPY => { + let array: OperationDmaInputCpyData = + data.try_into().map_err(|_| "Invalid OperationDmaInputCpyData size")?; + Ok(ExtOperationData::OperationDmaInputCpyData(array)) + } + ZiskOp::DMA_XMEMSET => { + let array: OperationDmaXMemSetData = + data.try_into().map_err(|_| "Invalid OperationDmaXMemSetData size")?; + Ok(ExtOperationData::OperationDmaXMemSetData(array)) + } + ZiskOp::DMA_XMEMCPY => { + let array: OperationDmaXMemCpyData = + data.try_into().map_err(|_| "Invalid OperationDmaXMemCpyData size")?; + Ok(ExtOperationData::OperationDmaXMemCpyData(array)) + } + ZiskOp::DMA_XMEMCMP => { + let array: OperationDmaXMemCmpData = + data.try_into().map_err(|_| "Invalid OperationDmaXMemCmpData size")?; + Ok(ExtOperationData::OperationDmaXMemCmpData(array)) + } _ => { let array: OperationData = data.try_into().map_err(|_| "Invalid OperationData size")?; @@ -373,7 +386,7 @@ impl OperationBusData { } ZiskOperationType::ArithEq => match inst.op { - ARITH256_OP => { + ZiskOp::ARITH256 => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -383,7 +396,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationArith256Data(data) } - ARITH256_MOD_OP => { + ZiskOp::ARITH256_MOD => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -393,7 +406,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationArith256ModData(data) } - SECP256K1_ADD_OP => { + ZiskOp::SECP256K1_ADD => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -403,7 +416,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationSecp256k1AddData(data) } - SECP256K1_DBL_OP => { + ZiskOp::SECP256K1_DBL => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -413,7 +426,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationSecp256k1DblData(data) } - BN254_CURVE_ADD_OP => { + ZiskOp::BN254_CURVE_ADD => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -423,7 +436,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254CurveAddData(data) } - BN254_CURVE_DBL_OP => { + ZiskOp::BN254_CURVE_DBL => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -433,7 +446,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254CurveDblData(data) } - BN254_COMPLEX_ADD_OP => { + ZiskOp::BN254_COMPLEX_ADD => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -443,7 +456,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254ComplexAddData(data) } - BN254_COMPLEX_SUB_OP => { + ZiskOp::BN254_COMPLEX_SUB => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -453,7 +466,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBn254ComplexSubData(data) } - BN254_COMPLEX_MUL_OP => { + ZiskOp::BN254_COMPLEX_MUL => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -467,7 +480,7 @@ impl OperationBusData { }, ZiskOperationType::ArithEq384 => match inst.op { - ARITH384_MOD_OP => { + ZiskOp::ARITH384_MOD => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -477,7 +490,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationArith384ModData(data) } - BLS12_381_CURVE_ADD_OP => { + ZiskOp::BLS12_381_CURVE_ADD => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -487,7 +500,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381CurveAddData(data) } - BLS12_381_CURVE_DBL_OP => { + ZiskOp::BLS12_381_CURVE_DBL => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -497,7 +510,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381CurveDblData(data) } - BLS12_381_COMPLEX_ADD_OP => { + ZiskOp::BLS12_381_COMPLEX_ADD => { let mut data = unsafe { uninit_array::() .assume_init() @@ -508,7 +521,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381ComplexAddData(data) } - BLS12_381_COMPLEX_SUB_OP => { + ZiskOp::BLS12_381_COMPLEX_SUB => { let mut data = unsafe { uninit_array::() .assume_init() @@ -519,7 +532,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationBls12_381ComplexSubData(data) } - BLS12_381_COMPLEX_MUL_OP => { + ZiskOp::BLS12_381_COMPLEX_MUL => { let mut data = unsafe { uninit_array::() .assume_init() @@ -534,7 +547,7 @@ impl OperationBusData { }, ZiskOperationType::BigInt => match inst.op { - ADD256_OP => { + ZiskOp::ADD256 => { let mut data = unsafe { uninit_array::().assume_init() }; data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -546,7 +559,7 @@ impl OperationBusData { _ => ExtOperationData::OperationData([op, op_type, a, b]), }, ZiskOperationType::Dma => match inst.op { - DMA_MEMCPY_OP => { + ZiskOp::DMA_MEMCPY => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -556,7 +569,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationDmaMemCpyData(data) } - DMA_MEMCMP_OP => { + ZiskOp::DMA_MEMCMP => { let mut data = unsafe { uninit_array::().assume_init() }; @@ -566,6 +579,46 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationDmaMemCmpData(data) } + ZiskOp::DMA_INPUTCPY => { + let mut data = unsafe { + uninit_array::().assume_init() + }; + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationDmaInputCpyData(data) + } + ZiskOp::DMA_XMEMSET => { + let mut data = unsafe { + uninit_array::().assume_init() + }; + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationDmaXMemSetData(data) + } + ZiskOp::DMA_XMEMCPY => { + let mut data = unsafe { + uninit_array::().assume_init() + }; + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationDmaXMemCpyData(data) + } + ZiskOp::DMA_XMEMCMP => { + let mut data = unsafe { + uninit_array::().assume_init() + }; + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationDmaXMemCmpData(data) + } _ => ExtOperationData::OperationData([op, op_type, a, b]), }, @@ -613,7 +666,7 @@ impl OperationBusData { } ZiskOperationType::ArithEq => match inst.op { - ARITH256_OP => { + ZiskOp::ARITH256 => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -622,7 +675,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - ARITH256_MOD_OP => { + ZiskOp::ARITH256_MOD => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -631,7 +684,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - SECP256K1_ADD_OP => { + ZiskOp::SECP256K1_ADD => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -640,7 +693,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - SECP256K1_DBL_OP => { + ZiskOp::SECP256K1_DBL => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -649,7 +702,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - BN254_CURVE_ADD_OP => { + ZiskOp::BN254_CURVE_ADD => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -658,7 +711,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - BN254_CURVE_DBL_OP => { + ZiskOp::BN254_CURVE_DBL => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -667,7 +720,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - BN254_COMPLEX_ADD_OP => { + ZiskOp::BN254_COMPLEX_ADD => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -676,7 +729,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - BN254_COMPLEX_SUB_OP => { + ZiskOp::BN254_COMPLEX_SUB => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -685,7 +738,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - BN254_COMPLEX_MUL_OP => { + ZiskOp::BN254_COMPLEX_MUL => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -701,7 +754,7 @@ impl OperationBusData { }, ZiskOperationType::ArithEq384 => match inst.op { - ARITH384_MOD_OP => { + ZiskOp::ARITH384_MOD => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -710,7 +763,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - BLS12_381_CURVE_ADD_OP => { + ZiskOp::BLS12_381_CURVE_ADD => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -719,7 +772,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - BLS12_381_CURVE_DBL_OP => { + ZiskOp::BLS12_381_CURVE_DBL => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -728,7 +781,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - BLS12_381_COMPLEX_ADD_OP => { + ZiskOp::BLS12_381_COMPLEX_ADD => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -737,7 +790,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - BLS12_381_COMPLEX_SUB_OP => { + ZiskOp::BLS12_381_COMPLEX_SUB => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -746,7 +799,7 @@ impl OperationBusData { .copy_from_slice(&ctx.precompiled.input_data); &buffer[..len] } - BLS12_381_COMPLEX_MUL_OP => { + ZiskOp::BLS12_381_COMPLEX_MUL => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -761,7 +814,7 @@ impl OperationBusData { } }, ZiskOperationType::BigInt => match inst.op { - ADD256_OP => { + ZiskOp::ADD256 => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -776,7 +829,12 @@ impl OperationBusData { } }, ZiskOperationType::Dma => match inst.op { - DMA_MEMCPY_OP | DMA_MEMCMP_OP => { + ZiskOp::DMA_MEMCPY + | ZiskOp::DMA_MEMCMP + | ZiskOp::DMA_INPUTCPY + | ZiskOp::DMA_XMEMSET + | ZiskOp::DMA_XMEMCPY + | ZiskOp::DMA_XMEMCMP => { let len = OPERATION_PRECOMPILED_BUS_DATA_SIZE + ctx.precompiled.input_data.len(); buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] @@ -830,6 +888,10 @@ impl OperationBusData { ExtOperationData::OperationAdd256Data(d) => d[OP] as u8, ExtOperationData::OperationDmaMemCpyData(d) => d[OP] as u8, ExtOperationData::OperationDmaMemCmpData(d) => d[OP] as u8, + ExtOperationData::OperationDmaInputCpyData(d) => d[OP] as u8, + ExtOperationData::OperationDmaXMemSetData(d) => d[OP] as u8, + ExtOperationData::OperationDmaXMemCpyData(d) => d[OP] as u8, + ExtOperationData::OperationDmaXMemCmpData(d) => d[OP] as u8, } } @@ -865,6 +927,10 @@ impl OperationBusData { ExtOperationData::OperationAdd256Data(d) => d[OP_TYPE], ExtOperationData::OperationDmaMemCpyData(d) => d[OP_TYPE], ExtOperationData::OperationDmaMemCmpData(d) => d[OP_TYPE], + ExtOperationData::OperationDmaInputCpyData(d) => d[OP_TYPE], + ExtOperationData::OperationDmaXMemSetData(d) => d[OP_TYPE], + ExtOperationData::OperationDmaXMemCpyData(d) => d[OP_TYPE], + ExtOperationData::OperationDmaXMemCmpData(d) => d[OP_TYPE], } } @@ -900,6 +966,10 @@ impl OperationBusData { ExtOperationData::OperationAdd256Data(d) => d[A], ExtOperationData::OperationDmaMemCpyData(d) => d[A], ExtOperationData::OperationDmaMemCmpData(d) => d[A], + ExtOperationData::OperationDmaInputCpyData(d) => d[A], + ExtOperationData::OperationDmaXMemSetData(d) => d[A], + ExtOperationData::OperationDmaXMemCpyData(d) => d[A], + ExtOperationData::OperationDmaXMemCmpData(d) => d[A], } } @@ -935,6 +1005,10 @@ impl OperationBusData { ExtOperationData::OperationAdd256Data(d) => d[B], ExtOperationData::OperationDmaMemCpyData(d) => d[B], ExtOperationData::OperationDmaMemCmpData(d) => d[B], + ExtOperationData::OperationDmaInputCpyData(d) => d[B], + ExtOperationData::OperationDmaXMemSetData(d) => d[B], + ExtOperationData::OperationDmaXMemCpyData(d) => d[B], + ExtOperationData::OperationDmaXMemCmpData(d) => d[B], } } } diff --git a/common/src/component/component_planner.rs b/common/src/component/component_planner.rs index e23fd3e99..40212d55d 100644 --- a/common/src/component/component_planner.rs +++ b/common/src/component/component_planner.rs @@ -139,8 +139,8 @@ impl CollectCounter { initial_skipped: 0, collect_count, collected: 0, - initial_skipping: initial_skip > 0, - final_skip_phase: false, + initial_skipping: collect_count > 0 && initial_skip > 0, + final_skip_phase: collect_count == 0, } } @@ -212,7 +212,6 @@ impl CollectCounter { return None; } } - if self.final_skip_phase { // Phase 3: Skip all remaining elements None diff --git a/core/src/elf2rom.rs b/core/src/elf2rom.rs index 50518e0c4..9f0ef85e7 100644 --- a/core/src/elf2rom.rs +++ b/core/src/elf2rom.rs @@ -3,22 +3,31 @@ use crate::{ add_end_and_lib, elf_extraction::{ - collect_elf_payload, collect_elf_payload_from_bytes, merge_adjacent_ro_sections, ElfPayload, + collect_elf_payload_from_bytes, get_symbol_addresses_from_bytes, + merge_adjacent_ro_sections, ElfPayload, }, riscv2zisk_context::{add_entry_exit_jmp, add_zisk_code, add_zisk_init_data}, AsmGenerationMethod, RoData, ZiskInst, ZiskRom, ZiskRom2Asm, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, }; use rayon::prelude::*; -use std::{error::Error, path::Path}; +use std::{error::Error, fs, path::Path}; /// Executes the ROM transpilation process: from ELF to Zisk pub fn elf2rom(elf_file: &Path) -> Result> { // Load the embedded float library const FLOAT_LIB_DATA: &[u8] = include_bytes!("../../lib-float/c/lib/ziskfloat.elf"); + let elf_data = + fs::read(elf_file).map_err(|_| format!("Error reading ELF file={}", elf_file.display()))?; + // Extract all relevant sections from the ELF file - let payloads: Vec = - vec![collect_elf_payload_from_bytes(FLOAT_LIB_DATA)?, collect_elf_payload(elf_file)?]; + let payloads: Vec = vec![ + collect_elf_payload_from_bytes(FLOAT_LIB_DATA)?, + collect_elf_payload_from_bytes(&elf_data)?, + ]; + + // Get DMA function addresses: (memcpy, memcmp, memset, memmove) + let dma_addrs = get_dma_symbol_addresses(&elf_data); // Create an empty ZiskRom instance let mut rom: ZiskRom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() }; @@ -29,7 +38,7 @@ pub fn elf2rom(elf_file: &Path) -> Result> { for (i, payload) in payloads.into_iter().enumerate() { // 1. Add executable code sections for section in &payload.exec { - add_zisk_code(&mut rom, section.addr, §ion.data); + add_zisk_code(&mut rom, section.addr, §ion.data, dma_addrs); } // 2. Add read-write data sections (will be copied to RAM) @@ -65,6 +74,21 @@ pub fn elf2rom(elf_file: &Path) -> Result> { Ok(rom) } +/// Get DMA function addresses from ELF data +/// Returns (memcpy, memcmp, memset, memmove), with 0 for missing symbols +fn get_dma_symbol_addresses(elf_data: &[u8]) -> (u64, u64, u64, u64) { + let symbols = ["memcpy", "memcmp", "memset", "memmove"]; + match get_symbol_addresses_from_bytes(elf_data, &symbols) { + Ok(addrs) => ( + addrs.get("memcpy").copied().unwrap_or(0), + addrs.get("memcmp").copied().unwrap_or(0), + addrs.get("memset").copied().unwrap_or(0), + addrs.get("memmove").copied().unwrap_or(0), + ), + Err(_) => (0, 0, 0, 0), + } +} + /// Optimizes instruction lookup by organizing instructions into direct-access arrays. /// /// ## Problem it solves: diff --git a/core/src/elf_extraction.rs b/core/src/elf_extraction.rs index 5a9db724c..4f84c9e67 100644 --- a/core/src/elf_extraction.rs +++ b/core/src/elf_extraction.rs @@ -5,7 +5,7 @@ use elf::{ endian::AnyEndian, ElfBytes, }; -use std::{error::Error, fs, path::Path}; +use std::{collections::HashMap, error::Error, fs, path::Path}; use crate::{is_elf_file, RAM_ADDR, RAM_SIZE}; @@ -167,6 +167,37 @@ pub fn merge_adjacent_ro_sections(sections: &[DataSection]) -> Vec merged } +/// Get addresses for a list of symbols from an ELF file +pub fn get_symbol_addresses( + elf_path: &Path, + symbol_names: &[&str], +) -> Result, Box> { + let file_data = fs::read(elf_path)?; + get_symbol_addresses_from_bytes(&file_data, symbol_names) +} + +/// Get addresses for a list of symbols from ELF bytes +pub fn get_symbol_addresses_from_bytes( + file_data: &[u8], + symbol_names: &[&str], +) -> Result, Box> { + let elf = ElfBytes::::minimal_parse(file_data)?; + let mut result = HashMap::new(); + let names_set: std::collections::HashSet<&str> = symbol_names.iter().copied().collect(); + + if let Some((symtab, strtab)) = elf.symbol_table()? { + for sym in symtab { + if let Ok(name) = strtab.get(sym.st_name as usize) { + if names_set.contains(name) { + result.insert(name.to_string(), sym.st_value); + } + } + } + } + + Ok(result) +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/src/inst_context.rs b/core/src/inst_context.rs index 381c23d54..75c1aafc5 100644 --- a/core/src/inst_context.rs +++ b/core/src/inst_context.rs @@ -120,6 +120,9 @@ pub struct InstContext { /// DataExt 64 bytes size. With this information it is possible to specify which variable part of the minimal trace /// is associated with the current instruction. Used by DMA precompile. pub data_ext_len: usize, + + /// Precompiles uses jmp_offset1 as extended param (static value known in transpilation time) + pub extended_arg: i64, } /// RisK instruction context implementation @@ -142,6 +145,7 @@ impl InstContext { precompiled: PrecompiledInstContext::default(), fcall: FcallInstContext::default(), data_ext_len: 0, + extended_arg: 0, } } diff --git a/core/src/mem.rs b/core/src/mem.rs index 26c6003f0..f4eaef304 100644 --- a/core/src/mem.rs +++ b/core/src/mem.rs @@ -909,6 +909,20 @@ impl Mem { dst_section.buffer[dst_offset..dst_offset + count].copy_from_slice(bytes); } + pub fn memset(&mut self, dst: u64, count: u64, data: u8) { + // Early return if source and destination are the same or count is zero + if count == 0 { + return; + } + + // Then, write to destination + let dst_section = self.get_writeable_section(dst, count); + let dst_offset: usize = (dst - dst_section.start) as usize; + + let count = count as usize; + dst_section.buffer[dst_offset..dst_offset + count].fill(data); + } + /// Reads `count` bytes from memory starting at `addr` and appends them as u64 values to `data`. /// The data is read in 64-bit aligned chunks and pushed to the vector. pub fn push_from_mem(&mut self, data: &mut Vec, addr: u64, count: u64) { @@ -948,16 +962,16 @@ impl Mem { // Compare byte by byte for i in 0..count_usize { - let byte_a = a_section.buffer[a_offset + i] as i8; - let byte_b = b_section.buffer[b_offset + i] as i8; + let byte_a = a_section.buffer[a_offset + i]; + let byte_b = b_section.buffer[b_offset + i]; if byte_a != byte_b { // Sign extend the difference to 64 bits let diff = (byte_a as i64) - (byte_b as i64); - return (diff as u64, i); + // return effective count, needs the last byte to compare. + return (diff as u64, i + 1); } } - // All bytes are equal (0, count_usize) } diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 6fd32082c..56ec9b639 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -3,6 +3,7 @@ //! attribute. use riscv::{riscv_interpreter, RiscvInstruction}; +use ziskos::syscalls::SYSCALL_DMA_MEMSET_ID; use crate::{ convert_vector, ZiskInstBuilder, ZiskRom, ARCH_ID_CSR_ADDR, ARCH_ID_ZISK, CSR_ADDR, @@ -39,6 +40,8 @@ const CSR_PRECOMPILED: [&str; 21] = [ ]; const CSR_PRECOMPILED_ADDR_START: u32 = 0x800; const CSR_PRECOMPILED_ADDR_END: u32 = CSR_PRECOMPILED_ADDR_START + CSR_PRECOMPILED.len() as u32; +const CSR_DMA_PRECOMPILED_ADDR_START: u32 = 0x813; +const CSR_DMA_PRECOMPILED_ADDR_END: u32 = 0x816; const CSR_PRECOMPILED_ADD256: u32 = CSR_PRECOMPILED_ADDR_START + 17; const CSR_FCALL_ADDR_START: u32 = 0x8C0; const CSR_FCALL_ADDR_END: u32 = 0x8DF; @@ -58,8 +61,13 @@ const FLOAT_HANDLER_RETURN_ADDR: u64 = FLOAT_HANDLER_ADDR + 4 * 34; // 31 regs + pub struct Riscv2ZiskContext<'a> { /// Map of program address to ZisK instructions pub insts: &'a mut HashMap, + // to store csr-port used on CSR instrucction for next instruction pub input_precompile: Option, pub output_precompile: Option, + // to store register used on CSR instrucction for next instruction as arg1 + // precompile (arg1, previous_arg1, arg2 || immediate) + pub input_precompile_reg: Option, + pub output_precompile_reg: Option, } impl Riscv2ZiskContext<'_> { @@ -83,9 +91,21 @@ impl Riscv2ZiskContext<'_> { // I.1. Integer Computational (Register-Register) "add" => { if riscv_instruction.rd == 0 && self.input_precompile == Some(0x813) { - self.create_register_op(riscv_instruction, "dma_memcpy", 4); - } else if riscv_instruction.rd == 10 && self.input_precompile == Some(0x814) { - self.create_register_op(riscv_instruction, "dma_memcmp", 4); + self.create_precompiles_op( + riscv_instruction, + "dma_memcpy", + riscv_instruction.rs1, + self.input_precompile_reg.unwrap(), + 4, + ); + } else if self.input_precompile == Some(0x814) { + self.create_precompiles_op( + riscv_instruction, + "dma_memcmp", + riscv_instruction.rs1, + self.input_precompile_reg.unwrap(), + 4, + ); } else if riscv_instruction.rs1 == 0 { if !next_instructions.is_empty() { // rd = rs1(0) + rs2 = rs2 followed by ret @@ -202,10 +222,10 @@ impl Riscv2ZiskContext<'_> { "ecall" => self.ecall(riscv_instruction), "ebreak" => self.nop(riscv_instruction, 4), "csrrw" => self.csrrw(riscv_instruction), - "csrrs" => self.csrrs(riscv_instruction), + "csrrs" => self.csrrs(riscv_instruction, next_instructions), "csrrc" => self.csrrc(riscv_instruction), "csrrwi" => self.csrrwi(riscv_instruction), - "csrrsi" => self.csrrsi(riscv_instruction), + "csrrsi" => self.csrrsi(riscv_instruction, next_instructions), "csrrci" => self.csrrci(riscv_instruction), // M: Integer Multiplication and Division @@ -611,6 +631,96 @@ impl Riscv2ZiskContext<'_> { self.insts.insert(i.rom_address, zib); } + /// Creates a Zisk precompiles operation that implements a RISC-V register operation, + /// loads both input parameters a and b from their respective registers, and stores the + /// result c into a register. + /// NOTE: How extended static param not it's used set it to zero (jmp_offset1) + pub fn create_precompiles_op( + &mut self, + i: &RiscvInstruction, + op: &str, + rs1: u32, + rs2: u32, + inst_size: u64, + ) { + // inst_size == 8 used for special cases where take arguments of precompiled of + // next instruction but no need to read again + assert!(inst_size == 2 || inst_size == 4 || inst_size == 8); + let mut zib = ZiskInstBuilder::new_from_riscv(i.rom_address, i.inst.clone()); + zib.src_a("reg", rs1 as u64, false); + zib.src_b("reg", rs2 as u64, false); + zib.op(op).unwrap(); + zib.store("reg", i.rd as i64, false, false); + zib.j(0, inst_size as i64); + zib.verbose(&format!( + "{} r{}, r{}, r{} => {op} r{}, r{rs1}, r{rs2}", + i.inst, i.rd, i.rs1, i.rs2, i.rd + )); + zib.build(); + self.insts.insert(i.rom_address, zib); + } + + /// Creates a Zisk operation that implements a RISC-V precompiles operation, i.e. an operation that + /// loads both input parameters a and b from their respective registers, + /// and stores the result c into a register + #[allow(clippy::too_many_arguments)] + pub fn create_extended_precompiles_op( + &mut self, + i: &RiscvInstruction, + op: &str, + rs1: u32, + rs2: u64, + rd: u32, + extended_arg: i64, + is_rs2_an_imm: bool, + inst_size: u64, + ) { + // inst_size == 8 used for special cases where take arguments of precompiled of + // next instruction but no need to read again + assert!(inst_size == 2 || inst_size == 4 || inst_size == 8); + let mut zib = ZiskInstBuilder::new_from_riscv(i.rom_address, i.inst.clone()); + zib.src_a("reg", rs1 as u64, false); + if is_rs2_an_imm { + zib.src_b("imm", rs2, false); + } else { + zib.src_b("reg", rs2, false); + } + zib.op(op).unwrap(); + zib.store("reg", rd as i64, false, false); + zib.j(extended_arg, inst_size as i64); + zib.verbose(&format!( + "{} r{}, r{}, r{} (precompiled {op} r{rd},r{rs1},r{rs2},{extended_arg} + jmp +{inst_size})", + i.inst, + i.rd, + i.rs1, + i.rs2, + )); + zib.build(); + self.insts.insert(i.rom_address, zib); + } + + /// Creates a Zisk operation that implements a RISC-V precompiles set extra param this + /// operation store in fixed address the value. + pub fn create_set_precompiles_param_op( + &mut self, + i: &RiscvInstruction, + rs1: u32, + inst_size: u64, + ) { + assert!(inst_size == 2 || inst_size == 4); + let mut zib = ZiskInstBuilder::new_from_riscv(i.rom_address, i.inst.clone()); + zib.src_a("imm", 0, false); + zib.src_b("reg", rs1 as u64, false); + zib.op("copyb").unwrap(); + zib.store("mem", EXTRA_PARAMS_ADDR as i64, false, false); + zib.j(0, inst_size as i64); + zib.verbose(&format!("sd r{}, (0x{}) (param 0x{:03X})", rs1, EXTRA_PARAMS_ADDR, i.csr)); + zib.build(); + self.output_precompile = Some(i.csr); + self.output_precompile_reg = Some(i.rs1); + self.insts.insert(i.rom_address, zib); + } + // beq rs1, rs2, label // eq([%rs1], [rs2]), j(label) @@ -1118,7 +1228,7 @@ impl Riscv2ZiskContext<'_> { /// in integer register rs1 is treated as a bit mask that specifies bit positions to be set in /// the CSR. Any bit that is high in rs1 will cause the corresponding bit to be set in the CSR, /// if that CSR bit is writable. - pub fn csrrs(&mut self, i: &RiscvInstruction) { + pub fn csrrs(&mut self, i: &RiscvInstruction, next_instructions: &[RiscvInstruction]) { let mut rom_address = i.rom_address; if i.rd == i.rs1 { if i.rd == 0 { @@ -1169,27 +1279,38 @@ impl Riscv2ZiskContext<'_> { self.insts.insert(rom_address, zib); } } + } else if i.rd == 0 + && (CSR_DMA_PRECOMPILED_ADDR_START..=CSR_DMA_PRECOMPILED_ADDR_END).contains(&i.csr) + { + assert!(!next_instructions.is_empty()); + // Special "extended" precompiles that could be use jmp_offset1 as extended static parameter that + // was sent to bus when is a precompiles + match i.csr { + 0x813 | 0x814 => { + self.transpile_dma_memcpy_memcmp_pattern(i, next_instructions); + } + 0x815 => { + self.transpile_dma_inputcpy_pattern(i, next_instructions); + } + 0x816 => { + self.transpile_dma_memset_pattern(i, next_instructions); + } + _ => { + panic!("Invalid CSR 0x{:03X}", i.csr); + } + } } else if i.rd == 0 { let mut zib = ZiskInstBuilder::new_from_riscv(rom_address, i.inst.clone()); zib.src_b("reg", i.rs1 as u64, false); - zib.j(4, 4); + if (CSR_PRECOMPILED_ADDR_START..=CSR_PRECOMPILED_ADDR_END).contains(&i.csr) { - match i.csr { - 0x813 | 0x814 => { - self.output_precompile = Some(i.csr); - zib.src_a("imm", 0, false); - zib.op("copyb").unwrap(); - zib.store("mem", EXTRA_PARAMS_ADDR as i64, false, false); - zib.verbose("param"); - } - _ => { - let precompiled = - CSR_PRECOMPILED[(i.csr - CSR_PRECOMPILED_ADDR_START) as usize]; - zib.src_a("imm", 0, false); - zib.op(precompiled).unwrap(); - zib.verbose(precompiled); - } - } + let precompiled = CSR_PRECOMPILED[(i.csr - CSR_PRECOMPILED_ADDR_START) as usize]; + zib.src_a("imm", 0, false); + zib.op(precompiled).unwrap(); + zib.verbose(precompiled); + // NOTE: if precompiles don't use extended static parameter (jmp_offset1), must be set to 0 + // to match with that precompiles proves + zib.j(0, 4); } else if (CSR_FCALL_PARAM_ADDR_START..=CSR_FCALL_PARAM_ADDR_END).contains(&i.csr) { let words = CSR_FCALL_PARAM_OFFSET_TO_WORDS[(i.csr - CSR_FCALL_PARAM_ADDR_START) as usize]; @@ -1199,11 +1320,13 @@ impl Riscv2ZiskContext<'_> { "csrrs 0x{0:X}, rs1={1} => copyb[fcall_param(r{1},{2})]", i.csr, i.rs1, words )); + zib.j(4, 4); } else { zib.src_a("mem", CSR_ADDR + (i.csr * 8) as u64, false); zib.op("or").unwrap(); zib.store("mem", CSR_ADDR as i64 + (i.csr * 8) as i64, false, false); zib.verbose(&format!("{} r{}, 0x{:x}, r{} # rs!=rd=0", i.inst, i.rd, i.csr, i.rs1)); + zib.j(4, 4); } zib.build(); self.insts.insert(rom_address, zib); @@ -1510,10 +1633,12 @@ impl Riscv2ZiskContext<'_> { } } */ - pub fn csrrsi(&mut self, i: &RiscvInstruction) { + pub fn csrrsi(&mut self, i: &RiscvInstruction, next_instructions: &[RiscvInstruction]) { let mut rom_address = i.rom_address; if i.rd == 0 { - if i.imme == 0 { + if i.csr == SYSCALL_DMA_MEMSET_ID as u32 { + self.transpile_dma_memset_pattern(i, next_instructions); + } else if i.imme == 0 { let mut zib = ZiskInstBuilder::new_from_riscv(rom_address, i.inst.clone()); zib.src_a("imm", 0, false); zib.src_b("imm", 0, false); @@ -1716,33 +1841,410 @@ impl Riscv2ZiskContext<'_> { self.insts.insert(rom_address, zib); } } + + fn transpile_dma_memset_pattern( + &mut self, + i: &RiscvInstruction, + next_instructions: &[RiscvInstruction], + ) { + if i.imme == 2 { + if next_instructions.len() > 1 + && next_instructions[0].inst == "addi" + && next_instructions[1].inst == "addi" + { + println!( + "Transpiling xmemset pattern at address 0x{:08x} with CSR 0x{:03X}", + i.rom_address, i.csr + ); + // xmemset transpilation pattern: + // + // csrsi 0x816, 2 ===> xmemset [x0|a0], a0, size, byte ─┐ + // addi x0, reg(dst), byte addi x0, reg(dst), byte (no-executed) │ jmp+12 + // addi x0, reg(dst), size addi x0, reg(dst), size (no-executed) │ + // .......... .......... <────────────────────────┘ + + let rs1 = i.rs1; // dst + let rs2 = next_instructions[1].imm; // count + let rd = next_instructions[0].rd; + let fill_byte = next_instructions[0].imm; // fill_byte + assert!((0..=0xFF).contains(&fill_byte)); + self.create_extended_precompiles_op( + i, + "dma_xmemset", + rs1, + rs2 as u64, + rd, + fill_byte as i64, + false, + 12, + ); + } else { + let next_0 = next_instructions.first().map(|inst| inst.inst.as_str()).unwrap_or(""); + let next_1 = next_instructions.get(1).map(|inst| inst.inst.as_str()).unwrap_or(""); + panic!( + "Invalid use of CSR (0x{:03X}) at address 0x{:08x}, must be used as xmemset with two \ + consecutive addi (next[0]:{} next[1]:{})", + i.csr, i.rom_address, next_0, next_1); + } + } else if i.imme == 0 { + if !next_instructions.is_empty() && next_instructions[0].inst == "addi" { + // xmemset transpilation pattern: + // + // csrs 0x816, reg(count) ===> xmemset [x0|a0], a0, reg(count), byte ─┐ + // addi x0, reg(dst), byte addi x0, reg(dst), byte (no-executed) │ jmp+8 + // .......... .......... <───────────────────────────────┘ + + let rs1 = i.rs1; // dst + let rs2 = next_instructions[0].rs1; // count + let rd = next_instructions[0].rd; + let fill_byte = next_instructions[0].imm; // byte (fill_byte) + println!( + "Transpiling xmemset pattern 2 at address 0x{:08x} with CSR 0x{:03X} fill_byte=0x{fill_byte:02X}", + i.rom_address, i.csr + ); + assert!((0..=0xFF).contains(&fill_byte)); + self.create_extended_precompiles_op( + i, + "dma_xmemset", + rs1, + rs2 as u64, + rd, + fill_byte as i64, + false, + 8, + ); + } else { + let next_0 = next_instructions.first().map(|inst| inst.inst.as_str()).unwrap_or(""); + panic!( + "Invalid use of CSR (0x{:03X}) at address 0x{:08x}, must be used as xmemset with a \ + consecutive addi (next[0]:{})", + i.csr, i.rom_address, next_0 + ); + } + } + } + + fn transpile_dma_memcpy_memcmp_pattern( + &mut self, + i: &RiscvInstruction, + next_instructions: &[RiscvInstruction], + ) { + if i.imme == 0 && !next_instructions.is_empty() { + if next_instructions[0].inst == "add" { + // memcpy/memcmp transpilation pattern: + // + // csrs 0x81x, reg(src) ===> sd reg(count), [EXTRA_PARAM] + // addi rd, reg(dst), reg(count) memcxx rd, reg(dst), reg(src) + // .......... .......... + + self.create_set_precompiles_param_op(i, next_instructions[0].rs2, 4); + return; + } + if next_instructions[0].inst == "addi" { + // memcpy/memcmp transpilation pattern: + // + // csrs 0x81x, reg(src) ===> memcxx rd, reg(dst), reg(src), count ─┐ + // addi rd, reg(dst), count addi rd, reg(dst), count │ jmp+8 + // .......... .......... <────────────────────────┘ + let rs1 = i.rs1; + let rs2 = next_instructions[0].rs1; + let rd = next_instructions[0].rd; + let count = next_instructions[0].imm as i64; // count + let op = if i.csr == 0x813 { "dma_xmemcpy" } else { "dma_xmemcmp" }; + self.create_extended_precompiles_op(i, op, rs1, rs2 as u64, rd, count, false, 8); + return; + } + } + let next_0 = next_instructions.first().map(|inst| inst.inst.as_str()).unwrap_or(""); + panic!( + "Invalid use of CSR (0x{:03X}) at address 0x{:08x}, must be used as memcpy/memcmp with a \ + consecutive addi (next[0]:{})", + i.csr, i.rom_address, next_0 + ); + } + fn transpile_dma_inputcpy_pattern( + &mut self, + i: &RiscvInstruction, + next_instructions: &[RiscvInstruction], + ) { + if i.imme == 0 && !next_instructions.is_empty() { + if next_instructions[0].inst == "add" { + // inputcpy transpilation pattern: + // + // csrs 0x815, reg(count) ===> inputcpy rd, reg(dst), reg(count) ─┐ + // add rd, reg(dst), reg(count) addi rd, reg(dst), reg(count) │ jmp+8 + // .......... .......... <─────────────────────┘ + let rs1 = next_instructions[0].rs1; + let rs2 = i.rs1; + let rd = next_instructions[0].rd; + self.create_extended_precompiles_op( + i, + "dma_inputcpy", + rs1, + rs2 as u64, + rd, + 0, + false, + 8, + ); + return; + } + if next_instructions[0].inst == "addi" { + // inputcpy transpilation pattern: + // + // csrs 0x815, reg(dst) ===> inputcpy rd, reg(dst), count ────┐ + // addi rd, reg(dst), count addi rd, reg(dst), count │ jmp+8 + // .......... .......... <───────────────────┘ + let rs1 = next_instructions[0].rs1; + let imm2 = next_instructions[0].imm as u64; + let rd = next_instructions[0].rd; + self.create_extended_precompiles_op(i, "dma_inputcpy", rs1, imm2, rd, 0, true, 8); + return; + } + } + let next_0 = next_instructions.first().map(|inst| inst.inst.as_str()).unwrap_or(""); + panic!( + "Invalid use of CSR (0x{:03X}) at address 0x{:08x}, must be used as inputcpy with a \ + consecutive addi (next[0]:{})", + i.csr, i.rom_address, next_0 + ); + } } // impl Riscv2ZiskContext /// Converts a buffer with RISC-V data into a vector of Zisk instructions, using the /// Riscv2ZiskContext to perform the instruction transpilation -pub fn add_zisk_code(rom: &mut ZiskRom, addr: u64, data: &[u8]) { +/// dma_addrs: (memcpy, memcmp, memset, memmove) addresses, 0 if not present +pub fn add_zisk_code(rom: &mut ZiskRom, addr: u64, data: &[u8], dma_addrs: (u64, u64, u64, u64)) { //print!("add_zisk_code() addr={}\n", addr); + let (memcpy_addr, memcmp_addr, memset_addr, memmove_addr) = dma_addrs; + // Convert input data to a u32 vector let code_vector: Vec = convert_vector(data); // Convert data vector to RISCV instructions let riscv_instructions = riscv_interpreter(addr, &code_vector); + // Detect AUIPC + JALR patterns that jump to DMA functions + // Pattern: AUIPC rd, imm20 followed by JALR ra, rd, imm12 + // Target address = PC + (imm20 << 12) + imm12 + let mut dma_call_pcs: std::collections::HashSet = std::collections::HashSet::new(); + + // Register numbers for a0, a1, a2 (x10, x11, x12) + const REG_A0: u32 = 10; + const REG_A1: u32 = 11; + const REG_A2: u32 = 12; + + // Types of register loading + #[derive(Debug, Clone)] + enum RegLoadType { + /// Immediate value: li rd, imm or addi rd, x0, imm + Immediate(i32), + /// Register + immediate: addi rd, rs, imm or mv rd, rs (imm=0) + RegPlusImm { rs: u32, imm: i32 }, + /// Load from memory: ld/lw rd, imm(rs) + MemLoad { rs: u32, imm: i32, size: u8 }, + /// Not found + NotFound, + } + + /// Find how a register is loaded before a given instruction index + fn find_reg_load( + riscv_instructions: &[RiscvInstruction], + call_idx: usize, + target_reg: u32, + ) -> (usize, RegLoadType) { + // Search backwards from call_idx + for offset in 1..=20.min(call_idx) { + let idx = call_idx - offset; + let inst = &riscv_instructions[idx]; + + // Check if this instruction writes to target_reg + if inst.rd != target_reg { + continue; + } + + // li rd, imm -> actually addi rd, x0, imm or lui+addi + // addi rd, rs, imm + if inst.inst == "addi" || inst.inst == "c.addi" { + if inst.rs1 == 0 { + // li rd, imm (addi rd, x0, imm) + return (offset, RegLoadType::Immediate(inst.imm)); + } else { + // addi rd, rs, imm + return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: inst.imm }); + } + } + + // c.li rd, imm + if inst.inst == "c.li" { + return (offset, RegLoadType::Immediate(inst.imm)); + } + + // mv rd, rs -> addi rd, rs, 0 or c.mv + if inst.inst == "c.mv" { + return (offset, RegLoadType::RegPlusImm { rs: inst.rs2, imm: 0 }); + } + + // lui rd, imm (upper immediate) + if inst.inst == "lui" || inst.inst == "c.lui" { + return (offset, RegLoadType::Immediate(inst.imm)); + } + + // ld rd, imm(rs) - 64-bit load + if inst.inst == "ld" || inst.inst == "c.ld" || inst.inst == "c.ldsp" { + return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 8 }); + } + + // lw rd, imm(rs) - 32-bit load + if inst.inst == "lw" || inst.inst == "c.lw" || inst.inst == "c.lwsp" { + return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 4 }); + } + + // lwu rd, imm(rs) - 32-bit unsigned load + if inst.inst == "lwu" { + return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 4 }); + } + + // add rd, rs1, rs2 (could be mv if rs2=0 or rs1=0) + if inst.inst == "add" || inst.inst == "c.add" { + if inst.rs1 == 0 { + return (offset, RegLoadType::RegPlusImm { rs: inst.rs2, imm: 0 }); + } else if inst.rs2 == 0 { + return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: 0 }); + } + // General add - treat as reg+imm with imm=0 (approximate) + return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: 0 }); + } + + // Other instruction that writes to target_reg but we don't recognize + // Stop searching since the register was overwritten + return (offset, RegLoadType::NotFound); + } + (0, RegLoadType::NotFound) + } + + fn reg_load_to_string(load: &RegLoadType) -> String { + match load { + RegLoadType::Immediate(imm) => format!("imm={}", imm), + RegLoadType::RegPlusImm { rs, imm } => format!("x{}+{}", rs, imm), + RegLoadType::MemLoad { rs, imm, size } => format!("{}(x{})[{}B]", imm, rs, size), + RegLoadType::NotFound => "???".to_string(), + } + } + + for i in 0..riscv_instructions.len().saturating_sub(1) { + let inst = &riscv_instructions[i]; + let next_inst = &riscv_instructions[i + 1]; + + // Check for AUIPC + JALR pattern + if inst.inst == "auipc" && next_inst.inst == "jalr" { + // AUIPC uses the same register that JALR reads from + if inst.rd == next_inst.rs1 { + // Calculate target address: PC + imm_auipc + imm_jalr + // Note: inst.imm for AUIPC is already shifted (has lower 12 bits as zero) + let target = (inst.rom_address as i64) + .wrapping_add(inst.imm as i64) + .wrapping_add(next_inst.imm as i64) as u64; + + // Check if target is a DMA function + let is_dma = (memcpy_addr != 0 && target == memcpy_addr) + || (memcmp_addr != 0 && target == memcmp_addr) + || (memset_addr != 0 && target == memset_addr) + || (memmove_addr != 0 && target == memmove_addr); + + if is_dma { + let func_name = if target == memcpy_addr { + "memcpy" + } else if target == memcmp_addr { + "memcmp" + } else if target == memset_addr { + "memset" + } else { + "inputcpy" + }; + + // Find how a0, a1, a2 are loaded (search from JALR position = i+1) + let jalr_idx = i + 1; + let (a0_offset, a0_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A0); + let (a1_offset, a1_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A1); + let (a2_offset, a2_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A2); + + // Check if any register load was not found + let has_missing = matches!(a0_load, RegLoadType::NotFound) + || matches!(a1_load, RegLoadType::NotFound) + || matches!(a2_load, RegLoadType::NotFound); + + println!( + "DMA call to {} at PC=0x{:x}:{}", + func_name, + next_inst.rom_address, + if has_missing { " [INCOMPLETE - needs analysis]" } else { "" } + ); + println!(" a0: offset=-{}, {}", a0_offset, reg_load_to_string(&a0_load)); + println!(" a1: offset=-{}, {}", a1_offset, reg_load_to_string(&a1_load)); + println!(" a2: offset=-{}, {}", a2_offset, reg_load_to_string(&a2_load)); + + dma_call_pcs.insert(inst.rom_address); + dma_call_pcs.insert(next_inst.rom_address); + } + } + } + + // Check for JAL (direct jump) pattern + if inst.inst == "jal" { + let target = (inst.rom_address as i64).wrapping_add(inst.imm as i64) as u64; + + let is_dma = (memcpy_addr != 0 && target == memcpy_addr) + || (memcmp_addr != 0 && target == memcmp_addr) + || (memset_addr != 0 && target == memset_addr) + || (memmove_addr != 0 && target == memmove_addr); + + if is_dma { + let func_name = if target == memcpy_addr { + "memcpy" + } else if target == memcmp_addr { + "memcmp" + } else if target == memset_addr { + "memset" + } else { + "inputcpy" + }; + + // Find how a0, a1, a2 are loaded + let (a0_offset, a0_load) = find_reg_load(&riscv_instructions, i, REG_A0); + let (a1_offset, a1_load) = find_reg_load(&riscv_instructions, i, REG_A1); + let (a2_offset, a2_load) = find_reg_load(&riscv_instructions, i, REG_A2); + + // Check if any register load was not found + let has_missing = matches!(a0_load, RegLoadType::NotFound) + || matches!(a1_load, RegLoadType::NotFound) + || matches!(a2_load, RegLoadType::NotFound); + + println!( + "DMA call to {} at PC=0x{:x} (JAL):{}", + func_name, + inst.rom_address, + if has_missing { " [INCOMPLETE - needs analysis]" } else { "" } + ); + println!(" a0: offset=-{}, {}", a0_offset, reg_load_to_string(&a0_load)); + println!(" a1: offset=-{}, {}", a1_offset, reg_load_to_string(&a1_load)); + println!(" a2: offset=-{}, {}", a2_offset, reg_load_to_string(&a2_load)); + + dma_call_pcs.insert(inst.rom_address); + } + } + } + // Create a context to convert RISCV instructions to ZisK instructions, using rom.insts let mut ctx = Riscv2ZiskContext { insts: &mut rom.insts, input_precompile: None, output_precompile: None, + input_precompile_reg: None, + output_precompile_reg: None, }; - // for (i, riscv_instruction) in riscv_instructions.iter().enumerate() { - // println!("RISCV#{i} 0x{:08X}", riscv_instruction.rom_address); - // } - // let zisk_memcmp_index = - // riscv_instructions.iter().position(|inst| inst.rom_address == 0x80236edc); - // let zisk_memcmp_index: Option = None; - // For all RISCV instructions for (i, riscv_instruction) in riscv_instructions.iter().enumerate() { //print!("add_zisk_code() converting RISCV instruction={}\n", @@ -1772,6 +2274,8 @@ pub fn add_zisk_code(rom: &mut ZiskRom, addr: u64, data: &[u8]) { // Convert RICV instruction to ZisK instruction and store it in rom.insts ctx.input_precompile = ctx.output_precompile; ctx.output_precompile = None; + ctx.input_precompile_reg = ctx.output_precompile_reg; + ctx.output_precompile_reg = None; ctx.convert(riscv_instruction, next_instructions); //print!(" to: {}", ctx.insts.iter().last().) } diff --git a/core/src/zisk_inst.rs b/core/src/zisk_inst.rs index 765352cbc..7b8c3b7f0 100644 --- a/core/src/zisk_inst.rs +++ b/core/src/zisk_inst.rs @@ -121,7 +121,7 @@ pub struct ZiskInst { pub store: u64, pub store_offset: i64, pub set_pc: bool, - pub op_with_step: bool, + pub is_precompiled: bool, // #[cfg(feature = "sp")] // pub set_sp: bool, pub ind_width: u64, @@ -159,7 +159,7 @@ impl Default for ZiskInst { store: 0, store_offset: 0, set_pc: false, - op_with_step: false, + is_precompiled: false, // #[cfg(feature = "sp")] // set_sp: false, ind_width: 0, @@ -234,8 +234,8 @@ impl ZiskInst { if self.set_pc { s += &format!(" set_pc={}", self.set_pc); } - if self.op_with_step { - s += &format!(" op_with_step={}", self.op_with_step); + if self.is_precompiled { + s += &format!(" op_with_step={}", self.is_precompiled); } if self.jmp_offset1 != 0 { s += &format!(" jmp_offset1={}", self.jmp_offset1); @@ -269,7 +269,7 @@ impl ZiskInst { let flags: u64 = 1 | (((self.a_src == SRC_IMM) as u64) << 1) | (((self.a_src == SRC_MEM) as u64) << 2) - | ((self.op_with_step as u64) << 3) + | ((self.is_precompiled as u64) << 3) | (((self.b_src == SRC_IMM) as u64) << 4) | (((self.b_src == SRC_MEM) as u64) << 5) | ((self.is_external_op as u64) << 6) diff --git a/core/src/zisk_inst_builder.rs b/core/src/zisk_inst_builder.rs index 615e5dc69..30a525109 100644 --- a/core/src/zisk_inst_builder.rs +++ b/core/src/zisk_inst_builder.rs @@ -208,7 +208,7 @@ impl ZiskInstBuilder { self.i.op_type = op.op_type().into(); self.i.input_size = op.input_size(); // assume that input_size > 0 implies a precompiled, and precompiled uses step on operations - self.i.op_with_step = op.input_size() > 0; + self.i.is_precompiled = op.input_size() > 0; Ok(()) } diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index dbc6d93ed..bdfbbb871 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -433,12 +433,15 @@ define_ops! { // opcpdes 0xc0-0xcf are available (DmaMemCpy, "dma_memcpy", Dma, DMA_COST, 0xd0, 8, 0, opc_dma_memcpy, op_dma_memcpy, ops_dma_memcpy), (DmaMemCmp, "dma_memcmp", Dma, DMA_COST, 0xd1, 16, 0, opc_dma_memcmp, op_dma_memcmp, ops_dma_memcmp), + (DmaInputCpy, "dma_inputcpy", Dma, DMA_COST, 0xd2, 8, 0, opc_dma_inputcpy, op_dma_inputcpy, ops_dma_inputcpy), + (DmaXMemCpy, "dma_xmemcpy", Dma, DMA_COST, 0xd6, 8, 0, opc_dma_xmemcpy, op_dma_xmemcpy, ops_dma_xmemcpy), + (DmaXMemCmp, "dma_xmemcmp", Dma, DMA_COST, 0xd7, 16, 0, opc_dma_xmemcmp, op_dma_xmemcmp, ops_dma_xmemcmp), + (DmaXMemSet, "dma_xmemset", Dma, DMA_COST, 0xd9, 8, 0, opc_dma_xmemset, op_dma_xmemset, ops_dma_xmemset), // opcodes 0xd2-0xd9 future reserved for dma operations (memset, memcpy256, memcmp256) (Dma64Aligned, "_dma_64_aligned", Dma, DMA_64_ALIGNED_COST, 0xda, 8, 0, opc_virtual, op_virtual, ops_virtual), (DmaUnaligned, "_dma_unaligned", Dma, DMA_UNALIGNED_COST, 0xdb, 8, 0, opc_virtual, op_virtual, ops_virtual), (DmaPre, "_dma_pre", Dma, DMA_PRE_POST_COST, 0xdc, 8, 0, opc_virtual, op_virtual, ops_virtual), (DmaPost, "_dma_post", Dma, DMA_PRE_POST_COST, 0xdd, 8, 0, opc_virtual, op_virtual, ops_virtual), - (DmaCmpByte, "_dma_cmp_byte", Dma, DMA_PRE_POST_COST, 0xde, 8, 0, opc_virtual, op_virtual, ops_virtual), // opcodes 0xda-0xdf reserved for dma extra operations (costs) // opcodes 0xe0,0xe1 are available (Arith384Mod, "arith384_mod", ArithEq384, ARITH_EQ_384_COST, 0xe2, 232, 48, opc_arith384_mod, op_arith384_mod, ops_arith384_mod), @@ -2418,27 +2421,32 @@ pub fn opc_halt(ctx: &mut InstContext) { } pub fn opc_dma_memcpy(ctx: &mut InstContext) { + opc_dma_memcpys(ctx, false) +} +pub fn opc_dma_xmemcpy(ctx: &mut InstContext) { + opc_dma_memcpys(ctx, true) +} +fn opc_dma_memcpys(ctx: &mut InstContext, extended: bool) { let dst = ctx.a; let src = ctx.b; match ctx.emulation_mode { EmulationMode::Mem => { - let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8); + let count = + if extended { ctx.extended_arg as u64 } else { ctx.mem.read(EXTRA_PARAMS_ADDR, 8) }; ctx.mem.memcpy(dst, src, count); } EmulationMode::GenerateMemReads => { // In generate mode we need to populate precompiled.input_data with // information needed - let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8); + let count = + if extended { ctx.extended_arg as u64 } else { ctx.mem.read(EXTRA_PARAMS_ADDR, 8) }; ctx.precompiled.input_data.clear(); #[cfg(feature = "debug_dma")] - println!( - "opc_dma_memcpy 0x{dst:08X} 0x{src:08X} {count} GMR STEP:{}", - ctx.emulation_mode, ctx.step - ); + println!("opc_dma_memcpy 0x{dst:08X} 0x{src:08X} {count} GMR STEP:{}", ctx.step); - let encoded = DmaInfo::fast_encode_memcpy(dst, src, count as usize); + let encoded = DmaInfo::encode_memcpy(dst, src, count as usize); ctx.precompiled.input_data.push(encoded); if count > 0 { @@ -2471,7 +2479,7 @@ pub fn opc_dma_memcpy(ctx: &mut InstContext) { data_len += src64_count; #[cfg(feature = "debug_dma")] println!( - "PRECOMPILED.INPUT_DATA: [{}] data_len:{data_len}", + "PRECOMPILED.MEMCPY.INPUT_DATA: [{}] data_len:{data_len}", ctx.precompiled .input_data .iter() @@ -2509,114 +2517,201 @@ pub fn opc_dma_memcpy(ctx: &mut InstContext) { pub fn op_dma_memcpy(_a: u64, _b: u64) -> (u64, bool) { unimplemented!("op_dma_memcpy() is not implemented"); } +#[inline(always)] +pub fn op_dma_xmemcpy(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_dma_xmemcpy() is not implemented"); +} #[inline(always)] pub fn ops_dma_memcpy(ctx: &InstContext, stats: &mut dyn OpStats) { - let dst = ctx.a; - let src = ctx.b; - let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8); + ops_dma_memcpys(ctx, stats, false) +} +#[inline(always)] +pub fn ops_dma_xmemcpy(ctx: &InstContext, stats: &mut dyn OpStats) { + ops_dma_memcpys(ctx, stats, true) +} +#[inline(always)] +fn ops_dma_memcpys(ctx: &InstContext, stats: &mut dyn OpStats, extended: bool) { + let addr_a = ctx.a; + let addr_b = ctx.b; + let count = if extended { ctx.extended_arg as u64 } else { ctx.mem.read(EXTRA_PARAMS_ADDR, 8) }; // pre, post, dma_align, dma_unalign if count == 0 { return; } - let pre = dst & 0x07 != 0; - let dst64 = dst & !0x07; - let src64 = src & !0x07; + let offset_a = addr_a & 0x07; + let offset_b = addr_b & 0x07; + let addr64_a = addr_a - offset_a; + let addr64_b = addr_b - offset_b; + let pre_count = (8 - offset_a) & 0x07; - if pre { - stats.mem_align_read(dst64, 1); - stats.mem_align_read(src64, 2); - stats.mem_align_write(dst64, 1); + if pre_count > 0 { + stats.mem_align_read(addr64_a, 1); + stats.mem_align_read(addr64_b, 1 + ((offset_b + pre_count) > 8) as usize); + stats.mem_align_write(addr64_a, 1); } - let dst64_end = (dst + count - 1) & !0x07; - let src64_end = (src + count - 1) & !0x07; - let post = dst64_end > dst64 && (dst + count - 1) & 0x07 != 7; - if post { - stats.mem_align_read(dst64_end, 1); - stats.mem_align_read(src64_end - 8, 2); - stats.mem_align_write(dst64_end, 1); + let post_count = (count - pre_count) & 0x07; + let remain_b = (16 - offset_a - pre_count) & 0x07; + let addr64_a_end = (addr_a + count - 1) & !0x07; + let addr64_b_end = (addr_b + count - 1) & !0x07; + if post_count > 0 { + let extra_b = (remain_b < post_count) as u64; + stats.mem_align_read(addr64_a_end, 1); + stats.mem_align_read(addr64_b_end - extra_b * 8, 1 + extra_b as usize); + stats.mem_align_write(addr64_a_end, 1); } - if count < 8 { - stats.add_extras(&[(ZiskOp::_DMA_PRE, pre as usize), (ZiskOp::_DMA_POST, post as usize)]); + let loop_count = ((count - pre_count - post_count) >> 32) as usize; + if loop_count == 0 { + // with count < 8, there aren't 64-bits loops. + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, (pre_count > 0) as usize), + (ZiskOp::_DMA_POST, (post_count > 0) as usize), + ]); } else { - let first_loop_dst64 = (dst + 7) >> 3; - let first_loop_src64 = (src + 7) >> 3; - let last_loop_dst64 = (dst + count - 8) >> 3; - let loop_count = (last_loop_dst64 + 1 - first_loop_dst64) as usize; + // calculate the resources used by 64-bits loop. + // count used are number of bytes read to demostrate memcmp(), usually count_eq + 1, + // but if all bytes are equal count = count_eq, no need extra reads + let first_loop_dst64 = (addr_a + pre_count) >> 3; + let first_loop_src64 = (addr_b + pre_count) >> 3; // same alignment - if dst & 0x07 == src & 0x07 { + if addr_a & 0x07 == addr_b & 0x07 { stats.mem_align_read(first_loop_src64, loop_count); stats.mem_align_write(first_loop_dst64, loop_count); + // add information about other machines to demostrate operation let units = loop_count.div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); stats.add_extras(&[ - (ZiskOp::_DMA_PRE, pre as usize), - (ZiskOp::_DMA_POST, post as usize), - (ZiskOp::_DMA_64_ALIGNED, units), + (ZiskOp::_DMA_PRE, (pre_count > 0) as usize), + (ZiskOp::_DMA_POST, (post_count > 0) as usize), + (ZiskOp::_DMA_64_ALIGNED, loop_count), ]); } else { stats.mem_align_read(first_loop_src64, loop_count + 1); stats.mem_align_write(first_loop_dst64, loop_count); + // add information about other machines to demostrate operation stats.add_extras(&[ - (ZiskOp::_DMA_PRE, pre as usize), - (ZiskOp::_DMA_POST, post as usize), + (ZiskOp::_DMA_PRE, (pre_count > 0) as usize), + (ZiskOp::_DMA_POST, (post_count > 0) as usize), (ZiskOp::_DMA_UNALIGNED, loop_count + 1), ]); } } } +#[inline(always)] pub fn opc_dma_memcmp(ctx: &mut InstContext) { + opc_dma_memcmps(ctx, false) +} +#[inline(always)] +pub fn opc_dma_xmemcmp(ctx: &mut InstContext) { + opc_dma_memcmps(ctx, true) +} + +fn opc_dma_memcmps(ctx: &mut InstContext, extended: bool) { let addr_a = ctx.a; let addr_b = ctx.b; - let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8); - - println!("opc_dma_memcmp 0x{addr_a:08X} 0x{addr_b:08X} {count} {:?}", ctx.emulation_mode); + let step = ctx.step; - if ctx.emulation_mode == EmulationMode::ConsumeMemReads { - ctx.c = ctx.precompiled.input_data[0]; - ctx.flag = false; - return; - } - let (op_result, count_eq) = ctx.mem.memcmp(addr_a, addr_b, count); - - if let EmulationMode::GenerateMemReads = ctx.emulation_mode { - // In generate mode we need to populate precompiled.input_data with - // information needed - ctx.precompiled.input_data.clear(); + match ctx.emulation_mode { + EmulationMode::Mem => { + let count = + if extended { ctx.extended_arg as u64 } else { ctx.mem.read(EXTRA_PARAMS_ADDR, 8) }; + let (result, effective_count) = ctx.mem.memcmp(addr_a, addr_b, count); + ctx.c = result; + } + EmulationMode::GenerateMemReads => { + // In generate mode we need to populate precompiled.input_data with + // information needed + let count = + if extended { ctx.extended_arg as u64 } else { ctx.mem.read(EXTRA_PARAMS_ADDR, 8) }; + ctx.precompiled.input_data.clear(); - // first element was the result of operation - ctx.precompiled.input_data.push(op_result); - ctx.precompiled.input_data.push(count_eq as u64); + #[cfg(feature = "debug_dma")] + println!("opc_dma_memcmp 0x{addr_a:08X} 0x{addr_b:08X} {count} GMR STEP:{step}"); + let (result, effective_count) = ctx.mem.memcmp(addr_a, addr_b, count); - let count_used = std::cmp::min(count, count_eq as u64 + 1); - ctx.mem.push_from_mem(&mut ctx.precompiled.input_data, addr_a, count_used); - ctx.mem.push_from_mem(&mut ctx.precompiled.input_data, addr_b, count_used); + let encoded = DmaInfo::encode_memcmp(addr_a, addr_b, effective_count, result); + println!("encoded: 0x{encoded:016X} effective_count:{effective_count}/{} result:0x{result:016X} count:{count} GMR S:{step}",DmaInfo::get_count(encoded)); + ctx.precompiled.input_data.push(encoded); + ctx.precompiled.input_data.push(count); - // read full source data - ctx.precompiled.step = ctx.step; + if count > 0 { + let addr_a_words = ((addr_a + effective_count as u64 + 7) >> 3) - (addr_a >> 3); + let addr_b_words = ((addr_b + effective_count as u64 + 7) >> 3) - (addr_b >> 3); + ctx.mem.push_from_mem( + &mut ctx.precompiled.input_data, + addr_a & !0x07, + addr_a_words * 8, + ); + ctx.mem.push_from_mem( + &mut ctx.precompiled.input_data, + addr_b & !0x07, + addr_b_words * 8, + ); + let data_len = addr_a_words + addr_b_words; + println!("dma_memcmp 0x{addr_a:08X} 0x{addr_b:08X} {count} {} GMR S:{step} DL:{data_len}", ctx.precompiled.input_data.len()); + #[cfg(feature = "debug_dma")] + println!( + "PRECOMPILED.MEMCMP.INPUT_DATA: [{}] data_len:{data_len}", + ctx.precompiled + .input_data + .iter() + .map(|x| format!("0x{x:016X}")) + .collect::>() + .join(",") + ); + } + ctx.precompiled.output_data.clear(); + ctx.precompiled.step = step; + ctx.c = result; + } + EmulationMode::ConsumeMemReads => { + let encoded = ctx.precompiled.input_data[0]; + let bus_count = ctx.precompiled.input_data[1]; + // println!("encoded: 0x{encoded:016X} bus_count:{bus_count} CMR S:{step}"); + let count = DmaInfo::get_count(encoded); + let addr_a_words = ((addr_a as usize + count + 7) >> 3) - (addr_a as usize >> 3); + let addr_b_words = ((addr_b as usize + count + 7) >> 3) - (addr_b as usize >> 3); + let data_len = addr_a_words + addr_b_words; + #[cfg(feature = "debug_dma")] + println!("dma_memcmp 0x{addr_a:08X} 0x{addr_b:08X} {count} CMR S:{step} DL:{data_len}"); + ctx.data_ext_len = data_len; + ctx.c = DmaInfo::get_memcmp_res_as_u64(encoded); + } } - - ctx.c = op_result; ctx.flag = false; } -/// Unimplemented. Arith256 can only be called from the system call context via InstContext. -/// This is provided just for completeness. +/// Unimplemented. DmaMemCmp and DmaXºMemCmp can only be called from the system call context +/// via InstContext. This is provided just for completeness. #[inline(always)] pub fn op_dma_memcmp(_a: u64, _b: u64) -> (u64, bool) { unimplemented!("op_dma_memcmp() is not implemented"); } +#[inline(always)] +pub fn op_dma_xmemcmp(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_dma_xmemcmp() is not implemented"); +} + #[inline(always)] pub fn ops_dma_memcmp(ctx: &InstContext, stats: &mut dyn OpStats) { + ops_dma_memcmps(ctx, stats, false) +} +#[inline(always)] +pub fn ops_dma_xmemcmp(ctx: &InstContext, stats: &mut dyn OpStats) { + ops_dma_memcmps(ctx, stats, true) +} + +#[inline(always)] +fn ops_dma_memcmps(ctx: &InstContext, stats: &mut dyn OpStats, extended: bool) { let addr_a = ctx.a; let addr_b = ctx.b; - let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8); + let count = if extended { ctx.extended_arg as u64 } else { ctx.mem.read(EXTRA_PARAMS_ADDR, 8) }; // pre, post, dma_align, dma_unalign if count == 0 { @@ -2624,40 +2719,43 @@ pub fn ops_dma_memcmp(ctx: &InstContext, stats: &mut dyn OpStats) { } let (res, count_eq) = ctx.mem.memcmp(addr_a, addr_b, count); - let pre = addr_a & 0x07 != 0; - let addr64_a = addr_a & !0x07; - let addr64_b = addr_b & !0x07; - - if pre { + let count = if count_eq as u64 == count { count } else { count_eq as u64 + 1 }; + let offset_a = addr_a & 0x07; + let offset_b = addr_b & 0x07; + let addr64_a = addr_a - offset_a; + let addr64_b = addr_b - offset_b; + let pre_count = std::cmp::min((8 - offset_a) & 0x07, count); + + if pre_count > 0 { stats.mem_align_read(addr64_a, 1); - stats.mem_align_read(addr64_b, 2); + stats.mem_align_read(addr64_b, 1 + ((offset_b + pre_count) > 8) as usize); stats.mem_align_read(addr64_a, 1); } + let post_count = (count - pre_count) & 0x07; + let remain_b = (16 - offset_a - pre_count) & 0x07; let addr64_a_end = (addr_a + count - 1) & !0x07; let addr64_b_end = (addr_b + count - 1) & !0x07; - let post = addr64_a_end > addr64_a && (addr_a + count - 1) & 0x07 != 7; - if post { + if post_count > 0 { + let extra_b = (remain_b < post_count) as u64; stats.mem_align_read(addr64_a_end, 1); - stats.mem_align_read(addr64_b_end - 8, 2); + stats.mem_align_read(addr64_b_end - extra_b * 8, 1 + extra_b as usize); stats.mem_align_read(addr64_a_end, 1); } - if count < 8 { + let loop_count = ((count - pre_count - post_count) >> 32) as usize; + if loop_count == 0 { // with count < 8, there aren't 64-bits loops. - stats.add_extras(&[(ZiskOp::_DMA_PRE, pre as usize), (ZiskOp::_DMA_POST, post as usize)]); + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, (pre_count > 0) as usize), + (ZiskOp::_DMA_POST, (post_count > 0) as usize), + ]); } else { // calculate the resources used by 64-bits loop. // count used are number of bytes read to demostrate memcmp(), usually count_eq + 1, // but if all bytes are equal count = count_eq, no need extra reads - let count_used = std::cmp::min(count, count_eq as u64 + 1); - let first_loop_dst64 = (addr_a + 7) >> 3; - let first_loop_src64 = (addr_b + 7) >> 3; - let last_loop_dst64 = (addr_a + count_used - 8) >> 3; - let loop_count = (last_loop_dst64 + 1 - first_loop_dst64) as usize; - - // need a machine to compare one byte - let compare_byte = count == count_eq as u64; + let first_loop_dst64 = (addr_a + pre_count) >> 3; + let first_loop_src64 = (addr_b + pre_count) >> 3; // same alignment if addr_a & 0x07 == addr_b & 0x07 { @@ -2666,21 +2764,408 @@ pub fn ops_dma_memcmp(ctx: &InstContext, stats: &mut dyn OpStats) { // add information about other machines to demostrate operation let units = loop_count.div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); stats.add_extras(&[ - (ZiskOp::_DMA_PRE, pre as usize), - (ZiskOp::_DMA_POST, post as usize), - (ZiskOp::_DMA_64_ALIGNED, units), - (ZiskOp::_DMA_CMP_BYTE, compare_byte as usize), + (ZiskOp::_DMA_PRE, (pre_count > 0) as usize), + (ZiskOp::_DMA_POST, (post_count > 0) as usize), + (ZiskOp::_DMA_64_ALIGNED, loop_count), ]); } else { stats.mem_align_read(first_loop_src64, loop_count + 1); stats.mem_align_read(first_loop_dst64, loop_count); // add information about other machines to demostrate operation stats.add_extras(&[ - (ZiskOp::_DMA_PRE, pre as usize), - (ZiskOp::_DMA_POST, post as usize), - (ZiskOp::_DMA_64_ALIGNED, loop_count + 1), - (ZiskOp::_DMA_CMP_BYTE, compare_byte as usize), + (ZiskOp::_DMA_PRE, (pre_count > 0) as usize), + (ZiskOp::_DMA_POST, (post_count > 0) as usize), + (ZiskOp::_DMA_UNALIGNED, loop_count + 1), ]); } } } + +fn read_from_input(ctx: &mut InstContext, dst: u64, count: u64) { + // Check for consistency + if count % 8 != 0 { + panic!("opc_dma_inputcpy() called without invalid count {count}"); + } + let count64 = count >> 3; + if ctx.fcall.result_size == 0 { + panic!("opc_dma_inputcpy() called with ctx.fcall.result_size==0"); + } + if ctx.fcall.result_size as usize > FCALL_RESULT_MAX_SIZE { + panic!( + "opc_dma_inputcpy() called with ctx.fcall.result_size=={}>32", + ctx.fcall.result_size + ); + } + if (ctx.fcall.result_got - 1 + count64) > ctx.fcall.result_size { + panic!( + "opc_dma_inputcpy() called with ctx.fcall.result_got({}) + {count64} >= ctx.fcall.result_size {}", + ctx.fcall.result_got, ctx.fcall.result_size + ); + } + ctx.mem.memcpy_from_data( + dst, + count, + &ctx.fcall.result, + (ctx.fcall.result_got - 1) as usize * 8, + ); + ctx.fcall.result_got += count64; +} + +fn read_and_get_from_input(ctx: &mut InstContext, dst: u64, count: u64) -> Vec { + // Check for consistency + if count % 8 != 0 { + panic!("opc_dma_inputcpy() called without invalid count {count}"); + } + let count64 = count >> 3; + if ctx.fcall.result_size == 0 { + panic!("opc_dma_inputcpy() called with ctx.fcall.result_size==0"); + } + if ctx.fcall.result_size as usize > FCALL_RESULT_MAX_SIZE { + panic!( + "opc_dma_inputcpy() called with ctx.fcall.result_size=={}>32", + ctx.fcall.result_size + ); + } + if (ctx.fcall.result_got - 1 + count64) > ctx.fcall.result_size { + panic!( + "opc_dma_inputcpy() called with ctx.fcall.result_got({}) + {count64} >= ctx.fcall.result_size {}", + ctx.fcall.result_got, ctx.fcall.result_size + ); + } + + ctx.mem.memcpy_from_data( + dst, + count, + &ctx.fcall.result, + (ctx.fcall.result_got - 1) as usize * 8, + ); + + let offset = (dst & 0x07) as usize; + let start_index = (ctx.fcall.result_got - 1) as usize; + let mut qwords_added = 0; + let mut input_data = Vec::new(); + + if offset == 0 { + // Fast path: aligned, direct copy + for i in 0..count64 as usize { + input_data.push(ctx.fcall.result[start_index + i]); + qwords_added += 1; + } + } else { + // Slow path: unaligned, need to shift and merge words + // When unaligned, we need count64 + 1 output words + let shift_bits = (offset * 8) as u32; + let shift_bits_comp = 64 - shift_bits; + + // First word: padding zeros in lower bytes, first data bytes in upper bytes + let first_word = ctx.fcall.result[start_index] << shift_bits; + input_data.push(first_word); + qwords_added += 1; + + // Middle words: merge parts of consecutive data words + for i in 0..(count64 as usize - 1) { + let low_part = ctx.fcall.result[start_index + i] >> shift_bits_comp; + let high_part = ctx.fcall.result[start_index + i + 1] << shift_bits; + input_data.push(low_part | high_part); + qwords_added += 1; + } + + // Last word: remaining bytes from last data word + if count64 > 0 { + let last_word = ctx.fcall.result[start_index + count64 as usize - 1] >> shift_bits_comp; + input_data.push(last_word); + qwords_added += 1; + } + } + + ctx.fcall.result_got += count64; + + input_data +} + +#[inline(always)] +pub fn opc_dma_inputcpy(ctx: &mut InstContext) { + opc_dma_inputcpys(ctx, false) +} + +#[inline(always)] +pub fn opc_dma_xinputcpy(ctx: &mut InstContext) { + opc_dma_inputcpys(ctx, true) +} + +#[inline(always)] +fn opc_dma_inputcpys(ctx: &mut InstContext, extended: bool) { + let dst = ctx.a; + let count = if extended { ctx.extended_arg as u64 } else { ctx.b }; + + match ctx.emulation_mode { + EmulationMode::Mem => { + read_from_input(ctx, dst, count); + } + EmulationMode::GenerateMemReads => { + // In generate mode we need to populate precompiled.input_data with + // information needed + ctx.precompiled.input_data.clear(); + + #[cfg(feature = "debug_dma")] + println!("opc_dma_inputcpy 0x{dst:08X} {count} GMR STEP:{}", ctx.step); + + let encoded = DmaInfo::encode_inputcpy(dst, count as usize); + ctx.precompiled.input_data.push(encoded); + + if count > 0 { + // read first dst unaligned part for dma-pre + let mut data_len = 0; + let dst64 = dst & !0x07; + // if dst64 != dst { + if DmaInfo::get_pre_count(encoded) > 0 { + let pre_data = ctx.mem.read(dst64, 8); + data_len += 1; + ctx.precompiled.input_data.push(pre_data); + } + + // read last dst unaligned part for dma-post + let to_dst = dst + count - 1; + // if to_dst & 0x07 != 0x07 { + if DmaInfo::get_post_count(encoded) > 0 { + let post_data = ctx.mem.read(to_dst & !0x07, 8); + data_len += 1; + ctx.precompiled.input_data.push(post_data); + } + #[cfg(feature = "debug_dma")] + println!( + "PRECOMPILED.INPUTCPY.INPUT_DATA: [{}] data_len:{data_len}", + ctx.precompiled + .input_data + .iter() + .map(|x| format!("0x{x:016X}")) + .collect::>() + .join(",") + ); + + let input_data = read_and_get_from_input(ctx, dst, count); + data_len += input_data.len(); + + assert_eq!(data_len, DmaInfo::get_data_size(encoded)); + + ctx.precompiled.input_data.extend(input_data); + } + ctx.precompiled.output_data.clear(); + ctx.precompiled.step = ctx.step; + } + EmulationMode::ConsumeMemReads => { + let encoded = ctx.precompiled.input_data[0]; + let count = DmaInfo::get_count(encoded); + #[cfg(feature = "debug_dma")] + println!( + "opc_dma_inputcpy 0x{dst:08X} {count} CMR STEP:{} DATA_EXT_LEN:{}", + ctx.step, + DmaInfo::get_data_size(encoded) + ); + ctx.data_ext_len = DmaInfo::get_data_size(encoded); + } + } + ctx.c = 0; + ctx.flag = false; +} + +#[inline(always)] +pub fn op_dma_inputcpy(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_dma_inputcpy() is not implemented"); +} + +#[inline(always)] +pub fn op_dma_xinputcpy(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_dma_xinputcpy() is not implemented"); +} + +#[inline(always)] +pub fn ops_dma_inputcpy(ctx: &InstContext, stats: &mut dyn OpStats) { + ops_dma_inputcpys(ctx, stats, false); +} +#[inline(always)] +pub fn ops_dma_xinputcpy(ctx: &InstContext, stats: &mut dyn OpStats) { + ops_dma_inputcpys(ctx, stats, true); +} + +#[inline(always)] +fn ops_dma_inputcpys(ctx: &InstContext, stats: &mut dyn OpStats, extended: bool) { + let addr_a = ctx.a; + let count = if extended { ctx.extended_arg as u64 } else { ctx.b }; + + // pre, post, dma_align, dma_unalign + if count == 0 { + return; + } + + let offset_a = addr_a & 0x07; + let addr64_a = addr_a - offset_a; + let pre_count = (8 - offset_a) & 0x07; + + if pre_count > 0 { + stats.mem_align_read(addr64_a, 1); + stats.mem_align_write(addr64_a, 1); + } + + let post_count = (count - pre_count) & 0x07; + let addr64_a_end = (addr_a + count - 1) & !0x07; + if post_count > 0 { + stats.mem_align_read(addr64_a_end, 1); + stats.mem_align_write(addr64_a_end, 1); + } + + let loop_count = ((count - pre_count - post_count) >> 32) as usize; + if loop_count == 0 { + // with count < 8, there aren't 64-bits loops. + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, (pre_count > 0) as usize), + (ZiskOp::_DMA_POST, (post_count > 0) as usize), + ]); + } else { + // calculate the resources used by 64-bits loop. + // count used are number of bytes read to demostrate memcmp(), usually count_eq + 1, + // but if all bytes are equal count = count_eq, no need extra reads + let first_loop_dst64 = (addr_a + pre_count) >> 3; + + stats.mem_align_write(first_loop_dst64, loop_count); + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, (pre_count > 0) as usize), + (ZiskOp::_DMA_POST, (post_count > 0) as usize), + (ZiskOp::_DMA_64_ALIGNED, loop_count), + ]); + } +} + +#[inline(always)] +pub fn opc_dma_xmemset(ctx: &mut InstContext) { + let dst = ctx.a; + let count = ctx.b; + let fill_byte = ctx.extended_arg as u8; + + match ctx.emulation_mode { + EmulationMode::Mem => { + ctx.mem.memset(dst, count, fill_byte); + } + EmulationMode::GenerateMemReads => { + // In generate mode we need to populate precompiled.input_data with + // information needed + ctx.precompiled.input_data.clear(); + + #[cfg(feature = "debug_dma")] + println!( + "opc_dma_memset 0x{dst:08X} 0x{fill_byte:02X} {count} GMR STEP:{} PC:0x{:08x}", + ctx.step, ctx.pc + ); + + let encoded = DmaInfo::encode_memset(dst, count as usize, fill_byte); + println!( + "encoded: 0x{encoded:016X} count:{count} GMR S:{} {}", + ctx.step, + DmaInfo::to_string(encoded) + ); + ctx.precompiled.input_data.push(encoded); + + if count > 0 { + // read first dst unaligned part for dma-pre + let mut data_len = 0; + let dst64 = dst & !0x07; + // if dst64 != dst { + if DmaInfo::get_pre_count(encoded) > 0 { + let pre_data = ctx.mem.read(dst64, 8); + data_len += 1; + ctx.precompiled.input_data.push(pre_data); + } + + // read last dst unaligned part for dma-post + let to_dst = dst + count - 1; + // if to_dst & 0x07 != 0x07 { + if DmaInfo::get_post_count(encoded) > 0 { + let post_data = ctx.mem.read(to_dst & !0x07, 8); + data_len += 1; + ctx.precompiled.input_data.push(post_data); + } + #[cfg(feature = "debug_dma")] + println!( + "PRECOMPILED.MEMSET.INPUT_DATA: [{}] data_len:{data_len}", + ctx.precompiled + .input_data + .iter() + .map(|x| format!("0x{x:016X}")) + .collect::>() + .join(",") + ); + assert_eq!(data_len as usize, DmaInfo::get_pre_writes(encoded)); + ctx.mem.memset(dst, count, fill_byte); + } + ctx.precompiled.output_data.clear(); + ctx.precompiled.step = ctx.step; + } + EmulationMode::ConsumeMemReads => { + let encoded = ctx.precompiled.input_data[0]; + let count = DmaInfo::get_count(encoded); + #[cfg(feature = "debug_dma")] + println!( + "opc_dma_memset 0x{dst:08X} 0x{fill_byte:02X} {count} CMR STEP:{} DATA_EXT_LEN:{}", + ctx.step, + DmaInfo::get_data_size(encoded) + ); + ctx.data_ext_len = DmaInfo::get_pre_writes(encoded); + } + } + ctx.c = 0; + ctx.flag = false; +} + +#[inline(always)] +pub fn op_dma_xmemset(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_dma_memset() is not implemented"); +} + +#[inline(always)] +pub fn ops_dma_xmemset(ctx: &InstContext, stats: &mut dyn OpStats) { + let addr_a = ctx.a; + let count = ctx.b; + + // pre, post, dma_align, dma_unalign + if count == 0 { + return; + } + + let offset_a = addr_a & 0x07; + let addr64_a = addr_a - offset_a; + let pre_count = std::cmp::min((8 - offset_a) & 0x07, count); + + if pre_count > 0 { + stats.mem_align_read(addr64_a, 1); + stats.mem_align_write(addr64_a, 1); + } + + let post_count = (count - pre_count) & 0x07; + let addr64_a_end = (addr_a + count - 1) & !0x07; + if post_count > 0 { + stats.mem_align_read(addr64_a_end, 1); + stats.mem_align_write(addr64_a_end, 1); + } + + let loop_count = ((count - pre_count - post_count) >> 32) as usize; + if loop_count == 0 { + // with count < 8, there aren't 64-bits loops. + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, (pre_count > 0) as usize), + (ZiskOp::_DMA_POST, (post_count > 0) as usize), + ]); + } else { + // calculate the resources used by 64-bits loop. + // count used are number of bytes read to demostrate memcmp(), usually count_eq + 1, + // but if all bytes are equal count = count_eq, no need extra reads + let first_loop_dst64 = (addr_a + pre_count) >> 3; + + stats.mem_align_write(first_loop_dst64, loop_count); + // add information about other machines to demostrate operation + stats.add_extras(&[ + (ZiskOp::_DMA_PRE, (pre_count > 0) as usize), + (ZiskOp::_DMA_POST, (post_count > 0) as usize), + (ZiskOp::_DMA_64_ALIGNED, loop_count), + ]); + } +} diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 190ec909a..d4fc82092 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -6874,6 +6874,19 @@ impl ZiskRom2Asm { ZiskOp::DmaMemCmp => { unimplemented!(); } + ZiskOp::DmaInputCpy => { + unimplemented!(); + } + ZiskOp::DmaXMemSet => { + unimplemented!(); + } + ZiskOp::DmaXMemCpy => { + unimplemented!(); + } + ZiskOp::DmaXMemCmp => { + unimplemented!(); + } + ZiskOp::Dma64Aligned => { unimplemented!("Internal opcode Dma64Aligned"); } @@ -6886,9 +6899,6 @@ impl ZiskRom2Asm { ZiskOp::DmaPost => { unimplemented!("Internal opcode DmaPost"); } - ZiskOp::DmaCmpByte => { - unimplemented!("Internal opcode DmaCmpByte"); - } } } diff --git a/emulator-asm/src/dma/direct_memcpy_mops.asm b/emulator-asm/src/dma/direct_memcpy_mops.asm index d256096a4..fe211df0a 100644 --- a/emulator-asm/src/dma/direct_memcpy_mops.asm +++ b/emulator-asm/src/dma/direct_memcpy_mops.asm @@ -30,9 +30,9 @@ .extern fast_dma_encode .include "dma_constants.inc" -.equ MOPS_ALIGNED_READ_2W, ((2 << MOPS_BLOCK_WORDS_SBITS) + MOPS_ALIGNED_BLOCK_READ) -.equ LOOP_COUNT_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_SBITS - LOOP_COUNT_SBITS) -.equ PRE_WRITES_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_SBITS - PRE_WRITES_SBITS) +.equ MOPS_ALIGNED_READ_2W, ((2 << MOPS_BLOCK_WORDS_RS) + MOPS_ALIGNED_BLOCK_READ) +.equ LOOP_COUNT_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_RS - LOOP_COUNT_RS) +.equ PRE_WRITES_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_RS - PRE_WRITES_RS) .section .text @@ -137,7 +137,7 @@ direct_dma_memcpy_mops: mov [r12 + r13 * 8], r9 # ~4 cycles - write dst post-value to trace mov r9, rax - shr r9, PRE_AND_LOOP_BYTES_SBITS + shr r9, PRE_AND_LOOP_BYTES_RS add r9, rsi and r9, ALIGN_MASK @@ -162,9 +162,9 @@ direct_dma_memcpy_mops: .L_src_to_mops: mov rcx, rax # 1 cycle - rcx = encoded - shr rcx, LOOP_COUNT_SBITS # 1 cycle - rcx = loop (32 bits) + shr rcx, LOOP_COUNT_RS # 1 cycle - rcx = loop (32 bits) jz .L_save_dst_with_loop_count_zero - shl rcx, MOPS_BLOCK_WORDS_SBITS # 1 cycle - rcx = loop | 0 (4 bits) | (32 bits) + shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - rcx = loop | 0 (4 bits) | (32 bits) test rax, UNALIGNED_DST_SRC_MASK jnz .L_src_extra_for_unaligned_loop @@ -271,7 +271,7 @@ direct_dma_memcpy_mops: .L_copy_forward_loop: # Copy aligned qwords (main bulk of data) mov rcx, rax # 1 cycle - shr rcx, LOOP_COUNT_SBITS # 1 cycle - rcx = loop_count (bits 32-63) + shr rcx, LOOP_COUNT_RS # 1 cycle - rcx = loop_count (bits 32-63) rep movsq # ~1.5-2 cycles per qword (aligned, optimized) # rsi, rdi advanced by loop_count * 8 @@ -283,7 +283,7 @@ direct_dma_memcpy_mops: # Extract and copy post_count bytes (1-7 bytes after aligned data) mov rcx, rax # 1 cycle - shr rcx, POST_COUNT_SBITS # 1 cycle - shift post_count to position + shr rcx, POST_COUNT_RS # 1 cycle - shift post_count to position and rcx, 0x07 # 1 cycle - rcx = post_count (bits 43-45) rep movsb # ~3-5 cycles per byte diff --git a/emulator-asm/src/dma/direct_memcpy_mtrace.asm b/emulator-asm/src/dma/direct_memcpy_mtrace.asm index 3dfb4ad79..ec2ae7fe3 100644 --- a/emulator-asm/src/dma/direct_memcpy_mtrace.asm +++ b/emulator-asm/src/dma/direct_memcpy_mtrace.asm @@ -249,10 +249,10 @@ direct_dma_memcpy_mtrace: # Total qwords = loop_count (bits 0-31) + extra_src_reads (bits 48-50) mov R_AUX2, R_ENCODE # 1 cycle - R_AUX2 = encoded - shr R_AUX2, LOOP_COUNT_SBITS # 1 cycle - R_AUX2 = loop_count (bits 32-63) + shr R_AUX2, LOOP_COUNT_RS # 1 cycle - R_AUX2 = loop_count (bits 32-63) mov R_AUX, R_ENCODE # 1 cycle - R_AUX = encoded - shr R_AUX, EXTRA_SRC_READS_SBITS # 1 cycle - shift extra_src_reads to position + shr R_AUX, EXTRA_SRC_READS_RS # 1 cycle - shift extra_src_reads to position and R_AUX, 0x03 # 1 cycle - R_AUX = extra_src_reads (bits 48-50) add R_AUX2, R_AUX # 1 cycle - R_AUX2 = total qwords to copy @@ -323,7 +323,7 @@ direct_dma_memcpy_mtrace: .L_copy_forward_loop: # Copy aligned qwords (main bulk of data) mov R_AUX2, R_ENCODE # 1 cycle - shr R_AUX2, LOOP_COUNT_SBITS # 1 cycle - R_AUX2 = loop_count (bits 32-63) + shr R_AUX2, LOOP_COUNT_RS # 1 cycle - R_AUX2 = loop_count (bits 32-63) rep movsq # ~1.5-2 cycles per qword (aligned, optimized) # R_SRC, R_DST advanced by loop_count * 8 @@ -335,7 +335,7 @@ direct_dma_memcpy_mtrace: # Extract and copy post_count bytes (1-7 bytes after aligned data) mov R_AUX2, R_ENCODE # 1 cycle - shr R_AUX2, POST_COUNT_SBITS # 1 cycle - shift post_count to position + shr R_AUX2, POST_COUNT_RS # 1 cycle - shift post_count to position and R_AUX2, 0x07 # 1 cycle - R_AUX2 = post_count (bits 43-45) rep movsb # ~3-5 cycles per byte diff --git a/emulator-asm/src/dma/dma_constants.inc b/emulator-asm/src/dma/dma_constants.inc index 058285e5f..a7da4a8e4 100644 --- a/emulator-asm/src/dma/dma_constants.inc +++ b/emulator-asm/src/dma/dma_constants.inc @@ -21,16 +21,18 @@ # bits offset mask # ---- ------ -------- # pre_count: 0-7 3 0 0x0000_0000_0000_0007 -# post_count: 0-7 3 3 0x0000_0000_0000_0038 -# pre_writes: 0,1,2 2 6 0x0000_0000_0000_00C0 -# dst_offset: 0-7 3 8 0x0000_0000_0000_0700 -# src_offset: 0-7 3 11 0x0000_0000_0000_3800 -# double_src_pre: 0,1 1 14 0x0000_0000_0000_4000 -# double_src_post: 0,1 1 15 0x0000_0000_0000_8000 -# extra_src_reads: 0-3 2 16 0x0000_0000_0003_0000 -# src64_inc_by_pre: 1 18 0x0000_0000_0004_0000 -# unaligned_dst_src: 1 19 0x0000_0000_0008_0000 -# pre_count: 0-7 3 29 only for optimization PRE_AND_LOOP_BYTES +# post_count: 0-7 4 3 0x0000_0000_0000_0078 (memcmp uses 8) +# pre_writes: 0,1,2 2 7 0x0000_0000_0000_0180 +# dst_offset: 0-7 3 9 0x0000_0000_0000_0E00 +# src_offset: 0-7 3 12 0x0000_0000_0000_7000 +# double_src_pre: 0,1 1 15 0x0000_0000_0000_8000 +# double_src_post: 0,1 1 16 0x0000_0000_0001_0000 +# extra_src_reads: 0-3 2 17 0x0000_0000_0006_0000 +# src64_inc_by_pre: 1 19 0x0000_0000_0008_0000 +# unaligned_dst_src: 1 20 0x0000_0000_0010_0000 +# fill_byte/cmp_res: 8 21 0x0000_0000_1FE0_0000 +# requires_dma: 1 29 0x0000_0000_2000_0000 +# pre_count: 0-7 3 32 only for optimization PRE_AND_LOOP_BYTES # loop_count 32 0 0xFFFF_FFFF_0000_0000 # BITS 31,32,32 @@ -38,30 +40,35 @@ .equ PRE_COUNT_MASK, 0x0000000000000007 .equ POST_COUNT_MASK, 0x0000000000000038 .equ PRE_WRITES_MASK, 0x00000000000000C0 -.equ DST_OFFSET_MASK, 0x0000000000000700 -.equ SRC_OFFSET_MASK, 0x0000000000003800 -.equ DOUBLE_SRC_PRE_MASK, 0x0000000000004000 -.equ DOUBLE_SRC_POST_MASK, 0x0000000000008000 -.equ EXTRA_SRC_READS_MASK, 0x0000000000030000 -.equ SRC64_INC_BY_PRE_MASK, 0x0000000000040000 -.equ UNALIGNED_DST_SRC_MASK, 0x0000000000080000 +.equ DST_OFFSET_MASK, 0x0000000000000E00 +.equ SRC_OFFSET_MASK, 0x0000000000007000 +.equ DOUBLE_SRC_PRE_MASK, 0x0000000000008000 +.equ DOUBLE_SRC_POST_MASK, 0x0000000000010000 +.equ EXTRA_SRC_READS_MASK, 0x0000000000060000 +.equ SRC64_INC_BY_PRE_MASK, 0x0000000000080000 +.equ UNALIGNED_DST_SRC_MASK, 0x0000000000100000 +.equ FILL_BYTE_CMP_RES_MASK, 0x000000001FE00000 +.equ REQUIRES_DMA_MASK, 0x0000000020000000 .equ LOOP_COUNT_MASK, 0xFFFFFFFF00000000 .equ ONLY_LOOP_COUNT_MASK, 0xFFFFFFFF00000000 +.equ FULL_ALIGN_MASK, 0x00000000001FFFFF +.equ DMA_DIRECT_MASK, 0x00000000201FFFFF -# PRE_COUNT_SBITS 0 -.equ POST_COUNT_SBITS, 3 -.equ PRE_WRITES_SBITS, 6 -.equ DST_OFFSET_SBITS, 8 -.equ SRC_OFFSET_SBITS, 11 -.equ DOUBLE_SRC_PRE_SBITS, 14 -.equ DOUBLE_SRC_POST_SBITS, 15 -.equ EXTRA_SRC_READS_SBITS, 16 -.equ SRC64_INC_BY_PRE_SBITS, 18 -.equ UNALIGNED_DST_SRC_SBITS, 19 -.equ LOOP_COUNT_SBITS, 32 -.equ PRE_AND_LOOP_BYTES_SBITS, 29 +.equ PRE_COUNT_RS, 0 +.equ POST_COUNT_RS, 3 +.equ PRE_WRITES_RS, 7 +.equ DST_OFFSET_RS, 9 +.equ SRC_OFFSET_RS, 12 +.equ DOUBLE_SRC_PRE_RS, 15 +.equ DOUBLE_SRC_POST_RS, 16 +.equ EXTRA_SRC_READS_RS, 17 +.equ SRC64_INC_BY_PRE_RS, 19 +.equ UNALIGNED_DST_SRC_RS, 20 +.equ FILL_BYTE_CMP_RES_RS, 21 +.equ LOOP_COUNT_RS, 35 +.equ PRE_AND_LOOP_BYTES_RS, 32 .equ ALIGN_MASK, 0xFFFFFFFFFFFFFFF8 -.equ MOPS_BLOCK_WORDS_SBITS, 36 +.equ MOPS_BLOCK_WORDS_RS, 36 .equ MOPS_BLOCK_ONE_WORD, 0x0000001000000000 .equ MOPS_BLOCK_READ, 0x0000000A00000000 .equ MOPS_BLOCK_WRITE, 0x0000000B00000000 diff --git a/emulator-asm/src/dma/fast_dma_encode.asm b/emulator-asm/src/dma/fast_dma_encode.asm index 533fba3d2..17a1678df 100644 --- a/emulator-asm/src/dma/fast_dma_encode.asm +++ b/emulator-asm/src/dma/fast_dma_encode.asm @@ -18,23 +18,37 @@ # # ENCODED METADATA (bits): # 0-2: pre_count - Bytes to copy before alignment (0-7) -# 3-5: post_count - Bytes to copy after aligned chunks (0-7) -# 6-7: pre_writes - Number of pre/post partial writes (0, 1, or 2) -# 8-10: dst_offset - Byte offset within dst qword (0-7) -# 11-13: src_offset - Byte offset within src qword (0-7) -# 14: double_src_pre - Flag: pre-read spans two src qwords -# 15: double_src_post - Flag: post-read spans two src qwords -# 16-17: extra_src_reads - Additional src qword reads needed (0-3) -# 18: src64_inc_by_pre - Flag: indicate loop use src64 + 8 -# 19: unaligned_dst_src - Flag: dst and src has diferent alignement -# 32-63: loop_count - Number of 8-byte chunks in main copy loop +# 3-6: post_count - Bytes to copy after aligned chunks (0-8) (* 8 memset case) +# 7-8: pre_writes - Number of pre/post partial writes (0, 1, or 2) +# 9-11: dst_offset - Byte offset within dst qword (0-7) +# 12-14: src_offset - Byte offset within src qword (0-7) +# 15: double_src_pre - Flag: pre-read spans two src qwords +# 16: double_src_post - Flag: post-read spans two src qwords +# 17-18: extra_src_reads - Additional src qword reads needed (0-3) +# 19: src64_inc_by_pre - Flag: indicate loop use src64 + 8 +# 20: unaligned_dst_src - Flag: dst and src has diferent alignement +# 21-28: fill_byte/cmp_res - Byte value for fill or compare result +# 29: requires_dma - Flag: indicates if operation requires DMA (*) +# 30-31: reserved +# 32-34: pre_count (loop) - Byte value for fill or compare result +# 35-63: loop_count - Number of 8-byte chunks in main copy loop +# +# (*) when compare exists an edge case when dst is aligned and effetive_count is multiple of 8, only +# PRE_POST machine could verify the different byte, in this case POST could be 8 bytes of length. +# effective_count is the number of bytes to check to compare. +# +# (*) The requires_dma flag is set by function caller when the operation it's a memcmp, because for +# this operation always a DMA is required. +# ################################################################################ -.global fast_dma_encode - +.global fast_dma_encode_memcpy +.global fast_dma_encode_memcmp +.global fast_dma_encode_memset +.global fast_dma_encode_inputcpy .section .text -fast_dma_encode: +fast_dma_encode_memcpy: mov rax, rdi and rax, 0x07 # dst_offset (0-7) shl rax, 7 # dst_offset << 7 @@ -48,13 +62,13 @@ fast_dma_encode: # Calculate table_count mov r8, rdx cmp r8, 16 - jb .L_count_lt_16 + jb .L_memcpy_count_lt_16 # count >= 16: table_count = (count & 0x07) | 0x08 and r8, 0x07 or r8, 0x08 -.L_count_lt_16: +.L_memcpy_count_lt_16: or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count # Look up encoded value in table (direct access since it's in the same file) @@ -67,6 +81,72 @@ fast_dma_encode: ret +// result ? + +fast_dma_encode_memcmp: + mov rax, rdi + and rax, 0x07 # dst_offset (0-7) + shl rax, 7 # dst_offset << 7 + + mov r8, rsi + and r8, 0x07 # src_offset (0-7) + shl r8, 4 # src_offset << 4 + + or rax, r8 # combine dst and src offsets + + # Calculate table_count + mov r8, rdx + cmp r8, 16 + jb .L_memcmp_count_lt_16 + + # count >= 16: table_count = (count & 0x07) | 0x08 + and r8, 0x07 + or r8, 0x08 + +.L_memcmp_count_lt_16: + or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count + + # Look up encoded value in table (direct access since it's in the same file) + mov rax, [fast_dma_encode_table + rax * 8] + + # Add (count >> 3) to result + mov r8, rdx + shl r8, 29 # r8 = count << 29 + add rax, r8 # result += (count << 29) + or rax, REQUIRES_DMA_MASK + + ret + +// byte ? + +fast_dma_encode_memset: + mov rax, rdi + and rax, 0x07 # dst_offset (0-7) + shl rax, 7 # dst_offset << 7 + + # Calculate table_count + mov r8, rdx + cmp r8, 16 + jb .L_memset_count_lt_16 + + # count >= 16: table_count = (count & 0x07) | 0x08 + and r8, 0x07 + or r8, 0x08 + +.L_memset_count_lt_16: + or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count + + # Look up encoded value in table (direct access since it's in the same file) + mov rax, [fast_dma_encode_table + rax * 8] + + # Add (count >> 3) to result + mov r8, rdx + shl r8, 29 # r8 = count << 29 + add rax, r8 # result += (count << 29) + + ret + + # Mark stack as non-executable (required by modern linkers) .section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/fast_dma_encode_table.asm b/emulator-asm/src/dma/fast_dma_encode_table.asm index 86f837ccc..2dad44adb 100644 --- a/emulator-asm/src/dma/fast_dma_encode_table.asm +++ b/emulator-asm/src/dma/fast_dma_encode_table.asm @@ -6,259 +6,515 @@ # asm_fast_encode_table test fast_dma_encode_table: - .quad 0x0000000000000000, 0xFFFFFFFFE0010048, 0xFFFFFFFFC0010050, 0xFFFFFFFFA0010058 # 0 - 3 D0 S0 C0 - .quad 0xffffffff80010060, 0xFFFFFFFF60010068, 0xFFFFFFFF40010070, 0xFFFFFFFF20010078 # 4 - 7 D0 S0 C4 - .quad 0x0000000000000000, 0xFFFFFFFFE0010048, 0xFFFFFFFFC0010050, 0xFFFFFFFFA0010058 # 8 - 11 D0 S0 C8 - .quad 0xffffffff80010060, 0xFFFFFFFF60010068, 0xFFFFFFFF40010070, 0xFFFFFFFF20010078 # 12 - 15 D0 S0 C12 - .quad 0x0000000000080800, 0xFFFFFFFFE0090848, 0xFFFFFFFFC0090850, 0xFFFFFFFFA0090858 # 16 - 19 D0 S1 C0 - .quad 0xffffffff80090860, 0xFFFFFFFF60090868, 0xFFFFFFFF40090870, 0xFFFFFFFF20090878 # 20 - 23 D0 S1 C4 - .quad 0x0000000000090800, 0xFFFFFFFFE0090848, 0xFFFFFFFFC0090850, 0xFFFFFFFFA0090858 # 24 - 27 D0 S1 C8 - .quad 0xffffffff80090860, 0xFFFFFFFF60090868, 0xFFFFFFFF40090870, 0xFFFFFFFF20090878 # 28 - 31 D0 S1 C12 - .quad 0x0000000000081000, 0xFFFFFFFFE0091048, 0xFFFFFFFFC0091050, 0xFFFFFFFFA0091058 # 32 - 35 D0 S2 C0 - .quad 0xffffffff80091060, 0xFFFFFFFF60091068, 0xFFFFFFFF40091070, 0xFFFFFFFF200A9078 # 36 - 39 D0 S2 C4 - .quad 0x0000000000091000, 0xFFFFFFFFE0091048, 0xFFFFFFFFC0091050, 0xFFFFFFFFA0091058 # 40 - 43 D0 S2 C8 - .quad 0xffffffff80091060, 0xFFFFFFFF60091068, 0xFFFFFFFF40091070, 0xFFFFFFFF200A9078 # 44 - 47 D0 S2 C12 - .quad 0x0000000000081800, 0xFFFFFFFFE0091848, 0xFFFFFFFFC0091850, 0xFFFFFFFFA0091858 # 48 - 51 D0 S3 C0 - .quad 0xffffffff80091860, 0xFFFFFFFF60091868, 0xFFFFFFFF400A9870, 0xFFFFFFFF200A9878 # 52 - 55 D0 S3 C4 - .quad 0x0000000000091800, 0xFFFFFFFFE0091848, 0xFFFFFFFFC0091850, 0xFFFFFFFFA0091858 # 56 - 59 D0 S3 C8 - .quad 0xffffffff80091860, 0xFFFFFFFF60091868, 0xFFFFFFFF400A9870, 0xFFFFFFFF200A9878 # 60 - 63 D0 S3 C12 - .quad 0x0000000000082000, 0xFFFFFFFFE0092048, 0xFFFFFFFFC0092050, 0xFFFFFFFFA0092058 # 64 - 67 D0 S4 C0 - .quad 0xffffffff80092060, 0xFFFFFFFF600AA068, 0xFFFFFFFF400AA070, 0xFFFFFFFF200AA078 # 68 - 71 D0 S4 C4 - .quad 0x0000000000092000, 0xFFFFFFFFE0092048, 0xFFFFFFFFC0092050, 0xFFFFFFFFA0092058 # 72 - 75 D0 S4 C8 - .quad 0xffffffff80092060, 0xFFFFFFFF600AA068, 0xFFFFFFFF400AA070, 0xFFFFFFFF200AA078 # 76 - 79 D0 S4 C12 - .quad 0x0000000000082800, 0xFFFFFFFFE0092848, 0xFFFFFFFFC0092850, 0xFFFFFFFFA0092858 # 80 - 83 D0 S5 C0 - .quad 0xffffffff800aa860, 0xFFFFFFFF600AA868, 0xFFFFFFFF400AA870, 0xFFFFFFFF200AA878 # 84 - 87 D0 S5 C4 - .quad 0x0000000000092800, 0xFFFFFFFFE0092848, 0xFFFFFFFFC0092850, 0xFFFFFFFFA0092858 # 88 - 91 D0 S5 C8 - .quad 0xffffffff800aa860, 0xFFFFFFFF600AA868, 0xFFFFFFFF400AA870, 0xFFFFFFFF200AA878 # 92 - 95 D0 S5 C12 - .quad 0x0000000000083000, 0xFFFFFFFFE0093048, 0xFFFFFFFFC0093050, 0xFFFFFFFFA00AB058 # 96 - 99 D0 S6 C0 - .quad 0xffffffff800ab060, 0xFFFFFFFF600AB068, 0xFFFFFFFF400AB070, 0xFFFFFFFF200AB078 # 100 - 103 D0 S6 C4 - .quad 0x0000000000093000, 0xFFFFFFFFE0093048, 0xFFFFFFFFC0093050, 0xFFFFFFFFA00AB058 # 104 - 107 D0 S6 C8 - .quad 0xffffffff800ab060, 0xFFFFFFFF600AB068, 0xFFFFFFFF400AB070, 0xFFFFFFFF200AB078 # 108 - 111 D0 S6 C12 - .quad 0x0000000000083800, 0xFFFFFFFFE0093848, 0xFFFFFFFFC00AB850, 0xFFFFFFFFA00AB858 # 112 - 115 D0 S7 C0 - .quad 0xffffffff800ab860, 0xFFFFFFFF600AB868, 0xFFFFFFFF400AB870, 0xFFFFFFFF200AB878 # 116 - 119 D0 S7 C4 - .quad 0x0000000000093800, 0xFFFFFFFFE0093848, 0xFFFFFFFFC00AB850, 0xFFFFFFFFA00AB858 # 120 - 123 D0 S7 C8 - .quad 0xffffffff800ab860, 0xFFFFFFFF600AB868, 0xFFFFFFFF400AB870, 0xFFFFFFFF200AB878 # 124 - 127 D0 S7 C12 - .quad 0x0000000000080100, 0x0000000000090141, 0x0000000000090142, 0x0000000000090143 # 128 - 131 D1 S0 C0 - .quad 0x0000000000090144, 0x0000000000090145, 0x0000000000090146, 0x0000000000090147 # 132 - 135 D1 S0 C4 - .quad 0xffffffffe009018f, 0xFFFFFFFFC00A8197, 0xFFFFFFFFA00A819F, 0xFFFFFFFF800A81A7 # 136 - 139 D1 S0 C8 - .quad 0xffffffff600a81af, 0xFFFFFFFF400A81B7, 0xFFFFFFFF200A81BF, 0x0000000000090147 # 140 - 143 D1 S0 C12 - .quad 0x0000000000000900, 0x0000000000010941, 0x0000000000010942, 0x0000000000010943 # 144 - 147 D1 S1 C0 - .quad 0x0000000000010944, 0x0000000000010945, 0x0000000000010946, 0x0000000000050947 # 148 - 151 D1 S1 C4 - .quad 0xffffffffe006098f, 0xFFFFFFFFC0060997, 0xFFFFFFFFA006099F, 0xFFFFFFFF800609A7 # 152 - 155 D1 S1 C8 - .quad 0xffffffff600609af, 0xFFFFFFFF400609B7, 0xFFFFFFFF200609BF, 0x0000000000050947 # 156 - 159 D1 S1 C12 - .quad 0x0000000000081100, 0x0000000000091141, 0x0000000000091142, 0x0000000000091143 # 160 - 163 D1 S2 C0 - .quad 0x0000000000091144, 0x0000000000091145, 0x00000000000D1146, 0x00000000000E5147 # 164 - 167 D1 S2 C4 - .quad 0xffffffffe00e518f, 0xFFFFFFFFC00E5197, 0xFFFFFFFFA00E519F, 0xFFFFFFFF800E51A7 # 168 - 171 D1 S2 C8 - .quad 0xffffffff600e51af, 0xFFFFFFFF400E51B7, 0xFFFFFFFF200E51BF, 0x00000000000E5147 # 172 - 175 D1 S2 C12 - .quad 0x0000000000081900, 0x0000000000091941, 0x0000000000091942, 0x0000000000091943 # 176 - 179 D1 S3 C0 - .quad 0x0000000000091944, 0x00000000000D1945, 0x00000000000E5946, 0x00000000000E5947 # 180 - 183 D1 S3 C4 - .quad 0xffffffffe00e598f, 0xFFFFFFFFC00E5997, 0xFFFFFFFFA00E599F, 0xFFFFFFFF800E59A7 # 184 - 187 D1 S3 C8 - .quad 0xffffffff600e59af, 0xFFFFFFFF400E59B7, 0xFFFFFFFF200FD9BF, 0x00000000000E5947 # 188 - 191 D1 S3 C12 - .quad 0x0000000000082100, 0x0000000000092141, 0x0000000000092142, 0x0000000000092143 # 192 - 195 D1 S4 C0 - .quad 0x00000000000d2144, 0x00000000000E6145, 0x00000000000E6146, 0x00000000000E6147 # 196 - 199 D1 S4 C4 - .quad 0xffffffffe00e618f, 0xFFFFFFFFC00E6197, 0xFFFFFFFFA00E619F, 0xFFFFFFFF800E61A7 # 200 - 203 D1 S4 C8 - .quad 0xffffffff600e61af, 0xFFFFFFFF400FE1B7, 0xFFFFFFFF200FE1BF, 0x00000000000E6147 # 204 - 207 D1 S4 C12 - .quad 0x0000000000082900, 0x0000000000092941, 0x0000000000092942, 0x00000000000D2943 # 208 - 211 D1 S5 C0 - .quad 0x00000000000e6944, 0x00000000000E6945, 0x00000000000E6946, 0x00000000000E6947 # 212 - 215 D1 S5 C4 - .quad 0xffffffffe00e698f, 0xFFFFFFFFC00E6997, 0xFFFFFFFFA00E699F, 0xFFFFFFFF800E69A7 # 216 - 219 D1 S5 C8 - .quad 0xffffffff600fe9af, 0xFFFFFFFF400FE9B7, 0xFFFFFFFF200FE9BF, 0x00000000000E6947 # 220 - 223 D1 S5 C12 - .quad 0x0000000000083100, 0x0000000000093141, 0x00000000000D3142, 0x00000000000E7143 # 224 - 227 D1 S6 C0 - .quad 0x00000000000e7144, 0x00000000000E7145, 0x00000000000E7146, 0x00000000000E7147 # 228 - 231 D1 S6 C4 - .quad 0xffffffffe00e718f, 0xFFFFFFFFC00E7197, 0xFFFFFFFFA00E719F, 0xFFFFFFFF800FF1A7 # 232 - 235 D1 S6 C8 - .quad 0xffffffff600ff1af, 0xFFFFFFFF400FF1B7, 0xFFFFFFFF200FF1BF, 0x00000000000E7147 # 236 - 239 D1 S6 C12 - .quad 0x0000000000083900, 0x00000000000D3941, 0x00000000000E7942, 0x00000000000E7943 # 240 - 243 D1 S7 C0 - .quad 0x00000000000e7944, 0x00000000000E7945, 0x00000000000E7946, 0x00000000000E7947 # 244 - 247 D1 S7 C4 - .quad 0xffffffffe00e798f, 0xFFFFFFFFC00E7997, 0xFFFFFFFFA00FF99F, 0xFFFFFFFF800FF9A7 # 248 - 251 D1 S7 C8 - .quad 0xffffffff600ff9af, 0xFFFFFFFF400FF9B7, 0xFFFFFFFF200FF9BF, 0x00000000000E7947 # 252 - 255 D1 S7 C12 - .quad 0x0000000000080200, 0x0000000000090241, 0x0000000000090242, 0x0000000000090243 # 256 - 259 D2 S0 C0 - .quad 0x0000000000090244, 0x0000000000090245, 0x0000000000090246, 0xFFFFFFFFE009028E # 260 - 263 D2 S0 C4 - .quad 0xffffffffc0090296, 0xFFFFFFFFA00A829E, 0xFFFFFFFF800A82A6, 0xFFFFFFFF600A82AE # 264 - 267 D2 S0 C8 - .quad 0xffffffff400a82b6, 0xFFFFFFFF200A82BE, 0x0000000000090246, 0xFFFFFFFFE009028E # 268 - 271 D2 S0 C12 - .quad 0x0000000000080a00, 0x0000000000090A41, 0x0000000000090A42, 0x0000000000090A43 # 272 - 275 D2 S1 C0 - .quad 0x0000000000090a44, 0x0000000000090A45, 0x0000000000090A46, 0xFFFFFFFFE0090A8E # 276 - 279 D2 S1 C4 - .quad 0xffffffffc00a8a96, 0xFFFFFFFFA00A8A9E, 0xFFFFFFFF800A8AA6, 0xFFFFFFFF600A8AAE # 280 - 283 D2 S1 C8 - .quad 0xffffffff400a8ab6, 0xFFFFFFFF200A8ABE, 0x0000000000090A46, 0xFFFFFFFFE0090A8E # 284 - 287 D2 S1 C12 - .quad 0x0000000000001200, 0x0000000000011241, 0x0000000000011242, 0x0000000000011243 # 288 - 291 D2 S2 C0 - .quad 0x0000000000011244, 0x0000000000011245, 0x0000000000051246, 0xFFFFFFFFE006128E # 292 - 295 D2 S2 C4 - .quad 0xffffffffc0061296, 0xFFFFFFFFA006129E, 0xFFFFFFFF800612A6, 0xFFFFFFFF600612AE # 296 - 299 D2 S2 C8 - .quad 0xffffffff400612b6, 0xFFFFFFFF200612BE, 0x0000000000051246, 0xFFFFFFFFE006128E # 300 - 303 D2 S2 C12 - .quad 0x0000000000081a00, 0x0000000000091A41, 0x0000000000091A42, 0x0000000000091A43 # 304 - 307 D2 S3 C0 - .quad 0x0000000000091a44, 0x00000000000D1A45, 0x00000000000E5A46, 0xFFFFFFFFE00E5A8E # 308 - 311 D2 S3 C4 - .quad 0xffffffffc00e5a96, 0xFFFFFFFFA00E5A9E, 0xFFFFFFFF800E5AA6, 0xFFFFFFFF600E5AAE # 312 - 315 D2 S3 C8 - .quad 0xffffffff400e5ab6, 0xFFFFFFFF200E5ABE, 0x00000000000E5A46, 0xFFFFFFFFE00E5A8E # 316 - 319 D2 S3 C12 - .quad 0x0000000000082200, 0x0000000000092241, 0x0000000000092242, 0x0000000000092243 # 320 - 323 D2 S4 C0 - .quad 0x00000000000d2244, 0x00000000000E6245, 0x00000000000E6246, 0xFFFFFFFFE00E628E # 324 - 327 D2 S4 C4 - .quad 0xffffffffc00e6296, 0xFFFFFFFFA00E629E, 0xFFFFFFFF800E62A6, 0xFFFFFFFF600E62AE # 328 - 331 D2 S4 C8 - .quad 0xffffffff400e62b6, 0xFFFFFFFF200FE2BE, 0x00000000000E6246, 0xFFFFFFFFE00E628E # 332 - 335 D2 S4 C12 - .quad 0x0000000000082a00, 0x0000000000092A41, 0x0000000000092A42, 0x00000000000D2A43 # 336 - 339 D2 S5 C0 - .quad 0x00000000000e6a44, 0x00000000000E6A45, 0x00000000000E6A46, 0xFFFFFFFFE00E6A8E # 340 - 343 D2 S5 C4 - .quad 0xffffffffc00e6a96, 0xFFFFFFFFA00E6A9E, 0xFFFFFFFF800E6AA6, 0xFFFFFFFF600E6AAE # 344 - 347 D2 S5 C8 - .quad 0xffffffff400feab6, 0xFFFFFFFF200FEABE, 0x00000000000E6A46, 0xFFFFFFFFE00E6A8E # 348 - 351 D2 S5 C12 - .quad 0x0000000000083200, 0x0000000000093241, 0x00000000000D3242, 0x00000000000E7243 # 352 - 355 D2 S6 C0 - .quad 0x00000000000e7244, 0x00000000000E7245, 0x00000000000E7246, 0xFFFFFFFFE00E728E # 356 - 359 D2 S6 C4 - .quad 0xffffffffc00e7296, 0xFFFFFFFFA00E729E, 0xFFFFFFFF800E72A6, 0xFFFFFFFF600FF2AE # 360 - 363 D2 S6 C8 - .quad 0xffffffff400ff2b6, 0xFFFFFFFF200FF2BE, 0x00000000000E7246, 0xFFFFFFFFE00E728E # 364 - 367 D2 S6 C12 - .quad 0x0000000000083a00, 0x00000000000D3A41, 0x00000000000E7A42, 0x00000000000E7A43 # 368 - 371 D2 S7 C0 - .quad 0x00000000000e7a44, 0x00000000000E7A45, 0x00000000000E7A46, 0xFFFFFFFFE00E7A8E # 372 - 375 D2 S7 C4 - .quad 0xffffffffc00e7a96, 0xFFFFFFFFA00E7A9E, 0xFFFFFFFF800FFAA6, 0xFFFFFFFF600FFAAE # 376 - 379 D2 S7 C8 - .quad 0xffffffff400ffab6, 0xFFFFFFFF200FFABE, 0x00000000000E7A46, 0xFFFFFFFFE00E7A8E # 380 - 383 D2 S7 C12 - .quad 0x0000000000080300, 0x0000000000090341, 0x0000000000090342, 0x0000000000090343 # 384 - 387 D3 S0 C0 - .quad 0x0000000000090344, 0x0000000000090345, 0xFFFFFFFFE009038D, 0xFFFFFFFFC0090395 # 388 - 391 D3 S0 C4 - .quad 0xffffffffa009039d, 0xFFFFFFFF800A83A5, 0xFFFFFFFF600A83AD, 0xFFFFFFFF400A83B5 # 392 - 395 D3 S0 C8 - .quad 0xffffffff200a83bd, 0x0000000000090345, 0xFFFFFFFFE009038D, 0xFFFFFFFFC0090395 # 396 - 399 D3 S0 C12 - .quad 0x0000000000080b00, 0x0000000000090B41, 0x0000000000090B42, 0x0000000000090B43 # 400 - 403 D3 S1 C0 - .quad 0x0000000000090b44, 0x0000000000090B45, 0xFFFFFFFFE0090B8D, 0xFFFFFFFFC0090B95 # 404 - 407 D3 S1 C4 - .quad 0xffffffffa00a8b9d, 0xFFFFFFFF800A8BA5, 0xFFFFFFFF600A8BAD, 0xFFFFFFFF400A8BB5 # 408 - 411 D3 S1 C8 - .quad 0xffffffff200a8bbd, 0x0000000000090B45, 0xFFFFFFFFE0090B8D, 0xFFFFFFFFC0090B95 # 412 - 415 D3 S1 C12 - .quad 0x0000000000081300, 0x0000000000091341, 0x0000000000091342, 0x0000000000091343 # 416 - 419 D3 S2 C0 - .quad 0x0000000000091344, 0x0000000000091345, 0xFFFFFFFFE009138D, 0xFFFFFFFFC00A9395 # 420 - 423 D3 S2 C4 - .quad 0xffffffffa00a939d, 0xFFFFFFFF800A93A5, 0xFFFFFFFF600A93AD, 0xFFFFFFFF400A93B5 # 424 - 427 D3 S2 C8 - .quad 0xffffffff200a93bd, 0x0000000000091345, 0xFFFFFFFFE009138D, 0xFFFFFFFFC00A9395 # 428 - 431 D3 S2 C12 - .quad 0x0000000000001b00, 0x0000000000011B41, 0x0000000000011B42, 0x0000000000011B43 # 432 - 435 D3 S3 C0 - .quad 0x0000000000011b44, 0x0000000000051B45, 0xFFFFFFFFE0061B8D, 0xFFFFFFFFC0061B95 # 436 - 439 D3 S3 C4 - .quad 0xffffffffa0061b9d, 0xFFFFFFFF80061BA5, 0xFFFFFFFF60061BAD, 0xFFFFFFFF40061BB5 # 440 - 443 D3 S3 C8 - .quad 0xffffffff20061bbd, 0x0000000000051B45, 0xFFFFFFFFE0061B8D, 0xFFFFFFFFC0061B95 # 444 - 447 D3 S3 C12 - .quad 0x0000000000082300, 0x0000000000092341, 0x0000000000092342, 0x0000000000092343 # 448 - 451 D3 S4 C0 - .quad 0x00000000000d2344, 0x00000000000E6345, 0xFFFFFFFFE00E638D, 0xFFFFFFFFC00E6395 # 452 - 455 D3 S4 C4 - .quad 0xffffffffa00e639d, 0xFFFFFFFF800E63A5, 0xFFFFFFFF600E63AD, 0xFFFFFFFF400E63B5 # 456 - 459 D3 S4 C8 - .quad 0xffffffff200e63bd, 0x00000000000E6345, 0xFFFFFFFFE00E638D, 0xFFFFFFFFC00E6395 # 460 - 463 D3 S4 C12 - .quad 0x0000000000082b00, 0x0000000000092B41, 0x0000000000092B42, 0x00000000000D2B43 # 464 - 467 D3 S5 C0 - .quad 0x00000000000e6b44, 0x00000000000E6B45, 0xFFFFFFFFE00E6B8D, 0xFFFFFFFFC00E6B95 # 468 - 471 D3 S5 C4 - .quad 0xffffffffa00e6b9d, 0xFFFFFFFF800E6BA5, 0xFFFFFFFF600E6BAD, 0xFFFFFFFF400E6BB5 # 472 - 475 D3 S5 C8 - .quad 0xffffffff200febbd, 0x00000000000E6B45, 0xFFFFFFFFE00E6B8D, 0xFFFFFFFFC00E6B95 # 476 - 479 D3 S5 C12 - .quad 0x0000000000083300, 0x0000000000093341, 0x00000000000D3342, 0x00000000000E7343 # 480 - 483 D3 S6 C0 - .quad 0x00000000000e7344, 0x00000000000E7345, 0xFFFFFFFFE00E738D, 0xFFFFFFFFC00E7395 # 484 - 487 D3 S6 C4 - .quad 0xffffffffa00e739d, 0xFFFFFFFF800E73A5, 0xFFFFFFFF600E73AD, 0xFFFFFFFF400FF3B5 # 488 - 491 D3 S6 C8 - .quad 0xffffffff200ff3bd, 0x00000000000E7345, 0xFFFFFFFFE00E738D, 0xFFFFFFFFC00E7395 # 492 - 495 D3 S6 C12 - .quad 0x0000000000083b00, 0x00000000000D3B41, 0x00000000000E7B42, 0x00000000000E7B43 # 496 - 499 D3 S7 C0 - .quad 0x00000000000e7b44, 0x00000000000E7B45, 0xFFFFFFFFE00E7B8D, 0xFFFFFFFFC00E7B95 # 500 - 503 D3 S7 C4 - .quad 0xffffffffa00e7b9d, 0xFFFFFFFF800E7BA5, 0xFFFFFFFF600FFBAD, 0xFFFFFFFF400FFBB5 # 504 - 507 D3 S7 C8 - .quad 0xffffffff200ffbbd, 0x00000000000E7B45, 0xFFFFFFFFE00E7B8D, 0xFFFFFFFFC00E7B95 # 508 - 511 D3 S7 C12 - .quad 0x0000000000080400, 0x0000000000090441, 0x0000000000090442, 0x0000000000090443 # 512 - 515 D4 S0 C0 - .quad 0x0000000000090444, 0xFFFFFFFFE009048C, 0xFFFFFFFFC0090494, 0xFFFFFFFFA009049C # 516 - 519 D4 S0 C4 - .quad 0xffffffff800904a4, 0xFFFFFFFF600A84AC, 0xFFFFFFFF400A84B4, 0xFFFFFFFF200A84BC # 520 - 523 D4 S0 C8 - .quad 0x0000000000090444, 0xFFFFFFFFE009048C, 0xFFFFFFFFC0090494, 0xFFFFFFFFA009049C # 524 - 527 D4 S0 C12 - .quad 0x0000000000080c00, 0x0000000000090C41, 0x0000000000090C42, 0x0000000000090C43 # 528 - 531 D4 S1 C0 - .quad 0x0000000000090c44, 0xFFFFFFFFE0090C8C, 0xFFFFFFFFC0090C94, 0xFFFFFFFFA0090C9C # 532 - 535 D4 S1 C4 - .quad 0xffffffff800a8ca4, 0xFFFFFFFF600A8CAC, 0xFFFFFFFF400A8CB4, 0xFFFFFFFF200A8CBC # 536 - 539 D4 S1 C8 - .quad 0x0000000000090c44, 0xFFFFFFFFE0090C8C, 0xFFFFFFFFC0090C94, 0xFFFFFFFFA0090C9C # 540 - 543 D4 S1 C12 - .quad 0x0000000000081400, 0x0000000000091441, 0x0000000000091442, 0x0000000000091443 # 544 - 547 D4 S2 C0 - .quad 0x0000000000091444, 0xFFFFFFFFE009148C, 0xFFFFFFFFC0091494, 0xFFFFFFFFA00A949C # 548 - 551 D4 S2 C4 - .quad 0xffffffff800a94a4, 0xFFFFFFFF600A94AC, 0xFFFFFFFF400A94B4, 0xFFFFFFFF200A94BC # 552 - 555 D4 S2 C8 - .quad 0x0000000000091444, 0xFFFFFFFFE009148C, 0xFFFFFFFFC0091494, 0xFFFFFFFFA00A949C # 556 - 559 D4 S2 C12 - .quad 0x0000000000081c00, 0x0000000000091C41, 0x0000000000091C42, 0x0000000000091C43 # 560 - 563 D4 S3 C0 - .quad 0x0000000000091c44, 0xFFFFFFFFE0091C8C, 0xFFFFFFFFC00A9C94, 0xFFFFFFFFA00A9C9C # 564 - 567 D4 S3 C4 - .quad 0xffffffff800a9ca4, 0xFFFFFFFF600A9CAC, 0xFFFFFFFF400A9CB4, 0xFFFFFFFF200A9CBC # 568 - 571 D4 S3 C8 - .quad 0x0000000000091c44, 0xFFFFFFFFE0091C8C, 0xFFFFFFFFC00A9C94, 0xFFFFFFFFA00A9C9C # 572 - 575 D4 S3 C12 - .quad 0x0000000000002400, 0x0000000000012441, 0x0000000000012442, 0x0000000000012443 # 576 - 579 D4 S4 C0 - .quad 0x0000000000052444, 0xFFFFFFFFE006248C, 0xFFFFFFFFC0062494, 0xFFFFFFFFA006249C # 580 - 583 D4 S4 C4 - .quad 0xffffffff800624a4, 0xFFFFFFFF600624AC, 0xFFFFFFFF400624B4, 0xFFFFFFFF200624BC # 584 - 587 D4 S4 C8 - .quad 0x0000000000052444, 0xFFFFFFFFE006248C, 0xFFFFFFFFC0062494, 0xFFFFFFFFA006249C # 588 - 591 D4 S4 C12 - .quad 0x0000000000082c00, 0x0000000000092C41, 0x0000000000092C42, 0x00000000000D2C43 # 592 - 595 D4 S5 C0 - .quad 0x00000000000e6c44, 0xFFFFFFFFE00E6C8C, 0xFFFFFFFFC00E6C94, 0xFFFFFFFFA00E6C9C # 596 - 599 D4 S5 C4 - .quad 0xffffffff800e6ca4, 0xFFFFFFFF600E6CAC, 0xFFFFFFFF400E6CB4, 0xFFFFFFFF200E6CBC # 600 - 603 D4 S5 C8 - .quad 0x00000000000e6c44, 0xFFFFFFFFE00E6C8C, 0xFFFFFFFFC00E6C94, 0xFFFFFFFFA00E6C9C # 604 - 607 D4 S5 C12 - .quad 0x0000000000083400, 0x0000000000093441, 0x00000000000D3442, 0x00000000000E7443 # 608 - 611 D4 S6 C0 - .quad 0x00000000000e7444, 0xFFFFFFFFE00E748C, 0xFFFFFFFFC00E7494, 0xFFFFFFFFA00E749C # 612 - 615 D4 S6 C4 - .quad 0xffffffff800e74a4, 0xFFFFFFFF600E74AC, 0xFFFFFFFF400E74B4, 0xFFFFFFFF200FF4BC # 616 - 619 D4 S6 C8 - .quad 0x00000000000e7444, 0xFFFFFFFFE00E748C, 0xFFFFFFFFC00E7494, 0xFFFFFFFFA00E749C # 620 - 623 D4 S6 C12 - .quad 0x0000000000083c00, 0x00000000000D3C41, 0x00000000000E7C42, 0x00000000000E7C43 # 624 - 627 D4 S7 C0 - .quad 0x00000000000e7c44, 0xFFFFFFFFE00E7C8C, 0xFFFFFFFFC00E7C94, 0xFFFFFFFFA00E7C9C # 628 - 631 D4 S7 C4 - .quad 0xffffffff800e7ca4, 0xFFFFFFFF600E7CAC, 0xFFFFFFFF400FFCB4, 0xFFFFFFFF200FFCBC # 632 - 635 D4 S7 C8 - .quad 0x00000000000e7c44, 0xFFFFFFFFE00E7C8C, 0xFFFFFFFFC00E7C94, 0xFFFFFFFFA00E7C9C # 636 - 639 D4 S7 C12 - .quad 0x0000000000080500, 0x0000000000090541, 0x0000000000090542, 0x0000000000090543 # 640 - 643 D5 S0 C0 - .quad 0xffffffffe009058b, 0xFFFFFFFFC0090593, 0xFFFFFFFFA009059B, 0xFFFFFFFF800905A3 # 644 - 647 D5 S0 C4 - .quad 0xffffffff600905ab, 0xFFFFFFFF400A85B3, 0xFFFFFFFF200A85BB, 0x0000000000090543 # 648 - 651 D5 S0 C8 - .quad 0xffffffffe009058b, 0xFFFFFFFFC0090593, 0xFFFFFFFFA009059B, 0xFFFFFFFF800905A3 # 652 - 655 D5 S0 C12 - .quad 0x0000000000080d00, 0x0000000000090D41, 0x0000000000090D42, 0x0000000000090D43 # 656 - 659 D5 S1 C0 - .quad 0xffffffffe0090d8b, 0xFFFFFFFFC0090D93, 0xFFFFFFFFA0090D9B, 0xFFFFFFFF80090DA3 # 660 - 663 D5 S1 C4 - .quad 0xffffffff600a8dab, 0xFFFFFFFF400A8DB3, 0xFFFFFFFF200A8DBB, 0x0000000000090D43 # 664 - 667 D5 S1 C8 - .quad 0xffffffffe0090d8b, 0xFFFFFFFFC0090D93, 0xFFFFFFFFA0090D9B, 0xFFFFFFFF80090DA3 # 668 - 671 D5 S1 C12 - .quad 0x0000000000081500, 0x0000000000091541, 0x0000000000091542, 0x0000000000091543 # 672 - 675 D5 S2 C0 - .quad 0xffffffffe009158b, 0xFFFFFFFFC0091593, 0xFFFFFFFFA009159B, 0xFFFFFFFF800A95A3 # 676 - 679 D5 S2 C4 - .quad 0xffffffff600a95ab, 0xFFFFFFFF400A95B3, 0xFFFFFFFF200A95BB, 0x0000000000091543 # 680 - 683 D5 S2 C8 - .quad 0xffffffffe009158b, 0xFFFFFFFFC0091593, 0xFFFFFFFFA009159B, 0xFFFFFFFF800A95A3 # 684 - 687 D5 S2 C12 - .quad 0x0000000000081d00, 0x0000000000091D41, 0x0000000000091D42, 0x0000000000091D43 # 688 - 691 D5 S3 C0 - .quad 0xffffffffe0091d8b, 0xFFFFFFFFC0091D93, 0xFFFFFFFFA00A9D9B, 0xFFFFFFFF800A9DA3 # 692 - 695 D5 S3 C4 - .quad 0xffffffff600a9dab, 0xFFFFFFFF400A9DB3, 0xFFFFFFFF200A9DBB, 0x0000000000091D43 # 696 - 699 D5 S3 C8 - .quad 0xffffffffe0091d8b, 0xFFFFFFFFC0091D93, 0xFFFFFFFFA00A9D9B, 0xFFFFFFFF800A9DA3 # 700 - 703 D5 S3 C12 - .quad 0x0000000000082500, 0x0000000000092541, 0x0000000000092542, 0x0000000000092543 # 704 - 707 D5 S4 C0 - .quad 0xffffffffe009258b, 0xFFFFFFFFC00AA593, 0xFFFFFFFFA00AA59B, 0xFFFFFFFF800AA5A3 # 708 - 711 D5 S4 C4 - .quad 0xffffffff600aa5ab, 0xFFFFFFFF400AA5B3, 0xFFFFFFFF200AA5BB, 0x0000000000092543 # 712 - 715 D5 S4 C8 - .quad 0xffffffffe009258b, 0xFFFFFFFFC00AA593, 0xFFFFFFFFA00AA59B, 0xFFFFFFFF800AA5A3 # 716 - 719 D5 S4 C12 - .quad 0x0000000000002d00, 0x0000000000012D41, 0x0000000000012D42, 0x0000000000052D43 # 720 - 723 D5 S5 C0 - .quad 0xffffffffe0062d8b, 0xFFFFFFFFC0062D93, 0xFFFFFFFFA0062D9B, 0xFFFFFFFF80062DA3 # 724 - 727 D5 S5 C4 - .quad 0xffffffff60062dab, 0xFFFFFFFF40062DB3, 0xFFFFFFFF20062DBB, 0x0000000000052D43 # 728 - 731 D5 S5 C8 - .quad 0xffffffffe0062d8b, 0xFFFFFFFFC0062D93, 0xFFFFFFFFA0062D9B, 0xFFFFFFFF80062DA3 # 732 - 735 D5 S5 C12 - .quad 0x0000000000083500, 0x0000000000093541, 0x00000000000D3542, 0x00000000000E7543 # 736 - 739 D5 S6 C0 - .quad 0xffffffffe00e758b, 0xFFFFFFFFC00E7593, 0xFFFFFFFFA00E759B, 0xFFFFFFFF800E75A3 # 740 - 743 D5 S6 C4 - .quad 0xffffffff600e75ab, 0xFFFFFFFF400E75B3, 0xFFFFFFFF200E75BB, 0x00000000000E7543 # 744 - 747 D5 S6 C8 - .quad 0xffffffffe00e758b, 0xFFFFFFFFC00E7593, 0xFFFFFFFFA00E759B, 0xFFFFFFFF800E75A3 # 748 - 751 D5 S6 C12 - .quad 0x0000000000083d00, 0x00000000000D3D41, 0x00000000000E7D42, 0x00000000000E7D43 # 752 - 755 D5 S7 C0 - .quad 0xffffffffe00e7d8b, 0xFFFFFFFFC00E7D93, 0xFFFFFFFFA00E7D9B, 0xFFFFFFFF800E7DA3 # 756 - 759 D5 S7 C4 - .quad 0xffffffff600e7dab, 0xFFFFFFFF400E7DB3, 0xFFFFFFFF200FFDBB, 0x00000000000E7D43 # 760 - 763 D5 S7 C8 - .quad 0xffffffffe00e7d8b, 0xFFFFFFFFC00E7D93, 0xFFFFFFFFA00E7D9B, 0xFFFFFFFF800E7DA3 # 764 - 767 D5 S7 C12 - .quad 0x0000000000080600, 0x0000000000090641, 0x0000000000090642, 0xFFFFFFFFE009068A # 768 - 771 D6 S0 C0 - .quad 0xffffffffc0090692, 0xFFFFFFFFA009069A, 0xFFFFFFFF800906A2, 0xFFFFFFFF600906AA # 772 - 775 D6 S0 C4 - .quad 0xffffffff400906b2, 0xFFFFFFFF200A86BA, 0x0000000000090642, 0xFFFFFFFFE009068A # 776 - 779 D6 S0 C8 - .quad 0xffffffffc0090692, 0xFFFFFFFFA009069A, 0xFFFFFFFF800906A2, 0xFFFFFFFF600906AA # 780 - 783 D6 S0 C12 - .quad 0x0000000000080e00, 0x0000000000090E41, 0x0000000000090E42, 0xFFFFFFFFE0090E8A # 784 - 787 D6 S1 C0 - .quad 0xffffffffc0090e92, 0xFFFFFFFFA0090E9A, 0xFFFFFFFF80090EA2, 0xFFFFFFFF60090EAA # 788 - 791 D6 S1 C4 - .quad 0xffffffff400a8eb2, 0xFFFFFFFF200A8EBA, 0x0000000000090E42, 0xFFFFFFFFE0090E8A # 792 - 795 D6 S1 C8 - .quad 0xffffffffc0090e92, 0xFFFFFFFFA0090E9A, 0xFFFFFFFF80090EA2, 0xFFFFFFFF60090EAA # 796 - 799 D6 S1 C12 - .quad 0x0000000000081600, 0x0000000000091641, 0x0000000000091642, 0xFFFFFFFFE009168A # 800 - 803 D6 S2 C0 - .quad 0xffffffffc0091692, 0xFFFFFFFFA009169A, 0xFFFFFFFF800916A2, 0xFFFFFFFF600A96AA # 804 - 807 D6 S2 C4 - .quad 0xffffffff400a96b2, 0xFFFFFFFF200A96BA, 0x0000000000091642, 0xFFFFFFFFE009168A # 808 - 811 D6 S2 C8 - .quad 0xffffffffc0091692, 0xFFFFFFFFA009169A, 0xFFFFFFFF800916A2, 0xFFFFFFFF600A96AA # 812 - 815 D6 S2 C12 - .quad 0x0000000000081e00, 0x0000000000091E41, 0x0000000000091E42, 0xFFFFFFFFE0091E8A # 816 - 819 D6 S3 C0 - .quad 0xffffffffc0091e92, 0xFFFFFFFFA0091E9A, 0xFFFFFFFF800A9EA2, 0xFFFFFFFF600A9EAA # 820 - 823 D6 S3 C4 - .quad 0xffffffff400a9eb2, 0xFFFFFFFF200A9EBA, 0x0000000000091E42, 0xFFFFFFFFE0091E8A # 824 - 827 D6 S3 C8 - .quad 0xffffffffc0091e92, 0xFFFFFFFFA0091E9A, 0xFFFFFFFF800A9EA2, 0xFFFFFFFF600A9EAA # 828 - 831 D6 S3 C12 - .quad 0x0000000000082600, 0x0000000000092641, 0x0000000000092642, 0xFFFFFFFFE009268A # 832 - 835 D6 S4 C0 - .quad 0xffffffffc0092692, 0xFFFFFFFFA00AA69A, 0xFFFFFFFF800AA6A2, 0xFFFFFFFF600AA6AA # 836 - 839 D6 S4 C4 - .quad 0xffffffff400aa6b2, 0xFFFFFFFF200AA6BA, 0x0000000000092642, 0xFFFFFFFFE009268A # 840 - 843 D6 S4 C8 - .quad 0xffffffffc0092692, 0xFFFFFFFFA00AA69A, 0xFFFFFFFF800AA6A2, 0xFFFFFFFF600AA6AA # 844 - 847 D6 S4 C12 - .quad 0x0000000000082e00, 0x0000000000092E41, 0x0000000000092E42, 0xFFFFFFFFE0092E8A # 848 - 851 D6 S5 C0 - .quad 0xffffffffc00aae92, 0xFFFFFFFFA00AAE9A, 0xFFFFFFFF800AAEA2, 0xFFFFFFFF600AAEAA # 852 - 855 D6 S5 C4 - .quad 0xffffffff400aaeb2, 0xFFFFFFFF200AAEBA, 0x0000000000092E42, 0xFFFFFFFFE0092E8A # 856 - 859 D6 S5 C8 - .quad 0xffffffffc00aae92, 0xFFFFFFFFA00AAE9A, 0xFFFFFFFF800AAEA2, 0xFFFFFFFF600AAEAA # 860 - 863 D6 S5 C12 - .quad 0x0000000000003600, 0x0000000000013641, 0x0000000000053642, 0xFFFFFFFFE006368A # 864 - 867 D6 S6 C0 - .quad 0xffffffffc0063692, 0xFFFFFFFFA006369A, 0xFFFFFFFF800636A2, 0xFFFFFFFF600636AA # 868 - 871 D6 S6 C4 - .quad 0xffffffff400636b2, 0xFFFFFFFF200636BA, 0x0000000000053642, 0xFFFFFFFFE006368A # 872 - 875 D6 S6 C8 - .quad 0xffffffffc0063692, 0xFFFFFFFFA006369A, 0xFFFFFFFF800636A2, 0xFFFFFFFF600636AA # 876 - 879 D6 S6 C12 - .quad 0x0000000000083e00, 0x00000000000D3E41, 0x00000000000E7E42, 0xFFFFFFFFE00E7E8A # 880 - 883 D6 S7 C0 - .quad 0xffffffffc00e7e92, 0xFFFFFFFFA00E7E9A, 0xFFFFFFFF800E7EA2, 0xFFFFFFFF600E7EAA # 884 - 887 D6 S7 C4 - .quad 0xffffffff400e7eb2, 0xFFFFFFFF200E7EBA, 0x00000000000E7E42, 0xFFFFFFFFE00E7E8A # 888 - 891 D6 S7 C8 - .quad 0xffffffffc00e7e92, 0xFFFFFFFFA00E7E9A, 0xFFFFFFFF800E7EA2, 0xFFFFFFFF600E7EAA # 892 - 895 D6 S7 C12 - .quad 0x0000000000080700, 0x0000000000090741, 0xFFFFFFFFE0090789, 0xFFFFFFFFC0090791 # 896 - 899 D7 S0 C0 - .quad 0xffffffffa0090799, 0xFFFFFFFF800907A1, 0xFFFFFFFF600907A9, 0xFFFFFFFF400907B1 # 900 - 903 D7 S0 C4 - .quad 0xffffffff200907b9, 0x0000000000090741, 0xFFFFFFFFE0090789, 0xFFFFFFFFC0090791 # 904 - 907 D7 S0 C8 - .quad 0xffffffffa0090799, 0xFFFFFFFF800907A1, 0xFFFFFFFF600907A9, 0xFFFFFFFF400907B1 # 908 - 911 D7 S0 C12 - .quad 0x0000000000080f00, 0x0000000000090F41, 0xFFFFFFFFE0090F89, 0xFFFFFFFFC0090F91 # 912 - 915 D7 S1 C0 - .quad 0xffffffffa0090f99, 0xFFFFFFFF80090FA1, 0xFFFFFFFF60090FA9, 0xFFFFFFFF40090FB1 # 916 - 919 D7 S1 C4 - .quad 0xffffffff200a8fb9, 0x0000000000090F41, 0xFFFFFFFFE0090F89, 0xFFFFFFFFC0090F91 # 920 - 923 D7 S1 C8 - .quad 0xffffffffa0090f99, 0xFFFFFFFF80090FA1, 0xFFFFFFFF60090FA9, 0xFFFFFFFF40090FB1 # 924 - 927 D7 S1 C12 - .quad 0x0000000000081700, 0x0000000000091741, 0xFFFFFFFFE0091789, 0xFFFFFFFFC0091791 # 928 - 931 D7 S2 C0 - .quad 0xffffffffa0091799, 0xFFFFFFFF800917A1, 0xFFFFFFFF600917A9, 0xFFFFFFFF400A97B1 # 932 - 935 D7 S2 C4 - .quad 0xffffffff200a97b9, 0x0000000000091741, 0xFFFFFFFFE0091789, 0xFFFFFFFFC0091791 # 936 - 939 D7 S2 C8 - .quad 0xffffffffa0091799, 0xFFFFFFFF800917A1, 0xFFFFFFFF600917A9, 0xFFFFFFFF400A97B1 # 940 - 943 D7 S2 C12 - .quad 0x0000000000081f00, 0x0000000000091F41, 0xFFFFFFFFE0091F89, 0xFFFFFFFFC0091F91 # 944 - 947 D7 S3 C0 - .quad 0xffffffffa0091f99, 0xFFFFFFFF80091FA1, 0xFFFFFFFF600A9FA9, 0xFFFFFFFF400A9FB1 # 948 - 951 D7 S3 C4 - .quad 0xffffffff200a9fb9, 0x0000000000091F41, 0xFFFFFFFFE0091F89, 0xFFFFFFFFC0091F91 # 952 - 955 D7 S3 C8 - .quad 0xffffffffa0091f99, 0xFFFFFFFF80091FA1, 0xFFFFFFFF600A9FA9, 0xFFFFFFFF400A9FB1 # 956 - 959 D7 S3 C12 - .quad 0x0000000000082700, 0x0000000000092741, 0xFFFFFFFFE0092789, 0xFFFFFFFFC0092791 # 960 - 963 D7 S4 C0 - .quad 0xffffffffa0092799, 0xFFFFFFFF800AA7A1, 0xFFFFFFFF600AA7A9, 0xFFFFFFFF400AA7B1 # 964 - 967 D7 S4 C4 - .quad 0xffffffff200aa7b9, 0x0000000000092741, 0xFFFFFFFFE0092789, 0xFFFFFFFFC0092791 # 968 - 971 D7 S4 C8 - .quad 0xffffffffa0092799, 0xFFFFFFFF800AA7A1, 0xFFFFFFFF600AA7A9, 0xFFFFFFFF400AA7B1 # 972 - 975 D7 S4 C12 - .quad 0x0000000000082f00, 0x0000000000092F41, 0xFFFFFFFFE0092F89, 0xFFFFFFFFC0092F91 # 976 - 979 D7 S5 C0 - .quad 0xffffffffa00aaf99, 0xFFFFFFFF800AAFA1, 0xFFFFFFFF600AAFA9, 0xFFFFFFFF400AAFB1 # 980 - 983 D7 S5 C4 - .quad 0xffffffff200aafb9, 0x0000000000092F41, 0xFFFFFFFFE0092F89, 0xFFFFFFFFC0092F91 # 984 - 987 D7 S5 C8 - .quad 0xffffffffa00aaf99, 0xFFFFFFFF800AAFA1, 0xFFFFFFFF600AAFA9, 0xFFFFFFFF400AAFB1 # 988 - 991 D7 S5 C12 - .quad 0x0000000000083700, 0x0000000000093741, 0xFFFFFFFFE0093789, 0xFFFFFFFFC00AB791 # 992 - 995 D7 S6 C0 - .quad 0xffffffffa00ab799, 0xFFFFFFFF800AB7A1, 0xFFFFFFFF600AB7A9, 0xFFFFFFFF400AB7B1 # 996 - 999 D7 S6 C4 - .quad 0xffffffff200ab7b9, 0x0000000000093741, 0xFFFFFFFFE0093789, 0xFFFFFFFFC00AB791 # 1000 - 1003 D7 S6 C8 - .quad 0xffffffffa00ab799, 0xFFFFFFFF800AB7A1, 0xFFFFFFFF600AB7A9, 0xFFFFFFFF400AB7B1 # 1004 - 1007 D7 S6 C12 - .quad 0x0000000000003f00, 0x0000000000053F41, 0xFFFFFFFFE0063F89, 0xFFFFFFFFC0063F91 # 1008 - 1011 D7 S7 C0 - .quad 0xffffffffa0063f99, 0xFFFFFFFF80063FA1, 0xFFFFFFFF60063FA9, 0xFFFFFFFF40063FB1 # 1012 - 1015 D7 S7 C4 - .quad 0xffffffff20063fb9, 0x0000000000053F41, 0xFFFFFFFFE0063F89, 0xFFFFFFFFC0063F91 # 1016 - 1019 D7 S7 C8 - .quad 0xffffffffa0063f99, 0xFFFFFFFF80063FA1, 0xFFFFFFFF60063FA9, 0xFFFFFFFF40063FB1 # 1020 - 1023 D7 S7 C12 + .quad 0x0000000000000000, 0xFFFFFFFF00020088, 0xFFFFFFFE00020090, 0xFFFFFFFD00020098 # 0 - 3 D0 S0 C0 + .quad 0xfffffffc000200a0, 0xFFFFFFFB000200A8, 0xFFFFFFFA000200B0, 0xFFFFFFF9000200B8 # 4 - 7 D0 S0 C4 + .quad 0x0000000000000000, 0xFFFFFFFF00020088, 0xFFFFFFFE00020090, 0xFFFFFFFD00020098 # 8 - 11 D0 S0 C8 + .quad 0xfffffffc000200a0, 0xFFFFFFFB000200A8, 0xFFFFFFFA000200B0, 0xFFFFFFF9000200B8 # 12 - 15 D0 S0 C12 + .quad 0x0000000000101000, 0xFFFFFFFF00121088, 0xFFFFFFFE00121090, 0xFFFFFFFD00121098 # 16 - 19 D0 S1 C0 + .quad 0xfffffffc001210a0, 0xFFFFFFFB001210A8, 0xFFFFFFFA001210B0, 0xFFFFFFF9001210B8 # 20 - 23 D0 S1 C4 + .quad 0x0000000000121000, 0xFFFFFFFF00121088, 0xFFFFFFFE00121090, 0xFFFFFFFD00121098 # 24 - 27 D0 S1 C8 + .quad 0xfffffffc001210a0, 0xFFFFFFFB001210A8, 0xFFFFFFFA001210B0, 0xFFFFFFF9001210B8 # 28 - 31 D0 S1 C12 + .quad 0x0000000000102000, 0xFFFFFFFF00122088, 0xFFFFFFFE00122090, 0xFFFFFFFD00122098 # 32 - 35 D0 S2 C0 + .quad 0xfffffffc001220a0, 0xFFFFFFFB001220A8, 0xFFFFFFFA001220B0, 0xFFFFFFF9001520B8 # 36 - 39 D0 S2 C4 + .quad 0x0000000000122000, 0xFFFFFFFF00122088, 0xFFFFFFFE00122090, 0xFFFFFFFD00122098 # 40 - 43 D0 S2 C8 + .quad 0xfffffffc001220a0, 0xFFFFFFFB001220A8, 0xFFFFFFFA001220B0, 0xFFFFFFF9001520B8 # 44 - 47 D0 S2 C12 + .quad 0x0000000000103000, 0xFFFFFFFF00123088, 0xFFFFFFFE00123090, 0xFFFFFFFD00123098 # 48 - 51 D0 S3 C0 + .quad 0xfffffffc001230a0, 0xFFFFFFFB001230A8, 0xFFFFFFFA001530B0, 0xFFFFFFF9001530B8 # 52 - 55 D0 S3 C4 + .quad 0x0000000000123000, 0xFFFFFFFF00123088, 0xFFFFFFFE00123090, 0xFFFFFFFD00123098 # 56 - 59 D0 S3 C8 + .quad 0xfffffffc001230a0, 0xFFFFFFFB001230A8, 0xFFFFFFFA001530B0, 0xFFFFFFF9001530B8 # 60 - 63 D0 S3 C12 + .quad 0x0000000000104000, 0xFFFFFFFF00124088, 0xFFFFFFFE00124090, 0xFFFFFFFD00124098 # 64 - 67 D0 S4 C0 + .quad 0xfffffffc001240a0, 0xFFFFFFFB001540A8, 0xFFFFFFFA001540B0, 0xFFFFFFF9001540B8 # 68 - 71 D0 S4 C4 + .quad 0x0000000000124000, 0xFFFFFFFF00124088, 0xFFFFFFFE00124090, 0xFFFFFFFD00124098 # 72 - 75 D0 S4 C8 + .quad 0xfffffffc001240a0, 0xFFFFFFFB001540A8, 0xFFFFFFFA001540B0, 0xFFFFFFF9001540B8 # 76 - 79 D0 S4 C12 + .quad 0x0000000000105000, 0xFFFFFFFF00125088, 0xFFFFFFFE00125090, 0xFFFFFFFD00125098 # 80 - 83 D0 S5 C0 + .quad 0xfffffffc001550a0, 0xFFFFFFFB001550A8, 0xFFFFFFFA001550B0, 0xFFFFFFF9001550B8 # 84 - 87 D0 S5 C4 + .quad 0x0000000000125000, 0xFFFFFFFF00125088, 0xFFFFFFFE00125090, 0xFFFFFFFD00125098 # 88 - 91 D0 S5 C8 + .quad 0xfffffffc001550a0, 0xFFFFFFFB001550A8, 0xFFFFFFFA001550B0, 0xFFFFFFF9001550B8 # 92 - 95 D0 S5 C12 + .quad 0x0000000000106000, 0xFFFFFFFF00126088, 0xFFFFFFFE00126090, 0xFFFFFFFD00156098 # 96 - 99 D0 S6 C0 + .quad 0xfffffffc001560a0, 0xFFFFFFFB001560A8, 0xFFFFFFFA001560B0, 0xFFFFFFF9001560B8 # 100 - 103 D0 S6 C4 + .quad 0x0000000000126000, 0xFFFFFFFF00126088, 0xFFFFFFFE00126090, 0xFFFFFFFD00156098 # 104 - 107 D0 S6 C8 + .quad 0xfffffffc001560a0, 0xFFFFFFFB001560A8, 0xFFFFFFFA001560B0, 0xFFFFFFF9001560B8 # 108 - 111 D0 S6 C12 + .quad 0x0000000000107000, 0xFFFFFFFF00127088, 0xFFFFFFFE00157090, 0xFFFFFFFD00157098 # 112 - 115 D0 S7 C0 + .quad 0xfffffffc001570a0, 0xFFFFFFFB001570A8, 0xFFFFFFFA001570B0, 0xFFFFFFF9001570B8 # 116 - 119 D0 S7 C4 + .quad 0x0000000000127000, 0xFFFFFFFF00127088, 0xFFFFFFFE00157090, 0xFFFFFFFD00157098 # 120 - 123 D0 S7 C8 + .quad 0xfffffffc001570a0, 0xFFFFFFFB001570A8, 0xFFFFFFFA001570B0, 0xFFFFFFF9001570B8 # 124 - 127 D0 S7 C12 + .quad 0x0000000000100200, 0x0000000000120281, 0x0000000000120282, 0x0000000000120283 # 128 - 131 D1 S0 C0 + .quad 0x0000000000120284, 0x0000000000120285, 0x0000000000120286, 0x0000000000120287 # 132 - 135 D1 S0 C4 + .quad 0xffffffff0012030f, 0xFFFFFFFE00150317, 0xFFFFFFFD0015031F, 0xFFFFFFFC00150327 # 136 - 139 D1 S0 C8 + .quad 0xfffffffb0015032f, 0xFFFFFFFA00150337, 0xFFFFFFF90015033F, 0x0000000000120287 # 140 - 143 D1 S0 C12 + .quad 0x0000000000001200, 0x0000000000021281, 0x0000000000021282, 0x0000000000021283 # 144 - 147 D1 S1 C0 + .quad 0x0000000000021284, 0x0000000000021285, 0x0000000000021286, 0x00000000000A1287 # 148 - 151 D1 S1 C4 + .quad 0xffffffff000c130f, 0xFFFFFFFE000C1317, 0xFFFFFFFD000C131F, 0xFFFFFFFC000C1327 # 152 - 155 D1 S1 C8 + .quad 0xfffffffb000c132f, 0xFFFFFFFA000C1337, 0xFFFFFFF9000C133F, 0x00000000000A1287 # 156 - 159 D1 S1 C12 + .quad 0x0000000000102200, 0x0000000000122281, 0x0000000000122282, 0x0000000000122283 # 160 - 163 D1 S2 C0 + .quad 0x0000000000122284, 0x0000000000122285, 0x00000000001A2286, 0x00000000001CA287 # 164 - 167 D1 S2 C4 + .quad 0xffffffff001ca30f, 0xFFFFFFFE001CA317, 0xFFFFFFFD001CA31F, 0xFFFFFFFC001CA327 # 168 - 171 D1 S2 C8 + .quad 0xfffffffb001ca32f, 0xFFFFFFFA001CA337, 0xFFFFFFF9001CA33F, 0x00000000001CA287 # 172 - 175 D1 S2 C12 + .quad 0x0000000000103200, 0x0000000000123281, 0x0000000000123282, 0x0000000000123283 # 176 - 179 D1 S3 C0 + .quad 0x0000000000123284, 0x00000000001A3285, 0x00000000001CB286, 0x00000000001CB287 # 180 - 183 D1 S3 C4 + .quad 0xffffffff001cb30f, 0xFFFFFFFE001CB317, 0xFFFFFFFD001CB31F, 0xFFFFFFFC001CB327 # 184 - 187 D1 S3 C8 + .quad 0xfffffffb001cb32f, 0xFFFFFFFA001CB337, 0xFFFFFFF9001FB33F, 0x00000000001CB287 # 188 - 191 D1 S3 C12 + .quad 0x0000000000104200, 0x0000000000124281, 0x0000000000124282, 0x0000000000124283 # 192 - 195 D1 S4 C0 + .quad 0x00000000001a4284, 0x00000000001CC285, 0x00000000001CC286, 0x00000000001CC287 # 196 - 199 D1 S4 C4 + .quad 0xffffffff001cc30f, 0xFFFFFFFE001CC317, 0xFFFFFFFD001CC31F, 0xFFFFFFFC001CC327 # 200 - 203 D1 S4 C8 + .quad 0xfffffffb001cc32f, 0xFFFFFFFA001FC337, 0xFFFFFFF9001FC33F, 0x00000000001CC287 # 204 - 207 D1 S4 C12 + .quad 0x0000000000105200, 0x0000000000125281, 0x0000000000125282, 0x00000000001A5283 # 208 - 211 D1 S5 C0 + .quad 0x00000000001cd284, 0x00000000001CD285, 0x00000000001CD286, 0x00000000001CD287 # 212 - 215 D1 S5 C4 + .quad 0xffffffff001cd30f, 0xFFFFFFFE001CD317, 0xFFFFFFFD001CD31F, 0xFFFFFFFC001CD327 # 216 - 219 D1 S5 C8 + .quad 0xfffffffb001fd32f, 0xFFFFFFFA001FD337, 0xFFFFFFF9001FD33F, 0x00000000001CD287 # 220 - 223 D1 S5 C12 + .quad 0x0000000000106200, 0x0000000000126281, 0x00000000001A6282, 0x00000000001CE283 # 224 - 227 D1 S6 C0 + .quad 0x00000000001ce284, 0x00000000001CE285, 0x00000000001CE286, 0x00000000001CE287 # 228 - 231 D1 S6 C4 + .quad 0xffffffff001ce30f, 0xFFFFFFFE001CE317, 0xFFFFFFFD001CE31F, 0xFFFFFFFC001FE327 # 232 - 235 D1 S6 C8 + .quad 0xfffffffb001fe32f, 0xFFFFFFFA001FE337, 0xFFFFFFF9001FE33F, 0x00000000001CE287 # 236 - 239 D1 S6 C12 + .quad 0x0000000000107200, 0x00000000001A7281, 0x00000000001CF282, 0x00000000001CF283 # 240 - 243 D1 S7 C0 + .quad 0x00000000001cf284, 0x00000000001CF285, 0x00000000001CF286, 0x00000000001CF287 # 244 - 247 D1 S7 C4 + .quad 0xffffffff001cf30f, 0xFFFFFFFE001CF317, 0xFFFFFFFD001FF31F, 0xFFFFFFFC001FF327 # 248 - 251 D1 S7 C8 + .quad 0xfffffffb001ff32f, 0xFFFFFFFA001FF337, 0xFFFFFFF9001FF33F, 0x00000000001CF287 # 252 - 255 D1 S7 C12 + .quad 0x0000000000100400, 0x0000000000120481, 0x0000000000120482, 0x0000000000120483 # 256 - 259 D2 S0 C0 + .quad 0x0000000000120484, 0x0000000000120485, 0x0000000000120486, 0xFFFFFFFF0012050E # 260 - 263 D2 S0 C4 + .quad 0xfffffffe00120516, 0xFFFFFFFD0015051E, 0xFFFFFFFC00150526, 0xFFFFFFFB0015052E # 264 - 267 D2 S0 C8 + .quad 0xfffffffa00150536, 0xFFFFFFF90015053E, 0x0000000000120486, 0xFFFFFFFF0012050E # 268 - 271 D2 S0 C12 + .quad 0x0000000000101400, 0x0000000000121481, 0x0000000000121482, 0x0000000000121483 # 272 - 275 D2 S1 C0 + .quad 0x0000000000121484, 0x0000000000121485, 0x0000000000121486, 0xFFFFFFFF0012150E # 276 - 279 D2 S1 C4 + .quad 0xfffffffe00151516, 0xFFFFFFFD0015151E, 0xFFFFFFFC00151526, 0xFFFFFFFB0015152E # 280 - 283 D2 S1 C8 + .quad 0xfffffffa00151536, 0xFFFFFFF90015153E, 0x0000000000121486, 0xFFFFFFFF0012150E # 284 - 287 D2 S1 C12 + .quad 0x0000000000002400, 0x0000000000022481, 0x0000000000022482, 0x0000000000022483 # 288 - 291 D2 S2 C0 + .quad 0x0000000000022484, 0x0000000000022485, 0x00000000000A2486, 0xFFFFFFFF000C250E # 292 - 295 D2 S2 C4 + .quad 0xfffffffe000c2516, 0xFFFFFFFD000C251E, 0xFFFFFFFC000C2526, 0xFFFFFFFB000C252E # 296 - 299 D2 S2 C8 + .quad 0xfffffffa000c2536, 0xFFFFFFF9000C253E, 0x00000000000A2486, 0xFFFFFFFF000C250E # 300 - 303 D2 S2 C12 + .quad 0x0000000000103400, 0x0000000000123481, 0x0000000000123482, 0x0000000000123483 # 304 - 307 D2 S3 C0 + .quad 0x0000000000123484, 0x00000000001A3485, 0x00000000001CB486, 0xFFFFFFFF001CB50E # 308 - 311 D2 S3 C4 + .quad 0xfffffffe001cb516, 0xFFFFFFFD001CB51E, 0xFFFFFFFC001CB526, 0xFFFFFFFB001CB52E # 312 - 315 D2 S3 C8 + .quad 0xfffffffa001cb536, 0xFFFFFFF9001CB53E, 0x00000000001CB486, 0xFFFFFFFF001CB50E # 316 - 319 D2 S3 C12 + .quad 0x0000000000104400, 0x0000000000124481, 0x0000000000124482, 0x0000000000124483 # 320 - 323 D2 S4 C0 + .quad 0x00000000001a4484, 0x00000000001CC485, 0x00000000001CC486, 0xFFFFFFFF001CC50E # 324 - 327 D2 S4 C4 + .quad 0xfffffffe001cc516, 0xFFFFFFFD001CC51E, 0xFFFFFFFC001CC526, 0xFFFFFFFB001CC52E # 328 - 331 D2 S4 C8 + .quad 0xfffffffa001cc536, 0xFFFFFFF9001FC53E, 0x00000000001CC486, 0xFFFFFFFF001CC50E # 332 - 335 D2 S4 C12 + .quad 0x0000000000105400, 0x0000000000125481, 0x0000000000125482, 0x00000000001A5483 # 336 - 339 D2 S5 C0 + .quad 0x00000000001cd484, 0x00000000001CD485, 0x00000000001CD486, 0xFFFFFFFF001CD50E # 340 - 343 D2 S5 C4 + .quad 0xfffffffe001cd516, 0xFFFFFFFD001CD51E, 0xFFFFFFFC001CD526, 0xFFFFFFFB001CD52E # 344 - 347 D2 S5 C8 + .quad 0xfffffffa001fd536, 0xFFFFFFF9001FD53E, 0x00000000001CD486, 0xFFFFFFFF001CD50E # 348 - 351 D2 S5 C12 + .quad 0x0000000000106400, 0x0000000000126481, 0x00000000001A6482, 0x00000000001CE483 # 352 - 355 D2 S6 C0 + .quad 0x00000000001ce484, 0x00000000001CE485, 0x00000000001CE486, 0xFFFFFFFF001CE50E # 356 - 359 D2 S6 C4 + .quad 0xfffffffe001ce516, 0xFFFFFFFD001CE51E, 0xFFFFFFFC001CE526, 0xFFFFFFFB001FE52E # 360 - 363 D2 S6 C8 + .quad 0xfffffffa001fe536, 0xFFFFFFF9001FE53E, 0x00000000001CE486, 0xFFFFFFFF001CE50E # 364 - 367 D2 S6 C12 + .quad 0x0000000000107400, 0x00000000001A7481, 0x00000000001CF482, 0x00000000001CF483 # 368 - 371 D2 S7 C0 + .quad 0x00000000001cf484, 0x00000000001CF485, 0x00000000001CF486, 0xFFFFFFFF001CF50E # 372 - 375 D2 S7 C4 + .quad 0xfffffffe001cf516, 0xFFFFFFFD001CF51E, 0xFFFFFFFC001FF526, 0xFFFFFFFB001FF52E # 376 - 379 D2 S7 C8 + .quad 0xfffffffa001ff536, 0xFFFFFFF9001FF53E, 0x00000000001CF486, 0xFFFFFFFF001CF50E # 380 - 383 D2 S7 C12 + .quad 0x0000000000100600, 0x0000000000120681, 0x0000000000120682, 0x0000000000120683 # 384 - 387 D3 S0 C0 + .quad 0x0000000000120684, 0x0000000000120685, 0xFFFFFFFF0012070D, 0xFFFFFFFE00120715 # 388 - 391 D3 S0 C4 + .quad 0xfffffffd0012071d, 0xFFFFFFFC00150725, 0xFFFFFFFB0015072D, 0xFFFFFFFA00150735 # 392 - 395 D3 S0 C8 + .quad 0xfffffff90015073d, 0x0000000000120685, 0xFFFFFFFF0012070D, 0xFFFFFFFE00120715 # 396 - 399 D3 S0 C12 + .quad 0x0000000000101600, 0x0000000000121681, 0x0000000000121682, 0x0000000000121683 # 400 - 403 D3 S1 C0 + .quad 0x0000000000121684, 0x0000000000121685, 0xFFFFFFFF0012170D, 0xFFFFFFFE00121715 # 404 - 407 D3 S1 C4 + .quad 0xfffffffd0015171d, 0xFFFFFFFC00151725, 0xFFFFFFFB0015172D, 0xFFFFFFFA00151735 # 408 - 411 D3 S1 C8 + .quad 0xfffffff90015173d, 0x0000000000121685, 0xFFFFFFFF0012170D, 0xFFFFFFFE00121715 # 412 - 415 D3 S1 C12 + .quad 0x0000000000102600, 0x0000000000122681, 0x0000000000122682, 0x0000000000122683 # 416 - 419 D3 S2 C0 + .quad 0x0000000000122684, 0x0000000000122685, 0xFFFFFFFF0012270D, 0xFFFFFFFE00152715 # 420 - 423 D3 S2 C4 + .quad 0xfffffffd0015271d, 0xFFFFFFFC00152725, 0xFFFFFFFB0015272D, 0xFFFFFFFA00152735 # 424 - 427 D3 S2 C8 + .quad 0xfffffff90015273d, 0x0000000000122685, 0xFFFFFFFF0012270D, 0xFFFFFFFE00152715 # 428 - 431 D3 S2 C12 + .quad 0x0000000000003600, 0x0000000000023681, 0x0000000000023682, 0x0000000000023683 # 432 - 435 D3 S3 C0 + .quad 0x0000000000023684, 0x00000000000A3685, 0xFFFFFFFF000C370D, 0xFFFFFFFE000C3715 # 436 - 439 D3 S3 C4 + .quad 0xfffffffd000c371d, 0xFFFFFFFC000C3725, 0xFFFFFFFB000C372D, 0xFFFFFFFA000C3735 # 440 - 443 D3 S3 C8 + .quad 0xfffffff9000c373d, 0x00000000000A3685, 0xFFFFFFFF000C370D, 0xFFFFFFFE000C3715 # 444 - 447 D3 S3 C12 + .quad 0x0000000000104600, 0x0000000000124681, 0x0000000000124682, 0x0000000000124683 # 448 - 451 D3 S4 C0 + .quad 0x00000000001a4684, 0x00000000001CC685, 0xFFFFFFFF001CC70D, 0xFFFFFFFE001CC715 # 452 - 455 D3 S4 C4 + .quad 0xfffffffd001cc71d, 0xFFFFFFFC001CC725, 0xFFFFFFFB001CC72D, 0xFFFFFFFA001CC735 # 456 - 459 D3 S4 C8 + .quad 0xfffffff9001cc73d, 0x00000000001CC685, 0xFFFFFFFF001CC70D, 0xFFFFFFFE001CC715 # 460 - 463 D3 S4 C12 + .quad 0x0000000000105600, 0x0000000000125681, 0x0000000000125682, 0x00000000001A5683 # 464 - 467 D3 S5 C0 + .quad 0x00000000001cd684, 0x00000000001CD685, 0xFFFFFFFF001CD70D, 0xFFFFFFFE001CD715 # 468 - 471 D3 S5 C4 + .quad 0xfffffffd001cd71d, 0xFFFFFFFC001CD725, 0xFFFFFFFB001CD72D, 0xFFFFFFFA001CD735 # 472 - 475 D3 S5 C8 + .quad 0xfffffff9001fd73d, 0x00000000001CD685, 0xFFFFFFFF001CD70D, 0xFFFFFFFE001CD715 # 476 - 479 D3 S5 C12 + .quad 0x0000000000106600, 0x0000000000126681, 0x00000000001A6682, 0x00000000001CE683 # 480 - 483 D3 S6 C0 + .quad 0x00000000001ce684, 0x00000000001CE685, 0xFFFFFFFF001CE70D, 0xFFFFFFFE001CE715 # 484 - 487 D3 S6 C4 + .quad 0xfffffffd001ce71d, 0xFFFFFFFC001CE725, 0xFFFFFFFB001CE72D, 0xFFFFFFFA001FE735 # 488 - 491 D3 S6 C8 + .quad 0xfffffff9001fe73d, 0x00000000001CE685, 0xFFFFFFFF001CE70D, 0xFFFFFFFE001CE715 # 492 - 495 D3 S6 C12 + .quad 0x0000000000107600, 0x00000000001A7681, 0x00000000001CF682, 0x00000000001CF683 # 496 - 499 D3 S7 C0 + .quad 0x00000000001cf684, 0x00000000001CF685, 0xFFFFFFFF001CF70D, 0xFFFFFFFE001CF715 # 500 - 503 D3 S7 C4 + .quad 0xfffffffd001cf71d, 0xFFFFFFFC001CF725, 0xFFFFFFFB001FF72D, 0xFFFFFFFA001FF735 # 504 - 507 D3 S7 C8 + .quad 0xfffffff9001ff73d, 0x00000000001CF685, 0xFFFFFFFF001CF70D, 0xFFFFFFFE001CF715 # 508 - 511 D3 S7 C12 + .quad 0x0000000000100800, 0x0000000000120881, 0x0000000000120882, 0x0000000000120883 # 512 - 515 D4 S0 C0 + .quad 0x0000000000120884, 0xFFFFFFFF0012090C, 0xFFFFFFFE00120914, 0xFFFFFFFD0012091C # 516 - 519 D4 S0 C4 + .quad 0xfffffffc00120924, 0xFFFFFFFB0015092C, 0xFFFFFFFA00150934, 0xFFFFFFF90015093C # 520 - 523 D4 S0 C8 + .quad 0x0000000000120884, 0xFFFFFFFF0012090C, 0xFFFFFFFE00120914, 0xFFFFFFFD0012091C # 524 - 527 D4 S0 C12 + .quad 0x0000000000101800, 0x0000000000121881, 0x0000000000121882, 0x0000000000121883 # 528 - 531 D4 S1 C0 + .quad 0x0000000000121884, 0xFFFFFFFF0012190C, 0xFFFFFFFE00121914, 0xFFFFFFFD0012191C # 532 - 535 D4 S1 C4 + .quad 0xfffffffc00151924, 0xFFFFFFFB0015192C, 0xFFFFFFFA00151934, 0xFFFFFFF90015193C # 536 - 539 D4 S1 C8 + .quad 0x0000000000121884, 0xFFFFFFFF0012190C, 0xFFFFFFFE00121914, 0xFFFFFFFD0012191C # 540 - 543 D4 S1 C12 + .quad 0x0000000000102800, 0x0000000000122881, 0x0000000000122882, 0x0000000000122883 # 544 - 547 D4 S2 C0 + .quad 0x0000000000122884, 0xFFFFFFFF0012290C, 0xFFFFFFFE00122914, 0xFFFFFFFD0015291C # 548 - 551 D4 S2 C4 + .quad 0xfffffffc00152924, 0xFFFFFFFB0015292C, 0xFFFFFFFA00152934, 0xFFFFFFF90015293C # 552 - 555 D4 S2 C8 + .quad 0x0000000000122884, 0xFFFFFFFF0012290C, 0xFFFFFFFE00122914, 0xFFFFFFFD0015291C # 556 - 559 D4 S2 C12 + .quad 0x0000000000103800, 0x0000000000123881, 0x0000000000123882, 0x0000000000123883 # 560 - 563 D4 S3 C0 + .quad 0x0000000000123884, 0xFFFFFFFF0012390C, 0xFFFFFFFE00153914, 0xFFFFFFFD0015391C # 564 - 567 D4 S3 C4 + .quad 0xfffffffc00153924, 0xFFFFFFFB0015392C, 0xFFFFFFFA00153934, 0xFFFFFFF90015393C # 568 - 571 D4 S3 C8 + .quad 0x0000000000123884, 0xFFFFFFFF0012390C, 0xFFFFFFFE00153914, 0xFFFFFFFD0015391C # 572 - 575 D4 S3 C12 + .quad 0x0000000000004800, 0x0000000000024881, 0x0000000000024882, 0x0000000000024883 # 576 - 579 D4 S4 C0 + .quad 0x00000000000a4884, 0xFFFFFFFF000C490C, 0xFFFFFFFE000C4914, 0xFFFFFFFD000C491C # 580 - 583 D4 S4 C4 + .quad 0xfffffffc000c4924, 0xFFFFFFFB000C492C, 0xFFFFFFFA000C4934, 0xFFFFFFF9000C493C # 584 - 587 D4 S4 C8 + .quad 0x00000000000a4884, 0xFFFFFFFF000C490C, 0xFFFFFFFE000C4914, 0xFFFFFFFD000C491C # 588 - 591 D4 S4 C12 + .quad 0x0000000000105800, 0x0000000000125881, 0x0000000000125882, 0x00000000001A5883 # 592 - 595 D4 S5 C0 + .quad 0x00000000001cd884, 0xFFFFFFFF001CD90C, 0xFFFFFFFE001CD914, 0xFFFFFFFD001CD91C # 596 - 599 D4 S5 C4 + .quad 0xfffffffc001cd924, 0xFFFFFFFB001CD92C, 0xFFFFFFFA001CD934, 0xFFFFFFF9001CD93C # 600 - 603 D4 S5 C8 + .quad 0x00000000001cd884, 0xFFFFFFFF001CD90C, 0xFFFFFFFE001CD914, 0xFFFFFFFD001CD91C # 604 - 607 D4 S5 C12 + .quad 0x0000000000106800, 0x0000000000126881, 0x00000000001A6882, 0x00000000001CE883 # 608 - 611 D4 S6 C0 + .quad 0x00000000001ce884, 0xFFFFFFFF001CE90C, 0xFFFFFFFE001CE914, 0xFFFFFFFD001CE91C # 612 - 615 D4 S6 C4 + .quad 0xfffffffc001ce924, 0xFFFFFFFB001CE92C, 0xFFFFFFFA001CE934, 0xFFFFFFF9001FE93C # 616 - 619 D4 S6 C8 + .quad 0x00000000001ce884, 0xFFFFFFFF001CE90C, 0xFFFFFFFE001CE914, 0xFFFFFFFD001CE91C # 620 - 623 D4 S6 C12 + .quad 0x0000000000107800, 0x00000000001A7881, 0x00000000001CF882, 0x00000000001CF883 # 624 - 627 D4 S7 C0 + .quad 0x00000000001cf884, 0xFFFFFFFF001CF90C, 0xFFFFFFFE001CF914, 0xFFFFFFFD001CF91C # 628 - 631 D4 S7 C4 + .quad 0xfffffffc001cf924, 0xFFFFFFFB001CF92C, 0xFFFFFFFA001FF934, 0xFFFFFFF9001FF93C # 632 - 635 D4 S7 C8 + .quad 0x00000000001cf884, 0xFFFFFFFF001CF90C, 0xFFFFFFFE001CF914, 0xFFFFFFFD001CF91C # 636 - 639 D4 S7 C12 + .quad 0x0000000000100a00, 0x0000000000120A81, 0x0000000000120A82, 0x0000000000120A83 # 640 - 643 D5 S0 C0 + .quad 0xffffffff00120b0b, 0xFFFFFFFE00120B13, 0xFFFFFFFD00120B1B, 0xFFFFFFFC00120B23 # 644 - 647 D5 S0 C4 + .quad 0xfffffffb00120b2b, 0xFFFFFFFA00150B33, 0xFFFFFFF900150B3B, 0x0000000000120A83 # 648 - 651 D5 S0 C8 + .quad 0xffffffff00120b0b, 0xFFFFFFFE00120B13, 0xFFFFFFFD00120B1B, 0xFFFFFFFC00120B23 # 652 - 655 D5 S0 C12 + .quad 0x0000000000101a00, 0x0000000000121A81, 0x0000000000121A82, 0x0000000000121A83 # 656 - 659 D5 S1 C0 + .quad 0xffffffff00121b0b, 0xFFFFFFFE00121B13, 0xFFFFFFFD00121B1B, 0xFFFFFFFC00121B23 # 660 - 663 D5 S1 C4 + .quad 0xfffffffb00151b2b, 0xFFFFFFFA00151B33, 0xFFFFFFF900151B3B, 0x0000000000121A83 # 664 - 667 D5 S1 C8 + .quad 0xffffffff00121b0b, 0xFFFFFFFE00121B13, 0xFFFFFFFD00121B1B, 0xFFFFFFFC00121B23 # 668 - 671 D5 S1 C12 + .quad 0x0000000000102a00, 0x0000000000122A81, 0x0000000000122A82, 0x0000000000122A83 # 672 - 675 D5 S2 C0 + .quad 0xffffffff00122b0b, 0xFFFFFFFE00122B13, 0xFFFFFFFD00122B1B, 0xFFFFFFFC00152B23 # 676 - 679 D5 S2 C4 + .quad 0xfffffffb00152b2b, 0xFFFFFFFA00152B33, 0xFFFFFFF900152B3B, 0x0000000000122A83 # 680 - 683 D5 S2 C8 + .quad 0xffffffff00122b0b, 0xFFFFFFFE00122B13, 0xFFFFFFFD00122B1B, 0xFFFFFFFC00152B23 # 684 - 687 D5 S2 C12 + .quad 0x0000000000103a00, 0x0000000000123A81, 0x0000000000123A82, 0x0000000000123A83 # 688 - 691 D5 S3 C0 + .quad 0xffffffff00123b0b, 0xFFFFFFFE00123B13, 0xFFFFFFFD00153B1B, 0xFFFFFFFC00153B23 # 692 - 695 D5 S3 C4 + .quad 0xfffffffb00153b2b, 0xFFFFFFFA00153B33, 0xFFFFFFF900153B3B, 0x0000000000123A83 # 696 - 699 D5 S3 C8 + .quad 0xffffffff00123b0b, 0xFFFFFFFE00123B13, 0xFFFFFFFD00153B1B, 0xFFFFFFFC00153B23 # 700 - 703 D5 S3 C12 + .quad 0x0000000000104a00, 0x0000000000124A81, 0x0000000000124A82, 0x0000000000124A83 # 704 - 707 D5 S4 C0 + .quad 0xffffffff00124b0b, 0xFFFFFFFE00154B13, 0xFFFFFFFD00154B1B, 0xFFFFFFFC00154B23 # 708 - 711 D5 S4 C4 + .quad 0xfffffffb00154b2b, 0xFFFFFFFA00154B33, 0xFFFFFFF900154B3B, 0x0000000000124A83 # 712 - 715 D5 S4 C8 + .quad 0xffffffff00124b0b, 0xFFFFFFFE00154B13, 0xFFFFFFFD00154B1B, 0xFFFFFFFC00154B23 # 716 - 719 D5 S4 C12 + .quad 0x0000000000005a00, 0x0000000000025A81, 0x0000000000025A82, 0x00000000000A5A83 # 720 - 723 D5 S5 C0 + .quad 0xffffffff000c5b0b, 0xFFFFFFFE000C5B13, 0xFFFFFFFD000C5B1B, 0xFFFFFFFC000C5B23 # 724 - 727 D5 S5 C4 + .quad 0xfffffffb000c5b2b, 0xFFFFFFFA000C5B33, 0xFFFFFFF9000C5B3B, 0x00000000000A5A83 # 728 - 731 D5 S5 C8 + .quad 0xffffffff000c5b0b, 0xFFFFFFFE000C5B13, 0xFFFFFFFD000C5B1B, 0xFFFFFFFC000C5B23 # 732 - 735 D5 S5 C12 + .quad 0x0000000000106a00, 0x0000000000126A81, 0x00000000001A6A82, 0x00000000001CEA83 # 736 - 739 D5 S6 C0 + .quad 0xffffffff001ceb0b, 0xFFFFFFFE001CEB13, 0xFFFFFFFD001CEB1B, 0xFFFFFFFC001CEB23 # 740 - 743 D5 S6 C4 + .quad 0xfffffffb001ceb2b, 0xFFFFFFFA001CEB33, 0xFFFFFFF9001CEB3B, 0x00000000001CEA83 # 744 - 747 D5 S6 C8 + .quad 0xffffffff001ceb0b, 0xFFFFFFFE001CEB13, 0xFFFFFFFD001CEB1B, 0xFFFFFFFC001CEB23 # 748 - 751 D5 S6 C12 + .quad 0x0000000000107a00, 0x00000000001A7A81, 0x00000000001CFA82, 0x00000000001CFA83 # 752 - 755 D5 S7 C0 + .quad 0xffffffff001cfb0b, 0xFFFFFFFE001CFB13, 0xFFFFFFFD001CFB1B, 0xFFFFFFFC001CFB23 # 756 - 759 D5 S7 C4 + .quad 0xfffffffb001cfb2b, 0xFFFFFFFA001CFB33, 0xFFFFFFF9001FFB3B, 0x00000000001CFA83 # 760 - 763 D5 S7 C8 + .quad 0xffffffff001cfb0b, 0xFFFFFFFE001CFB13, 0xFFFFFFFD001CFB1B, 0xFFFFFFFC001CFB23 # 764 - 767 D5 S7 C12 + .quad 0x0000000000100c00, 0x0000000000120C81, 0x0000000000120C82, 0xFFFFFFFF00120D0A # 768 - 771 D6 S0 C0 + .quad 0xfffffffe00120d12, 0xFFFFFFFD00120D1A, 0xFFFFFFFC00120D22, 0xFFFFFFFB00120D2A # 772 - 775 D6 S0 C4 + .quad 0xfffffffa00120d32, 0xFFFFFFF900150D3A, 0x0000000000120C82, 0xFFFFFFFF00120D0A # 776 - 779 D6 S0 C8 + .quad 0xfffffffe00120d12, 0xFFFFFFFD00120D1A, 0xFFFFFFFC00120D22, 0xFFFFFFFB00120D2A # 780 - 783 D6 S0 C12 + .quad 0x0000000000101c00, 0x0000000000121C81, 0x0000000000121C82, 0xFFFFFFFF00121D0A # 784 - 787 D6 S1 C0 + .quad 0xfffffffe00121d12, 0xFFFFFFFD00121D1A, 0xFFFFFFFC00121D22, 0xFFFFFFFB00121D2A # 788 - 791 D6 S1 C4 + .quad 0xfffffffa00151d32, 0xFFFFFFF900151D3A, 0x0000000000121C82, 0xFFFFFFFF00121D0A # 792 - 795 D6 S1 C8 + .quad 0xfffffffe00121d12, 0xFFFFFFFD00121D1A, 0xFFFFFFFC00121D22, 0xFFFFFFFB00121D2A # 796 - 799 D6 S1 C12 + .quad 0x0000000000102c00, 0x0000000000122C81, 0x0000000000122C82, 0xFFFFFFFF00122D0A # 800 - 803 D6 S2 C0 + .quad 0xfffffffe00122d12, 0xFFFFFFFD00122D1A, 0xFFFFFFFC00122D22, 0xFFFFFFFB00152D2A # 804 - 807 D6 S2 C4 + .quad 0xfffffffa00152d32, 0xFFFFFFF900152D3A, 0x0000000000122C82, 0xFFFFFFFF00122D0A # 808 - 811 D6 S2 C8 + .quad 0xfffffffe00122d12, 0xFFFFFFFD00122D1A, 0xFFFFFFFC00122D22, 0xFFFFFFFB00152D2A # 812 - 815 D6 S2 C12 + .quad 0x0000000000103c00, 0x0000000000123C81, 0x0000000000123C82, 0xFFFFFFFF00123D0A # 816 - 819 D6 S3 C0 + .quad 0xfffffffe00123d12, 0xFFFFFFFD00123D1A, 0xFFFFFFFC00153D22, 0xFFFFFFFB00153D2A # 820 - 823 D6 S3 C4 + .quad 0xfffffffa00153d32, 0xFFFFFFF900153D3A, 0x0000000000123C82, 0xFFFFFFFF00123D0A # 824 - 827 D6 S3 C8 + .quad 0xfffffffe00123d12, 0xFFFFFFFD00123D1A, 0xFFFFFFFC00153D22, 0xFFFFFFFB00153D2A # 828 - 831 D6 S3 C12 + .quad 0x0000000000104c00, 0x0000000000124C81, 0x0000000000124C82, 0xFFFFFFFF00124D0A # 832 - 835 D6 S4 C0 + .quad 0xfffffffe00124d12, 0xFFFFFFFD00154D1A, 0xFFFFFFFC00154D22, 0xFFFFFFFB00154D2A # 836 - 839 D6 S4 C4 + .quad 0xfffffffa00154d32, 0xFFFFFFF900154D3A, 0x0000000000124C82, 0xFFFFFFFF00124D0A # 840 - 843 D6 S4 C8 + .quad 0xfffffffe00124d12, 0xFFFFFFFD00154D1A, 0xFFFFFFFC00154D22, 0xFFFFFFFB00154D2A # 844 - 847 D6 S4 C12 + .quad 0x0000000000105c00, 0x0000000000125C81, 0x0000000000125C82, 0xFFFFFFFF00125D0A # 848 - 851 D6 S5 C0 + .quad 0xfffffffe00155d12, 0xFFFFFFFD00155D1A, 0xFFFFFFFC00155D22, 0xFFFFFFFB00155D2A # 852 - 855 D6 S5 C4 + .quad 0xfffffffa00155d32, 0xFFFFFFF900155D3A, 0x0000000000125C82, 0xFFFFFFFF00125D0A # 856 - 859 D6 S5 C8 + .quad 0xfffffffe00155d12, 0xFFFFFFFD00155D1A, 0xFFFFFFFC00155D22, 0xFFFFFFFB00155D2A # 860 - 863 D6 S5 C12 + .quad 0x0000000000006c00, 0x0000000000026C81, 0x00000000000A6C82, 0xFFFFFFFF000C6D0A # 864 - 867 D6 S6 C0 + .quad 0xfffffffe000c6d12, 0xFFFFFFFD000C6D1A, 0xFFFFFFFC000C6D22, 0xFFFFFFFB000C6D2A # 868 - 871 D6 S6 C4 + .quad 0xfffffffa000c6d32, 0xFFFFFFF9000C6D3A, 0x00000000000A6C82, 0xFFFFFFFF000C6D0A # 872 - 875 D6 S6 C8 + .quad 0xfffffffe000c6d12, 0xFFFFFFFD000C6D1A, 0xFFFFFFFC000C6D22, 0xFFFFFFFB000C6D2A # 876 - 879 D6 S6 C12 + .quad 0x0000000000107c00, 0x00000000001A7C81, 0x00000000001CFC82, 0xFFFFFFFF001CFD0A # 880 - 883 D6 S7 C0 + .quad 0xfffffffe001cfd12, 0xFFFFFFFD001CFD1A, 0xFFFFFFFC001CFD22, 0xFFFFFFFB001CFD2A # 884 - 887 D6 S7 C4 + .quad 0xfffffffa001cfd32, 0xFFFFFFF9001CFD3A, 0x00000000001CFC82, 0xFFFFFFFF001CFD0A # 888 - 891 D6 S7 C8 + .quad 0xfffffffe001cfd12, 0xFFFFFFFD001CFD1A, 0xFFFFFFFC001CFD22, 0xFFFFFFFB001CFD2A # 892 - 895 D6 S7 C12 + .quad 0x0000000000100e00, 0x0000000000120E81, 0xFFFFFFFF00120F09, 0xFFFFFFFE00120F11 # 896 - 899 D7 S0 C0 + .quad 0xfffffffd00120f19, 0xFFFFFFFC00120F21, 0xFFFFFFFB00120F29, 0xFFFFFFFA00120F31 # 900 - 903 D7 S0 C4 + .quad 0xfffffff900120f39, 0x0000000000120E81, 0xFFFFFFFF00120F09, 0xFFFFFFFE00120F11 # 904 - 907 D7 S0 C8 + .quad 0xfffffffd00120f19, 0xFFFFFFFC00120F21, 0xFFFFFFFB00120F29, 0xFFFFFFFA00120F31 # 908 - 911 D7 S0 C12 + .quad 0x0000000000101e00, 0x0000000000121E81, 0xFFFFFFFF00121F09, 0xFFFFFFFE00121F11 # 912 - 915 D7 S1 C0 + .quad 0xfffffffd00121f19, 0xFFFFFFFC00121F21, 0xFFFFFFFB00121F29, 0xFFFFFFFA00121F31 # 916 - 919 D7 S1 C4 + .quad 0xfffffff900151f39, 0x0000000000121E81, 0xFFFFFFFF00121F09, 0xFFFFFFFE00121F11 # 920 - 923 D7 S1 C8 + .quad 0xfffffffd00121f19, 0xFFFFFFFC00121F21, 0xFFFFFFFB00121F29, 0xFFFFFFFA00121F31 # 924 - 927 D7 S1 C12 + .quad 0x0000000000102e00, 0x0000000000122E81, 0xFFFFFFFF00122F09, 0xFFFFFFFE00122F11 # 928 - 931 D7 S2 C0 + .quad 0xfffffffd00122f19, 0xFFFFFFFC00122F21, 0xFFFFFFFB00122F29, 0xFFFFFFFA00152F31 # 932 - 935 D7 S2 C4 + .quad 0xfffffff900152f39, 0x0000000000122E81, 0xFFFFFFFF00122F09, 0xFFFFFFFE00122F11 # 936 - 939 D7 S2 C8 + .quad 0xfffffffd00122f19, 0xFFFFFFFC00122F21, 0xFFFFFFFB00122F29, 0xFFFFFFFA00152F31 # 940 - 943 D7 S2 C12 + .quad 0x0000000000103e00, 0x0000000000123E81, 0xFFFFFFFF00123F09, 0xFFFFFFFE00123F11 # 944 - 947 D7 S3 C0 + .quad 0xfffffffd00123f19, 0xFFFFFFFC00123F21, 0xFFFFFFFB00153F29, 0xFFFFFFFA00153F31 # 948 - 951 D7 S3 C4 + .quad 0xfffffff900153f39, 0x0000000000123E81, 0xFFFFFFFF00123F09, 0xFFFFFFFE00123F11 # 952 - 955 D7 S3 C8 + .quad 0xfffffffd00123f19, 0xFFFFFFFC00123F21, 0xFFFFFFFB00153F29, 0xFFFFFFFA00153F31 # 956 - 959 D7 S3 C12 + .quad 0x0000000000104e00, 0x0000000000124E81, 0xFFFFFFFF00124F09, 0xFFFFFFFE00124F11 # 960 - 963 D7 S4 C0 + .quad 0xfffffffd00124f19, 0xFFFFFFFC00154F21, 0xFFFFFFFB00154F29, 0xFFFFFFFA00154F31 # 964 - 967 D7 S4 C4 + .quad 0xfffffff900154f39, 0x0000000000124E81, 0xFFFFFFFF00124F09, 0xFFFFFFFE00124F11 # 968 - 971 D7 S4 C8 + .quad 0xfffffffd00124f19, 0xFFFFFFFC00154F21, 0xFFFFFFFB00154F29, 0xFFFFFFFA00154F31 # 972 - 975 D7 S4 C12 + .quad 0x0000000000105e00, 0x0000000000125E81, 0xFFFFFFFF00125F09, 0xFFFFFFFE00125F11 # 976 - 979 D7 S5 C0 + .quad 0xfffffffd00155f19, 0xFFFFFFFC00155F21, 0xFFFFFFFB00155F29, 0xFFFFFFFA00155F31 # 980 - 983 D7 S5 C4 + .quad 0xfffffff900155f39, 0x0000000000125E81, 0xFFFFFFFF00125F09, 0xFFFFFFFE00125F11 # 984 - 987 D7 S5 C8 + .quad 0xfffffffd00155f19, 0xFFFFFFFC00155F21, 0xFFFFFFFB00155F29, 0xFFFFFFFA00155F31 # 988 - 991 D7 S5 C12 + .quad 0x0000000000106e00, 0x0000000000126E81, 0xFFFFFFFF00126F09, 0xFFFFFFFE00156F11 # 992 - 995 D7 S6 C0 + .quad 0xfffffffd00156f19, 0xFFFFFFFC00156F21, 0xFFFFFFFB00156F29, 0xFFFFFFFA00156F31 # 996 - 999 D7 S6 C4 + .quad 0xfffffff900156f39, 0x0000000000126E81, 0xFFFFFFFF00126F09, 0xFFFFFFFE00156F11 # 1000 - 1003 D7 S6 C8 + .quad 0xfffffffd00156f19, 0xFFFFFFFC00156F21, 0xFFFFFFFB00156F29, 0xFFFFFFFA00156F31 # 1004 - 1007 D7 S6 C12 + .quad 0x0000000000007e00, 0x00000000000A7E81, 0xFFFFFFFF000C7F09, 0xFFFFFFFE000C7F11 # 1008 - 1011 D7 S7 C0 + .quad 0xfffffffd000c7f19, 0xFFFFFFFC000C7F21, 0xFFFFFFFB000C7F29, 0xFFFFFFFA000C7F31 # 1012 - 1015 D7 S7 C4 + .quad 0xfffffff9000c7f39, 0x00000000000A7E81, 0xFFFFFFFF000C7F09, 0xFFFFFFFE000C7F11 # 1016 - 1019 D7 S7 C8 + .quad 0xfffffffd000c7f19, 0xFFFFFFFC000C7F21, 0xFFFFFFFB000C7F29, 0xFFFFFFFA000C7F31 # 1020 - 1023 D7 S7 C12 + .quad 0x0000000000000000, 0xFFFFFFFF00020088, 0xFFFFFFFE00020090, 0xFFFFFFFD00020098 # 1024 - 1027 D0 S0 C0 neq + .quad 0xfffffffc000200a0, 0xFFFFFFFB000200A8, 0xFFFFFFFA000200B0, 0xFFFFFFF9000200B8 # 1028 - 1031 D0 S0 C4 neq + .quad 0xfffffff8000200c0, 0xFFFFFFFF00020088, 0xFFFFFFFE00020090, 0xFFFFFFFD00020098 # 1032 - 1035 D0 S0 C8 neq + .quad 0xfffffffc000200a0, 0xFFFFFFFB000200A8, 0xFFFFFFFA000200B0, 0xFFFFFFF9000200B8 # 1036 - 1039 D0 S0 C12 neq + .quad 0x0000000000101000, 0xFFFFFFFF00121088, 0xFFFFFFFE00121090, 0xFFFFFFFD00121098 # 1040 - 1043 D0 S1 C0 neq + .quad 0xfffffffc001210a0, 0xFFFFFFFB001210A8, 0xFFFFFFFA001210B0, 0xFFFFFFF9001210B8 # 1044 - 1047 D0 S1 C4 neq + .quad 0xfffffff8001510c0, 0xFFFFFFFF00121088, 0xFFFFFFFE00121090, 0xFFFFFFFD00121098 # 1048 - 1051 D0 S1 C8 neq + .quad 0xfffffffc001210a0, 0xFFFFFFFB001210A8, 0xFFFFFFFA001210B0, 0xFFFFFFF9001210B8 # 1052 - 1055 D0 S1 C12 neq + .quad 0x0000000000102000, 0xFFFFFFFF00122088, 0xFFFFFFFE00122090, 0xFFFFFFFD00122098 # 1056 - 1059 D0 S2 C0 neq + .quad 0xfffffffc001220a0, 0xFFFFFFFB001220A8, 0xFFFFFFFA001220B0, 0xFFFFFFF9001520B8 # 1060 - 1063 D0 S2 C4 neq + .quad 0xfffffff8001520c0, 0xFFFFFFFF00122088, 0xFFFFFFFE00122090, 0xFFFFFFFD00122098 # 1064 - 1067 D0 S2 C8 neq + .quad 0xfffffffc001220a0, 0xFFFFFFFB001220A8, 0xFFFFFFFA001220B0, 0xFFFFFFF9001520B8 # 1068 - 1071 D0 S2 C12 neq + .quad 0x0000000000103000, 0xFFFFFFFF00123088, 0xFFFFFFFE00123090, 0xFFFFFFFD00123098 # 1072 - 1075 D0 S3 C0 neq + .quad 0xfffffffc001230a0, 0xFFFFFFFB001230A8, 0xFFFFFFFA001530B0, 0xFFFFFFF9001530B8 # 1076 - 1079 D0 S3 C4 neq + .quad 0xfffffff8001530c0, 0xFFFFFFFF00123088, 0xFFFFFFFE00123090, 0xFFFFFFFD00123098 # 1080 - 1083 D0 S3 C8 neq + .quad 0xfffffffc001230a0, 0xFFFFFFFB001230A8, 0xFFFFFFFA001530B0, 0xFFFFFFF9001530B8 # 1084 - 1087 D0 S3 C12 neq + .quad 0x0000000000104000, 0xFFFFFFFF00124088, 0xFFFFFFFE00124090, 0xFFFFFFFD00124098 # 1088 - 1091 D0 S4 C0 neq + .quad 0xfffffffc001240a0, 0xFFFFFFFB001540A8, 0xFFFFFFFA001540B0, 0xFFFFFFF9001540B8 # 1092 - 1095 D0 S4 C4 neq + .quad 0xfffffff8001540c0, 0xFFFFFFFF00124088, 0xFFFFFFFE00124090, 0xFFFFFFFD00124098 # 1096 - 1099 D0 S4 C8 neq + .quad 0xfffffffc001240a0, 0xFFFFFFFB001540A8, 0xFFFFFFFA001540B0, 0xFFFFFFF9001540B8 # 1100 - 1103 D0 S4 C12 neq + .quad 0x0000000000105000, 0xFFFFFFFF00125088, 0xFFFFFFFE00125090, 0xFFFFFFFD00125098 # 1104 - 1107 D0 S5 C0 neq + .quad 0xfffffffc001550a0, 0xFFFFFFFB001550A8, 0xFFFFFFFA001550B0, 0xFFFFFFF9001550B8 # 1108 - 1111 D0 S5 C4 neq + .quad 0xfffffff8001550c0, 0xFFFFFFFF00125088, 0xFFFFFFFE00125090, 0xFFFFFFFD00125098 # 1112 - 1115 D0 S5 C8 neq + .quad 0xfffffffc001550a0, 0xFFFFFFFB001550A8, 0xFFFFFFFA001550B0, 0xFFFFFFF9001550B8 # 1116 - 1119 D0 S5 C12 neq + .quad 0x0000000000106000, 0xFFFFFFFF00126088, 0xFFFFFFFE00126090, 0xFFFFFFFD00156098 # 1120 - 1123 D0 S6 C0 neq + .quad 0xfffffffc001560a0, 0xFFFFFFFB001560A8, 0xFFFFFFFA001560B0, 0xFFFFFFF9001560B8 # 1124 - 1127 D0 S6 C4 neq + .quad 0xfffffff8001560c0, 0xFFFFFFFF00126088, 0xFFFFFFFE00126090, 0xFFFFFFFD00156098 # 1128 - 1131 D0 S6 C8 neq + .quad 0xfffffffc001560a0, 0xFFFFFFFB001560A8, 0xFFFFFFFA001560B0, 0xFFFFFFF9001560B8 # 1132 - 1135 D0 S6 C12 neq + .quad 0x0000000000107000, 0xFFFFFFFF00127088, 0xFFFFFFFE00157090, 0xFFFFFFFD00157098 # 1136 - 1139 D0 S7 C0 neq + .quad 0xfffffffc001570a0, 0xFFFFFFFB001570A8, 0xFFFFFFFA001570B0, 0xFFFFFFF9001570B8 # 1140 - 1143 D0 S7 C4 neq + .quad 0xfffffff8001570c0, 0xFFFFFFFF00127088, 0xFFFFFFFE00157090, 0xFFFFFFFD00157098 # 1144 - 1147 D0 S7 C8 neq + .quad 0xfffffffc001570a0, 0xFFFFFFFB001570A8, 0xFFFFFFFA001570B0, 0xFFFFFFF9001570B8 # 1148 - 1151 D0 S7 C12 neq + .quad 0x0000000000100200, 0x0000000000120281, 0x0000000000120282, 0x0000000000120283 # 1152 - 1155 D1 S0 C0 neq + .quad 0x0000000000120284, 0x0000000000120285, 0x0000000000120286, 0x0000000000120287 # 1156 - 1159 D1 S0 C4 neq + .quad 0xffffffff0012030f, 0xFFFFFFFE00150317, 0xFFFFFFFD0015031F, 0xFFFFFFFC00150327 # 1160 - 1163 D1 S0 C8 neq + .quad 0xfffffffb0015032f, 0xFFFFFFFA00150337, 0xFFFFFFF90015033F, 0xFFFFFFF800150347 # 1164 - 1167 D1 S0 C12 neq + .quad 0x0000000000001200, 0x0000000000021281, 0x0000000000021282, 0x0000000000021283 # 1168 - 1171 D1 S1 C0 neq + .quad 0x0000000000021284, 0x0000000000021285, 0x0000000000021286, 0x00000000000A1287 # 1172 - 1175 D1 S1 C4 neq + .quad 0xffffffff000c130f, 0xFFFFFFFE000C1317, 0xFFFFFFFD000C131F, 0xFFFFFFFC000C1327 # 1176 - 1179 D1 S1 C8 neq + .quad 0xfffffffb000c132f, 0xFFFFFFFA000C1337, 0xFFFFFFF9000C133F, 0xFFFFFFF8000C1347 # 1180 - 1183 D1 S1 C12 neq + .quad 0x0000000000102200, 0x0000000000122281, 0x0000000000122282, 0x0000000000122283 # 1184 - 1187 D1 S2 C0 neq + .quad 0x0000000000122284, 0x0000000000122285, 0x00000000001A2286, 0x00000000001CA287 # 1188 - 1191 D1 S2 C4 neq + .quad 0xffffffff001ca30f, 0xFFFFFFFE001CA317, 0xFFFFFFFD001CA31F, 0xFFFFFFFC001CA327 # 1192 - 1195 D1 S2 C8 neq + .quad 0xfffffffb001ca32f, 0xFFFFFFFA001CA337, 0xFFFFFFF9001CA33F, 0xFFFFFFF8001FA347 # 1196 - 1199 D1 S2 C12 neq + .quad 0x0000000000103200, 0x0000000000123281, 0x0000000000123282, 0x0000000000123283 # 1200 - 1203 D1 S3 C0 neq + .quad 0x0000000000123284, 0x00000000001A3285, 0x00000000001CB286, 0x00000000001CB287 # 1204 - 1207 D1 S3 C4 neq + .quad 0xffffffff001cb30f, 0xFFFFFFFE001CB317, 0xFFFFFFFD001CB31F, 0xFFFFFFFC001CB327 # 1208 - 1211 D1 S3 C8 neq + .quad 0xfffffffb001cb32f, 0xFFFFFFFA001CB337, 0xFFFFFFF9001FB33F, 0xFFFFFFF8001FB347 # 1212 - 1215 D1 S3 C12 neq + .quad 0x0000000000104200, 0x0000000000124281, 0x0000000000124282, 0x0000000000124283 # 1216 - 1219 D1 S4 C0 neq + .quad 0x00000000001a4284, 0x00000000001CC285, 0x00000000001CC286, 0x00000000001CC287 # 1220 - 1223 D1 S4 C4 neq + .quad 0xffffffff001cc30f, 0xFFFFFFFE001CC317, 0xFFFFFFFD001CC31F, 0xFFFFFFFC001CC327 # 1224 - 1227 D1 S4 C8 neq + .quad 0xfffffffb001cc32f, 0xFFFFFFFA001FC337, 0xFFFFFFF9001FC33F, 0xFFFFFFF8001FC347 # 1228 - 1231 D1 S4 C12 neq + .quad 0x0000000000105200, 0x0000000000125281, 0x0000000000125282, 0x00000000001A5283 # 1232 - 1235 D1 S5 C0 neq + .quad 0x00000000001cd284, 0x00000000001CD285, 0x00000000001CD286, 0x00000000001CD287 # 1236 - 1239 D1 S5 C4 neq + .quad 0xffffffff001cd30f, 0xFFFFFFFE001CD317, 0xFFFFFFFD001CD31F, 0xFFFFFFFC001CD327 # 1240 - 1243 D1 S5 C8 neq + .quad 0xfffffffb001fd32f, 0xFFFFFFFA001FD337, 0xFFFFFFF9001FD33F, 0xFFFFFFF8001FD347 # 1244 - 1247 D1 S5 C12 neq + .quad 0x0000000000106200, 0x0000000000126281, 0x00000000001A6282, 0x00000000001CE283 # 1248 - 1251 D1 S6 C0 neq + .quad 0x00000000001ce284, 0x00000000001CE285, 0x00000000001CE286, 0x00000000001CE287 # 1252 - 1255 D1 S6 C4 neq + .quad 0xffffffff001ce30f, 0xFFFFFFFE001CE317, 0xFFFFFFFD001CE31F, 0xFFFFFFFC001FE327 # 1256 - 1259 D1 S6 C8 neq + .quad 0xfffffffb001fe32f, 0xFFFFFFFA001FE337, 0xFFFFFFF9001FE33F, 0xFFFFFFF8001FE347 # 1260 - 1263 D1 S6 C12 neq + .quad 0x0000000000107200, 0x00000000001A7281, 0x00000000001CF282, 0x00000000001CF283 # 1264 - 1267 D1 S7 C0 neq + .quad 0x00000000001cf284, 0x00000000001CF285, 0x00000000001CF286, 0x00000000001CF287 # 1268 - 1271 D1 S7 C4 neq + .quad 0xffffffff001cf30f, 0xFFFFFFFE001CF317, 0xFFFFFFFD001FF31F, 0xFFFFFFFC001FF327 # 1272 - 1275 D1 S7 C8 neq + .quad 0xfffffffb001ff32f, 0xFFFFFFFA001FF337, 0xFFFFFFF9001FF33F, 0xFFFFFFF8001FF347 # 1276 - 1279 D1 S7 C12 neq + .quad 0x0000000000100400, 0x0000000000120481, 0x0000000000120482, 0x0000000000120483 # 1280 - 1283 D2 S0 C0 neq + .quad 0x0000000000120484, 0x0000000000120485, 0x0000000000120486, 0xFFFFFFFF0012050E # 1284 - 1287 D2 S0 C4 neq + .quad 0xfffffffe00120516, 0xFFFFFFFD0015051E, 0xFFFFFFFC00150526, 0xFFFFFFFB0015052E # 1288 - 1291 D2 S0 C8 neq + .quad 0xfffffffa00150536, 0xFFFFFFF90015053E, 0xFFFFFFF800150546, 0xFFFFFFFF0012050E # 1292 - 1295 D2 S0 C12 neq + .quad 0x0000000000101400, 0x0000000000121481, 0x0000000000121482, 0x0000000000121483 # 1296 - 1299 D2 S1 C0 neq + .quad 0x0000000000121484, 0x0000000000121485, 0x0000000000121486, 0xFFFFFFFF0012150E # 1300 - 1303 D2 S1 C4 neq + .quad 0xfffffffe00151516, 0xFFFFFFFD0015151E, 0xFFFFFFFC00151526, 0xFFFFFFFB0015152E # 1304 - 1307 D2 S1 C8 neq + .quad 0xfffffffa00151536, 0xFFFFFFF90015153E, 0xFFFFFFF800151546, 0xFFFFFFFF0012150E # 1308 - 1311 D2 S1 C12 neq + .quad 0x0000000000002400, 0x0000000000022481, 0x0000000000022482, 0x0000000000022483 # 1312 - 1315 D2 S2 C0 neq + .quad 0x0000000000022484, 0x0000000000022485, 0x00000000000A2486, 0xFFFFFFFF000C250E # 1316 - 1319 D2 S2 C4 neq + .quad 0xfffffffe000c2516, 0xFFFFFFFD000C251E, 0xFFFFFFFC000C2526, 0xFFFFFFFB000C252E # 1320 - 1323 D2 S2 C8 neq + .quad 0xfffffffa000c2536, 0xFFFFFFF9000C253E, 0xFFFFFFF8000C2546, 0xFFFFFFFF000C250E # 1324 - 1327 D2 S2 C12 neq + .quad 0x0000000000103400, 0x0000000000123481, 0x0000000000123482, 0x0000000000123483 # 1328 - 1331 D2 S3 C0 neq + .quad 0x0000000000123484, 0x00000000001A3485, 0x00000000001CB486, 0xFFFFFFFF001CB50E # 1332 - 1335 D2 S3 C4 neq + .quad 0xfffffffe001cb516, 0xFFFFFFFD001CB51E, 0xFFFFFFFC001CB526, 0xFFFFFFFB001CB52E # 1336 - 1339 D2 S3 C8 neq + .quad 0xfffffffa001cb536, 0xFFFFFFF9001CB53E, 0xFFFFFFF8001FB546, 0xFFFFFFFF001CB50E # 1340 - 1343 D2 S3 C12 neq + .quad 0x0000000000104400, 0x0000000000124481, 0x0000000000124482, 0x0000000000124483 # 1344 - 1347 D2 S4 C0 neq + .quad 0x00000000001a4484, 0x00000000001CC485, 0x00000000001CC486, 0xFFFFFFFF001CC50E # 1348 - 1351 D2 S4 C4 neq + .quad 0xfffffffe001cc516, 0xFFFFFFFD001CC51E, 0xFFFFFFFC001CC526, 0xFFFFFFFB001CC52E # 1352 - 1355 D2 S4 C8 neq + .quad 0xfffffffa001cc536, 0xFFFFFFF9001FC53E, 0xFFFFFFF8001FC546, 0xFFFFFFFF001CC50E # 1356 - 1359 D2 S4 C12 neq + .quad 0x0000000000105400, 0x0000000000125481, 0x0000000000125482, 0x00000000001A5483 # 1360 - 1363 D2 S5 C0 neq + .quad 0x00000000001cd484, 0x00000000001CD485, 0x00000000001CD486, 0xFFFFFFFF001CD50E # 1364 - 1367 D2 S5 C4 neq + .quad 0xfffffffe001cd516, 0xFFFFFFFD001CD51E, 0xFFFFFFFC001CD526, 0xFFFFFFFB001CD52E # 1368 - 1371 D2 S5 C8 neq + .quad 0xfffffffa001fd536, 0xFFFFFFF9001FD53E, 0xFFFFFFF8001FD546, 0xFFFFFFFF001CD50E # 1372 - 1375 D2 S5 C12 neq + .quad 0x0000000000106400, 0x0000000000126481, 0x00000000001A6482, 0x00000000001CE483 # 1376 - 1379 D2 S6 C0 neq + .quad 0x00000000001ce484, 0x00000000001CE485, 0x00000000001CE486, 0xFFFFFFFF001CE50E # 1380 - 1383 D2 S6 C4 neq + .quad 0xfffffffe001ce516, 0xFFFFFFFD001CE51E, 0xFFFFFFFC001CE526, 0xFFFFFFFB001FE52E # 1384 - 1387 D2 S6 C8 neq + .quad 0xfffffffa001fe536, 0xFFFFFFF9001FE53E, 0xFFFFFFF8001FE546, 0xFFFFFFFF001CE50E # 1388 - 1391 D2 S6 C12 neq + .quad 0x0000000000107400, 0x00000000001A7481, 0x00000000001CF482, 0x00000000001CF483 # 1392 - 1395 D2 S7 C0 neq + .quad 0x00000000001cf484, 0x00000000001CF485, 0x00000000001CF486, 0xFFFFFFFF001CF50E # 1396 - 1399 D2 S7 C4 neq + .quad 0xfffffffe001cf516, 0xFFFFFFFD001CF51E, 0xFFFFFFFC001FF526, 0xFFFFFFFB001FF52E # 1400 - 1403 D2 S7 C8 neq + .quad 0xfffffffa001ff536, 0xFFFFFFF9001FF53E, 0xFFFFFFF8001FF546, 0xFFFFFFFF001CF50E # 1404 - 1407 D2 S7 C12 neq + .quad 0x0000000000100600, 0x0000000000120681, 0x0000000000120682, 0x0000000000120683 # 1408 - 1411 D3 S0 C0 neq + .quad 0x0000000000120684, 0x0000000000120685, 0xFFFFFFFF0012070D, 0xFFFFFFFE00120715 # 1412 - 1415 D3 S0 C4 neq + .quad 0xfffffffd0012071d, 0xFFFFFFFC00150725, 0xFFFFFFFB0015072D, 0xFFFFFFFA00150735 # 1416 - 1419 D3 S0 C8 neq + .quad 0xfffffff90015073d, 0xFFFFFFF800150745, 0xFFFFFFFF0012070D, 0xFFFFFFFE00120715 # 1420 - 1423 D3 S0 C12 neq + .quad 0x0000000000101600, 0x0000000000121681, 0x0000000000121682, 0x0000000000121683 # 1424 - 1427 D3 S1 C0 neq + .quad 0x0000000000121684, 0x0000000000121685, 0xFFFFFFFF0012170D, 0xFFFFFFFE00121715 # 1428 - 1431 D3 S1 C4 neq + .quad 0xfffffffd0015171d, 0xFFFFFFFC00151725, 0xFFFFFFFB0015172D, 0xFFFFFFFA00151735 # 1432 - 1435 D3 S1 C8 neq + .quad 0xfffffff90015173d, 0xFFFFFFF800151745, 0xFFFFFFFF0012170D, 0xFFFFFFFE00121715 # 1436 - 1439 D3 S1 C12 neq + .quad 0x0000000000102600, 0x0000000000122681, 0x0000000000122682, 0x0000000000122683 # 1440 - 1443 D3 S2 C0 neq + .quad 0x0000000000122684, 0x0000000000122685, 0xFFFFFFFF0012270D, 0xFFFFFFFE00152715 # 1444 - 1447 D3 S2 C4 neq + .quad 0xfffffffd0015271d, 0xFFFFFFFC00152725, 0xFFFFFFFB0015272D, 0xFFFFFFFA00152735 # 1448 - 1451 D3 S2 C8 neq + .quad 0xfffffff90015273d, 0xFFFFFFF800152745, 0xFFFFFFFF0012270D, 0xFFFFFFFE00152715 # 1452 - 1455 D3 S2 C12 neq + .quad 0x0000000000003600, 0x0000000000023681, 0x0000000000023682, 0x0000000000023683 # 1456 - 1459 D3 S3 C0 neq + .quad 0x0000000000023684, 0x00000000000A3685, 0xFFFFFFFF000C370D, 0xFFFFFFFE000C3715 # 1460 - 1463 D3 S3 C4 neq + .quad 0xfffffffd000c371d, 0xFFFFFFFC000C3725, 0xFFFFFFFB000C372D, 0xFFFFFFFA000C3735 # 1464 - 1467 D3 S3 C8 neq + .quad 0xfffffff9000c373d, 0xFFFFFFF8000C3745, 0xFFFFFFFF000C370D, 0xFFFFFFFE000C3715 # 1468 - 1471 D3 S3 C12 neq + .quad 0x0000000000104600, 0x0000000000124681, 0x0000000000124682, 0x0000000000124683 # 1472 - 1475 D3 S4 C0 neq + .quad 0x00000000001a4684, 0x00000000001CC685, 0xFFFFFFFF001CC70D, 0xFFFFFFFE001CC715 # 1476 - 1479 D3 S4 C4 neq + .quad 0xfffffffd001cc71d, 0xFFFFFFFC001CC725, 0xFFFFFFFB001CC72D, 0xFFFFFFFA001CC735 # 1480 - 1483 D3 S4 C8 neq + .quad 0xfffffff9001cc73d, 0xFFFFFFF8001FC745, 0xFFFFFFFF001CC70D, 0xFFFFFFFE001CC715 # 1484 - 1487 D3 S4 C12 neq + .quad 0x0000000000105600, 0x0000000000125681, 0x0000000000125682, 0x00000000001A5683 # 1488 - 1491 D3 S5 C0 neq + .quad 0x00000000001cd684, 0x00000000001CD685, 0xFFFFFFFF001CD70D, 0xFFFFFFFE001CD715 # 1492 - 1495 D3 S5 C4 neq + .quad 0xfffffffd001cd71d, 0xFFFFFFFC001CD725, 0xFFFFFFFB001CD72D, 0xFFFFFFFA001CD735 # 1496 - 1499 D3 S5 C8 neq + .quad 0xfffffff9001fd73d, 0xFFFFFFF8001FD745, 0xFFFFFFFF001CD70D, 0xFFFFFFFE001CD715 # 1500 - 1503 D3 S5 C12 neq + .quad 0x0000000000106600, 0x0000000000126681, 0x00000000001A6682, 0x00000000001CE683 # 1504 - 1507 D3 S6 C0 neq + .quad 0x00000000001ce684, 0x00000000001CE685, 0xFFFFFFFF001CE70D, 0xFFFFFFFE001CE715 # 1508 - 1511 D3 S6 C4 neq + .quad 0xfffffffd001ce71d, 0xFFFFFFFC001CE725, 0xFFFFFFFB001CE72D, 0xFFFFFFFA001FE735 # 1512 - 1515 D3 S6 C8 neq + .quad 0xfffffff9001fe73d, 0xFFFFFFF8001FE745, 0xFFFFFFFF001CE70D, 0xFFFFFFFE001CE715 # 1516 - 1519 D3 S6 C12 neq + .quad 0x0000000000107600, 0x00000000001A7681, 0x00000000001CF682, 0x00000000001CF683 # 1520 - 1523 D3 S7 C0 neq + .quad 0x00000000001cf684, 0x00000000001CF685, 0xFFFFFFFF001CF70D, 0xFFFFFFFE001CF715 # 1524 - 1527 D3 S7 C4 neq + .quad 0xfffffffd001cf71d, 0xFFFFFFFC001CF725, 0xFFFFFFFB001FF72D, 0xFFFFFFFA001FF735 # 1528 - 1531 D3 S7 C8 neq + .quad 0xfffffff9001ff73d, 0xFFFFFFF8001FF745, 0xFFFFFFFF001CF70D, 0xFFFFFFFE001CF715 # 1532 - 1535 D3 S7 C12 neq + .quad 0x0000000000100800, 0x0000000000120881, 0x0000000000120882, 0x0000000000120883 # 1536 - 1539 D4 S0 C0 neq + .quad 0x0000000000120884, 0xFFFFFFFF0012090C, 0xFFFFFFFE00120914, 0xFFFFFFFD0012091C # 1540 - 1543 D4 S0 C4 neq + .quad 0xfffffffc00120924, 0xFFFFFFFB0015092C, 0xFFFFFFFA00150934, 0xFFFFFFF90015093C # 1544 - 1547 D4 S0 C8 neq + .quad 0xfffffff800150944, 0xFFFFFFFF0012090C, 0xFFFFFFFE00120914, 0xFFFFFFFD0012091C # 1548 - 1551 D4 S0 C12 neq + .quad 0x0000000000101800, 0x0000000000121881, 0x0000000000121882, 0x0000000000121883 # 1552 - 1555 D4 S1 C0 neq + .quad 0x0000000000121884, 0xFFFFFFFF0012190C, 0xFFFFFFFE00121914, 0xFFFFFFFD0012191C # 1556 - 1559 D4 S1 C4 neq + .quad 0xfffffffc00151924, 0xFFFFFFFB0015192C, 0xFFFFFFFA00151934, 0xFFFFFFF90015193C # 1560 - 1563 D4 S1 C8 neq + .quad 0xfffffff800151944, 0xFFFFFFFF0012190C, 0xFFFFFFFE00121914, 0xFFFFFFFD0012191C # 1564 - 1567 D4 S1 C12 neq + .quad 0x0000000000102800, 0x0000000000122881, 0x0000000000122882, 0x0000000000122883 # 1568 - 1571 D4 S2 C0 neq + .quad 0x0000000000122884, 0xFFFFFFFF0012290C, 0xFFFFFFFE00122914, 0xFFFFFFFD0015291C # 1572 - 1575 D4 S2 C4 neq + .quad 0xfffffffc00152924, 0xFFFFFFFB0015292C, 0xFFFFFFFA00152934, 0xFFFFFFF90015293C # 1576 - 1579 D4 S2 C8 neq + .quad 0xfffffff800152944, 0xFFFFFFFF0012290C, 0xFFFFFFFE00122914, 0xFFFFFFFD0015291C # 1580 - 1583 D4 S2 C12 neq + .quad 0x0000000000103800, 0x0000000000123881, 0x0000000000123882, 0x0000000000123883 # 1584 - 1587 D4 S3 C0 neq + .quad 0x0000000000123884, 0xFFFFFFFF0012390C, 0xFFFFFFFE00153914, 0xFFFFFFFD0015391C # 1588 - 1591 D4 S3 C4 neq + .quad 0xfffffffc00153924, 0xFFFFFFFB0015392C, 0xFFFFFFFA00153934, 0xFFFFFFF90015393C # 1592 - 1595 D4 S3 C8 neq + .quad 0xfffffff800153944, 0xFFFFFFFF0012390C, 0xFFFFFFFE00153914, 0xFFFFFFFD0015391C # 1596 - 1599 D4 S3 C12 neq + .quad 0x0000000000004800, 0x0000000000024881, 0x0000000000024882, 0x0000000000024883 # 1600 - 1603 D4 S4 C0 neq + .quad 0x00000000000a4884, 0xFFFFFFFF000C490C, 0xFFFFFFFE000C4914, 0xFFFFFFFD000C491C # 1604 - 1607 D4 S4 C4 neq + .quad 0xfffffffc000c4924, 0xFFFFFFFB000C492C, 0xFFFFFFFA000C4934, 0xFFFFFFF9000C493C # 1608 - 1611 D4 S4 C8 neq + .quad 0xfffffff8000c4944, 0xFFFFFFFF000C490C, 0xFFFFFFFE000C4914, 0xFFFFFFFD000C491C # 1612 - 1615 D4 S4 C12 neq + .quad 0x0000000000105800, 0x0000000000125881, 0x0000000000125882, 0x00000000001A5883 # 1616 - 1619 D4 S5 C0 neq + .quad 0x00000000001cd884, 0xFFFFFFFF001CD90C, 0xFFFFFFFE001CD914, 0xFFFFFFFD001CD91C # 1620 - 1623 D4 S5 C4 neq + .quad 0xfffffffc001cd924, 0xFFFFFFFB001CD92C, 0xFFFFFFFA001CD934, 0xFFFFFFF9001CD93C # 1624 - 1627 D4 S5 C8 neq + .quad 0xfffffff8001fd944, 0xFFFFFFFF001CD90C, 0xFFFFFFFE001CD914, 0xFFFFFFFD001CD91C # 1628 - 1631 D4 S5 C12 neq + .quad 0x0000000000106800, 0x0000000000126881, 0x00000000001A6882, 0x00000000001CE883 # 1632 - 1635 D4 S6 C0 neq + .quad 0x00000000001ce884, 0xFFFFFFFF001CE90C, 0xFFFFFFFE001CE914, 0xFFFFFFFD001CE91C # 1636 - 1639 D4 S6 C4 neq + .quad 0xfffffffc001ce924, 0xFFFFFFFB001CE92C, 0xFFFFFFFA001CE934, 0xFFFFFFF9001FE93C # 1640 - 1643 D4 S6 C8 neq + .quad 0xfffffff8001fe944, 0xFFFFFFFF001CE90C, 0xFFFFFFFE001CE914, 0xFFFFFFFD001CE91C # 1644 - 1647 D4 S6 C12 neq + .quad 0x0000000000107800, 0x00000000001A7881, 0x00000000001CF882, 0x00000000001CF883 # 1648 - 1651 D4 S7 C0 neq + .quad 0x00000000001cf884, 0xFFFFFFFF001CF90C, 0xFFFFFFFE001CF914, 0xFFFFFFFD001CF91C # 1652 - 1655 D4 S7 C4 neq + .quad 0xfffffffc001cf924, 0xFFFFFFFB001CF92C, 0xFFFFFFFA001FF934, 0xFFFFFFF9001FF93C # 1656 - 1659 D4 S7 C8 neq + .quad 0xfffffff8001ff944, 0xFFFFFFFF001CF90C, 0xFFFFFFFE001CF914, 0xFFFFFFFD001CF91C # 1660 - 1663 D4 S7 C12 neq + .quad 0x0000000000100a00, 0x0000000000120A81, 0x0000000000120A82, 0x0000000000120A83 # 1664 - 1667 D5 S0 C0 neq + .quad 0xffffffff00120b0b, 0xFFFFFFFE00120B13, 0xFFFFFFFD00120B1B, 0xFFFFFFFC00120B23 # 1668 - 1671 D5 S0 C4 neq + .quad 0xfffffffb00120b2b, 0xFFFFFFFA00150B33, 0xFFFFFFF900150B3B, 0xFFFFFFF800150B43 # 1672 - 1675 D5 S0 C8 neq + .quad 0xffffffff00120b0b, 0xFFFFFFFE00120B13, 0xFFFFFFFD00120B1B, 0xFFFFFFFC00120B23 # 1676 - 1679 D5 S0 C12 neq + .quad 0x0000000000101a00, 0x0000000000121A81, 0x0000000000121A82, 0x0000000000121A83 # 1680 - 1683 D5 S1 C0 neq + .quad 0xffffffff00121b0b, 0xFFFFFFFE00121B13, 0xFFFFFFFD00121B1B, 0xFFFFFFFC00121B23 # 1684 - 1687 D5 S1 C4 neq + .quad 0xfffffffb00151b2b, 0xFFFFFFFA00151B33, 0xFFFFFFF900151B3B, 0xFFFFFFF800151B43 # 1688 - 1691 D5 S1 C8 neq + .quad 0xffffffff00121b0b, 0xFFFFFFFE00121B13, 0xFFFFFFFD00121B1B, 0xFFFFFFFC00121B23 # 1692 - 1695 D5 S1 C12 neq + .quad 0x0000000000102a00, 0x0000000000122A81, 0x0000000000122A82, 0x0000000000122A83 # 1696 - 1699 D5 S2 C0 neq + .quad 0xffffffff00122b0b, 0xFFFFFFFE00122B13, 0xFFFFFFFD00122B1B, 0xFFFFFFFC00152B23 # 1700 - 1703 D5 S2 C4 neq + .quad 0xfffffffb00152b2b, 0xFFFFFFFA00152B33, 0xFFFFFFF900152B3B, 0xFFFFFFF800152B43 # 1704 - 1707 D5 S2 C8 neq + .quad 0xffffffff00122b0b, 0xFFFFFFFE00122B13, 0xFFFFFFFD00122B1B, 0xFFFFFFFC00152B23 # 1708 - 1711 D5 S2 C12 neq + .quad 0x0000000000103a00, 0x0000000000123A81, 0x0000000000123A82, 0x0000000000123A83 # 1712 - 1715 D5 S3 C0 neq + .quad 0xffffffff00123b0b, 0xFFFFFFFE00123B13, 0xFFFFFFFD00153B1B, 0xFFFFFFFC00153B23 # 1716 - 1719 D5 S3 C4 neq + .quad 0xfffffffb00153b2b, 0xFFFFFFFA00153B33, 0xFFFFFFF900153B3B, 0xFFFFFFF800153B43 # 1720 - 1723 D5 S3 C8 neq + .quad 0xffffffff00123b0b, 0xFFFFFFFE00123B13, 0xFFFFFFFD00153B1B, 0xFFFFFFFC00153B23 # 1724 - 1727 D5 S3 C12 neq + .quad 0x0000000000104a00, 0x0000000000124A81, 0x0000000000124A82, 0x0000000000124A83 # 1728 - 1731 D5 S4 C0 neq + .quad 0xffffffff00124b0b, 0xFFFFFFFE00154B13, 0xFFFFFFFD00154B1B, 0xFFFFFFFC00154B23 # 1732 - 1735 D5 S4 C4 neq + .quad 0xfffffffb00154b2b, 0xFFFFFFFA00154B33, 0xFFFFFFF900154B3B, 0xFFFFFFF800154B43 # 1736 - 1739 D5 S4 C8 neq + .quad 0xffffffff00124b0b, 0xFFFFFFFE00154B13, 0xFFFFFFFD00154B1B, 0xFFFFFFFC00154B23 # 1740 - 1743 D5 S4 C12 neq + .quad 0x0000000000005a00, 0x0000000000025A81, 0x0000000000025A82, 0x00000000000A5A83 # 1744 - 1747 D5 S5 C0 neq + .quad 0xffffffff000c5b0b, 0xFFFFFFFE000C5B13, 0xFFFFFFFD000C5B1B, 0xFFFFFFFC000C5B23 # 1748 - 1751 D5 S5 C4 neq + .quad 0xfffffffb000c5b2b, 0xFFFFFFFA000C5B33, 0xFFFFFFF9000C5B3B, 0xFFFFFFF8000C5B43 # 1752 - 1755 D5 S5 C8 neq + .quad 0xffffffff000c5b0b, 0xFFFFFFFE000C5B13, 0xFFFFFFFD000C5B1B, 0xFFFFFFFC000C5B23 # 1756 - 1759 D5 S5 C12 neq + .quad 0x0000000000106a00, 0x0000000000126A81, 0x00000000001A6A82, 0x00000000001CEA83 # 1760 - 1763 D5 S6 C0 neq + .quad 0xffffffff001ceb0b, 0xFFFFFFFE001CEB13, 0xFFFFFFFD001CEB1B, 0xFFFFFFFC001CEB23 # 1764 - 1767 D5 S6 C4 neq + .quad 0xfffffffb001ceb2b, 0xFFFFFFFA001CEB33, 0xFFFFFFF9001CEB3B, 0xFFFFFFF8001FEB43 # 1768 - 1771 D5 S6 C8 neq + .quad 0xffffffff001ceb0b, 0xFFFFFFFE001CEB13, 0xFFFFFFFD001CEB1B, 0xFFFFFFFC001CEB23 # 1772 - 1775 D5 S6 C12 neq + .quad 0x0000000000107a00, 0x00000000001A7A81, 0x00000000001CFA82, 0x00000000001CFA83 # 1776 - 1779 D5 S7 C0 neq + .quad 0xffffffff001cfb0b, 0xFFFFFFFE001CFB13, 0xFFFFFFFD001CFB1B, 0xFFFFFFFC001CFB23 # 1780 - 1783 D5 S7 C4 neq + .quad 0xfffffffb001cfb2b, 0xFFFFFFFA001CFB33, 0xFFFFFFF9001FFB3B, 0xFFFFFFF8001FFB43 # 1784 - 1787 D5 S7 C8 neq + .quad 0xffffffff001cfb0b, 0xFFFFFFFE001CFB13, 0xFFFFFFFD001CFB1B, 0xFFFFFFFC001CFB23 # 1788 - 1791 D5 S7 C12 neq + .quad 0x0000000000100c00, 0x0000000000120C81, 0x0000000000120C82, 0xFFFFFFFF00120D0A # 1792 - 1795 D6 S0 C0 neq + .quad 0xfffffffe00120d12, 0xFFFFFFFD00120D1A, 0xFFFFFFFC00120D22, 0xFFFFFFFB00120D2A # 1796 - 1799 D6 S0 C4 neq + .quad 0xfffffffa00120d32, 0xFFFFFFF900150D3A, 0xFFFFFFF800150D42, 0xFFFFFFFF00120D0A # 1800 - 1803 D6 S0 C8 neq + .quad 0xfffffffe00120d12, 0xFFFFFFFD00120D1A, 0xFFFFFFFC00120D22, 0xFFFFFFFB00120D2A # 1804 - 1807 D6 S0 C12 neq + .quad 0x0000000000101c00, 0x0000000000121C81, 0x0000000000121C82, 0xFFFFFFFF00121D0A # 1808 - 1811 D6 S1 C0 neq + .quad 0xfffffffe00121d12, 0xFFFFFFFD00121D1A, 0xFFFFFFFC00121D22, 0xFFFFFFFB00121D2A # 1812 - 1815 D6 S1 C4 neq + .quad 0xfffffffa00151d32, 0xFFFFFFF900151D3A, 0xFFFFFFF800151D42, 0xFFFFFFFF00121D0A # 1816 - 1819 D6 S1 C8 neq + .quad 0xfffffffe00121d12, 0xFFFFFFFD00121D1A, 0xFFFFFFFC00121D22, 0xFFFFFFFB00121D2A # 1820 - 1823 D6 S1 C12 neq + .quad 0x0000000000102c00, 0x0000000000122C81, 0x0000000000122C82, 0xFFFFFFFF00122D0A # 1824 - 1827 D6 S2 C0 neq + .quad 0xfffffffe00122d12, 0xFFFFFFFD00122D1A, 0xFFFFFFFC00122D22, 0xFFFFFFFB00152D2A # 1828 - 1831 D6 S2 C4 neq + .quad 0xfffffffa00152d32, 0xFFFFFFF900152D3A, 0xFFFFFFF800152D42, 0xFFFFFFFF00122D0A # 1832 - 1835 D6 S2 C8 neq + .quad 0xfffffffe00122d12, 0xFFFFFFFD00122D1A, 0xFFFFFFFC00122D22, 0xFFFFFFFB00152D2A # 1836 - 1839 D6 S2 C12 neq + .quad 0x0000000000103c00, 0x0000000000123C81, 0x0000000000123C82, 0xFFFFFFFF00123D0A # 1840 - 1843 D6 S3 C0 neq + .quad 0xfffffffe00123d12, 0xFFFFFFFD00123D1A, 0xFFFFFFFC00153D22, 0xFFFFFFFB00153D2A # 1844 - 1847 D6 S3 C4 neq + .quad 0xfffffffa00153d32, 0xFFFFFFF900153D3A, 0xFFFFFFF800153D42, 0xFFFFFFFF00123D0A # 1848 - 1851 D6 S3 C8 neq + .quad 0xfffffffe00123d12, 0xFFFFFFFD00123D1A, 0xFFFFFFFC00153D22, 0xFFFFFFFB00153D2A # 1852 - 1855 D6 S3 C12 neq + .quad 0x0000000000104c00, 0x0000000000124C81, 0x0000000000124C82, 0xFFFFFFFF00124D0A # 1856 - 1859 D6 S4 C0 neq + .quad 0xfffffffe00124d12, 0xFFFFFFFD00154D1A, 0xFFFFFFFC00154D22, 0xFFFFFFFB00154D2A # 1860 - 1863 D6 S4 C4 neq + .quad 0xfffffffa00154d32, 0xFFFFFFF900154D3A, 0xFFFFFFF800154D42, 0xFFFFFFFF00124D0A # 1864 - 1867 D6 S4 C8 neq + .quad 0xfffffffe00124d12, 0xFFFFFFFD00154D1A, 0xFFFFFFFC00154D22, 0xFFFFFFFB00154D2A # 1868 - 1871 D6 S4 C12 neq + .quad 0x0000000000105c00, 0x0000000000125C81, 0x0000000000125C82, 0xFFFFFFFF00125D0A # 1872 - 1875 D6 S5 C0 neq + .quad 0xfffffffe00155d12, 0xFFFFFFFD00155D1A, 0xFFFFFFFC00155D22, 0xFFFFFFFB00155D2A # 1876 - 1879 D6 S5 C4 neq + .quad 0xfffffffa00155d32, 0xFFFFFFF900155D3A, 0xFFFFFFF800155D42, 0xFFFFFFFF00125D0A # 1880 - 1883 D6 S5 C8 neq + .quad 0xfffffffe00155d12, 0xFFFFFFFD00155D1A, 0xFFFFFFFC00155D22, 0xFFFFFFFB00155D2A # 1884 - 1887 D6 S5 C12 neq + .quad 0x0000000000006c00, 0x0000000000026C81, 0x00000000000A6C82, 0xFFFFFFFF000C6D0A # 1888 - 1891 D6 S6 C0 neq + .quad 0xfffffffe000c6d12, 0xFFFFFFFD000C6D1A, 0xFFFFFFFC000C6D22, 0xFFFFFFFB000C6D2A # 1892 - 1895 D6 S6 C4 neq + .quad 0xfffffffa000c6d32, 0xFFFFFFF9000C6D3A, 0xFFFFFFF8000C6D42, 0xFFFFFFFF000C6D0A # 1896 - 1899 D6 S6 C8 neq + .quad 0xfffffffe000c6d12, 0xFFFFFFFD000C6D1A, 0xFFFFFFFC000C6D22, 0xFFFFFFFB000C6D2A # 1900 - 1903 D6 S6 C12 neq + .quad 0x0000000000107c00, 0x00000000001A7C81, 0x00000000001CFC82, 0xFFFFFFFF001CFD0A # 1904 - 1907 D6 S7 C0 neq + .quad 0xfffffffe001cfd12, 0xFFFFFFFD001CFD1A, 0xFFFFFFFC001CFD22, 0xFFFFFFFB001CFD2A # 1908 - 1911 D6 S7 C4 neq + .quad 0xfffffffa001cfd32, 0xFFFFFFF9001CFD3A, 0xFFFFFFF8001FFD42, 0xFFFFFFFF001CFD0A # 1912 - 1915 D6 S7 C8 neq + .quad 0xfffffffe001cfd12, 0xFFFFFFFD001CFD1A, 0xFFFFFFFC001CFD22, 0xFFFFFFFB001CFD2A # 1916 - 1919 D6 S7 C12 neq + .quad 0x0000000000100e00, 0x0000000000120E81, 0xFFFFFFFF00120F09, 0xFFFFFFFE00120F11 # 1920 - 1923 D7 S0 C0 neq + .quad 0xfffffffd00120f19, 0xFFFFFFFC00120F21, 0xFFFFFFFB00120F29, 0xFFFFFFFA00120F31 # 1924 - 1927 D7 S0 C4 neq + .quad 0xfffffff900120f39, 0xFFFFFFF800150F41, 0xFFFFFFFF00120F09, 0xFFFFFFFE00120F11 # 1928 - 1931 D7 S0 C8 neq + .quad 0xfffffffd00120f19, 0xFFFFFFFC00120F21, 0xFFFFFFFB00120F29, 0xFFFFFFFA00120F31 # 1932 - 1935 D7 S0 C12 neq + .quad 0x0000000000101e00, 0x0000000000121E81, 0xFFFFFFFF00121F09, 0xFFFFFFFE00121F11 # 1936 - 1939 D7 S1 C0 neq + .quad 0xfffffffd00121f19, 0xFFFFFFFC00121F21, 0xFFFFFFFB00121F29, 0xFFFFFFFA00121F31 # 1940 - 1943 D7 S1 C4 neq + .quad 0xfffffff900151f39, 0xFFFFFFF800151F41, 0xFFFFFFFF00121F09, 0xFFFFFFFE00121F11 # 1944 - 1947 D7 S1 C8 neq + .quad 0xfffffffd00121f19, 0xFFFFFFFC00121F21, 0xFFFFFFFB00121F29, 0xFFFFFFFA00121F31 # 1948 - 1951 D7 S1 C12 neq + .quad 0x0000000000102e00, 0x0000000000122E81, 0xFFFFFFFF00122F09, 0xFFFFFFFE00122F11 # 1952 - 1955 D7 S2 C0 neq + .quad 0xfffffffd00122f19, 0xFFFFFFFC00122F21, 0xFFFFFFFB00122F29, 0xFFFFFFFA00152F31 # 1956 - 1959 D7 S2 C4 neq + .quad 0xfffffff900152f39, 0xFFFFFFF800152F41, 0xFFFFFFFF00122F09, 0xFFFFFFFE00122F11 # 1960 - 1963 D7 S2 C8 neq + .quad 0xfffffffd00122f19, 0xFFFFFFFC00122F21, 0xFFFFFFFB00122F29, 0xFFFFFFFA00152F31 # 1964 - 1967 D7 S2 C12 neq + .quad 0x0000000000103e00, 0x0000000000123E81, 0xFFFFFFFF00123F09, 0xFFFFFFFE00123F11 # 1968 - 1971 D7 S3 C0 neq + .quad 0xfffffffd00123f19, 0xFFFFFFFC00123F21, 0xFFFFFFFB00153F29, 0xFFFFFFFA00153F31 # 1972 - 1975 D7 S3 C4 neq + .quad 0xfffffff900153f39, 0xFFFFFFF800153F41, 0xFFFFFFFF00123F09, 0xFFFFFFFE00123F11 # 1976 - 1979 D7 S3 C8 neq + .quad 0xfffffffd00123f19, 0xFFFFFFFC00123F21, 0xFFFFFFFB00153F29, 0xFFFFFFFA00153F31 # 1980 - 1983 D7 S3 C12 neq + .quad 0x0000000000104e00, 0x0000000000124E81, 0xFFFFFFFF00124F09, 0xFFFFFFFE00124F11 # 1984 - 1987 D7 S4 C0 neq + .quad 0xfffffffd00124f19, 0xFFFFFFFC00154F21, 0xFFFFFFFB00154F29, 0xFFFFFFFA00154F31 # 1988 - 1991 D7 S4 C4 neq + .quad 0xfffffff900154f39, 0xFFFFFFF800154F41, 0xFFFFFFFF00124F09, 0xFFFFFFFE00124F11 # 1992 - 1995 D7 S4 C8 neq + .quad 0xfffffffd00124f19, 0xFFFFFFFC00154F21, 0xFFFFFFFB00154F29, 0xFFFFFFFA00154F31 # 1996 - 1999 D7 S4 C12 neq + .quad 0x0000000000105e00, 0x0000000000125E81, 0xFFFFFFFF00125F09, 0xFFFFFFFE00125F11 # 2000 - 2003 D7 S5 C0 neq + .quad 0xfffffffd00155f19, 0xFFFFFFFC00155F21, 0xFFFFFFFB00155F29, 0xFFFFFFFA00155F31 # 2004 - 2007 D7 S5 C4 neq + .quad 0xfffffff900155f39, 0xFFFFFFF800155F41, 0xFFFFFFFF00125F09, 0xFFFFFFFE00125F11 # 2008 - 2011 D7 S5 C8 neq + .quad 0xfffffffd00155f19, 0xFFFFFFFC00155F21, 0xFFFFFFFB00155F29, 0xFFFFFFFA00155F31 # 2012 - 2015 D7 S5 C12 neq + .quad 0x0000000000106e00, 0x0000000000126E81, 0xFFFFFFFF00126F09, 0xFFFFFFFE00156F11 # 2016 - 2019 D7 S6 C0 neq + .quad 0xfffffffd00156f19, 0xFFFFFFFC00156F21, 0xFFFFFFFB00156F29, 0xFFFFFFFA00156F31 # 2020 - 2023 D7 S6 C4 neq + .quad 0xfffffff900156f39, 0xFFFFFFF800156F41, 0xFFFFFFFF00126F09, 0xFFFFFFFE00156F11 # 2024 - 2027 D7 S6 C8 neq + .quad 0xfffffffd00156f19, 0xFFFFFFFC00156F21, 0xFFFFFFFB00156F29, 0xFFFFFFFA00156F31 # 2028 - 2031 D7 S6 C12 neq + .quad 0x0000000000007e00, 0x00000000000A7E81, 0xFFFFFFFF000C7F09, 0xFFFFFFFE000C7F11 # 2032 - 2035 D7 S7 C0 neq + .quad 0xfffffffd000c7f19, 0xFFFFFFFC000C7F21, 0xFFFFFFFB000C7F29, 0xFFFFFFFA000C7F31 # 2036 - 2039 D7 S7 C4 neq + .quad 0xfffffff9000c7f39, 0xFFFFFFF8000C7F41, 0xFFFFFFFF000C7F09, 0xFFFFFFFE000C7F11 # 2040 - 2043 D7 S7 C8 neq + .quad 0xfffffffd000c7f19, 0xFFFFFFFC000C7F21, 0xFFFFFFFB000C7F29, 0xFFFFFFFA000C7F31 # 2044 - 2047 D7 S7 C12 neq \ No newline at end of file diff --git a/emulator/src/disasm.rs b/emulator/src/disasm.rs index 8ddedaf99..d958903f6 100644 --- a/emulator/src/disasm.rs +++ b/emulator/src/disasm.rs @@ -242,8 +242,8 @@ fn inst_to_asm(inst: &ZiskInst, labels: &HashMap, next_pc: Option { - let imm = (inst.a_offset_imm0 as i64 | ((inst.a_use_sp_imm1 as i64) << 32)) as i64; - if imm >= 0 && imm <= 9 { + let imm = inst.a_offset_imm0 as i64 | ((inst.a_use_sp_imm1 as i64) << 32); + if (0..=9).contains(&imm) { format!("{}", imm) } else { format!("0x{:x}", imm as u64) @@ -274,8 +274,8 @@ fn inst_to_asm(inst: &ZiskInst, labels: &HashMap, next_pc: Option { - let imm = (inst.b_offset_imm0 as i64 | ((inst.b_use_sp_imm1 as i64) << 32)) as i64; - if imm >= 0 && imm <= 9 { + let imm = inst.b_offset_imm0 as i64 | ((inst.b_use_sp_imm1 as i64) << 32); + if (0..=9).contains(&imm) { format!("{}", imm) } else { format!("0x{:x}", imm as u64) @@ -296,12 +296,10 @@ fn inst_to_asm(inst: &ZiskInst, labels: &HashMap, next_pc: Option= 0 { + format!("[a+{}]{}", offset, width) } else { - if offset >= 0 { - format!("[a+{}]{}", offset, width) - } else { - format!("[a{}]{}", offset, width) - } + format!("[a{}]{}", offset, width) } } _ => "?".to_string(), @@ -387,7 +385,7 @@ fn inst_to_asm(inst: &ZiskInst, labels: &HashMap, next_pc: Option Emu<'a> { emu } - pub fn create_emu_context(&mut self, inputs: Vec) -> EmuContext { + pub fn create_emu_context(&mut self, inputs: Vec, options: &EmuOptions) -> EmuContext { // Initialize an empty instance - let mut ctx = EmuContext::new(inputs); + let mut ctx = EmuContext::new(inputs, options); // Create a new read section for every RO data entry of the rom for i in 0..self.rom.ro_data.len() { @@ -1531,7 +1531,7 @@ impl<'a> Emu<'a> { callback: Option, ) { // Context, where the state of the execution is stored and modified at every execution step - self.ctx = self.create_emu_context(inputs.clone()); + self.ctx = self.create_emu_context(inputs.clone(), options); let mut elf = ElfSymbolReader::new(); if options.read_symbols { @@ -1808,7 +1808,7 @@ impl<'a> Emu<'a> { par_options: &ParEmuOptions, ) -> Vec { // Context, where the state of the execution is stored and modified at every execution step - self.ctx = self.create_emu_context(inputs); + self.ctx = self.create_emu_context(inputs, options); // Init pc to the rom entry address self.ctx.trace.start_state.pc = ROM_ENTRY; @@ -1918,7 +1918,7 @@ impl<'a> Emu<'a> { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); let pc = self.ctx.inst_ctx.pc; - //println!("PCLOG={}", instruction.to_text()); + // println!("PCLOG={}", instruction.to_text()); // Build the 'a' register value based on the source specified by the current instruction self.source_a(instruction); @@ -1927,6 +1927,9 @@ impl<'a> Emu<'a> { self.source_b(instruction); // Call the operation + if instruction.input_size > 0 { + self.ctx.inst_ctx.extended_arg = instruction.jmp_offset1; + } (instruction.func)(&mut self.ctx.inst_ctx); // Retrieve statistics data @@ -2044,6 +2047,7 @@ impl<'a> Emu<'a> { if instruction.input_size > 0 { self.ctx.inst_ctx.precompiled.input_data.clear(); self.ctx.inst_ctx.precompiled.output_data.clear(); + self.ctx.inst_ctx.extended_arg = instruction.jmp_offset1; } // Call the operation @@ -2130,16 +2134,11 @@ impl<'a> Emu<'a> { data_bus: &mut DB, ) -> bool { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); - #[cfg(feature = "minimal_trace_index_debug")] println!( "MINIMAL_TRACE step_emu_trace {} {}", self.ctx.inst_ctx.step, mem_reads_index ); - // println!( - // "DEBUG_TRACE {:09} 0x{:08x} {:?}", - // self.ctx.inst_ctx.step, self.ctx.inst_ctx.pc, self.ctx.inst_ctx.regs - // ); self.source_a_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); self.source_b_mem_reads_consume_databus(instruction, mem_reads, mem_reads_index, data_bus); @@ -2147,7 +2146,7 @@ impl<'a> Emu<'a> { if instruction.input_size > 0 { self.ctx.inst_ctx.precompiled.input_data.clear(); self.ctx.inst_ctx.precompiled.output_data.clear(); - + self.ctx.inst_ctx.extended_arg = instruction.jmp_offset1; // round_up => (size + 7) >> 3 let number_of_mem_reads = (instruction.input_size + 7) >> 3; for _ in 0..number_of_mem_reads { @@ -2222,7 +2221,7 @@ impl<'a> Emu<'a> { if instruction.input_size > 0 { self.ctx.inst_ctx.precompiled.input_data.clear(); self.ctx.inst_ctx.precompiled.output_data.clear(); - + self.ctx.inst_ctx.extended_arg = instruction.jmp_offset1; // round_up => (size + 7) >> 3 let number_of_mem_reads = (instruction.input_size + 7) >> 3; for _ in 0..number_of_mem_reads { @@ -2362,6 +2361,7 @@ impl<'a> Emu<'a> { if instruction.input_size > 0 { self.ctx.inst_ctx.precompiled.input_data.clear(); self.ctx.inst_ctx.precompiled.output_data.clear(); + self.ctx.inst_ctx.extended_arg = instruction.jmp_offset1; let number_of_mem_reads = (instruction.input_size + 7) >> 3; for _ in 0..number_of_mem_reads { let mem_read = mem_reads[*mem_reads_index]; @@ -2453,6 +2453,7 @@ impl<'a> Emu<'a> { if instruction.input_size > 0 { self.ctx.inst_ctx.precompiled.input_data.clear(); self.ctx.inst_ctx.precompiled.output_data.clear(); + self.ctx.inst_ctx.extended_arg = instruction.jmp_offset1; let number_of_mem_reads = (instruction.input_size + 7) >> 3; for _ in 0..number_of_mem_reads { let mem_read = mem_reads[*mem_reads_index]; @@ -2559,7 +2560,7 @@ impl<'a> Emu<'a> { // trace.set_a_src_sp(inst.a_src == SRC_SP), // #[cfg(feature = "sp")] // trace.set_a_use_sp_imm1(inst.a_use_sp_imm1), - trace.set_op_with_step(inst.op_with_step); + trace.set_is_precompiled(inst.is_precompiled); trace.set_b_src_imm(inst.b_src == SRC_IMM); trace.set_b_src_mem(inst.b_src == SRC_MEM); trace.set_b_src_reg(inst.b_src == SRC_REG); diff --git a/emulator/src/emu_context.rs b/emulator/src/emu_context.rs index 8bb7dacdb..0eec2486b 100644 --- a/emulator/src/emu_context.rs +++ b/emulator/src/emu_context.rs @@ -1,8 +1,6 @@ -use crate::Stats; +use crate::{EmuOptions, Stats}; use zisk_common::EmuTrace; -use zisk_core::{ - InstContext, INPUT_ADDR, MAX_INPUT_SIZE, RAM_ADDR, RAM_SIZE, REGS_IN_MAIN_TOTAL_NUMBER, -}; +use zisk_core::{InstContext, INPUT_ADDR, RAM_ADDR, RAM_SIZE, REGS_IN_MAIN_TOTAL_NUMBER}; /// ZisK emulator context data container, storing the state of the emulation pub struct EmuContext { @@ -17,13 +15,14 @@ pub struct EmuContext { pub last_callback_step: u64, pub trace: EmuTrace, pub do_stats: bool, + pub max_input_mem: u64, pub stats: Stats, } /// RisK emulator context implementation impl EmuContext { /// RisK emulator context constructor - pub fn new(input: Vec) -> EmuContext { + pub fn new(input: Vec, options: &EmuOptions) -> EmuContext { let mut ctx = EmuContext { inst_ctx: InstContext::default(), tracerv: Vec::new(), @@ -36,10 +35,11 @@ impl EmuContext { last_callback_step: 0, do_stats: false, stats: Stats::default(), + max_input_mem: options.max_input_mem, // 128 MiB }; // Check the input data size is inside the proper range - if input.len() > (MAX_INPUT_SIZE - 16) as usize { + if input.len() > (ctx.max_input_mem - 16) as usize { panic!("EmuContext::new() input size too big size={}", input.len()); } @@ -59,6 +59,6 @@ impl EmuContext { impl Default for EmuContext { fn default() -> Self { - Self::new(Vec::new()) + Self::new(Vec::new(), &EmuOptions::default()) } } diff --git a/emulator/src/emu_options.rs b/emulator/src/emu_options.rs index 71c860c39..749c0623d 100644 --- a/emulator/src/emu_options.rs +++ b/emulator/src/emu_options.rs @@ -2,7 +2,7 @@ use clap::Parser; use std::fmt; -use zisk_core::{DEFAULT_MAX_STEPS, DEFAULT_MAX_STEPS_STR}; +use zisk_core::{DEFAULT_MAX_STEPS, DEFAULT_MAX_STEPS_STR, MAX_INPUT_SIZE}; pub const ZISK_VERSION_MESSAGE: &str = concat!( env!("CARGO_PKG_VERSION"), @@ -124,6 +124,8 @@ pub struct EmuOptions { /// Requires options: -S -X #[clap(long, value_name = "DISASM_FILE")] pub disasm: Option, + #[clap(long, value_name = "MAX_INPUT_MEM", default_value = "134217728")] // 128 MiB + pub max_input_mem: u64, } impl Default for EmuOptions { @@ -161,6 +163,7 @@ impl Default for EmuOptions { no_thousands_sep: false, top_roi_filter: false, disasm: None, + max_input_mem: MAX_INPUT_SIZE, // 128 MiB } } } @@ -196,6 +199,7 @@ impl fmt::Display for EmuOptions { writeln!(f, "NO_THOUSANDS_SEP: {:?}", self.no_thousands_sep)?; writeln!(f, "TOP_ROI_FILTER: {:?}", self.top_roi_filter)?; writeln!(f, "DISASM: {:?}", self.disasm)?; + writeln!(f, "MAX_INPUT_MEM: {:?}", self.max_input_mem)?; Ok(()) } } diff --git a/emulator/src/stats.rs b/emulator/src/stats.rs index 84ddb3f1b..c91800535 100644 --- a/emulator/src/stats.rs +++ b/emulator/src/stats.rs @@ -919,7 +919,7 @@ impl Stats { { &self.rois[*index as usize].name } else { - &"" + "" }; } let instruction = rom.get_instruction(**pc); diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index 8c92a5257..e64247059 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -27,8 +27,16 @@ use std::collections::{BTreeMap, HashMap}; use zisk_common::{BusDeviceMetrics, ChunkId, ComponentBuilder, Instance, InstanceCtx, Plan}; use zisk_pil::ADD_256_AIR_IDS; use zisk_pil::DMA_64_ALIGNED_AIR_IDS; +use zisk_pil::DMA_64_ALIGNED_INPUT_CPY_AIR_IDS; +use zisk_pil::DMA_64_ALIGNED_MEM_AIR_IDS; +use zisk_pil::DMA_64_ALIGNED_MEM_CPY_AIR_IDS; +use zisk_pil::DMA_64_ALIGNED_MEM_SET_AIR_IDS; use zisk_pil::DMA_AIR_IDS; +use zisk_pil::DMA_INPUT_CPY_AIR_IDS; +use zisk_pil::DMA_MEM_CPY_AIR_IDS; use zisk_pil::DMA_PRE_POST_AIR_IDS; +use zisk_pil::DMA_PRE_POST_INPUT_CPY_AIR_IDS; +use zisk_pil::DMA_PRE_POST_MEM_CPY_AIR_IDS; use zisk_pil::DMA_UNALIGNED_AIR_IDS; use zisk_pil::{ ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, BINARY_AIR_IDS, @@ -463,13 +471,21 @@ impl StaticSMBundle { add256_collectors.push((*global_idx, add256_collector)); } // DMA AIRS - air_id if air_id == DMA_AIR_IDS[0] => { + air_id + if air_id == DMA_AIR_IDS[0] + || air_id == DMA_MEM_CPY_AIR_IDS[0] + || air_id == DMA_INPUT_CPY_AIR_IDS[0] => + { let dma_instance = secn_instance.as_any().downcast_ref::>().unwrap(); let dma_collector = dma_instance.build_dma_collector(ChunkId(chunk_id)); dma_collectors.push((*global_idx, dma_collector)); } - air_id if air_id == DMA_PRE_POST_AIR_IDS[0] => { + air_id + if air_id == DMA_PRE_POST_AIR_IDS[0] + || air_id == DMA_PRE_POST_MEM_CPY_AIR_IDS[0] + || air_id == DMA_PRE_POST_INPUT_CPY_AIR_IDS[0] => + { let dma_pre_post_instance = secn_instance .as_any() .downcast_ref::>() @@ -478,7 +494,13 @@ impl StaticSMBundle { dma_pre_post_instance.build_dma_collector(ChunkId(chunk_id)); dma_pre_post_collectors.push((*global_idx, dma_pre_post_collector)); } - air_id if air_id == DMA_64_ALIGNED_AIR_IDS[0] => { + air_id + if air_id == DMA_64_ALIGNED_AIR_IDS[0] + || air_id == DMA_64_ALIGNED_MEM_CPY_AIR_IDS[0] + || air_id == DMA_64_ALIGNED_INPUT_CPY_AIR_IDS[0] + || air_id == DMA_64_ALIGNED_MEM_SET_AIR_IDS[0] + || air_id == DMA_64_ALIGNED_MEM_AIR_IDS[0] => + { let dma_64_aligned_instance = secn_instance .as_any() .downcast_ref::>() diff --git a/pil/operations.pil b/pil/operations.pil index de2ef90e6..9341db9bb 100644 --- a/pil/operations.pil +++ b/pil/operations.pil @@ -91,10 +91,10 @@ const int __OP_DMA_MEMEQ__ = 0xD4; // main don't known in compilation time const int OP_DMA_X_OFFSET = 6; -const int OP_DMA_XMEMCPY = OP_DMA_MEMCPY + OP_DMA_X_OFFSET; -const int OP_DMA_XMEMCMP = OP_DMA_MEMCMP + OP_DMA_X_OFFSET; -const int OP_DMA_XMEMSET = __OP_DMA_MEMSET__ + OP_DMA_X_OFFSET; -const int OP_DMA_XMEMEQ = __OP_DMA_MEMEQ__ + OP_DMA_X_OFFSET; +const int OP_DMA_XMEMCPY = OP_DMA_MEMCPY + OP_DMA_X_OFFSET; // 0xD6 +const int OP_DMA_XMEMCMP = OP_DMA_MEMCMP + OP_DMA_X_OFFSET; // 0xD7 +const int OP_DMA_XMEMSET = __OP_DMA_MEMSET__ + OP_DMA_X_OFFSET; // 0xD9 +const int OP_DMA_XMEMEQ = __OP_DMA_MEMEQ__ + OP_DMA_X_OFFSET; // 0xDA const int OP_POSEIDON2 = 0xE1; diff --git a/pil/src/constants.rs b/pil/src/constants.rs index 5fcb5657b..1ffe73072 100644 --- a/pil/src/constants.rs +++ b/pil/src/constants.rs @@ -2,4 +2,5 @@ pub const DUAL_RANGE_BYTE_ID: usize = 88; pub const DUAL_RANGE_7_BITS_ID: usize = 77; pub const DMA_ROM_ID: usize = 8001; pub const DMA_PRE_POST_TABLE_ID: usize = 8002; -pub const DMA_PRE_POST_TABLE_SIZE: usize = 280; +pub const DMA_PRE_POST_TABLE_SIZE: usize = 1152; +pub const DMA_BYTE_CMP_TABLE_ID: usize = 8003; diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 145d4081f..755df439e 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -5,18 +5,18 @@ #![allow(non_upper_case_globals)] #![allow(dead_code)] -use fields::PrimeField64; use proofman_common as common; use proofman_common::GenericTrace; use proofman_common::PackedInfoConst; pub use proofman_macros::trace_row; pub use proofman_macros::values; +use fields::PrimeField64; use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "28c8185c73aa9a58d122501d67edea0d8dd62d1893c974f4155b3944a7c84bbd"; +pub const PILOUT_HASH: &str = "daf224a3536c4d7cba36710275c8d7535ccd3ebf274810d5acc48dd458c4702e"; pub const MERKLE_TREE_ARITY: u64 = 4; @@ -28,61 +28,79 @@ pub const ZISK_AIRGROUP_ID: usize = 0; pub const DMA_AIR_IDS: &[usize] = &[0]; -pub const DMA_64_ALIGNED_AIR_IDS: &[usize] = &[1]; +pub const DMA_MEM_CPY_AIR_IDS: &[usize] = &[1]; + +pub const DMA_INPUT_CPY_AIR_IDS: &[usize] = &[2]; + +pub const DMA_64_ALIGNED_AIR_IDS: &[usize] = &[3]; + +pub const DMA_64_ALIGNED_INPUT_CPY_AIR_IDS: &[usize] = &[4]; + +pub const DMA_64_ALIGNED_MEM_SET_AIR_IDS: &[usize] = &[5]; + +pub const DMA_64_ALIGNED_MEM_AIR_IDS: &[usize] = &[6]; + +pub const DMA_64_ALIGNED_MEM_CPY_AIR_IDS: &[usize] = &[7]; + +pub const DMA_UNALIGNED_AIR_IDS: &[usize] = &[8]; + +pub const DMA_PRE_POST_AIR_IDS: &[usize] = &[9]; -pub const DMA_UNALIGNED_AIR_IDS: &[usize] = &[2]; +pub const DMA_PRE_POST_MEM_CPY_AIR_IDS: &[usize] = &[10]; -pub const DMA_PRE_POST_AIR_IDS: &[usize] = &[3]; +pub const DMA_PRE_POST_INPUT_CPY_AIR_IDS: &[usize] = &[11]; -pub const MAIN_AIR_IDS: &[usize] = &[4]; +pub const MAIN_AIR_IDS: &[usize] = &[12]; -pub const ROM_AIR_IDS: &[usize] = &[5]; +pub const ROM_AIR_IDS: &[usize] = &[13]; -pub const MEM_AIR_IDS: &[usize] = &[6]; +pub const MEM_AIR_IDS: &[usize] = &[14]; -pub const ROM_DATA_AIR_IDS: &[usize] = &[7]; +pub const ROM_DATA_AIR_IDS: &[usize] = &[15]; -pub const INPUT_DATA_AIR_IDS: &[usize] = &[8]; +pub const INPUT_DATA_AIR_IDS: &[usize] = &[16]; -pub const MEM_ALIGN_AIR_IDS: &[usize] = &[9]; +pub const MEM_ALIGN_AIR_IDS: &[usize] = &[17]; -pub const MEM_ALIGN_BYTE_AIR_IDS: &[usize] = &[10]; +pub const MEM_ALIGN_BYTE_AIR_IDS: &[usize] = &[18]; -pub const MEM_ALIGN_READ_BYTE_AIR_IDS: &[usize] = &[11]; +pub const MEM_ALIGN_READ_BYTE_AIR_IDS: &[usize] = &[19]; -pub const MEM_ALIGN_WRITE_BYTE_AIR_IDS: &[usize] = &[12]; +pub const MEM_ALIGN_WRITE_BYTE_AIR_IDS: &[usize] = &[20]; -pub const ARITH_AIR_IDS: &[usize] = &[13]; +pub const ARITH_AIR_IDS: &[usize] = &[21]; -pub const BINARY_AIR_IDS: &[usize] = &[14]; +pub const BINARY_AIR_IDS: &[usize] = &[22]; -pub const BINARY_ADD_AIR_IDS: &[usize] = &[15]; +pub const BINARY_ADD_AIR_IDS: &[usize] = &[23]; -pub const BINARY_EXTENSION_AIR_IDS: &[usize] = &[16]; +pub const BINARY_EXTENSION_AIR_IDS: &[usize] = &[24]; -pub const ADD_256_AIR_IDS: &[usize] = &[17]; +pub const ADD_256_AIR_IDS: &[usize] = &[25]; -pub const ARITH_EQ_AIR_IDS: &[usize] = &[18]; +pub const ARITH_EQ_AIR_IDS: &[usize] = &[26]; -pub const ARITH_EQ_384_AIR_IDS: &[usize] = &[19]; +pub const ARITH_EQ_384_AIR_IDS: &[usize] = &[27]; -pub const KECCAKF_AIR_IDS: &[usize] = &[20]; +pub const KECCAKF_AIR_IDS: &[usize] = &[28]; -pub const SHA_256_F_AIR_IDS: &[usize] = &[21]; +pub const SHA_256_F_AIR_IDS: &[usize] = &[29]; -pub const POSEIDON_2_AIR_IDS: &[usize] = &[22]; +pub const POSEIDON_2_AIR_IDS: &[usize] = &[30]; -pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[23]; +pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[31]; -pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[24]; +pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[32]; + +pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[33]; -pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[25]; //PUBLICS use serde::Deserialize; use serde::Serialize; use serde_arrays; + fn default_array_rom_root() -> [u64; 4] { [0; 4] } @@ -91,346 +109,525 @@ fn default_array_inputs() -> [u64; 64] { [0; 64] } + #[derive(Debug, Serialize, Deserialize)] pub struct ZiskPublics { #[serde(default = "default_array_rom_root", with = "serde_arrays")] pub rom_root: [u64; 4], #[serde(default = "default_array_inputs", with = "serde_arrays")] pub inputs: [u64; 64], + } impl Default for ZiskPublics { fn default() -> Self { - Self { rom_root: [0; 4], inputs: [0; 64] } + Self { + rom_root: [0; 4], + inputs: [0; 64], + } } } values!(ZiskPublicValues { rom_root: [F; 4], inputs: [F; 64], }); - + values!(ZiskProofValues { - enable_input_data: F, enable_rom_data: F, enable_dma_64_aligned: F, enable_dma_64_aligned_input: F, enable_dma_unaligned: F, + enable_input_data: F, enable_rom_data: F, enable_dma_64_aligned: F, enable_dma_64_aligned_inputcpy: F, enable_dma_64_aligned_mem: F, enable_dma_64_aligned_memcpy: F, enable_dma_64_aligned_memset: F, enable_dma_unaligned: F, }); - + trace_row!(DmaFixedRow { __L1__: F, }); -pub type DmaFixed = GenericTrace, 2097152, 0, 0>; +pub type DmaFixed = GenericTrace, 16384, 0, 0>; trace_row!(DmaTraceRow { - sel:bit, h_count:ubit(24), count_lt_256:bit, l_count:ubit(9), h_src64:ubit(22), l_src64:ubit(7), src_offset:ubit(3), h_dst64:ubit(22), l_dst64:ubit(7), dst_offset:ubit(3), main_step:ubit(36), use_pre:bit, use_memcpy:bit, use_post:bit, src64_inc_by_pre:bit, pre_count:ubit(3), l_count64:ubit(9), src_offset_after_pre:ubit(3), + sel_memcpy:bit, sel_memcmp:bit, sel_memset:bit, fill_byte:u8, sel_extended:bit, sel_inputcpy:bit, h_count:ubit(24), count_lt_256:bit, l_count:ubit(9), count_diff_chunks:[u16; 2], h_dst64:ubit(22), l_dst64:ubit(7), dst_offset:ubit(3), main_step:ubit(36), h_src64:ubit(22), l_src64:ubit(7), src_offset:ubit(3), src_offset_after_pre:ubit(3), src64_inc_by_pre:bit, use_pre:bit, use_loop:bit, use_post:bit, pre_count:ubit(3), l_count64:ubit(9), pre_result_nz:bit, post_result_nz:bit, bus_pre_result:[u32; 2], bus_post_result:[u32; 2], loop_b0:u32, loop_extended_arg:u32, static_count:u32, b0:u32, extended_arg:u32, +}); +pub type DmaTrace = GenericTrace, 16384, 0, 0>; + + +pub type DmaTracePacked = GenericTrace, 16384, 0, 0>; + + +trace_row!(DmaMemCpyFixedRow { + __L1__: F, +}); +pub type DmaMemCpyFixed = GenericTrace, 16384, 0, 1>; + +trace_row!(DmaMemCpyTraceRow { + sel_memcpy:bit, sel_extended:bit, h_count:ubit(24), count_lt_256:bit, l_count:ubit(9), h_dst64:ubit(22), l_dst64:ubit(7), dst_offset:ubit(3), main_step:ubit(36), h_src64:ubit(22), l_src64:ubit(7), src_offset:ubit(3), src_offset_after_pre:ubit(3), src64_inc_by_pre:bit, use_pre:bit, use_loop:bit, use_post:bit, pre_count:ubit(3), l_count64:ubit(9), loop_b0:u32, loop_extended_arg:u32, static_count:u32, b0:u32, extended_arg:u32, +}); +pub type DmaMemCpyTrace = GenericTrace, 16384, 0, 1>; + + +pub type DmaMemCpyTracePacked = GenericTrace, 16384, 0, 1>; + + +trace_row!(DmaInputCpyFixedRow { + __L1__: F, +}); +pub type DmaInputCpyFixed = GenericTrace, 16384, 0, 2>; + +trace_row!(DmaInputCpyTraceRow { + sel_extended:bit, sel_inputcpy:bit, h_count:ubit(24), count_lt_256:bit, l_count:ubit(9), h_dst64:ubit(22), l_dst64:ubit(7), dst_offset:ubit(3), main_step:ubit(36), use_pre:bit, use_loop:bit, use_post:bit, pre_count:ubit(3), l_count64:ubit(9), loop_b0:u32, static_count:u32, b0:u32, }); -pub type DmaTrace = GenericTrace, 2097152, 0, 0>; +pub type DmaInputCpyTrace = GenericTrace, 16384, 0, 2>; + + +pub type DmaInputCpyTracePacked = GenericTrace, 16384, 0, 2>; -pub type DmaTracePacked = GenericTrace, 2097152, 0, 0>; trace_row!(Dma64AlignedFixedRow { __L1__: F, }); -pub type Dma64AlignedFixed = GenericTrace, 2097152, 0, 1>; +pub type Dma64AlignedFixed = GenericTrace, 16384, 0, 3>; trace_row!(Dma64AlignedTraceRow { - main_step:ubit(36), dst64:ubit(29), count:u32, sel_op:[bit; 4], seq_end:bit, is_mem_eq:bit, value:[[u32; 2]; 4], src64:ubit(29), previous_seq_end:bit, + src64:ubit(29), seq_end:bit, previous_seq_end:bit, sel_memcpy:bit, sel_memeq:bit, sel_memset:bit, fill_byte:u8, sel_memcpy_count_load:bit, sel_inputcpy:bit, main_step:ubit(36), dst64:ubit(29), count64:u32, sel_op_from_1:[bit; 3], l_value_chunks:[[u8; 2]; 4], h_value_chunks:[[ubit(24); 2]; 4], sel_op_mem_load:[bit; 4], +}); +pub type Dma64AlignedTrace = GenericTrace, 16384, 0, 3>; + + +pub type Dma64AlignedTracePacked = GenericTrace, 16384, 0, 3>; + + +trace_row!(Dma64AlignedInputCpyFixedRow { + __L1__: F, +}); +pub type Dma64AlignedInputCpyFixed = GenericTrace, 16384, 0, 4>; + +trace_row!(Dma64AlignedInputCpyTraceRow { + seq_end:bit, previous_seq_end:bit, sel_inputcpy:bit, main_step:ubit(36), dst64:ubit(29), count64:u32, sel_op_from_1:[bit; 3], l_value_chunks:[[u8; 2]; 4], h_value_chunks:[[ubit(24); 2]; 4], +}); +pub type Dma64AlignedInputCpyTrace = GenericTrace, 16384, 0, 4>; + + +pub type Dma64AlignedInputCpyTracePacked = GenericTrace, 16384, 0, 4>; + + +trace_row!(Dma64AlignedMemSetFixedRow { + __L1__: F, +}); +pub type Dma64AlignedMemSetFixed = GenericTrace, 16384, 0, 5>; + +trace_row!(Dma64AlignedMemSetTraceRow { + seq_end:bit, previous_seq_end:bit, sel_memset:bit, fill_byte:u8, main_step:ubit(36), dst64:ubit(29), count64:u32, sel_op_from_1:[bit; 7], +}); +pub type Dma64AlignedMemSetTrace = GenericTrace, 16384, 0, 5>; + + +pub type Dma64AlignedMemSetTracePacked = GenericTrace, 16384, 0, 5>; + + +trace_row!(Dma64AlignedMemFixedRow { + __L1__: F, +}); +pub type Dma64AlignedMemFixed = GenericTrace, 16384, 0, 6>; + +trace_row!(Dma64AlignedMemTraceRow { + src64:ubit(29), seq_end:bit, previous_seq_end:bit, sel_memcpy:bit, sel_memeq:bit, sel_memset:bit, fill_byte:u8, sel_memcpy_count_load:bit, main_step:ubit(36), dst64:ubit(29), count64:u32, sel_op_from_1:[bit; 3], value:[[u32; 2]; 4], sel_op_mem_load:[bit; 4], +}); +pub type Dma64AlignedMemTrace = GenericTrace, 16384, 0, 6>; + + +pub type Dma64AlignedMemTracePacked = GenericTrace, 16384, 0, 6>; + + +trace_row!(Dma64AlignedMemCpyFixedRow { + __L1__: F, +}); +pub type Dma64AlignedMemCpyFixed = GenericTrace, 16384, 0, 7>; + +trace_row!(Dma64AlignedMemCpyTraceRow { + src64:ubit(29), seq_end:bit, previous_seq_end:bit, sel_memcpy:bit, sel_memcpy_count_load:bit, main_step:ubit(36), dst64:ubit(29), count64:u32, sel_op_from_1:[bit; 7], value:[[u32; 2]; 8], }); -pub type Dma64AlignedTrace = GenericTrace, 2097152, 0, 1>; +pub type Dma64AlignedMemCpyTrace = GenericTrace, 16384, 0, 7>; + + +pub type Dma64AlignedMemCpyTracePacked = GenericTrace, 16384, 0, 7>; -pub type Dma64AlignedTracePacked = GenericTrace, 2097152, 0, 1>; trace_row!(DmaUnalignedFixedRow { __L1__: F, }); -pub type DmaUnalignedFixed = GenericTrace, 2097152, 0, 2>; +pub type DmaUnalignedFixed = GenericTrace, 16384, 0, 8>; trace_row!(DmaUnalignedTraceRow { - main_step:ubit(36), src64:ubit(29), dst64:ubit(29), count:u32, seq_end:bit, previous_seq_end:bit, is_mem_eq:bit, offset_7:bit, offset_6:bit, offset_5:bit, offset_4:bit, offset_3:bit, offset_2:bit, read_bytes:[u8; 8], no_last_no_seq_end:bit, write_value:[u32; 2], + main_step:ubit(36), src64:ubit(29), dst64:ubit(29), count:u32, seq_end:bit, previous_seq_end:bit, is_memeq:bit, offset_7:bit, offset_6:bit, offset_5:bit, offset_4:bit, offset_3:bit, offset_2:bit, read_bytes:[u8; 8], no_last_no_seq_end:bit, write_value:[u32; 2], }); -pub type DmaUnalignedTrace = GenericTrace, 2097152, 0, 2>; +pub type DmaUnalignedTrace = GenericTrace, 16384, 0, 8>; + + +pub type DmaUnalignedTracePacked = GenericTrace, 16384, 0, 8>; -pub type DmaUnalignedTracePacked = GenericTrace, 2097152, 0, 2>; trace_row!(DmaPrePostFixedRow { __L1__: F, }); -pub type DmaPrePostFixed = GenericTrace, 2097152, 0, 3>; +pub type DmaPrePostFixed = GenericTrace, 16384, 0, 9>; trace_row!(DmaPrePostTraceRow { - main_step:ubit(36), src64:ubit(29), dst64:ubit(29), dst_offset:ubit(3), src_offset:ubit(3), count:ubit(3), selread:[bit; 7], dst_offset_gt_src_offset:bit, enabled:bit, enabled_second_read:bit, bytes:[u8; 24], selb:[bit; 8], write_value:[u32; 4], + main_step:ubit(36), dst64:ubit(29), dst_offset:ubit(3), count:ubit(3), is_post:bit, sel_memcpy:bit, sel_memcmp:bit, memcmp_result_nz:bit, l_memcmp_result:u32, sel_inputcpy:bit, sel_memset:bit, selr:[bit; 7], dst_offset_gt_src_offset:bit, src64:ubit(29), src_offset:ubit(3), enabled_second_read:bit, fill_byte:u8, rb:[u8; 16], pb:[u8; 8], sb:[bit; 8], last_dst_byte:u8, abs_diff_dst_src:u8, memcmp_result_is_negative:bit, diff_factor:[u64; 2], bus_write_value:[u32; 2], write_value:[u32; 4], +}); +pub type DmaPrePostTrace = GenericTrace, 16384, 0, 9>; + + +pub type DmaPrePostTracePacked = GenericTrace, 16384, 0, 9>; + + +trace_row!(DmaPrePostMemCpyFixedRow { + __L1__: F, +}); +pub type DmaPrePostMemCpyFixed = GenericTrace, 16384, 0, 10>; + +trace_row!(DmaPrePostMemCpyTraceRow { + main_step:ubit(36), dst64:ubit(29), dst_offset:ubit(3), count:ubit(3), is_post:bit, sel_memcpy:bit, selr:[bit; 7], dst_offset_gt_src_offset:bit, src64:ubit(29), src_offset:ubit(3), enabled_second_read:bit, rb:[u8; 16], pb:[u8; 8], sb:[bit; 8], bus_write_value:[u32; 2], write_value:[u32; 4], +}); +pub type DmaPrePostMemCpyTrace = GenericTrace, 16384, 0, 10>; + + +pub type DmaPrePostMemCpyTracePacked = GenericTrace, 16384, 0, 10>; + + +trace_row!(DmaPrePostInputCpyFixedRow { + __L1__: F, +}); +pub type DmaPrePostInputCpyFixed = GenericTrace, 16384, 0, 11>; + +trace_row!(DmaPrePostInputCpyTraceRow { + main_step:ubit(36), dst64:ubit(29), dst_offset:ubit(3), count:ubit(3), is_post:bit, sel_inputcpy:bit, rb:[u8; 8], pb:[u8; 8], sb:[bit; 8], bus_write_value:[u32; 2], }); -pub type DmaPrePostTrace = GenericTrace, 2097152, 0, 3>; +pub type DmaPrePostInputCpyTrace = GenericTrace, 16384, 0, 11>; + + +pub type DmaPrePostInputCpyTracePacked = GenericTrace, 16384, 0, 11>; -pub type DmaPrePostTracePacked = GenericTrace, 2097152, 0, 3>; trace_row!(MainFixedRow { SEGMENT_L1: F, SEGMENT_STEP: F, __L1__: F, }); -pub type MainFixed = GenericTrace, 4194304, 0, 4>; +pub type MainFixed = GenericTrace, 4194304, 0, 12>; trace_row!(MainTraceRow { - a:[u32; 2], b:[u32; 2], c:[u32; 2], flag:bit, pc:u32, a_src_imm:bit, a_src_mem:bit, a_offset_imm0:u64, a_imm1:u32, op_with_step:bit, b_src_imm:bit, b_src_mem:bit, b_offset_imm0:u64, b_imm1:u32, b_src_ind:bit, ind_width:ubit(4), is_external_op:bit, op:u8, store_pc:bit, store_mem:bit, store_ind:bit, store_offset:u64, set_pc:bit, jmp_offset1:u64, jmp_offset2:u64, m32:bit, addr1:u32, a_reg_prev_mem_step:ubit(38), b_reg_prev_mem_step:ubit(38), store_reg_prev_mem_step:ubit(38), store_reg_prev_value:[u32; 2], a_src_reg:bit, b_src_reg:bit, store_reg:bit, + a:[u32; 2], b:[u32; 2], c:[u32; 2], flag:bit, pc:u32, a_src_imm:bit, a_src_mem:bit, a_offset_imm0:u64, a_imm1:u32, is_precompiled:bit, b_src_imm:bit, b_src_mem:bit, b_offset_imm0:u64, b_imm1:u32, b_src_ind:bit, ind_width:ubit(4), is_external_op:bit, op:u8, store_pc:bit, store_mem:bit, store_ind:bit, store_offset:u64, set_pc:bit, jmp_offset1:u64, jmp_offset2:u64, m32:bit, addr1:u32, a_reg_prev_mem_step:ubit(38), b_reg_prev_mem_step:ubit(38), store_reg_prev_mem_step:ubit(38), store_reg_prev_value:[u32; 2], a_src_reg:bit, b_src_reg:bit, store_reg:bit, }); -pub type MainTrace = GenericTrace, 4194304, 0, 4>; +pub type MainTrace = GenericTrace, 4194304, 0, 12>; + + +pub type MainTracePacked = GenericTrace, 4194304, 0, 12>; -pub type MainTracePacked = GenericTrace, 4194304, 0, 4>; trace_row!(RomFixedRow { __L1__: F, }); -pub type RomFixed = GenericTrace, 4194304, 0, 5>; +pub type RomFixed = GenericTrace, 4194304, 0, 13>; trace_row!(RomTraceRow { multiplicity:F, }); -pub type RomTrace = GenericTrace, 4194304, 0, 5>; +pub type RomTrace = GenericTrace, 4194304, 0, 13>; + trace_row!(MemFixedRow { SEGMENT_L1: F, __L1__: F, }); -pub type MemFixed = GenericTrace, 4194304, 0, 6>; +pub type MemFixed = GenericTrace, 4194304, 0, 14>; trace_row!(MemTraceRow { addr:ubit(29), step:ubit(38), sel:bit, addr_changes:bit, step_dual:ubit(38), sel_dual:bit, value:[u32; 2], wr:bit, previous_step:ubit(40), l_increment:ubit(22), h_increment:u16, read_same_addr:bit, }); -pub type MemTrace = GenericTrace, 4194304, 0, 6>; +pub type MemTrace = GenericTrace, 4194304, 0, 14>; + + +pub type MemTracePacked = GenericTrace, 4194304, 0, 14>; -pub type MemTracePacked = GenericTrace, 4194304, 0, 6>; trace_row!(RomDataFixedRow { SEGMENT_L1: F, __L1__: F, }); -pub type RomDataFixed = GenericTrace, 2097152, 0, 7>; +pub type RomDataFixed = GenericTrace, 2097152, 0, 15>; trace_row!(RomDataTraceRow { addr:ubit(29), step:ubit(38), sel:bit, addr_changes:bit, value:[u32; 2], }); -pub type RomDataTrace = GenericTrace, 2097152, 0, 7>; +pub type RomDataTrace = GenericTrace, 2097152, 0, 15>; + + +pub type RomDataTracePacked = GenericTrace, 2097152, 0, 15>; -pub type RomDataTracePacked = GenericTrace, 2097152, 0, 7>; trace_row!(InputDataFixedRow { SEGMENT_L1: F, __L1__: F, }); -pub type InputDataFixed = GenericTrace, 2097152, 0, 8>; +pub type InputDataFixed = GenericTrace, 2097152, 0, 16>; trace_row!(InputDataTraceRow { addr:ubit(29), step:ubit(38), sel:bit, addr_changes:bit, value_word:[u16; 4], is_free_read:bit, }); -pub type InputDataTrace = GenericTrace, 2097152, 0, 8>; +pub type InputDataTrace = GenericTrace, 2097152, 0, 16>; + + +pub type InputDataTracePacked = GenericTrace, 2097152, 0, 16>; -pub type InputDataTracePacked = GenericTrace, 2097152, 0, 8>; trace_row!(MemAlignFixedRow { L1: F, __L1__: F, }); -pub type MemAlignFixed = GenericTrace, 2097152, 0, 9>; +pub type MemAlignFixed = GenericTrace, 2097152, 0, 17>; trace_row!(MemAlignTraceRow { addr:ubit(29), offset:ubit(3), width:ubit(4), wr:bit, pc:u8, reset:bit, sel_up_to_down:bit, sel_down_to_up:bit, reg:[u8; 8], sel:[bit; 8], step:ubit(40), delta_addr:u64, sel_prove:bit, value:[u32; 2], }); -pub type MemAlignTrace = GenericTrace, 2097152, 0, 9>; +pub type MemAlignTrace = GenericTrace, 2097152, 0, 17>; + + +pub type MemAlignTracePacked = GenericTrace, 2097152, 0, 17>; -pub type MemAlignTracePacked = GenericTrace, 2097152, 0, 9>; trace_row!(MemAlignByteFixedRow { __L1__: F, }); -pub type MemAlignByteFixed = GenericTrace, 4194304, 0, 10>; +pub type MemAlignByteFixed = GenericTrace, 4194304, 0, 18>; trace_row!(MemAlignByteTraceRow { sel_high_4b:bit, sel_high_2b:bit, sel_high_b:bit, direct_value:u32, composed_value:u32, written_composed_value:u32, written_byte_value:u8, value_16b:u16, value_8b:u8, byte_value:u8, addr_w:ubit(29), step:ubit(40), is_write:bit, mem_write_values:[u32; 2], bus_byte:u8, }); -pub type MemAlignByteTrace = GenericTrace, 4194304, 0, 10>; +pub type MemAlignByteTrace = GenericTrace, 4194304, 0, 18>; + + +pub type MemAlignByteTracePacked = GenericTrace, 4194304, 0, 18>; -pub type MemAlignByteTracePacked = GenericTrace, 4194304, 0, 10>; trace_row!(MemAlignReadByteFixedRow { __L1__: F, }); -pub type MemAlignReadByteFixed = GenericTrace, 4194304, 0, 11>; +pub type MemAlignReadByteFixed = GenericTrace, 4194304, 0, 19>; trace_row!(MemAlignReadByteTraceRow { sel_high_4b:bit, sel_high_2b:bit, sel_high_b:bit, direct_value:u32, composed_value:u32, value_16b:u16, value_8b:u8, byte_value:u8, addr_w:ubit(29), step:ubit(40), }); -pub type MemAlignReadByteTrace = GenericTrace, 4194304, 0, 11>; +pub type MemAlignReadByteTrace = GenericTrace, 4194304, 0, 19>; + + +pub type MemAlignReadByteTracePacked = GenericTrace, 4194304, 0, 19>; -pub type MemAlignReadByteTracePacked = - GenericTrace, 4194304, 0, 11>; trace_row!(MemAlignWriteByteFixedRow { __L1__: F, }); -pub type MemAlignWriteByteFixed = GenericTrace, 4194304, 0, 12>; +pub type MemAlignWriteByteFixed = GenericTrace, 4194304, 0, 20>; trace_row!(MemAlignWriteByteTraceRow { sel_high_4b:bit, sel_high_2b:bit, sel_high_b:bit, direct_value:u32, composed_value:u32, written_composed_value:u32, written_byte_value:u8, value_16b:u16, value_8b:u8, byte_value:u8, addr_w:ubit(29), step:ubit(40), mem_write_values:[u32; 2], }); -pub type MemAlignWriteByteTrace = GenericTrace, 4194304, 0, 12>; +pub type MemAlignWriteByteTrace = GenericTrace, 4194304, 0, 20>; + + +pub type MemAlignWriteByteTracePacked = GenericTrace, 4194304, 0, 20>; -pub type MemAlignWriteByteTracePacked = - GenericTrace, 4194304, 0, 12>; trace_row!(ArithFixedRow { __L1__: F, }); -pub type ArithFixed = GenericTrace, 2097152, 0, 13>; +pub type ArithFixed = GenericTrace, 2097152, 0, 21>; trace_row!(ArithTraceRow { carry:[u64; 7], a:[u16; 4], b:[u16; 4], c:[u16; 4], d:[u16; 4], na:bit, nb:bit, nr:bit, np:bit, sext:bit, m32:bit, div:bit, fab:u64, na_fb:u64, nb_fa:u64, main_div:bit, main_mul:bit, signed:bit, div_by_zero:bit, div_overflow:bit, inv_sum_all_bs:u64, op:u8, bus_res1:u32, multiplicity:bit, range_ab:ubit(7), range_cd:ubit(7), }); -pub type ArithTrace = GenericTrace, 2097152, 0, 13>; +pub type ArithTrace = GenericTrace, 2097152, 0, 21>; + + +pub type ArithTracePacked = GenericTrace, 2097152, 0, 21>; -pub type ArithTracePacked = GenericTrace, 2097152, 0, 13>; trace_row!(BinaryFixedRow { __L1__: F, }); -pub type BinaryFixed = GenericTrace, 4194304, 0, 14>; +pub type BinaryFixed = GenericTrace, 4194304, 0, 22>; trace_row!(BinaryTraceRow { b_op:ubit(7), free_in_a:[u8; 8], free_in_b:[u8; 8], free_in_c:[u8; 8], carry:[bit; 8], mode32:bit, result_is_a:bit, use_first_byte:bit, c_is_signed:bit, b_op_or_sext:ubit(10), mode32_and_c_is_signed:bit, }); -pub type BinaryTrace = GenericTrace, 4194304, 0, 14>; +pub type BinaryTrace = GenericTrace, 4194304, 0, 22>; + + +pub type BinaryTracePacked = GenericTrace, 4194304, 0, 22>; -pub type BinaryTracePacked = GenericTrace, 4194304, 0, 14>; trace_row!(BinaryAddFixedRow { __L1__: F, }); -pub type BinaryAddFixed = GenericTrace, 4194304, 0, 15>; +pub type BinaryAddFixed = GenericTrace, 4194304, 0, 23>; trace_row!(BinaryAddTraceRow { a:[u32; 2], b:[u32; 2], c_chunks:[u16; 4], cout:[bit; 2], }); -pub type BinaryAddTrace = GenericTrace, 4194304, 0, 15>; +pub type BinaryAddTrace = GenericTrace, 4194304, 0, 23>; + + +pub type BinaryAddTracePacked = GenericTrace, 4194304, 0, 23>; -pub type BinaryAddTracePacked = GenericTrace, 4194304, 0, 15>; trace_row!(BinaryExtensionFixedRow { __L1__: F, }); -pub type BinaryExtensionFixed = GenericTrace, 4194304, 0, 16>; +pub type BinaryExtensionFixed = GenericTrace, 4194304, 0, 24>; trace_row!(BinaryExtensionTraceRow { op:ubit(6), free_in_a:[u8; 8], free_in_b:u8, free_in_c:[[u32; 2]; 8], op_is_shift:bit, b:[u32; 2], }); -pub type BinaryExtensionTrace = GenericTrace, 4194304, 0, 16>; +pub type BinaryExtensionTrace = GenericTrace, 4194304, 0, 24>; + + +pub type BinaryExtensionTracePacked = GenericTrace, 4194304, 0, 24>; -pub type BinaryExtensionTracePacked = - GenericTrace, 4194304, 0, 16>; trace_row!(Add256FixedRow { __L1__: F, }); -pub type Add256Fixed = GenericTrace, 1048576, 0, 17>; +pub type Add256Fixed = GenericTrace, 1048576, 0, 25>; trace_row!(Add256TraceRow { a:[[u32; 2]; 4], b:[[u32; 2]; 4], c_chunks:[[u16; 4]; 4], cout:[[bit; 2]; 4], addr_params:u32, addr_a:u32, addr_b:u32, addr_c:u32, step:ubit(40), cin:bit, sel:bit, }); -pub type Add256Trace = GenericTrace, 1048576, 0, 17>; +pub type Add256Trace = GenericTrace, 1048576, 0, 25>; + + +pub type Add256TracePacked = GenericTrace, 1048576, 0, 25>; -pub type Add256TracePacked = GenericTrace, 1048576, 0, 17>; trace_row!(ArithEqFixedRow { CLK_0: F, __L1__: F, }); -pub type ArithEqFixed = GenericTrace, 1048576, 0, 18>; +pub type ArithEqFixed = GenericTrace, 1048576, 0, 26>; trace_row!(ArithEqTraceRow { x1:u16, y1:u16, x2:u16, y2:u16, x3:u16, y3:u16, q0:ubit(22), q1:ubit(22), q2:ubit(22), s:ubit(22), sel_op:[bit; 9], sel_op_clk0:[bit; 9], x_delta_chunk_inv:u64, x_are_different:bit, x3_lt:bit, y3_lt:bit, carry:[[u64; 2]; 3], step_addr:ubit(40), }); -pub type ArithEqTrace = GenericTrace, 1048576, 0, 18>; +pub type ArithEqTrace = GenericTrace, 1048576, 0, 26>; + + +pub type ArithEqTracePacked = GenericTrace, 1048576, 0, 26>; -pub type ArithEqTracePacked = GenericTrace, 1048576, 0, 18>; trace_row!(ArithEq384FixedRow { CLK_0: F, __L1__: F, }); -pub type ArithEq384Fixed = GenericTrace, 1048576, 0, 19>; +pub type ArithEq384Fixed = GenericTrace, 1048576, 0, 27>; trace_row!(ArithEq384TraceRow { x1:u16, y1:u16, x2:u16, y2:u16, x3:u16, y3:u16, q0:ubit(22), q1:ubit(22), q2:ubit(22), s:ubit(22), sel_op:[bit; 6], sel_op_clk0:[bit; 6], x_delta_chunk_inv:u64, x_are_different:bit, x3_lt:bit, y3_lt:bit, carry:[[u64; 2]; 3], step_addr:ubit(40), }); -pub type ArithEq384Trace = GenericTrace, 1048576, 0, 19>; +pub type ArithEq384Trace = GenericTrace, 1048576, 0, 27>; + + +pub type ArithEq384TracePacked = GenericTrace, 1048576, 0, 27>; -pub type ArithEq384TracePacked = GenericTrace, 1048576, 0, 19>; trace_row!(KeccakfFixedRow { CLK_0: F, __L1__: F, }); -pub type KeccakfFixed = GenericTrace, 131072, 0, 20>; +pub type KeccakfFixed = GenericTrace, 131072, 0, 28>; trace_row!(KeccakfTraceRow { in_use_clk_0:bit, in_use:bit, state:[bit; 1600], chunk_acc:[ubit(22); 533], rem_acc:u8, step_addr:ubit(40), }); -pub type KeccakfTrace = GenericTrace, 131072, 0, 20>; +pub type KeccakfTrace = GenericTrace, 131072, 0, 28>; + + +pub type KeccakfTracePacked = GenericTrace, 131072, 0, 28>; -pub type KeccakfTracePacked = GenericTrace, 131072, 0, 20>; trace_row!(Sha256fFixedRow { CLK_0: F, __L1__: F, }); -pub type Sha256fFixed = GenericTrace, 262144, 0, 21>; +pub type Sha256fFixed = GenericTrace, 262144, 0, 29>; trace_row!(Sha256fTraceRow { a:[bit; 32], e:[bit; 32], w:[bit; 32], new_a_carry_bits:u8, new_e_carry_bits:u8, new_w_carry_bits:ubit(4), step_addr:ubit(40), in_use_clk_0:bit, in_use:bit, }); -pub type Sha256fTrace = GenericTrace, 262144, 0, 21>; +pub type Sha256fTrace = GenericTrace, 262144, 0, 29>; + + +pub type Sha256fTracePacked = GenericTrace, 262144, 0, 29>; -pub type Sha256fTracePacked = GenericTrace, 262144, 0, 21>; trace_row!(Poseidon2FixedRow { CLK_0: F, __L1__: F, }); -pub type Poseidon2Fixed = GenericTrace, 131072, 0, 22>; +pub type Poseidon2Fixed = GenericTrace, 131072, 0, 30>; trace_row!(Poseidon2TraceRow { in_use_clk_0:bit, in_use:bit, chunks:[[u32; 2]; 16], step_addr:ubit(40), }); -pub type Poseidon2Trace = GenericTrace, 131072, 0, 22>; +pub type Poseidon2Trace = GenericTrace, 131072, 0, 30>; + + +pub type Poseidon2TracePacked = GenericTrace, 131072, 0, 30>; -pub type Poseidon2TracePacked = GenericTrace, 131072, 0, 22>; trace_row!(SpecifiedRangesFixedRow { OPID: [F; 29], VALS: [F; 29], __L1__: F, }); -pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 23>; +pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 31>; trace_row!(SpecifiedRangesTraceRow { mul:[F; 29], }); -pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 23>; +pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 31>; + trace_row!(VirtualTable0FixedRow { UID: [F; 8], column: [F; 43], __L1__: F, }); -pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 24>; +pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 32>; trace_row!(VirtualTable0TraceRow { multiplicity:[F; 8], }); -pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 24>; +pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 32>; + trace_row!(VirtualTable1FixedRow { - UID: [F; 8], column: [F; 72], __L1__: F, + UID: [F; 8], column: [F; 64], __L1__: F, }); -pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 25>; +pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 33>; trace_row!(VirtualTable1TraceRow { multiplicity:[F; 8], }); -pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 25>; +pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 33>; + trace_row!(RomRomTraceRow { line: F, a_offset_imm0: F, a_imm1: F, b_offset_imm0: F, b_imm1: F, ind_width: F, op: F, store_offset: F, jmp_offset1: F, jmp_offset2: F, flags: F, }); -pub type RomRomTrace = GenericTrace, 4194304, 0, 5, 0>; +pub type RomRomTrace = GenericTrace, 4194304, 0, 13, 0>; + values!(Dma64AlignedAirValues { - segment_id: F, segment_previous_seq_end: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count: F, segment_previous_is_mem_eq: F, segment_last_seq_end: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count: F, segment_last_is_mem_eq: F, is_last_segment: F, segment_previous_src64: F, segment_last_src64: F, last_count_chunk: [F; 2], padding_size: F, im_direct: [FieldExtension; 5], + segment_id: F, segment_previous_seq_end: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count64: F, segment_previous_flags: F, segment_last_seq_end: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count64: F, segment_last_flags: F, is_last_segment: F, segment_previous_src64: F, segment_last_src64: F, segment_previous_fill_byte: F, segment_last_fill_byte: F, last_count_chunk: [F; 2], padding_size: F, im_direct: [FieldExtension; 5], +}); + +values!(Dma64AlignedInputCpyAirValues { + segment_id: F, segment_previous_seq_end: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count64: F, segment_previous_flags: F, segment_last_seq_end: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count64: F, segment_last_flags: F, is_last_segment: F, last_count_chunk: [F; 2], padding_size: F, im_direct: [FieldExtension; 5], +}); + +values!(Dma64AlignedMemSetAirValues { + segment_id: F, segment_previous_seq_end: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count64: F, segment_previous_flags: F, segment_last_seq_end: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count64: F, segment_last_flags: F, is_last_segment: F, segment_previous_fill_byte: F, segment_last_fill_byte: F, last_count_chunk: [F; 2], padding_size: F, im_direct: [FieldExtension; 5], +}); + +values!(Dma64AlignedMemAirValues { + segment_id: F, segment_previous_seq_end: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count64: F, segment_previous_flags: F, segment_last_seq_end: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count64: F, segment_last_flags: F, is_last_segment: F, segment_previous_src64: F, segment_last_src64: F, segment_previous_fill_byte: F, segment_last_fill_byte: F, last_count_chunk: [F; 2], padding_size: F, im_direct: [FieldExtension; 5], +}); + +values!(Dma64AlignedMemCpyAirValues { + segment_id: F, segment_previous_seq_end: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_count64: F, segment_previous_flags: F, segment_last_seq_end: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_count64: F, segment_last_flags: F, is_last_segment: F, segment_previous_src64: F, segment_last_src64: F, last_count_chunk: [F; 2], padding_size: F, im_direct: [FieldExtension; 5], }); values!(DmaUnalignedAirValues { - segment_id: F, segment_previous_seq_end: F, segment_previous_src64: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_offset: F, segment_previous_count: F, segment_previous_is_mem_eq: F, segment_first_bytes: [F; 8], segment_last_seq_end: F, segment_last_src64: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_offset: F, segment_last_count: F, segment_last_is_mem_eq: F, segment_next_bytes: [F; 8], is_last_segment: F, last_count_chunk: [F; 2], padding_rows: F, im_direct: [FieldExtension; 6], + segment_id: F, segment_previous_seq_end: F, segment_previous_src64: F, segment_previous_dst64: F, segment_previous_main_step: F, segment_previous_offset: F, segment_previous_count: F, segment_first_bytes: [F; 8], segment_last_seq_end: F, segment_last_src64: F, segment_last_dst64: F, segment_last_main_step: F, segment_last_offset: F, segment_last_count: F, segment_next_bytes: [F; 8], is_last_segment: F, segment_previous_is_memeq: F, segment_last_is_memeq: F, padding_size: F, last_count_chunk: [F; 2], im_direct: [FieldExtension; 6], }); values!(MainAirValues { @@ -477,10 +674,34 @@ values!(DmaAirGroupValues { gsum_result: FieldExtension, }); +values!(DmaMemCpyAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(DmaInputCpyAirGroupValues { + gsum_result: FieldExtension, +}); + values!(Dma64AlignedAirGroupValues { gsum_result: FieldExtension, }); +values!(Dma64AlignedInputCpyAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(Dma64AlignedMemSetAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(Dma64AlignedMemAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(Dma64AlignedMemCpyAirGroupValues { + gsum_result: FieldExtension, +}); + values!(DmaUnalignedAirGroupValues { gsum_result: FieldExtension, }); @@ -489,6 +710,14 @@ values!(DmaPrePostAirGroupValues { gsum_result: FieldExtension, }); +values!(DmaPrePostMemCpyAirGroupValues { + gsum_result: FieldExtension, +}); + +values!(DmaPrePostInputCpyAirGroupValues { + gsum_result: FieldExtension, +}); + values!(MainAirGroupValues { gsum_result: FieldExtension, }); @@ -578,324 +807,154 @@ values!(VirtualTable1AirGroupValues { }); pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ - ( - 0, - 0, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[1, 24, 1, 9, 22, 7, 3, 22, 7, 3, 36, 1, 1, 1, 1, 3, 9, 3], - }, - ), - ( - 0, - 1, - PackedInfoConst { - is_packed: true, - num_packed_words: 7, - unpack_info: &[36, 29, 32, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 29, 1], - }, - ), - ( - 0, - 2, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[ - 36, 29, 29, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 32, 32, - ], - }, - ), - ( - 0, - 3, - PackedInfoConst { - is_packed: true, - num_packed_words: 7, - unpack_info: &[ - 36, 29, 29, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, - ], - }, - ), - ( - 0, - 4, - PackedInfoConst { - is_packed: true, - num_packed_words: 14, - unpack_info: &[ - 32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, - 64, 1, 64, 64, 1, 32, 38, 38, 38, 32, 32, 1, 1, 1, - ], - }, - ), - ( - 0, - 6, - PackedInfoConst { - is_packed: true, - num_packed_words: 4, - unpack_info: &[29, 38, 1, 1, 38, 1, 32, 32, 1, 40, 22, 16, 1], - }, - ), - ( - 0, - 7, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[29, 38, 1, 1, 32, 32], - }, - ), - ( - 0, - 8, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[29, 38, 1, 1, 16, 16, 16, 16, 1], - }, - ), - ( - 0, - 9, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[ - 29, 3, 4, 1, 8, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 40, 64, 1, - 32, 32, - ], - }, - ), - ( - 0, - 10, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 1, 32, 32, 8], - }, - ), - ( - 0, - 11, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[1, 1, 1, 32, 32, 16, 8, 8, 29, 40], - }, - ), - ( - 0, - 12, - PackedInfoConst { - is_packed: true, - num_packed_words: 5, - unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 32, 32], - }, - ), - ( - 0, - 13, - PackedInfoConst { - is_packed: true, - num_packed_words: 17, - unpack_info: &[ - 64, 64, 64, 64, 64, 64, 64, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 1, 1, 1, 1, 1, 1, 1, 64, 64, 64, 1, 1, 1, 1, 1, 64, 8, 32, 1, 7, 7, - ], - }, - ), - ( - 0, - 14, - PackedInfoConst { - is_packed: true, - num_packed_words: 4, - unpack_info: &[ - 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, - ], - }, - ), - ( - 0, - 15, - PackedInfoConst { - is_packed: true, - num_packed_words: 4, - unpack_info: &[32, 32, 32, 32, 16, 16, 16, 16, 1, 1], - }, - ), - ( - 0, - 16, - PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[ - 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 1, 32, 32, - ], - }, - ), - ( - 0, - 17, - PackedInfoConst { - is_packed: true, - num_packed_words: 15, - unpack_info: &[ - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, - 40, 1, 1, - ], - }, - ), - ( - 0, - 18, - PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[ - 16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40, - ], - }, - ), - ( - 0, - 19, - PackedInfoConst { - is_packed: true, - num_packed_words: 11, - unpack_info: &[ - 16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, - 1, 1, 64, 64, 64, 64, 64, 64, 40, - ], - }, - ), - ( - 0, - 20, - PackedInfoConst { - is_packed: true, - num_packed_words: 209, - unpack_info: &[ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 8, 40, - ], - }, - ), - ( - 0, - 21, - PackedInfoConst { - is_packed: true, - num_packed_words: 3, - unpack_info: &[ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 40, 1, 1, - ], - }, - ), - ( - 0, - 22, - PackedInfoConst { - is_packed: true, - num_packed_words: 17, - unpack_info: &[ - 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40, - ], - }, - ), -]; + (0, 0, PackedInfoConst { + is_packed: true, + num_packed_words: 8, + unpack_info: &[1, 1, 1, 8, 1, 1, 24, 1, 9, 16, 16, 22, 7, 3, 36, 22, 7, 3, 3, 1, 1, 1, 1, 3, 9, 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 32], + }), + (0, 1, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[1, 1, 24, 1, 9, 22, 7, 3, 36, 22, 7, 3, 3, 1, 1, 1, 1, 3, 9, 32, 32, 32, 32, 32], + }), + (0, 2, PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[1, 1, 24, 1, 9, 22, 7, 3, 36, 1, 1, 1, 3, 9, 32, 32, 32], + }), + (0, 3, PackedInfoConst { + is_packed: true, + num_packed_words: 7, + unpack_info: &[29, 1, 1, 1, 1, 1, 8, 1, 1, 36, 29, 32, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 24, 24, 24, 24, 24, 24, 24, 24, 1, 1, 1, 1], + }), + (0, 4, PackedInfoConst { + is_packed: true, + num_packed_words: 6, + unpack_info: &[1, 1, 1, 36, 29, 32, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 24, 24, 24, 24, 24, 24, 24, 24], + }), + (0, 5, PackedInfoConst { + is_packed: true, + num_packed_words: 2, + unpack_info: &[1, 1, 1, 8, 36, 29, 32, 1, 1, 1, 1, 1, 1, 1], + }), + (0, 6, PackedInfoConst { + is_packed: true, + num_packed_words: 7, + unpack_info: &[29, 1, 1, 1, 1, 1, 8, 1, 36, 29, 32, 1, 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 1, 1, 1, 1], + }), + (0, 7, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[29, 1, 1, 1, 1, 36, 29, 32, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32], + }), + (0, 8, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[36, 29, 29, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 32, 32], + }), + (0, 9, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[36, 29, 3, 3, 1, 1, 1, 1, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 29, 3, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 1, 64, 64, 32, 32, 32, 32, 32, 32], + }), + (0, 10, PackedInfoConst { + is_packed: true, + num_packed_words: 8, + unpack_info: &[36, 29, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 29, 3, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 32], + }), + (0, 11, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[36, 29, 3, 3, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32], + }), + (0, 12, PackedInfoConst { + is_packed: true, + num_packed_words: 14, + unpack_info: &[32, 32, 32, 32, 32, 32, 1, 32, 1, 1, 64, 32, 1, 1, 1, 64, 32, 1, 4, 1, 8, 1, 1, 1, 64, 1, 64, 64, 1, 32, 38, 38, 38, 32, 32, 1, 1, 1], + }), + (0, 14, PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[29, 38, 1, 1, 38, 1, 32, 32, 1, 40, 22, 16, 1], + }), + (0, 15, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[29, 38, 1, 1, 32, 32], + }), + (0, 16, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[29, 38, 1, 1, 16, 16, 16, 16, 1], + }), + (0, 17, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[29, 3, 4, 1, 8, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 40, 64, 1, 32, 32], + }), + (0, 18, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 1, 32, 32, 8], + }), + (0, 19, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[1, 1, 1, 32, 32, 16, 8, 8, 29, 40], + }), + (0, 20, PackedInfoConst { + is_packed: true, + num_packed_words: 5, + unpack_info: &[1, 1, 1, 32, 32, 32, 8, 16, 8, 8, 29, 40, 32, 32], + }), + (0, 21, PackedInfoConst { + is_packed: true, + num_packed_words: 17, + unpack_info: &[64, 64, 64, 64, 64, 64, 64, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 64, 64, 64, 1, 1, 1, 1, 1, 64, 8, 32, 1, 7, 7], + }), + (0, 22, PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1], + }), + (0, 23, PackedInfoConst { + is_packed: true, + num_packed_words: 4, + unpack_info: &[32, 32, 32, 32, 16, 16, 16, 16, 1, 1], + }), + (0, 24, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 1, 32, 32], + }), + (0, 25, PackedInfoConst { + is_packed: true, + num_packed_words: 15, + unpack_info: &[32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 40, 1, 1], + }), + (0, 26, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], + }), + (0, 27, PackedInfoConst { + is_packed: true, + num_packed_words: 11, + unpack_info: &[16, 16, 16, 16, 16, 16, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 64, 64, 64, 64, 64, 64, 40], + }), + (0, 28, PackedInfoConst { + is_packed: true, + num_packed_words: 209, + unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 8, 40], + }), + (0, 29, PackedInfoConst { + is_packed: true, + num_packed_words: 3, + unpack_info: &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 40, 1, 1], + }), + (0, 30, PackedInfoConst { + is_packed: true, + num_packed_words: 17, + unpack_info: &[1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40], + }), +]; \ No newline at end of file diff --git a/pil/zisk.pil b/pil/zisk.pil index bc9b7f129..b39eb5f7a 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -45,12 +45,18 @@ enable_rom_data * (1 - enable_rom_data); proofval enable_dma_64_aligned; enable_dma_64_aligned * (1 - enable_dma_64_aligned); -proofval enable_dma_64_aligned_input; -enable_dma_64_aligned_input * (1 - enable_dma_64_aligned_input); +proofval enable_dma_64_aligned_inputcpy; +enable_dma_64_aligned_inputcpy * (1 - enable_dma_64_aligned_inputcpy); proofval enable_dma_64_aligned_mem; enable_dma_64_aligned_mem * (1 - enable_dma_64_aligned_mem); +proofval enable_dma_64_aligned_memcpy; +enable_dma_64_aligned_memcpy * (1 - enable_dma_64_aligned_memcpy); + +proofval enable_dma_64_aligned_memset; +enable_dma_64_aligned_memset * (1 - enable_dma_64_aligned_memset); + proofval enable_dma_unaligned; enable_dma_unaligned * (1 - enable_dma_unaligned); @@ -73,14 +79,21 @@ airgroup Zisk { virtual DualRange(id: DUAL_RANGE_7_BITS_ID, min1: 0, max1: P2_7-1, min2: 0, max2: P2_7-1) alias DualRange7Bits; virtual DualRange(id: DUAL_RANGE_BYTE_ID, min1: 0, max1: P2_8-1, min2: 0, max2: P2_8-1) alias DualByte; - Dma(); + Dma(N: 2**14); + Dma(N: 2**14, enable: E_DMA_MEMCPY) alias DmaMemCpy; + Dma(N: 2**14, enable: E_DMA_INPUTCPY) alias DmaInputCpy; + + Dma64Aligned(N: 2**14, enable_flag: enable_dma_64_aligned); + Dma64Aligned(N: 2**14, enable: E_DMA_INPUTCPY, enable_flag: enable_dma_64_aligned_inputcpy) alias Dma64AlignedInputCpy; + Dma64Aligned(N: 2**14, enable: E_DMA_MEMSET, enable_flag: enable_dma_64_aligned_memset, op_x_row: 8) alias Dma64AlignedMemSet; + Dma64Aligned(N: 2**14, enable: E_DMA_MEMCPY|E_DMA_MEMCMP|E_DMA_MEMSET, enable_flag: enable_dma_64_aligned_mem) alias Dma64AlignedMem; + Dma64Aligned(N: 2**14, enable: E_DMA_MEMCPY, enable_flag: enable_dma_64_aligned_memcpy, op_x_row: 8) alias Dma64AlignedMemCpy; - Dma64Aligned(enable_flag: enable_dma_64_aligned); - Dma64Aligned(enable: E_DMA_INPUTCPY, enable_flag: enable_dma_64_aligned_input) alias Dma64AlignmentInput; - Dma64Aligned(enable: E_DMA_MEMCPY|E_DMA_MEMCMP|E_DMA_MEMSET, enable_flag: enable_dma_64_aligned_mem) alias Dma64AlignmentMem; + DmaUnaligned(N: 2**14, enable_flag: enable_dma_unaligned); - DmaUnaligned(enable_flag: enable_dma_unaligned); - DmaPrePost(); + DmaPrePost(N: 2**14); + DmaPrePost(N: 2**14, enable: E_DMA_MEMCPY) alias DmaPrePostMemCpy; + DmaPrePost(N: 2**14, enable: E_DMA_INPUTCPY) alias DmaPrePostInputCpy; virtual DmaByteCmpTable(); virtual DmaRom(); diff --git a/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs b/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs index 480ea57f8..cc9061695 100644 --- a/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs +++ b/precompiles/arith_eq/src/mem_inputs/generate_mem_inputs.rs @@ -24,7 +24,7 @@ pub fn generate_mem_inputs( let params_offset = OPERATION_PRECOMPILED_BUS_DATA_SIZE + config.indirect_params; for iparam in 0..config.indirect_params { - MemBusHelpers::mem_aligned_load( + MemBusHelpers::mem_aligned_read( addr_main + iparam as u32 * 8, step_main, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], diff --git a/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs b/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs index 4d890e408..b2c0d5f95 100644 --- a/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs +++ b/precompiles/arith_eq_384/src/mem_inputs/generate_mem_inputs.rs @@ -23,7 +23,7 @@ pub fn generate_mem_inputs( let params_offset = OPERATION_PRECOMPILED_BUS_DATA_SIZE + config.indirect_params; for iparam in 0..config.indirect_params { - MemBusHelpers::mem_aligned_load( + MemBusHelpers::mem_aligned_read( addr_main + iparam as u32 * 8, step_main, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], diff --git a/precompiles/big_int/src/add256_gen_mem_inputs.rs b/precompiles/big_int/src/add256_gen_mem_inputs.rs index db7d9faea..3f6dbc936 100644 --- a/precompiles/big_int/src/add256_gen_mem_inputs.rs +++ b/precompiles/big_int/src/add256_gen_mem_inputs.rs @@ -24,7 +24,7 @@ pub fn generate_add256_mem_inputs( ) { // Start by generating the params (indirection read, direct, indirection write) for iparam in 0..PARAMS { - MemBusHelpers::mem_aligned_load( + MemBusHelpers::mem_aligned_read( addr_main + iparam as u32 * 8, step_main, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], @@ -36,7 +36,7 @@ pub fn generate_add256_mem_inputs( for iparam in 0..READ_PARAMS { let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam] as u32; for ichunk in 0..PARAM_CHUNKS { - MemBusHelpers::mem_aligned_load( + MemBusHelpers::mem_aligned_read( param_addr + ichunk as u32 * 8, step_main, data[START_READ_PARAMS + iparam * PARAM_CHUNKS + ichunk], diff --git a/precompiles/common/src/lib.rs b/precompiles/common/src/lib.rs index b1b2819a7..14b82b66f 100644 --- a/precompiles/common/src/lib.rs +++ b/precompiles/common/src/lib.rs @@ -145,9 +145,9 @@ impl MemProcessor for MemCounterProcessor<'_> { } impl MemBusHelpers { - /// Generates an aligned memory load operation. + /// Generates an aligned memory read operation. /// The address must be 8-byte aligned. - pub fn mem_aligned_load( + pub fn mem_aligned_read( addr: u32, step: u64, mem_value: u64, @@ -211,7 +211,7 @@ impl MemBusHelpers { /// Generates multiple aligned memory load operations from a slice of values. /// The address must be 8-byte aligned. - pub fn mem_aligned_load_from_slice( + pub fn mem_aligned_read_from_slice( addr: u32, step: u64, values: &[u64], @@ -237,6 +237,23 @@ impl MemBusHelpers { assert!(addr % 8 == 0); let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3; for (i, &value) in values.iter().enumerate() { + let data: [u64; 7] = + [MEMORY_STORE_OP, (addr as usize + i * 8) as u64, mem_step, 8, 0, 0, value]; + mem_processor.process_mem_data(&data); + } + } + /// Generates multiple aligned memory write operations with same fill pattern + /// The address must be 8-byte aligned. + pub fn mem_aligned_write_pattern( + addr: u32, + step: u64, + value: u64, + count64: usize, + mem_processor: &mut P, + ) { + assert!(addr % 8 == 0); + let mem_step = MEM_STEP_BASE + MAX_MEM_OPS_BY_MAIN_STEP * step + 3; + for i in 0..count64 { let data: [u64; 7] = [MEMORY_STORE_OP, (addr as usize + i * 8) as u64, mem_step, 8, 0, 0, value]; diff --git a/precompiles/dma/Cargo.toml b/precompiles/dma/Cargo.toml index 7c197b12b..d36685c30 100644 --- a/precompiles/dma/Cargo.toml +++ b/precompiles/dma/Cargo.toml @@ -31,8 +31,12 @@ generic-array = "0.14" default = [] dma_memcmp = [] debug_dma = [] +save_dma_collectors = [] +save_dma_inputs = [] +save_dma_plans = [] +debug_dma_gen_mem_inputs = [] gpu = ["proofman-common/gpu", "packed"] packed = ["proofman-common/packed"] no_lib_link = ["proofman-common/no_lib_link"] diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] \ No newline at end of file +disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] diff --git a/precompiles/dma/pil/dma.pil b/precompiles/dma/pil/dma.pil index 212414481..dbfabbc45 100644 --- a/precompiles/dma/pil/dma.pil +++ b/precompiles/dma/pil/dma.pil @@ -28,8 +28,6 @@ const int OP_DMA_POST_XMEMSET = OP_DMA_PRE_XMEMSET + OP_DMA_POST_OFFSET; airtemplate Dma(int N = 2**21, const int RC = 2, - const int op_x_row = 4, - const int selectors = 1, const int enable = E_DMA_ALL, const int enable_extended = 1) { @@ -40,7 +38,6 @@ airtemplate Dma(int N = 2**21, assert(enable_extended == 0 || enable_extended == 1); assert((enable_memcpy + enable_memcmp + enable_inputcpy + enable_memset) > 0); - assert(op_x_row >= 1); assert(RC == 2); const int has_src = (enable_memcpy || enable_memcmp); @@ -213,6 +210,8 @@ airtemplate Dma(int N = 2**21, const expr air.sel_inputcpy = 0; } + const expr use_src = sel_memcpy + sel_memcmp; + const expr bus_op = _bus_op; const expr loop_op = _loop_op; const expr pre_op = _pre_op; @@ -313,6 +312,9 @@ airtemplate Dma(int N = 2**21, const expr air.src = h_src64 * 2**10 + l_src64 * 2**3 + src_offset; const expr air.src64 = h_src64 * 2**10 + l_src64 * 2**3; + if (enable_memset) { + (sel_memset + sel_inputcpy) * src_offset_after_pre === 0; + } range_check(expression: h_src64, min: 0, max: 2**22-1, sel: sel); lookup_assumes(DUAL_RANGE_7_BITS_ID, expressions: [l_src64, l_dst64], sel: sel); @@ -355,6 +357,7 @@ airtemplate Dma(int N = 2**21, pre_result_nz * post_result_nz === 0; + // previous constraint => result_nz * (1 - result_nz) === 0; result_nz = pre_result_nz + post_result_nz; col witness bits(32) air.bus_pre_result[2]; @@ -403,7 +406,14 @@ airtemplate Dma(int N = 2**21, const int air.bus_post_result[2] = [0, 0]; } - const expr flags = use_pre + use_loop * 2 + use_post * 4 + src64_inc_by_pre * 8 + count_lt_256 * 16 + result_nz * 32; + const expr flags = use_pre * DMA_ROM_USE_PRE_F + + use_loop * DMA_ROM_LOOP_F + + use_post * DMA_ROM_USE_POST_F + + src64_inc_by_pre * DMA_ROM_SRC64_INC_BY_PRE_F + + count_lt_256 * DMA_ROM_COUNT_LT_256_F + + result_nz * DMA_ROM_NEQ_F + + use_src * DMA_ROM_USE_SRC_F; + lookup_assumes(DMA_ROM_ID, expressions: [dst_offset, src_offset, l_count, flags, pre_count, src_offset_after_pre, l_count64], sel: sel); // OP_DMA_MEMSET isn't implemented because usually memset used with static value known @@ -470,6 +480,8 @@ airtemplate Dma(int N = 2**21, // OP_DMA_PRE_XMEMCMP (dst, src, dst_offset, src_offset, pre_count, main_step, 0, r_nz, R0, R1) // OP_DMA_PRE_XMEMSET (dst, 0, dst_offset, 0, pre_count, main_step, value) + println(_pre_op); + println(pre_op); permutation_assumes(DMA_BUS_ID, [pre_op, dst64, src64, diff --git a/precompiles/dma/pil/dma_64_aligned.pil b/precompiles/dma/pil/dma_64_aligned.pil index 5379fa949..a44a2d3dc 100644 --- a/precompiles/dma/pil/dma_64_aligned.pil +++ b/precompiles/dma/pil/dma_64_aligned.pil @@ -20,6 +20,7 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance const int enable_memcmp = (enable & E_DMA_MEMCMP) ? 1 : 0; const int enable_inputcpy = (enable & E_DMA_INPUTCPY) ? 1 : 0; const int enable_memset = (enable & E_DMA_MEMSET) ? 1 : 0; + const int enables_count = enable_memcpy + enable_memcmp + enable_inputcpy + enable_memset; if (enable_count_load == -1) { enable_count_load = enable_memcpy; @@ -101,8 +102,8 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance // sel_xmemcpy 1 ┐ // sel_memeq 1 │ // sel_memset 1 ├ @[flags] → continuations (latch) - // sel_memcpy_count_load 1 │ // sel_inputcpy 1 ┘ + // sel_memcpy_count_load 1 this only was active first clock // ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ // b0 32 intermediate // extended_arg 32 intermediate @@ -152,26 +153,35 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance // temporal building expression for segment flags expr _flags = 0; + + expr _sel_mem_load = 0; const int enable_full = enable_memcpy && enable_memcmp && enable_memset && enable_inputcpy && enable_count_load; + const int F_SEL_MEMCPY = 1; + const int F_SEL_MEMCMP = 2; + const int F_SEL_INPUTCPY = 4; + const int F_SEL_MEMSET = 8; + if (enable_memcpy) { - col witness bits(1) air.sel_xmemcpy; - sel_xmemcpy * (1 - sel_xmemcpy) === 0; - _sel += sel_xmemcpy; - _op += sel_xmemcpy * OP_DMA_XMEMCPY; - _flags += sel_xmemcpy; + col witness bits(1) air.sel_memcpy; + sel_memcpy * (1 - sel_memcpy) === 0; + _sel += sel_memcpy; + _sel_mem_load += sel_memcpy; + _op += sel_memcpy * OP_DMA_XMEMCPY; + _flags += F_SEL_MEMCPY * sel_memcpy; } else { - const expr air.sel_xmemcpy = 0; + const expr air.sel_memcpy = 0; } if (enable_memcmp) { col witness bits(1) air.sel_memeq; sel_memeq * (1 - sel_memeq) === 0; _sel += sel_memeq; + _sel_mem_load += sel_memeq; _op += sel_memeq * OP_DMA_XMEMEQ; - _flags += 2 * sel_memeq; + _flags += F_SEL_MEMCMP * sel_memeq; } else { const expr air.sel_memeq = 0; } @@ -203,7 +213,7 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance _sel += sel_memset; _op += sel_memset * OP_DMA_XMEMSET; - _flags += 2 * sel_memset; + _flags += F_SEL_MEMSET * sel_memset; } else { const int air.sel_memset = 0; const int air.fill_byte = 0; @@ -215,9 +225,15 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance if (enable_count_load) { col witness bits(1) air.sel_memcpy_count_load; sel_memcpy_count_load * (1 - sel_memcpy_count_load) === 0; - _sel += sel_memcpy_count_load; - _op += sel_memcpy_count_load * OP_DMA_MEMCPY; - _flags += 4 * sel_memcpy_count_load; + + // enable read the counter from memory only could be active if operation memcpy is active. + sel_memcpy_count_load * (1 - sel_memcpy) === 0; + + // this selector only could be active the first clock of operation, after that must be 0. + sel_memcpy_count_load * (1 - previous_seq_end) === 0; + + // the selector "update" the operation from XMEMCPY to regular MEMCPY + _op += sel_memcpy_count_load * (OP_DMA_MEMCPY - OP_DMA_XMEMCPY); } else { const int air.sel_memcpy_count_load = 0; } @@ -228,16 +244,15 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance _sel += sel_inputcpy; _op += sel_inputcpy * OP_DMA_INPUTCPY; - _flags += 8 * sel_memset; + _flags += F_SEL_INPUTCPY * sel_inputcpy; } else { const expr air.sel_inputcpy = 0; } - const expr sel_memcpy = sel_xmemcpy + sel_memcpy_count_load; - const expr op = _op; const expr flags = _flags; const expr sel = _sel; + const expr sel_mem_load = _sel_mem_load; sel * (1 - sel) === 0; // Binary constraints @@ -375,9 +390,9 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance for (int i = 0; i < op_x_row; i++) { value[i][0] = l_value_chunks[i][0] + h_value_chunks[i][0] * P2_8; value[i][1] = l_value_chunks[i][1] + h_value_chunks[i][1] * P2_8; - range_check(h_value_chunks[i][0], 0, P2_24-1); - range_check(h_value_chunks[i][1], 0, P2_24-1); - range_dual_byte(l_value_chunks[i][0], l_value_chunks[i][1], sel_op[i]); + range_check(h_value_chunks[i][0], 0, P2_24-1, sel_op[i]); + range_check(h_value_chunks[i][1], 0, P2_24-1, sel_op[i]); + range_dual_byte(l_value_chunks[i][1], l_value_chunks[i][0], sel_op[i]); } } else if (has_src) { col witness bits(32) air.value[op_x_row][RC]; @@ -392,16 +407,41 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance assert(false, "Invalid case"); } + if (enable_memset && has_src) { + // a new col must be separated for memload. + col witness bits(1) air.sel_op_mem_load[op_x_row]; + } else { + const expr air.sel_op_mem_load[op_x_row]; + } + for (int i = 0; i < op_x_row; i++) { if (has_src) { + if (enable_memset) { + sel_op_mem_load[i] * (1 - sel_op_mem_load[i]) === 0; + sel_op_mem_load[i] <== sel_op[i] * sel_mem_load; + } else { + sel_op_mem_load[i] = sel_op[i]; + } precompiled_mem_load( - sel: sel_op[i], + sel: sel_op_mem_load[i], main_step:, addr: src64 * 8 + 8 * i, value: value[i] ); } + + if (enable_memset && enables_count > 1) { + // add constraint to force that memset operation value "read" must be fill word + // only if this instance isn't a dedicated memset instance. In case of dedicated + // instance value really was directly fill_dword. + sel_memset * (value[i][0] - fill_dword) === 0; + sel_memset * (value[i][1] - fill_dword) === 0; + } + + // load previous value before write operation, because only some bytes must be written + // only in case of memcpy, inputcpy and memset really write to memory, in memcmp do + // a read with same value, this value must match with src and dst. precompiled_mem_op( is_write: sel_memcpy + sel_memset + sel_inputcpy, sel: sel_op[i], @@ -421,18 +461,24 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance // // If not finish sequence in previous row, means current it's a continuation of previous sequence // count64 match with previous value plus ops_selected. - continue_seq_on_l1 * (count64 - (segment_previous_count64 - ops_selected)) == 0; - continue_seq_on_no_l1 * (count64 - ('count64 - ops_selected)) === 0; + continue_seq_on_l1 * (count64 - (segment_previous_count64 - op_x_row)) === 0; + continue_seq_on_no_l1 * (count64 - ('count64 - op_x_row)) === 0; // If finish sequence count64 match with ops_selected, because final count64 must be 0. // new_seq_on_l1 * (count64 - ops_selected) === 0; // new_seq_on_no_l1 * (count64 - ops_selected) === 0; seq_end * (count64 - ops_selected) === 0; + (1 - seq_end) * (ops_selected - op_x_row) === 0; // If previous row was a seq_end, start with new dst/src addresses, else increment by ops_selected - continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + ops_selected)) === 0; // continuation: L1 @[dst64_cont] - continue_seq_on_no_l1 * (dst64 - ('dst64 + ops_selected)) === 0; // continuation: !L1 @[dst64_cont] + continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + op_x_row)) === 0; // continuation: L1 @[dst64_cont] + continue_seq_on_no_l1 * (dst64 - ('dst64 + op_x_row)) === 0; // continuation: !L1 @[dst64_cont] + if (has_src) { + continue_seq_on_l1 * (src64 - (segment_previous_src64 + op_x_row)) === 0; + continue_seq_on_no_l1 * (src64 - ('src64 + op_x_row)) === 0; + } + // NOTE: transition of src64 is defined only // LATCHS: @@ -526,7 +572,19 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance } else if (!extended_arg_could_be_count) { _extended_arg = fill_byte; } else { - _extended_arg = count64 * 8 * (sel_xmemcpy + sel_memeq) + fill_byte; + // sel_memcpy_count_load only could be active when sel_memcpy is active, means that + // this pair of selector take following values: + // + // ┌─ sel_memcpy + // │ ┌─── sel_memcpy_count_load + // │ │ ┌─── sel_memcpy - sel_memcpy_count_load + // + // 0 0 0 disable, only active if it's memcmp(memeq) + // 0 1 -1 not allowed by constraints + // 1 0 1 means a xmemcpy, the count must be on extended_arg + // 1 1 1 means memcpy, the count value must be read from memory + + _extended_arg = count64 * 8 * (sel_memcpy - sel_memcpy_count_load + sel_memeq) + fill_byte; } if (degree(_extended_arg) > max_bus_op_degree) { @@ -549,25 +607,7 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance // Used to cancel padding operations assumes_padding_operation(op: 0, padding_size: padding_size); - - // TRANSITIONS - // dst64 - // src64 - // count64 - // - if (has_src) { - continue_seq_on_l1 * (src64 - (segment_previous_src64 + op_x_row)) === 0; - continue_seq_on_no_l1 * (src64 - ('src64 + ops_selected)) === 0; - } - - continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + op_x_row)) === 0; - continue_seq_on_no_l1 * (dst64 - ('dst64 + op_x_row)) === 0; - - continue_seq_on_l1 * (count64 - (segment_previous_count64 + op_x_row)) === 0; - continue_seq_on_no_l1 * (count64 - ('count64 + ops_selected)) === 0; - - (1 - seq_end) * (ops_selected - op_x_row) === 0; - + // FUTURE IMPROVEMENTS // // Delegate/Split a operation, if a operation uses more than 1 row, the final row could @@ -575,4 +615,8 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance // instance large and cheap instances, without selectors, that only admins full rows, and // the remaing part is done by other small instantation that has selectors. With this design // the cost of one memcpy could be near 6-7 columns + costs of memory. + // + // Analyze if it's possible that the equal memcmp could be done directly by Dma64Aligned without + // using controller, pre or post. + } \ No newline at end of file diff --git a/precompiles/dma/pil/dma_pre_post.pil b/precompiles/dma/pil/dma_pre_post.pil index 7333d7363..8d6b266ed 100644 --- a/precompiles/dma/pil/dma_pre_post.pil +++ b/precompiles/dma/pil/dma_pre_post.pil @@ -21,6 +21,7 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance const int has_src = (enable_memcpy || enable_memcmp); const int enabled_count = enable_memcpy + enable_memcmp + enable_inputcpy + enable_memset; + const int enable_write_mem = enable_memcpy || enable_inputcpy || enable_memset; const int enabled_all = enabled_count == 4; col witness bits(MAIN_STEP_BITS) main_step; @@ -37,16 +38,24 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance // enable_inputcpy x x ✓ x ✓ // enable_memset x x x x ✓ + col witness bits(1) is_post; + is_post * (1 - is_post) === 0; + // prepare operation selectors expr _sel = 0; + expr _sel_write = 0; + expr _sel_byte_rc = 0; // definition @[sel_memcpy] - + expr _pre_op = 0; if (enable_memcpy) { col witness bits(1) air.sel_memcpy; sel_memcpy * (1 - sel_memcpy) === 0; _sel += sel_memcpy; + _sel_write += sel_memcpy; + _sel_byte_rc += sel_memcpy; + _pre_op += sel_memcpy * OP_DMA_PRE_XMEMCPY; } else { const int air.sel_memcpy = 0; } @@ -57,7 +66,10 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance col witness bits(1) air.sel_memcmp; sel_memcmp * (1 - sel_memcmp) === 0; _sel += sel_memcmp; + // @[_sel_write] it's active for sel_memcmp because read the same that preloads before read, + // only need to verify that the value will written and pre read value are the same + _sel_byte_rc += sel_memcmp; col witness bits(1) air.memcmp_result_nz; memcmp_result_nz * (1 - memcmp_result_nz) === 0; // memcmp_result_nz only could be 1 if it's an memcmp operation @@ -66,6 +78,8 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance col witness bits(32) air.l_memcmp_result; // memcmp_result_nz is 0 the memcmp_result must be 0 (1 - memcmp_result_nz) * l_memcmp_result === 0; + + _pre_op += sel_memcmp * OP_DMA_PRE_XMEMCMP; } else { const int air.sel_memcmp = 0; const int air.memcmp_result[2] = [0, 0]; @@ -77,6 +91,9 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance col witness bits(1) air.sel_inputcpy; sel_inputcpy * (1 - sel_inputcpy) === 0; _sel += sel_inputcpy; + _sel_write += sel_inputcpy; + _sel_byte_rc += sel_inputcpy; + _pre_op += sel_inputcpy * OP_DMA_PRE_INPUTCPY; } else { const int air.sel_inputcpy = 0; } @@ -87,20 +104,30 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance col witness bits(1) air.sel_memset; sel_memset * (1 - sel_memset) === 0; _sel += sel_memset; + _sel_write += sel_memset; + _pre_op += sel_memset * OP_DMA_PRE_XMEMSET; } else { const int air.sel_memset = 0; } - + // definition @[sel] const expr sel = _sel; + + // sel_write and sel_byte_rc are a subset of sel, how sel is binary sel_write and sel_byte_rc + // are also binary + const expr sel_write = _sel_write; + const expr sel_byte_rc = _sel_byte_rc; + if (enabled_count > 1) { sel * (1 - sel) === 0; } + const expr pre_post_op = _pre_op + is_post * OP_DMA_POST_OFFSET; + // Prepare source and flags to selected the "row" const int RB; - expr _rom_flags = 0; + expr rom_flags = 0; if (has_src) { col witness bits(1) air.selr[7]; @@ -119,6 +146,7 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance const expr air.sr_0to6 = _sr_0to6; _sr_value = _sr_value + 7 * sr[7]; + const expr air.load_src = sel_memcpy + sel_memcmp; const expr sr_value = _sr_value; sr_0to6 * (1 - sr_0to6) === 0; @@ -139,10 +167,16 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance enabled_second_read * (1 - sel) === 0; // update rom flags + // sr_value is a expression of bits for this reason range check of sr_value is restricted + // to range [0, 7] - _rom_flags += DMA_PRE_POST_DST_OFFSET_GT_SRC_F * dst_offset_gt_src_offset + + println(rom_flags); + rom_flags += DMA_PRE_POST_DST_OFFSET_GT_SRC_F * dst_offset_gt_src_offset + DMA_PRE_POST_ENABLED_SECOND_READ_F * enabled_second_read + - DMA_PRE_POST_SR_VALUE_F * sr_value; + DMA_PRE_POST_SR_VALUE_F * sr_value + + DMA_PRE_POST_IS_POST * is_post + + DMA_PRE_POST_LOAD_SRC * load_src; + println(rom_flags); RB = RC * 4 * 2; } else { @@ -155,32 +189,29 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance } } + if (enable_memset) { + // if memset it's enable, take first rb as fill_free, and define a constraint + // to be sure that all are equals (sr must be 0). + col witness bits(8) air.fill_byte; + (1 - sel_memset) * fill_byte === 0; + } + const int RV = RB / 4; if (RB > 0) { col witness bits(8) air.rb[RB]; const expr air.r_values[RV]; for (int i = 0; i < RB; i+=2) { - range_dual_byte(rb[i+1], rb[i], sel); + range_dual_byte(rb[i+1], rb[i], sel_byte_rc); + if (enable_memset) { + sel_memset * (rb[i] - fill_byte) === 0; + sel_memset * (rb[i+1] - fill_byte) === 0; + } } for (int i = 0; i < RV; ++i) { r_values[i] = rb[i*4] + P2_8 * rb[i*4+1] + P2_16 * rb[i*4+2] + P2_24 * rb[i*4+3]; } - if (enable_memset) { - // if memset it's enable, take first rb as fill_free, and define a constraint - // to be sure that all are equals (sr must be 0). - const expr air.fill_byte = rb[0]; - const int _len = RC * 4; - for (int i = 1; i < _len; ++i) { - sel_memset * (rb[i] - rb[0]) === 0; - } - } - } else if (enable_memset) { - // if memset it's enable, take first rb as fill_free, and define a constraint - // to be sure that all are equals (sr must be 0). - col witness bits(8) air.fill_byte; - (1 - sel_memset) * fill_byte === 0; } const int PRB = RC * 4; // Pre-write bytes @@ -188,19 +219,20 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance const expr p_values[RC]; - for (int i = 0; i < RC; i+=2) { + for (int i = 0; i < PRB; i+=2) { range_dual_byte(pb[i+1], pb[i], sel); } for (int i = 0; i < RC; ++i) { p_values[i] = pb[i*4] + P2_8 * pb[i*4+1] + P2_16 * pb[i*4+2] + P2_24 * pb[i*4+3]; } + println(rom_flags); col witness bits(1) sb[8]; for (int i = 0; i < 8; ++i) { sb[i] * (1 - sb[i]) === 0; - _rom_flags += sb[i] * (1 << (7-i)); + rom_flags += sb[i] * (1 << (7-i)); } - + println(rom_flags); if (enable_memcmp) { // The DMA controller send throw bus, an flag @[memcmp_nz] to indicate that operation // must detect that last byte of count was different. This flag only could be active @@ -251,10 +283,10 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance const expr air.memcmp_result[2]; memcmp_result[0] = l_memcmp_result; memcmp_result[1] = 0xFFFF_FFFF * memcmp_result_is_negative; - - _rom_flags += DMA_PRE_POST_MEMCMP_RESULT_IS_NEG_F * memcmp_result_is_negative + - DMA_PRE_POST_MEMCMP_RESULT_NZ_F * memcmp_result_nz; - + println(rom_flags); + rom_flags += DMA_PRE_POST_MEMCMP_RESULT_IS_NEG_F * memcmp_result_is_negative + + DMA_PRE_POST_MEMCMP_RESULT_NZ_F * memcmp_result_nz; + println(rom_flags); lookup_assumes(DMA_BYTE_CMP_TABLE_ID, [last_dst_byte, memcmp_result_is_negative, abs_diff_dst_src @@ -371,11 +403,9 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance // Read previous value before write // Write final value - precompiled_mem_load(sel: sel_memcpy + sel_memcmp, main_step:, addr: src64 * 8, value: [r_values[0], r_values[1]]); + precompiled_mem_load(sel: load_src, main_step:, addr: src64 * 8, value: [r_values[0], r_values[1]]); precompiled_mem_load(sel: enabled_second_read, main_step:, addr: src64 * 8 + 8, value: [r_values[2], r_values[3]]); - - } else if (enable_inputcpy) { // at this point memcpy, memcmp are disabled, but enable_memset could be enabled expr _write_value_0 = 0; @@ -405,8 +435,18 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance // Read previous value before write // Write final value + // this load is always used to read previous value that was partially modified or verified precompiled_mem_load(sel:, main_step:, addr: dst64 * 8, value: p_values); - precompiled_mem_store(sel:, main_step:, addr: dst64 * 8, value: bus_write_value); + + if (enable_write_mem) { + // when is a memcmp reads, no writes + precompiled_mem_store(sel: sel_write, main_step:, addr: dst64 * 8, value: bus_write_value); + } + if (enable_memcmp) { + // for memcmp we need to verify that value read from memory are the same that value generated to write, because this value is the one that "compensate" the difference between src and dst, if this value is different that value read from memory, the memcmp result will be different that expected. + sel_memcmp * (p_values[0] - bus_write_value[0]) === 0; + sel_memcmp * (p_values[1] - bus_write_value[1]) === 0; + } // DMA ===> DMA_PRE_POST (PRE) // -------------------------------------------------------------------------------- @@ -417,7 +457,7 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance // OP_DMA_PRE_XMEMSET (dst, 0, dst_offset, 0, pre_count, main_step, value) // Send operation to bus - permutation_proves(DMA_BUS_ID, [DMA_MEM_PRE_POST, + permutation_proves(DMA_BUS_ID, [pre_post_op, dst64 * 8, src64 * 8, dst_offset, @@ -437,6 +477,9 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance // DMA_PRE_POST_MEMCMP_RESULT_IS_NEG_F = P2_13 @[memcmp_result_is_negative] (1 bit) // DMA_PRE_POST_MEMCMP_RESULT_NZ = P2_14 @[memcmp_result_nz] (1 bit) - const expr rom_flags = _rom_flags; + println(rom_flags); lookup_assumes(DMA_PRE_POST_TABLE_ID, [rom_flags, dst_offset, src_offset, count, ...diff_factor], sel: sel); + + // TBO: For single memcmp operation, we only need two reads, and after generate two values to compare, less + // columns. } \ No newline at end of file diff --git a/precompiles/dma/pil/dma_pre_post_table.pil b/precompiles/dma/pil/dma_pre_post_table.pil index 5afbcd775..acf703554 100644 --- a/precompiles/dma/pil/dma_pre_post_table.pil +++ b/precompiles/dma/pil/dma_pre_post_table.pil @@ -1,12 +1,14 @@ require "std_lookup.pil" -const int DMA_PRE_POST_TABLE_SIZE = 280 * 3; +const int DMA_PRE_POST_TABLE_SIZE = 288 * 4; const int DMA_PRE_POST_ENABLED_SECOND_READ_F = P2_8; const int DMA_PRE_POST_DST_OFFSET_GT_SRC_F = P2_9; const int DMA_PRE_POST_SR_VALUE_F = P2_10; const int DMA_PRE_POST_MEMCMP_RESULT_IS_NEG_F = P2_13; const int DMA_PRE_POST_MEMCMP_RESULT_NZ_F = P2_14; +const int DMA_PRE_POST_IS_POST = P2_15; +const int DMA_PRE_POST_LOAD_SRC = P2_16; airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { col fixed FLAGS; @@ -23,8 +25,8 @@ airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { // DMA_PRE_POST_DST_OFFSET_GT_SRC_F = P2_9 OUT @[dst_offset_gt_src_offset] (1 bit) // DMA_PRE_POST_SR_VALUE_F = P2_10 OUT @[sr_value] (0-7) (3 bits) // DMA_PRE_POST_MEMCMP_RESULT_IS_NEG_F = P2_13 IN @[memcmp_result_is_negative] (1 bit) - // DMA_PRE_POST_MEMCMP_RESULT_NZ_F = P2_14 IN @[memcmp_result_nz] (1 bit) - + // DMA_PRE_POST_MEMCMP_RESULT_NZ_F = P2_14 IN @[memcmp_result_nz] (1 bit) + // DMA_PRE_POST_IS_POST = P2_15 IN @[is_post] (1 bit) // POST OPERATION objective execute the last incomplete "write": // dst_offset = 0 // count ∈ [1,7] @@ -33,25 +35,16 @@ airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { const int flags_memcmp_result_positive = DMA_PRE_POST_MEMCMP_RESULT_NZ_F; const int flags_memcmp_result_negative = DMA_PRE_POST_MEMCMP_RESULT_NZ_F + DMA_PRE_POST_MEMCMP_RESULT_IS_NEG_F; - const int l_factors_positive[8]; - const int l_factors_negative[8]; - const int h_factors_positive[8]; - const int h_factors_negative[8]; - - for (int count = 0; count < 8; ++ count) { - const int l_factor = (1 << (8 * count)); - const int h_factor = (1 << (8 * count)); - - l_factors_positive[count] = count < 4 ? l_factor : 0; - l_factors_negative[count] = count < 4 ? -l_factor : 0; - h_factors_positive[count] = count >= 4 ? h_factor : 0; - h_factors_negative[count] = count >= 4 ? -h_factor : 0; - } + const int l_factors[8] = [ 1, P2_8, P2_16, P2_24, 0, 0, 0, 0]; + const int h_factors[8] = [ 0, 0, 0, 0, 1, P2_8, P2_16, P2_24]; + for (int src_offset = 0; src_offset < 8; ++src_offset) { table_offsets[src_offset] = index; - for (int count = 1; count < 8; ++ count) { - for (int k = 0; k < 3; ++k) { + // For the edge case of memcmp when dst_offset = 0 and count is a multiple of 8 that need + // to be validate for DmaPrePost, at least if last byte is the first different. + for (int count = 1; count < 9; ++ count) { + for (int k = 0; k < 4; ++k) { const int lindex = index + k; SRC_OFFSET[lindex] = src_offset; DST_OFFSET[lindex] = 0; @@ -64,21 +57,30 @@ airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { const int enabled_second_read = (src_offset + count) > 8 ? 1:0; // selr_value = src_offset // dst_offset_gt_src_offset = 0 - expr flags = selectors + DMA_PRE_POST_ENABLED_SECOND_READ_F * enabled_second_read - + DMA_PRE_POST_SR_VALUE_F * src_offset; - FLAGS[index] = flags; - FLAGS[index + 1] = flags + flags_memcmp_result_positive; - FLAGS[index + 2] = flags + flags_memcmp_result_negative; + expr flags_load_src = selectors + DMA_PRE_POST_ENABLED_SECOND_READ_F * enabled_second_read + + DMA_PRE_POST_LOAD_SRC + + DMA_PRE_POST_SR_VALUE_F * src_offset + + DMA_PRE_POST_IS_POST; // dst_offset = 0 ==> post operation + + expr flags_no_load_src = selectors + DMA_PRE_POST_SR_VALUE_F * src_offset + + DMA_PRE_POST_IS_POST; // dst_offset = 0 ==> post operation + + FLAGS[index] = flags_load_src; + FLAGS[index + 1] = flags_load_src + flags_memcmp_result_positive; + FLAGS[index + 2] = flags_load_src + flags_memcmp_result_negative; + FLAGS[index + 3] = flags_no_load_src; const int factor_index = count - 1; L_FACTOR[index] = 0; - L_FACTOR[index + 1] = l_factors_positive[factor_index]; - L_FACTOR[index + 2] = l_factors_negative[factor_index]; + L_FACTOR[index + 1] = l_factors[factor_index]; + L_FACTOR[index + 2] = -l_factors[factor_index]; + L_FACTOR[index + 3] = 0; H_FACTOR[index] = 0; - H_FACTOR[index + 1] = h_factors_positive[factor_index]; - H_FACTOR[index + 2] = h_factors_negative[factor_index]; - index += 3; + H_FACTOR[index + 1] = h_factors[factor_index]; + H_FACTOR[index + 2] = -h_factors[factor_index]; + H_FACTOR[index + 3] = 0; + index += 4; } } @@ -91,7 +93,7 @@ airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { for (int src_offset = 0; src_offset < 8; ++src_offset) { table_offsets[dst_offset * 8 + src_offset] = index; for (int count = 1; count < (9 - dst_offset); ++ count) { - for (int k = 0; k < 3; ++k) { + for (int k = 0; k < 4; ++k) { const int lindex = index + k; SRC_OFFSET[lindex] = src_offset; DST_OFFSET[lindex] = dst_offset; @@ -107,24 +109,32 @@ airtemplate DmaPrePostTable(int N = DMA_PRE_POST_TABLE_SIZE) { const int selr_value = dst_offset > src_offset ? dst_offset - src_offset: src_offset - dst_offset; // selr_value = src_offset // dst_offset_gt_src_offset = 0 - expr flags = selectors + DMA_PRE_POST_ENABLED_SECOND_READ_F * enabled_second_read - + DMA_PRE_POST_DST_OFFSET_GT_SRC_F * dst_offset_gt_src_offset - + DMA_PRE_POST_SR_VALUE_F * selr_value; - FLAGS[index] = flags; - FLAGS[index + 1] = flags + flags_memcmp_result_positive; - FLAGS[index + 2] = flags + flags_memcmp_result_negative; + expr flags_load_src = selectors + DMA_PRE_POST_ENABLED_SECOND_READ_F * enabled_second_read + + DMA_PRE_POST_DST_OFFSET_GT_SRC_F * dst_offset_gt_src_offset + + DMA_PRE_POST_LOAD_SRC + + DMA_PRE_POST_SR_VALUE_F * selr_value; + + expr flags_no_load_src = selectors + DMA_PRE_POST_DST_OFFSET_GT_SRC_F * dst_offset_gt_src_offset + + DMA_PRE_POST_SR_VALUE_F * selr_value; + + FLAGS[index] = flags_load_src; + FLAGS[index + 1] = flags_load_src + flags_memcmp_result_positive; + FLAGS[index + 2] = flags_load_src + flags_memcmp_result_negative; + FLAGS[index + 3] = flags_no_load_src; const int factor_index = dst_offset + count - 1; L_FACTOR[index] = 0; - L_FACTOR[index + 1] = l_factors_positive[factor_index]; - L_FACTOR[index + 2] = l_factors_negative[factor_index]; + L_FACTOR[index + 1] = l_factors[factor_index]; + L_FACTOR[index + 2] = -l_factors[factor_index]; + L_FACTOR[index + 3] = 0; H_FACTOR[index] = 0; - H_FACTOR[index + 1] = h_factors_positive[factor_index]; - H_FACTOR[index + 2] = h_factors_negative[factor_index]; + H_FACTOR[index + 1] = h_factors[factor_index]; + H_FACTOR[index + 2] = -h_factors[factor_index]; + H_FACTOR[index + 3] = 0; - index += 3; + index += 4; } } } diff --git a/precompiles/dma/pil/dma_rom.pil b/precompiles/dma/pil/dma_rom.pil index c21f7b55b..29d3f5aa6 100644 --- a/precompiles/dma/pil/dma_rom.pil +++ b/precompiles/dma/pil/dma_rom.pil @@ -1,6 +1,15 @@ require "std_lookup.pil" -const int DMA_ROM_TABLE_SIZE = 8 * 8 * P2_9; +// 3+3+9+1 = 16 bits = 2^16 TABLE +const int DMA_ROM_TABLE_SIZE = 8 * 8 * P2_9 * 3; + +const int DMA_ROM_USE_PRE_F = 1; +const int DMA_ROM_LOOP_F = 2; +const int DMA_ROM_USE_POST_F = 4; +const int DMA_ROM_SRC64_INC_BY_PRE_F = 8; +const int DMA_ROM_COUNT_LT_256_F = 16; +const int DMA_ROM_NEQ_F = 32; +const int DMA_ROM_USE_SRC_F = 64; airtemplate DmaRom(int N = DMA_ROM_TABLE_SIZE) { @@ -13,35 +22,51 @@ airtemplate DmaRom(int N = DMA_ROM_TABLE_SIZE) { col fixed L_COUNT64; int i = 0; - for (int dst_offset = 0; dst_offset < 8; ++dst_offset) { - for (int src_offset = 0; src_offset < 8; ++src_offset) { - for (int l_count = 0; l_count < P2_9; ++l_count) { - DST_OFFSET[i] = dst_offset; - SRC_OFFSET[i] = src_offset; - - if (l_count == 0) { - L_COUNT[i] = 0; - FLAGS[i] = 16; - PRE_COUNT[i] = 0; - SRC_OFFSET_AFTER_PRE[i] = src_offset; - L_COUNT64[i] = 0; - } else { - const int use_pre = dst_offset > 0; - const int pre_count = use_pre ? (((8 - dst_offset) < l_count) ? (8 - dst_offset) : l_count) : 0; - const int post_count = (l_count - pre_count) % 8; - const int use_post = post_count > 0; - const int memcpy_count = l_count - pre_count - post_count; - const int use_memcpy = memcpy_count > 0; - const int src64_inc_by_pre = (use_pre && (src_offset + pre_count) >= 8) ? 1 : 0; - const int count_lt_256 = l_count < 256 ? 1 : 0; - - L_COUNT[i] = l_count; - FLAGS[i] = use_pre + use_memcpy * 2 + use_post * 4 + src64_inc_by_pre * 8 + count_lt_256 * 16; - PRE_COUNT[i] = pre_count; - SRC_OFFSET_AFTER_PRE[i] = (src_offset + pre_count) % 8; - L_COUNT64[i] = (l_count - pre_count) / 8; + for (int icase = 0; icase < 3; ++icase) { + const int use_src = icase < 2; + const int neq = icase == 1 ? 1 : 0; + for (int dst_offset = 0; dst_offset < 8; ++dst_offset) { + for (int src_offset = 0; src_offset < 8; ++src_offset) { + for (int l_count = 0; l_count < P2_9; ++l_count) { + DST_OFFSET[i] = dst_offset; + SRC_OFFSET[i] = src_offset; + + if (l_count == 0) { + L_COUNT[i] = 0; + FLAGS[i] = 16 + neq * 32; + PRE_COUNT[i] = 0; + SRC_OFFSET_AFTER_PRE[i] = src_offset; + L_COUNT64[i] = 0; + } else { + const int use_pre = dst_offset > 0; + const int pre_count = use_pre ? (((8 - dst_offset) < l_count) ? (8 - dst_offset) : l_count) : 0; + int post_count = (l_count - pre_count) % 8; + int loop_count = l_count - pre_count - post_count; + if (neq && post_count == 0 && loop_count > 0) { + loop_count -= 1; + post_count = 8; + } + const int use_post = post_count > 0; + const int use_loop = loop_count > 0; + const int src64_inc_by_pre = (use_pre && (src_offset + pre_count) >= 8) ? 1 : 0; + const int count_lt_256 = l_count < 256 ? 1 : 0; + + L_COUNT[i] = l_count; + FLAGS[i] = use_pre * DMA_ROM_USE_PRE_F + + use_loop * DMA_ROM_LOOP_F + + use_post * DMA_ROM_USE_POST_F + + src64_inc_by_pre * DMA_ROM_SRC64_INC_BY_PRE_F + + count_lt_256 * DMA_ROM_COUNT_LT_256_F + + neq * DMA_ROM_NEQ_F + + use_src * DMA_ROM_USE_SRC_F; + + + PRE_COUNT[i] = pre_count; + SRC_OFFSET_AFTER_PRE[i] = use_src ? (src_offset + pre_count) % 8 : 0; + L_COUNT64[i] = (l_count - pre_count) / 8; + } + i += 1; } - i += 1; } } } diff --git a/precompiles/dma/pil/dma_unaligned.pil b/precompiles/dma/pil/dma_unaligned.pil index b0c0542b8..fcf6650fd 100644 --- a/precompiles/dma/pil/dma_unaligned.pil +++ b/precompiles/dma/pil/dma_unaligned.pil @@ -51,7 +51,7 @@ airtemplate DmaUnaligned(int N = 2**21, // Rows of instance const int enable_memcmp = (enable & E_DMA_MEMCMP) ? 1 : 0; assert((enable_memcmp + enable_memcpy) >= 1); - const int enable_full = enable_memcmp && enable_memcmp; + const int enable_full = enable_memcpy && enable_memcmp; // continuation control, used to create a new continuation subid to avoid collision // between DmaUnaligned continuations. If you instance diferent airs with same template @@ -208,7 +208,7 @@ airtemplate DmaUnaligned(int N = 2**21, // Rows of instance (1 - segment_previous_seq_end) * L1 * (segment_first_bytes[i] - read_bytes[i]) === 0; // force segment_next_bytes to zero, when it's finish of input - segment_last_seq_end * segment_next_bytes[i] == 0; + segment_last_seq_end * segment_next_bytes[i] === 0; // to force the "received" values from the bus are 0 when the last row of the previous segment // is final of input (seq_end). @@ -263,7 +263,7 @@ airtemplate DmaUnaligned(int N = 2**21, // Rows of instance a: [dst64 * 8, 0], b: [src64 * 8, 0], main_step:, - extended_arg: count, + extended_arg: count * 8, extra_args: [offset], mul: previous_seq_end); @@ -308,13 +308,13 @@ airtemplate DmaUnaligned(int N = 2**21, // Rows of instance // src64 = 'src64 + 1 // dst64 = 'dst64 + 1 - continue_seq_on_l1 * (count - (segment_previous_count - 1)) == 0; + continue_seq_on_l1 * (count - (segment_previous_count - 1)) === 0; continue_seq_on_no_l1 * (count - ('count - 1)) === 0; - continue_seq_on_l1 * (src64 - (segment_previous_src64 + 1)) == 0; + continue_seq_on_l1 * (src64 - (segment_previous_src64 + 1)) === 0; continue_seq_on_no_l1 * (src64 - ('src64 + 1)) === 0; - continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + 1)) == 0; + continue_seq_on_l1 * (dst64 - (segment_previous_dst64 + 1)) === 0; continue_seq_on_no_l1 * (dst64 - ('dst64 + 1)) === 0; // LATCHS: @@ -322,14 +322,14 @@ airtemplate DmaUnaligned(int N = 2**21, // Rows of instance // offset = 'offset // is_memeq = 'is_memeq // main_step = 'main_step - continue_seq_on_l1 * (offset - segment_previous_offset) == 0; + continue_seq_on_l1 * (offset - segment_previous_offset) === 0; continue_seq_on_no_l1 * (offset - 'offset) === 0; if (enable_full) { - continue_seq_on_l1 * (is_memeq - segment_previous_is_memeq) == 0; + continue_seq_on_l1 * (is_memeq - segment_previous_is_memeq) === 0; continue_seq_on_no_l1 * (is_memeq - 'is_memeq) === 0; } - continue_seq_on_l1 * (main_step - segment_previous_main_step) == 0; + continue_seq_on_l1 * (main_step - segment_previous_main_step) === 0; continue_seq_on_no_l1 * (main_step - 'main_step) === 0; // At end of sequence diff --git a/precompiles/dma/src/dma/dma.rs b/precompiles/dma/src/dma/dma.rs index cffd8cbd6..41d755ef9 100644 --- a/precompiles/dma/src/dma/dma.rs +++ b/precompiles/dma/src/dma/dma.rs @@ -6,9 +6,10 @@ use rayon::prelude::*; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use zisk_core::zisk_ops::ZiskOp; use zisk_pil::{DMA_ROM_ID, DUAL_RANGE_7_BITS_ID}; -use crate::{dma::dma_rom::DmaRom, DmaInput}; +use crate::{dma::dma_rom::DmaRom, DmaInput, DmaModule, DMA_ROM_WITH_MEMCMP_SIZE}; use precompiles_helpers::DmaInfo; #[cfg(feature = "packed")] @@ -36,6 +37,7 @@ pub struct DmaSM { pub dual_range_7_bits_id: usize, pub range_22_bits_id: usize, pub range_24_bits_id: usize, + pub range_16_bits_id: usize, } impl DmaSM { @@ -56,6 +58,9 @@ impl DmaSM { range_24_bits_id: std .get_range_id(0, 0xFF_FFFF, None) .expect("Failed to get 24b table ID"), + range_16_bits_id: std + .get_range_id(0, 0xFFFF, None) + .expect("Failed to get 16b table ID"), }) } @@ -74,6 +79,7 @@ impl DmaSM { local_22_bits_values: &mut Vec, local_24_bits_values: &mut Vec, local_24_bits_low_values: &mut [u32], + local_16_bits_multiplicities: &mut [u32], local_rom_multiplicities: &mut [u64], ) { let count = DmaInfo::get_count(input.encoded); @@ -110,12 +116,6 @@ impl DmaSM { local_22_bits_values.push(h_dst64); let dual_7_bits_row = ((l_src64 as usize) << 7) | l_dst64 as usize; local_dual_7_bits_multiplicities[dual_7_bits_row] += 1; - // println!( - // "local_dual_7_bits_multiplicities[{dual_7_bits_row} ({l_src64}|{l_dst64})] = {}", - // local_dual_7_bits_multiplicities[dual_7_bits_row] - // ); - - local_rom_multiplicities[DmaRom::get_row(input.dst & 0x07, input.src & 0x07, count)] += 1; trace.set_main_step(input.step); @@ -123,16 +123,73 @@ impl DmaSM { let loop_count = DmaInfo::get_loop_count(input.encoded); let post_count = DmaInfo::get_post_count(input.encoded); trace.set_use_pre(pre_count > 0); - trace.set_use_memcpy(loop_count > 0); + trace.set_use_loop(loop_count > 0); trace.set_use_post(post_count > 0); trace.set_src64_inc_by_pre(DmaInfo::get_src64_inc_by_pre(input.encoded) > 0); trace.set_pre_count(pre_count); trace.set_l_count64((l_count - pre_count as u16 - post_count as u16) >> 3); - trace.set_src_offset_after_pre((src_offset + pre_count) % 8); - trace.set_sel(true); + let use_src = input.op != ZiskOp::DMA_INPUTCPY && input.op != ZiskOp::DMA_XMEMSET; + if use_src { + trace.set_src_offset_after_pre((src_offset + pre_count) % 8); + } + match input.op { + ZiskOp::DMA_MEMCPY => trace.set_sel_memcpy(true), + ZiskOp::DMA_XMEMCPY => { + trace.set_sel_memcpy(true); + trace.set_sel_extended(true); + } + ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => { + trace.set_sel_memcmp(true); + trace.set_sel_extended(input.op == ZiskOp::DMA_XMEMCMP); + let pre_result_nz = DmaInfo::get_memcmp_pre_result_nz(input.encoded); + let post_result_nz = DmaInfo::get_memcmp_post_result_nz(input.encoded); + trace.set_pre_result_nz(pre_result_nz); + trace.set_post_result_nz(post_result_nz); + let count_diff = input.count_bus - count as u32; + assert!( + (count_diff == 0 && (pre_result_nz as u32 + post_result_nz as u32) == 0) + || (count_diff != 0 && (pre_result_nz as u32 + post_result_nz as u32) == 1), + "Invalid memcmp result for count_diff {count_diff}: ({}-{count}) \ + pre_result_nz={pre_result_nz}, post_result_nz={post_result_nz} {}", + input.count_bus, + DmaInfo::to_string(input.encoded) + ); + let diff_chunk = count_diff as u16; + trace.set_count_diff_chunks(0, diff_chunk); + local_16_bits_multiplicities[diff_chunk as usize] += 1; + + let diff_chunk = (count_diff >> 16) as u16; + trace.set_count_diff_chunks(1, diff_chunk); + local_16_bits_multiplicities[diff_chunk as usize] += 1; + if pre_result_nz { + let result = DmaInfo::get_memcmp_res_as_u64(input.encoded); + trace.set_bus_pre_result(0, result as u32); + trace.set_bus_pre_result(1, (result >> 32) as u32); + } + if post_result_nz { + let result = DmaInfo::get_memcmp_res_as_u64(input.encoded); + trace.set_bus_post_result(0, result as u32); + trace.set_bus_post_result(1, (result >> 32) as u32); + } + } + ZiskOp::DMA_INPUTCPY => trace.set_sel_inputcpy(true), + ZiskOp::DMA_XMEMSET => { + trace.set_sel_memset(true); + trace.set_sel_extended(true); + trace.set_fill_byte(DmaInfo::get_fill_byte(input.encoded)); + // println!("XMEMSET fill_byte: 0x{:02X}", DmaInfo::get_fill_byte(input.encoded)); + } + _ => panic!("Invalid DMA operation {}", input.op), + } + + let result_nz = (input.op == ZiskOp::DMA_MEMCMP || input.op == ZiskOp::DMA_XMEMCMP) + && DmaInfo::get_memcmp_result_nz(input.encoded); + let rom_index = + DmaRom::get_row(input.dst & 0x07, input.src & 0x07, count, result_nz, use_src); + local_rom_multiplicities[rom_index] += 1; } /// Processes a slice of operation data, updating the trace. @@ -142,34 +199,14 @@ impl DmaSM { /// * `input` - The operation data to process. #[inline(always)] pub fn process_empty_slice(&self, trace: &mut DmaTraceRowType) { + // trace was initialized with zeroes trace.set_count_lt_256(true); - trace.set_h_count(0); - trace.set_l_count(0); - - // to increase performance because the 99.99% of count is < 64K => h_count < 256 - trace.set_h_src64(0); - trace.set_l_src64(0); - trace.set_src_offset(0); - - trace.set_h_dst64(0); - trace.set_l_dst64(0); - trace.set_dst_offset(0); - - trace.set_main_step(0); - - trace.set_use_pre(false); - trace.set_use_memcpy(false); - trace.set_use_post(false); - - trace.set_src64_inc_by_pre(false); - - trace.set_pre_count(0); - trace.set_l_count64(0); - trace.set_src_offset_after_pre(0); - - trace.set_sel(false); } - +} +impl DmaModule for DmaSM { + fn get_name(&self) -> &'static str { + "dma" + } /// Computes the witness for a series of inputs and produces an `AirInstance`. /// /// # Arguments @@ -178,12 +215,12 @@ impl DmaSM { /// /// # Returns /// An `AirInstance` containing the computed witness data. - pub fn compute_witness( + fn compute_witness( &self, inputs: &[Vec], trace_buffer: Vec, ) -> ProofmanResult> { - let mut trace = DmaTraceType::::new_from_vec(trace_buffer)?; + let mut trace = DmaTraceType::::new_from_vec_zeroes(trace_buffer)?; let num_rows = trace.num_rows(); let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); @@ -211,6 +248,7 @@ impl DmaSM { global_22_bits_values, global_24_bits_values, global_24_bits_low_values, + global_16_bits_multiplicities, global_rom_multiplicities, ) = flat_inputs .par_chunks(chunk_size) @@ -221,7 +259,8 @@ impl DmaSM { let mut local_22_bits_values = Vec::::with_capacity(inputs.len() * 2); let mut local_24_bits_values = Vec::::new(); let mut local_24_bits_low_values = vec![0u32; 256]; - let mut local_rom_multiplicities = vec![0u64; 1 << 15]; + let mut local_16_bits_multiplicities = vec![0u32; 1 << 16]; + let mut local_rom_multiplicities = vec![0u64; DMA_ROM_WITH_MEMCMP_SIZE]; // Sum all local arrays into a global one for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { self.process_slice( @@ -231,6 +270,7 @@ impl DmaSM { &mut local_22_bits_values, &mut local_24_bits_values, &mut local_24_bits_low_values, + &mut local_16_bits_multiplicities, &mut local_rom_multiplicities, ); } @@ -239,6 +279,7 @@ impl DmaSM { local_22_bits_values, local_24_bits_values, local_24_bits_low_values, + local_16_bits_multiplicities, local_rom_multiplicities, ) }) @@ -250,7 +291,8 @@ impl DmaSM { Vec::new(), Vec::new(), vec![0u32; 256], - vec![0u64; 1 << 15], + vec![0u32; 1 << 16], + vec![0u64; DMA_ROM_WITH_MEMCMP_SIZE], ) }, // Combine two results @@ -269,6 +311,9 @@ impl DmaSM { for (i, &val) in local.4.iter().enumerate() { acc.4[i] += val; } + for (i, &val) in local.5.iter().enumerate() { + acc.5[i] += val; + } acc }, ); @@ -282,6 +327,7 @@ impl DmaSM { .inc_virtual_rows_ranged(self.dual_range_7_bits_id, &global_dual_7_bits_multiplicities); self.std.range_checks(self.range_24_bits_id, global_24_bits_low_values); self.std.inc_virtual_rows_ranged(self.rom_table_id, &global_rom_multiplicities); + self.std.range_checks(self.range_16_bits_id, global_16_bits_multiplicities); for value in global_22_bits_values { self.std.range_check(self.range_22_bits_id, value as i64, 1); @@ -297,7 +343,6 @@ impl DmaSM { *row = empty_row; }); } - timer_stop_and_log_trace!(DMA_TRACE); let from_trace = FromTrace::new(&mut trace); Ok(AirInstance::new_from_trace(from_trace)) diff --git a/precompiles/dma/src/dma/dma_collector.rs b/precompiles/dma/src/dma/dma_collector.rs index 95294baa3..91e8bb4bb 100644 --- a/precompiles/dma/src/dma/dma_collector.rs +++ b/precompiles/dma/src/dma/dma_collector.rs @@ -1,22 +1,29 @@ -//! The `DmaCollector` module defines an collector to calculate all inputs of an instance -//! for the Dma State Machine. +//! The `DmaCollector` module defines a collector to gather all inputs for an instance +//! of the DMA State Machine. use std::any::Any; -use zisk_common::{BusDevice, BusId, CollectCounter, ExtOperationData, OPERATION_BUS_ID, OP_TYPE}; -use zisk_core::ZiskOperationType; +use precompiles_helpers::DmaInfo; +use zisk_common::{BusDevice, BusId, ChunkId, DMA_ENCODED, OP, OPERATION_BUS_ID, OP_TYPE}; +use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; -use crate::DmaInput; +use crate::{DmaCollectCounters, DmaCollectorRoutingLog, DmaInput}; pub struct DmaCollector { + /// The chunk identifier being collected (used for tracing/debugging). + pub chunk_id: ChunkId, + /// Collected inputs for witness computation. pub inputs: Vec, + /// Routing log for debugging and tracking collection operations. + pub rlog: DmaCollectorRoutingLog, + /// The number of operations to collect. pub num_operations: u64, - /// Helper to skip instructions based on the plan's configuration. - pub collect_counter: CollectCounter, + /// Counters to determine which operations to collect based on the plan's configuration. + pub collect_counters: DmaCollectCounters, } impl DmaCollector { @@ -24,58 +31,96 @@ impl DmaCollector { /// /// # Arguments /// - /// * `bus_id` - The connected bus ID. + /// * `chunk_id` - The chunk identifier for this collector instance. /// * `num_operations` - The number of operations to collect. - /// * `collect_skipper` - The helper to skip instructions based on the plan's configuration. + /// * `collect_counters` - Counters to determine which operations to collect based on the plan's configuration. /// /// # Returns - /// A new `ArithInstanceCollector` instance initialized with the provided parameters. - pub fn new(num_operations: u64, collect_counter: CollectCounter) -> Self { + /// A new `DmaCollector` instance initialized with the provided parameters. + pub fn new( + chunk_id: ChunkId, + num_operations: u64, + collect_counters: DmaCollectCounters, + ) -> Self { Self { + chunk_id, inputs: Vec::with_capacity(num_operations as usize), num_operations, - collect_counter, + collect_counters, + rlog: DmaCollectorRoutingLog::new(chunk_id), } } /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments - /// * `_bus_id` - The ID of the bus (unused in this implementation). - /// * `data` - The data received from the bus. - /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// * `bus_id` - The ID of the bus (validated to be OPERATION_BUS_ID). + /// * `data` - The main data array received from the bus containing operation information. + /// * `data_ext` - Extended data array containing additional operation-specific information. /// /// # Returns - /// A tuple where: /// A boolean indicating whether the program should continue execution or terminate. /// Returns `true` to continue execution, `false` to stop. #[inline(always)] pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); - if self.inputs.len() == self.num_operations as usize { - return false; - } - if data[OP_TYPE] != ZiskOperationType::Dma as u64 { return true; } - if self.collect_counter.should_skip() { + if self.inputs.len() == self.num_operations as usize { + debug_assert!(self.collect_counters.is_final_skip()); + return self.rlog.log_discard_cond(false, 1, data, false); + } + + let encoded = data[DMA_ENCODED]; + let op = data[OP] as u8; + if DmaInfo::is_direct(encoded) { + if op == ZiskOp::DMA_MEMCMP || op == ZiskOp::DMA_XMEMCMP { + // We need to collect all memcmp/memcpy operations for the pre/post processing. + panic!("Direct memcmp/memcpy operations are not supported"); + } + self.rlog.log_discard(2, data); return true; } - let data: ExtOperationData = - data.try_into().expect("Regular Metrics: Failed to convert data"); - if let ExtOperationData::OperationDmaMemCpyData(data) = data { - self.inputs.push(DmaInput::from_memcpy(&data, data_ext)); - } else if let ExtOperationData::OperationDmaMemCmpData(data) = data { - self.inputs.push(DmaInput::from_memcmp(&data, data_ext)); + if self.collect_counters.should_collect_single_row(op) { + self.rlog.log_collect(1, data); + self.inputs.push(if op == ZiskOp::DMA_XMEMSET { + DmaInput::from_memset(encoded, op, data, data_ext) + } else { + DmaInput::from(encoded, op, data, data_ext) + }); + if self.inputs.len() >= self.num_operations as usize { + debug_assert!(self.collect_counters.is_final_skip()); + self.rlog.log_discard(4, data); + return true; + } } else { - panic!("Expected ExtOperationData::OperationDmaData"); + self.rlog.log_discard(3, data); } - self.inputs.len() < self.num_operations as usize + true + } + /// Returns debug information about the collector's state. + /// + /// When the `save_dma_collectors` feature is enabled, this returns detailed information + /// including chunk ID, number of collected inputs, counter information, and routing log. + /// Otherwise, returns an empty string. + /// + /// # Returns + /// A formatted string with debug information. + pub fn get_debug_info(&self) -> String { + #[cfg(feature = "save_dma_collectors")] + return format!( + "CC|{}|{}|{}\n", + self.chunk_id, + self.inputs.len(), + self.collect_counters.get_debug_info(), + ) + &self.rlog.get_debug_info(); + #[cfg(not(feature = "save_dma_collectors"))] + String::new() } } diff --git a/precompiles/dma/src/dma/dma_input.rs b/precompiles/dma/src/dma/dma_input.rs index 5dd7aba3a..ce7af899a 100644 --- a/precompiles/dma/src/dma/dma_input.rs +++ b/precompiles/dma/src/dma/dma_input.rs @@ -1,54 +1,79 @@ -use zisk_common::{ - OperationDmaMemCmpData, OperationDmaMemCpyData, A, B, OPERATION_BUS_DMA_MEMCMP_DATA_SIZE, - OPERATION_BUS_DMA_MEMCPY_DATA_SIZE, STEP, -}; +use precompiles_helpers::DmaInfo; +use zisk_common::{A, B, DMA_MEMCMP_COUNT_BUS, STEP}; +use zisk_core::zisk_ops::ZiskOp; -#[derive(Debug)] -pub enum DmaOperation { - MemCpy, - MemCmp, - InputCpy, - MemSet, - MemCpy256, -} #[derive(Debug)] pub struct DmaInput { pub src: u32, pub dst: u32, - pub operation: DmaOperation, + pub op: u8, pub encoded: u64, - pub count_eq: u32, - pub result: i32, + pub count_bus: u32, pub step: u64, // main step } impl DmaInput { - pub fn from_memcpy(data: &OperationDmaMemCpyData, _data_ext: &[u64]) -> Self { - let encoded = data[OPERATION_BUS_DMA_MEMCPY_DATA_SIZE - 1]; + pub fn from(encoded: u64, op: u8, data: &[u64], _data_ext: &[u64]) -> Self { Self { dst: data[A] as u32, src: data[B] as u32, step: data[STEP], encoded, - count_eq: 0, - result: 0, - operation: DmaOperation::MemCpy, + op, + count_bus: if op == ZiskOp::DMA_MEMCMP || op == ZiskOp::DMA_XMEMCMP { + data[DMA_MEMCMP_COUNT_BUS] as u32 + } else { + 0 + }, } } - - pub fn from_memcmp(data: &OperationDmaMemCmpData, _data_ext: &[u64]) -> Self { - let encoded = data[OPERATION_BUS_DMA_MEMCMP_DATA_SIZE - 2]; - let count_eq = data[OPERATION_BUS_DMA_MEMCMP_DATA_SIZE - 1] as u32; - let result = (data[OPERATION_BUS_DMA_MEMCMP_DATA_SIZE - 1] >> 32) as i32; - + pub fn from_memset(encoded: u64, op: u8, data: &[u64], _data_ext: &[u64]) -> Self { Self { dst: data[A] as u32, - src: data[B] as u32, + // src: (data[A] & 0x7) as u32, + src: 0, step: data[STEP], encoded, - count_eq, - result, - operation: DmaOperation::MemCmp, + op, + count_bus: DmaInfo::get_count(encoded) as u32, + } + } + + #[cfg(feature = "save_dma_inputs")] + /// Writes a list of DmaInput instances to a text file with columns separated by |. + /// Path is taken from DEBUG_OUTPUT_PATH environment variable, defaulting to "tmp/". + pub fn dump_to_file(inputs: &[Vec], filename: &str) -> std::io::Result<()> { + use std::io::Write; + let path = std::env::var("DEBUG_OUTPUT_PATH").unwrap_or_else(|_| "tmp/".to_string()); + let full_path = format!("{}{}", path, filename); + + let mut file = std::fs::File::create(&full_path)?; + + // Write header + writeln!( + file, + "{:>8}|{:>10}|{:>10}|{:>2}|{:>18}|{:>8}|{:>14}|{}|{}|{:>10}", + "pos", "src", "dst", "op", "encoded", "count_bus", "step", "pre", "post", "loop" + )?; + + // Write data rows + for (pos, input) in inputs.iter().flatten().enumerate() { + writeln!( + file, + "{:>8}|0x{:08X}|0x{:08X}|{:02X}|0x{:016X}|{:>8}|{:>14}|{}|{}|{:>10}", + pos, + input.src, + input.dst, + input.op, + input.encoded, + input.count_bus, + input.step, + DmaInfo::get_pre_count(input.encoded), + DmaInfo::get_post_count(input.encoded), + DmaInfo::get_loop_count(input.encoded), + )?; } + + Ok(()) } } diff --git a/precompiles/dma/src/dma/dma_inputcpy.rs b/precompiles/dma/src/dma/dma_inputcpy.rs new file mode 100644 index 000000000..e1f34f739 --- /dev/null +++ b/precompiles/dma/src/dma/dma_inputcpy.rs @@ -0,0 +1,257 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use rayon::prelude::*; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use zisk_pil::DMA_ROM_ID; + +use crate::{dma::dma_rom::DmaRom, DmaInput, DmaModule, DMA_ROM_WITHOUT_MEMCMP_SIZE}; +use precompiles_helpers::DmaInfo; + +#[cfg(feature = "packed")] +pub use zisk_pil::{ + DmaInputCpyTracePacked as DmaInputCpyTrace, DmaInputCpyTraceRowPacked as DmaInputCpyTraceRow, +}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{DmaInputCpyTrace, DmaInputCpyTraceRow}; + +/// The `DmaInputCpySM` struct encapsulates the logic of the Dma State Machine. +pub struct DmaInputCpySM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + pub rom_table_id: usize, + pub range_7_bits_id: usize, + pub range_22_bits_id: usize, + pub range_24_bits_id: usize, +} + +impl DmaInputCpySM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaInputCpySM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + rom_table_id: std.get_virtual_table_id(DMA_ROM_ID).expect("Failed to get dma rom ID"), + range_7_bits_id: std + .get_range_id(0, 0x07F, None) + .expect("Failed to get 7-bits range ID"), + range_22_bits_id: std + .get_range_id(0, 0x3F_FFFF, None) + .expect("Failed to get 22b range ID"), + range_24_bits_id: std + .get_range_id(0, 0xFF_FFFF, None) + .expect("Failed to get 24b range ID"), + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[allow(clippy::too_many_arguments)] + #[inline(always)] + pub fn process_slice( + &self, + input: &DmaInput, + trace: &mut DmaInputCpyTraceRow, + local_7_bits_multiplicities: &mut [u32], + local_22_bits_values: &mut Vec, + local_24_bits_values: &mut Vec, + local_24_bits_low_values: &mut [u32], + local_rom_multiplicities: &mut [u64], + ) { + let count = DmaInfo::get_count(input.encoded); + let count_lt_256 = count < 256; + let count_ge_256 = 1 - count_lt_256 as usize; + let h_count = ((count >> 8) - count_ge_256) as u32; + trace.set_count_lt_256(count_lt_256); + trace.set_h_count(h_count); + let l_count = (count & 0xFF) as u16 + 256 * count_ge_256 as u16; + trace.set_l_count(l_count); + + // to increase performance because the 99.99% of count is < 64K => h_count < 256 + if h_count < 256 { + local_24_bits_low_values[h_count as usize] += 1; + } else { + local_24_bits_values.push(h_count); + } + + let h_dst64 = input.dst >> 10; + let l_dst64 = (input.dst >> 3) as u8 & 0x7F; + + trace.set_h_dst64(h_dst64); + trace.set_l_dst64(l_dst64); + trace.set_dst_offset(input.dst as u8 & 0x07); + + local_22_bits_values.push(h_dst64); + local_7_bits_multiplicities[l_dst64 as usize] += 1; + + let rom_index = DmaRom::get_row(input.dst & 0x07, input.src & 0x07, count, false, false); + + local_rom_multiplicities[rom_index] += 1; + + trace.set_main_step(input.step); + + let pre_count = DmaInfo::get_pre_count(input.encoded) as u8; + let loop_count = DmaInfo::get_loop_count(input.encoded); + let post_count = DmaInfo::get_post_count(input.encoded); + trace.set_use_pre(pre_count > 0); + trace.set_use_loop(loop_count > 0); + trace.set_use_post(post_count > 0); + + trace.set_pre_count(pre_count); + trace.set_l_count64((l_count - pre_count as u16 - post_count as u16) >> 3); + + trace.set_sel_inputcpy(true); + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the DmaInputCpy trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut DmaInputCpyTraceRow) { + trace.set_count_lt_256(true); + } +} + +impl DmaModule for DmaInputCpySM { + fn get_name(&self) -> &'static str { + "dma_inputcpy" + } + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = DmaInputCpyTrace::::new_from_vec_zeroes(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); + assert!(total_inputs <= num_rows); + + tracing::debug!( + "··· Creating DmaInputCpy instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + // Calculate optimal chunk size + let num_threads = rayon::current_num_threads(); + let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); + + // TODO: add new interface with u32 to std to be used with global_rom_multiplicities + // Split the add256_trace.buffer into slices matching each inner vector’s length. + let ( + global_7_bits_multiplicities, + global_22_bits_values, + global_24_bits_values, + global_24_bits_low_values, + global_rom_multiplicities, + ) = flat_inputs + .par_chunks(chunk_size) + .zip(trace_rows.par_chunks_mut(chunk_size)) + .map(|(input_chunk, trace_chunk)| { + // Local array shared by this chunk + let mut local_7_bits_multiplicities = vec![0u32; 1 << 14]; + let mut local_22_bits_values = Vec::::with_capacity(inputs.len() * 2); + let mut local_24_bits_values = Vec::::new(); + let mut local_24_bits_low_values = vec![0u32; 256]; + let mut local_rom_multiplicities = vec![0u64; DMA_ROM_WITHOUT_MEMCMP_SIZE]; + // Sum all local arrays into a global one + for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { + self.process_slice( + input, + trace_row, + &mut local_7_bits_multiplicities, + &mut local_22_bits_values, + &mut local_24_bits_values, + &mut local_24_bits_low_values, + &mut local_rom_multiplicities, + ); + } + ( + local_7_bits_multiplicities, + local_22_bits_values, + local_24_bits_values, + local_24_bits_low_values, + local_rom_multiplicities, + ) + }) + .reduce( + // Identity: create empty accumulators + || { + ( + vec![0u32; 1 << 14], + Vec::new(), + Vec::new(), + vec![0u32; 256], + vec![0u64; DMA_ROM_WITHOUT_MEMCMP_SIZE], + ) + }, + // Combine two results + |mut acc, local| { + // Merge multiplicities (element-wise addition) + for (i, &val) in local.0.iter().enumerate() { + acc.0[i] += val; + } + // Concatenate value vectors + acc.1.extend(local.1); + acc.2.extend(local.2); + // Merge low values (element-wise addition) + for (i, &val) in local.3.iter().enumerate() { + acc.3[i] += val; + } + for (i, &val) in local.4.iter().enumerate() { + acc.4[i] += val; + } + acc + }, + ); + + self.std.range_checks(self.range_7_bits_id, global_7_bits_multiplicities); + self.std.range_checks(self.range_24_bits_id, global_24_bits_low_values); + self.std.inc_virtual_rows_ranged(self.rom_table_id, &global_rom_multiplicities); + + for value in global_22_bits_values { + self.std.range_check(self.range_22_bits_id, value as i64, 1); + } + for value in global_24_bits_values { + self.std.range_check(self.range_24_bits_id, value as i64, 1); + } + + if total_inputs < num_rows { + self.process_empty_slice(&mut trace_rows[total_inputs]); + let empty_row = trace_rows[total_inputs]; + trace_rows[total_inputs + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + } + + timer_stop_and_log_trace!(DMA_TRACE); + let from_trace = FromTrace::new(&mut trace); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma/dma_instance.rs b/precompiles/dma/src/dma/dma_instance.rs index 9d37c60a8..b470c36d9 100644 --- a/precompiles/dma/src/dma/dma_instance.rs +++ b/precompiles/dma/src/dma/dma_instance.rs @@ -5,13 +5,18 @@ //! execution plans. use crate::dma::dma_collector::DmaCollector; -use crate::{DmaCheckPoint, DmaSM}; +#[cfg(feature = "save_dma_collectors")] +use crate::save_dma_collectors; +#[cfg(feature = "save_dma_inputs")] +use crate::DmaInput; +use crate::{DmaCheckPoint, DmaModule}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::sync::Arc; -use zisk_common::ChunkId; -use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; -use zisk_pil::DmaTrace; +use zisk_common::{ + BusDevice, CheckPoint, ChunkId, Instance, InstanceCtx, InstanceType, PayloadType, +}; +use zisk_pil::{DmaInputCpyTrace, DmaMemCpyTrace, DmaTrace}; /// The `DmaInstance` struct represents an instance for the Dma State Machine. /// @@ -19,7 +24,7 @@ use zisk_pil::DmaTrace; /// to compute witnesses for the Dma State Machine. pub struct DmaInstance { /// Dma state machine. - dma_sm: Arc>, + module: Arc>, /// Instance context. ictx: InstanceCtx, @@ -36,22 +41,22 @@ impl DmaInstance { /// # Returns /// A new `DmaInstance` instance initialized with the provided state machine and /// context. - pub fn new(dma_sm: Arc>, ictx: InstanceCtx) -> Self { - Self { dma_sm, ictx } + pub fn new(module: Arc>, ictx: InstanceCtx) -> Self { + Self { module, ictx } } pub fn build_dma_collector(&self, chunk_id: ChunkId) -> DmaCollector { - assert_eq!( - self.ictx.plan.air_id, - DmaTrace::::AIR_ID, + debug_assert!( + [DmaTrace::::AIR_ID, DmaMemCpyTrace::::AIR_ID, DmaInputCpyTrace::::AIR_ID,] + .contains(&self.ictx.plan.air_id), "DmaInstance: Unsupported air_id: {:?}", self.ictx.plan.air_id ); let meta = self.ictx.plan.meta.as_ref().unwrap(); let collect_info = meta.downcast_ref::().unwrap(); - let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; - DmaCollector::new(num_ops, collect_counter) + let (num_ops, collect_counters) = collect_info.chunks[&chunk_id]; + DmaCollector::new(chunk_id, num_ops, collect_counters) } } @@ -73,12 +78,37 @@ impl Instance for DmaInstance { collectors: Vec<(usize, Box>)>, trace_buffer: Vec, ) -> ProofmanResult>> { + #[cfg(feature = "save_dma_collectors")] + let (debug, inputs): (Vec<_>, Vec<_>) = collectors + .into_iter() + .map(|(_, collector)| { + let collector = collector.as_any().downcast::().unwrap(); + (collector.get_debug_info(), collector.inputs) + }) + .unzip(); + #[cfg(not(feature = "save_dma_collectors"))] let inputs: Vec<_> = collectors .into_iter() .map(|(_, collector)| collector.as_any().downcast::().unwrap().inputs) .collect(); - Ok(Some(self.dma_sm.compute_witness(&inputs, trace_buffer)?)) + #[cfg(any(feature = "save_dma_collectors", feature = "save_dma_inputs"))] + let air_instance_id = + _pctx.dctx_find_air_instance_id(self.ictx.plan.global_id.unwrap()).unwrap(); + + #[cfg(feature = "save_dma_collectors")] + save_dma_collectors( + &format!("{}_collector_{air_instance_id:04}.txt", self.module.get_name()), + debug, + )?; + + #[cfg(feature = "save_dma_inputs")] + DmaInput::dump_to_file( + &inputs, + &format!("{}_inputs_{air_instance_id:04}.txt", self.module.get_name()), + )?; + + Ok(Some(self.module.compute_witness(&inputs, trace_buffer)?)) } /// Retrieves the checkpoint associated with this instance. @@ -108,7 +138,7 @@ impl Instance for DmaInstance { let meta = self.ictx.plan.meta.as_ref().unwrap(); let collect_info = meta.downcast_ref::().unwrap(); let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; - Some(Box::new(DmaCollector::new(num_ops, collect_counter))) + Some(Box::new(DmaCollector::new(chunk_id, num_ops, collect_counter))) } fn as_any(&self) -> &dyn std::any::Any { diff --git a/precompiles/dma/src/dma/dma_memcpy.rs b/precompiles/dma/src/dma/dma_memcpy.rs new file mode 100644 index 000000000..ce1b5648c --- /dev/null +++ b/precompiles/dma/src/dma/dma_memcpy.rs @@ -0,0 +1,289 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use rayon::prelude::*; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use zisk_core::zisk_ops::ZiskOp; +use zisk_pil::{DMA_ROM_ID, DUAL_RANGE_7_BITS_ID}; + +use crate::{dma::dma_rom::DmaRom, DmaInput, DmaModule, DMA_ROM_WITHOUT_MEMCMP_SIZE}; +use precompiles_helpers::DmaInfo; + +#[cfg(feature = "packed")] +pub use zisk_pil::{ + DmaMemCpyTracePacked as DmaMemCpyTrace, DmaMemCpyTraceRowPacked as DmaMemCpyTraceRow, +}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{DmaMemCpyTrace, DmaMemCpyTraceRow}; + +/// The `DmaSM` struct encapsulates the logic of the Dma State Machine. +pub struct DmaMemCpySM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + pub rom_table_id: usize, + pub dual_range_7_bits_id: usize, + pub range_22_bits_id: usize, + pub range_24_bits_id: usize, +} + +impl DmaMemCpySM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaSM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + rom_table_id: std.get_virtual_table_id(DMA_ROM_ID).expect("Failed to get dma rom ID"), + dual_range_7_bits_id: std + .get_virtual_table_id(DUAL_RANGE_7_BITS_ID) + .expect("Failed to get dual 7-bits table ID"), + range_22_bits_id: std + .get_range_id(0, 0x3F_FFFF, None) + .expect("Failed to get 22b table ID"), + range_24_bits_id: std + .get_range_id(0, 0xFF_FFFF, None) + .expect("Failed to get 24b table ID"), + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[allow(clippy::too_many_arguments)] + #[inline(always)] + pub fn process_slice( + &self, + input: &DmaInput, + trace: &mut DmaMemCpyTraceRow, + local_dual_7_bits_multiplicities: &mut [u64], + local_22_bits_values: &mut Vec, + local_24_bits_values: &mut Vec, + local_24_bits_low_values: &mut [u32], + local_rom_multiplicities: &mut [u64], + ) { + let count = DmaInfo::get_count(input.encoded); + let count_lt_256 = count < 256; + let count_ge_256 = 1 - count_lt_256 as usize; + let h_count = ((count >> 8) - count_ge_256) as u32; + trace.set_count_lt_256(count_lt_256); + trace.set_h_count(h_count); + let l_count = (count & 0xFF) as u16 + 256 * count_ge_256 as u16; + trace.set_l_count(l_count); + + // to increase performance because the 99.99% of count is < 64K => h_count < 256 + if h_count < 256 { + local_24_bits_low_values[h_count as usize] += 1; + } else { + local_24_bits_values.push(h_count); + } + + let h_src64 = input.src >> 10; + let h_dst64 = input.dst >> 10; + let l_src64 = (input.src >> 3) as u8 & 0x7F; + let l_dst64 = (input.dst >> 3) as u8 & 0x7F; + + trace.set_h_src64(h_src64); + trace.set_l_src64(l_src64); + let src_offset = input.src as u8 & 0x07; + trace.set_src_offset(src_offset); + + trace.set_h_dst64(h_dst64); + trace.set_l_dst64(l_dst64); + trace.set_dst_offset(input.dst as u8 & 0x07); + + local_22_bits_values.push(h_src64); + local_22_bits_values.push(h_dst64); + let dual_7_bits_row = ((l_src64 as usize) << 7) | l_dst64 as usize; + local_dual_7_bits_multiplicities[dual_7_bits_row] += 1; + // println!( + // "local_dual_7_bits_multiplicities[{dual_7_bits_row} ({l_src64}|{l_dst64})] = {}", + // local_dual_7_bits_multiplicities[dual_7_bits_row] + // ); + + let rom_index = DmaRom::get_row(input.dst & 0x07, input.src & 0x07, count, false, true); + // println!( + // "\x1B[1;35m[DmaMemCpy] ROM index: {rom_index} [dst_offset:{}, src_offset:{} count:{count}] E:0x{:016X} => {rom_index} \ + // OP:0x{:02X} S:{}\x1B[0m", + // input.dst & 0x07, + // input.src & 0x07, + // input.encoded, + // input.op, + // input.step + // ); + + local_rom_multiplicities[rom_index] += 1; + + trace.set_main_step(input.step); + + let pre_count = DmaInfo::get_pre_count(input.encoded) as u8; + let loop_count = DmaInfo::get_loop_count(input.encoded); + let post_count = DmaInfo::get_post_count(input.encoded); + trace.set_use_pre(pre_count > 0); + trace.set_use_loop(loop_count > 0); + trace.set_use_post(post_count > 0); + + trace.set_src64_inc_by_pre(DmaInfo::get_src64_inc_by_pre(input.encoded) > 0); + + trace.set_pre_count(pre_count); + trace.set_l_count64((l_count - pre_count as u16 - post_count as u16) >> 3); + trace.set_src_offset_after_pre((src_offset + pre_count) % 8); + + trace.set_sel_memcpy(true); + trace.set_sel_extended(input.op == ZiskOp::DMA_XMEMCPY); + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut DmaMemCpyTraceRow) { + trace.set_count_lt_256(true); + } +} + +impl DmaModule for DmaMemCpySM { + fn get_name(&self) -> &'static str { + "dma_memcpy" + } + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = DmaMemCpyTrace::::new_from_vec_zeroes(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|c| c.len()).sum(); + assert!(total_inputs <= num_rows); + + tracing::info!( + "··· Creating DmaMemCpy instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + // Calculate optimal chunk size + let num_threads = rayon::current_num_threads(); + let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); + + // TODO: add new interface with u32 to std to be used with global_rom_multiplicities + // Split the add256_trace.buffer into slices matching each inner vector’s length. + let ( + global_dual_7_bits_multiplicities, + global_22_bits_values, + global_24_bits_values, + global_24_bits_low_values, + global_rom_multiplicities, + ) = flat_inputs + .par_chunks(chunk_size) + .zip(trace_rows.par_chunks_mut(chunk_size)) + .map(|(input_chunk, trace_chunk)| { + // Local array shared by this chunk + let mut local_dual_7_bits_multiplicities = vec![0u64; 1 << 14]; + let mut local_22_bits_values = Vec::::with_capacity(inputs.len() * 2); + let mut local_24_bits_values = Vec::::new(); + let mut local_24_bits_low_values = vec![0u32; 256]; + let mut local_rom_multiplicities = vec![0u64; DMA_ROM_WITHOUT_MEMCMP_SIZE]; + // Sum all local arrays into a global one + for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { + self.process_slice( + input, + trace_row, + &mut local_dual_7_bits_multiplicities, + &mut local_22_bits_values, + &mut local_24_bits_values, + &mut local_24_bits_low_values, + &mut local_rom_multiplicities, + ); + } + ( + local_dual_7_bits_multiplicities, + local_22_bits_values, + local_24_bits_values, + local_24_bits_low_values, + local_rom_multiplicities, + ) + }) + .reduce( + // Identity: create empty accumulators + || { + ( + vec![0u64; 1 << 14], + Vec::new(), + Vec::new(), + vec![0u32; 256], + vec![0u64; DMA_ROM_WITHOUT_MEMCMP_SIZE], + ) + }, + // Combine two results + |mut acc, local| { + // Merge multiplicities (element-wise addition) + for (i, &val) in local.0.iter().enumerate() { + acc.0[i] += val; + } + // Concatenate value vectors + acc.1.extend(local.1); + acc.2.extend(local.2); + // Merge low values (element-wise addition) + for (i, &val) in local.3.iter().enumerate() { + acc.3[i] += val; + } + for (i, &val) in local.4.iter().enumerate() { + acc.4[i] += val; + } + acc + }, + ); + + // for (index, value) in global_dual_7_bits_multiplicities.iter().enumerate() { + // if *value != 0 { + // println!("DUAL_7_BITS[{index}]={value}") + // } + // } + self.std + .inc_virtual_rows_ranged(self.dual_range_7_bits_id, &global_dual_7_bits_multiplicities); + self.std.range_checks(self.range_24_bits_id, global_24_bits_low_values); + self.std.inc_virtual_rows_ranged(self.rom_table_id, &global_rom_multiplicities); + + for value in global_22_bits_values { + self.std.range_check(self.range_22_bits_id, value as i64, 1); + } + for value in global_24_bits_values { + self.std.range_check(self.range_24_bits_id, value as i64, 1); + } + + if total_inputs < num_rows { + self.process_empty_slice(&mut trace_rows[total_inputs]); + let empty_row = trace_rows[total_inputs]; + trace_rows[total_inputs + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + } + timer_stop_and_log_trace!(DMA_TRACE); + let from_trace = FromTrace::new(&mut trace); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma/dma_module.rs b/precompiles/dma/src/dma/dma_module.rs new file mode 100644 index 000000000..869e2b269 --- /dev/null +++ b/precompiles/dma/src/dma/dma_module.rs @@ -0,0 +1,11 @@ +use crate::DmaInput; +use proofman_common::{AirInstance, ProofmanResult}; + +pub trait DmaModule: Send + Sync { + fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult>; + fn get_name(&self) -> &'static str; +} diff --git a/precompiles/dma/src/dma/dma_rom.rs b/precompiles/dma/src/dma/dma_rom.rs index b68d88c69..598fb9b60 100644 --- a/precompiles/dma/src/dma/dma_rom.rs +++ b/precompiles/dma/src/dma/dma_rom.rs @@ -1,20 +1,21 @@ -use precompiles_helpers::DmaInfo; - pub enum DmaRom {} impl DmaRom { #[allow(dead_code)] - pub fn get_row_from_encoded(encoded: u64) -> usize { - let src_offset = DmaInfo::get_src_offset(encoded); - let dst_offset = DmaInfo::get_dst_offset(encoded); - let count = DmaInfo::get_count(encoded); - Self::get_row(dst_offset as u32, src_offset as u32, count) - } - pub fn get_row(dst_offset: u32, src_offset: u32, count: usize) -> usize { + pub fn get_row( + dst_offset: u32, + src_offset: u32, + count: usize, + neq: bool, + use_src: bool, + ) -> usize { assert!(dst_offset < 8, "dst_offset too big {dst_offset}"); assert!(src_offset < 8, "src_offset too big {src_offset}"); assert!(count < u32::MAX as usize, "count too big {count}"); let count = if count >= 256 { (count & 0xFF) + 256 } else { count & 0xFF }; - (dst_offset as usize * 8 + src_offset as usize) * 512 + count + (dst_offset as usize * 8 + src_offset as usize) * 512 + + count + + if neq { 1 << 15 } else { 0 } + + if use_src { 0 } else { 1 << 16 } } } diff --git a/precompiles/dma/src/dma/mod.rs b/precompiles/dma/src/dma/mod.rs index 4a85a16a5..ca0a29662 100644 --- a/precompiles/dma/src/dma/mod.rs +++ b/precompiles/dma/src/dma/mod.rs @@ -2,10 +2,16 @@ mod dma; mod dma_collector; mod dma_input; +mod dma_inputcpy; mod dma_instance; +mod dma_memcpy; +mod dma_module; mod dma_rom; pub use dma::*; pub use dma_collector::*; pub use dma_input::*; +pub use dma_inputcpy::*; pub use dma_instance::*; +pub use dma_memcpy::*; +pub use dma_module::*; diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs index e883d2c10..2b56544bb 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs @@ -5,27 +5,23 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; -use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use zisk_common::SegmentId; +use zisk_core::zisk_ops::ZiskOp; use zisk_pil::Dma64AlignedAirValues; #[cfg(feature = "packed")] -pub use zisk_pil::{Dma64AlignedTracePacked, Dma64AlignedTraceRowPacked}; +pub use zisk_pil::{ + Dma64AlignedTracePacked as Dma64AlignedTrace, + Dma64AlignedTraceRowPacked as Dma64AlignedTraceRow, +}; #[cfg(not(feature = "packed"))] pub use zisk_pil::{Dma64AlignedTrace, Dma64AlignedTraceRow}; -#[cfg(feature = "packed")] -type Dma64AlignedTraceRowType = Dma64AlignedTraceRowPacked; -#[cfg(feature = "packed")] -type Dma64AlignedTraceType = Dma64AlignedTracePacked; - -#[cfg(not(feature = "packed"))] -type Dma64AlignedTraceRowType = Dma64AlignedTraceRow; -#[cfg(not(feature = "packed"))] -type Dma64AlignedTraceType = Dma64AlignedTrace; - -use crate::{Dma64AlignedInput, DMA_64_ALIGNED_OPS_BY_ROW}; +use crate::{ + Dma64AlignedInput, Dma64AlignedModule, DMA_64_ALIGNED_OPS_BY_ROW, F_SEL_INPUTCPY, F_SEL_MEMCMP, + F_SEL_MEMCPY, F_SEL_MEMSET, +}; use precompiles_helpers::DmaInfo; /// The `Dma64AlignedSM` struct encapsulates the logic of the Dma64Aligned State Machine. @@ -62,7 +58,7 @@ impl Dma64AlignedSM { pub fn process_input( &self, input: &Dma64AlignedInput, - trace: &mut [Dma64AlignedTraceRowType], + trace: &mut [Dma64AlignedTraceRow], _local_16_bits_table: &mut [u32], // for input_cpy air_values: &mut Dma64AlignedAirValues, ) -> usize { @@ -79,9 +75,23 @@ impl Dma64AlignedSM { let mut src64 = ((input.src + 7) >> 3) + skip_count as u32; let mut seq_end = false; let addr_incr_by_row = self.op_x_rows as u32; + + let is_memcpy = input.op == ZiskOp::DMA_XMEMCPY || input.op == ZiskOp::DMA_MEMCPY; + let is_memeq = input.op == ZiskOp::DMA_MEMCMP || input.op == ZiskOp::DMA_XMEMCMP; + let is_memset = input.op == ZiskOp::DMA_XMEMSET; + let is_inputcpy = input.op == ZiskOp::DMA_INPUTCPY; + let fill_byte = if is_memset { (input.encoded & 0xFF) as u8 } else { 0 }; for (irow, row) in trace.iter_mut().enumerate().take(rows) { row.set_main_step(input.step); - row.set_is_mem_eq(input.is_mem_eq); + + row.set_sel_memcpy(is_memcpy); + row.set_sel_memeq(is_memeq); + row.set_sel_memset(is_memset); + row.set_sel_inputcpy(is_inputcpy); + if irow == 0 && input.skip_rows == 0 { + row.set_sel_memcpy_count_load(input.op == ZiskOp::DMA_MEMCPY); + } + row.set_fill_byte(fill_byte); row.set_previous_seq_end(irow == 0 && input.skip_rows == 0); // calculate the first aligned address @@ -91,13 +101,17 @@ impl Dma64AlignedSM { dst64 += addr_incr_by_row; src64 += addr_incr_by_row; - row.set_count(count64 as u32); + row.set_count64(count64 as u32); let use_count = if count64 <= self.op_x_rows { seq_end = true; for index in count64..self.op_x_rows { - row.set_sel_op(index, false); - row.set_value(index, 0, 0); - row.set_value(index, 1, 0); + if index > 0 { + row.set_sel_op_from_1(index - 1, false); + } + row.set_h_value_chunks(index, 0, 0); + row.set_h_value_chunks(index, 1, 0); + row.set_l_value_chunks(index, 0, 0); + row.set_l_value_chunks(index, 1, 0); } count64 } else { @@ -106,11 +120,15 @@ impl Dma64AlignedSM { }; row.set_seq_end(seq_end); for index in 0..use_count { - row.set_sel_op(index, true); + if index > 0 { + row.set_sel_op_from_1(index - 1, true); + } let value = input.src_values[src_values_index]; src_values_index += 1; - row.set_value(index, 0, value as u32); - row.set_value(index, 1, (value >> 32) as u32); + row.set_h_value_chunks(index, 0, (value >> 8) as u32); + row.set_h_value_chunks(index, 1, (value >> 40) as u32); + row.set_l_value_chunks(index, 0, value as u8); + row.set_l_value_chunks(index, 1, (value >> 32) as u8); } } @@ -120,20 +138,26 @@ impl Dma64AlignedSM { air_values.segment_last_src64 = F::ZERO; air_values.segment_last_dst64 = F::ZERO; air_values.segment_last_main_step = F::ZERO; - air_values.segment_last_count = F::ZERO; + air_values.segment_last_count64 = F::ZERO; air_values.last_count_chunk[0] = F::ZERO; air_values.last_count_chunk[1] = F::ZERO; - air_values.segment_last_is_mem_eq = F::ZERO; + air_values.segment_last_flags = F::ZERO; } else { air_values.segment_last_seq_end = F::ZERO; air_values.segment_last_src64 = F::from_u32(src64 - addr_incr_by_row); air_values.segment_last_dst64 = F::from_u32(dst64 - addr_incr_by_row); air_values.segment_last_main_step = F::from_u64(input.step); let last_count = initial_count - (rows - 1) * self.op_x_rows; - air_values.segment_last_count = F::from_u32(last_count as u32); + air_values.segment_last_count64 = F::from_u32(last_count as u32); air_values.last_count_chunk[0] = F::from_u16(last_count as u16); air_values.last_count_chunk[1] = F::from_u16((last_count >> 16) as u16); - air_values.segment_last_is_mem_eq = F::ZERO; + air_values.segment_last_flags = F::from_u16(match input.op { + ZiskOp::DMA_MEMCPY | ZiskOp::DMA_XMEMCPY => F_SEL_MEMCPY, + ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => F_SEL_MEMCMP, + ZiskOp::DMA_INPUTCPY => F_SEL_INPUTCPY, + ZiskOp::DMA_XMEMSET => F_SEL_MEMSET, + _ => panic!("Invalid operation 0x{:02X}", input.op), + } as u16); } } rows @@ -145,20 +169,15 @@ impl Dma64AlignedSM { /// * `trace` - A mutable reference to the Dma trace. /// * `input` - The operation data to process. #[inline(always)] - pub fn process_empty_slice(&self, trace: &mut Dma64AlignedTraceRowType) { - trace.set_main_step(0); - trace.set_is_mem_eq(false); - trace.set_dst64(0); - trace.set_src64(0); - trace.set_count(0); - for index in 0..self.op_x_rows { - trace.set_sel_op(index, false); - trace.set_value(index, 0, 0); - trace.set_value(index, 1, 0); - } + pub fn process_empty_slice(&self, trace: &mut Dma64AlignedTraceRow) { trace.set_seq_end(true); trace.set_previous_seq_end(true); } +} +impl Dma64AlignedModule for Dma64AlignedSM { + fn get_name(&self) -> &'static str { + "dma_64_aligned" + } /// Computes the witness for a series of inputs and produces an `AirInstance`. /// @@ -168,14 +187,14 @@ impl Dma64AlignedSM { /// /// # Returns /// An `AirInstance` containing the computed witness data. - pub fn compute_witness( + fn compute_witness( &self, inputs: &[Vec], segment_id: SegmentId, is_last_segment: bool, trace_buffer: Vec, ) -> ProofmanResult> { - let mut trace = Dma64AlignedTraceType::::new_from_vec(trace_buffer)?; + let mut trace = Dma64AlignedTrace::::new_from_vec(trace_buffer)?; let num_rows = trace.num_rows(); let total_inputs: usize = inputs @@ -218,25 +237,23 @@ impl Dma64AlignedSM { } // padding + air_values.padding_size = F::from_u32((num_rows - row_offset) as u32); + for padding_row in trace_rows.iter_mut().take(num_rows).skip(row_offset) { + self.process_empty_slice(padding_row); + } if row_offset < num_rows { - air_values.padding_size = F::from_u32((num_rows - row_offset) as u32); - self.process_empty_slice(&mut trace_rows[row_offset]); - let empty_row = trace_rows[row_offset]; - trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { - *row = empty_row; - }); air_values.segment_last_seq_end = F::ONE; air_values.segment_last_src64 = F::ZERO; air_values.segment_last_dst64 = F::ZERO; air_values.segment_last_main_step = F::ZERO; - air_values.segment_last_count = F::ZERO; + air_values.segment_last_count64 = F::ZERO; air_values.last_count_chunk[0] = F::ZERO; air_values.last_count_chunk[1] = F::ZERO; - air_values.segment_last_is_mem_eq = F::ZERO; + air_values.segment_last_flags = F::ZERO; } // add range check of count to check that it's a positive 32-bits number - let last_count = air_values.segment_last_count.as_canonical_u64(); + let last_count = air_values.segment_last_count64.as_canonical_u64(); local_16_bits_table[(last_count & 0xFFFF) as usize] += 1; local_16_bits_table[((last_count >> 16) & 0xFFFF) as usize] += 1; @@ -249,11 +266,11 @@ impl Dma64AlignedSM { let first_input = flat_inputs.first().unwrap(); if first_input.skip_rows == 0 { air_values.segment_previous_seq_end = F::ONE; - air_values.segment_previous_dst64 = F::from_u32(0); - air_values.segment_previous_src64 = F::from_u32(0); - air_values.segment_previous_main_step = F::from_u64(0); - air_values.segment_previous_count = F::from_u32(0); - air_values.segment_previous_is_mem_eq = F::from_bool(false); + air_values.segment_previous_dst64 = F::ZERO; + air_values.segment_previous_src64 = F::ZERO; + air_values.segment_previous_main_step = F::ZERO; + air_values.segment_previous_count64 = F::ZERO; + air_values.segment_previous_flags = F::ZERO; } else { assert!(segment_id > 0); air_values.segment_previous_seq_end = F::ZERO; @@ -262,10 +279,17 @@ impl Dma64AlignedSM { air_values.segment_previous_src64 = F::from_u32(trace_rows[0].get_src64() - self.op_x_rows as u32); air_values.segment_previous_main_step = F::from_u64(trace_rows[0].get_main_step()); - air_values.segment_previous_count = - F::from_u32(trace_rows[0].get_count() + self.op_x_rows as u32); - air_values.segment_previous_is_mem_eq = F::from_bool(trace_rows[0].get_is_mem_eq()); + air_values.segment_previous_count64 = + F::from_u32(trace_rows[0].get_count64() + self.op_x_rows as u32); + air_values.segment_previous_flags = F::from_u16(match first_input.op { + ZiskOp::DMA_MEMCPY | ZiskOp::DMA_XMEMCPY => F_SEL_MEMCPY, + ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => F_SEL_MEMCMP, + ZiskOp::DMA_INPUTCPY => F_SEL_INPUTCPY, + ZiskOp::DMA_XMEMSET => F_SEL_MEMSET, + _ => panic!("Invalid operation 0x{:02X}", first_input.op), + } as u16); } + #[cfg(feature = "debug_dma")] { println!("TRACE Dma64AlignedSM @{segment_id} [0] {:?}", trace[0]); diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs index c25df966d..db5a9774f 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs @@ -4,22 +4,28 @@ //! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for //! execution plans. -use crate::Dma64AlignedInput; +use crate::{Dma64AlignedInput, DmaCollectCounters, DmaCollectorRoutingLog}; +use precompiles_helpers::DmaInfo; use std::any::Any; -use zisk_common::{BusDevice, BusId, CollectCounter, OPERATION_BUS_ID, OP_TYPE}; -use zisk_core::ZiskOperationType; - +use zisk_common::{BusDevice, BusId, ChunkId, DMA_ENCODED, OP, OPERATION_BUS_ID, OP_TYPE}; +use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; +#[derive(Debug)] pub struct Dma64AlignedCollector { /// Collected inputs for witness computation. pub inputs: Vec, + pub chunk_id: ChunkId, + + pub rlog: DmaCollectorRoutingLog, + /// The number of inputs to collect. pub num_inputs: u64, /// Helper to skip instructions based on the plan's configuration. - pub collect_counter: CollectCounter, + pub collect_counters: DmaCollectCounters, pub trace_offset: usize, + pub ops_by_row: usize, pub last_segment_collector: bool, } @@ -35,16 +41,21 @@ impl Dma64AlignedCollector { /// # Returns /// A new `Dma64AlignedCollector` instance initialized with the provided parameters. pub fn new( + chunk_id: ChunkId, num_inputs: u64, - collect_counter: CollectCounter, + collect_counters: DmaCollectCounters, + ops_by_row: usize, last_segment_collector: bool, ) -> Self { Self { inputs: Vec::with_capacity(num_inputs as usize), num_inputs, - collect_counter, + collect_counters, trace_offset: 0, + ops_by_row, last_segment_collector, + rlog: DmaCollectorRoutingLog::new(chunk_id), + chunk_id, } } @@ -63,32 +74,77 @@ impl Dma64AlignedCollector { pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + return true; + } + if self.inputs.len() == self.num_inputs as usize { - return false; + debug_assert!(self.collect_counters.is_final_skip()); + return self.rlog.log_discard_cond(false, 1, data, false); } - if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + let op = data[OP] as u8; + let has_src = op == ZiskOp::DMA_MEMCPY + || op == ZiskOp::DMA_XMEMCPY + || op == ZiskOp::DMA_MEMCMP + || op == ZiskOp::DMA_XMEMCMP; + let encoded = data[DMA_ENCODED]; + + if has_src && !DmaInfo::dst_is_aligned_with_src(encoded) { + self.rlog.log_discard(2, data); return true; } - let rows = Dma64AlignedInput::get_rows(data) as u32; + let rows = DmaInfo::get_loop_count(encoded).div_ceil(self.ops_by_row); if rows == 0 { + self.rlog.log_discard(3, data); return true; } - - if let Some((skip, max_count)) = self.collect_counter.should_process(rows) { - self.inputs.push(Dma64AlignedInput::from( - data, - data_ext, - self.trace_offset, - skip as usize, - max_count as usize, - self.last_segment_collector && self.collect_counter.is_final_skip(), - )); + // self.collect_counters.memcpy.should_process(rows) + if let Some((skip, max_count, is_final_skip)) = + self.collect_counters.should_collect(rows as u64, op) + { + self.rlog.log_collect(rows, data); + self.inputs.push(if op == ZiskOp::DMA_XMEMSET { + Dma64AlignedInput::from_memset( + data, + self.trace_offset, + skip as usize, + self.ops_by_row, + max_count as usize, + self.last_segment_collector && is_final_skip, + ) + } else { + Dma64AlignedInput::from( + data, + data_ext, + self.trace_offset, + skip as usize, + self.ops_by_row, + max_count as usize, + self.last_segment_collector && is_final_skip, + ) + }); self.trace_offset += max_count as usize; + } else { + self.rlog.log_discard(10, data); } - - self.inputs.len() < self.num_inputs as usize + if self.inputs.len() >= self.num_inputs as usize { + debug_assert!(self.collect_counters.is_final_skip()); + return self.rlog.log_discard_cond(true, 11, data, false); + } + true + } + pub fn get_debug_info(&self) -> String { + #[cfg(feature = "save_dma_collectors")] + return format!( + "CC|{}|{}|{}\n", + self.chunk_id, + self.inputs.len(), + self.collect_counters.get_debug_info(), + ) + &self.rlog.get_debug_info(); + #[cfg(not(feature = "save_dma_collectors"))] + String::new() } } diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs index b1d159ac6..cd6e70d95 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs @@ -1,5 +1,6 @@ use precompiles_helpers::DmaInfo; -use zisk_common::{A, B, DMA_ENCODED, STEP}; +use zisk_common::{A, B, DMA_ENCODED, OP, STEP}; +use zisk_core::zisk_ops::ZiskOp; use crate::DMA_64_ALIGNED_OPS_BY_ROW; @@ -9,13 +10,14 @@ pub struct Dma64AlignedInput { pub dst: u32, pub is_first_instance_input: bool, pub is_last_instance_input: bool, - pub is_mem_eq: bool, + pub op: u8, pub trace_offset: u32, // offset inside trace to paralelize pub skip_rows: u32, // inside input how many rows skip pub rows: u32, // number of rows used pub step: u64, pub encoded: u64, pub src_values: Vec, + pub fill_byte: u8, } impl Dma64AlignedInput { @@ -45,15 +47,17 @@ impl Dma64AlignedInput { data_ext: &[u64], trace_offset: usize, skip_rows: usize, + ops_x_rows: usize, max_rows: usize, is_last_instance_input: bool, ) -> Self { + let op = data[OP] as u8; let encoded = data[DMA_ENCODED]; let pre_count = DmaInfo::get_pre_count(encoded) as u32; - let skip_count = skip_rows * DMA_64_ALIGNED_OPS_BY_ROW; + let skip_count = skip_rows * ops_x_rows; let data_offset = DmaInfo::get_loop_data_offset(encoded) + skip_count; let count = DmaInfo::get_loop_count(encoded) - skip_count; - let total_rows = DmaInfo::get_loop_count(encoded).div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); + let total_rows = DmaInfo::get_loop_count(encoded).div_ceil(ops_x_rows); let rows = std::cmp::min(total_rows - skip_rows, max_rows) as u32; Self { dst: data[A] as u32 + pre_count, @@ -66,7 +70,89 @@ impl Dma64AlignedInput { rows, encoded, src_values: data_ext[data_offset..data_offset + count].to_vec(), - is_mem_eq: false, + op: match op { + ZiskOp::DMA_MEMCPY => { + if DmaInfo::is_direct(encoded) { + ZiskOp::DMA_MEMCPY + } else { + ZiskOp::DMA_XMEMCPY + } + } + _ => op, + }, + fill_byte: 0, } } + pub fn from_memset( + data: &[u64], + trace_offset: usize, + skip_rows: usize, + ops_x_rows: usize, + max_rows: usize, + is_last_instance_input: bool, + ) -> Self { + let op = data[OP] as u8; + let encoded = data[DMA_ENCODED]; + let pre_count = DmaInfo::get_pre_count(encoded) as u32; + let total_rows = DmaInfo::get_loop_count(encoded).div_ceil(ops_x_rows); + let rows = std::cmp::min(total_rows - skip_rows, max_rows) as u32; + Self { + dst: data[A] as u32 + pre_count, + src: 0, + trace_offset: trace_offset as u32, + is_first_instance_input: trace_offset == 0, + is_last_instance_input, + step: data[STEP], + skip_rows: skip_rows as u32, + rows, + encoded, + src_values: vec![], + op, + fill_byte: DmaInfo::get_fill_byte(encoded), + } + } + + #[cfg(feature = "save_dma_inputs")] + /// Writes a list of Dma64AlignedInput instances to a text file with columns separated by |. + /// Path is taken from DEBUG_OUTPUT_PATH environment variable, defaulting to "tmp/". + pub fn save_debug_info(filename: &str, inputs: &[Vec]) -> std::io::Result<()> { + use std::io::Write; + + let path = std::env::var("DEBUG_OUTPUT_PATH").unwrap_or_else(|_| "tmp/".to_string()); + let full_path = format!("{}{}", path, filename); + + let mut file = std::fs::File::create(&full_path)?; + + // Write header + writeln!( + file, + "{:>8}|{:>10}|{:>10}|{:>22}|{:>21}|{:>4}|{:>12}|{:>9}|{:>8}|{:>14}|{:>18}|{:>9}|src_values", + "pos", "src", "dst", "is_first_instance_input", "is_last_instance_input", "op", "trace_offset", "skip_rows", "rows", "step", "encoded", "fill_byte" + )?; + + // Write data rows + for (pos, input) in inputs.iter().flatten().enumerate() { + let src_values_hex: Vec = + input.src_values.iter().map(|v| format!("0x{:016X}", v)).collect(); + writeln!( + file, + "{:>8}|0x{:08X}|0x{:08X}|{:>22}|{:>21}|{:>4}|{:>12}|{:>9}|{:>8}|{:>14}|0x{:016X}|{:>9}|{}", + pos, + input.src, + input.dst, + input.is_first_instance_input, + input.is_last_instance_input, + input.op, + input.trace_offset, + input.skip_rows, + input.rows, + input.step, + input.encoded, + input.fill_byte, + src_values_hex.join(",") + )?; + } + + Ok(()) + } } diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs new file mode 100644 index 000000000..6ae71a35a --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs @@ -0,0 +1,277 @@ +use std::sync::Arc; + +use fields::PrimeField64; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; +use zisk_common::SegmentId; +use zisk_core::zisk_ops::ZiskOp; +use zisk_pil::{Dma64AlignedInputCpyAirValues, DUAL_RANGE_BYTE_ID}; + +#[cfg(feature = "packed")] +pub use zisk_pil::{ + Dma64AlignedInputCpyTracePacked as Dma64AlignedInputCpyTrace, + Dma64AlignedInputCpyTraceRowPacked as Dma64AlignedInputCpyTraceRow, +}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{Dma64AlignedInputCpyTrace, Dma64AlignedInputCpyTraceRow}; + +use crate::{ + Dma64AlignedInput, Dma64AlignedModule, DMA_64_ALIGNED_INPUTCPY_OPS_BY_ROW, F_SEL_INPUTCPY, +}; +use precompiles_helpers::DmaInfo; + +/// The `Dma64AlignedInputCpySM` struct encapsulates the logic of the Dma64Aligned State Machine. +pub struct Dma64AlignedInputCpySM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + range_16_bits_id: usize, + range_24_bits_id: usize, + dual_range_byte_id: usize, + op_x_rows: usize, +} + +impl Dma64AlignedInputCpySM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `Dma64AlignedInputCpySM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + range_16_bits_id: std + .get_range_id(0, 0xFFFF, None) + .expect("Failed to get 16b table ID"), + range_24_bits_id: std + .get_range_id(0, (1 << 24) - 1, None) + .expect("Failed to get 24b table ID"), + dual_range_byte_id: std + .get_virtual_table_id(DUAL_RANGE_BYTE_ID) + .expect("Failed to get tabl eDUAL_RANGE_BYTE ID ID"), + op_x_rows: DMA_64_ALIGNED_INPUTCPY_OPS_BY_ROW, + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_input( + &self, + input: &Dma64AlignedInput, + trace: &mut [Dma64AlignedInputCpyTraceRow], + local_dual_byte: &mut [u64], // for input_cpy + values_24_bits: &mut Vec, + air_values: &mut Dma64AlignedInputCpyAirValues, + ) -> usize { + let mut values_index = 0; + let rows = input.rows as usize; + let skip_count = input.skip_rows as usize * self.op_x_rows; + let initial_count = DmaInfo::get_loop_count(input.encoded) - skip_count; + let mut count64 = initial_count; + let mut dst64 = ((input.dst + 7) >> 3) + skip_count as u32; + let mut seq_end = false; + let addr_incr_by_row = self.op_x_rows as u32; + for (irow, row) in trace.iter_mut().enumerate().take(rows) { + row.set_main_step(input.step); + debug_assert!(input.op == ZiskOp::DMA_INPUTCPY); + row.set_sel_inputcpy(true); + row.set_previous_seq_end(irow == 0 && input.skip_rows == 0); + + // calculate the first aligned address + // if dst is aligned is same address if not it's addr + 8 + row.set_dst64(dst64); + dst64 += addr_incr_by_row; + + row.set_count64(count64 as u32); + let use_count = if count64 <= self.op_x_rows { + seq_end = true; + count64 + } else { + count64 -= self.op_x_rows; + self.op_x_rows + }; + row.set_seq_end(seq_end); + for index in 0..use_count { + if index > 0 { + row.set_sel_op_from_1(index - 1, true); + } + let value = input.src_values[values_index]; + values_index += 1; + let h0 = ((value & 0xFFFF_FF00) >> 8) as u32; + let h1 = (value >> 40) as u32; + let l0: u8 = value as u8; + let l1 = (value >> 32) as u8; + row.set_h_value_chunks(index, 0, h0); + row.set_h_value_chunks(index, 1, h1); + row.set_l_value_chunks(index, 0, l0); + row.set_l_value_chunks(index, 1, l1); + let pos = ((l1 as usize) << 8) | (l0 as usize); + local_dual_byte[pos] += 1; + values_24_bits.push(h0); + values_24_bits.push(h1); + } + } + + if input.is_last_instance_input { + if seq_end { + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count64 = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_flags = F::ZERO; + } else { + air_values.segment_last_seq_end = F::ZERO; + air_values.segment_last_dst64 = F::from_u32(dst64 - addr_incr_by_row); + air_values.segment_last_main_step = F::from_u64(input.step); + let last_count = initial_count - (rows - 1) * self.op_x_rows; + air_values.segment_last_count64 = F::from_u32(last_count as u32); + air_values.last_count_chunk[0] = F::from_u16(last_count as u16); + air_values.last_count_chunk[1] = F::from_u16((last_count >> 16) as u16); + air_values.segment_last_flags = F::from_u16(F_SEL_INPUTCPY as u16); + } + } + rows + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut Dma64AlignedInputCpyTraceRow) { + trace.set_seq_end(true); + trace.set_previous_seq_end(true); + } +} +impl Dma64AlignedModule for Dma64AlignedInputCpySM { + fn get_name(&self) -> &'static str { + "dma_64_aligned_inputcpy" + } + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + fn compute_witness( + &self, + inputs: &[Vec], + segment_id: SegmentId, + is_last_segment: bool, + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = Dma64AlignedInputCpyTrace::::new_from_vec_zeroes(trace_buffer)?; + let num_rows = trace.num_rows(); + + println!("{:?}", inputs); + let total_inputs: usize = inputs + .iter() + .map(|inputs| inputs.iter().map(|input| input.rows as usize).sum::()) + .sum(); + + assert!(total_inputs > 0); + assert!( + total_inputs <= num_rows, + "Too many inputs, total_inputs:{total_inputs} num_rows:{num_rows}" + ); + + tracing::debug!( + "··· Creating Dma64Aligned instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_64_ALIGNED_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + let mut values_24_bits = Vec::with_capacity(num_rows * self.op_x_rows * 2); + let mut local_dual_byte = [0u64; 1 << 16]; + let mut air_values = Dma64AlignedInputCpyAirValues::::new(); + + let mut row_offset = 0; + for input in flat_inputs.iter() { + let rows_used = self.process_input( + input, + &mut trace_rows[row_offset..], + &mut local_dual_byte, + &mut values_24_bits, + &mut air_values, + ); + row_offset += rows_used; + } + + let padding_size = num_rows.saturating_sub(row_offset); + println!("DMA: Total rows used: {row_offset}, padding with {padding_size} empty rows to reach {num_rows}"); + air_values.padding_size = F::from_u32(padding_size as u32); + + // padding + if padding_size > 0 { + self.process_empty_slice(&mut trace_rows[row_offset]); + let empty_row = trace_rows[row_offset]; + trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count64 = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_flags = F::ZERO; + } + + // local_dual_byte[0] += padding_size as u64; + // self.std.range_check(self.range_24_bits_id, 0, padding_size as u64 * 2); + + // add range check of count to check that it's a positive 32-bits number + let last_count = air_values.segment_last_count64.as_canonical_u64(); + + self.std.range_check(self.range_16_bits_id, (last_count & 0xFFFF) as i64, 1); + self.std.range_check(self.range_16_bits_id, ((last_count >> 16) & 0xFFFF) as i64, 1); + self.std.inc_virtual_rows_ranged(self.dual_range_byte_id, &local_dual_byte); + for value in values_24_bits.iter() { + // println!("R:{} VALUE 0x{:08X}", self.range_24_bits_id, *value as i64); + self.std.range_check(self.range_24_bits_id, *value as i64, 1); + } + + let segment_id = segment_id.into(); + air_values.segment_id = F::from_usize(segment_id); + air_values.is_last_segment = F::from_bool(is_last_segment); + + let first_input = flat_inputs.first().unwrap(); + if first_input.skip_rows == 0 { + air_values.segment_previous_seq_end = F::ONE; + air_values.segment_previous_dst64 = F::ZERO; + air_values.segment_previous_main_step = F::ZERO; + air_values.segment_previous_count64 = F::ZERO; + air_values.segment_previous_flags = F::ZERO; + } else { + assert!(segment_id > 0); + air_values.segment_previous_seq_end = F::ZERO; + air_values.segment_previous_dst64 = + F::from_u32(trace_rows[0].get_dst64() - self.op_x_rows as u32); + air_values.segment_previous_main_step = F::from_u64(trace_rows[0].get_main_step()); + air_values.segment_previous_count64 = + F::from_u32(trace_rows[0].get_count64() + self.op_x_rows as u32); + air_values.segment_previous_flags = F::from_u16(F_SEL_INPUTCPY as u16); + } + timer_stop_and_log_trace!(DMA_64_ALIGNED_TRACE); + let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs index 054d5c847..17efd9b7b 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs @@ -4,13 +4,29 @@ //! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for //! execution plans. -use crate::{Dma64AlignedCollector, Dma64AlignedSM, DmaCheckPoint}; +#[cfg(feature = "save_dma_collectors")] +use crate::save_dma_collectors; +#[cfg(feature = "save_dma_inputs")] +use crate::Dma64AlignedInput; +use crate::{ + Dma64AlignedCollector, Dma64AlignedModule, DmaCheckPoint, DMA_64_ALIGNED_INPUTCPY_OPS_BY_ROW, + DMA_64_ALIGNED_MEMCPY_OPS_BY_ROW, DMA_64_ALIGNED_MEMSET_OPS_BY_ROW, + DMA_64_ALIGNED_MEM_OPS_BY_ROW, DMA_64_ALIGNED_OPS_BY_ROW, +}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::sync::Arc; use zisk_common::ChunkId; use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; -use zisk_pil::Dma64AlignedTrace; +use zisk_pil::{ + Dma64AlignedInputCpyTrace, Dma64AlignedMemCpyTrace, Dma64AlignedMemSetTrace, + Dma64AlignedMemTrace, Dma64AlignedTrace, +}; + +pub const F_SEL_MEMCPY: u64 = 1; +pub const F_SEL_MEMCMP: u64 = 2; +pub const F_SEL_INPUTCPY: u64 = 4; +pub const F_SEL_MEMSET: u64 = 8; /// The `Dma64AlignedInstance` struct represents an instance for the Dma State Machine. /// @@ -18,7 +34,7 @@ use zisk_pil::Dma64AlignedTrace; /// to compute witnesses for the Dma64Aligned State Machine. pub struct Dma64AlignedInstance { /// Dma state machine. - dma_64_aligned_sm: Arc>, + module: Arc>, /// Instance context. ictx: InstanceCtx, @@ -31,36 +47,40 @@ impl Dma64AlignedInstance { /// Creates a new `Dma64AlignedInstance`. /// /// # Arguments - /// * `dma_64_aligned_sm` - An `Arc`-wrapped reference to the Dma 64 Aligned State Machine. + /// * `module` - An `Arc`-wrapped reference to the Dma 64 Aligned Module. /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. /// * `bus_id` - The bus ID associated with this instance. /// /// # Returns /// A new `Dma64AlignedInstance` instance initialized with the provided state machine and /// context. - pub fn new(dma_64_aligned_sm: Arc>, ictx: InstanceCtx) -> Self { + pub fn new(module: Arc>, ictx: InstanceCtx) -> Self { let is_last_segment = { let meta = ictx.plan.meta.as_ref().unwrap(); let checkpoint = meta.downcast_ref::().unwrap(); checkpoint.is_last_segment }; - Self { dma_64_aligned_sm, ictx, is_last_segment } + Self { module, ictx, is_last_segment } } pub fn build_dma_collector(&self, chunk_id: ChunkId) -> Dma64AlignedCollector { - assert_eq!( - self.ictx.plan.air_id, - Dma64AlignedTrace::::AIR_ID, - "Dma64AlignedInstance: Unsupported air_id: {:?}", - self.ictx.plan.air_id - ); + let ops_by_row = match self.ictx.plan.air_id { + Dma64AlignedTrace::::AIR_ID => DMA_64_ALIGNED_OPS_BY_ROW, + Dma64AlignedMemCpyTrace::::AIR_ID => DMA_64_ALIGNED_MEMCPY_OPS_BY_ROW, + Dma64AlignedInputCpyTrace::::AIR_ID => DMA_64_ALIGNED_INPUTCPY_OPS_BY_ROW, + Dma64AlignedMemSetTrace::::AIR_ID => DMA_64_ALIGNED_MEMSET_OPS_BY_ROW, + Dma64AlignedMemTrace::::AIR_ID => DMA_64_ALIGNED_MEM_OPS_BY_ROW, + _ => panic!("Dma64AlignedInstance: Unsupported air_id: {:?}", self.ictx.plan.air_id), + }; let meta = self.ictx.plan.meta.as_ref().unwrap(); let collect_info = meta.downcast_ref::().unwrap(); - let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; + let (num_inputs, collect_counters) = collect_info.chunks[&chunk_id]; Dma64AlignedCollector::new( + chunk_id, num_inputs, - collect_counter, + collect_counters, + ops_by_row, Some(chunk_id) == collect_info.last_chunk, ) } @@ -84,16 +104,37 @@ impl Instance for Dma64AlignedInstance { collectors: Vec<(usize, Box>)>, trace_buffer: Vec, ) -> ProofmanResult>> { + #[cfg(feature = "save_dma_collectors")] + let (debug, inputs): (Vec<_>, Vec<_>) = collectors + .into_iter() + .map(|(_, collector)| { + let collector = collector.as_any().downcast::().unwrap(); + (collector.get_debug_info(), collector.inputs) + }) + .unzip(); + #[cfg(not(feature = "save_dma_collectors"))] let inputs: Vec<_> = collectors .into_iter() .map(|(_, collector)| { collector.as_any().downcast::().unwrap().inputs }) .collect(); - // Extract segment id from instance context + let segment_id = self.ictx.plan.segment_id.unwrap(); - Ok(Some(self.dma_64_aligned_sm.compute_witness( + #[cfg(feature = "save_dma_collectors")] + save_dma_collectors( + &format!("{}_collector_{segment_id:04}.txt", self.module.get_name()), + debug, + )?; + + #[cfg(feature = "save_dma_inputs")] + Dma64AlignedInput::save_debug_info( + &format!("{}_inputs_{segment_id:04}.txt", self.module.get_name()), + &inputs, + )?; + + Ok(Some(self.module.compute_witness( &inputs, segment_id, self.is_last_segment, @@ -118,21 +159,7 @@ impl Instance for Dma64AlignedInstance { } fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { - assert_eq!( - self.ictx.plan.air_id, - Dma64AlignedTrace::::AIR_ID, - "Dma64AlignedInstance: Unsupported air_id: {:?}", - self.ictx.plan.air_id - ); - - let meta = self.ictx.plan.meta.as_ref().unwrap(); - let collect_info = meta.downcast_ref::().unwrap(); - let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; - Some(Box::new(Dma64AlignedCollector::new( - num_inputs, - collect_counter, - Some(chunk_id) == collect_info.last_chunk, - ))) + Some(Box::new(self.build_dma_collector(chunk_id))) } fn as_any(&self) -> &dyn std::any::Any { diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs new file mode 100644 index 000000000..016913142 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs @@ -0,0 +1,291 @@ +use std::sync::Arc; + +use fields::PrimeField64; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; +use zisk_common::SegmentId; +use zisk_core::zisk_ops::ZiskOp; +use zisk_pil::Dma64AlignedMemAirValues; + +#[cfg(feature = "packed")] +pub use zisk_pil::{ + Dma64AlignedMemTracePacked as Dma64AlignedMemTrace, + Dma64AlignedMemTraceRowPacked as Dma64AlignedMemTraceRow, +}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{Dma64AlignedMemTrace, Dma64AlignedMemTraceRow}; + +use crate::{ + Dma64AlignedInput, Dma64AlignedModule, DMA_64_ALIGNED_MEM_OPS_BY_ROW, F_SEL_INPUTCPY, + F_SEL_MEMCMP, F_SEL_MEMCPY, F_SEL_MEMSET, +}; +use precompiles_helpers::DmaInfo; + +/// The `Dma64AlignedMemSM` struct encapsulates the logic of the Dma64Aligned State Machine. +pub struct Dma64AlignedMemSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + range_16_bits_id: usize, + op_x_rows: usize, +} + +impl Dma64AlignedMemSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `Dma64AlignedMemSM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + range_16_bits_id: std + .get_range_id(0, 0xFFFF, None) + .expect("Failed to get 16b table ID"), + op_x_rows: DMA_64_ALIGNED_MEM_OPS_BY_ROW, + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_input( + &self, + input: &Dma64AlignedInput, + trace: &mut [Dma64AlignedMemTraceRow], + _local_16_bits_table: &mut [u32], // for input_cpy + air_values: &mut Dma64AlignedMemAirValues, + ) -> usize { + let rows = input.rows as usize; + let skip_count = input.skip_rows as usize * self.op_x_rows; + let initial_count = DmaInfo::get_loop_count(input.encoded) - skip_count; + let mut count64 = initial_count; + // println!( + // "DMA_64_ALIGNED INPUT {input:?} count:{count64} rows:{rows} dma_info:{} S:{}", + // DmaInfo::to_string(input.encoded), + // input.step + // ); + let mut src_values_index = 0; + let mut dst64 = ((input.dst + 7) >> 3) + skip_count as u32; + let mut src64 = ((input.src + 7) >> 3) + skip_count as u32; + let mut seq_end = false; + let addr_incr_by_row = self.op_x_rows as u32; + let is_memcpy = input.op == ZiskOp::DMA_XMEMCPY; + let is_memeq = input.op == ZiskOp::DMA_MEMCMP || input.op == ZiskOp::DMA_XMEMCMP; + let is_memset = input.op == ZiskOp::DMA_XMEMSET; + let fill_byte = if is_memset { (input.encoded & 0xFF) as u8 } else { 0 }; + + for (irow, row) in trace.iter_mut().enumerate().take(rows) { + row.set_main_step(input.step); + row.set_sel_memcpy(is_memcpy); + row.set_sel_memeq(is_memeq); + if irow == 0 && input.skip_rows == 0 { + row.set_sel_memcpy_count_load(input.op == ZiskOp::DMA_MEMCPY); + } + row.set_sel_memset(is_memset); + row.set_fill_byte(fill_byte); + row.set_previous_seq_end(irow == 0 && input.skip_rows == 0); + + // calculate the first aligned address + // if dst is aligned is same address if not it's addr + 8 + row.set_dst64(dst64); + row.set_src64(src64); + dst64 += addr_incr_by_row; + src64 += addr_incr_by_row; + + row.set_count64(count64 as u32); + let use_count = if count64 <= self.op_x_rows { + seq_end = true; + for index in count64..self.op_x_rows { + if index > 0 { + row.set_sel_op_from_1(index - 1, false); + } + } + count64 + } else { + count64 -= self.op_x_rows; + self.op_x_rows + }; + row.set_seq_end(seq_end); + for index in 0..use_count { + if index > 0 { + row.set_sel_op_from_1(index - 1, true); + } + let value = input.src_values[src_values_index]; + row.set_value(index, 0, value as u32); + row.set_value(index, 1, (value >> 32) as u32); + src_values_index += 1; + } + } + + if input.is_last_instance_input { + if seq_end { + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count64 = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_flags = F::ZERO; + } else { + air_values.segment_last_seq_end = F::ZERO; + air_values.segment_last_src64 = F::from_u32(src64 - addr_incr_by_row); + air_values.segment_last_dst64 = F::from_u32(dst64 - addr_incr_by_row); + air_values.segment_last_main_step = F::from_u64(input.step); + let last_count = initial_count - (rows - 1) * self.op_x_rows; + air_values.segment_last_count64 = F::from_u32(last_count as u32); + air_values.last_count_chunk[0] = F::from_u16(last_count as u16); + air_values.last_count_chunk[1] = F::from_u16((last_count >> 16) as u16); + air_values.segment_last_flags = F::from_u16(match input.op { + ZiskOp::DMA_MEMCPY | ZiskOp::DMA_XMEMCPY => F_SEL_MEMCPY, + ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => F_SEL_MEMCMP, + ZiskOp::DMA_INPUTCPY => F_SEL_INPUTCPY, + ZiskOp::DMA_XMEMSET => F_SEL_MEMSET, + _ => panic!("Invalid operation 0x{:02X}", input.op), + } as u16); + } + } + rows + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut Dma64AlignedMemTraceRow) { + trace.set_seq_end(true); + trace.set_previous_seq_end(true); + } +} +impl Dma64AlignedModule for Dma64AlignedMemSM { + fn get_name(&self) -> &'static str { + "dma_64_aligned_mem" + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + fn compute_witness( + &self, + inputs: &[Vec], + segment_id: SegmentId, + is_last_segment: bool, + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = Dma64AlignedMemTrace::::new_from_vec_zeroes(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs + .iter() + .map(|inputs| inputs.iter().map(|input| input.rows as usize).sum::()) + .sum(); + + assert!(total_inputs > 0); + // println!("LAST INPUT: {:?}", inputs.last().unwrap()); + // println!("DMA_64_ALIGNED TOTALS total_inputs:{total_inputs} num_rows:{num_rows}"); + assert!( + total_inputs <= num_rows, + "Too many inputs, total_inputs:{total_inputs} num_rows:{num_rows}" + ); + + tracing::debug!( + "··· Creating Dma64Aligned instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_64_ALIGNED_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + let mut local_16_bits_table = vec![0u32; 1 << 16]; + let mut air_values = Dma64AlignedMemAirValues::::new(); + + // TODO: inputs between instances + let mut row_offset = 0; + for input in flat_inputs.iter() { + let rows_used = self.process_input( + input, + &mut trace_rows[row_offset..], + &mut local_16_bits_table, + &mut air_values, + ); + row_offset += rows_used; + } + + // padding + if row_offset < num_rows { + air_values.padding_size = F::from_u32((num_rows - row_offset) as u32); + self.process_empty_slice(&mut trace_rows[row_offset]); + let empty_row = trace_rows[row_offset]; + trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count64 = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_flags = F::ZERO; + } + + // add range check of count to check that it's a positive 32-bits number + let last_count = air_values.segment_last_count64.as_canonical_u64(); + local_16_bits_table[(last_count & 0xFFFF) as usize] += 1; + local_16_bits_table[((last_count >> 16) & 0xFFFF) as usize] += 1; + + self.std.range_checks(self.range_16_bits_id, local_16_bits_table); + + let segment_id = segment_id.into(); + air_values.segment_id = F::from_usize(segment_id); + air_values.is_last_segment = F::from_bool(is_last_segment); + + let first_input = flat_inputs.first().unwrap(); + if first_input.skip_rows == 0 { + air_values.segment_previous_seq_end = F::ONE; + air_values.segment_previous_dst64 = F::ZERO; + air_values.segment_previous_src64 = F::ZERO; + air_values.segment_previous_main_step = F::ZERO; + air_values.segment_previous_count64 = F::ZERO; + air_values.segment_previous_flags = F::ZERO; + } else { + assert!(segment_id > 0); + air_values.segment_previous_seq_end = F::ZERO; + air_values.segment_previous_dst64 = + F::from_u32(trace_rows[0].get_dst64() - self.op_x_rows as u32); + air_values.segment_previous_src64 = + F::from_u32(trace_rows[0].get_src64() - self.op_x_rows as u32); + air_values.segment_previous_main_step = F::from_u64(trace_rows[0].get_main_step()); + air_values.segment_previous_count64 = + F::from_u32(trace_rows[0].get_count64() + self.op_x_rows as u32); + air_values.segment_previous_flags = F::from_u16(match first_input.op { + ZiskOp::DMA_MEMCPY | ZiskOp::DMA_XMEMCPY => F_SEL_MEMCPY, + ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => F_SEL_MEMCMP, + ZiskOp::DMA_INPUTCPY => F_SEL_INPUTCPY, + ZiskOp::DMA_XMEMSET => F_SEL_MEMSET, + _ => panic!("Invalid operation 0x{:02X}", first_input.op), + } as u16); + } + timer_stop_and_log_trace!(DMA_64_ALIGNED_TRACE); + let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs new file mode 100644 index 000000000..355749336 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs @@ -0,0 +1,271 @@ +use std::sync::Arc; + +use fields::PrimeField64; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; +use zisk_common::SegmentId; +use zisk_core::zisk_ops::ZiskOp; +use zisk_pil::Dma64AlignedMemCpyAirValues; + +#[cfg(feature = "packed")] +pub use zisk_pil::{ + Dma64AlignedMemCpyTracePacked as Dma64AlignedMemCpyTrace, + Dma64AlignedMemCpyTraceRowPacked as Dma64AlignedMemCpyTraceRow, +}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{Dma64AlignedMemCpyTrace, Dma64AlignedMemCpyTraceRow}; + +use crate::{ + Dma64AlignedInput, Dma64AlignedModule, DMA_64_ALIGNED_MEMCPY_OPS_BY_ROW, F_SEL_MEMCPY, +}; +use precompiles_helpers::DmaInfo; + +/// The `Dma64AlignedMemCpySM` struct encapsulates the logic of the Dma64Aligned State Machine. +pub struct Dma64AlignedMemCpySM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + range_16_bits_id: usize, + op_x_rows: usize, +} + +impl Dma64AlignedMemCpySM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `Dma64AlignedMemCpySM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + range_16_bits_id: std + .get_range_id(0, 0xFFFF, None) + .expect("Failed to get 16b table ID"), + op_x_rows: DMA_64_ALIGNED_MEMCPY_OPS_BY_ROW, + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_input( + &self, + input: &Dma64AlignedInput, + trace: &mut [Dma64AlignedMemCpyTraceRow], + _local_16_bits_table: &mut [u32], // for input_cpy + air_values: &mut Dma64AlignedMemCpyAirValues, + ) -> usize { + let rows = input.rows as usize; + let skip_count = input.skip_rows as usize * self.op_x_rows; + let initial_count = DmaInfo::get_loop_count(input.encoded) - skip_count; + let mut count64 = initial_count; + // println!( + // "DMA_64_ALIGNED INPUT {input:?} count:{count64} rows:{rows} dma_info:{}", + // DmaInfo::to_string(input.encoded) + // ); + let mut src_values_index = 0; + let mut dst64 = ((input.dst + 7) >> 3) + skip_count as u32; + let mut src64 = ((input.src + 7) >> 3) + skip_count as u32; + let mut seq_end = false; + let addr_incr_by_row = self.op_x_rows as u32; + for (irow, row) in trace.iter_mut().enumerate().take(rows) { + row.set_main_step(input.step); + row.set_sel_memcpy(input.op == ZiskOp::DMA_XMEMCPY || input.op == ZiskOp::DMA_MEMCPY); + row.set_previous_seq_end(irow == 0 && input.skip_rows == 0); + + if irow == 0 && input.skip_rows == 0 { + row.set_sel_memcpy_count_load(input.op == ZiskOp::DMA_MEMCPY); + } + + // calculate the first aligned address + // if dst is aligned is same address if not it's addr + 8 + row.set_dst64(dst64); + row.set_src64(src64); + dst64 += addr_incr_by_row; + src64 += addr_incr_by_row; + + row.set_count64(count64 as u32); + // println!( + // "DMA_64_ALIGNED INPUT count64:{count64} dst64:{dst64} src64:{src64} ops_x_row:{}", + // self.op_x_rows + // ); + let use_count = if count64 <= self.op_x_rows { + seq_end = true; + // trace i zerofilled, not set values zero + count64 + } else { + count64 -= self.op_x_rows; + self.op_x_rows + }; + row.set_seq_end(seq_end); + for index in 0..use_count { + if index > 0 { + row.set_sel_op_from_1(index - 1, true); + } + let value = input.src_values[src_values_index]; + src_values_index += 1; + row.set_value(index, 0, value as u32); + row.set_value(index, 1, (value >> 32) as u32); + } + // println!("DMA_64_ALIGNED TRACE ROW {irow} {row:?}"); + } + + if input.is_last_instance_input { + if seq_end { + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count64 = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_flags = F::ZERO; + } else { + air_values.segment_last_seq_end = F::ZERO; + air_values.segment_last_src64 = F::from_u32(src64 - addr_incr_by_row); + air_values.segment_last_dst64 = F::from_u32(dst64 - addr_incr_by_row); + air_values.segment_last_main_step = F::from_u64(input.step); + let last_count = initial_count - (rows - 1) * self.op_x_rows; + air_values.segment_last_count64 = F::from_u32(last_count as u32); + air_values.last_count_chunk[0] = F::from_u16(last_count as u16); + air_values.last_count_chunk[1] = F::from_u16((last_count >> 16) as u16); + air_values.segment_last_flags = F::from_u16(F_SEL_MEMCPY as u16); + } + } + rows + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut Dma64AlignedMemCpyTraceRow) { + trace.set_seq_end(true); + trace.set_previous_seq_end(true); + } +} +impl Dma64AlignedModule for Dma64AlignedMemCpySM { + fn get_name(&self) -> &'static str { + "dma_64_aligned_memcpy" + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + fn compute_witness( + &self, + inputs: &[Vec], + segment_id: SegmentId, + is_last_segment: bool, + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = Dma64AlignedMemCpyTrace::::new_from_vec_zeroes(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs + .iter() + .map(|inputs| inputs.iter().map(|input| input.rows as usize).sum::()) + .sum(); + + assert!(total_inputs > 0); + // println!("LAST INPUT: {:?}", inputs.last().unwrap()); + // println!("DMA_64_ALIGNED TOTALS total_inputs:{total_inputs} num_rows:{num_rows}"); + assert!( + total_inputs <= num_rows, + "Too many inputs, total_inputs:{total_inputs} num_rows:{num_rows}" + ); + + tracing::debug!( + "··· Creating Dma64Aligned instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_64_ALIGNED_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + let mut local_16_bits_table = vec![0u32; 1 << 16]; + let mut air_values = Dma64AlignedMemCpyAirValues::::new(); + + // TODO: inputs between instances + let mut row_offset = 0; + for input in flat_inputs.iter() { + let rows_used = self.process_input( + input, + &mut trace_rows[row_offset..], + &mut local_16_bits_table, + &mut air_values, + ); + row_offset += rows_used; + } + + // padding + if row_offset < num_rows { + air_values.padding_size = F::from_u32((num_rows - row_offset) as u32); + self.process_empty_slice(&mut trace_rows[row_offset]); + let empty_row = trace_rows[row_offset]; + trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_src64 = F::ZERO; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count64 = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_flags = F::ZERO; + } + + // add range check of count to check that it's a positive 32-bits number + let last_count = air_values.segment_last_count64.as_canonical_u64(); + local_16_bits_table[(last_count & 0xFFFF) as usize] += 1; + local_16_bits_table[((last_count >> 16) & 0xFFFF) as usize] += 1; + + self.std.range_checks(self.range_16_bits_id, local_16_bits_table); + + let segment_id = segment_id.into(); + air_values.segment_id = F::from_usize(segment_id); + air_values.is_last_segment = F::from_bool(is_last_segment); + + let first_input = flat_inputs.first().unwrap(); + if first_input.skip_rows == 0 { + air_values.segment_previous_seq_end = F::ONE; + air_values.segment_previous_dst64 = F::ZERO; + air_values.segment_previous_src64 = F::ZERO; + air_values.segment_previous_main_step = F::ZERO; + air_values.segment_previous_count64 = F::ZERO; + air_values.segment_previous_flags = F::ZERO; + } else { + assert!(segment_id > 0); + air_values.segment_previous_seq_end = F::ZERO; + air_values.segment_previous_dst64 = + F::from_u32(trace_rows[0].get_dst64() - self.op_x_rows as u32); + air_values.segment_previous_src64 = + F::from_u32(trace_rows[0].get_src64() - self.op_x_rows as u32); + air_values.segment_previous_main_step = F::from_u64(trace_rows[0].get_main_step()); + air_values.segment_previous_count64 = + F::from_u32(trace_rows[0].get_count64() + self.op_x_rows as u32); + air_values.segment_previous_flags = F::from_u16(F_SEL_MEMCPY as u16); + } + timer_stop_and_log_trace!(DMA_64_ALIGNED_TRACE); + let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs new file mode 100644 index 000000000..bb56bd043 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs @@ -0,0 +1,255 @@ +use std::sync::Arc; + +use fields::PrimeField64; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; +use zisk_common::SegmentId; +use zisk_pil::Dma64AlignedMemSetAirValues; + +#[cfg(feature = "packed")] +pub use zisk_pil::{ + Dma64AlignedMemSetTracePacked as Dma64AlignedMemSetTrace, + Dma64AlignedMemSetTraceRowPacked as Dma64AlignedMemSetTraceRow, +}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{Dma64AlignedMemSetTrace, Dma64AlignedMemSetTraceRow}; + +use crate::{ + Dma64AlignedInput, Dma64AlignedModule, DMA_64_ALIGNED_MEMSET_OPS_BY_ROW, F_SEL_MEMSET, +}; +use precompiles_helpers::DmaInfo; + +/// The `Dma64AlignedMemSetSM` struct encapsulates the logic of the Dma64Aligned State Machine. +pub struct Dma64AlignedMemSetSM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + range_16_bits_id: usize, + op_x_rows: usize, +} + +impl Dma64AlignedMemSetSM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `Dma64AlignedMemSetSM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + range_16_bits_id: std + .get_range_id(0, 0xFFFF, None) + .expect("Failed to get 16b table ID"), + op_x_rows: DMA_64_ALIGNED_MEMSET_OPS_BY_ROW, + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_input( + &self, + input: &Dma64AlignedInput, + trace: &mut [Dma64AlignedMemSetTraceRow], + _local_16_bits_table: &mut [u32], // for input_cpy + air_values: &mut Dma64AlignedMemSetAirValues, + ) -> usize { + let rows = input.rows as usize; + let skip_count = input.skip_rows as usize * self.op_x_rows; + let initial_count = DmaInfo::get_loop_count(input.encoded) - skip_count; + let mut count64 = initial_count; + // println!( + // "DMA_64_ALIGNED INPUT {input:?} count:{count64} rows:{rows} dma_info:{}", + // DmaInfo::to_string(input.encoded) + // ); + let mut dst64 = ((input.dst + 7) >> 3) + skip_count as u32; + let mut seq_end = false; + let addr_incr_by_row = self.op_x_rows as u32; + + for (irow, row) in trace.iter_mut().enumerate().take(rows) { + row.set_main_step(input.step); + row.set_sel_memset(true); + row.set_fill_byte(input.fill_byte); + row.set_previous_seq_end(irow == 0 && input.skip_rows == 0); + + // calculate the first aligned address + // if dst is aligned is same address if not it's addr + 8 + row.set_dst64(dst64); + dst64 += addr_incr_by_row; + + row.set_count64(count64 as u32); + let use_count = if count64 <= self.op_x_rows { + seq_end = true; + count64 + } else { + count64 -= self.op_x_rows; + self.op_x_rows + }; + row.set_seq_end(seq_end); + for index in 0..use_count { + if index > 0 { + row.set_sel_op_from_1(index - 1, true); + } + } + } + + if input.is_last_instance_input { + if seq_end { + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count64 = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_flags = F::ZERO; + } else { + air_values.segment_last_seq_end = F::ZERO; + air_values.segment_last_dst64 = F::from_u32(dst64 - addr_incr_by_row); + air_values.segment_last_main_step = F::from_u64(input.step); + let last_count = initial_count - (rows - 1) * self.op_x_rows; + air_values.segment_last_count64 = F::from_u32(last_count as u32); + air_values.last_count_chunk[0] = F::from_u16(last_count as u16); + air_values.last_count_chunk[1] = F::from_u16((last_count >> 16) as u16); + air_values.segment_last_flags = F::from_u16(F_SEL_MEMSET as u16); + } + } + rows + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut Dma64AlignedMemSetTraceRow) { + trace.set_main_step(0); + trace.set_sel_memset(false); + trace.set_fill_byte(0); + trace.set_dst64(0); + trace.set_count64(0); + for index in 0..self.op_x_rows - 1 { + trace.set_sel_op_from_1(index, false); + } + trace.set_seq_end(true); + trace.set_previous_seq_end(true); + } +} +impl Dma64AlignedModule for Dma64AlignedMemSetSM { + fn get_name(&self) -> &'static str { + "dma_64_aligned_memset" + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + fn compute_witness( + &self, + inputs: &[Vec], + segment_id: SegmentId, + is_last_segment: bool, + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = Dma64AlignedMemSetTrace::::new_from_vec_zeroes(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs + .iter() + .map(|inputs| inputs.iter().map(|input| input.rows as usize).sum::()) + .sum(); + + assert!(total_inputs > 0); + // println!("LAST INPUT: {:?}", inputs.last().unwrap()); + // println!("DMA_64_ALIGNED TOTALS total_inputs:{total_inputs} num_rows:{num_rows}"); + assert!( + total_inputs <= num_rows, + "Too many inputs, total_inputs:{total_inputs} num_rows:{num_rows}" + ); + + tracing::debug!( + "··· Creating Dma64Aligned instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_64_ALIGNED_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + let mut local_16_bits_table = vec![0u32; 1 << 16]; + let mut air_values = Dma64AlignedMemSetAirValues::::new(); + + let mut row_offset = 0; + for input in flat_inputs.iter() { + let rows_used = self.process_input( + input, + &mut trace_rows[row_offset..], + &mut local_16_bits_table, + &mut air_values, + ); + row_offset += rows_used; + } + + // padding + if row_offset < num_rows { + air_values.padding_size = F::from_u32((num_rows - row_offset) as u32); + self.process_empty_slice(&mut trace_rows[row_offset]); + let empty_row = trace_rows[row_offset]; + trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + air_values.segment_last_seq_end = F::ONE; + air_values.segment_last_dst64 = F::ZERO; + air_values.segment_last_main_step = F::ZERO; + air_values.segment_last_count64 = F::ZERO; + air_values.last_count_chunk[0] = F::ZERO; + air_values.last_count_chunk[1] = F::ZERO; + air_values.segment_last_flags = F::ZERO; + } + + // add range check of count to check that it's a positive 32-bits number + let last_count = air_values.segment_last_count64.as_canonical_u64(); + local_16_bits_table[(last_count & 0xFFFF) as usize] += 1; + local_16_bits_table[((last_count >> 16) & 0xFFFF) as usize] += 1; + + self.std.range_checks(self.range_16_bits_id, local_16_bits_table); + + let segment_id = segment_id.into(); + air_values.segment_id = F::from_usize(segment_id); + air_values.is_last_segment = F::from_bool(is_last_segment); + + let first_input = flat_inputs.first().unwrap(); + if first_input.skip_rows == 0 { + air_values.segment_previous_seq_end = F::ONE; + air_values.segment_previous_dst64 = F::ZERO; + air_values.segment_previous_main_step = F::ZERO; + air_values.segment_previous_count64 = F::ZERO; + air_values.segment_previous_flags = F::ZERO; + } else { + assert!(segment_id > 0); + air_values.segment_previous_seq_end = F::ZERO; + air_values.segment_previous_dst64 = + F::from_u32(trace_rows[0].get_dst64() - self.op_x_rows as u32); + air_values.segment_previous_main_step = F::from_u64(trace_rows[0].get_main_step()); + air_values.segment_previous_count64 = + F::from_u32(trace_rows[0].get_count64() + self.op_x_rows as u32); + air_values.segment_previous_flags = F::from_u16(F_SEL_MEMSET as u16); + } + timer_stop_and_log_trace!(DMA_64_ALIGNED_TRACE); + let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_module.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_module.rs new file mode 100644 index 000000000..16364b9f9 --- /dev/null +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_module.rs @@ -0,0 +1,14 @@ +use crate::Dma64AlignedInput; +use proofman_common::{AirInstance, ProofmanResult}; +use zisk_common::SegmentId; + +pub trait Dma64AlignedModule: Send + Sync { + fn compute_witness( + &self, + inputs: &[Vec], + segment_id: SegmentId, + is_last_segment: bool, + trace_buffer: Vec, + ) -> ProofmanResult>; + fn get_name(&self) -> &'static str; +} diff --git a/precompiles/dma/src/dma_64_aligned/mod.rs b/precompiles/dma/src/dma_64_aligned/mod.rs index 9f48724d5..059ade510 100644 --- a/precompiles/dma/src/dma_64_aligned/mod.rs +++ b/precompiles/dma/src/dma_64_aligned/mod.rs @@ -2,9 +2,19 @@ mod dma_64_aligned; mod dma_64_aligned_collector; mod dma_64_aligned_input; +mod dma_64_aligned_inputcpy; mod dma_64_aligned_instance; +mod dma_64_aligned_mem; +mod dma_64_aligned_memcpy; +mod dma_64_aligned_memset; +mod dma_64_aligned_module; pub use dma_64_aligned::*; pub use dma_64_aligned_collector::*; pub use dma_64_aligned_input::*; +pub use dma_64_aligned_inputcpy::*; pub use dma_64_aligned_instance::*; +pub use dma_64_aligned_mem::*; +pub use dma_64_aligned_memcpy::*; +pub use dma_64_aligned_memset::*; +pub use dma_64_aligned_module::*; diff --git a/precompiles/dma/src/dma_bus_device.rs b/precompiles/dma/src/dma_bus_device.rs index 4194cd82e..257f22c1d 100644 --- a/precompiles/dma/src/dma_bus_device.rs +++ b/precompiles/dma/src/dma_bus_device.rs @@ -2,32 +2,117 @@ //! sent over the data bus. It connects to the bus and gathers metrics for specific //! `ZiskOperationType::Dma` instructions. +use std::fmt; +use std::ops::Add; + use precompiles_common::MemProcessor; use precompiles_helpers::DmaInfo; -use std::ops::Add; -use zisk_common::{BusDevice, BusDeviceMode, BusId, Metrics, B, OPERATION_BUS_ID, OP_TYPE}; -use zisk_common::{A, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; +use zisk_common::{BusDevice, BusDeviceMode, BusId, Metrics, OPERATION_BUS_ID, OP_TYPE, STEP}; +use zisk_common::{OP, OPERATION_PRECOMPILED_BUS_DATA_SIZE}; +use zisk_core::zisk_ops::ZiskOp; use zisk_core::ZiskOperationType; -use crate::{generate_dma_mem_inputs, skip_dma_mem_inputs, DMA_64_ALIGNED_OPS_BY_ROW}; +use crate::{generate_dma_mem_inputs, skip_dma_mem_inputs}; + +// The `DmaOpMultiCounter` struct represents a counter that monitors and measures +// dma specific operation on the data bus. +// +// Dma Full OnlyMemCpy OnlyInputCpy +// Dma64Aligned Full4 OnlyMemCpy8 OnlyInputCpy4 OnlyMemSet8 Mem4 +// DmaUnaligned Full +// DmaPrePost Full OnlyMemCpy OnlyInputCpy +// +// MEMCPY + XMEMCPY +// dma_memcpy | dma_pre_post_memcpy | dma_unaligned (unaligned_dst_src) +// dma_memcpy | dma_pre_post_memcpy | dma_64_aligned_memcpy (aligned_dst_src + lcount > 4) +// dma_memcpy | dma_pre_post_memcpy | dma_64_aligned_mem (aligned_dst_src + lcount <= 4) +// +// MEMCMP +// dma | dma_pre_post | dma_unaligned (unaligned_dst_src) +// dma | dma_pre_post | dma_64_aligned_mem (aligned_dst_src) +// +// INPUTCPY +// dma_inputcpy | dma_pre_post_inputcpy | dma_64_aligned_inputcpy +// +// XMEMSET +// dma | dma_pre_post | dma_64_aligned_memset +// +// With this config, for the memcpy the limit was 4 words of 64-bits, more than 4 it's +// better a OnlyMemCpy8 vs Mem4 +// +// DMA => 4 counters +// DMA_PRE_POST => 4 +// DMA_UNALIGNED = 4 +// DMA_UNALIGNED_INPUTS = 4 +// DMA_64_ALIGNED_ROWS = 6 +// DMA_64_ALIGNED_INPUTS = 4 -/// The `DmaCounter` struct represents a counter that monitors and measures -/// dma-related operations on the data bus. -/// -/// It tracks specific operation types (`ZiskOperationType`) and updates counters for each -/// accepted operation type whenever data is processed on the bus. +pub const DMA_OFFSET: usize = 0; +pub const DMA_PRE_POST_OFFSET: usize = 4; +pub const DMA_UNALIGNED_OFFSET: usize = 8; +pub const DMA_UNALIGNED_INPUTS_OFFSET: usize = 12; +pub const DMA_64_ALIGNED_OFFSET: usize = 16; +pub const DMA_64_ALIGNED_INPUTS_OFFSET: usize = 22; +pub const DMA_INPUT_GEN_COUNTERS: usize = 26; + +pub const DMA_COUNTER_MEMCPY: usize = 0; +pub const DMA_COUNTER_MEMSET: usize = 1; +pub const DMA_COUNTER_MEMCMP: usize = 2; +pub const DMA_COUNTER_INPUTCPY: usize = 3; +pub const DMA_COUNTER_MEMCPY_8: usize = 4; +pub const DMA_COUNTER_MEMSET_8: usize = 5; + +pub const DMA_COUNTER_OPS: usize = 4; +pub const DMA_COUNTER_OPS_EXT: usize = 6; #[derive(Debug)] pub struct DmaCounterInputGen { - /// sizes of memcpy - pub dma_pre_post_ops: usize, - pub dma_ops: usize, - pub dma_unaligned_inputs: usize, - pub dma_unaligned_rows: usize, - pub dma_64_aligned_inputs: usize, - pub dma_64_aligned_rows: usize, - - /// Bus device mode (counter or input generator). - pub mode: BusDeviceMode, + pub counters: [usize; DMA_INPUT_GEN_COUNTERS], + + mode: BusDeviceMode, +} + +impl fmt::Display for DmaCounterInputGen { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "ROWS:\n \ + memcpy4 memcpy8 memcmp inputcpy memset4 memset8\n \ + dma {:>8} {:>8} {:>8} {:>8} \n \ + dma_pre_post {:>8} {:>8} {:>8} {:>8} \n \ + dma_64_aligned {:>8} {:>8} {:>8} {:>8} {:>8} {:>8}\n \ + dma_unaligned {:>8} {:>8} {:>8} {:>8} \n\n \ + INPUTS\n \ + memcpy4 memcpy8 memcmp inputcpy memset4 memset8\n \ + dma_64_aligned {:>8} {:>8} {:>8} {:>8} \n \ + dma_unaligned {:>8} {:>8} {:>8} {:>8} \n\n", + self.counters[DMA_OFFSET + DMA_COUNTER_MEMCPY], + self.counters[DMA_OFFSET + DMA_COUNTER_MEMCMP], + self.counters[DMA_OFFSET + DMA_COUNTER_INPUTCPY], + self.counters[DMA_OFFSET + DMA_COUNTER_MEMSET], + self.counters[DMA_PRE_POST_OFFSET + DMA_COUNTER_MEMCPY], + self.counters[DMA_PRE_POST_OFFSET + DMA_COUNTER_MEMCMP], + self.counters[DMA_PRE_POST_OFFSET + DMA_COUNTER_INPUTCPY], + self.counters[DMA_PRE_POST_OFFSET + DMA_COUNTER_MEMSET], + self.counters[DMA_64_ALIGNED_OFFSET + DMA_COUNTER_MEMCPY], + self.counters[DMA_64_ALIGNED_OFFSET + DMA_COUNTER_MEMCPY_8], + self.counters[DMA_64_ALIGNED_OFFSET + DMA_COUNTER_MEMCMP], + self.counters[DMA_64_ALIGNED_OFFSET + DMA_COUNTER_INPUTCPY], + self.counters[DMA_64_ALIGNED_OFFSET + DMA_COUNTER_MEMSET], + self.counters[DMA_64_ALIGNED_OFFSET + DMA_COUNTER_MEMSET_8], + self.counters[DMA_UNALIGNED_OFFSET + DMA_COUNTER_MEMCPY], + self.counters[DMA_UNALIGNED_OFFSET + DMA_COUNTER_MEMCMP], + self.counters[DMA_UNALIGNED_OFFSET + DMA_COUNTER_INPUTCPY], + self.counters[DMA_UNALIGNED_OFFSET + DMA_COUNTER_MEMSET], + self.counters[DMA_64_ALIGNED_INPUTS_OFFSET + DMA_COUNTER_MEMCPY], + self.counters[DMA_64_ALIGNED_INPUTS_OFFSET + DMA_COUNTER_MEMCMP], + self.counters[DMA_64_ALIGNED_INPUTS_OFFSET + DMA_COUNTER_INPUTCPY], + self.counters[DMA_64_ALIGNED_INPUTS_OFFSET + DMA_COUNTER_MEMSET], + self.counters[DMA_UNALIGNED_INPUTS_OFFSET + DMA_COUNTER_MEMCPY], + self.counters[DMA_UNALIGNED_INPUTS_OFFSET + DMA_COUNTER_MEMCMP], + self.counters[DMA_UNALIGNED_INPUTS_OFFSET + DMA_COUNTER_INPUTCPY], + self.counters[DMA_UNALIGNED_INPUTS_OFFSET + DMA_COUNTER_MEMSET], + ) + } } impl DmaCounterInputGen { @@ -39,14 +124,51 @@ impl DmaCounterInputGen { /// # Returns /// A new `DmaCounter` instance. pub fn new(mode: BusDeviceMode) -> Self { - Self { - dma_pre_post_ops: 0, - dma_ops: 0, - dma_unaligned_inputs: 0, - dma_64_aligned_inputs: 0, - dma_unaligned_rows: 0, - dma_64_aligned_rows: 0, - mode, + Self { counters: [0; DMA_INPUT_GEN_COUNTERS], mode } + } + const OPS_X_ROW: [usize; 6] = [ + 4, // MEMCPY_4 + 4, // MEMSET_4 + 4, // MEMCMP + 4, // INPUTCPY + 8, // MEMCPY_8 + 8, // MEMSET_8 + ]; + const IS_DOUBLE: [usize; 6] = [ + 1, // MEMCPY_4 + 1, // MEMSET_4 + 0, // MEMCMP + 0, // INPUTCPY + 0, // MEMCPY_8 + 0, // MEMSET_8 + ]; + + fn incr_counters(&mut self, encoded: u64, operation: usize, _step: u64) { + if !DmaInfo::is_direct(encoded) { + if DmaInfo::get_pre_count(encoded) > 0 { + self.counters[DMA_PRE_POST_OFFSET + operation] += 1; + } + if DmaInfo::get_post_count(encoded) > 0 { + self.counters[DMA_PRE_POST_OFFSET + operation] += 1; + } + self.counters[DMA_OFFSET + operation] += 1; + } + let loop_count = DmaInfo::get_loop_count(encoded); + // it's effective loop count + let use_src = operation != DMA_COUNTER_MEMSET && operation != DMA_COUNTER_INPUTCPY; + if loop_count > 0 { + if DmaInfo::dst_is_aligned_with_src(encoded) || !use_src { + let rows = loop_count.div_ceil(Self::OPS_X_ROW[operation]); + self.counters[DMA_64_ALIGNED_OFFSET + operation] += rows; + self.counters[DMA_64_ALIGNED_INPUTS_OFFSET + operation] += 1; + if Self::IS_DOUBLE[operation] == 1 { + let rows = loop_count.div_ceil(Self::OPS_X_ROW[operation + 4]); + self.counters[DMA_64_ALIGNED_OFFSET + operation + 4] += rows; + } + } else { + self.counters[DMA_UNALIGNED_OFFSET + operation] += loop_count + 1; + self.counters[DMA_UNALIGNED_INPUTS_OFFSET + operation] += 1; + } } } @@ -56,41 +178,23 @@ impl DmaCounterInputGen { /// * `dst` - The destination address of operation. /// * `src` - The source address of operation. /// * `count` - The bytes of operation. - pub fn inst_count_memcpy(&mut self, dst: u64, src: u64, count: usize) { - let dst_offset = dst & 0x07; - let src_offset = src & 0x07; - - // offset => max bytes is 8 - offset - if count > 0 { - let remaining = if dst_offset > 0 { - self.dma_pre_post_ops += 1; - count - std::cmp::min(8 - dst_offset as usize, count) - } else { - count - }; - - if (remaining % 8) > 0 { - // adding a post because last write isn't full (8-bytes) - self.dma_pre_post_ops += 1; + pub fn inst_count(&mut self, encoded: u64, op: u8, step: u64) { + // count and plan no need the count, need the effective count: + // effective_count = if is_equal { count } else { count_eq + 1 } + // the count encoded was effective + match op { + ZiskOp::DMA_MEMCPY | ZiskOp::DMA_XMEMCPY => { + // if DmaInfo::dst_is_aligned_with_src(encoded) { + self.incr_counters(encoded, DMA_COUNTER_MEMCPY, step); + // } } - if dst_offset == src_offset { - // println!( - // "count: {count} remaining: {remaining} self.dma_64_aligned_ops: {}", - // self.dma_64_aligned_rows - // ); - let rows = (remaining >> 3).div_ceil(DMA_64_ALIGNED_OPS_BY_ROW); - self.dma_64_aligned_rows += rows; - self.dma_64_aligned_inputs += 1; - } else if remaining > 7 { - // check remaining because unaligned add an extra row for each unaligned, means - // if remaming >> 3 is 0, add extra row. - // on unalignmed_ops, each dst write use its src read and next src read also. - // the last src read don't have write. - self.dma_unaligned_rows += (remaining >> 3) + 1; - self.dma_unaligned_inputs += 1; + ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => { + self.incr_counters(encoded, DMA_COUNTER_MEMCMP, step) } + ZiskOp::DMA_INPUTCPY => self.incr_counters(encoded, DMA_COUNTER_INPUTCPY, step), + ZiskOp::DMA_XMEMSET => self.incr_counters(encoded, DMA_COUNTER_MEMSET, step), + _ => panic!("Unknown DMA Cmd 0x{op:02X}"), } - self.dma_ops += 1; } /// Processes data received on the bus, updating counters and generating inputs when applicable. @@ -148,10 +252,12 @@ impl Metrics for DmaCounterInputGen { /// An empty vector, as this implementation does not produce any derived inputs for the bus. #[inline(always)] fn measure(&mut self, data: &[u64]) { - let dst = data[A]; - let src = data[B]; - let count = DmaInfo::get_count(data[OPERATION_PRECOMPILED_BUS_DATA_SIZE]); - self.inst_count_memcpy(dst, src, count); + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + return; + } + let op = data[OP] as u8; + let encoded = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE]; + self.inst_count(encoded, op, data[STEP]); } /// Provides a dynamic reference for downcasting purposes. @@ -176,17 +282,30 @@ impl Add for DmaCounterInputGen { /// A new `DmaCounter` with combined counters. fn add(self, other: Self) -> DmaCounterInputGen { DmaCounterInputGen { - dma_pre_post_ops: self.dma_pre_post_ops + other.dma_pre_post_ops, - dma_ops: self.dma_ops + other.dma_ops, - dma_unaligned_inputs: self.dma_unaligned_inputs + other.dma_unaligned_inputs, - dma_64_aligned_inputs: self.dma_64_aligned_inputs + other.dma_64_aligned_inputs, - dma_unaligned_rows: self.dma_unaligned_rows + other.dma_unaligned_rows, - dma_64_aligned_rows: self.dma_64_aligned_rows + other.dma_64_aligned_rows, - mode: self.mode, + counters: std::array::from_fn(|i| self.counters[i] + other.counters[i]), + mode: self.mode.clone(), } } } +impl Add<&DmaCounterInputGen> for &DmaCounterInputGen { + type Output = DmaCounterInputGen; + + /// Combines two `DmaCounter` references by summing their counters. + /// + /// # Arguments + /// * `self` - Reference to the first `DmaCounter` instance. + /// * `other` - Reference to the second `DmaCounter` instance. + /// + /// # Returns + /// A new `DmaCounter` with combined counters. + fn add(self, other: &DmaCounterInputGen) -> DmaCounterInputGen { + DmaCounterInputGen { + counters: std::array::from_fn(|i| self.counters[i] + other.counters[i]), + mode: self.mode.clone(), + } + } +} impl BusDevice for DmaCounterInputGen { /// Provides a dynamic reference for downcasting purposes. fn as_any(self: Box) -> Box { diff --git a/precompiles/dma/src/dma_checkpoint.rs b/precompiles/dma/src/dma_checkpoint.rs new file mode 100644 index 000000000..94bb76142 --- /dev/null +++ b/precompiles/dma/src/dma_checkpoint.rs @@ -0,0 +1,30 @@ +use std::collections::HashMap; + +use zisk_common::ChunkId; + +use crate::DmaCollectCounters; + +#[derive(Default, Debug)] +pub struct DmaCheckPoint { + pub chunks: HashMap, + pub last_chunk: Option, + pub is_last_segment: bool, +} + +impl DmaCheckPoint { + #[cfg(feature = "save_dma_collectors")] + pub fn get_debug_info(&self, title: &str, segment_id: u64) -> String { + self.chunks + .iter() + .map(|(chunk_id, (num_inputs, collect_counters))| { + format!( + "{title} #{segment_id}@{chunk_id} [{num_inputs}|{}]{}{}", + collect_counters.get_debug_info(), + if Some(*chunk_id) == self.last_chunk { " [last_chunk]" } else { "" }, + if self.is_last_segment { " [last_segment]" } else { "" }, + ) + }) + .collect::>() + .join("\n") + } +} diff --git a/precompiles/dma/src/dma_collect_counters.rs b/precompiles/dma/src/dma_collect_counters.rs new file mode 100644 index 000000000..fc57abd22 --- /dev/null +++ b/precompiles/dma/src/dma_collect_counters.rs @@ -0,0 +1,89 @@ +use zisk_common::CollectCounter; +use zisk_core::zisk_ops::ZiskOp; + +#[derive(Debug, Clone, Copy)] +pub struct DmaCollectCounters { + // This counters are for a specific instance, means that only need to know of collect + // for each different operation, no for destination + pub memcpy: CollectCounter, + pub inputcpy: CollectCounter, + pub memset: CollectCounter, + pub memcmp: CollectCounter, +} + +impl DmaCollectCounters { + pub fn debug_assert_is_final_skip(&self) { + debug_assert!( + self.is_final_skip(), + "pending to collect => memcpy: {}/{}|inputcpy: {}/{}|memset: {}/{}|memcmp: {}/{}", + self.memcpy.collected, + self.memcpy.collect_count, + self.inputcpy.collected, + self.inputcpy.collect_count, + self.memset.collected, + self.memset.collect_count, + self.memcmp.collected, + self.memcmp.collect_count + ); + } + pub fn is_final_skip(&self) -> bool { + self.memcpy.is_final_skip() + && self.inputcpy.is_final_skip() + && self.memset.is_final_skip() + && self.memcmp.is_final_skip() + } + pub fn should_collect(&mut self, rows: u64, op: u8) -> Option<(u32, u32, bool)> { + let cc = match op { + ZiskOp::DMA_MEMCPY | ZiskOp::DMA_XMEMCPY => &mut self.memcpy, + ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => &mut self.memcmp, + ZiskOp::DMA_INPUTCPY => &mut self.inputcpy, + ZiskOp::DMA_XMEMSET => &mut self.memset, + _ => panic!("Invalid operation 0x{op:02X} for DmaCollectCounters"), + }; + + if let Some((skip, max_count)) = cc.should_process(rows as u32) { + Some((skip, max_count, cc.is_final_skip())) + } else { + None + } + } + #[inline(always)] + pub fn should_collect_single_row(&mut self, op: u8) -> bool { + self.should_collect(1, op).is_some() + } + + #[cfg(feature = "save_dma_collectors")] + pub fn get_full_debug_info(&self) -> String { + format!( + "CY:{}/{}|IC:{}/{}|MS:{}/{}|MC:{}/{}", + self.memcpy.collected, + self.memcpy.collect_count, + self.inputcpy.collected, + self.inputcpy.collect_count, + self.memset.collected, + self.memset.collect_count, + self.memcmp.collected, + self.memcmp.collect_count, + ) + } + #[cfg(feature = "save_dma_collectors")] + pub fn get_debug_info(&self) -> String { + (if self.memcpy.initial_skip == 0 { + format!("CY:{}|", self.memcpy.collect_count) + } else { + format!("CY:({}){}|", self.memcpy.collect_count, self.memcpy.initial_skip) + }) + &(if self.inputcpy.initial_skip == 0 { + format!("IC:{}|", self.inputcpy.collect_count) + } else { + format!("IC:({}){}|", self.inputcpy.collect_count, self.inputcpy.initial_skip) + }) + &(if self.memset.initial_skip == 0 { + format!("MS:{}|", self.memset.collect_count) + } else { + format!("MS:({}){}|", self.memset.collect_count, self.memset.initial_skip) + }) + &(if self.memcmp.initial_skip == 0 { + format!("MC:{}", self.memcmp.collect_count) + } else { + format!("MC:({}){}", self.memcmp.collect_count, self.memcmp.initial_skip) + }) + } +} diff --git a/precompiles/dma/src/dma_collector_routing_log.rs b/precompiles/dma/src/dma_collector_routing_log.rs new file mode 100644 index 000000000..ae96e7024 --- /dev/null +++ b/precompiles/dma/src/dma_collector_routing_log.rs @@ -0,0 +1,74 @@ +use zisk_common::ChunkId; + +#[cfg(feature = "save_dma_collectors")] +use zisk_common::STEP; + +#[cfg(feature = "save_dma_collectors")] +#[derive(Debug)] +pub struct DmaCollectorRoutingLog { + pub chunk_id: ChunkId, + pub log: Vec<(u8, u64, usize)>, +} + +#[cfg(not(feature = "save_dma_collectors"))] +#[derive(Debug)] +pub struct DmaCollectorRoutingLog {} + +#[cfg(not(feature = "save_dma_collectors"))] +impl DmaCollectorRoutingLog { + pub fn new(_chunk_id: ChunkId) -> Self { + Self {} + } + #[inline(always)] + pub fn log_collect(&mut self, _rows: usize, _data: &[u64]) {} + #[inline(always)] + pub fn log_discard(&mut self, _reason: u8, _data: &[u64]) {} + #[inline(always)] + pub fn log_discard_cond( + &mut self, + cond: bool, + _reason: u8, + _data: &[u64], + _result: bool, + ) -> bool { + cond + } +} + +#[cfg(feature = "save_dma_collectors")] +impl DmaCollectorRoutingLog { + pub fn new(chunk_id: ChunkId) -> Self { + Self { chunk_id, log: Vec::new() } + } + + pub fn get_debug_info(&self) -> String { + self.log + .iter() + .map(|(reason, step, rows)| { + format!( + "{}|{reason}|@{}|C:{rows}|S:{step}", + if *reason == 0 { "COLLECT" } else { "SKIP" }, + self.chunk_id + ) + }) + .collect::>() + .join("\n") + + "\n" + } + + #[inline(always)] + pub fn log_discard(&mut self, reason: u8, data: &[u64]) { + self.log.push((reason, data[zisk_common::STEP], 0)); + } + + #[inline(always)] + pub fn log_collect(&mut self, rows: usize, data: &[u64]) { + self.log.push((0, data[zisk_common::STEP], rows)); + } + + #[inline(always)] + pub fn log_discard_cond(&mut self, cond: bool, reason: u8, data: &[u64], result: bool) -> bool { + self.log.push((reason + cond as u8, data[STEP], 0)); + result + } +} diff --git a/precompiles/dma/src/dma_common.rs b/precompiles/dma/src/dma_common.rs new file mode 100644 index 000000000..79909828f --- /dev/null +++ b/precompiles/dma/src/dma_common.rs @@ -0,0 +1,24 @@ +use fields::PrimeField64; +use zisk_pil::{ + Dma64AlignedInputCpyTrace, Dma64AlignedMemCpyTrace, Dma64AlignedMemSetTrace, + Dma64AlignedMemTrace, Dma64AlignedTrace, DmaInputCpyTrace, DmaMemCpyTrace, + DmaPrePostInputCpyTrace, DmaPrePostMemCpyTrace, DmaPrePostTrace, DmaTrace, DmaUnalignedTrace, +}; + +pub fn get_dma_air_name(air_id: usize) -> &'static str { + match air_id { + DmaTrace::::AIR_ID => "Dma", + DmaMemCpyTrace::::AIR_ID => "DmaMemCpy", + DmaInputCpyTrace::::AIR_ID => "DmaInputCpy", + DmaPrePostTrace::::AIR_ID => "DmaPrePost", + DmaPrePostMemCpyTrace::::AIR_ID => "DmaPrePostMemCpy", + DmaPrePostInputCpyTrace::::AIR_ID => "DmaPrePostInputCpy", + Dma64AlignedTrace::::AIR_ID => "Dma64Aligned", + Dma64AlignedMemSetTrace::::AIR_ID => "Dma64AlignedMemSet", + Dma64AlignedMemCpyTrace::::AIR_ID => "Dma64AlignedMemCpy", + Dma64AlignedInputCpyTrace::::AIR_ID => "Dma64AlignedInputCpy", + Dma64AlignedMemTrace::::AIR_ID => "Dma64AlignedMem", + DmaUnalignedTrace::::AIR_ID => "DmaUnaligned", + _ => "Unknown", + } +} diff --git a/precompiles/dma/src/dma_constants.rs b/precompiles/dma/src/dma_constants.rs index d85d443a3..049829dc8 100644 --- a/precompiles/dma/src/dma_constants.rs +++ b/precompiles/dma/src/dma_constants.rs @@ -11,3 +11,9 @@ pub const START_WRITE_PARAMS: usize = START_READ_PARAMS + READ_PARAMS * PARAM_CHUNKS + RESULT_PARAMS; pub const WRITE_ADDR_PARAM: usize = READ_PARAMS + DIRECT_READ_PARAMS; pub const DMA_64_ALIGNED_OPS_BY_ROW: usize = 4; +pub const DMA_64_ALIGNED_INPUTCPY_OPS_BY_ROW: usize = 4; +pub const DMA_64_ALIGNED_MEMCPY_OPS_BY_ROW: usize = 8; +pub const DMA_64_ALIGNED_MEMSET_OPS_BY_ROW: usize = 8; +pub const DMA_64_ALIGNED_MEM_OPS_BY_ROW: usize = 4; +pub const DMA_ROM_WITHOUT_MEMCMP_SIZE: usize = 1 << 15; +pub const DMA_ROM_WITH_MEMCMP_SIZE: usize = 3 * (1 << 15); diff --git a/precompiles/dma/src/dma_gen_inputcpy_mem_inputs.rs b/precompiles/dma/src/dma_gen_inputcpy_mem_inputs.rs new file mode 100644 index 000000000..9fcc42315 --- /dev/null +++ b/precompiles/dma/src/dma_gen_inputcpy_mem_inputs.rs @@ -0,0 +1,101 @@ +use precompiles_common::MemBusHelpers; +use precompiles_common::MemProcessor; +use precompiles_helpers::DmaInfo; +use zisk_common::{A, DMA_ENCODED, STEP}; + +pub fn generate_dma_inputcpy_mem_inputs( + data: &[u64], + data_ext: &[u64], + mem_processors: &mut P, +) { + // inputcpy has same offset that dst, but when prepare data add zero-bytes before the input + // to emulate aligned operation. + + let dst = data[A]; + let dst_offset = dst & 0x07; + let encoded = data[DMA_ENCODED]; + let dst64 = (dst & !0x07) as u32; + let main_step = data[STEP]; + let pre_count = DmaInfo::get_pre_count(encoded) as u64; + + // NOTE: for dual memories it's very important to keep the order of loads and stores because + // stores happend after loads. + + let pre_value = if pre_count > 0 { + let pre_data_offset = DmaInfo::get_pre_data_offset(encoded); + + // pre-load of write address before unaligned write + let value_before_write = data_ext[DmaInfo::get_pre_write_offset(encoded)]; + MemBusHelpers::mem_aligned_read(dst64, main_step, value_before_write, mem_processors); + + // if src and dst have the same offset, no double read + // TBO: calculate_write_value with same offset + + let dst_offset_bits = dst_offset * 8; + let mask = 0xFFFF_FFFF_FFFF_FFFF << dst_offset_bits; + Some((value_before_write & !mask) | (data_ext[pre_data_offset] & mask)) + } else { + None + }; + + let post_count = DmaInfo::get_post_count(encoded) as u64; + let loop_count = DmaInfo::get_loop_count(encoded); + + let loop_values = if loop_count > 0 { + let loop_data_offset = DmaInfo::get_loop_data_offset(encoded); + let loop_data_count = DmaInfo::get_loop_count(encoded); + let loop_data_end = loop_data_offset + loop_data_count; + + Some(&data_ext[loop_data_offset..loop_data_end]) + } else { + None + }; + + let post_value = if post_count > 0 { + let post_data_offset = DmaInfo::get_post_data_offset(encoded); + let dst64 = dst as u32 + pre_count as u32 + loop_count as u32 * 8; + + // pre-load of write address before unaligned write + let value_before_write = data_ext[DmaInfo::get_post_write_offset(encoded)]; + MemBusHelpers::mem_aligned_read(dst64, main_step, value_before_write, mem_processors); + + let post_bits = post_count * 8; + let mask = 0xFFFF_FFFF_FFFF_FFFF << post_bits; + Some((value_before_write & mask) | (data_ext[post_data_offset] & !mask)) + } else { + None + }; + + // Before writes, all reads should be done, to avoid issues with dual memory + + if let Some(pre_value) = pre_value { + MemBusHelpers::mem_aligned_write(dst64, main_step, pre_value, mem_processors); + } + if let Some(loop_values) = loop_values { + let dst64 = (dst as u32 + pre_count as u32) & !0x07; + MemBusHelpers::mem_aligned_write_from_slice(dst64, main_step, loop_values, mem_processors); + } + if let Some(post_value) = post_value { + let dst64 = dst as u32 + pre_count as u32 + loop_count as u32 * 8; + MemBusHelpers::mem_aligned_write(dst64, main_step, post_value, mem_processors); + } +} + +pub fn skip_dma_inputcpy_mem_inputs(data: &[u64], mem_processors: &mut P) -> bool { + let dst = data[A]; + + let count = DmaInfo::get_count(data[DMA_ENCODED]) as u64; + // calculate range for dst and src to verify if any of them are included + // in the memcollector addresses. + + let dst64_from = dst as u32 & !0x07; + let dst64_to = (dst + count + 7) as u32 & !0x07; + + if !mem_processors.skip_addr_range(dst64_from, dst64_to) { + return false; + } + + // If any mem_collector includes this addresses we could skip this precompiles + // at mem input data generation. + true +} diff --git a/precompiles/dma/src/dma_gen_mem_inputs.rs b/precompiles/dma/src/dma_gen_mem_inputs.rs index 6bbc87d1c..ee8b16f3c 100644 --- a/precompiles/dma/src/dma_gen_mem_inputs.rs +++ b/precompiles/dma/src/dma_gen_mem_inputs.rs @@ -1,8 +1,15 @@ -use precompiles_common::MemBusHelpers; use precompiles_common::MemProcessor; -use precompiles_helpers::{DmaHelpers, DmaInfo}; -use zisk_common::{A, B, DMA_ENCODED, OP, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP}; -use zisk_core::{zisk_ops::ZiskOp, EXTRA_PARAMS_ADDR}; +use zisk_common::OP; +use zisk_core::zisk_ops::ZiskOp; + +use crate::generate_dma_inputcpy_mem_inputs; +use crate::generate_dma_memcmp_mem_inputs; +use crate::generate_dma_memcpy_mem_inputs; +use crate::generate_dma_memset_mem_inputs; +use crate::skip_dma_inputcpy_mem_inputs; +use crate::skip_dma_memcmp_mem_inputs; +use crate::skip_dma_memcpy_mem_inputs; +use crate::skip_dma_memset_mem_inputs; pub fn generate_dma_mem_inputs( data: &[u64], @@ -10,172 +17,16 @@ pub fn generate_dma_mem_inputs( _only_counters: bool, mem_processors: &mut P, ) { - let dst = data[A]; - let src = data[B]; - let encoded = data[DMA_ENCODED]; - - let dst64 = (dst & !0x07) as u32; - let src64 = (src & !0x07) as u32; - let main_step = data[STEP]; - let pre_count = DmaInfo::get_pre_count(encoded) as u64; - let dst_offset = dst & 0x07; - let src_offset = src & 0x07; - let aligned = dst_offset == src_offset; - - // NOTE: for dual memories it's very important to keep the order of loads and stores because - // stores happend after loads. - - MemBusHelpers::mem_aligned_load( - EXTRA_PARAMS_ADDR as u32, - main_step, - DmaInfo::get_count(encoded) as u64, - mem_processors, - ); - - if pre_count > 0 { - let pre_data_offset = DmaInfo::get_pre_data_offset(encoded); - let read_value = data_ext[pre_data_offset]; - - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_load@pre 0x{src64:08X} S:{main_step} V:{read_value} (0x{read_value:016X})"); - - MemBusHelpers::mem_aligned_load(src64, main_step, read_value, mem_processors); - - // pre-load of write address before unaligned write - let pre_value = data_ext[DmaInfo::get_pre_write_offset(encoded)]; - - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_load@pre-p 0x{dst64:08X} S:{main_step} V:{pre_value} (0x{pre_value:016X})"); - - MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, mem_processors); - - let write_value = if DmaInfo::is_double_read_pre(encoded) { - let second_read_value = data_ext[pre_data_offset + 1]; - #[cfg(feature = "debug_dma")] - println!( - "DMA: mem_aligned_load@pre2 0x{:08X} S:{main_step} V:{second_read_value} (0x{second_read_value:016X})", - src64 + 8 - ); - MemBusHelpers::mem_aligned_load( - src64 + 8, - main_step, - second_read_value, - mem_processors, - ); - DmaHelpers::calculate_write_value( - dst_offset, - src_offset, - pre_count, - pre_value, - &[read_value, second_read_value], - ) - } else { - DmaHelpers::calculate_write_value( - dst_offset, - src_offset, - pre_count, - pre_value, - &[read_value], - ) - }; - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_write@pre 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); - - MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, mem_processors); - } - - // this is part of words loop - let post_count = DmaInfo::get_post_count(encoded) as u64; - let loop_count = DmaInfo::get_loop_count(encoded); - if loop_count > 0 { - let loop_src = src as u32 + pre_count as u32; - let dst64 = (dst as u32 + pre_count as u32) & !0x07; - let src64 = loop_src & !0x07; - let loop_data_offset = DmaInfo::get_loop_data_offset(encoded); - let loop_data_count = DmaInfo::get_loop_count(encoded); - let loop_src_data_end = - loop_data_offset + loop_data_count + ((loop_src & 0x07) > 0) as usize; - if data_ext.len() <= loop_data_offset || data_ext.len() < loop_src_data_end { - println!("PRE-CRASH data_ext[{loop_data_offset}..{loop_src_data_end}] data_ext.len() = {} DATA={data:?} INFO{}", data_ext.len(), DmaInfo::to_string(encoded)); + match data[OP] as u8 { + ZiskOp::DMA_INPUTCPY => generate_dma_inputcpy_mem_inputs(data, data_ext, mem_processors), + ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => { + generate_dma_memcmp_mem_inputs(data, data_ext, mem_processors) } - let values = &data_ext[loop_data_offset..loop_src_data_end]; - - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_load_from_slice 0x{src64:08X} S:{main_step} V:{values:?}"); - - MemBusHelpers::mem_aligned_load_from_slice(src64, main_step, values, mem_processors); - - let src_offset = (src_offset + pre_count) & 0x07; - if aligned { - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_write_from_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); - MemBusHelpers::mem_aligned_write_from_slice(dst64, main_step, values, mem_processors); - } else { - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_write_from_read_unaligned_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); - MemBusHelpers::mem_aligned_write_from_read_unaligned_slice( - dst64, - main_step, - src_offset as u8, - values, - mem_processors, - ); + ZiskOp::DMA_XMEMSET => generate_dma_memset_mem_inputs(data, data_ext, mem_processors), + ZiskOp::DMA_MEMCPY | ZiskOp::DMA_XMEMCPY => { + generate_dma_memcpy_mem_inputs(data, data_ext, mem_processors) } - } - if post_count > 0 { - let src_offset = src & 0x07; - - let post_data_offset = DmaInfo::get_post_data_offset(encoded); - let src64 = (src as u32 + pre_count as u32 + loop_count as u32 * 8) & !0x07; - let dst64 = dst as u32 + pre_count as u32 + loop_count as u32 * 8; - let read_value = data_ext[post_data_offset]; - - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_load@post 0x{src64:08X} S:{main_step} V:{read_value} (0x{read_value:016X})"); - - MemBusHelpers::mem_aligned_load(src64, main_step, read_value, mem_processors); - - // pre-load of write address before unaligned write - let pre_value = data_ext[DmaInfo::get_post_write_offset(encoded)]; - - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_load@post-p 0x{dst64:08X} S:{main_step} V:{pre_value} (0x{pre_value:016X})"); - - MemBusHelpers::mem_aligned_load(dst64, main_step, pre_value, mem_processors); - - let write_value = if DmaInfo::is_double_read_post(encoded) { - let second_read_value = data_ext[post_data_offset + 1]; - #[cfg(feature = "debug_dma")] - println!( - "DMA: mem_aligned_load@post2 0x{:08X} S:{main_step} V:{second_read_value} (0x{second_read_value:016X})", - src64 + 8 - ); - MemBusHelpers::mem_aligned_load( - src64 + 8, - main_step, - second_read_value, - mem_processors, - ); - DmaHelpers::calculate_write_value( - 0, // in post offset it's 0 - (src_offset + pre_count) & 0x07, // src_offset it's modified by pre, aligned/unaligned no change offset - post_count, - pre_value, - &[read_value, second_read_value], - ) - } else { - DmaHelpers::calculate_write_value( - 0, // in post offset it's 0 - (src_offset + pre_count) & 0x07, // src_offset it's modified by pre, aligned/unaligned no change offset - post_count, - pre_value, - &[read_value], - ) - }; - - #[cfg(feature = "debug_dma")] - println!("DMA: mem_aligned_write@post 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); - MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, mem_processors); + _ => panic!("Invalid op 0x{:02X}", data[OP]), } } @@ -184,43 +35,15 @@ pub fn skip_dma_mem_inputs( _data_ext: &[u64], mem_processors: &mut P, ) -> bool { - let dst = data[A]; - let src = data[B]; - let op = data[OP] as u8; - - // A memcmp operation has two parts, any of them could be empty. - // - equal part, means that bytes of src and dst are the same (count_eq) - // - different part, at maximum one byte, to obtain difference (count - count_eq) - let count = DmaInfo::get_count(data[DMA_ENCODED]) as u64; - let use_count = match op { - ZiskOp::DMA_MEMCPY => count, - ZiskOp::DMA_MEMCMP => std::cmp::min( - count, - data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + 1] + 1, // count_eq + 1 (different byte) - ), - _ => panic!("Invalid operation inside skip_dma_mem_inputs (op:{op})"), - }; - // calculate range for dst and src to verify if any of them are included - // in the memcollector addresses. - - let dst64_from = dst as u32 & !0x07; - let src64_from = src as u32 & !0x07; - let dst64_to = (dst + use_count + 7) as u32 & !0x07; - let src64_to = (src + use_count + 7) as u32 & !0x07; - - if !mem_processors.skip_addr(EXTRA_PARAMS_ADDR as u32) { - return false; - } - - if !mem_processors.skip_addr_range(dst64_from, dst64_to) { - return false; - } - - if !mem_processors.skip_addr_range(src64_from, src64_to) { - return false; + match data[OP] as u8 { + ZiskOp::DMA_INPUTCPY => skip_dma_inputcpy_mem_inputs(data, mem_processors), + ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => { + skip_dma_memcmp_mem_inputs(data, mem_processors) + } + ZiskOp::DMA_XMEMSET => skip_dma_memset_mem_inputs(data, mem_processors), + ZiskOp::DMA_MEMCPY | ZiskOp::DMA_XMEMCPY => { + skip_dma_memcpy_mem_inputs(data, mem_processors) + } + _ => panic!("Invalid op 0x{:02X}", data[OP]), } - - // If any mem_collector includes this addresses we could skip this precompiles - // at mem input data generation. - true } diff --git a/precompiles/dma/src/dma_gen_memcmp_mem_inputs.rs b/precompiles/dma/src/dma_gen_memcmp_mem_inputs.rs new file mode 100644 index 000000000..afa8a557f --- /dev/null +++ b/precompiles/dma/src/dma_gen_memcmp_mem_inputs.rs @@ -0,0 +1,166 @@ +use precompiles_common::MemBusHelpers; +use precompiles_common::MemProcessor; +use precompiles_helpers::DmaInfo; +use zisk_common::{A, B, DMA_ENCODED, OP, STEP}; +use zisk_core::{zisk_ops::ZiskOp, EXTRA_PARAMS_ADDR}; + +pub fn generate_dma_memcmp_mem_inputs( + data: &[u64], + data_ext: &[u64], + mem_processors: &mut P, +) { + // encoding of count was done with effective count, means that if dst and src are equals, + // effective_count = count while if dst and src are different effective_count = count_eq + 1 + // count_eq is the number of beggining bytes equal between src and dst + + let dst = data[A]; + let src = data[B]; + let encoded = data[DMA_ENCODED]; + let op = data[OP] as u8; + let load_count_from_mem = op == ZiskOp::DMA_MEMCMP; + let dst64 = (dst & !0x07) as u32; + let src64 = (src & !0x07) as u32; + let step = data[STEP]; + let pre_count = DmaInfo::get_pre_count(encoded) as u64; + let dst_offset = dst & 0x07; + let src_offset = src & 0x07; + let unaligned = dst_offset != src_offset; + let count = DmaInfo::get_count(encoded) as u64; + + let src_words = ((src_offset + count + 7) >> 3) as usize; + let dst_words = ((dst_offset + count + 7) >> 3) as usize; + debug_assert_eq!( + src_words + dst_words, + data_ext.len(), + "[dma_memcmp] data length mismatch, expected {} but got {} DATA:[{}] INFO={}", + src_words + dst_words, + data_ext.len(), + data.iter().map(|v| format!("0x{v:016X}")).collect::>().join(", "), + DmaInfo::to_string(encoded) + ); + let dst_data = &data_ext[0..dst_words]; + let src_data = &data_ext[dst_words..dst_words + src_words]; + + if load_count_from_mem { + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memcmp] INPUT PARAM 0x{EXTRA_PARAMS_ADDR:08X} S:{step}"); + MemBusHelpers::mem_aligned_read(EXTRA_PARAMS_ADDR as u32, step, count, mem_processors); + } + + if pre_count > 0 { + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memcmp] INPUT PRE SRC:0x{src64:08X} DST:0x{dst64:08X} S:{step}"); + MemBusHelpers::mem_aligned_read(src64, step, src_data[0], mem_processors); + MemBusHelpers::mem_aligned_read(dst64, step, dst_data[0], mem_processors); + + if DmaInfo::is_double_read_pre(encoded) { + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memcmp] INPUT PRE DOUBLE SRC:0x{:08X} S:{step}", src64 + 8); + MemBusHelpers::mem_aligned_read(src64 + 8, step, src_data[1], mem_processors); + } + } + + // this is part of words loop + let post_count = DmaInfo::get_post_count(encoded) as u64; + let loop_count = DmaInfo::get_loop_count(encoded); + let src_data_offset = ((src_offset + pre_count) > 7) as usize; + let dst_data_offset = (pre_count > 0) as usize; + + if loop_count > 0 { + let src64_loop = src64 + src_data_offset as u32 * 8; + let dst64_loop = dst64 + dst_data_offset as u32 * 8; + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memcmp] INPUT LOOP SRC:0x{src64_loop:08X} DST:0x{dst64_loop:08X} C:{loop_count} S:{step}"); + MemBusHelpers::mem_aligned_read_from_slice( + src64_loop, + step, + &src_data[src_data_offset..src_data_offset + loop_count + unaligned as usize], + mem_processors, + ); + MemBusHelpers::mem_aligned_read_from_slice( + dst64_loop, + step, + &dst_data[dst_data_offset..dst_data_offset + loop_count], + mem_processors, + ); + } + + let dst_data_offset = dst_data_offset + loop_count; + + if post_count > 0 { + let src64_post = (src64 + pre_count as u32 + loop_count as u32 * 8) & !0x07; + let src_data_offset = (src64_post - src64) as usize >> 3; + let dst64_post = dst64 + dst_data_offset as u32 * 8; + + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memcmp] INPUT POST SRC:0x{src64_post:08X} DST:0x{dst64_post:08X} S:{step}"); + MemBusHelpers::mem_aligned_read( + src64_post, + step, + src_data[src_data_offset], + mem_processors, + ); + MemBusHelpers::mem_aligned_read( + dst64_post, + step, + dst_data[dst_data_offset], + mem_processors, + ); + + if DmaInfo::is_double_read_post(encoded) { + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memcmp] INPUT DOUBLE-POST SRC:0x{:08X} S:{step}", src64_post + 8); + MemBusHelpers::mem_aligned_read( + src64_post + 8, + step, + src_data[src_data_offset + 1], + mem_processors, + ); + } + } +} + +pub fn skip_dma_memcmp_mem_inputs(data: &[u64], mem_processors: &mut P) -> bool { + let dst = data[A]; + let src = data[B]; + let count = DmaInfo::get_count(data[DMA_ENCODED]) as u64; + let op = data[OP] as u8; + let load_count_from_mem = op == ZiskOp::DMA_MEMCMP; + + // calculate range for dst and src to verify if any of them are included + // in the memcollector addresses. + + let dst64_from = dst as u32 & !0x07; + let dst64_to = (dst + count + 7) as u32 & !0x07; + #[cfg(feature = "debug_dma_gen_mem_inputs")] + let (count64, step) = (dst64_to as u64 - dst64_from as u64 + 1, data[STEP]); + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memcmp] SKIP DST:[0x{dst64_from:08X}..=0x{dst64_to:08X}] C:{count} S:{step}"); + + if load_count_from_mem { + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memcmp] SKIP PARAM 0x{EXTRA_PARAMS_ADDR:08X} S:{step}"); + if !mem_processors.skip_addr(EXTRA_PARAMS_ADDR as u32) { + return false; + } + } + + if !mem_processors.skip_addr_range(dst64_from, dst64_to) { + return false; + } + + let src64_from = src as u32 & !0x07; + let src64_to = (src + count + 7) as u32 & !0x07; + #[cfg(feature = "debug_dma_gen_mem_inputs")] + let (count64, step) = (dst64_to as u64 - dst64_from as u64 + 1, data[STEP]); + + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memcmp] SKIP SRC:[0x{src64_from:08X}..=0x{src64_to:08X}] C:{count} S:{step}"); + if !mem_processors.skip_addr_range(src64_from, src64_to) { + return false; + } + + // If any mem_collector includes this addresses we could skip this precompiles + // at mem input data generation. + true +} diff --git a/precompiles/dma/src/dma_gen_memcpy_mem_inputs.rs b/precompiles/dma/src/dma_gen_memcpy_mem_inputs.rs new file mode 100644 index 000000000..9b6051a36 --- /dev/null +++ b/precompiles/dma/src/dma_gen_memcpy_mem_inputs.rs @@ -0,0 +1,206 @@ +use precompiles_common::MemBusHelpers; +use precompiles_common::MemProcessor; +use precompiles_helpers::{DmaHelpers, DmaInfo}; +use zisk_common::{A, B, DMA_ENCODED, OP, STEP}; +use zisk_core::{zisk_ops::ZiskOp, EXTRA_PARAMS_ADDR}; + +pub fn generate_dma_memcpy_mem_inputs( + data: &[u64], + data_ext: &[u64], + mem_processors: &mut P, +) { + let op = data[OP] as u8; + + let dst = data[A]; + let src = data[B]; + let encoded = data[DMA_ENCODED]; + let without_src = op == ZiskOp::DMA_INPUTCPY || op == ZiskOp::DMA_XMEMSET; + let with_src = !without_src; + let dst64 = (dst & !0x07) as u32; + let src64 = (src & !0x07) as u32; + let main_step = data[STEP]; + let pre_count = DmaInfo::get_pre_count(encoded) as u64; + let dst_offset = dst & 0x07; + let src_offset = src & 0x07; + let aligned = without_src || dst_offset == src_offset; + + // NOTE: for dual memories it's very important to keep the order of loads and stores because + // stores happend after loads. + + if op == ZiskOp::DMA_MEMCMP || op == ZiskOp::DMA_MEMCPY { + MemBusHelpers::mem_aligned_read( + EXTRA_PARAMS_ADDR as u32, + main_step, + DmaInfo::get_count(encoded) as u64, + mem_processors, + ); + } + + if pre_count > 0 { + let pre_data_offset = DmaInfo::get_pre_data_offset(encoded); + let read_value = data_ext[pre_data_offset]; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@pre 0x{src64:08X} S:{main_step} V:{read_value} (0x{read_value:016X})"); + if with_src { + MemBusHelpers::mem_aligned_read(src64, main_step, read_value, mem_processors); + } + // pre-load of write address before unaligned write + let pre_value = data_ext[DmaInfo::get_pre_write_offset(encoded)]; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@pre-p 0x{dst64:08X} S:{main_step} V:{pre_value} (0x{pre_value:016X})"); + + MemBusHelpers::mem_aligned_read(dst64, main_step, pre_value, mem_processors); + + let write_value = if DmaInfo::is_double_read_pre(encoded) { + let second_read_value = data_ext[pre_data_offset + 1]; + #[cfg(feature = "debug_dma")] + println!( + "DMA: mem_aligned_load@pre2 0x{:08X} S:{main_step} V:{second_read_value} (0x{second_read_value:016X})", + src64 + 8 + ); + if with_src { + MemBusHelpers::mem_aligned_read( + src64 + 8, + main_step, + second_read_value, + mem_processors, + ); + } + DmaHelpers::calculate_write_value( + dst_offset, + src_offset, + pre_count, + pre_value, + &[read_value, second_read_value], + ) + } else { + DmaHelpers::calculate_write_value( + dst_offset, + src_offset, + pre_count, + pre_value, + &[read_value], + ) + }; + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write@pre 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); + + MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, mem_processors); + } + + // this is part of words loop + let post_count = DmaInfo::get_post_count(encoded) as u64; + let loop_count = DmaInfo::get_loop_count(encoded); + if loop_count > 0 { + let loop_src = src as u32 + pre_count as u32; + let dst64 = (dst as u32 + pre_count as u32) & !0x07; + let src64 = loop_src & !0x07; + let loop_data_offset = DmaInfo::get_loop_data_offset(encoded); + let loop_data_count = DmaInfo::get_loop_count(encoded); + let loop_src_data_end = + loop_data_offset + loop_data_count + ((loop_src & 0x07) > 0) as usize; + let values = &data_ext[loop_data_offset..loop_src_data_end]; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load_from_slice 0x{src64:08X} S:{main_step} V:{values:?}"); + if with_src { + MemBusHelpers::mem_aligned_read_from_slice(src64, main_step, values, mem_processors); + } + + let src_offset = (src_offset + pre_count) & 0x07; + if aligned { + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write_from_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); + + MemBusHelpers::mem_aligned_write_from_slice(dst64, main_step, values, mem_processors); + } else { + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write_from_read_unaligned_slice 0x{dst64:08X} S:{main_step} V:{values:?}"); + MemBusHelpers::mem_aligned_write_from_read_unaligned_slice( + dst64, + main_step, + src_offset as u8, + values, + mem_processors, + ); + } + } + if post_count > 0 { + let src_offset = src & 0x07; + + let post_data_offset = DmaInfo::get_post_data_offset(encoded); + let src64 = (src as u32 + pre_count as u32 + loop_count as u32 * 8) & !0x07; + let dst64 = dst as u32 + pre_count as u32 + loop_count as u32 * 8; + let read_value = data_ext[post_data_offset]; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@post 0x{src64:08X} S:{main_step} V:{read_value} (0x{read_value:016X})"); + if with_src { + MemBusHelpers::mem_aligned_read(src64, main_step, read_value, mem_processors); + } + // pre-load of write address before unaligned write + let pre_value = data_ext[DmaInfo::get_post_write_offset(encoded)]; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_load@post-p 0x{dst64:08X} S:{main_step} V:{pre_value} (0x{pre_value:016X})"); + + MemBusHelpers::mem_aligned_read(dst64, main_step, pre_value, mem_processors); + + let write_value = if DmaInfo::is_double_read_post(encoded) { + let second_read_value = data_ext[post_data_offset + 1]; + #[cfg(feature = "debug_dma")] + println!( + "DMA: mem_aligned_load@post2 0x{:08X} S:{main_step} V:{second_read_value} (0x{second_read_value:016X})", + src64 + 8 + ); + if with_src { + MemBusHelpers::mem_aligned_read( + src64 + 8, + main_step, + second_read_value, + mem_processors, + ); + } + DmaHelpers::calculate_write_value( + 0, // in post offset it's 0 + (src_offset + pre_count) & 0x07, // src_offset it's modified by pre, aligned/unaligned no change offset + post_count, + pre_value, + &[read_value, second_read_value], + ) + } else { + DmaHelpers::calculate_write_value( + 0, // in post offset it's 0 + (src_offset + pre_count) & 0x07, // src_offset it's modified by pre, aligned/unaligned no change offset + post_count, + pre_value, + &[read_value], + ) + }; + + #[cfg(feature = "debug_dma")] + println!("DMA: mem_aligned_write@post 0x{dst64:08X} S:{main_step} V:{write_value} (0x{write_value:016X})"); + MemBusHelpers::mem_aligned_write(dst64, main_step, write_value, mem_processors); + } +} + +pub fn skip_dma_memcpy_mem_inputs(data: &[u64], mem_processors: &mut P) -> bool { + let dst = data[A]; + let src = data[B]; + + let count = DmaInfo::get_count(data[DMA_ENCODED]) as u64; + + // calculate range for dst and src to verify if any of them are included + // in the memcollector addresses. + + let dst64_from = dst as u32 & !0x07; + let src64_from = src as u32 & !0x07; + let dst64_to = (dst + count + 7) as u32 & !0x07; + let src64_to = (src + count + 7) as u32 & !0x07; + + mem_processors.skip_addr(EXTRA_PARAMS_ADDR as u32) + && mem_processors.skip_addr_range(dst64_from, dst64_to) + && mem_processors.skip_addr_range(src64_from, src64_to) +} diff --git a/precompiles/dma/src/dma_gen_memset_mem_inputs.rs b/precompiles/dma/src/dma_gen_memset_mem_inputs.rs new file mode 100644 index 000000000..b79fb3b4d --- /dev/null +++ b/precompiles/dma/src/dma_gen_memset_mem_inputs.rs @@ -0,0 +1,107 @@ +use precompiles_common::MemBusHelpers; +use precompiles_common::MemProcessor; +use precompiles_helpers::DmaInfo; +use zisk_common::{A, DMA_ENCODED, STEP}; + +pub fn generate_dma_memset_mem_inputs( + data: &[u64], + data_ext: &[u64], + mem_processors: &mut P, +) { + let dst = data[A]; + let encoded = data[DMA_ENCODED]; + let dst64 = (dst & !0x07) as u32; + let dst_offset = dst & 0x07; + let step = data[STEP]; + let pre_count = DmaInfo::get_pre_count(encoded) as u64; + let post_count = DmaInfo::get_post_count(encoded) as u64; + + debug_assert_eq!( + (pre_count > 0) as usize + (post_count > 0) as usize, + data_ext.len(), + "[dma_memset] data length mismatch DATA:[{}] INFO={}", + data.iter().map(|v| format!("0x{v:016X}")).collect::>().join(", "), + DmaInfo::to_string(encoded) + ); + + // the memset operation was simple, no unaligned loop, on aligned loop no need to read previous value + // really only need read previous value if has pre o post + + if pre_count > 0 { + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memset] INPUT PRE DST:0x{dst64:08X} S:{step}"); + MemBusHelpers::mem_aligned_read(dst64, step, data_ext[0], mem_processors); + } + + let loop_count = DmaInfo::get_loop_count(encoded); + + if post_count > 0 { + let dst64_post = ((dst + pre_count) as usize + loop_count * 8) as u32; + + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memset] INPUT POST DST:0x{dst64_post:08X} S:{step}"); + MemBusHelpers::mem_aligned_read( + dst64_post, + step, + data_ext[(pre_count > 0) as usize], + mem_processors, + ); + } + + let fill_byte = DmaInfo::get_fill_byte(encoded) as u64; + let fill_word = fill_byte + | fill_byte << 8 + | fill_byte << 16 + | fill_byte << 24 + | fill_byte << 32 + | fill_byte << 40 + | fill_byte << 48 + | fill_byte << 56; + + if pre_count > 0 { + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memset] INPUT PRE WRITE DST:0x{dst64:08X} S:{step}"); + let mask = (0xFFFF_FFFF_FFFF_FFFFu64 >> (64 - pre_count * 8)) << (dst_offset * 8); + let write_value = (fill_word & mask) | (data_ext[0] & !mask); + println!( + "WRITE_VALUE S:{step} F:{fill_word:016X} M:{mask:016X} D:{:016X} W:{write_value:016X}", + data_ext[0] + ); + MemBusHelpers::mem_aligned_write(dst64, step, write_value, mem_processors); + } + + if loop_count > 0 { + let dst64_loop = dst as u32 + pre_count as u32; + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memset] INPUT LOOP DST:0x{dst64_loop:08X} C:{loop_count} S:{step}"); + MemBusHelpers::mem_aligned_write_pattern( + dst64_loop, + step, + fill_word, + loop_count, + mem_processors, + ); + } + if post_count > 0 { + let dst64_post = ((dst + pre_count) as usize + loop_count * 8) as u32; + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memset] INPUT POST WRITE DST:0x{dst64_post:08X} S:{step}"); + let mask = 0xFFFF_FFFF_FFFF_FFFFu64 >> (64 - post_count * 8); + let write_value = (fill_word & mask) | (data_ext[(pre_count > 0) as usize] & !mask); + MemBusHelpers::mem_aligned_write(dst64_post, step, write_value, mem_processors); + } +} + +pub fn skip_dma_memset_mem_inputs(data: &[u64], mem_processors: &mut P) -> bool { + let dst = data[A]; + let encoded = data[DMA_ENCODED]; + let dst64 = (dst & !0x07) as u32; + let dst64_to = (dst + DmaInfo::get_count(encoded) as u64 - 1) as u32 & !0x07; + + #[cfg(feature = "debug_dma_gen_mem_inputs")] + let step = data[STEP]; + #[cfg(feature = "debug_dma_gen_mem_inputs")] + println!("[dma_memset] SKIP DST:[0x{dst64:08X}..=0x{dst64_to:08X}] S:{step}"); + + mem_processors.skip_addr_range(dst64, dst64_to) +} diff --git a/precompiles/dma/src/dma_instance_info.rs b/precompiles/dma/src/dma_instance_info.rs new file mode 100644 index 000000000..bd669ef1f --- /dev/null +++ b/precompiles/dma/src/dma_instance_info.rs @@ -0,0 +1,11 @@ +use std::collections::HashMap; + +use zisk_common::ChunkId; + +use crate::DmaCollectCounters; + +#[derive(Debug)] +pub struct DmaInstanceInfo { + pub chunks: HashMap, + pub last_chunk: Option, +} diff --git a/precompiles/dma/src/dma_instances_builder.rs b/precompiles/dma/src/dma_instances_builder.rs new file mode 100644 index 000000000..3a82b9591 --- /dev/null +++ b/precompiles/dma/src/dma_instances_builder.rs @@ -0,0 +1,263 @@ +use core::panic; +use std::collections::HashMap; + +use zisk_common::{CheckPoint, ChunkId, CollectCounter}; + +use crate::{ + DmaCheckPoint, DmaCollectCounters, DmaInstanceInfo, DMA_COUNTER_INPUTCPY, DMA_COUNTER_MEMCMP, + DMA_COUNTER_MEMCPY, DMA_COUNTER_MEMSET, +}; +#[derive(Debug)] +pub struct DmaInstancesBuilder { + pub tag: String, + pub current_chunk: Option, + pub max_instances: usize, + pub rows: usize, + pub rows_available: usize, + pub instances: Vec, + pub count_memcpy_rows: usize, + pub count_memset_rows: usize, + pub count_memcmp_rows: usize, + pub count_inputcpy_rows: usize, + pub skip_memcpy_rows: usize, + pub skip_memset_rows: usize, + pub skip_memcmp_rows: usize, + pub skip_inputcpy_rows: usize, + pub inputs_counter: usize, +} + +impl DmaInstancesBuilder { + pub fn new(tag: &str, max_instances: usize, rows: usize) -> Self { + Self { + tag: tag.to_string(), + current_chunk: None, + max_instances, + rows, + rows_available: 0, + instances: Vec::new(), + skip_memcpy_rows: 0, + skip_memset_rows: 0, + skip_memcmp_rows: 0, + skip_inputcpy_rows: 0, + count_memcpy_rows: 0, + count_memset_rows: 0, + count_memcmp_rows: 0, + count_inputcpy_rows: 0, + inputs_counter: 0, + } + } + pub fn count_to_skip(&mut self) { + self.skip_memcpy_rows += self.count_memcpy_rows; + self.skip_memset_rows += self.count_memset_rows; + self.skip_memcmp_rows += self.count_memcmp_rows; + self.skip_inputcpy_rows += self.count_inputcpy_rows; + } + pub fn reset_count_and_skip(&mut self) { + self.skip_memcpy_rows = 0; + self.skip_memset_rows = 0; + self.skip_memcmp_rows = 0; + self.skip_inputcpy_rows = 0; + self.count_memcpy_rows = 0; + self.count_memset_rows = 0; + self.count_memcmp_rows = 0; + self.count_inputcpy_rows = 0; + } + + pub fn open_new_instance(&mut self) { + println!( + "\x1B[36;1mOPEN_NEW_INSTANCE[{}]: {} instances, max_instances: {}, rows_available: {}, \ + count_memcpy_rows: {}, count_inputcpy_rows: {}, count_memset_rows: {}, \ + count_memcmp_rows: {}, skip_memcpy_rows: {}, skip_inputcpy_rows: {}, \ + skip_memset_rows: {}, skip_memcmp_rows: {}\x1B[0m", + self.tag, + self.instances.len(), + self.max_instances, + self.rows_available, + self.count_memcpy_rows, + self.count_inputcpy_rows, + self.count_memset_rows, + self.count_memcmp_rows, + self.skip_memcpy_rows, + self.skip_inputcpy_rows, + self.skip_memset_rows, + self.skip_memcmp_rows + ); + if self.rows_available > 0 { + panic!( + "[{}] Cannot open new instance, rows still available: {}", + self.tag, self.rows_available + ); + } + if self.instances.len() >= self.max_instances { + println!("{:?}", self); + panic!( + "[{}] Too many instances {} max: {}, cannot create more", + self.tag, + self.instances.len(), + self.max_instances + ); + } + self.instances.push(DmaInstanceInfo { chunks: HashMap::new(), last_chunk: None }); + self.rows_available = self.rows; + } + pub fn flush_current_chunk(&mut self) { + if let Some(chunk_id) = self.current_chunk { + if self.count_memcpy_rows == 0 + && self.count_inputcpy_rows == 0 + && self.count_memset_rows == 0 + && self.count_memcmp_rows == 0 + { + return; + } + if self.instances.is_empty() { + self.open_new_instance(); + } + self.instances.last_mut().unwrap().chunks.insert( + chunk_id, + ( + self.inputs_counter as u64, + DmaCollectCounters { + memcpy: CollectCounter::new( + self.skip_memcpy_rows as u32, + self.count_memcpy_rows as u32, + ), + inputcpy: CollectCounter::new( + self.skip_inputcpy_rows as u32, + self.count_inputcpy_rows as u32, + ), + memset: CollectCounter::new( + self.skip_memset_rows as u32, + self.count_memset_rows as u32, + ), + memcmp: CollectCounter::new( + self.skip_memcmp_rows as u32, + self.count_memcmp_rows as u32, + ), + }, + ), + ); + self.instances.last_mut().unwrap().last_chunk = Some(chunk_id); + } + } + #[inline(always)] + pub fn add_memcpy_rows(&mut self, chunk_id: ChunkId, skip: usize, rows: usize, inputs: usize) { + self.add_op_rows(chunk_id, skip, rows, inputs, DMA_COUNTER_MEMCPY); + } + #[inline(always)] + pub fn add_memset_rows(&mut self, chunk_id: ChunkId, skip: usize, rows: usize, inputs: usize) { + self.add_op_rows(chunk_id, skip, rows, inputs, DMA_COUNTER_MEMSET); + } + #[inline(always)] + pub fn add_memcmp_rows(&mut self, chunk_id: ChunkId, skip: usize, rows: usize, inputs: usize) { + self.add_op_rows(chunk_id, skip, rows, inputs, DMA_COUNTER_MEMCMP); + } + #[inline(always)] + pub fn add_inputcpy_rows( + &mut self, + chunk_id: ChunkId, + skip: usize, + rows: usize, + inputs: usize, + ) { + self.add_op_rows(chunk_id, skip, rows, inputs, DMA_COUNTER_INPUTCPY); + } + + pub fn add_op_rows( + &mut self, + chunk_id: ChunkId, + skip: usize, + rows: usize, + inputs: usize, + op: usize, + ) { + println!( + "\x1B[34;1mADD_OP_ROWS[{}]: chunk_id: {:?}, rows: {}, inputs: {}, op: 0x{op:02X}\x1B[0m", + self.tag, chunk_id, rows, inputs + ); + if Some(chunk_id) != self.current_chunk { + self.flush_current_chunk(); + self.reset_count_and_skip(); + self.current_chunk = Some(chunk_id); + } + let mut rows = rows; + while rows > 0 { + if self.rows_available == 0 { + self.flush_current_chunk(); + self.count_to_skip(); + self.open_new_instance(); + } + let rows_applicable = std::cmp::min(self.rows_available, rows); + rows -= rows_applicable; + self.rows_available -= rows_applicable; + match op { + DMA_COUNTER_MEMCPY => { + if skip > 0 { + assert!( + self.count_memcpy_rows == 0, + "Cannot have both skip and count for memcpy in the same chunk", + ); + self.skip_memcpy_rows += skip; + } + self.count_memcpy_rows += rows_applicable; + } + DMA_COUNTER_MEMSET => { + if skip > 0 { + assert!( + self.count_memset_rows == 0, + "Cannot have both skip and count for memset in the same chunk", + ); + self.skip_memset_rows += skip; + } + self.count_memset_rows += rows_applicable; + } + DMA_COUNTER_MEMCMP => { + if skip > 0 { + assert!( + self.count_memcmp_rows == 0, + "Cannot have both skip and count for memcmp in the same chunk", + ); + self.skip_memcmp_rows += skip; + } + self.count_memcmp_rows += rows_applicable; + } + DMA_COUNTER_INPUTCPY => { + if skip > 0 { + assert!( + self.count_inputcpy_rows == 0, + "Cannot have both skip and count for inputcpy in the same chunk", + ); + self.skip_inputcpy_rows += skip; + } + self.count_inputcpy_rows += rows_applicable; + } + _ => { + panic!("Unsupported operation for DMA instance builder 0x{op:02X}") + } + } + self.inputs_counter += inputs; + } + } + pub fn flush(&mut self) { + self.flush_current_chunk(); + self.reset_count_and_skip(); + self.current_chunk = None; + } + pub fn get_plan(&mut self) -> Vec<(CheckPoint, DmaCheckPoint)> { + self.flush(); + let mut checkpoints = Vec::new(); + let last_segment_id = self.instances.len().saturating_sub(1); + for (segment_id, dma_info) in self.instances.iter_mut().enumerate() { + let keys = dma_info.chunks.keys().cloned().collect::>(); + + checkpoints.push(( + CheckPoint::Multiple(keys), + DmaCheckPoint { + chunks: std::mem::take(&mut dma_info.chunks), + last_chunk: dma_info.last_chunk, + is_last_segment: segment_id == last_segment_id, + }, + )); + } + checkpoints + } +} diff --git a/precompiles/dma/src/dma_manager.rs b/precompiles/dma/src/dma_manager.rs index 57ff3add2..905779e14 100644 --- a/precompiles/dma/src/dma_manager.rs +++ b/precompiles/dma/src/dma_manager.rs @@ -4,11 +4,18 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::ProofCtx; use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, Plan, Planner}; -use zisk_pil::{Dma64AlignedTrace, DmaPrePostTrace, DmaTrace, DmaUnalignedTrace, ZiskProofValues}; +use zisk_pil::{ + Dma64AlignedInputCpyTrace, Dma64AlignedMemCpyTrace, Dma64AlignedMemSetTrace, + Dma64AlignedMemTrace, Dma64AlignedTrace, DmaInputCpyTrace, DmaMemCpyTrace, + DmaPrePostInputCpyTrace, DmaPrePostMemCpyTrace, DmaPrePostTrace, DmaTrace, DmaUnalignedTrace, + ZiskProofValues, +}; use crate::{ - Dma64AlignedInstance, Dma64AlignedSM, DmaCounterInputGen, DmaInstance, DmaPlanner, - DmaPrePostInstance, DmaPrePostSM, DmaSM, DmaUnalignedInstance, DmaUnalignedSM, + Dma64AlignedInputCpySM, Dma64AlignedInstance, Dma64AlignedMemCpySM, Dma64AlignedMemSM, + Dma64AlignedMemSetSM, Dma64AlignedSM, DmaCounterInputGen, DmaInputCpySM, DmaInstance, + DmaMemCpySM, DmaPlanner, DmaPrePostInputCpySM, DmaPrePostInstance, DmaPrePostMemCpySM, + DmaPrePostSM, DmaSM, DmaUnalignedInstance, DmaUnalignedSM, }; /// The `DmaManager` struct represents the Dma manager, @@ -17,8 +24,16 @@ use crate::{ pub struct DmaManager { /// Dma state machine dma_sm: Arc>, + dma_memcpy_sm: Arc>, + dma_inputcpy_sm: Arc>, dma_pre_post_sm: Arc>, + dma_pre_post_memcpy_sm: Arc>, + dma_pre_post_inputcpy_sm: Arc>, dma_64_aligned_sm: Arc>, + dma_64_aligned_mem_sm: Arc>, + dma_64_aligned_memcpy_sm: Arc>, + dma_64_aligned_memset_sm: Arc>, + dma_64_aligned_inputcpy_sm: Arc>, dma_unaligned_sm: Arc>, } @@ -29,11 +44,32 @@ impl DmaManager { /// An `Arc`-wrapped instance of `DmaManager`. pub fn new(std: Arc>) -> Arc { let dma_sm = DmaSM::new(std.clone()); + let dma_memcpy_sm = DmaMemCpySM::new(std.clone()); + let dma_inputcpy_sm = DmaInputCpySM::new(std.clone()); let dma_pre_post_sm = DmaPrePostSM::new(std.clone()); + let dma_pre_post_inputcpy_sm = DmaPrePostInputCpySM::new(std.clone()); + let dma_pre_post_memcpy_sm = DmaPrePostMemCpySM::new(std.clone()); let dma_64_aligned_sm = Dma64AlignedSM::new(std.clone()); + let dma_64_aligned_mem_sm = Dma64AlignedMemSM::new(std.clone()); + let dma_64_aligned_memcpy_sm = Dma64AlignedMemCpySM::new(std.clone()); + let dma_64_aligned_memset_sm = Dma64AlignedMemSetSM::new(std.clone()); + let dma_64_aligned_inputcpy_sm = Dma64AlignedInputCpySM::new(std.clone()); let dma_unaligned_sm = DmaUnalignedSM::new(std); - Arc::new(Self { dma_sm, dma_pre_post_sm, dma_64_aligned_sm, dma_unaligned_sm }) + Arc::new(Self { + dma_sm, + dma_memcpy_sm, + dma_inputcpy_sm, + dma_pre_post_sm, + dma_pre_post_inputcpy_sm, + dma_pre_post_memcpy_sm, + dma_64_aligned_sm, + dma_64_aligned_mem_sm, + dma_64_aligned_memcpy_sm, + dma_64_aligned_memset_sm, + dma_64_aligned_inputcpy_sm, + dma_unaligned_sm, + }) } pub fn build_dma_counter(&self, asm_execution: bool) -> DmaCounterInputGen { @@ -71,13 +107,41 @@ impl ComponentBuilder for DmaManager { /// Panics if the provided `air_id` is not supported. fn build_instance(&self, ictx: InstanceCtx) -> Box> { match ictx.plan.air_id { + // DMA controller instances DmaTrace::::AIR_ID => Box::new(DmaInstance::new(self.dma_sm.clone(), ictx)), + DmaMemCpyTrace::::AIR_ID => { + Box::new(DmaInstance::new(self.dma_memcpy_sm.clone(), ictx)) + } + DmaInputCpyTrace::::AIR_ID => { + Box::new(DmaInstance::new(self.dma_inputcpy_sm.clone(), ictx)) + } + // DMA pre post instances DmaPrePostTrace::::AIR_ID => { Box::new(DmaPrePostInstance::new(self.dma_pre_post_sm.clone(), ictx)) } + DmaPrePostMemCpyTrace::::AIR_ID => { + Box::new(DmaPrePostInstance::new(self.dma_pre_post_memcpy_sm.clone(), ictx)) + } + DmaPrePostInputCpyTrace::::AIR_ID => { + Box::new(DmaPrePostInstance::new(self.dma_pre_post_inputcpy_sm.clone(), ictx)) + } + // DMA 64 aligned instances Dma64AlignedTrace::::AIR_ID => { Box::new(Dma64AlignedInstance::new(self.dma_64_aligned_sm.clone(), ictx)) } + Dma64AlignedMemCpyTrace::::AIR_ID => { + Box::new(Dma64AlignedInstance::new(self.dma_64_aligned_memcpy_sm.clone(), ictx)) + } + Dma64AlignedInputCpyTrace::::AIR_ID => { + Box::new(Dma64AlignedInstance::new(self.dma_64_aligned_inputcpy_sm.clone(), ictx)) + } + Dma64AlignedMemSetTrace::::AIR_ID => { + Box::new(Dma64AlignedInstance::new(self.dma_64_aligned_memset_sm.clone(), ictx)) + } + Dma64AlignedMemTrace::::AIR_ID => { + Box::new(Dma64AlignedInstance::new(self.dma_64_aligned_mem_sm.clone(), ictx)) + } + // DMA unaligned instances DmaUnalignedTrace::::AIR_ID => { Box::new(DmaUnalignedInstance::new(self.dma_unaligned_sm.clone(), ictx)) } @@ -90,10 +154,22 @@ impl ComponentBuilder for DmaManager { fn configure_instances(&self, pctx: &ProofCtx, plannings: &[Plan]) { let enable_dma_64_aligned = plannings.iter().any(|p| p.air_id == Dma64AlignedTrace::::AIR_ID); + let enable_dma_64_aligned_memcpy = + plannings.iter().any(|p| p.air_id == Dma64AlignedMemCpyTrace::::AIR_ID); + let enable_dma_64_aligned_memset = + plannings.iter().any(|p| p.air_id == Dma64AlignedMemSetTrace::::AIR_ID); + let enable_dma_64_aligned_inputcpy = + plannings.iter().any(|p| p.air_id == Dma64AlignedInputCpyTrace::::AIR_ID); + let enable_dma_64_aligned_mem = + plannings.iter().any(|p| p.air_id == Dma64AlignedMemTrace::::AIR_ID); let enable_dma_unaligned = plannings.iter().any(|p| p.air_id == DmaUnalignedTrace::::AIR_ID); let mut proof_values = ZiskProofValues::from_vec_guard(pctx.get_proof_values()); proof_values.enable_dma_64_aligned = F::from_bool(enable_dma_64_aligned); proof_values.enable_dma_unaligned = F::from_bool(enable_dma_unaligned); + proof_values.enable_dma_64_aligned_memcpy = F::from_bool(enable_dma_64_aligned_memcpy); + proof_values.enable_dma_64_aligned_memset = F::from_bool(enable_dma_64_aligned_memset); + proof_values.enable_dma_64_aligned_inputcpy = F::from_bool(enable_dma_64_aligned_inputcpy); + proof_values.enable_dma_64_aligned_mem = F::from_bool(enable_dma_64_aligned_mem); } } diff --git a/precompiles/dma/src/dma_planner.rs b/precompiles/dma/src/dma_planner.rs index e031bd161..1d1ab7b2d 100644 --- a/precompiles/dma/src/dma_planner.rs +++ b/precompiles/dma/src/dma_planner.rs @@ -4,15 +4,19 @@ //! It organizes execution plans for both regular instances and table instances, //! leveraging arithmetic operation counts and metadata to construct detailed plans. -use crate::DmaCounterInputGen; -use std::any::Any; -use std::collections::HashMap; +use crate::DmaStrategy; use fields::PrimeField64; -use zisk_common::{ - BusDeviceMetrics, CheckPoint, ChunkId, CollectCounter, InstanceType, Plan, Planner, SegmentId, +use zisk_common::{BusDeviceMetrics, ChunkId, InstanceType, Plan, Planner, SegmentId}; +use zisk_pil::ZISK_AIRGROUP_ID; + +#[cfg(feature = "packed")] +use zisk_pil::{ + Dma64AlignedTracePacked as Dma64AlignedTrace, DmaInputCpyTracePacked as DmaInputCpyTrace, + DmaMemCpyTracePacked as DmaMemCpyTrace, DmaMemCpyTracePacked as DmaMemCpyTrace, + DmaPrePostTracePacked as DmaPrePostTrace, DmaTracePacked as DmaTrace, + DmaTraceRowPacked as DmaTraceRow, DmaUnalignedTracePacked as DmaUnalignedTrace, }; -use zisk_pil::{Dma64AlignedTrace, DmaPrePostTrace, DmaTrace, DmaUnalignedTrace}; /// The `DmaPlanner` struct organizes execution plans for arithmetic instances and tables. /// @@ -23,136 +27,14 @@ pub struct DmaPlanner { _marker: std::marker::PhantomData, } -#[derive(Default)] -pub struct DmaCheckPoint { - pub chunks: HashMap, - pub last_chunk: Option, - pub is_last_segment: bool, -} - -/// Macro to generate a plan function for a specific field of a struct. -/// -/// This macro creates a function that generates checkpoints from counts across multiple chunks, -/// allowing you to specify which field of the struct to use for counting. -/// -/// # Macro Arguments -/// * `$fn_name` - The name of the generated function. -/// * `$type` - The struct type containing the count field (must have a `chunk_id: ChunkId` field). -/// * `$field` - The field name to use as the count value (must be `u64`). -/// -/// # Generated Function -/// The generated function has the signature: -/// ```ignore -/// pub fn $fn_name( -/// counts: &[$type], -/// size: u64, -/// ) -> Vec<(CheckPoint, , bool>)> -/// ``` -/// -/// # Example -/// ```ignore -/// define_plan_for_field!(plan_by_inst_count, InstFropsCount, inst_count); -/// define_plan_for_field!(plan_by_frops_count, InstFropsCount, frops_count); -/// ``` -macro_rules! define_plan_for_field { - ($fn_name:ident, $type:ty, $field:ident) => { - define_plan_for_field!($fn_name, $type, $field, false, $field); - }; - ($fn_name:ident, $type:ty, $field:ident, $field_inputs: ident) => { - define_plan_for_field!($fn_name, $type, $field, true, $field_inputs); - }; - ($fn_name:ident, $type:ty, $field:ident, $has_input_counter: literal, $field_inputs: ident) => { - pub fn $fn_name( - counts: &Vec<(ChunkId, Box)>, - size: u64, - ) -> Vec<(CheckPoint, DmaCheckPoint)> { - if counts.is_empty() || size == 0 { - return vec![]; - } - // let tag = stringify!($field); - // let total = counts.len(); - // println!("plan_{tag} counts:{total} size:{size}"); - let mut checkpoints = Vec::new(); - let mut current_scope = - DmaCheckPoint { chunks: HashMap::new(), last_chunk: None, is_last_segment: false }; - let mut remaining_size = size; // Remaining size for the current scope. - - for (current_chunk, dyn_counter) in counts.iter() { - let counter = (**dyn_counter).as_any().downcast_ref::<$type>().unwrap(); - // println!("counter: {:?}", counter); - let mut inst_count = counter.$field as u64; - let mut cumulative_offset = 0u64; // Reset cumulative offset for each chunk. - - while inst_count > 0 { - let checkpoint_size = remaining_size.min(inst_count); - // println!("plan_{tag} C:{}/{total} #{current_chunk} I:{remaining_size}/{size} +{checkpoint_size}/{inst_count} skip:{cumulative_offset} count:{checkpoint_size}", index+1); - - current_scope.chunks.insert( - *current_chunk, - ( - // Use input counter to calculate the capacity of collector inputs vector, used - // for state machines that has different number of rows by input. - if $has_input_counter { - counter.$field_inputs as u64 - } else { - checkpoint_size - }, - CollectCounter::new(cumulative_offset as u32, checkpoint_size as u32), - ), - ); - current_scope.last_chunk = Some(*current_chunk); - - cumulative_offset += checkpoint_size; - inst_count -= checkpoint_size; - remaining_size -= checkpoint_size; - - if remaining_size == 0 { - // println!("plan_{tag} adding instance .... inst_count = {inst_count}"); - let keys = current_scope.chunks.keys().cloned().collect::>(); - checkpoints - .push((CheckPoint::Multiple(keys), std::mem::take(&mut current_scope))); - remaining_size = size; - } - } - } - // println!("plan_{tag} final counters interation"); - // Push any remaining checkpoints into the result. - if !current_scope.chunks.is_empty() { - let keys = current_scope.chunks.keys().cloned().collect::>(); - current_scope.is_last_segment = true; - checkpoints.push((CheckPoint::Multiple(keys), std::mem::take(&mut current_scope))); - } else if let Some(last) = checkpoints.last_mut() { - last.1.is_last_segment = true; - } - - checkpoints - } - }; -} - impl DmaPlanner { /// Creates a new `DmaPlanner`. /// /// # Returns /// A new `DmaPlanner` instance with no preconfigured instances or tables. pub fn new() -> Self { - Self { _marker: std::marker::PhantomData } + Self::default() } - - define_plan_for_field!(plan_dma_controller, DmaCounterInputGen, dma_ops); - define_plan_for_field!(plan_dma_pre_post, DmaCounterInputGen, dma_pre_post_ops); - define_plan_for_field!( - plan_dma_unaligned, - DmaCounterInputGen, - dma_unaligned_rows, - dma_unaligned_inputs - ); - define_plan_for_field!( - plan_dma_64_aligned, - DmaCounterInputGen, - dma_64_aligned_rows, - dma_64_aligned_inputs - ); } impl Planner for DmaPlanner { @@ -168,75 +50,22 @@ impl Planner for DmaPlanner { /// # Panics /// Panics if any counter cannot be downcasted to an `DmaCounter`. fn plan(&self, counters: Vec<(ChunkId, Box)>) -> Vec { - let mut dma_plans: Vec = - Self::plan_dma_controller(&counters, DmaTrace::::NUM_ROWS as u64) - .into_iter() - .map(|(check_point, collect_info)| { - let converted: Box = Box::new(collect_info); - Plan::new( - DmaTrace::::AIRGROUP_ID, - DmaTrace::::AIR_ID, - None, - InstanceType::Instance, - check_point, - Some(converted), - ) - }) - .collect(); - - let pre_post_plans: Vec = - Self::plan_dma_pre_post(&counters, DmaPrePostTrace::::NUM_ROWS as u64) - .into_iter() - .map(|(check_point, collect_info)| { - let converted: Box = Box::new(collect_info); - Plan::new( - DmaPrePostTrace::::AIRGROUP_ID, - DmaPrePostTrace::::AIR_ID, - None, - InstanceType::Instance, - check_point, - Some(converted), - ) - }) - .collect(); - dma_plans.extend(pre_post_plans); - - let aligned_plans: Vec = - Self::plan_dma_64_aligned(&counters, Dma64AlignedTrace::::NUM_ROWS as u64) - .into_iter() - .enumerate() - .map(|(segment_id, (check_point, collect_info))| { - let converted: Box = Box::new(collect_info); - Plan::new( - Dma64AlignedTrace::::AIRGROUP_ID, - Dma64AlignedTrace::::AIR_ID, - Some(SegmentId(segment_id)), - InstanceType::Instance, - check_point, - Some(converted), - ) - }) - .collect(); - dma_plans.extend(aligned_plans); - - let unaligned_plans: Vec = - Self::plan_dma_unaligned(&counters, DmaUnalignedTrace::::NUM_ROWS as u64) - .into_iter() - .enumerate() - .map(|(segment_id, (check_point, collect_info))| { - let converted: Box = Box::new(collect_info); - Plan::new( - DmaUnalignedTrace::::AIRGROUP_ID, - DmaUnalignedTrace::::AIR_ID, - Some(SegmentId(segment_id)), - InstanceType::Instance, - check_point, - Some(converted), - ) - }) - .collect(); - dma_plans.extend(unaligned_plans); - - dma_plans + // Calculate total counters by summing all DmaCounterInputGen instances + let mut dma_strategy = DmaStrategy::::default(); + let _plans = dma_strategy.calculate(counters); + let mut plans: Vec = Vec::new(); + for (air_id, segments) in _plans.into_iter() { + for (segment_id, (check_point, collect_info)) in segments.into_iter().enumerate() { + plans.push(Plan::new( + ZISK_AIRGROUP_ID, + air_id, + Some(SegmentId(segment_id)), + InstanceType::Instance, + check_point.clone(), + Some(Box::new(collect_info)), + )); + } + } + plans } } diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs index 3b04aa360..2ed8e280a 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs @@ -6,30 +6,30 @@ use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; use rayon::{ - iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}, + iter::{IndexedParallelIterator, ParallelIterator}, slice::{ParallelSlice, ParallelSliceMut}, }; -use zisk_pil::{DMA_PRE_POST_TABLE_ID, DMA_PRE_POST_TABLE_SIZE, DUAL_RANGE_BYTE_ID}; +use zisk_core::zisk_ops::ZiskOp; +use zisk_pil::{ + DMA_BYTE_CMP_TABLE_ID, DMA_PRE_POST_TABLE_ID, DMA_PRE_POST_TABLE_SIZE, DUAL_RANGE_BYTE_ID, +}; #[cfg(feature = "packed")] -pub use zisk_pil::{DmaPrePostTracePacked, DmaPrePostTraceRowPacked}; +pub use zisk_pil::{ + DmaPrePostTracePacked as DmaPrePostTrace, DmaPrePostTraceRowPacked as DmaPrePostTraceRow, +}; #[cfg(not(feature = "packed"))] pub use zisk_pil::{DmaPrePostTrace, DmaPrePostTraceRow}; -#[cfg(feature = "packed")] -type DmaPrePostTraceRowType = DmaPrePostTraceRowPacked; -#[cfg(feature = "packed")] -type DmaPrePostTraceType = DmaPrePostTracePacked; - -#[cfg(not(feature = "packed"))] -type DmaPrePostTraceRowType = DmaPrePostTraceRow; -#[cfg(not(feature = "packed"))] -type DmaPrePostTraceType = DmaPrePostTrace; - -use crate::{DmaPrePostInput, DmaPrePostRom}; +use crate::{DmaPrePostInput, DmaPrePostModule, DmaPrePostRom}; use precompiles_helpers::DmaInfo; +// Type aliases to simplify complex types +type MultTable = Vec>; +type PrePostAndByteCmpTables = (MultTable, MultTable); +type GlobalMultiplicities = (PrePostAndByteCmpTables, MultTable); + /// The `DmaPrePostSM` struct encapsulates the logic of the DmaPrePost State Machine. pub struct DmaPrePostSM { /// Reference to the PIL2 standard library. @@ -38,6 +38,9 @@ pub struct DmaPrePostSM { /// Range checks ID's pre_post_table_id: usize, + /// Table to verify byte comparison + byte_cmp_table_id: usize, + /// Dual Byte Range checks dual_range_byte_id: usize, } @@ -52,10 +55,13 @@ impl DmaPrePostSM { std: std.clone(), dual_range_byte_id: std .get_virtual_table_id(DUAL_RANGE_BYTE_ID) - .expect("Failed to get tabl eDUAL_RANGE_BYTE ID ID"), + .expect("Failed to get table DUAL_RANGE_BYTE indentifer"), + byte_cmp_table_id: std + .get_virtual_table_id(DMA_BYTE_CMP_TABLE_ID) + .expect("Failed to get table DMA_BYTE_CMP_TABLE indentifier"), pre_post_table_id: std .get_virtual_table_id(DMA_PRE_POST_TABLE_ID) - .expect("Failed to get table DMA_PRE_POST_TABLE_ID ID ID"), + .expect("Failed to get table DMA_PRE_POST_TABLE identifier"), }) } @@ -68,13 +74,21 @@ impl DmaPrePostSM { pub fn process_slice( &self, input: &DmaPrePostInput, - trace: &mut DmaPrePostTraceRowType, + trace: &mut DmaPrePostTraceRow, pre_post_table_mul: &mut [u64], + byte_cmp_table_mul: &mut [u64], local_dual_range_byte_mul: &mut [u64], ) { + let is_memcmp = input.op == ZiskOp::DMA_MEMCMP || input.op == ZiskOp::DMA_XMEMCMP; + let is_memcpy = input.op == ZiskOp::DMA_MEMCPY || input.op == ZiskOp::DMA_XMEMCPY; + let is_memset = input.op == ZiskOp::DMA_XMEMSET; + let is_inputcpy = input.op == ZiskOp::DMA_INPUTCPY; + let load_src = is_memcpy || is_memcmp; + let dst_offset = input.dst & 0x07; - let src_offset = input.src & 0x07; + let src_offset = if load_src { input.src & 0x07 } else { 0 }; let is_pre = dst_offset > 0; + let step = input.step; let dst64 = input.dst >> 3; let src64 = input.src >> 3; @@ -84,6 +98,7 @@ impl DmaPrePostSM { trace.set_src64(src64); trace.set_dst_offset(dst_offset as u8); trace.set_src_offset(src_offset as u8); + trace.set_is_post(!is_pre); let count = if is_pre { DmaInfo::get_pre_count(input.encoded) @@ -92,55 +107,71 @@ impl DmaPrePostSM { }; trace.set_count(count as u8); - trace.set_enabled(true); + + trace.set_sel_memcpy(is_memcpy); + trace.set_sel_memset(is_memset); + trace.set_sel_inputcpy(is_inputcpy); + trace.set_sel_memcmp(is_memcmp); + + let fill_byte = DmaInfo::get_fill_byte(input.encoded); + if is_memset { + trace.set_fill_byte(fill_byte); + } let second_read = (src_offset as usize + count) > 8; //println!("SECOND_READ: {second_read}"); trace.set_enabled_second_read(second_read); let mut value = input.src_values[0]; - let mut bytes = [0u8; 24]; - - bytes[0] = value as u8; - bytes[1] = (value >> 8) as u8; - bytes[2] = (value >> 16) as u8; - bytes[3] = (value >> 24) as u8; - bytes[4] = (value >> 32) as u8; - bytes[5] = (value >> 40) as u8; - bytes[6] = (value >> 48) as u8; - bytes[7] = (value >> 56) as u8; + let mut rb = [0u8; 16]; + let mut pb = [0u8; 8]; - local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; - local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; - local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; - local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + if is_memset { + for rb in rb.iter_mut() { + *rb = fill_byte; + } + } else { + rb[0] = value as u8; + rb[1] = (value >> 8) as u8; + rb[2] = (value >> 16) as u8; + rb[3] = (value >> 24) as u8; + rb[4] = (value >> 32) as u8; + rb[5] = (value >> 40) as u8; + rb[6] = (value >> 48) as u8; + rb[7] = (value >> 56) as u8; - if second_read { - value = input.src_values[1]; - bytes[8] = value as u8; - bytes[9] = (value >> 8) as u8; - bytes[10] = (value >> 16) as u8; - bytes[11] = (value >> 24) as u8; - bytes[12] = (value >> 32) as u8; - bytes[13] = (value >> 40) as u8; - bytes[14] = (value >> 48) as u8; - bytes[15] = (value >> 56) as u8; local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; - } else { - local_dual_range_byte_mul[0] += 4; + + if second_read { + value = input.src_values[1]; + rb[8] = value as u8; + rb[9] = (value >> 8) as u8; + rb[10] = (value >> 16) as u8; + rb[11] = (value >> 24) as u8; + rb[12] = (value >> 32) as u8; + rb[13] = (value >> 40) as u8; + rb[14] = (value >> 48) as u8; + rb[15] = (value >> 56) as u8; + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + } else { + local_dual_range_byte_mul[0] += 4; + } } value = input.dst_pre_value; - bytes[16] = value as u8; - bytes[17] = (value >> 8) as u8; - bytes[18] = (value >> 16) as u8; - bytes[19] = (value >> 24) as u8; - bytes[20] = (value >> 32) as u8; - bytes[21] = (value >> 40) as u8; - bytes[22] = (value >> 48) as u8; - bytes[23] = (value >> 56) as u8; + pb[0] = value as u8; + pb[1] = (value >> 8) as u8; + pb[2] = (value >> 16) as u8; + pb[3] = (value >> 24) as u8; + pb[4] = (value >> 32) as u8; + pb[5] = (value >> 40) as u8; + pb[6] = (value >> 48) as u8; + pb[7] = (value >> 56) as u8; local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; @@ -171,98 +202,109 @@ impl DmaPrePostSM { trace.set_write_value(2, write_value_23 as u32); trace.set_write_value(3, (write_value_23 >> 32) as u32); - trace.set_selb(0, (mask & 0x0000_0000_0000_00FF) != 0); - trace.set_selb(1, (mask & 0x0000_0000_0000_FF00) != 0); - trace.set_selb(2, (mask & 0x0000_0000_00FF_0000) != 0); - trace.set_selb(3, (mask & 0x0000_0000_FF00_0000) != 0); - trace.set_selb(4, (mask & 0x0000_00FF_0000_0000) != 0); - trace.set_selb(5, (mask & 0x0000_FF00_0000_0000) != 0); - trace.set_selb(6, (mask & 0x00FF_0000_0000_0000) != 0); - trace.set_selb(7, (mask & 0xFF00_0000_0000_0000) != 0); + trace.set_sb(0, (mask & 0x0000_0000_0000_00FF) != 0); + trace.set_sb(1, (mask & 0x0000_0000_0000_FF00) != 0); + trace.set_sb(2, (mask & 0x0000_0000_00FF_0000) != 0); + trace.set_sb(3, (mask & 0x0000_0000_FF00_0000) != 0); + trace.set_sb(4, (mask & 0x0000_00FF_0000_0000) != 0); + trace.set_sb(5, (mask & 0x0000_FF00_0000_0000) != 0); + trace.set_sb(6, (mask & 0x00FF_0000_0000_0000) != 0); + trace.set_sb(7, (mask & 0xFF00_0000_0000_0000) != 0); - for (index, byte) in bytes.iter().enumerate() { + for (index, byte) in rb.iter().enumerate() { // println!("PRE-POST bytes[{index}]: 0x{byte:02X}"); - trace.set_bytes(index, *byte); + trace.set_rb(index, *byte); + } + for (index, byte) in pb.iter().enumerate() { + // println!("PRE-POST bytes[{index}]: 0x{byte:02X}"); + trace.set_pb(index, *byte); } - trace.set_selread(0, selr_value == 0); - trace.set_selread(1, selr_value == 1); - trace.set_selread(2, selr_value == 2); - trace.set_selread(3, selr_value == 3); - trace.set_selread(4, selr_value == 4); - trace.set_selread(5, selr_value == 5); - trace.set_selread(6, selr_value == 6); - - // println!("PRE-POST write_value: 0x{write_value_01:016X} 0x{write_value_23:016X}"); + trace.set_selr(0, selr_value == 0); + trace.set_selr(1, selr_value == 1); + trace.set_selr(2, selr_value == 2); + trace.set_selr(3, selr_value == 3); + trace.set_selr(4, selr_value == 4); + trace.set_selr(5, selr_value == 5); + trace.set_selr(6, selr_value == 6); + + let table_row = if is_memcmp { + let result = DmaInfo::get_memcmp_res_as_u64(input.encoded); + let is_negative = DmaInfo::is_memcmp_negative(input.encoded); + let is_nz = result != 0; + trace.set_memcmp_result_is_negative(is_negative); + trace.set_memcmp_result_nz(is_nz); + let abs_diff_dst_src = if is_negative { (!result).wrapping_add(1) } else { result }; + assert!(abs_diff_dst_src <= 0xFF); + let abs_diff_dst_src = abs_diff_dst_src as u8; + trace.set_abs_diff_dst_src(abs_diff_dst_src); + + // the index of different byte determines the factor + let dst_index = dst_offset as usize + count - 1; + if is_negative { + // implies that count > 0 + if count < 5 { + trace.set_diff_factor(0, F::ORDER_U64 - (1 << (8 * dst_index))); + trace.set_diff_factor(1, 0); + } else { + trace.set_diff_factor(0, 0); + trace.set_diff_factor(1, F::ORDER_U64 - (1 << (8 * (dst_index - 4)))); + } + } else if is_nz { + if count < 5 { + trace.set_diff_factor(0, 1 << (8 * dst_index)); + trace.set_diff_factor(1, 0); + } else { + trace.set_diff_factor(0, 0); + trace.set_diff_factor(1, 1 << (8 * (dst_index - 4))); + } + } + + // calculate the contribution to byte_cmp_table multiplicity + if is_nz { + let last_dst_byte = pb[dst_index]; + let row_byte_cmp_table = if is_negative { + assert!( + abs_diff_dst_src >= last_dst_byte, + "abs_diff_dst_src: {abs_diff_dst_src} last_dst_byte: 0x{last_dst_byte:02X} result: 0x{result:016X} S:{step}", + ); + last_dst_byte as usize * 255 + (abs_diff_dst_src + last_dst_byte) as usize - 1 + } else { + assert!( + abs_diff_dst_src <= last_dst_byte, + "abs_diff_dst_src: {abs_diff_dst_src} last_dst_byte: 0x{last_dst_byte:02X} result: 0x{result:016X} S:{step}", + ); + last_dst_byte as usize * 255 + (last_dst_byte - abs_diff_dst_src) as usize + }; + // println!("\x1B[1;41mBYTE_CMP_TABLE[{row_byte_cmp_table}] abs_diff_dst_src: {abs_diff_dst_src} last_dst_byte: 0x{last_dst_byte:02X} is_negative:{is_negative} result: 0x{result:016X} S:{step}\x1B[0m"); + byte_cmp_table_mul[row_byte_cmp_table] += 1; + } + DmaPrePostRom::get_row( + dst_offset as usize, + src_offset as usize, + count, + is_nz, + is_negative, + true, + ) + } else { + DmaPrePostRom::get_row( + dst_offset as usize, + src_offset as usize, + count, + false, + false, + load_src, + ) + }; - let table_row = DmaPrePostRom::get_row(dst_offset as usize, src_offset as usize, count); - // println!("PRE-POST-ROM [{table_row}] dst_offset: {dst_offset} src_offset: {src_offset} count: {count}"); pre_post_table_mul[table_row] += 1; - - // println!("DMA_PRE_POST: bytes={bytes:?} selr_value={selr_value} mask=0x{mask:016X}"); - // println!( - // "DMA_PRE_POST: read_value_01=0x{read_value_01:016X} read_value_23=0x{read_value_23:016X}" - // ); - // println!("DMA_PRE_POST: write_value_xx=[0x{write_value_01:016X},0x{write_value_23:016X}] dst_offset={dst_offset} src_offset={src_offset}"); - // println!( - // "DMA_PRE_POST: selb={:?}", - // [ - // ((mask & 0x0000_0000_0000_00FF) != 0) as u8, - // ((mask & 0x0000_0000_0000_FF00) != 0) as u8, - // ((mask & 0x0000_0000_00FF_0000) != 0) as u8, - // ((mask & 0x0000_0000_FF00_0000) != 0) as u8, - // ((mask & 0x0000_00FF_0000_0000) != 0) as u8, - // ((mask & 0x0000_FF00_0000_0000) != 0) as u8, - // ((mask & 0x00FF_0000_0000_0000) != 0) as u8, - // ((mask & 0xFF00_0000_0000_0000) != 0) as u8 - // ] - // ); - // println!( - // "DMA_PRE_POST: selread={:?}", - // [ - // (selr_value == 0) as u8, - // (selr_value == 1) as u8, - // (selr_value == 2) as u8, - // (selr_value == 3) as u8, - // (selr_value == 4) as u8, - // (selr_value == 5) as u8, - // (selr_value == 6) as u8, - // (selr_value == 7) as u8 - // ] - // ); } +} - /// Processes a slice of operation data, updating the trace. - /// - /// # Arguments - /// * `trace` - A mutable reference to the Dma trace. - /// * `input` - The operation data to process. - #[inline(always)] - pub fn process_empty_slice(&self, trace: &mut DmaPrePostTraceRowType) { - trace.set_main_step(0); - trace.set_dst64(0); - trace.set_src64(0); - trace.set_dst_offset(0); - trace.set_src_offset(0); - for index in 0..7 { - trace.set_selread(index, false); - } - - trace.set_dst_offset_gt_src_offset(false); - trace.set_count(0); - trace.set_enabled(false); - trace.set_enabled_second_read(false); - - for index in 0..24 { - trace.set_bytes(index, 0); - } - for index in 0..8 { - trace.set_selb(index, false); - } - trace.set_write_value(0, 0); - trace.set_write_value(1, 0); - trace.set_write_value(2, 0); - trace.set_write_value(3, 0); +impl DmaPrePostModule for DmaPrePostSM { + fn get_name(&self) -> &'static str { + "dma_pre_post" } /// Computes the witness for a series of inputs and produces an `AirInstance`. @@ -273,12 +315,12 @@ impl DmaPrePostSM { /// /// # Returns /// An `AirInstance` containing the computed witness data. - pub fn compute_witness( + fn compute_witness( &self, inputs: &[Vec], trace_buffer: Vec, ) -> ProofmanResult> { - let mut trace = DmaPrePostTraceType::::new_from_vec(trace_buffer)?; + let mut trace = DmaPrePostTrace::::new_from_vec_zeroes(trace_buffer)?; let num_rows = trace.num_rows(); let total_inputs: usize = inputs.iter().map(|inputs| inputs.len()).sum(); @@ -302,16 +344,15 @@ impl DmaPrePostSM { let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); // Process in chunks to allow per-chunk local multiplicities arrays - let (global_pre_post_table_mul, global_dual_range_byte_mul): ( - Vec>, - Vec>, - ) = flat_inputs + let ((global_pre_post_table_mul, global_byte_cmp_table_mul), global_dual_range_byte_mul): GlobalMultiplicities = + flat_inputs .par_chunks(chunk_size) .zip(trace_rows.par_chunks_mut(chunk_size)) .map(|(input_chunk, trace_chunk)| { // Local array shared by this chunk let mut local_pre_post_table_mul = vec![0u64; DMA_PRE_POST_TABLE_SIZE]; let mut local_dual_range_byte_mul = vec![0u64; 1 << 16]; + let mut local_byte_cmp_table_mul = vec![0u64; 256 * 255]; // Sum all local arrays into a global one for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { @@ -319,29 +360,27 @@ impl DmaPrePostSM { input, trace_row, &mut local_pre_post_table_mul, + &mut local_byte_cmp_table_mul, &mut local_dual_range_byte_mul, - ); + ) } - (local_pre_post_table_mul, local_dual_range_byte_mul) + // Return nested tuple for unzip + ((local_pre_post_table_mul, local_byte_cmp_table_mul), local_dual_range_byte_mul) }) - .collect(); - + .unzip(); for pre_post_table_mul in global_pre_post_table_mul.iter() { // println!("PRE_POST_TABLE_MUL {:?}", pre_post_table_mul); self.std.inc_virtual_rows_ranged(self.pre_post_table_id, pre_post_table_mul); } - for dual_range_byte_mul in global_dual_range_byte_mul.iter() { - self.std.inc_virtual_rows_ranged(self.dual_range_byte_id, dual_range_byte_mul); + for byte_cmp_table_mul in global_byte_cmp_table_mul.iter() { + // println!("PRE_POST_TABLE_MUL {:?}", pre_post_table_mul); + self.std.inc_virtual_rows_ranged(self.byte_cmp_table_id, byte_cmp_table_mul); } - if total_inputs < num_rows { - self.process_empty_slice(&mut trace_rows[total_inputs]); - let empty_row = trace_rows[total_inputs]; - trace_rows[total_inputs + 1..].par_iter_mut().for_each(|row| { - *row = empty_row; - }); + for dual_range_byte_mul in global_dual_range_byte_mul.iter() { + self.std.inc_virtual_rows_ranged(self.dual_range_byte_id, dual_range_byte_mul); } let from_trace = FromTrace::new(&mut trace); timer_stop_and_log_trace!(DMA_PRE_POST_TRACE); diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs index 2f5d2344a..47329f6ff 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs @@ -3,20 +3,25 @@ use std::any::Any; -use zisk_common::{BusDevice, BusId, CollectCounter, OPERATION_BUS_ID, OP_TYPE}; -use zisk_core::ZiskOperationType; +use precompiles_helpers::DmaInfo; +use zisk_common::{BusDevice, BusId, ChunkId, DMA_ENCODED, OP, OPERATION_BUS_ID, OP_TYPE}; +use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; -use crate::DmaPrePostInput; +use crate::{DmaCollectCounters, DmaCollectorRoutingLog, DmaPrePostInput}; pub struct DmaPrePostCollector { + pub chunk_id: ChunkId, /// Collected inputs for witness computation. pub inputs: Vec, + /// Routing log for debugging and tracking collection operations. + pub rlog: DmaCollectorRoutingLog, + /// The number of operations to collect. - pub num_operations: u64, + pub num_inputs: u64, /// Helper to skip instructions based on the plan's configuration. - pub collect_counter: CollectCounter, + pub collect_counters: DmaCollectCounters, } impl DmaPrePostCollector { @@ -25,16 +30,18 @@ impl DmaPrePostCollector { /// # Arguments /// /// * `bus_id` - The connected bus ID. - /// * `num_operations` - The number of operations to collect. + /// * `num_inputs` - The number of inputs to collect. /// * `collect_skipper` - The helper to skip instructions based on the plan's configuration. /// /// # Returns - /// A new `ArithInstanceCollector` instance initialized with the provided parameters. - pub fn new(num_operations: u64, collect_counter: CollectCounter) -> Self { + /// A new `DmaPrePostCollector` instance initialized with the provided parameters. + pub fn new(chunk_id: ChunkId, num_inputs: u64, collect_counters: DmaCollectCounters) -> Self { Self { - inputs: Vec::with_capacity(num_operations as usize), - num_operations, - collect_counter, + chunk_id, + inputs: Vec::with_capacity(num_inputs as usize), + num_inputs, + collect_counters, + rlog: DmaCollectorRoutingLog::new(chunk_id), } } @@ -53,27 +60,54 @@ impl DmaPrePostCollector { pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); - if self.inputs.len() == self.num_operations as usize { - return false; + if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + return true; + } + + if self.inputs.len() == self.num_inputs as usize { + return self.rlog.log_discard_cond(false, 1, data, false); } - if data[OP_TYPE] != ZiskOperationType::Dma as u64 { + let op = data[OP] as u8; + let encoded = data[DMA_ENCODED]; + if DmaInfo::is_direct(encoded) { + if op == ZiskOp::DMA_MEMCMP || op == ZiskOp::DMA_XMEMCMP { + // We need to collect all memcmp/memcpy operations for the pre/post processing. + panic!("Direct memcmp/memcpy operations are not supported"); + } + self.rlog.log_discard(2, data); return true; } - // println!( - // "DmaPrePostCollector::process_data {} {:?}", - // DmaInfo::to_string(data[DMA_ENCODED]), - // self.collect_counter - // ); - let rows = DmaPrePostInput::get_count(data); - let res = self.collect_counter.should_process(rows as u32); - // println!("DmaPrePostCollector::process_data2 {} {:?}", rows, res); - if let Some((skip, max_count)) = res { - self.inputs.extend(DmaPrePostInput::from(data, data_ext, skip, max_count)); + let rows = DmaInfo::get_pre_writes(encoded); + if rows == 0 { + self.rlog.log_discard(3, data); + return true; + } + + if let Some((skip, max_count, _)) = self.collect_counters.should_collect(rows as u64, op) { + self.rlog.log_collect(rows, data); + if op == ZiskOp::DMA_XMEMSET { + self.inputs.extend(DmaPrePostInput::from_memset(data, data_ext, skip, max_count)); + } else { + self.inputs.extend(DmaPrePostInput::from(data, data_ext, skip, max_count)); + } + } else { + self.rlog.log_discard(10, data); } - // println!("DmaPrePostCollector::process_data3 input.len()={}", self.inputs.len()); - self.inputs.len() < self.num_operations as usize + self.rlog.log_discard_cond(self.inputs.len() < self.num_inputs as usize, 13, data, true) + } + + pub fn get_debug_info(&self) -> String { + #[cfg(feature = "save_dma_collectors")] + return format!( + "CC|{}|{}|{}\n", + self.chunk_id, + self.inputs.len(), + self.collect_counters.get_debug_info(), + ) + &self.rlog.get_debug_info(); + #[cfg(not(feature = "save_dma_collectors"))] + String::new() } } diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs index 51f7fef69..bfca2f5b7 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_input.rs @@ -1,23 +1,15 @@ use precompiles_helpers::DmaInfo; -use zisk_common::{A, B, DMA_ENCODED, STEP}; +use zisk_common::{A, B, DMA_ENCODED, OP, STEP}; #[derive(Debug)] pub struct DmaPrePostInput { pub src: u32, pub dst: u32, pub step: u64, - pub encoded: u64, + pub encoded: u64, // contains fill_byte/memcmp_result pub src_values: [u64; 2], pub dst_pre_value: u64, -} -impl std::fmt::Display for DmaPrePostInput { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "DmaPrePostInput {{ src: 0x{:08x}, dst: 0x{:08x}, step: {}, encoded: 0x{:016x} ({}), src_values: [0x{:016x}, 0x{:016x}], dst_pre_value: 0x{:016x} }}", - self.src, self.dst, self.step, self.encoded, DmaInfo::to_string(self.encoded), self.src_values[0], self.src_values[1], self.dst_pre_value - ) - } + pub op: u8, } impl DmaPrePostInput { pub fn get_count(data: &[u64]) -> usize { @@ -27,6 +19,7 @@ impl DmaPrePostInput { } pub fn from(data: &[u64], data_ext: &[u64], skip: u32, max_count: u32) -> Vec { let encoded = data[DMA_ENCODED]; + let op = data[OP] as u8; let mut inputs = Vec::new(); let pre_count = DmaInfo::get_pre_count(encoded); let mut skipped = 0; @@ -48,6 +41,7 @@ impl DmaPrePostInput { 0 }, ], + op, dst_pre_value: data_ext[DmaInfo::get_pre_write_offset(encoded)], }; inputs.push(input); @@ -71,9 +65,84 @@ impl DmaPrePostInput { }, ], dst_pre_value: data_ext[DmaInfo::get_post_write_offset(encoded)], + op, }; inputs.push(input); } inputs } + pub fn from_memset(data: &[u64], data_ext: &[u64], skip: u32, max_count: u32) -> Vec { + let encoded = data[DMA_ENCODED]; + let op = data[OP] as u8; + let mut inputs = Vec::new(); + let pre_count = DmaInfo::get_pre_count(encoded); + let mut skipped = 0; + if pre_count > 0 { + if skipped < skip { + skipped += 1; + } else { + inputs.push(Self { + dst: data[A] as u32, + src: 0, + step: data[STEP], + encoded, + src_values: [0, 0], + op, + dst_pre_value: data_ext[0], + }); + } + } + let post_count = DmaInfo::get_post_count(encoded); + if post_count > 0 && skipped >= skip && max_count as usize > inputs.len() { + let loop_count = DmaInfo::get_loop_count(encoded); + inputs.push(Self { + dst: data[A] as u32 + pre_count as u32 + loop_count as u32 * 8, + src: pre_count as u32 + loop_count as u32 * 8, + step: data[STEP], + encoded, + src_values: [0, 0], + dst_pre_value: data_ext[(pre_count > 0) as usize], + op, + }); + } + inputs + } + + #[cfg(feature = "save_dma_inputs")] + /// Writes a list of DmaPrePostInput instances to a text file with columns separated by |. + /// Path is taken from DEBUG_OUTPUT_PATH environment variable, defaulting to "tmp/". + pub fn dump_to_file(inputs: &[Vec], filename: &str) -> std::io::Result<()> { + use std::io::Write; + let path = std::env::var("DEBUG_OUTPUT_PATH").unwrap_or_else(|_| "tmp/".to_string()); + let full_path = format!("{}{}", path, filename); + + let mut file = std::fs::File::create(&full_path)?; + + // Write header + writeln!( + file, + "{:>8}|{:>10}|{:>10}|{:>14}|{:>18}|{:>18}|{:>4}|src_values", + "pos", "src", "dst", "step", "encoded", "dst_pre_value", "op" + )?; + + // Write data rows + for (pos, input) in inputs.iter().flatten().enumerate() { + let src_values_hex: Vec = + input.src_values.iter().map(|v| format!("0x{:016X}", v)).collect(); + writeln!( + file, + "{:>8}|0x{:08X}|0x{:08X}|{:>14}|0x{:016X}|0x{:016X}|{:>4}|{}", + pos, + input.src, + input.dst, + input.step, + input.encoded, + input.dst_pre_value, + input.op, + src_values_hex.join(",") + )?; + } + + Ok(()) + } } diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs new file mode 100644 index 000000000..8a272c07b --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs @@ -0,0 +1,297 @@ +use std::sync::Arc; + +use fields::PrimeField64; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use rayon::{ + iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}, + slice::{ParallelSlice, ParallelSliceMut}, +}; +use zisk_pil::{DMA_PRE_POST_TABLE_ID, DMA_PRE_POST_TABLE_SIZE, DUAL_RANGE_BYTE_ID}; + +#[cfg(feature = "packed")] +pub use zisk_pil::{ + DmaPrePostInputCpyTracePacked as DmaPrePostInputCpyTrace, + DmaPrePostInputCpyTraceRowPacked as DmaPrePostInputCpyTraceRow, +}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{DmaPrePostInputCpyTrace, DmaPrePostInputCpyTraceRow}; + +use crate::{DmaPrePostInput, DmaPrePostModule, DmaPrePostRom}; +use precompiles_helpers::DmaInfo; + +/// The `DmaPrePostSM` struct encapsulates the logic of the DmaPrePost State Machine. +pub struct DmaPrePostInputCpySM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + pre_post_table_id: usize, + + /// Dual Byte Range checks + dual_range_byte_id: usize, +} + +impl DmaPrePostInputCpySM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaPrePostInputCpySM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + dual_range_byte_id: std + .get_virtual_table_id(DUAL_RANGE_BYTE_ID) + .expect("Failed to get table DUAL_RANGE_BYTE ID"), + pre_post_table_id: std + .get_virtual_table_id(DMA_PRE_POST_TABLE_ID) + .expect("Failed to get table DMA_PRE_POST_TABLE_ID ID"), + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_slice( + &self, + input: &DmaPrePostInput, + trace: &mut DmaPrePostInputCpyTraceRow, + pre_post_table_mul: &mut [u64], + local_dual_range_byte_mul: &mut [u64], + ) { + let dst_offset = input.dst & 0x07; + let is_pre = dst_offset > 0; + + let dst64 = input.dst >> 3; + + trace.set_main_step(input.step); + trace.set_dst64(dst64); + trace.set_dst_offset(dst_offset as u8); + trace.set_is_post(!is_pre); + + let count = if is_pre { + DmaInfo::get_pre_count(input.encoded) + } else { + DmaInfo::get_post_count(input.encoded) + }; + + trace.set_count(count as u8); + // , set_main_step:ubit(36), set_dst64:ubit(29), set_dst_offset:ubit(3), set_count:ubit(3), set_sel_memcpy:bit, set_sel_memcmp:bit, set_memcmp_result_nz:bit, + // set_l_memcmp_result:u32, set_sel_inputcpy:bit, set_sel_memset:bit, set_selr:[bit; 7], set_dst_offset_gt_src_offset:bit, set_src64:ubit(29), set_src_offset:ubit(3), + // set_enabled_second_read:bit, set_rb:[u8; 16], set_pb:[u8; 8], set_sb:[bit; 8], set_last_dst_byte:u8, set_abs_diff_dst_src:u8, set_memcmp_result_is_negative:bit, + // set_diff_factor:[u64; 2], set_bus_write_value:[u32; 2], set_write_value:[u32; 4], + + trace.set_sel_inputcpy(false); + // intermediate: trace.last_dst_byte(0); + + let mut value = input.src_values[0]; + let mut rb = [0u8; 16]; + let mut pb = [0u8; 8]; + + rb[0] = value as u8; + rb[1] = (value >> 8) as u8; + rb[2] = (value >> 16) as u8; + rb[3] = (value >> 24) as u8; + rb[4] = (value >> 32) as u8; + rb[5] = (value >> 40) as u8; + rb[6] = (value >> 48) as u8; + rb[7] = (value >> 56) as u8; + + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + + value = input.dst_pre_value; + pb[0] = value as u8; + pb[1] = (value >> 8) as u8; + pb[2] = (value >> 16) as u8; + pb[3] = (value >> 24) as u8; + pb[4] = (value >> 32) as u8; + pb[5] = (value >> 40) as u8; + pb[6] = (value >> 48) as u8; + pb[7] = (value >> 56) as u8; + + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + + let _mask = 0xFFFF_FFFF_FFFF_FFFFu64 << (dst_offset * 8); + let mask = _mask ^ (_mask << (count * 8)); + + trace.set_sb(0, (mask & 0x0000_0000_0000_00FF) != 0); + trace.set_sb(1, (mask & 0x0000_0000_0000_FF00) != 0); + trace.set_sb(2, (mask & 0x0000_0000_00FF_0000) != 0); + trace.set_sb(3, (mask & 0x0000_0000_FF00_0000) != 0); + trace.set_sb(4, (mask & 0x0000_00FF_0000_0000) != 0); + trace.set_sb(5, (mask & 0x0000_FF00_0000_0000) != 0); + trace.set_sb(6, (mask & 0x00FF_0000_0000_0000) != 0); + trace.set_sb(7, (mask & 0xFF00_0000_0000_0000) != 0); + + for (index, byte) in rb.iter().enumerate() { + // println!("PRE-POST bytes[{index}]: 0x{byte:02X}"); + trace.set_rb(index, *byte); + } + for (index, byte) in pb.iter().enumerate() { + // println!("PRE-POST bytes[{index}]: 0x{byte:02X}"); + trace.set_pb(index, *byte); + } + + let table_row = DmaPrePostRom::get_row(dst_offset as usize, 0, count, false, false, false); + // println!("PRE-POST-ROM [{table_row}] dst_offset: {dst_offset} src_offset: {src_offset} count: {count}"); + pre_post_table_mul[table_row] += 1; + + // println!("DMA_PRE_POST: bytes={bytes:?} selr_value={selr_value} mask=0x{mask:016X}"); + // println!( + // "DMA_PRE_POST: read_value_01=0x{read_value_01:016X} read_value_23=0x{read_value_23:016X}" + // ); + // println!("DMA_PRE_POST: write_value_xx=[0x{write_value_01:016X},0x{write_value_23:016X}] dst_offset={dst_offset} src_offset={src_offset}"); + // println!( + // "DMA_PRE_POST: selb={:?}", + // [ + // ((mask & 0x0000_0000_0000_00FF) != 0) as u8, + // ((mask & 0x0000_0000_0000_FF00) != 0) as u8, + // ((mask & 0x0000_0000_00FF_0000) != 0) as u8, + // ((mask & 0x0000_0000_FF00_0000) != 0) as u8, + // ((mask & 0x0000_00FF_0000_0000) != 0) as u8, + // ((mask & 0x0000_FF00_0000_0000) != 0) as u8, + // ((mask & 0x00FF_0000_0000_0000) != 0) as u8, + // ((mask & 0xFF00_0000_0000_0000) != 0) as u8 + // ] + // ); + // println!( + // "DMA_PRE_POST: selread={:?}", + // [ + // (selr_value == 0) as u8, + // (selr_value == 1) as u8, + // (selr_value == 2) as u8, + // (selr_value == 3) as u8, + // (selr_value == 4) as u8, + // (selr_value == 5) as u8, + // (selr_value == 6) as u8, + // (selr_value == 7) as u8 + // ] + // ); + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_empty_slice(&self, trace: &mut DmaPrePostInputCpyTraceRow) { + trace.set_main_step(0); + trace.set_dst64(0); + trace.set_dst_offset(0); + + trace.set_count(0); + trace.set_sel_inputcpy(false); + // intermediate: trace.set_last_dst_byte(0); + + for index in 0..16 { + trace.set_rb(index, 0); + } + for index in 0..8 { + trace.set_pb(index, 0); + } + for index in 0..8 { + trace.set_sb(index, false); + } + } +} +impl DmaPrePostModule for DmaPrePostInputCpySM { + fn get_name(&self) -> &'static str { + "dma_pre_post_inputcpy" + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = DmaPrePostInputCpyTrace::::new_from_vec(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|inputs| inputs.len()).sum(); + + assert!(total_inputs <= num_rows); + assert!(total_inputs > 0); + + tracing::debug!( + "··· Creating DmaPrePostInputCpy instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_PRE_POST_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + // Calculate optimal chunk size + let num_threads = rayon::current_num_threads(); + let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); + + // Process in chunks to allow per-chunk local multiplicities arrays + let (global_pre_post_table_mul, global_dual_range_byte_mul): ( + Vec>, + Vec>, + ) = flat_inputs + .par_chunks(chunk_size) + .zip(trace_rows.par_chunks_mut(chunk_size)) + .map(|(input_chunk, trace_chunk)| { + // Local array shared by this chunk + let mut local_pre_post_table_mul = vec![0u64; DMA_PRE_POST_TABLE_SIZE]; + let mut local_dual_range_byte_mul = vec![0u64; 1 << 16]; + + // Sum all local arrays into a global one + for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { + self.process_slice( + input, + trace_row, + &mut local_pre_post_table_mul, + &mut local_dual_range_byte_mul, + ); + } + + (local_pre_post_table_mul, local_dual_range_byte_mul) + }) + .collect(); + + for pre_post_table_mul in global_pre_post_table_mul.iter() { + // println!("PRE_POST_TABLE_MUL {:?}", pre_post_table_mul); + self.std.inc_virtual_rows_ranged(self.pre_post_table_id, pre_post_table_mul); + } + + for dual_range_byte_mul in global_dual_range_byte_mul.iter() { + self.std.inc_virtual_rows_ranged(self.dual_range_byte_id, dual_range_byte_mul); + } + + if total_inputs < num_rows { + self.process_empty_slice(&mut trace_rows[total_inputs]); + let empty_row = trace_rows[total_inputs]; + trace_rows[total_inputs + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + } + let from_trace = FromTrace::new(&mut trace); + timer_stop_and_log_trace!(DMA_PRE_POST_TRACE); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs index 2d5dc5090..de664f4d4 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs @@ -4,21 +4,25 @@ //! It manages collected inputs and interacts with the `DmaPrePostSM` to compute witnesses for //! execution plans. -use crate::{DmaCheckPoint, DmaPrePostCollector, DmaPrePostSM}; +#[cfg(feature = "save_dma_inputs")] +use crate::DmaPrePostInput; +use crate::{DmaCheckPoint, DmaPrePostCollector, DmaPrePostModule}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::sync::Arc; use zisk_common::ChunkId; +#[cfg(feature = "save_dma_inputs")] +use zisk_common::SegmentId; use zisk_common::{BusDevice, CheckPoint, Instance, InstanceCtx, InstanceType, PayloadType}; -use zisk_pil::DmaPrePostTrace; +use zisk_pil::{DmaPrePostInputCpyTrace, DmaPrePostMemCpyTrace, DmaPrePostTrace}; /// The `DmaPrePostInstance` struct represents an instance for the DmaPrePost State Machine. /// -/// It encapsulates the `DmaPrePostSM` and its associated context, and it processes input data +/// It encapsulates the `DmaPrePostModule` and its associated context, and it processes input data /// to compute witnesses for the DmaPrePost State Machine. pub struct DmaPrePostInstance { /// DmaPrePost State machine. - dma_sm: Arc>, + module: Arc>, /// Instance context. ictx: InstanceCtx, @@ -35,22 +39,26 @@ impl DmaPrePostInstance { /// # Returns /// A new `DmaPrePostInstance` instance initialized with the provided state machine and /// context. - pub fn new(dma_sm: Arc>, ictx: InstanceCtx) -> Self { - Self { dma_sm, ictx } + pub fn new(module: Arc>, ictx: InstanceCtx) -> Self { + Self { module, ictx } } pub fn build_dma_collector(&self, chunk_id: ChunkId) -> DmaPrePostCollector { - assert_eq!( - self.ictx.plan.air_id, - DmaPrePostTrace::::AIR_ID, + debug_assert!( + [ + DmaPrePostTrace::::AIR_ID, + DmaPrePostMemCpyTrace::::AIR_ID, + DmaPrePostInputCpyTrace::::AIR_ID, + ] + .contains(&self.ictx.plan.air_id), "DmaPrePostInstance: Unsupported air_id: {:?}", self.ictx.plan.air_id ); let meta = self.ictx.plan.meta.as_ref().unwrap(); let collect_info: &DmaCheckPoint = meta.downcast_ref::().unwrap(); - let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; - DmaPrePostCollector::new(num_ops, collect_counter) + let (num_ops, collect_counters) = collect_info.chunks[&chunk_id]; + DmaPrePostCollector::new(chunk_id, num_ops, collect_counters) } } @@ -72,6 +80,15 @@ impl Instance for DmaPrePostInstance { collectors: Vec<(usize, Box>)>, trace_buffer: Vec, ) -> ProofmanResult>> { + #[cfg(feature = "save_dma_collectors")] + let (debug, inputs): (Vec<_>, Vec<_>) = collectors + .into_iter() + .map(|(_, collector)| { + let collector = collector.as_any().downcast::().unwrap(); + (collector.get_debug_info(), collector.inputs) + }) + .unzip(); + #[cfg(not(feature = "save_dma_collectors"))] let inputs: Vec<_> = collectors .into_iter() .map(|(_, collector)| { @@ -79,7 +96,23 @@ impl Instance for DmaPrePostInstance { }) .collect(); - Ok(Some(self.dma_sm.compute_witness(&inputs, trace_buffer)?)) + #[cfg(any(feature = "save_dma_collectors", feature = "save_dma_inputs"))] + let air_instance_id = + _pctx.dctx_find_air_instance_id(self.ictx.plan.global_id.unwrap()).unwrap(); + + #[cfg(feature = "save_dma_collectors")] + save_dma_collectors( + &format!("{}_collector_{air_instance_id:04}.txt", self.module.get_name()), + debug, + )?; + + #[cfg(feature = "save_dma_inputs")] + DmaPrePostInput::dump_to_file( + &inputs, + &format!("{}_inputs_{air_instance_id:04}.txt", self.module.get_name()), + )?; + + Ok(Some(self.module.compute_witness(&inputs, trace_buffer)?)) } /// Retrieves the checkpoint associated with this instance. @@ -108,11 +141,22 @@ impl Instance for DmaPrePostInstance { let meta = self.ictx.plan.meta.as_ref().unwrap(); let collect_info = meta.downcast_ref::().unwrap(); - let (num_ops, collect_counter) = collect_info.chunks[&chunk_id]; - Some(Box::new(DmaPrePostCollector::new(num_ops, collect_counter))) + let (num_ops, collect_counters) = collect_info.chunks[&chunk_id]; + Some(Box::new(DmaPrePostCollector::new(chunk_id, num_ops, collect_counters))) } fn as_any(&self) -> &dyn std::any::Any { self } } + +#[cfg(feature = "save_dma_collectors")] +pub fn save_dma_collectors(filename: &str, debug: Vec) -> std::io::Result<()> { + use std::fs; + + let path = std::env::var("DEBUG_OUTPUT_PATH").unwrap_or_else(|_| "tmp/".to_string()); + let full_path = format!("{}{}", path, filename); + + fs::write(&full_path, debug.join("\n"))?; + Ok(()) +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_memcpy.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_memcpy.rs new file mode 100644 index 000000000..accd847dc --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_memcpy.rs @@ -0,0 +1,304 @@ +use std::sync::Arc; + +use fields::PrimeField64; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +use rayon::{ + iter::{IndexedParallelIterator, ParallelIterator}, + slice::{ParallelSlice, ParallelSliceMut}, +}; +use zisk_pil::{DMA_PRE_POST_TABLE_ID, DMA_PRE_POST_TABLE_SIZE, DUAL_RANGE_BYTE_ID}; + +#[cfg(feature = "packed")] +pub use zisk_pil::{ + DmaPrePostMemCpyTracePacked as DmaPrePostMemCpyTrace, + DmaPrePostMemCpyTraceRowPacked as DmaPrePostMemCpyTraceRow, +}; + +#[cfg(not(feature = "packed"))] +pub use zisk_pil::{DmaPrePostMemCpyTrace, DmaPrePostMemCpyTraceRow}; + +use crate::{DmaPrePostInput, DmaPrePostModule, DmaPrePostRom}; +use precompiles_helpers::DmaInfo; + +/// The `DmaPrePostMemCpySM` struct encapsulates the logic of the DmaPrePost State Machine. +pub struct DmaPrePostMemCpySM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Range checks ID's + pre_post_table_id: usize, + + /// Dual Byte Range checks + dual_range_byte_id: usize, +} + +impl DmaPrePostMemCpySM { + /// Creates a new Dma State Machine instance. + /// + /// # Returns + /// A new `DmaPrePostMemCpySM` instance. + pub fn new(std: Arc>) -> Arc { + Arc::new(Self { + std: std.clone(), + dual_range_byte_id: std + .get_virtual_table_id(DUAL_RANGE_BYTE_ID) + .expect("Failed to get table DUAL_RANGE_BYTE ID"), + pre_post_table_id: std + .get_virtual_table_id(DMA_PRE_POST_TABLE_ID) + .expect("Failed to get table DMA_PRE_POST_TABLE_ID ID"), + }) + } + + /// Processes a slice of operation data, updating the trace. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Dma trace. + /// * `input` - The operation data to process. + #[inline(always)] + pub fn process_slice( + &self, + input: &DmaPrePostInput, + trace: &mut DmaPrePostMemCpyTraceRow, + pre_post_table_mul: &mut [u64], + local_dual_range_byte_mul: &mut [u64], + ) { + let dst_offset = input.dst & 0x07; + let src_offset = input.src & 0x07; + let is_pre = dst_offset > 0; + + let dst64 = input.dst >> 3; + let src64 = input.src >> 3; + + trace.set_main_step(input.step); + trace.set_dst64(dst64); + trace.set_src64(src64); + trace.set_dst_offset(dst_offset as u8); + trace.set_src_offset(src_offset as u8); + trace.set_is_post(!is_pre); + + let count = if is_pre { + DmaInfo::get_pre_count(input.encoded) + } else { + DmaInfo::get_post_count(input.encoded) + }; + + trace.set_count(count as u8); + // , set_main_step:ubit(36), set_dst64:ubit(29), set_dst_offset:ubit(3), set_count:ubit(3), set_sel_memcpy:bit, set_sel_memcmp:bit, set_memcmp_result_nz:bit, + // set_l_memcmp_result:u32, set_sel_inputcpy:bit, set_sel_memset:bit, set_selr:[bit; 7], set_dst_offset_gt_src_offset:bit, set_src64:ubit(29), set_src_offset:ubit(3), + // set_enabled_second_read:bit, set_rb:[u8; 16], set_pb:[u8; 8], set_sb:[bit; 8], set_last_dst_byte:u8, set_abs_diff_dst_src:u8, set_memcmp_result_is_negative:bit, + // set_diff_factor:[u64; 2], set_bus_write_value:[u32; 2], set_write_value:[u32; 4], + + trace.set_sel_memcpy(true); + // intermediate: trace.last_dst_byte(0); + let second_read = (src_offset as usize + count) > 8; + //println!("SECOND_READ: {second_read}"); + trace.set_enabled_second_read(second_read); + + let mut value = input.src_values[0]; + let mut rb = [0u8; 16]; + let mut pb = [0u8; 8]; + + rb[0] = value as u8; + rb[1] = (value >> 8) as u8; + rb[2] = (value >> 16) as u8; + rb[3] = (value >> 24) as u8; + rb[4] = (value >> 32) as u8; + rb[5] = (value >> 40) as u8; + rb[6] = (value >> 48) as u8; + rb[7] = (value >> 56) as u8; + + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + + // println!("DUAL_RANGE_BYTE_1({:08X})", (value & 0xFFFF)); + // println!("DUAL_RANGE_BYTE_1({:08X})", ((value >> 16) & 0xFFFF)); + // println!("DUAL_RANGE_BYTE_1({:08X})", ((value >> 32) & 0xFFFF)); + // println!("DUAL_RANGE_BYTE_1({:08X})", ((value >> 48) & 0xFFFF)); + + if second_read { + value = input.src_values[1]; + rb[8] = value as u8; + rb[9] = (value >> 8) as u8; + rb[10] = (value >> 16) as u8; + rb[11] = (value >> 24) as u8; + rb[12] = (value >> 32) as u8; + rb[13] = (value >> 40) as u8; + rb[14] = (value >> 48) as u8; + rb[15] = (value >> 56) as u8; + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + // println!("DUAL_RANGE_BYTE_2({:08X})", (value & 0xFFFF)); + // println!("DUAL_RANGE_BYTE_2({:08X})", ((value >> 16) & 0xFFFF)); + // println!("DUAL_RANGE_BYTE_2({:08X})", ((value >> 32) & 0xFFFF)); + // println!("DUAL_RANGE_BYTE_2({:08X})", ((value >> 48) & 0xFFFF)); + } else { + local_dual_range_byte_mul[0] += 4; + } + + value = input.dst_pre_value; + pb[0] = value as u8; + pb[1] = (value >> 8) as u8; + pb[2] = (value >> 16) as u8; + pb[3] = (value >> 24) as u8; + pb[4] = (value >> 32) as u8; + pb[5] = (value >> 40) as u8; + pb[6] = (value >> 48) as u8; + pb[7] = (value >> 56) as u8; + + local_dual_range_byte_mul[(value & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 16) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 32) & 0xFFFF) as usize] += 1; + local_dual_range_byte_mul[((value >> 48) & 0xFFFF) as usize] += 1; + + // println!("DUAL_RANGE_BYTE_3({:08X})", (value & 0xFFFF)); + // println!("DUAL_RANGE_BYTE_3({:08X})", ((value >> 16) & 0xFFFF)); + // println!("DUAL_RANGE_BYTE_3({:08X})", ((value >> 32) & 0xFFFF)); + // println!("DUAL_RANGE_BYTE_3({:08X})", ((value >> 48) & 0xFFFF)); + + let selr_value = if dst_offset > src_offset { + trace.set_dst_offset_gt_src_offset(true); + dst_offset - src_offset + } else { + trace.set_dst_offset_gt_src_offset(false); + src_offset - dst_offset + }; + + let _mask = 0xFFFF_FFFF_FFFF_FFFFu64 << (dst_offset * 8); + let mask = _mask ^ (_mask << (count * 8)); + + trace.set_sb(0, (mask & 0x0000_0000_0000_00FF) != 0); + trace.set_sb(1, (mask & 0x0000_0000_0000_FF00) != 0); + trace.set_sb(2, (mask & 0x0000_0000_00FF_0000) != 0); + trace.set_sb(3, (mask & 0x0000_0000_FF00_0000) != 0); + trace.set_sb(4, (mask & 0x0000_00FF_0000_0000) != 0); + trace.set_sb(5, (mask & 0x0000_FF00_0000_0000) != 0); + trace.set_sb(6, (mask & 0x00FF_0000_0000_0000) != 0); + trace.set_sb(7, (mask & 0xFF00_0000_0000_0000) != 0); + + for (index, byte) in rb.iter().enumerate() { + // println!("PRE-POST bytes[{index}]: 0x{byte:02X}"); + trace.set_rb(index, *byte); + } + for (index, byte) in pb.iter().enumerate() { + // println!("PRE-POST bytes[{index}]: 0x{byte:02X}"); + trace.set_pb(index, *byte); + } + + trace.set_selr(0, selr_value == 0); + trace.set_selr(1, selr_value == 1); + trace.set_selr(2, selr_value == 2); + trace.set_selr(3, selr_value == 3); + trace.set_selr(4, selr_value == 4); + trace.set_selr(5, selr_value == 5); + trace.set_selr(6, selr_value == 6); + + // println!("PRE-POST write_value: 0x{write_value_01:016X} 0x{write_value_23:016X}"); + + let table_row = DmaPrePostRom::get_row( + dst_offset as usize, + src_offset as usize, + count, + false, + false, + true, + ); + // println!("PRE-POST-ROM [{table_row}] dst_offset: {dst_offset} src_offset: {src_offset} count: {count}"); + pre_post_table_mul[table_row] += 1; + } +} +impl DmaPrePostModule for DmaPrePostMemCpySM { + fn get_name(&self) -> &'static str { + "dma_pre_post_memcpy" + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut trace = DmaPrePostMemCpyTrace::::new_from_vec_zeroes(trace_buffer)?; + let num_rows = trace.num_rows(); + + let total_inputs: usize = inputs.iter().map(|inputs| inputs.len()).sum(); + + assert!(total_inputs <= num_rows); + assert!(total_inputs > 0); + + tracing::debug!( + "··· Creating DmaPrePostMemCpy instance [{total_inputs} / {num_rows} rows filled {:.2}%]", + total_inputs as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(DMA_PRE_POST_TRACE); + + // Split the dma_trace.buffer into slices matching each inner vector’s length. + let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + let trace_rows = trace.buffer.as_mut_slice(); + + // Calculate optimal chunk size + let num_threads = rayon::current_num_threads(); + let chunk_size = std::cmp::max(1, flat_inputs.len() / num_threads); + + // Process in chunks to allow per-chunk local multiplicities arrays + let (global_pre_post_table_mul, global_dual_range_byte_mul): ( + Vec>, + Vec>, + ) = flat_inputs + .par_chunks(chunk_size) + .zip(trace_rows.par_chunks_mut(chunk_size)) + .map(|(input_chunk, trace_chunk)| { + // Local array shared by this chunk + let mut local_pre_post_table_mul = vec![0u64; DMA_PRE_POST_TABLE_SIZE]; + let mut local_dual_range_byte_mul = vec![0u64; 1 << 16]; + + // Sum all local arrays into a global one + for (input, trace_row) in input_chunk.iter().zip(trace_chunk.iter_mut()) { + self.process_slice( + input, + trace_row, + &mut local_pre_post_table_mul, + &mut local_dual_range_byte_mul, + ); + } + + (local_pre_post_table_mul, local_dual_range_byte_mul) + }) + .collect(); + + for pre_post_table_mul in global_pre_post_table_mul.iter() { + // println!("PRE_POST_TABLE_MUL {:?}", pre_post_table_mul); + self.std.inc_virtual_rows_ranged(self.pre_post_table_id, pre_post_table_mul); + } + + for dual_range_byte_mul in global_dual_range_byte_mul.iter() { + self.std.inc_virtual_rows_ranged(self.dual_range_byte_id, dual_range_byte_mul); + } + + /* + if total_inputs < num_rows { + self.process_empty_slice(&mut trace_rows[total_inputs]); + let empty_row = trace_rows[total_inputs]; + trace_rows[total_inputs + 1..].par_iter_mut().for_each(|row| { + *row = empty_row; + }); + }*/ + let from_trace = FromTrace::new(&mut trace); + timer_stop_and_log_trace!(DMA_PRE_POST_TRACE); + Ok(AirInstance::new_from_trace(from_trace)) + } +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_module.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_module.rs new file mode 100644 index 000000000..e30441178 --- /dev/null +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_module.rs @@ -0,0 +1,11 @@ +use crate::DmaPrePostInput; +use proofman_common::{AirInstance, ProofmanResult}; + +pub trait DmaPrePostModule: Send + Sync { + fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult>; + fn get_name(&self) -> &'static str; +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs index 0ca2bf430..b11fe283f 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_rom.rs @@ -5,19 +5,43 @@ pub enum DmaPrePostRom {} impl DmaPrePostRom { // Table generated from pil const TABLE_OFFSETS: [usize; 64] = [ - 0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98, 105, 112, 118, 124, 130, 136, - 142, 148, 154, 160, 165, 170, 175, 180, 185, 190, 195, 200, 204, 208, 212, 216, 220, 224, - 228, 232, 235, 238, 241, 244, 247, 250, 253, 256, 258, 260, 262, 264, 266, 268, 270, 272, - 273, 274, 275, 276, 277, 278, 279, + 0, 32, 64, 96, 128, 160, 192, 224, 256, 284, 312, 340, 368, 396, 424, 452, 480, 504, 528, + 552, 576, 600, 624, 648, 672, 692, 712, 732, 752, 772, 792, 812, 832, 848, 864, 880, 896, + 912, 928, 944, 960, 972, 984, 996, 1008, 1020, 1032, 1044, 1056, 1064, 1072, 1080, 1088, + 1096, 1104, 1112, 1120, 1124, 1128, 1132, 1136, 1140, 1144, 1148, ]; + #[allow(dead_code)] - pub fn get_row_from_encoded(encoded: u64) -> usize { + pub fn get_row_from_encoded( + encoded: u64, + memcmp_result_nz: bool, + memcmp_result_is_neg: bool, + load_src: bool, + ) -> usize { let src_offset = DmaInfo::get_src_offset(encoded); let dst_offset = DmaInfo::get_dst_offset(encoded); let count = DmaInfo::get_count(encoded); - Self::get_row(dst_offset, src_offset, count) + Self::get_row( + dst_offset, + src_offset, + count, + memcmp_result_nz, + memcmp_result_is_neg, + load_src, + ) } - pub fn get_row(dst_offset: usize, src_offset: usize, count: usize) -> usize { - Self::TABLE_OFFSETS[dst_offset * 8 + src_offset] + count - 1 + pub fn get_row( + dst_offset: usize, + src_offset: usize, + count: usize, + memcmp_result_nz: bool, + memcmp_result_is_neg: bool, + load_src: bool, + ) -> usize { + debug_assert!(!memcmp_result_is_neg || memcmp_result_nz); + debug_assert!(load_src || (!memcmp_result_is_neg && !memcmp_result_nz)); + Self::TABLE_OFFSETS[dst_offset * 8 + src_offset] + + (count - 1) * 4 + + if load_src { memcmp_result_is_neg as usize + memcmp_result_nz as usize } else { 3 } } } diff --git a/precompiles/dma/src/dma_pre_post/mod.rs b/precompiles/dma/src/dma_pre_post/mod.rs index 9e8132ecc..3808e8179 100644 --- a/precompiles/dma/src/dma_pre_post/mod.rs +++ b/precompiles/dma/src/dma_pre_post/mod.rs @@ -2,11 +2,17 @@ mod dma_pre_post; mod dma_pre_post_collector; mod dma_pre_post_input; +mod dma_pre_post_inputcpy; mod dma_pre_post_instance; +mod dma_pre_post_memcpy; +mod dma_pre_post_module; mod dma_pre_post_rom; pub use dma_pre_post::*; pub use dma_pre_post_collector::*; pub use dma_pre_post_input::*; +pub use dma_pre_post_inputcpy::*; pub use dma_pre_post_instance::*; +pub use dma_pre_post_memcpy::*; +pub use dma_pre_post_module::*; pub use dma_pre_post_rom::*; diff --git a/precompiles/dma/src/dma_strategy.rs b/precompiles/dma/src/dma_strategy.rs new file mode 100644 index 000000000..3b1ff187b --- /dev/null +++ b/precompiles/dma/src/dma_strategy.rs @@ -0,0 +1,604 @@ +//! The `DmaPlanner` module defines a planner for generating execution plans specific to +//! arithmetic operations. +//! +//! It organizes execution plans for both regular instances and table instances, +//! leveraging arithmetic operation counts and metadata to construct detailed plans. + +use core::panic; +use std::fmt; + +use crate::{ + DmaCheckPoint, DmaCounterInputGen, DmaInstancesBuilder, DMA_64_ALIGNED_INPUTS_OFFSET, + DMA_64_ALIGNED_OFFSET, DMA_COUNTER_INPUTCPY, DMA_COUNTER_MEMCMP, DMA_COUNTER_MEMCPY, + DMA_COUNTER_MEMCPY_8, DMA_COUNTER_MEMSET, DMA_COUNTER_MEMSET_8, DMA_COUNTER_OPS, + DMA_COUNTER_OPS_EXT, DMA_INPUT_GEN_COUNTERS, DMA_OFFSET, DMA_PRE_POST_OFFSET, + DMA_UNALIGNED_INPUTS_OFFSET, DMA_UNALIGNED_OFFSET, +}; + +use fields::PrimeField64; +use zisk_common::{BusDeviceMetrics, BusDeviceMode, CheckPoint, ChunkId}; +use zisk_pil::DmaUnalignedTrace; +#[cfg(not(feature = "packed"))] +use zisk_pil::{ + Dma64AlignedInputCpyTrace, Dma64AlignedMemCpyTrace, Dma64AlignedMemSetTrace, + Dma64AlignedMemTrace, Dma64AlignedTrace, DmaInputCpyTrace, DmaMemCpyTrace, + DmaPrePostInputCpyTrace, DmaPrePostMemCpyTrace, DmaPrePostTrace, DmaTrace, +}; + +#[cfg(feature = "packed")] +use zisk_pil::{ + Dma64AlignedTracePacked as Dma64AlignedTrace, DmaInputCpyTracePacked as DmaInputCpyTrace, + DmaMemCpyTracePacked as DmaMemCpyTrace, DmaMemCpyTracePacked as DmaMemCpyTrace, + DmaPrePostTracePacked as DmaPrePostTrace, DmaTracePacked as DmaTrace, + DmaTraceRowPacked as DmaTraceRow, DmaUnalignedTracePacked as DmaUnalignedTrace, +}; + +#[derive(Debug, Default, Clone)] +pub struct DmaInstances { + // memcpy: memcpy ==> full + // memcmp: full + // memset: full + // inputcpy: input_cpy ==> full + pub full: usize, + pub memcpy: usize, + pub inputcpy: usize, + pub rows_memcpy_to_full: usize, + pub rows_inputcpy_to_full: usize, +} + +impl fmt::Display for DmaInstances { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + " full {:>3}\n \ + memcpy {:>3} {:>6} → full\n \ + inputcpy {:>3} {:>6} → full\n", + self.full, + self.memcpy, + self.rows_memcpy_to_full, + self.inputcpy, + self.rows_inputcpy_to_full + ) + } +} + +#[derive(Debug, Default, Clone)] +pub struct Dma64AlignedInstances { + // memcpy: memcpy ==> mem ==> full + // memcmp: mem ==> full + // memset: memset ==> mem ==> full + // inputcpy: input_cpy ==> full + pub full: usize, + pub memcpy: usize, + pub inputcpy: usize, + pub mem: usize, + pub memset: usize, + pub rows_memcpy_to_mem: usize, + pub rows_memcpy_to_full: usize, + pub rows_inputcpy_to_full: usize, + pub rows_memset_to_mem: usize, + pub rows_memset_to_full: usize, + pub rows_memcmp_to_full: usize, +} + +impl fmt::Display for Dma64AlignedInstances { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + " full {:>3}\n \ + memcpy {:>3} {:>6} → mem {:>6} → full\n \ + inputcpy {:>3} {:>6} → full\n \ + mem {:>3}\n \ + memset {:>3} {:>6} → mem {:>6} → full\n \ + memcmp - {:>6} → full\n", + self.full, + self.memcpy, + self.rows_memcpy_to_mem, + self.rows_memcpy_to_full, + self.inputcpy, + self.rows_inputcpy_to_full, + self.mem, + self.memset, + self.rows_memset_to_mem, + self.rows_memset_to_full, + self.rows_memcmp_to_full + ) + } +} + +/// The `DmaStrategy` struct organizes execution plans for arithmetic instances and tables. +/// +/// It allows adding metadata about instances and tables and generates plans +/// based on the provided counters. +#[derive(Default)] +pub struct DmaStrategy { + pub dma: DmaInstances, + pub dma_pre_post: DmaInstances, + pub dma_64_aligned: Dma64AlignedInstances, + pub dma_unaligned: usize, + _marker: std::marker::PhantomData, +} + +impl fmt::Display for DmaStrategy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "────────────────────────────────────────── DMA\n\ + {}\ + ───────────────────────────────── DMA_PRE_POST\n\ + {}\ + ─────────────────────────────── DMA_64_ALIGNED\n\ + {}\ + ──────────────────────────────── DMA_UNALIGNED\n \ + full {:>3}\n\n", + self.dma, self.dma_pre_post, self.dma_64_aligned, self.dma_unaligned, + ) + } +} + +impl DmaStrategy { + /// Creates a new `DmaStrategy`. + /// + /// # Returns + /// A new `DmaStrategy` instance with no preconfigured instances or tables. + pub fn new() -> Self { + Self::default() + } + + // define_plan_for_field!(plan_dma_controller, DmaCounterInputGen, dma_ops); + // define_plan_for_field!(plan_dma_pre_post, DmaCounterInputGen, dma_pre_post_ops); + // define_plan_for_field!( + // plan_dma_unaligned, + // DmaCounterInputGen, + // dma_unaligned_rows, + // dma_unaligned_inputs + // ); + // define_plan_for_field!( + // plan_dma_64_aligned, + // DmaCounterInputGen, + // dma_64_aligned_rows, + // dma_64_aligned_inputs + // ); + + fn calculate_totals( + &self, + counters: &Vec<(ChunkId, Box)>, + ) -> DmaCounterInputGen { + let mut totals = DmaCounterInputGen::new(BusDeviceMode::Counter); + for (_, counter) in counters.iter() { + let counter = (**counter).as_any().downcast_ref::().unwrap(); + for i in 0..DMA_INPUT_GEN_COUNTERS { + totals.counters[i] += counter.counters[i]; + } + } + totals + } + + const DMA_ROWS: usize = DmaTrace::::NUM_ROWS; + const DMA_MEMCPY_ROWS: usize = DmaMemCpyTrace::::NUM_ROWS; + const DMA_INPUTCPY_ROWS: usize = DmaInputCpyTrace::::NUM_ROWS; + const DMA_PRE_POST_ROWS: usize = DmaPrePostTrace::::NUM_ROWS; + const DMA_PRE_POST_MEMCPY_ROWS: usize = DmaPrePostMemCpyTrace::::NUM_ROWS; + const DMA_PRE_POST_INPUTCPY_ROWS: usize = DmaPrePostInputCpyTrace::::NUM_ROWS; + const DMA_64_ALIGNED_ROWS: usize = Dma64AlignedTrace::::NUM_ROWS; + const DMA_64_ALIGNED_MEMCPY_ROWS: usize = Dma64AlignedMemCpyTrace::::NUM_ROWS; + const DMA_64_ALIGNED_MEMSET_ROWS: usize = Dma64AlignedMemSetTrace::::NUM_ROWS; + const DMA_64_ALIGNED_INPUTCPY_ROWS: usize = Dma64AlignedInputCpyTrace::::NUM_ROWS; + const DMA_64_ALIGNED_MEM_ROWS: usize = Dma64AlignedMemTrace::::NUM_ROWS; + const DMA_UNALIGNED_ROWS: usize = DmaUnalignedTrace::::NUM_ROWS; + // Dma + // DmaMemCpy + // DmaInputCpy + pub fn calculate_dma_strategy( + rows: &[usize], + rows_x_full_instance: usize, + rows_x_memcpy_instance: usize, + rows_x_inputcpy_instance: usize, + info: &mut DmaInstances, + ) { + let rows_full = rows[DMA_COUNTER_MEMSET] + rows[DMA_COUNTER_MEMCMP]; + let rows_memcpy = rows[DMA_COUNTER_MEMCPY]; + let rows_inputcpy = rows[DMA_COUNTER_INPUTCPY]; + + info.full = rows_full.div_ceil(rows_x_full_instance); + info.memcpy = rows_memcpy.div_ceil(rows_x_memcpy_instance); + info.inputcpy = rows_inputcpy.div_ceil(rows_x_inputcpy_instance); + + let remain_dma = rows_full % rows_x_full_instance; + let available_on_dma = if rows_full == 0 { 0 } else { rows_x_full_instance - remain_dma }; + let remain_dma_memcpy = rows_memcpy % rows_x_memcpy_instance; + let remain_dma_inputcpy = rows_inputcpy % rows_x_inputcpy_instance; + let remain = remain_dma_memcpy + remain_dma_inputcpy; + + if remain <= available_on_dma { + if remain_dma_memcpy > 0 { + info.memcpy -= 1; + info.rows_memcpy_to_full = remain_dma_memcpy; + } + if remain_dma_inputcpy > 0 { + info.inputcpy -= 1; + info.rows_inputcpy_to_full = remain_dma_inputcpy; + } + } else if remain_dma_memcpy <= available_on_dma { + if remain_dma_memcpy > 0 { + info.memcpy -= 1; + info.rows_memcpy_to_full = remain_dma_memcpy; + } + } else if remain_dma_inputcpy <= available_on_dma { + if remain_dma_inputcpy > 0 { + info.inputcpy -= 1; + info.rows_inputcpy_to_full = remain_dma_inputcpy; + } + } else if remain_dma_memcpy > 0 + && remain_dma_inputcpy > 0 + && remain <= (available_on_dma + rows_x_full_instance) + { + // COST(Dma) < COST(DmaMemCpy) + COST(DmaInputCpy) + info.memcpy -= 1; + info.inputcpy -= 1; + info.full += 1; + info.rows_memcpy_to_full = remain_dma_memcpy; + info.rows_inputcpy_to_full = remain_dma_inputcpy; + } + } + // DmaPrePost + // DmaPrePostMemCpy + // DmaPrePostInputCpy + // memcpy: memcpy ==> mem ==> full + // memcmp: mem ==> full + // memset: memset ==> mem ==> full + // inputcpy: input_cpy ==> full + + pub fn calculate_dma_64_alignment_strategy(rows: &[usize], info: &mut Dma64AlignedInstances) { + info.full = 0; + info.memcpy = rows[DMA_COUNTER_MEMCPY_8].div_ceil(Self::DMA_64_ALIGNED_MEMCPY_ROWS); + info.memset = rows[DMA_COUNTER_MEMSET_8].div_ceil(Self::DMA_64_ALIGNED_MEMSET_ROWS); + info.inputcpy = rows[DMA_COUNTER_INPUTCPY].div_ceil(Self::DMA_64_ALIGNED_INPUTCPY_ROWS); + + let rows_mem = rows[DMA_COUNTER_MEMCMP]; + info.mem = rows_mem.div_ceil(Self::DMA_64_ALIGNED_ROWS); + // TBO: To Be Optimized + info.rows_inputcpy_to_full = 0; + info.rows_memcpy_to_mem = 0; + info.rows_memcpy_to_full = 0; + info.rows_memset_to_mem = 0; + info.rows_memset_to_full = 0; + info.rows_memcmp_to_full = 0; + } + pub fn calculate_dma_unalignment_strategy(rows: &[usize]) -> usize { + let rows = rows[DMA_COUNTER_MEMCPY] + + rows[DMA_COUNTER_INPUTCPY] + + rows[DMA_COUNTER_MEMSET] + + rows[DMA_COUNTER_MEMCMP]; + rows.div_ceil(Self::DMA_UNALIGNED_ROWS) + } + fn calculate_strategy(&mut self, totals: &DmaCounterInputGen) { + Self::calculate_dma_strategy( + &totals.counters[DMA_OFFSET..DMA_OFFSET + DMA_COUNTER_OPS], + Self::DMA_ROWS, + Self::DMA_MEMCPY_ROWS, + Self::DMA_INPUTCPY_ROWS, + &mut self.dma, + ); + Self::calculate_dma_strategy( + &totals.counters[DMA_PRE_POST_OFFSET..DMA_PRE_POST_OFFSET + DMA_COUNTER_OPS], + Self::DMA_PRE_POST_ROWS, + Self::DMA_PRE_POST_MEMCPY_ROWS, + Self::DMA_PRE_POST_INPUTCPY_ROWS, + &mut self.dma_pre_post, + ); + Self::calculate_dma_64_alignment_strategy( + &totals.counters[DMA_64_ALIGNED_OFFSET..DMA_64_ALIGNED_OFFSET + DMA_COUNTER_OPS_EXT], + &mut self.dma_64_aligned, + ); + self.dma_unaligned = Self::calculate_dma_unalignment_strategy( + &totals.counters[DMA_UNALIGNED_OFFSET..DMA_UNALIGNED_OFFSET + DMA_COUNTER_OPS], + ); + } + // DmaUnaligned => + // Dma64Aligned => decision by chunk + // pub send_memcpy_to_mem: bool, + // pub send_memcpy_to_full: bool, + // pub send_inputcpy_to_full: bool, + // pub send_memset_to_mem: bool, + // pub send_memset_to_full: bool, + // pub send_mem_to_full: bool, + // + // pub rows_memcpy_to_full: usize, + // pub rows_inputcpy_to_full: usize, + + pub fn calculate( + &mut self, + counters: Vec<(ChunkId, Box)>, + ) -> Vec<(usize, Vec<(CheckPoint, DmaCheckPoint)>)> { + let totals: DmaCounterInputGen = self.calculate_totals(&counters); + println!("TOTALS_DEBUG_INFO {:?}", totals); + let totals_debug_info = format!("{}", totals); + println!("TOTALS_DEBUG_INFO_STR {}", totals_debug_info); + + self.calculate_strategy(&totals); + + let mut dma_pre_post_full = DmaInstancesBuilder::new( + "dma_pre_post_full", + self.dma_pre_post.full, + Self::DMA_PRE_POST_ROWS, + ); + let mut dma_pre_post_memcpy = DmaInstancesBuilder::new( + "dma_pre_post_memcpy", + self.dma_pre_post.memcpy, + Self::DMA_PRE_POST_MEMCPY_ROWS, + ); + let mut dma_pre_post_inputcpy = DmaInstancesBuilder::new( + "dma_pre_post_inputcpy", + self.dma_pre_post.inputcpy, + Self::DMA_PRE_POST_INPUTCPY_ROWS, + ); + + let mut dma_full = DmaInstancesBuilder::new("dma_full", self.dma.full, Self::DMA_ROWS); + let mut dma_memcpy = + DmaInstancesBuilder::new("dma_memcpy", self.dma.memcpy, Self::DMA_MEMCPY_ROWS); + let mut dma_inputcpy = + DmaInstancesBuilder::new("dma_inputcpy", self.dma.inputcpy, Self::DMA_INPUTCPY_ROWS); + + let mut dma_64_aligned_full = DmaInstancesBuilder::new( + "dma_64_aligned_full", + self.dma_64_aligned.full, + Self::DMA_64_ALIGNED_ROWS, + ); + let mut dma_64_aligned_memset = DmaInstancesBuilder::new( + "dma_64_aligned_memset", + self.dma_64_aligned.memset, + Self::DMA_64_ALIGNED_MEMSET_ROWS, + ); + let mut dma_64_aligned_memcpy = DmaInstancesBuilder::new( + "dma_64_aligned_memcpy", + self.dma_64_aligned.memcpy, + Self::DMA_64_ALIGNED_MEMCPY_ROWS, + ); + let mut dma_64_aligned_inputcpy = DmaInstancesBuilder::new( + "dma_64_aligned_inputcpy", + self.dma_64_aligned.inputcpy, + Self::DMA_64_ALIGNED_INPUTCPY_ROWS, + ); + let mut dma_64_aligned_mem = DmaInstancesBuilder::new( + "dma_64_aligned_mem", + self.dma_64_aligned.mem, + Self::DMA_64_ALIGNED_MEM_ROWS, + ); + + let mut dma_unaligned = + DmaInstancesBuilder::new("dma_unaligned", self.dma_unaligned, Self::DMA_UNALIGNED_ROWS); + + for (current_chunk, dyn_counter) in counters.iter() { + let counters = + (**dyn_counter).as_any().downcast_ref::().unwrap().counters; + println!( + "\x1B[33;1mProcessing chunk {:?} with counter: {:?}\x1B[0m", + current_chunk, counters + ); + + // DMA + + let rows = counters[DMA_OFFSET + DMA_COUNTER_MEMSET]; + if rows > 0 { + dma_full.add_op_rows(*current_chunk, 0, rows, rows, DMA_COUNTER_MEMSET); + } + + let rows = counters[DMA_OFFSET + DMA_COUNTER_MEMCMP]; + if rows > 0 { + dma_full.add_op_rows(*current_chunk, 0, rows, rows, DMA_COUNTER_MEMCMP); + } + + let mut rows = counters[DMA_OFFSET + DMA_COUNTER_MEMCPY]; + let skip = if rows > 0 && self.dma.rows_memcpy_to_full > 0 { + let rows_applicable = std::cmp::min(rows, self.dma.rows_memcpy_to_full); + dma_full.add_op_rows( + *current_chunk, + 0, + rows_applicable, + rows_applicable, + DMA_COUNTER_MEMCPY, + ); + rows -= rows_applicable; + self.dma.rows_memcpy_to_full -= rows_applicable; + rows_applicable + } else { + 0 + }; + if rows > 0 { + dma_memcpy.add_op_rows(*current_chunk, skip, rows, rows, DMA_COUNTER_MEMCPY); + } + + let mut rows = counters[DMA_OFFSET + DMA_COUNTER_INPUTCPY]; + let skip = if self.dma.rows_inputcpy_to_full > 0 { + let rows_applicable = std::cmp::min(rows, self.dma.rows_inputcpy_to_full); + dma_full.add_op_rows( + *current_chunk, + 0, + rows_applicable, + rows_applicable, + DMA_COUNTER_INPUTCPY, + ); + rows -= rows_applicable; + self.dma.rows_inputcpy_to_full -= rows_applicable; + rows_applicable + } else { + 0 + }; + if rows > 0 { + dma_inputcpy.add_op_rows(*current_chunk, skip, rows, rows, DMA_COUNTER_INPUTCPY); + } + + // DMA_PRE_POST + + let rows = counters[DMA_PRE_POST_OFFSET + DMA_COUNTER_MEMSET]; + if rows > 0 { + dma_pre_post_full.add_op_rows(*current_chunk, 0, rows, rows, DMA_COUNTER_MEMSET); + } + + let rows = counters[DMA_PRE_POST_OFFSET + DMA_COUNTER_MEMCMP]; + if rows > 0 { + dma_pre_post_full.add_op_rows(*current_chunk, 0, rows, rows, DMA_COUNTER_MEMCMP); + } + + let mut rows = counters[DMA_PRE_POST_OFFSET + DMA_COUNTER_MEMCPY]; + let skip = if rows > 0 && self.dma_pre_post.rows_memcpy_to_full > 0 { + let rows_applicable = std::cmp::min(rows, self.dma_pre_post.rows_memcpy_to_full); + println!("DMA_PRE_POST chunk {current_chunk} has {rows} memcpy rows, {rows_applicable} can \ + be sent to full instance [{}]", self.dma_pre_post.rows_memcpy_to_full); + dma_pre_post_full.add_op_rows( + *current_chunk, + 0, + rows_applicable, + rows_applicable, + DMA_COUNTER_MEMCPY, + ); + rows -= rows_applicable; + self.dma_pre_post.rows_memcpy_to_full -= rows_applicable; + rows_applicable + } else { + 0 + }; + if rows > 0 { + println!("DMA_PRE_POST_MEMCPY chunk {current_chunk} has {rows} memcpy rows"); + dma_pre_post_memcpy.add_op_rows( + *current_chunk, + skip, + rows, + rows, + DMA_COUNTER_MEMCPY, + ); + } + + let mut rows = counters[DMA_PRE_POST_OFFSET + DMA_COUNTER_INPUTCPY]; + let skip = if self.dma_pre_post.rows_inputcpy_to_full > 0 { + let rows_applicable = std::cmp::min(rows, self.dma_pre_post.rows_inputcpy_to_full); + dma_pre_post_full.add_op_rows( + *current_chunk, + 0, + rows_applicable, + rows_applicable, + DMA_COUNTER_INPUTCPY, + ); + rows -= rows_applicable; + self.dma_pre_post.rows_inputcpy_to_full -= rows_applicable; + rows_applicable + } else { + 0 + }; + if rows > 0 { + dma_pre_post_inputcpy.add_op_rows( + *current_chunk, + skip, + rows, + rows, + DMA_COUNTER_INPUTCPY, + ); + } + + // DMA_64_ALIGNED + + for op in 0..DMA_COUNTER_OPS { + let inputs = counters[DMA_64_ALIGNED_INPUTS_OFFSET + op]; + match op { + DMA_COUNTER_INPUTCPY => dma_64_aligned_inputcpy.add_op_rows( + *current_chunk, + 0, + counters[DMA_64_ALIGNED_OFFSET + op], + inputs, + op, + ), + DMA_COUNTER_MEMSET => dma_64_aligned_memset.add_op_rows( + *current_chunk, + 0, + counters[DMA_64_ALIGNED_OFFSET + DMA_COUNTER_MEMSET_8], + inputs, + op, + ), + DMA_COUNTER_MEMCMP => dma_64_aligned_mem.add_op_rows( + *current_chunk, + 0, + counters[DMA_64_ALIGNED_OFFSET + op], + inputs, + op, + ), + DMA_COUNTER_MEMCPY => dma_64_aligned_memcpy.add_op_rows( + *current_chunk, + 0, + counters[DMA_64_ALIGNED_OFFSET + DMA_COUNTER_MEMCPY_8], + inputs, + op, + ), + _ => panic!("Unexpected op code {op} in DMA 64 aligned counters"), + }; + } + + // DMA_UNALIGNED + + for op in 0..DMA_COUNTER_OPS { + let rows = counters[DMA_UNALIGNED_OFFSET + op]; + let inputs = counters[DMA_UNALIGNED_INPUTS_OFFSET + op]; + if rows > 0 { + dma_unaligned.add_op_rows(*current_chunk, 0, rows, inputs, op); + } + } + + // println!("chunk {current_chunk} counter: {counters:?}"); + } + + let plans = vec![ + (DmaTrace::::AIR_ID, dma_full.get_plan()), + (DmaMemCpyTrace::::AIR_ID, dma_memcpy.get_plan()), + (DmaInputCpyTrace::::AIR_ID, dma_inputcpy.get_plan()), + (DmaPrePostTrace::::AIR_ID, dma_pre_post_full.get_plan()), + (DmaPrePostMemCpyTrace::::AIR_ID, dma_pre_post_memcpy.get_plan()), + (DmaPrePostInputCpyTrace::::AIR_ID, dma_pre_post_inputcpy.get_plan()), + (Dma64AlignedTrace::::AIR_ID, dma_64_aligned_full.get_plan()), + (Dma64AlignedMemSetTrace::::AIR_ID, dma_64_aligned_memset.get_plan()), + (Dma64AlignedMemCpyTrace::::AIR_ID, dma_64_aligned_memcpy.get_plan()), + (Dma64AlignedInputCpyTrace::::AIR_ID, dma_64_aligned_inputcpy.get_plan()), + (Dma64AlignedMemTrace::::AIR_ID, dma_64_aligned_mem.get_plan()), + (DmaUnalignedTrace::::AIR_ID, dma_unaligned.get_plan()), + ]; + + #[cfg(feature = "save_dma_plans")] + self.save_plans("dma_plans.txt", totals_debug_info, &plans).unwrap(); + + plans + } + + #[cfg(feature = "save_dma_plans")] + fn save_plans( + &self, + filename: &str, + totals_debug_info: String, + plans: &Vec<(usize, Vec<(CheckPoint, DmaCheckPoint)>)>, + ) -> std::io::Result<()> { + let mut debug_info = format!( + "───────────────────────────────────────────────────── TOTALS\n{}\n{}", + totals_debug_info, self + ); + for (air_id, plan) in plans { + if plan.is_empty() { + continue; + } + let title = &format!("{}", get_dma_air_name::(*air_id)); + debug_info += &plan + .iter() + .enumerate() + .map(|(segment_id, (_checkpoint, dma_checkpoint))| { + dma_checkpoint.get_debug_info(title, segment_id as u64) + }) + .collect::>() + .join("\n"); + debug_info += "\n"; + } + use std::fs; + + let path = std::env::var("DEBUG_OUTPUT_PATH").unwrap_or_else(|_| "tmp/".to_string()); + let full_path = format!("{}{}", path, filename); + + fs::write(&full_path, debug_info)?; + Ok(()) + } +} diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs index 43e7a8b02..a1297bba1 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use fields::PrimeField64; -use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator as _}; use crate::DmaUnalignedInput; use pil_std_lib::Std; @@ -85,16 +84,13 @@ impl DmaUnalignedSM { let mut src64 = (input.src >> 3) + input.skip; let mut src_values_index = 0; + let mut seq_end = false; let mut next_value = 0; - // println!( - // "DMA_UNALIGNED INPUT {input:?} count:{count} rows:{rows} dma_info:{}", - // DmaInfo::to_string(input.encoded) - // ); assert!(rows > 0); for (irow, row) in trace.iter_mut().enumerate().take(rows) { row.set_main_step(input.step); - row.set_is_mem_eq(false); + row.set_is_memeq(input.is_mem_eq); row.set_no_last_no_seq_end(count != 0); row.set_previous_seq_end(input.skip == 0 && irow == 0); @@ -182,7 +178,7 @@ impl DmaUnalignedSM { air_values.segment_last_offset = F::ZERO; air_values.last_count_chunk[0] = F::ZERO; air_values.last_count_chunk[1] = F::ZERO; - air_values.segment_last_is_mem_eq = F::ZERO; + air_values.segment_last_is_memeq = F::ZERO; air_values.segment_next_bytes = [F::ZERO; 8]; } else { let last_row = rows - 1; @@ -195,7 +191,7 @@ impl DmaUnalignedSM { let count = trace[last_row].get_count(); air_values.last_count_chunk[0] = F::from_u16(count as u16); air_values.last_count_chunk[1] = F::from_u16((count >> 16) as u16); - air_values.segment_last_is_mem_eq = F::from_bool(trace[last_row].get_is_mem_eq()); + air_values.segment_last_is_memeq = F::from_bool(trace[last_row].get_is_memeq()); for (index, byte) in air_values.segment_next_bytes.iter_mut().enumerate() { *byte = F::from_u8((next_value >> (index * 8)) as u8); } @@ -211,35 +207,8 @@ impl DmaUnalignedSM { /// * `input` - The operation data to process. #[inline(always)] pub fn process_empty_slice(&self, trace: &mut DmaUnalignedTraceRowType) { - trace.set_main_step(0); - trace.set_is_mem_eq(false); - trace.set_no_last_no_seq_end(false); - trace.set_previous_seq_end(true); - - trace.set_dst64(0); - trace.set_src64(0); - - trace.set_offset_2(false); - trace.set_offset_3(false); - trace.set_offset_4(false); - trace.set_offset_5(false); - trace.set_offset_6(false); - trace.set_offset_7(false); - - trace.set_count(0); trace.set_seq_end(true); - - trace.set_read_bytes(0, 0); - trace.set_read_bytes(1, 0); - trace.set_read_bytes(2, 0); - trace.set_read_bytes(3, 0); - trace.set_read_bytes(4, 0); - trace.set_read_bytes(5, 0); - trace.set_read_bytes(6, 0); - trace.set_read_bytes(7, 0); - - trace.set_write_value(0, 0); - trace.set_write_value(1, 0); + trace.set_previous_seq_end(true); } /// Computes the witness for a series of inputs and produces an `AirInstance`. @@ -257,7 +226,7 @@ impl DmaUnalignedSM { is_last_segment: bool, trace_buffer: Vec, ) -> ProofmanResult> { - let mut trace = DmaUnalignedTraceType::::new_from_vec(trace_buffer)?; + let mut trace = DmaUnalignedTraceType::::new_from_vec_zeroes(trace_buffer)?; let num_rows = trace.num_rows(); let total_inputs: usize = inputs @@ -293,8 +262,8 @@ impl DmaUnalignedSM { row_offset += rows_used; } - let padding_rows = num_rows - row_offset; - let last_count = if padding_rows == 0 && !trace_rows[num_rows - 1].get_seq_end() { + let padding_size = num_rows - row_offset; + let last_count = if padding_size == 0 && !trace_rows[num_rows - 1].get_seq_end() { trace_rows[num_rows - 1].get_count() } else { 0 @@ -302,7 +271,7 @@ impl DmaUnalignedSM { self.std.range_check(self.range_16_bits_id, (last_count & 0xFFFF) as i64, 1); self.std.range_check(self.range_16_bits_id, ((last_count >> 16) & 0xFFFF) as i64, 1); - local_dual_byte_table[0] += (padding_rows * 4) as u64; + local_dual_byte_table[0] += (padding_size * 4) as u64; self.std.inc_virtual_rows_ranged(self.dual_range_byte_id, &local_dual_byte_table); air_values.segment_id = F::from_usize(segment_id.into()); @@ -315,7 +284,7 @@ impl DmaUnalignedSM { air_values.segment_previous_src64 = F::ZERO; air_values.segment_previous_main_step = F::ZERO; air_values.segment_previous_count = F::ZERO; - air_values.segment_previous_is_mem_eq = F::ZERO; + air_values.segment_previous_is_memeq = F::ZERO; air_values.segment_previous_offset = F::ZERO; air_values.segment_first_bytes = [F::ZERO; 8]; } else { @@ -324,7 +293,7 @@ impl DmaUnalignedSM { air_values.segment_previous_src64 = F::from_u32(trace_rows[0].get_src64() - 1); air_values.segment_previous_main_step = F::from_u64(trace_rows[0].get_main_step()); air_values.segment_previous_count = F::from_u32(trace_rows[0].get_count() + 1); - air_values.segment_previous_is_mem_eq = F::from_bool(trace_rows[0].get_is_mem_eq()); + air_values.segment_previous_is_memeq = F::from_bool(trace_rows[0].get_is_memeq()); air_values.segment_previous_offset = F::from_u8(DmaInfo::get_loop_src_offset(first_input.encoded)); for (index, byte) in air_values.segment_first_bytes.iter_mut().enumerate() { @@ -333,32 +302,18 @@ impl DmaUnalignedSM { } // padding - if padding_rows > 0 { - air_values.padding_rows = F::from_u32(padding_rows as u32); - self.process_empty_slice(&mut trace_rows[row_offset]); - let empty_row = trace_rows[row_offset]; - trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { - *row = empty_row; - }); + if padding_size > 0 { + air_values.padding_size = F::from_u32(padding_size as u32); + for row in trace_rows.iter_mut().take(num_rows).skip(row_offset) { + self.process_empty_slice(row); + } air_values.segment_last_seq_end = F::ONE; air_values.segment_last_src64 = F::ZERO; air_values.segment_last_dst64 = F::ZERO; air_values.segment_last_main_step = F::ZERO; air_values.segment_last_count = F::ZERO; - air_values.segment_last_is_mem_eq = F::ZERO; + air_values.segment_last_is_memeq = F::ZERO; air_values.segment_next_bytes = [F::ZERO; 8]; - } else { - trace[num_rows - 1].set_no_last_no_seq_end(false); - } - #[cfg(feature = "debug_dma")] - { - println!("TRACE DmaUnalignedSM @{segment_id} [0] {:?}", trace[0]); - println!( - "TRACE DmaUnalignedSM @{segment_id} [{}] {:?}", - num_rows - 1, - trace[num_rows - 1] - ); - println!("TRACE DmaUnalignedSM AIR_VALUES {:?}", air_values); } timer_stop_and_log_trace!(DMA_UNALIGNED_TRACE); let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs index b80ff0f63..006cef5d9 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs @@ -4,20 +4,25 @@ //! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for //! execution plans. -use crate::DmaUnalignedInput; +use crate::{DmaCollectCounters, DmaCollectorRoutingLog, DmaUnalignedInput}; use std::any::Any; -use zisk_common::{BusDevice, BusId, CollectCounter, OPERATION_BUS_ID, OP_TYPE}; -use zisk_core::ZiskOperationType; +use zisk_common::{BusDevice, BusId, ChunkId, OP, OPERATION_BUS_ID, OP_TYPE}; +use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; pub struct DmaUnalignedCollector { /// Collected inputs for witness computation. pub inputs: Vec, + pub chunk_id: ChunkId, + + /// Routing log for debugging and tracking collection operations. + pub rlog: DmaCollectorRoutingLog, + /// The number of operations to collect. pub num_inputs: u64, /// Helper to skip instructions based on the plan's configuration. - pub collect_counter: CollectCounter, + pub collect_counters: DmaCollectCounters, pub trace_offset: usize, pub last_segment_collector: bool, @@ -35,19 +40,25 @@ impl DmaUnalignedCollector { /// # Returns /// A new `DmaUnalignedCollector` instance initialized with the provided parameters. pub fn new( + chunk_id: zisk_common::ChunkId, num_inputs: u64, - collect_counter: CollectCounter, + collect_counters: DmaCollectCounters, last_segment_collector: bool, ) -> Self { Self { inputs: Vec::with_capacity(num_inputs as usize), num_inputs, - collect_counter, + collect_counters, trace_offset: 0, last_segment_collector, + chunk_id, + rlog: DmaCollectorRoutingLog::new(chunk_id), } } + const DMA_UNALIGNED_OPS: [u8; 4] = + [ZiskOp::DMA_MEMCPY, ZiskOp::DMA_XMEMCPY, ZiskOp::DMA_MEMCMP, ZiskOp::DMA_XMEMCMP]; + /// Processes data received on the bus, collecting the inputs necessary for witness computation. /// /// # Arguments @@ -63,32 +74,68 @@ impl DmaUnalignedCollector { pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool { debug_assert!(*bus_id == OPERATION_BUS_ID); - if self.collect_counter.is_final_skip() { - return false; - } - if data[OP_TYPE] != ZiskOperationType::Dma as u64 { return true; } - let rows = DmaUnalignedInput::get_count(data) as u32; + let rows = DmaUnalignedInput::get_count(data) as u64; if rows == 0 { return true; } - if let Some((skip, max_count)) = self.collect_counter.should_process(rows) { + let op = data[OP] as u8; + + if !Self::DMA_UNALIGNED_OPS.contains(&op) { + return true; + } + + if self.inputs.len() == self.num_inputs as usize { + self.collect_counters.debug_assert_is_final_skip(); + return self.rlog.log_discard_cond(false, 1, data, true); + } + + if let Some((skip, max_count, is_final_skip)) = + self.collect_counters.should_collect(rows, op) + { + self.rlog.log_collect(rows as usize, data); self.inputs.push(DmaUnalignedInput::from( data, data_ext, self.trace_offset, skip as usize, max_count as usize, - self.last_segment_collector && self.collect_counter.is_final_skip(), + self.last_segment_collector && is_final_skip, )); self.trace_offset += max_count as usize; + if self.inputs.len() >= self.num_inputs as usize { + self.collect_counters.debug_assert_is_final_skip(); + self.rlog.log_discard(10, data); + return false; + } + } else { + self.rlog.log_discard(11, data); } + true + } - !self.collect_counter.is_final_skip() + /// Returns debug information about the collector's state. + /// + /// When the `save_dma_collectors` feature is enabled, this returns detailed information + /// including chunk ID, number of collected inputs, counter information, and routing log. + /// Otherwise, returns an empty string. + /// + /// # Returns + /// A formatted string with debug information. + pub fn get_debug_info(&self) -> String { + #[cfg(feature = "save_dma_collectors")] + return format!( + "CC|{}|{}|{}\n", + self.chunk_id, + self.inputs.len(), + self.collect_counters.get_debug_info(), + ) + &self.rlog.get_debug_info(); + #[cfg(not(feature = "save_dma_collectors"))] + String::new() } } diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs index ce1761a65..8cc27b3d0 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs @@ -1,5 +1,6 @@ use precompiles_helpers::DmaInfo; -use zisk_common::{A, B, DMA_ENCODED, STEP}; +use zisk_common::{A, B, DMA_ENCODED, OP, STEP}; +use zisk_core::zisk_ops::ZiskOp; #[derive(Debug)] pub struct DmaUnalignedInput { @@ -7,6 +8,7 @@ pub struct DmaUnalignedInput { pub dst: u32, pub is_first_instance_input: bool, pub is_last_instance_input: bool, + pub is_mem_eq: bool, pub trace_offset: u32, // offset inside trace to paralelize pub skip: u32, // inside input how many rows skip pub count: u32, // number of rows used @@ -46,25 +48,25 @@ impl DmaUnalignedInput { is_last_instance_input: bool, ) -> Self { let encoded = data[DMA_ENCODED]; - + let op = data[OP] as u8; + debug_assert!( + op == ZiskOp::DMA_MEMCPY + || op == ZiskOp::DMA_XMEMCPY + || op == ZiskOp::DMA_MEMCMP + || op == ZiskOp::DMA_XMEMCMP, + "Unexpected operation on DmaUnalignedInput 0x{op:02X}", + ); let pre_count = DmaInfo::get_pre_count(encoded) as u32; let data_offset = DmaInfo::get_loop_data_offset(encoded) + skip; // unaligned need an extra row to read part of next bytes let pending_count = DmaInfo::get_loop_count(encoded) + 1 - skip; - let count = std::cmp::min(pending_count, max_count); - if data_offset >= data_ext.len() || (data_offset + count) > data_ext.len() { - println!( - "PROBLEM ON INPUT GENERATION STEP:{} data_ext.len={} src_values[{data_offset}..{}] {}", - data[STEP], - data_ext.len(), - data_offset + count, - DmaInfo::to_string(encoded) - ); - } + let count: usize = std::cmp::min(pending_count, max_count); + // if count not enough to finish unaligned memcpy, add extra source because one row // use next source value let src_values_count = if count < pending_count { count + 1 } else { count }; + let op = data[OP] as u8; assert!(DmaInfo::get_loop_count(encoded) > 0); Self { dst: data[A] as u32 + pre_count, @@ -76,10 +78,63 @@ impl DmaUnalignedInput { skip: skip as u32, count: count as u32, encoded, + is_mem_eq: op == ZiskOp::DMA_MEMCMP || op == ZiskOp::DMA_XMEMCMP, src_values: data_ext[data_offset..data_offset + src_values_count].to_vec(), } } pub fn get_rows(&self) -> usize { DmaInfo::get_loop_count(self.encoded) } + + #[cfg(feature = "save_dma_inputs")] + /// Writes a list of DmaUnalignedInput instances to a text file with columns separated by |. + /// Path is taken from DEBUG_OUTPUT_PATH environment variable, defaulting to "tmp/". + pub fn dump_to_file(inputs: &[Vec], filename: &str) -> std::io::Result<()> { + use std::io::Write; + let path = std::env::var("DEBUG_OUTPUT_PATH").unwrap_or_else(|_| "tmp/".to_string()); + let full_path = format!("{}{}", path, filename); + + let mut file = std::fs::File::create(&full_path)?; + + // Write header + writeln!( + file, + "{:>8}|{:>10}|{:>10}|{:>22}|{:>21}|{:>9}|{:>12}|{:>8}|{:>8}|{:>14}|{:>18}|src_values", + "pos", + "src", + "dst", + "is_first_instance_input", + "is_last_instance_input", + "is_mem_eq", + "trace_offset", + "skip", + "count", + "step", + "encoded" + )?; + + // Write data rows + for (pos, input) in inputs.iter().flatten().enumerate() { + let src_values_hex: Vec = + input.src_values.iter().map(|v| format!("0x{:016X}", v)).collect(); + writeln!( + file, + "{:>8}|0x{:08X}|0x{:08X}|{:>22}|{:>21}|{:>9}|{:>12}|{:>8}|{:>8}|{:>14}|0x{:016X}|{}", + pos, + input.src, + input.dst, + input.is_first_instance_input, + input.is_last_instance_input, + input.is_mem_eq, + input.trace_offset, + input.skip, + input.count, + input.step, + input.encoded, + src_values_hex.join(",") + )?; + } + + Ok(()) + } } diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs index 174a893d0..2d32af07c 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs @@ -4,6 +4,10 @@ //! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for //! execution plans. +#[cfg(feature = "save_dma_collectors")] +use crate::save_dma_collectors; +#[cfg(feature = "save_dma_inputs")] +use crate::DmaUnalignedInput; use crate::{DmaCheckPoint, DmaUnalignedCollector, DmaUnalignedSM}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; @@ -18,7 +22,7 @@ use zisk_pil::DmaUnalignedTrace; /// to compute witnesses for the DmaUnaligned State Machine. pub struct DmaUnalignedInstance { /// Dma state machine. - dma_64_aligned_sm: Arc>, + dma_unaligned_sm: Arc>, /// Instance context. ictx: InstanceCtx, @@ -31,20 +35,20 @@ impl DmaUnalignedInstance { /// Creates a new `DmaUnalignedInstance`. /// /// # Arguments - /// * `dma_64_aligned_sm` - An `Arc`-wrapped reference to the Dma 64 Aligned State Machine. + /// * `dma_unaligned_sm` - An `Arc`-wrapped reference to the Dma Unaligned State Machine. /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. /// * `bus_id` - The bus ID associated with this instance. /// /// # Returns /// A new `DmaUnalignedInstance` instance initialized with the provided state machine and /// context. - pub fn new(dma_64_aligned_sm: Arc>, ictx: InstanceCtx) -> Self { + pub fn new(dma_unaligned_sm: Arc>, ictx: InstanceCtx) -> Self { let is_last_segment = { let meta = ictx.plan.meta.as_ref().unwrap(); let checkpoint = meta.downcast_ref::().unwrap(); checkpoint.is_last_segment }; - Self { dma_64_aligned_sm, ictx, is_last_segment } + Self { dma_unaligned_sm, ictx, is_last_segment } } pub fn build_dma_collector(&self, chunk_id: ChunkId) -> DmaUnalignedCollector { @@ -59,6 +63,7 @@ impl DmaUnalignedInstance { let collect_info = meta.downcast_ref::().unwrap(); let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; DmaUnalignedCollector::new( + chunk_id, num_inputs, collect_counter, Some(chunk_id) == collect_info.last_chunk, @@ -84,16 +89,34 @@ impl Instance for DmaUnalignedInstance { collectors: Vec<(usize, Box>)>, trace_buffer: Vec, ) -> ProofmanResult>> { + #[cfg(feature = "save_dma_collectors")] + let (debug, inputs): (Vec<_>, Vec<_>) = collectors + .into_iter() + .map(|(_, collector)| { + let collector = collector.as_any().downcast::().unwrap(); + (collector.get_debug_info(), collector.inputs) + }) + .unzip(); + #[cfg(not(feature = "save_dma_collectors"))] let inputs: Vec<_> = collectors .into_iter() .map(|(_, collector)| { collector.as_any().downcast::().unwrap().inputs }) .collect(); - // Extract segment id from instance context + let segment_id = self.ictx.plan.segment_id.unwrap(); - Ok(Some(self.dma_64_aligned_sm.compute_witness( + #[cfg(feature = "save_dma_collectors")] + save_dma_collectors(&format!("dma_unaligned_collector_{segment_id:04}.txt"), debug)?; + + #[cfg(feature = "save_dma_inputs")] + DmaUnalignedInput::dump_to_file( + &inputs, + &format!("dma_unaligned_inputs_{segment_id:04}.txt"), + )?; + + Ok(Some(self.dma_unaligned_sm.compute_witness( &inputs, segment_id, self.is_last_segment, @@ -129,6 +152,7 @@ impl Instance for DmaUnalignedInstance { let collect_info = meta.downcast_ref::().unwrap(); let (num_inputs, collect_counter) = collect_info.chunks[&chunk_id]; Some(Box::new(DmaUnalignedCollector::new( + chunk_id, num_inputs, collect_counter, Some(chunk_id) == collect_info.last_chunk, diff --git a/precompiles/dma/src/lib.rs b/precompiles/dma/src/lib.rs index 26d62ace9..1894ebba2 100644 --- a/precompiles/dma/src/lib.rs +++ b/precompiles/dma/src/lib.rs @@ -1,19 +1,41 @@ mod dma; mod dma_64_aligned; mod dma_bus_device; +mod dma_checkpoint; +mod dma_collect_counters; +mod dma_collector_routing_log; +mod dma_common; mod dma_constants; +mod dma_gen_inputcpy_mem_inputs; mod dma_gen_mem_inputs; +mod dma_gen_memcmp_mem_inputs; +mod dma_gen_memcpy_mem_inputs; +mod dma_gen_memset_mem_inputs; +mod dma_instance_info; +mod dma_instances_builder; mod dma_manager; mod dma_planner; mod dma_pre_post; +mod dma_strategy; mod dma_unaligned; pub use dma::*; pub use dma_64_aligned::*; pub use dma_bus_device::*; +pub use dma_checkpoint::*; +pub use dma_collect_counters::*; +pub use dma_collector_routing_log::*; +pub use dma_common::*; pub use dma_constants::*; +pub use dma_gen_inputcpy_mem_inputs::*; pub use dma_gen_mem_inputs::*; +pub use dma_gen_memcmp_mem_inputs::*; +pub use dma_gen_memcpy_mem_inputs::*; +pub use dma_gen_memset_mem_inputs::*; +pub use dma_instance_info::*; +pub use dma_instances_builder::*; pub use dma_manager::*; pub use dma_planner::*; pub use dma_pre_post::*; +pub use dma_strategy::*; pub use dma_unaligned::*; diff --git a/precompiles/helpers/src/dma.rs b/precompiles/helpers/src/dma.rs index a96ada11c..8f8601345 100644 --- a/precompiles/helpers/src/dma.rs +++ b/precompiles/helpers/src/dma.rs @@ -15,32 +15,40 @@ pub struct DmaValues { pub src_offset_after_pre: u64, } -const FAST_ENCODE_TABLE_SIZE: usize = 8 * 8 * 16; +const FAST_ENCODE_TABLE_WO_NEQ_SIZE: usize = 8 * 8 * 16; +const FAST_ENCODE_TABLE_SIZE: usize = FAST_ENCODE_TABLE_WO_NEQ_SIZE * 2; const FAST_ENCODE_TABLE: [u64; FAST_ENCODE_TABLE_SIZE] = generate_fast_encode_table(); const fn generate_fast_encode_table() -> [u64; FAST_ENCODE_TABLE_SIZE] { let mut table = [0u64; FAST_ENCODE_TABLE_SIZE]; // fill table - let mut dst_offset: u64 = 0; - while dst_offset < 8 { - let base_index = dst_offset << 7; - let mut src_offset: u64 = 0; - while src_offset < 8 { - let index = (base_index + (src_offset << 4)) as usize; - let mut count: usize = 0; - while count < 16 { - let value = DmaInfo::encode_memcpy(dst_offset, src_offset, count); - let loop_count = DmaInfo::get_loop_count(value) as u64; - // The table is create to add directly de loop count and after all values - // are correct, for this reason substract de count, because we need diference - // between loop_count (shifted 32) and count (shifted 29) - table[index + count] = ((value & 0x0000_0000_FFFF_FFFF) + (loop_count << 32)) - .wrapping_sub((count as u64) << 29); - count += 1; + let mut neq_index = 0; + while neq_index < 2 { + let neq = neq_index != 0; + let neq_base = 8 * 8 * 16 * neq_index; + let mut dst_offset: u64 = 0; + while dst_offset < 8 { + let base_index = (dst_offset << 7) + neq_base as u64; + let mut src_offset: u64 = 0; + while src_offset < 8 { + let index = (base_index + (src_offset << 4)) as usize; + let mut count: usize = 0; + while count < 16 { + let value = DmaInfo::calculate_encode(dst_offset, src_offset, count, neq); + let loop_count = DmaInfo::get_loop_count(value) as u64; + // The table is create to add directly de loop count and after all values + // are correct, for this reason substract de count, because we need diference + // between loop_count (shifted 32) and count (shifted 29) + table[index + count] = ((value & 0x0000_0007_FFFF_FFFF) + + (loop_count << DmaInfo::DMA_LOOP_COUNT_RS)) + .wrapping_sub((count as u64) << DmaInfo::DMA_LPRE_COUNT_RS); + count += 1; + } + src_offset += 1; } - src_offset += 1; + dst_offset += 1; } - dst_offset += 1; + neq_index += 1; } table } @@ -50,41 +58,167 @@ pub struct DmaInfo {} impl DmaInfo { #[inline(always)] pub fn to_string(encoded: u64) -> String { - format!("loop_count: {}|pre_writes: {}|dst_offset: {}|src_offset: {}|pre_count: {}|post_count: {}|extra_src_reads: {}", + format!("LC:{}|PWR:{}|DO:{}|SO:{}|PRE:{}|POST:{}|ESR:{}|DPRE:{}|DPOS:{}|SIBP:{}|DA:{}|FB:{:02X}", Self::get_loop_count(encoded), Self::get_pre_writes(encoded), Self::get_dst_offset(encoded), Self::get_src_offset(encoded), Self::get_pre_count(encoded), Self::get_post_count(encoded), - Self::get_extra_src_reads(encoded)) + Self::get_extra_src_reads(encoded), + Self::is_double_read_pre(encoded) as usize, + Self::is_double_read_post(encoded) as usize, + Self::get_src64_inc_by_pre(encoded), + Self::dst_is_aligned_with_src(encoded) as usize, + Self::get_fill_byte(encoded)) } #[inline(always)] - pub const fn fast_encode_memcpy(dst: u64, src: u64, count: usize) -> u64 { + pub const fn encode_memcmp_neq(dst: u64, src: u64, count: usize, neq: bool) -> u64 { let table_count = if count >= 16 { count & 0x07 | 0x08 } else { count }; - FAST_ENCODE_TABLE[(((dst & 0x07) << 7) + ((src & 0x07) << 4)) as usize + table_count] - .wrapping_add((count as u64) << 29) + (FAST_ENCODE_TABLE[(((dst & 0x07) << 7) + ((src & 0x07) << 4)) as usize + + table_count + + FAST_ENCODE_TABLE_WO_NEQ_SIZE * neq as usize] + + Self::DMA_REQUIRES_DMA_TEST_MASK) + .wrapping_add((count as u64) << Self::DMA_LPRE_COUNT_RS) + } + + #[inline(always)] + pub const fn encode_memcmp(dst: u64, src: u64, count: usize, result: u64) -> u64 { + let table_count = if count >= 16 { count & 0x07 | 0x08 } else { count }; + (FAST_ENCODE_TABLE[(((dst & 0x07) << 7) + ((src & 0x07) << 4)) as usize + + table_count + + FAST_ENCODE_TABLE_WO_NEQ_SIZE * (result & 0x80 != 0) as usize] + + Self::DMA_REQUIRES_DMA_TEST_MASK + +((result & 0x1FF) << Self::DMA_FILL_BYTE_RS)) + .wrapping_add((count as u64) << Self::DMA_LPRE_COUNT_RS) } + #[inline(always)] pub const fn encode_memcpy(dst: u64, src: u64, count: usize) -> u64 { + let table_count = if count >= 16 { count & 0x07 | 0x08 } else { count }; + FAST_ENCODE_TABLE[(((dst & 0x07) << 7) + ((src & 0x07) << 4)) as usize + table_count] + .wrapping_add((count as u64) << Self::DMA_LPRE_COUNT_RS) + } + + #[inline(always)] + pub const fn encode_inputcpy(dst: u64, count: usize) -> u64 { + let table_count = if count >= 16 { count & 0x07 | 0x08 } else { count }; + FAST_ENCODE_TABLE[((dst & 0x07) << 7) as usize + table_count] + .wrapping_add((count as u64) << Self::DMA_LPRE_COUNT_RS) + } + + #[inline(always)] + pub const fn encode_memset(dst: u64, count: usize, fill_byte: u8) -> u64 { + let table_count = if count >= 16 { count & 0x07 | 0x08 } else { count }; + (FAST_ENCODE_TABLE[((dst & 0x07) << 7) as usize + table_count] + .wrapping_add((count as u64) << Self::DMA_LPRE_COUNT_RS)) + | ((fill_byte as u64) << Self::DMA_FILL_BYTE_RS) + } + + pub const DMA_PRE_COUNT_TEST_MASK: u64 = 0x07; + pub const DMA_PRE_COUNT_MASK: u64 = 0x07; + + pub const DMA_POST_COUNT_RS: u64 = 3; + pub const DMA_POST_COUNT_TEST_MASK: u64 = 0x78; + pub const DMA_POST_COUNT_MASK: u64 = 0x0F; + + pub const DMA_PRE_WRITES_RS: u64 = 7; + pub const DMA_PRE_WRITES_TEST_MASK: u64 = 0x180; + pub const DMA_PRE_WRITES_MASK: u64 = 0x003; + + pub const DMA_DST_OFFSET_RS: u64 = 9; + pub const DMA_DST_OFFSET_TEST_MASK: u64 = 0x0E00; + pub const DMA_DST_OFFSET_MASK: u64 = 0x007; + + pub const DMA_SRC_OFFSET_RS: u64 = 12; + pub const DMA_SRC_OFFSET_TEST_MASK: u64 = 0x70000; + pub const DMA_SRC_OFFSET_MASK: u64 = 0x007; + + pub const DMA_DOUBLE_SRC_PRE_RS: u64 = 15; + pub const DMA_DOUBLE_SRC_PRE_TEST_MASK: u64 = 0x08000; + + pub const DMA_DOUBLE_SRC_POST_RS: u64 = 16; + pub const DMA_DOUBLE_SRC_POST_TEST_MASK: u64 = 0x10000; + + pub const DMA_EXTRA_SRC_READS_RS: u64 = 17; + pub const DMA_EXTRA_SRC_READS_TEST_MASK: u64 = 0x60000; + pub const DMA_EXTRA_SRC_READS_MASK: u64 = 0x00003; + + pub const DMA_SRC64_INC_BY_PRE_RS: u64 = 19; + pub const DMA_SRC64_INC_BY_PRE_TEST_MASK: u64 = 0x80000; + + pub const DMA_UNALIGNED_DST_SRC_RS: u64 = 20; + pub const DMA_UNALIGNED_DST_SRC_TEST_MASK: u64 = 0x100000; + + pub const DMA_FILL_BYTE_RS: u64 = 21; + pub const DMA_FILL_BYTE_TEST_MASK: u64 = 0x1FE00000; + pub const DMA_FILL_BYTE_MASK: u64 = 0x000000FF; + + pub const DMA_FILL_BYTE_SIGN_TEST_MASK: u64 = 0x20000000; + + pub const DMA_LPRE_COUNT_RS: u64 = 32; + pub const DMA_LPRE_COUNT_TEST_MASK: u64 = 0x70000000; + pub const DMA_LPRE_COUNT_MASK: u64 = 0x00000007; + + // the REQUIRES_DMA flag is set after when operation is memcmp where + // dma need to calculate count_eq and verify it. + + pub const DMA_REQUIRES_DMA_RS: u64 = 30; + pub const DMA_REQUIRES_DMA_TEST_MASK: u64 = 0x40000000; + pub const DMA_REQUIRES_DMA_MASK: u64 = 0x00000001; + + pub const DMA_PRE_OR_POST_TEST_MASK: u64 = Self::DMA_PRE_COUNT_TEST_MASK + | Self::DMA_POST_COUNT_TEST_MASK; + + pub const DMA_LOOP_COUNT_RS: u64 = 35; + const DMA_FULL_ALIGNED_MASK: u64 = Self::DMA_PRE_COUNT_TEST_MASK + | Self::DMA_POST_COUNT_TEST_MASK + | Self::DMA_PRE_WRITES_TEST_MASK + | Self::DMA_DST_OFFSET_TEST_MASK + | Self::DMA_SRC_OFFSET_TEST_MASK + | Self::DMA_DOUBLE_SRC_PRE_TEST_MASK + | Self::DMA_DOUBLE_SRC_POST_TEST_MASK + | Self::DMA_EXTRA_SRC_READS_TEST_MASK + | Self::DMA_SRC64_INC_BY_PRE_TEST_MASK + | Self::DMA_UNALIGNED_DST_SRC_TEST_MASK; + + const DMA_DIRECT_MASK: u64 = Self::DMA_FULL_ALIGNED_MASK + | Self::DMA_REQUIRES_DMA_TEST_MASK; + + #[inline(always)] + pub const fn calculate_encode(dst: u64, src: u64, count: usize, neq: bool) -> u64 { // #bits bits // pre_count: 0-7 3 0-2 - // post_count: 0-7 3 3-5 - // pre_writes: 0,1,2 2 6-7 - // dst_offset: 0-7 3 8-10 - // src_offset: 0-7 3 11-13 - // double_src_pre: 0,1 1 14 - // double_src_post: 0,1 1 15 - // extra_src_reads: 0-3 2 16-17 - // src64_inc_by_pre: 1 18 + // post_count: 0-8(*) 4 3-6 (*) memcmp + // pre_writes: 0,1,2 2 7-8 + // dst_offset: 0-7 3 9-11 + // src_offset: 0-7 3 12-14 + // double_src_pre: 0,1 1 15 + // double_src_post: 0,1 1 16 + // extra_src_reads: 0-3 2 17-18 + // src64_inc_by_pre: 1 19 // unaligned_dst_src: 1 20 - // loop_count 32 32-63 + // fill_byte/cmp: 8 21-28 + // cmp_negative: 1 29 + // requires_dma: 1 30 + // (reserved) 1 31 + // lpre_count 3 32-34 + // loop_count 29 35-63 + + // (dst + count) & 0x7 = 7 + // (dst_offset + count) 0x07 = 7 + // + // loop = loop - 1 + // pre_writes = pre_writes + 1 + // post = 8 + // double_src_post = unaligned_dst_src ? 1:0; + // extra_src_reads = extra_src_read + 1 let dst_offset = dst & 0x07; let src_offset = src & 0x07; let count = count as u64; - let (pre_count, loop_count, post_count) = if dst_offset > 0 { + let (pre_count, mut loop_count, mut post_count) = if dst_offset > 0 { let _pre_count = 8 - dst_offset; if _pre_count >= count { (count, 0, 0) @@ -95,112 +229,280 @@ impl DmaInfo { } else { (0, count >> 3, count & 0x07) }; - let pre_writes = (pre_count > 0) as u64 + (post_count > 0) as u64; + let mut pre_writes = (pre_count > 0) as u64 + (post_count > 0) as u64; // let to_src_offset = (src + count - 1) & 0x07; let src_offset_pos = (src_offset + pre_count) & 0x07; - let double_src_post = (src_offset_pos + post_count) > 8; + let mut double_src_post = (src_offset_pos + post_count) > 8; let double_src_pre = (src_offset + pre_count) > 8; - let extra_src_reads = + let mut extra_src_reads = if count == 0 { 0 } else { (((src + count - 1) >> 3) - (src >> 3) + 1) - loop_count }; let src64_inc_by_pre = (pre_count > 0 && (src_offset + pre_count) >= 8) as u64; let unaligned_dst_src = (src_offset != dst_offset) as u64; + if neq && post_count == 0 && loop_count > 0 { + // (dst + count) 0x07 == 7 ==> (dst_offset + count) 0x07 == 7 ==> post_count == 0 + // loop = loop - 1 + // pre_writes = pre_writes + 1 + // post = 8 + // double_src_post = unaligned_dst_src ? 1:0; + // extra_src_reads = extra_src_read + 1 + loop_count = loop_count - 1; + pre_writes = pre_writes + 1; + post_count = 8; + double_src_post = src_offset != dst_offset; + extra_src_reads = extra_src_reads + 1; + } pre_count - | (post_count << 3) - | (pre_writes << 6) - | (dst_offset << 8) - | (src_offset << 11) - | ((double_src_pre as u64) << 14) - | ((double_src_post as u64) << 15) - | (extra_src_reads << 16) - | (src64_inc_by_pre << 18) - | (unaligned_dst_src << 19) - | (pre_count << 29) // optimization to read loop_count * 8 + pre_count - | (loop_count << 32) + | (post_count << Self::DMA_POST_COUNT_RS) + | (pre_writes << Self::DMA_PRE_WRITES_RS) + | (dst_offset << Self::DMA_DST_OFFSET_RS) + | (src_offset << Self::DMA_SRC_OFFSET_RS) + | ((double_src_pre as u64) << Self::DMA_DOUBLE_SRC_PRE_RS) + | ((double_src_post as u64) << Self::DMA_DOUBLE_SRC_POST_RS) + | (extra_src_reads << Self::DMA_EXTRA_SRC_READS_RS) + | (src64_inc_by_pre << Self::DMA_SRC64_INC_BY_PRE_RS) + | (unaligned_dst_src << Self::DMA_UNALIGNED_DST_SRC_RS) + // fill_byte / memcmp is 0 for memcpy << 21 + | (pre_count << Self::DMA_LPRE_COUNT_RS) // optimization to read loop_count * 8 + pre_count + | (loop_count << Self::DMA_LOOP_COUNT_RS) } + #[inline(always)] + pub fn calculate_encode2(dst: u64, src: u64, count: usize, neq: bool) -> u64 { + // #bits bits + // pre_count: 0-7 3 0-2 + // post_count: 0-8(*) 4 3-6 (*) memcmp + // pre_writes: 0,1,2 2 7-8 + // dst_offset: 0-7 3 9-11 + // src_offset: 0-7 3 12-14 + // double_src_pre: 0,1 1 15 + // double_src_post: 0,1 1 16 + // extra_src_reads: 0-3 2 17-18 + // src64_inc_by_pre: 1 19 + // unaligned_dst_src: 1 20 + // fill_byte/cmp: 8 21-28 + // requires_dma: 1 29 + // (reserved) 2 30-31 + // lpre_count 3 32-34 + // loop_count 29 35-63 + + // (dst + count) & 0x7 = 7 + // (dst_offset + count) 0x07 = 7 + // + // loop = loop - 1 + // pre_writes = pre_writes + 1 + // post = 8 + // double_src_post = unaligned_dst_src ? 1:0; + // extra_src_reads = extra_src_read + 1 + + let dst_offset = dst & 0x07; + let src_offset = src & 0x07; + + let count = count as u64; + let (pre_count, mut loop_count, mut post_count) = if dst_offset > 0 { + let _pre_count = 8 - dst_offset; + if _pre_count >= count { + (count, 0, 0) + } else { + let pending = count - _pre_count; + (_pre_count, pending >> 3, pending & 0x07) + } + } else { + (0, count >> 3, count & 0x07) + }; + let mut pre_writes = (pre_count > 0) as u64 + (post_count > 0) as u64; + // let to_src_offset = (src + count - 1) & 0x07; + let src_offset_pos = (src_offset + pre_count) & 0x07; + let mut double_src_post = (src_offset_pos + post_count) > 8; + let double_src_pre = (src_offset + pre_count) > 8; + let mut extra_src_reads = + if count == 0 { 0 } else { (((src + count - 1) >> 3) - (src >> 3) + 1) - loop_count }; + + let src64_inc_by_pre = (pre_count > 0 && (src_offset + pre_count) >= 8) as u64; + let unaligned_dst_src = (src_offset != dst_offset) as u64; + + if neq && post_count == 0 && loop_count > 0 { + // (dst + count) 0x07 == 7 ==> (dst_offset + count) 0x07 == 7 ==> post_count == 0 + // loop = loop - 1 + // pre_writes = pre_writes + 1 + // post = 8 + // double_src_post = unaligned_dst_src ? 1:0; + // extra_src_reads = extra_src_read + 1 + loop_count -= 1; + pre_writes += 1; + post_count = 8; + double_src_post = src_offset != dst_offset; + extra_src_reads += extra_src_reads; + } + pre_count + | (post_count << Self::DMA_POST_COUNT_RS) + | (pre_writes << Self::DMA_PRE_WRITES_RS) + | (dst_offset << Self::DMA_DST_OFFSET_RS) + | (src_offset << Self::DMA_SRC_OFFSET_RS) + | ((double_src_pre as u64) << Self::DMA_DOUBLE_SRC_PRE_RS) + | ((double_src_post as u64) << Self::DMA_DOUBLE_SRC_POST_RS) + | (extra_src_reads << Self::DMA_EXTRA_SRC_READS_RS) + | (src64_inc_by_pre << Self::DMA_SRC64_INC_BY_PRE_RS) + | (unaligned_dst_src << Self::DMA_UNALIGNED_DST_SRC_RS) + // fill_byte / memcmp is 0 for memcpy << 21 + | (pre_count << Self::DMA_LPRE_COUNT_RS) // optimization to read loop_count * 8 + pre_count + | (loop_count << Self::DMA_LOOP_COUNT_RS) + } + + #[inline(always)] pub const fn get_extra_src_reads(encoded: u64) -> usize { - (encoded as usize) >> 16 & 0x03 + (encoded as usize) >> Self::DMA_EXTRA_SRC_READS_RS & Self::DMA_EXTRA_SRC_READS_MASK as usize } + #[inline(always)] pub const fn get_count(encoded: u64) -> usize { Self::get_loop_count(encoded) * 8 + Self::get_pre_count(encoded) + Self::get_post_count(encoded) } + #[inline(always)] pub const fn get_dst_offset(encoded: u64) -> usize { - (encoded as usize >> 8) & 0x07 + (encoded as usize >> Self::DMA_DST_OFFSET_RS) & Self::DMA_DST_OFFSET_MASK as usize } + #[inline(always)] pub const fn get_src_offset(encoded: u64) -> usize { - (encoded as usize >> 11) & 0x07 + (encoded as usize >> Self::DMA_SRC_OFFSET_RS) & Self::DMA_SRC_OFFSET_MASK as usize } + #[inline(always)] pub const fn get_loop_count(encoded: u64) -> usize { - (encoded >> 32) as usize + (encoded >> Self::DMA_LOOP_COUNT_RS) as usize } + #[inline(always)] pub const fn get_pre_writes(encoded: u64) -> usize { - (encoded as usize >> 6) & 0x03 + (encoded as usize >> Self::DMA_PRE_WRITES_RS) & Self::DMA_PRE_WRITES_MASK as usize } + #[inline(always)] pub const fn is_double_read_pre(encoded: u64) -> bool { - encoded & (1 << 14) != 0 + encoded & Self::DMA_DOUBLE_SRC_PRE_TEST_MASK != 0 } + #[inline(always)] pub const fn is_double_read_post(encoded: u64) -> bool { - encoded & (1 << 15) != 0 + encoded & Self::DMA_DOUBLE_SRC_POST_TEST_MASK != 0 } + #[inline(always)] pub const fn get_pre_count(encoded: u64) -> usize { - (encoded as usize) & 0x07 + (encoded as usize) & Self::DMA_PRE_COUNT_MASK as usize } + #[inline(always)] pub const fn get_post_count(encoded: u64) -> usize { - (encoded as usize >> 3) & 0x07 + (encoded as usize >> Self::DMA_POST_COUNT_RS) & Self::DMA_POST_COUNT_MASK as usize } + #[inline(always)] pub const fn get_pre(encoded: u64) -> u8 { (Self::get_pre_count(encoded) > 0) as u8 + Self::is_double_read_pre(encoded) as u8 } + #[inline(always)] pub const fn get_post(encoded: u64) -> u8 { (Self::get_post_count(encoded) > 0) as u8 + Self::is_double_read_post(encoded) as u8 } + #[inline(always)] pub const fn get_src64_inc_by_pre(encoded: u64) -> usize { - (encoded as usize >> 18) & 0x01 + (encoded & Self::DMA_SRC64_INC_BY_PRE_TEST_MASK != 0) as usize } + #[inline(always)] pub const fn get_loop_data_offset(encoded: u64) -> usize { let pre_count = Self::get_pre_count(encoded); Self::get_pre_writes(encoded) + (pre_count > 0 && (Self::get_src_offset(encoded) + pre_count) >= 8) as usize } + #[inline(always)] pub const fn get_loop_src_offset(encoded: u64) -> u8 { (Self::get_src_offset(encoded) + Self::get_pre_count(encoded)) as u8 & 0x07 } + #[inline(always)] pub const fn get_src_size(encoded: u64) -> usize { Self::get_loop_count(encoded) + Self::get_extra_src_reads(encoded) } + #[inline(always)] pub const fn get_data_size(encoded: u64) -> usize { Self::get_pre_writes(encoded) + Self::get_src_size(encoded) } + #[inline(always)] pub const fn get_post_data_offset(encoded: u64) -> usize { Self::get_pre_writes(encoded) + Self::get_src_size(encoded) - (Self::is_double_read_post(encoded) as usize + 1) } + #[inline(always)] pub const fn get_pre_write_offset(_encoded: u64) -> usize { 0 } + #[inline(always)] pub const fn get_post_write_offset(encoded: u64) -> usize { (Self::get_pre_count(encoded) != 0) as usize } + #[inline(always)] pub const fn get_pre_data_offset(encoded: u64) -> usize { Self::get_pre_writes(encoded) } + #[inline(always)] + pub const fn dst_is_unaligned_with_src(encoded: u64) -> bool { + (encoded & Self::DMA_UNALIGNED_DST_SRC_TEST_MASK) != 0 + } + #[inline(always)] + pub const fn dst_is_aligned_with_src(encoded: u64) -> bool { + (encoded & Self::DMA_UNALIGNED_DST_SRC_TEST_MASK) == 0 + } + #[inline(always)] + pub const fn is_full_aligned(encoded: u64) -> bool { + (Self::DMA_FULL_ALIGNED_MASK & encoded) == 0 + } + #[inline(always)] + pub const fn is_direct(encoded: u64) -> bool { + (Self::DMA_DIRECT_MASK & encoded) == 0 && Self::get_loop_count(encoded) > 0 + } + #[inline(always)] + pub const fn get_fill_byte(encoded: u64) -> u8 { + (encoded >> Self::DMA_FILL_BYTE_RS) as u8 + } + #[inline(always)] + pub const fn is_memcmp_negative(encoded: u64) -> bool { + (encoded & Self::DMA_FILL_BYTE_SIGN_TEST_MASK) != 0 + } + + #[inline(always)] + pub const fn get_memcmp_res_as_u64(encoded: u64) -> u64 { + if (encoded & Self::DMA_FILL_BYTE_SIGN_TEST_MASK) != 0 { + (encoded >> Self::DMA_FILL_BYTE_RS) | !Self::DMA_FILL_BYTE_MASK + } else { + (encoded >> Self::DMA_FILL_BYTE_RS) & Self::DMA_FILL_BYTE_MASK + } } + + #[inline(always)] + pub const fn get_memcmp_pre_result_nz(encoded: u64) -> bool { + (encoded & Self::DMA_FILL_BYTE_TEST_MASK) != 0 && (encoded & Self::DMA_POST_COUNT_TEST_MASK) == 0 + && (encoded & Self::DMA_PRE_COUNT_TEST_MASK) != 0 + } + #[inline(always)] + pub const fn get_memcmp_post_result_nz(encoded: u64) -> bool { + (encoded & Self::DMA_FILL_BYTE_TEST_MASK) != 0 && (encoded & Self::DMA_POST_COUNT_TEST_MASK) != 0 + } + #[inline(always)] + pub const fn get_memcmp_result_nz(encoded: u64) -> bool { + (encoded & Self::DMA_FILL_BYTE_TEST_MASK) != 0 + } + + #[inline(always)] + pub const fn has_pre_or_post(encoded: u64) -> bool { + (encoded & Self::DMA_PRE_OR_POST_TEST_MASK) != 0 + } } impl DmaHelpers { @@ -501,46 +803,79 @@ mod tests { #[test] fn asm_fast_encode_table() { let table = generate_fast_encode_table(); - for i in 0..256 { - let dst_offset = i >> 5; + for i in 0..512 { + let dst_offset = (i >> 5) & 0x7; let src_offset = (i >> 2) & 0x7; println!( - "\t.quad 0x{:016x}, 0x{:016X}, 0x{:016X}, 0x{:016X} # {:4} - {:4} D{dst_offset} S{src_offset} C{}", + "\t.quad 0x{:016x}, 0x{:016X}, 0x{:016X}, 0x{:016X} # {:4} - {:4} D{dst_offset} S{src_offset} C{}{}", table[i * 4], table[i * 4 + 1], table[i * 4 + 2], table[i * 4 + 3], i * 4, i * 4 + 3, - (i * 4) & 0xF + (i * 4) & 0xF, + if i >= 256 { " neq" } else { "" } ); } - assert!(table.len() == 1024); + assert!(table.len() == 2048); + } + #[test] + fn test_simple() { + let dst = 0xA011FE70; + let src = 0xA011F4D0; + let count = 5; + + let encode = DmaInfo::calculate_encode(dst, src, count, false); + let fast_encode = DmaInfo::encode_memcpy(dst, src, count); + println!("encode: 0x{encode:016X} {}", DmaInfo::to_string(encode)); + println!("fast_encode: 0x{fast_encode:016X} {}", DmaInfo::to_string(fast_encode)); + let encode = DmaInfo::calculate_encode(dst, src, count, true); + let fast_encode = DmaInfo::encode_memcmp(dst, src, count, 0xDB); + println!("encode: 0x{encode:016X} {}", DmaInfo::to_string(encode)); + println!("fast_encode: 0x{fast_encode:016X} {}", DmaInfo::to_string(fast_encode)); + assert_eq!(encode, encode, + "testing with memcpy dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}"); } #[test] fn test_fast_encode_table() { for dst in 0..256 { for src in 0..256 { for count in 0..256 { - let encode = DmaInfo::encode_memcpy(dst, src, count); - let fast_encode = DmaInfo::fast_encode_memcpy(dst, src, count); - assert_eq!( + let encode = DmaInfo::calculate_encode(dst, src, count, false); + let fast_encode = DmaInfo::encode_memcpy(dst, src, count); + assert_eq!(encode, fast_encode, + "testing with memcpy dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}" + ); + assert_eq!(count, DmaInfo::get_count(encode), "testing with memcpy dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}"); + } + } + } + for neq in [false, true] { + for dst in 0..256 { + for src in 0..256 { + for count in 0..256 { + let encode = DmaInfo::calculate_encode2(dst, src, count, neq) | DmaInfo::DMA_REQUIRES_DMA_MASK; + let fast_encode = DmaInfo::encode_memcmp_neq(dst, src, count, neq); + assert_eq!( encode, fast_encode, - "testing dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}" - ); + "testing NEQ with memcmp dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}" + ); + assert_eq!(count, DmaInfo::get_count(encode), "testing NEQ with memcmp dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}"); + } } } } } - #[allow(dead_code)] + #[test] fn benchmark_fast_encode_vs_encode_memcpy() { use std::time::Instant; const ITERATIONS: usize = 10_000_000; let mut checksum1 = DmaInfo::encode_memcpy(0, 0, 8); - let mut checksum2 = DmaInfo::fast_encode_memcpy(0, 0, 8); + let mut checksum2 = DmaInfo::encode_memcpy(0, 0, 8); // Benchmark encode_memcpy (original) let start = Instant::now(); @@ -558,7 +893,7 @@ mod tests { let dst = (i & 0xFF) as u64; let src = ((i >> 8) & 0xFF) as u64; let count = (i >> 16) & 0xFF; - checksum2 ^= DmaInfo::fast_encode_memcpy(dst, src, count); + checksum2 ^= DmaInfo::encode_memcpy(dst, src, count); } let duration_fast = start.elapsed(); diff --git a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs index 38998b8d7..d037a96f8 100644 --- a/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs +++ b/precompiles/sha256f/src/sha256f_gen_mem_inputs.rs @@ -33,7 +33,7 @@ pub fn generate_sha256f_mem_inputs( // Start by generating the indirection reads for iparam in 0..indirect_params { - MemBusHelpers::mem_aligned_load( + MemBusHelpers::mem_aligned_read( addr_main + iparam as u32 * 8, step_main, data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], diff --git a/state-machines/main/pil/main.pil b/state-machines/main/pil/main.pil index 0cafd2be2..71d8d6a7f 100644 --- a/state-machines/main/pil/main.pil +++ b/state-machines/main/pil/main.pil @@ -360,17 +360,13 @@ airtemplate Main(int N = 2**21, int RC = 2, int stack_enabled = 0, // Sent to bus the external operation // Precompiles are generated throw transpilation because them not exists directly in RISCV - // The traspiler has context about the precompiled and knows that if a and b are 32-bit address - // in this case for specific precompileds you could use a_imm[1] and b_imm[1] to specify an - // offset over the address. Also has the possibility to send and extend static argument known in - // transpilation time. - + // The flag m32 is used to avoid send "garbage" values to bus, to simplify the other state // state machines and to increase the hits to FROPS (frequent operations). assumes_operation(op: , - a: [a[0] + a_src_reg * a_imm[1], (1 - m32) * a[1]], - b: [b[0] + b_src_reg * b_imm[1], (1 - m32) * b[1]], + a: [a[0], (1 - m32) * a[1]], + b: [b[0], (1 - m32) * b[1]], c: , flag:, main_step: STEP * is_precompiled, diff --git a/state-machines/mem-common/src/mem_align_planner.rs b/state-machines/mem-common/src/mem_align_planner.rs index 72a6e5ace..f477f5a6a 100644 --- a/state-machines/mem-common/src/mem_align_planner.rs +++ b/state-machines/mem-common/src/mem_align_planner.rs @@ -1,5 +1,11 @@ use core::panic; -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::HashMap, + fs::File, + io::{BufRead, BufReader, BufWriter, Write}, + path::Path, + sync::Arc, +}; use crate::{MemAlignCheckPoint, MemAlignCounters}; use crate::{MemAlignInstanceCounter, MemCounters}; @@ -80,6 +86,76 @@ impl<'a> MemAlignPlanner<'a> { full, } } + + /// Saves the counters to a file. + /// + /// # Parameters + /// - `path`: Path to the file where counters will be saved + /// + /// # Returns + /// Result indicating success or an IO error + pub fn save_counters_to_file>(&self, path: P) -> std::io::Result<()> { + let file = File::create(path)?; + let mut writer = BufWriter::new(file); + + for (chunk_id, mem_counters) in self.counters.as_ref() { + let mc = &mem_counters.mem_align_counters; + writeln!( + writer, + "{} {} {} {} {} {}", + chunk_id.0, mc.full_5, mc.full_3, mc.full_2, mc.read_byte, mc.write_byte + )?; + } + + writer.flush()?; + Ok(()) + } + + /// Loads counters from a file and calculates totals for use with align_plan_from_counters. + /// Returns the loaded counters and calculated totals (full_rows, read_byte, write_byte). + /// + /// # Parameters + /// - `path`: Path to the file containing saved counters + /// + /// # Returns + /// A tuple with (counters, full_rows, read_byte, write_byte) + pub fn load_counters_from_file>( + path: P, + ) -> std::io::Result<(Vec, u32, u32, u32)> { + let file = File::open(path)?; + let reader = BufReader::new(file); + + let mut counters: Vec = Vec::new(); + let mut full_rows = 0; + let mut read_byte = 0; + let mut write_byte = 0; + + for line in reader.lines() { + let line = line?; + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() != 6 { + continue; + } + + let counter = MemAlignCounters { + chunk_id: parts[0].parse().unwrap_or(0), + full_5: parts[1].parse().unwrap_or(0), + full_3: parts[2].parse().unwrap_or(0), + full_2: parts[3].parse().unwrap_or(0), + read_byte: parts[4].parse().unwrap_or(0), + write_byte: parts[5].parse().unwrap_or(0), + }; + + full_rows += counter.full_2 * 2 + counter.full_3 * 3 + counter.full_5 * 5; + read_byte += counter.read_byte; + write_byte += counter.write_byte; + + counters.push(counter); + } + + Ok((counters, full_rows, read_byte, write_byte)) + } + fn check_pendings(&self, pendings: &[u32; 5]) { if pendings.iter().any(|&x| x > 0) { println!( @@ -115,6 +191,7 @@ impl<'a> MemAlignPlanner<'a> { self.full.get_used() ); println!("[Pending] (F5,F3,F2,RB,WB) {pendings:?}"); + let _ = self.save_counters_to_file("tmp/mem_align_counters_crash.txt"); panic!("Some counters are pending"); } } diff --git a/state-machines/mem/src/mem_sm.rs b/state-machines/mem/src/mem_sm.rs index fed056328..eb3fab87f 100644 --- a/state-machines/mem/src/mem_sm.rs +++ b/state-machines/mem/src/mem_sm.rs @@ -69,15 +69,20 @@ impl MemSM { for i in 0..num_rows { let addr = trace[i].addr.as_canonical_u64() * 8; let step = trace[i].step.as_canonical_u64(); + let main_step = MemHelpers::mem_step_to_main_step(step); let op = if trace[i].wr.is_zero() { 'R' } else { 'W' }; let values = [trace[i].value[0].as_canonical_u64(), trace[i].value[1].as_canonical_u64()]; let value = values[0] | (values[1] << 32); - writeln!(writer, "{i:<8} {addr:#010X} {step} {op} {values:?} 0x{value:016X}").unwrap(); + writeln!( + writer, + "{i:<8} {addr:#010X} {step:>13} {main_step:>12} {op} {values:?} 0x{value:016X}" + ) + .unwrap(); let dual = !trace[i].sel_dual.is_zero(); if dual { let step = trace[i].step_dual.as_canonical_u64(); - writeln!(writer, "{i:<8} {addr:#010X} {step} R {values:?} 0x{value:016X} DUAL") + writeln!(writer, "{i:<8} {addr:#010X} {step:>13} {main_step:>12} R {values:?} 0x{value:016X} DUAL") .unwrap(); } } @@ -220,7 +225,7 @@ impl MemModule for MemSM { trace[i].set_wr(mem_op.is_write); #[cfg(feature = "debug_mem")] - if (lsb_increment >= MEM_INC_C_SIZE) || (msb_increment > MEM_INC_C_SIZE) { + if (l_increment >= (1 << 22)) || (h_increment >= (1 << 16)) { panic!("MemSM: increment's out of range: {} i:{} addr_changes:{} mem_op.addr:0x{:X} last_addr:0x{:X} mem_op.step:{} last_step:{}", increment, i, addr_changes as u8, mem_op.addr, last_addr, mem_op.step, last_step); } diff --git a/witness-computation/src/zisk_lib.rs b/witness-computation/src/zisk_lib.rs index 03d7eec7b..4dc163d59 100644 --- a/witness-computation/src/zisk_lib.rs +++ b/witness-computation/src/zisk_lib.rs @@ -30,11 +30,13 @@ use zisk_core::{Riscv2zisk, CHUNK_SIZE}; use zisk_pil::PACKED_INFO; use zisk_pil::{ ADD_256_AIR_IDS, ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, - BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, DMA_64_ALIGNED_AIR_IDS, DMA_AIR_IDS, - DMA_PRE_POST_AIR_IDS, DMA_UNALIGNED_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, - MEM_ALIGN_AIR_IDS, MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, - MEM_ALIGN_WRITE_BYTE_AIR_IDS, POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, - SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, + BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, DMA_64_ALIGNED_AIR_IDS, + DMA_64_ALIGNED_INPUT_CPY_AIR_IDS, DMA_64_ALIGNED_MEM_AIR_IDS, DMA_64_ALIGNED_MEM_CPY_AIR_IDS, + DMA_64_ALIGNED_MEM_SET_AIR_IDS, DMA_AIR_IDS, DMA_INPUT_CPY_AIR_IDS, DMA_MEM_CPY_AIR_IDS, + DMA_PRE_POST_AIR_IDS, DMA_PRE_POST_INPUT_CPY_AIR_IDS, DMA_PRE_POST_MEM_CPY_AIR_IDS, + DMA_UNALIGNED_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, MEM_ALIGN_AIR_IDS, + MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, MEM_ALIGN_WRITE_BYTE_AIR_IDS, + POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, }; pub struct WitnessLib { @@ -141,6 +143,14 @@ impl WitnessLibrary for WitnessLib { (ZISK_AIRGROUP_ID, DMA_PRE_POST_AIR_IDS[0]), (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_AIR_IDS[0]), (ZISK_AIRGROUP_ID, DMA_UNALIGNED_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_MEM_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_INPUT_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_PRE_POST_MEM_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_PRE_POST_INPUT_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_MEM_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_MEM_SET_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_INPUT_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_MEM_AIR_IDS[0]), ]; let sm_bundle = StaticSMBundle::new( diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index e9824b4c7..4696e7847 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -21,4 +21,8 @@ getrandom = { version = "0.2", features = ["custom"] } cfg-if = "1.0" tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } -bincode = { workspace = true } \ No newline at end of file +bincode = { workspace = true } + +[features] +default = ["inputcpy"] +inputcpy = [] \ No newline at end of file diff --git a/ziskos/entrypoint/src/dma.rs b/ziskos/entrypoint/src/dma.rs new file mode 100644 index 000000000..e239562c0 --- /dev/null +++ b/ziskos/entrypoint/src/dma.rs @@ -0,0 +1,180 @@ +/// Copies free-input data, as fcall result, directly to a memory location. +/// +/// This macro writes free-input data to the specified pointer using +/// custom CSR instructions. The memory does not need to be initialized. +/// +/// # Arguments +/// * `$dest` - Mutable reference to the destination (array, slice, or MaybeUninit) +/// * `$size` - Size in bytes (must be a const literal) +/// +/// # Safety +/// The caller must ensure the destination is valid and properly aligned. + +#[macro_export] +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +macro_rules! ziskos_inputcpy { + ($dest:expr, $size:literal) => {{ + unsafe { + core::arch::asm!( + "csrs 0x815, {ptr}", + "addi x0, {ptr}, {size}", + size = const $size, + ptr = in(reg) $dest.as_mut_ptr(), + options(nostack, preserves_flags), + ); + } + }}; + ($dest:expr, $size:expr) => {{ + unsafe { + core::arch::asm!( + "csrs 0x815, {ptr}", + "add x0, {ptr}, {size}", + size = in(reg) $size, + ptr = in(reg) $dest.as_mut_ptr(), + options(nostack, preserves_flags), + ); + } + }}; +} + +/// Copies memory from source to destination using DMA operations. +/// +/// This macro performs a memory copy operation using custom CSR instructions +/// for optimized performance in the zkVM environment. +/// +/// # Arguments +/// * `$dst` - Mutable reference to the destination (array, slice, or MaybeUninit) +/// * `$src` - Reference to the source (array or slice) +/// * `$size` - Size in bytes (can be a literal or expression) +/// +/// # Safety +/// The caller must ensure both source and destination are valid and properly aligned, +/// and that they do not overlap in memory. + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +#[macro_export] +macro_rules! ziskos_memcpy { + ($dst:expr, $src: expr, $size:literal) => {{ + unsafe { + core::arch::asm!( + "csrs 0x813, {src}", + "addi x0, {dst}, {size}", + size = const $size, + dst = in(reg) $dst.as_mut_ptr(), + src = in(reg) $src.as_ptr(), + options(nostack, preserves_flags), + ); + } + }}; + ($dst:expr, $src: expr, $size:expr) => {{ + unsafe { + core::arch::asm!( + "csrs 0x813, {src}", + "add x0, {dst}, {size}", + size = in(reg) $size, + dst = in(reg) $dst.as_mut_ptr(), + src = in(reg) $src.as_ptr(), + options(nostack, preserves_flags), + ); + } + }}; +} + +/// Compares two memory regions for equality using DMA operations. +/// +/// This macro performs a memory comparison operation using custom CSR instructions +/// for optimized performance in the zkVM environment. The result is stored in a register. +/// +/// # Arguments +/// * `$dst` - Mutable reference to the first memory region (array or slice) +/// * `$src` - Reference to the second memory region (array or slice) +/// * `$size` - Size in bytes to compare (can be a literal or expression) +/// +/// # Safety +/// The caller must ensure both memory regions are valid and properly aligned. + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +#[macro_export] +macro_rules! ziskos_memcmp { + ($dst:expr, $src: expr, $size:literal) => {{ + unsafe { + core::arch::asm!( + "csrs 0x814, {src}", + "addi x0, {dst}, {size}", + size = const $size, + dst = in(reg) $dst.as_mut_ptr(), + src = in(reg) $src.as_ptr(), + options(nostack, preserves_flags), + ); + } + }}; + ($dst:expr, $src: expr, $size:expr) => {{ + unsafe { + core::arch::asm!( + "csrs 0x814, {src}", + "add x0, {dst}, {size}", + size = in(reg) $size, + dst = in(reg) $dst.as_mut_ptr(), + src = in(reg) $src.as_ptr(), + options(nostack, preserves_flags), + ); + } + }}; +} + +/// Fills a memory region with a constant byte value using DMA operations. +/// +/// This macro performs a memory set operation using custom CSR instructions +/// for optimized performance in the zkVM environment. +/// +/// # Arguments +/// * `$dst` - Mutable reference to the destination memory (array, slice, or MaybeUninit) +/// * `$value` - Byte value to fill (can be a literal or expression) +/// * `$size` - Size in bytes (can be a literal or expression) +/// +/// # Safety +/// The caller must ensure the destination is valid and properly aligned. + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +#[macro_export] +macro_rules! ziskos_memset { + ($dst:expr, $value: literal, $size:literal) => {{ + unsafe { + core::arch::asm!( + "csrsi 0x816, 2", + "addi x0, {dst}, {size}", + "addi x0, {dst}, {value}", + size = const $size, + value = const $value, + dst = in(reg) $dst.as_mut_ptr(), + options(nostack, preserves_flags), + ); + } + }}; + ($dst:expr, $value: literal, $size:expr) => {{ + unsafe { + core::arch::asm!( + "csrs 0x816, {dst}", + "addi x0, {size}, {value}", + size = in(reg) $size, + value = const $value, + dst = in(reg) $dst.as_mut_ptr(), + options(nostack, preserves_flags), + ); + } + }}; + ($dst:expr, $value: expr, $size:expr) => {{ + unsafe { + core::arch::asm!( + "call memset", + in("a0") $dst.as_mut_ptr(), + in("a1") $value, + in("a2") $size, + lateout("t0") _, + lateout("a1") _, + lateout("ra") _, + options(nostack, preserves_flags), + ); + } + }}; +} diff --git a/ziskos/entrypoint/src/dma/inputcpy.s b/ziskos/entrypoint/src/dma/inputcpy.s new file mode 100644 index 000000000..cd2fddd03 --- /dev/null +++ b/ziskos/entrypoint/src/dma/inputcpy.s @@ -0,0 +1,14 @@ + .section ".note.GNU-stack","",@progbits + .text + .attribute 4, 16 + .attribute 5, "rv64im" + .globl inputcpy + .p2align 4 + .type inputcpy,@function +inputcpy: + csrs 0x815, a1 # Marker: Write count (a2) to CSR 0x813 + add x0,a0,a2 + ret + + .size inputcpy, .-inputcpy + .section .text.hot,"ax",@progbits \ No newline at end of file diff --git a/ziskos/entrypoint/src/dma/memcmp.s b/ziskos/entrypoint/src/dma/memcmp.s index d49cd21fc..2e6835d69 100644 --- a/ziskos/entrypoint/src/dma/memcmp.s +++ b/ziskos/entrypoint/src/dma/memcmp.s @@ -6,8 +6,8 @@ .p2align 4 .type memcmp,@function memcmp: - csrs 0x814, a2 # Marker: Write count (a2) to CSR 0x814 - add a0,a0,a1 + csrs 0x814, a1 # Marker: Write count (a2) to CSR 0x814 + add a0,a0,a2 ret .size memcmp, .-memcmp diff --git a/ziskos/entrypoint/src/dma/memcpy.s b/ziskos/entrypoint/src/dma/memcpy.s index 610b94ac5..a087f7d11 100644 --- a/ziskos/entrypoint/src/dma/memcpy.s +++ b/ziskos/entrypoint/src/dma/memcpy.s @@ -6,8 +6,8 @@ .p2align 4 .type memcpy,@function memcpy: - csrs 0x813, a2 # Marker: Write count (a2) to CSR 0x813 - add x0,a0,a1 + csrs 0x813, a1 # Marker: Write count (a2) to CSR 0x813 + add x0,a0,a2 ret .size memcpy, .-memcpy diff --git a/ziskos/entrypoint/src/dma/memmove.s b/ziskos/entrypoint/src/dma/memmove.s index f95b81222..af3655cac 100644 --- a/ziskos/entrypoint/src/dma/memmove.s +++ b/ziskos/entrypoint/src/dma/memmove.s @@ -6,8 +6,8 @@ .p2align 4 .type memmove,@function memmove: - csrs 0x813, a2 # Marker: Write count (a2) to CSR 0x813 - add x0,a0,a1 + csrs 0x813, a1 # Marker: Write count (a2) to CSR 0x813 + add x0,a0,a2 ret .size memmove, .-memmove .section .text.hot,"ax",@progbits \ No newline at end of file diff --git a/ziskos/entrypoint/src/dma/memset.s b/ziskos/entrypoint/src/dma/memset.s new file mode 100644 index 000000000..5b66c2506 --- /dev/null +++ b/ziskos/entrypoint/src/dma/memset.s @@ -0,0 +1,51 @@ + .section ".note.GNU-stack","",@progbits + .text + .attribute 4, 16 + .attribute 5, "rv64im" + .globl memset + .p2align 4 + .type memset,@function +# memset(void *s, int c, size_t n) +# a0 = destination pointer (s) +# a1 = fill value (c) +# a2 = byte count (n) +memset: + andi a1, a1, 0xff # Mask to byte value + slli a1, a1, 4 + la t0, .Ljump_table + add t0, t0, a1 + jr t0 + + # csrs 0x816, a1; addi x0, a0, fill_byte; ret; nop + # memset(a0, fill_byte, a2) + + # Jump table: 256 entries, each 16 bytes (4 instructions) + .p2align 4 +.Ljump_table: +.irp val, 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 + csrs 0x816, a0; addi x0, a2, \val; ret; nop +.endr +.irp val, 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 + csrs 0x816, a0; addi x0, a2, \val; ret; nop +.endr +.irp val, 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95 + csrs 0x816, a0; addi x0, a2, \val; ret; nop +.endr +.irp val, 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127 + csrs 0x816, a0; addi x0, a2, \val; ret; nop +.endr +.irp val, 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159 + csrs 0x816, a0; addi x0, a2, \val; ret; nop +.endr +.irp val, 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191 + csrs 0x816, a0; addi x0, a2, \val; ret; nop +.endr +.irp val, 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223 + csrs 0x816, a0; addi x0, a2, \val; ret; nop +.endr +.irp val, 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 + csrs 0x816, a0; addi x0, a2, \val; ret; nop +.endr + + .size memset, .-memset + .section .text.hot,"ax",@progbits \ No newline at end of file diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index fa4f3d047..4606240b3 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -4,7 +4,10 @@ #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] use core::arch::asm; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +mod dma; +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] mod fcall; + mod profile; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] pub use fcall::*; @@ -290,5 +293,7 @@ mod ziskos { } core::arch::global_asm!(include_str!("dma/memcpy.s")); core::arch::global_asm!(include_str!("dma/memmove.s")); - // core::arch::global_asm!(include_str!("dma/memcmp.s")); + core::arch::global_asm!(include_str!("dma/memcmp.s")); + core::arch::global_asm!(include_str!("dma/inputcpy.s")); + core::arch::global_asm!(include_str!("dma/memset.s")); } diff --git a/ziskos/entrypoint/src/syscalls/syscall.rs b/ziskos/entrypoint/src/syscalls/syscall.rs index be2b32703..f355eb051 100644 --- a/ziskos/entrypoint/src/syscalls/syscall.rs +++ b/ziskos/entrypoint/src/syscalls/syscall.rs @@ -21,3 +21,5 @@ pub const SYSCALL_ADD256_ID: u16 = 0x811; pub const SYSCALL_POSEIDON2_ID: u16 = 0x812; pub const SYSCALL_DMA_MEMCPY_ID: u16 = 0x813; pub const SYSCALL_DMA_MEMCMP_ID: u16 = 0x814; +pub const SYSCALL_DMA_INPUTCPY_ID: u16 = 0x815; +pub const SYSCALL_DMA_MEMSET_ID: u16 = 0x816; diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs index 18f9cd231..c389272a0 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs @@ -3,7 +3,7 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; + use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, ziskos_inputcpy}; use super::FCALL_BIG_INT_DIV_ID; } } @@ -41,16 +41,28 @@ pub fn fcall_division( ziskos_fcall!(FCALL_BIG_INT_DIV_ID); - let len_quo = ziskos_fcall_get() as usize; - for i in 0..len_quo { - quo[i] = ziskos_fcall_get(); - } + #[cfg(not(feature = "inputcpy"))] + { + let len_quo = ziskos_fcall_get() as usize; + for i in 0..len_quo { + quo[i] = ziskos_fcall_get(); + } + + let len_rem = ziskos_fcall_get() as usize; + for i in 0..len_rem { + rem[i] = ziskos_fcall_get(); + } - let len_rem = ziskos_fcall_get() as usize; - for i in 0..len_rem { - rem[i] = ziskos_fcall_get(); + (len_quo, len_rem) } + #[cfg(feature = "inputcpy")] + { + let len_quo = ziskos_fcall_get() as usize; + ziskos_inputcpy!(quo, len_quo * 8); + let len_rem = ziskos_fcall_get() as usize; + ziskos_inputcpy!(rem, len_rem * 8); - (len_quo, len_rem) + (len_quo, len_rem) + } } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs index 51df32d08..408bbe099 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs @@ -1,10 +1,12 @@ +use std::{ffi::c_void, mem::MaybeUninit}; + use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; use crate::{ - ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, + ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, ziskos_inputcpy, zisklib::{FCALL_SECP256K1_FP_INV_ID, FCALL_SECP256K1_FP_SQRT_ID} }; } @@ -63,6 +65,8 @@ pub fn fcall_secp256k1_fp_inv_in_place(p_value: &[u64; 4]) { /// /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. + +#[cfg(not(feature = "inputcpy"))] #[allow(unused_variables)] pub fn fcall_secp256k1_fp_sqrt(p_value: &[u64; 4], parity: u64) -> [u64; 5] { #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] @@ -73,7 +77,7 @@ pub fn fcall_secp256k1_fp_sqrt(p_value: &[u64; 4], parity: u64) -> [u64; 5] { ziskos_fcall_param!(parity, 1); ziskos_fcall!(FCALL_SECP256K1_FP_SQRT_ID); [ - ziskos_fcall_get(), // results[0] - indicates if a sqrt exists (1) or not (0) + ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), @@ -81,3 +85,36 @@ pub fn fcall_secp256k1_fp_sqrt(p_value: &[u64; 4], parity: u64) -> [u64; 5] { ] } } + +#[cfg(feature = "inputcpy")] +#[allow(unused_variables)] +#[inline(always)] +pub fn fcall_secp256k1_fp_sqrt_p(p_value: &[u64; 4], parity: u64, res: &mut [u64; 5]) { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!(); + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + ziskos_fcall_param!(p_value, 4); + ziskos_fcall_param!(parity, 1); + ziskos_fcall!(FCALL_SECP256K1_FP_SQRT_ID); + ziskos_inputcpy!(res, 40); + } +} + +#[cfg(feature = "inputcpy")] +#[allow(unused_variables)] +#[inline(always)] +pub fn fcall_secp256k1_fp_sqrt(p_value: &[u64; 4], parity: u64) -> [u64; 5] { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + unreachable!(); + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + use core::mem::MaybeUninit; + ziskos_fcall_param!(p_value, 4); + ziskos_fcall_param!(parity, 1); + ziskos_fcall!(FCALL_SECP256K1_FP_SQRT_ID); + let mut res: MaybeUninit<[u64; 5]> = MaybeUninit::uninit(); + ziskos_inputcpy!(res, 40); + unsafe { res.assume_init() } + } +} From 568d708ae940a557e322ca15a3cee3ab6c4dbbda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Sat, 14 Feb 2026 16:48:01 +0100 Subject: [PATCH 525/782] Feat/book (#786) * Book update * Minor changes * Sha hasher --- .gitignore | 2 + book/getting_started/quickstart.md | 111 ++++++++++++----------- book/getting_started/writing_programs.md | 93 ++++++------------- tools/test-env/test_sha_hasher.sh | 13 +-- 4 files changed, 94 insertions(+), 125 deletions(-) diff --git a/.gitignore b/.gitignore index fbc8f88e0..908ce0edc 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ state-machines/frequent-ops/src/frequent_ops_fixed.bin state-machines/arith/src/arith_frops_fixed.bin state-machines/binary/src/binary_basic_frops_fixed.bin state-machines/binary/src/binary_extension_frops_fixed.bin +reth-inputs/ +hints/ \ No newline at end of file diff --git a/book/getting_started/quickstart.md b/book/getting_started/quickstart.md index ad5addd9a..e76cecc2e 100644 --- a/book/getting_started/quickstart.md +++ b/book/getting_started/quickstart.md @@ -44,30 +44,28 @@ This will create a project with the following structure: ├── build.rs ├── Cargo.toml ├── .gitignore -└── src - └── main.rs +├── guest +| ├── src +| | └── main.rs +| └── Cargo.toml +└── host + ├── src + | └── main.rs + ├── bin + | ├── compressed.rs + | ├── execute.rs + | ├── prove.rs + | ├── plonk.rs + | ├── verify-constraints.rs + | └── ziskemu.rs + ├── build.rs + └── Cargo.toml ``` The example program takes a number `n` as input and computes the SHA-256 hash `n` times. The `build.rs` file generates an `input.bin` file containing the value of `n` (e.g., 20). This file is used in `main.rs` as input to calculate the hash. -You can run the program on your native architecture with the following command: -```bash -cargo run -``` -The output will be: -``` -public 0: 0x98211882 -public 1: 0xbd13089b -public 2: 0x6ccf1fca -public 3: 0x81f7f0e4 -public 4: 0xabf6352a -public 5: 0x0c39c9b1 -public 6: 0x1f142cac -public 7: 0x233f1280 -``` - ## Build The next step is to build the program using the `cargo-zisk` command to generate an ELF file (RISC-V), which will be used later to generate the proof. Execute: @@ -76,65 +74,76 @@ The next step is to build the program using the `cargo-zisk` command to generate cargo-zisk build --release ``` -This command builds the program using the `zkvm` target. The resulting `sha_hasher` ELF file (without extension) is generated in the `./target/riscv64ima-zisk-zkvm-elf/release` directory. +This command builds the program using the `zkvm` target. The resulting `sha_hasher` ELF file (without extension) is generated in the `./target/elf/riscv64ima-zisk-zkvm-elf/release` directory. ## Execute -Before generating a proof, you can test the program using the ZisK emulator to ensure its correctness. Specify the ELF file (using the `-e` or `--elf flag`) and the input file `input.bin` (using the `-i` or `--inputs` flag): +Before generating a proof, you can test the program using the ZisK emulator to ensure its correctness: ```bash -ziskemu -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher -i build/input.bin +cargo run --release --bin ziskemu ``` -The output will be: +The emulator will execute the program and display the public outputs: + ``` -98211882 -bd13089b -6ccf1fca -81f7f0e4 -abf6352a -0c39c9b1 -1f142cac -233f1280 +public 0: 0x98211882 +public 1: 0xbd13089b +public 2: 0x6ccf1fca +public 3: 0x81f7f0e4 +public 4: 0xabf6352a +public 5: 0x0c39c9b1 +public 6: 0x1f142cac +public 7: 0x233f1280 ``` -Alternatively, you can build and run the program with: +These outputs should match the native execution, confirming the program works correctly. + +## Verify Constraints + +Once you've confirmed the program executes correctly, you can verify the constraints without generating a full proof. This is useful for debugging and ensuring correctness: ```bash -cargo-zisk run --release -i build/input.bin +cargo run --release --bin verify-constraints ``` -## Prove +This command will: +1. Execute the program using the ZisK emulator +2. Generate the execution trace +3. Verify all arithmetic and logical constraints +4. Check that all state machine transitions are valid -Before generating a proof, you need to generate the program setup files. Execute: +If successful, you'll see: -```bash -cargo-zisk rom-setup -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher +``` +✓ All constraints for Instance #0 of Main were verified +✓ All constraints for Instance #0 of Rom were verified +... +✓ All global constraints were successfully verified ``` -Once the program setup is complete, you can generate and verify a proof using the `cargo-zisk prove` command by providing the ELF file (with the `-e` or `--elf` flag) and the input file (with the `-i` or `--input` flag). +## Prove -To generate and verify a proof for the previously built ELF and input files, execute: +To generate a cryptographic proof of execution, run: ```bash -cargo-zisk prove -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher -i build/input.bin -o proof -a -y +cargo run --release --bin prove ``` -This command generates the proof in the `./proof` directory. If everything goes well, you will see a message similar to: - -``` -... -[INFO ] ProofMan: ✓ Vadcop Final proof was verified -[INFO ] stop <<< GENERATING_VADCOP_PROOF 91706ms -[INFO ] ProofMan: Proofs generated successfully -``` +This will: +1. Execute the program and generate the execution trace +2. Compute witness values for all state machines +3. Generate the polynomial commitments +4. Create the zk-STARK proof -**Note**: You can use concurrent proof generation and GPU support to reduce proving time. For more details, refer to the [Writing Programs](./writing_programs.md) guide. +The proof will be saved in the `./proof` directory. This process may take several minutes depending on the program complexity. -## Verify Proof +## Compressed Proof (Optional) -To verify a generated proof, use the following command: +After generating the proof, you can optionally create a compressed version to reduce the proof size: ```bash -cargo-zisk verify -p ./proof/vadcop_final_proof.bin +cargo run --release --bin compressed ``` + +This generates an additional compressed proof on top of the existing one using recursive composition. The compressed proof is significantly smaller while maintaining the same security guarantees. diff --git a/book/getting_started/writing_programs.md b/book/getting_started/writing_programs.md index 904a86f1f..46bd5bec4 100644 --- a/book/getting_started/writing_programs.md +++ b/book/getting_started/writing_programs.md @@ -44,14 +44,7 @@ use ziskos::{read_input_slice, set_output}; use byteorder::ByteOrder; fn main() { - // Read the input data as a byte array from ziskos - let input = read_input_slice(); - - // Convert the input data to a u64 integer - let n: u64 = match input.as_ref().try_into() { - Ok(input_bytes) => u64::from_le_bytes(input_bytes), - Err(e) => panic!("Invalid input, error: {}", e), - }; + let n: u32 = ziskos::io::read(); let mut hash = [0u8; 32]; @@ -63,21 +56,16 @@ fn main() { hash = Into::<[u8; 32]>::into(*digest); } - // Split 'hash' value into chunks of 32 bits and write them to ziskos output - for i in 0..8 { - let val = byteorder::BigEndian::read_u32(&mut hash[i * 4..i * 4 + 4]); - set_output(i, val); - } + ziskos::io::commit(&output); } ``` `Cargo.toml`: ```toml [package] -name = "sha_hasher" +name = "guest" version = "0.1.0" edition = "2021" -default-run = "sha_hasher" [dependencies] byteorder = "1.5.0" @@ -86,27 +74,31 @@ ziskos = { git = "https://github.com/0xPolygonHermez/zisk.git" } ``` ### Input/Output Data -To provide input data for ZisK, you need to write that data in a binary file (e.g., `input.bin`). -If your program requires complex input data, consider using a serialization mechanism (like [`bincode`](https://crates.io/crates/bincode) crate) to store it in `input.bin` file. +To read input data in your ZisK program, use the `ziskos::io::read()` function, which deserializes data from the input: + +```rust +// Read a u32 value from input +let n: u32 = ziskos::io::read(); +``` -In your program, use the `ziskos::read_input_slice()` function to retrieve the input data from the `input.bin` file: +You can also read custom types that implement the `Deserialize` trait: ```rust -// Read the input data as a byte array from ziskos -let input = read_input_slice(); +// Read a custom struct from input +let my_data: MyStruct = ziskos::io::read(); ``` -To write public output data, use the `ziskos::set_output()` function. Since the function accepts `u32` values, split the output data into 32-bit chunks if necessary and increase the `id` parameter of the function in each call: +To write public output data, use the `ziskos::io::commit()` function, which serializes and commits the output: ```rust -// Split 'hash' value into chunks of 32 bits and write them to ziskos output -for i in 0..8 { - let val = byteorder::BigEndian::read_u32(&mut hash[i * 4..i * 4 + 4]); - set_output(i, val); -} +// Commit the hash as public output +let hash: [u8; 32] = compute_hash(); +ziskos::io::commit(&hash); ``` +The output can be any type that implements the `Serialize` trait. The data will be serialized and made available as public outputs that can be verified by anyone checking the proof. + ## Build Before compiling your program for ZisK, you can test it on the native architecture just like any regular Rust program using the `cargo` command. @@ -117,7 +109,7 @@ Once your program is ready to run on ZisK, compile it into an ELF file (RISC-V a cargo-zisk build ``` -This command compiles the program using the `zisk` target. The resulting `sha_hasher` ELF file (without extension) is generated in the `./target/riscv64ima-zisk-zkvm-elf/debug` directory. +This command compiles the program using the `zisk` target. The resulting `guest` ELF file (without extension) is generated in the `./target/riscv64ima-zisk-zkvm-elf/debug` directory. For production, compile the ELF file with the `--release` flag, similar to how you compile Rust projects: @@ -125,7 +117,7 @@ For production, compile the ELF file with the `--release` flag, similar to how y cargo-zisk build --release ``` -In this case, the `sha_hasher` ELF file will be generated in the `./target/riscv64ima-zisk-zkvm-elf/release` directory. +In this case, the `guest` ELF file will be generated in the `./target/elf/riscv64ima-zisk-zkvm-elf/release` directory. ## Execute @@ -133,13 +125,7 @@ You can test your compiled program using the ZisK emulator (`ziskemu`) before ge ```bash cargo-zisk build --release -ziskemu -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher -i build/input.bin -``` - -Alternatively, you can build and execute the program in the ZisK emulator with a single command: - -```bash -cargo-zisk run --release -i build/input.bin +ziskemu -e target/elf/riscv64ima-zisk-zkvm-elf/release/guest -i host/tmp/input.bin ``` If the program requires a large number of ZisK steps, you might encounter the following error: @@ -150,7 +136,7 @@ Error: Error executing Run command To resolve this, you can increase the number of execution steps using the `-n` (`--max-steps`) flag. For example: ```bash -ziskemu -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher -i build/input.bin -n 10000000000 +ziskemu -e target/elf/riscv64ima-zisk-zkvm-elf/release/guest -i host/tmp/input.bin -n 10000000000 ``` ## Metrics and Statistics @@ -158,36 +144,23 @@ ziskemu -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher -i build/input.bin ### Performance Metrics You can get performance metrics related to the program execution in ZisK using the `-m` (`--log-metrics`) flag in the `cargo-zisk run` command or in `ziskemu` tool: -```bash -cargo-zisk run --release -i build/input.bin -m -``` - -Or ```bash -ziskemu -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher -i build/input.bin -m +ziskemu -e target/elf/riscv64ima-zisk-zkvm-elf/release/guest -i host/tmp/input.bin -m ``` The output will include details such as execution time, throughput, and clock cycles per step: ``` process_rom() steps=85309 duration=0.0009 tp=89.8565 Msteps/s freq=3051.0000 33.9542 clocks/step -98211882 -bd13089b -6ccf1fca ... ``` ### Execution Statistics -You can get statistics related to the program execution in Zisk using the `-x` (`--stats`) flag in the `cargo-zisk run` command or in `ziskemu` tool: +You can get statistics related to the program execution in Zisk using the `-X` (`--stats`) flag in `ziskemu` tool: -```bash -cargo-zisk run --release -i build/input.bin -x -``` - -Or ```bash -ziskemu -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher -i build/input.bin -x +ziskemu -e target/elf/riscv64ima-zisk-zkvm-elf/release/guest -i host/tmp/input.bin -X ``` The output will include details such as cost definitions, total cost, register reads/writes, opcode statistics, etc: @@ -218,10 +191,6 @@ Opcodes: xor: 1.06 sec (77 steps/op) (13774 ops) signextend_b: 0.03 sec (109 steps/op) (320 ops) signextend_w: 0.03 sec (109 steps/op) (320 ops) - -98211882 -bd13089b -6ccf1fca ... ``` @@ -232,7 +201,7 @@ bd13089b Before generating a proof (or verifying the constraints), you need to generate the program setup files. This must be done the first time after building the program ELF file, or any time it changes: ```bash -cargo-zisk rom-setup -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher -k $HOME/.zisk/provingKey +cargo-zisk rom-setup -e target/elf/riscv64ima-zisk-zkvm-elf/release/guest -k $HOME/.zisk/provingKey ``` In this command: @@ -251,14 +220,12 @@ cargo-zisk clean Before generating a proof (which can take some time), you can verify that all constraints are satisfied: ```bash -LIB_EXT=$([[ "$(uname)" == "Darwin" ]] && echo "dylib" || echo "so") -cargo-zisk verify-constraints -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher -i build/input.bin -w $HOME/.zisk/bin/libzisk_witness.$LIB_EXT -k $HOME/.zisk/provingKey +cargo-zisk verify-constraints -e target/elf/riscv64ima-zisk-zkvm-elf/release/guest -i host/tmp/input.bin -k $HOME/.zisk/provingKey ``` In this command: * `-e` (`--elf`) specifies the ELF file location. * `-i` (`--input`) specifies the input file location. -* `-w` (`--witness`) specifies the location of the witness library. This is optional and defaults to `$HOME/.zisk/bin/libzisk_witness.$LIB_EXT`. * `-k` (`--proving-key`) specifies the directory containing the proving key. This is optional and defaults to `$HOME/.zisk/provingKey`. If everything is correct, you will see an output similar to: @@ -274,14 +241,12 @@ If everything is correct, you will see an output similar to: To generate a proof, run the following command: ```bash -LIB_EXT=$([[ "$(uname)" == "Darwin" ]] && echo "dylib" || echo "so") -cargo-zisk prove -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher -i build/input.bin -w $HOME/.zisk/bin/libzisk_witness.$LIB_EXT -k $HOME/.zisk/provingKey -o proof -a -y +cargo-zisk prove -e target/elf/riscv64ima-zisk-zkvm-elf/release/guest -i host/tmp/input.bin -k $HOME/.zisk/provingKey -o proof -a -y ``` In this command: * `-e` (`--elf`) specifies the ELF file location. * `-i` (`--input`) specifies the input file location. -* `-w` (`--witness`) specifies the location of the witness library. This is optional and defaults to `$HOME/.zisk/bin/libzisk_witness.$LIB_EXT`. * `-k` (`--proving-key`) specifies the directory containing the proving key. This is optional and defaults to `$HOME/.zisk/provingKey`. * `-o` (`--output`) determines the output directory (in this example `proof`). * `-a` (`--aggregation`) indicates that a final aggregated proof (containing all generated sub-proofs) should be produced. @@ -347,7 +312,7 @@ You can combine GPU-based execution with concurrent proof generation using multi To verify a generated proof, use the following command: ```bash -cargo-zisk verify -p ./proof/vadcop_final_proof.bin -s $HOME/.zisk/provingKey/zisk/vadcop_final/vadcop_final.starkinfo.json -e $HOME/.zisk/provingKey/zisk/vadcop_final/vadcop_final.verifier.bin -k $HOME/.zisk/provingKey/zisk/vadcop_final/vadcop_final.verkey.json +cargo-zisk verify -p ./proof/vadcop_final_proof.bin -k $HOME/.zisk/provingKey ``` In this command: diff --git a/tools/test-env/test_sha_hasher.sh b/tools/test-env/test_sha_hasher.sh index c185a4bd7..a9e93a396 100755 --- a/tools/test-env/test_sha_hasher.sh +++ b/tools/test-env/test_sha_hasher.sh @@ -2,7 +2,7 @@ source "./utils.sh" -PROJECT_NAME="sha_hasher" +PROJECT_NAME="guest" EXPECTED_OUTPUT="98211882|bd13089b|6ccf1fca|81f7f0e4|abf6352a|0c39c9b1|1f142cac|233f1280" main() { @@ -42,8 +42,8 @@ main() { step "Building program..." ensure cargo-zisk build --release || return 1 - ELF_PATH="target/riscv64ima-zisk-zkvm-elf/release/$PROJECT_NAME" - INPUT_BIN="build/input.bin" + ELF_PATH="target/elf/riscv64ima-zisk-zkvm-elf/release/$PROJECT_NAME" + INPUT_BIN="host/tmp/input.bin" step "Running program with ziskemu..." ensure ziskemu -e "$ELF_PATH" -i "$INPUT_BIN" -f | tee ziskemu_output.log || return 1 @@ -52,13 +52,6 @@ main() { return 1 fi - step "Running program with cargo-zisk run..." - ensure cargo-zisk run --release -i build/input.bin -f | tee run_output.log || return 1 - if ! grep -qE ${EXPECTED_OUTPUT} run_output.log; then - err "run program failed" - return 1 - fi - if is_gha && [[ "${PLATFORM}" == "darwin" ]]; then warn "Skipping prove and verify steps on macOS as it's not supported in GHA" else From 4553cdb0e9647523788ce44501caf88fd1ef3bd7 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 16 Feb 2026 18:25:19 +0100 Subject: [PATCH 526/782] Split assembly reset in fast/slow/trace --- emulator-asm/src/emu.c | 23 ++++++++-- emulator-asm/src/main.c | 96 +++++++++++++++++++++++++++++------------ 2 files changed, 89 insertions(+), 30 deletions(-) diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index 808293a07..cc18a03da 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -397,9 +397,10 @@ void precompile_cache_store( uint8_t* data, uint64_t size) fwrite(data, 1, size, precompile_file); fflush(precompile_file); #ifdef ASM_PRECOMPILE_CACHE_DEBUG + uint64_t previous_total_precompile_cache_size = total_precompile_cache_size; total_precompile_cache_size += size; total_precompile_cache_counter++; - printf("precompile_cache_store() Stored %lu bytes at pos=%lu total_precompile_cache_size=%lu total_precompile_cache_counter=%lu\n", size, ftell(precompile_file), total_precompile_cache_size, total_precompile_cache_counter); + printf("precompile_cache_store() Stored %lu bytes at pos=%lu file_size=%lu total_precompile_cache_size=%lu total_precompile_cache_counter=%lu\n", size, previous_total_precompile_cache_size, ftell(precompile_file), total_precompile_cache_size, total_precompile_cache_counter); #endif } @@ -500,7 +501,15 @@ extern int _opcode_keccak(uint64_t address) #ifdef ASM_CALL_METRICS if (emu_verbose) printf("opcode_keccak() calling keccakf1600_generic() counter=%lu address=%08lx\n", asm_call_metrics.keccak_counter, address); #else - if (emu_verbose) printf("opcode_keccak() calling keccakf1600_generic() address=%08lx\n", address); + if (emu_verbose) + { + printf("opcode_keccak() calling keccakf1600_generic() address=%08lx\n", address); + for (uint64_t i=0; i<200; i++) + { + printf("%02x", ((uint8_t *)address)[i]); + } + printf("\n"); + } #endif #endif @@ -523,7 +532,15 @@ extern int _opcode_keccak(uint64_t address) #endif #ifdef DEBUG - if (emu_verbose) printf("opcode_keccak() called keccakf1600_generic()\n"); + if (emu_verbose) + { + printf("opcode_keccak() called keccakf1600_generic()\n"); + for (uint64_t i=0; i<200; i++) + { + printf("%02x", ((uint8_t *)address)[i]); + } + printf("\n"); + } #endif #ifdef ASM_CALL_METRICS asm_call_metrics.keccak_counter++; diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index a665c6583..58820f399 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -256,7 +256,9 @@ uint64_t TimeDiff(const struct timeval startTime, const struct timeval endTime); void configure (void); void server_setup (void); -void server_reset (void); +void server_reset_fast (void); +void server_reset_slow (void); +void server_reset_trace (void); void server_run (void); void server_cleanup (void); @@ -635,7 +637,9 @@ int main(int argc, char *argv[]) server_setup(); // Reset the server, i.e. reset memory - server_reset(); + server_reset_fast(); + server_reset_slow(); + server_reset_trace(); // Create socket file descriptor int server_fd; @@ -766,6 +770,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_MT_RESPONSE; response[1] = (MEM_END && !MEM_ERROR) ? 0 : 1; response[2] = trace_size; @@ -795,6 +801,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_RH_RESPONSE; response[1] = MEM_END ? 0 : 1; response[2] = trace_size; @@ -825,6 +833,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_MO_RESPONSE; response[1] = MEM_END ? 0 : 1; response[2] = trace_size; @@ -855,6 +865,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_MA_RESPONSE; response[1] = MEM_END ? 0 : 1; response[2] = trace_size; @@ -888,6 +900,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_CM_RESPONSE; response[1] = 0; response[2] = trace_size; @@ -918,6 +932,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_FA_RESPONSE; response[1] = MEM_END ? 0 : 1; response[2] = 0; @@ -948,6 +964,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_MR_RESPONSE; response[1] = MEM_END ? 0 : 1; response[2] = trace_size; @@ -981,6 +999,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_CA_RESPONSE; response[1] = 0; response[2] = trace_size; @@ -1033,7 +1053,7 @@ int main(int argc, char *argv[]) #endif if (bReset) { - server_reset(); + server_reset_slow(); } if (bShutdown) @@ -4011,10 +4031,14 @@ void server_setup (void) if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); } -void server_reset (void) +void server_reset_fast (void) { + // Reset precompile read address for next emulation if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) *precompile_read_address = 0; +} +void server_reset_slow (void) +{ // Reset RAM data for next emulation if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) { @@ -4027,32 +4051,16 @@ void server_reset (void) duration = TimeDiff(start_time, stop_time); if (verbose) printf("server_reset() memset(ram) in %lu us\n", duration); #endif - if ((gen_method != Fast) && (gen_method != RomHistogram)) - { - // Reset trace: init output header data - pOutputTrace[0] = 0x000100; // Version, e.g. v1.0.0 [8] - pOutputTrace[1] = 1; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - pOutputTrace[2] = trace_size; // MT allocated size [8] -> to be updated after reallocation - pOutputTrace[3] = 0; // MT used size [8] -> to be updated after completion - - // Reset trace used size - trace_used_size = 0; - } } } -void server_run (void) +void server_reset_trace (void) { - if ((gen_method == RomHistogram)) { - memset((void *)trace_address, 0, trace_size); - } - -#ifdef ASM_CALL_METRICS - reset_asm_call_metrics(); -#endif - - // Init trace header - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && (gen_method != Fast)) + // Reset RAM data for next emulation + if ( (gen_method != ChunkPlayerMTCollectMem) && + (gen_method != ChunkPlayerMemReadsCollectMain) && + (gen_method != Fast) && + (gen_method != RomHistogram) ) { // Reset trace: init output header data pOutputTrace[0] = 0x000100; // Version, e.g. v1.0.0 [8] @@ -4063,6 +4071,22 @@ void server_run (void) // Reset trace used size trace_used_size = 0; } +} + +void server_run (void) +{ + // If ROM histogram, reset the trace area to 0 for the histogram data since it represents the + // ROM instruction multiplicity and one of them will be increased at every executed instruction + if ((gen_method == RomHistogram)) { + memset((void *)trace_address, 0, trace_size); + } + +#ifdef ASM_CALL_METRICS + reset_asm_call_metrics(); +#endif + + // Init trace header + server_reset_trace(); // Sync input shared memory if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) { @@ -4435,8 +4459,22 @@ extern int _print_regs() // printf("\n"); } +//struct timeval print_pc_tv; extern int _print_pc (uint64_t pc, uint64_t c) { + // print_pc_counter++; + // { + // struct timeval tv; + // gettimeofday(&tv, NULL); + // uint64_t duration = TimeDiff(print_pc_tv, tv); + // if (duration > 900) + // { + // uint64_t chunk = print_pc_counter / chunk_size; + // printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); + // fflush(stdout); + // } + // print_pc_tv = tv; + // } printf("s=%lu pc=%lx c=%lx", print_pc_counter, pc, c); /* Used for debugging printf(" r0=%lx", reg_0); @@ -4489,8 +4527,12 @@ extern void _chunk_done() // struct timeval tv; // gettimeofday(&tv, NULL); // uint64_t duration = TimeDiff(chunk_done_tv, tv); + // if (duration > 5000) + // { + // printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); + // fflush(stdout); + // } // chunk_done_tv = tv; - // printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); // } //gettimeofday(&sync_start, NULL); __sync_synchronize(); From 7636e2521756b6dcdaa137b11793458f55c67100 Mon Sep 17 00:00:00 2001 From: agnusmor <100322135+agnusmor@users.noreply.github.com> Date: Mon, 16 Feb 2026 18:25:32 +0100 Subject: [PATCH 527/782] Fix test sha_hasher (#788) * Fix test sha_hasher * Adding branch * minor fix * Fixing * Fix * Fix cargo build * Missing dependency * Cargo fmt * increasing timeout --------- Co-authored-by: RogerTaule --- .github/workflows/pr.yml | 2 +- Cargo.toml | 1 + cli/src/toolchain/new.rs | 2 ++ core/Cargo.toml | 3 +++ core/build.rs | 12 ++++++++++++ tools/test-env/test_sha_hasher.sh | 6 +++--- 6 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 core/build.rs diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 84eeb0fda..a9df5ae7a 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -114,7 +114,7 @@ jobs: test-macos: name: Test on macOS runs-on: macos-14 - timeout-minutes: 15 + timeout-minutes: 30 defaults: run: shell: bash diff --git a/Cargo.toml b/Cargo.toml index 6830f5ed9..3772a83d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ ziskemu = { path = "emulator" } asm-runner = { path = "emulator-asm/asm-runner" } executor = { path = "executor" } lib-c = { path = "lib-c" } +lib-float = { path = "lib-float" } zisk-pil = { path = "pil" } precomp-arith-eq = { path = "precompiles/arith_eq" } precomp-arith-eq-384 = { path = "precompiles/arith_eq_384" } diff --git a/cli/src/toolchain/new.rs b/cli/src/toolchain/new.rs index e5df8b33f..d92a8e245 100644 --- a/cli/src/toolchain/new.rs +++ b/cli/src/toolchain/new.rs @@ -21,6 +21,8 @@ impl NewCmd { // Clone the repository. let output = Command::new("git") .arg("clone") + .arg("--branch") + .arg("pre-develop-0.16.0") .arg(repo_url) .arg(root.as_os_str()) .arg("--recurse-submodules") diff --git a/core/Cargo.toml b/core/Cargo.toml index ee31e79aa..41bc075a0 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -31,6 +31,9 @@ paste = { workspace = true } elf = "0.7.4" tiny-keccak = { version = "2.0.2", features = ["keccak"] } +[build-dependencies] +lib-float = { workspace = true } + [features] default = [] diff --git a/core/build.rs b/core/build.rs new file mode 100644 index 000000000..d52d03228 --- /dev/null +++ b/core/build.rs @@ -0,0 +1,12 @@ +use std::path::Path; + +fn main() { + // Ensure lib-float is built before we try to include ziskfloat.elf + // The build-dependency on lib-float will trigger its build.rs first. + + // Tell cargo to rerun this build script if the float library changes + let float_lib_path = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../lib-float/c/lib/ziskfloat.elf"); + + println!("cargo:rerun-if-changed={}", float_lib_path.display()); +} diff --git a/tools/test-env/test_sha_hasher.sh b/tools/test-env/test_sha_hasher.sh index a9e93a396..3f0ce9bd6 100755 --- a/tools/test-env/test_sha_hasher.sh +++ b/tools/test-env/test_sha_hasher.sh @@ -3,7 +3,7 @@ source "./utils.sh" PROJECT_NAME="guest" -EXPECTED_OUTPUT="98211882|bd13089b|6ccf1fca|81f7f0e4|abf6352a|0c39c9b1|1f142cac|233f1280" +EXPECTED_OUTPUT="4fcbc136|2ce46a82|2248a8eb|785f0c7e|9dca7861|7267cace|d028d7e5|f6a2309c|000003e8|deadbeef" main() { info "▶️ Running $(basename "$0") script..." @@ -40,13 +40,13 @@ main() { cd "$PROJECT_NAME" step "Building program..." - ensure cargo-zisk build --release || return 1 + ensure cargo build --bin host --release || return 1 ELF_PATH="target/elf/riscv64ima-zisk-zkvm-elf/release/$PROJECT_NAME" INPUT_BIN="host/tmp/input.bin" step "Running program with ziskemu..." - ensure ziskemu -e "$ELF_PATH" -i "$INPUT_BIN" -f | tee ziskemu_output.log || return 1 + ensure ziskemu -e "$ELF_PATH" -i "$INPUT_BIN" -c | tee ziskemu_output.log || return 1 if ! grep -qE ${EXPECTED_OUTPUT} ziskemu_output.log; then err "run ziskemu failed" return 1 From a6d615550cc0ba7eb56b22335ad9e5f66c5e51a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Mon, 16 Feb 2026 18:44:35 +0100 Subject: [PATCH 528/782] Fix/debug mode (#792) * Adding air_values for visualization * Cargo update * Cargo update * Cargo update --- Cargo.lock | 212 +++++++++++++++++++------------------- sdk/src/prover/asm.rs | 4 + sdk/src/prover/backend.rs | 11 ++ sdk/src/prover/emu.rs | 4 + sdk/src/prover/mod.rs | 7 ++ 5 files changed, 132 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 545563469..06e5500e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,7 +186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -199,7 +199,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -260,7 +260,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -331,7 +331,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "synstructure", ] @@ -343,7 +343,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -365,7 +365,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -376,7 +376,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -488,14 +488,14 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] @@ -543,7 +543,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -615,7 +615,7 @@ dependencies = [ "rom-setup", "serde", "serde_json", - "sysinfo 0.38.1", + "sysinfo 0.38.0", "target-lexicon", "tokio", "tracing", @@ -775,7 +775,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1109,13 +1109,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" dependencies = [ "fields", "num-bigint", @@ -1149,7 +1149,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1160,7 +1160,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1241,7 +1241,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1251,7 +1251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1293,7 +1293,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1320,7 +1320,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1367,7 +1367,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1467,7 +1467,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" dependencies = [ "cfg-if", "num-bigint", @@ -1526,9 +1526,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1541,9 +1541,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1551,15 +1551,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1568,38 +1568,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1609,7 +1609,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -2370,9 +2369,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] @@ -2562,18 +2561,18 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags", ] [[package]] name = "objc2-io-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ "libc", "objc2-core-foundation", @@ -2745,7 +2744,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -2772,7 +2771,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" dependencies = [ "colored", "fields", @@ -2806,7 +2805,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3110,7 +3109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3134,7 +3133,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" dependencies = [ "bincode", "blake3", @@ -3170,7 +3169,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" dependencies = [ "bincode", "borsh", @@ -3202,7 +3201,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" dependencies = [ "fields", "itoa", @@ -3215,17 +3214,17 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" dependencies = [ "crossbeam-channel", "tracing", @@ -3234,7 +3233,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" dependencies = [ "bincode", "bytemuck", @@ -3246,7 +3245,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" dependencies = [ "bytemuck", "fields", @@ -3282,7 +3281,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.115", + "syn 2.0.116", "tempfile", ] @@ -3296,7 +3295,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3818,9 +3817,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ "bitflags", "core-foundation", @@ -3831,9 +3830,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", @@ -3897,7 +3896,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4214,9 +4213,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.115" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -4240,7 +4239,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4259,9 +4258,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.38.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5792d209c2eac902426c0c4a166c9f72147db453af548cf9bf3242644c4d4fe3" +checksum = "fe840c5b1afe259a5657392a4dbb74473a14c8db999c3ec2f4ae812e028a94da" dependencies = [ "libc", "memchr", @@ -4294,9 +4293,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tempfile" @@ -4337,7 +4336,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4348,7 +4347,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4462,7 +4461,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4593,9 +4592,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a" +checksum = "7f32a6f80051a4111560201420c7885d0082ba9efe2ab61875c587bb6b18b9a0" dependencies = [ "async-trait", "axum", @@ -4622,21 +4621,21 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27aac809edf60b741e2d7db6367214d078856b8a5bff0087e94ff330fb97b6fc" +checksum = "ce6d8958ed3be404120ca43ffa0fb1e1fc7be214e96c8d33bd43a131b6eebc9e" dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "tonic-prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0" +checksum = "9f86539c0089bfd09b1f8c0ab0239d80392af74c21bc9e0f15e1b4aca4c1647f" dependencies = [ "bytes", "prost", @@ -4645,16 +4644,16 @@ dependencies = [ [[package]] name = "tonic-prost-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4556786613791cfef4ed134aa670b61a85cfcacf71543ef33e8d801abae988f" +checksum = "65873ace111e90344b8973e94a1fc817c924473affff24629281f90daed1cd2e" dependencies = [ "prettyplease", "proc-macro2", "prost-build", "prost-types", "quote", - "syn 2.0.115", + "syn 2.0.116", "tempfile", "tonic-build", ] @@ -4753,7 +4752,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4846,9 +4845,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" @@ -4906,11 +4905,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", @@ -5062,7 +5061,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "wasm-bindgen-shared", ] @@ -5290,7 +5289,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5301,7 +5300,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5660,7 +5659,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.115", + "syn 2.0.116", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -5676,7 +5675,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -5721,7 +5720,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" dependencies = [ "colored", "fields", @@ -5801,7 +5800,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "synstructure", ] @@ -5822,7 +5821,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5842,7 +5841,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "synstructure", ] @@ -5863,7 +5862,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5896,7 +5895,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5942,6 +5941,7 @@ dependencies = [ "elf", "fields", "lib-c", + "lib-float", "paste", "precompiles-helpers", "rayon", @@ -6169,7 +6169,7 @@ dependencies = [ "sm-binary", "symbolic-common", "symbolic-demangle", - "sysinfo 0.38.1", + "sysinfo 0.38.0", "vergen-git2", "zisk-common", "zisk-core", diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index cb0eb213c..89d667d87 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -182,6 +182,10 @@ impl ProverEngine for AsmProver { self.core_prover.backend.get_instance_trace(instance_id, first_row, num_rows, offset) } + fn get_instance_air_values(&self, instance_id: usize) -> Result> { + self.core_prover.backend.get_instance_air_values(instance_id) + } + fn get_instance_fixed( &self, instance_id: usize, diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 92fedbcc8..1aa765a90 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -229,6 +229,17 @@ impl ProverBackend { .map_err(|e| anyhow::anyhow!("Error getting instance trace: {}", e)) } + pub(crate) fn get_instance_air_values(&self, instance_id: usize) -> Result> { + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot get instance AIR values in verifier mode"))?; + + proofman + .get_instance_air_values(instance_id) + .map_err(|e| anyhow::anyhow!("Error getting instance AIR values: {}", e)) + } + pub(crate) fn get_instance_fixed( &self, instance_id: usize, diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 8ac1b94bd..cb873f0bf 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -134,6 +134,10 @@ impl ProverEngine for EmuProver { self.core_prover.backend.get_instance_trace(instance_id, first_row, num_rows, offset) } + fn get_instance_air_values(&self, instance_id: usize) -> Result> { + self.core_prover.backend.get_instance_air_values(instance_id) + } + fn get_instance_fixed( &self, instance_id: usize, diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index e2a9f71c5..e7509dc6c 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -701,6 +701,8 @@ pub trait ProverEngine { offset: Option, ) -> Result>; + fn get_instance_air_values(&self, instance_id: usize) -> Result>; + fn get_instance_fixed( &self, instance_id: usize, @@ -848,6 +850,11 @@ impl ZiskProver { self.prover.get_instance_trace(instance_id, first_row, num_rows, offset) } + /// Get the instance AIR values for a given instance ID. + pub fn get_instance_air_values(&self, instance_id: usize) -> Result> { + self.prover.get_instance_air_values(instance_id) + } + /// Get the instance fixed for a given instance ID and row range. pub fn get_instance_fixed( &self, From 0749466f42deb14d1f21a12018800024ba5a1551 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:44:53 +0000 Subject: [PATCH 529/782] added msync to write_u64_at in shmem_writer.rs --- emulator-asm/asm-runner/src/shmem_writer.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index eab77671c..384009912 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -205,6 +205,14 @@ impl SharedMemoryWriter { unsafe { (self.ptr.add(offset) as *mut u64).write(value); } + + unsafe { + // Force changes to be flushed to the shared memory + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + if msync(self.ptr as *mut _, self.size, MS_SYNC) != 0 { + panic!("msync failed in write_u64_at: {:?}", io::Error::last_os_error()); + } + } } pub fn reset(&mut self) { From 76461a6575e0f23ce94d608b95afaadcdd2c8505 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 16 Feb 2026 19:41:19 +0100 Subject: [PATCH 530/782] Fix copilot in emu.c --- emulator-asm/src/emu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index cc18a03da..74eecf609 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -506,7 +506,7 @@ extern int _opcode_keccak(uint64_t address) printf("opcode_keccak() calling keccakf1600_generic() address=%08lx\n", address); for (uint64_t i=0; i<200; i++) { - printf("%02x", ((uint8_t *)address)[i]); + printf("%02x", ((uint8_t *)(uintptr_t)address)[i]); } printf("\n"); } @@ -537,7 +537,7 @@ extern int _opcode_keccak(uint64_t address) printf("opcode_keccak() called keccakf1600_generic()\n"); for (uint64_t i=0; i<200; i++) { - printf("%02x", ((uint8_t *)address)[i]); + printf("%02x", ((uint8_t *)(uintptr_t)address)[i]); } printf("\n"); } From 613c5439a49b51360152c1773d96310148e3afbe Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 16 Feb 2026 20:43:31 +0100 Subject: [PATCH 531/782] Change commented code by conditional precompilation in main.c --- emulator-asm/src/main.c | 251 +++++++++++++++++++++++----------------- 1 file changed, 142 insertions(+), 109 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 58820f399..dddc64f26 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4382,101 +4382,116 @@ void server_cleanup (void) } } -// extern uint64_t reg_0; -// extern uint64_t reg_1; -// extern uint64_t reg_2; -// extern uint64_t reg_3; -// extern uint64_t reg_4; -// extern uint64_t reg_5; -// extern uint64_t reg_6; -// extern uint64_t reg_7; -// extern uint64_t reg_8; -// extern uint64_t reg_9; -// extern uint64_t reg_10; -// extern uint64_t reg_11; -// extern uint64_t reg_12; -// extern uint64_t reg_13; -// extern uint64_t reg_14; -// extern uint64_t reg_15; -// extern uint64_t reg_16; -// extern uint64_t reg_17; -// extern uint64_t reg_18; -// extern uint64_t reg_19; -// extern uint64_t reg_20; -// extern uint64_t reg_21; -// extern uint64_t reg_22; -// extern uint64_t reg_23; -// extern uint64_t reg_24; -// extern uint64_t reg_25; -// extern uint64_t reg_26; -// extern uint64_t reg_27; -// extern uint64_t reg_28; -// extern uint64_t reg_29; -// extern uint64_t reg_30; -// extern uint64_t reg_31; -// extern uint64_t reg_32; -// extern uint64_t reg_33; -// extern uint64_t reg_34; +//#define PRINT_REGS +#ifdef PRINT_REGS +extern uint64_t reg_0; +extern uint64_t reg_1; +extern uint64_t reg_2; +extern uint64_t reg_3; +extern uint64_t reg_4; +extern uint64_t reg_5; +extern uint64_t reg_6; +extern uint64_t reg_7; +extern uint64_t reg_8; +extern uint64_t reg_9; +extern uint64_t reg_10; +extern uint64_t reg_11; +extern uint64_t reg_12; +extern uint64_t reg_13; +extern uint64_t reg_14; +extern uint64_t reg_15; +extern uint64_t reg_16; +extern uint64_t reg_17; +extern uint64_t reg_18; +extern uint64_t reg_19; +extern uint64_t reg_20; +extern uint64_t reg_21; +extern uint64_t reg_22; +extern uint64_t reg_23; +extern uint64_t reg_24; +extern uint64_t reg_25; +extern uint64_t reg_26; +extern uint64_t reg_27; +extern uint64_t reg_28; +extern uint64_t reg_29; +extern uint64_t reg_30; +extern uint64_t reg_31; +extern uint64_t reg_32; +extern uint64_t reg_33; +extern uint64_t reg_34; +#endif extern int _print_regs() { - // printf("print_regs()\n"); - // printf("\treg[ 0]=%lu=0x%lx=@%p\n", reg_0, reg_0, ®_0); - // //printf("\treg[ 1]=%lu=0x%lx=@%p\n", reg_1, reg_1, ®_1); - // //printf("\treg[ 2]=%lu=0x%lx=@%p\n", reg_2, reg_2, ®_2); - // printf("\treg[ 3]=%lu=0x%lx=@%p\n", reg_3, reg_3, ®_3); - // printf("\treg[ 4]=%lu=0x%lx=@%p\n", reg_4, reg_4, ®_4); - // /*printf("\treg[ 5]=%lu=0x%lx=@%p\n", reg_5, reg_5, ®_5); - // printf("\treg[ 6]=%lu=0x%lx=@%p\n", reg_6, reg_6, ®_6); - // printf("\treg[ 7]=%lu=0x%lx=@%p\n", reg_7, reg_7, ®_7); - // printf("\treg[ 8]=%lu=0x%lx=@%p\n", reg_8, reg_8, ®_8); - // printf("\treg[ 9]=%lu=0x%lx=@%p\n", reg_9, reg_9, ®_9); - // printf("\treg[10]=%lu=0x%lx=@%p\n", reg_10, reg_10, ®_10); - // printf("\treg[11]=%lu=0x%lx=@%p\n", reg_11, reg_11, ®_11); - // printf("\treg[12]=%lu=0x%lx=@%p\n", reg_12, reg_12, ®_12); - // printf("\treg[13]=%lu=0x%lx=@%p\n", reg_13, reg_13, ®_13); - // printf("\treg[14]=%lu=0x%lx=@%p\n", reg_14, reg_14, ®_14); - // printf("\treg[15]=%lu=0x%lx=@%p\n", reg_15, reg_15, ®_15); - // printf("\treg[16]=%lu=0x%lx=@%p\n", reg_16, reg_16, ®_16); - // printf("\treg[17]=%lu=0x%lx=@%p\n", reg_17, reg_17, ®_17); - // printf("\treg[18]=%lu=0x%lx=@%p\n", reg_18, reg_18, ®_18);*/ - // printf("\treg[19]=%lu=0x%lx=@%p\n", reg_19, reg_19, ®_19); - // printf("\treg[20]=%lu=0x%lx=@%p\n", reg_20, reg_20, ®_20); - // printf("\treg[21]=%lu=0x%lx=@%p\n", reg_21, reg_21, ®_21); - // printf("\treg[22]=%lu=0x%lx=@%p\n", reg_22, reg_22, ®_22); - // printf("\treg[23]=%lu=0x%lx=@%p\n", reg_23, reg_23, ®_23); - // printf("\treg[24]=%lu=0x%lx=@%p\n", reg_24, reg_24, ®_24); - // printf("\treg[25]=%lu=0x%lx=@%p\n", reg_25, reg_25, ®_25); - // printf("\treg[26]=%lu=0x%lx=@%p\n", reg_26, reg_26, ®_26); - // printf("\treg[27]=%lu=0x%lx=@%p\n", reg_27, reg_27, ®_27); - // printf("\treg[28]=%lu=0x%lx=@%p\n", reg_28, reg_28, ®_28); - // printf("\treg[29]=%lu=0x%lx=@%p\n", reg_29, reg_29, ®_29); - // printf("\treg[30]=%lu=0x%lx=@%p\n", reg_30, reg_30, ®_30); - // printf("\treg[31]=%lu=0x%lx=@%p\n", reg_31, reg_31, ®_31); - // printf("\treg[32]=%lu=0x%lx=@%p\n", reg_32, reg_32, ®_32); - // printf("\treg[33]=%lu=0x%lx=@%p\n", reg_33, reg_33, ®_33); - // printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); - // printf("\n"); +#ifdef PRINT_REGS + printf("print_regs()\n"); + printf("\treg[ 0]=%lu=0x%lx=@%p\n", reg_0, reg_0, ®_0); + printf("\treg[ 1]=%lu=0x%lx=@%p\n", reg_1, reg_1, ®_1); + printf("\treg[ 2]=%lu=0x%lx=@%p\n", reg_2, reg_2, ®_2); + printf("\treg[ 3]=%lu=0x%lx=@%p\n", reg_3, reg_3, ®_3); + printf("\treg[ 4]=%lu=0x%lx=@%p\n", reg_4, reg_4, ®_4); + printf("\treg[ 5]=%lu=0x%lx=@%p\n", reg_5, reg_5, ®_5); + printf("\treg[ 6]=%lu=0x%lx=@%p\n", reg_6, reg_6, ®_6); + printf("\treg[ 7]=%lu=0x%lx=@%p\n", reg_7, reg_7, ®_7); + printf("\treg[ 8]=%lu=0x%lx=@%p\n", reg_8, reg_8, ®_8); + printf("\treg[ 9]=%lu=0x%lx=@%p\n", reg_9, reg_9, ®_9); + printf("\treg[10]=%lu=0x%lx=@%p\n", reg_10, reg_10, ®_10); + printf("\treg[11]=%lu=0x%lx=@%p\n", reg_11, reg_11, ®_11); + printf("\treg[12]=%lu=0x%lx=@%p\n", reg_12, reg_12, ®_12); + printf("\treg[13]=%lu=0x%lx=@%p\n", reg_13, reg_13, ®_13); + printf("\treg[14]=%lu=0x%lx=@%p\n", reg_14, reg_14, ®_14); + printf("\treg[15]=%lu=0x%lx=@%p\n", reg_15, reg_15, ®_15); + printf("\treg[16]=%lu=0x%lx=@%p\n", reg_16, reg_16, ®_16); + printf("\treg[17]=%lu=0x%lx=@%p\n", reg_17, reg_17, ®_17); + printf("\treg[18]=%lu=0x%lx=@%p\n", reg_18, reg_18, ®_18); + printf("\treg[19]=%lu=0x%lx=@%p\n", reg_19, reg_19, ®_19); + printf("\treg[20]=%lu=0x%lx=@%p\n", reg_20, reg_20, ®_20); + printf("\treg[21]=%lu=0x%lx=@%p\n", reg_21, reg_21, ®_21); + printf("\treg[22]=%lu=0x%lx=@%p\n", reg_22, reg_22, ®_22); + printf("\treg[23]=%lu=0x%lx=@%p\n", reg_23, reg_23, ®_23); + printf("\treg[24]=%lu=0x%lx=@%p\n", reg_24, reg_24, ®_24); + printf("\treg[25]=%lu=0x%lx=@%p\n", reg_25, reg_25, ®_25); + printf("\treg[26]=%lu=0x%lx=@%p\n", reg_26, reg_26, ®_26); + printf("\treg[27]=%lu=0x%lx=@%p\n", reg_27, reg_27, ®_27); + printf("\treg[28]=%lu=0x%lx=@%p\n", reg_28, reg_28, ®_28); + printf("\treg[29]=%lu=0x%lx=@%p\n", reg_29, reg_29, ®_29); + printf("\treg[30]=%lu=0x%lx=@%p\n", reg_30, reg_30, ®_30); + printf("\treg[31]=%lu=0x%lx=@%p\n", reg_31, reg_31, ®_31); + printf("\treg[32]=%lu=0x%lx=@%p\n", reg_32, reg_32, ®_32); + printf("\treg[33]=%lu=0x%lx=@%p\n", reg_33, reg_33, ®_33); + printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); + printf("\n"); +#endif } -//struct timeval print_pc_tv; +//#define PRINT_PC_DURATION +#ifdef PRINT_PC_DURATION +struct timeval print_pc_tv; +#endif + extern int _print_pc (uint64_t pc, uint64_t c) { - // print_pc_counter++; - // { - // struct timeval tv; - // gettimeofday(&tv, NULL); - // uint64_t duration = TimeDiff(print_pc_tv, tv); - // if (duration > 900) - // { - // uint64_t chunk = print_pc_counter / chunk_size; - // printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); - // fflush(stdout); - // } - // print_pc_tv = tv; - // } +#ifdef PRINT_PC_DURATION + print_pc_counter++; + { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t duration = TimeDiff(print_pc_tv, tv); + if (duration > 900) + { + uint64_t chunk = print_pc_counter / chunk_size; + printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); + fflush(stdout); + } + print_pc_tv = tv; + } +#endif + printf("s=%lu pc=%lx c=%lx", print_pc_counter, pc, c); - /* Used for debugging + +//#define PRINT_PC_REGS +#ifdef PRINT_PC_REGS + /* Used for debugging */ printf(" r0=%lx", reg_0); printf(" r1=%lx", reg_1); printf(" r2=%lx", reg_2); @@ -4509,36 +4524,54 @@ extern int _print_pc (uint64_t pc, uint64_t c) printf(" r29=%lx", reg_29); printf(" r30=%lx", reg_30); printf(" r31=%lx", reg_31); - */ +#endif + printf("\n"); fflush(stdout); print_pc_counter++; } -// uint64_t chunk_done_counter = 0; -// struct timeval chunk_done_tv; -// struct timeval sync_start, sync_stop; -// uint64_t sync_duration = 0; +//#define CHUNK_DONE_DURATION +#ifdef CHUNK_DONE_DURATION +uint64_t chunk_done_counter = 0; +struct timeval chunk_done_tv; +#endif + +//#define CHUNK_DONE_SYNC_DURATION +#ifdef CHUNK_DONE_SYNC_DURATION +struct timeval sync_start, sync_stop; +uint64_t sync_duration = 0; +#endif + extern void _chunk_done() { - // chunk_done_counter++; - // if ((chunk_done_counter & 0xFF) == 0) - // { - // struct timeval tv; - // gettimeofday(&tv, NULL); - // uint64_t duration = TimeDiff(chunk_done_tv, tv); - // if (duration > 5000) - // { - // printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); - // fflush(stdout); - // } - // chunk_done_tv = tv; - // } - //gettimeofday(&sync_start, NULL); +#ifdef CHUNK_DONE_DURATION + chunk_done_counter++; + if ((chunk_done_counter & 0xFF) == 0) + { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t duration = TimeDiff(chunk_done_tv, tv); + if (duration > 5000) + { + printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); + fflush(stdout); + } + chunk_done_tv = tv; + } +#endif + +#ifdef CHUNK_DONE_SYNC_DURATION + gettimeofday(&sync_start, NULL); +#endif + __sync_synchronize(); - // gettimeofday(&sync_stop, NULL); - // sync_duration += TimeDiff(sync_start, sync_stop); - // printf("chunk_done() sync_duration=%lu\n", sync_duration); + +#ifdef CHUNK_DONE_SYNC_DURATION + gettimeofday(&sync_stop, NULL); + sync_duration += TimeDiff(sync_start, sync_stop); + printf("chunk_done() sync_duration=%lu\n", sync_duration); +#endif // Notify the caller that a new chunk is done and its trace is ready to be consumed assert(call_chunk_done); From 1763102a971c1fccda1d355d4f8eeb130be4ed0b Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Mon, 16 Feb 2026 20:06:07 +0000 Subject: [PATCH 532/782] Refactor hint buffer and macros --- .gitignore | 2 +- ziskos/entrypoint/src/hints/hint_buffer.rs | 435 +++++++++++++++------ ziskos/entrypoint/src/hints/macros.rs | 102 ++--- ziskos/entrypoint/src/hints/mod.rs | 42 +- 4 files changed, 375 insertions(+), 206 deletions(-) diff --git a/.gitignore b/.gitignore index 908ce0edc..fa85094b1 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ state-machines/arith/src/arith_frops_fixed.bin state-machines/binary/src/binary_basic_frops_fixed.bin state-machines/binary/src/binary_extension_frops_fixed.bin reth-inputs/ -hints/ \ No newline at end of file +/hints \ No newline at end of file diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index 2631f49be..84ff79375 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -1,64 +1,187 @@ -use bytes::{Bytes, BytesMut}; -use std::io::{self, Write}; +use std::collections::VecDeque; +use std::fs::File; +use std::io::{self, Read, Write}; use std::sync::{Arc, Condvar, Mutex}; -pub const DEFAULT_BUFFER_LEN: usize = 1 << 20; // 1 MiB - // TODO: Set MAX_WRITE_LEN based on writer type (file or socket) -pub const MAX_WRITER_LEN: usize = 128 * 1024; // 128KB is the max write size for Unix sockets -pub const HEADER_LEN: usize = 8; +pub const MAX_WRITER_LEN: usize = 128 * 1024; pub struct HintBuffer { inner: Mutex, not_empty: Condvar, } +struct RefVerify { + file: File, + off: u64, + scratch: Vec, +} + struct HintBufferInner { - buf: BytesMut, - commit_pos: usize, + cur: Vec, + queue: VecDeque>, closed: bool, paused: bool, - // counter: u64, + + // Reference verification state (DEBUG_HINTS_REF) + ref_verify: Option, } pub fn build_hint_buffer() -> Arc { Arc::new(HintBuffer { inner: Mutex::new(HintBufferInner { - buf: BytesMut::with_capacity(DEFAULT_BUFFER_LEN), - commit_pos: 0, + cur: Vec::new(), + queue: VecDeque::new(), closed: true, - paused: false, - // counter: 0, + paused: true, + ref_verify: None, }), not_empty: Condvar::new(), }) } -impl HintBufferInner { +impl HintBuffer { #[inline(always)] - fn write_bytes(&mut self, src: &[u8]) { - self.buf.extend_from_slice(src); + fn build_header_u64(hint_id: u32, len: usize, is_result: bool) -> u64 { + let len32: u32 = len + .try_into() + .expect("hint len exceeds u32::MAX (protocol uses 32-bit len)"); + + let hi: u32 = (hint_id & 0x7FFF_FFFF) | (if is_result { 0x8000_0000 } else { 0 }); + ((hi as u64) << 32) | (len32 as u64) } #[inline(always)] - fn commit(&mut self) { - self.commit_pos = self.buf.len(); + fn pad8(len: usize) -> usize { + (8 - (len & 7)) & 7 + } + + /// Lazily init ref verification (DEBUG_HINTS_REF) if present. + /// Also verifies START marker immediately on init. + fn ensure_ref_verify(g: &mut HintBufferInner) { + if g.ref_verify.is_some() { + return; + } + + let file_name = match std::env::var("DEBUG_HINTS_REF") { + Ok(s) if !s.is_empty() => s, + _ => return, // ref verification disabled + }; + + println!("DEBUG_HINTS_REF: opening reference file '{}'", file_name); + let mut rv = RefVerify { + file: File::open(&file_name).unwrap_or_else(|e| { + panic!("Failed to open DEBUG_HINTS_REF '{}': {}", file_name, e) + }), + off: 0, + scratch: Vec::new(), + }; + + // Verify START marker (u64 = 0) at the beginning of the stream + let start = 0u64.to_le_bytes(); + Self::verify_against_ref_impl(&mut rv, &start); + + g.ref_verify = Some(rv); + } + + fn verify_against_ref(g: &mut HintBufferInner, data: &[u8]) { + if g.ref_verify.is_none() { + return; + } + let rv = g.ref_verify.as_mut().unwrap(); + Self::verify_against_ref_impl(rv, data); + } + + fn verify_against_ref_impl(rv: &mut RefVerify, data: &[u8]) { + // Reuse scratch to avoid reallocations + rv.scratch.resize(data.len(), 0u8); + rv.file + .read_exact(&mut rv.scratch) + .unwrap_or_else(|e| { + let tid = std::thread::current().id(); + panic!( + "DEBUG_HINTS_REF mismatch: failed to read {} bytes at offset {}, threadid: {:?}: {}", + data.len(), + rv.off, + tid, + e + ) + }); + + if rv.scratch != data { + let mut i = 0usize; + while i < data.len() && rv.scratch[i] == data[i] { + i += 1; + } + let got = data[i]; + let exp = rv.scratch[i]; + let tid = std::thread::current().id(); + panic!( + "DEBUG_HINTS_REF mismatch at ref stream offset {} (chunk idx {}): expected 0x{:02x}, got 0x{:02x}, threadid: {:?}", + rv.off + i as u64, + i, + exp, + got, + tid + ); + } + + rv.off += data.len() as u64; + } + + fn finalize_ref_verify(g: &mut HintBufferInner) { + let Some(rv) = g.ref_verify.as_mut() else { + return; + }; + + // Verify END marker + let end_header: u64 = (1u64 << 32) | 0u64; + let end = end_header.to_le_bytes(); + Self::verify_against_ref_impl(rv, &end); + + // Ensure fully consumed (no trailing bytes) + let mut extra = [0u8; 1]; + if let Ok(()) = rv.file.read_exact(&mut extra) { + let tid = std::thread::current().id(); + panic!( + "DEBUG_HINTS_REF mismatch: reference file has extra trailing data starting at offset {} (next byte 0x{:02x}), threadid: {:?}", + rv.off, + extra[0], + tid + ); + } + + // Drop ref verifier + g.ref_verify = None; } -} -impl HintBuffer { pub fn close(&self) { let mut g = self.inner.lock().unwrap(); + + // If ref verification is enabled, we expect the stream to finish here. + // (If you call close() before draining / before emitting all hints, this can legitimately panic.) + Self::finalize_ref_verify(&mut g); + + g.cur.clear(); + g.queue.clear(); g.closed = true; + g.paused = true; + g.count = 0; self.not_empty.notify_all(); } pub fn reset(&self) { let mut g = self.inner.lock().unwrap(); - g.buf.clear(); - g.commit_pos = 0; + + g.cur.clear(); + g.queue.clear(); g.closed = false; g.paused = false; - // g.counter = 0; + g.count = 0; + + // Re-init reference verification for a new stream and verify START marker. + g.ref_verify = None; + Self::ensure_ref_verify(&mut g); + self.not_empty.notify_all(); } @@ -74,8 +197,7 @@ impl HintBuffer { #[inline(always)] pub fn is_paused(&self) -> bool { - let g = self.inner.lock().unwrap(); - g.paused + self.inner.lock().unwrap().paused } #[inline(always)] @@ -85,127 +207,214 @@ impl HintBuffer { } #[inline(always)] - pub fn write_hint_header(&self, hint_id: u32, len: usize, is_result: bool) { - let header = ((((if is_result { 0x8000_0000u64 } else { 0 }) | hint_id as u64) << 32) - | (len as u64)) - .to_le_bytes(); + pub unsafe fn write_hint_segments( + &self, + hint_id: u32, + segments: &[*const u8], + lengths: &[usize], + is_result: bool, + ) { + if !self.is_enabled() || !crate::hints::check_main_thread() { + return; + } + debug_assert_eq!(segments.len(), lengths.len(), "segments/lengths mismatch"); + + let mut total = 0usize; + for (&p, &l) in segments.iter().zip(lengths.iter()) { + debug_assert!(l == 0 || !p.is_null(), "null ptr with nonzero len"); + total = total.checked_add(l).expect("total len overflow"); + } + + let pad = Self::pad8(total); + let header = Self::build_header_u64(hint_id, total, is_result).to_le_bytes(); let mut g = self.inner.lock().unwrap(); - g.write_bytes(&header); + // Ensure reference verifier (and START) is ready, then compare this hint bytes right here. + Self::ensure_ref_verify(&mut g); - // g.counter += 1; + g.cur.reserve(8 + total + pad); + g.cur.extend_from_slice(&header); - // if g.counter == 32672 { - // panic!("Hint counter reached"); - // } - } + for (&p, &l) in segments.iter().zip(lengths.iter()) { + if l == 0 { + continue; + } + let s = std::slice::from_raw_parts(p, l); + g.cur.extend_from_slice(s); + } - #[inline(always)] - pub fn write_hint_data(&self, data: *const u8, len: usize) { - let payload = unsafe { std::slice::from_raw_parts(data, len) }; - self.inner.lock().unwrap().write_bytes(payload); + if pad > 0 { + const ZERO_PAD: [u8; 8] = [0; 8]; + g.cur.extend_from_slice(&ZERO_PAD[..pad]); + } + + #[cfg(zisk_hints_metrics)] + { + crate::hints::metrics::inc_hint_count(hint_id); + } + + let hint = std::mem::take(&mut g.cur); + g.cur = Vec::new(); + + Self::verify_against_ref(&mut g, &hint); + + g.queue.push_back(hint); + drop(g); + + self.not_empty.notify_one(); } #[inline(always)] - pub fn commit(&self) { + pub unsafe fn write_hint_len_prefixed_segments( + &self, + hint_id: u32, + segments: &[*const u8], + lengths: &[usize], + is_result: bool, + ) { + if !self.is_enabled() || !crate::hints::check_main_thread() { + return; + } + debug_assert_eq!(segments.len(), lengths.len(), "segments/lengths mismatch"); + + // total payload = sum(8 + len_i) + let mut total = 0usize; + for (&p, &l) in segments.iter().zip(lengths.iter()) { + debug_assert!(l == 0 || !p.is_null(), "null ptr with nonzero len"); + total = total.checked_add(8 + l).expect("total len overflow"); + } + + let pad = Self::pad8(total); + let header = Self::build_header_u64(hint_id, total, is_result).to_le_bytes(); + let mut g = self.inner.lock().unwrap(); - g.commit(); + + crate::hints::check_main_thread(); + debug_assert!(g.cur.is_empty(), "cur not empty at emit start"); + + // Ensure reference verifier (and START) is ready, then compare this hint bytes right here. + Self::ensure_ref_verify(&mut g); + + g.cur.reserve(8 + total + pad); + g.cur.extend_from_slice(&header); + + for (&p, &l) in segments.iter().zip(lengths.iter()) { + let len_bytes = (l as u64).to_le_bytes(); + g.cur.extend_from_slice(&len_bytes); + + if l != 0 { + let s = std::slice::from_raw_parts(p, l); + g.cur.extend_from_slice(s); + } + } + + if pad > 0 { + const ZERO_PAD: [u8; 8] = [0; 8]; + g.cur.extend_from_slice(&ZERO_PAD[..pad]); + } + + #[cfg(zisk_hints_metrics)] + { + crate::hints::metrics::inc_hint_count(hint_id); + } + + let hint = std::mem::take(&mut g.cur); + g.cur = Vec::new(); + + Self::verify_against_ref(&mut g, &hint); + + g.queue.push_back(hint); + drop(g); + self.not_empty.notify_one(); } pub fn drain_to_writer(&self, writer: &mut W) -> io::Result<()> { + let mut debug_file = match std::env::var("DEBUG_HINTS_FILE") { + Ok(file_name) => { + if !file_name.is_empty() { + println!("DEBUG_HINTS_FILE: opening debug output file '{}'", file_name); + match File::create(&file_name) { + Ok(f) => Some(f), + Err(e) => { + eprintln!("Failed to open DEBUG_HINTS_FILE '{}': {}", file_name, e); + None + } + } + } else { + None + } + } + _ => None, + }; + + // (Opcional) si quieres que DEBUG_HINTS_FILE incluya START: + if let Some(f) = debug_file.as_mut() { + f.write_all(&0u64.to_le_bytes())?; + } + + let mut out_buf: Vec = Vec::with_capacity(MAX_WRITER_LEN); + loop { - // Get chunk of hints to write from HintBuffer(under lock) - let chunk: Bytes = { + let hint: Option> = { let mut g = self.inner.lock().unwrap(); - while g.commit_pos == 0 && !g.closed { + while g.queue.is_empty() && !g.closed { g = self.not_empty.wait(g).unwrap(); } - if g.commit_pos == 0 && g.closed { - return Ok(()); + if g.queue.is_empty() && g.closed { + None + } else { + Some(g.queue.pop_front().unwrap()) } - - let n = g.commit_pos; - g.commit_pos = 0; - g.buf.split_to(n).freeze() }; - // Write hints from chunk without holding the lock - let mut chunk_pos = 0usize; - let chunk_len = chunk.len(); - let chunk_base = chunk.as_ptr(); - - // Start and end of the write buffer (MAX_WRITER_LEN) - let mut buf_start = 0usize; - let mut buf_end = 0usize; - - while chunk_pos < chunk_len { - let hint_header = unsafe { - let header_bytes = core::slice::from_raw_parts(chunk_base.add(chunk_pos), 8); - u64::from_le_bytes(header_bytes.try_into().unwrap()) - }; - - #[cfg(zisk_hints_metrics)] - { - let hint_id = (hint_header >> 32) as u32 & 0x7FFF_FFFF; - crate::hints::metrics::inc_hint_count(hint_id); + let Some(hint_bytes) = hint else { + if !out_buf.is_empty() { + writer.write_all(&out_buf)?; + if let Some(f) = debug_file.as_mut() { + f.write_all(&out_buf)?; + f.flush()?; + } + out_buf.clear(); } - let hint_data_len = (hint_header & 0xFFFF_FFFF) as usize; - let pad = (8 - (hint_data_len & 7)) & 7; - let hint_len = HEADER_LEN + hint_data_len + pad; + if let Some(f) = debug_file.as_mut() { + let end_header: u64 = (1u64 << 32) | 0u64; + f.write_all(&end_header.to_le_bytes())?; + f.flush()?; + } - // If adding this hint exceeds max write size, flush current write buffer - if buf_end - buf_start + hint_len > MAX_WRITER_LEN { - let buf: &[u8] = unsafe { - core::slice::from_raw_parts(chunk_base.add(buf_start), buf_end - buf_start) - }; - writer.write_all(buf)?; + return Ok(()); + }; - // Reset write buffer - buf_start = chunk_pos; - buf_end = chunk_pos; + if !out_buf.is_empty() && out_buf.len() + hint_bytes.len() > MAX_WRITER_LEN { + writer.write_all(&out_buf)?; + if let Some(f) = debug_file.as_mut() { + f.write_all(&out_buf)?; } + out_buf.clear(); + } + + if hint_bytes.len() > MAX_WRITER_LEN { + let mut off = 0usize; + while off < hint_bytes.len() { + let n = std::cmp::min(MAX_WRITER_LEN, hint_bytes.len() - off); + let part = &hint_bytes[off..off + n]; - // If single hint exceeds MAX_WRITER_LEN, write it in chunks - if hint_len > MAX_WRITER_LEN { - let mut hint_pos = 0usize; - while hint_pos < hint_len { - let chunk_size = std::cmp::min(MAX_WRITER_LEN, hint_len - hint_pos); - let hint_bytes: &[u8] = unsafe { - core::slice::from_raw_parts( - chunk_base.add(chunk_pos + hint_pos), - chunk_size, - ) - }; - - writer.write_all(hint_bytes)?; - - hint_pos += chunk_size; + writer.write_all(part)?; + if let Some(f) = debug_file.as_mut() { + f.write_all(part)?; } - // Advance to next hint - chunk_pos += hint_len; - // Reset write buffer - buf_start = chunk_pos; - buf_end = chunk_pos; - } else { - // Accumulate current hint into write buffer - buf_end += hint_len; - // Advance to next hint - chunk_pos += hint_len; + + off += n; } + continue; } - // Flush any remaining data in write buffer - if buf_end > buf_start { - let buf: &[u8] = unsafe { - core::slice::from_raw_parts(chunk_base.add(buf_start), buf_end - buf_start) - }; - writer.write_all(buf)?; - } + out_buf.extend_from_slice(&hint_bytes); } } } diff --git a/ziskos/entrypoint/src/hints/macros.rs b/ziskos/entrypoint/src/hints/macros.rs index aff8654af..f49e71d11 100644 --- a/ziskos/entrypoint/src/hints/macros.rs +++ b/ziskos/entrypoint/src/hints/macros.rs @@ -13,25 +13,15 @@ macro_rules! define_hint { return; } - #[cfg(zisk_hints_single_thread)] - crate::hints::check_main_thread(); + let segs: &[*const u8] = &[$( $arg ),+]; + let lens: &[usize] = &[$( $len ),+]; - let mut total_len = 0; - $( - total_len += $len; - )+ - - crate::hints::HINT_BUFFER.write_hint_header( + crate::hints::HINT_BUFFER.write_hint_segments( $hint_id, - total_len, + segs, + lens, $is_result, ); - - $( - $crate::hints::HINT_BUFFER.write_hint_data($arg, $len); - )+ - - $crate::hints::HINT_BUFFER.commit(); } $crate::hints::macros::register_hint_meta!($name, $hint_id); @@ -49,26 +39,23 @@ macro_rules! define_hint_pairs { ) => { paste::paste! { #[no_mangle] - pub unsafe extern "C" fn []( pairs: *const u8, num_pairs: usize) { + pub unsafe extern "C" fn [](pairs: *const u8, num_pairs: usize) { if !crate::hints::HINT_BUFFER.is_enabled() { return; } - #[cfg(zisk_hints_single_thread)] - crate::hints::check_main_thread(); - - crate::hints::HINT_BUFFER.write_hint_header( - $hint_id, - 8 + (num_pairs * $pair_len), - false, - ); - let num_pairs_bytes: [u8; 8] = (num_pairs as u64).to_le_bytes(); - crate::hints::HINT_BUFFER.write_hint_data(num_pairs_bytes.as_ptr(), num_pairs_bytes.len()); + let pairs_len = num_pairs * ($pair_len as usize); - crate::hints::HINT_BUFFER.write_hint_data(pairs, num_pairs * $pair_len); + let segs: &[*const u8] = &[num_pairs_bytes.as_ptr(), pairs]; + let lens: &[usize] = &[num_pairs_bytes.len(), pairs_len]; - $crate::hints::HINT_BUFFER.commit(); + crate::hints::HINT_BUFFER.write_hint_segments( + $hint_id, + segs, + lens, + $is_result, + ); } $crate::hints::macros::register_hint_meta!($name, $hint_id); @@ -86,34 +73,29 @@ macro_rules! define_hint_ptr { ) => { paste::paste! { #[no_mangle] - pub unsafe extern "C" fn []([<$arg _ptr>]: *const u8, [<$arg _len>]: usize) { + pub unsafe extern "C" fn []( + [<$arg _ptr>]: *const u8, + [<$arg _len>]: usize + ) { if !crate::hints::HINT_BUFFER.is_enabled() { return; } - #[cfg(zisk_hints_single_thread)] - crate::hints::check_main_thread(); + let segs: &[*const u8] = &[ [<$arg _ptr>] ]; + let lens: &[usize] = &[ [<$arg _len>] ]; - let pad = (8 - ([<$arg _len>] & 7)) & 7; - - crate::hints::HINT_BUFFER.write_hint_header( + crate::hints::HINT_BUFFER.write_hint_segments( $hint_id, - [<$arg _len>], + segs, + lens, $is_result, ); - - crate::hints::HINT_BUFFER.write_hint_data([<$arg _ptr>], [<$arg _len>]); - if pad > 0 { - const ZERO_PAD: [u8; 8] = [0; 8]; - crate::hints::HINT_BUFFER.write_hint_data(ZERO_PAD.as_ptr(), pad); - } - - $crate::hints::HINT_BUFFER.commit(); } $crate::hints::macros::register_hint_meta!($name, $hint_id); } }; + ( $name:ident => { hint_id: $hint_id:expr, @@ -123,42 +105,22 @@ macro_rules! define_hint_ptr { ) => { paste::paste! { #[no_mangle] - pub unsafe extern "C" fn []($( [<$arg _ptr>]: *const u8, [<$arg _len>]: usize ),+ + pub unsafe extern "C" fn []( + $( [<$arg _ptr>]: *const u8, [<$arg _len>]: usize ),+ ) { if !crate::hints::HINT_BUFFER.is_enabled() { return; } - #[cfg(zisk_hints_single_thread)] - crate::hints::check_main_thread(); + let segs: &[*const u8] = &[$( [<$arg _ptr>] ),+]; + let lens: &[usize] = &[$( [<$arg _len>] ),+]; - let mut total_len = 0; - $( - total_len += 8 + [<$arg _len>]; - )+ - - let pad = (8 - (total_len & 7)) & 7; - - crate::hints::HINT_BUFFER.write_hint_header( + crate::hints::HINT_BUFFER.write_hint_len_prefixed_segments( $hint_id, - total_len, + segs, + lens, $is_result, ); - - $( - { - let len_bytes: [u8; 8] = ([<$arg _len>] as u64).to_le_bytes(); - crate::hints::HINT_BUFFER.write_hint_data(len_bytes.as_ptr(), len_bytes.len()); - crate::hints::HINT_BUFFER.write_hint_data([<$arg _ptr>], [<$arg _len>]); - } - )+ - - if pad > 0 { - const ZERO_PAD: [u8; 8] = [0; 8]; - crate::hints::HINT_BUFFER.write_hint_data(ZERO_PAD.as_ptr(), pad); - } - - $crate::hints::HINT_BUFFER.commit(); } $crate::hints::macros::register_hint_meta!($name, $hint_id); diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index a7aa86992..ab3e10dac 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -21,7 +21,7 @@ use std::thread::{self, JoinHandle}; use std::{ffi::CStr, os::raw::c_char}; use std::{ io::{self, BufWriter, Write}, - sync::Arc, + sync::{Arc, Mutex}, }; use tokio::sync::oneshot; use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; @@ -74,7 +74,11 @@ impl HintFileWriterHandleCell { pub fn init_hints() -> io::Result<()> { // Initialize the main thread ID for single-threaded assert (if enabled) #[cfg(zisk_hints_single_thread)] - let _ = MAIN_TID.set(None); // Placeholder value to mark uninitialized + { + let tid = std::thread::current().id(); + *MAIN_TID.lock().unwrap() = Some(tid); + println!("Initializing MAIN_TID to {:?}", tid); + } if let Some(handle) = HINT_WRITER_HANDLE.take() { HINT_BUFFER.close(); @@ -141,6 +145,8 @@ pub fn init_hints_socket( Ok(()) } pub fn close_hints() -> io::Result<()> { + *MAIN_TID.lock().unwrap() = None; + HINT_BUFFER.close(); let handle = HINT_WRITER_HANDLE.take(); @@ -239,33 +245,25 @@ fn write_hints_to_socket(mut socket_writer: UnixSocketWriter) -> io::Result<()> } #[cfg(zisk_hints_single_thread)] -static MAIN_TID: OnceCell> = OnceCell::new(); +static MAIN_TID: Mutex> = Mutex::new(None); #[cfg(zisk_hints_single_thread)] #[inline(always)] -pub(crate) fn check_main_thread() { - // Panic on calls from a different thread +pub(crate) fn check_main_thread() -> bool { let tid = std::thread::current().id(); - match MAIN_TID.get() { - Some(main) => { - match main { - Some(main) => { - if *main != tid { - panic!( - "Precompile hint function called from non-main thread, main={:?}, current={:?}", - main, tid - ); - } - } - None => { - // If not initialized yet, record the first caller thread as main - let _ = MAIN_TID.set(Some(tid)); - } + let guard = MAIN_TID.lock().unwrap(); + + match *guard { + Some(main_tid) => { + if main_tid != tid { + println!("Warning: trying to write hint from thread {:?} but MAIN_TID is {:?}. Ignoring...", tid, main_tid); + return false; } + true } None => { - // If not initialized yet, record the first caller thread as main - let _ = MAIN_TID.set(Some(tid)); + println!("Warning: trying to write hint from thread {:?} before MAIN_TID is initialized. Ignoring...", tid); + false } } } From 6fd951082204b216fa4b030dfc4c4732460def03 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Mon, 16 Feb 2026 21:37:24 +0000 Subject: [PATCH 533/782] Refactor hint buffer --- ziskos/entrypoint/src/hints/hint_buffer.rs | 482 +++++++-------------- ziskos/entrypoint/src/hints/macros.rs | 105 +++-- ziskos/entrypoint/src/hints/mod.rs | 25 +- 3 files changed, 251 insertions(+), 361 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index 84ff79375..0808acf44 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -1,187 +1,72 @@ -use std::collections::VecDeque; +use bytes::{Bytes, BytesMut}; use std::fs::File; -use std::io::{self, Read, Write}; +use std::io::{self, Write}; use std::sync::{Arc, Condvar, Mutex}; -pub const MAX_WRITER_LEN: usize = 128 * 1024; +pub const DEFAULT_BUFFER_LEN: usize = 1 << 20; // 1 MiB + // TODO: Set MAX_WRITE_LEN based on writer type (file or socket) +pub const MAX_WRITER_LEN: usize = 128 * 1024; // 128KB is the max write size for Unix sockets +pub const HEADER_LEN: usize = 8; pub struct HintBuffer { inner: Mutex, not_empty: Condvar, } -struct RefVerify { - file: File, - off: u64, - scratch: Vec, -} - struct HintBufferInner { - cur: Vec, - queue: VecDeque>, + buf: BytesMut, + commit_pos: usize, closed: bool, paused: bool, + // counter: u64, +} - // Reference verification state (DEBUG_HINTS_REF) - ref_verify: Option, +pub struct HintWrite<'a> { + hb: &'a HintBuffer, + g: MutexGuard<'a, HintBufferInner>, } pub fn build_hint_buffer() -> Arc { Arc::new(HintBuffer { inner: Mutex::new(HintBufferInner { - cur: Vec::new(), - queue: VecDeque::new(), + buf: BytesMut::with_capacity(DEFAULT_BUFFER_LEN), + commit_pos: 0, closed: true, - paused: true, - ref_verify: None, + paused: false, + // counter: 0, }), not_empty: Condvar::new(), }) } -impl HintBuffer { +impl HintBufferInner { #[inline(always)] - fn build_header_u64(hint_id: u32, len: usize, is_result: bool) -> u64 { - let len32: u32 = len - .try_into() - .expect("hint len exceeds u32::MAX (protocol uses 32-bit len)"); - - let hi: u32 = (hint_id & 0x7FFF_FFFF) | (if is_result { 0x8000_0000 } else { 0 }); - ((hi as u64) << 32) | (len32 as u64) + fn write_bytes(&mut self, src: &[u8]) { + self.buf.extend_from_slice(src); } #[inline(always)] - fn pad8(len: usize) -> usize { - (8 - (len & 7)) & 7 - } - - /// Lazily init ref verification (DEBUG_HINTS_REF) if present. - /// Also verifies START marker immediately on init. - fn ensure_ref_verify(g: &mut HintBufferInner) { - if g.ref_verify.is_some() { - return; - } - - let file_name = match std::env::var("DEBUG_HINTS_REF") { - Ok(s) if !s.is_empty() => s, - _ => return, // ref verification disabled - }; - - println!("DEBUG_HINTS_REF: opening reference file '{}'", file_name); - let mut rv = RefVerify { - file: File::open(&file_name).unwrap_or_else(|e| { - panic!("Failed to open DEBUG_HINTS_REF '{}': {}", file_name, e) - }), - off: 0, - scratch: Vec::new(), - }; - - // Verify START marker (u64 = 0) at the beginning of the stream - let start = 0u64.to_le_bytes(); - Self::verify_against_ref_impl(&mut rv, &start); - - g.ref_verify = Some(rv); - } - - fn verify_against_ref(g: &mut HintBufferInner, data: &[u8]) { - if g.ref_verify.is_none() { - return; - } - let rv = g.ref_verify.as_mut().unwrap(); - Self::verify_against_ref_impl(rv, data); - } - - fn verify_against_ref_impl(rv: &mut RefVerify, data: &[u8]) { - // Reuse scratch to avoid reallocations - rv.scratch.resize(data.len(), 0u8); - rv.file - .read_exact(&mut rv.scratch) - .unwrap_or_else(|e| { - let tid = std::thread::current().id(); - panic!( - "DEBUG_HINTS_REF mismatch: failed to read {} bytes at offset {}, threadid: {:?}: {}", - data.len(), - rv.off, - tid, - e - ) - }); - - if rv.scratch != data { - let mut i = 0usize; - while i < data.len() && rv.scratch[i] == data[i] { - i += 1; - } - let got = data[i]; - let exp = rv.scratch[i]; - let tid = std::thread::current().id(); - panic!( - "DEBUG_HINTS_REF mismatch at ref stream offset {} (chunk idx {}): expected 0x{:02x}, got 0x{:02x}, threadid: {:?}", - rv.off + i as u64, - i, - exp, - got, - tid - ); - } - - rv.off += data.len() as u64; - } - - fn finalize_ref_verify(g: &mut HintBufferInner) { - let Some(rv) = g.ref_verify.as_mut() else { - return; - }; - - // Verify END marker - let end_header: u64 = (1u64 << 32) | 0u64; - let end = end_header.to_le_bytes(); - Self::verify_against_ref_impl(rv, &end); - - // Ensure fully consumed (no trailing bytes) - let mut extra = [0u8; 1]; - if let Ok(()) = rv.file.read_exact(&mut extra) { - let tid = std::thread::current().id(); - panic!( - "DEBUG_HINTS_REF mismatch: reference file has extra trailing data starting at offset {} (next byte 0x{:02x}), threadid: {:?}", - rv.off, - extra[0], - tid - ); - } - - // Drop ref verifier - g.ref_verify = None; + fn commit(&mut self) { + self.commit_pos = self.buf.len(); } +} +impl HintBuffer { pub fn close(&self) { let mut g = self.inner.lock().unwrap(); - - // If ref verification is enabled, we expect the stream to finish here. - // (If you call close() before draining / before emitting all hints, this can legitimately panic.) - Self::finalize_ref_verify(&mut g); - - g.cur.clear(); - g.queue.clear(); + g.buf.clear(); + g.commit_pos = 0; g.closed = true; - g.paused = true; - g.count = 0; self.not_empty.notify_all(); } pub fn reset(&self) { let mut g = self.inner.lock().unwrap(); - - g.cur.clear(); - g.queue.clear(); + g.buf.clear(); + g.commit_pos = 0; g.closed = false; g.paused = false; - g.count = 0; - - // Re-init reference verification for a new stream and verify START marker. - g.ref_verify = None; - Self::ensure_ref_verify(&mut g); - + // g.counter = 0; self.not_empty.notify_all(); } @@ -197,7 +82,8 @@ impl HintBuffer { #[inline(always)] pub fn is_paused(&self) -> bool { - self.inner.lock().unwrap().paused + let g = self.inner.lock().unwrap(); + g.paused } #[inline(always)] @@ -207,214 +93,182 @@ impl HintBuffer { } #[inline(always)] - pub unsafe fn write_hint_segments( - &self, - hint_id: u32, - segments: &[*const u8], - lengths: &[usize], - is_result: bool, - ) { - if !self.is_enabled() || !crate::hints::check_main_thread() { - return; - } - debug_assert_eq!(segments.len(), lengths.len(), "segments/lengths mismatch"); - - let mut total = 0usize; - for (&p, &l) in segments.iter().zip(lengths.iter()) { - debug_assert!(l == 0 || !p.is_null(), "null ptr with nonzero len"); - total = total.checked_add(l).expect("total len overflow"); - } - - let pad = Self::pad8(total); - let header = Self::build_header_u64(hint_id, total, is_result).to_le_bytes(); + pub fn begin_hint(&self, hint_id: u32, len: usize, is_result: bool) -> HintWrite<'_> { + let header = ((((if is_result { 0x8000_0000u64 } else { 0 }) | hint_id as u64) << 32) + | (len as u64)) + .to_le_bytes(); let mut g = self.inner.lock().unwrap(); + g.write_bytes(&header); - // Ensure reference verifier (and START) is ready, then compare this hint bytes right here. - Self::ensure_ref_verify(&mut g); - - g.cur.reserve(8 + total + pad); - g.cur.extend_from_slice(&header); - - for (&p, &l) in segments.iter().zip(lengths.iter()) { - if l == 0 { - continue; - } - let s = std::slice::from_raw_parts(p, l); - g.cur.extend_from_slice(s); - } - - if pad > 0 { - const ZERO_PAD: [u8; 8] = [0; 8]; - g.cur.extend_from_slice(&ZERO_PAD[..pad]); - } - - #[cfg(zisk_hints_metrics)] - { - crate::hints::metrics::inc_hint_count(hint_id); - } - - let hint = std::mem::take(&mut g.cur); - g.cur = Vec::new(); - - Self::verify_against_ref(&mut g, &hint); - - g.queue.push_back(hint); - drop(g); - - self.not_empty.notify_one(); + HintWrite { hb: self, g } } #[inline(always)] - pub unsafe fn write_hint_len_prefixed_segments( - &self, - hint_id: u32, - segments: &[*const u8], - lengths: &[usize], - is_result: bool, - ) { - if !self.is_enabled() || !crate::hints::check_main_thread() { - return; - } - debug_assert_eq!(segments.len(), lengths.len(), "segments/lengths mismatch"); + pub fn write_hint_data(&self, data: *const u8, len: usize) { + let payload = unsafe { std::slice::from_raw_parts(data, len) }; + self.inner.lock().unwrap().write_bytes(payload); + } - // total payload = sum(8 + len_i) - let mut total = 0usize; - for (&p, &l) in segments.iter().zip(lengths.iter()) { - debug_assert!(l == 0 || !p.is_null(), "null ptr with nonzero len"); - total = total.checked_add(8 + l).expect("total len overflow"); - } + #[inline(always)] + pub fn write_hint_start(&self) { + self.write_hint_header(HINT_START, 0, false); + self.commit(); + } - let pad = Self::pad8(total); - let header = Self::build_header_u64(hint_id, total, is_result).to_le_bytes(); + #[inline(always)] + pub fn write_hint_end(&self) { + self.write_hint_header(HINT_END, 0, false); + self.commit(); + } + #[inline(always)] + pub fn commit(&self) { let mut g = self.inner.lock().unwrap(); - - crate::hints::check_main_thread(); - debug_assert!(g.cur.is_empty(), "cur not empty at emit start"); - - // Ensure reference verifier (and START) is ready, then compare this hint bytes right here. - Self::ensure_ref_verify(&mut g); - - g.cur.reserve(8 + total + pad); - g.cur.extend_from_slice(&header); - - for (&p, &l) in segments.iter().zip(lengths.iter()) { - let len_bytes = (l as u64).to_le_bytes(); - g.cur.extend_from_slice(&len_bytes); - - if l != 0 { - let s = std::slice::from_raw_parts(p, l); - g.cur.extend_from_slice(s); - } - } - - if pad > 0 { - const ZERO_PAD: [u8; 8] = [0; 8]; - g.cur.extend_from_slice(&ZERO_PAD[..pad]); - } - - #[cfg(zisk_hints_metrics)] - { - crate::hints::metrics::inc_hint_count(hint_id); - } - - let hint = std::mem::take(&mut g.cur); - g.cur = Vec::new(); - - Self::verify_against_ref(&mut g, &hint); - - g.queue.push_back(hint); - drop(g); - + g.commit(); self.not_empty.notify_one(); } pub fn drain_to_writer(&self, writer: &mut W) -> io::Result<()> { let mut debug_file = match std::env::var("DEBUG_HINTS_FILE") { - Ok(file_name) => { - if !file_name.is_empty() { - println!("DEBUG_HINTS_FILE: opening debug output file '{}'", file_name); - match File::create(&file_name) { - Ok(f) => Some(f), - Err(e) => { - eprintln!("Failed to open DEBUG_HINTS_FILE '{}': {}", file_name, e); - None - } - } - } else { - None - } - } - _ => None, + Ok(file_name) => Some(File::create(&file_name)?), + Err(_) => None, }; - // (Opcional) si quieres que DEBUG_HINTS_FILE incluya START: - if let Some(f) = debug_file.as_mut() { - f.write_all(&0u64.to_le_bytes())?; - } - - let mut out_buf: Vec = Vec::with_capacity(MAX_WRITER_LEN); - loop { - let hint: Option> = { + // Get chunk of hints to write from HintBuffer(under lock) + let chunk: Bytes = { let mut g = self.inner.lock().unwrap(); - while g.queue.is_empty() && !g.closed { + while g.commit_pos == 0 && !g.closed { g = self.not_empty.wait(g).unwrap(); } - if g.queue.is_empty() && g.closed { - None - } else { - Some(g.queue.pop_front().unwrap()) - } - }; - - let Some(hint_bytes) = hint else { - if !out_buf.is_empty() { - writer.write_all(&out_buf)?; + if g.commit_pos == 0 && g.closed { if let Some(f) = debug_file.as_mut() { - f.write_all(&out_buf)?; f.flush()?; } - out_buf.clear(); + return Ok(()); } - if let Some(f) = debug_file.as_mut() { - let end_header: u64 = (1u64 << 32) | 0u64; - f.write_all(&end_header.to_le_bytes())?; - f.flush()?; - } - - return Ok(()); + let n = g.commit_pos; + g.commit_pos = 0; + g.buf.split_to(n).freeze() }; - if !out_buf.is_empty() && out_buf.len() + hint_bytes.len() > MAX_WRITER_LEN { - writer.write_all(&out_buf)?; - if let Some(f) = debug_file.as_mut() { - f.write_all(&out_buf)?; + // Write hints from chunk without holding the lock + let mut chunk_pos = 0usize; + let chunk_len = chunk.len(); + let chunk_base = chunk.as_ptr(); + + // Start and end of the write buffer (MAX_WRITER_LEN) + let mut buf_start = 0usize; + let mut buf_end = 0usize; + + while chunk_pos < chunk_len { + let hint_header = unsafe { + let header_bytes = core::slice::from_raw_parts(chunk_base.add(chunk_pos), 8); + u64::from_le_bytes(header_bytes.try_into().unwrap()) + }; + + let hint_id = (hint_header >> 32) as u32 & 0x7FFF_FFFF; + #[cfg(zisk_hints_metrics)] + { + crate::hints::metrics::inc_hint_count(hint_id); } - out_buf.clear(); - } - if hint_bytes.len() > MAX_WRITER_LEN { - let mut off = 0usize; - while off < hint_bytes.len() { - let n = std::cmp::min(MAX_WRITER_LEN, hint_bytes.len() - off); - let part = &hint_bytes[off..off + n]; + let hint_data_len = (hint_header & 0xFFFF_FFFF) as usize; + let pad = (8 - (hint_data_len & 7)) & 7; + let hint_len = HEADER_LEN + hint_data_len + pad; + + // If adding this hint exceeds max write size, flush current write buffer + if buf_end - buf_start + hint_len > MAX_WRITER_LEN { + let buf: &[u8] = unsafe { + core::slice::from_raw_parts(chunk_base.add(buf_start), buf_end - buf_start) + }; + writer.write_all(buf)?; - writer.write_all(part)?; if let Some(f) = debug_file.as_mut() { - f.write_all(part)?; + f.write_all(buf)?; } - off += n; + // Reset write buffer + buf_start = chunk_pos; + buf_end = chunk_pos; + } + + // If single hint exceeds MAX_WRITER_LEN, write it in chunks + if hint_len > MAX_WRITER_LEN { + let mut hint_pos = 0usize; + while hint_pos < hint_len { + let chunk_size = std::cmp::min(MAX_WRITER_LEN, hint_len - hint_pos); + let hint_bytes: &[u8] = unsafe { + core::slice::from_raw_parts( + chunk_base.add(chunk_pos + hint_pos), + chunk_size, + ) + }; + + writer.write_all(hint_bytes)?; + + if let Some(f) = debug_file.as_mut() { + f.write_all(hint_bytes)?; + } + + hint_pos += chunk_size; + } + // Advance to next hint + chunk_pos += hint_len; + // Reset write buffer + buf_start = chunk_pos; + buf_end = chunk_pos; + } else { + // Accumulate current hint into write buffer + buf_end += hint_len; + // Advance to next hint + chunk_pos += hint_len; } - continue; } - out_buf.extend_from_slice(&hint_bytes); + // Flush any remaining data in write buffer + if buf_end > buf_start { + let buf: &[u8] = unsafe { + core::slice::from_raw_parts(chunk_base.add(buf_start), buf_end - buf_start) + }; + writer.write_all(buf)?; + + if let Some(f) = debug_file.as_mut() { + f.write_all(buf)?; + } + } } } } + +impl<'a> HintWrite<'a> { + #[inline(always)] + pub fn write_hint_data_ptr(&mut self, data: *const u8, len: usize) { + if len == 0 { + return; + } + debug_assert!(!data.is_null(), "null ptr with nonzero len"); + let payload = unsafe { std::slice::from_raw_parts(data, len) }; + self.g.write_bytes(payload); + } + + #[inline(always)] + pub fn write_hint_data_slice(&mut self, payload: &[u8]) { + if payload.is_empty() { + return; + } + self.g.write_bytes(payload); + } + + #[inline(always)] + pub fn commit(mut self) { + self.g.commit(); + + drop(self.g); + self.hb.not_empty.notify_one(); + } +} \ No newline at end of file diff --git a/ziskos/entrypoint/src/hints/macros.rs b/ziskos/entrypoint/src/hints/macros.rs index f49e71d11..f49c2b391 100644 --- a/ziskos/entrypoint/src/hints/macros.rs +++ b/ziskos/entrypoint/src/hints/macros.rs @@ -9,19 +9,29 @@ macro_rules! define_hint { paste::paste! { #[no_mangle] pub unsafe extern "C" fn []($( $arg: *const u8 ),+) { - if !crate::hints::HINT_BUFFER.is_enabled() { + if !$crate::hints::HINT_BUFFER.is_enabled() { return; } - let segs: &[*const u8] = &[$( $arg ),+]; - let lens: &[usize] = &[$( $len ),+]; + #[cfg(zisk_hints_single_thread)] + if !$crate::hints::check_main_thread() { return; } - crate::hints::HINT_BUFFER.write_hint_segments( + let mut total_len = 0usize; + $( + total_len += $len; + )+ + + let mut w = $crate::hints::HINT_BUFFER.begin_hint( $hint_id, - segs, - lens, + total_len, $is_result, ); + + $( + w.write_hint_data_ptr($arg, $len); + )+ + + w.commit(); } $crate::hints::macros::register_hint_meta!($name, $hint_id); @@ -40,22 +50,27 @@ macro_rules! define_hint_pairs { paste::paste! { #[no_mangle] pub unsafe extern "C" fn [](pairs: *const u8, num_pairs: usize) { - if !crate::hints::HINT_BUFFER.is_enabled() { + if !$crate::hints::HINT_BUFFER.is_enabled() { return; } - let num_pairs_bytes: [u8; 8] = (num_pairs as u64).to_le_bytes(); - let pairs_len = num_pairs * ($pair_len as usize); + #[cfg(zisk_hints_single_thread)] + if !$crate::hints::check_main_thread() { return; } - let segs: &[*const u8] = &[num_pairs_bytes.as_ptr(), pairs]; - let lens: &[usize] = &[num_pairs_bytes.len(), pairs_len]; + let total_len = 8 + (num_pairs * ($pair_len as usize)); - crate::hints::HINT_BUFFER.write_hint_segments( + let mut w = $crate::hints::HINT_BUFFER.begin_hint( $hint_id, - segs, - lens, + total_len, $is_result, ); + + let num_pairs_bytes: [u8; 8] = (num_pairs as u64).to_le_bytes(); + w.write_hint_data_slice(&num_pairs_bytes); + + w.write_hint_data_ptr(pairs, num_pairs * ($pair_len as usize)); + + w.commit(); } $crate::hints::macros::register_hint_meta!($name, $hint_id); @@ -73,29 +88,35 @@ macro_rules! define_hint_ptr { ) => { paste::paste! { #[no_mangle] - pub unsafe extern "C" fn []( - [<$arg _ptr>]: *const u8, - [<$arg _len>]: usize - ) { - if !crate::hints::HINT_BUFFER.is_enabled() { + pub unsafe extern "C" fn []([<$arg _ptr>]: *const u8, [<$arg _len>]: usize) { + if !$crate::hints::HINT_BUFFER.is_enabled() { return; } - let segs: &[*const u8] = &[ [<$arg _ptr>] ]; - let lens: &[usize] = &[ [<$arg _len>] ]; + #[cfg(zisk_hints_single_thread)] + if !$crate::hints::check_main_thread() { return; } + + let pad = (8 - ([<$arg _len>] & 7)) & 7; - crate::hints::HINT_BUFFER.write_hint_segments( + let mut w = $crate::hints::HINT_BUFFER.begin_hint( $hint_id, - segs, - lens, + [<$arg _len>], $is_result, ); + + w.write_hint_data_ptr([<$arg _ptr>], [<$arg _len>]); + + if pad > 0 { + const ZERO_PAD: [u8; 8] = [0; 8]; + w.write_hint_data_slice(&ZERO_PAD[..pad]); + } + + w.commit(); } $crate::hints::macros::register_hint_meta!($name, $hint_id); } }; - ( $name:ident => { hint_id: $hint_id:expr, @@ -108,19 +129,41 @@ macro_rules! define_hint_ptr { pub unsafe extern "C" fn []( $( [<$arg _ptr>]: *const u8, [<$arg _len>]: usize ),+ ) { - if !crate::hints::HINT_BUFFER.is_enabled() { + if !$crate::hints::HINT_BUFFER.is_enabled() { return; } - let segs: &[*const u8] = &[$( [<$arg _ptr>] ),+]; - let lens: &[usize] = &[$( [<$arg _len>] ),+]; + #[cfg(zisk_hints_single_thread)] + if !$crate::hints::check_main_thread() { return; } + + let mut total_len = 0usize; + $( + total_len += 8 + [<$arg _len>]; + )+ + + let pad = (8 - (total_len & 7)) & 7; - crate::hints::HINT_BUFFER.write_hint_len_prefixed_segments( + let mut w = crate::hints::HINT_BUFFER.begin_hint( $hint_id, - segs, - lens, + total_len, $is_result, ); + + $( + { + let len_bytes: [u8; 8] = ([<$arg _len>] as u64).to_le_bytes(); + w.write_hint_data_slice(&len_bytes); + + w.write_hint_data_ptr([<$arg _ptr>], [<$arg _len>]); + } + )+ + + if pad > 0 { + const ZERO_PAD: [u8; 8] = [0; 8]; + w.write_hint_data_slice(&ZERO_PAD[..pad]); + } + + w.commit(); } $crate::hints::macros::register_hint_meta!($name, $hint_id); diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index ab3e10dac..47702a546 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -102,6 +102,9 @@ pub fn init_hints() -> io::Result<()> { HINT_BUFFER.reset(); + // Write HINT_START + HINT_BUFFER.write_hint_start(); + Ok(()) } @@ -144,11 +147,17 @@ pub fn init_hints_socket( Ok(()) } + pub fn close_hints() -> io::Result<()> { *MAIN_TID.lock().unwrap() = None; + // Write HINT_END + HINT_BUFFER.write_hint_end(); + + // Close the hint buffer to signal the writer thread to finish HINT_BUFFER.close(); + // Wait for the writer thread to finish and check for errors let handle = HINT_WRITER_HANDLE.take(); if let Some(handle) = handle { match handle.join() { @@ -167,25 +176,9 @@ pub fn close_hints() -> io::Result<()> { } pub fn write_hints(writer: &mut W) -> io::Result<()> { - let disable_prefix = std::env::var("HINTS_DISABLE_PREFIX").unwrap_or_default() == "1"; - - // Write HINT_START - if !disable_prefix { - let start_header: u64 = ((HINT_START as u64) << 32) | 0u64; - let start_bytes = start_header.to_le_bytes(); - writer.write_all(&start_bytes)?; - } - // Write hints from the buffer HINT_BUFFER.drain_to_writer(writer)?; - // Write HINT_END - if !disable_prefix { - let end_header: u64 = ((HINT_END as u64) << 32) | 0u64; - let end_bytes = end_header.to_le_bytes(); - writer.write_all(&end_bytes)?; - } - writer.flush()?; #[cfg(zisk_hints_metrics)] From a534057acad0e8aeda50a06e7b4c8ac6c16aec1d Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Mon, 16 Feb 2026 23:13:00 +0000 Subject: [PATCH 534/782] Refactor hint buffer write methods to use HintWrite struct --- ziskos/entrypoint/src/hints/hint_buffer.rs | 26 +++++++--------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index 0808acf44..c2215516b 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -1,7 +1,10 @@ use bytes::{Bytes, BytesMut}; use std::fs::File; use std::io::{self, Write}; -use std::sync::{Arc, Condvar, Mutex}; +use std::sync::{Arc, Condvar, Mutex, MutexGuard}; +use zisk_common::{ + CTRL_START, CTRL_END, +}; pub const DEFAULT_BUFFER_LEN: usize = 1 << 20; // 1 MiB // TODO: Set MAX_WRITE_LEN based on writer type (file or socket) @@ -104,29 +107,16 @@ impl HintBuffer { HintWrite { hb: self, g } } - #[inline(always)] - pub fn write_hint_data(&self, data: *const u8, len: usize) { - let payload = unsafe { std::slice::from_raw_parts(data, len) }; - self.inner.lock().unwrap().write_bytes(payload); - } - #[inline(always)] pub fn write_hint_start(&self) { - self.write_hint_header(HINT_START, 0, false); - self.commit(); + let w = self.begin_hint(CTRL_START, 0, false); + w.commit(); } #[inline(always)] pub fn write_hint_end(&self) { - self.write_hint_header(HINT_END, 0, false); - self.commit(); - } - - #[inline(always)] - pub fn commit(&self) { - let mut g = self.inner.lock().unwrap(); - g.commit(); - self.not_empty.notify_one(); + let w = self.begin_hint(CTRL_END, 0, false); + w.commit(); } pub fn drain_to_writer(&self, writer: &mut W) -> io::Result<()> { From d2c3497888c58add0e23814e4a90db4b648c00b3 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Tue, 17 Feb 2026 02:10:20 +0000 Subject: [PATCH 535/782] Refactor hint buffer to support optional debug writer in drain_to_writer --- ziskos/entrypoint/src/hints/hint_buffer.rs | 66 ++++++++++++---------- ziskos/entrypoint/src/hints/mod.rs | 24 +++++--- 2 files changed, 53 insertions(+), 37 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index c2215516b..dc7bd68c9 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -57,8 +57,6 @@ impl HintBufferInner { impl HintBuffer { pub fn close(&self) { let mut g = self.inner.lock().unwrap(); - g.buf.clear(); - g.commit_pos = 0; g.closed = true; self.not_empty.notify_all(); } @@ -119,28 +117,42 @@ impl HintBuffer { w.commit(); } - pub fn drain_to_writer(&self, writer: &mut W) -> io::Result<()> { - let mut debug_file = match std::env::var("DEBUG_HINTS_FILE") { - Ok(file_name) => Some(File::create(&file_name)?), - Err(_) => None, + pub fn drain_to_writer( + &self, + writer: &mut W, + mut debug_writer: Option<&mut D>, + ) -> io::Result<()> + where + W: Write + ?Sized, + D: Write + ?Sized, + { + // Write hints from the buffer to the writer and optionally to a debug writer + let mut write_all = |buf: &[u8]| -> io::Result<()> { + writer.write_all(buf)?; + + if let Some(debug_writer) = debug_writer.as_deref_mut() { + debug_writer.write_all(buf)?; + } + + Ok(()) }; - loop { - // Get chunk of hints to write from HintBuffer(under lock) + 'drain: loop { + // Get chunk of hints to write from HintBuffer (under lock) let chunk: Bytes = { let mut g = self.inner.lock().unwrap(); + // Wait until there's data to write or buffer is closed while g.commit_pos == 0 && !g.closed { g = self.not_empty.wait(g).unwrap(); } + // If buffer is empty and closed, we're done, we can exit the drain loop if g.commit_pos == 0 && g.closed { - if let Some(f) = debug_file.as_mut() { - f.flush()?; - } - return Ok(()); + break 'drain; } + // Take the committed chunk of hints to write let n = g.commit_pos; g.commit_pos = 0; g.buf.split_to(n).freeze() @@ -176,11 +188,7 @@ impl HintBuffer { let buf: &[u8] = unsafe { core::slice::from_raw_parts(chunk_base.add(buf_start), buf_end - buf_start) }; - writer.write_all(buf)?; - - if let Some(f) = debug_file.as_mut() { - f.write_all(buf)?; - } + write_all(buf)?; // Reset write buffer buf_start = chunk_pos; @@ -199,11 +207,7 @@ impl HintBuffer { ) }; - writer.write_all(hint_bytes)?; - - if let Some(f) = debug_file.as_mut() { - f.write_all(hint_bytes)?; - } + write_all(hint_bytes)?; hint_pos += chunk_size; } @@ -220,18 +224,22 @@ impl HintBuffer { } } - // Flush any remaining data in write buffer + // Write any remaining data in write buffer if buf_end > buf_start { let buf: &[u8] = unsafe { core::slice::from_raw_parts(chunk_base.add(buf_start), buf_end - buf_start) }; - writer.write_all(buf)?; - - if let Some(f) = debug_file.as_mut() { - f.write_all(buf)?; - } + write_all(buf)?; } } + + // Flush the writer and debug writer at the end + writer.flush()?; + if let Some(debug_writer) = debug_writer.as_deref_mut() { + debug_writer.flush()?; + } + + Ok(()) } } @@ -241,7 +249,7 @@ impl<'a> HintWrite<'a> { if len == 0 { return; } - debug_assert!(!data.is_null(), "null ptr with nonzero len"); + debug_assert!(!data.is_null(), "write_hint_data_ptr called with null data pointer"); let payload = unsafe { std::slice::from_raw_parts(data, len) }; self.g.write_bytes(payload); } diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 47702a546..89bfa8c0c 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -126,6 +126,7 @@ pub fn init_hints_file( pub fn init_hints_socket( socket_path: PathBuf, + debug_file: Option, ready: Option>, ) -> io::Result<()> { init_hints()?; @@ -142,7 +143,7 @@ pub fn init_hints_socket( // TODO: Implement open timeout socket_writer.open().map_err(io::Error::other)?; - let handle = thread::spawn(move || write_hints_to_socket(socket_writer)); + let handle = thread::spawn(move || write_hints_to_socket(socket_writer, debug_file)); HINT_WRITER_HANDLE.store(handle); Ok(()) @@ -175,11 +176,9 @@ pub fn close_hints() -> io::Result<()> { } } -pub fn write_hints(writer: &mut W) -> io::Result<()> { +pub fn write_hints(writer: &mut W, debug_writer: Option<&mut dyn Write>) -> io::Result<()> { // Write hints from the buffer - HINT_BUFFER.drain_to_writer(writer)?; - - writer.flush()?; + HINT_BUFFER.drain_to_writer(writer, debug_writer)?; #[cfg(zisk_hints_metrics)] crate::hints::metrics::print_metrics(); @@ -193,7 +192,7 @@ fn write_hints_to_file(path: PathBuf) -> io::Result<()> { let file = std::fs::File::create(path)?; let mut file_writer = BufWriter::with_capacity(1 << 20, file); - write_hints(&mut file_writer)?; + write_hints(&mut file_writer, None)?; Ok(()) } @@ -227,10 +226,19 @@ impl Write for UnixSocketWriter { } } -fn write_hints_to_socket(mut socket_writer: UnixSocketWriter) -> io::Result<()> { +fn write_hints_to_socket( + mut socket_writer: UnixSocketWriter, + debug_file: Option, +) -> io::Result<()> { debug_assert!(cfg!(target_endian = "little")); - write_hints(&mut socket_writer)?; + if let Some(path) = debug_file { + let file = std::fs::File::create(path)?; + let mut debug_writer = BufWriter::with_capacity(1 << 20, file); // 1 MiB buffer + write_hints(&mut socket_writer, Some(&mut debug_writer as &mut dyn Write))?; + } else { + write_hints(&mut socket_writer, None)?; + } socket_writer.close().map_err(io::Error::other)?; From fc8d0b2c86d7818db58bd91636c2c867c614ea5e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 17 Feb 2026 08:01:42 +0000 Subject: [PATCH 536/782] added non-blocking mechanism to unix socket --- common/src/io/stream/unix_socket.rs | 607 +++++++++++++++++++--------- 1 file changed, 413 insertions(+), 194 deletions(-) diff --git a/common/src/io/stream/unix_socket.rs b/common/src/io/stream/unix_socket.rs index 24d391ba2..926bf10da 100644 --- a/common/src/io/stream/unix_socket.rs +++ b/common/src/io/stream/unix_socket.rs @@ -344,12 +344,51 @@ impl UnixSocketStreamWriter { self.listener_fd = Some(sock_fd); Ok(()) } + + /// Check if a client is currently connected. + /// + /// Returns `true` if a client is connected and ready to receive data. + /// + /// # Example + /// + /// ```no_run + /// use std::thread; + /// use std::time::Duration; + /// + /// let mut writer = UnixSocketStreamWriter::new("/tmp/my.sock")?; + /// writer.open()?; + /// + /// // Wait for client to connect + /// while !writer.is_client_connected() { + /// thread::sleep(Duration::from_millis(10)); + /// } + /// + /// // Now write will succeed + /// writer.write(b"Hello, client!")?; + /// ``` + pub fn is_client_connected(&mut self) -> bool { + // Already have a connection + if self.socket.is_some() { + return true; + } + + // Try to receive socket from accept thread (non-blocking) + if let Some(rx) = &self.socket_receiver { + if let Ok(stream) = rx.try_recv() { + self.socket = Some(stream); + return true; + } + } + + false + } } impl StreamWrite for UnixSocketStreamWriter { /// Open/initialize the stream for writing /// - /// Creates a listening socket and waits for a client to connect (blocking). + /// Creates a listening socket and spawns a background thread to accept connections. + /// This is non-blocking - the actual client connection happens lazily on first write. fn open(&mut self) -> Result<()> { // If we already have a connected socket, we're done if self.socket.is_some() { @@ -361,54 +400,39 @@ impl StreamWrite for UnixSocketStreamWriter { self.create_listener()?; } - // If we don't have a socket yet, either spawn accept thread or wait for it - if self.socket.is_none() { - // Spawn accept thread if not already running - if self.accept_thread.is_none() { - let listener_fd = self.listener_fd.unwrap(); - let (tx, rx) = mpsc::channel(); - self.socket_receiver = Some(rx); - - let handle = thread::spawn(move || { - // Retry accept on EINTR - let conn_fd = loop { - let fd = unsafe { - libc::accept(listener_fd, std::ptr::null_mut(), std::ptr::null_mut()) - }; - - if fd < 0 { - let err = std::io::Error::last_os_error(); - if err.kind() == std::io::ErrorKind::Interrupted { - continue; // Retry on EINTR - } - eprintln!("Accept failed: {}", err); - return; - } - - break fd; + // Spawn accept thread if not already running + if self.accept_thread.is_none() { + let listener_fd = self.listener_fd.unwrap(); + let (tx, rx) = mpsc::channel(); + self.socket_receiver = Some(rx); + + let handle = thread::spawn(move || { + // Retry accept on EINTR + let conn_fd = loop { + let fd = unsafe { + libc::accept(listener_fd, std::ptr::null_mut(), std::ptr::null_mut()) }; - // Convert to UnixStream - let stream = unsafe { UnixStream::from_raw_fd(conn_fd) }; + if fd < 0 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::Interrupted { + continue; // Retry on EINTR + } + eprintln!("Accept failed: {}", err); + return; + } - // Send socket through channel - let _ = tx.send(stream); - }); + break fd; + }; - self.accept_thread = Some(handle); - } + // Convert to UnixStream + let stream = unsafe { UnixStream::from_raw_fd(conn_fd) }; - // Block waiting for the client connection - if let Some(rx) = &self.socket_receiver { - match rx.recv() { - Ok(stream) => { - self.socket = Some(stream); - } - Err(e) => { - return Err(anyhow::anyhow!("Failed to receive client connection: {}", e)); - } - } - } + // Send socket through channel + let _ = tx.send(stream); + }); + + self.accept_thread = Some(handle); } Ok(()) @@ -419,7 +443,8 @@ impl StreamWrite for UnixSocketStreamWriter { /// With SOCK_SEQPACKET, each write() sends exactly one complete message, /// providing natural message boundaries. /// - /// Returns an error if no client is connected yet. + /// Returns `NoClientConnected` error if no client has connected yet. + /// The caller can retry the write until a client connects. fn write(&mut self, item: &[u8]) -> Result { self.open()?; @@ -431,9 +456,14 @@ impl StreamWrite for UnixSocketStreamWriter { Ok(stream) => { self.socket = Some(stream); } - Err(_) => { + Err(mpsc::TryRecvError::Empty) => { + // Accept thread is running but client hasn't connected yet return Err(UnixSocketError::NoClientConnected.into()); } + Err(mpsc::TryRecvError::Disconnected) => { + // Accept thread died unexpectedly + return Err(anyhow::anyhow!("Accept thread terminated unexpectedly")); + } } } } @@ -486,83 +516,133 @@ impl Drop for UnixSocketStreamWriter { #[cfg(test)] mod tests { use super::*; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; - #[test] - fn test_single_message() { - let socket_path = "/tmp/test_unix_socket_single.sock"; - let _ = std::fs::remove_file(socket_path); // Clean up if exists - - let socket_path_clone = socket_path.to_string(); - - // Spawn writer (server) thread - let writer_thread = thread::spawn(move || { - let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); + /// Serialize all unix socket tests to prevent fd reuse races. + /// + /// When tests run in parallel and one panics, its Drop closes the listener fd + /// while the accept thread may still be blocked on it. Due to Linux fd reuse, + /// this can cause other tests' accept() calls to operate on the wrong fd (EINVAL). + static TEST_MUTEX: Mutex<()> = Mutex::new(()); + + /// Generate a unique socket path per test. + fn unique_socket_path(test_name: &str) -> String { + format!("/tmp/test_unix_socket_{}_pid{}.sock", test_name, std::process::id(),) + } - // Retry write until client connects - loop { - if let Err(e) = writer.write(b"Hello, World!") { - if let Some(UnixSocketError::NoClientConnected) = - e.downcast_ref::() + /// Helper: writer retries write until a client connects, panicking on unexpected errors. + fn write_with_retry(writer: &mut UnixSocketStreamWriter, data: &[u8]) { + loop { + match writer.write(data) { + Ok(_) => break, + Err(e) => { + if e.downcast_ref::() + .is_some_and(|ue| matches!(ue, UnixSocketError::NoClientConnected)) { - thread::sleep(Duration::from_millis(10)); + thread::sleep(Duration::from_millis(5)); continue; } - panic!("Unexpected error: {}", e); + panic!("Unexpected write error: {}", e); } - break; } + } + } + /// Synchronization state shared between the writer thread and the main (reader) thread. + struct WriterSync { + /// Signaled by the writer after open() completes (bound + listening + accept spawned). + ready: AtomicBool, + /// Signaled by the reader when it has finished reading. The writer waits for this + /// before closing, to prevent the socket from being torn down while the reader + /// still has buffered messages to read. + reader_done: AtomicBool, + } + + /// Helper: spawn writer in a thread with proper synchronization. + /// + /// Returns (join_handle, sync_state). The caller must: + /// 1. Wait for `sync.ready` before connecting the reader. + /// 2. Set `sync.reader_done` after the reader has finished reading. + fn spawn_writer_thread( + socket_path: &str, + write_fn: impl FnOnce(&mut UnixSocketStreamWriter) + Send + 'static, + ) -> (JoinHandle<()>, Arc) { + let sp = socket_path.to_string(); + let sync = Arc::new(WriterSync { + ready: AtomicBool::new(false), + reader_done: AtomicBool::new(false), + }); + let sync_clone = sync.clone(); + + let handle = thread::spawn(move || { + let mut writer = UnixSocketStreamWriter::new(&sp).unwrap(); + writer.open().unwrap(); + sync_clone.ready.store(true, Ordering::Release); + write_fn(&mut writer); + // Wait for reader to finish before closing, to avoid ECONNRESET + let start = std::time::Instant::now(); + while !sync_clone.reader_done.load(Ordering::Acquire) { + if start.elapsed() > Duration::from_secs(5) { + panic!("Timed out waiting for reader to finish"); + } + thread::sleep(Duration::from_millis(1)); + } writer.close().unwrap(); }); - // Give writer time to start listening - thread::sleep(Duration::from_millis(100)); + (handle, sync) + } + + /// Wait until the writer signals it has finished open() (bound + listening + accept spawned). + fn wait_for_writer(sync: &WriterSync) { + let start = std::time::Instant::now(); + while !sync.ready.load(Ordering::Acquire) { + if start.elapsed() > Duration::from_secs(5) { + panic!("Timed out waiting for writer to become ready"); + } + thread::sleep(Duration::from_millis(1)); + } + } + + #[test] + fn test_single_message() { + let _lock = TEST_MUTEX.lock().unwrap(); + let socket_path = unique_socket_path("single"); + let _ = std::fs::remove_file(&socket_path); + + let (writer_thread, sync) = spawn_writer_thread(&socket_path, |writer| { + write_with_retry(writer, b"Hello, World!"); + }); + + wait_for_writer(&sync); - // Reader connects and reads message - let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); + let mut reader = UnixSocketStreamReader::new(&socket_path).unwrap(); let message = reader.next().unwrap().unwrap(); assert_eq!(message, b"Hello, World!"); reader.close().unwrap(); + sync.reader_done.store(true, Ordering::Release); writer_thread.join().unwrap(); } #[test] fn test_multiple_messages() { - let socket_path = "/tmp/test_unix_socket_multi.sock"; - let _ = std::fs::remove_file(socket_path); - - let socket_path_clone = socket_path.to_string(); - - // Spawn writer (server) thread - let writer_thread = thread::spawn(move || { - let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); - - // Retry until client connects for first message - loop { - if let Err(e) = writer.write(b"First") { - if let Some(UnixSocketError::NoClientConnected) = - e.downcast_ref::() - { - thread::sleep(Duration::from_millis(10)); - continue; - } - panic!("Unexpected error: {}", e); - } - break; - } + let _lock = TEST_MUTEX.lock().unwrap(); + let socket_path = unique_socket_path("multi"); + let _ = std::fs::remove_file(&socket_path); + let (writer_thread, sync) = spawn_writer_thread(&socket_path, |writer| { + write_with_retry(writer, b"First"); writer.write(b"Second message").unwrap(); writer.write(b"Third message with more data!").unwrap(); - writer.close().unwrap(); }); - thread::sleep(Duration::from_millis(100)); + wait_for_writer(&sync); - // Reader connects and reads messages - let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); + let mut reader = UnixSocketStreamReader::new(&socket_path).unwrap(); let msg1 = reader.next().unwrap().unwrap(); assert_eq!(msg1, b"First"); let msg2 = reader.next().unwrap().unwrap(); @@ -571,42 +651,25 @@ mod tests { assert_eq!(msg3, b"Third message with more data!"); reader.close().unwrap(); + sync.reader_done.store(true, Ordering::Release); writer_thread.join().unwrap(); } #[test] fn test_message_boundaries() { - let socket_path = "/tmp/test_unix_socket_boundaries.sock"; - let _ = std::fs::remove_file(socket_path); - - let socket_path_clone = socket_path.to_string(); - - // Spawn writer (server) thread - let writer_thread = thread::spawn(move || { - let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); - - // Retry until client connects for first message - loop { - if let Err(e) = writer.write(b"ABC") { - if let Some(UnixSocketError::NoClientConnected) = - e.downcast_ref::() - { - thread::sleep(Duration::from_millis(10)); - continue; - } - panic!("Unexpected error: {}", e); - } - break; - } + let _lock = TEST_MUTEX.lock().unwrap(); + let socket_path = unique_socket_path("boundaries"); + let _ = std::fs::remove_file(&socket_path); + let (writer_thread, sync) = spawn_writer_thread(&socket_path, |writer| { + write_with_retry(writer, b"ABC"); writer.write(b"DEF").unwrap(); - writer.close().unwrap(); }); - thread::sleep(Duration::from_millis(100)); + wait_for_writer(&sync); // Reader should receive each message as discrete unit - let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); + let mut reader = UnixSocketStreamReader::new(&socket_path).unwrap(); let msg1 = reader.next().unwrap().unwrap(); assert_eq!(msg1, b"ABC"); let msg2 = reader.next().unwrap().unwrap(); @@ -614,84 +677,82 @@ mod tests { // Should NOT be concatenated like "ABCDEF" reader.close().unwrap(); + sync.reader_done.store(true, Ordering::Release); writer_thread.join().unwrap(); } #[test] fn test_large_message() { - let socket_path = "/tmp/test_unix_socket_large.sock"; - let _ = std::fs::remove_file(socket_path); - - let socket_path_clone = socket_path.to_string(); + let _lock = TEST_MUTEX.lock().unwrap(); + let socket_path = unique_socket_path("large"); + let _ = std::fs::remove_file(&socket_path); // Create a large message (64KB - within SOCK_SEQPACKET limits) let large_data: Vec = (0..64 * 1024).map(|i| (i % 256) as u8).collect(); let large_data_clone = large_data.clone(); - // Spawn writer (server) thread - let writer_thread = thread::spawn(move || { - let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); - - // Retry until client connects for first message - loop { - if let Err(e) = writer.write(&large_data) { - if let Some(UnixSocketError::NoClientConnected) = - e.downcast_ref::() - { - thread::sleep(Duration::from_millis(10)); - continue; - } - panic!("Unexpected error: {}", e); - } - break; - } - - writer.close().unwrap(); + let (writer_thread, sync) = spawn_writer_thread(&socket_path, move |writer| { + write_with_retry(writer, &large_data); }); - thread::sleep(Duration::from_millis(100)); + wait_for_writer(&sync); - // Reader receives large message - let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); + let mut reader = UnixSocketStreamReader::new(&socket_path).unwrap(); let message = reader.next().unwrap().unwrap(); assert_eq!(message, large_data_clone); reader.close().unwrap(); + sync.reader_done.store(true, Ordering::Release); writer_thread.join().unwrap(); } #[test] fn test_connection_close() { - let socket_path = "/tmp/test_unix_socket_close.sock"; - let _ = std::fs::remove_file(socket_path); + let _lock = TEST_MUTEX.lock().unwrap(); + let socket_path = unique_socket_path("close"); + let _ = std::fs::remove_file(&socket_path); - let socket_path_clone = socket_path.to_string(); + let sp = socket_path.clone(); - // Spawn writer (server) thread - let writer_thread = thread::spawn(move || { - let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); + let writer_ready = Arc::new(AtomicBool::new(false)); + let writer_ready_clone = writer_ready.clone(); + let reader_connected = Arc::new(AtomicBool::new(false)); + let reader_connected_clone = reader_connected.clone(); - // Retry until client connects for first message - loop { - if let Err(e) = writer.write(b"Message") { - if let Some(UnixSocketError::NoClientConnected) = - e.downcast_ref::() - { - thread::sleep(Duration::from_millis(10)); - continue; - } - panic!("Unexpected error: {}", e); + // This test intentionally lets the writer close to verify the reader sees EOF, + // so we don't use spawn_writer_thread (which defers close). + let writer_thread = thread::spawn(move || { + let mut writer = UnixSocketStreamWriter::new(&sp).unwrap(); + writer.open().unwrap(); + writer_ready_clone.store(true, Ordering::Release); + + // Wait for the reader to connect before writing + closing + let start = std::time::Instant::now(); + while !reader_connected_clone.load(Ordering::Acquire) { + if start.elapsed() > Duration::from_secs(5) { + panic!("Timed out waiting for reader to connect"); } - break; + thread::sleep(Duration::from_millis(1)); } + write_with_retry(&mut writer, b"Message"); writer.close().unwrap(); }); - thread::sleep(Duration::from_millis(100)); + // Wait for writer to be listening + let start = std::time::Instant::now(); + while !writer_ready.load(Ordering::Acquire) { + if start.elapsed() > Duration::from_secs(5) { + panic!("Timed out waiting for writer"); + } + thread::sleep(Duration::from_millis(1)); + } + + // Connect reader and signal writer + let mut reader = UnixSocketStreamReader::new(&socket_path).unwrap(); + reader.open().unwrap(); + reader_connected.store(true, Ordering::Release); - // Reader receives message - let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); let msg1 = reader.next().unwrap().unwrap(); assert_eq!(msg1, b"Message"); @@ -705,62 +766,220 @@ mod tests { #[test] fn test_stress_many_messages() { - let socket_path = "/tmp/test_unix_socket_stress.sock"; - let _ = std::fs::remove_file(socket_path); - - let socket_path_clone = socket_path.to_string(); + let _lock = TEST_MUTEX.lock().unwrap(); + let socket_path = unique_socket_path("stress"); + let _ = std::fs::remove_file(&socket_path); const NUM_MESSAGES: usize = 1000; - // Spawn writer (server) thread - let writer_thread = thread::spawn(move || { - let mut writer = UnixSocketStreamWriter::new(&socket_path_clone).unwrap(); + let (writer_thread, sync) = spawn_writer_thread(&socket_path, |writer| { + write_with_retry(writer, b"START"); - // Wait for client to connect with first message - loop { - if let Err(e) = writer.write(b"START") { - if let Some(UnixSocketError::NoClientConnected) = - e.downcast_ref::() - { - thread::sleep(Duration::from_millis(10)); - continue; - } - panic!("Unexpected error: {}", e); - } - break; - } - - // Send many messages rapidly for i in 0..NUM_MESSAGES { let msg = format!("Message {}", i); writer.write(msg.as_bytes()).unwrap(); } writer.write(b"END").unwrap(); - writer.close().unwrap(); }); - thread::sleep(Duration::from_millis(100)); + wait_for_writer(&sync); - // Reader receives all messages - let mut reader = UnixSocketStreamReader::new(socket_path).unwrap(); + let mut reader = UnixSocketStreamReader::new(&socket_path).unwrap(); - // Read START marker let start = reader.next().unwrap().unwrap(); assert_eq!(start, b"START"); - // Read all messages and verify order for i in 0..NUM_MESSAGES { let expected = format!("Message {}", i); let msg = reader.next().unwrap().unwrap(); assert_eq!(msg, expected.as_bytes(), "Message {} mismatch", i); } - // Read END marker let end = reader.next().unwrap().unwrap(); assert_eq!(end, b"END"); reader.close().unwrap(); + sync.reader_done.store(true, Ordering::Release); + writer_thread.join().unwrap(); + } + + #[test] + fn test_non_blocking_open() { + let _lock = TEST_MUTEX.lock().unwrap(); + let socket_path = unique_socket_path("nonblocking"); + let _ = std::fs::remove_file(&socket_path); + + let mut writer = UnixSocketStreamWriter::new(&socket_path).unwrap(); + + let start = std::time::Instant::now(); + writer.open().unwrap(); + let elapsed = start.elapsed(); + + // open() should return almost immediately (definitely under 100ms) + assert!( + elapsed.as_millis() < 100, + "open() took too long: {:?} - should be non-blocking", + elapsed + ); + + // But we shouldn't have a client connected yet + assert!(!writer.is_client_connected()); + + // Connect a dummy reader to unblock the accept thread before closing, + // preventing fd reuse races with the detached accept thread. + let mut dummy = UnixSocketStreamReader::new(&socket_path).unwrap(); + dummy.open().unwrap(); + + writer.close().unwrap(); + // Allow accept thread to fully terminate after fd is closed + thread::sleep(Duration::from_millis(10)); + } + + #[test] + fn test_is_client_connected() { + let _lock = TEST_MUTEX.lock().unwrap(); + let socket_path = unique_socket_path("is_connected"); + let _ = std::fs::remove_file(&socket_path); + + let sp = socket_path.clone(); + + let sync = Arc::new(WriterSync { + ready: AtomicBool::new(false), + reader_done: AtomicBool::new(false), + }); + let sync_clone = sync.clone(); + + let writer_thread = thread::spawn(move || { + let mut writer = UnixSocketStreamWriter::new(&sp).unwrap(); + writer.open().unwrap(); + sync_clone.ready.store(true, Ordering::Release); + + // Initially, no client should be connected + assert!(!writer.is_client_connected()); + + // Wait for client to connect + let mut connected = false; + for _ in 0..200 { + if writer.is_client_connected() { + connected = true; + break; + } + thread::sleep(Duration::from_millis(10)); + } + + assert!(connected, "Client should have connected"); + + // After connection, should remain true + assert!(writer.is_client_connected()); + + // Write should now succeed immediately + writer.write(b"Connected!").unwrap(); + + // Wait for reader to finish before closing + let start = std::time::Instant::now(); + while !sync_clone.reader_done.load(Ordering::Acquire) { + if start.elapsed() > Duration::from_secs(5) { + panic!("Timed out waiting for reader to finish"); + } + thread::sleep(Duration::from_millis(1)); + } + writer.close().unwrap(); + }); + + wait_for_writer(&sync); + + let mut reader = UnixSocketStreamReader::new(&socket_path).unwrap(); + let message = reader.next().unwrap().unwrap(); + assert_eq!(message, b"Connected!"); + reader.close().unwrap(); + + sync.reader_done.store(true, Ordering::Release); writer_thread.join().unwrap(); } + + #[test] + fn test_wait_for_client_with_is_connected() { + let _lock = TEST_MUTEX.lock().unwrap(); + let socket_path = unique_socket_path("wait_client"); + let _ = std::fs::remove_file(&socket_path); + + let sp = socket_path.clone(); + + let sync = Arc::new(WriterSync { + ready: AtomicBool::new(false), + reader_done: AtomicBool::new(false), + }); + let sync_clone = sync.clone(); + + let writer_thread = thread::spawn(move || { + let mut writer = UnixSocketStreamWriter::new(&sp).unwrap(); + writer.open().unwrap(); + sync_clone.ready.store(true, Ordering::Release); + + // Use is_client_connected() to wait for client + while !writer.is_client_connected() { + thread::sleep(Duration::from_millis(10)); + } + + // Now write will succeed without retries + writer.write(b"Message 1").unwrap(); + writer.write(b"Message 2").unwrap(); + writer.write(b"Message 3").unwrap(); + + // Wait for reader to finish before closing + let start = std::time::Instant::now(); + while !sync_clone.reader_done.load(Ordering::Acquire) { + if start.elapsed() > Duration::from_secs(5) { + panic!("Timed out waiting for reader to finish"); + } + thread::sleep(Duration::from_millis(1)); + } + writer.close().unwrap(); + }); + + wait_for_writer(&sync); + + let mut reader = UnixSocketStreamReader::new(&socket_path).unwrap(); + let msg1 = reader.next().unwrap().unwrap(); + assert_eq!(msg1, b"Message 1"); + let msg2 = reader.next().unwrap().unwrap(); + assert_eq!(msg2, b"Message 2"); + let msg3 = reader.next().unwrap().unwrap(); + assert_eq!(msg3, b"Message 3"); + reader.close().unwrap(); + + sync.reader_done.store(true, Ordering::Release); + writer_thread.join().unwrap(); + } + + #[test] + fn test_no_client_connected_error() { + let _lock = TEST_MUTEX.lock().unwrap(); + let socket_path = unique_socket_path("no_client"); + let _ = std::fs::remove_file(&socket_path); + + let mut writer = UnixSocketStreamWriter::new(&socket_path).unwrap(); + writer.open().unwrap(); + + // Try to write without any client connected + let result = writer.write(b"Data"); + + // Should get NoClientConnected error + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + err.downcast_ref::().is_some(), + "Expected UnixSocketError::NoClientConnected" + ); + + // Connect a dummy reader to unblock the accept thread before closing, + // preventing fd reuse races with the detached accept thread. + let mut dummy = UnixSocketStreamReader::new(&socket_path).unwrap(); + dummy.open().unwrap(); + + writer.close().unwrap(); + // Allow accept thread to fully terminate after fd is closed + thread::sleep(Duration::from_millis(10)); + } } From 4fc7d15d7e574ce5477af2f30ee756a11bd02fe1 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 10:13:32 +0100 Subject: [PATCH 537/782] Fix some comments and traces in main.c --- emulator-asm/src/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index dddc64f26..70fdedb4c 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4049,14 +4049,14 @@ void server_reset_slow (void) #ifdef DEBUG gettimeofday(&stop_time, NULL); duration = TimeDiff(start_time, stop_time); - if (verbose) printf("server_reset() memset(ram) in %lu us\n", duration); + if (verbose) printf("server_reset_slow() memset(ram) in %lu us\n", duration); #endif } } void server_reset_trace (void) { - // Reset RAM data for next emulation + // Reset trace header and trace_used_size for next emulation if ( (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && (gen_method != Fast) && From 576b4657f78fb63431c1741d9487e8a1d9734781 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 10:37:05 +0100 Subject: [PATCH 538/782] Synchronize shared memory areas as needed, i.e. before reading them, in main.c --- emulator-asm/src/main.c | 88 +++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 30 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 70fdedb4c..68a373958 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4096,6 +4096,22 @@ void server_run (void) exit(-1); } + // Sync control input shared memory + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + /*******/ /* ASM */ /*******/ @@ -5288,24 +5304,41 @@ int _wait_for_prec_avail (void) // Increment wait counter wait_counter++; - // Sync precompile shared memory + // Sync control output shared memory so that the writer can see the precompile reads we have + // done, and thus update the precompile_written_address if needed if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: 1 msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); + printf("ERROR: msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } - // Tell the writer that we have read the precompile results + // Tell the writer that we have read some precompile results sem_post(sem_prec_read); - // Make sure the semaphore is reset before checking the condition, + // Make sure the precompile available semaphore is reset before checking the condition, // since the caller may have posted it (even several times) before we called sem_wait() while (sem_trywait(sem_prec_avail) == 0) {/*printf("Purging sem_prec_avail\n");*/}; + // Sync control input shared memory so that we can see the latest precompile_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + // Check if there are already precompile results available if (*precompile_written_address > *precompile_read_address) { + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + return 0; } @@ -5333,6 +5366,15 @@ int _wait_for_prec_avail (void) fflush(stderr); exit(-1); } + + // Sync control input shared memory so that we can see the latest precompile_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (*precompile_exit_address != 0) { printf("ERROR: wait_for_prec_avail() found precompile_exit_address=%lu\n", *precompile_exit_address); @@ -5342,36 +5384,22 @@ int _wait_for_prec_avail (void) } if (*precompile_written_address > *precompile_read_address) { + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + return 0; } } - // // Wait for control input shared memory to synchronize - // uint64_t written; - // uint64_t read; - // for (uint64_t i=0; i Date: Tue, 17 Feb 2026 12:00:07 +0100 Subject: [PATCH 539/782] fix some DMA bugs --- core/src/elf2rom.rs | 2 +- core/src/inst_context.rs | 3 + core/src/riscv2zisk_context.rs | 4 +- core/src/zisk_ops.rs | 63 +++----- core/src/zisk_rom_2_asm.rs | 13 +- pil/src/pil_helpers/traces.rs | 2 +- precompiles/big_int/pil/big_int_add.pil | 2 +- precompiles/dma/pil/dma_pre_post.pil | 8 +- .../dma/src/dma_64_aligned/dma_64_aligned.rs | 7 +- .../dma_64_aligned_collector.rs | 68 ++++++-- .../dma_64_aligned/dma_64_aligned_input.rs | 26 +-- .../dma_64_aligned/dma_64_aligned_inputcpy.rs | 7 +- .../dma_64_aligned/dma_64_aligned_instance.rs | 5 +- .../src/dma_64_aligned/dma_64_aligned_mem.rs | 8 +- .../dma_64_aligned/dma_64_aligned_memcpy.rs | 17 +- .../dma_64_aligned/dma_64_aligned_memset.rs | 8 +- precompiles/dma/src/dma_collect_counters.rs | 18 +-- precompiles/dma/src/dma_common.rs | 111 +++++++++++++ .../dma/src/dma_pre_post/dma_pre_post.rs | 13 -- .../dma_pre_post/dma_pre_post_collector.rs | 2 +- .../src/dma_pre_post/dma_pre_post_inputcpy.rs | 6 - .../src/dma_pre_post/dma_pre_post_memcpy.rs | 5 - .../dma/src/dma_unaligned/dma_unaligned.rs | 32 +--- .../dma_unaligned/dma_unaligned_collector.rs | 58 ++++++- .../src/dma_unaligned/dma_unaligned_input.rs | 16 +- .../dma_unaligned/dma_unaligned_instance.rs | 5 +- .../src/zisklib/fcalls/big_int256_div.rs | 28 +++- .../src/zisklib/fcalls/big_int_div.rs | 4 +- .../src/zisklib/fcalls/bin_decomp.rs | 24 ++- .../src/zisklib/fcalls/bls12_381/fp.rs | 60 ++++--- .../src/zisklib/fcalls/bls12_381/fp2.rs | 84 ++++++---- .../src/zisklib/fcalls/bls12_381/twist.rs | 150 +++++++++++------- .../entrypoint/src/zisklib/fcalls/bn254/fp.rs | 18 ++- .../src/zisklib/fcalls/bn254/fp2.rs | 36 +++-- .../src/zisklib/fcalls/bn254/twist.rs | 120 ++++++++------ .../src/zisklib/fcalls/secp256k1/ecdsa.rs | 36 +++-- .../src/zisklib/fcalls/secp256k1/fn.rs | 17 +- .../src/zisklib/fcalls/secp256k1/fp.rs | 61 +++---- .../src/zisklib/fcalls/secp256r1/ecdsa.rs | 36 +++-- 39 files changed, 765 insertions(+), 418 deletions(-) diff --git a/core/src/elf2rom.rs b/core/src/elf2rom.rs index 8d20f1bbe..d5d911fb5 100644 --- a/core/src/elf2rom.rs +++ b/core/src/elf2rom.rs @@ -21,7 +21,7 @@ pub fn elf2rom(elf: &[u8]) -> Result> { let payloads: Vec = vec![collect_elf_payload_from_bytes(FLOAT_LIB_DATA)?, collect_elf_payload_from_bytes(elf)?]; // Get DMA function addresses: (memcpy, memcmp, memset, memmove) - let dma_addrs = get_dma_symbol_addresses(&elf); + let dma_addrs = get_dma_symbol_addresses(elf); // Create an empty ZiskRom instance let mut rom: ZiskRom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() }; diff --git a/core/src/inst_context.rs b/core/src/inst_context.rs index 75c1aafc5..a0846f0f8 100644 --- a/core/src/inst_context.rs +++ b/core/src/inst_context.rs @@ -123,6 +123,8 @@ pub struct InstContext { /// Precompiles uses jmp_offset1 as extended param (static value known in transpilation time) pub extended_arg: i64, + + pub stats_hint: u64, } /// RisK instruction context implementation @@ -146,6 +148,7 @@ impl InstContext { fcall: FcallInstContext::default(), data_ext_len: 0, extended_arg: 0, + stats_hint: 0, } } diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 333ec2335..1d2340c08 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -1391,7 +1391,7 @@ impl Riscv2ZiskContext<'_> { zib.op("add256").unwrap(); zib.verbose("add256"); zib.store("reg", i.rd as i64, false, false); - zib.j(4, 4); + zib.j(0, 4); zib.build(); self.insts.insert(rom_address, zib); } else { @@ -2006,7 +2006,7 @@ impl Riscv2ZiskContext<'_> { // add rd, reg(dst), reg(count) addi rd, reg(dst), reg(count) │ jmp+8 // .......... .......... <─────────────────────┘ let rs1 = next_instructions[0].rs1; - let rs2 = i.rs1; + let rs2 = next_instructions[0].rs2; let rd = next_instructions[0].rd; self.create_extended_precompiles_op( i, diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 37852ff7d..6e1567d28 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -1656,11 +1656,11 @@ pub fn opc_add256(ctx: &mut InstContext) { ctx.precompiled.input_data[4 + 2 * 4] = cout; } ctx.c = cout; - ctx.flag = cout != 0; + ctx.flag = false; } else { assert!(data[4 + 2 * 4] <= 1, "opc_add256: cout > 1"); ctx.c = data[4 + 2 * 4]; - ctx.flag = data[4 + 2 * 4] != 0; + ctx.flag = false; } } @@ -2624,7 +2624,6 @@ fn ops_dma_memcpys(ctx: &InstContext, stats: &mut dyn OpStats, extended: bool) { let addr_a = ctx.a; let addr_b = ctx.b; let count = if extended { ctx.extended_arg as u64 } else { ctx.mem.read(EXTRA_PARAMS_ADDR, 8) }; - // pre, post, dma_align, dma_unalign if count == 0 { return; @@ -2634,7 +2633,7 @@ fn ops_dma_memcpys(ctx: &InstContext, stats: &mut dyn OpStats, extended: bool) { let offset_b = addr_b & 0x07; let addr64_a = addr_a - offset_a; let addr64_b = addr_b - offset_b; - let pre_count = (8 - offset_a) & 0x07; + let pre_count = std::cmp::min((8 - offset_a) & 0x07, count); if pre_count > 0 { stats.mem_align_read(addr64_a, 1); @@ -2710,6 +2709,7 @@ fn opc_dma_memcmps(ctx: &mut InstContext, extended: bool) { let count = if extended { ctx.extended_arg as u64 } else { ctx.mem.read(EXTRA_PARAMS_ADDR, 8) }; let (result, effective_count) = ctx.mem.memcmp(dst, src, count); + ctx.stats_hint = effective_count as u64; ctx.c = result; } EmulationMode::GenerateMemReads => { @@ -2815,7 +2815,8 @@ pub fn ops_dma_xmemcmp(ctx: &InstContext, stats: &mut dyn OpStats) { fn ops_dma_memcmps(ctx: &InstContext, stats: &mut dyn OpStats, extended: bool) { let addr_a = ctx.a; let addr_b = ctx.b; - let count = if extended { ctx.extended_arg as u64 } else { ctx.mem.read(EXTRA_PARAMS_ADDR, 8) }; + // let _bus_count = if extended { ctx.extended_arg as u64 } else { ctx.mem.read(EXTRA_PARAMS_ADDR, 8) }; + let count = ctx.stats_hint; // pre, post, dma_align, dma_unalign if count == 0 { @@ -2913,27 +2914,32 @@ fn read_from_input(ctx: &mut InstContext, dst: u64, count: u64) { (ctx.fcall.result_got - 1) as usize * 8, ); ctx.fcall.result_got += count64; + if ctx.fcall.result_got > ctx.fcall.result_size { + ctx.mem.free_input = 0; + } else { + ctx.mem.free_input = ctx.fcall.result[ctx.fcall.result_got as usize - 1]; + } } fn read_and_get_from_input(ctx: &mut InstContext, dst: u64, count: u64) -> Vec { // Check for consistency if count % 8 != 0 { - panic!("opc_dma_inputcpy() called without invalid count {count}"); + panic!("opc_dma_inputcpy() called at 0x{:08x} without invalid count {count}", ctx.pc); } let count64 = count >> 3; if ctx.fcall.result_size == 0 { - panic!("opc_dma_inputcpy() called with ctx.fcall.result_size==0"); + panic!("opc_dma_inputcpy() called at 0x{:08x} with ctx.fcall.result_size==0", ctx.pc); } if ctx.fcall.result_size as usize > FCALL_RESULT_MAX_SIZE { panic!( - "opc_dma_inputcpy() called with ctx.fcall.result_size=={}>32", - ctx.fcall.result_size + "opc_dma_inputcpy() called at 0x{:08x} with ctx.fcall.result_size=={}>32", + ctx.pc, ctx.fcall.result_size ); } if (ctx.fcall.result_got - 1 + count64) > ctx.fcall.result_size { panic!( - "opc_dma_inputcpy() called with ctx.fcall.result_got({}) + {count64} >= ctx.fcall.result_size {}", - ctx.fcall.result_got, ctx.fcall.result_size + "opc_dma_inputcpy() called at 0x{:08x} with ctx.fcall.result_got({}) + {count64} >= ctx.fcall.result_size {}", + ctx.pc, ctx.fcall.result_got, ctx.fcall.result_size ); } @@ -2983,24 +2989,19 @@ fn read_and_get_from_input(ctx: &mut InstContext, dst: u64, count: u64) -> Vec ctx.fcall.result_size { + ctx.mem.free_input = 0; + } else { + ctx.mem.free_input = ctx.fcall.result[ctx.fcall.result_got as usize - 1]; + } input_data } #[inline(always)] -pub fn opc_dma_inputcpy(ctx: &mut InstContext) { - opc_dma_inputcpys(ctx, false) -} - -#[inline(always)] -pub fn opc_dma_xinputcpy(ctx: &mut InstContext) { - opc_dma_inputcpys(ctx, true) -} - -#[inline(always)] -fn opc_dma_inputcpys(ctx: &mut InstContext, extended: bool) { +fn opc_dma_inputcpy(ctx: &mut InstContext) { let dst: u64 = ctx.a; - let count = if extended { ctx.extended_arg as u64 } else { ctx.b }; + let count = ctx.b; match ctx.emulation_mode { EmulationMode::Mem => { @@ -3078,24 +3079,10 @@ pub fn op_dma_inputcpy(_a: u64, _b: u64) -> (u64, bool) { unimplemented!("op_dma_inputcpy() is not implemented"); } -#[inline(always)] -pub fn op_dma_xinputcpy(_a: u64, _b: u64) -> (u64, bool) { - unimplemented!("op_dma_xinputcpy() is not implemented"); -} - #[inline(always)] pub fn ops_dma_inputcpy(ctx: &InstContext, stats: &mut dyn OpStats) { - ops_dma_inputcpys(ctx, stats, false); -} -#[inline(always)] -pub fn ops_dma_xinputcpy(ctx: &InstContext, stats: &mut dyn OpStats) { - ops_dma_inputcpys(ctx, stats, true); -} - -#[inline(always)] -fn ops_dma_inputcpys(ctx: &InstContext, stats: &mut dyn OpStats, extended: bool) { let addr_a = ctx.a; - let count = if extended { ctx.extended_arg as u64 } else { ctx.b }; + let count = ctx.b; // pre, post, dma_align, dma_unalign if count == 0 { diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 36fda24db..d7532017f 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -7252,12 +7252,6 @@ impl ZiskRom2Asm { *code += &format!("\tmov {}, rax {}\n", REG_C, ctx.comment_str("c = rax")); Self::pop_internal_registers_except_c_and_flag(ctx, code, false); } - *code += &format!( - "\tmov {}, {} {}\n", - REG_FLAG, - REG_C, - ctx.comment_str("flag = rax") - ); // this precompiles store the result in minimal trace if ctx.minimal_trace() || ctx.zip() || ctx.mem_reads() { @@ -7271,9 +7265,9 @@ impl ZiskRom2Asm { // Set result ctx.c.is_saved = true; - ctx.flag_is_always_zero = false; + ctx.flag_is_always_zero = true; } - ZiskOp::DmaMemCpy => { + ZiskOp::DmaMemCpy | ZiskOp::DmaXMemCpy => { assert_eq!(inst.store, STORE_NONE); // Use the memory address as the first and unique parameter *code += &ctx.full_line_comment("DmaMemCpy".to_string()); @@ -7333,9 +7327,6 @@ impl ZiskRom2Asm { ZiskOp::DmaXMemSet => { unimplemented!(); } - ZiskOp::DmaXMemCpy => { - unimplemented!(); - } ZiskOp::DmaXMemCmp => { unimplemented!(); } diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 27de73ae9..89c239280 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "429daba0c5b58e7292d8075342aad48f1ae77ea0948f845a47702e8ca3b60a57"; +pub const PILOUT_HASH: &str = "1654b031412800684af604b3948bb3789b00f66cb4d0d88b1a980c98a3d2c8fe"; pub const MERKLE_TREE_ARITY: u64 = 4; diff --git a/precompiles/big_int/pil/big_int_add.pil b/precompiles/big_int/pil/big_int_add.pil index e463856e5..4f7bbf072 100644 --- a/precompiles/big_int/pil/big_int_add.pil +++ b/precompiles/big_int/pil/big_int_add.pil @@ -134,5 +134,5 @@ airtemplate BigIntAdd(const int N = 2**21, const int bits = 256, const int opera // proves the operation lauched by main, c = flag = carry_out proves_operation(op: operation_code, a:[0, 0], b:[addr_params, 0], c:[final_cout, 0], - flag:final_cout, main_step: step, mul:sel); + flag:0, main_step: step, mul:sel); } \ No newline at end of file diff --git a/precompiles/dma/pil/dma_pre_post.pil b/precompiles/dma/pil/dma_pre_post.pil index ed4e8ab21..f9c709ee5 100644 --- a/precompiles/dma/pil/dma_pre_post.pil +++ b/precompiles/dma/pil/dma_pre_post.pil @@ -354,8 +354,8 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance _wr_0 += factor * (1 - sb[i]) * pb[i]; _wr_1 += factor * (1 - sb[i+4]) * pb[i+4]; } - write_value[0] === _wr_0; - write_value[1] === _wr_1; + write_value[0] <== _wr_0; + write_value[1] <== _wr_1; // dst_offset > src_offset @@ -373,12 +373,12 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance // -1 1 │ R0 R1 R2 │ R3 R4 R5 R6 │ // └─────────────────┴─────────────────┘ - write_value[2] === sr[3] * ( sb[3] * P2_24 * rb[0]) + + write_value[2] <== sr[3] * ( sb[3] * P2_24 * rb[0]) + sr[2] * ( sb[2] * P2_16 * rb[0] + sb[3] * P2_24 * rb[1]) + sr[1] * ( sb[1] * P2_8 * rb[0] + sb[2] * P2_16 * rb[1] + sb[3] * P2_24 * rb[2]) + (1 - sb[0]) * pb[0] + (1 - sb[1]) * P2_8 * pb[1] + (1 - sb[2]) * P2_16 * pb[2] + (1 - sb[3]) * P2_24 * pb[3]; - write_value[3] === sr[7] * ( + sb[7] * P2_24 * rb[0]) + + write_value[3] <== sr[7] * ( + sb[7] * P2_24 * rb[0]) + sr[6] * ( + sb[6] * P2_16 * rb[0] + sb[7] * P2_24 * rb[1]) + sr[5] * ( + sb[5] * P2_8 * rb[0] + sb[6] * P2_16 * rb[1] + sb[7] * P2_24 * rb[2]) + sr[4] * (sb[4] * rb[0] + sb[5] * P2_8 * rb[1] + sb[6] * P2_16 * rb[2] + sb[7] * P2_24 * rb[3]) + diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs index 2a129213d..961c894bd 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs @@ -63,6 +63,7 @@ impl Dma64AlignedSM { air_values: &mut Dma64AlignedAirValues, ) -> usize { let rows = input.rows as usize; + let is_last_instance_input = rows >= trace.len(); let skip_count = input.skip_rows as usize * self.op_x_rows; let initial_count = DmaInfo::get_loop_count(input.encoded) - skip_count; let mut count64 = initial_count; @@ -132,7 +133,7 @@ impl Dma64AlignedSM { } } - if input.is_last_instance_input { + if is_last_instance_input { if seq_end { air_values.segment_last_seq_end = F::ONE; air_values.segment_last_src64 = F::ZERO; @@ -214,8 +215,8 @@ impl Dma64AlignedModule for Dma64AlignedSM { timer_start_trace!(DMA_64_ALIGNED_TRACE); - // Split the dma_trace.buffer into slices matching each inner vector’s length. - let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + // Flatten and reorder inputs to ensure first, last are in theirs positions + let flat_inputs = crate::flatten_and_reorder_inputs(inputs); let trace_rows = trace.buffer.as_mut_slice(); let mut local_16_bits_table = vec![0u32; 1 << 16]; diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs index 5bfc3c8c4..76397fd61 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs @@ -4,7 +4,7 @@ //! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for //! execution plans. -use crate::{Dma64AlignedInput, DmaCollectCounters, DmaCollectorRoutingLog}; +use crate::{Dma64AlignedInput, DmaCollectCounters, DmaCollectorRoutingLog, DmaInputPosition}; use precompiles_helpers::DmaInfo; use std::any::Any; use zisk_common::{BusDevice, BusId, ChunkId, DMA_ENCODED, OP, OPERATION_BUS_ID, OP_TYPE}; @@ -14,6 +14,10 @@ pub struct Dma64AlignedCollector { /// Collected inputs for witness computation. pub inputs: Vec, + /// index inside inputs of the last input, because at last stage must be swapped + /// with the last one, to ensure that it's the last one in the trace. + pub last_input_index: Option, + pub chunk_id: ChunkId, pub rlog: DmaCollectorRoutingLog, @@ -37,6 +41,8 @@ impl Dma64AlignedCollector { /// * `bus_id` - The connected bus ID. /// * `num_inputs` - The number of inputs to collect. /// * `collect_counter` - The helper to skip instructions based on the plan's configuration. + /// * `ops_by_row` - The number of operations per row. + /// * `last_segment_collector` - Indicates if this is the last segment collector. /// /// # Returns /// A new `Dma64AlignedCollector` instance initialized with the provided parameters. @@ -56,6 +62,7 @@ impl Dma64AlignedCollector { last_segment_collector, rlog: DmaCollectorRoutingLog::new(chunk_id), chunk_id, + last_input_index: None, } } @@ -101,19 +108,15 @@ impl Dma64AlignedCollector { return true; } // self.collect_counters.memcpy.should_process(rows) - if let Some((skip, max_count, is_final_skip)) = - self.collect_counters.should_collect(rows as u64, op) - { + if let Some((skip, max_count)) = self.collect_counters.should_collect(rows as u64, op) { self.rlog.log_collect(rows, data); - let is_last_input = self.last_segment_collector && is_final_skip; - self.inputs.push(match op { + self.add_input(match op { ZiskOp::DMA_XMEMSET => Dma64AlignedInput::from_memset( data, self.trace_offset, skip as usize, self.ops_by_row, max_count as usize, - is_last_input, ), ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => Dma64AlignedInput::from( data, @@ -122,7 +125,6 @@ impl Dma64AlignedCollector { skip as usize, self.ops_by_row, max_count as usize, - is_last_input, ), ZiskOp::DMA_INPUTCPY | ZiskOp::DMA_MEMCPY | ZiskOp::DMA_XMEMCPY => { Dma64AlignedInput::from( @@ -132,12 +134,11 @@ impl Dma64AlignedCollector { skip as usize, self.ops_by_row, max_count as usize, - is_last_input, ) } _ => panic!("Invalid operation 0x{op:02X}"), }); - + // Update trace offset self.trace_offset += max_count as usize; } else { self.rlog.log_discard(10, data); @@ -148,6 +149,40 @@ impl Dma64AlignedCollector { } true } + + /// Adds an input to the collector with proper ordering management. + /// + /// This method handles: + /// - Adding the input to the vector + /// - Managing inputs that must be first (swaps to position 0) + /// - Tracking inputs that must be last (stores index for later swap) + /// + /// # Arguments + /// * `input` - The input to add + #[inline(always)] + fn add_input(&mut self, input: Dma64AlignedInput) { + // Check if input must be first before pushing + let must_be_first = input.must_be_first(); + let must_be_last = input.must_be_last(); + let current_index = self.inputs.len(); + + // Push the input + self.inputs.push(input); + + // Handle ordering requirements + if must_be_first { + // Swap with position 0 if not already first + if current_index > 0 { + self.inputs.swap(0, current_index); + } + } else if must_be_last { + // Edge case: if an input is huge and it's both first and last, + // must_be_first takes precedence and this branch won't execute + assert!(self.last_input_index.is_none(), "Multiple inputs marked as last input"); + self.last_input_index = Some(current_index); + } + } + pub fn get_debug_info(&self) -> String { #[cfg(feature = "save_dma_collectors")] return format!( @@ -159,6 +194,19 @@ impl Dma64AlignedCollector { #[cfg(not(feature = "save_dma_collectors"))] String::new() } + pub fn take_inputs(&mut self) -> Vec { + if let Some(last_index) = self.last_input_index { + // If there's a last input index, swap it with the last element to ensure it's the last one in the trace. + let current_last_index = self.inputs.len() - 1; + self.inputs.swap(last_index, current_last_index); + } + std::mem::take(&mut self.inputs) + } + pub fn take_debug_inputs(&mut self) -> (String, Vec) { + let debug_info = self.get_debug_info(); + let inputs = self.take_inputs(); + (debug_info, inputs) + } } impl BusDevice for Dma64AlignedCollector { diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs index 1310a973f..f2b8371d3 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs @@ -8,7 +8,6 @@ use crate::DMA_64_ALIGNED_OPS_BY_ROW; pub struct Dma64AlignedInput { pub src: u32, pub dst: u32, - pub is_first_instance_input: bool, pub is_last_instance_input: bool, pub op: u8, pub trace_offset: u32, // offset inside trace to paralelize @@ -49,7 +48,6 @@ impl Dma64AlignedInput { skip_rows: usize, ops_x_rows: usize, max_rows: usize, - is_last_instance_input: bool, ) -> Self { let op = data[OP] as u8; let encoded = data[DMA_ENCODED]; @@ -63,8 +61,7 @@ impl Dma64AlignedInput { dst: data[A] as u32 + pre_count, src: data[B] as u32 + pre_count, trace_offset: trace_offset as u32, - is_first_instance_input: trace_offset == 0, - is_last_instance_input, + is_last_instance_input: max_rows < (total_rows - skip_rows), step: data[STEP], skip_rows: skip_rows as u32, rows, @@ -89,7 +86,6 @@ impl Dma64AlignedInput { skip_rows: usize, ops_x_rows: usize, max_rows: usize, - is_last_instance_input: bool, ) -> Self { let op = data[OP] as u8; let encoded = data[DMA_ENCODED]; @@ -100,8 +96,7 @@ impl Dma64AlignedInput { dst: data[A] as u32 + pre_count, src: 0, trace_offset: trace_offset as u32, - is_first_instance_input: trace_offset == 0, - is_last_instance_input, + is_last_instance_input: max_rows < (total_rows - skip_rows), step: data[STEP], skip_rows: skip_rows as u32, rows, @@ -118,7 +113,6 @@ impl Dma64AlignedInput { skip_rows: usize, ops_x_rows: usize, max_rows: usize, - is_last_instance_input: bool, ) -> Self { let dst = data[A] as u32; let src = data[B] as u32; @@ -134,8 +128,7 @@ impl Dma64AlignedInput { dst: dst + pre_count, src: src + pre_count, trace_offset: trace_offset as u32, - is_first_instance_input: trace_offset == 0, - is_last_instance_input, + is_last_instance_input: max_rows < (total_rows - skip_rows), step: data[STEP], skip_rows: skip_rows as u32, rows, @@ -170,7 +163,7 @@ impl Dma64AlignedInput { writeln!( file, "{:>8}|{:>10}|{:>10}|{:>22}|{:>21}|{:>4}|{:>12}|{:>9}|{:>8}|{:>14}|{:>18}|{:>9}|src_values", - "pos", "src", "dst", "is_first_instance_input", "is_last_instance_input", "op", "trace_offset", "skip_rows", "rows", "step", "encoded", "fill_byte" + "pos", "src", "dst", "is_last_instance_input", "op", "trace_offset", "skip_rows", "rows", "step", "encoded", "fill_byte" )?; // Write data rows @@ -183,7 +176,6 @@ impl Dma64AlignedInput { pos, input.src, input.dst, - input.is_first_instance_input, input.is_last_instance_input, input.op, input.trace_offset, @@ -199,3 +191,13 @@ impl Dma64AlignedInput { Ok(()) } } + +impl crate::DmaInputPosition for Dma64AlignedInput { + fn must_be_first(&self) -> bool { + self.skip_rows > 0 + } + + fn must_be_last(&self) -> bool { + self.is_last_instance_input + } +} diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs index 09992513e..b5335c612 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs @@ -74,6 +74,7 @@ impl Dma64AlignedInputCpySM { ) -> usize { let mut values_index = 0; let rows = input.rows as usize; + let is_last_instance_input = rows >= trace.len(); let skip_count = input.skip_rows as usize * self.op_x_rows; let initial_count = DmaInfo::get_loop_count(input.encoded) - skip_count; let mut count64 = initial_count; @@ -121,7 +122,7 @@ impl Dma64AlignedInputCpySM { } } - if input.is_last_instance_input { + if is_last_instance_input { if seq_end { air_values.segment_last_seq_end = F::ONE; air_values.segment_last_dst64 = F::ZERO; @@ -192,8 +193,8 @@ impl Dma64AlignedModule for Dma64AlignedInputCpySM { timer_start_trace!(DMA_64_ALIGNED_TRACE); - // Split the dma_trace.buffer into slices matching each inner vector’s length. - let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + // Flat the inputs and reorder to ensure first, last are in theirs positions. + let flat_inputs = crate::flatten_and_reorder_inputs(inputs); let trace_rows = trace.buffer.as_mut_slice(); let mut values_24_bits = Vec::with_capacity(num_rows * self.op_x_rows * 2); diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs index f71f9d20b..ec55b681e 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_instance.rs @@ -109,15 +109,14 @@ impl Instance for Dma64AlignedInstance { let (debug, inputs): (Vec<_>, Vec<_>) = collectors .into_iter() .map(|(_, collector)| { - let collector = collector.as_any().downcast::().unwrap(); - (collector.get_debug_info(), collector.inputs) + collector.as_any().downcast::().unwrap().take_debug_inputs() }) .unzip(); #[cfg(not(feature = "save_dma_collectors"))] let inputs: Vec<_> = collectors .into_iter() .map(|(_, collector)| { - collector.as_any().downcast::().unwrap().inputs + collector.as_any().downcast::().unwrap().take_inputs() }) .collect(); diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs index 9f310eef4..c4e2bdce2 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs @@ -64,6 +64,7 @@ impl Dma64AlignedMemSM { air_values: &mut Dma64AlignedMemAirValues, ) -> usize { let rows = input.rows as usize; + let is_last_instance_input = rows >= trace.len(); let skip_count = input.skip_rows as usize * self.op_x_rows; let initial_count = DmaInfo::get_loop_count(input.encoded) - skip_count; let mut count64 = initial_count; @@ -125,7 +126,7 @@ impl Dma64AlignedMemSM { } } - if input.is_last_instance_input { + if is_last_instance_input { if seq_end { air_values.segment_last_seq_end = F::ONE; air_values.segment_last_src64 = F::ZERO; @@ -206,9 +207,8 @@ impl Dma64AlignedModule for Dma64AlignedMemSM { dma_trace("Dma64AlignedMem", total_inputs, num_rows); timer_start_trace!(DMA_64_ALIGNED_TRACE); - - // Split the dma_trace.buffer into slices matching each inner vector’s length. - let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + // Flat the inputs and reorder to ensure first, last are in theirs positions. + let flat_inputs = crate::flatten_and_reorder_inputs(inputs); let trace_rows = trace.buffer.as_mut_slice(); let mut local_16_bits_table = vec![0u32; 1 << 16]; diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs index 8a318da97..fce27c00a 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs @@ -64,13 +64,11 @@ impl Dma64AlignedMemCpySM { air_values: &mut Dma64AlignedMemCpyAirValues, ) -> usize { let rows = input.rows as usize; + let is_last_instance_input = rows >= trace.len(); let skip_count = input.skip_rows as usize * self.op_x_rows; let initial_count = DmaInfo::get_loop_count(input.encoded) - skip_count; let mut count64 = initial_count; - // println!( - // "DMA_64_ALIGNED INPUT {input:?} count:{count64} rows:{rows} dma_info:{}", - // DmaInfo::to_string(input.encoded) - // ); + let mut src_values_index = 0; let mut dst64 = ((input.dst + 7) >> 3) + skip_count as u32; let mut src64 = ((input.src + 7) >> 3) + skip_count as u32; @@ -93,10 +91,6 @@ impl Dma64AlignedMemCpySM { src64 += addr_incr_by_row; row.set_count64(count64 as u32); - // println!( - // "DMA_64_ALIGNED INPUT count64:{count64} dst64:{dst64} src64:{src64} ops_x_row:{}", - // self.op_x_rows - // ); let use_count = if count64 <= self.op_x_rows { seq_end = true; // trace i zerofilled, not set values zero @@ -115,10 +109,9 @@ impl Dma64AlignedMemCpySM { row.set_value(index, 0, value as u32); row.set_value(index, 1, (value >> 32) as u32); } - // println!("DMA_64_ALIGNED TRACE ROW {irow} {row:?}"); } - if input.is_last_instance_input { + if is_last_instance_input { if seq_end { air_values.segment_last_seq_end = F::ONE; air_values.segment_last_src64 = F::ZERO; @@ -194,8 +187,8 @@ impl Dma64AlignedModule for Dma64AlignedMemCpySM { timer_start_trace!(DMA_64_ALIGNED_TRACE); - // Split the dma_trace.buffer into slices matching each inner vector’s length. - let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + // Flat the inputs and reorder to ensure first, last are in theirs positions. + let flat_inputs = crate::flatten_and_reorder_inputs(inputs); let trace_rows = trace.buffer.as_mut_slice(); let mut local_16_bits_table = vec![0u32; 1 << 16]; diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs index 9937dddf2..142159b0b 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs @@ -63,6 +63,7 @@ impl Dma64AlignedMemSetSM { air_values: &mut Dma64AlignedMemSetAirValues, ) -> usize { let rows = input.rows as usize; + let is_last_instance_input = rows >= trace.len(); let skip_count = input.skip_rows as usize * self.op_x_rows; let initial_count = DmaInfo::get_loop_count(input.encoded) - skip_count; let mut count64 = initial_count; @@ -101,7 +102,7 @@ impl Dma64AlignedMemSetSM { } } - if input.is_last_instance_input { + if is_last_instance_input { if seq_end { air_values.segment_last_seq_end = F::ONE; air_values.segment_last_dst64 = F::ZERO; @@ -183,8 +184,9 @@ impl Dma64AlignedModule for Dma64AlignedMemSetSM { timer_start_trace!(DMA_64_ALIGNED_TRACE); - // Split the dma_trace.buffer into slices matching each inner vector’s length. - let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); + // Flat the inputs and to ensure that the first and the last inputs are placed in the right order, + let flat_inputs = crate::flatten_and_reorder_inputs(inputs); + let trace_rows = trace.buffer.as_mut_slice(); let mut local_16_bits_table = vec![0u32; 1 << 16]; diff --git a/precompiles/dma/src/dma_collect_counters.rs b/precompiles/dma/src/dma_collect_counters.rs index f6687f290..90a6092a1 100644 --- a/precompiles/dma/src/dma_collect_counters.rs +++ b/precompiles/dma/src/dma_collect_counters.rs @@ -32,19 +32,13 @@ impl DmaCollectCounters { && self.memset.is_final_skip() && self.memcmp.is_final_skip() } - pub fn should_collect(&mut self, rows: u64, op: u8) -> Option<(u32, u32, bool)> { - let cc = match op { - ZiskOp::DMA_MEMCPY | ZiskOp::DMA_XMEMCPY => &mut self.memcpy, - ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => &mut self.memcmp, - ZiskOp::DMA_INPUTCPY => &mut self.inputcpy, - ZiskOp::DMA_XMEMSET => &mut self.memset, + pub fn should_collect(&mut self, rows: u64, op: u8) -> Option<(u32, u32)> { + match op { + ZiskOp::DMA_MEMCPY | ZiskOp::DMA_XMEMCPY => self.memcpy.should_process(rows as u32), + ZiskOp::DMA_MEMCMP | ZiskOp::DMA_XMEMCMP => self.memcmp.should_process(rows as u32), + ZiskOp::DMA_INPUTCPY => self.inputcpy.should_process(rows as u32), + ZiskOp::DMA_XMEMSET => self.memset.should_process(rows as u32), _ => panic!("Invalid operation 0x{op:02X} for DmaCollectCounters"), - }; - - if let Some((skip, max_count)) = cc.should_process(rows as u32) { - Some((skip, max_count, cc.is_final_skip())) - } else { - None } } #[inline(always)] diff --git a/precompiles/dma/src/dma_common.rs b/precompiles/dma/src/dma_common.rs index 190b2b2cf..530f7036c 100644 --- a/precompiles/dma/src/dma_common.rs +++ b/precompiles/dma/src/dma_common.rs @@ -33,3 +33,114 @@ pub fn dma_trace(title: &str, rows: usize, num_rows: usize) { ); } } + +/// Flattens and reorders input vectors to ensure proper sequencing. +/// +/// This function reorders vectors so that: +/// - The vector whose first element has `must_be_first()` == true is placed first +/// - The vector whose last element has `must_be_last()` == true is placed last +/// +/// This is necessary for DMA operations to maintain proper sequencing when +/// operations span multiple chunks or segments. +/// +/// # Type Parameters +/// * `T` - The input type, must implement `DmaInputPosition` +/// +/// # Arguments +/// * `inputs` - Slice of vectors containing DMA inputs +/// +/// # Returns +/// A flattened vector with references to inputs, properly ordered +pub fn flatten_and_reorder_inputs(inputs: &[Vec]) -> Vec<&T> +where + T: DmaInputPosition, +{ + if inputs.is_empty() { + return Vec::new(); + } + + // Find indices of vectors that must be first/last + let first_idx = + inputs.iter().position(|vec| vec.first().is_some_and(|input| input.must_be_first())); + + let last_idx = + inputs.iter().position(|vec| vec.last().is_some_and(|input| input.must_be_last())); + + match (first_idx, last_idx) { + (None, None) => { + // No special ordering required, simple flatten + inputs.iter().flatten().collect() + } + (Some(0), None) => { + // First is already at position 0, simple flatten + inputs.iter().flatten().collect() + } + (Some(f_idx), None) => { + // Only first needs reordering: move to beginning + std::iter::once(&inputs[f_idx]) + .chain(inputs[..f_idx].iter()) + .chain(inputs[f_idx + 1..].iter()) + .flatten() + .collect() + } + (None, Some(l_idx)) if l_idx == inputs.len() - 1 => { + // Last is already at final position, simple flatten + inputs.iter().flatten().collect() + } + (None, Some(l_idx)) => { + // Only last needs reordering: move to end + inputs[..l_idx] + .iter() + .chain(inputs[l_idx + 1..].iter()) + .chain(std::iter::once(&inputs[l_idx])) + .flatten() + .collect() + } + (Some(f_idx), Some(l_idx)) if f_idx == l_idx => { + // Same vector is both first and last (edge case: huge operation) + // Just put it first (or last, doesn't matter) + if f_idx == 0 { + inputs.iter().flatten().collect() + } else { + std::iter::once(&inputs[f_idx]) + .chain(inputs[..f_idx].iter()) + .chain(inputs[f_idx + 1..].iter()) + .flatten() + .collect() + } + } + (Some(f_idx), Some(l_idx)) if f_idx == 0 && l_idx == inputs.len() - 1 => { + // Already in correct order + inputs.iter().flatten().collect() + } + (Some(f_idx), Some(l_idx)) => { + // Both need reordering: first at beginning, last at end + // Handle different cases to avoid double-including indices + if f_idx < l_idx { + // first comes before last in original order + std::iter::once(&inputs[f_idx]) + .chain(inputs[..f_idx].iter()) + .chain(inputs[f_idx + 1..l_idx].iter()) + .chain(inputs[l_idx + 1..].iter()) + .chain(std::iter::once(&inputs[l_idx])) + .flatten() + .collect() + } else { + // last comes before first in original order + std::iter::once(&inputs[f_idx]) + .chain(inputs[..l_idx].iter()) + .chain(inputs[l_idx + 1..f_idx].iter()) + .chain(inputs[f_idx + 1..].iter()) + .chain(std::iter::once(&inputs[l_idx])) + .flatten() + .collect() + } + } + } +} + +/// Trait for types that have a skip_rows field +pub trait DmaInputPosition { + fn must_be_last(&self) -> bool; + fn must_be_first(&self) -> bool; +} diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs index 11fcd45c0..c42c06423 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs @@ -186,11 +186,6 @@ impl DmaPrePostSM { src_offset - dst_offset }; - let read_value_23 = - if selr_value > 0 { input.src_values[0] << (selr_value * 8) } else { 0 }; - let read_value_01 = (input.src_values[0] >> (selr_value * 8)) - | if selr_value > 0 { input.src_values[1] << (64 - selr_value * 8) } else { 0 }; - // NOTE: special case of count = 8 for memcmp, the mask must be all 0s, for this reason // apply mask to count before left shift. let mask = if count == 8 { @@ -201,14 +196,6 @@ impl DmaPrePostSM { _mask ^ (_mask << (count * 8)) }; - let write_value_01 = (read_value_01 & mask) | (input.dst_pre_value & !mask); - let write_value_23 = (read_value_23 & mask) | (input.dst_pre_value & !mask); - - trace.set_write_value(0, write_value_01 as u32); - trace.set_write_value(1, (write_value_01 >> 32) as u32); - trace.set_write_value(2, write_value_23 as u32); - trace.set_write_value(3, (write_value_23 >> 32) as u32); - trace.set_sb(0, (mask & 0x0000_0000_0000_00FF) != 0); trace.set_sb(1, (mask & 0x0000_0000_0000_FF00) != 0); trace.set_sb(2, (mask & 0x0000_0000_00FF_0000) != 0); diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs index 7d8d9bd7e..ed49dc99c 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs @@ -85,7 +85,7 @@ impl DmaPrePostCollector { return true; } - if let Some((skip, max_count, _)) = self.collect_counters.should_collect(rows as u64, op) { + if let Some((skip, max_count)) = self.collect_counters.should_collect(rows as u64, op) { self.rlog.log_collect(rows, data); self.inputs.extend(match op { ZiskOp::DMA_XMEMSET => { diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs index 9243da57d..d77956727 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs @@ -82,13 +82,7 @@ impl DmaPrePostInputCpySM { }; trace.set_count(count as u8); - // , set_main_step:ubit(36), set_dst64:ubit(29), set_dst_offset:ubit(3), set_count:ubit(3), set_sel_memcpy:bit, set_sel_memcmp:bit, set_memcmp_result_nz:bit, - // set_l_memcmp_result:u32, set_sel_inputcpy:bit, set_sel_memset:bit, set_selr:[bit; 7], set_dst_offset_gt_src_offset:bit, set_src64:ubit(29), set_src_offset:ubit(3), - // set_enabled_second_read:bit, set_rb:[u8; 16], set_pb:[u8; 8], set_sb:[bit; 8], set_last_dst_byte:u8, set_abs_diff_dst_src:u8, set_memcmp_result_is_negative:bit, - // set_diff_factor:[u64; 2], set_bus_write_value:[u32; 2], set_write_value:[u32; 4], - trace.set_sel_inputcpy(false); - // intermediate: trace.last_dst_byte(0); let mut value = input.src_values[0]; let mut rb = [0u8; 16]; diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_memcpy.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_memcpy.rs index c584bd032..edc7b0c9b 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_memcpy.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_memcpy.rs @@ -86,11 +86,6 @@ impl DmaPrePostMemCpySM { }; trace.set_count(count as u8); - // , set_main_step:ubit(36), set_dst64:ubit(29), set_dst_offset:ubit(3), set_count:ubit(3), set_sel_memcpy:bit, set_sel_memcmp:bit, set_memcmp_result_nz:bit, - // set_l_memcmp_result:u32, set_sel_inputcpy:bit, set_sel_memset:bit, set_selr:[bit; 7], set_dst_offset_gt_src_offset:bit, set_src64:ubit(29), set_src_offset:ubit(3), - // set_enabled_second_read:bit, set_rb:[u8; 16], set_pb:[u8; 8], set_sb:[bit; 8], set_last_dst_byte:u8, set_abs_diff_dst_src:u8, set_memcmp_result_is_negative:bit, - // set_diff_factor:[u64; 2], set_bus_write_value:[u32; 2], set_write_value:[u32; 4], - trace.set_sel_memcpy(true); // intermediate: trace.last_dst_byte(0); let second_read = (src_offset as usize + count) > 8; diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs index e181675fe..dac768002 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs @@ -77,6 +77,7 @@ impl DmaUnalignedSM { air_values: &mut DmaUnalignedAirValues, ) -> usize { let rows = input.count as usize; + let is_last_instance_input = rows >= trace.len(); let initial_count = DmaInfo::get_loop_count(input.encoded) - input.skip as usize; let mut count = initial_count; let src_offset = DmaInfo::get_loop_src_offset(input.encoded); @@ -112,19 +113,9 @@ impl DmaUnalignedSM { let value = input.src_values[src_values_index]; src_values_index += 1; - let write_value = if count == 0 { + if count == 0 { seq_end = true; next_value = 0; - match src_offset { - 1 => value >> 8, - 2 => value >> 16, - 3 => value >> 24, - 4 => value >> 32, - 5 => value >> 40, - 6 => value >> 48, - 7 => value >> 56, - _ => panic!("invalid src_offset {src_offset} on DmaUnaligned"), - } } else { count -= 1; if src_values_index >= input.src_values.len() { @@ -136,16 +127,6 @@ impl DmaUnalignedSM { ); } next_value = input.src_values[src_values_index]; - match src_offset { - 1 => (value >> 8) | (next_value << 56), - 2 => (value >> 16) | (next_value << 48), - 3 => (value >> 24) | (next_value << 40), - 4 => (value >> 32) | (next_value << 32), - 5 => (value >> 40) | (next_value << 24), - 6 => (value >> 48) | (next_value << 16), - 7 => (value >> 56) | (next_value << 8), - _ => panic!("invalid src_offset {src_offset} on DmaUnaligned"), - } }; row.set_read_bytes(0, value as u8); @@ -157,8 +138,8 @@ impl DmaUnalignedSM { row.set_read_bytes(6, (value >> 48) as u8); row.set_read_bytes(7, (value >> 56) as u8); - row.set_write_value(0, write_value as u32); - row.set_write_value(1, (write_value >> 32) as u32); + // row.set_write_value(0, write_value as u32); + // row.set_write_value(1, (write_value >> 32) as u32); let value = value as usize; local_dual_byte_table[value & 0xFFFF] += 1; @@ -167,7 +148,7 @@ impl DmaUnalignedSM { local_dual_byte_table[(value >> 48) & 0xFFFF] += 1; } - if input.is_last_instance_input { + if is_last_instance_input { if seq_end { air_values.segment_last_seq_end = F::ONE; air_values.segment_last_src64 = F::ZERO; @@ -233,7 +214,6 @@ impl DmaUnalignedSM { .iter() .map(|inputs| inputs.iter().map(|input| input.count as usize).sum::()) .sum(); - assert!(total_inputs <= num_rows); assert!(total_inputs > 0); @@ -241,8 +221,8 @@ impl DmaUnalignedSM { timer_start_trace!(DMA_UNALIGNED_TRACE); + let flat_inputs = crate::flatten_and_reorder_inputs(inputs); // Split the dma_trace.buffer into slices matching each inner vector’s length. - let flat_inputs: Vec<_> = inputs.iter().flatten().collect(); let trace_rows = trace.buffer.as_mut_slice(); // TODO: add std method to used short table, no sense with instances around 2^22 use 64 bits, need more space. diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs index 006cef5d9..75fc8bff9 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs @@ -4,7 +4,7 @@ //! It manages collected inputs and interacts with the `DmaSM` to compute witnesses for //! execution plans. -use crate::{DmaCollectCounters, DmaCollectorRoutingLog, DmaUnalignedInput}; +use crate::{DmaCollectCounters, DmaCollectorRoutingLog, DmaInputPosition, DmaUnalignedInput}; use std::any::Any; use zisk_common::{BusDevice, BusId, ChunkId, OP, OPERATION_BUS_ID, OP_TYPE}; use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; @@ -12,6 +12,7 @@ use zisk_core::{zisk_ops::ZiskOp, ZiskOperationType}; pub struct DmaUnalignedCollector { /// Collected inputs for witness computation. pub inputs: Vec, + pub last_input_index: Option, pub chunk_id: ChunkId, @@ -53,6 +54,7 @@ impl DmaUnalignedCollector { last_segment_collector, chunk_id, rlog: DmaCollectorRoutingLog::new(chunk_id), + last_input_index: None, } } @@ -94,18 +96,16 @@ impl DmaUnalignedCollector { return self.rlog.log_discard_cond(false, 1, data, true); } - if let Some((skip, max_count, is_final_skip)) = - self.collect_counters.should_collect(rows, op) - { + if let Some((skip, max_count)) = self.collect_counters.should_collect(rows, op) { self.rlog.log_collect(rows as usize, data); - self.inputs.push(DmaUnalignedInput::from( + self.add_input(DmaUnalignedInput::from( data, data_ext, self.trace_offset, skip as usize, max_count as usize, - self.last_segment_collector && is_final_skip, )); + self.trace_offset += max_count as usize; if self.inputs.len() >= self.num_inputs as usize { self.collect_counters.debug_assert_is_final_skip(); @@ -118,6 +118,39 @@ impl DmaUnalignedCollector { true } + /// Adds an input to the collector with proper ordering management. + /// + /// This method handles: + /// - Adding the input to the vector + /// - Managing inputs that must be first (swaps to position 0) + /// - Tracking inputs that must be last (stores index for later swap) + /// + /// # Arguments + /// * `input` - The input to add + #[inline(always)] + fn add_input(&mut self, input: DmaUnalignedInput) { + // Check if input must be first before pushing + let must_be_first = input.must_be_first(); + let must_be_last = input.must_be_last(); + let current_index = self.inputs.len(); + + // Push the input + self.inputs.push(input); + + // Handle ordering requirements + if must_be_first { + // Swap with position 0 if not already first + if current_index > 0 { + self.inputs.swap(0, current_index); + } + } else if must_be_last { + // Edge case: if an input is huge and it's both first and last, + // must_be_first takes precedence and this branch won't execute + assert!(self.last_input_index.is_none(), "Multiple inputs marked as last input"); + self.last_input_index = Some(current_index); + } + } + /// Returns debug information about the collector's state. /// /// When the `save_dma_collectors` feature is enabled, this returns detailed information @@ -137,6 +170,19 @@ impl DmaUnalignedCollector { #[cfg(not(feature = "save_dma_collectors"))] String::new() } + pub fn take_inputs(&mut self) -> Vec { + if let Some(last_index) = self.last_input_index { + // If there's a last input index, swap it with the last element to ensure it's the last one in the trace. + let current_last_index = self.inputs.len() - 1; + self.inputs.swap(last_index, current_last_index); + } + std::mem::take(&mut self.inputs) + } + pub fn take_debug_inputs(&mut self) -> (String, Vec) { + let debug_info = self.get_debug_info(); + let inputs = self.take_inputs(); + (debug_info, inputs) + } } impl BusDevice for DmaUnalignedCollector { diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs index 8cc27b3d0..91adaa043 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs @@ -6,7 +6,6 @@ use zisk_core::zisk_ops::ZiskOp; pub struct DmaUnalignedInput { pub src: u32, pub dst: u32, - pub is_first_instance_input: bool, pub is_last_instance_input: bool, pub is_mem_eq: bool, pub trace_offset: u32, // offset inside trace to paralelize @@ -45,7 +44,6 @@ impl DmaUnalignedInput { trace_offset: usize, skip: usize, max_count: usize, - is_last_instance_input: bool, ) -> Self { let encoded = data[DMA_ENCODED]; let op = data[OP] as u8; @@ -72,8 +70,7 @@ impl DmaUnalignedInput { dst: data[A] as u32 + pre_count, src: data[B] as u32 + DmaInfo::get_src64_inc_by_pre(encoded) as u32 * 8, trace_offset: trace_offset as u32, - is_first_instance_input: trace_offset == 0, - is_last_instance_input, + is_last_instance_input: max_count < pending_count, step: data[STEP], skip: skip as u32, count: count as u32, @@ -103,7 +100,6 @@ impl DmaUnalignedInput { "pos", "src", "dst", - "is_first_instance_input", "is_last_instance_input", "is_mem_eq", "trace_offset", @@ -123,7 +119,6 @@ impl DmaUnalignedInput { pos, input.src, input.dst, - input.is_first_instance_input, input.is_last_instance_input, input.is_mem_eq, input.trace_offset, @@ -138,3 +133,12 @@ impl DmaUnalignedInput { Ok(()) } } + +impl crate::DmaInputPosition for DmaUnalignedInput { + fn must_be_first(&self) -> bool { + self.skip > 0 + } + fn must_be_last(&self) -> bool { + self.is_last_instance_input + } +} diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs index 17e1ed60b..611b49956 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_instance.rs @@ -94,15 +94,14 @@ impl Instance for DmaUnalignedInstance { let (debug, inputs): (Vec<_>, Vec<_>) = collectors .into_iter() .map(|(_, collector)| { - let collector = collector.as_any().downcast::().unwrap(); - (collector.get_debug_info(), collector.inputs) + collector.as_any().downcast::().unwrap().take_debug_inputs() }) .unzip(); #[cfg(not(feature = "save_dma_collectors"))] let inputs: Vec<_> = collectors .into_iter() .map(|(_, collector)| { - collector.as_any().downcast::().unwrap().inputs + collector.as_any().downcast::().unwrap().take_inputs() }) .collect(); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs index ac3e54982..976529ffd 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int256_div.rs @@ -3,8 +3,12 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; + use crate::{ziskos_fcall, ziskos_fcall_param}; use super::FCALL_BIG_INT256_DIV_ID; + #[cfg(not(feature = "inputcpy"))] + use crate::ziskos_fcall_get; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } else { use crate::zisklib::fcalls_impl::big_int256_div::big_int256_div; } @@ -46,9 +50,23 @@ pub fn fcall_bigint256_div( ziskos_fcall_param!(a_value, 4); ziskos_fcall_param!(b_value, 4); ziskos_fcall!(FCALL_BIG_INT256_DIV_ID); - ( - [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()], - [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()], - ) + #[cfg(not(feature = "inputcpy"))] + { + ( + [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()], + [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()], + ) + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + // TODO: generate an [u64;8] and after return 2 slides + let mut quotient: MaybeUninit<[u64; 4]> = MaybeUninit::uninit(); + ziskos_inputcpy!(quotient, 32); + + let mut remainder: MaybeUninit<[u64; 4]> = MaybeUninit::uninit(); + ziskos_inputcpy!(remainder, 32); + (unsafe { quotient.assume_init() }, unsafe { remainder.assume_init() }) + } } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs index 1c82d1aac..9c9f4865d 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/big_int_div.rs @@ -3,8 +3,10 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, ziskos_inputcpy}; + use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_BIG_INT_DIV_ID; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } else { use crate::zisklib::fcalls_impl::big_int_div::big_int_div_into; } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs index f3e4e63a6..5be46c9aa 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bin_decomp.rs @@ -5,6 +5,8 @@ cfg_if! { use core::arch::asm; use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; use super::FCALL_BIN_DECOMP_ID; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } else { use crate::zisklib::fcalls_impl::bin_decomp::bin_decomp; } @@ -42,11 +44,23 @@ pub fn fcall_bin_decomp( ziskos_fcall!(FCALL_BIN_DECOMP_ID); let len_bits = ziskos_fcall_get() as usize; - let mut bits = vec![0u64; len_bits]; - for i in 0..len_bits { - bits[i] = ziskos_fcall_get(); - } + #[cfg(not(feature = "inputcpy"))] + { + let mut bits = vec![0u64; len_bits]; + for i in 0..len_bits { + bits[i] = ziskos_fcall_get(); + } - (len_bits, bits) + (len_bits, bits) + } + #[cfg(feature = "inputcpy")] + { + let mut bits: Vec = Vec::with_capacity(len_bits); + ziskos_inputcpy!(bits, len_bits * 8); + unsafe { + bits.set_len(len_bits); + } + (len_bits, bits) + } } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp.rs index 6aa9839df..4c58544a2 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp.rs @@ -4,9 +4,13 @@ cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; use crate::{ - ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, + ziskos_fcall, ziskos_fcall_param, zisklib::{FCALL_BLS12_381_FP_INV_ID, FCALL_BLS12_381_FP_SQRT_ID} }; + #[cfg(not(feature = "inputcpy"))] + use crate::ziskos_fcall_get; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } else { use crate::zisklib::fcalls_impl::bls12_381::{bls12_381_fp_inv, bls12_381_fp_sqrt}; } @@ -44,14 +48,24 @@ pub fn fcall_bls12_381_fp_inv( { ziskos_fcall_param!(p_value, 8); ziskos_fcall!(FCALL_BLS12_381_FP_INV_ID); - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ] + #[cfg(not(feature = "inputcpy"))] + { + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut result: MaybeUninit<[u64; 6]> = MaybeUninit::uninit(); + ziskos_inputcpy!(result, 48); + unsafe { result.assume_init() } + } } } @@ -88,14 +102,24 @@ pub fn fcall_bls12_381_fp_sqrt( { ziskos_fcall_param!(p_value, 8); ziskos_fcall!(FCALL_BLS12_381_FP_SQRT_ID); - [ - ziskos_fcall_get(), // results[0] - indicates if a sqrt exists (1) or not (0) - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ] + #[cfg(not(feature = "inputcpy"))] + { + [ + ziskos_fcall_get(), // results[0] - indicates if a sqrt exists (1) or not (0) + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut result: MaybeUninit<[u64; 7]> = MaybeUninit::uninit(); + ziskos_inputcpy!(result, 56); + unsafe { result.assume_init() } + } } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp2.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp2.rs index 91942d2aa..b82524b3b 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp2.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/fp2.rs @@ -4,9 +4,13 @@ cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; use crate::{ - ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, + ziskos_fcall, ziskos_fcall_param, zisklib::{FCALL_BLS12_381_FP2_INV_ID, FCALL_BLS12_381_FP2_SQRT_ID} }; + #[cfg(not(feature = "inputcpy"))] + use crate::ziskos_fcall_get; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } else { use crate::zisklib::fcalls_impl::bls12_381::{bls12_381_fp2_inv, bls12_381_fp2_sqrt_13}; } @@ -45,20 +49,30 @@ pub fn fcall_bls12_381_fp2_inv( { ziskos_fcall_param!(p_value, 12); ziskos_fcall!(FCALL_BLS12_381_FP2_INV_ID); - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ] + #[cfg(not(feature = "inputcpy"))] + { + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut res: MaybeUninit<[u64; 12]> = MaybeUninit::uninit(); + ziskos_inputcpy!(res, 96); + unsafe { res.assume_init() } + } } } @@ -94,20 +108,30 @@ pub fn fcall_bls12_381_fp2_sqrt( { ziskos_fcall_param!(p_value, 16); ziskos_fcall!(FCALL_BLS12_381_FP2_SQRT_ID); - [ - ziskos_fcall_get(), // results[0] - indicates if a sqrt exists (1) or not (0) - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ] + #[cfg(not(feature = "inputcpy"))] + { + [ + ziskos_fcall_get(), // results[0] - indicates if a sqrt exists (1) or not (0) + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut res: MaybeUninit<[u64; 13]> = MaybeUninit::uninit(); + ziskos_inputcpy!(res, 104); + unsafe { res.assume_init() } + } } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/twist.rs index 548f8c579..6edf3e2a6 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bls12_381/twist.rs @@ -4,9 +4,13 @@ cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; use crate::{ - ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, + ziskos_fcall, ziskos_fcall_param, zisklib::{FCALL_BLS12_381_TWIST_ADD_LINE_COEFFS_ID, FCALL_BLS12_381_TWIST_DBL_LINE_COEFFS_ID}, }; + #[cfg(not(feature = "inputcpy"))] + use crate::ziskos_fcall_get; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } else { use crate::zisklib::fcalls_impl::bls12_381::{bls12_381_twist_add_line_coeffs, bls12_381_twist_dbl_line_coeffs}; } @@ -48,36 +52,48 @@ pub fn fcall_bls12_381_twist_add_line_coeffs( ziskos_fcall_param!(p1_value, 24); ziskos_fcall_param!(p2_value, 24); ziskos_fcall!(FCALL_BLS12_381_TWIST_ADD_LINE_COEFFS_ID); - ( - [ - ziskos_fcall_get(), // 0 - ziskos_fcall_get(), // 1 - ziskos_fcall_get(), // 2 - ziskos_fcall_get(), // 3 - ziskos_fcall_get(), // 4 - ziskos_fcall_get(), // 5 - ziskos_fcall_get(), // 6 - ziskos_fcall_get(), // 7 - ziskos_fcall_get(), // 8 - ziskos_fcall_get(), // 9 - ziskos_fcall_get(), // 10 - ziskos_fcall_get(), // 11 - ], - [ - ziskos_fcall_get(), // 0 - ziskos_fcall_get(), // 1 - ziskos_fcall_get(), // 2 - ziskos_fcall_get(), // 3 - ziskos_fcall_get(), // 4 - ziskos_fcall_get(), // 5 - ziskos_fcall_get(), // 6 - ziskos_fcall_get(), // 7 - ziskos_fcall_get(), // 8 - ziskos_fcall_get(), // 9 - ziskos_fcall_get(), // 10 - ziskos_fcall_get(), // 11 - ], - ) + #[cfg(not(feature = "inputcpy"))] + { + ( + [ + ziskos_fcall_get(), // 0 + ziskos_fcall_get(), // 1 + ziskos_fcall_get(), // 2 + ziskos_fcall_get(), // 3 + ziskos_fcall_get(), // 4 + ziskos_fcall_get(), // 5 + ziskos_fcall_get(), // 6 + ziskos_fcall_get(), // 7 + ziskos_fcall_get(), // 8 + ziskos_fcall_get(), // 9 + ziskos_fcall_get(), // 10 + ziskos_fcall_get(), // 11 + ], + [ + ziskos_fcall_get(), // 0 + ziskos_fcall_get(), // 1 + ziskos_fcall_get(), // 2 + ziskos_fcall_get(), // 3 + ziskos_fcall_get(), // 4 + ziskos_fcall_get(), // 5 + ziskos_fcall_get(), // 6 + ziskos_fcall_get(), // 7 + ziskos_fcall_get(), // 8 + ziskos_fcall_get(), // 9 + ziskos_fcall_get(), // 10 + ziskos_fcall_get(), // 11 + ], + ) + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut lambda: MaybeUninit<[u64; 12]> = MaybeUninit::uninit(); + ziskos_inputcpy!(lambda, 12 * 8); + let mut mu: MaybeUninit<[u64; 12]> = MaybeUninit::uninit(); + ziskos_inputcpy!(mu, 12 * 8); + (unsafe { lambda.assume_init() }, unsafe { mu.assume_init() }) + } } } @@ -111,35 +127,47 @@ pub fn fcall_bls12_381_twist_dbl_line_coeffs( { ziskos_fcall_param!(p_value, 24); ziskos_fcall!(FCALL_BLS12_381_TWIST_DBL_LINE_COEFFS_ID); - ( - [ - ziskos_fcall_get(), // 0 - ziskos_fcall_get(), // 1 - ziskos_fcall_get(), // 2 - ziskos_fcall_get(), // 3 - ziskos_fcall_get(), // 4 - ziskos_fcall_get(), // 5 - ziskos_fcall_get(), // 6 - ziskos_fcall_get(), // 7 - ziskos_fcall_get(), // 8 - ziskos_fcall_get(), // 9 - ziskos_fcall_get(), // 10 - ziskos_fcall_get(), // 11 - ], - [ - ziskos_fcall_get(), // 0 - ziskos_fcall_get(), // 1 - ziskos_fcall_get(), // 2 - ziskos_fcall_get(), // 3 - ziskos_fcall_get(), // 4 - ziskos_fcall_get(), // 5 - ziskos_fcall_get(), // 6 - ziskos_fcall_get(), // 7 - ziskos_fcall_get(), // 8 - ziskos_fcall_get(), // 9 - ziskos_fcall_get(), // 10 - ziskos_fcall_get(), // 11 - ], - ) + #[cfg(not(feature = "inputcpy"))] + { + ( + [ + ziskos_fcall_get(), // 0 + ziskos_fcall_get(), // 1 + ziskos_fcall_get(), // 2 + ziskos_fcall_get(), // 3 + ziskos_fcall_get(), // 4 + ziskos_fcall_get(), // 5 + ziskos_fcall_get(), // 6 + ziskos_fcall_get(), // 7 + ziskos_fcall_get(), // 8 + ziskos_fcall_get(), // 9 + ziskos_fcall_get(), // 10 + ziskos_fcall_get(), // 11 + ], + [ + ziskos_fcall_get(), // 0 + ziskos_fcall_get(), // 1 + ziskos_fcall_get(), // 2 + ziskos_fcall_get(), // 3 + ziskos_fcall_get(), // 4 + ziskos_fcall_get(), // 5 + ziskos_fcall_get(), // 6 + ziskos_fcall_get(), // 7 + ziskos_fcall_get(), // 8 + ziskos_fcall_get(), // 9 + ziskos_fcall_get(), // 10 + ziskos_fcall_get(), // 11 + ], + ) + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut lambda: MaybeUninit<[u64; 12]> = MaybeUninit::uninit(); + ziskos_inputcpy!(lambda, 12 * 8); + let mut mu: MaybeUninit<[u64; 12]> = MaybeUninit::uninit(); + ziskos_inputcpy!(mu, 12 * 8); + (unsafe { lambda.assume_init() }, unsafe { mu.assume_init() }) + } } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp.rs index 1216dcbb2..596ee11e5 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp.rs @@ -3,7 +3,11 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, zisklib::FCALL_BN254_FP_INV_ID}; + use crate::{ziskos_fcall, ziskos_fcall_param, zisklib::FCALL_BN254_FP_INV_ID}; + #[cfg(not(feature = "inputcpy"))] + use crate::ziskos_fcall_get; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } else { use crate::zisklib::fcalls_impl::bn254::bn254_fp_inv; } @@ -41,6 +45,16 @@ pub fn fcall_bn254_fp_inv( { ziskos_fcall_param!(p_value, 4); ziskos_fcall!(FCALL_BN254_FP_INV_ID); - [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()] + #[cfg(not(feature = "inputcpy"))] + { + [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()] + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut result: MaybeUninit<[u64; 4]> = MaybeUninit::uninit(); + ziskos_inputcpy!(result, 4 * 8); + unsafe { result.assume_init() } + } } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp2.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp2.rs index 105baae5d..d5512cbcb 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp2.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254/fp2.rs @@ -3,7 +3,11 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, zisklib::FCALL_BN254_FP2_INV_ID}; + use crate::{ziskos_fcall, ziskos_fcall_param, zisklib::FCALL_BN254_FP2_INV_ID}; + #[cfg(not(feature = "inputcpy"))] + use crate::ziskos_fcall_get; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } else { use crate::zisklib::fcalls_impl::bn254::bn254_fp2_inv; } @@ -41,15 +45,25 @@ pub fn fcall_bn254_fp2_inv( { ziskos_fcall_param!(p_value, 8); ziskos_fcall!(FCALL_BN254_FP2_INV_ID); - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ] + #[cfg(not(feature = "inputcpy"))] + { + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut result: MaybeUninit<[u64; 8]> = MaybeUninit::uninit(); + ziskos_inputcpy!(result, 8 * 8); + unsafe { result.assume_init() } + } } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/bn254/twist.rs b/ziskos/entrypoint/src/zisklib/fcalls/bn254/twist.rs index 38639962b..fb2dd065a 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/bn254/twist.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/bn254/twist.rs @@ -4,10 +4,14 @@ cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; use crate::{ - ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, + ziskos_fcall, ziskos_fcall_param, zisklib::{FCALL_BN254_TWIST_ADD_LINE_COEFFS_ID, FCALL_BN254_TWIST_DBL_LINE_COEFFS_ID} }; - } else { + #[cfg(not(feature = "inputcpy"))] + use crate::ziskos_fcall_get; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; + } else { use crate::zisklib::fcalls_impl::bn254::{bn254_twist_add_line_coeffs, bn254_twist_dbl_line_coeffs}; } @@ -47,28 +51,40 @@ pub fn fcall_bn254_twist_add_line_coeffs( ziskos_fcall_param!(p1_value, 16); ziskos_fcall_param!(p2_value, 16); ziskos_fcall!(FCALL_BN254_TWIST_ADD_LINE_COEFFS_ID); - ( - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ], - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ], - ) + #[cfg(not(feature = "inputcpy"))] + { + ( + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ], + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ], + ) + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut lambda: MaybeUninit<[u64; 8]> = MaybeUninit::uninit(); + ziskos_inputcpy!(lambda, 8 * 8); + let mut mu: MaybeUninit<[u64; 8]> = MaybeUninit::uninit(); + ziskos_inputcpy!(mu, 8 * 8); + unsafe { (lambda.assume_init(), mu.assume_init()) } + } } } @@ -102,27 +118,39 @@ pub fn fcall_bn254_twist_dbl_line_coeffs( { ziskos_fcall_param!(p_value, 16); ziskos_fcall!(FCALL_BN254_TWIST_DBL_LINE_COEFFS_ID); - ( - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ], - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ], - ) + #[cfg(not(feature = "inputcpy"))] + { + ( + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ], + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ], + ) + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut lambda: MaybeUninit<[u64; 8]> = MaybeUninit::uninit(); + ziskos_inputcpy!(lambda, 8 * 8); + let mut mu: MaybeUninit<[u64; 8]> = MaybeUninit::uninit(); + ziskos_inputcpy!(mu, 8 * 8); + unsafe { (lambda.assume_init(), mu.assume_init()) } + } } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs index b9b95b1c3..035d548ad 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/ecdsa.rs @@ -3,7 +3,11 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, zisklib::FCALL_SECP256K1_ECDSA_VERIFY_ID}; + use crate::{ziskos_fcall, ziskos_fcall_param, zisklib::FCALL_SECP256K1_ECDSA_VERIFY_ID}; + #[cfg(not(feature = "inputcpy"))] + use crate::ziskos_fcall_get; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } } @@ -75,15 +79,25 @@ pub fn fcall_secp256k1_ecdsa_verify( ziskos_fcall_param!(r_value, 4); ziskos_fcall_param!(s_value, 4); ziskos_fcall!(FCALL_SECP256K1_ECDSA_VERIFY_ID); - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ] + #[cfg(not(feature = "inputcpy"))] + { + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut res: MaybeUninit<[u64; 8]> = MaybeUninit::uninit(); + ziskos_inputcpy!(res, 64); + unsafe { res.assume_init() } + } } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs index 1b27e77c9..dc71c98e0 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fn.rs @@ -3,7 +3,11 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, zisklib::FCALL_SECP256K1_FN_INV_ID}; + use crate::{ziskos_fcall, ziskos_fcall_param, zisklib::FCALL_SECP256K1_FN_INV_ID}; + #[cfg(not(feature = "inputcpy"))] + use crate::ziskos_fcall_get; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } else { use lib_c::secp256k1_fn_inv_c; } @@ -44,7 +48,16 @@ pub fn fcall_secp256k1_fn_inv( { ziskos_fcall_param!(p_value, 4); ziskos_fcall!(FCALL_SECP256K1_FN_INV_ID); - [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()] + #[cfg(not(feature = "inputcpy"))] + { + [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()] + } + #[cfg(feature = "inputcpy")] + { + let mut res: core::mem::MaybeUninit<[u64; 4]> = core::mem::MaybeUninit::uninit(); + ziskos_inputcpy!(res, 32); + unsafe { res.assume_init() } + } } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs index c8c4edfae..359eb866f 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256k1/fp.rs @@ -6,9 +6,13 @@ cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; use crate::{ - ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, ziskos_inputcpy, + ziskos_fcall, ziskos_fcall_param, zisklib::{FCALL_SECP256K1_FP_INV_ID, FCALL_SECP256K1_FP_SQRT_ID} }; + #[cfg(not(feature = "inputcpy"))] + use crate::ziskos_fcall_get; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } else { use lib_c::{secp256k1_fp_inv_c}; use crate::zisklib::fcalls_impl::secp256k1::secp256k1_fp_sqrt; @@ -51,7 +55,16 @@ pub fn fcall_secp256k1_fp_inv( { ziskos_fcall_param!(p_value, 4); ziskos_fcall!(FCALL_SECP256K1_FP_INV_ID); - [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()] + #[cfg(not(feature = "inputcpy"))] + { + [ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get(), ziskos_fcall_get()] + } + #[cfg(feature = "inputcpy")] + { + let mut res: MaybeUninit<[u64; 4]> = MaybeUninit::uninit(); + ziskos_inputcpy!(res, 32); + unsafe { res.assume_init() } + } } } @@ -92,8 +105,6 @@ pub fn fcall_secp256k1_fp_inv_in_place( /// /// Note that this is a *free-input call*, meaning the Zisk VM does not automatically verify the correctness /// of the result. It is the caller's responsibility to ensure it. - -#[cfg(not(feature = "inputcpy"))] #[allow(unused_variables)] pub fn fcall_secp256k1_fp_sqrt( p_value: &[u64; 4], @@ -116,13 +127,23 @@ pub fn fcall_secp256k1_fp_sqrt( ziskos_fcall_param!(p_value, 4); ziskos_fcall_param!(parity, 1); ziskos_fcall!(FCALL_SECP256K1_FP_SQRT_ID); - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ] + #[cfg(not(feature = "inputcpy"))] + { + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut res: MaybeUninit<[u64; 5]> = MaybeUninit::uninit(); + ziskos_inputcpy!(res, 40); + unsafe { res.assume_init() } + } } } @@ -140,21 +161,3 @@ pub fn fcall_secp256k1_fp_sqrt_p(p_value: &[u64; 4], parity: u64, res: &mut [u64 ziskos_inputcpy!(res, 40); } } - -#[cfg(feature = "inputcpy")] -#[allow(unused_variables)] -#[inline(always)] -pub fn fcall_secp256k1_fp_sqrt(p_value: &[u64; 4], parity: u64) -> [u64; 5] { - #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] - unreachable!(); - #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] - { - use core::mem::MaybeUninit; - ziskos_fcall_param!(p_value, 4); - ziskos_fcall_param!(parity, 1); - ziskos_fcall!(FCALL_SECP256K1_FP_SQRT_ID); - let mut res: MaybeUninit<[u64; 5]> = MaybeUninit::uninit(); - ziskos_inputcpy!(res, 40); - unsafe { res.assume_init() } - } -} diff --git a/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs index 947a9eb2a..2ddc45dde 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/secp256r1/ecdsa.rs @@ -3,7 +3,11 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { use core::arch::asm; - use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param, zisklib::FCALL_SECP256R1_ECDSA_VERIFY_ID}; + use crate::{ziskos_fcall, ziskos_fcall_param, zisklib::FCALL_SECP256R1_ECDSA_VERIFY_ID}; + #[cfg(not(feature = "inputcpy"))] + use crate::ziskos_fcall_get; + #[cfg(feature = "inputcpy")] + use crate::ziskos_inputcpy; } } @@ -75,15 +79,25 @@ pub fn fcall_secp256r1_ecdsa_verify( ziskos_fcall_param!(r_value, 4); ziskos_fcall_param!(s_value, 4); ziskos_fcall!(FCALL_SECP256R1_ECDSA_VERIFY_ID); - [ - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ziskos_fcall_get(), - ] + #[cfg(not(feature = "inputcpy"))] + { + [ + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ziskos_fcall_get(), + ] + } + #[cfg(feature = "inputcpy")] + { + use core::mem::MaybeUninit; + let mut res: MaybeUninit<[u64; 8]> = MaybeUninit::uninit(); + ziskos_inputcpy!(res, 64); + unsafe { res.assume_init() } + } } } From 4497fddf95cd6417afe42f2b4858aeaea627eb0f Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Tue, 17 Feb 2026 12:40:11 +0000 Subject: [PATCH 540/782] Fixes --- ziskos/entrypoint/src/hints/hint_buffer.rs | 2 +- ziskos/entrypoint/src/hints/mod.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index dc7bd68c9..bc6641271 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -173,9 +173,9 @@ impl HintBuffer { u64::from_le_bytes(header_bytes.try_into().unwrap()) }; - let hint_id = (hint_header >> 32) as u32 & 0x7FFF_FFFF; #[cfg(zisk_hints_metrics)] { + let hint_id = (hint_header >> 32) as u32 & 0x7FFF_FFFF; crate::hints::metrics::inc_hint_count(hint_id); } diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 89bfa8c0c..66cb65f24 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -150,7 +150,10 @@ pub fn init_hints_socket( } pub fn close_hints() -> io::Result<()> { - *MAIN_TID.lock().unwrap() = None; + #[cfg(zisk_hints_single_thread)] + { + *MAIN_TID.lock().unwrap() = None; + } // Write HINT_END HINT_BUFFER.write_hint_end(); From ca8f9d7754793a420753afb9bb58a12d1acc9395 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 17 Feb 2026 14:16:32 +0000 Subject: [PATCH 541/782] Fixing packed dma --- Cargo.lock | 231 ++++++++++++++------------- emulator/src/lib.rs | 4 +- pil/src/pil_helpers/traces.rs | 4 +- precompiles/dma/Cargo.toml | 7 +- precompiles/dma/pil/dma_pre_post.pil | 2 +- precompiles/dma/src/dma_common.rs | 2 +- precompiles/dma/src/dma_planner.rs | 8 - precompiles/dma/src/dma_strategy.rs | 14 +- precompiles/helpers/src/dma.rs | 41 ++--- 9 files changed, 158 insertions(+), 155 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86c0e1736..fe8c2c784 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,7 +186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -199,7 +199,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -260,7 +260,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -332,7 +332,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "synstructure", ] @@ -344,7 +344,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -366,7 +366,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -377,7 +377,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -489,14 +489,14 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] @@ -544,7 +544,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -616,7 +616,7 @@ dependencies = [ "rom-setup", "serde", "serde_json", - "sysinfo 0.38.1", + "sysinfo 0.38.0", "target-lexicon", "tokio", "tracing", @@ -747,9 +747,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.58" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" dependencies = [ "clap_builder", "clap_derive", @@ -757,9 +757,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" dependencies = [ "anstream", "anstyle", @@ -776,7 +776,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1110,13 +1110,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2f51b19a29cb9a19eda391a70a10d2450e126fd4" dependencies = [ "fields", "num-bigint", @@ -1150,7 +1150,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1161,7 +1161,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1242,7 +1242,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1252,7 +1252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1294,7 +1294,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1321,7 +1321,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1368,7 +1368,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1468,7 +1468,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2f51b19a29cb9a19eda391a70a10d2450e126fd4" dependencies = [ "cfg-if", "num-bigint", @@ -1527,9 +1527,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1542,9 +1542,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1552,15 +1552,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1569,38 +1569,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1610,7 +1610,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -2371,9 +2370,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] @@ -2563,18 +2562,18 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags", ] [[package]] name = "objc2-io-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ "libc", "objc2-core-foundation", @@ -2746,7 +2745,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -2773,7 +2772,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2f51b19a29cb9a19eda391a70a10d2450e126fd4" dependencies = [ "colored", "fields", @@ -2807,7 +2806,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -2990,6 +2989,7 @@ dependencies = [ "pil-std-lib", "precompiles-common", "precompiles-helpers", + "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -3111,7 +3111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3135,7 +3135,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2f51b19a29cb9a19eda391a70a10d2450e126fd4" dependencies = [ "bincode", "blake3", @@ -3171,7 +3171,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2f51b19a29cb9a19eda391a70a10d2450e126fd4" dependencies = [ "bincode", "borsh", @@ -3203,7 +3203,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2f51b19a29cb9a19eda391a70a10d2450e126fd4" dependencies = [ "fields", "itoa", @@ -3216,17 +3216,17 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2f51b19a29cb9a19eda391a70a10d2450e126fd4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2f51b19a29cb9a19eda391a70a10d2450e126fd4" dependencies = [ "crossbeam-channel", "tracing", @@ -3235,7 +3235,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2f51b19a29cb9a19eda391a70a10d2450e126fd4" dependencies = [ "bincode", "bytemuck", @@ -3247,7 +3247,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2f51b19a29cb9a19eda391a70a10d2450e126fd4" dependencies = [ "bytemuck", "fields", @@ -3283,7 +3283,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.115", + "syn 2.0.116", "tempfile", ] @@ -3297,7 +3297,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3819,9 +3819,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ "bitflags", "core-foundation", @@ -3832,9 +3832,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", @@ -3898,7 +3898,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4215,9 +4215,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.115" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -4241,7 +4241,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4260,9 +4260,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.38.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5792d209c2eac902426c0c4a166c9f72147db453af548cf9bf3242644c4d4fe3" +checksum = "fe840c5b1afe259a5657392a4dbb74473a14c8db999c3ec2f4ae812e028a94da" dependencies = [ "libc", "memchr", @@ -4295,9 +4295,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tempfile" @@ -4338,7 +4338,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4349,7 +4349,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4463,7 +4463,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4573,9 +4573,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.8+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -4594,9 +4594,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a" +checksum = "7f32a6f80051a4111560201420c7885d0082ba9efe2ab61875c587bb6b18b9a0" dependencies = [ "async-trait", "axum", @@ -4623,21 +4623,21 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27aac809edf60b741e2d7db6367214d078856b8a5bff0087e94ff330fb97b6fc" +checksum = "ce6d8958ed3be404120ca43ffa0fb1e1fc7be214e96c8d33bd43a131b6eebc9e" dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "tonic-prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0" +checksum = "9f86539c0089bfd09b1f8c0ab0239d80392af74c21bc9e0f15e1b4aca4c1647f" dependencies = [ "bytes", "prost", @@ -4646,16 +4646,16 @@ dependencies = [ [[package]] name = "tonic-prost-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4556786613791cfef4ed134aa670b61a85cfcacf71543ef33e8d801abae988f" +checksum = "65873ace111e90344b8973e94a1fc817c924473affff24629281f90daed1cd2e" dependencies = [ "prettyplease", "proc-macro2", "prost-build", "prost-types", "quote", - "syn 2.0.115", + "syn 2.0.116", "tempfile", "tonic-build", ] @@ -4754,7 +4754,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4847,9 +4847,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" @@ -4907,11 +4907,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", @@ -5063,7 +5063,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "wasm-bindgen-shared", ] @@ -5291,7 +5291,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5302,7 +5302,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5661,7 +5661,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.115", + "syn 2.0.116", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -5677,7 +5677,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -5722,7 +5722,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#2f51b19a29cb9a19eda391a70a10d2450e126fd4" dependencies = [ "colored", "fields", @@ -5802,7 +5802,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "synstructure", ] @@ -5823,7 +5823,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5843,7 +5843,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "synstructure", ] @@ -5864,7 +5864,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5897,7 +5897,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5950,9 +5950,14 @@ dependencies = [ "serde", "sha2", "tiny-keccak", + "zisk-definitions", "ziskos-hints", ] +[[package]] +name = "zisk-definitions" +version = "0.16.0" + [[package]] name = "zisk-distributed-common" version = "0.1.0" @@ -6165,12 +6170,13 @@ dependencies = [ "object", "proofman-common", "rayon", + "regex", "riscv", "sm-arith", "sm-binary", "symbolic-common", "symbolic-demangle", - "sysinfo 0.38.1", + "sysinfo 0.38.0", "vergen-git2", "zisk-common", "zisk-core", @@ -6202,6 +6208,7 @@ dependencies = [ "tiny-keccak", "tokio", "zisk-common", + "zisk-definitions", ] [[package]] diff --git a/emulator/src/lib.rs b/emulator/src/lib.rs index f56e79e7e..0a9b33e97 100644 --- a/emulator/src/lib.rs +++ b/emulator/src/lib.rs @@ -12,6 +12,7 @@ //! User configuration -------> EmuOptions / //! ``` +mod disasm; mod elf_symbol_reader; mod emu; mod emu_context; @@ -30,8 +31,8 @@ mod stats_cost_mark; mod stats_costs; pub mod stats_coverage_report; pub mod stats_report; -mod disasm; +pub use disasm::*; pub use elf_symbol_reader::*; pub use emu::*; pub use emu_context::*; @@ -49,5 +50,4 @@ pub use stats::*; pub use stats_cost_mark::*; pub use stats_costs::*; pub use stats_coverage_report::*; -pub use disasm::*; pub use stats_report::*; diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 89c239280..b06ffb5be 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -268,7 +268,7 @@ trace_row!(DmaPrePostFixedRow { pub type DmaPrePostFixed = GenericTrace, 2097152, 0, 9>; trace_row!(DmaPrePostTraceRow { - main_step:ubit(36), dst64:ubit(29), dst_offset:ubit(3), count:ubit(3), is_post:bit, sel_memcpy:bit, sel_memcmp:bit, memcmp_result_nz:bit, l_memcmp_result:u32, sel_inputcpy:bit, sel_memset:bit, selr:[bit; 7], dst_offset_gt_src_offset:bit, src64:ubit(29), src_offset:ubit(3), enabled_second_read:bit, fill_byte:u8, rb:[u8; 16], pb:[u8; 8], sb:[bit; 8], last_dst_byte:u8, abs_diff_dst_src:u8, memcmp_result_is_negative:bit, diff_factor:[u64; 2], bus_write_value:[u32; 2], write_value:[u32; 4], + main_step:ubit(36), dst64:ubit(29), dst_offset:ubit(3), count:ubit(4), is_post:bit, sel_memcpy:bit, sel_memcmp:bit, memcmp_result_nz:bit, l_memcmp_result:u32, sel_inputcpy:bit, sel_memset:bit, selr:[bit; 7], dst_offset_gt_src_offset:bit, src64:ubit(29), src_offset:ubit(3), enabled_second_read:bit, fill_byte:u8, rb:[u8; 16], pb:[u8; 8], sb:[bit; 8], last_dst_byte:u8, abs_diff_dst_src:u8, memcmp_result_is_negative:bit, diff_factor:[u64; 2], bus_write_value:[u32; 2], write_value:[u32; 4], }); pub type DmaPrePostTrace = GenericTrace, 2097152, 0, 9>; @@ -855,7 +855,7 @@ pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ (0, 9, PackedInfoConst { is_packed: true, num_packed_words: 11, - unpack_info: &[36, 29, 3, 3, 1, 1, 1, 1, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 29, 3, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 1, 64, 64, 32, 32, 32, 32, 32, 32], + unpack_info: &[36, 29, 3, 4, 1, 1, 1, 1, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 29, 3, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 1, 64, 64, 32, 32, 32, 32, 32, 32], }), (0, 10, PackedInfoConst { is_packed: true, diff --git a/precompiles/dma/Cargo.toml b/precompiles/dma/Cargo.toml index d36685c30..c4dec9390 100644 --- a/precompiles/dma/Cargo.toml +++ b/precompiles/dma/Cargo.toml @@ -35,8 +35,5 @@ save_dma_collectors = [] save_dma_inputs = [] save_dma_plans = [] debug_dma_gen_mem_inputs = [] -gpu = ["proofman-common/gpu", "packed"] -packed = ["proofman-common/packed"] -no_lib_link = ["proofman-common/no_lib_link"] -diagnostic = ["proofman-macros/diagnostic", "proofman/diagnostic"] -disable_distributed = ["proofman/disable_distributed", "proofman-common/disable_distributed"] +gpu = ["packed"] +packed = [] diff --git a/precompiles/dma/pil/dma_pre_post.pil b/precompiles/dma/pil/dma_pre_post.pil index f9c709ee5..797133b2d 100644 --- a/precompiles/dma/pil/dma_pre_post.pil +++ b/precompiles/dma/pil/dma_pre_post.pil @@ -27,7 +27,7 @@ airtemplate DmaPrePost(int N = 2**21, // Rows of instance col witness bits(MAIN_STEP_BITS) main_step; col witness bits(ADDR_W_BITS) dst64; col witness bits(3) dst_offset; - col witness bits(3) count; + col witness bits(4) count; // bytes // has sel ┌──────────────┐ diff --git a/precompiles/dma/src/dma_common.rs b/precompiles/dma/src/dma_common.rs index 530f7036c..1bafb3b2b 100644 --- a/precompiles/dma/src/dma_common.rs +++ b/precompiles/dma/src/dma_common.rs @@ -27,7 +27,7 @@ pub fn dma_trace(title: &str, rows: usize, num_rows: usize) { if rows == num_rows { tracing::debug!("··· Creating {title} instance [{rows} / {rows} rows filled 100%]"); } else { - tracing::info!( + tracing::debug!( "··· Creating {title} instance [{rows} / {num_rows} rows filled {:.2}%]", rows as f64 / num_rows as f64 * 100.0 ); diff --git a/precompiles/dma/src/dma_planner.rs b/precompiles/dma/src/dma_planner.rs index 1d1ab7b2d..0fa94e1a9 100644 --- a/precompiles/dma/src/dma_planner.rs +++ b/precompiles/dma/src/dma_planner.rs @@ -10,14 +10,6 @@ use fields::PrimeField64; use zisk_common::{BusDeviceMetrics, ChunkId, InstanceType, Plan, Planner, SegmentId}; use zisk_pil::ZISK_AIRGROUP_ID; -#[cfg(feature = "packed")] -use zisk_pil::{ - Dma64AlignedTracePacked as Dma64AlignedTrace, DmaInputCpyTracePacked as DmaInputCpyTrace, - DmaMemCpyTracePacked as DmaMemCpyTrace, DmaMemCpyTracePacked as DmaMemCpyTrace, - DmaPrePostTracePacked as DmaPrePostTrace, DmaTracePacked as DmaTrace, - DmaTraceRowPacked as DmaTraceRow, DmaUnalignedTracePacked as DmaUnalignedTrace, -}; - /// The `DmaPlanner` struct organizes execution plans for arithmetic instances and tables. /// /// It allows adding metadata about instances and tables and generates plans diff --git a/precompiles/dma/src/dma_strategy.rs b/precompiles/dma/src/dma_strategy.rs index d314b2dca..05e51a22a 100644 --- a/precompiles/dma/src/dma_strategy.rs +++ b/precompiles/dma/src/dma_strategy.rs @@ -20,20 +20,24 @@ use crate::get_dma_air_name; use fields::PrimeField64; use zisk_common::{BusDeviceMetrics, BusDeviceMode, CheckPoint, ChunkId}; -use zisk_pil::DmaUnalignedTrace; #[cfg(not(feature = "packed"))] use zisk_pil::{ Dma64AlignedInputCpyTrace, Dma64AlignedMemCpyTrace, Dma64AlignedMemSetTrace, Dma64AlignedMemTrace, Dma64AlignedTrace, DmaInputCpyTrace, DmaMemCpyTrace, - DmaPrePostInputCpyTrace, DmaPrePostMemCpyTrace, DmaPrePostTrace, DmaTrace, + DmaPrePostInputCpyTrace, DmaPrePostMemCpyTrace, DmaPrePostTrace, DmaTrace, DmaUnalignedTrace, }; #[cfg(feature = "packed")] use zisk_pil::{ + Dma64AlignedInputCpyTracePacked as Dma64AlignedInputCpyTrace, + Dma64AlignedMemCpyTracePacked as Dma64AlignedMemCpyTrace, + Dma64AlignedMemSetTracePacked as Dma64AlignedMemSetTrace, + Dma64AlignedMemTracePacked as Dma64AlignedMemTrace, Dma64AlignedTracePacked as Dma64AlignedTrace, DmaInputCpyTracePacked as DmaInputCpyTrace, - DmaMemCpyTracePacked as DmaMemCpyTrace, DmaMemCpyTracePacked as DmaMemCpyTrace, - DmaPrePostTracePacked as DmaPrePostTrace, DmaTracePacked as DmaTrace, - DmaTraceRowPacked as DmaTraceRow, DmaUnalignedTracePacked as DmaUnalignedTrace, + DmaMemCpyTracePacked as DmaMemCpyTrace, + DmaPrePostInputCpyTracePacked as DmaPrePostInputCpyTrace, + DmaPrePostMemCpyTracePacked as DmaPrePostMemCpyTrace, DmaPrePostTracePacked as DmaPrePostTrace, + DmaTracePacked as DmaTrace, DmaUnalignedTracePacked as DmaUnalignedTrace, }; #[derive(Debug, Default, Clone)] diff --git a/precompiles/helpers/src/dma.rs b/precompiles/helpers/src/dma.rs index 957d6a50a..b62480292 100644 --- a/precompiles/helpers/src/dma.rs +++ b/precompiles/helpers/src/dma.rs @@ -88,8 +88,8 @@ impl DmaInfo { (FAST_ENCODE_TABLE[(((dst & 0x07) << 7) + ((src & 0x07) << 4)) as usize + table_count + FAST_ENCODE_TABLE_WO_NEQ_SIZE * (result != 0) as usize] - + Self::DMA_REQUIRES_DMA_TEST_MASK - +((result & 0x1FF) << Self::DMA_FILL_BYTE_RS)) + + Self::DMA_REQUIRES_DMA_TEST_MASK + + ((result & 0x1FF) << Self::DMA_FILL_BYTE_RS)) .wrapping_add((count as u64) << Self::DMA_LPRE_COUNT_RS) } @@ -167,8 +167,8 @@ impl DmaInfo { pub const DMA_REQUIRES_DMA_TEST_MASK: u64 = 0x40000000; pub const DMA_REQUIRES_DMA_MASK: u64 = 0x00000001; - pub const DMA_PRE_OR_POST_TEST_MASK: u64 = Self::DMA_PRE_COUNT_TEST_MASK - | Self::DMA_POST_COUNT_TEST_MASK; + pub const DMA_PRE_OR_POST_TEST_MASK: u64 = + Self::DMA_PRE_COUNT_TEST_MASK | Self::DMA_POST_COUNT_TEST_MASK; pub const DMA_LOOP_COUNT_RS: u64 = 35; const DMA_FULL_ALIGNED_MASK: u64 = Self::DMA_PRE_COUNT_TEST_MASK @@ -182,8 +182,7 @@ impl DmaInfo { | Self::DMA_SRC64_INC_BY_PRE_TEST_MASK | Self::DMA_UNALIGNED_DST_SRC_TEST_MASK; - const DMA_DIRECT_MASK: u64 = Self::DMA_FULL_ALIGNED_MASK - | Self::DMA_REQUIRES_DMA_TEST_MASK; + const DMA_DIRECT_MASK: u64 = Self::DMA_FULL_ALIGNED_MASK | Self::DMA_REQUIRES_DMA_TEST_MASK; #[inline(always)] pub const fn calculate_encode(dst: u64, src: u64, count: usize, neq: bool) -> u64 { @@ -466,11 +465,11 @@ impl DmaInfo { } #[inline(always)] pub const fn is_direct(encoded: u64) -> bool { - (Self::DMA_DIRECT_MASK & encoded) == 0 && Self::get_loop_count(encoded) > 0 + (Self::DMA_DIRECT_MASK & encoded) == 0 && Self::get_loop_count(encoded) > 0 } #[inline(always)] pub const fn get_fill_byte(encoded: u64) -> u8 { - (encoded >> Self::DMA_FILL_BYTE_RS) as u8 + (encoded >> Self::DMA_FILL_BYTE_RS) as u8 } #[inline(always)] pub const fn is_memcmp_negative(encoded: u64) -> bool { @@ -483,26 +482,29 @@ impl DmaInfo { (encoded >> Self::DMA_FILL_BYTE_RS) | !Self::DMA_FILL_BYTE_MASK } else { (encoded >> Self::DMA_FILL_BYTE_RS) & Self::DMA_FILL_BYTE_MASK - } } + } + } #[inline(always)] pub const fn get_memcmp_pre_result_nz(encoded: u64) -> bool { - (encoded & Self::DMA_FILL_BYTE_TEST_MASK) != 0 && (encoded & Self::DMA_POST_COUNT_TEST_MASK) == 0 - && (encoded & Self::DMA_PRE_COUNT_TEST_MASK) != 0 + (encoded & Self::DMA_FILL_BYTE_TEST_MASK) != 0 + && (encoded & Self::DMA_POST_COUNT_TEST_MASK) == 0 + && (encoded & Self::DMA_PRE_COUNT_TEST_MASK) != 0 } #[inline(always)] pub const fn get_memcmp_post_result_nz(encoded: u64) -> bool { - (encoded & Self::DMA_FILL_BYTE_TEST_MASK) != 0 && (encoded & Self::DMA_POST_COUNT_TEST_MASK) != 0 + (encoded & Self::DMA_FILL_BYTE_TEST_MASK) != 0 + && (encoded & Self::DMA_POST_COUNT_TEST_MASK) != 0 } #[inline(always)] pub const fn get_memcmp_result_nz(encoded: u64) -> bool { - (encoded & Self::DMA_FILL_BYTE_TEST_MASK) != 0 + (encoded & Self::DMA_FILL_BYTE_TEST_MASK) != 0 } #[inline(always)] pub const fn has_pre_or_post(encoded: u64) -> bool { - (encoded & Self::DMA_PRE_OR_POST_TEST_MASK) != 0 - } + (encoded & Self::DMA_PRE_OR_POST_TEST_MASK) != 0 + } } impl DmaHelpers { @@ -814,7 +816,7 @@ mod tests { table[i * 4 + 3], i * 4, i * 4 + 3, - (i * 4) & 0xF, + (i * 4) & 0xF, if i >= 256 { " neq" } else { "" } ); } @@ -855,14 +857,15 @@ mod tests { for dst in 0..256 { for src in 0..256 { for count in 0..256 { - let encode = DmaInfo::calculate_encode2(dst, src, count, neq) | DmaInfo::DMA_REQUIRES_DMA_MASK; + let encode = DmaInfo::calculate_encode2(dst, src, count, neq) + | DmaInfo::DMA_REQUIRES_DMA_MASK; let fast_encode = DmaInfo::encode_memcmp_neq(dst, src, count, neq); assert_eq!( encode, fast_encode, "testing NEQ with memcmp dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}" - ); - assert_eq!(count, DmaInfo::get_count(encode), "testing NEQ with memcmp dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}"); + ); + assert_eq!(count, DmaInfo::get_count(encode), "testing NEQ with memcmp dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}"); } } } From 67ac00c9e9e7dda0b66746b95cd4453632f53370 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Tue, 17 Feb 2026 15:05:24 +0000 Subject: [PATCH 542/782] Add wait_for_client. Refactor hint initialization --- ziskos/entrypoint/src/hints/mod.rs | 93 ++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 66cb65f24..44520b1cd 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -13,11 +13,13 @@ mod sha256f; mod metrics; use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer}; -use anyhow::Result; +use anyhow::{anyhow, Result}; use once_cell::sync::Lazy; +use std::any; use std::cell::UnsafeCell; use std::path::PathBuf; use std::thread::{self, JoinHandle}; +use std::time::{Duration, Instant}; use std::{ffi::CStr, os::raw::c_char}; use std::{ io::{self, BufWriter, Write}, @@ -41,8 +43,8 @@ pub use secp256k1::*; pub use secp256r1::*; pub use sha256f::*; -pub const HINT_START: u32 = 0; -pub const HINT_END: u32 = 1; +pub const CLIENT_CONNECT_TIMEOUT: Duration = Duration::from_secs(3); +pub const WAIT_FOR_CLIENT_RETRY_DELAY: Duration = Duration::from_millis(5); static HINT_BUFFER: Lazy> = Lazy::new(|| build_hint_buffer()); static HINT_WRITER_HANDLE: Lazy = @@ -71,32 +73,36 @@ impl HintFileWriterHandleCell { } } -pub fn init_hints() -> io::Result<()> { - // Initialize the main thread ID for single-threaded assert (if enabled) - #[cfg(zisk_hints_single_thread)] - { - let tid = std::thread::current().id(); - *MAIN_TID.lock().unwrap() = Some(tid); - println!("Initializing MAIN_TID to {:?}", tid); - } - +pub fn wait_for_hints_writer() -> Result<()> { if let Some(handle) = HINT_WRITER_HANDLE.take() { HINT_BUFFER.close(); match handle.join() { Ok(result) => { if let Err(err) = result { - return Err(err); + return Err(anyhow!( + "Failed previous hints writer thread result, error: {}", err + )); } } Err(e) => { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("Failed hints writer thread, error: {:?}", e), - )) + return Err(anyhow!( + "Failed previous hints writer thread, error: {:?}", e + )); } } } + Ok(()) +} +pub fn init_hints() { + // Initialize the main thread ID for single-threaded assert (if enabled) + #[cfg(zisk_hints_single_thread)] + { + let tid = std::thread::current().id(); + *MAIN_TID.lock().unwrap() = Some(tid); + println!("Initializing MAIN_TID to {:?}", tid); + } + #[cfg(zisk_hints_metrics)] crate::hints::metrics::reset_metrics(); @@ -104,20 +110,20 @@ pub fn init_hints() -> io::Result<()> { // Write HINT_START HINT_BUFFER.write_hint_start(); - - Ok(()) } pub fn init_hints_file( hints_file_path: PathBuf, ready: Option>, -) -> io::Result<()> { - init_hints()?; +) -> Result<()> { + wait_for_hints_writer()?; if let Some(tx) = ready { let _ = tx.send(()); } + init_hints(); + let handle = thread::spawn(move || write_hints_to_file(hints_file_path)); HINT_WRITER_HANDLE.store(handle); @@ -128,20 +134,28 @@ pub fn init_hints_socket( socket_path: PathBuf, debug_file: Option, ready: Option>, -) -> io::Result<()> { - init_hints()?; +) -> Result<()> { + wait_for_hints_writer()?; // Create the Unix socket writer (server) - let mut socket_writer = UnixSocketWriter::new(&socket_path).map_err(io::Error::other)?; + let mut socket_writer = UnixSocketWriter::new(&socket_path)?; + + // Open the connection + socket_writer.open()?; // Notify that socket is ready after client connects if let Some(tx) = ready { let _ = tx.send(()); } - // Open the connection (waits for client to connect) - // TODO: Implement open timeout - socket_writer.open().map_err(io::Error::other)?; + // Wait for client to connect with a timeout + if let Err(e) = socket_writer.wait_for_client(CLIENT_CONNECT_TIMEOUT) { + return Err(anyhow!( + "Failed to wait for client to connect to hints socket, error: {}", e + )); + } + + init_hints(); let handle = thread::spawn(move || write_hints_to_socket(socket_writer, debug_file)); HINT_WRITER_HANDLE.store(handle); @@ -149,7 +163,7 @@ pub fn init_hints_socket( Ok(()) } -pub fn close_hints() -> io::Result<()> { +pub fn close_hints() -> Result<()> { #[cfg(zisk_hints_single_thread)] { *MAIN_TID.lock().unwrap() = None; @@ -167,11 +181,12 @@ pub fn close_hints() -> io::Result<()> { match handle.join() { Ok(result) => match result { Ok(()) => Ok(()), - Err(e) => return Err(e), + Err(e) => return Err(anyhow!( + "Failed hints writer thread result, error: {}", e + )), }, - Err(e) => Err(io::Error::new( - io::ErrorKind::Other, - format!("Failed hints writer thread, error: {:?}", e), + Err(e) => Err(anyhow!( + "Failed hints writer thread, error: {:?}", e )), } } else { @@ -214,6 +229,20 @@ impl UnixSocketWriter { self.inner.open() } + pub fn wait_for_client(&mut self, timeout: Duration) -> Result<()> { + let start = Instant::now(); + while !self.inner.is_client_connected() { + if start.elapsed() >= timeout { + return Err(anyhow!( + "Timeout waiting for client to connect to socket" + )); + } + thread::sleep(WAIT_FOR_CLIENT_RETRY_DELAY); + } + + Ok(()) + } + pub fn close(&mut self) -> Result<()> { self.inner.close() } From a921bf9204a5dd06894788d50bae18be54659f94 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 17:05:56 +0100 Subject: [PATCH 543/782] Redirect output to file in main.c --- emulator-asm/src/main.c | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 68a373958..ded7e1300 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -110,6 +110,25 @@ typedef enum { } GenMethod; GenMethod gen_method = Fast; +const char * gen_method_achronym(GenMethod method) +{ + switch (method) + { + case Fast: return "FT"; + case MinimalTrace: return "MT"; + case RomHistogram: return "RH"; + case MainTrace: return "MA"; + case ChunksOnly: return "CO"; + //case BusOp: return "bus-op"; + case Zip: return "ZP"; + case MemOp: return "MO"; + case ChunkPlayerMTCollectMem: return "CPM"; + case MemReads: return "MR"; + case ChunkPlayerMemReadsCollectMain: return "CPMCM"; + default: return "?"; + } +} + // Service TCP parameters #define SERVER_IP "127.0.0.1" // Change to your server IP uint16_t port = 0; @@ -582,6 +601,8 @@ void trace_map_initialize (void) pOutputTrace = (uint64_t *)TRACE_ADDR; } +bool redirect_output_to_file = true; + int main(int argc, char *argv[]) { #ifdef DEBUG @@ -606,6 +627,19 @@ int main(int argc, char *argv[]) // Parse arguments parse_arguments(argc, argv); + // Redirect output to file if requested + if (redirect_output_to_file) + { + char redirect_output_file[256]; + snprintf(redirect_output_file, sizeof(redirect_output_file), "/tmp/%s_%s_output.txt", shm_prefix, gen_method_achronym(gen_method)); + + // Redirect stdout to file + freopen(redirect_output_file, "w", stdout); + + // Redirect stderr to the same file + freopen(redirect_output_file, "a", stderr); + } + // Configure based on parguments configure(); @@ -4141,7 +4175,7 @@ void server_run (void) uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; - printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu\n", + printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", duration, realloc_counter, wait_counter, @@ -4156,7 +4190,10 @@ void server_run (void) end, error, max_steps, - chunk_size); + chunk_size, + precompile_written_address ? *precompile_written_address : 0, + precompile_read_address ? *precompile_read_address : 0 + ); if (gen_method == RomHistogram) { printf("Rom histogram size=%lu\n", histogram_size); From 66c32b71f8d5ab4e1bb047b8afa22088e3a8e861 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 17:19:31 +0100 Subject: [PATCH 544/782] Enable metrics in main.c --- emulator-asm/src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index ded7e1300..a0a770a43 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -303,7 +303,7 @@ void file_lock(void); bool output = false; bool output_riscof = false; bool silent = false; -bool metrics = false; +bool metrics = true; bool trace = false; bool trace_trace = false; bool verbose = false; From eca95ebca09a3dc643e4e0ef79060ef47c588645 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 18:32:34 +0100 Subject: [PATCH 545/782] Flush Duration trace in main.c --- emulator-asm/src/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index a0a770a43..3ffaad6b9 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4194,6 +4194,8 @@ void server_run (void) precompile_written_address ? *precompile_written_address : 0, precompile_read_address ? *precompile_read_address : 0 ); + fflush(stdout); + fflush(stderr); if (gen_method == RomHistogram) { printf("Rom histogram size=%lu\n", histogram_size); From 030684e1fd835fe0d384a90057a14aaae5420b9d Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 20:18:52 +0100 Subject: [PATCH 546/782] Enable verbose in main.c --- emulator-asm/src/main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 3ffaad6b9..1f50b70c6 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -306,7 +306,7 @@ bool silent = false; bool metrics = true; bool trace = false; bool trace_trace = false; -bool verbose = false; +bool verbose = true; bool save_to_file = false; bool share_input_shm = false; // Shares input shared memories: input, precompile results and control input, using a common name bool open_input_shm = false; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) @@ -4152,8 +4152,9 @@ void server_run (void) // Call emulator assembly code gettimeofday(&start_time,NULL); - if (verbose) printf("trace_address=%lx\n", trace_address); + if (verbose) printf("Before calling emulator_start() trace_address=%lx\n", trace_address); emulator_start(); + if (verbose) printf("After calling emulator_start() trace_address=%lx\n", trace_address); gettimeofday(&stop_time,NULL); assembly_duration = TimeDiff(start_time, stop_time); From 2ae8fa1df35ad4a482afb36541e4107993084409 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 22:26:16 +0100 Subject: [PATCH 547/782] Add flush() to some verbose traces in main.c --- emulator-asm/src/main.c | 62 +++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 1f50b70c6..689f28a03 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -761,20 +761,34 @@ int main(int argc, char *argv[]) if (bytes_read < 0) { printf("%s ERROR: Failed calling recv() bytes_read=%ld errno=%d=%s\n", log_name, bytes_read, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); break; } if (bytes_read != sizeof(request)) { if ((errno != 0) && (errno != 2)) { - printf("%s ERROR: Failed calling recv() invalid bytes_read=%ld errno=%d=%s\n", log_name, bytes_read, errno, strerror(errno)); + printf("%s WARNING: Failed calling recv() invalid bytes_read=%ld errno=%d=%s\n", log_name, bytes_read, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); } break; } #ifdef DEBUG - if (verbose) printf("%s recv() returned: %ld\n", log_name, bytes_read); + if (verbose) + { + printf("%s recv() returned: %ld\n", log_name, bytes_read); + fflush(stdout); + fflush(stderr); + } #endif - if (verbose) printf("%s recv()'d request=[%lu, 0x%lx, 0x%lx, 0x%lx, 0x%lx]\n", log_name, request[0], request[1], request[2], request[3], request[4]); + if (verbose) + { + printf("%s recv()'d request=[%lu, 0x%lx, 0x%lx, 0x%lx, 0x%lx]\n", log_name, request[0], request[1], request[2], request[3], request[4]); + fflush(stdout); + fflush(stderr); + } uint64_t response[5]; bReset = false; @@ -1074,17 +1088,29 @@ int main(int argc, char *argv[]) } } - if (verbose) printf("%s send()'ing response=[%lu, 0x%lx, 0x%lx, 0x%lx, 0x%lx]\n", log_name, response[0], response[1], response[2], response[3], response[4]); + if (verbose) + { + printf("%s send()'ing response=[%lu, 0x%lx, 0x%lx, 0x%lx, 0x%lx]\n", log_name, response[0], response[1], response[2], response[3], response[4]); + fflush(stdout); + fflush(stderr); + } ssize_t bytes_sent = send(client_fd, response, sizeof(response), MSG_WAITALL); if (bytes_sent != sizeof(response)) { printf("%s ERROR: Failed calling send() invalid bytes_sent=%ld errno=%d=%s\n", log_name, bytes_sent, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); break; } -#ifdef DEBUG - else if (verbose) printf("Response sent to client\n"); -#endif +//#ifdef DEBUG + else if (verbose) + { + printf("Response sent to client\n"); + fflush(stdout); + fflush(stderr); + } +//#endif if (bReset) { server_reset_slow(); @@ -4200,11 +4226,15 @@ void server_run (void) if (gen_method == RomHistogram) { printf("Rom histogram size=%lu\n", histogram_size); + fflush(stdout); + fflush(stderr); } } if (MEM_ERROR) { printf("Emulation ended with error code %lu\n", MEM_ERROR); + fflush(stdout); + fflush(stderr); } // Log output @@ -4213,7 +4243,12 @@ void server_run (void) unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; unsigned int output_size = 64; #ifdef DEBUG - if (verbose) printf("Output size=%d\n", output_size); + if (verbose) + { + printf("Output size=%d\n", output_size); + fflush(stdout); + fflush(stderr); + } #endif for (unsigned int i = 0; i < output_size; i++) @@ -4221,6 +4256,8 @@ void server_run (void) printf("%08x\n", *pOutput); pOutput++; } + fflush(stdout); + fflush(stderr); } // Log output for riscof tests @@ -4229,7 +4266,12 @@ void server_run (void) unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; unsigned int output_size = *pOutput; #ifdef DEBUG - if (verbose) printf("Output size=%d\n", output_size); + if (verbose) + { + printf("Output size=%d\n", output_size); + fflush(stdout); + fflush(stderr); + } #endif for (unsigned int i = 0; i < output_size; i++) @@ -4237,6 +4279,8 @@ void server_run (void) pOutput++; printf("%08x\n", *pOutput); } + fflush(stdout); + fflush(stderr); } // Complete output header data From 53dd436e9fe1ec31b05fb51813595bacba380db7 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 23:01:26 +0100 Subject: [PATCH 548/782] Add flush to Waiting... trace in main.c --- emulator-asm/src/main.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 689f28a03..868d691fc 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -728,7 +728,12 @@ int main(int argc, char *argv[]) struct sockaddr_in address; int addrlen = sizeof(address); int client_fd; - if (!silent) printf("%s Waiting for incoming connections to port %u...\n", log_name, port); + if (!silent) + { + printf("%s Waiting for incoming connections to port %u...\n", log_name, port); + fflush(stdout); + fflush(stderr); + } client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen); if (client_fd < 0) { From 7c3d11c8aa76cbc02e81e6e423c1d83b7087ccd8 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:32:27 +0000 Subject: [PATCH 549/782] added traces while debugging --- distributed/crates/worker/src/worker.rs | 8 ++++---- emulator-asm/asm-runner/src/asm_services/services.rs | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 76150eff8..68e62092b 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -662,10 +662,10 @@ impl Worker { println!("Stream END received for job {}, cleaned up buffer", job_id); return Ok(()); } - println!( - "Received stream data for job {}, stream type {:?}, processing...", - job_id, stream_type - ); + // println!( + // "Received stream data for job {}, stream type {:?}, processing...", + // job_id, stream_type + // ); let element = self.stream_buffers.get_mut(&job_id).ok_or_else(|| { anyhow::anyhow!( "Received stream data without START for job {} stream type {:?}", diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index cf0802d61..7e07cb0f3 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -294,16 +294,21 @@ impl AsmServices { out_buffer.extend_from_slice(&word.to_le_bytes()); } + tracing::info!("Sending request to service {} on {}: {:?}", service, addr, request); let mut stream = TcpStream::connect(&addr).with_context(|| format!("Failed to connect to {addr}"))?; + tracing::info!("Connected to service {} on {}", service, addr); // Set a read timeout to avoid indefinite blocking stream .set_read_timeout(Some(Duration::from_secs(10))) .context("Failed to set read timeout")?; + tracing::info!("Sending request payload to service {} on {}", service, addr); + // Send request payload if let Err(e) = stream.write_all(&out_buffer) { + tracing::error!("Failed to write request payload to service {} on {}: {}", service, addr, e); return Err(anyhow::anyhow!( "Failed to write request payload to service {} on {}: {}", service, @@ -312,6 +317,8 @@ impl AsmServices { )); } + tracing::info!("Request payload sent to service {} on {}", service, addr); + let total_timeout = Duration::from_secs(120); let start = Instant::now(); @@ -332,6 +339,7 @@ impl AsmServices { continue; } Err(e) => { + tracing::error!("Failed to read response payload from service {} on {}: {}", service, addr, e); return Err(e.into()); } } @@ -343,6 +351,8 @@ impl AsmServices { response[i] = u64::from_le_bytes(chunk.try_into()?); } + tracing::info!("Decoded response from service {} on {}: {:?}", service, addr, response); + Ok(Res::from_response_payload(response)) } From 720732824780dc5492c912f15ddb137ab809cad0 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 18 Feb 2026 10:49:15 +0100 Subject: [PATCH 550/782] Flush traces before and after calling emulator_start() in main.c --- emulator-asm/src/main.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 868d691fc..ee87729a1 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4154,7 +4154,8 @@ void server_run (void) server_reset_trace(); // Sync input shared memory - if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) { + if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) + { printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); @@ -4183,9 +4184,19 @@ void server_run (void) // Call emulator assembly code gettimeofday(&start_time,NULL); - if (verbose) printf("Before calling emulator_start() trace_address=%lx\n", trace_address); + if (verbose) + { + printf("Before calling emulator_start() trace_address=%lx\n", trace_address); + fflush(stdout); + fflush(stderr); + } emulator_start(); - if (verbose) printf("After calling emulator_start() trace_address=%lx\n", trace_address); + if (verbose) + { + printf("After calling emulator_start() trace_address=%lx\n", trace_address); + fflush(stdout); + fflush(stderr); + } gettimeofday(&stop_time,NULL); assembly_duration = TimeDiff(start_time, stop_time); From e2e0eb317220e2025488905810c952768a4408e5 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 18 Feb 2026 11:00:56 +0100 Subject: [PATCH 551/782] Get freopen() result in main.c --- emulator-asm/src/main.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index ee87729a1..481277961 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -634,12 +634,25 @@ int main(int argc, char *argv[]) snprintf(redirect_output_file, sizeof(redirect_output_file), "/tmp/%s_%s_output.txt", shm_prefix, gen_method_achronym(gen_method)); // Redirect stdout to file - freopen(redirect_output_file, "w", stdout); + FILE * file_pointer = freopen(redirect_output_file, "w", stdout); + if (file_pointer == NULL) + { + printf("ERROR: Failed to redirect stdout to file %s\n", redirect_output_file); + fflush(stdout); + fflush(stderr); + exit(-1); + } // Redirect stderr to the same file - freopen(redirect_output_file, "a", stderr); + file_pointer = freopen(redirect_output_file, "a", stderr); + if (file_pointer == NULL) + { + printf("ERROR: Failed to redirect stderr to file %s\n", redirect_output_file); + fflush(stdout); + fflush(stderr); + exit(-1); + } } - // Configure based on parguments configure(); From c5270232c93c5bceac78b09c4abc2e314e23b7a9 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 18 Feb 2026 13:37:36 +0100 Subject: [PATCH 552/782] Disable traces in main.c --- emulator-asm/src/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 481277961..ae5d6b591 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -303,10 +303,10 @@ void file_lock(void); bool output = false; bool output_riscof = false; bool silent = false; -bool metrics = true; +bool metrics = false; bool trace = false; bool trace_trace = false; -bool verbose = true; +bool verbose = false; bool save_to_file = false; bool share_input_shm = false; // Shares input shared memories: input, precompile results and control input, using a common name bool open_input_shm = false; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) @@ -601,7 +601,7 @@ void trace_map_initialize (void) pOutputTrace = (uint64_t *)TRACE_ADDR; } -bool redirect_output_to_file = true; +bool redirect_output_to_file = false; int main(int argc, char *argv[]) { From 97bcd69690264e5b28642bfd39f283b4c7e08cef Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:59:09 +0000 Subject: [PATCH 553/782] adding fence in hints_shmem.rs to fix when working with hints --- distributed/crates/worker/src/worker.rs | 10 +--------- emulator-asm/asm-runner/src/asm_services/services.rs | 10 ---------- emulator-asm/asm-runner/src/hints_shmem.rs | 12 +++++++++++- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 68e62092b..8fce81e80 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -641,10 +641,6 @@ impl Worker { hints_processor.reset(); } - println!( - "Stream START received for job {}, initialized buffer and reset hints processor", - job_id - ); return Ok(()); } else if stream_type == StreamMessageKind::End { // Ensure buffer exists @@ -659,13 +655,9 @@ impl Worker { // Clean up the stream buffer for this job self.stream_buffers.remove(&job_id); - println!("Stream END received for job {}, cleaned up buffer", job_id); return Ok(()); } - // println!( - // "Received stream data for job {}, stream type {:?}, processing...", - // job_id, stream_type - // ); + let element = self.stream_buffers.get_mut(&job_id).ok_or_else(|| { anyhow::anyhow!( "Received stream data without START for job {} stream type {:?}", diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 7e07cb0f3..cf0802d61 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -294,21 +294,16 @@ impl AsmServices { out_buffer.extend_from_slice(&word.to_le_bytes()); } - tracing::info!("Sending request to service {} on {}: {:?}", service, addr, request); let mut stream = TcpStream::connect(&addr).with_context(|| format!("Failed to connect to {addr}"))?; - tracing::info!("Connected to service {} on {}", service, addr); // Set a read timeout to avoid indefinite blocking stream .set_read_timeout(Some(Duration::from_secs(10))) .context("Failed to set read timeout")?; - tracing::info!("Sending request payload to service {} on {}", service, addr); - // Send request payload if let Err(e) = stream.write_all(&out_buffer) { - tracing::error!("Failed to write request payload to service {} on {}: {}", service, addr, e); return Err(anyhow::anyhow!( "Failed to write request payload to service {} on {}: {}", service, @@ -317,8 +312,6 @@ impl AsmServices { )); } - tracing::info!("Request payload sent to service {} on {}", service, addr); - let total_timeout = Duration::from_secs(120); let start = Instant::now(); @@ -339,7 +332,6 @@ impl AsmServices { continue; } Err(e) => { - tracing::error!("Failed to read response payload from service {} on {}: {}", service, addr, e); return Err(e.into()); } } @@ -351,8 +343,6 @@ impl AsmServices { response[i] = u64::from_le_bytes(chunk.try_into()?); } - tracing::info!("Decoded response from service {} on {}: {:?}", service, addr, response); - Ok(Res::from_response_payload(response)) } diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index cdfb1a1f2..a114f698d 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -9,7 +9,10 @@ use crate::{ }; use anyhow::Result; use named_sem::NamedSemaphore; -use std::{cell::RefCell, sync::atomic::AtomicBool}; +use std::{ + cell::RefCell, + sync::atomic::{fence, AtomicBool, Ordering}, +}; use tracing::debug; use zisk_common::io::StreamSink; @@ -215,6 +218,9 @@ impl StreamSink for HintsShmem { // Flow control: wait until all consumers have advanced enough // We need to wait for the slowest consumer (minimum read position) loop { + // Ensure we observe the latest read positions + fence(Ordering::Acquire); + // Find the slowest consumer (minimum read position) and its index let (slowest_idx, min_read_pos) = separate .iter() @@ -254,9 +260,13 @@ impl StreamSink for HintsShmem { // Write data ONCE to the unified shared memory buffer unified.data_writer.write_ring_buffer(&processed)?; + fence(Ordering::Release); + // Update write position ONCE in control memory unified.control_writer.write_u64_at(0, write_pos + data_size); + fence(Ordering::Release); + // Notify ALL consumers that new data is available for res in separate.iter_mut() { res.sem_available.post()?; From 83c8849a8718a6109c1203367b9baed774dd33e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Wed, 18 Feb 2026 14:32:57 +0100 Subject: [PATCH 554/782] Feature/plonk gpu (#795) * plonk gpu working * Remove verbose from example * Update ZiskVerifier * Cargo update --- Cargo.lock | 34 ++-- cli/src/commands/prove_snark.rs | 7 +- examples/Cargo.lock | 159 +++++++++--------- examples/sha-hasher/host/bin/compressed.rs | 2 +- examples/sha-hasher/host/bin/execute.rs | 2 +- examples/sha-hasher/host/bin/plonk.rs | 4 +- examples/sha-hasher/host/bin/prove.rs | 4 +- .../sha-hasher/host/bin/verify-constraints.rs | 2 +- examples/sha-hasher/host/bin/ziskemu.rs | 2 +- examples/sha-hasher/host/src/main.rs | 2 +- sdk/src/prover/asm.rs | 9 +- sdk/src/prover/backend.rs | 13 +- sdk/src/prover/emu.rs | 9 +- zisk-contracts/ZiskVerifier.sol | 2 +- 14 files changed, 133 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06e5500e9..95417c9ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -746,9 +746,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.58" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" dependencies = [ "clap_builder", "clap_derive", @@ -756,9 +756,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" dependencies = [ "anstream", "anstyle", @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "fields", "num-bigint", @@ -1467,7 +1467,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "cfg-if", "num-bigint", @@ -2771,7 +2771,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "colored", "fields", @@ -3133,7 +3133,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "bincode", "blake3", @@ -3169,7 +3169,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "bincode", "borsh", @@ -3201,7 +3201,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "fields", "itoa", @@ -3214,7 +3214,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "proc-macro2", "quote", @@ -3224,7 +3224,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "crossbeam-channel", "tracing", @@ -3233,7 +3233,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "bincode", "bytemuck", @@ -3245,7 +3245,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "bytemuck", "fields", @@ -4571,9 +4571,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.8+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -5720,7 +5720,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#8f129b767a75551348d1492bac5e32f05186a66b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "colored", "fields", diff --git a/cli/src/commands/prove_snark.rs b/cli/src/commands/prove_snark.rs index 1ed1a33ef..15862f9cb 100644 --- a/cli/src/commands/prove_snark.rs +++ b/cli/src/commands/prove_snark.rs @@ -55,11 +55,8 @@ impl ZiskProveSnark { let proof = zisk_proof.get_vadcop_final_proof()?; - let snark_proof = snark_wrapper.generate_final_snark_proof( - &proof, - Some(self.output_dir.clone()), - None, - )?; + let snark_proof = + snark_wrapper.generate_final_snark_proof(&proof, Some(self.output_dir.clone()))?; snark_proof.save(self.output_dir.join("final_snark_proof.bin")).map_err(|e| { anyhow::anyhow!( "Failed to save final SNARK proof to output dir {}: {}", diff --git a/examples/Cargo.lock b/examples/Cargo.lock index e9709ef98..759fdb9ff 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -171,7 +171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -184,7 +184,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -245,7 +245,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -310,7 +310,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "synstructure", ] @@ -322,7 +322,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -385,14 +385,14 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "blake3" @@ -437,7 +437,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -577,9 +577,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.58" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" dependencies = [ "clap_builder", "clap_derive", @@ -587,9 +587,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" dependencies = [ "anstream", "anstyle", @@ -606,7 +606,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -807,13 +807,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "fields", "num-bigint", @@ -847,7 +847,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -858,7 +858,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -925,7 +925,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -935,7 +935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -956,7 +956,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -974,7 +974,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1006,7 +1006,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1095,7 +1095,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "cfg-if", "num-bigint", @@ -1148,15 +1148,15 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "generic-array" @@ -1517,6 +1517,10 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" name = "lib-c" version = "0.16.0" +[[package]] +name = "lib-float" +version = "0.16.0" + [[package]] name = "libc" version = "0.2.182" @@ -1663,9 +1667,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] @@ -1837,18 +1841,18 @@ dependencies = [ [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags", ] [[package]] name = "objc2-io-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ "libc", "objc2-core-foundation", @@ -1955,7 +1959,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "colored", "fields", @@ -2232,7 +2236,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -2256,7 +2260,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "bincode", "blake3", @@ -2292,7 +2296,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "bincode", "borsh", @@ -2324,7 +2328,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "fields", "itoa", @@ -2337,17 +2341,17 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "crossbeam-channel", "tracing", @@ -2356,7 +2360,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "bincode", "bytemuck", @@ -2368,7 +2372,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "bytemuck", "fields", @@ -2791,9 +2795,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ "bitflags", "core-foundation", @@ -2804,9 +2808,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", @@ -2858,7 +2862,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3152,9 +3156,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.115" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -3169,7 +3173,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3188,9 +3192,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.38.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5792d209c2eac902426c0c4a166c9f72147db453af548cf9bf3242644c4d4fe3" +checksum = "fe840c5b1afe259a5657392a4dbb74473a14c8db999c3ec2f4ae812e028a94da" dependencies = [ "libc", "memchr", @@ -3260,7 +3264,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3271,7 +3275,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3375,7 +3379,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3449,9 +3453,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.8+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -3507,7 +3511,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3576,9 +3580,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" @@ -3624,11 +3628,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "wasm-bindgen", ] @@ -3756,7 +3760,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "wasm-bindgen-shared", ] @@ -3952,7 +3956,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3963,7 +3967,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4313,7 +4317,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.115", + "syn 2.0.116", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4329,7 +4333,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4374,7 +4378,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ed83276f2d22797b95289c11003828b0ccad826a" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" dependencies = [ "colored", "fields", @@ -4443,7 +4447,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "synstructure", ] @@ -4464,7 +4468,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4484,7 +4488,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "synstructure", ] @@ -4505,7 +4509,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4538,7 +4542,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4584,6 +4588,7 @@ dependencies = [ "elf", "fields", "lib-c", + "lib-float", "paste", "precompiles-helpers", "rayon", @@ -4716,7 +4721,7 @@ dependencies = [ "sm-binary", "symbolic-common", "symbolic-demangle", - "sysinfo 0.38.1", + "sysinfo 0.38.0", "vergen-git2", "zisk-common", "zisk-core", diff --git a/examples/sha-hasher/host/bin/compressed.rs b/examples/sha-hasher/host/bin/compressed.rs index d155284b4..d999ae8b4 100644 --- a/examples/sha-hasher/host/bin/compressed.rs +++ b/examples/sha-hasher/host/bin/compressed.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use zisk_sdk::{ZiskStdin, ZiskIO, ElfBinary, ProofOpts, ProverClient, include_elf}; +use zisk_sdk::{ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin, include_elf}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/execute.rs b/examples/sha-hasher/host/bin/execute.rs index 7a353566a..64eb7c594 100644 --- a/examples/sha-hasher/host/bin/execute.rs +++ b/examples/sha-hasher/host/bin/execute.rs @@ -1,6 +1,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -use zisk_sdk::{ZiskStdin, ZiskIO, ElfBinary, ProverClient, include_elf}; +use zisk_sdk::{ElfBinary, ProverClient, ZiskIO, ZiskStdin, include_elf}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/plonk.rs b/examples/sha-hasher/host/bin/plonk.rs index 8fb100e93..1fd3bad84 100644 --- a/examples/sha-hasher/host/bin/plonk.rs +++ b/examples/sha-hasher/host/bin/plonk.rs @@ -1,5 +1,7 @@ use anyhow::Result; -use zisk_sdk::{ZiskStdin, ZiskIO, ElfBinary, ProverClient, ZiskProofWithPublicValues, include_elf}; +use zisk_sdk::{ + ElfBinary, ProverClient, ZiskIO, ZiskProofWithPublicValues, ZiskStdin, include_elf, +}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/prove.rs b/examples/sha-hasher/host/bin/prove.rs index 96883e8da..c19cff898 100644 --- a/examples/sha-hasher/host/bin/prove.rs +++ b/examples/sha-hasher/host/bin/prove.rs @@ -2,8 +2,8 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use zisk_sdk::{ - ZiskStdin, ZiskIO, ElfBinary, - ProofOpts, ProverClient, ZiskProof, ZiskProofWithPublicValues, ZiskPublics, include_elf, + ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskProof, ZiskProofWithPublicValues, ZiskPublics, + ZiskStdin, include_elf, }; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/verify-constraints.rs b/examples/sha-hasher/host/bin/verify-constraints.rs index 53a8c003b..9ad65fb3b 100644 --- a/examples/sha-hasher/host/bin/verify-constraints.rs +++ b/examples/sha-hasher/host/bin/verify-constraints.rs @@ -1,7 +1,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use zisk_sdk::{ProverClient, elf_path, ZiskStdin, ZiskIO, ElfBinaryFromFile}; +use zisk_sdk::{ElfBinaryFromFile, ProverClient, ZiskIO, ZiskStdin, elf_path}; #[derive(Serialize, Deserialize, Debug)] struct Output { diff --git a/examples/sha-hasher/host/bin/ziskemu.rs b/examples/sha-hasher/host/bin/ziskemu.rs index 80a1c1c1f..2eb396214 100644 --- a/examples/sha-hasher/host/bin/ziskemu.rs +++ b/examples/sha-hasher/host/bin/ziskemu.rs @@ -1,6 +1,6 @@ use anyhow::Result; use std::path::PathBuf; -use zisk_sdk::{EmuOptions, elf_path, ziskemu, ZiskStdin, ZiskIO, ElfBinaryFromFile}; +use zisk_sdk::{ElfBinaryFromFile, EmuOptions, ZiskIO, ZiskStdin, elf_path, ziskemu}; fn main() -> Result<()> { let elf_path = elf_path!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/src/main.rs b/examples/sha-hasher/host/src/main.rs index bc0ce6ee3..c71bc1907 100644 --- a/examples/sha-hasher/host/src/main.rs +++ b/examples/sha-hasher/host/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use zisk_sdk::{ZiskStdin, ZiskIO, ElfBinary, ProofOpts, ProverClient, include_elf}; +use zisk_sdk::{ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin, include_elf}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 89d667d87..4c9c7b7aa 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -336,7 +336,14 @@ impl AsmCoreProver { let mut snark_wrapper = None; if use_snark_wrapper { check_paths_exist(&proving_key_snark)?; - snark_wrapper = Some(SnarkWrapper::new(&proving_key_snark, verbose.into())?); + let (aux_trace, d_buffers, reload_fixed_pols_gpu) = proofman.get_preallocated_buffers(); + snark_wrapper = Some(SnarkWrapper::new_with_preallocated_buffers( + &proving_key_snark, + verbose.into(), + Some(aux_trace), + Some(d_buffers), + Some(reload_fixed_pols_gpu), + )?); } let core = diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 1aa765a90..92d7cfd8d 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -426,7 +426,6 @@ impl ProverBackend { let snark_proof = self.snark_wrapper.as_ref().unwrap().generate_final_snark_proof( &vadcop_proof, proof_options.output_dir_path.clone(), - Some(proofman.get_device_buffers_ptr()), )?; let publics = ZiskPublics::new(&vadcop_proof.public_values); @@ -544,13 +543,11 @@ impl ProverBackend { pubs.extend(publics.public_bytes()); let vadcop_final_proof = VadcopFinalProof::new(proof_bytes, pubs, false); - let device_ptr = self.proofman.as_ref().map(|p| p.get_device_buffers_ptr()); - - let snark_proof = self.snark_wrapper.as_ref().unwrap().generate_final_snark_proof( - &vadcop_final_proof, - None, - device_ptr, - )?; + let snark_proof = self + .snark_wrapper + .as_ref() + .unwrap() + .generate_final_snark_proof(&vadcop_final_proof, None)?; if snark_proof.protocol_id == SnarkProtocol::Plonk.protocol_id() { Ok(ZiskProofWithPublicValues { diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index cb873f0bf..43ca2a7aa 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -268,7 +268,14 @@ impl EmuCoreProver { let mut snark_wrapper = None; if use_snark_wrapper { check_paths_exist(&proving_key_snark)?; - snark_wrapper = Some(SnarkWrapper::new(&proving_key_snark, verbose.into())?); + let (aux_trace, d_buffers, reload_fixed_pols_gpu) = proofman.get_preallocated_buffers(); + snark_wrapper = Some(SnarkWrapper::new_with_preallocated_buffers( + &proving_key_snark, + verbose.into(), + Some(aux_trace), + Some(d_buffers), + Some(reload_fixed_pols_gpu), + )?); } let core = diff --git a/zisk-contracts/ZiskVerifier.sol b/zisk-contracts/ZiskVerifier.sol index e819834aa..d47ab0705 100644 --- a/zisk-contracts/ZiskVerifier.sol +++ b/zisk-contracts/ZiskVerifier.sol @@ -19,7 +19,7 @@ contract ZiskVerifier is PlonkVerifier, IZiskVerifier { } function getRootCVadcopFinal() external pure returns (uint64[4] memory) { - return [uint64(17315851345413577629), uint64(6088251014406292380), uint64(9617272497734844709), uint64(18293967017889666983)]; + return [uint64(5756952873125057328), uint64(1254521327410429374), uint64(17471446849604192873), uint64(13226325674217234543)]; } // Modulus zkSNARK From 2ce9579809620048ca83ca8093f4080632a8452b Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 18 Feb 2026 13:38:18 +0000 Subject: [PATCH 555/782] Remove BUILD_HINTS in test-env scripts --- tools/test-env/.env | 1 - tools/test-env/build_zisk.sh | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/tools/test-env/.env b/tools/test-env/.env index d1c5f1bfc..fd4f2f696 100644 --- a/tools/test-env/.env +++ b/tools/test-env/.env @@ -19,7 +19,6 @@ DISTRIBUTED_THREADS=64 PROVE_FLAGS=-a -y BUILD_GPU=0 -BUILD_HINTS=0 DISABLE_CLONE_REPO=0 DISABLE_RECURSIVE_SETUP=0 diff --git a/tools/test-env/build_zisk.sh b/tools/test-env/build_zisk.sh index 8dfa5f3f3..46360e51c 100755 --- a/tools/test-env/build_zisk.sh +++ b/tools/test-env/build_zisk.sh @@ -116,17 +116,13 @@ main() { ensure cargo clean || return 1 ensure cargo update || return 1 + # We build features in that way to be ready to support more feature in the future FEATURES=() if [[ "${BUILD_GPU}" == "1" ]]; then FEATURES+=("gpu") warn "Building with GPU support..." fi - if [[ "${BUILD_HINTS}" == "1" ]]; then - FEATURES+=("hints") - warn "Building with hints support..." - fi - BUILD_FEATURES="" if (( ${#FEATURES[@]} > 0 )); then BUILD_FEATURES="--features $(IFS=,; echo "${FEATURES[*]}")" From 51df716702c58b65ba7488b1d1255ba641a7fe89 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:10:31 +0100 Subject: [PATCH 556/782] cargo clippy --- emulator-asm/asm-runner/src/shmem_writer.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index 384009912..13e994707 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -204,9 +204,7 @@ impl SharedMemoryWriter { unsafe { (self.ptr.add(offset) as *mut u64).write(value); - } - unsafe { // Force changes to be flushed to the shared memory #[cfg(all(target_os = "linux", target_arch = "x86_64"))] if msync(self.ptr as *mut _, self.size, MS_SYNC) != 0 { From 36dcb7d47fca9d9caeb8edcd4c4d678daa8472e5 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 14:18:44 +0000 Subject: [PATCH 557/782] fixing cargo test --- common/src/io/stream/unix_socket.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/common/src/io/stream/unix_socket.rs b/common/src/io/stream/unix_socket.rs index 926bf10da..a93f25076 100644 --- a/common/src/io/stream/unix_socket.rs +++ b/common/src/io/stream/unix_socket.rs @@ -348,24 +348,6 @@ impl UnixSocketStreamWriter { /// Check if a client is currently connected. /// /// Returns `true` if a client is connected and ready to receive data. - /// - /// # Example - /// - /// ```no_run - /// use std::thread; - /// use std::time::Duration; - /// - /// let mut writer = UnixSocketStreamWriter::new("/tmp/my.sock")?; - /// writer.open()?; - /// - /// // Wait for client to connect - /// while !writer.is_client_connected() { - /// thread::sleep(Duration::from_millis(10)); - /// } - /// - /// // Now write will succeed - /// writer.write(b"Hello, client!")?; - /// ``` pub fn is_client_connected(&mut self) -> bool { // Already have a connection if self.socket.is_some() { From 8a668743e2406453685a317521cb9712d9553bbd Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 18 Feb 2026 14:24:58 +0000 Subject: [PATCH 558/782] Fix Clippy --- ziskos/entrypoint/src/hints/hint_buffer.rs | 13 +++---- ziskos/entrypoint/src/hints/macros.rs | 2 +- ziskos/entrypoint/src/hints/mod.rs | 44 ++++++++-------------- 3 files changed, 21 insertions(+), 38 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index bc6641271..17dfe1960 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -1,10 +1,7 @@ use bytes::{Bytes, BytesMut}; -use std::fs::File; use std::io::{self, Write}; use std::sync::{Arc, Condvar, Mutex, MutexGuard}; -use zisk_common::{ - CTRL_START, CTRL_END, -}; +use zisk_common::{CTRL_END, CTRL_START}; pub const DEFAULT_BUFFER_LEN: usize = 1 << 20; // 1 MiB // TODO: Set MAX_WRITE_LEN based on writer type (file or socket) @@ -126,7 +123,7 @@ impl HintBuffer { W: Write + ?Sized, D: Write + ?Sized, { - // Write hints from the buffer to the writer and optionally to a debug writer + // Write hints from the buffer to the writer and optionally to a debug writer let mut write_all = |buf: &[u8]| -> io::Result<()> { writer.write_all(buf)?; @@ -234,8 +231,8 @@ impl HintBuffer { } // Flush the writer and debug writer at the end - writer.flush()?; - if let Some(debug_writer) = debug_writer.as_deref_mut() { + writer.flush()?; + if let Some(debug_writer) = debug_writer.as_deref_mut() { debug_writer.flush()?; } @@ -269,4 +266,4 @@ impl<'a> HintWrite<'a> { drop(self.g); self.hb.not_empty.notify_one(); } -} \ No newline at end of file +} diff --git a/ziskos/entrypoint/src/hints/macros.rs b/ziskos/entrypoint/src/hints/macros.rs index f49c2b391..abf385a5a 100644 --- a/ziskos/entrypoint/src/hints/macros.rs +++ b/ziskos/entrypoint/src/hints/macros.rs @@ -143,7 +143,7 @@ macro_rules! define_hint_ptr { let pad = (8 - (total_len & 7)) & 7; - let mut w = crate::hints::HINT_BUFFER.begin_hint( + let mut w = $crate::hints::HINT_BUFFER.begin_hint( $hint_id, total_len, $is_result, diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 44520b1cd..26f297719 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -15,7 +15,6 @@ mod metrics; use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer}; use anyhow::{anyhow, Result}; use once_cell::sync::Lazy; -use std::any; use std::cell::UnsafeCell; use std::path::PathBuf; use std::thread::{self, JoinHandle}; @@ -23,7 +22,7 @@ use std::time::{Duration, Instant}; use std::{ffi::CStr, os::raw::c_char}; use std::{ io::{self, BufWriter, Write}, - sync::{Arc, Mutex}, + sync::Arc, }; use tokio::sync::oneshot; use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; @@ -31,9 +30,6 @@ use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; #[cfg(zisk_hints_single_thread)] use std::thread::ThreadId; -#[cfg(zisk_hints_single_thread)] -use once_cell::sync::OnceCell; - pub use bls12_381::*; pub use bn254::*; pub use keccak256::*; @@ -73,21 +69,20 @@ impl HintFileWriterHandleCell { } } -pub fn wait_for_hints_writer() -> Result<()> { +fn wait_for_hints_writer() -> Result<()> { if let Some(handle) = HINT_WRITER_HANDLE.take() { HINT_BUFFER.close(); match handle.join() { Ok(result) => { if let Err(err) = result { return Err(anyhow!( - "Failed previous hints writer thread result, error: {}", err + "Failed previous hints writer thread result, error: {}", + err )); } } Err(e) => { - return Err(anyhow!( - "Failed previous hints writer thread, error: {:?}", e - )); + return Err(anyhow!("Failed previous hints writer thread, error: {:?}", e)); } } } @@ -100,7 +95,6 @@ pub fn init_hints() { { let tid = std::thread::current().id(); *MAIN_TID.lock().unwrap() = Some(tid); - println!("Initializing MAIN_TID to {:?}", tid); } #[cfg(zisk_hints_metrics)] @@ -112,10 +106,7 @@ pub fn init_hints() { HINT_BUFFER.write_hint_start(); } -pub fn init_hints_file( - hints_file_path: PathBuf, - ready: Option>, -) -> Result<()> { +pub fn init_hints_file(hints_file_path: PathBuf, ready: Option>) -> Result<()> { wait_for_hints_writer()?; if let Some(tx) = ready { @@ -143,16 +134,14 @@ pub fn init_hints_socket( // Open the connection socket_writer.open()?; - // Notify that socket is ready after client connects + // Notify that socket is ready if let Some(tx) = ready { let _ = tx.send(()); } // Wait for client to connect with a timeout if let Err(e) = socket_writer.wait_for_client(CLIENT_CONNECT_TIMEOUT) { - return Err(anyhow!( - "Failed to wait for client to connect to hints socket, error: {}", e - )); + return Err(anyhow!("Failed to wait for client to connect to hints socket, error: {}", e)); } init_hints(); @@ -181,20 +170,19 @@ pub fn close_hints() -> Result<()> { match handle.join() { Ok(result) => match result { Ok(()) => Ok(()), - Err(e) => return Err(anyhow!( - "Failed hints writer thread result, error: {}", e - )), + Err(e) => return Err(anyhow!("Failed hints writer thread result, error: {}", e)), }, - Err(e) => Err(anyhow!( - "Failed hints writer thread, error: {:?}", e - )), + Err(e) => Err(anyhow!("Failed hints writer thread, error: {:?}", e)), } } else { Ok(()) } } -pub fn write_hints(writer: &mut W, debug_writer: Option<&mut dyn Write>) -> io::Result<()> { +pub fn write_hints( + writer: &mut W, + debug_writer: Option<&mut dyn Write>, +) -> io::Result<()> { // Write hints from the buffer HINT_BUFFER.drain_to_writer(writer, debug_writer)?; @@ -233,9 +221,7 @@ impl UnixSocketWriter { let start = Instant::now(); while !self.inner.is_client_connected() { if start.elapsed() >= timeout { - return Err(anyhow!( - "Timeout waiting for client to connect to socket" - )); + return Err(anyhow!("Timeout waiting for client to connect to socket")); } thread::sleep(WAIT_FOR_CLIENT_RETRY_DELAY); } From cd956dbf8425e91de533d51ee3109b6ec0b172eb Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:34:16 +0000 Subject: [PATCH 559/782] added debugging messages --- emulator-asm/asm-runner/src/hints_shmem.rs | 12 ++++++++++++ precompiles/hints/src/hints_processor.rs | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index a114f698d..1338d5c01 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -188,9 +188,11 @@ impl StreamSink for HintsShmem { #[inline] fn submit(&self, processed: Vec) -> anyhow::Result<()> { let data_size = processed.len() as u64; + debug!("[SHMEM] submit() called with {} u64 elements", data_size); // Early return for empty data if data_size == 0 { + debug!("[SHMEM] submit() early return - empty data"); return Ok(()); } @@ -214,6 +216,7 @@ impl StreamSink for HintsShmem { // Read current write position once let write_pos = unified.control_writer.read_u64_at(0); + debug!("[SHMEM] Current write_pos={}, attempting to submit {} u64s", write_pos, data_size); // Flow control: wait until all consumers have advanced enough // We need to wait for the slowest consumer (minimum read position) @@ -247,30 +250,39 @@ impl StreamSink for HintsShmem { // Flow control based on buffer occupancy if available_space >= data_size { + debug!("[SHMEM] Sufficient space available ({}), breaking flow control loop", available_space); break; } // Not enough space - wait for the SLOWEST consumer to signal progress + debug!("[SHMEM] Insufficient space: available={}, needed={}, waiting on consumer {} (read_pos={})", + available_space, data_size, slowest_idx, min_read_pos); // Retry on interrupt (EINTR) if separate[slowest_idx].sem_read.wait().is_err() { + debug!("[SHMEM] sem_read.wait() returned error, retrying"); continue; } + debug!("[SHMEM] sem_read.wait() succeeded, retrying space check"); } // Write data ONCE to the unified shared memory buffer + debug!("[SHMEM] Writing {} u64s to ring buffer at pos {}", data_size, write_pos); unified.data_writer.write_ring_buffer(&processed)?; fence(Ordering::Release); // Update write position ONCE in control memory unified.control_writer.write_u64_at(0, write_pos + data_size); + debug!("[SHMEM] Updated write_pos to {}", write_pos + data_size); fence(Ordering::Release); // Notify ALL consumers that new data is available + debug!("[SHMEM] Posting to {} consumer semaphores", separate.len()); for res in separate.iter_mut() { res.sem_available.post()?; } + debug!("[SHMEM] submit() completed successfully"); Ok(()) } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 1bd6b3cc2..99c2e38bf 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -531,11 +531,16 @@ impl HintsProcessor { /// Drainer thread that waits for hints to complete and drains ready results from queue. fn drainer_thread(state: Arc, hints_sink: Arc) { + debug!("[DRAINER] Thread started"); loop { + debug!("[DRAINER] Attempting to acquire queue lock"); let mut queue = state.queue.lock().unwrap(); + debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", + queue.buffer.len(), queue.next_drain_seq); // Check for shutdown if state.shutdown.load(Ordering::Acquire) { + debug!("[DRAINER] Shutdown signal received, exiting"); break; } @@ -543,25 +548,31 @@ impl HintsProcessor { let mut drained_any = false; while let Some(Some(res)) = queue.buffer.front() { drained_any = true; + let current_seq = queue.next_drain_seq; match res { Ok(data) => { // Clone data before dropping lock + debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); let data_to_submit = data.clone(); queue.buffer.pop_front(); queue.next_drain_seq += 1; // Drop lock before submitting to avoid blocking workers + debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); drop(queue); // Submit to sink + debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); if let Err(e) = hints_sink.submit(data_to_submit) { eprintln!("Error submitting to sink: {}", e); state.error_flag.store(true, Ordering::Release); state.drain_signal.notify_all(); return; } + debug!("[DRAINER] Completed submit seq={}", current_seq); // Re-acquire lock for next iteration + debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); queue = state.queue.lock().unwrap(); } Err(e) => { @@ -578,20 +589,25 @@ impl HintsProcessor { // If we drained any results, notify wait_for_completion that buffer changed if drained_any { + debug!("[DRAINER] Notifying wait_for_completion"); state.drain_signal.notify_all(); } // Check for shutdown again before waiting if state.shutdown.load(Ordering::Acquire) { + debug!("[DRAINER] Shutdown signal received before wait, exiting"); break; } // Wait for notification that a hint completed + debug!("[DRAINER] Waiting on drain_signal condvar"); #[allow(unused_assignments)] { queue = state.drain_signal.wait(queue).unwrap(); } + debug!("[DRAINER] Woke up from condvar wait"); } + debug!("[DRAINER] Thread exiting"); } /// Waits for all pending hints to be processed and drained. From fb7d83feea92eb414ae8f5ed28d5a00a01fb8a8f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:01:55 +0000 Subject: [PATCH 560/782] temptative fix --- precompiles/hints/src/hints_processor.rs | 252 +++++++++++++++++------ 1 file changed, 186 insertions(+), 66 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 99c2e38bf..366b94835 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -392,11 +392,12 @@ impl HintsProcessor { // Handle pass-through hints immediately if hint.is_passthrough { queue.buffer.push_back(Some(Ok(hint.data.clone()))); - // Notify immediately while holding the lock to ensure drainer sees the result - // Release lock after this block, avoiding duplicate notification - drop(queue); - // Use notify_all since wait_for_completion also waits on this condvar + // Notify BEFORE releasing lock to prevent race condition where drainer + // checks buffer, finds it empty, then we notify, then drainer waits + // (missing our notification). Notifying while holding lock ensures + // drainer will see the buffer change when it wakes. self.state.drain_signal.notify_all(); + drop(queue); // Continue to next hint without spawning worker idx += length; continue; @@ -522,20 +523,21 @@ impl HintsProcessor { // Fill the slot to allow drainer to proceed (critical for ordering) queue.buffer[offset] = Some(result); - // Release lock before notifying - drop(queue); - - // Notify drainer thread (use notify_all to wake any waiting threads) + // Notify BEFORE releasing lock to prevent race condition where drainer + // checks buffer, finds no ready result, then we notify, then drainer waits + // (missing our notification). Notifying while holding lock ensures + // drainer will see the buffer change when it wakes. state.drain_signal.notify_all(); + drop(queue); } /// Drainer thread that waits for hints to complete and drains ready results from queue. fn drainer_thread(state: Arc, hints_sink: Arc) { debug!("[DRAINER] Thread started"); + let mut queue = state.queue.lock().unwrap(); + loop { - debug!("[DRAINER] Attempting to acquire queue lock"); - let mut queue = state.queue.lock().unwrap(); - debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", + debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", queue.buffer.len(), queue.next_drain_seq); // Check for shutdown @@ -544,68 +546,61 @@ impl HintsProcessor { break; } - // Drain all consecutive ready results from the front - let mut drained_any = false; - while let Some(Some(res)) = queue.buffer.front() { - drained_any = true; - let current_seq = queue.next_drain_seq; - match res { - Ok(data) => { - // Clone data before dropping lock - debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); - let data_to_submit = data.clone(); - queue.buffer.pop_front(); - queue.next_drain_seq += 1; - - // Drop lock before submitting to avoid blocking workers - debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); - drop(queue); - - // Submit to sink - debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); - if let Err(e) = hints_sink.submit(data_to_submit) { - eprintln!("Error submitting to sink: {}", e); + // Check if front slot has a ready result + match queue.buffer.front() { + Some(Some(res)) => { + // Ready result found - process it + let current_seq = queue.next_drain_seq; + match res { + Ok(data) => { + debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); + let data_to_submit = data.clone(); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; + + // Notify wait_for_completion that buffer changed + state.drain_signal.notify_all(); + + // Drop lock before submitting to avoid blocking workers + debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); + drop(queue); + + // Submit to sink + debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); + if let Err(e) = hints_sink.submit(data_to_submit) { + eprintln!("Error submitting to sink: {}", e); + state.error_flag.store(true, Ordering::Release); + // Re-acquire lock to notify while holding it + let _queue = state.queue.lock().unwrap(); + state.drain_signal.notify_all(); + return; + } + debug!("[DRAINER] Completed submit seq={}", current_seq); + + // Re-acquire lock for next iteration + debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); + queue = state.queue.lock().unwrap(); + } + Err(e) => { + // Error found - signal to stop state.error_flag.store(true, Ordering::Release); + eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; state.drain_signal.notify_all(); return; } - debug!("[DRAINER] Completed submit seq={}", current_seq); - - // Re-acquire lock for next iteration - debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); - queue = state.queue.lock().unwrap(); - } - Err(e) => { - // Error found - signal to stop - state.error_flag.store(true, Ordering::Release); - eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); - queue.buffer.pop_front(); - queue.next_drain_seq += 1; - state.drain_signal.notify_all(); - return; } } + Some(None) | None => { + // No ready result at front (pending or empty buffer) + // CRITICAL: Must wait IMMEDIATELY after this check with NO code in between + // to prevent race condition where worker notifies after we check but before we wait. + debug!("[DRAINER] No ready result, waiting on drain_signal condvar"); + queue = state.drain_signal.wait(queue).unwrap(); + debug!("[DRAINER] Woke up from condvar wait"); + } } - - // If we drained any results, notify wait_for_completion that buffer changed - if drained_any { - debug!("[DRAINER] Notifying wait_for_completion"); - state.drain_signal.notify_all(); - } - - // Check for shutdown again before waiting - if state.shutdown.load(Ordering::Acquire) { - debug!("[DRAINER] Shutdown signal received before wait, exiting"); - break; - } - - // Wait for notification that a hint completed - debug!("[DRAINER] Waiting on drain_signal condvar"); - #[allow(unused_assignments)] - { - queue = state.drain_signal.wait(queue).unwrap(); - } - debug!("[DRAINER] Woke up from condvar wait"); } debug!("[DRAINER] Thread exiting"); } @@ -1675,4 +1670,129 @@ mod tests { assert_eq!(results[i][0], i as u64 + 1000, "Result {} out of order", i); } } + + /// Regression test for race condition where drainer thread misses notifications. + /// + /// This test attempts to trigger a race between workers completing hints and + /// the drainer thread waiting on the condvar. The race occurs when: + /// 1. Drainer checks buffer.front() and finds no ready result + /// 2. Worker completes and calls notify_all() + /// 3. Drainer enters wait() - MISSED the notification! + /// + /// With the fix (notify before drop), this race is eliminated because the + /// notification happens while holding the lock, ensuring the drainer either: + /// - Sees the result before checking (doesn't wait) + /// - Is already waiting when notified (wakes up) + #[test] + fn test_race_condition_drainer_notification_lost() { + use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + use std::sync::Arc; + use std::thread; + use std::time::{Duration, Instant}; + + struct RecordingSink { + received_count: Arc, + } + + impl StreamSink for RecordingSink { + fn submit(&self, _processed: Vec) -> Result<()> { + self.received_count.fetch_add(1, Ordering::SeqCst); + Ok(()) + } + } + + // Custom hint code for testing (0x7FFF_0200 to avoid conflicts) + const RACE_HINT: u32 = 0x7FFF_0200; + + // Test parameters designed to maximize race window hits + const NUM_THREADS: usize = 32; + const HINTS_PER_BATCH: usize = 100; + const ITERATIONS: usize = 100; + const TIMEOUT_MS: u64 = 500; + + for iteration in 0..ITERATIONS { + let received_count = Arc::new(AtomicUsize::new(0)); + let sink = RecordingSink { received_count: Arc::clone(&received_count) }; + + let p = HintsProcessor::builder(sink) + .num_threads(NUM_THREADS) + .custom_hint(RACE_HINT, |data| { + // Tiny random delay (0-1000ns) to desynchronize worker completions + // This maximizes the chance of workers completing just as + // drainer is between checking buffer and entering wait + let hash = data[0].wrapping_mul(2654435761); + let delay_ns = hash % 1000; + if delay_ns > 0 { + std::hint::spin_loop(); + // Tiny busy-wait (more precise than sleep for nanoseconds) + let start = Instant::now(); + while start.elapsed().as_nanos() < delay_ns as u128 { + std::hint::spin_loop(); + } + } + Ok(vec![data[0] + 1]) + }) + .build() + .unwrap(); + + // Build batch of hints + let mut data = Vec::with_capacity(HINTS_PER_BATCH * 2); + for i in 0..HINTS_PER_BATCH { + data.push(make_header(RACE_HINT, 1)); + data.push(i as u64); + } + + // Process hints + p.process_hints(&data, false).unwrap(); + + // Use watchdog to detect hang + let completed = Arc::new(AtomicBool::new(false)); + let completed_clone = Arc::clone(&completed); + + let watchdog = thread::spawn(move || { + let start = Instant::now(); + while !completed_clone.load(Ordering::Acquire) { + if start.elapsed() > Duration::from_millis(TIMEOUT_MS) { + return false; // Timeout - race condition triggered! + } + thread::sleep(Duration::from_millis(10)); + } + true // Completed in time + }); + + // Wait for completion + let result = p.wait_for_completion(); + completed.store(true, Ordering::Release); + + let watchdog_ok = watchdog.join().unwrap(); + assert!( + watchdog_ok, + "RACE CONDITION DETECTED: Iteration {} hung for >{}ms. \ + Drainer missed worker notifications.", + iteration, + TIMEOUT_MS + ); + + assert!(result.is_ok(), "Iteration {} failed: {:?}", iteration, result); + + // Verify all hints were processed + let count = received_count.load(Ordering::SeqCst); + assert_eq!( + count, HINTS_PER_BATCH, + "Iteration {}: Expected {} results, got {}", + iteration, HINTS_PER_BATCH, count + ); + } + + println!( + "\n========================================\n\ + Race Condition Regression Test PASSED\n\ + Iterations: {}\n\ + Hints per iteration: {}\n\ + Worker threads: {}\n\ + No hangs detected!\n\ + ========================================\n", + ITERATIONS, HINTS_PER_BATCH, NUM_THREADS + ); + } } From 5ecbc0a7a19f86c69a2ae1e82969bfc1bb93c570 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:43:53 +0000 Subject: [PATCH 561/782] temptative fix II --- precompiles/hints/src/hints_processor.rs | 555 +++--------------- precompiles/hints/src/lib.rs | 2 + .../hints/src/ordered_result_buffer.rs | 490 ++++++++++++++++ 3 files changed, 571 insertions(+), 476 deletions(-) create mode 100644 precompiles/hints/src/ordered_result_buffer.rs diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 366b94835..43509a964 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -4,12 +4,13 @@ //! that are received as a stream of `u64` values. Hints are used to provide preprocessed //! data to precompile operations in the ZisK zkVM. +use crate::ordered_result_buffer::{BufferStatus, OrderedResultBuffer}; use anyhow::Result; use rayon::{ThreadPool, ThreadPoolBuilder}; -use std::collections::{HashMap, VecDeque}; +use std::collections::HashMap; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::{Arc, Condvar, Mutex}; +use std::sync::{Arc, Mutex}; use std::time::Instant; use tracing::{debug, info}; use zisk_common::io::{StreamProcessor, StreamSink}; @@ -34,55 +35,6 @@ use ziskos_hints::handlers::secp256k1::{ use ziskos_hints::handlers::secp256r1::secp256r1_ecdsa_verify_hint; use ziskos_hints::handlers::sha256::sha256_hint; -/// Ordered result buffer with drain state. -/// -/// This structure maintains a VecDeque that holds processed results in order, -/// allowing out-of-order completion while ensuring in-order output. -struct ResultQueue { - /// The result buffer: None = pending, Some(Ok(...)) = ready, Some(Err(...)) = error - buffer: VecDeque>>>, - /// Sequence ID of the next result to drain from buffer[0] - next_drain_seq: usize, -} - -/// Thread-safe shared state for parallel hint processing. -struct HintProcessorState { - /// Ordered results ready for draining - queue: Mutex, - /// Notifies drainer thread when a hint completes - drain_signal: Condvar, - /// Next sequence ID to assign to incoming hints - next_seq: AtomicUsize, - /// Signals processing should stop - error_flag: AtomicBool, - /// Signals drainer thread to shut down - shutdown: AtomicBool, - /// Invalidates stale workers after reset - generation: AtomicUsize, -} - -impl HintProcessorState { - fn new() -> Self { - Self { - queue: Mutex::new(ResultQueue { buffer: VecDeque::new(), next_drain_seq: 0 }), - drain_signal: Condvar::new(), - next_seq: AtomicUsize::new(0), - error_flag: AtomicBool::new(false), - shutdown: AtomicBool::new(false), - generation: AtomicUsize::new(0), - } - } - - fn reset(&self) { - self.error_flag.store(false, Ordering::Release); - self.next_seq.store(0, Ordering::Relaxed); - self.generation.fetch_add(1, Ordering::SeqCst); - let mut queue = self.queue.lock().unwrap(); - queue.buffer.clear(); - queue.next_drain_seq = 0; - } -} - /// Type alias for custom hint handler functions. pub type CustomHintHandler = Arc Result> + Send + Sync>; @@ -144,20 +96,20 @@ impl HintsProcessorBuilder { .build() .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; - let state = Arc::new(HintProcessorState::new()); + let buffer = Arc::new(OrderedResultBuffer::new()); let hints_sink = self.hints_sink; // Spawn drainer thread - let drainer_state = Arc::clone(&state); + let drainer_buffer = Arc::clone(&buffer); let drainer_sink = Arc::clone(&hints_sink); let drainer_thread = std::thread::spawn(move || { - HintsProcessor::drainer_thread(drainer_state, drainer_sink); + HintsProcessor::drainer_thread(drainer_buffer, drainer_sink); }); Ok(HintsProcessor { pool, num_hint: AtomicUsize::new(0), - state, + buffer, stats: if self.enable_stats { Some(Mutex::new(HashMap::new())) } else { None }, hints_sink, drainer_thread: ManuallyDrop::new(drainer_thread), @@ -180,8 +132,8 @@ pub struct HintsProcessor { num_hint: AtomicUsize, - /// Shared state for parallel hint processing - state: Arc, + /// Ordered result buffer for parallel processing with in-order output + buffer: Arc>>>, /// Optional statistics collected during hint processing (for debugging). stats: Option>>, @@ -270,7 +222,7 @@ impl HintsProcessor { let mut idx = 0; while idx < hints.len() { // Check for error before processing each hint - if self.state.error_flag.load(Ordering::Acquire) { + if self.buffer.status() == BufferStatus::Error { return Err(anyhow::anyhow!("Processing stopped due to previous error")); } let (parsed_hint, consumed) = @@ -367,52 +319,32 @@ impl HintsProcessor { break; } HintCode::Ctrl(CtrlHint::Cancel) => { - // Cancel current stream: set error and notify - self.state.error_flag.store(true, Ordering::Release); - self.state.drain_signal.notify_all(); + // Cancel current stream: set error + self.buffer.set_error(); return Err(anyhow::anyhow!("Stream cancelled")); } HintCode::Ctrl(CtrlHint::Error) => { // External error signal - self.state.error_flag.store(true, Ordering::Release); - self.state.drain_signal.notify_all(); + self.buffer.set_error(); return Err(anyhow::anyhow!("Stream error signalled")); } _ => {} // Built-in data hint or custom hint; continue processing } - // Capture generation outside mutex - SeqCst provides sufficient ordering - let generation = self.state.generation.load(Ordering::SeqCst); - - // Atomically reserve slot - use Relaxed for seq since mutex provides ordering - let seq_id = { - let mut queue = self.state.queue.lock().unwrap(); - let seq = self.state.next_seq.fetch_add(1, Ordering::Relaxed); - - // Handle pass-through hints immediately - if hint.is_passthrough { - queue.buffer.push_back(Some(Ok(hint.data.clone()))); - // Notify BEFORE releasing lock to prevent race condition where drainer - // checks buffer, finds it empty, then we notify, then drainer waits - // (missing our notification). Notifying while holding lock ensures - // drainer will see the buffer change when it wakes. - self.state.drain_signal.notify_all(); - drop(queue); - // Continue to next hint without spawning worker - idx += length; - continue; - } else { - queue.buffer.push_back(None); - } - - seq - }; + // Handle pass-through hints immediately (no worker needed) + if hint.is_passthrough { + self.buffer.reserve_and_fill(Ok(hint.data.clone())); + idx += length; + continue; + } - // Spawn processing task for async hints (Noop already handled above) - let state = Arc::clone(&self.state); + // Reserve slot and spawn worker for async processing + let seq_id = self.buffer.reserve(); + let buffer = Arc::clone(&self.buffer); let custom_handlers = Arc::clone(&self.custom_handlers); self.pool.spawn(move || { - Self::worker_thread(state, hint, generation, seq_id, custom_handlers); + let result = Self::dispatch_hint(hint, custom_handlers); + buffer.fill(seq_id, result); }); idx += length; @@ -455,153 +387,31 @@ impl HintsProcessor { } } - /// Worker thread that processes a single hint and stores the result. - /// - /// # Arguments - /// - /// * `state` - Shared processor state - /// * `hint` - The hint to process - /// * `generation` - Generation number for detecting stale workers - /// * `seq_id` - Sequence ID for ordering results - /// * `custom_handlers` - Custom hint handlers - fn worker_thread( - state: Arc, - hint: PrecompileHint, - generation: usize, - seq_id: usize, - custom_handlers: Arc>, + /// Drainer thread that takes results in order and submits to sink. + fn drainer_thread( + buffer: Arc>>>, + hints_sink: Arc, ) { - // Check generation first to detect stale workers (before processing) - let current_gen = state.generation.load(Ordering::SeqCst); - if generation != current_gen { - // Worker belongs to old generation; ignore - return; - } - - // println!("Processing Hint => {:?}:", hint); - - // Check if we should stop due to error - but still need to fill the slot - let result = if state.error_flag.load(Ordering::Acquire) { - Err(anyhow::anyhow!("Processing stopped due to error")) - } else { - // Process the hint - Self::dispatch_hint(hint, custom_handlers) - }; - - // println!( - // "Hint result: {:x?} bytes", - // match &result { - // Ok(data) => format!("{:?}", data), - // Err(e) => format!("Err({})", e), - // } - // ); - - // Store result - MUST fill slot even if error occurred - let mut queue = state.queue.lock().unwrap(); - - // Check generation again in case reset happened during processing - let current_gen = state.generation.load(Ordering::SeqCst); - if generation != current_gen { - // Worker belongs to old generation; buffer was cleared and repopulated - // Our seq_id is from the old session and doesn't correspond to current slots - return; - } - - // Calculate offset in buffer; handle drained slots - if seq_id < queue.next_drain_seq { - // This result belongs to a previous stream/session; ignore - return; - } - let offset = seq_id - queue.next_drain_seq; - - // Check if slot exists - if not, drainer already processed and removed it - if offset >= queue.buffer.len() { - // Slot was already drained; safe to drop this result - return; - } - - // Fill the slot to allow drainer to proceed (critical for ordering) - queue.buffer[offset] = Some(result); - - // Notify BEFORE releasing lock to prevent race condition where drainer - // checks buffer, finds no ready result, then we notify, then drainer waits - // (missing our notification). Notifying while holding lock ensures - // drainer will see the buffer change when it wakes. - state.drain_signal.notify_all(); - drop(queue); - } - - /// Drainer thread that waits for hints to complete and drains ready results from queue. - fn drainer_thread(state: Arc, hints_sink: Arc) { debug!("[DRAINER] Thread started"); - let mut queue = state.queue.lock().unwrap(); - loop { - debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", - queue.buffer.len(), queue.next_drain_seq); - - // Check for shutdown - if state.shutdown.load(Ordering::Acquire) { - debug!("[DRAINER] Shutdown signal received, exiting"); - break; - } - - // Check if front slot has a ready result - match queue.buffer.front() { - Some(Some(res)) => { - // Ready result found - process it - let current_seq = queue.next_drain_seq; - match res { - Ok(data) => { - debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); - let data_to_submit = data.clone(); - queue.buffer.pop_front(); - queue.next_drain_seq += 1; - - // Notify wait_for_completion that buffer changed - state.drain_signal.notify_all(); - - // Drop lock before submitting to avoid blocking workers - debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); - drop(queue); - - // Submit to sink - debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); - if let Err(e) = hints_sink.submit(data_to_submit) { - eprintln!("Error submitting to sink: {}", e); - state.error_flag.store(true, Ordering::Release); - // Re-acquire lock to notify while holding it - let _queue = state.queue.lock().unwrap(); - state.drain_signal.notify_all(); - return; - } - debug!("[DRAINER] Completed submit seq={}", current_seq); - - // Re-acquire lock for next iteration - debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); - queue = state.queue.lock().unwrap(); - } - Err(e) => { - // Error found - signal to stop - state.error_flag.store(true, Ordering::Release); - eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); - queue.buffer.pop_front(); - queue.next_drain_seq += 1; - state.drain_signal.notify_all(); - return; - } + while let Some(result) = buffer.take_next() { + match result { + Ok(data) => { + debug!("[DRAINER] Submitting {} u64s", data.len()); + if let Err(e) = hints_sink.submit(data) { + eprintln!("Error submitting to sink: {}", e); + buffer.set_error(); + return; } } - Some(None) | None => { - // No ready result at front (pending or empty buffer) - // CRITICAL: Must wait IMMEDIATELY after this check with NO code in between - // to prevent race condition where worker notifies after we check but before we wait. - debug!("[DRAINER] No ready result, waiting on drain_signal condvar"); - queue = state.drain_signal.wait(queue).unwrap(); - debug!("[DRAINER] Woke up from condvar wait"); + Err(e) => { + eprintln!("Hint processing error: {}", e); + buffer.set_error(); + return; } } } + debug!("[DRAINER] Thread exiting"); } @@ -615,20 +425,10 @@ impl HintsProcessor { /// * `Ok(())` - All hints processed successfully /// * `Err` - If an error occurred during processing pub fn wait_for_completion(&self) -> Result<()> { - let mut queue = self.state.queue.lock().unwrap(); - - while !queue.buffer.is_empty() { - if self.state.error_flag.load(Ordering::Acquire) { - return Err(anyhow::anyhow!("Processing stopped due to error")); - } - // Wait for notification that buffer state changed - queue = self.state.drain_signal.wait(queue).unwrap(); - } - - if self.state.error_flag.load(Ordering::Acquire) { + let status = self.buffer.wait_until_drained(); + if status == BufferStatus::Error { return Err(anyhow::anyhow!("Processing stopped due to error")); } - Ok(()) } @@ -709,7 +509,7 @@ impl HintsProcessor { fn reset(&self) { self.num_hint.store(0, Ordering::Relaxed); - self.state.reset(); + self.buffer.reset(); if let Some(stats) = self.stats.as_ref() { stats.lock().unwrap().clear(); } @@ -727,8 +527,7 @@ impl HintsProcessor { impl Drop for HintsProcessor { fn drop(&mut self) { // Signal drainer thread to shut down - self.state.shutdown.store(true, Ordering::Release); - self.state.drain_signal.notify_all(); + self.buffer.shutdown(); // Join the drainer thread to ensure clean shutdown // Safety: We only take the value once in drop @@ -800,9 +599,7 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Buffer should be empty after completion - let queue = p.state.queue.lock().unwrap(); - assert!(queue.buffer.is_empty()); - assert_eq!(queue.next_drain_seq, 1); + assert!(p.buffer.is_empty()); } #[test] @@ -820,10 +617,8 @@ mod tests { assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Verify all hints were processed (buffer empty, next_drain_seq advanced) - let queue = p.state.queue.lock().unwrap(); - assert!(queue.buffer.is_empty()); - assert_eq!(queue.next_drain_seq, 3); + // Verify all hints were processed (buffer empty) + assert!(p.buffer.is_empty()); } #[test] @@ -837,10 +632,8 @@ mod tests { assert!(p.process_hints(&data2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Verify sequence continued across calls - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 2); - assert!(queue.buffer.is_empty()); + // Verify all hints were processed + assert!(p.buffer.is_empty()); } #[test] @@ -851,8 +644,7 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // No hints processed - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 0); + assert!(p.buffer.is_empty()); } // Negative tests @@ -892,15 +684,14 @@ mod tests { // Reset should clear any error state p.reset(); - assert!(!p.state.error_flag.load(Ordering::Acquire)); + assert_eq!(p.buffer.status(), BufferStatus::Active); // Should be able to process new hints after reset (8 bytes = 1 u64) let good = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x42]; assert!(p.process_hints(&good, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); + assert!(p.buffer.is_empty()); } #[test] @@ -925,11 +716,7 @@ mod tests { p.process_hints(&end, false).unwrap(); // Buffer should already be empty - { - let queue = p.state.queue.lock().unwrap(); - assert!(queue.buffer.is_empty()); - assert_eq!(queue.next_drain_seq, 2); - } + assert!(p.buffer.is_empty()); // Explicit wait should be instant assert!(p.wait_for_completion().is_ok()); @@ -945,7 +732,7 @@ mod tests { assert!(result.unwrap_err().to_string().contains("cancelled")); // Error flag should be set - assert!(p.state.error_flag.load(Ordering::Acquire)); + assert_eq!(p.buffer.status(), BufferStatus::Error); } #[test] @@ -958,7 +745,7 @@ mod tests { assert!(result.unwrap_err().to_string().contains("error")); // Error flag should be set - assert!(p.state.error_flag.load(Ordering::Acquire)); + assert_eq!(p.buffer.status(), BufferStatus::Error); } #[test] @@ -1087,7 +874,7 @@ mod tests { // Wait should detect the error from drainer thread std::thread::sleep(std::time::Duration::from_millis(100)); - assert!(p.state.error_flag.load(Ordering::Acquire)); + assert_eq!(p.buffer.status(), BufferStatus::Error); } // Builder tests @@ -1327,22 +1114,14 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Verify no results yet (hint is partial) - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 0); - assert!(queue.buffer.is_empty()); - } + assert!(p.buffer.is_empty()); // Second batch completes the hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Now we should have the complete result (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1359,21 +1138,14 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Verify no results yet - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 0); - } + assert!(p.buffer.is_empty()); // Second batch completes the hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Now we should have the complete result (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1395,21 +1167,14 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Still no complete results - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 0); - } + assert!(p.buffer.is_empty()); // Third batch completes the hint assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Now we should have the complete result (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1433,31 +1198,18 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // No results yet - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 0); - } + assert!(p.buffer.is_empty()); // Complete first hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have first result now - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - } - // Complete second hint assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have both results - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 2); - assert!(queue.buffer.is_empty()); - } + // Should have both results (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1477,22 +1229,12 @@ mod tests { assert!(p.process_hints(&batch1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have result for complete hint - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - } - // Complete the partial hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have both results - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 2); - assert!(queue.buffer.is_empty()); - } + // Should have both results (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1506,12 +1248,8 @@ mod tests { assert!(p.process_hints(&batch1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have result - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Should have result (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1538,12 +1276,8 @@ mod tests { // End stream should wait for all hints to complete assert!(p.process_hints(&end, false).is_ok()); - // Everything should be processed - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Everything should be processed (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1575,10 +1309,8 @@ mod tests { assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - } + // Buffer should be drained + assert!(p.buffer.is_empty()); } #[test] @@ -1602,12 +1334,8 @@ mod tests { assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have complete result - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Should have complete result (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1670,129 +1398,4 @@ mod tests { assert_eq!(results[i][0], i as u64 + 1000, "Result {} out of order", i); } } - - /// Regression test for race condition where drainer thread misses notifications. - /// - /// This test attempts to trigger a race between workers completing hints and - /// the drainer thread waiting on the condvar. The race occurs when: - /// 1. Drainer checks buffer.front() and finds no ready result - /// 2. Worker completes and calls notify_all() - /// 3. Drainer enters wait() - MISSED the notification! - /// - /// With the fix (notify before drop), this race is eliminated because the - /// notification happens while holding the lock, ensuring the drainer either: - /// - Sees the result before checking (doesn't wait) - /// - Is already waiting when notified (wakes up) - #[test] - fn test_race_condition_drainer_notification_lost() { - use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::sync::Arc; - use std::thread; - use std::time::{Duration, Instant}; - - struct RecordingSink { - received_count: Arc, - } - - impl StreamSink for RecordingSink { - fn submit(&self, _processed: Vec) -> Result<()> { - self.received_count.fetch_add(1, Ordering::SeqCst); - Ok(()) - } - } - - // Custom hint code for testing (0x7FFF_0200 to avoid conflicts) - const RACE_HINT: u32 = 0x7FFF_0200; - - // Test parameters designed to maximize race window hits - const NUM_THREADS: usize = 32; - const HINTS_PER_BATCH: usize = 100; - const ITERATIONS: usize = 100; - const TIMEOUT_MS: u64 = 500; - - for iteration in 0..ITERATIONS { - let received_count = Arc::new(AtomicUsize::new(0)); - let sink = RecordingSink { received_count: Arc::clone(&received_count) }; - - let p = HintsProcessor::builder(sink) - .num_threads(NUM_THREADS) - .custom_hint(RACE_HINT, |data| { - // Tiny random delay (0-1000ns) to desynchronize worker completions - // This maximizes the chance of workers completing just as - // drainer is between checking buffer and entering wait - let hash = data[0].wrapping_mul(2654435761); - let delay_ns = hash % 1000; - if delay_ns > 0 { - std::hint::spin_loop(); - // Tiny busy-wait (more precise than sleep for nanoseconds) - let start = Instant::now(); - while start.elapsed().as_nanos() < delay_ns as u128 { - std::hint::spin_loop(); - } - } - Ok(vec![data[0] + 1]) - }) - .build() - .unwrap(); - - // Build batch of hints - let mut data = Vec::with_capacity(HINTS_PER_BATCH * 2); - for i in 0..HINTS_PER_BATCH { - data.push(make_header(RACE_HINT, 1)); - data.push(i as u64); - } - - // Process hints - p.process_hints(&data, false).unwrap(); - - // Use watchdog to detect hang - let completed = Arc::new(AtomicBool::new(false)); - let completed_clone = Arc::clone(&completed); - - let watchdog = thread::spawn(move || { - let start = Instant::now(); - while !completed_clone.load(Ordering::Acquire) { - if start.elapsed() > Duration::from_millis(TIMEOUT_MS) { - return false; // Timeout - race condition triggered! - } - thread::sleep(Duration::from_millis(10)); - } - true // Completed in time - }); - - // Wait for completion - let result = p.wait_for_completion(); - completed.store(true, Ordering::Release); - - let watchdog_ok = watchdog.join().unwrap(); - assert!( - watchdog_ok, - "RACE CONDITION DETECTED: Iteration {} hung for >{}ms. \ - Drainer missed worker notifications.", - iteration, - TIMEOUT_MS - ); - - assert!(result.is_ok(), "Iteration {} failed: {:?}", iteration, result); - - // Verify all hints were processed - let count = received_count.load(Ordering::SeqCst); - assert_eq!( - count, HINTS_PER_BATCH, - "Iteration {}: Expected {} results, got {}", - iteration, HINTS_PER_BATCH, count - ); - } - - println!( - "\n========================================\n\ - Race Condition Regression Test PASSED\n\ - Iterations: {}\n\ - Hints per iteration: {}\n\ - Worker threads: {}\n\ - No hangs detected!\n\ - ========================================\n", - ITERATIONS, HINTS_PER_BATCH, NUM_THREADS - ); - } } diff --git a/precompiles/hints/src/lib.rs b/precompiles/hints/src/lib.rs index 82eefa35c..3460a87b5 100644 --- a/precompiles/hints/src/lib.rs +++ b/precompiles/hints/src/lib.rs @@ -1,3 +1,5 @@ mod hints_processor; +mod ordered_result_buffer; pub use hints_processor::HintsProcessor; +pub use ordered_result_buffer::{BufferStatus, OrderedResultBuffer}; diff --git a/precompiles/hints/src/ordered_result_buffer.rs b/precompiles/hints/src/ordered_result_buffer.rs new file mode 100644 index 000000000..4db159b6f --- /dev/null +++ b/precompiles/hints/src/ordered_result_buffer.rs @@ -0,0 +1,490 @@ +//! Ordered Result Buffer +//! +//! A thread-safe buffer that allows out-of-order insertion but guarantees +//! in-order consumption. Used for parallel processing where results must +//! be output in the original submission order. + +use std::collections::VecDeque; +use std::sync::{Condvar, Mutex}; + +/// Status of the buffer +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BufferStatus { + /// Normal operation + Active, + /// Shutdown requested - no more results expected + Shutdown, + /// Error occurred - stop processing + Error, +} + +/// Internal state protected by mutex +struct State { + /// Ring buffer of slots: None = pending, Some = ready + buffer: VecDeque>, + /// Sequence ID of the next result to drain (buffer[0] corresponds to this) + next_drain: usize, + /// Current status + status: BufferStatus, +} + +/// A thread-safe buffer for reordering results from parallel workers. +/// +/// Workers can fill slots out of order using sequence IDs, but consumers +/// always receive results in the original order. +/// +/// # Example +/// +/// ```ignore +/// let buffer = OrderedResultBuffer::new(); +/// +/// // Producer: reserve slots and fill (possibly out of order) +/// let seq1 = buffer.reserve(); // 0 +/// let seq2 = buffer.reserve(); // 1 +/// buffer.fill(seq2, "second"); // Fill out of order +/// buffer.fill(seq1, "first"); +/// +/// // Consumer: always gets results in order +/// assert_eq!(buffer.take_next(), Some("first")); +/// assert_eq!(buffer.take_next(), Some("second")); +/// ``` +pub struct OrderedResultBuffer { + inner: Mutex>, + signal: Condvar, +} + +impl OrderedResultBuffer { + /// Create a new empty buffer. + pub fn new() -> Self { + Self { + inner: Mutex::new(State { + buffer: VecDeque::new(), + next_drain: 0, + status: BufferStatus::Active, + }), + signal: Condvar::new(), + } + } + + /// Reserve a slot in the buffer and return its sequence ID. + /// + /// The caller must later call `fill()` with this sequence ID. + pub fn reserve(&self) -> usize { + let mut state = self.inner.lock().unwrap(); + let seq = state.next_drain + state.buffer.len(); + state.buffer.push_back(None); + seq + } + + /// Reserve a slot and immediately fill it with a value. + /// + /// Equivalent to `reserve()` followed by `fill()`, but atomic. + /// Useful for passthrough items that don't need async processing. + pub fn reserve_and_fill(&self, value: T) -> usize { + let mut state = self.inner.lock().unwrap(); + let seq = state.next_drain + state.buffer.len(); + state.buffer.push_back(Some(value)); + // Notify while holding lock to prevent race + self.signal.notify_all(); + seq + } + + /// Fill a previously reserved slot with a value. + /// + /// # Panics + /// + /// Panics if `seq_id` was not reserved or was already filled. + pub fn fill(&self, seq_id: usize, value: T) { + let mut state = self.inner.lock().unwrap(); + + // Calculate offset in buffer + let offset = seq_id.checked_sub(state.next_drain); + let offset = match offset { + Some(o) if o < state.buffer.len() => o, + _ => { + // Slot was already drained or invalid - ignore + // This can happen after reset() if stale workers complete + return; + } + }; + + // Fill the slot + debug_assert!(state.buffer[offset].is_none(), "Slot {} already filled", seq_id); + state.buffer[offset] = Some(value); + + // Notify while holding lock to prevent race condition + self.signal.notify_all(); + } + + /// Take the next in-order result, blocking until one is ready. + /// + /// Returns `None` if the buffer is shutdown or in error state + /// and no more results are available. + pub fn take_next(&self) -> Option { + let mut state = self.inner.lock().unwrap(); + + loop { + // Check if front slot has a ready result + if let Some(Some(_)) = state.buffer.front() { + // Pop and return the value + let value = state.buffer.pop_front().unwrap().unwrap(); + state.next_drain += 1; + // Notify wait_until_drained that buffer changed + self.signal.notify_all(); + return Some(value); + } + + // No ready result - check if we should stop waiting + match state.status { + BufferStatus::Active => { + // Wait for notification + state = self.signal.wait(state).unwrap(); + } + BufferStatus::Shutdown | BufferStatus::Error => { + // Check one more time after status change + if let Some(Some(_)) = state.buffer.front() { + let value = state.buffer.pop_front().unwrap().unwrap(); + state.next_drain += 1; + self.signal.notify_all(); + return Some(value); + } + return None; + } + } + } + } + + /// Wait until all reserved slots have been drained. + /// + /// Returns the final status (Active means all drained, Error/Shutdown + /// means stopped early). + pub fn wait_until_drained(&self) -> BufferStatus { + let mut state = self.inner.lock().unwrap(); + + loop { + if state.buffer.is_empty() { + return BufferStatus::Active; + } + + if state.status == BufferStatus::Error { + return BufferStatus::Error; + } + + state = self.signal.wait(state).unwrap(); + } + } + + /// Signal an error condition. Wakes all waiting threads. + pub fn set_error(&self) { + let mut state = self.inner.lock().unwrap(); + state.status = BufferStatus::Error; + self.signal.notify_all(); + } + + /// Signal shutdown. Wakes all waiting threads. + pub fn shutdown(&self) { + let mut state = self.inner.lock().unwrap(); + state.status = BufferStatus::Shutdown; + self.signal.notify_all(); + } + + /// Get current status. + pub fn status(&self) -> BufferStatus { + self.inner.lock().unwrap().status + } + + /// Check if buffer is empty (all results drained). + pub fn is_empty(&self) -> bool { + self.inner.lock().unwrap().buffer.is_empty() + } + + /// Get count of pending/ready slots. + pub fn len(&self) -> usize { + self.inner.lock().unwrap().buffer.len() + } + + /// Reset the buffer for reuse. + /// + /// Clears all pending slots and resets sequence counter. + pub fn reset(&self) { + let mut state = self.inner.lock().unwrap(); + state.buffer.clear(); + state.next_drain = 0; + state.status = BufferStatus::Active; + } +} + +impl Default for OrderedResultBuffer { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + use std::thread; + use std::time::{Duration, Instant}; + + #[test] + fn test_basic_order() { + let buf = OrderedResultBuffer::new(); + + let seq0 = buf.reserve(); + let seq1 = buf.reserve(); + let seq2 = buf.reserve(); + + assert_eq!(seq0, 0); + assert_eq!(seq1, 1); + assert_eq!(seq2, 2); + + // Fill out of order + buf.fill(seq2, "third"); + buf.fill(seq0, "first"); + buf.fill(seq1, "second"); + + // Take in order + assert_eq!(buf.take_next(), Some("first")); + assert_eq!(buf.take_next(), Some("second")); + assert_eq!(buf.take_next(), Some("third")); + } + + #[test] + fn test_reserve_and_fill() { + let buf = OrderedResultBuffer::new(); + + buf.reserve_and_fill("immediate"); + let seq = buf.reserve(); + buf.fill(seq, "delayed"); + + assert_eq!(buf.take_next(), Some("immediate")); + assert_eq!(buf.take_next(), Some("delayed")); + } + + #[test] + fn test_blocking_take() { + let buf = Arc::new(OrderedResultBuffer::new()); + let seq = buf.reserve(); + + let buf_clone = Arc::clone(&buf); + let handle = thread::spawn(move || { + thread::sleep(Duration::from_millis(50)); + buf_clone.fill(seq, 42); + }); + + let start = Instant::now(); + let value = buf.take_next(); + let elapsed = start.elapsed(); + + assert_eq!(value, Some(42)); + assert!(elapsed >= Duration::from_millis(40)); + + handle.join().unwrap(); + } + + #[test] + fn test_out_of_order_parallel() { + let buf = Arc::new(OrderedResultBuffer::new()); + let num_items = 100; + + // Reserve all slots + let seqs: Vec<_> = (0..num_items).map(|_| buf.reserve()).collect(); + + // Fill from multiple threads in reverse order + let handles: Vec<_> = seqs + .into_iter() + .rev() + .map(|seq| { + let buf = Arc::clone(&buf); + thread::spawn(move || { + buf.fill(seq, seq * 10); + }) + }) + .collect(); + + // Drain and verify order + for i in 0..num_items { + let value = buf.take_next().unwrap(); + assert_eq!(value, i * 10); + } + + for h in handles { + h.join().unwrap(); + } + } + + #[test] + fn test_shutdown_wakes_waiter() { + let buf = Arc::new(OrderedResultBuffer::::new()); + buf.reserve(); // Reserve but don't fill + + let buf_clone = Arc::clone(&buf); + let handle = thread::spawn(move || { + thread::sleep(Duration::from_millis(50)); + buf_clone.shutdown(); + }); + + let start = Instant::now(); + let result = buf.take_next(); + let elapsed = start.elapsed(); + + assert!(result.is_none()); + assert!(elapsed >= Duration::from_millis(40)); + assert_eq!(buf.status(), BufferStatus::Shutdown); + + handle.join().unwrap(); + } + + #[test] + fn test_error_wakes_waiter() { + let buf = Arc::new(OrderedResultBuffer::::new()); + buf.reserve(); + + let buf_clone = Arc::clone(&buf); + let handle = thread::spawn(move || { + thread::sleep(Duration::from_millis(50)); + buf_clone.set_error(); + }); + + let result = buf.take_next(); + assert!(result.is_none()); + assert_eq!(buf.status(), BufferStatus::Error); + + handle.join().unwrap(); + } + + #[test] + fn test_wait_until_drained() { + let buf = Arc::new(OrderedResultBuffer::new()); + + // Reserve and fill slots + for i in 0..5 { + buf.reserve_and_fill(i); + } + + // Drain in another thread + let buf_clone = Arc::clone(&buf); + let drainer = thread::spawn(move || while let Some(_) = buf_clone.take_next() {}); + + // Wait for drain + let status = buf.wait_until_drained(); + assert_eq!(status, BufferStatus::Active); + assert!(buf.is_empty()); + + buf.shutdown(); + drainer.join().unwrap(); + } + + #[test] + fn test_reset() { + let buf = OrderedResultBuffer::new(); + + buf.reserve_and_fill(1); + buf.reserve_and_fill(2); + buf.set_error(); + + assert_eq!(buf.status(), BufferStatus::Error); + assert_eq!(buf.len(), 2); + + buf.reset(); + + assert_eq!(buf.status(), BufferStatus::Active); + assert!(buf.is_empty()); + + // Can use again + buf.reserve_and_fill(100); + assert_eq!(buf.take_next(), Some(100)); + } + + #[test] + fn test_stale_fill_after_reset() { + let buf = OrderedResultBuffer::new(); + + let seq = buf.reserve(); + buf.reset(); + + // Stale fill should be ignored (not panic) + buf.fill(seq, "stale"); + + assert!(buf.is_empty()); + } + + #[test] + fn test_stress_throughput() { + let buf = Arc::new(OrderedResultBuffer::new()); + let num_items = 100_000; + + let producer_buf = Arc::clone(&buf); + let producer = thread::spawn(move || { + for i in 0..num_items { + producer_buf.reserve_and_fill(i); + } + producer_buf.shutdown(); + }); + + let start = Instant::now(); + let mut count = 0; + while let Some(_) = buf.take_next() { + count += 1; + } + let elapsed = start.elapsed(); + + producer.join().unwrap(); + + assert_eq!(count, num_items); + let ops_per_sec = num_items as f64 / elapsed.as_secs_f64(); + println!( + "OrderedResultBuffer stress: {} ops in {:?} ({:.0} ops/sec)", + num_items, elapsed, ops_per_sec + ); + assert!(ops_per_sec > 100_000.0, "Too slow: {:.0} ops/sec", ops_per_sec); + } + + #[test] + fn test_parallel_stress() { + let buf = Arc::new(OrderedResultBuffer::new()); + let num_items = 10_000; + let num_producers = 8; + + // Reserve all first (single threaded) + let seqs: Vec<_> = (0..num_items).map(|_| buf.reserve()).collect(); + + // Distribute to producers + let chunk_size = num_items / num_producers; + let producers: Vec<_> = (0..num_producers) + .map(|p| { + let buf = Arc::clone(&buf); + let chunk: Vec<_> = seqs[p * chunk_size..(p + 1) * chunk_size].to_vec(); + thread::spawn(move || { + for seq in chunk { + buf.fill(seq, seq * 2); + } + }) + }) + .collect(); + + // Consume + let buf_consumer = Arc::clone(&buf); + let consumer = thread::spawn(move || { + let mut results = Vec::with_capacity(num_items); + for _ in 0..num_items { + if let Some(v) = buf_consumer.take_next() { + results.push(v); + } + } + results + }); + + for p in producers { + p.join().unwrap(); + } + + let results = consumer.join().unwrap(); + + // Verify in-order + for (i, &v) in results.iter().enumerate() { + assert_eq!(v, i * 2, "Out of order at index {}", i); + } + } +} From d5126b615b87198f0ed4c0d51f3438abfe14463f Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 19 Feb 2026 06:22:03 +0000 Subject: [PATCH 562/782] adding dbg messages --- precompiles/hints/src/hints_processor.rs | 444 +++++++++++++--- precompiles/hints/src/lib.rs | 2 - .../hints/src/ordered_result_buffer.rs | 490 ------------------ 3 files changed, 365 insertions(+), 571 deletions(-) delete mode 100644 precompiles/hints/src/ordered_result_buffer.rs diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 43509a964..c4cef4a68 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -4,13 +4,12 @@ //! that are received as a stream of `u64` values. Hints are used to provide preprocessed //! data to precompile operations in the ZisK zkVM. -use crate::ordered_result_buffer::{BufferStatus, OrderedResultBuffer}; use anyhow::Result; use rayon::{ThreadPool, ThreadPoolBuilder}; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Condvar, Mutex}; use std::time::Instant; use tracing::{debug, info}; use zisk_common::io::{StreamProcessor, StreamSink}; @@ -35,6 +34,55 @@ use ziskos_hints::handlers::secp256k1::{ use ziskos_hints::handlers::secp256r1::secp256r1_ecdsa_verify_hint; use ziskos_hints::handlers::sha256::sha256_hint; +/// Ordered result buffer with drain state. +/// +/// This structure maintains a VecDeque that holds processed results in order, +/// allowing out-of-order completion while ensuring in-order output. +struct ResultQueue { + /// The result buffer: None = pending, Some(Ok(...)) = ready, Some(Err(...)) = error + buffer: VecDeque>>>, + /// Sequence ID of the next result to drain from buffer[0] + next_drain_seq: usize, +} + +/// Thread-safe shared state for parallel hint processing. +struct HintProcessorState { + /// Ordered results ready for draining + queue: Mutex, + /// Notifies drainer thread when a hint completes + drain_signal: Condvar, + /// Next sequence ID to assign to incoming hints + next_seq: AtomicUsize, + /// Signals processing should stop + error_flag: AtomicBool, + /// Signals drainer thread to shut down + shutdown: AtomicBool, + /// Invalidates stale workers after reset + generation: AtomicUsize, +} + +impl HintProcessorState { + fn new() -> Self { + Self { + queue: Mutex::new(ResultQueue { buffer: VecDeque::new(), next_drain_seq: 0 }), + drain_signal: Condvar::new(), + next_seq: AtomicUsize::new(0), + error_flag: AtomicBool::new(false), + shutdown: AtomicBool::new(false), + generation: AtomicUsize::new(0), + } + } + + fn reset(&self) { + self.error_flag.store(false, Ordering::Release); + self.next_seq.store(0, Ordering::Relaxed); + self.generation.fetch_add(1, Ordering::SeqCst); + let mut queue = self.queue.lock().unwrap(); + queue.buffer.clear(); + queue.next_drain_seq = 0; + } +} + /// Type alias for custom hint handler functions. pub type CustomHintHandler = Arc Result> + Send + Sync>; @@ -96,20 +144,20 @@ impl HintsProcessorBuilder { .build() .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; - let buffer = Arc::new(OrderedResultBuffer::new()); + let state = Arc::new(HintProcessorState::new()); let hints_sink = self.hints_sink; // Spawn drainer thread - let drainer_buffer = Arc::clone(&buffer); + let drainer_state = Arc::clone(&state); let drainer_sink = Arc::clone(&hints_sink); let drainer_thread = std::thread::spawn(move || { - HintsProcessor::drainer_thread(drainer_buffer, drainer_sink); + HintsProcessor::drainer_thread(drainer_state, drainer_sink); }); Ok(HintsProcessor { pool, num_hint: AtomicUsize::new(0), - buffer, + state, stats: if self.enable_stats { Some(Mutex::new(HashMap::new())) } else { None }, hints_sink, drainer_thread: ManuallyDrop::new(drainer_thread), @@ -132,8 +180,8 @@ pub struct HintsProcessor { num_hint: AtomicUsize, - /// Ordered result buffer for parallel processing with in-order output - buffer: Arc>>>, + /// Shared state for parallel hint processing + state: Arc, /// Optional statistics collected during hint processing (for debugging). stats: Option>>, @@ -213,6 +261,7 @@ impl HintsProcessor { /// * `Ok(false)` - Batch processed successfully, no CTRL_END /// * `Err` - If a previous error occurred or hints are malformed pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { + debug!("[PROCESS] process_hints called with {} u64s, first_batch={}", hints.len(), first_batch); let mut has_ctrl_end = false; // Take any pending partial hint from previous batch @@ -222,7 +271,7 @@ impl HintsProcessor { let mut idx = 0; while idx < hints.len() { // Check for error before processing each hint - if self.buffer.status() == BufferStatus::Error { + if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to previous error")); } let (parsed_hint, consumed) = @@ -319,32 +368,53 @@ impl HintsProcessor { break; } HintCode::Ctrl(CtrlHint::Cancel) => { - // Cancel current stream: set error - self.buffer.set_error(); + // Cancel current stream: set error and notify + self.state.error_flag.store(true, Ordering::Release); + self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream cancelled")); } HintCode::Ctrl(CtrlHint::Error) => { // External error signal - self.buffer.set_error(); + self.state.error_flag.store(true, Ordering::Release); + self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream error signalled")); } _ => {} // Built-in data hint or custom hint; continue processing } - // Handle pass-through hints immediately (no worker needed) - if hint.is_passthrough { - self.buffer.reserve_and_fill(Ok(hint.data.clone())); - idx += length; - continue; - } + // Capture generation outside mutex - SeqCst provides sufficient ordering + let generation = self.state.generation.load(Ordering::SeqCst); + + // Atomically reserve slot - use Relaxed for seq since mutex provides ordering + let seq_id = { + let mut queue = self.state.queue.lock().unwrap(); + let seq = self.state.next_seq.fetch_add(1, Ordering::Relaxed); + + // Handle pass-through hints immediately + if hint.is_passthrough { + queue.buffer.push_back(Some(Ok(hint.data.clone()))); + // Notify immediately while holding the lock to ensure drainer sees the result + // Release lock after this block, avoiding duplicate notification + drop(queue); + // Use notify_all since wait_for_completion also waits on this condvar + self.state.drain_signal.notify_all(); + // Continue to next hint without spawning worker + idx += length; + continue; + } else { + queue.buffer.push_back(None); + } + + seq + }; - // Reserve slot and spawn worker for async processing - let seq_id = self.buffer.reserve(); - let buffer = Arc::clone(&self.buffer); + // Spawn processing task for async hints (Noop already handled above) + let state = Arc::clone(&self.state); let custom_handlers = Arc::clone(&self.custom_handlers); self.pool.spawn(move || { - let result = Self::dispatch_hint(hint, custom_handlers); - buffer.fill(seq_id, result); + debug!("[WORKER] seq={} dispatched", seq_id); + Self::worker_thread(state, hint, generation, seq_id, custom_handlers); + debug!("[WORKER] seq={} done", seq_id); }); idx += length; @@ -387,31 +457,165 @@ impl HintsProcessor { } } - /// Drainer thread that takes results in order and submits to sink. - fn drainer_thread( - buffer: Arc>>>, - hints_sink: Arc, + /// Worker thread that processes a single hint and stores the result. + /// + /// # Arguments + /// + /// * `state` - Shared processor state + /// * `hint` - The hint to process + /// * `generation` - Generation number for detecting stale workers + /// * `seq_id` - Sequence ID for ordering results + /// * `custom_handlers` - Custom hint handlers + fn worker_thread( + state: Arc, + hint: PrecompileHint, + generation: usize, + seq_id: usize, + custom_handlers: Arc>, ) { + // Check generation first to detect stale workers (before processing) + let current_gen = state.generation.load(Ordering::SeqCst); + if generation != current_gen { + debug!("[WORKER] seq={} early return: generation mismatch (worker={}, current={})", seq_id, generation, current_gen); + return; + } + + // Catch panics to prevent permanently-stuck None slots in the buffer. + // If dispatch_hint panics, Rayon catches it silently but the slot would + // stay None forever, blocking the drainer from making progress. + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + if state.error_flag.load(Ordering::Acquire) { + Err(anyhow::anyhow!("Processing stopped due to error")) + } else { + Self::dispatch_hint(hint, custom_handlers) + } + })) + .unwrap_or_else(|panic_info| { + let msg = if let Some(s) = panic_info.downcast_ref::<&str>() { + s.to_string() + } else if let Some(s) = panic_info.downcast_ref::() { + s.clone() + } else { + "unknown panic".to_string() + }; + Err(anyhow::anyhow!("Worker panicked processing hint: {}", msg)) + }); + + debug!("[WORKER] seq={} processed, acquiring lock", seq_id); + + // Store result - MUST fill slot even if error occurred + let mut queue = state.queue.lock().unwrap(); + + // Check generation again in case reset happened during processing + let current_gen = state.generation.load(Ordering::SeqCst); + if generation != current_gen { + debug!("[WORKER] seq={} early return: generation mismatch after processing", seq_id); + return; + } + + // Calculate offset in buffer; handle drained slots + if seq_id < queue.next_drain_seq { + debug!("[WORKER] seq={} early return: seq_id < next_drain_seq ({})", seq_id, queue.next_drain_seq); + return; + } + let offset = seq_id - queue.next_drain_seq; + + // Check if slot exists - if not, drainer already processed and removed it + if offset >= queue.buffer.len() { + debug!("[WORKER] seq={} early return: offset {} >= buffer.len() {}", seq_id, offset, queue.buffer.len()); + return; + } + + // Fill the slot to allow drainer to proceed (critical for ordering) + queue.buffer[offset] = Some(result); + debug!("[WORKER] seq={} filled slot at offset {}, buffer_len={}", seq_id, offset, queue.buffer.len()); + + // Release lock before notifying + drop(queue); + + // Notify drainer thread (use notify_all to wake any waiting threads) + debug!("[WORKER] seq={} calling notify_all", seq_id); + state.drain_signal.notify_all(); + } + + /// Drainer thread that waits for hints to complete and drains ready results from queue. + fn drainer_thread(state: Arc, hints_sink: Arc) { debug!("[DRAINER] Thread started"); + loop { + debug!("[DRAINER] Attempting to acquire queue lock"); + let mut queue = state.queue.lock().unwrap(); + debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", + queue.buffer.len(), queue.next_drain_seq); + + // Check for shutdown + if state.shutdown.load(Ordering::Acquire) { + debug!("[DRAINER] Shutdown signal received, exiting"); + break; + } - while let Some(result) = buffer.take_next() { - match result { - Ok(data) => { - debug!("[DRAINER] Submitting {} u64s", data.len()); - if let Err(e) = hints_sink.submit(data) { - eprintln!("Error submitting to sink: {}", e); - buffer.set_error(); + // Drain all consecutive ready results from the front + let mut drained_any = false; + while let Some(Some(res)) = queue.buffer.front() { + drained_any = true; + let current_seq = queue.next_drain_seq; + match res { + Ok(data) => { + // Clone data before dropping lock + debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); + let data_to_submit = data.clone(); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; + + // Drop lock before submitting to avoid blocking workers + debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); + drop(queue); + + // Submit to sink + debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); + if let Err(e) = hints_sink.submit(data_to_submit) { + eprintln!("Error submitting to sink: {}", e); + state.error_flag.store(true, Ordering::Release); + state.drain_signal.notify_all(); + return; + } + debug!("[DRAINER] Completed submit seq={}", current_seq); + + // Re-acquire lock for next iteration + debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); + queue = state.queue.lock().unwrap(); + } + Err(e) => { + // Error found - signal to stop + state.error_flag.store(true, Ordering::Release); + eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; + state.drain_signal.notify_all(); return; } } - Err(e) => { - eprintln!("Hint processing error: {}", e); - buffer.set_error(); - return; - } } - } + // If we drained any results, notify wait_for_completion that buffer changed + if drained_any { + debug!("[DRAINER] Notifying wait_for_completion"); + state.drain_signal.notify_all(); + } + + // Check for shutdown again before waiting + if state.shutdown.load(Ordering::Acquire) { + debug!("[DRAINER] Shutdown signal received before wait, exiting"); + break; + } + + // Wait for notification that a hint completed + debug!("[DRAINER] Waiting on drain_signal condvar"); + #[allow(unused_assignments)] + { + queue = state.drain_signal.wait(queue).unwrap(); + } + debug!("[DRAINER] Woke up from condvar wait"); + } debug!("[DRAINER] Thread exiting"); } @@ -425,10 +629,20 @@ impl HintsProcessor { /// * `Ok(())` - All hints processed successfully /// * `Err` - If an error occurred during processing pub fn wait_for_completion(&self) -> Result<()> { - let status = self.buffer.wait_until_drained(); - if status == BufferStatus::Error { + let mut queue = self.state.queue.lock().unwrap(); + + while !queue.buffer.is_empty() { + if self.state.error_flag.load(Ordering::Acquire) { + return Err(anyhow::anyhow!("Processing stopped due to error")); + } + // Wait for notification that buffer state changed + queue = self.state.drain_signal.wait(queue).unwrap(); + } + + if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to error")); } + Ok(()) } @@ -509,7 +723,7 @@ impl HintsProcessor { fn reset(&self) { self.num_hint.store(0, Ordering::Relaxed); - self.buffer.reset(); + self.state.reset(); if let Some(stats) = self.stats.as_ref() { stats.lock().unwrap().clear(); } @@ -527,7 +741,8 @@ impl HintsProcessor { impl Drop for HintsProcessor { fn drop(&mut self) { // Signal drainer thread to shut down - self.buffer.shutdown(); + self.state.shutdown.store(true, Ordering::Release); + self.state.drain_signal.notify_all(); // Join the drainer thread to ensure clean shutdown // Safety: We only take the value once in drop @@ -599,7 +814,9 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Buffer should be empty after completion - assert!(p.buffer.is_empty()); + let queue = p.state.queue.lock().unwrap(); + assert!(queue.buffer.is_empty()); + assert_eq!(queue.next_drain_seq, 1); } #[test] @@ -617,8 +834,10 @@ mod tests { assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Verify all hints were processed (buffer empty) - assert!(p.buffer.is_empty()); + // Verify all hints were processed (buffer empty, next_drain_seq advanced) + let queue = p.state.queue.lock().unwrap(); + assert!(queue.buffer.is_empty()); + assert_eq!(queue.next_drain_seq, 3); } #[test] @@ -632,8 +851,10 @@ mod tests { assert!(p.process_hints(&data2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Verify all hints were processed - assert!(p.buffer.is_empty()); + // Verify sequence continued across calls + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 2); + assert!(queue.buffer.is_empty()); } #[test] @@ -644,7 +865,8 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // No hints processed - assert!(p.buffer.is_empty()); + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); } // Negative tests @@ -684,14 +906,15 @@ mod tests { // Reset should clear any error state p.reset(); - assert_eq!(p.buffer.status(), BufferStatus::Active); + assert!(!p.state.error_flag.load(Ordering::Acquire)); // Should be able to process new hints after reset (8 bytes = 1 u64) let good = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x42]; assert!(p.process_hints(&good, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - assert!(p.buffer.is_empty()); + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); } #[test] @@ -716,7 +939,11 @@ mod tests { p.process_hints(&end, false).unwrap(); // Buffer should already be empty - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert!(queue.buffer.is_empty()); + assert_eq!(queue.next_drain_seq, 2); + } // Explicit wait should be instant assert!(p.wait_for_completion().is_ok()); @@ -732,7 +959,7 @@ mod tests { assert!(result.unwrap_err().to_string().contains("cancelled")); // Error flag should be set - assert_eq!(p.buffer.status(), BufferStatus::Error); + assert!(p.state.error_flag.load(Ordering::Acquire)); } #[test] @@ -745,7 +972,7 @@ mod tests { assert!(result.unwrap_err().to_string().contains("error")); // Error flag should be set - assert_eq!(p.buffer.status(), BufferStatus::Error); + assert!(p.state.error_flag.load(Ordering::Acquire)); } #[test] @@ -874,7 +1101,7 @@ mod tests { // Wait should detect the error from drainer thread std::thread::sleep(std::time::Duration::from_millis(100)); - assert_eq!(p.buffer.status(), BufferStatus::Error); + assert!(p.state.error_flag.load(Ordering::Acquire)); } // Builder tests @@ -1114,14 +1341,22 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Verify no results yet (hint is partial) - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + assert!(queue.buffer.is_empty()); + } // Second batch completes the hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result (buffer drained) - assert!(p.buffer.is_empty()); + // Now we should have the complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1138,14 +1373,21 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Verify no results yet - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + } // Second batch completes the hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result (buffer drained) - assert!(p.buffer.is_empty()); + // Now we should have the complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1167,14 +1409,21 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Still no complete results - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + } // Third batch completes the hint assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result (buffer drained) - assert!(p.buffer.is_empty()); + // Now we should have the complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1198,18 +1447,31 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // No results yet - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + } // Complete first hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); + // Should have first result now + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + } + // Complete second hint assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have both results (buffer drained) - assert!(p.buffer.is_empty()); + // Should have both results + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 2); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1229,12 +1491,22 @@ mod tests { assert!(p.process_hints(&batch1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); + // Should have result for complete hint + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + } + // Complete the partial hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have both results (buffer drained) - assert!(p.buffer.is_empty()); + // Should have both results + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 2); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1248,8 +1520,12 @@ mod tests { assert!(p.process_hints(&batch1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have result (buffer drained) - assert!(p.buffer.is_empty()); + // Should have result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1276,8 +1552,12 @@ mod tests { // End stream should wait for all hints to complete assert!(p.process_hints(&end, false).is_ok()); - // Everything should be processed (buffer drained) - assert!(p.buffer.is_empty()); + // Everything should be processed + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1309,8 +1589,10 @@ mod tests { assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Buffer should be drained - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + } } #[test] @@ -1334,8 +1616,12 @@ mod tests { assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have complete result (buffer drained) - assert!(p.buffer.is_empty()); + // Should have complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] diff --git a/precompiles/hints/src/lib.rs b/precompiles/hints/src/lib.rs index 3460a87b5..82eefa35c 100644 --- a/precompiles/hints/src/lib.rs +++ b/precompiles/hints/src/lib.rs @@ -1,5 +1,3 @@ mod hints_processor; -mod ordered_result_buffer; pub use hints_processor::HintsProcessor; -pub use ordered_result_buffer::{BufferStatus, OrderedResultBuffer}; diff --git a/precompiles/hints/src/ordered_result_buffer.rs b/precompiles/hints/src/ordered_result_buffer.rs deleted file mode 100644 index 4db159b6f..000000000 --- a/precompiles/hints/src/ordered_result_buffer.rs +++ /dev/null @@ -1,490 +0,0 @@ -//! Ordered Result Buffer -//! -//! A thread-safe buffer that allows out-of-order insertion but guarantees -//! in-order consumption. Used for parallel processing where results must -//! be output in the original submission order. - -use std::collections::VecDeque; -use std::sync::{Condvar, Mutex}; - -/// Status of the buffer -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BufferStatus { - /// Normal operation - Active, - /// Shutdown requested - no more results expected - Shutdown, - /// Error occurred - stop processing - Error, -} - -/// Internal state protected by mutex -struct State { - /// Ring buffer of slots: None = pending, Some = ready - buffer: VecDeque>, - /// Sequence ID of the next result to drain (buffer[0] corresponds to this) - next_drain: usize, - /// Current status - status: BufferStatus, -} - -/// A thread-safe buffer for reordering results from parallel workers. -/// -/// Workers can fill slots out of order using sequence IDs, but consumers -/// always receive results in the original order. -/// -/// # Example -/// -/// ```ignore -/// let buffer = OrderedResultBuffer::new(); -/// -/// // Producer: reserve slots and fill (possibly out of order) -/// let seq1 = buffer.reserve(); // 0 -/// let seq2 = buffer.reserve(); // 1 -/// buffer.fill(seq2, "second"); // Fill out of order -/// buffer.fill(seq1, "first"); -/// -/// // Consumer: always gets results in order -/// assert_eq!(buffer.take_next(), Some("first")); -/// assert_eq!(buffer.take_next(), Some("second")); -/// ``` -pub struct OrderedResultBuffer { - inner: Mutex>, - signal: Condvar, -} - -impl OrderedResultBuffer { - /// Create a new empty buffer. - pub fn new() -> Self { - Self { - inner: Mutex::new(State { - buffer: VecDeque::new(), - next_drain: 0, - status: BufferStatus::Active, - }), - signal: Condvar::new(), - } - } - - /// Reserve a slot in the buffer and return its sequence ID. - /// - /// The caller must later call `fill()` with this sequence ID. - pub fn reserve(&self) -> usize { - let mut state = self.inner.lock().unwrap(); - let seq = state.next_drain + state.buffer.len(); - state.buffer.push_back(None); - seq - } - - /// Reserve a slot and immediately fill it with a value. - /// - /// Equivalent to `reserve()` followed by `fill()`, but atomic. - /// Useful for passthrough items that don't need async processing. - pub fn reserve_and_fill(&self, value: T) -> usize { - let mut state = self.inner.lock().unwrap(); - let seq = state.next_drain + state.buffer.len(); - state.buffer.push_back(Some(value)); - // Notify while holding lock to prevent race - self.signal.notify_all(); - seq - } - - /// Fill a previously reserved slot with a value. - /// - /// # Panics - /// - /// Panics if `seq_id` was not reserved or was already filled. - pub fn fill(&self, seq_id: usize, value: T) { - let mut state = self.inner.lock().unwrap(); - - // Calculate offset in buffer - let offset = seq_id.checked_sub(state.next_drain); - let offset = match offset { - Some(o) if o < state.buffer.len() => o, - _ => { - // Slot was already drained or invalid - ignore - // This can happen after reset() if stale workers complete - return; - } - }; - - // Fill the slot - debug_assert!(state.buffer[offset].is_none(), "Slot {} already filled", seq_id); - state.buffer[offset] = Some(value); - - // Notify while holding lock to prevent race condition - self.signal.notify_all(); - } - - /// Take the next in-order result, blocking until one is ready. - /// - /// Returns `None` if the buffer is shutdown or in error state - /// and no more results are available. - pub fn take_next(&self) -> Option { - let mut state = self.inner.lock().unwrap(); - - loop { - // Check if front slot has a ready result - if let Some(Some(_)) = state.buffer.front() { - // Pop and return the value - let value = state.buffer.pop_front().unwrap().unwrap(); - state.next_drain += 1; - // Notify wait_until_drained that buffer changed - self.signal.notify_all(); - return Some(value); - } - - // No ready result - check if we should stop waiting - match state.status { - BufferStatus::Active => { - // Wait for notification - state = self.signal.wait(state).unwrap(); - } - BufferStatus::Shutdown | BufferStatus::Error => { - // Check one more time after status change - if let Some(Some(_)) = state.buffer.front() { - let value = state.buffer.pop_front().unwrap().unwrap(); - state.next_drain += 1; - self.signal.notify_all(); - return Some(value); - } - return None; - } - } - } - } - - /// Wait until all reserved slots have been drained. - /// - /// Returns the final status (Active means all drained, Error/Shutdown - /// means stopped early). - pub fn wait_until_drained(&self) -> BufferStatus { - let mut state = self.inner.lock().unwrap(); - - loop { - if state.buffer.is_empty() { - return BufferStatus::Active; - } - - if state.status == BufferStatus::Error { - return BufferStatus::Error; - } - - state = self.signal.wait(state).unwrap(); - } - } - - /// Signal an error condition. Wakes all waiting threads. - pub fn set_error(&self) { - let mut state = self.inner.lock().unwrap(); - state.status = BufferStatus::Error; - self.signal.notify_all(); - } - - /// Signal shutdown. Wakes all waiting threads. - pub fn shutdown(&self) { - let mut state = self.inner.lock().unwrap(); - state.status = BufferStatus::Shutdown; - self.signal.notify_all(); - } - - /// Get current status. - pub fn status(&self) -> BufferStatus { - self.inner.lock().unwrap().status - } - - /// Check if buffer is empty (all results drained). - pub fn is_empty(&self) -> bool { - self.inner.lock().unwrap().buffer.is_empty() - } - - /// Get count of pending/ready slots. - pub fn len(&self) -> usize { - self.inner.lock().unwrap().buffer.len() - } - - /// Reset the buffer for reuse. - /// - /// Clears all pending slots and resets sequence counter. - pub fn reset(&self) { - let mut state = self.inner.lock().unwrap(); - state.buffer.clear(); - state.next_drain = 0; - state.status = BufferStatus::Active; - } -} - -impl Default for OrderedResultBuffer { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::sync::Arc; - use std::thread; - use std::time::{Duration, Instant}; - - #[test] - fn test_basic_order() { - let buf = OrderedResultBuffer::new(); - - let seq0 = buf.reserve(); - let seq1 = buf.reserve(); - let seq2 = buf.reserve(); - - assert_eq!(seq0, 0); - assert_eq!(seq1, 1); - assert_eq!(seq2, 2); - - // Fill out of order - buf.fill(seq2, "third"); - buf.fill(seq0, "first"); - buf.fill(seq1, "second"); - - // Take in order - assert_eq!(buf.take_next(), Some("first")); - assert_eq!(buf.take_next(), Some("second")); - assert_eq!(buf.take_next(), Some("third")); - } - - #[test] - fn test_reserve_and_fill() { - let buf = OrderedResultBuffer::new(); - - buf.reserve_and_fill("immediate"); - let seq = buf.reserve(); - buf.fill(seq, "delayed"); - - assert_eq!(buf.take_next(), Some("immediate")); - assert_eq!(buf.take_next(), Some("delayed")); - } - - #[test] - fn test_blocking_take() { - let buf = Arc::new(OrderedResultBuffer::new()); - let seq = buf.reserve(); - - let buf_clone = Arc::clone(&buf); - let handle = thread::spawn(move || { - thread::sleep(Duration::from_millis(50)); - buf_clone.fill(seq, 42); - }); - - let start = Instant::now(); - let value = buf.take_next(); - let elapsed = start.elapsed(); - - assert_eq!(value, Some(42)); - assert!(elapsed >= Duration::from_millis(40)); - - handle.join().unwrap(); - } - - #[test] - fn test_out_of_order_parallel() { - let buf = Arc::new(OrderedResultBuffer::new()); - let num_items = 100; - - // Reserve all slots - let seqs: Vec<_> = (0..num_items).map(|_| buf.reserve()).collect(); - - // Fill from multiple threads in reverse order - let handles: Vec<_> = seqs - .into_iter() - .rev() - .map(|seq| { - let buf = Arc::clone(&buf); - thread::spawn(move || { - buf.fill(seq, seq * 10); - }) - }) - .collect(); - - // Drain and verify order - for i in 0..num_items { - let value = buf.take_next().unwrap(); - assert_eq!(value, i * 10); - } - - for h in handles { - h.join().unwrap(); - } - } - - #[test] - fn test_shutdown_wakes_waiter() { - let buf = Arc::new(OrderedResultBuffer::::new()); - buf.reserve(); // Reserve but don't fill - - let buf_clone = Arc::clone(&buf); - let handle = thread::spawn(move || { - thread::sleep(Duration::from_millis(50)); - buf_clone.shutdown(); - }); - - let start = Instant::now(); - let result = buf.take_next(); - let elapsed = start.elapsed(); - - assert!(result.is_none()); - assert!(elapsed >= Duration::from_millis(40)); - assert_eq!(buf.status(), BufferStatus::Shutdown); - - handle.join().unwrap(); - } - - #[test] - fn test_error_wakes_waiter() { - let buf = Arc::new(OrderedResultBuffer::::new()); - buf.reserve(); - - let buf_clone = Arc::clone(&buf); - let handle = thread::spawn(move || { - thread::sleep(Duration::from_millis(50)); - buf_clone.set_error(); - }); - - let result = buf.take_next(); - assert!(result.is_none()); - assert_eq!(buf.status(), BufferStatus::Error); - - handle.join().unwrap(); - } - - #[test] - fn test_wait_until_drained() { - let buf = Arc::new(OrderedResultBuffer::new()); - - // Reserve and fill slots - for i in 0..5 { - buf.reserve_and_fill(i); - } - - // Drain in another thread - let buf_clone = Arc::clone(&buf); - let drainer = thread::spawn(move || while let Some(_) = buf_clone.take_next() {}); - - // Wait for drain - let status = buf.wait_until_drained(); - assert_eq!(status, BufferStatus::Active); - assert!(buf.is_empty()); - - buf.shutdown(); - drainer.join().unwrap(); - } - - #[test] - fn test_reset() { - let buf = OrderedResultBuffer::new(); - - buf.reserve_and_fill(1); - buf.reserve_and_fill(2); - buf.set_error(); - - assert_eq!(buf.status(), BufferStatus::Error); - assert_eq!(buf.len(), 2); - - buf.reset(); - - assert_eq!(buf.status(), BufferStatus::Active); - assert!(buf.is_empty()); - - // Can use again - buf.reserve_and_fill(100); - assert_eq!(buf.take_next(), Some(100)); - } - - #[test] - fn test_stale_fill_after_reset() { - let buf = OrderedResultBuffer::new(); - - let seq = buf.reserve(); - buf.reset(); - - // Stale fill should be ignored (not panic) - buf.fill(seq, "stale"); - - assert!(buf.is_empty()); - } - - #[test] - fn test_stress_throughput() { - let buf = Arc::new(OrderedResultBuffer::new()); - let num_items = 100_000; - - let producer_buf = Arc::clone(&buf); - let producer = thread::spawn(move || { - for i in 0..num_items { - producer_buf.reserve_and_fill(i); - } - producer_buf.shutdown(); - }); - - let start = Instant::now(); - let mut count = 0; - while let Some(_) = buf.take_next() { - count += 1; - } - let elapsed = start.elapsed(); - - producer.join().unwrap(); - - assert_eq!(count, num_items); - let ops_per_sec = num_items as f64 / elapsed.as_secs_f64(); - println!( - "OrderedResultBuffer stress: {} ops in {:?} ({:.0} ops/sec)", - num_items, elapsed, ops_per_sec - ); - assert!(ops_per_sec > 100_000.0, "Too slow: {:.0} ops/sec", ops_per_sec); - } - - #[test] - fn test_parallel_stress() { - let buf = Arc::new(OrderedResultBuffer::new()); - let num_items = 10_000; - let num_producers = 8; - - // Reserve all first (single threaded) - let seqs: Vec<_> = (0..num_items).map(|_| buf.reserve()).collect(); - - // Distribute to producers - let chunk_size = num_items / num_producers; - let producers: Vec<_> = (0..num_producers) - .map(|p| { - let buf = Arc::clone(&buf); - let chunk: Vec<_> = seqs[p * chunk_size..(p + 1) * chunk_size].to_vec(); - thread::spawn(move || { - for seq in chunk { - buf.fill(seq, seq * 2); - } - }) - }) - .collect(); - - // Consume - let buf_consumer = Arc::clone(&buf); - let consumer = thread::spawn(move || { - let mut results = Vec::with_capacity(num_items); - for _ in 0..num_items { - if let Some(v) = buf_consumer.take_next() { - results.push(v); - } - } - results - }); - - for p in producers { - p.join().unwrap(); - } - - let results = consumer.join().unwrap(); - - // Verify in-order - for (i, &v) in results.iter().enumerate() { - assert_eq!(v, i * 2, "Out of order at index {}", i); - } - } -} From 4a438015f54609f03bf8debbdcf93413fb0f193f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Thu, 19 Feb 2026 07:25:14 +0100 Subject: [PATCH 563/782] Feature/multiple guest program 0.16.0 (#790) * Now providing pk when calling sdk methods * Adding use hints to program pk * First commit refactoring executor to decouple responsbilities * Minor changes * Working on more examples * Keep working * Fix * Fix Mac * Fix emu asm stub * Cargo fmt * Adding multiple program example. Emulator working. Assembly pending * Assembly working. Not viable for multiple setups * Fix Mac compilation * More fixes * Cargo clippy * Limiting to one prover client * Cargo update * Small fixes * Cargo update --------- Co-authored-by: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> --- Cargo.lock | 70 +- Cargo.toml | 2 - cli/src/commands/execute.rs | 9 +- cli/src/commands/prove.rs | 8 +- cli/src/commands/stats.rs | 7 +- cli/src/commands/verify_constraints.rs | 8 +- common/src/io/stream/zisk_stream.rs | 19 +- common/src/types.rs | 75 ++ distributed/crates/worker/src/worker.rs | 16 +- .../asm-runner/src/asm_services/services.rs | 6 + examples/Cargo.lock | 96 +- examples/Cargo.toml | 2 +- examples/multiple-programs/guest/Cargo.lock | 293 +++++ examples/multiple-programs/guest/Cargo.toml | 8 + examples/multiple-programs/guest/src/main.rs | 26 + examples/multiple-programs/guest_2/Cargo.lock | 293 +++++ examples/multiple-programs/guest_2/Cargo.toml | 8 + .../multiple-programs/guest_2/src/main.rs | 26 + examples/multiple-programs/host/Cargo.toml | 18 + examples/multiple-programs/host/build.rs | 4 + examples/multiple-programs/host/src/main.rs | 67 ++ examples/sha-hasher/guest/Cargo.toml | 2 +- examples/sha-hasher/host/Cargo.toml | 2 +- examples/sha-hasher/host/bin/compressed.rs | 8 +- examples/sha-hasher/host/bin/execute.rs | 4 +- examples/sha-hasher/host/bin/plonk.rs | 6 +- examples/sha-hasher/host/bin/prove.rs | 4 +- .../sha-hasher/host/bin/verify-constraints.rs | 4 +- examples/sha-hasher/host/build.rs | 2 +- examples/sha-hasher/host/src/main.rs | 8 +- executor/Cargo.toml | 1 + {witness-computation => executor}/NEW_SM.md | 0 executor/src/air_classifier.rs | 70 ++ executor/src/asm_resources.rs | 129 +++ executor/src/collector.rs | 345 ++++++ executor/src/emu_asm.rs | 142 +-- executor/src/emu_asm_stub.rs | 25 +- executor/src/emu_rust.rs | 38 +- executor/src/executor.rs | 997 +++--------------- executor/src/lib.rs | 63 +- executor/src/planner.rs | 153 +++ executor/src/registry.rs | 154 +++ executor/src/rom_executor.rs | 105 ++ executor/src/sm_static_bundle.rs | 23 +- executor/src/state.rs | 127 +++ executor/src/utils.rs | 192 ++++ executor/src/witness_generator.rs | 139 +++ executor/src/witness_orchestrator.rs | 366 +++++++ sdk/Cargo.toml | 5 +- sdk/src/client.rs | 10 + sdk/src/lib.rs | 3 +- sdk/src/prover/asm.rs | 166 +-- sdk/src/prover/backend.rs | 162 +-- sdk/src/prover/emu.rs | 89 +- sdk/src/prover/mod.rs | 102 +- sdk/src/zisk_lib_loader.rs | 61 -- state-machines/rom/src/rom.rs | 15 +- witness-computation/Cargo.toml | 50 - witness-computation/README.md | 62 -- witness-computation/rom/input.bin | Bin 8 -> 0 bytes witness-computation/rom/zisk.elf | Bin 490192 -> 0 bytes witness-computation/src/lib.rs | 3 - witness-computation/src/zisk_lib.rs | 299 ------ 63 files changed, 3382 insertions(+), 1815 deletions(-) create mode 100644 examples/multiple-programs/guest/Cargo.lock create mode 100644 examples/multiple-programs/guest/Cargo.toml create mode 100644 examples/multiple-programs/guest/src/main.rs create mode 100644 examples/multiple-programs/guest_2/Cargo.lock create mode 100644 examples/multiple-programs/guest_2/Cargo.toml create mode 100644 examples/multiple-programs/guest_2/src/main.rs create mode 100644 examples/multiple-programs/host/Cargo.toml create mode 100644 examples/multiple-programs/host/build.rs create mode 100644 examples/multiple-programs/host/src/main.rs rename {witness-computation => executor}/NEW_SM.md (100%) create mode 100644 executor/src/air_classifier.rs create mode 100644 executor/src/asm_resources.rs create mode 100644 executor/src/collector.rs create mode 100644 executor/src/planner.rs create mode 100644 executor/src/registry.rs create mode 100644 executor/src/rom_executor.rs create mode 100644 executor/src/state.rs create mode 100644 executor/src/utils.rs create mode 100644 executor/src/witness_generator.rs create mode 100644 executor/src/witness_orchestrator.rs delete mode 100644 sdk/src/zisk_lib_loader.rs delete mode 100644 witness-computation/Cargo.toml delete mode 100644 witness-computation/README.md delete mode 100644 witness-computation/rom/input.bin delete mode 100755 witness-computation/rom/zisk.elf delete mode 100644 witness-computation/src/lib.rs delete mode 100644 witness-computation/src/zisk_lib.rs diff --git a/Cargo.lock b/Cargo.lock index 95417c9ff..1e7c8fa2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -393,9 +393,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.4" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" dependencies = [ "aws-lc-sys", "zeroize", @@ -558,9 +558,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5c6f81257d10a0f602a294ae4182251151ff97dbb504ef9afcdda4a64b24d9b4" [[package]] name = "bytemuck" @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "fields", "num-bigint", @@ -1428,6 +1428,7 @@ dependencies = [ "precomp-poseidon2", "precomp-sha256f", "precompiles-common", + "precompiles-hints", "proofman", "proofman-common", "proofman-util", @@ -1467,7 +1468,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "cfg-if", "num-bigint", @@ -2771,7 +2772,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "colored", "fields", @@ -3133,7 +3134,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "bincode", "blake3", @@ -3169,7 +3170,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "bincode", "borsh", @@ -3201,7 +3202,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "fields", "itoa", @@ -3214,7 +3215,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "proc-macro2", "quote", @@ -3224,7 +3225,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "crossbeam-channel", "tracing", @@ -3233,7 +3234,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "bincode", "bytemuck", @@ -3245,7 +3246,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "bytemuck", "fields", @@ -5720,7 +5721,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "colored", "fields", @@ -6091,7 +6092,6 @@ dependencies = [ "zisk-core", "zisk-distributed-common", "zisk-verifier", - "zisk-witness", "ziskemu", ] @@ -6104,44 +6104,6 @@ dependencies = [ "proofman-verifier", ] -[[package]] -name = "zisk-witness" -version = "0.16.0" -dependencies = [ - "anyhow", - "asm-runner", - "data-bus", - "executor", - "fields", - "mem-common", - "pil-std-lib", - "precomp-arith-eq", - "precomp-arith-eq-384", - "precomp-big-int", - "precomp-dma", - "precomp-keccakf", - "precomp-poseidon2", - "precomp-sha256f", - "precompiles-hints", - "proofman", - "proofman-common", - "proofman-macros", - "proofman-util", - "rayon", - "sm-arith", - "sm-binary", - "sm-frequent-ops", - "sm-main", - "sm-mem", - "sm-rom", - "tracing", - "witness", - "zisk-common", - "zisk-core", - "zisk-pil", - "ziskemu", -] - [[package]] name = "ziskclib" version = "0.16.0" diff --git a/Cargo.toml b/Cargo.toml index 3772a83d3..bb19c7b67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ members = [ "state-machines/mem", "state-machines/mem-cpp", "state-machines/rom", - "witness-computation", "ziskos/entrypoint", "ziskos-hints", "precompiles/arith_eq", @@ -93,7 +92,6 @@ sm-frequent-ops = { path = "state-machines/frequent-ops" } mem-common = { path = "state-machines/mem-common" } mem-planner-cpp = { path = "state-machines/mem-cpp" } sm-rom = { path = "state-machines/rom" } -zisk-witness = { path = "witness-computation" } ziskclib = { path = "ziskclib" } ziskos = { path = "ziskos/entrypoint" } ziskos-hints = { path = "ziskos-hints" } diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index ef86c9e02..5cd0150b7 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -129,9 +129,8 @@ impl ZiskExecute { .build()?; let elf = ElfBinaryFromFile::new(&self.elf, false)?; - - prover.setup(&elf)?; - prover.execute(stdin) + let (pk, _) = prover.setup(&elf)?; + prover.execute(&pk, stdin) } pub fn run_asm( @@ -152,10 +151,10 @@ impl ZiskExecute { .build()?; let elf = ElfBinaryFromFile::new(&self.elf, hints_stream.is_some())?; - prover.setup(&elf)?; + let (pk, _) = prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { prover.set_hints_stream(hints_stream)?; } - prover.execute(stdin) + prover.execute(&pk, stdin) } } diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index c50e216a4..4fe154a3d 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -226,7 +226,7 @@ impl ZiskProve { .build()?; let elf = ElfBinaryFromFile::new(&self.elf, false)?; - prover.setup(&elf)?; + let (pk, _) = prover.setup(&elf)?; let proof_options = ProofOpts { aggregation: self.aggregation, @@ -239,7 +239,7 @@ impl ZiskProve { let world_rank = prover.world_rank(); - let mut prover = prover.prove(stdin).with_proof_options(proof_options); + let mut prover = prover.prove(&pk, stdin).with_proof_options(proof_options); if self.snark { prover = prover.plonk(); } @@ -273,7 +273,7 @@ impl ZiskProve { .build()?; let elf = ElfBinaryFromFile::new(&self.elf, hints_stream.is_some())?; - prover.setup(&elf)?; + let (pk, _) = prover.setup(&elf)?; let proof_options = ProofOpts { aggregation: self.aggregation, @@ -290,7 +290,7 @@ impl ZiskProve { let world_rank = prover.world_rank(); - let mut prover = prover.prove(stdin).with_proof_options(proof_options); + let mut prover = prover.prove(&pk, stdin).with_proof_options(proof_options); if self.snark { prover = prover.plonk(); } diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index af2d3fb28..4f84d4d77 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -161,9 +161,10 @@ impl ZiskStats { .build()?; let elf = ElfBinaryFromFile::new(&self.elf, false)?; - prover.setup(&elf)?; + let (pk, _) = prover.setup(&elf)?; prover.stats( + &pk, stdin, self.debug.clone(), self.minimal_memory, @@ -189,13 +190,13 @@ impl ZiskStats { .build()?; let elf = ElfBinaryFromFile::new(&self.elf, hints_stream.is_some())?; - prover.setup(&elf)?; + let (pk, _) = prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { prover.set_hints_stream(hints_stream)?; } let mpi_node = self.mpi_node.map(|n| n as u32); - prover.stats(stdin, self.debug.clone(), self.minimal_memory, mpi_node) + prover.stats(&pk, stdin, self.debug.clone(), self.minimal_memory, mpi_node) } /// Prints stats individually and grouped, with aligned columns. diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 5a6dbbae8..c201eefc0 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -142,9 +142,9 @@ impl ZiskVerifyConstraints { .build()?; let elf = ElfBinaryFromFile::new(&self.elf, false)?; - prover.setup(&elf)?; + let (pk, _) = prover.setup(&elf)?; - prover.verify_constraints_debug(stdin, self.debug.clone()) + prover.verify_constraints_debug(&pk, stdin, self.debug.clone()) } pub fn run_asm( @@ -165,11 +165,11 @@ impl ZiskVerifyConstraints { .build()?; let elf = ElfBinaryFromFile::new(&self.elf, hints_stream.is_some())?; - prover.setup(&elf)?; + let (pk, _) = prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { prover.set_hints_stream(hints_stream)?; } - prover.verify_constraints_debug(stdin, self.debug.clone()) + prover.verify_constraints_debug(&pk, stdin, self.debug.clone()) } } diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index 3894205b8..2c07b2f98 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -1,6 +1,7 @@ //! ZiskStream is responsible for reading precompile hints from a stream source and sent to a hints processor. use anyhow::Result; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Sender}; use std::sync::Arc; use std::thread::{self, JoinHandle}; @@ -50,6 +51,8 @@ pub struct ZiskStream { /// Join handle for the background thread. thread_handle: Option>, + + hints_stream_initialized: AtomicBool, } impl ZiskStream { @@ -61,7 +64,12 @@ impl ZiskStream { /// # Returns /// A new `ZiskStream` instance without a running thread. pub fn new(hints_processor: impl StreamProcessor) -> Self { - Self { hints_processor: Arc::new(hints_processor), tx: None, thread_handle: None } + Self { + hints_processor: Arc::new(hints_processor), + tx: None, + thread_handle: None, + hints_stream_initialized: AtomicBool::new(false), + } } /// Stop the current background thread if running. @@ -101,6 +109,8 @@ impl ZiskStream { self.thread_handle = Some(thread_handle); + self.hints_stream_initialized.store(true, Ordering::SeqCst); + Ok(()) } @@ -144,6 +154,7 @@ impl ZiskStream { pub fn reset(&mut self) { self.hints_processor.reset(); + self.hints_stream_initialized.store(false, Ordering::SeqCst); } /// Trigger the background thread to process hints asynchronously. @@ -156,6 +167,12 @@ impl ZiskStream { /// * `Ok(())` - If the command was successfully sent /// * `Err` - If there's no active thread or the channel is closed pub fn start_stream(&mut self) -> Result<()> { + if !self.hints_stream_initialized.load(Ordering::SeqCst) { + return Err(anyhow::anyhow!( + "Hints stream is not initialized. Call set_hints_stream_src first." + )); + } + if let Some(tx) = &self.tx { tx.send(ThreadCommand::Process).map_err(|e| { anyhow::anyhow!("Failed to send process command to background thread: {}", e) diff --git a/common/src/types.rs b/common/src/types.rs index 5fad3c5c8..91d1b7a4d 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -170,6 +170,81 @@ pub struct Stats { pub num_chunks: usize, } +impl Stats { + /// Creates stats for an instance with no collection phase. + /// + /// Used for main instances and ROM instances with ASM emulator that skip collection. + /// Sets `collect_duration` to 0 and `num_chunks` to 0. + pub fn new_no_collection(airgroup_id: usize, air_id: usize) -> Self { + Self { + airgroup_id, + air_id, + collect_start_time: Instant::now(), + collect_duration: 0, + witness_start_time: Instant::now(), + witness_duration: 0, + num_chunks: 0, + } + } + + /// Creates stats for an instance with a pending collection phase. + /// + /// Used when collection is about to start. The `collect_duration` will be + /// updated later via `set_collect_duration` when collection completes. + pub fn new_pending_collection(airgroup_id: usize, air_id: usize, num_chunks: usize) -> Self { + Self { + airgroup_id, + air_id, + collect_start_time: Instant::now(), + collect_duration: 0, + witness_start_time: Instant::now(), + witness_duration: 0, + num_chunks, + } + } + + /// Creates stats for an instance with completed collection. + /// + /// Used when collection has finished and we know the actual timing. + pub fn new_with_collection( + airgroup_id: usize, + air_id: usize, + num_chunks: usize, + collect_start_time: Instant, + collect_duration: u64, + ) -> Self { + Self { + airgroup_id, + air_id, + collect_start_time, + collect_duration, + witness_start_time: Instant::now(), + witness_duration: 0, + num_chunks, + } + } + + /// Creates stats for a main instance (no collection, witness already computed). + /// + /// Used when witness computation has finished and we know the timing. + /// Main instances don't have a collection phase. + pub fn new_main_completed( + airgroup_id: usize, + air_id: usize, + witness_start_time: Instant, + ) -> Self { + Self { + airgroup_id, + air_id, + collect_start_time: Instant::now(), + collect_duration: 0, + witness_start_time, + witness_duration: witness_start_time.elapsed().as_millis(), + num_chunks: 0, + } + } +} + pub trait ElfBinaryLike { fn elf(&self) -> &[u8]; fn name(&self) -> &str; diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 76150eff8..6c987702f 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -16,7 +16,7 @@ use zisk_common::ElfBinaryFromFile; use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; use zisk_distributed_common::{ComputeCapacity, JobId, WorkerId}; use zisk_distributed_common::{HintsSourceDto, StreamDataDto, StreamMessageKind, StreamPayloadDto}; -use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProver}; +use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProgramPK, ZiskProver}; use proofman::ExecutionInfo; use proofman::ProofInfo; @@ -248,6 +248,8 @@ pub struct Worker { stream_buffers: HashMap>)>, // (job_id, (next_seq, (seq_number, data))) hints_processor: Option, + + pk: ZiskProgramPK, } impl Worker { @@ -269,7 +271,7 @@ impl Worker { ); let elf = ElfBinaryFromFile::new(&prover_config.elf, prover_config.hints)?; - prover.setup(&elf)?; + let (pk, _) = prover.setup(&elf)?; Ok(Worker:: { _worker_id: worker_id, @@ -279,6 +281,7 @@ impl Worker { current_computation: None, prover, prover_config, + pk, stream_buffers: HashMap::new(), hints_processor: None, }) @@ -305,7 +308,7 @@ impl Worker { ); let elf = ElfBinaryFromFile::new(&prover_config.elf, prover_config.hints)?; - prover.setup(&elf)?; + let (pk, _) = prover.setup(&elf)?; Ok(Worker:: { _worker_id: worker_id, @@ -315,6 +318,7 @@ impl Worker { current_computation: None, prover, prover_config, + pk, stream_buffers: HashMap::new(), hints_processor: None, }) @@ -472,6 +476,7 @@ impl Worker { tx: mpsc::UnboundedSender, ) -> JoinHandle<()> { let prover = self.prover.clone(); + let pk = self.pk.clone(); let options = self.get_proof_options(false); @@ -501,6 +506,7 @@ impl Worker { phase_inputs, inputs_source, hints_source, + &pk, options, ) .await; @@ -538,6 +544,7 @@ impl Worker { phase_inputs: ProvePhaseInputs, input_source: InputSourceDto, hints_source: HintsSourceDto, + pk: &ZiskProgramPK, options: ProofOptions, ) -> Result> { let phase = proofman::ProvePhase::Contributions; @@ -573,6 +580,8 @@ impl Worker { } } + prover.register_program(pk)?; + let challenge = match prover.prove_phase(phase_inputs, options, phase) { Ok(proofman::ProvePhaseResult::Contributions(challenge)) => { info!("Contribution computation successful for {job_id}"); @@ -885,6 +894,7 @@ impl Worker { phase_inputs, input_source_dto, hints_source_dto, + &self.pk, options, ) .await; diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index cf0802d61..a78f051a5 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -45,6 +45,7 @@ impl fmt::Display for AsmService { const ASM_SERVICE_BASE_PORT: u16 = 23115; +#[derive(Debug, Clone)] pub struct AsmServices { world_rank: i32, local_rank: i32, @@ -250,6 +251,11 @@ impl AsmServices { base_port.unwrap_or(ASM_SERVICE_BASE_PORT) + rank_offset } + pub fn port_base_offset(base_port: Option, n_processes: i32, n_setups: u64) -> u16 { + let setups_offset = n_setups as u16 * (n_processes as u16 * Self::SERVICES.len() as u16); + base_port.unwrap_or(ASM_SERVICE_BASE_PORT) + setups_offset + } + pub fn send_status_request(&self, service: &AsmService) -> Result { self.send_request(service, &PingRequest {}) } diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 759fdb9ff..df06244f5 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -333,9 +333,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.4" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" dependencies = [ "aws-lc-sys", "zeroize", @@ -452,9 +452,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5c6f81257d10a0f602a294ae4182251151ff97dbb504ef9afcdda4a64b24d9b4" [[package]] name = "bytemuck" @@ -813,7 +813,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "fields", "num-bigint", @@ -1056,6 +1056,7 @@ dependencies = [ "precomp-poseidon2", "precomp-sha256f", "precompiles-common", + "precompiles-hints", "proofman", "proofman-common", "proofman-util", @@ -1092,10 +1093,36 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fibonacci-guest" +version = "0.1.0" +dependencies = [ + "byteorder", + "ziskos", +] + +[[package]] +name = "fibonacci-guest-2" +version = "0.1.0" +dependencies = [ + "byteorder", + "ziskos", +] + +[[package]] +name = "fibonacci-host" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "sha2", + "zisk-sdk", +] + [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "cfg-if", "num-bigint", @@ -1959,7 +1986,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "colored", "fields", @@ -2260,7 +2287,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "bincode", "blake3", @@ -2296,7 +2323,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "bincode", "borsh", @@ -2328,7 +2355,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "fields", "itoa", @@ -2341,7 +2368,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "proc-macro2", "quote", @@ -2351,7 +2378,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "crossbeam-channel", "tracing", @@ -2360,7 +2387,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "bincode", "bytemuck", @@ -2372,7 +2399,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "bytemuck", "fields", @@ -4378,7 +4405,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#28ec794c611b5c53c7096c565da80bd0639f2fe9" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" dependencies = [ "colored", "fields", @@ -4652,7 +4679,6 @@ dependencies = [ "zisk-core", "zisk-distributed-common", "zisk-verifier", - "zisk-witness", "ziskemu", ] @@ -4665,44 +4691,6 @@ dependencies = [ "proofman-verifier", ] -[[package]] -name = "zisk-witness" -version = "0.16.0" -dependencies = [ - "anyhow", - "asm-runner", - "data-bus", - "executor", - "fields", - "mem-common", - "pil-std-lib", - "precomp-arith-eq", - "precomp-arith-eq-384", - "precomp-big-int", - "precomp-dma", - "precomp-keccakf", - "precomp-poseidon2", - "precomp-sha256f", - "precompiles-hints", - "proofman", - "proofman-common", - "proofman-macros", - "proofman-util", - "rayon", - "sm-arith", - "sm-binary", - "sm-frequent-ops", - "sm-main", - "sm-mem", - "sm-rom", - "tracing", - "witness", - "zisk-common", - "zisk-core", - "zisk-pil", - "ziskemu", -] - [[package]] name = "ziskemu" version = "0.16.0" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 9bf3cbe84..ff3658290 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["sha-hasher/host", "sha-hasher/guest"] +members = ["sha-hasher/host", "sha-hasher/guest", "multiple-programs/host", "multiple-programs/guest", "multiple-programs/guest_2"] resolver = "2" diff --git a/examples/multiple-programs/guest/Cargo.lock b/examples/multiple-programs/guest/Cargo.lock new file mode 100644 index 000000000..a31235f0c --- /dev/null +++ b/examples/multiple-programs/guest/Cargo.lock @@ -0,0 +1,293 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lib-c" +version = "0.13.1" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "sha-hasher-guest" +version = "0.1.0" +dependencies = [ + "byteorder", + "sha2", + "ziskos", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ziskos" +version = "0.13.1" +dependencies = [ + "cfg-if", + "getrandom", + "lazy_static", + "lib-c", + "num-bigint", + "num-integer", + "num-traits", + "rand", + "static_assertions", + "tiny-keccak", +] diff --git a/examples/multiple-programs/guest/Cargo.toml b/examples/multiple-programs/guest/Cargo.toml new file mode 100644 index 000000000..9b0c3ab8b --- /dev/null +++ b/examples/multiple-programs/guest/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fibonacci-guest" +version = "0.1.0" +edition = "2021" + +[dependencies] +byteorder = "1.5.0" +ziskos = { path = "../../../ziskos/entrypoint" } diff --git a/examples/multiple-programs/guest/src/main.rs b/examples/multiple-programs/guest/src/main.rs new file mode 100644 index 000000000..ed657d058 --- /dev/null +++ b/examples/multiple-programs/guest/src/main.rs @@ -0,0 +1,26 @@ +// This example program takes a number `n` as input and computes the SHA-256 hash `n` times sequentially. + +// Mark the main function as the entry point for ZisK +#![no_main] +ziskos::entrypoint!(main); + +fn main() { + // Read the input data + let n: u32 = ziskos::io::read(); + + let module = 233; + + ziskos::io::commit(&n); + ziskos::io::commit(&module); + + let mut a = 0; + let mut b = 1; + for _ in 0..n { + let mut c = a + b; + c %= module; + a = b; + b = c; + } + + ziskos::io::commit(&b); +} diff --git a/examples/multiple-programs/guest_2/Cargo.lock b/examples/multiple-programs/guest_2/Cargo.lock new file mode 100644 index 000000000..a31235f0c --- /dev/null +++ b/examples/multiple-programs/guest_2/Cargo.lock @@ -0,0 +1,293 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lib-c" +version = "0.13.1" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "sha-hasher-guest" +version = "0.1.0" +dependencies = [ + "byteorder", + "sha2", + "ziskos", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ziskos" +version = "0.13.1" +dependencies = [ + "cfg-if", + "getrandom", + "lazy_static", + "lib-c", + "num-bigint", + "num-integer", + "num-traits", + "rand", + "static_assertions", + "tiny-keccak", +] diff --git a/examples/multiple-programs/guest_2/Cargo.toml b/examples/multiple-programs/guest_2/Cargo.toml new file mode 100644 index 000000000..1953a492a --- /dev/null +++ b/examples/multiple-programs/guest_2/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fibonacci-guest-2" +version = "0.1.0" +edition = "2021" + +[dependencies] +byteorder = "1.5.0" +ziskos = { path = "../../../ziskos/entrypoint" } diff --git a/examples/multiple-programs/guest_2/src/main.rs b/examples/multiple-programs/guest_2/src/main.rs new file mode 100644 index 000000000..5bc024070 --- /dev/null +++ b/examples/multiple-programs/guest_2/src/main.rs @@ -0,0 +1,26 @@ +// This example program takes a number `n` as input and computes the SHA-256 hash `n` times sequentially. + +// Mark the main function as the entry point for ZisK +#![no_main] +ziskos::entrypoint!(main); + +fn main() { + // Read the input data + let n: u32 = ziskos::io::read(); + + let module = 253; + + ziskos::io::commit(&n); + ziskos::io::commit(&module); + + let mut a = 0; + let mut b = 1; + for _ in 0..n { + let mut c = a + b; + c %= module; + a = b; + b = c; + } + + ziskos::io::commit(&b); +} diff --git a/examples/multiple-programs/host/Cargo.toml b/examples/multiple-programs/host/Cargo.toml new file mode 100644 index 000000000..3d32922b9 --- /dev/null +++ b/examples/multiple-programs/host/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "fibonacci-host" +version = "0.1.0" +edition = "2021" + +[dependencies] +zisk-sdk = { workspace = true } +anyhow = "1.0" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sha2 = "0.10.8" + +[build-dependencies] +zisk-sdk = { workspace = true } + +[features] +default = [] +packed = ["zisk-sdk/packed"] +gpu = ["zisk-sdk/gpu", "packed"] \ No newline at end of file diff --git a/examples/multiple-programs/host/build.rs b/examples/multiple-programs/host/build.rs new file mode 100644 index 000000000..9a7c14db5 --- /dev/null +++ b/examples/multiple-programs/host/build.rs @@ -0,0 +1,4 @@ +fn main() { + zisk_sdk::build_program("../guest"); + zisk_sdk::build_program("../guest_2"); +} diff --git a/examples/multiple-programs/host/src/main.rs b/examples/multiple-programs/host/src/main.rs new file mode 100644 index 000000000..98b5093c8 --- /dev/null +++ b/examples/multiple-programs/host/src/main.rs @@ -0,0 +1,67 @@ +use anyhow::Result; +use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; + +pub const ELF: ElfBinary = include_elf!("fibonacci-guest"); +pub const ELF2: ElfBinary = include_elf!("fibonacci-guest-2"); + +fn main() -> Result<()> { + println!("Starting ZisK Prover Client...\n"); + + // Create an input stream and write '1000' to it. + let n = 1000u32; + let stdin = ZiskStdin::new(); + stdin.write(&n); + + // Create a `ProverClient` method. + let client = ProverClient::builder().build().unwrap(); + + println!("Setting up first program..."); + let (pk, vkey) = client.setup(&ELF)?; + + println!("Setting up second program..."); + let (pk2, vkey2) = client.setup(&ELF2)?; + + // Execute the program using the `ProverClient.execute` method, without generating a proof. + println!("Executing first program..."); + let result = client.execute(&pk, stdin.clone())?; + + println!( + "Program executed successfully: {} cycles in {:.2?}", + result.execution.steps, result.duration + ); + + println!("Generating proof for first program..."); + let proof_opts = ProofOpts::default().minimal_memory(); + let vadcop_result = client.prove(&pk, stdin).with_proof_options(proof_opts).run()?; + + println!("Verifying proof..."); + client.verify(vadcop_result.get_proof(), vadcop_result.get_publics(), &vkey)?; + + println!("Successfully generated and verified proof for first program!\n"); + + let n = 2000u32; + let stdin2 = ZiskStdin::new(); + stdin2.write(&n); + + // Execute the program using the `ProverClient.execute` method, without generating a proof. + println!("Executing second program..."); + let result2 = client.execute(&pk2, stdin2.clone())?; + + println!( + "Program executed successfully: {} cycles in {:.2?}", + result2.execution.steps, result2.duration + ); + + println!("Generating proof for second program..."); + let proof_opts = ProofOpts::default().minimal_memory(); + let vadcop_result2 = client.prove(&pk2, stdin2).with_proof_options(proof_opts).run()?; + + println!("Verifying proof..."); + client.verify(vadcop_result2.get_proof(), vadcop_result2.get_publics(), &vkey2)?; + + println!("Successfully generated and verified proof for second program!\n"); + + println!("All proofs generated and verified successfully!"); + + Ok(()) +} diff --git a/examples/sha-hasher/guest/Cargo.toml b/examples/sha-hasher/guest/Cargo.toml index 17dbb8710..5c7540ff9 100644 --- a/examples/sha-hasher/guest/Cargo.toml +++ b/examples/sha-hasher/guest/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sha-hasher-guest" version = "0.1.0" -edition = "2024" +edition = "2021" [dependencies] byteorder = "1.5.0" diff --git a/examples/sha-hasher/host/Cargo.toml b/examples/sha-hasher/host/Cargo.toml index 6ff343636..b3e85874f 100644 --- a/examples/sha-hasher/host/Cargo.toml +++ b/examples/sha-hasher/host/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sha-hasher-host" version = "0.1.0" -edition = "2024" +edition = "2021" [dependencies] zisk-sdk = { workspace = true } diff --git a/examples/sha-hasher/host/bin/compressed.rs b/examples/sha-hasher/host/bin/compressed.rs index d999ae8b4..76d1faacb 100644 --- a/examples/sha-hasher/host/bin/compressed.rs +++ b/examples/sha-hasher/host/bin/compressed.rs @@ -14,15 +14,15 @@ fn main() -> Result<()> { // Create a `ProverClient` method. println!("Building prover client..."); - let client = ProverClient::builder().asm().base_port(54321).build().unwrap(); + let client = ProverClient::builder().build().unwrap(); println!("Setting up program..."); - let vkey = client.setup(&ELF)?; + let (pk, vkey) = client.setup(&ELF)?; println!("Setup completed successfully"); println!("Generating Vadcop proof..."); let proof_opts = ProofOpts::default().minimal_memory(); - let vadcop_result = client.prove(stdin).with_proof_options(proof_opts).run()?; + let vadcop_result = client.prove(&pk, stdin).with_proof_options(proof_opts).run()?; println!("Vadcop proof generated in {:?}", vadcop_result.get_duration()); println!("Compressing proof (this may take a while)..."); @@ -30,7 +30,7 @@ fn main() -> Result<()> { client.compress(vadcop_result.get_proof(), vadcop_result.get_publics(), &vkey)?; // Alternatively, you can also call `compressed()` on the `ProverClient.prove` method to generate a compressed proof directly. - // let result = client.prove(stdin).with_proof_options(proof_opts).compressed().run()?; + // let result = client.prove(&pk, stdin).with_proof_options(proof_opts).compressed().run()?; println!("Verifying compressed proof..."); client.verify( diff --git a/examples/sha-hasher/host/bin/execute.rs b/examples/sha-hasher/host/bin/execute.rs index 64eb7c594..1bacc871f 100644 --- a/examples/sha-hasher/host/bin/execute.rs +++ b/examples/sha-hasher/host/bin/execute.rs @@ -25,12 +25,12 @@ fn main() -> Result<()> { let client = ProverClient::builder().asm().base_port(54321).build().unwrap(); println!("Setting up program..."); - client.setup(&ELF)?; + let (pk, _) = client.setup(&ELF)?; println!("Setup completed successfully"); // Execute the program using the `ProverClient.execute` method, without generating a proof. println!("Executing program (no proof generation)..."); - let result = client.execute(stdin.clone())?; + let result = client.execute(&pk, stdin.clone())?; println!("\u{2713} Execution completed successfully!"); println!("Cycles: {}", result.get_execution_steps()); diff --git a/examples/sha-hasher/host/bin/plonk.rs b/examples/sha-hasher/host/bin/plonk.rs index 1fd3bad84..39529a953 100644 --- a/examples/sha-hasher/host/bin/plonk.rs +++ b/examples/sha-hasher/host/bin/plonk.rs @@ -19,16 +19,16 @@ fn main() -> Result<()> { let client = ProverClient::builder().asm().base_port(54321).snark().build().unwrap(); println!("Setting up program and generating verification key..."); - let vkey = client.setup(&ELF)?; + let (pk, vkey) = client.setup(&ELF)?; println!("Setup completed successfully"); println!("Generating PLONK proof (this may take a while)..."); - let snark_proof = client.prove(stdin).plonk().run()?; + let snark_proof = client.prove(&pk, stdin).plonk().run()?; println!("PLONK proof generated successfully in {:?}", snark_proof.get_duration()); println!("Execution steps: {}", snark_proof.get_execution_steps()); // Alternatively, it can also be done in two steps - // let vadcop_result = client.prove(stdin).run()?; + // let vadcop_result = client.prove(&pk, stdin).run()?; // let snark_proof = client.prove_snark(&vadcop_result.get_proof(), &vadcop_result.get_publics(), &vkey)?; println!("Verifying PLONK proof..."); diff --git a/examples/sha-hasher/host/bin/prove.rs b/examples/sha-hasher/host/bin/prove.rs index c19cff898..78bc19137 100644 --- a/examples/sha-hasher/host/bin/prove.rs +++ b/examples/sha-hasher/host/bin/prove.rs @@ -29,12 +29,12 @@ fn main() -> Result<()> { let client = ProverClient::builder().asm().base_port(54321).build().unwrap(); println!("Setting up program..."); - client.setup(&ELF)?; + let (pk, _) = client.setup(&ELF)?; println!("Setup completed successfully"); println!("Generating proof (this may take a while)..."); let proof_opts = ProofOpts::default().minimal_memory(); - let result = client.prove(stdin).with_proof_options(proof_opts).run()?; + let result = client.prove(&pk, stdin).with_proof_options(proof_opts).run()?; println!("Proof generated successfully in {:?}", result.get_duration()); println!("Execution steps: {}", result.get_execution_steps()); diff --git a/examples/sha-hasher/host/bin/verify-constraints.rs b/examples/sha-hasher/host/bin/verify-constraints.rs index 9ad65fb3b..7dd8ace6d 100644 --- a/examples/sha-hasher/host/bin/verify-constraints.rs +++ b/examples/sha-hasher/host/bin/verify-constraints.rs @@ -27,11 +27,11 @@ fn main() -> Result<()> { let client = ProverClient::builder().emu().verify_constraints().build().unwrap(); println!("Setting up program..."); - client.setup(&elf)?; + let (pk, _vkey) = client.setup(&elf)?; println!("Setup completed successfully"); println!("Verifying constraints (no proof generation)..."); - let result = client.verify_constraints(stdin.clone())?; + let result = client.verify_constraints(&pk, stdin.clone())?; println!("\u{2713} VerifyConstraints completed successfully!"); println!("Cycles: {}", result.get_execution_steps()); diff --git a/examples/sha-hasher/host/build.rs b/examples/sha-hasher/host/build.rs index da463c8f5..7c4531e6b 100644 --- a/examples/sha-hasher/host/build.rs +++ b/examples/sha-hasher/host/build.rs @@ -1,5 +1,5 @@ use std::path::PathBuf; -use zisk_sdk::{ZiskIO, ZiskStdin, build_program}; +use zisk_sdk::{build_program, ZiskIO, ZiskStdin}; fn main() { build_program("../guest"); diff --git a/examples/sha-hasher/host/src/main.rs b/examples/sha-hasher/host/src/main.rs index c71bc1907..7a6c34d4a 100644 --- a/examples/sha-hasher/host/src/main.rs +++ b/examples/sha-hasher/host/src/main.rs @@ -12,12 +12,12 @@ fn main() -> Result<()> { stdin.write(&n); // Create a `ProverClient` method. - let client = ProverClient::builder().build().unwrap(); + let client = ProverClient::builder().asm().build().unwrap(); - let vkey = client.setup(&ELF)?; + let (pk, vkey) = client.setup(&ELF)?; // Execute the program using the `ProverClient.execute` method, without generating a proof. - let result = client.execute(stdin.clone())?; + let result = client.execute(&pk, stdin.clone())?; println!( "ZisK has executed program with {} cycles in {:?}", @@ -25,7 +25,7 @@ fn main() -> Result<()> { ); let proof_opts = ProofOpts::default().minimal_memory(); - let vadcop_result = client.prove(stdin).with_proof_options(proof_opts).run()?; + let vadcop_result = client.prove(&pk, stdin).with_proof_options(proof_opts).run()?; client.verify(vadcop_result.get_proof(), vadcop_result.get_publics(), &vkey)?; println!("successfully generated and verified proof for the program!"); diff --git a/executor/Cargo.toml b/executor/Cargo.toml index b36c6222a..7fab72504 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -28,6 +28,7 @@ tracing = { workspace = true } itertools = { workspace = true } rayon = { workspace = true } pil-std-lib = { workspace = true } +precompiles-hints = { workspace = true } anyhow = { workspace = true } crossbeam = "0.8.4" diff --git a/witness-computation/NEW_SM.md b/executor/NEW_SM.md similarity index 100% rename from witness-computation/NEW_SM.md rename to executor/NEW_SM.md diff --git a/executor/src/air_classifier.rs b/executor/src/air_classifier.rs new file mode 100644 index 000000000..fc5d378fc --- /dev/null +++ b/executor/src/air_classifier.rs @@ -0,0 +1,70 @@ +//! AIR classification helpers. +//! +//! This module provides helpers for classifying AIR types based on their IDs, +//! centralizing the scattered `*_AIR_IDS.contains()` checks throughout the executor. + +use zisk_pil::{ + INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, ZISK_AIRGROUP_ID, +}; + +/// Helper for classifying AIR instances by their ID. +/// +/// Centralizes the logic for determining AIR types, replacing scattered +/// `*_AIR_IDS.contains()` checks throughout the codebase. +pub struct AirClassifier; + +impl AirClassifier { + /// Checks if the AIR ID corresponds to a main state machine. + #[inline] + pub fn is_main(air_id: usize) -> bool { + MAIN_AIR_IDS.contains(&air_id) + } + + /// Checks if the AIR ID corresponds to the ROM state machine. + #[inline] + pub fn is_rom(air_id: usize) -> bool { + air_id == ROM_AIR_IDS[0] + } + + /// Checks if the plan targets the ROM instance that requires special handling. + /// + /// ROM instances need to be added to the proof context with first partition assignment. + #[inline] + pub fn is_rom_instance(airgroup_id: usize, air_id: usize) -> bool { + airgroup_id == ZISK_AIRGROUP_ID && Self::is_rom(air_id) + } + + /// Checks if the AIR ID corresponds to a memory-related state machine. + /// + /// Memory-related AIRs include MEM, ROM_DATA, and INPUT_DATA. + #[inline] + pub fn is_memory_related(air_id: usize) -> bool { + air_id == MEM_AIR_IDS[0] || air_id == ROM_DATA_AIR_IDS[0] || air_id == INPUT_DATA_AIR_IDS[0] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_main() { + for &air_id in MAIN_AIR_IDS { + assert!(AirClassifier::is_main(air_id)); + } + } + + #[test] + fn test_is_rom() { + for &air_id in ROM_AIR_IDS { + assert!(AirClassifier::is_rom(air_id)); + } + } + + #[test] + fn test_is_memory_related() { + assert!(AirClassifier::is_memory_related(MEM_AIR_IDS[0])); + assert!(AirClassifier::is_memory_related(ROM_DATA_AIR_IDS[0])); + assert!(AirClassifier::is_memory_related(INPUT_DATA_AIR_IDS[0])); + } +} diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs new file mode 100644 index 000000000..b1e5c8b1f --- /dev/null +++ b/executor/src/asm_resources.rs @@ -0,0 +1,129 @@ +use std::sync::{Arc, Mutex}; + +use anyhow::Result; +use asm_runner::HintsFile; +use asm_runner::HintsShmem; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +use asm_runner::{MOOutputShmem, MTOutputShmem, RHOutputShmem, SharedMemoryWriter}; +use precompiles_hints::HintsProcessor; +use zisk_common::io::{StreamSource, ZiskStream}; + +/// Encapsulates assembly-related resources including shared memory and hints stream. +#[derive(Clone)] +pub struct AsmResources { + /// Optional baseline port to communicate with assembly microservices. + pub base_port: Option, + + /// Local rank for distributed execution. + pub local_rank: i32, + + /// Map unlocked flag. + pub unlock_mapped_memory: bool, + + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + pub asm_shmem_mt: Arc>, + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + pub asm_shmem_mo: Arc>, + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + pub asm_shmem_rh: Arc>>, + /// Shared memory writers for each assembly service. + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + pub shmem_input_writer: Arc>>, + + /// Pipeline for handling precompile hints. + pub hints_stream: Option>>, +} + +impl std::fmt::Debug for AsmResources { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AsmResources") + .field("base_port", &self.base_port) + .field("local_rank", &self.local_rank) + .field("unlock_mapped_memory", &self.unlock_mapped_memory) + .finish_non_exhaustive() + } +} + +impl AsmResources { + pub fn new( + local_rank: i32, + base_port: Option, + unlock_mapped_memory: bool, + verbose_mode: proofman_common::VerboseMode, + with_hints: bool, + ) -> Self { + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + let asm_shmem_mt = MTOutputShmem::new(local_rank, base_port, unlock_mapped_memory) + .expect("asm_resources: Failed to create PreloadedMT"); + + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + let asm_shmem_mo = MOOutputShmem::new(local_rank, base_port, unlock_mapped_memory) + .expect("asm_resources: Failed to create PreloadedMO"); + + // Create hints pipeline with null hints stream initially. + // Debug flag: true = HintsShmem (shared memory), false = HintsFile (file output) + + const USE_SHARED_MEMORY_HINTS: bool = true; + + let hints_stream = if with_hints { + let hints_processor = if USE_SHARED_MEMORY_HINTS { + let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory) + .expect("asm_resources: Failed to create HintsShmem"); + + HintsProcessor::builder(hints_shmem) + .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) + .build() + .expect("asm_resources: Failed to create PrecompileHintsProcessor") + } else { + let hints_file = HintsFile::new(format!("hints_results_{}.bin", local_rank)) + .expect("asm_resources: Failed to create HintsFile"); + + HintsProcessor::builder(hints_file) + .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) + .build() + .expect("asm_resources: Failed to create PrecompileHintsProcessor") + }; + + Some(Arc::new(Mutex::new(ZiskStream::new(hints_processor)))) + } else { + None + }; + + Self { + hints_stream, + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + asm_shmem_mt: Arc::new(Mutex::new(asm_shmem_mt)), + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + asm_shmem_mo: Arc::new(Mutex::new(asm_shmem_mo)), + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + asm_shmem_rh: Arc::new(Mutex::new(None)), + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + shmem_input_writer: Arc::new(Mutex::new(None)), + base_port, + local_rank, + unlock_mapped_memory, + } + } + + pub fn start_stream(&self) -> Result<()> { + if let Some(hints_stream) = &self.hints_stream { + hints_stream.lock().unwrap().start_stream() + } else { + Ok(()) + } + } + + pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { + if let Some(hints_stream) = &self.hints_stream { + hints_stream.lock().unwrap().set_hints_stream_src(stream) + } else { + Err(anyhow::anyhow!("Hints stream not initialized")) + } + } + + pub fn reset(&self) { + if let Some(hints_stream) = &self.hints_stream { + hints_stream.lock().unwrap().reset(); + } + } +} diff --git a/executor/src/collector.rs b/executor/src/collector.rs new file mode 100644 index 000000000..23fa30575 --- /dev/null +++ b/executor/src/collector.rs @@ -0,0 +1,345 @@ +//! Chunk collector component. +//! +//! ## Overview +//! +//! During witness computation, secondary state machines need data from +//! specific execution chunks. This component: +//! +//! 1. Determines which chunks each instance needs (via checkpoints) +//! 2. Orders chunks for optimal parallel processing +//! 3. Executes chunks and routes data to the appropriate collectors +//! +//! ## Chunk Ordering Strategy +//! +//! Uses a greedy algorithm that prioritizes completing instances that need +//! fewer remaining chunks, minimizing time-to-first-completion. + +use anyhow::Result; +use crossbeam::atomic::AtomicCell; +use data_bus::DataBusTrait; +use fields::PrimeField64; +use proofman_common::ProofCtx; +use std::{ + collections::HashMap, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, + time::Instant, +}; +use zisk_common::{CheckPoint, EmuTrace, Instance, Stats}; +use zisk_core::ZiskRom; +use ziskemu::ZiskEmulator; + +use crate::{state::ChunkCollector, ExecutionState, StaticSMBundle}; + +pub struct ChunkDataCollector { + /// State machine bundle for building data buses. + sm_bundle: Arc>, +} + +impl ChunkDataCollector { + /// Creates a new `ChunkDataCollector`. + /// + /// # Arguments + /// * `sm_bundle` - State machine bundle. + pub fn new(sm_bundle: Arc>) -> Self { + Self { sm_bundle } + } + + pub fn set_rom(&self, zisk_rom: Arc) { + self.sm_bundle.set_rom(zisk_rom); + } + + /// Computes which chunks need to be executed for each instance. + /// + /// # Arguments + /// * `min_traces` - Minimal traces from execution. + /// * `secn_instances` - Map of global ID to secondary instances. + /// + /// # Returns + /// Tuple of (chunks_to_execute, global_id_chunks) where: + /// - chunks_to_execute[chunk_id] = list of global_ids that need this chunk + /// - global_id_chunks[global_id] = list of chunk_ids this instance needs + #[allow(clippy::borrowed_box)] + pub fn compute_chunks_to_execute( + &self, + min_traces: &[EmuTrace], + secn_instances: &HashMap>>, + ) -> (Vec>, HashMap>) { + let mut chunks_to_execute = vec![Vec::new(); min_traces.len()]; + let mut global_id_chunks: HashMap> = HashMap::new(); + + secn_instances.iter().for_each(|(global_idx, secn_instance)| { + match secn_instance.check_point() { + CheckPoint::None => {} + CheckPoint::Single(chunk_id) => { + chunks_to_execute[chunk_id.as_usize()].push(*global_idx); + global_id_chunks.entry(*global_idx).or_default().push(chunk_id.as_usize()); + } + CheckPoint::Multiple(chunk_ids) => { + chunk_ids.iter().for_each(|&chunk_id| { + chunks_to_execute[chunk_id.as_usize()].push(*global_idx); + global_id_chunks.entry(*global_idx).or_default().push(chunk_id.as_usize()); + }); + } + } + }); + + for chunk_ids in global_id_chunks.values_mut() { + chunk_ids.sort(); + } + + (chunks_to_execute, global_id_chunks) + } + + /// Orders chunks for optimal processing. + /// + /// Uses a greedy algorithm to minimize the time until any instance + /// has all its chunks collected. + /// + /// # Arguments + /// * `chunks_to_execute` - Which instances need each chunk. + /// * `global_id_chunks` - Which chunks each instance needs. + /// + /// # Returns + /// Ordered list of chunk IDs to process. + pub fn order_chunks( + &self, + chunks_to_execute: &[Vec], + global_id_chunks: &HashMap>, + ) -> Vec { + let mut ordered_chunks = Vec::new(); + let mut already_selected_chunks = vec![false; chunks_to_execute.len()]; + + let mut n_global_ids_incompleted = global_id_chunks.len(); + let mut n_chunks_by_global_id: HashMap = + global_id_chunks.iter().map(|(global_id, chunks)| (*global_id, chunks.len())).collect(); + + while n_global_ids_incompleted > 0 { + let selected_global_id = n_chunks_by_global_id + .iter() + .filter(|(_, &count)| count > 0) + .min_by_key(|(_, &count)| count) + .map(|(&global_id, _)| global_id); + + if let Some(global_id) = selected_global_id { + for chunk_id in global_id_chunks[&global_id].iter() { + if already_selected_chunks[*chunk_id] { + continue; + } + ordered_chunks.push(*chunk_id); + already_selected_chunks[*chunk_id] = true; + for global_idx in chunks_to_execute[*chunk_id].iter() { + if let Some(count) = n_chunks_by_global_id.get_mut(global_idx) { + *count -= 1; + if *count == 0 { + n_chunks_by_global_id.remove(global_idx); + n_global_ids_incompleted -= 1; + } + } + } + } + } else { + break; + } + } + + ordered_chunks + } + + /// Collects chunk data for a single secondary instance. + /// + /// Convenience method that wraps `collect()` for single-instance collection. + /// Avoids the caller needing to create a HashMap for one instance. + /// + /// # Arguments + /// * `pctx` - Proof context. + /// * `state` - Execution state for storing collectors. + /// * `global_id` - Global ID of the instance. + /// * `instance` - The secondary instance to collect for. + #[allow(clippy::borrowed_box)] + pub fn collect_single( + &self, + pctx: &ProofCtx, + state: &ExecutionState, + global_id: usize, + instance: &Box>, + ) -> Result<()> { + let mut map = HashMap::with_capacity(1); + map.insert(global_id, instance); + self.collect(pctx, state, map)?; + Ok(()) + } + + /// Collects chunk data for the given secondary instances. + /// + /// Processes chunks in parallel, collecting data into the execution state's + /// collectors_by_instance map. + /// + /// # Arguments + /// * `pctx` - Proof context. + /// * `state` - Execution state for storing collectors. + /// * `secn_instances` - Map of global ID to secondary instances. + #[allow(clippy::borrowed_box)] + pub fn collect( + &self, + pctx: &ProofCtx, + state: &ExecutionState, + secn_instances: HashMap>>, + ) -> Result<()> { + let min_traces_guard = state.min_traces.read().unwrap(); + let min_traces = min_traces_guard.as_ref().expect("min_traces should not be None"); + + // Compute chunks to execute + let (chunks_to_execute, global_id_chunks) = + self.compute_chunks_to_execute(min_traces, &secn_instances); + + let ordered_chunks = self.order_chunks(&chunks_to_execute, &global_id_chunks); + let global_ids: Vec = secn_instances.keys().copied().collect(); + + let collect_start_times: Vec>> = + global_ids.iter().map(|_| AtomicCell::new(None)).collect(); + + let global_ids_map: HashMap = + global_ids.iter().enumerate().map(|(idx, &id)| (id, idx)).collect(); + + // Create data buses for each chunk + let data_buses = self + .sm_bundle + .build_data_bus_collectors(pctx, &secn_instances, &chunks_to_execute) + .into_iter() + .map(Mutex::new) + .collect::>(); + + let n_chunks_left: Vec = global_ids + .iter() + .map(|global_id| AtomicUsize::new(global_id_chunks[global_id].len())) + .collect(); + + // Initialize collectors and stats + for global_id in global_ids.iter() { + let (airgroup_id, air_id) = + pctx.dctx_get_instance_info(*global_id).expect("Failed to get instance info"); + let stats = Stats::new_pending_collection( + airgroup_id, + air_id, + global_id_chunks[global_id].len(), + ); + + state + .collectors_by_instance + .write() + .unwrap() + .insert(*global_id, (0..global_id_chunks[global_id].len()).map(|_| None).collect()); + state.stats.insert_witness_stats(*global_id, stats); + } + + let next_chunk = AtomicUsize::new(0); + let zisk_rom = state.get_rom()?; + + rayon::in_place_scope(|scope| { + for _ in 0..rayon::current_num_threads() { + let next_chunk = &next_chunk; + let n_chunks_left = &n_chunks_left; + let collectors_by_instance = &state.collectors_by_instance; + let collect_start_times = &collect_start_times; + let stats = &state.stats; + let min_traces = &min_traces; + let data_buses = &data_buses; + let zisk_rom = &zisk_rom; + let global_ids_map = &global_ids_map; + let global_id_chunks = &global_id_chunks; + let ordered_chunks = &ordered_chunks; + let chunks_to_execute = &chunks_to_execute; + let pctx = &pctx; + + scope.spawn(move |_| loop { + let next_chunk_id = next_chunk.fetch_add(1, Ordering::Relaxed); + if next_chunk_id >= ordered_chunks.len() { + break; + } + let chunk_id = ordered_chunks[next_chunk_id]; + + if let Some(mut data_bus) = data_buses[chunk_id].lock().unwrap().take() { + for global_id in chunks_to_execute[chunk_id].iter() { + let start_time_cell = &collect_start_times[global_ids_map[global_id]]; + if start_time_cell.load().is_none() { + start_time_cell.store(Some(Instant::now())); + } + } + + ZiskEmulator::process_emu_traces::( + zisk_rom, + min_traces, + chunk_id, + &mut data_bus, + ); + + // Collect all device results locally + let devices = data_bus.into_devices(false); + let mut entries: Vec<(usize, usize, Option)> = Vec::new(); + let mut affected_globals: Vec<(usize, usize)> = Vec::new(); + + for (global_id, collector) in devices { + if let Some(global_id) = global_id { + let global_id_idx = *global_ids_map + .get(&global_id) + .expect("Global ID not found in map"); + + let chunk_order = &global_id_chunks[&global_id]; + let position = chunk_order + .iter() + .position(|&id| id == chunk_id) + .expect("Chunk ID not found in order"); + + entries.push(( + global_id, + position, + Some((chunk_id, collector.unwrap())), + )); + affected_globals.push((global_id, global_id_idx)); + } + } + + // Single write-lock acquisition + { + let mut guard = collectors_by_instance.write().unwrap(); + for (global_id, position, entry) in entries.iter_mut() { + guard.get_mut(global_id).unwrap()[*position] = entry.take(); + } + } + + // Update atomic counters and mark ready instances + for (global_id, global_id_idx) in affected_globals { + if n_chunks_left[global_id_idx].fetch_sub(1, Ordering::SeqCst) == 1 { + pctx.set_witness_ready(global_id, true); + + let collect_start_time = collect_start_times[global_id_idx] + .load() + .expect("Collect start time was not set"); + let collect_duration = + collect_start_time.elapsed().as_millis() as u64; + + let (airgroup_id, air_id) = pctx + .dctx_get_instance_info(global_id) + .expect("Failed to get instance info"); + let new_stats = Stats::new_with_collection( + airgroup_id, + air_id, + global_id_chunks[&global_id].len(), + collect_start_time, + collect_duration, + ); + + stats.insert_witness_stats(global_id, new_stats); + } + } + } + }); + } + }); + + Ok(()) + } +} diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 88bb04657..be8d48c49 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -4,17 +4,20 @@ use std::{ thread::JoinHandle, }; +use crate::AsmResources; use crate::{ DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, MAX_NUM_STEPS, }; +use anyhow::Result; use asm_runner::{ shmem_input_name, write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmService, AsmServices, - MOOutputShmem, MTOutputShmem, RHOutputShmem, SharedMemoryWriter, + SharedMemoryWriter, }; use data_bus::DataBusTrait; use fields::PrimeField64; use proofman_common::ProofCtx; use sm_rom::RomSM; +use zisk_common::io::StreamSource; use zisk_common::{ io::ZiskStdin, stats_begin, stats_end, ChunkId, EmuTrace, ExecutorStatsHandle, StatsScope, }; @@ -22,18 +25,12 @@ use zisk_core::{ZiskRom, MAX_INPUT_SIZE}; use ziskemu::ZiskEmulator; pub struct EmulatorAsm { - /// ZisK ROM, a binary file containing the ZisK program to be executed. - pub zisk_rom: Arc, - /// World rank for distributed execution. Default to 0 for single-node execution. world_rank: i32, /// Local rank for distributed execution. Default to 0 for single-node execution. local_rank: i32, - /// Optional baseline port to communicate with assembly microservices. - base_port: Option, - /// Map unlocked flag /// This is used to unlock the memory map for the ROM file. unlock_mapped_memory: bool, @@ -44,55 +41,46 @@ pub struct EmulatorAsm { /// Optional ROM state machine, used for assembly ROM execution. rom_sm: Option>, - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_mt: Arc>, - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_mo: Arc>, - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_rh: Arc>>, - - /// Shared memory writers for each assembly service. - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - shmem_input_writer: Arc>>, + /// Assembly resources including shared memory and hints stream. + asm_resources: Mutex>, } impl EmulatorAsm { #[allow(clippy::too_many_arguments)] pub fn new( - zisk_rom: Arc, world_rank: i32, local_rank: i32, - base_port: Option, unlock_mapped_memory: bool, chunk_size: u64, rom_sm: Option>, + _verbose_mode: proofman_common::VerboseMode, ) -> Self { - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - let asm_shmem_mt = MTOutputShmem::new(local_rank, base_port, unlock_mapped_memory) - .expect("Failed to create PreloadedMT"); - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - let asm_shmem_mo = MOOutputShmem::new(local_rank, base_port, unlock_mapped_memory) - .expect("Failed to create PreloadedMO"); - Self { - zisk_rom, world_rank, local_rank, - base_port, unlock_mapped_memory, chunk_size, rom_sm, - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_mt: Arc::new(Mutex::new(asm_shmem_mt)), - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_mo: Arc::new(Mutex::new(asm_shmem_mo)), - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_rh: Arc::new(Mutex::new(None)), - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - shmem_input_writer: Arc::new(Mutex::new(None)), + asm_resources: Mutex::new(None), } } + pub fn get_chunk_size(&self) -> u64 { + self.chunk_size + } + + pub fn set_asm_resources(&self, asm_resources: AsmResources) { + *self.asm_resources.lock().unwrap() = Some(asm_resources); + } + + pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { + self.asm_resources.lock().unwrap().as_ref().unwrap().set_hints_stream_src(stream) + } + + pub fn reset_hints_stream(&self) { + self.asm_resources.lock().unwrap().as_ref().unwrap().reset(); + } + /// Computes minimal traces by processing the ZisK ROM with given public inputs. /// /// # Arguments @@ -110,11 +98,14 @@ impl EmulatorAsm { /// * `Option>` - Optional join handle for the memory-only ASM runner. /// * `u64` - Total number of steps. #[allow(clippy::type_complexity)] + #[allow(clippy::too_many_arguments)] pub fn execute( &self, + zisk_rom: &ZiskRom, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, + use_hints: bool, stats: &ExecutorStatsHandle, _caller_stats_scope: &StatsScope, ) -> ( @@ -124,26 +115,50 @@ impl EmulatorAsm { Option>, u64, ) { + let asm_resources_guard = self.asm_resources.lock().unwrap(); + let asm_resources = asm_resources_guard.as_ref().expect("AsmResources not initialized"); + + if use_hints { + asm_resources.start_stream().expect("Failed to start hints stream"); + } + stats_begin!(stats, _caller_stats_scope, _exec_scope, "EXECUTE_WITH_ASSEMBLY", 0); stats_begin!(stats, &_exec_scope, _write_scope, "ASM_WRITE_INPUT", 0); - let mut input_writer = self.shmem_input_writer.lock().unwrap(); - input_writer.get_or_insert_with(|| self.create_shmem_writer(&AsmService::MO)); + asm_resources.shmem_input_writer.lock().unwrap().get_or_insert_with(|| { + let port = + AsmServices::port_base_for(asm_resources.base_port, asm_resources.local_rank); + let shmem_input_name = shmem_input_name(port, asm_resources.local_rank); + tracing::debug!( + "Initializing SharedMemoryWriter for service {:?} at '{}'", + AsmService::MO, + shmem_input_name + ); + SharedMemoryWriter::new( + &shmem_input_name, + MAX_INPUT_SIZE as usize, + asm_resources.unlock_mapped_memory, + ) + .expect("Failed to create SharedMemoryWriter") + }); - write_input(&mut stdin.lock().unwrap(), input_writer.as_ref().unwrap()); + write_input( + &mut stdin.lock().unwrap(), + asm_resources.shmem_input_writer.lock().unwrap().as_ref().unwrap(), + ); stats_end!(stats, &_write_scope); let chunk_size = self.chunk_size; - let (world_rank, local_rank, base_port) = - (self.world_rank, self.local_rank, self.base_port); + let (world_rank, local_rank) = (self.world_rank, self.local_rank); let _stats = stats.clone(); // Run the assembly Memory Operations (MO) runner thread let handle_mo = std::thread::spawn({ - let asm_shmem_mo = self.asm_shmem_mo.clone(); + let asm_shmem_mo = asm_resources.asm_shmem_mo.clone(); + let base_port = asm_resources.base_port; move || { AsmRunnerMO::run( &mut asm_shmem_mo.lock().unwrap(), @@ -164,7 +179,8 @@ impl EmulatorAsm { let _stats = stats.clone(); let handle_rh = (has_rom_sm).then(|| { - let asm_shmem_rh = self.asm_shmem_rh.clone(); + let asm_shmem_rh = asm_resources.asm_shmem_rh.clone(); + let base_port = asm_resources.base_port; let unlock_mapped_memory = self.unlock_mapped_memory; std::thread::spawn(move || { AsmRunnerRH::run( @@ -179,8 +195,9 @@ impl EmulatorAsm { .expect("Error during ROM Histogram execution") }) }); + drop(asm_resources_guard); - let (min_traces, main_count, secn_count) = self.run_mt_assembly(sm_bundle, stats); + let (min_traces, main_count, secn_count) = self.run_mt_assembly(zisk_rom, sm_bundle, stats); // Store execute steps let steps = min_traces.iter().map(|trace| trace.steps).sum::(); @@ -197,27 +214,9 @@ impl EmulatorAsm { (min_traces, main_count, secn_count, Some(handle_mo), steps) } - fn create_shmem_writer(&self, service: &asm_runner::AsmService) -> SharedMemoryWriter { - let port = AsmServices::port_base_for(self.base_port, self.local_rank); - - let shmem_input_name = shmem_input_name(port, self.local_rank); - - tracing::debug!( - "Initializing SharedMemoryWriter for service {:?} at '{}'", - service, - shmem_input_name - ); - - SharedMemoryWriter::new( - &shmem_input_name, - MAX_INPUT_SIZE as usize, - self.unlock_mapped_memory, - ) - .expect("Failed to create SharedMemoryWriter") - } - fn run_mt_assembly( &self, + zisk_rom: &ZiskRom, sm_bundle: &StaticSMBundle, stats: &ExecutorStatsHandle, ) -> (Vec, DeviceMetricsList, NestedDeviceMetricsList) { @@ -232,7 +231,6 @@ impl EmulatorAsm { let emu_traces = rayon::in_place_scope(|scope| { let on_chunk = |idx: usize, emu_trace: std::sync::Arc| { let chunk_id = ChunkId(idx); - let zisk_rom = &self.zisk_rom; let results_ref = &results_mu; scope.spawn(move |_| { stats_begin!(stats, mt_scope_id, _chunk_scope, "MT_CHUNK_PLAYER", 0); @@ -254,17 +252,21 @@ impl EmulatorAsm { }); }; - AsmRunnerMT::run_and_count( - &mut self.asm_shmem_mt.lock().unwrap(), + let asm_resources_guard = self.asm_resources.lock().unwrap(); + let asm_resources = asm_resources_guard.as_ref().expect("AsmResources not initialized"); + let result = AsmRunnerMT::run_and_count( + &mut asm_resources.asm_shmem_mt.lock().unwrap(), MAX_NUM_STEPS, self.chunk_size, on_chunk, self.world_rank, self.local_rank, - self.base_port, + asm_resources.base_port, stats.clone(), ) - .expect("Error during ASM execution") + .expect("Error during ASM execution"); + drop(asm_resources_guard); + result }); // Unwrap the Arc pointers now that all rayon tasks have completed @@ -306,9 +308,11 @@ impl EmulatorAsm { impl crate::Emulator for EmulatorAsm { fn execute( &self, + zisk_rom: &ZiskRom, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, + use_hints: bool, stats: &ExecutorStatsHandle, caller_stats_scope: &StatsScope, ) -> ( @@ -318,6 +322,6 @@ impl crate::Emulator for EmulatorAsm { Option>, u64, ) { - self.execute(stdin, pctx, sm_bundle, stats, caller_stats_scope) + self.execute(zisk_rom, stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope) } } diff --git a/executor/src/emu_asm_stub.rs b/executor/src/emu_asm_stub.rs index c2d7cd91a..92abaf37c 100644 --- a/executor/src/emu_asm_stub.rs +++ b/executor/src/emu_asm_stub.rs @@ -6,9 +6,12 @@ use std::{ use crate::{DeviceMetricsList, NestedDeviceMetricsList, StaticSMBundle}; use asm_runner::AsmRunnerMO; +use crate::AsmResources; +use anyhow::Result; use fields::PrimeField64; use proofman_common::ProofCtx; use sm_rom::RomSM; +use zisk_common::io::StreamSource; use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; use zisk_core::ZiskRom; @@ -17,23 +20,25 @@ pub struct EmulatorAsm {} impl EmulatorAsm { #[allow(clippy::too_many_arguments)] pub fn new( - _zisk_rom: Arc, _world_rank: i32, _local_rank: i32, - _base_port: Option, _unlock_mapped_memory: bool, _chunk_size: u64, _rom_sm: Option>, + _verbose_mode: proofman_common::VerboseMode, ) -> Self { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); } #[allow(clippy::type_complexity)] + #[allow(clippy::too_many_arguments)] pub fn execute( &self, + _zisk_rom: &ZiskRom, _stdin: &Mutex, _pctx: &ProofCtx, _sm_bundle: &StaticSMBundle, + _use_hints: bool, _stats: &ExecutorStatsHandle, _caller_stats_scope: &StatsScope, ) -> ( @@ -45,4 +50,20 @@ impl EmulatorAsm { ) { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); } + + pub fn set_asm_resources(&self, _asm_resources: AsmResources) { + unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); + } + + pub fn get_chunk_size(&self) -> u64 { + unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); + } + + pub fn set_hints_stream_src(&self, _stream: StreamSource) -> Result<()> { + unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); + } + + pub fn reset_hints_stream(&self) { + unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); + } } diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index cd360a833..2766f0262 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -1,8 +1,4 @@ -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, - thread::JoinHandle, -}; +use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; use asm_runner::AsmRunnerMO; use data_bus::DataBusTrait; @@ -22,9 +18,6 @@ use crate::{ }; pub struct EmulatorRust { - /// ZisK ROM, a binary file containing the ZisK program to be executed. - pub zisk_rom: Arc, - /// Chunk size for processing. chunk_size: u64, } @@ -33,8 +26,12 @@ impl EmulatorRust { /// The number of threads to use for parallel processing when computing minimal traces. const NUM_THREADS: usize = 16; - pub fn new(zisk_rom: Arc, chunk_size: u64) -> Self { - Self { zisk_rom, chunk_size } + pub fn new(chunk_size: u64) -> Self { + Self { chunk_size } + } + + pub fn get_chunk_size(&self) -> u64 { + self.chunk_size } /// Computes minimal traces by processing the ZisK ROM with the given public inputs. @@ -55,6 +52,7 @@ impl EmulatorRust { /// * `u64` - Total number of steps. pub fn execute( &self, + zisk_rom: &ZiskRom, stdin: &Mutex, sm_bundle: &StaticSMBundle, ) -> ( @@ -64,19 +62,24 @@ impl EmulatorRust { Option>, u64, ) { - let min_traces = self.run_emulator(Self::NUM_THREADS, &mut stdin.lock().unwrap()); + let min_traces = self.run_emulator(zisk_rom, Self::NUM_THREADS, &mut stdin.lock().unwrap()); // Store execute steps let steps = min_traces.iter().map(|trace| trace.steps).sum::(); timer_start_info!(COUNT); - let (main_count, secn_count) = self.count(&min_traces, sm_bundle); + let (main_count, secn_count) = self.count(zisk_rom, &min_traces, sm_bundle); timer_stop_and_log_info!(COUNT); (min_traces, main_count, secn_count, None, steps) } - fn run_emulator(&self, num_threads: usize, stdin: &mut ZiskStdin) -> Vec { + fn run_emulator( + &self, + zisk_rom: &ZiskRom, + num_threads: usize, + stdin: &mut ZiskStdin, + ) -> Vec { // Call emulate with these options let input_data = stdin.read_bytes(); @@ -87,7 +90,7 @@ impl EmulatorRust { ..EmuOptions::default() }; - ZiskEmulator::compute_minimal_traces(&self.zisk_rom, &input_data, &emu_options, num_threads) + ZiskEmulator::compute_minimal_traces(zisk_rom, &input_data, &emu_options, num_threads) .expect("Error during emulator execution") } @@ -104,6 +107,7 @@ impl EmulatorRust { /// containing the metrics for each chunk. fn count( &self, + zisk_rom: &ZiskRom, min_traces: &[EmuTrace], sm_bundle: &StaticSMBundle, ) -> (DeviceMetricsList, NestedDeviceMetricsList) { @@ -113,7 +117,7 @@ impl EmulatorRust { let mut data_bus = sm_bundle.build_data_bus_counters(); ZiskEmulator::process_emu_trace::( - &self.zisk_rom, + zisk_rom, minimal_trace, &mut data_bus, true, @@ -159,9 +163,11 @@ impl EmulatorRust { impl crate::Emulator for EmulatorRust { fn execute( &self, + zisk_rom: &ZiskRom, stdin: &Mutex, _pctx: &ProofCtx, sm_bundle: &StaticSMBundle, + _use_hints: bool, _stats: &ExecutorStatsHandle, _caller_stats_scope: &zisk_common::StatsScope, ) -> ( @@ -171,6 +177,6 @@ impl crate::Emulator for EmulatorRust { Option>, u64, ) { - self.execute(stdin, sm_bundle) + self.execute(zisk_rom, stdin, sm_bundle) } } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index b7714ed8b..8edc977c8 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -1,10 +1,9 @@ //! The `ZiskExecutor` module serves as the core orchestrator for executing the ZisK ROM program -//! and generating witness computations. It manages the execution of the state machines, from initial -//! planning to witness computation, ensuring efficient parallel processing and resource -//! utilization. +//! and generating witness computations. It manages the execution of the state machines, +//! from initial planning to witness computation. //! -//! This module handles both main and secondary state machines, integrating complex tasks such as -//! planning, configuration, and witness generation into a streamlined process. +//! This module handles both main and secondary state machines, integrating tasks such as +//! planning, configuration, and witness computation. //! //! ## Executor Workflow //! The execution is divided into distinct, sequential phases: @@ -19,51 +18,34 @@ //! By structuring these phases, the `ZiskExecutor` ensures high-performance execution while //! maintaining clarity and modularity in the computation process. +use crate::{ + state::ExecutionState, witness_orchestrator::WitnessContext, AirClassifier, AsmResources, + EmulatorKind, InstancePlanner, InstanceRegistry, RomExecutor, StaticSMBundle, + WitnessOrchestrator, +}; use fields::PrimeField64; -use pil_std_lib::Std; use proofman_common::{create_pool, BufferPool, ProofCtx, ProofmanResult, SetupCtx}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; -use sm_rom::RomInstance; -use std::sync::atomic::{AtomicUsize, Ordering}; +use sm_main::MainSM; +use std::{ + sync::{Arc, RwLock}, + time::Instant, +}; use witness::WitnessComponent; -use zisk_common::io::{StreamSource, ZiskStdin, ZiskStream}; - -use data_bus::DataBusTrait; -use sm_main::{MainInstance, MainPlanner, MainSM}; use zisk_common::{ - stats_begin, stats_end, BusDevice, BusDeviceMetrics, CheckPoint, ChunkId, EmuTrace, - ExecutorStatsHandle, Instance, InstanceCtx, InstanceType, Plan, Stats, StatsType, - ZiskExecutionResult, + io::{StreamSource, ZiskStdin}, + stats_begin, stats_end, BusDeviceMetrics, ChunkId, ExecutorStatsHandle, StatsCostPerType, + StatsType, ZiskExecutionResult, }; +use zisk_core::ZiskRom; +use zisk_pil::ZiskPublicValues; use zisk_pil::{ - ZiskPublicValues, INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SPECIFIED_RANGES_AIR_IDS, VIRTUAL_TABLE_0_AIR_IDS, VIRTUAL_TABLE_1_AIR_IDS, ZISK_AIRGROUP_ID, }; -use std::time::Instant; -use std::{ - collections::HashMap, - sync::{Arc, Mutex, RwLock}, -}; - -use crossbeam::atomic::AtomicCell; - -use zisk_common::StatsCostPerType; -use zisk_core::ZiskRom; -use ziskemu::ZiskEmulator; - -use crate::{Emulator, EmulatorKind, StaticSMBundle}; - use anyhow::Result; pub type DeviceMetricsByChunk = (ChunkId, Box); // (chunk_id, metrics) -type ChunkCollector = (usize, Box>); - -#[allow(dead_code)] -enum MinimalTraceExecutionMode { - Emulator, - AsmWithCounter, -} /// The maximum number of steps to execute in the emulator or assembly runner. pub const MAX_NUM_STEPS: u64 = 1 << 36; @@ -71,769 +53,199 @@ pub const MAX_NUM_STEPS: u64 = 1 << 36; /// The `ZiskExecutor` struct orchestrates the execution of the ZisK ROM program, managing state /// machines, planning, and witness computation. pub struct ZiskExecutor { - /// Standard input for the ZisK program execution. - stdin: Mutex, - - /// The emulator backend used for execution. - emulator: EmulatorKind, - - /// Chunk size for processing. - chunk_size: u64, - - /// Pipeline for handling precompile hints. - hints_stream: Mutex>, - - /// ZisK ROM, a binary file containing the ZisK program to be executed. - zisk_rom: Arc, - - /// Planning information for main state machines. - min_traces: Arc>>>, - - /// Planning information for secondary state machines. - secn_planning: RwLock>, - - /// Main state machine instances, indexed by their global ID. - main_instances: RwLock>>, - - /// Secondary state machine instances, indexed by their global ID. - secn_instances: RwLock>>>, - - /// Standard library instance, providing common functionalities. - std: Arc>, - - /// Execution result, including the number of executed steps. - execution_result: Mutex, - - /// State machine bundle, containing the state machines and their configurations. - sm_bundle: StaticSMBundle, - - /// Collectors by instance, storing statistics and collectors for each instance. - collectors_by_instance: Arc>>>>, - - /// Statistics collected during the execution, including time taken for collection and witness computation. - stats: ExecutorStatsHandle, + /// Shared execution state. + state: ExecutionState, + /// ROM executor component. + rom_executor: RomExecutor, + /// Instance planner component. + planner: InstancePlanner, + /// Instance registry component. + registry: InstanceRegistry, + /// Witness orchestrator component. + orchestrator: WitnessOrchestrator, } impl ZiskExecutor { /// Creates a new instance of the `ZiskExecutor`. /// + /// The ROM can be set or changed via `set_rom()` before calling `execute()`. + /// /// # Arguments - /// * `zisk_rom` - An `Arc`-wrapped ZisK ROM instance. + /// * `std` - Standard library instance. + /// * `sm_bundle` - State machine bundle. + /// * `chunk_size` - Chunk size for processing. + /// * `emulator` - Emulator backend to use. /// * `hints_stream` - Optional hints stream for processing precompile hints. #[allow(clippy::too_many_arguments)] - pub fn new( - zisk_rom: Arc, - std: Arc>, - sm_bundle: StaticSMBundle, - chunk_size: u64, - emulator: EmulatorKind, - hints_stream: Option, - ) -> Self { - Self { - stdin: Mutex::new(ZiskStdin::null()), - emulator, - chunk_size, - hints_stream: Mutex::new(hints_stream), - zisk_rom, - min_traces: Arc::new(RwLock::new(None)), - secn_planning: RwLock::new(Vec::new()), - main_instances: RwLock::new(HashMap::new()), - secn_instances: RwLock::new(HashMap::new()), - collectors_by_instance: Arc::new(RwLock::new(HashMap::new())), - std, - execution_result: Mutex::new(ZiskExecutionResult::default()), - sm_bundle, - stats: ExecutorStatsHandle::new(), - } - } - - pub fn set_stdin(&self, stdin: ZiskStdin) { - let mut guard = self.stdin.lock().unwrap(); - *guard = stdin; - } + pub fn new(sm_bundle: StaticSMBundle, emulator: EmulatorKind) -> Self { + let sm_bundle = Arc::new(sm_bundle); + let is_asm_emulator = emulator.is_asm_emulator(); + let chunk_size = emulator.get_chunk_size(); - pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { - if let Some(hints_stream) = self.hints_stream.lock().unwrap().as_mut() { - hints_stream.set_hints_stream_src(stream) - } else { - Err(anyhow::anyhow!("No hints stream configured")) - } - } - - #[allow(clippy::type_complexity)] - pub fn get_execution_result(&self) -> (ZiskExecutionResult, ExecutorStatsHandle) { - (self.execution_result.lock().unwrap().clone(), self.stats.clone()) - } - - pub fn store_stats(&self) { - self.stats.store_stats(); - } - - /// Adds main state machine instances to the proof context and assigns global IDs. - /// - /// # Arguments - /// * `pctx` - Proof context. - /// * `main_planning` - Planning information for main state machines. - fn assign_main_instances( - &self, - pctx: &ProofCtx, - sctx: &SetupCtx, - global_ids: &RwLock>, - main_planning: Vec, - ) -> u64 { - let mut main_instances = self.main_instances.write().unwrap(); - - let setup_main = sctx.get_setup(ZISK_AIRGROUP_ID, MAIN_AIR_IDS[0]).unwrap(); - let n_bits = setup_main.stark_info.stark_struct.n_bits; - let total_cols: u64 = setup_main - .stark_info - .map_sections_n - .iter() - .filter(|(key, _)| *key != "const") - .map(|(_, value)| *value) - .sum(); - let cost = (1 << n_bits) * total_cols; - let total_cost = cost * main_planning.len() as u64; - - for mut plan in main_planning { - let global_id = pctx - .add_instance_assign(plan.airgroup_id, plan.air_id) - .expect("Failed to add instance"); - plan.set_global_id(global_id); - global_ids.write().unwrap().push(global_id); - main_instances - .entry(global_id) - .or_insert_with(|| self.create_main_instance(plan, global_id)); - } - - total_cost - } - - /// Creates main state machine instance based on a main planning. - /// - /// # Arguments - /// * `global_id` - Global ID of the main instance to be created. - /// - /// # Returns - /// A main instance for the provided global ID. - fn create_main_instance(&self, plan: Plan, global_id: usize) -> MainInstance { - MainInstance::new(InstanceCtx::new(global_id, plan), self.std.clone()) - } - - /// Adds secondary state machine instances to the proof context and assigns global IDs. - /// - /// # Arguments - /// * `pctx` - Proof context. - /// * `secn_planning` - Planning information for secondary state machines. - fn assign_secn_instances( - &self, - pctx: &ProofCtx, - global_ids: &RwLock>, - secn_planning: &mut [Plan], - ) { - for plan in secn_planning.iter_mut() { - // If the node has rank 0 and the plan targets the ROM instance, - // we need to add it to the proof context using a special method. - // This method allows us to mark it as an instance to be computed by node 0. - let global_id = if plan.airgroup_id == ZISK_AIRGROUP_ID && plan.air_id == ROM_AIR_IDS[0] - { - // If this is the ROM instance, we need to add it to the proof context - // with the rank 0. - pctx.add_instance_assign_first_partition(plan.airgroup_id, plan.air_id) - .expect("Failed to add ROM instance") - } else { - match plan.instance_type { - InstanceType::Instance => pctx - .add_instance(plan.airgroup_id, plan.air_id) - .expect("Failed to add instance"), - InstanceType::Table => { - pctx.add_table(plan.airgroup_id, plan.air_id).expect("Failed to add table") - } - } - }; - - global_ids.write().unwrap().push(global_id); - plan.set_global_id(global_id); + Self { + state: ExecutionState::new(), + rom_executor: RomExecutor::new(emulator), + planner: InstancePlanner::new(chunk_size), + registry: InstanceRegistry::new(sm_bundle.clone()), + orchestrator: WitnessOrchestrator::new(chunk_size, sm_bundle, is_asm_emulator), } } - /// Creates a secondary state machine instance based on the provided global ID. + /// Sets the ZisK ROM (ELF) for execution. /// - /// # Arguments - /// * `global_id` - Global ID of the secondary state machine instance. - /// - /// # Returns - /// A secondary state machine instance for the provided global ID. - fn create_secn_instance(&self, global_id: usize) -> Box> { - let mut secn_planning_guard = self.secn_planning.write().unwrap(); - - let plan_idx = - secn_planning_guard.iter().position(|plan| plan.global_id.unwrap() == global_id); - if plan_idx.is_none() { - panic!("Secondary instance not found"); - } - - let plan_idx = plan_idx.unwrap(); - let plan = secn_planning_guard.remove(plan_idx); - - let global_id = plan.global_id.unwrap(); - - let ictx = InstanceCtx::new(global_id, plan); - self.sm_bundle.build_instance(ictx) - } - - /// Expands and computes witnesses for a main instance. + /// This method allows changing the ROM between executions without + /// recreating the executor, making the executor more reusable. /// /// # Arguments - /// * `pctx` - Proof context. - /// * `main_instance` - Main instance to compute witness for - fn witness_main_instance( - &self, - pctx: &ProofCtx, - main_instance: &MainInstance, - trace_buffer: Vec, - _caller_stats_id: u64, - ) -> ProofmanResult<()> { - let (airgroup_id, air_id) = pctx - .dctx_get_instance_info(main_instance.ictx.global_id) - .expect("Failed to get instance info"); - let witness_start_time = Instant::now(); - - stats_begin!(self.stats, _caller_stats_id, _stats_scope, "AIR_MAIN_WITNESS", air_id); - - let min_traces_guard = self.min_traces.read().unwrap(); - let min_traces = min_traces_guard.as_ref().expect("min_traces should not be None"); - - let air_instance = main_instance.compute_witness( - &self.zisk_rom, - min_traces, - self.chunk_size, - main_instance, - trace_buffer, - )?; - - pctx.add_air_instance(air_instance, main_instance.ictx.global_id); - - stats_end!(self.stats, &_stats_scope); - - let stats = Stats { - airgroup_id, - air_id, - collect_start_time: Instant::now(), - collect_duration: 0, - witness_start_time: Instant::now(), - witness_duration: witness_start_time.elapsed().as_millis(), - num_chunks: 0, - }; - - self.stats.insert_witness_stats(main_instance.ictx.global_id, stats); - - Ok(()) - } - - /// computes witness for a secondary state machines instance. - /// - /// # Arguments - /// * `pctx` - Proof context. - /// * `sctx` - Setup context. - /// * `global_id` - Global ID of the secondary state machine instance. - /// * `secn_instance` - Secondary state machine instance to compute witness for - fn witness_secn_instance( - &self, - pctx: &ProofCtx, - sctx: &SetupCtx, - global_id: usize, - secn_instance: &dyn Instance, - trace_buffer: Vec, - _caller_stats_id: u64, - ) -> ProofmanResult<()> { - let witness_start_time = Instant::now(); - - #[cfg(feature = "stats")] - let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id)?; - stats_begin!(self.stats, _caller_stats_id, _stats_scope, "AIR_SECN_WITNESS", air_id); - - let collectors_by_instance = { - let mut guard = self.collectors_by_instance.write().unwrap(); - - guard - .remove(&global_id) - .expect("Missing collectors for given global_id") - .into_iter() - .enumerate() - .map(|(idx, opt)| { - opt.unwrap_or_else(|| { - panic!("Collector at index {} for global_id {} is None", idx, global_id) - }) - }) - .collect() - }; - - if let Some(air_instance) = - secn_instance.compute_witness(pctx, sctx, collectors_by_instance, trace_buffer)? - { - pctx.add_air_instance(air_instance, global_id); - } - - stats_end!(self.stats, &_stats_scope); - - self.stats.set_witness_duration(global_id, witness_start_time.elapsed().as_millis()); - Ok(()) + /// * `zisk_rom` - The ZisK ROM to execute. + pub fn set_rom(&self, zisk_rom: Arc, use_hints: bool) { + self.state.set_rom(zisk_rom.clone(), use_hints); + self.orchestrator.set_rom(zisk_rom); } - fn order_chunks( - &self, - chunks_to_execute: &[Vec], - global_id_chunks: &HashMap>, - ) -> Vec { - let mut ordered_chunks = Vec::new(); - let mut already_selected_chunks = vec![false; chunks_to_execute.len()]; - - let mut n_global_ids_incompleted = global_id_chunks.len(); - let mut n_chunks_by_global_id: HashMap = - global_id_chunks.iter().map(|(global_id, chunks)| (*global_id, chunks.len())).collect(); - - while n_global_ids_incompleted > 0 { - let selected_global_id = n_chunks_by_global_id - .iter() - .filter(|(_, &count)| count > 0) - .min_by_key(|(_, &count)| count) - .map(|(&global_id, _)| global_id); - - if let Some(global_id) = selected_global_id { - for chunk_id in global_id_chunks[&global_id].iter() { - if already_selected_chunks[*chunk_id] { - continue; - } - ordered_chunks.push(*chunk_id); - already_selected_chunks[*chunk_id] = true; - for global_idx in chunks_to_execute[*chunk_id].iter() { - if let Some(count) = n_chunks_by_global_id.get_mut(global_idx) { - *count -= 1; - if *count == 0 { - n_chunks_by_global_id.remove(global_idx); - n_global_ids_incompleted -= 1; - } - } - } - } - } else { - break; - } - } - - ordered_chunks + /// Sets the standard input for execution. + pub fn set_stdin(&self, stdin: ZiskStdin) { + self.rom_executor.set_stdin(stdin); } - /// Expands for a secondary state machines instance. - /// - /// # Arguments - /// * `pctx` - Proof context. - /// * `sctx` - Setup context. - /// * `global_id` - Global ID of the secondary state machine instance. - /// * `secn_instance` - Secondary state machine instance to compute witness for - #[allow(clippy::borrowed_box)] - fn witness_collect_instances( - &self, - pctx: Arc>, - secn_instances: HashMap>>, - ) { - let min_traces_guard = self.min_traces.read().unwrap(); - let min_traces = min_traces_guard.as_ref().expect("min_traces should not be None"); - - // Group the instances by the chunk they need to process - let (chunks_to_execute, global_id_chunks) = - self.chunks_to_execute(min_traces, &secn_instances); - - let ordered_chunks = self.order_chunks(&chunks_to_execute, &global_id_chunks); - let global_ids: Vec = secn_instances.keys().copied().collect(); - - let collect_start_times: Vec>> = - global_ids.iter().map(|_| AtomicCell::new(None)).collect(); - - let global_ids_map: HashMap = - global_ids.iter().enumerate().map(|(idx, &id)| (id, idx)).collect(); - - // Create data buses for each chunk - let data_buses = self - .sm_bundle - .build_data_bus_collectors(&pctx, &secn_instances, &chunks_to_execute) - .into_iter() - .map(Mutex::new) - .collect::>(); - - let n_chunks_left: Vec = global_ids - .iter() - .map(|global_id| AtomicUsize::new(global_id_chunks[global_id].len())) - .collect(); - - for global_id in global_ids.iter() { - let (airgroup_id, air_id) = - pctx.dctx_get_instance_info(*global_id).expect("Failed to get instance info"); - let stats = Stats { - airgroup_id, - air_id, - collect_start_time: Instant::now(), - collect_duration: 0, - witness_start_time: Instant::now(), - witness_duration: 0, - num_chunks: global_id_chunks[global_id].len(), - }; - - self.collectors_by_instance - .write() - .unwrap() - .insert(*global_id, (0..global_id_chunks[global_id].len()).map(|_| None).collect()); - self.stats.insert_witness_stats(*global_id, stats); - } - - let next_chunk = AtomicUsize::new(0); - - rayon::in_place_scope(|scope| { - for _ in 0..rayon::current_num_threads() { - let next_chunk = &next_chunk; - let n_chunks_left = &n_chunks_left; - let collectors_by_instance = &self.collectors_by_instance; - let collect_start_times = &collect_start_times; - let _stats = &self.stats; - let min_traces = &min_traces; - let data_buses = &data_buses; - let zisk_rom = &self.zisk_rom; - let global_ids_map = &global_ids_map; - let global_id_chunks = &global_id_chunks; - let ordered_chunks = &ordered_chunks; - let chunks_to_execute = &chunks_to_execute; - let pctx = &pctx; - - scope.spawn(move |_| loop { - let next_chunk_id = next_chunk.fetch_add(1, Ordering::Relaxed); - if next_chunk_id >= ordered_chunks.len() { - break; - } - let chunk_id = ordered_chunks[next_chunk_id]; - - if let Some(mut data_bus) = data_buses[chunk_id].lock().unwrap().take() { - for global_id in chunks_to_execute[chunk_id].iter() { - let start_time_cell = &collect_start_times[global_ids_map[global_id]]; - if start_time_cell.load().is_none() { - start_time_cell.store(Some(Instant::now())); - } - } - - ZiskEmulator::process_emu_traces::( - zisk_rom, - min_traces, - chunk_id, - &mut data_bus, - ); - - // Collect all device results locally to minimize lock acquisitions - let devices = data_bus.into_devices(false); - let mut entries: Vec<(usize, usize, Option)> = Vec::new(); - let mut affected_globals: Vec<(usize, usize)> = Vec::new(); - - for (global_id, collector) in devices { - if let Some(global_id) = global_id { - let global_id_idx = *global_ids_map - .get(&global_id) - .expect("Global ID not found in map"); - - let chunk_order = &global_id_chunks[&global_id]; - let position = chunk_order - .iter() - .position(|&id| id == chunk_id) - .expect("Chunk ID not found in order"); - - entries.push(( - global_id, - position, - Some((chunk_id, collector.unwrap())), - )); - affected_globals.push((global_id, global_id_idx)); - } - } - - // Single write-lock acquisition to flush all results from this chunk - { - let mut guard = collectors_by_instance.write().unwrap(); - for (global_id, position, entry) in entries.iter_mut() { - guard.get_mut(global_id).unwrap()[*position] = entry.take(); - } - } - - // Update atomic counters and mark ready instances (no lock needed) - for (global_id, global_id_idx) in affected_globals { - if n_chunks_left[global_id_idx].fetch_sub(1, Ordering::SeqCst) == 1 { - pctx.set_witness_ready(global_id, true); - - let collect_start_time = collect_start_times[global_id_idx] - .load() - .expect("Collect start time was not set"); - let collect_duration = - collect_start_time.elapsed().as_millis() as u64; - - let (airgroup_id, air_id) = pctx - .dctx_get_instance_info(global_id) - .expect("Failed to get instance info"); - let stats = Stats { - airgroup_id, - air_id, - collect_start_time, - collect_duration, - witness_start_time: Instant::now(), - witness_duration: 0, - num_chunks: global_id_chunks[&global_id].len(), - }; - - _stats.insert_witness_stats(global_id, stats); - } - } - } - }); - } - }); + /// Sets ASM resources for execution (only applicable for ASM emulator). + pub fn set_asm_resources(&self, asm_resources: AsmResources) { + self.rom_executor.set_asm_resources(asm_resources); } - /// Computes and generates witness for secondary state machine instance of type `Table`. - /// - /// # Arguments - /// * `pctx` - Proof context. - /// * `sctx` - Setup context. - /// * `global_id` - Global ID of the secondary state machine instance. - /// * `table_instance` - Secondary state machine table instance to compute witness for - fn witness_table( - &self, - pctx: &ProofCtx, - sctx: &SetupCtx, - global_id: usize, - table_instance: &dyn Instance, - trace_buffer: Vec, - _caller_stats_id: u64, - ) -> ProofmanResult<()> { - #[cfg(feature = "stats")] - let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id)?; - stats_begin!(self.stats, _caller_stats_id, _stats_scope, "AIR_WITNESS_TABLE", air_id); - - assert_eq!(table_instance.instance_type(), InstanceType::Table, "Instance is not a table"); - - if let Some(air_instance) = - table_instance.compute_witness(pctx, sctx, vec![], trace_buffer)? - { - if pctx - .dctx_is_my_process_instance(global_id) - .expect("Failed to check instance ownership") - { - pctx.add_air_instance(air_instance, global_id); - } - } - - stats_end!(self.stats, &_stats_scope); - - Ok(()) + /// Sets the hints stream source. + pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { + self.rom_executor.set_hints_stream_src(stream) } - /// Computes all the chunks to be executed to generate the witness given an instance. - /// - /// # Arguments - /// * `min_traces` - Minimal traces - /// * `secn_instance` - Secondary state machine instance to group. - /// - /// # Returns - /// A vector of booleans indicating which chunks to execute. - #[allow(clippy::borrowed_box)] - fn chunks_to_execute( - &self, - min_traces: &[EmuTrace], - secn_instances: &HashMap>>, - ) -> (Vec>, HashMap>) { - let mut chunks_to_execute = vec![Vec::new(); min_traces.len()]; - let mut global_id_chunks: HashMap> = HashMap::new(); - secn_instances.iter().for_each(|(global_idx, secn_instance)| { - match secn_instance.check_point() { - CheckPoint::None => {} - CheckPoint::Single(chunk_id) => { - chunks_to_execute[chunk_id.as_usize()].push(*global_idx); - global_id_chunks.entry(*global_idx).or_default().push(chunk_id.as_usize()); - } - CheckPoint::Multiple(chunk_ids) => { - chunk_ids.iter().for_each(|&chunk_id| { - chunks_to_execute[chunk_id.as_usize()].push(*global_idx); - global_id_chunks.entry(*global_idx).or_default().push(chunk_id.as_usize()); - }); - } - } - }); - - for chunk_ids in global_id_chunks.values_mut() { - chunk_ids.sort(); - } - - (chunks_to_execute, global_id_chunks) + /// Gets the execution result and stats. + #[allow(clippy::type_complexity)] + pub fn get_execution_result(&self) -> (ZiskExecutionResult, ExecutorStatsHandle) { + (self.state.get_execution_result(), self.state.get_stats()) } - fn reset(&self) { - // Reset the internal state of the executor - *self.execution_result.lock().unwrap() = ZiskExecutionResult::default(); - *self.min_traces.write().unwrap() = None; - *self.secn_planning.write().unwrap() = Vec::new(); - self.main_instances.write().unwrap().clear(); - self.secn_instances.write().unwrap().clear(); - self.collectors_by_instance.write().unwrap().clear(); - self.stats.reset(); + /// Stores statistics to persistent storage. + pub fn store_stats(&self) { + self.state.stats.store_stats(); } } impl WitnessComponent for ZiskExecutor { /// Executes the ZisK ROM program and calculate the plans for main and secondary state machines. - /// - /// # Arguments - /// * `pctx` - Proof context. - /// - /// # Returns - /// A vector of global IDs for the instances to compute witness for. fn execute( &self, pctx: Arc>, sctx: Arc>, global_ids: &RwLock>, ) -> ProofmanResult<()> { - self.reset(); + self.state.reset(); - stats_begin!(self.stats, 0, _exec_scope, "EXECUTE", 0); + stats_begin!(self.state.stats, 0, _exec_scope, "EXECUTE", 0); // Set the start time of the current execution - self.stats.set_start_time(Instant::now()); - - // Process and write precompile atomically - if let Ok(mut hints_stream_guard) = self.hints_stream.lock() { - if let Some(hints_stream) = hints_stream_guard.as_mut() { - let _ = hints_stream.start_stream(); - } - } + self.state.stats.set_start_time(Instant::now()); - // Process the ROM to collect the Minimal Traces + // Phase 1: Execute ROM to collect minimal traces timer_start_info!(COMPUTE_MINIMAL_TRACE); - let (min_traces, main_count, mut secn_count, handle_mo, steps) = - self.emulator.execute(&self.stdin, &pctx, &self.sm_bundle, &self.stats, &_exec_scope); + let zisk_rom = self + .state + .get_rom() + .map_err(|e| proofman_common::ProofmanError::InvalidSetup(e.to_string()))?; + let output = self.rom_executor.execute( + &zisk_rom, + &pctx, + self.registry.sm_bundle(), + self.state.use_hints.load(std::sync::atomic::Ordering::SeqCst), + &self.state.stats, + &_exec_scope, + ); timer_stop_and_log_info!(COMPUTE_MINIMAL_TRACE); - // Plan the main and secondary instances using the counted metrics - stats_begin!(self.stats, &_exec_scope, _main_plan_scope, "MAIN_PLAN", 0); + // Phase 2: Plan main instances + stats_begin!(self.state.stats, &_exec_scope, _main_plan_scope, "MAIN_PLAN", 0); timer_start_info!(PLAN); - let (main_planning, public_values) = - MainPlanner::plan::(&min_traces, main_count, self.chunk_size); - *self.min_traces.write().unwrap() = Some(min_traces); - let cost_main = self.assign_main_instances(&pctx, &sctx, global_ids, main_planning); + let main_output = self.planner.plan_main::(&output.min_traces, output.main_count); + *self.state.min_traces.write().unwrap() = Some(output.min_traces); - stats_end!(self.stats, &_main_plan_scope); - stats_begin!(self.stats, &_exec_scope, _secn_plan_scope, "SECN_PLAN", 0); + let (main_assignments, cost_main) = + self.planner.assign_main_instances(&pctx, &sctx, global_ids, main_output.plans); + self.registry.populate_main_instances(&self.state, main_assignments); - let mut secn_planning = self.sm_bundle.plan_sec(&mut secn_count); + stats_end!(self.state.stats, &_main_plan_scope); + + // Phase 3: Plan secondary instances + stats_begin!(self.state.stats, &_exec_scope, _secn_plan_scope, "SECN_PLAN", 0); + + let mut secn_count = output.secn_count; + let mut secn_planning = + self.planner.plan_secondary(self.registry.sm_bundle(), &mut secn_count); timer_stop_and_log_info!(PLAN); timer_start_info!(PLAN_MEM_CPP); - stats_end!(self.stats, &_secn_plan_scope); + stats_end!(self.state.stats, &_secn_plan_scope); - if let Some(handle_mo) = handle_mo { - stats_begin!(self.stats, &_exec_scope, _mo_wait_scope, "MO_PLAN_WAIT", 0); + // Handle memory operations from ASM runner + if let Some(handle_mo) = output.handle_mo { + stats_begin!(self.state.stats, &_exec_scope, _mo_wait_scope, "MO_PLAN_WAIT", 0); - // Wait for the memory operations thread to finish let asm_runner_mo = handle_mo.join().expect("Error during Assembly Memory Operations thread execution"); - stats_end!(self.stats, &_mo_wait_scope); - stats_begin!(self.stats, &_exec_scope, _mo_add_scope, "MO_PLAN_ADD", 0); + stats_end!(self.state.stats, &_mo_wait_scope); + stats_begin!(self.state.stats, &_exec_scope, _mo_add_scope, "MO_PLAN_ADD", 0); secn_planning - .entry(self.sm_bundle.get_mem_sm_id()) + .entry(self.registry.sm_bundle().get_mem_sm_id()) .or_default() .extend(asm_runner_mo.plans); - stats_end!(self.stats, &_mo_add_scope); + stats_end!(self.state.stats, &_mo_add_scope); } timer_stop_and_log_info!(PLAN_MEM_CPP); - stats_begin!(self.stats, &_exec_scope, _config_scope, "CONFIGURE_INSTANCES", 0); + // Phase 4: Configure and assign secondary instances + stats_begin!(self.state.stats, &_exec_scope, _config_scope, "CONFIGURE_INSTANCES", 0); - // Configure the instances - self.sm_bundle.configure_instances(&pctx, &secn_planning); + // Configure secondary state machine instances based on planning + self.registry.configure_sm_instances(&pctx, &secn_planning); - // Flatten all plans - let mut secn_planning = - secn_planning.into_iter().flat_map(|(_, plans)| plans).collect::>(); + let mut cost_per_type = StatsCostPerType::default(); + cost_per_type.add_cost(StatsType::Main, cost_main); + + let mut secn_planning: Vec<_> = + secn_planning.into_iter().flat_map(|(_, plans)| plans).collect(); - // Assign the instances - self.assign_secn_instances(&pctx, global_ids, &mut secn_planning); + self.planner.assign_secn_instances(&pctx, global_ids, &mut secn_planning); - // Get the global IDs of the instances to compute witness for - let secn_global_ids = - secn_planning.iter().map(|plan| plan.global_id.unwrap()).collect::>(); - let secn_global_ids_vec: Vec = secn_global_ids.to_vec(); + let secn_global_ids: Vec = + secn_planning.iter().map(|plan| plan.global_id.unwrap()).collect(); // Add public values to the proof context let mut publics = ZiskPublicValues::from_vec_guard(pctx.get_publics()); - for (index, value) in public_values.iter() { + for (index, value) in main_output.public_values.iter() { publics.inputs[*index as usize] = F::from_u32(*value); } drop(publics); - // Update internal state with the computed minimal traces and planning. - *self.secn_planning.write().unwrap() = secn_planning; + // Store secondary planning in execution state + *self.state.secn_planning.write().unwrap() = secn_planning; - let mut cost_per_type = StatsCostPerType::default(); - cost_per_type.add_cost(StatsType::Main, cost_main); + // Create secondary instances + self.registry.populate_secn_instances(&self.state, &secn_global_ids); - let mut secn_instances = self.secn_instances.write().unwrap(); - for global_id in &secn_global_ids_vec { - secn_instances - .entry(*global_id) - .or_insert_with(|| self.create_secn_instance(*global_id)); - secn_instances[global_id].reset(); - let (airgroup_id, air_id) = pctx.dctx_get_instance_info(*global_id)?; - if secn_instances[global_id].instance_type() == InstanceType::Instance { - let checkpoint = secn_instances[global_id].check_point(); - let chunks = match checkpoint { - CheckPoint::None => vec![], - CheckPoint::Single(chunk_id) => vec![chunk_id.as_usize()], - CheckPoint::Multiple(chunk_ids) => { - chunk_ids.iter().map(|id| id.as_usize()).collect() - } - }; - let mem_global_id = air_id == MEM_AIR_IDS[0] - || air_id == ROM_DATA_AIR_IDS[0] - || air_id == INPUT_DATA_AIR_IDS[0]; - pctx.dctx_set_chunks(*global_id, chunks, mem_global_id); - } + // Configure instance checkpoints using registry method + self.registry.configure_checkpoints(&pctx, &self.state, &secn_global_ids); - let setup = sctx.get_setup(airgroup_id, air_id)?; - let n_bits = setup.stark_info.stark_struct.n_bits; - let total_cols: u64 = setup - .stark_info - .map_sections_n - .iter() - .filter(|(key, _)| *key != "const") - .map(|(_, value)| *value) - .sum(); - let cost = (1 << n_bits) * total_cols; - cost_per_type.add_cost(secn_instances[global_id].stats_type(), cost); - } + // Reset hints stream + self.rom_executor.reset_hints_stream(); - if let Ok(mut hints_stream_guard) = self.hints_stream.lock() { - if let Some(hints_stream) = hints_stream_guard.as_mut() { - hints_stream.reset(); - } - } - - stats_end!(self.stats, &_config_scope); - stats_end!(self.stats, &_exec_scope); - - // #[cfg(feature = "stats")] - // self.stats.store_stats(); + stats_end!(self.state.stats, &_config_scope); + stats_end!(self.state.stats, &_exec_scope); let tables_air_ids = [SPECIFIED_RANGES_AIR_IDS[0], VIRTUAL_TABLE_0_AIR_IDS[0], VIRTUAL_TABLE_1_AIR_IDS[0]]; @@ -852,19 +264,15 @@ impl WitnessComponent for ZiskExecutor { } // Store the execution result - let execution_result = ZiskExecutionResult::new(steps, cost_per_type); - *self.execution_result.lock().unwrap() = execution_result; + let execution_result = ZiskExecutionResult::new(output.steps, cost_per_type); + + // Store the execution result + self.state.set_execution_result(execution_result); Ok(()) } /// Computes the witness for the main and secondary state machines. - /// - /// # Arguments - /// * `stage` - The current stage id - /// * `pctx` - Proof context. - /// * `sctx` - Setup context. - /// * `global_ids` - Global IDs of the instances to compute witness for. fn calculate_witness( &self, stage: u32, @@ -878,77 +286,18 @@ impl WitnessComponent for ZiskExecutor { return Ok(()); } - stats_begin!(self.stats, 0, _witness_scope, "CALCULATE_WITNESS", 0); - - let is_asm_emulator = self.emulator.is_asm_emulator(); + stats_begin!(self.state.stats, 0, _witness_scope, "CALCULATE_WITNESS", 0); let pool = create_pool(n_cores); pool.install(|| -> ProofmanResult<()> { + let ctx = WitnessContext::new(&pctx, &sctx, &self.state, buffer_pool, &_witness_scope); for &global_id in global_ids { - let (airgroup_id, air_id) = - pctx.dctx_get_instance_info(global_id).expect("Failed to get instance info"); - - if MAIN_AIR_IDS.contains(&air_id) { - let main_instance = &self.main_instances.read().unwrap()[&global_id]; - - self.witness_main_instance( - &pctx, - main_instance, - buffer_pool.take_buffer(), - _witness_scope.id(), - )?; - } else { - let secn_instance = &self.secn_instances.read().unwrap()[&global_id]; - - match secn_instance.instance_type() { - InstanceType::Instance => { - if !self.collectors_by_instance.read().unwrap().contains_key(&global_id) - { - if air_id == ROM_AIR_IDS[0] && is_asm_emulator { - let stats = Stats { - airgroup_id, - air_id, - collect_start_time: Instant::now(), - collect_duration: 0, - witness_start_time: Instant::now(), - witness_duration: 0, - num_chunks: 0, - }; - - self.collectors_by_instance - .write() - .unwrap() - .insert(global_id, Vec::new()); - self.stats.insert_witness_stats(global_id, stats); - } else { - let mut secn_instances = HashMap::new(); - secn_instances.insert(global_id, secn_instance); - self.witness_collect_instances(pctx.clone(), secn_instances); - } - } - self.witness_secn_instance( - &pctx, - &sctx, - global_id, - &**secn_instance, - buffer_pool.take_buffer(), - _witness_scope.id(), - )?; - } - InstanceType::Table => self.witness_table( - &pctx, - &sctx, - global_id, - &**secn_instance, - Vec::new(), - _witness_scope.id(), - )?, - } - } + self.orchestrator.compute_witness_for_instance(&ctx, global_id)?; } Ok(()) })?; - stats_end!(self.stats, &_witness_scope); + + stats_end!(self.state.stats, &_witness_scope); Ok(()) } @@ -962,76 +311,22 @@ impl WitnessComponent for ZiskExecutor { n_cores: usize, _buffer_pool: &dyn BufferPool, ) -> ProofmanResult<()> { - stats_begin!(self.stats, 0, _pre_scope, "PRE_CALCULATE_WITNESS", 0); + stats_begin!(self.state.stats, 0, _pre_scope, "PRE_CALCULATE_WITNESS", 0); if stage != 1 { return Ok(()); } - let secn_instances_guard = self.secn_instances.read().unwrap(); - - let is_asm_emulator = self.emulator.is_asm_emulator(); - - let mut secn_instances = HashMap::new(); - for &global_id in global_ids { - let (airgroup_id, air_id) = - pctx.dctx_get_instance_info(global_id).expect("Failed to get instance info"); - if MAIN_AIR_IDS.contains(&air_id) { - pctx.set_witness_ready(global_id, false); - } else if air_id == ROM_AIR_IDS[0] { - if is_asm_emulator { - pctx.set_witness_ready(global_id, false); - } else { - let secn_instance = &secn_instances_guard[&global_id]; - let rom_instance = - secn_instance.as_any().downcast_ref::().unwrap(); - if rom_instance.skip_collector() { - let stats = Stats { - airgroup_id, - air_id, - collect_start_time: Instant::now(), - collect_duration: 0, - witness_start_time: Instant::now(), - witness_duration: 0, - num_chunks: 0, - }; - - self.collectors_by_instance.write().unwrap().insert(global_id, Vec::new()); - self.stats.insert_witness_stats(global_id, stats); - pctx.set_witness_ready(global_id, true); - } else { - secn_instances.insert(global_id, secn_instance); - } - } - } else { - let secn_instance = &secn_instances_guard[&global_id]; - - if secn_instance.instance_type() == InstanceType::Instance - && !self.collectors_by_instance.read().unwrap().contains_key(&global_id) - { - secn_instances.insert(global_id, secn_instance); - } else { - pctx.set_witness_ready(global_id, true); - } - } - } let pool = create_pool(n_cores); - pool.install(|| { - if !secn_instances.is_empty() { - self.witness_collect_instances(pctx.clone(), secn_instances); - } - }); + let result = + pool.install(|| self.orchestrator.pre_calculate(&pctx, &self.state, global_ids)); + result?; - stats_end!(self.stats, &_pre_scope); + stats_end!(self.state.stats, &_pre_scope); Ok(()) } /// Debugs the main and secondary state machines. - /// - /// # Arguments - /// * `pctx` - Proof context. - /// * `sctx` - Setup context. - /// * `global_ids` - Global IDs of the instances to debug. fn debug( &self, pctx: Arc>, @@ -1042,10 +337,10 @@ impl WitnessComponent for ZiskExecutor { let (_airgroup_id, air_id) = pctx.dctx_get_instance_info(global_id).expect("Failed to get instance info"); - if MAIN_AIR_IDS.contains(&air_id) { + if AirClassifier::is_main(air_id) { MainSM::debug(&pctx, &sctx); } else { - let secn_instances = self.secn_instances.read().unwrap(); + let secn_instances = self.state.secn_instances.read().unwrap(); let secn_instance = secn_instances.get(&global_id).expect("Instance not found"); secn_instance.debug(&pctx, &sctx); diff --git a/executor/src/lib.rs b/executor/src/lib.rs index e0e0cd9f4..625e01dee 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -1,3 +1,6 @@ +mod air_classifier; +mod asm_resources; +mod collector; mod dummy_counter; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod emu_asm; @@ -5,10 +8,22 @@ mod emu_asm; mod emu_asm_stub; mod emu_rust; mod executor; +mod planner; +mod registry; +mod rom_executor; mod sm_static_bundle; +mod state; mod static_data_bus; mod static_data_bus_collect; +mod utils; +use anyhow::Result; +mod witness_generator; +mod witness_orchestrator; + +use air_classifier::*; +pub use asm_resources::*; +use collector::*; pub use dummy_counter::*; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub use emu_asm::*; @@ -16,9 +31,18 @@ pub use emu_asm::*; pub use emu_asm_stub::*; pub use emu_rust::*; pub use executor::*; +use planner::*; +use registry::*; +use rom_executor::*; pub use sm_static_bundle::*; +pub use state::*; pub use static_data_bus::*; pub use static_data_bus_collect::*; +pub use utils::*; +use witness_generator::*; +use witness_orchestrator::*; +use zisk_common::io::StreamSource; +use zisk_core::ZiskRom; pub type DeviceMetricsList = Vec; pub type NestedDeviceMetricsList = HashMap; @@ -30,13 +54,16 @@ use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; /// Trait for unified execution across different emulator backends +#[allow(clippy::too_many_arguments)] pub trait Emulator: Send + Sync { /// Execute the emulator fn execute( &self, + zisk_rom: &ZiskRom, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, + use_hints: bool, stats: &ExecutorStatsHandle, caller_stats_scope: &StatsScope, ) -> ( @@ -59,14 +86,44 @@ impl EmulatorKind { pub fn is_asm_emulator(&self) -> bool { matches!(self, Self::Asm(_)) } + + pub fn get_chunk_size(&self) -> u64 { + match self { + Self::Asm(e) => e.get_chunk_size(), + Self::Rust(e) => e.get_chunk_size(), + } + } + + pub fn set_asm_resources(&self, asm_resources: AsmResources) { + match self { + Self::Asm(e) => e.set_asm_resources(asm_resources), + Self::Rust(_) => (), // No ASM resources in Rust emulator + }; + } + + pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { + match self { + Self::Asm(e) => e.set_hints_stream_src(stream), + Self::Rust(_) => Err(anyhow::anyhow!("Hints stream not supported in Rust emulator")), + } + } + + pub fn reset_hints_stream(&self) { + match self { + Self::Asm(e) => e.reset_hints_stream(), + Self::Rust(_) => (), // No hints stream in Rust emulator + } + } } impl Emulator for EmulatorKind { fn execute( &self, + zisk_rom: &ZiskRom, stdin: &Mutex, pctx: &ProofCtx, sm_bundle: &StaticSMBundle, + use_hints: bool, stats: &ExecutorStatsHandle, caller_stats_scope: &StatsScope, ) -> ( @@ -77,8 +134,10 @@ impl Emulator for EmulatorKind { u64, ) { match self { - Self::Asm(e) => e.execute(stdin, pctx, sm_bundle, stats, caller_stats_scope), - Self::Rust(e) => e.execute(stdin, sm_bundle), + Self::Asm(e) => { + e.execute(zisk_rom, stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope) + } + Self::Rust(e) => e.execute(zisk_rom, stdin, sm_bundle), } } } diff --git a/executor/src/planner.rs b/executor/src/planner.rs new file mode 100644 index 000000000..6559880c6 --- /dev/null +++ b/executor/src/planner.rs @@ -0,0 +1,153 @@ +//! Instance planning component. +//! +//! This module handles the planning and assignment of main and secondary +//! state machine instances to the proof context. + +use fields::PrimeField64; +use proofman_common::{ProofCtx, SetupCtx}; +use sm_main::MainPlanner; +use std::{collections::BTreeMap, sync::RwLock}; +use zisk_common::{EmuTrace, InstanceType, Plan}; +use zisk_pil::{MAIN_AIR_IDS, ZISK_AIRGROUP_ID}; + +use crate::AirClassifier; +use crate::{DeviceMetricsList, NestedDeviceMetricsList, StaticSMBundle}; + +/// Output from main planning. +pub struct MainPlanningOutput { + /// Plans for main instances. + pub plans: Vec, + /// Public values extracted during planning. + pub public_values: Vec<(u64, u32)>, +} + +/// Component responsible for instance planning. +/// +/// Handles the strategic planning of main and secondary state machine +/// instances based on execution metrics. Planning determines: +/// - How many instances of each state machine type are needed +/// - How work is distributed across instances +/// - Global ID assignments for proof context registration +pub struct InstancePlanner { + /// Chunk size for dividing execution into manageable pieces. + chunk_size: u64, +} + +impl InstancePlanner { + /// Creates a new `InstancePlanner`. + /// + /// # Arguments + /// * `chunk_size` - The chunk size for processing. + pub fn new(chunk_size: u64) -> Self { + Self { chunk_size } + } + + /// Plans main state machine instances. + /// + /// # Arguments + /// * `min_traces` - Minimal traces from execution. + /// * `main_count` - Device metrics for main instances. + /// + /// # Returns + /// Planning output with plans and public values. + pub fn plan_main( + &self, + min_traces: &[EmuTrace], + main_count: DeviceMetricsList, + ) -> MainPlanningOutput { + let (plans, public_values) = + MainPlanner::plan::(min_traces, main_count, self.chunk_size); + MainPlanningOutput { plans, public_values } + } + + /// Plans secondary state machine instances. + /// + /// # Arguments + /// * `sm_bundle` - State machine bundle. + /// * `secn_count` - Device metrics for secondary instances. + /// + /// # Returns + /// BTreeMap of SM type ID to plans. + pub fn plan_secondary( + &self, + sm_bundle: &StaticSMBundle, + secn_count: &mut NestedDeviceMetricsList, + ) -> BTreeMap> { + sm_bundle.plan_sec(secn_count) + } + + /// Assigns main instances to the proof context. + /// + /// # Arguments + /// * `pctx` - Proof context. + /// * `global_ids` - Lock for storing assigned global IDs. + /// * `plans` - Plans to assign. + /// + /// # Returns + /// Vector of (global_id, plan) pairs for instance creation. + pub fn assign_main_instances( + &self, + pctx: &ProofCtx, + sctx: &SetupCtx, + global_ids: &RwLock>, + plans: Vec, + ) -> (Vec<(usize, Plan)>, u64) { + let mut assignments = Vec::with_capacity(plans.len()); + + let setup_main = sctx.get_setup(ZISK_AIRGROUP_ID, MAIN_AIR_IDS[0]).unwrap(); + let n_bits = setup_main.stark_info.stark_struct.n_bits; + let total_cols: u64 = setup_main + .stark_info + .map_sections_n + .iter() + .filter(|(key, _)| *key != "const") + .map(|(_, value)| *value) + .sum(); + let cost = (1 << n_bits) * total_cols; + let total_cost = cost * plans.len() as u64; + + for mut plan in plans { + let global_id = pctx + .add_instance_assign(plan.airgroup_id, plan.air_id) + .expect("Failed to add instance"); + plan.set_global_id(global_id); + global_ids.write().unwrap().push(global_id); + assignments.push((global_id, plan)); + } + + (assignments, total_cost) + } + + /// Assigns secondary instances to the proof context. + /// + /// # Arguments + /// * `pctx` - Proof context. + /// * `global_ids` - Lock for storing assigned global IDs. + /// * `plans` - Plans to assign (will be mutated with global IDs). + pub fn assign_secn_instances( + &self, + pctx: &ProofCtx, + global_ids: &RwLock>, + plans: &mut [Plan], + ) { + for plan in plans.iter_mut() { + // ROM instances need special first partition assignment + let global_id = if AirClassifier::is_rom_instance(plan.airgroup_id, plan.air_id) { + pctx.add_instance_assign_first_partition(plan.airgroup_id, plan.air_id) + .expect("Failed to add ROM instance") + } else { + match plan.instance_type { + InstanceType::Instance => pctx + .add_instance(plan.airgroup_id, plan.air_id) + .expect("Failed to add instance"), + InstanceType::Table => { + pctx.add_table(plan.airgroup_id, plan.air_id).expect("Failed to add table") + } + } + }; + + global_ids.write().unwrap().push(global_id); + plan.set_global_id(global_id); + } + } +} diff --git a/executor/src/registry.rs b/executor/src/registry.rs new file mode 100644 index 000000000..215d46397 --- /dev/null +++ b/executor/src/registry.rs @@ -0,0 +1,154 @@ +//! Instance registry component. +//! +//! This module handles the creation and lifecycle management of main and secondary +//! state machine instances. + +use fields::PrimeField64; +use proofman_common::ProofCtx; +use sm_main::MainInstance; +use std::sync::Arc; +use zisk_common::{CheckPoint, Instance, InstanceCtx, InstanceType, Plan}; + +use crate::AirClassifier; +use crate::{state::ExecutionState, StaticSMBundle}; + +pub struct InstanceRegistry { + /// State machine bundle for secondary instance creation. + sm_bundle: Arc>, +} + +impl InstanceRegistry { + /// Creates a new `InstanceRegistry`. + /// + /// # Arguments + /// * `sm_bundle` - State machine bundle. + pub fn new(sm_bundle: Arc>) -> Self { + Self { sm_bundle } + } + + /// Creates a main state machine instance. + /// + /// # Arguments + /// * `plan` - The plan for the instance. + /// * `global_id` - The global ID assigned to this instance. + pub fn create_main_instance(&self, plan: Plan, global_id: usize) -> MainInstance { + MainInstance::new(InstanceCtx::new(global_id, plan), self.sm_bundle.get_std()) + } + + /// Creates a secondary state machine instance. + /// + /// # Arguments + /// * `plan` - The plan for the instance. + /// * `global_id` - The global ID assigned to this instance. + pub fn create_secn_instance(&self, plan: Plan, global_id: usize) -> Box> { + let ictx = InstanceCtx::new(global_id, plan); + self.sm_bundle.build_instance(ictx) + } + + /// Creates a secondary instance by looking up the plan in execution state. + /// + /// # Arguments + /// * `state` - The execution state containing the plans. + /// * `global_id` - The global ID to look up. + pub fn create_secn_instance_from_state( + &self, + state: &ExecutionState, + global_id: usize, + ) -> Box> { + let mut secn_planning_guard = state.secn_planning.write().unwrap(); + + // Find and remove in single operation using swap_remove for O(1) removal + let plan = secn_planning_guard + .iter() + .position(|plan| plan.global_id == Some(global_id)) + .map(|idx| secn_planning_guard.swap_remove(idx)) + .unwrap_or_else(|| panic!("Secondary instance not found for global_id: {}", global_id)); + + self.create_secn_instance(plan, global_id) + } + + /// Populates main instances in the execution state. + /// + /// # Arguments + /// * `state` - The execution state to populate. + /// * `assignments` - Vector of (global_id, plan) pairs. + pub fn populate_main_instances( + &self, + state: &ExecutionState, + assignments: Vec<(usize, Plan)>, + ) { + let mut main_instances = state.main_instances.write().unwrap(); + for (global_id, plan) in assignments { + main_instances + .entry(global_id) + .or_insert_with(|| self.create_main_instance(plan, global_id)); + } + } + + /// Populates secondary instances in the execution state. + /// + /// # Arguments + /// * `state` - The execution state to populate. + /// * `global_ids` - Vector of global IDs for instances to create. + pub fn populate_secn_instances(&self, state: &ExecutionState, global_ids: &[usize]) { + let mut secn_instances = state.secn_instances.write().unwrap(); + for &global_id in global_ids { + secn_instances + .entry(global_id) + .or_insert_with(|| self.create_secn_instance_from_state(state, global_id)); + } + } + + /// Gets a reference to the state machine bundle. + pub fn sm_bundle(&self) -> &StaticSMBundle { + &self.sm_bundle + } + + /// Configures secondary state machine instances based on planning. + /// + /// # Arguments + /// * `pctx` - Proof context. + /// * `plannings` - Map of SM ID to plans. + pub fn configure_sm_instances( + &self, + pctx: &ProofCtx, + plannings: &std::collections::BTreeMap>, + ) { + self.sm_bundle.configure_instances(pctx, plannings); + } + + /// Configures checkpoints for secondary instances. + /// + /// # Arguments + /// * `pctx` - Proof context. + /// * `state` - Execution state containing the instances. + /// * `global_ids` - Global IDs of secondary instances to configure. + pub fn configure_checkpoints( + &self, + pctx: &ProofCtx, + state: &ExecutionState, + global_ids: &[usize], + ) { + let secn_instances = state.secn_instances.read().unwrap(); + + for &global_id in global_ids { + secn_instances[&global_id].reset(); + + if secn_instances[&global_id].instance_type() == InstanceType::Instance { + let checkpoint = secn_instances[&global_id].check_point(); + let chunks = match checkpoint { + CheckPoint::None => vec![], + CheckPoint::Single(chunk_id) => vec![chunk_id.as_usize()], + CheckPoint::Multiple(chunk_ids) => { + chunk_ids.iter().map(|id| id.as_usize()).collect() + } + }; + + let (_, air_id) = + pctx.dctx_get_instance_info(global_id).expect("Failed to get instance info"); + let is_memory_related = AirClassifier::is_memory_related(air_id); + pctx.dctx_set_chunks(global_id, chunks, is_memory_related); + } + } + } +} diff --git a/executor/src/rom_executor.rs b/executor/src/rom_executor.rs new file mode 100644 index 000000000..80847d6e7 --- /dev/null +++ b/executor/src/rom_executor.rs @@ -0,0 +1,105 @@ +//! ROM executor +//! +//! This module handles the execution of a ZisK ROM program, coordinating +//! the emulator backend and hints stream processing. + +use crate::{ + AsmResources, DeviceMetricsList, Emulator, EmulatorKind, NestedDeviceMetricsList, + StaticSMBundle, +}; +use anyhow::Result; +use asm_runner::AsmRunnerMO; +use fields::PrimeField64; +use proofman_common::ProofCtx; +use std::{sync::Mutex, thread::JoinHandle}; +use zisk_common::{ + io::{StreamSource, ZiskStdin}, + EmuTrace, ExecutorStatsHandle, StatsScope, +}; +use zisk_core::ZiskRom; + +/// Output from ROM execution. +pub struct RomExecutionOutput { + /// Minimal traces collected during execution. + pub min_traces: Vec, + /// Device metrics for main state machines. + pub main_count: DeviceMetricsList, + /// Device metrics for secondary state machines. + pub secn_count: NestedDeviceMetricsList, + /// Handle to memory operations thread (for ASM emulator). + pub handle_mo: Option>, + /// Execution result with step counts. + pub steps: u64, +} + +pub struct RomExecutor { + /// The emulator backend used for execution. + emulator: EmulatorKind, + + /// Standard input for the ZisK program execution. + stdin: Mutex, +} + +impl RomExecutor { + /// Creates a new `RomExecutor`. + /// + /// # Arguments + /// * `emulator` - The emulator backend to use. + /// * `hints_stream` - Optional hints stream for precompile processing. + pub fn new(emulator: EmulatorKind) -> Self { + Self { emulator, stdin: Mutex::new(ZiskStdin::null()) } + } + + /// Sets the standard input for execution. + pub fn set_stdin(&self, stdin: ZiskStdin) { + *self.stdin.lock().unwrap() = stdin; + } + + pub fn set_asm_resources(&self, asm_resources: AsmResources) { + self.emulator.set_asm_resources(asm_resources); + } + + /// Sets the hints stream source. + pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { + self.emulator.set_hints_stream_src(stream) + } + + /// Resets the hints stream if configured. + pub fn reset_hints_stream(&self) { + self.emulator.reset_hints_stream() + } + + /// Executes the ROM program and collects minimal traces. + /// + /// # Arguments + /// * `zisk_rom` - The ROM to execute. + /// * `pctx` - Proof context. + /// * `sm_bundle` - State machine bundle. + /// * `use_hints` - Flag to indicate whether to use hints. + /// * `stats` - Statistics handle. + /// * `caller_stats_scope` - Parent statistics scope. + /// + /// # Returns + /// Execution output containing traces, metrics, and results. + pub fn execute( + &self, + zisk_rom: &ZiskRom, + pctx: &ProofCtx, + sm_bundle: &StaticSMBundle, + use_hints: bool, + stats: &ExecutorStatsHandle, + caller_stats_scope: &StatsScope, + ) -> RomExecutionOutput { + let (min_traces, main_count, secn_count, handle_mo, steps) = self.emulator.execute( + zisk_rom, + &self.stdin, + pctx, + sm_bundle, + use_hints, + stats, + caller_stats_scope, + ); + + RomExecutionOutput { min_traces, main_count, secn_count, handle_mo, steps } + } +} diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index 8c92a5257..19b4aca10 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use crate::{NestedDeviceMetricsList, StaticDataBusCollect}; use data_bus::DataBusTrait; use fields::PrimeField64; +use pil_std_lib::Std; use precomp_arith_eq::{ArithEqInstance, ArithEqManager}; use precomp_arith_eq_384::ArithEq384Instance; use precomp_arith_eq_384::ArithEq384Manager; @@ -37,7 +38,7 @@ use zisk_pil::{ POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, }; -use crate::StaticDataBus; +use crate::{StaticDataBus, ZiskRom}; use rayon::prelude::*; type SMAirType = Vec<(usize, usize)>; @@ -134,19 +135,37 @@ impl StateMachines { pub struct StaticSMBundle { process_only_operation_bus: bool, sm: BTreeMap>, + std: Arc>, } impl StaticSMBundle { #[allow(clippy::too_many_arguments)] - pub fn new(process_only_operation_bus: bool, sm: Vec<(SMAirType, StateMachines)>) -> Self { + pub fn new( + process_only_operation_bus: bool, + std: Arc>, + sm: Vec<(SMAirType, StateMachines)>, + ) -> Self { Self { process_only_operation_bus, sm: BTreeMap::from_iter( sm.into_iter().map(|(air_ids, sm)| (sm.type_id(), (air_ids, sm))), ), + std, } } + pub fn set_rom(&self, zisk_rom: Arc) { + for (_, sm) in self.sm.values() { + if let StateMachines::RomSM(rom_sm) = sm { + rom_sm.set_rom(zisk_rom.clone()); + } + } + } + + pub fn get_std(&self) -> Arc> { + self.std.clone() + } + pub fn get_mem_sm_id(&self) -> usize { 1 } diff --git a/executor/src/state.rs b/executor/src/state.rs new file mode 100644 index 000000000..08e71cc27 --- /dev/null +++ b/executor/src/state.rs @@ -0,0 +1,127 @@ +//! Shared execution state for the ZisK executor components. + +use anyhow::Result; +use fields::PrimeField64; +use sm_main::MainInstance; +use std::{ + collections::HashMap, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, RwLock, + }, +}; +use zisk_common::{BusDevice, EmuTrace, ExecutorStatsHandle, Instance, Plan, ZiskExecutionResult}; +use zisk_core::ZiskRom; + +/// Type alias for chunk collectors: (chunk_id, collector) +pub type ChunkCollector = (usize, Box>); + +pub struct ExecutionState { + /// ZisK ROM (ELF), can be changed between executions. + pub zisk_rom: RwLock>>, + + /// Planning information for main state machines (minimal traces from emulation). + pub min_traces: Arc>>>, + + /// Planning information for secondary state machines. + pub secn_planning: RwLock>, + + /// Main state machine instances, indexed by their global ID. + pub main_instances: RwLock>>, + + /// Secondary state machine instances, indexed by their global ID. + pub secn_instances: RwLock>>>, + + /// Collectors by instance, storing statistics and collectors for each instance. + pub collectors_by_instance: Arc>>>>, + + /// Execution result, including the number of executed steps. + pub execution_result: Mutex, + + /// Statistics collected during the execution. + pub stats: ExecutorStatsHandle, + + /// Flag to indicate if the ROM has been initialized + pub is_rom_initialized: AtomicBool, + + /// Flag to indicate whether to use hints during execution + pub use_hints: AtomicBool, +} + +impl ExecutionState { + /// Creates a new `ExecutionState` with default values. + pub fn new() -> Self { + Self { + zisk_rom: RwLock::new(None), + min_traces: Arc::new(RwLock::new(None)), + secn_planning: RwLock::new(Vec::new()), + main_instances: RwLock::new(HashMap::new()), + secn_instances: RwLock::new(HashMap::new()), + collectors_by_instance: Arc::new(RwLock::new(HashMap::new())), + execution_result: Mutex::new(ZiskExecutionResult::default()), + stats: ExecutorStatsHandle::new(), + is_rom_initialized: AtomicBool::new(false), + use_hints: AtomicBool::new(false), + } + } + + /// Sets the ZisK ROM for execution. + /// + /// This can be called between executions to change the ROM/ELF + /// without recreating the executor. + pub fn set_rom(&self, rom: Arc, use_hints: bool) { + *self.zisk_rom.write().unwrap() = Some(rom); + self.is_rom_initialized.store(true, Ordering::SeqCst); + self.use_hints.store(use_hints, Ordering::SeqCst); + } + + /// Gets the current ZisK ROM. + /// + /// # Panics + /// Panics if no ROM has been set. + pub fn get_rom(&self) -> Result> { + if !self.is_rom_initialized.load(Ordering::SeqCst) { + return Err(anyhow::anyhow!("ROM not initialized. Call set_rom() before get_rom()")); + } + + Ok(self + .zisk_rom + .read() + .unwrap() + .as_ref() + .expect("ROM not set. Call set_rom() before execute()") + .clone()) + } + + /// Resets all internal state to default values. + pub fn reset(&self) { + *self.execution_result.lock().unwrap() = ZiskExecutionResult::default(); + *self.min_traces.write().unwrap() = None; + *self.secn_planning.write().unwrap() = Vec::new(); + self.main_instances.write().unwrap().clear(); + self.secn_instances.write().unwrap().clear(); + self.collectors_by_instance.write().unwrap().clear(); + self.stats.reset(); + } + + /// Gets a clone of the execution result. + pub fn get_execution_result(&self) -> ZiskExecutionResult { + self.execution_result.lock().unwrap().clone() + } + + /// Sets the execution result. + pub fn set_execution_result(&self, result: ZiskExecutionResult) { + *self.execution_result.lock().unwrap() = result; + } + + /// Gets a clone of the stats handle. + pub fn get_stats(&self) -> ExecutorStatsHandle { + self.stats.clone() + } +} + +impl Default for ExecutionState { + fn default() -> Self { + Self::new() + } +} diff --git a/executor/src/utils.rs b/executor/src/utils.rs new file mode 100644 index 000000000..2635f33df --- /dev/null +++ b/executor/src/utils.rs @@ -0,0 +1,192 @@ +use crate::{EmulatorAsm, EmulatorKind, EmulatorRust, StateMachines, StaticSMBundle, ZiskExecutor}; +use fields::PrimeField64; +use pil_std_lib::Std; +use precomp_arith_eq::ArithEqManager; +use precomp_arith_eq_384::ArithEq384Manager; +use precomp_big_int::Add256Manager; +use precomp_dma::DmaManager; +use precomp_keccakf::KeccakfManager; +use precomp_poseidon2::Poseidon2Manager; +use precomp_sha256f::Sha256fManager; +use proofman::register_std; +use proofman_common::PackedInfo; +use proofman_common::VerboseMode; +use sm_arith::ArithSM; +use sm_binary::BinarySM; +use sm_mem::Mem; +use sm_rom::RomSM; +use std::{collections::HashMap, sync::Arc}; +use tracing::debug; +use witness::WitnessManager; + +use zisk_core::CHUNK_SIZE; +#[cfg(feature = "packed")] +use zisk_pil::PACKED_INFO; +use zisk_pil::{ + ADD_256_AIR_IDS, ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, + BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, DMA_64_ALIGNED_AIR_IDS, DMA_AIR_IDS, + DMA_PRE_POST_AIR_IDS, DMA_UNALIGNED_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, + MEM_ALIGN_AIR_IDS, MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, + MEM_ALIGN_WRITE_BYTE_AIR_IDS, POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, + SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, +}; + +use anyhow::Result; + +pub fn get_packed_info() -> HashMap<(usize, usize), PackedInfo> { + let mut _packed_info = HashMap::new(); + #[cfg(feature = "packed")] + { + for packed_info in PACKED_INFO.iter() { + _packed_info.insert( + (packed_info.0, packed_info.1), + PackedInfo::new( + packed_info.2.is_packed, + packed_info.2.num_packed_words, + packed_info.2.unpack_info.to_vec(), + ), + ); + } + } + _packed_info +} + +/// Registers the witness components +/// +/// # Arguments +/// * `wcm` - An `Arc`-wrapped `WitnessManager` instance that orchestrates witness generation. +/// +/// This method performs the following steps: +/// 2. Initializes core and secondary state machines for witness generation. +/// 3. Registers the state machines with the `ZiskExecutor`. +/// 4. Registers the `ZiskExecutor` as a component in the `WitnessManager`. +fn initialize_executor( + verbose_mode: proofman_common::VerboseMode, + shared_tables: bool, + is_asm_emulator: bool, + unlock_mapped_memory: Option, + wcm: &WitnessManager, +) -> Result>> { + let rank_info = wcm.get_rank_info(); + + proofman_common::initialize_logger(verbose_mode, Some(&rank_info)); + + // Step 3: Initialize the secondary state machines + let std = Std::new(wcm.get_pctx(), wcm.get_sctx(), shared_tables)?; + register_std(wcm, &std); + + let rom_sm = RomSM::new(is_asm_emulator); + let binary_sm = BinarySM::new(std.clone()); + let arith_sm = ArithSM::new(std.clone()); + let mem_sm = Mem::new(std.clone()); + // Step 4: Initialize the precompiles state machines + let keccakf_sm = KeccakfManager::new(std.clone()); + let sha256f_sm = Sha256fManager::new(std.clone()); + let poseidon2_sm = Poseidon2Manager::new(); + let arith_eq_sm = ArithEqManager::new(std.clone()); + let arith_eq_384_sm = ArithEq384Manager::new(std.clone()); + let add256_sm = Add256Manager::new(std.clone()); + let dma_sm = DmaManager::new(std.clone()); + + let mem_instances = vec![ + (ZISK_AIRGROUP_ID, MEM_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, ROM_DATA_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, INPUT_DATA_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, MEM_ALIGN_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, MEM_ALIGN_BYTE_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, MEM_ALIGN_WRITE_BYTE_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, MEM_ALIGN_READ_BYTE_AIR_IDS[0]), + ]; + + let binary_instances = vec![ + (ZISK_AIRGROUP_ID, BINARY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, BINARY_ADD_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, BINARY_EXTENSION_AIR_IDS[0]), + ]; + + let dma_instances = vec![ + (ZISK_AIRGROUP_ID, DMA_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_PRE_POST_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_UNALIGNED_AIR_IDS[0]), + ]; + + let sm_bundle = StaticSMBundle::new( + is_asm_emulator, + std.clone(), + vec![ + (vec![(ZISK_AIRGROUP_ID, ROM_AIR_IDS[0])], StateMachines::RomSM(rom_sm.clone())), + (mem_instances, StateMachines::MemSM(mem_sm.clone())), + (binary_instances, StateMachines::BinarySM(binary_sm.clone())), + (vec![(ZISK_AIRGROUP_ID, ARITH_AIR_IDS[0])], StateMachines::ArithSM(arith_sm.clone())), + // The precompiles state machines + ( + vec![(ZISK_AIRGROUP_ID, KECCAKF_AIR_IDS[0])], + StateMachines::KeccakfManager(keccakf_sm.clone()), + ), + ( + vec![(ZISK_AIRGROUP_ID, SHA_256_F_AIR_IDS[0])], + StateMachines::Sha256fManager(sha256f_sm.clone()), + ), + ( + vec![(ZISK_AIRGROUP_ID, POSEIDON_2_AIR_IDS[0])], + StateMachines::Poseidon2Manager(poseidon2_sm.clone()), + ), + ( + vec![(ZISK_AIRGROUP_ID, ARITH_EQ_AIR_IDS[0])], + StateMachines::ArithEqManager(arith_eq_sm.clone()), + ), + ( + vec![(ZISK_AIRGROUP_ID, ARITH_EQ_384_AIR_IDS[0])], + StateMachines::ArithEq384Manager(arith_eq_384_sm.clone()), + ), + ( + vec![(ZISK_AIRGROUP_ID, ADD_256_AIR_IDS[0])], + StateMachines::Add256Manager(add256_sm.clone()), + ), + (dma_instances, StateMachines::DmaManager(dma_sm.clone())), + ], + ); + + let emulator = if is_asm_emulator { + debug!("Using ASM emulator"); + EmulatorKind::Asm(EmulatorAsm::new( + rank_info.world_rank, + rank_info.local_rank, + unlock_mapped_memory.unwrap_or(false), + CHUNK_SIZE, + Some(rom_sm.clone()), + verbose_mode, + )) + } else { + debug!("Using Rust emulator"); + EmulatorKind::Rust(EmulatorRust::new(CHUNK_SIZE)) + }; + + let executor = Arc::new(ZiskExecutor::new(sm_bundle, emulator)); + + // Step 7: Register the executor as a component in the Witness Manager + wcm.register_component(executor.clone()); + + wcm.set_witness_initialized(); + + Ok(executor) +} + +pub fn init_executor_emu( + verbose: VerboseMode, + shared_tables: bool, + wcm: &WitnessManager, +) -> Result>> { + initialize_executor(verbose, shared_tables, false, None, wcm) +} + +#[allow(clippy::too_many_arguments)] +pub fn init_executor_asm( + verbose: VerboseMode, + shared_tables: bool, + unlock_mapped_memory: bool, + wcm: &WitnessManager, +) -> Result>> { + initialize_executor(verbose, shared_tables, true, Some(unlock_mapped_memory), wcm) +} diff --git a/executor/src/witness_generator.rs b/executor/src/witness_generator.rs new file mode 100644 index 000000000..bc5398789 --- /dev/null +++ b/executor/src/witness_generator.rs @@ -0,0 +1,139 @@ +//! Witness computation component. +//! +//! This module handles the computation of witnesses for main and +//! secondary state machine instances. + +use fields::PrimeField64; +use proofman_common::{ProofCtx, ProofmanResult, SetupCtx}; +use sm_main::MainInstance; +use std::time::Instant; +use zisk_common::{stats_begin, stats_end, BusDevice, Instance, InstanceType, Stats}; + +use crate::state::ExecutionState; + +/// Component responsible for witness computation. +/// +/// Handles the computation of witnesses for: +/// - **Main instances**: Compute from minimal traces with chunk processing +/// - **Secondary instances**: Compute from collected chunk data +/// - **Table instances**: Compute static lookup tables +pub struct WitnessGenerator { + /// Chunk size for trace processing. + chunk_size: u64, +} + +impl WitnessGenerator { + /// Creates a new `WitnessGenerator`. + /// + /// # Arguments + /// * `chunk_size` - Chunk size for processing. + pub fn new(chunk_size: u64) -> Self { + Self { chunk_size } + } + + /// Computes witness for a main state machine instance. + /// + /// # Arguments + /// * `pctx` - Proof context. + /// * `state` - Execution state. + /// * `main_instance` - The main instance to compute witness for. + /// * `trace_buffer` - Buffer for trace data. + /// * `caller_stats_id` - Parent stats scope ID. + pub fn compute_main_witness( + &self, + pctx: &ProofCtx, + state: &ExecutionState, + main_instance: &MainInstance, + trace_buffer: Vec, + _caller_stats_id: u64, + ) -> ProofmanResult<()> { + let witness_start_time = Instant::now(); + + let (airgroup_id, air_id) = pctx + .dctx_get_instance_info(main_instance.ictx.global_id) + .expect("Failed to get instance info"); + + stats_begin!(state.stats, _caller_stats_id, _stats_scope, "AIR_MAIN_WITNESS", air_id); + + let zisk_rom = state + .get_rom() + .map_err(|e| proofman_common::ProofmanError::InvalidConfiguration(e.to_string()))?; + let min_traces_guard = state.min_traces.read().unwrap(); + let min_traces = min_traces_guard.as_ref().expect("min_traces should not be None"); + + let air_instance = main_instance.compute_witness( + &zisk_rom, + min_traces, + self.chunk_size, + main_instance, + trace_buffer, + )?; + + pctx.add_air_instance(air_instance, main_instance.ictx.global_id); + + stats_end!(state.stats, &_stats_scope); + + let stats = Stats::new_main_completed(airgroup_id, air_id, witness_start_time); + + state.stats.insert_witness_stats(main_instance.ictx.global_id, stats); + + Ok(()) + } + + /// Computes witness for a secondary state machine instance. + /// + /// # Arguments + /// * `pctx` - Proof context. + /// * `sctx` - Setup context. + /// * `state` - Execution state. + /// * `global_id` - Global ID of the instance. + /// * `secn_instance` - The secondary instance to compute witness for. + /// * `collectors` - Collectors for chunk data. + /// * `should_add_instance` - Whether to add the computed AIR instance to the proof + /// * `trace_buffer` - Buffer for trace data. + /// * `_caller_stats_id` - Parent stats scope ID. + #[allow(clippy::too_many_arguments)] + pub fn compute_secn_witness( + &self, + pctx: &ProofCtx, + sctx: &SetupCtx, + state: &ExecutionState, + global_id: usize, + secn_instance: &dyn Instance, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + _caller_stats_id: u64, + ) -> ProofmanResult<()> { + let witness_start_time = Instant::now(); + + let _stats_msg = match secn_instance.instance_type() { + InstanceType::Instance => "AIR_SECN_WITNESS", + InstanceType::Table => "AIR_WITNESS_TABLE", + }; + + let (_airgroup_id, _air_id) = + pctx.dctx_get_instance_info(global_id).expect("Failed to get instance info"); + + stats_begin!(state.stats, _caller_stats_id, _stats_scope, _stats_msg, _air_id); + + let air_instance = secn_instance.compute_witness(pctx, sctx, collectors, trace_buffer)?; + + if let Some(air_instance) = air_instance { + let should_add_instance = secn_instance.instance_type() == InstanceType::Instance + || (secn_instance.instance_type() == InstanceType::Table + && pctx + .dctx_is_my_process_instance(global_id) + .expect("Failed to check instance ownership")); + + if should_add_instance { + pctx.add_air_instance(air_instance, global_id); + } + } + + stats_end!(state.stats, &_stats_scope); + + state.stats.set_witness_duration(global_id, witness_start_time.elapsed().as_millis()); + + Ok(()) + } +} diff --git a/executor/src/witness_orchestrator.rs b/executor/src/witness_orchestrator.rs new file mode 100644 index 000000000..4800df895 --- /dev/null +++ b/executor/src/witness_orchestrator.rs @@ -0,0 +1,366 @@ +//! Witness orchestrator component. +//! +//! This module handles the logic for witness computation, coordinating between collectors and +//! witness generators + +use crate::{ + state::ExecutionState, AirClassifier, ChunkDataCollector, StaticSMBundle, WitnessGenerator, +}; +use fields::PrimeField64; +use proofman_common::{BufferPool, ProofCtx, ProofmanResult, SetupCtx}; +use sm_rom::RomInstance; +use std::collections::HashMap; +use std::sync::Arc; +use zisk_common::{BusDevice, Instance, InstanceType, Stats, StatsScope}; +use zisk_core::ZiskRom; + +/// Type alias for the secondary instances map (owned). +type SecnInstanceMap = HashMap>>; + +/// Type alias for the secondary instances map (borrowed). +type SecnInstanceMapRef<'a, F> = HashMap>>; + +/// Context for witness computation operations. +pub struct WitnessContext<'a, F: PrimeField64> { + /// Proof context. + pub pctx: &'a ProofCtx, + + /// Setup context. + pub sctx: &'a SetupCtx, + + /// Execution state. + pub state: &'a ExecutionState, + + /// Buffer pool for trace data. + pub buffer_pool: &'a dyn BufferPool, + + /// Statistics scope. + pub stats_scope: &'a StatsScope, +} + +impl<'a, F: PrimeField64> WitnessContext<'a, F> { + /// Creates a new witness context. + pub fn new( + pctx: &'a ProofCtx, + sctx: &'a SetupCtx, + state: &'a ExecutionState, + buffer_pool: &'a dyn BufferPool, + stats_scope: &'a StatsScope, + ) -> Self { + Self { pctx, sctx, state, buffer_pool, stats_scope } + } + + /// Gets instance info (airgroup_id, air_id) for a global ID. + pub fn get_instance_info(&self, global_id: usize) -> (usize, usize) { + self.pctx.dctx_get_instance_info(global_id).expect("Failed to get instance info") + } +} + +/// Component responsible for orchestrating witness computation. +pub struct WitnessOrchestrator { + /// Chunk data collector for secondary instances. + collector: ChunkDataCollector, + + /// Witness computer for all instance types. + witness_generator: WitnessGenerator, + + /// Whether using ASM emulator (cached to avoid passing through all calls). + is_asm_emulator: bool, +} + +impl WitnessOrchestrator { + /// Creates a new `WitnessOrchestrator`. + /// + /// # Arguments + /// * `chunk_size` - Chunk size for trace processing. + /// * `sm_bundle` - Static state machine bundle for collector initialization. + /// * `is_asm_emulator` - Whether using ASM emulator. + pub fn new(chunk_size: u64, sm_bundle: Arc>, is_asm_emulator: bool) -> Self { + let collector = ChunkDataCollector::new(sm_bundle.clone()); + let witness_generator = WitnessGenerator::new(chunk_size); + + Self { collector, witness_generator, is_asm_emulator } + } + + pub fn set_rom(&self, zisk_rom: Arc) { + self.collector.set_rom(zisk_rom.clone()); + } + + /// Computes witness for a single global ID. + /// + /// Routes to the appropriate witness computation method based on + /// instance type and handles special cases like ROM with ASM emulator. + /// + /// # Arguments + /// * `ctx` - Witness context with shared references. + /// * `global_id` - Global ID of the instance. + pub fn compute_witness_for_instance( + &self, + ctx: &WitnessContext<'_, F>, + global_id: usize, + ) -> ProofmanResult<()> { + let (airgroup_id, air_id) = ctx.get_instance_info(global_id); + + if AirClassifier::is_main(air_id) { + self.compute_main_witness( + ctx.pctx, + ctx.state, + global_id, + ctx.buffer_pool, + ctx.stats_scope, + ) + } else { + self.compute_secondary_witness( + ctx.pctx, + ctx.sctx, + ctx.state, + global_id, + airgroup_id, + air_id, + ctx.buffer_pool, + ctx.stats_scope, + ) + } + } + + /// Computes witness for a main instance. + /// + /// # Arguments + /// * `pctx` - Proof context. + /// * `state` - Execution state. + /// * `global_id` - Global ID of the main instance. + /// * `buffer_pool` - Buffer pool for trace data. + /// * `stats_scope` - Statistics scope for recording stats. + fn compute_main_witness( + &self, + pctx: &ProofCtx, + state: &ExecutionState, + global_id: usize, + buffer_pool: &dyn BufferPool, + stats_scope: &StatsScope, + ) -> ProofmanResult<()> { + let main_instances = state.main_instances.read().unwrap(); + let main_instance = &main_instances[&global_id]; + + self.witness_generator.compute_main_witness( + pctx, + state, + main_instance, + buffer_pool.take_buffer(), + stats_scope.id(), + ) + } + + /// Computes witness for a secondary instance. + /// + /// # Arguments + /// * `pctx` - Proof context. + /// * `sctx` - Setup context. + /// * `state` - Execution state. + /// * `global_id` - Global ID of the secondary instance. + /// * `airgroup_id` - AIR group ID of the instance. + /// * `air_id` - AIR ID of the instance. + /// * `buffer_pool` - Buffer pool for trace data. + /// * `stats_scope` - Statistics scope for recording stats. + #[allow(clippy::too_many_arguments)] + fn compute_secondary_witness( + &self, + pctx: &ProofCtx, + sctx: &SetupCtx, + state: &ExecutionState, + global_id: usize, + airgroup_id: usize, + air_id: usize, + buffer_pool: &dyn BufferPool, + stats_scope: &StatsScope, + ) -> ProofmanResult<()> { + let secn_instances = state.secn_instances.read().unwrap(); + let secn_instance = &secn_instances[&global_id]; + + if secn_instance.instance_type() == InstanceType::Instance { + let needs_collection = + !state.collectors_by_instance.read().unwrap().contains_key(&global_id); + + if needs_collection { + if AirClassifier::is_rom(air_id) && self.is_asm_emulator { + // ROM with ASM emulator: skip collection + self.register_empty_collector(state, global_id, airgroup_id, air_id); + } else { + // Collect data for this instance + self.collector.collect_single(pctx, state, global_id, secn_instance).map_err( + |e| proofman_common::ProofmanError::InvalidConfiguration(e.to_string()), + )?; + } + } + } + + let instance = &**secn_instance; + let collectors = + Self::take_collectors_for_instance(state, global_id, instance.instance_type()); + + self.witness_generator.compute_secn_witness( + pctx, + sctx, + state, + global_id, + instance, + collectors, + buffer_pool.take_buffer(), + stats_scope.id(), + ) + } + + /// Registers an empty collector for instances that skip collection. + /// + /// # Arguments + /// * `state` - Execution state. + /// * `global_id` - Global ID of the instance. + /// * `airgroup_id` - AIR group ID of the instance. + /// * `air_id` - AIR ID of the instance. + fn register_empty_collector( + &self, + state: &ExecutionState, + global_id: usize, + airgroup_id: usize, + air_id: usize, + ) { + let stats = Stats::new_no_collection(airgroup_id, air_id); + + state.collectors_by_instance.write().unwrap().insert(global_id, Vec::new()); + state.stats.insert_witness_stats(global_id, stats); + } + + /// Extracts collectors from state, returning an empty list for table instances. + /// + /// # Arguments + /// * `state` - Execution state. + /// * `global_id` - Global ID of the instance. + /// * `instance_type` - Type of the instance (Instance or Table). + fn take_collectors_for_instance( + state: &ExecutionState, + global_id: usize, + instance_type: InstanceType, + ) -> Vec<(usize, Box>)> { + match instance_type { + InstanceType::Instance => { + let mut guard = state.collectors_by_instance.write().unwrap(); + + guard + .remove(&global_id) + .expect("Missing collectors for given global_id") + .into_iter() + .enumerate() + .map(|(idx, opt)| { + opt.unwrap_or_else(|| { + panic!("Collector at index {} for global_id {} is None", idx, global_id) + }) + }) + .collect() + } + InstanceType::Table => { + vec![] + } + } + } + + /// Pre-calculates witnesses by determining which instances need collection. + /// + /// Sets witness readiness flags and collects data for instances that need it. + /// + /// # Arguments + /// * `pctx` - Proof context. + /// * `state` - Execution state. + /// * `global_ids` - Global IDs to pre-calculate. + pub fn pre_calculate( + &self, + pctx: &ProofCtx, + state: &ExecutionState, + global_ids: &[usize], + ) -> ProofmanResult<()> { + let secn_instances_guard = state.secn_instances.read().unwrap(); + + let mut instances_to_collect = HashMap::new(); + + for &global_id in global_ids { + let (airgroup_id, air_id) = + pctx.dctx_get_instance_info(global_id).expect("Failed to get instance info"); + + if AirClassifier::is_main(air_id) { + pctx.set_witness_ready(global_id, false); + } else if AirClassifier::is_rom(air_id) { + self.handle_rom_pre_calculate( + pctx, + state, + &secn_instances_guard, + &mut instances_to_collect, + global_id, + airgroup_id, + air_id, + ); + } else { + self.handle_secondary_pre_calculate( + pctx, + state, + &secn_instances_guard, + &mut instances_to_collect, + global_id, + ); + } + } + + // Collect all instances that need collection + if !instances_to_collect.is_empty() { + self.collector + .collect(pctx, state, instances_to_collect) + .map_err(|e| proofman_common::ProofmanError::InvalidConfiguration(e.to_string()))?; + } + Ok(()) + } + + /// Handles ROM instance pre-calculation. + #[allow(clippy::too_many_arguments)] + fn handle_rom_pre_calculate<'a>( + &self, + pctx: &ProofCtx, + state: &ExecutionState, + secn_instances: &'a SecnInstanceMap, + instances_to_collect: &mut SecnInstanceMapRef<'a, F>, + global_id: usize, + airgroup_id: usize, + air_id: usize, + ) { + if self.is_asm_emulator { + pctx.set_witness_ready(global_id, false); + } else { + let secn_instance = &secn_instances[&global_id]; + let rom_instance = secn_instance.as_any().downcast_ref::().unwrap(); + + if rom_instance.skip_collector() { + self.register_empty_collector(state, global_id, airgroup_id, air_id); + pctx.set_witness_ready(global_id, true); + } else { + instances_to_collect.insert(global_id, secn_instance); + } + } + } + + /// Handles secondary instance pre-calculation. + fn handle_secondary_pre_calculate<'a>( + &self, + pctx: &ProofCtx, + state: &ExecutionState, + secn_instances: &'a SecnInstanceMap, + instances_to_collect: &mut SecnInstanceMapRef<'a, F>, + global_id: usize, + ) { + let secn_instance = &secn_instances[&global_id]; + + if secn_instance.instance_type() == InstanceType::Instance + && !state.collectors_by_instance.read().unwrap().contains_key(&global_id) + { + instances_to_collect.insert(global_id, secn_instance); + } else { + pctx.set_witness_ready(global_id, true); + } + } +} diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 094ec261c..08f329e61 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -19,13 +19,12 @@ asm-runner = { workspace = true } colored = { workspace = true } tracing = { workspace = true } zisk-distributed-common = { workspace = true } -executor = { workspace = true } ziskemu = { workspace = true } zisk-core = { workspace = true } zisk-verifier = { workspace = true } zisk-build = { workspace = true } -zisk-witness = { workspace = true } sha2 = { workspace = true } +executor = { workspace = true } bincode = { workspace = true } serde = { workspace = true } @@ -39,7 +38,7 @@ gpu = [ "ziskemu/gpu", "packed" ] -packed = ["proofman-common/packed", "executor/packed", "zisk-witness/packed"] +packed = ["proofman-common/packed", "executor/packed"] stats = [] disable_distributed = [ "proofman/disable_distributed", diff --git a/sdk/src/client.rs b/sdk/src/client.rs index 76580886f..0a65b5fa7 100644 --- a/sdk/src/client.rs +++ b/sdk/src/client.rs @@ -6,12 +6,22 @@ //! This legacy interface is maintained for backward compatibility. use crate::ProverClientBuilder; +use std::sync::atomic::{AtomicBool, Ordering}; + +static PROVER_CLIENT_CREATED: AtomicBool = AtomicBool::new(false); pub struct ProverClient; impl ProverClient { #[must_use] pub fn builder() -> ProverClientBuilder { + if PROVER_CLIENT_CREATED.swap(true, Ordering::SeqCst) { + panic!( + "ProverClient::builder() can only be called once! \ + Multiple ProverClient instances are not supported. \ + Reuse the existing client for all operations." + ); + } ProverClientBuilder::new() } } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 24a33fe46..52f8801a5 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -3,7 +3,6 @@ mod client; mod prover; mod utils; mod verifier; -mod zisk_lib_loader; mod ziskemu; pub use builder::*; @@ -11,7 +10,7 @@ pub use client::ProverClient; pub use prover::*; pub use utils::*; pub use verifier::*; -pub use zisk_lib_loader::*; + pub use ziskemu::*; pub use proofman_common::VerboseMode; diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 4c9c7b7aa..ae55bbcd8 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -2,24 +2,25 @@ use crate::get_asm_paths; use crate::{ check_paths_exist, ensure_custom_commits, prover::{ProverBackend, ProverEngine, ZiskBackend}, - ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, ZiskProgramVK, + ZiskAggPhaseResult, ZiskExecuteResult, ZiskPhaseResult, ZiskProgramPK, ZiskProgramVK, ZiskProof, ZiskProofWithPublicValues, ZiskProveResult, ZiskPublics, ZiskVerifyConstraintsResult, }; use crate::{ProofMode, ProofOpts}; use asm_runner::{AsmRunnerOptions, AsmServices}; +use executor::{get_packed_info, init_executor_asm, AsmResources}; use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, RowInfo, VerboseMode}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rom_setup::DEFAULT_CACHE_PATH; -use std::sync::OnceLock; -use std::{collections::HashMap, path::PathBuf}; -use tracing::info; +use std::path::PathBuf; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ElfBinaryLike; use zisk_common::ExecutorStatsHandle; +use zisk_core::Riscv2zisk; use zisk_distributed_common::LoggingConfig; -use zisk_witness::get_packed_info; use anyhow::Result; @@ -31,6 +32,7 @@ impl ZiskBackend for Asm { pub struct AsmProver { pub(crate) core_prover: AsmCoreProver, + pub n_setups: AtomicU64, } impl AsmProver { @@ -62,13 +64,13 @@ impl AsmProver { logging_config, )?; - Ok(Self { core_prover }) + Ok(Self { core_prover, n_setups: AtomicU64::new(0) }) } pub fn new_verifier(proving_key: PathBuf, proving_key_snark: PathBuf) -> Result { let core_prover = AsmCoreProver::new_verifier(proving_key, proving_key_snark)?; - Ok(Self { core_prover }) + Ok(Self { core_prover, n_setups: AtomicU64::new(0) }) } } @@ -85,6 +87,10 @@ impl ProverEngine for AsmProver { self.core_prover.backend.set_stdin(stdin) } + fn register_program(&self, pk: &ZiskProgramPK) -> Result<()> { + self.core_prover.backend.register_program(pk) + } + fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()> { self.core_prover.backend.set_hints_stream(hints_stream) } @@ -97,10 +103,26 @@ impl ProverEngine for AsmProver { .unwrap_or(0) } - fn setup(&self, elf: &impl ElfBinaryLike) -> Result { + fn setup(&self, elf: &impl ElfBinaryLike) -> Result<(ZiskProgramPK, ZiskProgramVK)> { let pctx = self.core_prover.backend.get_pctx()?; let (rom_bin_path, vk) = ensure_custom_commits(&pctx, elf)?; - let custom_commits_map = HashMap::from([("rom".to_string(), rom_bin_path)]); + + let world_rank = self.core_prover.rank_info.world_rank; + let local_rank = self.core_prover.rank_info.local_rank; + let n_processes = self.core_prover.rank_info.n_processes; + let unlock_mapped_memory = self.core_prover.unlock_mapped_memory; + let verbose_mode = self.core_prover.verbose; + let rank_info = self.core_prover.rank_info.clone(); + let base_port = Some(AsmServices::port_base_offset( + self.core_prover.base_port, + n_processes, + self.n_setups.load(Ordering::SeqCst), + )); + + let rv2zk = Riscv2zisk::new(elf.elf()); + + let zisk_rom = rv2zk.run().unwrap_or_else(|e| panic!("Application error: {e}")); + let zisk_rom = Arc::new(zisk_rom); let default_cache_path = std::env::var("HOME") .map(PathBuf::from) @@ -116,60 +138,64 @@ impl ProverEngine for AsmProver { check_paths_exist(&asm_rh_path)?; timer_start_info!(STARTING_ASM_MICROSERVICES); - let world_rank = self.core_prover.rank_info.world_rank; - let local_rank = self.core_prover.rank_info.local_rank; - let asm_services = AsmServices::new(world_rank, local_rank, self.core_prover.base_port); + let asm_services = AsmServices::new(world_rank, local_rank, base_port); let asm_runner_options = AsmRunnerOptions::new() - .with_base_port(self.core_prover.base_port) + .with_base_port(base_port) .with_world_rank(world_rank) .with_local_rank(local_rank) - .with_verbose(self.core_prover.verbose == VerboseMode::Debug) - .with_metrics(self.core_prover.verbose == VerboseMode::Debug) - .with_unlock_mapped_memory(self.core_prover.unlock_mapped_memory); + .with_verbose(verbose_mode == VerboseMode::Debug) + .with_metrics(verbose_mode == VerboseMode::Debug) + .with_unlock_mapped_memory(unlock_mapped_memory); asm_services.start_asm_services(&asm_mt_path, asm_runner_options)?; timer_stop_and_log_info!(STARTING_ASM_MICROSERVICES); - let witness_lib = ZiskLibLoader::load_asm( - self.core_prover.verbose, - self.core_prover.shared_tables, - asm_mt_path.clone(), - asm_rh_path, - self.core_prover.base_port, - self.core_prover.unlock_mapped_memory, + let asm_resources = AsmResources::new( + local_rank, + base_port, + unlock_mapped_memory, + verbose_mode, elf.with_hints(), - )?; - - self.core_prover - .asm_services - .set(asm_services) - .map_err(|_| anyhow::anyhow!("ASM services have already been initialized."))?; - - self.core_prover.backend.register_witness_lib( - elf.elf(), - witness_lib, - custom_commits_map, - )?; - Ok(ZiskProgramVK { vk }) + ); + + self.n_setups.fetch_add(1, Ordering::SeqCst); + + Ok(( + ZiskProgramPK { + zisk_rom, + elf_bin_path: rom_bin_path, + asm_services: Some(asm_services), + asm_resources: Some(asm_resources), + rank_info, + use_hints: elf.with_hints(), + }, + ZiskProgramVK { vk }, + )) } fn get_execution_info(&self) -> Result { self.core_prover.backend.get_execution_info() } - fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result { - self.core_prover.backend.execute(stdin, output_path) + fn execute( + &self, + pk: &ZiskProgramPK, + stdin: ZiskStdin, + output_path: Option, + ) -> Result { + self.core_prover.backend.execute(pk, stdin, output_path) } fn stats( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, debug_info: Option>, minimal_memory: bool, mpi_node: Option, ) -> Result<(i32, i32, Option)> { - self.core_prover.backend.stats(stdin, debug_info, minimal_memory, mpi_node) + self.core_prover.backend.stats(pk, stdin, debug_info, minimal_memory, mpi_node) } fn get_instance_trace( @@ -198,14 +224,19 @@ impl ProverEngine for AsmProver { fn verify_constraints_debug( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, debug_info: Option>, ) -> Result { - self.core_prover.backend.verify_constraints_debug(stdin, debug_info) + self.core_prover.backend.verify_constraints_debug(pk, stdin, debug_info) } - fn verify_constraints(&self, stdin: ZiskStdin) -> Result { - self.core_prover.backend.verify_constraints(stdin) + fn verify_constraints( + &self, + pk: &ZiskProgramPK, + stdin: ZiskStdin, + ) -> Result { + self.core_prover.backend.verify_constraints(pk, stdin) } fn vk(&self, elf: &impl ElfBinaryLike) -> Result { @@ -216,17 +247,14 @@ impl ProverEngine for AsmProver { self.core_prover.backend.verify(proof, publics, vk) } - fn prove_debug(&self, stdin: ZiskStdin, proof_options: ProofOpts) -> Result { - self.core_prover.backend.prove_debug(stdin, proof_options) - } - fn prove( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, mode: ProofMode, proof_options: ProofOpts, ) -> Result { - self.core_prover.backend.prove(stdin, mode, proof_options) + self.core_prover.backend.prove(pk, stdin, mode, proof_options) } fn prove_snark( @@ -273,28 +301,10 @@ impl ProverEngine for AsmProver { pub struct AsmCoreProver { backend: ProverBackend, - asm_services: OnceLock, rank_info: RankInfo, - verbose: VerboseMode, - shared_tables: bool, base_port: Option, unlock_mapped_memory: bool, -} - -impl Drop for AsmCoreProver { - fn drop(&mut self) { - // Shut down ASM microservices - info!(">>> [{}] Stopping ASM microservices.", self.rank_info.world_rank); - if let Some(asm_services) = &self.asm_services.get() { - if let Err(e) = asm_services.stop_asm_services() { - tracing::error!( - ">>> [{}] Failed to stop ASM microservices: {}", - self.rank_info.world_rank, - e - ); - } - } - } + verbose: VerboseMode, } impl AsmCoreProver { @@ -346,17 +356,27 @@ impl AsmCoreProver { )?); } - let core = - ProverBackend::new(proofman, snark_wrapper, proving_key, Some(proving_key_snark)); + let executor = init_executor_asm( + verbose.into(), + shared_tables, + unlock_mapped_memory, + &proofman.get_wcm(), + )?; + + let core = ProverBackend::new( + proofman, + snark_wrapper, + executor, + proving_key, + Some(proving_key_snark), + ); Ok(Self { backend: core, - asm_services: OnceLock::new(), rank_info, - verbose: verbose.into(), - shared_tables, base_port, unlock_mapped_memory, + verbose: verbose.into(), }) } @@ -366,12 +386,10 @@ impl AsmCoreProver { Ok(Self { backend: core_prover, - asm_services: OnceLock::new(), rank_info: RankInfo { world_rank: 0, local_rank: 0, n_processes: 1 }, - verbose: VerboseMode::Info, - shared_tables: false, base_port: None, unlock_mapped_memory: false, + verbose: VerboseMode::Info, }) } } diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 92d7cfd8d..cb8cbbfc4 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -7,11 +7,12 @@ use crate::{ }; use crate::{ProofMode, ProofOpts}; use crate::{ - ZiskAggPhaseResult, ZiskExecuteResult, ZiskPhaseResult, ZiskProgramVK, ZiskProof, - ZiskProveResult, ZiskVerifyConstraintsResult, + ZiskAggPhaseResult, ZiskExecuteResult, ZiskPhaseResult, ZiskProgramPK, ZiskProgramVK, + ZiskProof, ZiskProveResult, ZiskVerifyConstraintsResult, }; use anyhow::Result; use colored::Colorize; +use executor::ZiskExecutor; use fields::Goldilocks; use proofman::{ AggProofs, ExecutionInfo, ProofInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult, @@ -22,18 +23,16 @@ use proofman_util::VadcopFinalProof; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; -use std::sync::OnceLock; use zisk_common::stats_mark; use zisk_common::{ io::{StreamSource, ZiskStdin}, ElfBinaryLike, ExecutorStatsHandle, ZiskExecutionResult, }; -use zisk_witness::WitnessLib; pub(crate) struct ProverBackend { proofman: Option>, snark_wrapper: Option>, - witness_lib: OnceLock>, + executor: Option>>, proving_key_path: PathBuf, proving_key_snark_path: Option, } @@ -42,13 +41,14 @@ impl ProverBackend { pub fn new( proofman: ProofMan, snark_wrapper: Option>, + executor: Arc>, proving_key_path: PathBuf, proving_key_snark_path: Option, ) -> Self { Self { proofman: Some(proofman), snark_wrapper, - witness_lib: OnceLock::new(), + executor: Some(executor), proving_key_path, proving_key_snark_path, } @@ -61,7 +61,7 @@ impl ProverBackend { Self { proofman: None, snark_wrapper: None, - witness_lib: OnceLock::new(), + executor: None, proving_key_path, proving_key_snark_path, } @@ -74,56 +74,56 @@ impl ProverBackend { Ok(proofman.get_wcm().get_pctx()) } - pub fn register_witness_lib( - &self, - elf: &[u8], - mut witness_lib: WitnessLib, - custom_commits_map: HashMap, - ) -> Result<()> { + pub fn register_program(&self, program_pk: &ZiskProgramPK) -> Result<()> { + let executor = self.executor.as_ref().ok_or_else(|| { + anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") + })?; + let proofman = self.proofman.as_ref().ok_or_else(|| { anyhow::anyhow!("Proofman is not initialized. Please initialize it before use.") })?; - witness_lib.register_witness(elf, &proofman.get_wcm())?; - - if self.witness_lib.set(witness_lib).is_err() { - return Err(anyhow::anyhow!("Witness library has already been registered.")); + if let Some(asm_resources) = &program_pk.asm_resources { + executor.set_asm_resources(asm_resources.clone()); } + executor.set_rom(program_pk.zisk_rom.clone(), program_pk.use_hints); + + let custom_commits_map = + HashMap::from([("rom".to_string(), program_pk.elf_bin_path.clone())]); proofman .register_custom_commits(custom_commits_map) .map_err(|e| anyhow::anyhow!(e.to_string())) } pub fn set_stdin(&self, stdin: ZiskStdin) -> Result<()> { - let witness_lib = self.witness_lib.get().ok_or_else(|| { - anyhow::anyhow!("Witness_lib is not initialized. Please initialize it before use.") + let executor = self.executor.as_ref().ok_or_else(|| { + anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") })?; - witness_lib.set_stdin(stdin); + executor.set_stdin(stdin); Ok(()) } pub fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()> { - let witness_lib = self.witness_lib.get().ok_or_else(|| { - anyhow::anyhow!("Witness_lib is not initialized. Please initialize it before use.") + let executor = self.executor.as_ref().ok_or_else(|| { + anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") })?; - witness_lib.set_hints_stream(hints_stream) + executor.set_hints_stream_src(hints_stream) } pub fn execution_result(&self) -> Result<(ZiskExecutionResult, ExecutorStatsHandle)> { - let witness_lib = self.witness_lib.get().ok_or_else(|| { - anyhow::anyhow!("Witness_lib is not initialized. Please initialize it before use.") + let executor = self.executor.as_ref().ok_or_else(|| { + anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") })?; - let (result, stats) = witness_lib.execution_result().ok_or_else(|| { - anyhow::anyhow!("Failed to get execution result from emulator prover") - })?; + let (result, stats) = executor.get_execution_result(); Ok((result, stats)) } pub(crate) fn execute( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, output_path: Option, ) -> Result { @@ -132,11 +132,13 @@ impl ProverBackend { .as_ref() .ok_or_else(|| anyhow::anyhow!("Cannot execute in verifier mode"))?; - let witness_lib = self.witness_lib.get().ok_or_else(|| { - anyhow::anyhow!("witness_lib is not initialized. Please initialize it before use.") + let executor = self.executor.as_ref().ok_or_else(|| { + anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") })?; - witness_lib.set_stdin(stdin); + self.register_program(pk)?; + + executor.set_stdin(stdin); let start = std::time::Instant::now(); @@ -146,9 +148,7 @@ impl ProverBackend { let elapsed = start.elapsed(); - let (result, _) = witness_lib.execution_result().ok_or_else(|| { - anyhow::anyhow!("Failed to get execution result from emulator prover") - })?; + let (result, _) = executor.get_execution_result(); let publics = proofman.get_publics(); @@ -157,6 +157,7 @@ impl ProverBackend { pub(crate) fn stats( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, debug_info: Option>, minimal_memory: bool, @@ -167,13 +168,15 @@ impl ProverBackend { .as_ref() .ok_or_else(|| anyhow::anyhow!("Cannot compute stats in verifier mode"))?; - let witness_lib = self.witness_lib.get().ok_or_else(|| { - anyhow::anyhow!("witness_lib is not initialized. Please initialize it before use.") + let executor = self.executor.as_ref().ok_or_else(|| { + anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") })?; let debug_info = create_debug_info(debug_info, self.proving_key_path.clone())?; - witness_lib.set_stdin(stdin); + self.register_program(pk)?; + + executor.set_stdin(stdin); let rank_info = proofman.get_rank_info(); @@ -205,9 +208,7 @@ impl ProverBackend { .map_err(|e| anyhow::anyhow!("Error generating execution: {}", e))?; let (_, stats): (ZiskExecutionResult, ExecutorStatsHandle) = - witness_lib.execution_result().ok_or_else(|| { - anyhow::anyhow!("Failed to get execution result from emulator prover") - })?; + executor.get_execution_result(); Ok((rank_info.world_rank, rank_info.n_processes, Some(stats))) } @@ -259,6 +260,7 @@ impl ProverBackend { pub(crate) fn verify_constraints_debug( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, debug_info: Option>, ) -> Result { @@ -267,24 +269,24 @@ impl ProverBackend { .as_ref() .ok_or_else(|| anyhow::anyhow!("Cannot verify constraints in verifier mode"))?; - let witness_lib = self.witness_lib.get().ok_or_else(|| { - anyhow::anyhow!("witness_lib is not initialized. Please initialize it before use.") + let executor = self.executor.as_ref().ok_or_else(|| { + anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") })?; let start = std::time::Instant::now(); let debug_info = create_debug_info(debug_info, self.proving_key_path.clone())?; - witness_lib.set_stdin(stdin); + self.register_program(pk)?; + + executor.set_stdin(stdin); proofman .verify_proof_constraints_from_lib(&debug_info, false) .map_err(|e| anyhow::anyhow!("Error generating proof: {}", e))?; let elapsed = start.elapsed(); - let (result, stats) = witness_lib.execution_result().ok_or_else(|| { - anyhow::anyhow!("Failed to get execution result from emulator prover") - })?; + let (result, stats) = executor.get_execution_result(); stats_mark!(stats, 0, "END", 0); @@ -298,69 +300,19 @@ impl ProverBackend { pub(crate) fn verify_constraints( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, ) -> Result { - self.verify_constraints_debug(stdin, None) + self.verify_constraints_debug(pk, stdin, None) } pub(crate) fn vk(&self, elf: &impl ElfBinaryLike) -> Result { get_program_vk_with_proving_key(elf, self.proving_key_path.clone()) } - pub(crate) fn prove_debug( - &self, - stdin: ZiskStdin, - proof_options: ProofOpts, - ) -> Result { - let proofman = self - .proofman - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Cannot prove in verifier mode"))?; - - let witness_lib = self.witness_lib.get().ok_or_else(|| { - anyhow::anyhow!("witness_lib is not initialized. Please initialize it before use.") - })?; - - let start = std::time::Instant::now(); - - witness_lib.set_stdin(stdin); - - proofman.set_barrier(); - proofman - .generate_proof_from_lib( - ProvePhaseInputs::Full(ProofInfo::new(None, 1, vec![0], 0)), - ProofOptions::new( - false, - false, - false, - false, - proof_options.verify_proofs, - proof_options.minimal_memory, - proof_options.save_proofs, - proof_options.output_dir_path.clone(), - ), - ProvePhase::Full, - ) - .map_err(|e| anyhow::anyhow!("Error generating proof: {}", e))?; - - let elapsed = start.elapsed(); - - let (execution_result, stats) = witness_lib.execution_result().ok_or_else(|| { - anyhow::anyhow!("Failed to get execution result from emulator prover") - })?; - - stats_mark!(stats, 0, "END", 0); - - #[cfg(feature = "stats")] - stats.store_stats(); - - proofman.set_barrier(); - - Ok(ZiskProveResult::new_null(execution_result, elapsed, stats)) - } - pub(crate) fn prove( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, mode: ProofMode, proof_options: ProofOpts, @@ -370,10 +322,12 @@ impl ProverBackend { .as_ref() .ok_or_else(|| anyhow::anyhow!("Cannot prove in verifier mode"))?; - let witness_lib = self.witness_lib.get().ok_or_else(|| { - anyhow::anyhow!("witness_lib is not initialized. Please initialize it before use.") + let executor = self.executor.as_ref().ok_or_else(|| { + anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") })?; + self.register_program(pk)?; + if mode == ProofMode::Snark && self.snark_wrapper.is_none() { return Err(anyhow::anyhow!( "Snark wrapper is not initialized. Cannot generate snark proof." @@ -382,7 +336,7 @@ impl ProverBackend { let start = std::time::Instant::now(); - witness_lib.set_stdin(stdin); + executor.set_stdin(stdin); let compressed = matches!(mode, ProofMode::VadcopFinalCompressed); @@ -409,9 +363,7 @@ impl ProverBackend { _ => (None, None), }; - let (execution_result, stats) = witness_lib.execution_result().ok_or_else(|| { - anyhow::anyhow!("Failed to get execution result from emulator prover") - })?; + let (execution_result, stats) = executor.get_execution_result(); // Store the stats in stats.json stats_mark!(stats, 0, "END", 0); diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 43ca2a7aa..baf0f570c 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -1,20 +1,21 @@ use crate::{ check_paths_exist, prover::{ProverBackend, ProverEngine, ZiskBackend}, - ZiskAggPhaseResult, ZiskExecuteResult, ZiskLibLoader, ZiskPhaseResult, ZiskProgramVK, + ZiskAggPhaseResult, ZiskExecuteResult, ZiskPhaseResult, ZiskProgramPK, ZiskProgramVK, ZiskProof, ZiskProofWithPublicValues, ZiskProveResult, ZiskPublics, ZiskVerifyConstraintsResult, }; use crate::{ensure_custom_commits, ProofMode, ProofOpts}; +use executor::{get_packed_info, init_executor_emu}; use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; -use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, RowInfo, VerboseMode}; -use std::collections::HashMap; +use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, RowInfo}; use std::path::PathBuf; +use std::sync::Arc; use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ElfBinaryLike; use zisk_common::ExecutorStatsHandle; +use zisk_core::Riscv2zisk; use zisk_distributed_common::LoggingConfig; -use zisk_witness::get_packed_info; use anyhow::Result; @@ -76,26 +77,35 @@ impl ProverEngine for EmuProver { self.core_prover.backend.set_stdin(stdin) } + fn register_program(&self, pk: &ZiskProgramPK) -> Result<()> { + self.core_prover.backend.register_program(pk) + } + fn set_hints_stream(&self, _: StreamSource) -> Result<()> { unreachable!("EMU prover does not support precompile hints"); } - fn setup(&self, elf: &impl ElfBinaryLike) -> Result { + fn setup(&self, elf: &impl ElfBinaryLike) -> Result<(ZiskProgramPK, ZiskProgramVK)> { let pctx = self.core_prover.backend.get_pctx()?; let (rom_bin_path, vk) = ensure_custom_commits(&pctx, elf)?; - let custom_commits_map = HashMap::from([("rom".to_string(), rom_bin_path)]); - // Build emulator library - let witness_lib = - ZiskLibLoader::load_emu(self.core_prover.verbose, self.core_prover.shared_tables)?; - - self.core_prover.backend.register_witness_lib( - elf.elf(), - witness_lib, - custom_commits_map, - )?; - Ok(ZiskProgramVK { vk }) + let rv2zk = Riscv2zisk::new(elf.elf()); + + let zisk_rom = rv2zk.run().unwrap_or_else(|e| panic!("Application error: {e}")); + let zisk_rom = Arc::new(zisk_rom); + + Ok(( + ZiskProgramPK { + zisk_rom, + elf_bin_path: rom_bin_path, + asm_resources: None, + asm_services: None, + rank_info: self.core_prover.rank_info.clone(), + use_hints: false, + }, + ZiskProgramVK { vk }, + )) } fn executed_steps(&self) -> u64 { @@ -110,18 +120,24 @@ impl ProverEngine for EmuProver { self.core_prover.backend.get_execution_info() } - fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result { - self.core_prover.backend.execute(stdin, output_path) + fn execute( + &self, + pk: &ZiskProgramPK, + stdin: ZiskStdin, + output_path: Option, + ) -> Result { + self.core_prover.backend.execute(pk, stdin, output_path) } fn stats( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, debug_info: Option>, minimal_memory: bool, mpi_node: Option, ) -> Result<(i32, i32, Option)> { - self.core_prover.backend.stats(stdin, debug_info, minimal_memory, mpi_node) + self.core_prover.backend.stats(pk, stdin, debug_info, minimal_memory, mpi_node) } fn get_instance_trace( @@ -150,14 +166,19 @@ impl ProverEngine for EmuProver { fn verify_constraints_debug( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, debug_info: Option>, ) -> Result { - self.core_prover.backend.verify_constraints_debug(stdin, debug_info) + self.core_prover.backend.verify_constraints_debug(pk, stdin, debug_info) } - fn verify_constraints(&self, stdin: ZiskStdin) -> Result { - self.core_prover.backend.verify_constraints(stdin) + fn verify_constraints( + &self, + pk: &ZiskProgramPK, + stdin: ZiskStdin, + ) -> Result { + self.core_prover.backend.verify_constraints(pk, stdin) } fn vk(&self, elf: &impl ElfBinaryLike) -> Result { @@ -168,17 +189,14 @@ impl ProverEngine for EmuProver { self.core_prover.backend.verify(proof, publics, vk) } - fn prove_debug(&self, stdin: ZiskStdin, proof_options: ProofOpts) -> Result { - self.core_prover.backend.prove_debug(stdin, proof_options) - } - fn prove( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, mode: ProofMode, proof_options: ProofOpts, ) -> Result { - self.core_prover.backend.prove(stdin, mode, proof_options) + self.core_prover.backend.prove(pk, stdin, mode, proof_options) } fn prove_snark( @@ -226,8 +244,6 @@ impl ProverEngine for EmuProver { pub struct EmuCoreProver { backend: ProverBackend, rank_info: RankInfo, - verbose: VerboseMode, - shared_tables: bool, } impl EmuCoreProver { @@ -278,10 +294,17 @@ impl EmuCoreProver { )?); } - let core = - ProverBackend::new(proofman, snark_wrapper, proving_key, Some(proving_key_snark)); + let executor = init_executor_emu(verbose.into(), shared_tables, &proofman.get_wcm())?; + + let core = ProverBackend::new( + proofman, + snark_wrapper, + executor, + proving_key, + Some(proving_key_snark), + ); - Ok(Self { backend: core, rank_info, verbose: verbose.into(), shared_tables }) + Ok(Self { backend: core, rank_info }) } #[allow(clippy::too_many_arguments)] @@ -291,8 +314,6 @@ impl EmuCoreProver { Ok(Self { backend: core_prover, rank_info: RankInfo { world_rank: 0, local_rank: 0, n_processes: 1 }, - verbose: VerboseMode::Info, - shared_tables: false, }) } } diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index e7509dc6c..27376a847 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -7,24 +7,29 @@ pub use emu::*; use proofman::{ AggProofs, ExecutionInfo, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProtocol, }; -use proofman_common::{ProofOptions, RowInfo}; +use proofman_common::{ProofOptions, RankInfo, RowInfo}; use proofman_util::VadcopFinalProof; use sha2::{Digest, Sha256}; use anyhow::{Context, Result}; +use asm_runner::AsmServices; +use executor::AsmResources; use proofman::PlanningInfo; use serde::{Deserialize, Serialize}; use std::fs::File; use std::{ cell::Cell, path::{Path, PathBuf}, + sync::Arc, time::Duration, }; +use tracing::info; use zisk_common::ElfBinaryLike; use zisk_common::{ io::{StreamSource, ZiskStdin}, ExecutorStatsHandle, StatsCostPerType, ZiskExecutionResult, }; +use zisk_core::ZiskRom; pub struct ZiskExecuteResult { pub execution: ZiskExecutionResult, @@ -114,6 +119,32 @@ impl ZiskVerifyConstraintsResult { } } +#[derive(Debug, Clone)] +pub struct ZiskProgramPK { + pub zisk_rom: Arc, + pub elf_bin_path: PathBuf, + pub asm_resources: Option, + pub asm_services: Option, + pub rank_info: RankInfo, + pub use_hints: bool, +} + +impl Drop for ZiskProgramPK { + fn drop(&mut self) { + // Shut down ASM microservices + info!(">>> [{}] Stopping ASM microservices.", self.rank_info.world_rank); + if let Some(asm_services) = &self.asm_services { + if let Err(e) = asm_services.stop_asm_services() { + tracing::error!( + ">>> [{}] Failed to stop ASM microservices: {}", + self.rank_info.world_rank, + e + ); + } + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ZiskProgramVK { pub vk: Vec, @@ -679,7 +710,7 @@ pub struct ZiskAggPhaseResult { } pub trait ProverEngine { - fn setup(&self, elf: &impl ElfBinaryLike) -> Result; + fn setup(&self, elf: &impl ElfBinaryLike) -> Result<(ZiskProgramPK, ZiskProgramVK)>; fn world_rank(&self) -> i32; @@ -687,6 +718,8 @@ pub trait ProverEngine { fn set_stdin(&self, stdin: ZiskStdin) -> Result<()>; + fn register_program(&self, pk: &ZiskProgramPK) -> Result<()>; + fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()>; fn executed_steps(&self) -> u64; @@ -711,10 +744,16 @@ pub trait ProverEngine { offset: Option, ) -> Result>; - fn execute(&self, stdin: ZiskStdin, output_path: Option) -> Result; + fn execute( + &self, + pk: &ZiskProgramPK, + stdin: ZiskStdin, + output_path: Option, + ) -> Result; fn stats( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, debug_info: Option>, minimal_memory: bool, @@ -723,20 +762,24 @@ pub trait ProverEngine { fn verify_constraints_debug( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, debug_info: Option>, ) -> Result; - fn verify_constraints(&self, stdin: ZiskStdin) -> Result; + fn verify_constraints( + &self, + pk: &ZiskProgramPK, + stdin: ZiskStdin, + ) -> Result; fn vk(&self, elf: &impl ElfBinaryLike) -> Result; fn verify(&self, proof: &ZiskProof, publics: &ZiskPublics, vk: &ZiskProgramVK) -> Result<()>; - fn prove_debug(&self, stdin: ZiskStdin, proof_options: ProofOpts) -> Result; - fn prove( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, mode: ProofMode, proof_options: ProofOpts, @@ -788,7 +831,7 @@ impl ZiskProver { Self { prover } } - pub fn setup(&self, elf: &impl ElfBinaryLike) -> Result { + pub fn setup(&self, elf: &impl ElfBinaryLike) -> Result<(ZiskProgramPK, ZiskProgramVK)> { self.prover.setup(elf) } @@ -797,6 +840,10 @@ impl ZiskProver { self.prover.set_stdin(stdin) } + pub fn register_program(&self, pk: &ZiskProgramPK) -> Result<()> { + self.prover.register_program(pk) + } + pub fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()> { self.prover.set_hints_stream(hints_stream) } @@ -824,19 +871,20 @@ impl ZiskProver { /// Execute the prover with the given standard input and output path. /// It only runs the execution without generating a proof. - pub fn execute(&self, stdin: ZiskStdin) -> Result { - self.prover.execute(stdin, None) + pub fn execute(&self, pk: &ZiskProgramPK, stdin: ZiskStdin) -> Result { + self.prover.execute(pk, stdin, None) } /// Get the execution statistics with the given standard input and debug information. pub fn stats( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, debug_info: Option>, minimal_memory: bool, mpi_node: Option, ) -> Result<(i32, i32, Option)> { - self.prover.stats(stdin, debug_info, minimal_memory, mpi_node) + self.prover.stats(pk, stdin, debug_info, minimal_memory, mpi_node) } /// Get the instance trace for a given instance ID and row range. @@ -869,15 +917,20 @@ impl ZiskProver { /// Verify the constraints with the given standard input and debug information. pub fn verify_constraints_debug( &self, + pk: &ZiskProgramPK, stdin: ZiskStdin, debug_info: Option>, ) -> Result { - self.prover.verify_constraints_debug(stdin, debug_info) + self.prover.verify_constraints_debug(pk, stdin, debug_info) } /// Verify the constraints with the given standard input. - pub fn verify_constraints(&self, stdin: ZiskStdin) -> Result { - self.prover.verify_constraints(stdin) + pub fn verify_constraints( + &self, + pk: &ZiskProgramPK, + stdin: ZiskStdin, + ) -> Result { + self.prover.verify_constraints(pk, stdin) } pub fn vk(&self, elf: &impl ElfBinaryLike) -> Result { @@ -898,10 +951,10 @@ impl ZiskProver { /// /// # Example /// ```ignore - /// let result = prover.prove(stdin).compressed().run()?; + /// let result = prover.prove(&pk, stdin).compressed().run()?; /// ``` - pub fn prove(&self, stdin: ZiskStdin) -> ProveBuilder<'_, C> { - ProveBuilder::new(&self.prover, stdin) + pub fn prove<'a>(&'a self, pk: &'a ZiskProgramPK, stdin: ZiskStdin) -> ProveBuilder<'a, C> { + ProveBuilder::new(&self.prover, pk, stdin) } pub fn prove_snark( @@ -958,14 +1011,21 @@ impl ZiskProver { /// ``` pub struct ProveBuilder<'a, C: ZiskBackend> { prover: &'a C::Prover, + pk: &'a ZiskProgramPK, stdin: ZiskStdin, mode: ProofMode, proof_options: ProofOpts, } impl<'a, C: ZiskBackend> ProveBuilder<'a, C> { - fn new(prover: &'a C::Prover, stdin: ZiskStdin) -> Self { - Self { prover, stdin, mode: ProofMode::VadcopFinal, proof_options: ProofOpts::default() } + fn new(prover: &'a C::Prover, pk: &'a ZiskProgramPK, stdin: ZiskStdin) -> Self { + Self { + prover, + pk, + stdin, + mode: ProofMode::VadcopFinal, + proof_options: ProofOpts::default(), + } } /// Enable compressed proof generation. @@ -986,10 +1046,6 @@ impl<'a, C: ZiskBackend> ProveBuilder<'a, C> { /// Execute the proof generation with the configured options. pub fn run(self) -> Result { - self.prover.prove(self.stdin, self.mode, self.proof_options) - } - - pub fn run_debug(self) -> Result { - self.prover.prove_debug(self.stdin, self.proof_options) + self.prover.prove(self.pk, self.stdin, self.mode, self.proof_options) } } diff --git a/sdk/src/zisk_lib_loader.rs b/sdk/src/zisk_lib_loader.rs deleted file mode 100644 index 85f1316fd..000000000 --- a/sdk/src/zisk_lib_loader.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::path::PathBuf; - -use fields::PrimeField64; -use proofman_common::VerboseMode; -use zisk_witness::WitnessLib; - -use anyhow::Result; - -#[derive(Default)] -pub struct ZiskLibLoader; - -impl ZiskLibLoader { - #[allow(clippy::too_many_arguments)] - fn load_library( - verbose: VerboseMode, - shared_tables: bool, - asm_mt_filename: Option, - asm_rh_filename: Option, - base_port: Option, - unlock_mapped_memory: Option, - with_hints: bool, - ) -> Result> { - Ok(WitnessLib::new( - verbose, - asm_mt_filename, - asm_rh_filename, - base_port, - unlock_mapped_memory.unwrap_or(false), - shared_tables, - with_hints, - )) - } - - pub fn load_emu( - verbose: VerboseMode, - shared_tables: bool, - ) -> Result> { - Self::load_library(verbose, shared_tables, None, None, None, None, false) - } - - #[allow(clippy::too_many_arguments)] - pub fn load_asm( - verbose: VerboseMode, - shared_tables: bool, - asm_mt_filename: PathBuf, - asm_rh_filename: PathBuf, - base_port: Option, - unlock_mapped_memory: bool, - with_hints: bool, - ) -> Result> { - Self::load_library( - verbose, - shared_tables, - Some(asm_mt_filename), - Some(asm_rh_filename), - base_port, - Some(unlock_mapped_memory), - with_hints, - ) - } -} diff --git a/state-machines/rom/src/rom.rs b/state-machines/rom/src/rom.rs index 6b3dc07ab..73107d48d 100644 --- a/state-machines/rom/src/rom.rs +++ b/state-machines/rom/src/rom.rs @@ -9,7 +9,6 @@ //! collectors. use std::{ - path::PathBuf, sync::{atomic::AtomicU64, Arc, Mutex}, thread::JoinHandle, }; @@ -30,7 +29,7 @@ use zisk_pil::{MainTrace, RomRomTrace, RomRomTraceRow, RomTrace}; /// The `RomSM` struct represents the ROM State Machine pub struct RomSM { /// Zisk Rom - zisk_rom: Arc, + zisk_rom: Mutex>>, /// Shared biod instruction counter for monitoring ROM operations. bios_inst_count: Arc>, @@ -49,8 +48,8 @@ impl RomSM { /// /// # Returns /// An `Arc`-wrapped instance of `RomSM`. - pub fn new(zisk_rom: Arc, asm_rom_path: Option) -> Arc { - let (bios_inst_count, prog_inst_count) = if asm_rom_path.is_some() { + pub fn new(is_asm_emulator: bool) -> Arc { + let (bios_inst_count, prog_inst_count) = if is_asm_emulator { (vec![], vec![]) } else { ( @@ -60,7 +59,7 @@ impl RomSM { }; Arc::new(Self { - zisk_rom, + zisk_rom: Mutex::new(None), bios_inst_count: Arc::new(bios_inst_count), prog_inst_count: Arc::new(prog_inst_count), asm_runner_handler: Mutex::new(None), @@ -71,6 +70,10 @@ impl RomSM { *self.asm_runner_handler.lock().unwrap() = Some(handler); } + pub fn set_rom(&self, zisk_rom: Arc) { + *self.zisk_rom.lock().unwrap() = Some(zisk_rom); + } + /// Computes the witness for the provided plan using the given ROM. /// /// # Arguments @@ -301,7 +304,7 @@ impl ComponentBuilder for RomSM { let handle_rh = handle_rh_guard.take(); Box::new(RomInstance::new( - self.zisk_rom.clone(), + self.zisk_rom.lock().unwrap().as_ref().unwrap().clone(), ictx, self.bios_inst_count.clone(), self.prog_inst_count.clone(), diff --git a/witness-computation/Cargo.toml b/witness-computation/Cargo.toml deleted file mode 100644 index 68f40a33a..000000000 --- a/witness-computation/Cargo.toml +++ /dev/null @@ -1,50 +0,0 @@ -[package] -name = "zisk-witness" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -keywords = { workspace = true } -repository = { workspace = true } -categories = { workspace = true } - -[dependencies] -executor = { workspace = true } -asm-runner = { workspace = true } -precompiles-hints = { workspace = true } -sm-arith = { workspace = true } -sm-binary = { workspace = true } -sm-main = { workspace = true } -sm-mem = { workspace = true } -mem-common = { workspace = true } -sm-rom = { workspace = true } -sm-frequent-ops = { workspace = true } -data-bus = { workspace = true } -precomp-keccakf = { workspace = true } -precomp-sha256f = { workspace = true } -precomp-poseidon2 = { workspace = true } -precomp-big-int = { workspace = true } -precomp-arith-eq = { workspace = true } -precomp-arith-eq-384 = { workspace = true } -precomp-dma = { workspace = true } -zisk-pil = { workspace = true } -ziskemu = { workspace = true } -zisk-core = { workspace = true } -zisk-common = { workspace = true } - -proofman = { workspace = true } -proofman-common = { workspace = true } -proofman-util = { workspace = true } -proofman-macros = { workspace = true } -witness = { workspace = true } -fields = { workspace=true } -pil-std-lib = { workspace = true } -tracing = { workspace = true } - -anyhow = { workspace = true } - -rayon = { workspace = true } - -[features] -default = [] -gpu = ["packed"] -packed = [] \ No newline at end of file diff --git a/witness-computation/README.md b/witness-computation/README.md deleted file mode 100644 index 558deef8d..000000000 --- a/witness-computation/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Quickstart - -This guide will show you how to compile zisk-wc library and create all the staff needed to generate a proof. - -## Requirements - -Before starting, make sure you have [Rust](https://www.rust-lang.org/tools/install) installed on your system. - -Optional recommendations: - -- [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) extension if you are using VS Code to assist you when writing Rust code. -- [PIL2 Highlight syntax code](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) if you are using VS Code to highlight your code when writing PIL2 code. - -Install the following repositories: - -```bash -git clone https://github.com/0xPolygonHermez/pil2-compiler.git -git clone https://github.com/0xPolygonHermez/pil2-proofman-js.git -git clone https://github.com/0xPolygonHermez/pil2-components.git -git clone https://github.com/0xPolygonHermez/pil2-proofman.git -``` - -## Compile the PIL files to generate a PILOUT - -Compiling the PIL using the [PIL2 compiler repository](https://github.com/0xPolygonHermez/pil2-compiler.git) you generate a PILOUT file. Compile the PIL2 compiler by running the following commands: - -```bash -node ../pil2-compiler/src/pil.js ./zkevm/zisk-wc/pil/zisk.pil -I ../pil2-components/lib/std/pil -``` - -This command will generate a `zisk.pilout` file that contains the arithmetization, public inputs, constraints, constant values, and other proof-generation-specific details described by the PIL2 project. - -## Generate the setup files - -``` -node ../pil2-proofman-js/src/setup/main_genSetup.js -a ./zkvm/zisk-wc/pil/zisk.pilout -s ./zkevm/zisk-wc/setup/stark_structs.json -b ./zkvm/zisk-wc/setup -/build -``` - -## Compile the dynamic library - -``` -cd zisk-wc -cargo build - -``` - -## Launch the proof generation - -``` -cd ../pil2-proofman -cargo run --bin proofman-cli prove --wc-lib ../zisk/target/debug/libzisk_wc.dylib --proving-key ../zisk/zkvm/zisk-wc/setup -/build/provingKey --public-inputs ../zisk/zkvm/zisk-wc/inputs/inputs.hex -``` - - - - // fn execute(&self, pctx: &mut ProofCtx, wneeds: &WitnessNeeds) { - // Creates the ectx with the workers pool inside - // TODO let mut ectx = self.wcm.createExecutionContext(wneeds); - self.main_sm.execute(pctx, ectx); - // TODO ectx.terminate(); diff --git a/witness-computation/rom/input.bin b/witness-computation/rom/input.bin deleted file mode 100644 index 515d6f34b7fb54351e6cc35081a8b35484c76992..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 LcmWeoX8;2L0hs_e diff --git a/witness-computation/rom/zisk.elf b/witness-computation/rom/zisk.elf deleted file mode 100755 index d81ec78062f496674d433b57a9697925ae2e95cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 490192 zcmeFa3wTu3)%d;7oH>&V;Ub3+WGJE|idH4|BqSheyaciBzqQyz)V^Lif@rN;6-3+G z36lvItyPW?V7v<`_9E>esI|2b4f1MREt(Lm)wU7vRz)=7D*1kEpEHvL5?lNB{r=zc zJ^DPjXP>k8+H0@9?t7n|A60&JQphk==D(2oNCmZoJgboZ@!yNr=lDPPe}yVn7XL3$ z`MRwoZw6&wCxTzPEP2|mWy(xD86(WU@|XPgbBoC2Ry;ByLmPJz!U@HquOr@-eF_?!a&U!Xw!Q%botHBON+c$96_MC;E| zYHLE>`q5#dJ}mpFb70hLdp~~5x7DMeL8|O*rK0B9Rk3@&t*%my%FTVzx|T^?#lLF@ zsYY{4rD>L0%AFN4;>#k+o)rnjheuQqIe<%5azoJrQl1u+=Efv#;|nZhmZp>)8KjDA zdH?jBOA|`jhVe*QlZfDbHQYFzmXp zs$VVd8=thTAu3jWpYGH1o%*e99p1;giYIKVq>p!r$8GEMKHfDxYFppzt8MF!KHjNEY-@QR?<#(7TWk7wm-wk|J=4d###OfUav$&1 zL$>v1AMYww+SbQ?yh|*%t+3Ji8a3X}`t|Wn-N*X%@vh>9)rMR5O@p%kAuKtV66O^Sx%+eaewGt{mq9J0I8b-3RP??h~m7FwMnP-$LcTq}to12uo zIAYjEa}96$Cgp8etCE#fw5z1eG?hGKrf%nKX;OAkj^WnNHIk(@%GtVAIn$q2_RvVk zxvWLSYpsyz!g}S8eU(b+cFf7JBilOdp&e%Yj;3Q`6AkFfKWH8+HbU|Iyu<8pO~eh~ zo?B#vMkU8L-7lp%!7r0RX`aw@zuQlR>;jeR77SAEodsqY`qS=jZ8JwdWhO^M$0JTL zlSde4>^StuF>6%pX!OFEmlf|rybtj{#QRX}2;Lvg`@?uY>SYxxKHiFzoMOewzhT8D zUTlF^Bo_W&zgV~?&yHMe*r_mjTB-U*J-4T0#kcok4Z|{D*J6$JoEr}}M;l?c3YxaB zFrw8dLul*FT4C5j>s0cHQ^Iy(%5bLfY+8eIo9p~`Ep>joj+MHdd1Ou4U0i24Th=RY z+FE5-&jrRxBV9e$aHh8?Z`Okb?^W{1r^1;vleTWlT;%~Hx`}n%vPC&N9+Wjz<`~10 zHIwy{^|jBLr=05_G}7nHGn`9zDDV1*WKC6a%qf;VY@Xp=x>K*MyJ?>CfDvuw+14Dx z+4+#cdzCz_#&Va?_pMyr)^44noY@Z<>DD=hGh>+H?Y!UMy)t7@S?b_9yuwRB^hd;#{{V5b1^S4m!=nq4&V+_;I z+tnF6?jW;mc-~*3Jv?+@ei3V8Tb1);6ZtJNXju)&K-jiMsaVN!bib}_zq({i~mp_E3PTWe;5}lKeQ}Xa(r2=_>{8P#BY?w$}cXBm3*(1_ocjd zz`==?{L)Frkn2&EDmgaA8a1ipQI?TBy3$CFNg2=tK4>!HLn7*`;UiPwTm{R$|}pgXIq75LZ^{s)>93()&3jck1X5V@V0GL{TiB%ENiMBZ(9wc zX+N^8@WXRye+cbIl%-By$XXmt`w?Z+Mqrbx{z3Z@Weqv4z+Xjso+k&>el_ifm!*zB z+_pBKPkVkBRM}SPYqTF;Htor4fU}AA!^;|8PtZo-4=b}?!`98boc6=Y+S5NzA68cROiG*74f;Ih#szD()| zv>#lSI`?c)pnSgUSjY z#Sg4{i1vfZMn{hWrcL_+WiyZYKj8BQ?FW<o3nQ2hWC|(!QXq zd0h>(T1fkXveIieK@FDPsO9X?6n0@@dpO&bpW4VTb9ziiQ0)*&yC(mube>WXK9 zKb-dYW$g?9WLt}BXrEtZEp1_}Z__@vtaQM~z?@3^+_Dvy529}l?Q_eTmLCkA52Af; z+03$$;NM95NZHKE%&X}-+DFQoR+rHCdD=(H3P->{rO(qoQr6J=HR!yY_TjSTJI`lb zmeW35HuJa{wCSXMxGeRP>zLPZv=5g}TQQq9<7gi$OKqqHw;I}q%B*Mc1yZAE&+p&e z2Fy;{hstK2wVe6>k@l)=#V&m7nJ3d;m8HJN8jb!5?M0Tq6e~WcI9C4Q@z~U(`FA`v z_jqj77tK_uN|qX5N;Y9H#~EKTCq-0}GP!=JvS+pr6#MCHUg6u%q3CSU&)->z5AeN1 zP5PPE%%K0Yp#P+xf3@xpF30r-!?|oFv4!++2>P!G`lI*t_>2Ai6Yo2`xBlKGnf~oT z|II=F^+ErZ%=o?czarDW6dSJRZv^u3!~3LBEo}jSl7?z*rH$s0&~$_FNjQINAQ~LI2G`|MfxtmdyCQ_V?#qXgcu#k4nqb zG>D8d-9^|K&5uWzzHie&?A*cHK6QllQ8e#Vnc|-~-)ZvsqOwx=^I^;~{r=N?_Ah?w zn6Cbd{Qme&$jdRBmX)b)FxCbzR`!H3@u{P`U^I8Zn3#dl)D1>Q0AouQhC7pal>c&! z!0*!|{E+rY03_i6cu7DDC@Ech(<*?>7s-j&c&Z#X`2m8KWGF zch*e91D<15sc7{yLP|yw_c@NMk{;CX5By9+N|6;rgGgc8);CR4sA{|C_>U4s_mAY^G?6t}rUTt`7H|lojvlkogaOM`&z0*n!?^^0l zI|lw&vA1BE@{%{IWVvyOm$VHpX)^W^%3Zbvc=<;9)fVyFzv!mN8QweR8_wjZJezK~ z>6I!|_nxa#-fqU+Q-v&lV~qFQjfRsum4CBUve+p061GaebP+J`)p(^}U9H^oSsI@A z^x43>4R}{;cvAPi4D8*!f9_%B-MrNB7MyP+%WFn@3x@OiMwLE$jB=xI0qgrJeSWL5 zN0b}hcYa`aPhV@K&!1t~BOby&_;v63waWYcY{S`0`TWhy?^^yb#+>t1`p@51_MP|Z zzR}ly1%6Y&X#wM&Z>0ac(6H~k#)rdvyyqL0x91wl=i!AWL-+!kjdZL+!^^KSqVSuW zZi8Qc3fz}~>u9)6m6 zV^P+gHS{zr_$#_13(I>BzPquCHKdgJrudy|QeM9nN`Kd%lR(>#xS2+t1}o`*-zTdUkbyrZAy&+`@=?x0y3j?}%-H|fvX z-$BQgia&8IfMBfI)LBZ+s1Xl5?;Sec-@EXJqLcm>)R-W=93=CW=Ib8~@ntv@&F&MiNN&u1ClZrTXF^S>qhG(z+E9QvO(2EK;J zFaMSi{Z=dUD>t0?5*q*Lw`MTEhg7ES{2smdgL&XY`PC81TfiI_oQEE~U!|u^1I`L! zzi+ANw;F&0-w2#_bAY=DI5T}X)a}|Fe1Z=T^KQQ0aDM+ZFjI!})!!q7Hw(R@-px_y zbw2!kH2nQeAUCfv&j~(Ecscso<;?S8AExm3SJ8)i!RKm9!3|hm;(X<$eg(azAv56i zcY)aeOx9ZDcHK3|4Epp=);*}Z1HrBMZ-+UNXO;IAp6{87EPfxJpCxpQ`aE9^%u&Gn zYXI{_U>0|Sc@Z%0&BFAa&xhZyMmJD`t2gHw)@7aYBEs|V*FyMf%Q|4L1tx0_p3Wp0 z1O7_GUqRg+V50Xf9_5*=?>hKmdo|_#%A0dGyk9e1_{^yT&Xi-&)9(T2Y;@Rb$m2w< z!fY1v&9%t-9!m5YxIw#h@Eg2eJwv&JAM$aV@)$7TabRlPrYtdB zdp5ZFbvvgYYkKjqj`dUEt|cD;ude%*wr9|7xacn@f=z7xBJ+J0{RPi#{4=)b1Ro|j zs`SS1V2kK4FsXZ|vX0wX!)qzQRcxc!N-w|Kh_+k~oQDIt{Tevub>X&g8E|gQ;%1&` z9P3%X1zs!g&I8s)_@YW^cCeQ!)cPk{J({_04(8g-TuZy)O_|1Ap?5G>ZyjrR4fuUT zDR@O#2a`D`3h@Un!XLOd@CR0dBl67Jdf5MH3-)K>)%XK`JvqMSSg-CC{DOSL>1c$m z5v{xMp$>N74e<}6EirT=`hJVp@|S?;pwl)6IuZY3A^JY3dz0YRj+dZ4B|1@Hh<^6! zQbxMj=QD4!$irIr6+S~&(b1aE>^o@T2a;FNthB>*z`283< zAKN{63pyWoQ(BPyab57HOvg5&%L4e`I&i!OeD+cz|3Z5&Wf@+-*@j!hTJ;~GqEl~D zl9w_aW3+9%=*k}`XNGB*C)JF$N8W9uH(m$43l#aBvG(v~M)d3(loP*PMPEPGu$TQq zIafYsL|;7yxqDAJmwy%gaJXTo-^5QvSA6Rz!yWcEIVp74v(KWtCo{Ki8{TBwh`w~9 z@}9j^MPHhTA2Wlp1RTGuqGyj+-i8^2JtoB;e+bsy)99L9TC*=9bz_9hPl%nB_N`AScM1L$_~`bnX~sm~ z1$A%1xrVc0C_Z67=r)Y`<{QZ}13uV^j-dZ0eVKyDTyHYQrUYm0gC|yk8#<(F2040k z^0bx236BvcOx3!n(6h3c`bEs|UUZ9J_a?RBYrP1qDA(g-S;D( zrK{nc=`Au2F(c1Tu8F0m{s=fj#eN>?E?HtEPc{xuZ{*oq=#($jjCB_-l=VA2{rdIj z*o{W|f-e$FysNzTU&ZGdi@o{)e@1-evB>ua%B>x!ZEN(kBMtYSt;+l0CkFA9_U*h4 z(0<)^V4NU2lJaEmrF;1&io?{PhzJZ{~X!=m0|z5K&4+F zVc7TVRo<0v7|}N-G7jte!3T!-$M>oK3_E=LXz%&=4ew@rh$+JjcjRtxzd)rgm|)n4 zysG=jIOz+BEuPzmpF}*<8bu!VXx>Y2Ji&19{c}s_pZUNpB;6eLz z>je4$4;d1=r&}ME*k`Qsd)6R=&K+2XEu6+Wt_W;I1M67juj8Z^)^QQAX8P+$-MRI7 zgM9Om-lW;AXEU@x79+1Kcy*}os`$Oy77i0zxIv|-HX}c!zWhvL{HfEBpB29RQ1>=m z1WyzqKa?Ur5y7iPd9_;@`x0f>FW37}(M@Z>>zi6nc)4w>r&5>L(0SG} zoE_+F(NlBVz^SIt$ze|;vPnhH!QKqp;_I2IE#So5GkRt!zVl%0O;*nof)lu1Ln%0E zJ+nzU$?3>p95@eX`lP2K(}T~}a5QbFBGZHMtpZv*^9q>X8erL!k7?ZioeD+INSytd z^vq=N6FqYcrQjrbWg&w)q;7iVO7zSxy6Kr`wGQ=WQ)cwct!sfpezLUYNV{;6$mKEV zmg~uvG)u0L{0TBWzUI*A)EnTb2zIb$j6H0L!M@93>8Y&ejA0_@$7wmAP;*#%C!~Ftl|Msy)`Y%W0+rOi{f2=mN zAGn@%pNxENcnn#?PFIT0f*j-Hq^FdCyTG5I>FR}`LtP#8or+9jZ<~ENcBVa|M2_t{ zkmIY~1ny>Ej>~U9Qhu|pW4(Dh(bW$jbBBOyCwBP|+F+yactiK`HoQgs5yp87ALt>& zV-2HI3z7GB<~`7eURY|_L(q!?e==<^B(HM%Tj=je`1^IN^Dx6+0`KT`E@zBZ-Opd= zhm<=vm)PT=vCbb@XJQ0z-u>u;Y4FC1Ab-<4XE2Qm2v~>}>VJ7S8*S+u}<>p(! zxNnTJA+GZ{;?p?u!UlP^A>i!uZR>Hsd0)dxZ+uT}f$(htJ<)OzdgtCi@2mz-{6*#_xvez5uDyg@OHg-`m$F_neH)^6 z&P&2$wpWK;L+7Nc$HKShSIxI?AeSe1&2#Ei$mJv1c{&^ZXgIU+Pq$MFj@Vjf{(9w@ z=oOLgp%3^tRpTiXA>ZtiYMiQF!!AO;gSuz_0Gw8{2m2Bwd|xLx8QpL?ivDlw{?T(@ z0l)FuXVUuXoXhaJ@ar!1^_O$T8-{Z|ygZu{{pHnd!EaoTAF&?&3{Hy!oLayMKO#ey zbPG7)M`Y?=>M`;fHgpo4hH9CHPJ=}zB~KDWrWC(I^m2dZd22KAzzxJHONqtvbbb>Xew4Q1Wi`j8tC9EV+J=8o%lip6$3~}q zKah9h7%lIm#!oa>Km? zo}~}I0Q|IpXY~8p2E(~^J^S=SS?3N-)9BQ3z{iHZ6-GX{`!YCnrLwJv@}7adbqnF+ zk+k0zv_A>Heck6<$#by=(W$>scFq87_*2Jv|4pA;OIeF6S<_z-ORZ&1pJYuh&}-^7 zi63@1Yf5ay8hfhHmv8ds^3GcyhfiLooDT24D7-sZ=;PG^r}c4b!Wqo_^thsCrXj06h1b##F@8+`OiQW(RIX_X>tJeA?Q_fg}*jS z(0#LgU15rRSED1qRmcA15khCnS{>xgAxBY1|Ba)S?QW+3*~D4R=$%r-iC^W%TQ5yR z@1QS(y7TlW%K2!TayltbYlGh3#(opG*BwDDIGw)m0(eB37ueLD7a9!bN0hrLzdlyO@{(tx3%BFXpbHnG3%CBAd7=yNK^KZnJ>wke=)!x@ zg+jLhucIH%0WWm?_OmF_-E)X*OKS!Rot(MSKc?Kf!Abnd*TLx?#}GeM`;)JO(>>rG z)V;5H%G>u6cv0e;N^I)< zFYsILB46=C#-vV;&F;@!N}f7K`#I}g#Mb_o&`A6jiK_-Vxx{Xk#I$3P!w-C$J>?B$ z4?o$5iM*z#TxHn9@k<1z$nQGz!Zq;TZpwA1`ZhZsoz|}bT75^kg~a}w*}JNG0-JWe z))9KYt16A|LeHZ!#OL<+yuP5knNOgb3(*S_>(vYtJEZr%s*c9izT@ky>Q{j^p-XR7 zUkAfX9aVgz)`N0gFV1SgMtgp-SpI^Gx)#csO` zkx}7aVrcuGdpNgsA#t>EWb}<|$R95>oDbeL%(9xX>GQ8w?icC}@7*8bQy+y-jg7{q zPQU2K(asekRQkok*jGVcyfa*-&%#F^itV00RMs9@o{OHlkQ~DE>@$eWvN!bV$;k3K zlwVTvE`45^*lXms9eF!PIRif+23)Mt7amLuzl+#>H*Llf!@r_qcPDKcA<0zzJMPTrR|=e?FqE~ zvu^9nSw}8F+Wr)|#P9fe2Qm6~a@mI<_nkWTnx1kpxV-_*kr!(P?Zw}CM7aY8qF4Sl z*7<5R^GKm9X7tRp44#VLMQ%KkYZ*LS!*lvwYS;zn44ymd@jrCFr3E@Xugo%IfXvsi zR{FWzh429|(i`D}Wx@yGAmeX@50(iZ_;v48^!ax9;6_T>$B9&v_r|vOuS6$(7vBy2 zGEHFq7Jm4zF1%Xqf;WGt@$%~4@Nt^7g#7X!7-w;iU#{t|b3hs=WO(sAfj{{wu#jPF zz3;wc zH1g7bKEdB;UrTAB3w|TIu78(a`66_G*UzC>{~6giLC;U;(5t`0I{wt3Uz5=LM(F)K zI8j1Jy_YDyvJ$;=k|+nAf5i9fgo5B~>z+FyC`8&&kxF@}BDTjaa{j}iSZWEa2N z&4CXuS#CuCa=2mFW4|wbkh#r-Hj}_*9yu2AFHRO&rYzSqlUQ2tOm7r>jSTC(nhxdV zE+KbP#{7^q@>W`ZN#4pu*3ef;VgIGGX{0%*=fXuZSW_h&{lLGyx{a_ zFTV;o9vaATHGV=7a-6}f8b6^3ITqaNn5Wh)Bj9zB5;}$W(z%{;J#>n+__`%B4c#&wnOPao3puW@?!u`BIj*1OsX-+-=xPK)uUsCxy=0^LGv(EeL=%Zfm^)a3W2TkIlqi_jQd z5=FPzMeuP@&*&Cshv*j8E!E`vECtgPdw0i(EtVMCn*ncC-^BXetznT9`(pH*+3*E& z4IdyU$EG)4k1v8P{dCF9ttAr~gTSu6Sp z->lTg*SUclfioIij$Rkv?4SCJ_{!Q}q|Ew@=->X=GjC*h7(A-yjZGemECW-|V=6Xz zFtSYDt7DEbpAEkuht(gP9^)B#)Y6(fKX!*M=b&HMEAn9l`Xvi1&@VyV*??Y=y&};s zqJMLstw~J78J6fKWLf-bO|Nc#wWil%=#4B3PCx#g#?zUMENj0Sz7-#~)X3|i6Z%Db z7usvzWvTcs=LWhToR*?rf_m0>xj}pvV!K>;Bab{;|4YFMIxPw4)Ef9MnYpzFzKh^g zaEFi62Jk00;Y@xAoC<_axm`FN1x{}VIK6^DHQx89v@N>~e`*ytou_fsG12yOIa3q& z{VBmIpSA1vDDfWqw^hh;k;w9+@ENka1X+e|=1Im;vN!DxrVd`oQu1RPXY#$uBF6fb zO3U8zpts@ELyz>{yA9tOS*}Nxw-#x+wvlV+GUWQj(ZtD{iIa(=HbCDqb|~)*>|xXW z$jLbTm|S=mIhM7VdJ;ai@HFx*@lIX~G<=p=`BBdQlKZk6uu~t7jc&ruF6{Je=SJ)- zax6K6WT`P$+Gtz5(YLh{C&wnDv#N%`i^$yO`$b<6H^1!nA&=h`121RXD)w+5Bwj3{ z{f=zB?2d%D7{ke(jIQ`LvZ-4!CDBpZTM)eg9e< z>hyom5T7Pi^0(uBzws|YJlf3ph~J9en48t@%}29t#Bw_471$f$C49_iYhZ7Jy0hU1 zbV5FILkYk5Ij$JB1JEOXdjmSue;9_=p6<7#qbAI(Xv#P*H`r_DMp z?(R4GIsa*_A7ctm>m%rd3BKRBolA*o2G;O=dvZiZyS$_ zK$d3&@u-pW@5Q54K|D&?Egtpym-=yt$g;+%n=EUbx@6hucbSh`(VEulC?A{aTi0`8#(ppEDCB(<)U8n3QzV+qj8`1NRL5|-e zH;W(n?nOrWtg)P7`9S$G@)wCU-zC001ewOK9*SSBW8`V9(>&<1of!E!V%{?3rrk(i zcrrY-lNj^_bPapa7nWe3JBX1tBgeCeljn%9f28g$ zulMCn&hDYps}CmUx{L9NfiEPkKI9d>FOA=IoHs}NpyVxAaRzP+drq9alfC4H*zPUs z@b9tR_lWJjP{zj}K>z93lXG-muO{YY&r0&?!}bDy1H4Z@eei36O>U>H(L_}FW^^GPQsrKe8sFj>E`FVL-7shr|E>B?+(Q`5IFs@!KlQJPY+z1z0q{bZ03vYytGwl zGB(;e3qD!w%k~sxbi}*t#q`5IiCsP#c_yCFHhBztxbXTV;!hvJxv(vq3yb<^#-BZh zxdG#hx$x4Az^5i>KV<^XB`;GR*ime>@S0T#9fxwxev`yH>_Y+9s^ZxZ_;D?Um$L-9 z!7g_@zun%IeF-ns>{fmLm!Djs~(3(&+XDHRZFo6H)y@$>lEZ| z=HuuEWYhY!_$X$VP8q|T-U@U|D|9Z`boO=14Cu@nK|85C9j(}wn}~_=1$I&5OZ#Uh zS0KmYr^K;s5BRy@DV}lT_2io$t29SL+!A zxl6%2>)Yd_EJdeeeEW6aBr(u>cq6g_K4Ps*@>(a=92c#+3A&&!;34xw_Kd542!6yA z%qMxGC~cSjd@TD={|NS> zjzq@EafNgA-jh>_ERuWH-#G@)JT_m_#`m0UIPd$oMPFx)@41$}>lg4XMr(YXuf+^!-_wTkHIb$DoS!v9Qp$YO zQ{cUk@SecB0Ny(U-pjzc0Ny(U-V5r^9}5lVrVWPIN%^&X_$=hrZ-p-hwBS>)U%l`b zdR@~Sp9H51Sknsw8NVBx;MJh+$6e4!;$^E!&ijEY{9_@*oK@Y2gnI-`8hjf%ic$*Eh)FAls7rm?XC0D)K0@ z@8;LNQwJa$(BWE2nJ+jx5!RbnEL}B?xvmJ#OE>VHe3gddpLtkBPGzQsBXwwWw37qR zleZB&<2>6+4skPly$KsLi@0Em_;0!tpCw&=1hMNDo$rgD)1us>apax0!b4lgB}`}U z796C$1HR#z^DO*wJtZ+{vW#;P!8o28D!S@;r|t#GofLCU^LQ`B-t;)*Xz|m%v6J?k=ayirDeZJas57>8_^;un3oC5rS==X-P&V&eYXR@`lr8sva9_aI zgt|Yrx>m*39B#xOtHqDtE{fpJ5pZXpu13zO%iTfx{t-7%DarNeyCu$&{uL(`J>A*K z9YnvocJ!zZAMNNIn6BIMemu8Xm04;~d<5V1t3=4Q&Qf+^v%$C7qv8b-{f&Qey`|g~ zbX3i%ZCTSknZNw2Uw_ajw=OcTXg%{Pi_D9oBVG2f?YZrp&W2j=tcmWZ@7^m?X%t$9ZMHH&k09*wWN;BQ;1*Ew|OkG|$0bgv(! zalb4cwj%CK@U9E+z5=`#SmEUOnLd5TgB!FI9v4~)jWzv*&SC#uPdYcgoW&VD{dpu_ z>I>h@dQ$G6Tl(IuT1)wOSB#34FW-r5?TSrYzB5)F z-d&VZ&@(lkJBfDbd$P1VB#um$gm=e7$Q`)E7l!ktoo%J~rhoZveb-0%^4+l#xoanZ zuHkoac&`tulKTN_ch_GnbV}S8pA(L_@MwlkGN1V6&}tsE`pmqTSa%?eDxMOU$?J%7&qmA=BZ4lo&<7U!APn7d0$sI_`v}mlHRQD9J~8eH zDuHedVFOtT+t#+u*kj9CyIS5U%e1IBfshqSqB+adb|4mMz-=0o&~ysIB1YopG#BR)^I4UO!EpF=Kl9b4KEa>K~w z$VFQcJmCI!n1Mn z(FJ+XW6f?mwE$jxW@oJY<(;m;(0WFF+?TAHCm^e({n$J5=T*0N>$`XTN+e+a3o~Q8Sm+lf>`94eQtZZiTva~pxce2U1LtP z#WJ|RkakVdZh*A27gw5gHFqRLBDC%57t0a&#R5NUFK!6g)h*IypxyPpxw(d%asNR(=Zd@(lwTgk}As=mJ~la9DV6{FUm(qL1UYN*9k9+BmafzM9euPD4 z{$p#Yw9j(;zqOXSue$X=vX&ZF#r*&HTKe-#{_m`1#R&(lr9#Ji=C$O$CH#zH);DYz zMYCAnNF>CaX2=`vrX&2ulz5~2Y)JvMZ4yM@AR2vG3yhE&mSv3RO}*tFLH=1*6TS`gyRFS zk@$q-U+LIL$24lG#B=au*6+s8$@=`CCO>W-Ie=QmAda;yerLx@d?LPL{7v@g`Tob% zW0K#HdyD-s?-0MVFF6Q!u7}(*A7X78C;W-Z@XpU`~_kuiZS?fm|YU*uT_@lr1FQZBT^w>&hDJ5zJ;FSxJn8#%G!i*vX$ z)#5#I=qFZjSC;unvx$-%hu?=aka?lcv@Emir)Sjn0Ww;wS_!6@Yx z6!Se%BBbB_IMsj;60%e6otgFbc{%Yn?70!2y=qjlp%(nVX*iM5r+BH+zOU}D;|`e@ zIR$bhr7Hf%hb2c6HrHDQzML6PSfiZ0$0erO6CbhY5WB@XHGat-2HQi&ktnWv*{0zMS<1$yu~rRJW^bfYo6>A^D1a#4^MSTiRG)G4o){%4zc)FJ1-a3TyowG@uH}2SNOOgq7g6zT>qD;S zi)@@#u^~Pj{uMtKeB!fKg_4gW?C`&1m6E4pz87hK?<|`+I`fYrCwe%%j{IQXVz)>x z;|{-_mXE}r_LqC+q;sYj`KR28%o9J>M38B6O@|`mI>(e-w89u=R;I>-QvvcTu^YC6 zSZtcaZk6piwnM)8eFrhxPB%>KR({7$a!2HmYWGMSr|~LYPW`%1Tw=dqy=u`D*&JK^ zipBqae$AJ!yED2&(^5Z6+{Ievu$DQjWrTT0SWAof=E_?2GM~4)&1Zqk$F^iX#G<{- z$B#!VZv1|W#e%{9S8~xZ@ zNvz+azh0M8D^kz;;PXV7=l(uQ&-SwfkI0-p{ln9=pR|Q>de~0iZf5+U#C<|r#>(18 z+J}mC-=`npcMluYo%b_wx!8#KO)Vi)^E7&U#464EA_IX9yIc6)=l9V@OtE((_kwF4 zZ|pB|YV2rY+QHpo)%nHuthZ4tkk3n^x zZZYV7Fm%66460#N5KD@@hLP8Xiufg?Eq^Wz_yeb7tF!VuL~NVGKBCS)TH4R-)eqKs zC8O(f&MR?~jDarP{b@88S|`U@>*oluIr~IE1bXoGK&G;5&sf?oi{I8PdF&o^ z3habqf z1KaemZ<`p0{1JAHyy7(d`($62vo?V)#|~ke*04vioE&5AP8;48og7o-fqJnS*#E5E z*q;}=>p9}lQ1VRpVhubszRr;GUCWvvK9$gFtHP#BULIbMJ;!(*J_q>Xs|IbW%JG}5 zOxv~*)?V<*@760mk5%vqn9a#CM?6rt}Ppnbqcni7Zc|l$Tn0sCR(5)XQHjXh%!+VpZ z_&xby;IAUa34anRew?}0ei9qQvyxRIp6!X1KTiIfy^3=Fm0;J%smBt_Wo;RU{Q6Mt zATGvtEWvjy!FL>QhjN}H^$9Vue3 zClZ$Z@3D_sgW85|+V{w#O~cxj7!|Dr3xfPe89%fRoiz;2zR&B9+{s9^U5?F9p8ETT%~x zJJ7%Y4}-p$+)|_uq$IdVJA-S4jaDLdz@O1pON(gfAku;j*vMCTO~G#^A0k; z-rFq3p9(M1auCEmnkKAGHs?_!bFwN6eLR{5`*<`B_wl$cn4_B58y-cA;FECb2K)>5 z|75>xkb0z)y@uz|4Z(c7k0t!!^FqZY>62;S_Nuj4{?-&@zz+xMzb(T$hke|vgVE#g z2(lqOQXJl=`2{^8yd!PUFGV8P0l%yTP7nFe^q1U4@ON0^cf+@(&`k6jz5uxPuv@{n zLtG1gZ@+g!^8=psqML}ZBySR_UrNl7I&-?qz3wsx9Xos#8r6yKz(EZx&-=b80s%wFe=`YHFr}(nD-zvHs|r_(5{D$BxVym(M?Y_@2wY}tP_4Fhop60 z#oe^Y>M+q?a(3r|fiFW3_#)Ay;&)@;OW=3(JY#4bL?5kxg7w^|X;SixuWthW5q|RN zWBGK-Ns*`ec#j(qy_D5mqy7Wqcl?v_+ch3JsU5wHKWzW;MF$9-(9<8r2e0LC_@q>t zvrPD;;n?8~2hbonsi})z5$JVi1bYU(4sSZ3txvDiHi2)~S?%b8kgr!8OZ|71Z-4q+ zD&NxWa`?MX2hg&yA9Kn28rUOj>Y;|5F=Ji--#Hs~ujIKCOX077em3d>i8-EL49-35 zhn-6fm{>*Pf>;Urx|&v%J6&y)YKaxd0aR#veLa19>Lc;3h~32B7hlYewf2j3P7^;n znWAs6Ia_FzFFat+9LJtGaZ@dJ?75X8_VEkE_eqx4=^Qk&a6rBbnE6H8k5B$c;_3L1 z(f!>j_~D2(oF^dnmPfA5*9RZON=kMWAqV)}AN%|F&w$VJokdmhEXCg{$>fWyq3CDq zda?N7$Yt$LbUgk+E$2U;(LPPA_;6xm&KGI@qW%)-jbN+qr#0&pt|MjPB*53H>vUygV{lxv- ziG8SlQJ9limjkY^-u-`cW%+PpE^E@2C8Vzm0dY8sc>9i_xN$kF8A zN8%$Rxn`5>GbHljw_0P|9K{*obGcKqTF=v7awdN(lkgiuD?h$4GRB*~t3viNuG)6tj`+xxhuF>RUA*`6 zA$DP@s-F&h>mv6^9tS-EZ_0W{D0N(^%%*l^cF`AXYfER_f0aFhkC}(Gr(7-dkLV|R3Y4sw>|t1qQm4F4 z-hW6b{V5Za>|uysBzqXvHhKSn=p5>lS4;hUo=bbmLDK#o^q27{E2RD&cuRXJrOrHF zlse@_(*GUcOMl9HrOw~?O2~W4ze;=J3?=X=FOvFJ#+5o{vDCLvm;RKEQr`@GsZ(Ag z^LrDxQh(DGz4|}glSr0^JLJ5WmW4>uIy;43BHt0%p7;IdmCxJc^IYW4f1Y^Hmz}1k z(Q~Ym=(*(`iS`006n{5;nOUM9M_f$=?sM3kuk?TEn+Jzo9L9 zV!|!TJb~}{+3#w*rs@;%n{+?Tw~a4JKkN_R8{`*=Ztzk!@D2D(a4O_mlmfm9;XA?n z=PJYpwEOq(bZ;rZFY3Qf_%;6Cs$91aACNN~ZMUr2rs?xmWq*@1;a;lp6eltX-wr+% zpAQi6f)*r)K^v}m=a=XS*bNw+=_1cE| z_{#pwXRx77OMn;H&;&7fxaapHqGK%Uo_}sbWuBS%F7dnPnb+=d@gwbg{t*KO`+Q5xQk{3(%l9CB zM^u~1W8Z3XzDD++0{_X0t>!zoH6_T{>GGXkHdoGmfk`eb1g}aQsPk4y_9Z0O7VPQ0 z!1Dw4KqSwIo&!(5)hNDL=Nw(mp6K&Q*e`9bp^?aoE3wXtItGIGwspn_G(_Z#9Dh$# z+2@a@?3a0eo7{cwQt#O>ajUGa*qN@mC+0Et-AQteayHnUP`gKX-A!?ZO7_Dm#J)Ag zVyl_+n$v^xEREbN!Cud5{q4;G`9Nr3avmqdc^sjI&J99~J(`YM=GE_b-ow|)oC6H( z-)gPXv9pp7D@u`rttjl5&08ik4Q`0%`Z;Tz2P1dLdKhMjVMxxJ^$>r`-#b9(wcSZR zP;7U0JrWQ0g@b&9WY4cP^x7#s|C{x1ANMrzNoPEI%Z2D1&i-v_*S$?qO!8uWLRur z61bn5Q}g?twSQmX4{}*sx{njkvR56N`8Eojkkfq+TjIq)$IG5l*j_+B_OGWOoP3l04;fLOWFC!=C`g%jlYvU!z7T;s2 zT!Cc@tVllVDE%L1|5MIZ-Yb909^W7%Ms??Pudb$;uS6$Ue*+O*+>Go>^a4^3=r3#2Fu>gVs>W zo-yY`tdvM!7h9>`g~&*wv{N&50FpNgN( zcU-_Del3x)d{`AH154{EVEFopwL9S1e)8eNsVt^lHfHo`^z?DS=^;yg+tjhWv^~B% z-d!;!dlB1&Pby9n9ht!;v-ekI>skyrNo4mI%^4SYsJzA40Klzq1_ zXFT#{4@vw%SMFGdPEyXz)38A)eV)LZFXxjxI(3{mjBj!HhSw|=TgExaD@yaEuH#Gc z_B;#xzifP&l=sARd?OKN?8jAn!6l~jH-BA=ZS>>SwreUsv6-{Bg__Qd|Il!WFEvi^ zjh@@5e1C_}D1KX5^O3+3pVybQx3MMd`^&i4gdVb;VD9_rf#9srUjn}E(gVG;{U5Yx z=ZsE%thgp0eZn^ye49~nd>(7lPk6|zT!hVMeI(bdDndcbvRrIMykiA>yK6p%{zhOU zc04d}`NyJHM0S#E!uYc()|&l2WJ}Je70tpY9=HDV_=qOHCpJdmJDn~zQEY4cmS(=! z=+>@m(rHE2(1o+kZlsa#Y&VCbk6BuIYW&U>^r;0e#a%EF<)oGizZ0{wPgwqPM_~V6 zlbkZ&jC0nYc8C8=ZP9h+BxgrtPA>U=ZF?e>>*b7@KW1GMehl{cHgZVMa8`Q_c08Q< zhFR}L0#o+ZvFRT}Ln(>Ne7}WnS*Vv5Drekk<=oIj?s(1a_yEp<)ma8`_6pBS93J2l zlGx2n%@6QsdXhG~f-@@vVhQ*Oy7;gwIwjWg&zVbpYx!RGiTKWcTjwJUO`-Ub6*AYo z`YfonjfoQ_cM;4_!*w|`@54^y5<~6q&(H+l@yohqzd46yK8#b7oJFJU9zKp-8+xVsMdA?a<{Zn{L`{ao!@*J5P=&~ObAJQ;DWP-UDx-*+P?U}^X?B&4^15X&bQrVL_Iv-il zbkPaT$O~tI@P+byI#@p!x``grG}GTA=sg&{$HDwLzbdlG{J9^7@p2_z%it6251-j5 zZT@ZFDKR%o8{v7MUUj|k5Waja@p=xrH;3RIm2PNn+ z?#$3@zGgWwQdsL?v!a=231Y7V-wujD-FPK@LcY9~bI@V#&LBrIu9p6sYp4>rs{FC! z)mfA5cQDXKfBym;_%&hp_%_}NjfEFG?V%lJd|6XI?Rw;rpkYL4A@mad;=Fu_^PkY` znSfp|L$5dYlBYM4Ygw;HS+52Qp7qy|b%I`NcKdiWC1gE%(o8)mJPXaZ_anTAc`&}L z1@KE`o%#J3G+XmBbnBTH0@p}=rJnCiqN`Xh&L6POi?m9Op`wE%$Hp`e3 z=W|$3AOFl+m%%R$(6%8(p08^T#B7;!Y#wyg-}wVy`xBZ+=&$!$*st{Cnx{|XeKwYp zm?)Erl5@v$*ORuD6@_hLBfHjCeFJ-rKQ`L%=a#xARw(vXjIbRK#v zu(3{3&h>LAgLy*jF7gT^sJD~*_k9j@w&+9T2^s(9u9zy>^ME>?oFe_T4Q%`_cJvGI zme~3Z|7=p~Sac^gBP{ogbObgi2F^Q+;2X~NTr4mHo09q7OZyIPcocKH109jUFELEc zfMnv-_$7-un_}e$_BV|`YHEEfwgvm^aK=ddVs9q=>v~;wU8TRa$9|vRn%wDQTkV~(;-3aQ*7#9; zf$w*T{SEj(?BmvS)`7Tn*-jh3up54R0{j9#Y`h71$m&4Re}SJ@89PjT$kRoyXYR4N znDyEh(Bo^2$$R)Fdyf|13UoWujvu;@ad!v4???W)iEVzp;(+sj;;(j>*T5g^A>ZO} zb#D{sp6gxlpWhPs68#v-lUgRTJSD@dh$#;%A3_-B;yGY{Iv?nfgMB^CXt@;Z&%$ zk=Aa1UxdGvC%%wx1A5KZX!_8%?T$P-hqRYD^{RhGZ*jj%NOV~Ik5(uiihwJ=lkkzx z|GT^JKb)Mae9w^xYrD*Ee;x@_O6kX({J5v0s%}3x9}E8S%>N!ASU&!}>+)XqnZ!SG z>YCUuU7Ph~?1RZ4&pIMUY({+N=p46lSEt)7XT4b)+20pG1s>J-l8;3Hac-GCBUvA9 zC-}x5A4Pw2Ow6Fyi2Wz{sK>q&^waxIdfgeT?Y*X*c3whd#%mic=e_vuk9GFpsv}L{ zB}=iDdK`5GFtWa0X75bq)a92kmj(FIGN%sa@^8kQE1## zt|cF!&wUWT`ttY(SMGWgo$w|x2lI9#9mAor@0 z!{I(sowwoJ$jH4FUVAq0os})tGvw>QCf^^cs|?qJyFIru-?df)j~unuGwNV)?ctLJ z{qQ*ge(mjB4xxjUVxJDa(r_x4?muU_Z!gf(y93_)fHv8?(!eoKe-FmKmYV}^k+9wy z(fNhCS(cntwF}AZS2dXr3~bq_&uj8+leYP@+Ev@WwR<^xX>v}F@7YzZlWMO(zxwlz zG(|+l#rE1(ZpIdkW3BYPY2cynOVc!|OUa&DuJ3;*W_|`7&HovWDfT1w$1!nC?>NeN z^$d;`-`Stmd^fB8W3e$dITAVV*Ml!jo!6IqS(g6DzP_uD+!gnUoWflL-yjEbu|@8{ zit+7-tdHI^z21?oh|U} z#m^wtS-#txbTfa4=H^cEI|L807x85&!x>VoGx;XH3r-z$pM3>xIQT+YL5 ze1};0Wu9w9;=!6J`f zkw*w~4~Kk1sbz}tQ}<%wG0s4IkK92G`R&`uG2chr_e;rb=eV_eOV0g0_5$)%+(#?< zE55%9C*M8OboWM8S-*q$p1VNV{Z@y(TkhnpnXf2vlVR`-#jkrfEdHsSjSh6%60cy9 z-cuzn$-Q=sk>pQ-bITC=zZjCfvTx~fPD}Pn5j4p3<`b*Z27l+D?U_Pz6Pkyhd5F7D4D&?JL7b?+6U>qQE^|em?0FJHDKq(p z`KI7v{$_qC_RuQwocKMrQ0lRu;{kXz7BR1glYiGKcK&c=c6TS|#Ri+>3PeW|PxaH^ zF}8)Reejm0^J(Nh?dB26y@h|TjnFcq&%G7EJHgn=Rm5Wb(E0c=N1t;_VDo%hx7}v#PL8{!2dzbJyS?A(9>@?gd{kI`Az54p- zWswUr%{NMAxd##7BOAo}vX>N}lVc{==7r?$M9GWGJpB6=L5c0iXUD&Ln|0HzgGP&ox z!n9{~473?jzBi3;TNH94k1I0*jyc=*#J~KkwgK_sVh0jN+p+LK+kG6p>u7uySIk?_pfuNn7<@(fi<|OnCem?PQsD3m)a>{b-NJTHc*lEA7FO~OA zm)ViCdVBvI>)ZoZ`F_dQQ}Ac6{+;>k{(Z=;>Rh?-To2rl_5Is^YF<+NkLFYK`IqvN z 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 { - asm_mt_path: Option, - asm_rh_path: Option, - executor: Option>>, - chunk_size: u64, - base_port: Option, - unlock_mapped_memory: bool, - shared_tables: bool, - verbose_mode: proofman_common::VerboseMode, - with_hints: bool, -} - -pub fn get_packed_info() -> HashMap<(usize, usize), PackedInfo> { - let mut _packed_info = HashMap::new(); - #[cfg(feature = "packed")] - { - for packed_info in PACKED_INFO.iter() { - _packed_info.insert( - (packed_info.0, packed_info.1), - PackedInfo::new( - packed_info.2.is_packed, - packed_info.2.num_packed_words, - packed_info.2.unpack_info.to_vec(), - ), - ); - } - } - _packed_info -} - -impl WitnessLib { - pub fn new( - verbose_mode: proofman_common::VerboseMode, - asm_mt_path: Option, - asm_rh_path: Option, - base_port: Option, - unlock_mapped_memory: bool, - shared_tables: bool, - with_hints: bool, - ) -> Self { - Self { - asm_mt_path, - asm_rh_path, - executor: None, - chunk_size: CHUNK_SIZE, - base_port, - unlock_mapped_memory, - shared_tables, - verbose_mode, - with_hints, - } - } - - /// Registers the witness components and initializes the execution pipeline. - /// - /// # Arguments - /// * `wcm` - An `Arc`-wrapped `WitnessManager` instance that orchestrates witness generation. - /// - /// This method performs the following steps: - /// 1. Converts a RISC-V program to the ZisK ROM format using `Riscv2zisk`. - /// 2. Initializes core and secondary state machines for witness generation. - /// 3. Registers the state machines with the `ZiskExecutor`. - /// 4. Registers the `ZiskExecutor` as a component in the `WitnessManager`. - /// - /// # Panics - /// Panics if the `Riscv2zisk` conversion fails or if required paths cannot be resolved. - pub fn register_witness(&mut self, elf: &[u8], wcm: &WitnessManager) -> ProofmanResult<()> { - assert_eq!(self.asm_mt_path.is_some(), self.asm_rh_path.is_some()); - - let rank_info = wcm.get_rank_info(); - - // Step 1: Create an instance of the RISCV -> ZisK program converter - let rv2zk = Riscv2zisk::new(elf); - - // Step 2: Convert program to ROM - let zisk_rom = rv2zk.run().unwrap_or_else(|e| panic!("Application error: {e}")); - let zisk_rom = Arc::new(zisk_rom); - - // Step 3: Initialize the secondary state machines - let std = Std::new(wcm.get_pctx(), wcm.get_sctx(), self.shared_tables)?; - register_std(wcm, &std); - - let rom_sm = RomSM::new(zisk_rom.clone(), self.asm_rh_path.clone()); - let binary_sm = BinarySM::new(std.clone()); - let arith_sm = ArithSM::new(std.clone()); - let mem_sm = Mem::new(std.clone()); - // Step 4: Initialize the precompiles state machines - let keccakf_sm = KeccakfManager::new(std.clone()); - let sha256f_sm = Sha256fManager::new(std.clone()); - let poseidon2_sm = Poseidon2Manager::new(); - let arith_eq_sm = ArithEqManager::new(std.clone()); - let arith_eq_384_sm = ArithEq384Manager::new(std.clone()); - let add256_sm = Add256Manager::new(std.clone()); - let dma_sm = DmaManager::new(std.clone()); - - let mem_instances = vec![ - (ZISK_AIRGROUP_ID, MEM_AIR_IDS[0]), - (ZISK_AIRGROUP_ID, ROM_DATA_AIR_IDS[0]), - (ZISK_AIRGROUP_ID, INPUT_DATA_AIR_IDS[0]), - (ZISK_AIRGROUP_ID, MEM_ALIGN_AIR_IDS[0]), - (ZISK_AIRGROUP_ID, MEM_ALIGN_BYTE_AIR_IDS[0]), - (ZISK_AIRGROUP_ID, MEM_ALIGN_WRITE_BYTE_AIR_IDS[0]), - (ZISK_AIRGROUP_ID, MEM_ALIGN_READ_BYTE_AIR_IDS[0]), - ]; - - let binary_instances = vec![ - (ZISK_AIRGROUP_ID, BINARY_AIR_IDS[0]), - (ZISK_AIRGROUP_ID, BINARY_ADD_AIR_IDS[0]), - (ZISK_AIRGROUP_ID, BINARY_EXTENSION_AIR_IDS[0]), - ]; - - let dma_instances = vec![ - (ZISK_AIRGROUP_ID, DMA_AIR_IDS[0]), - (ZISK_AIRGROUP_ID, DMA_PRE_POST_AIR_IDS[0]), - (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_AIR_IDS[0]), - (ZISK_AIRGROUP_ID, DMA_UNALIGNED_AIR_IDS[0]), - ]; - - let sm_bundle = StaticSMBundle::new( - self.asm_mt_path.is_some(), - vec![ - (vec![(ZISK_AIRGROUP_ID, ROM_AIR_IDS[0])], StateMachines::RomSM(rom_sm.clone())), - (mem_instances, StateMachines::MemSM(mem_sm.clone())), - (binary_instances, StateMachines::BinarySM(binary_sm.clone())), - ( - vec![(ZISK_AIRGROUP_ID, ARITH_AIR_IDS[0])], - StateMachines::ArithSM(arith_sm.clone()), - ), - // The precompiles state machines - ( - vec![(ZISK_AIRGROUP_ID, KECCAKF_AIR_IDS[0])], - StateMachines::KeccakfManager(keccakf_sm.clone()), - ), - ( - vec![(ZISK_AIRGROUP_ID, SHA_256_F_AIR_IDS[0])], - StateMachines::Sha256fManager(sha256f_sm.clone()), - ), - ( - vec![(ZISK_AIRGROUP_ID, POSEIDON_2_AIR_IDS[0])], - StateMachines::Poseidon2Manager(poseidon2_sm.clone()), - ), - ( - vec![(ZISK_AIRGROUP_ID, ARITH_EQ_AIR_IDS[0])], - StateMachines::ArithEqManager(arith_eq_sm.clone()), - ), - ( - vec![(ZISK_AIRGROUP_ID, ARITH_EQ_384_AIR_IDS[0])], - StateMachines::ArithEq384Manager(arith_eq_384_sm.clone()), - ), - ( - vec![(ZISK_AIRGROUP_ID, ADD_256_AIR_IDS[0])], - StateMachines::Add256Manager(add256_sm.clone()), - ), - (dma_instances, StateMachines::DmaManager(dma_sm.clone())), - ], - ); - - let is_asm_emulator = self.asm_mt_path.is_some(); - let emulator = if is_asm_emulator { - debug!("Using ASM emulator"); - EmulatorKind::Asm(EmulatorAsm::new( - zisk_rom.clone(), - rank_info.world_rank, - rank_info.local_rank, - self.base_port, - self.unlock_mapped_memory, - self.chunk_size, - Some(rom_sm.clone()), - )) - } else { - debug!("Using Rust emulator"); - EmulatorKind::Rust(EmulatorRust::new(zisk_rom.clone(), self.chunk_size)) - }; - - // Create hints pipeline with null hints stream initially. - // Debug flag: true = HintsShmem (shared memory), false = HintsFile (file output) - let hints_stream = if self.with_hints { - const USE_SHARED_MEMORY_HINTS: bool = true; - - let hints_processor = if USE_SHARED_MEMORY_HINTS { - let hints_shmem = HintsShmem::new( - self.base_port, - rank_info.local_rank, - self.unlock_mapped_memory, - ) - .expect("zisk_lib: Failed to create HintsShmem"); - - HintsProcessor::builder(hints_shmem) - .enable_stats(self.verbose_mode != proofman_common::VerboseMode::Info) - .build() - .expect("zisk_lib: Failed to create PrecompileHintsProcessor") - } else { - let hints_file = - HintsFile::new(format!("hints_results_{}.bin", rank_info.local_rank)) - .expect("zisk_lib: Failed to create HintsFile"); - - HintsProcessor::builder(hints_file) - .enable_stats(self.verbose_mode != proofman_common::VerboseMode::Info) - .build() - .expect("zisk_lib: Failed to create PrecompileHintsProcessor") - }; - - Some(ZiskStream::new(hints_processor)) - } else { - None - }; - - let executor = Arc::new(ZiskExecutor::new( - zisk_rom, - std, - sm_bundle, - self.chunk_size, - emulator, - hints_stream, - )); - - // Step 7: Register the executor as a component in the Witness Manager - wcm.register_component(executor.clone()); - - self.executor = Some(executor); - - wcm.set_witness_initialized(); - - Ok(()) - } - - pub fn set_stdin(&self, stdin: ZiskStdin) { - if let Some(executor) = &self.executor { - executor.set_stdin(stdin); - } - } - - pub fn set_hints_stream(&self, hints_stream: zisk_common::io::StreamSource) -> Result<()> { - if !self.with_hints { - return Err(anyhow::anyhow!( - "Hints stream cannot be set when WitnessLib is initialized without hints" - )); - } - if let Some(executor) = &self.executor { - executor.set_hints_stream_src(hints_stream) - } else { - Err(anyhow::anyhow!("Executor not initialized")) - } - } - - /// Returns the execution result of the witness computation. - /// - /// # Returns - /// * `u16` - The execution result code. - pub fn execution_result(&self) -> Option<(ZiskExecutionResult, ExecutorStatsHandle)> { - self.executor.as_ref().map(|executor| executor.get_execution_result()) - } -} From b4b6cccab89a7eb3b99edc6845a3fac449554f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Thu, 19 Feb 2026 10:40:08 +0100 Subject: [PATCH 564/782] Feature/no check setup (#798) * Removing need of check-setup and rom-setup * Mac fix * Cargo update * Minor change * Renaming variable * Adding traces * Fix build assembly from binary * Pointing to pre-develop-0.16.0 --- .gitignore | 3 +- Cargo.lock | 22 ++-- book/getting_started/installation.md | 5 - book/getting_started/writing_programs.md | 5 - cli/src/commands/build.rs | 8 -- cli/src/commands/execute.rs | 5 + cli/src/commands/prove.rs | 6 +- cli/src/commands/rom_setup.rs | 6 +- cli/src/commands/stats.rs | 6 +- cli/src/commands/verify_constraints.rs | 4 + examples/Cargo.lock | 22 ++-- rom-setup/src/asm_setup.rs | 131 ++++++++++++++++------- rom-setup/src/utils.rs | 6 -- sdk/src/builder.rs | 12 +++ sdk/src/prover/asm.rs | 34 +++++- zisk-contracts/PlonkVerifier.sol | 4 +- zisk-contracts/ZiskVerifier.sol | 2 +- ziskbuild/src/build.rs | 10 +- ziskbuild/src/lib.rs | 3 - 19 files changed, 183 insertions(+), 111 deletions(-) diff --git a/.gitignore b/.gitignore index fa85094b1..eac4bda67 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ state-machines/arith/src/arith_frops_fixed.bin state-machines/binary/src/binary_basic_frops_fixed.bin state-machines/binary/src/binary_extension_frops_fixed.bin reth-inputs/ -/hints \ No newline at end of file +/hints +/benchmark \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1e7c8fa2a..fceba6e1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "fields", "num-bigint", @@ -1468,7 +1468,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "cfg-if", "num-bigint", @@ -2772,7 +2772,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "colored", "fields", @@ -3134,7 +3134,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "bincode", "blake3", @@ -3170,7 +3170,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "bincode", "borsh", @@ -3202,7 +3202,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "fields", "itoa", @@ -3215,7 +3215,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "proc-macro2", "quote", @@ -3225,7 +3225,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "crossbeam-channel", "tracing", @@ -3234,7 +3234,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "bincode", "bytemuck", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "bytemuck", "fields", @@ -5721,7 +5721,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "colored", "fields", diff --git a/book/getting_started/installation.md b/book/getting_started/installation.md index 96ad408f4..e88089c6f 100644 --- a/book/getting_started/installation.md +++ b/book/getting_started/installation.md @@ -225,11 +225,6 @@ Please note that the process can be long, taking approximately 45-60 minutes dep cp -R build/provingKey $HOME/.zisk ``` -8. Generate constant tree files: - ```bash - cargo-zisk check-setup -a - ``` - ## Uninstall Zisk 1. Uninstall ZisK toolchain: diff --git a/book/getting_started/writing_programs.md b/book/getting_started/writing_programs.md index 46bd5bec4..0d53bcbd6 100644 --- a/book/getting_started/writing_programs.md +++ b/book/getting_started/writing_programs.md @@ -298,11 +298,6 @@ Follow these steps to enable GPU support: cargo build --release --features gpu ``` -4. Regenerate constant tree files: - ```bash - cargo-zisk check-setup -a - ``` - You can combine GPU-based execution with concurrent proof generation using multiple processes, as described in the **Concurrent Proof Generation** section. > **Note:** GPU memory is typically more limited than CPU memory. When combining GPU execution with concurrent proof generation, ensure that each process has sufficient memory available on the GPU to avoid out-of-memory errors. diff --git a/cli/src/commands/build.rs b/cli/src/commands/build.rs index c3d49b138..87ae0f9bb 100644 --- a/cli/src/commands/build.rs +++ b/cli/src/commands/build.rs @@ -18,9 +18,6 @@ pub struct ZiskBuild { #[clap(long)] no_default_features: bool, - #[clap(short = 'z', long)] - zisk_path: Option, - #[clap(long)] hints: bool, } @@ -46,11 +43,6 @@ impl ZiskBuild { command.args(["--target", ZISK_TARGET]); - // Pass zisk_path to build scripts via environment variable - if let Some(zisk_path) = &self.zisk_path { - command.env("ZISK_PATH", zisk_path); - } - // Set up the command to inherit the parent's stdout and stderr command.stdout(Stdio::inherit()); command.stderr(Stdio::inherit()); diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 5cd0150b7..5734ab5a9 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -43,6 +43,10 @@ pub struct ZiskExecute { #[clap(short = 'H', long)] pub hints: Option, + /// Force ROM setup + #[clap(short = 'n', long, default_value_t = false)] + pub no_auto_setup: bool, + /// Setup folder path #[clap(short = 'k', long)] pub proving_key: Option, @@ -145,6 +149,7 @@ impl ZiskExecute { .verbose(self.verbose) .shared_tables(self.shared_tables) .asm_path_opt(self.asm.clone()) + .no_auto_setup(self.no_auto_setup) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) .print_command_info() diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 4fe154a3d..af5617ecd 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -91,7 +91,7 @@ pub struct ZiskProve { #[clap(short = 't', long)] pub max_streams: Option, - #[clap(short = 'n', long)] + #[clap(short = 'h', long)] pub number_threads_witness: Option, #[clap(short = 'x', long)] @@ -109,6 +109,9 @@ pub struct ZiskProve { #[clap(short = 'r', long, default_value_t = false)] pub rma: bool, + #[clap(short = 'n', long, default_value_t = false)] + pub no_auto_setup: bool, + #[clap(long, default_value_t = false)] pub snark: bool, } @@ -267,6 +270,7 @@ impl ZiskProve { .shared_tables(self.shared_tables) .asm_path_opt(self.asm.clone()) .base_port_opt(self.port) + .no_auto_setup(self.no_auto_setup) .unlock_mapped_memory(self.unlock_mapped_memory) .gpu(gpu_params) .print_command_info() diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index 9418b1299..69a814fce 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -24,10 +24,6 @@ pub struct ZiskRomSetup { #[clap(short = 'k', long)] pub proving_key: Option, - /// Setup folder path - #[clap(short = 'z', long)] - pub zisk_path: Option, - /// Output dir path #[clap(short = 'o', long)] pub output_dir: Option, @@ -78,7 +74,7 @@ impl ZiskRomSetup { let elf = ElfBinaryFromFile::new(&self.elf, self.hints)?; rom_merkle_setup::(&pctx, &elf, &self.output_dir)?; - gen_assembly(&self.elf, &self.zisk_path, &self.output_dir, self.hints, self.verbose > 0)?; + gen_assembly(&self.elf, &self.output_dir, self.hints, self.verbose > 0)?; println!(); tracing::info!("{}", "ROM setup successfully completed".bright_green().bold()); diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index 4f84d4d77..f59cde91e 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -70,7 +70,7 @@ pub struct ZiskStats { #[arg(short = 'v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] pub verbose: u8, // Using u8 to hold the number of `-v` - #[clap(short = 'n', long)] + #[clap(short = 'h', long)] pub number_threads_witness: Option, #[clap(short = 'x', long)] @@ -88,6 +88,9 @@ pub struct ZiskStats { #[clap(short = 'j', long, default_value_t = false)] pub shared_tables: bool, + + #[clap(short = 'n', long, default_value_t = false)] + pub no_auto_setup: bool, } impl ZiskStats { @@ -184,6 +187,7 @@ impl ZiskStats { .verbose(self.verbose) .shared_tables(self.shared_tables) .asm_path_opt(self.asm.clone()) + .no_auto_setup(self.no_auto_setup) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) .print_command_info() diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index c201eefc0..8148b20f0 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -63,6 +63,9 @@ pub struct ZiskVerifyConstraints { #[clap(short = 'u', long, conflicts_with = "emulator")] pub unlock_mapped_memory: bool, + #[clap(short = 'n', long, default_value_t = false)] + pub no_auto_setup: bool, + /// Verbosity (-v, -vv) #[arg(short = 'v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] pub verbose: u8, // Using u8 to hold the number of `-v` @@ -159,6 +162,7 @@ impl ZiskVerifyConstraints { .verbose(self.verbose) .shared_tables(self.shared_tables) .asm_path_opt(self.asm.clone()) + .no_auto_setup(self.no_auto_setup) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) .print_command_info() diff --git a/examples/Cargo.lock b/examples/Cargo.lock index df06244f5..67224742a 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -813,7 +813,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "fields", "num-bigint", @@ -1122,7 +1122,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "cfg-if", "num-bigint", @@ -1986,7 +1986,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "colored", "fields", @@ -2287,7 +2287,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "bincode", "blake3", @@ -2323,7 +2323,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "bincode", "borsh", @@ -2355,7 +2355,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "fields", "itoa", @@ -2368,7 +2368,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "proc-macro2", "quote", @@ -2378,7 +2378,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "crossbeam-channel", "tracing", @@ -2387,7 +2387,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "bincode", "bytemuck", @@ -2399,7 +2399,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "bytemuck", "fields", @@ -4405,7 +4405,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#b04b2389a345718eab7431c8008c001e3dcd5fad" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" dependencies = [ "colored", "fields", diff --git a/rom-setup/src/asm_setup.rs b/rom-setup/src/asm_setup.rs index 6ad504296..993e9f13a 100644 --- a/rom-setup/src/asm_setup.rs +++ b/rom-setup/src/asm_setup.rs @@ -1,9 +1,9 @@ +use anyhow::Context; +use anyhow::Result; use std::{ path::{Path, PathBuf}, process::{Command, Stdio}, }; - -use anyhow::Result; use zisk_core::{is_elf_file, AsmGenerationMethod, Riscv2zisk}; use crate::get_elf_data_hash_from_path; @@ -12,11 +12,19 @@ use crate::get_elf_data_hash_from_path; pub fn assembly_files_exist(elf: &Path, output_path: &Path, hints: bool) -> Result { let elf_hash = get_elf_data_hash_from_path(elf)?; - let stem = elf.file_stem().unwrap().to_str().unwrap(); + let stem = elf + .file_stem() + .context("Failed to extract file stem from ELF path")? + .to_str() + .context("Failed to convert ELF file stem to string")?; let stem = if hints { format!("{stem}-hints") } else { stem.to_string() }; let new_filename = format!("{stem}-{elf_hash}.tmp"); let base_path = output_path.join(new_filename); - let file_stem = base_path.file_stem().unwrap().to_str().unwrap(); + let file_stem = base_path + .file_stem() + .context("Failed to extract file stem from base path")? + .to_str() + .context("Failed to convert file stem to string")?; let bin_mt_file = format!("{file_stem}-mt.bin"); let bin_mt_file = base_path.with_file_name(bin_mt_file); @@ -32,7 +40,6 @@ pub fn assembly_files_exist(elf: &Path, output_path: &Path, hints: bool) -> Resu pub fn gen_assembly( _elf: &Path, - _zisk_path: &Option, _output_dir: &Option, _hints: bool, _verbose: bool, @@ -41,37 +48,41 @@ pub fn gen_assembly( #[cfg(not(target_os = "macos"))] { let output_path = crate::get_output_path(_output_dir)?; - let elf_hash = get_elf_data_hash_from_path(_elf)?; - + let elf_data = + std::fs::read(_elf).with_context(|| format!("Error reading ELF file: {_elf:?}"))?; + let stem = _elf + .file_stem() + .context("Failed to extract file stem from ELF path")? + .to_str() + .context("Failed to convert ELF file stem to string")?; tracing::info!("Computing assembly setup"); - let zisk_path = crate::get_zisk_path(_zisk_path.as_ref()); - _generate_assembly(_elf, &elf_hash, &zisk_path, output_path.as_path(), _hints, _verbose)?; + generate_assembly(&elf_data, stem, output_path.as_path(), _hints, _verbose)?; tracing::info!("Assembly setup generated at {}", output_path.display()); } Ok(()) } -fn _generate_assembly( - elf: &Path, - elf_hash: &str, - zisk_path: &Path, +pub fn generate_assembly( + elf: &[u8], + elf_name: &str, output_path: &Path, hints: bool, verbose: bool, ) -> Result<(), anyhow::Error> { - // Read the ELF file and check if it is a valid ELF file - let elf_file_path = PathBuf::from(elf); - let file_data = std::fs::read(&elf_file_path)?; + let elf_hash = blake3::hash(elf).to_hex().to_string(); - if !is_elf_file(&file_data).unwrap_or_else(|_| panic!("Error reading ROM file")) { - panic!("ROM file is not a valid ELF file"); + if !is_elf_file(elf).context("Error reading ROM file")? { + anyhow::bail!("ROM file is not a valid ELF file"); } - let stem = elf.file_stem().unwrap().to_str().unwrap(); - let stem = if hints { format!("{stem}-hints") } else { stem.to_string() }; + let stem = if hints { format!("{elf_name}-hints") } else { elf_name.to_string() }; let new_filename = format!("{stem}-{elf_hash}.tmp"); let base_path = output_path.join(new_filename); - let file_stem = base_path.file_stem().unwrap().to_str().unwrap(); + let file_stem = base_path + .file_stem() + .context("Failed to extract file stem from base path")? + .to_str() + .context("Failed to convert file stem to string")?; let bin_mt_file = format!("{file_stem}-mt.bin"); let bin_mt_file = base_path.with_file_name(bin_mt_file); @@ -82,22 +93,62 @@ fn _generate_assembly( let bin_mo_file = format!("{file_stem}-mo.bin"); let bin_mo_file = base_path.with_file_name(bin_mo_file); - [ + let installed_path = crate::get_default_zisk_path().join("emulator-asm"); + + // Only check workspace if we're running via `cargo run` or `cargo build` + // Check if the current executable is in the target directory (development mode) + let is_development = std::env::current_exe() + .ok() + .and_then(|exe| exe.to_str().map(|s| s.contains("/target/"))) + .unwrap_or(false); + + let workspace_path = if is_development { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).parent().and_then(|workspace_root| { + let cargo_toml = workspace_root.join("Cargo.toml"); + let emulator_path = workspace_root.join("emulator-asm"); + if emulator_path.exists() && cargo_toml.exists() { + Some(emulator_path) + } else { + None + } + }) + } else { + None + }; + + let (emulator_asm_path, source) = if let Some(ws_path) = workspace_path { + (ws_path, "workspace") + } else if installed_path.exists() { + (installed_path, "installed") + } else { + (installed_path, "installed (not found)") + }; + + println!("Looking for emulator-asm at: {} ({})", emulator_asm_path.display(), source); + + if !emulator_asm_path.exists() { + anyhow::bail!( + "emulator-asm directory not found. Expected at: {}", + emulator_asm_path.display() + ); + } + + let emulator_asm_path = + emulator_asm_path.to_str().context("Failed to convert emulator-asm path to string")?; + + for (file, gen_method) in [ (bin_mt_file, AsmGenerationMethod::AsmMinimalTraces), (bin_rh_file, AsmGenerationMethod::AsmRomHistogram), (bin_mo_file, AsmGenerationMethod::AsmMemOp), - ] - .iter() - .for_each(|(file, gen_method)| { + ] { let asm_file = file.with_extension("asm"); // Convert the ELF file to Zisk format and generates an assembly file - let rv2zk = Riscv2zisk::new(&file_data); + let rv2zk = Riscv2zisk::new(elf); + let asm_file_str = + asm_file.to_str().context("Failed to convert asm_file path to string")?; rv2zk - .runfile(asm_file.to_str().unwrap().to_string(), *gen_method, false, false, hints) - .expect("Error converting elf to assembly"); - - let emulator_asm_path = zisk_path.join("emulator-asm"); - let emulator_asm_path = emulator_asm_path.to_str().unwrap(); + .runfile(asm_file_str.to_string(), gen_method, false, false, hints) + .map_err(|e| anyhow::anyhow!("Error converting ELF to assembly: {}", e))?; // Build the emulator assembly let status = Command::new("make") @@ -106,27 +157,27 @@ fn _generate_assembly( .stdout(if verbose { Stdio::inherit() } else { Stdio::null() }) .stderr(if verbose { Stdio::inherit() } else { Stdio::null() }) .status() - .expect("Failed to run make clean"); + .context("Failed to execute 'make clean' command")?; if !status.success() { - eprintln!("make clean failed"); - std::process::exit(1); + anyhow::bail!("'make clean' failed with exit code: {:?}", status.code()); } + let out_file_str = file.to_str().context("Failed to convert output file path to string")?; + let status = Command::new("make") - .arg(format!("EMU_PATH={}", asm_file.to_str().unwrap())) - .arg(format!("OUT_PATH={}", file.to_str().unwrap())) + .arg(format!("EMU_PATH={}", asm_file_str)) + .arg(format!("OUT_PATH={}", out_file_str)) .current_dir(emulator_asm_path) .stdout(if verbose { Stdio::inherit() } else { Stdio::null() }) .stderr(if verbose { Stdio::inherit() } else { Stdio::null() }) .status() - .expect("Failed to run make"); + .context("Failed to execute 'make' command")?; if !status.success() { - eprintln!("make failed"); - std::process::exit(1); + anyhow::bail!("'make' failed with exit code: {:?}", status.code()); } - }); + } Ok(()) } diff --git a/rom-setup/src/utils.rs b/rom-setup/src/utils.rs index 702760263..a36827915 100644 --- a/rom-setup/src/utils.rs +++ b/rom-setup/src/utils.rs @@ -25,12 +25,6 @@ pub fn get_default_zisk_path() -> PathBuf { PathBuf::from(zisk_path) } -/// Gets the zisk folder. -/// Uses the default one if not specified by user. -pub fn get_zisk_path(zisk_path: Option<&PathBuf>) -> PathBuf { - zisk_path.cloned().unwrap_or_else(get_default_zisk_path) -} - pub fn get_output_path(output_dir: &Option) -> Result { let output_path = if output_dir.is_none() { let cache_path = std::env::var("HOME") diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index bf80211c7..bf6f25edb 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -59,6 +59,7 @@ pub struct ProverClientBuilder { asm_path: Option, base_port: Option, unlock_mapped_memory: bool, + no_auto_setup: bool, // Prove-specific fields (only available when Operation = Prove) gpu_params: ParamsGPU, @@ -211,6 +212,12 @@ impl ProverClientBuilder { self } + #[must_use] + pub fn no_auto_setup(mut self, no_auto_setup: bool) -> Self { + self.no_auto_setup = no_auto_setup; + self + } + #[must_use] pub fn base_port(mut self, base_port: u16) -> Self { self.base_port = Some(base_port); @@ -429,6 +436,7 @@ impl ProverClientBuilder { self.shared_tables, self.base_port, self.unlock_mapped_memory, + self.no_auto_setup, self.gpu_params, self.logging_config, )? @@ -481,6 +489,7 @@ impl From> for ProverClientBuilder { asm_path: None, base_port: None, unlock_mapped_memory: false, + no_auto_setup: false, _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -509,6 +518,7 @@ impl From> for ProverClientBuilder { asm_path: builder.asm_path, base_port: builder.base_port, unlock_mapped_memory: builder.unlock_mapped_memory, + no_auto_setup: builder.no_auto_setup, _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -539,6 +549,7 @@ impl From> asm_path: builder.asm_path, base_port: builder.base_port, unlock_mapped_memory: builder.unlock_mapped_memory, + no_auto_setup: builder.no_auto_setup, _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -567,6 +578,7 @@ impl From> for ProverClientBuilder, unlock_mapped_memory: bool, + no_auto_setup: bool, gpu_params: ParamsGPU, logging_config: Option, ) -> Result { @@ -60,6 +61,7 @@ impl AsmProver { shared_tables, base_port, unlock_mapped_memory, + no_auto_setup, gpu_params, logging_config, )?; @@ -134,8 +136,30 @@ impl ProverEngine for AsmProver { let asm_mt_path = default_cache_path.join(asm_mt_filename); let asm_rh_path = default_cache_path.join(asm_rh_filename); - check_paths_exist(&asm_mt_path)?; - check_paths_exist(&asm_rh_path)?; + if check_paths_exist(&asm_mt_path).is_err() || check_paths_exist(&asm_rh_path).is_err() { + if self.core_prover.no_auto_setup { + return Err(anyhow::anyhow!( + "Assembly files not found for ELF {}. Force ROM setup is enabled, but assembly files are still missing. Please ensure that the assembly generation process has been completed successfully.", + elf.name() + )); + } + + tracing::info!( + ">>> ROM SETUP (one time only) - Generating assembly files for ELF: {}", + elf.name() + ); + timer_start_info!(ROM_SETUP); + let output_path = get_output_path(&None)?; + generate_assembly( + elf.elf(), + elf.name(), + &output_path, + elf.with_hints(), + self.core_prover.verbose != VerboseMode::Info, + )?; + timer_stop_and_log_info!(ROM_SETUP); + tracing::info!("<<< ROM SETUP complete - Assembly files cached for future use"); + } timer_start_info!(STARTING_ASM_MICROSERVICES); let asm_services = AsmServices::new(world_rank, local_rank, base_port); @@ -305,6 +329,7 @@ pub struct AsmCoreProver { base_port: Option, unlock_mapped_memory: bool, verbose: VerboseMode, + no_auto_setup: bool, } impl AsmCoreProver { @@ -319,6 +344,7 @@ impl AsmCoreProver { shared_tables: bool, base_port: Option, unlock_mapped_memory: bool, + no_auto_setup: bool, gpu_params: ParamsGPU, logging_config: Option, ) -> Result { @@ -377,6 +403,7 @@ impl AsmCoreProver { base_port, unlock_mapped_memory, verbose: verbose.into(), + no_auto_setup, }) } @@ -390,6 +417,7 @@ impl AsmCoreProver { base_port: None, unlock_mapped_memory: false, verbose: VerboseMode::Info, + no_auto_setup: false, }) } } diff --git a/zisk-contracts/PlonkVerifier.sol b/zisk-contracts/PlonkVerifier.sol index 794bbe1c4..782982af5 100644 --- a/zisk-contracts/PlonkVerifier.sol +++ b/zisk-contracts/PlonkVerifier.sol @@ -51,8 +51,8 @@ contract PlonkVerifier { uint256 constant Qry = 1770043357969798948108297406468647422219312923587004610833401748122605130485; uint256 constant Qox = 429670666919728039002263956209270185134416998857239332019860571923582607053; uint256 constant Qoy = 11024111753296480975480562321760673479457513637396733200807061983645283791758; - uint256 constant Qcx = 4434809406798938738431069472809456595063321363145767360968983463659931159037; - uint256 constant Qcy = 19430845146272042503995416110175245795271603235563329595858119690639500549372; + uint256 constant Qcx = 18759539946247966109746686045765624942782666785179752870440507130849473521082; + uint256 constant Qcy = 3887166330695441169968198076272085557699876114516022583985382505001285608090; uint256 constant S1x = 14529212361763788633811869842580421415134713687938570863064659590110934903740; uint256 constant S1y = 15130539367607292893898013497807485005818775366682240859886811844322272868379; uint256 constant S2x = 88575883471378427909504541476422256478241201143920420405836063934407519240; diff --git a/zisk-contracts/ZiskVerifier.sol b/zisk-contracts/ZiskVerifier.sol index d47ab0705..55e0ea77f 100644 --- a/zisk-contracts/ZiskVerifier.sol +++ b/zisk-contracts/ZiskVerifier.sol @@ -19,7 +19,7 @@ contract ZiskVerifier is PlonkVerifier, IZiskVerifier { } function getRootCVadcopFinal() external pure returns (uint64[4] memory) { - return [uint64(5756952873125057328), uint64(1254521327410429374), uint64(17471446849604192873), uint64(13226325674217234543)]; + return [uint64(18197808752607027641), uint64(10557759893038425521), uint64(2118750178470058523), uint64(17544832608316643878)]; } // Modulus zkSNARK diff --git a/ziskbuild/src/build.rs b/ziskbuild/src/build.rs index 9e1272697..a23a53484 100644 --- a/ziskbuild/src/build.rs +++ b/ziskbuild/src/build.rs @@ -69,13 +69,8 @@ pub fn execute_build_program( let program_dir: Utf8PathBuf = program_dir.try_into().expect("Failed to convert PathBuf to Utf8PathBuf"); - // Check for ZISK_PATH and ZISK_HINTS environment variables if not set in args + // Check for ZISK_HINTS environment variables if not set in args let mut args = args.clone(); - if args.zisk_path.is_none() { - if let Ok(env_path) = std::env::var("ZISK_PATH") { - args.zisk_path = Some(env_path); - } - } if args.hints.is_none() { if let Ok(env_hints) = std::env::var("ZISK_HINTS") { args.hints = env_hints.parse().ok(); @@ -102,7 +97,6 @@ pub fn execute_build_program( let hints = args.hints.unwrap_or(false); println!("cargo:rerun-if-env-changed=ZISK_HINTS"); - let zisk_path_buf = args.zisk_path.as_ref().map(PathBuf::from); let output_path = get_output_path(&None)?; for (_, elf_path) in target_elf_paths.iter() { let elf_path_std = elf_path.as_std_path(); @@ -120,7 +114,7 @@ pub fn execute_build_program( }; if !assembly_exists || hints_changed { - gen_assembly(elf_path_std, &zisk_path_buf, &None, hints, true)?; + gen_assembly(elf_path_std, &None, hints, true)?; std::fs::write(&hints_marker, new_value)?; } } diff --git a/ziskbuild/src/lib.rs b/ziskbuild/src/lib.rs index 7ec3085c3..4341902cf 100644 --- a/ziskbuild/src/lib.rs +++ b/ziskbuild/src/lib.rs @@ -44,9 +44,6 @@ pub struct BuildArgs { #[clap(long, value_name = "ELF_NAME")] elf_name: Option, - #[clap(short = 'z', long, value_name = "ZISK_PATH")] - zisk_path: Option, - #[clap(long, value_name = "HINTS")] pub hints: Option, } From c7594b42e66e84797adfb197a2f71203fb89ba70 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 19 Feb 2026 13:03:49 +0000 Subject: [PATCH 565/782] Cargo fmt --- examples/sha-hasher/host/bin/compressed.rs | 2 +- examples/sha-hasher/host/bin/execute.rs | 2 +- examples/sha-hasher/host/bin/plonk.rs | 2 +- examples/sha-hasher/host/bin/prove.rs | 4 ++-- examples/sha-hasher/host/bin/verify-constraints.rs | 2 +- examples/sha-hasher/host/bin/ziskemu.rs | 2 +- examples/sha-hasher/host/src/main.rs | 2 +- sdk/src/prover/mod.rs | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/sha-hasher/host/bin/compressed.rs b/examples/sha-hasher/host/bin/compressed.rs index 76d1faacb..8508e5be8 100644 --- a/examples/sha-hasher/host/bin/compressed.rs +++ b/examples/sha-hasher/host/bin/compressed.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use zisk_sdk::{ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin, include_elf}; +use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/execute.rs b/examples/sha-hasher/host/bin/execute.rs index 1bacc871f..ef728d0a6 100644 --- a/examples/sha-hasher/host/bin/execute.rs +++ b/examples/sha-hasher/host/bin/execute.rs @@ -1,6 +1,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -use zisk_sdk::{ElfBinary, ProverClient, ZiskIO, ZiskStdin, include_elf}; +use zisk_sdk::{include_elf, ElfBinary, ProverClient, ZiskIO, ZiskStdin}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/plonk.rs b/examples/sha-hasher/host/bin/plonk.rs index 39529a953..bbbf54a87 100644 --- a/examples/sha-hasher/host/bin/plonk.rs +++ b/examples/sha-hasher/host/bin/plonk.rs @@ -1,6 +1,6 @@ use anyhow::Result; use zisk_sdk::{ - ElfBinary, ProverClient, ZiskIO, ZiskProofWithPublicValues, ZiskStdin, include_elf, + include_elf, ElfBinary, ProverClient, ZiskIO, ZiskProofWithPublicValues, ZiskStdin, }; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/prove.rs b/examples/sha-hasher/host/bin/prove.rs index 78bc19137..70734438b 100644 --- a/examples/sha-hasher/host/bin/prove.rs +++ b/examples/sha-hasher/host/bin/prove.rs @@ -2,8 +2,8 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use zisk_sdk::{ - ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskProof, ZiskProofWithPublicValues, ZiskPublics, - ZiskStdin, include_elf, + include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskProof, ZiskProofWithPublicValues, + ZiskPublics, ZiskStdin, }; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/verify-constraints.rs b/examples/sha-hasher/host/bin/verify-constraints.rs index 7dd8ace6d..e5d988f3d 100644 --- a/examples/sha-hasher/host/bin/verify-constraints.rs +++ b/examples/sha-hasher/host/bin/verify-constraints.rs @@ -1,7 +1,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use zisk_sdk::{ElfBinaryFromFile, ProverClient, ZiskIO, ZiskStdin, elf_path}; +use zisk_sdk::{elf_path, ElfBinaryFromFile, ProverClient, ZiskIO, ZiskStdin}; #[derive(Serialize, Deserialize, Debug)] struct Output { diff --git a/examples/sha-hasher/host/bin/ziskemu.rs b/examples/sha-hasher/host/bin/ziskemu.rs index 2eb396214..7c2a8d6ee 100644 --- a/examples/sha-hasher/host/bin/ziskemu.rs +++ b/examples/sha-hasher/host/bin/ziskemu.rs @@ -1,6 +1,6 @@ use anyhow::Result; use std::path::PathBuf; -use zisk_sdk::{ElfBinaryFromFile, EmuOptions, ZiskIO, ZiskStdin, elf_path, ziskemu}; +use zisk_sdk::{elf_path, ziskemu, ElfBinaryFromFile, EmuOptions, ZiskIO, ZiskStdin}; fn main() -> Result<()> { let elf_path = elf_path!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/src/main.rs b/examples/sha-hasher/host/src/main.rs index 7a6c34d4a..4077855ad 100644 --- a/examples/sha-hasher/host/src/main.rs +++ b/examples/sha-hasher/host/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use zisk_sdk::{ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin, include_elf}; +use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 27376a847..41a685618 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -132,8 +132,8 @@ pub struct ZiskProgramPK { impl Drop for ZiskProgramPK { fn drop(&mut self) { // Shut down ASM microservices - info!(">>> [{}] Stopping ASM microservices.", self.rank_info.world_rank); if let Some(asm_services) = &self.asm_services { + info!(">>> [{}] Stopping ASM microservices.", self.rank_info.world_rank); if let Err(e) = asm_services.stop_asm_services() { tracing::error!( ">>> [{}] Failed to stop ASM microservices: {}", From 2ef3126d5f6c0f37182b4c2bc245508ae0a3a893 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Thu, 19 Feb 2026 14:28:34 +0100 Subject: [PATCH 566/782] DMA assembly executor WIP --- core/src/direct_calls.rs | 219 ++++ core/src/lib.rs | 2 + core/src/riscv2zisk_context.rs | 219 +--- core/src/zisk_rom_2_asm.rs | 1 + emulator-asm/src/dma/Makefile | 55 +- .../src/dma/direct_inpucpy_mtrace.asm | 314 +++++ emulator-asm/src/dma/direct_memcpy_mops.asm | 3 - emulator-asm/src/dma/direct_xinput_mops.asm | 268 +++++ emulator-asm/src/dma/direct_xmemset_mops.asm | 247 ++-- .../src/dma/direct_xmemset_mtrace.asm | 527 ++++---- emulator-asm/src/dma/dma_constants.inc | 23 +- emulator-asm/src/dma/fast_dma_encode.asm | 91 +- .../src/dma/fast_dma_encode_table.asm | 1059 +++++++++-------- emulator-asm/src/dma/fast_memcpy.asm | 133 +++ emulator-asm/src/dma/fast_memcpy64.asm | 111 ++ emulator-asm/src/dma/fast_memset.asm | 139 +++ emulator-asm/src/dma/test_dma.cpp | 7 + emulator-asm/src/dma/test_dma_inputcpy | Bin 0 -> 92392 bytes emulator-asm/src/dma/test_dma_inputcpy_mops | Bin 0 -> 93368 bytes .../src/dma/test_dma_inputcpy_mops.cpp | 334 ++++++ emulator-asm/src/dma/test_dma_memset_mops | Bin 0 -> 84816 bytes emulator-asm/src/dma/test_dma_memset_mops.cpp | 329 +++++ emulator-asm/src/dma/test_dma_memset_mtrace | Bin 0 -> 110824 bytes .../src/dma/test_dma_memset_mtrace.cpp | 314 +++++ emulator-asm/src/dma/test_fast_memcpy | Bin 0 -> 75800 bytes emulator-asm/src/dma/test_fast_memcpy.cpp | 193 +++ precompiles/helpers/src/dma.rs | 192 ++- ziskos/entrypoint/src/dma.rs | 14 +- 28 files changed, 3513 insertions(+), 1281 deletions(-) create mode 100644 core/src/direct_calls.rs create mode 100644 emulator-asm/src/dma/direct_inpucpy_mtrace.asm create mode 100644 emulator-asm/src/dma/direct_xinput_mops.asm create mode 100644 emulator-asm/src/dma/fast_memcpy.asm create mode 100644 emulator-asm/src/dma/fast_memcpy64.asm create mode 100644 emulator-asm/src/dma/fast_memset.asm create mode 100755 emulator-asm/src/dma/test_dma_inputcpy create mode 100755 emulator-asm/src/dma/test_dma_inputcpy_mops create mode 100644 emulator-asm/src/dma/test_dma_inputcpy_mops.cpp create mode 100755 emulator-asm/src/dma/test_dma_memset_mops create mode 100644 emulator-asm/src/dma/test_dma_memset_mops.cpp create mode 100755 emulator-asm/src/dma/test_dma_memset_mtrace create mode 100644 emulator-asm/src/dma/test_dma_memset_mtrace.cpp create mode 100755 emulator-asm/src/dma/test_fast_memcpy create mode 100644 emulator-asm/src/dma/test_fast_memcpy.cpp diff --git a/core/src/direct_calls.rs b/core/src/direct_calls.rs new file mode 100644 index 000000000..cea9fb57a --- /dev/null +++ b/core/src/direct_calls.rs @@ -0,0 +1,219 @@ +use riscv::RiscvInstruction; + +pub fn dma_direct_calls( + riscv_instructions: &Vec, + dma_addrs: (u64, u64, u64, u64), +) { + let (memcpy_addr, memcmp_addr, memset_addr, memmove_addr) = dma_addrs; + + // Detect AUIPC + JALR patterns that jump to DMA functions + // Pattern: AUIPC rd, imm20 followed by JALR ra, rd, imm12 + // Target address = PC + (imm20 << 12) + imm12 + let mut dma_call_pcs: std::collections::HashSet = std::collections::HashSet::new(); + + // Register numbers for a0, a1, a2 (x10, x11, x12) + const REG_A0: u32 = 10; + const REG_A1: u32 = 11; + const REG_A2: u32 = 12; + + // Types of register loading + #[derive(Debug, Clone)] + enum RegLoadType { + /// Immediate value: li rd, imm or addi rd, x0, imm + Immediate(i32), + /// Register + immediate: addi rd, rs, imm or mv rd, rs (imm=0) + RegPlusImm { rs: u32, imm: i32 }, + /// Load from memory: ld/lw rd, imm(rs) + MemLoad { rs: u32, imm: i32, size: u8 }, + /// Not found + NotFound, + } + + /// Find how a register is loaded before a given instruction index + fn find_reg_load( + riscv_instructions: &[RiscvInstruction], + call_idx: usize, + target_reg: u32, + ) -> (usize, RegLoadType) { + // Search backwards from call_idx + for offset in 1..=20.min(call_idx) { + let idx = call_idx - offset; + let inst = &riscv_instructions[idx]; + + // Check if this instruction writes to target_reg + if inst.rd != target_reg { + continue; + } + + // li rd, imm -> actually addi rd, x0, imm or lui+addi + // addi rd, rs, imm + if inst.inst == "addi" || inst.inst == "c.addi" { + if inst.rs1 == 0 { + // li rd, imm (addi rd, x0, imm) + return (offset, RegLoadType::Immediate(inst.imm)); + } else { + // addi rd, rs, imm + return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: inst.imm }); + } + } + + // c.li rd, imm + if inst.inst == "c.li" { + return (offset, RegLoadType::Immediate(inst.imm)); + } + + // mv rd, rs -> addi rd, rs, 0 or c.mv + if inst.inst == "c.mv" { + return (offset, RegLoadType::RegPlusImm { rs: inst.rs2, imm: 0 }); + } + + // lui rd, imm (upper immediate) + if inst.inst == "lui" || inst.inst == "c.lui" { + return (offset, RegLoadType::Immediate(inst.imm)); + } + + // ld rd, imm(rs) - 64-bit load + if inst.inst == "ld" || inst.inst == "c.ld" || inst.inst == "c.ldsp" { + return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 8 }); + } + + // lw rd, imm(rs) - 32-bit load + if inst.inst == "lw" || inst.inst == "c.lw" || inst.inst == "c.lwsp" { + return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 4 }); + } + + // lwu rd, imm(rs) - 32-bit unsigned load + if inst.inst == "lwu" { + return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 4 }); + } + + // add rd, rs1, rs2 (could be mv if rs2=0 or rs1=0) + if inst.inst == "add" || inst.inst == "c.add" { + if inst.rs1 == 0 { + return (offset, RegLoadType::RegPlusImm { rs: inst.rs2, imm: 0 }); + } else if inst.rs2 == 0 { + return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: 0 }); + } + // General add - treat as reg+imm with imm=0 (approximate) + return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: 0 }); + } + + // Other instruction that writes to target_reg but we don't recognize + // Stop searching since the register was overwritten + return (offset, RegLoadType::NotFound); + } + (0, RegLoadType::NotFound) + } + + fn reg_load_to_string(load: &RegLoadType) -> String { + match load { + RegLoadType::Immediate(imm) => format!("imm={}", imm), + RegLoadType::RegPlusImm { rs, imm } => format!("x{}+{}", rs, imm), + RegLoadType::MemLoad { rs, imm, size } => format!("{}(x{})[{}B]", imm, rs, size), + RegLoadType::NotFound => "???".to_string(), + } + } + + for i in 0..riscv_instructions.len().saturating_sub(1) { + let inst = &riscv_instructions[i]; + let next_inst = &riscv_instructions[i + 1]; + + // Check for AUIPC + JALR pattern + if inst.inst == "auipc" && next_inst.inst == "jalr" { + // AUIPC uses the same register that JALR reads from + if inst.rd == next_inst.rs1 { + // Calculate target address: PC + imm_auipc + imm_jalr + // Note: inst.imm for AUIPC is already shifted (has lower 12 bits as zero) + let target = (inst.rom_address as i64) + .wrapping_add(inst.imm as i64) + .wrapping_add(next_inst.imm as i64) as u64; + + // Check if target is a DMA function + let is_dma = (memcpy_addr != 0 && target == memcpy_addr) + || (memcmp_addr != 0 && target == memcmp_addr) + || (memset_addr != 0 && target == memset_addr) + || (memmove_addr != 0 && target == memmove_addr); + + if is_dma { + let func_name = if target == memcpy_addr { + "memcpy" + } else if target == memcmp_addr { + "memcmp" + } else if target == memset_addr { + "memset" + } else { + "inputcpy" + }; + + // Find how a0, a1, a2 are loaded (search from JALR position = i+1) + let jalr_idx = i + 1; + let (a0_offset, a0_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A0); + let (a1_offset, a1_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A1); + let (a2_offset, a2_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A2); + + // Check if any register load was not found + let has_missing = matches!(a0_load, RegLoadType::NotFound) + || matches!(a1_load, RegLoadType::NotFound) + || matches!(a2_load, RegLoadType::NotFound); + + println!( + "DMA call to {} at PC=0x{:x}:{}", + func_name, + next_inst.rom_address, + if has_missing { " [INCOMPLETE - needs analysis]" } else { "" } + ); + println!(" a0: offset=-{}, {}", a0_offset, reg_load_to_string(&a0_load)); + println!(" a1: offset=-{}, {}", a1_offset, reg_load_to_string(&a1_load)); + println!(" a2: offset=-{}, {}", a2_offset, reg_load_to_string(&a2_load)); + + dma_call_pcs.insert(inst.rom_address); + dma_call_pcs.insert(next_inst.rom_address); + } + } + } + + // Check for JAL (direct jump) pattern + if inst.inst == "jal" { + let target = (inst.rom_address as i64).wrapping_add(inst.imm as i64) as u64; + + let is_dma = (memcpy_addr != 0 && target == memcpy_addr) + || (memcmp_addr != 0 && target == memcmp_addr) + || (memset_addr != 0 && target == memset_addr) + || (memmove_addr != 0 && target == memmove_addr); + + if is_dma { + let func_name = if target == memcpy_addr { + "memcpy" + } else if target == memcmp_addr { + "memcmp" + } else if target == memset_addr { + "memset" + } else { + "inputcpy" + }; + + // Find how a0, a1, a2 are loaded + let (a0_offset, a0_load) = find_reg_load(&riscv_instructions, i, REG_A0); + let (a1_offset, a1_load) = find_reg_load(&riscv_instructions, i, REG_A1); + let (a2_offset, a2_load) = find_reg_load(&riscv_instructions, i, REG_A2); + + // Check if any register load was not found + let has_missing = matches!(a0_load, RegLoadType::NotFound) + || matches!(a1_load, RegLoadType::NotFound) + || matches!(a2_load, RegLoadType::NotFound); + + println!( + "DMA call to {} at PC=0x{:x} (JAL):{}", + func_name, + inst.rom_address, + if has_missing { " [INCOMPLETE - needs analysis]" } else { "" } + ); + println!(" a0: offset=-{}, {}", a0_offset, reg_load_to_string(&a0_load)); + println!(" a1: offset=-{}, {}", a1_offset, reg_load_to_string(&a1_load)); + println!(" a2: offset=-{}, {}", a2_offset, reg_load_to_string(&a2_load)); + + dma_call_pcs.insert(inst.rom_address); + } + } + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index f2904da87..5682dcba5 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -51,6 +51,7 @@ //! //! The zisk_core crate contains basic structures and functionality used by several other modules: //! opcodes, instructions and transpilation +pub mod direct_calls; pub mod elf2rom; pub mod elf_extraction; pub mod fcall; @@ -69,6 +70,7 @@ pub mod zisk_required_operation; pub mod zisk_rom; pub mod zisk_rom_2_asm; +pub use direct_calls::*; pub use elf2rom::*; pub use fcall::*; pub use helpers::*; diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 1d2340c08..6ae38f6e6 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -96,7 +96,9 @@ impl Riscv2ZiskContext<'_> { // I.1. Integer Computational (Register-Register) "add" => { - if riscv_instruction.rd == 0 && self.input_precompile == Some(0x813) { + if riscv_instruction.rd == 0 + && self.input_precompile == Some(SYSCALL_DMA_MEMCPY_ID as u32) + { self.create_precompiles_op( riscv_instruction, "dma_memcpy", @@ -104,7 +106,7 @@ impl Riscv2ZiskContext<'_> { self.input_precompile_reg.unwrap(), 4, ); - } else if self.input_precompile == Some(0x814) { + } else if self.input_precompile == Some(SYSCALL_DMA_MEMCMP_ID as u32) { self.create_precompiles_op( riscv_instruction, "dma_memcmp", @@ -2048,225 +2050,14 @@ impl Riscv2ZiskContext<'_> { pub fn add_zisk_code(rom: &mut ZiskRom, addr: u64, data: &[u8], _dma_addrs: (u64, u64, u64, u64)) { //print!("add_zisk_code() addr={}\n", addr); - // let (memcpy_addr, memcmp_addr, memset_addr, memmove_addr) = dma_addrs; - // Convert input data to a u32 vector let code_vector: Vec = convert_vector(data); // Convert data vector to RISCV instructions let riscv_instructions = riscv_interpreter(addr, &code_vector); - /* - // Detect AUIPC + JALR patterns that jump to DMA functions - // Pattern: AUIPC rd, imm20 followed by JALR ra, rd, imm12 - // Target address = PC + (imm20 << 12) + imm12 - let mut dma_call_pcs: std::collections::HashSet = std::collections::HashSet::new(); - - // Register numbers for a0, a1, a2 (x10, x11, x12) - const REG_A0: u32 = 10; - const REG_A1: u32 = 11; - const REG_A2: u32 = 12; - - // Types of register loading - #[derive(Debug, Clone)] - enum RegLoadType { - /// Immediate value: li rd, imm or addi rd, x0, imm - Immediate(i32), - /// Register + immediate: addi rd, rs, imm or mv rd, rs (imm=0) - RegPlusImm { rs: u32, imm: i32 }, - /// Load from memory: ld/lw rd, imm(rs) - MemLoad { rs: u32, imm: i32, size: u8 }, - /// Not found - NotFound, - } - - /// Find how a register is loaded before a given instruction index - fn find_reg_load( - riscv_instructions: &[RiscvInstruction], - call_idx: usize, - target_reg: u32, - ) -> (usize, RegLoadType) { - // Search backwards from call_idx - for offset in 1..=20.min(call_idx) { - let idx = call_idx - offset; - let inst = &riscv_instructions[idx]; - - // Check if this instruction writes to target_reg - if inst.rd != target_reg { - continue; - } - - // li rd, imm -> actually addi rd, x0, imm or lui+addi - // addi rd, rs, imm - if inst.inst == "addi" || inst.inst == "c.addi" { - if inst.rs1 == 0 { - // li rd, imm (addi rd, x0, imm) - return (offset, RegLoadType::Immediate(inst.imm)); - } else { - // addi rd, rs, imm - return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: inst.imm }); - } - } - - // c.li rd, imm - if inst.inst == "c.li" { - return (offset, RegLoadType::Immediate(inst.imm)); - } - - // mv rd, rs -> addi rd, rs, 0 or c.mv - if inst.inst == "c.mv" { - return (offset, RegLoadType::RegPlusImm { rs: inst.rs2, imm: 0 }); - } - - // lui rd, imm (upper immediate) - if inst.inst == "lui" || inst.inst == "c.lui" { - return (offset, RegLoadType::Immediate(inst.imm)); - } - - // ld rd, imm(rs) - 64-bit load - if inst.inst == "ld" || inst.inst == "c.ld" || inst.inst == "c.ldsp" { - return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 8 }); - } - - // lw rd, imm(rs) - 32-bit load - if inst.inst == "lw" || inst.inst == "c.lw" || inst.inst == "c.lwsp" { - return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 4 }); - } - - // lwu rd, imm(rs) - 32-bit unsigned load - if inst.inst == "lwu" { - return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 4 }); - } - - // add rd, rs1, rs2 (could be mv if rs2=0 or rs1=0) - if inst.inst == "add" || inst.inst == "c.add" { - if inst.rs1 == 0 { - return (offset, RegLoadType::RegPlusImm { rs: inst.rs2, imm: 0 }); - } else if inst.rs2 == 0 { - return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: 0 }); - } - // General add - treat as reg+imm with imm=0 (approximate) - return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: 0 }); - } - - // Other instruction that writes to target_reg but we don't recognize - // Stop searching since the register was overwritten - return (offset, RegLoadType::NotFound); - } - (0, RegLoadType::NotFound) - } - - fn reg_load_to_string(load: &RegLoadType) -> String { - match load { - RegLoadType::Immediate(imm) => format!("imm={}", imm), - RegLoadType::RegPlusImm { rs, imm } => format!("x{}+{}", rs, imm), - RegLoadType::MemLoad { rs, imm, size } => format!("{}(x{})[{}B]", imm, rs, size), - RegLoadType::NotFound => "???".to_string(), - } - } - - for i in 0..riscv_instructions.len().saturating_sub(1) { - let inst = &riscv_instructions[i]; - let next_inst = &riscv_instructions[i + 1]; - - // Check for AUIPC + JALR pattern - if inst.inst == "auipc" && next_inst.inst == "jalr" { - // AUIPC uses the same register that JALR reads from - if inst.rd == next_inst.rs1 { - // Calculate target address: PC + imm_auipc + imm_jalr - // Note: inst.imm for AUIPC is already shifted (has lower 12 bits as zero) - let target = (inst.rom_address as i64) - .wrapping_add(inst.imm as i64) - .wrapping_add(next_inst.imm as i64) as u64; - - // Check if target is a DMA function - let is_dma = (memcpy_addr != 0 && target == memcpy_addr) - || (memcmp_addr != 0 && target == memcmp_addr) - || (memset_addr != 0 && target == memset_addr) - || (memmove_addr != 0 && target == memmove_addr); - - if is_dma { - let func_name = if target == memcpy_addr { - "memcpy" - } else if target == memcmp_addr { - "memcmp" - } else if target == memset_addr { - "memset" - } else { - "inputcpy" - }; - - // Find how a0, a1, a2 are loaded (search from JALR position = i+1) - let jalr_idx = i + 1; - let (a0_offset, a0_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A0); - let (a1_offset, a1_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A1); - let (a2_offset, a2_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A2); - - // Check if any register load was not found - let has_missing = matches!(a0_load, RegLoadType::NotFound) - || matches!(a1_load, RegLoadType::NotFound) - || matches!(a2_load, RegLoadType::NotFound); - - println!( - "DMA call to {} at PC=0x{:x}:{}", - func_name, - next_inst.rom_address, - if has_missing { " [INCOMPLETE - needs analysis]" } else { "" } - ); - println!(" a0: offset=-{}, {}", a0_offset, reg_load_to_string(&a0_load)); - println!(" a1: offset=-{}, {}", a1_offset, reg_load_to_string(&a1_load)); - println!(" a2: offset=-{}, {}", a2_offset, reg_load_to_string(&a2_load)); - - dma_call_pcs.insert(inst.rom_address); - dma_call_pcs.insert(next_inst.rom_address); - } - } - } - // Check for JAL (direct jump) pattern - if inst.inst == "jal" { - let target = (inst.rom_address as i64).wrapping_add(inst.imm as i64) as u64; + // dma_direct_calls(&riscv_instructions, _dma_addrs); - let is_dma = (memcpy_addr != 0 && target == memcpy_addr) - || (memcmp_addr != 0 && target == memcmp_addr) - || (memset_addr != 0 && target == memset_addr) - || (memmove_addr != 0 && target == memmove_addr); - - if is_dma { - let func_name = if target == memcpy_addr { - "memcpy" - } else if target == memcmp_addr { - "memcmp" - } else if target == memset_addr { - "memset" - } else { - "inputcpy" - }; - - // Find how a0, a1, a2 are loaded - let (a0_offset, a0_load) = find_reg_load(&riscv_instructions, i, REG_A0); - let (a1_offset, a1_load) = find_reg_load(&riscv_instructions, i, REG_A1); - let (a2_offset, a2_load) = find_reg_load(&riscv_instructions, i, REG_A2); - - // Check if any register load was not found - let has_missing = matches!(a0_load, RegLoadType::NotFound) - || matches!(a1_load, RegLoadType::NotFound) - || matches!(a2_load, RegLoadType::NotFound); - - println!( - "DMA call to {} at PC=0x{:x} (JAL):{}", - func_name, - inst.rom_address, - if has_missing { " [INCOMPLETE - needs analysis]" } else { "" } - ); - println!(" a0: offset=-{}, {}", a0_offset, reg_load_to_string(&a0_load)); - println!(" a1: offset=-{}, {}", a1_offset, reg_load_to_string(&a1_load)); - println!(" a2: offset=-{}, {}", a2_offset, reg_load_to_string(&a2_load)); - - dma_call_pcs.insert(inst.rom_address); - } - } - } - */ // Create a context to convert RISCV instructions to ZisK instructions, using rom.insts let mut ctx = Riscv2ZiskContext { insts: &mut rom.insts, diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index d7532017f..50abcad8f 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -54,6 +54,7 @@ const REG_CHUNK_PLAYER_ADDRESS: &str = "rbp"; // Used only in chunk player const TRACE_ADDR_NUMBER: u64 = 0xd0000000 + 0x20; // Fcall params and result lengths +// NOTE: if these parameters are update, review dma_constants.inc const FCALL_PARAMS_LENGTH: u64 = 386; const FCALL_RESULT_LENGTH: u64 = 8193; diff --git a/emulator-asm/src/dma/Makefile b/emulator-asm/src/dma/Makefile index 0f82efa4d..c92a80287 100644 --- a/emulator-asm/src/dma/Makefile +++ b/emulator-asm/src/dma/Makefile @@ -6,14 +6,31 @@ CXXFLAGS = -std=c++17 -O0 -Wall -Wextra -g -no-pie LDFLAGS = -no-pie ASMFLAGS = -g --noexecstack -I$(CURDIR) -SRCS_ASM = direct_memcpy_mtrace.asm memcpy_fast.asm fast_dma_encode.asm direct_memcpy_mops.asm -SRCS_CPP = test_dma.cpp +SRCS_ASM = direct_memcpy_mtrace.asm memcpy_fast.asm fast_dma_encode.asm direct_memcpy_mops.asm direct_xmemset_mops.asm direct_xmemset_mtrace.asm fast_memset.asm fast_memcpy.asm direct_xinput_mops.asm +SRCS_CPP = test_dma.cpp test_dma_memset_mops.cpp test_dma_memset_mtrace.cpp test_fast_memcpy.cpp test_dma_inputcpy_mops.cpp OBJS_ASM = direct_memcpy_mtrace.o memcpy_fast.o fast_dma_encode.o direct_memcpy_mops.o -OBJS_CPP = $(SRCS_CPP:.cpp=.o) -OBJS = $(OBJS_ASM) $(OBJS_CPP) +OBJS = $(OBJS_ASM) test_dma.o TARGET = test_dma +TARGET_MEMSET = test_dma_memset_mops +TARGET_MEMSET_MTRACE = test_dma_memset_mtrace +TARGET_FAST_MEMCPY = test_fast_memcpy +TARGET_INPUTCPY = test_dma_inputcpy_mops -all: $(TARGET) +# Memset MOPS-specific objects +OBJS_MEMSET_ASM = direct_xmemset_mops.o fast_memset.o +OBJS_MEMSET = $(OBJS_MEMSET_ASM) test_dma_memset_mops.o + +# Memset MTRACE-specific objects +OBJS_MEMSET_MTRACE_ASM = direct_xmemset_mtrace.o fast_memset.o fast_dma_encode.o fast_memcpy.o +OBJS_MEMSET_MTRACE = $(OBJS_MEMSET_MTRACE_ASM) test_dma_memset_mtrace.o + +# Fast memcpy test objects +OBJS_FAST_MEMCPY = fast_memcpy.o test_fast_memcpy.o + +# Inputcpy test objects +OBJS_INPUTCPY = direct_xinput_mops.o fast_memcpy.o test_dma_inputcpy_mops.o + +all: $(TARGET) $(TARGET_MEMSET) $(TARGET_MEMSET_MTRACE) $(TARGET_FAST_MEMCPY) $(TARGET_INPUTCPY) %.o: %.asm as $(ASMFLAGS) -o $@ $< @@ -24,10 +41,34 @@ all: $(TARGET) $(TARGET): $(OBJS) $(CXX) $(LDFLAGS) -o $@ $^ +$(TARGET_MEMSET): $(OBJS_MEMSET) + $(CXX) $(LDFLAGS) -o $@ $^ + +$(TARGET_MEMSET_MTRACE): $(OBJS_MEMSET_MTRACE) + $(CXX) $(LDFLAGS) -o $@ $^ + +$(TARGET_FAST_MEMCPY): $(OBJS_FAST_MEMCPY) + $(CXX) $(LDFLAGS) -o $@ $^ + +$(TARGET_INPUTCPY): $(OBJS_INPUTCPY) + $(CXX) $(LDFLAGS) -o $@ $^ + test: $(TARGET) ./$(TARGET) +test_memset: $(TARGET_MEMSET) + ./$(TARGET_MEMSET) + +test_memset_mtrace: $(TARGET_MEMSET_MTRACE) + ./$(TARGET_MEMSET_MTRACE) + +run_fast_memcpy: $(TARGET_FAST_MEMCPY) + ./$(TARGET_FAST_MEMCPY) + +test_inputcpy: $(TARGET_INPUTCPY) + ./$(TARGET_INPUTCPY) + clean: - rm -f $(OBJS) $(TARGET) + rm -f $(OBJS) $(OBJS_MEMSET) $(OBJS_MEMSET_MTRACE) $(OBJS_FAST_MEMCPY) $(OBJS_INPUTCPY) $(TARGET) $(TARGET_MEMSET) $(TARGET_MEMSET_MTRACE) $(TARGET_FAST_MEMCPY) $(TARGET_INPUTCPY) -.PHONY: all test_dma clean +.PHONY: all test test_memset test_memset_mtrace run_fast_memcpy test_inputcpy clean diff --git a/emulator-asm/src/dma/direct_inpucpy_mtrace.asm b/emulator-asm/src/dma/direct_inpucpy_mtrace.asm new file mode 100644 index 000000000..6ab88977f --- /dev/null +++ b/emulator-asm/src/dma/direct_inpucpy_mtrace.asm @@ -0,0 +1,314 @@ +.intel_syntax noprefix +.code64 + +# +# inputcpy_mtrace - Optimized version with memory tracing and actual copy +# +# This function performs three main tasks: +# 1. Encodes inputcpy metadata (offsets, counts, flags) using fast_dma_encode +# 2. Records memory trace (pre-values and src data for verification/rollback) +# 3. Performs the actual memory copy from src to dst (with overlap handling) +# +# REGISTER USAGE: +# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r9, r12, r13 +# Does NOT use XMM registers (caller doesn't need to save them) +# Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) +# +# PARAMETERS (NON System V AMD64 ABI): +# rdi = dst (u64) - Destination address +# rsi = reserved - Input source +# rdx = count (usize) - Number of bytes to copy +# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) +# +# RETURN VALUE: +# RAX = Number of 64-bit words written to trace buffer +# +# MEMORY TRACE FORMAT (written to trace buffer sequentially): +# [0] = Encoded metadata (64-bit value with offsets, counts, flags) +# [1] = Pre-write value at aligned(dst) IF pre_count > 0 +# [1 or 2] = Post-write value at aligned(dst+count) IF post_count > 0 +# [...] = All aligned qwords from aligned(src) to aligned(src+count) +# +# The trace buffer captures: +# - Original destination values (for undo/verification) +# - Source data (for verification) +# - Metadata needed to reconstruct the operation +# +# MEMORY COPY BEHAVIOR: +# - Handles overlapping src/dst correctly (like memmove) +# - For non-overlapping: optimized copy using pre_count/loop_count/post_count +# - For overlapping: backward byte-by-byte copy to avoid corruption +# +# PERFORMANCE: +# - Encoding: ~15-20 cycles (function call to fast_dma_encode, table lookup) +# - Trace writes: ~4 cycles per qword write +# - Src data copy to trace: ~1.5-2 cycles per qword (rep movsq) +# - Final inputcpy (non-overlap): ~1.5-2 cycles per qword (rep movsq aligned) +# - Final inputcpy (overlap): ~100-150 cycles overhead + ~4-5 cycles per byte (std/rep movsb/cld) +# +# SIDE EFFECTS: +# - Modifies memory at dst (count bytes) +# - Modifies trace buffer (variable size depending on pre/post counts) +# - Preserves direction flag (cld called after any std) + +.global direct_dma_inputcpy_mtrace +.global dma_inputcpy_mtrace +.global direct_dma_inputcpy_mtrace_with_count_check + +.extern fast_dma_encode_inputcpy +.extern trace_address_threshold +# .extern trace_resize_request + +.ifdef DEBUG +.section .data +.align 8 + dma_check_case: .quad 0 + dma_check_step: .quad 0 + dma_check_aux: .quad 0 + dma_check_threshold: .quad 0 +.endif + +.include "dma_constants.inc" + +.section .text + +.set R_MT_INDEX, r13 +.set R_MT_ADDR, r12 +.set R_STEP, r14 +.set R_AUX, r9 +.set R_AUX2, rcx # NOTE: used by rep +.set R_SRC, rsi # NOTE: used by rep +.set R_DST, rdi # NOTE: used by rep +.set R_COUNT, rdx +.set R_ENCODE, rax + +dma_inputcpy_mtrace: + push R_MT_ADDR # ~3 cycles - save callee-saved register + push R_MT_INDEX # ~3 cycles - save callee-saved register + push R_AUX # ~3 cycles - save callee-saved register + push rbx # ~3 cycles - save callee-saved register + + mov R_MT_ADDR, R_COUNT # 1 cycle - setup trace address from count + xor R_MT_INDEX, R_MT_INDEX # 1 cycle - initialize trace index to 0 + call direct_dma_inputcpy_mtrace # ~5 cycles + function cost + + mov R_ENCODE, R_MT_INDEX # 1 cycle - return trace index in R_ENCODE + pop rbx # ~3 cycles - restore register + pop R_AUX # ~3 cycles - restore register + pop R_MT_INDEX # ~3 cycles - restore register + pop R_MT_ADDR # ~3 cycles - restore register + + ret # ~5 cycles + +.L_inputcpy_check_mtrace_available: + + # trace_address_threshold containt the address "limit" before call _realloc_trace + # trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE + + # calculate bytes of mtrace used and verify if throw the limit + +.ifdef DEBUG + mov qword ptr [dma_check_case], 1 +.endif + + lea R_AUX, [R_MT_ADDR + 8 * R_MT_INDEX] # 1 cycle - calculate address mtrace + lea R_AUX, [R_AUX + R_COUNT + MAX_DMA_MT_MARGIN] # 1 cycle - calculate current mtrace bytes usage + sub R_AUX, [trace_address_threshold] # ~4 cycles - bytes over threshold (can be negative) + jc .L_inputcpy_mtrace_continue # 2 cycles (predicted) - negative means space available + + # check if bytes over threshold are usual for current situation on inside chunk + # R_STEP contain number the steps to end of chunk, we need number the steps consumed + +.ifdef DEBUG + mov qword ptr [dma_check_case], 2 + mov [dma_check_step], R_STEP + mov [dma_check_aux], R_AUX +.endif + + mov R_AUX2, CHUNK_SIZE # 1 cycle - load chunk size constant + sub R_AUX2, R_STEP # 1 cycle - calculate steps consumed in chunk + imul R_AUX2, MAX_BYTES_MTRACE_STEP # ~3 cycles - bytes expected for consumed steps + cmp R_AUX2, R_AUX # 1 cycle - compare expected vs actual + jae .L_inputcpy_mtrace_continue # 2 cycles (predicted) - expected >= actual, ok + + + # at this point we need to increase trace, registers R_ENCODE, R_AUX no need to save. +.ifdef DEBUG + mov qword ptr [dma_check_case], 3 + mov R_AUX, [trace_address_threshold] + mov [dma_check_threshold], R_AUX +.endif + + # mov qword ptr [trace_resize_request], R_COUNT + + push R_COUNT # ~3 cycles - save general purpose registers + push r8 # ~3 cycles + push r10 # ~3 cycles + push r11 # ~3 cycles + push R_SRC # ~3 cycles + push R_DST # ~3 cycles + + # IMPORTANT: inside call means unaligned to 16 bits + + sub rsp, 16*16 + 8 # 1 cycle - allocate stack space for 16 XMM registers + + movaps [rsp + 0*16], xmm0 # ~4 cycles - save XMM registers (aligned stores) + movaps [rsp + 1*16], xmm1 # ~4 cycles + movaps [rsp + 2*16], xmm2 # ~4 cycles + movaps [rsp + 3*16], xmm3 # ~4 cycles + movaps [rsp + 4*16], xmm4 # ~4 cycles + movaps [rsp + 5*16], xmm5 # ~4 cycles + movaps [rsp + 6*16], xmm6 # ~4 cycles + movaps [rsp + 7*16], xmm7 # ~4 cycles + movaps [rsp + 8*16], xmm8 # ~4 cycles + movaps [rsp + 9*16], xmm9 # ~4 cycles + movaps [rsp + 10*16], xmm10 # ~4 cycles + movaps [rsp + 11*16], xmm11 # ~4 cycles + movaps [rsp + 12*16], xmm12 # ~4 cycles + movaps [rsp + 13*16], xmm13 # ~4 cycles + movaps [rsp + 14*16], xmm14 # ~4 cycles + movaps [rsp + 15*16], xmm15 # ~4 cycles + + call _realloc_trace # ~5 cycles + function cost (~100-500 cycles) + + movaps xmm0, [rsp + 0*16] # ~4 cycles - restore XMM registers (aligned loads) + movaps xmm1, [rsp + 1*16] # ~4 cycles + movaps xmm2, [rsp + 2*16] # ~4 cycles + movaps xmm3, [rsp + 3*16] # ~4 cycles + movaps xmm4, [rsp + 4*16] # ~4 cycles + movaps xmm5, [rsp + 5*16] # ~4 cycles + movaps xmm6, [rsp + 6*16] # ~4 cycles + movaps xmm7, [rsp + 7*16] # ~4 cycles + movaps xmm8, [rsp + 8*16] # ~4 cycles + movaps xmm9, [rsp + 9*16] # ~4 cycles + movaps xmm10, [rsp + 10*16] # ~4 cycles + movaps xmm11, [rsp + 11*16] # ~4 cycles + movaps xmm12, [rsp + 12*16] # ~4 cycles + movaps xmm13, [rsp + 13*16] # ~4 cycles + movaps xmm14, [rsp + 14*16] # ~4 cycles + movaps xmm15, [rsp + 15*16] # ~4 cycles + + add rsp, 16*16 +8 # 1 cycle - deallocate stack space + + pop R_DST # ~3 cycles - restore general purpose registers + pop R_SRC # ~3 cycles + pop r11 # ~3 cycles + pop r10 # ~3 cycles + pop r8 # ~3 cycles + pop R_COUNT # ~3 cycles + + jmp .L_inputcpy_mtrace_continue + +direct_dma_inputcpy_mtrace_with_count_check: + + # Call fast_dma_encode to calculate encoding + # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count + # Result will be returned in R_ENCODE (encoded value) + + cmp R_COUNT, MAX_DMA_BYTES_DIRECT_MTRACE # 1 cycle - check if count exceeds direct threshold + ja .L_inputcpy_check_mtrace_available # 2 cycles (not taken usually) - large count, check trace space + +.L_inputcpy_mtrace_continue: +direct_dma_inputcpy_mtrace: + + # Call fast_dma_encode to calculate encoding + # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count + # Result will be returned in R_ENCODE (encoded value) + + call fast_dma_encode_inputcpy + + mov [R_MT_ADDR + R_MT_INDEX * 8], R_ENCODE # ~4 cycles - write encoded result to mem trace + inc R_MT_INDEX # 1 cycle - advance R_MT_INDEX (mem trace index) + +.L_pre_dst_to_mtrace: + # If pre_count > 0, write aligned dst value to trace + test R_ENCODE, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_post_dst_to_mtrace # 2 cycles (predicted taken) + + # Branch with pre_count > 0: save original dst value before it's overwritten + mov R_AUX, R_DST # 1 cycle - get original dst + and R_AUX, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov R_AUX, [R_AUX] # ~4 cycles - read qword from aligned dst + mov [R_MT_ADDR + R_MT_INDEX * 8], R_AUX # ~4 cycles - write dst pre-value to trace + inc R_MT_INDEX # 1 cycle - advance trace index + +.L_post_dst_to_mtrace: + + # If post_count > 0, write aligned (dst+count) value to trace + test R_ENCODE, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_input_to_mtrace # 2 cycles (predicted taken) - skip to input copy + + lea R_AUX, [R_DST + R_COUNT - 1] # 1 cycle - R_AUX = dst + count - 1 (last dst byte) + and R_AUX, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov R_AUX, [R_AUX] # ~4 cycles - read qword at (dst+count) aligned + mov [R_MT_ADDR + R_MT_INDEX * 8], R_AUX # ~4 cycles - write dst post-value to trace + inc R_MT_INDEX # 1 cycle - advance trace index + +.L_input_to_mtrace: + # Copy input data to trace buffer + # Total qwords = loop_count (bits 0-31) + extra_src_reads (bits 48-50) + + mov R_AUX2, R_ENCODE # 1 cycle - R_AUX2 = encoded + shr R_AUX2, LOOP_COUNT_RS # 1 cycle - R_AUX2 = loop_count (bits 32-63) + + mov R_AUX, R_ENCODE # 1 cycle - R_AUX = encoded + shr R_AUX, EXTRA_SRC_READS_RS # 1 cycle - shift extra_src_reads to position + and R_AUX, 0x03 # 1 cycle - R_AUX = extra_src_reads (bits 48-50) + add R_AUX2, R_AUX # 1 cycle - R_AUX2 = total qwords to copy + + mov R_AUX, qword ptr [fcall_ctx + FCALL_RESULT_GOT * 8] + lea R_SRC, [fcall_ctx + rcx * 8 + FCALL_RESULT * 8 - 8] + add qword ptr [fcall_ctx + FCALL_RESULT_GOT * 8], R_AUX2 + + mov R_AUX, R_SRC + push R_DST # ~3 cycles - save dst pointer + lea R_DST, [R_MT_ADDR + R_MT_INDEX * 8] # 1 cycle - R_DST = trace buffer destination + add R_MT_INDEX, R_AUX2 # 1 cycle - advance trace index by qwords copied + + call fast_memcpy64 + # rep movsq # ~1.5-2 cycles per qword (hardware optimized) + + pop R_DST # ~3 cycles - restore dst pointer + mov R_SRC, R_AUX # 1 cycle - restore original src pointer + + mov R_AUX2, R_COUNT + call fast_memcpy +.L_done: + ret # ~5 cycles + +# Performance estimate (Modern x86-64, L1 cache hits): +# +# NON-OVERLAPPING FORWARD COPY PATH: +# - fast_dma_encode call: ~15-20 cycles (function call + table lookup) +# - Write encoding to trace: ~4 cycles +# - Pre-value trace (conditional): ~12 cycles (if pre_count > 0) +# - Post-value trace (conditional): ~12 cycles (if post_count > 0) +# - Source data to trace: ~1.5-2 cycles per qword (rep movsq) +# - Pre-bytes copy: ~3-5 cycles per byte (if pre_count > 0, max 7 bytes) +# - Aligned qwords copy: ~1.5-2 cycles per qword (rep movsq, main data) +# - Post-bytes copy: ~3-5 cycles per byte (if post_count > 0, max 7 bytes) +# - Function overhead: ~10 cycles (push/pop, branches, return) +# +# TOTAL (best case, aligned, no pre/post): +# ~30 cycles base + ~2 cycles per qword (trace + copy) +# +# TOTAL (typical case, some alignment): +# ~50 cycles base + ~2 cycles per qword + ~4 cycles per pre/post byte +# +# OVERLAPPING BACKWARD COPY PATH: +# - Same trace overhead: ~30-50 cycles +# - std instruction: ~20-50 cycles (serializing, causes pipeline flush) +# - Backward byte copy: ~3-5 cycles per byte (rep movsb backward) +# - cld instruction: ~20-50 cycles (serializing, causes pipeline flush) +# +# TOTAL (overlap, worst case): +# ~100-150 cycles base + ~4-5 cycles per byte +# +# NOTES: +# - Assumes L1 cache hits for all memory accesses +# - rep movsq/movsb performance varies by microarchitecture +# - Actual cycles may vary ±20% depending on CPU model and memory alignment +# - Fast path (aligned, no overlap) is ~2-3x faster than overlap path + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/direct_memcpy_mops.asm b/emulator-asm/src/dma/direct_memcpy_mops.asm index e9df164b9..7d1d14ba7 100644 --- a/emulator-asm/src/dma/direct_memcpy_mops.asm +++ b/emulator-asm/src/dma/direct_memcpy_mops.asm @@ -34,9 +34,6 @@ .extern fast_dma_encode .include "dma_constants.inc" -.equ MOPS_ALIGNED_READ_2W, ((2 << MOPS_BLOCK_WORDS_RS) + MOPS_ALIGNED_BLOCK_READ) -.equ LOOP_COUNT_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_RS - LOOP_COUNT_RS) -.equ PRE_WRITES_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_RS - PRE_WRITES_RS) .section .text diff --git a/emulator-asm/src/dma/direct_xinput_mops.asm b/emulator-asm/src/dma/direct_xinput_mops.asm new file mode 100644 index 000000000..bf9927f63 --- /dev/null +++ b/emulator-asm/src/dma/direct_xinput_mops.asm @@ -0,0 +1,268 @@ +.intel_syntax noprefix +.code64 + +################################################################################ +# inputcpy_mops - Optimized inputcpy with memory ops tracing +# +# This function performs two main tasks: +# 1. Records all addresses of memory operations (read and write addresses) +# 2. Performs the actual inputcpy operation filling with free-inputs +# +# REGISTER USAGE: +# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r8, r9, r12, r13 +# Does NOT use XMM registers (caller doesn't need to save them) +# Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) +# +# PARAMETERS (NON System V AMD64 ABI): +# rdi = dst (u64) - Destination address to fill +# rsi = value (u8 in low byte) - Byte value to set (0-255) +# rdx = count (usize) - Number of bytes to set +# r12 = mops_base_addr (u64*) - Pointer to memory ops trace buffer base +# r13 = mops_index (usize) - Current index in mops buffer (input/output) +# +################################################################################ + +.global direct_dma_inputcpy_mops +.global dma_inputcpy_mops +.extern fast_memcpy +.extern fcall_ctx + +.include "dma_constants.inc" + +.section .text + +# Standard ABI wrapper that saves/restores callee-saved registers +# and initializes mops tracking state before calling direct implementation + +# rdi = destination +# rsi = count (bytes) +# rdx = pointer to data trace +# rax = return qword of trace + +dma_inputcpy_mops: + + # Save callee-saved registers + push r12 # ~3 cycles - save r12 (used as mops base address) + push r13 # ~3 cycles - save r13 (used as mops index) + + mov r12, rdx # 1 cycle - r12 = mops buffer base address + xor r13, r13 # 1 cycle - r13 = 0 (initialize mops index) + mov rdx, rsi + + call direct_dma_inputcpy_mops # ~5 cycles + function cost + + mov rax, r13 # 1 cycle - return mops count in rax + pop r13 # ~3 cycles - restore r13 + pop r12 # ~3 cycles - restore r12 + + ret # ~5 cycles + +# Direct entry point for assembly callers (no ABI overhead) +# More efficient when caller manages register preservation + +# arguments: +# rdi: destination adress +# rdx: count (bytes) +# r12 + r13: mops trace + +direct_dma_inputcpy_mops: + + # Modified registers (caller must handle): + # r9 = scratch for mops address calculation + # rcx = mops index (incremented, output) + + # test count = 0 + test rdx, rdx + jz .L_inputcpy_mops_done + + # test dst aligned + test rdi, 0x7 + jnz .L_inputcpy_mops_rdi_unaligned + + # test count multiple of 8 + test rdx, 0x07 + jnz .L_inputcpy_mops_count_remain + + # FAST BRANCH + # dst is aligned, count is a multiple of 8 and greater than zero + # => no pre-reads, only one MOPS write block + + # FAST BRANCH - MOPS (MOPS_ALIGNED_BLOCK_WRITE) + + mov rax, rdx + shr rax, 3 + shl rax, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position + mov r9, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags + add r9, rax + add r9, rdi # rdi aligned + + mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry (block write) + inc r13 # 1 cycle - advance mops index + + jmp .L_fast_inputcpy + +.L_inputcpy_mops_count_remain: + # BRANCH 1 + # dst is aligned, but count is NOT a multiple of 8, + # => one pre-read (post) before one MOPS write block + # NOTE: if count < 8 no problem, because you need to do read and write. + + # BRANCH 1 - MOPS (MOPS_ALIGNED_READ (POST) + MOPS_ALIGNED_BLOCK_WRITE) + + # BRANCH 1 - common MOPS part + + lea r9, [rdx + 7] + shr r9, 3 + + # BRANCH 1 - specific MOPS pre-read part + + lea rcx, [rdi + r9 * 8 - 8] + mov rax, MOPS_ALIGNED_READ + add rcx, rax + mov [r12 + r13 * 8], rcx + + # BRANCH 1 - specific MOPS block write + # set rcx = qwords to write + + shl r9, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position + mov rax, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags + add rax, r9 + add rax, rdi # rdi is aligned in this path + + mov [r12 + r13 * 8 + 8], rax # ~4 cycles - write mops entry (block write) + add r13, 2 + + jmp .L_fast_inputcpy + +.L_inputcpy_mops_rdi_unaligned: + # BRANCH 2 - worse + # dst is NOT aligned + # => BRANCH 2.1 one pre-read (pre) + no post + # => BRANCH 2.2 one pre-read (pre) + second post pre-read + + # [EC] only PRE but [rdi + rdx] & 0x07 !== 0 + mov rcx, rdi + and rcx, 0x07 + lea rcx, [rcx + rdx + 7] # optimization to be used in this branch + test rcx, 0xFFFFFFFFFFFFFFF0 # (rcx + rdx) > 8 => (rcx + rdx + 7) > 15 => + # (rcx + rdx + 7) & 0xF..F0 != 0 + jnz .L_pre_branch_2_2 + jmp .L_branch_2_1 + +.L_pre_branch_2_2: + lea rax, [rcx - 7] + test rax, 0x7 + jnz .L_branch_2_2 + +.L_branch_2_1: + + # BRANCH 2.1 - MOPS (MOPS_ALIGNED_READ (PRE) + MOPS_ALIGNED_BLOCK_WRITE) + + # BRANCH 2.1 - specific MOPS block write + # NOTE: at least one qword because count > 0 + # rcx = (rdi & 0x7) + rdx + 7 ==> (rcx >> 3) qwords + mov rax, rdi + and rax, ALIGN_MASK + shr rcx, 3 + shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position + mov r9, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags + add rcx, r9 + add rcx, rax + + mov [r12 + r13 * 8 + 8], rcx # ~4 cycles - write mops entry (block write) + + # BRANCH 2.1 - specific MOPS pre-read part PRE + # rax = rdi & ALIGN_MASK + + mov rcx, MOPS_ALIGNED_READ + add rcx, rax + mov [r12 + r13 * 8], rcx + add r13, 2 + + jmp .L_fast_inputcpy + +.L_branch_2_2: + + # BRANCH 2.2 - MOPS (2xMOPS_ALIGNED_READ (PRE/POST) + MOPS_ALIGNED_BLOCK_WRITE) + # BRANCH 2.2 - specific MOPS pre-read part PRE + # rcx = (rdi & 0x7) + rdx + 7 ==> (rcx >> 3) qwords + + shr rcx, 3 + shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position + mov rax, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags + add rax, rcx # rax = MOPS_ALIGNED_BLOCK_WRITE | (count_q << MOPS_BLOCK_WORDS_RS) + mov rcx, rdi + and rcx, ALIGN_MASK + add rax, rcx # rax |= rdi & ALIGN MASK + + mov [r12 + r13 * 8 + 16], rax # ~4 cycles - write mops entry (block write) + + # rcx = rdi & ALIGN_MASK + # BRANCH 2.2 - PRE write + + mov rax, MOPS_ALIGNED_READ + + add rcx, rax # rcx = rdi & ALIGN_MASK + mov [r12 + r13 * 8], rcx + + # BRANCH 2.2 - POST write + + lea r9, [rdi + rdx] + and r9, ALIGN_MASK + add r9, rax + mov [r12 + r13 * 8 + 8], r9 + + add r13, 3 + + # rsi = input + mops + # incr mops + +.L_fast_inputcpy: + mov rcx, qword ptr [fcall_ctx + FCALL_RESULT_GOT * 8] + lea rsi, [fcall_ctx + rcx * 8 + FCALL_RESULT * 8 - 8] + lea rcx, [rdx + 7] + shr rcx, 3 + add qword ptr [fcall_ctx + FCALL_RESULT_GOT * 8], rcx + + jmp fast_memcpy + +.L_inputcpy_mops_done: + ret + + + +# Performance estimate (Modern x86-64, Intel Skylake/AMD Zen+, L1 cache hits): +# +# MEMSET OPERATION WITH MOPS TRACING: +# - fast_dma_encode call: ~15-20 cycles (function call + table lookup) +# - Pre-read mops entry: ~8-10 cycles (if pre_count > 0: calc + and + store + inc) +# - Post-read mops entry: ~10-12 cycles (if post_count > 0: lea + and + add + store + inc) +# - Block write mops entry: ~12-15 cycles (extract + shift + combine + store + inc) +# - Byte value expansion: ~5-6 cycles (movzx + mov + imul) +# - Qword fill (rep stosq): ~0.5-1.0 cycles per qword (ERMSB optimization) +# - Remaining bytes (rep stosb): ~1.0-2.0 cycles per byte (0-7 bytes) +# - Function overhead: ~3-5 cycles (branches, return) +# +# TOTAL (typical case, 64 bytes, aligned, no pre/post): +# ~15 (encode) + ~15 (block mops) + ~6 (expand) + 8*0.75 (fill) + ~4 (overhead) +# = ~46 cycles (~1.39 GB/s @ 3 GHz) +# +# TOTAL (misaligned case, 64 bytes with pre/post): +# ~15 (encode) + ~10 (pre) + ~12 (post) + ~15 (block) + ~6 (expand) + 7*0.75 + 4*1.5 (fill) + ~4 +# = ~73 cycles (~0.88 GB/s @ 3 GHz) +# +# TOTAL (large fill, 4096 bytes, aligned): +# ~15 (encode) + ~15 (mops) + ~6 (expand) + 512*0.5 (fill) + ~4 (overhead) +# = ~296 cycles (~13.8 GB/s @ 3 GHz, approaching L1D bandwidth) +# +# NOTES: +# - Assumes L1D cache hits for all memory accesses (~4 cycle latency, ~64 GB/s bandwidth) +# - rep stosq/stosb uses Enhanced REP MOVSB/STOSB (ERMSB) on modern CPUs (post-2013) +# - ERMSB enables microcode to use wide stores (16-64 bytes per iteration internally) +# - For fills >256 bytes, performance approaches memory bandwidth limits +# - Actual cycles vary ±20-30% by microarchitecture (Skylake/Zen/Alder Lake) +# - Mops overhead: ~30-50 cycles base + minimal per-byte impact +# - No overlap handling needed for inputcpy (writes only, no read-modify-write hazards) + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/direct_xmemset_mops.asm b/emulator-asm/src/dma/direct_xmemset_mops.asm index d0d169447..36987398a 100644 --- a/emulator-asm/src/dma/direct_xmemset_mops.asm +++ b/emulator-asm/src/dma/direct_xmemset_mops.asm @@ -24,28 +24,32 @@ .global direct_dma_xmemset_mops .global dma_xmemset_mops -.extern fast_dma_encode +.extern fast_memset .include "dma_constants.inc" - .section .text # Standard ABI wrapper that saves/restores callee-saved registers # and initializes mops tracking state before calling direct implementation + +# rdi = destination +# rsi: byte to write +# rdx: count (bytes) +# rcx = pointer to data trace +# rax = return qword of trace + dma_xmemset_mops: # Save callee-saved registers push r12 # ~3 cycles - save r12 (used as mops base address) push r13 # ~3 cycles - save r13 (used as mops index) - push rbx # ~3 cycles - save rbx mov r12, rcx # 1 cycle - r12 = mops buffer base address xor r13, r13 # 1 cycle - r13 = 0 (initialize mops index) call direct_dma_xmemset_mops # ~5 cycles + function cost mov rax, r13 # 1 cycle - return mops count in rax - pop rbx # ~3 cycles - restore rbx pop r13 # ~3 cycles - restore r13 pop r12 # ~3 cycles - restore r12 @@ -54,94 +58,165 @@ dma_xmemset_mops: # Direct entry point for assembly callers (no ABI overhead) # More efficient when caller manages register preservation +# arguments: +# rdi: destination adress +# rsi: byte to write +# rdx: count (bytes) +# r12 + r13: mops trace + direct_dma_xmemset_mops: # Modified registers (caller must handle): - # rax = encoded metadata (output from fast_dma_encode) - # rcx = scratch register (loop counter) - # rdi = destination pointer (modified by rep stosq/stosb) - # rsi = value preserved until expansion - # r8 = scratch for value expansion # r9 = scratch for mops address calculation - # r13 = mops index (incremented, output) - - # Call fast_dma_encode to calculate encoding - # Parameters already in correct registers: rdi=dst, rsi=value, rdx=count - # Result will be returned in rax (encoded metadata) - - call fast_dma_encode # ~15-20 cycles - table lookup encoding - - # Check if count is zero - test rdx, rdx # 1 cycle - check if count == 0 - jz .L_done # 2 cycles (unlikely) - nothing to do - -.L_pre_dst_to_mops: - # If pre_count > 0, record aligned dst read address to mops - test rax, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 - jz .L_post_dst_to_mops # 2 cycles (predicted taken) - -.L_pre_is_active: - # Record aligned read of dst qword that will be partially overwritten - mov r9, MOPS_ALIGNED_READ # 1 cycle - r9 = flags for aligned read - add r9, rdi # 1 cycle - r9 = flags + dst address - and r9, ALIGN_MASK # 1 cycle - align address to 8-byte boundary - mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry (aligned read) - inc r13 # 1 cycle - advance mops index - -.L_post_dst_to_mops: - - # If post_count > 0, record aligned (dst+count) read address to mops - test rax, POST_COUNT_MASK # 1 cycle - check if post_count > 0 - jz .L_src_to_mops # 2 cycles (predicted taken) - skip if no post bytes - -.L_post_is_active: - # Record aligned read of dst qword at end that will be partially overwritten - mov rcx, MOPS_ALIGNED_READ # 1 cycle - rcx = flags for aligned read - lea r9, [rdi + rdx - 1] # 1 cycle - r9 = dst + count - 1 (last byte address) - and r9, ALIGN_MASK # 1 cycle - align address to 8-byte boundary - add r9, rcx # 1 cycle - r9 = flags + aligned address - mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry (aligned read) - inc r13 # 1 cycle - advance mops index - -.L_src_to_mops: - # Record block write operation (aligned writes to dst) - mov rcx, rax # 1 cycle - rcx = encoded metadata - shr rcx, LOOP_COUNT_RS # 1 cycle - extract loop_count (qwords to write) - shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position - - mov r9, rax # 1 cycle - r9 = encoded metadata - and r9, PRE_WRITES_MASK # 1 cycle - extract pre_writes count - shl r9, PRE_WRITES_TO_MOPS_BLOCK # 1 cycle - shift pre_writes to correct position - add r9, rcx # 1 cycle - combine loop_count and pre_writes - add r9, rdi # 1 cycle - add base dst address - - mov rcx, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags - add r9, rcx # 1 cycle - r9 = flags + address + counts - and r9, ALIGN_MASK # 1 cycle - align address field - - mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry (block write) - inc r13 # 1 cycle - advance mops index - jmp .L_mops_done # ~2 cycles - skip to memset - -.L_mops_done: - - # Perform actual memset: fill dst with byte value using rep stosq + rep stosb - # This is the optimal approach on modern x86-64 (no alignment overhead needed) - - movzx rax, sil # 1 cycle - rax = byte value (zero-extended) - mov r8, 0x0101010101010101 # 1 cycle - r8 = replication pattern - imul rax, r8 # ~3 cycles - rax = byte value replicated 8 times - - mov rcx, rdx # 1 cycle - rcx = count (total bytes) - shr rcx, 3 # 1 cycle - rcx = count / 8 (qwords to write) - rep stosq # ~0.5-1.0 cycles per qword (fast string ops) + # rcx = mops index (incremented, output) + + # test count = 0 + test rdx, rdx + jz .L_xmemset_mops_done + + # test dst aligned + test rdi, 0x7 + jnz .L_xmemset_mops_rdi_unaligned + + # test count multiple of 8 + test rdx, 0x07 + jnz .L_memset_mops_count_remain + + # FAST BRANCH + # dst is aligned, count is a multiple of 8 and greater than zero + # => no pre-reads, only one MOPS write block + + # FAST BRANCH - MOPS (MOPS_ALIGNED_BLOCK_WRITE) + + mov rax, rdx + shr rax, 3 + shl rax, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position + mov r9, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags + add r9, rax + add r9, rdi # rdi aligned + + mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry (block write) + inc r13 # 1 cycle - advance mops index + + jmp fast_memset + +.L_memset_mops_count_remain: + # BRANCH 1 + # dst is aligned, but count is NOT a multiple of 8, + # => one pre-read (post) before one MOPS write block + # NOTE: if count < 8 no problem, because you need to do read and write. + + # BRANCH 1 - MOPS (MOPS_ALIGNED_READ (POST) + MOPS_ALIGNED_BLOCK_WRITE) + + # BRANCH 1 - common MOPS part + + lea r9, [rdx + 7] + shr r9, 3 + + # BRANCH 1 - specific MOPS pre-read part + + lea rcx, [rdi + r9 * 8 - 8] + mov rax, MOPS_ALIGNED_READ + add rcx, rax + mov [r12 + r13 * 8], rcx + + # BRANCH 1 - specific MOPS block write + # set rcx = qwords to write + + shl r9, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position + mov rax, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags + add rax, r9 + add rax, rdi # rdi is aligned in this path + + mov [r12 + r13 * 8 + 8], rax # ~4 cycles - write mops entry (block write) + add r13, 2 + + jmp fast_memset + +.L_xmemset_mops_rdi_unaligned: + # BRANCH 2 - worse + # dst is NOT aligned + # => BRANCH 2.1 one pre-read (pre) + no post + # => BRANCH 2.2 one pre-read (pre) + second post pre-read - mov rcx, rdx # 1 cycle - rcx = count (original) - and rcx, 7 # 1 cycle - rcx = count % 8 (remaining bytes) - rep stosb # ~1.0-2.0 cycles per byte (remaining 0-7 bytes) + # [EC] only PRE but [rdi + rdx] & 0x07 !== 0 + mov rcx, rdi + and rcx, 0x07 + lea rcx, [rcx + rdx + 7] # optimization to be used in this branch + test rcx, 0xFFFFFFFFFFFFFFF0 # (rcx + rdx) > 8 => (rcx + rdx + 7) > 15 => + # (rcx + rdx + 7) & 0xF..F0 != 0 + jnz .L_pre_branch_2_2 + jmp .L_branch_2_1 + +.L_pre_branch_2_2: + lea rax, [rcx - 7] + test rax, 0x7 + jnz .L_branch_2_2 + +.L_branch_2_1: + + # BRANCH 2.1 - MOPS (MOPS_ALIGNED_READ (PRE) + MOPS_ALIGNED_BLOCK_WRITE) + + # BRANCH 2.1 - specific MOPS block write + # NOTE: at least one qword because count > 0 + # rcx = (rdi & 0x7) + rdx + 7 ==> (rcx >> 3) qwords + mov rax, rdi + and rax, ALIGN_MASK + shr rcx, 3 + shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position + mov r9, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags + add rcx, r9 + add rcx, rax + + mov [r12 + r13 * 8 + 8], rcx # ~4 cycles - write mops entry (block write) + + # BRANCH 2.1 - specific MOPS pre-read part PRE + # rax = rdi & ALIGN_MASK + + mov rcx, MOPS_ALIGNED_READ + add rcx, rax + mov [r12 + r13 * 8], rcx + add r13, 2 + + jmp fast_memset + +.L_branch_2_2: + + # BRANCH 2.2 - MOPS (2xMOPS_ALIGNED_READ (PRE/POST) + MOPS_ALIGNED_BLOCK_WRITE) + # BRANCH 2.2 - specific MOPS pre-read part PRE + # rcx = (rdi & 0x7) + rdx + 7 ==> (rcx >> 3) qwords + + shr rcx, 3 + shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position + mov rax, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags + add rax, rcx # rax = MOPS_ALIGNED_BLOCK_WRITE | (count_q << MOPS_BLOCK_WORDS_RS) + mov rcx, rdi + and rcx, ALIGN_MASK + add rax, rcx # rax |= rdi & ALIGN MASK + + mov [r12 + r13 * 8 + 16], rax # ~4 cycles - write mops entry (block write) + + # rcx = rdi & ALIGN_MASK + # BRANCH 2.2 - PRE write + + mov rax, MOPS_ALIGNED_READ + + add rcx, rax # rcx = rdi & ALIGN_MASK + mov [r12 + r13 * 8], rcx + + # BRANCH 2.2 - POST write + + lea r9, [rdi + rdx] + and r9, ALIGN_MASK + add r9, rax + mov [r12 + r13 * 8 + 8], r9 + + add r13, 3 + + jmp fast_memset +.L_xmemset_mops_done: + ret -.L_done: - ret # ~5 cycles # Performance estimate (Modern x86-64, Intel Skylake/AMD Zen+, L1 cache hits): # diff --git a/emulator-asm/src/dma/direct_xmemset_mtrace.asm b/emulator-asm/src/dma/direct_xmemset_mtrace.asm index 95105e76c..93d5054ce 100644 --- a/emulator-asm/src/dma/direct_xmemset_mtrace.asm +++ b/emulator-asm/src/dma/direct_xmemset_mtrace.asm @@ -1,376 +1,279 @@ .intel_syntax noprefix .code64 +################################################################################ +# memset_mtrace - Optimized memset with memory ops tracing # -# xmemcpy_mtrace - Optimized version with memory tracing and actual memset -# -# This function performs three main tasks: -# 1. Encodes memset metadata (offsets, counts, flags) using fast_dma_encode -# 2. Records memory trace (pre-values and src data for verification/rollback) -# 3. Performs the actual memset to dst +# This function performs two main tasks: +# 1. Records all addresses of memory operations (read and write addresses) +# 2. Performs the actual memset operation filling dst with the byte value # # REGISTER USAGE: -# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r9, r12, r13 +# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r8, r9, r12, r13 # Does NOT use XMM registers (caller doesn't need to save them) # Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) # -# PARAMETERS (NON System V AMD64 ABI): -# rdi = dst (u64) - Destination address -# rsi = fill_byte (u64) - Byte to fill -# rdx = count (usize) - Number of bytes to copy -# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) -# -# RETURN VALUE: -# RAX = Number of 64-bit words written to trace buffer +# MTRACE SIZE CONTROL: # -# MEMORY TRACE FORMAT (written to trace buffer sequentially): -# [0] = Encoded metadata (64-bit value with offsets, counts, flags) -# [1] = Pre-write value at aligned(dst) IF pre_count > 0 -# [1 or 2] = Post-write value at aligned(dst+count) IF post_count > 0 -# [...] = All aligned qwords from aligned(src) to aligned(src+count) +# xmemset use at maximum 3 qwords to store encode, pre and post, for this reason this +# function doesn't require analyze if need call to realloc. # -# The trace buffer captures: -# - Original destination values (pre values of pre/post) -# - Metadata needed to reconstruct the operation -# -# PERFORMANCE: -# - Encoding: ~15-20 cycles (function call to fast_dma_encode, table lookup) -# - Trace writes: ~4 cycles per qword write -# - Src data copy to trace: ~1.5-2 cycles per qword (rep movsq) -# - Final memcpy (non-overlap): ~1.5-2 cycles per qword (rep movsq aligned) -# - Final memcpy (overlap): ~100-150 cycles overhead + ~4-5 cycles per byte (std/rep movsb/cld) +# PARAMETERS (NON System V AMD64 ABI): +# rdi = dst (u64) - Destination address to fill +# rsi = value (u8 in low byte) - Byte value to set (0-255) +# rdx = count (usize) - Number of bytes to set +# r12 = mops_base_addr (u64*) - Pointer to memory ops trace buffer base +# r13 = mops_index (usize) - Current index in mops buffer (input/output) # -# SIDE EFFECTS: -# - Modifies memory at dst (count bytes) -# - Modifies trace buffer (variable size depending on pre/post counts) -# - Preserves direction flag (cld called after any std) +################################################################################ .global direct_dma_xmemset_mtrace .global dma_xmemset_mtrace -.global direct_dma_xmemset_mtrace_with_count_check - -.extern fast_dma_encode -.extern trace_address_threshold -# .extern trace_resize_request - -.ifdef DEBUG -.section .data -.align 8 - dma_check_case: .quad 0 - dma_check_step: .quad 0 - dma_check_aux: .quad 0 - dma_check_threshold: .quad 0 -.endif +.extern fast_memset .include "dma_constants.inc" .section .text -.set R_MT_INDEX, r13 -.set R_MT_ADDR, r12 -.set R_STEP, r14 -.set R_AUX, r9 -.set R_AUX2, rcx # NOTE: used by rep -.set R_SRC, rsi # NOTE: used by rep -.set R_DST, rdi # NOTE: used by rep -.set R_COUNT, rdx -.set R_ENCODE, rax +# Standard ABI wrapper that saves/restores callee-saved registers +# and initializes mops tracking state before calling direct implementation + +# rdi = destination +# rsi: byte to write +# rdx: count (bytes) +# rcx = pointer to data trace +# rax = return qword of trace dma_xmemset_mtrace: - push R_MT_ADDR # ~3 cycles - save callee-saved register - push R_MT_INDEX # ~3 cycles - save callee-saved register - push R_AUX # ~3 cycles - save callee-saved register - push rbx # ~3 cycles - save callee-saved register - - mov R_MT_ADDR, R_COUNT # 1 cycle - setup trace address from count - xor R_MT_INDEX, R_MT_INDEX # 1 cycle - initialize trace index to 0 - call direct_dma_xmemset_mtrace # ~5 cycles + function cost - mov R_ENCODE, R_MT_INDEX # 1 cycle - return trace index in R_ENCODE - pop rbx # ~3 cycles - restore register - pop R_AUX # ~3 cycles - restore register - pop R_MT_INDEX # ~3 cycles - restore register - pop R_MT_ADDR # ~3 cycles - restore register - - ret # ~5 cycles - -.L_memcpy_check_mtrace_available: - - # trace_address_threshold containt the address "limit" before call _realloc_trace - # trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE - - # calculate bytes of mtrace used and verify if throw the limit - -.ifdef DEBUG - mov qword ptr [dma_check_case], 1 -.endif - - lea R_AUX, [R_MT_ADDR + 8 * R_MT_INDEX] # 1 cycle - calculate address mtrace - lea R_AUX, [R_AUX + R_COUNT + MAX_DMA_MT_MARGIN] # 1 cycle - calculate current mtrace bytes usage - sub R_AUX, [trace_address_threshold] # ~4 cycles - bytes over threshold (can be negative) - jc .L_memcpy_mtrace_continue # 2 cycles (predicted) - negative means space available - - # check if bytes over threshold are usual for current situation on inside chunk - # R_STEP contain number the steps to end of chunk, we need number the steps consumed - -.ifdef DEBUG - mov qword ptr [dma_check_case], 2 - mov [dma_check_step], R_STEP - mov [dma_check_aux], R_AUX -.endif - - mov R_AUX2, CHUNK_SIZE # 1 cycle - load chunk size constant - sub R_AUX2, R_STEP # 1 cycle - calculate steps consumed in chunk - imul R_AUX2, MAX_BYTES_MTRACE_STEP # ~3 cycles - bytes expected for consumed steps - cmp R_AUX2, R_AUX # 1 cycle - compare expected vs actual - jae .L_memcpy_mtrace_continue # 2 cycles (predicted) - expected >= actual, ok - - - # at this point we need to increase trace, registers R_ENCODE, R_AUX no need to save. -.ifdef DEBUG - mov qword ptr [dma_check_case], 3 - mov R_AUX, [trace_address_threshold] - mov [dma_check_threshold], R_AUX -.endif - - # mov qword ptr [trace_resize_request], R_COUNT - - push R_COUNT # ~3 cycles - save general purpose registers - push r8 # ~3 cycles - push r10 # ~3 cycles - push r11 # ~3 cycles - push R_SRC # ~3 cycles - push R_DST # ~3 cycles - - # IMPORTANT: inside call means unaligned to 16 bits - - sub rsp, 16*16 + 8 # 1 cycle - allocate stack space for 16 XMM registers - - movaps [rsp + 0*16], xmm0 # ~4 cycles - save XMM registers (aligned stores) - movaps [rsp + 1*16], xmm1 # ~4 cycles - movaps [rsp + 2*16], xmm2 # ~4 cycles - movaps [rsp + 3*16], xmm3 # ~4 cycles - movaps [rsp + 4*16], xmm4 # ~4 cycles - movaps [rsp + 5*16], xmm5 # ~4 cycles - movaps [rsp + 6*16], xmm6 # ~4 cycles - movaps [rsp + 7*16], xmm7 # ~4 cycles - movaps [rsp + 8*16], xmm8 # ~4 cycles - movaps [rsp + 9*16], xmm9 # ~4 cycles - movaps [rsp + 10*16], xmm10 # ~4 cycles - movaps [rsp + 11*16], xmm11 # ~4 cycles - movaps [rsp + 12*16], xmm12 # ~4 cycles - movaps [rsp + 13*16], xmm13 # ~4 cycles - movaps [rsp + 14*16], xmm14 # ~4 cycles - movaps [rsp + 15*16], xmm15 # ~4 cycles - - call _realloc_trace # ~5 cycles + function cost (~100-500 cycles) - - movaps xmm0, [rsp + 0*16] # ~4 cycles - restore XMM registers (aligned loads) - movaps xmm1, [rsp + 1*16] # ~4 cycles - movaps xmm2, [rsp + 2*16] # ~4 cycles - movaps xmm3, [rsp + 3*16] # ~4 cycles - movaps xmm4, [rsp + 4*16] # ~4 cycles - movaps xmm5, [rsp + 5*16] # ~4 cycles - movaps xmm6, [rsp + 6*16] # ~4 cycles - movaps xmm7, [rsp + 7*16] # ~4 cycles - movaps xmm8, [rsp + 8*16] # ~4 cycles - movaps xmm9, [rsp + 9*16] # ~4 cycles - movaps xmm10, [rsp + 10*16] # ~4 cycles - movaps xmm11, [rsp + 11*16] # ~4 cycles - movaps xmm12, [rsp + 12*16] # ~4 cycles - movaps xmm13, [rsp + 13*16] # ~4 cycles - movaps xmm14, [rsp + 14*16] # ~4 cycles - movaps xmm15, [rsp + 15*16] # ~4 cycles + # Save callee-saved registers + push r12 # ~3 cycles - save r12 (used as mops base address) + push r13 # ~3 cycles - save r13 (used as mops index) - add rsp, 16*16 +8 # 1 cycle - deallocate stack space - - pop R_DST # ~3 cycles - restore general purpose registers - pop R_SRC # ~3 cycles - pop r11 # ~3 cycles - pop r10 # ~3 cycles - pop r8 # ~3 cycles - pop R_COUNT # ~3 cycles + mov r12, rcx # 1 cycle - r12 = mops buffer base address + xor r13, r13 # 1 cycle - r13 = 0 (initialize mops index) + call direct_dma_xmemset_mtrace # ~5 cycles + function cost - jmp .L_memcpy_mtrace_continue + mov rax, r13 # 1 cycle - return mops count in rax + pop r13 # ~3 cycles - restore r13 + pop r12 # ~3 cycles - restore r12 -direct_dma_xmemset_mtrace_with_count_check: + ret # ~5 cycles - # Call fast_dma_encode to calculate encoding - # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count - # Result will be returned in R_ENCODE (encoded value) +# Direct entry point for assembly callers (no ABI overhead) +# More efficient when caller manages register preservation - cmp R_COUNT, MAX_DMA_BYTES_DIRECT_MTRACE # 1 cycle - check if count exceeds direct threshold - ja .L_memcpy_check_mtrace_available # 2 cycles (not taken usually) - large count, check trace space +# arguments: +# rdi: destination adress +# rsi: byte to write +# rdx: count (bytes) +# r12 + r13: mops trace -.L_memcpy_mtrace_continue: direct_dma_xmemset_mtrace: - # Call fast_dma_encode to calculate encoding - # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count - # Result will be returned in R_ENCODE (encoded value) + # Modified registers (caller must handle): + # r9 = scratch for mops address calculation + # rcx = mops index (incremented, output) - call fast_dma_encode # ~15-20 cycles - table lookup encoding - - mov [R_MT_ADDR + R_MT_INDEX * 8], R_ENCODE # ~4 cycles - write encoded result to mem trace - inc R_MT_INDEX # 1 cycle - advance R_MT_INDEX (mem trace index) - -.L_pre_dst_to_mtrace: - # If pre_count > 0, write aligned dst value to trace - test R_ENCODE, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 - jz .L_post_dst_to_mtrace # 2 cycles (predicted taken) - - # Branch with pre_count > 0: save original dst value before it's overwritten - mov R_AUX, R_DST # 1 cycle - get original dst - and R_AUX, ALIGN_MASK # 1 cycle - align to 8-byte boundary - mov R_AUX, [R_AUX] # ~4 cycles - read qword from aligned dst - mov [R_MT_ADDR + R_MT_INDEX * 8], R_AUX # ~4 cycles - write dst pre-value to trace - inc R_MT_INDEX # 1 cycle - advance trace index - -.L_post_dst_to_mtrace: - - # If post_count > 0, write aligned (dst+count) value to trace - test R_ENCODE, POST_COUNT_MASK # 1 cycle - check if post_count > 0 - jz .L_src_to_mtrace # 2 cycles (predicted taken) - skip to src copy - - lea R_AUX, [R_DST + R_COUNT - 1] # 1 cycle - R_AUX = dst + count - 1 (last dst byte) - and R_AUX, ALIGN_MASK # 1 cycle - align to 8-byte boundary - mov R_AUX, [R_AUX] # ~4 cycles - read qword at (dst+count) aligned - mov [R_MT_ADDR + R_MT_INDEX * 8], R_AUX # ~4 cycles - write dst post-value to trace - inc R_MT_INDEX # 1 cycle - advance trace index - -.L_src_to_mtrace: - # Copy source data to trace buffer - # Total qwords = loop_count (bits 0-31) + extra_src_reads (bits 48-50) + # test count = 0 + test rdx, rdx + jz .L_xmemset_mtrace_count_zero - mov R_AUX2, R_ENCODE # 1 cycle - R_AUX2 = encoded - shr R_AUX2, LOOP_COUNT_RS # 1 cycle - R_AUX2 = loop_count (bits 32-63) - - mov R_AUX, R_ENCODE # 1 cycle - R_AUX = encoded - shr R_AUX, EXTRA_SRC_READS_RS # 1 cycle - shift extra_src_reads to position - and R_AUX, 0x03 # 1 cycle - R_AUX = extra_src_reads (bits 48-50) - add R_AUX2, R_AUX # 1 cycle - R_AUX2 = total qwords to copy - - # Setup for rep movsq: copy from aligned src to trace buffer - mov R_AUX, R_SRC # 1 cycle - preserve original src pointer - and R_SRC, ALIGN_MASK # 1 cycle - R_SRC = src aligned to 8 bytes - - push R_DST # ~3 cycles - save dst pointer - lea R_DST, [R_MT_ADDR + R_MT_INDEX * 8] # 1 cycle - R_DST = trace buffer destination - add R_MT_INDEX, R_AUX2 # 1 cycle - advance trace index by qwords copied - - rep movsq # ~1.5-2 cycles per qword (hardware optimized) + # test dst aligned + test rdi, 0x7 + jnz .L_xmemset_mtrace_rdi_unaligned - pop R_DST # ~3 cycles - restore dst pointer - mov R_SRC, R_AUX # 1 cycle - restore original src pointer - -.L_mtrace_done: - # Check for memory overlap to decide copy direction - # NOTE: R_DST and R_SRC now contain their ORIGINAL values (restored above) - # Overlap exists if: src < dst < src+count (forward overlap) - cmp R_DST, R_SRC # 1 cycle - compare dst with src - jb .L_copy_forward # 2 cycles (predicted) - dst < src, no overlap - lea R_AUX, [R_SRC + R_COUNT] # 1 cycle - R_AUX = src + count - cmp R_DST, R_AUX # 1 cycle - compare dst with (src+count) - jae .L_copy_forward # 2 cycles (predicted) - dst >= src+count, no overlap + # test count multiple of 8 + test rdx, 0x07 + jnz .L_memset_mtrace_count_remain + + # FAST BRANCH + # dst is aligned, count is a multiple of 8 and greater than zero + # => no pre-reads, only encoding + + # FAST BRANCH - MTRACE (ENCODING) + + # FAST DIRECT ENCODING + + # encode loop count, how count is multiple of 8, direct shift + mov r9, rdx + shl r9, PRE_AND_LOOP_BYTES_RS + + # encode fill byte + movzx eax, sil + shl rax, FILL_BYTE_CMP_RES_RS + add rax, r9 + + # store encoded on mtrace + mov [r12 + r13 * 8], rax + inc r13 + + jmp fast_memset + +.L_memset_mtrace_count_remain: + # BRANCH 1 + # dst is aligned, but count is NOT a multiple of 8, + # => one pre-read (post) + # NOTE: if count ∈ [1,7] no problem, because you need to do pre-read + + # BRANCH 1 - MTRACE (ENCODING + ALIGNED_READ (POST) + + # encode fill byte + movzx eax, sil + shl rax, FILL_BYTE_CMP_RES_RS + + # encode post count + mov r9, rdx + and r9, 0x07 + shl r9, POST_COUNT_RS + + # encode = fill byte + post_count + add rax, r9 - # Overlap detected (src < dst < src+count), must copy backward - # Setup: R_SRC = src+count-1, R_DST = dst+count-1, R_AUX2 = count - # Uses ORIGINAL R_SRC and R_DST values (restored from R_AUX and stack) + # encode += template + or rax, ENCODE_MEMSET_ALIGNED_NO_COUNT_M8 + + # encode loop count (count % 8 == 0) + mov r9, rdx + and r9, ALIGN_MASK + shl r9, PRE_AND_LOOP_BYTES_RS + add rax, r9 + + # store encode to mtrace + mov [r12 + r13 * 8], rax + + # unshift loop count, r9 containts count64 + shr r9, PRE_AND_LOOP_BYTES_RS + 3 + + # BRANCH 1 - specific pre-read part + # r9 contains count64 to index need to substract 1*8 + lea rcx, [rdi + r9 * 8 - 8] + mov [r12 + r13 * 8 + 8], rcx + add r13, 2 + + jmp fast_memset + +.L_xmemset_mtrace_rdi_unaligned: + # BRANCH 2 - worse + # dst is NOT aligned + # => BRANCH 2.1 one pre-read (pre) + no post + # => BRANCH 2.2 one pre-read (pre) + second post pre-read - lea R_SRC, [R_SRC + R_COUNT - 1] # 1 cycle - R_SRC = src + count - 1 (from original) - lea R_DST, [R_DST + R_COUNT - 1] # 1 cycle - R_DST = dst + count - 1 (from original) - mov R_AUX2, R_COUNT # 1 cycle - R_AUX2 = count + # [EC] only PRE but [rdi + rdx] & 0x07 !== 0 + + call fast_dma_encode_memset_with_byte + mov [r12 + r13 * 8], rax - std # ~20-50 cycles - set DF (serializing, pipeline flush) - rep movsb # ~3-5 cycles per byte (backward copy, slower than forward) - cld # ~20-50 cycles - clear DF (serializing, pipeline flush) + test rax, PRE_COUNT_MASK + jz .L_xmemset_mtrace_rdi_unaligned_no_pre - ret # ~5 cycles + mov r9, rdi + and r9, ALIGN_MASK + mov rcx, [r9] + mov [r12 + r13 * 8 + 8], rcx -.L_copy_forward: - # No overlap detected, perform optimized forward copy - cmp R_COUNT, 16 # 1 cycle - check if count >= 16 (worth alignment) - jae .L_copy_forward_pre # 2 cycles (predicted) - use 3-phase aligned copy + test rax, POST_COUNT_MASK + jz .L_xmemset_mtrace_rdi_unaligned_pre_no_post - # Small copy (count < 16): copy all bytes directly - mov R_AUX2, R_COUNT # 1 cycle - R_AUX2 = count - rep movsb # ~3-5 cycles per byte (unaligned small copy) + mov rcx, rax + shr rcx, LOOP_COUNT_RS - ret # ~5 cycles + # r9 = dst & 0x07 of previous calculation + # r9 + loop * 8 + 8 for pre part -.L_copy_forward_pre: - # Copy in 3 phases: pre-alignment bytes, aligned qwords, post-alignment bytes + mov rcx, [r9 + 8 + rcx * 8] + mov [r12 + r13 * 8 + 16], rcx + add r13, 3 - # If pre_count > 0, copy unaligned prefix bytes + jmp fast_memset - test R_ENCODE, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 - jz .L_copy_forward_loop # 2 cycles (predicted) +.L_xmemset_mtrace_rdi_unaligned_pre_no_post: + add r13, 2 - # Extract and copy pre_count bytes (1-7 bytes to reach alignment) + jmp fast_memset - mov R_AUX2, R_ENCODE # 1 cycle - and R_AUX2, PRE_COUNT_MASK # 1 cycle - R_AUX2 = pre_count (bits 0-3) +.L_xmemset_mtrace_rdi_unaligned_no_pre: + test rax, POST_COUNT_MASK + jz .L_xmemset_mtrace_rdi_unaligned_no_pre_no_post - rep movsb # ~3-5 cycles per byte - # R_SRC, R_DST now 8-byte aligned + mov r9, rdi + and r9, ALIGN_MASK -.L_copy_forward_loop: - # Copy aligned qwords (main bulk of data) - mov R_AUX2, R_ENCODE # 1 cycle - shr R_AUX2, LOOP_COUNT_RS # 1 cycle - R_AUX2 = loop_count (bits 32-63) - rep movsq # ~1.5-2 cycles per qword (aligned, optimized) - # R_SRC, R_DST advanced by loop_count * 8 + mov rcx, rax + shr rcx, LOOP_COUNT_RS -.L_check_forward_post: + # r9 = dst & 0x07 of previous calculation + # rdi + loop * 8 + 8 for pre part - # If post_count > 0, copy remaining unaligned suffix bytes - test R_ENCODE, POST_COUNT_MASK # 1 cycle - check if post_count > 0 - jz .L_done # 2 cycles (predicted) + mov rcx, [r9 + 8 + rcx * 8] + mov [r12 + r13 * 8 + 8], rcx + add r13, 2 - # Extract and copy post_count bytes (1-7 bytes after aligned data) - mov R_AUX2, R_ENCODE # 1 cycle - shr R_AUX2, POST_COUNT_RS # 1 cycle - shift post_count to position - and R_AUX2, 0x07 # 1 cycle - R_AUX2 = post_count (bits 43-45) + jmp fast_memset - rep movsb # ~3-5 cycles per byte - # R_SRC, R_DST now point past end of data +.L_xmemset_mtrace_rdi_unaligned_no_pre_no_post: + inc r13 -.L_done: - ret # ~5 cycles + jmp fast_memset -# Performance estimate (Modern x86-64, L1 cache hits): +.L_xmemset_mtrace_count_zero: + + # encode in fast way, for zero-lenght memset + + # encode dst offset + mov r9, rdi + and r9, 0x07 + shl r9, DST_OFFSET_RS + + # encode fill byte + movzx eax, sil + shl rax, FILL_BYTE_CMP_RES_RS + add r9, rax + + # encode template of MEMSET_ZERO + add r9, ENCODE_MEMSET_ZERO + + # add encode to mtrace + mov [r12 + r13 * 8], r9 + inc r13 + + jmp fast_memset + +.L_xmemset_mtrace_done: + ret + +# Performance estimate (Modern x86-64, Intel Skylake/AMD Zen+, L1 cache hits): # -# NON-OVERLAPPING FORWARD COPY PATH: +# MEMSET OPERATION WITH MOPS TRACING: # - fast_dma_encode call: ~15-20 cycles (function call + table lookup) -# - Write encoding to trace: ~4 cycles -# - Pre-value trace (conditional): ~12 cycles (if pre_count > 0) -# - Post-value trace (conditional): ~12 cycles (if post_count > 0) -# - Source data to trace: ~1.5-2 cycles per qword (rep movsq) -# - Pre-bytes copy: ~3-5 cycles per byte (if pre_count > 0, max 7 bytes) -# - Aligned qwords copy: ~1.5-2 cycles per qword (rep movsq, main data) -# - Post-bytes copy: ~3-5 cycles per byte (if post_count > 0, max 7 bytes) -# - Function overhead: ~10 cycles (push/pop, branches, return) -# -# TOTAL (best case, aligned, no pre/post): -# ~30 cycles base + ~2 cycles per qword (trace + copy) +# - Pre-read mops entry: ~8-10 cycles (if pre_count > 0: calc + and + store + inc) +# - Post-read mops entry: ~10-12 cycles (if post_count > 0: lea + and + add + store + inc) +# - Block write mops entry: ~12-15 cycles (extract + shift + combine + store + inc) +# - Byte value expansion: ~5-6 cycles (movzx + mov + imul) +# - Qword fill (rep stosq): ~0.5-1.0 cycles per qword (ERMSB optimization) +# - Remaining bytes (rep stosb): ~1.0-2.0 cycles per byte (0-7 bytes) +# - Function overhead: ~3-5 cycles (branches, return) # -# TOTAL (typical case, some alignment): -# ~50 cycles base + ~2 cycles per qword + ~4 cycles per pre/post byte +# TOTAL (typical case, 64 bytes, aligned, no pre/post): +# ~15 (encode) + ~15 (block mops) + ~6 (expand) + 8*0.75 (fill) + ~4 (overhead) +# = ~46 cycles (~1.39 GB/s @ 3 GHz) # -# OVERLAPPING BACKWARD COPY PATH: -# - Same trace overhead: ~30-50 cycles -# - std instruction: ~20-50 cycles (serializing, causes pipeline flush) -# - Backward byte copy: ~3-5 cycles per byte (rep movsb backward) -# - cld instruction: ~20-50 cycles (serializing, causes pipeline flush) +# TOTAL (misaligned case, 64 bytes with pre/post): +# ~15 (encode) + ~10 (pre) + ~12 (post) + ~15 (block) + ~6 (expand) + 7*0.75 + 4*1.5 (fill) + ~4 +# = ~73 cycles (~0.88 GB/s @ 3 GHz) # -# TOTAL (overlap, worst case): -# ~100-150 cycles base + ~4-5 cycles per byte +# TOTAL (large fill, 4096 bytes, aligned): +# ~15 (encode) + ~15 (mops) + ~6 (expand) + 512*0.5 (fill) + ~4 (overhead) +# = ~296 cycles (~13.8 GB/s @ 3 GHz, approaching L1D bandwidth) # # NOTES: -# - Assumes L1 cache hits for all memory accesses -# - rep movsq/movsb performance varies by microarchitecture -# - Actual cycles may vary ±20% depending on CPU model and memory alignment -# - Fast path (aligned, no overlap) is ~2-3x faster than overlap path +# - Assumes L1D cache hits for all memory accesses (~4 cycle latency, ~64 GB/s bandwidth) +# - rep stosq/stosb uses Enhanced REP MOVSB/STOSB (ERMSB) on modern CPUs (post-2013) +# - ERMSB enables microcode to use wide stores (16-64 bytes per iteration internally) +# - For fills >256 bytes, performance approaches memory bandwidth limits +# - Actual cycles vary ±20-30% by microarchitecture (Skylake/Zen/Alder Lake) +# - Mops overhead: ~30-50 cycles base + minimal per-byte impact +# - No overlap handling needed for memset (writes only, no read-modify-write hazards) # Mark stack as non-executable (required by modern linkers) .section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/dma_constants.inc b/emulator-asm/src/dma/dma_constants.inc index 0dfeec09d..7cd0aec0f 100644 --- a/emulator-asm/src/dma/dma_constants.inc +++ b/emulator-asm/src/dma/dma_constants.inc @@ -48,7 +48,7 @@ .equ SRC64_INC_BY_PRE_MASK, 0x0000000000080000 .equ UNALIGNED_DST_SRC_MASK, 0x0000000000100000 .equ FILL_BYTE_CMP_RES_MASK, 0x000000001FE00000 -.equ REQUIRES_DMA_MASK, 0x0000000020000000 +.equ REQUIRES_DMA_MASK, 0x0000000040000000 .equ LOOP_COUNT_MASK, 0xFFFFFFFF00000000 .equ ONLY_LOOP_COUNT_MASK, 0xFFFFFFFF00000000 .equ FULL_ALIGN_MASK, 0x00000000001FFFFF @@ -77,6 +77,25 @@ .equ MOPS_ALIGNED_BLOCK_READ, 0x0000000E00000000 .equ MOPS_ALIGNED_BLOCK_WRITE, 0x0000000F00000000 +.equ FCALL_PARAMS_LENGTH, 386 +.equ FCALL_RESULT_LENGTH, 8193 +.equ FCALL_FUNCTION_ID, 0 +.equ FCALL_PARAMS_CAPACITY, FCALL_FUNCTION_ID + 1 +.equ FCALL_PARAMS_SIZE, FCALL_PARAMS_CAPACITY + 1 +.equ FCALL_PARAMS, FCALL_PARAMS_SIZE + 1 +.equ FCALL_RESULT_CAPACITY, FCALL_PARAMS + FCALL_PARAMS_LENGTH +.equ FCALL_RESULT_SIZE, FCALL_RESULT_CAPACITY + 1 +.equ FCALL_RESULT, FCALL_RESULT_SIZE + 1 +.equ FCALL_RESULT_GOT, FCALL_RESULT + FCALL_RESULT_LENGTH + .equ MOPS_ALIGNED_READ_2W, ((2 << MOPS_BLOCK_WORDS_RS) + MOPS_ALIGNED_BLOCK_READ) .equ LOOP_COUNT_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_RS - LOOP_COUNT_RS) -.equ PRE_WRITES_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_RS - PRE_WRITES_RS) \ No newline at end of file +.equ PRE_WRITES_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_RS - PRE_WRITES_RS) + +# Additional encode constants for fast_dma_encode +.equ DMA_LPRE_COUNT_RS, 32 +.equ DMA_FILL_BYTE_RS, 21 +.equ DMA_FILL_BITS9_MASK, 0x1FF + +.equ ENCODE_MEMSET_ZERO, REQUIRES_DMA_MASK +.equ ENCODE_MEMSET_ALIGNED_NO_COUNT_M8, REQUIRES_DMA_MASK | (1 << PRE_WRITES_RS) diff --git a/emulator-asm/src/dma/fast_dma_encode.asm b/emulator-asm/src/dma/fast_dma_encode.asm index 17a1678df..4361216fb 100644 --- a/emulator-asm/src/dma/fast_dma_encode.asm +++ b/emulator-asm/src/dma/fast_dma_encode.asm @@ -1,6 +1,9 @@ .intel_syntax noprefix .code64 +# Include DMA constants +.include "dma_constants.inc" + ################################################################################ # fast_dma_encode - Optimized function to encode dma information # @@ -28,8 +31,9 @@ # 19: src64_inc_by_pre - Flag: indicate loop use src64 + 8 # 20: unaligned_dst_src - Flag: dst and src has diferent alignement # 21-28: fill_byte/cmp_res - Byte value for fill or compare result -# 29: requires_dma - Flag: indicates if operation requires DMA (*) -# 30-31: reserved +# 29: cmp_negative flag - Comparation between two bytes generate 9 bits (one of them for sign) +# 30: requires_dma - Flag: indicates if operation requires DMA (*) +# 31: reserved # 32-34: pre_count (loop) - Byte value for fill or compare result # 35-63: loop_count - Number of 8-byte chunks in main copy loop # @@ -45,9 +49,16 @@ .global fast_dma_encode_memcpy .global fast_dma_encode_memcmp .global fast_dma_encode_memset +.global fast_dma_encode_memset_with_byte .global fast_dma_encode_inputcpy +.global fast_dma_encode_memcmp_with_result +.global fast_dma_encode .section .text +# Alias for compatibility +fast_dma_encode: + jmp fast_dma_encode_memcpy + fast_dma_encode_memcpy: mov rax, rdi and rax, 0x07 # dst_offset (0-7) @@ -76,76 +87,72 @@ fast_dma_encode_memcpy: # Add (count >> 3) to result mov r8, rdx - shl r8, 29 # r8 = count << 29 + shl r8, DMA_LPRE_COUNT_RS # r8 = count << 29 add rax, r8 # result += (count << 29) ret -// result ? +# PARAMETERS: +# rdi = dst (u64) - Destination address +# rdx = count (usize) - Number of bytes to copy -fast_dma_encode_memcmp: +fast_dma_encode_inputcpy: +fast_dma_encode_no_src: mov rax, rdi and rax, 0x07 # dst_offset (0-7) - shl rax, 7 # dst_offset << 7 - - mov r8, rsi - and r8, 0x07 # src_offset (0-7) - shl r8, 4 # src_offset << 4 - - or rax, r8 # combine dst and src offsets + shl rax, 4 # dst_offset << 7 # Calculate table_count mov r8, rdx cmp r8, 16 - jb .L_memcmp_count_lt_16 + jb .L_fast_dma_encode_inputcpy_count_lt_16 # count >= 16: table_count = (count & 0x07) | 0x08 and r8, 0x07 or r8, 0x08 -.L_memcmp_count_lt_16: +.L_fast_dma_encode_inputcpy_count_lt_16: or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count # Look up encoded value in table (direct access since it's in the same file) - mov rax, [fast_dma_encode_table + rax * 8] + mov rax, [fast_dma_encode_no_src_table + rax * 8] # Add (count >> 3) to result mov r8, rdx - shl r8, 29 # r8 = count << 29 + shl r8, DMA_LPRE_COUNT_RS # r8 = count << 29 add rax, r8 # result += (count << 29) - or rax, REQUIRES_DMA_MASK ret -// byte ? - -fast_dma_encode_memset: - mov rax, rdi - and rax, 0x07 # dst_offset (0-7) - shl rax, 7 # dst_offset << 7 - - # Calculate table_count - mov r8, rdx - cmp r8, 16 - jb .L_memset_count_lt_16 - - # count >= 16: table_count = (count & 0x07) | 0x08 - and r8, 0x07 - or r8, 0x08 - -.L_memset_count_lt_16: - or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count +fast_dma_encode_memcmp: + call fast_dma_encode_memcpy # Get the same encoding as memcpy for pre/post counts and offsets + or rax, REQUIRES_DMA_MASK - # Look up encoded value in table (direct access since it's in the same file) - mov rax, [fast_dma_encode_table + rax * 8] + ret - # Add (count >> 3) to result - mov r8, rdx - shl r8, 29 # r8 = count << 29 - add rax, r8 # result += (count << 29) - +# PARAMETERS (System V AMD64 ABI): +# rdi = dst (u64) - Destination address +# rsi = src (u64) - Source address +# rdx = count (usize) - Number of bytes to copy +# r9 = result (9 bits) - NOTE: value will be modified +fast_dma_encode_memcmp_with_result: + call fast_dma_encode_memcpy # Get the same encoding as memcpy for pre/post counts and offsets + or rax, REQUIRES_DMA_MASK + and r9, DMA_FILL_BITS9_MASK # Ensure result is in lower 9 bits + shl r9, DMA_FILL_BYTE_RS # r8 has the result byte in the lower 8 bits + or rax, r9 ret +# PARAMETERS (System V AMD64 ABI): +# rdi = dst (u64) - Destination address +# rsi = fill byte - Source address +# rdx = count (usize) - Number of bytes to copy +fast_dma_encode_memset_with_byte: + call fast_dma_encode_no_src + movzx r9, sil + shl r9, DMA_FILL_BYTE_RS # r8 has the result byte in the lower 8 bits + or rax, r9 + ret # Mark stack as non-executable (required by modern linkers) .section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/fast_dma_encode_table.asm b/emulator-asm/src/dma/fast_dma_encode_table.asm index 2dad44adb..2436e5f3a 100644 --- a/emulator-asm/src/dma/fast_dma_encode_table.asm +++ b/emulator-asm/src/dma/fast_dma_encode_table.asm @@ -5,516 +5,551 @@ # generated with precompiles/helpers/src/dma # asm_fast_encode_table test +fast_dma_encode_no_src_table: + .quad 0x0000000040000000, 0xFFFFFFFF40000088, 0xFFFFFFFE40000090, 0xFFFFFFFD40000098 # 0 - 3 D0 C0 + .quad 0xfffffffc400000a0, 0xFFFFFFFB400000A8, 0xFFFFFFFA400000B0, 0xFFFFFFF9400000B8 # 4 - 7 D0 C4 + .quad 0x0000000000000000, 0xFFFFFFFF40000088, 0xFFFFFFFE40000090, 0xFFFFFFFD40000098 # 8 - 11 D0 C8 + .quad 0xfffffffc400000a0, 0xFFFFFFFB400000A8, 0xFFFFFFFA400000B0, 0xFFFFFFF9400000B8 # 12 - 15 D0 C12 + .quad 0x0000000040000200, 0x0000000040000281, 0x0000000040000282, 0x0000000040000283 # 16 - 19 D0 C0 + .quad 0x0000000040000284, 0x0000000040000285, 0x0000000040000286, 0x0000000040000287 # 20 - 23 D0 C4 + .quad 0xffffffff4000030f, 0xFFFFFFFE40000317, 0xFFFFFFFD4000031F, 0xFFFFFFFC40000327 # 24 - 27 D0 C8 + .quad 0xfffffffb4000032f, 0xFFFFFFFA40000337, 0xFFFFFFF94000033F, 0x0000000040000287 # 28 - 31 D0 C12 + .quad 0x0000000040000400, 0x0000000040000481, 0x0000000040000482, 0x0000000040000483 # 32 - 35 D1 C0 + .quad 0x0000000040000484, 0x0000000040000485, 0x0000000040000486, 0xFFFFFFFF4000050E # 36 - 39 D1 C4 + .quad 0xfffffffe40000516, 0xFFFFFFFD4000051E, 0xFFFFFFFC40000526, 0xFFFFFFFB4000052E # 40 - 43 D1 C8 + .quad 0xfffffffa40000536, 0xFFFFFFF94000053E, 0x0000000040000486, 0xFFFFFFFF4000050E # 44 - 47 D1 C12 + .quad 0x0000000040000600, 0x0000000040000681, 0x0000000040000682, 0x0000000040000683 # 48 - 51 D1 C0 + .quad 0x0000000040000684, 0x0000000040000685, 0xFFFFFFFF4000070D, 0xFFFFFFFE40000715 # 52 - 55 D1 C4 + .quad 0xfffffffd4000071d, 0xFFFFFFFC40000725, 0xFFFFFFFB4000072D, 0xFFFFFFFA40000735 # 56 - 59 D1 C8 + .quad 0xfffffff94000073d, 0x0000000040000685, 0xFFFFFFFF4000070D, 0xFFFFFFFE40000715 # 60 - 63 D1 C12 + .quad 0x0000000040000800, 0x0000000040000881, 0x0000000040000882, 0x0000000040000883 # 64 - 67 D2 C0 + .quad 0x0000000040000884, 0xFFFFFFFF4000090C, 0xFFFFFFFE40000914, 0xFFFFFFFD4000091C # 68 - 71 D2 C4 + .quad 0xfffffffc40000924, 0xFFFFFFFB4000092C, 0xFFFFFFFA40000934, 0xFFFFFFF94000093C # 72 - 75 D2 C8 + .quad 0x0000000040000884, 0xFFFFFFFF4000090C, 0xFFFFFFFE40000914, 0xFFFFFFFD4000091C # 76 - 79 D2 C12 + .quad 0x0000000040000a00, 0x0000000040000A81, 0x0000000040000A82, 0x0000000040000A83 # 80 - 83 D2 C0 + .quad 0xffffffff40000b0b, 0xFFFFFFFE40000B13, 0xFFFFFFFD40000B1B, 0xFFFFFFFC40000B23 # 84 - 87 D2 C4 + .quad 0xfffffffb40000b2b, 0xFFFFFFFA40000B33, 0xFFFFFFF940000B3B, 0x0000000040000A83 # 88 - 91 D2 C8 + .quad 0xffffffff40000b0b, 0xFFFFFFFE40000B13, 0xFFFFFFFD40000B1B, 0xFFFFFFFC40000B23 # 92 - 95 D2 C12 + .quad 0x0000000040000c00, 0x0000000040000C81, 0x0000000040000C82, 0xFFFFFFFF40000D0A # 96 - 99 D3 C0 + .quad 0xfffffffe40000d12, 0xFFFFFFFD40000D1A, 0xFFFFFFFC40000D22, 0xFFFFFFFB40000D2A # 100 - 103 D3 C4 + .quad 0xfffffffa40000d32, 0xFFFFFFF940000D3A, 0x0000000040000C82, 0xFFFFFFFF40000D0A # 104 - 107 D3 C8 + .quad 0xfffffffe40000d12, 0xFFFFFFFD40000D1A, 0xFFFFFFFC40000D22, 0xFFFFFFFB40000D2A # 108 - 111 D3 C12 + .quad 0x0000000040000e00, 0x0000000040000E81, 0xFFFFFFFF40000F09, 0xFFFFFFFE40000F11 # 112 - 115 D3 C0 + .quad 0xfffffffd40000f19, 0xFFFFFFFC40000F21, 0xFFFFFFFB40000F29, 0xFFFFFFFA40000F31 # 116 - 119 D3 C4 + .quad 0xfffffff940000f39, 0x0000000040000E81, 0xFFFFFFFF40000F09, 0xFFFFFFFE40000F11 # 120 - 123 D3 C8 + .quad 0xfffffffd40000f19, 0xFFFFFFFC40000F21, 0xFFFFFFFB40000F29, 0xFFFFFFFA40000F31 # 124 - 127 D3 C12 fast_dma_encode_table: - .quad 0x0000000000000000, 0xFFFFFFFF00020088, 0xFFFFFFFE00020090, 0xFFFFFFFD00020098 # 0 - 3 D0 S0 C0 - .quad 0xfffffffc000200a0, 0xFFFFFFFB000200A8, 0xFFFFFFFA000200B0, 0xFFFFFFF9000200B8 # 4 - 7 D0 S0 C4 - .quad 0x0000000000000000, 0xFFFFFFFF00020088, 0xFFFFFFFE00020090, 0xFFFFFFFD00020098 # 8 - 11 D0 S0 C8 - .quad 0xfffffffc000200a0, 0xFFFFFFFB000200A8, 0xFFFFFFFA000200B0, 0xFFFFFFF9000200B8 # 12 - 15 D0 S0 C12 - .quad 0x0000000000101000, 0xFFFFFFFF00121088, 0xFFFFFFFE00121090, 0xFFFFFFFD00121098 # 16 - 19 D0 S1 C0 - .quad 0xfffffffc001210a0, 0xFFFFFFFB001210A8, 0xFFFFFFFA001210B0, 0xFFFFFFF9001210B8 # 20 - 23 D0 S1 C4 - .quad 0x0000000000121000, 0xFFFFFFFF00121088, 0xFFFFFFFE00121090, 0xFFFFFFFD00121098 # 24 - 27 D0 S1 C8 - .quad 0xfffffffc001210a0, 0xFFFFFFFB001210A8, 0xFFFFFFFA001210B0, 0xFFFFFFF9001210B8 # 28 - 31 D0 S1 C12 - .quad 0x0000000000102000, 0xFFFFFFFF00122088, 0xFFFFFFFE00122090, 0xFFFFFFFD00122098 # 32 - 35 D0 S2 C0 - .quad 0xfffffffc001220a0, 0xFFFFFFFB001220A8, 0xFFFFFFFA001220B0, 0xFFFFFFF9001520B8 # 36 - 39 D0 S2 C4 - .quad 0x0000000000122000, 0xFFFFFFFF00122088, 0xFFFFFFFE00122090, 0xFFFFFFFD00122098 # 40 - 43 D0 S2 C8 - .quad 0xfffffffc001220a0, 0xFFFFFFFB001220A8, 0xFFFFFFFA001220B0, 0xFFFFFFF9001520B8 # 44 - 47 D0 S2 C12 - .quad 0x0000000000103000, 0xFFFFFFFF00123088, 0xFFFFFFFE00123090, 0xFFFFFFFD00123098 # 48 - 51 D0 S3 C0 - .quad 0xfffffffc001230a0, 0xFFFFFFFB001230A8, 0xFFFFFFFA001530B0, 0xFFFFFFF9001530B8 # 52 - 55 D0 S3 C4 - .quad 0x0000000000123000, 0xFFFFFFFF00123088, 0xFFFFFFFE00123090, 0xFFFFFFFD00123098 # 56 - 59 D0 S3 C8 - .quad 0xfffffffc001230a0, 0xFFFFFFFB001230A8, 0xFFFFFFFA001530B0, 0xFFFFFFF9001530B8 # 60 - 63 D0 S3 C12 - .quad 0x0000000000104000, 0xFFFFFFFF00124088, 0xFFFFFFFE00124090, 0xFFFFFFFD00124098 # 64 - 67 D0 S4 C0 - .quad 0xfffffffc001240a0, 0xFFFFFFFB001540A8, 0xFFFFFFFA001540B0, 0xFFFFFFF9001540B8 # 68 - 71 D0 S4 C4 - .quad 0x0000000000124000, 0xFFFFFFFF00124088, 0xFFFFFFFE00124090, 0xFFFFFFFD00124098 # 72 - 75 D0 S4 C8 - .quad 0xfffffffc001240a0, 0xFFFFFFFB001540A8, 0xFFFFFFFA001540B0, 0xFFFFFFF9001540B8 # 76 - 79 D0 S4 C12 - .quad 0x0000000000105000, 0xFFFFFFFF00125088, 0xFFFFFFFE00125090, 0xFFFFFFFD00125098 # 80 - 83 D0 S5 C0 - .quad 0xfffffffc001550a0, 0xFFFFFFFB001550A8, 0xFFFFFFFA001550B0, 0xFFFFFFF9001550B8 # 84 - 87 D0 S5 C4 - .quad 0x0000000000125000, 0xFFFFFFFF00125088, 0xFFFFFFFE00125090, 0xFFFFFFFD00125098 # 88 - 91 D0 S5 C8 - .quad 0xfffffffc001550a0, 0xFFFFFFFB001550A8, 0xFFFFFFFA001550B0, 0xFFFFFFF9001550B8 # 92 - 95 D0 S5 C12 - .quad 0x0000000000106000, 0xFFFFFFFF00126088, 0xFFFFFFFE00126090, 0xFFFFFFFD00156098 # 96 - 99 D0 S6 C0 - .quad 0xfffffffc001560a0, 0xFFFFFFFB001560A8, 0xFFFFFFFA001560B0, 0xFFFFFFF9001560B8 # 100 - 103 D0 S6 C4 - .quad 0x0000000000126000, 0xFFFFFFFF00126088, 0xFFFFFFFE00126090, 0xFFFFFFFD00156098 # 104 - 107 D0 S6 C8 - .quad 0xfffffffc001560a0, 0xFFFFFFFB001560A8, 0xFFFFFFFA001560B0, 0xFFFFFFF9001560B8 # 108 - 111 D0 S6 C12 - .quad 0x0000000000107000, 0xFFFFFFFF00127088, 0xFFFFFFFE00157090, 0xFFFFFFFD00157098 # 112 - 115 D0 S7 C0 - .quad 0xfffffffc001570a0, 0xFFFFFFFB001570A8, 0xFFFFFFFA001570B0, 0xFFFFFFF9001570B8 # 116 - 119 D0 S7 C4 - .quad 0x0000000000127000, 0xFFFFFFFF00127088, 0xFFFFFFFE00157090, 0xFFFFFFFD00157098 # 120 - 123 D0 S7 C8 - .quad 0xfffffffc001570a0, 0xFFFFFFFB001570A8, 0xFFFFFFFA001570B0, 0xFFFFFFF9001570B8 # 124 - 127 D0 S7 C12 - .quad 0x0000000000100200, 0x0000000000120281, 0x0000000000120282, 0x0000000000120283 # 128 - 131 D1 S0 C0 - .quad 0x0000000000120284, 0x0000000000120285, 0x0000000000120286, 0x0000000000120287 # 132 - 135 D1 S0 C4 - .quad 0xffffffff0012030f, 0xFFFFFFFE00150317, 0xFFFFFFFD0015031F, 0xFFFFFFFC00150327 # 136 - 139 D1 S0 C8 - .quad 0xfffffffb0015032f, 0xFFFFFFFA00150337, 0xFFFFFFF90015033F, 0x0000000000120287 # 140 - 143 D1 S0 C12 - .quad 0x0000000000001200, 0x0000000000021281, 0x0000000000021282, 0x0000000000021283 # 144 - 147 D1 S1 C0 - .quad 0x0000000000021284, 0x0000000000021285, 0x0000000000021286, 0x00000000000A1287 # 148 - 151 D1 S1 C4 - .quad 0xffffffff000c130f, 0xFFFFFFFE000C1317, 0xFFFFFFFD000C131F, 0xFFFFFFFC000C1327 # 152 - 155 D1 S1 C8 - .quad 0xfffffffb000c132f, 0xFFFFFFFA000C1337, 0xFFFFFFF9000C133F, 0x00000000000A1287 # 156 - 159 D1 S1 C12 - .quad 0x0000000000102200, 0x0000000000122281, 0x0000000000122282, 0x0000000000122283 # 160 - 163 D1 S2 C0 - .quad 0x0000000000122284, 0x0000000000122285, 0x00000000001A2286, 0x00000000001CA287 # 164 - 167 D1 S2 C4 - .quad 0xffffffff001ca30f, 0xFFFFFFFE001CA317, 0xFFFFFFFD001CA31F, 0xFFFFFFFC001CA327 # 168 - 171 D1 S2 C8 - .quad 0xfffffffb001ca32f, 0xFFFFFFFA001CA337, 0xFFFFFFF9001CA33F, 0x00000000001CA287 # 172 - 175 D1 S2 C12 - .quad 0x0000000000103200, 0x0000000000123281, 0x0000000000123282, 0x0000000000123283 # 176 - 179 D1 S3 C0 - .quad 0x0000000000123284, 0x00000000001A3285, 0x00000000001CB286, 0x00000000001CB287 # 180 - 183 D1 S3 C4 - .quad 0xffffffff001cb30f, 0xFFFFFFFE001CB317, 0xFFFFFFFD001CB31F, 0xFFFFFFFC001CB327 # 184 - 187 D1 S3 C8 - .quad 0xfffffffb001cb32f, 0xFFFFFFFA001CB337, 0xFFFFFFF9001FB33F, 0x00000000001CB287 # 188 - 191 D1 S3 C12 - .quad 0x0000000000104200, 0x0000000000124281, 0x0000000000124282, 0x0000000000124283 # 192 - 195 D1 S4 C0 - .quad 0x00000000001a4284, 0x00000000001CC285, 0x00000000001CC286, 0x00000000001CC287 # 196 - 199 D1 S4 C4 - .quad 0xffffffff001cc30f, 0xFFFFFFFE001CC317, 0xFFFFFFFD001CC31F, 0xFFFFFFFC001CC327 # 200 - 203 D1 S4 C8 - .quad 0xfffffffb001cc32f, 0xFFFFFFFA001FC337, 0xFFFFFFF9001FC33F, 0x00000000001CC287 # 204 - 207 D1 S4 C12 - .quad 0x0000000000105200, 0x0000000000125281, 0x0000000000125282, 0x00000000001A5283 # 208 - 211 D1 S5 C0 - .quad 0x00000000001cd284, 0x00000000001CD285, 0x00000000001CD286, 0x00000000001CD287 # 212 - 215 D1 S5 C4 - .quad 0xffffffff001cd30f, 0xFFFFFFFE001CD317, 0xFFFFFFFD001CD31F, 0xFFFFFFFC001CD327 # 216 - 219 D1 S5 C8 - .quad 0xfffffffb001fd32f, 0xFFFFFFFA001FD337, 0xFFFFFFF9001FD33F, 0x00000000001CD287 # 220 - 223 D1 S5 C12 - .quad 0x0000000000106200, 0x0000000000126281, 0x00000000001A6282, 0x00000000001CE283 # 224 - 227 D1 S6 C0 - .quad 0x00000000001ce284, 0x00000000001CE285, 0x00000000001CE286, 0x00000000001CE287 # 228 - 231 D1 S6 C4 - .quad 0xffffffff001ce30f, 0xFFFFFFFE001CE317, 0xFFFFFFFD001CE31F, 0xFFFFFFFC001FE327 # 232 - 235 D1 S6 C8 - .quad 0xfffffffb001fe32f, 0xFFFFFFFA001FE337, 0xFFFFFFF9001FE33F, 0x00000000001CE287 # 236 - 239 D1 S6 C12 - .quad 0x0000000000107200, 0x00000000001A7281, 0x00000000001CF282, 0x00000000001CF283 # 240 - 243 D1 S7 C0 - .quad 0x00000000001cf284, 0x00000000001CF285, 0x00000000001CF286, 0x00000000001CF287 # 244 - 247 D1 S7 C4 - .quad 0xffffffff001cf30f, 0xFFFFFFFE001CF317, 0xFFFFFFFD001FF31F, 0xFFFFFFFC001FF327 # 248 - 251 D1 S7 C8 - .quad 0xfffffffb001ff32f, 0xFFFFFFFA001FF337, 0xFFFFFFF9001FF33F, 0x00000000001CF287 # 252 - 255 D1 S7 C12 - .quad 0x0000000000100400, 0x0000000000120481, 0x0000000000120482, 0x0000000000120483 # 256 - 259 D2 S0 C0 - .quad 0x0000000000120484, 0x0000000000120485, 0x0000000000120486, 0xFFFFFFFF0012050E # 260 - 263 D2 S0 C4 - .quad 0xfffffffe00120516, 0xFFFFFFFD0015051E, 0xFFFFFFFC00150526, 0xFFFFFFFB0015052E # 264 - 267 D2 S0 C8 - .quad 0xfffffffa00150536, 0xFFFFFFF90015053E, 0x0000000000120486, 0xFFFFFFFF0012050E # 268 - 271 D2 S0 C12 - .quad 0x0000000000101400, 0x0000000000121481, 0x0000000000121482, 0x0000000000121483 # 272 - 275 D2 S1 C0 - .quad 0x0000000000121484, 0x0000000000121485, 0x0000000000121486, 0xFFFFFFFF0012150E # 276 - 279 D2 S1 C4 - .quad 0xfffffffe00151516, 0xFFFFFFFD0015151E, 0xFFFFFFFC00151526, 0xFFFFFFFB0015152E # 280 - 283 D2 S1 C8 - .quad 0xfffffffa00151536, 0xFFFFFFF90015153E, 0x0000000000121486, 0xFFFFFFFF0012150E # 284 - 287 D2 S1 C12 - .quad 0x0000000000002400, 0x0000000000022481, 0x0000000000022482, 0x0000000000022483 # 288 - 291 D2 S2 C0 - .quad 0x0000000000022484, 0x0000000000022485, 0x00000000000A2486, 0xFFFFFFFF000C250E # 292 - 295 D2 S2 C4 - .quad 0xfffffffe000c2516, 0xFFFFFFFD000C251E, 0xFFFFFFFC000C2526, 0xFFFFFFFB000C252E # 296 - 299 D2 S2 C8 - .quad 0xfffffffa000c2536, 0xFFFFFFF9000C253E, 0x00000000000A2486, 0xFFFFFFFF000C250E # 300 - 303 D2 S2 C12 - .quad 0x0000000000103400, 0x0000000000123481, 0x0000000000123482, 0x0000000000123483 # 304 - 307 D2 S3 C0 - .quad 0x0000000000123484, 0x00000000001A3485, 0x00000000001CB486, 0xFFFFFFFF001CB50E # 308 - 311 D2 S3 C4 - .quad 0xfffffffe001cb516, 0xFFFFFFFD001CB51E, 0xFFFFFFFC001CB526, 0xFFFFFFFB001CB52E # 312 - 315 D2 S3 C8 - .quad 0xfffffffa001cb536, 0xFFFFFFF9001CB53E, 0x00000000001CB486, 0xFFFFFFFF001CB50E # 316 - 319 D2 S3 C12 - .quad 0x0000000000104400, 0x0000000000124481, 0x0000000000124482, 0x0000000000124483 # 320 - 323 D2 S4 C0 - .quad 0x00000000001a4484, 0x00000000001CC485, 0x00000000001CC486, 0xFFFFFFFF001CC50E # 324 - 327 D2 S4 C4 - .quad 0xfffffffe001cc516, 0xFFFFFFFD001CC51E, 0xFFFFFFFC001CC526, 0xFFFFFFFB001CC52E # 328 - 331 D2 S4 C8 - .quad 0xfffffffa001cc536, 0xFFFFFFF9001FC53E, 0x00000000001CC486, 0xFFFFFFFF001CC50E # 332 - 335 D2 S4 C12 - .quad 0x0000000000105400, 0x0000000000125481, 0x0000000000125482, 0x00000000001A5483 # 336 - 339 D2 S5 C0 - .quad 0x00000000001cd484, 0x00000000001CD485, 0x00000000001CD486, 0xFFFFFFFF001CD50E # 340 - 343 D2 S5 C4 - .quad 0xfffffffe001cd516, 0xFFFFFFFD001CD51E, 0xFFFFFFFC001CD526, 0xFFFFFFFB001CD52E # 344 - 347 D2 S5 C8 - .quad 0xfffffffa001fd536, 0xFFFFFFF9001FD53E, 0x00000000001CD486, 0xFFFFFFFF001CD50E # 348 - 351 D2 S5 C12 - .quad 0x0000000000106400, 0x0000000000126481, 0x00000000001A6482, 0x00000000001CE483 # 352 - 355 D2 S6 C0 - .quad 0x00000000001ce484, 0x00000000001CE485, 0x00000000001CE486, 0xFFFFFFFF001CE50E # 356 - 359 D2 S6 C4 - .quad 0xfffffffe001ce516, 0xFFFFFFFD001CE51E, 0xFFFFFFFC001CE526, 0xFFFFFFFB001FE52E # 360 - 363 D2 S6 C8 - .quad 0xfffffffa001fe536, 0xFFFFFFF9001FE53E, 0x00000000001CE486, 0xFFFFFFFF001CE50E # 364 - 367 D2 S6 C12 - .quad 0x0000000000107400, 0x00000000001A7481, 0x00000000001CF482, 0x00000000001CF483 # 368 - 371 D2 S7 C0 - .quad 0x00000000001cf484, 0x00000000001CF485, 0x00000000001CF486, 0xFFFFFFFF001CF50E # 372 - 375 D2 S7 C4 - .quad 0xfffffffe001cf516, 0xFFFFFFFD001CF51E, 0xFFFFFFFC001FF526, 0xFFFFFFFB001FF52E # 376 - 379 D2 S7 C8 - .quad 0xfffffffa001ff536, 0xFFFFFFF9001FF53E, 0x00000000001CF486, 0xFFFFFFFF001CF50E # 380 - 383 D2 S7 C12 - .quad 0x0000000000100600, 0x0000000000120681, 0x0000000000120682, 0x0000000000120683 # 384 - 387 D3 S0 C0 - .quad 0x0000000000120684, 0x0000000000120685, 0xFFFFFFFF0012070D, 0xFFFFFFFE00120715 # 388 - 391 D3 S0 C4 - .quad 0xfffffffd0012071d, 0xFFFFFFFC00150725, 0xFFFFFFFB0015072D, 0xFFFFFFFA00150735 # 392 - 395 D3 S0 C8 - .quad 0xfffffff90015073d, 0x0000000000120685, 0xFFFFFFFF0012070D, 0xFFFFFFFE00120715 # 396 - 399 D3 S0 C12 - .quad 0x0000000000101600, 0x0000000000121681, 0x0000000000121682, 0x0000000000121683 # 400 - 403 D3 S1 C0 - .quad 0x0000000000121684, 0x0000000000121685, 0xFFFFFFFF0012170D, 0xFFFFFFFE00121715 # 404 - 407 D3 S1 C4 - .quad 0xfffffffd0015171d, 0xFFFFFFFC00151725, 0xFFFFFFFB0015172D, 0xFFFFFFFA00151735 # 408 - 411 D3 S1 C8 - .quad 0xfffffff90015173d, 0x0000000000121685, 0xFFFFFFFF0012170D, 0xFFFFFFFE00121715 # 412 - 415 D3 S1 C12 - .quad 0x0000000000102600, 0x0000000000122681, 0x0000000000122682, 0x0000000000122683 # 416 - 419 D3 S2 C0 - .quad 0x0000000000122684, 0x0000000000122685, 0xFFFFFFFF0012270D, 0xFFFFFFFE00152715 # 420 - 423 D3 S2 C4 - .quad 0xfffffffd0015271d, 0xFFFFFFFC00152725, 0xFFFFFFFB0015272D, 0xFFFFFFFA00152735 # 424 - 427 D3 S2 C8 - .quad 0xfffffff90015273d, 0x0000000000122685, 0xFFFFFFFF0012270D, 0xFFFFFFFE00152715 # 428 - 431 D3 S2 C12 - .quad 0x0000000000003600, 0x0000000000023681, 0x0000000000023682, 0x0000000000023683 # 432 - 435 D3 S3 C0 - .quad 0x0000000000023684, 0x00000000000A3685, 0xFFFFFFFF000C370D, 0xFFFFFFFE000C3715 # 436 - 439 D3 S3 C4 - .quad 0xfffffffd000c371d, 0xFFFFFFFC000C3725, 0xFFFFFFFB000C372D, 0xFFFFFFFA000C3735 # 440 - 443 D3 S3 C8 - .quad 0xfffffff9000c373d, 0x00000000000A3685, 0xFFFFFFFF000C370D, 0xFFFFFFFE000C3715 # 444 - 447 D3 S3 C12 - .quad 0x0000000000104600, 0x0000000000124681, 0x0000000000124682, 0x0000000000124683 # 448 - 451 D3 S4 C0 - .quad 0x00000000001a4684, 0x00000000001CC685, 0xFFFFFFFF001CC70D, 0xFFFFFFFE001CC715 # 452 - 455 D3 S4 C4 - .quad 0xfffffffd001cc71d, 0xFFFFFFFC001CC725, 0xFFFFFFFB001CC72D, 0xFFFFFFFA001CC735 # 456 - 459 D3 S4 C8 - .quad 0xfffffff9001cc73d, 0x00000000001CC685, 0xFFFFFFFF001CC70D, 0xFFFFFFFE001CC715 # 460 - 463 D3 S4 C12 - .quad 0x0000000000105600, 0x0000000000125681, 0x0000000000125682, 0x00000000001A5683 # 464 - 467 D3 S5 C0 - .quad 0x00000000001cd684, 0x00000000001CD685, 0xFFFFFFFF001CD70D, 0xFFFFFFFE001CD715 # 468 - 471 D3 S5 C4 - .quad 0xfffffffd001cd71d, 0xFFFFFFFC001CD725, 0xFFFFFFFB001CD72D, 0xFFFFFFFA001CD735 # 472 - 475 D3 S5 C8 - .quad 0xfffffff9001fd73d, 0x00000000001CD685, 0xFFFFFFFF001CD70D, 0xFFFFFFFE001CD715 # 476 - 479 D3 S5 C12 - .quad 0x0000000000106600, 0x0000000000126681, 0x00000000001A6682, 0x00000000001CE683 # 480 - 483 D3 S6 C0 - .quad 0x00000000001ce684, 0x00000000001CE685, 0xFFFFFFFF001CE70D, 0xFFFFFFFE001CE715 # 484 - 487 D3 S6 C4 - .quad 0xfffffffd001ce71d, 0xFFFFFFFC001CE725, 0xFFFFFFFB001CE72D, 0xFFFFFFFA001FE735 # 488 - 491 D3 S6 C8 - .quad 0xfffffff9001fe73d, 0x00000000001CE685, 0xFFFFFFFF001CE70D, 0xFFFFFFFE001CE715 # 492 - 495 D3 S6 C12 - .quad 0x0000000000107600, 0x00000000001A7681, 0x00000000001CF682, 0x00000000001CF683 # 496 - 499 D3 S7 C0 - .quad 0x00000000001cf684, 0x00000000001CF685, 0xFFFFFFFF001CF70D, 0xFFFFFFFE001CF715 # 500 - 503 D3 S7 C4 - .quad 0xfffffffd001cf71d, 0xFFFFFFFC001CF725, 0xFFFFFFFB001FF72D, 0xFFFFFFFA001FF735 # 504 - 507 D3 S7 C8 - .quad 0xfffffff9001ff73d, 0x00000000001CF685, 0xFFFFFFFF001CF70D, 0xFFFFFFFE001CF715 # 508 - 511 D3 S7 C12 - .quad 0x0000000000100800, 0x0000000000120881, 0x0000000000120882, 0x0000000000120883 # 512 - 515 D4 S0 C0 - .quad 0x0000000000120884, 0xFFFFFFFF0012090C, 0xFFFFFFFE00120914, 0xFFFFFFFD0012091C # 516 - 519 D4 S0 C4 - .quad 0xfffffffc00120924, 0xFFFFFFFB0015092C, 0xFFFFFFFA00150934, 0xFFFFFFF90015093C # 520 - 523 D4 S0 C8 - .quad 0x0000000000120884, 0xFFFFFFFF0012090C, 0xFFFFFFFE00120914, 0xFFFFFFFD0012091C # 524 - 527 D4 S0 C12 - .quad 0x0000000000101800, 0x0000000000121881, 0x0000000000121882, 0x0000000000121883 # 528 - 531 D4 S1 C0 - .quad 0x0000000000121884, 0xFFFFFFFF0012190C, 0xFFFFFFFE00121914, 0xFFFFFFFD0012191C # 532 - 535 D4 S1 C4 - .quad 0xfffffffc00151924, 0xFFFFFFFB0015192C, 0xFFFFFFFA00151934, 0xFFFFFFF90015193C # 536 - 539 D4 S1 C8 - .quad 0x0000000000121884, 0xFFFFFFFF0012190C, 0xFFFFFFFE00121914, 0xFFFFFFFD0012191C # 540 - 543 D4 S1 C12 - .quad 0x0000000000102800, 0x0000000000122881, 0x0000000000122882, 0x0000000000122883 # 544 - 547 D4 S2 C0 - .quad 0x0000000000122884, 0xFFFFFFFF0012290C, 0xFFFFFFFE00122914, 0xFFFFFFFD0015291C # 548 - 551 D4 S2 C4 - .quad 0xfffffffc00152924, 0xFFFFFFFB0015292C, 0xFFFFFFFA00152934, 0xFFFFFFF90015293C # 552 - 555 D4 S2 C8 - .quad 0x0000000000122884, 0xFFFFFFFF0012290C, 0xFFFFFFFE00122914, 0xFFFFFFFD0015291C # 556 - 559 D4 S2 C12 - .quad 0x0000000000103800, 0x0000000000123881, 0x0000000000123882, 0x0000000000123883 # 560 - 563 D4 S3 C0 - .quad 0x0000000000123884, 0xFFFFFFFF0012390C, 0xFFFFFFFE00153914, 0xFFFFFFFD0015391C # 564 - 567 D4 S3 C4 - .quad 0xfffffffc00153924, 0xFFFFFFFB0015392C, 0xFFFFFFFA00153934, 0xFFFFFFF90015393C # 568 - 571 D4 S3 C8 - .quad 0x0000000000123884, 0xFFFFFFFF0012390C, 0xFFFFFFFE00153914, 0xFFFFFFFD0015391C # 572 - 575 D4 S3 C12 - .quad 0x0000000000004800, 0x0000000000024881, 0x0000000000024882, 0x0000000000024883 # 576 - 579 D4 S4 C0 - .quad 0x00000000000a4884, 0xFFFFFFFF000C490C, 0xFFFFFFFE000C4914, 0xFFFFFFFD000C491C # 580 - 583 D4 S4 C4 - .quad 0xfffffffc000c4924, 0xFFFFFFFB000C492C, 0xFFFFFFFA000C4934, 0xFFFFFFF9000C493C # 584 - 587 D4 S4 C8 - .quad 0x00000000000a4884, 0xFFFFFFFF000C490C, 0xFFFFFFFE000C4914, 0xFFFFFFFD000C491C # 588 - 591 D4 S4 C12 - .quad 0x0000000000105800, 0x0000000000125881, 0x0000000000125882, 0x00000000001A5883 # 592 - 595 D4 S5 C0 - .quad 0x00000000001cd884, 0xFFFFFFFF001CD90C, 0xFFFFFFFE001CD914, 0xFFFFFFFD001CD91C # 596 - 599 D4 S5 C4 - .quad 0xfffffffc001cd924, 0xFFFFFFFB001CD92C, 0xFFFFFFFA001CD934, 0xFFFFFFF9001CD93C # 600 - 603 D4 S5 C8 - .quad 0x00000000001cd884, 0xFFFFFFFF001CD90C, 0xFFFFFFFE001CD914, 0xFFFFFFFD001CD91C # 604 - 607 D4 S5 C12 - .quad 0x0000000000106800, 0x0000000000126881, 0x00000000001A6882, 0x00000000001CE883 # 608 - 611 D4 S6 C0 - .quad 0x00000000001ce884, 0xFFFFFFFF001CE90C, 0xFFFFFFFE001CE914, 0xFFFFFFFD001CE91C # 612 - 615 D4 S6 C4 - .quad 0xfffffffc001ce924, 0xFFFFFFFB001CE92C, 0xFFFFFFFA001CE934, 0xFFFFFFF9001FE93C # 616 - 619 D4 S6 C8 - .quad 0x00000000001ce884, 0xFFFFFFFF001CE90C, 0xFFFFFFFE001CE914, 0xFFFFFFFD001CE91C # 620 - 623 D4 S6 C12 - .quad 0x0000000000107800, 0x00000000001A7881, 0x00000000001CF882, 0x00000000001CF883 # 624 - 627 D4 S7 C0 - .quad 0x00000000001cf884, 0xFFFFFFFF001CF90C, 0xFFFFFFFE001CF914, 0xFFFFFFFD001CF91C # 628 - 631 D4 S7 C4 - .quad 0xfffffffc001cf924, 0xFFFFFFFB001CF92C, 0xFFFFFFFA001FF934, 0xFFFFFFF9001FF93C # 632 - 635 D4 S7 C8 - .quad 0x00000000001cf884, 0xFFFFFFFF001CF90C, 0xFFFFFFFE001CF914, 0xFFFFFFFD001CF91C # 636 - 639 D4 S7 C12 - .quad 0x0000000000100a00, 0x0000000000120A81, 0x0000000000120A82, 0x0000000000120A83 # 640 - 643 D5 S0 C0 - .quad 0xffffffff00120b0b, 0xFFFFFFFE00120B13, 0xFFFFFFFD00120B1B, 0xFFFFFFFC00120B23 # 644 - 647 D5 S0 C4 - .quad 0xfffffffb00120b2b, 0xFFFFFFFA00150B33, 0xFFFFFFF900150B3B, 0x0000000000120A83 # 648 - 651 D5 S0 C8 - .quad 0xffffffff00120b0b, 0xFFFFFFFE00120B13, 0xFFFFFFFD00120B1B, 0xFFFFFFFC00120B23 # 652 - 655 D5 S0 C12 - .quad 0x0000000000101a00, 0x0000000000121A81, 0x0000000000121A82, 0x0000000000121A83 # 656 - 659 D5 S1 C0 - .quad 0xffffffff00121b0b, 0xFFFFFFFE00121B13, 0xFFFFFFFD00121B1B, 0xFFFFFFFC00121B23 # 660 - 663 D5 S1 C4 - .quad 0xfffffffb00151b2b, 0xFFFFFFFA00151B33, 0xFFFFFFF900151B3B, 0x0000000000121A83 # 664 - 667 D5 S1 C8 - .quad 0xffffffff00121b0b, 0xFFFFFFFE00121B13, 0xFFFFFFFD00121B1B, 0xFFFFFFFC00121B23 # 668 - 671 D5 S1 C12 - .quad 0x0000000000102a00, 0x0000000000122A81, 0x0000000000122A82, 0x0000000000122A83 # 672 - 675 D5 S2 C0 - .quad 0xffffffff00122b0b, 0xFFFFFFFE00122B13, 0xFFFFFFFD00122B1B, 0xFFFFFFFC00152B23 # 676 - 679 D5 S2 C4 - .quad 0xfffffffb00152b2b, 0xFFFFFFFA00152B33, 0xFFFFFFF900152B3B, 0x0000000000122A83 # 680 - 683 D5 S2 C8 - .quad 0xffffffff00122b0b, 0xFFFFFFFE00122B13, 0xFFFFFFFD00122B1B, 0xFFFFFFFC00152B23 # 684 - 687 D5 S2 C12 - .quad 0x0000000000103a00, 0x0000000000123A81, 0x0000000000123A82, 0x0000000000123A83 # 688 - 691 D5 S3 C0 - .quad 0xffffffff00123b0b, 0xFFFFFFFE00123B13, 0xFFFFFFFD00153B1B, 0xFFFFFFFC00153B23 # 692 - 695 D5 S3 C4 - .quad 0xfffffffb00153b2b, 0xFFFFFFFA00153B33, 0xFFFFFFF900153B3B, 0x0000000000123A83 # 696 - 699 D5 S3 C8 - .quad 0xffffffff00123b0b, 0xFFFFFFFE00123B13, 0xFFFFFFFD00153B1B, 0xFFFFFFFC00153B23 # 700 - 703 D5 S3 C12 - .quad 0x0000000000104a00, 0x0000000000124A81, 0x0000000000124A82, 0x0000000000124A83 # 704 - 707 D5 S4 C0 - .quad 0xffffffff00124b0b, 0xFFFFFFFE00154B13, 0xFFFFFFFD00154B1B, 0xFFFFFFFC00154B23 # 708 - 711 D5 S4 C4 - .quad 0xfffffffb00154b2b, 0xFFFFFFFA00154B33, 0xFFFFFFF900154B3B, 0x0000000000124A83 # 712 - 715 D5 S4 C8 - .quad 0xffffffff00124b0b, 0xFFFFFFFE00154B13, 0xFFFFFFFD00154B1B, 0xFFFFFFFC00154B23 # 716 - 719 D5 S4 C12 - .quad 0x0000000000005a00, 0x0000000000025A81, 0x0000000000025A82, 0x00000000000A5A83 # 720 - 723 D5 S5 C0 - .quad 0xffffffff000c5b0b, 0xFFFFFFFE000C5B13, 0xFFFFFFFD000C5B1B, 0xFFFFFFFC000C5B23 # 724 - 727 D5 S5 C4 - .quad 0xfffffffb000c5b2b, 0xFFFFFFFA000C5B33, 0xFFFFFFF9000C5B3B, 0x00000000000A5A83 # 728 - 731 D5 S5 C8 - .quad 0xffffffff000c5b0b, 0xFFFFFFFE000C5B13, 0xFFFFFFFD000C5B1B, 0xFFFFFFFC000C5B23 # 732 - 735 D5 S5 C12 - .quad 0x0000000000106a00, 0x0000000000126A81, 0x00000000001A6A82, 0x00000000001CEA83 # 736 - 739 D5 S6 C0 - .quad 0xffffffff001ceb0b, 0xFFFFFFFE001CEB13, 0xFFFFFFFD001CEB1B, 0xFFFFFFFC001CEB23 # 740 - 743 D5 S6 C4 - .quad 0xfffffffb001ceb2b, 0xFFFFFFFA001CEB33, 0xFFFFFFF9001CEB3B, 0x00000000001CEA83 # 744 - 747 D5 S6 C8 - .quad 0xffffffff001ceb0b, 0xFFFFFFFE001CEB13, 0xFFFFFFFD001CEB1B, 0xFFFFFFFC001CEB23 # 748 - 751 D5 S6 C12 - .quad 0x0000000000107a00, 0x00000000001A7A81, 0x00000000001CFA82, 0x00000000001CFA83 # 752 - 755 D5 S7 C0 - .quad 0xffffffff001cfb0b, 0xFFFFFFFE001CFB13, 0xFFFFFFFD001CFB1B, 0xFFFFFFFC001CFB23 # 756 - 759 D5 S7 C4 - .quad 0xfffffffb001cfb2b, 0xFFFFFFFA001CFB33, 0xFFFFFFF9001FFB3B, 0x00000000001CFA83 # 760 - 763 D5 S7 C8 - .quad 0xffffffff001cfb0b, 0xFFFFFFFE001CFB13, 0xFFFFFFFD001CFB1B, 0xFFFFFFFC001CFB23 # 764 - 767 D5 S7 C12 - .quad 0x0000000000100c00, 0x0000000000120C81, 0x0000000000120C82, 0xFFFFFFFF00120D0A # 768 - 771 D6 S0 C0 - .quad 0xfffffffe00120d12, 0xFFFFFFFD00120D1A, 0xFFFFFFFC00120D22, 0xFFFFFFFB00120D2A # 772 - 775 D6 S0 C4 - .quad 0xfffffffa00120d32, 0xFFFFFFF900150D3A, 0x0000000000120C82, 0xFFFFFFFF00120D0A # 776 - 779 D6 S0 C8 - .quad 0xfffffffe00120d12, 0xFFFFFFFD00120D1A, 0xFFFFFFFC00120D22, 0xFFFFFFFB00120D2A # 780 - 783 D6 S0 C12 - .quad 0x0000000000101c00, 0x0000000000121C81, 0x0000000000121C82, 0xFFFFFFFF00121D0A # 784 - 787 D6 S1 C0 - .quad 0xfffffffe00121d12, 0xFFFFFFFD00121D1A, 0xFFFFFFFC00121D22, 0xFFFFFFFB00121D2A # 788 - 791 D6 S1 C4 - .quad 0xfffffffa00151d32, 0xFFFFFFF900151D3A, 0x0000000000121C82, 0xFFFFFFFF00121D0A # 792 - 795 D6 S1 C8 - .quad 0xfffffffe00121d12, 0xFFFFFFFD00121D1A, 0xFFFFFFFC00121D22, 0xFFFFFFFB00121D2A # 796 - 799 D6 S1 C12 - .quad 0x0000000000102c00, 0x0000000000122C81, 0x0000000000122C82, 0xFFFFFFFF00122D0A # 800 - 803 D6 S2 C0 - .quad 0xfffffffe00122d12, 0xFFFFFFFD00122D1A, 0xFFFFFFFC00122D22, 0xFFFFFFFB00152D2A # 804 - 807 D6 S2 C4 - .quad 0xfffffffa00152d32, 0xFFFFFFF900152D3A, 0x0000000000122C82, 0xFFFFFFFF00122D0A # 808 - 811 D6 S2 C8 - .quad 0xfffffffe00122d12, 0xFFFFFFFD00122D1A, 0xFFFFFFFC00122D22, 0xFFFFFFFB00152D2A # 812 - 815 D6 S2 C12 - .quad 0x0000000000103c00, 0x0000000000123C81, 0x0000000000123C82, 0xFFFFFFFF00123D0A # 816 - 819 D6 S3 C0 - .quad 0xfffffffe00123d12, 0xFFFFFFFD00123D1A, 0xFFFFFFFC00153D22, 0xFFFFFFFB00153D2A # 820 - 823 D6 S3 C4 - .quad 0xfffffffa00153d32, 0xFFFFFFF900153D3A, 0x0000000000123C82, 0xFFFFFFFF00123D0A # 824 - 827 D6 S3 C8 - .quad 0xfffffffe00123d12, 0xFFFFFFFD00123D1A, 0xFFFFFFFC00153D22, 0xFFFFFFFB00153D2A # 828 - 831 D6 S3 C12 - .quad 0x0000000000104c00, 0x0000000000124C81, 0x0000000000124C82, 0xFFFFFFFF00124D0A # 832 - 835 D6 S4 C0 - .quad 0xfffffffe00124d12, 0xFFFFFFFD00154D1A, 0xFFFFFFFC00154D22, 0xFFFFFFFB00154D2A # 836 - 839 D6 S4 C4 - .quad 0xfffffffa00154d32, 0xFFFFFFF900154D3A, 0x0000000000124C82, 0xFFFFFFFF00124D0A # 840 - 843 D6 S4 C8 - .quad 0xfffffffe00124d12, 0xFFFFFFFD00154D1A, 0xFFFFFFFC00154D22, 0xFFFFFFFB00154D2A # 844 - 847 D6 S4 C12 - .quad 0x0000000000105c00, 0x0000000000125C81, 0x0000000000125C82, 0xFFFFFFFF00125D0A # 848 - 851 D6 S5 C0 - .quad 0xfffffffe00155d12, 0xFFFFFFFD00155D1A, 0xFFFFFFFC00155D22, 0xFFFFFFFB00155D2A # 852 - 855 D6 S5 C4 - .quad 0xfffffffa00155d32, 0xFFFFFFF900155D3A, 0x0000000000125C82, 0xFFFFFFFF00125D0A # 856 - 859 D6 S5 C8 - .quad 0xfffffffe00155d12, 0xFFFFFFFD00155D1A, 0xFFFFFFFC00155D22, 0xFFFFFFFB00155D2A # 860 - 863 D6 S5 C12 - .quad 0x0000000000006c00, 0x0000000000026C81, 0x00000000000A6C82, 0xFFFFFFFF000C6D0A # 864 - 867 D6 S6 C0 - .quad 0xfffffffe000c6d12, 0xFFFFFFFD000C6D1A, 0xFFFFFFFC000C6D22, 0xFFFFFFFB000C6D2A # 868 - 871 D6 S6 C4 - .quad 0xfffffffa000c6d32, 0xFFFFFFF9000C6D3A, 0x00000000000A6C82, 0xFFFFFFFF000C6D0A # 872 - 875 D6 S6 C8 - .quad 0xfffffffe000c6d12, 0xFFFFFFFD000C6D1A, 0xFFFFFFFC000C6D22, 0xFFFFFFFB000C6D2A # 876 - 879 D6 S6 C12 - .quad 0x0000000000107c00, 0x00000000001A7C81, 0x00000000001CFC82, 0xFFFFFFFF001CFD0A # 880 - 883 D6 S7 C0 - .quad 0xfffffffe001cfd12, 0xFFFFFFFD001CFD1A, 0xFFFFFFFC001CFD22, 0xFFFFFFFB001CFD2A # 884 - 887 D6 S7 C4 - .quad 0xfffffffa001cfd32, 0xFFFFFFF9001CFD3A, 0x00000000001CFC82, 0xFFFFFFFF001CFD0A # 888 - 891 D6 S7 C8 - .quad 0xfffffffe001cfd12, 0xFFFFFFFD001CFD1A, 0xFFFFFFFC001CFD22, 0xFFFFFFFB001CFD2A # 892 - 895 D6 S7 C12 - .quad 0x0000000000100e00, 0x0000000000120E81, 0xFFFFFFFF00120F09, 0xFFFFFFFE00120F11 # 896 - 899 D7 S0 C0 - .quad 0xfffffffd00120f19, 0xFFFFFFFC00120F21, 0xFFFFFFFB00120F29, 0xFFFFFFFA00120F31 # 900 - 903 D7 S0 C4 - .quad 0xfffffff900120f39, 0x0000000000120E81, 0xFFFFFFFF00120F09, 0xFFFFFFFE00120F11 # 904 - 907 D7 S0 C8 - .quad 0xfffffffd00120f19, 0xFFFFFFFC00120F21, 0xFFFFFFFB00120F29, 0xFFFFFFFA00120F31 # 908 - 911 D7 S0 C12 - .quad 0x0000000000101e00, 0x0000000000121E81, 0xFFFFFFFF00121F09, 0xFFFFFFFE00121F11 # 912 - 915 D7 S1 C0 - .quad 0xfffffffd00121f19, 0xFFFFFFFC00121F21, 0xFFFFFFFB00121F29, 0xFFFFFFFA00121F31 # 916 - 919 D7 S1 C4 - .quad 0xfffffff900151f39, 0x0000000000121E81, 0xFFFFFFFF00121F09, 0xFFFFFFFE00121F11 # 920 - 923 D7 S1 C8 - .quad 0xfffffffd00121f19, 0xFFFFFFFC00121F21, 0xFFFFFFFB00121F29, 0xFFFFFFFA00121F31 # 924 - 927 D7 S1 C12 - .quad 0x0000000000102e00, 0x0000000000122E81, 0xFFFFFFFF00122F09, 0xFFFFFFFE00122F11 # 928 - 931 D7 S2 C0 - .quad 0xfffffffd00122f19, 0xFFFFFFFC00122F21, 0xFFFFFFFB00122F29, 0xFFFFFFFA00152F31 # 932 - 935 D7 S2 C4 - .quad 0xfffffff900152f39, 0x0000000000122E81, 0xFFFFFFFF00122F09, 0xFFFFFFFE00122F11 # 936 - 939 D7 S2 C8 - .quad 0xfffffffd00122f19, 0xFFFFFFFC00122F21, 0xFFFFFFFB00122F29, 0xFFFFFFFA00152F31 # 940 - 943 D7 S2 C12 - .quad 0x0000000000103e00, 0x0000000000123E81, 0xFFFFFFFF00123F09, 0xFFFFFFFE00123F11 # 944 - 947 D7 S3 C0 - .quad 0xfffffffd00123f19, 0xFFFFFFFC00123F21, 0xFFFFFFFB00153F29, 0xFFFFFFFA00153F31 # 948 - 951 D7 S3 C4 - .quad 0xfffffff900153f39, 0x0000000000123E81, 0xFFFFFFFF00123F09, 0xFFFFFFFE00123F11 # 952 - 955 D7 S3 C8 - .quad 0xfffffffd00123f19, 0xFFFFFFFC00123F21, 0xFFFFFFFB00153F29, 0xFFFFFFFA00153F31 # 956 - 959 D7 S3 C12 - .quad 0x0000000000104e00, 0x0000000000124E81, 0xFFFFFFFF00124F09, 0xFFFFFFFE00124F11 # 960 - 963 D7 S4 C0 - .quad 0xfffffffd00124f19, 0xFFFFFFFC00154F21, 0xFFFFFFFB00154F29, 0xFFFFFFFA00154F31 # 964 - 967 D7 S4 C4 - .quad 0xfffffff900154f39, 0x0000000000124E81, 0xFFFFFFFF00124F09, 0xFFFFFFFE00124F11 # 968 - 971 D7 S4 C8 - .quad 0xfffffffd00124f19, 0xFFFFFFFC00154F21, 0xFFFFFFFB00154F29, 0xFFFFFFFA00154F31 # 972 - 975 D7 S4 C12 - .quad 0x0000000000105e00, 0x0000000000125E81, 0xFFFFFFFF00125F09, 0xFFFFFFFE00125F11 # 976 - 979 D7 S5 C0 - .quad 0xfffffffd00155f19, 0xFFFFFFFC00155F21, 0xFFFFFFFB00155F29, 0xFFFFFFFA00155F31 # 980 - 983 D7 S5 C4 - .quad 0xfffffff900155f39, 0x0000000000125E81, 0xFFFFFFFF00125F09, 0xFFFFFFFE00125F11 # 984 - 987 D7 S5 C8 - .quad 0xfffffffd00155f19, 0xFFFFFFFC00155F21, 0xFFFFFFFB00155F29, 0xFFFFFFFA00155F31 # 988 - 991 D7 S5 C12 - .quad 0x0000000000106e00, 0x0000000000126E81, 0xFFFFFFFF00126F09, 0xFFFFFFFE00156F11 # 992 - 995 D7 S6 C0 - .quad 0xfffffffd00156f19, 0xFFFFFFFC00156F21, 0xFFFFFFFB00156F29, 0xFFFFFFFA00156F31 # 996 - 999 D7 S6 C4 - .quad 0xfffffff900156f39, 0x0000000000126E81, 0xFFFFFFFF00126F09, 0xFFFFFFFE00156F11 # 1000 - 1003 D7 S6 C8 - .quad 0xfffffffd00156f19, 0xFFFFFFFC00156F21, 0xFFFFFFFB00156F29, 0xFFFFFFFA00156F31 # 1004 - 1007 D7 S6 C12 - .quad 0x0000000000007e00, 0x00000000000A7E81, 0xFFFFFFFF000C7F09, 0xFFFFFFFE000C7F11 # 1008 - 1011 D7 S7 C0 - .quad 0xfffffffd000c7f19, 0xFFFFFFFC000C7F21, 0xFFFFFFFB000C7F29, 0xFFFFFFFA000C7F31 # 1012 - 1015 D7 S7 C4 - .quad 0xfffffff9000c7f39, 0x00000000000A7E81, 0xFFFFFFFF000C7F09, 0xFFFFFFFE000C7F11 # 1016 - 1019 D7 S7 C8 - .quad 0xfffffffd000c7f19, 0xFFFFFFFC000C7F21, 0xFFFFFFFB000C7F29, 0xFFFFFFFA000C7F31 # 1020 - 1023 D7 S7 C12 - .quad 0x0000000000000000, 0xFFFFFFFF00020088, 0xFFFFFFFE00020090, 0xFFFFFFFD00020098 # 1024 - 1027 D0 S0 C0 neq - .quad 0xfffffffc000200a0, 0xFFFFFFFB000200A8, 0xFFFFFFFA000200B0, 0xFFFFFFF9000200B8 # 1028 - 1031 D0 S0 C4 neq - .quad 0xfffffff8000200c0, 0xFFFFFFFF00020088, 0xFFFFFFFE00020090, 0xFFFFFFFD00020098 # 1032 - 1035 D0 S0 C8 neq - .quad 0xfffffffc000200a0, 0xFFFFFFFB000200A8, 0xFFFFFFFA000200B0, 0xFFFFFFF9000200B8 # 1036 - 1039 D0 S0 C12 neq - .quad 0x0000000000101000, 0xFFFFFFFF00121088, 0xFFFFFFFE00121090, 0xFFFFFFFD00121098 # 1040 - 1043 D0 S1 C0 neq - .quad 0xfffffffc001210a0, 0xFFFFFFFB001210A8, 0xFFFFFFFA001210B0, 0xFFFFFFF9001210B8 # 1044 - 1047 D0 S1 C4 neq - .quad 0xfffffff8001510c0, 0xFFFFFFFF00121088, 0xFFFFFFFE00121090, 0xFFFFFFFD00121098 # 1048 - 1051 D0 S1 C8 neq - .quad 0xfffffffc001210a0, 0xFFFFFFFB001210A8, 0xFFFFFFFA001210B0, 0xFFFFFFF9001210B8 # 1052 - 1055 D0 S1 C12 neq - .quad 0x0000000000102000, 0xFFFFFFFF00122088, 0xFFFFFFFE00122090, 0xFFFFFFFD00122098 # 1056 - 1059 D0 S2 C0 neq - .quad 0xfffffffc001220a0, 0xFFFFFFFB001220A8, 0xFFFFFFFA001220B0, 0xFFFFFFF9001520B8 # 1060 - 1063 D0 S2 C4 neq - .quad 0xfffffff8001520c0, 0xFFFFFFFF00122088, 0xFFFFFFFE00122090, 0xFFFFFFFD00122098 # 1064 - 1067 D0 S2 C8 neq - .quad 0xfffffffc001220a0, 0xFFFFFFFB001220A8, 0xFFFFFFFA001220B0, 0xFFFFFFF9001520B8 # 1068 - 1071 D0 S2 C12 neq - .quad 0x0000000000103000, 0xFFFFFFFF00123088, 0xFFFFFFFE00123090, 0xFFFFFFFD00123098 # 1072 - 1075 D0 S3 C0 neq - .quad 0xfffffffc001230a0, 0xFFFFFFFB001230A8, 0xFFFFFFFA001530B0, 0xFFFFFFF9001530B8 # 1076 - 1079 D0 S3 C4 neq - .quad 0xfffffff8001530c0, 0xFFFFFFFF00123088, 0xFFFFFFFE00123090, 0xFFFFFFFD00123098 # 1080 - 1083 D0 S3 C8 neq - .quad 0xfffffffc001230a0, 0xFFFFFFFB001230A8, 0xFFFFFFFA001530B0, 0xFFFFFFF9001530B8 # 1084 - 1087 D0 S3 C12 neq - .quad 0x0000000000104000, 0xFFFFFFFF00124088, 0xFFFFFFFE00124090, 0xFFFFFFFD00124098 # 1088 - 1091 D0 S4 C0 neq - .quad 0xfffffffc001240a0, 0xFFFFFFFB001540A8, 0xFFFFFFFA001540B0, 0xFFFFFFF9001540B8 # 1092 - 1095 D0 S4 C4 neq - .quad 0xfffffff8001540c0, 0xFFFFFFFF00124088, 0xFFFFFFFE00124090, 0xFFFFFFFD00124098 # 1096 - 1099 D0 S4 C8 neq - .quad 0xfffffffc001240a0, 0xFFFFFFFB001540A8, 0xFFFFFFFA001540B0, 0xFFFFFFF9001540B8 # 1100 - 1103 D0 S4 C12 neq - .quad 0x0000000000105000, 0xFFFFFFFF00125088, 0xFFFFFFFE00125090, 0xFFFFFFFD00125098 # 1104 - 1107 D0 S5 C0 neq - .quad 0xfffffffc001550a0, 0xFFFFFFFB001550A8, 0xFFFFFFFA001550B0, 0xFFFFFFF9001550B8 # 1108 - 1111 D0 S5 C4 neq - .quad 0xfffffff8001550c0, 0xFFFFFFFF00125088, 0xFFFFFFFE00125090, 0xFFFFFFFD00125098 # 1112 - 1115 D0 S5 C8 neq - .quad 0xfffffffc001550a0, 0xFFFFFFFB001550A8, 0xFFFFFFFA001550B0, 0xFFFFFFF9001550B8 # 1116 - 1119 D0 S5 C12 neq - .quad 0x0000000000106000, 0xFFFFFFFF00126088, 0xFFFFFFFE00126090, 0xFFFFFFFD00156098 # 1120 - 1123 D0 S6 C0 neq - .quad 0xfffffffc001560a0, 0xFFFFFFFB001560A8, 0xFFFFFFFA001560B0, 0xFFFFFFF9001560B8 # 1124 - 1127 D0 S6 C4 neq - .quad 0xfffffff8001560c0, 0xFFFFFFFF00126088, 0xFFFFFFFE00126090, 0xFFFFFFFD00156098 # 1128 - 1131 D0 S6 C8 neq - .quad 0xfffffffc001560a0, 0xFFFFFFFB001560A8, 0xFFFFFFFA001560B0, 0xFFFFFFF9001560B8 # 1132 - 1135 D0 S6 C12 neq - .quad 0x0000000000107000, 0xFFFFFFFF00127088, 0xFFFFFFFE00157090, 0xFFFFFFFD00157098 # 1136 - 1139 D0 S7 C0 neq - .quad 0xfffffffc001570a0, 0xFFFFFFFB001570A8, 0xFFFFFFFA001570B0, 0xFFFFFFF9001570B8 # 1140 - 1143 D0 S7 C4 neq - .quad 0xfffffff8001570c0, 0xFFFFFFFF00127088, 0xFFFFFFFE00157090, 0xFFFFFFFD00157098 # 1144 - 1147 D0 S7 C8 neq - .quad 0xfffffffc001570a0, 0xFFFFFFFB001570A8, 0xFFFFFFFA001570B0, 0xFFFFFFF9001570B8 # 1148 - 1151 D0 S7 C12 neq - .quad 0x0000000000100200, 0x0000000000120281, 0x0000000000120282, 0x0000000000120283 # 1152 - 1155 D1 S0 C0 neq - .quad 0x0000000000120284, 0x0000000000120285, 0x0000000000120286, 0x0000000000120287 # 1156 - 1159 D1 S0 C4 neq - .quad 0xffffffff0012030f, 0xFFFFFFFE00150317, 0xFFFFFFFD0015031F, 0xFFFFFFFC00150327 # 1160 - 1163 D1 S0 C8 neq - .quad 0xfffffffb0015032f, 0xFFFFFFFA00150337, 0xFFFFFFF90015033F, 0xFFFFFFF800150347 # 1164 - 1167 D1 S0 C12 neq - .quad 0x0000000000001200, 0x0000000000021281, 0x0000000000021282, 0x0000000000021283 # 1168 - 1171 D1 S1 C0 neq - .quad 0x0000000000021284, 0x0000000000021285, 0x0000000000021286, 0x00000000000A1287 # 1172 - 1175 D1 S1 C4 neq - .quad 0xffffffff000c130f, 0xFFFFFFFE000C1317, 0xFFFFFFFD000C131F, 0xFFFFFFFC000C1327 # 1176 - 1179 D1 S1 C8 neq - .quad 0xfffffffb000c132f, 0xFFFFFFFA000C1337, 0xFFFFFFF9000C133F, 0xFFFFFFF8000C1347 # 1180 - 1183 D1 S1 C12 neq - .quad 0x0000000000102200, 0x0000000000122281, 0x0000000000122282, 0x0000000000122283 # 1184 - 1187 D1 S2 C0 neq - .quad 0x0000000000122284, 0x0000000000122285, 0x00000000001A2286, 0x00000000001CA287 # 1188 - 1191 D1 S2 C4 neq - .quad 0xffffffff001ca30f, 0xFFFFFFFE001CA317, 0xFFFFFFFD001CA31F, 0xFFFFFFFC001CA327 # 1192 - 1195 D1 S2 C8 neq - .quad 0xfffffffb001ca32f, 0xFFFFFFFA001CA337, 0xFFFFFFF9001CA33F, 0xFFFFFFF8001FA347 # 1196 - 1199 D1 S2 C12 neq - .quad 0x0000000000103200, 0x0000000000123281, 0x0000000000123282, 0x0000000000123283 # 1200 - 1203 D1 S3 C0 neq - .quad 0x0000000000123284, 0x00000000001A3285, 0x00000000001CB286, 0x00000000001CB287 # 1204 - 1207 D1 S3 C4 neq - .quad 0xffffffff001cb30f, 0xFFFFFFFE001CB317, 0xFFFFFFFD001CB31F, 0xFFFFFFFC001CB327 # 1208 - 1211 D1 S3 C8 neq - .quad 0xfffffffb001cb32f, 0xFFFFFFFA001CB337, 0xFFFFFFF9001FB33F, 0xFFFFFFF8001FB347 # 1212 - 1215 D1 S3 C12 neq - .quad 0x0000000000104200, 0x0000000000124281, 0x0000000000124282, 0x0000000000124283 # 1216 - 1219 D1 S4 C0 neq - .quad 0x00000000001a4284, 0x00000000001CC285, 0x00000000001CC286, 0x00000000001CC287 # 1220 - 1223 D1 S4 C4 neq - .quad 0xffffffff001cc30f, 0xFFFFFFFE001CC317, 0xFFFFFFFD001CC31F, 0xFFFFFFFC001CC327 # 1224 - 1227 D1 S4 C8 neq - .quad 0xfffffffb001cc32f, 0xFFFFFFFA001FC337, 0xFFFFFFF9001FC33F, 0xFFFFFFF8001FC347 # 1228 - 1231 D1 S4 C12 neq - .quad 0x0000000000105200, 0x0000000000125281, 0x0000000000125282, 0x00000000001A5283 # 1232 - 1235 D1 S5 C0 neq - .quad 0x00000000001cd284, 0x00000000001CD285, 0x00000000001CD286, 0x00000000001CD287 # 1236 - 1239 D1 S5 C4 neq - .quad 0xffffffff001cd30f, 0xFFFFFFFE001CD317, 0xFFFFFFFD001CD31F, 0xFFFFFFFC001CD327 # 1240 - 1243 D1 S5 C8 neq - .quad 0xfffffffb001fd32f, 0xFFFFFFFA001FD337, 0xFFFFFFF9001FD33F, 0xFFFFFFF8001FD347 # 1244 - 1247 D1 S5 C12 neq - .quad 0x0000000000106200, 0x0000000000126281, 0x00000000001A6282, 0x00000000001CE283 # 1248 - 1251 D1 S6 C0 neq - .quad 0x00000000001ce284, 0x00000000001CE285, 0x00000000001CE286, 0x00000000001CE287 # 1252 - 1255 D1 S6 C4 neq - .quad 0xffffffff001ce30f, 0xFFFFFFFE001CE317, 0xFFFFFFFD001CE31F, 0xFFFFFFFC001FE327 # 1256 - 1259 D1 S6 C8 neq - .quad 0xfffffffb001fe32f, 0xFFFFFFFA001FE337, 0xFFFFFFF9001FE33F, 0xFFFFFFF8001FE347 # 1260 - 1263 D1 S6 C12 neq - .quad 0x0000000000107200, 0x00000000001A7281, 0x00000000001CF282, 0x00000000001CF283 # 1264 - 1267 D1 S7 C0 neq - .quad 0x00000000001cf284, 0x00000000001CF285, 0x00000000001CF286, 0x00000000001CF287 # 1268 - 1271 D1 S7 C4 neq - .quad 0xffffffff001cf30f, 0xFFFFFFFE001CF317, 0xFFFFFFFD001FF31F, 0xFFFFFFFC001FF327 # 1272 - 1275 D1 S7 C8 neq - .quad 0xfffffffb001ff32f, 0xFFFFFFFA001FF337, 0xFFFFFFF9001FF33F, 0xFFFFFFF8001FF347 # 1276 - 1279 D1 S7 C12 neq - .quad 0x0000000000100400, 0x0000000000120481, 0x0000000000120482, 0x0000000000120483 # 1280 - 1283 D2 S0 C0 neq - .quad 0x0000000000120484, 0x0000000000120485, 0x0000000000120486, 0xFFFFFFFF0012050E # 1284 - 1287 D2 S0 C4 neq - .quad 0xfffffffe00120516, 0xFFFFFFFD0015051E, 0xFFFFFFFC00150526, 0xFFFFFFFB0015052E # 1288 - 1291 D2 S0 C8 neq - .quad 0xfffffffa00150536, 0xFFFFFFF90015053E, 0xFFFFFFF800150546, 0xFFFFFFFF0012050E # 1292 - 1295 D2 S0 C12 neq - .quad 0x0000000000101400, 0x0000000000121481, 0x0000000000121482, 0x0000000000121483 # 1296 - 1299 D2 S1 C0 neq - .quad 0x0000000000121484, 0x0000000000121485, 0x0000000000121486, 0xFFFFFFFF0012150E # 1300 - 1303 D2 S1 C4 neq - .quad 0xfffffffe00151516, 0xFFFFFFFD0015151E, 0xFFFFFFFC00151526, 0xFFFFFFFB0015152E # 1304 - 1307 D2 S1 C8 neq - .quad 0xfffffffa00151536, 0xFFFFFFF90015153E, 0xFFFFFFF800151546, 0xFFFFFFFF0012150E # 1308 - 1311 D2 S1 C12 neq - .quad 0x0000000000002400, 0x0000000000022481, 0x0000000000022482, 0x0000000000022483 # 1312 - 1315 D2 S2 C0 neq - .quad 0x0000000000022484, 0x0000000000022485, 0x00000000000A2486, 0xFFFFFFFF000C250E # 1316 - 1319 D2 S2 C4 neq - .quad 0xfffffffe000c2516, 0xFFFFFFFD000C251E, 0xFFFFFFFC000C2526, 0xFFFFFFFB000C252E # 1320 - 1323 D2 S2 C8 neq - .quad 0xfffffffa000c2536, 0xFFFFFFF9000C253E, 0xFFFFFFF8000C2546, 0xFFFFFFFF000C250E # 1324 - 1327 D2 S2 C12 neq - .quad 0x0000000000103400, 0x0000000000123481, 0x0000000000123482, 0x0000000000123483 # 1328 - 1331 D2 S3 C0 neq - .quad 0x0000000000123484, 0x00000000001A3485, 0x00000000001CB486, 0xFFFFFFFF001CB50E # 1332 - 1335 D2 S3 C4 neq - .quad 0xfffffffe001cb516, 0xFFFFFFFD001CB51E, 0xFFFFFFFC001CB526, 0xFFFFFFFB001CB52E # 1336 - 1339 D2 S3 C8 neq - .quad 0xfffffffa001cb536, 0xFFFFFFF9001CB53E, 0xFFFFFFF8001FB546, 0xFFFFFFFF001CB50E # 1340 - 1343 D2 S3 C12 neq - .quad 0x0000000000104400, 0x0000000000124481, 0x0000000000124482, 0x0000000000124483 # 1344 - 1347 D2 S4 C0 neq - .quad 0x00000000001a4484, 0x00000000001CC485, 0x00000000001CC486, 0xFFFFFFFF001CC50E # 1348 - 1351 D2 S4 C4 neq - .quad 0xfffffffe001cc516, 0xFFFFFFFD001CC51E, 0xFFFFFFFC001CC526, 0xFFFFFFFB001CC52E # 1352 - 1355 D2 S4 C8 neq - .quad 0xfffffffa001cc536, 0xFFFFFFF9001FC53E, 0xFFFFFFF8001FC546, 0xFFFFFFFF001CC50E # 1356 - 1359 D2 S4 C12 neq - .quad 0x0000000000105400, 0x0000000000125481, 0x0000000000125482, 0x00000000001A5483 # 1360 - 1363 D2 S5 C0 neq - .quad 0x00000000001cd484, 0x00000000001CD485, 0x00000000001CD486, 0xFFFFFFFF001CD50E # 1364 - 1367 D2 S5 C4 neq - .quad 0xfffffffe001cd516, 0xFFFFFFFD001CD51E, 0xFFFFFFFC001CD526, 0xFFFFFFFB001CD52E # 1368 - 1371 D2 S5 C8 neq - .quad 0xfffffffa001fd536, 0xFFFFFFF9001FD53E, 0xFFFFFFF8001FD546, 0xFFFFFFFF001CD50E # 1372 - 1375 D2 S5 C12 neq - .quad 0x0000000000106400, 0x0000000000126481, 0x00000000001A6482, 0x00000000001CE483 # 1376 - 1379 D2 S6 C0 neq - .quad 0x00000000001ce484, 0x00000000001CE485, 0x00000000001CE486, 0xFFFFFFFF001CE50E # 1380 - 1383 D2 S6 C4 neq - .quad 0xfffffffe001ce516, 0xFFFFFFFD001CE51E, 0xFFFFFFFC001CE526, 0xFFFFFFFB001FE52E # 1384 - 1387 D2 S6 C8 neq - .quad 0xfffffffa001fe536, 0xFFFFFFF9001FE53E, 0xFFFFFFF8001FE546, 0xFFFFFFFF001CE50E # 1388 - 1391 D2 S6 C12 neq - .quad 0x0000000000107400, 0x00000000001A7481, 0x00000000001CF482, 0x00000000001CF483 # 1392 - 1395 D2 S7 C0 neq - .quad 0x00000000001cf484, 0x00000000001CF485, 0x00000000001CF486, 0xFFFFFFFF001CF50E # 1396 - 1399 D2 S7 C4 neq - .quad 0xfffffffe001cf516, 0xFFFFFFFD001CF51E, 0xFFFFFFFC001FF526, 0xFFFFFFFB001FF52E # 1400 - 1403 D2 S7 C8 neq - .quad 0xfffffffa001ff536, 0xFFFFFFF9001FF53E, 0xFFFFFFF8001FF546, 0xFFFFFFFF001CF50E # 1404 - 1407 D2 S7 C12 neq - .quad 0x0000000000100600, 0x0000000000120681, 0x0000000000120682, 0x0000000000120683 # 1408 - 1411 D3 S0 C0 neq - .quad 0x0000000000120684, 0x0000000000120685, 0xFFFFFFFF0012070D, 0xFFFFFFFE00120715 # 1412 - 1415 D3 S0 C4 neq - .quad 0xfffffffd0012071d, 0xFFFFFFFC00150725, 0xFFFFFFFB0015072D, 0xFFFFFFFA00150735 # 1416 - 1419 D3 S0 C8 neq - .quad 0xfffffff90015073d, 0xFFFFFFF800150745, 0xFFFFFFFF0012070D, 0xFFFFFFFE00120715 # 1420 - 1423 D3 S0 C12 neq - .quad 0x0000000000101600, 0x0000000000121681, 0x0000000000121682, 0x0000000000121683 # 1424 - 1427 D3 S1 C0 neq - .quad 0x0000000000121684, 0x0000000000121685, 0xFFFFFFFF0012170D, 0xFFFFFFFE00121715 # 1428 - 1431 D3 S1 C4 neq - .quad 0xfffffffd0015171d, 0xFFFFFFFC00151725, 0xFFFFFFFB0015172D, 0xFFFFFFFA00151735 # 1432 - 1435 D3 S1 C8 neq - .quad 0xfffffff90015173d, 0xFFFFFFF800151745, 0xFFFFFFFF0012170D, 0xFFFFFFFE00121715 # 1436 - 1439 D3 S1 C12 neq - .quad 0x0000000000102600, 0x0000000000122681, 0x0000000000122682, 0x0000000000122683 # 1440 - 1443 D3 S2 C0 neq - .quad 0x0000000000122684, 0x0000000000122685, 0xFFFFFFFF0012270D, 0xFFFFFFFE00152715 # 1444 - 1447 D3 S2 C4 neq - .quad 0xfffffffd0015271d, 0xFFFFFFFC00152725, 0xFFFFFFFB0015272D, 0xFFFFFFFA00152735 # 1448 - 1451 D3 S2 C8 neq - .quad 0xfffffff90015273d, 0xFFFFFFF800152745, 0xFFFFFFFF0012270D, 0xFFFFFFFE00152715 # 1452 - 1455 D3 S2 C12 neq - .quad 0x0000000000003600, 0x0000000000023681, 0x0000000000023682, 0x0000000000023683 # 1456 - 1459 D3 S3 C0 neq - .quad 0x0000000000023684, 0x00000000000A3685, 0xFFFFFFFF000C370D, 0xFFFFFFFE000C3715 # 1460 - 1463 D3 S3 C4 neq - .quad 0xfffffffd000c371d, 0xFFFFFFFC000C3725, 0xFFFFFFFB000C372D, 0xFFFFFFFA000C3735 # 1464 - 1467 D3 S3 C8 neq - .quad 0xfffffff9000c373d, 0xFFFFFFF8000C3745, 0xFFFFFFFF000C370D, 0xFFFFFFFE000C3715 # 1468 - 1471 D3 S3 C12 neq - .quad 0x0000000000104600, 0x0000000000124681, 0x0000000000124682, 0x0000000000124683 # 1472 - 1475 D3 S4 C0 neq - .quad 0x00000000001a4684, 0x00000000001CC685, 0xFFFFFFFF001CC70D, 0xFFFFFFFE001CC715 # 1476 - 1479 D3 S4 C4 neq - .quad 0xfffffffd001cc71d, 0xFFFFFFFC001CC725, 0xFFFFFFFB001CC72D, 0xFFFFFFFA001CC735 # 1480 - 1483 D3 S4 C8 neq - .quad 0xfffffff9001cc73d, 0xFFFFFFF8001FC745, 0xFFFFFFFF001CC70D, 0xFFFFFFFE001CC715 # 1484 - 1487 D3 S4 C12 neq - .quad 0x0000000000105600, 0x0000000000125681, 0x0000000000125682, 0x00000000001A5683 # 1488 - 1491 D3 S5 C0 neq - .quad 0x00000000001cd684, 0x00000000001CD685, 0xFFFFFFFF001CD70D, 0xFFFFFFFE001CD715 # 1492 - 1495 D3 S5 C4 neq - .quad 0xfffffffd001cd71d, 0xFFFFFFFC001CD725, 0xFFFFFFFB001CD72D, 0xFFFFFFFA001CD735 # 1496 - 1499 D3 S5 C8 neq - .quad 0xfffffff9001fd73d, 0xFFFFFFF8001FD745, 0xFFFFFFFF001CD70D, 0xFFFFFFFE001CD715 # 1500 - 1503 D3 S5 C12 neq - .quad 0x0000000000106600, 0x0000000000126681, 0x00000000001A6682, 0x00000000001CE683 # 1504 - 1507 D3 S6 C0 neq - .quad 0x00000000001ce684, 0x00000000001CE685, 0xFFFFFFFF001CE70D, 0xFFFFFFFE001CE715 # 1508 - 1511 D3 S6 C4 neq - .quad 0xfffffffd001ce71d, 0xFFFFFFFC001CE725, 0xFFFFFFFB001CE72D, 0xFFFFFFFA001FE735 # 1512 - 1515 D3 S6 C8 neq - .quad 0xfffffff9001fe73d, 0xFFFFFFF8001FE745, 0xFFFFFFFF001CE70D, 0xFFFFFFFE001CE715 # 1516 - 1519 D3 S6 C12 neq - .quad 0x0000000000107600, 0x00000000001A7681, 0x00000000001CF682, 0x00000000001CF683 # 1520 - 1523 D3 S7 C0 neq - .quad 0x00000000001cf684, 0x00000000001CF685, 0xFFFFFFFF001CF70D, 0xFFFFFFFE001CF715 # 1524 - 1527 D3 S7 C4 neq - .quad 0xfffffffd001cf71d, 0xFFFFFFFC001CF725, 0xFFFFFFFB001FF72D, 0xFFFFFFFA001FF735 # 1528 - 1531 D3 S7 C8 neq - .quad 0xfffffff9001ff73d, 0xFFFFFFF8001FF745, 0xFFFFFFFF001CF70D, 0xFFFFFFFE001CF715 # 1532 - 1535 D3 S7 C12 neq - .quad 0x0000000000100800, 0x0000000000120881, 0x0000000000120882, 0x0000000000120883 # 1536 - 1539 D4 S0 C0 neq - .quad 0x0000000000120884, 0xFFFFFFFF0012090C, 0xFFFFFFFE00120914, 0xFFFFFFFD0012091C # 1540 - 1543 D4 S0 C4 neq - .quad 0xfffffffc00120924, 0xFFFFFFFB0015092C, 0xFFFFFFFA00150934, 0xFFFFFFF90015093C # 1544 - 1547 D4 S0 C8 neq - .quad 0xfffffff800150944, 0xFFFFFFFF0012090C, 0xFFFFFFFE00120914, 0xFFFFFFFD0012091C # 1548 - 1551 D4 S0 C12 neq - .quad 0x0000000000101800, 0x0000000000121881, 0x0000000000121882, 0x0000000000121883 # 1552 - 1555 D4 S1 C0 neq - .quad 0x0000000000121884, 0xFFFFFFFF0012190C, 0xFFFFFFFE00121914, 0xFFFFFFFD0012191C # 1556 - 1559 D4 S1 C4 neq - .quad 0xfffffffc00151924, 0xFFFFFFFB0015192C, 0xFFFFFFFA00151934, 0xFFFFFFF90015193C # 1560 - 1563 D4 S1 C8 neq - .quad 0xfffffff800151944, 0xFFFFFFFF0012190C, 0xFFFFFFFE00121914, 0xFFFFFFFD0012191C # 1564 - 1567 D4 S1 C12 neq - .quad 0x0000000000102800, 0x0000000000122881, 0x0000000000122882, 0x0000000000122883 # 1568 - 1571 D4 S2 C0 neq - .quad 0x0000000000122884, 0xFFFFFFFF0012290C, 0xFFFFFFFE00122914, 0xFFFFFFFD0015291C # 1572 - 1575 D4 S2 C4 neq - .quad 0xfffffffc00152924, 0xFFFFFFFB0015292C, 0xFFFFFFFA00152934, 0xFFFFFFF90015293C # 1576 - 1579 D4 S2 C8 neq - .quad 0xfffffff800152944, 0xFFFFFFFF0012290C, 0xFFFFFFFE00122914, 0xFFFFFFFD0015291C # 1580 - 1583 D4 S2 C12 neq - .quad 0x0000000000103800, 0x0000000000123881, 0x0000000000123882, 0x0000000000123883 # 1584 - 1587 D4 S3 C0 neq - .quad 0x0000000000123884, 0xFFFFFFFF0012390C, 0xFFFFFFFE00153914, 0xFFFFFFFD0015391C # 1588 - 1591 D4 S3 C4 neq - .quad 0xfffffffc00153924, 0xFFFFFFFB0015392C, 0xFFFFFFFA00153934, 0xFFFFFFF90015393C # 1592 - 1595 D4 S3 C8 neq - .quad 0xfffffff800153944, 0xFFFFFFFF0012390C, 0xFFFFFFFE00153914, 0xFFFFFFFD0015391C # 1596 - 1599 D4 S3 C12 neq - .quad 0x0000000000004800, 0x0000000000024881, 0x0000000000024882, 0x0000000000024883 # 1600 - 1603 D4 S4 C0 neq - .quad 0x00000000000a4884, 0xFFFFFFFF000C490C, 0xFFFFFFFE000C4914, 0xFFFFFFFD000C491C # 1604 - 1607 D4 S4 C4 neq - .quad 0xfffffffc000c4924, 0xFFFFFFFB000C492C, 0xFFFFFFFA000C4934, 0xFFFFFFF9000C493C # 1608 - 1611 D4 S4 C8 neq - .quad 0xfffffff8000c4944, 0xFFFFFFFF000C490C, 0xFFFFFFFE000C4914, 0xFFFFFFFD000C491C # 1612 - 1615 D4 S4 C12 neq - .quad 0x0000000000105800, 0x0000000000125881, 0x0000000000125882, 0x00000000001A5883 # 1616 - 1619 D4 S5 C0 neq - .quad 0x00000000001cd884, 0xFFFFFFFF001CD90C, 0xFFFFFFFE001CD914, 0xFFFFFFFD001CD91C # 1620 - 1623 D4 S5 C4 neq - .quad 0xfffffffc001cd924, 0xFFFFFFFB001CD92C, 0xFFFFFFFA001CD934, 0xFFFFFFF9001CD93C # 1624 - 1627 D4 S5 C8 neq - .quad 0xfffffff8001fd944, 0xFFFFFFFF001CD90C, 0xFFFFFFFE001CD914, 0xFFFFFFFD001CD91C # 1628 - 1631 D4 S5 C12 neq - .quad 0x0000000000106800, 0x0000000000126881, 0x00000000001A6882, 0x00000000001CE883 # 1632 - 1635 D4 S6 C0 neq - .quad 0x00000000001ce884, 0xFFFFFFFF001CE90C, 0xFFFFFFFE001CE914, 0xFFFFFFFD001CE91C # 1636 - 1639 D4 S6 C4 neq - .quad 0xfffffffc001ce924, 0xFFFFFFFB001CE92C, 0xFFFFFFFA001CE934, 0xFFFFFFF9001FE93C # 1640 - 1643 D4 S6 C8 neq - .quad 0xfffffff8001fe944, 0xFFFFFFFF001CE90C, 0xFFFFFFFE001CE914, 0xFFFFFFFD001CE91C # 1644 - 1647 D4 S6 C12 neq - .quad 0x0000000000107800, 0x00000000001A7881, 0x00000000001CF882, 0x00000000001CF883 # 1648 - 1651 D4 S7 C0 neq - .quad 0x00000000001cf884, 0xFFFFFFFF001CF90C, 0xFFFFFFFE001CF914, 0xFFFFFFFD001CF91C # 1652 - 1655 D4 S7 C4 neq - .quad 0xfffffffc001cf924, 0xFFFFFFFB001CF92C, 0xFFFFFFFA001FF934, 0xFFFFFFF9001FF93C # 1656 - 1659 D4 S7 C8 neq - .quad 0xfffffff8001ff944, 0xFFFFFFFF001CF90C, 0xFFFFFFFE001CF914, 0xFFFFFFFD001CF91C # 1660 - 1663 D4 S7 C12 neq - .quad 0x0000000000100a00, 0x0000000000120A81, 0x0000000000120A82, 0x0000000000120A83 # 1664 - 1667 D5 S0 C0 neq - .quad 0xffffffff00120b0b, 0xFFFFFFFE00120B13, 0xFFFFFFFD00120B1B, 0xFFFFFFFC00120B23 # 1668 - 1671 D5 S0 C4 neq - .quad 0xfffffffb00120b2b, 0xFFFFFFFA00150B33, 0xFFFFFFF900150B3B, 0xFFFFFFF800150B43 # 1672 - 1675 D5 S0 C8 neq - .quad 0xffffffff00120b0b, 0xFFFFFFFE00120B13, 0xFFFFFFFD00120B1B, 0xFFFFFFFC00120B23 # 1676 - 1679 D5 S0 C12 neq - .quad 0x0000000000101a00, 0x0000000000121A81, 0x0000000000121A82, 0x0000000000121A83 # 1680 - 1683 D5 S1 C0 neq - .quad 0xffffffff00121b0b, 0xFFFFFFFE00121B13, 0xFFFFFFFD00121B1B, 0xFFFFFFFC00121B23 # 1684 - 1687 D5 S1 C4 neq - .quad 0xfffffffb00151b2b, 0xFFFFFFFA00151B33, 0xFFFFFFF900151B3B, 0xFFFFFFF800151B43 # 1688 - 1691 D5 S1 C8 neq - .quad 0xffffffff00121b0b, 0xFFFFFFFE00121B13, 0xFFFFFFFD00121B1B, 0xFFFFFFFC00121B23 # 1692 - 1695 D5 S1 C12 neq - .quad 0x0000000000102a00, 0x0000000000122A81, 0x0000000000122A82, 0x0000000000122A83 # 1696 - 1699 D5 S2 C0 neq - .quad 0xffffffff00122b0b, 0xFFFFFFFE00122B13, 0xFFFFFFFD00122B1B, 0xFFFFFFFC00152B23 # 1700 - 1703 D5 S2 C4 neq - .quad 0xfffffffb00152b2b, 0xFFFFFFFA00152B33, 0xFFFFFFF900152B3B, 0xFFFFFFF800152B43 # 1704 - 1707 D5 S2 C8 neq - .quad 0xffffffff00122b0b, 0xFFFFFFFE00122B13, 0xFFFFFFFD00122B1B, 0xFFFFFFFC00152B23 # 1708 - 1711 D5 S2 C12 neq - .quad 0x0000000000103a00, 0x0000000000123A81, 0x0000000000123A82, 0x0000000000123A83 # 1712 - 1715 D5 S3 C0 neq - .quad 0xffffffff00123b0b, 0xFFFFFFFE00123B13, 0xFFFFFFFD00153B1B, 0xFFFFFFFC00153B23 # 1716 - 1719 D5 S3 C4 neq - .quad 0xfffffffb00153b2b, 0xFFFFFFFA00153B33, 0xFFFFFFF900153B3B, 0xFFFFFFF800153B43 # 1720 - 1723 D5 S3 C8 neq - .quad 0xffffffff00123b0b, 0xFFFFFFFE00123B13, 0xFFFFFFFD00153B1B, 0xFFFFFFFC00153B23 # 1724 - 1727 D5 S3 C12 neq - .quad 0x0000000000104a00, 0x0000000000124A81, 0x0000000000124A82, 0x0000000000124A83 # 1728 - 1731 D5 S4 C0 neq - .quad 0xffffffff00124b0b, 0xFFFFFFFE00154B13, 0xFFFFFFFD00154B1B, 0xFFFFFFFC00154B23 # 1732 - 1735 D5 S4 C4 neq - .quad 0xfffffffb00154b2b, 0xFFFFFFFA00154B33, 0xFFFFFFF900154B3B, 0xFFFFFFF800154B43 # 1736 - 1739 D5 S4 C8 neq - .quad 0xffffffff00124b0b, 0xFFFFFFFE00154B13, 0xFFFFFFFD00154B1B, 0xFFFFFFFC00154B23 # 1740 - 1743 D5 S4 C12 neq - .quad 0x0000000000005a00, 0x0000000000025A81, 0x0000000000025A82, 0x00000000000A5A83 # 1744 - 1747 D5 S5 C0 neq - .quad 0xffffffff000c5b0b, 0xFFFFFFFE000C5B13, 0xFFFFFFFD000C5B1B, 0xFFFFFFFC000C5B23 # 1748 - 1751 D5 S5 C4 neq - .quad 0xfffffffb000c5b2b, 0xFFFFFFFA000C5B33, 0xFFFFFFF9000C5B3B, 0xFFFFFFF8000C5B43 # 1752 - 1755 D5 S5 C8 neq - .quad 0xffffffff000c5b0b, 0xFFFFFFFE000C5B13, 0xFFFFFFFD000C5B1B, 0xFFFFFFFC000C5B23 # 1756 - 1759 D5 S5 C12 neq - .quad 0x0000000000106a00, 0x0000000000126A81, 0x00000000001A6A82, 0x00000000001CEA83 # 1760 - 1763 D5 S6 C0 neq - .quad 0xffffffff001ceb0b, 0xFFFFFFFE001CEB13, 0xFFFFFFFD001CEB1B, 0xFFFFFFFC001CEB23 # 1764 - 1767 D5 S6 C4 neq - .quad 0xfffffffb001ceb2b, 0xFFFFFFFA001CEB33, 0xFFFFFFF9001CEB3B, 0xFFFFFFF8001FEB43 # 1768 - 1771 D5 S6 C8 neq - .quad 0xffffffff001ceb0b, 0xFFFFFFFE001CEB13, 0xFFFFFFFD001CEB1B, 0xFFFFFFFC001CEB23 # 1772 - 1775 D5 S6 C12 neq - .quad 0x0000000000107a00, 0x00000000001A7A81, 0x00000000001CFA82, 0x00000000001CFA83 # 1776 - 1779 D5 S7 C0 neq - .quad 0xffffffff001cfb0b, 0xFFFFFFFE001CFB13, 0xFFFFFFFD001CFB1B, 0xFFFFFFFC001CFB23 # 1780 - 1783 D5 S7 C4 neq - .quad 0xfffffffb001cfb2b, 0xFFFFFFFA001CFB33, 0xFFFFFFF9001FFB3B, 0xFFFFFFF8001FFB43 # 1784 - 1787 D5 S7 C8 neq - .quad 0xffffffff001cfb0b, 0xFFFFFFFE001CFB13, 0xFFFFFFFD001CFB1B, 0xFFFFFFFC001CFB23 # 1788 - 1791 D5 S7 C12 neq - .quad 0x0000000000100c00, 0x0000000000120C81, 0x0000000000120C82, 0xFFFFFFFF00120D0A # 1792 - 1795 D6 S0 C0 neq - .quad 0xfffffffe00120d12, 0xFFFFFFFD00120D1A, 0xFFFFFFFC00120D22, 0xFFFFFFFB00120D2A # 1796 - 1799 D6 S0 C4 neq - .quad 0xfffffffa00120d32, 0xFFFFFFF900150D3A, 0xFFFFFFF800150D42, 0xFFFFFFFF00120D0A # 1800 - 1803 D6 S0 C8 neq - .quad 0xfffffffe00120d12, 0xFFFFFFFD00120D1A, 0xFFFFFFFC00120D22, 0xFFFFFFFB00120D2A # 1804 - 1807 D6 S0 C12 neq - .quad 0x0000000000101c00, 0x0000000000121C81, 0x0000000000121C82, 0xFFFFFFFF00121D0A # 1808 - 1811 D6 S1 C0 neq - .quad 0xfffffffe00121d12, 0xFFFFFFFD00121D1A, 0xFFFFFFFC00121D22, 0xFFFFFFFB00121D2A # 1812 - 1815 D6 S1 C4 neq - .quad 0xfffffffa00151d32, 0xFFFFFFF900151D3A, 0xFFFFFFF800151D42, 0xFFFFFFFF00121D0A # 1816 - 1819 D6 S1 C8 neq - .quad 0xfffffffe00121d12, 0xFFFFFFFD00121D1A, 0xFFFFFFFC00121D22, 0xFFFFFFFB00121D2A # 1820 - 1823 D6 S1 C12 neq - .quad 0x0000000000102c00, 0x0000000000122C81, 0x0000000000122C82, 0xFFFFFFFF00122D0A # 1824 - 1827 D6 S2 C0 neq - .quad 0xfffffffe00122d12, 0xFFFFFFFD00122D1A, 0xFFFFFFFC00122D22, 0xFFFFFFFB00152D2A # 1828 - 1831 D6 S2 C4 neq - .quad 0xfffffffa00152d32, 0xFFFFFFF900152D3A, 0xFFFFFFF800152D42, 0xFFFFFFFF00122D0A # 1832 - 1835 D6 S2 C8 neq - .quad 0xfffffffe00122d12, 0xFFFFFFFD00122D1A, 0xFFFFFFFC00122D22, 0xFFFFFFFB00152D2A # 1836 - 1839 D6 S2 C12 neq - .quad 0x0000000000103c00, 0x0000000000123C81, 0x0000000000123C82, 0xFFFFFFFF00123D0A # 1840 - 1843 D6 S3 C0 neq - .quad 0xfffffffe00123d12, 0xFFFFFFFD00123D1A, 0xFFFFFFFC00153D22, 0xFFFFFFFB00153D2A # 1844 - 1847 D6 S3 C4 neq - .quad 0xfffffffa00153d32, 0xFFFFFFF900153D3A, 0xFFFFFFF800153D42, 0xFFFFFFFF00123D0A # 1848 - 1851 D6 S3 C8 neq - .quad 0xfffffffe00123d12, 0xFFFFFFFD00123D1A, 0xFFFFFFFC00153D22, 0xFFFFFFFB00153D2A # 1852 - 1855 D6 S3 C12 neq - .quad 0x0000000000104c00, 0x0000000000124C81, 0x0000000000124C82, 0xFFFFFFFF00124D0A # 1856 - 1859 D6 S4 C0 neq - .quad 0xfffffffe00124d12, 0xFFFFFFFD00154D1A, 0xFFFFFFFC00154D22, 0xFFFFFFFB00154D2A # 1860 - 1863 D6 S4 C4 neq - .quad 0xfffffffa00154d32, 0xFFFFFFF900154D3A, 0xFFFFFFF800154D42, 0xFFFFFFFF00124D0A # 1864 - 1867 D6 S4 C8 neq - .quad 0xfffffffe00124d12, 0xFFFFFFFD00154D1A, 0xFFFFFFFC00154D22, 0xFFFFFFFB00154D2A # 1868 - 1871 D6 S4 C12 neq - .quad 0x0000000000105c00, 0x0000000000125C81, 0x0000000000125C82, 0xFFFFFFFF00125D0A # 1872 - 1875 D6 S5 C0 neq - .quad 0xfffffffe00155d12, 0xFFFFFFFD00155D1A, 0xFFFFFFFC00155D22, 0xFFFFFFFB00155D2A # 1876 - 1879 D6 S5 C4 neq - .quad 0xfffffffa00155d32, 0xFFFFFFF900155D3A, 0xFFFFFFF800155D42, 0xFFFFFFFF00125D0A # 1880 - 1883 D6 S5 C8 neq - .quad 0xfffffffe00155d12, 0xFFFFFFFD00155D1A, 0xFFFFFFFC00155D22, 0xFFFFFFFB00155D2A # 1884 - 1887 D6 S5 C12 neq - .quad 0x0000000000006c00, 0x0000000000026C81, 0x00000000000A6C82, 0xFFFFFFFF000C6D0A # 1888 - 1891 D6 S6 C0 neq - .quad 0xfffffffe000c6d12, 0xFFFFFFFD000C6D1A, 0xFFFFFFFC000C6D22, 0xFFFFFFFB000C6D2A # 1892 - 1895 D6 S6 C4 neq - .quad 0xfffffffa000c6d32, 0xFFFFFFF9000C6D3A, 0xFFFFFFF8000C6D42, 0xFFFFFFFF000C6D0A # 1896 - 1899 D6 S6 C8 neq - .quad 0xfffffffe000c6d12, 0xFFFFFFFD000C6D1A, 0xFFFFFFFC000C6D22, 0xFFFFFFFB000C6D2A # 1900 - 1903 D6 S6 C12 neq - .quad 0x0000000000107c00, 0x00000000001A7C81, 0x00000000001CFC82, 0xFFFFFFFF001CFD0A # 1904 - 1907 D6 S7 C0 neq - .quad 0xfffffffe001cfd12, 0xFFFFFFFD001CFD1A, 0xFFFFFFFC001CFD22, 0xFFFFFFFB001CFD2A # 1908 - 1911 D6 S7 C4 neq - .quad 0xfffffffa001cfd32, 0xFFFFFFF9001CFD3A, 0xFFFFFFF8001FFD42, 0xFFFFFFFF001CFD0A # 1912 - 1915 D6 S7 C8 neq - .quad 0xfffffffe001cfd12, 0xFFFFFFFD001CFD1A, 0xFFFFFFFC001CFD22, 0xFFFFFFFB001CFD2A # 1916 - 1919 D6 S7 C12 neq - .quad 0x0000000000100e00, 0x0000000000120E81, 0xFFFFFFFF00120F09, 0xFFFFFFFE00120F11 # 1920 - 1923 D7 S0 C0 neq - .quad 0xfffffffd00120f19, 0xFFFFFFFC00120F21, 0xFFFFFFFB00120F29, 0xFFFFFFFA00120F31 # 1924 - 1927 D7 S0 C4 neq - .quad 0xfffffff900120f39, 0xFFFFFFF800150F41, 0xFFFFFFFF00120F09, 0xFFFFFFFE00120F11 # 1928 - 1931 D7 S0 C8 neq - .quad 0xfffffffd00120f19, 0xFFFFFFFC00120F21, 0xFFFFFFFB00120F29, 0xFFFFFFFA00120F31 # 1932 - 1935 D7 S0 C12 neq - .quad 0x0000000000101e00, 0x0000000000121E81, 0xFFFFFFFF00121F09, 0xFFFFFFFE00121F11 # 1936 - 1939 D7 S1 C0 neq - .quad 0xfffffffd00121f19, 0xFFFFFFFC00121F21, 0xFFFFFFFB00121F29, 0xFFFFFFFA00121F31 # 1940 - 1943 D7 S1 C4 neq - .quad 0xfffffff900151f39, 0xFFFFFFF800151F41, 0xFFFFFFFF00121F09, 0xFFFFFFFE00121F11 # 1944 - 1947 D7 S1 C8 neq - .quad 0xfffffffd00121f19, 0xFFFFFFFC00121F21, 0xFFFFFFFB00121F29, 0xFFFFFFFA00121F31 # 1948 - 1951 D7 S1 C12 neq - .quad 0x0000000000102e00, 0x0000000000122E81, 0xFFFFFFFF00122F09, 0xFFFFFFFE00122F11 # 1952 - 1955 D7 S2 C0 neq - .quad 0xfffffffd00122f19, 0xFFFFFFFC00122F21, 0xFFFFFFFB00122F29, 0xFFFFFFFA00152F31 # 1956 - 1959 D7 S2 C4 neq - .quad 0xfffffff900152f39, 0xFFFFFFF800152F41, 0xFFFFFFFF00122F09, 0xFFFFFFFE00122F11 # 1960 - 1963 D7 S2 C8 neq - .quad 0xfffffffd00122f19, 0xFFFFFFFC00122F21, 0xFFFFFFFB00122F29, 0xFFFFFFFA00152F31 # 1964 - 1967 D7 S2 C12 neq - .quad 0x0000000000103e00, 0x0000000000123E81, 0xFFFFFFFF00123F09, 0xFFFFFFFE00123F11 # 1968 - 1971 D7 S3 C0 neq - .quad 0xfffffffd00123f19, 0xFFFFFFFC00123F21, 0xFFFFFFFB00153F29, 0xFFFFFFFA00153F31 # 1972 - 1975 D7 S3 C4 neq - .quad 0xfffffff900153f39, 0xFFFFFFF800153F41, 0xFFFFFFFF00123F09, 0xFFFFFFFE00123F11 # 1976 - 1979 D7 S3 C8 neq - .quad 0xfffffffd00123f19, 0xFFFFFFFC00123F21, 0xFFFFFFFB00153F29, 0xFFFFFFFA00153F31 # 1980 - 1983 D7 S3 C12 neq - .quad 0x0000000000104e00, 0x0000000000124E81, 0xFFFFFFFF00124F09, 0xFFFFFFFE00124F11 # 1984 - 1987 D7 S4 C0 neq - .quad 0xfffffffd00124f19, 0xFFFFFFFC00154F21, 0xFFFFFFFB00154F29, 0xFFFFFFFA00154F31 # 1988 - 1991 D7 S4 C4 neq - .quad 0xfffffff900154f39, 0xFFFFFFF800154F41, 0xFFFFFFFF00124F09, 0xFFFFFFFE00124F11 # 1992 - 1995 D7 S4 C8 neq - .quad 0xfffffffd00124f19, 0xFFFFFFFC00154F21, 0xFFFFFFFB00154F29, 0xFFFFFFFA00154F31 # 1996 - 1999 D7 S4 C12 neq - .quad 0x0000000000105e00, 0x0000000000125E81, 0xFFFFFFFF00125F09, 0xFFFFFFFE00125F11 # 2000 - 2003 D7 S5 C0 neq - .quad 0xfffffffd00155f19, 0xFFFFFFFC00155F21, 0xFFFFFFFB00155F29, 0xFFFFFFFA00155F31 # 2004 - 2007 D7 S5 C4 neq - .quad 0xfffffff900155f39, 0xFFFFFFF800155F41, 0xFFFFFFFF00125F09, 0xFFFFFFFE00125F11 # 2008 - 2011 D7 S5 C8 neq - .quad 0xfffffffd00155f19, 0xFFFFFFFC00155F21, 0xFFFFFFFB00155F29, 0xFFFFFFFA00155F31 # 2012 - 2015 D7 S5 C12 neq - .quad 0x0000000000106e00, 0x0000000000126E81, 0xFFFFFFFF00126F09, 0xFFFFFFFE00156F11 # 2016 - 2019 D7 S6 C0 neq - .quad 0xfffffffd00156f19, 0xFFFFFFFC00156F21, 0xFFFFFFFB00156F29, 0xFFFFFFFA00156F31 # 2020 - 2023 D7 S6 C4 neq - .quad 0xfffffff900156f39, 0xFFFFFFF800156F41, 0xFFFFFFFF00126F09, 0xFFFFFFFE00156F11 # 2024 - 2027 D7 S6 C8 neq - .quad 0xfffffffd00156f19, 0xFFFFFFFC00156F21, 0xFFFFFFFB00156F29, 0xFFFFFFFA00156F31 # 2028 - 2031 D7 S6 C12 neq - .quad 0x0000000000007e00, 0x00000000000A7E81, 0xFFFFFFFF000C7F09, 0xFFFFFFFE000C7F11 # 2032 - 2035 D7 S7 C0 neq - .quad 0xfffffffd000c7f19, 0xFFFFFFFC000C7F21, 0xFFFFFFFB000C7F29, 0xFFFFFFFA000C7F31 # 2036 - 2039 D7 S7 C4 neq - .quad 0xfffffff9000c7f39, 0xFFFFFFF8000C7F41, 0xFFFFFFFF000C7F09, 0xFFFFFFFE000C7F11 # 2040 - 2043 D7 S7 C8 neq - .quad 0xfffffffd000c7f19, 0xFFFFFFFC000C7F21, 0xFFFFFFFB000C7F29, 0xFFFFFFFA000C7F31 # 2044 - 2047 D7 S7 C12 neq \ No newline at end of file + .quad 0x0000000040000000, 0xFFFFFFFF40020088, 0xFFFFFFFE40020090, 0xFFFFFFFD40020098 # 0 - 3 D0 S0 C0 + .quad 0xfffffffc400200a0, 0xFFFFFFFB400200A8, 0xFFFFFFFA400200B0, 0xFFFFFFF9400200B8 # 4 - 7 D0 S0 C4 + .quad 0x0000000000000000, 0xFFFFFFFF40020088, 0xFFFFFFFE40020090, 0xFFFFFFFD40020098 # 8 - 11 D0 S0 C8 + .quad 0xfffffffc400200a0, 0xFFFFFFFB400200A8, 0xFFFFFFFA400200B0, 0xFFFFFFF9400200B8 # 12 - 15 D0 S0 C12 + .quad 0x0000000040101000, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 16 - 19 D0 S1 C0 + .quad 0xfffffffc401210a0, 0xFFFFFFFB401210A8, 0xFFFFFFFA401210B0, 0xFFFFFFF9401210B8 # 20 - 23 D0 S1 C4 + .quad 0x0000000000121000, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 24 - 27 D0 S1 C8 + .quad 0xfffffffc401210a0, 0xFFFFFFFB401210A8, 0xFFFFFFFA401210B0, 0xFFFFFFF9401210B8 # 28 - 31 D0 S1 C12 + .quad 0x0000000040102000, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 32 - 35 D0 S2 C0 + .quad 0xfffffffc401220a0, 0xFFFFFFFB401220A8, 0xFFFFFFFA401220B0, 0xFFFFFFF9401520B8 # 36 - 39 D0 S2 C4 + .quad 0x0000000000122000, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 40 - 43 D0 S2 C8 + .quad 0xfffffffc401220a0, 0xFFFFFFFB401220A8, 0xFFFFFFFA401220B0, 0xFFFFFFF9401520B8 # 44 - 47 D0 S2 C12 + .quad 0x0000000040103000, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 48 - 51 D0 S3 C0 + .quad 0xfffffffc401230a0, 0xFFFFFFFB401230A8, 0xFFFFFFFA401530B0, 0xFFFFFFF9401530B8 # 52 - 55 D0 S3 C4 + .quad 0x0000000000123000, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 56 - 59 D0 S3 C8 + .quad 0xfffffffc401230a0, 0xFFFFFFFB401230A8, 0xFFFFFFFA401530B0, 0xFFFFFFF9401530B8 # 60 - 63 D0 S3 C12 + .quad 0x0000000040104000, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 64 - 67 D0 S4 C0 + .quad 0xfffffffc401240a0, 0xFFFFFFFB401540A8, 0xFFFFFFFA401540B0, 0xFFFFFFF9401540B8 # 68 - 71 D0 S4 C4 + .quad 0x0000000000124000, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 72 - 75 D0 S4 C8 + .quad 0xfffffffc401240a0, 0xFFFFFFFB401540A8, 0xFFFFFFFA401540B0, 0xFFFFFFF9401540B8 # 76 - 79 D0 S4 C12 + .quad 0x0000000040105000, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 80 - 83 D0 S5 C0 + .quad 0xfffffffc401550a0, 0xFFFFFFFB401550A8, 0xFFFFFFFA401550B0, 0xFFFFFFF9401550B8 # 84 - 87 D0 S5 C4 + .quad 0x0000000000125000, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 88 - 91 D0 S5 C8 + .quad 0xfffffffc401550a0, 0xFFFFFFFB401550A8, 0xFFFFFFFA401550B0, 0xFFFFFFF9401550B8 # 92 - 95 D0 S5 C12 + .quad 0x0000000040106000, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 96 - 99 D0 S6 C0 + .quad 0xfffffffc401560a0, 0xFFFFFFFB401560A8, 0xFFFFFFFA401560B0, 0xFFFFFFF9401560B8 # 100 - 103 D0 S6 C4 + .quad 0x0000000000126000, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 104 - 107 D0 S6 C8 + .quad 0xfffffffc401560a0, 0xFFFFFFFB401560A8, 0xFFFFFFFA401560B0, 0xFFFFFFF9401560B8 # 108 - 111 D0 S6 C12 + .quad 0x0000000040107000, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 112 - 115 D0 S7 C0 + .quad 0xfffffffc401570a0, 0xFFFFFFFB401570A8, 0xFFFFFFFA401570B0, 0xFFFFFFF9401570B8 # 116 - 119 D0 S7 C4 + .quad 0x0000000000127000, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 120 - 123 D0 S7 C8 + .quad 0xfffffffc401570a0, 0xFFFFFFFB401570A8, 0xFFFFFFFA401570B0, 0xFFFFFFF9401570B8 # 124 - 127 D0 S7 C12 + .quad 0x0000000040100200, 0x0000000040120281, 0x0000000040120282, 0x0000000040120283 # 128 - 131 D1 S0 C0 + .quad 0x0000000040120284, 0x0000000040120285, 0x0000000040120286, 0x0000000040120287 # 132 - 135 D1 S0 C4 + .quad 0xffffffff4012030f, 0xFFFFFFFE40150317, 0xFFFFFFFD4015031F, 0xFFFFFFFC40150327 # 136 - 139 D1 S0 C8 + .quad 0xfffffffb4015032f, 0xFFFFFFFA40150337, 0xFFFFFFF94015033F, 0x0000000040120287 # 140 - 143 D1 S0 C12 + .quad 0x0000000040001200, 0x0000000040021281, 0x0000000040021282, 0x0000000040021283 # 144 - 147 D1 S1 C0 + .quad 0x0000000040021284, 0x0000000040021285, 0x0000000040021286, 0x00000000400A1287 # 148 - 151 D1 S1 C4 + .quad 0xffffffff400c130f, 0xFFFFFFFE400C1317, 0xFFFFFFFD400C131F, 0xFFFFFFFC400C1327 # 152 - 155 D1 S1 C8 + .quad 0xfffffffb400c132f, 0xFFFFFFFA400C1337, 0xFFFFFFF9400C133F, 0x00000000400A1287 # 156 - 159 D1 S1 C12 + .quad 0x0000000040102200, 0x0000000040122281, 0x0000000040122282, 0x0000000040122283 # 160 - 163 D1 S2 C0 + .quad 0x0000000040122284, 0x0000000040122285, 0x00000000401A2286, 0x00000000401CA287 # 164 - 167 D1 S2 C4 + .quad 0xffffffff401ca30f, 0xFFFFFFFE401CA317, 0xFFFFFFFD401CA31F, 0xFFFFFFFC401CA327 # 168 - 171 D1 S2 C8 + .quad 0xfffffffb401ca32f, 0xFFFFFFFA401CA337, 0xFFFFFFF9401CA33F, 0x00000000401CA287 # 172 - 175 D1 S2 C12 + .quad 0x0000000040103200, 0x0000000040123281, 0x0000000040123282, 0x0000000040123283 # 176 - 179 D1 S3 C0 + .quad 0x0000000040123284, 0x00000000401A3285, 0x00000000401CB286, 0x00000000401CB287 # 180 - 183 D1 S3 C4 + .quad 0xffffffff401cb30f, 0xFFFFFFFE401CB317, 0xFFFFFFFD401CB31F, 0xFFFFFFFC401CB327 # 184 - 187 D1 S3 C8 + .quad 0xfffffffb401cb32f, 0xFFFFFFFA401CB337, 0xFFFFFFF9401FB33F, 0x00000000401CB287 # 188 - 191 D1 S3 C12 + .quad 0x0000000040104200, 0x0000000040124281, 0x0000000040124282, 0x0000000040124283 # 192 - 195 D1 S4 C0 + .quad 0x00000000401a4284, 0x00000000401CC285, 0x00000000401CC286, 0x00000000401CC287 # 196 - 199 D1 S4 C4 + .quad 0xffffffff401cc30f, 0xFFFFFFFE401CC317, 0xFFFFFFFD401CC31F, 0xFFFFFFFC401CC327 # 200 - 203 D1 S4 C8 + .quad 0xfffffffb401cc32f, 0xFFFFFFFA401FC337, 0xFFFFFFF9401FC33F, 0x00000000401CC287 # 204 - 207 D1 S4 C12 + .quad 0x0000000040105200, 0x0000000040125281, 0x0000000040125282, 0x00000000401A5283 # 208 - 211 D1 S5 C0 + .quad 0x00000000401cd284, 0x00000000401CD285, 0x00000000401CD286, 0x00000000401CD287 # 212 - 215 D1 S5 C4 + .quad 0xffffffff401cd30f, 0xFFFFFFFE401CD317, 0xFFFFFFFD401CD31F, 0xFFFFFFFC401CD327 # 216 - 219 D1 S5 C8 + .quad 0xfffffffb401fd32f, 0xFFFFFFFA401FD337, 0xFFFFFFF9401FD33F, 0x00000000401CD287 # 220 - 223 D1 S5 C12 + .quad 0x0000000040106200, 0x0000000040126281, 0x00000000401A6282, 0x00000000401CE283 # 224 - 227 D1 S6 C0 + .quad 0x00000000401ce284, 0x00000000401CE285, 0x00000000401CE286, 0x00000000401CE287 # 228 - 231 D1 S6 C4 + .quad 0xffffffff401ce30f, 0xFFFFFFFE401CE317, 0xFFFFFFFD401CE31F, 0xFFFFFFFC401FE327 # 232 - 235 D1 S6 C8 + .quad 0xfffffffb401fe32f, 0xFFFFFFFA401FE337, 0xFFFFFFF9401FE33F, 0x00000000401CE287 # 236 - 239 D1 S6 C12 + .quad 0x0000000040107200, 0x00000000401A7281, 0x00000000401CF282, 0x00000000401CF283 # 240 - 243 D1 S7 C0 + .quad 0x00000000401cf284, 0x00000000401CF285, 0x00000000401CF286, 0x00000000401CF287 # 244 - 247 D1 S7 C4 + .quad 0xffffffff401cf30f, 0xFFFFFFFE401CF317, 0xFFFFFFFD401FF31F, 0xFFFFFFFC401FF327 # 248 - 251 D1 S7 C8 + .quad 0xfffffffb401ff32f, 0xFFFFFFFA401FF337, 0xFFFFFFF9401FF33F, 0x00000000401CF287 # 252 - 255 D1 S7 C12 + .quad 0x0000000040100400, 0x0000000040120481, 0x0000000040120482, 0x0000000040120483 # 256 - 259 D2 S0 C0 + .quad 0x0000000040120484, 0x0000000040120485, 0x0000000040120486, 0xFFFFFFFF4012050E # 260 - 263 D2 S0 C4 + .quad 0xfffffffe40120516, 0xFFFFFFFD4015051E, 0xFFFFFFFC40150526, 0xFFFFFFFB4015052E # 264 - 267 D2 S0 C8 + .quad 0xfffffffa40150536, 0xFFFFFFF94015053E, 0x0000000040120486, 0xFFFFFFFF4012050E # 268 - 271 D2 S0 C12 + .quad 0x0000000040101400, 0x0000000040121481, 0x0000000040121482, 0x0000000040121483 # 272 - 275 D2 S1 C0 + .quad 0x0000000040121484, 0x0000000040121485, 0x0000000040121486, 0xFFFFFFFF4012150E # 276 - 279 D2 S1 C4 + .quad 0xfffffffe40151516, 0xFFFFFFFD4015151E, 0xFFFFFFFC40151526, 0xFFFFFFFB4015152E # 280 - 283 D2 S1 C8 + .quad 0xfffffffa40151536, 0xFFFFFFF94015153E, 0x0000000040121486, 0xFFFFFFFF4012150E # 284 - 287 D2 S1 C12 + .quad 0x0000000040002400, 0x0000000040022481, 0x0000000040022482, 0x0000000040022483 # 288 - 291 D2 S2 C0 + .quad 0x0000000040022484, 0x0000000040022485, 0x00000000400A2486, 0xFFFFFFFF400C250E # 292 - 295 D2 S2 C4 + .quad 0xfffffffe400c2516, 0xFFFFFFFD400C251E, 0xFFFFFFFC400C2526, 0xFFFFFFFB400C252E # 296 - 299 D2 S2 C8 + .quad 0xfffffffa400c2536, 0xFFFFFFF9400C253E, 0x00000000400A2486, 0xFFFFFFFF400C250E # 300 - 303 D2 S2 C12 + .quad 0x0000000040103400, 0x0000000040123481, 0x0000000040123482, 0x0000000040123483 # 304 - 307 D2 S3 C0 + .quad 0x0000000040123484, 0x00000000401A3485, 0x00000000401CB486, 0xFFFFFFFF401CB50E # 308 - 311 D2 S3 C4 + .quad 0xfffffffe401cb516, 0xFFFFFFFD401CB51E, 0xFFFFFFFC401CB526, 0xFFFFFFFB401CB52E # 312 - 315 D2 S3 C8 + .quad 0xfffffffa401cb536, 0xFFFFFFF9401CB53E, 0x00000000401CB486, 0xFFFFFFFF401CB50E # 316 - 319 D2 S3 C12 + .quad 0x0000000040104400, 0x0000000040124481, 0x0000000040124482, 0x0000000040124483 # 320 - 323 D2 S4 C0 + .quad 0x00000000401a4484, 0x00000000401CC485, 0x00000000401CC486, 0xFFFFFFFF401CC50E # 324 - 327 D2 S4 C4 + .quad 0xfffffffe401cc516, 0xFFFFFFFD401CC51E, 0xFFFFFFFC401CC526, 0xFFFFFFFB401CC52E # 328 - 331 D2 S4 C8 + .quad 0xfffffffa401cc536, 0xFFFFFFF9401FC53E, 0x00000000401CC486, 0xFFFFFFFF401CC50E # 332 - 335 D2 S4 C12 + .quad 0x0000000040105400, 0x0000000040125481, 0x0000000040125482, 0x00000000401A5483 # 336 - 339 D2 S5 C0 + .quad 0x00000000401cd484, 0x00000000401CD485, 0x00000000401CD486, 0xFFFFFFFF401CD50E # 340 - 343 D2 S5 C4 + .quad 0xfffffffe401cd516, 0xFFFFFFFD401CD51E, 0xFFFFFFFC401CD526, 0xFFFFFFFB401CD52E # 344 - 347 D2 S5 C8 + .quad 0xfffffffa401fd536, 0xFFFFFFF9401FD53E, 0x00000000401CD486, 0xFFFFFFFF401CD50E # 348 - 351 D2 S5 C12 + .quad 0x0000000040106400, 0x0000000040126481, 0x00000000401A6482, 0x00000000401CE483 # 352 - 355 D2 S6 C0 + .quad 0x00000000401ce484, 0x00000000401CE485, 0x00000000401CE486, 0xFFFFFFFF401CE50E # 356 - 359 D2 S6 C4 + .quad 0xfffffffe401ce516, 0xFFFFFFFD401CE51E, 0xFFFFFFFC401CE526, 0xFFFFFFFB401FE52E # 360 - 363 D2 S6 C8 + .quad 0xfffffffa401fe536, 0xFFFFFFF9401FE53E, 0x00000000401CE486, 0xFFFFFFFF401CE50E # 364 - 367 D2 S6 C12 + .quad 0x0000000040107400, 0x00000000401A7481, 0x00000000401CF482, 0x00000000401CF483 # 368 - 371 D2 S7 C0 + .quad 0x00000000401cf484, 0x00000000401CF485, 0x00000000401CF486, 0xFFFFFFFF401CF50E # 372 - 375 D2 S7 C4 + .quad 0xfffffffe401cf516, 0xFFFFFFFD401CF51E, 0xFFFFFFFC401FF526, 0xFFFFFFFB401FF52E # 376 - 379 D2 S7 C8 + .quad 0xfffffffa401ff536, 0xFFFFFFF9401FF53E, 0x00000000401CF486, 0xFFFFFFFF401CF50E # 380 - 383 D2 S7 C12 + .quad 0x0000000040100600, 0x0000000040120681, 0x0000000040120682, 0x0000000040120683 # 384 - 387 D3 S0 C0 + .quad 0x0000000040120684, 0x0000000040120685, 0xFFFFFFFF4012070D, 0xFFFFFFFE40120715 # 388 - 391 D3 S0 C4 + .quad 0xfffffffd4012071d, 0xFFFFFFFC40150725, 0xFFFFFFFB4015072D, 0xFFFFFFFA40150735 # 392 - 395 D3 S0 C8 + .quad 0xfffffff94015073d, 0x0000000040120685, 0xFFFFFFFF4012070D, 0xFFFFFFFE40120715 # 396 - 399 D3 S0 C12 + .quad 0x0000000040101600, 0x0000000040121681, 0x0000000040121682, 0x0000000040121683 # 400 - 403 D3 S1 C0 + .quad 0x0000000040121684, 0x0000000040121685, 0xFFFFFFFF4012170D, 0xFFFFFFFE40121715 # 404 - 407 D3 S1 C4 + .quad 0xfffffffd4015171d, 0xFFFFFFFC40151725, 0xFFFFFFFB4015172D, 0xFFFFFFFA40151735 # 408 - 411 D3 S1 C8 + .quad 0xfffffff94015173d, 0x0000000040121685, 0xFFFFFFFF4012170D, 0xFFFFFFFE40121715 # 412 - 415 D3 S1 C12 + .quad 0x0000000040102600, 0x0000000040122681, 0x0000000040122682, 0x0000000040122683 # 416 - 419 D3 S2 C0 + .quad 0x0000000040122684, 0x0000000040122685, 0xFFFFFFFF4012270D, 0xFFFFFFFE40152715 # 420 - 423 D3 S2 C4 + .quad 0xfffffffd4015271d, 0xFFFFFFFC40152725, 0xFFFFFFFB4015272D, 0xFFFFFFFA40152735 # 424 - 427 D3 S2 C8 + .quad 0xfffffff94015273d, 0x0000000040122685, 0xFFFFFFFF4012270D, 0xFFFFFFFE40152715 # 428 - 431 D3 S2 C12 + .quad 0x0000000040003600, 0x0000000040023681, 0x0000000040023682, 0x0000000040023683 # 432 - 435 D3 S3 C0 + .quad 0x0000000040023684, 0x00000000400A3685, 0xFFFFFFFF400C370D, 0xFFFFFFFE400C3715 # 436 - 439 D3 S3 C4 + .quad 0xfffffffd400c371d, 0xFFFFFFFC400C3725, 0xFFFFFFFB400C372D, 0xFFFFFFFA400C3735 # 440 - 443 D3 S3 C8 + .quad 0xfffffff9400c373d, 0x00000000400A3685, 0xFFFFFFFF400C370D, 0xFFFFFFFE400C3715 # 444 - 447 D3 S3 C12 + .quad 0x0000000040104600, 0x0000000040124681, 0x0000000040124682, 0x0000000040124683 # 448 - 451 D3 S4 C0 + .quad 0x00000000401a4684, 0x00000000401CC685, 0xFFFFFFFF401CC70D, 0xFFFFFFFE401CC715 # 452 - 455 D3 S4 C4 + .quad 0xfffffffd401cc71d, 0xFFFFFFFC401CC725, 0xFFFFFFFB401CC72D, 0xFFFFFFFA401CC735 # 456 - 459 D3 S4 C8 + .quad 0xfffffff9401cc73d, 0x00000000401CC685, 0xFFFFFFFF401CC70D, 0xFFFFFFFE401CC715 # 460 - 463 D3 S4 C12 + .quad 0x0000000040105600, 0x0000000040125681, 0x0000000040125682, 0x00000000401A5683 # 464 - 467 D3 S5 C0 + .quad 0x00000000401cd684, 0x00000000401CD685, 0xFFFFFFFF401CD70D, 0xFFFFFFFE401CD715 # 468 - 471 D3 S5 C4 + .quad 0xfffffffd401cd71d, 0xFFFFFFFC401CD725, 0xFFFFFFFB401CD72D, 0xFFFFFFFA401CD735 # 472 - 475 D3 S5 C8 + .quad 0xfffffff9401fd73d, 0x00000000401CD685, 0xFFFFFFFF401CD70D, 0xFFFFFFFE401CD715 # 476 - 479 D3 S5 C12 + .quad 0x0000000040106600, 0x0000000040126681, 0x00000000401A6682, 0x00000000401CE683 # 480 - 483 D3 S6 C0 + .quad 0x00000000401ce684, 0x00000000401CE685, 0xFFFFFFFF401CE70D, 0xFFFFFFFE401CE715 # 484 - 487 D3 S6 C4 + .quad 0xfffffffd401ce71d, 0xFFFFFFFC401CE725, 0xFFFFFFFB401CE72D, 0xFFFFFFFA401FE735 # 488 - 491 D3 S6 C8 + .quad 0xfffffff9401fe73d, 0x00000000401CE685, 0xFFFFFFFF401CE70D, 0xFFFFFFFE401CE715 # 492 - 495 D3 S6 C12 + .quad 0x0000000040107600, 0x00000000401A7681, 0x00000000401CF682, 0x00000000401CF683 # 496 - 499 D3 S7 C0 + .quad 0x00000000401cf684, 0x00000000401CF685, 0xFFFFFFFF401CF70D, 0xFFFFFFFE401CF715 # 500 - 503 D3 S7 C4 + .quad 0xfffffffd401cf71d, 0xFFFFFFFC401CF725, 0xFFFFFFFB401FF72D, 0xFFFFFFFA401FF735 # 504 - 507 D3 S7 C8 + .quad 0xfffffff9401ff73d, 0x00000000401CF685, 0xFFFFFFFF401CF70D, 0xFFFFFFFE401CF715 # 508 - 511 D3 S7 C12 + .quad 0x0000000040100800, 0x0000000040120881, 0x0000000040120882, 0x0000000040120883 # 512 - 515 D4 S0 C0 + .quad 0x0000000040120884, 0xFFFFFFFF4012090C, 0xFFFFFFFE40120914, 0xFFFFFFFD4012091C # 516 - 519 D4 S0 C4 + .quad 0xfffffffc40120924, 0xFFFFFFFB4015092C, 0xFFFFFFFA40150934, 0xFFFFFFF94015093C # 520 - 523 D4 S0 C8 + .quad 0x0000000040120884, 0xFFFFFFFF4012090C, 0xFFFFFFFE40120914, 0xFFFFFFFD4012091C # 524 - 527 D4 S0 C12 + .quad 0x0000000040101800, 0x0000000040121881, 0x0000000040121882, 0x0000000040121883 # 528 - 531 D4 S1 C0 + .quad 0x0000000040121884, 0xFFFFFFFF4012190C, 0xFFFFFFFE40121914, 0xFFFFFFFD4012191C # 532 - 535 D4 S1 C4 + .quad 0xfffffffc40151924, 0xFFFFFFFB4015192C, 0xFFFFFFFA40151934, 0xFFFFFFF94015193C # 536 - 539 D4 S1 C8 + .quad 0x0000000040121884, 0xFFFFFFFF4012190C, 0xFFFFFFFE40121914, 0xFFFFFFFD4012191C # 540 - 543 D4 S1 C12 + .quad 0x0000000040102800, 0x0000000040122881, 0x0000000040122882, 0x0000000040122883 # 544 - 547 D4 S2 C0 + .quad 0x0000000040122884, 0xFFFFFFFF4012290C, 0xFFFFFFFE40122914, 0xFFFFFFFD4015291C # 548 - 551 D4 S2 C4 + .quad 0xfffffffc40152924, 0xFFFFFFFB4015292C, 0xFFFFFFFA40152934, 0xFFFFFFF94015293C # 552 - 555 D4 S2 C8 + .quad 0x0000000040122884, 0xFFFFFFFF4012290C, 0xFFFFFFFE40122914, 0xFFFFFFFD4015291C # 556 - 559 D4 S2 C12 + .quad 0x0000000040103800, 0x0000000040123881, 0x0000000040123882, 0x0000000040123883 # 560 - 563 D4 S3 C0 + .quad 0x0000000040123884, 0xFFFFFFFF4012390C, 0xFFFFFFFE40153914, 0xFFFFFFFD4015391C # 564 - 567 D4 S3 C4 + .quad 0xfffffffc40153924, 0xFFFFFFFB4015392C, 0xFFFFFFFA40153934, 0xFFFFFFF94015393C # 568 - 571 D4 S3 C8 + .quad 0x0000000040123884, 0xFFFFFFFF4012390C, 0xFFFFFFFE40153914, 0xFFFFFFFD4015391C # 572 - 575 D4 S3 C12 + .quad 0x0000000040004800, 0x0000000040024881, 0x0000000040024882, 0x0000000040024883 # 576 - 579 D4 S4 C0 + .quad 0x00000000400a4884, 0xFFFFFFFF400C490C, 0xFFFFFFFE400C4914, 0xFFFFFFFD400C491C # 580 - 583 D4 S4 C4 + .quad 0xfffffffc400c4924, 0xFFFFFFFB400C492C, 0xFFFFFFFA400C4934, 0xFFFFFFF9400C493C # 584 - 587 D4 S4 C8 + .quad 0x00000000400a4884, 0xFFFFFFFF400C490C, 0xFFFFFFFE400C4914, 0xFFFFFFFD400C491C # 588 - 591 D4 S4 C12 + .quad 0x0000000040105800, 0x0000000040125881, 0x0000000040125882, 0x00000000401A5883 # 592 - 595 D4 S5 C0 + .quad 0x00000000401cd884, 0xFFFFFFFF401CD90C, 0xFFFFFFFE401CD914, 0xFFFFFFFD401CD91C # 596 - 599 D4 S5 C4 + .quad 0xfffffffc401cd924, 0xFFFFFFFB401CD92C, 0xFFFFFFFA401CD934, 0xFFFFFFF9401CD93C # 600 - 603 D4 S5 C8 + .quad 0x00000000401cd884, 0xFFFFFFFF401CD90C, 0xFFFFFFFE401CD914, 0xFFFFFFFD401CD91C # 604 - 607 D4 S5 C12 + .quad 0x0000000040106800, 0x0000000040126881, 0x00000000401A6882, 0x00000000401CE883 # 608 - 611 D4 S6 C0 + .quad 0x00000000401ce884, 0xFFFFFFFF401CE90C, 0xFFFFFFFE401CE914, 0xFFFFFFFD401CE91C # 612 - 615 D4 S6 C4 + .quad 0xfffffffc401ce924, 0xFFFFFFFB401CE92C, 0xFFFFFFFA401CE934, 0xFFFFFFF9401FE93C # 616 - 619 D4 S6 C8 + .quad 0x00000000401ce884, 0xFFFFFFFF401CE90C, 0xFFFFFFFE401CE914, 0xFFFFFFFD401CE91C # 620 - 623 D4 S6 C12 + .quad 0x0000000040107800, 0x00000000401A7881, 0x00000000401CF882, 0x00000000401CF883 # 624 - 627 D4 S7 C0 + .quad 0x00000000401cf884, 0xFFFFFFFF401CF90C, 0xFFFFFFFE401CF914, 0xFFFFFFFD401CF91C # 628 - 631 D4 S7 C4 + .quad 0xfffffffc401cf924, 0xFFFFFFFB401CF92C, 0xFFFFFFFA401FF934, 0xFFFFFFF9401FF93C # 632 - 635 D4 S7 C8 + .quad 0x00000000401cf884, 0xFFFFFFFF401CF90C, 0xFFFFFFFE401CF914, 0xFFFFFFFD401CF91C # 636 - 639 D4 S7 C12 + .quad 0x0000000040100a00, 0x0000000040120A81, 0x0000000040120A82, 0x0000000040120A83 # 640 - 643 D5 S0 C0 + .quad 0xffffffff40120b0b, 0xFFFFFFFE40120B13, 0xFFFFFFFD40120B1B, 0xFFFFFFFC40120B23 # 644 - 647 D5 S0 C4 + .quad 0xfffffffb40120b2b, 0xFFFFFFFA40150B33, 0xFFFFFFF940150B3B, 0x0000000040120A83 # 648 - 651 D5 S0 C8 + .quad 0xffffffff40120b0b, 0xFFFFFFFE40120B13, 0xFFFFFFFD40120B1B, 0xFFFFFFFC40120B23 # 652 - 655 D5 S0 C12 + .quad 0x0000000040101a00, 0x0000000040121A81, 0x0000000040121A82, 0x0000000040121A83 # 656 - 659 D5 S1 C0 + .quad 0xffffffff40121b0b, 0xFFFFFFFE40121B13, 0xFFFFFFFD40121B1B, 0xFFFFFFFC40121B23 # 660 - 663 D5 S1 C4 + .quad 0xfffffffb40151b2b, 0xFFFFFFFA40151B33, 0xFFFFFFF940151B3B, 0x0000000040121A83 # 664 - 667 D5 S1 C8 + .quad 0xffffffff40121b0b, 0xFFFFFFFE40121B13, 0xFFFFFFFD40121B1B, 0xFFFFFFFC40121B23 # 668 - 671 D5 S1 C12 + .quad 0x0000000040102a00, 0x0000000040122A81, 0x0000000040122A82, 0x0000000040122A83 # 672 - 675 D5 S2 C0 + .quad 0xffffffff40122b0b, 0xFFFFFFFE40122B13, 0xFFFFFFFD40122B1B, 0xFFFFFFFC40152B23 # 676 - 679 D5 S2 C4 + .quad 0xfffffffb40152b2b, 0xFFFFFFFA40152B33, 0xFFFFFFF940152B3B, 0x0000000040122A83 # 680 - 683 D5 S2 C8 + .quad 0xffffffff40122b0b, 0xFFFFFFFE40122B13, 0xFFFFFFFD40122B1B, 0xFFFFFFFC40152B23 # 684 - 687 D5 S2 C12 + .quad 0x0000000040103a00, 0x0000000040123A81, 0x0000000040123A82, 0x0000000040123A83 # 688 - 691 D5 S3 C0 + .quad 0xffffffff40123b0b, 0xFFFFFFFE40123B13, 0xFFFFFFFD40153B1B, 0xFFFFFFFC40153B23 # 692 - 695 D5 S3 C4 + .quad 0xfffffffb40153b2b, 0xFFFFFFFA40153B33, 0xFFFFFFF940153B3B, 0x0000000040123A83 # 696 - 699 D5 S3 C8 + .quad 0xffffffff40123b0b, 0xFFFFFFFE40123B13, 0xFFFFFFFD40153B1B, 0xFFFFFFFC40153B23 # 700 - 703 D5 S3 C12 + .quad 0x0000000040104a00, 0x0000000040124A81, 0x0000000040124A82, 0x0000000040124A83 # 704 - 707 D5 S4 C0 + .quad 0xffffffff40124b0b, 0xFFFFFFFE40154B13, 0xFFFFFFFD40154B1B, 0xFFFFFFFC40154B23 # 708 - 711 D5 S4 C4 + .quad 0xfffffffb40154b2b, 0xFFFFFFFA40154B33, 0xFFFFFFF940154B3B, 0x0000000040124A83 # 712 - 715 D5 S4 C8 + .quad 0xffffffff40124b0b, 0xFFFFFFFE40154B13, 0xFFFFFFFD40154B1B, 0xFFFFFFFC40154B23 # 716 - 719 D5 S4 C12 + .quad 0x0000000040005a00, 0x0000000040025A81, 0x0000000040025A82, 0x00000000400A5A83 # 720 - 723 D5 S5 C0 + .quad 0xffffffff400c5b0b, 0xFFFFFFFE400C5B13, 0xFFFFFFFD400C5B1B, 0xFFFFFFFC400C5B23 # 724 - 727 D5 S5 C4 + .quad 0xfffffffb400c5b2b, 0xFFFFFFFA400C5B33, 0xFFFFFFF9400C5B3B, 0x00000000400A5A83 # 728 - 731 D5 S5 C8 + .quad 0xffffffff400c5b0b, 0xFFFFFFFE400C5B13, 0xFFFFFFFD400C5B1B, 0xFFFFFFFC400C5B23 # 732 - 735 D5 S5 C12 + .quad 0x0000000040106a00, 0x0000000040126A81, 0x00000000401A6A82, 0x00000000401CEA83 # 736 - 739 D5 S6 C0 + .quad 0xffffffff401ceb0b, 0xFFFFFFFE401CEB13, 0xFFFFFFFD401CEB1B, 0xFFFFFFFC401CEB23 # 740 - 743 D5 S6 C4 + .quad 0xfffffffb401ceb2b, 0xFFFFFFFA401CEB33, 0xFFFFFFF9401CEB3B, 0x00000000401CEA83 # 744 - 747 D5 S6 C8 + .quad 0xffffffff401ceb0b, 0xFFFFFFFE401CEB13, 0xFFFFFFFD401CEB1B, 0xFFFFFFFC401CEB23 # 748 - 751 D5 S6 C12 + .quad 0x0000000040107a00, 0x00000000401A7A81, 0x00000000401CFA82, 0x00000000401CFA83 # 752 - 755 D5 S7 C0 + .quad 0xffffffff401cfb0b, 0xFFFFFFFE401CFB13, 0xFFFFFFFD401CFB1B, 0xFFFFFFFC401CFB23 # 756 - 759 D5 S7 C4 + .quad 0xfffffffb401cfb2b, 0xFFFFFFFA401CFB33, 0xFFFFFFF9401FFB3B, 0x00000000401CFA83 # 760 - 763 D5 S7 C8 + .quad 0xffffffff401cfb0b, 0xFFFFFFFE401CFB13, 0xFFFFFFFD401CFB1B, 0xFFFFFFFC401CFB23 # 764 - 767 D5 S7 C12 + .quad 0x0000000040100c00, 0x0000000040120C81, 0x0000000040120C82, 0xFFFFFFFF40120D0A # 768 - 771 D6 S0 C0 + .quad 0xfffffffe40120d12, 0xFFFFFFFD40120D1A, 0xFFFFFFFC40120D22, 0xFFFFFFFB40120D2A # 772 - 775 D6 S0 C4 + .quad 0xfffffffa40120d32, 0xFFFFFFF940150D3A, 0x0000000040120C82, 0xFFFFFFFF40120D0A # 776 - 779 D6 S0 C8 + .quad 0xfffffffe40120d12, 0xFFFFFFFD40120D1A, 0xFFFFFFFC40120D22, 0xFFFFFFFB40120D2A # 780 - 783 D6 S0 C12 + .quad 0x0000000040101c00, 0x0000000040121C81, 0x0000000040121C82, 0xFFFFFFFF40121D0A # 784 - 787 D6 S1 C0 + .quad 0xfffffffe40121d12, 0xFFFFFFFD40121D1A, 0xFFFFFFFC40121D22, 0xFFFFFFFB40121D2A # 788 - 791 D6 S1 C4 + .quad 0xfffffffa40151d32, 0xFFFFFFF940151D3A, 0x0000000040121C82, 0xFFFFFFFF40121D0A # 792 - 795 D6 S1 C8 + .quad 0xfffffffe40121d12, 0xFFFFFFFD40121D1A, 0xFFFFFFFC40121D22, 0xFFFFFFFB40121D2A # 796 - 799 D6 S1 C12 + .quad 0x0000000040102c00, 0x0000000040122C81, 0x0000000040122C82, 0xFFFFFFFF40122D0A # 800 - 803 D6 S2 C0 + .quad 0xfffffffe40122d12, 0xFFFFFFFD40122D1A, 0xFFFFFFFC40122D22, 0xFFFFFFFB40152D2A # 804 - 807 D6 S2 C4 + .quad 0xfffffffa40152d32, 0xFFFFFFF940152D3A, 0x0000000040122C82, 0xFFFFFFFF40122D0A # 808 - 811 D6 S2 C8 + .quad 0xfffffffe40122d12, 0xFFFFFFFD40122D1A, 0xFFFFFFFC40122D22, 0xFFFFFFFB40152D2A # 812 - 815 D6 S2 C12 + .quad 0x0000000040103c00, 0x0000000040123C81, 0x0000000040123C82, 0xFFFFFFFF40123D0A # 816 - 819 D6 S3 C0 + .quad 0xfffffffe40123d12, 0xFFFFFFFD40123D1A, 0xFFFFFFFC40153D22, 0xFFFFFFFB40153D2A # 820 - 823 D6 S3 C4 + .quad 0xfffffffa40153d32, 0xFFFFFFF940153D3A, 0x0000000040123C82, 0xFFFFFFFF40123D0A # 824 - 827 D6 S3 C8 + .quad 0xfffffffe40123d12, 0xFFFFFFFD40123D1A, 0xFFFFFFFC40153D22, 0xFFFFFFFB40153D2A # 828 - 831 D6 S3 C12 + .quad 0x0000000040104c00, 0x0000000040124C81, 0x0000000040124C82, 0xFFFFFFFF40124D0A # 832 - 835 D6 S4 C0 + .quad 0xfffffffe40124d12, 0xFFFFFFFD40154D1A, 0xFFFFFFFC40154D22, 0xFFFFFFFB40154D2A # 836 - 839 D6 S4 C4 + .quad 0xfffffffa40154d32, 0xFFFFFFF940154D3A, 0x0000000040124C82, 0xFFFFFFFF40124D0A # 840 - 843 D6 S4 C8 + .quad 0xfffffffe40124d12, 0xFFFFFFFD40154D1A, 0xFFFFFFFC40154D22, 0xFFFFFFFB40154D2A # 844 - 847 D6 S4 C12 + .quad 0x0000000040105c00, 0x0000000040125C81, 0x0000000040125C82, 0xFFFFFFFF40125D0A # 848 - 851 D6 S5 C0 + .quad 0xfffffffe40155d12, 0xFFFFFFFD40155D1A, 0xFFFFFFFC40155D22, 0xFFFFFFFB40155D2A # 852 - 855 D6 S5 C4 + .quad 0xfffffffa40155d32, 0xFFFFFFF940155D3A, 0x0000000040125C82, 0xFFFFFFFF40125D0A # 856 - 859 D6 S5 C8 + .quad 0xfffffffe40155d12, 0xFFFFFFFD40155D1A, 0xFFFFFFFC40155D22, 0xFFFFFFFB40155D2A # 860 - 863 D6 S5 C12 + .quad 0x0000000040006c00, 0x0000000040026C81, 0x00000000400A6C82, 0xFFFFFFFF400C6D0A # 864 - 867 D6 S6 C0 + .quad 0xfffffffe400c6d12, 0xFFFFFFFD400C6D1A, 0xFFFFFFFC400C6D22, 0xFFFFFFFB400C6D2A # 868 - 871 D6 S6 C4 + .quad 0xfffffffa400c6d32, 0xFFFFFFF9400C6D3A, 0x00000000400A6C82, 0xFFFFFFFF400C6D0A # 872 - 875 D6 S6 C8 + .quad 0xfffffffe400c6d12, 0xFFFFFFFD400C6D1A, 0xFFFFFFFC400C6D22, 0xFFFFFFFB400C6D2A # 876 - 879 D6 S6 C12 + .quad 0x0000000040107c00, 0x00000000401A7C81, 0x00000000401CFC82, 0xFFFFFFFF401CFD0A # 880 - 883 D6 S7 C0 + .quad 0xfffffffe401cfd12, 0xFFFFFFFD401CFD1A, 0xFFFFFFFC401CFD22, 0xFFFFFFFB401CFD2A # 884 - 887 D6 S7 C4 + .quad 0xfffffffa401cfd32, 0xFFFFFFF9401CFD3A, 0x00000000401CFC82, 0xFFFFFFFF401CFD0A # 888 - 891 D6 S7 C8 + .quad 0xfffffffe401cfd12, 0xFFFFFFFD401CFD1A, 0xFFFFFFFC401CFD22, 0xFFFFFFFB401CFD2A # 892 - 895 D6 S7 C12 + .quad 0x0000000040100e00, 0x0000000040120E81, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 896 - 899 D7 S0 C0 + .quad 0xfffffffd40120f19, 0xFFFFFFFC40120F21, 0xFFFFFFFB40120F29, 0xFFFFFFFA40120F31 # 900 - 903 D7 S0 C4 + .quad 0xfffffff940120f39, 0x0000000040120E81, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 904 - 907 D7 S0 C8 + .quad 0xfffffffd40120f19, 0xFFFFFFFC40120F21, 0xFFFFFFFB40120F29, 0xFFFFFFFA40120F31 # 908 - 911 D7 S0 C12 + .quad 0x0000000040101e00, 0x0000000040121E81, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 912 - 915 D7 S1 C0 + .quad 0xfffffffd40121f19, 0xFFFFFFFC40121F21, 0xFFFFFFFB40121F29, 0xFFFFFFFA40121F31 # 916 - 919 D7 S1 C4 + .quad 0xfffffff940151f39, 0x0000000040121E81, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 920 - 923 D7 S1 C8 + .quad 0xfffffffd40121f19, 0xFFFFFFFC40121F21, 0xFFFFFFFB40121F29, 0xFFFFFFFA40121F31 # 924 - 927 D7 S1 C12 + .quad 0x0000000040102e00, 0x0000000040122E81, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 928 - 931 D7 S2 C0 + .quad 0xfffffffd40122f19, 0xFFFFFFFC40122F21, 0xFFFFFFFB40122F29, 0xFFFFFFFA40152F31 # 932 - 935 D7 S2 C4 + .quad 0xfffffff940152f39, 0x0000000040122E81, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 936 - 939 D7 S2 C8 + .quad 0xfffffffd40122f19, 0xFFFFFFFC40122F21, 0xFFFFFFFB40122F29, 0xFFFFFFFA40152F31 # 940 - 943 D7 S2 C12 + .quad 0x0000000040103e00, 0x0000000040123E81, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 944 - 947 D7 S3 C0 + .quad 0xfffffffd40123f19, 0xFFFFFFFC40123F21, 0xFFFFFFFB40153F29, 0xFFFFFFFA40153F31 # 948 - 951 D7 S3 C4 + .quad 0xfffffff940153f39, 0x0000000040123E81, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 952 - 955 D7 S3 C8 + .quad 0xfffffffd40123f19, 0xFFFFFFFC40123F21, 0xFFFFFFFB40153F29, 0xFFFFFFFA40153F31 # 956 - 959 D7 S3 C12 + .quad 0x0000000040104e00, 0x0000000040124E81, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 960 - 963 D7 S4 C0 + .quad 0xfffffffd40124f19, 0xFFFFFFFC40154F21, 0xFFFFFFFB40154F29, 0xFFFFFFFA40154F31 # 964 - 967 D7 S4 C4 + .quad 0xfffffff940154f39, 0x0000000040124E81, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 968 - 971 D7 S4 C8 + .quad 0xfffffffd40124f19, 0xFFFFFFFC40154F21, 0xFFFFFFFB40154F29, 0xFFFFFFFA40154F31 # 972 - 975 D7 S4 C12 + .quad 0x0000000040105e00, 0x0000000040125E81, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 976 - 979 D7 S5 C0 + .quad 0xfffffffd40155f19, 0xFFFFFFFC40155F21, 0xFFFFFFFB40155F29, 0xFFFFFFFA40155F31 # 980 - 983 D7 S5 C4 + .quad 0xfffffff940155f39, 0x0000000040125E81, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 984 - 987 D7 S5 C8 + .quad 0xfffffffd40155f19, 0xFFFFFFFC40155F21, 0xFFFFFFFB40155F29, 0xFFFFFFFA40155F31 # 988 - 991 D7 S5 C12 + .quad 0x0000000040106e00, 0x0000000040126E81, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 992 - 995 D7 S6 C0 + .quad 0xfffffffd40156f19, 0xFFFFFFFC40156F21, 0xFFFFFFFB40156F29, 0xFFFFFFFA40156F31 # 996 - 999 D7 S6 C4 + .quad 0xfffffff940156f39, 0x0000000040126E81, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 1000 - 1003 D7 S6 C8 + .quad 0xfffffffd40156f19, 0xFFFFFFFC40156F21, 0xFFFFFFFB40156F29, 0xFFFFFFFA40156F31 # 1004 - 1007 D7 S6 C12 + .quad 0x0000000040007e00, 0x00000000400A7E81, 0xFFFFFFFF400C7F09, 0xFFFFFFFE400C7F11 # 1008 - 1011 D7 S7 C0 + .quad 0xfffffffd400c7f19, 0xFFFFFFFC400C7F21, 0xFFFFFFFB400C7F29, 0xFFFFFFFA400C7F31 # 1012 - 1015 D7 S7 C4 + .quad 0xfffffff9400c7f39, 0x00000000400A7E81, 0xFFFFFFFF400C7F09, 0xFFFFFFFE400C7F11 # 1016 - 1019 D7 S7 C8 + .quad 0xfffffffd400c7f19, 0xFFFFFFFC400C7F21, 0xFFFFFFFB400C7F29, 0xFFFFFFFA400C7F31 # 1020 - 1023 D7 S7 C12 + .quad 0x0000000040000000, 0xFFFFFFFF40020088, 0xFFFFFFFE40020090, 0xFFFFFFFD40020098 # 1024 - 1027 D0 S0 C0 neq + .quad 0xfffffffc400200a0, 0xFFFFFFFB400200A8, 0xFFFFFFFA400200B0, 0xFFFFFFF9400200B8 # 1028 - 1031 D0 S0 C4 neq + .quad 0xfffffff8400200c0, 0xFFFFFFFF40020088, 0xFFFFFFFE40020090, 0xFFFFFFFD40020098 # 1032 - 1035 D0 S0 C8 neq + .quad 0xfffffffc400200a0, 0xFFFFFFFB400200A8, 0xFFFFFFFA400200B0, 0xFFFFFFF9400200B8 # 1036 - 1039 D0 S0 C12 neq + .quad 0x0000000040101000, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 1040 - 1043 D0 S1 C0 neq + .quad 0xfffffffc401210a0, 0xFFFFFFFB401210A8, 0xFFFFFFFA401210B0, 0xFFFFFFF9401210B8 # 1044 - 1047 D0 S1 C4 neq + .quad 0xfffffff8401510c0, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 1048 - 1051 D0 S1 C8 neq + .quad 0xfffffffc401210a0, 0xFFFFFFFB401210A8, 0xFFFFFFFA401210B0, 0xFFFFFFF9401210B8 # 1052 - 1055 D0 S1 C12 neq + .quad 0x0000000040102000, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 1056 - 1059 D0 S2 C0 neq + .quad 0xfffffffc401220a0, 0xFFFFFFFB401220A8, 0xFFFFFFFA401220B0, 0xFFFFFFF9401520B8 # 1060 - 1063 D0 S2 C4 neq + .quad 0xfffffff8401520c0, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 1064 - 1067 D0 S2 C8 neq + .quad 0xfffffffc401220a0, 0xFFFFFFFB401220A8, 0xFFFFFFFA401220B0, 0xFFFFFFF9401520B8 # 1068 - 1071 D0 S2 C12 neq + .quad 0x0000000040103000, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 1072 - 1075 D0 S3 C0 neq + .quad 0xfffffffc401230a0, 0xFFFFFFFB401230A8, 0xFFFFFFFA401530B0, 0xFFFFFFF9401530B8 # 1076 - 1079 D0 S3 C4 neq + .quad 0xfffffff8401530c0, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 1080 - 1083 D0 S3 C8 neq + .quad 0xfffffffc401230a0, 0xFFFFFFFB401230A8, 0xFFFFFFFA401530B0, 0xFFFFFFF9401530B8 # 1084 - 1087 D0 S3 C12 neq + .quad 0x0000000040104000, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 1088 - 1091 D0 S4 C0 neq + .quad 0xfffffffc401240a0, 0xFFFFFFFB401540A8, 0xFFFFFFFA401540B0, 0xFFFFFFF9401540B8 # 1092 - 1095 D0 S4 C4 neq + .quad 0xfffffff8401540c0, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 1096 - 1099 D0 S4 C8 neq + .quad 0xfffffffc401240a0, 0xFFFFFFFB401540A8, 0xFFFFFFFA401540B0, 0xFFFFFFF9401540B8 # 1100 - 1103 D0 S4 C12 neq + .quad 0x0000000040105000, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 1104 - 1107 D0 S5 C0 neq + .quad 0xfffffffc401550a0, 0xFFFFFFFB401550A8, 0xFFFFFFFA401550B0, 0xFFFFFFF9401550B8 # 1108 - 1111 D0 S5 C4 neq + .quad 0xfffffff8401550c0, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 1112 - 1115 D0 S5 C8 neq + .quad 0xfffffffc401550a0, 0xFFFFFFFB401550A8, 0xFFFFFFFA401550B0, 0xFFFFFFF9401550B8 # 1116 - 1119 D0 S5 C12 neq + .quad 0x0000000040106000, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 1120 - 1123 D0 S6 C0 neq + .quad 0xfffffffc401560a0, 0xFFFFFFFB401560A8, 0xFFFFFFFA401560B0, 0xFFFFFFF9401560B8 # 1124 - 1127 D0 S6 C4 neq + .quad 0xfffffff8401560c0, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 1128 - 1131 D0 S6 C8 neq + .quad 0xfffffffc401560a0, 0xFFFFFFFB401560A8, 0xFFFFFFFA401560B0, 0xFFFFFFF9401560B8 # 1132 - 1135 D0 S6 C12 neq + .quad 0x0000000040107000, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 1136 - 1139 D0 S7 C0 neq + .quad 0xfffffffc401570a0, 0xFFFFFFFB401570A8, 0xFFFFFFFA401570B0, 0xFFFFFFF9401570B8 # 1140 - 1143 D0 S7 C4 neq + .quad 0xfffffff8401570c0, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 1144 - 1147 D0 S7 C8 neq + .quad 0xfffffffc401570a0, 0xFFFFFFFB401570A8, 0xFFFFFFFA401570B0, 0xFFFFFFF9401570B8 # 1148 - 1151 D0 S7 C12 neq + .quad 0x0000000040100200, 0x0000000040120281, 0x0000000040120282, 0x0000000040120283 # 1152 - 1155 D1 S0 C0 neq + .quad 0x0000000040120284, 0x0000000040120285, 0x0000000040120286, 0x0000000040120287 # 1156 - 1159 D1 S0 C4 neq + .quad 0xffffffff4012030f, 0xFFFFFFFE40150317, 0xFFFFFFFD4015031F, 0xFFFFFFFC40150327 # 1160 - 1163 D1 S0 C8 neq + .quad 0xfffffffb4015032f, 0xFFFFFFFA40150337, 0xFFFFFFF94015033F, 0xFFFFFFF840150347 # 1164 - 1167 D1 S0 C12 neq + .quad 0x0000000040001200, 0x0000000040021281, 0x0000000040021282, 0x0000000040021283 # 1168 - 1171 D1 S1 C0 neq + .quad 0x0000000040021284, 0x0000000040021285, 0x0000000040021286, 0x00000000400A1287 # 1172 - 1175 D1 S1 C4 neq + .quad 0xffffffff400c130f, 0xFFFFFFFE400C1317, 0xFFFFFFFD400C131F, 0xFFFFFFFC400C1327 # 1176 - 1179 D1 S1 C8 neq + .quad 0xfffffffb400c132f, 0xFFFFFFFA400C1337, 0xFFFFFFF9400C133F, 0xFFFFFFF8400C1347 # 1180 - 1183 D1 S1 C12 neq + .quad 0x0000000040102200, 0x0000000040122281, 0x0000000040122282, 0x0000000040122283 # 1184 - 1187 D1 S2 C0 neq + .quad 0x0000000040122284, 0x0000000040122285, 0x00000000401A2286, 0x00000000401CA287 # 1188 - 1191 D1 S2 C4 neq + .quad 0xffffffff401ca30f, 0xFFFFFFFE401CA317, 0xFFFFFFFD401CA31F, 0xFFFFFFFC401CA327 # 1192 - 1195 D1 S2 C8 neq + .quad 0xfffffffb401ca32f, 0xFFFFFFFA401CA337, 0xFFFFFFF9401CA33F, 0xFFFFFFF8401FA347 # 1196 - 1199 D1 S2 C12 neq + .quad 0x0000000040103200, 0x0000000040123281, 0x0000000040123282, 0x0000000040123283 # 1200 - 1203 D1 S3 C0 neq + .quad 0x0000000040123284, 0x00000000401A3285, 0x00000000401CB286, 0x00000000401CB287 # 1204 - 1207 D1 S3 C4 neq + .quad 0xffffffff401cb30f, 0xFFFFFFFE401CB317, 0xFFFFFFFD401CB31F, 0xFFFFFFFC401CB327 # 1208 - 1211 D1 S3 C8 neq + .quad 0xfffffffb401cb32f, 0xFFFFFFFA401CB337, 0xFFFFFFF9401FB33F, 0xFFFFFFF8401FB347 # 1212 - 1215 D1 S3 C12 neq + .quad 0x0000000040104200, 0x0000000040124281, 0x0000000040124282, 0x0000000040124283 # 1216 - 1219 D1 S4 C0 neq + .quad 0x00000000401a4284, 0x00000000401CC285, 0x00000000401CC286, 0x00000000401CC287 # 1220 - 1223 D1 S4 C4 neq + .quad 0xffffffff401cc30f, 0xFFFFFFFE401CC317, 0xFFFFFFFD401CC31F, 0xFFFFFFFC401CC327 # 1224 - 1227 D1 S4 C8 neq + .quad 0xfffffffb401cc32f, 0xFFFFFFFA401FC337, 0xFFFFFFF9401FC33F, 0xFFFFFFF8401FC347 # 1228 - 1231 D1 S4 C12 neq + .quad 0x0000000040105200, 0x0000000040125281, 0x0000000040125282, 0x00000000401A5283 # 1232 - 1235 D1 S5 C0 neq + .quad 0x00000000401cd284, 0x00000000401CD285, 0x00000000401CD286, 0x00000000401CD287 # 1236 - 1239 D1 S5 C4 neq + .quad 0xffffffff401cd30f, 0xFFFFFFFE401CD317, 0xFFFFFFFD401CD31F, 0xFFFFFFFC401CD327 # 1240 - 1243 D1 S5 C8 neq + .quad 0xfffffffb401fd32f, 0xFFFFFFFA401FD337, 0xFFFFFFF9401FD33F, 0xFFFFFFF8401FD347 # 1244 - 1247 D1 S5 C12 neq + .quad 0x0000000040106200, 0x0000000040126281, 0x00000000401A6282, 0x00000000401CE283 # 1248 - 1251 D1 S6 C0 neq + .quad 0x00000000401ce284, 0x00000000401CE285, 0x00000000401CE286, 0x00000000401CE287 # 1252 - 1255 D1 S6 C4 neq + .quad 0xffffffff401ce30f, 0xFFFFFFFE401CE317, 0xFFFFFFFD401CE31F, 0xFFFFFFFC401FE327 # 1256 - 1259 D1 S6 C8 neq + .quad 0xfffffffb401fe32f, 0xFFFFFFFA401FE337, 0xFFFFFFF9401FE33F, 0xFFFFFFF8401FE347 # 1260 - 1263 D1 S6 C12 neq + .quad 0x0000000040107200, 0x00000000401A7281, 0x00000000401CF282, 0x00000000401CF283 # 1264 - 1267 D1 S7 C0 neq + .quad 0x00000000401cf284, 0x00000000401CF285, 0x00000000401CF286, 0x00000000401CF287 # 1268 - 1271 D1 S7 C4 neq + .quad 0xffffffff401cf30f, 0xFFFFFFFE401CF317, 0xFFFFFFFD401FF31F, 0xFFFFFFFC401FF327 # 1272 - 1275 D1 S7 C8 neq + .quad 0xfffffffb401ff32f, 0xFFFFFFFA401FF337, 0xFFFFFFF9401FF33F, 0xFFFFFFF8401FF347 # 1276 - 1279 D1 S7 C12 neq + .quad 0x0000000040100400, 0x0000000040120481, 0x0000000040120482, 0x0000000040120483 # 1280 - 1283 D2 S0 C0 neq + .quad 0x0000000040120484, 0x0000000040120485, 0x0000000040120486, 0xFFFFFFFF4012050E # 1284 - 1287 D2 S0 C4 neq + .quad 0xfffffffe40120516, 0xFFFFFFFD4015051E, 0xFFFFFFFC40150526, 0xFFFFFFFB4015052E # 1288 - 1291 D2 S0 C8 neq + .quad 0xfffffffa40150536, 0xFFFFFFF94015053E, 0xFFFFFFF840150546, 0xFFFFFFFF4012050E # 1292 - 1295 D2 S0 C12 neq + .quad 0x0000000040101400, 0x0000000040121481, 0x0000000040121482, 0x0000000040121483 # 1296 - 1299 D2 S1 C0 neq + .quad 0x0000000040121484, 0x0000000040121485, 0x0000000040121486, 0xFFFFFFFF4012150E # 1300 - 1303 D2 S1 C4 neq + .quad 0xfffffffe40151516, 0xFFFFFFFD4015151E, 0xFFFFFFFC40151526, 0xFFFFFFFB4015152E # 1304 - 1307 D2 S1 C8 neq + .quad 0xfffffffa40151536, 0xFFFFFFF94015153E, 0xFFFFFFF840151546, 0xFFFFFFFF4012150E # 1308 - 1311 D2 S1 C12 neq + .quad 0x0000000040002400, 0x0000000040022481, 0x0000000040022482, 0x0000000040022483 # 1312 - 1315 D2 S2 C0 neq + .quad 0x0000000040022484, 0x0000000040022485, 0x00000000400A2486, 0xFFFFFFFF400C250E # 1316 - 1319 D2 S2 C4 neq + .quad 0xfffffffe400c2516, 0xFFFFFFFD400C251E, 0xFFFFFFFC400C2526, 0xFFFFFFFB400C252E # 1320 - 1323 D2 S2 C8 neq + .quad 0xfffffffa400c2536, 0xFFFFFFF9400C253E, 0xFFFFFFF8400C2546, 0xFFFFFFFF400C250E # 1324 - 1327 D2 S2 C12 neq + .quad 0x0000000040103400, 0x0000000040123481, 0x0000000040123482, 0x0000000040123483 # 1328 - 1331 D2 S3 C0 neq + .quad 0x0000000040123484, 0x00000000401A3485, 0x00000000401CB486, 0xFFFFFFFF401CB50E # 1332 - 1335 D2 S3 C4 neq + .quad 0xfffffffe401cb516, 0xFFFFFFFD401CB51E, 0xFFFFFFFC401CB526, 0xFFFFFFFB401CB52E # 1336 - 1339 D2 S3 C8 neq + .quad 0xfffffffa401cb536, 0xFFFFFFF9401CB53E, 0xFFFFFFF8401FB546, 0xFFFFFFFF401CB50E # 1340 - 1343 D2 S3 C12 neq + .quad 0x0000000040104400, 0x0000000040124481, 0x0000000040124482, 0x0000000040124483 # 1344 - 1347 D2 S4 C0 neq + .quad 0x00000000401a4484, 0x00000000401CC485, 0x00000000401CC486, 0xFFFFFFFF401CC50E # 1348 - 1351 D2 S4 C4 neq + .quad 0xfffffffe401cc516, 0xFFFFFFFD401CC51E, 0xFFFFFFFC401CC526, 0xFFFFFFFB401CC52E # 1352 - 1355 D2 S4 C8 neq + .quad 0xfffffffa401cc536, 0xFFFFFFF9401FC53E, 0xFFFFFFF8401FC546, 0xFFFFFFFF401CC50E # 1356 - 1359 D2 S4 C12 neq + .quad 0x0000000040105400, 0x0000000040125481, 0x0000000040125482, 0x00000000401A5483 # 1360 - 1363 D2 S5 C0 neq + .quad 0x00000000401cd484, 0x00000000401CD485, 0x00000000401CD486, 0xFFFFFFFF401CD50E # 1364 - 1367 D2 S5 C4 neq + .quad 0xfffffffe401cd516, 0xFFFFFFFD401CD51E, 0xFFFFFFFC401CD526, 0xFFFFFFFB401CD52E # 1368 - 1371 D2 S5 C8 neq + .quad 0xfffffffa401fd536, 0xFFFFFFF9401FD53E, 0xFFFFFFF8401FD546, 0xFFFFFFFF401CD50E # 1372 - 1375 D2 S5 C12 neq + .quad 0x0000000040106400, 0x0000000040126481, 0x00000000401A6482, 0x00000000401CE483 # 1376 - 1379 D2 S6 C0 neq + .quad 0x00000000401ce484, 0x00000000401CE485, 0x00000000401CE486, 0xFFFFFFFF401CE50E # 1380 - 1383 D2 S6 C4 neq + .quad 0xfffffffe401ce516, 0xFFFFFFFD401CE51E, 0xFFFFFFFC401CE526, 0xFFFFFFFB401FE52E # 1384 - 1387 D2 S6 C8 neq + .quad 0xfffffffa401fe536, 0xFFFFFFF9401FE53E, 0xFFFFFFF8401FE546, 0xFFFFFFFF401CE50E # 1388 - 1391 D2 S6 C12 neq + .quad 0x0000000040107400, 0x00000000401A7481, 0x00000000401CF482, 0x00000000401CF483 # 1392 - 1395 D2 S7 C0 neq + .quad 0x00000000401cf484, 0x00000000401CF485, 0x00000000401CF486, 0xFFFFFFFF401CF50E # 1396 - 1399 D2 S7 C4 neq + .quad 0xfffffffe401cf516, 0xFFFFFFFD401CF51E, 0xFFFFFFFC401FF526, 0xFFFFFFFB401FF52E # 1400 - 1403 D2 S7 C8 neq + .quad 0xfffffffa401ff536, 0xFFFFFFF9401FF53E, 0xFFFFFFF8401FF546, 0xFFFFFFFF401CF50E # 1404 - 1407 D2 S7 C12 neq + .quad 0x0000000040100600, 0x0000000040120681, 0x0000000040120682, 0x0000000040120683 # 1408 - 1411 D3 S0 C0 neq + .quad 0x0000000040120684, 0x0000000040120685, 0xFFFFFFFF4012070D, 0xFFFFFFFE40120715 # 1412 - 1415 D3 S0 C4 neq + .quad 0xfffffffd4012071d, 0xFFFFFFFC40150725, 0xFFFFFFFB4015072D, 0xFFFFFFFA40150735 # 1416 - 1419 D3 S0 C8 neq + .quad 0xfffffff94015073d, 0xFFFFFFF840150745, 0xFFFFFFFF4012070D, 0xFFFFFFFE40120715 # 1420 - 1423 D3 S0 C12 neq + .quad 0x0000000040101600, 0x0000000040121681, 0x0000000040121682, 0x0000000040121683 # 1424 - 1427 D3 S1 C0 neq + .quad 0x0000000040121684, 0x0000000040121685, 0xFFFFFFFF4012170D, 0xFFFFFFFE40121715 # 1428 - 1431 D3 S1 C4 neq + .quad 0xfffffffd4015171d, 0xFFFFFFFC40151725, 0xFFFFFFFB4015172D, 0xFFFFFFFA40151735 # 1432 - 1435 D3 S1 C8 neq + .quad 0xfffffff94015173d, 0xFFFFFFF840151745, 0xFFFFFFFF4012170D, 0xFFFFFFFE40121715 # 1436 - 1439 D3 S1 C12 neq + .quad 0x0000000040102600, 0x0000000040122681, 0x0000000040122682, 0x0000000040122683 # 1440 - 1443 D3 S2 C0 neq + .quad 0x0000000040122684, 0x0000000040122685, 0xFFFFFFFF4012270D, 0xFFFFFFFE40152715 # 1444 - 1447 D3 S2 C4 neq + .quad 0xfffffffd4015271d, 0xFFFFFFFC40152725, 0xFFFFFFFB4015272D, 0xFFFFFFFA40152735 # 1448 - 1451 D3 S2 C8 neq + .quad 0xfffffff94015273d, 0xFFFFFFF840152745, 0xFFFFFFFF4012270D, 0xFFFFFFFE40152715 # 1452 - 1455 D3 S2 C12 neq + .quad 0x0000000040003600, 0x0000000040023681, 0x0000000040023682, 0x0000000040023683 # 1456 - 1459 D3 S3 C0 neq + .quad 0x0000000040023684, 0x00000000400A3685, 0xFFFFFFFF400C370D, 0xFFFFFFFE400C3715 # 1460 - 1463 D3 S3 C4 neq + .quad 0xfffffffd400c371d, 0xFFFFFFFC400C3725, 0xFFFFFFFB400C372D, 0xFFFFFFFA400C3735 # 1464 - 1467 D3 S3 C8 neq + .quad 0xfffffff9400c373d, 0xFFFFFFF8400C3745, 0xFFFFFFFF400C370D, 0xFFFFFFFE400C3715 # 1468 - 1471 D3 S3 C12 neq + .quad 0x0000000040104600, 0x0000000040124681, 0x0000000040124682, 0x0000000040124683 # 1472 - 1475 D3 S4 C0 neq + .quad 0x00000000401a4684, 0x00000000401CC685, 0xFFFFFFFF401CC70D, 0xFFFFFFFE401CC715 # 1476 - 1479 D3 S4 C4 neq + .quad 0xfffffffd401cc71d, 0xFFFFFFFC401CC725, 0xFFFFFFFB401CC72D, 0xFFFFFFFA401CC735 # 1480 - 1483 D3 S4 C8 neq + .quad 0xfffffff9401cc73d, 0xFFFFFFF8401FC745, 0xFFFFFFFF401CC70D, 0xFFFFFFFE401CC715 # 1484 - 1487 D3 S4 C12 neq + .quad 0x0000000040105600, 0x0000000040125681, 0x0000000040125682, 0x00000000401A5683 # 1488 - 1491 D3 S5 C0 neq + .quad 0x00000000401cd684, 0x00000000401CD685, 0xFFFFFFFF401CD70D, 0xFFFFFFFE401CD715 # 1492 - 1495 D3 S5 C4 neq + .quad 0xfffffffd401cd71d, 0xFFFFFFFC401CD725, 0xFFFFFFFB401CD72D, 0xFFFFFFFA401CD735 # 1496 - 1499 D3 S5 C8 neq + .quad 0xfffffff9401fd73d, 0xFFFFFFF8401FD745, 0xFFFFFFFF401CD70D, 0xFFFFFFFE401CD715 # 1500 - 1503 D3 S5 C12 neq + .quad 0x0000000040106600, 0x0000000040126681, 0x00000000401A6682, 0x00000000401CE683 # 1504 - 1507 D3 S6 C0 neq + .quad 0x00000000401ce684, 0x00000000401CE685, 0xFFFFFFFF401CE70D, 0xFFFFFFFE401CE715 # 1508 - 1511 D3 S6 C4 neq + .quad 0xfffffffd401ce71d, 0xFFFFFFFC401CE725, 0xFFFFFFFB401CE72D, 0xFFFFFFFA401FE735 # 1512 - 1515 D3 S6 C8 neq + .quad 0xfffffff9401fe73d, 0xFFFFFFF8401FE745, 0xFFFFFFFF401CE70D, 0xFFFFFFFE401CE715 # 1516 - 1519 D3 S6 C12 neq + .quad 0x0000000040107600, 0x00000000401A7681, 0x00000000401CF682, 0x00000000401CF683 # 1520 - 1523 D3 S7 C0 neq + .quad 0x00000000401cf684, 0x00000000401CF685, 0xFFFFFFFF401CF70D, 0xFFFFFFFE401CF715 # 1524 - 1527 D3 S7 C4 neq + .quad 0xfffffffd401cf71d, 0xFFFFFFFC401CF725, 0xFFFFFFFB401FF72D, 0xFFFFFFFA401FF735 # 1528 - 1531 D3 S7 C8 neq + .quad 0xfffffff9401ff73d, 0xFFFFFFF8401FF745, 0xFFFFFFFF401CF70D, 0xFFFFFFFE401CF715 # 1532 - 1535 D3 S7 C12 neq + .quad 0x0000000040100800, 0x0000000040120881, 0x0000000040120882, 0x0000000040120883 # 1536 - 1539 D4 S0 C0 neq + .quad 0x0000000040120884, 0xFFFFFFFF4012090C, 0xFFFFFFFE40120914, 0xFFFFFFFD4012091C # 1540 - 1543 D4 S0 C4 neq + .quad 0xfffffffc40120924, 0xFFFFFFFB4015092C, 0xFFFFFFFA40150934, 0xFFFFFFF94015093C # 1544 - 1547 D4 S0 C8 neq + .quad 0xfffffff840150944, 0xFFFFFFFF4012090C, 0xFFFFFFFE40120914, 0xFFFFFFFD4012091C # 1548 - 1551 D4 S0 C12 neq + .quad 0x0000000040101800, 0x0000000040121881, 0x0000000040121882, 0x0000000040121883 # 1552 - 1555 D4 S1 C0 neq + .quad 0x0000000040121884, 0xFFFFFFFF4012190C, 0xFFFFFFFE40121914, 0xFFFFFFFD4012191C # 1556 - 1559 D4 S1 C4 neq + .quad 0xfffffffc40151924, 0xFFFFFFFB4015192C, 0xFFFFFFFA40151934, 0xFFFFFFF94015193C # 1560 - 1563 D4 S1 C8 neq + .quad 0xfffffff840151944, 0xFFFFFFFF4012190C, 0xFFFFFFFE40121914, 0xFFFFFFFD4012191C # 1564 - 1567 D4 S1 C12 neq + .quad 0x0000000040102800, 0x0000000040122881, 0x0000000040122882, 0x0000000040122883 # 1568 - 1571 D4 S2 C0 neq + .quad 0x0000000040122884, 0xFFFFFFFF4012290C, 0xFFFFFFFE40122914, 0xFFFFFFFD4015291C # 1572 - 1575 D4 S2 C4 neq + .quad 0xfffffffc40152924, 0xFFFFFFFB4015292C, 0xFFFFFFFA40152934, 0xFFFFFFF94015293C # 1576 - 1579 D4 S2 C8 neq + .quad 0xfffffff840152944, 0xFFFFFFFF4012290C, 0xFFFFFFFE40122914, 0xFFFFFFFD4015291C # 1580 - 1583 D4 S2 C12 neq + .quad 0x0000000040103800, 0x0000000040123881, 0x0000000040123882, 0x0000000040123883 # 1584 - 1587 D4 S3 C0 neq + .quad 0x0000000040123884, 0xFFFFFFFF4012390C, 0xFFFFFFFE40153914, 0xFFFFFFFD4015391C # 1588 - 1591 D4 S3 C4 neq + .quad 0xfffffffc40153924, 0xFFFFFFFB4015392C, 0xFFFFFFFA40153934, 0xFFFFFFF94015393C # 1592 - 1595 D4 S3 C8 neq + .quad 0xfffffff840153944, 0xFFFFFFFF4012390C, 0xFFFFFFFE40153914, 0xFFFFFFFD4015391C # 1596 - 1599 D4 S3 C12 neq + .quad 0x0000000040004800, 0x0000000040024881, 0x0000000040024882, 0x0000000040024883 # 1600 - 1603 D4 S4 C0 neq + .quad 0x00000000400a4884, 0xFFFFFFFF400C490C, 0xFFFFFFFE400C4914, 0xFFFFFFFD400C491C # 1604 - 1607 D4 S4 C4 neq + .quad 0xfffffffc400c4924, 0xFFFFFFFB400C492C, 0xFFFFFFFA400C4934, 0xFFFFFFF9400C493C # 1608 - 1611 D4 S4 C8 neq + .quad 0xfffffff8400c4944, 0xFFFFFFFF400C490C, 0xFFFFFFFE400C4914, 0xFFFFFFFD400C491C # 1612 - 1615 D4 S4 C12 neq + .quad 0x0000000040105800, 0x0000000040125881, 0x0000000040125882, 0x00000000401A5883 # 1616 - 1619 D4 S5 C0 neq + .quad 0x00000000401cd884, 0xFFFFFFFF401CD90C, 0xFFFFFFFE401CD914, 0xFFFFFFFD401CD91C # 1620 - 1623 D4 S5 C4 neq + .quad 0xfffffffc401cd924, 0xFFFFFFFB401CD92C, 0xFFFFFFFA401CD934, 0xFFFFFFF9401CD93C # 1624 - 1627 D4 S5 C8 neq + .quad 0xfffffff8401fd944, 0xFFFFFFFF401CD90C, 0xFFFFFFFE401CD914, 0xFFFFFFFD401CD91C # 1628 - 1631 D4 S5 C12 neq + .quad 0x0000000040106800, 0x0000000040126881, 0x00000000401A6882, 0x00000000401CE883 # 1632 - 1635 D4 S6 C0 neq + .quad 0x00000000401ce884, 0xFFFFFFFF401CE90C, 0xFFFFFFFE401CE914, 0xFFFFFFFD401CE91C # 1636 - 1639 D4 S6 C4 neq + .quad 0xfffffffc401ce924, 0xFFFFFFFB401CE92C, 0xFFFFFFFA401CE934, 0xFFFFFFF9401FE93C # 1640 - 1643 D4 S6 C8 neq + .quad 0xfffffff8401fe944, 0xFFFFFFFF401CE90C, 0xFFFFFFFE401CE914, 0xFFFFFFFD401CE91C # 1644 - 1647 D4 S6 C12 neq + .quad 0x0000000040107800, 0x00000000401A7881, 0x00000000401CF882, 0x00000000401CF883 # 1648 - 1651 D4 S7 C0 neq + .quad 0x00000000401cf884, 0xFFFFFFFF401CF90C, 0xFFFFFFFE401CF914, 0xFFFFFFFD401CF91C # 1652 - 1655 D4 S7 C4 neq + .quad 0xfffffffc401cf924, 0xFFFFFFFB401CF92C, 0xFFFFFFFA401FF934, 0xFFFFFFF9401FF93C # 1656 - 1659 D4 S7 C8 neq + .quad 0xfffffff8401ff944, 0xFFFFFFFF401CF90C, 0xFFFFFFFE401CF914, 0xFFFFFFFD401CF91C # 1660 - 1663 D4 S7 C12 neq + .quad 0x0000000040100a00, 0x0000000040120A81, 0x0000000040120A82, 0x0000000040120A83 # 1664 - 1667 D5 S0 C0 neq + .quad 0xffffffff40120b0b, 0xFFFFFFFE40120B13, 0xFFFFFFFD40120B1B, 0xFFFFFFFC40120B23 # 1668 - 1671 D5 S0 C4 neq + .quad 0xfffffffb40120b2b, 0xFFFFFFFA40150B33, 0xFFFFFFF940150B3B, 0xFFFFFFF840150B43 # 1672 - 1675 D5 S0 C8 neq + .quad 0xffffffff40120b0b, 0xFFFFFFFE40120B13, 0xFFFFFFFD40120B1B, 0xFFFFFFFC40120B23 # 1676 - 1679 D5 S0 C12 neq + .quad 0x0000000040101a00, 0x0000000040121A81, 0x0000000040121A82, 0x0000000040121A83 # 1680 - 1683 D5 S1 C0 neq + .quad 0xffffffff40121b0b, 0xFFFFFFFE40121B13, 0xFFFFFFFD40121B1B, 0xFFFFFFFC40121B23 # 1684 - 1687 D5 S1 C4 neq + .quad 0xfffffffb40151b2b, 0xFFFFFFFA40151B33, 0xFFFFFFF940151B3B, 0xFFFFFFF840151B43 # 1688 - 1691 D5 S1 C8 neq + .quad 0xffffffff40121b0b, 0xFFFFFFFE40121B13, 0xFFFFFFFD40121B1B, 0xFFFFFFFC40121B23 # 1692 - 1695 D5 S1 C12 neq + .quad 0x0000000040102a00, 0x0000000040122A81, 0x0000000040122A82, 0x0000000040122A83 # 1696 - 1699 D5 S2 C0 neq + .quad 0xffffffff40122b0b, 0xFFFFFFFE40122B13, 0xFFFFFFFD40122B1B, 0xFFFFFFFC40152B23 # 1700 - 1703 D5 S2 C4 neq + .quad 0xfffffffb40152b2b, 0xFFFFFFFA40152B33, 0xFFFFFFF940152B3B, 0xFFFFFFF840152B43 # 1704 - 1707 D5 S2 C8 neq + .quad 0xffffffff40122b0b, 0xFFFFFFFE40122B13, 0xFFFFFFFD40122B1B, 0xFFFFFFFC40152B23 # 1708 - 1711 D5 S2 C12 neq + .quad 0x0000000040103a00, 0x0000000040123A81, 0x0000000040123A82, 0x0000000040123A83 # 1712 - 1715 D5 S3 C0 neq + .quad 0xffffffff40123b0b, 0xFFFFFFFE40123B13, 0xFFFFFFFD40153B1B, 0xFFFFFFFC40153B23 # 1716 - 1719 D5 S3 C4 neq + .quad 0xfffffffb40153b2b, 0xFFFFFFFA40153B33, 0xFFFFFFF940153B3B, 0xFFFFFFF840153B43 # 1720 - 1723 D5 S3 C8 neq + .quad 0xffffffff40123b0b, 0xFFFFFFFE40123B13, 0xFFFFFFFD40153B1B, 0xFFFFFFFC40153B23 # 1724 - 1727 D5 S3 C12 neq + .quad 0x0000000040104a00, 0x0000000040124A81, 0x0000000040124A82, 0x0000000040124A83 # 1728 - 1731 D5 S4 C0 neq + .quad 0xffffffff40124b0b, 0xFFFFFFFE40154B13, 0xFFFFFFFD40154B1B, 0xFFFFFFFC40154B23 # 1732 - 1735 D5 S4 C4 neq + .quad 0xfffffffb40154b2b, 0xFFFFFFFA40154B33, 0xFFFFFFF940154B3B, 0xFFFFFFF840154B43 # 1736 - 1739 D5 S4 C8 neq + .quad 0xffffffff40124b0b, 0xFFFFFFFE40154B13, 0xFFFFFFFD40154B1B, 0xFFFFFFFC40154B23 # 1740 - 1743 D5 S4 C12 neq + .quad 0x0000000040005a00, 0x0000000040025A81, 0x0000000040025A82, 0x00000000400A5A83 # 1744 - 1747 D5 S5 C0 neq + .quad 0xffffffff400c5b0b, 0xFFFFFFFE400C5B13, 0xFFFFFFFD400C5B1B, 0xFFFFFFFC400C5B23 # 1748 - 1751 D5 S5 C4 neq + .quad 0xfffffffb400c5b2b, 0xFFFFFFFA400C5B33, 0xFFFFFFF9400C5B3B, 0xFFFFFFF8400C5B43 # 1752 - 1755 D5 S5 C8 neq + .quad 0xffffffff400c5b0b, 0xFFFFFFFE400C5B13, 0xFFFFFFFD400C5B1B, 0xFFFFFFFC400C5B23 # 1756 - 1759 D5 S5 C12 neq + .quad 0x0000000040106a00, 0x0000000040126A81, 0x00000000401A6A82, 0x00000000401CEA83 # 1760 - 1763 D5 S6 C0 neq + .quad 0xffffffff401ceb0b, 0xFFFFFFFE401CEB13, 0xFFFFFFFD401CEB1B, 0xFFFFFFFC401CEB23 # 1764 - 1767 D5 S6 C4 neq + .quad 0xfffffffb401ceb2b, 0xFFFFFFFA401CEB33, 0xFFFFFFF9401CEB3B, 0xFFFFFFF8401FEB43 # 1768 - 1771 D5 S6 C8 neq + .quad 0xffffffff401ceb0b, 0xFFFFFFFE401CEB13, 0xFFFFFFFD401CEB1B, 0xFFFFFFFC401CEB23 # 1772 - 1775 D5 S6 C12 neq + .quad 0x0000000040107a00, 0x00000000401A7A81, 0x00000000401CFA82, 0x00000000401CFA83 # 1776 - 1779 D5 S7 C0 neq + .quad 0xffffffff401cfb0b, 0xFFFFFFFE401CFB13, 0xFFFFFFFD401CFB1B, 0xFFFFFFFC401CFB23 # 1780 - 1783 D5 S7 C4 neq + .quad 0xfffffffb401cfb2b, 0xFFFFFFFA401CFB33, 0xFFFFFFF9401FFB3B, 0xFFFFFFF8401FFB43 # 1784 - 1787 D5 S7 C8 neq + .quad 0xffffffff401cfb0b, 0xFFFFFFFE401CFB13, 0xFFFFFFFD401CFB1B, 0xFFFFFFFC401CFB23 # 1788 - 1791 D5 S7 C12 neq + .quad 0x0000000040100c00, 0x0000000040120C81, 0x0000000040120C82, 0xFFFFFFFF40120D0A # 1792 - 1795 D6 S0 C0 neq + .quad 0xfffffffe40120d12, 0xFFFFFFFD40120D1A, 0xFFFFFFFC40120D22, 0xFFFFFFFB40120D2A # 1796 - 1799 D6 S0 C4 neq + .quad 0xfffffffa40120d32, 0xFFFFFFF940150D3A, 0xFFFFFFF840150D42, 0xFFFFFFFF40120D0A # 1800 - 1803 D6 S0 C8 neq + .quad 0xfffffffe40120d12, 0xFFFFFFFD40120D1A, 0xFFFFFFFC40120D22, 0xFFFFFFFB40120D2A # 1804 - 1807 D6 S0 C12 neq + .quad 0x0000000040101c00, 0x0000000040121C81, 0x0000000040121C82, 0xFFFFFFFF40121D0A # 1808 - 1811 D6 S1 C0 neq + .quad 0xfffffffe40121d12, 0xFFFFFFFD40121D1A, 0xFFFFFFFC40121D22, 0xFFFFFFFB40121D2A # 1812 - 1815 D6 S1 C4 neq + .quad 0xfffffffa40151d32, 0xFFFFFFF940151D3A, 0xFFFFFFF840151D42, 0xFFFFFFFF40121D0A # 1816 - 1819 D6 S1 C8 neq + .quad 0xfffffffe40121d12, 0xFFFFFFFD40121D1A, 0xFFFFFFFC40121D22, 0xFFFFFFFB40121D2A # 1820 - 1823 D6 S1 C12 neq + .quad 0x0000000040102c00, 0x0000000040122C81, 0x0000000040122C82, 0xFFFFFFFF40122D0A # 1824 - 1827 D6 S2 C0 neq + .quad 0xfffffffe40122d12, 0xFFFFFFFD40122D1A, 0xFFFFFFFC40122D22, 0xFFFFFFFB40152D2A # 1828 - 1831 D6 S2 C4 neq + .quad 0xfffffffa40152d32, 0xFFFFFFF940152D3A, 0xFFFFFFF840152D42, 0xFFFFFFFF40122D0A # 1832 - 1835 D6 S2 C8 neq + .quad 0xfffffffe40122d12, 0xFFFFFFFD40122D1A, 0xFFFFFFFC40122D22, 0xFFFFFFFB40152D2A # 1836 - 1839 D6 S2 C12 neq + .quad 0x0000000040103c00, 0x0000000040123C81, 0x0000000040123C82, 0xFFFFFFFF40123D0A # 1840 - 1843 D6 S3 C0 neq + .quad 0xfffffffe40123d12, 0xFFFFFFFD40123D1A, 0xFFFFFFFC40153D22, 0xFFFFFFFB40153D2A # 1844 - 1847 D6 S3 C4 neq + .quad 0xfffffffa40153d32, 0xFFFFFFF940153D3A, 0xFFFFFFF840153D42, 0xFFFFFFFF40123D0A # 1848 - 1851 D6 S3 C8 neq + .quad 0xfffffffe40123d12, 0xFFFFFFFD40123D1A, 0xFFFFFFFC40153D22, 0xFFFFFFFB40153D2A # 1852 - 1855 D6 S3 C12 neq + .quad 0x0000000040104c00, 0x0000000040124C81, 0x0000000040124C82, 0xFFFFFFFF40124D0A # 1856 - 1859 D6 S4 C0 neq + .quad 0xfffffffe40124d12, 0xFFFFFFFD40154D1A, 0xFFFFFFFC40154D22, 0xFFFFFFFB40154D2A # 1860 - 1863 D6 S4 C4 neq + .quad 0xfffffffa40154d32, 0xFFFFFFF940154D3A, 0xFFFFFFF840154D42, 0xFFFFFFFF40124D0A # 1864 - 1867 D6 S4 C8 neq + .quad 0xfffffffe40124d12, 0xFFFFFFFD40154D1A, 0xFFFFFFFC40154D22, 0xFFFFFFFB40154D2A # 1868 - 1871 D6 S4 C12 neq + .quad 0x0000000040105c00, 0x0000000040125C81, 0x0000000040125C82, 0xFFFFFFFF40125D0A # 1872 - 1875 D6 S5 C0 neq + .quad 0xfffffffe40155d12, 0xFFFFFFFD40155D1A, 0xFFFFFFFC40155D22, 0xFFFFFFFB40155D2A # 1876 - 1879 D6 S5 C4 neq + .quad 0xfffffffa40155d32, 0xFFFFFFF940155D3A, 0xFFFFFFF840155D42, 0xFFFFFFFF40125D0A # 1880 - 1883 D6 S5 C8 neq + .quad 0xfffffffe40155d12, 0xFFFFFFFD40155D1A, 0xFFFFFFFC40155D22, 0xFFFFFFFB40155D2A # 1884 - 1887 D6 S5 C12 neq + .quad 0x0000000040006c00, 0x0000000040026C81, 0x00000000400A6C82, 0xFFFFFFFF400C6D0A # 1888 - 1891 D6 S6 C0 neq + .quad 0xfffffffe400c6d12, 0xFFFFFFFD400C6D1A, 0xFFFFFFFC400C6D22, 0xFFFFFFFB400C6D2A # 1892 - 1895 D6 S6 C4 neq + .quad 0xfffffffa400c6d32, 0xFFFFFFF9400C6D3A, 0xFFFFFFF8400C6D42, 0xFFFFFFFF400C6D0A # 1896 - 1899 D6 S6 C8 neq + .quad 0xfffffffe400c6d12, 0xFFFFFFFD400C6D1A, 0xFFFFFFFC400C6D22, 0xFFFFFFFB400C6D2A # 1900 - 1903 D6 S6 C12 neq + .quad 0x0000000040107c00, 0x00000000401A7C81, 0x00000000401CFC82, 0xFFFFFFFF401CFD0A # 1904 - 1907 D6 S7 C0 neq + .quad 0xfffffffe401cfd12, 0xFFFFFFFD401CFD1A, 0xFFFFFFFC401CFD22, 0xFFFFFFFB401CFD2A # 1908 - 1911 D6 S7 C4 neq + .quad 0xfffffffa401cfd32, 0xFFFFFFF9401CFD3A, 0xFFFFFFF8401FFD42, 0xFFFFFFFF401CFD0A # 1912 - 1915 D6 S7 C8 neq + .quad 0xfffffffe401cfd12, 0xFFFFFFFD401CFD1A, 0xFFFFFFFC401CFD22, 0xFFFFFFFB401CFD2A # 1916 - 1919 D6 S7 C12 neq + .quad 0x0000000040100e00, 0x0000000040120E81, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 1920 - 1923 D7 S0 C0 neq + .quad 0xfffffffd40120f19, 0xFFFFFFFC40120F21, 0xFFFFFFFB40120F29, 0xFFFFFFFA40120F31 # 1924 - 1927 D7 S0 C4 neq + .quad 0xfffffff940120f39, 0xFFFFFFF840150F41, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 1928 - 1931 D7 S0 C8 neq + .quad 0xfffffffd40120f19, 0xFFFFFFFC40120F21, 0xFFFFFFFB40120F29, 0xFFFFFFFA40120F31 # 1932 - 1935 D7 S0 C12 neq + .quad 0x0000000040101e00, 0x0000000040121E81, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 1936 - 1939 D7 S1 C0 neq + .quad 0xfffffffd40121f19, 0xFFFFFFFC40121F21, 0xFFFFFFFB40121F29, 0xFFFFFFFA40121F31 # 1940 - 1943 D7 S1 C4 neq + .quad 0xfffffff940151f39, 0xFFFFFFF840151F41, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 1944 - 1947 D7 S1 C8 neq + .quad 0xfffffffd40121f19, 0xFFFFFFFC40121F21, 0xFFFFFFFB40121F29, 0xFFFFFFFA40121F31 # 1948 - 1951 D7 S1 C12 neq + .quad 0x0000000040102e00, 0x0000000040122E81, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 1952 - 1955 D7 S2 C0 neq + .quad 0xfffffffd40122f19, 0xFFFFFFFC40122F21, 0xFFFFFFFB40122F29, 0xFFFFFFFA40152F31 # 1956 - 1959 D7 S2 C4 neq + .quad 0xfffffff940152f39, 0xFFFFFFF840152F41, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 1960 - 1963 D7 S2 C8 neq + .quad 0xfffffffd40122f19, 0xFFFFFFFC40122F21, 0xFFFFFFFB40122F29, 0xFFFFFFFA40152F31 # 1964 - 1967 D7 S2 C12 neq + .quad 0x0000000040103e00, 0x0000000040123E81, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 1968 - 1971 D7 S3 C0 neq + .quad 0xfffffffd40123f19, 0xFFFFFFFC40123F21, 0xFFFFFFFB40153F29, 0xFFFFFFFA40153F31 # 1972 - 1975 D7 S3 C4 neq + .quad 0xfffffff940153f39, 0xFFFFFFF840153F41, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 1976 - 1979 D7 S3 C8 neq + .quad 0xfffffffd40123f19, 0xFFFFFFFC40123F21, 0xFFFFFFFB40153F29, 0xFFFFFFFA40153F31 # 1980 - 1983 D7 S3 C12 neq + .quad 0x0000000040104e00, 0x0000000040124E81, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 1984 - 1987 D7 S4 C0 neq + .quad 0xfffffffd40124f19, 0xFFFFFFFC40154F21, 0xFFFFFFFB40154F29, 0xFFFFFFFA40154F31 # 1988 - 1991 D7 S4 C4 neq + .quad 0xfffffff940154f39, 0xFFFFFFF840154F41, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 1992 - 1995 D7 S4 C8 neq + .quad 0xfffffffd40124f19, 0xFFFFFFFC40154F21, 0xFFFFFFFB40154F29, 0xFFFFFFFA40154F31 # 1996 - 1999 D7 S4 C12 neq + .quad 0x0000000040105e00, 0x0000000040125E81, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 2000 - 2003 D7 S5 C0 neq + .quad 0xfffffffd40155f19, 0xFFFFFFFC40155F21, 0xFFFFFFFB40155F29, 0xFFFFFFFA40155F31 # 2004 - 2007 D7 S5 C4 neq + .quad 0xfffffff940155f39, 0xFFFFFFF840155F41, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 2008 - 2011 D7 S5 C8 neq + .quad 0xfffffffd40155f19, 0xFFFFFFFC40155F21, 0xFFFFFFFB40155F29, 0xFFFFFFFA40155F31 # 2012 - 2015 D7 S5 C12 neq + .quad 0x0000000040106e00, 0x0000000040126E81, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 2016 - 2019 D7 S6 C0 neq + .quad 0xfffffffd40156f19, 0xFFFFFFFC40156F21, 0xFFFFFFFB40156F29, 0xFFFFFFFA40156F31 # 2020 - 2023 D7 S6 C4 neq + .quad 0xfffffff940156f39, 0xFFFFFFF840156F41, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 2024 - 2027 D7 S6 C8 neq + .quad 0xfffffffd40156f19, 0xFFFFFFFC40156F21, 0xFFFFFFFB40156F29, 0xFFFFFFFA40156F31 # 2028 - 2031 D7 S6 C12 neq + .quad 0x0000000040007e00, 0x00000000400A7E81, 0xFFFFFFFF400C7F09, 0xFFFFFFFE400C7F11 # 2032 - 2035 D7 S7 C0 neq + .quad 0xfffffffd400c7f19, 0xFFFFFFFC400C7F21, 0xFFFFFFFB400C7F29, 0xFFFFFFFA400C7F31 # 2036 - 2039 D7 S7 C4 neq + .quad 0xfffffff9400c7f39, 0xFFFFFFF8400C7F41, 0xFFFFFFFF400C7F09, 0xFFFFFFFE400C7F11 # 2040 - 2043 D7 S7 C8 neq + .quad 0xfffffffd400c7f19, 0xFFFFFFFC400C7F21, 0xFFFFFFFB400C7F29, 0xFFFFFFFA400C7F31 # 2044 - 2047 D7 S7 C12 neq + + diff --git a/emulator-asm/src/dma/fast_memcpy.asm b/emulator-asm/src/dma/fast_memcpy.asm new file mode 100644 index 000000000..87d5a16ef --- /dev/null +++ b/emulator-asm/src/dma/fast_memcpy.asm @@ -0,0 +1,133 @@ +.intel_syntax noprefix +.code64 +.text +.global fast_memcpy +.type fast_memcpy, @function + +# void fast_memcpy(void* dst, void *src, size_t n) +# rdi = dst +# rsi = src +# rdx = n (bytes) +# +# Clobbers: rax, rcx, r9 + +fast_memcpy: + test rdx, 0xFFFFFFFFFFFFFFF8 + jz .L_fast_memcpy_count_lt_8 + + # only first could be lt 32x8=256 bytes + movzx ecx, dl + and rdx, 0xFFFFFFFFFFFFFF07 + shr rcx, 3 + + # Jump to entry that leaves exactly q MOVSQ until the end + lea r9, [rip + .L_fast_memcpy_jump_qword_table] + jmp [r9 + rcx*8] + +.p2align 3 +.L_fast_memcpy_jump_qword_table: + .quad .L_q0 + .quad .L_q1 + .quad .L_q2 + .quad .L_q3 + .quad .L_q4 + .quad .L_q5 + .quad .L_q6 + .quad .L_q7 + .quad .L_q8 + .quad .L_q9 + .quad .L_q10 + .quad .L_q11 + .quad .L_q12 + .quad .L_q13 + .quad .L_q14 + .quad .L_q15 + .quad .L_q16 + .quad .L_q17 + .quad .L_q18 + .quad .L_q19 + .quad .L_q20 + .quad .L_q21 + .quad .L_q22 + .quad .L_q23 + .quad .L_q24 + .quad .L_q25 + .quad .L_q26 + .quad .L_q27 + .quad .L_q28 + .quad .L_q29 + .quad .L_q30 + .quad .L_q31 + .quad .L_q32 + +# Fallthrough chain: entering at q31 executes 31 STOSQ down to q1 +.L_q32: movsq +.L_q31: movsq +.L_q30: movsq +.L_q29: movsq +.L_q28: movsq +.L_q27: movsq +.L_q26: movsq +.L_q25: movsq +.L_q24: movsq +.L_q23: movsq +.L_q22: movsq +.L_q21: movsq +.L_q20: movsq +.L_q19: movsq +.L_q18: movsq +.L_q17: movsq +.L_q16: movsq +.L_q15: movsq +.L_q14: movsq +.L_q13: movsq +.L_q12: movsq +.L_q11: movsq +.L_q10: movsq +.L_q9: movsq +.L_q8: movsq +.L_q7: movsq +.L_q6: movsq +.L_q5: movsq +.L_q4: movsq +.L_q3: movsq +.L_q2: movsq +.L_q1: movsq +.L_q0: + + # check if remain more 32 x 8 = 256 bytes blocks + + test rdx, 0xFFFFFFFFFFFFFF00 # 0xFFFF_FFFF_FFFF_FF00 + jz .L_fast_memcpy_count_lt_8 + sub rdx, 256 + jmp .L_q32 + + +.L_fast_memcpy_count_lt_8: + + # Jump to byte tail entry + lea r9, [rip + .L_fast_memcpy_jump_byte_table] + jmp [r9 + rdx*8] + +.p2align 3 +.L_fast_memcpy_jump_byte_table: + .quad .L_b0 + .quad .L_b1 + .quad .L_b2 + .quad .L_b3 + .quad .L_b4 + .quad .L_b5 + .quad .L_b6 + .quad .L_b7 + +.L_b7: movsb +.L_b6: movsb +.L_b5: movsb +.L_b4: movsb +.L_b3: movsb +.L_b2: movsb +.L_b1: movsb +.L_b0: + ret + +.size fast_memcpy, .-fast_memcpy diff --git a/emulator-asm/src/dma/fast_memcpy64.asm b/emulator-asm/src/dma/fast_memcpy64.asm new file mode 100644 index 000000000..264340c5f --- /dev/null +++ b/emulator-asm/src/dma/fast_memcpy64.asm @@ -0,0 +1,111 @@ +.intel_syntax noprefix +.code64 +.text +.global fast_memcpy64 +.type fast_memcpy64, @function + +# void fast_memcpy64(uint64_t* dst, uint64_t *v, size_t n_qwords) +# rdi = dst +# rsi = src +# rdx = n (QWORDS) +# +# Clobbers: rax, rcx, r9 + +fast_memcpy64: + + # only first could be lt 32x8=256 bytes + # 256 bytes => 32 qwords + test rdx, 0x1F + jz .L_fast_memcpy64_mul256 + + mov rcx, rdx + and rdx, 0xFFFFFFFFFFFFFFE0 + sub rcx, rdx + + # Jump to entry that leaves exactly q MOVSQ until the end + lea r9, [rip + .L_fast_memcpy64_jump_qword_table] + jmp [r9 + rcx*8] + +.p2align 3 +.L_fast_memcpy64_jump_qword_table: + .quad .L_q0 + .quad .L_q1 + .quad .L_q2 + .quad .L_q3 + .quad .L_q4 + .quad .L_q5 + .quad .L_q6 + .quad .L_q7 + .quad .L_q8 + .quad .L_q9 + .quad .L_q10 + .quad .L_q11 + .quad .L_q12 + .quad .L_q13 + .quad .L_q14 + .quad .L_q15 + .quad .L_q16 + .quad .L_q17 + .quad .L_q18 + .quad .L_q19 + .quad .L_q20 + .quad .L_q21 + .quad .L_q22 + .quad .L_q23 + .quad .L_q24 + .quad .L_q25 + .quad .L_q26 + .quad .L_q27 + .quad .L_q28 + .quad .L_q29 + .quad .L_q30 + .quad .L_q31 + .quad .L_q32 + +# Fallthrough chain: entering at q31 executes 31 STOSQ down to q1 +.L_q32: movsq +.L_q31: movsq +.L_q30: movsq +.L_q29: movsq +.L_q28: movsq +.L_q27: movsq +.L_q26: movsq +.L_q25: movsq +.L_q24: movsq +.L_q23: movsq +.L_q22: movsq +.L_q21: movsq +.L_q20: movsq +.L_q19: movsq +.L_q18: movsq +.L_q17: movsq +.L_q16: movsq +.L_q15: movsq +.L_q14: movsq +.L_q13: movsq +.L_q12: movsq +.L_q11: movsq +.L_q10: movsq +.L_q9: movsq +.L_q8: movsq +.L_q7: movsq +.L_q6: movsq +.L_q5: movsq +.L_q4: movsq +.L_q3: movsq +.L_q2: movsq +.L_q1: movsq +.L_q0: + + # check if remain more 32 x 8 = 256 bytes blocks + +.L_fast_memcpy64_mul256: + test rdx, 0xFFFFFFFFFFFFFFE0 # 0xFFFF_FFFF_FFFF_FF00 + jz .L_fast_memcpy64_done + sub rdx, 32 + jmp .L_q32 + +.L_fast_memcpy64_done: + ret + +.size fast_memcpy64, .-fast_memcpy64 diff --git a/emulator-asm/src/dma/fast_memset.asm b/emulator-asm/src/dma/fast_memset.asm new file mode 100644 index 000000000..7cdd98de2 --- /dev/null +++ b/emulator-asm/src/dma/fast_memset.asm @@ -0,0 +1,139 @@ +.intel_syntax noprefix +.code64 +.text +.global fast_memset +.type fast_memset, @function + +# void fast_memset(void* dst, uint8_t v, size_t n) +# rdi = dst +# rsi = v (only low 8 bits used) +# rdx = n (bytes) +# +# Clobbers: rax, rcx, r9 + +fast_memset: + movzx eax, sil + + test rdx, 0xFFFFFFFFFFFFFFF8 + jz .L_fast_memset_count_lt_8 + + # Build 64-bit pattern 0xvvvvvvvvvvvvvvvv in RAX (needed for all paths) + mov r9, 0x0101010101010101 + imul rax, r9 # rax = v * 0x0101010101010101 + + # only first could be lt 32x8=256 bytes + movzx ecx, dl + and rdx, 0xFFFFFFFFFFFFFF07 + + # Jump to entry that leaves exactly q STOSQ until the end + shr ecx, 3 + lea r9, [rip + .L_fast_memcpy_jump_qword_table] + jmp [r9 + rcx*8] + +.p2align 3 +.L_fast_memcpy_jump_qword_table: + .quad .L_q0 + .quad .L_q1 + .quad .L_q2 + .quad .L_q3 + .quad .L_q4 + .quad .L_q5 + .quad .L_q6 + .quad .L_q7 + .quad .L_q8 + .quad .L_q9 + .quad .L_q10 + .quad .L_q11 + .quad .L_q12 + .quad .L_q13 + .quad .L_q14 + .quad .L_q15 + .quad .L_q16 + .quad .L_q17 + .quad .L_q18 + .quad .L_q19 + .quad .L_q20 + .quad .L_q21 + .quad .L_q22 + .quad .L_q23 + .quad .L_q24 + .quad .L_q25 + .quad .L_q26 + .quad .L_q27 + .quad .L_q28 + .quad .L_q29 + .quad .L_q30 + .quad .L_q31 + .quad .L_q32 + +# Fallthrough chain: entering at q31 executes 31 STOSQ down to q1 +.L_q32: stosq +.L_q31: stosq +.L_q30: stosq +.L_q29: stosq +.L_q28: stosq +.L_q27: stosq +.L_q26: stosq +.L_q25: stosq +.L_q24: stosq +.L_q23: stosq +.L_q22: stosq +.L_q21: stosq +.L_q20: stosq +.L_q19: stosq +.L_q18: stosq +.L_q17: stosq +.L_q16: stosq +.L_q15: stosq +.L_q14: stosq +.L_q13: stosq +.L_q12: stosq +.L_q11: stosq +.L_q10: stosq +.L_q9: stosq +.L_q8: stosq +.L_q7: stosq +.L_q6: stosq +.L_q5: stosq +.L_q4: stosq +.L_q3: stosq +.L_q2: stosq +.L_q1: stosq +.L_q0: + + # check if remain more 32 x 8 = 256 bytes blocks + + test rdx, 0xFFFFFFFFFFFFFF00 # 0xFFFF_FFFF_FFFF_FF00 + jz .L_fast_memset_count_lt_8 + sub rdx, 256 + jmp .L_q32 + + +.L_fast_memset_count_lt_8: + + # Jump to byte tail entry + lea r9, [rip + .L_fast_memset_jump_byte_table] + jmp [r9 + rdx*8] + +.p2align 3 +.L_fast_memset_jump_byte_table: + .quad .L_b0 + .quad .L_b1 + .quad .L_b2 + .quad .L_b3 + .quad .L_b4 + .quad .L_b5 + .quad .L_b6 + .quad .L_b7 + +.L_b7: stosb +.L_b6: stosb +.L_b5: stosb +.L_b4: stosb +.L_b3: stosb +.L_b2: stosb +.L_b1: stosb +.L_b0: + ret + +.size fast_memset, .-fast_memset \ No newline at end of file diff --git a/emulator-asm/src/dma/test_dma.cpp b/emulator-asm/src/dma/test_dma.cpp index dc7005b44..86173760d 100644 --- a/emulator-asm/src/dma/test_dma.cpp +++ b/emulator-asm/src/dma/test_dma.cpp @@ -9,11 +9,18 @@ // External assembly function declarations extern "C" { + uint64_t trace_address_threshold = 0; uint64_t dma_memcpy_mtrace(uint64_t dst, uint64_t src, uint64_t count, uint64_t* trace_ptr); uint64_t dma_memcpy_mops(uint64_t dst, uint64_t src, uint64_t count, uint64_t* mops_ptr); void dma_memcpy_fast(uint64_t dst, uint64_t src, uint64_t count); uint64_t fast_dma_encode(uint64_t dst, uint64_t src, uint64_t count); + void _realloc_trace(void); } + +void _realloc_trace(void) { + +} + const char *mops_labels[16] = {"NOP", "CWR1", "RD1", "WR1", "RD2", "WR2", "RD4", "WR4", "RD8", "WR8", "ARD", "AWR", "BR", "BW", "ABR", "ABW"}; diff --git a/emulator-asm/src/dma/test_dma_inputcpy b/emulator-asm/src/dma/test_dma_inputcpy new file mode 100755 index 0000000000000000000000000000000000000000..02bf8f18a92593a788376914036a88d0fd2d5688 GIT binary patch literal 92392 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 literal 0 HcmV?d00001 diff --git a/emulator-asm/src/dma/test_dma_inputcpy_mops b/emulator-asm/src/dma/test_dma_inputcpy_mops new file mode 100755 index 0000000000000000000000000000000000000000..ce3868986bf23b58c191154e63fe7424849b315b GIT binary patch literal 93368 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 Q`Tt!?r+h4sA#iK`KWx!rjQ{`u literal 0 HcmV?d00001 diff --git a/emulator-asm/src/dma/test_dma_inputcpy_mops.cpp b/emulator-asm/src/dma/test_dma_inputcpy_mops.cpp new file mode 100644 index 000000000..c20facc84 --- /dev/null +++ b/emulator-asm/src/dma/test_dma_inputcpy_mops.cpp @@ -0,0 +1,334 @@ +#include +#include +#include +#include +#include +#include +#include + +// FCALL constants from dma_constants.inc +const uint64_t FCALL_PARAMS_LENGTH = 386; +const uint64_t FCALL_RESULT_LENGTH = 8193; +const uint64_t FCALL_FUNCTION_ID = 0; +const uint64_t FCALL_PARAMS_CAPACITY = FCALL_FUNCTION_ID + 1; +const uint64_t FCALL_PARAMS_SIZE = FCALL_PARAMS_CAPACITY + 1; +const uint64_t FCALL_PARAMS = FCALL_PARAMS_SIZE + 1; +const uint64_t FCALL_RESULT_CAPACITY = FCALL_PARAMS + FCALL_PARAMS_LENGTH; +const uint64_t FCALL_RESULT_SIZE = FCALL_RESULT_CAPACITY + 1; +const uint64_t FCALL_RESULT = FCALL_RESULT_SIZE + 1; // 391 +const uint64_t FCALL_RESULT_GOT = FCALL_RESULT + FCALL_RESULT_LENGTH; // 8584 +const uint64_t FCALL_CTX_LENGTH = FCALL_RESULT_GOT + 1; // 8585 + +// External assembly function declarations and fcall_ctx +extern "C" { + uint64_t trace_address_threshold = 0; + uint64_t fcall_ctx[FCALL_CTX_LENGTH]; + uint64_t dma_inputcpy_mops(uint64_t dst, uint64_t count, uint64_t* mops_ptr); +} + +const char *mops_labels[16] = {"NOP", "CWR1", "RD1", "WR1", "RD2", "WR2", "RD4", "WR4", "RD8", "WR8", + "ARD", "AWR", "BR", "BW", "ABR", "ABW"}; + +// MOPS constants from dma_constants.inc +const uint64_t MOPS_ALIGNED_READ = 0x0000'000C'0000'0000ULL; +const uint64_t MOPS_ALIGNED_BLOCK_WRITE = 0x0000'000F'0000'0000ULL; +const uint64_t MOPS_BLOCK_WORDS_SBITS = 36; +const uint64_t ALIGN_MASK = 0xFFFF'FFFF'FFFF'FFF8ULL; + +// Helper class to manage aligned test buffers +class AlignedBuffer { +public: + std::vector data; + + AlignedBuffer(size_t size) : data(size, 0) {} + + uint64_t* aligned_ptr() { + return reinterpret_cast(data.data()); + } + + uint8_t* byte_ptr() { + return data.data(); + } + + void fill_pattern(uint8_t start = 0) { + for (size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast(start + i); + } + } + + void fill_value(uint8_t value) { + std::fill(data.begin(), data.end(), value); + } +}; + +void print_mops_trace(uint64_t* mops_ptr, size_t mops_count) { + std::cout << "MOPS Trace (" << mops_count << " entries):\n"; + for (size_t i = 0; i < mops_count; ++i) { + uint64_t entry = mops_ptr[i]; + uint64_t opcode = (entry >> 32) & 0x0F; + uint64_t addr = entry & 0xFFFF'FFFF; + uint64_t block_words = entry >> MOPS_BLOCK_WORDS_SBITS; + + printf(" [%ld] %s (0x%X) addr=0x%08lX", i, mops_labels[opcode], (unsigned)opcode, addr); + if (opcode == 0x0E || opcode == 0x0F) { // ABR or ABW + printf(" words=%ld", block_words); + } + printf(" (raw=0x%016lX)\n", entry); + } +} + +bool validate_mops_trace(uint64_t dst, uint64_t count, uint64_t* mops_ptr, size_t mops_count) { + if (count == 0) { + if (mops_count != 0) { + printf("❌ FAIL: Expected 0 mops entries for count=0, got %ld\n", mops_count); + return false; + } + return true; + } + + uint64_t dst_aligned = dst & ALIGN_MASK; + uint64_t dst_offset = dst & 0x07; + uint64_t last_byte_addr = dst + count - 1; + uint64_t last_qword_aligned = last_byte_addr & ALIGN_MASK; + + // Calculate how many qwords are affected + uint64_t qwords_affected = ((count + dst_offset + 7) >> 3); + + // Determine if we need pre and post reads + bool needs_pre_read = (dst_offset != 0); + bool needs_post_read = ((dst_offset + count) & 0x07) != 0 && (last_qword_aligned > dst_aligned || dst_offset == 0); + + // Expected number of mops entries + size_t expected_entries = 0; + if (needs_pre_read) expected_entries++; + if (needs_post_read) expected_entries++; + expected_entries++; // Always have block write for count > 0 + + if (mops_count != expected_entries) { + printf("❌ FAIL: Expected %ld mops entries, got %ld\n", expected_entries, mops_count); + printf(" dst=0x%lX, count=%ld, dst_offset=%ld\n", dst, count, dst_offset); + printf(" needs_pre_read=%d, needs_post_read=%d\n", needs_pre_read, needs_post_read); + print_mops_trace(mops_ptr, mops_count); + return false; + } + + size_t mops_idx = 0; + + // Verify pre-read if expected + if (needs_pre_read) { + uint64_t expected = MOPS_ALIGNED_READ + dst_aligned; + if (mops_ptr[mops_idx] != expected) { + printf("❌ FAIL: PRE-READ mops[%ld]: expected 0x%016lX, got 0x%016lX\n", + mops_idx, expected, mops_ptr[mops_idx]); + return false; + } + mops_idx++; + } + + // Verify post-read if expected + if (needs_post_read) { + uint64_t expected = MOPS_ALIGNED_READ + last_qword_aligned; + if (mops_ptr[mops_idx] != expected) { + printf("❌ FAIL: POST-READ mops[%ld]: expected 0x%016lX, got 0x%016lX\n", + mops_idx, expected, mops_ptr[mops_idx]); + return false; + } + mops_idx++; + } + + // Verify block write + uint64_t expected_block_write = MOPS_ALIGNED_BLOCK_WRITE + + (qwords_affected << MOPS_BLOCK_WORDS_SBITS) + + dst_aligned; + if (mops_ptr[mops_idx] != expected_block_write) { + printf("❌ FAIL: BLOCK-WRITE mops[%ld]: expected 0x%016lX, got 0x%016lX\n", + mops_idx, expected_block_write, mops_ptr[mops_idx]); + printf(" qwords_affected=%ld, dst_aligned=0x%lX\n", qwords_affected, dst_aligned); + return false; + } + + return true; +} + +void init_fcall_result_data(uint64_t pattern_start) { + // Initialize FCALL_RESULT with test pattern + for (size_t i = 0; i < FCALL_RESULT_LENGTH; ++i) { + uint64_t value = 0; + for (int b = 0; b < 8; ++b) { + value |= ((uint64_t)(pattern_start + i * 8 + b)) << (b * 8); + } + fcall_ctx[FCALL_RESULT + i] = value; + } + + // Initialize FCALL_RESULT_GOT to 1 (next read from FCALL_RESULT[0]) + fcall_ctx[FCALL_RESULT_GOT] = 1; +} + +bool test_inputcpy_single(uint64_t dst_offset, size_t count) { + if (count % 8 != 0) { + std::cout << "❌ ERROR: count must be multiple of 8, got " << count << "\n"; + return false; + } + + // Initialize fcall_ctx with test data (pattern starting at 0x10) + init_fcall_result_data(0x10); + + // Save initial FCALL_RESULT_GOT + uint64_t initial_result_got = fcall_ctx[FCALL_RESULT_GOT]; + + // Allocate destination buffer + AlignedBuffer dst_buf(2048); + dst_buf.fill_pattern(0xA0); // Fill with different pattern + + // Calculate destination address with offset + uint64_t dst_addr = reinterpret_cast(dst_buf.byte_ptr() + 64) + dst_offset; + + // Allocate MOPS trace buffer + AlignedBuffer mops_buf(256); + mops_buf.fill_value(0); + uint64_t* mops_ptr = mops_buf.aligned_ptr(); + + // Call assembly function + uint64_t mops_count = dma_inputcpy_mops(dst_addr, count, mops_ptr); + + // Verify FCALL_RESULT_GOT was updated correctly + uint64_t expected_result_got = initial_result_got + (count / 8); + uint64_t actual_result_got = fcall_ctx[FCALL_RESULT_GOT]; + + if (actual_result_got != expected_result_got) { + std::cout << "❌ FAIL: FCALL_RESULT_GOT not updated correctly\n"; + std::cout << " dst_offset=" << dst_offset << " count=" << count << "\n"; std::cout << " Expected FCALL_RESULT_GOT=" << expected_result_got + << " Got=" << actual_result_got << "\n"; + return false; + } + + // Verify copied data matches FCALL_RESULT + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + bool data_ok = true; + + for (size_t i = 0; i < count; ++i) { + // Calculate which qword and byte within that qword + size_t qword_idx = i / 8; + size_t byte_in_qword = i % 8; + + // Get expected byte from FCALL_RESULT (accounting for initial_result_got - 1) + uint64_t source_qword = fcall_ctx[FCALL_RESULT + (initial_result_got - 1) + qword_idx]; + uint8_t expected = (source_qword >> (byte_in_qword * 8)) & 0xFF; + + if (dst_bytes[i] != expected) { + std::cout << "❌ FAIL: Data mismatch at byte " << i << "\n"; + std::cout << " dst_offset=" << dst_offset << " count=" << count << "\n"; + std::cout << " Expected=0x" << std::hex << (int)expected + << " Got=0x" << (int)dst_bytes[i] << std::dec << "\n"; + data_ok = false; + break; + } + } + + if (!data_ok) { + std::cout << "\nFirst 32 bytes of destination:\n "; + for (size_t i = 0; i < std::min(count, size_t(32)); ++i) { + if (i > 0 && i % 16 == 0) std::cout << "\n "; + std::cout << std::hex << std::setw(2) << std::setfill('0') + << (int)dst_bytes[i] << " "; + } + std::cout << std::dec << "\n"; + + std::cout << "\nExpected (from FCALL_RESULT):\n "; + for (size_t i = 0; i < std::min(count, size_t(32)); ++i) { + if (i > 0 && i % 16 == 0) std::cout << "\n "; + size_t qword_idx = i / 8; + size_t byte_in_qword = i % 8; + uint64_t source_qword = fcall_ctx[FCALL_RESULT + (initial_result_got - 1) + qword_idx]; + uint8_t expected = (source_qword >> (byte_in_qword * 8)) & 0xFF; + std::cout << std::hex << std::setw(2) << std::setfill('0') + << (int)expected << " "; + } + std::cout << std::dec << "\n"; + + return false; + } + + // Validate MOPS trace + if (!validate_mops_trace(dst_addr, count, mops_ptr, mops_count)) { + std::cout << "❌ FAIL (MOPS): dst=0x" << std::hex << dst_addr << std::dec + << " dst_offset=" << dst_offset << ", count=" << count << "\n"; + return false; + } + + return true; +} + +void print_progress(int current, int total) { + if (current % 100 == 0 || current == total) { + std::cout << "Progress: " << current << "/" << total + << " (" << (current * 100 / total) << "%)\r" << std::flush; + } +} + +int main() { + std::cout << "==============================================\n"; + std::cout << " Testing dma_inputcpy_mops implementation\n"; + std::cout << "==============================================\n\n"; + + std::cout << "FCALL_CTX structure:\n"; + std::cout << " - Total length: " << FCALL_CTX_LENGTH << " qwords (" + << (FCALL_CTX_LENGTH * 8) << " bytes)\n"; + std::cout << " - FCALL_RESULT offset: " << FCALL_RESULT << " (data input)\n"; + std::cout << " - FCALL_RESULT_GOT offset: " << FCALL_RESULT_GOT << " (read counter)\n\n"; + + int total_tests = 0; + int passed_tests = 0; + int failed_tests = 0; + + // Test parameters + const int max_count = 1024; + const int count_step = 8; // Must be multiple of 8 + const int max_offset = 7; // Test offsets 0-7 + + std::cout << "Test configuration:\n"; + std::cout << " - Destination offsets: 0-" << max_offset << "\n"; + std::cout << " - Byte counts: 0-" << max_count << " (step=" << count_step << ", multiples of 8 only)\n"; + std::cout << " - Total tests: " << ((max_offset + 1) * ((max_count / count_step) + 1)) << "\n\n"; + + std::cout << "Running tests...\n"; + + // Test all combinations of dst_offset and count (must be multiple of 8) + for (int dst_off = 0; dst_off <= max_offset; ++dst_off) { + for (int count = 0; count <= max_count; count += count_step) { + total_tests++; + print_progress(total_tests, ((max_offset + 1) * ((max_count / count_step) + 1))); + + if (test_inputcpy_single(dst_off, count)) { + passed_tests++; + } else { + failed_tests++; + std::cout << "\n❌ FAILED: dst_offset=" << dst_off + << ", count=" << count << "\n"; + + // Stop on first failure for debugging + if (failed_tests >= 1) { + std::cout << "\nStopping on first failure for debugging.\n"; + std::cout << "You can debug with: gdb ./test_dma_inputcpy\n"; + goto done; + } + } + } + } + +done: + std::cout << "\n\n==============================================\n"; + std::cout << " Test Results\n"; + std::cout << "==============================================\n"; + std::cout << "Total tests: " << total_tests << "\n"; + std::cout << "Passed: " << passed_tests << " ✅\n"; + std::cout << "Failed: " << failed_tests << " ❌\n"; + + if (failed_tests == 0) { + std::cout << "\n🎉 ALL TESTS PASSED! 🎉\n"; + return 0; + } else { + std::cout << "\n❌ SOME TESTS FAILED ❌\n"; + return 1; + } +} diff --git a/emulator-asm/src/dma/test_dma_memset_mops b/emulator-asm/src/dma/test_dma_memset_mops new file mode 100755 index 0000000000000000000000000000000000000000..0641c401d6ebadaef32a34f04cf251db87411da8 GIT binary patch literal 84816 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 +#include +#include +#include +#include +#include + +// External assembly function declarations +extern "C" { + uint64_t trace_address_threshold = 0; + void fast_memset(uint64_t dst, uint8_t value, uint64_t count); + uint64_t dma_xmemset_mops(uint64_t dst, uint8_t value, uint64_t count, uint64_t* mops_ptr); +} + +const char *mops_labels[16] = {"NOP", "CWR1", "RD1", "WR1", "RD2", "WR2", "RD4", "WR4", "RD8", "WR8", + "ARD", "AWR", "BR", "BW", "ABR", "ABW"}; + +// MOPS constants from dma_constants.inc +const uint64_t MOPS_ALIGNED_READ = 0x0000'000C'0000'0000ULL; +const uint64_t MOPS_ALIGNED_BLOCK_WRITE = 0x0000'000F'0000'0000ULL; +const uint64_t MOPS_BLOCK_WORDS_SBITS = 36; +const uint64_t ALIGN_MASK = 0xFFFF'FFFF'FFFF'FFF8ULL; + +// Helper class to manage aligned test buffers +class AlignedBuffer { +public: + std::vector data; + + AlignedBuffer(size_t size) : data(size, 0) {} + + uint64_t* aligned_ptr() { + return reinterpret_cast(data.data()); + } + + uint8_t* byte_ptr() { + return data.data(); + } + + void fill_pattern(uint8_t start = 0) { + for (size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast(start + i); + } + } + + void fill_value(uint8_t value) { + std::fill(data.begin(), data.end(), value); + } + + bool verify_pattern(uint8_t start = 0, const char *title = "") { + for (size_t i = 0; i < data.size(); ++i) { + uint8_t expected = static_cast(start + i); + if (data[i] != expected) { + printf("❌ FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", + title, expected, i, data[i]); + return false; + } + } + return true; + } + + bool verify_pattern_except(uint8_t start, size_t from, size_t count, const char *title = "") { + size_t to = from + count; + for (size_t i = 0; i < data.size(); ++i) { + if (i >= from && i < to) continue; + uint8_t expected = static_cast(start + i); + if (data[i] != expected) { + printf("❌ FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X (from=%ld, count=%ld)\n", + title, expected, i, data[i], from, count); + return false; + } + } + return true; + } + + bool verify_fill(uint8_t value, size_t from, size_t count, const char *title = "") { + size_t to = from + count; + for (size_t i = from; i < to; ++i) { + if (data[i] != value) { + printf("❌ FAIL FILL VERIFICATION of %s at [%ld]: Expected: 0x%02X vs 0x%02X (from=%ld, count=%ld)\n", + title, i, value, data[i], from, count); + return false; + } + } + return true; + } + + bool verify_fill_except(uint8_t value, size_t from, size_t count, const char *title = "") { + size_t to = from + count; + for (size_t i = 0; i < data.size(); ++i) { + if (i >= from && i < to) continue; + if (data[i] != value) { + printf("❌ FAIL FILL VERIFICATION of %s at [%ld]: Expected: 0x%02X vs 0x%02X (should be outside from=%ld, count=%ld)\n", + title, i, value, data[i], from, count); + return false; + } + } + return true; + } +}; + +void print_mops_trace(uint64_t* mops_ptr, size_t mops_count) { + std::cout << "MOPS Trace (" << mops_count << " entries):\n"; + for (size_t i = 0; i < mops_count; ++i) { + uint64_t entry = mops_ptr[i]; + uint64_t opcode = (entry >> 32) & 0x0F; + uint64_t addr = entry & 0xFFFF'FFFF; + uint64_t block_words = entry >> MOPS_BLOCK_WORDS_SBITS; + + printf(" [%ld] %s (0x%X) addr=0x%08lX", i, mops_labels[opcode], (unsigned)opcode, addr); + if (opcode == 0x0E || opcode == 0x0F) { // ABR or ABW + printf(" words=%ld", block_words); + } + printf(" (raw=0x%016lX)\n", entry); + } +} + +bool validate_mops_trace(uint64_t dst, uint64_t count, uint64_t* mops_ptr, size_t mops_count) { + if (count == 0) { + if (mops_count != 0) { + printf("❌ FAIL: Expected 0 mops entries for count=0, got %ld\n", mops_count); + return false; + } + return true; + } + + uint64_t dst_aligned = dst & ALIGN_MASK; + uint64_t dst_offset = dst & 0x07; + uint64_t last_byte_addr = dst + count - 1; + uint64_t last_qword_aligned = last_byte_addr & ALIGN_MASK; + + // Calculate how many qwords are affected + uint64_t qwords_affected = ((count + dst_offset + 7) >> 3); + + // Determine if we need pre and post reads + bool needs_pre_read = (dst_offset != 0); + bool needs_post_read = ((dst_offset + count) & 0x07) != 0 && (last_qword_aligned > dst_aligned || dst_offset == 0); + + // Expected number of mops entries + size_t expected_entries = 0; + if (needs_pre_read) expected_entries++; + if (needs_post_read) expected_entries++; + expected_entries++; // Always have block write for count > 0 + + if (mops_count != expected_entries) { + printf("❌ FAIL: Expected %ld mops entries, got %ld\n", expected_entries, mops_count); + printf(" dst=0x%lX, count=%ld, dst_offset=%ld\n", dst, count, dst_offset); + printf(" needs_pre_read=%d, needs_post_read=%d\n", needs_pre_read, needs_post_read); + print_mops_trace(mops_ptr, mops_count); + return false; + } + + size_t mops_idx = 0; + + // Verify pre-read if expected + if (needs_pre_read) { + uint64_t expected = MOPS_ALIGNED_READ + dst_aligned; + if (mops_ptr[mops_idx] != expected) { + printf("❌ FAIL: PRE-READ mops[%ld]: expected 0x%016lX, got 0x%016lX\n", + mops_idx, expected, mops_ptr[mops_idx]); + return false; + } else + mops_idx++; + } + + // Verify post-read if expected + if (needs_post_read) { + uint64_t expected = MOPS_ALIGNED_READ + last_qword_aligned; + if (mops_ptr[mops_idx] != expected) { + printf("❌ FAIL: POST-READ mops[%ld]: expected 0x%016lX, got 0x%016lX\n", + mops_idx, expected, mops_ptr[mops_idx]); + return false; + } + mops_idx++; + } + + // Verify block write + uint64_t expected_block_write = MOPS_ALIGNED_BLOCK_WRITE + + (qwords_affected << MOPS_BLOCK_WORDS_SBITS) + + dst_aligned; + if (mops_ptr[mops_idx] != expected_block_write) { + printf("❌ FAIL: BLOCK-WRITE mops[%ld]: expected 0x%016lX, got 0x%016lX\n", + mops_idx, expected_block_write, mops_ptr[mops_idx]); + printf(" qwords_affected=%ld, dst_aligned=0x%lX\n", qwords_affected, dst_aligned); + return false; + } + + return true; +} + +bool test_fast_memset_single(size_t dst_offset, size_t count, uint8_t value) { + constexpr size_t BUFFER_SIZE = 2048; + constexpr size_t TEST_AREA_START = 512; + constexpr uint8_t PATTERN_START = 0x37; + + // Allocate buffer and fill with pattern to detect overwrites + AlignedBuffer buffer(BUFFER_SIZE); + buffer.fill_pattern(PATTERN_START); + + // Calculate destination address + uint64_t dst = reinterpret_cast(buffer.byte_ptr() + TEST_AREA_START) + dst_offset; + + // Call the function + fast_memset(dst, value, count); + + // Verify that the memset was performed correctly + if (!buffer.verify_fill(value, TEST_AREA_START + dst_offset, count, "memset result")) { + printf("❌ TEST FAILED: dst_offset=%ld, count=%ld, value=0x%02X\n", + dst_offset, count, value); + return false; + } + + // Verify that nothing outside the target area was modified + if (!buffer.verify_pattern_except(PATTERN_START, TEST_AREA_START + dst_offset, count, + "buffer overflow detection")) { + printf("❌ TEST FAILED (OVERFLOW): dst_offset=%ld, count=%ld, value=0x%02X\n", + dst_offset, count, value); + return false; + } + return true; +} + +bool test_memset_mops_single(size_t dst_offset, size_t count, uint8_t value) { + constexpr size_t BUFFER_SIZE = 2048; + constexpr size_t TEST_AREA_START = 512; + constexpr uint8_t PATTERN_START = 0x37; + + // Allocate buffer and fill with pattern to detect overwrites + AlignedBuffer buffer(BUFFER_SIZE); + buffer.fill_pattern(PATTERN_START); + + // Allocate mops trace buffer (16 u64 entries as specified) + AlignedBuffer mops_buffer(16 * sizeof(uint64_t)); + mops_buffer.fill_value(0); + + // Calculate destination address + uint64_t dst = reinterpret_cast(buffer.byte_ptr() + TEST_AREA_START) + dst_offset; + uint64_t* mops_ptr = mops_buffer.aligned_ptr(); + + // Call the function + uint64_t mops_count = dma_xmemset_mops(dst, value, count, mops_ptr); + + // Verify that the memset was performed correctly + if (!buffer.verify_fill(value, TEST_AREA_START + dst_offset, count, "memset result")) { + printf("❌ TEST FAILED: dst_offset=%ld, count=%ld, value=0x%02X\n", + dst_offset, count, value); + return false; + } + + // Verify that nothing outside the target area was modified + if (!buffer.verify_pattern_except(PATTERN_START, TEST_AREA_START + dst_offset, count, + "buffer overflow detection")) { + printf("❌ TEST FAILED (OVERFLOW): dst_offset=%ld, count=%ld, value=0x%02X\n", + dst_offset, count, value); + return false; + } + + // Validate mops trace + if (!validate_mops_trace(dst, count, mops_ptr, mops_count)) { + printf("❌ TEST FAILED (MOPS): dst=0x%08lX dst_offset=%ld, count=%ld\n", + dst, dst_offset, count); + return false; + } + + return true; +} + +void test_all_combinations() { + constexpr uint8_t TEST_VALUE = 0xAB; + size_t total_tests = 0; + size_t passed_tests = 0; + size_t failed_tests = 0; + + std::cout << "Starting comprehensive memset_mops tests...\n"; + std::cout << "Testing dst_offsets 0-7 × lengths 0-1024\n"; + std::cout << "Total tests: " << (8 * 1025) << "\n\n"; + + for (size_t dst_offset = 0; dst_offset <= 7; ++dst_offset) { + std::cout << "Testing dst_offset=" << dst_offset << "...\n"; + + for (size_t count = 0; count <= 1024; ++count) { + total_tests += 2; + + if (test_fast_memset_single(dst_offset, count, TEST_VALUE)) { + passed_tests++; + } else { + failed_tests++; + std::cout << "❌ FAILED: memset offset=" << dst_offset << " count=" << count << "\n"; + // Don't stop on first failure, continue testing + } + if (test_memset_mops_single(dst_offset, count, TEST_VALUE)) { + passed_tests++; + } else { + failed_tests++; + std::cout << "❌ FAILED: memset_ops offset=" << dst_offset << " count=" << count << "\n"; + // Don't stop on first failure, continue testing + } + // Progress indicator every 128 tests + if (count % 128 == 0 && count > 0) { + std::cout << " ... progress: " << count << "/1024" << std::endl; + } + } + } + + std::cout << "\n========================================\n"; + std::cout << "TEST SUMMARY\n"; + std::cout << "========================================\n"; + std::cout << "Total tests: " << total_tests << "\n"; + std::cout << "Passed: " << passed_tests << " ✅\n"; + std::cout << "Failed: " << failed_tests << " ❌\n"; + std::cout << "Success rate: " << (100.0 * passed_tests / total_tests) << "%\n"; + std::cout << "========================================\n"; + + if (failed_tests == 0) { + std::cout << "\n🎉 ALL TESTS PASSED! 🎉\n"; + } else { + std::cout << "\n⚠️ SOME TESTS FAILED ⚠️\n"; + exit(1); + } +} + +int main() { + std::cout << "==============================================\n"; + std::cout << " DMA MEMSET MOPS COMPREHENSIVE TEST SUITE\n"; + std::cout << "==============================================\n\n"; + + test_all_combinations(); + + return 0; +} diff --git a/emulator-asm/src/dma/test_dma_memset_mtrace b/emulator-asm/src/dma/test_dma_memset_mtrace new file mode 100755 index 0000000000000000000000000000000000000000..db4b56896ea77bd75e78797bae45f3f17dad889c GIT binary patch literal 110824 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

D$dLIopR4$Ugz!bIX%ptzXnFV`A?V@SG+2SW{ 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 +#include +#include +#include +#include +#include + +// External assembly function declarations +extern "C" { + uint64_t trace_address_threshold = 0; + void fast_memset(uint64_t dst, uint8_t value, uint64_t count); + uint64_t dma_xmemset_mtrace(uint64_t dst, uint8_t value, uint64_t count, uint64_t* mtrace_ptr); + uint64_t fast_dma_encode_memset_with_byte(uint64_t dst, uint8_t value, uint64_t count); +} + +const uint64_t ALIGN_MASK = 0xFFFF'FFFF'FFFF'FFF8ULL; + +// Helper class to manage aligned test buffers +class AlignedBuffer { +public: + std::vector data; + + AlignedBuffer(size_t size) : data(size, 0) {} + + uint64_t* aligned_ptr() { + return reinterpret_cast(data.data()); + } + + uint8_t* byte_ptr() { + return data.data(); + } + + const uint8_t* byte_ptr() const { + return data.data(); + } + + void fill_pattern(uint8_t start = 0) { + for (size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast(start + i); + } + } + + void fill_value(uint8_t value) { + std::fill(data.begin(), data.end(), value); + } + + bool verify_pattern(uint8_t start = 0, const char *title = "") { + for (size_t i = 0; i < data.size(); ++i) { + uint8_t expected = static_cast(start + i); + if (data[i] != expected) { + printf("❌ FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", + title, expected, i, data[i]); + return false; + } + } + return true; + } + + bool verify_pattern_except(uint8_t start, size_t from, size_t count, const char *title = "") { + size_t to = from + count; + for (size_t i = 0; i < data.size(); ++i) { + if (i >= from && i < to) continue; + uint8_t expected = static_cast(start + i); + if (data[i] != expected) { + printf("❌ FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X (from=%ld, count=%ld)\n", + title, expected, i, data[i], from, count); + return false; + } + } + return true; + } + + bool verify_fill(uint8_t value, size_t from, size_t count, const char *title = "") { + size_t to = from + count; + for (size_t i = from; i < to; ++i) { + if (data[i] != value) { + printf("❌ FAIL FILL VERIFICATION of %s at [%ld]: Expected: 0x%02X vs 0x%02X (from=%ld, count=%ld)\n", + title, i, value, data[i], from, count); + return false; + } + } + return true; + } + + bool verify_fill_except(uint8_t value, size_t from, size_t count, const char *title = "") { + size_t to = from + count; + for (size_t i = 0; i < data.size(); ++i) { + if (i >= from && i < to) continue; + if (data[i] != value) { + printf("❌ FAIL FILL VERIFICATION of %s at [%ld]: Expected: 0x%02X vs 0x%02X (should be outside from=%ld, count=%ld)\n", + title, i, value, data[i], from, count); + return false; + } + } + return true; + } +}; + +void print_mtrace(uint64_t* mtrace_ptr, size_t mtrace_count) { + std::cout << "MTRACE (" << mtrace_count << " entries):\n"; + for (size_t i = 0; i < mtrace_count; ++i) { + uint64_t entry = mtrace_ptr[i]; + printf(" [%ld] 0x%016lX\n", i, entry); + } +} + +bool validate_mtrace(uint64_t dst, uint64_t count, uint8_t value, + uint64_t* mtrace_ptr, size_t mtrace_count, + const AlignedBuffer& buffer, size_t test_area_start, size_t dst_offset) { + + // Expected encode value from fast_dma_encode_memset_with_byte + uint64_t expected_encode = fast_dma_encode_memset_with_byte(dst, value, count); + + if (mtrace_count == 0) { + printf("❌ FAIL: Expected at least 1 mtrace entry (encode), got 0\n"); + return false; + } + + // Verify encode (first entry) + if (mtrace_ptr[0] != expected_encode) { + printf("❌ FAIL: ENCODE mtrace[0]: expected 0x%016lX, got 0x%016lX\n", + expected_encode, mtrace_ptr[0]); + printf(" dst=0x%lX, count=%ld, value=0x%02X, dst_offset=%ld\n", + dst, count, value, dst_offset); + print_mtrace(mtrace_ptr, mtrace_count); + return false; + } + + uint64_t dst_aligned = dst & ALIGN_MASK; + + // For count=0, only encode is generated, no PRE/POST + if (count == 0) { + if (mtrace_count != 1) { + printf("❌ FAIL: Expected 1 mtrace entry for count=0, got %ld\n", mtrace_count); + print_mtrace(mtrace_ptr, mtrace_count); + return false; + } + return true; + } + + // Determine if we need PRE and POST reads + bool needs_pre = (dst_offset > 0); + bool needs_post = ((dst_offset + count) & 0x07) != 0 & (dst_offset == 0 || (dst_offset + count) > 8); + + size_t expected_entries = 1; // Always have encode + size_t mtrace_idx = 1; + + if (needs_pre) { + expected_entries++; + } + if (needs_post) { + expected_entries++; + } + + if (mtrace_count != expected_entries) { + printf("❌ FAIL: Expected %ld mtrace entries, got %ld\n", expected_entries, mtrace_count); + printf(" dst=0x%lX, count=%ld, value=0x%02X, dst_offset=%ld\n", + dst, count, value, dst_offset); + printf(" needs_pre=%d, needs_post=%d\n", needs_pre, needs_post); + print_mtrace(mtrace_ptr, mtrace_count); + return false; + } + + // Verify PRE entry if expected + if (needs_pre) { + // PRE contains the value read from the first aligned qword before modification + // We can't easily verify the exact value since it's read before memset + // Just verify the entry exists + if (mtrace_idx >= mtrace_count) { + printf("❌ FAIL: Expected PRE entry at mtrace[%ld], but only %ld entries\n", + mtrace_idx, mtrace_count); + return false; + } + mtrace_idx++; + } + + // Verify POST entry if expected + if (needs_post) { + if (mtrace_idx >= mtrace_count) { + printf("❌ FAIL: Expected POST entry at mtrace[%ld], but only %ld entries\n", + mtrace_idx, mtrace_count); + return false; + } + mtrace_idx++; + } + + return true; +} + +bool test_memset_mtrace_single(size_t dst_offset, size_t count, uint8_t value) { + constexpr size_t BUFFER_SIZE = 2048; + constexpr size_t TEST_AREA_START = 512; + constexpr uint8_t PATTERN_START = 0x37; + + // Allocate buffer and fill with pattern to detect overwrites + AlignedBuffer buffer(BUFFER_SIZE); + buffer.fill_pattern(PATTERN_START); + + // Allocate mtrace buffer (max 3 u64 entries: encode + PRE + POST) + AlignedBuffer mtrace_buffer(3 * sizeof(uint64_t)); + mtrace_buffer.fill_value(0); + + // Calculate destination address + uint64_t dst = reinterpret_cast(buffer.byte_ptr() + TEST_AREA_START) + dst_offset; + uint64_t* mtrace_ptr = mtrace_buffer.aligned_ptr(); + printf("TEST: dst_offset=%ld, count=%ld, value=0x%02X\n", dst_offset, count, value); + + // Call the function + uint64_t mtrace_count = dma_xmemset_mtrace(dst, value, count, mtrace_ptr); + + // Verify that the memset was performed correctly + if (!buffer.verify_fill(value, TEST_AREA_START + dst_offset, count, "memset result")) { + printf("❌ TEST FAILED: dst_offset=%ld, count=%ld, value=0x%02X\n", + dst_offset, count, value); + return false; + } + + // Verify that nothing outside the target area was modified + if (!buffer.verify_pattern_except(PATTERN_START, TEST_AREA_START + dst_offset, count, + "buffer overflow detection")) { + printf("❌ TEST FAILED (OVERFLOW): dst_offset=%ld, count=%ld, value=0x%02X\n", + dst_offset, count, value); + return false; + } + + // Validate mtrace + if (!validate_mtrace(dst, count, value, mtrace_ptr, mtrace_count, + buffer, TEST_AREA_START, dst_offset)) { + printf("❌ TEST FAILED (MTRACE): dst_offset=%ld, count=%ld, value=0x%02X\n", + dst_offset, count, value); + return false; + } + + return true; +} + +void print_progress(int current, int total) { + if (current % 100 == 0 || current == total) { + std::cout << "Progress: " << current << "/" << total + << " (" << (current * 100 / total) << "%)\r" << std::flush; + } +} + +int main() { + std::cout << "==============================================\n"; + std::cout << " DMA MEMSET MTRACE COMPREHENSIVE TEST SUITE\n"; + std::cout << "==============================================\n\n"; + + int total_tests = 0; + int passed_tests = 0; + int failed_tests = 0; + + // Test parameters + const int max_count = 1024; + const int max_offset = 7; + const int num_values = 16; // Test 16 different byte values instead of all 256 + + // Test specific values: 0x00, 0xFF, and some random values + const uint8_t test_values[] = {0x00, 0x01, 0x10, 0x37, 0x55, 0x7F, 0x80, 0xAA, + 0xAB, 0xC0, 0xCD, 0xEF, 0xF0, 0xFE, 0xFF, 0x42}; + + std::cout << "Test configuration:\n"; + std::cout << " - Destination offsets: 0-" << max_offset << "\n"; + std::cout << " - Byte counts: 0-" << max_count << "\n"; + std::cout << " - Test values: " << num_values << " different bytes\n"; + std::cout << " - Total tests: " << ((max_offset + 1) * (max_count + 1) * num_values) << "\n\n"; + + std::cout << "Running tests...\n"; + + // Test all combinations + for (size_t value_idx = 0; value_idx < num_values; ++value_idx) { + uint8_t value = test_values[value_idx]; + + for (size_t dst_offset = 0; dst_offset <= max_offset; ++dst_offset) { + for (size_t count = 0; count <= max_count; ++count) { + total_tests++; + print_progress(total_tests, (max_offset + 1) * (max_count + 1) * num_values); + + if (test_memset_mtrace_single(dst_offset, count, value)) { + passed_tests++; + } else { + failed_tests++; + std::cout << "\n❌ FAILED: dst_offset=" << dst_offset + << ", count=" << count + << ", value=0x" << std::hex << (int)value << std::dec << "\n"; + + // Stop on first failure for debugging + if (failed_tests >= 1) { + std::cout << "\nStopping on first failure for debugging.\n"; + std::cout << "You can debug with: gdb ./test_dma_memset_mtrace\n"; + goto done; + } + } + } + } + } + +done: + std::cout << "\n\n==============================================\n"; + std::cout << " Test Results\n"; + std::cout << "==============================================\n"; + std::cout << "Total tests: " << total_tests << "\n"; + std::cout << "Passed: " << passed_tests << " ✅\n"; + std::cout << "Failed: " << failed_tests << " ❌\n"; + + if (failed_tests == 0) { + std::cout << "\n🎉 ALL TESTS PASSED! 🎉\n"; + } else { + std::cout << "\n⚠️ SOME TESTS FAILED ⚠️\n"; + return 1; + } + + return 0; +} diff --git a/emulator-asm/src/dma/test_fast_memcpy b/emulator-asm/src/dma/test_fast_memcpy new file mode 100755 index 0000000000000000000000000000000000000000..fac30802be6206385cab28e851a6807eb1b30a70 GIT binary patch literal 75800 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 +#include +#include +#include +#include +#include + +// External assembly function declaration +extern "C" { + void fast_memcpy(uint64_t dst, uint64_t src, uint64_t count); +} + +// Helper class to manage aligned test buffers +class AlignedBuffer { +public: + std::vector data; + + AlignedBuffer(size_t size) : data(size, 0) {} + + uint64_t* aligned_ptr() { + return reinterpret_cast(data.data()); + } + + uint8_t* byte_ptr() { + return data.data(); + } + + void fill_pattern(uint8_t start = 0) { + for (size_t i = 0; i < data.size(); ++i) { + data[i] = static_cast(start + i); + } + } + + void fill_value(uint8_t value) { + std::fill(data.begin(), data.end(), value); + } +}; + +bool test_fast_memcpy_single(uint64_t dst_offset, uint64_t src_offset, size_t count) { + // Allocate buffers with extra space for offsets + AlignedBuffer src_buf(2048); + AlignedBuffer dst_buf(2048); + + // Fill source with pattern starting at 0x10 + src_buf.fill_pattern(0x10); + + // Fill destination with different pattern (0xA0) + dst_buf.fill_pattern(0xA0); + + // Calculate actual addresses with offsets + uint64_t src_addr = reinterpret_cast(src_buf.byte_ptr() + 64) + src_offset; + uint64_t dst_addr = reinterpret_cast(dst_buf.byte_ptr() + 64) + dst_offset; + + // Call assembly function + fast_memcpy(dst_addr, src_addr, count); + + // Verify the memcpy was performed correctly + const uint8_t* src_bytes = reinterpret_cast(src_addr); + const uint8_t* dst_bytes = reinterpret_cast(dst_addr); + + bool ok = true; + for (size_t i = 0; i < count; ++i) { + if (dst_bytes[i] != src_bytes[i]) { + std::cout << "❌ FAIL at byte " << i + << " dst_off=" << dst_offset + << " src_off=" << src_offset + << " count=" << count + << " expected=0x" << std::hex << (int)src_bytes[i] + << " got=0x" << (int)dst_bytes[i] << std::dec << "\n"; + ok = false; + break; + } + } + + if (!ok) { + // Print context around failure + std::cout << "Source bytes around copy region:\n"; + for (size_t i = 0; i < std::min(count, size_t(32)); ++i) { + if (i % 16 == 0) std::cout << " "; + std::cout << std::hex << std::setw(2) << std::setfill('0') + << (int)src_bytes[i] << " "; + if (i % 16 == 15) std::cout << "\n"; + } + std::cout << "\nDestination bytes around copy region:\n"; + for (size_t i = 0; i < std::min(count, size_t(32)); ++i) { + if (i % 16 == 0) std::cout << " "; + std::cout << std::hex << std::setw(2) << std::setfill('0') + << (int)dst_bytes[i] << " "; + if (i % 16 == 15) std::cout << "\n"; + } + std::cout << std::dec << "\n"; + + // Also verify that areas outside the copy region weren't touched + const uint8_t* dst_base = dst_buf.byte_ptr() + 64; + size_t offset_in_buf = dst_bytes - dst_base; + + // Check before region + for (size_t i = 0; i < offset_in_buf; ++i) { + uint8_t expected = static_cast(0xA0 + i); + if (dst_base[i] != expected) { + std::cout << "❌ Buffer corruption BEFORE copy region at " << i << "\n"; + break; + } + } + + // Check after region + for (size_t i = offset_in_buf + count; i < 128; ++i) { + uint8_t expected = static_cast(0xA0 + i); + if (dst_base[i] != expected) { + std::cout << "❌ Buffer corruption AFTER copy region at " << i << "\n"; + break; + } + } + } + + return ok; +} + +void print_progress(int current, int total) { + if (current % 100 == 0 || current == total) { + std::cout << "Progress: " << current << "/" << total + << " (" << (current * 100 / total) << "%)\r" << std::flush; + } +} + +int main() { + std::cout << "==============================================\n"; + std::cout << " Testing fast_memcpy assembly implementation\n"; + std::cout << " (Duff's device based implementation)\n"; + std::cout << "==============================================\n\n"; + + int total_tests = 0; + int passed_tests = 0; + int failed_tests = 0; + + // Test parameters + const int max_count = 1024; + const int count_step = 1; // Test every byte count + const int max_offset = 7; // Test offsets 0-7 + + std::cout << "Test configuration:\n"; + std::cout << " - Destination offsets: 0-" << max_offset << "\n"; + std::cout << " - Source offsets: 0-" << max_offset << "\n"; + std::cout << " - Byte counts: 0-" << max_count << " (step=" << count_step << ")\n"; + std::cout << " - Total tests: " << ((max_offset + 1) * (max_offset + 1) * ((max_count / count_step) + 1)) << "\n\n"; + + std::cout << "Running tests...\n"; + + // Test all combinations of dst_offset, src_offset, and count + for (int dst_off = 0; dst_off <= max_offset; ++dst_off) { + for (int src_off = 0; src_off <= max_offset; ++src_off) { + for (int count = 0; count <= max_count; count += count_step) { + total_tests++; + print_progress(total_tests, ((max_offset + 1) * (max_offset + 1) * ((max_count / count_step) + 1))); + std::cout << "\nTEST: dst_offset=" << dst_off + << ", src_offset=" << src_off + << ", count=" << count << "\n"; + if (test_fast_memcpy_single(dst_off, src_off, count)) { + passed_tests++; + } else { + failed_tests++; + std::cout << "\n❌ FAILED: dst_offset=" << dst_off + << ", src_offset=" << src_off + << ", count=" << count << "\n"; + + // Stop on first failure for debugging + if (failed_tests >= 1) { + std::cout << "\nStopping on first failure for debugging.\n"; + std::cout << "You can debug with: gdb ./test_fast_memcpy\n"; + std::cout << " Break at: break test_fast_memcpy_single\n"; + goto done; + } + } + } + } + } + +done: + std::cout << "\n\n==============================================\n"; + std::cout << " Test Results\n"; + std::cout << "==============================================\n"; + std::cout << "Total tests: " << total_tests << "\n"; + std::cout << "Passed: " << passed_tests << " ✅\n"; + std::cout << "Failed: " << failed_tests << " ❌\n"; + + if (failed_tests == 0) { + std::cout << "\n🎉 ALL TESTS PASSED! 🎉\n"; + return 0; + } else { + std::cout << "\n❌ SOME TESTS FAILED ❌\n"; + return 1; + } +} diff --git a/precompiles/helpers/src/dma.rs b/precompiles/helpers/src/dma.rs index 957d6a50a..0d0ff33f7 100644 --- a/precompiles/helpers/src/dma.rs +++ b/precompiles/helpers/src/dma.rs @@ -15,9 +15,29 @@ pub struct DmaValues { pub src_offset_after_pre: u64, } +// #bits bits +// pre_count: 0-7 3 0-2 +// post_count: 0-8(*) 4 3-6 (*) memcmp +// pre_writes: 0,1,2 2 7-8 +// dst_offset: 0-7 3 9-11 +// src_offset: 0-7 3 12-14 +// double_src_pre: 0,1 1 15 +// double_src_post: 0,1 1 16 +// extra_src_reads: 0-3 2 17-18 +// src64_inc_by_pre: 1 19 +// unaligned_dst_src: 1 20 +// fill_byte/cmp: 8 21-28 +// cmp_negative: 1 29 +// requires_dma: 1 30 +// (reserved) 1 31 +// lpre_count 3 32-34 +// loop_count 29 35-63 + const FAST_ENCODE_TABLE_WO_NEQ_SIZE: usize = 8 * 8 * 16; const FAST_ENCODE_TABLE_SIZE: usize = FAST_ENCODE_TABLE_WO_NEQ_SIZE * 2; +const FAST_ENCODE_NO_SRC_TABLE_SIZE: usize = 8 * 16; const FAST_ENCODE_TABLE: [u64; FAST_ENCODE_TABLE_SIZE] = generate_fast_encode_table(); +const FAST_ENCODE_NO_SRC_TABLE: [u64; FAST_ENCODE_NO_SRC_TABLE_SIZE] = generate_fast_encode_no_src_table(); const fn generate_fast_encode_table() -> [u64; FAST_ENCODE_TABLE_SIZE] { let mut table = [0u64; FAST_ENCODE_TABLE_SIZE]; @@ -34,7 +54,7 @@ const fn generate_fast_encode_table() -> [u64; FAST_ENCODE_TABLE_SIZE] { let index = (base_index + (src_offset << 4)) as usize; let mut count: usize = 0; while count < 16 { - let value = DmaInfo::calculate_encode(dst_offset, src_offset, count, neq); + let value = DmaInfo::calculate_encode(dst_offset, src_offset, count, neq, true); let loop_count = DmaInfo::get_loop_count(value) as u64; // The table is create to add directly de loop count and after all values // are correct, for this reason substract de count, because we need diference @@ -53,6 +73,29 @@ const fn generate_fast_encode_table() -> [u64; FAST_ENCODE_TABLE_SIZE] { table } +const fn generate_fast_encode_no_src_table() -> [u64; FAST_ENCODE_NO_SRC_TABLE_SIZE] { + let mut table = [0u64; FAST_ENCODE_NO_SRC_TABLE_SIZE]; + // fill table + let mut dst_offset: u64 = 0; + while dst_offset < 8 { + let index = (dst_offset << 4) as usize; + let mut count: usize = 0; + while count < 16 { + let value = DmaInfo::calculate_encode_no_src(dst_offset, count); + let loop_count = DmaInfo::get_loop_count(value) as u64; + // The table is create to add directly de loop count and after all values + // are correct, for this reason substract de count, because we need diference + // between loop_count (shifted 32) and count (shifted 29) + table[index + count] = ((value & 0x0000_0007_FFFF_FFFF) + + (loop_count << DmaInfo::DMA_LOOP_COUNT_RS)) + .wrapping_sub((count as u64) << DmaInfo::DMA_LPRE_COUNT_RS); + count += 1; + } + dst_offset += 1; + } + table +} + pub struct DmaInfo {} impl DmaInfo { @@ -103,14 +146,14 @@ impl DmaInfo { #[inline(always)] pub const fn encode_inputcpy(dst: u64, count: usize) -> u64 { let table_count = if count >= 16 { count & 0x07 | 0x08 } else { count }; - FAST_ENCODE_TABLE[((dst & 0x07) << 7) as usize + table_count] + FAST_ENCODE_NO_SRC_TABLE[((dst & 0x07) << 4) as usize + table_count] .wrapping_add((count as u64) << Self::DMA_LPRE_COUNT_RS) } #[inline(always)] pub const fn encode_memset(dst: u64, count: usize, fill_byte: u8) -> u64 { let table_count = if count >= 16 { count & 0x07 | 0x08 } else { count }; - (FAST_ENCODE_TABLE[((dst & 0x07) << 7) as usize + table_count] + (FAST_ENCODE_NO_SRC_TABLE[((dst & 0x07) << 4) as usize + table_count] .wrapping_add((count as u64) << Self::DMA_LPRE_COUNT_RS)) | ((fill_byte as u64) << Self::DMA_FILL_BYTE_RS) } @@ -154,6 +197,8 @@ impl DmaInfo { pub const DMA_FILL_BYTE_TEST_MASK: u64 = 0x1FE00000; pub const DMA_FILL_BYTE_MASK: u64 = 0x000000FF; + pub const DMA_FILL_BITS9_MASK: u64 = 0x000001FF; + pub const DMA_FILL_BYTE_SIGN_TEST_MASK: u64 = 0x20000000; pub const DMA_LPRE_COUNT_RS: u64 = 32; @@ -186,34 +231,7 @@ impl DmaInfo { | Self::DMA_REQUIRES_DMA_TEST_MASK; #[inline(always)] - pub const fn calculate_encode(dst: u64, src: u64, count: usize, neq: bool) -> u64 { - // #bits bits - // pre_count: 0-7 3 0-2 - // post_count: 0-8(*) 4 3-6 (*) memcmp - // pre_writes: 0,1,2 2 7-8 - // dst_offset: 0-7 3 9-11 - // src_offset: 0-7 3 12-14 - // double_src_pre: 0,1 1 15 - // double_src_post: 0,1 1 16 - // extra_src_reads: 0-3 2 17-18 - // src64_inc_by_pre: 1 19 - // unaligned_dst_src: 1 20 - // fill_byte/cmp: 8 21-28 - // cmp_negative: 1 29 - // requires_dma: 1 30 - // (reserved) 1 31 - // lpre_count 3 32-34 - // loop_count 29 35-63 - - // (dst + count) & 0x7 = 7 - // (dst_offset + count) 0x07 = 7 - // - // loop = loop - 1 - // pre_writes = pre_writes + 1 - // post = 8 - // double_src_post = unaligned_dst_src ? 1:0; - // extra_src_reads = extra_src_read + 1 - + pub const fn calculate_encode(dst: u64, src: u64, count: usize, neq: bool, has_src: bool) -> u64 { let dst_offset = dst & 0x07; let src_offset = src & 0x07; @@ -247,12 +265,14 @@ impl DmaInfo { // post = 8 // double_src_post = unaligned_dst_src ? 1:0; // extra_src_reads = extra_src_read + 1 - loop_count = loop_count - 1; - pre_writes = pre_writes + 1; + loop_count -= 1; + pre_writes += 1; post_count = 8; double_src_post = src_offset != dst_offset; - extra_src_reads = extra_src_reads + 1; + extra_src_reads += 1; } + let requires_dma = count == 0 || pre_count != 0 || post_count != 0; + if has_src { pre_count | (post_count << Self::DMA_POST_COUNT_RS) | (pre_writes << Self::DMA_PRE_WRITES_RS) @@ -263,44 +283,26 @@ impl DmaInfo { | (extra_src_reads << Self::DMA_EXTRA_SRC_READS_RS) | (src64_inc_by_pre << Self::DMA_SRC64_INC_BY_PRE_RS) | (unaligned_dst_src << Self::DMA_UNALIGNED_DST_SRC_RS) - // fill_byte / memcmp is 0 for memcpy << 21 | (pre_count << Self::DMA_LPRE_COUNT_RS) // optimization to read loop_count * 8 + pre_count | (loop_count << Self::DMA_LOOP_COUNT_RS) + | ((requires_dma as u64) << Self::DMA_REQUIRES_DMA_RS) + } else { + pre_count + | (post_count << Self::DMA_POST_COUNT_RS) + | (pre_writes << Self::DMA_PRE_WRITES_RS) + | (dst_offset << Self::DMA_DST_OFFSET_RS) + | (pre_count << Self::DMA_LPRE_COUNT_RS) // optimization to read loop_count * 8 + pre_count + | (loop_count << Self::DMA_LOOP_COUNT_RS) + | ((requires_dma as u64) << Self::DMA_REQUIRES_DMA_RS) + } } #[inline(always)] - pub fn calculate_encode2(dst: u64, src: u64, count: usize, neq: bool) -> u64 { - // #bits bits - // pre_count: 0-7 3 0-2 - // post_count: 0-8(*) 4 3-6 (*) memcmp - // pre_writes: 0,1,2 2 7-8 - // dst_offset: 0-7 3 9-11 - // src_offset: 0-7 3 12-14 - // double_src_pre: 0,1 1 15 - // double_src_post: 0,1 1 16 - // extra_src_reads: 0-3 2 17-18 - // src64_inc_by_pre: 1 19 - // unaligned_dst_src: 1 20 - // fill_byte/cmp: 8 21-28 - // requires_dma: 1 29 - // (reserved) 2 30-31 - // lpre_count 3 32-34 - // loop_count 29 35-63 - - // (dst + count) & 0x7 = 7 - // (dst_offset + count) 0x07 = 7 - // - // loop = loop - 1 - // pre_writes = pre_writes + 1 - // post = 8 - // double_src_post = unaligned_dst_src ? 1:0; - // extra_src_reads = extra_src_read + 1 - + pub const fn calculate_encode_no_src(dst: u64, count: usize) -> u64 { let dst_offset = dst & 0x07; - let src_offset = src & 0x07; let count = count as u64; - let (pre_count, mut loop_count, mut post_count) = if dst_offset > 0 { + let (pre_count, loop_count, post_count) = if dst_offset > 0 { let _pre_count = 8 - dst_offset; if _pre_count >= count { (count, 0, 0) @@ -311,43 +313,15 @@ impl DmaInfo { } else { (0, count >> 3, count & 0x07) }; - let mut pre_writes = (pre_count > 0) as u64 + (post_count > 0) as u64; - // let to_src_offset = (src + count - 1) & 0x07; - let src_offset_pos = (src_offset + pre_count) & 0x07; - let mut double_src_post = (src_offset_pos + post_count) > 8; - let double_src_pre = (src_offset + pre_count) > 8; - let mut extra_src_reads = - if count == 0 { 0 } else { (((src + count - 1) >> 3) - (src >> 3) + 1) - loop_count }; - - let src64_inc_by_pre = (pre_count > 0 && (src_offset + pre_count) >= 8) as u64; - let unaligned_dst_src = (src_offset != dst_offset) as u64; - - if neq && post_count == 0 && loop_count > 0 { - // (dst + count) 0x07 == 7 ==> (dst_offset + count) 0x07 == 7 ==> post_count == 0 - // loop = loop - 1 - // pre_writes = pre_writes + 1 - // post = 8 - // double_src_post = unaligned_dst_src ? 1:0; - // extra_src_reads = extra_src_read + 1 - loop_count -= 1; - pre_writes += 1; - post_count = 8; - double_src_post = src_offset != dst_offset; - extra_src_reads += extra_src_reads; - } + let pre_writes = (pre_count > 0) as u64 + (post_count > 0) as u64; + let requires_dma = count == 0 || pre_count != 0 || post_count != 0; pre_count | (post_count << Self::DMA_POST_COUNT_RS) | (pre_writes << Self::DMA_PRE_WRITES_RS) | (dst_offset << Self::DMA_DST_OFFSET_RS) - | (src_offset << Self::DMA_SRC_OFFSET_RS) - | ((double_src_pre as u64) << Self::DMA_DOUBLE_SRC_PRE_RS) - | ((double_src_post as u64) << Self::DMA_DOUBLE_SRC_POST_RS) - | (extra_src_reads << Self::DMA_EXTRA_SRC_READS_RS) - | (src64_inc_by_pre << Self::DMA_SRC64_INC_BY_PRE_RS) - | (unaligned_dst_src << Self::DMA_UNALIGNED_DST_SRC_RS) - // fill_byte / memcmp is 0 for memcpy << 21 | (pre_count << Self::DMA_LPRE_COUNT_RS) // optimization to read loop_count * 8 + pre_count | (loop_count << Self::DMA_LOOP_COUNT_RS) + | ((requires_dma as u64) << Self::DMA_REQUIRES_DMA_RS) } #[inline(always)] @@ -820,17 +794,37 @@ mod tests { } assert!(table.len() == 2048); } + + #[test] + fn asm_fast_encode_no_src_table() { + let table = generate_fast_encode_no_src_table(); + for i in 0..32 { + let dst_offset = (i >> 3) & 0x7; + println!( + "\t.quad 0x{:016x}, 0x{:016X}, 0x{:016X}, 0x{:016X} # {:4} - {:4} D{dst_offset} C{}", + table[i * 4], + table[i * 4 + 1], + table[i * 4 + 2], + table[i * 4 + 3], + i * 4, + i * 4 + 3, + (i * 4) & 0xF, + ); + } + assert!(table.len() == 128); + } + #[test] fn test_simple() { let dst = 0xA011FE70; let src = 0xA011F4D0; let count = 5; - let encode = DmaInfo::calculate_encode(dst, src, count, false); + let encode = DmaInfo::calculate_encode(dst, src, count, false, true); let fast_encode = DmaInfo::encode_memcpy(dst, src, count); println!("encode: 0x{encode:016X} {}", DmaInfo::to_string(encode)); println!("fast_encode: 0x{fast_encode:016X} {}", DmaInfo::to_string(fast_encode)); - let encode = DmaInfo::calculate_encode(dst, src, count, true); + let encode = DmaInfo::calculate_encode(dst, src, count, true, true); let fast_encode = DmaInfo::encode_memcmp(dst, src, count, 0xDB); println!("encode: 0x{encode:016X} {}", DmaInfo::to_string(encode)); println!("fast_encode: 0x{fast_encode:016X} {}", DmaInfo::to_string(fast_encode)); @@ -842,7 +836,7 @@ mod tests { for dst in 0..256 { for src in 0..256 { for count in 0..256 { - let encode = DmaInfo::calculate_encode(dst, src, count, false); + let encode = DmaInfo::calculate_encode(dst, src, count, false, true); let fast_encode = DmaInfo::encode_memcpy(dst, src, count); assert_eq!(encode, fast_encode, "testing with memcpy dst:0x{dst:08X} src:0x{src:08X} count:{count} E:0x{encode:016X} FE:0x{fast_encode:016X}" @@ -855,7 +849,7 @@ mod tests { for dst in 0..256 { for src in 0..256 { for count in 0..256 { - let encode = DmaInfo::calculate_encode2(dst, src, count, neq) | DmaInfo::DMA_REQUIRES_DMA_MASK; + let encode = DmaInfo::calculate_encode(dst, src, count, neq, true) | DmaInfo::DMA_REQUIRES_DMA_MASK; let fast_encode = DmaInfo::encode_memcmp_neq(dst, src, count, neq); assert_eq!( encode, diff --git a/ziskos/entrypoint/src/dma.rs b/ziskos/entrypoint/src/dma.rs index 832b983dc..9e465843f 100644 --- a/ziskos/entrypoint/src/dma.rs +++ b/ziskos/entrypoint/src/dma.rs @@ -101,30 +101,36 @@ macro_rules! ziskos_memcpy { #[macro_export] macro_rules! ziskos_memcmp { ($dst:expr, $src: expr, $size:literal) => {{ + let v: i64; unsafe { core::arch::asm!( "csrs {port}, {src}", - "addi x0, {dst}, {size}", + "addi {res}, {dst}, {size}", port = const zisk_definitions::SYSCALL_DMA_MEMCMP_ID, size = const $size, - dst = in(reg) $dst.as_mut_ptr(), + dst = in(reg) $dst.as_ptr(), src = in(reg) $src.as_ptr(), + res = out(reg) v, options(nostack, preserves_flags), ); } + v }}; ($dst:expr, $src: expr, $size:expr) => {{ + let v: i64; unsafe { core::arch::asm!( "csrs {port}, {src}", - "add x0, {dst}, {size}", + "add {res}, {dst}, {size}", port = const zisk_definitions::SYSCALL_DMA_MEMCMP_ID, size = in(reg) $size, - dst = in(reg) $dst.as_mut_ptr(), + dst = in(reg) $dst.as_ptr(), src = in(reg) $src.as_ptr(), + res = out(reg) v, options(nostack, preserves_flags), ); } + v }}; } From d0d253eb9dfb275129556c79310911a7d4ab60a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20i=20Buxadera?= Date: Thu, 19 Feb 2026 14:46:15 +0100 Subject: [PATCH 567/782] Finish merge --- precompiles/dma/src/dma_common.rs | 12 +++----- precompiles/helpers/src/dma.rs | 49 ++++++++++++++++--------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/precompiles/dma/src/dma_common.rs b/precompiles/dma/src/dma_common.rs index 1bafb3b2b..eca6ed308 100644 --- a/precompiles/dma/src/dma_common.rs +++ b/precompiles/dma/src/dma_common.rs @@ -24,14 +24,10 @@ pub fn get_dma_air_name(air_id: usize) -> &'static str { } pub fn dma_trace(title: &str, rows: usize, num_rows: usize) { - if rows == num_rows { - tracing::debug!("··· Creating {title} instance [{rows} / {rows} rows filled 100%]"); - } else { - tracing::debug!( - "··· Creating {title} instance [{rows} / {num_rows} rows filled {:.2}%]", - rows as f64 / num_rows as f64 * 100.0 - ); - } + tracing::debug!( + "··· Creating {title} instance [{rows} / {num_rows} rows filled {:.2}%]", + rows as f64 / num_rows as f64 * 100.0 + ); } /// Flattens and reorders input vectors to ensure proper sequencing. diff --git a/precompiles/helpers/src/dma.rs b/precompiles/helpers/src/dma.rs index 994503643..177b124e6 100644 --- a/precompiles/helpers/src/dma.rs +++ b/precompiles/helpers/src/dma.rs @@ -37,7 +37,8 @@ const FAST_ENCODE_TABLE_WO_NEQ_SIZE: usize = 8 * 8 * 16; const FAST_ENCODE_TABLE_SIZE: usize = FAST_ENCODE_TABLE_WO_NEQ_SIZE * 2; const FAST_ENCODE_NO_SRC_TABLE_SIZE: usize = 8 * 16; const FAST_ENCODE_TABLE: [u64; FAST_ENCODE_TABLE_SIZE] = generate_fast_encode_table(); -const FAST_ENCODE_NO_SRC_TABLE: [u64; FAST_ENCODE_NO_SRC_TABLE_SIZE] = generate_fast_encode_no_src_table(); +const FAST_ENCODE_NO_SRC_TABLE: [u64; FAST_ENCODE_NO_SRC_TABLE_SIZE] = + generate_fast_encode_no_src_table(); const fn generate_fast_encode_table() -> [u64; FAST_ENCODE_TABLE_SIZE] { let mut table = [0u64; FAST_ENCODE_TABLE_SIZE]; @@ -77,20 +78,20 @@ const fn generate_fast_encode_no_src_table() -> [u64; FAST_ENCODE_NO_SRC_TABLE_S let mut table = [0u64; FAST_ENCODE_NO_SRC_TABLE_SIZE]; // fill table let mut dst_offset: u64 = 0; - while dst_offset < 8 { + while dst_offset < 8 { let index = (dst_offset << 4) as usize; - let mut count: usize = 0; - while count < 16 { - let value = DmaInfo::calculate_encode_no_src(dst_offset, count); - let loop_count = DmaInfo::get_loop_count(value) as u64; - // The table is create to add directly de loop count and after all values - // are correct, for this reason substract de count, because we need diference - // between loop_count (shifted 32) and count (shifted 29) - table[index + count] = ((value & 0x0000_0007_FFFF_FFFF) - + (loop_count << DmaInfo::DMA_LOOP_COUNT_RS)) - .wrapping_sub((count as u64) << DmaInfo::DMA_LPRE_COUNT_RS); - count += 1; - } + let mut count: usize = 0; + while count < 16 { + let value = DmaInfo::calculate_encode_no_src(dst_offset, count); + let loop_count = DmaInfo::get_loop_count(value) as u64; + // The table is create to add directly de loop count and after all values + // are correct, for this reason substract de count, because we need diference + // between loop_count (shifted 32) and count (shifted 29) + table[index + count] = ((value & 0x0000_0007_FFFF_FFFF) + + (loop_count << DmaInfo::DMA_LOOP_COUNT_RS)) + .wrapping_sub((count as u64) << DmaInfo::DMA_LPRE_COUNT_RS); + count += 1; + } dst_offset += 1; } table @@ -230,7 +231,13 @@ impl DmaInfo { const DMA_DIRECT_MASK: u64 = Self::DMA_FULL_ALIGNED_MASK | Self::DMA_REQUIRES_DMA_TEST_MASK; #[inline(always)] - pub const fn calculate_encode(dst: u64, src: u64, count: usize, neq: bool, has_src: bool) -> u64 { + pub const fn calculate_encode( + dst: u64, + src: u64, + count: usize, + neq: bool, + has_src: bool, + ) -> u64 { let dst_offset = dst & 0x07; let src_offset = src & 0x07; @@ -272,7 +279,7 @@ impl DmaInfo { } let requires_dma = count == 0 || pre_count != 0 || post_count != 0; if has_src { - pre_count + pre_count | (post_count << Self::DMA_POST_COUNT_RS) | (pre_writes << Self::DMA_PRE_WRITES_RS) | (dst_offset << Self::DMA_DST_OFFSET_RS) @@ -286,7 +293,7 @@ impl DmaInfo { | (loop_count << Self::DMA_LOOP_COUNT_RS) | ((requires_dma as u64) << Self::DMA_REQUIRES_DMA_RS) } else { - pre_count + pre_count | (post_count << Self::DMA_POST_COUNT_RS) | (pre_writes << Self::DMA_PRE_WRITES_RS) | (dst_offset << Self::DMA_DST_OFFSET_RS) @@ -810,7 +817,7 @@ mod tests { table[i * 4 + 3], i * 4, i * 4 + 3, - (i * 4) & 0xF, + (i * 4) & 0xF, ); } assert!(table.len() == 128); @@ -851,12 +858,8 @@ mod tests { for dst in 0..256 { for src in 0..256 { for count in 0..256 { -<<<<<<< fix/packed-dma - let encode = DmaInfo::calculate_encode2(dst, src, count, neq) + let encode = DmaInfo::calculate_encode(dst, src, count, neq) | DmaInfo::DMA_REQUIRES_DMA_MASK; -======= - let encode = DmaInfo::calculate_encode(dst, src, count, neq, true) | DmaInfo::DMA_REQUIRES_DMA_MASK; ->>>>>>> feature/dma-memcmp-inputcpy let fast_encode = DmaInfo::encode_memcmp_neq(dst, src, count, neq); assert_eq!( encode, From dc997894e39e16455da546495d0caca60a3703fc Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 20 Feb 2026 17:22:02 +0000 Subject: [PATCH 568/782] Update plonk prover --- Cargo.lock | 180 ++++++++++++++++++++++---------------------- examples/Cargo.lock | 138 ++++++++++++++++----------------- 2 files changed, 159 insertions(+), 159 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fceba6e1f..fca250795 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,9 +111,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "ark-bls12-381" @@ -186,7 +186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -199,7 +199,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -260,7 +260,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -331,7 +331,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] @@ -343,7 +343,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -365,7 +365,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -376,7 +376,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -488,7 +488,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -543,7 +543,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -558,9 +558,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6f81257d10a0f602a294ae4182251151ff97dbb504ef9afcdda4a64b24d9b4" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" @@ -746,9 +746,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.59" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -756,9 +756,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.59" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -775,7 +775,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1109,13 +1109,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "fields", "num-bigint", @@ -1149,7 +1149,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1160,7 +1160,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1241,7 +1241,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1251,7 +1251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1293,7 +1293,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1320,7 +1320,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1367,7 +1367,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1468,7 +1468,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "cfg-if", "num-bigint", @@ -1581,7 +1581,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2163,9 +2163,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "d36139f1c97c42c0c86a411910b04e48d4939a0376e6e0f989420cbdee0120e5" dependencies = [ "once_cell", "wasm-bindgen", @@ -2745,7 +2745,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2772,7 +2772,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "colored", "fields", @@ -2806,7 +2806,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3110,7 +3110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3134,7 +3134,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "bincode", "blake3", @@ -3170,7 +3170,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "bincode", "borsh", @@ -3202,7 +3202,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "fields", "itoa", @@ -3215,17 +3215,17 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "crossbeam-channel", "tracing", @@ -3234,7 +3234,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "bincode", "bytemuck", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "bytemuck", "fields", @@ -3282,7 +3282,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.116", + "syn 2.0.117", "tempfile", ] @@ -3296,7 +3296,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3818,9 +3818,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation", @@ -3831,9 +3831,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.16.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -3897,7 +3897,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4214,9 +4214,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -4240,7 +4240,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4337,7 +4337,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4348,7 +4348,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4462,7 +4462,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4593,9 +4593,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f32a6f80051a4111560201420c7885d0082ba9efe2ab61875c587bb6b18b9a0" +checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", "axum", @@ -4622,21 +4622,21 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6d8958ed3be404120ca43ffa0fb1e1fc7be214e96c8d33bd43a131b6eebc9e" +checksum = "1882ac3bf5ef12877d7ed57aad87e75154c11931c2ba7e6cde5e22d63522c734" dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "tonic-prost" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f86539c0089bfd09b1f8c0ab0239d80392af74c21bc9e0f15e1b4aca4c1647f" +checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" dependencies = [ "bytes", "prost", @@ -4645,16 +4645,16 @@ dependencies = [ [[package]] name = "tonic-prost-build" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65873ace111e90344b8973e94a1fc817c924473affff24629281f90daed1cd2e" +checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a" dependencies = [ "prettyplease", "proc-macro2", "prost-build", "prost-types", "quote", - "syn 2.0.116", + "syn 2.0.117", "tempfile", "tonic-build", ] @@ -4753,7 +4753,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5018,9 +5018,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "9ff9c7baef35ac3c0e17d8bfc9ad75eb62f85a2f02bccc906699dadb0aa9c622" dependencies = [ "cfg-if", "once_cell", @@ -5031,9 +5031,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "d24699cd39db9966cf6e2ef10d2f72779c961ad905911f395ea201c3ec9f545d" dependencies = [ "cfg-if", "futures-util", @@ -5045,9 +5045,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "39455e84ad887a0bbc93c116d72403f1bb0a39e37dd6f235a43e2128a0c7f1fd" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5055,22 +5055,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "dff4761f60b0b51fd13fec8764167b7bbcc34498ce3e52805fe1db6f2d56b6d6" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "bc6a171c53d98021a93a474c4a4579d76ba97f9517d871bc12e27640f218b6dd" dependencies = [ "unicode-ident", ] @@ -5124,9 +5124,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "668fa5d00434e890a452ab060d24e3904d1be93f7bb01b70e5603baa2b8ab23b" dependencies = [ "js-sys", "wasm-bindgen", @@ -5290,7 +5290,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5301,7 +5301,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5660,7 +5660,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.116", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -5676,7 +5676,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -5721,7 +5721,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "colored", "fields", @@ -5801,7 +5801,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] @@ -5822,7 +5822,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5842,7 +5842,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] @@ -5863,7 +5863,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5896,7 +5896,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 67224742a..f9ad4452f 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "ark-bls12-381" @@ -171,7 +171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -184,7 +184,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -245,7 +245,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -310,7 +310,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] @@ -322,7 +322,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -385,7 +385,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -437,7 +437,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -452,9 +452,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6f81257d10a0f602a294ae4182251151ff97dbb504ef9afcdda4a64b24d9b4" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" @@ -577,9 +577,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.59" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -587,9 +587,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.59" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -606,7 +606,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -807,13 +807,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "fields", "num-bigint", @@ -847,7 +847,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -858,7 +858,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -925,7 +925,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -935,7 +935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -956,7 +956,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -974,7 +974,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1006,7 +1006,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1122,7 +1122,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "cfg-if", "num-bigint", @@ -1514,9 +1514,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "d36139f1c97c42c0c86a411910b04e48d4939a0376e6e0f989420cbdee0120e5" dependencies = [ "once_cell", "wasm-bindgen", @@ -1986,7 +1986,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "colored", "fields", @@ -2263,7 +2263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2287,7 +2287,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "bincode", "blake3", @@ -2323,7 +2323,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "bincode", "borsh", @@ -2355,7 +2355,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "fields", "itoa", @@ -2368,17 +2368,17 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "crossbeam-channel", "tracing", @@ -2387,7 +2387,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "bincode", "bytemuck", @@ -2399,7 +2399,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "bytemuck", "fields", @@ -2822,9 +2822,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation", @@ -2835,9 +2835,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.16.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -2889,7 +2889,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3183,9 +3183,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3200,7 +3200,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3291,7 +3291,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3302,7 +3302,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3406,7 +3406,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3538,7 +3538,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3757,9 +3757,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "9ff9c7baef35ac3c0e17d8bfc9ad75eb62f85a2f02bccc906699dadb0aa9c622" dependencies = [ "cfg-if", "once_cell", @@ -3770,9 +3770,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "39455e84ad887a0bbc93c116d72403f1bb0a39e37dd6f235a43e2128a0c7f1fd" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3780,22 +3780,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "dff4761f60b0b51fd13fec8764167b7bbcc34498ce3e52805fe1db6f2d56b6d6" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "bc6a171c53d98021a93a474c4a4579d76ba97f9517d871bc12e27640f218b6dd" dependencies = [ "unicode-ident", ] @@ -3983,7 +3983,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3994,7 +3994,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4344,7 +4344,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.116", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4360,7 +4360,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4405,7 +4405,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cc5f43d2076bab0ca71b1dfcc59e686cb4d0b084" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" dependencies = [ "colored", "fields", @@ -4474,7 +4474,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] @@ -4495,7 +4495,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4515,7 +4515,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] @@ -4536,7 +4536,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4569,7 +4569,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] From 31118f54bb69d77b47c934fab7222b171844f8bd Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 23 Feb 2026 11:19:47 +0100 Subject: [PATCH 569/782] Refactor main.c, for the sake of clarity --- emulator-asm/src/main.c | 374 ++++++++++++++++++++++++++-------------- 1 file changed, 247 insertions(+), 127 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index ae5d6b591..53477fdad 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -13,49 +13,76 @@ #include #include #include -#include "../../lib-c/c/src/ec/ec.hpp" -#include "../../lib-c/c/src/fcall/fcall.hpp" -#include "../../lib-c/c/src/arith256/arith256.hpp" -#include "emu.hpp" #include #include #include #include #include #include -//#include +#include "emu.hpp" + +/**************************/ +/* Assembly-provided code */ +/**************************/ -// Assembly-provided functions +// This is the emulator assembly code start function, which will execute the code in the ROM until +// it ends, and generate the trace in the output trace memory. +// It is called from C to start the execution of the assembly code. void emulator_start(void); + +// These functions are implemented in assembly and provide access to configuration parameters used +// to generate the assembly code, and that in some cases must match the C main program configuration uint64_t get_max_bios_pc(void); uint64_t get_max_program_pc(void); -uint64_t get_gen_method(void); +uint64_t get_gen_method(void); // Must match the C main program provided argument uint64_t get_precompile_results(void); +// These variables are updated by the assembly code to provide information about the execution +// status and trace generation, accessed by C to generate the response to the client +extern uint64_t MEM_STEP; // Current step, i.e. number of executed instructions, updated by assembly at every step or at the end of every chunk, depending on the generation method +extern uint64_t MEM_END; // Indicates the end of execution +extern uint64_t MEM_ERROR; // Indicates an error during execution +extern uint64_t MEM_TRACE_ADDRESS; // Address of the trace memory +extern uint64_t MEM_CHUNK_ADDRESS; // Address of the current chunk +extern uint64_t MEM_CHUNK_START_STEP; // Step at which the current chunk started + +/***************/ +/* Definitions */ +/***************/ + // Address map +// There definitions must match the ZisK rust code ones at core/src/mem.rs used to generate the +// assembly code, and that are used by the assembly code to access memory and generate the trace #define ROM_ADDR (uint64_t)0x80000000 #define ROM_SIZE (uint64_t)0x08000000 // 128MB - #define INPUT_ADDR (uint64_t)0x90000000 #define MAX_INPUT_SIZE (uint64_t)0x08000000 // 128MB - #define RAM_ADDR (uint64_t)0xa0000000 #define RAM_SIZE (uint64_t)0x20000000 // 512MB #define SYS_ADDR RAM_ADDR #define SYS_SIZE (uint64_t)0x10000 #define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) +// Trace address space configuration. There is an initial trace size that is mapped at the +// beginning of the trace address space, and that is used for the first execution. +// When more trace space is needed, new chunks of a delta size are mapped, until reaching the +// maximum trace size. Trace space only grows, it never shrinks, to avoid the overhead of +// unmapping and remapping memory, and to allow the client to access the full trace until the end of +// execution, even if the trace size is increased during execution. #define TRACE_ADDR (uint64_t)0xd0000000 #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 // 6GB #define TRACE_DELTA_SIZE (uint64_t)0x080000000 // 2GB #define TRACE_MAX_SIZE (uint64_t)0x1000000000 // 64GB #define TRACE_NUMBER_OF_CHUNKS (((TRACE_MAX_SIZE - TRACE_INITIAL_SIZE) / TRACE_DELTA_SIZE) + 1) - -uint64_t initial_trace_size = TRACE_INITIAL_SIZE; -uint64_t trace_address = TRACE_ADDR; -uint64_t trace_size = TRACE_INITIAL_SIZE; -uint64_t trace_used_size = 0; - +#define TRACE_SIZE_GRANULARITY (1014*1014) // ROM histogram trace size is round up to a multiple of this granularity + +// Control input and output shared memory configuration. +// Control input is used to tell the assembly code how many precompile result u64 fields have been +// written by the client. Control output is used to tell the client how many precompile result u64 +// fields have been read by the assembly code, so the client can know when it can write new +// precompile results. Assembly code waits when the number of read fields is not lower than the +// number of written fields, and client waits when the number of written fields would exceed the +// number of read fields plus the available precompile shared memory size, which is a circular buffer #define CONTROL_INPUT_ADDR (uint64_t)0x70000000 #define CONTROL_INPUT_SIZE (uint64_t)0x1000 // 4kB #define CONTROL_OUTPUT_ADDR (uint64_t)0x70001000 @@ -63,16 +90,13 @@ uint64_t trace_used_size = 0; #define CONTROL_RETRY_DELAY_US 1000 // 1ms #define CONTROL_NUMBER_OF_RETRIES 1000 // 1s max total +// Maximum number of steps to execute, used by the client to limit the execution steps of the +// assembly code. This limit is set by the ZisK PIL constraints. #define MAX_STEPS (1ULL << 36) -uint8_t * pInput = (uint8_t *)INPUT_ADDR; -uint8_t * pInputLast = (uint8_t *)(INPUT_ADDR + 10440504 - 64); -uint8_t * pRam = (uint8_t *)RAM_ADDR; -uint8_t * pRom = (uint8_t *)ROM_ADDR; -uint64_t * pInputTrace = (uint64_t *)TRACE_ADDR; -uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; - // Assembly service request/response types +// Only the methods supported by the configured generation method will be implemented by the server, +// e.g. gen_method=1 => PING, MT and SHUTDOWN; the rest will fail with an error response. #define TYPE_PING 1 // Ping #define TYPE_PONG 2 #define TYPE_MT_REQUEST 3 // Minimal trace @@ -94,7 +118,52 @@ uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; #define TYPE_SD_REQUEST 1000000 // Shutdown #define TYPE_SD_RESPONSE 1000001 -// Generation method, to be used with mandatory argument --gen= +// Server IP address, used by the client to connect to the server +#define SERVER_IP "127.0.0.1" // Change to your server IP; otherwise use localhost IP address + +// Chunk size used in generation methods that generate a trace chunk at every N steps, e.g. gen_method=1 or gen_method=7. +// It must be a power of two, and it is used to calculate the trace address threshold at which the next chunk must be mapped, +// to avoid reaching the end of the currently mapped trace memory. +#define CHUNK_SIZE (1ULL << 18) + +// Maximum trace chunk size, used to determine when the trace address is close to the end of the +// currently mapped trace memory and the next chunk must be mapped. It is calculated based on the +// maximum number of bytes that can be generated in a chunk +// Worst case: every chunk instruction is a keccak operation, with an input data of 200 bytes +// (let's use 256 bytes to be safe), and the trace includes the access to 2 source registers, 2 +// destination registers and 3 memory addresses (e.g. for a keccak operation with 3 memory operands), +// which are the maximum number of registers and memory addresses that can be accessed by a chunk +// instruction, according to the ZisK assembly code generation configuration. +#define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) +#define MAX_TRACE_CHUNK_INFO ((44*8) + 32) +#define MAX_BYTES_DIRECT_MTRACE 256 +#define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) +#define MAX_CHUNK_TRACE_SIZE ((CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) + +// Maximum precompile results share memory size +// It is a circular buffer +#define MAX_PRECOMPILE_SIZE (uint64_t)0x400000 // 4MB + +// Maximum chunk mask for zip generation method, which indicates which chunks are included in the trace, +// and must be between 0 and 7 (inclusive), as it is used to generate a mask of 8 bits where each +// bit indicates if the corresponding chunk is included in the trace or not. +#define MAX_CHUNK_MASK 7 + +// Maximum length of the shared memory prefix, e.g. "ZISK_12345" +// This prefix is used to generate the names of the shared memories and semaphores used for +// communication and synchronization between the server and the client, +#define MAX_SHM_PREFIX_LENGTH 64 + +/*********************/ +/* Generation method */ +/*********************/ + +// Specifies how the assembly code generates the trace, and what information it includes. +// It is specified with the mandatory argument --gen= +// It must match the value returned by the assembly function get_gen_method() +// The enum names are equivalent to the rust ones defined in core/src/riscv2zisk.rs as AsmGenerationMethod +// ZisK uses generation methods 1 (minimal trace), 2 (ROM histogram) and 7 (memory operations) +// but the rest of methods can be used for testing and debugging purposes typedef enum { Fast = 0, MinimalTrace = 1, @@ -108,8 +177,11 @@ typedef enum { MemReads = 9, ChunkPlayerMemReadsCollectMain = 10, } GenMethod; + +// Default generation method, can be overridden by the --gen argument GenMethod gen_method = Fast; +// Returns the acronym of the generation method, used for logging and file naming const char * gen_method_achronym(GenMethod method) { switch (method) @@ -129,8 +201,11 @@ const char * gen_method_achronym(GenMethod method) } } +// Pointers to the input, RAM, ROM and trace memory, used by both C and assembly code to access these memories +uint64_t * pInputTrace = (uint64_t *)TRACE_ADDR; // Used for trace consumption, i.e. chunk player +uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; // Used for trace generation, i.e. assembly code writes the trace to this address, and client reads it from this address + // Service TCP parameters -#define SERVER_IP "127.0.0.1" // Change to your server IP uint16_t port = 0; uint16_t arguments_port = 0; @@ -141,8 +216,6 @@ bool call_chunk_done = false; bool do_shutdown = false; // If true, the client will perform a shutdown request to the server when done uint64_t number_of_mt_requests = 1; // Loop to send this number of minimal trace requests -char input_file[4096]; - // To be used when calculating partial durations // Time measurements cannot be overlapped struct timeval start_time; @@ -157,65 +230,30 @@ uint64_t total_duration; // To be used when calculating the assembly duration uint64_t assembly_duration; -extern uint64_t MEM_STEP; -extern uint64_t MEM_END; -extern uint64_t MEM_ERROR; -extern uint64_t MEM_TRACE_ADDRESS; -extern uint64_t MEM_CHUNK_ADDRESS; -extern uint64_t MEM_CHUNK_START_STEP; - +// Counters used in functions called from assembly code uint64_t realloc_counter = 0; uint64_t wait_counter = 0; +uint64_t print_pc_counter = 0; -extern void zisk_keccakf(uint64_t state[25]); -/* Used for debugging -extern uint64_t reg_0; -extern uint64_t reg_1; -extern uint64_t reg_2; -extern uint64_t reg_3; -extern uint64_t reg_4; -extern uint64_t reg_5; -extern uint64_t reg_6; -extern uint64_t reg_7; -extern uint64_t reg_8; -extern uint64_t reg_9; -extern uint64_t reg_10; -extern uint64_t reg_11; -extern uint64_t reg_12; -extern uint64_t reg_13; -extern uint64_t reg_14; -extern uint64_t reg_15; -extern uint64_t reg_16; -extern uint64_t reg_17; -extern uint64_t reg_18; -extern uint64_t reg_19; -extern uint64_t reg_20; -extern uint64_t reg_21; -extern uint64_t reg_22; -extern uint64_t reg_23; -extern uint64_t reg_24; -extern uint64_t reg_25; -extern uint64_t reg_26; -extern uint64_t reg_27; -extern uint64_t reg_28; -extern uint64_t reg_29; -extern uint64_t reg_30; -extern uint64_t reg_31; -*/ +// Chunk player globals +uint64_t chunk_player_address = 0; +uint64_t chunk_player_mt_size = TRACE_INITIAL_SIZE; -bool is_power_of_two (uint64_t number) { +// Checks if a number is a power of two, used to validate the max steps and chunk size provided by the client +bool is_power_of_two (uint64_t number) +{ return (number != 0) && ((number & (number - 1)) == 0); } -#define INITIAL_CHUNK_SIZE (1ULL << 18) -uint64_t chunk_size = INITIAL_CHUNK_SIZE; -uint64_t chunk_size_mask = INITIAL_CHUNK_SIZE - 1; -uint64_t max_steps = (1ULL << 32); +/*************/ +/* MAX STEPS */ +/*************/ -// Chunk player globals -uint64_t chunk_player_address = 0; -uint64_t chunk_player_mt_size = TRACE_INITIAL_SIZE; // TODO +// Maximum number of steps to execute, used by the client to limit the execution steps of the +// assembly code. +uint64_t max_steps = (1ULL << 32); +// Sets the maximum number of steps provided by the client in the request void set_max_steps (uint64_t new_max_steps) { if (!is_power_of_two(new_max_steps)) @@ -228,25 +266,33 @@ void set_max_steps (uint64_t new_max_steps) max_steps = new_max_steps; } -// Worst case: every chunk instruction is a keccak operation, with an input data of 256 bytes - -#define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) -#define MAX_TRACE_CHUNK_INFO ((44*8) + 32) -#define MAX_BYTES_DIRECT_MTRACE 256 -#define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) -#define MAX_CHUNK_TRACE_SIZE ((INITIAL_CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) - uint64_t trace_address_threshold = TRACE_ADDR + TRACE_INITIAL_SIZE - MAX_CHUNK_TRACE_SIZE; -uint64_t print_pc_counter = 0; int map_locked_flag = MAP_LOCKED; -#ifdef ASM_PRECOMPILE_CACHE -bool precompile_cache_enabled = false; -#endif +/**************/ +/* TRACE SIZE */ +/**************/ -bool precompile_results_enabled = false; -uint64_t * precompile_results_address = NULL; +uint64_t initial_trace_size = TRACE_INITIAL_SIZE; +uint64_t trace_address = TRACE_ADDR; +uint64_t trace_size = TRACE_INITIAL_SIZE; +uint64_t trace_used_size = 0; + +void set_trace_size (uint64_t new_trace_size) +{ + // Update trace global variables + trace_size = new_trace_size; + trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; + pOutputTrace[2] = trace_size; +} + +/**************/ +/* CHUNK SIZE */ +/**************/ + +uint64_t chunk_size = CHUNK_SIZE; +uint64_t chunk_size_mask = CHUNK_SIZE - 1; void set_chunk_size (uint64_t new_chunk_size) { @@ -262,17 +308,9 @@ void set_chunk_size (uint64_t new_chunk_size) trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; } -void set_trace_size (uint64_t new_trace_size) -{ - // Update trace global variables - trace_size = new_trace_size; - trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; - pOutputTrace[2] = trace_size; -} - +// Forward declarations of functions implemented later in the code void parse_arguments(int argc, char *argv[]); uint64_t TimeDiff(const struct timeval startTime, const struct timeval endTime); - void configure (void); void server_setup (void); void server_reset_fast (void); @@ -280,13 +318,10 @@ void server_reset_slow (void); void server_reset_trace (void); void server_run (void); void server_cleanup (void); - void client_setup (void); void client_run (void); void client_cleanup (void); - void _chunk_done(void); - void log_minimal_trace(void); void log_histogram(void); void log_main_trace(void); @@ -294,12 +329,10 @@ void log_mem_trace(void); void log_mem_op(void); void save_mem_op_to_files(void); void log_chunk_player_main_trace(void); - int recv_all_with_timeout (int sockfd, void *buffer, size_t length, int flags, int timeout_sec); - void file_lock(void); -// Configuration +// Configuration globals, set by arguments bool output = false; bool output_riscof = false; bool silent = false; @@ -310,6 +343,8 @@ bool verbose = false; bool save_to_file = false; bool share_input_shm = false; // Shares input shared memories: input, precompile results and control input, using a common name bool open_input_shm = false; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) +char input_file[4096] = {0}; +bool redirect_output_to_file = false; // ROM histogram uint64_t histogram_size = 0; @@ -318,10 +353,12 @@ uint64_t program_size = 0; // Zip uint64_t chunk_mask = 0x0; // 0, 1, 2, 3, 4, 5, 6 or 7 -#define MAX_CHUNK_MASK 7 -// Maximum length of the shared memory prefix, e.g. SHMZISK12345678 -#define MAX_SHM_PREFIX_LENGTH 64 +/*****************/ +/* SHARED MEMORY */ +/*****************/ + +// Shared memory prefix char shm_prefix[MAX_SHM_PREFIX_LENGTH]; // Input shared memory @@ -346,19 +383,27 @@ sem_t * sem_chunk_done = NULL; char sem_shutdown_done_name[128]; sem_t * sem_shutdown_done = NULL; -// File lock name +// File lock name, used to lock a file that indicates that the assembly emulator process is running, +// to prevent multiple instances of the server from running at the same time. char file_lock_name[128]; int file_lock_fd = -1; // Log name char log_name[128]; +// Process id int process_id = 0; -uint64_t input_size = 0; +/**************************/ +/* PRECOMPILE AND CONTROL */ +/**************************/ -#define MAX_PRECOMPILE_SIZE (uint64_t)0x400000 // 4MB -//#define MAX_PRECOMPILE_SIZE (uint64_t)0x100000 // 1MB +#ifdef ASM_PRECOMPILE_CACHE +bool precompile_cache_enabled = false; +#endif + +bool precompile_results_enabled = false; +uint64_t * precompile_results_address = NULL; // Precompile results shared memory char shmem_precompile_name[128]; @@ -601,7 +646,9 @@ void trace_map_initialize (void) pOutputTrace = (uint64_t *)TRACE_ADDR; } -bool redirect_output_to_file = false; +/********/ +/* MAIN */ +/********/ int main(int argc, char *argv[]) { @@ -653,6 +700,7 @@ int main(int argc, char *argv[]) exit(-1); } } + // Configure based on parguments configure(); @@ -1155,7 +1203,6 @@ int main(int argc, char *argv[]) // Close the server close(server_fd); - /************/ /* CLEAN UP */ /************/ @@ -1180,6 +1227,11 @@ int main(int argc, char *argv[]) #endif } +/*******************************/ +/* ARGUMENTS AND CONFIGURATION */ +/*******************************/ + +// Print usage information: valid arguments void print_usage (void) { printf("Usage: ziskemuasm\n"); @@ -1219,9 +1271,11 @@ void print_usage (void) { printf("\t-r \n"); } + printf("\t--redirect-output-to-file redirect output to file\n"); printf("\t-h/--help print this\n"); } +// Parse main function arguments and configure global variables accordingly void parse_arguments(int argc, char *argv[]) { strcpy(shm_prefix, "ZISK"); @@ -1542,6 +1596,11 @@ void parse_arguments(int argc, char *argv[]) open_input_shm = true; continue; } + if (strcmp(argv[i], "--redirect-output-to-file") == 0) + { + redirect_output_to_file = true; + continue; + } #ifdef ASM_PRECOMPILE_CACHE if (strcmp(argv[i], "--precompile-cache-store") == 0) { @@ -1645,6 +1704,7 @@ void parse_arguments(int argc, char *argv[]) } } +// Configure global variables based on generation method and other arguments void configure (void) { // Select configuration based on generation method @@ -2090,6 +2150,14 @@ void configure (void) } } + if (precompile_results_enabled && (gen_method == ChunkPlayerMTCollectMem || gen_method == ChunkPlayerMemReadsCollectMain)) + { + printf("ERROR: configure() precompile results enabled is not compatible with generation method %u\n", gen_method); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (arguments_port != 0) { port = arguments_port; @@ -2122,6 +2190,10 @@ void configure (void) } } +/**********/ +/* CLIENT */ +/**********/ + void client_setup (void) { assert(!server); @@ -2176,7 +2248,7 @@ void client_setup (void) /* PRECOMPILE_RESULTS */ /**********************/ - if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + if (precompile_results_enabled) { /**************/ /* PRECOMPILE */ @@ -2747,7 +2819,7 @@ void client_run (void) /*****************************/ /* Read precompile file data */ /*****************************/ - if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + if (precompile_results_enabled) { // reset written counter *precompile_written_address = 0; @@ -2871,7 +2943,7 @@ void client_run (void) request[3] = 0; request[4] = 0; - if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + if (precompile_results_enabled) { client_write_precompile_results(); } @@ -2947,7 +3019,7 @@ void client_run (void) exit(-1); } - if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + if (precompile_results_enabled) { client_write_precompile_results(); } @@ -3013,7 +3085,7 @@ void client_run (void) exit(-1); } - if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + if (precompile_results_enabled) { client_write_precompile_results(); } @@ -3599,6 +3671,10 @@ void client_cleanup (void) } } +/**********/ +/* SERVER */ +/**********/ + void server_setup (void) { assert(server); @@ -3719,7 +3795,7 @@ void server_setup (void) /* PRECOMPILE_RESULTS */ /**********************/ - if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + if (precompile_results_enabled) { /**************/ /* PRECOMPILE */ @@ -4008,7 +4084,6 @@ void server_setup (void) bios_size = ((max_bios_pc - 0x1000) >> 2) + 1; program_size = max_program_pc - 0x80000000 + 1; histogram_size = (4 + 1 + bios_size + 1 + program_size)*8; -#define TRACE_SIZE_GRANULARITY (1014*1014) initial_trace_size = ((histogram_size/TRACE_SIZE_GRANULARITY) + 1) * TRACE_SIZE_GRANULARITY; trace_size = initial_trace_size; } @@ -4112,7 +4187,20 @@ void server_setup (void) void server_reset_fast (void) { // Reset precompile read address for next emulation - if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) *precompile_read_address = 0; + if (precompile_results_enabled) + { + // Set precompile read counter to 0 for next emulation + *precompile_read_address = 0; + + // Sync control output shared memory so that the writer can see the precompile reads we have + // done, and thus update the precompile_written_address if needed + if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } } void server_reset_slow (void) @@ -4511,6 +4599,10 @@ void server_cleanup (void) } } +/**************/ +/* PRINT REGS */ +/**************/ + //#define PRINT_REGS #ifdef PRINT_REGS extern uint64_t reg_0; @@ -4550,6 +4642,7 @@ extern uint64_t reg_33; extern uint64_t reg_34; #endif +// Used for debugging purposes extern int _print_regs() { #ifdef PRINT_REGS @@ -4593,11 +4686,16 @@ extern int _print_regs() #endif } +/************/ +/* PRINT PC */ +/************/ + //#define PRINT_PC_DURATION #ifdef PRINT_PC_DURATION struct timeval print_pc_tv; #endif +// Used for debugging purposes extern int _print_pc (uint64_t pc, uint64_t c) { #ifdef PRINT_PC_DURATION @@ -4660,6 +4758,10 @@ extern int _print_pc (uint64_t pc, uint64_t c) print_pc_counter++; } +/**************/ +/* CHUNK DONE */ +/**************/ + //#define CHUNK_DONE_DURATION #ifdef CHUNK_DONE_DURATION uint64_t chunk_done_counter = 0; @@ -4672,6 +4774,7 @@ struct timeval sync_start, sync_stop; uint64_t sync_duration = 0; #endif +// Called by the assembly to notify that a chunk is done and its trace is ready to be consumed extern void _chunk_done() { #ifdef CHUNK_DONE_DURATION @@ -4714,10 +4817,18 @@ extern void _chunk_done() } } +/*****************/ +/* REALLOC TRACE */ +/*****************/ + +// Called by the assembly to reallocate the trace when needed, e.g. for the next chunk, +// to increase the trace size by another chunk size extern void _realloc_trace (void) { + // Increase realloc counter realloc_counter++; + // Map next chunk of the trace shared memory trace_map_next_chunk(); // Update trace global variables @@ -4728,6 +4839,10 @@ extern void _realloc_trace (void) #endif } +/*****************/ +/* LOG FUNCTIONS */ +/*****************/ + /* Trace data structure [8B] Number of chunks: C @@ -5391,6 +5506,11 @@ void log_chunk_player_main_trace(void) printf("Chunk=%p size=%lu\n", chunk, mem_reads_size); } +/*************/ +/* FILE LOCK */ +/*************/ + +// Lock file exclusively to ensure that only one instance of the program is running at a time void file_lock(void) { // Open (or create) the lock file. We don't need to write to it. @@ -5412,6 +5532,11 @@ void file_lock(void) } } +/*********************************/ +/* WAIT FOR PRECOMPILE AVAILABLE */ +/*********************************/ + +// Called by the assembly when prec_written == prec_read, to wait for new precompile results to be available int _wait_for_prec_avail (void) { // Increment wait counter @@ -5514,8 +5639,3 @@ int _wait_for_prec_avail (void) fflush(stderr); exit(-1); } - -void post_prec_read (void) -{ - sem_post(sem_prec_read); -} From c1e831742037245d70626c3a4e7925b08ee12420 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 23 Feb 2026 12:42:59 +0100 Subject: [PATCH 570/782] Fix sha hasher GHA --- emulator-asm/src/main.c | 2 +- tools/test-env/test_sha_hasher.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 53477fdad..eb1762373 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4195,7 +4195,7 @@ void server_reset_fast (void) // Sync control output shared memory so that the writer can see the precompile reads we have // done, and thus update the precompile_written_address if needed if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); + printf("ERROR: server_reset_fast() msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); diff --git a/tools/test-env/test_sha_hasher.sh b/tools/test-env/test_sha_hasher.sh index 3f0ce9bd6..29f3b6734 100755 --- a/tools/test-env/test_sha_hasher.sh +++ b/tools/test-env/test_sha_hasher.sh @@ -40,7 +40,7 @@ main() { cd "$PROJECT_NAME" step "Building program..." - ensure cargo build --bin host --release || return 1 + ensure cargo build --release || return 1 ELF_PATH="target/elf/riscv64ima-zisk-zkvm-elf/release/$PROJECT_NAME" INPUT_BIN="host/tmp/input.bin" From 28480cfc1e90955395f08c57655467e2ea050a3c Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 23 Feb 2026 15:19:20 +0100 Subject: [PATCH 571/782] Undo changes unrelated to asm --- distributed/crates/worker/src/worker.rs | 10 +++- emulator-asm/asm-runner/src/hints_shmem.rs | 24 +------- precompiles/hints/src/hints_processor.rs | 67 +++++++--------------- tools/test-env/test_sha_hasher.sh | 2 +- 4 files changed, 32 insertions(+), 71 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index f304f8099..6c987702f 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -650,6 +650,10 @@ impl Worker { hints_processor.reset(); } + println!( + "Stream START received for job {}, initialized buffer and reset hints processor", + job_id + ); return Ok(()); } else if stream_type == StreamMessageKind::End { // Ensure buffer exists @@ -664,9 +668,13 @@ impl Worker { // Clean up the stream buffer for this job self.stream_buffers.remove(&job_id); + println!("Stream END received for job {}, cleaned up buffer", job_id); return Ok(()); } - + println!( + "Received stream data for job {}, stream type {:?}, processing...", + job_id, stream_type + ); let element = self.stream_buffers.get_mut(&job_id).ok_or_else(|| { anyhow::anyhow!( "Received stream data without START for job {} stream type {:?}", diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index 1338d5c01..cdfb1a1f2 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -9,10 +9,7 @@ use crate::{ }; use anyhow::Result; use named_sem::NamedSemaphore; -use std::{ - cell::RefCell, - sync::atomic::{fence, AtomicBool, Ordering}, -}; +use std::{cell::RefCell, sync::atomic::AtomicBool}; use tracing::debug; use zisk_common::io::StreamSink; @@ -188,11 +185,9 @@ impl StreamSink for HintsShmem { #[inline] fn submit(&self, processed: Vec) -> anyhow::Result<()> { let data_size = processed.len() as u64; - debug!("[SHMEM] submit() called with {} u64 elements", data_size); // Early return for empty data if data_size == 0 { - debug!("[SHMEM] submit() early return - empty data"); return Ok(()); } @@ -216,14 +211,10 @@ impl StreamSink for HintsShmem { // Read current write position once let write_pos = unified.control_writer.read_u64_at(0); - debug!("[SHMEM] Current write_pos={}, attempting to submit {} u64s", write_pos, data_size); // Flow control: wait until all consumers have advanced enough // We need to wait for the slowest consumer (minimum read position) loop { - // Ensure we observe the latest read positions - fence(Ordering::Acquire); - // Find the slowest consumer (minimum read position) and its index let (slowest_idx, min_read_pos) = separate .iter() @@ -250,39 +241,26 @@ impl StreamSink for HintsShmem { // Flow control based on buffer occupancy if available_space >= data_size { - debug!("[SHMEM] Sufficient space available ({}), breaking flow control loop", available_space); break; } // Not enough space - wait for the SLOWEST consumer to signal progress - debug!("[SHMEM] Insufficient space: available={}, needed={}, waiting on consumer {} (read_pos={})", - available_space, data_size, slowest_idx, min_read_pos); // Retry on interrupt (EINTR) if separate[slowest_idx].sem_read.wait().is_err() { - debug!("[SHMEM] sem_read.wait() returned error, retrying"); continue; } - debug!("[SHMEM] sem_read.wait() succeeded, retrying space check"); } // Write data ONCE to the unified shared memory buffer - debug!("[SHMEM] Writing {} u64s to ring buffer at pos {}", data_size, write_pos); unified.data_writer.write_ring_buffer(&processed)?; - fence(Ordering::Release); - // Update write position ONCE in control memory unified.control_writer.write_u64_at(0, write_pos + data_size); - debug!("[SHMEM] Updated write_pos to {}", write_pos + data_size); - - fence(Ordering::Release); // Notify ALL consumers that new data is available - debug!("[SHMEM] Posting to {} consumer semaphores", separate.len()); for res in separate.iter_mut() { res.sem_available.post()?; } - debug!("[SHMEM] submit() completed successfully"); Ok(()) } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index c4cef4a68..1bd6b3cc2 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -261,7 +261,6 @@ impl HintsProcessor { /// * `Ok(false)` - Batch processed successfully, no CTRL_END /// * `Err` - If a previous error occurred or hints are malformed pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { - debug!("[PROCESS] process_hints called with {} u64s, first_batch={}", hints.len(), first_batch); let mut has_ctrl_end = false; // Take any pending partial hint from previous batch @@ -412,9 +411,7 @@ impl HintsProcessor { let state = Arc::clone(&self.state); let custom_handlers = Arc::clone(&self.custom_handlers); self.pool.spawn(move || { - debug!("[WORKER] seq={} dispatched", seq_id); Self::worker_thread(state, hint, generation, seq_id, custom_handlers); - debug!("[WORKER] seq={} done", seq_id); }); idx += length; @@ -476,32 +473,27 @@ impl HintsProcessor { // Check generation first to detect stale workers (before processing) let current_gen = state.generation.load(Ordering::SeqCst); if generation != current_gen { - debug!("[WORKER] seq={} early return: generation mismatch (worker={}, current={})", seq_id, generation, current_gen); + // Worker belongs to old generation; ignore return; } - // Catch panics to prevent permanently-stuck None slots in the buffer. - // If dispatch_hint panics, Rayon catches it silently but the slot would - // stay None forever, blocking the drainer from making progress. - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - if state.error_flag.load(Ordering::Acquire) { - Err(anyhow::anyhow!("Processing stopped due to error")) - } else { - Self::dispatch_hint(hint, custom_handlers) - } - })) - .unwrap_or_else(|panic_info| { - let msg = if let Some(s) = panic_info.downcast_ref::<&str>() { - s.to_string() - } else if let Some(s) = panic_info.downcast_ref::() { - s.clone() - } else { - "unknown panic".to_string() - }; - Err(anyhow::anyhow!("Worker panicked processing hint: {}", msg)) - }); + // println!("Processing Hint => {:?}:", hint); - debug!("[WORKER] seq={} processed, acquiring lock", seq_id); + // Check if we should stop due to error - but still need to fill the slot + let result = if state.error_flag.load(Ordering::Acquire) { + Err(anyhow::anyhow!("Processing stopped due to error")) + } else { + // Process the hint + Self::dispatch_hint(hint, custom_handlers) + }; + + // println!( + // "Hint result: {:x?} bytes", + // match &result { + // Ok(data) => format!("{:?}", data), + // Err(e) => format!("Err({})", e), + // } + // ); // Store result - MUST fill slot even if error occurred let mut queue = state.queue.lock().unwrap(); @@ -509,47 +501,41 @@ impl HintsProcessor { // Check generation again in case reset happened during processing let current_gen = state.generation.load(Ordering::SeqCst); if generation != current_gen { - debug!("[WORKER] seq={} early return: generation mismatch after processing", seq_id); + // Worker belongs to old generation; buffer was cleared and repopulated + // Our seq_id is from the old session and doesn't correspond to current slots return; } // Calculate offset in buffer; handle drained slots if seq_id < queue.next_drain_seq { - debug!("[WORKER] seq={} early return: seq_id < next_drain_seq ({})", seq_id, queue.next_drain_seq); + // This result belongs to a previous stream/session; ignore return; } let offset = seq_id - queue.next_drain_seq; // Check if slot exists - if not, drainer already processed and removed it if offset >= queue.buffer.len() { - debug!("[WORKER] seq={} early return: offset {} >= buffer.len() {}", seq_id, offset, queue.buffer.len()); + // Slot was already drained; safe to drop this result return; } // Fill the slot to allow drainer to proceed (critical for ordering) queue.buffer[offset] = Some(result); - debug!("[WORKER] seq={} filled slot at offset {}, buffer_len={}", seq_id, offset, queue.buffer.len()); // Release lock before notifying drop(queue); // Notify drainer thread (use notify_all to wake any waiting threads) - debug!("[WORKER] seq={} calling notify_all", seq_id); state.drain_signal.notify_all(); } /// Drainer thread that waits for hints to complete and drains ready results from queue. fn drainer_thread(state: Arc, hints_sink: Arc) { - debug!("[DRAINER] Thread started"); loop { - debug!("[DRAINER] Attempting to acquire queue lock"); let mut queue = state.queue.lock().unwrap(); - debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", - queue.buffer.len(), queue.next_drain_seq); // Check for shutdown if state.shutdown.load(Ordering::Acquire) { - debug!("[DRAINER] Shutdown signal received, exiting"); break; } @@ -557,31 +543,25 @@ impl HintsProcessor { let mut drained_any = false; while let Some(Some(res)) = queue.buffer.front() { drained_any = true; - let current_seq = queue.next_drain_seq; match res { Ok(data) => { // Clone data before dropping lock - debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); let data_to_submit = data.clone(); queue.buffer.pop_front(); queue.next_drain_seq += 1; // Drop lock before submitting to avoid blocking workers - debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); drop(queue); // Submit to sink - debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); if let Err(e) = hints_sink.submit(data_to_submit) { eprintln!("Error submitting to sink: {}", e); state.error_flag.store(true, Ordering::Release); state.drain_signal.notify_all(); return; } - debug!("[DRAINER] Completed submit seq={}", current_seq); // Re-acquire lock for next iteration - debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); queue = state.queue.lock().unwrap(); } Err(e) => { @@ -598,25 +578,20 @@ impl HintsProcessor { // If we drained any results, notify wait_for_completion that buffer changed if drained_any { - debug!("[DRAINER] Notifying wait_for_completion"); state.drain_signal.notify_all(); } // Check for shutdown again before waiting if state.shutdown.load(Ordering::Acquire) { - debug!("[DRAINER] Shutdown signal received before wait, exiting"); break; } // Wait for notification that a hint completed - debug!("[DRAINER] Waiting on drain_signal condvar"); #[allow(unused_assignments)] { queue = state.drain_signal.wait(queue).unwrap(); } - debug!("[DRAINER] Woke up from condvar wait"); } - debug!("[DRAINER] Thread exiting"); } /// Waits for all pending hints to be processed and drained. diff --git a/tools/test-env/test_sha_hasher.sh b/tools/test-env/test_sha_hasher.sh index 29f3b6734..3f0ce9bd6 100755 --- a/tools/test-env/test_sha_hasher.sh +++ b/tools/test-env/test_sha_hasher.sh @@ -40,7 +40,7 @@ main() { cd "$PROJECT_NAME" step "Building program..." - ensure cargo build --release || return 1 + ensure cargo build --bin host --release || return 1 ELF_PATH="target/elf/riscv64ima-zisk-zkvm-elf/release/$PROJECT_NAME" INPUT_BIN="host/tmp/input.bin" From 69707ff53f1ff8249aa237519b483682933c86f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Mon, 23 Feb 2026 15:33:28 +0100 Subject: [PATCH 572/782] Merge pull request #805 from 0xPolygonHermez/fix/ziskclib-make Modification asm setup --- Cargo.lock | 48 ++++++------ rom-setup/src/asm_setup.rs | 156 +++++++++++++++++++++++++++---------- ziskbuild/src/build.rs | 8 +- 3 files changed, 146 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fca250795..bccf3edeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,9 +690,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -1216,9 +1216,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -2163,9 +2163,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.86" +version = "0.3.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36139f1c97c42c0c86a411910b04e48d4939a0376e6e0f989420cbdee0120e5" +checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d" dependencies = [ "once_cell", "wasm-bindgen", @@ -2285,9 +2285,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -3310,9 +3310,9 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" +checksum = "83c41efbf8f90ac44de7f3a868f0867851d261b56291732d0cbf7cceaaeb55a6" dependencies = [ "bitflags", "memchr", @@ -3683,9 +3683,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -5018,9 +5018,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.109" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff9c7baef35ac3c0e17d8bfc9ad75eb62f85a2f02bccc906699dadb0aa9c622" +checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac" dependencies = [ "cfg-if", "once_cell", @@ -5031,9 +5031,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.59" +version = "0.4.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24699cd39db9966cf6e2ef10d2f72779c961ad905911f395ea201c3ec9f545d" +checksum = "fe88540d1c934c4ec8e6db0afa536876c5441289d7f9f9123d4f065ac1250a6b" dependencies = [ "cfg-if", "futures-util", @@ -5045,9 +5045,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.109" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39455e84ad887a0bbc93c116d72403f1bb0a39e37dd6f235a43e2128a0c7f1fd" +checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5055,9 +5055,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.109" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff4761f60b0b51fd13fec8764167b7bbcc34498ce3e52805fe1db6f2d56b6d6" +checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af" dependencies = [ "bumpalo", "proc-macro2", @@ -5068,9 +5068,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.109" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6a171c53d98021a93a474c4a4579d76ba97f9517d871bc12e27640f218b6dd" +checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41" dependencies = [ "unicode-ident", ] @@ -5124,9 +5124,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.86" +version = "0.3.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668fa5d00434e890a452ab060d24e3904d1be93f7bb01b70e5603baa2b8ab23b" +checksum = "9d6bb20ed2d9572df8584f6dc81d68a41a625cadc6f15999d649a70ce7e3597a" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/rom-setup/src/asm_setup.rs b/rom-setup/src/asm_setup.rs index 993e9f13a..74c699e8d 100644 --- a/rom-setup/src/asm_setup.rs +++ b/rom-setup/src/asm_setup.rs @@ -8,8 +8,113 @@ use zisk_core::{is_elf_file, AsmGenerationMethod, Riscv2zisk}; use crate::get_elf_data_hash_from_path; -/// Check if all assembly binary files exist for a given ELF and output path -pub fn assembly_files_exist(elf: &Path, output_path: &Path, hints: bool) -> Result { +fn find_workspace_root(start: &Path) -> Option { + let mut current = Some(start); + + while let Some(dir) = current { + let cargo_toml = dir.join("Cargo.toml"); + + if cargo_toml.exists() { + if let Ok(contents) = std::fs::read_to_string(&cargo_toml) { + if contents.contains("[workspace]") { + return Some(dir.to_path_buf()); + } + } + } + + current = dir.parent(); + } + + None +} + +pub fn resolve_emulator_asm(installed_path: PathBuf, verbose: bool) -> Result { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + let workspace_root = + if manifest_dir.exists() { find_workspace_root(&manifest_dir) } else { None }; + + let emulator_asm_path = if let Some(ref root) = workspace_root { + let candidate = root.join("emulator-asm"); + if candidate.exists() { + if verbose { + println!("Using emulator-asm from workspace: {}", candidate.display()); + } + candidate + } else { + if verbose { + println!("Workspace found but emulator-asm not present, using installed path"); + } + installed_path + } + } else { + if verbose { + println!("No workspace found, using installed path: {}", installed_path.display()); + } + installed_path + }; + + println!("Looking for emulator-asm at: {}", emulator_asm_path.display()); + + if !emulator_asm_path.exists() { + anyhow::bail!("emulator-asm directory not found at: {}", emulator_asm_path.display()); + } + + let emulator_parent = + emulator_asm_path.parent().context("Failed to get parent directory of emulator-asm")?; + let ziskclib_path = emulator_parent.join("ziskclib"); + let target_lib_path = emulator_parent.join("target/release/libziskclib.a"); + + if ziskclib_path.exists() { + if verbose { + println!("Found ziskclib at: {}", ziskclib_path.display()); + println!("Building ziskclib..."); + } + + let output = Command::new("cargo") + .args(["build", "--release", "-p", "ziskclib"]) + .current_dir(emulator_parent) + .output() + .context("Failed to execute cargo build for ziskclib")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + anyhow::bail!("Failed to build ziskclib:\nstdout: {}\nstderr: {}", stdout, stderr); + } + + if !target_lib_path.exists() { + anyhow::bail!( + "ziskclib build succeeded but library not found at: {}", + target_lib_path.display() + ); + } + + if verbose { + println!("ziskclib built successfully at: {}", target_lib_path.display()); + } + } else { + if !target_lib_path.exists() { + anyhow::bail!( + "libziskclib.a not found at: {}\nziskclib directory not found at: {}\nCannot build or locate ziskclib library", + target_lib_path.display(), + ziskclib_path.display() + ); + } + if verbose { + println!("Using existing ziskclib at: {}", target_lib_path.display()); + } + } + + Ok(emulator_asm_path) +} + +/// Get the paths to all assembly binary files for a given ELF and output path +pub fn get_assembly_file_paths( + elf: &Path, + output_path: &Path, + hints: bool, +) -> Result> { let elf_hash = get_elf_data_hash_from_path(elf)?; let stem = elf @@ -35,7 +140,13 @@ pub fn assembly_files_exist(elf: &Path, output_path: &Path, hints: bool) -> Resu let bin_mo_file = format!("{file_stem}-mo.bin"); let bin_mo_file = base_path.with_file_name(bin_mo_file); - Ok(bin_mt_file.exists() && bin_rh_file.exists() && bin_mo_file.exists()) + Ok(vec![bin_mt_file, bin_rh_file, bin_mo_file]) +} + +/// Check if all assembly binary files exist for a given ELF and output path +pub fn assembly_files_exist(elf: &Path, output_path: &Path, hints: bool) -> Result { + let files = get_assembly_file_paths(elf, output_path, hints)?; + Ok(files.iter().all(|f| f.exists())) } pub fn gen_assembly( @@ -94,44 +205,7 @@ pub fn generate_assembly( let bin_mo_file = base_path.with_file_name(bin_mo_file); let installed_path = crate::get_default_zisk_path().join("emulator-asm"); - - // Only check workspace if we're running via `cargo run` or `cargo build` - // Check if the current executable is in the target directory (development mode) - let is_development = std::env::current_exe() - .ok() - .and_then(|exe| exe.to_str().map(|s| s.contains("/target/"))) - .unwrap_or(false); - - let workspace_path = if is_development { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).parent().and_then(|workspace_root| { - let cargo_toml = workspace_root.join("Cargo.toml"); - let emulator_path = workspace_root.join("emulator-asm"); - if emulator_path.exists() && cargo_toml.exists() { - Some(emulator_path) - } else { - None - } - }) - } else { - None - }; - - let (emulator_asm_path, source) = if let Some(ws_path) = workspace_path { - (ws_path, "workspace") - } else if installed_path.exists() { - (installed_path, "installed") - } else { - (installed_path, "installed (not found)") - }; - - println!("Looking for emulator-asm at: {} ({})", emulator_asm_path.display(), source); - - if !emulator_asm_path.exists() { - anyhow::bail!( - "emulator-asm directory not found. Expected at: {}", - emulator_asm_path.display() - ); - } + let emulator_asm_path = resolve_emulator_asm(installed_path, verbose)?; let emulator_asm_path = emulator_asm_path.to_str().context("Failed to convert emulator-asm path to string")?; diff --git a/ziskbuild/src/build.rs b/ziskbuild/src/build.rs index a23a53484..df9987efe 100644 --- a/ziskbuild/src/build.rs +++ b/ziskbuild/src/build.rs @@ -3,7 +3,7 @@ use crate::{ ZISK_TARGET, }; use cargo_metadata::camino::Utf8PathBuf; -use rom_setup::{assembly_files_exist, gen_assembly, get_output_path}; +use rom_setup::{assembly_files_exist, gen_assembly, get_assembly_file_paths, get_output_path}; use std::{ io::{BufRead, BufReader}, path::PathBuf, @@ -117,6 +117,12 @@ pub fn execute_build_program( gen_assembly(elf_path_std, &None, hints, true)?; std::fs::write(&hints_marker, new_value)?; } + + // Tell cargo to rerun if any assembly file is deleted + let assembly_files = get_assembly_file_paths(elf_path_std, &output_path, hints)?; + for asm_file in assembly_files { + println!("cargo:rerun-if-changed={}", asm_file.display()); + } } if let Some(output_directory) = &args.output_directory { From deb6c79a0f2071731a796fa18b3407dfb3cce129 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 23 Feb 2026 15:43:44 +0100 Subject: [PATCH 573/782] Fix riscof assembly test --- emulator-asm/src/main.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index eb1762373..b110ebad3 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4263,20 +4263,23 @@ void server_run (void) exit(-1); } - // Sync control input shared memory - if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + if (precompile_results_enabled) + { + // Sync control input shared memory + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - // Sync precompile shared memory - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } } /*******/ From f671035fce854ffd3f658f5142f1c4f875ae1ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:55:34 +0000 Subject: [PATCH 574/782] Cleaning up mem pils --- pil/zisk.pil | 4 ++-- state-machines/mem/pil/mem.pil | 6 +++--- state-machines/mem/pil/mem_align.pil | 8 ++++---- state-machines/mem/pil/mem_align_rom.pil | 13 ++----------- 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/pil/zisk.pil b/pil/zisk.pil index f9eed8b35..a7138cedd 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -78,9 +78,9 @@ airgroup Zisk { // Memory Mem(N: 2**22, base_address: 0xA000_0000, size_mb: 512, large_mem: 1, dual_mem: 1); Mem(N: 2**21, base_address: 0x8000_0000, size_mb: 128, immutable: 1, enable_flag: enable_rom_data) alias RomData; - Mem(N: 2**21, base_address: 0x9000_0000, size_mb: 128, free_input_mem: 1, enable_flag: enable_input_data, use_predefined_ranges: 0) alias InputData; + Mem(N: 2**21, base_address: 0x9000_0000, size_mb: 128, free_input_mem: 1, enable_flag: enable_input_data) alias InputData; - MemAlign(N: 2**21, use_predefined_ranges: 0); + MemAlign(N: 2**21); MemAlignByte(N: 2**22, read: 1, write: 1); MemAlignByte(N: 2**22, read: 1, write: 0) alias MemAlignReadByte; MemAlignByte(N: 2**22, read: 0, write: 1) alias MemAlignWriteByte; diff --git a/state-machines/mem/pil/mem.pil b/state-machines/mem/pil/mem.pil index d7baa9076..0c142b4f4 100644 --- a/state-machines/mem/pil/mem.pil +++ b/state-machines/mem/pil/mem.pil @@ -80,7 +80,7 @@ const int MAX_RANGE_CHECK_CHUNK = 2**16; airtemplate Mem(const int N = 2**21, const int id = MEMORY_ID, const int RC = 2, const int bytes = 8, const int base_address = 0, const int size_mb = 128, int immutable = 0, - const int free_input_mem = 0, const expr enable_flag = 1, const int use_predefined_ranges = 0, + const int free_input_mem = 0, const expr enable_flag = 1, const int large_mem = 0, const int dual_mem = 0, const int continuous_addresses = 0) { col fixed SEGMENT_L1 = [1,0...]; @@ -149,8 +149,8 @@ airtemplate Mem(const int N = 2**21, const int id = MEMORY_ID, const int RC = 2, value[index] = value_word[index*2] + MAX_RANGE_CHECK_CHUNK * value_word[index*2 + 1]; // how value is a free-input, must be checked that it's 32-bit well formed value - range_check(value_word[index*2], 0, MAX_RANGE_CHECK_CHUNK - 1, predefined: use_predefined_ranges); - range_check(value_word[index*2+1], 0, MAX_RANGE_CHECK_CHUNK - 1, predefined: use_predefined_ranges); + range_check(value_word[index*2], 0, MAX_RANGE_CHECK_CHUNK - 1); + range_check(value_word[index*2+1], 0, MAX_RANGE_CHECK_CHUNK - 1); } } else { col witness bits(32) air.value[RC]; diff --git a/state-machines/mem/pil/mem_align.pil b/state-machines/mem/pil/mem_align.pil index 650bcfcf1..2d0db39fc 100644 --- a/state-machines/mem/pil/mem_align.pil +++ b/state-machines/mem/pil/mem_align.pil @@ -30,7 +30,7 @@ require "mem_align_rom.pil" +---+---+---+---+ +---+===+===+---+ | 0 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | +---+---+---+---+ +---+===+===+---+ - |<- v ->| + |<- v ->| [R] In the first clock cycle, we perform an aligned read to w [W] In the second clock cycle, we compute an aligned write of v to w @@ -41,7 +41,7 @@ require "mem_align_rom.pil" +---+---+---+---+ +---+===+===+===+ +===+===+===+===+ +===+---+---+---+ | 0 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | | 0 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | +---+---+---+---+ +---+===+===+===+ +===+===+===+===+ +===+---+---+---+ - |<---------------- v ---------------->| + |<---------------- v ---------------->| [R] In the first clock cycle, we perform an aligned read to w1 [V] In the second clock cycle, we return the demanded value v from w1 and w2 @@ -89,7 +89,7 @@ require "mem_align_rom.pil" Notice that it is enough with 8 combinations. */ -airtemplate MemAlign(const int N = 2**10, const int RC = 2, const int CHUNK_NUM = 8, const int CHUNK_BITS = 8, const int use_predefined_ranges = 0) { +airtemplate MemAlign(const int N = 2**10, const int RC = 2, const int CHUNK_NUM = 8, const int CHUNK_BITS = 8) { const int CHUNKS_BY_RC = CHUNK_NUM / RC; col witness bits(29) addr; @@ -111,7 +111,7 @@ airtemplate MemAlign(const int N = 2**10, const int RC = 2, const int CHUNK_NUM // - 'reg == reg in transitions V <- W, W <- R, // in any case, sel_up_to_down,sel_down_to_up are 0 in [V] steps. for (int i = 0; i < CHUNK_NUM; i++) { - range_check(reg[i], 0, 2**CHUNK_BITS-1, predefined: use_predefined_ranges); + range_check(reg[i], 0, 2**CHUNK_BITS-1); (reg[i]' - reg[i]) * sel[i] * sel_up_to_down === 0; ('reg[i] - reg[i]) * sel[i] * sel_down_to_up === 0; diff --git a/state-machines/mem/pil/mem_align_rom.pil b/state-machines/mem/pil/mem_align_rom.pil index b198a5747..a926257dc 100644 --- a/state-machines/mem/pil/mem_align_rom.pil +++ b/state-machines/mem/pil/mem_align_rom.pil @@ -3,21 +3,11 @@ require "opids.pil" const int MEM_ALIGN_ROM_SIZE = P2_8; -airtemplate MemAlignRom(const int N = MEM_ALIGN_ROM_SIZE, const int CHUNK_NUM = 8, const int DEFAULT_OFFSET = 0, const int DEFAULT_WIDTH = 8, const int disable_fixed = 0) { +airtemplate MemAlignRom(const int N = MEM_ALIGN_ROM_SIZE, const int CHUNK_NUM = 8, const int DEFAULT_OFFSET = 0, const int DEFAULT_WIDTH = 8) { if (N < MEM_ALIGN_ROM_SIZE) { error(`N must be at least ${MEM_ALIGN_ROM_SIZE}, but N=${N} was provided`); } - col witness multiplicity; - - if (disable_fixed) { - col fixed _K = [0...]; - multiplicity * _K === 0; - - println("*** DISABLE_FIXED ***"); - return; - } - // Define the size of each sub-program: RV, RWV, RVR, RWVWR const int spsize[4] = [2, 3, 3, 5]; @@ -319,5 +309,6 @@ airtemplate MemAlignRom(const int N = MEM_ALIGN_ROM_SIZE, const int CHUNK_NUM = } // Ensure the program is being followed by the MemAlign + col witness multiplicity; lookup_proves(MEMORY_ALIGN_ROM_ID, [PC, DELTA_PC, DELTA_ADDR, OFFSET, WIDTH, FLAGS], multiplicity); } \ No newline at end of file From 7f916ba68b0db8c67f310efcb0643f91a90bdc62 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Mon, 23 Feb 2026 15:16:45 +0000 Subject: [PATCH 575/782] Fix book --- book/getting_started/distributed_execution.md | 4 ++-- book/getting_started/precompiles.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/book/getting_started/distributed_execution.md b/book/getting_started/distributed_execution.md index 1153b75b5..d2b7e8573 100644 --- a/book/getting_started/distributed_execution.md +++ b/book/getting_started/distributed_execution.md @@ -449,7 +449,7 @@ The `--compute-capacity` flag indicates the total compute units required to gene | `--coordinator-url` | - | String | http://127.0.0.1:50051 | URL of the coordinator to send the request to | | `--data-id` | - | String | Auto (from filename or UUID) | Custom identifier for the proof job | | `--hints-uri` | - | String | - | Path/URI to the precompile hints source | -| `--stream-hints` | - | Boolean | false | Stream hints from the coordinator to workers via gRPC (see [Precompile Hints](../book/getting_started/precompile_hints.md)) | +| `--stream-hints` | - | Boolean | false | Stream hints from the coordinator to workers via gRPC (see [Precompile Hints](precompile_hints.md)) | | `--direct-inputs` | `-x` | Boolean | false | Send input data inline via gRPC instead of as a file path | | `--minimal-compute-capacity` | `-m` | Number | Same as `--compute-capacity` | Minimum acceptable compute capacity (allows partial worker allocation) | | `--simulated-node` | - | Number | - | Simulated node ID (for testing) | @@ -464,7 +464,7 @@ The `prove` subcommand supports two modes for delivering inputs and hints to wor **Hints modes** (controlled by `--hints-uri` and `--stream-hints`): - **Path mode** (default): The coordinator sends the hints URI to workers. Each worker loads hints from the specified path independently. -- **Streaming mode** (`--stream-hints`): The coordinator reads hints from the URI and broadcasts them to all workers in real-time via gRPC. See the [Precompile Hints documentation](../book/getting_started/precompile_hints.md) for details. +- **Streaming mode** (`--stream-hints`): The coordinator reads hints from the URI and broadcasts them to all workers in real-time via gRPC. See the [Precompile Hints documentation](precompile_hints.md) for details. **Examples:** ```bash diff --git a/book/getting_started/precompiles.md b/book/getting_started/precompiles.md index f6c501c09..043c6de19 100644 --- a/book/getting_started/precompiles.md +++ b/book/getting_started/precompiles.md @@ -19,7 +19,7 @@ Below is a summary of the precompiles currently available in ZisK: - [syscall_arith256](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/arith256.rs): Multiplication followed by addition over 256-bit non-negative integers. - [syscall_arith256_mod](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/arith256_mod.rs): Modular multiplication followed by addition over 256-bit non-negative integers. - [syscall_arith384_mod](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/arith384_mod.rs): Modular multiplication followed by addition over 256-bit non-negative integers. -- [syscall_keccak_f](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/keccakf.rs): Keccak-f[1600] permutation function from the [Keccak](https://keccak.team/files/Keccak-reference-3.0.pdf) cryptographic sponge construction. +- [syscall_keccak_f](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/keccakf.rs): Keccak-f 1600 permutation function from the [Keccak](https://keccak.team/files/Keccak-reference-3.0.pdf) cryptographic sponge construction. - [syscall_sha256_f](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/sha256f.rs): Extend and compress function of the [SHA-256](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) cryptographic hash algorithm. - [syscall_syscall_poseidon2](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/poseidon2.rs): Compression function of the [Poseidon2](https://eprint.iacr.org/2023/323.pdf) cryptographic hash algorithm. - [syscall_secp256k1_add](https://github.com/0xPolygonHermez/zisk/tree/main/ziskos/entrypoint/src/syscalls/secp256k1_add.rs): Elliptic curve point addition over the [Secp256k1](https://en.bitcoin.it/wiki/Secp256k1) curve. From 8b1ad3b909c6e9b00fe9209a3bd2107690b86c02 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 20 Feb 2026 20:07:58 +0000 Subject: [PATCH 576/782] First version fix --- cli/src/commands/execute.rs | 4 +-- cli/src/commands/prove.rs | 4 +-- cli/src/commands/stats.rs | 4 +-- cli/src/commands/verify_constraints.rs | 4 +-- common/src/io/stdin/zisk_stdin.rs | 25 +++++++++++++++---- common/src/io/stream/zisk_stream.rs | 5 +++- .../crates/coordinator/src/coordinator.rs | 4 +-- distributed/crates/worker/src/worker.rs | 24 ++++++------------ executor/src/asm_resources.rs | 2 +- executor/src/emu_asm.rs | 11 ++++---- executor/src/emu_asm_stub.rs | 4 --- executor/src/executor.rs | 12 ++------- executor/src/lib.rs | 9 ------- executor/src/rom_executor.rs | 11 +------- sdk/src/prover/asm.rs | 6 +---- sdk/src/prover/backend.rs | 12 +-------- sdk/src/prover/emu.rs | 6 +---- sdk/src/prover/mod.rs | 11 +------- 18 files changed, 55 insertions(+), 103 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 5734ab5a9..5899f20f7 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -139,7 +139,7 @@ impl ZiskExecute { pub fn run_asm( &mut self, - stdin: ZiskStdin, + mut stdin: ZiskStdin, hints_stream: Option, ) -> Result { let prover = ProverClient::builder() @@ -158,7 +158,7 @@ impl ZiskExecute { let elf = ElfBinaryFromFile::new(&self.elf, hints_stream.is_some())?; let (pk, _) = prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { - prover.set_hints_stream(hints_stream)?; + stdin.set_hints_stream(hints_stream); } prover.execute(&pk, stdin) } diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index af5617ecd..f734523b7 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -256,7 +256,7 @@ impl ZiskProve { pub fn run_asm( &mut self, - stdin: ZiskStdin, + mut stdin: ZiskStdin, hints_stream: Option, gpu_params: Option, ) -> Result<(ZiskProveResult, i32)> { @@ -289,7 +289,7 @@ impl ZiskProve { }; if let Some(hints_stream) = hints_stream { - prover.set_hints_stream(hints_stream)?; + stdin.set_hints_stream(hints_stream); } let world_rank = prover.world_rank(); diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index f59cde91e..efa14d5ac 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -177,7 +177,7 @@ impl ZiskStats { pub fn run_asm( &mut self, - stdin: ZiskStdin, + mut stdin: ZiskStdin, hints_stream: Option, ) -> Result<(i32, i32, Option)> { let prover = ProverClient::builder() @@ -197,7 +197,7 @@ impl ZiskStats { let (pk, _) = prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { - prover.set_hints_stream(hints_stream)?; + stdin.set_hints_stream(hints_stream); } let mpi_node = self.mpi_node.map(|n| n as u32); prover.stats(&pk, stdin, self.debug.clone(), self.minimal_memory, mpi_node) diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 8148b20f0..455e1df4d 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -152,7 +152,7 @@ impl ZiskVerifyConstraints { pub fn run_asm( &mut self, - stdin: ZiskStdin, + mut stdin: ZiskStdin, hints_stream: Option, ) -> Result { let prover = ProverClient::builder() @@ -172,7 +172,7 @@ impl ZiskVerifyConstraints { let (pk, _) = prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { - prover.set_hints_stream(hints_stream)?; + stdin.set_hints_stream(hints_stream); } prover.verify_constraints_debug(&pk, stdin, self.debug.clone()) } diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index 531c5b309..c01f53c67 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -1,4 +1,4 @@ -use crate::io::{ZiskFileStdin, ZiskMemoryStdin, ZiskNullStdin}; +use crate::io::{StreamSource, ZiskFileStdin, ZiskMemoryStdin, ZiskNullStdin}; use anyhow::Result; use serde::{de::DeserializeOwned, Serialize}; use std::path::Path; @@ -93,6 +93,7 @@ impl ZiskIO for ZiskIOVariant { #[derive(Clone)] pub struct ZiskStdin { io: Arc, + hints_stream: Option>, } impl ZiskIO for ZiskStdin { @@ -134,21 +135,27 @@ impl Default for ZiskStdin { impl ZiskStdin { /// Create new memory-based stdin pub fn new() -> Self { - Self { io: Arc::new(ZiskIOVariant::Memory(ZiskMemoryStdin::new(Vec::new()))) } + Self { + io: Arc::new(ZiskIOVariant::Memory(ZiskMemoryStdin::new(Vec::new()))), + hints_stream: None, + } } /// Create a null stdin (no input) pub fn null() -> Self { - Self { io: Arc::new(ZiskIOVariant::Null(ZiskNullStdin)) } + Self { io: Arc::new(ZiskIOVariant::Null(ZiskNullStdin)), hints_stream: None } } /// Create a file-based stdin pub fn from_file>(path: P) -> Result { - Ok(Self { io: Arc::new(ZiskIOVariant::File(ZiskFileStdin::new(path)?)) }) + Ok(Self { + io: Arc::new(ZiskIOVariant::File(ZiskFileStdin::new(path)?)), + hints_stream: None, + }) } pub fn from_vec(data: Vec) -> Self { - Self { io: Arc::new(ZiskIOVariant::Memory(ZiskMemoryStdin::new(data))) } + Self { io: Arc::new(ZiskIOVariant::Memory(ZiskMemoryStdin::new(data))), hints_stream: None } } /// Create a ZiskStdin from a URI string @@ -177,4 +184,12 @@ impl ZiskStdin { ZiskStdin::from_file(uri.as_str()) } } + + pub fn set_hints_stream(&mut self, stream: StreamSource) { + self.hints_stream = Some(Arc::new(stream)); + } + + pub fn take_hints_stream(&mut self) -> Option> { + self.hints_stream.take() + } } diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index 2c07b2f98..9783b03b3 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -88,7 +88,10 @@ impl ZiskStream { /// /// # Arguments /// * `stream` - The new StreamSource source for reading hints. - pub fn set_hints_stream_src(&mut self, mut stream: StreamSource) -> Result<()> { + pub fn set_hints_stream_src(&mut self, stream: Arc) -> Result<()> { + let mut stream = Arc::try_unwrap(stream).map_err(|_| { + anyhow::anyhow!("Cannot take ownership of StreamSource: multiple references exist") + })?; if !stream.is_active() { // Stop the existing thread if running self.stop_thread(); diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index e66f932b9..f598824e7 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -785,12 +785,12 @@ impl Coordinator { }; let hints_relay = PrecompileHintsRelay::new(dispatcher); let mut stream = ZiskStream::new(hints_relay); - let stream_reader = StreamSource::from_uri(hints_uri).map_err(|e| { + let stream_reader = Arc::new(StreamSource::from_uri(hints_uri).map_err(|e| { CoordinatorError::Internal(format!( "Failed to create hints stream reader for job {}: {}", job.job_id, e )) - })?; + })?); stream.set_hints_stream_src(stream_reader).map_err(|e| { CoordinatorError::Internal(format!( "Failed to set hints stream for job {}: {}", diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 6c987702f..48c278e4b 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -549,26 +549,16 @@ impl Worker { ) -> Result> { let phase = proofman::ProvePhase::Contributions; - match input_source { - InputSourceDto::InputPath(inputs_uri) => { - let stdin = ZiskStdin::from_file(inputs_uri)?; - - prover.set_stdin(stdin)?; - } - InputSourceDto::InputData(input_data) => { - let stdin = ZiskStdin::from_vec(input_data); - prover.set_stdin(stdin)?; - } - InputSourceDto::InputNull => { - let stdin = ZiskStdin::null(); - prover.set_stdin(stdin)?; - } - } + let mut stdin = match input_source { + InputSourceDto::InputPath(inputs_uri) => ZiskStdin::from_file(inputs_uri)?, + InputSourceDto::InputData(input_data) => ZiskStdin::from_vec(input_data), + InputSourceDto::InputNull => ZiskStdin::null(), + }; match hints_source { HintsSourceDto::HintsPath(hints_uri) => { let hints_stream = StreamSource::from_uri(hints_uri)?; - prover.set_hints_stream(hints_stream)?; + stdin.set_hints_stream(hints_stream); } HintsSourceDto::HintsStream(_hints_uri) => { // For HintsStream, the worker will receive hint data via stream_data messages @@ -580,6 +570,8 @@ impl Worker { } } + prover.set_stdin(stdin)?; + prover.register_program(pk)?; let challenge = match prover.prove_phase(phase_inputs, options, phase) { diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index b1e5c8b1f..0e6c40879 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -113,7 +113,7 @@ impl AsmResources { } } - pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { + pub fn set_hints_stream_src(&self, stream: Arc) -> Result<()> { if let Some(hints_stream) = &self.hints_stream { hints_stream.lock().unwrap().set_hints_stream_src(stream) } else { diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index be8d48c49..2207febed 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -8,7 +8,6 @@ use crate::AsmResources; use crate::{ DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, MAX_NUM_STEPS, }; -use anyhow::Result; use asm_runner::{ shmem_input_name, write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmService, AsmServices, SharedMemoryWriter, @@ -17,7 +16,6 @@ use data_bus::DataBusTrait; use fields::PrimeField64; use proofman_common::ProofCtx; use sm_rom::RomSM; -use zisk_common::io::StreamSource; use zisk_common::{ io::ZiskStdin, stats_begin, stats_end, ChunkId, EmuTrace, ExecutorStatsHandle, StatsScope, }; @@ -73,10 +71,6 @@ impl EmulatorAsm { *self.asm_resources.lock().unwrap() = Some(asm_resources); } - pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { - self.asm_resources.lock().unwrap().as_ref().unwrap().set_hints_stream_src(stream) - } - pub fn reset_hints_stream(&self) { self.asm_resources.lock().unwrap().as_ref().unwrap().reset(); } @@ -119,6 +113,11 @@ impl EmulatorAsm { let asm_resources = asm_resources_guard.as_ref().expect("AsmResources not initialized"); if use_hints { + let hints_stream = + stdin.lock().unwrap().take_hints_stream().expect("Hints stream not set"); + asm_resources + .set_hints_stream_src(hints_stream) + .expect("Failed to set hints stream source"); asm_resources.start_stream().expect("Failed to start hints stream"); } diff --git a/executor/src/emu_asm_stub.rs b/executor/src/emu_asm_stub.rs index 92abaf37c..c7b4c1458 100644 --- a/executor/src/emu_asm_stub.rs +++ b/executor/src/emu_asm_stub.rs @@ -59,10 +59,6 @@ impl EmulatorAsm { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); } - pub fn set_hints_stream_src(&self, _stream: StreamSource) -> Result<()> { - unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); - } - pub fn reset_hints_stream(&self) { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 8edc977c8..cb058943b 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -33,9 +33,8 @@ use std::{ }; use witness::WitnessComponent; use zisk_common::{ - io::{StreamSource, ZiskStdin}, - stats_begin, stats_end, BusDeviceMetrics, ChunkId, ExecutorStatsHandle, StatsCostPerType, - StatsType, ZiskExecutionResult, + io::ZiskStdin, stats_begin, stats_end, BusDeviceMetrics, ChunkId, ExecutorStatsHandle, + StatsCostPerType, StatsType, ZiskExecutionResult, }; use zisk_core::ZiskRom; use zisk_pil::ZiskPublicValues; @@ -43,8 +42,6 @@ use zisk_pil::{ SPECIFIED_RANGES_AIR_IDS, VIRTUAL_TABLE_0_AIR_IDS, VIRTUAL_TABLE_1_AIR_IDS, ZISK_AIRGROUP_ID, }; -use anyhow::Result; - pub type DeviceMetricsByChunk = (ChunkId, Box); // (chunk_id, metrics) /// The maximum number of steps to execute in the emulator or assembly runner. @@ -113,11 +110,6 @@ impl ZiskExecutor { self.rom_executor.set_asm_resources(asm_resources); } - /// Sets the hints stream source. - pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { - self.rom_executor.set_hints_stream_src(stream) - } - /// Gets the execution result and stats. #[allow(clippy::type_complexity)] pub fn get_execution_result(&self) -> (ZiskExecutionResult, ExecutorStatsHandle) { diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 625e01dee..aa47d4478 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -17,7 +17,6 @@ mod static_data_bus; mod static_data_bus_collect; mod utils; -use anyhow::Result; mod witness_generator; mod witness_orchestrator; @@ -41,7 +40,6 @@ pub use static_data_bus_collect::*; pub use utils::*; use witness_generator::*; use witness_orchestrator::*; -use zisk_common::io::StreamSource; use zisk_core::ZiskRom; pub type DeviceMetricsList = Vec; @@ -101,13 +99,6 @@ impl EmulatorKind { }; } - pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { - match self { - Self::Asm(e) => e.set_hints_stream_src(stream), - Self::Rust(_) => Err(anyhow::anyhow!("Hints stream not supported in Rust emulator")), - } - } - pub fn reset_hints_stream(&self) { match self { Self::Asm(e) => e.reset_hints_stream(), diff --git a/executor/src/rom_executor.rs b/executor/src/rom_executor.rs index 80847d6e7..bcb5482f2 100644 --- a/executor/src/rom_executor.rs +++ b/executor/src/rom_executor.rs @@ -7,15 +7,11 @@ use crate::{ AsmResources, DeviceMetricsList, Emulator, EmulatorKind, NestedDeviceMetricsList, StaticSMBundle, }; -use anyhow::Result; use asm_runner::AsmRunnerMO; use fields::PrimeField64; use proofman_common::ProofCtx; use std::{sync::Mutex, thread::JoinHandle}; -use zisk_common::{ - io::{StreamSource, ZiskStdin}, - EmuTrace, ExecutorStatsHandle, StatsScope, -}; +use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; use zisk_core::ZiskRom; /// Output from ROM execution. @@ -59,11 +55,6 @@ impl RomExecutor { self.emulator.set_asm_resources(asm_resources); } - /// Sets the hints stream source. - pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { - self.emulator.set_hints_stream_src(stream) - } - /// Resets the hints stream if configured. pub fn reset_hints_stream(&self) { self.emulator.reset_hints_stream() diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 575ba2637..0e722fa43 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -16,7 +16,7 @@ use rom_setup::{generate_assembly, get_output_path, DEFAULT_CACHE_PATH}; use std::path::PathBuf; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; -use zisk_common::io::{StreamSource, ZiskStdin}; +use zisk_common::io::ZiskStdin; use zisk_common::ElfBinaryLike; use zisk_common::ExecutorStatsHandle; use zisk_core::Riscv2zisk; @@ -93,10 +93,6 @@ impl ProverEngine for AsmProver { self.core_prover.backend.register_program(pk) } - fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()> { - self.core_prover.backend.set_hints_stream(hints_stream) - } - fn executed_steps(&self) -> u64 { self.core_prover .backend diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index cb8cbbfc4..739d8e387 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -24,10 +24,7 @@ use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; use zisk_common::stats_mark; -use zisk_common::{ - io::{StreamSource, ZiskStdin}, - ElfBinaryLike, ExecutorStatsHandle, ZiskExecutionResult, -}; +use zisk_common::{io::ZiskStdin, ElfBinaryLike, ExecutorStatsHandle, ZiskExecutionResult}; pub(crate) struct ProverBackend { proofman: Option>, @@ -104,13 +101,6 @@ impl ProverBackend { Ok(()) } - pub fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()> { - let executor = self.executor.as_ref().ok_or_else(|| { - anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") - })?; - executor.set_hints_stream_src(hints_stream) - } - pub fn execution_result(&self) -> Result<(ZiskExecutionResult, ExecutorStatsHandle)> { let executor = self.executor.as_ref().ok_or_else(|| { anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index baf0f570c..71aeec4eb 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -11,7 +11,7 @@ use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, RowInfo}; use std::path::PathBuf; use std::sync::Arc; -use zisk_common::io::{StreamSource, ZiskStdin}; +use zisk_common::io::ZiskStdin; use zisk_common::ElfBinaryLike; use zisk_common::ExecutorStatsHandle; use zisk_core::Riscv2zisk; @@ -81,10 +81,6 @@ impl ProverEngine for EmuProver { self.core_prover.backend.register_program(pk) } - fn set_hints_stream(&self, _: StreamSource) -> Result<()> { - unreachable!("EMU prover does not support precompile hints"); - } - fn setup(&self, elf: &impl ElfBinaryLike) -> Result<(ZiskProgramPK, ZiskProgramVK)> { let pctx = self.core_prover.backend.get_pctx()?; diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 41a685618..1bae1c57d 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -25,10 +25,7 @@ use std::{ }; use tracing::info; use zisk_common::ElfBinaryLike; -use zisk_common::{ - io::{StreamSource, ZiskStdin}, - ExecutorStatsHandle, StatsCostPerType, ZiskExecutionResult, -}; +use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, StatsCostPerType, ZiskExecutionResult}; use zisk_core::ZiskRom; pub struct ZiskExecuteResult { @@ -720,8 +717,6 @@ pub trait ProverEngine { fn register_program(&self, pk: &ZiskProgramPK) -> Result<()>; - fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()>; - fn executed_steps(&self) -> u64; fn get_execution_info(&self) -> Result; @@ -844,10 +839,6 @@ impl ZiskProver { self.prover.register_program(pk) } - pub fn set_hints_stream(&self, hints_stream: StreamSource) -> Result<()> { - self.prover.set_hints_stream(hints_stream) - } - /// Get the world rank of the prover. The world rank is the rank of the prover in the global MPI context. /// If MPI is not used, this will always return 0. pub fn world_rank(&self) -> i32 { From b294bef0c0dacbcc1d98fb4b79ac8457c79489a7 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 20 Feb 2026 20:13:15 +0000 Subject: [PATCH 577/782] Moving Arc up --- common/src/io/stdin/zisk_stdin.rs | 8 ++++++-- common/src/io/stream/zisk_stream.rs | 5 +---- distributed/crates/coordinator/src/coordinator.rs | 4 ++-- executor/src/asm_resources.rs | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index c01f53c67..2343c2ba3 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -189,7 +189,11 @@ impl ZiskStdin { self.hints_stream = Some(Arc::new(stream)); } - pub fn take_hints_stream(&mut self) -> Option> { - self.hints_stream.take() + pub fn take_hints_stream(&mut self) -> Option { + self.hints_stream.take().map(|arc| { + Arc::try_unwrap(arc).unwrap_or_else(|_| { + panic!("StreamSource has multiple references") + }) + }) } } diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index 9783b03b3..2c07b2f98 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -88,10 +88,7 @@ impl ZiskStream { /// /// # Arguments /// * `stream` - The new StreamSource source for reading hints. - pub fn set_hints_stream_src(&mut self, stream: Arc) -> Result<()> { - let mut stream = Arc::try_unwrap(stream).map_err(|_| { - anyhow::anyhow!("Cannot take ownership of StreamSource: multiple references exist") - })?; + pub fn set_hints_stream_src(&mut self, mut stream: StreamSource) -> Result<()> { if !stream.is_active() { // Stop the existing thread if running self.stop_thread(); diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index f598824e7..e66f932b9 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -785,12 +785,12 @@ impl Coordinator { }; let hints_relay = PrecompileHintsRelay::new(dispatcher); let mut stream = ZiskStream::new(hints_relay); - let stream_reader = Arc::new(StreamSource::from_uri(hints_uri).map_err(|e| { + let stream_reader = StreamSource::from_uri(hints_uri).map_err(|e| { CoordinatorError::Internal(format!( "Failed to create hints stream reader for job {}: {}", job.job_id, e )) - })?); + })?; stream.set_hints_stream_src(stream_reader).map_err(|e| { CoordinatorError::Internal(format!( "Failed to set hints stream for job {}: {}", diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index 0e6c40879..b1e5c8b1f 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -113,7 +113,7 @@ impl AsmResources { } } - pub fn set_hints_stream_src(&self, stream: Arc) -> Result<()> { + pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { if let Some(hints_stream) = &self.hints_stream { hints_stream.lock().unwrap().set_hints_stream_src(stream) } else { From 9790055487511425efb807799a7716b020fe80d0 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 16 Feb 2026 18:25:19 +0100 Subject: [PATCH 578/782] Split assembly reset in fast/slow/trace --- emulator-asm/src/emu.c | 23 ++++++++-- emulator-asm/src/main.c | 96 +++++++++++++++++++++++++++++------------ 2 files changed, 89 insertions(+), 30 deletions(-) diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index 808293a07..cc18a03da 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -397,9 +397,10 @@ void precompile_cache_store( uint8_t* data, uint64_t size) fwrite(data, 1, size, precompile_file); fflush(precompile_file); #ifdef ASM_PRECOMPILE_CACHE_DEBUG + uint64_t previous_total_precompile_cache_size = total_precompile_cache_size; total_precompile_cache_size += size; total_precompile_cache_counter++; - printf("precompile_cache_store() Stored %lu bytes at pos=%lu total_precompile_cache_size=%lu total_precompile_cache_counter=%lu\n", size, ftell(precompile_file), total_precompile_cache_size, total_precompile_cache_counter); + printf("precompile_cache_store() Stored %lu bytes at pos=%lu file_size=%lu total_precompile_cache_size=%lu total_precompile_cache_counter=%lu\n", size, previous_total_precompile_cache_size, ftell(precompile_file), total_precompile_cache_size, total_precompile_cache_counter); #endif } @@ -500,7 +501,15 @@ extern int _opcode_keccak(uint64_t address) #ifdef ASM_CALL_METRICS if (emu_verbose) printf("opcode_keccak() calling keccakf1600_generic() counter=%lu address=%08lx\n", asm_call_metrics.keccak_counter, address); #else - if (emu_verbose) printf("opcode_keccak() calling keccakf1600_generic() address=%08lx\n", address); + if (emu_verbose) + { + printf("opcode_keccak() calling keccakf1600_generic() address=%08lx\n", address); + for (uint64_t i=0; i<200; i++) + { + printf("%02x", ((uint8_t *)address)[i]); + } + printf("\n"); + } #endif #endif @@ -523,7 +532,15 @@ extern int _opcode_keccak(uint64_t address) #endif #ifdef DEBUG - if (emu_verbose) printf("opcode_keccak() called keccakf1600_generic()\n"); + if (emu_verbose) + { + printf("opcode_keccak() called keccakf1600_generic()\n"); + for (uint64_t i=0; i<200; i++) + { + printf("%02x", ((uint8_t *)address)[i]); + } + printf("\n"); + } #endif #ifdef ASM_CALL_METRICS asm_call_metrics.keccak_counter++; diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index a665c6583..58820f399 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -256,7 +256,9 @@ uint64_t TimeDiff(const struct timeval startTime, const struct timeval endTime); void configure (void); void server_setup (void); -void server_reset (void); +void server_reset_fast (void); +void server_reset_slow (void); +void server_reset_trace (void); void server_run (void); void server_cleanup (void); @@ -635,7 +637,9 @@ int main(int argc, char *argv[]) server_setup(); // Reset the server, i.e. reset memory - server_reset(); + server_reset_fast(); + server_reset_slow(); + server_reset_trace(); // Create socket file descriptor int server_fd; @@ -766,6 +770,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_MT_RESPONSE; response[1] = (MEM_END && !MEM_ERROR) ? 0 : 1; response[2] = trace_size; @@ -795,6 +801,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_RH_RESPONSE; response[1] = MEM_END ? 0 : 1; response[2] = trace_size; @@ -825,6 +833,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_MO_RESPONSE; response[1] = MEM_END ? 0 : 1; response[2] = trace_size; @@ -855,6 +865,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_MA_RESPONSE; response[1] = MEM_END ? 0 : 1; response[2] = trace_size; @@ -888,6 +900,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_CM_RESPONSE; response[1] = 0; response[2] = trace_size; @@ -918,6 +932,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_FA_RESPONSE; response[1] = MEM_END ? 0 : 1; response[2] = 0; @@ -948,6 +964,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_MR_RESPONSE; response[1] = MEM_END ? 0 : 1; response[2] = trace_size; @@ -981,6 +999,8 @@ int main(int argc, char *argv[]) server_run(); + server_reset_fast(); + response[0] = TYPE_CA_RESPONSE; response[1] = 0; response[2] = trace_size; @@ -1033,7 +1053,7 @@ int main(int argc, char *argv[]) #endif if (bReset) { - server_reset(); + server_reset_slow(); } if (bShutdown) @@ -4011,10 +4031,14 @@ void server_setup (void) if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); } -void server_reset (void) +void server_reset_fast (void) { + // Reset precompile read address for next emulation if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) *precompile_read_address = 0; +} +void server_reset_slow (void) +{ // Reset RAM data for next emulation if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) { @@ -4027,32 +4051,16 @@ void server_reset (void) duration = TimeDiff(start_time, stop_time); if (verbose) printf("server_reset() memset(ram) in %lu us\n", duration); #endif - if ((gen_method != Fast) && (gen_method != RomHistogram)) - { - // Reset trace: init output header data - pOutputTrace[0] = 0x000100; // Version, e.g. v1.0.0 [8] - pOutputTrace[1] = 1; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - pOutputTrace[2] = trace_size; // MT allocated size [8] -> to be updated after reallocation - pOutputTrace[3] = 0; // MT used size [8] -> to be updated after completion - - // Reset trace used size - trace_used_size = 0; - } } } -void server_run (void) +void server_reset_trace (void) { - if ((gen_method == RomHistogram)) { - memset((void *)trace_address, 0, trace_size); - } - -#ifdef ASM_CALL_METRICS - reset_asm_call_metrics(); -#endif - - // Init trace header - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && (gen_method != Fast)) + // Reset RAM data for next emulation + if ( (gen_method != ChunkPlayerMTCollectMem) && + (gen_method != ChunkPlayerMemReadsCollectMain) && + (gen_method != Fast) && + (gen_method != RomHistogram) ) { // Reset trace: init output header data pOutputTrace[0] = 0x000100; // Version, e.g. v1.0.0 [8] @@ -4063,6 +4071,22 @@ void server_run (void) // Reset trace used size trace_used_size = 0; } +} + +void server_run (void) +{ + // If ROM histogram, reset the trace area to 0 for the histogram data since it represents the + // ROM instruction multiplicity and one of them will be increased at every executed instruction + if ((gen_method == RomHistogram)) { + memset((void *)trace_address, 0, trace_size); + } + +#ifdef ASM_CALL_METRICS + reset_asm_call_metrics(); +#endif + + // Init trace header + server_reset_trace(); // Sync input shared memory if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) { @@ -4435,8 +4459,22 @@ extern int _print_regs() // printf("\n"); } +//struct timeval print_pc_tv; extern int _print_pc (uint64_t pc, uint64_t c) { + // print_pc_counter++; + // { + // struct timeval tv; + // gettimeofday(&tv, NULL); + // uint64_t duration = TimeDiff(print_pc_tv, tv); + // if (duration > 900) + // { + // uint64_t chunk = print_pc_counter / chunk_size; + // printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); + // fflush(stdout); + // } + // print_pc_tv = tv; + // } printf("s=%lu pc=%lx c=%lx", print_pc_counter, pc, c); /* Used for debugging printf(" r0=%lx", reg_0); @@ -4489,8 +4527,12 @@ extern void _chunk_done() // struct timeval tv; // gettimeofday(&tv, NULL); // uint64_t duration = TimeDiff(chunk_done_tv, tv); + // if (duration > 5000) + // { + // printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); + // fflush(stdout); + // } // chunk_done_tv = tv; - // printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); // } //gettimeofday(&sync_start, NULL); __sync_synchronize(); From 297d3de16d0d5e74723e5437119e9665934b6cfc Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 16 Feb 2026 19:41:19 +0100 Subject: [PATCH 579/782] Fix copilot in emu.c --- emulator-asm/src/emu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index cc18a03da..74eecf609 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -506,7 +506,7 @@ extern int _opcode_keccak(uint64_t address) printf("opcode_keccak() calling keccakf1600_generic() address=%08lx\n", address); for (uint64_t i=0; i<200; i++) { - printf("%02x", ((uint8_t *)address)[i]); + printf("%02x", ((uint8_t *)(uintptr_t)address)[i]); } printf("\n"); } @@ -537,7 +537,7 @@ extern int _opcode_keccak(uint64_t address) printf("opcode_keccak() called keccakf1600_generic()\n"); for (uint64_t i=0; i<200; i++) { - printf("%02x", ((uint8_t *)address)[i]); + printf("%02x", ((uint8_t *)(uintptr_t)address)[i]); } printf("\n"); } From f8896f83a87946985d1597fa0050ef50d076c77f Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 16 Feb 2026 20:43:31 +0100 Subject: [PATCH 580/782] Change commented code by conditional precompilation in main.c --- emulator-asm/src/main.c | 251 +++++++++++++++++++++++----------------- 1 file changed, 142 insertions(+), 109 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 58820f399..dddc64f26 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4382,101 +4382,116 @@ void server_cleanup (void) } } -// extern uint64_t reg_0; -// extern uint64_t reg_1; -// extern uint64_t reg_2; -// extern uint64_t reg_3; -// extern uint64_t reg_4; -// extern uint64_t reg_5; -// extern uint64_t reg_6; -// extern uint64_t reg_7; -// extern uint64_t reg_8; -// extern uint64_t reg_9; -// extern uint64_t reg_10; -// extern uint64_t reg_11; -// extern uint64_t reg_12; -// extern uint64_t reg_13; -// extern uint64_t reg_14; -// extern uint64_t reg_15; -// extern uint64_t reg_16; -// extern uint64_t reg_17; -// extern uint64_t reg_18; -// extern uint64_t reg_19; -// extern uint64_t reg_20; -// extern uint64_t reg_21; -// extern uint64_t reg_22; -// extern uint64_t reg_23; -// extern uint64_t reg_24; -// extern uint64_t reg_25; -// extern uint64_t reg_26; -// extern uint64_t reg_27; -// extern uint64_t reg_28; -// extern uint64_t reg_29; -// extern uint64_t reg_30; -// extern uint64_t reg_31; -// extern uint64_t reg_32; -// extern uint64_t reg_33; -// extern uint64_t reg_34; +//#define PRINT_REGS +#ifdef PRINT_REGS +extern uint64_t reg_0; +extern uint64_t reg_1; +extern uint64_t reg_2; +extern uint64_t reg_3; +extern uint64_t reg_4; +extern uint64_t reg_5; +extern uint64_t reg_6; +extern uint64_t reg_7; +extern uint64_t reg_8; +extern uint64_t reg_9; +extern uint64_t reg_10; +extern uint64_t reg_11; +extern uint64_t reg_12; +extern uint64_t reg_13; +extern uint64_t reg_14; +extern uint64_t reg_15; +extern uint64_t reg_16; +extern uint64_t reg_17; +extern uint64_t reg_18; +extern uint64_t reg_19; +extern uint64_t reg_20; +extern uint64_t reg_21; +extern uint64_t reg_22; +extern uint64_t reg_23; +extern uint64_t reg_24; +extern uint64_t reg_25; +extern uint64_t reg_26; +extern uint64_t reg_27; +extern uint64_t reg_28; +extern uint64_t reg_29; +extern uint64_t reg_30; +extern uint64_t reg_31; +extern uint64_t reg_32; +extern uint64_t reg_33; +extern uint64_t reg_34; +#endif extern int _print_regs() { - // printf("print_regs()\n"); - // printf("\treg[ 0]=%lu=0x%lx=@%p\n", reg_0, reg_0, ®_0); - // //printf("\treg[ 1]=%lu=0x%lx=@%p\n", reg_1, reg_1, ®_1); - // //printf("\treg[ 2]=%lu=0x%lx=@%p\n", reg_2, reg_2, ®_2); - // printf("\treg[ 3]=%lu=0x%lx=@%p\n", reg_3, reg_3, ®_3); - // printf("\treg[ 4]=%lu=0x%lx=@%p\n", reg_4, reg_4, ®_4); - // /*printf("\treg[ 5]=%lu=0x%lx=@%p\n", reg_5, reg_5, ®_5); - // printf("\treg[ 6]=%lu=0x%lx=@%p\n", reg_6, reg_6, ®_6); - // printf("\treg[ 7]=%lu=0x%lx=@%p\n", reg_7, reg_7, ®_7); - // printf("\treg[ 8]=%lu=0x%lx=@%p\n", reg_8, reg_8, ®_8); - // printf("\treg[ 9]=%lu=0x%lx=@%p\n", reg_9, reg_9, ®_9); - // printf("\treg[10]=%lu=0x%lx=@%p\n", reg_10, reg_10, ®_10); - // printf("\treg[11]=%lu=0x%lx=@%p\n", reg_11, reg_11, ®_11); - // printf("\treg[12]=%lu=0x%lx=@%p\n", reg_12, reg_12, ®_12); - // printf("\treg[13]=%lu=0x%lx=@%p\n", reg_13, reg_13, ®_13); - // printf("\treg[14]=%lu=0x%lx=@%p\n", reg_14, reg_14, ®_14); - // printf("\treg[15]=%lu=0x%lx=@%p\n", reg_15, reg_15, ®_15); - // printf("\treg[16]=%lu=0x%lx=@%p\n", reg_16, reg_16, ®_16); - // printf("\treg[17]=%lu=0x%lx=@%p\n", reg_17, reg_17, ®_17); - // printf("\treg[18]=%lu=0x%lx=@%p\n", reg_18, reg_18, ®_18);*/ - // printf("\treg[19]=%lu=0x%lx=@%p\n", reg_19, reg_19, ®_19); - // printf("\treg[20]=%lu=0x%lx=@%p\n", reg_20, reg_20, ®_20); - // printf("\treg[21]=%lu=0x%lx=@%p\n", reg_21, reg_21, ®_21); - // printf("\treg[22]=%lu=0x%lx=@%p\n", reg_22, reg_22, ®_22); - // printf("\treg[23]=%lu=0x%lx=@%p\n", reg_23, reg_23, ®_23); - // printf("\treg[24]=%lu=0x%lx=@%p\n", reg_24, reg_24, ®_24); - // printf("\treg[25]=%lu=0x%lx=@%p\n", reg_25, reg_25, ®_25); - // printf("\treg[26]=%lu=0x%lx=@%p\n", reg_26, reg_26, ®_26); - // printf("\treg[27]=%lu=0x%lx=@%p\n", reg_27, reg_27, ®_27); - // printf("\treg[28]=%lu=0x%lx=@%p\n", reg_28, reg_28, ®_28); - // printf("\treg[29]=%lu=0x%lx=@%p\n", reg_29, reg_29, ®_29); - // printf("\treg[30]=%lu=0x%lx=@%p\n", reg_30, reg_30, ®_30); - // printf("\treg[31]=%lu=0x%lx=@%p\n", reg_31, reg_31, ®_31); - // printf("\treg[32]=%lu=0x%lx=@%p\n", reg_32, reg_32, ®_32); - // printf("\treg[33]=%lu=0x%lx=@%p\n", reg_33, reg_33, ®_33); - // printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); - // printf("\n"); +#ifdef PRINT_REGS + printf("print_regs()\n"); + printf("\treg[ 0]=%lu=0x%lx=@%p\n", reg_0, reg_0, ®_0); + printf("\treg[ 1]=%lu=0x%lx=@%p\n", reg_1, reg_1, ®_1); + printf("\treg[ 2]=%lu=0x%lx=@%p\n", reg_2, reg_2, ®_2); + printf("\treg[ 3]=%lu=0x%lx=@%p\n", reg_3, reg_3, ®_3); + printf("\treg[ 4]=%lu=0x%lx=@%p\n", reg_4, reg_4, ®_4); + printf("\treg[ 5]=%lu=0x%lx=@%p\n", reg_5, reg_5, ®_5); + printf("\treg[ 6]=%lu=0x%lx=@%p\n", reg_6, reg_6, ®_6); + printf("\treg[ 7]=%lu=0x%lx=@%p\n", reg_7, reg_7, ®_7); + printf("\treg[ 8]=%lu=0x%lx=@%p\n", reg_8, reg_8, ®_8); + printf("\treg[ 9]=%lu=0x%lx=@%p\n", reg_9, reg_9, ®_9); + printf("\treg[10]=%lu=0x%lx=@%p\n", reg_10, reg_10, ®_10); + printf("\treg[11]=%lu=0x%lx=@%p\n", reg_11, reg_11, ®_11); + printf("\treg[12]=%lu=0x%lx=@%p\n", reg_12, reg_12, ®_12); + printf("\treg[13]=%lu=0x%lx=@%p\n", reg_13, reg_13, ®_13); + printf("\treg[14]=%lu=0x%lx=@%p\n", reg_14, reg_14, ®_14); + printf("\treg[15]=%lu=0x%lx=@%p\n", reg_15, reg_15, ®_15); + printf("\treg[16]=%lu=0x%lx=@%p\n", reg_16, reg_16, ®_16); + printf("\treg[17]=%lu=0x%lx=@%p\n", reg_17, reg_17, ®_17); + printf("\treg[18]=%lu=0x%lx=@%p\n", reg_18, reg_18, ®_18); + printf("\treg[19]=%lu=0x%lx=@%p\n", reg_19, reg_19, ®_19); + printf("\treg[20]=%lu=0x%lx=@%p\n", reg_20, reg_20, ®_20); + printf("\treg[21]=%lu=0x%lx=@%p\n", reg_21, reg_21, ®_21); + printf("\treg[22]=%lu=0x%lx=@%p\n", reg_22, reg_22, ®_22); + printf("\treg[23]=%lu=0x%lx=@%p\n", reg_23, reg_23, ®_23); + printf("\treg[24]=%lu=0x%lx=@%p\n", reg_24, reg_24, ®_24); + printf("\treg[25]=%lu=0x%lx=@%p\n", reg_25, reg_25, ®_25); + printf("\treg[26]=%lu=0x%lx=@%p\n", reg_26, reg_26, ®_26); + printf("\treg[27]=%lu=0x%lx=@%p\n", reg_27, reg_27, ®_27); + printf("\treg[28]=%lu=0x%lx=@%p\n", reg_28, reg_28, ®_28); + printf("\treg[29]=%lu=0x%lx=@%p\n", reg_29, reg_29, ®_29); + printf("\treg[30]=%lu=0x%lx=@%p\n", reg_30, reg_30, ®_30); + printf("\treg[31]=%lu=0x%lx=@%p\n", reg_31, reg_31, ®_31); + printf("\treg[32]=%lu=0x%lx=@%p\n", reg_32, reg_32, ®_32); + printf("\treg[33]=%lu=0x%lx=@%p\n", reg_33, reg_33, ®_33); + printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); + printf("\n"); +#endif } -//struct timeval print_pc_tv; +//#define PRINT_PC_DURATION +#ifdef PRINT_PC_DURATION +struct timeval print_pc_tv; +#endif + extern int _print_pc (uint64_t pc, uint64_t c) { - // print_pc_counter++; - // { - // struct timeval tv; - // gettimeofday(&tv, NULL); - // uint64_t duration = TimeDiff(print_pc_tv, tv); - // if (duration > 900) - // { - // uint64_t chunk = print_pc_counter / chunk_size; - // printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); - // fflush(stdout); - // } - // print_pc_tv = tv; - // } +#ifdef PRINT_PC_DURATION + print_pc_counter++; + { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t duration = TimeDiff(print_pc_tv, tv); + if (duration > 900) + { + uint64_t chunk = print_pc_counter / chunk_size; + printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); + fflush(stdout); + } + print_pc_tv = tv; + } +#endif + printf("s=%lu pc=%lx c=%lx", print_pc_counter, pc, c); - /* Used for debugging + +//#define PRINT_PC_REGS +#ifdef PRINT_PC_REGS + /* Used for debugging */ printf(" r0=%lx", reg_0); printf(" r1=%lx", reg_1); printf(" r2=%lx", reg_2); @@ -4509,36 +4524,54 @@ extern int _print_pc (uint64_t pc, uint64_t c) printf(" r29=%lx", reg_29); printf(" r30=%lx", reg_30); printf(" r31=%lx", reg_31); - */ +#endif + printf("\n"); fflush(stdout); print_pc_counter++; } -// uint64_t chunk_done_counter = 0; -// struct timeval chunk_done_tv; -// struct timeval sync_start, sync_stop; -// uint64_t sync_duration = 0; +//#define CHUNK_DONE_DURATION +#ifdef CHUNK_DONE_DURATION +uint64_t chunk_done_counter = 0; +struct timeval chunk_done_tv; +#endif + +//#define CHUNK_DONE_SYNC_DURATION +#ifdef CHUNK_DONE_SYNC_DURATION +struct timeval sync_start, sync_stop; +uint64_t sync_duration = 0; +#endif + extern void _chunk_done() { - // chunk_done_counter++; - // if ((chunk_done_counter & 0xFF) == 0) - // { - // struct timeval tv; - // gettimeofday(&tv, NULL); - // uint64_t duration = TimeDiff(chunk_done_tv, tv); - // if (duration > 5000) - // { - // printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); - // fflush(stdout); - // } - // chunk_done_tv = tv; - // } - //gettimeofday(&sync_start, NULL); +#ifdef CHUNK_DONE_DURATION + chunk_done_counter++; + if ((chunk_done_counter & 0xFF) == 0) + { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t duration = TimeDiff(chunk_done_tv, tv); + if (duration > 5000) + { + printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); + fflush(stdout); + } + chunk_done_tv = tv; + } +#endif + +#ifdef CHUNK_DONE_SYNC_DURATION + gettimeofday(&sync_start, NULL); +#endif + __sync_synchronize(); - // gettimeofday(&sync_stop, NULL); - // sync_duration += TimeDiff(sync_start, sync_stop); - // printf("chunk_done() sync_duration=%lu\n", sync_duration); + +#ifdef CHUNK_DONE_SYNC_DURATION + gettimeofday(&sync_stop, NULL); + sync_duration += TimeDiff(sync_start, sync_stop); + printf("chunk_done() sync_duration=%lu\n", sync_duration); +#endif // Notify the caller that a new chunk is done and its trace is ready to be consumed assert(call_chunk_done); From 11555c8760baf9d578d39ce81392c294793d7e12 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 10:13:32 +0100 Subject: [PATCH 581/782] Fix some comments and traces in main.c --- emulator-asm/src/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index dddc64f26..70fdedb4c 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4049,14 +4049,14 @@ void server_reset_slow (void) #ifdef DEBUG gettimeofday(&stop_time, NULL); duration = TimeDiff(start_time, stop_time); - if (verbose) printf("server_reset() memset(ram) in %lu us\n", duration); + if (verbose) printf("server_reset_slow() memset(ram) in %lu us\n", duration); #endif } } void server_reset_trace (void) { - // Reset RAM data for next emulation + // Reset trace header and trace_used_size for next emulation if ( (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && (gen_method != Fast) && From a8092208eef5d938bf522e57d25725c6cbbea43d Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 10:37:05 +0100 Subject: [PATCH 582/782] Synchronize shared memory areas as needed, i.e. before reading them, in main.c --- emulator-asm/src/main.c | 88 +++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 30 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 70fdedb4c..68a373958 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4096,6 +4096,22 @@ void server_run (void) exit(-1); } + // Sync control input shared memory + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + /*******/ /* ASM */ /*******/ @@ -5288,24 +5304,41 @@ int _wait_for_prec_avail (void) // Increment wait counter wait_counter++; - // Sync precompile shared memory + // Sync control output shared memory so that the writer can see the precompile reads we have + // done, and thus update the precompile_written_address if needed if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: 1 msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); + printf("ERROR: msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } - // Tell the writer that we have read the precompile results + // Tell the writer that we have read some precompile results sem_post(sem_prec_read); - // Make sure the semaphore is reset before checking the condition, + // Make sure the precompile available semaphore is reset before checking the condition, // since the caller may have posted it (even several times) before we called sem_wait() while (sem_trywait(sem_prec_avail) == 0) {/*printf("Purging sem_prec_avail\n");*/}; + // Sync control input shared memory so that we can see the latest precompile_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + // Check if there are already precompile results available if (*precompile_written_address > *precompile_read_address) { + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + return 0; } @@ -5333,6 +5366,15 @@ int _wait_for_prec_avail (void) fflush(stderr); exit(-1); } + + // Sync control input shared memory so that we can see the latest precompile_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (*precompile_exit_address != 0) { printf("ERROR: wait_for_prec_avail() found precompile_exit_address=%lu\n", *precompile_exit_address); @@ -5342,36 +5384,22 @@ int _wait_for_prec_avail (void) } if (*precompile_written_address > *precompile_read_address) { + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + return 0; } } - // // Wait for control input shared memory to synchronize - // uint64_t written; - // uint64_t read; - // for (uint64_t i=0; i Date: Tue, 17 Feb 2026 17:05:56 +0100 Subject: [PATCH 583/782] Redirect output to file in main.c --- emulator-asm/src/main.c | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 68a373958..ded7e1300 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -110,6 +110,25 @@ typedef enum { } GenMethod; GenMethod gen_method = Fast; +const char * gen_method_achronym(GenMethod method) +{ + switch (method) + { + case Fast: return "FT"; + case MinimalTrace: return "MT"; + case RomHistogram: return "RH"; + case MainTrace: return "MA"; + case ChunksOnly: return "CO"; + //case BusOp: return "bus-op"; + case Zip: return "ZP"; + case MemOp: return "MO"; + case ChunkPlayerMTCollectMem: return "CPM"; + case MemReads: return "MR"; + case ChunkPlayerMemReadsCollectMain: return "CPMCM"; + default: return "?"; + } +} + // Service TCP parameters #define SERVER_IP "127.0.0.1" // Change to your server IP uint16_t port = 0; @@ -582,6 +601,8 @@ void trace_map_initialize (void) pOutputTrace = (uint64_t *)TRACE_ADDR; } +bool redirect_output_to_file = true; + int main(int argc, char *argv[]) { #ifdef DEBUG @@ -606,6 +627,19 @@ int main(int argc, char *argv[]) // Parse arguments parse_arguments(argc, argv); + // Redirect output to file if requested + if (redirect_output_to_file) + { + char redirect_output_file[256]; + snprintf(redirect_output_file, sizeof(redirect_output_file), "/tmp/%s_%s_output.txt", shm_prefix, gen_method_achronym(gen_method)); + + // Redirect stdout to file + freopen(redirect_output_file, "w", stdout); + + // Redirect stderr to the same file + freopen(redirect_output_file, "a", stderr); + } + // Configure based on parguments configure(); @@ -4141,7 +4175,7 @@ void server_run (void) uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; - printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu\n", + printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", duration, realloc_counter, wait_counter, @@ -4156,7 +4190,10 @@ void server_run (void) end, error, max_steps, - chunk_size); + chunk_size, + precompile_written_address ? *precompile_written_address : 0, + precompile_read_address ? *precompile_read_address : 0 + ); if (gen_method == RomHistogram) { printf("Rom histogram size=%lu\n", histogram_size); From fcd51ee8357e3fe4780c8fd2aba621c97ceca99e Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 17:19:31 +0100 Subject: [PATCH 584/782] Enable metrics in main.c --- emulator-asm/src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index ded7e1300..a0a770a43 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -303,7 +303,7 @@ void file_lock(void); bool output = false; bool output_riscof = false; bool silent = false; -bool metrics = false; +bool metrics = true; bool trace = false; bool trace_trace = false; bool verbose = false; From d38d9d1b761f89fe1a1c458c78f1606365bc3f22 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 18:32:34 +0100 Subject: [PATCH 585/782] Flush Duration trace in main.c --- emulator-asm/src/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index a0a770a43..3ffaad6b9 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4194,6 +4194,8 @@ void server_run (void) precompile_written_address ? *precompile_written_address : 0, precompile_read_address ? *precompile_read_address : 0 ); + fflush(stdout); + fflush(stderr); if (gen_method == RomHistogram) { printf("Rom histogram size=%lu\n", histogram_size); From a8e7e90dade003dca2515ee8482555da771eb613 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 20:18:52 +0100 Subject: [PATCH 586/782] Enable verbose in main.c --- emulator-asm/src/main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 3ffaad6b9..1f50b70c6 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -306,7 +306,7 @@ bool silent = false; bool metrics = true; bool trace = false; bool trace_trace = false; -bool verbose = false; +bool verbose = true; bool save_to_file = false; bool share_input_shm = false; // Shares input shared memories: input, precompile results and control input, using a common name bool open_input_shm = false; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) @@ -4152,8 +4152,9 @@ void server_run (void) // Call emulator assembly code gettimeofday(&start_time,NULL); - if (verbose) printf("trace_address=%lx\n", trace_address); + if (verbose) printf("Before calling emulator_start() trace_address=%lx\n", trace_address); emulator_start(); + if (verbose) printf("After calling emulator_start() trace_address=%lx\n", trace_address); gettimeofday(&stop_time,NULL); assembly_duration = TimeDiff(start_time, stop_time); From 30660ba3f49c9e3755835bc10a3133996f01e5e7 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 22:26:16 +0100 Subject: [PATCH 587/782] Add flush() to some verbose traces in main.c --- emulator-asm/src/main.c | 62 +++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 1f50b70c6..689f28a03 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -761,20 +761,34 @@ int main(int argc, char *argv[]) if (bytes_read < 0) { printf("%s ERROR: Failed calling recv() bytes_read=%ld errno=%d=%s\n", log_name, bytes_read, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); break; } if (bytes_read != sizeof(request)) { if ((errno != 0) && (errno != 2)) { - printf("%s ERROR: Failed calling recv() invalid bytes_read=%ld errno=%d=%s\n", log_name, bytes_read, errno, strerror(errno)); + printf("%s WARNING: Failed calling recv() invalid bytes_read=%ld errno=%d=%s\n", log_name, bytes_read, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); } break; } #ifdef DEBUG - if (verbose) printf("%s recv() returned: %ld\n", log_name, bytes_read); + if (verbose) + { + printf("%s recv() returned: %ld\n", log_name, bytes_read); + fflush(stdout); + fflush(stderr); + } #endif - if (verbose) printf("%s recv()'d request=[%lu, 0x%lx, 0x%lx, 0x%lx, 0x%lx]\n", log_name, request[0], request[1], request[2], request[3], request[4]); + if (verbose) + { + printf("%s recv()'d request=[%lu, 0x%lx, 0x%lx, 0x%lx, 0x%lx]\n", log_name, request[0], request[1], request[2], request[3], request[4]); + fflush(stdout); + fflush(stderr); + } uint64_t response[5]; bReset = false; @@ -1074,17 +1088,29 @@ int main(int argc, char *argv[]) } } - if (verbose) printf("%s send()'ing response=[%lu, 0x%lx, 0x%lx, 0x%lx, 0x%lx]\n", log_name, response[0], response[1], response[2], response[3], response[4]); + if (verbose) + { + printf("%s send()'ing response=[%lu, 0x%lx, 0x%lx, 0x%lx, 0x%lx]\n", log_name, response[0], response[1], response[2], response[3], response[4]); + fflush(stdout); + fflush(stderr); + } ssize_t bytes_sent = send(client_fd, response, sizeof(response), MSG_WAITALL); if (bytes_sent != sizeof(response)) { printf("%s ERROR: Failed calling send() invalid bytes_sent=%ld errno=%d=%s\n", log_name, bytes_sent, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); break; } -#ifdef DEBUG - else if (verbose) printf("Response sent to client\n"); -#endif +//#ifdef DEBUG + else if (verbose) + { + printf("Response sent to client\n"); + fflush(stdout); + fflush(stderr); + } +//#endif if (bReset) { server_reset_slow(); @@ -4200,11 +4226,15 @@ void server_run (void) if (gen_method == RomHistogram) { printf("Rom histogram size=%lu\n", histogram_size); + fflush(stdout); + fflush(stderr); } } if (MEM_ERROR) { printf("Emulation ended with error code %lu\n", MEM_ERROR); + fflush(stdout); + fflush(stderr); } // Log output @@ -4213,7 +4243,12 @@ void server_run (void) unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; unsigned int output_size = 64; #ifdef DEBUG - if (verbose) printf("Output size=%d\n", output_size); + if (verbose) + { + printf("Output size=%d\n", output_size); + fflush(stdout); + fflush(stderr); + } #endif for (unsigned int i = 0; i < output_size; i++) @@ -4221,6 +4256,8 @@ void server_run (void) printf("%08x\n", *pOutput); pOutput++; } + fflush(stdout); + fflush(stderr); } // Log output for riscof tests @@ -4229,7 +4266,12 @@ void server_run (void) unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; unsigned int output_size = *pOutput; #ifdef DEBUG - if (verbose) printf("Output size=%d\n", output_size); + if (verbose) + { + printf("Output size=%d\n", output_size); + fflush(stdout); + fflush(stderr); + } #endif for (unsigned int i = 0; i < output_size; i++) @@ -4237,6 +4279,8 @@ void server_run (void) pOutput++; printf("%08x\n", *pOutput); } + fflush(stdout); + fflush(stderr); } // Complete output header data From 75471cd9bd91d7ce1df9a1923db19adfebbd0320 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 17 Feb 2026 23:01:26 +0100 Subject: [PATCH 588/782] Add flush to Waiting... trace in main.c --- emulator-asm/src/main.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 689f28a03..868d691fc 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -728,7 +728,12 @@ int main(int argc, char *argv[]) struct sockaddr_in address; int addrlen = sizeof(address); int client_fd; - if (!silent) printf("%s Waiting for incoming connections to port %u...\n", log_name, port); + if (!silent) + { + printf("%s Waiting for incoming connections to port %u...\n", log_name, port); + fflush(stdout); + fflush(stderr); + } client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen); if (client_fd < 0) { From 4e54b4eba660faeba7a8854b8c23b50262f4ac42 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 18 Feb 2026 10:49:15 +0100 Subject: [PATCH 589/782] Flush traces before and after calling emulator_start() in main.c --- emulator-asm/src/main.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 868d691fc..ee87729a1 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -4154,7 +4154,8 @@ void server_run (void) server_reset_trace(); // Sync input shared memory - if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) { + if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) + { printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); @@ -4183,9 +4184,19 @@ void server_run (void) // Call emulator assembly code gettimeofday(&start_time,NULL); - if (verbose) printf("Before calling emulator_start() trace_address=%lx\n", trace_address); + if (verbose) + { + printf("Before calling emulator_start() trace_address=%lx\n", trace_address); + fflush(stdout); + fflush(stderr); + } emulator_start(); - if (verbose) printf("After calling emulator_start() trace_address=%lx\n", trace_address); + if (verbose) + { + printf("After calling emulator_start() trace_address=%lx\n", trace_address); + fflush(stdout); + fflush(stderr); + } gettimeofday(&stop_time,NULL); assembly_duration = TimeDiff(start_time, stop_time); From 31ecfee8dd7cfd3399bc3363c3421ba7edb6ccb3 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:32:27 +0000 Subject: [PATCH 590/782] added traces while debugging --- distributed/crates/worker/src/worker.rs | 8 ++++---- emulator-asm/asm-runner/src/asm_services/services.rs | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 48c278e4b..862e2e546 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -663,10 +663,10 @@ impl Worker { println!("Stream END received for job {}, cleaned up buffer", job_id); return Ok(()); } - println!( - "Received stream data for job {}, stream type {:?}, processing...", - job_id, stream_type - ); + // println!( + // "Received stream data for job {}, stream type {:?}, processing...", + // job_id, stream_type + // ); let element = self.stream_buffers.get_mut(&job_id).ok_or_else(|| { anyhow::anyhow!( "Received stream data without START for job {} stream type {:?}", diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index a78f051a5..e77b5d817 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -300,16 +300,21 @@ impl AsmServices { out_buffer.extend_from_slice(&word.to_le_bytes()); } + tracing::info!("Sending request to service {} on {}: {:?}", service, addr, request); let mut stream = TcpStream::connect(&addr).with_context(|| format!("Failed to connect to {addr}"))?; + tracing::info!("Connected to service {} on {}", service, addr); // Set a read timeout to avoid indefinite blocking stream .set_read_timeout(Some(Duration::from_secs(10))) .context("Failed to set read timeout")?; + tracing::info!("Sending request payload to service {} on {}", service, addr); + // Send request payload if let Err(e) = stream.write_all(&out_buffer) { + tracing::error!("Failed to write request payload to service {} on {}: {}", service, addr, e); return Err(anyhow::anyhow!( "Failed to write request payload to service {} on {}: {}", service, @@ -318,6 +323,8 @@ impl AsmServices { )); } + tracing::info!("Request payload sent to service {} on {}", service, addr); + let total_timeout = Duration::from_secs(120); let start = Instant::now(); @@ -338,6 +345,7 @@ impl AsmServices { continue; } Err(e) => { + tracing::error!("Failed to read response payload from service {} on {}: {}", service, addr, e); return Err(e.into()); } } @@ -349,6 +357,8 @@ impl AsmServices { response[i] = u64::from_le_bytes(chunk.try_into()?); } + tracing::info!("Decoded response from service {} on {}: {:?}", service, addr, response); + Ok(Res::from_response_payload(response)) } From 8394795ba2bf69d5ac92e6be1235570fb2c90674 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 18 Feb 2026 11:00:56 +0100 Subject: [PATCH 591/782] Get freopen() result in main.c --- emulator-asm/src/main.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index ee87729a1..481277961 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -634,12 +634,25 @@ int main(int argc, char *argv[]) snprintf(redirect_output_file, sizeof(redirect_output_file), "/tmp/%s_%s_output.txt", shm_prefix, gen_method_achronym(gen_method)); // Redirect stdout to file - freopen(redirect_output_file, "w", stdout); + FILE * file_pointer = freopen(redirect_output_file, "w", stdout); + if (file_pointer == NULL) + { + printf("ERROR: Failed to redirect stdout to file %s\n", redirect_output_file); + fflush(stdout); + fflush(stderr); + exit(-1); + } // Redirect stderr to the same file - freopen(redirect_output_file, "a", stderr); + file_pointer = freopen(redirect_output_file, "a", stderr); + if (file_pointer == NULL) + { + printf("ERROR: Failed to redirect stderr to file %s\n", redirect_output_file); + fflush(stdout); + fflush(stderr); + exit(-1); + } } - // Configure based on parguments configure(); From 6a549afe08cc2ad4823641559a7ea35306740580 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 18 Feb 2026 13:37:36 +0100 Subject: [PATCH 592/782] Disable traces in main.c --- emulator-asm/src/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 481277961..ae5d6b591 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -303,10 +303,10 @@ void file_lock(void); bool output = false; bool output_riscof = false; bool silent = false; -bool metrics = true; +bool metrics = false; bool trace = false; bool trace_trace = false; -bool verbose = true; +bool verbose = false; bool save_to_file = false; bool share_input_shm = false; // Shares input shared memories: input, precompile results and control input, using a common name bool open_input_shm = false; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) @@ -601,7 +601,7 @@ void trace_map_initialize (void) pOutputTrace = (uint64_t *)TRACE_ADDR; } -bool redirect_output_to_file = true; +bool redirect_output_to_file = false; int main(int argc, char *argv[]) { From 41f39a811ca767d86087346cffe574a6a07645aa Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:59:09 +0000 Subject: [PATCH 593/782] adding fence in hints_shmem.rs to fix when working with hints --- distributed/crates/worker/src/worker.rs | 10 +--------- emulator-asm/asm-runner/src/asm_services/services.rs | 10 ---------- emulator-asm/asm-runner/src/hints_shmem.rs | 12 +++++++++++- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 862e2e546..71e680bb1 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -642,10 +642,6 @@ impl Worker { hints_processor.reset(); } - println!( - "Stream START received for job {}, initialized buffer and reset hints processor", - job_id - ); return Ok(()); } else if stream_type == StreamMessageKind::End { // Ensure buffer exists @@ -660,13 +656,9 @@ impl Worker { // Clean up the stream buffer for this job self.stream_buffers.remove(&job_id); - println!("Stream END received for job {}, cleaned up buffer", job_id); return Ok(()); } - // println!( - // "Received stream data for job {}, stream type {:?}, processing...", - // job_id, stream_type - // ); + let element = self.stream_buffers.get_mut(&job_id).ok_or_else(|| { anyhow::anyhow!( "Received stream data without START for job {} stream type {:?}", diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index e77b5d817..a78f051a5 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -300,21 +300,16 @@ impl AsmServices { out_buffer.extend_from_slice(&word.to_le_bytes()); } - tracing::info!("Sending request to service {} on {}: {:?}", service, addr, request); let mut stream = TcpStream::connect(&addr).with_context(|| format!("Failed to connect to {addr}"))?; - tracing::info!("Connected to service {} on {}", service, addr); // Set a read timeout to avoid indefinite blocking stream .set_read_timeout(Some(Duration::from_secs(10))) .context("Failed to set read timeout")?; - tracing::info!("Sending request payload to service {} on {}", service, addr); - // Send request payload if let Err(e) = stream.write_all(&out_buffer) { - tracing::error!("Failed to write request payload to service {} on {}: {}", service, addr, e); return Err(anyhow::anyhow!( "Failed to write request payload to service {} on {}: {}", service, @@ -323,8 +318,6 @@ impl AsmServices { )); } - tracing::info!("Request payload sent to service {} on {}", service, addr); - let total_timeout = Duration::from_secs(120); let start = Instant::now(); @@ -345,7 +338,6 @@ impl AsmServices { continue; } Err(e) => { - tracing::error!("Failed to read response payload from service {} on {}: {}", service, addr, e); return Err(e.into()); } } @@ -357,8 +349,6 @@ impl AsmServices { response[i] = u64::from_le_bytes(chunk.try_into()?); } - tracing::info!("Decoded response from service {} on {}: {:?}", service, addr, response); - Ok(Res::from_response_payload(response)) } diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index cdfb1a1f2..a114f698d 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -9,7 +9,10 @@ use crate::{ }; use anyhow::Result; use named_sem::NamedSemaphore; -use std::{cell::RefCell, sync::atomic::AtomicBool}; +use std::{ + cell::RefCell, + sync::atomic::{fence, AtomicBool, Ordering}, +}; use tracing::debug; use zisk_common::io::StreamSink; @@ -215,6 +218,9 @@ impl StreamSink for HintsShmem { // Flow control: wait until all consumers have advanced enough // We need to wait for the slowest consumer (minimum read position) loop { + // Ensure we observe the latest read positions + fence(Ordering::Acquire); + // Find the slowest consumer (minimum read position) and its index let (slowest_idx, min_read_pos) = separate .iter() @@ -254,9 +260,13 @@ impl StreamSink for HintsShmem { // Write data ONCE to the unified shared memory buffer unified.data_writer.write_ring_buffer(&processed)?; + fence(Ordering::Release); + // Update write position ONCE in control memory unified.control_writer.write_u64_at(0, write_pos + data_size); + fence(Ordering::Release); + // Notify ALL consumers that new data is available for res in separate.iter_mut() { res.sem_available.post()?; From 24e02d79fca53d2582aafaed988c37eccb194ad3 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:34:16 +0000 Subject: [PATCH 594/782] added debugging messages --- emulator-asm/asm-runner/src/hints_shmem.rs | 12 ++++++++++++ precompiles/hints/src/hints_processor.rs | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index a114f698d..1338d5c01 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -188,9 +188,11 @@ impl StreamSink for HintsShmem { #[inline] fn submit(&self, processed: Vec) -> anyhow::Result<()> { let data_size = processed.len() as u64; + debug!("[SHMEM] submit() called with {} u64 elements", data_size); // Early return for empty data if data_size == 0 { + debug!("[SHMEM] submit() early return - empty data"); return Ok(()); } @@ -214,6 +216,7 @@ impl StreamSink for HintsShmem { // Read current write position once let write_pos = unified.control_writer.read_u64_at(0); + debug!("[SHMEM] Current write_pos={}, attempting to submit {} u64s", write_pos, data_size); // Flow control: wait until all consumers have advanced enough // We need to wait for the slowest consumer (minimum read position) @@ -247,30 +250,39 @@ impl StreamSink for HintsShmem { // Flow control based on buffer occupancy if available_space >= data_size { + debug!("[SHMEM] Sufficient space available ({}), breaking flow control loop", available_space); break; } // Not enough space - wait for the SLOWEST consumer to signal progress + debug!("[SHMEM] Insufficient space: available={}, needed={}, waiting on consumer {} (read_pos={})", + available_space, data_size, slowest_idx, min_read_pos); // Retry on interrupt (EINTR) if separate[slowest_idx].sem_read.wait().is_err() { + debug!("[SHMEM] sem_read.wait() returned error, retrying"); continue; } + debug!("[SHMEM] sem_read.wait() succeeded, retrying space check"); } // Write data ONCE to the unified shared memory buffer + debug!("[SHMEM] Writing {} u64s to ring buffer at pos {}", data_size, write_pos); unified.data_writer.write_ring_buffer(&processed)?; fence(Ordering::Release); // Update write position ONCE in control memory unified.control_writer.write_u64_at(0, write_pos + data_size); + debug!("[SHMEM] Updated write_pos to {}", write_pos + data_size); fence(Ordering::Release); // Notify ALL consumers that new data is available + debug!("[SHMEM] Posting to {} consumer semaphores", separate.len()); for res in separate.iter_mut() { res.sem_available.post()?; } + debug!("[SHMEM] submit() completed successfully"); Ok(()) } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 1bd6b3cc2..99c2e38bf 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -531,11 +531,16 @@ impl HintsProcessor { /// Drainer thread that waits for hints to complete and drains ready results from queue. fn drainer_thread(state: Arc, hints_sink: Arc) { + debug!("[DRAINER] Thread started"); loop { + debug!("[DRAINER] Attempting to acquire queue lock"); let mut queue = state.queue.lock().unwrap(); + debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", + queue.buffer.len(), queue.next_drain_seq); // Check for shutdown if state.shutdown.load(Ordering::Acquire) { + debug!("[DRAINER] Shutdown signal received, exiting"); break; } @@ -543,25 +548,31 @@ impl HintsProcessor { let mut drained_any = false; while let Some(Some(res)) = queue.buffer.front() { drained_any = true; + let current_seq = queue.next_drain_seq; match res { Ok(data) => { // Clone data before dropping lock + debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); let data_to_submit = data.clone(); queue.buffer.pop_front(); queue.next_drain_seq += 1; // Drop lock before submitting to avoid blocking workers + debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); drop(queue); // Submit to sink + debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); if let Err(e) = hints_sink.submit(data_to_submit) { eprintln!("Error submitting to sink: {}", e); state.error_flag.store(true, Ordering::Release); state.drain_signal.notify_all(); return; } + debug!("[DRAINER] Completed submit seq={}", current_seq); // Re-acquire lock for next iteration + debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); queue = state.queue.lock().unwrap(); } Err(e) => { @@ -578,20 +589,25 @@ impl HintsProcessor { // If we drained any results, notify wait_for_completion that buffer changed if drained_any { + debug!("[DRAINER] Notifying wait_for_completion"); state.drain_signal.notify_all(); } // Check for shutdown again before waiting if state.shutdown.load(Ordering::Acquire) { + debug!("[DRAINER] Shutdown signal received before wait, exiting"); break; } // Wait for notification that a hint completed + debug!("[DRAINER] Waiting on drain_signal condvar"); #[allow(unused_assignments)] { queue = state.drain_signal.wait(queue).unwrap(); } + debug!("[DRAINER] Woke up from condvar wait"); } + debug!("[DRAINER] Thread exiting"); } /// Waits for all pending hints to be processed and drained. From 7810e93b57ea3236ab7635e012d017def957d2c1 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:01:55 +0000 Subject: [PATCH 595/782] temptative fix --- precompiles/hints/src/hints_processor.rs | 252 +++++++++++++++++------ 1 file changed, 186 insertions(+), 66 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 99c2e38bf..366b94835 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -392,11 +392,12 @@ impl HintsProcessor { // Handle pass-through hints immediately if hint.is_passthrough { queue.buffer.push_back(Some(Ok(hint.data.clone()))); - // Notify immediately while holding the lock to ensure drainer sees the result - // Release lock after this block, avoiding duplicate notification - drop(queue); - // Use notify_all since wait_for_completion also waits on this condvar + // Notify BEFORE releasing lock to prevent race condition where drainer + // checks buffer, finds it empty, then we notify, then drainer waits + // (missing our notification). Notifying while holding lock ensures + // drainer will see the buffer change when it wakes. self.state.drain_signal.notify_all(); + drop(queue); // Continue to next hint without spawning worker idx += length; continue; @@ -522,20 +523,21 @@ impl HintsProcessor { // Fill the slot to allow drainer to proceed (critical for ordering) queue.buffer[offset] = Some(result); - // Release lock before notifying - drop(queue); - - // Notify drainer thread (use notify_all to wake any waiting threads) + // Notify BEFORE releasing lock to prevent race condition where drainer + // checks buffer, finds no ready result, then we notify, then drainer waits + // (missing our notification). Notifying while holding lock ensures + // drainer will see the buffer change when it wakes. state.drain_signal.notify_all(); + drop(queue); } /// Drainer thread that waits for hints to complete and drains ready results from queue. fn drainer_thread(state: Arc, hints_sink: Arc) { debug!("[DRAINER] Thread started"); + let mut queue = state.queue.lock().unwrap(); + loop { - debug!("[DRAINER] Attempting to acquire queue lock"); - let mut queue = state.queue.lock().unwrap(); - debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", + debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", queue.buffer.len(), queue.next_drain_seq); // Check for shutdown @@ -544,68 +546,61 @@ impl HintsProcessor { break; } - // Drain all consecutive ready results from the front - let mut drained_any = false; - while let Some(Some(res)) = queue.buffer.front() { - drained_any = true; - let current_seq = queue.next_drain_seq; - match res { - Ok(data) => { - // Clone data before dropping lock - debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); - let data_to_submit = data.clone(); - queue.buffer.pop_front(); - queue.next_drain_seq += 1; - - // Drop lock before submitting to avoid blocking workers - debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); - drop(queue); - - // Submit to sink - debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); - if let Err(e) = hints_sink.submit(data_to_submit) { - eprintln!("Error submitting to sink: {}", e); + // Check if front slot has a ready result + match queue.buffer.front() { + Some(Some(res)) => { + // Ready result found - process it + let current_seq = queue.next_drain_seq; + match res { + Ok(data) => { + debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); + let data_to_submit = data.clone(); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; + + // Notify wait_for_completion that buffer changed + state.drain_signal.notify_all(); + + // Drop lock before submitting to avoid blocking workers + debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); + drop(queue); + + // Submit to sink + debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); + if let Err(e) = hints_sink.submit(data_to_submit) { + eprintln!("Error submitting to sink: {}", e); + state.error_flag.store(true, Ordering::Release); + // Re-acquire lock to notify while holding it + let _queue = state.queue.lock().unwrap(); + state.drain_signal.notify_all(); + return; + } + debug!("[DRAINER] Completed submit seq={}", current_seq); + + // Re-acquire lock for next iteration + debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); + queue = state.queue.lock().unwrap(); + } + Err(e) => { + // Error found - signal to stop state.error_flag.store(true, Ordering::Release); + eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; state.drain_signal.notify_all(); return; } - debug!("[DRAINER] Completed submit seq={}", current_seq); - - // Re-acquire lock for next iteration - debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); - queue = state.queue.lock().unwrap(); - } - Err(e) => { - // Error found - signal to stop - state.error_flag.store(true, Ordering::Release); - eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); - queue.buffer.pop_front(); - queue.next_drain_seq += 1; - state.drain_signal.notify_all(); - return; } } + Some(None) | None => { + // No ready result at front (pending or empty buffer) + // CRITICAL: Must wait IMMEDIATELY after this check with NO code in between + // to prevent race condition where worker notifies after we check but before we wait. + debug!("[DRAINER] No ready result, waiting on drain_signal condvar"); + queue = state.drain_signal.wait(queue).unwrap(); + debug!("[DRAINER] Woke up from condvar wait"); + } } - - // If we drained any results, notify wait_for_completion that buffer changed - if drained_any { - debug!("[DRAINER] Notifying wait_for_completion"); - state.drain_signal.notify_all(); - } - - // Check for shutdown again before waiting - if state.shutdown.load(Ordering::Acquire) { - debug!("[DRAINER] Shutdown signal received before wait, exiting"); - break; - } - - // Wait for notification that a hint completed - debug!("[DRAINER] Waiting on drain_signal condvar"); - #[allow(unused_assignments)] - { - queue = state.drain_signal.wait(queue).unwrap(); - } - debug!("[DRAINER] Woke up from condvar wait"); } debug!("[DRAINER] Thread exiting"); } @@ -1675,4 +1670,129 @@ mod tests { assert_eq!(results[i][0], i as u64 + 1000, "Result {} out of order", i); } } + + /// Regression test for race condition where drainer thread misses notifications. + /// + /// This test attempts to trigger a race between workers completing hints and + /// the drainer thread waiting on the condvar. The race occurs when: + /// 1. Drainer checks buffer.front() and finds no ready result + /// 2. Worker completes and calls notify_all() + /// 3. Drainer enters wait() - MISSED the notification! + /// + /// With the fix (notify before drop), this race is eliminated because the + /// notification happens while holding the lock, ensuring the drainer either: + /// - Sees the result before checking (doesn't wait) + /// - Is already waiting when notified (wakes up) + #[test] + fn test_race_condition_drainer_notification_lost() { + use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + use std::sync::Arc; + use std::thread; + use std::time::{Duration, Instant}; + + struct RecordingSink { + received_count: Arc, + } + + impl StreamSink for RecordingSink { + fn submit(&self, _processed: Vec) -> Result<()> { + self.received_count.fetch_add(1, Ordering::SeqCst); + Ok(()) + } + } + + // Custom hint code for testing (0x7FFF_0200 to avoid conflicts) + const RACE_HINT: u32 = 0x7FFF_0200; + + // Test parameters designed to maximize race window hits + const NUM_THREADS: usize = 32; + const HINTS_PER_BATCH: usize = 100; + const ITERATIONS: usize = 100; + const TIMEOUT_MS: u64 = 500; + + for iteration in 0..ITERATIONS { + let received_count = Arc::new(AtomicUsize::new(0)); + let sink = RecordingSink { received_count: Arc::clone(&received_count) }; + + let p = HintsProcessor::builder(sink) + .num_threads(NUM_THREADS) + .custom_hint(RACE_HINT, |data| { + // Tiny random delay (0-1000ns) to desynchronize worker completions + // This maximizes the chance of workers completing just as + // drainer is between checking buffer and entering wait + let hash = data[0].wrapping_mul(2654435761); + let delay_ns = hash % 1000; + if delay_ns > 0 { + std::hint::spin_loop(); + // Tiny busy-wait (more precise than sleep for nanoseconds) + let start = Instant::now(); + while start.elapsed().as_nanos() < delay_ns as u128 { + std::hint::spin_loop(); + } + } + Ok(vec![data[0] + 1]) + }) + .build() + .unwrap(); + + // Build batch of hints + let mut data = Vec::with_capacity(HINTS_PER_BATCH * 2); + for i in 0..HINTS_PER_BATCH { + data.push(make_header(RACE_HINT, 1)); + data.push(i as u64); + } + + // Process hints + p.process_hints(&data, false).unwrap(); + + // Use watchdog to detect hang + let completed = Arc::new(AtomicBool::new(false)); + let completed_clone = Arc::clone(&completed); + + let watchdog = thread::spawn(move || { + let start = Instant::now(); + while !completed_clone.load(Ordering::Acquire) { + if start.elapsed() > Duration::from_millis(TIMEOUT_MS) { + return false; // Timeout - race condition triggered! + } + thread::sleep(Duration::from_millis(10)); + } + true // Completed in time + }); + + // Wait for completion + let result = p.wait_for_completion(); + completed.store(true, Ordering::Release); + + let watchdog_ok = watchdog.join().unwrap(); + assert!( + watchdog_ok, + "RACE CONDITION DETECTED: Iteration {} hung for >{}ms. \ + Drainer missed worker notifications.", + iteration, + TIMEOUT_MS + ); + + assert!(result.is_ok(), "Iteration {} failed: {:?}", iteration, result); + + // Verify all hints were processed + let count = received_count.load(Ordering::SeqCst); + assert_eq!( + count, HINTS_PER_BATCH, + "Iteration {}: Expected {} results, got {}", + iteration, HINTS_PER_BATCH, count + ); + } + + println!( + "\n========================================\n\ + Race Condition Regression Test PASSED\n\ + Iterations: {}\n\ + Hints per iteration: {}\n\ + Worker threads: {}\n\ + No hangs detected!\n\ + ========================================\n", + ITERATIONS, HINTS_PER_BATCH, NUM_THREADS + ); + } } From 76adcea606debdc009cd4c5fcedc8e043b0c5d6e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:43:53 +0000 Subject: [PATCH 596/782] temptative fix II --- precompiles/hints/src/hints_processor.rs | 555 +++--------------- precompiles/hints/src/lib.rs | 2 + .../hints/src/ordered_result_buffer.rs | 490 ++++++++++++++++ 3 files changed, 571 insertions(+), 476 deletions(-) create mode 100644 precompiles/hints/src/ordered_result_buffer.rs diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 366b94835..43509a964 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -4,12 +4,13 @@ //! that are received as a stream of `u64` values. Hints are used to provide preprocessed //! data to precompile operations in the ZisK zkVM. +use crate::ordered_result_buffer::{BufferStatus, OrderedResultBuffer}; use anyhow::Result; use rayon::{ThreadPool, ThreadPoolBuilder}; -use std::collections::{HashMap, VecDeque}; +use std::collections::HashMap; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::{Arc, Condvar, Mutex}; +use std::sync::{Arc, Mutex}; use std::time::Instant; use tracing::{debug, info}; use zisk_common::io::{StreamProcessor, StreamSink}; @@ -34,55 +35,6 @@ use ziskos_hints::handlers::secp256k1::{ use ziskos_hints::handlers::secp256r1::secp256r1_ecdsa_verify_hint; use ziskos_hints::handlers::sha256::sha256_hint; -/// Ordered result buffer with drain state. -/// -/// This structure maintains a VecDeque that holds processed results in order, -/// allowing out-of-order completion while ensuring in-order output. -struct ResultQueue { - /// The result buffer: None = pending, Some(Ok(...)) = ready, Some(Err(...)) = error - buffer: VecDeque>>>, - /// Sequence ID of the next result to drain from buffer[0] - next_drain_seq: usize, -} - -/// Thread-safe shared state for parallel hint processing. -struct HintProcessorState { - /// Ordered results ready for draining - queue: Mutex, - /// Notifies drainer thread when a hint completes - drain_signal: Condvar, - /// Next sequence ID to assign to incoming hints - next_seq: AtomicUsize, - /// Signals processing should stop - error_flag: AtomicBool, - /// Signals drainer thread to shut down - shutdown: AtomicBool, - /// Invalidates stale workers after reset - generation: AtomicUsize, -} - -impl HintProcessorState { - fn new() -> Self { - Self { - queue: Mutex::new(ResultQueue { buffer: VecDeque::new(), next_drain_seq: 0 }), - drain_signal: Condvar::new(), - next_seq: AtomicUsize::new(0), - error_flag: AtomicBool::new(false), - shutdown: AtomicBool::new(false), - generation: AtomicUsize::new(0), - } - } - - fn reset(&self) { - self.error_flag.store(false, Ordering::Release); - self.next_seq.store(0, Ordering::Relaxed); - self.generation.fetch_add(1, Ordering::SeqCst); - let mut queue = self.queue.lock().unwrap(); - queue.buffer.clear(); - queue.next_drain_seq = 0; - } -} - /// Type alias for custom hint handler functions. pub type CustomHintHandler = Arc Result> + Send + Sync>; @@ -144,20 +96,20 @@ impl HintsProcessorBuilder { .build() .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; - let state = Arc::new(HintProcessorState::new()); + let buffer = Arc::new(OrderedResultBuffer::new()); let hints_sink = self.hints_sink; // Spawn drainer thread - let drainer_state = Arc::clone(&state); + let drainer_buffer = Arc::clone(&buffer); let drainer_sink = Arc::clone(&hints_sink); let drainer_thread = std::thread::spawn(move || { - HintsProcessor::drainer_thread(drainer_state, drainer_sink); + HintsProcessor::drainer_thread(drainer_buffer, drainer_sink); }); Ok(HintsProcessor { pool, num_hint: AtomicUsize::new(0), - state, + buffer, stats: if self.enable_stats { Some(Mutex::new(HashMap::new())) } else { None }, hints_sink, drainer_thread: ManuallyDrop::new(drainer_thread), @@ -180,8 +132,8 @@ pub struct HintsProcessor { num_hint: AtomicUsize, - /// Shared state for parallel hint processing - state: Arc, + /// Ordered result buffer for parallel processing with in-order output + buffer: Arc>>>, /// Optional statistics collected during hint processing (for debugging). stats: Option>>, @@ -270,7 +222,7 @@ impl HintsProcessor { let mut idx = 0; while idx < hints.len() { // Check for error before processing each hint - if self.state.error_flag.load(Ordering::Acquire) { + if self.buffer.status() == BufferStatus::Error { return Err(anyhow::anyhow!("Processing stopped due to previous error")); } let (parsed_hint, consumed) = @@ -367,52 +319,32 @@ impl HintsProcessor { break; } HintCode::Ctrl(CtrlHint::Cancel) => { - // Cancel current stream: set error and notify - self.state.error_flag.store(true, Ordering::Release); - self.state.drain_signal.notify_all(); + // Cancel current stream: set error + self.buffer.set_error(); return Err(anyhow::anyhow!("Stream cancelled")); } HintCode::Ctrl(CtrlHint::Error) => { // External error signal - self.state.error_flag.store(true, Ordering::Release); - self.state.drain_signal.notify_all(); + self.buffer.set_error(); return Err(anyhow::anyhow!("Stream error signalled")); } _ => {} // Built-in data hint or custom hint; continue processing } - // Capture generation outside mutex - SeqCst provides sufficient ordering - let generation = self.state.generation.load(Ordering::SeqCst); - - // Atomically reserve slot - use Relaxed for seq since mutex provides ordering - let seq_id = { - let mut queue = self.state.queue.lock().unwrap(); - let seq = self.state.next_seq.fetch_add(1, Ordering::Relaxed); - - // Handle pass-through hints immediately - if hint.is_passthrough { - queue.buffer.push_back(Some(Ok(hint.data.clone()))); - // Notify BEFORE releasing lock to prevent race condition where drainer - // checks buffer, finds it empty, then we notify, then drainer waits - // (missing our notification). Notifying while holding lock ensures - // drainer will see the buffer change when it wakes. - self.state.drain_signal.notify_all(); - drop(queue); - // Continue to next hint without spawning worker - idx += length; - continue; - } else { - queue.buffer.push_back(None); - } - - seq - }; + // Handle pass-through hints immediately (no worker needed) + if hint.is_passthrough { + self.buffer.reserve_and_fill(Ok(hint.data.clone())); + idx += length; + continue; + } - // Spawn processing task for async hints (Noop already handled above) - let state = Arc::clone(&self.state); + // Reserve slot and spawn worker for async processing + let seq_id = self.buffer.reserve(); + let buffer = Arc::clone(&self.buffer); let custom_handlers = Arc::clone(&self.custom_handlers); self.pool.spawn(move || { - Self::worker_thread(state, hint, generation, seq_id, custom_handlers); + let result = Self::dispatch_hint(hint, custom_handlers); + buffer.fill(seq_id, result); }); idx += length; @@ -455,153 +387,31 @@ impl HintsProcessor { } } - /// Worker thread that processes a single hint and stores the result. - /// - /// # Arguments - /// - /// * `state` - Shared processor state - /// * `hint` - The hint to process - /// * `generation` - Generation number for detecting stale workers - /// * `seq_id` - Sequence ID for ordering results - /// * `custom_handlers` - Custom hint handlers - fn worker_thread( - state: Arc, - hint: PrecompileHint, - generation: usize, - seq_id: usize, - custom_handlers: Arc>, + /// Drainer thread that takes results in order and submits to sink. + fn drainer_thread( + buffer: Arc>>>, + hints_sink: Arc, ) { - // Check generation first to detect stale workers (before processing) - let current_gen = state.generation.load(Ordering::SeqCst); - if generation != current_gen { - // Worker belongs to old generation; ignore - return; - } - - // println!("Processing Hint => {:?}:", hint); - - // Check if we should stop due to error - but still need to fill the slot - let result = if state.error_flag.load(Ordering::Acquire) { - Err(anyhow::anyhow!("Processing stopped due to error")) - } else { - // Process the hint - Self::dispatch_hint(hint, custom_handlers) - }; - - // println!( - // "Hint result: {:x?} bytes", - // match &result { - // Ok(data) => format!("{:?}", data), - // Err(e) => format!("Err({})", e), - // } - // ); - - // Store result - MUST fill slot even if error occurred - let mut queue = state.queue.lock().unwrap(); - - // Check generation again in case reset happened during processing - let current_gen = state.generation.load(Ordering::SeqCst); - if generation != current_gen { - // Worker belongs to old generation; buffer was cleared and repopulated - // Our seq_id is from the old session and doesn't correspond to current slots - return; - } - - // Calculate offset in buffer; handle drained slots - if seq_id < queue.next_drain_seq { - // This result belongs to a previous stream/session; ignore - return; - } - let offset = seq_id - queue.next_drain_seq; - - // Check if slot exists - if not, drainer already processed and removed it - if offset >= queue.buffer.len() { - // Slot was already drained; safe to drop this result - return; - } - - // Fill the slot to allow drainer to proceed (critical for ordering) - queue.buffer[offset] = Some(result); - - // Notify BEFORE releasing lock to prevent race condition where drainer - // checks buffer, finds no ready result, then we notify, then drainer waits - // (missing our notification). Notifying while holding lock ensures - // drainer will see the buffer change when it wakes. - state.drain_signal.notify_all(); - drop(queue); - } - - /// Drainer thread that waits for hints to complete and drains ready results from queue. - fn drainer_thread(state: Arc, hints_sink: Arc) { debug!("[DRAINER] Thread started"); - let mut queue = state.queue.lock().unwrap(); - loop { - debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", - queue.buffer.len(), queue.next_drain_seq); - - // Check for shutdown - if state.shutdown.load(Ordering::Acquire) { - debug!("[DRAINER] Shutdown signal received, exiting"); - break; - } - - // Check if front slot has a ready result - match queue.buffer.front() { - Some(Some(res)) => { - // Ready result found - process it - let current_seq = queue.next_drain_seq; - match res { - Ok(data) => { - debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); - let data_to_submit = data.clone(); - queue.buffer.pop_front(); - queue.next_drain_seq += 1; - - // Notify wait_for_completion that buffer changed - state.drain_signal.notify_all(); - - // Drop lock before submitting to avoid blocking workers - debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); - drop(queue); - - // Submit to sink - debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); - if let Err(e) = hints_sink.submit(data_to_submit) { - eprintln!("Error submitting to sink: {}", e); - state.error_flag.store(true, Ordering::Release); - // Re-acquire lock to notify while holding it - let _queue = state.queue.lock().unwrap(); - state.drain_signal.notify_all(); - return; - } - debug!("[DRAINER] Completed submit seq={}", current_seq); - - // Re-acquire lock for next iteration - debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); - queue = state.queue.lock().unwrap(); - } - Err(e) => { - // Error found - signal to stop - state.error_flag.store(true, Ordering::Release); - eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); - queue.buffer.pop_front(); - queue.next_drain_seq += 1; - state.drain_signal.notify_all(); - return; - } + while let Some(result) = buffer.take_next() { + match result { + Ok(data) => { + debug!("[DRAINER] Submitting {} u64s", data.len()); + if let Err(e) = hints_sink.submit(data) { + eprintln!("Error submitting to sink: {}", e); + buffer.set_error(); + return; } } - Some(None) | None => { - // No ready result at front (pending or empty buffer) - // CRITICAL: Must wait IMMEDIATELY after this check with NO code in between - // to prevent race condition where worker notifies after we check but before we wait. - debug!("[DRAINER] No ready result, waiting on drain_signal condvar"); - queue = state.drain_signal.wait(queue).unwrap(); - debug!("[DRAINER] Woke up from condvar wait"); + Err(e) => { + eprintln!("Hint processing error: {}", e); + buffer.set_error(); + return; } } } + debug!("[DRAINER] Thread exiting"); } @@ -615,20 +425,10 @@ impl HintsProcessor { /// * `Ok(())` - All hints processed successfully /// * `Err` - If an error occurred during processing pub fn wait_for_completion(&self) -> Result<()> { - let mut queue = self.state.queue.lock().unwrap(); - - while !queue.buffer.is_empty() { - if self.state.error_flag.load(Ordering::Acquire) { - return Err(anyhow::anyhow!("Processing stopped due to error")); - } - // Wait for notification that buffer state changed - queue = self.state.drain_signal.wait(queue).unwrap(); - } - - if self.state.error_flag.load(Ordering::Acquire) { + let status = self.buffer.wait_until_drained(); + if status == BufferStatus::Error { return Err(anyhow::anyhow!("Processing stopped due to error")); } - Ok(()) } @@ -709,7 +509,7 @@ impl HintsProcessor { fn reset(&self) { self.num_hint.store(0, Ordering::Relaxed); - self.state.reset(); + self.buffer.reset(); if let Some(stats) = self.stats.as_ref() { stats.lock().unwrap().clear(); } @@ -727,8 +527,7 @@ impl HintsProcessor { impl Drop for HintsProcessor { fn drop(&mut self) { // Signal drainer thread to shut down - self.state.shutdown.store(true, Ordering::Release); - self.state.drain_signal.notify_all(); + self.buffer.shutdown(); // Join the drainer thread to ensure clean shutdown // Safety: We only take the value once in drop @@ -800,9 +599,7 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Buffer should be empty after completion - let queue = p.state.queue.lock().unwrap(); - assert!(queue.buffer.is_empty()); - assert_eq!(queue.next_drain_seq, 1); + assert!(p.buffer.is_empty()); } #[test] @@ -820,10 +617,8 @@ mod tests { assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Verify all hints were processed (buffer empty, next_drain_seq advanced) - let queue = p.state.queue.lock().unwrap(); - assert!(queue.buffer.is_empty()); - assert_eq!(queue.next_drain_seq, 3); + // Verify all hints were processed (buffer empty) + assert!(p.buffer.is_empty()); } #[test] @@ -837,10 +632,8 @@ mod tests { assert!(p.process_hints(&data2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Verify sequence continued across calls - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 2); - assert!(queue.buffer.is_empty()); + // Verify all hints were processed + assert!(p.buffer.is_empty()); } #[test] @@ -851,8 +644,7 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // No hints processed - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 0); + assert!(p.buffer.is_empty()); } // Negative tests @@ -892,15 +684,14 @@ mod tests { // Reset should clear any error state p.reset(); - assert!(!p.state.error_flag.load(Ordering::Acquire)); + assert_eq!(p.buffer.status(), BufferStatus::Active); // Should be able to process new hints after reset (8 bytes = 1 u64) let good = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x42]; assert!(p.process_hints(&good, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); + assert!(p.buffer.is_empty()); } #[test] @@ -925,11 +716,7 @@ mod tests { p.process_hints(&end, false).unwrap(); // Buffer should already be empty - { - let queue = p.state.queue.lock().unwrap(); - assert!(queue.buffer.is_empty()); - assert_eq!(queue.next_drain_seq, 2); - } + assert!(p.buffer.is_empty()); // Explicit wait should be instant assert!(p.wait_for_completion().is_ok()); @@ -945,7 +732,7 @@ mod tests { assert!(result.unwrap_err().to_string().contains("cancelled")); // Error flag should be set - assert!(p.state.error_flag.load(Ordering::Acquire)); + assert_eq!(p.buffer.status(), BufferStatus::Error); } #[test] @@ -958,7 +745,7 @@ mod tests { assert!(result.unwrap_err().to_string().contains("error")); // Error flag should be set - assert!(p.state.error_flag.load(Ordering::Acquire)); + assert_eq!(p.buffer.status(), BufferStatus::Error); } #[test] @@ -1087,7 +874,7 @@ mod tests { // Wait should detect the error from drainer thread std::thread::sleep(std::time::Duration::from_millis(100)); - assert!(p.state.error_flag.load(Ordering::Acquire)); + assert_eq!(p.buffer.status(), BufferStatus::Error); } // Builder tests @@ -1327,22 +1114,14 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Verify no results yet (hint is partial) - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 0); - assert!(queue.buffer.is_empty()); - } + assert!(p.buffer.is_empty()); // Second batch completes the hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Now we should have the complete result (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1359,21 +1138,14 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Verify no results yet - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 0); - } + assert!(p.buffer.is_empty()); // Second batch completes the hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Now we should have the complete result (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1395,21 +1167,14 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Still no complete results - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 0); - } + assert!(p.buffer.is_empty()); // Third batch completes the hint assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Now we should have the complete result (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1433,31 +1198,18 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // No results yet - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 0); - } + assert!(p.buffer.is_empty()); // Complete first hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have first result now - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - } - // Complete second hint assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have both results - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 2); - assert!(queue.buffer.is_empty()); - } + // Should have both results (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1477,22 +1229,12 @@ mod tests { assert!(p.process_hints(&batch1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have result for complete hint - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - } - // Complete the partial hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have both results - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 2); - assert!(queue.buffer.is_empty()); - } + // Should have both results (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1506,12 +1248,8 @@ mod tests { assert!(p.process_hints(&batch1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have result - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Should have result (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1538,12 +1276,8 @@ mod tests { // End stream should wait for all hints to complete assert!(p.process_hints(&end, false).is_ok()); - // Everything should be processed - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Everything should be processed (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1575,10 +1309,8 @@ mod tests { assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - } + // Buffer should be drained + assert!(p.buffer.is_empty()); } #[test] @@ -1602,12 +1334,8 @@ mod tests { assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have complete result - { - let queue = p.state.queue.lock().unwrap(); - assert_eq!(queue.next_drain_seq, 1); - assert!(queue.buffer.is_empty()); - } + // Should have complete result (buffer drained) + assert!(p.buffer.is_empty()); } #[test] @@ -1670,129 +1398,4 @@ mod tests { assert_eq!(results[i][0], i as u64 + 1000, "Result {} out of order", i); } } - - /// Regression test for race condition where drainer thread misses notifications. - /// - /// This test attempts to trigger a race between workers completing hints and - /// the drainer thread waiting on the condvar. The race occurs when: - /// 1. Drainer checks buffer.front() and finds no ready result - /// 2. Worker completes and calls notify_all() - /// 3. Drainer enters wait() - MISSED the notification! - /// - /// With the fix (notify before drop), this race is eliminated because the - /// notification happens while holding the lock, ensuring the drainer either: - /// - Sees the result before checking (doesn't wait) - /// - Is already waiting when notified (wakes up) - #[test] - fn test_race_condition_drainer_notification_lost() { - use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - use std::sync::Arc; - use std::thread; - use std::time::{Duration, Instant}; - - struct RecordingSink { - received_count: Arc, - } - - impl StreamSink for RecordingSink { - fn submit(&self, _processed: Vec) -> Result<()> { - self.received_count.fetch_add(1, Ordering::SeqCst); - Ok(()) - } - } - - // Custom hint code for testing (0x7FFF_0200 to avoid conflicts) - const RACE_HINT: u32 = 0x7FFF_0200; - - // Test parameters designed to maximize race window hits - const NUM_THREADS: usize = 32; - const HINTS_PER_BATCH: usize = 100; - const ITERATIONS: usize = 100; - const TIMEOUT_MS: u64 = 500; - - for iteration in 0..ITERATIONS { - let received_count = Arc::new(AtomicUsize::new(0)); - let sink = RecordingSink { received_count: Arc::clone(&received_count) }; - - let p = HintsProcessor::builder(sink) - .num_threads(NUM_THREADS) - .custom_hint(RACE_HINT, |data| { - // Tiny random delay (0-1000ns) to desynchronize worker completions - // This maximizes the chance of workers completing just as - // drainer is between checking buffer and entering wait - let hash = data[0].wrapping_mul(2654435761); - let delay_ns = hash % 1000; - if delay_ns > 0 { - std::hint::spin_loop(); - // Tiny busy-wait (more precise than sleep for nanoseconds) - let start = Instant::now(); - while start.elapsed().as_nanos() < delay_ns as u128 { - std::hint::spin_loop(); - } - } - Ok(vec![data[0] + 1]) - }) - .build() - .unwrap(); - - // Build batch of hints - let mut data = Vec::with_capacity(HINTS_PER_BATCH * 2); - for i in 0..HINTS_PER_BATCH { - data.push(make_header(RACE_HINT, 1)); - data.push(i as u64); - } - - // Process hints - p.process_hints(&data, false).unwrap(); - - // Use watchdog to detect hang - let completed = Arc::new(AtomicBool::new(false)); - let completed_clone = Arc::clone(&completed); - - let watchdog = thread::spawn(move || { - let start = Instant::now(); - while !completed_clone.load(Ordering::Acquire) { - if start.elapsed() > Duration::from_millis(TIMEOUT_MS) { - return false; // Timeout - race condition triggered! - } - thread::sleep(Duration::from_millis(10)); - } - true // Completed in time - }); - - // Wait for completion - let result = p.wait_for_completion(); - completed.store(true, Ordering::Release); - - let watchdog_ok = watchdog.join().unwrap(); - assert!( - watchdog_ok, - "RACE CONDITION DETECTED: Iteration {} hung for >{}ms. \ - Drainer missed worker notifications.", - iteration, - TIMEOUT_MS - ); - - assert!(result.is_ok(), "Iteration {} failed: {:?}", iteration, result); - - // Verify all hints were processed - let count = received_count.load(Ordering::SeqCst); - assert_eq!( - count, HINTS_PER_BATCH, - "Iteration {}: Expected {} results, got {}", - iteration, HINTS_PER_BATCH, count - ); - } - - println!( - "\n========================================\n\ - Race Condition Regression Test PASSED\n\ - Iterations: {}\n\ - Hints per iteration: {}\n\ - Worker threads: {}\n\ - No hangs detected!\n\ - ========================================\n", - ITERATIONS, HINTS_PER_BATCH, NUM_THREADS - ); - } } diff --git a/precompiles/hints/src/lib.rs b/precompiles/hints/src/lib.rs index 82eefa35c..3460a87b5 100644 --- a/precompiles/hints/src/lib.rs +++ b/precompiles/hints/src/lib.rs @@ -1,3 +1,5 @@ mod hints_processor; +mod ordered_result_buffer; pub use hints_processor::HintsProcessor; +pub use ordered_result_buffer::{BufferStatus, OrderedResultBuffer}; diff --git a/precompiles/hints/src/ordered_result_buffer.rs b/precompiles/hints/src/ordered_result_buffer.rs new file mode 100644 index 000000000..4db159b6f --- /dev/null +++ b/precompiles/hints/src/ordered_result_buffer.rs @@ -0,0 +1,490 @@ +//! Ordered Result Buffer +//! +//! A thread-safe buffer that allows out-of-order insertion but guarantees +//! in-order consumption. Used for parallel processing where results must +//! be output in the original submission order. + +use std::collections::VecDeque; +use std::sync::{Condvar, Mutex}; + +/// Status of the buffer +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BufferStatus { + /// Normal operation + Active, + /// Shutdown requested - no more results expected + Shutdown, + /// Error occurred - stop processing + Error, +} + +/// Internal state protected by mutex +struct State { + /// Ring buffer of slots: None = pending, Some = ready + buffer: VecDeque>, + /// Sequence ID of the next result to drain (buffer[0] corresponds to this) + next_drain: usize, + /// Current status + status: BufferStatus, +} + +/// A thread-safe buffer for reordering results from parallel workers. +/// +/// Workers can fill slots out of order using sequence IDs, but consumers +/// always receive results in the original order. +/// +/// # Example +/// +/// ```ignore +/// let buffer = OrderedResultBuffer::new(); +/// +/// // Producer: reserve slots and fill (possibly out of order) +/// let seq1 = buffer.reserve(); // 0 +/// let seq2 = buffer.reserve(); // 1 +/// buffer.fill(seq2, "second"); // Fill out of order +/// buffer.fill(seq1, "first"); +/// +/// // Consumer: always gets results in order +/// assert_eq!(buffer.take_next(), Some("first")); +/// assert_eq!(buffer.take_next(), Some("second")); +/// ``` +pub struct OrderedResultBuffer { + inner: Mutex>, + signal: Condvar, +} + +impl OrderedResultBuffer { + /// Create a new empty buffer. + pub fn new() -> Self { + Self { + inner: Mutex::new(State { + buffer: VecDeque::new(), + next_drain: 0, + status: BufferStatus::Active, + }), + signal: Condvar::new(), + } + } + + /// Reserve a slot in the buffer and return its sequence ID. + /// + /// The caller must later call `fill()` with this sequence ID. + pub fn reserve(&self) -> usize { + let mut state = self.inner.lock().unwrap(); + let seq = state.next_drain + state.buffer.len(); + state.buffer.push_back(None); + seq + } + + /// Reserve a slot and immediately fill it with a value. + /// + /// Equivalent to `reserve()` followed by `fill()`, but atomic. + /// Useful for passthrough items that don't need async processing. + pub fn reserve_and_fill(&self, value: T) -> usize { + let mut state = self.inner.lock().unwrap(); + let seq = state.next_drain + state.buffer.len(); + state.buffer.push_back(Some(value)); + // Notify while holding lock to prevent race + self.signal.notify_all(); + seq + } + + /// Fill a previously reserved slot with a value. + /// + /// # Panics + /// + /// Panics if `seq_id` was not reserved or was already filled. + pub fn fill(&self, seq_id: usize, value: T) { + let mut state = self.inner.lock().unwrap(); + + // Calculate offset in buffer + let offset = seq_id.checked_sub(state.next_drain); + let offset = match offset { + Some(o) if o < state.buffer.len() => o, + _ => { + // Slot was already drained or invalid - ignore + // This can happen after reset() if stale workers complete + return; + } + }; + + // Fill the slot + debug_assert!(state.buffer[offset].is_none(), "Slot {} already filled", seq_id); + state.buffer[offset] = Some(value); + + // Notify while holding lock to prevent race condition + self.signal.notify_all(); + } + + /// Take the next in-order result, blocking until one is ready. + /// + /// Returns `None` if the buffer is shutdown or in error state + /// and no more results are available. + pub fn take_next(&self) -> Option { + let mut state = self.inner.lock().unwrap(); + + loop { + // Check if front slot has a ready result + if let Some(Some(_)) = state.buffer.front() { + // Pop and return the value + let value = state.buffer.pop_front().unwrap().unwrap(); + state.next_drain += 1; + // Notify wait_until_drained that buffer changed + self.signal.notify_all(); + return Some(value); + } + + // No ready result - check if we should stop waiting + match state.status { + BufferStatus::Active => { + // Wait for notification + state = self.signal.wait(state).unwrap(); + } + BufferStatus::Shutdown | BufferStatus::Error => { + // Check one more time after status change + if let Some(Some(_)) = state.buffer.front() { + let value = state.buffer.pop_front().unwrap().unwrap(); + state.next_drain += 1; + self.signal.notify_all(); + return Some(value); + } + return None; + } + } + } + } + + /// Wait until all reserved slots have been drained. + /// + /// Returns the final status (Active means all drained, Error/Shutdown + /// means stopped early). + pub fn wait_until_drained(&self) -> BufferStatus { + let mut state = self.inner.lock().unwrap(); + + loop { + if state.buffer.is_empty() { + return BufferStatus::Active; + } + + if state.status == BufferStatus::Error { + return BufferStatus::Error; + } + + state = self.signal.wait(state).unwrap(); + } + } + + /// Signal an error condition. Wakes all waiting threads. + pub fn set_error(&self) { + let mut state = self.inner.lock().unwrap(); + state.status = BufferStatus::Error; + self.signal.notify_all(); + } + + /// Signal shutdown. Wakes all waiting threads. + pub fn shutdown(&self) { + let mut state = self.inner.lock().unwrap(); + state.status = BufferStatus::Shutdown; + self.signal.notify_all(); + } + + /// Get current status. + pub fn status(&self) -> BufferStatus { + self.inner.lock().unwrap().status + } + + /// Check if buffer is empty (all results drained). + pub fn is_empty(&self) -> bool { + self.inner.lock().unwrap().buffer.is_empty() + } + + /// Get count of pending/ready slots. + pub fn len(&self) -> usize { + self.inner.lock().unwrap().buffer.len() + } + + /// Reset the buffer for reuse. + /// + /// Clears all pending slots and resets sequence counter. + pub fn reset(&self) { + let mut state = self.inner.lock().unwrap(); + state.buffer.clear(); + state.next_drain = 0; + state.status = BufferStatus::Active; + } +} + +impl Default for OrderedResultBuffer { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + use std::thread; + use std::time::{Duration, Instant}; + + #[test] + fn test_basic_order() { + let buf = OrderedResultBuffer::new(); + + let seq0 = buf.reserve(); + let seq1 = buf.reserve(); + let seq2 = buf.reserve(); + + assert_eq!(seq0, 0); + assert_eq!(seq1, 1); + assert_eq!(seq2, 2); + + // Fill out of order + buf.fill(seq2, "third"); + buf.fill(seq0, "first"); + buf.fill(seq1, "second"); + + // Take in order + assert_eq!(buf.take_next(), Some("first")); + assert_eq!(buf.take_next(), Some("second")); + assert_eq!(buf.take_next(), Some("third")); + } + + #[test] + fn test_reserve_and_fill() { + let buf = OrderedResultBuffer::new(); + + buf.reserve_and_fill("immediate"); + let seq = buf.reserve(); + buf.fill(seq, "delayed"); + + assert_eq!(buf.take_next(), Some("immediate")); + assert_eq!(buf.take_next(), Some("delayed")); + } + + #[test] + fn test_blocking_take() { + let buf = Arc::new(OrderedResultBuffer::new()); + let seq = buf.reserve(); + + let buf_clone = Arc::clone(&buf); + let handle = thread::spawn(move || { + thread::sleep(Duration::from_millis(50)); + buf_clone.fill(seq, 42); + }); + + let start = Instant::now(); + let value = buf.take_next(); + let elapsed = start.elapsed(); + + assert_eq!(value, Some(42)); + assert!(elapsed >= Duration::from_millis(40)); + + handle.join().unwrap(); + } + + #[test] + fn test_out_of_order_parallel() { + let buf = Arc::new(OrderedResultBuffer::new()); + let num_items = 100; + + // Reserve all slots + let seqs: Vec<_> = (0..num_items).map(|_| buf.reserve()).collect(); + + // Fill from multiple threads in reverse order + let handles: Vec<_> = seqs + .into_iter() + .rev() + .map(|seq| { + let buf = Arc::clone(&buf); + thread::spawn(move || { + buf.fill(seq, seq * 10); + }) + }) + .collect(); + + // Drain and verify order + for i in 0..num_items { + let value = buf.take_next().unwrap(); + assert_eq!(value, i * 10); + } + + for h in handles { + h.join().unwrap(); + } + } + + #[test] + fn test_shutdown_wakes_waiter() { + let buf = Arc::new(OrderedResultBuffer::::new()); + buf.reserve(); // Reserve but don't fill + + let buf_clone = Arc::clone(&buf); + let handle = thread::spawn(move || { + thread::sleep(Duration::from_millis(50)); + buf_clone.shutdown(); + }); + + let start = Instant::now(); + let result = buf.take_next(); + let elapsed = start.elapsed(); + + assert!(result.is_none()); + assert!(elapsed >= Duration::from_millis(40)); + assert_eq!(buf.status(), BufferStatus::Shutdown); + + handle.join().unwrap(); + } + + #[test] + fn test_error_wakes_waiter() { + let buf = Arc::new(OrderedResultBuffer::::new()); + buf.reserve(); + + let buf_clone = Arc::clone(&buf); + let handle = thread::spawn(move || { + thread::sleep(Duration::from_millis(50)); + buf_clone.set_error(); + }); + + let result = buf.take_next(); + assert!(result.is_none()); + assert_eq!(buf.status(), BufferStatus::Error); + + handle.join().unwrap(); + } + + #[test] + fn test_wait_until_drained() { + let buf = Arc::new(OrderedResultBuffer::new()); + + // Reserve and fill slots + for i in 0..5 { + buf.reserve_and_fill(i); + } + + // Drain in another thread + let buf_clone = Arc::clone(&buf); + let drainer = thread::spawn(move || while let Some(_) = buf_clone.take_next() {}); + + // Wait for drain + let status = buf.wait_until_drained(); + assert_eq!(status, BufferStatus::Active); + assert!(buf.is_empty()); + + buf.shutdown(); + drainer.join().unwrap(); + } + + #[test] + fn test_reset() { + let buf = OrderedResultBuffer::new(); + + buf.reserve_and_fill(1); + buf.reserve_and_fill(2); + buf.set_error(); + + assert_eq!(buf.status(), BufferStatus::Error); + assert_eq!(buf.len(), 2); + + buf.reset(); + + assert_eq!(buf.status(), BufferStatus::Active); + assert!(buf.is_empty()); + + // Can use again + buf.reserve_and_fill(100); + assert_eq!(buf.take_next(), Some(100)); + } + + #[test] + fn test_stale_fill_after_reset() { + let buf = OrderedResultBuffer::new(); + + let seq = buf.reserve(); + buf.reset(); + + // Stale fill should be ignored (not panic) + buf.fill(seq, "stale"); + + assert!(buf.is_empty()); + } + + #[test] + fn test_stress_throughput() { + let buf = Arc::new(OrderedResultBuffer::new()); + let num_items = 100_000; + + let producer_buf = Arc::clone(&buf); + let producer = thread::spawn(move || { + for i in 0..num_items { + producer_buf.reserve_and_fill(i); + } + producer_buf.shutdown(); + }); + + let start = Instant::now(); + let mut count = 0; + while let Some(_) = buf.take_next() { + count += 1; + } + let elapsed = start.elapsed(); + + producer.join().unwrap(); + + assert_eq!(count, num_items); + let ops_per_sec = num_items as f64 / elapsed.as_secs_f64(); + println!( + "OrderedResultBuffer stress: {} ops in {:?} ({:.0} ops/sec)", + num_items, elapsed, ops_per_sec + ); + assert!(ops_per_sec > 100_000.0, "Too slow: {:.0} ops/sec", ops_per_sec); + } + + #[test] + fn test_parallel_stress() { + let buf = Arc::new(OrderedResultBuffer::new()); + let num_items = 10_000; + let num_producers = 8; + + // Reserve all first (single threaded) + let seqs: Vec<_> = (0..num_items).map(|_| buf.reserve()).collect(); + + // Distribute to producers + let chunk_size = num_items / num_producers; + let producers: Vec<_> = (0..num_producers) + .map(|p| { + let buf = Arc::clone(&buf); + let chunk: Vec<_> = seqs[p * chunk_size..(p + 1) * chunk_size].to_vec(); + thread::spawn(move || { + for seq in chunk { + buf.fill(seq, seq * 2); + } + }) + }) + .collect(); + + // Consume + let buf_consumer = Arc::clone(&buf); + let consumer = thread::spawn(move || { + let mut results = Vec::with_capacity(num_items); + for _ in 0..num_items { + if let Some(v) = buf_consumer.take_next() { + results.push(v); + } + } + results + }); + + for p in producers { + p.join().unwrap(); + } + + let results = consumer.join().unwrap(); + + // Verify in-order + for (i, &v) in results.iter().enumerate() { + assert_eq!(v, i * 2, "Out of order at index {}", i); + } + } +} From ebd4b64cb199f5efe5e65b48f0b9cc397ed1addb Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 19 Feb 2026 06:22:03 +0000 Subject: [PATCH 597/782] adding dbg messages --- precompiles/hints/src/hints_processor.rs | 444 +++++++++++++--- precompiles/hints/src/lib.rs | 2 - .../hints/src/ordered_result_buffer.rs | 490 ------------------ 3 files changed, 365 insertions(+), 571 deletions(-) delete mode 100644 precompiles/hints/src/ordered_result_buffer.rs diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 43509a964..c4cef4a68 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -4,13 +4,12 @@ //! that are received as a stream of `u64` values. Hints are used to provide preprocessed //! data to precompile operations in the ZisK zkVM. -use crate::ordered_result_buffer::{BufferStatus, OrderedResultBuffer}; use anyhow::Result; use rayon::{ThreadPool, ThreadPoolBuilder}; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Condvar, Mutex}; use std::time::Instant; use tracing::{debug, info}; use zisk_common::io::{StreamProcessor, StreamSink}; @@ -35,6 +34,55 @@ use ziskos_hints::handlers::secp256k1::{ use ziskos_hints::handlers::secp256r1::secp256r1_ecdsa_verify_hint; use ziskos_hints::handlers::sha256::sha256_hint; +/// Ordered result buffer with drain state. +/// +/// This structure maintains a VecDeque that holds processed results in order, +/// allowing out-of-order completion while ensuring in-order output. +struct ResultQueue { + /// The result buffer: None = pending, Some(Ok(...)) = ready, Some(Err(...)) = error + buffer: VecDeque>>>, + /// Sequence ID of the next result to drain from buffer[0] + next_drain_seq: usize, +} + +/// Thread-safe shared state for parallel hint processing. +struct HintProcessorState { + /// Ordered results ready for draining + queue: Mutex, + /// Notifies drainer thread when a hint completes + drain_signal: Condvar, + /// Next sequence ID to assign to incoming hints + next_seq: AtomicUsize, + /// Signals processing should stop + error_flag: AtomicBool, + /// Signals drainer thread to shut down + shutdown: AtomicBool, + /// Invalidates stale workers after reset + generation: AtomicUsize, +} + +impl HintProcessorState { + fn new() -> Self { + Self { + queue: Mutex::new(ResultQueue { buffer: VecDeque::new(), next_drain_seq: 0 }), + drain_signal: Condvar::new(), + next_seq: AtomicUsize::new(0), + error_flag: AtomicBool::new(false), + shutdown: AtomicBool::new(false), + generation: AtomicUsize::new(0), + } + } + + fn reset(&self) { + self.error_flag.store(false, Ordering::Release); + self.next_seq.store(0, Ordering::Relaxed); + self.generation.fetch_add(1, Ordering::SeqCst); + let mut queue = self.queue.lock().unwrap(); + queue.buffer.clear(); + queue.next_drain_seq = 0; + } +} + /// Type alias for custom hint handler functions. pub type CustomHintHandler = Arc Result> + Send + Sync>; @@ -96,20 +144,20 @@ impl HintsProcessorBuilder { .build() .map_err(|e| anyhow::anyhow!("Failed to create thread pool: {}", e))?; - let buffer = Arc::new(OrderedResultBuffer::new()); + let state = Arc::new(HintProcessorState::new()); let hints_sink = self.hints_sink; // Spawn drainer thread - let drainer_buffer = Arc::clone(&buffer); + let drainer_state = Arc::clone(&state); let drainer_sink = Arc::clone(&hints_sink); let drainer_thread = std::thread::spawn(move || { - HintsProcessor::drainer_thread(drainer_buffer, drainer_sink); + HintsProcessor::drainer_thread(drainer_state, drainer_sink); }); Ok(HintsProcessor { pool, num_hint: AtomicUsize::new(0), - buffer, + state, stats: if self.enable_stats { Some(Mutex::new(HashMap::new())) } else { None }, hints_sink, drainer_thread: ManuallyDrop::new(drainer_thread), @@ -132,8 +180,8 @@ pub struct HintsProcessor { num_hint: AtomicUsize, - /// Ordered result buffer for parallel processing with in-order output - buffer: Arc>>>, + /// Shared state for parallel hint processing + state: Arc, /// Optional statistics collected during hint processing (for debugging). stats: Option>>, @@ -213,6 +261,7 @@ impl HintsProcessor { /// * `Ok(false)` - Batch processed successfully, no CTRL_END /// * `Err` - If a previous error occurred or hints are malformed pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { + debug!("[PROCESS] process_hints called with {} u64s, first_batch={}", hints.len(), first_batch); let mut has_ctrl_end = false; // Take any pending partial hint from previous batch @@ -222,7 +271,7 @@ impl HintsProcessor { let mut idx = 0; while idx < hints.len() { // Check for error before processing each hint - if self.buffer.status() == BufferStatus::Error { + if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to previous error")); } let (parsed_hint, consumed) = @@ -319,32 +368,53 @@ impl HintsProcessor { break; } HintCode::Ctrl(CtrlHint::Cancel) => { - // Cancel current stream: set error - self.buffer.set_error(); + // Cancel current stream: set error and notify + self.state.error_flag.store(true, Ordering::Release); + self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream cancelled")); } HintCode::Ctrl(CtrlHint::Error) => { // External error signal - self.buffer.set_error(); + self.state.error_flag.store(true, Ordering::Release); + self.state.drain_signal.notify_all(); return Err(anyhow::anyhow!("Stream error signalled")); } _ => {} // Built-in data hint or custom hint; continue processing } - // Handle pass-through hints immediately (no worker needed) - if hint.is_passthrough { - self.buffer.reserve_and_fill(Ok(hint.data.clone())); - idx += length; - continue; - } + // Capture generation outside mutex - SeqCst provides sufficient ordering + let generation = self.state.generation.load(Ordering::SeqCst); + + // Atomically reserve slot - use Relaxed for seq since mutex provides ordering + let seq_id = { + let mut queue = self.state.queue.lock().unwrap(); + let seq = self.state.next_seq.fetch_add(1, Ordering::Relaxed); + + // Handle pass-through hints immediately + if hint.is_passthrough { + queue.buffer.push_back(Some(Ok(hint.data.clone()))); + // Notify immediately while holding the lock to ensure drainer sees the result + // Release lock after this block, avoiding duplicate notification + drop(queue); + // Use notify_all since wait_for_completion also waits on this condvar + self.state.drain_signal.notify_all(); + // Continue to next hint without spawning worker + idx += length; + continue; + } else { + queue.buffer.push_back(None); + } + + seq + }; - // Reserve slot and spawn worker for async processing - let seq_id = self.buffer.reserve(); - let buffer = Arc::clone(&self.buffer); + // Spawn processing task for async hints (Noop already handled above) + let state = Arc::clone(&self.state); let custom_handlers = Arc::clone(&self.custom_handlers); self.pool.spawn(move || { - let result = Self::dispatch_hint(hint, custom_handlers); - buffer.fill(seq_id, result); + debug!("[WORKER] seq={} dispatched", seq_id); + Self::worker_thread(state, hint, generation, seq_id, custom_handlers); + debug!("[WORKER] seq={} done", seq_id); }); idx += length; @@ -387,31 +457,165 @@ impl HintsProcessor { } } - /// Drainer thread that takes results in order and submits to sink. - fn drainer_thread( - buffer: Arc>>>, - hints_sink: Arc, + /// Worker thread that processes a single hint and stores the result. + /// + /// # Arguments + /// + /// * `state` - Shared processor state + /// * `hint` - The hint to process + /// * `generation` - Generation number for detecting stale workers + /// * `seq_id` - Sequence ID for ordering results + /// * `custom_handlers` - Custom hint handlers + fn worker_thread( + state: Arc, + hint: PrecompileHint, + generation: usize, + seq_id: usize, + custom_handlers: Arc>, ) { + // Check generation first to detect stale workers (before processing) + let current_gen = state.generation.load(Ordering::SeqCst); + if generation != current_gen { + debug!("[WORKER] seq={} early return: generation mismatch (worker={}, current={})", seq_id, generation, current_gen); + return; + } + + // Catch panics to prevent permanently-stuck None slots in the buffer. + // If dispatch_hint panics, Rayon catches it silently but the slot would + // stay None forever, blocking the drainer from making progress. + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + if state.error_flag.load(Ordering::Acquire) { + Err(anyhow::anyhow!("Processing stopped due to error")) + } else { + Self::dispatch_hint(hint, custom_handlers) + } + })) + .unwrap_or_else(|panic_info| { + let msg = if let Some(s) = panic_info.downcast_ref::<&str>() { + s.to_string() + } else if let Some(s) = panic_info.downcast_ref::() { + s.clone() + } else { + "unknown panic".to_string() + }; + Err(anyhow::anyhow!("Worker panicked processing hint: {}", msg)) + }); + + debug!("[WORKER] seq={} processed, acquiring lock", seq_id); + + // Store result - MUST fill slot even if error occurred + let mut queue = state.queue.lock().unwrap(); + + // Check generation again in case reset happened during processing + let current_gen = state.generation.load(Ordering::SeqCst); + if generation != current_gen { + debug!("[WORKER] seq={} early return: generation mismatch after processing", seq_id); + return; + } + + // Calculate offset in buffer; handle drained slots + if seq_id < queue.next_drain_seq { + debug!("[WORKER] seq={} early return: seq_id < next_drain_seq ({})", seq_id, queue.next_drain_seq); + return; + } + let offset = seq_id - queue.next_drain_seq; + + // Check if slot exists - if not, drainer already processed and removed it + if offset >= queue.buffer.len() { + debug!("[WORKER] seq={} early return: offset {} >= buffer.len() {}", seq_id, offset, queue.buffer.len()); + return; + } + + // Fill the slot to allow drainer to proceed (critical for ordering) + queue.buffer[offset] = Some(result); + debug!("[WORKER] seq={} filled slot at offset {}, buffer_len={}", seq_id, offset, queue.buffer.len()); + + // Release lock before notifying + drop(queue); + + // Notify drainer thread (use notify_all to wake any waiting threads) + debug!("[WORKER] seq={} calling notify_all", seq_id); + state.drain_signal.notify_all(); + } + + /// Drainer thread that waits for hints to complete and drains ready results from queue. + fn drainer_thread(state: Arc, hints_sink: Arc) { debug!("[DRAINER] Thread started"); + loop { + debug!("[DRAINER] Attempting to acquire queue lock"); + let mut queue = state.queue.lock().unwrap(); + debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", + queue.buffer.len(), queue.next_drain_seq); + + // Check for shutdown + if state.shutdown.load(Ordering::Acquire) { + debug!("[DRAINER] Shutdown signal received, exiting"); + break; + } - while let Some(result) = buffer.take_next() { - match result { - Ok(data) => { - debug!("[DRAINER] Submitting {} u64s", data.len()); - if let Err(e) = hints_sink.submit(data) { - eprintln!("Error submitting to sink: {}", e); - buffer.set_error(); + // Drain all consecutive ready results from the front + let mut drained_any = false; + while let Some(Some(res)) = queue.buffer.front() { + drained_any = true; + let current_seq = queue.next_drain_seq; + match res { + Ok(data) => { + // Clone data before dropping lock + debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); + let data_to_submit = data.clone(); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; + + // Drop lock before submitting to avoid blocking workers + debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); + drop(queue); + + // Submit to sink + debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); + if let Err(e) = hints_sink.submit(data_to_submit) { + eprintln!("Error submitting to sink: {}", e); + state.error_flag.store(true, Ordering::Release); + state.drain_signal.notify_all(); + return; + } + debug!("[DRAINER] Completed submit seq={}", current_seq); + + // Re-acquire lock for next iteration + debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); + queue = state.queue.lock().unwrap(); + } + Err(e) => { + // Error found - signal to stop + state.error_flag.store(true, Ordering::Release); + eprintln!("[seq={}] Error: {}", queue.next_drain_seq, e); + queue.buffer.pop_front(); + queue.next_drain_seq += 1; + state.drain_signal.notify_all(); return; } } - Err(e) => { - eprintln!("Hint processing error: {}", e); - buffer.set_error(); - return; - } } - } + // If we drained any results, notify wait_for_completion that buffer changed + if drained_any { + debug!("[DRAINER] Notifying wait_for_completion"); + state.drain_signal.notify_all(); + } + + // Check for shutdown again before waiting + if state.shutdown.load(Ordering::Acquire) { + debug!("[DRAINER] Shutdown signal received before wait, exiting"); + break; + } + + // Wait for notification that a hint completed + debug!("[DRAINER] Waiting on drain_signal condvar"); + #[allow(unused_assignments)] + { + queue = state.drain_signal.wait(queue).unwrap(); + } + debug!("[DRAINER] Woke up from condvar wait"); + } debug!("[DRAINER] Thread exiting"); } @@ -425,10 +629,20 @@ impl HintsProcessor { /// * `Ok(())` - All hints processed successfully /// * `Err` - If an error occurred during processing pub fn wait_for_completion(&self) -> Result<()> { - let status = self.buffer.wait_until_drained(); - if status == BufferStatus::Error { + let mut queue = self.state.queue.lock().unwrap(); + + while !queue.buffer.is_empty() { + if self.state.error_flag.load(Ordering::Acquire) { + return Err(anyhow::anyhow!("Processing stopped due to error")); + } + // Wait for notification that buffer state changed + queue = self.state.drain_signal.wait(queue).unwrap(); + } + + if self.state.error_flag.load(Ordering::Acquire) { return Err(anyhow::anyhow!("Processing stopped due to error")); } + Ok(()) } @@ -509,7 +723,7 @@ impl HintsProcessor { fn reset(&self) { self.num_hint.store(0, Ordering::Relaxed); - self.buffer.reset(); + self.state.reset(); if let Some(stats) = self.stats.as_ref() { stats.lock().unwrap().clear(); } @@ -527,7 +741,8 @@ impl HintsProcessor { impl Drop for HintsProcessor { fn drop(&mut self) { // Signal drainer thread to shut down - self.buffer.shutdown(); + self.state.shutdown.store(true, Ordering::Release); + self.state.drain_signal.notify_all(); // Join the drainer thread to ensure clean shutdown // Safety: We only take the value once in drop @@ -599,7 +814,9 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Buffer should be empty after completion - assert!(p.buffer.is_empty()); + let queue = p.state.queue.lock().unwrap(); + assert!(queue.buffer.is_empty()); + assert_eq!(queue.next_drain_seq, 1); } #[test] @@ -617,8 +834,10 @@ mod tests { assert!(p.process_hints(&data, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Verify all hints were processed (buffer empty) - assert!(p.buffer.is_empty()); + // Verify all hints were processed (buffer empty, next_drain_seq advanced) + let queue = p.state.queue.lock().unwrap(); + assert!(queue.buffer.is_empty()); + assert_eq!(queue.next_drain_seq, 3); } #[test] @@ -632,8 +851,10 @@ mod tests { assert!(p.process_hints(&data2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Verify all hints were processed - assert!(p.buffer.is_empty()); + // Verify sequence continued across calls + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 2); + assert!(queue.buffer.is_empty()); } #[test] @@ -644,7 +865,8 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // No hints processed - assert!(p.buffer.is_empty()); + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); } // Negative tests @@ -684,14 +906,15 @@ mod tests { // Reset should clear any error state p.reset(); - assert_eq!(p.buffer.status(), BufferStatus::Active); + assert!(!p.state.error_flag.load(Ordering::Acquire)); // Should be able to process new hints after reset (8 bytes = 1 u64) let good = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x42]; assert!(p.process_hints(&good, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - assert!(p.buffer.is_empty()); + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); } #[test] @@ -716,7 +939,11 @@ mod tests { p.process_hints(&end, false).unwrap(); // Buffer should already be empty - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert!(queue.buffer.is_empty()); + assert_eq!(queue.next_drain_seq, 2); + } // Explicit wait should be instant assert!(p.wait_for_completion().is_ok()); @@ -732,7 +959,7 @@ mod tests { assert!(result.unwrap_err().to_string().contains("cancelled")); // Error flag should be set - assert_eq!(p.buffer.status(), BufferStatus::Error); + assert!(p.state.error_flag.load(Ordering::Acquire)); } #[test] @@ -745,7 +972,7 @@ mod tests { assert!(result.unwrap_err().to_string().contains("error")); // Error flag should be set - assert_eq!(p.buffer.status(), BufferStatus::Error); + assert!(p.state.error_flag.load(Ordering::Acquire)); } #[test] @@ -874,7 +1101,7 @@ mod tests { // Wait should detect the error from drainer thread std::thread::sleep(std::time::Duration::from_millis(100)); - assert_eq!(p.buffer.status(), BufferStatus::Error); + assert!(p.state.error_flag.load(Ordering::Acquire)); } // Builder tests @@ -1114,14 +1341,22 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Verify no results yet (hint is partial) - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + assert!(queue.buffer.is_empty()); + } // Second batch completes the hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result (buffer drained) - assert!(p.buffer.is_empty()); + // Now we should have the complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1138,14 +1373,21 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Verify no results yet - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + } // Second batch completes the hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result (buffer drained) - assert!(p.buffer.is_empty()); + // Now we should have the complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1167,14 +1409,21 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // Still no complete results - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + } // Third batch completes the hint assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Now we should have the complete result (buffer drained) - assert!(p.buffer.is_empty()); + // Now we should have the complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1198,18 +1447,31 @@ mod tests { assert!(p.wait_for_completion().is_ok()); // No results yet - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 0); + } // Complete first hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); + // Should have first result now + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + } + // Complete second hint assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have both results (buffer drained) - assert!(p.buffer.is_empty()); + // Should have both results + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 2); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1229,12 +1491,22 @@ mod tests { assert!(p.process_hints(&batch1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); + // Should have result for complete hint + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + } + // Complete the partial hint assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have both results (buffer drained) - assert!(p.buffer.is_empty()); + // Should have both results + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 2); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1248,8 +1520,12 @@ mod tests { assert!(p.process_hints(&batch1, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have result (buffer drained) - assert!(p.buffer.is_empty()); + // Should have result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1276,8 +1552,12 @@ mod tests { // End stream should wait for all hints to complete assert!(p.process_hints(&end, false).is_ok()); - // Everything should be processed (buffer drained) - assert!(p.buffer.is_empty()); + // Everything should be processed + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] @@ -1309,8 +1589,10 @@ mod tests { assert!(p.process_hints(&batch2, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Buffer should be drained - assert!(p.buffer.is_empty()); + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + } } #[test] @@ -1334,8 +1616,12 @@ mod tests { assert!(p.process_hints(&batch3, false).is_ok()); assert!(p.wait_for_completion().is_ok()); - // Should have complete result (buffer drained) - assert!(p.buffer.is_empty()); + // Should have complete result + { + let queue = p.state.queue.lock().unwrap(); + assert_eq!(queue.next_drain_seq, 1); + assert!(queue.buffer.is_empty()); + } } #[test] diff --git a/precompiles/hints/src/lib.rs b/precompiles/hints/src/lib.rs index 3460a87b5..82eefa35c 100644 --- a/precompiles/hints/src/lib.rs +++ b/precompiles/hints/src/lib.rs @@ -1,5 +1,3 @@ mod hints_processor; -mod ordered_result_buffer; pub use hints_processor::HintsProcessor; -pub use ordered_result_buffer::{BufferStatus, OrderedResultBuffer}; diff --git a/precompiles/hints/src/ordered_result_buffer.rs b/precompiles/hints/src/ordered_result_buffer.rs deleted file mode 100644 index 4db159b6f..000000000 --- a/precompiles/hints/src/ordered_result_buffer.rs +++ /dev/null @@ -1,490 +0,0 @@ -//! Ordered Result Buffer -//! -//! A thread-safe buffer that allows out-of-order insertion but guarantees -//! in-order consumption. Used for parallel processing where results must -//! be output in the original submission order. - -use std::collections::VecDeque; -use std::sync::{Condvar, Mutex}; - -/// Status of the buffer -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BufferStatus { - /// Normal operation - Active, - /// Shutdown requested - no more results expected - Shutdown, - /// Error occurred - stop processing - Error, -} - -/// Internal state protected by mutex -struct State { - /// Ring buffer of slots: None = pending, Some = ready - buffer: VecDeque>, - /// Sequence ID of the next result to drain (buffer[0] corresponds to this) - next_drain: usize, - /// Current status - status: BufferStatus, -} - -/// A thread-safe buffer for reordering results from parallel workers. -/// -/// Workers can fill slots out of order using sequence IDs, but consumers -/// always receive results in the original order. -/// -/// # Example -/// -/// ```ignore -/// let buffer = OrderedResultBuffer::new(); -/// -/// // Producer: reserve slots and fill (possibly out of order) -/// let seq1 = buffer.reserve(); // 0 -/// let seq2 = buffer.reserve(); // 1 -/// buffer.fill(seq2, "second"); // Fill out of order -/// buffer.fill(seq1, "first"); -/// -/// // Consumer: always gets results in order -/// assert_eq!(buffer.take_next(), Some("first")); -/// assert_eq!(buffer.take_next(), Some("second")); -/// ``` -pub struct OrderedResultBuffer { - inner: Mutex>, - signal: Condvar, -} - -impl OrderedResultBuffer { - /// Create a new empty buffer. - pub fn new() -> Self { - Self { - inner: Mutex::new(State { - buffer: VecDeque::new(), - next_drain: 0, - status: BufferStatus::Active, - }), - signal: Condvar::new(), - } - } - - /// Reserve a slot in the buffer and return its sequence ID. - /// - /// The caller must later call `fill()` with this sequence ID. - pub fn reserve(&self) -> usize { - let mut state = self.inner.lock().unwrap(); - let seq = state.next_drain + state.buffer.len(); - state.buffer.push_back(None); - seq - } - - /// Reserve a slot and immediately fill it with a value. - /// - /// Equivalent to `reserve()` followed by `fill()`, but atomic. - /// Useful for passthrough items that don't need async processing. - pub fn reserve_and_fill(&self, value: T) -> usize { - let mut state = self.inner.lock().unwrap(); - let seq = state.next_drain + state.buffer.len(); - state.buffer.push_back(Some(value)); - // Notify while holding lock to prevent race - self.signal.notify_all(); - seq - } - - /// Fill a previously reserved slot with a value. - /// - /// # Panics - /// - /// Panics if `seq_id` was not reserved or was already filled. - pub fn fill(&self, seq_id: usize, value: T) { - let mut state = self.inner.lock().unwrap(); - - // Calculate offset in buffer - let offset = seq_id.checked_sub(state.next_drain); - let offset = match offset { - Some(o) if o < state.buffer.len() => o, - _ => { - // Slot was already drained or invalid - ignore - // This can happen after reset() if stale workers complete - return; - } - }; - - // Fill the slot - debug_assert!(state.buffer[offset].is_none(), "Slot {} already filled", seq_id); - state.buffer[offset] = Some(value); - - // Notify while holding lock to prevent race condition - self.signal.notify_all(); - } - - /// Take the next in-order result, blocking until one is ready. - /// - /// Returns `None` if the buffer is shutdown or in error state - /// and no more results are available. - pub fn take_next(&self) -> Option { - let mut state = self.inner.lock().unwrap(); - - loop { - // Check if front slot has a ready result - if let Some(Some(_)) = state.buffer.front() { - // Pop and return the value - let value = state.buffer.pop_front().unwrap().unwrap(); - state.next_drain += 1; - // Notify wait_until_drained that buffer changed - self.signal.notify_all(); - return Some(value); - } - - // No ready result - check if we should stop waiting - match state.status { - BufferStatus::Active => { - // Wait for notification - state = self.signal.wait(state).unwrap(); - } - BufferStatus::Shutdown | BufferStatus::Error => { - // Check one more time after status change - if let Some(Some(_)) = state.buffer.front() { - let value = state.buffer.pop_front().unwrap().unwrap(); - state.next_drain += 1; - self.signal.notify_all(); - return Some(value); - } - return None; - } - } - } - } - - /// Wait until all reserved slots have been drained. - /// - /// Returns the final status (Active means all drained, Error/Shutdown - /// means stopped early). - pub fn wait_until_drained(&self) -> BufferStatus { - let mut state = self.inner.lock().unwrap(); - - loop { - if state.buffer.is_empty() { - return BufferStatus::Active; - } - - if state.status == BufferStatus::Error { - return BufferStatus::Error; - } - - state = self.signal.wait(state).unwrap(); - } - } - - /// Signal an error condition. Wakes all waiting threads. - pub fn set_error(&self) { - let mut state = self.inner.lock().unwrap(); - state.status = BufferStatus::Error; - self.signal.notify_all(); - } - - /// Signal shutdown. Wakes all waiting threads. - pub fn shutdown(&self) { - let mut state = self.inner.lock().unwrap(); - state.status = BufferStatus::Shutdown; - self.signal.notify_all(); - } - - /// Get current status. - pub fn status(&self) -> BufferStatus { - self.inner.lock().unwrap().status - } - - /// Check if buffer is empty (all results drained). - pub fn is_empty(&self) -> bool { - self.inner.lock().unwrap().buffer.is_empty() - } - - /// Get count of pending/ready slots. - pub fn len(&self) -> usize { - self.inner.lock().unwrap().buffer.len() - } - - /// Reset the buffer for reuse. - /// - /// Clears all pending slots and resets sequence counter. - pub fn reset(&self) { - let mut state = self.inner.lock().unwrap(); - state.buffer.clear(); - state.next_drain = 0; - state.status = BufferStatus::Active; - } -} - -impl Default for OrderedResultBuffer { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::sync::Arc; - use std::thread; - use std::time::{Duration, Instant}; - - #[test] - fn test_basic_order() { - let buf = OrderedResultBuffer::new(); - - let seq0 = buf.reserve(); - let seq1 = buf.reserve(); - let seq2 = buf.reserve(); - - assert_eq!(seq0, 0); - assert_eq!(seq1, 1); - assert_eq!(seq2, 2); - - // Fill out of order - buf.fill(seq2, "third"); - buf.fill(seq0, "first"); - buf.fill(seq1, "second"); - - // Take in order - assert_eq!(buf.take_next(), Some("first")); - assert_eq!(buf.take_next(), Some("second")); - assert_eq!(buf.take_next(), Some("third")); - } - - #[test] - fn test_reserve_and_fill() { - let buf = OrderedResultBuffer::new(); - - buf.reserve_and_fill("immediate"); - let seq = buf.reserve(); - buf.fill(seq, "delayed"); - - assert_eq!(buf.take_next(), Some("immediate")); - assert_eq!(buf.take_next(), Some("delayed")); - } - - #[test] - fn test_blocking_take() { - let buf = Arc::new(OrderedResultBuffer::new()); - let seq = buf.reserve(); - - let buf_clone = Arc::clone(&buf); - let handle = thread::spawn(move || { - thread::sleep(Duration::from_millis(50)); - buf_clone.fill(seq, 42); - }); - - let start = Instant::now(); - let value = buf.take_next(); - let elapsed = start.elapsed(); - - assert_eq!(value, Some(42)); - assert!(elapsed >= Duration::from_millis(40)); - - handle.join().unwrap(); - } - - #[test] - fn test_out_of_order_parallel() { - let buf = Arc::new(OrderedResultBuffer::new()); - let num_items = 100; - - // Reserve all slots - let seqs: Vec<_> = (0..num_items).map(|_| buf.reserve()).collect(); - - // Fill from multiple threads in reverse order - let handles: Vec<_> = seqs - .into_iter() - .rev() - .map(|seq| { - let buf = Arc::clone(&buf); - thread::spawn(move || { - buf.fill(seq, seq * 10); - }) - }) - .collect(); - - // Drain and verify order - for i in 0..num_items { - let value = buf.take_next().unwrap(); - assert_eq!(value, i * 10); - } - - for h in handles { - h.join().unwrap(); - } - } - - #[test] - fn test_shutdown_wakes_waiter() { - let buf = Arc::new(OrderedResultBuffer::::new()); - buf.reserve(); // Reserve but don't fill - - let buf_clone = Arc::clone(&buf); - let handle = thread::spawn(move || { - thread::sleep(Duration::from_millis(50)); - buf_clone.shutdown(); - }); - - let start = Instant::now(); - let result = buf.take_next(); - let elapsed = start.elapsed(); - - assert!(result.is_none()); - assert!(elapsed >= Duration::from_millis(40)); - assert_eq!(buf.status(), BufferStatus::Shutdown); - - handle.join().unwrap(); - } - - #[test] - fn test_error_wakes_waiter() { - let buf = Arc::new(OrderedResultBuffer::::new()); - buf.reserve(); - - let buf_clone = Arc::clone(&buf); - let handle = thread::spawn(move || { - thread::sleep(Duration::from_millis(50)); - buf_clone.set_error(); - }); - - let result = buf.take_next(); - assert!(result.is_none()); - assert_eq!(buf.status(), BufferStatus::Error); - - handle.join().unwrap(); - } - - #[test] - fn test_wait_until_drained() { - let buf = Arc::new(OrderedResultBuffer::new()); - - // Reserve and fill slots - for i in 0..5 { - buf.reserve_and_fill(i); - } - - // Drain in another thread - let buf_clone = Arc::clone(&buf); - let drainer = thread::spawn(move || while let Some(_) = buf_clone.take_next() {}); - - // Wait for drain - let status = buf.wait_until_drained(); - assert_eq!(status, BufferStatus::Active); - assert!(buf.is_empty()); - - buf.shutdown(); - drainer.join().unwrap(); - } - - #[test] - fn test_reset() { - let buf = OrderedResultBuffer::new(); - - buf.reserve_and_fill(1); - buf.reserve_and_fill(2); - buf.set_error(); - - assert_eq!(buf.status(), BufferStatus::Error); - assert_eq!(buf.len(), 2); - - buf.reset(); - - assert_eq!(buf.status(), BufferStatus::Active); - assert!(buf.is_empty()); - - // Can use again - buf.reserve_and_fill(100); - assert_eq!(buf.take_next(), Some(100)); - } - - #[test] - fn test_stale_fill_after_reset() { - let buf = OrderedResultBuffer::new(); - - let seq = buf.reserve(); - buf.reset(); - - // Stale fill should be ignored (not panic) - buf.fill(seq, "stale"); - - assert!(buf.is_empty()); - } - - #[test] - fn test_stress_throughput() { - let buf = Arc::new(OrderedResultBuffer::new()); - let num_items = 100_000; - - let producer_buf = Arc::clone(&buf); - let producer = thread::spawn(move || { - for i in 0..num_items { - producer_buf.reserve_and_fill(i); - } - producer_buf.shutdown(); - }); - - let start = Instant::now(); - let mut count = 0; - while let Some(_) = buf.take_next() { - count += 1; - } - let elapsed = start.elapsed(); - - producer.join().unwrap(); - - assert_eq!(count, num_items); - let ops_per_sec = num_items as f64 / elapsed.as_secs_f64(); - println!( - "OrderedResultBuffer stress: {} ops in {:?} ({:.0} ops/sec)", - num_items, elapsed, ops_per_sec - ); - assert!(ops_per_sec > 100_000.0, "Too slow: {:.0} ops/sec", ops_per_sec); - } - - #[test] - fn test_parallel_stress() { - let buf = Arc::new(OrderedResultBuffer::new()); - let num_items = 10_000; - let num_producers = 8; - - // Reserve all first (single threaded) - let seqs: Vec<_> = (0..num_items).map(|_| buf.reserve()).collect(); - - // Distribute to producers - let chunk_size = num_items / num_producers; - let producers: Vec<_> = (0..num_producers) - .map(|p| { - let buf = Arc::clone(&buf); - let chunk: Vec<_> = seqs[p * chunk_size..(p + 1) * chunk_size].to_vec(); - thread::spawn(move || { - for seq in chunk { - buf.fill(seq, seq * 2); - } - }) - }) - .collect(); - - // Consume - let buf_consumer = Arc::clone(&buf); - let consumer = thread::spawn(move || { - let mut results = Vec::with_capacity(num_items); - for _ in 0..num_items { - if let Some(v) = buf_consumer.take_next() { - results.push(v); - } - } - results - }); - - for p in producers { - p.join().unwrap(); - } - - let results = consumer.join().unwrap(); - - // Verify in-order - for (i, &v) in results.iter().enumerate() { - assert_eq!(v, i * 2, "Out of order at index {}", i); - } - } -} From 97ba498099022f5f6ad108374f515e6a43d5b68b Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 20 Feb 2026 07:16:35 +0000 Subject: [PATCH 598/782] fixing coordinator worker to avoid blocking when receiving grpc hints --- distributed/crates/worker/src/lib.rs | 1 + .../crates/worker/src/stream_ordering.rs | 123 +++++++++++++ distributed/crates/worker/src/worker.rs | 170 +++++++----------- distributed/crates/worker/src/worker_node.rs | 11 +- precompiles/hints/src/hints_processor.rs | 11 +- 5 files changed, 198 insertions(+), 118 deletions(-) create mode 100644 distributed/crates/worker/src/stream_ordering.rs diff --git a/distributed/crates/worker/src/lib.rs b/distributed/crates/worker/src/lib.rs index 08bf6303d..5522ef780 100644 --- a/distributed/crates/worker/src/lib.rs +++ b/distributed/crates/worker/src/lib.rs @@ -5,6 +5,7 @@ //! communication, and job handling capabilities. pub mod config; +mod stream_ordering; pub mod worker; pub mod worker_node; diff --git a/distributed/crates/worker/src/stream_ordering.rs b/distributed/crates/worker/src/stream_ordering.rs new file mode 100644 index 000000000..3fd1524f3 --- /dev/null +++ b/distributed/crates/worker/src/stream_ordering.rs @@ -0,0 +1,123 @@ +use anyhow::Result; +use precompiles_hints::HintsProcessor; +use std::cmp::Reverse; +use std::collections::BinaryHeap; +use std::sync::mpsc; +use std::sync::Arc; +use tracing::{error, info}; +use zisk_common::reinterpret_vec; +use zisk_distributed_common::{JobId, StreamDataDto, StreamMessageKind}; + +/// Per-job actor that reorders out-of-order stream chunks and feeds them +/// to `HintsProcessor::process_hints` in strict sequence order. +pub struct StreamOrderingActor { + sender: Option>, + thread_handle: Option>, +} + +impl StreamOrderingActor { + /// Spawns the ordering thread and returns the actor handle. + pub fn new(hints_processor: Arc, job_id: JobId) -> Self { + let (tx, rx) = mpsc::channel::(); + + let handle = std::thread::spawn(move || Self::run(rx, hints_processor, job_id)); + + Self { sender: Some(tx), thread_handle: Some(handle) } + } + + /// Enqueues a stream message for ordered delivery to `process_hints`. + /// + /// This call is non-blocking and safe to invoke from an async context. + pub fn send(&self, msg: StreamDataDto) -> Result<()> { + self.sender + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Stream ordering actor already shut down"))? + .send(msg) + .map_err(|_| anyhow::anyhow!("Stream ordering actor channel closed unexpectedly")) + } + + // Error propagation: when run_inner returns Err, rx is dropped, closing the channel. + // The next actor.send() in the gRPC loop then returns Err. + fn run(rx: mpsc::Receiver, hints_processor: Arc, job_id: JobId) { + if let Err(e) = Self::run_inner(rx, &hints_processor, &job_id) { + error!("Stream ordering actor failed for job {}: {}", job_id, e); + } + } + + fn run_inner( + rx: mpsc::Receiver, + hints_processor: &HintsProcessor, + job_id: &JobId, + ) -> Result<()> { + // Min-heap ordered by sequence number (Reverse makes BinaryHeap a min-heap) + let mut heap: BinaryHeap)>> = BinaryHeap::new(); + let mut next_seq: u32 = 1; + let mut is_first = true; + + loop { + match rx.recv() { + Ok(msg) => match msg.stream_type { + StreamMessageKind::End => { + if !heap.is_empty() { + return Err(anyhow::anyhow!( + "Stream End received for job {} but {} buffered chunk(s) remain \ + (next expected seq: {}). Sequence gap detected.", + job_id, + heap.len(), + next_seq + )); + } + info!("Stream ordering actor: received End for job {}", job_id); + return Ok(()); + } + StreamMessageKind::Data => { + let payload_dto = msg.stream_payload.ok_or_else(|| { + anyhow::anyhow!("Data message missing payload for job {}", job_id) + })?; + + heap.push(Reverse((payload_dto.sequence_number, payload_dto.payload))); + + // Drain all consecutive in-order sequences from the heap, + // accumulating their bytes into a single buffer so that + // process_hints is called exactly once per recv() iteration. + if !matches!(heap.peek(), Some(Reverse((s, _))) if *s == next_seq) { + continue; + } + let mut combined: Vec = Vec::new(); + while matches!(heap.peek(), Some(Reverse((s, _))) if *s == next_seq) { + let Reverse((_, data)) = heap.pop().unwrap(); + combined.extend_from_slice(&data); + next_seq += 1; + } + + let hints = reinterpret_vec(combined)?; + let first = std::mem::replace(&mut is_first, false); + hints_processor.process_hints(&hints, first)?; + } + StreamMessageKind::Start => { + return Err(anyhow::anyhow!( + "Unexpected Start message received mid-stream for job {}", + job_id + )); + } + }, + Err(_) => { + // Channel closed — sender was dropped (job cancelled or complete) + info!("Stream ordering actor: channel closed for job {}", job_id); + return Ok(()); + } + } + } + } +} + +impl Drop for StreamOrderingActor { + fn drop(&mut self) { + // Drop the sender first so the thread's recv() returns Err and exits + self.sender.take(); + // Then join to ensure the thread shuts down cleanly before we return + if let Some(handle) = self.thread_handle.take() { + let _ = handle.join(); + } + } +} diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 71e680bb1..f99943aa1 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -4,20 +4,19 @@ use cargo_zisk::commands::get_proving_key; use precompiles_hints::HintsProcessor; use proofman::{AggProofs, ContributionsInfo}; use rom_setup::{get_elf_data_hash, DEFAULT_CACHE_PATH}; -use std::collections::hash_map::Entry; -use std::collections::HashMap; use std::fs; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; use zisk_common::io::{StreamProcessor, StreamSource, ZiskStdin}; -use zisk_common::reinterpret_vec; use zisk_common::ElfBinaryFromFile; use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; use zisk_distributed_common::{ComputeCapacity, JobId, WorkerId}; -use zisk_distributed_common::{HintsSourceDto, StreamDataDto, StreamMessageKind, StreamPayloadDto}; +use zisk_distributed_common::{HintsSourceDto, StreamDataDto, StreamMessageKind}; use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProgramPK, ZiskProver}; +use crate::stream_ordering::StreamOrderingActor; + use proofman::ExecutionInfo; use proofman::ProofInfo; use proofman::ProvePhaseInputs; @@ -246,10 +245,9 @@ pub struct Worker { prover: Arc>, prover_config: ProverConfig, - stream_buffers: HashMap>)>, // (job_id, (next_seq, (seq_number, data))) - hints_processor: Option, - + stream_actor: Option, pk: ZiskProgramPK, + hints_processor: Option>, } impl Worker { @@ -282,7 +280,7 @@ impl Worker { prover, prover_config, pk, - stream_buffers: HashMap::new(), + stream_actor: None, hints_processor: None, }) } @@ -319,7 +317,7 @@ impl Worker { prover, prover_config, pk, - stream_buffers: HashMap::new(), + stream_actor: None, hints_processor: None, }) } @@ -368,6 +366,8 @@ impl Worker { if let Some(handle) = self.current_computation.take() { handle.abort(); } + // Drop the actor: closes the channel, which signals the ordering thread to exit + self.stream_actor = None; } pub fn new_job( @@ -561,8 +561,8 @@ impl Worker { stdin.set_hints_stream(hints_stream); } HintsSourceDto::HintsStream(_hints_uri) => { - // For HintsStream, the worker will receive hint data via stream_data messages - // The hints will be processed by the hints_processor in process_stream_data + // For HintsStream, the worker will receive hint data via StreamData gRPC messages + // routed through the stream ordering actor into the hints processor. // No need to set hints_stream on prover for this case } HintsSourceDto::HintsNull => { @@ -594,116 +594,72 @@ impl Worker { Ok(challenge) } - pub async fn process_stream_data(&mut self, stream_data: StreamDataDto) -> Result<()> { - let job_id = stream_data.job_id; - let stream_type = stream_data.stream_type; - - if self.hints_processor.is_none() { - let base_port = self.prover_config.asm_port; - let local_rank = self.prover.local_rank(); - let unlock_mapped_memory = self.prover_config.unlock_mapped_memory; - let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory)?; - let processor = HintsProcessor::builder(hints_shmem) - .build() - .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e))?; - - let worker_idx = self - .current_job - .as_ref() - .ok_or_else(|| { - anyhow::anyhow!( - "Received stream data for job {}, but no current job is set", - job_id - ) - })? - .lock() - .await - .rank_id as usize; + /// Routes an incoming `StreamData` message to the per-job ordering actor. + /// + /// - `Start`: initialises the `HintsProcessor` (if needed), resets it, and spawns the actor. + /// - `Data` / `End`: enqueues the message into the actor's channel — O(1), non-blocking. + /// + /// The actor thread owns the reorder buffer and calls `process_hints` in sequence order. + pub async fn route_stream_data(&mut self, stream_data: StreamDataDto) -> Result<()> { + match &stream_data.stream_type { + StreamMessageKind::Start => { + let job_id = stream_data.job_id.clone(); - processor.set_has_rom_sm(worker_idx == 0); - self.hints_processor = Some(processor); - } + self.ensure_hints_processor(&job_id).await?; - // Check the existence of stream buffer based on stream type - if stream_type == StreamMessageKind::Start { - // Check if buffer already exists - match self.stream_buffers.entry(job_id.clone()) { - Entry::Occupied(_) => { - return Err(anyhow::anyhow!("Received duplicate START for job {}", job_id)); - } - Entry::Vacant(entry) => { - entry.insert((1, HashMap::new())); - } - } + let hints_processor = self.hints_processor.as_ref().unwrap(); - // Reset hints processor state for the new stream/job - // This is crucial for consecutive proofs to work correctly - if let Some(ref mut hints_processor) = self.hints_processor { hints_processor.reset(); - } - return Ok(()); - } else if stream_type == StreamMessageKind::End { - // Ensure buffer exists - if !self.stream_buffers.contains_key(&job_id) { - return Err(anyhow::anyhow!( - "Received {:?} without START for job {}", - stream_type, - job_id, - )); - } + let hints_processor = Arc::clone(hints_processor); - // Clean up the stream buffer for this job - self.stream_buffers.remove(&job_id); + // Replace any existing actor (handles reconnect / job restart) + self.stream_actor = Some(StreamOrderingActor::new(hints_processor, job_id)); + } + StreamMessageKind::Data | StreamMessageKind::End => match &self.stream_actor { + Some(actor) => actor.send(stream_data)?, + None => { + return Err(anyhow::anyhow!( + "Received stream {:?} without a prior Start for job {}", + stream_data.stream_type, + stream_data.job_id + )); + } + }, + } + Ok(()) + } + /// Lazily initialises the `HintsProcessor` using configuration from the current job. + async fn ensure_hints_processor(&mut self, job_id: &JobId) -> Result<()> { + if self.hints_processor.is_some() { return Ok(()); } - let element = self.stream_buffers.get_mut(&job_id).ok_or_else(|| { - anyhow::anyhow!( - "Received stream data without START for job {} stream type {:?}", - job_id, - stream_type - ) - })?; - - let next_seq = &mut element.0; - let stream_buffer = &mut element.1; - - let StreamPayloadDto { sequence_number: current_seq, mut payload } = - stream_data.stream_payload.ok_or_else(|| { + let base_port = self.prover_config.asm_port; + let local_rank = self.prover.local_rank(); + let unlock_mapped_memory = self.prover_config.unlock_mapped_memory; + let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory)?; + let processor = HintsProcessor::builder(hints_shmem) + .build() + .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e))?; + + let worker_idx = self + .current_job + .as_ref() + .ok_or_else(|| { anyhow::anyhow!( - "Missing stream payload for job {} stream type {:?}", - job_id, - stream_type + "Received stream data for job {}, but no current job is set", + job_id ) - })?; - - // Check if this is the expected sequence number - // If not, buffer it for later processing - if current_seq != *next_seq { - println!( - "Received out-of-order stream data for job {}, expected seq {}, got seq {}, buffering", - job_id, *next_seq, current_seq - ); - stream_buffer.insert(current_seq, payload); - return Ok(()); - } + })? + .lock() + .await + .rank_id as usize; - // Process the current payload (which has the expected sequence number) - // and increment next_seq to expect the following sequence - *next_seq += 1; - - // Check if we have any buffered subsequent payloads waiting - // If so, append them to the current payload in order - while let Some(buffered_data) = stream_buffer.remove(next_seq) { - payload.extend(buffered_data); - *next_seq += 1; - } + processor.set_has_rom_sm(worker_idx == 0); - // Process the hints - let payload = reinterpret_vec(payload)?; - self.hints_processor.as_mut().unwrap().process_hints(&payload, current_seq == 1)?; + self.hints_processor = Some(Arc::new(processor)); Ok(()) } diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index e081a68a0..136c866e3 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -744,22 +744,19 @@ impl WorkerNodeGrpc { return Err(anyhow!("Stream data received without current job context")); } - let job = self.worker.current_job().clone().unwrap().clone(); + let job = self.worker.current_job().unwrap(); let current_job_id = job.lock().await.job_id.clone(); let stream_data_dto: StreamDataDto = stream_data.into(); - let job_id = stream_data_dto.job_id.clone(); - if current_job_id != job_id { + if current_job_id != stream_data_dto.job_id { return Err(anyhow!( "Job ID mismatch in StreamData: expected {}, got {}", current_job_id.as_string(), - job_id + stream_data_dto.job_id )); } - self.worker.process_stream_data(stream_data_dto).await?; - - Ok(()) + self.worker.route_stream_data(stream_data_dto).await } } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index c4cef4a68..57dacc297 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -530,12 +530,15 @@ impl HintsProcessor { queue.buffer[offset] = Some(result); debug!("[WORKER] seq={} filled slot at offset {}, buffer_len={}", seq_id, offset, queue.buffer.len()); - // Release lock before notifying - drop(queue); - - // Notify drainer thread (use notify_all to wake any waiting threads) + // Notify WHILE holding the lock to prevent a missed-wakeup race. + // If we drop the lock first and notify after, the drainer can acquire the lock, + // drain this slot, and call condvar::wait() before our notify_all fires — + // losing the signal permanently and leaving the drainer stuck. debug!("[WORKER] seq={} calling notify_all", seq_id); state.drain_signal.notify_all(); + + // Release lock after notifying + drop(queue); } /// Drainer thread that waits for hints to complete and drains ready results from queue. From c4518f00052095441bf3e89bd22ffda581c47c61 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 20 Feb 2026 07:57:15 +0000 Subject: [PATCH 599/782] added end hint message --- distributed/crates/worker/src/worker.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index f99943aa1..9cec0efcc 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -601,6 +601,12 @@ impl Worker { /// /// The actor thread owns the reorder buffer and calls `process_hints` in sequence order. pub async fn route_stream_data(&mut self, stream_data: StreamDataDto) -> Result<()> { + if matches!(stream_data.stream_type, StreamMessageKind::End) { + info!( + "Received End stream message for job {}. Routing to stream actor and shutting it down.", + stream_data.job_id + ); + } match &stream_data.stream_type { StreamMessageKind::Start => { let job_id = stream_data.job_id.clone(); From 18879eeca7f49e59ab495b3e826d2ef3c7ae3eb7 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 20 Feb 2026 09:34:56 +0000 Subject: [PATCH 600/782] temptative fixing --- distributed/crates/worker/src/worker.rs | 51 ++++++++++--------- precompiles/hints/src/hints_processor.rs | 64 ++++++++++++++++++------ 2 files changed, 74 insertions(+), 41 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 9cec0efcc..8d8b7b8a5 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -406,27 +406,27 @@ impl Worker { } pub async fn partial_contribution_mpi_broadcast(&self, job: &Mutex) -> Result<()> { - let job = job.lock().await; - let job_id = job.job_id.clone(); - - let proof_info = ProofInfo::new( - None, - job.total_compute_units as usize, - job.allocation.clone(), - job.rank_id as usize, - ); - let phase_inputs = proofman::ProvePhaseInputs::Contributions(proof_info); + let mut serialized = { + let job = job.lock().await; + let proof_info = ProofInfo::new( + None, + job.total_compute_units as usize, + job.allocation.clone(), + job.rank_id as usize, + ); + let phase_inputs = ProvePhaseInputs::Contributions(proof_info); - let options = self.get_proof_options(false); + let options = self.get_proof_options(false); - let mut serialized = borsh::to_vec(&( - JobPhase::Contributions, - job_id, - phase_inputs, - options, - job.data_ctx.input_source.clone(), - )) - .unwrap(); + borsh::to_vec(&( + JobPhase::Contributions, + job.job_id.clone(), + phase_inputs, + options, + job.data_ctx.input_source.clone(), + )) + .unwrap() + }; self.prover.mpi_broadcast(&mut serialized)?; Ok(()) @@ -447,15 +447,16 @@ impl Worker { job: &Mutex, challenges: Vec, ) -> Result<()> { - let job = job.lock().await; - let job_id = job.job_id.clone(); + let mut serialized = { + let job = job.lock().await; + let job_id = job.job_id.clone(); - let phase_inputs = proofman::ProvePhaseInputs::Internal(challenges); + let phase_inputs = proofman::ProvePhaseInputs::Internal(challenges); - let options = self.get_proof_options(false); + let options = self.get_proof_options(false); - let mut serialized = - borsh::to_vec(&(JobPhase::Prove, job_id, phase_inputs, options)).unwrap(); + borsh::to_vec(&(JobPhase::Prove, job_id, phase_inputs, options)).unwrap() + }; self.prover.mpi_broadcast(&mut serialized)?; Ok(()) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 57dacc297..9cbac5dda 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -10,7 +10,7 @@ use std::collections::{HashMap, VecDeque}; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; -use std::time::Instant; +use std::time::{Duration, Instant}; use tracing::{debug, info}; use zisk_common::io::{StreamProcessor, StreamSink}; use zisk_common::{ @@ -261,7 +261,11 @@ impl HintsProcessor { /// * `Ok(false)` - Batch processed successfully, no CTRL_END /// * `Err` - If a previous error occurred or hints are malformed pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { - debug!("[PROCESS] process_hints called with {} u64s, first_batch={}", hints.len(), first_batch); + debug!( + "[PROCESS] process_hints called with {} u64s, first_batch={}", + hints.len(), + first_batch + ); let mut has_ctrl_end = false; // Take any pending partial hint from previous batch @@ -476,7 +480,10 @@ impl HintsProcessor { // Check generation first to detect stale workers (before processing) let current_gen = state.generation.load(Ordering::SeqCst); if generation != current_gen { - debug!("[WORKER] seq={} early return: generation mismatch (worker={}, current={})", seq_id, generation, current_gen); + debug!( + "[WORKER] seq={} early return: generation mismatch (worker={}, current={})", + seq_id, generation, current_gen + ); return; } @@ -515,30 +522,48 @@ impl HintsProcessor { // Calculate offset in buffer; handle drained slots if seq_id < queue.next_drain_seq { - debug!("[WORKER] seq={} early return: seq_id < next_drain_seq ({})", seq_id, queue.next_drain_seq); + debug!( + "[WORKER] seq={} early return: seq_id < next_drain_seq ({})", + seq_id, queue.next_drain_seq + ); return; } let offset = seq_id - queue.next_drain_seq; // Check if slot exists - if not, drainer already processed and removed it if offset >= queue.buffer.len() { - debug!("[WORKER] seq={} early return: offset {} >= buffer.len() {}", seq_id, offset, queue.buffer.len()); + debug!( + "[WORKER] seq={} early return: offset {} >= buffer.len() {}", + seq_id, + offset, + queue.buffer.len() + ); return; } // Fill the slot to allow drainer to proceed (critical for ordering) queue.buffer[offset] = Some(result); - debug!("[WORKER] seq={} filled slot at offset {}, buffer_len={}", seq_id, offset, queue.buffer.len()); - - // Notify WHILE holding the lock to prevent a missed-wakeup race. - // If we drop the lock first and notify after, the drainer can acquire the lock, - // drain this slot, and call condvar::wait() before our notify_all fires — - // losing the signal permanently and leaving the drainer stuck. - debug!("[WORKER] seq={} calling notify_all", seq_id); - state.drain_signal.notify_all(); + debug!( + "[WORKER] seq={} filled slot at offset {}, buffer_len={}", + seq_id, + offset, + queue.buffer.len() + ); // Release lock after notifying drop(queue); + + // Only wake the drainer when we filled the FRONT slot (offset == 0). + // The drainer can only make progress when buffer[0] is ready; waking it + // for any other slot causes it to re-check, find nothing drainable, and + // go back to sleep — pure overhead (O(N) context switches for N hints). + // When offset > 0, the drainer will reach this slot naturally during its + // current drain cycle after the front slots are consumed. + // Notify WHILE holding the lock to prevent a missed-wakeup race. + if offset == 0 { + debug!("[WORKER] seq={} calling notify_all (front slot)", seq_id); + state.drain_signal.notify_all(); + } } /// Drainer thread that waits for hints to complete and drains ready results from queue. @@ -547,8 +572,11 @@ impl HintsProcessor { loop { debug!("[DRAINER] Attempting to acquire queue lock"); let mut queue = state.queue.lock().unwrap(); - debug!("[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", - queue.buffer.len(), queue.next_drain_seq); + debug!( + "[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", + queue.buffer.len(), + queue.next_drain_seq + ); // Check for shutdown if state.shutdown.load(Ordering::Acquire) { @@ -564,7 +592,11 @@ impl HintsProcessor { match res { Ok(data) => { // Clone data before dropping lock - debug!("[DRAINER] Found ready result seq={}, size={} u64s, cloning", current_seq, data.len()); + debug!( + "[DRAINER] Found ready result seq={}, size={} u64s, cloning", + current_seq, + data.len() + ); let data_to_submit = data.clone(); queue.buffer.pop_front(); queue.next_drain_seq += 1; From 21bf6fa5c32ff1476d51bf30ff1f9d5224efe023 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:46:21 +0000 Subject: [PATCH 601/782] temptative fixing --- precompiles/hints/src/hints_processor.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 9cbac5dda..59a12d555 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -550,20 +550,22 @@ impl HintsProcessor { queue.buffer.len() ); - // Release lock after notifying - drop(queue); - // Only wake the drainer when we filled the FRONT slot (offset == 0). // The drainer can only make progress when buffer[0] is ready; waking it // for any other slot causes it to re-check, find nothing drainable, and // go back to sleep — pure overhead (O(N) context switches for N hints). // When offset > 0, the drainer will reach this slot naturally during its // current drain cycle after the front slots are consumed. - // Notify WHILE holding the lock to prevent a missed-wakeup race. + // Notify WHILE holding the lock to prevent a missed-wakeup race: the + // drainer cannot enter condvar.wait() while we hold the lock, so this + // notification cannot be lost. if offset == 0 { debug!("[WORKER] seq={} calling notify_all (front slot)", seq_id); state.drain_signal.notify_all(); } + + // Release lock after notifying + drop(queue); } /// Drainer thread that waits for hints to complete and drains ready results from queue. @@ -643,11 +645,14 @@ impl HintsProcessor { break; } - // Wait for notification that a hint completed - debug!("[DRAINER] Waiting on drain_signal condvar"); + debug!("[DRAINER] Waiting on drain_signal condvar (timeout=50ms)"); #[allow(unused_assignments)] { - queue = state.drain_signal.wait(queue).unwrap(); + let (q, _) = state + .drain_signal + .wait_timeout(queue, Duration::from_millis(50)) + .unwrap(); + queue = q; } debug!("[DRAINER] Woke up from condvar wait"); } From 96b93f2849d804521b26b6f5a1903f269a5632b5 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:12:43 +0000 Subject: [PATCH 602/782] added tokio debugging --- distributed/crates/worker/src/worker.rs | 22 ++++++++++-- distributed/crates/worker/src/worker_node.rs | 24 +++++++++++++ emulator-asm/asm-runner/src/asm_mt_runner.rs | 10 ++++++ emulator-asm/asm-runner/src/grpc_metrics.rs | 38 ++++++++++++++++++++ emulator-asm/asm-runner/src/lib.rs | 3 ++ 5 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 emulator-asm/asm-runner/src/grpc_metrics.rs diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 8d8b7b8a5..f48fff618 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use asm_runner::HintsShmem; +use asm_runner::{HintsShmem, GRPC_METRICS}; use cargo_zisk::commands::get_proving_key; use precompiles_hints::HintsProcessor; use proofman::{AggProofs, ContributionsInfo}; @@ -622,9 +622,13 @@ impl Worker { // Replace any existing actor (handles reconnect / job restart) self.stream_actor = Some(StreamOrderingActor::new(hints_processor, job_id)); + GRPC_METRICS.active_streams.fetch_add(1, std::sync::atomic::Ordering::Relaxed); } - StreamMessageKind::Data | StreamMessageKind::End => match &self.stream_actor { - Some(actor) => actor.send(stream_data)?, + StreamMessageKind::Data => match &self.stream_actor { + Some(actor) => { + GRPC_METRICS.callbacks_invoked.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + actor.send(stream_data)?; + } None => { return Err(anyhow::anyhow!( "Received stream {:?} without a prior Start for job {}", @@ -633,6 +637,18 @@ impl Worker { )); } }, + StreamMessageKind::End => match &self.stream_actor { + Some(actor) => { + actor.send(stream_data)?; + GRPC_METRICS.active_streams.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); + } + None => { + return Err(anyhow::anyhow!( + "Received stream End without a prior Start for job {}", + stream_data.job_id + )); + } + }, } Ok(()) } diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 136c866e3..46c29a575 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -1,5 +1,6 @@ use crate::{worker::ComputationResult, ProverConfig, Worker}; use anyhow::{anyhow, Result}; +use asm_runner::GRPC_METRICS; use proofman::{AggProofs, ContributionsInfo, ExecutionInfo}; use std::path::Path; use std::{path::PathBuf, time::Duration}; @@ -18,6 +19,11 @@ use zisk_distributed_grpc_api::execute_task_response::ResultData; use zisk_distributed_grpc_api::*; use zisk_sdk::{Asm, Emu, ZiskBackend}; +/// Tokio scheduler latency above which we count a starvation event. +const STARVATION_THRESHOLD: Duration = Duration::from_millis(100); +/// How often the canary task wakes up to check scheduler latency. +const CANARY_INTERVAL: Duration = Duration::from_millis(10); + use crate::config::WorkerServiceConfig; pub enum WorkerNode { @@ -188,6 +194,24 @@ impl WorkerNodeGrpc { let (computation_tx, mut computation_rx) = mpsc::unbounded_channel::(); let mut heartbeat_interval = tokio::time::interval(Duration::from_secs(30)); + // Spawn a canary task that measures tokio scheduler latency. + // If the runtime is starved (e.g. a blocking call is occupying all threads), + // this task wakes up late and increments the starvation counter visible to + // AsmRunnerMT's semaphore-timeout diagnostic. + let _canary = tokio::spawn(async { + let mut interval = tokio::time::interval(CANARY_INTERVAL); + loop { + let before = std::time::Instant::now(); + interval.tick().await; + let latency = before.elapsed(); + if latency > STARVATION_THRESHOLD { + GRPC_METRICS + .thread_starvation_count + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + } + }); + // Main non-blocking event loop loop { tokio::select! { diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index d7c8b7c43..e1018eb29 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -162,6 +162,16 @@ impl AsmRunnerMT { continue } Err(e) => { + // Print gRPC health snapshot to correlate the stall with stream activity. + let thread = std::thread::current(); + let thread_name = thread.name().unwrap_or(""); + tracing::warn!( + "[MT_RUNNER] Semaphore timeout on thread {:?} (name={:?}, on_tokio={})", + thread.id(), + thread_name, + thread_name.contains("tokio"), + ); + crate::GRPC_METRICS.diagnose_grpc_health(); error!("Semaphore '{}' error: {:?}", sem_chunk_done_name, e); if chunk_id.0 == 0 { diff --git a/emulator-asm/asm-runner/src/grpc_metrics.rs b/emulator-asm/asm-runner/src/grpc_metrics.rs new file mode 100644 index 000000000..132f6c5bd --- /dev/null +++ b/emulator-asm/asm-runner/src/grpc_metrics.rs @@ -0,0 +1,38 @@ +use std::sync::atomic::{AtomicU64, Ordering}; + +/// Global gRPC health counters. +/// +/// Incremented by the worker's stream-handling code and read by `AsmRunnerMT` +/// when a semaphore timeout fires so we can correlate a stalled ASM with gRPC +/// activity (or lack thereof) at the same moment. +pub struct GrpcMetrics { + /// Number of hint streams that have been started but not yet ended. + pub active_streams: AtomicU64, + /// Total `StreamData::Data` messages forwarded to the ordering actor. + pub callbacks_invoked: AtomicU64, + /// Number of times the Tokio event-loop was delayed by more than the + /// starvation threshold (detected by a periodic canary task in the worker). + pub thread_starvation_count: AtomicU64, +} + +impl GrpcMetrics { + const fn new() -> Self { + Self { + active_streams: AtomicU64::new(0), + callbacks_invoked: AtomicU64::new(0), + thread_starvation_count: AtomicU64::new(0), + } + } + + pub fn diagnose_grpc_health(&self) { + println!("=== GRPC METRICS ==="); + println!("Active streams: {}", self.active_streams.load(Ordering::Relaxed)); + println!("Callbacks invoked: {}", self.callbacks_invoked.load(Ordering::Relaxed)); + println!( + "Thread starvation events: {}", + self.thread_starvation_count.load(Ordering::Relaxed) + ); + } +} + +pub static GRPC_METRICS: GrpcMetrics = GrpcMetrics::new(); diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 9e743c996..2257b6b6c 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -1,5 +1,8 @@ extern crate libc; +mod grpc_metrics; +pub use grpc_metrics::{GrpcMetrics, GRPC_METRICS}; + mod asm_mo; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod asm_mo_runner; From 078067c903e4047a71766a529a83a027b02d228a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:39:55 +0000 Subject: [PATCH 603/782] fix grpc metrics --- distributed/crates/worker/src/worker.rs | 22 ++------------ distributed/crates/worker/src/worker_node.rs | 32 +++++++++----------- emulator-asm/asm-runner/src/hints_shmem.rs | 5 ++- precompiles/hints/src/hints_processor.rs | 6 ++-- 4 files changed, 23 insertions(+), 42 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index f48fff618..8d8b7b8a5 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use asm_runner::{HintsShmem, GRPC_METRICS}; +use asm_runner::HintsShmem; use cargo_zisk::commands::get_proving_key; use precompiles_hints::HintsProcessor; use proofman::{AggProofs, ContributionsInfo}; @@ -622,13 +622,9 @@ impl Worker { // Replace any existing actor (handles reconnect / job restart) self.stream_actor = Some(StreamOrderingActor::new(hints_processor, job_id)); - GRPC_METRICS.active_streams.fetch_add(1, std::sync::atomic::Ordering::Relaxed); } - StreamMessageKind::Data => match &self.stream_actor { - Some(actor) => { - GRPC_METRICS.callbacks_invoked.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - actor.send(stream_data)?; - } + StreamMessageKind::Data | StreamMessageKind::End => match &self.stream_actor { + Some(actor) => actor.send(stream_data)?, None => { return Err(anyhow::anyhow!( "Received stream {:?} without a prior Start for job {}", @@ -637,18 +633,6 @@ impl Worker { )); } }, - StreamMessageKind::End => match &self.stream_actor { - Some(actor) => { - actor.send(stream_data)?; - GRPC_METRICS.active_streams.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); - } - None => { - return Err(anyhow::anyhow!( - "Received stream End without a prior Start for job {}", - stream_data.job_id - )); - } - }, } Ok(()) } diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 46c29a575..8c58d4dd9 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -194,24 +194,6 @@ impl WorkerNodeGrpc { let (computation_tx, mut computation_rx) = mpsc::unbounded_channel::(); let mut heartbeat_interval = tokio::time::interval(Duration::from_secs(30)); - // Spawn a canary task that measures tokio scheduler latency. - // If the runtime is starved (e.g. a blocking call is occupying all threads), - // this task wakes up late and increments the starvation counter visible to - // AsmRunnerMT's semaphore-timeout diagnostic. - let _canary = tokio::spawn(async { - let mut interval = tokio::time::interval(CANARY_INTERVAL); - loop { - let before = std::time::Instant::now(); - interval.tick().await; - let latency = before.elapsed(); - if latency > STARVATION_THRESHOLD { - GRPC_METRICS - .thread_starvation_count - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); - } - } - }); - // Main non-blocking event loop loop { tokio::select! { @@ -507,7 +489,21 @@ impl WorkerNodeGrpc { } } coordinator_message::Payload::StreamData(stream_data) => { + GRPC_METRICS.active_streams.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + GRPC_METRICS.callbacks_invoked.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let before = std::time::Instant::now(); + self.handle_stream_data(stream_data).await?; + + let elapsed = before.elapsed(); + if elapsed > Duration::from_millis(100) { + println!("WARNING: Callback took {:?} - possible starvation", elapsed); + GRPC_METRICS + .thread_starvation_count + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + + GRPC_METRICS.active_streams.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); } coordinator_message::Payload::JobCancelled(cancelled) => { info!("Job {} cancelled: {}", cancelled.job_id, cancelled.reason); diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index 1338d5c01..85b2e9267 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -250,7 +250,10 @@ impl StreamSink for HintsShmem { // Flow control based on buffer occupancy if available_space >= data_size { - debug!("[SHMEM] Sufficient space available ({}), breaking flow control loop", available_space); + debug!( + "[SHMEM] Sufficient space available ({}), breaking flow control loop", + available_space + ); break; } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 59a12d555..e386f6632 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -648,10 +648,8 @@ impl HintsProcessor { debug!("[DRAINER] Waiting on drain_signal condvar (timeout=50ms)"); #[allow(unused_assignments)] { - let (q, _) = state - .drain_signal - .wait_timeout(queue, Duration::from_millis(50)) - .unwrap(); + let (q, _) = + state.drain_signal.wait_timeout(queue, Duration::from_millis(50)).unwrap(); queue = q; } debug!("[DRAINER] Woke up from condvar wait"); From 5dc289996c8ee626b0017c9a2bc7dfaefa4d14d7 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:33:15 +0000 Subject: [PATCH 604/782] remove wait_timeout --- precompiles/hints/src/hints_processor.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index e386f6632..a1c67db37 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -645,12 +645,11 @@ impl HintsProcessor { break; } - debug!("[DRAINER] Waiting on drain_signal condvar (timeout=50ms)"); + // Wait for notification that a hint completed + debug!("[DRAINER] Waiting on drain_signal condvar"); #[allow(unused_assignments)] { - let (q, _) = - state.drain_signal.wait_timeout(queue, Duration::from_millis(50)).unwrap(); - queue = q; + queue = state.drain_signal.wait(queue).unwrap(); } debug!("[DRAINER] Woke up from condvar wait"); } From 0c5335197f649bfafcdd0cd77cd1f7260d0dad35 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:01:10 +0000 Subject: [PATCH 605/782] improved debug msg --- distributed/crates/worker/src/worker_node.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 8c58d4dd9..ce3568f56 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -769,6 +769,13 @@ impl WorkerNodeGrpc { let stream_data_dto: StreamDataDto = stream_data.into(); + info!( + "Received stream data for job {}: stream type {:?}, payload size {} bytes", + stream_data_dto.job_id, + stream_data_dto.stream_type, + stream_data_dto.stream_payload.as_ref().map_or(0, |p| p.payload.len()) + ); + if current_job_id != stream_data_dto.job_id { return Err(anyhow!( "Job ID mismatch in StreamData: expected {}, got {}", From ae02ec735317c4f5cf177fc61c38cdf68e4b616d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:05:29 +0000 Subject: [PATCH 606/782] added log --- distributed/crates/worker/src/worker_node.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index ce3568f56..14e01b435 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -201,10 +201,14 @@ impl WorkerNodeGrpc { Some(result) = response_stream.next() => { match result { Ok(message) => { + let uuid = uuid::Uuid::new_v4(); + + info!("0: START {}", uuid); if let Err(e) = self.handle_coordinator_message(message, &message_sender, &computation_tx).await { error!("Error handling coordinator message: {}", e); break; } + info!("1: END {}", uuid); } Err(e) => { error!("Error receiving message from coordinator: {}", e); From 815307d2692ce1abe8bdf98cc3f74197cfb1dcb3 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 21 Feb 2026 03:38:17 +0000 Subject: [PATCH 607/782] fix: move grpc spawn to spawn_blocking --- distributed/crates/worker/src/worker.rs | 81 ++++++++++---------- distributed/crates/worker/src/worker_node.rs | 13 ++-- precompiles/hints/src/hints_processor.rs | 2 +- 3 files changed, 45 insertions(+), 51 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 8d8b7b8a5..731509d39 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -402,7 +402,7 @@ impl Worker { tx: mpsc::UnboundedSender, ) -> Result> { self.partial_contribution_mpi_broadcast(&job).await?; - Ok(self.partial_contribution(job, tx).await) + Ok(self.partial_contribution(job, tx)) } pub async fn partial_contribution_mpi_broadcast(&self, job: &Mutex) -> Result<()> { @@ -439,7 +439,7 @@ impl Worker { tx: mpsc::UnboundedSender, ) -> Result> { self.prove_mpi_broadcast(&job, challenges.clone()).await?; - Ok(self.prove(job, challenges, tx).await) + Ok(self.prove(job, challenges, tx)) } pub async fn prove_mpi_broadcast( @@ -462,27 +462,26 @@ impl Worker { Ok(()) } - pub async fn handle_aggregate( + pub fn handle_aggregate( &self, job: Arc>, agg_params: AggregationParams, tx: mpsc::UnboundedSender, ) -> JoinHandle<()> { - self.aggregate(job, agg_params, tx).await + self.aggregate(job, agg_params, tx) } - pub async fn partial_contribution( + pub fn partial_contribution( &self, job: Arc>, tx: mpsc::UnboundedSender, ) -> JoinHandle<()> { let prover = self.prover.clone(); let pk = self.pk.clone(); - let options = self.get_proof_options(false); - tokio::spawn(async move { - let guard = job.lock().await; + tokio::task::spawn_blocking(move || { + let guard = job.blocking_lock(); let job_id = guard.job_id.clone(); info!("Computing Contribution for {job_id}"); @@ -495,26 +494,23 @@ impl Worker { ); let phase_inputs = proofman::ProvePhaseInputs::Contributions(proof_info); - let inputs_source = guard.data_ctx.input_source.clone(); let hints_source = guard.data_ctx.hints_source.clone(); - drop(guard); let result = Self::execute_contribution_task( job_id.clone(), - prover.as_ref(), + &prover, phase_inputs, inputs_source, hints_source, &pk, options, - ) - .await; - - let mut guard = job.lock().await; + ); + let mut guard = job.blocking_lock(); guard.executed_steps = prover.executed_steps(); + drop(guard); let execution_info = prover.get_execution_info().unwrap_or_else(|_| ExecutionInfo::default()); @@ -539,7 +535,7 @@ impl Worker { }) } - pub async fn execute_contribution_task( + pub fn execute_contribution_task( job_id: JobId, prover: &ZiskProver, phase_inputs: ProvePhaseInputs, @@ -646,10 +642,17 @@ impl Worker { let base_port = self.prover_config.asm_port; let local_rank = self.prover.local_rank(); let unlock_mapped_memory = self.prover_config.unlock_mapped_memory; - let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory)?; - let processor = HintsProcessor::builder(hints_shmem) - .build() - .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e))?; + + // HintsShmem::new and HintsProcessor::build perform OS-level shared-memory operations; + // run them on the blocking thread pool to avoid stalling Tokio workers. + let processor = tokio::task::spawn_blocking(move || -> Result { + let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory)?; + HintsProcessor::builder(hints_shmem) + .build() + .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e)) + }) + .await + .map_err(|e| anyhow::anyhow!("hints processor init panicked: {e}"))??; let worker_idx = self .current_job @@ -671,27 +674,23 @@ impl Worker { Ok(()) } - pub async fn prove( + pub fn prove( &self, job: Arc>, challenges: Vec, tx: mpsc::UnboundedSender, ) -> JoinHandle<()> { let prover = self.prover.clone(); - let options = self.get_proof_options(false); - tokio::spawn(async move { - let job = job.lock().await; - let job_id = job.job_id.clone(); + tokio::task::spawn_blocking(move || { + let job_id = job.blocking_lock().job_id.clone(); info!("Computing Prove for {job_id}"); let phase_inputs = proofman::ProvePhaseInputs::Internal(challenges); + let result = Self::execute_prove_task(job_id.clone(), &prover, phase_inputs, options); - let result = - Self::execute_prove_task(job_id.clone(), prover.as_ref(), phase_inputs, options) - .await; match result { Ok(data) => { let _ = tx.send(ComputationResult::Proofs { @@ -712,7 +711,7 @@ impl Worker { }) } - pub async fn execute_prove_task( + pub fn execute_prove_task( job_id: JobId, prover: &ZiskProver, phase_inputs: ProvePhaseInputs, @@ -741,19 +740,20 @@ impl Worker { Ok(proof) } - pub async fn aggregate( + pub fn aggregate( &self, job: Arc>, agg_params: AggregationParams, tx: mpsc::UnboundedSender, ) -> JoinHandle<()> { let prover = self.prover.clone(); - let options = self.get_proof_options(agg_params.compressed); - tokio::spawn(async move { - let job = job.lock().await; - let job_id = job.job_id.clone(); + tokio::task::spawn_blocking(move || { + let (job_id, executed_steps) = { + let guard = job.blocking_lock(); + (guard.job_id.clone(), guard.executed_steps) + }; info!("Starting aggregation step for {job_id}"); @@ -783,7 +783,7 @@ impl Worker { job_id, success: true, result: Ok(Some(proof)), - executed_steps: job.executed_steps, + executed_steps, }); } Err(error) => { @@ -792,7 +792,7 @@ impl Worker { job_id, success: false, result: Err(error), - executed_steps: job.executed_steps, + executed_steps, }); } } @@ -837,14 +837,13 @@ impl Worker { let result = Self::execute_contribution_task( job_id, - self.prover.as_ref(), + &self.prover, phase_inputs, input_source_dto, hints_source_dto, &self.pk, options, - ) - .await; + ); if let Err(e) = result { error!("Error during Contributions MPI broadcast execution: {}. Waiting for new job...", e); } @@ -853,9 +852,7 @@ impl Worker { let (job_id, phase_inputs, options): (JobId, ProvePhaseInputs, ProofOptions) = borsh::from_slice(&bytes[1..]).unwrap(); - let result = - Self::execute_prove_task(job_id, self.prover.as_ref(), phase_inputs, options) - .await; + let result = Self::execute_prove_task(job_id, &self.prover, phase_inputs, options); if let Err(e) = result { error!( "Error during Prove MPI broadcast execution: {}. Waiting for new job...", diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 14e01b435..405e81f69 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -19,11 +19,6 @@ use zisk_distributed_grpc_api::execute_task_response::ResultData; use zisk_distributed_grpc_api::*; use zisk_sdk::{Asm, Emu, ZiskBackend}; -/// Tokio scheduler latency above which we count a starvation event. -const STARVATION_THRESHOLD: Duration = Duration::from_millis(100); -/// How often the canary task wakes up to check scheduler latency. -const CANARY_INTERVAL: Duration = Duration::from_millis(10); - use crate::config::WorkerServiceConfig; pub enum WorkerNode { @@ -756,9 +751,11 @@ impl WorkerNodeGrpc { final_proof: agg_params.final_proof, compressed: agg_params.compressed, }; - self.worker.set_current_computation( - self.worker.handle_aggregate(job, agg_params, computation_tx.clone()).await, - ); + self.worker.set_current_computation(self.worker.handle_aggregate( + job, + agg_params, + computation_tx.clone(), + )); Ok(()) } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index a1c67db37..4598c431d 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -10,7 +10,7 @@ use std::collections::{HashMap, VecDeque}; use std::mem::ManuallyDrop; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; -use std::time::{Duration, Instant}; +use std::time::Instant; use tracing::{debug, info}; use zisk_common::io::{StreamProcessor, StreamSink}; use zisk_common::{ From 398f6399e2d18e57cc3b34f085340e1068fc6170 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 21 Feb 2026 03:48:18 +0000 Subject: [PATCH 608/782] remove debug msg --- distributed/crates/worker/src/worker_node.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 405e81f69..3df1106a8 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -196,14 +196,10 @@ impl WorkerNodeGrpc { Some(result) = response_stream.next() => { match result { Ok(message) => { - let uuid = uuid::Uuid::new_v4(); - - info!("0: START {}", uuid); if let Err(e) = self.handle_coordinator_message(message, &message_sender, &computation_tx).await { error!("Error handling coordinator message: {}", e); break; } - info!("1: END {}", uuid); } Err(e) => { error!("Error receiving message from coordinator: {}", e); From f20e40e2d907bd03b72df87916b6eac76f80d8ee Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 21 Feb 2026 03:55:22 +0000 Subject: [PATCH 609/782] remove debug msg --- distributed/crates/worker/src/worker.rs | 6 ------ distributed/crates/worker/src/worker_node.rs | 7 ------- 2 files changed, 13 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 731509d39..1a04472f5 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -598,12 +598,6 @@ impl Worker { /// /// The actor thread owns the reorder buffer and calls `process_hints` in sequence order. pub async fn route_stream_data(&mut self, stream_data: StreamDataDto) -> Result<()> { - if matches!(stream_data.stream_type, StreamMessageKind::End) { - info!( - "Received End stream message for job {}. Routing to stream actor and shutting it down.", - stream_data.job_id - ); - } match &stream_data.stream_type { StreamMessageKind::Start => { let job_id = stream_data.job_id.clone(); diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 3df1106a8..4bbd2de14 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -766,13 +766,6 @@ impl WorkerNodeGrpc { let stream_data_dto: StreamDataDto = stream_data.into(); - info!( - "Received stream data for job {}: stream type {:?}, payload size {} bytes", - stream_data_dto.job_id, - stream_data_dto.stream_type, - stream_data_dto.stream_payload.as_ref().map_or(0, |p| p.payload.len()) - ); - if current_job_id != stream_data_dto.job_id { return Err(anyhow!( "Job ID mismatch in StreamData: expected {}, got {}", From 9f01e2fca4715c4d5c71ec8c202bcdba793b368c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 21 Feb 2026 04:05:46 +0000 Subject: [PATCH 610/782] fix: added hash hints_stream --- common/src/io/stdin/zisk_stdin.rs | 4 ++++ executor/src/emu_asm.rs | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index 2343c2ba3..e3fb553fd 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -196,4 +196,8 @@ impl ZiskStdin { }) }) } + + pub fn has_hints_stream(&self) -> bool { + self.hints_stream.is_some() + } } diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 2207febed..05179f6ac 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -112,7 +112,8 @@ impl EmulatorAsm { let asm_resources_guard = self.asm_resources.lock().unwrap(); let asm_resources = asm_resources_guard.as_ref().expect("AsmResources not initialized"); - if use_hints { + let has_hints_stream = stdin.lock().unwrap().has_hints_stream(); + if use_hints && has_hints_stream { let hints_stream = stdin.lock().unwrap().take_hints_stream().expect("Hints stream not set"); asm_resources From db625713dcc2bd1ad229fe655962f6a4aaa5d52b Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 21 Feb 2026 04:12:07 +0000 Subject: [PATCH 611/782] fix: Arc pk in worker.rs --- distributed/crates/worker/src/worker.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 1a04472f5..7ccbd56ed 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -246,7 +246,7 @@ pub struct Worker { prover_config: ProverConfig, stream_actor: Option, - pk: ZiskProgramPK, + pk: Arc, hints_processor: Option>, } @@ -279,7 +279,7 @@ impl Worker { current_computation: None, prover, prover_config, - pk, + pk: Arc::new(pk), stream_actor: None, hints_processor: None, }) @@ -316,7 +316,7 @@ impl Worker { current_computation: None, prover, prover_config, - pk, + pk: Arc::new(pk), stream_actor: None, hints_processor: None, }) From 77a705a05128f45f98f945e76892760642bd8aac Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Mon, 23 Feb 2026 09:52:25 +0000 Subject: [PATCH 612/782] Feature set partition --- Cargo.lock | 97 +++++++++++--------- Cargo.toml | 16 ++-- common/src/io/stdin/zisk_stdin.rs | 4 +- distributed/crates/worker/src/worker.rs | 28 +++--- distributed/crates/worker/src/worker_node.rs | 6 ++ sdk/src/prover/asm.rs | 9 ++ sdk/src/prover/backend.rs | 20 +++- sdk/src/prover/emu.rs | 9 ++ sdk/src/prover/mod.rs | 16 ++++ 9 files changed, 133 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bccf3edeb..5750cc5b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -603,7 +603,7 @@ dependencies = [ "colored", "dirs", "executor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "futures", "indicatif", "mpi", @@ -1115,9 +1115,9 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "num-bigint", "num-traits", ] @@ -1414,7 +1414,7 @@ dependencies = [ "asm-runner", "crossbeam", "data-bus", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "itertools 0.14.0", "mem-common", "mem-planner-cpp", @@ -1465,6 +1465,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fields" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +dependencies = [ + "cfg-if", + "num-bigint", + "paste", + "serde", +] + [[package]] name = "fields" version = "0.16.0" @@ -2336,7 +2347,7 @@ name = "mem-common" version = "0.16.0" dependencies = [ "clap", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "num-bigint", "num-traits", "proofman-common", @@ -2772,10 +2783,10 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" dependencies = [ "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "num-bigint", "num-traits", "proofman-common", @@ -2894,7 +2905,7 @@ dependencies = [ "ark-secp256k1", "ark-secp256r1", "ark-std", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "lazy_static", "lib-c", "mem-common", @@ -2930,7 +2941,7 @@ dependencies = [ "ark-ff", "ark-secp256k1", "ark-std", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "lazy_static", "lib-c", "mem-common", @@ -2961,7 +2972,7 @@ dependencies = [ name = "precomp-big-int" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "generic-array", "lib-c", "mem-common", @@ -2982,7 +2993,7 @@ dependencies = [ name = "precomp-dma" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "generic-array", "lib-c", "mem-common", @@ -3005,7 +3016,7 @@ name = "precomp-keccakf" version = "0.16.0" dependencies = [ "circuit", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "path-clean", "pil-std-lib", "precompiles-common", @@ -3025,7 +3036,7 @@ dependencies = [ name = "precomp-poseidon2" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "mem-common", "pil-std-lib", "precompiles-common", @@ -3045,7 +3056,7 @@ dependencies = [ name = "precomp-sha256f" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "mem-common", "pil-std-lib", "precompiles-common", @@ -3064,7 +3075,7 @@ dependencies = [ name = "precompiles-common" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "mem-common", "sm-mem", "zisk-common", @@ -3134,7 +3145,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" dependencies = [ "bincode", "blake3", @@ -3145,7 +3156,7 @@ dependencies = [ "crossbeam-channel", "csv", "curves", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "libloading", "mpi", "num-bigint", @@ -3170,7 +3181,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" dependencies = [ "bincode", "borsh", @@ -3180,7 +3191,7 @@ dependencies = [ "crossbeam-queue", "csv", "env", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "lazy_static", "libloading", "mpi", @@ -3202,9 +3213,9 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "itoa", "proofman-common", "proofman-starks-lib-c", @@ -3215,7 +3226,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" dependencies = [ "proc-macro2", "quote", @@ -3225,7 +3236,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" dependencies = [ "crossbeam-channel", "tracing", @@ -3234,7 +3245,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" dependencies = [ "bincode", "bytemuck", @@ -3246,10 +3257,10 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" dependencies = [ "bytemuck", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "proofman-util", "rayon", "tracing", @@ -3608,7 +3619,7 @@ dependencies = [ "anyhow", "blake3", "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "proofman-common", "sm-rom", "tracing", @@ -4030,7 +4041,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "sm-arith" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "num-bigint", "pil-std-lib", "proofman-common", @@ -4050,7 +4061,7 @@ dependencies = [ name = "sm-binary" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "num-bigint", "pil-std-lib", "proofman-common", @@ -4070,7 +4081,7 @@ name = "sm-frequent-ops" version = "0.16.0" dependencies = [ "clap", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "num-bigint", "proofman-common", "proofman-util", @@ -4084,7 +4095,7 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "mem-common", "num-bigint", "pil-std-lib", @@ -4103,7 +4114,7 @@ dependencies = [ name = "sm-mem" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "mem-common", "num-bigint", "num-traits", @@ -4124,7 +4135,7 @@ name = "sm-rom" version = "0.16.0" dependencies = [ "asm-runner", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "itertools 0.14.0", "proofman-common", "proofman-macros", @@ -5721,10 +5732,10 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" dependencies = [ "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "libloading", "proofman-common", "proofman-util", @@ -5917,7 +5928,7 @@ version = "0.16.0" dependencies = [ "anyhow", "bincode", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "libc", "mpi", "proofman", @@ -5940,7 +5951,7 @@ name = "zisk-core" version = "0.16.0" dependencies = [ "elf", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "lib-c", "lib-float", "paste", @@ -6036,7 +6047,7 @@ dependencies = [ "clap", "colored", "config", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "precompiles-hints", "proofman", "proofman-common", @@ -6062,7 +6073,7 @@ dependencies = [ name = "zisk-pil" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "proofman-common", "proofman-macros", "rayon", @@ -6079,7 +6090,7 @@ dependencies = [ "bincode", "colored", "executor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "proofman", "proofman-common", "proofman-util", @@ -6119,7 +6130,7 @@ dependencies = [ "clap", "criterion 0.5.1", "data-bus", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "mem-common", "memmap2", "num-format", @@ -6147,7 +6158,7 @@ dependencies = [ "bytes", "cfg-if", "ctor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -6172,7 +6183,7 @@ dependencies = [ "anyhow", "bincode", "cfg-if", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "getrandom 0.2.17", "lazy_static", "lib-c", diff --git a/Cargo.toml b/Cargo.toml index bb19c7b67..b1607d95c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,14 +108,14 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } # Proofman Local development # proofman = { path = "../pil2-proofman/proofman" } # proofman-common = { path = "../pil2-proofman/common" } diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index e3fb553fd..2f046e8f5 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -191,9 +191,7 @@ impl ZiskStdin { pub fn take_hints_stream(&mut self) -> Option { self.hints_stream.take().map(|arc| { - Arc::try_unwrap(arc).unwrap_or_else(|_| { - panic!("StreamSource has multiple references") - }) + Arc::try_unwrap(arc).unwrap_or_else(|_| panic!("StreamSource has multiple references")) }) } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 7ccbd56ed..ea6ce0d5e 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -18,7 +18,6 @@ use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProgramPK, ZiskProver}; use crate::stream_ordering::StreamOrderingActor; use proofman::ExecutionInfo; -use proofman::ProofInfo; use proofman::ProvePhaseInputs; use proofman_common::ParamsGPU; use proofman_common::ProofOptions; @@ -408,13 +407,8 @@ impl Worker { pub async fn partial_contribution_mpi_broadcast(&self, job: &Mutex) -> Result<()> { let mut serialized = { let job = job.lock().await; - let proof_info = ProofInfo::new( - None, - job.total_compute_units as usize, - job.allocation.clone(), - job.rank_id as usize, - ); - let phase_inputs = ProvePhaseInputs::Contributions(proof_info); + + let phase_inputs = ProvePhaseInputs::Contributions(); let options = self.get_proof_options(false); @@ -486,14 +480,7 @@ impl Worker { info!("Computing Contribution for {job_id}"); - let proof_info = ProofInfo::new( - None, - guard.total_compute_units as usize, - guard.allocation.clone(), - guard.rank_id as usize, - ); - - let phase_inputs = proofman::ProvePhaseInputs::Contributions(proof_info); + let phase_inputs = proofman::ProvePhaseInputs::Contributions(); let inputs_source = guard.data_ctx.input_source.clone(); let hints_source = guard.data_ctx.hints_source.clone(); drop(guard); @@ -627,6 +614,15 @@ impl Worker { Ok(()) } + pub fn set_partition( + &self, + total_compute_units: usize, + allocation: Vec, + worker_idx: usize, + ) -> Result<()> { + self.prover.set_partition(total_compute_units, allocation, worker_idx) + } + /// Lazily initialises the `HintsProcessor` using configuration from the current job. async fn ensure_hints_processor(&mut self, job_id: &JobId) -> Result<()> { if self.hints_processor.is_some() { diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 4bbd2de14..6fe29cac6 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -544,6 +544,12 @@ impl WorkerNodeGrpc { return Err(anyhow!("Expected ContributionParams for Partial Contribution task")); }; + self.worker.set_partition( + params.job_compute_units as usize, + params.worker_allocation.clone(), + params.rank_id as usize, + )?; + let job_id = JobId::from(request.job_id); let input_source = match params.input_source { Some(InputSource::InputPath(ref inputs_uris)) => { diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 0e722fa43..e71a7a57e 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -304,6 +304,15 @@ impl ProverEngine for AsmProver { self.core_prover.backend.prove_phase(phase_inputs, options, phase) } + fn set_partition( + &self, + total_compute_units: usize, + allocation: Vec, + rank_id: usize, + ) -> Result<()> { + self.core_prover.backend.set_partition(total_compute_units, allocation, rank_id) + } + fn aggregate_proofs( &self, agg_proofs: Vec, diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 739d8e387..1a544cd6b 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -15,7 +15,7 @@ use colored::Colorize; use executor::ZiskExecutor; use fields::Goldilocks; use proofman::{ - AggProofs, ExecutionInfo, ProofInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult, + AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProtocol, SnarkWrapper, }; use proofman_common::{ProofCtx, ProofOptions, RowInfo}; @@ -330,10 +330,12 @@ impl ProverBackend { let compressed = matches!(mode, ProofMode::VadcopFinalCompressed); + proofman.set_partition(1, vec![0], 0)?; + proofman.set_barrier(); let proof = proofman .generate_proof_from_lib( - ProvePhaseInputs::Full(ProofInfo::new(None, 1, vec![0], 0)), + ProvePhaseInputs::Full(), ProofOptions::new( false, proof_options.aggregation, @@ -524,6 +526,20 @@ impl ProverBackend { .map_err(|e| anyhow::anyhow!("Error generating proof in phase {:?}: {}", phase, e)) } + pub(crate) fn set_partition( + &self, + total_compute_units: usize, + allocation: Vec, + rank_id: usize, + ) -> Result<()> { + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot set partition in verifier mode"))?; + + Ok(proofman.set_partition(total_compute_units, allocation, rank_id)?) + } + pub(crate) fn get_execution_info(&self) -> Result { let proofman = self .proofman diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 71aeec4eb..97306120d 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -222,6 +222,15 @@ impl ProverEngine for EmuProver { self.core_prover.backend.prove_phase(phase_inputs, options, phase) } + fn set_partition( + &self, + total_compute_units: usize, + allocation: Vec, + rank_id: usize, + ) -> Result<()> { + self.core_prover.backend.set_partition(total_compute_units, allocation, rank_id) + } + fn aggregate_proofs( &self, agg_proofs: Vec, diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 1bae1c57d..8b8de5ef6 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -801,6 +801,13 @@ pub trait ProverEngine { phase: ProvePhase, ) -> Result; + fn set_partition( + &self, + total_compute_units: usize, + allocation: Vec, + rank_id: usize, + ) -> Result<()>; + fn aggregate_proofs( &self, agg_proofs: Vec, @@ -975,6 +982,15 @@ impl ZiskProver { self.prover.prove_phase(phase_inputs, options, phase) } + pub fn set_partition( + &self, + total_compute_units: usize, + allocation: Vec, + rank_id: usize, + ) -> Result<()> { + self.prover.set_partition(total_compute_units, allocation, rank_id) + } + pub fn aggregate_proofs( &self, agg_proofs: Vec, From 4f54a8fb8004b467037447b218697d65bc1b0524 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:21:58 +0000 Subject: [PATCH 613/782] update Cargo.lock --- Cargo.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5750cc5b9..7e58e8962 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" dependencies = [ "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "num-bigint", @@ -1468,7 +1468,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" dependencies = [ "cfg-if", "num-bigint", @@ -2783,7 +2783,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" dependencies = [ "colored", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", @@ -3145,7 +3145,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" dependencies = [ "bincode", "blake3", @@ -3181,7 +3181,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" dependencies = [ "bincode", "borsh", @@ -3213,7 +3213,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" dependencies = [ "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", "itoa", @@ -3226,7 +3226,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" dependencies = [ "proc-macro2", "quote", @@ -3236,7 +3236,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" dependencies = [ "crossbeam-channel", "tracing", @@ -3245,7 +3245,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" dependencies = [ "bincode", "bytemuck", @@ -3257,7 +3257,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" dependencies = [ "bytemuck", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", @@ -5732,7 +5732,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#0da9c107ec1d84ddf992077d8dab4ff3f21b1567" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" dependencies = [ "colored", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", From 10413e3f4e517b8ccfe5aa574906cc17e4212285 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:23:32 +0000 Subject: [PATCH 614/782] remove debug messages --- emulator-asm/asm-runner/src/hints_shmem.rs | 15 ------ precompiles/hints/src/hints_processor.rs | 54 ---------------------- 2 files changed, 69 deletions(-) diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index 85b2e9267..a114f698d 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -188,11 +188,9 @@ impl StreamSink for HintsShmem { #[inline] fn submit(&self, processed: Vec) -> anyhow::Result<()> { let data_size = processed.len() as u64; - debug!("[SHMEM] submit() called with {} u64 elements", data_size); // Early return for empty data if data_size == 0 { - debug!("[SHMEM] submit() early return - empty data"); return Ok(()); } @@ -216,7 +214,6 @@ impl StreamSink for HintsShmem { // Read current write position once let write_pos = unified.control_writer.read_u64_at(0); - debug!("[SHMEM] Current write_pos={}, attempting to submit {} u64s", write_pos, data_size); // Flow control: wait until all consumers have advanced enough // We need to wait for the slowest consumer (minimum read position) @@ -250,42 +247,30 @@ impl StreamSink for HintsShmem { // Flow control based on buffer occupancy if available_space >= data_size { - debug!( - "[SHMEM] Sufficient space available ({}), breaking flow control loop", - available_space - ); break; } // Not enough space - wait for the SLOWEST consumer to signal progress - debug!("[SHMEM] Insufficient space: available={}, needed={}, waiting on consumer {} (read_pos={})", - available_space, data_size, slowest_idx, min_read_pos); // Retry on interrupt (EINTR) if separate[slowest_idx].sem_read.wait().is_err() { - debug!("[SHMEM] sem_read.wait() returned error, retrying"); continue; } - debug!("[SHMEM] sem_read.wait() succeeded, retrying space check"); } // Write data ONCE to the unified shared memory buffer - debug!("[SHMEM] Writing {} u64s to ring buffer at pos {}", data_size, write_pos); unified.data_writer.write_ring_buffer(&processed)?; fence(Ordering::Release); // Update write position ONCE in control memory unified.control_writer.write_u64_at(0, write_pos + data_size); - debug!("[SHMEM] Updated write_pos to {}", write_pos + data_size); fence(Ordering::Release); // Notify ALL consumers that new data is available - debug!("[SHMEM] Posting to {} consumer semaphores", separate.len()); for res in separate.iter_mut() { res.sem_available.post()?; } - debug!("[SHMEM] submit() completed successfully"); Ok(()) } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 4598c431d..12f78fdae 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -261,11 +261,6 @@ impl HintsProcessor { /// * `Ok(false)` - Batch processed successfully, no CTRL_END /// * `Err` - If a previous error occurred or hints are malformed pub fn process_hints(&self, hints: &[u64], first_batch: bool) -> Result { - debug!( - "[PROCESS] process_hints called with {} u64s, first_batch={}", - hints.len(), - first_batch - ); let mut has_ctrl_end = false; // Take any pending partial hint from previous batch @@ -416,9 +411,7 @@ impl HintsProcessor { let state = Arc::clone(&self.state); let custom_handlers = Arc::clone(&self.custom_handlers); self.pool.spawn(move || { - debug!("[WORKER] seq={} dispatched", seq_id); Self::worker_thread(state, hint, generation, seq_id, custom_handlers); - debug!("[WORKER] seq={} done", seq_id); }); idx += length; @@ -480,10 +473,6 @@ impl HintsProcessor { // Check generation first to detect stale workers (before processing) let current_gen = state.generation.load(Ordering::SeqCst); if generation != current_gen { - debug!( - "[WORKER] seq={} early return: generation mismatch (worker={}, current={})", - seq_id, generation, current_gen - ); return; } @@ -508,47 +497,28 @@ impl HintsProcessor { Err(anyhow::anyhow!("Worker panicked processing hint: {}", msg)) }); - debug!("[WORKER] seq={} processed, acquiring lock", seq_id); - // Store result - MUST fill slot even if error occurred let mut queue = state.queue.lock().unwrap(); // Check generation again in case reset happened during processing let current_gen = state.generation.load(Ordering::SeqCst); if generation != current_gen { - debug!("[WORKER] seq={} early return: generation mismatch after processing", seq_id); return; } // Calculate offset in buffer; handle drained slots if seq_id < queue.next_drain_seq { - debug!( - "[WORKER] seq={} early return: seq_id < next_drain_seq ({})", - seq_id, queue.next_drain_seq - ); return; } let offset = seq_id - queue.next_drain_seq; // Check if slot exists - if not, drainer already processed and removed it if offset >= queue.buffer.len() { - debug!( - "[WORKER] seq={} early return: offset {} >= buffer.len() {}", - seq_id, - offset, - queue.buffer.len() - ); return; } // Fill the slot to allow drainer to proceed (critical for ordering) queue.buffer[offset] = Some(result); - debug!( - "[WORKER] seq={} filled slot at offset {}, buffer_len={}", - seq_id, - offset, - queue.buffer.len() - ); // Only wake the drainer when we filled the FRONT slot (offset == 0). // The drainer can only make progress when buffer[0] is ready; waking it @@ -560,7 +530,6 @@ impl HintsProcessor { // drainer cannot enter condvar.wait() while we hold the lock, so this // notification cannot be lost. if offset == 0 { - debug!("[WORKER] seq={} calling notify_all (front slot)", seq_id); state.drain_signal.notify_all(); } @@ -570,19 +539,11 @@ impl HintsProcessor { /// Drainer thread that waits for hints to complete and drains ready results from queue. fn drainer_thread(state: Arc, hints_sink: Arc) { - debug!("[DRAINER] Thread started"); loop { - debug!("[DRAINER] Attempting to acquire queue lock"); let mut queue = state.queue.lock().unwrap(); - debug!( - "[DRAINER] Acquired queue lock, buffer_len={}, next_drain_seq={}", - queue.buffer.len(), - queue.next_drain_seq - ); // Check for shutdown if state.shutdown.load(Ordering::Acquire) { - debug!("[DRAINER] Shutdown signal received, exiting"); break; } @@ -590,35 +551,25 @@ impl HintsProcessor { let mut drained_any = false; while let Some(Some(res)) = queue.buffer.front() { drained_any = true; - let current_seq = queue.next_drain_seq; match res { Ok(data) => { // Clone data before dropping lock - debug!( - "[DRAINER] Found ready result seq={}, size={} u64s, cloning", - current_seq, - data.len() - ); let data_to_submit = data.clone(); queue.buffer.pop_front(); queue.next_drain_seq += 1; // Drop lock before submitting to avoid blocking workers - debug!("[DRAINER] Dropping lock before submit seq={}", current_seq); drop(queue); // Submit to sink - debug!("[DRAINER] Calling hints_sink.submit() for seq={}", current_seq); if let Err(e) = hints_sink.submit(data_to_submit) { eprintln!("Error submitting to sink: {}", e); state.error_flag.store(true, Ordering::Release); state.drain_signal.notify_all(); return; } - debug!("[DRAINER] Completed submit seq={}", current_seq); // Re-acquire lock for next iteration - debug!("[DRAINER] Re-acquiring lock after seq={}", current_seq); queue = state.queue.lock().unwrap(); } Err(e) => { @@ -635,25 +586,20 @@ impl HintsProcessor { // If we drained any results, notify wait_for_completion that buffer changed if drained_any { - debug!("[DRAINER] Notifying wait_for_completion"); state.drain_signal.notify_all(); } // Check for shutdown again before waiting if state.shutdown.load(Ordering::Acquire) { - debug!("[DRAINER] Shutdown signal received before wait, exiting"); break; } // Wait for notification that a hint completed - debug!("[DRAINER] Waiting on drain_signal condvar"); #[allow(unused_assignments)] { queue = state.drain_signal.wait(queue).unwrap(); } - debug!("[DRAINER] Woke up from condvar wait"); } - debug!("[DRAINER] Thread exiting"); } /// Waits for all pending hints to be processed and drained. From 582b080653a5bb54c82071eda7d845188264d82e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:25:42 +0000 Subject: [PATCH 615/782] fix: add a method to discover if it has to calculate rom sm --- distributed/crates/worker/src/worker.rs | 23 ++++++------------- .../asm-runner/src/asm_services/services.rs | 2 +- executor/src/emu_asm.rs | 9 +++----- executor/src/emu_rust.rs | 2 -- executor/src/executor.rs | 7 +++++- executor/src/lib.rs | 5 +--- executor/src/rom_executor.rs | 3 --- sdk/src/prover/asm.rs | 4 ++++ sdk/src/prover/backend.rs | 9 ++++++++ sdk/src/prover/emu.rs | 4 ++++ sdk/src/prover/mod.rs | 6 +++++ 11 files changed, 41 insertions(+), 33 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index ea6ce0d5e..cc45c87d3 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -589,7 +589,7 @@ impl Worker { StreamMessageKind::Start => { let job_id = stream_data.job_id.clone(); - self.ensure_hints_processor(&job_id).await?; + self.ensure_hints_processor().await?; let hints_processor = self.hints_processor.as_ref().unwrap(); @@ -614,6 +614,10 @@ impl Worker { Ok(()) } + pub fn is_first_partition(&self) -> Result { + self.prover.is_first_partition() + } + pub fn set_partition( &self, total_compute_units: usize, @@ -624,7 +628,7 @@ impl Worker { } /// Lazily initialises the `HintsProcessor` using configuration from the current job. - async fn ensure_hints_processor(&mut self, job_id: &JobId) -> Result<()> { + async fn ensure_hints_processor(&mut self) -> Result<()> { if self.hints_processor.is_some() { return Ok(()); } @@ -644,20 +648,7 @@ impl Worker { .await .map_err(|e| anyhow::anyhow!("hints processor init panicked: {e}"))??; - let worker_idx = self - .current_job - .as_ref() - .ok_or_else(|| { - anyhow::anyhow!( - "Received stream data for job {}, but no current job is set", - job_id - ) - })? - .lock() - .await - .rank_id as usize; - - processor.set_has_rom_sm(worker_idx == 0); + processor.set_has_rom_sm(self.prover.is_first_partition()?); self.hints_processor = Some(Arc::new(processor)); diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index a78f051a5..0f9fc4a32 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -208,7 +208,7 @@ impl AsmServices { unsafe { command.pre_exec(|| { - // Ignore failure silently (matches nice behavior) + // Ignore failure silently libc::setpriority(libc::PRIO_PROCESS, 0, -5); Ok(()) }); diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 05179f6ac..c33c4f914 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -14,7 +14,6 @@ use asm_runner::{ }; use data_bus::DataBusTrait; use fields::PrimeField64; -use proofman_common::ProofCtx; use sm_rom::RomSM; use zisk_common::{ io::ZiskStdin, stats_begin, stats_end, ChunkId, EmuTrace, ExecutorStatsHandle, StatsScope, @@ -97,7 +96,6 @@ impl EmulatorAsm { &self, zisk_rom: &ZiskRom, stdin: &Mutex, - pctx: &ProofCtx, sm_bundle: &StaticSMBundle, use_hints: bool, stats: &ExecutorStatsHandle, @@ -174,7 +172,7 @@ impl EmulatorAsm { }); // Run the ROM histogram only on partition 0 as it is always computed by this partition - let has_rom_sm = pctx.dctx_is_first_partition(); + let has_rom_sm = true; //pctx.dctx_is_first_partition(); let _stats = stats.clone(); @@ -203,7 +201,7 @@ impl EmulatorAsm { let steps = min_traces.iter().map(|trace| trace.steps).sum::(); // If the world rank is 0, wait for the ROM Histogram thread to finish and set the handler - if has_rom_sm { + if handle_rh.is_some() { self.rom_sm.as_ref().unwrap().set_asm_runner_handler( handle_rh.expect("Error during Assembly ROM Histogram thread execution"), ); @@ -310,7 +308,6 @@ impl crate::Emulator for EmulatorAsm { &self, zisk_rom: &ZiskRom, stdin: &Mutex, - pctx: &ProofCtx, sm_bundle: &StaticSMBundle, use_hints: bool, stats: &ExecutorStatsHandle, @@ -322,6 +319,6 @@ impl crate::Emulator for EmulatorAsm { Option>, u64, ) { - self.execute(zisk_rom, stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope) + self.execute(zisk_rom, stdin, sm_bundle, use_hints, stats, caller_stats_scope) } } diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index 2766f0262..410192931 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -3,7 +3,6 @@ use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; use asm_runner::AsmRunnerMO; use data_bus::DataBusTrait; use fields::PrimeField64; -use proofman_common::ProofCtx; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rayon::prelude::*; use zisk_common::{ @@ -165,7 +164,6 @@ impl crate::Emulator for EmulatorRust { &self, zisk_rom: &ZiskRom, stdin: &Mutex, - _pctx: &ProofCtx, sm_bundle: &StaticSMBundle, _use_hints: bool, _stats: &ExecutorStatsHandle, diff --git a/executor/src/executor.rs b/executor/src/executor.rs index cb058943b..14a4a5d7a 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -146,7 +146,6 @@ impl WitnessComponent for ZiskExecutor { .map_err(|e| proofman_common::ProofmanError::InvalidSetup(e.to_string()))?; let output = self.rom_executor.execute( &zisk_rom, - &pctx, self.registry.sm_bundle(), self.state.use_hints.load(std::sync::atomic::Ordering::SeqCst), &self.state.stats, @@ -233,6 +232,12 @@ impl WitnessComponent for ZiskExecutor { // Configure instance checkpoints using registry method self.registry.configure_checkpoints(&pctx, &self.state, &secn_global_ids); + // // Wait until the ROM histogram thread finishes and set the handler for the ASM emulator if applicable + // if handle_rh.is_some() { + // handle_rh.join().expect("Error during Assembly ROM Histogram thread execution"); + // } + // TODO! Afegir al hints_shmem en el reset que els resets estan posats a zero + // Reset hints stream self.rom_executor.reset_hints_stream(); diff --git a/executor/src/lib.rs b/executor/src/lib.rs index aa47d4478..c5ec78c9a 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -47,7 +47,6 @@ pub type NestedDeviceMetricsList = HashMap; use asm_runner::AsmRunnerMO; use fields::PrimeField64; -use proofman_common::ProofCtx; use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; @@ -59,7 +58,6 @@ pub trait Emulator: Send + Sync { &self, zisk_rom: &ZiskRom, stdin: &Mutex, - pctx: &ProofCtx, sm_bundle: &StaticSMBundle, use_hints: bool, stats: &ExecutorStatsHandle, @@ -112,7 +110,6 @@ impl Emulator for EmulatorKind { &self, zisk_rom: &ZiskRom, stdin: &Mutex, - pctx: &ProofCtx, sm_bundle: &StaticSMBundle, use_hints: bool, stats: &ExecutorStatsHandle, @@ -126,7 +123,7 @@ impl Emulator for EmulatorKind { ) { match self { Self::Asm(e) => { - e.execute(zisk_rom, stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope) + e.execute(zisk_rom, stdin, sm_bundle, use_hints, stats, caller_stats_scope) } Self::Rust(e) => e.execute(zisk_rom, stdin, sm_bundle), } diff --git a/executor/src/rom_executor.rs b/executor/src/rom_executor.rs index bcb5482f2..658ad4ebc 100644 --- a/executor/src/rom_executor.rs +++ b/executor/src/rom_executor.rs @@ -9,7 +9,6 @@ use crate::{ }; use asm_runner::AsmRunnerMO; use fields::PrimeField64; -use proofman_common::ProofCtx; use std::{sync::Mutex, thread::JoinHandle}; use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; use zisk_core::ZiskRom; @@ -75,7 +74,6 @@ impl RomExecutor { pub fn execute( &self, zisk_rom: &ZiskRom, - pctx: &ProofCtx, sm_bundle: &StaticSMBundle, use_hints: bool, stats: &ExecutorStatsHandle, @@ -84,7 +82,6 @@ impl RomExecutor { let (min_traces, main_count, secn_count, handle_mo, steps) = self.emulator.execute( zisk_rom, &self.stdin, - pctx, sm_bundle, use_hints, stats, diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index e71a7a57e..7b73c5dfe 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -313,6 +313,10 @@ impl ProverEngine for AsmProver { self.core_prover.backend.set_partition(total_compute_units, allocation, rank_id) } + fn is_first_partition(&self) -> Result { + self.core_prover.backend.is_first_partition() + } + fn aggregate_proofs( &self, agg_proofs: Vec, diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 1a544cd6b..32d3c8fe8 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -540,6 +540,15 @@ impl ProverBackend { Ok(proofman.set_partition(total_compute_units, allocation, rank_id)?) } + pub(crate) fn is_first_partition(&self) -> Result { + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot set partition in verifier mode"))?; + + Ok(proofman.is_first_partition()) + } + pub(crate) fn get_execution_info(&self) -> Result { let proofman = self .proofman diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 97306120d..7ccea1e90 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -231,6 +231,10 @@ impl ProverEngine for EmuProver { self.core_prover.backend.set_partition(total_compute_units, allocation, rank_id) } + fn is_first_partition(&self) -> Result { + self.core_prover.backend.is_first_partition() + } + fn aggregate_proofs( &self, agg_proofs: Vec, diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 8b8de5ef6..217797bb1 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -808,6 +808,8 @@ pub trait ProverEngine { rank_id: usize, ) -> Result<()>; + fn is_first_partition(&self) -> Result; + fn aggregate_proofs( &self, agg_proofs: Vec, @@ -991,6 +993,10 @@ impl ZiskProver { self.prover.set_partition(total_compute_units, allocation, rank_id) } + pub fn is_first_partition(&self) -> Result { + self.prover.is_first_partition() + } + pub fn aggregate_proofs( &self, agg_proofs: Vec, From 15a44cbac2bb4a60f50a4d51f74b11fa349b9cb0 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:52:57 +0000 Subject: [PATCH 616/782] fix: perform the rh join() before finish the execution because in some corner cases it can fail --- executor/src/emu_asm.rs | 20 +++++++++++++------- executor/src/emu_rust.rs | 2 ++ executor/src/executor.rs | 7 +------ executor/src/lib.rs | 5 ++++- executor/src/rom_executor.rs | 3 +++ sdk/src/prover/emu.rs | 6 +++--- state-machines/rom/src/rom.rs | 18 ++++++------------ state-machines/rom/src/rom_instance.rs | 15 +++++---------- 8 files changed, 37 insertions(+), 39 deletions(-) diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index c33c4f914..779aafe24 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -14,6 +14,7 @@ use asm_runner::{ }; use data_bus::DataBusTrait; use fields::PrimeField64; +use proofman_common::ProofCtx; use sm_rom::RomSM; use zisk_common::{ io::ZiskStdin, stats_begin, stats_end, ChunkId, EmuTrace, ExecutorStatsHandle, StatsScope, @@ -96,6 +97,7 @@ impl EmulatorAsm { &self, zisk_rom: &ZiskRom, stdin: &Mutex, + pctx: &ProofCtx, sm_bundle: &StaticSMBundle, use_hints: bool, stats: &ExecutorStatsHandle, @@ -172,7 +174,7 @@ impl EmulatorAsm { }); // Run the ROM histogram only on partition 0 as it is always computed by this partition - let has_rom_sm = true; //pctx.dctx_is_first_partition(); + let has_rom_sm = pctx.dctx_is_first_partition(); let _stats = stats.clone(); @@ -200,11 +202,14 @@ impl EmulatorAsm { // Store execute steps let steps = min_traces.iter().map(|trace| trace.steps).sum::(); - // If the world rank is 0, wait for the ROM Histogram thread to finish and set the handler - if handle_rh.is_some() { - self.rom_sm.as_ref().unwrap().set_asm_runner_handler( - handle_rh.expect("Error during Assembly ROM Histogram thread execution"), - ); + // If the world rank is 0, wait for the ROM Histogram thread to finish and collect the result + if has_rom_sm { + let rh_data = handle_rh + .expect("ROM Histogram thread was not spawned") + .join() + .expect("Error during ROM Histogram thread execution"); + + self.rom_sm.as_ref().unwrap().set_rh_data(rh_data); } stats_end!(stats, &_exec_scope); @@ -308,6 +313,7 @@ impl crate::Emulator for EmulatorAsm { &self, zisk_rom: &ZiskRom, stdin: &Mutex, + pctx: &ProofCtx, sm_bundle: &StaticSMBundle, use_hints: bool, stats: &ExecutorStatsHandle, @@ -319,6 +325,6 @@ impl crate::Emulator for EmulatorAsm { Option>, u64, ) { - self.execute(zisk_rom, stdin, sm_bundle, use_hints, stats, caller_stats_scope) + self.execute(zisk_rom, stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope) } } diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index 410192931..2766f0262 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; use asm_runner::AsmRunnerMO; use data_bus::DataBusTrait; use fields::PrimeField64; +use proofman_common::ProofCtx; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rayon::prelude::*; use zisk_common::{ @@ -164,6 +165,7 @@ impl crate::Emulator for EmulatorRust { &self, zisk_rom: &ZiskRom, stdin: &Mutex, + _pctx: &ProofCtx, sm_bundle: &StaticSMBundle, _use_hints: bool, _stats: &ExecutorStatsHandle, diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 14a4a5d7a..cb058943b 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -146,6 +146,7 @@ impl WitnessComponent for ZiskExecutor { .map_err(|e| proofman_common::ProofmanError::InvalidSetup(e.to_string()))?; let output = self.rom_executor.execute( &zisk_rom, + &pctx, self.registry.sm_bundle(), self.state.use_hints.load(std::sync::atomic::Ordering::SeqCst), &self.state.stats, @@ -232,12 +233,6 @@ impl WitnessComponent for ZiskExecutor { // Configure instance checkpoints using registry method self.registry.configure_checkpoints(&pctx, &self.state, &secn_global_ids); - // // Wait until the ROM histogram thread finishes and set the handler for the ASM emulator if applicable - // if handle_rh.is_some() { - // handle_rh.join().expect("Error during Assembly ROM Histogram thread execution"); - // } - // TODO! Afegir al hints_shmem en el reset que els resets estan posats a zero - // Reset hints stream self.rom_executor.reset_hints_stream(); diff --git a/executor/src/lib.rs b/executor/src/lib.rs index c5ec78c9a..aa47d4478 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -47,6 +47,7 @@ pub type NestedDeviceMetricsList = HashMap; use asm_runner::AsmRunnerMO; use fields::PrimeField64; +use proofman_common::ProofCtx; use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; @@ -58,6 +59,7 @@ pub trait Emulator: Send + Sync { &self, zisk_rom: &ZiskRom, stdin: &Mutex, + pctx: &ProofCtx, sm_bundle: &StaticSMBundle, use_hints: bool, stats: &ExecutorStatsHandle, @@ -110,6 +112,7 @@ impl Emulator for EmulatorKind { &self, zisk_rom: &ZiskRom, stdin: &Mutex, + pctx: &ProofCtx, sm_bundle: &StaticSMBundle, use_hints: bool, stats: &ExecutorStatsHandle, @@ -123,7 +126,7 @@ impl Emulator for EmulatorKind { ) { match self { Self::Asm(e) => { - e.execute(zisk_rom, stdin, sm_bundle, use_hints, stats, caller_stats_scope) + e.execute(zisk_rom, stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope) } Self::Rust(e) => e.execute(zisk_rom, stdin, sm_bundle), } diff --git a/executor/src/rom_executor.rs b/executor/src/rom_executor.rs index 658ad4ebc..bcb5482f2 100644 --- a/executor/src/rom_executor.rs +++ b/executor/src/rom_executor.rs @@ -9,6 +9,7 @@ use crate::{ }; use asm_runner::AsmRunnerMO; use fields::PrimeField64; +use proofman_common::ProofCtx; use std::{sync::Mutex, thread::JoinHandle}; use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; use zisk_core::ZiskRom; @@ -74,6 +75,7 @@ impl RomExecutor { pub fn execute( &self, zisk_rom: &ZiskRom, + pctx: &ProofCtx, sm_bundle: &StaticSMBundle, use_hints: bool, stats: &ExecutorStatsHandle, @@ -82,6 +84,7 @@ impl RomExecutor { let (min_traces, main_count, secn_count, handle_mo, steps) = self.emulator.execute( zisk_rom, &self.stdin, + pctx, sm_bundle, use_hints, stats, diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 7ccea1e90..837161c72 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -232,9 +232,9 @@ impl ProverEngine for EmuProver { } fn is_first_partition(&self) -> Result { - self.core_prover.backend.is_first_partition() - } - + self.core_prover.backend.is_first_partition() + } + fn aggregate_proofs( &self, agg_proofs: Vec, diff --git a/state-machines/rom/src/rom.rs b/state-machines/rom/src/rom.rs index 73107d48d..f12bc563a 100644 --- a/state-machines/rom/src/rom.rs +++ b/state-machines/rom/src/rom.rs @@ -8,10 +8,7 @@ //! - `ComponentBuilder` trait implementations for creating counters, planners, and input //! collectors. -use std::{ - sync::{atomic::AtomicU64, Arc, Mutex}, - thread::JoinHandle, -}; +use std::sync::{atomic::AtomicU64, Arc, Mutex}; use crate::{RomInstance, RomPlanner}; use asm_runner::{AsmRHData, AsmRunnerRH}; @@ -37,7 +34,7 @@ pub struct RomSM { /// Shared program instruction counter for monitoring ROM operations. prog_inst_count: Arc>, - asm_runner_handler: Mutex>>, + rh_data: Mutex>, } impl RomSM { @@ -62,12 +59,12 @@ impl RomSM { zisk_rom: Mutex::new(None), bios_inst_count: Arc::new(bios_inst_count), prog_inst_count: Arc::new(prog_inst_count), - asm_runner_handler: Mutex::new(None), + rh_data: Mutex::new(None), }) } - pub fn set_asm_runner_handler(&self, handler: JoinHandle) { - *self.asm_runner_handler.lock().unwrap() = Some(handler); + pub fn set_rh_data(&self, handler: AsmRunnerRH) { + *self.rh_data.lock().unwrap() = Some(handler); } pub fn set_rom(&self, zisk_rom: Arc) { @@ -300,15 +297,12 @@ impl ComponentBuilder for RomSM { /// # Returns /// A boxed implementation of `RomInstance`. fn build_instance(&self, ictx: InstanceCtx) -> Box> { - let mut handle_rh_guard = self.asm_runner_handler.lock().unwrap(); - let handle_rh = handle_rh_guard.take(); - Box::new(RomInstance::new( self.zisk_rom.lock().unwrap().as_ref().unwrap().clone(), ictx, self.bios_inst_count.clone(), self.prog_inst_count.clone(), - handle_rh, + self.rh_data.lock().unwrap().take(), )) } } diff --git a/state-machines/rom/src/rom_instance.rs b/state-machines/rom/src/rom_instance.rs index 97d013643..92ac66994 100644 --- a/state-machines/rom/src/rom_instance.rs +++ b/state-machines/rom/src/rom_instance.rs @@ -2,10 +2,7 @@ //! //! It is responsible for computing witnesses for ROM-related execution plans, -use std::{ - sync::{atomic::AtomicU64, Arc}, - thread::JoinHandle, -}; +use std::sync::{atomic::AtomicU64, Arc}; use crate::{rom_counter::RomCounter, RomSM}; use asm_runner::AsmRunnerRH; @@ -41,7 +38,7 @@ pub struct RomInstance { counter_stats: Mutex>, /// Optional handle for the ROM assembly runner thread. - handle_rh: Mutex>>, + handle_rh: Mutex>, /// Cached result from the assembly runner thread. asm_result: Mutex>, @@ -61,7 +58,7 @@ impl RomInstance { ictx: InstanceCtx, bios_inst_count: Arc>, prog_inst_count: Arc>, - handle_rh: Option>, + handle_rh: Option, ) -> Self { Self { zisk_rom, @@ -121,10 +118,8 @@ impl Instance for RomInstance { // Check if we already have the result cached if self.asm_result.lock().unwrap().is_none() { // Join the thread and cache the result - let handle_rh = self.handle_rh.lock().unwrap().take().unwrap(); - let result_rh = - handle_rh.join().expect("Error during Rom Histogram thread execution"); - *self.asm_result.lock().unwrap() = Some(result_rh); + let rh_data = self.handle_rh.lock().unwrap().take().unwrap(); + *self.asm_result.lock().unwrap() = Some(rh_data); } // Use the cached result From 97ac1c155b6e5af11ca9e8e0776bc93dd7ad67c7 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Mon, 23 Feb 2026 10:55:33 +0000 Subject: [PATCH 617/782] Move hints_stream out of stdin --- cli/src/commands/execute.rs | 4 +-- cli/src/commands/prove.rs | 4 +-- cli/src/commands/stats.rs | 4 +-- cli/src/commands/verify_constraints.rs | 4 +-- common/src/io/stdin/zisk_stdin.rs | 31 ++++------------------ distributed/crates/worker/src/worker.rs | 4 +-- emulator-asm/asm-runner/src/shmem_utils.rs | 2 +- executor/src/asm_resources.rs | 15 +++++++++-- executor/src/emu_asm.rs | 9 ++----- executor/src/emu_rust.rs | 4 +-- sdk/src/prover/mod.rs | 22 +++++++++++++++ 11 files changed, 55 insertions(+), 48 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 5899f20f7..c47557818 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -139,7 +139,7 @@ impl ZiskExecute { pub fn run_asm( &mut self, - mut stdin: ZiskStdin, + stdin: ZiskStdin, hints_stream: Option, ) -> Result { let prover = ProverClient::builder() @@ -158,7 +158,7 @@ impl ZiskExecute { let elf = ElfBinaryFromFile::new(&self.elf, hints_stream.is_some())?; let (pk, _) = prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { - stdin.set_hints_stream(hints_stream); + pk.register_hints_stream(hints_stream)?; } prover.execute(&pk, stdin) } diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index f734523b7..835cc448f 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -256,7 +256,7 @@ impl ZiskProve { pub fn run_asm( &mut self, - mut stdin: ZiskStdin, + stdin: ZiskStdin, hints_stream: Option, gpu_params: Option, ) -> Result<(ZiskProveResult, i32)> { @@ -289,7 +289,7 @@ impl ZiskProve { }; if let Some(hints_stream) = hints_stream { - stdin.set_hints_stream(hints_stream); + pk.register_hints_stream(hints_stream)?; } let world_rank = prover.world_rank(); diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index efa14d5ac..51bced3c1 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -177,7 +177,7 @@ impl ZiskStats { pub fn run_asm( &mut self, - mut stdin: ZiskStdin, + stdin: ZiskStdin, hints_stream: Option, ) -> Result<(i32, i32, Option)> { let prover = ProverClient::builder() @@ -197,7 +197,7 @@ impl ZiskStats { let (pk, _) = prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { - stdin.set_hints_stream(hints_stream); + pk.register_hints_stream(hints_stream)?; } let mpi_node = self.mpi_node.map(|n| n as u32); prover.stats(&pk, stdin, self.debug.clone(), self.minimal_memory, mpi_node) diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 455e1df4d..edee9c86e 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -152,7 +152,7 @@ impl ZiskVerifyConstraints { pub fn run_asm( &mut self, - mut stdin: ZiskStdin, + stdin: ZiskStdin, hints_stream: Option, ) -> Result { let prover = ProverClient::builder() @@ -172,7 +172,7 @@ impl ZiskVerifyConstraints { let (pk, _) = prover.setup(&elf)?; if let Some(hints_stream) = hints_stream { - stdin.set_hints_stream(hints_stream); + pk.register_hints_stream(hints_stream)?; } prover.verify_constraints_debug(&pk, stdin, self.debug.clone()) } diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index 2f046e8f5..531c5b309 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -1,4 +1,4 @@ -use crate::io::{StreamSource, ZiskFileStdin, ZiskMemoryStdin, ZiskNullStdin}; +use crate::io::{ZiskFileStdin, ZiskMemoryStdin, ZiskNullStdin}; use anyhow::Result; use serde::{de::DeserializeOwned, Serialize}; use std::path::Path; @@ -93,7 +93,6 @@ impl ZiskIO for ZiskIOVariant { #[derive(Clone)] pub struct ZiskStdin { io: Arc, - hints_stream: Option>, } impl ZiskIO for ZiskStdin { @@ -135,27 +134,21 @@ impl Default for ZiskStdin { impl ZiskStdin { /// Create new memory-based stdin pub fn new() -> Self { - Self { - io: Arc::new(ZiskIOVariant::Memory(ZiskMemoryStdin::new(Vec::new()))), - hints_stream: None, - } + Self { io: Arc::new(ZiskIOVariant::Memory(ZiskMemoryStdin::new(Vec::new()))) } } /// Create a null stdin (no input) pub fn null() -> Self { - Self { io: Arc::new(ZiskIOVariant::Null(ZiskNullStdin)), hints_stream: None } + Self { io: Arc::new(ZiskIOVariant::Null(ZiskNullStdin)) } } /// Create a file-based stdin pub fn from_file>(path: P) -> Result { - Ok(Self { - io: Arc::new(ZiskIOVariant::File(ZiskFileStdin::new(path)?)), - hints_stream: None, - }) + Ok(Self { io: Arc::new(ZiskIOVariant::File(ZiskFileStdin::new(path)?)) }) } pub fn from_vec(data: Vec) -> Self { - Self { io: Arc::new(ZiskIOVariant::Memory(ZiskMemoryStdin::new(data))), hints_stream: None } + Self { io: Arc::new(ZiskIOVariant::Memory(ZiskMemoryStdin::new(data))) } } /// Create a ZiskStdin from a URI string @@ -184,18 +177,4 @@ impl ZiskStdin { ZiskStdin::from_file(uri.as_str()) } } - - pub fn set_hints_stream(&mut self, stream: StreamSource) { - self.hints_stream = Some(Arc::new(stream)); - } - - pub fn take_hints_stream(&mut self) -> Option { - self.hints_stream.take().map(|arc| { - Arc::try_unwrap(arc).unwrap_or_else(|_| panic!("StreamSource has multiple references")) - }) - } - - pub fn has_hints_stream(&self) -> bool { - self.hints_stream.is_some() - } } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index cc45c87d3..11ac9132c 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -533,7 +533,7 @@ impl Worker { ) -> Result> { let phase = proofman::ProvePhase::Contributions; - let mut stdin = match input_source { + let stdin = match input_source { InputSourceDto::InputPath(inputs_uri) => ZiskStdin::from_file(inputs_uri)?, InputSourceDto::InputData(input_data) => ZiskStdin::from_vec(input_data), InputSourceDto::InputNull => ZiskStdin::null(), @@ -542,7 +542,7 @@ impl Worker { match hints_source { HintsSourceDto::HintsPath(hints_uri) => { let hints_stream = StreamSource::from_uri(hints_uri)?; - stdin.set_hints_stream(hints_stream); + pk.register_hints_stream(hints_stream)?; } HintsSourceDto::HintsStream(_hints_uri) => { // For HintsStream, the worker will receive hint data via StreamData gRPC messages diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index e02c00853..dcef03254 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -329,7 +329,7 @@ pub unsafe fn unmap(ptr: *mut c_void, size: usize) { } } -pub fn write_input(stdin: &mut ZiskStdin, shmem_input_writer: &SharedMemoryWriter) { +pub fn write_input(stdin: &ZiskStdin, shmem_input_writer: &SharedMemoryWriter) { const HEADER_SIZE: usize = size_of::(); let inputs = stdin.read_bytes(); diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index b1e5c8b1f..a69e26241 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -6,6 +6,7 @@ use asm_runner::HintsShmem; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] use asm_runner::{MOOutputShmem, MTOutputShmem, RHOutputShmem, SharedMemoryWriter}; use precompiles_hints::HintsProcessor; +use std::sync::atomic::{AtomicBool, Ordering}; use zisk_common::io::{StreamSource, ZiskStream}; /// Encapsulates assembly-related resources including shared memory and hints stream. @@ -32,6 +33,8 @@ pub struct AsmResources { /// Pipeline for handling precompile hints. pub hints_stream: Option>>, + + pub hints_stream_initialized: Arc, } impl std::fmt::Debug for AsmResources { @@ -91,6 +94,7 @@ impl AsmResources { Self { hints_stream, + hints_stream_initialized: Arc::new(AtomicBool::new(false)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] asm_shmem_mt: Arc::new(Mutex::new(asm_shmem_mt)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] @@ -115,15 +119,22 @@ impl AsmResources { pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { if let Some(hints_stream) = &self.hints_stream { - hints_stream.lock().unwrap().set_hints_stream_src(stream) + hints_stream.lock().unwrap().set_hints_stream_src(stream)?; } else { - Err(anyhow::anyhow!("Hints stream not initialized")) + return Err(anyhow::anyhow!("Hints stream not initialized")); } + self.hints_stream_initialized.store(true, Ordering::SeqCst); + Ok(()) + } + + pub fn is_hints_stream_initialized(&self) -> bool { + self.hints_stream_initialized.load(Ordering::SeqCst) } pub fn reset(&self) { if let Some(hints_stream) = &self.hints_stream { hints_stream.lock().unwrap().reset(); + self.hints_stream_initialized.store(false, Ordering::SeqCst); } } } diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 779aafe24..63073eedf 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -112,13 +112,8 @@ impl EmulatorAsm { let asm_resources_guard = self.asm_resources.lock().unwrap(); let asm_resources = asm_resources_guard.as_ref().expect("AsmResources not initialized"); - let has_hints_stream = stdin.lock().unwrap().has_hints_stream(); + let has_hints_stream = asm_resources.is_hints_stream_initialized(); if use_hints && has_hints_stream { - let hints_stream = - stdin.lock().unwrap().take_hints_stream().expect("Hints stream not set"); - asm_resources - .set_hints_stream_src(hints_stream) - .expect("Failed to set hints stream source"); asm_resources.start_stream().expect("Failed to start hints stream"); } @@ -144,7 +139,7 @@ impl EmulatorAsm { }); write_input( - &mut stdin.lock().unwrap(), + &stdin.lock().unwrap(), asm_resources.shmem_input_writer.lock().unwrap().as_ref().unwrap(), ); diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index 2766f0262..e2bb22081 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -62,7 +62,7 @@ impl EmulatorRust { Option>, u64, ) { - let min_traces = self.run_emulator(zisk_rom, Self::NUM_THREADS, &mut stdin.lock().unwrap()); + let min_traces = self.run_emulator(zisk_rom, Self::NUM_THREADS, &stdin.lock().unwrap()); // Store execute steps let steps = min_traces.iter().map(|trace| trace.steps).sum::(); @@ -78,7 +78,7 @@ impl EmulatorRust { &self, zisk_rom: &ZiskRom, num_threads: usize, - stdin: &mut ZiskStdin, + stdin: &ZiskStdin, ) -> Vec { // Call emulate with these options let input_data = stdin.read_bytes(); diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 217797bb1..d5bcbefd6 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -24,6 +24,7 @@ use std::{ time::Duration, }; use tracing::info; +use zisk_common::io::StreamSource; use zisk_common::ElfBinaryLike; use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, StatsCostPerType, ZiskExecutionResult}; use zisk_core::ZiskRom; @@ -126,6 +127,27 @@ pub struct ZiskProgramPK { pub use_hints: bool, } +impl ZiskProgramPK { + pub fn register_hints_stream(&self, stream: StreamSource) -> Result<()> { + if self.use_hints { + if let Some(asm_resources) = &self.asm_resources { + asm_resources + .set_hints_stream_src(stream) + .expect("Failed to set hints stream source"); + } else { + return Err(anyhow::anyhow!( + "ASM resources not initialized, cannot register hints stream" + )); + } + } else { + return Err(anyhow::anyhow!( + "Hints not enabled for this program, cannot register hints stream" + )); + } + Ok(()) + } +} + impl Drop for ZiskProgramPK { fn drop(&mut self) { // Shut down ASM microservices From a3ded4f29da615dcd571ca4a213829665ff86625 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:58:40 +0000 Subject: [PATCH 618/782] added an assert to check whether control shmem are reset or not --- emulator-asm/asm-runner/src/hints_shmem.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index a114f698d..5a78639d0 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -286,6 +286,11 @@ impl StreamSink for HintsShmem { for res in separate.iter_mut() { while res.sem_available.try_wait().is_ok() {} while res.sem_read.try_wait().is_ok() {} + + assert!( + res.control_reader.read_u64_at(0) == 0, + "Control reader position should be reset to 0" + ); } } From 8e1ca15f6a85821277c4d402d5817b576dea2119 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:34:36 +0000 Subject: [PATCH 619/782] remove GRPC metrics --- distributed/crates/worker/src/worker_node.rs | 15 -------- emulator-asm/asm-runner/src/asm_mt_runner.rs | 10 ------ emulator-asm/asm-runner/src/grpc_metrics.rs | 38 -------------------- emulator-asm/asm-runner/src/lib.rs | 3 -- 4 files changed, 66 deletions(-) delete mode 100644 emulator-asm/asm-runner/src/grpc_metrics.rs diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 6fe29cac6..4b73956f4 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -1,6 +1,5 @@ use crate::{worker::ComputationResult, ProverConfig, Worker}; use anyhow::{anyhow, Result}; -use asm_runner::GRPC_METRICS; use proofman::{AggProofs, ContributionsInfo, ExecutionInfo}; use std::path::Path; use std::{path::PathBuf, time::Duration}; @@ -484,21 +483,7 @@ impl WorkerNodeGrpc { } } coordinator_message::Payload::StreamData(stream_data) => { - GRPC_METRICS.active_streams.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - GRPC_METRICS.callbacks_invoked.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let before = std::time::Instant::now(); - self.handle_stream_data(stream_data).await?; - - let elapsed = before.elapsed(); - if elapsed > Duration::from_millis(100) { - println!("WARNING: Callback took {:?} - possible starvation", elapsed); - GRPC_METRICS - .thread_starvation_count - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); - } - - GRPC_METRICS.active_streams.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); } coordinator_message::Payload::JobCancelled(cancelled) => { info!("Job {} cancelled: {}", cancelled.job_id, cancelled.reason); diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index e1018eb29..d7c8b7c43 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -162,16 +162,6 @@ impl AsmRunnerMT { continue } Err(e) => { - // Print gRPC health snapshot to correlate the stall with stream activity. - let thread = std::thread::current(); - let thread_name = thread.name().unwrap_or(""); - tracing::warn!( - "[MT_RUNNER] Semaphore timeout on thread {:?} (name={:?}, on_tokio={})", - thread.id(), - thread_name, - thread_name.contains("tokio"), - ); - crate::GRPC_METRICS.diagnose_grpc_health(); error!("Semaphore '{}' error: {:?}", sem_chunk_done_name, e); if chunk_id.0 == 0 { diff --git a/emulator-asm/asm-runner/src/grpc_metrics.rs b/emulator-asm/asm-runner/src/grpc_metrics.rs deleted file mode 100644 index 132f6c5bd..000000000 --- a/emulator-asm/asm-runner/src/grpc_metrics.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::sync::atomic::{AtomicU64, Ordering}; - -/// Global gRPC health counters. -/// -/// Incremented by the worker's stream-handling code and read by `AsmRunnerMT` -/// when a semaphore timeout fires so we can correlate a stalled ASM with gRPC -/// activity (or lack thereof) at the same moment. -pub struct GrpcMetrics { - /// Number of hint streams that have been started but not yet ended. - pub active_streams: AtomicU64, - /// Total `StreamData::Data` messages forwarded to the ordering actor. - pub callbacks_invoked: AtomicU64, - /// Number of times the Tokio event-loop was delayed by more than the - /// starvation threshold (detected by a periodic canary task in the worker). - pub thread_starvation_count: AtomicU64, -} - -impl GrpcMetrics { - const fn new() -> Self { - Self { - active_streams: AtomicU64::new(0), - callbacks_invoked: AtomicU64::new(0), - thread_starvation_count: AtomicU64::new(0), - } - } - - pub fn diagnose_grpc_health(&self) { - println!("=== GRPC METRICS ==="); - println!("Active streams: {}", self.active_streams.load(Ordering::Relaxed)); - println!("Callbacks invoked: {}", self.callbacks_invoked.load(Ordering::Relaxed)); - println!( - "Thread starvation events: {}", - self.thread_starvation_count.load(Ordering::Relaxed) - ); - } -} - -pub static GRPC_METRICS: GrpcMetrics = GrpcMetrics::new(); diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 2257b6b6c..9e743c996 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -1,8 +1,5 @@ extern crate libc; -mod grpc_metrics; -pub use grpc_metrics::{GrpcMetrics, GRPC_METRICS}; - mod asm_mo; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod asm_mo_runner; From 2867fa878243d538885a47abb086a6368cd98ab4 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:45:44 +0100 Subject: [PATCH 620/782] fix macOS --- executor/src/emu_asm_stub.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/executor/src/emu_asm_stub.rs b/executor/src/emu_asm_stub.rs index c7b4c1458..bb8fb52e3 100644 --- a/executor/src/emu_asm_stub.rs +++ b/executor/src/emu_asm_stub.rs @@ -7,11 +7,9 @@ use crate::{DeviceMetricsList, NestedDeviceMetricsList, StaticSMBundle}; use asm_runner::AsmRunnerMO; use crate::AsmResources; -use anyhow::Result; use fields::PrimeField64; use proofman_common::ProofCtx; use sm_rom::RomSM; -use zisk_common::io::StreamSource; use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; use zisk_core::ZiskRom; From fd30c1b1b9cd498e6d2b84759eff0a87196d280e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:45:15 +0000 Subject: [PATCH 621/782] Bug in secp256k1 --- .../entrypoint/src/zisklib/lib/bn254/curve.rs | 16 ++++++++++++- .../src/zisklib/lib/secp256k1/curve.rs | 24 +++++++++++++------ .../src/zisklib/lib/secp256k1/ecdsa.rs | 4 ++-- .../src/zisklib/lib/secp256r1/curve.rs | 24 +++++++++++++------ 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs index 428fd32d0..36166a0f9 100644 --- a/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/bn254/curve.rs @@ -12,7 +12,7 @@ use crate::{ use super::{ constants::{E_B, G1_IDENTITY, P}, - fp::{add_fp_bn254, inv_fp_bn254, mul_fp_bn254, square_fp_bn254}, + fp::{add_fp_bn254, inv_fp_bn254, mul_fp_bn254, neg_fp_bn254, square_fp_bn254}, fr::{reduce_fr_bn254, scalar_bytes_be_to_u64_le_bn254}, }; @@ -201,6 +201,20 @@ pub fn dbl_bn254(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) - [p1.x[0], p1.x[1], p1.x[2], p1.x[3], p1.y[0], p1.y[1], p1.y[2], p1.y[3]] } +/// Negation of a point +pub fn neg_bn254(p: &[u64; 8], #[cfg(feature = "hints")] hints: &mut Vec) -> [u64; 8] { + let x: [u64; 4] = p[0..4].try_into().unwrap(); + let y: [u64; 4] = p[4..8].try_into().unwrap(); + + // Compute the negation + let y_neg = neg_fp_bn254( + &y, + #[cfg(feature = "hints")] + hints, + ); + [x[0], x[1], x[2], x[3], y_neg[0], y_neg[1], y_neg[2], y_neg[3]] +} + /// Multiplies a non-zero point `p` on the BN254 curve by a scalar `k` on the BN254 scalar field pub fn scalar_mul_bn254( p: &[u64; 8], diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs index 3b9e2ce54..05ddae115 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/curve.rs @@ -475,13 +475,23 @@ pub fn secp256k1_triple_scalar_mul_with_g( hints, ); - let mut gpq = SyscallPoint256 { x: gp.x, y: gp.y }; - let gpq_is_infinity = secp256k1_add_non_infinity_points( - &mut gpq, - &q, - #[cfg(feature = "hints")] - hints, - ); + let (gpq, gpq_is_infinity) = if gp_is_infinity { + // G + P = 𝒪, so G + P + Q = Q + (SyscallPoint256 { x: q.x, y: q.y }, false) + } else if pq_is_infinity { + // P + Q = 𝒪, so G + P + Q = G + (G_POINT256, false) + } else { + // Normal case: add Q to (G + P) + let mut gpq_temp = SyscallPoint256 { x: gp.x, y: gp.y }; + let is_inf = secp256k1_add_non_infinity_points( + &mut gpq_temp, + &q, + #[cfg(feature = "hints")] + hints, + ); + (gpq_temp, is_inf) + }; if is_one(r) && is_one(s) && is_one(t) { // Return g + p + q diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs index fbbc3819d..861600916 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256k1/ecdsa.rs @@ -154,8 +154,8 @@ pub fn secp256k1_ecdsa_recover( // Hint the result // The following functions hints (x,y) satisfying - // [z]G + [r]R + [-s](x,y) == 𝒪 - // We can use it + // (x, y) == [s⁻¹·z (mod n)]G + [s⁻¹·r (mod n)]R iff [z]G + [r]R + [-s](x, y) == 𝒪 + // We can use it by flipping the signs of r and s and its order let neg_s = secp256k1_fn_neg( s, #[cfg(feature = "hints")] diff --git a/ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs b/ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs index 38ad31614..ceb5665cf 100644 --- a/ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs +++ b/ziskos/entrypoint/src/zisklib/lib/secp256r1/curve.rs @@ -125,13 +125,23 @@ pub fn secp256r1_triple_scalar_mul_with_g( hints, ); - let mut gpq = SyscallPoint256 { x: gp.x, y: gp.y }; - let gpq_is_infinity = secp256r1_add_non_infinity_points( - &mut gpq, - &q, - #[cfg(feature = "hints")] - hints, - ); + let (gpq, gpq_is_infinity) = if gp_is_infinity { + // G + P = 𝒪, so G + P + Q = Q + (SyscallPoint256 { x: q.x, y: q.y }, false) + } else if pq_is_infinity { + // P + Q = 𝒪, so G + P + Q = G + (G_POINT256, false) + } else { + // Normal case: add Q to (G + P) + let mut gpq_temp = SyscallPoint256 { x: gp.x, y: gp.y }; + let is_inf = secp256r1_add_non_infinity_points( + &mut gpq_temp, + &q, + #[cfg(feature = "hints")] + hints, + ); + (gpq_temp, is_inf) + }; if is_one(r) && is_one(s) && is_one(t) { // Return g + p + q From 618e140891d36adde1ce87f13f8ef9c63e56df12 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:02:13 +0000 Subject: [PATCH 622/782] update proofman in Cargo.toml to 0.16.0 --- Cargo.lock | 135 ++++++++++++++++++++++++----------------------------- Cargo.toml | 16 +++---- 2 files changed, 70 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e58e8962..0a9300663 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -603,7 +603,7 @@ dependencies = [ "colored", "dirs", "executor", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "futures", "indicatif", "mpi", @@ -1115,9 +1115,9 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "num-bigint", "num-traits", ] @@ -1414,7 +1414,7 @@ dependencies = [ "asm-runner", "crossbeam", "data-bus", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "itertools 0.14.0", "mem-common", "mem-planner-cpp", @@ -1468,18 +1468,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" -dependencies = [ - "cfg-if", - "num-bigint", - "paste", - "serde", -] - -[[package]] -name = "fields" -version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" dependencies = [ "cfg-if", "num-bigint", @@ -2174,9 +2163,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.88" +version = "0.3.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d" +checksum = "f4eacb0641a310445a4c513f2a5e23e19952e269c6a38887254d5f837a305506" dependencies = [ "once_cell", "wasm-bindgen", @@ -2284,9 +2273,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.23" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839" dependencies = [ "cc", "libc", @@ -2347,7 +2336,7 @@ name = "mem-common" version = "0.16.0" dependencies = [ "clap", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "num-bigint", "num-traits", "proofman-common", @@ -2783,10 +2772,10 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" dependencies = [ "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "num-bigint", "num-traits", "proofman-common", @@ -2905,7 +2894,7 @@ dependencies = [ "ark-secp256k1", "ark-secp256r1", "ark-std", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "lazy_static", "lib-c", "mem-common", @@ -2941,7 +2930,7 @@ dependencies = [ "ark-ff", "ark-secp256k1", "ark-std", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "lazy_static", "lib-c", "mem-common", @@ -2972,7 +2961,7 @@ dependencies = [ name = "precomp-big-int" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "generic-array", "lib-c", "mem-common", @@ -2993,7 +2982,7 @@ dependencies = [ name = "precomp-dma" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "generic-array", "lib-c", "mem-common", @@ -3016,7 +3005,7 @@ name = "precomp-keccakf" version = "0.16.0" dependencies = [ "circuit", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "path-clean", "pil-std-lib", "precompiles-common", @@ -3036,7 +3025,7 @@ dependencies = [ name = "precomp-poseidon2" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -3056,7 +3045,7 @@ dependencies = [ name = "precomp-sha256f" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -3075,7 +3064,7 @@ dependencies = [ name = "precompiles-common" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "mem-common", "sm-mem", "zisk-common", @@ -3145,7 +3134,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" dependencies = [ "bincode", "blake3", @@ -3156,7 +3145,7 @@ dependencies = [ "crossbeam-channel", "csv", "curves", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "libloading", "mpi", "num-bigint", @@ -3181,7 +3170,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" dependencies = [ "bincode", "borsh", @@ -3191,7 +3180,7 @@ dependencies = [ "crossbeam-queue", "csv", "env", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "lazy_static", "libloading", "mpi", @@ -3213,9 +3202,9 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "itoa", "proofman-common", "proofman-starks-lib-c", @@ -3226,7 +3215,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" dependencies = [ "proc-macro2", "quote", @@ -3236,7 +3225,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" dependencies = [ "crossbeam-channel", "tracing", @@ -3245,7 +3234,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" dependencies = [ "bincode", "bytemuck", @@ -3257,10 +3246,10 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" dependencies = [ "bytemuck", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "proofman-util", "rayon", "tracing", @@ -3619,7 +3608,7 @@ dependencies = [ "anyhow", "blake3", "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "proofman-common", "sm-rom", "tracing", @@ -4041,7 +4030,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "sm-arith" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "num-bigint", "pil-std-lib", "proofman-common", @@ -4061,7 +4050,7 @@ dependencies = [ name = "sm-binary" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "num-bigint", "pil-std-lib", "proofman-common", @@ -4081,7 +4070,7 @@ name = "sm-frequent-ops" version = "0.16.0" dependencies = [ "clap", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "num-bigint", "proofman-common", "proofman-util", @@ -4095,7 +4084,7 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "mem-common", "num-bigint", "pil-std-lib", @@ -4114,7 +4103,7 @@ dependencies = [ name = "sm-mem" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "mem-common", "num-bigint", "num-traits", @@ -4135,7 +4124,7 @@ name = "sm-rom" version = "0.16.0" dependencies = [ "asm-runner", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "itertools 0.14.0", "proofman-common", "proofman-macros", @@ -4311,9 +4300,9 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tempfile" -version = "3.25.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", "getrandom 0.4.1", @@ -5029,9 +5018,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.111" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac" +checksum = "05d7d0fce354c88b7982aec4400b3e7fcf723c32737cef571bd165f7613557ee" dependencies = [ "cfg-if", "once_cell", @@ -5042,9 +5031,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.61" +version = "0.4.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe88540d1c934c4ec8e6db0afa536876c5441289d7f9f9123d4f065ac1250a6b" +checksum = "ee85afca410ac4abba5b584b12e77ea225db6ee5471d0aebaae0861166f9378a" dependencies = [ "cfg-if", "futures-util", @@ -5056,9 +5045,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.111" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1" +checksum = "55839b71ba921e4f75b674cb16f843f4b1f3b26ddfcb3454de1cf65cc021ec0f" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5066,9 +5055,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.111" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af" +checksum = "caf2e969c2d60ff52e7e98b7392ff1588bffdd1ccd4769eba27222fd3d621571" dependencies = [ "bumpalo", "proc-macro2", @@ -5079,9 +5068,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.111" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41" +checksum = "0861f0dcdf46ea819407495634953cdcc8a8c7215ab799a7a7ce366be71c7b30" dependencies = [ "unicode-ident", ] @@ -5135,9 +5124,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.88" +version = "0.3.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6bb20ed2d9572df8584f6dc81d68a41a625cadc6f15999d649a70ce7e3597a" +checksum = "10053fbf9a374174094915bbce141e87a6bf32ecd9a002980db4b638405e8962" dependencies = [ "js-sys", "wasm-bindgen", @@ -5732,10 +5721,10 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition#3fa9da9315b4f1dedd0faa7bae7139b0ba8d7482" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" dependencies = [ "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "libloading", "proofman-common", "proofman-util", @@ -5928,7 +5917,7 @@ version = "0.16.0" dependencies = [ "anyhow", "bincode", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "libc", "mpi", "proofman", @@ -5951,7 +5940,7 @@ name = "zisk-core" version = "0.16.0" dependencies = [ "elf", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "lib-c", "lib-float", "paste", @@ -6047,7 +6036,7 @@ dependencies = [ "clap", "colored", "config", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "precompiles-hints", "proofman", "proofman-common", @@ -6073,7 +6062,7 @@ dependencies = [ name = "zisk-pil" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "proofman-common", "proofman-macros", "rayon", @@ -6090,7 +6079,7 @@ dependencies = [ "bincode", "colored", "executor", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "proofman", "proofman-common", "proofman-util", @@ -6130,7 +6119,7 @@ dependencies = [ "clap", "criterion 0.5.1", "data-bus", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "mem-common", "memmap2", "num-format", @@ -6158,7 +6147,7 @@ dependencies = [ "bytes", "cfg-if", "ctor", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", + "fields", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -6183,7 +6172,7 @@ dependencies = [ "anyhow", "bincode", "cfg-if", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fset-partition)", + "fields", "getrandom 0.2.17", "lazy_static", "lib-c", diff --git a/Cargo.toml b/Cargo.toml index b1607d95c..bb19c7b67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,14 +108,14 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/set-partition" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } # Proofman Local development # proofman = { path = "../pil2-proofman/proofman" } # proofman-common = { path = "../pil2-proofman/common" } From 07777359260d9827c6b0dbdd8a5bfe4fe872f805 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:05:58 +0000 Subject: [PATCH 623/782] fix typo --- emulator-asm/src/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 5f716de79..0262269f6 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -182,7 +182,7 @@ typedef enum { GenMethod gen_method = Fast; // Returns the acronym of the generation method, used for logging and file naming -const char * gen_method_achronym(GenMethod method) +const char * gen_method_acronym(GenMethod method) { switch (method) { @@ -678,7 +678,7 @@ int main(int argc, char *argv[]) if (redirect_output_to_file) { char redirect_output_file[256]; - snprintf(redirect_output_file, sizeof(redirect_output_file), "/tmp/%s_%s_output.txt", shm_prefix, gen_method_achronym(gen_method)); + snprintf(redirect_output_file, sizeof(redirect_output_file), "/tmp/%s_%s_output.txt", shm_prefix, gen_method_acronym(gen_method)); // Redirect stdout to file FILE * file_pointer = freopen(redirect_output_file, "w", stdout); From 34f1e39d981b683121ccf3f7cbb6f3461ce671bd Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:06:45 +0000 Subject: [PATCH 624/782] improve command banners --- cli/src/commands/clean.rs | 11 +++---- cli/src/commands/execute.rs | 11 ++++--- cli/src/commands/prove.rs | 11 ++++--- cli/src/commands/prove_snark.rs | 9 +++--- cli/src/commands/rom_setup.rs | 8 +++++ cli/src/commands/stats.rs | 11 ++++--- cli/src/commands/verify_constraints.rs | 11 ++++--- cli/src/ux.rs | 6 +++- sdk/src/builder.rs | 44 +++----------------------- 9 files changed, 55 insertions(+), 67 deletions(-) diff --git a/cli/src/commands/clean.rs b/cli/src/commands/clean.rs index d4eca382a..4530edaf3 100644 --- a/cli/src/commands/clean.rs +++ b/cli/src/commands/clean.rs @@ -7,7 +7,10 @@ use anyhow::{Context, Result}; use proofman_common::VerboseMode; use zisk_sdk::setup_logger; -use crate::{commands::get_home_zisk_path, ux::print_banner}; +use crate::{ + commands::get_home_zisk_path, + ux::{print_banner, print_banner_command}, +}; /// Deletes the default zisk setup folder #[derive(Parser, Debug)] @@ -19,11 +22,7 @@ impl ZiskClean { setup_logger(VerboseMode::Info); print_banner(); - tracing::info!( - "{}", - format!("{} Clean", format!("{: >12}", "Command").bright_green().bold()) - ); - tracing::info!(""); + print_banner_command("Clean"); let home_zisk_path = get_home_zisk_path(); let cache_zisk_path = home_zisk_path.join("cache"); diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index c47557818..bba869268 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -7,7 +7,7 @@ use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::ElfBinaryFromFile; use zisk_sdk::{ProverClient, ZiskExecuteResult}; -use crate::ux::{print_banner, print_banner_field}; +use crate::ux::{print_banner, print_banner_command, print_banner_field}; use zisk_common::io::{StreamSource, ZiskStdin}; #[derive(Parser)] @@ -84,9 +84,12 @@ impl ZiskExecute { print_banner(); - if let Some(inputs) = &self.inputs { - print_banner_field("Input", inputs); - } + print_banner_command("Execute"); + + print_banner_field("Elf", self.elf.display()); + + let inputs_str = self.inputs.clone().unwrap_or_else(|| "None".dimmed().to_string()); + print_banner_field("Input", inputs_str); if let Some(hints) = &self.hints { print_banner_field("Prec. Hints", hints); diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 835cc448f..12ba1da0d 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -1,4 +1,4 @@ -use crate::ux::{print_banner, print_banner_field}; +use crate::ux::{print_banner, print_banner_command, print_banner_field}; use anyhow::Result; use colored::Colorize; @@ -125,6 +125,10 @@ impl ZiskProve { print_banner(); + print_banner_command("Prove"); + + print_banner_field("Elf", self.elf.display()); + let mut gpu_params = None; if self.preallocate || self.max_streams.is_some() @@ -138,9 +142,8 @@ impl ZiskProve { gpu_params = Some(gpu_params_new); } - if let Some(inputs) = &self.inputs { - print_banner_field("Input", inputs); - } + let inputs_str = self.inputs.clone().unwrap_or_else(|| "None".dimmed().to_string()); + print_banner_field("Input", inputs_str); if let Some(hints) = &self.hints { print_banner_field("Prec. Hints", hints); diff --git a/cli/src/commands/prove_snark.rs b/cli/src/commands/prove_snark.rs index 15862f9cb..ee66c462a 100644 --- a/cli/src/commands/prove_snark.rs +++ b/cli/src/commands/prove_snark.rs @@ -5,7 +5,7 @@ use colored::Colorize; use fields::Goldilocks; use std::path::PathBuf; -use crate::ux::print_banner; +use crate::ux::{print_banner, print_banner_command, print_banner_field}; use proofman::SnarkWrapper; use zisk_sdk::ZiskProofWithPublicValues; @@ -37,11 +37,12 @@ pub struct ZiskProveSnark { impl ZiskProveSnark { pub fn run(&self) -> Result<()> { - println!("{} ProveSnark", format!("{: >12}", "Command").bright_green().bold()); - println!(); - print_banner(); + print_banner_command("Prove SNARK"); + + print_banner_field("Elf", self.elf.display()); + let zisk_proof = ZiskProofWithPublicValues::load(&self.proof).map_err(|e| { anyhow::anyhow!( "Failed to load ZiskProofWithPublicValues from file {}: {}", diff --git a/cli/src/commands/rom_setup.rs b/cli/src/commands/rom_setup.rs index 43c0319b2..7a35631d5 100644 --- a/cli/src/commands/rom_setup.rs +++ b/cli/src/commands/rom_setup.rs @@ -44,9 +44,17 @@ impl ZiskRomSetup { print_banner(); print_banner_field("Command", "Rom Setup"); + print_banner_field("Elf", self.elf.display()); + if self.hints { + print_banner_field("Hints", "Enabled".yellow()); + } let proving_key = get_proving_key(self.proving_key.as_ref()); + print_banner_field("Proving Key", proving_key.display()); + + println!(); + let mpi_ctx = Arc::new(MpiCtx::new()); let mut pctx = ProofCtx::create_ctx(proving_key, false, self.verbose.into(), mpi_ctx)?; diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index 51bced3c1..c47eb728b 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -11,7 +11,7 @@ use zisk_common::{ExecutorStatsHandle, Stats}; use zisk_pil::*; use zisk_sdk::ProverClient; -use crate::ux::{print_banner, print_banner_field}; +use crate::ux::{print_banner, print_banner_command, print_banner_field}; #[derive(Parser)] #[command(author, about, long_about = None, version = ZISK_VERSION_MESSAGE)] @@ -102,9 +102,12 @@ impl ZiskStats { print_banner(); - if let Some(inputs) = &self.inputs { - print_banner_field("Input", inputs); - } + print_banner_command("Stats"); + + print_banner_field("Elf", self.elf.display()); + + let inputs_str = self.inputs.clone().unwrap_or_else(|| "None".dimmed().to_string()); + print_banner_field("Input", inputs_str); if let Some(hints) = &self.hints { print_banner_field("Prec. Hints", hints); diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index edee9c86e..8337a2a29 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -1,4 +1,4 @@ -use crate::ux::{print_banner, print_banner_field}; +use crate::ux::{print_banner, print_banner_command, print_banner_field}; use anyhow::Result; use clap::Parser; @@ -86,9 +86,12 @@ impl ZiskVerifyConstraints { print_banner(); - if let Some(inputs) = &self.inputs { - print_banner_field("Input", inputs); - } + print_banner_command("Verify Constraints"); + + print_banner_field("Elf", self.elf.display()); + + let inputs_str = self.inputs.clone().unwrap_or_else(|| "None".dimmed().to_string()); + print_banner_field("Input", inputs_str); if let Some(hints) = &self.hints { print_banner_field("Prec. Hints", hints); diff --git a/cli/src/ux.rs b/cli/src/ux.rs index 0e5af437e..e8823082a 100644 --- a/cli/src/ux.rs +++ b/cli/src/ux.rs @@ -50,6 +50,10 @@ pub fn print_banner() { // ); } -pub fn print_banner_field(label: &str, value: &str) { +pub fn print_banner_command(command: impl std::fmt::Display) { + print_banner_field("Command", command); +} + +pub fn print_banner_field(label: &str, value: impl std::fmt::Display) { println!("{} {}", format!("{: >12}", label).bright_green().bold(), value); } diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index bf6f25edb..50fc6a848 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -297,12 +297,7 @@ impl ProverClientBuilder { let proving_key_snark = get_proving_key_snark(self.proving_key_snark.as_ref()); if self.print_command_info { - Self::print_emu_command_info( - self.witness, - self.verify_constraints, - &proving_key, - &proving_key_snark, - ); + Self::print_emu_command_info(&proving_key, &proving_key_snark); } let emu = if self.verifier { @@ -324,20 +319,7 @@ impl ProverClientBuilder { Ok(ZiskProver::::new(emu)) } - fn print_emu_command_info( - witness: bool, - verify_constraints: bool, - proving_key: &Path, - proving_key_snark: &Path, - ) { - if witness { - println!("{: >12} StatsConstraints", "Command".bright_green().bold()); - } else if verify_constraints { - println!("{: >12} VerifyConstraints", "Command".bright_green().bold()); - } else { - println!("{: >12} Prove", "Command".bright_green().bold()); - } - + fn print_emu_command_info(proving_key: &Path, proving_key_snark: &Path) { println!( "{: >12} {}", "Emulator".bright_green().bold(), @@ -415,12 +397,7 @@ impl ProverClientBuilder { let proving_key_snark = get_proving_key_snark(self.proving_key_snark.as_ref()); if self.print_command_info { - Self::print_asm_command_info( - self.witness, - self.verify_constraints, - &proving_key, - &proving_key_snark, - ); + Self::print_asm_command_info(&proving_key, &proving_key_snark); } let asm = if self.verifier { @@ -445,20 +422,7 @@ impl ProverClientBuilder { Ok(ZiskProver::::new(asm)) } - fn print_asm_command_info( - witness: bool, - verify_constraints: bool, - proving_key: &Path, - proving_key_snark: &Path, - ) { - if witness { - println!("{: >12} StatsConstraints", "Command".bright_green().bold()); - } else if verify_constraints { - println!("{: >12} VerifyConstraints", "Command".bright_green().bold()); - } else { - println!("{: >12} Prove", "Command".bright_green().bold()); - } - + fn print_asm_command_info(proving_key: &Path, proving_key_snark: &Path) { println!("{: >12} {}", "Proving Key".bright_green().bold(), proving_key.display()); println!("{: >12} {}", "SNARK Key".bright_green().bold(), proving_key_snark.display()); From b4a7f3826a2b3027e25ec677ab584c33fa64623a Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:08:12 +0000 Subject: [PATCH 625/782] imp: modify exposed level of a var --- executor/src/asm_resources.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index a69e26241..ea8a9b45d 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -34,7 +34,7 @@ pub struct AsmResources { /// Pipeline for handling precompile hints. pub hints_stream: Option>>, - pub hints_stream_initialized: Arc, + hints_stream_initialized: Arc, } impl std::fmt::Debug for AsmResources { From 942755bb23ea431c76df526793e3c9249ecc8c14 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:10:15 +0000 Subject: [PATCH 626/782] modify doc comment and rename var --- state-machines/rom/src/rom_instance.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/state-machines/rom/src/rom_instance.rs b/state-machines/rom/src/rom_instance.rs index 92ac66994..6d2d1aba4 100644 --- a/state-machines/rom/src/rom_instance.rs +++ b/state-machines/rom/src/rom_instance.rs @@ -37,8 +37,8 @@ pub struct RomInstance { /// Execution statistics counter for ROM instructions. counter_stats: Mutex>, - /// Optional handle for the ROM assembly runner thread. - handle_rh: Mutex>, + /// Rom Histogram data from the assembly runner thread. + rh_data: Mutex>, /// Cached result from the assembly runner thread. asm_result: Mutex>, @@ -58,7 +58,7 @@ impl RomInstance { ictx: InstanceCtx, bios_inst_count: Arc>, prog_inst_count: Arc>, - handle_rh: Option, + rh_data: Option, ) -> Self { Self { zisk_rom, @@ -66,7 +66,7 @@ impl RomInstance { bios_inst_count: Mutex::new(bios_inst_count), prog_inst_count: Mutex::new(prog_inst_count), counter_stats: Mutex::new(None), - handle_rh: Mutex::new(handle_rh), + rh_data: Mutex::new(rh_data), asm_result: Mutex::new(None), } } @@ -76,7 +76,7 @@ impl RomInstance { } pub fn is_asm_execution(&self) -> bool { - self.handle_rh.lock().unwrap().is_some() || self.asm_result.lock().unwrap().is_some() + self.rh_data.lock().unwrap().is_some() || self.asm_result.lock().unwrap().is_some() } pub fn build_rom_collector(&self, _chunk_id: ChunkId) -> Option { @@ -118,7 +118,7 @@ impl Instance for RomInstance { // Check if we already have the result cached if self.asm_result.lock().unwrap().is_none() { // Join the thread and cache the result - let rh_data = self.handle_rh.lock().unwrap().take().unwrap(); + let rh_data = self.rh_data.lock().unwrap().take().unwrap(); *self.asm_result.lock().unwrap() = Some(rh_data); } From 0047819c9a4e41e181c861bcee3efcb04f37e198 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:11:33 +0000 Subject: [PATCH 627/782] fix: doc comment --- state-machines/rom/src/rom_instance.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state-machines/rom/src/rom_instance.rs b/state-machines/rom/src/rom_instance.rs index 6d2d1aba4..3f8bacfd9 100644 --- a/state-machines/rom/src/rom_instance.rs +++ b/state-machines/rom/src/rom_instance.rs @@ -117,7 +117,7 @@ impl Instance for RomInstance { if self.is_asm_execution() { // Check if we already have the result cached if self.asm_result.lock().unwrap().is_none() { - // Join the thread and cache the result + // Retrieve the data from the assembly runner let rh_data = self.rh_data.lock().unwrap().take().unwrap(); *self.asm_result.lock().unwrap() = Some(rh_data); } From c01ecb473a881ffbf2058c14a2ba932827d852b4 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:13:37 +0000 Subject: [PATCH 628/782] imp: return result instead of expect --- sdk/src/prover/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index d5bcbefd6..e67e6b626 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -133,7 +133,7 @@ impl ZiskProgramPK { if let Some(asm_resources) = &self.asm_resources { asm_resources .set_hints_stream_src(stream) - .expect("Failed to set hints stream source"); + .map_err(|e| anyhow::anyhow!("Failed to set hints stream source: {}", e))?; } else { return Err(anyhow::anyhow!( "ASM resources not initialized, cannot register hints stream" From 4d9db136ac37c713e8fa49d33248c82509d04bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Tue, 24 Feb 2026 10:31:48 +0100 Subject: [PATCH 629/782] Fallback to installation path when cargo not exists (#812) * Fallback to installation path when cargo not exists * Adding simplifications * Fix --- rom-setup/src/asm_setup.rs | 111 +++++++++++++++++++++++++------------ rom-setup/src/utils.rs | 2 +- 2 files changed, 76 insertions(+), 37 deletions(-) diff --git a/rom-setup/src/asm_setup.rs b/rom-setup/src/asm_setup.rs index 74c699e8d..ce4901fea 100644 --- a/rom-setup/src/asm_setup.rs +++ b/rom-setup/src/asm_setup.rs @@ -28,33 +28,57 @@ fn find_workspace_root(start: &Path) -> Option { None } -pub fn resolve_emulator_asm(installed_path: PathBuf, verbose: bool) -> Result { +pub fn resolve_emulator_asm() -> Result { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let workspace_root = if manifest_dir.exists() { find_workspace_root(&manifest_dir) } else { None }; - let emulator_asm_path = if let Some(ref root) = workspace_root { - let candidate = root.join("emulator-asm"); - if candidate.exists() { - if verbose { - println!("Using emulator-asm from workspace: {}", candidate.display()); - } - candidate + let cargo_available = Command::new("cargo") + .arg("--version") + .status() + .map(|status| status.success()) + .unwrap_or(false); + + // Check if we can build from workspace (need both cargo and workspace with ziskclib) + let can_build_from_workspace = cargo_available + && if let Some(ref root) = workspace_root { + let candidate = root.join("emulator-asm"); + let ziskclib_path = root.join("ziskclib"); + candidate.exists() && ziskclib_path.exists() } else { - if verbose { - println!("Workspace found but emulator-asm not present, using installed path"); - } - installed_path - } + false + }; + + let installed_path = crate::get_default_zisk_path(); + let installed_asm_path = installed_path.join("zisk/emulator-asm"); + + let emulator_asm_path = if can_build_from_workspace { + let candidate = workspace_root.unwrap().join("emulator-asm"); + tracing::debug!("Using emulator-asm from workspace: {}", candidate.display()); + candidate } else { - if verbose { - println!("No workspace found, using installed path: {}", installed_path.display()); + if !cargo_available { + tracing::debug!( + "Cargo not available, using installed path: {}", + installed_asm_path.display() + ); + } else if workspace_root.is_none() { + tracing::debug!( + "No workspace found, using installed path: {}", + installed_asm_path.display() + ); + } else { + tracing::debug!( + "Workspace missing ziskclib source, using installed path: {}", + installed_asm_path.display() + ); } - installed_path + + installed_asm_path.clone() }; - println!("Looking for emulator-asm at: {}", emulator_asm_path.display()); + tracing::info!("Looking for emulator-asm at: {}", emulator_asm_path.display()); if !emulator_asm_path.exists() { anyhow::bail!("emulator-asm directory not found at: {}", emulator_asm_path.display()); @@ -63,13 +87,21 @@ pub fn resolve_emulator_asm(installed_path: PathBuf, verbose: bool) -> Result Result String { /// Gets the default zisk folder location in the home installation directory. pub fn get_default_zisk_path() -> PathBuf { - let zisk_path = format!("{}/.zisk/zisk", get_home_dir()); + let zisk_path = format!("{}/.zisk", get_home_dir()); PathBuf::from(zisk_path) } From ad94309a2e2d467c3226351404f50b259e4623a4 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Tue, 24 Feb 2026 11:13:48 +0100 Subject: [PATCH 630/782] fix tests and dma bugs --- .gitignore | 4 +- core/src/direct_calls.rs | 12 +- core/src/riscv2zisk_context.rs | 22 +- core/src/zisk_ops.rs | 2 +- core/src/zisk_rom_2_asm.rs | 184 +++- emulator-asm/Makefile | 14 +- emulator-asm/src/dma/Makefile | 74 -- emulator-asm/src/dma/check_dynamic_mtrace.asm | 195 ++++ ...nput_mops.asm => direct_inputcpy_mops.asm} | 46 +- ..._mtrace.asm => direct_inputcpy_mtrace.asm} | 174 +--- emulator-asm/src/dma/direct_memcmp_mops.asm | 280 +++++ emulator-asm/src/dma/direct_memcmp_mtrace.asm | 238 +++++ emulator-asm/src/dma/direct_memcpy_mops.asm | 507 +++++---- emulator-asm/src/dma/direct_memcpy_mtrace.asm | 216 +--- emulator-asm/src/dma/direct_memset_mops.asm | 256 +++++ emulator-asm/src/dma/direct_memset_mtrace.asm | 268 +++++ emulator-asm/src/dma/direct_xmemset_mops.asm | 255 ----- .../src/dma/direct_xmemset_mtrace.asm | 279 ----- emulator-asm/src/dma/dma_constants.inc | 84 +- emulator-asm/src/dma/fast_dma_encode.asm | 131 +-- .../src/dma/fast_dma_encode_macro.inc | 114 ++ .../src/dma/fast_dma_encode_table.asm | 489 +++++++-- emulator-asm/src/dma/fast_inputcpy.asm | 162 +++ emulator-asm/src/dma/fast_memcmp.asm | 153 +++ emulator-asm/src/dma/fast_memset.asm | 6 +- emulator-asm/src/dma/test/Makefile | 61 ++ emulator-asm/src/dma/test/test_dma.cpp | 32 + emulator-asm/src/dma/test/test_dma_encode.cpp | 209 ++++ emulator-asm/src/dma/test/test_dma_encode.hpp | 69 ++ .../src/dma/test/test_dma_inputcpy_mops.cpp | 138 +++ .../src/dma/test/test_dma_inputcpy_mops.hpp | 6 + .../src/dma/test/test_dma_inputcpy_mtrace.cpp | 135 +++ .../src/dma/test/test_dma_inputcpy_mtrace.hpp | 6 + emulator-asm/src/dma/test/test_dma_mem.cpp | 29 + emulator-asm/src/dma/test/test_dma_mem.hpp | 32 + .../src/dma/test/test_dma_mem_mops.cpp | 151 +++ .../src/dma/test/test_dma_mem_mops.hpp | 28 + .../src/dma/test/test_dma_mem_mtrace.cpp | 38 + .../src/dma/test/test_dma_mem_mtrace.hpp | 20 + .../src/dma/test/test_dma_memcmp_mops.cpp | 151 +++ .../src/dma/test/test_dma_memcmp_mops.hpp | 6 + .../src/dma/test/test_dma_memcmp_mtrace.cpp | 144 +++ .../src/dma/test/test_dma_memcmp_mtrace.hpp | 6 + .../src/dma/test/test_dma_memcpy_mops.cpp | 159 +++ .../src/dma/test/test_dma_memcpy_mops.hpp | 6 + .../src/dma/test/test_dma_memcpy_mtrace.cpp | 148 +++ .../src/dma/test/test_dma_memcpy_mtrace.hpp | 6 + .../src/dma/test/test_dma_memset_mops.cpp | 126 +++ .../src/dma/test/test_dma_memset_mops.hpp | 6 + .../src/dma/test/test_dma_memset_mtrace.cpp | 124 +++ .../src/dma/test/test_dma_memset_mtrace.hpp | 6 + emulator-asm/src/dma/test/test_dma_tools.cpp | 95 ++ emulator-asm/src/dma/test/test_dma_tools.hpp | 15 + emulator-asm/src/dma/test/test_mock.cpp | 16 + emulator-asm/src/dma/test/test_mock.hpp | 26 + emulator-asm/src/dma/test_dma.cpp | 985 ------------------ emulator-asm/src/dma/test_dma_api.asm | 75 ++ emulator-asm/src/dma/test_dma_inputcpy | Bin 92392 -> 0 bytes emulator-asm/src/dma/test_dma_inputcpy_mops | Bin 93368 -> 0 bytes .../src/dma/test_dma_inputcpy_mops.cpp | 334 ------ emulator-asm/src/dma/test_dma_memset_mops | Bin 84816 -> 0 bytes emulator-asm/src/dma/test_dma_memset_mops.cpp | 329 ------ emulator-asm/src/dma/test_dma_memset_mtrace | Bin 110824 -> 0 bytes .../src/dma/test_dma_memset_mtrace.cpp | 314 ------ emulator-asm/src/dma/test_fast_memcpy | Bin 75800 -> 0 bytes emulator-asm/src/dma/test_fast_memcpy.cpp | 193 ---- pil/src/pil_helpers/traces.rs | 10 +- precompiles/dma/src/dma/dma_collector.rs | 2 +- .../dma_64_aligned_collector.rs | 2 +- .../dma_64_aligned/dma_64_aligned_input.rs | 16 +- .../dma/src/dma_collector_routing_log.rs | 16 +- precompiles/dma/src/dma_instances_builder.rs | 49 +- .../dma/src/dma_pre_post/dma_pre_post.rs | 2 +- .../dma_pre_post/dma_pre_post_collector.rs | 2 +- .../src/dma_pre_post/dma_pre_post_inputcpy.rs | 32 - .../src/dma_pre_post/dma_pre_post_instance.rs | 3 +- .../dma/src/dma_unaligned/dma_unaligned.rs | 3 +- .../dma_unaligned/dma_unaligned_collector.rs | 6 +- .../src/dma_unaligned/dma_unaligned_input.rs | 4 +- precompiles/helpers/src/dma.rs | 74 +- state-machines/main/src/main_sm.rs | 1 - ziskos/entrypoint/src/lib.rs | 2 +- 82 files changed, 4878 insertions(+), 3735 deletions(-) delete mode 100644 emulator-asm/src/dma/Makefile create mode 100644 emulator-asm/src/dma/check_dynamic_mtrace.asm rename emulator-asm/src/dma/{direct_xinput_mops.asm => direct_inputcpy_mops.asm} (86%) rename emulator-asm/src/dma/{direct_inpucpy_mtrace.asm => direct_inputcpy_mtrace.asm} (50%) create mode 100644 emulator-asm/src/dma/direct_memcmp_mops.asm create mode 100644 emulator-asm/src/dma/direct_memcmp_mtrace.asm create mode 100644 emulator-asm/src/dma/direct_memset_mops.asm create mode 100644 emulator-asm/src/dma/direct_memset_mtrace.asm delete mode 100644 emulator-asm/src/dma/direct_xmemset_mops.asm delete mode 100644 emulator-asm/src/dma/direct_xmemset_mtrace.asm create mode 100644 emulator-asm/src/dma/fast_dma_encode_macro.inc create mode 100644 emulator-asm/src/dma/fast_inputcpy.asm create mode 100644 emulator-asm/src/dma/fast_memcmp.asm create mode 100644 emulator-asm/src/dma/test/Makefile create mode 100644 emulator-asm/src/dma/test/test_dma.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_encode.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_encode.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_inputcpy_mops.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_inputcpy_mops.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_inputcpy_mtrace.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_inputcpy_mtrace.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_mem.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_mem.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_mem_mops.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_mem_mops.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_mem_mtrace.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_mem_mtrace.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_memcmp_mops.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_memcmp_mops.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_memcmp_mtrace.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_memcmp_mtrace.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_memcpy_mops.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_memcpy_mops.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_memcpy_mtrace.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_memcpy_mtrace.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_memset_mops.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_memset_mops.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_memset_mtrace.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_memset_mtrace.hpp create mode 100644 emulator-asm/src/dma/test/test_dma_tools.cpp create mode 100644 emulator-asm/src/dma/test/test_dma_tools.hpp create mode 100644 emulator-asm/src/dma/test/test_mock.cpp create mode 100644 emulator-asm/src/dma/test/test_mock.hpp delete mode 100644 emulator-asm/src/dma/test_dma.cpp create mode 100644 emulator-asm/src/dma/test_dma_api.asm delete mode 100755 emulator-asm/src/dma/test_dma_inputcpy delete mode 100755 emulator-asm/src/dma/test_dma_inputcpy_mops delete mode 100644 emulator-asm/src/dma/test_dma_inputcpy_mops.cpp delete mode 100755 emulator-asm/src/dma/test_dma_memset_mops delete mode 100644 emulator-asm/src/dma/test_dma_memset_mops.cpp delete mode 100755 emulator-asm/src/dma/test_dma_memset_mtrace delete mode 100644 emulator-asm/src/dma/test_dma_memset_mtrace.cpp delete mode 100755 emulator-asm/src/dma/test_fast_memcpy delete mode 100644 emulator-asm/src/dma/test_fast_memcpy.cpp diff --git a/.gitignore b/.gitignore index fbc8f88e0..992311b7f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,9 @@ *.log /emulator-asm/build* /emulator-asm/src/dma/*.o -/emulator-asm/src/dma/test_dma +/emulator-asm/src/dma/test/*.o +/emulator-asm/src/dma/test/test_dma +/emulator-asm/src/dma/test/build /emulator-asm/src/emu.asm /cache /lib-c/c/build diff --git a/core/src/direct_calls.rs b/core/src/direct_calls.rs index cea9fb57a..e9b433159 100644 --- a/core/src/direct_calls.rs +++ b/core/src/direct_calls.rs @@ -147,9 +147,9 @@ pub fn dma_direct_calls( // Find how a0, a1, a2 are loaded (search from JALR position = i+1) let jalr_idx = i + 1; - let (a0_offset, a0_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A0); - let (a1_offset, a1_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A1); - let (a2_offset, a2_load) = find_reg_load(&riscv_instructions, jalr_idx, REG_A2); + let (a0_offset, a0_load) = find_reg_load(riscv_instructions, jalr_idx, REG_A0); + let (a1_offset, a1_load) = find_reg_load(riscv_instructions, jalr_idx, REG_A1); + let (a2_offset, a2_load) = find_reg_load(riscv_instructions, jalr_idx, REG_A2); // Check if any register load was not found let has_missing = matches!(a0_load, RegLoadType::NotFound) @@ -193,9 +193,9 @@ pub fn dma_direct_calls( }; // Find how a0, a1, a2 are loaded - let (a0_offset, a0_load) = find_reg_load(&riscv_instructions, i, REG_A0); - let (a1_offset, a1_load) = find_reg_load(&riscv_instructions, i, REG_A1); - let (a2_offset, a2_load) = find_reg_load(&riscv_instructions, i, REG_A2); + let (a0_offset, a0_load) = find_reg_load(riscv_instructions, i, REG_A0); + let (a1_offset, a1_load) = find_reg_load(riscv_instructions, i, REG_A1); + let (a2_offset, a2_load) = find_reg_load(riscv_instructions, i, REG_A2); // Check if any register load was not found let has_missing = matches!(a0_load, RegLoadType::NotFound) diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 6ae38f6e6..5b5dcc383 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -1891,15 +1891,15 @@ impl Riscv2ZiskContext<'_> { { // xmemset transpilation pattern: // - // csrsi 0x816, 2 ===> xmemset [x0|a0], a0, size, byte ─┐ - // addi x0, reg(dst), byte addi x0, reg(dst), byte (no-executed) │ jmp+12 - // addi x0, reg(dst), size addi x0, reg(dst), size (no-executed) │ - // .......... .......... <────────────────────────┘ + // csrsi 0x816, 2 ===> xmemset [x0|a0], a0, size, byte ──┐ + // addi x0, reg(dst), size addi x0, reg(dst), size (no-executed) │ jmp+12 + // addi x0, reg(dst), value addi x0, reg(dst), value (no-executed) │ + // .......... .......... <─────────────────────────┘ - let rs1 = i.rs1; // dst - let rs2 = next_instructions[1].imm; // count + let rs1 = next_instructions[0].rs1; // dst + let rs2 = next_instructions[0].imm; // count let rd = next_instructions[0].rd; - let fill_byte = next_instructions[0].imm; // fill_byte + let fill_byte = next_instructions[1].imm; // fill_byte assert!((0..=0xFF).contains(&fill_byte)); self.create_extended_precompiles_op( i, @@ -1908,7 +1908,7 @@ impl Riscv2ZiskContext<'_> { rs2 as u64, rd, fill_byte as i64, - false, + true, 12, ); } else { @@ -1923,9 +1923,9 @@ impl Riscv2ZiskContext<'_> { if !next_instructions.is_empty() && next_instructions[0].inst == "addi" { // xmemset transpilation pattern: // - // csrs 0x816, reg(count) ===> xmemset [x0|a0], a0, reg(count), byte ─┐ - // addi x0, reg(dst), byte addi x0, reg(dst), byte (no-executed) │ jmp+8 - // .......... .......... <───────────────────────────────┘ + // csrs 0x816, reg(dst) ===> xmemset [x0|a0], a0, reg(count), byte ─┐ + // addi x0, reg(cout), byte addi x0, reg(dst), byte (no-executed) │ jmp+8 + // .......... .......... <─────────────────────────────┘ let rs1 = i.rs1; // dst let rs2 = next_instructions[0].rs1; // count diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 6e1567d28..373577a3d 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -2532,7 +2532,7 @@ fn opc_dma_memcpys(ctx: &mut InstContext, extended: bool) { if extended { ctx.extended_arg as u64 } else { ctx.mem.read(EXTRA_PARAMS_ADDR, 8) }; ctx.precompiled.input_data.clear(); - #[cfg(feature = "debug_dma")] + #[cfg(feature = "log_dma_ops")] println!("opc_dma_memcpy 0x{dst:08X} 0x{src:08X} {count} GMR STEP:{}", ctx.step); let encoded = DmaInfo::encode_memcpy(dst, src, count as usize); diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 50abcad8f..417ca756f 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -7269,7 +7269,6 @@ impl ZiskRom2Asm { ctx.flag_is_always_zero = true; } ZiskOp::DmaMemCpy | ZiskOp::DmaXMemCpy => { - assert_eq!(inst.store, STORE_NONE); // Use the memory address as the first and unique parameter *code += &ctx.full_line_comment("DmaMemCpy".to_string()); @@ -7283,12 +7282,22 @@ impl ZiskRom2Asm { ctx.b.string_value, ctx.comment_str("rsi = b = source") ); - *code += &format!( - "\tmov rdx, 0x{:08x} {}\n", - EXTRA_PARAMS_ADDR, - ctx.comment_str("rdx = @EXTERN_PARAM") - ); - *code += &format!("\tmov rdx, [rdx] {}\n", ctx.comment_str("rdx = [EXTERN_PARAM]")); + if inst.op == ZiskOp::DMA_MEMCPY { + *code += &format!( + "\tmov rdx, 0x{:08x} {}\n", + EXTRA_PARAMS_ADDR, + ctx.comment_str("rdx = @EXTERN_PARAM") + ); + *code += + &format!("\tmov rdx, [rdx] {}\n", ctx.comment_str("rdx = [EXTERN_PARAM]")); + } else { + // DMA_XMEMCPY (take count from extended static param) + *code += &format!( + "\tmov rdx, {} {}\n", + inst.jmp_offset1, + ctx.comment(format!("rdx = {}", inst.jmp_offset1)) + ); + } assert_eq!(REG_MEM_READS_ADDRESS, "r12"); assert_eq!(REG_MEM_READS_SIZE, "r13"); @@ -7315,23 +7324,166 @@ impl ZiskRom2Asm { } // Set result - *code += &format!("\txor {}, {} {}\n", REG_C, REG_C, ctx.comment_str("c = 0")); + *code += &format!("\tmov {}, rax {}\n", REG_C, ctx.comment_str("c = rax")); ctx.c.is_saved = true; ctx.flag_is_always_zero = true; } - ZiskOp::DmaMemCmp => { - unimplemented!(); + ZiskOp::DmaMemCmp | ZiskOp::DmaXMemCmp => { + // Use the memory address as the first and unique parameter + *code += &ctx.full_line_comment("DmaMemCmp".to_string()); + + *code += &format!( + "\tmov rdi, {} {}\n", + ctx.a.string_value, + ctx.comment_str("rdi = a = destination") + ); + *code += &format!( + "\tmov rsi, {} {}\n", + ctx.b.string_value, + ctx.comment_str("rsi = b = source") + ); + if inst.op == ZiskOp::DMA_MEMCMP { + *code += &format!( + "\tmov rdx, 0x{:08x} {}\n", + EXTRA_PARAMS_ADDR, + ctx.comment_str("rdx = @EXTERN_PARAM") + ); + *code += + &format!("\tmov rdx, [rdx] {}\n", ctx.comment_str("rdx = [EXTERN_PARAM]")); + } else { + // DMA_XMEMCMP (take count from extended static param) + *code += &format!( + "\tmov rdx, {} {}\n", + inst.jmp_offset1, + ctx.comment(format!("rdx = {}", inst.jmp_offset1)) + ); + } + + assert_eq!(REG_MEM_READS_ADDRESS, "r12"); + assert_eq!(REG_MEM_READS_SIZE, "r13"); + + match ctx.mode { + AsmGenerationMethod::AsmMinimalTraces => { + // the number of mem_reads of trace used by memcpy could be + // large, need to control the count of each operation, and + // if it's necessary call to increase minimal trace + *code += "\tcall direct_dma_memcmp_mtrace\n"; + } + AsmGenerationMethod::AsmRomHistogram => { + // ROM hasn't a variable trace, only multiplicities + *code += "\tcall fast_memcmp\n"; + } + AsmGenerationMethod::AsmMemOp => { + // the maximum number of mops of memcpy is limited because in case of range + // of address only send one address as block. The maximum number of mops + // generated by memcmp is 6, means that no need check size. + // 2 pre-reads + 2 src-reads + 1 src-loop + 1 read block = 6 mops + *code += "\tcall direct_dma_memcmp_mops\n"; + } + _ => unimplemented!("dma_memcmp not implemented for method {:?}", ctx.mode), + } + + // Set result + *code += &format!( + "\tmov {}, rax {}\n", + REG_C, + ctx.comment_str("c = rax (result memcmp)") + ); + ctx.c.is_saved = true; + ctx.flag_is_always_zero = true; } ZiskOp::DmaInputCpy => { - unimplemented!(); + // Use the memory address as the first and unique parameter + *code += &ctx.full_line_comment("DmaInputCpy".to_string()); + + *code += &format!( + "\tmov rdi, {} {}\n", + ctx.a.string_value, + ctx.comment_str("rdi = a = destination") + ); + *code += &format!( + "\tmov rdx, {} {}\n", + ctx.b.string_value, + ctx.comment_str("rdx = b = count") + ); + + assert_eq!(REG_MEM_READS_ADDRESS, "r12"); + assert_eq!(REG_MEM_READS_SIZE, "r13"); + + match ctx.mode { + AsmGenerationMethod::AsmMinimalTraces => { + // the number of mem_reads of trace used by memcpy could be + // large, need to control the count of each operation, and + // if it's necessary call to increase minimal trace + *code += "\tcall direct_dma_inputcpy_mtrace_with_count_check\n"; + } + AsmGenerationMethod::AsmRomHistogram => { + // ROM hasn't a variable trace, only multiplicities + *code += "\tcall fast_inputcpy\n"; + } + AsmGenerationMethod::AsmMemOp => { + // the maximum number of mops of memcpy is limited because in case of range + // of address only send one address as block. The maximum number of mops + // generated by memcpy is 6, means that no need check size. + // 2 pre-reads + 2 src-reads + 1 src-loop + 1 write block = 6 mops + *code += "\tcall direct_dma_inputcpy_mops\n"; + } + _ => unimplemented!("dma_inputcpy not implemented for method {:?}", ctx.mode), + } + + // Set result + *code += &format!("\tmov {}, rax {}\n", REG_C, ctx.comment_str("c = rax")); + ctx.c.is_saved = true; + ctx.flag_is_always_zero = true; } ZiskOp::DmaXMemSet => { - unimplemented!(); - } - ZiskOp::DmaXMemCmp => { - unimplemented!(); - } + // Use the memory address as the first and unique parameter + *code += &ctx.full_line_comment("DmaXMemSet".to_string()); + *code += &format!( + "\tmov rdi, {} {}\n", + ctx.a.string_value, + ctx.comment_str("rdi = a = destination") + ); + *code += &format!( + "\tmov rdx, {} {}\n", + ctx.b.string_value, + ctx.comment_str("rdx = b = source") + ); + *code += &format!( + "\tmov rsi, {} {}\n", + inst.jmp_offset1, + ctx.comment(format!("rsi = {}", inst.jmp_offset1)) + ); + assert_eq!(REG_MEM_READS_ADDRESS, "r12"); + assert_eq!(REG_MEM_READS_SIZE, "r13"); + + match ctx.mode { + AsmGenerationMethod::AsmMinimalTraces => { + // the number of mem_reads of trace used by memcpy could be + // large, need to control the count of each operation, and + // if it's necessary call to increase minimal trace + *code += "\tcall direct_dma_xmemset_mtrace\n"; + } + AsmGenerationMethod::AsmRomHistogram => { + // ROM hasn't a variable trace, only multiplicities + *code += "\tcall fast_memset\n"; + } + AsmGenerationMethod::AsmMemOp => { + // the maximum number of mops of memcpy is limited because in case of range + // of address only send one address as block. The maximum number of mops + // generated by memcpy is 6, means that no need check size. + // 2 pre-reads + 2 src-reads + 1 src-loop + 1 write block = 6 mops + *code += "\tcall direct_dma_xmemset_mops\n"; + } + _ => unimplemented!("dma_memset not implemented for method {:?}", ctx.mode), + } + + // Set result + *code += &format!("\tmov {}, rax {}\n", REG_C, ctx.comment_str("c = rax")); + ctx.c.is_saved = true; + ctx.flag_is_always_zero = true; + } ZiskOp::Dma64Aligned => { unimplemented!("Internal opcode Dma64Aligned"); } diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index c8859ffdb..1425688d8 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -24,7 +24,19 @@ endif # Default EMU_PATH and OUT_PATH EMU_PATH ?= src/emu.asm OUT_PATH ?= build/ziskemuasm -DMA_OBJS = build/dma/fast_dma_encode.o build/dma/memcpy_fast.o build/dma/direct_memcpy_mtrace.o build/dma/direct_memcpy_mops.o +# build/dma/memcpy_fast.o +DMA_OBJS = build/dma/fast_dma_encode.o \ + build/dma/direct_memcpy_mops.o build/dma/direct_memcpy_mtrace.o \ + build/dma/direct_inputcpy_mops.o build/dma/direct_inputcpy_mtrace.o \ + build/dma/direct_memcmp_mops.o build/dma/direct_memcmp_mtrace.o \ + build/dma/direct_memset_mops.o build/dma/direct_memset_mtrace.o \ + build/dma/fast_memcmp.o \ + build/dma/fast_memcpy64.o \ + build/dma/fast_memcpy.o \ + build/dma/fast_memset.o \ + build/dma/fast_inputcpy.o \ + build/dma/memcpy_fast.o \ + build/dma/check_dynamic_mtrace.o # Ensure the output directory exists OUT_DIR := $(dir $(OUT_PATH)) diff --git a/emulator-asm/src/dma/Makefile b/emulator-asm/src/dma/Makefile deleted file mode 100644 index c92a80287..000000000 --- a/emulator-asm/src/dma/Makefile +++ /dev/null @@ -1,74 +0,0 @@ -# Makefile for encode_memcpy_inline test - -CXX = g++ -CXXFLAGS = -std=c++17 -O0 -Wall -Wextra -g -no-pie -# CXXFLAGS = -std=c++17 -O2 -Wall -Wextra -g -no-pie -LDFLAGS = -no-pie -ASMFLAGS = -g --noexecstack -I$(CURDIR) - -SRCS_ASM = direct_memcpy_mtrace.asm memcpy_fast.asm fast_dma_encode.asm direct_memcpy_mops.asm direct_xmemset_mops.asm direct_xmemset_mtrace.asm fast_memset.asm fast_memcpy.asm direct_xinput_mops.asm -SRCS_CPP = test_dma.cpp test_dma_memset_mops.cpp test_dma_memset_mtrace.cpp test_fast_memcpy.cpp test_dma_inputcpy_mops.cpp -OBJS_ASM = direct_memcpy_mtrace.o memcpy_fast.o fast_dma_encode.o direct_memcpy_mops.o -OBJS = $(OBJS_ASM) test_dma.o -TARGET = test_dma -TARGET_MEMSET = test_dma_memset_mops -TARGET_MEMSET_MTRACE = test_dma_memset_mtrace -TARGET_FAST_MEMCPY = test_fast_memcpy -TARGET_INPUTCPY = test_dma_inputcpy_mops - -# Memset MOPS-specific objects -OBJS_MEMSET_ASM = direct_xmemset_mops.o fast_memset.o -OBJS_MEMSET = $(OBJS_MEMSET_ASM) test_dma_memset_mops.o - -# Memset MTRACE-specific objects -OBJS_MEMSET_MTRACE_ASM = direct_xmemset_mtrace.o fast_memset.o fast_dma_encode.o fast_memcpy.o -OBJS_MEMSET_MTRACE = $(OBJS_MEMSET_MTRACE_ASM) test_dma_memset_mtrace.o - -# Fast memcpy test objects -OBJS_FAST_MEMCPY = fast_memcpy.o test_fast_memcpy.o - -# Inputcpy test objects -OBJS_INPUTCPY = direct_xinput_mops.o fast_memcpy.o test_dma_inputcpy_mops.o - -all: $(TARGET) $(TARGET_MEMSET) $(TARGET_MEMSET_MTRACE) $(TARGET_FAST_MEMCPY) $(TARGET_INPUTCPY) - -%.o: %.asm - as $(ASMFLAGS) -o $@ $< - -%.o: %.cpp - $(CXX) $(CXXFLAGS) -c -o $@ $< - -$(TARGET): $(OBJS) - $(CXX) $(LDFLAGS) -o $@ $^ - -$(TARGET_MEMSET): $(OBJS_MEMSET) - $(CXX) $(LDFLAGS) -o $@ $^ - -$(TARGET_MEMSET_MTRACE): $(OBJS_MEMSET_MTRACE) - $(CXX) $(LDFLAGS) -o $@ $^ - -$(TARGET_FAST_MEMCPY): $(OBJS_FAST_MEMCPY) - $(CXX) $(LDFLAGS) -o $@ $^ - -$(TARGET_INPUTCPY): $(OBJS_INPUTCPY) - $(CXX) $(LDFLAGS) -o $@ $^ - -test: $(TARGET) - ./$(TARGET) - -test_memset: $(TARGET_MEMSET) - ./$(TARGET_MEMSET) - -test_memset_mtrace: $(TARGET_MEMSET_MTRACE) - ./$(TARGET_MEMSET_MTRACE) - -run_fast_memcpy: $(TARGET_FAST_MEMCPY) - ./$(TARGET_FAST_MEMCPY) - -test_inputcpy: $(TARGET_INPUTCPY) - ./$(TARGET_INPUTCPY) - -clean: - rm -f $(OBJS) $(OBJS_MEMSET) $(OBJS_MEMSET_MTRACE) $(OBJS_FAST_MEMCPY) $(OBJS_INPUTCPY) $(TARGET) $(TARGET_MEMSET) $(TARGET_MEMSET_MTRACE) $(TARGET_FAST_MEMCPY) $(TARGET_INPUTCPY) - -.PHONY: all test test_memset test_memset_mtrace run_fast_memcpy test_inputcpy clean diff --git a/emulator-asm/src/dma/check_dynamic_mtrace.asm b/emulator-asm/src/dma/check_dynamic_mtrace.asm new file mode 100644 index 000000000..4f12f7d1d --- /dev/null +++ b/emulator-asm/src/dma/check_dynamic_mtrace.asm @@ -0,0 +1,195 @@ +.intel_syntax noprefix +.code64 + +# +# check_dynamic_mtrace - Check if mtrace buffer needs dynamic expansion +# +# PURPOSE: +# This function determines whether the memory trace (mtrace) buffer needs to be +# dynamically expanded. It should be called whenever the data to be written to +# the trace exceeds the MAX_BYTES_DIRECT_MTRACE threshold. +# +# HOW IT WORKS: +# The function uses a two-level check to minimize overhead: +# +# 1. FAST PATH (most common): Check if current mtrace position plus required +# bytes is below the threshold. If yes, return immediately - no expansion needed. +# +# 2. SLOW PATH: If we're past the threshold, check if this is expected based on +# how many steps have been consumed in the current chunk. We use a worst-case +# assumption where each step consumes MAX_BYTES_MTRACE_STEP bytes. This value +# is slightly larger than MAX_BYTES_DIRECT_MTRACE because it includes other +# mtrace costs like register read operations. +# +# By using worst-case assumptions, we avoid checking on every mtrace write. +# If actual usage is within expected bounds for consumed steps, no expansion +# is needed. Only when actual usage exceeds expected usage do we trigger realloc. +# +# REGISTER USAGE: +# Uses: R_MT_ADDR, R_MT_INDEX, R_STEP, R_COUNT, R_AUX, R_AUX2, R_DST, R_SRC +# Preserves: All XMM registers (saved/restored only when calling _realloc_trace) +# Preserves: r8, r10, r11, R_SRC, R_DST, R_COUNT (when calling _realloc_trace) +# +# PARAMETERS (using DMA register convention): +# R_MT_ADDR = Base address of mtrace buffer +# R_MT_INDEX = Current index into mtrace buffer (in qwords) +# R_STEP = Steps remaining to end of chunk +# R_COUNT = Bytes required by current request (with margin) +# +# RETURN VALUE: +# None (returns via ret, mtrace may have been expanded) +# +# PERFORMANCE: +# - Fast path (no expansion needed): ~8-9 cycles +# - Slow path (within expected bounds): ~15-17 cycles +# - Realloc path: ~200-600 cycles (includes XMM save/restore + _realloc_trace call) +# +# SIDE EFFECTS: +# - May trigger _realloc_trace which expands the mtrace buffer +# - Updates trace_address_threshold after realloc + +.global check_dynamic_mtrace + +.extern fast_dma_encode +.extern trace_address_threshold +# .extern trace_resize_request + +.ifdef DEBUG +.section .data +.align 8 + dma_check_case: .quad 0 + dma_check_step: .quad 0 + dma_check_aux: .quad 0 + dma_check_threshold: .quad 0 +.endif + +.include "dma_constants.inc" + +.section .text + +# REGISTER INPUTS: +# R_MT_ADDR = base address of mtrace buffer +# R_MT_INDEX = current index into mtrace (qwords) +# R_STEP = steps remaining until end of chunk +# R_COUNT = bytes needed by current request + +check_dynamic_mtrace: + + # FAST PATH: Check if we're below the threshold + # + # trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE + # This gives us the "safe" limit before considering reallocation. + # Calculate: current_addr + required_bytes + margin, compare with threshold. + +.ifdef DEBUG + mov qword ptr [dma_check_case], 1 +.endif + + lea R_AUX, [R_MT_ADDR + 8 * R_MT_INDEX] # 1 cycle - current mtrace address + lea R_AUX, [R_AUX + R_COUNT + MAX_DMA_MT_MARGIN] # 1 cycle - add required bytes + safety margin + sub R_AUX, [trace_address_threshold] # ~4 cycles - bytes over threshold (negative = OK) + jnc .L_calculate_current_margin # 2 cycles (predicted) - if negative, space available + ret # FAST PATH EXIT: ~8-9 cycles total + + # SLOW PATH: Check if usage is within expected bounds for consumed steps + # + # Instead of checking every time, we use worst-case assumptions: + # - Each step may consume up to MAX_BYTES_MTRACE_STEP bytes + # - MAX_BYTES_MTRACE_STEP > MAX_BYTES_DIRECT_MTRACE (includes register reads, etc.) + # - If actual usage <= (steps_consumed * MAX_BYTES_MTRACE_STEP), no realloc needed + # + # R_STEP contains steps REMAINING to end of chunk + # steps_consumed = CHUNK_SIZE - R_STEP + +.L_calculate_current_margin: + +.ifdef DEBUG + mov qword ptr [dma_check_case], 2 + mov [dma_check_step], R_STEP + mov [dma_check_aux], R_AUX +.endif + + # Calculate expected worst-case mtrace usage for consumed steps + mov R_AUX2, CHUNK_SIZE # 1 cycle - total steps per chunk + sub R_AUX2, R_STEP # 1 cycle - steps_consumed = total - remaining + imul R_AUX2, MAX_BYTES_MTRACE_STEP # ~3 cycles - expected_bytes = steps * worst_case_per_step + cmp R_AUX2, R_AUX # 1 cycle - compare expected vs actual overflow + jb .L_call_realloc # 2 cycles (predicted) - if expected < actual, need realloc + ret # SLOW PATH EXIT: ~15-17 cycles total + +.L_call_realloc: + + # REALLOC PATH: Actual usage exceeds expected bounds, must expand mtrace + # + # Save all volatile registers since _realloc_trace follows System V ABI + # and may use any of them. We're being called from non-ABI-compliant code, + # so we must preserve everything our caller expects. +.ifdef DEBUG + mov qword ptr [dma_check_case], 3 + mov R_AUX, [trace_address_threshold] + mov [dma_check_threshold], R_AUX +.endif + + # Save general purpose registers used by DMA operations + push R_COUNT # 1 cycle - save count + push r8 # 1 cycle + push r10 # 1 cycle + push r11 # 1 cycle + push R_SRC # 1 cycle - save source address + push R_DST # 1 cycle - save destination address + + # Allocate stack for XMM registers (16 registers x 16 bytes + 8 for alignment) + # Note: We're inside a call, so stack is unaligned to 16 bytes + sub rsp, 16*16 + 8 # 1 cycle - allocate 264 bytes + + # Save all XMM registers (may be used by caller for optimized operations) + movaps [rsp + 0*16], xmm0 # 1 cycle - aligned 128-bit stores + movaps [rsp + 1*16], xmm1 # 1 cycle + movaps [rsp + 2*16], xmm2 # 1 cycle + movaps [rsp + 3*16], xmm3 # 1 cycle + movaps [rsp + 4*16], xmm4 # 1 cycle + movaps [rsp + 5*16], xmm5 # 1 cycle + movaps [rsp + 6*16], xmm6 # 1 cycle + movaps [rsp + 7*16], xmm7 # 1 cycle + movaps [rsp + 8*16], xmm8 # 1 cycle + movaps [rsp + 9*16], xmm9 # 1 cycle + movaps [rsp + 10*16], xmm10 # 1 cycle + movaps [rsp + 11*16], xmm11 # 1 cycle + movaps [rsp + 12*16], xmm12 # 1 cycle + movaps [rsp + 13*16], xmm13 # 1 cycle + movaps [rsp + 14*16], xmm14 # 1 cycle + movaps [rsp + 15*16], xmm15 # 1 cycle + + call _realloc_trace # ~5 cycles call + ~100-500 cycles function body + + # Restore all XMM registers + movaps xmm0, [rsp + 0*16] # 1 cycle - aligned 128-bit loads + movaps xmm1, [rsp + 1*16] # 1 cycle + movaps xmm2, [rsp + 2*16] # 1 cycle + movaps xmm3, [rsp + 3*16] # 1 cycle + movaps xmm4, [rsp + 4*16] # 1 cycle + movaps xmm5, [rsp + 5*16] # 1 cycle + movaps xmm6, [rsp + 6*16] # 1 cycle + movaps xmm7, [rsp + 7*16] # 1 cycle + movaps xmm8, [rsp + 8*16] # 1 cycle + movaps xmm9, [rsp + 9*16] # 1 cycle + movaps xmm10, [rsp + 10*16] # 1 cycle + movaps xmm11, [rsp + 11*16] # 1 cycle + movaps xmm12, [rsp + 12*16] # 1 cycle + movaps xmm13, [rsp + 13*16] # 1 cycle + movaps xmm14, [rsp + 14*16] # 1 cycle + movaps xmm15, [rsp + 15*16] # 1 cycle + + add rsp, 16*16 +8 # 1 cycle - deallocate stack space + + # Restore general purpose registers + pop R_DST # 1 cycle - restore destination address + pop R_SRC # 1 cycle - restore source address + pop r11 # 1 cycle + pop r10 # 1 cycle + pop r8 # 1 cycle + pop R_COUNT # 1 cycle - restore count +.L_memcpy_mtrace_continue: + ret + +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/direct_xinput_mops.asm b/emulator-asm/src/dma/direct_inputcpy_mops.asm similarity index 86% rename from emulator-asm/src/dma/direct_xinput_mops.asm rename to emulator-asm/src/dma/direct_inputcpy_mops.asm index bf9927f63..1503c2f00 100644 --- a/emulator-asm/src/dma/direct_xinput_mops.asm +++ b/emulator-asm/src/dma/direct_inputcpy_mops.asm @@ -23,7 +23,6 @@ ################################################################################ .global direct_dma_inputcpy_mops -.global dma_inputcpy_mops .extern fast_memcpy .extern fcall_ctx @@ -31,32 +30,6 @@ .section .text -# Standard ABI wrapper that saves/restores callee-saved registers -# and initializes mops tracking state before calling direct implementation - -# rdi = destination -# rsi = count (bytes) -# rdx = pointer to data trace -# rax = return qword of trace - -dma_inputcpy_mops: - - # Save callee-saved registers - push r12 # ~3 cycles - save r12 (used as mops base address) - push r13 # ~3 cycles - save r13 (used as mops index) - - mov r12, rdx # 1 cycle - r12 = mops buffer base address - xor r13, r13 # 1 cycle - r13 = 0 (initialize mops index) - mov rdx, rsi - - call direct_dma_inputcpy_mops # ~5 cycles + function cost - - mov rax, r13 # 1 cycle - return mops count in rax - pop r13 # ~3 cycles - restore r13 - pop r12 # ~3 cycles - restore r12 - - ret # ~5 cycles - # Direct entry point for assembly callers (no ABI overhead) # More efficient when caller manages register preservation @@ -99,7 +72,9 @@ direct_dma_inputcpy_mops: mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry (block write) inc r13 # 1 cycle - advance mops index - jmp .L_fast_inputcpy + jmp fast_inputcpy + # fast_inputcpy "execute" the return + .L_inputcpy_mops_count_remain: # BRANCH 1 @@ -132,7 +107,8 @@ direct_dma_inputcpy_mops: mov [r12 + r13 * 8 + 8], rax # ~4 cycles - write mops entry (block write) add r13, 2 - jmp .L_fast_inputcpy + jmp fast_inputcpy + # fast_inputcpy "execute" the return .L_inputcpy_mops_rdi_unaligned: # BRANCH 2 - worse @@ -179,7 +155,8 @@ direct_dma_inputcpy_mops: mov [r12 + r13 * 8], rcx add r13, 2 - jmp .L_fast_inputcpy + jmp fast_inputcpy + # fast_inputcpy "execute" the return .L_branch_2_2: @@ -217,14 +194,9 @@ direct_dma_inputcpy_mops: # rsi = input + mops # incr mops -.L_fast_inputcpy: - mov rcx, qword ptr [fcall_ctx + FCALL_RESULT_GOT * 8] - lea rsi, [fcall_ctx + rcx * 8 + FCALL_RESULT * 8 - 8] - lea rcx, [rdx + 7] - shr rcx, 3 - add qword ptr [fcall_ctx + FCALL_RESULT_GOT * 8], rcx + jmp fast_inputcpy - jmp fast_memcpy + # fast_inputcpy "execute" the return .L_inputcpy_mops_done: ret diff --git a/emulator-asm/src/dma/direct_inpucpy_mtrace.asm b/emulator-asm/src/dma/direct_inputcpy_mtrace.asm similarity index 50% rename from emulator-asm/src/dma/direct_inpucpy_mtrace.asm rename to emulator-asm/src/dma/direct_inputcpy_mtrace.asm index 6ab88977f..9d0691e59 100644 --- a/emulator-asm/src/dma/direct_inpucpy_mtrace.asm +++ b/emulator-asm/src/dma/direct_inputcpy_mtrace.asm @@ -52,23 +52,15 @@ # - Preserves direction flag (cld called after any std) .global direct_dma_inputcpy_mtrace -.global dma_inputcpy_mtrace .global direct_dma_inputcpy_mtrace_with_count_check -.extern fast_dma_encode_inputcpy .extern trace_address_threshold -# .extern trace_resize_request - -.ifdef DEBUG -.section .data -.align 8 - dma_check_case: .quad 0 - dma_check_step: .quad 0 - dma_check_aux: .quad 0 - dma_check_threshold: .quad 0 -.endif +.extern fcall_ctx +.extern fast_memcpy +.extern fast_memcpy64 .include "dma_constants.inc" +.include "fast_dma_encode_macro.inc" .section .text @@ -82,122 +74,12 @@ .set R_COUNT, rdx .set R_ENCODE, rax -dma_inputcpy_mtrace: - push R_MT_ADDR # ~3 cycles - save callee-saved register - push R_MT_INDEX # ~3 cycles - save callee-saved register - push R_AUX # ~3 cycles - save callee-saved register - push rbx # ~3 cycles - save callee-saved register - - mov R_MT_ADDR, R_COUNT # 1 cycle - setup trace address from count - xor R_MT_INDEX, R_MT_INDEX # 1 cycle - initialize trace index to 0 - call direct_dma_inputcpy_mtrace # ~5 cycles + function cost - - mov R_ENCODE, R_MT_INDEX # 1 cycle - return trace index in R_ENCODE - pop rbx # ~3 cycles - restore register - pop R_AUX # ~3 cycles - restore register - pop R_MT_INDEX # ~3 cycles - restore register - pop R_MT_ADDR # ~3 cycles - restore register - - ret # ~5 cycles - -.L_inputcpy_check_mtrace_available: - - # trace_address_threshold containt the address "limit" before call _realloc_trace - # trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE - - # calculate bytes of mtrace used and verify if throw the limit - -.ifdef DEBUG - mov qword ptr [dma_check_case], 1 -.endif - - lea R_AUX, [R_MT_ADDR + 8 * R_MT_INDEX] # 1 cycle - calculate address mtrace - lea R_AUX, [R_AUX + R_COUNT + MAX_DMA_MT_MARGIN] # 1 cycle - calculate current mtrace bytes usage - sub R_AUX, [trace_address_threshold] # ~4 cycles - bytes over threshold (can be negative) - jc .L_inputcpy_mtrace_continue # 2 cycles (predicted) - negative means space available - - # check if bytes over threshold are usual for current situation on inside chunk - # R_STEP contain number the steps to end of chunk, we need number the steps consumed - -.ifdef DEBUG - mov qword ptr [dma_check_case], 2 - mov [dma_check_step], R_STEP - mov [dma_check_aux], R_AUX -.endif - - mov R_AUX2, CHUNK_SIZE # 1 cycle - load chunk size constant - sub R_AUX2, R_STEP # 1 cycle - calculate steps consumed in chunk - imul R_AUX2, MAX_BYTES_MTRACE_STEP # ~3 cycles - bytes expected for consumed steps - cmp R_AUX2, R_AUX # 1 cycle - compare expected vs actual - jae .L_inputcpy_mtrace_continue # 2 cycles (predicted) - expected >= actual, ok - - # at this point we need to increase trace, registers R_ENCODE, R_AUX no need to save. -.ifdef DEBUG - mov qword ptr [dma_check_case], 3 - mov R_AUX, [trace_address_threshold] - mov [dma_check_threshold], R_AUX -.endif - - # mov qword ptr [trace_resize_request], R_COUNT - - push R_COUNT # ~3 cycles - save general purpose registers - push r8 # ~3 cycles - push r10 # ~3 cycles - push r11 # ~3 cycles - push R_SRC # ~3 cycles - push R_DST # ~3 cycles - - # IMPORTANT: inside call means unaligned to 16 bits - - sub rsp, 16*16 + 8 # 1 cycle - allocate stack space for 16 XMM registers - - movaps [rsp + 0*16], xmm0 # ~4 cycles - save XMM registers (aligned stores) - movaps [rsp + 1*16], xmm1 # ~4 cycles - movaps [rsp + 2*16], xmm2 # ~4 cycles - movaps [rsp + 3*16], xmm3 # ~4 cycles - movaps [rsp + 4*16], xmm4 # ~4 cycles - movaps [rsp + 5*16], xmm5 # ~4 cycles - movaps [rsp + 6*16], xmm6 # ~4 cycles - movaps [rsp + 7*16], xmm7 # ~4 cycles - movaps [rsp + 8*16], xmm8 # ~4 cycles - movaps [rsp + 9*16], xmm9 # ~4 cycles - movaps [rsp + 10*16], xmm10 # ~4 cycles - movaps [rsp + 11*16], xmm11 # ~4 cycles - movaps [rsp + 12*16], xmm12 # ~4 cycles - movaps [rsp + 13*16], xmm13 # ~4 cycles - movaps [rsp + 14*16], xmm14 # ~4 cycles - movaps [rsp + 15*16], xmm15 # ~4 cycles - - call _realloc_trace # ~5 cycles + function cost (~100-500 cycles) - - movaps xmm0, [rsp + 0*16] # ~4 cycles - restore XMM registers (aligned loads) - movaps xmm1, [rsp + 1*16] # ~4 cycles - movaps xmm2, [rsp + 2*16] # ~4 cycles - movaps xmm3, [rsp + 3*16] # ~4 cycles - movaps xmm4, [rsp + 4*16] # ~4 cycles - movaps xmm5, [rsp + 5*16] # ~4 cycles - movaps xmm6, [rsp + 6*16] # ~4 cycles - movaps xmm7, [rsp + 7*16] # ~4 cycles - movaps xmm8, [rsp + 8*16] # ~4 cycles - movaps xmm9, [rsp + 9*16] # ~4 cycles - movaps xmm10, [rsp + 10*16] # ~4 cycles - movaps xmm11, [rsp + 11*16] # ~4 cycles - movaps xmm12, [rsp + 12*16] # ~4 cycles - movaps xmm13, [rsp + 13*16] # ~4 cycles - movaps xmm14, [rsp + 14*16] # ~4 cycles - movaps xmm15, [rsp + 15*16] # ~4 cycles - - add rsp, 16*16 +8 # 1 cycle - deallocate stack space - - pop R_DST # ~3 cycles - restore general purpose registers - pop R_SRC # ~3 cycles - pop r11 # ~3 cycles - pop r10 # ~3 cycles - pop r8 # ~3 cycles - pop R_COUNT # ~3 cycles - - jmp .L_inputcpy_mtrace_continue +# DIRECT CALL +# RDI = DST +# RSI = RESERVED(INPUT) +# RDX = COUNT +# RCX = TRACE direct_dma_inputcpy_mtrace_with_count_check: @@ -205,24 +87,27 @@ direct_dma_inputcpy_mtrace_with_count_check: # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count # Result will be returned in R_ENCODE (encoded value) - cmp R_COUNT, MAX_DMA_BYTES_DIRECT_MTRACE # 1 cycle - check if count exceeds direct threshold - ja .L_inputcpy_check_mtrace_available # 2 cycles (not taken usually) - large count, check trace space + cmp R_COUNT, MAX_DMA_BYTES_DIRECT_MTRACE # 1 cycle - check if count exceeds direct threshold + ja .L_inputcpy_check_dynamic_trace # 2 cycles (not taken usually) - large count, check trace space + jmp direct_dma_inputcpy_mtrace + +.L_inputcpy_check_dynamic_trace: + call check_dynamic_mtrace -.L_inputcpy_mtrace_continue: direct_dma_inputcpy_mtrace: # Call fast_dma_encode to calculate encoding # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count # Result will be returned in R_ENCODE (encoded value) - call fast_dma_encode_inputcpy + FAST_DMA_ENCODE_NO_SRC mov [R_MT_ADDR + R_MT_INDEX * 8], R_ENCODE # ~4 cycles - write encoded result to mem trace inc R_MT_INDEX # 1 cycle - advance R_MT_INDEX (mem trace index) .L_pre_dst_to_mtrace: # If pre_count > 0, write aligned dst value to trace - test R_ENCODE, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + test R_ENCODE, DMA_PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 jz .L_post_dst_to_mtrace # 2 cycles (predicted taken) # Branch with pre_count > 0: save original dst value before it's overwritten @@ -235,7 +120,7 @@ direct_dma_inputcpy_mtrace: .L_post_dst_to_mtrace: # If post_count > 0, write aligned (dst+count) value to trace - test R_ENCODE, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + test R_ENCODE, DMA_POST_COUNT_MASK # 1 cycle - check if post_count > 0 jz .L_input_to_mtrace # 2 cycles (predicted taken) - skip to input copy lea R_AUX, [R_DST + R_COUNT - 1] # 1 cycle - R_AUX = dst + count - 1 (last dst byte) @@ -245,34 +130,29 @@ direct_dma_inputcpy_mtrace: inc R_MT_INDEX # 1 cycle - advance trace index .L_input_to_mtrace: - # Copy input data to trace buffer - # Total qwords = loop_count (bits 0-31) + extra_src_reads (bits 48-50) + # Copy input data to trace buffer, always aligned. + # Total qwords = (count + 7) - mov R_AUX2, R_ENCODE # 1 cycle - R_AUX2 = encoded - shr R_AUX2, LOOP_COUNT_RS # 1 cycle - R_AUX2 = loop_count (bits 32-63) + lea R_AUX2, [R_COUNT + 7] # 1 cycle - R_AUX2 = count + 7 + shr R_AUX2, 3 # 1 cycle - R_AUX2 = round_up(count/8) - mov R_AUX, R_ENCODE # 1 cycle - R_AUX = encoded - shr R_AUX, EXTRA_SRC_READS_RS # 1 cycle - shift extra_src_reads to position - and R_AUX, 0x03 # 1 cycle - R_AUX = extra_src_reads (bits 48-50) - add R_AUX2, R_AUX # 1 cycle - R_AUX2 = total qwords to copy - mov R_AUX, qword ptr [fcall_ctx + FCALL_RESULT_GOT * 8] - lea R_SRC, [fcall_ctx + rcx * 8 + FCALL_RESULT * 8 - 8] - add qword ptr [fcall_ctx + FCALL_RESULT_GOT * 8], R_AUX2 + lea R_SRC, [fcall_ctx + R_AUX * 8 + FCALL_RESULT * 8 - 8] - mov R_AUX, R_SRC push R_DST # ~3 cycles - save dst pointer lea R_DST, [R_MT_ADDR + R_MT_INDEX * 8] # 1 cycle - R_DST = trace buffer destination add R_MT_INDEX, R_AUX2 # 1 cycle - advance trace index by qwords copied + push R_COUNT + mov R_COUNT, R_AUX2 call fast_memcpy64 # rep movsq # ~1.5-2 cycles per qword (hardware optimized) + pop R_COUNT pop R_DST # ~3 cycles - restore dst pointer - mov R_SRC, R_AUX # 1 cycle - restore original src pointer mov R_AUX2, R_COUNT - call fast_memcpy + jmp fast_inputcpy .L_done: ret # ~5 cycles diff --git a/emulator-asm/src/dma/direct_memcmp_mops.asm b/emulator-asm/src/dma/direct_memcmp_mops.asm new file mode 100644 index 000000000..2b1bc5e19 --- /dev/null +++ b/emulator-asm/src/dma/direct_memcmp_mops.asm @@ -0,0 +1,280 @@ +.intel_syntax noprefix +.code64 + +################################################################################ +# memcmp_mops - Optimized version with memory ops tracing and actual copy. This +# is an variant of memcmp operation, xmemcmp operation, that it +# doesn't read the count from zisk memory. +# +# This function performs two main tasks: +# 1. Records all addresses of memory operations (read and write addresses) +# 2. Performs the actual memory copy from src to dst (with overlap handling) +# +# REGISTER USAGE: +# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r9, r11, r12, r13 +# Does NOT use XMM registers (caller doesn't need to save them) +# Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) +# +# PARAMETERS (NON System V AMD64 ABI): +# rdi -> rbx = dst (u64) - Destination address +# rsi -> rax = src (u64) - Source address +# rdx -> count (usize) - Number of bytes to copy +# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) +# +# MEMORY COPY BEHAVIOR: +# - Handles overlapping src/dst correctly (like memmove) +# - For non-overlapping: optimized copy using pre_count/loop_count/post_count +# - For overlapping: backward byte-by-byte copy to avoid corruption +################################################################################ + +.global direct_dma_memcmp_mops +.global direct_dma_xmemcmp_mops +.extern fast_dma_encode +.extern fast_memcpy +.extern fast_memcpy64 + +.include "dma_constants.inc" +.include "fast_dma_encode_macro.inc" + +.section .text + +# call directly from assembly without standard ABI call +# more eficient + +direct_dma_memcmp_mops: + + # updated registers: + # r9 = no save (value_reg) + # rcx = no save (available from asm) + # rdi = no save (available from asm) + # rsi = no save (available from asm) + # r13 = with new mops index (output) + # rax = encoded + + mov r9, (MOPS_ALIGNED_READ + EXTRA_PARAMETER_ADDR) + mov [r12 + r13 * 8], r9 + inc r13 + +direct_dma_xmemcmp_mops: + + # Call fast_dma_encode to calculate encoding + # Parameters already in correct registers: rdi=dst, rsi=src, rdx=count + # Result will be returned in rax (encoded value) + + test rdx, rdx + jz .L_dma_memcmp_mops_count_zero + + call fast_memcmp + + # in case of original count > 0, the effective count must be > 0 because at least + # need check one byte to see if are equals or not + + # at end the memcpy must return rax with correct value, but these information + # could be extract from encoded, this is the stragegy to avoid manage two values + # encoded and result + + mov r9, rax + and r9, 0x1FF + jz .L_fast_dma_memcmp_encode_eq + + # Non-equal case: use table with NEQ offset + FAST_DMA_ENCODE_MEMCMP FAST_ENCODE_TABLE_WO_NEQ_SIZE + + shl r9, DMA_CMP_RES_RS # 1 cycle - shift cmp_result to position (bits 21-28) + or rax, r9 # 1 cycle - combine with encoding + jmp .L_pre_dst_to_mops # 1 cycle + +.L_fast_dma_memcmp_encode_eq: + # Equal case: use base table (offset 0) + FAST_DMA_ENCODE_MEMCMP 0 + +.L_pre_dst_to_mops: + # If pre_count > 0, write aligned dst value to trace + test rax, DMA_PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_post_dst_to_mops # 2 cycles (predicted taken) + +.L_pre_is_active: + # Branch with pre_count > 0: save original dst value before it's overwritten + mov r9, MOPS_ALIGNED_READ # r9 = flags aligned read + add r9, rdi # 1 cycle - get original dst + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov [r12 + r13 * 8], r9 # ~4 cycles - write dst pre-address to trace + + test rax, DMA_DOUBLE_SRC_PRE_MASK + jnz .L_pre_double_src_to_mops + +.L_pre_single_src_to_mops: + + mov r9, MOPS_ALIGNED_READ # r9 = flags aligned read + add r9, rsi # 1 cycle - get original src + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + jmp .L_pre_src_inc_mops_index + +.L_pre_double_src_to_mops: + + mov r9, MOPS_ALIGNED_READ_2W # r9 = flags double read + add r9, rsi # 1 cycle - get original src + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + +.L_pre_src_inc_mops_index: + add r13, 2 # add 2 (pre-write, block single/dual src) + +.L_post_dst_to_mops: + + # If post_count > 0, write aligned (dst+count) value to trace + test rax, DMA_POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_src_to_mops # 2 cycles (predicted taken) - skip to src copy + +.L_post_is_active: + # preparing post pre-write read + mov rcx, MOPS_ALIGNED_READ # rcx = flags aligned read + lea r9, [rdi + rdx - 1] # 1 cycle - r9 = dst + count - 1 (last dst byte) + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + add r9, rcx # 1 cycle - r9 mops with dst aligned address + mov [r12 + r13 * 8], r9 # ~4 cycles - write dst post-value to trace + + # preparing post src read, calculating base src address + mov r9, rax + shr r9, DMA_PRE_AND_LOOP_BYTES_RS + add r9, rsi + and r9, ALIGN_MASK + + # check if single read or double read + test rax, DMA_DOUBLE_SRC_POST_MASK + jnz .L_post_double_src_to_mops + +.L_post_single_src_to_mops: + # not double read, load flags and store in mops trace + mov rcx, MOPS_ALIGNED_READ # r9 = flags aligned read + add r9, rcx # 1 cycle - get original src + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + jmp .L_post_src_inc_mops_index + +.L_post_double_src_to_mops: + # its double read, load flags for double read, and store in mops trace + mov rcx, MOPS_ALIGNED_READ_2W # r9 = flags aligned read + add r9, rcx # 1 cycle - get original src + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + +.L_post_src_inc_mops_index: + # adding two "slots", because we store pre-write read and source-read + add r13, 2 # add 2 (pre-write, block single/dual src) + +.L_src_to_mops: + # extract loop count from encoded + mov rcx, rax # 1 cycle - rcx = encoded + shr rcx, DMA_LOOP_COUNT_RS # 1 cycle - rcx = loop (32 bits) + + # check edge case loop_count = 0 + jz .L_prepare_result + shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - rcx = loop | 0 (4 bits) | (32 bits) + + # in case of unaligned loop, add and extra read because each qword verification + # of dst need part of current read and part of next read. + + test rax, DMA_UNALIGNED_DST_SRC_MASK + jnz .L_src_extra_for_unaligned_loop + + # add flags of aligned block read + mov r9, MOPS_ALIGNED_BLOCK_READ # 1 cycle - r9 = read block + jmp .L_src_block_before_address + +.L_src_extra_for_unaligned_loop: + # add special flags with that add one to current count loop count in r9 + mov r9, MOPS_ALIGNED_BLOCK_READ + MOPS_BLOCK_ONE_WORD + +.L_src_block_before_address: + # at this point in r9 we have flags, and lenght but we need to add the + # base src address. For do it, first we need to know if we need to pass + # the first src because it was used only for pre part. + add r9, rcx + test rax, DMA_SRC64_INC_BY_PRE_MASK + jnz .L_src_incr_by_pre + add r9, rsi # 1 cycle - rcx = first block src address + jmp .L_src_to_mops_ready + +.L_src_incr_by_pre: + # in this patch the first src address is used exclusively by pre part, for this + # reason we add rsi + 8 to r9 + lea r9, [rsi + r9 + 8] + +.L_src_to_mops_ready: + # before store all, we need to align them + and r9, ALIGN_MASK + mov [r12 + r13 * 8], r9 # ~4 cycles - write first block src read address + + # rcx = loop_count + mov r9, MOPS_ALIGNED_BLOCK_READ + add rcx, r9 + test rax, DMA_PRE_COUNT_MASK + jz .L_dst_loop_has_not_offset + add rcx, 8 + +.L_dst_loop_has_not_offset: + add rcx, rdi + and rcx, ALIGN_MASK + mov [r12 + r13 * 8 + 8], rcx # ~4 cycles - write first block src read address + + add r13, 2 + +.L_prepare_result: + # how we are in comparation, we don't write pre/post parts because when pre-read + # this parts and with this is enough. In case of loop is different because we don't + # pre-read for this reason we need to read to verify that are equals. The loop part + # only could verify that all are equal. + + # extract result from encoded + shr rax, DMA_CMP_RES_RS + test rax, 0x100 + jnz .L_memcmp_mops_negative_res + and rax, 0xFF + jmp .L_memcmp_mops_res_ready + +.L_memcmp_mops_negative_res: + or rax, 0xFFFFFFFFFFFFFF00 + +.L_memcmp_mops_res_ready: + ret + +.L_dma_memcmp_mops_count_zero: + xor rax, rax + ret + +# Performance estimate (Modern x86-64, L1 cache hits): +# +# NON-OVERLAPPING FORWARD COPY PATH: +# - fast_dma_encode call: ~15-20 cycles (function call + table lookup) +# - Write mops entries: ~4-6 cycles per entry +# - Pre-read mops (conditional): ~12 cycles (if pre_count > 0) +# - Post-read mops (conditional): ~12 cycles (if post_count > 0) +# - Block src read mops: ~8-12 cycles (address calculation + write) +# - Pre-bytes copy: ~3-5 cycles per byte (if pre_count > 0, max 7 bytes) +# - Aligned qwords copy: ~1.5-2 cycles per qword (rep movsq, main data) +# - Post-bytes copy: ~3-5 cycles per byte (if post_count > 0, max 7 bytes) +# - Function overhead: ~10 cycles (push/pop, branches, return) +# +# TOTAL (best case, aligned, no pre/post): +# ~30 cycles base + ~2 cycles per qword (trace + copy) +# +# TOTAL (typical case, some alignment): +# ~50 cycles base + ~2 cycles per qword + ~4 cycles per pre/post byte +# +# OVERLAPPING BACKWARD COPY PATH: +# - Same mops overhead: ~30-50 cycles +# - std instruction: ~20-50 cycles (serializing, causes pipeline flush) +# - Backward byte copy: ~3-5 cycles per byte (rep movsb backward) +# - cld instruction: ~20-50 cycles (serializing, causes pipeline flush) +# +# TOTAL (overlap, worst case): +# ~100-150 cycles base + ~4-5 cycles per byte +# +# NOTES: +# - Assumes L1 cache hits for all memory accesses +# - rep movsq/movsb performance varies by microarchitecture +# - Actual cycles may vary ±20% depending on CPU model and memory alignment +# - Fast path (aligned, no overlap) is ~2-3x faster than overlap path + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/direct_memcmp_mtrace.asm b/emulator-asm/src/dma/direct_memcmp_mtrace.asm new file mode 100644 index 000000000..894771e41 --- /dev/null +++ b/emulator-asm/src/dma/direct_memcmp_mtrace.asm @@ -0,0 +1,238 @@ +.intel_syntax noprefix +.code64 + +# +# memcmp_mtrace - Memory comparison with mtrace recording +# +# This function performs two main tasks: +# 1. Encodes memcmp metadata (offsets, counts, comparison result) using FAST_DMA_ENCODE +# 2. Records memory trace entries for later verification/replay +# +# NOTE: The actual comparison is performed by fast_memcmp (called via tail jump). +# This function only handles the mtrace recording part. +# +# REGISTER USAGE: +# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r9, r12, r13 +# Does NOT use XMM registers (caller doesn't need to save them) +# Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) +# +# PARAMETERS (NON System V AMD64 ABI): +# rdi = ptr1 (u64) - First memory region pointer +# rsi = ptr2 (u64) - Second memory region pointer +# rdx = count (usize) - Number of bytes to compare +# [r12 + r13*8] = mtrace_ptr (u64*) - Pointer to mtrace buffer (input/output) +# +# RETURN VALUE: +# rax = comparison result (0 if equal, byte difference if not) +# +# MTRACE FORMAT (written to mtrace buffer sequentially): +# [0] = Encoded metadata (offsets, counts, cmp_result in bits 21-28) +# [1] = Pre-read value at aligned(ptr1) IF pre_count > 0 +# [1 or 2] = Post-read value at aligned(ptr1+count) IF post_count > 0 +# [...] = All aligned qwords from aligned(ptr2) to aligned(ptr2+count) +# +# The mtrace buffer captures: +# - Source data from ptr2 (for verification) +# - Comparison result encoded in metadata +# - Pre/post values for boundary alignment handling +# +# COMPARISON BEHAVIOR: +# - Does NOT modify memory (read-only operation) +# - Records all data needed to verify/replay the comparison +# - Tail-calls fast_memcmp to perform actual byte comparison +# +# PERFORMANCE: +# - FAST_DMA_ENCODE macro: ~15-20 cycles (logic + table lookup) +# - Trace writes: ~4 cycles per qword write +# - Src data copy to trace: ~1-2 cycles per qword (rep movsq) +# - Function overhead: ~10-15 cycles (branches, setup) +# +# SIDE EFFECTS: +# - Does NOT modify ptr1 or ptr2 memory (read-only comparison) +# - Modifies mtrace buffer (variable size depending on pre/post counts) +# - Advances r13 (mtrace index) + +.global direct_dma_memcmp_mtrace +.global dma_memcmp_mtrace +.global _dma_memcmp_mtrace_test +// .global direct_dma_memcmp_mtrace_with_count_check +.extern fast_memcmp + +# .extern trace_resize_request + +.include "dma_constants.inc" +.include "fast_dma_encode_macro.inc" +.section .text + +direct_dma_memcmp_mtrace: + + # First step calculate the effective count (length), because must be the length + # really used, the other length it's only used when it's send to bus + + # First step was store the original size to mtrace + 1, because the position 0 + # will used by encoded. The function fast_memcmp modifies rdx with the effective + # count + + test rdx, rdx + jz .L_dma_memcmp_mtrace_count_zero + + # store on mtrace the bus count, original, no effective + mov [r12 + r13 * 8 + 8], rdx + + call fast_memcmp + # in case of original count > 0, the effective count must be > 0 because at least + # need check one byte to see if are equals or not + + # at end the memcpy must return rax with correct value, but these information + # could be extract from encoded, this is the stragegy to avoid manage two values + # encoded and result + + mov r9, rax + and r9, 0x1FF + jz .L_fast_dma_memcmp_encode_eq + + # Non-equal case: use table with NEQ offset + FAST_DMA_ENCODE_MEMCMP FAST_ENCODE_TABLE_WO_NEQ_SIZE + + shl r9, DMA_CMP_RES_RS # 1 cycle - shift cmp_result to position (bits 21-28) + or rax, r9 # 1 cycle - combine with encoding + jmp .L_dma_memcmp_encode_done # 1 cycle + +.L_fast_dma_memcmp_encode_eq: + # Equal case: use base table (offset 0) + FAST_DMA_ENCODE_MEMCMP 0 + +.L_dma_memcmp_encode_done: + # store before a potential realloc + + mov [r12 + r13 * 8], rax + add r13, 2 + + # Check if count exceeds direct mtrace threshold + # Parameters: rdi=ptr1, rsi=ptr2, rdx=count + + cmp rdx, MAX_DMA_BYTES_DIRECT_MTRACE # 1 cycle - check threshold + ja .L_memcmp_check_dynamic_trace # 1 cycle (not taken usually) + jmp .L_memcmp_mtrace_encoded_stored # 1 cycle - fall through to main function + +.L_memcmp_check_dynamic_trace: + call check_dynamic_mtrace # expand mtrace buffer if needed + +.L_memcmp_mtrace_encoded_stored: + +.L_pre_ptr1_to_mtrace: + # If pre_count > 0, record aligned ptr1 value + test rax, DMA_PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_post_ptr1_to_mtrace # 1 cycle (predicted taken) + + # Pre-read: save qword at aligned(ptr1) for boundary handling + mov r9, rdi # 1 cycle - r9 = ptr1 + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov r9, [r9] # ~4 cycles - read qword from aligned ptr1 + mov [r12 + r13 * 8], r9 # ~4 cycles - write to mtrace + inc r13 # 1 cycle - advance mtrace index + +.L_post_ptr1_to_mtrace: + + # If post_count > 0, record aligned (ptr1+count) value + test rax, DMA_POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_ptr2_to_mtrace # 1 cycle (predicted taken) + + lea r9, [rdi + rdx - 1] # 1 cycle - r9 = ptr1 + count - 1 (last byte) + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary + mov r9, [r9] # ~4 cycles - read qword at aligned(ptr1+count) + mov [r12 + r13 * 8], r9 # ~4 cycles - write to mtrace + inc r13 # 1 cycle - advance mtrace index + +.L_ptr2_to_mtrace: + # Copy ptr2 (source) data to mtrace buffer for verification + # Total qwords = loop_count + extra_src_reads + + mov rcx, rax # 1 cycle - rcx = encoding + shr rcx, DMA_LOOP_COUNT_RS # 1 cycle - rcx = loop_count (bits 35+) + + mov r9, rax # 1 cycle - r9 = encoding + shr r9, DMA_EXTRA_SRC_READS_RS # 1 cycle - shift to extra_src_reads + and r9, 0x03 # 1 cycle - r9 = extra_src_reads (0-3) + add rcx, r9 # 1 cycle - rcx = total qwords to copy + + # Setup for rep movsq: copy aligned ptr2 data to mtrace + mov r9, rsi # 1 cycle - preserve original ptr2 + and rsi, ALIGN_MASK # 1 cycle - rsi = ptr2 aligned to 8 bytes + + push rdi # 1 cycle - save ptr1 + lea rdi, [r12 + r13 * 8] # 1 cycle - rdi = mtrace destination + add r13, rcx # 1 cycle - advance mtrace index + + rep movsq # ~1-2 cycles per qword (ERMSB optimized) + + pop rdi # 1 cycle - restore ptr1 + mov rsi, r9 # 1 cycle - restore original ptr2 + +.L_mtrace_done: + + shr rax, DMA_CMP_RES_RS + test rax, 0x100 + jnz .L_memcmp_mtrace_negative_res + and rax, 0xFF + jmp .L_memcmp_mtrace_res_ready + +.L_memcmp_mtrace_negative_res: + or rax, 0xFFFFFFFFFFFFFF00 + +.L_memcmp_mtrace_res_ready: + ret # ~5 cycles + +.L_dma_memcmp_mtrace_count_zero: + # this path used if bus count is 0 + # bus_count = 0 ==> effective_count = 0 + # bus_count > 0 ==> effective_count > 0 (at least need to check first byte) + + FAST_DMA_ENCODE_COUNT_ZERO + + mov [r12 + r13 * 8], rax # ~4 cycles - write encoding to mtrace + + # rdx contains 0, it's more fast use rdx rather immediate 0. + + mov [r12 + r13 * 8 + 8], rdx # ~4 cycles - write encoding to mtrace + + add r13, 2 # 1 cycle - advance mtrace index + xor rax, rax + ret + +# Performance estimate (Modern x86-64, L1 cache hits): +# +# NON-OVERLAPPING FORWARD COPY PATH: +# - fast_dma_encode call: ~15-20 cycles (function call + table lookup) +# - Write encoding to trace: ~4 cycles +# - Pre-value trace (conditional): ~12 cycles (if pre_count > 0) +# - Post-value trace (conditional): ~12 cycles (if post_count > 0) +# - Source data to trace: ~1.5-2 cycles per qword (rep movsq) +# - Pre-bytes copy: ~3-5 cycles per byte (if pre_count > 0, max 7 bytes) +# - Aligned qwords copy: ~1.5-2 cycles per qword (rep movsq, main data) +# - Post-bytes copy: ~3-5 cycles per byte (if post_count > 0, max 7 bytes) +# - Function overhead: ~10 cycles (push/pop, branches, return) +# +# TOTAL (best case, aligned, no pre/post): +# ~30 cycles base + ~2 cycles per qword (trace + copy) +# +# TOTAL (typical case, some alignment): +# ~50 cycles base + ~2 cycles per qword + ~4 cycles per pre/post byte +# +# OVERLAPPING BACKWARD COPY PATH: +# - Same trace overhead: ~30-50 cycles +# - std instruction: ~20-50 cycles (serializing, causes pipeline flush) +# - Backward byte copy: ~3-5 cycles per byte (rep movsb backward) +# - cld instruction: ~20-50 cycles (serializing, causes pipeline flush) +# +# TOTAL (overlap, worst case): +# ~100-150 cycles base + ~4-5 cycles per byte +# +# NOTES: +# - Assumes L1 cache hits for all memory accesses +# - rep movsq/movsb performance varies by microarchitecture +# - Actual cycles may vary ±20% depending on CPU model and memory alignment +# - Fast path (aligned, no overlap) is ~2-3x faster than overlap path + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/direct_memcpy_mops.asm b/emulator-asm/src/dma/direct_memcpy_mops.asm index 7d1d14ba7..ec10ee936 100644 --- a/emulator-asm/src/dma/direct_memcpy_mops.asm +++ b/emulator-asm/src/dma/direct_memcpy_mops.asm @@ -2,374 +2,369 @@ .code64 ################################################################################ -# memcpy_mops - Optimized version with memory ops tracing and actual copy. This -# is an variant of memcpy operation, xmemcpy operation, that it -# doesn't read the count from zisk memory. +# direct_memcpy_mops / direct_xmemcpy_mops - Memory copy with mops tracing # -# This function performs two main tasks: -# 1. Records all addresses of memory operations (read and write addresses) -# 2. Performs the actual memory copy from src to dst (with overlap handling) +# These functions perform memory copy operations while recording all memory +# operation addresses (mops) for verification. Two variants exist: +# +# - memcpy_mops: Records an EXTRA_PARAMETER_ADDR read (count comes from memory) +# - xmemcpy_mops: Extended variant where count is passed directly (no extra read) +# +# MAIN TASKS: +# 1. Encode memcpy metadata (offsets, counts, alignment flags) +# 2. Record all memory operation addresses (reads and writes) to mops buffer +# 3. Perform the actual memory copy from src to dst (with overlap handling) # # REGISTER USAGE: -# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r9, r11, r12, r13 -# Does NOT use XMM registers (caller doesn't need to save them) -# Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) +# Uses: rax, rcx, rdx, rdi, rsi, r9, r12, r13 +# Does NOT use XMM registers (caller doesn't need to save them) +# Modifies: r13 (mops index output) # -# PARAMETERS (NON System V AMD64 ABI): -# rdi -> rbx = dst (u64) - Destination address -# rsi -> rax = src (u64) - Source address -# rdx -> count (usize) - Number of bytes to copy -# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) +# PARAMETERS (non-standard ABI): +# rdi = dst (u64) - Destination address +# rsi = src (u64) - Source address +# rdx = count (usize) - Number of bytes to copy +# r12 = mops buffer base address - Base pointer to memory ops buffer +# r13 = mops buffer index - Current index (updated on return) +# +# RETURN: +# r13 = Updated mops index (number of entries written) # # MEMORY COPY BEHAVIOR: # - Handles overlapping src/dst correctly (like memmove) -# - For non-overlapping: optimized copy using pre_count/loop_count/post_count -# - For overlapping: backward byte-by-byte copy to avoid corruption +# - Non-overlapping: optimized 3-phase copy (pre/loop/post alignment) +# - Overlapping: backward byte-by-byte copy to avoid corruption ################################################################################ .global direct_dma_memcpy_mops .global direct_dma_xmemcpy_mops -.global dma_memcpy_mops -.global dma_xmemcpy_mops -.extern fast_dma_encode +.extern check_dynamic_mtrace .include "dma_constants.inc" +.include "fast_dma_encode_macro.inc" .section .text -# mov rdx, [0xA000_0F00] # save count before call -# mov rdi, rbx # rdi = dst (rbx) -# mov rsi, rax # rsi = src (rax) - -# [r12 + r13*8] = trace_ptr (u64*) - Pointer to memory trace buffer (input/output) - -# call function with standard ABI call -dma_xmemcpy_mops: - - # save registers used - push r12 # register used as mops base address - push r13 # register used as mops index - push rbx # - - mov r12, rcx - xor r13, r13 - call direct_dma_xmemcpy_mops - - mov rax, r13 - pop rbx - pop r13 - pop r12 - - ret - -# call directly from assembly without standard ABI call -# more eficient +################################################################################ +# direct_dma_xmemcpy_mops - Fast memory copy with mops (extended variant) +# +# Direct entry point for generated code (non-standard ABI). The extended +# variant receives count in rdx, so no extra memory read is recorded. +# +# PARAMETERS (non-standard ABI): +# rdi = destination address +# rsi = source address +# rdx = byte count +# r12 = mops buffer base address +# r13 = mops buffer index (input/output) +# +# RETURN: +# r13 = updated mops index +################################################################################ direct_dma_xmemcpy_mops: - # updated registers: - # r9 = no save (value_reg) - # rcx = no save (available from asm) - # rdi = no save (available from asm) - # rsi = no save (available from asm) - # r13 = with new mops index (output) - # rax = encoded + # Modified registers (no save needed - caller expects these to change): + # r9 = scratch register + # rcx = scratch register + # rdi = advanced during copy + # rsi = advanced during copy + # r13 = updated mops index (output) + # rax = encoded metadata - # Call fast_dma_encode to calculate encoding - # Parameters already in correct registers: rdi=dst, rsi=src, rdx=count - # Result will be returned in rax (encoded value) + # Encode memcpy parameters: rdi=dst, rsi=src, rdx=count + FAST_DMA_ENCODE # ~15-20 cycles - table lookup encoding - call fast_dma_encode # ~15-20 cycles - table lookup encoding - - # jmp the part of write + # Skip the EXTENDED_PARAM read entry (not needed for xmemcpy) jmp direct_dma_xmemcpy_common_entry_point - -# call function with standard ABI call -dma_memcpy_mops: - - # save registers used - push r12 # register used as mops base address - push r13 # register used as mops index - push rbx # - - mov r12, rcx - xor r13, r13 - call direct_dma_memcpy_mops - - mov rax, r13 - pop rbx - pop r13 - pop r12 - - ret - -# call directly from assembly without standard ABI call -# more eficient +################################################################################ +# direct_dma_memcpy_mops - Fast memory copy with mops (standard variant) +# +# Direct entry point for generated code (non-standard ABI). Records an extra +# memory read from EXTENDED_PARAM address because the memcpy opcode reads +# count from that location. +# +# PARAMETERS (non-standard ABI): +# rdi = destination address +# rsi = source address +# rdx = byte count +# r12 = mops buffer base address +# r13 = mops buffer index (input/output) +# +# RETURN: +# r13 = updated mops index +################################################################################ direct_dma_memcpy_mops: - # updated registers: - # r9 = no save (value_reg) - # rcx = no save (available from asm) - # rdi = no save (available from asm) - # rsi = no save (available from asm) - # r13 = with new mops index (output) - # rax = encoded - - # Call fast_dma_encode to calculate encoding - # Parameters already in correct registers: rdi=dst, rsi=src, rdx=count - # Result will be returned in rax (encoded value) - - call fast_dma_encode # ~15-20 cycles - table lookup encoding - - # add read parameter count to mops - - mov r9, (MOPS_ALIGNED_READ + EXTRA_PARAMETER_ADDR) - mov [r12 + r13 * 8], r9 - inc r13 + # Modified registers (no save needed - caller expects these to change): + # r9 = scratch register + # rcx = scratch register + # rdi = advanced during copy + # rsi = advanced during copy + # r13 = updated mops index (output) + # rax = encoded metadata + + # Encode memcpy parameters: rdi=dst, rsi=src, rdx=count + FAST_DMA_ENCODE # ~15-20 cycles - table lookup encoding + + # Record EXTENDED_PARAM read (memcpy opcode reads count from this address) + mov r9, (MOPS_ALIGNED_READ + EXTRA_PARAMETER_ADDR) # 1 cycle + mov [r12 + r13 * 8], r9 # ~4 cycles + inc r13 # 1 cycle direct_dma_xmemcpy_common_entry_point: - # check if count is zero - test rdx, rdx # compare count - jz .L_done # jump if zero + # Early exit if count is zero + test rdx, rdx # 1 cycle - check count + jz .L_done # 2 cycles (predicted) - nothing to copy + + # ========== PHASE 1: Record PRE-alignment memory operations ========== .L_pre_dst_to_mops: - # If pre_count > 0, write aligned dst value to trace - test rax, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 - jz .L_post_dst_to_mops # 2 cycles (predicted taken) + # Check if pre_count > 0 (unaligned prefix bytes to copy) + test rax, DMA_PRE_COUNT_MASK # 1 cycle - check pre_count bits + jz .L_post_dst_to_mops # 2 cycles (predicted) - skip if aligned .L_pre_is_active: - # Branch with pre_count > 0: save original dst value before it's overwritten - mov r9, MOPS_ALIGNED_READ # r9 = flags aligned read - add r9, rdi # 1 cycle - get original dst + # Pre-alignment read: record dst read (original value before overwrite) + mov r9, MOPS_ALIGNED_READ # 1 cycle - read operation flag + add r9, rdi # 1 cycle - add dst address and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary - mov [r12 + r13 * 8], r9 # ~4 cycles - write dst pre-address to trace + mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry - test rax, DOUBLE_SRC_PRE_MASK - jnz .L_pre_double_src_to_mops + # Check if source spans two qwords (unaligned causing double read) + test rax, DMA_DOUBLE_SRC_PRE_MASK # 1 cycle + jnz .L_pre_double_src_to_mops # 2 cycles (predicted) .L_pre_single_src_to_mops: - - mov r9, MOPS_ALIGNED_READ # r9 = flags aligned read - add r9, rsi # 1 cycle - get original src + # Source fits in single qword + mov r9, MOPS_ALIGNED_READ # 1 cycle - single read flag + add r9, rsi # 1 cycle - add src address and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary - mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address - jmp .L_pre_src_inc_mops_index + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write mops entry + jmp .L_pre_src_inc_mops_index # 2 cycles .L_pre_double_src_to_mops: - - mov r9, MOPS_ALIGNED_READ_2W # r9 = flags double read - add r9, rsi # 1 cycle - get original src + # Source spans two qwords (needs double read) + mov r9, MOPS_ALIGNED_READ_2W # 1 cycle - double read flag + add r9, rsi # 1 cycle - add src address and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary - mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write mops entry .L_pre_src_inc_mops_index: - add r13, 2 # add 2 (pre-write, block single/dual src) + add r13, 2 # 1 cycle - advance index (dst + src entries) -.L_post_dst_to_mops: + # ========== PHASE 2: Record POST-alignment memory operations ========== - # If post_count > 0, write aligned (dst+count) value to trace - test rax, POST_COUNT_MASK # 1 cycle - check if post_count > 0 - jz .L_src_to_mops # 2 cycles (predicted taken) - skip to src copy +.L_post_dst_to_mops: + # Check if post_count > 0 (unaligned suffix bytes to copy) + test rax, DMA_POST_COUNT_MASK # 1 cycle - check post_count bits + jz .L_src_to_mops # 2 cycles (predicted) - skip if no suffix .L_post_is_active: - mov rcx, MOPS_ALIGNED_READ # rcx = flags aligned read - lea r9, [rdi + rdx - 1] # 1 cycle - r9 = dst + count - 1 (last dst byte) + # Post-alignment read: record dst read at end of copy region + mov rcx, MOPS_ALIGNED_READ # 1 cycle - read operation flag + lea r9, [rdi + rdx - 1] # 1 cycle - r9 = last dst byte address and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary - add r9, rcx # 1 cycle - r9 mops with dst aligned address - mov [r12 + r13 * 8], r9 # ~4 cycles - write dst post-value to trace + add r9, rcx # 1 cycle - add mops flags + mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry - mov r9, rax - shr r9, PRE_AND_LOOP_BYTES_RS - add r9, rsi - and r9, ALIGN_MASK + # Calculate source address for post-alignment bytes + mov r9, rax # 1 cycle + shr r9, DMA_PRE_AND_LOOP_BYTES_RS # 1 cycle - extract pre+loop byte offset + add r9, rsi # 1 cycle - add to source + and r9, ALIGN_MASK # 1 cycle - align to 8-byte boundary - test rax, DOUBLE_SRC_POST_MASK - jnz .L_post_double_src_to_mops + # Check if source spans two qwords + test rax, DMA_DOUBLE_SRC_POST_MASK # 1 cycle + jnz .L_post_double_src_to_mops # 2 cycles (predicted) .L_post_single_src_to_mops: - - mov rcx, MOPS_ALIGNED_READ # r9 = flags aligned read - add r9, rcx # 1 cycle - get original src - mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address - jmp .L_post_src_inc_mops_index + # Source fits in single qword + mov rcx, MOPS_ALIGNED_READ # 1 cycle - single read flag + add r9, rcx # 1 cycle - add mops flags + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write mops entry + jmp .L_post_src_inc_mops_index # 2 cycles .L_post_double_src_to_mops: - - mov rcx, MOPS_ALIGNED_READ_2W # r9 = flags aligned read - add r9, rcx # 1 cycle - get original src - mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write src address + # Source spans two qwords (needs double read) + mov rcx, MOPS_ALIGNED_READ_2W # 1 cycle - double read flag + add r9, rcx # 1 cycle - add mops flags + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write mops entry .L_post_src_inc_mops_index: - add r13, 2 # add 2 (pre-write, block single/dual src) + add r13, 2 # 1 cycle - advance index (dst + src entries) + + # ========== PHASE 3: Record LOOP (aligned bulk) memory operations ========== .L_src_to_mops: - mov rcx, rax # 1 cycle - rcx = encoded - shr rcx, LOOP_COUNT_RS # 1 cycle - rcx = loop (32 bits) - jz .L_save_dst_with_loop_count_zero - shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - rcx = loop | 0 (4 bits) | (32 bits) + # Extract loop_count (number of aligned qwords to copy) + mov rcx, rax # 1 cycle - rcx = encoded + shr rcx, DMA_LOOP_COUNT_RS # 1 cycle - rcx = loop_count + jz .L_save_dst_with_loop_count_zero # 2 cycles - no aligned bulk + shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - format for mops block entry - test rax, UNALIGNED_DST_SRC_MASK - jnz .L_src_extra_for_unaligned_loop + # Check if source is unaligned (needs extra word per iteration) + test rax, DMA_UNALIGNED_DST_SRC_MASK # 1 cycle + jnz .L_src_extra_for_unaligned_loop # 2 cycles (predicted) - mov r9, MOPS_ALIGNED_BLOCK_READ # 1 cycle - r9 = read block - jmp .L_src_block_before_address + mov r9, MOPS_ALIGNED_BLOCK_READ # 1 cycle - aligned block read flag + jmp .L_src_block_before_address # 2 cycles .L_src_extra_for_unaligned_loop: - mov r9, MOPS_ALIGNED_BLOCK_READ + MOPS_BLOCK_ONE_WORD + # Unaligned source requires one extra word per block + mov r9, MOPS_ALIGNED_BLOCK_READ + MOPS_BLOCK_ONE_WORD # 1 cycle .L_src_block_before_address: - add r9, rcx - test rax, SRC64_INC_BY_PRE_MASK - jnz .L_src_incr_by_pre - add r9, rsi # 1 cycle - rcx = first block src address - jmp .L_src_to_mops_ready + add r9, rcx # 1 cycle - add block word count + test rax, DMA_SRC64_INC_BY_PRE_MASK # 1 cycle - check pre-alignment offset + jnz .L_src_incr_by_pre # 2 cycles (predicted) + add r9, rsi # 1 cycle - use base src address + jmp .L_src_to_mops_ready # 2 cycles .L_src_incr_by_pre: - lea r9, [rsi + r9 + 8] + # Source starts one qword after base (due to pre-alignment) + lea r9, [rsi + r9 + 8] # 1 cycle .L_src_to_mops_ready: - and r9, ALIGN_MASK - mov [r12 + r13 * 8], r9 # ~4 cycles - write first block src read address - inc r13 + and r9, ALIGN_MASK # 1 cycle - align address + mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry + inc r13 # 1 cycle .L_save_dst_addr_reusing_rcx: + # Record destination write block + # Strategy: treat all writes as one block (cannot write same address twice per step) - mov r9, rax # rcx = encoded - and r9, PRE_WRITES_MASK # rcx = pre_writes mask - shl r9, PRE_WRITES_TO_MOPS_BLOCK # rcx = pre_writes offset (with correct shift) - add r9, rcx - add r9, rdi + mov r9, rax # 1 cycle - r9 = encoded + and r9, DMA_PRE_WRITES_MASK # 1 cycle - extract pre_writes count + shl r9, PRE_WRITES_TO_MOPS_BLOCK # 1 cycle - format for mops block + add r9, rcx # 1 cycle - add loop block count + add r9, rdi # 1 cycle - add dst base address - mov rcx, MOPS_ALIGNED_BLOCK_WRITE - add r9, rcx - and r9, ALIGN_MASK + mov rcx, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - write block flag + add r9, rcx # 1 cycle - add mops flags + and r9, ALIGN_MASK # 1 cycle - align address - mov [r12 + r13 * 8], r9 - inc r13 - jmp .L_mops_done + mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry + inc r13 # 1 cycle + jmp .L_mops_done # 2 cycles .L_save_dst_with_loop_count_zero: - mov r9, rax # rcx = encoded - and r9, PRE_WRITES_MASK # rcx = pre_writes mask - shl r9, PRE_WRITES_TO_MOPS_BLOCK # rcx = pre_writes offset (with correct shift) - add r9, rdi + # No loop iterations - pre/post writes may be consecutive (single block) + + mov r9, rax # 1 cycle - r9 = encoded + and r9, DMA_PRE_WRITES_MASK # 1 cycle - extract pre_writes count + shl r9, PRE_WRITES_TO_MOPS_BLOCK # 1 cycle - format for mops block + add r9, rdi # 1 cycle - add dst base address - mov rcx, MOPS_ALIGNED_BLOCK_WRITE - add r9, rcx - and r9, ALIGN_MASK + mov rcx, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - write block flag + add r9, rcx # 1 cycle - add mops flags + and r9, ALIGN_MASK # 1 cycle - align address - mov [r12 + r13 * 8], r9 - inc r13 + mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry + inc r13 # 1 cycle + + # ========== PHASE 4: Perform actual memory copy ========== .L_mops_done: # Check for memory overlap to decide copy direction - # NOTE: rdi and rsi contain their ORIGINAL values (not modified in mops section) - # Overlap exists if: src < dst < src+count (forward overlap) + # Overlap exists if: src < dst < src+count (forward copy would corrupt) cmp rdi, rsi # 1 cycle - compare dst with src - jb .L_copy_forward # 2 cycles (predicted) - dst < src, no overlap + jb .L_copy_forward # 2 cycles (predicted) - dst < src, safe lea r9, [rsi + rdx] # 1 cycle - r9 = src + count cmp rdi, r9 # 1 cycle - compare dst with (src+count) - jae .L_copy_forward # 2 cycles (predicted) - dst >= src+count, no overlap + jae .L_copy_forward # 2 cycles (predicted) - dst >= src+count, safe # Overlap detected (src < dst < src+count), must copy backward - # Setup: rsi = src+count-1, rdi = dst+count-1, rcx = count - # Uses ORIGINAL rsi and rdi values (not modified during mops recording) + # Setup pointers to end of regions for backward copy - lea rsi, [rsi + rdx - 1] # 1 cycle - rsi = src + count - 1 (from original) - lea rdi, [rdi + rdx - 1] # 1 cycle - rdi = dst + count - 1 (from original) - mov rcx, rdx # 1 cycle - rcx = count + mov rax, rdi + lea rsi, [rsi + rdx - 1] # 1 cycle - rsi = last src byte + lea rdi, [rdi + rdx - 1] # 1 cycle - rdi = last dst byte + mov rcx, rdx # 1 cycle - rcx = byte count - std # ~20-50 cycles - set DF (serializing, pipeline flush) - rep movsb # ~3-5 cycles per byte (backward copy, slower than forward) - cld # ~20-50 cycles - clear DF (serializing, pipeline flush) + std # ~20-50 cycles - set direction flag (backward) + rep movsb # ~3-5 cycles/byte (backward, slower) + cld # ~20-50 cycles - clear direction flag - ret # ~5 cycles + ret # ~3 cycles .L_copy_forward: - # No overlap detected, perform optimized forward copy - cmp rdx, 16 # 1 cycle - check if count >= 16 (worth alignment) - jae .L_copy_forward_pre # 2 cycles (predicted) - use 3-phase aligned copy + # No overlap - perform optimized forward copy + // cmp rdx, 16 # 1 cycle - check if count >= 16 + // jae .L_copy_forward_pre # 2 cycles (predicted) - use 3-phase copy - # Small copy (count < 16): copy all bytes directly + mov rax, rdi + # Small copy (count < 16): direct byte copy mov rcx, rdx # 1 cycle - rcx = count - rep movsb # ~3-5 cycles per byte (unaligned small copy) - - ret # ~5 cycles + rep movsb # ~3-5 cycles/byte + ret # ~3 cycles +/* .L_copy_forward_pre: - # Copy in 3 phases: pre-alignment bytes, aligned qwords, post-alignment bytes - # If pre_count > 0, copy unaligned prefix bytes - test rax, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 - jz .L_copy_forward_loop # 2 cycles (predicted) + # 3-phase copy: pre-alignment bytes, aligned qwords, post-alignment bytes + + # Phase A: Pre-alignment bytes (0-7 bytes to reach 8-byte alignment) + test rax, DMA_PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + jz .L_copy_forward_loop # 2 cycles (predicted) - # Extract and copy pre_count bytes (1-7 bytes to reach alignment) mov rcx, rax # 1 cycle - and rcx, PRE_COUNT_MASK # 1 cycle - rcx = pre_count (bits 0-3) - - rep movsb # ~3-5 cycles per byte - # rsi, rdi now 8-byte aligned + and rcx, DMA_PRE_COUNT_MASK # 1 cycle - rcx = pre_count + rep movsb # ~3-5 cycles/byte - rsi/rdi now aligned .L_copy_forward_loop: - # Copy aligned qwords (main bulk of data) + # Phase B: Aligned qwords (bulk data transfer) mov rcx, rax # 1 cycle - shr rcx, LOOP_COUNT_RS # 1 cycle - rcx = loop_count (bits 32-63) - rep movsq # ~1.5-2 cycles per qword (aligned, optimized) - # rsi, rdi advanced by loop_count * 8 + shr rcx, DMA_LOOP_COUNT_RS # 1 cycle - rcx = loop_count + rep movsq # ~1.5-2 cycles/qword (optimized) .L_check_forward_post: + # Phase C: Post-alignment bytes (0-7 remaining bytes) + test rax, DMA_POST_COUNT_MASK # 1 cycle - check if post_count > 0 + jz .L_done # 2 cycles (predicted) - # If post_count > 0, copy remaining unaligned suffix bytes - test rax, POST_COUNT_MASK # 1 cycle - check if post_count > 0 - jz .L_done # 2 cycles (predicted) - - # Extract and copy post_count bytes (1-7 bytes after aligned data) mov rcx, rax # 1 cycle - shr rcx, POST_COUNT_RS # 1 cycle - shift post_count to position - and rcx, 0x07 # 1 cycle - rcx = post_count (bits 43-45) - - rep movsb # ~3-5 cycles per byte - # rsi, rdi now point past end of data - + shr rcx, DMA_POST_COUNT_RS # 1 cycle - extract post_count + and rcx, 0x0F # 1 cycle - mask to 3 bits + rep movsb # ~3-5 cycles/byte +*/ .L_done: - ret # ~5 cycles + mov rax, rdi + ret # ~3 cycles -# Performance estimate (Modern x86-64, L1 cache hits): -# -# NON-OVERLAPPING FORWARD COPY PATH: -# - fast_dma_encode call: ~15-20 cycles (function call + table lookup) -# - Write mops entries: ~4-6 cycles per entry -# - Pre-read mops (conditional): ~12 cycles (if pre_count > 0) -# - Post-read mops (conditional): ~12 cycles (if post_count > 0) -# - Block src read mops: ~8-12 cycles (address calculation + write) -# - Pre-bytes copy: ~3-5 cycles per byte (if pre_count > 0, max 7 bytes) -# - Aligned qwords copy: ~1.5-2 cycles per qword (rep movsq, main data) -# - Post-bytes copy: ~3-5 cycles per byte (if post_count > 0, max 7 bytes) -# - Function overhead: ~10 cycles (push/pop, branches, return) +################################################################################ +# PERFORMANCE ESTIMATES (Modern x86-64, L1 cache hits) # -# TOTAL (best case, aligned, no pre/post): -# ~30 cycles base + ~2 cycles per qword (trace + copy) +# NON-OVERLAPPING FORWARD COPY: +# - FAST_DMA_ENCODE macro: ~15-20 cycles (table lookup) +# - EXTENDED_PARAM entry: ~6 cycles (memcpy variant only) +# - Pre-alignment mops: ~12 cycles (if pre_count > 0) +# - Post-alignment mops: ~12 cycles (if post_count > 0) +# - Block src/dst mops: ~10-15 cycles (address calc + writes) +# - Pre-bytes copy: ~3-5 cycles/byte (max 7 bytes) +# - Aligned qwords copy: ~1.5-2 cycles/qword (rep movsq) +# - Post-bytes copy: ~3-5 cycles/byte (max 7 bytes) # -# TOTAL (typical case, some alignment): -# ~50 cycles base + ~2 cycles per qword + ~4 cycles per pre/post byte +# Best case (aligned, no pre/post): ~35 cycles + ~2 cycles/qword +# Typical case (some alignment): ~55 cycles + ~2 cycles/qword # -# OVERLAPPING BACKWARD COPY PATH: -# - Same mops overhead: ~30-50 cycles -# - std instruction: ~20-50 cycles (serializing, causes pipeline flush) -# - Backward byte copy: ~3-5 cycles per byte (rep movsb backward) -# - cld instruction: ~20-50 cycles (serializing, causes pipeline flush) +# OVERLAPPING BACKWARD COPY: +# - Same mops overhead: ~35-55 cycles +# - std instruction: ~20-50 cycles (pipeline flush) +# - Backward byte copy: ~3-5 cycles/byte (rep movsb) +# - cld instruction: ~20-50 cycles (pipeline flush) # -# TOTAL (overlap, worst case): -# ~100-150 cycles base + ~4-5 cycles per byte +# Worst case: ~100-150 cycles + ~4-5 cycles/byte # # NOTES: -# - Assumes L1 cache hits for all memory accesses -# - rep movsq/movsb performance varies by microarchitecture -# - Actual cycles may vary ±20% depending on CPU model and memory alignment -# - Fast path (aligned, no overlap) is ~2-3x faster than overlap path +# - Assumes L1 cache hits for all memory accesses +# - rep movsq/movsb performance varies by microarchitecture +# - Actual cycles may vary ±20% depending on CPU model +# - Forward aligned path is ~2-3x faster than backward path +################################################################################ # Mark stack as non-executable (required by modern linkers) .section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/direct_memcpy_mtrace.asm b/emulator-asm/src/dma/direct_memcpy_mtrace.asm index 99c6c8497..4e4fd97dd 100644 --- a/emulator-asm/src/dma/direct_memcpy_mtrace.asm +++ b/emulator-asm/src/dma/direct_memcpy_mtrace.asm @@ -52,153 +52,17 @@ # - Preserves direction flag (cld called after any std) .global direct_dma_memcpy_mtrace -.global dma_memcpy_mtrace .global direct_dma_memcpy_mtrace_with_count_check .extern fast_dma_encode .extern trace_address_threshold # .extern trace_resize_request -.ifdef DEBUG -.section .data -.align 8 - dma_check_case: .quad 0 - dma_check_step: .quad 0 - dma_check_aux: .quad 0 - dma_check_threshold: .quad 0 -.endif - .include "dma_constants.inc" +.include "fast_dma_encode_macro.inc" .section .text -.set R_MT_INDEX, r13 -.set R_MT_ADDR, r12 -.set R_STEP, r14 -.set R_AUX, r9 -.set R_AUX2, rcx # NOTE: used by rep -.set R_SRC, rsi # NOTE: used by rep -.set R_DST, rdi # NOTE: used by rep -.set R_COUNT, rdx -.set R_ENCODE, rax - -dma_memcpy_mtrace: - push R_MT_ADDR # ~3 cycles - save callee-saved register - push R_MT_INDEX # ~3 cycles - save callee-saved register - push R_AUX # ~3 cycles - save callee-saved register - push rbx # ~3 cycles - save callee-saved register - - mov R_MT_ADDR, R_COUNT # 1 cycle - setup trace address from count - xor R_MT_INDEX, R_MT_INDEX # 1 cycle - initialize trace index to 0 - call direct_dma_memcpy_mtrace # ~5 cycles + function cost - - mov R_ENCODE, R_MT_INDEX # 1 cycle - return trace index in R_ENCODE - pop rbx # ~3 cycles - restore register - pop R_AUX # ~3 cycles - restore register - pop R_MT_INDEX # ~3 cycles - restore register - pop R_MT_ADDR # ~3 cycles - restore register - - ret # ~5 cycles - -.L_memcpy_check_mtrace_available: - - # trace_address_threshold containt the address "limit" before call _realloc_trace - # trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE - - # calculate bytes of mtrace used and verify if throw the limit - -.ifdef DEBUG - mov qword ptr [dma_check_case], 1 -.endif - - lea R_AUX, [R_MT_ADDR + 8 * R_MT_INDEX] # 1 cycle - calculate address mtrace - lea R_AUX, [R_AUX + R_COUNT + MAX_DMA_MT_MARGIN] # 1 cycle - calculate current mtrace bytes usage - sub R_AUX, [trace_address_threshold] # ~4 cycles - bytes over threshold (can be negative) - jc .L_memcpy_mtrace_continue # 2 cycles (predicted) - negative means space available - - # check if bytes over threshold are usual for current situation on inside chunk - # R_STEP contain number the steps to end of chunk, we need number the steps consumed - -.ifdef DEBUG - mov qword ptr [dma_check_case], 2 - mov [dma_check_step], R_STEP - mov [dma_check_aux], R_AUX -.endif - - mov R_AUX2, CHUNK_SIZE # 1 cycle - load chunk size constant - sub R_AUX2, R_STEP # 1 cycle - calculate steps consumed in chunk - imul R_AUX2, MAX_BYTES_MTRACE_STEP # ~3 cycles - bytes expected for consumed steps - cmp R_AUX2, R_AUX # 1 cycle - compare expected vs actual - jae .L_memcpy_mtrace_continue # 2 cycles (predicted) - expected >= actual, ok - - - # at this point we need to increase trace, registers R_ENCODE, R_AUX no need to save. -.ifdef DEBUG - mov qword ptr [dma_check_case], 3 - mov R_AUX, [trace_address_threshold] - mov [dma_check_threshold], R_AUX -.endif - - # mov qword ptr [trace_resize_request], R_COUNT - - push R_COUNT # ~3 cycles - save general purpose registers - push r8 # ~3 cycles - push r10 # ~3 cycles - push r11 # ~3 cycles - push R_SRC # ~3 cycles - push R_DST # ~3 cycles - - # IMPORTANT: inside call means unaligned to 16 bits - - sub rsp, 16*16 + 8 # 1 cycle - allocate stack space for 16 XMM registers - - movaps [rsp + 0*16], xmm0 # ~4 cycles - save XMM registers (aligned stores) - movaps [rsp + 1*16], xmm1 # ~4 cycles - movaps [rsp + 2*16], xmm2 # ~4 cycles - movaps [rsp + 3*16], xmm3 # ~4 cycles - movaps [rsp + 4*16], xmm4 # ~4 cycles - movaps [rsp + 5*16], xmm5 # ~4 cycles - movaps [rsp + 6*16], xmm6 # ~4 cycles - movaps [rsp + 7*16], xmm7 # ~4 cycles - movaps [rsp + 8*16], xmm8 # ~4 cycles - movaps [rsp + 9*16], xmm9 # ~4 cycles - movaps [rsp + 10*16], xmm10 # ~4 cycles - movaps [rsp + 11*16], xmm11 # ~4 cycles - movaps [rsp + 12*16], xmm12 # ~4 cycles - movaps [rsp + 13*16], xmm13 # ~4 cycles - movaps [rsp + 14*16], xmm14 # ~4 cycles - movaps [rsp + 15*16], xmm15 # ~4 cycles - - call _realloc_trace # ~5 cycles + function cost (~100-500 cycles) - - movaps xmm0, [rsp + 0*16] # ~4 cycles - restore XMM registers (aligned loads) - movaps xmm1, [rsp + 1*16] # ~4 cycles - movaps xmm2, [rsp + 2*16] # ~4 cycles - movaps xmm3, [rsp + 3*16] # ~4 cycles - movaps xmm4, [rsp + 4*16] # ~4 cycles - movaps xmm5, [rsp + 5*16] # ~4 cycles - movaps xmm6, [rsp + 6*16] # ~4 cycles - movaps xmm7, [rsp + 7*16] # ~4 cycles - movaps xmm8, [rsp + 8*16] # ~4 cycles - movaps xmm9, [rsp + 9*16] # ~4 cycles - movaps xmm10, [rsp + 10*16] # ~4 cycles - movaps xmm11, [rsp + 11*16] # ~4 cycles - movaps xmm12, [rsp + 12*16] # ~4 cycles - movaps xmm13, [rsp + 13*16] # ~4 cycles - movaps xmm14, [rsp + 14*16] # ~4 cycles - movaps xmm15, [rsp + 15*16] # ~4 cycles - - add rsp, 16*16 +8 # 1 cycle - deallocate stack space - - pop R_DST # ~3 cycles - restore general purpose registers - pop R_SRC # ~3 cycles - pop r11 # ~3 cycles - pop r10 # ~3 cycles - pop r8 # ~3 cycles - pop R_COUNT # ~3 cycles - - jmp .L_memcpy_mtrace_continue - direct_dma_memcpy_mtrace_with_count_check: # Call fast_dma_encode to calculate encoding @@ -206,23 +70,26 @@ direct_dma_memcpy_mtrace_with_count_check: # Result will be returned in R_ENCODE (encoded value) cmp R_COUNT, MAX_DMA_BYTES_DIRECT_MTRACE # 1 cycle - check if count exceeds direct threshold - ja .L_memcpy_check_mtrace_available # 2 cycles (not taken usually) - large count, check trace space + ja .L_memcpy_check_dynamic_trace # 2 cycles (not taken usually) - large count, check trace space + jmp direct_dma_memcpy_mtrace + +.L_memcpy_check_dynamic_trace: + call check_dynamic_mtrace -.L_memcpy_mtrace_continue: direct_dma_memcpy_mtrace: # Call fast_dma_encode to calculate encoding # Parameters already in correct registers: R_DST=dst, R_SRC=src, R_COUNT=count # Result will be returned in R_ENCODE (encoded value) - call fast_dma_encode # ~15-20 cycles - table lookup encoding + FAST_DMA_ENCODE # ~15-18 cycles - table lookup encoding mov [R_MT_ADDR + R_MT_INDEX * 8], R_ENCODE # ~4 cycles - write encoded result to mem trace inc R_MT_INDEX # 1 cycle - advance R_MT_INDEX (mem trace index) .L_pre_dst_to_mtrace: # If pre_count > 0, write aligned dst value to trace - test R_ENCODE, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 + test R_ENCODE, DMA_PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 jz .L_post_dst_to_mtrace # 2 cycles (predicted taken) # Branch with pre_count > 0: save original dst value before it's overwritten @@ -235,7 +102,7 @@ direct_dma_memcpy_mtrace: .L_post_dst_to_mtrace: # If post_count > 0, write aligned (dst+count) value to trace - test R_ENCODE, POST_COUNT_MASK # 1 cycle - check if post_count > 0 + test R_ENCODE, DMA_POST_COUNT_MASK # 1 cycle - check if post_count > 0 jz .L_src_to_mtrace # 2 cycles (predicted taken) - skip to src copy lea R_AUX, [R_DST + R_COUNT - 1] # 1 cycle - R_AUX = dst + count - 1 (last dst byte) @@ -249,10 +116,10 @@ direct_dma_memcpy_mtrace: # Total qwords = loop_count (bits 0-31) + extra_src_reads (bits 48-50) mov R_AUX2, R_ENCODE # 1 cycle - R_AUX2 = encoded - shr R_AUX2, LOOP_COUNT_RS # 1 cycle - R_AUX2 = loop_count (bits 32-63) + shr R_AUX2, DMA_LOOP_COUNT_RS # 1 cycle - R_AUX2 = loop_count (bits 32-63) mov R_AUX, R_ENCODE # 1 cycle - R_AUX = encoded - shr R_AUX, EXTRA_SRC_READS_RS # 1 cycle - shift extra_src_reads to position + shr R_AUX, DMA_EXTRA_SRC_READS_RS # 1 cycle - shift extra_src_reads to position and R_AUX, 0x03 # 1 cycle - R_AUX = extra_src_reads (bits 48-50) add R_AUX2, R_AUX # 1 cycle - R_AUX2 = total qwords to copy @@ -282,7 +149,8 @@ direct_dma_memcpy_mtrace: # Overlap detected (src < dst < src+count), must copy backward # Setup: R_SRC = src+count-1, R_DST = dst+count-1, R_AUX2 = count # Uses ORIGINAL R_SRC and R_DST values (restored from R_AUX and stack) - + + mov rax, R_DST lea R_SRC, [R_SRC + R_COUNT - 1] # 1 cycle - R_SRC = src + count - 1 (from original) lea R_DST, [R_DST + R_COUNT - 1] # 1 cycle - R_DST = dst + count - 1 (from original) mov R_AUX2, R_COUNT # 1 cycle - R_AUX2 = count @@ -295,53 +163,55 @@ direct_dma_memcpy_mtrace: .L_copy_forward: # No overlap detected, perform optimized forward copy - cmp R_COUNT, 16 # 1 cycle - check if count >= 16 (worth alignment) - jae .L_copy_forward_pre # 2 cycles (predicted) - use 3-phase aligned copy + // cmp R_COUNT, 16 # 1 cycle - check if count >= 16 (worth alignment) + // jae .L_copy_forward_pre # 2 cycles (predicted) - use 3-phase aligned copy # Small copy (count < 16): copy all bytes directly + mov rax, R_DST mov R_AUX2, R_COUNT # 1 cycle - R_AUX2 = count rep movsb # ~3-5 cycles per byte (unaligned small copy) ret # ~5 cycles -.L_copy_forward_pre: - # Copy in 3 phases: pre-alignment bytes, aligned qwords, post-alignment bytes +# .L_copy_forward_pre: +# # Copy in 3 phases: pre-alignment bytes, aligned qwords, post-alignment bytes - # If pre_count > 0, copy unaligned prefix bytes +# # If pre_count > 0, copy unaligned prefix bytes - test R_ENCODE, PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 - jz .L_copy_forward_loop # 2 cycles (predicted) +# test R_ENCODE, DMA_PRE_COUNT_MASK # 1 cycle - check if pre_count > 0 +# jz .L_copy_forward_loop # 2 cycles (predicted) - # Extract and copy pre_count bytes (1-7 bytes to reach alignment) +# # Extract and copy pre_count bytes (1-7 bytes to reach alignment) - mov R_AUX2, R_ENCODE # 1 cycle - and R_AUX2, PRE_COUNT_MASK # 1 cycle - R_AUX2 = pre_count (bits 0-3) +# mov R_AUX2, R_ENCODE # 1 cycle +# and R_AUX2, DMA_PRE_COUNT_MASK # 1 cycle - R_AUX2 = pre_count (bits 0-3) - rep movsb # ~3-5 cycles per byte - # R_SRC, R_DST now 8-byte aligned +# rep movsb # ~3-5 cycles per byte +# # R_SRC, R_DST now 8-byte aligned -.L_copy_forward_loop: - # Copy aligned qwords (main bulk of data) - mov R_AUX2, R_ENCODE # 1 cycle - shr R_AUX2, LOOP_COUNT_RS # 1 cycle - R_AUX2 = loop_count (bits 32-63) - rep movsq # ~1.5-2 cycles per qword (aligned, optimized) - # R_SRC, R_DST advanced by loop_count * 8 +# .L_copy_forward_loop: +# # Copy aligned qwords (main bulk of data) +# mov R_AUX2, R_ENCODE # 1 cycle +# shr R_AUX2, DMA_LOOP_COUNT_RS # 1 cycle - R_AUX2 = loop_count (bits 32-63) +# rep movsq # ~1.5-2 cycles per qword (aligned, optimized) +# # R_SRC, R_DST advanced by loop_count * 8 -.L_check_forward_post: +# .L_check_forward_post: - # If post_count > 0, copy remaining unaligned suffix bytes - test R_ENCODE, POST_COUNT_MASK # 1 cycle - check if post_count > 0 - jz .L_done # 2 cycles (predicted) +# # If post_count > 0, copy remaining unaligned suffix bytes +# test R_ENCODE, DMA_POST_COUNT_MASK # 1 cycle - check if post_count > 0 +# jz .L_done # 2 cycles (predicted) - # Extract and copy post_count bytes (1-7 bytes after aligned data) - mov R_AUX2, R_ENCODE # 1 cycle - shr R_AUX2, POST_COUNT_RS # 1 cycle - shift post_count to position - and R_AUX2, 0x07 # 1 cycle - R_AUX2 = post_count (bits 43-45) +# # Extract and copy post_count bytes (1-7 bytes after aligned data) +# mov R_AUX2, R_ENCODE # 1 cycle +# shr R_AUX2, DMA_POST_COUNT_RS # 1 cycle - shift post_count to position +# and R_AUX2, 0x0F # 1 cycle - R_AUX2 = post_count (bits 43-45) - rep movsb # ~3-5 cycles per byte - # R_SRC, R_DST now point past end of data +# rep movsb # ~3-5 cycles per byte +# # R_SRC, R_DST now point past end of data .L_done: + mov rax, rdi ret # ~5 cycles # Performance estimate (Modern x86-64, L1 cache hits): diff --git a/emulator-asm/src/dma/direct_memset_mops.asm b/emulator-asm/src/dma/direct_memset_mops.asm new file mode 100644 index 000000000..9ceff6e36 --- /dev/null +++ b/emulator-asm/src/dma/direct_memset_mops.asm @@ -0,0 +1,256 @@ +.intel_syntax noprefix +.code64 + +################################################################################ +# direct_dma_xmemset_mops - Memory set with mops (memory operation) tracing +# +# This function fills a memory region with a byte value while recording all +# memory operation addresses to the mops buffer for verification. +# +# MAIN TASKS: +# 1. Record memory operation addresses (pre-reads for partial qwords, writes) +# 2. Perform the actual memset operation (via fast_memset) +# +# REGISTER USAGE: +# Uses: rax, rcx, rdx, rdi, rsi, r9, r12, r13 +# Does NOT use XMM registers (caller doesn't need to save them) +# Modifies: r13 (mops index output) +# +# PARAMETERS (non-standard ABI): +# rdi = dst (u64) - Destination address to fill +# rsi = value (u8 in low byte) - Byte value to set (0-255) +# rdx = count (usize) - Number of bytes to set +# r12 = mops buffer base address - Base pointer to mops buffer +# r13 = mops buffer index - Current index (updated on return) +# +# RETURN: +# r13 = Updated mops index +# +# BRANCHES: +# FAST: dst aligned + count multiple of 8 → only write block entry +# BRANCH 1: dst aligned + count NOT multiple of 8 → 1 pre-read (post) + write +# BRANCH 2.1: dst unaligned + fits single range → 1 pre-read (pre) + write +# BRANCH 2.2: dst unaligned + spans qwords → 2 pre-reads (pre/post) + write +################################################################################ + +.global direct_dma_xmemset_mops +.extern fast_memset + +.include "dma_constants.inc" + +.section .text + +################################################################################ +# direct_dma_xmemset_mops - Direct entry point (non-standard ABI) +# +# Called directly from generated assembly code without ABI overhead. +# More efficient when caller manages register preservation. +# +# PARAMETERS: +# rdi = destination address +# rsi = byte value (0-255) +# rdx = byte count +# r12 = mops buffer base +# r13 = mops buffer index (input/output) +################################################################################ + +direct_dma_xmemset_mops: + + # Modified registers (caller must handle): + # r9 = scratch for mops address calculation + # rcx = scratch for calculations + # r13 = mops index (updated on return) + + # Early exit if count = 0 + test rdx, rdx + jz .L_xmemset_mops_done + + # Check if dst is 8-byte aligned + test rdi, 0x7 + jnz .L_xmemset_mops_rdi_unaligned + + # Check if count is multiple of 8 + test rdx, 0x07 + jnz .L_memset_mops_count_remain + + # ========== FAST PATH ========== + # dst is aligned AND count is multiple of 8 + # => No pre-reads needed, only one write block entry + + mov rax, rdx + shr rax, 3 # 1 cycle - rax = count / 8 (qwords) + shl rax, MOPS_BLOCK_WORDS_RS # 1 cycle - format for mops block + mov r9, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - write block flag + add r9, rax # 1 cycle - add qword count + add r9, rdi # 1 cycle - add dst (already aligned) + + mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry + inc r13 # 1 cycle - advance mops index + + jmp fast_memset # tail call to fast_memset + + # fast_memset "execute" the return, memset set rax = rdi + + # ========== BRANCH 1 ========== + # dst aligned, count NOT multiple of 8 + # => 1 pre-read (post qword) + 1 write block + +.L_memset_mops_count_remain: + + # Calculate qwords needed: ceil(count / 8) = (count + 7) / 8 + lea r9, [rdx + 7] + shr r9, 3 # 1 cycle - r9 = qwords to write + + # BRANCH 1 - POST pre-read: read last qword (partial overwrite) + lea rcx, [rdi + r9 * 8 - 8] # 1 cycle - address of last qword + mov rax, MOPS_ALIGNED_READ # 1 cycle - read flag + add rcx, rax # 1 cycle - combine + mov [r12 + r13 * 8], rcx # ~4 cycles - write pre-read entry + + # BRANCH 1 - Write block entry + shl r9, MOPS_BLOCK_WORDS_RS # 1 cycle - format qwords for mops + mov rax, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - write block flag + add rax, r9 # 1 cycle - add qword count + add rax, rdi # 1 cycle - add dst (aligned) + + mov [r12 + r13 * 8 + 8], rax # ~4 cycles - write block entry + add r13, 2 + # 1 cycle - advance index by 2 + jmp fast_memset # tail call to fast_memset + + # fast_memset "execute" the return, memset set rax = rdi + + # ========== BRANCH 2 ========== + # dst NOT aligned + # Must determine if we need 1 or 2 pre-reads + +.L_xmemset_mops_rdi_unaligned: + + # Calculate total span: (rdi & 0x7) + count + # If span <= 8: only PRE read needed (BRANCH 2.1) + # If span > 8 and end is aligned: only PRE read needed (BRANCH 2.1) + # If span > 8 and end is unaligned: PRE + POST reads needed (BRANCH 2.2) + + mov rcx, rdi + and rcx, 0x07 # 1 cycle - offset within qword + lea rcx, [rcx + rdx + 7] # 1 cycle - rcx = offset + count + 7 + test rcx, 0xFFFFFFFFFFFFFFF0 # 1 cycle - check if (offset + count + 7) > 15 + # => (offset + count) > 8 => spans qwords + jnz .L_pre_branch_2_2 # 2 cycles (predicted) + jmp .L_branch_2_1 # single qword span + + # Check if end is unaligned (needs POST read) +.L_pre_branch_2_2: + lea rax, [rcx - 7] # 1 cycle - rax = offset + count + test rax, 0x7 # 1 cycle - check if end is aligned + jnz .L_branch_2_2 # 2 cycles - end unaligned, need POST + + # ========== BRANCH 2.1 ========== + # dst unaligned, but end IS aligned (or fits in one qword) + # => 1 pre-read (PRE) + 1 write block + +.L_branch_2_1: + + # Calculate aligned base and qword count + # rcx = (rdi & 0x7) + rdx + 7 → (rcx >> 3) = qwords needed + mov rax, rdi + and rax, ALIGN_MASK # 1 cycle - rax = aligned dst + shr rcx, 3 # 1 cycle - rcx = qwords + shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - format for mops + mov r9, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - write block flag + add rcx, r9 # 1 cycle - add flag + add rcx, rax # 1 cycle - add aligned address + + mov [r12 + r13 * 8 + 8], rcx # ~4 cycles - write block entry + + # PRE read entry (first qword contains unaligned start) + mov rcx, MOPS_ALIGNED_READ # 1 cycle - read flag + add rcx, rax # 1 cycle - add aligned address + mov [r12 + r13 * 8], rcx # ~4 cycles - write pre-read entry + add r13, 2 # 1 cycle - advance index by 2 + + jmp fast_memset # tail call to fast_memset + + # fast_memset "execute" the return, memset set rax = rdi + + # ========== BRANCH 2.2 ========== + # dst unaligned AND end unaligned (spans multiple partial qwords) + # => 2 pre-reads (PRE + POST) + 1 write block + +.L_branch_2_2: + + # rcx = (rdi & 0x7) + rdx + 7 → (rcx >> 3) = qwords needed + shr rcx, 3 # 1 cycle - rcx = qwords + shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - format for mops + mov rax, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - write block flag + add rax, rcx # 1 cycle - add qword count + mov rcx, rdi + and rcx, ALIGN_MASK # 1 cycle - rcx = aligned dst + add rax, rcx # 1 cycle - add aligned address + + mov [r12 + r13 * 8 + 16], rax # ~4 cycles - write block entry (3rd slot) + + # PRE read entry (first partial qword) + mov rax, MOPS_ALIGNED_READ # 1 cycle - read flag + add rcx, rax # 1 cycle - rcx = aligned dst + read flag + mov [r12 + r13 * 8], rcx # ~4 cycles - write PRE read entry + + # POST read entry (last partial qword) + lea r9, [rdi + rdx] # 1 cycle - r9 = dst + count + and r9, ALIGN_MASK # 1 cycle - align to qword + add r9, rax # 1 cycle - add read flag + mov [r12 + r13 * 8 + 8], r9 # ~4 cycles - write POST read entry + + add r13, 3 # 1 cycle - advance index by 3 + + jmp fast_memset # tail call to fast_memset + + # fast_memset "execute" the return, memset set rax = rdi + +.L_xmemset_mops_done: + + mov rax, rdi + ret # ~3 cycles + + +################################################################################ +# PERFORMANCE ESTIMATES (Modern x86-64, L1 cache hits) +# +# FAST PATH (aligned dst, count multiple of 8): +# - Mops entry: ~8-10 cycles +# - fast_memset overhead: ~5-10 cycles +# - Qword fill (rep stosq): ~0.5-1.0 cycles/qword (ERMSB) +# Total: ~15-20 cycles + ~0.75 cycles/qword +# +# BRANCH 1 (aligned dst, count NOT multiple of 8): +# - Pre-read + block entries: ~15-18 cycles +# - fast_memset + fill: ~5-10 cycles + ~0.75 cycles/qword +# Total: ~25-30 cycles + ~0.75 cycles/qword +# +# BRANCH 2.1 (unaligned dst, end aligned or single qword): +# - PRE read + block entries: ~18-22 cycles +# - fast_memset + fill: ~5-10 cycles + ~0.75 cycles/qword +# Total: ~28-35 cycles + ~0.75 cycles/qword +# +# BRANCH 2.2 (unaligned dst AND end): +# - PRE + POST + block entries: ~25-30 cycles +# - fast_memset + fill: ~5-10 cycles + ~0.75 cycles/qword +# Total: ~35-45 cycles + ~0.75 cycles/qword +# +# EXAMPLE (64-byte aligned fill): +# ~20 cycles mops + ~10 cycles setup + 8 qwords * 0.75 = ~36 cycles +# Throughput: ~1.8 GB/s @ 3 GHz +# +# EXAMPLE (4096-byte aligned fill): +# ~20 cycles mops + ~10 cycles setup + 512 qwords * 0.5 = ~286 cycles +# Throughput: ~14.3 GB/s @ 3 GHz (approaching L1D bandwidth) +# +# NOTES: +# - Assumes L1D cache hits (~4 cycle latency) +# - rep stosq uses ERMSB optimization on modern CPUs (post-2013) +# - For fills >256 bytes, approaches memory bandwidth limits +# - Actual cycles vary ±20% by microarchitecture +################################################################################ + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/direct_memset_mtrace.asm b/emulator-asm/src/dma/direct_memset_mtrace.asm new file mode 100644 index 000000000..d5ba1cacc --- /dev/null +++ b/emulator-asm/src/dma/direct_memset_mtrace.asm @@ -0,0 +1,268 @@ +.intel_syntax noprefix +.code64 + +################################################################################ +# direct_dma_xmemset_mtrace - Memory set with mtrace (memory trace) recording +# +# This function fills a memory region with a byte value while recording +# the operation encoding and pre-values to the mtrace buffer for verification. +# +# MAIN TASKS: +# 1. Encode memset metadata (dst_offset, count, fill_byte, alignment info) +# 2. Record pre-values of partial qwords (before overwriting) +# 3. Perform the actual memset operation (via fast_memset) +# +# MTRACE SIZE: +# xmemset uses at most 3 qwords: encode + pre + post +# Therefore, no realloc check is needed (always fits within threshold) +# +# REGISTER USAGE: +# Uses: rax, rcx, rdx, rdi, rsi, r9, r12, r13 +# Does NOT use XMM registers (caller doesn't need to save them) +# Modifies: r13 (mtrace index output) +# +# PARAMETERS (non-standard ABI): +# rdi = dst (u64) - Destination address to fill +# rsi = value (u8 in low byte) - Byte value to set (0-255) +# rdx = count (usize) - Number of bytes to set +# r12 = mtrace buffer base address - Base pointer to mtrace buffer +# r13 = mtrace buffer index - Current index (updated on return) +# +# RETURN: +# r13 = Updated mtrace index +# +# BRANCHES: +# FAST: dst aligned + count multiple of 8 → encode only (no pre-reads) +# BRANCH 1: dst aligned + count NOT multiple of 8 → encode + 1 post pre-read +# BRANCH 2: dst unaligned → encode + 0-2 pre-reads depending on alignment +################################################################################ + +.global direct_dma_xmemset_mtrace +.extern fast_memset + +.include "dma_constants.inc" +.include "fast_dma_encode_macro.inc" + +.section .text + +################################################################################ +# direct_dma_xmemset_mtrace - Direct entry point (non-standard ABI) +# +# Called directly from generated assembly code without ABI overhead. +# More efficient when caller manages register preservation. +# +# PARAMETERS: +# rdi = destination address +# rsi = byte value (0-255) +# rdx = byte count +# r12 = mtrace buffer base +# r13 = mtrace buffer index (input/output) +################################################################################ + +direct_dma_xmemset_mtrace: + + # Modified registers (caller must handle): + # r9 = scratch for calculations + # rcx = scratch for address/value storage + # r13 = mtrace index (updated on return) + + # Early exit path if count = 0 + test rdx, rdx + jz .L_xmemset_mtrace_count_zero + + # Check if dst is 8-byte aligned + test rdi, 0x7 + jnz .L_xmemset_mtrace_rdi_unaligned + + # Check if count is multiple of 8 + test rdx, 0x07 + jnz .L_memset_mtrace_count_remain + + # ========== FAST PATH ========== + # dst is aligned AND count is multiple of 8 + # => No partial qwords, no pre-reads needed, only encoding + + # Encode loop_bytes (count is already multiple of 8) + mov r9, rdx + shl r9, DMA_PRE_AND_LOOP_BYTES_RS # 1 cycle - shift to loop_bytes position + + # Encode fill byte + movzx eax, sil # 1 cycle - zero-extend byte value + shl rax, DMA_FILL_BYTE_RS # 1 cycle - shift to fill_byte position + add rax, r9 # 1 cycle - combine + + # Store encoded value to mtrace + mov [r12 + r13 * 8], rax # ~4 cycles - write encoding + inc r13 # 1 cycle - advance mtrace index + + jmp fast_memset # tail call to fast_memset + + # ========== BRANCH 1 ========== + # dst aligned, count NOT multiple of 8 + # => Need 1 post pre-read (last partial qword) + +.L_memset_mtrace_count_remain: + # dst_offset = 0 + + movzx r9, sil # 1 cycle - zero-extend byte value + shl r9, DMA_FILL_BYTE_RS # 1 cycle - shift to fill_byte position + + FAST_DMA_ENCODE_NO_SRC + + # Encode post_count (remaining bytes after aligned portion) + add rax, r9 + + # Store encoding to mtrace + mov [r12 + r13 * 8], rax # ~4 cycles + + # Calculate qword count for post pre-read address + + shr rax, DMA_LOOP_COUNT_RS + mov rcx, [rdi + rax * 8] # 1 cycle - rcx = post qword address + mov [r12 + r13 * 8 + 8], rcx # ~4 cycles - store post pre-read address + add r13, 2 # 1 cycle - advance index by 2 + + jmp fast_memset # tail call to fast_memset + + # ========== BRANCH 2 ========== + # dst NOT aligned - uses full FAST_DMA_ENCODE macro + # Depending on alignment, may need 0, 1, or 2 pre-reads + +.L_xmemset_mtrace_rdi_unaligned: + + # Use macro for complex encoding (handles all alignment cases) + FAST_DMA_ENCODE_NO_SRC # ~15-20 cycles + + # Add fill byte to encoding + movzx r9, sil # 1 cycle - zero-extend byte value + shl r9, DMA_FILL_BYTE_RS # 1 cycle - shift to position + or rax, r9 # 1 cycle - combine with encoding + + # Store encoding to mtrace + mov [r12 + r13 * 8], rax # ~4 cycles + + # Check if PRE pre-read needed (unaligned start) + test rax, DMA_PRE_COUNT_MASK # 1 cycle + jz .L_xmemset_mtrace_rdi_unaligned_no_pre # 2 cycles (predicted) + + # PRE pre-read: save original value of first partial qword + mov r9, rdi + and r9, ALIGN_MASK # 1 cycle - r9 = aligned dst + mov rcx, [r9] # ~4 cycles - read current value + mov [r12 + r13 * 8 + 8], rcx # ~4 cycles - store pre-value + + # Check if POST pre-read also needed (unaligned end) + test rax, DMA_POST_COUNT_MASK # 1 cycle + jz .L_xmemset_mtrace_rdi_unaligned_pre_no_post # 2 cycles + + # POST pre-read: save original value of last partial qword + # r9 still contains (dst & ALIGN_MASK) from previous calculation + # Calculate post qword address: aligned_dst + 8 + loop_count * 8 + + mov rcx, rax + shr rcx, DMA_PRE_AND_LOOP_BYTES_RS + + mov rcx, [rdi + rcx] # ~4 cycles - read post pre-value + mov [r12 + r13 * 8 + 16], rcx # ~4 cycles - store as third mtrace entry + add r13, 3 # 1 cycle - advance by 3 (encode + pre + post) + + jmp fast_memset # tail call to fast_memset + + # ----- BRANCH 2.1: PRE only (no POST) ----- + # Unaligned start but aligned end +.L_xmemset_mtrace_rdi_unaligned_pre_no_post: + add r13, 2 # 1 cycle - advance by 2 (encode + pre) + + jmp fast_memset # tail call to fast_memset + + # ----- BRANCH 2.2: NO PRE (start happens to be aligned) ----- + # When unaligned path was taken but PRE=0 (edge case) +.L_xmemset_mtrace_rdi_unaligned_no_pre: + # Check if POST pre-read needed + test rax, DMA_POST_COUNT_MASK # 1 cycle + jz .L_xmemset_mtrace_rdi_unaligned_no_pre_no_post # 2 cycles + + # Calculate aligned dst for post address + mov r9, rdi # 1 cycle + and r9, ALIGN_MASK # 1 cycle - r9 = aligned dst + + # Extract loop_count to calculate post address + mov rcx, rax # 1 cycle + shr rcx, DMA_LOOP_COUNT_RS # 1 cycle - rcx = loop_count + + # POST pre-read: save original value of last qword + # Address: aligned_dst + 8 + loop_count * 8 + mov rcx, [r9 + 8 + rcx * 8] # ~4 cycles - read post pre-value + mov [r12 + r13 * 8 + 8], rcx # ~4 cycles - store as second mtrace entry + add r13, 2 # 1 cycle - advance by 2 (encode + post) + + jmp fast_memset # tail call to fast_memset + + # ----- BRANCH 2.3: NO PRE, NO POST ----- + # Edge case where both ends happen to be aligned despite taking unaligned path +.L_xmemset_mtrace_rdi_unaligned_no_pre_no_post: + inc r13 # 1 cycle - advance by 1 (encode only) + + jmp fast_memset # tail call to fast_memset + + # ========== COUNT = 0 CASE ========== + # Zero-length memset: only encoding, no pre-reads needed + # Creates minimal mtrace entry for zero-byte operation +.L_xmemset_mtrace_count_zero: + + FAST_DMA_ENCODE_COUNT_ZERO 0 + + # Encode fill byte + movzx r9, sil # 1 cycle - zero-extend byte value + shl r9, DMA_FILL_BYTE_RS # 1 cycle - shift to position + + # Add template for MEMSET_ZERO operation type + add rax, r9 # 1 cycle + + + # Store encoding to mtrace (no pre-reads for zero-length) + mov [r12 + r13 * 8], rax # ~4 cycles + inc r13 # 1 cycle + + jmp fast_memset # tail call to fast_memset + + # NOTE: This label is unreachable - all paths use tail calls to fast_memset + + +# Performance Estimate (Modern x86-64, Intel Skylake/AMD Zen+, L1 cache hits): +# +# MEMSET OPERATION WITH MTRACE RECORDING: +# - FAST_DMA_ENCODE macro: ~15-20 cycles (logic + table lookup) +# - Encoding store: ~4 cycles (mov to mtrace buffer) +# - Pre pre-read (if needed): ~8-10 cycles (and + mov load + mov store) +# - Post pre-read (if needed): ~10-12 cycles (lea + mov load + mov store) +# - Fill byte insertion: ~3 cycles (movzx + shl + or) +# - Tail call jump: ~1-2 cycles +# +# PATH TIMING: +# +# FAST PATH (aligned dst, count % 8 == 0): +# 7 (setup) + 3 (fill byte) + 4 (store) + 8 (post pre-read) + 2 (tail) +# = ~24 cycles overhead + fast_memset execution +# +# BRANCH 1 (aligned dst, count % 8 != 0): +# 15 (encode) + 3 (fill byte) + 4 (store) + 8 (post pre-read) + 2 (tail) +# = ~32 cycles overhead + fast_memset execution +# +# BRANCH 2.1 (unaligned dst, PRE only): +# 20 (encode) + 3 (fill byte) + 4 (store) + 10 (pre pre-read) + 2 (tail) +# = ~39 cycles overhead + fast_memset execution +# +# BRANCH 2.2 (unaligned dst, PRE + POST): +# 20 (encode) + 3 (fill byte) + 4 (store) + 10 (pre) + 12 (post) + 2 (tail) +# = ~51 cycles overhead + fast_memset execution +# +# NOTES: +# - Mtrace overhead is independent of fill size (constant per operation) +# - Pre-reads capture original values for later verification +# - All paths use tail calls, minimizing return overhead +# - Encoding + pre-reads add ~24-51 cycles vs direct fast_memset call +# - Actual fill performance depends on fast_memset (ERMSB ~0.5 cycles/qword) + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/direct_xmemset_mops.asm b/emulator-asm/src/dma/direct_xmemset_mops.asm deleted file mode 100644 index 36987398a..000000000 --- a/emulator-asm/src/dma/direct_xmemset_mops.asm +++ /dev/null @@ -1,255 +0,0 @@ -.intel_syntax noprefix -.code64 - -################################################################################ -# memset_mops - Optimized memset with memory ops tracing -# -# This function performs two main tasks: -# 1. Records all addresses of memory operations (read and write addresses) -# 2. Performs the actual memset operation filling dst with the byte value -# -# REGISTER USAGE: -# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r8, r9, r12, r13 -# Does NOT use XMM registers (caller doesn't need to save them) -# Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) -# -# PARAMETERS (NON System V AMD64 ABI): -# rdi = dst (u64) - Destination address to fill -# rsi = value (u8 in low byte) - Byte value to set (0-255) -# rdx = count (usize) - Number of bytes to set -# r12 = mops_base_addr (u64*) - Pointer to memory ops trace buffer base -# r13 = mops_index (usize) - Current index in mops buffer (input/output) -# -################################################################################ - -.global direct_dma_xmemset_mops -.global dma_xmemset_mops -.extern fast_memset - -.include "dma_constants.inc" - -.section .text - -# Standard ABI wrapper that saves/restores callee-saved registers -# and initializes mops tracking state before calling direct implementation - -# rdi = destination -# rsi: byte to write -# rdx: count (bytes) -# rcx = pointer to data trace -# rax = return qword of trace - -dma_xmemset_mops: - - # Save callee-saved registers - push r12 # ~3 cycles - save r12 (used as mops base address) - push r13 # ~3 cycles - save r13 (used as mops index) - - mov r12, rcx # 1 cycle - r12 = mops buffer base address - xor r13, r13 # 1 cycle - r13 = 0 (initialize mops index) - call direct_dma_xmemset_mops # ~5 cycles + function cost - - mov rax, r13 # 1 cycle - return mops count in rax - pop r13 # ~3 cycles - restore r13 - pop r12 # ~3 cycles - restore r12 - - ret # ~5 cycles - -# Direct entry point for assembly callers (no ABI overhead) -# More efficient when caller manages register preservation - -# arguments: -# rdi: destination adress -# rsi: byte to write -# rdx: count (bytes) -# r12 + r13: mops trace - -direct_dma_xmemset_mops: - - # Modified registers (caller must handle): - # r9 = scratch for mops address calculation - # rcx = mops index (incremented, output) - - # test count = 0 - test rdx, rdx - jz .L_xmemset_mops_done - - # test dst aligned - test rdi, 0x7 - jnz .L_xmemset_mops_rdi_unaligned - - # test count multiple of 8 - test rdx, 0x07 - jnz .L_memset_mops_count_remain - - # FAST BRANCH - # dst is aligned, count is a multiple of 8 and greater than zero - # => no pre-reads, only one MOPS write block - - # FAST BRANCH - MOPS (MOPS_ALIGNED_BLOCK_WRITE) - - mov rax, rdx - shr rax, 3 - shl rax, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position - mov r9, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags - add r9, rax - add r9, rdi # rdi aligned - - mov [r12 + r13 * 8], r9 # ~4 cycles - write mops entry (block write) - inc r13 # 1 cycle - advance mops index - - jmp fast_memset - -.L_memset_mops_count_remain: - # BRANCH 1 - # dst is aligned, but count is NOT a multiple of 8, - # => one pre-read (post) before one MOPS write block - # NOTE: if count < 8 no problem, because you need to do read and write. - - # BRANCH 1 - MOPS (MOPS_ALIGNED_READ (POST) + MOPS_ALIGNED_BLOCK_WRITE) - - # BRANCH 1 - common MOPS part - - lea r9, [rdx + 7] - shr r9, 3 - - # BRANCH 1 - specific MOPS pre-read part - - lea rcx, [rdi + r9 * 8 - 8] - mov rax, MOPS_ALIGNED_READ - add rcx, rax - mov [r12 + r13 * 8], rcx - - # BRANCH 1 - specific MOPS block write - # set rcx = qwords to write - - shl r9, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position - mov rax, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags - add rax, r9 - add rax, rdi # rdi is aligned in this path - - mov [r12 + r13 * 8 + 8], rax # ~4 cycles - write mops entry (block write) - add r13, 2 - - jmp fast_memset - -.L_xmemset_mops_rdi_unaligned: - # BRANCH 2 - worse - # dst is NOT aligned - # => BRANCH 2.1 one pre-read (pre) + no post - # => BRANCH 2.2 one pre-read (pre) + second post pre-read - - # [EC] only PRE but [rdi + rdx] & 0x07 !== 0 - mov rcx, rdi - and rcx, 0x07 - lea rcx, [rcx + rdx + 7] # optimization to be used in this branch - test rcx, 0xFFFFFFFFFFFFFFF0 # (rcx + rdx) > 8 => (rcx + rdx + 7) > 15 => - # (rcx + rdx + 7) & 0xF..F0 != 0 - jnz .L_pre_branch_2_2 - jmp .L_branch_2_1 - -.L_pre_branch_2_2: - lea rax, [rcx - 7] - test rax, 0x7 - jnz .L_branch_2_2 - -.L_branch_2_1: - - # BRANCH 2.1 - MOPS (MOPS_ALIGNED_READ (PRE) + MOPS_ALIGNED_BLOCK_WRITE) - - # BRANCH 2.1 - specific MOPS block write - # NOTE: at least one qword because count > 0 - # rcx = (rdi & 0x7) + rdx + 7 ==> (rcx >> 3) qwords - mov rax, rdi - and rax, ALIGN_MASK - shr rcx, 3 - shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position - mov r9, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags - add rcx, r9 - add rcx, rax - - mov [r12 + r13 * 8 + 8], rcx # ~4 cycles - write mops entry (block write) - - # BRANCH 2.1 - specific MOPS pre-read part PRE - # rax = rdi & ALIGN_MASK - - mov rcx, MOPS_ALIGNED_READ - add rcx, rax - mov [r12 + r13 * 8], rcx - add r13, 2 - - jmp fast_memset - -.L_branch_2_2: - - # BRANCH 2.2 - MOPS (2xMOPS_ALIGNED_READ (PRE/POST) + MOPS_ALIGNED_BLOCK_WRITE) - # BRANCH 2.2 - specific MOPS pre-read part PRE - # rcx = (rdi & 0x7) + rdx + 7 ==> (rcx >> 3) qwords - - shr rcx, 3 - shl rcx, MOPS_BLOCK_WORDS_RS # 1 cycle - shift to block words field position - mov rax, MOPS_ALIGNED_BLOCK_WRITE # 1 cycle - rcx = block write flags - add rax, rcx # rax = MOPS_ALIGNED_BLOCK_WRITE | (count_q << MOPS_BLOCK_WORDS_RS) - mov rcx, rdi - and rcx, ALIGN_MASK - add rax, rcx # rax |= rdi & ALIGN MASK - - mov [r12 + r13 * 8 + 16], rax # ~4 cycles - write mops entry (block write) - - # rcx = rdi & ALIGN_MASK - # BRANCH 2.2 - PRE write - - mov rax, MOPS_ALIGNED_READ - - add rcx, rax # rcx = rdi & ALIGN_MASK - mov [r12 + r13 * 8], rcx - - # BRANCH 2.2 - POST write - - lea r9, [rdi + rdx] - and r9, ALIGN_MASK - add r9, rax - mov [r12 + r13 * 8 + 8], r9 - - add r13, 3 - - jmp fast_memset -.L_xmemset_mops_done: - ret - - -# Performance estimate (Modern x86-64, Intel Skylake/AMD Zen+, L1 cache hits): -# -# MEMSET OPERATION WITH MOPS TRACING: -# - fast_dma_encode call: ~15-20 cycles (function call + table lookup) -# - Pre-read mops entry: ~8-10 cycles (if pre_count > 0: calc + and + store + inc) -# - Post-read mops entry: ~10-12 cycles (if post_count > 0: lea + and + add + store + inc) -# - Block write mops entry: ~12-15 cycles (extract + shift + combine + store + inc) -# - Byte value expansion: ~5-6 cycles (movzx + mov + imul) -# - Qword fill (rep stosq): ~0.5-1.0 cycles per qword (ERMSB optimization) -# - Remaining bytes (rep stosb): ~1.0-2.0 cycles per byte (0-7 bytes) -# - Function overhead: ~3-5 cycles (branches, return) -# -# TOTAL (typical case, 64 bytes, aligned, no pre/post): -# ~15 (encode) + ~15 (block mops) + ~6 (expand) + 8*0.75 (fill) + ~4 (overhead) -# = ~46 cycles (~1.39 GB/s @ 3 GHz) -# -# TOTAL (misaligned case, 64 bytes with pre/post): -# ~15 (encode) + ~10 (pre) + ~12 (post) + ~15 (block) + ~6 (expand) + 7*0.75 + 4*1.5 (fill) + ~4 -# = ~73 cycles (~0.88 GB/s @ 3 GHz) -# -# TOTAL (large fill, 4096 bytes, aligned): -# ~15 (encode) + ~15 (mops) + ~6 (expand) + 512*0.5 (fill) + ~4 (overhead) -# = ~296 cycles (~13.8 GB/s @ 3 GHz, approaching L1D bandwidth) -# -# NOTES: -# - Assumes L1D cache hits for all memory accesses (~4 cycle latency, ~64 GB/s bandwidth) -# - rep stosq/stosb uses Enhanced REP MOVSB/STOSB (ERMSB) on modern CPUs (post-2013) -# - ERMSB enables microcode to use wide stores (16-64 bytes per iteration internally) -# - For fills >256 bytes, performance approaches memory bandwidth limits -# - Actual cycles vary ±20-30% by microarchitecture (Skylake/Zen/Alder Lake) -# - Mops overhead: ~30-50 cycles base + minimal per-byte impact -# - No overlap handling needed for memset (writes only, no read-modify-write hazards) - -# Mark stack as non-executable (required by modern linkers) -.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/direct_xmemset_mtrace.asm b/emulator-asm/src/dma/direct_xmemset_mtrace.asm deleted file mode 100644 index 93d5054ce..000000000 --- a/emulator-asm/src/dma/direct_xmemset_mtrace.asm +++ /dev/null @@ -1,279 +0,0 @@ -.intel_syntax noprefix -.code64 - -################################################################################ -# memset_mtrace - Optimized memset with memory ops tracing -# -# This function performs two main tasks: -# 1. Records all addresses of memory operations (read and write addresses) -# 2. Performs the actual memset operation filling dst with the byte value -# -# REGISTER USAGE: -# Uses general-purpose registers: rax, rbx, rcx, rdx, rdi, rsi, r8, r9, r12, r13 -# Does NOT use XMM registers (caller doesn't need to save them) -# Preserves callee-saved registers (rbx, r12, r13 saved/restored in wrapper) -# -# MTRACE SIZE CONTROL: -# -# xmemset use at maximum 3 qwords to store encode, pre and post, for this reason this -# function doesn't require analyze if need call to realloc. -# -# PARAMETERS (NON System V AMD64 ABI): -# rdi = dst (u64) - Destination address to fill -# rsi = value (u8 in low byte) - Byte value to set (0-255) -# rdx = count (usize) - Number of bytes to set -# r12 = mops_base_addr (u64*) - Pointer to memory ops trace buffer base -# r13 = mops_index (usize) - Current index in mops buffer (input/output) -# -################################################################################ - -.global direct_dma_xmemset_mtrace -.global dma_xmemset_mtrace -.extern fast_memset - -.include "dma_constants.inc" - -.section .text - -# Standard ABI wrapper that saves/restores callee-saved registers -# and initializes mops tracking state before calling direct implementation - -# rdi = destination -# rsi: byte to write -# rdx: count (bytes) -# rcx = pointer to data trace -# rax = return qword of trace - -dma_xmemset_mtrace: - - # Save callee-saved registers - push r12 # ~3 cycles - save r12 (used as mops base address) - push r13 # ~3 cycles - save r13 (used as mops index) - - mov r12, rcx # 1 cycle - r12 = mops buffer base address - xor r13, r13 # 1 cycle - r13 = 0 (initialize mops index) - call direct_dma_xmemset_mtrace # ~5 cycles + function cost - - mov rax, r13 # 1 cycle - return mops count in rax - pop r13 # ~3 cycles - restore r13 - pop r12 # ~3 cycles - restore r12 - - ret # ~5 cycles - -# Direct entry point for assembly callers (no ABI overhead) -# More efficient when caller manages register preservation - -# arguments: -# rdi: destination adress -# rsi: byte to write -# rdx: count (bytes) -# r12 + r13: mops trace - -direct_dma_xmemset_mtrace: - - # Modified registers (caller must handle): - # r9 = scratch for mops address calculation - # rcx = mops index (incremented, output) - - # test count = 0 - test rdx, rdx - jz .L_xmemset_mtrace_count_zero - - # test dst aligned - test rdi, 0x7 - jnz .L_xmemset_mtrace_rdi_unaligned - - # test count multiple of 8 - test rdx, 0x07 - jnz .L_memset_mtrace_count_remain - - # FAST BRANCH - # dst is aligned, count is a multiple of 8 and greater than zero - # => no pre-reads, only encoding - - # FAST BRANCH - MTRACE (ENCODING) - - # FAST DIRECT ENCODING - - # encode loop count, how count is multiple of 8, direct shift - mov r9, rdx - shl r9, PRE_AND_LOOP_BYTES_RS - - # encode fill byte - movzx eax, sil - shl rax, FILL_BYTE_CMP_RES_RS - add rax, r9 - - # store encoded on mtrace - mov [r12 + r13 * 8], rax - inc r13 - - jmp fast_memset - -.L_memset_mtrace_count_remain: - # BRANCH 1 - # dst is aligned, but count is NOT a multiple of 8, - # => one pre-read (post) - # NOTE: if count ∈ [1,7] no problem, because you need to do pre-read - - # BRANCH 1 - MTRACE (ENCODING + ALIGNED_READ (POST) - - # encode fill byte - movzx eax, sil - shl rax, FILL_BYTE_CMP_RES_RS - - # encode post count - mov r9, rdx - and r9, 0x07 - shl r9, POST_COUNT_RS - - # encode = fill byte + post_count - add rax, r9 - - # encode += template - or rax, ENCODE_MEMSET_ALIGNED_NO_COUNT_M8 - - # encode loop count (count % 8 == 0) - mov r9, rdx - and r9, ALIGN_MASK - shl r9, PRE_AND_LOOP_BYTES_RS - add rax, r9 - - # store encode to mtrace - mov [r12 + r13 * 8], rax - - # unshift loop count, r9 containts count64 - shr r9, PRE_AND_LOOP_BYTES_RS + 3 - - # BRANCH 1 - specific pre-read part - # r9 contains count64 to index need to substract 1*8 - lea rcx, [rdi + r9 * 8 - 8] - mov [r12 + r13 * 8 + 8], rcx - add r13, 2 - - jmp fast_memset - -.L_xmemset_mtrace_rdi_unaligned: - # BRANCH 2 - worse - # dst is NOT aligned - # => BRANCH 2.1 one pre-read (pre) + no post - # => BRANCH 2.2 one pre-read (pre) + second post pre-read - - # [EC] only PRE but [rdi + rdx] & 0x07 !== 0 - - call fast_dma_encode_memset_with_byte - mov [r12 + r13 * 8], rax - - test rax, PRE_COUNT_MASK - jz .L_xmemset_mtrace_rdi_unaligned_no_pre - - mov r9, rdi - and r9, ALIGN_MASK - mov rcx, [r9] - mov [r12 + r13 * 8 + 8], rcx - - test rax, POST_COUNT_MASK - jz .L_xmemset_mtrace_rdi_unaligned_pre_no_post - - mov rcx, rax - shr rcx, LOOP_COUNT_RS - - # r9 = dst & 0x07 of previous calculation - # r9 + loop * 8 + 8 for pre part - - mov rcx, [r9 + 8 + rcx * 8] - mov [r12 + r13 * 8 + 16], rcx - add r13, 3 - - jmp fast_memset - -.L_xmemset_mtrace_rdi_unaligned_pre_no_post: - add r13, 2 - - jmp fast_memset - -.L_xmemset_mtrace_rdi_unaligned_no_pre: - test rax, POST_COUNT_MASK - jz .L_xmemset_mtrace_rdi_unaligned_no_pre_no_post - - mov r9, rdi - and r9, ALIGN_MASK - - mov rcx, rax - shr rcx, LOOP_COUNT_RS - - # r9 = dst & 0x07 of previous calculation - # rdi + loop * 8 + 8 for pre part - - mov rcx, [r9 + 8 + rcx * 8] - mov [r12 + r13 * 8 + 8], rcx - add r13, 2 - - jmp fast_memset - -.L_xmemset_mtrace_rdi_unaligned_no_pre_no_post: - inc r13 - - jmp fast_memset - -.L_xmemset_mtrace_count_zero: - - # encode in fast way, for zero-lenght memset - - # encode dst offset - mov r9, rdi - and r9, 0x07 - shl r9, DST_OFFSET_RS - - # encode fill byte - movzx eax, sil - shl rax, FILL_BYTE_CMP_RES_RS - add r9, rax - - # encode template of MEMSET_ZERO - add r9, ENCODE_MEMSET_ZERO - - # add encode to mtrace - mov [r12 + r13 * 8], r9 - inc r13 - - jmp fast_memset - -.L_xmemset_mtrace_done: - ret - -# Performance estimate (Modern x86-64, Intel Skylake/AMD Zen+, L1 cache hits): -# -# MEMSET OPERATION WITH MOPS TRACING: -# - fast_dma_encode call: ~15-20 cycles (function call + table lookup) -# - Pre-read mops entry: ~8-10 cycles (if pre_count > 0: calc + and + store + inc) -# - Post-read mops entry: ~10-12 cycles (if post_count > 0: lea + and + add + store + inc) -# - Block write mops entry: ~12-15 cycles (extract + shift + combine + store + inc) -# - Byte value expansion: ~5-6 cycles (movzx + mov + imul) -# - Qword fill (rep stosq): ~0.5-1.0 cycles per qword (ERMSB optimization) -# - Remaining bytes (rep stosb): ~1.0-2.0 cycles per byte (0-7 bytes) -# - Function overhead: ~3-5 cycles (branches, return) -# -# TOTAL (typical case, 64 bytes, aligned, no pre/post): -# ~15 (encode) + ~15 (block mops) + ~6 (expand) + 8*0.75 (fill) + ~4 (overhead) -# = ~46 cycles (~1.39 GB/s @ 3 GHz) -# -# TOTAL (misaligned case, 64 bytes with pre/post): -# ~15 (encode) + ~10 (pre) + ~12 (post) + ~15 (block) + ~6 (expand) + 7*0.75 + 4*1.5 (fill) + ~4 -# = ~73 cycles (~0.88 GB/s @ 3 GHz) -# -# TOTAL (large fill, 4096 bytes, aligned): -# ~15 (encode) + ~15 (mops) + ~6 (expand) + 512*0.5 (fill) + ~4 (overhead) -# = ~296 cycles (~13.8 GB/s @ 3 GHz, approaching L1D bandwidth) -# -# NOTES: -# - Assumes L1D cache hits for all memory accesses (~4 cycle latency, ~64 GB/s bandwidth) -# - rep stosq/stosb uses Enhanced REP MOVSB/STOSB (ERMSB) on modern CPUs (post-2013) -# - ERMSB enables microcode to use wide stores (16-64 bytes per iteration internally) -# - For fills >256 bytes, performance approaches memory bandwidth limits -# - Actual cycles vary ±20-30% by microarchitecture (Skylake/Zen/Alder Lake) -# - Mops overhead: ~30-50 cycles base + minimal per-byte impact -# - No overlap handling needed for memset (writes only, no read-modify-write hazards) - -# Mark stack as non-executable (required by modern linkers) -.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/dma_constants.inc b/emulator-asm/src/dma/dma_constants.inc index 7cd0aec0f..e968242ff 100644 --- a/emulator-asm/src/dma/dma_constants.inc +++ b/emulator-asm/src/dma/dma_constants.inc @@ -1,4 +1,17 @@ .intel_syntax noprefix +.code64 + +# GENERAL CONSTANTS + +.set R_MT_INDEX, r13 +.set R_MT_ADDR, r12 +.set R_STEP, r14 +.set R_AUX, r9 +.set R_AUX2, rcx # NOTE: used by rep +.set R_SRC, rsi # NOTE: used by rep +.set R_DST, rdi # NOTE: used by rep +.set R_COUNT, rdx +.set R_ENCODE, rax # GENERAL CONSTANTS @@ -8,6 +21,7 @@ .equ MAX_BYTES_DIRECT_MTRACE, 256 .equ MAX_BYTES_MTRACE_STEP, (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) .equ MAX_CHUNK_TRACE_SIZE, CHUNK_SIZE * MAX_BYTES_MTRACE_STEP + MAX_TRACE_CHUNK_INFO +.equ FAST_ENCODE_TABLE_WO_NEQ_SIZE, 8 * 8 * 16 # 1 encoded + 2 prewrites + 2 src reads .equ MAX_DMA_EXTRA_BYTES, (2 + 2 + 1) * 8 @@ -37,36 +51,40 @@ # BITS 31,32,32 -.equ PRE_COUNT_MASK, 0x0000000000000007 -.equ POST_COUNT_MASK, 0x0000000000000038 -.equ PRE_WRITES_MASK, 0x00000000000000C0 -.equ DST_OFFSET_MASK, 0x0000000000000E00 -.equ SRC_OFFSET_MASK, 0x0000000000007000 -.equ DOUBLE_SRC_PRE_MASK, 0x0000000000008000 -.equ DOUBLE_SRC_POST_MASK, 0x0000000000010000 -.equ EXTRA_SRC_READS_MASK, 0x0000000000060000 -.equ SRC64_INC_BY_PRE_MASK, 0x0000000000080000 -.equ UNALIGNED_DST_SRC_MASK, 0x0000000000100000 -.equ FILL_BYTE_CMP_RES_MASK, 0x000000001FE00000 -.equ REQUIRES_DMA_MASK, 0x0000000040000000 -.equ LOOP_COUNT_MASK, 0xFFFFFFFF00000000 -.equ ONLY_LOOP_COUNT_MASK, 0xFFFFFFFF00000000 -.equ FULL_ALIGN_MASK, 0x00000000001FFFFF -.equ DMA_DIRECT_MASK, 0x00000000201FFFFF +.equ DMA_PRE_COUNT_MASK, 0x0000000000000007 +.equ DMA_POST_COUNT_MASK, 0x0000000000000078 +.equ DMA_PRE_WRITES_MASK, 0x0000000000000180 +.equ DMA_DST_OFFSET_MASK, 0x0000000000000E00 +.equ DMA_SRC_OFFSET_MASK, 0x0000000000007000 +.equ DMA_DOUBLE_SRC_PRE_MASK, 0x0000000000008000 +.equ DMA_DOUBLE_SRC_POST_MASK, 0x0000000000010000 +.equ DMA_EXTRA_SRC_READS_MASK, 0x0000000000060000 +.equ DMA_SRC64_INC_BY_PRE_MASK, 0x0000000000080000 +.equ DMA_UNALIGNED_DST_SRC_MASK, 0x0000000000100000 +.equ DMA_FILL_BYTE_MASK, 0x000000000FE00000 +.equ DMA_CMP_RES_MASK, 0x000000001FE00000 +.equ DMA_REQUIRES_DMA_MASK, 0x0000000040000000 +.equ DMA_LOOP_COUNT_MASK, 0xFFFFFFFF00000000 +.equ DMA_ONLY_LOOP_COUNT_MASK, 0xFFFFFFFF00000000 +.equ DMA_FULL_ALIGN_MASK, 0x00000000001FFFFF +.equ DMA_DIRECT_MASK, 0x00000000201FFFFF +.equ DMA_CMP_RES_SIGN_TEST_MASK, 0x0000000020000000 + -.equ PRE_COUNT_RS, 0 -.equ POST_COUNT_RS, 3 -.equ PRE_WRITES_RS, 7 -.equ DST_OFFSET_RS, 9 -.equ SRC_OFFSET_RS, 12 -.equ DOUBLE_SRC_PRE_RS, 15 -.equ DOUBLE_SRC_POST_RS, 16 -.equ EXTRA_SRC_READS_RS, 17 -.equ SRC64_INC_BY_PRE_RS, 19 -.equ UNALIGNED_DST_SRC_RS, 20 -.equ FILL_BYTE_CMP_RES_RS, 21 -.equ LOOP_COUNT_RS, 35 -.equ PRE_AND_LOOP_BYTES_RS, 32 +.equ DMA_PRE_COUNT_RS, 0 +.equ DMA_POST_COUNT_RS, 3 +.equ DMA_PRE_WRITES_RS, 7 +.equ DMA_DST_OFFSET_RS, 9 +.equ DMA_SRC_OFFSET_RS, 12 +.equ DMA_DOUBLE_SRC_PRE_RS, 15 +.equ DMA_DOUBLE_SRC_POST_RS, 16 +.equ DMA_EXTRA_SRC_READS_RS, 17 +.equ DMA_SRC64_INC_BY_PRE_RS, 19 +.equ DMA_UNALIGNED_DST_SRC_RS, 20 +.equ DMA_FILL_BYTE_RS, 21 +.equ DMA_CMP_RES_RS, 21 +.equ DMA_LOOP_COUNT_RS, 35 +.equ DMA_PRE_AND_LOOP_BYTES_RS, 32 .equ ALIGN_MASK, 0xFFFFFFFFFFFFFFF8 .equ MOPS_BLOCK_WORDS_RS, 36 .equ MOPS_BLOCK_ONE_WORD, 0x0000001000000000 @@ -89,13 +107,13 @@ .equ FCALL_RESULT_GOT, FCALL_RESULT + FCALL_RESULT_LENGTH .equ MOPS_ALIGNED_READ_2W, ((2 << MOPS_BLOCK_WORDS_RS) + MOPS_ALIGNED_BLOCK_READ) -.equ LOOP_COUNT_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_RS - LOOP_COUNT_RS) -.equ PRE_WRITES_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_RS - PRE_WRITES_RS) +.equ LOOP_COUNT_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_RS - DMA_LOOP_COUNT_RS) +.equ PRE_WRITES_TO_MOPS_BLOCK, (MOPS_BLOCK_WORDS_RS - DMA_PRE_WRITES_RS) # Additional encode constants for fast_dma_encode .equ DMA_LPRE_COUNT_RS, 32 .equ DMA_FILL_BYTE_RS, 21 .equ DMA_FILL_BITS9_MASK, 0x1FF -.equ ENCODE_MEMSET_ZERO, REQUIRES_DMA_MASK -.equ ENCODE_MEMSET_ALIGNED_NO_COUNT_M8, REQUIRES_DMA_MASK | (1 << PRE_WRITES_RS) +.equ ENCODE_MEMSET_ZERO, DMA_REQUIRES_DMA_MASK +.equ ENCODE_MEMSET_ALIGNED_NO_COUNT_M8, DMA_REQUIRES_DMA_MASK | (1 << DMA_PRE_WRITES_RS) diff --git a/emulator-asm/src/dma/fast_dma_encode.asm b/emulator-asm/src/dma/fast_dma_encode.asm index 4361216fb..1397a3ffb 100644 --- a/emulator-asm/src/dma/fast_dma_encode.asm +++ b/emulator-asm/src/dma/fast_dma_encode.asm @@ -46,88 +46,46 @@ # ################################################################################ -.global fast_dma_encode_memcpy -.global fast_dma_encode_memcmp -.global fast_dma_encode_memset -.global fast_dma_encode_memset_with_byte -.global fast_dma_encode_inputcpy -.global fast_dma_encode_memcmp_with_result +.global fast_dma_memcpy_encode +.global fast_dma_memcmp_encode +.global fast_dma_memset_encode +.global fast_dma_memset_with_byte_encode +.global fast_dma_inputcpy_encode +.global fast_dma_memcmp_with_result_encode .global fast_dma_encode .section .text -# Alias for compatibility -fast_dma_encode: - jmp fast_dma_encode_memcpy - -fast_dma_encode_memcpy: - mov rax, rdi - and rax, 0x07 # dst_offset (0-7) - shl rax, 7 # dst_offset << 7 - - mov r8, rsi - and r8, 0x07 # src_offset (0-7) - shl r8, 4 # src_offset << 4 - - or rax, r8 # combine dst and src offsets - - # Calculate table_count - mov r8, rdx - cmp r8, 16 - jb .L_memcpy_count_lt_16 - - # count >= 16: table_count = (count & 0x07) | 0x08 - and r8, 0x07 - or r8, 0x08 - -.L_memcpy_count_lt_16: - or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count - - # Look up encoded value in table (direct access since it's in the same file) - mov rax, [fast_dma_encode_table + rax * 8] - - # Add (count >> 3) to result - mov r8, rdx - shl r8, DMA_LPRE_COUNT_RS # r8 = count << 29 - add rax, r8 # result += (count << 29) - - ret +.include "dma_constants.inc" +.include "fast_dma_encode_macro.inc" -# PARAMETERS: +# PARAMETERS (System V AMD64 ABI): # rdi = dst (u64) - Destination address +# rsi = src (u64) - Source address # rdx = count (usize) - Number of bytes to copy +# RESULT rax = encoded value +fast_dma_memcpy_encode: + FAST_DMA_ENCODE + ret -fast_dma_encode_inputcpy: -fast_dma_encode_no_src: - mov rax, rdi - and rax, 0x07 # dst_offset (0-7) - shl rax, 4 # dst_offset << 7 - - # Calculate table_count - mov r8, rdx - cmp r8, 16 - jb .L_fast_dma_encode_inputcpy_count_lt_16 - - # count >= 16: table_count = (count & 0x07) | 0x08 - and r8, 0x07 - or r8, 0x08 - -.L_fast_dma_encode_inputcpy_count_lt_16: - or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count - - # Look up encoded value in table (direct access since it's in the same file) - mov rax, [fast_dma_encode_no_src_table + rax * 8] - - # Add (count >> 3) to result - mov r8, rdx - shl r8, DMA_LPRE_COUNT_RS # r8 = count << 29 - add rax, r8 # result += (count << 29) - +# PARAMETERS (System V AMD64 ABI): +# rdi = dst (u64) - Destination address +# rsi = src (u64) - Source address +# rdx = count (usize) - Number of bytes to copy +# RESULT rax = encoded value +# NOTE: This function don't encode the result, only take in consideration to calculate +# NOTE: FAST_ENCODE_TABLE_WO_NEQ_SIZE ==> DMA_REQUIRES_DMA_MASK +fast_dma_memcmp_neq_encode: + FAST_DMA_ENCODE_MEMCMP FAST_ENCODE_TABLE_WO_NEQ_SIZE ret -fast_dma_encode_memcmp: - call fast_dma_encode_memcpy # Get the same encoding as memcpy for pre/post counts and offsets - or rax, REQUIRES_DMA_MASK - +# PARAMETERS (System V AMD64 ABI): +# rdi = dst (u64) - Destination address +# rsi = src (u64) - Source address +# rdx = count (usize) - Number of bytes to copy +# RESULT rax = encoded value +# NOTE: This function don't encode the result, only take in consideration to calculate +fast_dma_memcmp_eq_encode: + FAST_DMA_ENCODE_MEMCMP 0 ret # PARAMETERS (System V AMD64 ABI): @@ -135,20 +93,37 @@ fast_dma_encode_memcmp: # rsi = src (u64) - Source address # rdx = count (usize) - Number of bytes to copy # r9 = result (9 bits) - NOTE: value will be modified -fast_dma_encode_memcmp_with_result: - call fast_dma_encode_memcpy # Get the same encoding as memcpy for pre/post counts and offsets - or rax, REQUIRES_DMA_MASK +# RESULT rax = encoded value +fast_dma_memcmp_encode: and r9, DMA_FILL_BITS9_MASK # Ensure result is in lower 9 bits + jz .L_fast_dma_memcmp_encode_eq + FAST_DMA_ENCODE_MEMCMP FAST_ENCODE_TABLE_WO_NEQ_SIZE shl r9, DMA_FILL_BYTE_RS # r8 has the result byte in the lower 8 bits or rax, r9 ret +.L_fast_dma_memcmp_encode_eq: + FAST_DMA_ENCODE_MEMCMP 0 + ret + + +# PARAMETERS: +# rdi = dst (u64) - Destination address +# rdx = count (usize) - Number of bytes to copy + + +fast_dma_inputcpy_encode: +fast_dma_no_src_encode: + FAST_DMA_ENCODE_NO_SRC + ret + + # PARAMETERS (System V AMD64 ABI): # rdi = dst (u64) - Destination address # rsi = fill byte - Source address # rdx = count (usize) - Number of bytes to copy -fast_dma_encode_memset_with_byte: - call fast_dma_encode_no_src +fast_dma_memset_with_byte_encode: + FAST_DMA_ENCODE_NO_SRC movzx r9, sil shl r9, DMA_FILL_BYTE_RS # r8 has the result byte in the lower 8 bits or rax, r9 diff --git a/emulator-asm/src/dma/fast_dma_encode_macro.inc b/emulator-asm/src/dma/fast_dma_encode_macro.inc new file mode 100644 index 000000000..b0d056a63 --- /dev/null +++ b/emulator-asm/src/dma/fast_dma_encode_macro.inc @@ -0,0 +1,114 @@ +.intel_syntax noprefix +.code64 + +.extern fast_dma_encode_no_src_table +.extern fast_dma_encode_table +.extern fast_dma_encode_memcmp_table + +.macro FAST_DMA_ENCODE + mov rax, rdi + and rax, 0x07 # dst_offset (0-7) + shl rax, 7 # dst_offset << 7 + + mov r8, rsi + and r8, 0x07 # src_offset (0-7) + shl r8, 4 # src_offset << 4 + + or rax, r8 # combine dst and src offsets + + # Calculate table_count + mov r8, rdx + cmp r8, 16 + jb 1f + + # count >= 16: table_count = (count & 0x07) | 0x08 + and r8, 0x07 + or r8, 0x08 + +1: + or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count + + # Look up encoded value in table (direct access since it's in the same file) + mov rax, [fast_dma_encode_table + rax * 8] + + # Add (count >> 3) to result + mov r8, rdx + shl r8, DMA_LPRE_COUNT_RS + add rax, r8 +.endm + +.macro FAST_DMA_ENCODE_NO_SRC + mov rax, rdi + and rax, 0x07 # dst_offset (0-7) + shl rax, 4 # dst_offset << 4 + + # Calculate table_count + mov r8, rdx + cmp r8, 16 + jb 1f + + # count >= 16: table_count = (count & 0x07) | 0x08 + and r8, 0x07 + or r8, 0x08 + +1: + or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count + + mov rax, [fast_dma_encode_no_src_table + rax * 8] + + # Add (count >> 3) to result + mov r8, rdx + shl r8, DMA_LPRE_COUNT_RS + add rax, r8 +.endm + + +.macro FAST_DMA_ENCODE_MEMCMP extra_table_offset=0 + mov rax, rdi + and rax, 0x07 # dst_offset (0-7) + shl rax, 7 # dst_offset << 7 + + mov r8, rsi + and r8, 0x07 # src_offset (0-7) + shl r8, 4 # src_offset << 4 + + or rax, r8 # combine dst and src offsets + + # Calculate table_count + mov r8, rdx + cmp r8, 16 + jb 1f + + # count >= 16: table_count = (count & 0x07) | 0x08 + and r8, 0x07 + or r8, 0x08 + +1: + or rax, r8 # rax = index = (dst<<7) + (src<<4) + table_count + + # Look up encoded value in table (direct access since it's in the same file) + mov rax, [fast_dma_encode_memcmp_table + rax * 8 + \extra_table_offset * 8] + + # Add (count >> 3) to result + mov r8, rdx + shl r8, DMA_LPRE_COUNT_RS + add rax, r8 +.endm + +# always a zero encoded requires a DMA machine, because It is the only machine +# that can formally verify a DMA operation with count = 0. +.macro FAST_DMA_ENCODE_COUNT_ZERO use_src=1 + mov rax, rdi + and rax, 0x07 # dst_offset (0-7) + shl rax, DMA_DST_OFFSET_RS # dst_offset << 7 + + .if \use_src + mov r8, rsi + and r8, 0x07 # src_offset (0-7) + shl r8, DMA_SRC_OFFSET_RS # src_offset << 4 + lea rax, [rax + r8 + DMA_REQUIRES_DMA_MASK] + .else + or rax, DMA_REQUIRES_DMA_MASK + .endif +.endm + \ No newline at end of file diff --git a/emulator-asm/src/dma/fast_dma_encode_table.asm b/emulator-asm/src/dma/fast_dma_encode_table.asm index 2436e5f3a..30597027c 100644 --- a/emulator-asm/src/dma/fast_dma_encode_table.asm +++ b/emulator-asm/src/dma/fast_dma_encode_table.asm @@ -1,6 +1,10 @@ .intel_syntax noprefix .code64 +.globl fast_dma_encode_no_src_table +.globl fast_dma_encode_table +.globl fast_dma_encode_memcmp_table + .section .data # generated with precompiles/helpers/src/dma # asm_fast_encode_table test @@ -38,40 +42,41 @@ fast_dma_encode_no_src_table: .quad 0xfffffffd40000f19, 0xFFFFFFFC40000F21, 0xFFFFFFFB40000F29, 0xFFFFFFFA40000F31 # 116 - 119 D3 C4 .quad 0xfffffff940000f39, 0x0000000040000E81, 0xFFFFFFFF40000F09, 0xFFFFFFFE40000F11 # 120 - 123 D3 C8 .quad 0xfffffffd40000f19, 0xFFFFFFFC40000F21, 0xFFFFFFFB40000F29, 0xFFFFFFFA40000F31 # 124 - 127 D3 C12 + fast_dma_encode_table: .quad 0x0000000040000000, 0xFFFFFFFF40020088, 0xFFFFFFFE40020090, 0xFFFFFFFD40020098 # 0 - 3 D0 S0 C0 .quad 0xfffffffc400200a0, 0xFFFFFFFB400200A8, 0xFFFFFFFA400200B0, 0xFFFFFFF9400200B8 # 4 - 7 D0 S0 C4 .quad 0x0000000000000000, 0xFFFFFFFF40020088, 0xFFFFFFFE40020090, 0xFFFFFFFD40020098 # 8 - 11 D0 S0 C8 .quad 0xfffffffc400200a0, 0xFFFFFFFB400200A8, 0xFFFFFFFA400200B0, 0xFFFFFFF9400200B8 # 12 - 15 D0 S0 C12 - .quad 0x0000000040101000, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 16 - 19 D0 S1 C0 + .quad 0x0000000040001000, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 16 - 19 D0 S1 C0 .quad 0xfffffffc401210a0, 0xFFFFFFFB401210A8, 0xFFFFFFFA401210B0, 0xFFFFFFF9401210B8 # 20 - 23 D0 S1 C4 .quad 0x0000000000121000, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 24 - 27 D0 S1 C8 .quad 0xfffffffc401210a0, 0xFFFFFFFB401210A8, 0xFFFFFFFA401210B0, 0xFFFFFFF9401210B8 # 28 - 31 D0 S1 C12 - .quad 0x0000000040102000, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 32 - 35 D0 S2 C0 + .quad 0x0000000040002000, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 32 - 35 D0 S2 C0 .quad 0xfffffffc401220a0, 0xFFFFFFFB401220A8, 0xFFFFFFFA401220B0, 0xFFFFFFF9401520B8 # 36 - 39 D0 S2 C4 .quad 0x0000000000122000, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 40 - 43 D0 S2 C8 .quad 0xfffffffc401220a0, 0xFFFFFFFB401220A8, 0xFFFFFFFA401220B0, 0xFFFFFFF9401520B8 # 44 - 47 D0 S2 C12 - .quad 0x0000000040103000, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 48 - 51 D0 S3 C0 + .quad 0x0000000040003000, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 48 - 51 D0 S3 C0 .quad 0xfffffffc401230a0, 0xFFFFFFFB401230A8, 0xFFFFFFFA401530B0, 0xFFFFFFF9401530B8 # 52 - 55 D0 S3 C4 .quad 0x0000000000123000, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 56 - 59 D0 S3 C8 .quad 0xfffffffc401230a0, 0xFFFFFFFB401230A8, 0xFFFFFFFA401530B0, 0xFFFFFFF9401530B8 # 60 - 63 D0 S3 C12 - .quad 0x0000000040104000, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 64 - 67 D0 S4 C0 + .quad 0x0000000040004000, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 64 - 67 D0 S4 C0 .quad 0xfffffffc401240a0, 0xFFFFFFFB401540A8, 0xFFFFFFFA401540B0, 0xFFFFFFF9401540B8 # 68 - 71 D0 S4 C4 .quad 0x0000000000124000, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 72 - 75 D0 S4 C8 .quad 0xfffffffc401240a0, 0xFFFFFFFB401540A8, 0xFFFFFFFA401540B0, 0xFFFFFFF9401540B8 # 76 - 79 D0 S4 C12 - .quad 0x0000000040105000, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 80 - 83 D0 S5 C0 + .quad 0x0000000040005000, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 80 - 83 D0 S5 C0 .quad 0xfffffffc401550a0, 0xFFFFFFFB401550A8, 0xFFFFFFFA401550B0, 0xFFFFFFF9401550B8 # 84 - 87 D0 S5 C4 .quad 0x0000000000125000, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 88 - 91 D0 S5 C8 .quad 0xfffffffc401550a0, 0xFFFFFFFB401550A8, 0xFFFFFFFA401550B0, 0xFFFFFFF9401550B8 # 92 - 95 D0 S5 C12 - .quad 0x0000000040106000, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 96 - 99 D0 S6 C0 + .quad 0x0000000040006000, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 96 - 99 D0 S6 C0 .quad 0xfffffffc401560a0, 0xFFFFFFFB401560A8, 0xFFFFFFFA401560B0, 0xFFFFFFF9401560B8 # 100 - 103 D0 S6 C4 .quad 0x0000000000126000, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 104 - 107 D0 S6 C8 .quad 0xfffffffc401560a0, 0xFFFFFFFB401560A8, 0xFFFFFFFA401560B0, 0xFFFFFFF9401560B8 # 108 - 111 D0 S6 C12 - .quad 0x0000000040107000, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 112 - 115 D0 S7 C0 + .quad 0x0000000040007000, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 112 - 115 D0 S7 C0 .quad 0xfffffffc401570a0, 0xFFFFFFFB401570A8, 0xFFFFFFFA401570B0, 0xFFFFFFF9401570B8 # 116 - 119 D0 S7 C4 .quad 0x0000000000127000, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 120 - 123 D0 S7 C8 .quad 0xfffffffc401570a0, 0xFFFFFFFB401570A8, 0xFFFFFFFA401570B0, 0xFFFFFFF9401570B8 # 124 - 127 D0 S7 C12 - .quad 0x0000000040100200, 0x0000000040120281, 0x0000000040120282, 0x0000000040120283 # 128 - 131 D1 S0 C0 + .quad 0x0000000040000200, 0x0000000040120281, 0x0000000040120282, 0x0000000040120283 # 128 - 131 D1 S0 C0 .quad 0x0000000040120284, 0x0000000040120285, 0x0000000040120286, 0x0000000040120287 # 132 - 135 D1 S0 C4 .quad 0xffffffff4012030f, 0xFFFFFFFE40150317, 0xFFFFFFFD4015031F, 0xFFFFFFFC40150327 # 136 - 139 D1 S0 C8 .quad 0xfffffffb4015032f, 0xFFFFFFFA40150337, 0xFFFFFFF94015033F, 0x0000000040120287 # 140 - 143 D1 S0 C12 @@ -79,35 +84,35 @@ fast_dma_encode_table: .quad 0x0000000040021284, 0x0000000040021285, 0x0000000040021286, 0x00000000400A1287 # 148 - 151 D1 S1 C4 .quad 0xffffffff400c130f, 0xFFFFFFFE400C1317, 0xFFFFFFFD400C131F, 0xFFFFFFFC400C1327 # 152 - 155 D1 S1 C8 .quad 0xfffffffb400c132f, 0xFFFFFFFA400C1337, 0xFFFFFFF9400C133F, 0x00000000400A1287 # 156 - 159 D1 S1 C12 - .quad 0x0000000040102200, 0x0000000040122281, 0x0000000040122282, 0x0000000040122283 # 160 - 163 D1 S2 C0 + .quad 0x0000000040002200, 0x0000000040122281, 0x0000000040122282, 0x0000000040122283 # 160 - 163 D1 S2 C0 .quad 0x0000000040122284, 0x0000000040122285, 0x00000000401A2286, 0x00000000401CA287 # 164 - 167 D1 S2 C4 .quad 0xffffffff401ca30f, 0xFFFFFFFE401CA317, 0xFFFFFFFD401CA31F, 0xFFFFFFFC401CA327 # 168 - 171 D1 S2 C8 .quad 0xfffffffb401ca32f, 0xFFFFFFFA401CA337, 0xFFFFFFF9401CA33F, 0x00000000401CA287 # 172 - 175 D1 S2 C12 - .quad 0x0000000040103200, 0x0000000040123281, 0x0000000040123282, 0x0000000040123283 # 176 - 179 D1 S3 C0 + .quad 0x0000000040003200, 0x0000000040123281, 0x0000000040123282, 0x0000000040123283 # 176 - 179 D1 S3 C0 .quad 0x0000000040123284, 0x00000000401A3285, 0x00000000401CB286, 0x00000000401CB287 # 180 - 183 D1 S3 C4 .quad 0xffffffff401cb30f, 0xFFFFFFFE401CB317, 0xFFFFFFFD401CB31F, 0xFFFFFFFC401CB327 # 184 - 187 D1 S3 C8 .quad 0xfffffffb401cb32f, 0xFFFFFFFA401CB337, 0xFFFFFFF9401FB33F, 0x00000000401CB287 # 188 - 191 D1 S3 C12 - .quad 0x0000000040104200, 0x0000000040124281, 0x0000000040124282, 0x0000000040124283 # 192 - 195 D1 S4 C0 + .quad 0x0000000040004200, 0x0000000040124281, 0x0000000040124282, 0x0000000040124283 # 192 - 195 D1 S4 C0 .quad 0x00000000401a4284, 0x00000000401CC285, 0x00000000401CC286, 0x00000000401CC287 # 196 - 199 D1 S4 C4 .quad 0xffffffff401cc30f, 0xFFFFFFFE401CC317, 0xFFFFFFFD401CC31F, 0xFFFFFFFC401CC327 # 200 - 203 D1 S4 C8 .quad 0xfffffffb401cc32f, 0xFFFFFFFA401FC337, 0xFFFFFFF9401FC33F, 0x00000000401CC287 # 204 - 207 D1 S4 C12 - .quad 0x0000000040105200, 0x0000000040125281, 0x0000000040125282, 0x00000000401A5283 # 208 - 211 D1 S5 C0 + .quad 0x0000000040005200, 0x0000000040125281, 0x0000000040125282, 0x00000000401A5283 # 208 - 211 D1 S5 C0 .quad 0x00000000401cd284, 0x00000000401CD285, 0x00000000401CD286, 0x00000000401CD287 # 212 - 215 D1 S5 C4 .quad 0xffffffff401cd30f, 0xFFFFFFFE401CD317, 0xFFFFFFFD401CD31F, 0xFFFFFFFC401CD327 # 216 - 219 D1 S5 C8 .quad 0xfffffffb401fd32f, 0xFFFFFFFA401FD337, 0xFFFFFFF9401FD33F, 0x00000000401CD287 # 220 - 223 D1 S5 C12 - .quad 0x0000000040106200, 0x0000000040126281, 0x00000000401A6282, 0x00000000401CE283 # 224 - 227 D1 S6 C0 + .quad 0x0000000040006200, 0x0000000040126281, 0x00000000401A6282, 0x00000000401CE283 # 224 - 227 D1 S6 C0 .quad 0x00000000401ce284, 0x00000000401CE285, 0x00000000401CE286, 0x00000000401CE287 # 228 - 231 D1 S6 C4 .quad 0xffffffff401ce30f, 0xFFFFFFFE401CE317, 0xFFFFFFFD401CE31F, 0xFFFFFFFC401FE327 # 232 - 235 D1 S6 C8 .quad 0xfffffffb401fe32f, 0xFFFFFFFA401FE337, 0xFFFFFFF9401FE33F, 0x00000000401CE287 # 236 - 239 D1 S6 C12 - .quad 0x0000000040107200, 0x00000000401A7281, 0x00000000401CF282, 0x00000000401CF283 # 240 - 243 D1 S7 C0 + .quad 0x0000000040007200, 0x00000000401A7281, 0x00000000401CF282, 0x00000000401CF283 # 240 - 243 D1 S7 C0 .quad 0x00000000401cf284, 0x00000000401CF285, 0x00000000401CF286, 0x00000000401CF287 # 244 - 247 D1 S7 C4 .quad 0xffffffff401cf30f, 0xFFFFFFFE401CF317, 0xFFFFFFFD401FF31F, 0xFFFFFFFC401FF327 # 248 - 251 D1 S7 C8 .quad 0xfffffffb401ff32f, 0xFFFFFFFA401FF337, 0xFFFFFFF9401FF33F, 0x00000000401CF287 # 252 - 255 D1 S7 C12 - .quad 0x0000000040100400, 0x0000000040120481, 0x0000000040120482, 0x0000000040120483 # 256 - 259 D2 S0 C0 + .quad 0x0000000040000400, 0x0000000040120481, 0x0000000040120482, 0x0000000040120483 # 256 - 259 D2 S0 C0 .quad 0x0000000040120484, 0x0000000040120485, 0x0000000040120486, 0xFFFFFFFF4012050E # 260 - 263 D2 S0 C4 .quad 0xfffffffe40120516, 0xFFFFFFFD4015051E, 0xFFFFFFFC40150526, 0xFFFFFFFB4015052E # 264 - 267 D2 S0 C8 .quad 0xfffffffa40150536, 0xFFFFFFF94015053E, 0x0000000040120486, 0xFFFFFFFF4012050E # 268 - 271 D2 S0 C12 - .quad 0x0000000040101400, 0x0000000040121481, 0x0000000040121482, 0x0000000040121483 # 272 - 275 D2 S1 C0 + .quad 0x0000000040001400, 0x0000000040121481, 0x0000000040121482, 0x0000000040121483 # 272 - 275 D2 S1 C0 .quad 0x0000000040121484, 0x0000000040121485, 0x0000000040121486, 0xFFFFFFFF4012150E # 276 - 279 D2 S1 C4 .quad 0xfffffffe40151516, 0xFFFFFFFD4015151E, 0xFFFFFFFC40151526, 0xFFFFFFFB4015152E # 280 - 283 D2 S1 C8 .quad 0xfffffffa40151536, 0xFFFFFFF94015153E, 0x0000000040121486, 0xFFFFFFFF4012150E # 284 - 287 D2 S1 C12 @@ -115,35 +120,35 @@ fast_dma_encode_table: .quad 0x0000000040022484, 0x0000000040022485, 0x00000000400A2486, 0xFFFFFFFF400C250E # 292 - 295 D2 S2 C4 .quad 0xfffffffe400c2516, 0xFFFFFFFD400C251E, 0xFFFFFFFC400C2526, 0xFFFFFFFB400C252E # 296 - 299 D2 S2 C8 .quad 0xfffffffa400c2536, 0xFFFFFFF9400C253E, 0x00000000400A2486, 0xFFFFFFFF400C250E # 300 - 303 D2 S2 C12 - .quad 0x0000000040103400, 0x0000000040123481, 0x0000000040123482, 0x0000000040123483 # 304 - 307 D2 S3 C0 + .quad 0x0000000040003400, 0x0000000040123481, 0x0000000040123482, 0x0000000040123483 # 304 - 307 D2 S3 C0 .quad 0x0000000040123484, 0x00000000401A3485, 0x00000000401CB486, 0xFFFFFFFF401CB50E # 308 - 311 D2 S3 C4 .quad 0xfffffffe401cb516, 0xFFFFFFFD401CB51E, 0xFFFFFFFC401CB526, 0xFFFFFFFB401CB52E # 312 - 315 D2 S3 C8 .quad 0xfffffffa401cb536, 0xFFFFFFF9401CB53E, 0x00000000401CB486, 0xFFFFFFFF401CB50E # 316 - 319 D2 S3 C12 - .quad 0x0000000040104400, 0x0000000040124481, 0x0000000040124482, 0x0000000040124483 # 320 - 323 D2 S4 C0 + .quad 0x0000000040004400, 0x0000000040124481, 0x0000000040124482, 0x0000000040124483 # 320 - 323 D2 S4 C0 .quad 0x00000000401a4484, 0x00000000401CC485, 0x00000000401CC486, 0xFFFFFFFF401CC50E # 324 - 327 D2 S4 C4 .quad 0xfffffffe401cc516, 0xFFFFFFFD401CC51E, 0xFFFFFFFC401CC526, 0xFFFFFFFB401CC52E # 328 - 331 D2 S4 C8 .quad 0xfffffffa401cc536, 0xFFFFFFF9401FC53E, 0x00000000401CC486, 0xFFFFFFFF401CC50E # 332 - 335 D2 S4 C12 - .quad 0x0000000040105400, 0x0000000040125481, 0x0000000040125482, 0x00000000401A5483 # 336 - 339 D2 S5 C0 + .quad 0x0000000040005400, 0x0000000040125481, 0x0000000040125482, 0x00000000401A5483 # 336 - 339 D2 S5 C0 .quad 0x00000000401cd484, 0x00000000401CD485, 0x00000000401CD486, 0xFFFFFFFF401CD50E # 340 - 343 D2 S5 C4 .quad 0xfffffffe401cd516, 0xFFFFFFFD401CD51E, 0xFFFFFFFC401CD526, 0xFFFFFFFB401CD52E # 344 - 347 D2 S5 C8 .quad 0xfffffffa401fd536, 0xFFFFFFF9401FD53E, 0x00000000401CD486, 0xFFFFFFFF401CD50E # 348 - 351 D2 S5 C12 - .quad 0x0000000040106400, 0x0000000040126481, 0x00000000401A6482, 0x00000000401CE483 # 352 - 355 D2 S6 C0 + .quad 0x0000000040006400, 0x0000000040126481, 0x00000000401A6482, 0x00000000401CE483 # 352 - 355 D2 S6 C0 .quad 0x00000000401ce484, 0x00000000401CE485, 0x00000000401CE486, 0xFFFFFFFF401CE50E # 356 - 359 D2 S6 C4 .quad 0xfffffffe401ce516, 0xFFFFFFFD401CE51E, 0xFFFFFFFC401CE526, 0xFFFFFFFB401FE52E # 360 - 363 D2 S6 C8 .quad 0xfffffffa401fe536, 0xFFFFFFF9401FE53E, 0x00000000401CE486, 0xFFFFFFFF401CE50E # 364 - 367 D2 S6 C12 - .quad 0x0000000040107400, 0x00000000401A7481, 0x00000000401CF482, 0x00000000401CF483 # 368 - 371 D2 S7 C0 + .quad 0x0000000040007400, 0x00000000401A7481, 0x00000000401CF482, 0x00000000401CF483 # 368 - 371 D2 S7 C0 .quad 0x00000000401cf484, 0x00000000401CF485, 0x00000000401CF486, 0xFFFFFFFF401CF50E # 372 - 375 D2 S7 C4 .quad 0xfffffffe401cf516, 0xFFFFFFFD401CF51E, 0xFFFFFFFC401FF526, 0xFFFFFFFB401FF52E # 376 - 379 D2 S7 C8 .quad 0xfffffffa401ff536, 0xFFFFFFF9401FF53E, 0x00000000401CF486, 0xFFFFFFFF401CF50E # 380 - 383 D2 S7 C12 - .quad 0x0000000040100600, 0x0000000040120681, 0x0000000040120682, 0x0000000040120683 # 384 - 387 D3 S0 C0 + .quad 0x0000000040000600, 0x0000000040120681, 0x0000000040120682, 0x0000000040120683 # 384 - 387 D3 S0 C0 .quad 0x0000000040120684, 0x0000000040120685, 0xFFFFFFFF4012070D, 0xFFFFFFFE40120715 # 388 - 391 D3 S0 C4 .quad 0xfffffffd4012071d, 0xFFFFFFFC40150725, 0xFFFFFFFB4015072D, 0xFFFFFFFA40150735 # 392 - 395 D3 S0 C8 .quad 0xfffffff94015073d, 0x0000000040120685, 0xFFFFFFFF4012070D, 0xFFFFFFFE40120715 # 396 - 399 D3 S0 C12 - .quad 0x0000000040101600, 0x0000000040121681, 0x0000000040121682, 0x0000000040121683 # 400 - 403 D3 S1 C0 + .quad 0x0000000040001600, 0x0000000040121681, 0x0000000040121682, 0x0000000040121683 # 400 - 403 D3 S1 C0 .quad 0x0000000040121684, 0x0000000040121685, 0xFFFFFFFF4012170D, 0xFFFFFFFE40121715 # 404 - 407 D3 S1 C4 .quad 0xfffffffd4015171d, 0xFFFFFFFC40151725, 0xFFFFFFFB4015172D, 0xFFFFFFFA40151735 # 408 - 411 D3 S1 C8 .quad 0xfffffff94015173d, 0x0000000040121685, 0xFFFFFFFF4012170D, 0xFFFFFFFE40121715 # 412 - 415 D3 S1 C12 - .quad 0x0000000040102600, 0x0000000040122681, 0x0000000040122682, 0x0000000040122683 # 416 - 419 D3 S2 C0 + .quad 0x0000000040002600, 0x0000000040122681, 0x0000000040122682, 0x0000000040122683 # 416 - 419 D3 S2 C0 .quad 0x0000000040122684, 0x0000000040122685, 0xFFFFFFFF4012270D, 0xFFFFFFFE40152715 # 420 - 423 D3 S2 C4 .quad 0xfffffffd4015271d, 0xFFFFFFFC40152725, 0xFFFFFFFB4015272D, 0xFFFFFFFA40152735 # 424 - 427 D3 S2 C8 .quad 0xfffffff94015273d, 0x0000000040122685, 0xFFFFFFFF4012270D, 0xFFFFFFFE40152715 # 428 - 431 D3 S2 C12 @@ -151,35 +156,35 @@ fast_dma_encode_table: .quad 0x0000000040023684, 0x00000000400A3685, 0xFFFFFFFF400C370D, 0xFFFFFFFE400C3715 # 436 - 439 D3 S3 C4 .quad 0xfffffffd400c371d, 0xFFFFFFFC400C3725, 0xFFFFFFFB400C372D, 0xFFFFFFFA400C3735 # 440 - 443 D3 S3 C8 .quad 0xfffffff9400c373d, 0x00000000400A3685, 0xFFFFFFFF400C370D, 0xFFFFFFFE400C3715 # 444 - 447 D3 S3 C12 - .quad 0x0000000040104600, 0x0000000040124681, 0x0000000040124682, 0x0000000040124683 # 448 - 451 D3 S4 C0 + .quad 0x0000000040004600, 0x0000000040124681, 0x0000000040124682, 0x0000000040124683 # 448 - 451 D3 S4 C0 .quad 0x00000000401a4684, 0x00000000401CC685, 0xFFFFFFFF401CC70D, 0xFFFFFFFE401CC715 # 452 - 455 D3 S4 C4 .quad 0xfffffffd401cc71d, 0xFFFFFFFC401CC725, 0xFFFFFFFB401CC72D, 0xFFFFFFFA401CC735 # 456 - 459 D3 S4 C8 .quad 0xfffffff9401cc73d, 0x00000000401CC685, 0xFFFFFFFF401CC70D, 0xFFFFFFFE401CC715 # 460 - 463 D3 S4 C12 - .quad 0x0000000040105600, 0x0000000040125681, 0x0000000040125682, 0x00000000401A5683 # 464 - 467 D3 S5 C0 + .quad 0x0000000040005600, 0x0000000040125681, 0x0000000040125682, 0x00000000401A5683 # 464 - 467 D3 S5 C0 .quad 0x00000000401cd684, 0x00000000401CD685, 0xFFFFFFFF401CD70D, 0xFFFFFFFE401CD715 # 468 - 471 D3 S5 C4 .quad 0xfffffffd401cd71d, 0xFFFFFFFC401CD725, 0xFFFFFFFB401CD72D, 0xFFFFFFFA401CD735 # 472 - 475 D3 S5 C8 .quad 0xfffffff9401fd73d, 0x00000000401CD685, 0xFFFFFFFF401CD70D, 0xFFFFFFFE401CD715 # 476 - 479 D3 S5 C12 - .quad 0x0000000040106600, 0x0000000040126681, 0x00000000401A6682, 0x00000000401CE683 # 480 - 483 D3 S6 C0 + .quad 0x0000000040006600, 0x0000000040126681, 0x00000000401A6682, 0x00000000401CE683 # 480 - 483 D3 S6 C0 .quad 0x00000000401ce684, 0x00000000401CE685, 0xFFFFFFFF401CE70D, 0xFFFFFFFE401CE715 # 484 - 487 D3 S6 C4 .quad 0xfffffffd401ce71d, 0xFFFFFFFC401CE725, 0xFFFFFFFB401CE72D, 0xFFFFFFFA401FE735 # 488 - 491 D3 S6 C8 .quad 0xfffffff9401fe73d, 0x00000000401CE685, 0xFFFFFFFF401CE70D, 0xFFFFFFFE401CE715 # 492 - 495 D3 S6 C12 - .quad 0x0000000040107600, 0x00000000401A7681, 0x00000000401CF682, 0x00000000401CF683 # 496 - 499 D3 S7 C0 + .quad 0x0000000040007600, 0x00000000401A7681, 0x00000000401CF682, 0x00000000401CF683 # 496 - 499 D3 S7 C0 .quad 0x00000000401cf684, 0x00000000401CF685, 0xFFFFFFFF401CF70D, 0xFFFFFFFE401CF715 # 500 - 503 D3 S7 C4 .quad 0xfffffffd401cf71d, 0xFFFFFFFC401CF725, 0xFFFFFFFB401FF72D, 0xFFFFFFFA401FF735 # 504 - 507 D3 S7 C8 .quad 0xfffffff9401ff73d, 0x00000000401CF685, 0xFFFFFFFF401CF70D, 0xFFFFFFFE401CF715 # 508 - 511 D3 S7 C12 - .quad 0x0000000040100800, 0x0000000040120881, 0x0000000040120882, 0x0000000040120883 # 512 - 515 D4 S0 C0 + .quad 0x0000000040000800, 0x0000000040120881, 0x0000000040120882, 0x0000000040120883 # 512 - 515 D4 S0 C0 .quad 0x0000000040120884, 0xFFFFFFFF4012090C, 0xFFFFFFFE40120914, 0xFFFFFFFD4012091C # 516 - 519 D4 S0 C4 .quad 0xfffffffc40120924, 0xFFFFFFFB4015092C, 0xFFFFFFFA40150934, 0xFFFFFFF94015093C # 520 - 523 D4 S0 C8 .quad 0x0000000040120884, 0xFFFFFFFF4012090C, 0xFFFFFFFE40120914, 0xFFFFFFFD4012091C # 524 - 527 D4 S0 C12 - .quad 0x0000000040101800, 0x0000000040121881, 0x0000000040121882, 0x0000000040121883 # 528 - 531 D4 S1 C0 + .quad 0x0000000040001800, 0x0000000040121881, 0x0000000040121882, 0x0000000040121883 # 528 - 531 D4 S1 C0 .quad 0x0000000040121884, 0xFFFFFFFF4012190C, 0xFFFFFFFE40121914, 0xFFFFFFFD4012191C # 532 - 535 D4 S1 C4 .quad 0xfffffffc40151924, 0xFFFFFFFB4015192C, 0xFFFFFFFA40151934, 0xFFFFFFF94015193C # 536 - 539 D4 S1 C8 .quad 0x0000000040121884, 0xFFFFFFFF4012190C, 0xFFFFFFFE40121914, 0xFFFFFFFD4012191C # 540 - 543 D4 S1 C12 - .quad 0x0000000040102800, 0x0000000040122881, 0x0000000040122882, 0x0000000040122883 # 544 - 547 D4 S2 C0 + .quad 0x0000000040002800, 0x0000000040122881, 0x0000000040122882, 0x0000000040122883 # 544 - 547 D4 S2 C0 .quad 0x0000000040122884, 0xFFFFFFFF4012290C, 0xFFFFFFFE40122914, 0xFFFFFFFD4015291C # 548 - 551 D4 S2 C4 .quad 0xfffffffc40152924, 0xFFFFFFFB4015292C, 0xFFFFFFFA40152934, 0xFFFFFFF94015293C # 552 - 555 D4 S2 C8 .quad 0x0000000040122884, 0xFFFFFFFF4012290C, 0xFFFFFFFE40122914, 0xFFFFFFFD4015291C # 556 - 559 D4 S2 C12 - .quad 0x0000000040103800, 0x0000000040123881, 0x0000000040123882, 0x0000000040123883 # 560 - 563 D4 S3 C0 + .quad 0x0000000040003800, 0x0000000040123881, 0x0000000040123882, 0x0000000040123883 # 560 - 563 D4 S3 C0 .quad 0x0000000040123884, 0xFFFFFFFF4012390C, 0xFFFFFFFE40153914, 0xFFFFFFFD4015391C # 564 - 567 D4 S3 C4 .quad 0xfffffffc40153924, 0xFFFFFFFB4015392C, 0xFFFFFFFA40153934, 0xFFFFFFF94015393C # 568 - 571 D4 S3 C8 .quad 0x0000000040123884, 0xFFFFFFFF4012390C, 0xFFFFFFFE40153914, 0xFFFFFFFD4015391C # 572 - 575 D4 S3 C12 @@ -187,35 +192,35 @@ fast_dma_encode_table: .quad 0x00000000400a4884, 0xFFFFFFFF400C490C, 0xFFFFFFFE400C4914, 0xFFFFFFFD400C491C # 580 - 583 D4 S4 C4 .quad 0xfffffffc400c4924, 0xFFFFFFFB400C492C, 0xFFFFFFFA400C4934, 0xFFFFFFF9400C493C # 584 - 587 D4 S4 C8 .quad 0x00000000400a4884, 0xFFFFFFFF400C490C, 0xFFFFFFFE400C4914, 0xFFFFFFFD400C491C # 588 - 591 D4 S4 C12 - .quad 0x0000000040105800, 0x0000000040125881, 0x0000000040125882, 0x00000000401A5883 # 592 - 595 D4 S5 C0 + .quad 0x0000000040005800, 0x0000000040125881, 0x0000000040125882, 0x00000000401A5883 # 592 - 595 D4 S5 C0 .quad 0x00000000401cd884, 0xFFFFFFFF401CD90C, 0xFFFFFFFE401CD914, 0xFFFFFFFD401CD91C # 596 - 599 D4 S5 C4 .quad 0xfffffffc401cd924, 0xFFFFFFFB401CD92C, 0xFFFFFFFA401CD934, 0xFFFFFFF9401CD93C # 600 - 603 D4 S5 C8 .quad 0x00000000401cd884, 0xFFFFFFFF401CD90C, 0xFFFFFFFE401CD914, 0xFFFFFFFD401CD91C # 604 - 607 D4 S5 C12 - .quad 0x0000000040106800, 0x0000000040126881, 0x00000000401A6882, 0x00000000401CE883 # 608 - 611 D4 S6 C0 + .quad 0x0000000040006800, 0x0000000040126881, 0x00000000401A6882, 0x00000000401CE883 # 608 - 611 D4 S6 C0 .quad 0x00000000401ce884, 0xFFFFFFFF401CE90C, 0xFFFFFFFE401CE914, 0xFFFFFFFD401CE91C # 612 - 615 D4 S6 C4 .quad 0xfffffffc401ce924, 0xFFFFFFFB401CE92C, 0xFFFFFFFA401CE934, 0xFFFFFFF9401FE93C # 616 - 619 D4 S6 C8 .quad 0x00000000401ce884, 0xFFFFFFFF401CE90C, 0xFFFFFFFE401CE914, 0xFFFFFFFD401CE91C # 620 - 623 D4 S6 C12 - .quad 0x0000000040107800, 0x00000000401A7881, 0x00000000401CF882, 0x00000000401CF883 # 624 - 627 D4 S7 C0 + .quad 0x0000000040007800, 0x00000000401A7881, 0x00000000401CF882, 0x00000000401CF883 # 624 - 627 D4 S7 C0 .quad 0x00000000401cf884, 0xFFFFFFFF401CF90C, 0xFFFFFFFE401CF914, 0xFFFFFFFD401CF91C # 628 - 631 D4 S7 C4 .quad 0xfffffffc401cf924, 0xFFFFFFFB401CF92C, 0xFFFFFFFA401FF934, 0xFFFFFFF9401FF93C # 632 - 635 D4 S7 C8 .quad 0x00000000401cf884, 0xFFFFFFFF401CF90C, 0xFFFFFFFE401CF914, 0xFFFFFFFD401CF91C # 636 - 639 D4 S7 C12 - .quad 0x0000000040100a00, 0x0000000040120A81, 0x0000000040120A82, 0x0000000040120A83 # 640 - 643 D5 S0 C0 + .quad 0x0000000040000a00, 0x0000000040120A81, 0x0000000040120A82, 0x0000000040120A83 # 640 - 643 D5 S0 C0 .quad 0xffffffff40120b0b, 0xFFFFFFFE40120B13, 0xFFFFFFFD40120B1B, 0xFFFFFFFC40120B23 # 644 - 647 D5 S0 C4 .quad 0xfffffffb40120b2b, 0xFFFFFFFA40150B33, 0xFFFFFFF940150B3B, 0x0000000040120A83 # 648 - 651 D5 S0 C8 .quad 0xffffffff40120b0b, 0xFFFFFFFE40120B13, 0xFFFFFFFD40120B1B, 0xFFFFFFFC40120B23 # 652 - 655 D5 S0 C12 - .quad 0x0000000040101a00, 0x0000000040121A81, 0x0000000040121A82, 0x0000000040121A83 # 656 - 659 D5 S1 C0 + .quad 0x0000000040001a00, 0x0000000040121A81, 0x0000000040121A82, 0x0000000040121A83 # 656 - 659 D5 S1 C0 .quad 0xffffffff40121b0b, 0xFFFFFFFE40121B13, 0xFFFFFFFD40121B1B, 0xFFFFFFFC40121B23 # 660 - 663 D5 S1 C4 .quad 0xfffffffb40151b2b, 0xFFFFFFFA40151B33, 0xFFFFFFF940151B3B, 0x0000000040121A83 # 664 - 667 D5 S1 C8 .quad 0xffffffff40121b0b, 0xFFFFFFFE40121B13, 0xFFFFFFFD40121B1B, 0xFFFFFFFC40121B23 # 668 - 671 D5 S1 C12 - .quad 0x0000000040102a00, 0x0000000040122A81, 0x0000000040122A82, 0x0000000040122A83 # 672 - 675 D5 S2 C0 + .quad 0x0000000040002a00, 0x0000000040122A81, 0x0000000040122A82, 0x0000000040122A83 # 672 - 675 D5 S2 C0 .quad 0xffffffff40122b0b, 0xFFFFFFFE40122B13, 0xFFFFFFFD40122B1B, 0xFFFFFFFC40152B23 # 676 - 679 D5 S2 C4 .quad 0xfffffffb40152b2b, 0xFFFFFFFA40152B33, 0xFFFFFFF940152B3B, 0x0000000040122A83 # 680 - 683 D5 S2 C8 .quad 0xffffffff40122b0b, 0xFFFFFFFE40122B13, 0xFFFFFFFD40122B1B, 0xFFFFFFFC40152B23 # 684 - 687 D5 S2 C12 - .quad 0x0000000040103a00, 0x0000000040123A81, 0x0000000040123A82, 0x0000000040123A83 # 688 - 691 D5 S3 C0 + .quad 0x0000000040003a00, 0x0000000040123A81, 0x0000000040123A82, 0x0000000040123A83 # 688 - 691 D5 S3 C0 .quad 0xffffffff40123b0b, 0xFFFFFFFE40123B13, 0xFFFFFFFD40153B1B, 0xFFFFFFFC40153B23 # 692 - 695 D5 S3 C4 .quad 0xfffffffb40153b2b, 0xFFFFFFFA40153B33, 0xFFFFFFF940153B3B, 0x0000000040123A83 # 696 - 699 D5 S3 C8 .quad 0xffffffff40123b0b, 0xFFFFFFFE40123B13, 0xFFFFFFFD40153B1B, 0xFFFFFFFC40153B23 # 700 - 703 D5 S3 C12 - .quad 0x0000000040104a00, 0x0000000040124A81, 0x0000000040124A82, 0x0000000040124A83 # 704 - 707 D5 S4 C0 + .quad 0x0000000040004a00, 0x0000000040124A81, 0x0000000040124A82, 0x0000000040124A83 # 704 - 707 D5 S4 C0 .quad 0xffffffff40124b0b, 0xFFFFFFFE40154B13, 0xFFFFFFFD40154B1B, 0xFFFFFFFC40154B23 # 708 - 711 D5 S4 C4 .quad 0xfffffffb40154b2b, 0xFFFFFFFA40154B33, 0xFFFFFFF940154B3B, 0x0000000040124A83 # 712 - 715 D5 S4 C8 .quad 0xffffffff40124b0b, 0xFFFFFFFE40154B13, 0xFFFFFFFD40154B1B, 0xFFFFFFFC40154B23 # 716 - 719 D5 S4 C12 @@ -223,35 +228,35 @@ fast_dma_encode_table: .quad 0xffffffff400c5b0b, 0xFFFFFFFE400C5B13, 0xFFFFFFFD400C5B1B, 0xFFFFFFFC400C5B23 # 724 - 727 D5 S5 C4 .quad 0xfffffffb400c5b2b, 0xFFFFFFFA400C5B33, 0xFFFFFFF9400C5B3B, 0x00000000400A5A83 # 728 - 731 D5 S5 C8 .quad 0xffffffff400c5b0b, 0xFFFFFFFE400C5B13, 0xFFFFFFFD400C5B1B, 0xFFFFFFFC400C5B23 # 732 - 735 D5 S5 C12 - .quad 0x0000000040106a00, 0x0000000040126A81, 0x00000000401A6A82, 0x00000000401CEA83 # 736 - 739 D5 S6 C0 + .quad 0x0000000040006a00, 0x0000000040126A81, 0x00000000401A6A82, 0x00000000401CEA83 # 736 - 739 D5 S6 C0 .quad 0xffffffff401ceb0b, 0xFFFFFFFE401CEB13, 0xFFFFFFFD401CEB1B, 0xFFFFFFFC401CEB23 # 740 - 743 D5 S6 C4 .quad 0xfffffffb401ceb2b, 0xFFFFFFFA401CEB33, 0xFFFFFFF9401CEB3B, 0x00000000401CEA83 # 744 - 747 D5 S6 C8 .quad 0xffffffff401ceb0b, 0xFFFFFFFE401CEB13, 0xFFFFFFFD401CEB1B, 0xFFFFFFFC401CEB23 # 748 - 751 D5 S6 C12 - .quad 0x0000000040107a00, 0x00000000401A7A81, 0x00000000401CFA82, 0x00000000401CFA83 # 752 - 755 D5 S7 C0 + .quad 0x0000000040007a00, 0x00000000401A7A81, 0x00000000401CFA82, 0x00000000401CFA83 # 752 - 755 D5 S7 C0 .quad 0xffffffff401cfb0b, 0xFFFFFFFE401CFB13, 0xFFFFFFFD401CFB1B, 0xFFFFFFFC401CFB23 # 756 - 759 D5 S7 C4 .quad 0xfffffffb401cfb2b, 0xFFFFFFFA401CFB33, 0xFFFFFFF9401FFB3B, 0x00000000401CFA83 # 760 - 763 D5 S7 C8 .quad 0xffffffff401cfb0b, 0xFFFFFFFE401CFB13, 0xFFFFFFFD401CFB1B, 0xFFFFFFFC401CFB23 # 764 - 767 D5 S7 C12 - .quad 0x0000000040100c00, 0x0000000040120C81, 0x0000000040120C82, 0xFFFFFFFF40120D0A # 768 - 771 D6 S0 C0 + .quad 0x0000000040000c00, 0x0000000040120C81, 0x0000000040120C82, 0xFFFFFFFF40120D0A # 768 - 771 D6 S0 C0 .quad 0xfffffffe40120d12, 0xFFFFFFFD40120D1A, 0xFFFFFFFC40120D22, 0xFFFFFFFB40120D2A # 772 - 775 D6 S0 C4 .quad 0xfffffffa40120d32, 0xFFFFFFF940150D3A, 0x0000000040120C82, 0xFFFFFFFF40120D0A # 776 - 779 D6 S0 C8 .quad 0xfffffffe40120d12, 0xFFFFFFFD40120D1A, 0xFFFFFFFC40120D22, 0xFFFFFFFB40120D2A # 780 - 783 D6 S0 C12 - .quad 0x0000000040101c00, 0x0000000040121C81, 0x0000000040121C82, 0xFFFFFFFF40121D0A # 784 - 787 D6 S1 C0 + .quad 0x0000000040001c00, 0x0000000040121C81, 0x0000000040121C82, 0xFFFFFFFF40121D0A # 784 - 787 D6 S1 C0 .quad 0xfffffffe40121d12, 0xFFFFFFFD40121D1A, 0xFFFFFFFC40121D22, 0xFFFFFFFB40121D2A # 788 - 791 D6 S1 C4 .quad 0xfffffffa40151d32, 0xFFFFFFF940151D3A, 0x0000000040121C82, 0xFFFFFFFF40121D0A # 792 - 795 D6 S1 C8 .quad 0xfffffffe40121d12, 0xFFFFFFFD40121D1A, 0xFFFFFFFC40121D22, 0xFFFFFFFB40121D2A # 796 - 799 D6 S1 C12 - .quad 0x0000000040102c00, 0x0000000040122C81, 0x0000000040122C82, 0xFFFFFFFF40122D0A # 800 - 803 D6 S2 C0 + .quad 0x0000000040002c00, 0x0000000040122C81, 0x0000000040122C82, 0xFFFFFFFF40122D0A # 800 - 803 D6 S2 C0 .quad 0xfffffffe40122d12, 0xFFFFFFFD40122D1A, 0xFFFFFFFC40122D22, 0xFFFFFFFB40152D2A # 804 - 807 D6 S2 C4 .quad 0xfffffffa40152d32, 0xFFFFFFF940152D3A, 0x0000000040122C82, 0xFFFFFFFF40122D0A # 808 - 811 D6 S2 C8 .quad 0xfffffffe40122d12, 0xFFFFFFFD40122D1A, 0xFFFFFFFC40122D22, 0xFFFFFFFB40152D2A # 812 - 815 D6 S2 C12 - .quad 0x0000000040103c00, 0x0000000040123C81, 0x0000000040123C82, 0xFFFFFFFF40123D0A # 816 - 819 D6 S3 C0 + .quad 0x0000000040003c00, 0x0000000040123C81, 0x0000000040123C82, 0xFFFFFFFF40123D0A # 816 - 819 D6 S3 C0 .quad 0xfffffffe40123d12, 0xFFFFFFFD40123D1A, 0xFFFFFFFC40153D22, 0xFFFFFFFB40153D2A # 820 - 823 D6 S3 C4 .quad 0xfffffffa40153d32, 0xFFFFFFF940153D3A, 0x0000000040123C82, 0xFFFFFFFF40123D0A # 824 - 827 D6 S3 C8 .quad 0xfffffffe40123d12, 0xFFFFFFFD40123D1A, 0xFFFFFFFC40153D22, 0xFFFFFFFB40153D2A # 828 - 831 D6 S3 C12 - .quad 0x0000000040104c00, 0x0000000040124C81, 0x0000000040124C82, 0xFFFFFFFF40124D0A # 832 - 835 D6 S4 C0 + .quad 0x0000000040004c00, 0x0000000040124C81, 0x0000000040124C82, 0xFFFFFFFF40124D0A # 832 - 835 D6 S4 C0 .quad 0xfffffffe40124d12, 0xFFFFFFFD40154D1A, 0xFFFFFFFC40154D22, 0xFFFFFFFB40154D2A # 836 - 839 D6 S4 C4 .quad 0xfffffffa40154d32, 0xFFFFFFF940154D3A, 0x0000000040124C82, 0xFFFFFFFF40124D0A # 840 - 843 D6 S4 C8 .quad 0xfffffffe40124d12, 0xFFFFFFFD40154D1A, 0xFFFFFFFC40154D22, 0xFFFFFFFB40154D2A # 844 - 847 D6 S4 C12 - .quad 0x0000000040105c00, 0x0000000040125C81, 0x0000000040125C82, 0xFFFFFFFF40125D0A # 848 - 851 D6 S5 C0 + .quad 0x0000000040005c00, 0x0000000040125C81, 0x0000000040125C82, 0xFFFFFFFF40125D0A # 848 - 851 D6 S5 C0 .quad 0xfffffffe40155d12, 0xFFFFFFFD40155D1A, 0xFFFFFFFC40155D22, 0xFFFFFFFB40155D2A # 852 - 855 D6 S5 C4 .quad 0xfffffffa40155d32, 0xFFFFFFF940155D3A, 0x0000000040125C82, 0xFFFFFFFF40125D0A # 856 - 859 D6 S5 C8 .quad 0xfffffffe40155d12, 0xFFFFFFFD40155D1A, 0xFFFFFFFC40155D22, 0xFFFFFFFB40155D2A # 860 - 863 D6 S5 C12 @@ -259,35 +264,293 @@ fast_dma_encode_table: .quad 0xfffffffe400c6d12, 0xFFFFFFFD400C6D1A, 0xFFFFFFFC400C6D22, 0xFFFFFFFB400C6D2A # 868 - 871 D6 S6 C4 .quad 0xfffffffa400c6d32, 0xFFFFFFF9400C6D3A, 0x00000000400A6C82, 0xFFFFFFFF400C6D0A # 872 - 875 D6 S6 C8 .quad 0xfffffffe400c6d12, 0xFFFFFFFD400C6D1A, 0xFFFFFFFC400C6D22, 0xFFFFFFFB400C6D2A # 876 - 879 D6 S6 C12 - .quad 0x0000000040107c00, 0x00000000401A7C81, 0x00000000401CFC82, 0xFFFFFFFF401CFD0A # 880 - 883 D6 S7 C0 + .quad 0x0000000040007c00, 0x00000000401A7C81, 0x00000000401CFC82, 0xFFFFFFFF401CFD0A # 880 - 883 D6 S7 C0 .quad 0xfffffffe401cfd12, 0xFFFFFFFD401CFD1A, 0xFFFFFFFC401CFD22, 0xFFFFFFFB401CFD2A # 884 - 887 D6 S7 C4 .quad 0xfffffffa401cfd32, 0xFFFFFFF9401CFD3A, 0x00000000401CFC82, 0xFFFFFFFF401CFD0A # 888 - 891 D6 S7 C8 .quad 0xfffffffe401cfd12, 0xFFFFFFFD401CFD1A, 0xFFFFFFFC401CFD22, 0xFFFFFFFB401CFD2A # 892 - 895 D6 S7 C12 - .quad 0x0000000040100e00, 0x0000000040120E81, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 896 - 899 D7 S0 C0 + .quad 0x0000000040000e00, 0x0000000040120E81, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 896 - 899 D7 S0 C0 .quad 0xfffffffd40120f19, 0xFFFFFFFC40120F21, 0xFFFFFFFB40120F29, 0xFFFFFFFA40120F31 # 900 - 903 D7 S0 C4 .quad 0xfffffff940120f39, 0x0000000040120E81, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 904 - 907 D7 S0 C8 .quad 0xfffffffd40120f19, 0xFFFFFFFC40120F21, 0xFFFFFFFB40120F29, 0xFFFFFFFA40120F31 # 908 - 911 D7 S0 C12 - .quad 0x0000000040101e00, 0x0000000040121E81, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 912 - 915 D7 S1 C0 + .quad 0x0000000040001e00, 0x0000000040121E81, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 912 - 915 D7 S1 C0 .quad 0xfffffffd40121f19, 0xFFFFFFFC40121F21, 0xFFFFFFFB40121F29, 0xFFFFFFFA40121F31 # 916 - 919 D7 S1 C4 .quad 0xfffffff940151f39, 0x0000000040121E81, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 920 - 923 D7 S1 C8 .quad 0xfffffffd40121f19, 0xFFFFFFFC40121F21, 0xFFFFFFFB40121F29, 0xFFFFFFFA40121F31 # 924 - 927 D7 S1 C12 - .quad 0x0000000040102e00, 0x0000000040122E81, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 928 - 931 D7 S2 C0 + .quad 0x0000000040002e00, 0x0000000040122E81, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 928 - 931 D7 S2 C0 .quad 0xfffffffd40122f19, 0xFFFFFFFC40122F21, 0xFFFFFFFB40122F29, 0xFFFFFFFA40152F31 # 932 - 935 D7 S2 C4 .quad 0xfffffff940152f39, 0x0000000040122E81, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 936 - 939 D7 S2 C8 .quad 0xfffffffd40122f19, 0xFFFFFFFC40122F21, 0xFFFFFFFB40122F29, 0xFFFFFFFA40152F31 # 940 - 943 D7 S2 C12 - .quad 0x0000000040103e00, 0x0000000040123E81, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 944 - 947 D7 S3 C0 + .quad 0x0000000040003e00, 0x0000000040123E81, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 944 - 947 D7 S3 C0 .quad 0xfffffffd40123f19, 0xFFFFFFFC40123F21, 0xFFFFFFFB40153F29, 0xFFFFFFFA40153F31 # 948 - 951 D7 S3 C4 .quad 0xfffffff940153f39, 0x0000000040123E81, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 952 - 955 D7 S3 C8 .quad 0xfffffffd40123f19, 0xFFFFFFFC40123F21, 0xFFFFFFFB40153F29, 0xFFFFFFFA40153F31 # 956 - 959 D7 S3 C12 - .quad 0x0000000040104e00, 0x0000000040124E81, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 960 - 963 D7 S4 C0 + .quad 0x0000000040004e00, 0x0000000040124E81, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 960 - 963 D7 S4 C0 .quad 0xfffffffd40124f19, 0xFFFFFFFC40154F21, 0xFFFFFFFB40154F29, 0xFFFFFFFA40154F31 # 964 - 967 D7 S4 C4 .quad 0xfffffff940154f39, 0x0000000040124E81, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 968 - 971 D7 S4 C8 .quad 0xfffffffd40124f19, 0xFFFFFFFC40154F21, 0xFFFFFFFB40154F29, 0xFFFFFFFA40154F31 # 972 - 975 D7 S4 C12 - .quad 0x0000000040105e00, 0x0000000040125E81, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 976 - 979 D7 S5 C0 + .quad 0x0000000040005e00, 0x0000000040125E81, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 976 - 979 D7 S5 C0 .quad 0xfffffffd40155f19, 0xFFFFFFFC40155F21, 0xFFFFFFFB40155F29, 0xFFFFFFFA40155F31 # 980 - 983 D7 S5 C4 .quad 0xfffffff940155f39, 0x0000000040125E81, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 984 - 987 D7 S5 C8 .quad 0xfffffffd40155f19, 0xFFFFFFFC40155F21, 0xFFFFFFFB40155F29, 0xFFFFFFFA40155F31 # 988 - 991 D7 S5 C12 - .quad 0x0000000040106e00, 0x0000000040126E81, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 992 - 995 D7 S6 C0 + .quad 0x0000000040006e00, 0x0000000040126E81, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 992 - 995 D7 S6 C0 + .quad 0xfffffffd40156f19, 0xFFFFFFFC40156F21, 0xFFFFFFFB40156F29, 0xFFFFFFFA40156F31 # 996 - 999 D7 S6 C4 + .quad 0xfffffff940156f39, 0x0000000040126E81, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 1000 - 1003 D7 S6 C8 + .quad 0xfffffffd40156f19, 0xFFFFFFFC40156F21, 0xFFFFFFFB40156F29, 0xFFFFFFFA40156F31 # 1004 - 1007 D7 S6 C12 + .quad 0x0000000040007e00, 0x00000000400A7E81, 0xFFFFFFFF400C7F09, 0xFFFFFFFE400C7F11 # 1008 - 1011 D7 S7 C0 + .quad 0xfffffffd400c7f19, 0xFFFFFFFC400C7F21, 0xFFFFFFFB400C7F29, 0xFFFFFFFA400C7F31 # 1012 - 1015 D7 S7 C4 + .quad 0xfffffff9400c7f39, 0x00000000400A7E81, 0xFFFFFFFF400C7F09, 0xFFFFFFFE400C7F11 # 1016 - 1019 D7 S7 C8 + .quad 0xfffffffd400c7f19, 0xFFFFFFFC400C7F21, 0xFFFFFFFB400C7F29, 0xFFFFFFFA400C7F31 # 1020 - 1023 D7 S7 C12 + +fast_dma_encode_memcmp_table: + .quad 0x0000000040000000, 0xFFFFFFFF40020088, 0xFFFFFFFE40020090, 0xFFFFFFFD40020098 # 0 - 3 D0 S0 C0 + .quad 0xfffffffc400200a0, 0xFFFFFFFB400200A8, 0xFFFFFFFA400200B0, 0xFFFFFFF9400200B8 # 4 - 7 D0 S0 C4 + .quad 0x0000000040000000, 0xFFFFFFFF40020088, 0xFFFFFFFE40020090, 0xFFFFFFFD40020098 # 8 - 11 D0 S0 C8 + .quad 0xfffffffc400200a0, 0xFFFFFFFB400200A8, 0xFFFFFFFA400200B0, 0xFFFFFFF9400200B8 # 12 - 15 D0 S0 C12 + .quad 0x0000000040001000, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 16 - 19 D0 S1 C0 + .quad 0xfffffffc401210a0, 0xFFFFFFFB401210A8, 0xFFFFFFFA401210B0, 0xFFFFFFF9401210B8 # 20 - 23 D0 S1 C4 + .quad 0x0000000040121000, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 24 - 27 D0 S1 C8 + .quad 0xfffffffc401210a0, 0xFFFFFFFB401210A8, 0xFFFFFFFA401210B0, 0xFFFFFFF9401210B8 # 28 - 31 D0 S1 C12 + .quad 0x0000000040002000, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 32 - 35 D0 S2 C0 + .quad 0xfffffffc401220a0, 0xFFFFFFFB401220A8, 0xFFFFFFFA401220B0, 0xFFFFFFF9401520B8 # 36 - 39 D0 S2 C4 + .quad 0x0000000040122000, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 40 - 43 D0 S2 C8 + .quad 0xfffffffc401220a0, 0xFFFFFFFB401220A8, 0xFFFFFFFA401220B0, 0xFFFFFFF9401520B8 # 44 - 47 D0 S2 C12 + .quad 0x0000000040003000, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 48 - 51 D0 S3 C0 + .quad 0xfffffffc401230a0, 0xFFFFFFFB401230A8, 0xFFFFFFFA401530B0, 0xFFFFFFF9401530B8 # 52 - 55 D0 S3 C4 + .quad 0x0000000040123000, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 56 - 59 D0 S3 C8 + .quad 0xfffffffc401230a0, 0xFFFFFFFB401230A8, 0xFFFFFFFA401530B0, 0xFFFFFFF9401530B8 # 60 - 63 D0 S3 C12 + .quad 0x0000000040004000, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 64 - 67 D0 S4 C0 + .quad 0xfffffffc401240a0, 0xFFFFFFFB401540A8, 0xFFFFFFFA401540B0, 0xFFFFFFF9401540B8 # 68 - 71 D0 S4 C4 + .quad 0x0000000040124000, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 72 - 75 D0 S4 C8 + .quad 0xfffffffc401240a0, 0xFFFFFFFB401540A8, 0xFFFFFFFA401540B0, 0xFFFFFFF9401540B8 # 76 - 79 D0 S4 C12 + .quad 0x0000000040005000, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 80 - 83 D0 S5 C0 + .quad 0xfffffffc401550a0, 0xFFFFFFFB401550A8, 0xFFFFFFFA401550B0, 0xFFFFFFF9401550B8 # 84 - 87 D0 S5 C4 + .quad 0x0000000040125000, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 88 - 91 D0 S5 C8 + .quad 0xfffffffc401550a0, 0xFFFFFFFB401550A8, 0xFFFFFFFA401550B0, 0xFFFFFFF9401550B8 # 92 - 95 D0 S5 C12 + .quad 0x0000000040006000, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 96 - 99 D0 S6 C0 + .quad 0xfffffffc401560a0, 0xFFFFFFFB401560A8, 0xFFFFFFFA401560B0, 0xFFFFFFF9401560B8 # 100 - 103 D0 S6 C4 + .quad 0x0000000040126000, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 104 - 107 D0 S6 C8 + .quad 0xfffffffc401560a0, 0xFFFFFFFB401560A8, 0xFFFFFFFA401560B0, 0xFFFFFFF9401560B8 # 108 - 111 D0 S6 C12 + .quad 0x0000000040007000, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 112 - 115 D0 S7 C0 + .quad 0xfffffffc401570a0, 0xFFFFFFFB401570A8, 0xFFFFFFFA401570B0, 0xFFFFFFF9401570B8 # 116 - 119 D0 S7 C4 + .quad 0x0000000040127000, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 120 - 123 D0 S7 C8 + .quad 0xfffffffc401570a0, 0xFFFFFFFB401570A8, 0xFFFFFFFA401570B0, 0xFFFFFFF9401570B8 # 124 - 127 D0 S7 C12 + .quad 0x0000000040000200, 0x0000000040120281, 0x0000000040120282, 0x0000000040120283 # 128 - 131 D1 S0 C0 + .quad 0x0000000040120284, 0x0000000040120285, 0x0000000040120286, 0x0000000040120287 # 132 - 135 D1 S0 C4 + .quad 0xffffffff4012030f, 0xFFFFFFFE40150317, 0xFFFFFFFD4015031F, 0xFFFFFFFC40150327 # 136 - 139 D1 S0 C8 + .quad 0xfffffffb4015032f, 0xFFFFFFFA40150337, 0xFFFFFFF94015033F, 0x0000000040120287 # 140 - 143 D1 S0 C12 + .quad 0x0000000040001200, 0x0000000040021281, 0x0000000040021282, 0x0000000040021283 # 144 - 147 D1 S1 C0 + .quad 0x0000000040021284, 0x0000000040021285, 0x0000000040021286, 0x00000000400A1287 # 148 - 151 D1 S1 C4 + .quad 0xffffffff400c130f, 0xFFFFFFFE400C1317, 0xFFFFFFFD400C131F, 0xFFFFFFFC400C1327 # 152 - 155 D1 S1 C8 + .quad 0xfffffffb400c132f, 0xFFFFFFFA400C1337, 0xFFFFFFF9400C133F, 0x00000000400A1287 # 156 - 159 D1 S1 C12 + .quad 0x0000000040002200, 0x0000000040122281, 0x0000000040122282, 0x0000000040122283 # 160 - 163 D1 S2 C0 + .quad 0x0000000040122284, 0x0000000040122285, 0x00000000401A2286, 0x00000000401CA287 # 164 - 167 D1 S2 C4 + .quad 0xffffffff401ca30f, 0xFFFFFFFE401CA317, 0xFFFFFFFD401CA31F, 0xFFFFFFFC401CA327 # 168 - 171 D1 S2 C8 + .quad 0xfffffffb401ca32f, 0xFFFFFFFA401CA337, 0xFFFFFFF9401CA33F, 0x00000000401CA287 # 172 - 175 D1 S2 C12 + .quad 0x0000000040003200, 0x0000000040123281, 0x0000000040123282, 0x0000000040123283 # 176 - 179 D1 S3 C0 + .quad 0x0000000040123284, 0x00000000401A3285, 0x00000000401CB286, 0x00000000401CB287 # 180 - 183 D1 S3 C4 + .quad 0xffffffff401cb30f, 0xFFFFFFFE401CB317, 0xFFFFFFFD401CB31F, 0xFFFFFFFC401CB327 # 184 - 187 D1 S3 C8 + .quad 0xfffffffb401cb32f, 0xFFFFFFFA401CB337, 0xFFFFFFF9401FB33F, 0x00000000401CB287 # 188 - 191 D1 S3 C12 + .quad 0x0000000040004200, 0x0000000040124281, 0x0000000040124282, 0x0000000040124283 # 192 - 195 D1 S4 C0 + .quad 0x00000000401a4284, 0x00000000401CC285, 0x00000000401CC286, 0x00000000401CC287 # 196 - 199 D1 S4 C4 + .quad 0xffffffff401cc30f, 0xFFFFFFFE401CC317, 0xFFFFFFFD401CC31F, 0xFFFFFFFC401CC327 # 200 - 203 D1 S4 C8 + .quad 0xfffffffb401cc32f, 0xFFFFFFFA401FC337, 0xFFFFFFF9401FC33F, 0x00000000401CC287 # 204 - 207 D1 S4 C12 + .quad 0x0000000040005200, 0x0000000040125281, 0x0000000040125282, 0x00000000401A5283 # 208 - 211 D1 S5 C0 + .quad 0x00000000401cd284, 0x00000000401CD285, 0x00000000401CD286, 0x00000000401CD287 # 212 - 215 D1 S5 C4 + .quad 0xffffffff401cd30f, 0xFFFFFFFE401CD317, 0xFFFFFFFD401CD31F, 0xFFFFFFFC401CD327 # 216 - 219 D1 S5 C8 + .quad 0xfffffffb401fd32f, 0xFFFFFFFA401FD337, 0xFFFFFFF9401FD33F, 0x00000000401CD287 # 220 - 223 D1 S5 C12 + .quad 0x0000000040006200, 0x0000000040126281, 0x00000000401A6282, 0x00000000401CE283 # 224 - 227 D1 S6 C0 + .quad 0x00000000401ce284, 0x00000000401CE285, 0x00000000401CE286, 0x00000000401CE287 # 228 - 231 D1 S6 C4 + .quad 0xffffffff401ce30f, 0xFFFFFFFE401CE317, 0xFFFFFFFD401CE31F, 0xFFFFFFFC401FE327 # 232 - 235 D1 S6 C8 + .quad 0xfffffffb401fe32f, 0xFFFFFFFA401FE337, 0xFFFFFFF9401FE33F, 0x00000000401CE287 # 236 - 239 D1 S6 C12 + .quad 0x0000000040007200, 0x00000000401A7281, 0x00000000401CF282, 0x00000000401CF283 # 240 - 243 D1 S7 C0 + .quad 0x00000000401cf284, 0x00000000401CF285, 0x00000000401CF286, 0x00000000401CF287 # 244 - 247 D1 S7 C4 + .quad 0xffffffff401cf30f, 0xFFFFFFFE401CF317, 0xFFFFFFFD401FF31F, 0xFFFFFFFC401FF327 # 248 - 251 D1 S7 C8 + .quad 0xfffffffb401ff32f, 0xFFFFFFFA401FF337, 0xFFFFFFF9401FF33F, 0x00000000401CF287 # 252 - 255 D1 S7 C12 + .quad 0x0000000040000400, 0x0000000040120481, 0x0000000040120482, 0x0000000040120483 # 256 - 259 D2 S0 C0 + .quad 0x0000000040120484, 0x0000000040120485, 0x0000000040120486, 0xFFFFFFFF4012050E # 260 - 263 D2 S0 C4 + .quad 0xfffffffe40120516, 0xFFFFFFFD4015051E, 0xFFFFFFFC40150526, 0xFFFFFFFB4015052E # 264 - 267 D2 S0 C8 + .quad 0xfffffffa40150536, 0xFFFFFFF94015053E, 0x0000000040120486, 0xFFFFFFFF4012050E # 268 - 271 D2 S0 C12 + .quad 0x0000000040001400, 0x0000000040121481, 0x0000000040121482, 0x0000000040121483 # 272 - 275 D2 S1 C0 + .quad 0x0000000040121484, 0x0000000040121485, 0x0000000040121486, 0xFFFFFFFF4012150E # 276 - 279 D2 S1 C4 + .quad 0xfffffffe40151516, 0xFFFFFFFD4015151E, 0xFFFFFFFC40151526, 0xFFFFFFFB4015152E # 280 - 283 D2 S1 C8 + .quad 0xfffffffa40151536, 0xFFFFFFF94015153E, 0x0000000040121486, 0xFFFFFFFF4012150E # 284 - 287 D2 S1 C12 + .quad 0x0000000040002400, 0x0000000040022481, 0x0000000040022482, 0x0000000040022483 # 288 - 291 D2 S2 C0 + .quad 0x0000000040022484, 0x0000000040022485, 0x00000000400A2486, 0xFFFFFFFF400C250E # 292 - 295 D2 S2 C4 + .quad 0xfffffffe400c2516, 0xFFFFFFFD400C251E, 0xFFFFFFFC400C2526, 0xFFFFFFFB400C252E # 296 - 299 D2 S2 C8 + .quad 0xfffffffa400c2536, 0xFFFFFFF9400C253E, 0x00000000400A2486, 0xFFFFFFFF400C250E # 300 - 303 D2 S2 C12 + .quad 0x0000000040003400, 0x0000000040123481, 0x0000000040123482, 0x0000000040123483 # 304 - 307 D2 S3 C0 + .quad 0x0000000040123484, 0x00000000401A3485, 0x00000000401CB486, 0xFFFFFFFF401CB50E # 308 - 311 D2 S3 C4 + .quad 0xfffffffe401cb516, 0xFFFFFFFD401CB51E, 0xFFFFFFFC401CB526, 0xFFFFFFFB401CB52E # 312 - 315 D2 S3 C8 + .quad 0xfffffffa401cb536, 0xFFFFFFF9401CB53E, 0x00000000401CB486, 0xFFFFFFFF401CB50E # 316 - 319 D2 S3 C12 + .quad 0x0000000040004400, 0x0000000040124481, 0x0000000040124482, 0x0000000040124483 # 320 - 323 D2 S4 C0 + .quad 0x00000000401a4484, 0x00000000401CC485, 0x00000000401CC486, 0xFFFFFFFF401CC50E # 324 - 327 D2 S4 C4 + .quad 0xfffffffe401cc516, 0xFFFFFFFD401CC51E, 0xFFFFFFFC401CC526, 0xFFFFFFFB401CC52E # 328 - 331 D2 S4 C8 + .quad 0xfffffffa401cc536, 0xFFFFFFF9401FC53E, 0x00000000401CC486, 0xFFFFFFFF401CC50E # 332 - 335 D2 S4 C12 + .quad 0x0000000040005400, 0x0000000040125481, 0x0000000040125482, 0x00000000401A5483 # 336 - 339 D2 S5 C0 + .quad 0x00000000401cd484, 0x00000000401CD485, 0x00000000401CD486, 0xFFFFFFFF401CD50E # 340 - 343 D2 S5 C4 + .quad 0xfffffffe401cd516, 0xFFFFFFFD401CD51E, 0xFFFFFFFC401CD526, 0xFFFFFFFB401CD52E # 344 - 347 D2 S5 C8 + .quad 0xfffffffa401fd536, 0xFFFFFFF9401FD53E, 0x00000000401CD486, 0xFFFFFFFF401CD50E # 348 - 351 D2 S5 C12 + .quad 0x0000000040006400, 0x0000000040126481, 0x00000000401A6482, 0x00000000401CE483 # 352 - 355 D2 S6 C0 + .quad 0x00000000401ce484, 0x00000000401CE485, 0x00000000401CE486, 0xFFFFFFFF401CE50E # 356 - 359 D2 S6 C4 + .quad 0xfffffffe401ce516, 0xFFFFFFFD401CE51E, 0xFFFFFFFC401CE526, 0xFFFFFFFB401FE52E # 360 - 363 D2 S6 C8 + .quad 0xfffffffa401fe536, 0xFFFFFFF9401FE53E, 0x00000000401CE486, 0xFFFFFFFF401CE50E # 364 - 367 D2 S6 C12 + .quad 0x0000000040007400, 0x00000000401A7481, 0x00000000401CF482, 0x00000000401CF483 # 368 - 371 D2 S7 C0 + .quad 0x00000000401cf484, 0x00000000401CF485, 0x00000000401CF486, 0xFFFFFFFF401CF50E # 372 - 375 D2 S7 C4 + .quad 0xfffffffe401cf516, 0xFFFFFFFD401CF51E, 0xFFFFFFFC401FF526, 0xFFFFFFFB401FF52E # 376 - 379 D2 S7 C8 + .quad 0xfffffffa401ff536, 0xFFFFFFF9401FF53E, 0x00000000401CF486, 0xFFFFFFFF401CF50E # 380 - 383 D2 S7 C12 + .quad 0x0000000040000600, 0x0000000040120681, 0x0000000040120682, 0x0000000040120683 # 384 - 387 D3 S0 C0 + .quad 0x0000000040120684, 0x0000000040120685, 0xFFFFFFFF4012070D, 0xFFFFFFFE40120715 # 388 - 391 D3 S0 C4 + .quad 0xfffffffd4012071d, 0xFFFFFFFC40150725, 0xFFFFFFFB4015072D, 0xFFFFFFFA40150735 # 392 - 395 D3 S0 C8 + .quad 0xfffffff94015073d, 0x0000000040120685, 0xFFFFFFFF4012070D, 0xFFFFFFFE40120715 # 396 - 399 D3 S0 C12 + .quad 0x0000000040001600, 0x0000000040121681, 0x0000000040121682, 0x0000000040121683 # 400 - 403 D3 S1 C0 + .quad 0x0000000040121684, 0x0000000040121685, 0xFFFFFFFF4012170D, 0xFFFFFFFE40121715 # 404 - 407 D3 S1 C4 + .quad 0xfffffffd4015171d, 0xFFFFFFFC40151725, 0xFFFFFFFB4015172D, 0xFFFFFFFA40151735 # 408 - 411 D3 S1 C8 + .quad 0xfffffff94015173d, 0x0000000040121685, 0xFFFFFFFF4012170D, 0xFFFFFFFE40121715 # 412 - 415 D3 S1 C12 + .quad 0x0000000040002600, 0x0000000040122681, 0x0000000040122682, 0x0000000040122683 # 416 - 419 D3 S2 C0 + .quad 0x0000000040122684, 0x0000000040122685, 0xFFFFFFFF4012270D, 0xFFFFFFFE40152715 # 420 - 423 D3 S2 C4 + .quad 0xfffffffd4015271d, 0xFFFFFFFC40152725, 0xFFFFFFFB4015272D, 0xFFFFFFFA40152735 # 424 - 427 D3 S2 C8 + .quad 0xfffffff94015273d, 0x0000000040122685, 0xFFFFFFFF4012270D, 0xFFFFFFFE40152715 # 428 - 431 D3 S2 C12 + .quad 0x0000000040003600, 0x0000000040023681, 0x0000000040023682, 0x0000000040023683 # 432 - 435 D3 S3 C0 + .quad 0x0000000040023684, 0x00000000400A3685, 0xFFFFFFFF400C370D, 0xFFFFFFFE400C3715 # 436 - 439 D3 S3 C4 + .quad 0xfffffffd400c371d, 0xFFFFFFFC400C3725, 0xFFFFFFFB400C372D, 0xFFFFFFFA400C3735 # 440 - 443 D3 S3 C8 + .quad 0xfffffff9400c373d, 0x00000000400A3685, 0xFFFFFFFF400C370D, 0xFFFFFFFE400C3715 # 444 - 447 D3 S3 C12 + .quad 0x0000000040004600, 0x0000000040124681, 0x0000000040124682, 0x0000000040124683 # 448 - 451 D3 S4 C0 + .quad 0x00000000401a4684, 0x00000000401CC685, 0xFFFFFFFF401CC70D, 0xFFFFFFFE401CC715 # 452 - 455 D3 S4 C4 + .quad 0xfffffffd401cc71d, 0xFFFFFFFC401CC725, 0xFFFFFFFB401CC72D, 0xFFFFFFFA401CC735 # 456 - 459 D3 S4 C8 + .quad 0xfffffff9401cc73d, 0x00000000401CC685, 0xFFFFFFFF401CC70D, 0xFFFFFFFE401CC715 # 460 - 463 D3 S4 C12 + .quad 0x0000000040005600, 0x0000000040125681, 0x0000000040125682, 0x00000000401A5683 # 464 - 467 D3 S5 C0 + .quad 0x00000000401cd684, 0x00000000401CD685, 0xFFFFFFFF401CD70D, 0xFFFFFFFE401CD715 # 468 - 471 D3 S5 C4 + .quad 0xfffffffd401cd71d, 0xFFFFFFFC401CD725, 0xFFFFFFFB401CD72D, 0xFFFFFFFA401CD735 # 472 - 475 D3 S5 C8 + .quad 0xfffffff9401fd73d, 0x00000000401CD685, 0xFFFFFFFF401CD70D, 0xFFFFFFFE401CD715 # 476 - 479 D3 S5 C12 + .quad 0x0000000040006600, 0x0000000040126681, 0x00000000401A6682, 0x00000000401CE683 # 480 - 483 D3 S6 C0 + .quad 0x00000000401ce684, 0x00000000401CE685, 0xFFFFFFFF401CE70D, 0xFFFFFFFE401CE715 # 484 - 487 D3 S6 C4 + .quad 0xfffffffd401ce71d, 0xFFFFFFFC401CE725, 0xFFFFFFFB401CE72D, 0xFFFFFFFA401FE735 # 488 - 491 D3 S6 C8 + .quad 0xfffffff9401fe73d, 0x00000000401CE685, 0xFFFFFFFF401CE70D, 0xFFFFFFFE401CE715 # 492 - 495 D3 S6 C12 + .quad 0x0000000040007600, 0x00000000401A7681, 0x00000000401CF682, 0x00000000401CF683 # 496 - 499 D3 S7 C0 + .quad 0x00000000401cf684, 0x00000000401CF685, 0xFFFFFFFF401CF70D, 0xFFFFFFFE401CF715 # 500 - 503 D3 S7 C4 + .quad 0xfffffffd401cf71d, 0xFFFFFFFC401CF725, 0xFFFFFFFB401FF72D, 0xFFFFFFFA401FF735 # 504 - 507 D3 S7 C8 + .quad 0xfffffff9401ff73d, 0x00000000401CF685, 0xFFFFFFFF401CF70D, 0xFFFFFFFE401CF715 # 508 - 511 D3 S7 C12 + .quad 0x0000000040000800, 0x0000000040120881, 0x0000000040120882, 0x0000000040120883 # 512 - 515 D4 S0 C0 + .quad 0x0000000040120884, 0xFFFFFFFF4012090C, 0xFFFFFFFE40120914, 0xFFFFFFFD4012091C # 516 - 519 D4 S0 C4 + .quad 0xfffffffc40120924, 0xFFFFFFFB4015092C, 0xFFFFFFFA40150934, 0xFFFFFFF94015093C # 520 - 523 D4 S0 C8 + .quad 0x0000000040120884, 0xFFFFFFFF4012090C, 0xFFFFFFFE40120914, 0xFFFFFFFD4012091C # 524 - 527 D4 S0 C12 + .quad 0x0000000040001800, 0x0000000040121881, 0x0000000040121882, 0x0000000040121883 # 528 - 531 D4 S1 C0 + .quad 0x0000000040121884, 0xFFFFFFFF4012190C, 0xFFFFFFFE40121914, 0xFFFFFFFD4012191C # 532 - 535 D4 S1 C4 + .quad 0xfffffffc40151924, 0xFFFFFFFB4015192C, 0xFFFFFFFA40151934, 0xFFFFFFF94015193C # 536 - 539 D4 S1 C8 + .quad 0x0000000040121884, 0xFFFFFFFF4012190C, 0xFFFFFFFE40121914, 0xFFFFFFFD4012191C # 540 - 543 D4 S1 C12 + .quad 0x0000000040002800, 0x0000000040122881, 0x0000000040122882, 0x0000000040122883 # 544 - 547 D4 S2 C0 + .quad 0x0000000040122884, 0xFFFFFFFF4012290C, 0xFFFFFFFE40122914, 0xFFFFFFFD4015291C # 548 - 551 D4 S2 C4 + .quad 0xfffffffc40152924, 0xFFFFFFFB4015292C, 0xFFFFFFFA40152934, 0xFFFFFFF94015293C # 552 - 555 D4 S2 C8 + .quad 0x0000000040122884, 0xFFFFFFFF4012290C, 0xFFFFFFFE40122914, 0xFFFFFFFD4015291C # 556 - 559 D4 S2 C12 + .quad 0x0000000040003800, 0x0000000040123881, 0x0000000040123882, 0x0000000040123883 # 560 - 563 D4 S3 C0 + .quad 0x0000000040123884, 0xFFFFFFFF4012390C, 0xFFFFFFFE40153914, 0xFFFFFFFD4015391C # 564 - 567 D4 S3 C4 + .quad 0xfffffffc40153924, 0xFFFFFFFB4015392C, 0xFFFFFFFA40153934, 0xFFFFFFF94015393C # 568 - 571 D4 S3 C8 + .quad 0x0000000040123884, 0xFFFFFFFF4012390C, 0xFFFFFFFE40153914, 0xFFFFFFFD4015391C # 572 - 575 D4 S3 C12 + .quad 0x0000000040004800, 0x0000000040024881, 0x0000000040024882, 0x0000000040024883 # 576 - 579 D4 S4 C0 + .quad 0x00000000400a4884, 0xFFFFFFFF400C490C, 0xFFFFFFFE400C4914, 0xFFFFFFFD400C491C # 580 - 583 D4 S4 C4 + .quad 0xfffffffc400c4924, 0xFFFFFFFB400C492C, 0xFFFFFFFA400C4934, 0xFFFFFFF9400C493C # 584 - 587 D4 S4 C8 + .quad 0x00000000400a4884, 0xFFFFFFFF400C490C, 0xFFFFFFFE400C4914, 0xFFFFFFFD400C491C # 588 - 591 D4 S4 C12 + .quad 0x0000000040005800, 0x0000000040125881, 0x0000000040125882, 0x00000000401A5883 # 592 - 595 D4 S5 C0 + .quad 0x00000000401cd884, 0xFFFFFFFF401CD90C, 0xFFFFFFFE401CD914, 0xFFFFFFFD401CD91C # 596 - 599 D4 S5 C4 + .quad 0xfffffffc401cd924, 0xFFFFFFFB401CD92C, 0xFFFFFFFA401CD934, 0xFFFFFFF9401CD93C # 600 - 603 D4 S5 C8 + .quad 0x00000000401cd884, 0xFFFFFFFF401CD90C, 0xFFFFFFFE401CD914, 0xFFFFFFFD401CD91C # 604 - 607 D4 S5 C12 + .quad 0x0000000040006800, 0x0000000040126881, 0x00000000401A6882, 0x00000000401CE883 # 608 - 611 D4 S6 C0 + .quad 0x00000000401ce884, 0xFFFFFFFF401CE90C, 0xFFFFFFFE401CE914, 0xFFFFFFFD401CE91C # 612 - 615 D4 S6 C4 + .quad 0xfffffffc401ce924, 0xFFFFFFFB401CE92C, 0xFFFFFFFA401CE934, 0xFFFFFFF9401FE93C # 616 - 619 D4 S6 C8 + .quad 0x00000000401ce884, 0xFFFFFFFF401CE90C, 0xFFFFFFFE401CE914, 0xFFFFFFFD401CE91C # 620 - 623 D4 S6 C12 + .quad 0x0000000040007800, 0x00000000401A7881, 0x00000000401CF882, 0x00000000401CF883 # 624 - 627 D4 S7 C0 + .quad 0x00000000401cf884, 0xFFFFFFFF401CF90C, 0xFFFFFFFE401CF914, 0xFFFFFFFD401CF91C # 628 - 631 D4 S7 C4 + .quad 0xfffffffc401cf924, 0xFFFFFFFB401CF92C, 0xFFFFFFFA401FF934, 0xFFFFFFF9401FF93C # 632 - 635 D4 S7 C8 + .quad 0x00000000401cf884, 0xFFFFFFFF401CF90C, 0xFFFFFFFE401CF914, 0xFFFFFFFD401CF91C # 636 - 639 D4 S7 C12 + .quad 0x0000000040000a00, 0x0000000040120A81, 0x0000000040120A82, 0x0000000040120A83 # 640 - 643 D5 S0 C0 + .quad 0xffffffff40120b0b, 0xFFFFFFFE40120B13, 0xFFFFFFFD40120B1B, 0xFFFFFFFC40120B23 # 644 - 647 D5 S0 C4 + .quad 0xfffffffb40120b2b, 0xFFFFFFFA40150B33, 0xFFFFFFF940150B3B, 0x0000000040120A83 # 648 - 651 D5 S0 C8 + .quad 0xffffffff40120b0b, 0xFFFFFFFE40120B13, 0xFFFFFFFD40120B1B, 0xFFFFFFFC40120B23 # 652 - 655 D5 S0 C12 + .quad 0x0000000040001a00, 0x0000000040121A81, 0x0000000040121A82, 0x0000000040121A83 # 656 - 659 D5 S1 C0 + .quad 0xffffffff40121b0b, 0xFFFFFFFE40121B13, 0xFFFFFFFD40121B1B, 0xFFFFFFFC40121B23 # 660 - 663 D5 S1 C4 + .quad 0xfffffffb40151b2b, 0xFFFFFFFA40151B33, 0xFFFFFFF940151B3B, 0x0000000040121A83 # 664 - 667 D5 S1 C8 + .quad 0xffffffff40121b0b, 0xFFFFFFFE40121B13, 0xFFFFFFFD40121B1B, 0xFFFFFFFC40121B23 # 668 - 671 D5 S1 C12 + .quad 0x0000000040002a00, 0x0000000040122A81, 0x0000000040122A82, 0x0000000040122A83 # 672 - 675 D5 S2 C0 + .quad 0xffffffff40122b0b, 0xFFFFFFFE40122B13, 0xFFFFFFFD40122B1B, 0xFFFFFFFC40152B23 # 676 - 679 D5 S2 C4 + .quad 0xfffffffb40152b2b, 0xFFFFFFFA40152B33, 0xFFFFFFF940152B3B, 0x0000000040122A83 # 680 - 683 D5 S2 C8 + .quad 0xffffffff40122b0b, 0xFFFFFFFE40122B13, 0xFFFFFFFD40122B1B, 0xFFFFFFFC40152B23 # 684 - 687 D5 S2 C12 + .quad 0x0000000040003a00, 0x0000000040123A81, 0x0000000040123A82, 0x0000000040123A83 # 688 - 691 D5 S3 C0 + .quad 0xffffffff40123b0b, 0xFFFFFFFE40123B13, 0xFFFFFFFD40153B1B, 0xFFFFFFFC40153B23 # 692 - 695 D5 S3 C4 + .quad 0xfffffffb40153b2b, 0xFFFFFFFA40153B33, 0xFFFFFFF940153B3B, 0x0000000040123A83 # 696 - 699 D5 S3 C8 + .quad 0xffffffff40123b0b, 0xFFFFFFFE40123B13, 0xFFFFFFFD40153B1B, 0xFFFFFFFC40153B23 # 700 - 703 D5 S3 C12 + .quad 0x0000000040004a00, 0x0000000040124A81, 0x0000000040124A82, 0x0000000040124A83 # 704 - 707 D5 S4 C0 + .quad 0xffffffff40124b0b, 0xFFFFFFFE40154B13, 0xFFFFFFFD40154B1B, 0xFFFFFFFC40154B23 # 708 - 711 D5 S4 C4 + .quad 0xfffffffb40154b2b, 0xFFFFFFFA40154B33, 0xFFFFFFF940154B3B, 0x0000000040124A83 # 712 - 715 D5 S4 C8 + .quad 0xffffffff40124b0b, 0xFFFFFFFE40154B13, 0xFFFFFFFD40154B1B, 0xFFFFFFFC40154B23 # 716 - 719 D5 S4 C12 + .quad 0x0000000040005a00, 0x0000000040025A81, 0x0000000040025A82, 0x00000000400A5A83 # 720 - 723 D5 S5 C0 + .quad 0xffffffff400c5b0b, 0xFFFFFFFE400C5B13, 0xFFFFFFFD400C5B1B, 0xFFFFFFFC400C5B23 # 724 - 727 D5 S5 C4 + .quad 0xfffffffb400c5b2b, 0xFFFFFFFA400C5B33, 0xFFFFFFF9400C5B3B, 0x00000000400A5A83 # 728 - 731 D5 S5 C8 + .quad 0xffffffff400c5b0b, 0xFFFFFFFE400C5B13, 0xFFFFFFFD400C5B1B, 0xFFFFFFFC400C5B23 # 732 - 735 D5 S5 C12 + .quad 0x0000000040006a00, 0x0000000040126A81, 0x00000000401A6A82, 0x00000000401CEA83 # 736 - 739 D5 S6 C0 + .quad 0xffffffff401ceb0b, 0xFFFFFFFE401CEB13, 0xFFFFFFFD401CEB1B, 0xFFFFFFFC401CEB23 # 740 - 743 D5 S6 C4 + .quad 0xfffffffb401ceb2b, 0xFFFFFFFA401CEB33, 0xFFFFFFF9401CEB3B, 0x00000000401CEA83 # 744 - 747 D5 S6 C8 + .quad 0xffffffff401ceb0b, 0xFFFFFFFE401CEB13, 0xFFFFFFFD401CEB1B, 0xFFFFFFFC401CEB23 # 748 - 751 D5 S6 C12 + .quad 0x0000000040007a00, 0x00000000401A7A81, 0x00000000401CFA82, 0x00000000401CFA83 # 752 - 755 D5 S7 C0 + .quad 0xffffffff401cfb0b, 0xFFFFFFFE401CFB13, 0xFFFFFFFD401CFB1B, 0xFFFFFFFC401CFB23 # 756 - 759 D5 S7 C4 + .quad 0xfffffffb401cfb2b, 0xFFFFFFFA401CFB33, 0xFFFFFFF9401FFB3B, 0x00000000401CFA83 # 760 - 763 D5 S7 C8 + .quad 0xffffffff401cfb0b, 0xFFFFFFFE401CFB13, 0xFFFFFFFD401CFB1B, 0xFFFFFFFC401CFB23 # 764 - 767 D5 S7 C12 + .quad 0x0000000040000c00, 0x0000000040120C81, 0x0000000040120C82, 0xFFFFFFFF40120D0A # 768 - 771 D6 S0 C0 + .quad 0xfffffffe40120d12, 0xFFFFFFFD40120D1A, 0xFFFFFFFC40120D22, 0xFFFFFFFB40120D2A # 772 - 775 D6 S0 C4 + .quad 0xfffffffa40120d32, 0xFFFFFFF940150D3A, 0x0000000040120C82, 0xFFFFFFFF40120D0A # 776 - 779 D6 S0 C8 + .quad 0xfffffffe40120d12, 0xFFFFFFFD40120D1A, 0xFFFFFFFC40120D22, 0xFFFFFFFB40120D2A # 780 - 783 D6 S0 C12 + .quad 0x0000000040001c00, 0x0000000040121C81, 0x0000000040121C82, 0xFFFFFFFF40121D0A # 784 - 787 D6 S1 C0 + .quad 0xfffffffe40121d12, 0xFFFFFFFD40121D1A, 0xFFFFFFFC40121D22, 0xFFFFFFFB40121D2A # 788 - 791 D6 S1 C4 + .quad 0xfffffffa40151d32, 0xFFFFFFF940151D3A, 0x0000000040121C82, 0xFFFFFFFF40121D0A # 792 - 795 D6 S1 C8 + .quad 0xfffffffe40121d12, 0xFFFFFFFD40121D1A, 0xFFFFFFFC40121D22, 0xFFFFFFFB40121D2A # 796 - 799 D6 S1 C12 + .quad 0x0000000040002c00, 0x0000000040122C81, 0x0000000040122C82, 0xFFFFFFFF40122D0A # 800 - 803 D6 S2 C0 + .quad 0xfffffffe40122d12, 0xFFFFFFFD40122D1A, 0xFFFFFFFC40122D22, 0xFFFFFFFB40152D2A # 804 - 807 D6 S2 C4 + .quad 0xfffffffa40152d32, 0xFFFFFFF940152D3A, 0x0000000040122C82, 0xFFFFFFFF40122D0A # 808 - 811 D6 S2 C8 + .quad 0xfffffffe40122d12, 0xFFFFFFFD40122D1A, 0xFFFFFFFC40122D22, 0xFFFFFFFB40152D2A # 812 - 815 D6 S2 C12 + .quad 0x0000000040003c00, 0x0000000040123C81, 0x0000000040123C82, 0xFFFFFFFF40123D0A # 816 - 819 D6 S3 C0 + .quad 0xfffffffe40123d12, 0xFFFFFFFD40123D1A, 0xFFFFFFFC40153D22, 0xFFFFFFFB40153D2A # 820 - 823 D6 S3 C4 + .quad 0xfffffffa40153d32, 0xFFFFFFF940153D3A, 0x0000000040123C82, 0xFFFFFFFF40123D0A # 824 - 827 D6 S3 C8 + .quad 0xfffffffe40123d12, 0xFFFFFFFD40123D1A, 0xFFFFFFFC40153D22, 0xFFFFFFFB40153D2A # 828 - 831 D6 S3 C12 + .quad 0x0000000040004c00, 0x0000000040124C81, 0x0000000040124C82, 0xFFFFFFFF40124D0A # 832 - 835 D6 S4 C0 + .quad 0xfffffffe40124d12, 0xFFFFFFFD40154D1A, 0xFFFFFFFC40154D22, 0xFFFFFFFB40154D2A # 836 - 839 D6 S4 C4 + .quad 0xfffffffa40154d32, 0xFFFFFFF940154D3A, 0x0000000040124C82, 0xFFFFFFFF40124D0A # 840 - 843 D6 S4 C8 + .quad 0xfffffffe40124d12, 0xFFFFFFFD40154D1A, 0xFFFFFFFC40154D22, 0xFFFFFFFB40154D2A # 844 - 847 D6 S4 C12 + .quad 0x0000000040005c00, 0x0000000040125C81, 0x0000000040125C82, 0xFFFFFFFF40125D0A # 848 - 851 D6 S5 C0 + .quad 0xfffffffe40155d12, 0xFFFFFFFD40155D1A, 0xFFFFFFFC40155D22, 0xFFFFFFFB40155D2A # 852 - 855 D6 S5 C4 + .quad 0xfffffffa40155d32, 0xFFFFFFF940155D3A, 0x0000000040125C82, 0xFFFFFFFF40125D0A # 856 - 859 D6 S5 C8 + .quad 0xfffffffe40155d12, 0xFFFFFFFD40155D1A, 0xFFFFFFFC40155D22, 0xFFFFFFFB40155D2A # 860 - 863 D6 S5 C12 + .quad 0x0000000040006c00, 0x0000000040026C81, 0x00000000400A6C82, 0xFFFFFFFF400C6D0A # 864 - 867 D6 S6 C0 + .quad 0xfffffffe400c6d12, 0xFFFFFFFD400C6D1A, 0xFFFFFFFC400C6D22, 0xFFFFFFFB400C6D2A # 868 - 871 D6 S6 C4 + .quad 0xfffffffa400c6d32, 0xFFFFFFF9400C6D3A, 0x00000000400A6C82, 0xFFFFFFFF400C6D0A # 872 - 875 D6 S6 C8 + .quad 0xfffffffe400c6d12, 0xFFFFFFFD400C6D1A, 0xFFFFFFFC400C6D22, 0xFFFFFFFB400C6D2A # 876 - 879 D6 S6 C12 + .quad 0x0000000040007c00, 0x00000000401A7C81, 0x00000000401CFC82, 0xFFFFFFFF401CFD0A # 880 - 883 D6 S7 C0 + .quad 0xfffffffe401cfd12, 0xFFFFFFFD401CFD1A, 0xFFFFFFFC401CFD22, 0xFFFFFFFB401CFD2A # 884 - 887 D6 S7 C4 + .quad 0xfffffffa401cfd32, 0xFFFFFFF9401CFD3A, 0x00000000401CFC82, 0xFFFFFFFF401CFD0A # 888 - 891 D6 S7 C8 + .quad 0xfffffffe401cfd12, 0xFFFFFFFD401CFD1A, 0xFFFFFFFC401CFD22, 0xFFFFFFFB401CFD2A # 892 - 895 D6 S7 C12 + .quad 0x0000000040000e00, 0x0000000040120E81, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 896 - 899 D7 S0 C0 + .quad 0xfffffffd40120f19, 0xFFFFFFFC40120F21, 0xFFFFFFFB40120F29, 0xFFFFFFFA40120F31 # 900 - 903 D7 S0 C4 + .quad 0xfffffff940120f39, 0x0000000040120E81, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 904 - 907 D7 S0 C8 + .quad 0xfffffffd40120f19, 0xFFFFFFFC40120F21, 0xFFFFFFFB40120F29, 0xFFFFFFFA40120F31 # 908 - 911 D7 S0 C12 + .quad 0x0000000040001e00, 0x0000000040121E81, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 912 - 915 D7 S1 C0 + .quad 0xfffffffd40121f19, 0xFFFFFFFC40121F21, 0xFFFFFFFB40121F29, 0xFFFFFFFA40121F31 # 916 - 919 D7 S1 C4 + .quad 0xfffffff940151f39, 0x0000000040121E81, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 920 - 923 D7 S1 C8 + .quad 0xfffffffd40121f19, 0xFFFFFFFC40121F21, 0xFFFFFFFB40121F29, 0xFFFFFFFA40121F31 # 924 - 927 D7 S1 C12 + .quad 0x0000000040002e00, 0x0000000040122E81, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 928 - 931 D7 S2 C0 + .quad 0xfffffffd40122f19, 0xFFFFFFFC40122F21, 0xFFFFFFFB40122F29, 0xFFFFFFFA40152F31 # 932 - 935 D7 S2 C4 + .quad 0xfffffff940152f39, 0x0000000040122E81, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 936 - 939 D7 S2 C8 + .quad 0xfffffffd40122f19, 0xFFFFFFFC40122F21, 0xFFFFFFFB40122F29, 0xFFFFFFFA40152F31 # 940 - 943 D7 S2 C12 + .quad 0x0000000040003e00, 0x0000000040123E81, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 944 - 947 D7 S3 C0 + .quad 0xfffffffd40123f19, 0xFFFFFFFC40123F21, 0xFFFFFFFB40153F29, 0xFFFFFFFA40153F31 # 948 - 951 D7 S3 C4 + .quad 0xfffffff940153f39, 0x0000000040123E81, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 952 - 955 D7 S3 C8 + .quad 0xfffffffd40123f19, 0xFFFFFFFC40123F21, 0xFFFFFFFB40153F29, 0xFFFFFFFA40153F31 # 956 - 959 D7 S3 C12 + .quad 0x0000000040004e00, 0x0000000040124E81, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 960 - 963 D7 S4 C0 + .quad 0xfffffffd40124f19, 0xFFFFFFFC40154F21, 0xFFFFFFFB40154F29, 0xFFFFFFFA40154F31 # 964 - 967 D7 S4 C4 + .quad 0xfffffff940154f39, 0x0000000040124E81, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 968 - 971 D7 S4 C8 + .quad 0xfffffffd40124f19, 0xFFFFFFFC40154F21, 0xFFFFFFFB40154F29, 0xFFFFFFFA40154F31 # 972 - 975 D7 S4 C12 + .quad 0x0000000040005e00, 0x0000000040125E81, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 976 - 979 D7 S5 C0 + .quad 0xfffffffd40155f19, 0xFFFFFFFC40155F21, 0xFFFFFFFB40155F29, 0xFFFFFFFA40155F31 # 980 - 983 D7 S5 C4 + .quad 0xfffffff940155f39, 0x0000000040125E81, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 984 - 987 D7 S5 C8 + .quad 0xfffffffd40155f19, 0xFFFFFFFC40155F21, 0xFFFFFFFB40155F29, 0xFFFFFFFA40155F31 # 988 - 991 D7 S5 C12 + .quad 0x0000000040006e00, 0x0000000040126E81, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 992 - 995 D7 S6 C0 .quad 0xfffffffd40156f19, 0xFFFFFFFC40156F21, 0xFFFFFFFB40156F29, 0xFFFFFFFA40156F31 # 996 - 999 D7 S6 C4 .quad 0xfffffff940156f39, 0x0000000040126E81, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 1000 - 1003 D7 S6 C8 .quad 0xfffffffd40156f19, 0xFFFFFFFC40156F21, 0xFFFFFFFB40156F29, 0xFFFFFFFA40156F31 # 1004 - 1007 D7 S6 C12 @@ -299,35 +562,35 @@ fast_dma_encode_table: .quad 0xfffffffc400200a0, 0xFFFFFFFB400200A8, 0xFFFFFFFA400200B0, 0xFFFFFFF9400200B8 # 1028 - 1031 D0 S0 C4 neq .quad 0xfffffff8400200c0, 0xFFFFFFFF40020088, 0xFFFFFFFE40020090, 0xFFFFFFFD40020098 # 1032 - 1035 D0 S0 C8 neq .quad 0xfffffffc400200a0, 0xFFFFFFFB400200A8, 0xFFFFFFFA400200B0, 0xFFFFFFF9400200B8 # 1036 - 1039 D0 S0 C12 neq - .quad 0x0000000040101000, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 1040 - 1043 D0 S1 C0 neq + .quad 0x0000000040001000, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 1040 - 1043 D0 S1 C0 neq .quad 0xfffffffc401210a0, 0xFFFFFFFB401210A8, 0xFFFFFFFA401210B0, 0xFFFFFFF9401210B8 # 1044 - 1047 D0 S1 C4 neq .quad 0xfffffff8401510c0, 0xFFFFFFFF40121088, 0xFFFFFFFE40121090, 0xFFFFFFFD40121098 # 1048 - 1051 D0 S1 C8 neq .quad 0xfffffffc401210a0, 0xFFFFFFFB401210A8, 0xFFFFFFFA401210B0, 0xFFFFFFF9401210B8 # 1052 - 1055 D0 S1 C12 neq - .quad 0x0000000040102000, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 1056 - 1059 D0 S2 C0 neq + .quad 0x0000000040002000, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 1056 - 1059 D0 S2 C0 neq .quad 0xfffffffc401220a0, 0xFFFFFFFB401220A8, 0xFFFFFFFA401220B0, 0xFFFFFFF9401520B8 # 1060 - 1063 D0 S2 C4 neq .quad 0xfffffff8401520c0, 0xFFFFFFFF40122088, 0xFFFFFFFE40122090, 0xFFFFFFFD40122098 # 1064 - 1067 D0 S2 C8 neq .quad 0xfffffffc401220a0, 0xFFFFFFFB401220A8, 0xFFFFFFFA401220B0, 0xFFFFFFF9401520B8 # 1068 - 1071 D0 S2 C12 neq - .quad 0x0000000040103000, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 1072 - 1075 D0 S3 C0 neq + .quad 0x0000000040003000, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 1072 - 1075 D0 S3 C0 neq .quad 0xfffffffc401230a0, 0xFFFFFFFB401230A8, 0xFFFFFFFA401530B0, 0xFFFFFFF9401530B8 # 1076 - 1079 D0 S3 C4 neq .quad 0xfffffff8401530c0, 0xFFFFFFFF40123088, 0xFFFFFFFE40123090, 0xFFFFFFFD40123098 # 1080 - 1083 D0 S3 C8 neq .quad 0xfffffffc401230a0, 0xFFFFFFFB401230A8, 0xFFFFFFFA401530B0, 0xFFFFFFF9401530B8 # 1084 - 1087 D0 S3 C12 neq - .quad 0x0000000040104000, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 1088 - 1091 D0 S4 C0 neq + .quad 0x0000000040004000, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 1088 - 1091 D0 S4 C0 neq .quad 0xfffffffc401240a0, 0xFFFFFFFB401540A8, 0xFFFFFFFA401540B0, 0xFFFFFFF9401540B8 # 1092 - 1095 D0 S4 C4 neq .quad 0xfffffff8401540c0, 0xFFFFFFFF40124088, 0xFFFFFFFE40124090, 0xFFFFFFFD40124098 # 1096 - 1099 D0 S4 C8 neq .quad 0xfffffffc401240a0, 0xFFFFFFFB401540A8, 0xFFFFFFFA401540B0, 0xFFFFFFF9401540B8 # 1100 - 1103 D0 S4 C12 neq - .quad 0x0000000040105000, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 1104 - 1107 D0 S5 C0 neq + .quad 0x0000000040005000, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 1104 - 1107 D0 S5 C0 neq .quad 0xfffffffc401550a0, 0xFFFFFFFB401550A8, 0xFFFFFFFA401550B0, 0xFFFFFFF9401550B8 # 1108 - 1111 D0 S5 C4 neq .quad 0xfffffff8401550c0, 0xFFFFFFFF40125088, 0xFFFFFFFE40125090, 0xFFFFFFFD40125098 # 1112 - 1115 D0 S5 C8 neq .quad 0xfffffffc401550a0, 0xFFFFFFFB401550A8, 0xFFFFFFFA401550B0, 0xFFFFFFF9401550B8 # 1116 - 1119 D0 S5 C12 neq - .quad 0x0000000040106000, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 1120 - 1123 D0 S6 C0 neq + .quad 0x0000000040006000, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 1120 - 1123 D0 S6 C0 neq .quad 0xfffffffc401560a0, 0xFFFFFFFB401560A8, 0xFFFFFFFA401560B0, 0xFFFFFFF9401560B8 # 1124 - 1127 D0 S6 C4 neq .quad 0xfffffff8401560c0, 0xFFFFFFFF40126088, 0xFFFFFFFE40126090, 0xFFFFFFFD40156098 # 1128 - 1131 D0 S6 C8 neq .quad 0xfffffffc401560a0, 0xFFFFFFFB401560A8, 0xFFFFFFFA401560B0, 0xFFFFFFF9401560B8 # 1132 - 1135 D0 S6 C12 neq - .quad 0x0000000040107000, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 1136 - 1139 D0 S7 C0 neq + .quad 0x0000000040007000, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 1136 - 1139 D0 S7 C0 neq .quad 0xfffffffc401570a0, 0xFFFFFFFB401570A8, 0xFFFFFFFA401570B0, 0xFFFFFFF9401570B8 # 1140 - 1143 D0 S7 C4 neq .quad 0xfffffff8401570c0, 0xFFFFFFFF40127088, 0xFFFFFFFE40157090, 0xFFFFFFFD40157098 # 1144 - 1147 D0 S7 C8 neq .quad 0xfffffffc401570a0, 0xFFFFFFFB401570A8, 0xFFFFFFFA401570B0, 0xFFFFFFF9401570B8 # 1148 - 1151 D0 S7 C12 neq - .quad 0x0000000040100200, 0x0000000040120281, 0x0000000040120282, 0x0000000040120283 # 1152 - 1155 D1 S0 C0 neq + .quad 0x0000000040000200, 0x0000000040120281, 0x0000000040120282, 0x0000000040120283 # 1152 - 1155 D1 S0 C0 neq .quad 0x0000000040120284, 0x0000000040120285, 0x0000000040120286, 0x0000000040120287 # 1156 - 1159 D1 S0 C4 neq .quad 0xffffffff4012030f, 0xFFFFFFFE40150317, 0xFFFFFFFD4015031F, 0xFFFFFFFC40150327 # 1160 - 1163 D1 S0 C8 neq .quad 0xfffffffb4015032f, 0xFFFFFFFA40150337, 0xFFFFFFF94015033F, 0xFFFFFFF840150347 # 1164 - 1167 D1 S0 C12 neq @@ -335,35 +598,35 @@ fast_dma_encode_table: .quad 0x0000000040021284, 0x0000000040021285, 0x0000000040021286, 0x00000000400A1287 # 1172 - 1175 D1 S1 C4 neq .quad 0xffffffff400c130f, 0xFFFFFFFE400C1317, 0xFFFFFFFD400C131F, 0xFFFFFFFC400C1327 # 1176 - 1179 D1 S1 C8 neq .quad 0xfffffffb400c132f, 0xFFFFFFFA400C1337, 0xFFFFFFF9400C133F, 0xFFFFFFF8400C1347 # 1180 - 1183 D1 S1 C12 neq - .quad 0x0000000040102200, 0x0000000040122281, 0x0000000040122282, 0x0000000040122283 # 1184 - 1187 D1 S2 C0 neq + .quad 0x0000000040002200, 0x0000000040122281, 0x0000000040122282, 0x0000000040122283 # 1184 - 1187 D1 S2 C0 neq .quad 0x0000000040122284, 0x0000000040122285, 0x00000000401A2286, 0x00000000401CA287 # 1188 - 1191 D1 S2 C4 neq .quad 0xffffffff401ca30f, 0xFFFFFFFE401CA317, 0xFFFFFFFD401CA31F, 0xFFFFFFFC401CA327 # 1192 - 1195 D1 S2 C8 neq .quad 0xfffffffb401ca32f, 0xFFFFFFFA401CA337, 0xFFFFFFF9401CA33F, 0xFFFFFFF8401FA347 # 1196 - 1199 D1 S2 C12 neq - .quad 0x0000000040103200, 0x0000000040123281, 0x0000000040123282, 0x0000000040123283 # 1200 - 1203 D1 S3 C0 neq + .quad 0x0000000040003200, 0x0000000040123281, 0x0000000040123282, 0x0000000040123283 # 1200 - 1203 D1 S3 C0 neq .quad 0x0000000040123284, 0x00000000401A3285, 0x00000000401CB286, 0x00000000401CB287 # 1204 - 1207 D1 S3 C4 neq .quad 0xffffffff401cb30f, 0xFFFFFFFE401CB317, 0xFFFFFFFD401CB31F, 0xFFFFFFFC401CB327 # 1208 - 1211 D1 S3 C8 neq .quad 0xfffffffb401cb32f, 0xFFFFFFFA401CB337, 0xFFFFFFF9401FB33F, 0xFFFFFFF8401FB347 # 1212 - 1215 D1 S3 C12 neq - .quad 0x0000000040104200, 0x0000000040124281, 0x0000000040124282, 0x0000000040124283 # 1216 - 1219 D1 S4 C0 neq + .quad 0x0000000040004200, 0x0000000040124281, 0x0000000040124282, 0x0000000040124283 # 1216 - 1219 D1 S4 C0 neq .quad 0x00000000401a4284, 0x00000000401CC285, 0x00000000401CC286, 0x00000000401CC287 # 1220 - 1223 D1 S4 C4 neq .quad 0xffffffff401cc30f, 0xFFFFFFFE401CC317, 0xFFFFFFFD401CC31F, 0xFFFFFFFC401CC327 # 1224 - 1227 D1 S4 C8 neq .quad 0xfffffffb401cc32f, 0xFFFFFFFA401FC337, 0xFFFFFFF9401FC33F, 0xFFFFFFF8401FC347 # 1228 - 1231 D1 S4 C12 neq - .quad 0x0000000040105200, 0x0000000040125281, 0x0000000040125282, 0x00000000401A5283 # 1232 - 1235 D1 S5 C0 neq + .quad 0x0000000040005200, 0x0000000040125281, 0x0000000040125282, 0x00000000401A5283 # 1232 - 1235 D1 S5 C0 neq .quad 0x00000000401cd284, 0x00000000401CD285, 0x00000000401CD286, 0x00000000401CD287 # 1236 - 1239 D1 S5 C4 neq .quad 0xffffffff401cd30f, 0xFFFFFFFE401CD317, 0xFFFFFFFD401CD31F, 0xFFFFFFFC401CD327 # 1240 - 1243 D1 S5 C8 neq .quad 0xfffffffb401fd32f, 0xFFFFFFFA401FD337, 0xFFFFFFF9401FD33F, 0xFFFFFFF8401FD347 # 1244 - 1247 D1 S5 C12 neq - .quad 0x0000000040106200, 0x0000000040126281, 0x00000000401A6282, 0x00000000401CE283 # 1248 - 1251 D1 S6 C0 neq + .quad 0x0000000040006200, 0x0000000040126281, 0x00000000401A6282, 0x00000000401CE283 # 1248 - 1251 D1 S6 C0 neq .quad 0x00000000401ce284, 0x00000000401CE285, 0x00000000401CE286, 0x00000000401CE287 # 1252 - 1255 D1 S6 C4 neq .quad 0xffffffff401ce30f, 0xFFFFFFFE401CE317, 0xFFFFFFFD401CE31F, 0xFFFFFFFC401FE327 # 1256 - 1259 D1 S6 C8 neq .quad 0xfffffffb401fe32f, 0xFFFFFFFA401FE337, 0xFFFFFFF9401FE33F, 0xFFFFFFF8401FE347 # 1260 - 1263 D1 S6 C12 neq - .quad 0x0000000040107200, 0x00000000401A7281, 0x00000000401CF282, 0x00000000401CF283 # 1264 - 1267 D1 S7 C0 neq + .quad 0x0000000040007200, 0x00000000401A7281, 0x00000000401CF282, 0x00000000401CF283 # 1264 - 1267 D1 S7 C0 neq .quad 0x00000000401cf284, 0x00000000401CF285, 0x00000000401CF286, 0x00000000401CF287 # 1268 - 1271 D1 S7 C4 neq .quad 0xffffffff401cf30f, 0xFFFFFFFE401CF317, 0xFFFFFFFD401FF31F, 0xFFFFFFFC401FF327 # 1272 - 1275 D1 S7 C8 neq .quad 0xfffffffb401ff32f, 0xFFFFFFFA401FF337, 0xFFFFFFF9401FF33F, 0xFFFFFFF8401FF347 # 1276 - 1279 D1 S7 C12 neq - .quad 0x0000000040100400, 0x0000000040120481, 0x0000000040120482, 0x0000000040120483 # 1280 - 1283 D2 S0 C0 neq + .quad 0x0000000040000400, 0x0000000040120481, 0x0000000040120482, 0x0000000040120483 # 1280 - 1283 D2 S0 C0 neq .quad 0x0000000040120484, 0x0000000040120485, 0x0000000040120486, 0xFFFFFFFF4012050E # 1284 - 1287 D2 S0 C4 neq .quad 0xfffffffe40120516, 0xFFFFFFFD4015051E, 0xFFFFFFFC40150526, 0xFFFFFFFB4015052E # 1288 - 1291 D2 S0 C8 neq .quad 0xfffffffa40150536, 0xFFFFFFF94015053E, 0xFFFFFFF840150546, 0xFFFFFFFF4012050E # 1292 - 1295 D2 S0 C12 neq - .quad 0x0000000040101400, 0x0000000040121481, 0x0000000040121482, 0x0000000040121483 # 1296 - 1299 D2 S1 C0 neq + .quad 0x0000000040001400, 0x0000000040121481, 0x0000000040121482, 0x0000000040121483 # 1296 - 1299 D2 S1 C0 neq .quad 0x0000000040121484, 0x0000000040121485, 0x0000000040121486, 0xFFFFFFFF4012150E # 1300 - 1303 D2 S1 C4 neq .quad 0xfffffffe40151516, 0xFFFFFFFD4015151E, 0xFFFFFFFC40151526, 0xFFFFFFFB4015152E # 1304 - 1307 D2 S1 C8 neq .quad 0xfffffffa40151536, 0xFFFFFFF94015153E, 0xFFFFFFF840151546, 0xFFFFFFFF4012150E # 1308 - 1311 D2 S1 C12 neq @@ -371,35 +634,35 @@ fast_dma_encode_table: .quad 0x0000000040022484, 0x0000000040022485, 0x00000000400A2486, 0xFFFFFFFF400C250E # 1316 - 1319 D2 S2 C4 neq .quad 0xfffffffe400c2516, 0xFFFFFFFD400C251E, 0xFFFFFFFC400C2526, 0xFFFFFFFB400C252E # 1320 - 1323 D2 S2 C8 neq .quad 0xfffffffa400c2536, 0xFFFFFFF9400C253E, 0xFFFFFFF8400C2546, 0xFFFFFFFF400C250E # 1324 - 1327 D2 S2 C12 neq - .quad 0x0000000040103400, 0x0000000040123481, 0x0000000040123482, 0x0000000040123483 # 1328 - 1331 D2 S3 C0 neq + .quad 0x0000000040003400, 0x0000000040123481, 0x0000000040123482, 0x0000000040123483 # 1328 - 1331 D2 S3 C0 neq .quad 0x0000000040123484, 0x00000000401A3485, 0x00000000401CB486, 0xFFFFFFFF401CB50E # 1332 - 1335 D2 S3 C4 neq .quad 0xfffffffe401cb516, 0xFFFFFFFD401CB51E, 0xFFFFFFFC401CB526, 0xFFFFFFFB401CB52E # 1336 - 1339 D2 S3 C8 neq .quad 0xfffffffa401cb536, 0xFFFFFFF9401CB53E, 0xFFFFFFF8401FB546, 0xFFFFFFFF401CB50E # 1340 - 1343 D2 S3 C12 neq - .quad 0x0000000040104400, 0x0000000040124481, 0x0000000040124482, 0x0000000040124483 # 1344 - 1347 D2 S4 C0 neq + .quad 0x0000000040004400, 0x0000000040124481, 0x0000000040124482, 0x0000000040124483 # 1344 - 1347 D2 S4 C0 neq .quad 0x00000000401a4484, 0x00000000401CC485, 0x00000000401CC486, 0xFFFFFFFF401CC50E # 1348 - 1351 D2 S4 C4 neq .quad 0xfffffffe401cc516, 0xFFFFFFFD401CC51E, 0xFFFFFFFC401CC526, 0xFFFFFFFB401CC52E # 1352 - 1355 D2 S4 C8 neq .quad 0xfffffffa401cc536, 0xFFFFFFF9401FC53E, 0xFFFFFFF8401FC546, 0xFFFFFFFF401CC50E # 1356 - 1359 D2 S4 C12 neq - .quad 0x0000000040105400, 0x0000000040125481, 0x0000000040125482, 0x00000000401A5483 # 1360 - 1363 D2 S5 C0 neq + .quad 0x0000000040005400, 0x0000000040125481, 0x0000000040125482, 0x00000000401A5483 # 1360 - 1363 D2 S5 C0 neq .quad 0x00000000401cd484, 0x00000000401CD485, 0x00000000401CD486, 0xFFFFFFFF401CD50E # 1364 - 1367 D2 S5 C4 neq .quad 0xfffffffe401cd516, 0xFFFFFFFD401CD51E, 0xFFFFFFFC401CD526, 0xFFFFFFFB401CD52E # 1368 - 1371 D2 S5 C8 neq .quad 0xfffffffa401fd536, 0xFFFFFFF9401FD53E, 0xFFFFFFF8401FD546, 0xFFFFFFFF401CD50E # 1372 - 1375 D2 S5 C12 neq - .quad 0x0000000040106400, 0x0000000040126481, 0x00000000401A6482, 0x00000000401CE483 # 1376 - 1379 D2 S6 C0 neq + .quad 0x0000000040006400, 0x0000000040126481, 0x00000000401A6482, 0x00000000401CE483 # 1376 - 1379 D2 S6 C0 neq .quad 0x00000000401ce484, 0x00000000401CE485, 0x00000000401CE486, 0xFFFFFFFF401CE50E # 1380 - 1383 D2 S6 C4 neq .quad 0xfffffffe401ce516, 0xFFFFFFFD401CE51E, 0xFFFFFFFC401CE526, 0xFFFFFFFB401FE52E # 1384 - 1387 D2 S6 C8 neq .quad 0xfffffffa401fe536, 0xFFFFFFF9401FE53E, 0xFFFFFFF8401FE546, 0xFFFFFFFF401CE50E # 1388 - 1391 D2 S6 C12 neq - .quad 0x0000000040107400, 0x00000000401A7481, 0x00000000401CF482, 0x00000000401CF483 # 1392 - 1395 D2 S7 C0 neq + .quad 0x0000000040007400, 0x00000000401A7481, 0x00000000401CF482, 0x00000000401CF483 # 1392 - 1395 D2 S7 C0 neq .quad 0x00000000401cf484, 0x00000000401CF485, 0x00000000401CF486, 0xFFFFFFFF401CF50E # 1396 - 1399 D2 S7 C4 neq .quad 0xfffffffe401cf516, 0xFFFFFFFD401CF51E, 0xFFFFFFFC401FF526, 0xFFFFFFFB401FF52E # 1400 - 1403 D2 S7 C8 neq .quad 0xfffffffa401ff536, 0xFFFFFFF9401FF53E, 0xFFFFFFF8401FF546, 0xFFFFFFFF401CF50E # 1404 - 1407 D2 S7 C12 neq - .quad 0x0000000040100600, 0x0000000040120681, 0x0000000040120682, 0x0000000040120683 # 1408 - 1411 D3 S0 C0 neq + .quad 0x0000000040000600, 0x0000000040120681, 0x0000000040120682, 0x0000000040120683 # 1408 - 1411 D3 S0 C0 neq .quad 0x0000000040120684, 0x0000000040120685, 0xFFFFFFFF4012070D, 0xFFFFFFFE40120715 # 1412 - 1415 D3 S0 C4 neq .quad 0xfffffffd4012071d, 0xFFFFFFFC40150725, 0xFFFFFFFB4015072D, 0xFFFFFFFA40150735 # 1416 - 1419 D3 S0 C8 neq .quad 0xfffffff94015073d, 0xFFFFFFF840150745, 0xFFFFFFFF4012070D, 0xFFFFFFFE40120715 # 1420 - 1423 D3 S0 C12 neq - .quad 0x0000000040101600, 0x0000000040121681, 0x0000000040121682, 0x0000000040121683 # 1424 - 1427 D3 S1 C0 neq + .quad 0x0000000040001600, 0x0000000040121681, 0x0000000040121682, 0x0000000040121683 # 1424 - 1427 D3 S1 C0 neq .quad 0x0000000040121684, 0x0000000040121685, 0xFFFFFFFF4012170D, 0xFFFFFFFE40121715 # 1428 - 1431 D3 S1 C4 neq .quad 0xfffffffd4015171d, 0xFFFFFFFC40151725, 0xFFFFFFFB4015172D, 0xFFFFFFFA40151735 # 1432 - 1435 D3 S1 C8 neq .quad 0xfffffff94015173d, 0xFFFFFFF840151745, 0xFFFFFFFF4012170D, 0xFFFFFFFE40121715 # 1436 - 1439 D3 S1 C12 neq - .quad 0x0000000040102600, 0x0000000040122681, 0x0000000040122682, 0x0000000040122683 # 1440 - 1443 D3 S2 C0 neq + .quad 0x0000000040002600, 0x0000000040122681, 0x0000000040122682, 0x0000000040122683 # 1440 - 1443 D3 S2 C0 neq .quad 0x0000000040122684, 0x0000000040122685, 0xFFFFFFFF4012270D, 0xFFFFFFFE40152715 # 1444 - 1447 D3 S2 C4 neq .quad 0xfffffffd4015271d, 0xFFFFFFFC40152725, 0xFFFFFFFB4015272D, 0xFFFFFFFA40152735 # 1448 - 1451 D3 S2 C8 neq .quad 0xfffffff94015273d, 0xFFFFFFF840152745, 0xFFFFFFFF4012270D, 0xFFFFFFFE40152715 # 1452 - 1455 D3 S2 C12 neq @@ -407,35 +670,35 @@ fast_dma_encode_table: .quad 0x0000000040023684, 0x00000000400A3685, 0xFFFFFFFF400C370D, 0xFFFFFFFE400C3715 # 1460 - 1463 D3 S3 C4 neq .quad 0xfffffffd400c371d, 0xFFFFFFFC400C3725, 0xFFFFFFFB400C372D, 0xFFFFFFFA400C3735 # 1464 - 1467 D3 S3 C8 neq .quad 0xfffffff9400c373d, 0xFFFFFFF8400C3745, 0xFFFFFFFF400C370D, 0xFFFFFFFE400C3715 # 1468 - 1471 D3 S3 C12 neq - .quad 0x0000000040104600, 0x0000000040124681, 0x0000000040124682, 0x0000000040124683 # 1472 - 1475 D3 S4 C0 neq + .quad 0x0000000040004600, 0x0000000040124681, 0x0000000040124682, 0x0000000040124683 # 1472 - 1475 D3 S4 C0 neq .quad 0x00000000401a4684, 0x00000000401CC685, 0xFFFFFFFF401CC70D, 0xFFFFFFFE401CC715 # 1476 - 1479 D3 S4 C4 neq .quad 0xfffffffd401cc71d, 0xFFFFFFFC401CC725, 0xFFFFFFFB401CC72D, 0xFFFFFFFA401CC735 # 1480 - 1483 D3 S4 C8 neq .quad 0xfffffff9401cc73d, 0xFFFFFFF8401FC745, 0xFFFFFFFF401CC70D, 0xFFFFFFFE401CC715 # 1484 - 1487 D3 S4 C12 neq - .quad 0x0000000040105600, 0x0000000040125681, 0x0000000040125682, 0x00000000401A5683 # 1488 - 1491 D3 S5 C0 neq + .quad 0x0000000040005600, 0x0000000040125681, 0x0000000040125682, 0x00000000401A5683 # 1488 - 1491 D3 S5 C0 neq .quad 0x00000000401cd684, 0x00000000401CD685, 0xFFFFFFFF401CD70D, 0xFFFFFFFE401CD715 # 1492 - 1495 D3 S5 C4 neq .quad 0xfffffffd401cd71d, 0xFFFFFFFC401CD725, 0xFFFFFFFB401CD72D, 0xFFFFFFFA401CD735 # 1496 - 1499 D3 S5 C8 neq .quad 0xfffffff9401fd73d, 0xFFFFFFF8401FD745, 0xFFFFFFFF401CD70D, 0xFFFFFFFE401CD715 # 1500 - 1503 D3 S5 C12 neq - .quad 0x0000000040106600, 0x0000000040126681, 0x00000000401A6682, 0x00000000401CE683 # 1504 - 1507 D3 S6 C0 neq + .quad 0x0000000040006600, 0x0000000040126681, 0x00000000401A6682, 0x00000000401CE683 # 1504 - 1507 D3 S6 C0 neq .quad 0x00000000401ce684, 0x00000000401CE685, 0xFFFFFFFF401CE70D, 0xFFFFFFFE401CE715 # 1508 - 1511 D3 S6 C4 neq .quad 0xfffffffd401ce71d, 0xFFFFFFFC401CE725, 0xFFFFFFFB401CE72D, 0xFFFFFFFA401FE735 # 1512 - 1515 D3 S6 C8 neq .quad 0xfffffff9401fe73d, 0xFFFFFFF8401FE745, 0xFFFFFFFF401CE70D, 0xFFFFFFFE401CE715 # 1516 - 1519 D3 S6 C12 neq - .quad 0x0000000040107600, 0x00000000401A7681, 0x00000000401CF682, 0x00000000401CF683 # 1520 - 1523 D3 S7 C0 neq + .quad 0x0000000040007600, 0x00000000401A7681, 0x00000000401CF682, 0x00000000401CF683 # 1520 - 1523 D3 S7 C0 neq .quad 0x00000000401cf684, 0x00000000401CF685, 0xFFFFFFFF401CF70D, 0xFFFFFFFE401CF715 # 1524 - 1527 D3 S7 C4 neq .quad 0xfffffffd401cf71d, 0xFFFFFFFC401CF725, 0xFFFFFFFB401FF72D, 0xFFFFFFFA401FF735 # 1528 - 1531 D3 S7 C8 neq .quad 0xfffffff9401ff73d, 0xFFFFFFF8401FF745, 0xFFFFFFFF401CF70D, 0xFFFFFFFE401CF715 # 1532 - 1535 D3 S7 C12 neq - .quad 0x0000000040100800, 0x0000000040120881, 0x0000000040120882, 0x0000000040120883 # 1536 - 1539 D4 S0 C0 neq + .quad 0x0000000040000800, 0x0000000040120881, 0x0000000040120882, 0x0000000040120883 # 1536 - 1539 D4 S0 C0 neq .quad 0x0000000040120884, 0xFFFFFFFF4012090C, 0xFFFFFFFE40120914, 0xFFFFFFFD4012091C # 1540 - 1543 D4 S0 C4 neq .quad 0xfffffffc40120924, 0xFFFFFFFB4015092C, 0xFFFFFFFA40150934, 0xFFFFFFF94015093C # 1544 - 1547 D4 S0 C8 neq .quad 0xfffffff840150944, 0xFFFFFFFF4012090C, 0xFFFFFFFE40120914, 0xFFFFFFFD4012091C # 1548 - 1551 D4 S0 C12 neq - .quad 0x0000000040101800, 0x0000000040121881, 0x0000000040121882, 0x0000000040121883 # 1552 - 1555 D4 S1 C0 neq + .quad 0x0000000040001800, 0x0000000040121881, 0x0000000040121882, 0x0000000040121883 # 1552 - 1555 D4 S1 C0 neq .quad 0x0000000040121884, 0xFFFFFFFF4012190C, 0xFFFFFFFE40121914, 0xFFFFFFFD4012191C # 1556 - 1559 D4 S1 C4 neq .quad 0xfffffffc40151924, 0xFFFFFFFB4015192C, 0xFFFFFFFA40151934, 0xFFFFFFF94015193C # 1560 - 1563 D4 S1 C8 neq .quad 0xfffffff840151944, 0xFFFFFFFF4012190C, 0xFFFFFFFE40121914, 0xFFFFFFFD4012191C # 1564 - 1567 D4 S1 C12 neq - .quad 0x0000000040102800, 0x0000000040122881, 0x0000000040122882, 0x0000000040122883 # 1568 - 1571 D4 S2 C0 neq + .quad 0x0000000040002800, 0x0000000040122881, 0x0000000040122882, 0x0000000040122883 # 1568 - 1571 D4 S2 C0 neq .quad 0x0000000040122884, 0xFFFFFFFF4012290C, 0xFFFFFFFE40122914, 0xFFFFFFFD4015291C # 1572 - 1575 D4 S2 C4 neq .quad 0xfffffffc40152924, 0xFFFFFFFB4015292C, 0xFFFFFFFA40152934, 0xFFFFFFF94015293C # 1576 - 1579 D4 S2 C8 neq .quad 0xfffffff840152944, 0xFFFFFFFF4012290C, 0xFFFFFFFE40122914, 0xFFFFFFFD4015291C # 1580 - 1583 D4 S2 C12 neq - .quad 0x0000000040103800, 0x0000000040123881, 0x0000000040123882, 0x0000000040123883 # 1584 - 1587 D4 S3 C0 neq + .quad 0x0000000040003800, 0x0000000040123881, 0x0000000040123882, 0x0000000040123883 # 1584 - 1587 D4 S3 C0 neq .quad 0x0000000040123884, 0xFFFFFFFF4012390C, 0xFFFFFFFE40153914, 0xFFFFFFFD4015391C # 1588 - 1591 D4 S3 C4 neq .quad 0xfffffffc40153924, 0xFFFFFFFB4015392C, 0xFFFFFFFA40153934, 0xFFFFFFF94015393C # 1592 - 1595 D4 S3 C8 neq .quad 0xfffffff840153944, 0xFFFFFFFF4012390C, 0xFFFFFFFE40153914, 0xFFFFFFFD4015391C # 1596 - 1599 D4 S3 C12 neq @@ -443,35 +706,35 @@ fast_dma_encode_table: .quad 0x00000000400a4884, 0xFFFFFFFF400C490C, 0xFFFFFFFE400C4914, 0xFFFFFFFD400C491C # 1604 - 1607 D4 S4 C4 neq .quad 0xfffffffc400c4924, 0xFFFFFFFB400C492C, 0xFFFFFFFA400C4934, 0xFFFFFFF9400C493C # 1608 - 1611 D4 S4 C8 neq .quad 0xfffffff8400c4944, 0xFFFFFFFF400C490C, 0xFFFFFFFE400C4914, 0xFFFFFFFD400C491C # 1612 - 1615 D4 S4 C12 neq - .quad 0x0000000040105800, 0x0000000040125881, 0x0000000040125882, 0x00000000401A5883 # 1616 - 1619 D4 S5 C0 neq + .quad 0x0000000040005800, 0x0000000040125881, 0x0000000040125882, 0x00000000401A5883 # 1616 - 1619 D4 S5 C0 neq .quad 0x00000000401cd884, 0xFFFFFFFF401CD90C, 0xFFFFFFFE401CD914, 0xFFFFFFFD401CD91C # 1620 - 1623 D4 S5 C4 neq .quad 0xfffffffc401cd924, 0xFFFFFFFB401CD92C, 0xFFFFFFFA401CD934, 0xFFFFFFF9401CD93C # 1624 - 1627 D4 S5 C8 neq .quad 0xfffffff8401fd944, 0xFFFFFFFF401CD90C, 0xFFFFFFFE401CD914, 0xFFFFFFFD401CD91C # 1628 - 1631 D4 S5 C12 neq - .quad 0x0000000040106800, 0x0000000040126881, 0x00000000401A6882, 0x00000000401CE883 # 1632 - 1635 D4 S6 C0 neq + .quad 0x0000000040006800, 0x0000000040126881, 0x00000000401A6882, 0x00000000401CE883 # 1632 - 1635 D4 S6 C0 neq .quad 0x00000000401ce884, 0xFFFFFFFF401CE90C, 0xFFFFFFFE401CE914, 0xFFFFFFFD401CE91C # 1636 - 1639 D4 S6 C4 neq .quad 0xfffffffc401ce924, 0xFFFFFFFB401CE92C, 0xFFFFFFFA401CE934, 0xFFFFFFF9401FE93C # 1640 - 1643 D4 S6 C8 neq .quad 0xfffffff8401fe944, 0xFFFFFFFF401CE90C, 0xFFFFFFFE401CE914, 0xFFFFFFFD401CE91C # 1644 - 1647 D4 S6 C12 neq - .quad 0x0000000040107800, 0x00000000401A7881, 0x00000000401CF882, 0x00000000401CF883 # 1648 - 1651 D4 S7 C0 neq + .quad 0x0000000040007800, 0x00000000401A7881, 0x00000000401CF882, 0x00000000401CF883 # 1648 - 1651 D4 S7 C0 neq .quad 0x00000000401cf884, 0xFFFFFFFF401CF90C, 0xFFFFFFFE401CF914, 0xFFFFFFFD401CF91C # 1652 - 1655 D4 S7 C4 neq .quad 0xfffffffc401cf924, 0xFFFFFFFB401CF92C, 0xFFFFFFFA401FF934, 0xFFFFFFF9401FF93C # 1656 - 1659 D4 S7 C8 neq .quad 0xfffffff8401ff944, 0xFFFFFFFF401CF90C, 0xFFFFFFFE401CF914, 0xFFFFFFFD401CF91C # 1660 - 1663 D4 S7 C12 neq - .quad 0x0000000040100a00, 0x0000000040120A81, 0x0000000040120A82, 0x0000000040120A83 # 1664 - 1667 D5 S0 C0 neq + .quad 0x0000000040000a00, 0x0000000040120A81, 0x0000000040120A82, 0x0000000040120A83 # 1664 - 1667 D5 S0 C0 neq .quad 0xffffffff40120b0b, 0xFFFFFFFE40120B13, 0xFFFFFFFD40120B1B, 0xFFFFFFFC40120B23 # 1668 - 1671 D5 S0 C4 neq .quad 0xfffffffb40120b2b, 0xFFFFFFFA40150B33, 0xFFFFFFF940150B3B, 0xFFFFFFF840150B43 # 1672 - 1675 D5 S0 C8 neq .quad 0xffffffff40120b0b, 0xFFFFFFFE40120B13, 0xFFFFFFFD40120B1B, 0xFFFFFFFC40120B23 # 1676 - 1679 D5 S0 C12 neq - .quad 0x0000000040101a00, 0x0000000040121A81, 0x0000000040121A82, 0x0000000040121A83 # 1680 - 1683 D5 S1 C0 neq + .quad 0x0000000040001a00, 0x0000000040121A81, 0x0000000040121A82, 0x0000000040121A83 # 1680 - 1683 D5 S1 C0 neq .quad 0xffffffff40121b0b, 0xFFFFFFFE40121B13, 0xFFFFFFFD40121B1B, 0xFFFFFFFC40121B23 # 1684 - 1687 D5 S1 C4 neq .quad 0xfffffffb40151b2b, 0xFFFFFFFA40151B33, 0xFFFFFFF940151B3B, 0xFFFFFFF840151B43 # 1688 - 1691 D5 S1 C8 neq .quad 0xffffffff40121b0b, 0xFFFFFFFE40121B13, 0xFFFFFFFD40121B1B, 0xFFFFFFFC40121B23 # 1692 - 1695 D5 S1 C12 neq - .quad 0x0000000040102a00, 0x0000000040122A81, 0x0000000040122A82, 0x0000000040122A83 # 1696 - 1699 D5 S2 C0 neq + .quad 0x0000000040002a00, 0x0000000040122A81, 0x0000000040122A82, 0x0000000040122A83 # 1696 - 1699 D5 S2 C0 neq .quad 0xffffffff40122b0b, 0xFFFFFFFE40122B13, 0xFFFFFFFD40122B1B, 0xFFFFFFFC40152B23 # 1700 - 1703 D5 S2 C4 neq .quad 0xfffffffb40152b2b, 0xFFFFFFFA40152B33, 0xFFFFFFF940152B3B, 0xFFFFFFF840152B43 # 1704 - 1707 D5 S2 C8 neq .quad 0xffffffff40122b0b, 0xFFFFFFFE40122B13, 0xFFFFFFFD40122B1B, 0xFFFFFFFC40152B23 # 1708 - 1711 D5 S2 C12 neq - .quad 0x0000000040103a00, 0x0000000040123A81, 0x0000000040123A82, 0x0000000040123A83 # 1712 - 1715 D5 S3 C0 neq + .quad 0x0000000040003a00, 0x0000000040123A81, 0x0000000040123A82, 0x0000000040123A83 # 1712 - 1715 D5 S3 C0 neq .quad 0xffffffff40123b0b, 0xFFFFFFFE40123B13, 0xFFFFFFFD40153B1B, 0xFFFFFFFC40153B23 # 1716 - 1719 D5 S3 C4 neq .quad 0xfffffffb40153b2b, 0xFFFFFFFA40153B33, 0xFFFFFFF940153B3B, 0xFFFFFFF840153B43 # 1720 - 1723 D5 S3 C8 neq .quad 0xffffffff40123b0b, 0xFFFFFFFE40123B13, 0xFFFFFFFD40153B1B, 0xFFFFFFFC40153B23 # 1724 - 1727 D5 S3 C12 neq - .quad 0x0000000040104a00, 0x0000000040124A81, 0x0000000040124A82, 0x0000000040124A83 # 1728 - 1731 D5 S4 C0 neq + .quad 0x0000000040004a00, 0x0000000040124A81, 0x0000000040124A82, 0x0000000040124A83 # 1728 - 1731 D5 S4 C0 neq .quad 0xffffffff40124b0b, 0xFFFFFFFE40154B13, 0xFFFFFFFD40154B1B, 0xFFFFFFFC40154B23 # 1732 - 1735 D5 S4 C4 neq .quad 0xfffffffb40154b2b, 0xFFFFFFFA40154B33, 0xFFFFFFF940154B3B, 0xFFFFFFF840154B43 # 1736 - 1739 D5 S4 C8 neq .quad 0xffffffff40124b0b, 0xFFFFFFFE40154B13, 0xFFFFFFFD40154B1B, 0xFFFFFFFC40154B23 # 1740 - 1743 D5 S4 C12 neq @@ -479,35 +742,35 @@ fast_dma_encode_table: .quad 0xffffffff400c5b0b, 0xFFFFFFFE400C5B13, 0xFFFFFFFD400C5B1B, 0xFFFFFFFC400C5B23 # 1748 - 1751 D5 S5 C4 neq .quad 0xfffffffb400c5b2b, 0xFFFFFFFA400C5B33, 0xFFFFFFF9400C5B3B, 0xFFFFFFF8400C5B43 # 1752 - 1755 D5 S5 C8 neq .quad 0xffffffff400c5b0b, 0xFFFFFFFE400C5B13, 0xFFFFFFFD400C5B1B, 0xFFFFFFFC400C5B23 # 1756 - 1759 D5 S5 C12 neq - .quad 0x0000000040106a00, 0x0000000040126A81, 0x00000000401A6A82, 0x00000000401CEA83 # 1760 - 1763 D5 S6 C0 neq + .quad 0x0000000040006a00, 0x0000000040126A81, 0x00000000401A6A82, 0x00000000401CEA83 # 1760 - 1763 D5 S6 C0 neq .quad 0xffffffff401ceb0b, 0xFFFFFFFE401CEB13, 0xFFFFFFFD401CEB1B, 0xFFFFFFFC401CEB23 # 1764 - 1767 D5 S6 C4 neq .quad 0xfffffffb401ceb2b, 0xFFFFFFFA401CEB33, 0xFFFFFFF9401CEB3B, 0xFFFFFFF8401FEB43 # 1768 - 1771 D5 S6 C8 neq .quad 0xffffffff401ceb0b, 0xFFFFFFFE401CEB13, 0xFFFFFFFD401CEB1B, 0xFFFFFFFC401CEB23 # 1772 - 1775 D5 S6 C12 neq - .quad 0x0000000040107a00, 0x00000000401A7A81, 0x00000000401CFA82, 0x00000000401CFA83 # 1776 - 1779 D5 S7 C0 neq + .quad 0x0000000040007a00, 0x00000000401A7A81, 0x00000000401CFA82, 0x00000000401CFA83 # 1776 - 1779 D5 S7 C0 neq .quad 0xffffffff401cfb0b, 0xFFFFFFFE401CFB13, 0xFFFFFFFD401CFB1B, 0xFFFFFFFC401CFB23 # 1780 - 1783 D5 S7 C4 neq .quad 0xfffffffb401cfb2b, 0xFFFFFFFA401CFB33, 0xFFFFFFF9401FFB3B, 0xFFFFFFF8401FFB43 # 1784 - 1787 D5 S7 C8 neq .quad 0xffffffff401cfb0b, 0xFFFFFFFE401CFB13, 0xFFFFFFFD401CFB1B, 0xFFFFFFFC401CFB23 # 1788 - 1791 D5 S7 C12 neq - .quad 0x0000000040100c00, 0x0000000040120C81, 0x0000000040120C82, 0xFFFFFFFF40120D0A # 1792 - 1795 D6 S0 C0 neq + .quad 0x0000000040000c00, 0x0000000040120C81, 0x0000000040120C82, 0xFFFFFFFF40120D0A # 1792 - 1795 D6 S0 C0 neq .quad 0xfffffffe40120d12, 0xFFFFFFFD40120D1A, 0xFFFFFFFC40120D22, 0xFFFFFFFB40120D2A # 1796 - 1799 D6 S0 C4 neq .quad 0xfffffffa40120d32, 0xFFFFFFF940150D3A, 0xFFFFFFF840150D42, 0xFFFFFFFF40120D0A # 1800 - 1803 D6 S0 C8 neq .quad 0xfffffffe40120d12, 0xFFFFFFFD40120D1A, 0xFFFFFFFC40120D22, 0xFFFFFFFB40120D2A # 1804 - 1807 D6 S0 C12 neq - .quad 0x0000000040101c00, 0x0000000040121C81, 0x0000000040121C82, 0xFFFFFFFF40121D0A # 1808 - 1811 D6 S1 C0 neq + .quad 0x0000000040001c00, 0x0000000040121C81, 0x0000000040121C82, 0xFFFFFFFF40121D0A # 1808 - 1811 D6 S1 C0 neq .quad 0xfffffffe40121d12, 0xFFFFFFFD40121D1A, 0xFFFFFFFC40121D22, 0xFFFFFFFB40121D2A # 1812 - 1815 D6 S1 C4 neq .quad 0xfffffffa40151d32, 0xFFFFFFF940151D3A, 0xFFFFFFF840151D42, 0xFFFFFFFF40121D0A # 1816 - 1819 D6 S1 C8 neq .quad 0xfffffffe40121d12, 0xFFFFFFFD40121D1A, 0xFFFFFFFC40121D22, 0xFFFFFFFB40121D2A # 1820 - 1823 D6 S1 C12 neq - .quad 0x0000000040102c00, 0x0000000040122C81, 0x0000000040122C82, 0xFFFFFFFF40122D0A # 1824 - 1827 D6 S2 C0 neq + .quad 0x0000000040002c00, 0x0000000040122C81, 0x0000000040122C82, 0xFFFFFFFF40122D0A # 1824 - 1827 D6 S2 C0 neq .quad 0xfffffffe40122d12, 0xFFFFFFFD40122D1A, 0xFFFFFFFC40122D22, 0xFFFFFFFB40152D2A # 1828 - 1831 D6 S2 C4 neq .quad 0xfffffffa40152d32, 0xFFFFFFF940152D3A, 0xFFFFFFF840152D42, 0xFFFFFFFF40122D0A # 1832 - 1835 D6 S2 C8 neq .quad 0xfffffffe40122d12, 0xFFFFFFFD40122D1A, 0xFFFFFFFC40122D22, 0xFFFFFFFB40152D2A # 1836 - 1839 D6 S2 C12 neq - .quad 0x0000000040103c00, 0x0000000040123C81, 0x0000000040123C82, 0xFFFFFFFF40123D0A # 1840 - 1843 D6 S3 C0 neq + .quad 0x0000000040003c00, 0x0000000040123C81, 0x0000000040123C82, 0xFFFFFFFF40123D0A # 1840 - 1843 D6 S3 C0 neq .quad 0xfffffffe40123d12, 0xFFFFFFFD40123D1A, 0xFFFFFFFC40153D22, 0xFFFFFFFB40153D2A # 1844 - 1847 D6 S3 C4 neq .quad 0xfffffffa40153d32, 0xFFFFFFF940153D3A, 0xFFFFFFF840153D42, 0xFFFFFFFF40123D0A # 1848 - 1851 D6 S3 C8 neq .quad 0xfffffffe40123d12, 0xFFFFFFFD40123D1A, 0xFFFFFFFC40153D22, 0xFFFFFFFB40153D2A # 1852 - 1855 D6 S3 C12 neq - .quad 0x0000000040104c00, 0x0000000040124C81, 0x0000000040124C82, 0xFFFFFFFF40124D0A # 1856 - 1859 D6 S4 C0 neq + .quad 0x0000000040004c00, 0x0000000040124C81, 0x0000000040124C82, 0xFFFFFFFF40124D0A # 1856 - 1859 D6 S4 C0 neq .quad 0xfffffffe40124d12, 0xFFFFFFFD40154D1A, 0xFFFFFFFC40154D22, 0xFFFFFFFB40154D2A # 1860 - 1863 D6 S4 C4 neq .quad 0xfffffffa40154d32, 0xFFFFFFF940154D3A, 0xFFFFFFF840154D42, 0xFFFFFFFF40124D0A # 1864 - 1867 D6 S4 C8 neq .quad 0xfffffffe40124d12, 0xFFFFFFFD40154D1A, 0xFFFFFFFC40154D22, 0xFFFFFFFB40154D2A # 1868 - 1871 D6 S4 C12 neq - .quad 0x0000000040105c00, 0x0000000040125C81, 0x0000000040125C82, 0xFFFFFFFF40125D0A # 1872 - 1875 D6 S5 C0 neq + .quad 0x0000000040005c00, 0x0000000040125C81, 0x0000000040125C82, 0xFFFFFFFF40125D0A # 1872 - 1875 D6 S5 C0 neq .quad 0xfffffffe40155d12, 0xFFFFFFFD40155D1A, 0xFFFFFFFC40155D22, 0xFFFFFFFB40155D2A # 1876 - 1879 D6 S5 C4 neq .quad 0xfffffffa40155d32, 0xFFFFFFF940155D3A, 0xFFFFFFF840155D42, 0xFFFFFFFF40125D0A # 1880 - 1883 D6 S5 C8 neq .quad 0xfffffffe40155d12, 0xFFFFFFFD40155D1A, 0xFFFFFFFC40155D22, 0xFFFFFFFB40155D2A # 1884 - 1887 D6 S5 C12 neq @@ -515,35 +778,35 @@ fast_dma_encode_table: .quad 0xfffffffe400c6d12, 0xFFFFFFFD400C6D1A, 0xFFFFFFFC400C6D22, 0xFFFFFFFB400C6D2A # 1892 - 1895 D6 S6 C4 neq .quad 0xfffffffa400c6d32, 0xFFFFFFF9400C6D3A, 0xFFFFFFF8400C6D42, 0xFFFFFFFF400C6D0A # 1896 - 1899 D6 S6 C8 neq .quad 0xfffffffe400c6d12, 0xFFFFFFFD400C6D1A, 0xFFFFFFFC400C6D22, 0xFFFFFFFB400C6D2A # 1900 - 1903 D6 S6 C12 neq - .quad 0x0000000040107c00, 0x00000000401A7C81, 0x00000000401CFC82, 0xFFFFFFFF401CFD0A # 1904 - 1907 D6 S7 C0 neq + .quad 0x0000000040007c00, 0x00000000401A7C81, 0x00000000401CFC82, 0xFFFFFFFF401CFD0A # 1904 - 1907 D6 S7 C0 neq .quad 0xfffffffe401cfd12, 0xFFFFFFFD401CFD1A, 0xFFFFFFFC401CFD22, 0xFFFFFFFB401CFD2A # 1908 - 1911 D6 S7 C4 neq .quad 0xfffffffa401cfd32, 0xFFFFFFF9401CFD3A, 0xFFFFFFF8401FFD42, 0xFFFFFFFF401CFD0A # 1912 - 1915 D6 S7 C8 neq .quad 0xfffffffe401cfd12, 0xFFFFFFFD401CFD1A, 0xFFFFFFFC401CFD22, 0xFFFFFFFB401CFD2A # 1916 - 1919 D6 S7 C12 neq - .quad 0x0000000040100e00, 0x0000000040120E81, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 1920 - 1923 D7 S0 C0 neq + .quad 0x0000000040000e00, 0x0000000040120E81, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 1920 - 1923 D7 S0 C0 neq .quad 0xfffffffd40120f19, 0xFFFFFFFC40120F21, 0xFFFFFFFB40120F29, 0xFFFFFFFA40120F31 # 1924 - 1927 D7 S0 C4 neq .quad 0xfffffff940120f39, 0xFFFFFFF840150F41, 0xFFFFFFFF40120F09, 0xFFFFFFFE40120F11 # 1928 - 1931 D7 S0 C8 neq .quad 0xfffffffd40120f19, 0xFFFFFFFC40120F21, 0xFFFFFFFB40120F29, 0xFFFFFFFA40120F31 # 1932 - 1935 D7 S0 C12 neq - .quad 0x0000000040101e00, 0x0000000040121E81, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 1936 - 1939 D7 S1 C0 neq + .quad 0x0000000040001e00, 0x0000000040121E81, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 1936 - 1939 D7 S1 C0 neq .quad 0xfffffffd40121f19, 0xFFFFFFFC40121F21, 0xFFFFFFFB40121F29, 0xFFFFFFFA40121F31 # 1940 - 1943 D7 S1 C4 neq .quad 0xfffffff940151f39, 0xFFFFFFF840151F41, 0xFFFFFFFF40121F09, 0xFFFFFFFE40121F11 # 1944 - 1947 D7 S1 C8 neq .quad 0xfffffffd40121f19, 0xFFFFFFFC40121F21, 0xFFFFFFFB40121F29, 0xFFFFFFFA40121F31 # 1948 - 1951 D7 S1 C12 neq - .quad 0x0000000040102e00, 0x0000000040122E81, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 1952 - 1955 D7 S2 C0 neq + .quad 0x0000000040002e00, 0x0000000040122E81, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 1952 - 1955 D7 S2 C0 neq .quad 0xfffffffd40122f19, 0xFFFFFFFC40122F21, 0xFFFFFFFB40122F29, 0xFFFFFFFA40152F31 # 1956 - 1959 D7 S2 C4 neq .quad 0xfffffff940152f39, 0xFFFFFFF840152F41, 0xFFFFFFFF40122F09, 0xFFFFFFFE40122F11 # 1960 - 1963 D7 S2 C8 neq .quad 0xfffffffd40122f19, 0xFFFFFFFC40122F21, 0xFFFFFFFB40122F29, 0xFFFFFFFA40152F31 # 1964 - 1967 D7 S2 C12 neq - .quad 0x0000000040103e00, 0x0000000040123E81, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 1968 - 1971 D7 S3 C0 neq + .quad 0x0000000040003e00, 0x0000000040123E81, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 1968 - 1971 D7 S3 C0 neq .quad 0xfffffffd40123f19, 0xFFFFFFFC40123F21, 0xFFFFFFFB40153F29, 0xFFFFFFFA40153F31 # 1972 - 1975 D7 S3 C4 neq .quad 0xfffffff940153f39, 0xFFFFFFF840153F41, 0xFFFFFFFF40123F09, 0xFFFFFFFE40123F11 # 1976 - 1979 D7 S3 C8 neq .quad 0xfffffffd40123f19, 0xFFFFFFFC40123F21, 0xFFFFFFFB40153F29, 0xFFFFFFFA40153F31 # 1980 - 1983 D7 S3 C12 neq - .quad 0x0000000040104e00, 0x0000000040124E81, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 1984 - 1987 D7 S4 C0 neq + .quad 0x0000000040004e00, 0x0000000040124E81, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 1984 - 1987 D7 S4 C0 neq .quad 0xfffffffd40124f19, 0xFFFFFFFC40154F21, 0xFFFFFFFB40154F29, 0xFFFFFFFA40154F31 # 1988 - 1991 D7 S4 C4 neq .quad 0xfffffff940154f39, 0xFFFFFFF840154F41, 0xFFFFFFFF40124F09, 0xFFFFFFFE40124F11 # 1992 - 1995 D7 S4 C8 neq .quad 0xfffffffd40124f19, 0xFFFFFFFC40154F21, 0xFFFFFFFB40154F29, 0xFFFFFFFA40154F31 # 1996 - 1999 D7 S4 C12 neq - .quad 0x0000000040105e00, 0x0000000040125E81, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 2000 - 2003 D7 S5 C0 neq + .quad 0x0000000040005e00, 0x0000000040125E81, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 2000 - 2003 D7 S5 C0 neq .quad 0xfffffffd40155f19, 0xFFFFFFFC40155F21, 0xFFFFFFFB40155F29, 0xFFFFFFFA40155F31 # 2004 - 2007 D7 S5 C4 neq .quad 0xfffffff940155f39, 0xFFFFFFF840155F41, 0xFFFFFFFF40125F09, 0xFFFFFFFE40125F11 # 2008 - 2011 D7 S5 C8 neq .quad 0xfffffffd40155f19, 0xFFFFFFFC40155F21, 0xFFFFFFFB40155F29, 0xFFFFFFFA40155F31 # 2012 - 2015 D7 S5 C12 neq - .quad 0x0000000040106e00, 0x0000000040126E81, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 2016 - 2019 D7 S6 C0 neq + .quad 0x0000000040006e00, 0x0000000040126E81, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 2016 - 2019 D7 S6 C0 neq .quad 0xfffffffd40156f19, 0xFFFFFFFC40156F21, 0xFFFFFFFB40156F29, 0xFFFFFFFA40156F31 # 2020 - 2023 D7 S6 C4 neq .quad 0xfffffff940156f39, 0xFFFFFFF840156F41, 0xFFFFFFFF40126F09, 0xFFFFFFFE40156F11 # 2024 - 2027 D7 S6 C8 neq .quad 0xfffffffd40156f19, 0xFFFFFFFC40156F21, 0xFFFFFFFB40156F29, 0xFFFFFFFA40156F31 # 2028 - 2031 D7 S6 C12 neq @@ -551,5 +814,3 @@ fast_dma_encode_table: .quad 0xfffffffd400c7f19, 0xFFFFFFFC400C7F21, 0xFFFFFFFB400C7F29, 0xFFFFFFFA400C7F31 # 2036 - 2039 D7 S7 C4 neq .quad 0xfffffff9400c7f39, 0xFFFFFFF8400C7F41, 0xFFFFFFFF400C7F09, 0xFFFFFFFE400C7F11 # 2040 - 2043 D7 S7 C8 neq .quad 0xfffffffd400c7f19, 0xFFFFFFFC400C7F21, 0xFFFFFFFB400C7F29, 0xFFFFFFFA400C7F31 # 2044 - 2047 D7 S7 C12 neq - - diff --git a/emulator-asm/src/dma/fast_inputcpy.asm b/emulator-asm/src/dma/fast_inputcpy.asm new file mode 100644 index 000000000..16b7bab6e --- /dev/null +++ b/emulator-asm/src/dma/fast_inputcpy.asm @@ -0,0 +1,162 @@ +.intel_syntax noprefix +.code64 +.text +.global fast_inputcpy +.type fast_inputcpy, @function +.extern fcall_ctx +.extern MEM_FREE_INPUT + +.include "dma_constants.inc" + +# PARAMS +# rdi = dst +# rdx = n (bytes) +# +# Clobbers: rax, rcx, r9, rsi + +fast_inputcpy: + # load current pointer to input data + mov rcx, qword ptr [fcall_ctx + FCALL_RESULT_GOT * 8] + + # set rsi = fcall_ctx.result[rcx - 1] + lea rsi, [fcall_ctx + rcx * 8 + FCALL_RESULT * 8 - 8] + + # rcx = round_up(rdx/8) + lea rcx, [rdx + 7] + shr rcx, 3 + + # read next value, r9 = fcall_ctx.result[rcx + rdx - 1] + mov r9, [rsi + rcx * 8] + + # save next value to MEM_FREE_INPUT + mov qword ptr [MEM_FREE_INPUT], r9 + + # increase FCALL_RESULT_GOT in rcx + add qword ptr [fcall_ctx + FCALL_RESULT_GOT * 8], rcx + + # rsi = input src + # rdx = input bytes to copy + # rdi = dst + + # return dst + mov rax, rdi + + test rdx, 0xFFFFFFFFFFFFFFF8 + jz .L_fast_inputcpy_count_lt_8 + + # only first could be lt 32x8=256 bytes + movzx ecx, dl + and rdx, 0xFFFFFFFFFFFFFF07 + shr rcx, 3 + + # Jump to entry that leaves exactly q MOVSQ until the end + lea r9, [rip + .L_fast_inputcpy_jump_qword_table] + jmp [r9 + rcx*8] + +.p2align 3 +.L_fast_inputcpy_jump_qword_table: + .quad .L_q0 + .quad .L_q1 + .quad .L_q2 + .quad .L_q3 + .quad .L_q4 + .quad .L_q5 + .quad .L_q6 + .quad .L_q7 + .quad .L_q8 + .quad .L_q9 + .quad .L_q10 + .quad .L_q11 + .quad .L_q12 + .quad .L_q13 + .quad .L_q14 + .quad .L_q15 + .quad .L_q16 + .quad .L_q17 + .quad .L_q18 + .quad .L_q19 + .quad .L_q20 + .quad .L_q21 + .quad .L_q22 + .quad .L_q23 + .quad .L_q24 + .quad .L_q25 + .quad .L_q26 + .quad .L_q27 + .quad .L_q28 + .quad .L_q29 + .quad .L_q30 + .quad .L_q31 + .quad .L_q32 + +# Fallthrough chain: entering at q31 executes 31 STOSQ down to q1 +.L_q32: movsq +.L_q31: movsq +.L_q30: movsq +.L_q29: movsq +.L_q28: movsq +.L_q27: movsq +.L_q26: movsq +.L_q25: movsq +.L_q24: movsq +.L_q23: movsq +.L_q22: movsq +.L_q21: movsq +.L_q20: movsq +.L_q19: movsq +.L_q18: movsq +.L_q17: movsq +.L_q16: movsq +.L_q15: movsq +.L_q14: movsq +.L_q13: movsq +.L_q12: movsq +.L_q11: movsq +.L_q10: movsq +.L_q9: movsq +.L_q8: movsq +.L_q7: movsq +.L_q6: movsq +.L_q5: movsq +.L_q4: movsq +.L_q3: movsq +.L_q2: movsq +.L_q1: movsq +.L_q0: + + # check if remain more 32 x 8 = 256 bytes blocks + + test rdx, 0xFFFFFFFFFFFFFF00 # 0xFFFF_FFFF_FFFF_FF00 + jz .L_fast_inputcpy_count_lt_8 + sub rdx, 256 + jmp .L_q32 + + +.L_fast_inputcpy_count_lt_8: + + # Jump to byte tail entry + lea r9, [rip + .L_fast_inputcpy_jump_byte_table] + jmp [r9 + rdx*8] + +.p2align 3 +.L_fast_inputcpy_jump_byte_table: + .quad .L_b0 + .quad .L_b1 + .quad .L_b2 + .quad .L_b3 + .quad .L_b4 + .quad .L_b5 + .quad .L_b6 + .quad .L_b7 + +.L_b7: movsb +.L_b6: movsb +.L_b5: movsb +.L_b4: movsb +.L_b3: movsb +.L_b2: movsb +.L_b1: movsb +.L_b0: + ret + +.size fast_inputcpy, .-fast_inputcpy diff --git a/emulator-asm/src/dma/fast_memcmp.asm b/emulator-asm/src/dma/fast_memcmp.asm new file mode 100644 index 000000000..de6502447 --- /dev/null +++ b/emulator-asm/src/dma/fast_memcmp.asm @@ -0,0 +1,153 @@ +.intel_syntax noprefix +.code64 + + +###################################################################################### +# fast_memcmp - Optimized comparison of two memory regions. Returns (a - b) of the +# first different byte, or 0 if equal. Also updates rdx with the +# effective count (number of bytes checked to find the difference). +# PARAMETERS +# rdi: addr_a +# rsi: addr_b +# rdx: count (bytes) +# +# RESULT +# rdx: updated with effective count (bytes) +# rax: i64 comparison result of first different bytes (a - b) +# +# CLOBBERED REGS: rcx +# ─────────────────────────────────────────────────────────────────────────────────── +# fast_memcmp_count_nz - Same as fast_memcmp, but assumes count > 0 (caller must +# verify this beforehand). +# PARAMETERS +# rdi: addr_a +# rsi: addr_b +# rdx: count (bytes) +# +# RESULT +# rdx: updated with effective count (bytes) +# rax: i64 comparison result of first different bytes (a - b) +# +# CLOBBERED REGS: rcx +# ─────────────────────────────────────────────────────────────────────────────────── +# get_memcmp_effective_count - Updates rdx with the effective count, which is the +# number of bytes needed to compare a and b. +# PARAMETERS +# rdi: addr_a +# rsi: addr_b +# rdx: count (bytes) +# +# RESULT +# rdx: updated with effective count (bytes) +# +# CLOBBERED REGS: rcx +################################################################################ + +.global fast_memcmp +.global fast_memcmp_count_nz +.global get_memcmp_effective_count + +.section .text + +# PARAMETERS +# rdi: addr_a +# rsi: addr_b +# rdx: count (bytes) +# +# RESULT +# rdx: updated with effective count (bytes) +# rax: i64 comparison result of first different bytes (a - b) +# +# CLOBBERED REGS: rcx + +fast_memcmp: + + mov rcx, rdx # rcx = rdx (count) + test rcx, rcx + jz .L_dma_memcmp_zero + + repe cmpsb # Compare byte-by-byte; on mismatch, increments + # rdi, rsi and decrements rcx + + jz .L_fast_memcmp_eq # Jump if all bytes were equal + sub rdx, rcx # rdx = rdx - rcx (*) + movzx rax, byte ptr [rdi - 1] # rax = a (zero-extended) + movzx rcx, byte ptr [rsi - 1] # rcx = b (zero-extended) + sub rax, rcx # rax = a - b + sub rdi, rdx # restore rdi + sub rsi, rdx # restore rsi + ret + +.L_fast_memcmp_eq: + xor rax, rax # rax = 0 + sub rdi, rdx # restore rdi + sub rsi, rdx # restore rsi + ret + +.L_dma_memcmp_zero: + xor rax, rax # rax = 0 + ret + +# PARAMETERS +# rdi: addr_a +# rsi: addr_b +# rdx: count (bytes), must be > 0 +# +# RESULT +# rdx: updated with effective count (bytes) +# rax: i64 comparison result of first different bytes (a - b) +# +# CLOBBERED REGS: rcx + +fast_memcmp_count_nz: + + mov rcx, rdx # rcx = rdx (count) + repe cmpsb # Compare byte-by-byte; on mismatch, increments + # rdi, rsi and decrements rcx + + jz .L_fast_memcmp_count_nz_eq # Jump if all bytes were equal + + sub rdx, rcx # rdx = rdx - rcx (*) + movzx rax, byte ptr [rdi - 1] # rax = a (zero-extended) + movzx rcx, byte ptr [rsi - 1] # rcx = b (zero-extended) + sub rax, rcx # rax = a - b + add rdi, rdx # restore rdi + add rsi, rdx # restore rsi + ret + +.L_fast_memcmp_count_nz_eq: + xor rax, rax # rax = 0 (buffers are equal) + add rdi, rdx # restore rdi + add rsi, rdx # restore rsi + ret + +# PARAMETERS +# rdi: addr_a +# rsi: addr_b +# rdx: count (bytes) +# +# RESULT +# rdx: updated with effective count (bytes) +# +# CLOBBERED REGS: rcx + +get_memcmp_effective_count: + + mov rcx, rdx # rcx = rdx (count) + repe cmpsb # Compare byte-by-byte; on mismatch, increments + # rdi, rsi and decrements rcx + + jz .L_get_memcmp_effective_count_eq # Jump if all bytes were equal + + sub rdx, rcx # rdx = rdx - rcx (*) + add rdi, rdx # restore rdi + add rsi, rdx # restore rsi + ret + +.L_get_memcmp_effective_count_eq: + add rdi, rdx # restore rdi + add rsi, rdx # restore rsi + ret + +# Mark stack as non-executable (required by modern linkers) +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/fast_memset.asm b/emulator-asm/src/dma/fast_memset.asm index 7cdd98de2..46cfd6616 100644 --- a/emulator-asm/src/dma/fast_memset.asm +++ b/emulator-asm/src/dma/fast_memset.asm @@ -13,7 +13,8 @@ fast_memset: movzx eax, sil - + mov rsi, rdi + test rdx, 0xFFFFFFFFFFFFFFF8 jz .L_fast_memset_count_lt_8 @@ -134,6 +135,7 @@ fast_memset: .L_b2: stosb .L_b1: stosb .L_b0: + mov rax, rsi ret -.size fast_memset, .-fast_memset \ No newline at end of file +.size fast_memset, .-fast_memset diff --git a/emulator-asm/src/dma/test/Makefile b/emulator-asm/src/dma/test/Makefile new file mode 100644 index 000000000..c421ce776 --- /dev/null +++ b/emulator-asm/src/dma/test/Makefile @@ -0,0 +1,61 @@ +# Makefile for encode_memcpy_inline test + +CXX = g++ +CXXFLAGS = -std=c++17 -O0 -Wall -Wextra -g -no-pie -I../../../../state-machines/mem-cpp/cpp +LDFLAGS = -no-pie +ASMFLAGS = -g --noexecstack -I$(CURDIR) -I.. +BDIR = build + +# Output directory for object files (current directory) +OBJDIR = . + +SRCS_ASM = ../direct_memcpy_mtrace.asm ../memcpy_fast.asm ../fast_memcpy.asm ../direct_memcpy_mops.asm \ + ../direct_memset_mops.asm ../direct_memset_mtrace.asm ../fast_memset.asm ../fast_memcpy64.asm \ + ../direct_inputcpy_mops.asm ../direct_inputcpy_mtrace.asm ../fast_dma_encode.asm \ + ../check_dynamic_mtrace.asm ../direct_memcmp_mops.asm ../direct_memcmp_mtrace.asm \ + ../fast_memcmp.asm ../fast_inputcpy.asm ../test_dma_api.asm -- + +SRCS_CPP = test_dma.cpp test_dma_encode.cpp test_dma_tools.cpp test_mock.cpp \ + test_dma_memcmp_mtrace.cpp test_dma_memcmp_mops.cpp \ + test_dma_memcpy_mtrace.cpp test_dma_memcpy_mops.cpp \ + test_dma_memset_mtrace.cpp test_dma_memset_mops.cpp \ + test_dma_inputcpy_mtrace.cpp test_dma_inputcpy_mops.cpp \ + test_dma_mem.cpp test_dma_mem_mtrace.cpp test_dma_mem_mops.cpp + + + +OBJS_ASM = $(BDIR)/direct_memcpy_mtrace.o $(BDIR)/memcpy_fast.o $(BDIR)/fast_memcpy.o \ + $(BDIR)/direct_memcpy_mops.o $(BDIR)/direct_memset_mops.o $(BDIR)/direct_memset_mtrace.o \ + $(BDIR)/fast_memset.o $(BDIR)/fast_memcpy64.o $(BDIR)/direct_inputcpy_mops.o \ + $(BDIR)/direct_inputcpy_mtrace.o $(BDIR)/fast_dma_encode.o $(BDIR)/check_dynamic_mtrace.o \ + $(BDIR)/direct_memcmp_mops.o $(BDIR)/direct_memcmp_mtrace.o $(BDIR)/fast_memcmp.o \ + $(BDIR)/fast_inputcpy.o $(BDIR)/test_dma_api.o + +OBJS = $(OBJS_ASM) test_dma.o test_dma_encode.o test_dma_tools.o test_mock.o \ + test_dma_memcmp_mtrace.o test_dma_memcmp_mops.o \ + test_dma_memcpy_mtrace.o test_dma_memcpy_mops.o \ + test_dma_memset_mtrace.o test_dma_memset_mops.o \ + test_dma_inputcpy_mtrace.o test_dma_inputcpy_mops.o \ + test_dma_mem.o test_dma_mem_mtrace.o test_dma_mem_mops.o + +TARGET = test_dma + +all: $(TARGET) + +# Rule to compile assembly files from parent directory into current directory +$(BDIR)/%.o: ../%.asm + as $(ASMFLAGS) -o $@ $< + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +$(TARGET): $(OBJS) + $(CXX) $(LDFLAGS) -o $@ $^ + +test: $(TARGET) + ./$(TARGET) + +clean: + rm -f *.o $(TARGET) $(OBJS_ASM) + +.PHONY: all test clean diff --git a/emulator-asm/src/dma/test/test_dma.cpp b/emulator-asm/src/dma/test/test_dma.cpp new file mode 100644 index 000000000..782ddb7be --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "test_dma_encode.hpp" +#include "test_dma_tools.hpp" +#include "test_mock.hpp" +#include "test_dma_memcmp_mops.hpp" +#include "test_dma_memcpy_mops.hpp" +#include "test_dma_memset_mops.hpp" +#include "test_dma_inputcpy_mops.hpp" +#include "test_dma_memcmp_mtrace.hpp" +#include "test_dma_memcpy_mtrace.hpp" +#include "test_dma_memset_mtrace.hpp" +#include "test_dma_inputcpy_mtrace.hpp" + +int main () { + test_dma_inputcpy_mops(); + test_dma_inputcpy_mtrace(); + test_dma_memcpy_mops(); + test_dma_memcmp_mops(); + test_dma_memset_mops(); + test_dma_memcpy_mtrace(); + test_dma_memcmp_mtrace(); + test_dma_memset_mtrace(); +} + + diff --git a/emulator-asm/src/dma/test/test_dma_encode.cpp b/emulator-asm/src/dma/test/test_dma_encode.cpp new file mode 100644 index 000000000..6b84a5dd5 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_encode.cpp @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include +#include + +#include "test_dma_encode.hpp" + +struct EncodeInfo { + const char *title; + uint64_t mask; + uint64_t rs_bits; +}; + +EncodeInfo encode_info[] = { + {"pre_count", DMA_PRE_COUNT_TEST_MASK, 0}, + {"post_count", DMA_POST_COUNT_TEST_MASK, DMA_POST_COUNT_RS}, + {"pre_writes", DMA_PRE_WRITES_TEST_MASK, DMA_PRE_WRITES_RS}, + {"dst_offset", DMA_DST_OFFSET_TEST_MASK, DMA_DST_OFFSET_RS}, + {"src_offset", DMA_SRC_OFFSET_TEST_MASK, DMA_SRC_OFFSET_RS}, + {"double_src_pre", DMA_DOUBLE_SRC_PRE_TEST_MASK, DMA_DOUBLE_SRC_PRE_RS}, + {"double_src_post", DMA_DOUBLE_SRC_POST_TEST_MASK, DMA_DOUBLE_SRC_POST_RS}, + {"extra_src_reads", DMA_EXTRA_SRC_READS_TEST_MASK, DMA_EXTRA_SRC_READS_RS}, + {"src64_inc_by_pre", DMA_SRC64_INC_BY_PRE_TEST_MASK, DMA_SRC64_INC_BY_PRE_RS}, + {"unaligned_dst_src", DMA_UNALIGNED_DST_SRC_TEST_MASK, DMA_UNALIGNED_DST_SRC_RS}, + {"fill_byte_cmp_negative", DMA_FILL_BYTE_CMD_RES_TEST_MASK, DMA_FILL_BYTE_RS}, + {"requires_dma", DMA_REQUIRES_DMA_TEST_MASK, DMA_REQUIRES_DMA_RS}, + {"lpre_count", DMA_LPRE_COUNT_TEST_MASK, DMA_LPRE_COUNT_RS}, + {"loop_count", DMA_LOOP_COUNT_TEST_MASK, DMA_LOOP_COUNT_RS}, + {"", 0, 0} +}; + + +// #bits bits +// pre_count: 0-7 3 0-2 +// post_count: 0-8(*) 4 3-6 (*) memcmp +// pre_writes: 0,1,2 2 7-8 +// dst_offset: 0-7 3 9-11 +// src_offset: 0-7 3 12-14 +// double_src_pre: 0,1 1 15 +// double_src_post: 0,1 1 16 +// extra_src_reads: 0-3 2 17-18 +// src64_inc_by_pre: 1 19 +// unaligned_dst_src: 1 20 +// fill_byte/cmp: 8 21-28 +// cmp_negative: 1 29 +// requires_dma: 1 30 +// (reserved) 1 31 +// lpre_count 3 32-34 +// loop_count 29 35-63 + + +uint64_t calculate_encode_memcmp(uint64_t dst, uint64_t src, size_t count, int result) { + return calculate_encode(dst, src, count, result != 0, true) | DMA_REQUIRES_DMA_TEST_MASK | ((result & DMA_FILL_BITS9_MASK) << DMA_FILL_BYTE_RS); +} + +uint64_t calculate_encode_memset(uint64_t dst, size_t count, uint64_t byte) { + return calculate_encode(dst, 0, count, false, false) | ((byte & DMA_FILL_BYTE_MASK) << DMA_FILL_BYTE_RS); +} + +uint64_t calculate_encode_inputcpy(uint64_t dst, size_t count) { + return calculate_encode(dst, 0, count, false, false); +} + +uint64_t calculate_encode(uint64_t dst, uint64_t src, size_t count, bool neq, bool has_src) { + + uint64_t dst_offset = dst & 0x07; + uint64_t src_offset = src & 0x07; + + uint64_t pre_count = 0; + uint64_t loop_count = 0; + uint64_t post_count = 0; + + if (dst_offset > 0) { + pre_count = 8 - dst_offset; + if (pre_count >= count) { + pre_count = count; + } else { + uint64_t pending = count - pre_count; + loop_count = pending >> 3; + post_count = pending & 0x7; + } + } else { + loop_count = count >> 3; + post_count = count & 0x07; + } + + uint64_t pre_writes = (pre_count > 0) + (post_count > 0); + // uint64_T to_src_offset = (src + count - 1) & 0x07; + uint64_t src_offset_pos = (src_offset + pre_count) & 0x07; + uint64_t double_src_post = (src_offset_pos + post_count) > 8; + uint64_t double_src_pre = (src_offset + pre_count) > 8; + uint64_t extra_src_reads = count == 0 ? 0 : ((((src + count - 1) >> 3) - (src >> 3) + 1) - loop_count); + + uint64_t src64_inc_by_pre = (pre_count > 0 && (src_offset + pre_count) >= 8); + uint64_t unaligned_dst_src = count > 0 && src_offset != dst_offset; + + if (neq && post_count == 0 && loop_count > 0) { + // (dst + count) 0x07 == 7 ==> (dst_offset + count) 0x07 == 7 ==> post_count == 0 + // loop = loop - 1 + // pre_writes = pre_writes + 1 + // post = 8 + // double_src_post = unaligned_dst_src ? 1:0; + // extra_src_reads = extra_src_read + 1 + loop_count -= 1; + pre_writes += 1; + post_count = 8; + double_src_post = src_offset != dst_offset; + extra_src_reads += 1; + } + uint64_t requires_dma = count == 0 || pre_count != 0 || post_count != 0; + if (has_src) { + return pre_count + | (post_count << DMA_POST_COUNT_RS) + | (pre_writes << DMA_PRE_WRITES_RS) + | (dst_offset << DMA_DST_OFFSET_RS) + | (src_offset << DMA_SRC_OFFSET_RS) + | (double_src_pre << DMA_DOUBLE_SRC_PRE_RS) + | (double_src_post << DMA_DOUBLE_SRC_POST_RS) + | (extra_src_reads << DMA_EXTRA_SRC_READS_RS) + | (src64_inc_by_pre << DMA_SRC64_INC_BY_PRE_RS) + | (unaligned_dst_src << DMA_UNALIGNED_DST_SRC_RS) + | (pre_count << DMA_LPRE_COUNT_RS) // optimization to read loop_count * 8 + pre_count + | (loop_count << DMA_LOOP_COUNT_RS) + | (requires_dma << DMA_REQUIRES_DMA_RS); + } + return + pre_count + | (post_count << DMA_POST_COUNT_RS) + | (pre_writes << DMA_PRE_WRITES_RS) + | (dst_offset << DMA_DST_OFFSET_RS) + | (pre_count << DMA_LPRE_COUNT_RS) // optimization to read loop_count * 8 + pre_count + | (loop_count << DMA_LOOP_COUNT_RS) + | (requires_dma << DMA_REQUIRES_DMA_RS); +} + +void basic_print_encode_mismatch(uint64_t expected, uint64_t found) { + static const char *_hexdigits = "0123456789ABCDF"; + char _expected[256]; + char _found[256]; + size_t _iexpected = 0; + size_t _ifound = 0; + for (size_t i_digit=0; i_digit<16; ++i_digit) { + uint8_t byte_expected = (expected >> (60 - 4 * i_digit)) & 0x0F; + uint8_t byte_found = (found >> (60 - 4 * i_digit)) & 0x0F; + if (i_digit && (i_digit % 4) == 0) { + _expected[_iexpected] = '_'; + _found[_ifound] = '_'; + ++_ifound; + ++_iexpected; + } + if (byte_expected != byte_found) { + strcpy(_expected + _iexpected, "\x1B[1;31m"); + strcpy(_found + _ifound, "\x1B[1;31m"); + _ifound += 7; + _iexpected += 7; + } + _expected[_iexpected] = _hexdigits[byte_expected]; + _found[_ifound] = _hexdigits[byte_found]; + ++_ifound; + ++_iexpected; + if (byte_expected != byte_found) { + strcpy(_expected + _iexpected, "\x1B[0m"); + strcpy(_found + _ifound, "\x1B[0m"); + _ifound += 4; + _iexpected += 4; + } + } + _expected[_iexpected] = '\0'; + _found[_ifound] = '\0'; + printf("expected:%s\n found:%s\n", _expected, _found); +} + + +void print_encode_mismatch(uint64_t expected, uint64_t found) { + static const char *_hexdigits = "0123456789ABCDF"; + std::stringstream s_expected; + std::stringstream s_found; + for (size_t i_digit=0; i_digit<16; ++i_digit) { + uint8_t byte_expected = (expected >> (60 - 4 * i_digit)) & 0x0F; + uint8_t byte_found = (found >> (60 - 4 * i_digit)) & 0x0F; + if (i_digit && (i_digit % 4) == 0) { + s_expected << '_'; + s_found << '_'; + } + if (byte_expected != byte_found) { + s_expected << "\x1B[1;31m"; + s_found << "\x1B[1;31m"; + } + s_expected << _hexdigits[byte_expected]; + s_found << _hexdigits[byte_found]; + if (byte_expected != byte_found) { + s_expected << "\x1B[0m"; + s_found << "\x1B[0m"; + } + } + size_t i_group = 0; + while (encode_info[i_group].title[0]) { + uint64_t g_expected = (expected & encode_info[i_group].mask); + uint64_t g_found = (found & encode_info[i_group].mask); + if (g_expected != g_found) { + s_expected << " " << encode_info[i_group].title << ":" << (g_expected >> encode_info[i_group].rs_bits); + s_found << " " << encode_info[i_group].title << ":" << (g_found >> encode_info[i_group].rs_bits); + } + ++i_group; + } + printf("expected:%s\n found:%s\n", s_expected.str().c_str(), s_found.str().c_str()); +} diff --git a/emulator-asm/src/dma/test/test_dma_encode.hpp b/emulator-asm/src/dma/test/test_dma_encode.hpp new file mode 100644 index 000000000..38d364822 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_encode.hpp @@ -0,0 +1,69 @@ +#ifndef __DMA_ENCODE__HPP_ +#define __DMA_ENCODE__HPP_ + +#include +#include +#include + +#define DMA_PRE_COUNT_TEST_MASK 0x07 +#define DMA_PRE_COUNT_MASK 0x07 +#define DMA_POST_COUNT_RS 3 +#define DMA_POST_COUNT_TEST_MASK 0x78 +#define DMA_POST_COUNT_MASK 0x0F +#define DMA_PRE_WRITES_RS 7 +#define DMA_PRE_WRITES_TEST_MASK 0x180 +#define DMA_PRE_WRITES_MASK 0x003 +#define DMA_DST_OFFSET_RS 9 +#define DMA_DST_OFFSET_TEST_MASK 0x0E00 +#define DMA_DST_OFFSET_MASK 0x007 +#define DMA_SRC_OFFSET_RS 12 +#define DMA_SRC_OFFSET_TEST_MASK 0x70000 +#define DMA_SRC_OFFSET_MASK 0x007 +#define DMA_DOUBLE_SRC_PRE_RS 15 +#define DMA_DOUBLE_SRC_PRE_TEST_MASK 0x08000 +#define DMA_DOUBLE_SRC_POST_RS 16 +#define DMA_DOUBLE_SRC_POST_TEST_MASK 0x10000 +#define DMA_EXTRA_SRC_READS_RS 17 +#define DMA_EXTRA_SRC_READS_TEST_MASK 0x60000 +#define DMA_EXTRA_SRC_READS_MASK 0x00003 +#define DMA_SRC64_INC_BY_PRE_RS 19 +#define DMA_SRC64_INC_BY_PRE_TEST_MASK 0x80000 +#define DMA_UNALIGNED_DST_SRC_RS 20 +#define DMA_UNALIGNED_DST_SRC_TEST_MASK 0x100000 +#define DMA_FILL_BYTE_RS 21 +#define DMA_FILL_BYTE_TEST_MASK 0x1FE00000 +#define DMA_FILL_BYTE_CMD_RES_TEST_MASK 0x3FE00000 +#define DMA_FILL_BYTE_MASK 0x000000FF +#define DMA_FILL_BITS9_MASK 0x000001FF +#define DMA_FILL_BYTE_SIGN_TEST_MASK 0x20000000 +#define DMA_LPRE_COUNT_RS 32 +#define DMA_LPRE_COUNT_TEST_MASK 0x700000000 +#define DMA_LPRE_COUNT_MASK 0x00000007 +#define DMA_REQUIRES_DMA_RS 30 +#define DMA_REQUIRES_DMA_TEST_MASK 0x40000000 +#define DMA_REQUIRES_DMA_MASK 0x00000001 +#define DMA_LOOP_COUNT_TEST_MASK 0xFFFFFFF800000000 + +#define DMA_PRE_OR_POST_TEST_MASK (DMA_PRE_COUNT_TEST_MASK | DMA_POST_COUNT_TEST_MASK) +#define DMA_LOOP_COUNT_RS 35 +#define DMA_FULL_ALIGNED_MASK (DMA_PRE_COUNT_TEST_MASK \ + | DMA_POST_COUNT_TEST_MASK \ + | DMA_PRE_WRITES_TEST_MASK \ + | DMA_DST_OFFSET_TEST_MASK \ + | DMA_SRC_OFFSET_TEST_MASK \ + | DMA_DOUBLE_SRC_PRE_TEST_MASK \ + | DMA_DOUBLE_SRC_POST_TEST_MASK \ + | DMA_EXTRA_SRC_READS_TEST_MASK \ + | DMA_SRC64_INC_BY_PRE_TEST_MASK \ + | DMA_UNALIGNED_DST_SRC_TEST_MASK) + +#define DMA_DIRECT_MASK (DMA_FULL_ALIGNED_MASK | DMA_REQUIRES_DMA_TEST_MASK) + +uint64_t calculate_encode(uint64_t dst, uint64_t src, size_t count, bool neq = false, bool has_src = true); +uint64_t calculate_encode_memset(uint64_t dst, size_t count, uint64_t byte); +uint64_t calculate_encode_memcmp(uint64_t dst, uint64_t src, size_t count, int result = 0); +uint64_t calculate_encode_inputcpy(uint64_t dst, size_t count); +void print_encode_mismatch(uint64_t expected, uint64_t found); + + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_inputcpy_mops.cpp b/emulator-asm/src/dma/test/test_dma_inputcpy_mops.cpp new file mode 100644 index 000000000..e0e697d9b --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_inputcpy_mops.cpp @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_dma_mem_mops.hpp" +#include "test_dma_inputcpy_mops.hpp" +#include "test_dma_tools.hpp" +#include "test_dma_encode.hpp" +#include "test_mock.hpp" + +extern "C" { + uint64_t test_asm_dma_inputcpy_mops(uint8_t *dst, uint8_t *src, size_t count, uint64_t *trace); +} + +class TestDmaInputCpyMops: public TestDmaMemMops { +protected: + uint64_t *prev_dst; + bool execute_single_test(void); + bool check_mop(size_t index, uint64_t expected, const char *tag); +public: + TestDmaInputCpyMops(size_t max_count = 1024); + virtual ~TestDmaInputCpyMops(); + void run(void); +}; + +TestDmaInputCpyMops::TestDmaInputCpyMops(size_t max_count): + TestDmaMemMops(max_count, false) { + prev_dst = (uint64_t *)malloc(data_size); +} + +TestDmaInputCpyMops::~TestDmaInputCpyMops(void) { + free(prev_dst); +} + +void TestDmaInputCpyMops::run(void) { + fill_pattern((uint8_t *)(fcall_ctx + FCALL_RESULT), FCALL_RESULT_LENGTH, 3013102105130209); + printf("DST:0x%08lX\n", (uint64_t)dst); + size_t total_tests = 0; + src_offset = 0; + for (dst_offset = 0; dst_offset < 7; ++dst_offset) { + for (count = 0; count < 1024; ++count) { + if (!execute_single_test()) { + printf("\nTest is [\x1B[1;31mFAIL\x1B[0m]\n"); + dump(); + return; + } + ++total_tests; + } + } + printf("\nAll %ld tests are [\x1B[1;32mOK\x1B[0m]\n", total_tests); +} + +bool TestDmaInputCpyMops::check_mop(size_t index, uint64_t expected, const char *tag) { + if (mtrace[index] != expected) { + printf("\nERROR: %s expected: 0x%016lX (%s) found: mtrace[%ld]:%016lX (%s)\n", tag, expected, + decode(expected).c_str(), index, mtrace[index], decode(mtrace[index]).c_str()); + return false; + } + return true; +} +bool TestDmaInputCpyMops::execute_single_test(void) { + fill_pattern((uint8_t *)(fcall_ctx + FCALL_RESULT), FCALL_RESULT_LENGTH, 15436 + dst_offset + count); + fcall_ctx[FCALL_RESULT_GOT] = 1; + fill_pattern(dst, data_size, 1821904675 + dst_offset + count); + uint8_t *p_dst = dst + dst_offset; + + memcpy(prev_dst, dst, data_size); + printf("\rTEST dst_offset:%ld count:%4ld", dst_offset, count); + fflush(stdout); + uint64_t res = test_asm_dma_inputcpy_mops(p_dst, 0, count, test_trace); + size_t trace_count = test_trace[0]; + uint64_t _dst = (uint64_t)dst + dst_offset; + if (res != _dst) { + printf("\nERROR: invalid result expected:0x%08lX found:0x%08lX\n", _dst, res); + return false; + } + // uint64_t encode = calculate_encode((uint64_t)p_dst, (uint64_t)p_src, count); + size_t index = 0; + size_t pre_count = (dst_offset > 0 && count > 0) ? 8 - dst_offset : 0; + if (pre_count > count) { + pre_count = count; + } + if (pre_count > 0) { + if (!check_mop(index, encode_aligned_read((uint64_t)dst), "PRE pre write")) { + return false; + } + index += 1; + } + size_t loop_count = (count - pre_count) >> 3; + size_t post_count = (count - pre_count) & 0x07; + if (post_count > 0) { + uint64_t dst_post = ((uint64_t)dst + dst_offset + pre_count + loop_count * 8) & ~0x07; + if (!check_mop(index, encode_aligned_read((uint64_t)dst_post), "POST pre write")) { + return false; + } + index += 1; + } + if (count > 0) { + size_t dst_qwords = (dst_offset + count + 7) >> 3; + if (!check_mop(index, encode_aligned_block_write((uint64_t)dst, dst_qwords), "dst write")) { + return false; + } + ++index; + } + if (trace_count != index) { + printf("ERROR: invalid mtrace len expected:%ld vs found:%ld\n", index, trace_count); + return false; + } + memcpy((uint8_t *)prev_dst + dst_offset, fcall_ctx + FCALL_RESULT, count); + if (memcmp(prev_dst, dst, data_size) != 0) { + printf("\nERROR: inputcpy operation\n"); + int errors = 0; + uint8_t *_dst = (uint8_t *)prev_dst; + for (size_t i = 0; i < data_size; ++i) { + if (_dst[i] == dst[i]) continue; + printf("[%ld] 0x%02X 0x%02X NO MATCH\n", i, _dst[i], dst[i]); + ++errors; + if (errors > 16) { + printf(".... and more\n"); + break; + } + } + printf("\nERROR: memcpy operation\n"); + return false; + } + return true; +} + +void test_dma_inputcpy_mops() { + printf("\x1B[1;34mTEST DMA INPUTCPY MOPS =================================================\x1B[0m\n"); + TestDmaInputCpyMops test(1024); + test.run(); +} diff --git a/emulator-asm/src/dma/test/test_dma_inputcpy_mops.hpp b/emulator-asm/src/dma/test/test_dma_inputcpy_mops.hpp new file mode 100644 index 000000000..841780f08 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_inputcpy_mops.hpp @@ -0,0 +1,6 @@ +#ifndef __TEST_DMA_INPUTCPY_MOPS__HPP__ +#define __TEST_DMA_INPUTCPY_MOPS__HPP__ + +void test_dma_inputcpy_mops(); + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_inputcpy_mtrace.cpp b/emulator-asm/src/dma/test/test_dma_inputcpy_mtrace.cpp new file mode 100644 index 000000000..19a393511 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_inputcpy_mtrace.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_dma_mem_mtrace.hpp" +#include "test_dma_inputcpy_mtrace.hpp" +#include "test_dma_tools.hpp" +#include "test_dma_encode.hpp" +#include "test_mock.hpp" + +extern "C" { + uint64_t test_asm_dma_inputcpy_mtrace(uint8_t *dst, uint8_t *src, size_t count, uint64_t *trace); +} + +class TestDmaInputCpyMtrace: public TestDmaMemMtrace { +protected: + uint64_t *prev_dst; + uint64_t *check_dst; + bool execute_single_test(void); +public: + TestDmaInputCpyMtrace(size_t max_count = 1024); + virtual ~TestDmaInputCpyMtrace(); + void run(void); +}; + +TestDmaInputCpyMtrace::TestDmaInputCpyMtrace(size_t max_count): + TestDmaMemMtrace(max_count, false) { + prev_dst = (uint64_t *)malloc(data_size); + check_dst = (uint64_t *)malloc(data_size); +} + +TestDmaInputCpyMtrace::~TestDmaInputCpyMtrace(void) { + free(prev_dst); + free(check_dst); +} + +void TestDmaInputCpyMtrace::run(void) { + fill_pattern((uint8_t *)(fcall_ctx + FCALL_RESULT), FCALL_RESULT_LENGTH, 3013102105130209); + size_t total_tests = 0; + for (dst_offset = 0; dst_offset < 7; ++dst_offset) { + for (count = 0; count < 1024; ++count) { + if (!execute_single_test()) { + printf("\nTest is [\x1B[1;31mFAIL\x1B[0m]\n"); + dump(); + return; + } + ++total_tests; + } + } + printf("\nAll %ld tests are [\x1B[1;32mOK\x1B[0m]\n", total_tests); +} + +bool TestDmaInputCpyMtrace::execute_single_test(void) { + fill_pattern((uint8_t *)(fcall_ctx + FCALL_RESULT), FCALL_RESULT_LENGTH, 15436 + dst_offset + count); + fcall_ctx[FCALL_RESULT_GOT] = 1; + fill_pattern(dst, data_size, 1821904675); + uint8_t *p_dst = dst + dst_offset; + + memcpy(prev_dst, dst, data_size); + printf("\rTEST dst_offset:%ld count:%4ld", dst_offset, count); + fflush(stdout); + uint64_t res = test_asm_dma_inputcpy_mtrace(p_dst, 0, count, test_trace); + size_t trace_count = test_trace[0]; + if (trace_count < 1) { + printf("\nERROR: invalid trace_count %ld\n", trace_count); + return false; + } + uint64_t _dst = (uint64_t)dst + dst_offset; + if (res != _dst) { + printf("\nERROR: invalid result expected:0x%08lX found:0x%08lX\n", _dst, res); + return false; + } + uint64_t encode = calculate_encode_inputcpy((uint64_t)p_dst, count); + if (mtrace[0] != encode) { + printf("\nERROR: invalid encoded\n"); + print_encode_mismatch(encode, mtrace[0]); + return false; + } + size_t index = 1; + size_t pre_count = (dst_offset > 0 && count > 0) ? 8 - dst_offset : 0; + if (pre_count > count) { + pre_count = count; + } + if (pre_count > 0) { + if (mtrace[index] != prev_dst[0]) { + printf("\nERROR: pre write pre-value expected: dst[0]:0x%016lX found: mtrace[%ld]:%016lX\n", prev_dst[0], index, mtrace[index]); + return false; + } + ++index; + } + + size_t post_count = (count - pre_count) & 0x07; + if (post_count > 0) { + size_t last_dst_index = (dst_offset + count - 1) >> 3; + if (mtrace[index] != prev_dst[last_dst_index]) { + printf("\nERROR: post write pre-value expected: dst[%ld]:0x%016lX vs found mtrace[%ld]:0x%016lX\n", last_dst_index, prev_dst[last_dst_index], index, mtrace[index]); + return false; + } + ++index; + } + size_t input_qwords = count > 0 ? (count + 7) >> 3 : 0; + for (size_t i = 0; i < input_qwords; ++i) { + uint64_t expected = fcall_ctx[FCALL_RESULT + i]; + if (mtrace[index] != expected) { + printf("\nERROR: input value expected: input[%ld]:0x%016lX vs found mtrace[%ld]:0x%016lX\n", i, expected, index, mtrace[index]); + return false; + } + ++index; + } + if (trace_count != index) { + printf("\nERROR: invalid mtrace len expected:%ld vs found:%ld\n", index, trace_count); + size_t _count = index > trace_count ? index + 1: trace_count + 1; + for (size_t i = 0; i <= _count; ++i) { + printf("mtrace[%ld] 0x%016lX\n", i, mtrace[i]); + } + return false; + } + memcpy((uint8_t *)prev_dst + dst_offset, fcall_ctx + FCALL_RESULT, count); + if (memcmp(prev_dst, dst, data_size) != 0) { + printf("\nERROR: inputcpy operation\n"); + + return false; + } + return true; +} + +void test_dma_inputcpy_mtrace() { + printf("\x1B[1;34mTEST DMA INPUTCPY MTRACE =================================================\x1B[0m\n"); + TestDmaInputCpyMtrace test(1024); + test.run(); +} diff --git a/emulator-asm/src/dma/test/test_dma_inputcpy_mtrace.hpp b/emulator-asm/src/dma/test/test_dma_inputcpy_mtrace.hpp new file mode 100644 index 000000000..d7ff614ec --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_inputcpy_mtrace.hpp @@ -0,0 +1,6 @@ +#ifndef __TEST_DMA_INPUTCPY_MTRACE__HPP__ +#define __TEST_DMA_INPUTCPY_MTRACE__HPP__ + +void test_dma_inputcpy_mtrace(); + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_mem.cpp b/emulator-asm/src/dma/test/test_dma_mem.cpp new file mode 100644 index 000000000..77ce7ae8e --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_mem.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_dma_mem.hpp" +#include "test_dma_tools.hpp" +#include "test_dma_encode.hpp" + +TestDmaMem::TestDmaMem(size_t max_count, bool use_src): + max_count(max_count), use_src(use_src) { + data_size = sizeof(uint64_t) * (max_count + 16); + trace_size = sizeof(uint64_t) * max_count * 4; + src = use_src ? (uint8_t *)malloc(data_size) : NULL; + dst = (uint8_t *)malloc(data_size); + aligned_dst = (uint64_t *)dst; + aligned_src = (uint64_t *)src; + test_trace = (uint64_t *)malloc(trace_size); + mtrace = test_trace + 1; +} + +TestDmaMem::~TestDmaMem(void) { + if (src) free(src); + free(dst); + free(test_trace); +} \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_mem.hpp b/emulator-asm/src/dma/test/test_dma_mem.hpp new file mode 100644 index 000000000..81cf02246 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_mem.hpp @@ -0,0 +1,32 @@ +#ifndef __TEST_DMA_MEM__HPP__ +#define __TEST_DMA_MEM__HPP__ + +#include +#include +#include + +#define EXTRA_PARAMETER_ADDR 0xA0000F00 + +class TestDmaMem { +protected: + uint8_t *dst; + uint8_t *src; + uint64_t *aligned_dst; + uint64_t *aligned_src; + uint64_t *test_trace; + uint64_t *mtrace; + size_t max_count; + size_t data_size; + size_t trace_size; + uint64_t src_offset; + uint64_t dst_offset; + bool use_src; + int diff_dst_src; + uint64_t count; +public: + TestDmaMem(size_t max_count = 1024, bool use_src = true); + virtual ~TestDmaMem(); + virtual void run(void) = 0; +}; + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_mem_mops.cpp b/emulator-asm/src/dma/test/test_dma_mem_mops.cpp new file mode 100644 index 000000000..f9a777398 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_mem_mops.cpp @@ -0,0 +1,151 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_dma_mem_mops.hpp" +#include "test_dma_tools.hpp" +#include "test_dma_encode.hpp" +#include "mem_config.hpp" + +TestDmaMemMops::TestDmaMemMops(size_t max_count, bool use_src): + TestDmaMem(max_count, use_src) { +} + +TestDmaMemMops::~TestDmaMemMops(void) { +} + +std::string TestDmaMemMops::decode(uint64_t value) { + uint32_t flags = value >> 32; + uint8_t bytes = flags & 0x0F; + uint32_t addr = value & 0xFFFF'FFFF; + uint32_t count = flags >> MOPS_BLOCK_COUNT_SBITS; + std::ostringstream oss; + oss << std::setfill('0') << std::setw(8) << std::hex << std::uppercase; + switch (bytes) { + // byte + case 1: + case 2: + case 4: + case 8: { + if (flags & MOPS_WRITE_FLAG) { + oss << "READ(0x"; + } else { + oss << "WRITE(0x"; + } + oss << addr << "," << std::setw(0) << std::dec << bytes << ")"; + return oss.str(); + } + case MOPS_ALIGNED_READ: { + oss << "ALIGNED_READ(0x" << addr << ")"; + return oss.str(); + } + case MOPS_ALIGNED_WRITE: { + oss << "ALIGNED_WRITE(0x" << addr << ")"; + return oss.str(); + } + case MOPS_BLOCK_READ: { + oss << "BLOCK_READ(0x" << addr << "," << std::setw(0) << std::dec << count << ")"; + return oss.str(); + } + case MOPS_BLOCK_WRITE: { + oss << "BLOCK_WRITE(0x" << addr << "," << std::setw(0) << std::dec << count << ")"; + return oss.str(); + } + case MOPS_ALIGNED_BLOCK_READ: { + oss << "ALIGNED_BLOCK_READ(0x" << addr << "," << std::setw(0) << std::dec << count << ")"; + return oss.str(); + } + case MOPS_ALIGNED_BLOCK_WRITE: { + oss << "ALIGNED_BLOCK_WRITE(0x" << addr << "," << std::setw(0) << std::dec << count << ")"; + return oss.str(); + } + default: { + oss << "?¿ " << std::setw(2) << bytes; + return oss.str(); + } + } +} + +void TestDmaMemMops::dump(void) { + printf("---------------------------------\n"); + size_t trace_count = test_trace[0]; + for (size_t index = 0; index < trace_count; ++index) { + uint64_t trace = test_trace[index+1]; + uint32_t addr = trace & 0xFFFF'FFFF; + uint32_t flags = trace >> 32; + printf("mops[%ld] 0x%08X_%08X %s", index, flags, addr, decode(test_trace[index+1]).c_str()); + if (src) { + if (addr >= (uint64_t)src && addr < (uint64_t)(src + max_count)) { + printf(" SRC+%ld", (uint64_t) addr - (uint64_t) src); + } + } + if (addr >= (uint64_t)dst && addr < (uint64_t)(dst + max_count)) { + printf(" DST+%ld", (uint64_t) addr - (uint64_t) dst); + } + printf("\n"); + } +} + +uint64_t TestDmaMemMops::encode_read(uint32_t addr, uint8_t bytes) { + switch (bytes) { + case 1: + return (1ull << 32) | (uint64_t)addr; + case 2: + return (2ull << 32) | (uint64_t)addr; + case 4: + return (4ull << 32) | (uint64_t)addr; + case 8: + return (8ull << 32) | (uint64_t)addr; + default: + throw std::runtime_error("encode_read: invalid bytes: " + std::to_string((int)bytes)); + } +} +uint64_t TestDmaMemMops::encode_write(uint32_t addr, uint8_t bytes) { + switch (bytes) { + case 1: + return ((1ull + MOPS_WRITE_FLAG) << 32) | (uint64_t)addr; + case 2: + return ((2ull + MOPS_WRITE_FLAG) << 32) | (uint64_t)addr; + case 4: + return ((4ull + MOPS_WRITE_FLAG) << 32) | (uint64_t)addr; + case 8: + return ((8ull + MOPS_WRITE_FLAG) << 32) | (uint64_t)addr; + default: + throw std::runtime_error("encode_write: invalid bytes: " + std::to_string((int)bytes)); + } +} +uint64_t TestDmaMemMops::encode_aligned_read(uint32_t addr) { + return ((uint64_t) MOPS_ALIGNED_READ << 32) | (uint64_t) addr; +} +uint64_t TestDmaMemMops::encode_aligned_x_read(uint32_t addr, uint32_t count) { + if (count == 1) { + return ((uint64_t) MOPS_ALIGNED_READ << 32) | (uint64_t) addr; + } + return encode_aligned_block_read(addr, count); +} +uint64_t TestDmaMemMops::encode_aligned_write(uint32_t addr) { + return ((uint64_t) MOPS_ALIGNED_WRITE << 32) | (uint64_t) addr; +} +uint64_t TestDmaMemMops::encode_block_read(uint32_t addr, uint32_t count) { + return ((uint64_t) MOPS_BLOCK_READ << 32) | ((uint64_t) count << (MOPS_BLOCK_COUNT_SBITS + 32)) | addr; +} +uint64_t TestDmaMemMops::encode_block_write(uint32_t addr, uint32_t count) { + return ((uint64_t) MOPS_BLOCK_WRITE << 32) | ((uint64_t) count << (MOPS_BLOCK_COUNT_SBITS + 32)) | addr; +} +uint64_t TestDmaMemMops::encode_aligned_block_read(uint32_t addr, uint32_t count) { + return ((uint64_t) MOPS_ALIGNED_BLOCK_READ << 32) | ((uint64_t) count << (MOPS_BLOCK_COUNT_SBITS + 32)) | addr; +} +uint64_t TestDmaMemMops::encode_aligned_block_write(uint32_t addr, uint32_t count) { + return ((uint64_t) MOPS_ALIGNED_BLOCK_WRITE << 32) | ((uint64_t) count << (MOPS_BLOCK_COUNT_SBITS + 32)) | addr; + +} + diff --git a/emulator-asm/src/dma/test/test_dma_mem_mops.hpp b/emulator-asm/src/dma/test/test_dma_mem_mops.hpp new file mode 100644 index 000000000..aa70f132c --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_mem_mops.hpp @@ -0,0 +1,28 @@ +#ifndef __TEST_DMA_MEM_MTRACE_MOPS__HPP__ +#define __TEST_DMA_MEM_MTRACE_MOPS__HPP__ + +#include +#include +#include +#include "test_dma_mem.hpp" + +class TestDmaMemMops: public TestDmaMem { +protected: + void dump(void); +public: + TestDmaMemMops(size_t max_count = 1024, bool use_src = true); + virtual ~TestDmaMemMops(); + virtual void run(void) = 0; + std::string decode(uint64_t value); + uint64_t encode_read(uint32_t addr, uint8_t bytes); + uint64_t encode_write(uint32_t addr, uint8_t bytes); + uint64_t encode_aligned_read(uint32_t addr); + uint64_t encode_aligned_write(uint32_t addr); + uint64_t encode_block_read(uint32_t addr, uint32_t count); + uint64_t encode_block_write(uint32_t addr, uint32_t count); + uint64_t encode_aligned_block_read(uint32_t addr, uint32_t count); + uint64_t encode_aligned_block_write(uint32_t addr, uint32_t count); + uint64_t encode_aligned_x_read(uint32_t addr, uint32_t count); +}; + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_mem_mtrace.cpp b/emulator-asm/src/dma/test/test_dma_mem_mtrace.cpp new file mode 100644 index 000000000..b3503be45 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_mem_mtrace.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_dma_mem_mtrace.hpp" +#include "test_dma_tools.hpp" +#include "test_dma_encode.hpp" + +TestDmaMemMtrace::TestDmaMemMtrace(size_t max_count, bool use_src): + TestDmaMem(max_count, use_src) { +} + +TestDmaMemMtrace::~TestDmaMemMtrace(void) { +} + +void TestDmaMemMtrace::dump(void) { + printf("---------------------------------\n"); + size_t dst_qwords = (dst_offset + count + 7) >> 3; + for (size_t index = 0; index < dst_qwords; ++index) { + printf("dst64[%ld] 0x%016lX\n", index, aligned_dst[index]); + } + if (src) { + printf("---------------------------------\n"); + size_t src_qwords = (src_offset + count + 7) >> 3; + for (size_t index = 0; index < src_qwords; ++index) { + printf("src64[%ld] 0x%016lX\n", index, aligned_src[index]); + } + } + printf("---------------------------------\n"); + size_t trace_count = test_trace[0]; + for (size_t index = 0; index < trace_count; ++index) { + printf("mtrace[%ld] 0x%016lX\n", index, test_trace[index+1]); + } +} diff --git a/emulator-asm/src/dma/test/test_dma_mem_mtrace.hpp b/emulator-asm/src/dma/test/test_dma_mem_mtrace.hpp new file mode 100644 index 000000000..878acbeb3 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_mem_mtrace.hpp @@ -0,0 +1,20 @@ +#ifndef __TEST_DMA_MEM_MTRACE__HPP__ +#define __TEST_DMA_MEM_MTRACE__HPP__ + +#include +#include +#include +#include "test_dma_mem.hpp" + +class TestDmaMemMtrace: public TestDmaMem { +protected: + int diff_dst_src; + uint64_t count; + void dump(void); +public: + TestDmaMemMtrace(size_t max_count = 1024, bool use_src = true); + virtual ~TestDmaMemMtrace(); + virtual void run(void) = 0; +}; + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_memcmp_mops.cpp b/emulator-asm/src/dma/test/test_dma_memcmp_mops.cpp new file mode 100644 index 000000000..15333ff33 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memcmp_mops.cpp @@ -0,0 +1,151 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_dma_mem_mops.hpp" +#include "test_dma_memcmp_mops.hpp" +#include "test_dma_tools.hpp" +#include "test_dma_encode.hpp" + +extern "C" { + size_t test_asm_dma_memcmp_mops(uint8_t *dst, uint8_t *src, size_t count, uint64_t *trace); +} + +class TestDmaMemCmpMops: public TestDmaMemMops { +protected: + int diff_dst_src; + uint64_t bus_count; + bool execute_single_test(void); + bool check_mop(size_t index, uint64_t expected, const char *tag); +public: + TestDmaMemCmpMops(size_t max_count = 1024); + void run(void); +}; + +TestDmaMemCmpMops::TestDmaMemCmpMops(size_t max_count): + TestDmaMemMops(max_count) { +} + + +void TestDmaMemCmpMops::run(void) { + fill_pattern(src,data_size, 3013102105130209); + size_t total_tests = 0; + for (uint64_t icase = 0; icase < 3; ++icase) { + diff_dst_src = icase == 0 ? 0 : (60 * icase - 180 * (icase - 1)); + for (dst_offset = 0; dst_offset < 7; ++dst_offset) { + for (src_offset = 0; src_offset < 7; ++src_offset) { + for (count = 0; count < 1024; ++count) { + for (uint64_t i_count_case = 0; i_count_case < 3; ++i_count_case) { + if (i_count_case > 0) { + bus_count = count + 1 + (i_count_case - 1) * (dst_offset + src_offset); + } + if (!execute_single_test()) { + printf("\nTest is [\x1B[1;31mFAIL\x1B[0m]\n"); + dump(); + return; + } + ++total_tests; + } + } + } + } + } + printf("\nAll %ld tests are [\x1B[1;32mOK\x1B[0m]\n", total_tests); +} + +bool TestDmaMemCmpMops::check_mop(size_t index, uint64_t expected, const char *tag) { + if (mtrace[index] != expected) { + printf("\nERROR: %s expected: 0x%016lX (%s) found: mtrace[%ld]:%016lX (%s)\n", tag, expected, + decode(expected).c_str(), index, mtrace[index], decode(mtrace[index]).c_str()); + return false; + } + return true; +} +bool TestDmaMemCmpMops::execute_single_test(void) { + memset(test_trace, 0, trace_size); + fill_pattern(dst, data_size, 1821904675); + uint8_t *p_dst = dst + dst_offset; + uint8_t *p_src = src + src_offset; + + int cmp_res = create_memcmp_data(p_dst, p_src, count, diff_dst_src); + printf("\rTEST dst_offset:%ld src_offset:%ld count:%4ld (bus_count:%4ld) cmp_res:%4d (diff:%4d)", + dst_offset, src_offset, count, bus_count, cmp_res, diff_dst_src); + fflush(stdout); + int res = test_asm_dma_memcmp_mops(p_dst, p_src, count, test_trace); + size_t trace_count = test_trace[0]; + if (trace_count < 1) { + printf("\nERROR: invalid trace_count %ld\n", trace_count); + return false; + } + if (res != cmp_res) { + uint8_t byte_dst = dst[dst_offset + count - 1]; + uint8_t byte_src = src[src_offset + count - 1]; + printf("\nERROR: invalid result expected:%d found:%d DST:0x%02X SRC:0x%02X\n", + cmp_res, res, byte_dst, byte_src); + return false; + } + if (mtrace[0] != encode_aligned_read(EXTRA_PARAMETER_ADDR)) { + printf("\nERROR: not found valid param read\n"); + return false; + } + // uint64_t encode = calculate_encode((uint64_t)p_dst, (uint64_t)p_src, count); + size_t index = 1; + size_t pre_count = (dst_offset > 0 && count > 0) ? 8 - dst_offset : 0; + if (pre_count > count) { + pre_count = count; + } + if (pre_count > 0) { + size_t src_blocks = 1 + ((src_offset + pre_count) > 8); + if (!check_mop(index, encode_aligned_read((uint64_t)dst), "PRE pre write") || + !check_mop(index + 1, encode_aligned_x_read((uint64_t)src, src_blocks), "PRE src read")) { + return false; + } + index += 2; + } + size_t loop_count = (count - pre_count) >> 3; + size_t post_count = (count - pre_count) & 0x07; + if (loop_count > 0 && post_count == 0 && res != 0) { + loop_count -= 1; + post_count = 8; + } + if (post_count > 0) { + uint64_t src_post = ((uint64_t)src + src_offset + pre_count + loop_count * 8) & ~0x07; + uint64_t dst_post = ((uint64_t)dst + dst_offset + pre_count + loop_count * 8) & ~0x07; + size_t src_blocks = 1 + ((((src_offset + pre_count) & 0x07) + post_count) > 8); + if (!check_mop(index, encode_aligned_read((uint64_t)dst_post), "POST pre write") || + !check_mop(index + 1, encode_aligned_x_read((uint64_t)src_post, src_blocks), "POST src read")) { + return false; + } + index += 2; + } + if (loop_count > 0) { + uint64_t src_loop = ((uint64_t)src + src_offset + pre_count) & ~0x07; + uint64_t dst_loop = pre_count > 0 ? (uint64_t)dst + 8 : (uint64_t)dst; + size_t src_count = dst_offset == src_offset ? loop_count : (loop_count + 1); + + if (!check_mop(index, encode_aligned_block_read(src_loop, src_count), "LOOP src read")) { + return false; + } + if (!check_mop(index+1, encode_aligned_block_read(dst_loop, loop_count), "LOOP dst read (cmp)")) { + return false; + } + index += 2; + } + if (trace_count != index) { + printf("ERROR: invalid mtrace len expected:%ld vs found:%ld\n", index, trace_count); + return false; + } + return true; +} + + +void test_dma_memcmp_mops() { + printf("\x1B[1;34mTEST DMA MEMCMP MOPS =================================================\x1B[0m\n"); + TestDmaMemCmpMops test(1024); + test.run(); +} diff --git a/emulator-asm/src/dma/test/test_dma_memcmp_mops.hpp b/emulator-asm/src/dma/test/test_dma_memcmp_mops.hpp new file mode 100644 index 000000000..b5d543f82 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memcmp_mops.hpp @@ -0,0 +1,6 @@ +#ifndef __TEST_DMA_MEMCMP_MOPS__HPP__ +#define __TEST_DMA_MEMCMP_MOPS__HPP__ + +void test_dma_memcmp_mops(); + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_memcmp_mtrace.cpp b/emulator-asm/src/dma/test/test_dma_memcmp_mtrace.cpp new file mode 100644 index 000000000..68aa8c78e --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memcmp_mtrace.cpp @@ -0,0 +1,144 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_dma_mem_mtrace.hpp" +#include "test_dma_memcmp_mtrace.hpp" +#include "test_dma_tools.hpp" +#include "test_dma_encode.hpp" + +extern "C" { + size_t test_asm_dma_memcmp_mtrace(uint8_t *dst, uint8_t *src, size_t count, uint64_t *trace); +} + +class TestDmaMemCmpMtrace: public TestDmaMemMtrace { +protected: + int diff_dst_src; + uint64_t bus_count; + bool execute_single_test(void); +public: + TestDmaMemCmpMtrace(size_t max_count = 1024); + void run(void); +}; + +TestDmaMemCmpMtrace::TestDmaMemCmpMtrace(size_t max_count): + TestDmaMemMtrace(max_count) { +} + + +void TestDmaMemCmpMtrace::run(void) { + fill_pattern(src,data_size, 3013102105130209); + size_t total_tests = 0; + for (uint64_t icase = 0; icase < 3; ++icase) { + diff_dst_src = icase == 0 ? 0 : (60 * icase - 180 * (icase - 1)); + for (dst_offset = 0; dst_offset < 7; ++dst_offset) { + for (src_offset = 0; src_offset < 7; ++src_offset) { + for (count = 0; count < 1024; ++count) { + for (uint64_t i_count_case = 0; i_count_case < 3; ++i_count_case) { + if (i_count_case > 0) { + bus_count = count + 1 + (i_count_case - 1) * (dst_offset + src_offset); + } + if (!execute_single_test()) { + printf("\nTest is [\x1B[1;31mFAIL\x1B[0m]\n"); + dump(); + return; + } + ++total_tests; + } + } + } + } + } + printf("\nAll %ld tests are [\x1B[1;32mOK\x1B[0m]\n", total_tests); +} + +bool TestDmaMemCmpMtrace::execute_single_test(void) { + memset(test_trace, 0, trace_size); + fill_pattern(dst, data_size, 1821904675); + bus_count = count; + uint8_t *p_dst = dst + dst_offset; + uint8_t *p_src = src + src_offset; + + int cmp_res = create_memcmp_data(p_dst, p_src, count, diff_dst_src); + printf("\rTEST dst_offset:%ld src_offset:%ld count:%4ld (bus_count:%4ld) cmp_res:%4d (diff:%4d)", + dst_offset, src_offset, count, bus_count, cmp_res, diff_dst_src); + fflush(stdout); + int res = test_asm_dma_memcmp_mtrace(p_dst, p_src, bus_count, test_trace); + size_t trace_count = test_trace[0]; + if (trace_count < 2) { + printf("\nERROR: invalid trace_count %ld\n", trace_count); + return false; + } + if (res != cmp_res) { + uint8_t byte_dst = dst[dst_offset + count - 1]; + uint8_t byte_src = src[src_offset + count - 1]; + printf("\nERROR: invalid result expected:%d found:%d DST:0x%02X SRC:0x%02X\n", + cmp_res, res, byte_dst, byte_src); + return false; + } + uint64_t encode = calculate_encode_memcmp((uint64_t)p_dst, (uint64_t)p_src, count, res); + if (mtrace[0] != encode) { + printf("\nERROR: invalid encoded\n"); + print_encode_mismatch(encode, mtrace[0]); + return false; + } + if (mtrace[1] != bus_count) { + printf("ERROR: invalid bus_count expected:%ld found:%ld\n", bus_count, mtrace[1]); + return false; + } + size_t index = 2; + size_t pre_count = (dst_offset > 0 && count > 0) ? 8 - dst_offset : 0; + if (pre_count > count) { + pre_count = count; + } + if (pre_count > 0) { + if (mtrace[index] != aligned_dst[0]) { + printf("ERROR: pre write pre-value expected: dst[0]:0x%016lX found: mtrace[%ld]:%016lX\n", aligned_dst[0], index, mtrace[index]); + return false; + } + ++index; + } + + size_t loop_count = (count - pre_count) >> 3; + size_t post_count = (count - pre_count) & 0x07; + if (loop_count > 0 && post_count == 0 && res != 0) { + loop_count -= 1; + post_count = 8; + } + if (post_count > 0) { + size_t last_dst_index = (dst_offset + count - 1) >> 3; + if (mtrace[index] != aligned_dst[last_dst_index]) { + printf("ERROR: post write pre-value expected: dst[%ld]:0x%016lX vs found mtrace[%ld]:0x%016lX\n", last_dst_index, aligned_dst[last_dst_index], index, mtrace[index]); + return false; + } + ++index; + } + size_t src_qwords = count > 0 ? (src_offset + count + 7) >> 3 : 0; + for (size_t i_src = 0; i_src < src_qwords; ++i_src) { + if (mtrace[index] != aligned_src[i_src]) { + printf("ERROR: src value expected: src[%ld]:0x%016lX vs found mtrace[%ld]:0x%016lX\n", i_src, aligned_src[i_src], index, mtrace[index]); + return false; + } + ++index; + } + if (trace_count != index) { + printf("ERROR: invalid mtrace len expected:%ld vs found:%ld\n", index, trace_count); + size_t _count = index > trace_count ? index + 1: trace_count + 1; + for (size_t i = 0; i <= _count; ++i) { + printf("mtrace[%ld] 0x%016lX\n", i, mtrace[i]); + } + return false; + } + + return true; +} + +void test_dma_memcmp_mtrace() { + printf("\x1B[1;34mTEST DMA MEMCMP MTRACE =================================================\x1B[0m\n"); + TestDmaMemCmpMtrace test(1024); + test.run(); +} \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_memcmp_mtrace.hpp b/emulator-asm/src/dma/test/test_dma_memcmp_mtrace.hpp new file mode 100644 index 000000000..9909c2ca1 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memcmp_mtrace.hpp @@ -0,0 +1,6 @@ +#ifndef __TEST_DMA_MEMCMP_MTRACE__HPP__ +#define __TEST_DMA_MEMCMP_MTRACE__HPP__ + +void test_dma_memcmp_mtrace(); + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_memcpy_mops.cpp b/emulator-asm/src/dma/test/test_dma_memcpy_mops.cpp new file mode 100644 index 000000000..a7b12899c --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memcpy_mops.cpp @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_dma_mem_mops.hpp" +#include "test_dma_memcpy_mops.hpp" +#include "test_dma_tools.hpp" +#include "test_dma_encode.hpp" + +extern "C" { + uint64_t test_asm_dma_memcpy_mops(uint8_t *dst, uint8_t *src, size_t count, uint64_t *trace); +} + +class TestDmaMemCpyMops: public TestDmaMemMops { +protected: + uint64_t *prev_dst; + bool execute_single_test(void); + bool check_mop(size_t index, uint64_t expected, const char *tag); +public: + TestDmaMemCpyMops(size_t max_count = 1024); + virtual ~TestDmaMemCpyMops(); + void run(void); +}; + +TestDmaMemCpyMops::TestDmaMemCpyMops(size_t max_count): + TestDmaMemMops(max_count) { + prev_dst = (uint64_t *)malloc(data_size); +} + +TestDmaMemCpyMops::~TestDmaMemCpyMops(void) { + free(prev_dst); +} + +void TestDmaMemCpyMops::run(void) { + fill_pattern(src,data_size, 3013102105130209); + printf("SRC:0x%08lX DST:0x%08lX\n", (uint64_t)src, (uint64_t)dst); + size_t total_tests = 0; + for (dst_offset = 0; dst_offset < 7; ++dst_offset) { + for (src_offset = 0; src_offset < 7; ++src_offset) { + for (count = 0; count < 1024; ++count) { + if (!execute_single_test()) { + printf("\nTest is [\x1B[1;31mFAIL\x1B[0m]\n"); + dump(); + return; + } + ++total_tests; + } + } + } + printf("\nAll %ld tests are [\x1B[1;32mOK\x1B[0m]\n", total_tests); +} + +bool TestDmaMemCpyMops::check_mop(size_t index, uint64_t expected, const char *tag) { + if (mtrace[index] != expected) { + printf("\nERROR: %s expected: 0x%016lX (%s) found: mtrace[%ld]:%016lX (%s)\n", tag, expected, + decode(expected).c_str(), index, mtrace[index], decode(mtrace[index]).c_str()); + return false; + } + return true; +} +bool TestDmaMemCpyMops::execute_single_test(void) { + memset(test_trace, 0, trace_size); + fill_pattern(dst, data_size, 1821904675); + uint8_t *p_dst = dst + dst_offset; + uint8_t *p_src = src + src_offset; + + memcpy(prev_dst, dst, data_size); + printf("\rTEST dst_offset:%ld src_offset:%ld count:%4ld", + dst_offset, src_offset, count); + fflush(stdout); + uint64_t res = test_asm_dma_memcpy_mops(p_dst, p_src, count, test_trace); + size_t trace_count = test_trace[0]; + if (trace_count < 1) { + printf("\nERROR: invalid trace_count %ld\n", trace_count); + return false; + } + uint64_t _dst = (uint64_t)dst + dst_offset; + if (res != _dst) { + printf("\nERROR: invalid result expected:0x%08lX found:0x%08lX\n", _dst, res); + return false; + } + if (mtrace[0] != encode_aligned_read(EXTRA_PARAMETER_ADDR)) { + printf("\nERROR: not found valid param read\n"); + return false; + } + // uint64_t encode = calculate_encode((uint64_t)p_dst, (uint64_t)p_src, count); + size_t index = 1; + size_t pre_count = (dst_offset > 0 && count > 0) ? 8 - dst_offset : 0; + if (pre_count > count) { + pre_count = count; + } + if (pre_count > 0) { + size_t src_blocks = 1 + ((src_offset + pre_count) > 8); + if (!check_mop(index, encode_aligned_read((uint64_t)dst), "PRE pre write") || + !check_mop(index + 1, encode_aligned_x_read((uint64_t)src, src_blocks), "PRE src read")) { + return false; + } + index += 2; + } + size_t loop_count = (count - pre_count) >> 3; + size_t post_count = (count - pre_count) & 0x07; + if (post_count > 0) { + uint64_t src_post = ((uint64_t)src + src_offset + pre_count + loop_count * 8) & ~0x07; + uint64_t dst_post = ((uint64_t)dst + dst_offset + pre_count + loop_count * 8) & ~0x07; + size_t src_blocks = 1 + ((((src_offset + pre_count) & 0x07) + post_count) > 8); + if (!check_mop(index, encode_aligned_read((uint64_t)dst_post), "POST pre write") || + !check_mop(index + 1, encode_aligned_x_read((uint64_t)src_post, src_blocks), "POST src read")) { + return false; + } + index += 2; + } + if (loop_count > 0) { + uint64_t src_loop = ((uint64_t)src + src_offset + pre_count) & ~0x07; + size_t src_count = dst_offset == src_offset ? loop_count : (loop_count + 1); + if (!check_mop(index, encode_aligned_block_read(src_loop, src_count), "LOOP src read")) { + return false; + } + ++index; + } + if (count > 0) { + size_t dst_qwords = (dst_offset + count + 7) >> 3; + if (!check_mop(index, encode_aligned_block_write((uint64_t)dst, dst_qwords), "dst write")) { + return false; + } + ++index; + } + if (trace_count != index) { + printf("ERROR: invalid mtrace len expected:%ld vs found:%ld\n", index, trace_count); + return false; + } + memcpy((uint8_t *)prev_dst + dst_offset, src + src_offset, count); + if (memcmp(prev_dst, dst, data_size) != 0) { + int errors = 0; + uint8_t *_dst = (uint8_t *)prev_dst; + for (size_t i = 0; i < data_size; ++i) { + if (_dst[i] == src[i]) continue; + printf("[%ld] 0x%02X 0x%02X NO MATCH\n", i, _dst[i], src[i]); + ++errors; + if (errors > 16) { + printf(".... and more\n"); + break; + } + } + printf("\nERROR: memcpy operation\n"); + return false; + } + return true; +} + +void test_dma_memcpy_mops() { + printf("\x1B[1;34mTEST DMA MEMCPY MOPS =================================================\x1B[0m\n"); + TestDmaMemCpyMops test(1024); + test.run(); +} diff --git a/emulator-asm/src/dma/test/test_dma_memcpy_mops.hpp b/emulator-asm/src/dma/test/test_dma_memcpy_mops.hpp new file mode 100644 index 000000000..ef4199215 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memcpy_mops.hpp @@ -0,0 +1,6 @@ +#ifndef __TEST_DMA_MEMCPY_MOPS__HPP__ +#define __TEST_DMA_MEMCPY_MOPS__HPP__ + +void test_dma_memcpy_mops(); + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_memcpy_mtrace.cpp b/emulator-asm/src/dma/test/test_dma_memcpy_mtrace.cpp new file mode 100644 index 000000000..6f915ebae --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memcpy_mtrace.cpp @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_dma_mem_mtrace.hpp" +#include "test_dma_memcpy_mtrace.hpp" +#include "test_dma_tools.hpp" +#include "test_dma_encode.hpp" + +#define BUFFER_SIZE (sizeof(uint64_t) * 2 * 1024) + +extern "C" { + uint64_t test_asm_dma_memcpy_mtrace(uint8_t *dst, uint8_t *src, size_t count, uint64_t *trace); +} + +class TestDmaMemCpyMtrace: public TestDmaMemMtrace { +protected: + uint64_t *prev_dst; + uint64_t *check_dst; + bool execute_single_test(void); +public: + TestDmaMemCpyMtrace(size_t max_count = 1024); + virtual ~TestDmaMemCpyMtrace(); + void run(void); +}; + +TestDmaMemCpyMtrace::TestDmaMemCpyMtrace(size_t max_count): + TestDmaMemMtrace(max_count) { + prev_dst = (uint64_t *)malloc(data_size); + check_dst = (uint64_t *)malloc(data_size); +} + +TestDmaMemCpyMtrace::~TestDmaMemCpyMtrace(void) { + free(prev_dst); + free(check_dst); +} + +void TestDmaMemCpyMtrace::run(void) { + fill_pattern(src,data_size, 3013102105130209); + size_t total_tests = 0; + for (dst_offset = 0; dst_offset < 7; ++dst_offset) { + for (src_offset = 0; src_offset < 7; ++src_offset) { + for (count = 0; count < 1024; ++count) { + if (!execute_single_test()) { + printf("\nTest is [\x1B[1;31mFAIL\x1B[0m]\n"); + dump(); + return; + } + ++total_tests; + } + } + } + printf("\nAll %ld tests are [\x1B[1;32mOK\x1B[0m]\n", total_tests); +} + +bool TestDmaMemCpyMtrace::execute_single_test(void) { + memset(test_trace, 0, trace_size); + fill_pattern(dst, data_size, 1821904675); + uint8_t *p_dst = dst + dst_offset; + uint8_t *p_src = src + src_offset; + + memcpy(prev_dst, dst, data_size); + printf("\rTEST dst_offset:%ld src_offset:%ld count:%4ld", + dst_offset, src_offset, count); + fflush(stdout); + uint64_t res = test_asm_dma_memcpy_mtrace(p_dst, p_src, count, test_trace); + size_t trace_count = test_trace[0]; + if (trace_count < 1) { + printf("\nERROR: invalid trace_count %ld\n", trace_count); + return false; + } + uint64_t _dst = (uint64_t)dst + dst_offset; + if (res != _dst) { + printf("\nERROR: invalid result expected:0x%08lX found:0x%08lX\n", _dst, res); + return false; + } + uint64_t encode = calculate_encode((uint64_t)p_dst, (uint64_t)p_src, count); + if (mtrace[0] != encode) { + printf("\nERROR: invalid encoded\n"); + print_encode_mismatch(encode, mtrace[0]); + return false; + } + size_t index = 1; + size_t pre_count = (dst_offset > 0 && count > 0) ? 8 - dst_offset : 0; + if (pre_count > count) { + pre_count = count; + } + if (pre_count > 0) { + if (mtrace[index] != prev_dst[0]) { + printf("\nERROR: pre write pre-value expected: dst[0]:0x%016lX found: mtrace[%ld]:%016lX\n", prev_dst[0], index, mtrace[index]); + return false; + } + ++index; + } + + size_t post_count = (count - pre_count) & 0x07; + if (post_count > 0) { + size_t last_dst_index = (dst_offset + count - 1) >> 3; + if (mtrace[index] != prev_dst[last_dst_index]) { + printf("\nERROR: post write pre-value expected: dst[%ld]:0x%016lX vs found mtrace[%ld]:0x%016lX\n", last_dst_index, prev_dst[last_dst_index], index, mtrace[index]); + return false; + } + ++index; + } + size_t src_qwords = count > 0 ? (src_offset + count + 7) >> 3 : 0; + for (size_t i_src = 0; i_src < src_qwords; ++i_src) { + if (mtrace[index] != aligned_src[i_src]) { + printf("ERROR: src value expected: src[%ld]:0x%016lX vs found mtrace[%ld]:0x%016lX\n", i_src, aligned_src[i_src], index, mtrace[index]); + return false; + } + ++index; + } + if (trace_count != index) { + printf("ERROR: invalid mtrace len expected:%ld vs found:%ld\n", index, trace_count); + size_t _count = index > trace_count ? index + 1: trace_count + 1; + for (size_t i = 0; i <= _count; ++i) { + printf("mtrace[%ld] 0x%016lX\n", i, mtrace[i]); + } + return false; + } + memcpy((uint8_t *)prev_dst + dst_offset, src + src_offset, count); + if (memcmp(prev_dst, dst, data_size) != 0) { + int errors = 0; + uint8_t *_dst = (uint8_t *)prev_dst; + for (size_t i = 0; i < data_size; ++i) { + if (_dst[i] == src[i]) continue; + printf("[%ld] 0x%02X 0x%02X NO MATCH\n", i, _dst[i], src[i]); + ++errors; + if (errors > 16) { + printf(".... and more\n"); + break; + } + } + printf("\nERROR: memcpy operation\n"); + return false; + } + return true; +} + +void test_dma_memcpy_mtrace() { + printf("\x1B[1;34mTEST DMA MEMCPY MTRACE =================================================\x1B[0m\n"); + TestDmaMemCpyMtrace test(1024); + test.run(); +} diff --git a/emulator-asm/src/dma/test/test_dma_memcpy_mtrace.hpp b/emulator-asm/src/dma/test/test_dma_memcpy_mtrace.hpp new file mode 100644 index 000000000..6942343c4 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memcpy_mtrace.hpp @@ -0,0 +1,6 @@ +#ifndef __TEST_DMA_MEMCPY_MTRACE__HPP__ +#define __TEST_DMA_MEMCPY_MTRACE__HPP__ + +void test_dma_memcpy_mtrace(); + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_memset_mops.cpp b/emulator-asm/src/dma/test/test_dma_memset_mops.cpp new file mode 100644 index 000000000..14ee6618b --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memset_mops.cpp @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_dma_mem_mops.hpp" +#include "test_dma_memset_mops.hpp" +#include "test_dma_tools.hpp" +#include "test_dma_encode.hpp" + +extern "C" { + uint64_t test_asm_dma_memset_mops(uint8_t *dst, uint64_t byte, size_t count, uint64_t *trace); +} + +class TestDmaMemSetMops: public TestDmaMemMops { +protected: + uint64_t *prev_dst; + uint64_t byte; + bool execute_single_test(void); + bool check_mop(size_t index, uint64_t expected, const char *tag); +public: + TestDmaMemSetMops(size_t max_count = 1024); + virtual ~TestDmaMemSetMops(); + void run(void); +}; + +TestDmaMemSetMops::TestDmaMemSetMops(size_t max_count): + TestDmaMemMops(max_count, false) { + prev_dst = (uint64_t *)malloc(data_size); +} + +TestDmaMemSetMops::~TestDmaMemSetMops(void) { + free(prev_dst); +} + +void TestDmaMemSetMops::run(void) { + printf("DST:0x%08lX\n", (uint64_t)dst); + size_t total_tests = 0; + src_offset = 0; + for (byte = 0; byte <= 0xFF; ++byte) { + for (dst_offset = 0; dst_offset < 7; ++dst_offset) { + for (count = 0; count < 1024; ++count) { + if (!execute_single_test()) { + printf("\nTest is [\x1B[1;31mFAIL\x1B[0m]\n"); + dump(); + return; + } + ++total_tests; + } + } + } + printf("\nAll %ld tests are [\x1B[1;32mOK\x1B[0m]\n", total_tests); +} + +bool TestDmaMemSetMops::check_mop(size_t index, uint64_t expected, const char *tag) { + if (mtrace[index] != expected) { + printf("\nERROR: %s expected: 0x%016lX (%s) found: mtrace[%ld]:%016lX (%s)\n", tag, expected, + decode(expected).c_str(), index, mtrace[index], decode(mtrace[index]).c_str()); + return false; + } + return true; +} +bool TestDmaMemSetMops::execute_single_test(void) { + memset(test_trace, 0, trace_size); + fill_pattern(dst, data_size, 1821904675); + uint8_t *p_dst = dst + dst_offset; + + memcpy(prev_dst, dst, data_size); + printf("\rTEST byte:0x%02lX dst_offset:%ld count:%4ld", + byte, dst_offset, count); + fflush(stdout); + uint64_t res = test_asm_dma_memset_mops(p_dst, byte, count, test_trace); + size_t trace_count = test_trace[0]; + uint64_t _dst = (uint64_t)dst + dst_offset; + if (res != _dst) { + printf("\nERROR: invalid result expected:0x%08lX found:0x%08lX\n", _dst, res); + return false; + } + size_t index = 0; + size_t pre_count = (dst_offset > 0 && count > 0) ? 8 - dst_offset : 0; + if (pre_count > count) { + pre_count = count; + } + if (pre_count > 0) { + if (!check_mop(index, encode_aligned_read((uint64_t)dst), "PRE pre write") ) { + return false; + } + index += 1; + } + size_t loop_count = (count - pre_count) >> 3; + size_t post_count = (count - pre_count) & 0x07; + if (post_count > 0) { + uint64_t dst_post = ((uint64_t)dst + dst_offset + pre_count + loop_count * 8) & ~0x07; + if (!check_mop(index, encode_aligned_read((uint64_t)dst_post), "POST pre write")) { + return false; + } + index += 1; + } + if (count > 0) { + size_t dst_qwords = (dst_offset + count + 7) >> 3; + if (!check_mop(index, encode_aligned_block_write((uint64_t)dst, dst_qwords), "dst write")) { + return false; + } + ++index; + } + if (trace_count != index) { + printf("ERROR: invalid mtrace len expected:%ld vs found:%ld\n", index, trace_count); + return false; + } + memset((uint8_t *)prev_dst + dst_offset, byte, count); + if (memcmp(prev_dst, dst, data_size) != 0) { + printf("\nERROR: memset operation\n"); + return false; + } + return true; +} + +void test_dma_memset_mops() { + printf("\x1B[1;34mTEST DMA MEMSET MOPS =================================================\x1B[0m\n"); + TestDmaMemSetMops test(1024); + test.run(); +} diff --git a/emulator-asm/src/dma/test/test_dma_memset_mops.hpp b/emulator-asm/src/dma/test/test_dma_memset_mops.hpp new file mode 100644 index 000000000..e5ec86c8f --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memset_mops.hpp @@ -0,0 +1,6 @@ +#ifndef __TEST_DMA_MEMSET_MOPS__HPP__ +#define __TEST_DMA_MEMSET_MOPS__HPP__ + +void test_dma_memset_mops(); + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_memset_mtrace.cpp b/emulator-asm/src/dma/test/test_dma_memset_mtrace.cpp new file mode 100644 index 000000000..602d63775 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memset_mtrace.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "test_dma_mem_mtrace.hpp" +#include "test_dma_memset_mtrace.hpp" +#include "test_dma_tools.hpp" +#include "test_dma_encode.hpp" + +extern "C" { + uint64_t test_asm_dma_memset_mtrace(uint8_t *dst, uint64_t byte, size_t count, uint64_t *trace); +} + +class TestDmaMemSetMtrace: public TestDmaMemMtrace { +protected: + uint64_t *prev_dst; + uint64_t byte; + bool execute_single_test(void); +public: + TestDmaMemSetMtrace(size_t max_count = 1024); + virtual ~TestDmaMemSetMtrace(); + void run(void); +}; + +TestDmaMemSetMtrace::TestDmaMemSetMtrace(size_t max_count): + TestDmaMemMtrace(max_count,false) { + prev_dst = (uint64_t *)malloc(data_size); +} + +TestDmaMemSetMtrace::~TestDmaMemSetMtrace(void) { + free(prev_dst); +} + +void TestDmaMemSetMtrace::run(void) { + size_t total_tests = 0; + src_offset = 0; + for (byte = 0; byte <= 0xFF; ++byte) { + for (dst_offset = 0; dst_offset < 7; ++dst_offset) { + for (count = 0; count < 1024; ++count) { + if (!execute_single_test()) { + printf("\nTest is [\x1B[1;31mFAIL\x1B[0m]\n"); + printf("---------------------------------\n"); + size_t dst_qwords = (dst_offset + count + 7) >> 3; + for (size_t index = 0; index < dst_qwords; ++index) { + printf("prev_dst64[%ld] 0x%016lX\n", index, prev_dst[index]); + } + dump(); + return; + } + ++total_tests; + } + } + } + printf("\nAll %ld tests are [\x1B[1;32mOK\x1B[0m]\n", total_tests); +} + +bool TestDmaMemSetMtrace::execute_single_test(void) { + memset(test_trace, 0, trace_size); + fill_pattern(dst, data_size, 18219046755); + uint8_t *p_dst = dst + dst_offset; + + memcpy(prev_dst, dst, data_size); + printf("\rTEST byte:0x%02lX dst_offset:%ld count:%4ld", + byte, dst_offset, count); + fflush(stdout); + uint64_t res = test_asm_dma_memset_mtrace(p_dst, byte, count, test_trace); + size_t trace_count = test_trace[0]; + if (trace_count < 1) { + printf("\nERROR: invalid trace_count %ld\n", trace_count); + return false; + } + uint64_t _dst = (uint64_t)dst + dst_offset; + if (res != _dst) { + printf("\nERROR: invalid result expected:0x%08lX found:0x%08lX\n", _dst, res); + return false; + } + uint64_t encode = calculate_encode_memset((uint64_t)p_dst, count, byte); + if (mtrace[0] != encode) { + printf("\nERROR: invalid encoded\n"); + print_encode_mismatch(encode, mtrace[0]); + return false; + } + size_t index = 1; + size_t pre_count = (dst_offset > 0 && count > 0) ? 8 - dst_offset : 0; + if (pre_count > count) { + pre_count = count; + } + if (pre_count > 0) { + if (mtrace[index] != prev_dst[0]) { + printf("\nERROR: pre write pre-value expected: dst[0]:0x%016lX found: mtrace[%ld]:%016lX\n", prev_dst[0], index, mtrace[index]); + return false; + } + ++index; + } + + size_t post_count = (count - pre_count) & 0x07; + if (post_count > 0) { + size_t last_dst_index = (dst_offset + count - 1) >> 3; + if (mtrace[index] != prev_dst[last_dst_index]) { + printf("\nERROR: post write pre-value expected: dst[%ld]:0x%016lX vs found mtrace[%ld]:0x%016lX\n", last_dst_index, prev_dst[last_dst_index], index, mtrace[index]); + return false; + } + ++index; + } + if (trace_count != index) { + printf("ERROR: invalid mtrace len expected:%ld vs found:%ld\n", index, trace_count); + size_t _count = index > trace_count ? index + 1: trace_count + 1; + for (size_t i = 0; i <= _count; ++i) { + printf("mtrace[%ld] 0x%016lX\n", i, mtrace[i]); + } + return false; + } + return true; +} + +void test_dma_memset_mtrace() { + printf("\x1B[1;34mTEST DMA MEMSET MTRACE =================================================\x1B[0m\n"); + TestDmaMemSetMtrace test(1024); + test.run(); +} diff --git a/emulator-asm/src/dma/test/test_dma_memset_mtrace.hpp b/emulator-asm/src/dma/test/test_dma_memset_mtrace.hpp new file mode 100644 index 000000000..7c6753c82 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_memset_mtrace.hpp @@ -0,0 +1,6 @@ +#ifndef __TEST_DMA_MEMSET_MTRACE__HPP__ +#define __TEST_DMA_MEMSET_MTRACE__HPP__ + +void test_dma_memset_mtrace(); + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_dma_tools.cpp b/emulator-asm/src/dma/test/test_dma_tools.cpp new file mode 100644 index 000000000..3a7b58020 --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_tools.cpp @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "test_dma_tools.hpp" + +uint8_t *fill_pattern(uint8_t *data, size_t count, uint64_t seed) { + std::mt19937_64 rng(seed); + uint64_t *p_data = (uint64_t *)data; + + size_t count64 = count >> 3; + for (size_t i = 0; i < count64; ++i) { + p_data[i] = rng(); + } + size_t count_bytes = count & 0x07; + if (count_bytes > 0) { + uint64_t value = rng(); + uint8_t *value_bytes = (uint8_t *)&value; + uint8_t *bytes = data + (count64 << 3); + for (size_t i = 0; i < count_bytes; ++i) { + bytes[i] = value_bytes[i]; + } + + } + return data; +} + +bool check_pattern_slice(uint64_t *data, size_t from, size_t to, uint64_t seed) { + std::mt19937_64 rng(seed); + + for (size_t i = 0; i < to; ++i) { + const uint64_t rvalue = rng(); + if (i < from) continue; + if (data[i] != rvalue) { + return false; + } + } + return true; +} + +bool check_pattern_exclude_slice(uint64_t *data, size_t count, size_t from, size_t to, uint64_t seed) { + std::mt19937_64 rng(seed); + + for (size_t i = 0; i < count; ++i) { + const uint64_t rvalue = rng(); + if (i >= from && i <= to) continue; + if (data[i] != rvalue) { + return false; + } + } + return true; +} + +int create_memcmp_data(uint8_t *dst, uint8_t *src, size_t ef_count, int diff_dst_src) { + if (ef_count == 0) { + return 0; + } + + size_t count = diff_dst_src == 0 ? ef_count : ef_count - 1; + + for (size_t i = 0; i < count; ++i) { + dst[i] = src[i]; + } + if (diff_dst_src > 0) { + if (src[count] == 255) { + src[count] = 254; + dst[count] = 255; + return 1; + } + if (diff_dst_src > (255 - (int)src[count])) { + dst[count] = 255; + return 255 - (int)src[count]; + } + dst[count] = (int) src[count] + diff_dst_src; + return diff_dst_src; + } + if (diff_dst_src < 0) { + if (src[count] == 0) { + src[count] = 1; + dst[count] = 0; + return -1; + } + if (diff_dst_src < (0 - (int)src[count])) { + dst[count] = 0; + return 0 - (int)src[count]; + } + dst[count] = (int) src[count] + diff_dst_src; + return diff_dst_src; + } + return 0; +} diff --git a/emulator-asm/src/dma/test/test_dma_tools.hpp b/emulator-asm/src/dma/test/test_dma_tools.hpp new file mode 100644 index 000000000..26fb68faf --- /dev/null +++ b/emulator-asm/src/dma/test/test_dma_tools.hpp @@ -0,0 +1,15 @@ +#ifndef __TEST_DMA_TOOLS__HPP__ +#define __TEST_DMA_TOOLS__HPP__ + +#include +#include +#include + +#include "test_dma_tools.hpp" + +uint8_t *fill_pattern(uint8_t *data, size_t count, uint64_t seed); +bool check_pattern_slice(uint64_t *data, size_t from, size_t to, uint64_t seed); +bool check_pattern_exclude_slice(uint64_t *data, size_t count, size_t from, size_t to, uint64_t seed); +int create_memcmp_data(uint8_t *dst, uint8_t *src, size_t ef_count, int diff_dst_src); + +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test/test_mock.cpp b/emulator-asm/src/dma/test/test_mock.cpp new file mode 100644 index 000000000..d5edbd569 --- /dev/null +++ b/emulator-asm/src/dma/test/test_mock.cpp @@ -0,0 +1,16 @@ +#include +#include + +#include "test_mock.hpp" + +extern "C" { + uint64_t trace_address_threshold = 0; + uint64_t fcall_ctx[FCALL_CTX_LENGTH]; + uint64_t MEM_FREE_INPUT = 0; +} + +extern "C" void _realloc_trace(void) { + +} + + diff --git a/emulator-asm/src/dma/test/test_mock.hpp b/emulator-asm/src/dma/test/test_mock.hpp new file mode 100644 index 000000000..b6319cf80 --- /dev/null +++ b/emulator-asm/src/dma/test/test_mock.hpp @@ -0,0 +1,26 @@ +#ifndef __TEST_MOCK__HPP__ +#define __TEST_MOCK__HPP__ + +#include +#include + +#define FCALL_PARAMS_LENGTH 386 +#define FCALL_RESULT_LENGTH 8193 +#define FCALL_FUNCTION_ID 0 +#define FCALL_PARAMS_CAPACITY (FCALL_FUNCTION_ID + 1) +#define FCALL_PARAMS_SIZE (FCALL_PARAMS_CAPACITY + 1) +#define FCALL_PARAMS (FCALL_PARAMS_SIZE + 1) +#define FCALL_RESULT_CAPACITY (FCALL_PARAMS + FCALL_PARAMS_LENGTH) +#define FCALL_RESULT_SIZE (FCALL_RESULT_CAPACITY + 1) +#define FCALL_RESULT (FCALL_RESULT_SIZE + 1) // 391 +#define FCALL_RESULT_GOT (FCALL_RESULT + FCALL_RESULT_LENGTH) // 8584 +#define FCALL_CTX_LENGTH (FCALL_RESULT_GOT + 1) // 8585 + +extern "C" { + extern uint64_t trace_address_threshold; + extern uint64_t fcall_ctx[FCALL_CTX_LENGTH]; + extern uint64_t MEM_FREE_INPUT; +} + +extern "C" void _realloc_trace(void); +#endif \ No newline at end of file diff --git a/emulator-asm/src/dma/test_dma.cpp b/emulator-asm/src/dma/test_dma.cpp deleted file mode 100644 index 86173760d..000000000 --- a/emulator-asm/src/dma/test_dma.cpp +++ /dev/null @@ -1,985 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define NO_OVERLAPPING 0xFFFFFFFF - -// External assembly function declarations -extern "C" { - uint64_t trace_address_threshold = 0; - uint64_t dma_memcpy_mtrace(uint64_t dst, uint64_t src, uint64_t count, uint64_t* trace_ptr); - uint64_t dma_memcpy_mops(uint64_t dst, uint64_t src, uint64_t count, uint64_t* mops_ptr); - void dma_memcpy_fast(uint64_t dst, uint64_t src, uint64_t count); - uint64_t fast_dma_encode(uint64_t dst, uint64_t src, uint64_t count); - void _realloc_trace(void); -} - -void _realloc_trace(void) { - -} - -const char *mops_labels[16] = {"NOP", "CWR1", "RD1", "WR1", "RD2", "WR2", "RD4", "WR4", "RD8", "WR8", - "ARD", "AWR", "BR", "BW", "ABR", "ABW"}; - -// MOPS constants from dma_constants.inc -const uint64_t EXTRA_PARAMETER_ADDR = 0xA000'0F00; -const uint64_t MOPS_ALIGNED_READ = 0x0000'000C'0000'0000ULL; -const uint64_t MOPS_ALIGNED_BLOCK_READ = 0x0000'000E'0000'0000ULL; -const uint64_t MOPS_ALIGNED_BLOCK_WRITE = 0x0000'000F'0000'0000ULL; -const uint64_t MOPS_BLOCK_WORDS_SBITS = 36; - -class Memory { -protected: - uint8_t *original_bytes; -public: - uint8_t *bytes; - size_t size; - Memory (size_t size): size(size) { - bytes = (uint8_t *) aligned_alloc(8, size); - if (bytes == NULL) { - printf("Failed to allocate bytes\n"); - exit(1); - } - if ((((uint64_t) bytes) & 0x07) != 0) { - printf("Invalid allocation %p\n", bytes); - exit(1); - } - original_bytes = (uint8_t *) aligned_alloc(8, size); - if (original_bytes == NULL) { - printf("Failed to allocate original_bytes\n"); - free(bytes); - exit(1); - } - } - Memory(const Memory&) = delete; - Memory& operator=(const Memory&) = delete; - ~Memory() { - if (original_bytes) free(original_bytes); - if (bytes) free(bytes); - original_bytes = NULL; - bytes = NULL; - } - - const uint8_t *get_original_bytes(uint8_t *reference) { - return original_bytes + (reference - bytes); - } - void fill_pattern(uint8_t start = 0) { - for (size_t i = 0; i < size; ++i) { - bytes[i] = start + i; - } - memcpy(original_bytes, bytes, size); - } - - bool verify_pattern(uint8_t start = 0, const char *title = "") { - for (size_t i = 0; i < size; ++i) { - uint8_t expected = start + i; - if (bytes[i] != expected) { - printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", - title, expected, i, bytes[i]); - return false; - } - } - return true; - } - - bool verify_pattern_except(uint8_t start, size_t addr, size_t count, const char *title = "") { - size_t from = addr - (size_t)bytes; - size_t to = from + count - 1; - for (size_t i = 0; i < size; ++i) { - if (i >= from && i <= to) continue; - uint8_t expected = start + i; - if (bytes[i] != expected) { - printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", - title, expected, i, bytes[i]); - return false; - } - } - return true; - } -}; - -// Helper class to manage aligned test buffers -class AlignedBuffer { -public: - std::vector data; - - AlignedBuffer(size_t size) : data(size, 0) {} - - uint64_t* aligned_ptr() { - return reinterpret_cast(data.data()); - } - - uint8_t* byte_ptr() { - return data.data(); - } - - void fill_pattern(uint8_t start = 0) { - for (size_t i = 0; i < data.size(); ++i) { - data[i] = static_cast(start + i); - } - } - - bool verify_pattern(uint8_t start = 0, const char *title = "") { - for (size_t i = 0; i < data.size(); ++i) { - uint8_t expected = static_cast(start + i); - if (data[i] != expected) { - printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", - title, expected, i, data[i]); - return false; - } - } - return true; - } - - bool verify_pattern_except(uint8_t start, size_t from, size_t count, const char *title = "") { - size_t to = from + count - 1; - for (size_t i = 0; i < data.size(); ++i) { - if (i >= from && i <= to) continue; - uint8_t expected = static_cast(start + i); - if (data[i] != expected) { - printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", - title, expected, i, data[i]); - return false; - } - } - return true; - } - - bool verify_fill(uint8_t value, const char *title = "") { - for (size_t i = 0; i < data.size(); ++i) { - if (data[i] != value) { - printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]:0x%02X\n", - title, value, i, data[i]); - return false; - } - } - return true; - } - - bool verify_fill_except(uint8_t value, size_t from, size_t count, const char *title = "") { - size_t to = from + count - 1; - for (size_t i = 0; i < data.size(); ++i) { - if (i >= from && i <= to) continue; - if (data[i] != value) { - printf("FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]:0x%02X\n", - title, value, i, data[i]); - return false; - } - } - return true; - } - - void fill_value(uint8_t value) { - std::fill(data.begin(), data.end(), value); - } -}; - -#define TRACE_EQ(EXPECTED, CALCULATED, MSG) \ - if (EXPECTED != CALCULATED) { \ - fprintf(stderr, "❌ FAIL: Trace comparation on %s (E: 0x%016lX vs 0x%016lX) \n", MSG, EXPECTED, CALCULATED); \ - exit(1); \ - } - -// Reference implementation for encode_memcpy from Rust -uint64_t encode_memcpy_reference(uint64_t dst, uint64_t src, uint64_t count) { - uint64_t dst_offset = dst & 0x07; - uint64_t src_offset = src & 0x07; - - uint64_t pre_count, loop_count, post_count; - - if (dst_offset > 0) { - uint64_t _pre_count = 8 - dst_offset; - if (_pre_count >= count) { - pre_count = count; - loop_count = 0; - post_count = 0; - } else { - uint64_t pending = count - _pre_count; - pre_count = _pre_count; - loop_count = pending >> 3; - post_count = pending & 0x07; - } - } else { - pre_count = 0; - loop_count = count >> 3; - post_count = count & 0x07; - } - - uint64_t pre_writes = (pre_count > 0) + (post_count > 0); - uint64_t src_offset_pos = (src_offset + pre_count) & 0x07; - uint64_t double_src_post = (src_offset_pos + post_count) > 8; - uint64_t double_src_pre = (src_offset + pre_count) > 8; - uint64_t extra_src_reads = - (count == 0) ? 0 : ((((src + count - 1) >> 3) - (src >> 3) + 1) - loop_count); - uint64_t src64_inc_by_pre = (pre_count > 0 && (src_offset + pre_count) >= 8); - uint64_t unaligned_dst_src = (src_offset != dst_offset); - - return pre_count - | (post_count << 3) - | (pre_writes << 6) - | (dst_offset << 8) - | (src_offset << 11) - | (double_src_pre << 14) - | (double_src_post << 15) - | (extra_src_reads << 16) - | (src64_inc_by_pre << 18) - | (unaligned_dst_src << 19) - | (pre_count << 29) - | (loop_count << 32); -} - -// Extract fields from encoded value -struct EncodedInfo { - uint64_t loop_count; - uint64_t pre_writes; - uint64_t dst_offset; - uint64_t src_offset; - uint64_t pre_count; - uint64_t post_count; - bool double_src_pre; - bool double_src_post; - uint64_t extra_src_reads; - uint64_t src64_inc_by_pre; - uint64_t unaligned_dst_src; - - EncodedInfo(uint64_t encoded) { - loop_count = encoded >> 32; - pre_count = encoded & 0x07; - post_count = (encoded >> 3) & 0x07; - pre_writes = (encoded >> 6) & 0x03; - dst_offset = (encoded >> 8) & 0x07; - src_offset = (encoded >> 11) & 0x07; - double_src_pre = (encoded >> 14) & 0x01; - double_src_post = (encoded >> 15) & 0x01; - extra_src_reads = (encoded >> 16) & 0x03; - src64_inc_by_pre = (encoded >> 18) & 0x01; - unaligned_dst_src = (encoded >> 19) & 0x01; - } - - void print() const { - std::cout << " loop_count: " << loop_count << "\n" - << " pre_writes: " << pre_writes << "\n" - << " dst_offset: " << dst_offset << "\n" - << " src_offset: " << src_offset << "\n" - << " pre_count: " << pre_count << "\n" - << " post_count: " << post_count << "\n" - << " double_src_pre: " << double_src_pre << "\n" - << " double_src_post: " << double_src_post << "\n" - << " extra_src_reads: " << extra_src_reads << "\n" - << " src64_inc_by_pre: " << src64_inc_by_pre << "\n" - << " unaligned_dst_src: " << unaligned_dst_src << "\n"; - } -}; - -void print_hex_dump(const char* label, const uint8_t* data, size_t size) { - std::cout << label << " (" << size << " bytes):\n"; - for (size_t i = 0; i < size; i += 16) { - std::cout << " " << std::hex << std::setw(4) << std::setfill('0') << i << ": "; - for (size_t j = 0; j < 16 && i + j < size; ++j) { - std::cout << std::hex << std::setw(2) << std::setfill('0') - << static_cast(data[i + j]) << " "; - } - std::cout << "\n"; - } - std::cout << std::dec; -} - -bool test_memcpy_mtrace(Memory &mem, uint64_t dst_offset, uint64_t src_offset, size_t count, - const char* description, int64_t overlapping = NO_OVERLAPPING) { - - - bool is_overlapping = overlapping != NO_OVERLAPPING; - if (is_overlapping) { - printf("\n\x1b[1;36m##### test_memcpy_mtrace(%ld, %ld, %ld,\"%s\", overlapping:%ld) #####\x1b[0m\n", - dst_offset, src_offset, count, description, overlapping); - } else { - printf("\n\x1b[1;36m##### test_memcpy_mtrace(%ld, %ld, %ld,\"%s\") #####\x1b[0m\n", - dst_offset, src_offset, count, description); - } - mem.fill_pattern(0x10); - - uint8_t *src; - uint8_t *dst; - - if (is_overlapping) { - src = mem.bytes + 1024; - dst = src + overlapping; - } else { - src = mem.bytes + 1024; - dst = src + ((count + 7) & ~0x07) + 1024; - } - AlignedBuffer trace_buf(4096); - - // Clear trace buffer - trace_buf.fill_value(0); - - if (!mem.verify_pattern(0x10) || !trace_buf.verify_fill(0, "trace_buff")) { - return false; - } - - uint64_t src_addr = ((uint64_t) src) + src_offset; - uint64_t dst_addr = ((uint64_t) dst) + dst_offset; - uint64_t* trace_ptr = trace_buf.aligned_ptr(); - - printf("TEST dst:0x%08lX src:0x%08lX count:%ld trace:%p\n", dst_addr, src_addr, count, trace_ptr); - - // Calculate reference encoding BEFORE assembly call to avoid register corruption - uint64_t encoded_ref = encode_memcpy_reference(dst_addr, src_addr, count); - - // Call assembly function and capture return value - uint64_t qwords_written = dma_memcpy_mtrace(dst_addr, src_addr, count, trace_ptr); - - // Get encoded value from trace - uint64_t encoded_asm = trace_ptr[0]; - - if (encoded_asm != encoded_ref) { - printf("Encoded (ASM): 0x%016lX\n", encoded_asm); - printf("Encoded (REF): 0x%016lX\n", encoded_ref); - - std::cerr << "❌ FAIL: Encoded value mismatch!\n"; - EncodedInfo info_asm(encoded_asm); - EncodedInfo info_ref(encoded_ref); - std::cout << "ASM info:\n"; - info_asm.print(); - std::cout << "REF info:\n"; - info_ref.print(); - return false; - } - - // Verify the actual memcpy was performed - const uint8_t* src_bytes; - if (is_overlapping) { - src_bytes = mem.get_original_bytes((uint8_t *)src_addr); - } else { - src_bytes = (uint8_t *)src_addr; - } - const uint8_t* dst_bytes = reinterpret_cast(dst_addr); - - bool copy_ok = true; - for (size_t i = 0; i < count; ++i) { - if (dst_bytes[i] != src_bytes[i]) { - std::cerr << "❌ FAIL: Memory copy mismatch at byte " << i << "\n"; - std::cerr << " Expected: 0x" << std::hex << static_cast(src_bytes[i]) - << ", Got: 0x" << static_cast(dst_bytes[i]) << std::dec << "\n"; - copy_ok = false; - break; - } - } - - if (!copy_ok) { - print_hex_dump("Source", src_bytes, std::min(count, size_t(64))); - print_hex_dump("Destination", dst_bytes, std::min(count, size_t(64))); - return false; - } - - std::cout << "✅ PASS: Encoding and copy correct\n"; - - // Print trace buffer summary - EncodedInfo info(encoded_asm); - size_t trace_idx = 1; - std::cout << "Trace buffer:\n"; - std::cout << " [0] Encoded: 0x" << std::hex << trace_ptr[0] << std::dec << "\n"; - - uint64_t *dst_original = (uint64_t*) mem.get_original_bytes((uint8_t *) (dst_addr & ~0x07)); - if (info.pre_count > 0) { - TRACE_EQ(dst_original[0], trace_ptr[trace_idx], "PRE pre-write value not match"); - trace_idx++; - } - - if (info.post_count > 0) { - size_t last_dst_index = (dst_offset + count - 1) >> 3; - TRACE_EQ(dst_original[last_dst_index], trace_ptr[trace_idx], "POST pre-write value not match"); - trace_idx++; - } - - size_t expected_src_qwords = info.loop_count + info.extra_src_reads; - // Verify that the function returned the correct number of qwords written - size_t expected_total_qwords = trace_idx + expected_src_qwords; - - if (qwords_written != expected_total_qwords) { - std::cerr << "❌ FAIL: Incorrect number of qwords returned!\n"; - std::cerr << " Expected: " << expected_total_qwords << " qwords\n"; - std::cerr << " Got: " << qwords_written << " qwords\n"; - return false; - } - - uint64_t *src_original = (uint64_t*) mem.get_original_bytes((uint8_t *) (src_addr & ~0x07)); - for (size_t index = 0; index < expected_src_qwords; ++index) { - TRACE_EQ(src_original[index], trace_ptr[trace_idx], "SRC values not match"); - trace_idx++; - } - if (!mem.verify_pattern_except(0x10, dst_addr, count, "mem (out)") || - !trace_buf.verify_fill_except(0, 0, qwords_written * 8, "trace_buff (out)")) { - return false; - } - std::cout << "✅ Returned correct qword count: " << qwords_written << " qwords\n"; - - return true; -} - -bool test_memcpy_mops(Memory &mem, uint64_t dst_offset, uint64_t src_offset, size_t count, - const char* description, int64_t overlapping = NO_OVERLAPPING) { - - - bool is_overlapping = overlapping != NO_OVERLAPPING; - if (is_overlapping) { - printf("\n\x1b[1;35m##### test_memcpy_mops(%ld, %ld, %ld,\"%s\", overlapping:%ld) #####\x1b[0m\n", - dst_offset, src_offset, count, description, overlapping); - } else { - printf("\n\x1b[1;35m##### test_memcpy_mops(%ld, %ld, %ld,\"%s\") #####\x1b[0m\n", - dst_offset, src_offset, count, description); - } - mem.fill_pattern(0x10); - - uint8_t *src; - uint8_t *dst; - - if (is_overlapping) { - src = mem.bytes + 1024; - dst = src + overlapping; - } else { - src = mem.bytes + 1024; - dst = src + ((count + 7) & ~0x07) + 1024; - } - AlignedBuffer mops_buf(4096); - - // Clear mops buffer - mops_buf.fill_value(0); - - if (!mem.verify_pattern(0x10) || !mops_buf.verify_fill(0, "mops_buff")) { - return false; - } - - uint64_t src_addr = ((uint64_t) src) + src_offset; - uint64_t dst_addr = ((uint64_t) dst) + dst_offset; - uint64_t* mops_ptr = mops_buf.aligned_ptr(); - - printf("TEST dst:0x%08lX src:0x%08lX count:%ld mops:%p\n", dst_addr, src_addr, count, mops_ptr); - - // Calculate reference encoding to know expected structure - uint64_t encoded_ref = encode_memcpy_reference(dst_addr, src_addr, count); - EncodedInfo info(encoded_ref); - printf("INFO pre:%ld%s post:%ld%s loop:%ld sibp:%ld\n", info.pre_count, info.double_src_pre ? "+D":"", - info.post_count, info.double_src_post ? "+D":"", info.loop_count, info.src64_inc_by_pre); - - // Call assembly function and capture return value - uint64_t mops_entries = dma_memcpy_mops(dst_addr, src_addr, count, mops_ptr); - - // Verify the actual memcpy was performed - const uint8_t* src_bytes; - if (is_overlapping) { - src_bytes = mem.get_original_bytes((uint8_t *)src_addr); - } else { - src_bytes = (uint8_t *)src_addr; - } - const uint8_t* dst_bytes = reinterpret_cast(dst_addr); - - bool copy_ok = true; - for (size_t i = 0; i < count; ++i) { - if (dst_bytes[i] != src_bytes[i]) { - std::cerr << "❌ FAIL: Memory copy mismatch at byte " << i << "\n"; - std::cerr << " Expected: 0x" << std::hex << static_cast(src_bytes[i]) - << ", Got: 0x" << static_cast(dst_bytes[i]) << std::dec << "\n"; - copy_ok = false; - break; - } - } - - if (!copy_ok) { - print_hex_dump("Source", src_bytes, std::min(count, size_t(64))); - print_hex_dump("Destination", dst_bytes, std::min(count, size_t(64))); - return false; - } - - std::vector> expected; - - expected.emplace_back(MOPS_ALIGNED_READ + EXTRA_PARAMETER_ADDR, "PARAM count"); - - if (info.pre_count > 0) { - expected.emplace_back(MOPS_ALIGNED_READ + (dst_addr & ~0x07ULL), "PRE preread dst"); - expected.emplace_back((info.double_src_pre ? (MOPS_ALIGNED_BLOCK_READ + (2ULL << MOPS_BLOCK_WORDS_SBITS)): MOPS_ALIGNED_READ) + - (src_addr & ~0x07ULL), "PRE src read"); - } - - if (info.post_count > 0) { - expected.emplace_back(MOPS_ALIGNED_READ + ((dst_addr + count - 1) & ~0x07ULL), "POST preread dst"); - expected.emplace_back((info.double_src_post ? (MOPS_ALIGNED_BLOCK_READ + (2ULL << MOPS_BLOCK_WORDS_SBITS)): MOPS_ALIGNED_READ) + - ((src_addr + info.pre_count + info.loop_count * 8) & ~0x07ULL), "POST src read"); - } - - if (info.loop_count > 0) { - expected.emplace_back(MOPS_ALIGNED_BLOCK_READ + ((info.loop_count + (info.unaligned_dst_src)) << MOPS_BLOCK_WORDS_SBITS) - + ((src_addr + info.pre_count) & ~0x07ULL), "LOOP read src"); - } - - if (count > 0) { - expected.emplace_back(MOPS_ALIGNED_BLOCK_WRITE + ((info.loop_count + info.pre_writes) << MOPS_BLOCK_WORDS_SBITS) - + (dst_addr & ~0x07ULL), "write dst"); - } - - size_t max_entries = std::max(mops_entries, expected.size()); - bool errors = false; - for (size_t i = 0; i < max_entries; ++i) { - printf("MOPS[%2ld] = ", i); - if (i < mops_entries) { - printf("%3ld %3s 0x%08lX # ", mops_ptr[i] >> MOPS_BLOCK_WORDS_SBITS, - mops_labels[(mops_ptr[i] >> 32) & 0x0F], mops_ptr[i] & 0xFFFF'FFFF); - } else { - printf("--- --- ---------- #"); - } - if (i < expected.size()) { - printf("%3ld %3s 0x%08lX %s", expected[i].first >> MOPS_BLOCK_WORDS_SBITS, - mops_labels[(expected[i].first >> 32) & 0x0F], expected[i].first & 0xFFFF'FFFF, expected[i].second.c_str()); - } - if (i >= mops_entries || i >= expected.size()) { - printf(" \x1B[31;1mFAIL\x1B[0m\n"); - errors = true; - } else if (mops_ptr[i] != expected[i].first) { - printf(" \x1B[31;1mNOT MATCH\x1B[0m\n"); - errors = true; - } else { - printf("\n"); - } - } - - // Verify total mops entries count - if (mops_entries != expected.size()) { - printf("FAIL: Incorrect number of mops entries (E:%ld vs %ld)", expected.size(), mops_entries); - return false; - } - - if (errors) { - return false; - } - std::cout << "✅ PASS: MOPS entries and copy correct (" << mops_entries << " entries)\n"; - - if (!mem.verify_pattern_except(0x10, dst_addr, count, "mem (out)") || - !mops_buf.verify_fill_except(0, 0, mops_entries * 8, "mops_buff (out)")) { - return false; - } - - return true; -} - -bool test_overlapping_copy(const char* description, int64_t offset) { - std::cout << "\n=== Test: " << description << " ===\n"; - std::cout << "Offset: " << offset << " bytes\n"; - - AlignedBuffer buf(2048); - AlignedBuffer trace_buf(2048); - - // Fill with pattern - buf.fill_pattern(0x20); - trace_buf.fill_value(0); - - size_t count = 32; - uint64_t src_addr = reinterpret_cast(buf.byte_ptr() + 64); - uint64_t dst_addr = src_addr + offset; - - // Validate bounds - if (dst_addr < reinterpret_cast(buf.byte_ptr()) || - dst_addr + count > reinterpret_cast(buf.byte_ptr() + buf.data.size())) { - std::cerr << "❌ FAIL: dst_addr out of bounds\n"; - return false; - } - uint64_t* trace_ptr = trace_buf.aligned_ptr(); - - // Save original data - std::vector original_src(count); - std::memcpy(original_src.data(), reinterpret_cast(src_addr), count); - - // Call function - dma_memcpy_mtrace(dst_addr, src_addr, count, trace_ptr); - - // Verify copy - const uint8_t* dst_bytes = reinterpret_cast(dst_addr); - bool ok = true; - for (size_t i = 0; i < count; ++i) { - if (dst_bytes[i] != original_src[i]) { - std::cerr << "❌ FAIL: Overlapping copy mismatch at byte " << i << "\n"; - ok = false; - break; - } - } - - if (ok) { - std::cout << "✅ PASS: Overlapping copy correct\n"; - } - - return ok; -} - -bool test_fast_memcpy(uint64_t dst_offset, uint64_t src_offset, size_t count, - const char* description) { - std::cout << "\n=== Test Fast: " << description << " ===\n"; - std::cout << "dst_offset=" << dst_offset << ", src_offset=" << src_offset - << ", count=" << count << "\n"; - - // Allocate buffers - AlignedBuffer src_buf(2048); - AlignedBuffer dst_buf(2048); - - // Fill source with pattern - src_buf.fill_pattern(0x10); - // Fill destination with different pattern - dst_buf.fill_pattern(0xA0); - - // Calculate actual addresses with offsets - uint64_t src_addr = reinterpret_cast(src_buf.byte_ptr() + 64) + src_offset; - uint64_t dst_addr = reinterpret_cast(dst_buf.byte_ptr() + 64) + dst_offset; - - // Call assembly function (no trace) - dma_memcpy_fast(dst_addr, src_addr, count); - - // Verify the memcpy was performed - const uint8_t* src_bytes = reinterpret_cast(src_addr); - const uint8_t* dst_bytes = reinterpret_cast(dst_addr); - - bool copy_ok = true; - for (size_t i = 0; i < count; ++i) { - if (dst_bytes[i] != src_bytes[i]) { - std::cout << "❌ FAIL: Mismatch at byte " << i << "\n"; - std::cout << " Expected: 0x" << std::hex << (int)src_bytes[i] - << " Got: 0x" << (int)dst_bytes[i] << std::dec << "\n"; - copy_ok = false; - break; - } - } - - if (!copy_ok) { - return false; - } - - std::cout << "✅ PASS: Fast copy correct\n"; - return true; -} - -bool test_fast_overlapping_heap(const char* description, int64_t offset) { - std::cout << "\n=== Test Fast Overlap (HEAP): " << description << " ===\n"; - std::cout << "Offset: " << offset << " bytes\n"; - - size_t count = 32; - size_t buffer_size = 4096; - - // Allocate with aligned_alloc to get proper alignment and avoid vector overhead - uint8_t* buf = (uint8_t*)aligned_alloc(8, buffer_size); - if (!buf) { - std::cerr << "❌ FAIL: allocation failed\n"; - return false; - } - - // Fill with pattern - for (size_t i = 0; i < buffer_size; ++i) { - buf[i] = 0x20 + (i & 0xFF); - } - - uint64_t src_addr = reinterpret_cast(buf + 1024); - uint64_t dst_addr = src_addr + offset; - - // Validate bounds - if (dst_addr < reinterpret_cast(buf) || - dst_addr + count > reinterpret_cast(buf + buffer_size)) { - std::cerr << "❌ FAIL: dst_addr out of bounds\n"; - free(buf); - return false; - } - - // Set canaries - size_t min_addr = std::min(src_addr, dst_addr) - reinterpret_cast(buf); - size_t max_addr = std::max(src_addr + count, dst_addr + count) - reinterpret_cast(buf); - - const uint8_t CANARY = 0xCA; - for (size_t i = min_addr - 8; i < min_addr; ++i) { - buf[i] = CANARY; - } - for (size_t i = max_addr; i < max_addr + 8; ++i) { - buf[i] = CANARY; - } - - // Save original data - uint8_t original_src[32]; - const uint8_t* src_bytes = reinterpret_cast(src_addr); - for (size_t i = 0; i < count; ++i) { - original_src[i] = src_bytes[i]; - } - - // Call function - printf("dma_memcpy_fast(0x%016lx,0x%016lx,%ld)\n", dst_addr, src_addr, count); - dma_memcpy_fast(dst_addr, src_addr, count); - printf("dma_memcpy_fast-END\n"); - - // Check canaries - bool canaries_ok = true; - for (size_t i = min_addr - 8; i < min_addr; ++i) { - if (buf[i] != CANARY) { - std::cerr << "❌ FAIL: Canary corrupted BEFORE at offset " << i << "\n"; - canaries_ok = false; - } - } - for (size_t i = max_addr; i < max_addr + 8; ++i) { - if (buf[i] != CANARY) { - std::cerr << "❌ FAIL: Canary corrupted AFTER at offset " << i << "\n"; - canaries_ok = false; - } - } - - // Verify result - const uint8_t* dst_bytes = reinterpret_cast(dst_addr); - bool ok = canaries_ok; - for (size_t i = 0; i < count; ++i) { - if (dst_bytes[i] != original_src[i]) { - std::cout << "❌ FAIL: Mismatch at byte " << i << "\n"; - ok = false; - break; - } - } - - free(buf); - - if (ok) { - std::cout << "✅ PASS: Fast overlapping copy correct (heap)\n"; - } - - return ok; -} - -bool test_fast_overlapping(const char* description, int64_t offset) { - std::cout << "\n=== Test Fast Overlap: " << description << " ===\n"; - std::cout << "Offset: " << offset << " bytes\n"; - - size_t count = 32; - - // Use statically allocated aligned buffer with canaries - static uint8_t static_buf[4096] __attribute__((aligned(8))); - - // Fill with pattern - for (size_t i = 0; i < sizeof(static_buf); ++i) { - static_buf[i] = 0x20 + (i & 0xFF); - } - - // Calculate addresses with safety margins - size_t guard_size = 64; // 64 bytes before and after - uint64_t src_addr = reinterpret_cast(static_buf + 1024); - uint64_t dst_addr = src_addr + offset; - - // Validate bounds - if (dst_addr < reinterpret_cast(static_buf + guard_size) || - dst_addr + count > reinterpret_cast(static_buf + sizeof(static_buf) - guard_size)) { - std::cerr << "❌ FAIL: dst_addr out of bounds\n"; - return false; - } - - // Set canary values around the operation zone - size_t min_addr = std::min(src_addr, dst_addr) - reinterpret_cast(static_buf); - size_t max_addr = std::max(src_addr + count, dst_addr + count) - reinterpret_cast(static_buf); - - // Fill canaries before and after - const uint8_t CANARY = 0xCA; - for (size_t i = min_addr - 8; i < min_addr; ++i) { - static_buf[i] = CANARY; - } - for (size_t i = max_addr; i < max_addr + 8; ++i) { - static_buf[i] = CANARY; - } - - // Save original data using simple array - uint8_t original_src[32]; - const uint8_t* src_bytes = reinterpret_cast(src_addr); - for (size_t i = 0; i < count; ++i) { - original_src[i] = src_bytes[i]; - } - // std::memcpy(original_src.data(), reinterpret_cast(src_addr), count); - - // Call fast function - printf("dma_memcpy_fast(0x%016lx,0x%016lx,%ld)\n", dst_addr, src_addr, count); - dma_memcpy_fast(dst_addr, src_addr, count); - printf("dma_memcpy_fast-END\n"); - - // Check canaries for buffer overflow - bool canaries_ok = true; - for (size_t i = min_addr - 8; i < min_addr; ++i) { - if (static_buf[i] != CANARY) { - std::cerr << "❌ FAIL: Canary corrupted BEFORE region at offset " << i - << " (expected 0x" << std::hex << (int)CANARY - << ", got 0x" << (int)static_buf[i] << std::dec << ")\n"; - canaries_ok = false; - } - } - for (size_t i = max_addr; i < max_addr + 8; ++i) { - if (static_buf[i] != CANARY) { - std::cerr << "❌ FAIL: Canary corrupted AFTER region at offset " << i - << " (expected 0x" << std::hex << (int)CANARY - << ", got 0x" << (int)static_buf[i] << std::dec << ")\n"; - canaries_ok = false; - } - } - - if (!canaries_ok) { - std::cerr << "❌ BUFFER OVERFLOW DETECTED!\n"; - std::cerr << " src_addr: 0x" << std::hex << src_addr << std::dec << "\n"; - std::cerr << " dst_addr: 0x" << std::hex << dst_addr << std::dec << "\n"; - std::cerr << " count: " << count << "\n"; - std::cerr << " offset: " << offset << "\n"; - return false; - } - - // Verify result - const uint8_t* dst_bytes = reinterpret_cast(dst_addr); - bool ok = true; - for (size_t i = 0; i < count; ++i) { - if (dst_bytes[i] != original_src[i]) { - std::cout << "❌ FAIL: Mismatch at byte " << i << "\n"; - ok = false; - break; - } - } - - if (!ok) { - return false; - } - - std::cout << "✅ PASS: Fast overlapping copy correct\n"; - return true; -} - -int main() { - Memory mem (8192); - std::cout << "Testing DMA memory operations assembly implementation\n"; - std::cout << "=====================================================\n"; - - int passed = 0; - int total = 0; - - // Test mtrace (memory trace with full data) - std::cout << "\n\x1b[1;33m=== MTRACE Tests (Full Memory Trace) ===\x1b[0m\n"; - auto run_mtrace_test = [&](uint64_t dst_off, uint64_t src_off, size_t count, const char* desc) { - total++; - if (test_memcpy_mtrace(mem, dst_off, src_off, count, desc)) { - passed++; - } - }; - - run_mtrace_test(0, 0, 0, "Zero count"); - run_mtrace_test(0, 0, 1, "Single byte, aligned"); - run_mtrace_test(0, 0, 8, "One qword, aligned"); - run_mtrace_test(0, 0, 16, "Two qwords, aligned"); - run_mtrace_test(1, 0, 7, "dst_offset=1, count=7"); - run_mtrace_test(7, 0, 1, "dst_offset=7, count=1"); - run_mtrace_test(7, 0, 2, "dst_offset=7, count=2"); - run_mtrace_test(3, 5, 10, "dst_offset=3, src_offset=5, count=10"); - run_mtrace_test(0, 0, 100, "Large aligned copy"); - run_mtrace_test(3, 5, 100, "Large unaligned copy"); - - // Test mops (memory operations - addresses only) - std::cout << "\n\x1b[1;33m=== MOPS Tests (Memory Operations) ===\x1b[0m\n"; - auto run_mops_test = [&](uint64_t dst_off, uint64_t src_off, size_t count, const char* desc) { - total++; - if (test_memcpy_mops(mem, dst_off, src_off, count, desc)) { - passed++; - } else { - exit(1); - } - }; - - run_mops_test(0, 0, 0, "Zero count"); - run_mops_test(0, 0, 1, "Single byte, aligned"); - run_mops_test(0, 0, 8, "One qword, aligned"); - run_mops_test(0, 0, 16, "Two qwords, aligned"); - run_mops_test(1, 0, 7, "dst_offset=1, count=7"); - run_mops_test(7, 0, 1, "dst_offset=7, count=1"); - run_mops_test(7, 0, 2, "dst_offset=7, count=2"); - run_mops_test(3, 5, 10, "dst_offset=3, src_offset=5, count=10"); - run_mops_test(0, 0, 100, "Large aligned copy"); - run_mops_test(3, 5, 100, "Large unaligned copy"); - - // Comprehensive test - std::cout << "\n=== Comprehensive Test ===\n"; - for (uint64_t dst_off = 0; dst_off < 8; ++dst_off) { - for (uint64_t src_off = 0; src_off < 8; ++src_off) { - for (size_t count = 0; count < 128; ++count) { - total++; - if (test_memcpy_mtrace(mem, dst_off, src_off, count, "Comprehensive")) { - passed++; - } - total++; - if (test_memcpy_mtrace(mem, dst_off, src_off, count, "Comprehensive overlapping 0", 0)) { - passed++; - } - // total++; - // if (count > 4) { - // if (test_encode_memcpy(mem, dst_off, src_off, count, "Comprehensive overlapping -4", -4)) { - // passed++; - // } - // total++; - // if (test_encode_memcpy(mem, dst_off, src_off, count, "Comprehensive overlapping 4", 4)) { - // passed++; - // } - // total++; - // if (test_encode_memcpy(mem, dst_off, src_off, count, "Comprehensive overlapping 1 byte", count-1)) { - // passed++; - // } - // } - } - } - } - // Overlapping tests - total++; - if (test_overlapping_copy("Forward overlap (dst > src)", 8)) passed++; - - total++; - if (test_overlapping_copy("Backward overlap (dst < src)", -8)) passed++; - - total++; - if (test_overlapping_copy("No overlap (large gap)", 100)) passed++; - - // Fast memcpy tests - std::cout << "\n=== Fast Memcpy Tests ===\n"; - auto run_fast_test = [&](uint64_t dst_off, uint64_t src_off, size_t count, const char* desc) { - total++; - if (test_fast_memcpy(dst_off, src_off, count, desc)) { - passed++; - } - }; - - run_fast_test(0, 0, 0, "Zero count"); - run_fast_test(0, 0, 1, "Single byte, aligned"); - run_fast_test(0, 0, 8, "One qword, aligned"); - run_fast_test(0, 0, 16, "Two qwords, aligned"); - run_fast_test(1, 0, 7, "dst_offset=1, count=7"); - run_fast_test(7, 0, 1, "dst_offset=7, count=1"); - run_fast_test(3, 5, 10, "dst_offset=3, src_offset=5, count=10"); - run_fast_test(0, 0, 100, "Large aligned copy"); - run_fast_test(3, 5, 100, "Large unaligned copy"); - run_fast_test(1, 2, 1000, "Very large copy"); - - // Fast overlapping tests - total++; - if (test_fast_overlapping("Forward overlap (dst > src)", 8)) passed++; - - // Test with heap allocation - total++; - if (test_fast_overlapping_heap("Forward overlap (dst > src) HEAP", 8)) passed++; - - - total++; - if (test_fast_overlapping("Backward overlap (dst < src)", -8)) passed++; - - total++; - if (test_fast_overlapping("No overlap (large gap)", 100)) passed++; - - // Summary - std::cout << "\n=== Test Summary ===\n"; - std::cout << "Passed: " << passed << "/" << total << " tests\n"; - std::cout << "Success rate: " << (100.0 * passed / total) << "%\n"; - - if (passed == total) { - std::cout << "\n✅ All tests passed!\n"; - return 0; - } else { - std::cout << "\n❌ Some tests failed!\n"; - return 1; - } -} diff --git a/emulator-asm/src/dma/test_dma_api.asm b/emulator-asm/src/dma/test_dma_api.asm new file mode 100644 index 000000000..8ffbd6429 --- /dev/null +++ b/emulator-asm/src/dma/test_dma_api.asm @@ -0,0 +1,75 @@ +.intel_syntax noprefix +.code64 + +.section .text + +.macro ABI_WRAPPER abi_call asm_call +.global \abi_call +.extern \asm_call + +\abi_call: + push r12 # 1 cycle - save callee-saved register + push r13 # 1 cycle - save callee-saved register + push r9 # 1 cycle - save caller-saved register (used internally) + push rbx # 1 cycle - save callee-saved register + + mov r12, rcx # 1 cycle - setup mtrace address from count parameter + mov r13, 1 # 1 cycle - initialize mtrace index to 1, first position for count + call \asm_call # ~3 cycles + function cost + + dec r13 + mov [r12], r13 # store in first position the length + pop rbx # 1 cycle - restore register + pop r9 # 1 cycle - restore register + pop r13 # 1 cycle - restore register + pop r12 # 1 cycle - restore register + + ret +.endm + +# [memcpy] +# PARAMETERS (System V AMD64 ABI): +# rdi = dst +# rsi = src +# rdx = count +# rcx = mtrace_ptr +# RETURN: rax = dst + +ABI_WRAPPER test_asm_dma_memcpy_mops direct_dma_memcpy_mops +ABI_WRAPPER test_asm_dma_memcpy_mtrace direct_dma_memcpy_mtrace + +# [memcmp] +# PARAMETERS (System V AMD64 ABI): +# rdi = dst +# rsi = src +# rdx = count +# rcx = mtrace_ptr +# RETURN: rax = result compare + +ABI_WRAPPER test_asm_dma_memcmp_mops direct_dma_memcmp_mops +ABI_WRAPPER test_asm_dma_memcmp_mtrace direct_dma_memcmp_mtrace + +# [memset] +# PARAMETERS (System V AMD64 ABI): +# rdi = dst +# rsi = byte +# rdx = count +# rcx = mtrace_ptr +# RETURN: rax = result compare + + +ABI_WRAPPER test_asm_dma_memset_mops direct_dma_xmemset_mops +ABI_WRAPPER test_asm_dma_memset_mtrace direct_dma_xmemset_mtrace + +# [inputcpy] +# PARAMETERS (System V AMD64 ABI): +# rdi = dst +# rsi = 0 +# rdx = count +# rcx = mtrace_ptr +# RETURN: rax = result compare + +ABI_WRAPPER test_asm_dma_inputcpy_mops direct_dma_inputcpy_mops +ABI_WRAPPER test_asm_dma_inputcpy_mtrace direct_dma_inputcpy_mtrace + +.section .note.GNU-stack,"",%progbits diff --git a/emulator-asm/src/dma/test_dma_inputcpy b/emulator-asm/src/dma/test_dma_inputcpy deleted file mode 100755 index 02bf8f18a92593a788376914036a88d0fd2d5688..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92392 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 diff --git a/emulator-asm/src/dma/test_dma_inputcpy_mops b/emulator-asm/src/dma/test_dma_inputcpy_mops deleted file mode 100755 index ce3868986bf23b58c191154e63fe7424849b315b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93368 zcmeFa34ByV_CI=WcVBKNH!FlBBrFXCNZ3L`0tsPhmOz6bkwp;D5Rw4V5Re4K1w;dC z#3;BgQE?gfQN{&vXH!rSaT$jh9mQod45+w_GX7BB_j{^(Ns#&f=DpA7ectDNXy|+D zEOqMCsj5?T>(=dDT0C*A%QTJP$7KvRi1`xzLQ+@5C@2rh_>BT1*6@!eHry%oqOnbyC4hN1;JzBq*HuEG9K> zB>0H|_X~KbhX5l8gL0wrlo%?i6B>4DtMUb5WIp<4-`R@4Z;{Fugfn%$k0$7vX}jny z<~yMC9nkrL@Q4-0&x1wNBzd?EZYZv#=6$#qKGT5iaoj%=gtv0rj7R=9d625E>@@ppE zH2>1cZ*6Yu{BuFl>HQ{svf|>sK^@FP8Gb0QO%j~x_V`jp)jvVfr18`K)|k;M_50U0 zP@ubL3xU_z9Vd~e4}MSn_(}A9eG>YYC(+aSB=R*Uk-z#R^p8%Wr~gUh zGfpD^^hxB;J&F7~Cy`%%68c*w(F3yK!?*QkE*ObMn&Cg+FRWlVF-l7pELvPsT3c7P zw63(&D4jL6E_Xm_Y2Cu5i!UgxuB=&5x3IKw>C(kZOHL~nZguVW@~L$>Ipqt>mX_8n zEvu>np}2SoXanb!)mD|4F0QRxT3NPe{It?(14@gh4l12mmtQ)yvhD)puc$6rz6kL2 zsf(*?i>pGZFs)?ll&OoSmKIB4IfH|y%qy!XEvv3xT)tcwxj9vfYfDjHWhqAB(z2x& zmR8kN)qz>MV8x2kC6!BS7uS?kSJhoux_p4-fZhK)v&*N!==@Su-S(}(eE z5YYwY5GR&HtX$E@jUHV(u>SyK^vszfM~yGd=|9jIH*x$Zpdjb0cPhQ|Z#& z{&}I)z;H6Re?I!A3$ac8b77`p%EGseq-J*GgnQ4X9RmfKtN zh$mH5#WLb9*0iTL+>G$+F)q~jvb3rw)@hB-zi+Xh*(65Wl2Vub5%jnS`hf^K=ZWCQ zr^hkJdt#6X7#!c6Z-TL3*h~BZSSIrXKi&wsHc?m!5p?Bgl_NER&Uq>L@kP*CcJPxK zL09vV;tYtO2iF+n6hzSBxX{NRL5~hm4I>ai*GoF#mPF8FBIIXA(9v+A&)f()`%>_; zFoNDTNCaFGL2nm9Kb1V?fm0qh<$?dddEkfm&c6ie4|@aatanNgcGqsLa~3))N^p3;Lj81lyh*a;7=0gRC92X;ExdJ z6mxK+;P(>e)N*jW;I|X!lyY#j;MWu9^mcHG;8zpp)N*jH;FlBUlya~{@Qa9ZDmmyE zd?|5GAqNKtelBrN9S41a&m+z$<6wf|vxsx5K4<`^e{#Ma-|)^kfrifl^J<`>ADF*> z$NaD`{rj@WU-pfFh9iM(-;4-sJL(RYuLa)utu8U7!PXjD=V$q=hb^0nf|j+L9%%SS z^F^q|YgRcZ=Gi+=D39`ot{ypUr&1{bZ{{-vSbu+ zXnZhF*oFXXkRp#FMN<+^FnVHROv?EBPmiLqlN+ELwz>oBVtQ;{HPQ$)=DoFPl^<|| zEy_(rKgc1mV?Dfg6g7xnvkX3s-~2}4vd?Vbl$_WY%WgmNc*g7V*B?moBW)NTYxt3d zf75Wkh97A7zJ~8=xKG2q8t&2X4Gni|_?m`0HQcV@Rt;a)@I?)u*Kmu5n>Bn|!zVS| zq~YTlKBnO#8a|}q0~+41;k_De)G+Xe|A+s6-p*lM7ZtedpIjgxdK_+MuYsH2iSJ^J zYwxF(H*nwX|>b==a z?P^Z{AGCJE_Q1O0tzp8d zJPQr8e*NnazT3GPzPlB^TOVkcepR4h(rT2q-dHvWV6{=#19>4qcUDB|UI5)<>A{aM z%C^X4^e$2mzf}T51DuU?{Q(=k*d16m{Z^ps>B9PE6C}72Uxttd7`JZvdf?*W`mGqm zh;ejt{mR`&{7qZ0Dt;UiP$CMb5&=CG=>9-q@z$mE&~9UyEmj<|#H_1|pFmq~V$-c& z`M6PM!4aFx)hnL}CIyFz)o{$Fx|Pts8rUXP)jwrLG{q%|9%wGsfkwht^v27PB4Q+#*pR`r+UY*d_hH2Hkfj1O2XIFakOW0(Og~wDE+Y;(V z>x!?^eX+2(xo!lEI+5pvxy`~6gBC{`^vKV_(z~$q`r<9hGcAkWuZm_jJyE>@&mZ{@ zMXOv=bY5i9*`cEEMfH9+nsJT6IW?=0(lz!iYqjj@?m&e+xTw=m^nGx;z z*iUTF>Fl0*)TBjP*a)_?26_^^b;Mw4Sh*ifb%f1xs0OK{OzkQ@EQB2BI}*`EaNqWI z#fJm8D!A={yJ1`4z0Wv>M-?BfFFtJ6uRLt8dR?+vSF#}*V^pkZY(H}*P#N%^L^$b) z8Mtia5yNK~%dZOTvX)nHuf{OepB);=Qv;VBZU!mP@KP!#w}xl9w5kZK^YlR7K`w62 zL5{^x_h64Eyr~XHsfc^EdxuZnT7mvacgFHfUm_HBn~(aVLl`K(}$QpVu&6 z;jMo2JHOGl)?e}HD$uYtKua2nH@(bfA5?xPRvbdu2w@VIAQuTtk7Zg)mKrcpZ1qD+ ziPEy~W!9O3Gn5t(w3hWs3%bg#=FiA%SUZ!M)-|%afps@2OlfM^OR<&g1PyEF3SU)K zf(|P#W^>rBT_|iTT2*I>5*AP-y!bW0(u78MvQE2o?FzW(wY5EPH3PfW4gh5P9Q=x{woY&z z*H>#)%?($feZ=F8#qhdy1v3Pv-9~E?{kG2ILKkNEy$YeiP4^YAZ+Iv0Uh`FJdrGt1 z#NvbHnET>48wJ9NUvrM=v#gMFUYH7v=mYLhm1(T*>UVt~^lw=Yib?Um=P+hn#4wHd8jZ)7dAB{-2t6dWh@J2T!-5?T|$#80VYij8@UTa)!4?Nj)8h< zUaT|JNGcRG{&EY^0EV$k)E_a|Y>i*@Etd$5)}OSZMk{*?%h}a@0Sno+wg95iYd8l6 z>R%GPE)$BG;&N;4)@2=_;rftMKS;hVLwB4#CqD$q_>AYCf39aY-fO;rvat=!lHq~6PRhvAb$ARX&e?x=W zGy}V;%D{$4K~w?q7(2i^nTpY&=CazYCZk===OaYx(Xm_li}eq!s?kc+#heu?ri(TK z#dtI^7=WezpL`uO=ylw3=@I<55b@ryl~yQwD(zD4+Zd8u8 z1ftaZkoAHomBAVxZW_jx*3*0rn{_`|rxk%dNwPYrJN?i&-p!%uAtgB+NFB1n#7$K5 zlT^M{UaR?HF=bctQ-IAiMAn^sG-BCSe>kCG<*|lkN7qe17AW5ysM^|P?V&Sx|F-R@ z)$q=?e|O2+ectYhhJ9JPabFR$uJ~9{@zMCT_o&ubtE<;&)lXtYcQwCxSbPjQ<+MIh zK0850*cA^1g^ntrHX)&rVWG=JXd5)0x3%K7pI3_P+s%Jdd7&^hEIXaVb?t9HjYdcm zsRgCQz1(3RvV7e|H3#m0Cn|f>nX}RIXkCrg^P1sOjPwxW9?dv-NOBAeF|N{#eTp$4 z#8|8u&w}xCgY=nQ&9f*aqasZUqPyNii9ie&hP#>zLK3d9L@kMp*1tm)XseY|-^;Q> zM%G{d8C3pHEePeJuhIG{l=Uy-rd`clnDr`c*Y;4>kHT50Z&&dF1D{>RUntP5z>j$P z2;xz6JAh*tP@KG;3>C6e7xFyyHd>E|7-wn5Jz%^n*DC^{K)~^mq zR(~k#6*}u)F!U|YLEiu9`=Ns$B3)m6z{E|}%U)=PO&1?Kew_D2<@?lqkm|J)ueSBTDG5kkCEp zA)yW+Vi(0&m50ntp@NTJ|!vR&r7Zi?SUrq~E zagfTrB$WHl0adw9!Q5Y{+$%%5r)pVr!=qn`2gZeR-=cGWLI%rjR=G!na`z7BejY+} z?=r@p)R2y;TE`t=Z$U#FhdP6w9O4hu{ENvq!HvvF^W1by`so zQh(kT&W%071nhGiZCLgr)vhzvP5-gs1N70SWKN!bw82GsgaIeZP<`?YK9tbhu!d7VGCq4*eEZCCR=LB|2RS3&^wQS%7S$G%ag#9%Y4l4+5s-CcmfQ`Dw>7QV~@WA@@uN8lc zpg8FhP(BWm&AlrCnJ^%Ds=0f8Sf=Zf?P49WznK{S8O1Dt;f^In8j7ai|w< zZNRFs`2EI?>=m-!&{OVKXt*jBJ2D7a5u&bmgfK3GbsbPSa81$8si z7tDm!jz&cXwuc%a(Xc(x@NuBw?dAkDtyXr@`d$r-u>=V5gLOh7%MPG{VBw^FFsK>p zEkJH~$l3In+7WVA|qdAhNwsm!V$- znKSQJn3LOJMBunRU$KcaT`07b9Zw$hS%tE{|hYq`b=xA(dPHatJ$=gl25$87D~}XFlO3+P ztvna5Eo!Vh$J?CkwQXqrArIXUu6)DMtBQ|d3`E2P`m4o`WBuU(Xj|aJO_4s5jYe+i zp>H)7zYWNaun!R7LQ!@#|HuJQRJM5Xm>KCJ5w>4;!qHQ(_mI#{E1 zEQMWeS94#Y+z@VY3t=_a2|aS`1zV5`RYy}%qBl#5=v60<8fIXJRc*YR#v)WJJ<>`4 z2c#1Q#Tv#{H7I(8j0yKQJ*7Yk`ddk;3D7TvqB^Q-yziw%Wo>_28!^Upts>f9$zgv> zk7=l*bw1z+X`3iKTu~lqF(RjZ(rQEw zfB|UYW&5BP5W!J(Q z9|1=-enUyZiOx~BHQ>Ql{mOkt-2h~unKX~1iRRHgXl*|0kim~`E`xtR?QE&h7iwF^ ziw1`H6B>yN`W8VeA(Uxdi^_Rgw&sZgsF5Dr)qI3~a$PDK2Sx5`{!~!*O;iMvoT996 zIb%wZ91*)9&FoD2xpoPr6PP}+`=JO-di5(07}Q`iE8Yh^5t(#!g;C+YapLHzKion!4BC%<#jA>cM(RMIkLqox z!BL|wIezmbkmV^>vuf1QA4wpHm}OhGskA4mtomOuq$3Pe6A(HlLjoKAgvQ4sgUA}4 zXe%9x<0LXZIYNty4`9)Q&0}oJ;wdd|>a6BVKa}7sjsFzV|Dg|}*=g*qW?LRoa52KJ zj8dcvWBO`oPRq*H>)idTTe~>01oO8Y>1(N()aJh1ll4|H>i7ytz|35U#x8 z6I#%D>T}8ir#x`V1E)N2$^)l7aLNOxJaEbb|4R>;c!!Oj%W?a`kLklD;-kr0O1+;< z_FB#0`51oqK#U(g-QtH&-}pIwa)~i|#*`dm%9tEO!Cb&x;CX;~fCYdB#>gpSjFB^@ z7^CoihA|RgB%pj^6p-`f89IGbFZ?G>E}80^wzRCg(wAxYDr@SNR#nz!4T;ubhR-*< zM|H*7z8P>Y0Y`g2hWKYKK9@@MSJ7yFeH@gQ{m{X$_y< zd5v%E$ng`0_=;C7sVuLntndx+En2*!HdvN#{^F%Rya-xTH*7#(--5+;J}8SeekUL3 zEpkRk8?e(?Q419-dQ{KstMws6Us6jK&!3OCIca4?y?iy5l@+z6OO{ra;;r0@VLc$M zNqA8=NDF2N+fy>7xZjlGkz>Rban2B5WvFiO3#+eeqnC(5Nksn1Q>Xp+3c~qEO`JUX zw0<+DjGtEg-Bn7wnow$I=0| zs*b2l9a{<|Pgzz|Q&qFTC#3%U`$rqm!GU$!V* zA>QM=@YQv-@TD}PuAz;GtKjYWAw!lUAYd-2E?c^w za;dLwA-u4tY(;5p)kT$=S%!&Ey1U4W>DCWVJ-z$*@rg)3{rd6a8Gv5^765+q*74)3 z@oYc!?c>Kc0v5k}{Prxr zz+nvIs$IvA_XIS?*a_TW=4r$GL4zyhlY#3PeCNJ~et~>OT*BD6)Cuv<1>V)hh_s^K z12ei&kh-ez&BOBsdC$E#F5wE-=-4*nQK05u1K1P%WI!XxpA_alM!EmM-vfTz|DgXc z_#W_c07Lp0hV|nZmeKw{@cV*K`{qaHcbzQ?SigzjSA*X-l3yOyUk(0>|G>Wn{59aq zPJXcdqO_EK$Jad!BR$w&YzKb%Jm*0`Pdz$oP)BjS+jU!54<9qW4>_DU z3Cm0m^OG=6-#rQ6hb(#E?*qSuA6UlBu#TCKX?x)KahwhemjRheAwplR0Ds!+7|XCF zWT$(eNe$%R3jUHejvw!bu^;AVxP$yH;IDk^`0*yNLwwf_VgU2+2mb(U#8Is<|4dE*px*6{jtc4Vq5M4`;G9LI9@i%!z90M>|8o5J9#2F$?j0Tq zusyb*9JcutNPCha%AvnjD0_V19e~^ikefxh-=HjLkL!$3S&yNt82B_3G9iAc(!o0K z0Y3@+?veaiVg6z8Q^EH|@@ItkF({V&iIIHQ=ur86!5;&DNejN}pe%nP_;ujtM)Iq| z`m4de0{rYq{`@fi8t`udzg;AMLYV&;_*a3CW9i}gk5zmhNPED)0Q~zR`SkNmVI4;x z^Alu#&xaYv$2Y`(1HTe!&qY9j?La-+S3dC8gEbU$+c?Ud6Ct-MT-F>k_gu(~hfGM1 z%P#`-*CpUz4gSgEVH5b5fPb=h_#*gA!S5QWKVl4i1b!{}Cu@&m;I9S$vdH|pQ_{YU z2q9mBUmVH5BwW9t;KyLz#!FHW^-}?x`R9N?3j8T8_$uH_{lTvTKR=RR9oF9jegpWu zBl%@v{)^z>0RG8h%tzpF1^=Y=2mjsH`CVf|?b8wS_Hpn}X5Ucoldvv1S$~}az8CzH zweKb1{{sD|N7m0(94fyF{7mHU7s;=Ps6Y7m;Gb+h`w0B8;GfL@$G~3!zT5%^`*~eh ze@9HrH-V2g7b4m>5VCJ5_^*Ngd;Vn>yz>-QY*iVznCNIgl1t{{hY3vU<3#*i(Y5if zCmQS8Do#u`&TP)M<{1|&oYLMQ)<|QP#;NYq=adIddEk@>PI=&z2Tpn5lm||E;FJeW zdEo!69%$1)kvJbeacUa0-@hUS5I}Y&z$cUP6y2o{*HON z#&Js`AKXvKXa6FwbJlTV6JtK9$ZUxU9N-@4>M34S4s)$mLW=WDoJ!z(nrS;Gf3+^pei z8h)VRmm2=0VH@2alQnE@$I0-yeq~>UhE*Ca((rr@muYyRhATC^OvAMrHfY$W;RX$F z)bJJ!@6a%47k(Rgh%a;cJUr)L=F1t_AHS2_Z_qM9b64f&^&gOzrOCk@3UW`{B}vtj z+UW6F6y#SJCjA5K27LoE~6GKB_C+-7qR!-Dd~<`3U8}Wqrb*w zZws_Bt?*f`aoKAJze(c{iZtx$I!ScS;xa>tiKCiFJFZ<8Hr)m5Or1|-q#<}0iPhLO2z)ZPf?@Z&}HNINo zK8LMS_-= ztN2qizgpw6mj~KC8kfCA;BRU?*nX)4RsLZ46Eq&=-=guLzC9Wb`tyLs+v@Va*ZAog z_vb17{WQKv<2f3?OXETNk7!&U*D(6&dGHJ^ze?jldo?)(SWi9vWdsMu|KfK3kPPRb z$@rS9N8;*|Im&C^gLH882S@d6(8AKL^O&i%jIj!R!!k@ST~xg&&M>+nVQ`|3VM2fS zhZWkJ-ajYeSJi1`dzYPxMs4r0Z$Mqzd+lohqQ-#ZiAO57NBcRHGTZzGIXtVpAoWaP zM$eJKNcT#+Sd>_shk|4zgOu5E5ZTRRkWx5Eyk+k36E!SQtRfz&Tute6bzV)NRTQ1nB%G&a9oIn%#JAw8(wVy2&hZLx!s zE@!$Fxnc|2u}IHja6xQg(xXhL4@Y`PyJwl+hUH9bQPLYskH|tg(3Zt}UZc!-RqiyJ zb*q@=$ty(qwsesxK^KZ`Qt7oAez6!3{kC)^a5rp$c;MaHEo6IVl7e5zD6ihmr!Jn5)uzp)+xy zO20t)p{demuBH5N<>1>WKSHJ7qP$1%DU{%9JJ8{e8M@l$IS&iWcb*YA$k`^az*&H@Tx|=TT7g5HO9d7=>jVyS zY=Og_b^=E@T?G1_J_1KMp9mc7>=Ib)uytH*$2uPi9OwK~V8CHxxY|x|_y-AGZBKLV z6FAX%Lf|Cl1%Zr9 z&UwE-@%7Fjfg2nfbDS&bdZ$p}4bF6dH#*A&-sD^_@Mh-;fwwsO1m5a=CGa-qM}fCH zX#=R|4#zLB$(bYYPUlL2cRBY7+~_{JMR%(+_N z3 z3(m~~UvxGLe98Gh;LDDgN4Zy=jsmwiIQR;DhqFN7PUlL2uR4zje9d`B;4bGofx8_B za#zypPL{wooCyNobj}mF$GJw}Th9Fg_c~iO{6yf}&TkraLO6CM?Q@1|I9uR5&P5vD zCh%Qni-vy(OipslEyPDR64$j8(%tY zi9sbZOG}qa!Ze~@gILNxQCXIDCuCccmdcVxC;rkMc7j)l?|IbsU}R}^7y{(h&^{mIt=yafGns>5@Jx4o=+^eO660{&UrIvkr*081sHg zdF(0#6XSexQYsTJfaO0ypNTob{X7gZg`LTpf|La@-N$hzMg4hwKYLT@=cvICXZ+D8sl6)N6tqb7wf? z1$G(w$wsgq4YBv4Qd;lD_8~O;D`345V!zJZX{m`Y>MHw2_+~Gw_c{3Agk+=yC3B5^ zPiHM-LvdStl|w{N`a@%(=LUNvis=a!fAve)ZZ1NM^m(lN&GsMD@wgSNk|5h;p~li2 zbhwmz6TZbNg?-25Uw&Ck8SzbRV<)hmwf~OX?~%>pV2Wi#?MCakQl=+t0d1BP^QHAh zR|rXUL_PSU(kM6kIkZa38QyE)D|*Ky(dbehhgK{wJFSQA(a)grQ6GZoO8Ft}R=^4E z(Q#AHF#qD#1-_D8)` zwZ<|7WmSeT)pbT{jcM1$Dt2W{cFf&a38XGJ?Q%pzdhu%VKVx>ea0nYQF7(sX2Gec_ z*OBsvppX&sa2K#{Fl{%AXO1_5ER%xDh8UibPQBZ-=b+kOLFiiwF%elvp8^OyYTCKb zNL~_Lae|Pz=2_GJ7c8PsVMs`dqeG0Cx4}r=VcO54yO1)CLWe;#y4bAlG_xzK8bx}H zhm_Y%`vbHB371h=d>!*N22tu;rhRp9<-2==dX%!d%Ej}I)OSsLJjxMW*P`W9KQQgS z0Y!K#n89fCHHu-I8Lkfb&UdiI)nSk`7=Cee80<^|?9$Z_YxB*HJ0Rp4gh?lPio~>K z)#wgp%-PW?UFf!$^5~2h4tZRRl=rYqH)9q=4<;#5NEdoYIxl(xNfb5ae1!7Sa${4r zQrdYaW;Lj@_~RzdV^MFw?QL>FcyAn-p$iO4uXp}I1Jl1g>{B z3*6xB5qQ1xnZO&IqXKVox{qMKTOGf^+npH#?{sPf-sM~;@NQ?5zjN$5Dw*^PeY>R9e1BQCl+RyutJ&Uc0jT;NO- zxX?LIV3l*Zz;m5j1)k?TA+XxnCvcJToxt;**wNIt)EOwS&Y31~nX^pba_44&E1W+H zywKSxaFz3pzw%u2Wbr5I zJXs0o(B7;Ya81Z6%Q6gK7S|5`teuGUtNZT5BF-@T-sPi?zOGCFPu~PM)$sO>CzjCn zJFI66U*Dhm0Qj@IgSaWHJwsvN+30m1^PJ9db8i~w6u?Go$X^|U{r{m(@S&o#Tyg;J!6z55LrSnwh0fE!h zlDP96XNTaG&c^~5I{y|}<;3Gwtn_aQxXs5m?T5}7wKdjCw_)@E!+Z{Uce1FL#sj1G zjNZRN#y39u7LY~WS7R?JG>kzrAaM}HA4Bp*AZeM)>{z&1WMWZJD!xkRr87e^kJ;Cw zT#?BEe`rVsreZ(`!_D09yBo&WaFgNr2=459U00fN@sKRoJfdwr0IM=#BqXAJ=60lG zp3g=)!;DHtYcU)=3t}ihwD}aFjYY_!2ysz|43f<6AZ=U{=&0vc!_{r5)5HkUZJ1Lo zaJcg_8>gG!=?#y$x{Y)O2^{6{F4xsE`xP3k zICE^?tE>ifU65nGPIeg=Fjhrs6-Mt~5bgu|cuSG=k<`7==1*;J2kWgMTdhrce-FP| zW7Fu+v}Ck|b)NkJ76=qGzsKcQ%Ksf)lPRNjC3=7rNc#$u-j}#^y+CLhD(p>_3js0sb6F)KH%FHkmS;*^Ipd;~_Y@3#ZZa zn`ms4{WON=wUD?gBrnqgGZ|^)b0D+X9*zJhTAl^G<} zI*$>>CL27Ljnw9HXE~zGJW|Z`$I(sO+-K)zVq*Z*6nx!3QM2MaXg`b&&T8gavH>bQU_Oo!4d2W!W+R2FeC#;;13U_%QGcQQO2xrTA zpD|-u_ZYg<`ze-71M#jTvObNk84U^ZHKdF#8$kCyVa8HR6q#M#-1fY?ytw>kPPcD_C!a*r$cr_q*o9gipZwwguZ16_?%v({ApISA&8~-6K8o zd%)(I?Jq>NqF9}R`!P29nf5sd+9gn*1KMDG-P2i?aC1$2HhL;epA61)a!bjT4r*ta z$uqrU7UFUgDDEWlVFyzaIKQ^wDg{#R6w{ARmXLx$kFS(#R)y5hFuy>w z?hj63h%4Tf!e^T{2F65i=7hM+8)09Sc_Cs5Z^$nViO_moMmtn+vB}-K>%qP=D56?r zwV9HQR#|Q)4TIl|g4^Kji%k1TNO5@V1bjP~L+*r(9@j(l<>rX)xK9G-`w*A%N+jtR zmIn4UroA1Z#^uHMYlp9@w>T~oH{0ySl1PSY6k}A%7n#tJye{T+$a|Kcza(EHA*c=C z;S#xuNm$~i+K-v#8Q=mskHG>-&`?RRj*?(+l0=7KVgVBKSd=Sf9-@kko16?xy1b8m z5@njl(3XZ`ePY^Yz#OvPBI{lotjo+<2r~BPrhR8G#q-H%ea*~b{bPPWjqSsxeXAcM z1i8|&t~8m5xYV$1q@XS`B(|B^91c;GRSJGEZT{*d^UXP#0x>7%Z3HL#sA+G^RpM8i zMBJ$S8C|fQ>qZRh+aSZ+Wp^zMF-{}H?(C|9ho1uVZ9sOzB*lE$p|=G&XmVlu+oh`2w7og58V0_)6)fWVxIUQKd1S+pn7PBqxeu zD)U(g{+Kw-X`qybGG9pprDD~tFX8Z|;H(UBuOU}uU*fWVz$h?p0Q0_(0JEDLDAj`& zUhFE*H;m0-zZnvt4zWzNm&mPly@vSyF=T%D9XV+hRq72c`+Vk)LY+F`t78ovhuv0yyZ3)W%C2(TJuwU@Xb#0?R4|9( zn~3;k?=kK7h6IIc-S&lui@JJ^=Rl{FQ{O7~}OrRoYWt|3!8yD+bGJ7{QT zy$|1q$&{SFzO-qtIiLoC$GHTvx4pq__Zp;R%zY62fWo55*OS6;nfbLL;XB;+eTa&b zF@L2XreLMn8H$l%|G{m)Jw_|*1YURYMVaD1;kL7VI{z^6Mu+nsq=TM!+jk&xNuAFI zuRP4}gTZU>guCHQ;jaX*KFq(EmD%gIKPuGv?*#9@Fu#cQ?RVS1BK(Q{FM+os%%22b z+Xviskzb8>^Y7q&9_DlLW`F6nJ0K=VFZh)_+%u~BCmn)b`(JLm8fzZ21Lzs#vF;K9 z3?&flvERUwz#IbRgcbrOLxhpUg|OY%W8aT9G3P>Jeo)GAZbJCA2YKu#(0Rp#)!9)33u|?mpUj5Had`P+uh{Y@m<#G4^gVcN+WaZ*2Sd z9=iv|0tL;(xcuBI2k&0&*UiCmLpd(=*xWFppxGJREPTZ}?Swnf{q1^>eHy&z8Uf~n zmIB3Pb;j+UpRu-Lp>x4Jj{;#UDey;6hiCV{KEgc$37Rcq~vdd9vy?PZiPQ% zePJK=*so!D%1u~L@cNRk27>Uv^Vl;$;(eky7Kw@EODiS~XZfaOj~@ZvJkS@BCmv8; zLcPA=B=866L-ELI_qOa33!a6e;hkvu|e`4s1;w2EhxP`c(gwopBBha};=q3pKp@ooC%ShtG06Vvh-GM{n z1xUOWlHyc=KR}-Xf#NoHJVv(p5tyF^1&pMI9^gIH#=aIU<~j3 zl5A9_`$aBmGzOMyJ;eA7KWvxbxKOq3Z@R1-Fh&^^T+cw@g`kk(^vnXk!n8hzv#H$m zXYl?JnpU zE4-hb-wvhL+HYfN$2!e`h~mmpY5uL?USi)1ZR9Q?_YrBS{g@@~M%ymPgTYsTzJbbR zjBrJ1q)kK4?$21;krq=Q0sonh3`tsMhs8}(k=YCW$1P+`5$tg`lwlVGJ>WaAO{@`A zQL@gFRj!CUWp6?wB!QjLQbZO^1#8haUb9`7VNfzG6ho#2-}ut$OAPu#`G&0Uj@{hw z&4b)8yaP*;uX74e$EbzKJop1I?z%GiIaM&OHy0ckd9wLrJ&s~B3ff))A+=$YF))=q z%zDKQMq#_V!SH6zg*P*XCjJ=#V-E-);p@H$>spg3lQKqcu1qrWMQggq`xAddG&}~T z3#E#@k;dsWquBPKSY|WR!Rku(?NDc=aru)m-X4X?T~rSN%O7H2ZgNGFG0ApdobYFX zH#fwOFD(`q8BvTQqdy#t%Ma1GnHq_1VaB{0vl*hEEFdxO_vZ5z&;96{F(35#7Cx7~ zhM2$fp=Uju<6`#r;a;7Q(%wRDcT>#YklT7@42*5+EDwG{d$Pu;H;Uo$4YAzsnD{Bu zcVY;gm@w+i=g>60^4&k7Yt0@2#l0rFOArNmAfqXLz0Y=7Z0>Vgr(qn;1i8&lIEz+= z?iIt&bkqiIj12U%J=PSDlDw1I+M&^y?f!yW9GWxt23mi@UIjlX6 zn%LyD=%hyWNkxNr|Kzr&qwrOcg@?1yE@iWn60;X89^b!Q)<)Eik2fA=w%}uhHo?aV zCRs+}Etn_WPg{o&^+@>W(R6%@b?5l%1s$AO6L*l3yclw9Dp_o ziL@-=>DJCyQ%r`?(vzuMkF|-CXUq;O>Dx}9& zJ$ky??6!Ov*gvHZACvw^M<-eXQLoS44`4LXsJ+Ogo&=3qSh>P*_3^mt`(OtVCLJV; z31dt}Wx3(%?s4~uQY;VJUa=CngmW!$e+3JAS77!bCmRWqPr8i6Z{a&vxjP4KME0mw z>=`J-RqFm0-H-Bf$W|FM5wct}+)ud_=i(qov*T02p6V_z75ny<>@{X1V!Qc^+x-a4 z8v{d3UJwa+{3r8+6k_(R$edUNM`WFqM8NHAe&uJ1s&y3@*!0^bNa=Z5xFUS>&%oB7b`6^3X z4dPYS1M#$ihw8#CWmw`W2mCLR2pDX6;7*mu@(soQERJj{eT8h%F6@{a&=0e6%p^i) z|0I-MBDdman{nhb+wo9-;V1S&_{%z;L_h+M^F@{}>|f&un472kB1?Z3F(GTBnMBC! zJpHG#tI~x(!Hgqh{*|Hp!oR|ND1bQC+u$j|u^r%BOxQ^Mi~-A(XKWF=9^1(~HX41N zsRW!QEkJxqN}QT=g0 z6K{*_EE_|oZ<4hK@*bm*;UE=40d_05;kgG&`?hz#LhgM?_iNLh>?j^=^n8XjL4T{m zNTgH#fPk<+JIBk1_*UOeI4dk4GNAa&Mt=)BnzoYPJG z2!-|WARv?4;%cn6&KwTp^!PbIF1M16UElvWKF{ikzE?i;j_hh z4b7T8S8UsA&FKbYp0^DY>@++NL)`#dn#7w4zvbkc@$6PSaH>aXH#B(8gKi=FSUb(MVD%zV@<=Oe{?BE+MH0Y*$J9F>#qj)@1PbvzfV zGAjXZcUJNtWfl+1DvM5^Nb=)~>>Y!^n==vqfm7QLfDA{Ygd>xvVY8EaKSQ_58L6cB z$XdCim8iD~-63a~DK(ivDP9a4KSOAE@1q;eRT?f2X^<3))`d}{_g;E)v$Bt8b|bxc z3A#Yei;Bl{yuvfQS714i^QH0=kM%11M6q_(eHoDG=8m}?kj~IxW=Depg zcgO*gJw~@-9-HACrG5x`(q)*@Bx=|UW-;s1A$6WR=5jz5!SlndCB18D>HjEIJo4Jg zQl&5ET#V10rS6zAK&s-=b}lr7Y-CTWH}8xz7+X5AvAAlLR1 zvHS?Wxlua!&O_7YMhoJxW+Ig2I)ZquY`7&iMvxfGhjEk}D@cO13qc?^PLL$)AfiyN znk!PRfeAnogwxUbXEcyRL44NdsA6tX%nq28X`Sx}(mtL&dVmqdQz&uGh}1bhOI?=3 zNU5h0_bi)Yr8wqhQYG3*-(oe5xVzeezgB#@IhaeiNbv`w4mk@X{|CR5-`fku=PXf8 zeT)K}giJ|^AJs5tfpL{6FlWCQ$V2Cm4Y7}7`+75;dlmdD-Uy^{RM=C9(PYzb?_Vf) ziy1c;9DXC^Jf2OM*WNOO5OA6U+Djx3-7i%Q`iMc=SM8MoU z^&hUdW`C4IHggXpJJeP2UJYllot+4no9_rjmM!eKR|g`?=I&t5!A=Cs%{K}n z%NF)mDFn>T7Y!oHju}GB+nSvSnVl~ngbP<|YbBImrcj7VTi6O>Sc*$wfs{S4FL zy9chM{}Gs+ zI)eosa@5Js!wwI6UTs!XEjQBt>fVDT1QlFOW4c1KT#S^ZpqY97qIvJ<0(`HXf;Vbt zq4i1U0iFDdK}_rn$-J^omGSEF`v+7g=aVMQL3GWl?76rLHvUNE-L|^1AF>v+Y1IbY z1aeDt$Vvq7Me!gBc0aV9UxkIHV6TlbjAh&?6RZkR`NB7`av&yeJ>=!*^RQ3UV3;9W zRe6K*d1gqqs`85RlTfIK4_fku=666k<$cbsc`M_tV}?hP!B{o23K~ND14j7BLB(=( z8*fyKXD?c1otclUQE8q;ZXlqMMq~mV;7R*!AT;iQx_9w)-#s48(}b7jxfgNZOHjWH za@2;AJS&RI^5k+;)HwrjJ?b=%eeqBwXr>n8CJ0~m~cgP+1ptQ-ufV4#W-gOoE}x~ zv3J66qT~?>Yzhh)ihrKR?u3RDC2xTD=OAAz;Ulo9^I>UwU9E2+@C$`lEt4t3$Bo9Q zOi#fE)~ydZU(|9uG;`@u zNvXhj5Ln(y({=RwT93UBwG~b4A#g`4O=Ykp>S~XD3nn{m_L;Dw-&(Pmhz-aq>i zH?`b9Goq+#^z-OahPMpdoZHN#e<1_EDmbXgE5Vb8$t$J^i`1NBW^yhd2cir{1{0HF z%9nV%bS4}WQ(FvK)79l%z)6thH>@%!Yd7TbPS=-^`LLO=7;_Z+!opz~I**vKRPQww z9)s>j&3M{`U+Zl&&xcs%)24eG*a_E~Nm+7jX^PML9PF&i%5=J4Awf7`U zMbp`Qu=~>`EvxBVWm)8sUS10?$lN$$XS*z21vOev#j0JRw>AA zs}HqR3o^(0fZ7%ba<&l{jgFRok?aHUy$Wf6xs5U=Uy0b8zcTq8kZrz$;kj}g4v%WuLj~ZJX6pw3+#6DfsNYO70)6H zoR=YRotZWyl`mdk`=fBE`zHiazCvL&BGMNQlXs=@llzs6jN$6NX~Xj~L<&b(%seik zaEyC9l<{o~m8Cdt2Xv_{VxM2~IA0c~d@DqGK5i(fRkCX4J0hAo4V|j6I+1nc8zYM9orR!MxYZrU zT?W2D5;jZS2+J~g2ITQV4JYox1MWC_nJ<~B)}~2#5aoj&%Pl>DFP>=iOV9}lKXb>^ z%RE+KvUs`R@he7tVNZ|P$#+vEi&~6$A0G)`j=J~Z`zmU+61wR&yuErsAWLQDi!7}L z20e}VRrrRRFSn?jO{qz^I@g=y=+|Fdv2FYixf^yFr(eXT8>Sj!(1k=sxO$L{8e{cd zj>axrF7Eb|D)unNgM?U!%!&p7FNtRWu%1;dWKK&=+eUm&m*_ES#zG$bG^VDDnK#Fy^Q?7Y8{CsAIy zZ(JE|IzNL5mu{-<5@)lv-y}}~Acu>&aE*0s^08(t{Gm%<0L}zl=(U!Kn~tDYc!}H< zaRC_SjJAT&z~bD@ zeuKFm6Mc4{xGCSm5%yk?Z0=3UrY85{WK$EwSCO9bpbM$*nc9O{*gl4o2qurxg6#~? z^Dv@ldRy8MMY_%1%|S^vsB_$WEy?VKixD>vU8QJ?^klxIq^RDPAY62gYDT`Zq{(yQ zK%T3}mxsu4r(1VQ$;~5Qnrc0Oq|#phcu2)74Sc04 ztU(vC4GmS~C?ol?1q|;?bc$a& zWoL+d`Y&kfqOp4Pew78J#?5!P#4sc7aWr$$UE*NA$)&0P#8@lZC{(`J6|RIa=pF>9 zqGWeZlBLRnZexp{Zt{gMRT+*z!}~D?X30gfp5 z+@-3Tg+y~u*iG{A5r433>K;V(4sl&Ti*m0HiZOhg36ccbcw_43Ol+pajuoMeOO&o? z2dl8Iy3~z9Q!a&fx@qD=LE^s<<+^L)rXVpB&5)sqTQt$;drVQUpqvE0dzL`dW+;o| zjj~Nz6knA}AdT-T7zw=Ca$cNoIwdSe`3Xc5_##olHePr`pD-F1o3FD(@wJ*9rV{vq zP6AUlUr31JJ1q%Gs8tTp9G;U;;Kk+{{wTh|Ve|c-gn1Mp4ZkD<8gJF2c;YGHO`r*d zX}FYUK!l8hI;0YqvPp~Lv8x=OHBZQd@ZefZwyB<8_GO5{H=8_==*dcxdp|RsJWn5M z0G7d@AlEc%AsUy(7=?t~9KIdPYF#oCkXZn$KI*y+zB zB6#wnv*6&NppFZ2O#YIKk;~^>|@tZ8LR;5kqrk(G*~>A7FIkL7~<^ZDlsy1BnyQ3ALS_i;Vl+N$j7l4GY_5Sb}#~_}sR9DhvJ~ zo_ld^m}zvJ3GJ%8b>XgcfG3_H=1s#%fQtHY1nS z^5*-g98p?*%qu+IyLSTv(W4lh^erS?|MHmG)N>-jQUEQqQ#q~^YizAb<7<fX=xeXvGnn^N+2~vvgP&4&U_QK zD`eb#aEYiakCwImi8!-A&n3f~uACxLLlcTAEFy*i!2Cj@a9G4rS z8kIFiqZXJGf;H!1$>^fOHVisCAXpDW4dgBSBA(!1|na-B3v)L=nuCW%Bz;m%jA%Hl6 zuD=ClWka8<5EtnU-J#T&&N8wQ6y^Z7{vkrMsS?y!$%ba=%y+_TbmS?;*1y4MHkGm%9#Uh+>BV^NZ0@2vQ7?vbXLF%K z(U*8yvGvb7noWO+RB>Wo3LQ;Uo$4JkTdP-{>K$`iZGqfHo9^_+R zVYZ9?BN*%~8wJjdy+PniL=V1rxeNvE%<+r(nJO2GzrJPh3srH~m_8QA9@m+#3vYy! zYcnp=S%#ukBN(vk>wCzH!r9dF0ObA(*!ovC&8DHKZLFx+5Qu%4sy6uhk@%!G$ED4& zZ%B@v3Oag2>;!=&{n#CYa=|I6%|qQ{*&$h3>Bk%9LPO^ij7iBh7kS0f|3Q}!OWQoy zO`b#zXR$l`;;|CoCHQt{hVCrX=*UC!t$#DrZ0bK114?RpiJkQbs?J8}HZB(Hoy+Wa z6$ky@QU?OB58{Dbj+{}tdgz<-0e@iZgJO3{E~9=>z|jKU;jZ?v4E1n;%z{CI&@A|D zx}F8+GD}CEseN;RpJ^jkuIy6Rz~U3n$Exv$rH&e3;iZnkPh9GB=0mL8q099rT*ROE zBamb4SK+6?JiYl0#F);)I-%_M0bBndtJ(AqP-8_0n(96v>{7#csy}ubZtk?{$syBw zgBm)=FLMV^IH8@}zS#~Ae$8$d9Lhs^3b+#Dp|KyD_52J1IQc74z|Rl>L}Gw?<~WRR z_^v_L);}<7Hf;b^idB(%q0yCZu51FeK-kCuC=?<&F&2kU8)9Wtv#Hty0|xNy!3XHqx^sR`$cmoxGb|% zZ>JF|!uVz6OLz%No)}(Aba;W{T-uz_+GgnNPNNZh_}eJEq2Ki(F5(|fs$u_3r}3}Z zIP?J6`lpl4CVK`4bWF+txcx*qCXOK7< zUy}*r%za?YL24=VzK`(GIexqrF(aO=q!&%^L< z|E}Oj3c8;jKskil`ACnGBk%orDE>~}5E#JnF`L|&lS0K|OO`TG0-q{u(I)Pt+K`$9yc;7|)aW zZO+0sc)#GHn_g@!JM3NTHhc(b z>)#zWn~t`W)vJv8o{l#3 zEHkoM{9f#c<{)?LUxPQB7?EN{J0kX%ECmt!2Z4cD15B*5jL!TaAc?EBK@+$t3VUEU z)jWaH)&O3OuL@|Le{G9n(Li1I;v%LqpD~;wO+E1Y4}j0%+nqwj9r!E2*sh5+E$-u% zWcYE#y`Dz^Cd(V=AeWhl(B&Cnn(y@T3!_^<%u=3IQyvV%`fdZ!4Llu!__S`b+5=Au z;xoIwn*qF&8UFyXyEasG ztcH8TO{->n-qA00aP&mm9dZM!jCT^1*&A17{GcU1(Txy29Xa*)!Sd%DkrSIH2Pn|@j^o)_aWUIZeE2~TRU`71-iAmv8uo?+be|+tuivoeM)4S z7J)tAX!fR68MT@lR3P>oNmALbx>W4>PNVBD6??uf)>=yk-O0aAKVQ6Q|R-_ z2N$eEtu=14y57d7v(w9KNWcoYS+}?+_Dq%F*o#cQG10hBbOdXp(hzL{T&)JpLtMY zroTs5^4SMBUXpAtb7mf1`=x6SZ~Ps3_(gfR;4ij8JW2&d7yJ~X>42&3f`@>`q^aD3 zWCvC^$=x)vchEG#6R1(}%OsIYp9PeqFwrzr_)SDWO}E`d;~)yFse|SwUP7YaM@}M_ z+9rHi4{!Qp99g%o(^(H}{EaRBTy^vW|3J7qc(CF%s1wbJBv3@~YbudT`}_sNSo>cn zMGy1Evk#sn0r^!)9F@d2lCq%eBn{p?1@8FQ{IQLTSlfrI$T~~%_-`ct zB1y5H^0T4Awo{9stXm#8ZCNj7NEfZy{1wR!t(1R}OL^pH=d=ACo|q;@`$k%*dAB6R zSd^q6MA9|~701HOWuyudKe~}tZuZrs{i2(8LoIELrpR09QO7P^{XH)J@kGBEU8?W%)>7JSU@0x^ zypfiU2EXkSxm3g(Sly3K-PDb1G=tUs=+rjK1?|DQk(QPQzY-L=^a(d-+MhG+=Io+X zr@`+HMJ|2a&FS#Atc2z+T7eq;R8i#8qKv8DNt0Ap?_`&*9^B=$LbXd!x6(!3Avfo^ zKj(NYCoNtLem*I3se+trK4Tf{h65;&y7q>p>dYosn@eL&Lk+z7vcEgp@#8Lr^oLacfWOZLPaoSK-DcSRd(QE0I*gDUn*_x!u(Y zlcpO0dJM?|yIY5r?_C`Mu;mth$$~!0-_b<6n|El{LsW-_xTx8x%DJGYbgP%pxO!XM9NY)G)3$PY&~@ickpv^-RO{*1lPI98hMoHuJKbGKQCvLfL)kV| z#Y*fqq|xIkMNuieKtS9_pnYyNq^nHTx8Deo-qnHfcDPA^lSR6sJEH-D*d>V=bKK{q zpxu$k5EJ~KE{jJ_M<2#V$68IQm?x*fNI2X}O>FP5sdsPpcAjqVKGR@&eutMfV5mxe z2r&$Jy9d7P-KcDhcJUz?8yySI$8{Dmu8o|efL$WMsI8IdLFN%1x8Bx4_yJB2EsvOy zQCv)QJ4{cy@nnb^L69Rnd^#dLU^*5O=%^Q|AT)kMT3Ev|%|~vztz+BnjukYbZ|sQV zQR87Z7x$L#ixI)A&^5K}xC`RJL!eRA8(qI!^CqhJoi|9wj4c<0jQjZ49jmEVPV}LL zaHbpV>+z(!TR5p zIX3hr+)y{6G4F|>Ex*Xa1g4)uWT-P>&thV4H{)^2U#4{5dm|kY&3Pw;w0ls)w4`-A zd++L6!bz9PQGez-nP776w_1*D@HxFas%toZXFz zbwmlF_n1OSuyiyNrFyyAS$C9oG@b6)a(;Kj>rD`nZ$kKiaQC=n z6(P^?wh~5$&_CswX#x%ltM|3GLVW2;U^+_P&gdtOXsER8f1qWXc~}X1IEi=Qn=ar2 zzCi~ohA7aCA~?OI^D&&czt7eBABu4k`rKPZs`&+`2$0x)?t7W7>Ppdj-yytT*xVhU zGfcCARDn$*)n9TmatbXIF=gln6H~)1g$5QE_n{O5JQU%+lo3e%_;AD>f>IHB-^B>$ z6k7I6#z$&7xCT5FVA1L<#J@ZRJsN>UYGG#8Wy6GpcSqjj@*=u_jGo60UJ3G^i10#` zMfbm4S0PQS?+rAFhRUy?wANP8si_JT@VYkV5yH5qJEwo`#g9G#~a=$UodJMHcA%XuvY)8Bh4q78iSm=99H&+^1gV`Bdn$ z`&qbKPXJd@z2{KmCyN(Y5&Qs(Sd+POCS7&T=ZZ74Ri{vzsdS|(1w6v9C2Br{Xm5>d z?cB5Hwv&6dcG}Bs+LJAJm3#LbK747m-ZYU>ZQv)hi^Oc#Ga#v5AQiS{Ln*(4?lb=8bNg~;C)ecg-84q%%&=+ zSEi7jx!|pt`JAG?I8x5;DIY1@o1*WJMz+rX+g9FM#zOx^(L<4&`L~W={w-LK3X%OE z08W6IpDbQfLl9l9Yh8=#jpHwmzlZSmG5kG?zyE97xnrb9SH`Kxm$ z>FO78Q1{T)91f9p(c#zW_7V=wzeQJ<=|Fn{MW3U?AL0=CFvZ^S zIy_6T&(hTw>FTRE*ndt}FVo?>I4t;Q9HK2vI4r^;qBesZjhScv7<*kNuBaVHqEYlu zQ&ib_YRpIb>qvg+1$89reQL~7`^B)Q#=f=x5>K~XR=uaZd5zs{e+N%j#=F!~E7}x^ zx2sij&Z?Vy^)U9i{kK<3tx#*@FR*`KMby&x8MWdm6{BLWASw%|i)y0);%#c9hAe(c zsoT93H0}!fV{sJn1GO-ITG>avDU9>O{@wZ%HlxpYQtat6;mVRv3>YF4+{U$NSonp7v%&3^2`cD0_aj^NQI`+br4UOY?KKX~AI12`ps zXm=#qO5N9_mf#J>kp*6(w*Nq>=gjLD(`xZYo>Lp1QHw7>qBehC zZNID*A5jY)Rd0Dl^;}kW?4{hzr_?0QP0dfLJ8)Lj=2JK|cc}$9#qo6E9ZEf-(U4W@ zw7L~=knatrl{%xAJQaU2x?#Cm9se@uuv;zsh*FQLwkdUU{HSV)pHjQ0)$(|!S`l5p zLM=np-!{*~(YLF`@pWoV{06k-lp2^uYdb09()f#N;W-sKt+sDd%RWblYU_SPwNzDK zx7v0^?Ww9=r__PHYF}0@zo>g|NUf!c9#QQ%_2wrv&f+P9u@1ZCNfmiU?b{cBUM+b= zsVCLS)9OyjflfJ0=PKVuU%=Zt)Y^{%mulTN(a`1U04g|4Nv^2lbb4N``=nZh2VMBv z8cz_cIsUThZ$76MzW|te2@K%&xL^@4*oh=j3<)a5#`yr9t9;w^f||me0!_Rpf{SJ( zUkPM(;F?C!4xA|~)oR5@)q)q*vN@$LtEFdNQRCkP_3Tx9<5TL^i)z_rwQ3rjuve{l zPW5y4%PJ|^*C0C&J`{hOz^M6D8)`mkL$|06r6ejP#H@)QrK@I?$xUcR>H$g}-Pp$2 zngF-~85Ye7mEPN>QI1}*mm?b?`Vk~S?_E}lPHW13QoV_aeNy$|6g{QZ;qgIi(| zIiAJIep21q{AJZ0*JS$|cQc;dq_$Dgi(vLRUDhKNbzK{Ovt}xc!1Y&q=Zg3vuc(tR z0@r(0KM>e;QQdG^t(yw1$d=|W)5{1C#3rK6_677;gL>F4c*g7vHCY;8uWtG%=ZX;0 zXe>we5>9r}6;jYBqz)d%nR8090ITTwHjQf( z_?%)A7399ARyIqwzNR+O%c)m)(*>iw86t!z0+iUUBB=hteX8xb_#^oDPV^JAgZM?Y zk20VDylg8jBd>uJJLE+dnMm-IK-=jlG7RYqE%EiUU~A`aXq!WML{4~251WwzY*a8~ zK;w91qb@i#nG|;iGpz?Fc~q&YY6EAls#T}e+N0{te*@ntm3~Uybo5u%%}3+U&@Bp~ zI#IK6vT?htIle)ypF(Zc;EyTY1|e^t)JHU7|B<?6X!pLxgpt_a zZHcJ0KTzA_qUY#Zn1~enFQH#;eopO&Ag!t+d)2V$0y>!qda+-DF0uNDYExCkW_2rd z0uw=FzuFAq+4PE9dPH?olU`OmNAO{6x7j8v$Np!N@S+}iFRNai5M9&~HSt0A-=bbC z;_pE7rlK~A;io1J%6=7qCI6&iFQ}H&YVnj(q5)xxSrlSov+UnSvK3!eZxW?VaR-^;xqtP`Q1+3Y0_p@1GzY|!YY`t;Hw%8ti-dhP1#Atst8i61n za_&|g@h&Em0AZMLO8b8!?Xn-L&Wmb8RV}$q#o}kY)L`N~?T;gtWp(_#>i7wT{YPqu%u7(}$>?|}Fo`_*#v77)8V@+4?|rTuQ3u)lryIOu! zYd6rlVz(PYqiUK+ZTnvX%hZEMqJ7>DT**b8ZC6dtdFx<;$nE#q5vT+E)M`qH(r=wr zYpSZ}5p`Qttq1u(;>~3u-|dHyD;htiR?MQdZ?a#v;#IXFjt?YYWL|u*UFTWA)M+C1 z?XMxvrue6r#M!uM<>zKGfc+mpy_irT`5W)%68O1UlVE?2Od~K%)_t#NVYQddrHZC+xq5rPB1AT6$UCbR~cSv&6&xF}lAkeuplxiU!?izhb?n+L>J2fS0KK z;RpAs*lEoPbhFH27W>nj`yFc8n73su=o+&~#{ONjZ1-y#DVluDIve{vU{_0@Q#Wej z;s%@LK=!{ygO|nUFmj*=uDYmVM^qbBmiTF1k{;e>`I7x%ykw2N6de%*`WV3rAi`Us zW-XQdG1T-Xl-C1tc&yE z&2BxV2Ct}C2baY)H*4AK*W;0w$*Z&MR@kNFpOgH z`^Yj_aS!y=7nORZmdvaKtQ%1coIKsrr9!6rzO&_0u~gaF+dY|?%jCQ7%T>doLC!9jvm2$d!vXHV4joj5fFy7wN)z@PM-7}th%Q-VLUfnSV|5B+uG(Dag znocHn&&*b)orzTXZ1VWDa#cK~SC3AQC!Azb7n0bK%I8aIdf-&csa&-( zq-(ZI;F;vu_%^37dUOsM#;d8~cs0@M43#R*!9q2gPfbwpLbG~3aX8alljrPg5?YyM;ozQ_FNUPar96a>Ss}gLu9t2 z&p|qe@ADY|cF{f2T%yO&6IINR-T~?iLJ$({$Q5(d9I$p@rtIW%l`1-`7vB@06sScd zHJ5Sd^8$`DnWS~oIY%2$#(i~2X80V=o}3`C=coz&!HIg_O;&^ii61G#N@akZ33C59c0 z65X`GaO#^(Ra3fIbwkb5o^xdk2Hi{5!b~w$sEs(?b>mJB1fCTFcM7?pMZREXVixs6 zz0X=HklJi6eb&jG&k>q|#BGRwr*14dDU7kgbn=9QzCN+d89RC+0n+W+QBD;nO9dyD zPG>3=cR)JT)RaZNn4T?{oyvv6L`e_&AYo!?V2Fbyq|zBDHGBT51O2ET>BLz=cn?P7 z*iqCFhiwizE>orI@1+i<{-oh#rFE}Yr^}^tg!QTFG?+}eRJN)h1auAD4o8>+SIm`<61-eMTm0sAGG;0J)43N2obK=4b#uNrbVJ1~}vPICEjB;nDiZNu! zlsm960a7^^&L_8D8A49Sn&Zw)xipiSN?`~B1%(oLi*P=dm-Y4--W8ypo#jj=Q=ZEt!6}%2LZl4KNq!p0 z3#s4bHO zxC7TJL4(N1({!?r8krnTj~-2<-przzIoDVK1aM}mR1V>*!yvX)xoTBG{}^xCl77=! z3z(bCSXr(_x_Tbu=4au47L5@N9<@U=s1Wu<{X#=0o6CU3f|{u*pRw`kE@Fo~ zvhV~w?aUTyubULMx3^lJ#a??F1EC@(v0F2B=fq4+FVO7TLvwvtsY5!6!5`}*)5)Hl zLDET=zE1 zk5*s(Dddr>>G_}TYv+GNwrS!tbe?9VhlDk;z(9Iy?%YfneP#wzC@JZ&%z|qe7|o_; z^CXVU5CC7RmI@PSHw{~s4uw?tEGb*er2?E`d%CQ(odU5+qL-qRu7FWx(ivzxmUB?k z#E_s5QUNc3IImc52qCNlUq>IzhkanTuHShK2HFT653I9#tx7`!tq`a*IDj$;aSzr= zW6C2JSq`$MmN}nE&yt`M@(v8no_v*tBy@WVxFns1G(TuYP&YD_Y0~xd7#Tcq$~l%C zIdtMMUZqv#|BuKkW-^oL`x0b(Ig=tIBC#9xN)F_j;gA4gOzBLaFj^?E20+zD#UK|Z ztgP0KinIAVC>#8Rti!=ATM|3l6TMx%UEA6_3)R_T=Ju&fF;mW^aht+;xc&UD9i2P+ z+B>J(JC7p`#@g*^&eaLs{z4HW23F~8rE|7;E?1oFtP(q^w0CA99MWeyAyKPrF(M-p zq7ZqIh88z%9-|}hAf5Hx*SZe6OSJ@*CX95h*R&kp&gz|qVK(6DFi*#wN(NRym2?4O z=vpTg?cXOTgcuIqH8OZ&==g{;bRe{{JM@@mQI7=U)qG_r4QdV$AIw@9vXE)Hw1cij zPZx$xkhY#2-vMQPm(hoZ#(VMQG%(T&lc@_*g|X!LUB^y1g9D=jgF`1yTcsIoTHi_) zA{{7{Wi^t1CU++rKiV4CEbHLVu_Ra+X%7}`kiDd8)L1;&6r?Tm-gCLhj6=-S@->Gw zdEw0-eMBqx?WE#sD;%7Rbc?FnjN4g*)arAhcR<_H`)9MD?L-d`znN4OT%eeowqT-x z?o(w;8{WA|a0B2RU9ZP!g4_*3!8o0qyNWc`J&_#WrHv=lwPx3{*aqE{b5qk*>l_8c z6lN@+SUm9Rp&4g(iWK$+M+?xeHEZLN7-j4%4b0e+7NRs z2BJ5R+f~#CSf<0|Pk~c_G+3$(31BFErBXhXpP5e4O?o;5bCyJCL-RU!9(uP|ZK(|b zY7{x2sC8bIUI$VcwS!eDwnPSw4ILUu9xxWk*wBe2Rl+FRbnP<}*rVW(WPl+$ks=ol zR7B7P8-J|cIT!}TYOZ<#2oK|8Y(8rt=#)YF*D49gd^Q5^9F+bdD<+NHCfg@6Q@Nt6 zkA`ys7b^DcaVRp!Ne*iM5tQ6%x6Gh3&#yZDTvRo#gNJorqpl0u$iw{!2j--)mgnPz zy1aGtk(t2v!hoeUp21JG^kjioh?yRk$7=Eqo@GyxJvOWj*ji(z?DnPP~2#&rofoWo-vHg_vR9Ni>6EJZ3TRTKZfgnLRT;v&UuI z#yw%Non*OiU;~~lKNDhSu-F50l^Vkgv}>@328TAvU|_-HO1|)73a%)2@j;R}Gg*s} zFTLg*a@zE0d@^z)Ti?7cqHLAbAbqOssJYjSoI)e^$BEQeFVPQKyA0S*J)LC z=9KBZb`qGOJ$QXiMQE57P$O0@^i^_cxiqqOZ~N6$H^P-~4rDLYY-_vrz_`_Ai+$d- zFz9I}hKI0Q0d46w%9l;$E17-1jPngomw|gk9eTqMNVY~Gbt4bMf?c6k*KqnsJ>ZEd z8N*joiF)9yPoGbDT(Qi=Fgr!cwBeW;37?RzFyyHW}K+k*)~CEY7NJ9J#a-n^RRvhZ44Z-HT}>?{Og!Q5YO4LD7wVG zSs|d8U7d&4dmp{RmvoAm)Z}(5E5PRx+nDiaaXD>azC_zgXpB$6^*A#HvscmTp-_<8 zXl3)5LcSWqFf3v3Uer{~T|MZj7im6j^q|+zi-MCbKe%5i6tJNC*jyK{HqC?xpVpI` zn8M4zsg?#GzZU$$Msa5jF_6*MP$RN%b&zPt=FaQMTy_PUiAztXU89bQX$r$o!e%@* zzhT+JQUz*5Mo&)G%lJNV7#hUhayzv%BpqoP?pv?YrMd#}h)kExvA5lfS#n}JT4ZOY zGMHf^Jy9EUR^3R`y%iYH*XjL(&11UJ?>H8Q&|^)orcJZ$klxoZ42_u+7$&=n-AlOQ z=!MD5#OxHVYlgp`6@s#7?Vu@TqvqG~dXWYr<4$05jde-WW54ir?X##jDR?&0RS6fU zlR`bIDm0b3tP$64#D-O!&QaTo=L+l)(AAqQ&Vp0+kSKtpurTg}jzfzSmC6h_C@TOS zm!d^&)ow#_t?KckhiT?sp0^+=C4^Sj>_B68fJQ*Uz$bhcn6c(g`q`+WFgD&dzcSv7 zmriC}bsW>+WZLkgXxRJpR2>|>hIf#9&Xbm5V`)d(wS&nCEq;2a_^?Zy3e3k0Pl7pm zP7G=l%?Yqy43e4@RfD;PMv_cXlIMku5wBplMjjJz99{F!(5NuN(W5tmr%+c1V`+AZ zvmBCenziI~X|_nJF&q@E>!Ps;*BN0qXE0s|weoP!`OJn?{#@!p#mU?|3x~;^1>>Qd z&BFzgDo^1r1J4l;P^NuWr1=0chIS;Ae42o@+ekd8#+fgHFP?cD!WWs#Fb{-P2T1n% z3|~w;*TmTE+J6W0094BIRuhLoy3s|+dWCui!qv*$Jlrir&!LKwG!(NfblhI3)KlFA4nWbP2z@DD$iV}Jz&_<(a z-ucx!cYwyeYuI`{ljPOV9YFG5!rg)%C3l8O`Y|vJ6A6#7M|bAnBeXK-p)f#40co&U zn<_DSK~Fo}UiAe`Bx`;vp@{2ZV{ow|@&r~j3s7j}+BuE)g|-`pr6**S^Kc0+mnE`9 z5os;LnVNdxbn@C9q%zT@eTAhs?CScqhERNBTTtJ~ROdKOZxG&IHr>(d2=gPVoP}4m zoKDd+1raegl{=kg70$8)iHDh%rCyQ6A_MVxm|@_`V=~VTrhv%R(L12RB+thL9AxA& z#kiM=b2qu*1d&A5jCK?^!mf*RGN+CGW8@?->tJeV0?a!oBT(HGA}~3IO%q!{U})ub zvQ*4iqE8T-e8G>x*YyOyHrn&@I_-A_E?L7Qe&A=*^vV#Eh{K(YAr{i5BKye?d_aer z5@KmO4pPY-&Xcfzsu|ItVMns6QV;n@I=>jwjFno{~H}DMzZ#}c(Q&WTFNHU}cVjzrzOVDf}YIY1n zexe7yh`7z4<>$fS1wfn0RiA+0aS#=1EgvCVY>s32*JvjdY>%KBJ4s? z8AiC=NjkQ*p6<6}*0*}0fm#c14)OAuChW#xbAQy)!!9#lM&&_)N#|(pGn-3WIG3T} zHsu3mt7cWoCm^YzV`GCaOA`)tLk1pS_>18tGjV5uTg^Yy30%MlmVP z(4qq3HRr&_r*v??m~yO2K(&*(AfIVXa|=cdBO&L?B%l66US4;?t`UO@tSx~qPZK!2 z1V#KCG!{Hi@4}Q`OcmAwh8lY3sY8g!$;2$?D$~{jI#TZqcCb4Y5$u=kcmqd1I9L)u zQ@*3*YG*AVAOiya8H}+56c|TDTNl`I4f&I`NmTI_vV;j?OEp&kMKX})&bFze4V!vK zB`YN`-VsGl`WRKRG}g$I5b)%=>Pd(QBii78nQx@>>$bC7`?>?S;GPs4ngl5f`E$g% zNG(BJ3(Ho~C<3BmAA9gbfz}aHx)%f8U2{PhJ!A__>5^GuW>&nbBuH{FF$9X~p-Jod zs8#qGfo_rkp}7*hm1zXVBEF#nqGowZI0S`DlfXPuP{N!#%ct^R6QH|)&Lv1x&~V3Oz?%%iC?swK{so!Q)4+R@=ZK2Y^C`n5e;K%2C=7fsg7U?6@~@r zXeCYPfo?HV>m<|zmLVw$m`x-x;uI2sg+@?OSP6RpHL`T%4qambfo^WlB-)`x z8L6v8OJ-%CM}eSV9Rx(UFC-FSZ>6|RYv`(l)HPHbnV_}yNNb)jyr~m-GJx$Wgq6)X z@JOaD%o1g238t*Q1a}Vf6!h`hN-xcA$YA%GW&F5zQ7-XEZQ3jN~0l)4C_) zKy)vltX9i8EN(HJ+N*|Z>X^5lhH3(+_pE5AnJLf>G<)jFdOefFiLsK-Ke=r|5I9hS zhyvWEg=n=kBSIFZlg=C_&!DV~9%TAWDhItA#CPZVreoAZrQI=W|D*jjU#DVlK*B8+1M4h?^} z%Fk(;*tM;2q`f&%clIY~8PZGZm=lSb#Pra_e8Pyi21?UngFGXwxaz$#gjbPudjak? zjr;)HFyTqe1g<9fm@9x7wT9s?q+wMfgr&jb#%euHa&v@jO=gl-hPYeW^#MZx?9%9k z7`wE(3@Zfm)TV#0fsmJ=P7T<(=)Dem6`}3XVp`@HdK#KYihFs=I+(XS}APf3xi<822Fl7Coe?X=i}V?t>8l>d&3uE~#HM8ze~!zdf4MbBC_pDX3C%EIh46Gr*9 zbur<-u{wnmW0-Al&40;b%M7~C>ED%}nX%sJu1ejrm2!8kn9k2mX1cKywmZ?=JwY*C zArJg8ETwgJ^mXQQ#o6vJ(W&(*S!<9mC9e`Wz?o_ zio6JIS$F1q)n9;Aj@>_S0YO4cm59f5Ou@PS3CzFsLo%k!(?nu;oZrRLopW*^Sf^yLre$FW?twW{J zWV|$+n?e$fYSGJ1V99v)QK9TSY}j<6JMrvsZ%N_kF8z`cpD?)S9aN^7IPR*Z(}c+E zxk*cJUnJ;aIIIWnE=iP-R}S;5NtecZva>}5YH-zXhZf;2d_hgZ#ome8T%Itq&(h93 zz&r*I-;wuH=K-GCmsz{37I2N72~aH7CBXlRJ?f`1l|-8<;X-p&PjU%11w>})`Pn2; zfeWM3X;`j=v&Kg!qu#y61r>~wyQZIi8#B9oZ;hE1G zMLt-zG-h_{{&*yIU`sz&;pUjxKl|pG*(^I7Gh1E{$IPyz>tbdf(sxB-W_!^yF|%vv z=9t+abVJPS0ooojn|&UOncX}`V`jt7o>;L$@l|wPJ9c5e0Y(2R$X11B^h?z|#dtzq$$cG~_v-x8(X7+P@Ys~D? zI2JQIFK&;SJr-|`ncWnJV`huQo|xGPaX4l+HQXLEdlxQ`nXL-jVrFB)k(k+Zurp>h z7fi&=HiBa@vklP0{4n4VYmGHp?#C5f>mR-jjO!b$mkRv? z(WiIdMCDj*e)ziqk60_LuLaWoHsE3Pe-3a9c-Y0~+TlgMB>dpx0iNGq3;ZEdL3#F1 z0(UDe@FkoMO*vzU_O#9)WL`{N^+w@Rtlg&i4pB_}=>se2Mk6Hw%46g4&C3 zi}`*}3p|L2X9fNRUut|1*OZSg@czDL;4@yRWqsSgAM(Th(ZB-@XaNJHdg2Qdz6>|J zaH7=Oug`@qtpRa$)P;YWNYedHxNxiA1>)jD4Q{=TpLteo0Dq6bUw_?Ky!D|Pyxo*R z=i9iq>&M;iNP2v0$&(_|CPY;AxPiv2LktF4Or)jeE)TP zxzzVtE%4<&D83IT@D&00eu2}scDz6OhA|1mgGo*SVYNQhv7!oU3bq zI3E}Iqk)3%5jcH|#`}9%;O`E=e@)8WZLV2(dA77CpqK&?GSjo z7l`Y_0uR#LX@S2UsAoao$lC8M>LGzY6o7ve@Gv>@dB7u}w0OTR#&D+>1imx?{|AAu z3c#CzH!7z+0Phg^<^X(`z_$h9M+Ckr0M7`#KLEd9;D-b7_X~V90RJt4p9;XQ2>j^) z{J#nO69M=?2>jUqycOe}@bH-ce67H9hcuo|ca8}Bo&fw|fu9Y)8|gFup3}EkU%OM6 zZ+U)U{r(3?Z&}N%yp(T}{~4FF()xBl5BUp*ud-e(^_!%8{zt&qL!WWgVtKj0Q-S5{xxSKMnX&Yqhoc-hN+EB`L=>6XfYH3*1%W1^zL>mqAZ^sI1F5yioVR z1u(UCl>g9-hD&nm8Ke)Z|2McCaAxUW36fiAKlo`kgrNX z8zsG~OiTKQ1s=rPr??zR=q`+N+ zP2hJ6+_gsp{;a@VQ%~T}3*0pi1pYmNyH=0DH$eDxpq}kQ4`>em?f{(X=^7jYe+=+8 z?EU}j_I`or)B6eTgXo*zm2zD3P15{6mxKOV*3S?n@NY``Aicc?_)^rTy-lZ=T^mu#xgzEK-s*maToU*TfG-36 zKP~0MWWr2j9RevS3~ z0|Nrlr>{tQ*8?O^|6bs(KSkhc!OE%ruB%7jI{_#9H=kR4N$@i$>0RfEq<=u*t{+L@ zPYK-hISKp=4buNu_=foar6jlhO47R?A9=72hR#w?KbrJg0H=1j&K7xaMBu@49uv6h z*pc*~7P#yB5%?>BhtbJ00K?!J0J%Qh{anH!Dd$m6zu21Ipy96y{5K`N>v$9RrzQP6 zSL*cIezUA^NP5>Kd2AfB5>EyE9vh5yj`xnrx8i-I&$S=EPJU zQz_W42J`LM6=L>0rqHk;3aiW}yS9a=;KcQm82oxAt_nI1E-wf6JrB|NoOp1XxteX988X5w{{9{9Xx#3$WeVs$#g0W92z-z zob&EKeIhyT92go)4xW&LJa8#_{6un;p*piJh+jeF9vB{QlBWQ4xG+vgutr7Gvthh! zVC>M)h^2G#w${lL$uVc(z=1JqbS&u%9=~gZQX!mn^!PZoPxaj6@k!bPa$G+@fXBxV z9vn|f`th+r-~EB(ckMrxbO=l(bD3Tk70*Cj8A?Z+`$_4zMI`gk$RJu&%YWC1*^`pq zPFc7FOvN1ImOF#PqbNnn7)#!H7jO(rQ{$;k$48Exc8(oCKI(1Q!Ml*#6Gu#`P2U6% zF*eRv5;Wk>g9O}Bg#;z^%#VNogqjafu<^PSf71c0Z#aPU%?8lZI@}TF2SyI)Hf!1d zwC+0te%SF5^fbOd;7!N-E_(CvZlJ!c85t`&kLe}-ErVKK11E^nbJAPc6UUue@m?(Z zA5iBUt5GT{PLLWVd;okr0r3yihslw_;|D;5$zh_`Gs&^z0g$QL$Z<$YH_dE#fqk$Ydbq@dT$j5pTzJlBxi>WBdp*Nt{T!p|r-W-T8aLbT)hLs_YBy_|r zLxZV|#137mB&4;t9;kPJZ>pHD>$!)i8ht*ne9A&^9E+ztwI23;;DTPW%`*ZyA_`mf z>cF91EZf11bHiAnp?gdu<+W!iTz{4el*KuSF99T-B!(1X?Hz0!?j#Qj@Gy9&bKvyI z!0^zZ`O@GBb82TI10L)Q>UEXu0G4j=p9rL`_p)*nYvnW>{RW;6{hI=9mxFeLQp8j2hdI+ zv^WAS2-(igpQk=_Izh3Aw97vgfY2G)T?GqHJb^S6g8VaWfXbb5O$c^3>_BxIE+X?a zN^jt*LACx2goxK|)D4QSl$>eoii&l*^HfiV&hyeq)TrUAKMM8|-2EK3*KW8(g#+xa zHTc`5T#AQr_X=+C&5;8qyRb!F7xo%Ef8N2a5EWWEL7PXGKc9W zwh~l~CX5h-^Gh177o;}S$RW53I?4K$x3ot{36bxFCr+qLLfQuyb^fBmK@ zgWL(uiQz#b2Y}k2VI7mdu65jy2D`3dfTqA%8?7Iohj;K2K3QW}`$SXtu%ykh>%%m8 zyoYCSp-ilLd$5oX6g8$`ljW*gO$S&h`h<`K5myAc1y+5M+?9=y0=OQpzQHxjfpXr+ z7Ysd{BGgG=bW+GZs{Y_zv)RggeI2sZA7l)TXp9J6`~YnIOy9(V2 z9YbBcJv>O?5F7P>2gX~h=9@Jc>Qy>qbwJn(p)g@sTR=9?mxh=ou4xr|h)Uy;Xj1PG z8SmRSNbecsQ;|bx3Q_)EwK;?k>%QbN4~q|xhmE#@^zIs?zb6{(o4^RuU2+$HKFJxw zCTfLD|DnM_m@%a9LDMaVGxvIKA=_gh2s!kVRr4%ZVo^Z`$InG&4)PG+7P^hGtT~ZU z@M%Z~7GOfyVe@<}Uu6bCwkXh3w^J(*=tC@V&>S9f;3sKv+W|h6GTw=?%D>E%V+-ZE{%e_OP>9i~-hI^Sd zh2k<~rvfg+T;o-#0`_IXdX>W zV1=7`f)x&UuTBMgno9zs@7((`KFtbucg6U|e}?l~mgOCU}|1QbDOY*mS6XV*wj|Rk)^K4u6!? zy#FQDk2}kHNdgZZHxC%9%aBVx-HRi_4c+=r$NL#REic;2ceN;0Q=B?{G^$KRPV)PUrcq_I{J!wEqKmPWkssx-UrnFG&7r zLK=RAf65cg{}I3_iRm9}X+Q4p!#pbeh`znQVEB zbLw`?o6_&`jrwekCZD-~1{dDuh=IIM2`E z&wwbt+?(ITiRBOJ6bs#g^?mUDUk>CyKh)0&&kyPJZwtIIaBF=%kpCNZ>iplhQ|CXb zPwtWG>ixZf8-y${Z4%tX)qm~nbXTJneOJ3T_4qy>AY{PJZ{ql0y2kwfg1kg~GThyG zx^GMV?@5JCH<qf?{Tk$_vP}I=oXO^tPWad&B=G$DJ=b)(PTs#Q05|!!1mLDL zbKWfZ1IetrQvwMPtiiiUzq3*Pj85@GIh*w6z2<(fLH@hGqEmbqaUR}ZF#nwm^7p@> zbNy8Kh$$nO|Lu}Lh`)WW>KtP-4uknkImBP-I+*|Qgif(8fIpMZoX_LJT$^{$`IFl< zbbo+<1k=)eAPE=qBkCCZHX}ZPejD*Og+D{LMt+c(FtmqD_t$<*=YK!q3B14H`w6{a Q`Tt!?r+h4sA#iK`KWx!rjQ{`u diff --git a/emulator-asm/src/dma/test_dma_inputcpy_mops.cpp b/emulator-asm/src/dma/test_dma_inputcpy_mops.cpp deleted file mode 100644 index c20facc84..000000000 --- a/emulator-asm/src/dma/test_dma_inputcpy_mops.cpp +++ /dev/null @@ -1,334 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -// FCALL constants from dma_constants.inc -const uint64_t FCALL_PARAMS_LENGTH = 386; -const uint64_t FCALL_RESULT_LENGTH = 8193; -const uint64_t FCALL_FUNCTION_ID = 0; -const uint64_t FCALL_PARAMS_CAPACITY = FCALL_FUNCTION_ID + 1; -const uint64_t FCALL_PARAMS_SIZE = FCALL_PARAMS_CAPACITY + 1; -const uint64_t FCALL_PARAMS = FCALL_PARAMS_SIZE + 1; -const uint64_t FCALL_RESULT_CAPACITY = FCALL_PARAMS + FCALL_PARAMS_LENGTH; -const uint64_t FCALL_RESULT_SIZE = FCALL_RESULT_CAPACITY + 1; -const uint64_t FCALL_RESULT = FCALL_RESULT_SIZE + 1; // 391 -const uint64_t FCALL_RESULT_GOT = FCALL_RESULT + FCALL_RESULT_LENGTH; // 8584 -const uint64_t FCALL_CTX_LENGTH = FCALL_RESULT_GOT + 1; // 8585 - -// External assembly function declarations and fcall_ctx -extern "C" { - uint64_t trace_address_threshold = 0; - uint64_t fcall_ctx[FCALL_CTX_LENGTH]; - uint64_t dma_inputcpy_mops(uint64_t dst, uint64_t count, uint64_t* mops_ptr); -} - -const char *mops_labels[16] = {"NOP", "CWR1", "RD1", "WR1", "RD2", "WR2", "RD4", "WR4", "RD8", "WR8", - "ARD", "AWR", "BR", "BW", "ABR", "ABW"}; - -// MOPS constants from dma_constants.inc -const uint64_t MOPS_ALIGNED_READ = 0x0000'000C'0000'0000ULL; -const uint64_t MOPS_ALIGNED_BLOCK_WRITE = 0x0000'000F'0000'0000ULL; -const uint64_t MOPS_BLOCK_WORDS_SBITS = 36; -const uint64_t ALIGN_MASK = 0xFFFF'FFFF'FFFF'FFF8ULL; - -// Helper class to manage aligned test buffers -class AlignedBuffer { -public: - std::vector data; - - AlignedBuffer(size_t size) : data(size, 0) {} - - uint64_t* aligned_ptr() { - return reinterpret_cast(data.data()); - } - - uint8_t* byte_ptr() { - return data.data(); - } - - void fill_pattern(uint8_t start = 0) { - for (size_t i = 0; i < data.size(); ++i) { - data[i] = static_cast(start + i); - } - } - - void fill_value(uint8_t value) { - std::fill(data.begin(), data.end(), value); - } -}; - -void print_mops_trace(uint64_t* mops_ptr, size_t mops_count) { - std::cout << "MOPS Trace (" << mops_count << " entries):\n"; - for (size_t i = 0; i < mops_count; ++i) { - uint64_t entry = mops_ptr[i]; - uint64_t opcode = (entry >> 32) & 0x0F; - uint64_t addr = entry & 0xFFFF'FFFF; - uint64_t block_words = entry >> MOPS_BLOCK_WORDS_SBITS; - - printf(" [%ld] %s (0x%X) addr=0x%08lX", i, mops_labels[opcode], (unsigned)opcode, addr); - if (opcode == 0x0E || opcode == 0x0F) { // ABR or ABW - printf(" words=%ld", block_words); - } - printf(" (raw=0x%016lX)\n", entry); - } -} - -bool validate_mops_trace(uint64_t dst, uint64_t count, uint64_t* mops_ptr, size_t mops_count) { - if (count == 0) { - if (mops_count != 0) { - printf("❌ FAIL: Expected 0 mops entries for count=0, got %ld\n", mops_count); - return false; - } - return true; - } - - uint64_t dst_aligned = dst & ALIGN_MASK; - uint64_t dst_offset = dst & 0x07; - uint64_t last_byte_addr = dst + count - 1; - uint64_t last_qword_aligned = last_byte_addr & ALIGN_MASK; - - // Calculate how many qwords are affected - uint64_t qwords_affected = ((count + dst_offset + 7) >> 3); - - // Determine if we need pre and post reads - bool needs_pre_read = (dst_offset != 0); - bool needs_post_read = ((dst_offset + count) & 0x07) != 0 && (last_qword_aligned > dst_aligned || dst_offset == 0); - - // Expected number of mops entries - size_t expected_entries = 0; - if (needs_pre_read) expected_entries++; - if (needs_post_read) expected_entries++; - expected_entries++; // Always have block write for count > 0 - - if (mops_count != expected_entries) { - printf("❌ FAIL: Expected %ld mops entries, got %ld\n", expected_entries, mops_count); - printf(" dst=0x%lX, count=%ld, dst_offset=%ld\n", dst, count, dst_offset); - printf(" needs_pre_read=%d, needs_post_read=%d\n", needs_pre_read, needs_post_read); - print_mops_trace(mops_ptr, mops_count); - return false; - } - - size_t mops_idx = 0; - - // Verify pre-read if expected - if (needs_pre_read) { - uint64_t expected = MOPS_ALIGNED_READ + dst_aligned; - if (mops_ptr[mops_idx] != expected) { - printf("❌ FAIL: PRE-READ mops[%ld]: expected 0x%016lX, got 0x%016lX\n", - mops_idx, expected, mops_ptr[mops_idx]); - return false; - } - mops_idx++; - } - - // Verify post-read if expected - if (needs_post_read) { - uint64_t expected = MOPS_ALIGNED_READ + last_qword_aligned; - if (mops_ptr[mops_idx] != expected) { - printf("❌ FAIL: POST-READ mops[%ld]: expected 0x%016lX, got 0x%016lX\n", - mops_idx, expected, mops_ptr[mops_idx]); - return false; - } - mops_idx++; - } - - // Verify block write - uint64_t expected_block_write = MOPS_ALIGNED_BLOCK_WRITE + - (qwords_affected << MOPS_BLOCK_WORDS_SBITS) + - dst_aligned; - if (mops_ptr[mops_idx] != expected_block_write) { - printf("❌ FAIL: BLOCK-WRITE mops[%ld]: expected 0x%016lX, got 0x%016lX\n", - mops_idx, expected_block_write, mops_ptr[mops_idx]); - printf(" qwords_affected=%ld, dst_aligned=0x%lX\n", qwords_affected, dst_aligned); - return false; - } - - return true; -} - -void init_fcall_result_data(uint64_t pattern_start) { - // Initialize FCALL_RESULT with test pattern - for (size_t i = 0; i < FCALL_RESULT_LENGTH; ++i) { - uint64_t value = 0; - for (int b = 0; b < 8; ++b) { - value |= ((uint64_t)(pattern_start + i * 8 + b)) << (b * 8); - } - fcall_ctx[FCALL_RESULT + i] = value; - } - - // Initialize FCALL_RESULT_GOT to 1 (next read from FCALL_RESULT[0]) - fcall_ctx[FCALL_RESULT_GOT] = 1; -} - -bool test_inputcpy_single(uint64_t dst_offset, size_t count) { - if (count % 8 != 0) { - std::cout << "❌ ERROR: count must be multiple of 8, got " << count << "\n"; - return false; - } - - // Initialize fcall_ctx with test data (pattern starting at 0x10) - init_fcall_result_data(0x10); - - // Save initial FCALL_RESULT_GOT - uint64_t initial_result_got = fcall_ctx[FCALL_RESULT_GOT]; - - // Allocate destination buffer - AlignedBuffer dst_buf(2048); - dst_buf.fill_pattern(0xA0); // Fill with different pattern - - // Calculate destination address with offset - uint64_t dst_addr = reinterpret_cast(dst_buf.byte_ptr() + 64) + dst_offset; - - // Allocate MOPS trace buffer - AlignedBuffer mops_buf(256); - mops_buf.fill_value(0); - uint64_t* mops_ptr = mops_buf.aligned_ptr(); - - // Call assembly function - uint64_t mops_count = dma_inputcpy_mops(dst_addr, count, mops_ptr); - - // Verify FCALL_RESULT_GOT was updated correctly - uint64_t expected_result_got = initial_result_got + (count / 8); - uint64_t actual_result_got = fcall_ctx[FCALL_RESULT_GOT]; - - if (actual_result_got != expected_result_got) { - std::cout << "❌ FAIL: FCALL_RESULT_GOT not updated correctly\n"; - std::cout << " dst_offset=" << dst_offset << " count=" << count << "\n"; std::cout << " Expected FCALL_RESULT_GOT=" << expected_result_got - << " Got=" << actual_result_got << "\n"; - return false; - } - - // Verify copied data matches FCALL_RESULT - const uint8_t* dst_bytes = reinterpret_cast(dst_addr); - bool data_ok = true; - - for (size_t i = 0; i < count; ++i) { - // Calculate which qword and byte within that qword - size_t qword_idx = i / 8; - size_t byte_in_qword = i % 8; - - // Get expected byte from FCALL_RESULT (accounting for initial_result_got - 1) - uint64_t source_qword = fcall_ctx[FCALL_RESULT + (initial_result_got - 1) + qword_idx]; - uint8_t expected = (source_qword >> (byte_in_qword * 8)) & 0xFF; - - if (dst_bytes[i] != expected) { - std::cout << "❌ FAIL: Data mismatch at byte " << i << "\n"; - std::cout << " dst_offset=" << dst_offset << " count=" << count << "\n"; - std::cout << " Expected=0x" << std::hex << (int)expected - << " Got=0x" << (int)dst_bytes[i] << std::dec << "\n"; - data_ok = false; - break; - } - } - - if (!data_ok) { - std::cout << "\nFirst 32 bytes of destination:\n "; - for (size_t i = 0; i < std::min(count, size_t(32)); ++i) { - if (i > 0 && i % 16 == 0) std::cout << "\n "; - std::cout << std::hex << std::setw(2) << std::setfill('0') - << (int)dst_bytes[i] << " "; - } - std::cout << std::dec << "\n"; - - std::cout << "\nExpected (from FCALL_RESULT):\n "; - for (size_t i = 0; i < std::min(count, size_t(32)); ++i) { - if (i > 0 && i % 16 == 0) std::cout << "\n "; - size_t qword_idx = i / 8; - size_t byte_in_qword = i % 8; - uint64_t source_qword = fcall_ctx[FCALL_RESULT + (initial_result_got - 1) + qword_idx]; - uint8_t expected = (source_qword >> (byte_in_qword * 8)) & 0xFF; - std::cout << std::hex << std::setw(2) << std::setfill('0') - << (int)expected << " "; - } - std::cout << std::dec << "\n"; - - return false; - } - - // Validate MOPS trace - if (!validate_mops_trace(dst_addr, count, mops_ptr, mops_count)) { - std::cout << "❌ FAIL (MOPS): dst=0x" << std::hex << dst_addr << std::dec - << " dst_offset=" << dst_offset << ", count=" << count << "\n"; - return false; - } - - return true; -} - -void print_progress(int current, int total) { - if (current % 100 == 0 || current == total) { - std::cout << "Progress: " << current << "/" << total - << " (" << (current * 100 / total) << "%)\r" << std::flush; - } -} - -int main() { - std::cout << "==============================================\n"; - std::cout << " Testing dma_inputcpy_mops implementation\n"; - std::cout << "==============================================\n\n"; - - std::cout << "FCALL_CTX structure:\n"; - std::cout << " - Total length: " << FCALL_CTX_LENGTH << " qwords (" - << (FCALL_CTX_LENGTH * 8) << " bytes)\n"; - std::cout << " - FCALL_RESULT offset: " << FCALL_RESULT << " (data input)\n"; - std::cout << " - FCALL_RESULT_GOT offset: " << FCALL_RESULT_GOT << " (read counter)\n\n"; - - int total_tests = 0; - int passed_tests = 0; - int failed_tests = 0; - - // Test parameters - const int max_count = 1024; - const int count_step = 8; // Must be multiple of 8 - const int max_offset = 7; // Test offsets 0-7 - - std::cout << "Test configuration:\n"; - std::cout << " - Destination offsets: 0-" << max_offset << "\n"; - std::cout << " - Byte counts: 0-" << max_count << " (step=" << count_step << ", multiples of 8 only)\n"; - std::cout << " - Total tests: " << ((max_offset + 1) * ((max_count / count_step) + 1)) << "\n\n"; - - std::cout << "Running tests...\n"; - - // Test all combinations of dst_offset and count (must be multiple of 8) - for (int dst_off = 0; dst_off <= max_offset; ++dst_off) { - for (int count = 0; count <= max_count; count += count_step) { - total_tests++; - print_progress(total_tests, ((max_offset + 1) * ((max_count / count_step) + 1))); - - if (test_inputcpy_single(dst_off, count)) { - passed_tests++; - } else { - failed_tests++; - std::cout << "\n❌ FAILED: dst_offset=" << dst_off - << ", count=" << count << "\n"; - - // Stop on first failure for debugging - if (failed_tests >= 1) { - std::cout << "\nStopping on first failure for debugging.\n"; - std::cout << "You can debug with: gdb ./test_dma_inputcpy\n"; - goto done; - } - } - } - } - -done: - std::cout << "\n\n==============================================\n"; - std::cout << " Test Results\n"; - std::cout << "==============================================\n"; - std::cout << "Total tests: " << total_tests << "\n"; - std::cout << "Passed: " << passed_tests << " ✅\n"; - std::cout << "Failed: " << failed_tests << " ❌\n"; - - if (failed_tests == 0) { - std::cout << "\n🎉 ALL TESTS PASSED! 🎉\n"; - return 0; - } else { - std::cout << "\n❌ SOME TESTS FAILED ❌\n"; - return 1; - } -} diff --git a/emulator-asm/src/dma/test_dma_memset_mops b/emulator-asm/src/dma/test_dma_memset_mops deleted file mode 100755 index 0641c401d6ebadaef32a34f04cf251db87411da8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84816 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 -#include -#include -#include -#include -#include - -// External assembly function declarations -extern "C" { - uint64_t trace_address_threshold = 0; - void fast_memset(uint64_t dst, uint8_t value, uint64_t count); - uint64_t dma_xmemset_mops(uint64_t dst, uint8_t value, uint64_t count, uint64_t* mops_ptr); -} - -const char *mops_labels[16] = {"NOP", "CWR1", "RD1", "WR1", "RD2", "WR2", "RD4", "WR4", "RD8", "WR8", - "ARD", "AWR", "BR", "BW", "ABR", "ABW"}; - -// MOPS constants from dma_constants.inc -const uint64_t MOPS_ALIGNED_READ = 0x0000'000C'0000'0000ULL; -const uint64_t MOPS_ALIGNED_BLOCK_WRITE = 0x0000'000F'0000'0000ULL; -const uint64_t MOPS_BLOCK_WORDS_SBITS = 36; -const uint64_t ALIGN_MASK = 0xFFFF'FFFF'FFFF'FFF8ULL; - -// Helper class to manage aligned test buffers -class AlignedBuffer { -public: - std::vector data; - - AlignedBuffer(size_t size) : data(size, 0) {} - - uint64_t* aligned_ptr() { - return reinterpret_cast(data.data()); - } - - uint8_t* byte_ptr() { - return data.data(); - } - - void fill_pattern(uint8_t start = 0) { - for (size_t i = 0; i < data.size(); ++i) { - data[i] = static_cast(start + i); - } - } - - void fill_value(uint8_t value) { - std::fill(data.begin(), data.end(), value); - } - - bool verify_pattern(uint8_t start = 0, const char *title = "") { - for (size_t i = 0; i < data.size(); ++i) { - uint8_t expected = static_cast(start + i); - if (data[i] != expected) { - printf("❌ FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", - title, expected, i, data[i]); - return false; - } - } - return true; - } - - bool verify_pattern_except(uint8_t start, size_t from, size_t count, const char *title = "") { - size_t to = from + count; - for (size_t i = 0; i < data.size(); ++i) { - if (i >= from && i < to) continue; - uint8_t expected = static_cast(start + i); - if (data[i] != expected) { - printf("❌ FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X (from=%ld, count=%ld)\n", - title, expected, i, data[i], from, count); - return false; - } - } - return true; - } - - bool verify_fill(uint8_t value, size_t from, size_t count, const char *title = "") { - size_t to = from + count; - for (size_t i = from; i < to; ++i) { - if (data[i] != value) { - printf("❌ FAIL FILL VERIFICATION of %s at [%ld]: Expected: 0x%02X vs 0x%02X (from=%ld, count=%ld)\n", - title, i, value, data[i], from, count); - return false; - } - } - return true; - } - - bool verify_fill_except(uint8_t value, size_t from, size_t count, const char *title = "") { - size_t to = from + count; - for (size_t i = 0; i < data.size(); ++i) { - if (i >= from && i < to) continue; - if (data[i] != value) { - printf("❌ FAIL FILL VERIFICATION of %s at [%ld]: Expected: 0x%02X vs 0x%02X (should be outside from=%ld, count=%ld)\n", - title, i, value, data[i], from, count); - return false; - } - } - return true; - } -}; - -void print_mops_trace(uint64_t* mops_ptr, size_t mops_count) { - std::cout << "MOPS Trace (" << mops_count << " entries):\n"; - for (size_t i = 0; i < mops_count; ++i) { - uint64_t entry = mops_ptr[i]; - uint64_t opcode = (entry >> 32) & 0x0F; - uint64_t addr = entry & 0xFFFF'FFFF; - uint64_t block_words = entry >> MOPS_BLOCK_WORDS_SBITS; - - printf(" [%ld] %s (0x%X) addr=0x%08lX", i, mops_labels[opcode], (unsigned)opcode, addr); - if (opcode == 0x0E || opcode == 0x0F) { // ABR or ABW - printf(" words=%ld", block_words); - } - printf(" (raw=0x%016lX)\n", entry); - } -} - -bool validate_mops_trace(uint64_t dst, uint64_t count, uint64_t* mops_ptr, size_t mops_count) { - if (count == 0) { - if (mops_count != 0) { - printf("❌ FAIL: Expected 0 mops entries for count=0, got %ld\n", mops_count); - return false; - } - return true; - } - - uint64_t dst_aligned = dst & ALIGN_MASK; - uint64_t dst_offset = dst & 0x07; - uint64_t last_byte_addr = dst + count - 1; - uint64_t last_qword_aligned = last_byte_addr & ALIGN_MASK; - - // Calculate how many qwords are affected - uint64_t qwords_affected = ((count + dst_offset + 7) >> 3); - - // Determine if we need pre and post reads - bool needs_pre_read = (dst_offset != 0); - bool needs_post_read = ((dst_offset + count) & 0x07) != 0 && (last_qword_aligned > dst_aligned || dst_offset == 0); - - // Expected number of mops entries - size_t expected_entries = 0; - if (needs_pre_read) expected_entries++; - if (needs_post_read) expected_entries++; - expected_entries++; // Always have block write for count > 0 - - if (mops_count != expected_entries) { - printf("❌ FAIL: Expected %ld mops entries, got %ld\n", expected_entries, mops_count); - printf(" dst=0x%lX, count=%ld, dst_offset=%ld\n", dst, count, dst_offset); - printf(" needs_pre_read=%d, needs_post_read=%d\n", needs_pre_read, needs_post_read); - print_mops_trace(mops_ptr, mops_count); - return false; - } - - size_t mops_idx = 0; - - // Verify pre-read if expected - if (needs_pre_read) { - uint64_t expected = MOPS_ALIGNED_READ + dst_aligned; - if (mops_ptr[mops_idx] != expected) { - printf("❌ FAIL: PRE-READ mops[%ld]: expected 0x%016lX, got 0x%016lX\n", - mops_idx, expected, mops_ptr[mops_idx]); - return false; - } else - mops_idx++; - } - - // Verify post-read if expected - if (needs_post_read) { - uint64_t expected = MOPS_ALIGNED_READ + last_qword_aligned; - if (mops_ptr[mops_idx] != expected) { - printf("❌ FAIL: POST-READ mops[%ld]: expected 0x%016lX, got 0x%016lX\n", - mops_idx, expected, mops_ptr[mops_idx]); - return false; - } - mops_idx++; - } - - // Verify block write - uint64_t expected_block_write = MOPS_ALIGNED_BLOCK_WRITE + - (qwords_affected << MOPS_BLOCK_WORDS_SBITS) + - dst_aligned; - if (mops_ptr[mops_idx] != expected_block_write) { - printf("❌ FAIL: BLOCK-WRITE mops[%ld]: expected 0x%016lX, got 0x%016lX\n", - mops_idx, expected_block_write, mops_ptr[mops_idx]); - printf(" qwords_affected=%ld, dst_aligned=0x%lX\n", qwords_affected, dst_aligned); - return false; - } - - return true; -} - -bool test_fast_memset_single(size_t dst_offset, size_t count, uint8_t value) { - constexpr size_t BUFFER_SIZE = 2048; - constexpr size_t TEST_AREA_START = 512; - constexpr uint8_t PATTERN_START = 0x37; - - // Allocate buffer and fill with pattern to detect overwrites - AlignedBuffer buffer(BUFFER_SIZE); - buffer.fill_pattern(PATTERN_START); - - // Calculate destination address - uint64_t dst = reinterpret_cast(buffer.byte_ptr() + TEST_AREA_START) + dst_offset; - - // Call the function - fast_memset(dst, value, count); - - // Verify that the memset was performed correctly - if (!buffer.verify_fill(value, TEST_AREA_START + dst_offset, count, "memset result")) { - printf("❌ TEST FAILED: dst_offset=%ld, count=%ld, value=0x%02X\n", - dst_offset, count, value); - return false; - } - - // Verify that nothing outside the target area was modified - if (!buffer.verify_pattern_except(PATTERN_START, TEST_AREA_START + dst_offset, count, - "buffer overflow detection")) { - printf("❌ TEST FAILED (OVERFLOW): dst_offset=%ld, count=%ld, value=0x%02X\n", - dst_offset, count, value); - return false; - } - return true; -} - -bool test_memset_mops_single(size_t dst_offset, size_t count, uint8_t value) { - constexpr size_t BUFFER_SIZE = 2048; - constexpr size_t TEST_AREA_START = 512; - constexpr uint8_t PATTERN_START = 0x37; - - // Allocate buffer and fill with pattern to detect overwrites - AlignedBuffer buffer(BUFFER_SIZE); - buffer.fill_pattern(PATTERN_START); - - // Allocate mops trace buffer (16 u64 entries as specified) - AlignedBuffer mops_buffer(16 * sizeof(uint64_t)); - mops_buffer.fill_value(0); - - // Calculate destination address - uint64_t dst = reinterpret_cast(buffer.byte_ptr() + TEST_AREA_START) + dst_offset; - uint64_t* mops_ptr = mops_buffer.aligned_ptr(); - - // Call the function - uint64_t mops_count = dma_xmemset_mops(dst, value, count, mops_ptr); - - // Verify that the memset was performed correctly - if (!buffer.verify_fill(value, TEST_AREA_START + dst_offset, count, "memset result")) { - printf("❌ TEST FAILED: dst_offset=%ld, count=%ld, value=0x%02X\n", - dst_offset, count, value); - return false; - } - - // Verify that nothing outside the target area was modified - if (!buffer.verify_pattern_except(PATTERN_START, TEST_AREA_START + dst_offset, count, - "buffer overflow detection")) { - printf("❌ TEST FAILED (OVERFLOW): dst_offset=%ld, count=%ld, value=0x%02X\n", - dst_offset, count, value); - return false; - } - - // Validate mops trace - if (!validate_mops_trace(dst, count, mops_ptr, mops_count)) { - printf("❌ TEST FAILED (MOPS): dst=0x%08lX dst_offset=%ld, count=%ld\n", - dst, dst_offset, count); - return false; - } - - return true; -} - -void test_all_combinations() { - constexpr uint8_t TEST_VALUE = 0xAB; - size_t total_tests = 0; - size_t passed_tests = 0; - size_t failed_tests = 0; - - std::cout << "Starting comprehensive memset_mops tests...\n"; - std::cout << "Testing dst_offsets 0-7 × lengths 0-1024\n"; - std::cout << "Total tests: " << (8 * 1025) << "\n\n"; - - for (size_t dst_offset = 0; dst_offset <= 7; ++dst_offset) { - std::cout << "Testing dst_offset=" << dst_offset << "...\n"; - - for (size_t count = 0; count <= 1024; ++count) { - total_tests += 2; - - if (test_fast_memset_single(dst_offset, count, TEST_VALUE)) { - passed_tests++; - } else { - failed_tests++; - std::cout << "❌ FAILED: memset offset=" << dst_offset << " count=" << count << "\n"; - // Don't stop on first failure, continue testing - } - if (test_memset_mops_single(dst_offset, count, TEST_VALUE)) { - passed_tests++; - } else { - failed_tests++; - std::cout << "❌ FAILED: memset_ops offset=" << dst_offset << " count=" << count << "\n"; - // Don't stop on first failure, continue testing - } - // Progress indicator every 128 tests - if (count % 128 == 0 && count > 0) { - std::cout << " ... progress: " << count << "/1024" << std::endl; - } - } - } - - std::cout << "\n========================================\n"; - std::cout << "TEST SUMMARY\n"; - std::cout << "========================================\n"; - std::cout << "Total tests: " << total_tests << "\n"; - std::cout << "Passed: " << passed_tests << " ✅\n"; - std::cout << "Failed: " << failed_tests << " ❌\n"; - std::cout << "Success rate: " << (100.0 * passed_tests / total_tests) << "%\n"; - std::cout << "========================================\n"; - - if (failed_tests == 0) { - std::cout << "\n🎉 ALL TESTS PASSED! 🎉\n"; - } else { - std::cout << "\n⚠️ SOME TESTS FAILED ⚠️\n"; - exit(1); - } -} - -int main() { - std::cout << "==============================================\n"; - std::cout << " DMA MEMSET MOPS COMPREHENSIVE TEST SUITE\n"; - std::cout << "==============================================\n\n"; - - test_all_combinations(); - - return 0; -} diff --git a/emulator-asm/src/dma/test_dma_memset_mtrace b/emulator-asm/src/dma/test_dma_memset_mtrace deleted file mode 100755 index db4b56896ea77bd75e78797bae45f3f17dad889c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 110824 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

D$dLIopR4$Ugz!bIX%ptzXnFV`A?V@SG+2SW{ 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 -#include -#include -#include -#include -#include - -// External assembly function declarations -extern "C" { - uint64_t trace_address_threshold = 0; - void fast_memset(uint64_t dst, uint8_t value, uint64_t count); - uint64_t dma_xmemset_mtrace(uint64_t dst, uint8_t value, uint64_t count, uint64_t* mtrace_ptr); - uint64_t fast_dma_encode_memset_with_byte(uint64_t dst, uint8_t value, uint64_t count); -} - -const uint64_t ALIGN_MASK = 0xFFFF'FFFF'FFFF'FFF8ULL; - -// Helper class to manage aligned test buffers -class AlignedBuffer { -public: - std::vector data; - - AlignedBuffer(size_t size) : data(size, 0) {} - - uint64_t* aligned_ptr() { - return reinterpret_cast(data.data()); - } - - uint8_t* byte_ptr() { - return data.data(); - } - - const uint8_t* byte_ptr() const { - return data.data(); - } - - void fill_pattern(uint8_t start = 0) { - for (size_t i = 0; i < data.size(); ++i) { - data[i] = static_cast(start + i); - } - } - - void fill_value(uint8_t value) { - std::fill(data.begin(), data.end(), value); - } - - bool verify_pattern(uint8_t start = 0, const char *title = "") { - for (size_t i = 0; i < data.size(); ++i) { - uint8_t expected = static_cast(start + i); - if (data[i] != expected) { - printf("❌ FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X\n", - title, expected, i, data[i]); - return false; - } - } - return true; - } - - bool verify_pattern_except(uint8_t start, size_t from, size_t count, const char *title = "") { - size_t to = from + count; - for (size_t i = 0; i < data.size(); ++i) { - if (i >= from && i < to) continue; - uint8_t expected = static_cast(start + i); - if (data[i] != expected) { - printf("❌ FAIL PATTERN VERIFICATION of %s: Expected: 0x%02X vs data[%ld]=0x%02X (from=%ld, count=%ld)\n", - title, expected, i, data[i], from, count); - return false; - } - } - return true; - } - - bool verify_fill(uint8_t value, size_t from, size_t count, const char *title = "") { - size_t to = from + count; - for (size_t i = from; i < to; ++i) { - if (data[i] != value) { - printf("❌ FAIL FILL VERIFICATION of %s at [%ld]: Expected: 0x%02X vs 0x%02X (from=%ld, count=%ld)\n", - title, i, value, data[i], from, count); - return false; - } - } - return true; - } - - bool verify_fill_except(uint8_t value, size_t from, size_t count, const char *title = "") { - size_t to = from + count; - for (size_t i = 0; i < data.size(); ++i) { - if (i >= from && i < to) continue; - if (data[i] != value) { - printf("❌ FAIL FILL VERIFICATION of %s at [%ld]: Expected: 0x%02X vs 0x%02X (should be outside from=%ld, count=%ld)\n", - title, i, value, data[i], from, count); - return false; - } - } - return true; - } -}; - -void print_mtrace(uint64_t* mtrace_ptr, size_t mtrace_count) { - std::cout << "MTRACE (" << mtrace_count << " entries):\n"; - for (size_t i = 0; i < mtrace_count; ++i) { - uint64_t entry = mtrace_ptr[i]; - printf(" [%ld] 0x%016lX\n", i, entry); - } -} - -bool validate_mtrace(uint64_t dst, uint64_t count, uint8_t value, - uint64_t* mtrace_ptr, size_t mtrace_count, - const AlignedBuffer& buffer, size_t test_area_start, size_t dst_offset) { - - // Expected encode value from fast_dma_encode_memset_with_byte - uint64_t expected_encode = fast_dma_encode_memset_with_byte(dst, value, count); - - if (mtrace_count == 0) { - printf("❌ FAIL: Expected at least 1 mtrace entry (encode), got 0\n"); - return false; - } - - // Verify encode (first entry) - if (mtrace_ptr[0] != expected_encode) { - printf("❌ FAIL: ENCODE mtrace[0]: expected 0x%016lX, got 0x%016lX\n", - expected_encode, mtrace_ptr[0]); - printf(" dst=0x%lX, count=%ld, value=0x%02X, dst_offset=%ld\n", - dst, count, value, dst_offset); - print_mtrace(mtrace_ptr, mtrace_count); - return false; - } - - uint64_t dst_aligned = dst & ALIGN_MASK; - - // For count=0, only encode is generated, no PRE/POST - if (count == 0) { - if (mtrace_count != 1) { - printf("❌ FAIL: Expected 1 mtrace entry for count=0, got %ld\n", mtrace_count); - print_mtrace(mtrace_ptr, mtrace_count); - return false; - } - return true; - } - - // Determine if we need PRE and POST reads - bool needs_pre = (dst_offset > 0); - bool needs_post = ((dst_offset + count) & 0x07) != 0 & (dst_offset == 0 || (dst_offset + count) > 8); - - size_t expected_entries = 1; // Always have encode - size_t mtrace_idx = 1; - - if (needs_pre) { - expected_entries++; - } - if (needs_post) { - expected_entries++; - } - - if (mtrace_count != expected_entries) { - printf("❌ FAIL: Expected %ld mtrace entries, got %ld\n", expected_entries, mtrace_count); - printf(" dst=0x%lX, count=%ld, value=0x%02X, dst_offset=%ld\n", - dst, count, value, dst_offset); - printf(" needs_pre=%d, needs_post=%d\n", needs_pre, needs_post); - print_mtrace(mtrace_ptr, mtrace_count); - return false; - } - - // Verify PRE entry if expected - if (needs_pre) { - // PRE contains the value read from the first aligned qword before modification - // We can't easily verify the exact value since it's read before memset - // Just verify the entry exists - if (mtrace_idx >= mtrace_count) { - printf("❌ FAIL: Expected PRE entry at mtrace[%ld], but only %ld entries\n", - mtrace_idx, mtrace_count); - return false; - } - mtrace_idx++; - } - - // Verify POST entry if expected - if (needs_post) { - if (mtrace_idx >= mtrace_count) { - printf("❌ FAIL: Expected POST entry at mtrace[%ld], but only %ld entries\n", - mtrace_idx, mtrace_count); - return false; - } - mtrace_idx++; - } - - return true; -} - -bool test_memset_mtrace_single(size_t dst_offset, size_t count, uint8_t value) { - constexpr size_t BUFFER_SIZE = 2048; - constexpr size_t TEST_AREA_START = 512; - constexpr uint8_t PATTERN_START = 0x37; - - // Allocate buffer and fill with pattern to detect overwrites - AlignedBuffer buffer(BUFFER_SIZE); - buffer.fill_pattern(PATTERN_START); - - // Allocate mtrace buffer (max 3 u64 entries: encode + PRE + POST) - AlignedBuffer mtrace_buffer(3 * sizeof(uint64_t)); - mtrace_buffer.fill_value(0); - - // Calculate destination address - uint64_t dst = reinterpret_cast(buffer.byte_ptr() + TEST_AREA_START) + dst_offset; - uint64_t* mtrace_ptr = mtrace_buffer.aligned_ptr(); - printf("TEST: dst_offset=%ld, count=%ld, value=0x%02X\n", dst_offset, count, value); - - // Call the function - uint64_t mtrace_count = dma_xmemset_mtrace(dst, value, count, mtrace_ptr); - - // Verify that the memset was performed correctly - if (!buffer.verify_fill(value, TEST_AREA_START + dst_offset, count, "memset result")) { - printf("❌ TEST FAILED: dst_offset=%ld, count=%ld, value=0x%02X\n", - dst_offset, count, value); - return false; - } - - // Verify that nothing outside the target area was modified - if (!buffer.verify_pattern_except(PATTERN_START, TEST_AREA_START + dst_offset, count, - "buffer overflow detection")) { - printf("❌ TEST FAILED (OVERFLOW): dst_offset=%ld, count=%ld, value=0x%02X\n", - dst_offset, count, value); - return false; - } - - // Validate mtrace - if (!validate_mtrace(dst, count, value, mtrace_ptr, mtrace_count, - buffer, TEST_AREA_START, dst_offset)) { - printf("❌ TEST FAILED (MTRACE): dst_offset=%ld, count=%ld, value=0x%02X\n", - dst_offset, count, value); - return false; - } - - return true; -} - -void print_progress(int current, int total) { - if (current % 100 == 0 || current == total) { - std::cout << "Progress: " << current << "/" << total - << " (" << (current * 100 / total) << "%)\r" << std::flush; - } -} - -int main() { - std::cout << "==============================================\n"; - std::cout << " DMA MEMSET MTRACE COMPREHENSIVE TEST SUITE\n"; - std::cout << "==============================================\n\n"; - - int total_tests = 0; - int passed_tests = 0; - int failed_tests = 0; - - // Test parameters - const int max_count = 1024; - const int max_offset = 7; - const int num_values = 16; // Test 16 different byte values instead of all 256 - - // Test specific values: 0x00, 0xFF, and some random values - const uint8_t test_values[] = {0x00, 0x01, 0x10, 0x37, 0x55, 0x7F, 0x80, 0xAA, - 0xAB, 0xC0, 0xCD, 0xEF, 0xF0, 0xFE, 0xFF, 0x42}; - - std::cout << "Test configuration:\n"; - std::cout << " - Destination offsets: 0-" << max_offset << "\n"; - std::cout << " - Byte counts: 0-" << max_count << "\n"; - std::cout << " - Test values: " << num_values << " different bytes\n"; - std::cout << " - Total tests: " << ((max_offset + 1) * (max_count + 1) * num_values) << "\n\n"; - - std::cout << "Running tests...\n"; - - // Test all combinations - for (size_t value_idx = 0; value_idx < num_values; ++value_idx) { - uint8_t value = test_values[value_idx]; - - for (size_t dst_offset = 0; dst_offset <= max_offset; ++dst_offset) { - for (size_t count = 0; count <= max_count; ++count) { - total_tests++; - print_progress(total_tests, (max_offset + 1) * (max_count + 1) * num_values); - - if (test_memset_mtrace_single(dst_offset, count, value)) { - passed_tests++; - } else { - failed_tests++; - std::cout << "\n❌ FAILED: dst_offset=" << dst_offset - << ", count=" << count - << ", value=0x" << std::hex << (int)value << std::dec << "\n"; - - // Stop on first failure for debugging - if (failed_tests >= 1) { - std::cout << "\nStopping on first failure for debugging.\n"; - std::cout << "You can debug with: gdb ./test_dma_memset_mtrace\n"; - goto done; - } - } - } - } - } - -done: - std::cout << "\n\n==============================================\n"; - std::cout << " Test Results\n"; - std::cout << "==============================================\n"; - std::cout << "Total tests: " << total_tests << "\n"; - std::cout << "Passed: " << passed_tests << " ✅\n"; - std::cout << "Failed: " << failed_tests << " ❌\n"; - - if (failed_tests == 0) { - std::cout << "\n🎉 ALL TESTS PASSED! 🎉\n"; - } else { - std::cout << "\n⚠️ SOME TESTS FAILED ⚠️\n"; - return 1; - } - - return 0; -} diff --git a/emulator-asm/src/dma/test_fast_memcpy b/emulator-asm/src/dma/test_fast_memcpy deleted file mode 100755 index fac30802be6206385cab28e851a6807eb1b30a70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75800 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 -#include -#include -#include -#include -#include - -// External assembly function declaration -extern "C" { - void fast_memcpy(uint64_t dst, uint64_t src, uint64_t count); -} - -// Helper class to manage aligned test buffers -class AlignedBuffer { -public: - std::vector data; - - AlignedBuffer(size_t size) : data(size, 0) {} - - uint64_t* aligned_ptr() { - return reinterpret_cast(data.data()); - } - - uint8_t* byte_ptr() { - return data.data(); - } - - void fill_pattern(uint8_t start = 0) { - for (size_t i = 0; i < data.size(); ++i) { - data[i] = static_cast(start + i); - } - } - - void fill_value(uint8_t value) { - std::fill(data.begin(), data.end(), value); - } -}; - -bool test_fast_memcpy_single(uint64_t dst_offset, uint64_t src_offset, size_t count) { - // Allocate buffers with extra space for offsets - AlignedBuffer src_buf(2048); - AlignedBuffer dst_buf(2048); - - // Fill source with pattern starting at 0x10 - src_buf.fill_pattern(0x10); - - // Fill destination with different pattern (0xA0) - dst_buf.fill_pattern(0xA0); - - // Calculate actual addresses with offsets - uint64_t src_addr = reinterpret_cast(src_buf.byte_ptr() + 64) + src_offset; - uint64_t dst_addr = reinterpret_cast(dst_buf.byte_ptr() + 64) + dst_offset; - - // Call assembly function - fast_memcpy(dst_addr, src_addr, count); - - // Verify the memcpy was performed correctly - const uint8_t* src_bytes = reinterpret_cast(src_addr); - const uint8_t* dst_bytes = reinterpret_cast(dst_addr); - - bool ok = true; - for (size_t i = 0; i < count; ++i) { - if (dst_bytes[i] != src_bytes[i]) { - std::cout << "❌ FAIL at byte " << i - << " dst_off=" << dst_offset - << " src_off=" << src_offset - << " count=" << count - << " expected=0x" << std::hex << (int)src_bytes[i] - << " got=0x" << (int)dst_bytes[i] << std::dec << "\n"; - ok = false; - break; - } - } - - if (!ok) { - // Print context around failure - std::cout << "Source bytes around copy region:\n"; - for (size_t i = 0; i < std::min(count, size_t(32)); ++i) { - if (i % 16 == 0) std::cout << " "; - std::cout << std::hex << std::setw(2) << std::setfill('0') - << (int)src_bytes[i] << " "; - if (i % 16 == 15) std::cout << "\n"; - } - std::cout << "\nDestination bytes around copy region:\n"; - for (size_t i = 0; i < std::min(count, size_t(32)); ++i) { - if (i % 16 == 0) std::cout << " "; - std::cout << std::hex << std::setw(2) << std::setfill('0') - << (int)dst_bytes[i] << " "; - if (i % 16 == 15) std::cout << "\n"; - } - std::cout << std::dec << "\n"; - - // Also verify that areas outside the copy region weren't touched - const uint8_t* dst_base = dst_buf.byte_ptr() + 64; - size_t offset_in_buf = dst_bytes - dst_base; - - // Check before region - for (size_t i = 0; i < offset_in_buf; ++i) { - uint8_t expected = static_cast(0xA0 + i); - if (dst_base[i] != expected) { - std::cout << "❌ Buffer corruption BEFORE copy region at " << i << "\n"; - break; - } - } - - // Check after region - for (size_t i = offset_in_buf + count; i < 128; ++i) { - uint8_t expected = static_cast(0xA0 + i); - if (dst_base[i] != expected) { - std::cout << "❌ Buffer corruption AFTER copy region at " << i << "\n"; - break; - } - } - } - - return ok; -} - -void print_progress(int current, int total) { - if (current % 100 == 0 || current == total) { - std::cout << "Progress: " << current << "/" << total - << " (" << (current * 100 / total) << "%)\r" << std::flush; - } -} - -int main() { - std::cout << "==============================================\n"; - std::cout << " Testing fast_memcpy assembly implementation\n"; - std::cout << " (Duff's device based implementation)\n"; - std::cout << "==============================================\n\n"; - - int total_tests = 0; - int passed_tests = 0; - int failed_tests = 0; - - // Test parameters - const int max_count = 1024; - const int count_step = 1; // Test every byte count - const int max_offset = 7; // Test offsets 0-7 - - std::cout << "Test configuration:\n"; - std::cout << " - Destination offsets: 0-" << max_offset << "\n"; - std::cout << " - Source offsets: 0-" << max_offset << "\n"; - std::cout << " - Byte counts: 0-" << max_count << " (step=" << count_step << ")\n"; - std::cout << " - Total tests: " << ((max_offset + 1) * (max_offset + 1) * ((max_count / count_step) + 1)) << "\n\n"; - - std::cout << "Running tests...\n"; - - // Test all combinations of dst_offset, src_offset, and count - for (int dst_off = 0; dst_off <= max_offset; ++dst_off) { - for (int src_off = 0; src_off <= max_offset; ++src_off) { - for (int count = 0; count <= max_count; count += count_step) { - total_tests++; - print_progress(total_tests, ((max_offset + 1) * (max_offset + 1) * ((max_count / count_step) + 1))); - std::cout << "\nTEST: dst_offset=" << dst_off - << ", src_offset=" << src_off - << ", count=" << count << "\n"; - if (test_fast_memcpy_single(dst_off, src_off, count)) { - passed_tests++; - } else { - failed_tests++; - std::cout << "\n❌ FAILED: dst_offset=" << dst_off - << ", src_offset=" << src_off - << ", count=" << count << "\n"; - - // Stop on first failure for debugging - if (failed_tests >= 1) { - std::cout << "\nStopping on first failure for debugging.\n"; - std::cout << "You can debug with: gdb ./test_fast_memcpy\n"; - std::cout << " Break at: break test_fast_memcpy_single\n"; - goto done; - } - } - } - } - } - -done: - std::cout << "\n\n==============================================\n"; - std::cout << " Test Results\n"; - std::cout << "==============================================\n"; - std::cout << "Total tests: " << total_tests << "\n"; - std::cout << "Passed: " << passed_tests << " ✅\n"; - std::cout << "Failed: " << failed_tests << " ❌\n"; - - if (failed_tests == 0) { - std::cout << "\n🎉 ALL TESTS PASSED! 🎉\n"; - return 0; - } else { - std::cout << "\n❌ SOME TESTS FAILED ❌\n"; - return 1; - } -} diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index b06ffb5be..58cb45422 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "1654b031412800684af604b3948bb3789b00f66cb4d0d88b1a980c98a3d2c8fe"; +pub const PILOUT_HASH: &str = "afb474f9c9b38252b8443eabb79061a3d02ed2fc237a247a09c9d26d3c420e6b"; pub const MERKLE_TREE_ARITY: u64 = 4; @@ -282,7 +282,7 @@ trace_row!(DmaPrePostMemCpyFixedRow { pub type DmaPrePostMemCpyFixed = GenericTrace, 2097152, 0, 10>; trace_row!(DmaPrePostMemCpyTraceRow { - main_step:ubit(36), dst64:ubit(29), dst_offset:ubit(3), count:ubit(3), is_post:bit, sel_memcpy:bit, selr:[bit; 7], dst_offset_gt_src_offset:bit, src64:ubit(29), src_offset:ubit(3), enabled_second_read:bit, rb:[u8; 16], pb:[u8; 8], sb:[bit; 8], bus_write_value:[u32; 2], write_value:[u32; 4], + main_step:ubit(36), dst64:ubit(29), dst_offset:ubit(3), count:ubit(4), is_post:bit, sel_memcpy:bit, selr:[bit; 7], dst_offset_gt_src_offset:bit, src64:ubit(29), src_offset:ubit(3), enabled_second_read:bit, rb:[u8; 16], pb:[u8; 8], sb:[bit; 8], bus_write_value:[u32; 2], write_value:[u32; 4], }); pub type DmaPrePostMemCpyTrace = GenericTrace, 2097152, 0, 10>; @@ -296,7 +296,7 @@ trace_row!(DmaPrePostInputCpyFixedRow { pub type DmaPrePostInputCpyFixed = GenericTrace, 2097152, 0, 11>; trace_row!(DmaPrePostInputCpyTraceRow { - main_step:ubit(36), dst64:ubit(29), dst_offset:ubit(3), count:ubit(3), is_post:bit, sel_inputcpy:bit, rb:[u8; 8], pb:[u8; 8], sb:[bit; 8], bus_write_value:[u32; 2], + main_step:ubit(36), dst64:ubit(29), dst_offset:ubit(3), count:ubit(4), is_post:bit, sel_inputcpy:bit, rb:[u8; 8], pb:[u8; 8], sb:[bit; 8], bus_write_value:[u32; 2], }); pub type DmaPrePostInputCpyTrace = GenericTrace, 2097152, 0, 11>; @@ -860,12 +860,12 @@ pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ (0, 10, PackedInfoConst { is_packed: true, num_packed_words: 8, - unpack_info: &[36, 29, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 29, 3, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 32], + unpack_info: &[36, 29, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 29, 3, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32, 32, 32, 32, 32], }), (0, 11, PackedInfoConst { is_packed: true, num_packed_words: 5, - unpack_info: &[36, 29, 3, 3, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32], + unpack_info: &[36, 29, 3, 4, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 32, 32], }), (0, 12, PackedInfoConst { is_packed: true, diff --git a/precompiles/dma/src/dma/dma_collector.rs b/precompiles/dma/src/dma/dma_collector.rs index 91e8bb4bb..7cece1b88 100644 --- a/precompiles/dma/src/dma/dma_collector.rs +++ b/precompiles/dma/src/dma/dma_collector.rs @@ -86,7 +86,7 @@ impl DmaCollector { } if self.collect_counters.should_collect_single_row(op) { - self.rlog.log_collect(1, data); + self.rlog.log_collect(1, data, 0, 0); self.inputs.push(if op == ZiskOp::DMA_XMEMSET { DmaInput::from_memset(encoded, op, data, data_ext) } else { diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs index 76397fd61..1e929c773 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_collector.rs @@ -109,7 +109,7 @@ impl Dma64AlignedCollector { } // self.collect_counters.memcpy.should_process(rows) if let Some((skip, max_count)) = self.collect_counters.should_collect(rows as u64, op) { - self.rlog.log_collect(rows, data); + self.rlog.log_collect(rows as u32, data, skip, max_count); self.add_input(match op { ZiskOp::DMA_XMEMSET => Dma64AlignedInput::from_memset( data, diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs index f2b8371d3..19c1e0651 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs @@ -162,8 +162,18 @@ impl Dma64AlignedInput { // Write header writeln!( file, - "{:>8}|{:>10}|{:>10}|{:>22}|{:>21}|{:>4}|{:>12}|{:>9}|{:>8}|{:>14}|{:>18}|{:>9}|src_values", - "pos", "src", "dst", "is_last_instance_input", "op", "trace_offset", "skip_rows", "rows", "step", "encoded", "fill_byte" + "{:>8}|{:>10}|{:>10}|{:>22}|{:>4}|{:>12}|{:>9}|{:>8}|{:>14}|{:>18}|{:>9}|src_values", + "pos", + "src", + "dst", + "is_last_input", + "op", + "trace_offset", + "skip_rows", + "rows", + "step", + "encoded", + "fill_byte" )?; // Write data rows @@ -172,7 +182,7 @@ impl Dma64AlignedInput { input.src_values.iter().map(|v| format!("0x{:016X}", v)).collect(); writeln!( file, - "{:>8}|0x{:08X}|0x{:08X}|{:>22}|{:>21}|{:>4}|{:>12}|{:>9}|{:>8}|{:>14}|0x{:016X}|{:>9}|{}", + "{:>8}|0x{:08X}|0x{:08X}|{:>22}|{:>4}|{:>12}|{:>9}|{:>8}|{:>14}|0x{:016X}|{:>9}|{}", pos, input.src, input.dst, diff --git a/precompiles/dma/src/dma_collector_routing_log.rs b/precompiles/dma/src/dma_collector_routing_log.rs index ae96e7024..642423151 100644 --- a/precompiles/dma/src/dma_collector_routing_log.rs +++ b/precompiles/dma/src/dma_collector_routing_log.rs @@ -7,7 +7,7 @@ use zisk_common::STEP; #[derive(Debug)] pub struct DmaCollectorRoutingLog { pub chunk_id: ChunkId, - pub log: Vec<(u8, u64, usize)>, + pub log: Vec<(u8, u64, u32, u32, u32)>, } #[cfg(not(feature = "save_dma_collectors"))] @@ -20,7 +20,7 @@ impl DmaCollectorRoutingLog { Self {} } #[inline(always)] - pub fn log_collect(&mut self, _rows: usize, _data: &[u64]) {} + pub fn log_collect(&mut self, _rows: u32, _data: &[u64], _skip: u32, _max_count: u32) {} #[inline(always)] pub fn log_discard(&mut self, _reason: u8, _data: &[u64]) {} #[inline(always)] @@ -44,9 +44,9 @@ impl DmaCollectorRoutingLog { pub fn get_debug_info(&self) -> String { self.log .iter() - .map(|(reason, step, rows)| { + .map(|(reason, step, rows, skip, max_count)| { format!( - "{}|{reason}|@{}|C:{rows}|S:{step}", + "{}|{reason}|@{}|C:{rows}|K:{skip}|M:{max_count}|S:{step}", if *reason == 0 { "COLLECT" } else { "SKIP" }, self.chunk_id ) @@ -58,17 +58,17 @@ impl DmaCollectorRoutingLog { #[inline(always)] pub fn log_discard(&mut self, reason: u8, data: &[u64]) { - self.log.push((reason, data[zisk_common::STEP], 0)); + self.log.push((reason, data[zisk_common::STEP], 0, 0, 0)); } #[inline(always)] - pub fn log_collect(&mut self, rows: usize, data: &[u64]) { - self.log.push((0, data[zisk_common::STEP], rows)); + pub fn log_collect(&mut self, rows: u32, data: &[u64], skip: u32, max_count: u32) { + self.log.push((0, data[zisk_common::STEP], rows, skip, max_count)); } #[inline(always)] pub fn log_discard_cond(&mut self, cond: bool, reason: u8, data: &[u64], result: bool) -> bool { - self.log.push((reason + cond as u8, data[STEP], 0)); + self.log.push((reason + cond as u8, data[STEP], 0, 0, 0)); result } } diff --git a/precompiles/dma/src/dma_instances_builder.rs b/precompiles/dma/src/dma_instances_builder.rs index c1fc26a78..90358d526 100644 --- a/precompiles/dma/src/dma_instances_builder.rs +++ b/precompiles/dma/src/dma_instances_builder.rs @@ -51,6 +51,10 @@ impl DmaInstancesBuilder { self.skip_memset_rows += self.count_memset_rows; self.skip_memcmp_rows += self.count_memcmp_rows; self.skip_inputcpy_rows += self.count_inputcpy_rows; + self.count_memcpy_rows = 0; + self.count_memset_rows = 0; + self.count_memcmp_rows = 0; + self.count_inputcpy_rows = 0; } pub fn reset_count_and_skip(&mut self) { self.skip_memcpy_rows = 0; @@ -94,30 +98,29 @@ impl DmaInstancesBuilder { if self.instances.is_empty() { self.open_new_instance(); } - self.instances.last_mut().unwrap().chunks.insert( - chunk_id, - ( - self.inputs_counter as u64, - DmaCollectCounters { - memcpy: CollectCounter::new( - self.skip_memcpy_rows as u32, - self.count_memcpy_rows as u32, - ), - inputcpy: CollectCounter::new( - self.skip_inputcpy_rows as u32, - self.count_inputcpy_rows as u32, - ), - memset: CollectCounter::new( - self.skip_memset_rows as u32, - self.count_memset_rows as u32, - ), - memcmp: CollectCounter::new( - self.skip_memcmp_rows as u32, - self.count_memcmp_rows as u32, - ), - }, + let collect_counters = DmaCollectCounters { + memcpy: CollectCounter::new( + self.skip_memcpy_rows as u32, + self.count_memcpy_rows as u32, ), - ); + inputcpy: CollectCounter::new( + self.skip_inputcpy_rows as u32, + self.count_inputcpy_rows as u32, + ), + memset: CollectCounter::new( + self.skip_memset_rows as u32, + self.count_memset_rows as u32, + ), + memcmp: CollectCounter::new( + self.skip_memcmp_rows as u32, + self.count_memcmp_rows as u32, + ), + }; + self.instances + .last_mut() + .unwrap() + .chunks + .insert(chunk_id, (self.inputs_counter as u64, collect_counters)); self.instances.last_mut().unwrap().last_chunk = Some(chunk_id); } } diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs index c42c06423..9765897b5 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post.rs @@ -229,7 +229,7 @@ impl DmaPrePostSM { } else { 0 }; - let is_negative = DmaInfo::is_memcmp_negative(input.encoded); + let is_negative = result != 0 && DmaInfo::is_memcmp_negative(input.encoded); let is_nz = result != 0; trace.set_memcmp_result_is_negative(is_negative); trace.set_memcmp_result_nz(is_nz); diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs index ed49dc99c..df3b54777 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_collector.rs @@ -86,7 +86,7 @@ impl DmaPrePostCollector { } if let Some((skip, max_count)) = self.collect_counters.should_collect(rows as u64, op) { - self.rlog.log_collect(rows, data); + self.rlog.log_collect(rows as u32, data, skip, max_count); self.inputs.extend(match op { ZiskOp::DMA_XMEMSET => { DmaPrePostInput::from_memset(data, data_ext, skip, max_count) diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs index d77956727..d62d49e99 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_inputcpy.rs @@ -141,38 +141,6 @@ impl DmaPrePostInputCpySM { let table_row = DmaPrePostRom::get_row(dst_offset as usize, 0, count, false, false, false); // println!("PRE-POST-ROM [{table_row}] dst_offset: {dst_offset} src_offset: {src_offset} count: {count}"); pre_post_table_mul[table_row] += 1; - - // println!("DMA_PRE_POST: bytes={bytes:?} selr_value={selr_value} mask=0x{mask:016X}"); - // println!( - // "DMA_PRE_POST: read_value_01=0x{read_value_01:016X} read_value_23=0x{read_value_23:016X}" - // ); - // println!("DMA_PRE_POST: write_value_xx=[0x{write_value_01:016X},0x{write_value_23:016X}] dst_offset={dst_offset} src_offset={src_offset}"); - // println!( - // "DMA_PRE_POST: selb={:?}", - // [ - // ((mask & 0x0000_0000_0000_00FF) != 0) as u8, - // ((mask & 0x0000_0000_0000_FF00) != 0) as u8, - // ((mask & 0x0000_0000_00FF_0000) != 0) as u8, - // ((mask & 0x0000_0000_FF00_0000) != 0) as u8, - // ((mask & 0x0000_00FF_0000_0000) != 0) as u8, - // ((mask & 0x0000_FF00_0000_0000) != 0) as u8, - // ((mask & 0x00FF_0000_0000_0000) != 0) as u8, - // ((mask & 0xFF00_0000_0000_0000) != 0) as u8 - // ] - // ); - // println!( - // "DMA_PRE_POST: selread={:?}", - // [ - // (selr_value == 0) as u8, - // (selr_value == 1) as u8, - // (selr_value == 2) as u8, - // (selr_value == 3) as u8, - // (selr_value == 4) as u8, - // (selr_value == 5) as u8, - // (selr_value == 6) as u8, - // (selr_value == 7) as u8 - // ] - // ); } /// Processes a slice of operation data, updating the trace. diff --git a/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs b/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs index 47dae0a1e..8fc012189 100644 --- a/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs +++ b/precompiles/dma/src/dma_pre_post/dma_pre_post_instance.rs @@ -10,8 +10,7 @@ use crate::{DmaCheckPoint, DmaPrePostCollector, DmaPrePostModule}; use fields::PrimeField64; use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; use std::sync::Arc; -#[cfg(feature = "save_dma_inputs")] -use zisk_common::SegmentId; + use zisk_common::{ BusDevice, CheckPoint, ChunkId, Instance, InstanceCtx, InstanceType, PayloadType, StatsType, }; diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs index dac768002..b01a6759a 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned.rs @@ -214,7 +214,8 @@ impl DmaUnalignedSM { .iter() .map(|inputs| inputs.iter().map(|input| input.count as usize).sum::()) .sum(); - assert!(total_inputs <= num_rows); + + assert!(total_inputs <= num_rows, "total_inputs({total_inputs}) > num_rows({num_rows})"); assert!(total_inputs > 0); dma_trace("DmaUnaligned", total_inputs, num_rows); diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs index 75fc8bff9..810c74726 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_collector.rs @@ -80,6 +80,8 @@ impl DmaUnalignedCollector { return true; } + // Method get_count get the rows that applies, means that if a + // input has src, dst aligned not applies let rows = DmaUnalignedInput::get_count(data) as u64; if rows == 0 { return true; @@ -93,11 +95,11 @@ impl DmaUnalignedCollector { if self.inputs.len() == self.num_inputs as usize { self.collect_counters.debug_assert_is_final_skip(); - return self.rlog.log_discard_cond(false, 1, data, true); + return self.rlog.log_discard_cond(false, 3, data, true); } if let Some((skip, max_count)) = self.collect_counters.should_collect(rows, op) { - self.rlog.log_collect(rows as usize, data); + self.rlog.log_collect(rows as u32, data, skip, max_count); self.add_input(DmaUnalignedInput::from( data, data_ext, diff --git a/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs b/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs index 91adaa043..5a6e8dc79 100644 --- a/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs +++ b/precompiles/dma/src/dma_unaligned/dma_unaligned_input.rs @@ -96,7 +96,7 @@ impl DmaUnalignedInput { // Write header writeln!( file, - "{:>8}|{:>10}|{:>10}|{:>22}|{:>21}|{:>9}|{:>12}|{:>8}|{:>8}|{:>14}|{:>18}|src_values", + "{:>8}|{:>10}|{:>10}|{:>22}|{:>9}|{:>12}|{:>8}|{:>8}|{:>14}|{:>18}|src_values", "pos", "src", "dst", @@ -115,7 +115,7 @@ impl DmaUnalignedInput { input.src_values.iter().map(|v| format!("0x{:016X}", v)).collect(); writeln!( file, - "{:>8}|0x{:08X}|0x{:08X}|{:>22}|{:>21}|{:>9}|{:>12}|{:>8}|{:>8}|{:>14}|0x{:016X}|{}", + "{:>8}|0x{:08X}|0x{:08X}|{:>22}|{:>9}|{:>12}|{:>8}|{:>8}|{:>14}|0x{:016X}|{}", pos, input.src, input.dst, diff --git a/precompiles/helpers/src/dma.rs b/precompiles/helpers/src/dma.rs index 177b124e6..7c191c34b 100644 --- a/precompiles/helpers/src/dma.rs +++ b/precompiles/helpers/src/dma.rs @@ -33,16 +33,46 @@ pub struct DmaValues { // lpre_count 3 32-34 // loop_count 29 35-63 -const FAST_ENCODE_TABLE_WO_NEQ_SIZE: usize = 8 * 8 * 16; -const FAST_ENCODE_TABLE_SIZE: usize = FAST_ENCODE_TABLE_WO_NEQ_SIZE * 2; +const FAST_ENCODE_TABLE_SIZE: usize = 8 * 8 * 16; +const FAST_ENCODE_TABLE_MEMCMP_SIZE: usize = FAST_ENCODE_TABLE_SIZE * 2; const FAST_ENCODE_NO_SRC_TABLE_SIZE: usize = 8 * 16; const FAST_ENCODE_TABLE: [u64; FAST_ENCODE_TABLE_SIZE] = generate_fast_encode_table(); +const FAST_ENCODE_MEMCMP_TABLE: [u64; FAST_ENCODE_TABLE_MEMCMP_SIZE] = + generate_fast_encode_memcmp_table(); const FAST_ENCODE_NO_SRC_TABLE: [u64; FAST_ENCODE_NO_SRC_TABLE_SIZE] = generate_fast_encode_no_src_table(); const fn generate_fast_encode_table() -> [u64; FAST_ENCODE_TABLE_SIZE] { let mut table = [0u64; FAST_ENCODE_TABLE_SIZE]; // fill table + let mut dst_offset: u64 = 0; + while dst_offset < 8 { + let base_index = dst_offset << 7; + let mut src_offset: u64 = 0; + while src_offset < 8 { + let index = (base_index + (src_offset << 4)) as usize; + let mut count: usize = 0; + while count < 16 { + let value = DmaInfo::calculate_encode(dst_offset, src_offset, count, false, true); + let loop_count = DmaInfo::get_loop_count(value) as u64; + // The table is create to add directly de loop count and after all values + // are correct, for this reason substract de count, because we need diference + // between loop_count (shifted 32) and count (shifted 29) + table[index + count] = ((value & 0x0000_0007_FFFF_FFFF) + + (loop_count << DmaInfo::DMA_LOOP_COUNT_RS)) + .wrapping_sub((count as u64) << DmaInfo::DMA_LPRE_COUNT_RS); + count += 1; + } + src_offset += 1; + } + dst_offset += 1; + } + table +} + +const fn generate_fast_encode_memcmp_table() -> [u64; FAST_ENCODE_TABLE_MEMCMP_SIZE] { + let mut table = [0u64; FAST_ENCODE_TABLE_MEMCMP_SIZE]; + // fill table let mut neq_index = 0; while neq_index < 2 { let neq = neq_index != 0; @@ -55,7 +85,8 @@ const fn generate_fast_encode_table() -> [u64; FAST_ENCODE_TABLE_SIZE] { let index = (base_index + (src_offset << 4)) as usize; let mut count: usize = 0; while count < 16 { - let value = DmaInfo::calculate_encode(dst_offset, src_offset, count, neq, true); + let value = DmaInfo::calculate_encode(dst_offset, src_offset, count, neq, true) + | DmaInfo::DMA_REQUIRES_DMA_TEST_MASK; let loop_count = DmaInfo::get_loop_count(value) as u64; // The table is create to add directly de loop count and after all values // are correct, for this reason substract de count, because we need diference @@ -119,21 +150,19 @@ impl DmaInfo { #[inline(always)] pub const fn encode_memcmp_neq(dst: u64, src: u64, count: usize, neq: bool) -> u64 { let table_count = if count >= 16 { count & 0x07 | 0x08 } else { count }; - (FAST_ENCODE_TABLE[(((dst & 0x07) << 7) + ((src & 0x07) << 4)) as usize + (FAST_ENCODE_MEMCMP_TABLE[(((dst & 0x07) << 7) + ((src & 0x07) << 4)) as usize + table_count - + FAST_ENCODE_TABLE_WO_NEQ_SIZE * neq as usize] - + Self::DMA_REQUIRES_DMA_TEST_MASK) + + FAST_ENCODE_TABLE_SIZE * neq as usize]) .wrapping_add((count as u64) << Self::DMA_LPRE_COUNT_RS) } #[inline(always)] pub const fn encode_memcmp(dst: u64, src: u64, count: usize, result: u64) -> u64 { let table_count = if count >= 16 { count & 0x07 | 0x08 } else { count }; - (FAST_ENCODE_TABLE[(((dst & 0x07) << 7) + ((src & 0x07) << 4)) as usize + (FAST_ENCODE_MEMCMP_TABLE[(((dst & 0x07) << 7) + ((src & 0x07) << 4)) as usize + table_count - + FAST_ENCODE_TABLE_WO_NEQ_SIZE * (result != 0) as usize] - + Self::DMA_REQUIRES_DMA_TEST_MASK - + ((result & 0x1FF) << Self::DMA_FILL_BYTE_RS)) + + FAST_ENCODE_TABLE_SIZE * (result != 0) as usize] + + ((result & Self::DMA_FILL_BITS9_MASK) << Self::DMA_FILL_BYTE_RS)) .wrapping_add((count as u64) << Self::DMA_LPRE_COUNT_RS) } @@ -262,7 +291,7 @@ impl DmaInfo { if count == 0 { 0 } else { (((src + count - 1) >> 3) - (src >> 3) + 1) - loop_count }; let src64_inc_by_pre = (pre_count > 0 && (src_offset + pre_count) >= 8) as u64; - let unaligned_dst_src = (src_offset != dst_offset) as u64; + let unaligned_dst_src = (count > 0 && src_offset != dst_offset) as u64; if neq && post_count == 0 && loop_count > 0 { // (dst + count) 0x07 == 7 ==> (dst_offset + count) 0x07 == 7 ==> post_count == 0 @@ -786,6 +815,27 @@ mod tests { #[test] fn asm_fast_encode_table() { let table = generate_fast_encode_table(); + for i in 0..256 { + let dst_offset = (i >> 5) & 0x7; + let src_offset = (i >> 2) & 0x7; + println!( + "\t.quad 0x{:016x}, 0x{:016X}, 0x{:016X}, 0x{:016X} # {:4} - {:4} D{dst_offset} S{src_offset} C{}{}", + table[i * 4], + table[i * 4 + 1], + table[i * 4 + 2], + table[i * 4 + 3], + i * 4, + i * 4 + 3, + (i * 4) & 0xF, + if i >= 256 { " neq" } else { "" } + ); + } + assert!(table.len() == 1024); + } + + #[test] + fn asm_fast_encode_memcmp_table() { + let table = generate_fast_encode_memcmp_table(); for i in 0..512 { let dst_offset = (i >> 5) & 0x7; let src_offset = (i >> 2) & 0x7; @@ -858,7 +908,7 @@ mod tests { for dst in 0..256 { for src in 0..256 { for count in 0..256 { - let encode = DmaInfo::calculate_encode(dst, src, count, neq) + let encode = DmaInfo::calculate_encode(dst, src, count, neq, true) | DmaInfo::DMA_REQUIRES_DMA_MASK; let fast_encode = DmaInfo::encode_memcmp_neq(dst, src, count, neq); assert_eq!( diff --git a/state-machines/main/src/main_sm.rs b/state-machines/main/src/main_sm.rs index ec1506ba7..74eec2573 100644 --- a/state-machines/main/src/main_sm.rs +++ b/state-machines/main/src/main_sm.rs @@ -209,7 +209,6 @@ impl MainInstance { &mut large_range_checks, ); self.update_std_range_checks(segment_id, step_range_check, &large_range_checks); - // Generate and add the AIR instance let from_trace = FromTrace::new(&mut main_trace).with_air_values(&mut air_values); Ok(AirInstance::new_from_trace(from_trace)) diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 2cdb26a1e..87b069718 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -285,6 +285,6 @@ mod ziskos { core::arch::global_asm!(include_str!("dma/memcpy.s")); core::arch::global_asm!(include_str!("dma/memmove.s")); core::arch::global_asm!(include_str!("dma/memcmp.s")); - core::arch::global_asm!(include_str!("dma/inputcpy.s")); + //core::arch::global_asm!(include_str!("dma/inputcpy.s")); core::arch::global_asm!(include_str!("dma/memset.s")); } From 04d98b14952dc2c60cf1657b8368ed34a9d5f06e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:21:01 +0000 Subject: [PATCH 631/782] minor reset improvements --- distributed/crates/worker/src/stream_ordering.rs | 7 +++---- distributed/crates/worker/src/worker.rs | 10 ++++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/distributed/crates/worker/src/stream_ordering.rs b/distributed/crates/worker/src/stream_ordering.rs index 3fd1524f3..5a5dcd807 100644 --- a/distributed/crates/worker/src/stream_ordering.rs +++ b/distributed/crates/worker/src/stream_ordering.rs @@ -115,9 +115,8 @@ impl Drop for StreamOrderingActor { fn drop(&mut self) { // Drop the sender first so the thread's recv() returns Err and exits self.sender.take(); - // Then join to ensure the thread shuts down cleanly before we return - if let Some(handle) = self.thread_handle.take() { - let _ = handle.join(); - } + + // Drop the ordering thread, it will terminate promptly once the channel is closed. + self.thread_handle.take(); } } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 11ac9132c..d1fb91bd0 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -365,8 +365,14 @@ impl Worker { if let Some(handle) = self.current_computation.take() { handle.abort(); } - // Drop the actor: closes the channel, which signals the ordering thread to exit - self.stream_actor = None; + + // Drop the actor on a blocking thread: closes the channel, which signals the ordering + // thread to exit, without blocking the Tokio runtime worker thread. + if let Some(stream_actor) = self.stream_actor.take() { + tokio::task::spawn_blocking(move || { + drop(stream_actor); + }); + } } pub fn new_job( From d8d8a023ea5841da34f18af3ec649d48498d6602 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Tue, 24 Feb 2026 12:46:18 +0100 Subject: [PATCH 632/782] fix clippy error --- core/src/direct_calls.rs | 5 +---- executor/src/utils.rs | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/core/src/direct_calls.rs b/core/src/direct_calls.rs index e9b433159..0a5340622 100644 --- a/core/src/direct_calls.rs +++ b/core/src/direct_calls.rs @@ -1,9 +1,6 @@ use riscv::RiscvInstruction; -pub fn dma_direct_calls( - riscv_instructions: &Vec, - dma_addrs: (u64, u64, u64, u64), -) { +pub fn dma_direct_calls(riscv_instructions: &[RiscvInstruction], dma_addrs: (u64, u64, u64, u64)) { let (memcpy_addr, memcmp_addr, memset_addr, memmove_addr) = dma_addrs; // Detect AUIPC + JALR patterns that jump to DMA functions diff --git a/executor/src/utils.rs b/executor/src/utils.rs index 2635f33df..b49af58e8 100644 --- a/executor/src/utils.rs +++ b/executor/src/utils.rs @@ -24,11 +24,13 @@ use zisk_core::CHUNK_SIZE; use zisk_pil::PACKED_INFO; use zisk_pil::{ ADD_256_AIR_IDS, ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, - BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, DMA_64_ALIGNED_AIR_IDS, DMA_AIR_IDS, - DMA_PRE_POST_AIR_IDS, DMA_UNALIGNED_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, - MEM_ALIGN_AIR_IDS, MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, - MEM_ALIGN_WRITE_BYTE_AIR_IDS, POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, - SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, + BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, DMA_64_ALIGNED_AIR_IDS, + DMA_64_ALIGNED_INPUT_CPY_AIR_IDS, DMA_64_ALIGNED_MEM_AIR_IDS, DMA_64_ALIGNED_MEM_CPY_AIR_IDS, + DMA_64_ALIGNED_MEM_SET_AIR_IDS, DMA_AIR_IDS, DMA_INPUT_CPY_AIR_IDS, DMA_MEM_CPY_AIR_IDS, + DMA_PRE_POST_AIR_IDS, DMA_PRE_POST_INPUT_CPY_AIR_IDS, DMA_PRE_POST_MEM_CPY_AIR_IDS, + DMA_UNALIGNED_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, MEM_ALIGN_AIR_IDS, + MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, MEM_ALIGN_WRITE_BYTE_AIR_IDS, + POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, }; use anyhow::Result; @@ -109,6 +111,14 @@ fn initialize_executor( (ZISK_AIRGROUP_ID, DMA_PRE_POST_AIR_IDS[0]), (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_AIR_IDS[0]), (ZISK_AIRGROUP_ID, DMA_UNALIGNED_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_MEM_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_INPUT_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_PRE_POST_MEM_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_PRE_POST_INPUT_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_MEM_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_MEM_SET_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_INPUT_CPY_AIR_IDS[0]), + (ZISK_AIRGROUP_ID, DMA_64_ALIGNED_MEM_AIR_IDS[0]), ]; let sm_bundle = StaticSMBundle::new( From acaaf0476a06f442a042d21fc3f41b27d03e703d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Tue, 24 Feb 2026 12:52:20 +0100 Subject: [PATCH 633/782] Fixing poseidon2 (#813) * Fixing poseidon2 * Cargo fmt --- Cargo.lock | 22 +++++++++---------- common/src/bus/data_bus_operation.rs | 11 ++++++---- .../poseidon2/src/poseidon2_bus_device.rs | 4 ++-- .../poseidon2/src/poseidon2_gen_mem_inputs.rs | 6 ++--- precompiles/poseidon2/src/poseidon2_input.rs | 4 ++-- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a9300663..81b1a3469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" dependencies = [ "fields", "num-bigint", @@ -1468,7 +1468,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" dependencies = [ "cfg-if", "num-bigint", @@ -2772,7 +2772,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" dependencies = [ "colored", "fields", @@ -3134,7 +3134,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" dependencies = [ "bincode", "blake3", @@ -3170,7 +3170,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" dependencies = [ "bincode", "borsh", @@ -3202,7 +3202,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" dependencies = [ "fields", "itoa", @@ -3215,7 +3215,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" dependencies = [ "proc-macro2", "quote", @@ -3225,7 +3225,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" dependencies = [ "crossbeam-channel", "tracing", @@ -3234,7 +3234,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" dependencies = [ "bincode", "bytemuck", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" dependencies = [ "bytemuck", "fields", @@ -5721,7 +5721,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#503796a3a8ccff42752987e177deaeffe15c49ff" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" dependencies = [ "colored", "fields", diff --git a/common/src/bus/data_bus_operation.rs b/common/src/bus/data_bus_operation.rs index f087d7903..59c7ff668 100644 --- a/common/src/bus/data_bus_operation.rs +++ b/common/src/bus/data_bus_operation.rs @@ -387,8 +387,10 @@ impl OperationBusData { ZiskOperationType::Poseidon2 => { let mut data = unsafe { uninit_array::().assume_init() }; - data[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - data[OPERATION_BUS_DATA_SIZE..].copy_from_slice(&ctx.precompiled.input_data); + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); ExtOperationData::OperationPoseidon2Data(data) } @@ -646,8 +648,9 @@ impl OperationBusData { ZiskOperationType::Poseidon2 => { debug_assert_eq!(ctx.precompiled.input_data.len(), 16); - buffer[0..OPERATION_BUS_DATA_SIZE].copy_from_slice(&[op, op_type, a, b]); - buffer[OPERATION_BUS_DATA_SIZE..OPERATION_BUS_POSEIDON2_DATA_SIZE] + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..OPERATION_BUS_POSEIDON2_DATA_SIZE] .copy_from_slice(&ctx.precompiled.input_data); &buffer[..OPERATION_BUS_POSEIDON2_DATA_SIZE] } diff --git a/precompiles/poseidon2/src/poseidon2_bus_device.rs b/precompiles/poseidon2/src/poseidon2_bus_device.rs index e206184c2..4663ad03a 100644 --- a/precompiles/poseidon2/src/poseidon2_bus_device.rs +++ b/precompiles/poseidon2/src/poseidon2_bus_device.rs @@ -7,7 +7,7 @@ use std::ops::Add; use precompiles_common::MemProcessor; use zisk_common::{ - BusDevice, BusDeviceMode, BusId, Counter, Metrics, A, B, OPERATION_BUS_ID, OP_TYPE, + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, STEP, }; use zisk_core::ZiskOperationType; @@ -73,7 +73,7 @@ impl Poseidon2CounterInputGen { return true; } - let step_main = data[A]; + let step_main = data[STEP]; let addr_main = data[B] as u32; match self.mode { diff --git a/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs b/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs index 9996be979..c87432c12 100644 --- a/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs +++ b/precompiles/poseidon2/src/poseidon2_gen_mem_inputs.rs @@ -2,7 +2,7 @@ use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; use precompiles_common::MemBusHelpers; use precompiles_common::MemProcessor; -use zisk_common::OPERATION_BUS_DATA_SIZE; +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; #[derive(Debug)] pub struct Poseidon2MemInputConfig { @@ -21,7 +21,7 @@ pub fn generate_poseidon2_mem_inputs( ) { // Get the basic data from the input // op,op_type,a,b,... - let state: &mut [u64; 16] = &mut data[4..20].try_into().unwrap(); + let state: &mut [u64; 16] = &mut data[5..21].try_into().unwrap(); // Apply the poseidon2 hash function let state_gl = state.map(Goldilocks::new); @@ -34,7 +34,7 @@ pub fn generate_poseidon2_mem_inputs( let write_params = 1; let chunks_per_param = 16; let params_count = read_params + write_params; - let params_offset = OPERATION_BUS_DATA_SIZE; + let params_offset = OPERATION_PRECOMPILED_BUS_DATA_SIZE; for iparam in 0..params_count { let is_write = iparam >= read_params; let param_index = if is_write { iparam - read_params } else { iparam }; diff --git a/precompiles/poseidon2/src/poseidon2_input.rs b/precompiles/poseidon2/src/poseidon2_input.rs index ad50b39c4..716829a7c 100644 --- a/precompiles/poseidon2/src/poseidon2_input.rs +++ b/precompiles/poseidon2/src/poseidon2_input.rs @@ -10,9 +10,9 @@ pub struct Poseidon2Input { impl Poseidon2Input { pub fn from(values: &OperationPoseidon2Data) -> Self { Self { - step_main: values[2], + step_main: values[4], addr_main: values[3] as u32, - state: values[4..20].try_into().unwrap(), + state: values[5..21].try_into().unwrap(), } } } From 3a1a8bc7132be9c7f0b93cb42ad0d4f2b19bdd40 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Tue, 24 Feb 2026 14:15:38 +0000 Subject: [PATCH 634/782] Fix mutex import for single-thread assert --- ziskos/entrypoint/src/hints/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 26f297719..b518bf295 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -29,6 +29,8 @@ use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; #[cfg(zisk_hints_single_thread)] use std::thread::ThreadId; +#[cfg(zisk_hints_single_thread)] +use std::sync::Mutex; pub use bls12_381::*; pub use bn254::*; From 19f829e1d9359f388a5a742c0e2a4f2db2ef1b65 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Tue, 24 Feb 2026 17:49:16 +0100 Subject: [PATCH 635/782] fix test and merge bug --- emulator-asm/src/main.c | 9 ++++-- precompiles/helpers/src/dma.rs | 56 +--------------------------------- 2 files changed, 8 insertions(+), 57 deletions(-) diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 29d2a1c9c..154efe8d4 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -483,6 +483,8 @@ uint64_t trace_total_mapped_size = 0; // Total mapped trace size void * trace_get_chunk_address (uint64_t chunk_id) { + assert(gen_method != RomHistogram || chunk_id == 0); + if (chunk_id == 0) { return (void *)TRACE_ADDR; @@ -495,6 +497,11 @@ void * trace_get_chunk_address (uint64_t chunk_id) uint64_t trace_get_chunk_size (uint64_t chunk_id) { + if (gen_method == RomHistogram) { + assert(chunk_id == 0); + return trace_size; + } + if (chunk_id == 0) { return TRACE_INITIAL_SIZE; @@ -4155,8 +4162,6 @@ void server_setup (void) gettimeofday(&stop_time, NULL); duration = TimeDiff(start_time, stop_time); #endif - printf("%s trace mapping on %p with %ld MB\n", log_name, pTrace, chunk_player_mt_size >> 20); - fflush(stdout); if (pTrace == MAP_FAILED) { printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); diff --git a/precompiles/helpers/src/dma.rs b/precompiles/helpers/src/dma.rs index 7c191c34b..5744520b5 100644 --- a/precompiles/helpers/src/dma.rs +++ b/precompiles/helpers/src/dma.rs @@ -909,7 +909,7 @@ mod tests { for src in 0..256 { for count in 0..256 { let encode = DmaInfo::calculate_encode(dst, src, count, neq, true) - | DmaInfo::DMA_REQUIRES_DMA_MASK; + | DmaInfo::DMA_REQUIRES_DMA_TEST_MASK; let fast_encode = DmaInfo::encode_memcmp_neq(dst, src, count, neq); assert_eq!( encode, @@ -922,58 +922,4 @@ mod tests { } } } - - #[test] - fn benchmark_fast_encode_vs_encode_memcpy() { - use std::time::Instant; - - const ITERATIONS: usize = 10_000_000; - let mut checksum1 = DmaInfo::encode_memcpy(0, 0, 8); - let mut checksum2 = DmaInfo::encode_memcpy(0, 0, 8); - - // Benchmark encode_memcpy (original) - let start = Instant::now(); - for i in 0..ITERATIONS { - let dst = (i & 0xFF) as u64; - let src = ((i >> 8) & 0xFF) as u64; - let count = (i >> 16) & 0xFF; - checksum1 ^= DmaInfo::encode_memcpy(dst, src, count); - } - let duration_encode = start.elapsed(); - - // Benchmark fast_encode (table-based) - let start = Instant::now(); - for i in 0..ITERATIONS { - let dst = (i & 0xFF) as u64; - let src = ((i >> 8) & 0xFF) as u64; - let count = (i >> 16) & 0xFF; - checksum2 ^= DmaInfo::encode_memcpy(dst, src, count); - } - let duration_fast = start.elapsed(); - - // Verify both produce same results - assert_eq!(checksum1, checksum2, "Checksums must match!"); - - // Print results - println!("\n═══════════════════════════════════════════════════════"); - println!(" DMA Encoding Benchmark ({} iterations)", ITERATIONS); - println!("═══════════════════════════════════════════════════════"); - println!( - " encode_memcpy: {:?} ({:.2} ns/op)", - duration_encode, - duration_encode.as_nanos() as f64 / ITERATIONS as f64 - ); - println!( - " fast_encode: {:?} ({:.2} ns/op)", - duration_fast, - duration_fast.as_nanos() as f64 / ITERATIONS as f64 - ); - println!("───────────────────────────────────────────────────────"); - let speedup = duration_encode.as_nanos() as f64 / duration_fast.as_nanos() as f64; - println!(" Speedup: {:.2}x faster", speedup); - println!("═══════════════════════════════════════════════════════\n"); - - // Assert that fast_encode is actually faster - assert!(duration_fast < duration_encode, "fast_encode should be faster than encode_memcpy"); - } } From ea049129c548fbf3fd66f17a78c9044c37dc50ea Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Tue, 24 Feb 2026 18:12:18 +0000 Subject: [PATCH 636/782] Add Blake2b hint codes and implementation for compression --- common/src/hints.rs | 3 ++ ziskos/entrypoint/src/hints/blake2b.rs | 39 ++++++++++++++++++++++++++ ziskos/entrypoint/src/hints/mod.rs | 2 ++ 3 files changed, 44 insertions(+) create mode 100644 ziskos/entrypoint/src/hints/blake2b.rs diff --git a/common/src/hints.rs b/common/src/hints.rs index 9b95cb7b8..4bb8f3c2c 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -92,6 +92,9 @@ pub const HINT_VERIFY_KZG_PROOF: u32 = 0x0600; // Keccak256 hint codes pub const HINT_KECCAK256: u32 = 0x0700; +// Blake2b hint codes +pub const HINT_BLAKE2B_COMPRESS: u32 = 0x0800; + /// Control code variants for stream control. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u32)] diff --git a/ziskos/entrypoint/src/hints/blake2b.rs b/ziskos/entrypoint/src/hints/blake2b.rs new file mode 100644 index 000000000..fce3eab5f --- /dev/null +++ b/ziskos/entrypoint/src/hints/blake2b.rs @@ -0,0 +1,39 @@ +use crate::hints::{HINT_BUFFER, macros::{define_hint, register_hint_meta}}; +use zisk_common::{ + HINT_BLAKE2B_COMPRESS, +}; + +#[no_mangle] +pub unsafe extern "C" fn hint_blake2b_compress( + rounds: u32, + state: *mut u64, + message: *const u64, + offset: *const u64, + final_block: u8, + #[cfg(feature = "hints")] hints: &mut Vec, +) { + if !HINT_BUFFER.is_enabled() { + return; + } + + #[cfg(zisk_hints_single_thread)] + crate::hints::check_main_thread(); + + let total_len = 8 + 64 + 128 + 16 + 8; // rounds + state + message + offset + final_block + + let mut w = HINT_BUFFER.begin_hint(HINT_BLAKE2B_COMPRESS, total_len, false); + + let rounds_bytes: [u8; 8] = (rounds as u64).to_le_bytes(); + w.write_hint_data_slice(&rounds_bytes); + + w.write_hint_data_ptr(state as *const u8, 64); + w.write_hint_data_ptr(message as *const u8, 128); + w.write_hint_data_ptr(offset as *const u8, 16); + + let final_block_bytes: [u8; 8] = (final_block as u64).to_le_bytes(); + w.write_hint_data_slice(&final_block_bytes); + + w.commit(); +} + +register_hint_meta!(blake2b_compress, HINT_BLAKE2B_COMPRESS); \ No newline at end of file diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index b518bf295..103687bea 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -1,3 +1,4 @@ +mod blake2b; mod bls12_381; mod bn254; mod hint_buffer; @@ -32,6 +33,7 @@ use std::thread::ThreadId; #[cfg(zisk_hints_single_thread)] use std::sync::Mutex; +pub use blake2b::*; pub use bls12_381::*; pub use bn254::*; pub use keccak256::*; From 4e1f558498797c13cf047e506d0c2bc082040d0f Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Tue, 24 Feb 2026 21:30:05 +0100 Subject: [PATCH 637/782] fix cargo fmt --- ziskos/entrypoint/src/hints/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index b518bf295..07920cb4c 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -27,10 +27,10 @@ use std::{ use tokio::sync::oneshot; use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; -#[cfg(zisk_hints_single_thread)] -use std::thread::ThreadId; #[cfg(zisk_hints_single_thread)] use std::sync::Mutex; +#[cfg(zisk_hints_single_thread)] +use std::thread::ThreadId; pub use bls12_381::*; pub use bn254::*; From 8ce97f8a9f2c7a812af8f868b88e1561897dee79 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Wed, 25 Feb 2026 08:44:04 +0000 Subject: [PATCH 638/782] Checking publics and proof values --- Cargo.lock | 58 +++++++++---------- .../crates/coordinator/src/coordinator.rs | 48 ++++++++++++--- ziskos/entrypoint/src/hints/mod.rs | 4 +- 3 files changed, 72 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81b1a3469..3188bcbb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" dependencies = [ "fields", "num-bigint", @@ -1468,7 +1468,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" dependencies = [ "cfg-if", "num-bigint", @@ -2163,9 +2163,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.89" +version = "0.3.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4eacb0641a310445a4c513f2a5e23e19952e269c6a38887254d5f837a305506" +checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" dependencies = [ "once_cell", "wasm-bindgen", @@ -2772,7 +2772,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" dependencies = [ "colored", "fields", @@ -3134,7 +3134,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" dependencies = [ "bincode", "blake3", @@ -3170,7 +3170,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" dependencies = [ "bincode", "borsh", @@ -3202,7 +3202,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" dependencies = [ "fields", "itoa", @@ -3215,7 +3215,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" dependencies = [ "proc-macro2", "quote", @@ -3225,7 +3225,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" dependencies = [ "crossbeam-channel", "tracing", @@ -3234,7 +3234,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" dependencies = [ "bincode", "bytemuck", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" dependencies = [ "bytemuck", "fields", @@ -3538,9 +3538,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" @@ -3696,9 +3696,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", "log", @@ -5018,9 +5018,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.112" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d7d0fce354c88b7982aec4400b3e7fcf723c32737cef571bd165f7613557ee" +checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" dependencies = [ "cfg-if", "once_cell", @@ -5031,9 +5031,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.62" +version = "0.4.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee85afca410ac4abba5b584b12e77ea225db6ee5471d0aebaae0861166f9378a" +checksum = "8a89f4650b770e4521aa6573724e2aed4704372151bd0de9d16a3bbabb87441a" dependencies = [ "cfg-if", "futures-util", @@ -5045,9 +5045,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.112" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55839b71ba921e4f75b674cb16f843f4b1f3b26ddfcb3454de1cf65cc021ec0f" +checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5055,9 +5055,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.112" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf2e969c2d60ff52e7e98b7392ff1588bffdd1ccd4769eba27222fd3d621571" +checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" dependencies = [ "bumpalo", "proc-macro2", @@ -5068,9 +5068,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.112" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0861f0dcdf46ea819407495634953cdcc8a8c7215ab799a7a7ce366be71c7b30" +checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" dependencies = [ "unicode-ident", ] @@ -5124,9 +5124,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.89" +version = "0.3.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10053fbf9a374174094915bbce141e87a6bf32ecd9a002980db4b638405e8962" +checksum = "705eceb4ce901230f8625bd1d665128056ccbe4b7408faa625eec1ba80f59a97" dependencies = [ "js-sys", "wasm-bindgen", @@ -5721,7 +5721,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" dependencies = [ "colored", "fields", diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index e66f932b9..80557ed54 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -1414,13 +1414,47 @@ impl Coordinator { } else { // Standard mode: aggregate challenges from all participating workers // Each worker contributes their portion of the overall challenge space - let challenges: Vec> = phase1_results - .values() - .map(|results| match &results.data { - JobResultData::Challenges(values) => values.challenges.clone(), - _ => unreachable!("Expected Challenges data in Phase1 results"), - }) - .collect(); + let (challenges, execution_info): (Vec>, Vec) = + phase1_results + .values() + .map(|results| match &results.data { + JobResultData::Challenges(values) => { + (values.challenges.clone(), values.execution_info.clone()) + } + _ => unreachable!("Expected Challenges data in Phase1 results"), + }) + .unzip(); + + let first = execution_info.first().ok_or_else(|| { + CoordinatorError::Internal(format!("No execution info found in job {}", job.job_id)) + })?; + + let mut mismatched_workers = Vec::new(); + + for (worker_idx, info) in execution_info.iter().enumerate() { + if info.publics != first.publics || info.proof_values != first.proof_values { + mismatched_workers.push((worker_idx, info)); + } + } + + if !mismatched_workers.is_empty() { + // Format detailed mismatch report + let mismatch_report: Vec = mismatched_workers + .iter() + .map(|(idx, info)| { + format!( + "Worker {} differs: publics={:?}, proof_values={:?}", + idx, info.publics, info.proof_values + ) + }) + .collect(); + + return Err(CoordinatorError::Internal(format!( + "ExecutionInfo mismatch in job {}:\n{}", + job.job_id, + mismatch_report.join("\n") + ))); + } // Flatten all worker contributions into unified challenge vector // Maintains worker indexing and airgroup assignments for proper coordination diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index b518bf295..07920cb4c 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -27,10 +27,10 @@ use std::{ use tokio::sync::oneshot; use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; -#[cfg(zisk_hints_single_thread)] -use std::thread::ThreadId; #[cfg(zisk_hints_single_thread)] use std::sync::Mutex; +#[cfg(zisk_hints_single_thread)] +use std::thread::ThreadId; pub use bls12_381::*; pub use bn254::*; From dcff5c59bcdb61eebab4698a35a362ca57a6a695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 11 Feb 2026 12:00:34 +0000 Subject: [PATCH 639/782] Adding a working blake2 impl --- precompiles/helpers/src/blake2/blake2b/mod.rs | 396 ++++++++++++++++++ .../helpers/src/blake2/blake2b/round.rs | 118 ++++++ .../helpers/src/blake2/blake2b/utils.rs | 19 + precompiles/helpers/src/blake2/mod.rs | 7 + precompiles/helpers/src/lib.rs | 2 + 5 files changed, 542 insertions(+) create mode 100644 precompiles/helpers/src/blake2/blake2b/mod.rs create mode 100644 precompiles/helpers/src/blake2/blake2b/round.rs create mode 100644 precompiles/helpers/src/blake2/blake2b/utils.rs create mode 100644 precompiles/helpers/src/blake2/mod.rs diff --git a/precompiles/helpers/src/blake2/blake2b/mod.rs b/precompiles/helpers/src/blake2/blake2b/mod.rs new file mode 100644 index 000000000..9877e70e0 --- /dev/null +++ b/precompiles/helpers/src/blake2/blake2b/mod.rs @@ -0,0 +1,396 @@ +mod round; +mod utils; + +use round::blake2b_round; +use utils::*; + +/// BLAKE2b initialization vectors +const IV: [u64; 8] = [ + 0x6A09E667F3BCC908, + 0xBB67AE8584CAA73B, + 0x3C6EF372FE94F82B, + 0xA54FF53A5F1D36F1, + 0x510E527FADE682D1, + 0x9B05688C2B3E6C1F, + 0x1F83D9ABFB41BD6B, + 0x5BE0CD19137E2179, +]; + +/// BLAKE2b state representation as 16 x 64-bit words +/// v[0..15] each containing 64 bits +type Blake2StateBits = [[u64; 64]; 16]; + +/// BLAKE2b compression function F +/// +/// # Arguments +/// * `rounds` - Number of rounds (typically 12 for BLAKE2b) +/// * `state` - The internal state h (8 x 64-bit words as bits) +/// * `message` - The message block m (16 x 64-bit words as bits) +/// * `t` - Offset counters (2 x 64-bit words) +/// * `f` - Final block flag +pub fn blake2b(rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { + // Initialize working vector v + let mut v: Blake2StateBits = [[0u64; 64]; 16]; + + // Convert inputs to bit representation + let mut h_bits: [[u64; 64]; 8] = [[0u64; 64]; 8]; + for i in 0..8 { + h_bits[i] = bits_from_u64(h[i]); + } + + let mut m_bits: [[u64; 64]; 16] = [[0u64; 64]; 16]; + for i in 0..16 { + m_bits[i] = bits_from_u64(m[i]); + } + + // Convert iv to bits + let mut iv_bits: [[u64; 64]; 8] = [[0u64; 64]; 8]; + for i in 0..8 { + iv_bits[i] = bits_from_u64(IV[i]); + } + + // v[0..7] = h[0..7] + v[0..8].copy_from_slice(&h_bits); + + // v[8..15] = IV[0..7] + v[8..16].copy_from_slice(&iv_bits); + + // v[12] ^= t[0] (low word of offset) + #[allow(clippy::needless_range_loop)] + for z in 0..64 { + v[12][z] ^= (t[0] >> z) & 1; + } + + // v[13] ^= t[1] (high word of offset) + #[allow(clippy::needless_range_loop)] + for z in 0..64 { + v[13][z] ^= (t[1] >> z) & 1; + } + + // v[14] ^= 0xFFFFFFFFFFFFFFFF if f is true (invert all bits) + if f { + #[allow(clippy::needless_range_loop)] + for z in 0..64 { + v[14][z] ^= 1; + } + } + + // Perform rounds + for r in 0..rounds { + blake2b_round(&mut v, &m_bits, r); + } + + // Finalize: h[i] = h[i] ^ v[i] ^ v[i+8] + for i in 0..8 { + for z in 0..64 { + h_bits[i][z] ^= v[i][z] ^ v[i + 8][z]; + } + } + + // Convert back to u64 + for i in 0..8 { + h[i] = u64_from_bits(&h_bits[i]); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_blake2b_eip152_vector1() { + // Test vector from EIP-152 + // Input: + // rounds = 0 + // h = 48c9bdf267e6096a 3ba7ca8485ae67bb 2bf894fe72f36e3c f1361d5f3af54fa5 + // d182e6ad7f520e51 1f6c3e2b8c68059b 6bbd41fbabd9831f 79217e1319cde05b + // m = 6162630000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // t = 03 00 00 00 00 00 00 00, 00 00 00 00 00 00 00 00 + // f = true + // + // Expected output: + // 08c9bcf367e6096a 3ba7ca8485ae67bb 2bf894fe72f36e3c f1361d5f3af54fa5 + // d282e6ad7f520e51 1f6c3e2b8c68059b 9442be0454267ce0 79217e1319cde05b + + let rounds = 0u32; + + let mut h: [u64; 8] = [ + 0x48c9bdf267e6096au64.swap_bytes(), + 0x3ba7ca8485ae67bbu64.swap_bytes(), + 0x2bf894fe72f36e3cu64.swap_bytes(), + 0xf1361d5f3af54fa5u64.swap_bytes(), + 0xd182e6ad7f520e51u64.swap_bytes(), + 0x1f6c3e2b8c68059bu64.swap_bytes(), + 0x6bbd41fbabd9831fu64.swap_bytes(), + 0x79217e1319cde05bu64.swap_bytes(), + ]; + + let m: [u64; 16] = [ + 0x6162630000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + ]; + + let t: [u64; 2] = [0x0300000000000000u64.swap_bytes(), 0x0000000000000000u64.swap_bytes()]; + + let f = true; + + blake2b(rounds, &mut h, &m, &t, f); + + // Expected output (8 × u64, little-endian) + let expected = [ + 0x08c9bcf367e6096au64.swap_bytes(), + 0x3ba7ca8485ae67bbu64.swap_bytes(), + 0x2bf894fe72f36e3cu64.swap_bytes(), + 0xf1361d5f3af54fa5u64.swap_bytes(), + 0xd282e6ad7f520e51u64.swap_bytes(), + 0x1f6c3e2b8c68059bu64.swap_bytes(), + 0x9442be0454267ce0u64.swap_bytes(), + 0x79217e1319cde05bu64.swap_bytes(), + ]; + + assert_eq!( + h, expected, + "Blake2b does not match:\n exp: {:02x?},\n got: {:02x?}", + expected, h + ); + } + + #[test] + fn test_blake2b_eip152_vector2() { + // Test vector from EIP-152 + // Input: + // rounds = 12 + // h = 48c9bdf267e6096a 3ba7ca8485ae67bb 2bf894fe72f36e3c f1361d5f3af54fa5 + // d182e6ad7f520e51 1f6c3e2b8c68059b 6bbd41fbabd9831f 79217e1319cde05b + // m = 6162630000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // t = 03 00 00 00 00 00 00 00, 00 00 00 00 00 00 00 00 + // f = true + // + // Expected output: + // ba80a53f981c4d0d 6a2797b69f12f6e9 4c212f14685ac4b7 4b12bb6fdbffa2d1 + // 7d87c5392aab792d c252d5de4533cc95 18d38aa8dbf1925a b92386edd4009923 + + let rounds = 12; + + let mut h: [u64; 8] = [ + 0x48c9bdf267e6096au64.swap_bytes(), + 0x3ba7ca8485ae67bbu64.swap_bytes(), + 0x2bf894fe72f36e3cu64.swap_bytes(), + 0xf1361d5f3af54fa5u64.swap_bytes(), + 0xd182e6ad7f520e51u64.swap_bytes(), + 0x1f6c3e2b8c68059bu64.swap_bytes(), + 0x6bbd41fbabd9831fu64.swap_bytes(), + 0x79217e1319cde05bu64.swap_bytes(), + ]; + + let m: [u64; 16] = [ + 0x6162630000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + ]; + + let t: [u64; 2] = [0x0300000000000000u64.swap_bytes(), 0x0000000000000000u64.swap_bytes()]; + + let f = true; + + blake2b(rounds, &mut h, &m, &t, f); + + let expected: [u64; 8] = [ + 0xba80a53f981c4d0du64.swap_bytes(), + 0x6a2797b69f12f6e9u64.swap_bytes(), + 0x4c212f14685ac4b7u64.swap_bytes(), + 0x4b12bb6fdbffa2d1u64.swap_bytes(), + 0x7d87c5392aab792du64.swap_bytes(), + 0xc252d5de4533cc95u64.swap_bytes(), + 0x18d38aa8dbf1925au64.swap_bytes(), + 0xb92386edd4009923u64.swap_bytes(), + ]; + + assert_eq!( + h, expected, + "Blake2b does not match:\n exp: {:016x?},\n got: {:016x?}", + expected, h + ); + } + + #[test] + fn test_blake2b_eip152_vector3() { + // Test vector from EIP-152 + // Input: + // rounds = 12 + // h = 48c9bdf267e6096a 3ba7ca8485ae67bb 2bf894fe72f36e3c f1361d5f3af54fa5 + // d182e6ad7f520e51 1f6c3e2b8c68059b 6bbd41fbabd9831f 79217e1319cde05b + // m = 6162630000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // t = 03 00 00 00 00 00 00 00, 00 00 00 00 00 00 00 00 + // f = false + // + // Expected output: + // 75ab69d3190a562c 51aef8d88f1c2775 876944407270c42c 9844252c26d28752 + // 98743e7f6d5ea2f2 d3e8d226039cd31b 4e426ac4f2d3d666 a610c2116fde4735 + + let rounds = 12; + + let mut h: [u64; 8] = [ + 0x48c9bdf267e6096au64.swap_bytes(), + 0x3ba7ca8485ae67bbu64.swap_bytes(), + 0x2bf894fe72f36e3cu64.swap_bytes(), + 0xf1361d5f3af54fa5u64.swap_bytes(), + 0xd182e6ad7f520e51u64.swap_bytes(), + 0x1f6c3e2b8c68059bu64.swap_bytes(), + 0x6bbd41fbabd9831fu64.swap_bytes(), + 0x79217e1319cde05bu64.swap_bytes(), + ]; + + let m: [u64; 16] = [ + 0x6162630000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + ]; + + let t: [u64; 2] = [0x0300000000000000u64.swap_bytes(), 0x0000000000000000u64.swap_bytes()]; + + let f = false; + + blake2b(rounds, &mut h, &m, &t, f); + + let expected: [u64; 8] = [ + 0x75ab69d3190a562cu64.swap_bytes(), + 0x51aef8d88f1c2775u64.swap_bytes(), + 0x876944407270c42cu64.swap_bytes(), + 0x9844252c26d28752u64.swap_bytes(), + 0x98743e7f6d5ea2f2u64.swap_bytes(), + 0xd3e8d226039cd31bu64.swap_bytes(), + 0x4e426ac4f2d3d666u64.swap_bytes(), + 0xa610c2116fde4735u64.swap_bytes(), + ]; + + assert_eq!( + h, expected, + "Blake2b does not match:\n exp: {:016x?},\n got: {:016x?}", + expected, h + ); + } + + #[test] + fn test_blake2b_eip152_vector4() { + // Test vector from EIP-152 + // Input: + // rounds = 1 + // h = 48c9bdf267e6096a 3ba7ca8485ae67bb 2bf894fe72f36e3c f1361d5f3af54fa5 + // d182e6ad7f520e51 1f6c3e2b8c68059b 6bbd41fbabd9831f 79217e1319cde05b + // m = 6162630000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // 0000000000000000 0000000000000000 0000000000000000 0000000000000000 + // t = 03 00 00 00 00 00 00 00, 00 00 00 00 00 00 00 00 + // f = true + // + // Expected output: + // b63a380cb2897d52 1994a85234ee2c18 1b5f844d2c624c00 2677e9703449d2fb + // a551b3a8333bcdf5 f2f7e08993d53923 de3d64fcc68c034e 717b9293fed7a421 + + let rounds = 1; + + let mut h: [u64; 8] = [ + 0x48c9bdf267e6096au64.swap_bytes(), + 0x3ba7ca8485ae67bbu64.swap_bytes(), + 0x2bf894fe72f36e3cu64.swap_bytes(), + 0xf1361d5f3af54fa5u64.swap_bytes(), + 0xd182e6ad7f520e51u64.swap_bytes(), + 0x1f6c3e2b8c68059bu64.swap_bytes(), + 0x6bbd41fbabd9831fu64.swap_bytes(), + 0x79217e1319cde05bu64.swap_bytes(), + ]; + + let m: [u64; 16] = [ + 0x6162630000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + 0x0000000000000000u64.swap_bytes(), + ]; + + let t: [u64; 2] = [0x0300000000000000u64.swap_bytes(), 0x0000000000000000u64.swap_bytes()]; + + let f = true; + + blake2b(rounds, &mut h, &m, &t, f); + + let expected: [u64; 8] = [ + 0xb63a380cb2897d52u64.swap_bytes(), + 0x1994a85234ee2c18u64.swap_bytes(), + 0x1b5f844d2c624c00u64.swap_bytes(), + 0x2677e9703449d2fbu64.swap_bytes(), + 0xa551b3a8333bcdf5u64.swap_bytes(), + 0xf2f7e08993d53923u64.swap_bytes(), + 0xde3d64fcc68c034eu64.swap_bytes(), + 0x717b9293fed7a421u64.swap_bytes(), + ]; + + assert_eq!( + h, expected, + "Blake2b does not match:\n exp: {:016x?},\n got: {:016x?}", + expected, h + ); + } +} diff --git a/precompiles/helpers/src/blake2/blake2b/round.rs b/precompiles/helpers/src/blake2/blake2b/round.rs new file mode 100644 index 000000000..26c028b13 --- /dev/null +++ b/precompiles/helpers/src/blake2/blake2b/round.rs @@ -0,0 +1,118 @@ +use super::Blake2StateBits; + +/// Message word permutation schedule +/// 10 different permutations, cycling for rounds >= 10 +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +/// Rotation constants for G function +const R1: usize = 32; +const R2: usize = 24; +const R3: usize = 16; +const R4: usize = 63; + +/// BLAKE2b round function +pub(crate) fn blake2b_round(v: &mut Blake2StateBits, m: &[[u64; 64]; 16], round: u32) { + // Message word selection permutation for this round + let s = &SIGMA[(round % 10) as usize]; + + // Column step + g(v, 0, 4, 8, 12, &m[s[0]], &m[s[1]]); + g(v, 1, 5, 9, 13, &m[s[2]], &m[s[3]]); + g(v, 2, 6, 10, 14, &m[s[4]], &m[s[5]]); + g(v, 3, 7, 11, 15, &m[s[6]], &m[s[7]]); + + // Diagonal step + g(v, 0, 5, 10, 15, &m[s[8]], &m[s[9]]); + g(v, 1, 6, 11, 12, &m[s[10]], &m[s[11]]); + g(v, 2, 7, 8, 13, &m[s[12]], &m[s[13]]); + g(v, 3, 4, 9, 14, &m[s[14]], &m[s[15]]); +} + +/// G mixing function +/// +/// The G function mixes two input words `x` and `y` from the message block into the state. +/// It operates on 4 state words: v[a], v[b], v[c], v[d] +#[allow(clippy::too_many_arguments)] +fn g( + v: &mut Blake2StateBits, + a: usize, + b: usize, + c: usize, + d: usize, + x: &[u64; 64], + y: &[u64; 64], +) { + let mut va = v[a]; + let mut vb = v[b]; + let mut vc = v[c]; + let mut vd = v[d]; + + // v[a] := (v[a] + v[b] + x) mod 2^64 + add64_bits(&mut va, &vb); + add64_bits(&mut va, x); + + // v[d] := (v[d] ^ v[a]) >>> R1 + xor64_bits(&mut vd, &va); + rotr64_bits(&mut vd, R1); + + // v[c] := (v[c] + v[d]) mod 2^64 + add64_bits(&mut vc, &vd); + + // v[b] := (v[b] ^ v[c]) >>> R2 + xor64_bits(&mut vb, &vc); + rotr64_bits(&mut vb, R2); + + // v[a] := (v[a] + v[b] + y) mod 2^64 + add64_bits(&mut va, &vb); + add64_bits(&mut va, y); + + // v[d] := (v[d] ^ v[a]) >>> R3 + xor64_bits(&mut vd, &va); + rotr64_bits(&mut vd, R3); + + // v[c] := (v[c] + v[d]) mod 2^64 + add64_bits(&mut vc, &vd); + + // v[b] := (v[b] ^ v[c]) >>> R4 + xor64_bits(&mut vb, &vc); + rotr64_bits(&mut vb, R4); + + v[a] = va; + v[b] = vb; + v[c] = vc; + v[d] = vd; +} + +fn add64_bits(a: &mut [u64; 64], b: &[u64; 64]) { + let mut carry = 0u64; + for z in 0..64 { + let sum = a[z] + b[z] + carry; + a[z] = sum % 2; + carry = sum / 2; + } +} + +fn xor64_bits(a: &mut [u64; 64], b: &[u64; 64]) { + for z in 0..64 { + a[z] ^= b[z]; + } +} + +fn rotr64_bits(a: &mut [u64; 64], n: usize) { + let mut temp = [0u64; 64]; + for z in 0..64 { + temp[z] = a[(z + n) % 64]; + } + *a = temp; +} diff --git a/precompiles/helpers/src/blake2/blake2b/utils.rs b/precompiles/helpers/src/blake2/blake2b/utils.rs new file mode 100644 index 000000000..8ffcc24d1 --- /dev/null +++ b/precompiles/helpers/src/blake2/blake2b/utils.rs @@ -0,0 +1,19 @@ +pub const fn bits_from_u64(value: u64) -> [u64; 64] { + let mut bits = [0u64; 64]; + let mut i = 0; + while i < 64 { + bits[i] = (value >> i) & 1; + i += 1; + } + bits +} + +pub fn u64_from_bits(bits: &[u64; 64]) -> u64 { + let mut value = 0u64; + for (i, &bit) in bits.iter().enumerate() { + if bit == 1 { + value |= 1u64 << i; + } + } + value +} diff --git a/precompiles/helpers/src/blake2/mod.rs b/precompiles/helpers/src/blake2/mod.rs new file mode 100644 index 000000000..5513f49c8 --- /dev/null +++ b/precompiles/helpers/src/blake2/mod.rs @@ -0,0 +1,7 @@ +mod blake2b; +// mod blake2b_expr; +// mod blake2b_state; + +pub use blake2b::*; +// pub use blake2b_expr::blake2b_expr; +// pub use blake2b_state::blake2b_state; diff --git a/precompiles/helpers/src/lib.rs b/precompiles/helpers/src/lib.rs index 2e090138a..1e8c590a3 100644 --- a/precompiles/helpers/src/lib.rs +++ b/precompiles/helpers/src/lib.rs @@ -1,6 +1,7 @@ mod arith_eq; mod arith_eq_384; mod big_int; +mod blake2; mod common; mod dma; mod keccak; @@ -8,6 +9,7 @@ mod keccak; pub use arith_eq::*; pub use arith_eq_384::*; pub use big_int::*; +pub use blake2::blake2b; pub use common::*; pub use dma::*; pub use keccak::{ From 46825e54a5799d4f0070b366fc027fd2723e1d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:18:48 +0000 Subject: [PATCH 640/782] First blake version --- precompiles/blake2/pil/blake2br.pil | 506 ++++++++++++++++++++++++++++ 1 file changed, 506 insertions(+) create mode 100644 precompiles/blake2/pil/blake2br.pil diff --git a/precompiles/blake2/pil/blake2br.pil b/precompiles/blake2/pil/blake2br.pil new file mode 100644 index 000000000..7613a8e7b --- /dev/null +++ b/precompiles/blake2/pil/blake2br.pil @@ -0,0 +1,506 @@ +require "std_constants.pil" +require "std_lookup.pil" +require "operations.pil" +require "opids.pil" + +// Precompile in charge of performing the BLAKE2b round function. +// For reference: https://datatracker.ietf.org/doc/html/rfc7693 or https://www.blake2.net/ + +// We use little-endian representation + +airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION_BUS_ID) { + /* + ═══════════════════════════════════════════════════════════════════════════ + BLAKE2B ROUND FUNCTION SPECIFICATION + ═══════════════════════════════════════════════════════════════════════════ + + BLAKE2BR(v: [u64; 16], m: [u64; 16], round_idx: u32) -> [u64; 16] + + Each round consists of 8 G function calls: + • 4 Column steps (G0-G3): Mix columns independently + • 4 Diagonal steps (G0-G3): Mix diagonals independently + + Each G function performs 8 sequential operations on 4 u64 values: + v[a] := (v[a] + v[b] + x) mod 2⁶⁴ + v[d] := (v[d] ^ v[a]) >>> 32 + v[c] := (v[c] + v[d]) mod 2⁶⁴ + v[b] := (v[b] ^ v[c]) >>> 24 + v[a] := (v[a] + v[b] + y) mod 2⁶⁴ + v[d] := (v[d] ^ v[a]) >>> 16 + v[c] := (v[c] + v[d]) mod 2⁶⁴ + v[b] := (v[b] ^ v[c]) >>> 63 + + Message words x,y selected by SIGMA[round_idx % 10] + + ═══════════════════════════════════════════════════════════════════════════ + EXECUTION TRACE LAYOUT (24 clocks per round) + ═══════════════════════════════════════════════════════════════════════════ + + Each G function uses 3 rows: + Row 0: Initial state + first message word (input) + Row 1: Intermediate state + second message (intermediate) + Row 2: Final state (output) + + ROW va[2] vb[2][32] vc[2] vd[2][32] ms0[2] STAGE + |------|----------|----------|----------|----------|----------|----------------| + | 0 | v[0] | v[4] | v[8] | v[12] | m[s[0]] | G1 - COL MIX | <-- V/M IN + | 1 | v[0]i | v[4]i | v[8]i | v[12]i | m[s[1]] | G1 - COL MIX | <-- M IN + | 2 | v[0]o | v[4]o | v[8]o | v[12]o | XXXXXXX | G1 - COL MIX | + |------|----------|----------|----------|----------|----------|----------------| + | 3 | v[1] | v[5] | v[9] | v[13] | m[s[2]] | G2 - COL MIX | <-- V/M IN + | 4 | v[1]i | v[5]i | v[9]i | v[13]i | m[s[3]] | G2 - COL MIX | <-- M IN + | 5 | v[1]o | v[5]o | v[9]o | v[13]o | XXXXXXX | G2 - COL MIX | + |------|----------|----------|----------|----------|----------|----------------| + | 6 | v[2] | v[6] | v[10] | v[14] | m[s[4]] | G3 - COL MIX | <-- V/M IN + | 7 | v[2]i | v[6]i | v[10]i | v[14]i | m[s[5]] | G3 - COL MIX | <-- M IN + | 8 | v[2]o | v[6]o | v[10]o | v[14]o | XXXXXXX | G3 - COL MIX | + |------|----------|----------|----------|----------|----------|----------------| + | 9 | v[3] | v[7] | v[11] | v[15] | m[s[6]] | G4 - COL MIX | <-- V/M IN + | 10 | v[3]i | v[7]i | v[11]i | v[15]i | m[s[7]] | G4 - COL MIX | <-- M IN + | 11 | v[3]o | v[7]o | v[11]o | v[15]o | XXXXXXX | G4 - COL MIX | + |------|----------|----------|----------|----------|----------|----------------| + | 12 | v[0] | v[5] | v[10] | v[15] | m[s[8]] | G1 - DIA MIX | <-- M IN + | 13 | v[0]i | v[5]i | v[10]i | v[15]i | m[s[9]] | G1 - DIA MIX | <-- M IN + | 14 | v[0]o | v[5]o | v[10]o | v[15]o | XXXXXXX | G1 - DIA MIX | <-- V OUT + |------|----------|----------|----------|----------|----------|----------------| + | 15 | v[1] | v[6] | v[11] | v[12] | m[s[10]] | G2 - DIA MIX | <-- M IN + | 16 | v[1]i | v[6]i | v[11]i | v[12]i | m[s[11]] | G2 - DIA MIX | <-- M IN + | 17 | v[1]o | v[6]o | v[11]o | v[12]o | XXXXXXXX | G2 - DIA MIX | <-- V OUT + |------|----------|----------|----------|----------|----------|----------------| + | 18 | v[2] | v[7] | v[8] | v[13] | m[s[12]] | G3 - DIA MIX | <-- M IN + | 19 | v[2]i | v[7]i | v[8]i | v[13]i | m[s[13]] | G3 - DIA MIX | <-- M IN + | 20 | v[2]o | v[7]o | v[8]o | v[13]o | XXXXXXXX | G3 - DIA MIX | <-- V OUT + |------|----------|----------|----------|----------|----------|----------------| + | 21 | v[3] | v[4] | v[9] | v[14] | m[s[14]] | G4 - DIA MIX | <-- M IN + | 22 | v[3]i | v[4]i | v[9]i | v[14]i | m[s[15]] | G4 - DIA MIX | <-- M IN + | 23 | v[3]o | v[4]o | v[9]o | v[14]o | XXXXXXXX | G4 - DIA MIX | <-- V OUT + |------|----------|----------|----------|----------|----------|----------------| + */ + + const int CLOCKS_PER_G = 3; + const int G_FUNCTIONS_PER_ROUND = 8; + const int CLOCK_NUM = CLOCKS_PER_G * G_FUNCTIONS_PER_ROUND; + + // Ensure that the Blake2br fits + if (N < 2*CLOCK_NUM) { + error(`Blake2br requires N >= ${2*CLOCK_NUM}, got N=${N}`); + } + + // Calculate usable capacity + const int NUM_NON_USABLE_ROWS = N % CLOCK_NUM; + const int NUM_BLAKE2BR; + if (NUM_NON_USABLE_ROWS == 0) { + NUM_BLAKE2BR = N / CLOCK_NUM; + } else { + NUM_BLAKE2BR = (N - NUM_NON_USABLE_ROWS) / CLOCK_NUM - 1; // The -1 is because CLOCK_NUM is not a divisor of N + } + println(`Blake2br capacity: ${NUM_BLAKE2BR} rounds (N=${N})`); + + col fixed CLK_0 = [[1, 0:(CLOCK_NUM-1)]:NUM_BLAKE2BR, 0...]; + + const expr CLK[CLOCK_NUM]; + for (int i = 0; i < CLOCK_NUM; i++) { + CLK[i] = (i)'CLK_0; + } + + // Define the clock selectors + col witness bits(1) in_use_clk_0; // 1 at the first clock cycle of the Blake2broperation, 0 otherwise + col witness bits(1) in_use; // 1 when the Blake2br operation is in use, 0 otherwise + + in_use_clk_0 * (1 - in_use_clk_0) === 0; + in_use * (1 - in_use) === 0; + + in_use_clk_0 - CLK_0 * in_use === 0; // This constraint is two-fold: + // · in_use_clk_0 can only be active when CLK_0 is active + // · if in_use is active then so is in_use_clk_0 + + // Selector latching: once activated, stays active for full cycle + const expr in_use_continues = clock_set(start: 1, end: CLOCK_NUM); + in_use_continues * (in_use - 'in_use) === 0; + + // --> Constraints to assert the BLAKE2B round function + + /// Message word permutation schedule + const int SIGMA[10][16]; + SIGMA[0] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + SIGMA[1] = [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]; + SIGMA[2] = [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4]; + SIGMA[3] = [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8]; + SIGMA[4] = [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13]; + SIGMA[5] = [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9]; + SIGMA[6] = [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11]; + SIGMA[7] = [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10]; + SIGMA[8] = [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5]; + SIGMA[9] = [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]; + + // 1] Start by deriving m[SIGMA[idx][off + 2*i]], m[SIGMA[idx][off + 2*i + 1]] + + // Indicator of the SIGMA index + col witness bits(4) idx; + + // idx should be a value in [0,10) that is fixed along the cycle + (1 - CLK_0) * (idx - 'idx) === 0; + + col witness bits(1) idx_sel[10]; + expr sum_sel = 0; + expr sum_w_sel = 0; + for (int i = 0; i < 10; i++) { + // Selectors are bits + idx_sel[i] * (idx_sel[i] - 1) === 0; + + // Add all selectors + sum_sel += idx_sel[i]; + + // Add weighted selectors to compute idx + sum_w_sel += idx_sel[i] * i; + } + sum_sel * (sum_sel - 1) === 0; // At most one of the idx_sel is active + idx === sum_w_sel; // Check that idx is the index of the active idx_sel + // Moreover, this ensures that idx is in [0,10) + // Since idx is fixed within the cycle, all idx_sel are fixed as well + + // SIGMA[idx] = ∑ᵢ idx_sel[i]·SIGMA[i] + col witness bits(4) s; + expr s_expr = 0; + for (int j = 0; j < 10; j++) { + expr sum_sigmas = 0; + int sig_idx = 0; + for (int k = 0; k < CLOCK_NUM; k++) { + if (k > 0 && k % 2 == 0) { + continue; + } + sum_sigmas += CLK[k] * SIGMA[j][sig_idx]; + sig_idx++; + } + s_expr += idx_sel[j] * sum_sigmas; + } + s <== s_expr; + + // Permutation to get actual message words from indices + col fixed MSG_IDX = [0, 1, 0, 2, 3, 0, 4, 5, 0, 6, 7, 0, + 8, 9, 0, 10, 11, 0, 12, 13, 0, 14, 15, 0]...; + + // The m-values in order + col witness bits(32) m[2]; + + // The m-values in the index order + col witness bits(32) ms[2]; + + const expr ms_next[2] = [ms[0]', ms[1]']; + + // TODO: Decide the selector! + permutation_assumes(opid: BLAKE2BR_PERMUTATION_ID, expressions: [MSG_IDX, m[0], m[1]]); + permutation_proves(opid: BLAKE2BR_PERMUTATION_ID, expressions: [s, ms[0], ms[1]]); + + // 2] Compute the g mixing function + + // The four v-values of g + col witness bits(32) va[2]; + col witness bits(1) vb[2][32]; + col witness bits(32) vc[2]; + col witness bits(1) vd[2][32]; + + // va and vc will be range checked later on + + // vb and vd are bits + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 32; j++) { + vb[i][j] * (1 - vb[i][j]) === 0; + vd[i][j] * (1 - vd[i][j]) === 0; + } + } + + // Define intermediate v-values + expr va_im[2] = [va[0]', va[1]']; + expr vc_im[2] = [vc[0]', vc[1]']; + expr vb_im[2][32]; + expr vd_im[2][32]; + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 32; j++) { + vb_im[i][j] = vb[i][j]'; + vd_im[i][j] = vd[i][j]'; + } + } + + // Define output v-values + expr va_out[2] = [va[0]'2, va[1]'2]; + expr vc_out[2] = [vc[0]'2, vc[1]'2]; + expr vb_out[2][32]; + expr vd_out[2][32]; + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 32; j++) { + vb_out[i][j] = vb[i][j]'2; + vd_out[i][j] = vd[i][j]'2; + } + } + + // Verify the internal operations of the g function + // va' == (va + vb + ms) mod 2**64 + expr vbpack[2] = [pack(vb[0]), pack(vb[1])]; + add3_check(va_im, va, vbpack, ms); + + // vd' == (vd ^ va') >>> 32 iff va' == (vd' <<< 32) ^ vd + rotl_xor_check(va_im, vd_im, vd, 32); + + // vc' == (vc + vd') mod 2**64 + expr vdimpack[2] = [pack(vd_im[0]), pack(vd_im[1])]; + add2_check(vc_im, vc, vdimpack); + + // vb' == (vb ^ vc') >>> 24 iff vc' == (vb' <<< 24) ^ vb + rotl_xor_check(vc_im, vb_im, vb, 24); + + // va'' == (va' + vb' + ms) mod 2**64 + expr vbimpack[2] = [pack(vb_im[0]), pack(vb_im[1])]; + add3_check(va_out, va_im, vbimpack, ms_next); + + // vd'' == (vd' ^ va'') >>> 16 iff va'' == (vd'' <<< 16) ^ vd' + rotl_xor_check(va_out, vd_out, vd_im, 16); + + // vc'' == (vc' + vd'') mod 2**64 + expr vdoutpack[2] = [pack(vd_out[0]), pack(vd_out[1])]; + add2_check(vc_out, vc_im, vdoutpack); + + // vb'' == (vb' ^ vc'') >>> 63 iff vc'' == (vb'' <<< 63) ^ vb' + rotl_xor_check(vc_out, vb_out, vb_im, 63); + + // --> Constraints to read inputs from memory and write outputs to memory + + /* + We should take care of how we handle the memory access. + + The Blake2br inputs are received via two indirections and one value inside a structure. + The address received from the MainSM is the address of the structure + The first indirection is the adress of the state + The second indirection is the address of the input + We have to relate all these addresses, by proving that: + · ADDR_STATE === ADDR_IND_0 + · ADDR_INPUT === ADDR_IND_1 + while sending both addr_ind_0 and addr_ind_1 to the memory. + */ + + col witness bits(40) step_addr; + + /* + MEMORY ACCESS MAP + ========================================================= + 0 | STEP_MAIN | R | ADDR_STATE | state[0] + | ... | | ... | ... + 15 | STEP_MAIN | R | ADDR_STATE + 120 | state[15] + 16 | STEP_MAIN | R | ADDR_INPUT | input[0] + | ... | | ... | ... + 31 | STEP_MAIN | R | ADDR_INPUT + 120 | input[15] + 32 | STEP_MAIN + 1 | W | ADDR_STATE | state[0] + | ... | | ... | ... + 47 | STEP_MAIN + 1 | W | ADDR_STATE + 120 | state[15] + 48 | STEP_MAIN | R | ADDR_OP | index + 49 | STEP_MAIN | R | ADDR_OP + 8 | ADDR_IND_0 + 50 | STEP_MAIN | R | ADDR_OP + 16 | ADDR_IND_1 + ========================================================= + */ + + const int STEP_MAIN = 0; + const int ADDR_OP = STEP_MAIN + 1; + const int ADDR_STATE = ADDR_OP + 1; + const int ADDR_INPUT = ADDR_STATE + 1; + const int ADDR_IND_0 = ADDR_INPUT + 1; + const int ADDR_IND_1 = ADDR_IND_0 + 1; + + clock_eq(step_addr, ADDR_STATE, ADDR_IND_0) === 0; + clock_eq(step_addr, ADDR_INPUT, ADDR_IND_1) === 0; + + // Perform 4 memory operations in parallel + // Total: 51 mem ops, Steps Needed: ceil(51/4) = 12 < 24 (CLOCK_NUM) + // - 0..3 -> Read State + // - 4..7 -> Read Input + // - 8..11 -> Write State + // - 12 -> Read Index and ADDR_INDs + const int READ_STIN_OPS = 32; + const int WRITE_STOUT_OPS = 16; + const int READ_REM_OPS = 3; + const int MEM_OPS = READ_STIN_OPS + WRITE_STOUT_OPS + READ_REM_OPS; + + const int MEM_OPS_IN_PARALLEL = 4; + const int CLOCKS_WITH_READ_STIN_OPS = (READ_STIN_OPS + MEM_OPS_IN_PARALLEL - 1) / MEM_OPS_IN_PARALLEL; + const int CLOCKS_WITH_WRITE_STOUT_OPS = (WRITE_STOUT_OPS + MEM_OPS_IN_PARALLEL - 1) / MEM_OPS_IN_PARALLEL; + const int CLOCKS_WITH_MEM_OPS = (MEM_OPS + MEM_OPS_IN_PARALLEL - 1) / MEM_OPS_IN_PARALLEL; + + const expr mem_sel = clock_set(in_use, start: 0, end: CLOCKS_WITH_MEM_OPS); + + const expr mem_is_write = clock_set(start: CLOCKS_WITH_READ_STIN_OPS, end: CLOCKS_WITH_WRITE_STOUT_OPS); + + const expr main_step = clock_shift(step_addr, STEP_MAIN, start: 0, end: CLOCKS_WITH_MEM_OPS); + + for (int i = 0; i < MEM_OPS_IN_PARALLEL; i++) { + const expr mem_addr = clock_shift(step_addr, ADDR_STATE, start: 0, end: 4, delta: 8*i) + + clock_shift(step_addr, ADDR_INPUT, start: 4, end: 8, delta: 8*i) + + clock_shift(step_addr, ADDR_STATE, start: 8, end: 12, delta: 8*i) + + clock_shift(step_addr, ADDR_OP, start: 12, delta: 8*i); + + // TODO! + // expr mem_value[2]; + // mem_value[0] = CLK[0] * a_packed'3 + CLK[1] * a_packed + CLK[2] * e_packed' + CLK[3] * 2'e_packed + + // CLK[4] * w_spacked + CLK[5] * w_spacked' + CLK[6] * w_spacked'2 + CLK[7] * w_spacked'3 + + // CLK[8] * w_spacked'4 + CLK[9] * w_spacked'5 + CLK[10] * w_spacked'6 + CLK[11] * w_spacked'7 + + // CLK[12] * a_packed'59 + CLK[13] * a_packed'56 + CLK[14] * e_packed'57 + CLK[15] * e_packed'54 + + // clock_map(step_addr, ADDR_IND_0, 16) + clock_map(step_addr, ADDR_IND_1, 17); + + // mem_value[1] = CLK[0] * a_packed'2 + CLK[1] * 'a_packed + CLK[2] * e_packed + CLK[3] * 3'e_packed + + // CLK[4] * w_spacked' + CLK[5] * w_spacked'2 + CLK[6] * w_spacked'3 + CLK[7] * w_spacked'4 + + // CLK[8] * w_spacked'5 + CLK[9] * w_spacked'6 + CLK[10] * w_spacked'7 + CLK[11] * w_spacked'8 + + // CLK[12] * a_packed'58 + CLK[13] * a_packed'55 + CLK[14] * e_packed'56 + CLK[15] * e_packed'53; + // // addresses are 32-bit values + const expr mem_value[2] = [0 , 0]; + + precompiled_mem_op( + is_write: mem_is_write, + sel: mem_sel, + main_step: main_step, + addr: mem_addr, + value: mem_value + ); + } + + // --> Constraints to make sure that this coprocessor is called from the main processor + + lookup_proves(operation_bus_id, [OP_BLAKE2BR, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(STEP_MAIN)], mul: in_use_clk_0); + + function pack(const expr a[]): expr { + const int len = length(a); + expr packed = 0; + for (int j = 0; j < len; j++) { + packed += a[j] * 2**j; + } + return packed; + } + + // Checks a == (b + c + d) mod 2⁶⁴ + // Assumes a,b,c,d are all given as 2 limbs of 32-bits each, i.e., a = a0 + 2³²·a1 < 2⁶⁴ + // It also assumes that p > 3·2³² + function add3_check(const expr a[], const expr b[], const expr c[], const expr d[]): expr[] { + assert(length(a) == 2 && length(b) == 2 && length(c) == 2 && length(d) == 2); + // a == (b + c + d) mod 2⁶⁴ iff a - b - c - d = 0, -2⁶⁴, -2·2⁶⁴ (over the integers) + + // Since we work over a finite field, we instead check that + // (1) a0 - b0 - c0 - d0 = 0, -2³², -2·2³² (mod p) + // (2) a - b - c - d = 0, -2⁶⁴, -2·2⁶⁴ (mod p) + + // Notice that since p > 2·2³² we have that (1) holds over the integers + // Now since a - b - c - d = (a0 - b0 - c0 - d0) + 2³²·(a1 - b1 - c1 - d1), + // we have that a - b - c - d = 0 (mod 2³²) (3) + + // Using CRT with (2) and (3) we obtain: + // a - b - c - d = 0, -2⁶⁴, -2·2⁶⁴ (mod 2³²·p) (4) + // Now, since a,b,c,d < 2⁶⁴ and 2³²·p > 3·2⁶⁴, we have that (4) holds over the integers + + expr sum_0 = a[0] - b[0] - c[0] - d[0]; + expr sum_1 = a[1] - b[1] - c[1] - d[1]; + expr sum = sum_0 + P2_32*sum_1; + + sum_0 * (sum_0 + P2_32) * (sum_0 + 2*P2_32) === 0; + sum * (sum + P2_64) * (sum + 2*P2_64) === 0; + } + + // Checks a == (b + c) mod 2⁶⁴ + // Assumes a,b,c are all given as 2 limbs of 32-bits each, i.e., a = a0 + 2³²·a1 < 2⁶⁴ + // It also assumes that p > 2·2³² + function add2_check(const expr a[], const expr b[], const expr c[]): expr[] { + assert(length(a) == 2 && length(b) == 2 && length(c) == 2); + + // a == (b + c) mod 2⁶⁴ iff a - b - c = 0, -2⁶⁴ (over the integers) + + // Since we work over a finite field, we instead check that + // (1) a0 - b0 - c0 = 0, -2³² (mod p) + // (2) a - b - c = 0, -2⁶⁴ (mod p) + + // Notice that since p > 2³² we have that (1) holds over the integers + // Now since a - b - c = (a0 - b0 - c0) + 2³²·(a1 - b1 - c1), + // we have that a - b - c = 0 (mod 2³²) (3) + + // Using CRT with (2) and (3) we obtain: + // a - b - c = 0, -2⁶⁴ (mod 2³²·p) (4) + // Now, since a,b,c < 2⁶⁴ and 2³²·p > 2·2⁶⁴, we have that (4) holds over the integers + + expr sum_0 = a[0] - b[0] - c[0]; + expr sum_1 = a[1] - b[1] - c[1]; + expr sum = sum_0 + P2_32*sum_1; + + sum_0 * (sum_0 + P2_32) === 0; + sum * (sum + P2_64) === 0; + } + + // Checks a == (b <<< n) ^ c + // Assumes a is given as 2 limbs of 32-bits each, i.e., a = a0 + 2³²·a1 + // Assumes b,c are both given unpacked as 64 bits, i.e., b = b0 + 2·b1 + ... + 2⁶³·b63 + function rotl_xor_check(const expr a[], const expr b[][], const expr c[][], const int n) { + assert(length(a) == 2 && length(b) == 2 && length(c) == 2); + assert(length(b[0]) == 32 && length(b[1]) == 32 && length(c[0]) == 32 && length(c[1]) == 32); + + // Unpack b and c into 64-bit arrays + expr b_unpacked[64]; + expr c_unpacked[64]; + for (int i = 0; i < 32; i++) { + b_unpacked[i] = b[0][i]; + b_unpacked[32 + i] = b[1][i]; + c_unpacked[i] = c[0][i]; + c_unpacked[32 + i] = c[1][i]; + } + + // Rotate + expr b_rot[64]; + for (int i = 0; i < 64; i++) { + b_rot[i] = b_unpacked[(i + 64 - n) % 64]; + } + + // XOR + expr xor_result[64]; + for (int i = 0; i < 64; i++) { + xor_result[i] = b_rot[i] + c_unpacked[i] - 2 * b_rot[i] * c_unpacked[i]; + } + + // Pack into two 32-bit limbs + expr packed_low = 0; + expr packed_high = 0; + for (int i = 0; i < 32; i++) { + packed_low += xor_result[i] * 2**i; + packed_high += xor_result[32 + i] * 2**i; + } + + // Check equality + a[0] === packed_low; + a[1] === packed_high; + + // Notice that these equalities also ensure that a[0], a[1] are range checked + } + + function clock_eq(const expr mvcol, int pos1, int pos2): const expr { + return air.CLK_0 * (mvcol'(pos1) - mvcol'(pos2)); + } + + function clock_set(const expr mvcol = 1, int start = 0, int end = -1): const expr { + expr result = 0; + if (end == -1) { + end = start; + } + for (int i = start; i < end; i++) { + result += air.CLK[i]; + } + return result * mvcol; + } + + function clock_shift(const expr mvcol, const int pos, const int start = 0, int end = -1, int offset = 0, const int delta = 0): const expr { + expr result = 0; + if (end == -1) { + end = start + 1; + } + for (int i = start; i < end; i++) { + const int iclock = (pos - i) % air.CLOCK_NUM; + if (offset != 0) { + result += air.CLK[i] * (mvcol'(iclock) + offset); + } else { + result += air.CLK[i] * mvcol'(iclock); + } + offset += delta; + } + return result; + } +} \ No newline at end of file From b1c4e15dcbd8a3b1b0a37cab96c340d539ec3542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:01:58 +0000 Subject: [PATCH 641/782] First version of blake done --- Cargo.lock | 50 ++- Cargo.toml | 2 + common/src/bus/data_bus_operation.rs | 39 ++- core/src/helpers.rs | 7 + core/src/riscv2zisk_context.rs | 10 +- core/src/zisk_inst.rs | 2 + core/src/zisk_ops.rs | 115 +++++-- core/src/zisk_rom_2_asm.rs | 8 + executor/Cargo.toml | 1 + executor/src/sm_static_bundle.rs | 44 ++- executor/src/static_data_bus.rs | 12 +- executor/src/static_data_bus_collect.rs | 26 ++ executor/src/utils.rs | 12 +- pil/operations.pil | 4 +- pil/opids.pil | 1 + pil/zisk.pil | 3 + precompiles/blake2/Cargo.toml | 29 ++ precompiles/blake2/pil/blake2br.pil | 90 +++-- precompiles/blake2/src/blake2.rs | 322 ++++++++++++++++++ precompiles/blake2/src/blake2_bus_device.rs | 143 ++++++++ precompiles/blake2/src/blake2_constants.rs | 39 +++ .../blake2/src/blake2_gen_mem_inputs.rs | 105 ++++++ precompiles/blake2/src/blake2_input.rs | 26 ++ precompiles/blake2/src/blake2_instance.rs | 195 +++++++++++ precompiles/blake2/src/blake2_manager.rs | 80 +++++ precompiles/blake2/src/blake2_planner.rs | 136 ++++++++ precompiles/blake2/src/lib.rs | 17 + precompiles/helpers/src/blake2/blake2b/mod.rs | 81 +---- .../helpers/src/blake2/blake2b/round.rs | 98 ++---- .../helpers/src/blake2/blake2b/utils.rs | 19 -- precompiles/helpers/src/lib.rs | 2 +- ziskos/entrypoint/src/syscalls/blake2br.rs | 31 ++ ziskos/entrypoint/src/syscalls/mod.rs | 2 + ziskos/entrypoint/src/syscalls/syscall.rs | 3 + ziskos/entrypoint/src/zisklib/lib/blake2b.rs | 100 ++++++ ziskos/entrypoint/src/zisklib/lib/mod.rs | 2 + 36 files changed, 1602 insertions(+), 254 deletions(-) create mode 100644 precompiles/blake2/Cargo.toml create mode 100644 precompiles/blake2/src/blake2.rs create mode 100644 precompiles/blake2/src/blake2_bus_device.rs create mode 100644 precompiles/blake2/src/blake2_constants.rs create mode 100644 precompiles/blake2/src/blake2_gen_mem_inputs.rs create mode 100644 precompiles/blake2/src/blake2_input.rs create mode 100644 precompiles/blake2/src/blake2_instance.rs create mode 100644 precompiles/blake2/src/blake2_manager.rs create mode 100644 precompiles/blake2/src/blake2_planner.rs create mode 100644 precompiles/blake2/src/lib.rs delete mode 100644 precompiles/helpers/src/blake2/blake2b/utils.rs create mode 100644 ziskos/entrypoint/src/syscalls/blake2br.rs create mode 100644 ziskos/entrypoint/src/zisklib/lib/blake2b.rs diff --git a/Cargo.lock b/Cargo.lock index 81b1a3469..1193b50f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "fields", "num-bigint", @@ -1423,6 +1423,7 @@ dependencies = [ "precomp-arith-eq", "precomp-arith-eq-384", "precomp-big-int", + "precomp-blake2", "precomp-dma", "precomp-keccakf", "precomp-poseidon2", @@ -1468,7 +1469,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "cfg-if", "num-bigint", @@ -2772,7 +2773,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "colored", "fields", @@ -2978,6 +2979,25 @@ dependencies = [ "zisk-pil", ] +[[package]] +name = "precomp-blake2" +version = "0.16.0" +dependencies = [ + "fields", + "mem-common", + "pil-std-lib", + "precompiles-common", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sm-mem", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + [[package]] name = "precomp-dma" version = "0.16.0" @@ -3134,7 +3154,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "bincode", "blake3", @@ -3170,7 +3190,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "bincode", "borsh", @@ -3202,7 +3222,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "fields", "itoa", @@ -3215,7 +3235,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "proc-macro2", "quote", @@ -3225,7 +3245,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "crossbeam-channel", "tracing", @@ -3234,7 +3254,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "bincode", "bytemuck", @@ -3246,7 +3266,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "bytemuck", "fields", @@ -3538,9 +3558,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" @@ -3696,9 +3716,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", "log", @@ -5721,7 +5741,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#ac960d5e88a33538fd3d154cfed65716b12c4a99" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "colored", "fields", diff --git a/Cargo.toml b/Cargo.toml index bb19c7b67..8a10c116d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "precompiles/hints", "precompiles/keccakf", "precompiles/sha256f", + "precompiles/blake2", "precompiles/big_int", "precompiles/dma", "lib-c", @@ -79,6 +80,7 @@ precompiles-common = { path = "precompiles/common" } precompiles-helpers = { path = "precompiles/helpers" } precomp-keccakf = { path = "precompiles/keccakf" } precomp-sha256f = { path = "precompiles/sha256f" } +precomp-blake2 = { path = "precompiles/blake2" } precomp-poseidon2 = { path = "precompiles/poseidon2" } precomp-big-int = { path = "precompiles/big_int" } precomp-dma = { path = "precompiles/dma" } diff --git a/common/src/bus/data_bus_operation.rs b/common/src/bus/data_bus_operation.rs index 59c7ff668..24e006b62 100644 --- a/common/src/bus/data_bus_operation.rs +++ b/common/src/bus/data_bus_operation.rs @@ -74,6 +74,8 @@ pub const OPERATION_BUS_SECP256R1_ADD_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 2 * POINT_256_BITS_SIZE; pub const OPERATION_BUS_SECP256R1_DBL_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + POINT_256_BITS_SIZE; +pub const OPERATION_BUS_BLAKE2_DATA_SIZE: usize = + OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2 * INDIRECTION_SIZE + 33 * DATA_64_BITS_SIZE; // bus_data_size + 4 params (&a, &b, cin, &c, a, b) pub const OPERATION_BUS_ADD_256_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE @@ -87,8 +89,7 @@ pub const OPERATION_BUS_DMA_MEMCPY_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_ // 5 bus_precompiled_data + encoded + count_eq pub const OPERATION_BUS_DMA_MEMCMP_DATA_SIZE: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + 2; -// 4 bus_data + 5 addr + 4 x 384 = 4 + 5 + 4 * 6 = 33 -pub const MAX_OPERATION_DATA_SIZE: usize = OPERATION_BUS_ARITH_384_MOD_DATA_SIZE; +pub const MAX_OPERATION_DATA_SIZE: usize = OPERATION_BUS_BLAKE2_DATA_SIZE; /// Index of the operation value in the operation data payload. pub const OP: usize = 0; @@ -132,6 +133,7 @@ pub type OperationDmaMemCpyData = [D; OPERATION_BUS_DMA_MEMCPY_DATA_SIZE]; pub type OperationDmaMemCmpData = [D; OPERATION_BUS_DMA_MEMCMP_DATA_SIZE]; pub type OperationSecp256r1AddData = [D; OPERATION_BUS_SECP256R1_ADD_DATA_SIZE]; pub type OperationSecp256r1DblData = [D; OPERATION_BUS_SECP256R1_DBL_DATA_SIZE]; +pub type OperationBlake2Data = [D; OPERATION_BUS_BLAKE2_DATA_SIZE]; pub enum ExtOperationData { OperationData(OperationData), @@ -158,6 +160,7 @@ pub enum ExtOperationData { OperationDmaMemCmpData(OperationDmaMemCmpData), OperationSecp256r1AddData(OperationSecp256r1AddData), OperationSecp256r1DblData(OperationSecp256r1DblData), + OperationBlake2Data(OperationBlake2Data), } const KECCAK_OP: u8 = ZiskOp::Keccak.code(); @@ -183,6 +186,7 @@ const DMA_MEMCPY_OP: u8 = ZiskOp::DmaMemCpy.code(); const DMA_MEMCMP_OP: u8 = ZiskOp::DmaMemCmp.code(); const SECP256R1_ADD_OP: u8 = ZiskOp::Secp256r1Add.code(); const SECP256R1_DBL_OP: u8 = ZiskOp::Secp256r1Dbl.code(); +const BLAKE2_OP: u8 = ZiskOp::Blake2.code(); // impl> TryFrom<&[D]> for ExtOperationData { impl> TryFrom<&[D]> for ExtOperationData { @@ -209,6 +213,11 @@ impl> TryFrom<&[D]> for ExtOperationData { data.try_into().map_err(|_| "Invalid OperationPoseidon2Data size")?; Ok(ExtOperationData::OperationPoseidon2Data(array)) } + BLAKE2_OP => { + let array: OperationBlake2Data = + data.try_into().map_err(|_| "Invalid OperationBlake2Data size")?; + Ok(ExtOperationData::OperationBlake2Data(array)) + } ARITH256_OP => { let array: OperationArith256Data = data.try_into().map_err(|_| "Invalid OperationArith256Data size")?; @@ -394,6 +403,16 @@ impl OperationBusData { ExtOperationData::OperationPoseidon2Data(data) } + ZiskOperationType::Blake2 => { + let mut data = + unsafe { uninit_array::().assume_init() }; + data[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE..] + .copy_from_slice(&ctx.precompiled.input_data); + ExtOperationData::OperationBlake2Data(data) + } + ZiskOperationType::ArithEq => match inst.op { ARITH256_OP => { let mut data = unsafe { @@ -587,6 +606,7 @@ impl OperationBusData { } _ => ExtOperationData::OperationData([op, op_type, a, b]), }, + ZiskOperationType::Dma => match inst.op { DMA_MEMCPY_OP => { let mut data = unsafe { @@ -655,6 +675,15 @@ impl OperationBusData { &buffer[..OPERATION_BUS_POSEIDON2_DATA_SIZE] } + ZiskOperationType::Blake2 => { + debug_assert_eq!(ctx.precompiled.input_data.len(), 35); + buffer[0..OPERATION_PRECOMPILED_BUS_DATA_SIZE] + .copy_from_slice(&[op, op_type, a, b, step]); + buffer[OPERATION_PRECOMPILED_BUS_DATA_SIZE..OPERATION_BUS_BLAKE2_DATA_SIZE] + .copy_from_slice(&ctx.precompiled.input_data); + &buffer[..OPERATION_BUS_BLAKE2_DATA_SIZE] + } + ZiskOperationType::ArithEq => match inst.op { ARITH256_OP => { let len = @@ -821,6 +850,7 @@ impl OperationBusData { &buffer[..OPERATION_BUS_DATA_SIZE] } }, + ZiskOperationType::BigInt => match inst.op { ADD256_OP => { let len = @@ -836,6 +866,7 @@ impl OperationBusData { &buffer[..OPERATION_BUS_DATA_SIZE] } }, + ZiskOperationType::Dma => match inst.op { DMA_MEMCPY_OP | DMA_MEMCMP_OP => { let len = @@ -893,6 +924,7 @@ impl OperationBusData { ExtOperationData::OperationDmaMemCmpData(d) => d[OP] as u8, ExtOperationData::OperationSecp256r1AddData(d) => d[OP] as u8, ExtOperationData::OperationSecp256r1DblData(d) => d[OP] as u8, + ExtOperationData::OperationBlake2Data(d) => d[OP] as u8, } } @@ -930,6 +962,7 @@ impl OperationBusData { ExtOperationData::OperationDmaMemCmpData(d) => d[OP_TYPE], ExtOperationData::OperationSecp256r1AddData(d) => d[OP_TYPE], ExtOperationData::OperationSecp256r1DblData(d) => d[OP_TYPE], + ExtOperationData::OperationBlake2Data(d) => d[OP_TYPE], } } @@ -967,6 +1000,7 @@ impl OperationBusData { ExtOperationData::OperationDmaMemCmpData(d) => d[A], ExtOperationData::OperationSecp256r1AddData(d) => d[A], ExtOperationData::OperationSecp256r1DblData(d) => d[A], + ExtOperationData::OperationBlake2Data(d) => d[A], } } @@ -1004,6 +1038,7 @@ impl OperationBusData { ExtOperationData::OperationDmaMemCmpData(d) => d[B], ExtOperationData::OperationSecp256r1AddData(d) => d[B], ExtOperationData::OperationSecp256r1DblData(d) => d[B], + ExtOperationData::OperationBlake2Data(d) => d[B], } } } diff --git a/core/src/helpers.rs b/core/src/helpers.rs index 76882c1e4..a6e57c9e8 100644 --- a/core/src/helpers.rs +++ b/core/src/helpers.rs @@ -3,6 +3,8 @@ use sha2::compress256; #[allow(deprecated)] use sha2::digest::generic_array::{typenum::U64, GenericArray}; +use precompiles_helpers::blake2b_round; + #[allow(deprecated)] pub fn sha256f(state: &mut [u64; 4], input: &[u64; 8]) { let state_u32: &mut [u32; 8] = unsafe { &mut *(state.as_mut_ptr() as *mut [u32; 8]) }; @@ -10,3 +12,8 @@ pub fn sha256f(state: &mut [u64; 4], input: &[u64; 8]) { unsafe { &*(input.as_ptr() as *const [GenericArray; 1]) }; compress256(state_u32, input_u8); } + +#[allow(deprecated)] +pub fn blake2br(index: u64, state: &mut [u64; 16], input: &[u64; 16]) { + blake2b_round(state, input, index as u32); +} diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index fbfde894a..e9155d3b7 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -11,10 +11,11 @@ use crate::{ }; use std::collections::HashMap; -// The CSR precompiled addresses are defined in the `ZiskOS` `ziskos/entrypoint/src` files -// because legacy versions of Rust do not support constant parameters in `asm!` macros. -const CSR_PRECOMPILED: [&str; 23] = [ +// The CSR precompiled addresses are defined in the `ziskos/entrypoint/src/syscall.rs` file +// because legacy versions of Rust do not support constant parameters in `asm!` macros. +// Important: The order should be the same as in such file. +const CSR_PRECOMPILED: [&str; 24] = [ "keccak", "arith256", "arith256_mod", @@ -32,12 +33,13 @@ const CSR_PRECOMPILED: [&str; 23] = [ "bls12_381_complex_add", "bls12_381_complex_sub", "bls12_381_complex_mul", - "add256", + "add256", // Note: Constant CSR_PRECOMPILED_ADD256 needs to be updated if this is moved "poseidon2", "dma_memcpy", "dma_memcmp", "secp256r1_add", "secp256r1_dbl", + "blake2", ]; const CSR_PRECOMPILED_ADDR_START: u32 = 0x800; const CSR_PRECOMPILED_ADDR_END: u32 = CSR_PRECOMPILED_ADDR_START + CSR_PRECOMPILED.len() as u32; diff --git a/core/src/zisk_inst.rs b/core/src/zisk_inst.rs index 765352cbc..f0d5a09a3 100644 --- a/core/src/zisk_inst.rs +++ b/core/src/zisk_inst.rs @@ -79,6 +79,7 @@ pub enum ZiskOperationType { Keccak, Sha256, Poseidon2, + Blake2, PubOut, ArithEq, ArithEq384, @@ -105,6 +106,7 @@ pub const BIG_INT_OP_TYPE_ID: u32 = ZiskOperationType::BigInt as u32; pub const FCALL_PARAM_OP_TYPE_ID: u32 = ZiskOperationType::FcallParam as u32; pub const FCALL_OP_TYPE_ID: u32 = ZiskOperationType::Fcall as u32; pub const DMA_OP_TYPE_ID: u32 = ZiskOperationType::Dma as u32; +pub const BLAKE2_OP_TYPE_ID: u32 = ZiskOperationType::Blake2 as u32; /// ZisK instruction definition /// diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index cb1d2af0c..1719902e7 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -23,7 +23,7 @@ use std::{ use tiny_keccak::keccakf; use crate::{ - sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, + blake2br, sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, EXTRA_PARAMS, M64, REG_A0, SYS_ADDR, }; @@ -56,6 +56,7 @@ pub enum OpType { ArithEq384, BigInt, Dma, + Blake2, } impl From for ZiskOperationType { @@ -74,6 +75,7 @@ impl From for ZiskOperationType { OpType::ArithEq384 => ZiskOperationType::ArithEq384, OpType::BigInt => ZiskOperationType::BigInt, OpType::Dma => ZiskOperationType::Dma, + OpType::Blake2 => ZiskOperationType::Blake2, } } } @@ -96,6 +98,7 @@ impl Display for OpType { Self::ArithEq384 => write!(f, "Arith384"), Self::BigInt => write!(f, "BigInt"), Self::Dma => write!(f, "Dma"), + Self::Blake2 => write!(f, "Blake2"), } } } @@ -119,6 +122,7 @@ impl FromStr for OpType { "aeq384" => Ok(Self::ArithEq384), "bint" => Ok(Self::BigInt), "dma" => Ok(Self::Dma), + "bl" => Ok(Self::Blake2), _ => Err(InvalidOpTypeError), } } @@ -358,6 +362,7 @@ const FCALL_COST: u64 = INTERNAL_COST; const ARITH_EQ_384_COST: u64 = 79 * 24; const ADD256_COST: u64 = 104; const DMA_COST: u64 = 42; +const BLAKE2_COST: u64 = 1; //TODO: TBD const DMA_64_ALIGNED_COST: u64 = 40; const DMA_UNALIGNED_COST: u64 = 42; @@ -455,6 +460,7 @@ define_ops! { (Secp256k1Dbl, "secp256k1_dbl", ArithEq, ARITH_EQ_COST, 0xf5, 64, 64, opc_secp256k1_dbl, op_secp256k1_dbl, ops_secp256k1_dbl), (Secp256r1Add, "secp256r1_add", ArithEq, ARITH_EQ_COST, 0xe8, 144, 64, opc_secp256r1_add, op_secp256r1_add, ops_secp256r1_add), (Secp256r1Dbl, "secp256r1_dbl", ArithEq, ARITH_EQ_COST, 0xe9, 64, 64, opc_secp256r1_dbl, op_secp256r1_dbl, ops_secp256r1_dbl), + (Blake2, "blake2", Blake2, BLAKE2_COST, 0xea, 280 , 128, opc_blake2, op_blake2, ops_blake2), (FcallParam, "fcall_param", Fcall, FCALL_COST, 0xf6, 0, 0, opc_fcall_param, op_fcall_param, ops_none), (Fcall, "fcall", Fcall, FCALL_COST, 0xf7, 0, 0, opc_fcall, op_fcall, ops_none), (FcallGet, "fcall_get", Fcall, FCALL_COST, 0xf8, 0, 0, opc_fcall_get, op_fcall_get, ops_none), @@ -1319,7 +1325,7 @@ pub fn opc_sha256(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 4 + 4; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 2, 2, 4, 4, &mut data, "sha256"); + precompiled_load_data(ctx, 2, 2, 4, 4, None, &mut data, "sha256"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // Get the state and input slices @@ -1444,6 +1450,50 @@ pub fn ops_poseidon2(ctx: &InstContext, stats: &mut dyn OpStats) { precompiled_stats_direct_data(ctx, stats, 16, 16); } +#[inline(always)] +pub fn opc_blake2(ctx: &mut InstContext) { + const WORDS: usize = 3 + 2 * 16; // index,addr_state,addr_input,state[16],input[16] + let mut data = [0u64; WORDS]; + + precompiled_load_data(ctx, 3, 2, 16, 0, Some(0), &mut data, "blake2"); + + if ctx.emulation_mode != EmulationMode::ConsumeMemReads { + // Get the state and input slices + // 0 - index + // 1 - addr_state + // 2 - addr_input + let index = data[0]; + let (params, rest) = data.split_at_mut(3); + let (state_slice, input_slice) = rest.split_at_mut(16); + let state: &mut [u64; 16] = state_slice.try_into().unwrap(); + let input: &[u64; 16] = input_slice[..16].try_into().unwrap(); + + // Compute the blake2br output with the fastest implementation available + blake2br(index, state, input); + + let state_addr = params[1]; + for (i, d) in state.iter().enumerate() { + ctx.mem.write(state_addr + (8 * i as u64), *d, 8); + } + } + + ctx.c = 0; + ctx.flag = false; +} + +/// Unimplemented. Blake2 can only be called from the system call context via InstContext. +/// This is provided just for completeness. +#[inline(always)] +pub fn op_blake2(_a: u64, _b: u64) -> (u64, bool) { + unimplemented!("op_blake2() is not implemented"); +} + +#[inline(always)] +pub fn ops_blake2(ctx: &InstContext, stats: &mut dyn OpStats) { + precompiled_stats_data(ctx, stats, &[4, 8], &[], 1); +} + +#[allow(clippy::too_many_arguments)] #[inline(always)] pub fn precompiled_load_data( ctx: &mut InstContext, @@ -1451,6 +1501,7 @@ pub fn precompiled_load_data( load_indirections: usize, load_chunks: usize, load_rem: usize, + direct_load_param_idx: Option, data: &mut [u64], title: &str, ) { @@ -1461,7 +1512,7 @@ pub fn precompiled_load_data( load_chunks, load_rem, 0, - None, + direct_load_param_idx, data, title, ); @@ -1475,7 +1526,7 @@ pub fn precompiled_load_data_with_result( load_indirections: usize, load_chunks: usize, load_rem: usize, - direct_load_param: Option, + direct_load_param_idx: Option, data: &mut [u64], title: &str, ) { @@ -1486,7 +1537,7 @@ pub fn precompiled_load_data_with_result( load_chunks, load_rem, 1, - direct_load_param, + direct_load_param_idx, data, title, ); @@ -1501,7 +1552,7 @@ fn internal_precompiled_load_data( load_chunks: usize, load_rem: usize, result: usize, - direct_load_param: Option, // If one of the load parameters isn't an indirection + direct_load_param_idx: Option, // Index of the load parameters that isn't an indirection data: &mut [u64], title: &str, ) { @@ -1532,10 +1583,10 @@ fn internal_precompiled_load_data( return; } - // Write the indirections to data + // Write the indirections/direct_params to data for (i, data) in data.iter_mut().enumerate().take(params_count) { let indirection = ctx.mem.read(address + (8 * i as u64), 8); - if indirection & 0x7 != 0 && Some(i) != direct_load_param { + if indirection & 0x7 != 0 && Some(i) != direct_load_param_idx { panic!( "[{title}] precompiled_check_address() found address_{i} [0x{address:08X}]=0x{indirection:08X} \ not aligned to 8 bytes at PC:0x{:08X} STEP:{}", @@ -1545,11 +1596,23 @@ fn internal_precompiled_load_data( *data = indirection; } + // Write the data let mut data_offset = params_count; for i in 0..load_indirections { + let param_idx = if let Some(direct_idx) = direct_load_param_idx { + if i >= direct_idx { + i + 1 + } else { + i + } + } else { + i + }; + let data_offset = i * load_chunks + data_offset; // if there aren't indirections, take directly from the address - let param_address = if params_count == 0 { address + data_offset as u64 } else { data[i] }; + let param_address = + if params_count == 0 { address + data_offset as u64 } else { data[param_idx] }; for j in 0..load_chunks { let addr = param_address + (8 * j as u64); data[data_offset + j] = ctx.mem.read(addr, 8); @@ -1678,7 +1741,7 @@ pub fn opc_arith256(ctx: &mut InstContext) { const WORDS: usize = 5 + 3 * 4; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 5, 3, 4, 0, &mut data, "arith256"); + precompiled_load_data(ctx, 5, 3, 4, 0, None, &mut data, "arith256"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 5 indirections @@ -1725,7 +1788,7 @@ pub fn opc_arith256_mod(ctx: &mut InstContext) { const WORDS: usize = 5 + 4 * 4; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 5, 4, 4, 0, &mut data, "arith256_mod"); + precompiled_load_data(ctx, 5, 4, 4, 0, None, &mut data, "arith256_mod"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 5 indirections @@ -1771,7 +1834,7 @@ pub fn opc_secp256k1_add(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 8; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 2, 2, 8, 0, &mut data, "secp256k1_add"); + precompiled_load_data(ctx, 2, 2, 8, 0, None, &mut data, "secp256k1_add"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 2 indirections @@ -1810,7 +1873,7 @@ pub fn opc_secp256k1_dbl(ctx: &mut InstContext) { const WORDS: usize = 8; // one input of 8 64-bit words let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 0, 1, 8, 0, &mut data, "secp256k1_dbl"); + precompiled_load_data(ctx, 0, 1, 8, 0, None, &mut data, "secp256k1_dbl"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { let p1: &[u64; 8] = &data; @@ -1844,7 +1907,7 @@ pub fn opc_secp256r1_add(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 8; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 2, 2, 8, 0, &mut data, "secp256r1_add"); + precompiled_load_data(ctx, 2, 2, 8, 0, None, &mut data, "secp256r1_add"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 2 indirections @@ -1883,7 +1946,7 @@ pub fn opc_secp256r1_dbl(ctx: &mut InstContext) { const WORDS: usize = 8; // one input of 8 64-bit words let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 0, 1, 8, 0, &mut data, "secp256r1_dbl"); + precompiled_load_data(ctx, 0, 1, 8, 0, None, &mut data, "secp256r1_dbl"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { let p1: &[u64; 8] = &data; @@ -1917,7 +1980,7 @@ pub fn opc_bn254_curve_add(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 8; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 2, 2, 8, 0, &mut data, "bn254_curve_add"); + precompiled_load_data(ctx, 2, 2, 8, 0, None, &mut data, "bn254_curve_add"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 2 indirections @@ -1957,7 +2020,7 @@ pub fn opc_bn254_curve_dbl(ctx: &mut InstContext) { const WORDS: usize = 8; // one input of 8 64-bit words let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 0, 1, 8, 0, &mut data, "bn254_curve_dbl"); + precompiled_load_data(ctx, 0, 1, 8, 0, None, &mut data, "bn254_curve_dbl"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { let p1: &[u64; 8] = &data; @@ -1991,7 +2054,7 @@ pub fn opc_bn254_complex_add(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 8; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 2, 2, 8, 0, &mut data, "bn254_complex_add"); + precompiled_load_data(ctx, 2, 2, 8, 0, None, &mut data, "bn254_complex_add"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 2 indirections @@ -2031,7 +2094,7 @@ pub fn opc_bn254_complex_sub(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 8; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 2, 2, 8, 0, &mut data, "bn254_complex_sub"); + precompiled_load_data(ctx, 2, 2, 8, 0, None, &mut data, "bn254_complex_sub"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 2 indirections @@ -2071,7 +2134,7 @@ pub fn opc_bn254_complex_mul(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 8; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 2, 2, 8, 0, &mut data, "bn254_complex_mul"); + precompiled_load_data(ctx, 2, 2, 8, 0, None, &mut data, "bn254_complex_mul"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 2 indirections @@ -2111,7 +2174,7 @@ pub fn opc_arith384_mod(ctx: &mut InstContext) { const WORDS: usize = 5 + 4 * 6; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 5, 4, 6, 0, &mut data, "arith384_mod"); + precompiled_load_data(ctx, 5, 4, 6, 0, None, &mut data, "arith384_mod"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 5 indirections @@ -2157,7 +2220,7 @@ pub fn opc_bls12_381_curve_add(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 12; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 2, 2, 12, 0, &mut data, "bls12_381_curve_add"); + precompiled_load_data(ctx, 2, 2, 12, 0, None, &mut data, "bls12_381_curve_add"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 2 indirections @@ -2197,7 +2260,7 @@ pub fn opc_bls12_381_curve_dbl(ctx: &mut InstContext) { const WORDS: usize = 12; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 0, 1, 12, 0, &mut data, "bls12_381_curve_dbl"); + precompiled_load_data(ctx, 0, 1, 12, 0, None, &mut data, "bls12_381_curve_dbl"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { let p1: &[u64; 12] = &data; @@ -2231,7 +2294,7 @@ pub fn opc_bls12_381_complex_add(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 12; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 2, 2, 12, 0, &mut data, "bls12_381_complex_add"); + precompiled_load_data(ctx, 2, 2, 12, 0, None, &mut data, "bls12_381_complex_add"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 2 indirections @@ -2271,7 +2334,7 @@ pub fn opc_bls12_381_complex_sub(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 12; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 2, 2, 12, 0, &mut data, "bls12_381_complex_sub"); + precompiled_load_data(ctx, 2, 2, 12, 0, None, &mut data, "bls12_381_complex_sub"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 2 indirections @@ -2311,7 +2374,7 @@ pub fn opc_bls12_381_complex_mul(ctx: &mut InstContext) { const WORDS: usize = 2 + 2 * 12; let mut data = [0u64; WORDS]; - precompiled_load_data(ctx, 2, 2, 12, 0, &mut data, "bls12_381_complex_mul"); + precompiled_load_data(ctx, 2, 2, 12, 0, None, &mut data, "bls12_381_complex_mul"); if ctx.emulation_mode != EmulationMode::ConsumeMemReads { // ignore 2 indirections diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index bd47128f1..a948b17fb 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -244,6 +244,7 @@ impl ZiskAsmContext { | ZiskOp::Add256 | ZiskOp::Secp256r1Add | ZiskOp::Secp256r1Dbl + | ZiskOp::Blake2 ) } @@ -313,6 +314,9 @@ impl ZiskAsmContext { pub fn precompile_results_add256(&self) -> bool { self.precompile_results() } + pub fn precompile_results_blake2(&self) -> bool { + self.precompile_results() + } pub fn call_wait_for_prec_avail(&self) -> bool { self.precompile_results() } @@ -640,6 +644,7 @@ impl ZiskRom2Asm { *code += ".extern opcode_bls12_381_complex_sub\n"; *code += ".extern opcode_bls12_381_complex_mul\n"; *code += ".extern opcode_add256\n"; + *code += ".extern opcode_blake2\n"; *code += ".extern chunk_done\n"; *code += ".extern print_fcall_ctx\n"; *code += ".extern print_pc\n"; @@ -5448,6 +5453,9 @@ impl ZiskRom2Asm { ctx.c.is_saved = true; ctx.flag_is_always_zero = true; } + ZiskOp::Blake2 => { + unimplemented!(); + } ZiskOp::PubOut => { assert!(ctx.store_b_in_c); ctx.c.is_constant = ctx.b.is_constant; diff --git a/executor/Cargo.toml b/executor/Cargo.toml index 7fab72504..dcf71ecdc 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -36,6 +36,7 @@ precompiles-common = { workspace = true } precomp-keccakf = { workspace = true } precomp-sha256f = { workspace = true } precomp-poseidon2 = { workspace = true } +precomp-blake2 = { workspace = true } precomp-arith-eq = { workspace = true } precomp-arith-eq-384 = { workspace = true } precomp-big-int = { workspace = true } diff --git a/executor/src/sm_static_bundle.rs b/executor/src/sm_static_bundle.rs index 19b4aca10..b83b59237 100644 --- a/executor/src/sm_static_bundle.rs +++ b/executor/src/sm_static_bundle.rs @@ -8,6 +8,7 @@ use precomp_arith_eq::{ArithEqInstance, ArithEqManager}; use precomp_arith_eq_384::ArithEq384Instance; use precomp_arith_eq_384::ArithEq384Manager; use precomp_big_int::{Add256Instance, Add256Manager}; +use precomp_blake2::{Blake2Instance, Blake2Manager}; use precomp_dma::Dma64AlignedInstance; use precomp_dma::DmaInstance; use precomp_dma::DmaManager; @@ -33,9 +34,10 @@ use zisk_pil::DMA_PRE_POST_AIR_IDS; use zisk_pil::DMA_UNALIGNED_AIR_IDS; use zisk_pil::{ ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, BINARY_AIR_IDS, - BINARY_EXTENSION_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, MEM_ALIGN_AIR_IDS, - MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, MEM_ALIGN_WRITE_BYTE_AIR_IDS, - POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, + BINARY_EXTENSION_AIR_IDS, BLAKE_2_BR_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, + MEM_ALIGN_AIR_IDS, MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, + MEM_ALIGN_WRITE_BYTE_AIR_IDS, POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, + SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, }; use crate::{StaticDataBus, ZiskRom}; @@ -52,6 +54,7 @@ pub enum StateMachines { KeccakfManager(Arc>), Sha256fManager(Arc>), Poseidon2Manager(Arc>), + Blake2Manager(Arc>), ArithEqManager(Arc>), ArithEq384Manager(Arc>), Add256Manager(Arc>), @@ -68,10 +71,11 @@ impl StateMachines { StateMachines::KeccakfManager(_) => 4, StateMachines::Sha256fManager(_) => 5, StateMachines::Poseidon2Manager(_) => 6, - StateMachines::ArithEqManager(_) => 7, - StateMachines::ArithEq384Manager(_) => 8, - StateMachines::Add256Manager(_) => 9, - StateMachines::DmaManager(_) => 10, + StateMachines::Blake2Manager(_) => 7, + StateMachines::ArithEqManager(_) => 8, + StateMachines::ArithEq384Manager(_) => 9, + StateMachines::Add256Manager(_) => 10, + StateMachines::DmaManager(_) => 11, } } @@ -90,6 +94,7 @@ impl StateMachines { StateMachines::KeccakfManager(sm) => (**sm).build_planner(), StateMachines::Sha256fManager(sm) => (**sm).build_planner(), StateMachines::Poseidon2Manager(sm) => (**sm).build_planner(), + StateMachines::Blake2Manager(sm) => (**sm).build_planner(), StateMachines::ArithEqManager(sm) => (**sm).build_planner(), StateMachines::ArithEq384Manager(sm) => (**sm).build_planner(), StateMachines::Add256Manager(sm) => (**sm).build_planner(), @@ -108,6 +113,7 @@ impl StateMachines { StateMachines::KeccakfManager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::Sha256fManager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::Poseidon2Manager(sm) => (**sm).configure_instances(pctx, plans), + StateMachines::Blake2Manager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::ArithEqManager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::ArithEq384Manager(sm) => (**sm).configure_instances(pctx, plans), StateMachines::Add256Manager(sm) => (**sm).configure_instances(pctx, plans), @@ -124,6 +130,7 @@ impl StateMachines { StateMachines::KeccakfManager(sm) => (**sm).build_instance(ictx), StateMachines::Sha256fManager(sm) => (**sm).build_instance(ictx), StateMachines::Poseidon2Manager(sm) => (**sm).build_instance(ictx), + StateMachines::Blake2Manager(sm) => (**sm).build_instance(ictx), StateMachines::ArithEqManager(sm) => (**sm).build_instance(ictx), StateMachines::ArithEq384Manager(sm) => (**sm).build_instance(ictx), StateMachines::Add256Manager(sm) => (**sm).build_instance(ictx), @@ -223,6 +230,7 @@ impl StaticSMBundle { let mut keccakf_counter = None; let mut sha256f_counter = None; let mut poseidon2_counter = None; + let mut blake2_counter = None; let mut arith_eq_counter = None; let mut arith_eq_384_counter = None; let mut add256_counter = None; @@ -261,6 +269,12 @@ impl StaticSMBundle { poseidon2_sm.build_poseidon2_counter(self.process_only_operation_bus), )); } + StateMachines::Blake2Manager(blake2_sm) => { + blake2_counter = Some(( + sm.type_id(), + blake2_sm.build_blake2_counter(self.process_only_operation_bus), + )); + } StateMachines::ArithEqManager(arith_eq_sm) => { arith_eq_counter = Some(( sm.type_id(), @@ -297,6 +311,7 @@ impl StaticSMBundle { keccakf_counter.expect("Keccakf counter not found"), sha256f_counter.expect("Sha256f counter not found"), poseidon2_counter.expect("Poseidon2 counter not found"), + blake2_counter.expect("Blake2 counter not found"), arith_eq_counter.expect("ArithEq counter not found"), arith_eq_384_counter.expect("ArithEq384 counter not found"), add256_counter.expect("Add256 counter not found"), @@ -329,6 +344,7 @@ impl StaticSMBundle { let mut keccakf_collectors = Vec::new(); let mut sha256f_collectors = Vec::new(); let mut poseidon2_collectors = Vec::new(); + let mut blake2_collectors = Vec::new(); let mut arith_eq_collectors = Vec::new(); let mut arith_eq_384_collectors = Vec::new(); let mut add256_collectors = Vec::new(); @@ -456,6 +472,13 @@ impl StaticSMBundle { poseidon2_instance.build_poseidon2_collector(ChunkId(chunk_id)); poseidon2_collectors.push((*global_idx, poseidon2_collector)); } + air_id if air_id == BLAKE_2_BR_AIR_IDS[0] => { + let blake2_instance = + secn_instance.as_any().downcast_ref::>().unwrap(); + let blake2_collector = + blake2_instance.build_blake2_collector(ChunkId(chunk_id)); + blake2_collectors.push((*global_idx, blake2_collector)); + } air_id if air_id == ARITH_EQ_AIR_IDS[0] => { let arith_eq_instance = secn_instance .as_any() @@ -534,6 +557,7 @@ impl StaticSMBundle { let mut keccakf_inputs_generator = None; let mut sha256f_inputs_generator = None; let mut poseidon2_inputs_generator = None; + let mut blake2_inputs_generator = None; let mut arith_inputs_generator = None; let mut add256_inputs_generator = None; let mut dma_inputs_generator = None; @@ -554,6 +578,10 @@ impl StaticSMBundle { poseidon2_inputs_generator = Some(poseidon2_sm.build_poseidon2_input_generator()); } + StateMachines::Blake2Manager(blake2_sm) => { + blake2_inputs_generator = + Some(blake2_sm.build_blake2_input_generator()); + } StateMachines::ArithEqManager(arith_eq_sm) => { arith_eq_inputs_generator = Some(arith_eq_sm.build_arith_eq_input_generator()); @@ -583,6 +611,7 @@ impl StaticSMBundle { keccakf_collectors, sha256f_collectors, poseidon2_collectors, + blake2_collectors, arith_eq_collectors, arith_eq_384_collectors, add256_collectors, @@ -596,6 +625,7 @@ impl StaticSMBundle { keccakf_inputs_generator.expect("KeccakF input generator not found"), sha256f_inputs_generator.expect("SHA256F input generator not found"), poseidon2_inputs_generator.expect("Poseidon2 input generator not found"), + blake2_inputs_generator.expect("Blake2 input generator not found"), arith_inputs_generator.expect("Arith input generator not found"), add256_inputs_generator.expect("Add256 input generator not found"), dma_inputs_generator.expect("Dma input generator not found"), diff --git a/executor/src/static_data_bus.rs b/executor/src/static_data_bus.rs index a1a6e4468..de495aa6e 100644 --- a/executor/src/static_data_bus.rs +++ b/executor/src/static_data_bus.rs @@ -10,6 +10,7 @@ use mem_common::MemCounters; use precomp_arith_eq::ArithEqCounterInputGen; use precomp_arith_eq_384::ArithEq384CounterInputGen; use precomp_big_int::Add256CounterInputGen; +use precomp_blake2::Blake2CounterInputGen; use precomp_dma::DmaCounterInputGen; use precomp_keccakf::KeccakfCounterInputGen; use precomp_poseidon2::Poseidon2CounterInputGen; @@ -21,7 +22,7 @@ use sm_main::MainCounter; use zisk_common::{BusDeviceMetrics, BusId, PayloadType, MEM_BUS_ID, OPERATION_BUS_ID}; use zisk_core::{ ARITH_EQ_384_OP_TYPE_ID, ARITH_EQ_OP_TYPE_ID, ARITH_OP_TYPE_ID, BIG_INT_OP_TYPE_ID, - BINARY_E_OP_TYPE_ID, BINARY_OP_TYPE_ID, DMA_OP_TYPE_ID, KECCAK_OP_TYPE_ID, + BINARY_E_OP_TYPE_ID, BINARY_OP_TYPE_ID, BLAKE2_OP_TYPE_ID, DMA_OP_TYPE_ID, KECCAK_OP_TYPE_ID, POSEIDON2_OP_TYPE_ID, PUB_OUT_OP_TYPE_ID, SHA256_OP_TYPE_ID, }; @@ -46,6 +47,7 @@ pub struct StaticDataBus { pub keccakf_counter: (usize, KeccakfCounterInputGen), pub sha256f_counter: (usize, Sha256fCounterInputGen), pub poseidon2_counter: (usize, Poseidon2CounterInputGen), + pub blake2_counter: (usize, Blake2CounterInputGen), pub arith_eq_counter: (usize, ArithEqCounterInputGen), pub arith_eq_384_counter: (usize, ArithEq384CounterInputGen), pub add_256_counter: (usize, Add256CounterInputGen), @@ -66,6 +68,7 @@ impl StaticDataBus { keccakf_counter: (usize, KeccakfCounterInputGen), sha256f_counter: (usize, Sha256fCounterInputGen), poseidon2_counter: (usize, Poseidon2CounterInputGen), + blake2_counter: (usize, Blake2CounterInputGen), arith_eq_counter: (usize, ArithEqCounterInputGen), arith_eq_384_counter: (usize, ArithEq384CounterInputGen), add_256_counter: (usize, Add256CounterInputGen), @@ -81,6 +84,7 @@ impl StaticDataBus { keccakf_counter, sha256f_counter, poseidon2_counter, + blake2_counter, arith_eq_counter, arith_eq_384_counter, add_256_counter, @@ -141,6 +145,11 @@ impl StaticDataBus { data, &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), ), + BLAKE2_OP_TYPE_ID => self.blake2_counter.1.process_data( + &bus_id, + data, + &mut MemCounterProcessor::new(self.mem_counter.1.as_mut()), + ), ARITH_EQ_OP_TYPE_ID => self.arith_eq_counter.1.process_data( &bus_id, data, @@ -209,6 +218,7 @@ impl DataBusTrait> for StaticDataBus { pub sha256f_inputs_generator: Sha256fCounterInputGen, pub poseidon2_collector: Vec<(usize, Poseidon2Collector)>, pub poseidon2_inputs_generator: Poseidon2CounterInputGen, + pub blake2_collector: Vec<(usize, Blake2Collector)>, + pub blake2_inputs_generator: Blake2CounterInputGen, /// Arithmetic equality collectors pub arith_eq_collector: Vec<(usize, ArithEqCollector)>, @@ -97,6 +101,7 @@ const ARITH_TYPE: u64 = ZiskOperationType::Arith as u64; const KECCAK_TYPE: u64 = ZiskOperationType::Keccak as u64; const SHA256_TYPE: u64 = ZiskOperationType::Sha256 as u64; const POSEIDON2_TYPE: u64 = ZiskOperationType::Poseidon2 as u64; +const BLAKE2_TYPE: u64 = ZiskOperationType::Blake2 as u64; const ARITH_EQ_TYPE: u64 = ZiskOperationType::ArithEq as u64; const ARITH_EQ_384_TYPE: u64 = ZiskOperationType::ArithEq384 as u64; const BIG_INT_OP_TYPE_ID: u64 = ZiskOperationType::BigInt as u64; @@ -115,6 +120,7 @@ impl StaticDataBusCollect { keccakf_collector: Vec<(usize, KeccakfCollector)>, sha256f_collector: Vec<(usize, Sha256fCollector)>, poseidon2_collector: Vec<(usize, Poseidon2Collector)>, + blake2_collector: Vec<(usize, Blake2Collector)>, arith_eq_collector: Vec<(usize, ArithEqCollector)>, arith_eq_384_collector: Vec<(usize, ArithEq384Collector)>, add256_collector: Vec<(usize, Add256Collector)>, @@ -128,6 +134,7 @@ impl StaticDataBusCollect { keccakf_inputs_generator: KeccakfCounterInputGen, sha256f_inputs_generator: Sha256fCounterInputGen, poseidon2_inputs_generator: Poseidon2CounterInputGen, + blake2_inputs_generator: Blake2CounterInputGen, arith_inputs_generator: ArithCounterInputGen, add256_inputs_generator: Add256CounterInputGen, dma_inputs_generator: DmaCounterInputGen, @@ -142,6 +149,7 @@ impl StaticDataBusCollect { keccakf_collector, sha256f_collector, poseidon2_collector, + blake2_collector, arith_eq_collector, arith_eq_384_collector, add256_collector, @@ -155,6 +163,7 @@ impl StaticDataBusCollect { keccakf_inputs_generator, sha256f_inputs_generator, poseidon2_inputs_generator, + blake2_inputs_generator, arith_inputs_generator, add256_inputs_generator, dma_inputs_generator, @@ -246,6 +255,19 @@ impl StaticDataBusCollect { ), ); } + BLAKE2_TYPE => { + for (_, blake2_collector) in &mut self.blake2_collector { + blake2_collector.process_data(&bus_id, data); + } + self.blake2_inputs_generator.process_data( + &bus_id, + data, + &mut MemCollectorProcessor::new( + &mut self.mem_collector, + &mut self.mem_align_collector, + ), + ); + } ARITH_EQ_TYPE => { for (_, arith_eq_collector) in &mut self.arith_eq_collector { arith_eq_collector.process_data(&bus_id, data); @@ -391,6 +413,10 @@ impl DataBusTrait>> result.push((Some(id), Some(Box::new(collector) as Box>))); } + for (id, collector) in self.blake2_collector { + result.push((Some(id), Some(Box::new(collector) as Box>))); + } + for (id, collector) in self.arith_eq_collector { result.push((Some(id), Some(Box::new(collector) as Box>))); } diff --git a/executor/src/utils.rs b/executor/src/utils.rs index 2635f33df..359d04374 100644 --- a/executor/src/utils.rs +++ b/executor/src/utils.rs @@ -4,6 +4,7 @@ use pil_std_lib::Std; use precomp_arith_eq::ArithEqManager; use precomp_arith_eq_384::ArithEq384Manager; use precomp_big_int::Add256Manager; +use precomp_blake2::Blake2Manager; use precomp_dma::DmaManager; use precomp_keccakf::KeccakfManager; use precomp_poseidon2::Poseidon2Manager; @@ -24,9 +25,9 @@ use zisk_core::CHUNK_SIZE; use zisk_pil::PACKED_INFO; use zisk_pil::{ ADD_256_AIR_IDS, ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, - BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, DMA_64_ALIGNED_AIR_IDS, DMA_AIR_IDS, - DMA_PRE_POST_AIR_IDS, DMA_UNALIGNED_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, - MEM_ALIGN_AIR_IDS, MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, + BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, BLAKE_2_BR_AIR_IDS, DMA_64_ALIGNED_AIR_IDS, + DMA_AIR_IDS, DMA_PRE_POST_AIR_IDS, DMA_UNALIGNED_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, + MEM_AIR_IDS, MEM_ALIGN_AIR_IDS, MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, MEM_ALIGN_WRITE_BYTE_AIR_IDS, POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, }; @@ -83,6 +84,7 @@ fn initialize_executor( let keccakf_sm = KeccakfManager::new(std.clone()); let sha256f_sm = Sha256fManager::new(std.clone()); let poseidon2_sm = Poseidon2Manager::new(); + let blake2_sm = Blake2Manager::new(std.clone()); let arith_eq_sm = ArithEqManager::new(std.clone()); let arith_eq_384_sm = ArithEq384Manager::new(std.clone()); let add256_sm = Add256Manager::new(std.clone()); @@ -132,6 +134,10 @@ fn initialize_executor( vec![(ZISK_AIRGROUP_ID, POSEIDON_2_AIR_IDS[0])], StateMachines::Poseidon2Manager(poseidon2_sm.clone()), ), + ( + vec![(ZISK_AIRGROUP_ID, BLAKE_2_BR_AIR_IDS[0])], + StateMachines::Blake2Manager(blake2_sm.clone()), + ), ( vec![(ZISK_AIRGROUP_ID, ARITH_EQ_AIR_IDS[0])], StateMachines::ArithEqManager(arith_eq_sm.clone()), diff --git a/pil/operations.pil b/pil/operations.pil index 859fca342..d7748a4c1 100644 --- a/pil/operations.pil +++ b/pil/operations.pil @@ -11,7 +11,7 @@ - DMA Operations: - 0xDA-0xDF - Precompiles: - - 0xE1-0xE9 + - 0xE1-0xEA - 0xF0-0xF5 - 0xF9-0xFE - Misc: @@ -100,6 +100,8 @@ const int OP_COMPLEX_MUL_BLS12_381 = 0xE7; const int OP_EC_ADD_SECP256R1 = 0xE8; const int OP_EC_DBL_SECP256R1 = 0xE9; +const int OP_BLAKE2BR = 0xEA; + const int OP_ADD256 = 0xF0; const int OP_KECCAKF = 0xF1; const int OP_ARITH_256 = 0xF2; diff --git a/pil/opids.pil b/pil/opids.pil index 2509e760b..5f6820fc6 100644 --- a/pil/opids.pil +++ b/pil/opids.pil @@ -28,6 +28,7 @@ const int BINARY_EXTENSION_FROPS_TABLE_ID = 5012; // Precompiles const int ARITH_EQ_LT_TABLE_ID = 5002; const int KECCAKF_TABLE_ID = 126; +const int BLAKE2BR_PERMUTATION_ID = 127; // DMA const int DMA_BUS_ID = 8000; diff --git a/pil/zisk.pil b/pil/zisk.pil index f9eed8b35..4b607e3f8 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -27,6 +27,7 @@ require "dma/pil/dma_pre_post_table.pil" require "dma/pil/dma_64_aligned.pil" require "dma/pil/dma_unaligned.pil" require "poseidon2/pil/poseidon2.pil" +require "blake2/pil/blake2br.pil" enable_range_stats(); @@ -117,6 +118,8 @@ airgroup Zisk { Poseidon2(N: 2**17); + Blake2br(N: 2**20); + // Public Inputs for (int i = 0; i < PUBLIC_INPUTS_64_BITS; i++) { direct_global_update_proves(OPERATION_BUS_ID, [OP_PUBOUT, i, 0, inputs[i*2], inputs[i*2 + 1], inputs[i*2], inputs[i*2 + 1], 0, 0]); diff --git a/precompiles/blake2/Cargo.toml b/precompiles/blake2/Cargo.toml new file mode 100644 index 000000000..43cad97e4 --- /dev/null +++ b/precompiles/blake2/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "precomp-blake2" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +keywords = { workspace = true } +repository = { workspace = true } +categories = { workspace = true } + +[dependencies] +zisk-core = { workspace = true } +zisk-common = { workspace = true } +zisk-pil = { workspace = true } +precompiles-common = { workspace = true } +sm-mem = { workspace = true } +mem-common = { workspace = true } + +proofman-common = { workspace = true } +proofman-macros = { workspace = true } +proofman-util = { workspace = true } +pil-std-lib = { workspace = true } +fields = { workspace=true } +tracing = { workspace = true } +rayon = { workspace = true } + +[features] +default = [] +gpu = ["packed"] +packed = [] \ No newline at end of file diff --git a/precompiles/blake2/pil/blake2br.pil b/precompiles/blake2/pil/blake2br.pil index 7613a8e7b..d81af9506 100644 --- a/precompiles/blake2/pil/blake2br.pil +++ b/precompiles/blake2/pil/blake2br.pil @@ -1,5 +1,6 @@ require "std_constants.pil" require "std_lookup.pil" +require "std_permutation.pil" require "operations.pil" require "opids.pil" @@ -8,6 +9,8 @@ require "opids.pil" // We use little-endian representation +// TODO: Add missing range checks! + airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION_BUS_ID) { /* ═══════════════════════════════════════════════════════════════════════════ @@ -79,44 +82,38 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION const int CLOCKS_PER_G = 3; const int G_FUNCTIONS_PER_ROUND = 8; - const int CLOCK_NUM = CLOCKS_PER_G * G_FUNCTIONS_PER_ROUND; + const int CLOCKS = CLOCKS_PER_G * G_FUNCTIONS_PER_ROUND; // Ensure that the Blake2br fits - if (N < 2*CLOCK_NUM) { - error(`Blake2br requires N >= ${2*CLOCK_NUM}, got N=${N}`); + if (N < 2*CLOCKS) { + error(`Blake2br requires N >= ${2*CLOCKS}, got N=${N}`); } // Calculate usable capacity - const int NUM_NON_USABLE_ROWS = N % CLOCK_NUM; + const int NUM_NON_USABLE_ROWS = N % CLOCKS; const int NUM_BLAKE2BR; if (NUM_NON_USABLE_ROWS == 0) { - NUM_BLAKE2BR = N / CLOCK_NUM; + NUM_BLAKE2BR = N / CLOCKS; } else { - NUM_BLAKE2BR = (N - NUM_NON_USABLE_ROWS) / CLOCK_NUM - 1; // The -1 is because CLOCK_NUM is not a divisor of N + NUM_BLAKE2BR = (N - NUM_NON_USABLE_ROWS) / CLOCKS - 1; // The -1 is because CLOCKS is not a divisor of N } println(`Blake2br capacity: ${NUM_BLAKE2BR} rounds (N=${N})`); - col fixed CLK_0 = [[1, 0:(CLOCK_NUM-1)]:NUM_BLAKE2BR, 0...]; + col fixed CLK_0 = [[1, 0:(CLOCKS-1)]:NUM_BLAKE2BR, 0...]; - const expr CLK[CLOCK_NUM]; - for (int i = 0; i < CLOCK_NUM; i++) { + const expr CLK[CLOCKS]; + for (int i = 0; i < CLOCKS; i++) { CLK[i] = (i)'CLK_0; } - // Define the clock selectors - col witness bits(1) in_use_clk_0; // 1 at the first clock cycle of the Blake2broperation, 0 otherwise - col witness bits(1) in_use; // 1 when the Blake2br operation is in use, 0 otherwise + // Define the clock selector + col witness bits(1) in_use; // 1 when the Blake2br operation is in use, 0 otherwise - in_use_clk_0 * (1 - in_use_clk_0) === 0; in_use * (1 - in_use) === 0; - in_use_clk_0 - CLK_0 * in_use === 0; // This constraint is two-fold: - // · in_use_clk_0 can only be active when CLK_0 is active - // · if in_use is active then so is in_use_clk_0 - // Selector latching: once activated, stays active for full cycle - const expr in_use_continues = clock_set(start: 1, end: CLOCK_NUM); - in_use_continues * (in_use - 'in_use) === 0; + const expr in_use_active = clock_set(start: 1, end: CLOCKS); + in_use_active * (in_use - 'in_use) === 0; // --> Constraints to assert the BLAKE2B round function @@ -165,7 +162,7 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION for (int j = 0; j < 10; j++) { expr sum_sigmas = 0; int sig_idx = 0; - for (int k = 0; k < CLOCK_NUM; k++) { + for (int k = 0; k < CLOCKS; k++) { if (k > 0 && k % 2 == 0) { continue; } @@ -188,12 +185,20 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION const expr ms_next[2] = [ms[0]', ms[1]']; - // TODO: Decide the selector! - permutation_assumes(opid: BLAKE2BR_PERMUTATION_ID, expressions: [MSG_IDX, m[0], m[1]]); - permutation_proves(opid: BLAKE2BR_PERMUTATION_ID, expressions: [s, ms[0], ms[1]]); + // Perform the permutation + col witness bits(1) in_use_clk_perm; // 1 when the permutation is activated + const expr perm_active = clock_set(in_use, start: 0, end: CLOCKS - G_FUNCTIONS_PER_ROUND, step: 2, skip: 1); + in_use_clk_perm <== perm_active; + + permutation_assumes(opid: BLAKE2BR_PERMUTATION_ID, expressions: [MSG_IDX, m[0], m[1]], sel: in_use_clk_perm); + permutation_proves(opid: BLAKE2BR_PERMUTATION_ID, expressions: [s, ms[0], ms[1]], sel: in_use_clk_perm); // 2] Compute the g mixing function + col witness bits(1) in_use_clk_g; // 1 when the g function is activated + const expr g_active = clock_set(in_use, start: 0, end: CLOCKS / 3, step: 1, skip: 2); + in_use_clk_g <== g_active; + // The four v-values of g col witness bits(32) va[2]; col witness bits(1) vb[2][32]; @@ -309,7 +314,7 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION clock_eq(step_addr, ADDR_INPUT, ADDR_IND_1) === 0; // Perform 4 memory operations in parallel - // Total: 51 mem ops, Steps Needed: ceil(51/4) = 12 < 24 (CLOCK_NUM) + // Total: 51 mem ops, Steps Needed: ceil(51/4) = 12 < 24 (CLOCKS) // - 0..3 -> Read State // - 4..7 -> Read Input // - 8..11 -> Write State @@ -361,6 +366,8 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION } // --> Constraints to make sure that this coprocessor is called from the main processor + col witness bits(1) in_use_clk_0; // 1 at the first clock cycle of the Blake2br operation, 0 otherwise + in_use_clk_0 <== CLK_0 * in_use; lookup_proves(operation_bus_id, [OP_BLAKE2BR, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(STEP_MAIN)], mul: in_use_clk_0); @@ -396,8 +403,8 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION expr sum_1 = a[1] - b[1] - c[1] - d[1]; expr sum = sum_0 + P2_32*sum_1; - sum_0 * (sum_0 + P2_32) * (sum_0 + 2*P2_32) === 0; - sum * (sum + P2_64) * (sum + 2*P2_64) === 0; + air.in_use_clk_g * (sum_0 * (sum_0 + P2_32) * (sum_0 + 2*P2_32)) === 0; + air.in_use_clk_g * (sum * (sum + P2_64) * (sum + 2*P2_64)) === 0; } // Checks a == (b + c) mod 2⁶⁴ @@ -424,8 +431,8 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION expr sum_1 = a[1] - b[1] - c[1]; expr sum = sum_0 + P2_32*sum_1; - sum_0 * (sum_0 + P2_32) === 0; - sum * (sum + P2_64) === 0; + air.in_use_clk_g * (sum_0 * (sum_0 + P2_32)) === 0; + air.in_use_clk_g * (sum * (sum + P2_64)) === 0; } // Checks a == (b <<< n) ^ c @@ -466,8 +473,8 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION } // Check equality - a[0] === packed_low; - a[1] === packed_high; + air.in_use_clk_g * (a[0] - packed_low) === 0; + air.in_use_clk_g * (a[1] - packed_high) === 0; // Notice that these equalities also ensure that a[0], a[1] are range checked } @@ -476,13 +483,26 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION return air.CLK_0 * (mvcol'(pos1) - mvcol'(pos2)); } - function clock_set(const expr mvcol = 1, int start = 0, int end = -1): const expr { + function clock_set(const expr mvcol = 1, int start = 0, int end = -1, const int step = 1, const int skip = 0): const expr { expr result = 0; if (end == -1) { - end = start; + end = start + 1; } - for (int i = start; i < end; i++) { - result += air.CLK[i]; + + int clock_idx = start; + int count = 0; + int in_group = 0; + while (count < end - start) { + result += air.CLK[clock_idx]; + count++; + in_group++; + clock_idx++; + + // After 'step' consecutive clocks, skip 'skip' clocks + if (skip > 0 && in_group == step) { + clock_idx += skip; + in_group = 0; + } } return result * mvcol; } @@ -493,7 +513,7 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION end = start + 1; } for (int i = start; i < end; i++) { - const int iclock = (pos - i) % air.CLOCK_NUM; + const int iclock = (pos - i) % air.CLOCKS; if (offset != 0) { result += air.CLK[i] * (mvcol'(iclock) + offset); } else { diff --git a/precompiles/blake2/src/blake2.rs b/precompiles/blake2/src/blake2.rs new file mode 100644 index 000000000..989a379df --- /dev/null +++ b/precompiles/blake2/src/blake2.rs @@ -0,0 +1,322 @@ +use core::panic; +use std::sync::Arc; + +use fields::PrimeField64; +use rayon::prelude::*; + +use pil_std_lib::Std; +use proofman_common::{AirInstance, FromTrace, ProofmanResult}; +use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; +#[cfg(feature = "packed")] +use zisk_pil::{Blake2TracePacked, Blake2TraceRowPacked}; +#[cfg(not(feature = "packed"))] +use zisk_pil::{Blake2brTrace, Blake2brTraceRow}; + +#[cfg(feature = "packed")] +type Blake2TraceRowType = Blake2TraceRowPacked; +#[cfg(feature = "packed")] +type Blake2TraceType = Blake2TracePacked; + +#[cfg(not(feature = "packed"))] +type Blake2TraceRowType = Blake2brTraceRow; +#[cfg(not(feature = "packed"))] +type Blake2TraceType = Blake2brTrace; + +use super::{ + blake2_constants::{CLOCKS, CLOCKS_PER_G, R1_G, R2_G, R3_G, R4_G, SIGMA}, + Blake2Input, +}; + +/// The `Blake2SM` struct encapsulates the logic of the Blake2 State Machine. +pub struct Blake2SM { + /// Reference to the PIL2 standard library. + pub std: Arc>, + + /// Number of available blake2s in the trace. + pub num_available_blake2s: usize, + + num_non_usable_rows: usize, +} + +impl Blake2SM { + /// Creates a new Blake2 State Machine instance. + /// + /// # Returns + /// A new `Blake2SM` instance. + pub fn new(std: Arc>) -> Arc { + // Compute some useful values + let num_non_usable_rows = Blake2TraceType::::NUM_ROWS % CLOCKS; + let num_available_blake2s = + Blake2TraceType::::NUM_ROWS / CLOCKS - (num_non_usable_rows != 0) as usize; + + Arc::new(Self { std, num_available_blake2s, num_non_usable_rows }) + } + + /// Processes a slice of operation data, updating the trace and multiplicities. + /// + /// # Arguments + /// * `trace` - A mutable reference to the Blake2 trace. + /// * `num_circuits` - The number of circuits to process. + /// * `input` - The operation data to process. + /// * `multiplicity` - A mutable slice to update with multiplicities for the operation. + #[inline(always)] + pub fn process_input(&self, input: &Blake2Input, trace: &mut [Blake2TraceRowType]) { + let step_main = input.step_main; + let addr_main = input.addr_main; + let state_addr = input.state_addr; + let input_addr = input.input_addr; + let index = input.index as u8; + let state = &input.state; + let input = &input.input; + + // Fill the step_addr + trace[0].set_step_addr(step_main); // STEP_MAIN + trace[1].set_step_addr(addr_main as u64); // ADDR_OP + trace[2].set_step_addr(state_addr as u64); // ADDR_STATE + trace[3].set_step_addr(input_addr as u64); // ADDR_INPUT + trace[4].set_step_addr(state_addr as u64); // ADDR_IND_0 + trace[5].set_step_addr(input_addr as u64); // ADDR_IND_1 + + // Set latched columns + let idx_usize = index as usize; + for row in trace.iter_mut().take(CLOCKS) { + // Activate the in_use selector + row.set_in_use(true); + + // Set idx + row.set_idx(index); + + // Set idx_sel + row.set_idx_sel(idx_usize, true); + } + + // Set m columns + let mut m_idx = 0; + for (i, &inp) in input.iter().enumerate() { + trace[m_idx].set_m(0, inp as u32); + trace[m_idx].set_m(1, (inp >> 32) as u32); + m_idx += 1; + if i % CLOCKS_PER_G == (CLOCKS_PER_G - 1) { + m_idx += 1; + } + } + + // Set ms columns + let s = &SIGMA[idx_usize]; + let mut ms: [u64; 16] = [0u64; 16]; + m_idx = 0; + for i in 0..input.len() { + ms[i] = input[s[i]]; + + trace[m_idx].set_ms(0, trace[m_idx].get_m(0)); + trace[m_idx].set_ms(1, trace[m_idx].get_m(1)); + m_idx += 1; + if i % CLOCKS_PER_G == (CLOCKS_PER_G - 1) { + m_idx += 1; + } + } + + // Column mixing + compute_g_and_set_vs(trace, 0, &[state[0], state[4], state[8], state[12]], &[ms[0], ms[1]]); + compute_g_and_set_vs(trace, 1, &[state[1], state[5], state[9], state[13]], &[ms[2], ms[3]]); + compute_g_and_set_vs( + trace, + 2, + &[state[2], state[6], state[10], state[14]], + &[ms[4], ms[5]], + ); + compute_g_and_set_vs( + trace, + 3, + &[state[3], state[7], state[11], state[15]], + &[ms[6], ms[7]], + ); + + // Diagonal mixing + compute_g_and_set_vs( + trace, + 4, + &[state[0], state[5], state[10], state[15]], + &[ms[8], ms[9]], + ); + compute_g_and_set_vs( + trace, + 5, + &[state[1], state[6], state[11], state[12]], + &[ms[10], ms[11]], + ); + compute_g_and_set_vs( + trace, + 6, + &[state[2], state[7], state[8], state[13]], + &[ms[12], ms[13]], + ); + compute_g_and_set_vs( + trace, + 7, + &[state[3], state[4], state[9], state[14]], + &[ms[14], ms[15]], + ); + + fn compute_g_and_set_vs( + trace: &mut [Blake2TraceRowType], + i: usize, + v: &[u64; 4], + m: &[u64; 2], + ) { + // Compute the g function + let (va, vb, vc, vd) = (v[0], v[1], v[2], v[3]); + let (va_i, vb_i, vc_i, vd_i) = compute_half_g(va, vb, vc, vd, m[0], R1_G, R2_G); + let (va_o, vb_o, vc_o, vd_o) = compute_half_g(va_i, vb_i, vc_i, vd_i, m[1], R3_G, R4_G); + + // Set va, vb, vc, vd columns + set_vs(&mut trace[3 * i], va, vb, vc, vd); + set_vs(&mut trace[3 * i + 1], va_i, vb_i, vc_i, vd_i); + set_vs(&mut trace[3 * i + 2], va_o, vb_o, vc_o, vd_o); + } + + fn compute_half_g( + va: u64, + vb: u64, + vc: u64, + vd: u64, + m: u64, + r1: u32, + r2: u32, + ) -> (u64, u64, u64, u64) { + let va = va.wrapping_add(vb).wrapping_add(m); + let vd = (vd ^ va).rotate_right(r1); + let vc = vc.wrapping_add(vd); + let vb = (vb ^ vc).rotate_right(r2); + (va, vb, vc, vd) + } + + fn set_vs( + row: &mut Blake2TraceRowType, + va: u64, + vb: u64, + vc: u64, + vd: u64, + ) { + let low_va = va as u32; + let high_va = (va >> 32) as u32; + row.set_va(0, low_va); + row.set_va(1, high_va); + + let low_vb = vb as u32; + let low_vb = u32_to_le_bits(low_vb); + let high_vb = (vb >> 32) as u32; + let high_vb = u32_to_le_bits(high_vb); + for j in 0..32 { + row.set_vb(0, j, low_vb[j]); + row.set_vb(1, j, high_vb[j]); + } + + let low_vc = vc as u32; + let high_vc = (vc >> 32) as u32; + row.set_vc(0, low_vc); + row.set_vc(1, high_vc); + + let low_vd = vd as u32; + let low_vd = u32_to_le_bits(low_vd); + let high_vd = (vd >> 32) as u32; + let high_vd = u32_to_le_bits(high_vd); + for j in 0..32 { + row.set_vd(0, j, low_vd[j]); + row.set_vd(1, j, high_vd[j]); + } + } + + fn u32_to_le_bits(x: u32) -> [bool; 32] { + let mut bits = [false; 32]; + for i in 0..32 { + if ((x >> i) & 1) != 0 { + bits[i] = true; + } + } + bits + } + } + + /// Computes the witness for a series of inputs and produces an `AirInstance`. + /// + /// # Arguments + /// * `sctx` - The setup context containing the setup data. + /// * `inputs` - A slice of operations to process. + /// + /// # Returns + /// An `AirInstance` containing the computed witness data. + pub fn compute_witness( + &self, + inputs: &[Vec], + trace_buffer: Vec, + ) -> ProofmanResult> { + let mut blake2_trace = Blake2TraceType::new_from_vec_zeroes(trace_buffer)?; + let num_rows = blake2_trace.num_rows(); + let num_available_blake2s = self.num_available_blake2s; + + // Check that we can fit all the blake2s in the trace + let num_inputs = inputs.iter().map(|v| v.len()).sum::(); + let num_rows_filled = num_inputs * CLOCKS; + let num_rows_needed = if num_inputs < num_available_blake2s { + num_inputs * CLOCKS + } else if num_inputs == num_available_blake2s { + num_rows + } else { + panic!( + "Exceeded available Blake2s inputs: requested {}, but only {} are available.", + num_inputs, self.num_available_blake2s + ); + }; + + tracing::debug!( + "··· Creating Blake2 instance [{} / {} rows filled {:.2}%]", + num_rows_needed, + num_rows, + num_rows_needed as f64 / num_rows as f64 * 100.0 + ); + + timer_start_trace!(BLAKE2_TRACE); + + // Split trace into chunks for parallel processing + let mut trace_rows = blake2_trace.buffer.as_mut_slice(); + let mut par_traces = Vec::new(); + let mut inputs_indexes = Vec::new(); + for (i, inputs) in inputs.iter().enumerate() { + for (j, _) in inputs.iter().enumerate() { + let (head, tail) = trace_rows.split_at_mut(CLOCKS); + par_traces.push(head); + inputs_indexes.push((i, j)); + trace_rows = tail; + } + } + + // Fill the trace + par_traces.into_par_iter().enumerate().for_each(|(index, trace)| { + let input_index = inputs_indexes[index]; + let input = &inputs[input_index.0][input_index.1]; + self.process_input(input, trace) + }); + + timer_stop_and_log_trace!(BLAKE2_TRACE); + + timer_start_trace!(BLAKE2_PADDING); + // // Set a = e = w = 0 for the state and input rows + // let zero_row = Blake2TraceRowType::::default(); + + // let padding_start = num_rows_filled; + // let padding_end = num_rows - self.num_non_usable_rows - CLOCKS; + + // if padding_start < padding_end { + // blake2_trace.buffer[padding_start..padding_end] + // .par_iter_mut() + // .for_each(|row| { + // *row = zero_row; + // }); + // } + + timer_stop_and_log_trace!(BLAKE2_PADDING); + + Ok(AirInstance::new_from_trace(FromTrace::new(&mut blake2_trace))) + } +} diff --git a/precompiles/blake2/src/blake2_bus_device.rs b/precompiles/blake2/src/blake2_bus_device.rs new file mode 100644 index 000000000..d4e798375 --- /dev/null +++ b/precompiles/blake2/src/blake2_bus_device.rs @@ -0,0 +1,143 @@ +//! The `Blake2Counter` module defines a counter for tracking blake2-related operations +//! sent over the data bus. It connects to the bus and gathers metrics for specific +//! `ZiskOperationType::Blake2` instructions. + +use std::ops::Add; + +use precompiles_common::MemProcessor; +use zisk_common::STEP; +use zisk_common::{ + BusDevice, BusDeviceMode, BusId, Counter, Metrics, B, OPERATION_BUS_ID, OP_TYPE, +}; +use zisk_core::ZiskOperationType; + +use crate::{generate_blake2_mem_inputs, skip_blake2_mem_inputs}; + +/// The `Blake2Counter` struct represents a counter that monitors and measures +/// blake2-related operations on the data bus. +/// +/// It tracks specific operation types (`ZiskOperationType`) and updates counters for each +/// accepted operation type whenever data is processed on the bus. +pub struct Blake2CounterInputGen { + /// Blake2 counter. + counter: Counter, + + /// Bus device mode (counter or input generator). + mode: BusDeviceMode, +} + +impl Blake2CounterInputGen { + /// Creates a new instance of `Blake2Counter`. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus to which this counter is connected. + /// * `op_type` - A vector of `ZiskOperationType` instructions to monitor. + /// + /// # Returns + /// A new `Blake2Counter` instance. + pub fn new(mode: BusDeviceMode) -> Self { + Self { counter: Counter::default(), mode } + } + + /// Retrieves the count of instructions for a specific `ZiskOperationType`. + /// + /// # Arguments + /// * `op_type` - The operation type to retrieve the count for. + /// + /// # Returns + /// Returns the count of instructions for the specified operation type. + pub fn inst_count(&self, op_type: ZiskOperationType) -> Option { + (op_type == ZiskOperationType::Blake2).then_some(self.counter.inst_count) + } + + /// Processes data received on the bus, updating counters and generating inputs when applicable. + /// + /// # Arguments + /// * `bus_id` - The ID of the bus sending the data. + /// * `data` - The data received from the bus. + /// * `mem_processors` – A queue of mem_processors bus operations used to send derived inputs. + /// + /// # Returns + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data( + &mut self, + bus_id: &BusId, + data: &[u64], + mem_processors: &mut P, + ) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if data[OP_TYPE] as u32 != ZiskOperationType::Blake2 as u32 { + return true; + } + + let step_main = data[STEP]; + let addr_main = data[B] as u32; + + match self.mode { + BusDeviceMode::Counter => { + self.measure(data); + generate_blake2_mem_inputs(addr_main, step_main, data, true, mem_processors); + } + BusDeviceMode::CounterAsm => { + self.measure(data); + } + BusDeviceMode::InputGenerator => { + if skip_blake2_mem_inputs(addr_main, data, mem_processors) { + return true; + } + generate_blake2_mem_inputs(addr_main, step_main, data, false, mem_processors); + } + } + + true + } +} + +impl Metrics for Blake2CounterInputGen { + /// Tracks activity on the connected bus and updates counters for recognized operations. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `_data` - The data received from the bus. + /// + /// # Returns + /// An empty vector, as this implementation does not produce any derived inputs for the bus. + #[inline(always)] + fn measure(&mut self, _data: &[u64]) { + self.counter.update(1); + } + + /// Provides a dynamic reference for downcasting purposes. + /// + /// # Returns + /// A reference to `self` as `dyn std::any::Any`. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl Add for Blake2CounterInputGen { + type Output = Blake2CounterInputGen; + + /// Combines two `Blake2Counter` instances by summing their counters. + /// + /// # Arguments + /// * `self` - The first `Blake2Counter` instance. + /// * `other` - The second `Blake2Counter` instance. + /// + /// # Returns + /// A new `Blake2Counter` with combined counters. + fn add(self, other: Self) -> Blake2CounterInputGen { + Blake2CounterInputGen { counter: &self.counter + &other.counter, mode: self.mode } + } +} + +impl BusDevice for Blake2CounterInputGen { + /// Provides a dynamic reference for downcasting purposes. + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/blake2/src/blake2_constants.rs b/precompiles/blake2/src/blake2_constants.rs new file mode 100644 index 000000000..09418437b --- /dev/null +++ b/precompiles/blake2/src/blake2_constants.rs @@ -0,0 +1,39 @@ +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; + +// Memory layout +pub const PARAMS: usize = 3; +pub const READ_PARAMS: usize = 2; +pub const DIRECT_READ_PARAMS: usize = 1; +pub const DIRECT_READ_PARAM_POS: usize = 0; +pub const WRITE_PARAMS: usize = 1; +pub const RESULT_PARAMS: usize = 0; +pub const PARAM_CHUNKS: usize = 16; +pub const START_READ_PARAMS: usize = OPERATION_PRECOMPILED_BUS_DATA_SIZE + PARAMS; + +// Generic Parameters +pub const CLOCKS_PER_G: usize = 3; +pub const G_FUNCTIONS_COLUMN_MIXING: usize = 4; +pub const G_FUNCTIONS_DIAGONAL_MIXING: usize = 4; +pub const CLOCKS_COLUMN_MIXING: usize = CLOCKS_PER_G * G_FUNCTIONS_COLUMN_MIXING; +pub const CLOCKS_DIAGONAL_MIXING: usize = CLOCKS_PER_G * G_FUNCTIONS_DIAGONAL_MIXING; +pub const CLOCKS: usize = CLOCKS_COLUMN_MIXING + CLOCKS_DIAGONAL_MIXING; + +/// Message word permutation schedule +pub const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +/// Rotation constants for G function +pub const R1_G: u32 = 32; +pub const R2_G: u32 = 24; +pub const R3_G: u32 = 16; +pub const R4_G: u32 = 63; diff --git a/precompiles/blake2/src/blake2_gen_mem_inputs.rs b/precompiles/blake2/src/blake2_gen_mem_inputs.rs new file mode 100644 index 000000000..28651278c --- /dev/null +++ b/precompiles/blake2/src/blake2_gen_mem_inputs.rs @@ -0,0 +1,105 @@ +use precompiles_common::MemBusHelpers; +use precompiles_common::MemProcessor; + +use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; +use zisk_core::blake2br; + +use crate::blake2_constants::{ + DIRECT_READ_PARAMS, DIRECT_READ_PARAM_POS, PARAMS, PARAM_CHUNKS, READ_PARAMS, START_READ_PARAMS, +}; + +#[derive(Debug)] +pub struct Blake2MemInputConfig { + pub indirect_params: usize, + pub rewrite_params: bool, + pub read_params: usize, + pub write_params: usize, + pub chunks_per_param: usize, +} + +pub fn generate_blake2_mem_inputs( + addr_main: u32, + step_main: u64, + data: &[u64], + only_counters: bool, + mem_processors: &mut P, +) { + // data = [op,op_type,a,b,step,index,addr[2],state[16],input[16]] + + // Start by generating the params (direct, indirection write, indirection read) + for iparam in 0..PARAMS { + MemBusHelpers::mem_aligned_load( + addr_main + iparam as u32 * 8, + step_main, + data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + iparam], + mem_processors, + ); + } + + // Generate memory load params + for iparam in 0..READ_PARAMS { + let param_idx = if iparam >= DIRECT_READ_PARAM_POS { iparam + 1 } else { iparam }; + + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_idx] as u32; + for ichunk in 0..PARAM_CHUNKS { + MemBusHelpers::mem_aligned_load( + param_addr + ichunk as u32 * 8, + step_main, + data[START_READ_PARAMS + iparam * PARAM_CHUNKS + ichunk], + mem_processors, + ); + } + } + + let mut write_data = [0u64; PARAM_CHUNKS]; + if !only_counters { + let index = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE]; + let mut state: [u64; 16] = + data[START_READ_PARAMS..START_READ_PARAMS + PARAM_CHUNKS].try_into().unwrap(); + let input: [u64; 16] = data + [START_READ_PARAMS + PARAM_CHUNKS..START_READ_PARAMS + 2 * PARAM_CHUNKS] + .try_into() + .unwrap(); + blake2br(index, &mut state, &input); + write_data.copy_from_slice(&state); + } + + // verify write param + let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + DIRECT_READ_PARAMS] as u32; + for (ichunk, write_data) in write_data.iter().enumerate().take(PARAM_CHUNKS) { + let param_addr = write_addr + ichunk as u32 * 8; + MemBusHelpers::mem_aligned_write(param_addr, step_main, *write_data, mem_processors); + } +} + +pub fn skip_blake2_mem_inputs( + addr_main: u32, + data: &[u64], + mem_processors: &mut P, +) -> bool { + let indirect_params = 2; + let read_params = 3; + let write_params = 1; + let chunks_per_param = [1usize, 16, 16, 16]; + + for iparam in 0..indirect_params { + let addr = addr_main + iparam as u32 * 8; + if !mem_processors.skip_addr(addr) { + return false; + } + } + + for (iparam, &chunks) in chunks_per_param.iter().enumerate().take(read_params + write_params) { + let is_write = iparam >= read_params; + let param_index = if is_write { iparam - read_params } else { iparam }; + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32; + + for ichunk in 0..chunks { + let addr = param_addr + ichunk as u32 * 8; + if !mem_processors.skip_addr(addr) { + return false; + } + } + } + true +} diff --git a/precompiles/blake2/src/blake2_input.rs b/precompiles/blake2/src/blake2_input.rs new file mode 100644 index 000000000..3da43dc67 --- /dev/null +++ b/precompiles/blake2/src/blake2_input.rs @@ -0,0 +1,26 @@ +use zisk_common::OperationBlake2Data; + +#[derive(Debug)] +pub struct Blake2Input { + pub addr_main: u32, + pub step_main: u64, + pub index: u64, + pub state_addr: u32, + pub input_addr: u32, + pub state: [u64; 16], + pub input: [u64; 16], +} + +impl Blake2Input { + pub fn from(values: &OperationBlake2Data) -> Self { + Self { + addr_main: values[3] as u32, + step_main: values[4], + index: values[5], + state_addr: values[6] as u32, + input_addr: values[7] as u32, + state: values[8..24].try_into().unwrap(), + input: values[24..40].try_into().unwrap(), + } + } +} diff --git a/precompiles/blake2/src/blake2_instance.rs b/precompiles/blake2/src/blake2_instance.rs new file mode 100644 index 000000000..1d63d0adc --- /dev/null +++ b/precompiles/blake2/src/blake2_instance.rs @@ -0,0 +1,195 @@ +//! The `Blake2Instance` module defines an instance to perform the witness computation +//! for the Blake2 State Machine. +//! +//! It manages collected inputs and interacts with the `Blake2SM` to compute witnesses for +//! execution plans. + +use crate::{Blake2Input, Blake2SM}; +use fields::PrimeField64; +use proofman_common::{AirInstance, ProofCtx, ProofmanResult, SetupCtx}; +use std::{any::Any, collections::HashMap, sync::Arc}; +use zisk_common::ChunkId; +use zisk_common::{ + BusDevice, BusId, CheckPoint, CollectSkipper, ExtOperationData, Instance, InstanceCtx, + InstanceType, PayloadType, OPERATION_BUS_ID, OP_TYPE, +}; +use zisk_core::ZiskOperationType; +use zisk_pil::Blake2brTrace; + +/// The `Blake2Instance` struct represents an instance for the Blake2 State Machine. +/// +/// It encapsulates the `Blake2SM` and its associated context, and it processes input data +/// to compute witnesses for the Blake2 State Machine. +pub struct Blake2Instance { + /// Blake2 state machine. + blake2_sm: Arc>, + + /// Instance context. + ictx: InstanceCtx, +} + +impl Blake2Instance { + /// Creates a new `Blake2Instance`. + /// + /// # Arguments + /// * `blake2_sm` - An `Arc`-wrapped reference to the Blake2 State Machine. + /// * `ictx` - The `InstanceCtx` associated with this instance, containing the execution plan. + /// * `bus_id` - The bus ID associated with this instance. + /// + /// # Returns + /// A new `Blake2Instance` instance initialized with the provided state machine and + /// context. + pub fn new(blake2_sm: Arc>, ictx: InstanceCtx) -> Self { + Self { blake2_sm, ictx } + } + + pub fn build_blake2_collector(&self, chunk_id: ChunkId) -> Blake2Collector { + assert_eq!( + self.ictx.plan.air_id, + Blake2brTrace::::AIR_ID, + "Blake2Instance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::>().unwrap(); + let (num_ops, collect_skipper) = collect_info[&chunk_id]; + Blake2Collector::new(num_ops, collect_skipper) + } +} + +impl Instance for Blake2Instance { + /// Computes the witness for the blake2 execution plan. + /// + /// This method leverages the `Blake2SM` to generate an `AirInstance` using the collected + /// inputs. + /// + /// # Arguments + /// * `_pctx` - The proof context, unused in this implementation. + /// + /// # Returns + /// An `Option` containing the computed `AirInstance`. + fn compute_witness( + &self, + _pctx: &ProofCtx, + _sctx: &SetupCtx, + collectors: Vec<(usize, Box>)>, + trace_buffer: Vec, + ) -> ProofmanResult>> { + let inputs: Vec<_> = collectors + .into_iter() + .map(|(_, collector)| collector.as_any().downcast::().unwrap().inputs) + .collect(); + + Ok(Some(self.blake2_sm.compute_witness(&inputs, trace_buffer)?)) + } + + /// Retrieves the checkpoint associated with this instance. + /// + /// # Returns + /// A `CheckPoint` object representing the checkpoint of the execution plan. + fn check_point(&self) -> &CheckPoint { + &self.ictx.plan.check_point + } + + /// Retrieves the type of this instance. + /// + /// # Returns + /// An `InstanceType` representing the type of this instance (`InstanceType::Instance`). + fn instance_type(&self) -> InstanceType { + InstanceType::Instance + } + + fn build_inputs_collector(&self, chunk_id: ChunkId) -> Option>> { + assert_eq!( + self.ictx.plan.air_id, + Blake2brTrace::::AIR_ID, + "Blake2Instance: Unsupported air_id: {:?}", + self.ictx.plan.air_id + ); + + let meta = self.ictx.plan.meta.as_ref().unwrap(); + let collect_info = meta.downcast_ref::>().unwrap(); + let (num_ops, collect_skipper) = collect_info[&chunk_id]; + Some(Box::new(Blake2Collector::new(num_ops, collect_skipper))) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +pub struct Blake2Collector { + /// Collected inputs for witness computation. + inputs: Vec, + + /// The number of operations to collect. + num_operations: u64, + + /// Helper to skip instructions based on the plan's configuration. + collect_skipper: CollectSkipper, +} + +impl Blake2Collector { + /// Creates a new `Blake2Collector`. + /// + /// # Arguments + /// + /// * `bus_id` - The connected bus ID. + /// * `num_operations` - The number of operations to collect. + /// * `collect_skipper` - The helper to skip instructions based on the plan's configuration. + /// + /// # Returns + /// A new `ArithInstanceCollector` instance initialized with the provided parameters. + pub fn new(num_operations: u64, collect_skipper: CollectSkipper) -> Self { + Self { + inputs: Vec::with_capacity(num_operations as usize), + num_operations, + collect_skipper, + } + } + + /// Processes data received on the bus, collecting the inputs necessary for witness computation. + /// + /// # Arguments + /// * `_bus_id` - The ID of the bus (unused in this implementation). + /// * `data` - The data received from the bus. + /// * `pending` – A queue of pending bus operations used to send derived inputs. + /// + /// # Returns + /// A tuple where: + /// A boolean indicating whether the program should continue execution or terminate. + /// Returns `true` to continue execution, `false` to stop. + #[inline(always)] + pub fn process_data(&mut self, bus_id: &BusId, data: &[PayloadType]) -> bool { + debug_assert!(*bus_id == OPERATION_BUS_ID); + + if self.inputs.len() == self.num_operations as usize { + return false; + } + + if data[OP_TYPE] as u32 != ZiskOperationType::Blake2 as u32 { + return true; + } + + if self.collect_skipper.should_skip() { + return true; + } + + let data: ExtOperationData = + data.try_into().expect("Regular Metrics: Failed to convert data"); + if let ExtOperationData::OperationBlake2Data(data) = data { + self.inputs.push(Blake2Input::from(&data)); + } else { + panic!("Expected ExtOperationData::OperationData"); + } + + self.inputs.len() < self.num_operations as usize + } +} + +impl BusDevice for Blake2Collector { + fn as_any(self: Box) -> Box { + self + } +} diff --git a/precompiles/blake2/src/blake2_manager.rs b/precompiles/blake2/src/blake2_manager.rs new file mode 100644 index 000000000..c67863bce --- /dev/null +++ b/precompiles/blake2/src/blake2_manager.rs @@ -0,0 +1,80 @@ +use std::sync::Arc; + +use fields::PrimeField64; +use pil_std_lib::Std; +use zisk_common::{BusDeviceMode, ComponentBuilder, Instance, InstanceCtx, InstanceInfo, Planner}; +use zisk_core::ZiskOperationType; +use zisk_pil::Blake2brTrace; + +use crate::{Blake2CounterInputGen, Blake2Instance, Blake2Planner, Blake2SM}; + +/// The `Blake2Manager` struct represents the Blake2 manager, +/// which is responsible for managing the Blake2 state machine and its table state machine. +#[allow(dead_code)] +pub struct Blake2Manager { + /// Blake2 state machine + blake2_sm: Arc>, +} + +impl Blake2Manager { + /// Creates a new instance of `Blake2Manager`. + /// + /// # Returns + /// An `Arc`-wrapped instance of `Blake2Manager`. + pub fn new(std: Arc>) -> Arc { + let blake2_sm = Blake2SM::new(std); + + Arc::new(Self { blake2_sm }) + } + + pub fn build_blake2_counter(&self, asm_execution: bool) -> Blake2CounterInputGen { + match asm_execution { + true => Blake2CounterInputGen::new(BusDeviceMode::CounterAsm), + false => Blake2CounterInputGen::new(BusDeviceMode::Counter), + } + } + + pub fn build_blake2_input_generator(&self) -> Blake2CounterInputGen { + Blake2CounterInputGen::new(BusDeviceMode::InputGenerator) + } +} + +impl ComponentBuilder for Blake2Manager { + /// Builds a planner to plan blake2-related instances. + /// + /// # Returns + /// A boxed implementation of `RegularPlanner`. + fn build_planner(&self) -> Box { + // Get the number of blake2s that a single blake2 instance can handle + let num_available_blake2s = self.blake2_sm.num_available_blake2s; + + Box::new(Blake2Planner::new().add_instance(InstanceInfo::new( + Blake2brTrace::::AIRGROUP_ID, + Blake2brTrace::::AIR_ID, + num_available_blake2s, + ZiskOperationType::Blake2, + ))) + } + + /// Builds an inputs data collector for blake2 operations. + /// + /// # Arguments + /// * `ictx` - The context of the instance, containing the plan and its associated + /// configurations. + /// + /// # Returns + /// A boxed implementation of `BusDeviceInstance` specific to the requested `air_id` instance. + /// + /// # Panics + /// Panics if the provided `air_id` is not supported. + fn build_instance(&self, ictx: InstanceCtx) -> Box> { + match ictx.plan.air_id { + id if id == Blake2brTrace::::AIR_ID => { + Box::new(Blake2Instance::new(self.blake2_sm.clone(), ictx)) + } + _ => { + panic!("Blake2Builder::get_instance() Unsupported air_id: {:?}", ictx.plan.air_id) + } + } + } +} diff --git a/precompiles/blake2/src/blake2_planner.rs b/precompiles/blake2/src/blake2_planner.rs new file mode 100644 index 000000000..e4cacaa82 --- /dev/null +++ b/precompiles/blake2/src/blake2_planner.rs @@ -0,0 +1,136 @@ +//! The `Blake2Planner` module defines a planner for generating execution plans specific to +//! arithmetic operations. +//! +//! It organizes execution plans for both regular instances and table instances, +//! leveraging arithmetic operation counts and metadata to construct detailed plans. + +use std::any::Any; + +use crate::Blake2CounterInputGen; + +use zisk_common::{ + plan, BusDeviceMetrics, CheckPoint, ChunkId, InstCount, InstanceInfo, InstanceType, Metrics, + Plan, Planner, TableInfo, +}; + +/// The `Blake2Planner` struct organizes execution plans for arithmetic instances and tables. +/// +/// It allows adding metadata about instances and tables and generates plans +/// based on the provided counters. +#[derive(Default)] +pub struct Blake2Planner { + /// Arithmetic instances info to be planned. + instances_info: Vec, + + /// Arithmetic table instances info to be planned. + tables_info: Vec, +} + +impl Blake2Planner { + /// Creates a new `Blake2Planner`. + /// + /// # Returns + /// A new `Blake2Planner` instance with no preconfigured instances or tables. + pub fn new() -> Self { + Self { instances_info: Vec::new(), tables_info: Vec::new() } + } + + /// Adds an arithmetic instance to the planner. + /// + /// # Arguments + /// * `instance_info` - The `InstanceInfo` describing the arithmetic instance to be added. + /// + /// # Returns + /// The updated `Blake2Planner` instance. + pub fn add_instance(mut self, instance_info: InstanceInfo) -> Self { + self.instances_info.push(instance_info); + self + } + + /// Adds an arithmetic table instance to the planner. + /// + /// # Arguments + /// * `table_info` - The `TableInfo` describing the arithmetic table instance to be added. + /// + /// # Returns + /// The updated `Blake2Planner` instance. + pub fn add_table_instance(mut self, table_info: TableInfo) -> Self { + self.tables_info.push(table_info); + self + } +} + +impl Planner for Blake2Planner { + /// Generates execution plans for arithmetic instances and tables. + /// + /// # Arguments + /// * `counters` - A vector of counters, each associated with a `ChunkId` and `ArithCounter` + /// metrics data. + /// + /// # Returns + /// A vector of `Plan` instances representing execution configurations for the instances and + /// tables. + /// + /// # Panics + /// Panics if any counter cannot be downcasted to an `ArithCounter`. + fn plan(&self, counters: Vec<(ChunkId, Box)>) -> Vec { + // Prepare counts + let mut count: Vec> = Vec::with_capacity(self.instances_info.len()); + + for _ in 0..self.instances_info.len() { + count.push(Vec::new()); + } + + counters.iter().for_each(|(chunk_id, counter)| { + let reg_counter = + Metrics::as_any(&**counter).downcast_ref::().unwrap(); + + // Iterate over `instances_info` and add `InstCount` objects to the correct vector + for (index, instance_info) in self.instances_info.iter().enumerate() { + let inst_count = InstCount::new( + *chunk_id, + reg_counter.inst_count(instance_info.op_type).unwrap(), + ); + + // Add the `InstCount` to the corresponding inner vector + count[index].push(inst_count); + } + }); + + let mut plan_result = Vec::new(); + + for (idx, instance) in self.instances_info.iter().enumerate() { + let plan: Vec<_> = plan(&count[idx], instance.num_ops as u64) + .into_iter() + .map(|(check_point, collect_info)| { + let converted: Box = Box::new(collect_info); + Plan::new( + instance.airgroup_id, + instance.air_id, + None, + InstanceType::Instance, + check_point, + Some(converted), + ) + }) + .collect(); + + plan_result.extend(plan); + } + + if !plan_result.is_empty() { + for table_instance in self.tables_info.iter() { + plan_result.push(Plan::new( + table_instance.airgroup_id, + table_instance.air_id, + None, + InstanceType::Table, + CheckPoint::None, + None, + )); + } + } + + plan_result + } +} diff --git a/precompiles/blake2/src/lib.rs b/precompiles/blake2/src/lib.rs new file mode 100644 index 000000000..5b2c9ccb6 --- /dev/null +++ b/precompiles/blake2/src/lib.rs @@ -0,0 +1,17 @@ +mod blake2; +mod blake2_bus_device; +mod blake2_constants; +mod blake2_gen_mem_inputs; +mod blake2_input; +mod blake2_instance; +mod blake2_manager; +mod blake2_planner; + +pub use blake2::*; +pub use blake2_bus_device::*; +pub use blake2_constants::*; +pub use blake2_gen_mem_inputs::*; +pub use blake2_input::*; +pub use blake2_instance::*; +pub use blake2_manager::*; +pub use blake2_planner::*; diff --git a/precompiles/helpers/src/blake2/blake2b/mod.rs b/precompiles/helpers/src/blake2/blake2b/mod.rs index 9877e70e0..65eaafa26 100644 --- a/precompiles/helpers/src/blake2/blake2b/mod.rs +++ b/precompiles/helpers/src/blake2/blake2b/mod.rs @@ -1,8 +1,6 @@ mod round; -mod utils; -use round::blake2b_round; -use utils::*; +pub use round::blake2b_round; /// BLAKE2b initialization vectors const IV: [u64; 8] = [ @@ -16,11 +14,7 @@ const IV: [u64; 8] = [ 0x5BE0CD19137E2179, ]; -/// BLAKE2b state representation as 16 x 64-bit words -/// v[0..15] each containing 64 bits -type Blake2StateBits = [[u64; 64]; 16]; - -/// BLAKE2b compression function F +/// BLAKE2b compression function /// /// # Arguments /// * `rounds` - Number of rounds (typically 12 for BLAKE2b) @@ -28,68 +22,27 @@ type Blake2StateBits = [[u64; 64]; 16]; /// * `message` - The message block m (16 x 64-bit words as bits) /// * `t` - Offset counters (2 x 64-bit words) /// * `f` - Final block flag -pub fn blake2b(rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { - // Initialize working vector v - let mut v: Blake2StateBits = [[0u64; 64]; 16]; - - // Convert inputs to bit representation - let mut h_bits: [[u64; 64]; 8] = [[0u64; 64]; 8]; - for i in 0..8 { - h_bits[i] = bits_from_u64(h[i]); - } - - let mut m_bits: [[u64; 64]; 16] = [[0u64; 64]; 16]; - for i in 0..16 { - m_bits[i] = bits_from_u64(m[i]); - } - - // Convert iv to bits - let mut iv_bits: [[u64; 64]; 8] = [[0u64; 64]; 8]; - for i in 0..8 { - iv_bits[i] = bits_from_u64(IV[i]); - } - - // v[0..7] = h[0..7] - v[0..8].copy_from_slice(&h_bits); +pub fn blake2b_compress(rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { + let mut v = [0u64; 16]; - // v[8..15] = IV[0..7] - v[8..16].copy_from_slice(&iv_bits); - - // v[12] ^= t[0] (low word of offset) - #[allow(clippy::needless_range_loop)] - for z in 0..64 { - v[12][z] ^= (t[0] >> z) & 1; - } + v[..8].copy_from_slice(h); + v[8..16].copy_from_slice(&IV); - // v[13] ^= t[1] (high word of offset) - #[allow(clippy::needless_range_loop)] - for z in 0..64 { - v[13][z] ^= (t[1] >> z) & 1; - } + v[12] ^= t[0]; + v[13] ^= t[1]; - // v[14] ^= 0xFFFFFFFFFFFFFFFF if f is true (invert all bits) if f { - #[allow(clippy::needless_range_loop)] - for z in 0..64 { - v[14][z] ^= 1; - } + v[14] = !v[14]; } - // Perform rounds for r in 0..rounds { - blake2b_round(&mut v, &m_bits, r); - } - - // Finalize: h[i] = h[i] ^ v[i] ^ v[i+8] - for i in 0..8 { - for z in 0..64 { - h_bits[i][z] ^= v[i][z] ^ v[i + 8][z]; - } + println!("Blake2b_round with v = {:x?}, m = {:x?}", v, m); + blake2b_round(&mut v, m, r); + panic!("Blake2b_round with v = {:x?}, m = {:x?}", v, m); } - // Convert back to u64 for i in 0..8 { - h[i] = u64_from_bits(&h_bits[i]); + h[i] ^= v[i] ^ v[i + 8]; } } @@ -151,7 +104,7 @@ mod tests { let f = true; - blake2b(rounds, &mut h, &m, &t, f); + blake2b_compress(rounds, &mut h, &m, &t, f); // Expected output (8 × u64, little-endian) let expected = [ @@ -226,7 +179,7 @@ mod tests { let f = true; - blake2b(rounds, &mut h, &m, &t, f); + blake2b_compress(rounds, &mut h, &m, &t, f); let expected: [u64; 8] = [ 0xba80a53f981c4d0du64.swap_bytes(), @@ -300,7 +253,7 @@ mod tests { let f = false; - blake2b(rounds, &mut h, &m, &t, f); + blake2b_compress(rounds, &mut h, &m, &t, f); let expected: [u64; 8] = [ 0x75ab69d3190a562cu64.swap_bytes(), @@ -374,7 +327,7 @@ mod tests { let f = true; - blake2b(rounds, &mut h, &m, &t, f); + blake2b_compress(rounds, &mut h, &m, &t, f); let expected: [u64; 8] = [ 0xb63a380cb2897d52u64.swap_bytes(), diff --git a/precompiles/helpers/src/blake2/blake2b/round.rs b/precompiles/helpers/src/blake2/blake2b/round.rs index 26c028b13..66da64619 100644 --- a/precompiles/helpers/src/blake2/blake2b/round.rs +++ b/precompiles/helpers/src/blake2/blake2b/round.rs @@ -1,7 +1,4 @@ -use super::Blake2StateBits; - /// Message word permutation schedule -/// 10 different permutations, cycling for rounds >= 10 const SIGMA: [[usize; 16]; 10] = [ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], @@ -16,27 +13,27 @@ const SIGMA: [[usize; 16]; 10] = [ ]; /// Rotation constants for G function -const R1: usize = 32; -const R2: usize = 24; -const R3: usize = 16; -const R4: usize = 63; +const R1: u32 = 32; +const R2: u32 = 24; +const R3: u32 = 16; +const R4: u32 = 63; /// BLAKE2b round function -pub(crate) fn blake2b_round(v: &mut Blake2StateBits, m: &[[u64; 64]; 16], round: u32) { +pub fn blake2b_round(v: &mut [u64; 16], m: &[u64; 16], round: u32) { // Message word selection permutation for this round let s = &SIGMA[(round % 10) as usize]; // Column step - g(v, 0, 4, 8, 12, &m[s[0]], &m[s[1]]); - g(v, 1, 5, 9, 13, &m[s[2]], &m[s[3]]); - g(v, 2, 6, 10, 14, &m[s[4]], &m[s[5]]); - g(v, 3, 7, 11, 15, &m[s[6]], &m[s[7]]); + g(v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + g(v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + g(v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + g(v, 3, 7, 11, 15, m[s[6]], m[s[7]]); // Diagonal step - g(v, 0, 5, 10, 15, &m[s[8]], &m[s[9]]); - g(v, 1, 6, 11, 12, &m[s[10]], &m[s[11]]); - g(v, 2, 7, 8, 13, &m[s[12]], &m[s[13]]); - g(v, 3, 4, 9, 14, &m[s[14]], &m[s[15]]); + g(v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + g(v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + g(v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + g(v, 3, 4, 9, 14, m[s[14]], m[s[15]]); } /// G mixing function @@ -44,75 +41,24 @@ pub(crate) fn blake2b_round(v: &mut Blake2StateBits, m: &[[u64; 64]; 16], round: /// The G function mixes two input words `x` and `y` from the message block into the state. /// It operates on 4 state words: v[a], v[b], v[c], v[d] #[allow(clippy::too_many_arguments)] -fn g( - v: &mut Blake2StateBits, - a: usize, - b: usize, - c: usize, - d: usize, - x: &[u64; 64], - y: &[u64; 64], -) { +fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { let mut va = v[a]; let mut vb = v[b]; let mut vc = v[c]; let mut vd = v[d]; - // v[a] := (v[a] + v[b] + x) mod 2^64 - add64_bits(&mut va, &vb); - add64_bits(&mut va, x); - - // v[d] := (v[d] ^ v[a]) >>> R1 - xor64_bits(&mut vd, &va); - rotr64_bits(&mut vd, R1); - - // v[c] := (v[c] + v[d]) mod 2^64 - add64_bits(&mut vc, &vd); - - // v[b] := (v[b] ^ v[c]) >>> R2 - xor64_bits(&mut vb, &vc); - rotr64_bits(&mut vb, R2); - - // v[a] := (v[a] + v[b] + y) mod 2^64 - add64_bits(&mut va, &vb); - add64_bits(&mut va, y); - - // v[d] := (v[d] ^ v[a]) >>> R3 - xor64_bits(&mut vd, &va); - rotr64_bits(&mut vd, R3); - - // v[c] := (v[c] + v[d]) mod 2^64 - add64_bits(&mut vc, &vd); + va = va.wrapping_add(vb).wrapping_add(x); + vd = (vd ^ va).rotate_right(R1); + vc = vc.wrapping_add(vd); + vb = (vb ^ vc).rotate_right(R2); - // v[b] := (v[b] ^ v[c]) >>> R4 - xor64_bits(&mut vb, &vc); - rotr64_bits(&mut vb, R4); + va = va.wrapping_add(vb).wrapping_add(y); + vd = (vd ^ va).rotate_right(R3); + vc = vc.wrapping_add(vd); + vb = (vb ^ vc).rotate_right(R4); v[a] = va; v[b] = vb; v[c] = vc; v[d] = vd; } - -fn add64_bits(a: &mut [u64; 64], b: &[u64; 64]) { - let mut carry = 0u64; - for z in 0..64 { - let sum = a[z] + b[z] + carry; - a[z] = sum % 2; - carry = sum / 2; - } -} - -fn xor64_bits(a: &mut [u64; 64], b: &[u64; 64]) { - for z in 0..64 { - a[z] ^= b[z]; - } -} - -fn rotr64_bits(a: &mut [u64; 64], n: usize) { - let mut temp = [0u64; 64]; - for z in 0..64 { - temp[z] = a[(z + n) % 64]; - } - *a = temp; -} diff --git a/precompiles/helpers/src/blake2/blake2b/utils.rs b/precompiles/helpers/src/blake2/blake2b/utils.rs deleted file mode 100644 index 8ffcc24d1..000000000 --- a/precompiles/helpers/src/blake2/blake2b/utils.rs +++ /dev/null @@ -1,19 +0,0 @@ -pub const fn bits_from_u64(value: u64) -> [u64; 64] { - let mut bits = [0u64; 64]; - let mut i = 0; - while i < 64 { - bits[i] = (value >> i) & 1; - i += 1; - } - bits -} - -pub fn u64_from_bits(bits: &[u64; 64]) -> u64 { - let mut value = 0u64; - for (i, &bit) in bits.iter().enumerate() { - if bit == 1 { - value |= 1u64 << i; - } - } - value -} diff --git a/precompiles/helpers/src/lib.rs b/precompiles/helpers/src/lib.rs index 1e8c590a3..b747221ec 100644 --- a/precompiles/helpers/src/lib.rs +++ b/precompiles/helpers/src/lib.rs @@ -9,7 +9,7 @@ mod keccak; pub use arith_eq::*; pub use arith_eq_384::*; pub use big_int::*; -pub use blake2::blake2b; +pub use blake2::{blake2b_compress, blake2b_round}; pub use common::*; pub use dma::*; pub use keccak::{ diff --git a/ziskos/entrypoint/src/syscalls/blake2br.rs b/ziskos/entrypoint/src/syscalls/blake2br.rs new file mode 100644 index 000000000..c8f0ab6cf --- /dev/null +++ b/ziskos/entrypoint/src/syscalls/blake2br.rs @@ -0,0 +1,31 @@ +//! Blake2br system call interception + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +use core::arch::asm; + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +use crate::ziskos_syscall; + +#[derive(Debug)] +#[repr(C)] +pub struct SyscallBlake2bRoundParams<'a> { + pub index: u64, // a number in [0,10) + pub state: &'a mut [u64; 16], + pub input: &'a [u64; 16], +} + +#[allow(unused_variables)] +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_syscall_blake2b_round")] +pub extern "C" fn syscall_blake2b_round( + params: &mut SyscallBlake2bRoundParams, + #[cfg(feature = "hints")] hints: &mut Vec, +) { + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + ziskos_syscall!(0x817, params); + + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + { + unimplemented!(); + } +} diff --git a/ziskos/entrypoint/src/syscalls/mod.rs b/ziskos/entrypoint/src/syscalls/mod.rs index 8e3b54a4a..095801e66 100644 --- a/ziskos/entrypoint/src/syscalls/mod.rs +++ b/ziskos/entrypoint/src/syscalls/mod.rs @@ -2,6 +2,7 @@ mod add256; mod arith256; mod arith256_mod; mod arith384_mod; +mod blake2br; mod bls12_381_complex_add; mod bls12_381_complex_mul; mod bls12_381_complex_sub; @@ -27,6 +28,7 @@ pub use add256::*; pub use arith256::*; pub use arith256_mod::*; pub use arith384_mod::*; +pub use blake2br::*; pub use bls12_381_complex_add::*; pub use bls12_381_complex_mul::*; pub use bls12_381_complex_sub::*; diff --git a/ziskos/entrypoint/src/syscalls/syscall.rs b/ziskos/entrypoint/src/syscalls/syscall.rs index fbead90e8..94e9ef9ed 100644 --- a/ziskos/entrypoint/src/syscalls/syscall.rs +++ b/ziskos/entrypoint/src/syscalls/syscall.rs @@ -1,5 +1,7 @@ // Syscall 0x800 - 0x84F (80 syscalls) +// Important: Syscalls should be contiguous and in the same order as in riscv2zisk_context.rs + pub const SYSCALL_KECCAKF_ID: u16 = 0x800; pub const SYSCALL_ARITH256_ID: u16 = 0x801; pub const SYSCALL_ARITH256_MOD_ID: u16 = 0x802; @@ -23,3 +25,4 @@ pub const SYSCALL_DMA_MEMCPY_ID: u16 = 0x813; pub const SYSCALL_DMA_MEMCMP_ID: u16 = 0x814; pub const SYSCALL_SECP256R1_ADD_ID: u16 = 0x815; pub const SYSCALL_SECP256R1_DBL_ID: u16 = 0x816; +pub const SYSCALL_BLAKE2B_ROUND_ID: u16 = 0x817; diff --git a/ziskos/entrypoint/src/zisklib/lib/blake2b.rs b/ziskos/entrypoint/src/zisklib/lib/blake2b.rs new file mode 100644 index 000000000..c3a660376 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/lib/blake2b.rs @@ -0,0 +1,100 @@ +use crate::syscalls::{syscall_blake2b_round, SyscallBlake2bRoundParams}; + +/// BLAKE2b initialization vectors +const IV: [u64; 8] = [ + 0x6A09E667F3BCC908, + 0xBB67AE8584CAA73B, + 0x3C6EF372FE94F82B, + 0xA54FF53A5F1D36F1, + 0x510E527FADE682D1, + 0x9B05688C2B3E6C1F, + 0x1F83D9ABFB41BD6B, + 0x5BE0CD19137E2179, +]; + +fn blake2b_compress( + rounds: u32, + h: &mut [u64; 8], + m: &[u64; 16], + t: &[u64; 2], + f: bool, + #[cfg(feature = "hints")] hints: &mut Vec, +) { + let mut v = [0u64; 16]; + + v[..8].copy_from_slice(h); + v[8..16].copy_from_slice(&IV); + + v[12] ^= t[0]; + v[13] ^= t[1]; + + if f { + v[14] = !v[14]; + } + + for r in 0..rounds { + blake2b_round( + &mut v, + m, + r, + #[cfg(feature = "hints")] + hints, + ); + } + + for i in 0..8 { + h[i] ^= v[i] ^ v[i + 8]; + } +} + +fn blake2b_round( + v: &mut [u64; 16], + m: &[u64; 16], + round: u32, + #[cfg(feature = "hints")] hints: &mut Vec, +) { + let mut params = SyscallBlake2bRoundParams { index: (round % 10) as u64, state: v, input: m }; + syscall_blake2b_round( + &mut params, + #[cfg(feature = "hints")] + hints, + ); +} + +/// C-compatible wrapper for full Blake2b compression function +/// +/// # Safety +/// - `input` must point to at least `input_len` bytes +/// - `output` must point to a writable buffer of at least 32 bytes +#[cfg_attr(not(feature = "hints"), no_mangle)] +#[cfg_attr(feature = "hints", export_name = "hints_blake2b_compress_c")] +pub unsafe extern "C" fn blake2b_compress_c( + rounds: u32, + state: *mut u64, + message: *const u64, + offset: *const u64, + final_block: u8, + #[cfg(feature = "hints")] hints: &mut Vec, +) { + // Parse state + let state_slice = core::slice::from_raw_parts_mut(state, 8); + let state_array: &mut [u64; 8] = &mut *(state_slice.as_mut_ptr() as *mut [u64; 8]); + + // Parse message + let message_slice = core::slice::from_raw_parts(message, 16); + let message_array: &[u64; 16] = &*(message_slice.as_ptr() as *const [u64; 16]); + + // Parse offset + let offset_slice = core::slice::from_raw_parts(offset, 2); + let offset_array: &[u64; 2] = &*(offset_slice.as_ptr() as *const [u64; 2]); + + blake2b_compress( + rounds, + state_array, + message_array, + offset_array, + final_block != 0, + #[cfg(feature = "hints")] + hints, + ); +} diff --git a/ziskos/entrypoint/src/zisklib/lib/mod.rs b/ziskos/entrypoint/src/zisklib/lib/mod.rs index 9a2812661..ed0a23168 100644 --- a/ziskos/entrypoint/src/zisklib/lib/mod.rs +++ b/ziskos/entrypoint/src/zisklib/lib/mod.rs @@ -1,4 +1,5 @@ mod array_lib; +mod blake2b; mod bls12_381; mod bn254; mod constants; @@ -10,6 +11,7 @@ mod utils; // For public consumption pub use array_lib::*; +pub use blake2b::*; pub use bls12_381::*; pub use bn254::*; pub use constants::*; From 2858a5e544829e74018c591e1538f4ec9d00141e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:06:56 +0000 Subject: [PATCH 642/782] Blake2 fully working --- core/src/zisk_ops.rs | 2 +- pil/src/pil_helpers/traces.rs | 45 +- pil/zisk.pil | 2 +- precompiles/blake2/pil/blake2br.pil | 577 ++++++++++++------ precompiles/blake2/src/blake2.rs | 190 ++++-- .../blake2/src/blake2_gen_mem_inputs.rs | 5 +- precompiles/helpers/src/blake2/blake2b/mod.rs | 2 - 7 files changed, 559 insertions(+), 264 deletions(-) diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 1719902e7..17458b760 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -362,7 +362,7 @@ const FCALL_COST: u64 = INTERNAL_COST; const ARITH_EQ_384_COST: u64 = 79 * 24; const ADD256_COST: u64 = 104; const DMA_COST: u64 = 42; -const BLAKE2_COST: u64 = 1; //TODO: TBD +const BLAKE2_COST: u64 = 24 * 205; const DMA_64_ALIGNED_COST: u64 = 40; const DMA_UNALIGNED_COST: u64 = 42; diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index c3aa3d09a..8e2e24f41 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "1e049cc2bf07c7cb06f032528ca43e3363ca78452f0ed5e01ac03ad760c04a34"; +pub const PILOUT_HASH: &str = "1a494665d4c813a58cb06027e1300a2636004d29ed4c40deda93285f65c128f6"; pub const MERKLE_TREE_ARITY: u64 = 4; @@ -72,11 +72,13 @@ pub const SHA_256_F_AIR_IDS: &[usize] = &[21]; pub const POSEIDON_2_AIR_IDS: &[usize] = &[22]; -pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[23]; +pub const BLAKE_2_BR_AIR_IDS: &[usize] = &[23]; -pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[24]; +pub const SPECIFIED_RANGES_AIR_IDS: &[usize] = &[24]; -pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[25]; +pub const VIRTUAL_TABLE_0_AIR_IDS: &[usize] = &[25]; + +pub const VIRTUAL_TABLE_1_AIR_IDS: &[usize] = &[26]; //PUBLICS @@ -439,37 +441,51 @@ pub type Poseidon2Trace = GenericTrace, 131072, 0, 22>; pub type Poseidon2TracePacked = GenericTrace, 131072, 0, 22>; +trace_row!(Blake2brFixedRow { + CLK_0: F, MSG_IDX: F, __L1__: F, +}); +pub type Blake2brFixed = GenericTrace, 262144, 0, 23>; + +trace_row!(Blake2brTraceRow { + in_use:bit, round_idx:ubit(4), round_idx_sel:[bit; 10], sigma_idx:ubit(4), m_limbs:[[u16; 2]; 2], ms:[u32; 2], perm_active:bit, g_active:bit, va_limbs:[[u16; 2]; 2], vc_limbs:[[u16; 2]; 2], vb:[[bit; 32]; 2], vd:[[bit; 32]; 2], step_addr:ubit(40), in_use_clk_0:bit, +}); +pub type Blake2brTrace = GenericTrace, 262144, 0, 23>; + + +pub type Blake2brTracePacked = GenericTrace, 262144, 0, 23>; + + trace_row!(SpecifiedRangesFixedRow { OPID: [F; 29], VALS: [F; 29], __L1__: F, }); -pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 23>; +pub type SpecifiedRangesFixed = GenericTrace, 1048576, 0, 24>; trace_row!(SpecifiedRangesTraceRow { mul:[F; 29], }); -pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 23>; +pub type SpecifiedRangesTrace = GenericTrace, 1048576, 0, 24>; trace_row!(VirtualTable0FixedRow { UID: [F; 8], column: [F; 43], __L1__: F, }); -pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 24>; +pub type VirtualTable0Fixed = GenericTrace, 2097152, 0, 25>; trace_row!(VirtualTable0TraceRow { multiplicity:[F; 8], }); -pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 24>; +pub type VirtualTable0Trace = GenericTrace, 2097152, 0, 25>; trace_row!(VirtualTable1FixedRow { UID: [F; 8], column: [F; 72], __L1__: F, }); -pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 25>; +pub type VirtualTable1Fixed = GenericTrace, 2097152, 0, 26>; trace_row!(VirtualTable1TraceRow { multiplicity:[F; 8], }); -pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 25>; +pub type VirtualTable1Trace = GenericTrace, 2097152, 0, 26>; trace_row!(RomRomTraceRow { @@ -618,6 +634,10 @@ values!(Poseidon2AirGroupValues { gsum_result: FieldExtension, }); +values!(Blake2brAirGroupValues { + gsum_result: FieldExtension, +}); + values!(SpecifiedRangesAirGroupValues { gsum_result: FieldExtension, }); @@ -741,4 +761,9 @@ pub const PACKED_INFO: &[(usize, usize, PackedInfoConst)] = &[ num_packed_words: 17, unpack_info: &[1, 1, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40], }), + (0, 23, PackedInfoConst { + is_packed: true, + num_packed_words: 7, + unpack_info: &[1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 16, 16, 16, 16, 32, 32, 1, 1, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 40, 1], + }), ]; \ No newline at end of file diff --git a/pil/zisk.pil b/pil/zisk.pil index 4b607e3f8..14ba1ec7a 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -118,7 +118,7 @@ airgroup Zisk { Poseidon2(N: 2**17); - Blake2br(N: 2**20); + Blake2br(N: 2**18); // Public Inputs for (int i = 0; i < PUBLIC_INPUTS_64_BITS; i++) { diff --git a/precompiles/blake2/pil/blake2br.pil b/precompiles/blake2/pil/blake2br.pil index d81af9506..ce8c7f492 100644 --- a/precompiles/blake2/pil/blake2br.pil +++ b/precompiles/blake2/pil/blake2br.pil @@ -1,6 +1,7 @@ require "std_constants.pil" require "std_lookup.pil" require "std_permutation.pil" +require "std_range_check.pil" require "operations.pil" require "opids.pil" @@ -9,9 +10,7 @@ require "opids.pil" // We use little-endian representation -// TODO: Add missing range checks! - -airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION_BUS_ID) { +airtemplate Blake2br(const int N = 2**20, const int operation_bus_id = OPERATION_BUS_ID) { /* ═══════════════════════════════════════════════════════════════════════════ BLAKE2B ROUND FUNCTION SPECIFICATION @@ -81,8 +80,21 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION */ const int CLOCKS_PER_G = 3; - const int G_FUNCTIONS_PER_ROUND = 8; - const int CLOCKS = CLOCKS_PER_G * G_FUNCTIONS_PER_ROUND; + const int NUM_G_FUNCTIONS = 8; + const int CLOCKS = CLOCKS_PER_G * NUM_G_FUNCTIONS; + + /// Message word permutation schedule + const int SIGMA[10][16]; + SIGMA[0] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + SIGMA[1] = [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]; + SIGMA[2] = [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4]; + SIGMA[3] = [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8]; + SIGMA[4] = [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13]; + SIGMA[5] = [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9]; + SIGMA[6] = [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11]; + SIGMA[7] = [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10]; + SIGMA[8] = [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5]; + SIGMA[9] = [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]; // Ensure that the Blake2br fits if (N < 2*CLOCKS) { @@ -99,115 +111,138 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION } println(`Blake2br capacity: ${NUM_BLAKE2BR} rounds (N=${N})`); + // ═══════════════════════════════════════════════════════════════════════════ + // CLOCK SELECTORS + // ═══════════════════════════════════════════════════════════════════════════ + + // Base clock: 1 at row 0, 0 elsewhere within each 24-row cycle col fixed CLK_0 = [[1, 0:(CLOCKS-1)]:NUM_BLAKE2BR, 0...]; + // Clock selectors for each row in the cycle const expr CLK[CLOCKS]; for (int i = 0; i < CLOCKS; i++) { CLK[i] = (i)'CLK_0; } - // Define the clock selector - col witness bits(1) in_use; // 1 when the Blake2br operation is in use, 0 otherwise + col witness bits(1) in_use; in_use * (1 - in_use) === 0; // Selector latching: once activated, stays active for full cycle - const expr in_use_active = clock_set(start: 1, end: CLOCKS); - in_use_active * (in_use - 'in_use) === 0; + const expr in_use_latched = clock_set(start: 1, end: CLOCKS); + in_use_latched * (in_use - 'in_use) === 0; - // --> Constraints to assert the BLAKE2B round function + // ═══════════════════════════════════════════════════════════════════════════ + // ROUND INDEX AND SIGMA PERMUTATION + // ═══════════════════════════════════════════════════════════════════════════ - /// Message word permutation schedule - const int SIGMA[10][16]; - SIGMA[0] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; - SIGMA[1] = [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]; - SIGMA[2] = [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4]; - SIGMA[3] = [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8]; - SIGMA[4] = [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13]; - SIGMA[5] = [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9]; - SIGMA[6] = [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11]; - SIGMA[7] = [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10]; - SIGMA[8] = [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5]; - SIGMA[9] = [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]; - - // 1] Start by deriving m[SIGMA[idx][off + 2*i]], m[SIGMA[idx][off + 2*i + 1]] + // 1] Start by deriving m[SIGMA[round_idx][off + 2*i]], m[SIGMA[round_idx][off + 2*i + 1]] - // Indicator of the SIGMA index - col witness bits(4) idx; + // Round index (0-9, determines which SIGMA row to use) + col witness bits(4) round_idx; - // idx should be a value in [0,10) that is fixed along the cycle - (1 - CLK_0) * (idx - 'idx) === 0; + // Round index is constant within a round + (1 - CLK_0) * (round_idx - 'round_idx) === 0; + + // One-hot encoding for round index selection + col witness bits(1) round_idx_sel[10]; - col witness bits(1) idx_sel[10]; expr sum_sel = 0; - expr sum_w_sel = 0; + expr sum_weighted_sel = 0; for (int i = 0; i < 10; i++) { // Selectors are bits - idx_sel[i] * (idx_sel[i] - 1) === 0; + round_idx_sel[i] * (round_idx_sel[i] - 1) === 0; // Add all selectors - sum_sel += idx_sel[i]; + sum_sel += round_idx_sel[i]; - // Add weighted selectors to compute idx - sum_w_sel += idx_sel[i] * i; + // Add weighted selectors to compute round_idx + sum_weighted_sel += round_idx_sel[i] * i; } - sum_sel * (sum_sel - 1) === 0; // At most one of the idx_sel is active - idx === sum_w_sel; // Check that idx is the index of the active idx_sel - // Moreover, this ensures that idx is in [0,10) - // Since idx is fixed within the cycle, all idx_sel are fixed as well - - // SIGMA[idx] = ∑ᵢ idx_sel[i]·SIGMA[i] - col witness bits(4) s; - expr s_expr = 0; + sum_sel * (sum_sel - 1) === 0; // At most one of the round_idx_sel is active + round_idx === sum_weighted_sel; // Check that round_idx is the index of the active round_idx_sel + // Moreover, this ensures that round_idx is in [0,10) + // Since round_idx is fixed within the cycle, all round_idx_sel are fixed as well + + // Compute permuted message index: SIGMA[round_idx] = ∑ᵢ round_idx_sel[i]·SIGMA[i] + col witness bits(4) sigma_idx; + expr sigma_idx_expr = 0; for (int j = 0; j < 10; j++) { - expr sum_sigmas = 0; - int sig_idx = 0; + expr sigma_sum = 0; + int msg_pos = 0; for (int k = 0; k < CLOCKS; k++) { - if (k > 0 && k % 2 == 0) { + // Skip output rows (no message word needed) + if (k > 0 && k % CLOCKS_PER_G == (CLOCKS_PER_G - 1)) { continue; } - sum_sigmas += CLK[k] * SIGMA[j][sig_idx]; - sig_idx++; + sigma_sum += CLK[k] * SIGMA[j][msg_pos]; + msg_pos++; } - s_expr += idx_sel[j] * sum_sigmas; + sigma_idx_expr += round_idx_sel[j] * sigma_sum; } - s <== s_expr; + sigma_idx <== sigma_idx_expr; - // Permutation to get actual message words from indices + // ═══════════════════════════════════════════════════════════════════════════ + // MESSAGE WORDS (m and ms) + // ═══════════════════════════════════════════════════════════════════════════ + + // Message index for permutation col fixed MSG_IDX = [0, 1, 0, 2, 3, 0, 4, 5, 0, 6, 7, 0, 8, 9, 0, 10, 11, 0, 12, 13, 0, 14, 15, 0]...; - // The m-values in order - col witness bits(32) m[2]; + // Original message words (16-bit limbs for range checking) + col witness bits(16) m_limbs[2][2]; + const expr m[2] = [m_limbs[0][0] + P2_16 * m_limbs[0][1], m_limbs[1][0] + P2_16 * m_limbs[1][1]]; - // The m-values in the index order - col witness bits(32) ms[2]; + // Range check message limbs + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + range_check(expression: m_limbs[i][j], min: 0, max: P2_16 - 1); + } + } - const expr ms_next[2] = [ms[0]', ms[1]']; + // Permuted message words (selected by sigma_idx) + col witness bits(32) ms[2]; + // Permutation: map message index -> permuted message // Perform the permutation - col witness bits(1) in_use_clk_perm; // 1 when the permutation is activated - const expr perm_active = clock_set(in_use, start: 0, end: CLOCKS - G_FUNCTIONS_PER_ROUND, step: 2, skip: 1); - in_use_clk_perm <== perm_active; - - permutation_assumes(opid: BLAKE2BR_PERMUTATION_ID, expressions: [MSG_IDX, m[0], m[1]], sel: in_use_clk_perm); - permutation_proves(opid: BLAKE2BR_PERMUTATION_ID, expressions: [s, ms[0], ms[1]], sel: in_use_clk_perm); - - // 2] Compute the g mixing function - - col witness bits(1) in_use_clk_g; // 1 when the g function is activated - const expr g_active = clock_set(in_use, start: 0, end: CLOCKS / 3, step: 1, skip: 2); - in_use_clk_g <== g_active; - - // The four v-values of g - col witness bits(32) va[2]; + col witness bits(1) perm_active; + const expr perm_active_expr = clock_set(in_use, start: 0, end: CLOCKS - NUM_G_FUNCTIONS, step: 2, skip: 1); + perm_active <== perm_active_expr; + + permutation_assumes(opid: BLAKE2BR_PERMUTATION_ID, expressions: [MSG_IDX, m[0], m[1]], sel: perm_active); + permutation_proves(opid: BLAKE2BR_PERMUTATION_ID, expressions: [sigma_idx, ms[0], ms[1]], sel: perm_active); + + // ═══════════════════════════════════════════════════════════════════════════ + // G-FUNCTION STATE VARIABLES + // ═══════════════════════════════════════════════════════════════════════════ + + // G-function active selector (every 3 rows, skipping output rows) + col witness bits(1) g_active; + const expr g_active_expr = clock_set(in_use, start: 0, end: CLOCKS / CLOCKS_PER_G, step: 1, skip: 2); + g_active <== g_active_expr; + + // State variables: va, vb, vc, vd (each 64-bit) + // va, vc stored as 16-bit limbs for range checking + // vb, vd stored as individual bits for rotation operations + col witness bits(16) va_limbs[2][2]; + col witness bits(16) vc_limbs[2][2]; col witness bits(1) vb[2][32]; - col witness bits(32) vc[2]; col witness bits(1) vd[2][32]; - // va and vc will be range checked later on + // Reconstructed 32-bit values + const expr va[2] = [va_limbs[0][0] + P2_16 * va_limbs[0][1], va_limbs[1][0] + P2_16 * va_limbs[1][1]]; + const expr vc[2] = [vc_limbs[0][0] + P2_16 * vc_limbs[0][1], vc_limbs[1][0] + P2_16 * vc_limbs[1][1]]; - // vb and vd are bits + // Range check va and vc limbs + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + range_check(expression: va_limbs[i][j], min: 0, max: P2_16 - 1); + range_check(expression: vc_limbs[i][j], min: 0, max: P2_16 - 1); + } + } + + // Bit constraints for vb and vd for (int i = 0; i < 2; i++) { for (int j = 0; j < 32; j++) { vb[i][j] * (1 - vb[i][j]) === 0; @@ -215,11 +250,19 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION } } - // Define intermediate v-values - expr va_im[2] = [va[0]', va[1]']; - expr vc_im[2] = [vc[0]', vc[1]']; - expr vb_im[2][32]; - expr vd_im[2][32]; + // Packed bit representations + const expr vb_packed[2] = [pack_bits(vb[0]), pack_bits(vb[1])]; + const expr vd_packed[2] = [pack_bits(vd[0]), pack_bits(vd[1])]; + + // ═══════════════════════════════════════════════════════════════════════════ + // G-FUNCTION INTERMEDIATE AND OUTPUT VALUES + // ═══════════════════════════════════════════════════════════════════════════ + + // Intermediate values + const expr va_im[2] = [va[0]', va[1]']; + const expr vc_im[2] = [vc[0]', vc[1]']; + const expr vb_im[2][32]; + const expr vd_im[2][32]; for (int i = 0; i < 2; i++) { for (int j = 0; j < 32; j++) { vb_im[i][j] = vb[i][j]'; @@ -227,11 +270,14 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION } } - // Define output v-values - expr va_out[2] = [va[0]'2, va[1]'2]; - expr vc_out[2] = [vc[0]'2, vc[1]'2]; - expr vb_out[2][32]; - expr vd_out[2][32]; + const expr vb_im_packed[2] = [pack_bits(vb_im[0]), pack_bits(vb_im[1])]; + const expr vd_im_packed[2] = [pack_bits(vd_im[0]), pack_bits(vd_im[1])]; + + // Output values + const expr va_out[2] = [va[0]'2, va[1]'2]; + const expr vc_out[2] = [vc[0]'2, vc[1]'2]; + const expr vb_out[2][32]; + const expr vd_out[2][32]; for (int i = 0; i < 2; i++) { for (int j = 0; j < 32; j++) { vb_out[i][j] = vb[i][j]'2; @@ -239,70 +285,89 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION } } - // Verify the internal operations of the g function - // va' == (va + vb + ms) mod 2**64 - expr vbpack[2] = [pack(vb[0]), pack(vb[1])]; - add3_check(va_im, va, vbpack, ms); + const expr vd_out_packed[2] = [pack_bits(vd_out[0]), pack_bits(vd_out[1])]; - // vd' == (vd ^ va') >>> 32 iff va' == (vd' <<< 32) ^ vd - rotl_xor_check(va_im, vd_im, vd, 32); - // vc' == (vc + vd') mod 2**64 - expr vdimpack[2] = [pack(vd_im[0]), pack(vd_im[1])]; - add2_check(vc_im, vc, vdimpack); + // ═══════════════════════════════════════════════════════════════════════════ + // G-FUNCTION CONSTRAINTS (First Half) + // ═══════════════════════════════════════════════════════════════════════════ + // Operations: va' = va + vb + m[0] + // vd' = (vd ^ va') >>> 32 [iff va' == (vd' <<< 32) ^ vd] + // vc' = vc + vd' + // vb' = (vb ^ vc') >>> 24 [iff vc' == (vb' <<< 24) ^ vb] - // vb' == (vb ^ vc') >>> 24 iff vc' == (vb' <<< 24) ^ vb + add3_check(va_im, va, vb_packed, ms); + rotl_xor_check(va_im, vd_im, vd, 32); + add2_check(vc_im, vc, vd_im_packed); rotl_xor_check(vc_im, vb_im, vb, 24); - // va'' == (va' + vb' + ms) mod 2**64 - expr vbimpack[2] = [pack(vb_im[0]), pack(vb_im[1])]; - add3_check(va_out, va_im, vbimpack, ms_next); + // ═══════════════════════════════════════════════════════════════════════════ + // G-FUNCTION CONSTRAINTS (Second Half) + // ═══════════════════════════════════════════════════════════════════════════ + // Operations: va'' = va' + vb' + m[1] + // vd'' = (vd' ^ va'') >>> 16 [va'' == (vd'' <<< 16) ^ vd'] + // vc'' = vc' + vd'' + // vb'' = (vb' ^ vc'') >>> 63 [vc'' == (vb'' <<< 63) ^ vb'] - // vd'' == (vd' ^ va'') >>> 16 iff va'' == (vd'' <<< 16) ^ vd' + const expr ms_next[2] = [ms[0]', ms[1]']; + add3_check(va_out, va_im, vb_im_packed, ms_next); rotl_xor_check(va_out, vd_out, vd_im, 16); - - // vc'' == (vc' + vd'') mod 2**64 - expr vdoutpack[2] = [pack(vd_out[0]), pack(vd_out[1])]; - add2_check(vc_out, vc_im, vdoutpack); - - // vb'' == (vb' ^ vc'') >>> 63 iff vc'' == (vb'' <<< 63) ^ vb' + add2_check(vc_out, vc_im, vd_out_packed); rotl_xor_check(vc_out, vb_out, vb_im, 63); - // --> Constraints to read inputs from memory and write outputs to memory - - /* - We should take care of how we handle the memory access. - - The Blake2br inputs are received via two indirections and one value inside a structure. - The address received from the MainSM is the address of the structure - The first indirection is the adress of the state - The second indirection is the address of the input - We have to relate all these addresses, by proving that: - · ADDR_STATE === ADDR_IND_0 - · ADDR_INPUT === ADDR_IND_1 - while sending both addr_ind_0 and addr_ind_1 to the memory. - */ + // ═══════════════════════════════════════════════════════════════════════════ + // COLUMN-TO-DIAGONAL TRANSITION CONSTRAINTS + // ═══════════════════════════════════════════════════════════════════════════ + // Ensure diagonal mixing inputs match column mixing outputs + + // va: diagonal inputs come from column outputs at specific offsets + (CLK[12] + CLK[15] + CLK[18] + CLK[21]) * (va[0] - 10'va[0]) === 0; + + // vb: different routing for each diagonal + (CLK[12] + CLK[15] + CLK[18]) * (vb_packed[0] - 7'vb_packed[0]) === 0; + CLK[21] * (vb_packed[0] - 19'vb_packed[0]) === 0; + + // vc: pairs have same routing + (CLK[12] + CLK[15]) * (vc[0] - 4'vc[0]) === 0; + (CLK[18] + CLK[21]) * (vc[0] - 16'vc[0]) === 0; + + // vd: first diagonal different from rest + (CLK[12]) * (vd_packed[0] - 'vd_packed[0]) === 0; + (CLK[15] + CLK[18] + CLK[21]) * (vd_packed[0] - 13'vd_packed[0]) === 0; + + // ═══════════════════════════════════════════════════════════════════════════ + // MEMORY INTERFACE + // ═══════════════════════════════════════════════════════════════════════════ + // + // MEMORY ACCESS MAP + // ========================================================= + // 0 | STEP_MAIN | R | ADDR_STATE | state[0] + // | ... | | ... | ... + // 15 | STEP_MAIN | R | ADDR_STATE + 120 | state[15] + // 16 | STEP_MAIN | R | ADDR_INPUT | input[0] + // | ... | | ... | ... + // 31 | STEP_MAIN | R | ADDR_INPUT + 120 | input[15] + // 32 | STEP_MAIN + 1 | W | ADDR_STATE | state[0] + // | ... | | ... | ... + // 47 | STEP_MAIN + 1 | W | ADDR_STATE + 120 | state[15] + // 48 | STEP_MAIN | R | ADDR_OP | index + // 49 | STEP_MAIN | R | ADDR_OP + 8 | ADDR_IND_0 + // 50 | STEP_MAIN | R | ADDR_OP + 16 | ADDR_IND_1 + // ========================================================= + // + // Memory Access Schedule (51 total operations, 4 in parallel): + // Slots 0-3: Read state[0..15] (16 ops) + // Slots 4-7: Read input[0..15] (16 ops) + // Slots 8-11: Write state'[0..15] (16 ops) + // Slot 12: Read index, addr_ind_0, addr_ind_1 (3 ops) + // + // Address indirection: + // - addr_state is read from addr_op + 8 + // - addr_input is read from addr_op + 16 + // - Both must match the actual memory addresses used col witness bits(40) step_addr; - /* - MEMORY ACCESS MAP - ========================================================= - 0 | STEP_MAIN | R | ADDR_STATE | state[0] - | ... | | ... | ... - 15 | STEP_MAIN | R | ADDR_STATE + 120 | state[15] - 16 | STEP_MAIN | R | ADDR_INPUT | input[0] - | ... | | ... | ... - 31 | STEP_MAIN | R | ADDR_INPUT + 120 | input[15] - 32 | STEP_MAIN + 1 | W | ADDR_STATE | state[0] - | ... | | ... | ... - 47 | STEP_MAIN + 1 | W | ADDR_STATE + 120 | state[15] - 48 | STEP_MAIN | R | ADDR_OP | index - 49 | STEP_MAIN | R | ADDR_OP + 8 | ADDR_IND_0 - 50 | STEP_MAIN | R | ADDR_OP + 16 | ADDR_IND_1 - ========================================================= - */ - const int STEP_MAIN = 0; const int ADDR_OP = STEP_MAIN + 1; const int ADDR_STATE = ADDR_OP + 1; @@ -310,51 +375,135 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION const int ADDR_IND_0 = ADDR_INPUT + 1; const int ADDR_IND_1 = ADDR_IND_0 + 1; + // Verify address indirections match clock_eq(step_addr, ADDR_STATE, ADDR_IND_0) === 0; clock_eq(step_addr, ADDR_INPUT, ADDR_IND_1) === 0; - // Perform 4 memory operations in parallel - // Total: 51 mem ops, Steps Needed: ceil(51/4) = 12 < 24 (CLOCKS) - // - 0..3 -> Read State - // - 4..7 -> Read Input - // - 8..11 -> Write State - // - 12 -> Read Index and ADDR_INDs - const int READ_STIN_OPS = 32; - const int WRITE_STOUT_OPS = 16; - const int READ_REM_OPS = 3; - const int MEM_OPS = READ_STIN_OPS + WRITE_STOUT_OPS + READ_REM_OPS; + // Memory operation counts + const int NUM_STATE_WORDS = 16; + const int NUM_INPUT_WORDS = 16; + const int NUM_PARALLEL_MEM_OPS = 4; + const int WORD_SIZE = 8; // bytes per u64 - const int MEM_OPS_IN_PARALLEL = 4; - const int CLOCKS_WITH_READ_STIN_OPS = (READ_STIN_OPS + MEM_OPS_IN_PARALLEL - 1) / MEM_OPS_IN_PARALLEL; - const int CLOCKS_WITH_WRITE_STOUT_OPS = (WRITE_STOUT_OPS + MEM_OPS_IN_PARALLEL - 1) / MEM_OPS_IN_PARALLEL; - const int CLOCKS_WITH_MEM_OPS = (MEM_OPS + MEM_OPS_IN_PARALLEL - 1) / MEM_OPS_IN_PARALLEL; - - const expr mem_sel = clock_set(in_use, start: 0, end: CLOCKS_WITH_MEM_OPS); - - const expr mem_is_write = clock_set(start: CLOCKS_WITH_READ_STIN_OPS, end: CLOCKS_WITH_WRITE_STOUT_OPS); - - const expr main_step = clock_shift(step_addr, STEP_MAIN, start: 0, end: CLOCKS_WITH_MEM_OPS); - - for (int i = 0; i < MEM_OPS_IN_PARALLEL; i++) { - const expr mem_addr = clock_shift(step_addr, ADDR_STATE, start: 0, end: 4, delta: 8*i) + - clock_shift(step_addr, ADDR_INPUT, start: 4, end: 8, delta: 8*i) + - clock_shift(step_addr, ADDR_STATE, start: 8, end: 12, delta: 8*i) + - clock_shift(step_addr, ADDR_OP, start: 12, delta: 8*i); - - // TODO! - // expr mem_value[2]; - // mem_value[0] = CLK[0] * a_packed'3 + CLK[1] * a_packed + CLK[2] * e_packed' + CLK[3] * 2'e_packed + - // CLK[4] * w_spacked + CLK[5] * w_spacked' + CLK[6] * w_spacked'2 + CLK[7] * w_spacked'3 + - // CLK[8] * w_spacked'4 + CLK[9] * w_spacked'5 + CLK[10] * w_spacked'6 + CLK[11] * w_spacked'7 + - // CLK[12] * a_packed'59 + CLK[13] * a_packed'56 + CLK[14] * e_packed'57 + CLK[15] * e_packed'54 + - // clock_map(step_addr, ADDR_IND_0, 16) + clock_map(step_addr, ADDR_IND_1, 17); - - // mem_value[1] = CLK[0] * a_packed'2 + CLK[1] * 'a_packed + CLK[2] * e_packed + CLK[3] * 3'e_packed + - // CLK[4] * w_spacked' + CLK[5] * w_spacked'2 + CLK[6] * w_spacked'3 + CLK[7] * w_spacked'4 + - // CLK[8] * w_spacked'5 + CLK[9] * w_spacked'6 + CLK[10] * w_spacked'7 + CLK[11] * w_spacked'8 + - // CLK[12] * a_packed'58 + CLK[13] * a_packed'55 + CLK[14] * e_packed'56 + CLK[15] * e_packed'53; - // // addresses are 32-bit values - const expr mem_value[2] = [0 , 0]; + const int CLOCKS_READ_STATE = NUM_STATE_WORDS / NUM_PARALLEL_MEM_OPS; + const int CLOCKS_READ_INPUT = NUM_INPUT_WORDS / NUM_PARALLEL_MEM_OPS; + const int CLOCKS_WRITE_STATE = NUM_STATE_WORDS / NUM_PARALLEL_MEM_OPS; + + // Memory operation configuration + const int CLK_SEQ_READ_PARAMS[1] = [2]; + const int CLK_SEQ_READ_STATE[4] = [0, 3, 6, 9]; + const int CLK_SEQ_READ_INPUT[4] = [1, 7, 13, 19]; + const int CLK_SEQ_WRITE_STATE[4] = [14, 17, 20, 23]; + const int CLK_SEQ_ALL_MEM_OPS[13] = [2, 0, 3, 6, 9, 1, 7, 13, 19, 14, 17, 20, 23]; // All the previous + + expr mem_sel = clock_set_with_seq(in_use, seq: CLK_SEQ_ALL_MEM_OPS); + const expr mem_is_write = clock_set_with_seq(seq: CLK_SEQ_WRITE_STATE); + const expr main_step = clock_shift_with_seq(step_addr, STEP_MAIN, seq: CLK_SEQ_ALL_MEM_OPS); + + // Generate 4 parallel memory ports + const expr clk_read_params = clock_set_with_seq(seq: CLK_SEQ_READ_PARAMS); + const expr clk_read_state = clock_set_with_seq(seq: CLK_SEQ_READ_STATE); + const expr clk_read_input = clock_set_with_seq(seq: CLK_SEQ_READ_INPUT); + const expr clk_write_state = clock_set_with_seq(seq: CLK_SEQ_WRITE_STATE); + for (int port = 0; port < NUM_PARALLEL_MEM_OPS; port++) { + const expr mem_addr = + // Op structure access: round_idx, ADDR_STATE ptr, ADDR_INPUT ptr + clock_shift(step_addr, ADDR_OP, + start: 2, + offset: WORD_SIZE * port, + delta: WORD_SIZE) + + // Read state: ports read state[port], state[port+4], state[port+8], state[port+12] + clock_shift(step_addr, ADDR_STATE, + start: 0, end: CLOCKS_READ_STATE, + step: 1, skip: 2, + offset: WORD_SIZE * NUM_PARALLEL_MEM_OPS * port, + delta: WORD_SIZE) + + // Read input: ports read input[port*4+0..3] across 4 clocks + clock_shift(step_addr, ADDR_INPUT, + start: 1, end: 1 + CLOCKS_READ_INPUT, + step: 1, skip: 5, + offset: WORD_SIZE * port, + delta: WORD_SIZE * NUM_PARALLEL_MEM_OPS) + + // Write state: ports write state'[port], state'[port+4], etc. + clock_shift(step_addr, ADDR_STATE, + start: 14, end: 14 + CLOCKS_WRITE_STATE, + step: 1, skip: 2, + offset_shift: port, + offset: WORD_SIZE * NUM_PARALLEL_MEM_OPS * port, + delta: WORD_SIZE); + + // Value calculation for each port + expr mem_value[2] = [0, 0]; + switch (port) { + case 0: + // Input index + mem_value[0] += clk_read_params * round_idx; + + // Input v + mem_value[0] += clk_read_state * va[0]; + mem_value[1] += clk_read_state * va[1]; + + // Input m + mem_value[0] += clk_read_input * 'm[0]; + mem_value[1] += clk_read_input * 'm[1]; + + // Output v + mem_value[0] += clk_write_state * va[0]; + mem_value[1] += clk_write_state * va[1]; + + case 1: + // Input addr_ind_0 + mem_value[0] += clock_shift(step_addr, ADDR_IND_0, 2); + + // Input v + mem_value[0] += clk_read_state * vb_packed[0]; + mem_value[1] += clk_read_state * vb_packed[1]; + + // Input m + mem_value[0] += clk_read_input * m[0]; + mem_value[1] += clk_read_input * m[1]; + + // Output v + mem_value[0] += clk_write_state * vb_packed[0]; + mem_value[1] += clk_write_state * vb_packed[1]; + + case 2: + // Input addr_ind_1 + mem_value[0] += clock_shift(step_addr, ADDR_IND_1, 2); + + // Input v + mem_value[0] += clk_read_state * vc[0]; + mem_value[1] += clk_read_state * vc[1]; + + // Input m + mem_value[0] += clk_read_input * m[0]'2; + mem_value[1] += clk_read_input * m[1]'2; + + // Output v + mem_value[0] += clk_write_state * vc[0]; + mem_value[1] += clk_write_state * vc[1]; + + case 3: + // Input v + mem_value[0] += clk_read_state * vd_packed[0]; + mem_value[1] += clk_read_state * vd_packed[1]; + + // Input m + mem_value[0] += clk_read_input * m[0]'3; + mem_value[1] += clk_read_input * m[1]'3; + + // Output v + mem_value[0] += clk_write_state * vd_packed[0]; + mem_value[1] += clk_write_state * vd_packed[1]; + + default: + error(`Unexpected port index: ${port}`); + } + + // Port 3 is unused at clock 2 (only 3 ops: round_idx, ADDR_STATE, ADDR_INPUT) + if (port == NUM_PARALLEL_MEM_OPS - 1) { + mem_sel -= CLK[2] * in_use; + } precompiled_mem_op( is_write: mem_is_write, @@ -365,13 +514,15 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION ); } - // --> Constraints to make sure that this coprocessor is called from the main processor + // ═══════════════════════════════════════════════════════════════════════════ + // MAIN PROCESSOR INTEGRATION + // ═══════════════════════════════════════════════════════════════════════════ col witness bits(1) in_use_clk_0; // 1 at the first clock cycle of the Blake2br operation, 0 otherwise in_use_clk_0 <== CLK_0 * in_use; lookup_proves(operation_bus_id, [OP_BLAKE2BR, 0, 0, step_addr'(ADDR_OP), 0, 0, 0, 0, step_addr'(STEP_MAIN)], mul: in_use_clk_0); - function pack(const expr a[]): expr { + function pack_bits(const expr a[]): expr { const int len = length(a); expr packed = 0; for (int j = 0; j < len; j++) { @@ -383,6 +534,8 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION // Checks a == (b + c + d) mod 2⁶⁴ // Assumes a,b,c,d are all given as 2 limbs of 32-bits each, i.e., a = a0 + 2³²·a1 < 2⁶⁴ // It also assumes that p > 3·2³² + // + // Adapted on https://github.com/Plonky3/Plonky3/blob/main/air/src/utils.rs#L82 function add3_check(const expr a[], const expr b[], const expr c[], const expr d[]): expr[] { assert(length(a) == 2 && length(b) == 2 && length(c) == 2 && length(d) == 2); // a == (b + c + d) mod 2⁶⁴ iff a - b - c - d = 0, -2⁶⁴, -2·2⁶⁴ (over the integers) @@ -403,8 +556,8 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION expr sum_1 = a[1] - b[1] - c[1] - d[1]; expr sum = sum_0 + P2_32*sum_1; - air.in_use_clk_g * (sum_0 * (sum_0 + P2_32) * (sum_0 + 2*P2_32)) === 0; - air.in_use_clk_g * (sum * (sum + P2_64) * (sum + 2*P2_64)) === 0; + air.g_active * (sum_0 * (sum_0 + P2_32) * (sum_0 + 2*P2_32)) === 0; + air.g_active * (sum * (sum + P2_64) * (sum + 2*P2_64)) === 0; } // Checks a == (b + c) mod 2⁶⁴ @@ -431,8 +584,8 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION expr sum_1 = a[1] - b[1] - c[1]; expr sum = sum_0 + P2_32*sum_1; - air.in_use_clk_g * (sum_0 * (sum_0 + P2_32)) === 0; - air.in_use_clk_g * (sum * (sum + P2_64)) === 0; + air.g_active * (sum_0 * (sum_0 + P2_32)) === 0; + air.g_active * (sum * (sum + P2_64)) === 0; } // Checks a == (b <<< n) ^ c @@ -473,8 +626,8 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION } // Check equality - air.in_use_clk_g * (a[0] - packed_low) === 0; - air.in_use_clk_g * (a[1] - packed_high) === 0; + air.g_active * (a[0] - packed_low) === 0; + air.g_active * (a[1] - packed_high) === 0; // Notice that these equalities also ensure that a[0], a[1] are range checked } @@ -507,19 +660,61 @@ airtemplate Blake2br(const int N = 2**22, const int operation_bus_id = OPERATION return result * mvcol; } - function clock_shift(const expr mvcol, const int pos, const int start = 0, int end = -1, int offset = 0, const int delta = 0): const expr { + function clock_set_with_seq(const expr mvcol = 1, const int seq[]): const expr { + const int len = length(seq); + assert(len > 0); + + expr result = 0; + for (int i = 0; i < len; i++) { + result += air.CLK[seq[i]]; + } + return result * mvcol; + } + + function clock_shift(const expr mvcol, const int pos, const int start = 0, int end = -1, const int step = 1, const int skip = 0, int offset = 0, const int delta = 0, const int offset_shift = 0): const expr { expr result = 0; if (end == -1) { end = start + 1; } - for (int i = start; i < end; i++) { - const int iclock = (pos - i) % air.CLOCKS; - if (offset != 0) { - result += air.CLK[i] * (mvcol'(iclock) + offset); + + const int num_clocks = end - start; + + int clock_idx = start; + int count = 0; + int in_group = 0; + while (count < num_clocks) { + const int col_idx = (pos - clock_idx) % air.CLOCKS; + // Compute the shifted offset index: (count + offset_shift) mod num_clocks + const int shifted_count = (count + offset_shift) % num_clocks; + const int current_offset = offset + delta * shifted_count; + + if (current_offset != 0) { + result += air.CLK[clock_idx] * (mvcol'(col_idx) + current_offset); } else { - result += air.CLK[i] * mvcol'(iclock); + result += air.CLK[clock_idx] * mvcol'(col_idx); + } + + count++; + in_group++; + clock_idx++; + + // After 'step' consecutive clocks, skip 'skip' clocks + if (skip > 0 && in_group == step) { + clock_idx += skip; + in_group = 0; } - offset += delta; + } + return result; + } + + function clock_shift_with_seq(const expr mvcol, const int pos, const int seq[]): const expr { + const int len = length(seq); + assert(len > 0); + + expr result = 0; + for (int i = 0; i < len; i++) { + const int col_idx = (pos - seq[i]) % air.CLOCKS; + result += air.CLK[seq[i]] * mvcol'(col_idx); } return result; } diff --git a/precompiles/blake2/src/blake2.rs b/precompiles/blake2/src/blake2.rs index 989a379df..311a7624b 100644 --- a/precompiles/blake2/src/blake2.rs +++ b/precompiles/blake2/src/blake2.rs @@ -36,6 +36,8 @@ pub struct Blake2SM { pub num_available_blake2s: usize, num_non_usable_rows: usize, + + range_id: usize, } impl Blake2SM { @@ -49,7 +51,9 @@ impl Blake2SM { let num_available_blake2s = Blake2TraceType::::NUM_ROWS / CLOCKS - (num_non_usable_rows != 0) as usize; - Arc::new(Self { std, num_available_blake2s, num_non_usable_rows }) + let range_id = std.get_range_id(0, (1 << 16) - 1, None).expect("Failed to get range ID"); + + Arc::new(Self { std, num_available_blake2s, num_non_usable_rows, range_id }) } /// Processes a slice of operation data, updating the trace and multiplicities. @@ -60,7 +64,13 @@ impl Blake2SM { /// * `input` - The operation data to process. /// * `multiplicity` - A mutable slice to update with multiplicities for the operation. #[inline(always)] - pub fn process_input(&self, input: &Blake2Input, trace: &mut [Blake2TraceRowType]) { + pub fn process_input( + &self, + input: &Blake2Input, + trace: &mut [Blake2TraceRowType], + ) -> [u32; 65536] { + let mut range_checks = [0u32; 65536]; // 2^16 range checks for the 16-bit limbs + let step_main = input.step_main; let addr_main = input.addr_main; let state_addr = input.state_addr; @@ -84,19 +94,28 @@ impl Blake2SM { row.set_in_use(true); // Set idx - row.set_idx(index); + row.set_round_idx(index); // Set idx_sel - row.set_idx_sel(idx_usize, true); + row.set_round_idx_sel(idx_usize, true); } // Set m columns let mut m_idx = 0; for (i, &inp) in input.iter().enumerate() { - trace[m_idx].set_m(0, inp as u32); - trace[m_idx].set_m(1, (inp >> 32) as u32); + let low_inp = [inp as u16, (inp >> 16) as u16]; + let high_inp = [(inp >> 32) as u16, (inp >> 48) as u16]; + for j in 0..2 { + trace[m_idx].set_m_limbs(0, j, low_inp[j]); + trace[m_idx].set_m_limbs(1, j, high_inp[j]); + } + range_checks[low_inp[0] as usize] += 1; + range_checks[low_inp[1] as usize] += 1; + range_checks[high_inp[0] as usize] += 1; + range_checks[high_inp[1] as usize] += 1; + m_idx += 1; - if i % CLOCKS_PER_G == (CLOCKS_PER_G - 1) { + if (i + 1) % (CLOCKS_PER_G - 1) == 0 { m_idx += 1; } } @@ -106,27 +125,42 @@ impl Blake2SM { let mut ms: [u64; 16] = [0u64; 16]; m_idx = 0; for i in 0..input.len() { - ms[i] = input[s[i]]; + let inp = input[s[i]]; + ms[i] = inp; - trace[m_idx].set_ms(0, trace[m_idx].get_m(0)); - trace[m_idx].set_ms(1, trace[m_idx].get_m(1)); + trace[m_idx].set_ms(0, inp as u32); + trace[m_idx].set_ms(1, (inp >> 32) as u32); m_idx += 1; - if i % CLOCKS_PER_G == (CLOCKS_PER_G - 1) { + if (i + 1) % (CLOCKS_PER_G - 1) == 0 { m_idx += 1; } } // Column mixing - compute_g_and_set_vs(trace, 0, &[state[0], state[4], state[8], state[12]], &[ms[0], ms[1]]); - compute_g_and_set_vs(trace, 1, &[state[1], state[5], state[9], state[13]], &[ms[2], ms[3]]); - compute_g_and_set_vs( + let (state0, state4, state8, state12) = compute_g_and_set_vs( + trace, + &mut range_checks, + 0, + &[state[0], state[4], state[8], state[12]], + &[ms[0], ms[1]], + ); + let (state1, state5, state9, state13) = compute_g_and_set_vs( + trace, + &mut range_checks, + 1, + &[state[1], state[5], state[9], state[13]], + &[ms[2], ms[3]], + ); + let (state2, state6, state10, state14) = compute_g_and_set_vs( trace, + &mut range_checks, 2, &[state[2], state[6], state[10], state[14]], &[ms[4], ms[5]], ); - compute_g_and_set_vs( + let (state3, state7, state11, state15) = compute_g_and_set_vs( trace, + &mut range_checks, 3, &[state[3], state[7], state[11], state[15]], &[ms[6], ms[7]], @@ -135,44 +169,53 @@ impl Blake2SM { // Diagonal mixing compute_g_and_set_vs( trace, + &mut range_checks, 4, - &[state[0], state[5], state[10], state[15]], + &[state0, state5, state10, state15], &[ms[8], ms[9]], ); compute_g_and_set_vs( trace, + &mut range_checks, 5, - &[state[1], state[6], state[11], state[12]], + &[state1, state6, state11, state12], &[ms[10], ms[11]], ); compute_g_and_set_vs( trace, + &mut range_checks, 6, - &[state[2], state[7], state[8], state[13]], + &[state2, state7, state8, state13], &[ms[12], ms[13]], ); compute_g_and_set_vs( trace, + &mut range_checks, 7, - &[state[3], state[4], state[9], state[14]], + &[state3, state4, state9, state14], &[ms[14], ms[15]], ); + return range_checks; + fn compute_g_and_set_vs( trace: &mut [Blake2TraceRowType], + range_checks: &mut [u32; 65536], i: usize, v: &[u64; 4], m: &[u64; 2], - ) { + ) -> (u64, u64, u64, u64) { // Compute the g function let (va, vb, vc, vd) = (v[0], v[1], v[2], v[3]); let (va_i, vb_i, vc_i, vd_i) = compute_half_g(va, vb, vc, vd, m[0], R1_G, R2_G); let (va_o, vb_o, vc_o, vd_o) = compute_half_g(va_i, vb_i, vc_i, vd_i, m[1], R3_G, R4_G); // Set va, vb, vc, vd columns - set_vs(&mut trace[3 * i], va, vb, vc, vd); - set_vs(&mut trace[3 * i + 1], va_i, vb_i, vc_i, vd_i); - set_vs(&mut trace[3 * i + 2], va_o, vb_o, vc_o, vd_o); + set_vs(&mut trace[3 * i], range_checks, va, vb, vc, vd); + set_vs(&mut trace[3 * i + 1], range_checks, va_i, vb_i, vc_i, vd_i); + set_vs(&mut trace[3 * i + 2], range_checks, va_o, vb_o, vc_o, vd_o); + + (va_o, vb_o, vc_o, vd_o) } fn compute_half_g( @@ -193,15 +236,22 @@ impl Blake2SM { fn set_vs( row: &mut Blake2TraceRowType, + range_checks: &mut [u32; 65536], va: u64, vb: u64, vc: u64, vd: u64, ) { - let low_va = va as u32; - let high_va = (va >> 32) as u32; - row.set_va(0, low_va); - row.set_va(1, high_va); + let low_va = [va as u16, (va >> 16) as u16]; + let high_va = [(va >> 32) as u16, (va >> 48) as u16]; + for j in 0..2 { + row.set_va_limbs(0, j, low_va[j]); + row.set_va_limbs(1, j, high_va[j]); + } + range_checks[low_va[0] as usize] += 1; + range_checks[low_va[1] as usize] += 1; + range_checks[high_va[0] as usize] += 1; + range_checks[high_va[1] as usize] += 1; let low_vb = vb as u32; let low_vb = u32_to_le_bits(low_vb); @@ -212,10 +262,16 @@ impl Blake2SM { row.set_vb(1, j, high_vb[j]); } - let low_vc = vc as u32; - let high_vc = (vc >> 32) as u32; - row.set_vc(0, low_vc); - row.set_vc(1, high_vc); + let low_vc = [vc as u16, (vc >> 16) as u16]; + let high_vc = [(vc >> 32) as u16, (vc >> 48) as u16]; + for j in 0..2 { + row.set_vc_limbs(0, j, low_vc[j]); + row.set_vc_limbs(1, j, high_vc[j]); + } + range_checks[low_vc[0] as usize] += 1; + range_checks[low_vc[1] as usize] += 1; + range_checks[high_vc[0] as usize] += 1; + range_checks[high_vc[1] as usize] += 1; let low_vd = vd as u32; let low_vd = u32_to_le_bits(low_vd); @@ -229,9 +285,9 @@ impl Blake2SM { fn u32_to_le_bits(x: u32) -> [bool; 32] { let mut bits = [false; 32]; - for i in 0..32 { + for (i, bit) in bits.iter_mut().enumerate() { if ((x >> i) & 1) != 0 { - bits[i] = true; + *bit = true; } } bits @@ -251,16 +307,17 @@ impl Blake2SM { inputs: &[Vec], trace_buffer: Vec, ) -> ProofmanResult> { - let mut blake2_trace = Blake2TraceType::new_from_vec_zeroes(trace_buffer)?; - let num_rows = blake2_trace.num_rows(); + let mut trace = Blake2TraceType::new_from_vec_zeroes(trace_buffer)?; + let num_rows = trace.num_rows(); let num_available_blake2s = self.num_available_blake2s; // Check that we can fit all the blake2s in the trace let num_inputs = inputs.iter().map(|v| v.len()).sum::(); + let all_ops_used = num_inputs == num_available_blake2s; let num_rows_filled = num_inputs * CLOCKS; let num_rows_needed = if num_inputs < num_available_blake2s { num_inputs * CLOCKS - } else if num_inputs == num_available_blake2s { + } else if all_ops_used { num_rows } else { panic!( @@ -279,7 +336,7 @@ impl Blake2SM { timer_start_trace!(BLAKE2_TRACE); // Split trace into chunks for parallel processing - let mut trace_rows = blake2_trace.buffer.as_mut_slice(); + let mut trace_rows = trace.buffer.as_mut_slice(); let mut par_traces = Vec::new(); let mut inputs_indexes = Vec::new(); for (i, inputs) in inputs.iter().enumerate() { @@ -291,32 +348,51 @@ impl Blake2SM { } } - // Fill the trace - par_traces.into_par_iter().enumerate().for_each(|(index, trace)| { - let input_index = inputs_indexes[index]; - let input = &inputs[input_index.0][input_index.1]; - self.process_input(input, trace) - }); + // Fill the trace and collect range checks + let range_checks_vec: Vec<[u32; 65536]> = par_traces + .into_par_iter() + .enumerate() + .map(|(index, trace)| { + let input_index = inputs_indexes[index]; + let input = &inputs[input_index.0][input_index.1]; + self.process_input(input, trace) + }) + .collect(); + + // Aggregate all range checks + let mut range_checks = vec![0; 65536]; + for rc in range_checks_vec { + for i in 0..65536 { + range_checks[i] += rc[i]; + } + } timer_stop_and_log_trace!(BLAKE2_TRACE); - timer_start_trace!(BLAKE2_PADDING); - // // Set a = e = w = 0 for the state and input rows - // let zero_row = Blake2TraceRowType::::default(); + let mut padding_row = Blake2TraceRowType::default(); + // In the no-op rows, the `idx` should be the same as the previous one until the end + // to make the constraint `(1 - CLK_0) * (idx - 'idx) === 0;` be satisfied + // As a consequence, one should also set idx_sel + if all_ops_used { + let prev_idx = trace.buffer[num_rows_filled - 1].get_round_idx(); + padding_row.set_round_idx(prev_idx); + padding_row.set_round_idx_sel(prev_idx as usize, true); + } - // let padding_start = num_rows_filled; - // let padding_end = num_rows - self.num_non_usable_rows - CLOCKS; + trace.buffer[num_rows_filled..num_rows].par_iter_mut().for_each(|slot| *slot = padding_row); - // if padding_start < padding_end { - // blake2_trace.buffer[padding_start..padding_end] - // .par_iter_mut() - // .for_each(|row| { - // *row = zero_row; - // }); - // } + // Perform the zero range checks + let mut count_zeros = ((num_available_blake2s - num_inputs + + (self.num_non_usable_rows != 0) as usize) + * CLOCKS + + self.num_non_usable_rows) + * 12; // 12 range checked columns, 8 from va and vc, and 4 from m16 + count_zeros += 8 * num_inputs * 4; // m16 columns have one padding row per g function + // and there are 8 g functions per blake2 + range_checks[0] += count_zeros as u32; - timer_stop_and_log_trace!(BLAKE2_PADDING); + self.std.range_checks(self.range_id, range_checks); - Ok(AirInstance::new_from_trace(FromTrace::new(&mut blake2_trace))) + Ok(AirInstance::new_from_trace(FromTrace::new(&mut trace))) } } diff --git a/precompiles/blake2/src/blake2_gen_mem_inputs.rs b/precompiles/blake2/src/blake2_gen_mem_inputs.rs index 28651278c..fd19269c1 100644 --- a/precompiles/blake2/src/blake2_gen_mem_inputs.rs +++ b/precompiles/blake2/src/blake2_gen_mem_inputs.rs @@ -5,7 +5,7 @@ use zisk_common::OPERATION_PRECOMPILED_BUS_DATA_SIZE; use zisk_core::blake2br; use crate::blake2_constants::{ - DIRECT_READ_PARAMS, DIRECT_READ_PARAM_POS, PARAMS, PARAM_CHUNKS, READ_PARAMS, START_READ_PARAMS, + DIRECT_READ_PARAMS, PARAMS, PARAM_CHUNKS, READ_PARAMS, START_READ_PARAMS, }; #[derive(Debug)] @@ -38,7 +38,8 @@ pub fn generate_blake2_mem_inputs( // Generate memory load params for iparam in 0..READ_PARAMS { - let param_idx = if iparam >= DIRECT_READ_PARAM_POS { iparam + 1 } else { iparam }; + // let param_idx = if iparam >= DIRECT_READ_PARAM_POS { iparam + 1 } else { iparam }; + let param_idx = iparam + 1; let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_idx] as u32; for ichunk in 0..PARAM_CHUNKS { diff --git a/precompiles/helpers/src/blake2/blake2b/mod.rs b/precompiles/helpers/src/blake2/blake2b/mod.rs index 65eaafa26..b5a63501f 100644 --- a/precompiles/helpers/src/blake2/blake2b/mod.rs +++ b/precompiles/helpers/src/blake2/blake2b/mod.rs @@ -36,9 +36,7 @@ pub fn blake2b_compress(rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; } for r in 0..rounds { - println!("Blake2b_round with v = {:x?}, m = {:x?}", v, m); blake2b_round(&mut v, m, r); - panic!("Blake2b_round with v = {:x?}, m = {:x?}", v, m); } for i in 0..8 { From b03a90c337e0f3ba2dea3314b7180839f3be6d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:42:06 +0000 Subject: [PATCH 643/782] Minor fix gpu --- precompiles/blake2/src/blake2.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/precompiles/blake2/src/blake2.rs b/precompiles/blake2/src/blake2.rs index 311a7624b..09ba521a5 100644 --- a/precompiles/blake2/src/blake2.rs +++ b/precompiles/blake2/src/blake2.rs @@ -8,14 +8,14 @@ use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; #[cfg(feature = "packed")] -use zisk_pil::{Blake2TracePacked, Blake2TraceRowPacked}; +use zisk_pil::{Blake2brTracePacked, Blake2brTraceRowPacked}; #[cfg(not(feature = "packed"))] use zisk_pil::{Blake2brTrace, Blake2brTraceRow}; #[cfg(feature = "packed")] -type Blake2TraceRowType = Blake2TraceRowPacked; +type Blake2TraceRowType = Blake2brTraceRowPacked; #[cfg(feature = "packed")] -type Blake2TraceType = Blake2TracePacked; +type Blake2TraceType = Blake2brTracePacked; #[cfg(not(feature = "packed"))] type Blake2TraceRowType = Blake2brTraceRow; From 3dbc70d61c48a868f4085e53a57047244ae4150f Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 19 Feb 2026 10:49:05 +0100 Subject: [PATCH 644/782] Implement Blake2b round in assembly and hints --- core/src/zisk_rom_2_asm.rs | 91 +++++++++++++++++++++- emulator-asm/src/emu.c | 55 +++++++++++++ emulator-asm/src/emu.hpp | 3 + lib-c/c/Makefile | 6 +- lib-c/c/src/blake2/blake2.cpp | 76 ++++++++++++++++++ lib-c/c/src/blake2/blake2.hpp | 16 ++++ ziskos/entrypoint/src/syscalls/blake2br.rs | 10 ++- 7 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 lib-c/c/src/blake2/blake2.cpp create mode 100644 lib-c/c/src/blake2/blake2.hpp diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index a948b17fb..afb8faf03 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -5346,6 +5346,94 @@ impl ZiskRom2Asm { ctx.c.is_saved = true; ctx.flag_is_always_zero = true; } + ZiskOp::Blake2 => { + // Use the memory address as the first and unique parameter + *code += &ctx.full_line_comment("Blake2: rdi = b".to_string()); + + if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() + { + // Use the memory address as the first and unique parameter + *code += &format!( + "\tmov rdi, {} {}\n", + ctx.b.string_value, + ctx.comment_str("rdi = b = address") + ); + + // Save data into mem_reads + if ctx.minimal_trace() || ctx.zip() || ctx.mem_reads() { + // If zip, check if chunk is active + if ctx.zip() { + *code += &format!( + "\ttest {}, 1 {}\n", + REG_ACTIVE_CHUNK, + ctx.comment_str("active_chunk == 1 ?") + ); + *code += &format!("\tjnz pc_{:x}_blake2_active_chunk\n", ctx.pc); + *code += &format!("\tjmp pc_{:x}_blake2_active_chunk_done\n", ctx.pc); + *code += &format!("pc_{:x}_blake2_active_chunk:\n", ctx.pc); + } + Self::precompiled_save_mem_reads(ctx, code, 3, &[0, 16, 16]); + if ctx.zip() { + *code += &format!("pc_{:x}_blake2_active_chunk_done:\n", ctx.pc); + } + } + + // Save memory operations into mem_reads + if ctx.mem_op() { + Self::mem_op_precompiled_read_and_write( + ctx, + code, + 3, + &[0, 16, 16], + 1, + 1, + 16, + ); + } + + // Get result from precompile results data + if ctx.precompile_results_blake2() { + *code += "\tmov rdi, [rdi+8]\n"; + Self::precompile_results_array(ctx, code, unusual_code, "rdi", 16); + } else { + // Call the Blake2 function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_blake2\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } + } + + // Consume mem reads + if ctx.chunk_player_mem_reads_collect_main() { + *code += &format!( + "\tmov [{} + {}*8], {} {}\n", + REG_MEM_READS_ADDRESS, + REG_MEM_READS_SIZE, + REG_CHUNK_PLAYER_ADDRESS, + ctx.comment_str("Main[4] = precompiler data address") + ); + *code += &format!( + "\tinc {} {}\n", + REG_MEM_READS_SIZE, + ctx.comment_str("mem_reads_size++") + ); + } + if ctx.chunk_player_mt_collect_mem() || ctx.chunk_player_mem_reads_collect_main() { + *code += &format!( + "\tadd {}, 35*8 {}\n", + REG_CHUNK_PLAYER_ADDRESS, + ctx.comment_str("chunk_address += 35*8") + ); + } + + // Set result + *code += + &format!("\txor {}, {} {}\n", REG_C, REG_C, ctx.comment_str("Blake2: c = 0")); + ctx.c.is_saved = true; + ctx.flag_is_always_zero = true; + } ZiskOp::Poseidon2 => { // Use the memory address as the first and unique parameter *code += &ctx.full_line_comment("Poseidon2: rdi = A0".to_string()); @@ -5453,9 +5541,6 @@ impl ZiskRom2Asm { ctx.c.is_saved = true; ctx.flag_is_always_zero = true; } - ZiskOp::Blake2 => { - unimplemented!(); - } ZiskOp::PubOut => { assert!(ctx.store_b_in_c); ctx.c.is_constant = ctx.b.is_constant; diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index 74eecf609..baf58a523 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -17,6 +17,7 @@ #include "../../lib-c/c/src/bn254/bn254.hpp" #include "../../lib-c/c/src/bls12_381/bls12_381.hpp" #include "../../lib-c/c/src/poseidon2/poseidon2_goldilocks.hpp" +#include "../../lib-c/c/src/blake2/blake2.hpp" #include "bcon/bcon_sha256.hpp" extern void zisk_sha256(uint64_t state[4], uint64_t input[8]); @@ -39,6 +40,8 @@ void reset_asm_call_metrics (void) asm_call_metrics.keccak_duration = 0; asm_call_metrics.sha256_counter = 0; asm_call_metrics.sha256_duration = 0; + asm_call_metrics.blake2_counter = 0; + asm_call_metrics.blake2_duration = 0; asm_call_metrics.poseidon2_counter = 0; asm_call_metrics.poseidon2_duration = 0; asm_call_metrics.arith256_counter = 0; @@ -111,6 +114,16 @@ void print_asm_call_metrics (uint64_t total_duration) duration, percentage); + // Print blake2 metrics + percentage = total_duration == 0 ? 0 : (asm_call_metrics.blake2_duration * 1000) / total_duration; + duration = asm_call_metrics.blake2_counter == 0 ? 0 : (asm_call_metrics.blake2_duration * 1000) / asm_call_metrics.blake2_counter; + asm_call_total_duration += asm_call_metrics.blake2_duration; + printf("Blake2: counter = %lu, duration = %lu us, single duration = %lu ns, per thousand = %lu \n", + asm_call_metrics.blake2_counter, + asm_call_metrics.blake2_duration, + duration, + percentage); + // Print poseidon2 metrics percentage = total_duration == 0 ? 0 : (asm_call_metrics.poseidon2_duration * 1000) / total_duration; duration = asm_call_metrics.poseidon2_counter == 0 ? 0 : (asm_call_metrics.poseidon2_duration * 1000) / asm_call_metrics.poseidon2_counter; @@ -592,6 +605,48 @@ extern int _opcode_sha256(uint64_t * address) return 0; } +extern int _opcode_blake2(uint64_t * address) +{ +#ifdef ASM_CALL_METRICS + gettimeofday(&asm_call_start, NULL); +#endif +#ifdef DEBUG +#ifdef ASM_CALL_METRICS + if (emu_verbose) printf("opcode_blake2() calling blake2b() counter=%lu address=%08lx\n", asm_call_metrics.blake2_counter, address); +#else + if (emu_verbose) printf("opcode_blake2() calling blake2b() address=%08lx\n", address); +#endif +#endif + +#ifdef ASM_PRECOMPILE_CACHE + if (precompile_cache_storing) + { +#endif + // Call blake2b compression function + blake2b_round((uint64_t *)address[1], (uint64_t *)address[2], address[0]); + +#ifdef ASM_PRECOMPILE_CACHE + // Store result in cache + precompile_cache_store((uint8_t *)address[1], 16*8); + } + else if (precompile_cache_loading) + { + // Load result from cache + precompile_cache_load((uint8_t *)address[1], 16*8); + } +#endif + +#ifdef DEBUG + if (emu_verbose) printf("opcode_blake2() called blake2b()\n"); +#endif +#ifdef ASM_CALL_METRICS + asm_call_metrics.blake2_counter++; + gettimeofday(&asm_call_stop, NULL); + asm_call_metrics.blake2_duration += TimeDiff(asm_call_start, asm_call_stop); +#endif + return 0; +} + extern int _opcode_poseidon2(uint64_t address) { #ifdef ASM_CALL_METRICS diff --git a/emulator-asm/src/emu.hpp b/emulator-asm/src/emu.hpp index 7fe7cc080..cae983a9c 100644 --- a/emulator-asm/src/emu.hpp +++ b/emulator-asm/src/emu.hpp @@ -27,6 +27,9 @@ typedef struct { uint64_t sha256_counter; uint64_t sha256_duration; + uint64_t blake2_counter; + uint64_t blake2_duration; + uint64_t poseidon2_counter; uint64_t poseidon2_duration; diff --git a/lib-c/c/Makefile b/lib-c/c/Makefile index 905f37e69..97ad92fdd 100644 --- a/lib-c/c/Makefile +++ b/lib-c/c/Makefile @@ -47,7 +47,8 @@ CPP_OBJS = $(BUILD_DIR)/fecc.o \ $(BUILD_DIR)/add256.o \ $(BUILD_DIR)/globals.o \ $(BUILD_DIR)/goldilocks_base_field.o \ - $(BUILD_DIR)/poseidon2_goldilocks.o + $(BUILD_DIR)/poseidon2_goldilocks.o \ + $(BUILD_DIR)/blake2.o ALL_OBJS = $(ASM_OBJS) $(CPP_OBJS) @@ -167,6 +168,9 @@ $(BUILD_DIR)/goldilocks_base_field.o: $(SRC_DIR)/poseidon2/goldilocks_base_field $(BUILD_DIR)/poseidon2_goldilocks.o: $(SRC_DIR)/poseidon2/poseidon2_goldilocks.cpp $(SRC_DIR)/poseidon2/poseidon2_goldilocks.hpp $(SRC_DIR)/poseidon2/poseidon2_goldilocks_constants.hpp | $(BUILD_DIR) gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ +$(BUILD_DIR)/blake2.o: $(SRC_DIR)/blake2/blake2.cpp $(SRC_DIR)/blake2/blake2.hpp | $(BUILD_DIR) + gcc $(CFLAGS) $(INCLUDES) -c $< -o $@ + clean: rm -rf $(BUILD_DIR) rm -rf $(LIB_DIR) diff --git a/lib-c/c/src/blake2/blake2.cpp b/lib-c/c/src/blake2/blake2.cpp new file mode 100644 index 000000000..c9580b2bb --- /dev/null +++ b/lib-c/c/src/blake2/blake2.cpp @@ -0,0 +1,76 @@ +#include "blake2.hpp" +#include + +/// Message word permutation schedule +const size_t SIGMA[10][16] = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, +}; + +/// Rotation constants for G function +const uint32_t R1 = 32; +const uint32_t R2 = 24; +const uint32_t R3 = 16; +const uint32_t R4 = 63; + +// U64 rotate left and right functions +static inline uint64_t rotate_left_64(uint64_t x, unsigned int n) { + n &= 63; + return (x << n) | (x >> (64 - n)); +} +static inline uint64_t rotate_right_64(uint64_t x, unsigned int n) { + n &= 63; + return (x >> n) | (x << (64 - n)); +} + +/// G mixing function +/// +/// The G function mixes two input words `x` and `y` from the message block into the state. +/// It operates on 4 state words: v[a], v[b], v[c], v[d] +static inline void g(uint64_t v[16], size_t a, size_t b, size_t c, size_t d, uint64_t x, uint64_t y) { + uint64_t va = v[a]; + uint64_t vb = v[b]; + uint64_t vc = v[c]; + uint64_t vd = v[d]; + + va = va + vb + x; + vd = rotate_right_64(vd ^ va, R1); + vc = vc + vd; + vb = rotate_right_64(vb ^ vc, R2); + + va = va + vb + y; + vd = rotate_right_64(vd ^ va, R3); + vc = vc + vd; + vb = rotate_right_64(vb ^ vc, R4); + + v[a] = va; + v[b] = vb; + v[c] = vc; + v[d] = vd; +} + +/// BLAKE2b round function +void blake2b_round(uint64_t v[16], const uint64_t m[16], uint64_t round) { + // Message word selection permutation for this round + const size_t* s = SIGMA[round % 10]; + + // Column step + g(v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + g(v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + g(v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + g(v, 3, 7, 11, 15, m[s[6]], m[s[7]]); + + // Diagonal step + g(v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + g(v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + g(v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + g(v, 3, 4, 9, 14, m[s[14]], m[s[15]]); +} diff --git a/lib-c/c/src/blake2/blake2.hpp b/lib-c/c/src/blake2/blake2.hpp new file mode 100644 index 000000000..41d3f7b80 --- /dev/null +++ b/lib-c/c/src/blake2/blake2.hpp @@ -0,0 +1,16 @@ +#ifndef LIB_C_BLAKE2_HPP +#define LIB_C_BLAKE2_HPP + +#include // uint64_t + +#ifdef __cplusplus +extern "C" { +#endif + +void blake2b_round(uint64_t v[16], const uint64_t m[16], uint64_t round); + +#ifdef __cplusplus +} +#endif + +#endif // LIB_C_BLAKE2_HPP \ No newline at end of file diff --git a/ziskos/entrypoint/src/syscalls/blake2br.rs b/ziskos/entrypoint/src/syscalls/blake2br.rs index c8f0ab6cf..c8105e8e8 100644 --- a/ziskos/entrypoint/src/syscalls/blake2br.rs +++ b/ziskos/entrypoint/src/syscalls/blake2br.rs @@ -6,6 +6,9 @@ use core::arch::asm; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] use crate::ziskos_syscall; +#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] +use precompiles_helpers::blake2b_round; + #[derive(Debug)] #[repr(C)] pub struct SyscallBlake2bRoundParams<'a> { @@ -26,6 +29,11 @@ pub extern "C" fn syscall_blake2b_round( #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { - unimplemented!(); + blake2b_round(params.state, params.input, params.index as u32); + + #[cfg(feature = "hints")] + { + hints.extend_from_slice(params.state); + } } } From db3a950c600e842b806175b71378a0485d3ca518 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 25 Feb 2026 03:58:39 +0000 Subject: [PATCH 645/782] Fix clippy --- precompiles/blake2/src/blake2.rs | 5 ++--- ziskos/entrypoint/src/hints/blake2b.rs | 10 +++++----- ziskos/entrypoint/src/hints/mod.rs | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/precompiles/blake2/src/blake2.rs b/precompiles/blake2/src/blake2.rs index 09ba521a5..36bdbddbf 100644 --- a/precompiles/blake2/src/blake2.rs +++ b/precompiles/blake2/src/blake2.rs @@ -7,11 +7,10 @@ use rayon::prelude::*; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; -#[cfg(feature = "packed")] -use zisk_pil::{Blake2brTracePacked, Blake2brTraceRowPacked}; #[cfg(not(feature = "packed"))] use zisk_pil::{Blake2brTrace, Blake2brTraceRow}; - +#[cfg(feature = "packed")] +use zisk_pil::{Blake2brTracePacked, Blake2brTraceRowPacked}; #[cfg(feature = "packed")] type Blake2TraceRowType = Blake2brTraceRowPacked; #[cfg(feature = "packed")] diff --git a/ziskos/entrypoint/src/hints/blake2b.rs b/ziskos/entrypoint/src/hints/blake2b.rs index fce3eab5f..f17421ff5 100644 --- a/ziskos/entrypoint/src/hints/blake2b.rs +++ b/ziskos/entrypoint/src/hints/blake2b.rs @@ -1,7 +1,8 @@ -use crate::hints::{HINT_BUFFER, macros::{define_hint, register_hint_meta}}; -use zisk_common::{ - HINT_BLAKE2B_COMPRESS, +use crate::hints::{ + macros::{define_hint, register_hint_meta}, + HINT_BUFFER, }; +use zisk_common::HINT_BLAKE2B_COMPRESS; #[no_mangle] pub unsafe extern "C" fn hint_blake2b_compress( @@ -10,7 +11,6 @@ pub unsafe extern "C" fn hint_blake2b_compress( message: *const u64, offset: *const u64, final_block: u8, - #[cfg(feature = "hints")] hints: &mut Vec, ) { if !HINT_BUFFER.is_enabled() { return; @@ -36,4 +36,4 @@ pub unsafe extern "C" fn hint_blake2b_compress( w.commit(); } -register_hint_meta!(blake2b_compress, HINT_BLAKE2B_COMPRESS); \ No newline at end of file +register_hint_meta!(blake2b_compress, HINT_BLAKE2B_COMPRESS); diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 103687bea..1832b5ad0 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -28,10 +28,10 @@ use std::{ use tokio::sync::oneshot; use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; -#[cfg(zisk_hints_single_thread)] -use std::thread::ThreadId; #[cfg(zisk_hints_single_thread)] use std::sync::Mutex; +#[cfg(zisk_hints_single_thread)] +use std::thread::ThreadId; pub use blake2b::*; pub use bls12_381::*; From 56d3f4a1d5c261f3b7d0cb62d2c7f879fe74de49 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 25 Feb 2026 07:26:22 +0000 Subject: [PATCH 646/782] added hint blake2 handler --- common/src/hints.rs | 9 ++++++++ precompiles/hints/src/hints_processor.rs | 4 ++++ ziskos-hints/src/handlers/blake2b.rs | 22 ++++++++++++++++++++ ziskos-hints/src/handlers/mod.rs | 1 + ziskos/entrypoint/src/zisklib/lib/blake2b.rs | 2 +- 5 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 ziskos-hints/src/handlers/blake2b.rs diff --git a/common/src/hints.rs b/common/src/hints.rs index 4bb8f3c2c..ee6dcf271 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -188,6 +188,10 @@ pub enum BuiltInHint { // Keccak256 hint types. /// Compute Keccak-256 hash. Keccak256 = HINT_KECCAK256, + + // Blake2b hint types. + /// Blake2b compression function. + Blake2bCompress = HINT_BLAKE2B_COMPRESS, } impl Display for BuiltInHint { @@ -220,6 +224,8 @@ impl Display for BuiltInHint { BuiltInHint::VerifyKzgProof => "VERIFY_KZG_PROOF", // Keccak256 Hint BuiltInHint::Keccak256 => "KECCAK256", + // Blake2b Hint + BuiltInHint::Blake2bCompress => "BLAKE2B_COMPRESS", }; write!(f, "{} ({:#x})", name, *self as u32) @@ -344,6 +350,9 @@ impl HintCode { // Keccak256 Hint HintCode::BuiltIn(BuiltInHint::Keccak256) => HINT_KECCAK256, + // Blake2b Hint + HintCode::BuiltIn(BuiltInHint::Blake2bCompress) => HINT_BLAKE2B_COMPRESS, + // Custom Hints HintCode::Custom(code) => code, } diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 12f78fdae..bf9ef5e26 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -17,6 +17,7 @@ use zisk_common::{ BuiltInHint, CtrlHint, HintCode, PartialPrecompileHint, PrecompileHint, PrecompileHintParseResult, }; +use ziskos_hints::handlers::blake2b::blake2b_compress_hint; use ziskos_hints::handlers::bls381::{ bls12_381_fp2_to_g2_hint, bls12_381_fp_to_g1_hint, bls12_381_g1_add_hint, bls12_381_g1_msm_hint, bls12_381_g2_add_hint, bls12_381_g2_msm_hint, @@ -701,6 +702,9 @@ impl HintsProcessor { // Keccak256 Hint Codes BuiltInHint::Keccak256 => keccak256_hint(&data, data_len_bytes), + + // Blake2b Hint Codes + BuiltInHint::Blake2bCompress => blake2b_compress_hint(&data), } } diff --git a/ziskos-hints/src/handlers/blake2b.rs b/ziskos-hints/src/handlers/blake2b.rs new file mode 100644 index 000000000..f81fe72ff --- /dev/null +++ b/ziskos-hints/src/handlers/blake2b.rs @@ -0,0 +1,22 @@ +use crate::{handlers::validate_hint_length, hint_fields, zisklib}; + +use anyhow::Result; + +/// Processes an `HINT_BLAKE2B_COMPRESS` hint. +#[inline] +pub fn blake2b_compress_hint(data: &[u64]) -> Result> { + hint_fields![ROUNDS: 1, STATE: 8, MESSAGE: 16, OFFSET: 2, FINAL_BLOCK: 1]; + + validate_hint_length(data, EXPECTED_LEN, "HINT_BLAKE2B_COMPRESS")?; + + let rounds = data[ROUNDS_OFFSET] as u32; + let mut state: [u64; 8] = data[STATE_OFFSET..STATE_OFFSET + STATE_SIZE].try_into().unwrap(); + let message = data[MESSAGE_OFFSET..MESSAGE_OFFSET + MESSAGE_SIZE].try_into().unwrap(); + let offset = data[OFFSET_OFFSET..OFFSET_OFFSET + OFFSET_SIZE].try_into().unwrap(); + let final_block = data[FINAL_BLOCK_OFFSET] as u64 != 0; + + let mut hints = Vec::new(); + zisklib::blake2b_compress(rounds, &mut state, message, offset, final_block, &mut hints); + + Ok(hints) +} diff --git a/ziskos-hints/src/handlers/mod.rs b/ziskos-hints/src/handlers/mod.rs index 34e2feabe..6149bde03 100644 --- a/ziskos-hints/src/handlers/mod.rs +++ b/ziskos-hints/src/handlers/mod.rs @@ -1,3 +1,4 @@ +pub mod blake2b; pub mod bls381; pub mod bn254; pub mod keccak256; diff --git a/ziskos/entrypoint/src/zisklib/lib/blake2b.rs b/ziskos/entrypoint/src/zisklib/lib/blake2b.rs index c3a660376..c115a9381 100644 --- a/ziskos/entrypoint/src/zisklib/lib/blake2b.rs +++ b/ziskos/entrypoint/src/zisklib/lib/blake2b.rs @@ -12,7 +12,7 @@ const IV: [u64; 8] = [ 0x5BE0CD19137E2179, ]; -fn blake2b_compress( +pub fn blake2b_compress( rounds: u32, h: &mut [u64; 8], m: &[u64; 16], From 709299de7bc282995f1a7c6d63abbd75151fd1f0 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:26:33 +0000 Subject: [PATCH 647/782] fix try_from in hints.rs --- common/src/hints.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/src/hints.rs b/common/src/hints.rs index ee6dcf271..a8030f690 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -264,6 +264,8 @@ impl TryFrom for BuiltInHint { HINT_VERIFY_KZG_PROOF => Ok(Self::VerifyKzgProof), // Keccak256 Hint HINT_KECCAK256 => Ok(Self::Keccak256), + // Blake2b Hint + HINT_BLAKE2B_COMPRESS => Ok(Self::Blake2bCompress), _ => Err(anyhow::anyhow!("Invalid built-in hint code: {:#x}", value)), } } From e19c1139756b9402b8ed02a3ede7fd99a0fd7d3e Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 25 Feb 2026 11:11:52 +0100 Subject: [PATCH 648/782] fix some copilot comments, remove not used files --- cli/src/commands/verify_constraints.rs | 17 +- core/src/direct_calls.rs | 216 ------------------------- core/src/lib.rs | 2 - core/src/riscv2zisk_context.rs | 2 - emulator/src/regions_of_interest.rs | 2 +- precompiles/common/src/lib.rs | 4 +- precompiles/dma/src/dma_common.rs | 19 +-- 7 files changed, 19 insertions(+), 243 deletions(-) delete mode 100644 core/src/direct_calls.rs diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index fa3422ae9..2e94385aa 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -3,7 +3,6 @@ use anyhow::Result; use clap::Parser; use colored::Colorize; -use std::panic; use std::path::PathBuf; use tracing::warn; use zisk_build::ZISK_VERSION_MESSAGE; @@ -80,16 +79,16 @@ pub struct ZiskVerifyConstraints { impl ZiskVerifyConstraints { pub fn run(&mut self) -> Result<()> { - panic::set_hook(Box::new(|panic_info| { - eprintln!("\x1B[31mPANIC DETECTED"); - eprintln!("{} at {:?}", panic_info, panic_info.location()); + // panic::set_hook(Box::new(|panic_info| { + // eprintln!("\x1B[31mPANIC DETECTED"); + // eprintln!("{} at {:?}", panic_info, panic_info.location()); - // Backtrace - let bt = std::backtrace::Backtrace::force_capture(); - eprintln!("Backtrace:\n{}", bt); + // // Backtrace + // let bt = std::backtrace::Backtrace::force_capture(); + // eprintln!("Backtrace:\n{}", bt); - std::process::exit(101); - })); + // std::process::exit(101); + // })); // Check if the deprecated alias was used if std::env::args().any(|arg| arg == "--input") { diff --git a/core/src/direct_calls.rs b/core/src/direct_calls.rs deleted file mode 100644 index 0a5340622..000000000 --- a/core/src/direct_calls.rs +++ /dev/null @@ -1,216 +0,0 @@ -use riscv::RiscvInstruction; - -pub fn dma_direct_calls(riscv_instructions: &[RiscvInstruction], dma_addrs: (u64, u64, u64, u64)) { - let (memcpy_addr, memcmp_addr, memset_addr, memmove_addr) = dma_addrs; - - // Detect AUIPC + JALR patterns that jump to DMA functions - // Pattern: AUIPC rd, imm20 followed by JALR ra, rd, imm12 - // Target address = PC + (imm20 << 12) + imm12 - let mut dma_call_pcs: std::collections::HashSet = std::collections::HashSet::new(); - - // Register numbers for a0, a1, a2 (x10, x11, x12) - const REG_A0: u32 = 10; - const REG_A1: u32 = 11; - const REG_A2: u32 = 12; - - // Types of register loading - #[derive(Debug, Clone)] - enum RegLoadType { - /// Immediate value: li rd, imm or addi rd, x0, imm - Immediate(i32), - /// Register + immediate: addi rd, rs, imm or mv rd, rs (imm=0) - RegPlusImm { rs: u32, imm: i32 }, - /// Load from memory: ld/lw rd, imm(rs) - MemLoad { rs: u32, imm: i32, size: u8 }, - /// Not found - NotFound, - } - - /// Find how a register is loaded before a given instruction index - fn find_reg_load( - riscv_instructions: &[RiscvInstruction], - call_idx: usize, - target_reg: u32, - ) -> (usize, RegLoadType) { - // Search backwards from call_idx - for offset in 1..=20.min(call_idx) { - let idx = call_idx - offset; - let inst = &riscv_instructions[idx]; - - // Check if this instruction writes to target_reg - if inst.rd != target_reg { - continue; - } - - // li rd, imm -> actually addi rd, x0, imm or lui+addi - // addi rd, rs, imm - if inst.inst == "addi" || inst.inst == "c.addi" { - if inst.rs1 == 0 { - // li rd, imm (addi rd, x0, imm) - return (offset, RegLoadType::Immediate(inst.imm)); - } else { - // addi rd, rs, imm - return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: inst.imm }); - } - } - - // c.li rd, imm - if inst.inst == "c.li" { - return (offset, RegLoadType::Immediate(inst.imm)); - } - - // mv rd, rs -> addi rd, rs, 0 or c.mv - if inst.inst == "c.mv" { - return (offset, RegLoadType::RegPlusImm { rs: inst.rs2, imm: 0 }); - } - - // lui rd, imm (upper immediate) - if inst.inst == "lui" || inst.inst == "c.lui" { - return (offset, RegLoadType::Immediate(inst.imm)); - } - - // ld rd, imm(rs) - 64-bit load - if inst.inst == "ld" || inst.inst == "c.ld" || inst.inst == "c.ldsp" { - return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 8 }); - } - - // lw rd, imm(rs) - 32-bit load - if inst.inst == "lw" || inst.inst == "c.lw" || inst.inst == "c.lwsp" { - return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 4 }); - } - - // lwu rd, imm(rs) - 32-bit unsigned load - if inst.inst == "lwu" { - return (offset, RegLoadType::MemLoad { rs: inst.rs1, imm: inst.imm, size: 4 }); - } - - // add rd, rs1, rs2 (could be mv if rs2=0 or rs1=0) - if inst.inst == "add" || inst.inst == "c.add" { - if inst.rs1 == 0 { - return (offset, RegLoadType::RegPlusImm { rs: inst.rs2, imm: 0 }); - } else if inst.rs2 == 0 { - return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: 0 }); - } - // General add - treat as reg+imm with imm=0 (approximate) - return (offset, RegLoadType::RegPlusImm { rs: inst.rs1, imm: 0 }); - } - - // Other instruction that writes to target_reg but we don't recognize - // Stop searching since the register was overwritten - return (offset, RegLoadType::NotFound); - } - (0, RegLoadType::NotFound) - } - - fn reg_load_to_string(load: &RegLoadType) -> String { - match load { - RegLoadType::Immediate(imm) => format!("imm={}", imm), - RegLoadType::RegPlusImm { rs, imm } => format!("x{}+{}", rs, imm), - RegLoadType::MemLoad { rs, imm, size } => format!("{}(x{})[{}B]", imm, rs, size), - RegLoadType::NotFound => "???".to_string(), - } - } - - for i in 0..riscv_instructions.len().saturating_sub(1) { - let inst = &riscv_instructions[i]; - let next_inst = &riscv_instructions[i + 1]; - - // Check for AUIPC + JALR pattern - if inst.inst == "auipc" && next_inst.inst == "jalr" { - // AUIPC uses the same register that JALR reads from - if inst.rd == next_inst.rs1 { - // Calculate target address: PC + imm_auipc + imm_jalr - // Note: inst.imm for AUIPC is already shifted (has lower 12 bits as zero) - let target = (inst.rom_address as i64) - .wrapping_add(inst.imm as i64) - .wrapping_add(next_inst.imm as i64) as u64; - - // Check if target is a DMA function - let is_dma = (memcpy_addr != 0 && target == memcpy_addr) - || (memcmp_addr != 0 && target == memcmp_addr) - || (memset_addr != 0 && target == memset_addr) - || (memmove_addr != 0 && target == memmove_addr); - - if is_dma { - let func_name = if target == memcpy_addr { - "memcpy" - } else if target == memcmp_addr { - "memcmp" - } else if target == memset_addr { - "memset" - } else { - "inputcpy" - }; - - // Find how a0, a1, a2 are loaded (search from JALR position = i+1) - let jalr_idx = i + 1; - let (a0_offset, a0_load) = find_reg_load(riscv_instructions, jalr_idx, REG_A0); - let (a1_offset, a1_load) = find_reg_load(riscv_instructions, jalr_idx, REG_A1); - let (a2_offset, a2_load) = find_reg_load(riscv_instructions, jalr_idx, REG_A2); - - // Check if any register load was not found - let has_missing = matches!(a0_load, RegLoadType::NotFound) - || matches!(a1_load, RegLoadType::NotFound) - || matches!(a2_load, RegLoadType::NotFound); - - println!( - "DMA call to {} at PC=0x{:x}:{}", - func_name, - next_inst.rom_address, - if has_missing { " [INCOMPLETE - needs analysis]" } else { "" } - ); - println!(" a0: offset=-{}, {}", a0_offset, reg_load_to_string(&a0_load)); - println!(" a1: offset=-{}, {}", a1_offset, reg_load_to_string(&a1_load)); - println!(" a2: offset=-{}, {}", a2_offset, reg_load_to_string(&a2_load)); - - dma_call_pcs.insert(inst.rom_address); - dma_call_pcs.insert(next_inst.rom_address); - } - } - } - - // Check for JAL (direct jump) pattern - if inst.inst == "jal" { - let target = (inst.rom_address as i64).wrapping_add(inst.imm as i64) as u64; - - let is_dma = (memcpy_addr != 0 && target == memcpy_addr) - || (memcmp_addr != 0 && target == memcmp_addr) - || (memset_addr != 0 && target == memset_addr) - || (memmove_addr != 0 && target == memmove_addr); - - if is_dma { - let func_name = if target == memcpy_addr { - "memcpy" - } else if target == memcmp_addr { - "memcmp" - } else if target == memset_addr { - "memset" - } else { - "inputcpy" - }; - - // Find how a0, a1, a2 are loaded - let (a0_offset, a0_load) = find_reg_load(riscv_instructions, i, REG_A0); - let (a1_offset, a1_load) = find_reg_load(riscv_instructions, i, REG_A1); - let (a2_offset, a2_load) = find_reg_load(riscv_instructions, i, REG_A2); - - // Check if any register load was not found - let has_missing = matches!(a0_load, RegLoadType::NotFound) - || matches!(a1_load, RegLoadType::NotFound) - || matches!(a2_load, RegLoadType::NotFound); - - println!( - "DMA call to {} at PC=0x{:x} (JAL):{}", - func_name, - inst.rom_address, - if has_missing { " [INCOMPLETE - needs analysis]" } else { "" } - ); - println!(" a0: offset=-{}, {}", a0_offset, reg_load_to_string(&a0_load)); - println!(" a1: offset=-{}, {}", a1_offset, reg_load_to_string(&a1_load)); - println!(" a2: offset=-{}, {}", a2_offset, reg_load_to_string(&a2_load)); - - dma_call_pcs.insert(inst.rom_address); - } - } - } -} diff --git a/core/src/lib.rs b/core/src/lib.rs index 5682dcba5..f2904da87 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -51,7 +51,6 @@ //! //! The zisk_core crate contains basic structures and functionality used by several other modules: //! opcodes, instructions and transpilation -pub mod direct_calls; pub mod elf2rom; pub mod elf_extraction; pub mod fcall; @@ -70,7 +69,6 @@ pub mod zisk_required_operation; pub mod zisk_rom; pub mod zisk_rom_2_asm; -pub use direct_calls::*; pub use elf2rom::*; pub use fcall::*; pub use helpers::*; diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 5b5dcc383..dbf537e90 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -2056,8 +2056,6 @@ pub fn add_zisk_code(rom: &mut ZiskRom, addr: u64, data: &[u8], _dma_addrs: (u64 // Convert data vector to RISCV instructions let riscv_instructions = riscv_interpreter(addr, &code_vector); - // dma_direct_calls(&riscv_instructions, _dma_addrs); - // Create a context to convert RISCV instructions to ZisK instructions, using rom.insts let mut ctx = Riscv2ZiskContext { insts: &mut rom.insts, diff --git a/emulator/src/regions_of_interest.rs b/emulator/src/regions_of_interest.rs index 43cf2425f..e48745f1d 100644 --- a/emulator/src/regions_of_interest.rs +++ b/emulator/src/regions_of_interest.rs @@ -4,7 +4,7 @@ use std::io::{BufWriter, Write}; use crate::{get_ops_costs, StatsCosts, MAIN_COST}; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct CallerInfo { pub calls: usize, pub steps: usize, diff --git a/precompiles/common/src/lib.rs b/precompiles/common/src/lib.rs index 4cd7b3019..7b918f4d3 100644 --- a/precompiles/common/src/lib.rs +++ b/precompiles/common/src/lib.rs @@ -228,7 +228,7 @@ impl MemBusHelpers { } /// Generates multiple aligned memory double load operations from a slice of values. This function - /// is usefull for memcmp when are aligned because the words must be the same. At same time do dst + /// is useful for memcmp when are aligned because the words must be the same. At same time do dst /// and src read. The address must be 8-byte aligned. pub fn mem_double_aligned_read_from_slice( dst: u32, @@ -319,7 +319,7 @@ impl MemBusHelpers { } /// Generates aligned memory reads from an unaligned read slice using the specified source offset. - /// This function is usefull for memcmp, because at same time read src and dst like memcpy but only + /// This function is useful for memcmp, because at same time read src and dst like memcpy but only /// with reads. The number of dst reads generated is `values.len() - 1` because the last value is not /// enough to create a full 8-byte dst read. The address must be 8-byte aligned. pub fn mem_aligned_read_from_read_unaligned_slice( diff --git a/precompiles/dma/src/dma_common.rs b/precompiles/dma/src/dma_common.rs index eca6ed308..58219182f 100644 --- a/precompiles/dma/src/dma_common.rs +++ b/precompiles/dma/src/dma_common.rs @@ -93,17 +93,14 @@ where .collect() } (Some(f_idx), Some(l_idx)) if f_idx == l_idx => { - // Same vector is both first and last (edge case: huge operation) - // Just put it first (or last, doesn't matter) - if f_idx == 0 { - inputs.iter().flatten().collect() - } else { - std::iter::once(&inputs[f_idx]) - .chain(inputs[..f_idx].iter()) - .chain(inputs[f_idx + 1..].iter()) - .flatten() - .collect() - } + // Same vector is both first and last: all constrained inputs belong to one + // large ("huge") DMA operation that spans from its first to its last element. + // The only case in which this can happen is when there is a single collector and, + // therefore, the length of the collector’s input list is 1. Within a collector, + // the number of inputs does not necessarily have to be 1. + assert!(f_idx == 0); + assert!(inputs.len() == 1); + inputs.iter().flatten().collect() } (Some(f_idx), Some(l_idx)) if f_idx == 0 && l_idx == inputs.len() - 1 => { // Already in correct order From 4b8db0d4eb9db7d281409de0048f6d3878fd28be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 25 Feb 2026 11:50:48 +0000 Subject: [PATCH 649/782] Blake2b working --- core/src/riscv2zisk_context.rs | 2 +- core/src/zisk_ops.rs | 2 +- executor/src/utils.rs | 4 ++-- ziskos-hints/src/handlers/blake2b.rs | 2 +- ziskos/entrypoint/src/syscalls/blake2br.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/riscv2zisk_context.rs b/core/src/riscv2zisk_context.rs index 328e7a57a..429b8e913 100644 --- a/core/src/riscv2zisk_context.rs +++ b/core/src/riscv2zisk_context.rs @@ -15,7 +15,7 @@ use crate::{ use std::collections::HashMap; -// The CSR precompiled addresses are defined in the `ziskos/entrypoint/src/syscall.rs` file +// The CSR precompiled addresses are defined in the `definitions/src/syscall.rs` file // because legacy versions of Rust do not support constant parameters in `asm!` macros. // Important: The order should be the same as in such file. const CSR_PRECOMPILED: [&str; 26] = [ diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index ec7ca3abc..9e58d912e 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -23,7 +23,7 @@ use std::{ use tiny_keccak::keccakf; use crate::{ - sha256f, blake2br, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, + blake2br, sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, EXTRA_PARAMS_ADDR, M64, REG_A0, SYS_ADDR, }; diff --git a/executor/src/utils.rs b/executor/src/utils.rs index bb0f53d8f..441257183 100644 --- a/executor/src/utils.rs +++ b/executor/src/utils.rs @@ -25,13 +25,13 @@ use zisk_core::CHUNK_SIZE; use zisk_pil::PACKED_INFO; use zisk_pil::{ ADD_256_AIR_IDS, ARITH_AIR_IDS, ARITH_EQ_384_AIR_IDS, ARITH_EQ_AIR_IDS, BINARY_ADD_AIR_IDS, - BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, DMA_64_ALIGNED_AIR_IDS, + BINARY_AIR_IDS, BINARY_EXTENSION_AIR_IDS, BLAKE_2_BR_AIR_IDS, DMA_64_ALIGNED_AIR_IDS, DMA_64_ALIGNED_INPUT_CPY_AIR_IDS, DMA_64_ALIGNED_MEM_AIR_IDS, DMA_64_ALIGNED_MEM_CPY_AIR_IDS, DMA_64_ALIGNED_MEM_SET_AIR_IDS, DMA_AIR_IDS, DMA_INPUT_CPY_AIR_IDS, DMA_MEM_CPY_AIR_IDS, DMA_PRE_POST_AIR_IDS, DMA_PRE_POST_INPUT_CPY_AIR_IDS, DMA_PRE_POST_MEM_CPY_AIR_IDS, DMA_UNALIGNED_AIR_IDS, INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MEM_AIR_IDS, MEM_ALIGN_AIR_IDS, MEM_ALIGN_BYTE_AIR_IDS, MEM_ALIGN_READ_BYTE_AIR_IDS, MEM_ALIGN_WRITE_BYTE_AIR_IDS, - POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, BLAKE_2_BR_AIR_IDS + POSEIDON_2_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, SHA_256_F_AIR_IDS, ZISK_AIRGROUP_ID, }; use anyhow::Result; diff --git a/ziskos-hints/src/handlers/blake2b.rs b/ziskos-hints/src/handlers/blake2b.rs index f81fe72ff..7920334d5 100644 --- a/ziskos-hints/src/handlers/blake2b.rs +++ b/ziskos-hints/src/handlers/blake2b.rs @@ -13,7 +13,7 @@ pub fn blake2b_compress_hint(data: &[u64]) -> Result> { let mut state: [u64; 8] = data[STATE_OFFSET..STATE_OFFSET + STATE_SIZE].try_into().unwrap(); let message = data[MESSAGE_OFFSET..MESSAGE_OFFSET + MESSAGE_SIZE].try_into().unwrap(); let offset = data[OFFSET_OFFSET..OFFSET_OFFSET + OFFSET_SIZE].try_into().unwrap(); - let final_block = data[FINAL_BLOCK_OFFSET] as u64 != 0; + let final_block = data[FINAL_BLOCK_OFFSET] != 0; let mut hints = Vec::new(); zisklib::blake2b_compress(rounds, &mut state, message, offset, final_block, &mut hints); diff --git a/ziskos/entrypoint/src/syscalls/blake2br.rs b/ziskos/entrypoint/src/syscalls/blake2br.rs index c8105e8e8..8337cd897 100644 --- a/ziskos/entrypoint/src/syscalls/blake2br.rs +++ b/ziskos/entrypoint/src/syscalls/blake2br.rs @@ -25,7 +25,7 @@ pub extern "C" fn syscall_blake2b_round( #[cfg(feature = "hints")] hints: &mut Vec, ) { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] - ziskos_syscall!(0x817, params); + ziskos_syscall!(zisk_definitions::SYSCALL_BLAKE2B_ROUND_ID, params); #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] { From 13daf7bcd90c97fe9c357d1845d4946ecb72293d Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 25 Feb 2026 11:55:28 +0000 Subject: [PATCH 650/782] Fix check_man_thread --- ziskos/entrypoint/src/hints/blake2b.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ziskos/entrypoint/src/hints/blake2b.rs b/ziskos/entrypoint/src/hints/blake2b.rs index f17421ff5..a9c4e66b1 100644 --- a/ziskos/entrypoint/src/hints/blake2b.rs +++ b/ziskos/entrypoint/src/hints/blake2b.rs @@ -1,7 +1,4 @@ -use crate::hints::{ - macros::{define_hint, register_hint_meta}, - HINT_BUFFER, -}; +use crate::hints::{macros::register_hint_meta, HINT_BUFFER}; use zisk_common::HINT_BLAKE2B_COMPRESS; #[no_mangle] @@ -17,7 +14,9 @@ pub unsafe extern "C" fn hint_blake2b_compress( } #[cfg(zisk_hints_single_thread)] - crate::hints::check_main_thread(); + if !check_main_thread() { + return; + } let total_len = 8 + 64 + 128 + 16 + 8; // rounds + state + message + offset + final_block From 74a7283c3f04d0a7e4e9952543b94e9342b1487f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:31:31 +0000 Subject: [PATCH 651/782] Address review comments: fix panic messages, fix skip logic, optimize memory usage, fix docs Co-authored-by: hecmas <68732820+hecmas@users.noreply.github.com> --- precompiles/blake2/src/blake2.rs | 43 ++++++++++++------- .../blake2/src/blake2_gen_mem_inputs.rs | 29 +++++++------ precompiles/blake2/src/blake2_instance.rs | 2 +- precompiles/blake2/src/blake2_manager.rs | 2 +- ziskos/entrypoint/src/zisklib/lib/blake2b.rs | 5 ++- 5 files changed, 49 insertions(+), 32 deletions(-) diff --git a/precompiles/blake2/src/blake2.rs b/precompiles/blake2/src/blake2.rs index 36bdbddbf..07cb84b71 100644 --- a/precompiles/blake2/src/blake2.rs +++ b/precompiles/blake2/src/blake2.rs @@ -347,24 +347,35 @@ impl Blake2SM { } } - // Fill the trace and collect range checks - let range_checks_vec: Vec<[u32; 65536]> = par_traces + // Fill the trace and aggregate range checks in parallel without + // materializing one large Vec<[u32; 65536]>. + let range_checks_array: [u32; 65536] = par_traces .into_par_iter() .enumerate() - .map(|(index, trace)| { - let input_index = inputs_indexes[index]; - let input = &inputs[input_index.0][input_index.1]; - self.process_input(input, trace) - }) - .collect(); - - // Aggregate all range checks - let mut range_checks = vec![0; 65536]; - for rc in range_checks_vec { - for i in 0..65536 { - range_checks[i] += rc[i]; - } - } + .fold( + || [0u32; 65536], + |mut acc, (index, trace)| { + let input_index = inputs_indexes[index]; + let input = &inputs[input_index.0][input_index.1]; + let rc = self.process_input(input, trace); + for i in 0..65536 { + acc[i] += rc[i]; + } + acc + }, + ) + .reduce( + || [0u32; 65536], + |mut a, b| { + for i in 0..65536 { + a[i] += b[i]; + } + a + }, + ); + + // Convert aggregated array into Vec for downstream use. + let mut range_checks = Vec::from(range_checks_array); timer_stop_and_log_trace!(BLAKE2_TRACE); diff --git a/precompiles/blake2/src/blake2_gen_mem_inputs.rs b/precompiles/blake2/src/blake2_gen_mem_inputs.rs index a24bc7004..0028eb5a4 100644 --- a/precompiles/blake2/src/blake2_gen_mem_inputs.rs +++ b/precompiles/blake2/src/blake2_gen_mem_inputs.rs @@ -78,29 +78,34 @@ pub fn skip_blake2_mem_inputs( data: &[u64], mem_processors: &mut P, ) -> bool { - let indirect_params = 2; - let read_params = 3; - let write_params = 1; - let chunks_per_param = [1usize, 16, 16, 16]; - - for iparam in 0..indirect_params { + // Check all PARAMS words at addr_main (index, addr_state, addr_input) + for iparam in 0..PARAMS { let addr = addr_main + iparam as u32 * 8; if !mem_processors.skip_addr(addr) { return false; } } - for (iparam, &chunks) in chunks_per_param.iter().enumerate().take(read_params + write_params) { - let is_write = iparam >= read_params; - let param_index = if is_write { iparam - read_params } else { iparam }; - let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_index] as u32; - - for ichunk in 0..chunks { + // Check READ_PARAMS arrays (state and input, each PARAM_CHUNKS u64s) + for iparam in 0..READ_PARAMS { + let param_idx = iparam + 1; + let param_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + param_idx] as u32; + for ichunk in 0..PARAM_CHUNKS { let addr = param_addr + ichunk as u32 * 8; if !mem_processors.skip_addr(addr) { return false; } } } + + // Check write address (output state array) + let write_addr = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE + DIRECT_READ_PARAMS] as u32; + for ichunk in 0..PARAM_CHUNKS { + let addr = write_addr + ichunk as u32 * 8; + if !mem_processors.skip_addr(addr) { + return false; + } + } + true } diff --git a/precompiles/blake2/src/blake2_instance.rs b/precompiles/blake2/src/blake2_instance.rs index 1d63d0adc..827f74bb8 100644 --- a/precompiles/blake2/src/blake2_instance.rs +++ b/precompiles/blake2/src/blake2_instance.rs @@ -181,7 +181,7 @@ impl Blake2Collector { if let ExtOperationData::OperationBlake2Data(data) = data { self.inputs.push(Blake2Input::from(&data)); } else { - panic!("Expected ExtOperationData::OperationData"); + panic!("Expected ExtOperationData::OperationBlake2Data"); } self.inputs.len() < self.num_operations as usize diff --git a/precompiles/blake2/src/blake2_manager.rs b/precompiles/blake2/src/blake2_manager.rs index c67863bce..18d073dee 100644 --- a/precompiles/blake2/src/blake2_manager.rs +++ b/precompiles/blake2/src/blake2_manager.rs @@ -73,7 +73,7 @@ impl ComponentBuilder for Blake2Manager { Box::new(Blake2Instance::new(self.blake2_sm.clone(), ictx)) } _ => { - panic!("Blake2Builder::get_instance() Unsupported air_id: {:?}", ictx.plan.air_id) + panic!("Blake2Manager::build_instance() Unsupported air_id: {:?}", ictx.plan.air_id) } } } diff --git a/ziskos/entrypoint/src/zisklib/lib/blake2b.rs b/ziskos/entrypoint/src/zisklib/lib/blake2b.rs index c115a9381..9d6b66074 100644 --- a/ziskos/entrypoint/src/zisklib/lib/blake2b.rs +++ b/ziskos/entrypoint/src/zisklib/lib/blake2b.rs @@ -64,8 +64,9 @@ fn blake2b_round( /// C-compatible wrapper for full Blake2b compression function /// /// # Safety -/// - `input` must point to at least `input_len` bytes -/// - `output` must point to a writable buffer of at least 32 bytes +/// - `state` must point to a writable buffer of at least 8 `u64`s +/// - `message` must point to at least 16 `u64`s +/// - `offset` must point to at least 2 `u64`s #[cfg_attr(not(feature = "hints"), no_mangle)] #[cfg_attr(feature = "hints", export_name = "hints_blake2b_compress_c")] pub unsafe extern "C" fn blake2b_compress_c( From 3a227ccfc0697708a182fe1d4d276aee7f3d8e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Masip=20Ardevol?= <68732820+hecmas@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:36:46 +0000 Subject: [PATCH 652/782] Reverting vector change --- precompiles/blake2/src/blake2.rs | 43 ++++++++++++-------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/precompiles/blake2/src/blake2.rs b/precompiles/blake2/src/blake2.rs index 07cb84b71..36bdbddbf 100644 --- a/precompiles/blake2/src/blake2.rs +++ b/precompiles/blake2/src/blake2.rs @@ -347,35 +347,24 @@ impl Blake2SM { } } - // Fill the trace and aggregate range checks in parallel without - // materializing one large Vec<[u32; 65536]>. - let range_checks_array: [u32; 65536] = par_traces + // Fill the trace and collect range checks + let range_checks_vec: Vec<[u32; 65536]> = par_traces .into_par_iter() .enumerate() - .fold( - || [0u32; 65536], - |mut acc, (index, trace)| { - let input_index = inputs_indexes[index]; - let input = &inputs[input_index.0][input_index.1]; - let rc = self.process_input(input, trace); - for i in 0..65536 { - acc[i] += rc[i]; - } - acc - }, - ) - .reduce( - || [0u32; 65536], - |mut a, b| { - for i in 0..65536 { - a[i] += b[i]; - } - a - }, - ); - - // Convert aggregated array into Vec for downstream use. - let mut range_checks = Vec::from(range_checks_array); + .map(|(index, trace)| { + let input_index = inputs_indexes[index]; + let input = &inputs[input_index.0][input_index.1]; + self.process_input(input, trace) + }) + .collect(); + + // Aggregate all range checks + let mut range_checks = vec![0; 65536]; + for rc in range_checks_vec { + for i in 0..65536 { + range_checks[i] += rc[i]; + } + } timer_stop_and_log_trace!(BLAKE2_TRACE); From 37d3886fef3273031ed43ce062add379d6f62611 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Thu, 26 Feb 2026 12:22:55 +0100 Subject: [PATCH 653/782] fix problem with wc of dma --- Cargo.lock | 22 ++-- pil/src/pil_helpers/traces.rs | 2 +- precompiles/dma/pil/dma_64_aligned.pil | 48 ++++++-- .../dma/src/dma_64_aligned/dma_64_aligned.rs | 106 +++++++++++++----- .../dma_64_aligned/dma_64_aligned_input.rs | 4 - .../dma_64_aligned/dma_64_aligned_inputcpy.rs | 10 +- .../src/dma_64_aligned/dma_64_aligned_mem.rs | 54 +++++---- .../dma_64_aligned/dma_64_aligned_memcpy.rs | 12 +- .../dma_64_aligned/dma_64_aligned_memset.rs | 28 +++-- 9 files changed, 182 insertions(+), 104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4f61b7c7..8c3696895 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1116,7 +1116,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "fields", "num-bigint", @@ -1470,7 +1470,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "cfg-if", "num-bigint", @@ -2774,7 +2774,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "colored", "fields", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "bincode", "blake3", @@ -3192,7 +3192,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "bincode", "borsh", @@ -3224,7 +3224,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "fields", "itoa", @@ -3237,7 +3237,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "proc-macro2", "quote", @@ -3247,7 +3247,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "crossbeam-channel", "tracing", @@ -3256,7 +3256,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "bincode", "bytemuck", @@ -3268,7 +3268,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "bytemuck", "fields", @@ -5743,7 +5743,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#e80c90c73c94fdbe6353370a54db7ed4b0cdb87e" dependencies = [ "colored", "fields", diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index 85d6e165f..a6625dd03 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "8974008e4b3256f291a870e441a2b9657b4bcf255884d6dd0a084a430436a715"; +pub const PILOUT_HASH: &str = "b86dab0c9c7c324ad5667e56c7eb399b53ddd1f86efae71242f5be294cfdb812"; pub const MERKLE_TREE_ARITY: u64 = 4; diff --git a/precompiles/dma/pil/dma_64_aligned.pil b/precompiles/dma/pil/dma_64_aligned.pil index 3a2f9c309..ccd63598b 100644 --- a/precompiles/dma/pil/dma_64_aligned.pil +++ b/precompiles/dma/pil/dma_64_aligned.pil @@ -1,6 +1,22 @@ -// The Dma64Aligned state-machine is used to verify full aligned 64-bit operations, for operations -// that use a mem_read - +// The Dma64Aligned state-machine is used to verify full aligned 64-bit operations. +// +// This machine can be configured to enable each specific command individually, as well as to +// define the number of operations per row. Depending on the type of machine, it makes sense to +// support up to 8 ops. +// +// This machine can also be configured to support "direct operations". These are operations that +// do not require the involvement of the DMA controller, nor the pre or post dma operations. +// +// For an operation to be direct, its addresses must be aligned and its count must be a multiple of 8. +// On the other hand, this does not apply to memcmp, since it requires DMA in order to demonstrate +// the relationship between the count, the effective count, and the result of the operation. +// +// This machine has continuations, since an operation can span multiple rows. Each instantiation of +// this airtemplate generates a different air with an unique continuation ID, because they must not +// be mixed. +// +// This implies that an operation that has been launched for a specific AIR (airtemplate instantiation +// with a particular configuration) must be completed within the same AIR. airtemplate Dma64Aligned(int N = 2**21, // Rows of instance const int RC = 2, // Number of chunks for native value @@ -129,6 +145,7 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance airval is_last_segment; // 1 if this is the last segment, 0 otherwise. is_last_segment * (1 - is_last_segment) === 0; + // the inputcpy or memset doesn't has source, they don't use it if (has_src) { col witness bits(ADDR_W_BITS) air.src64; @@ -139,9 +156,10 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance const int air.segment_last_src64 = 0; } + // This witness is used to mark the last row of an onperation. col witness bits(1) seq_end; seq_end * (1 - seq_end) === 0; - + col witness bits(1) previous_seq_end; previous_seq_end <== L1 * (segment_previous_seq_end - 'seq_end) + 'seq_end; @@ -390,9 +408,20 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance for (int i = 0; i < op_x_row; i++) { value[i][0] = l_value_chunks[i][0] + h_value_chunks[i][0] * P2_8; value[i][1] = l_value_chunks[i][1] + h_value_chunks[i][1] * P2_8; - range_check(h_value_chunks[i][0], 0, P2_24-1, sel_op[i]); - range_check(h_value_chunks[i][1], 0, P2_24-1, sel_op[i]); - range_dual_byte(l_value_chunks[i][1], l_value_chunks[i][0], sel_op[i]); + + // PERFORMANCE OPTIMIZATION: In this instance it's a non-specific instance, only use + // this range checks with the input it's select, to avoid "compute" this + // range check in other situation where it didn't used. + + if (enables_count >= 2) { + range_check(h_value_chunks[i][0], 0, P2_24-1, sel_inputcpy); + range_check(h_value_chunks[i][1], 0, P2_24-1, sel_inputcpy); + range_dual_byte(l_value_chunks[i][1], l_value_chunks[i][0], sel_inputcpy); + } else { + range_check(h_value_chunks[i][0], 0, P2_24-1, sel_op[i]); + range_check(h_value_chunks[i][1], 0, P2_24-1, sel_op[i]); + range_dual_byte(l_value_chunks[i][1], l_value_chunks[i][0], sel_op[i]); + } } } else if (has_src) { col witness bits(32) air.value[op_x_row][RC]; @@ -435,8 +464,8 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance // add constraint to force that memset operation value "read" must be fill word // only if this instance isn't a dedicated memset instance. In case of dedicated // instance value really was directly fill_dword. - sel_memset * (value[i][0] - fill_dword) === 0; - sel_memset * (value[i][1] - fill_dword) === 0; + sel_op[i] * sel_memset * (value[i][0] - fill_dword) === 0; + sel_op[i] * sel_memset * (value[i][1] - fill_dword) === 0; } // load previous value before write operation, because only some bytes must be written @@ -618,5 +647,4 @@ airtemplate Dma64Aligned(int N = 2**21, // Rows of instance // // Analyze if it's possible that the equal memcmp could be done directly by Dma64Aligned without // using controller, pre or post. - } \ No newline at end of file diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs index 961c894bd..8fd0a6482 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned.rs @@ -7,7 +7,7 @@ use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; use zisk_common::SegmentId; use zisk_core::zisk_ops::ZiskOp; -use zisk_pil::Dma64AlignedAirValues; +use zisk_pil::{Dma64AlignedAirValues, DUAL_RANGE_BYTE_ID}; #[cfg(feature = "packed")] pub use zisk_pil::{ @@ -31,6 +31,9 @@ pub struct Dma64AlignedSM { /// Range checks ID's range_16_bits_id: usize, + range_24_bits_id: usize, + dual_range_byte_id: usize, + op_x_rows: usize, } @@ -45,6 +48,12 @@ impl Dma64AlignedSM { range_16_bits_id: std .get_range_id(0, 0xFFFF, None) .expect("Failed to get 16b table ID"), + range_24_bits_id: std + .get_range_id(0, (1 << 24) - 1, None) + .expect("Failed to get 24b table ID"), + dual_range_byte_id: std + .get_virtual_table_id(DUAL_RANGE_BYTE_ID) + .expect("Failed to get tabl eDUAL_RANGE_BYTE ID ID"), op_x_rows: DMA_64_ALIGNED_OPS_BY_ROW, }) } @@ -59,7 +68,9 @@ impl Dma64AlignedSM { &self, input: &Dma64AlignedInput, trace: &mut [Dma64AlignedTraceRow], - _local_16_bits_table: &mut [u32], // for input_cpy + dual_byte_range_check_values: &mut Vec, + range_check_24b_values: &mut Vec, + range_check_non_used_ops: &mut u64, air_values: &mut Dma64AlignedAirValues, ) -> usize { let rows = input.rows as usize; @@ -81,7 +92,7 @@ impl Dma64AlignedSM { let is_memeq = input.op == ZiskOp::DMA_MEMCMP || input.op == ZiskOp::DMA_XMEMCMP; let is_memset = input.op == ZiskOp::DMA_XMEMSET; let is_inputcpy = input.op == ZiskOp::DMA_INPUTCPY; - let fill_byte = if is_memset { (input.encoded & 0xFF) as u8 } else { 0 }; + let fill_byte = if is_memset { DmaInfo::get_fill_byte(input.encoded) } else { 0 }; for (irow, row) in trace.iter_mut().enumerate().take(rows) { row.set_main_step(input.step); @@ -92,7 +103,6 @@ impl Dma64AlignedSM { if irow == 0 && input.skip_rows == 0 { row.set_sel_memcpy_count_load(input.op == ZiskOp::DMA_MEMCPY); } - row.set_fill_byte(fill_byte); row.set_previous_seq_end(irow == 0 && input.skip_rows == 0); // calculate the first aligned address @@ -105,31 +115,48 @@ impl Dma64AlignedSM { row.set_count64(count64 as u32); let use_count = if count64 <= self.op_x_rows { seq_end = true; - for index in count64..self.op_x_rows { - if index > 0 { - row.set_sel_op_from_1(index - 1, false); - } - row.set_h_value_chunks(index, 0, 0); - row.set_h_value_chunks(index, 1, 0); - row.set_l_value_chunks(index, 0, 0); - row.set_l_value_chunks(index, 1, 0); - } count64 } else { count64 -= self.op_x_rows; self.op_x_rows }; row.set_seq_end(seq_end); - for index in 0..use_count { - if index > 0 { - row.set_sel_op_from_1(index - 1, true); + if !is_memset { + for index in 0..use_count { + if index > 0 { + row.set_sel_op_from_1(index - 1, true); + } + let value = input.src_values[src_values_index]; + src_values_index += 1; + let h0 = ((value >> 8) & 0xFFFFFF) as u32; + let h1 = ((value >> 40) & 0xFFFFFF) as u32; + let l0 = value as u8; + let l1 = (value >> 32) as u8; + row.set_h_value_chunks(index, 0, h0); + row.set_h_value_chunks(index, 1, h1); + row.set_l_value_chunks(index, 0, l0); + row.set_l_value_chunks(index, 1, l1); + if is_inputcpy { + dual_byte_range_check_values.push(l0 as u16 + ((l1 as u16) << 8)); + range_check_24b_values.push(h0); + range_check_24b_values.push(h1); + } + } + if is_inputcpy && use_count < self.op_x_rows { + *range_check_non_used_ops += (self.op_x_rows - use_count) as u64; + } + } else { + let fill_bytes = fill_byte as u32 * 0x010101; + row.set_fill_byte(fill_byte); + for index in 0..use_count { + if index > 0 { + row.set_sel_op_from_1(index - 1, true); + } + row.set_h_value_chunks(index, 0, fill_bytes); + row.set_h_value_chunks(index, 1, fill_bytes); + row.set_l_value_chunks(index, 0, fill_byte); + row.set_l_value_chunks(index, 1, fill_byte); } - let value = input.src_values[src_values_index]; - src_values_index += 1; - row.set_h_value_chunks(index, 0, (value >> 8) as u32); - row.set_h_value_chunks(index, 1, (value >> 40) as u32); - row.set_l_value_chunks(index, 0, value as u8); - row.set_l_value_chunks(index, 1, (value >> 32) as u8); } } @@ -143,6 +170,7 @@ impl Dma64AlignedSM { air_values.last_count_chunk[0] = F::ZERO; air_values.last_count_chunk[1] = F::ZERO; air_values.segment_last_flags = F::ZERO; + air_values.segment_last_fill_byte = F::ZERO; } else { air_values.segment_last_seq_end = F::ZERO; air_values.segment_last_src64 = F::from_u32(src64 - addr_incr_by_row); @@ -159,6 +187,7 @@ impl Dma64AlignedSM { ZiskOp::DMA_XMEMSET => F_SEL_MEMSET, _ => panic!("Invalid operation 0x{:02X}", input.op), } as u16); + air_values.segment_last_fill_byte = F::from_u8(fill_byte); } } rows @@ -195,7 +224,7 @@ impl Dma64AlignedModule for Dma64AlignedSM { is_last_segment: bool, trace_buffer: Vec, ) -> ProofmanResult> { - let mut trace = Dma64AlignedTrace::::new_from_vec(trace_buffer)?; + let mut trace = Dma64AlignedTrace::::new_from_vec_zeroes(trace_buffer)?; let num_rows = trace.num_rows(); let total_inputs: usize = inputs @@ -219,23 +248,28 @@ impl Dma64AlignedModule for Dma64AlignedSM { let flat_inputs = crate::flatten_and_reorder_inputs(inputs); let trace_rows = trace.buffer.as_mut_slice(); - let mut local_16_bits_table = vec![0u32; 1 << 16]; let mut air_values = Dma64AlignedAirValues::::new(); - + let mut dual_byte_range_check_values = Vec::new(); + let mut range_check_24b_values = Vec::new(); + let mut range_check_non_used_ops = 0u64; // TODO: inputs between instances let mut row_offset = 0; for input in flat_inputs.iter() { let rows_used = self.process_input( input, &mut trace_rows[row_offset..], - &mut local_16_bits_table, + &mut dual_byte_range_check_values, + &mut range_check_24b_values, + &mut range_check_non_used_ops, &mut air_values, ); row_offset += rows_used; } // padding - air_values.padding_size = F::from_u32((num_rows - row_offset) as u32); + let padding_size = num_rows.saturating_sub(row_offset); + air_values.padding_size = F::from_u32(padding_size as u32); + for padding_row in trace_rows.iter_mut().take(num_rows).skip(row_offset) { self.process_empty_slice(padding_row); } @@ -248,14 +282,24 @@ impl Dma64AlignedModule for Dma64AlignedSM { air_values.last_count_chunk[0] = F::ZERO; air_values.last_count_chunk[1] = F::ZERO; air_values.segment_last_flags = F::ZERO; + air_values.segment_last_fill_byte = F::ZERO; } // add range check of count to check that it's a positive 32-bits number let last_count = air_values.segment_last_count64.as_canonical_u64(); - local_16_bits_table[(last_count & 0xFFFF) as usize] += 1; - local_16_bits_table[((last_count >> 16) & 0xFFFF) as usize] += 1; + self.std.range_check(self.range_16_bits_id, (last_count & 0xFFFF) as i64, 1); + self.std.range_check(self.range_16_bits_id, ((last_count >> 16) & 0xFFFF) as i64, 1); - self.std.range_checks(self.range_16_bits_id, local_16_bits_table); + // range check of 24 must be multiplied by 2 because there are two values, but dual range check + // it's dual, no need to multiply by 2. + self.std.inc_virtual_row(self.dual_range_byte_id, 0u64, range_check_non_used_ops); + self.std.range_check(self.range_24_bits_id, 0, range_check_non_used_ops * 2); + for value in dual_byte_range_check_values { + self.std.inc_virtual_row(self.dual_range_byte_id, value as u64, 1); + } + for value in range_check_24b_values { + self.std.range_check(self.range_24_bits_id, value as i64, 1); + } let segment_id = segment_id.into(); air_values.segment_id = F::from_usize(segment_id); @@ -269,6 +313,7 @@ impl Dma64AlignedModule for Dma64AlignedSM { air_values.segment_previous_main_step = F::ZERO; air_values.segment_previous_count64 = F::ZERO; air_values.segment_previous_flags = F::ZERO; + air_values.segment_previous_fill_byte = F::ZERO; } else { assert!(segment_id > 0); air_values.segment_previous_seq_end = F::ZERO; @@ -286,6 +331,7 @@ impl Dma64AlignedModule for Dma64AlignedSM { ZiskOp::DMA_XMEMSET => F_SEL_MEMSET, _ => panic!("Invalid operation 0x{:02X}", first_input.op), } as u16); + air_values.segment_previous_fill_byte = F::from_u8(trace_rows[0].get_fill_byte()); } #[cfg(feature = "debug_dma")] diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs index 19c1e0651..4deab6413 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_input.rs @@ -16,7 +16,6 @@ pub struct Dma64AlignedInput { pub step: u64, pub encoded: u64, pub src_values: Vec, - pub fill_byte: u8, } impl Dma64AlignedInput { @@ -77,7 +76,6 @@ impl Dma64AlignedInput { } _ => op, }, - fill_byte: 0, } } pub fn from_memset( @@ -103,7 +101,6 @@ impl Dma64AlignedInput { encoded, src_values: vec![], op, - fill_byte: DmaInfo::get_fill_byte(encoded), } } pub fn from_memcmp( @@ -144,7 +141,6 @@ impl Dma64AlignedInput { } _ => op, }, - fill_byte: 0, } } diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs index b5335c612..58be44c72 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs @@ -213,16 +213,14 @@ impl Dma64AlignedModule for Dma64AlignedInputCpySM { row_offset += rows_used; } + // padding let padding_size = num_rows.saturating_sub(row_offset); air_values.padding_size = F::from_u32(padding_size as u32); - // padding if padding_size > 0 { - self.process_empty_slice(&mut trace_rows[row_offset]); - let empty_row = trace_rows[row_offset]; - trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { - *row = empty_row; - }); + for padding_row in trace_rows.iter_mut().take(num_rows).skip(row_offset) { + self.process_empty_slice(padding_row); + } air_values.segment_last_seq_end = F::ONE; air_values.segment_last_dst64 = F::ZERO; air_values.segment_last_main_step = F::ZERO; diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs index c4e2bdce2..b1f581647 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs @@ -78,10 +78,10 @@ impl Dma64AlignedMemSM { let mut src64 = ((input.src + 7) >> 3) + skip_count as u32; let mut seq_end = false; let addr_incr_by_row = self.op_x_rows as u32; - let is_memcpy = input.op == ZiskOp::DMA_XMEMCPY; + let is_memcpy = input.op == ZiskOp::DMA_XMEMCPY || input.op == ZiskOp::DMA_MEMCPY; let is_memeq = input.op == ZiskOp::DMA_MEMCMP || input.op == ZiskOp::DMA_XMEMCMP; let is_memset = input.op == ZiskOp::DMA_XMEMSET; - let fill_byte = if is_memset { (input.encoded & 0xFF) as u8 } else { 0 }; + let fill_byte = if is_memset { DmaInfo::get_fill_byte(input.encoded) } else { 0 }; for (irow, row) in trace.iter_mut().enumerate().take(rows) { row.set_main_step(input.step); @@ -91,7 +91,6 @@ impl Dma64AlignedMemSM { row.set_sel_memcpy_count_load(input.op == ZiskOp::DMA_MEMCPY); } row.set_sel_memset(is_memset); - row.set_fill_byte(fill_byte); row.set_previous_seq_end(irow == 0 && input.skip_rows == 0); // calculate the first aligned address @@ -104,25 +103,32 @@ impl Dma64AlignedMemSM { row.set_count64(count64 as u32); let use_count = if count64 <= self.op_x_rows { seq_end = true; - for index in count64..self.op_x_rows { - if index > 0 { - row.set_sel_op_from_1(index - 1, false); - } - } count64 } else { count64 -= self.op_x_rows; self.op_x_rows }; row.set_seq_end(seq_end); - for index in 0..use_count { - if index > 0 { - row.set_sel_op_from_1(index - 1, true); + if !is_memset { + for index in 0..use_count { + if index > 0 { + row.set_sel_op_from_1(index - 1, true); + } + let value = input.src_values[src_values_index]; + row.set_value(index, 0, value as u32); + row.set_value(index, 1, (value >> 32) as u32); + src_values_index += 1; + } + } else { + let fill_bytes = fill_byte as u32 * 0x01010101; + row.set_fill_byte(fill_byte); + for index in 0..use_count { + if index > 0 { + row.set_sel_op_from_1(index - 1, true); + } + row.set_value(index, 0, fill_bytes); + row.set_value(index, 1, fill_bytes); } - let value = input.src_values[src_values_index]; - row.set_value(index, 0, value as u32); - row.set_value(index, 1, (value >> 32) as u32); - src_values_index += 1; } } @@ -136,6 +142,7 @@ impl Dma64AlignedMemSM { air_values.last_count_chunk[0] = F::ZERO; air_values.last_count_chunk[1] = F::ZERO; air_values.segment_last_flags = F::ZERO; + air_values.segment_last_fill_byte = F::ZERO; } else { air_values.segment_last_seq_end = F::ZERO; air_values.segment_last_src64 = F::from_u32(src64 - addr_incr_by_row); @@ -152,6 +159,7 @@ impl Dma64AlignedMemSM { ZiskOp::DMA_XMEMSET => F_SEL_MEMSET, _ => panic!("Invalid operation 0x{:02X}", input.op), } as u16); + air_values.segment_last_fill_byte = F::from_u8(fill_byte); } } rows @@ -227,13 +235,14 @@ impl Dma64AlignedModule for Dma64AlignedMemSM { } // padding + let padding_size = num_rows.saturating_sub(row_offset); + air_values.padding_size = F::from_u32(padding_size as u32); + if row_offset < num_rows { - air_values.padding_size = F::from_u32((num_rows - row_offset) as u32); - self.process_empty_slice(&mut trace_rows[row_offset]); - let empty_row = trace_rows[row_offset]; - trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { - *row = empty_row; - }); + for padding_row in trace_rows.iter_mut().take(num_rows).skip(row_offset) { + self.process_empty_slice(padding_row); + } + air_values.segment_last_seq_end = F::ONE; air_values.segment_last_src64 = F::ZERO; air_values.segment_last_dst64 = F::ZERO; @@ -242,6 +251,7 @@ impl Dma64AlignedModule for Dma64AlignedMemSM { air_values.last_count_chunk[0] = F::ZERO; air_values.last_count_chunk[1] = F::ZERO; air_values.segment_last_flags = F::ZERO; + air_values.segment_last_fill_byte = F::ZERO; } // add range check of count to check that it's a positive 32-bits number @@ -263,6 +273,7 @@ impl Dma64AlignedModule for Dma64AlignedMemSM { air_values.segment_previous_main_step = F::ZERO; air_values.segment_previous_count64 = F::ZERO; air_values.segment_previous_flags = F::ZERO; + air_values.segment_previous_fill_byte = F::ZERO; } else { assert!(segment_id > 0); air_values.segment_previous_seq_end = F::ZERO; @@ -280,6 +291,7 @@ impl Dma64AlignedModule for Dma64AlignedMemSM { ZiskOp::DMA_XMEMSET => F_SEL_MEMSET, _ => panic!("Invalid operation 0x{:02X}", first_input.op), } as u16); + air_values.segment_previous_fill_byte = F::from_u8(trace_rows[0].get_fill_byte()); } timer_stop_and_log_trace!(DMA_64_ALIGNED_TRACE); let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs index fce27c00a..d76757a98 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs @@ -207,13 +207,13 @@ impl Dma64AlignedModule for Dma64AlignedMemCpySM { } // padding + let padding_size = num_rows.saturating_sub(row_offset); + air_values.padding_size = F::from_u32(padding_size as u32); + if row_offset < num_rows { - air_values.padding_size = F::from_u32((num_rows - row_offset) as u32); - self.process_empty_slice(&mut trace_rows[row_offset]); - let empty_row = trace_rows[row_offset]; - trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { - *row = empty_row; - }); + for padding_row in trace_rows.iter_mut().take(num_rows).skip(row_offset) { + self.process_empty_slice(padding_row); + } air_values.segment_last_seq_end = F::ONE; air_values.segment_last_src64 = F::ZERO; air_values.segment_last_dst64 = F::ZERO; diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs index 142159b0b..d99fd66b3 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs @@ -75,10 +75,11 @@ impl Dma64AlignedMemSetSM { let mut seq_end = false; let addr_incr_by_row = self.op_x_rows as u32; + let fill_byte = DmaInfo::get_fill_byte(input.encoded); for (irow, row) in trace.iter_mut().enumerate().take(rows) { row.set_main_step(input.step); row.set_sel_memset(true); - row.set_fill_byte(input.fill_byte); + row.set_fill_byte(fill_byte); row.set_previous_seq_end(irow == 0 && input.skip_rows == 0); // calculate the first aligned address @@ -111,6 +112,7 @@ impl Dma64AlignedMemSetSM { air_values.last_count_chunk[0] = F::ZERO; air_values.last_count_chunk[1] = F::ZERO; air_values.segment_last_flags = F::ZERO; + air_values.segment_last_fill_byte = F::ZERO; } else { air_values.segment_last_seq_end = F::ZERO; air_values.segment_last_dst64 = F::from_u32(dst64 - addr_incr_by_row); @@ -120,6 +122,7 @@ impl Dma64AlignedMemSetSM { air_values.last_count_chunk[0] = F::from_u16(last_count as u16); air_values.last_count_chunk[1] = F::from_u16((last_count >> 16) as u16); air_values.segment_last_flags = F::from_u16(F_SEL_MEMSET as u16); + air_values.segment_last_fill_byte = F::from_u8(fill_byte); } } rows @@ -132,14 +135,6 @@ impl Dma64AlignedMemSetSM { /// * `input` - The operation data to process. #[inline(always)] pub fn process_empty_slice(&self, trace: &mut Dma64AlignedMemSetTraceRow) { - trace.set_main_step(0); - trace.set_sel_memset(false); - trace.set_fill_byte(0); - trace.set_dst64(0); - trace.set_count64(0); - for index in 0..self.op_x_rows - 1 { - trace.set_sel_op_from_1(index, false); - } trace.set_seq_end(true); trace.set_previous_seq_end(true); } @@ -204,13 +199,13 @@ impl Dma64AlignedModule for Dma64AlignedMemSetSM { } // padding + let padding_size = num_rows.saturating_sub(row_offset); + air_values.padding_size = F::from_u32(padding_size as u32); + if row_offset < num_rows { - air_values.padding_size = F::from_u32((num_rows - row_offset) as u32); - self.process_empty_slice(&mut trace_rows[row_offset]); - let empty_row = trace_rows[row_offset]; - trace_rows[row_offset + 1..].par_iter_mut().for_each(|row| { - *row = empty_row; - }); + for padding_row in trace_rows.iter_mut().take(num_rows).skip(row_offset) { + self.process_empty_slice(padding_row); + } air_values.segment_last_seq_end = F::ONE; air_values.segment_last_dst64 = F::ZERO; air_values.segment_last_main_step = F::ZERO; @@ -218,6 +213,7 @@ impl Dma64AlignedModule for Dma64AlignedMemSetSM { air_values.last_count_chunk[0] = F::ZERO; air_values.last_count_chunk[1] = F::ZERO; air_values.segment_last_flags = F::ZERO; + air_values.segment_last_fill_byte = F::ZERO; } // add range check of count to check that it's a positive 32-bits number @@ -238,6 +234,7 @@ impl Dma64AlignedModule for Dma64AlignedMemSetSM { air_values.segment_previous_main_step = F::ZERO; air_values.segment_previous_count64 = F::ZERO; air_values.segment_previous_flags = F::ZERO; + air_values.segment_previous_fill_byte = F::ZERO; } else { assert!(segment_id > 0); air_values.segment_previous_seq_end = F::ZERO; @@ -247,6 +244,7 @@ impl Dma64AlignedModule for Dma64AlignedMemSetSM { air_values.segment_previous_count64 = F::from_u32(trace_rows[0].get_count64() + self.op_x_rows as u32); air_values.segment_previous_flags = F::from_u16(F_SEL_MEMSET as u16); + air_values.segment_previous_fill_byte = F::from_u8(trace_rows[0].get_fill_byte()); } timer_stop_and_log_trace!(DMA_64_ALIGNED_TRACE); let from_trace = FromTrace::new(&mut trace).with_air_values(&mut air_values); From 62beb12be818e4315faf8723c4ac25c61fb7f94b Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Thu, 26 Feb 2026 12:25:38 +0100 Subject: [PATCH 654/782] fix cargo clippy errors --- precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs | 1 - precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs | 1 - precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs | 1 - precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs | 1 - 4 files changed, 4 deletions(-) diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs index 58be44c72..2f731ffc7 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_inputcpy.rs @@ -5,7 +5,6 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; -use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use zisk_common::SegmentId; use zisk_core::zisk_ops::ZiskOp; use zisk_pil::{Dma64AlignedInputCpyAirValues, DUAL_RANGE_BYTE_ID}; diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs index b1f581647..ee144d5c5 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_mem.rs @@ -5,7 +5,6 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; -use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use zisk_common::SegmentId; use zisk_core::zisk_ops::ZiskOp; use zisk_pil::Dma64AlignedMemAirValues; diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs index d76757a98..537005a91 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memcpy.rs @@ -5,7 +5,6 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; -use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use zisk_common::SegmentId; use zisk_core::zisk_ops::ZiskOp; use zisk_pil::Dma64AlignedMemCpyAirValues; diff --git a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs index d99fd66b3..94732c7db 100644 --- a/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs +++ b/precompiles/dma/src/dma_64_aligned/dma_64_aligned_memset.rs @@ -5,7 +5,6 @@ use fields::PrimeField64; use pil_std_lib::Std; use proofman_common::{AirInstance, FromTrace, ProofmanResult}; use proofman_util::{timer_start_trace, timer_stop_and_log_trace}; -use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use zisk_common::SegmentId; use zisk_pil::Dma64AlignedMemSetAirValues; From 3b89051b716c1968cdef4e6026059904bd6793c1 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 26 Feb 2026 12:52:56 +0000 Subject: [PATCH 655/782] Refactor hint_custom function --- ziskos/entrypoint/src/hints/custom.rs | 20 +++++++++++++------- ziskos/entrypoint/src/hints/mod.rs | 14 ++++++++------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/ziskos/entrypoint/src/hints/custom.rs b/ziskos/entrypoint/src/hints/custom.rs index ec999e7cb..5b9391e13 100644 --- a/ziskos/entrypoint/src/hints/custom.rs +++ b/ziskos/entrypoint/src/hints/custom.rs @@ -1,19 +1,25 @@ -use crate::hints::HINT_BUFFER; +use crate::hints::{macros::define_hint_ptr, HINT_BUFFER}; #[no_mangle] -pub unsafe extern "C" fn hint_custom(hint_id: u32, data_ptr: *const u8, data_len: usize) { +pub unsafe extern "C" fn hint_custom( + hint_id: u32, + data_ptr: *const u8, + data_len: usize, + is_result: u8, +) { if !HINT_BUFFER.is_enabled() { return; } - HINT_BUFFER.write_hint_header(hint_id, data_len, false); - HINT_BUFFER.write_hint_data(data_ptr, data_len); + let mut w = HINT_BUFFER.begin_hint(hint_id, data_len, is_result != 0); + + w.write_hint_data_ptr(data_ptr, data_len); let pad = (8 - (data_len & 7)) & 7; if pad > 0 { const ZERO_PAD: [u8; 8] = [0; 8]; - HINT_BUFFER.write_hint_data(ZERO_PAD.as_ptr(), pad); + w.write_hint_data_slice(&ZERO_PAD[..pad]); } - HINT_BUFFER.commit(); -} \ No newline at end of file + w.commit(); +} diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index c95fbd487..9d8805981 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -24,7 +24,7 @@ use std::time::{Duration, Instant}; use std::{ffi::CStr, os::raw::c_char}; use std::{ io::{self, BufWriter, Write}, - sync::{Arc, Mutex}, + sync::Arc, }; use tokio::sync::oneshot; use zisk_common::io::{StreamWrite, UnixSocketStreamWriter}; @@ -37,6 +37,7 @@ use std::thread::ThreadId; pub use blake2b::*; pub use bls12_381::*; pub use bn254::*; +pub use custom::*; pub use keccak256::*; pub use kzg::*; pub use modexp::*; @@ -109,9 +110,6 @@ pub fn init_hints() { // Write HINT_START HINT_BUFFER.write_hint_start(); - - // Write HINT_START - HINT_BUFFER.write_hint_start(); } pub fn init_hints_file(hints_file_path: PathBuf, ready: Option>) -> Result<()> { @@ -159,8 +157,12 @@ pub fn init_hints_socket( Ok(()) } -pub fn close_hints() -> io::Result<()> { - *MAIN_TID.lock().unwrap() = None; + +pub fn close_hints() -> Result<()> { + #[cfg(zisk_hints_single_thread)] + { + *MAIN_TID.lock().unwrap() = None; + } // Write HINT_END HINT_BUFFER.write_hint_end(); From 314358950010828db37f2b2ed762a7388a27ee8f Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 26 Feb 2026 12:59:17 +0000 Subject: [PATCH 656/782] Fixes --- book/getting_started/precompile_hints.md | 9 +++++---- ziskos/entrypoint/src/hints/custom.rs | 5 +++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/book/getting_started/precompile_hints.md b/book/getting_started/precompile_hints.md index 2b2ae0ae3..f0e4af069 100644 --- a/book/getting_started/precompile_hints.md +++ b/book/getting_started/precompile_hints.md @@ -272,13 +272,13 @@ To generate hints from the guest program you need to follow these steps and requ 4. **Enable hints at compile time**: Compile your guest program with `RUSTFLAGS='--cfg zisk_hints'` for the native target to activate hint code generation and FFI helper functions in the `ziskos` crate. 5. **Ensure deterministic execution**: Verify that both the native execution that generates hints and the guest compiled for the `zkvm/zisk` target execute deterministically and produce/consume hints in the exact same order. See [Deterministic Execution Requirement](#54-deterministic-execution-requirement). -To illustrate these steps, consider the `zisk-reth` guest program, which executes and verifies Ethereum Mainnet blocks using the ZisK zkVM: +To illustrate these steps, consider the `zec-reth` guest program, which executes and verifies Ethereum Mainnet blocks using the ZisK zkVM: https://github.com/0xPolygonHermez/zisk-eth-client/tree/main-reth/bin/guest ### 5.1 Emit Hint Requests -`zisk-reth` relies on `reth` crates, which expose a `Crypto` trait that allows a guest program to override precompile implementations. This enables zkVM-optimized implementations while also emitting hints so the computation can be performed outside the zkVM. +`zec-reth` relies on `reth` crates, which expose a `Crypto` trait that allows a guest program to override precompile implementations. This enables zkVM-optimized implementations while also emitting hints so the computation can be performed outside the zkVM. For example, the SHA-256 implementation for the `Crypto` trait can be found here: @@ -302,13 +302,13 @@ After the hint generation, execution continues in the native target code to comp To start hints generation from your guest program you must call one of the following functions from the `ziskos::hints` crate: ```rust -pub fn init_hints_file(hints_file_path: PathBuf) -> io::Result<()> +pub fn init_hints_file(hints_file_path: PathBuf, ready: Option>) -> Result<()> ``` This function stores the generated hints in the file specified by the `hints_file_path` parameter. ```rust -pub fn init_hints_socket(socket_path: PathBuf,ready: Option>) -> io::Result<()> +pub fn init_hints_socket(socket_path: PathBuf, ready: Option>) -> io::Result<()> ``` This function sends the hints through the Unix socket specified by the `socket_path` parameter. @@ -365,6 +365,7 @@ Using threads or iterating over non-deterministically ordered data structures ma ### 5.5 FFI Hints Helper Functions | Code | Function | +| ---- | -------- | | `0x0100` | `fn hint_sha256(f: *const u8, len: usize);` | | `0x0200` | `fn hint_bn254_g1_add(p1: *const u8, p2: *const u8);`| | `0x0201` | `fn hint_bn254_g1_mul(point: *const u8, scalar: *const u8);` | diff --git a/ziskos/entrypoint/src/hints/custom.rs b/ziskos/entrypoint/src/hints/custom.rs index 5b9391e13..a7224257f 100644 --- a/ziskos/entrypoint/src/hints/custom.rs +++ b/ziskos/entrypoint/src/hints/custom.rs @@ -11,6 +11,11 @@ pub unsafe extern "C" fn hint_custom( return; } + #[cfg(zisk_hints_single_thread)] + if crate::hints::check_main_thread() { + return; + } + let mut w = HINT_BUFFER.begin_hint(hint_id, data_len, is_result != 0); w.write_hint_data_ptr(data_ptr, data_len); From 5030e6e7b36ea9776844bde7261e7c5020fa2440 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 26 Feb 2026 13:12:44 +0000 Subject: [PATCH 657/782] Fix logic in hint_custom function --- ziskos/entrypoint/src/hints/custom.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ziskos/entrypoint/src/hints/custom.rs b/ziskos/entrypoint/src/hints/custom.rs index a7224257f..9ca45c1b7 100644 --- a/ziskos/entrypoint/src/hints/custom.rs +++ b/ziskos/entrypoint/src/hints/custom.rs @@ -12,7 +12,7 @@ pub unsafe extern "C" fn hint_custom( } #[cfg(zisk_hints_single_thread)] - if crate::hints::check_main_thread() { + if !crate::hints::check_main_thread() { return; } From 500ea40b874b377c2280d8ebfe491f3355e25fc5 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 26 Feb 2026 13:18:20 +0000 Subject: [PATCH 658/782] Doc fixes --- book/getting_started/precompile_hints.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/book/getting_started/precompile_hints.md b/book/getting_started/precompile_hints.md index f0e4af069..2612d5783 100644 --- a/book/getting_started/precompile_hints.md +++ b/book/getting_started/precompile_hints.md @@ -308,17 +308,19 @@ pub fn init_hints_file(hints_file_path: PathBuf, ready: Option>) -> io::Result<()> +pub fn init_hints_socket(socket_path: PathBuf, debug_file: Option, ready: Option>) -> Result<()> ``` This function sends the hints through the Unix socket specified by the `socket_path` parameter. The optional `ready` parameter can be used for synchronization with the host when the guest program is executed in a separate thread to generate hints in parallel. It signals `ready` when the hints generation is ready to start writing hints through the Unix socket. +The optional `debug_file` parameter can be used to store, in the specified file, a copy of the hints sent through the socket. This file can later be used for debugging purposes. + To close hints generation you must call: ```rust -pub fn close_hints() -> io::Result<()> +pub fn close_hints() -> Result<()> ``` You should call these functions only when the guest is compiled for the native target used for hints generation. This can be achieved by placing the code under the following configuration flag: @@ -388,7 +390,7 @@ Using threads or iterating over non-deterministically ordered data structures ma To extend the built-in precompile hints, you can generate custom hints for new precompiles. The first step is to register the new hint in the `HintsProcessor`, as explained in section [Custom Hint Handlers](#4-custom-hint-handlers). Once the precompile hint is registered, you can generate hints for it from the guest program using the following FFI function: ```rust -fn hint_custom(hint_id: u32, data_ptr: *const u8, data_len: usize); +fn hint_custom(hint_id: u32, data_ptr: *const u8, data_len: usize, is_result: u8); ``` and following the same guidelines described for the built-in FFI hint helper functions. From 80cd120d33645d672582949b296aaf7377006941 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 26 Feb 2026 14:20:53 +0000 Subject: [PATCH 659/782] Fixes --- book/getting_started/precompile_hints.md | 2 +- ziskos/entrypoint/src/hints/custom.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/book/getting_started/precompile_hints.md b/book/getting_started/precompile_hints.md index 2612d5783..511c9e0de 100644 --- a/book/getting_started/precompile_hints.md +++ b/book/getting_started/precompile_hints.md @@ -368,7 +368,7 @@ Using threads or iterating over non-deterministically ordered data structures ma | Code | Function | | ---- | -------- | -| `0x0100` | `fn hint_sha256(f: *const u8, len: usize);` | +| `0x0100` | `fn hint_sha256(f_ptr: *const u8, f_len: usize);` | | `0x0200` | `fn hint_bn254_g1_add(p1: *const u8, p2: *const u8);`| | `0x0201` | `fn hint_bn254_g1_mul(point: *const u8, scalar: *const u8);` | | `0x0205` | `fn hint_bn254_pairing_check(pairs: *const u8, num_pairs: usize);` | diff --git a/ziskos/entrypoint/src/hints/custom.rs b/ziskos/entrypoint/src/hints/custom.rs index 9ca45c1b7..7c16bf735 100644 --- a/ziskos/entrypoint/src/hints/custom.rs +++ b/ziskos/entrypoint/src/hints/custom.rs @@ -1,4 +1,4 @@ -use crate::hints::{macros::define_hint_ptr, HINT_BUFFER}; +use crate::hints::HINT_BUFFER; #[no_mangle] pub unsafe extern "C" fn hint_custom( From b71258ca8eb39ee97b5d2fbbfbb940d8379e40f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Thu, 26 Feb 2026 16:04:01 +0100 Subject: [PATCH 660/782] Merge pull request #819 from 0xPolygonHermez/tmp-0.16.0 Tmp 0.16.0 --- Cargo.lock | 160 +++++-------- .../crates/coordinator/src/coordinator.rs | 1 + distributed/crates/worker/src/worker.rs | 26 ++- distributed/crates/worker/src/worker_node.rs | 1 + examples/Cargo.lock | 219 ++++++++---------- sdk/src/prover/asm.rs | 9 +- sdk/src/prover/backend.rs | 18 +- sdk/src/prover/emu.rs | 9 +- sdk/src/prover/mod.rs | 9 +- 9 files changed, 223 insertions(+), 229 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4f61b7c7..a9ced45ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,7 +186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -199,7 +199,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -260,7 +260,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -332,7 +332,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -344,7 +344,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -366,7 +366,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -377,7 +377,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -489,7 +489,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.117", + "syn", ] [[package]] @@ -544,7 +544,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -776,7 +776,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -848,7 +848,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.2", + "unicode-width", "windows-sys 0.59.0", ] @@ -1110,13 +1110,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.117", + "syn", ] [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "fields", "num-bigint", @@ -1150,7 +1150,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn", ] [[package]] @@ -1161,7 +1161,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1242,7 +1242,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1252,7 +1252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.117", + "syn", ] [[package]] @@ -1294,7 +1294,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1321,7 +1321,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1368,7 +1368,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1470,7 +1470,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "cfg-if", "num-bigint", @@ -1583,7 +1583,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2052,7 +2052,7 @@ dependencies = [ "console", "number_prefix", "portable-atomic", - "unicode-width 0.2.2", + "unicode-width", "web-time", ] @@ -2651,15 +2651,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "papergrid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608b6444acf7f5ea39e8bd06dd6037e34a4b5ddfb29ae840edad49ea798e9e79" -dependencies = [ - "unicode-width 0.1.14", -] - [[package]] name = "parking_lot" version = "0.12.5" @@ -2747,7 +2738,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2774,7 +2765,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "colored", "fields", @@ -2808,7 +2799,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3132,7 +3123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn", ] [[package]] @@ -3156,7 +3147,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "bincode", "blake3", @@ -3192,7 +3183,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "bincode", "borsh", @@ -3214,7 +3205,6 @@ dependencies = [ "serde", "serde_json", "sysinfo 0.35.2", - "tabled", "thiserror 2.0.18", "tracing", "tracing-subscriber", @@ -3224,7 +3214,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "fields", "itoa", @@ -3237,17 +3227,17 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "crossbeam-channel", "tracing", @@ -3256,7 +3246,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "bincode", "bytemuck", @@ -3268,7 +3258,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "bytemuck", "fields", @@ -3304,7 +3294,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.117", + "syn", "tempfile", ] @@ -3318,7 +3308,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3919,7 +3909,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4223,17 +4213,6 @@ dependencies = [ "symbolic-common", ] -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.117" @@ -4262,7 +4241,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4293,27 +4272,6 @@ dependencies = [ "windows 0.62.2", ] -[[package]] -name = "tabled" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2407502760ccfd538f2fb1f843dd87b6daf1a17848d57bc5a25617e408ef4c7a" -dependencies = [ - "papergrid", - "tabled_derive", -] - -[[package]] -name = "tabled_derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278ea3921cee8c5a69e0542998a089f7a14fa43c9c4e4f9951295da89bd0c943" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "target-lexicon" version = "0.13.5" @@ -4359,7 +4317,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4370,7 +4328,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4484,7 +4442,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4651,7 +4609,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4676,7 +4634,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.117", + "syn", "tempfile", "tonic-build", ] @@ -4775,7 +4733,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4878,12 +4836,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-width" version = "0.2.2" @@ -5084,7 +5036,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -5312,7 +5264,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -5323,7 +5275,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -5682,7 +5634,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -5698,7 +5650,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -5743,7 +5695,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#957aec4718875a62824837da0c29726a8bc90c9b" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "colored", "fields", @@ -5823,7 +5775,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -5844,7 +5796,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -5864,7 +5816,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -5885,7 +5837,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -5918,7 +5870,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 80557ed54..265f9169f 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -1261,6 +1261,7 @@ impl Coordinator { worker_index: challenge.worker_index, airgroup_id: challenge.airgroup_id as usize, challenge: challenge.challenge, + aggregated: false, }) .collect(); diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index d1fb91bd0..0fcfd5d68 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -2,7 +2,7 @@ use anyhow::Result; use asm_runner::HintsShmem; use cargo_zisk::commands::get_proving_key; use precompiles_hints::HintsProcessor; -use proofman::{AggProofs, ContributionsInfo}; +use proofman::{AggProofs, AggProofsRegister, ContributionsInfo}; use rom_setup::{get_elf_data_hash, DEFAULT_CACHE_PATH}; use std::fs; use std::sync::Arc; @@ -736,6 +736,30 @@ impl Worker { let prover = self.prover.clone(); let options = self.get_proof_options(agg_params.compressed); + let agg_proofs_register: Vec = agg_params + .agg_proofs + .iter() + .map(|v| AggProofsRegister { + airgroup_id: v.airgroup_id, + worker_indexes: vec![v.worker_idx as usize], + }) + .collect(); + + if let Err(error) = prover.register_aggregated_proofs(agg_proofs_register) { + let job_guard = job.blocking_lock(); + let job_id = job_guard.job_id.clone(); + let executed_steps = job_guard.executed_steps; + + let _ = tx.send(ComputationResult::AggProof { + job_id, + success: false, + result: Err(error), + executed_steps, + }); + + return tokio::spawn(async {}); + } + tokio::task::spawn_blocking(move || { let (job_id, executed_steps) = { let guard = job.blocking_lock(); diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 4b73956f4..271ada78d 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -687,6 +687,7 @@ impl WorkerNodeGrpc { worker_index: ch.worker_index, airgroup_id: ch.airgroup_id as usize, challenge: ch.challenge, + aggregated: false, }) .collect(); diff --git a/examples/Cargo.lock b/examples/Cargo.lock index f9ad4452f..c2d74d7a6 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -171,7 +171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -184,7 +184,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -245,7 +245,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -279,6 +279,7 @@ dependencies = [ "mem-common", "mem-planner-cpp", "named-sem", + "proofman-common", "rayon", "thiserror 2.0.18", "tracing", @@ -310,7 +311,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -322,7 +323,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -385,7 +386,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.117", + "syn", ] [[package]] @@ -437,7 +438,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -548,9 +549,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -606,7 +607,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -807,13 +808,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.117", + "syn", ] [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "fields", "num-bigint", @@ -847,7 +848,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn", ] [[package]] @@ -858,7 +859,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -900,9 +901,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -925,7 +926,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -935,7 +936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.117", + "syn", ] [[package]] @@ -956,7 +957,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -974,7 +975,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1006,7 +1007,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1051,6 +1052,7 @@ dependencies = [ "precomp-arith-eq", "precomp-arith-eq-384", "precomp-big-int", + "precomp-blake2", "precomp-dma", "precomp-keccakf", "precomp-poseidon2", @@ -1122,7 +1124,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "cfg-if", "num-bigint", @@ -1514,9 +1516,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.86" +version = "0.3.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36139f1c97c42c0c86a411910b04e48d4939a0376e6e0f989420cbdee0120e5" +checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" dependencies = [ "once_cell", "wasm-bindgen", @@ -1603,9 +1605,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libz-sys" -version = "1.1.23" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839" dependencies = [ "cc", "libc", @@ -1615,9 +1617,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -1923,15 +1925,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "papergrid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608b6444acf7f5ea39e8bd06dd6037e34a4b5ddfb29ae840edad49ea798e9e79" -dependencies = [ - "unicode-width", -] - [[package]] name = "parking_lot" version = "0.12.5" @@ -1986,7 +1979,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "colored", "fields", @@ -2132,6 +2125,25 @@ dependencies = [ "zisk-pil", ] +[[package]] +name = "precomp-blake2" +version = "0.16.0" +dependencies = [ + "fields", + "mem-common", + "pil-std-lib", + "precompiles-common", + "proofman-common", + "proofman-macros", + "proofman-util", + "rayon", + "sm-mem", + "tracing", + "zisk-common", + "zisk-core", + "zisk-pil", +] + [[package]] name = "precomp-dma" version = "0.16.0" @@ -2143,6 +2155,7 @@ dependencies = [ "pil-std-lib", "precompiles-common", "precompiles-helpers", + "proofman", "proofman-common", "proofman-macros", "proofman-util", @@ -2263,7 +2276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn", ] [[package]] @@ -2287,7 +2300,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "bincode", "blake3", @@ -2323,7 +2336,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "bincode", "borsh", @@ -2345,7 +2358,6 @@ dependencies = [ "serde", "serde_json", "sysinfo 0.35.2", - "tabled", "thiserror 2.0.18", "tracing", "tracing-subscriber", @@ -2355,7 +2367,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "fields", "itoa", @@ -2368,17 +2380,17 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "crossbeam-channel", "tracing", @@ -2387,7 +2399,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "bincode", "bytemuck", @@ -2399,7 +2411,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "bytemuck", "fields", @@ -2607,9 +2619,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "ring" @@ -2687,9 +2699,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -2700,9 +2712,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", "log", @@ -2889,7 +2901,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3170,17 +3182,6 @@ dependencies = [ "symbolic-common", ] -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.117" @@ -3200,7 +3201,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3231,32 +3232,11 @@ dependencies = [ "windows 0.62.2", ] -[[package]] -name = "tabled" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2407502760ccfd538f2fb1f843dd87b6daf1a17848d57bc5a25617e408ef4c7a" -dependencies = [ - "papergrid", - "tabled_derive", -] - -[[package]] -name = "tabled_derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278ea3921cee8c5a69e0542998a089f7a14fa43c9c4e4f9951295da89bd0c943" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "tempfile" -version = "3.25.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", "getrandom 0.4.1", @@ -3291,7 +3271,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3302,7 +3282,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3406,7 +3386,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3538,7 +3518,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3611,12 +3591,6 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -3757,9 +3731,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.109" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff9c7baef35ac3c0e17d8bfc9ad75eb62f85a2f02bccc906699dadb0aa9c622" +checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" dependencies = [ "cfg-if", "once_cell", @@ -3770,9 +3744,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.109" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39455e84ad887a0bbc93c116d72403f1bb0a39e37dd6f235a43e2128a0c7f1fd" +checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3780,22 +3754,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.109" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff4761f60b0b51fd13fec8764167b7bbcc34498ce3e52805fe1db6f2d56b6d6" +checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.109" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6a171c53d98021a93a474c4a4579d76ba97f9517d871bc12e27640f218b6dd" +checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" dependencies = [ "unicode-ident", ] @@ -3983,7 +3957,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3994,7 +3968,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4344,7 +4318,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4360,7 +4334,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4405,7 +4379,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#0aea9d80193cf14edf1cb1c92f045a58afb23ebe" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" dependencies = [ "colored", "fields", @@ -4474,7 +4448,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -4495,7 +4469,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4515,7 +4489,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -4536,7 +4510,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4569,7 +4543,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4623,9 +4597,14 @@ dependencies = [ "serde", "sha2", "tiny-keccak", + "zisk-definitions", "ziskos-hints", ] +[[package]] +name = "zisk-definitions" +version = "0.16.0" + [[package]] name = "zisk-distributed-common" version = "0.1.0" @@ -4704,6 +4683,7 @@ dependencies = [ "object", "proofman-common", "rayon", + "regex", "riscv", "sm-arith", "sm-binary", @@ -4741,6 +4721,7 @@ dependencies = [ "tiny-keccak", "tokio", "zisk-common", + "zisk-definitions", ] [[package]] diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 7b73c5dfe..4ebd1facd 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -9,7 +9,10 @@ use crate::{ use crate::{ProofMode, ProofOpts}; use asm_runner::{AsmRunnerOptions, AsmServices}; use executor::{get_packed_info, init_executor_asm, AsmResources}; -use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; +use proofman::{ + AggProofs, AggProofsRegister, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, + SnarkWrapper, +}; use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, RowInfo, VerboseMode}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rom_setup::{generate_assembly, get_output_path, DEFAULT_CACHE_PATH}; @@ -317,6 +320,10 @@ impl ProverEngine for AsmProver { self.core_prover.backend.is_first_partition() } + fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()> { + self.core_prover.backend.register_aggregated_proofs(agg_proofs) + } + fn aggregate_proofs( &self, agg_proofs: Vec, diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 32d3c8fe8..fd3ee5af4 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -15,8 +15,8 @@ use colored::Colorize; use executor::ZiskExecutor; use fields::Goldilocks; use proofman::{ - AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult, - SnarkProtocol, SnarkWrapper, + AggProofs, AggProofsRegister, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, + ProvePhaseResult, SnarkProtocol, SnarkWrapper, }; use proofman_common::{ProofCtx, ProofOptions, RowInfo}; use proofman_util::VadcopFinalProof; @@ -557,6 +557,20 @@ impl ProverBackend { Ok(proofman.get_execution_info()) } + pub(crate) fn register_aggregated_proofs( + &self, + agg_proofs: Vec, + ) -> Result<()> { + let proofman = self + .proofman + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Cannot aggregate proofs in verifier mode"))?; + + proofman + .register_aggregated_proofs(agg_proofs) + .map_err(|e| anyhow::anyhow!("Error registering aggregate proof: {}", e)) + } + pub(crate) fn aggregate_proofs( &self, agg_proofs: Vec, diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 837161c72..df2568c1d 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -7,7 +7,10 @@ use crate::{ }; use crate::{ensure_custom_commits, ProofMode, ProofOpts}; use executor::{get_packed_info, init_executor_emu}; -use proofman::{AggProofs, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper}; +use proofman::{ + AggProofs, AggProofsRegister, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, + SnarkWrapper, +}; use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, RowInfo}; use std::path::PathBuf; use std::sync::Arc; @@ -235,6 +238,10 @@ impl ProverEngine for EmuProver { self.core_prover.backend.is_first_partition() } + fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()> { + self.core_prover.backend.register_aggregated_proofs(agg_proofs) + } + fn aggregate_proofs( &self, agg_proofs: Vec, diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index e67e6b626..b885d5504 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -5,7 +5,8 @@ pub use asm::*; use backend::*; pub use emu::*; use proofman::{ - AggProofs, ExecutionInfo, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProtocol, + AggProofs, AggProofsRegister, ExecutionInfo, ProvePhase, ProvePhaseInputs, ProvePhaseResult, + SnarkProtocol, }; use proofman_common::{ProofOptions, RankInfo, RowInfo}; use proofman_util::VadcopFinalProof; @@ -832,6 +833,8 @@ pub trait ProverEngine { fn is_first_partition(&self) -> Result; + fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()>; + fn aggregate_proofs( &self, agg_proofs: Vec, @@ -1019,6 +1022,10 @@ impl ZiskProver { self.prover.is_first_partition() } + pub fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()> { + self.prover.register_aggregated_proofs(agg_proofs) + } + pub fn aggregate_proofs( &self, agg_proofs: Vec, From 4d76f75d5770637e46c8a803e6dd4358c661730f Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Feb 2026 10:17:19 +0000 Subject: [PATCH 661/782] Starting main as soon as MT finished --- executor/src/executor.rs | 2 +- executor/src/registry.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/executor/src/executor.rs b/executor/src/executor.rs index cb058943b..fdb2a81d9 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -164,7 +164,7 @@ impl WitnessComponent for ZiskExecutor { let (main_assignments, cost_main) = self.planner.assign_main_instances(&pctx, &sctx, global_ids, main_output.plans); - self.registry.populate_main_instances(&self.state, main_assignments); + self.registry.populate_main_instances(&pctx, &self.state, main_assignments)?; stats_end!(self.state.stats, &_main_plan_scope); diff --git a/executor/src/registry.rs b/executor/src/registry.rs index 215d46397..21cf6dcfc 100644 --- a/executor/src/registry.rs +++ b/executor/src/registry.rs @@ -4,7 +4,7 @@ //! state machine instances. use fields::PrimeField64; -use proofman_common::ProofCtx; +use proofman_common::{ProofCtx, ProofmanResult}; use sm_main::MainInstance; use std::sync::Arc; use zisk_common::{CheckPoint, Instance, InstanceCtx, InstanceType, Plan}; @@ -74,15 +74,22 @@ impl InstanceRegistry { /// * `assignments` - Vector of (global_id, plan) pairs. pub fn populate_main_instances( &self, + pctx: &ProofCtx, state: &ExecutionState, assignments: Vec<(usize, Plan)>, - ) { + ) -> ProofmanResult<()> { let mut main_instances = state.main_instances.write().unwrap(); for (global_id, plan) in assignments { main_instances .entry(global_id) .or_insert_with(|| self.create_main_instance(plan, global_id)); + + let is_mine = pctx.dctx_is_my_process_instance(global_id)?; + if is_mine { + pctx.set_witness_ready(global_id, false); + } } + Ok(()) } /// Populates secondary instances in the execution state. From fd725e6f2c3dda09a7bc891eb34ae59153ed99fb Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 26 Feb 2026 16:43:07 +0000 Subject: [PATCH 662/782] Update documentation for emit hint requests --- book/getting_started/precompile_hints.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/book/getting_started/precompile_hints.md b/book/getting_started/precompile_hints.md index 511c9e0de..f28b19723 100644 --- a/book/getting_started/precompile_hints.md +++ b/book/getting_started/precompile_hints.md @@ -297,6 +297,12 @@ This call generates the hint input data using the exact input values that will l After the hint generation, execution continues in the native target code to compute the SHA-256 result. +From the guest program, we generate hints containing the input data for the corresponding `zisklib` functions (in this example, the `sha256_c` function). These `zisklib` functions may internally invoke one or more precompiles to produce the final result. + +When the hints are processed by the `HintsProcessor`, it executes the same ``zisklib function using the implementation code for the zkvm/zisk target. This produces the exact precompile results expected when executing the guest ELF inside the zkVM. + +As a result, for each `zisklib` function invocation, the `HintsProcessor` may generate one or more precompile hint results corresponding to the precompile inputs originally emitted by the guest. + ### 5.2 Initialize/Finalize Hint Stream To start hints generation from your guest program you must call one of the following functions from the `ziskos::hints` crate: From 3fa3551bdcf931aef7e33f884cc8884265edccc9 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 26 Feb 2026 20:02:29 +0000 Subject: [PATCH 663/782] Update test-env scripts to use zec-reth guest --- .github/workflows/pr.yml | 6 ++++-- cli/src/toolchain/new.rs | 18 +++++++++++------- tools/test-env/.env | 5 +++-- tools/test-env/test_eth_block.sh | 2 +- tools/test-env/test_sha_hasher.sh | 2 +- 5 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a9df5ae7a..8f90e996c 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -86,6 +86,8 @@ jobs: ./build_setup.sh - name: Test sha_hasher + env: + ZISK_TEMPLATE_BRANCH: pre-develop-0.16.0 run: | cd "$GITHUB_WORKSPACE/tools/test-env" ./test_sha_hasher.sh @@ -98,7 +100,7 @@ jobs: - name: Verify Constraints Ethereum block env: DISABLE_PROVE: "1" - BLOCK_INPUTS: "18885301_210_24_rsp.bin" + BLOCK_INPUTS: "mainnet_24341018_290_29_zec_reth.bin" run: | cd "$GITHUB_WORKSPACE/tools/test-env" ./test_eth_block.sh @@ -106,7 +108,7 @@ jobs: - name: Prove Ethereum block env: DISABLE_ROM_SETUP: "1" - BLOCK_INPUTS_DISTRIBUTED: "21429020_5_0_rsp.bin" + BLOCK_INPUTS_DISTRIBUTED: "mainnet_24341035_74_5_zec_reth.bin" run: | cd "$GITHUB_WORKSPACE/tools/test-env" ./test_eth_block.sh diff --git a/cli/src/toolchain/new.rs b/cli/src/toolchain/new.rs index d92a8e245..d89038e5a 100644 --- a/cli/src/toolchain/new.rs +++ b/cli/src/toolchain/new.rs @@ -19,16 +19,20 @@ impl NewCmd { } // Clone the repository. - let output = Command::new("git") - .arg("clone") - .arg("--branch") - .arg("pre-develop-0.16.0") + let mut cmd = Command::new("git"); + cmd.arg("clone") .arg(repo_url) .arg(root.as_os_str()) .arg("--recurse-submodules") - .arg("--depth=1") - .output() - .expect("failed to execute command"); + .arg("--depth=1"); + + // Check if ZISK_TEMPLATE_BRANCH environment variable is set, and if so, use it as the branch to clone. + if let Ok(branch) = std::env::var("ZISK_TEMPLATE_BRANCH") { + cmd.arg("--branch").arg(&branch); + } + + let output = cmd.output().expect("failed to execute command"); + if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(anyhow::anyhow!("failed to clone repository: {}", stderr)); diff --git a/tools/test-env/.env b/tools/test-env/.env index fd4f2f696..6f2d91f25 100644 --- a/tools/test-env/.env +++ b/tools/test-env/.env @@ -3,6 +3,7 @@ PIL2_PROOFMAN_BRANCH=tags/v0.15.0 PIL2_PROOFMAN_JS_BRANCH=tags/v0.15.0 PIL2_COMPILER_BRANCH=tags/v0.8.0 ZISK_TESTVECTORS_BRANCH=main +ZISK_TEMPLATE_BRANCH=pre-develop-0.16.0 ZISK_SETUP_FILE=zisk-provingkey-0.14.0.tar.gz PACKAGE_SETUP_VERSION=0.14.0 @@ -10,8 +11,8 @@ SETUP_ADD_DYLIBS=0 PP_INPUTS=pp_input_1_1.bin PP_INPUTS_DISTRIBUTED=pp_input_1_1.bin,pp_input_20_20.bin -BLOCK_INPUTS=20852412_38_3_rsp.bin -BLOCK_INPUTS_DISTRIBUTED=20852412_38_3_rsp.bin,21077746_52_26_rsp.bin,18885301_210_24_rsp.bin +BLOCK_INPUTS=mainnet_24341035_74_5_zec_reth.bin +BLOCK_INPUTS_DISTRIBUTED=mainnet_24341035_74_5_zec_reth.bin,mainnet_24341018_290_29_zec_reth.bin BLOCK_FOLDER= DISTRIBUTED_PROCESSES=2 diff --git a/tools/test-env/test_eth_block.sh b/tools/test-env/test_eth_block.sh index cc46824f8..3c056d9b3 100755 --- a/tools/test-env/test_eth_block.sh +++ b/tools/test-env/test_eth_block.sh @@ -5,7 +5,7 @@ source "./test_elf.sh" main() { info "▶️ Running $(basename "$0") script..." - ELF_FILE="eth-client/elf/zec-rsp.elf" + ELF_FILE="eth-client/elf/zec-reth.elf" INPUTS_PATH="eth-client/inputs" test_elf "${ELF_FILE}" "${INPUTS_PATH}" "BLOCK_INPUTS" "BLOCK_INPUTS_DISTRIBUTED" "Ethereum blocks" || return 1 diff --git a/tools/test-env/test_sha_hasher.sh b/tools/test-env/test_sha_hasher.sh index 3f0ce9bd6..2217ebcad 100755 --- a/tools/test-env/test_sha_hasher.sh +++ b/tools/test-env/test_sha_hasher.sh @@ -2,7 +2,7 @@ source "./utils.sh" -PROJECT_NAME="guest" +PROJECT_NAME="sha-hasher" EXPECTED_OUTPUT="4fcbc136|2ce46a82|2248a8eb|785f0c7e|9dca7861|7267cace|d028d7e5|f6a2309c|000003e8|deadbeef" main() { From 3d51cb91fb7bbccf52eae13b1ff82326f94a7df1 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 26 Feb 2026 20:41:45 +0000 Subject: [PATCH 664/782] Fix macos sha_hasher test in PR GHA --- .github/workflows/pr.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 8f90e996c..6e596cdf7 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -142,6 +142,8 @@ jobs: ./build_zisk.sh - name: Test sha_hasher + env: + ZISK_TEMPLATE_BRANCH: pre-develop-0.16.0 run: | cd "$GITHUB_WORKSPACE/tools/test-env" ./test_sha_hasher.sh From e1e6063ab1fe73bf3c173519fb198d12cf2e1b0d Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Fri, 27 Feb 2026 12:09:00 +0000 Subject: [PATCH 665/782] Add instructions for enabling hint support in zkVM execution --- book/getting_started/precompile_hints.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/book/getting_started/precompile_hints.md b/book/getting_started/precompile_hints.md index f28b19723..4b65e095b 100644 --- a/book/getting_started/precompile_hints.md +++ b/book/getting_started/precompile_hints.md @@ -357,6 +357,8 @@ If a hints file was generated, it can be consumed using the `--hints` flag in th If you want to display metrics in the console about the number of hints generated during native guest execution, you can additionally compile the guest with the `--cfg zisk_hints_metrics` flag. +To enable hint support when executing the guest inside the zkVM (ELF guest), you must pass the `--hints` flag when generating the assembly ROM using the `cargo-zisk rom-setup` command. + ### 5.4 Deterministic Execution Requirement An important requirement of the hints generation flow is that the native execution that generates the hints must be fully deterministic and always produce hints in the exact same order. From 6e0d3e3a55851f6ccf0f28f0079a29fb335824cf Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Fri, 27 Feb 2026 12:12:57 +0000 Subject: [PATCH 666/782] Add note about hint processing limitations in emulation mode --- book/getting_started/precompile_hints.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/book/getting_started/precompile_hints.md b/book/getting_started/precompile_hints.md index 4b65e095b..b782099c0 100644 --- a/book/getting_started/precompile_hints.md +++ b/book/getting_started/precompile_hints.md @@ -359,6 +359,8 @@ If you want to display metrics in the console about the number of hints generate To enable hint support when executing the guest inside the zkVM (ELF guest), you must pass the `--hints` flag when generating the assembly ROM using the `cargo-zisk rom-setup` command. +**NOTE:** Hint processing is not supported when executing the guest ELF file in emulation mode. + ### 5.4 Deterministic Execution Requirement An important requirement of the hints generation flow is that the native execution that generates the hints must be fully deterministic and always produce hints in the exact same order. From 2daf443b1dd96f56396be01ccd51e4ca6e06c443 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Fri, 27 Feb 2026 12:18:20 +0000 Subject: [PATCH 667/782] Fix typo in precompile hints documentation regarding zisklib function execution --- book/getting_started/precompile_hints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/getting_started/precompile_hints.md b/book/getting_started/precompile_hints.md index b782099c0..42ab078d6 100644 --- a/book/getting_started/precompile_hints.md +++ b/book/getting_started/precompile_hints.md @@ -299,7 +299,7 @@ After the hint generation, execution continues in the native target code to comp From the guest program, we generate hints containing the input data for the corresponding `zisklib` functions (in this example, the `sha256_c` function). These `zisklib` functions may internally invoke one or more precompiles to produce the final result. -When the hints are processed by the `HintsProcessor`, it executes the same ``zisklib function using the implementation code for the zkvm/zisk target. This produces the exact precompile results expected when executing the guest ELF inside the zkVM. +When the hints are processed by the `HintsProcessor`, it executes the same `zisklib` function using the implementation code for the zkvm/zisk target. This produces the exact precompile results expected when executing the guest ELF inside the zkVM. As a result, for each `zisklib` function invocation, the `HintsProcessor` may generate one or more precompile hint results corresponding to the precompile inputs originally emitted by the guest. From 45aca8e7b1007117fce197855379a8d7deaa6a84 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Fri, 27 Feb 2026 14:49:10 +0000 Subject: [PATCH 668/782] Update environment configuration for version 0.16.0 --- tools/test-env/.env | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/test-env/.env b/tools/test-env/.env index 6f2d91f25..8f357ad44 100644 --- a/tools/test-env/.env +++ b/tools/test-env/.env @@ -1,12 +1,12 @@ -ZISK_BRANCH=tags/v0.15.0 -PIL2_PROOFMAN_BRANCH=tags/v0.15.0 -PIL2_PROOFMAN_JS_BRANCH=tags/v0.15.0 -PIL2_COMPILER_BRANCH=tags/v0.8.0 +ZISK_BRANCH=pre-develop-0.16.0 +PIL2_PROOFMAN_BRANCH=pre-develop-0.16.0 +PIL2_PROOFMAN_JS_BRANCH=pre-develop-0.16.0 +PIL2_COMPILER_BRANCH=tags/v0.9.0 ZISK_TESTVECTORS_BRANCH=main ZISK_TEMPLATE_BRANCH=pre-develop-0.16.0 -ZISK_SETUP_FILE=zisk-provingkey-0.14.0.tar.gz -PACKAGE_SETUP_VERSION=0.14.0 +ZISK_SETUP_FILE=zisk-provingkey-0.16.0.tar.gz +PACKAGE_SETUP_VERSION=0.16.0 SETUP_ADD_DYLIBS=0 PP_INPUTS=pp_input_1_1.bin From 66f74faaf3a3970a4cc6d85006a0c26702e2f440 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Fri, 27 Feb 2026 15:24:55 +0000 Subject: [PATCH 669/782] Update PROJECT_NAME in test_sha_hasher script to "guest" --- tools/test-env/test_sha_hasher.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/test-env/test_sha_hasher.sh b/tools/test-env/test_sha_hasher.sh index 2217ebcad..3f0ce9bd6 100755 --- a/tools/test-env/test_sha_hasher.sh +++ b/tools/test-env/test_sha_hasher.sh @@ -2,7 +2,7 @@ source "./utils.sh" -PROJECT_NAME="sha-hasher" +PROJECT_NAME="guest" EXPECTED_OUTPUT="4fcbc136|2ce46a82|2248a8eb|785f0c7e|9dca7861|7267cace|d028d7e5|f6a2309c|000003e8|deadbeef" main() { From 83caad6910b40c1fec67e5ea79086fcadba6e4ba Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 27 Feb 2026 21:17:38 +0100 Subject: [PATCH 670/782] Split main.c into several code files --- emulator-asm/Makefile | 4 +- emulator-asm/src/asm_provided.hpp | 31 + emulator-asm/src/c_provided.c | 381 +++ emulator-asm/src/c_provided.hpp | 12 + emulator-asm/src/client.c | 1502 +++++++++ emulator-asm/src/client.hpp | 8 + emulator-asm/src/configuration.c | 974 ++++++ emulator-asm/src/configuration.hpp | 7 + emulator-asm/src/constants.hpp | 117 + emulator-asm/src/emu.c | 4 +- emulator-asm/src/emu.hpp | 3 + emulator-asm/src/globals.c | 136 + emulator-asm/src/globals.hpp | 166 + emulator-asm/src/main.c | 5034 +--------------------------- emulator-asm/src/server.c | 958 ++++++ emulator-asm/src/server.hpp | 11 + emulator-asm/src/trace.c | 238 ++ emulator-asm/src/trace.hpp | 12 + emulator-asm/src/trace_logs.c | 678 ++++ emulator-asm/src/trace_logs.hpp | 14 + 20 files changed, 5293 insertions(+), 4997 deletions(-) create mode 100644 emulator-asm/src/asm_provided.hpp create mode 100644 emulator-asm/src/c_provided.c create mode 100644 emulator-asm/src/c_provided.hpp create mode 100644 emulator-asm/src/client.c create mode 100644 emulator-asm/src/client.hpp create mode 100644 emulator-asm/src/configuration.c create mode 100644 emulator-asm/src/configuration.hpp create mode 100644 emulator-asm/src/constants.hpp create mode 100644 emulator-asm/src/globals.c create mode 100644 emulator-asm/src/globals.hpp create mode 100644 emulator-asm/src/server.c create mode 100644 emulator-asm/src/server.hpp create mode 100644 emulator-asm/src/trace.c create mode 100644 emulator-asm/src/trace.hpp create mode 100644 emulator-asm/src/trace_logs.c create mode 100644 emulator-asm/src/trace_logs.hpp diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 1425688d8..75835a40b 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -56,9 +56,9 @@ build/dma.o: $(DMA_OBJS) ld -r $(DMA_OBJS) -o $@ # Compile the final executable -$(OUT_PATH): build/emu.o src/main.c src/emu.c src/chfast/keccak.c build/dma.o +$(OUT_PATH): build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c build/dma.o mkdir -p $(OUT_DIR) - gcc $(CFLAGS) src/main.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ + gcc $(CFLAGS) src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ clean: rm -rf build diff --git a/emulator-asm/src/asm_provided.hpp b/emulator-asm/src/asm_provided.hpp new file mode 100644 index 000000000..9a6b42e5b --- /dev/null +++ b/emulator-asm/src/asm_provided.hpp @@ -0,0 +1,31 @@ +#ifndef EMULATOR_ASM_ASM_PROVIDED_HPP +#define EMULATOR_ASM_ASM_PROVIDED_HPP + +#include + +/**************************/ +/* Assembly-provided code */ +/**************************/ + +// This is the emulator assembly code start function, which will execute the code in the ROM until +// it ends, and generate the trace in the output trace memory. +// It is called from C to start the execution of the assembly code. +void emulator_start(void); + +// These functions are implemented in assembly and provide access to configuration parameters used +// to generate the assembly code, and that in some cases must match the C main program configuration +uint64_t get_max_bios_pc(void); +uint64_t get_max_program_pc(void); +uint64_t get_gen_method(void); // Must match the C main program provided argument +uint64_t get_precompile_results(void); + +// These variables are updated by the assembly code to provide information about the execution +// status and trace generation, accessed by C to generate the response to the client +extern uint64_t MEM_STEP; // Current step, i.e. number of executed instructions, updated by assembly at every step or at the end of every chunk, depending on the generation method +extern uint64_t MEM_END; // Indicates the end of execution +extern uint64_t MEM_ERROR; // Indicates an error during execution +extern uint64_t MEM_TRACE_ADDRESS; // Address of the trace memory +extern uint64_t MEM_CHUNK_ADDRESS; // Address of the current chunk +extern uint64_t MEM_CHUNK_START_STEP; // Step at which the current chunk started + +#endif // EMULATOR_ASM_ASM_PROVIDED_HPP \ No newline at end of file diff --git a/emulator-asm/src/c_provided.c b/emulator-asm/src/c_provided.c new file mode 100644 index 000000000..cecec70cf --- /dev/null +++ b/emulator-asm/src/c_provided.c @@ -0,0 +1,381 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "c_provided.hpp" +#include "globals.hpp" +#include "trace.hpp" +#include "emu.hpp" + +/**************/ +/* TRACE SIZE */ +/**************/ + +void set_trace_size (uint64_t new_trace_size) +{ + // Update trace global variables + // printf("%s trace resize (trace_resize_request: %ld): %ld MB => %ld MB\n", log_name, trace_resize_request, trace_size >> 20, new_trace_size >> 20); + + // trace_resize_request = 0; + + trace_size = new_trace_size; + trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; + pOutputTrace[2] = trace_size; +} + +/**************/ +/* PRINT REGS */ +/**************/ + +//#define PRINT_REGS +#ifdef PRINT_REGS +extern uint64_t reg_0; +extern uint64_t reg_1; +extern uint64_t reg_2; +extern uint64_t reg_3; +extern uint64_t reg_4; +extern uint64_t reg_5; +extern uint64_t reg_6; +extern uint64_t reg_7; +extern uint64_t reg_8; +extern uint64_t reg_9; +extern uint64_t reg_10; +extern uint64_t reg_11; +extern uint64_t reg_12; +extern uint64_t reg_13; +extern uint64_t reg_14; +extern uint64_t reg_15; +extern uint64_t reg_16; +extern uint64_t reg_17; +extern uint64_t reg_18; +extern uint64_t reg_19; +extern uint64_t reg_20; +extern uint64_t reg_21; +extern uint64_t reg_22; +extern uint64_t reg_23; +extern uint64_t reg_24; +extern uint64_t reg_25; +extern uint64_t reg_26; +extern uint64_t reg_27; +extern uint64_t reg_28; +extern uint64_t reg_29; +extern uint64_t reg_30; +extern uint64_t reg_31; +extern uint64_t reg_32; +extern uint64_t reg_33; +extern uint64_t reg_34; +#endif + +// Used for debugging purposes +extern int _print_regs() +{ +#ifdef PRINT_REGS + printf("print_regs()\n"); + printf("\treg[ 0]=%lu=0x%lx=@%p\n", reg_0, reg_0, ®_0); + printf("\treg[ 1]=%lu=0x%lx=@%p\n", reg_1, reg_1, ®_1); + printf("\treg[ 2]=%lu=0x%lx=@%p\n", reg_2, reg_2, ®_2); + printf("\treg[ 3]=%lu=0x%lx=@%p\n", reg_3, reg_3, ®_3); + printf("\treg[ 4]=%lu=0x%lx=@%p\n", reg_4, reg_4, ®_4); + printf("\treg[ 5]=%lu=0x%lx=@%p\n", reg_5, reg_5, ®_5); + printf("\treg[ 6]=%lu=0x%lx=@%p\n", reg_6, reg_6, ®_6); + printf("\treg[ 7]=%lu=0x%lx=@%p\n", reg_7, reg_7, ®_7); + printf("\treg[ 8]=%lu=0x%lx=@%p\n", reg_8, reg_8, ®_8); + printf("\treg[ 9]=%lu=0x%lx=@%p\n", reg_9, reg_9, ®_9); + printf("\treg[10]=%lu=0x%lx=@%p\n", reg_10, reg_10, ®_10); + printf("\treg[11]=%lu=0x%lx=@%p\n", reg_11, reg_11, ®_11); + printf("\treg[12]=%lu=0x%lx=@%p\n", reg_12, reg_12, ®_12); + printf("\treg[13]=%lu=0x%lx=@%p\n", reg_13, reg_13, ®_13); + printf("\treg[14]=%lu=0x%lx=@%p\n", reg_14, reg_14, ®_14); + printf("\treg[15]=%lu=0x%lx=@%p\n", reg_15, reg_15, ®_15); + printf("\treg[16]=%lu=0x%lx=@%p\n", reg_16, reg_16, ®_16); + printf("\treg[17]=%lu=0x%lx=@%p\n", reg_17, reg_17, ®_17); + printf("\treg[18]=%lu=0x%lx=@%p\n", reg_18, reg_18, ®_18); + printf("\treg[19]=%lu=0x%lx=@%p\n", reg_19, reg_19, ®_19); + printf("\treg[20]=%lu=0x%lx=@%p\n", reg_20, reg_20, ®_20); + printf("\treg[21]=%lu=0x%lx=@%p\n", reg_21, reg_21, ®_21); + printf("\treg[22]=%lu=0x%lx=@%p\n", reg_22, reg_22, ®_22); + printf("\treg[23]=%lu=0x%lx=@%p\n", reg_23, reg_23, ®_23); + printf("\treg[24]=%lu=0x%lx=@%p\n", reg_24, reg_24, ®_24); + printf("\treg[25]=%lu=0x%lx=@%p\n", reg_25, reg_25, ®_25); + printf("\treg[26]=%lu=0x%lx=@%p\n", reg_26, reg_26, ®_26); + printf("\treg[27]=%lu=0x%lx=@%p\n", reg_27, reg_27, ®_27); + printf("\treg[28]=%lu=0x%lx=@%p\n", reg_28, reg_28, ®_28); + printf("\treg[29]=%lu=0x%lx=@%p\n", reg_29, reg_29, ®_29); + printf("\treg[30]=%lu=0x%lx=@%p\n", reg_30, reg_30, ®_30); + printf("\treg[31]=%lu=0x%lx=@%p\n", reg_31, reg_31, ®_31); + printf("\treg[32]=%lu=0x%lx=@%p\n", reg_32, reg_32, ®_32); + printf("\treg[33]=%lu=0x%lx=@%p\n", reg_33, reg_33, ®_33); + printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); + printf("\n"); +#endif +} + +/************/ +/* PRINT PC */ +/************/ + +//#define PRINT_PC_DURATION +#ifdef PRINT_PC_DURATION +struct timeval print_pc_tv; +#endif + +// Used for debugging purposes +extern int _print_pc (uint64_t pc, uint64_t c) +{ +#ifdef PRINT_PC_DURATION + print_pc_counter++; + { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t duration = TimeDiff(print_pc_tv, tv); + if (duration > 900) + { + uint64_t chunk = print_pc_counter / chunk_size; + printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); + fflush(stdout); + } + print_pc_tv = tv; + } +#endif + + printf("s=%lu pc=%lx c=%lx", print_pc_counter, pc, c); + +//#define PRINT_PC_REGS +#ifdef PRINT_PC_REGS + /* Used for debugging */ + printf(" r0=%lx", reg_0); + printf(" r1=%lx", reg_1); + printf(" r2=%lx", reg_2); + printf(" r3=%lx", reg_3); + printf(" r4=%lx", reg_4); + printf(" r5=%lx", reg_5); + printf(" r6=%lx", reg_6); + printf(" r7=%lx", reg_7); + printf(" r8=%lx", reg_8); + printf(" r9=%lx", reg_9); + printf(" r10=%lx", reg_10); + printf(" r11=%lx", reg_11); + printf(" r12=%lx", reg_12); + printf(" r13=%lx", reg_13); + printf(" r14=%lx", reg_14); + printf(" r15=%lx", reg_15); + printf(" r16=%lx", reg_16); + printf(" r17=%lx", reg_17); + printf(" r18=%lx", reg_18); + printf(" r19=%lx", reg_19); + printf(" r20=%lx", reg_20); + printf(" r21=%lx", reg_21); + printf(" r22=%lx", reg_22); + printf(" r23=%lx", reg_23); + printf(" r24=%lx", reg_24); + printf(" r25=%lx", reg_25); + printf(" r26=%lx", reg_26); + printf(" r27=%lx", reg_27); + printf(" r28=%lx", reg_28); + printf(" r29=%lx", reg_29); + printf(" r30=%lx", reg_30); + printf(" r31=%lx", reg_31); +#endif + + printf("\n"); + fflush(stdout); + print_pc_counter++; +} + +/**************/ +/* CHUNK DONE */ +/**************/ + +//#define CHUNK_DONE_DURATION +#ifdef CHUNK_DONE_DURATION +uint64_t chunk_done_counter = 0; +struct timeval chunk_done_tv; +#endif + +//#define CHUNK_DONE_SYNC_DURATION +#ifdef CHUNK_DONE_SYNC_DURATION +struct timeval sync_start, sync_stop; +uint64_t sync_duration = 0; +#endif + +// Called by the assembly to notify that a chunk is done and its trace is ready to be consumed +extern void _chunk_done() +{ +#ifdef CHUNK_DONE_DURATION + chunk_done_counter++; + if ((chunk_done_counter & 0xFF) == 0) + { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t duration = TimeDiff(chunk_done_tv, tv); + if (duration > 5000) + { + printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); + fflush(stdout); + } + chunk_done_tv = tv; + } +#endif + +#ifdef CHUNK_DONE_SYNC_DURATION + gettimeofday(&sync_start, NULL); +#endif + + __sync_synchronize(); + +#ifdef CHUNK_DONE_SYNC_DURATION + gettimeofday(&sync_stop, NULL); + sync_duration += TimeDiff(sync_start, sync_stop); + printf("chunk_done() sync_duration=%lu\n", sync_duration); +#endif + + // Notify the caller that a new chunk is done and its trace is ready to be consumed + assert(call_chunk_done); + int result = sem_post(sem_chunk_done); + if (result == -1) + { + printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +/*****************/ +/* REALLOC TRACE */ +/*****************/ + +// Called by the assembly to reallocate the trace when needed, e.g. for the next chunk, +// to increase the trace size by another chunk size +extern void _realloc_trace (void) +{ + // Increase realloc counter + realloc_counter++; + + // Map next chunk of the trace shared memory + trace_map_next_chunk(); + + // Update trace global variables + set_trace_size(trace_total_mapped_size); + +#ifdef DEBUG + if (verbose) printf("realloc_trace() realloc counter=%lu trace_address=0x%lx trace_size=%lu=%lx max_address=0x%lx trace_address_threshold=0x%lx chunk_size=%lu\n", realloc_counter, trace_address, trace_size, trace_size, trace_address + trace_size, trace_address_threshold, chunk_size); +#endif +} + +/*********************************/ +/* WAIT FOR PRECOMPILE AVAILABLE */ +/*********************************/ + +// Called by the assembly when prec_written == prec_read, to wait for new precompile results to be available +int _wait_for_prec_avail (void) +{ + // Increment wait counter + wait_counter++; + + // Sync control output shared memory so that the writer can see the precompile reads we have + // done, and thus update the precompile_written_address if needed + if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Tell the writer that we have read some precompile results + sem_post(sem_prec_read); + + // Make sure the precompile available semaphore is reset before checking the condition, + // since the caller may have posted it (even several times) before we called sem_wait() + while (sem_trywait(sem_prec_avail) == 0) {/*printf("Purging sem_prec_avail\n");*/}; + + // Sync control input shared memory so that we can see the latest precompile_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check if there are already precompile results available + if (*precompile_written_address > *precompile_read_address) + { + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + return 0; + } + + // Wait again, but blocking this time + while (true) + { + struct timespec ts; + int result = clock_gettime(CLOCK_REALTIME, &ts); + if (result == -1) + { + printf("ERROR: wait_for_prec_avail() failed calling clock_gettime() errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + ts.tv_sec += 5; // 5 seconds timeout + + //printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + result = sem_timedwait(sem_prec_avail, &ts); + //printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + if ((result == -1) && (errno != ETIMEDOUT)) + { + printf("ERROR: wait_for_prec_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync control input shared memory so that we can see the latest precompile_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (*precompile_exit_address != 0) + { + printf("ERROR: wait_for_prec_avail() found precompile_exit_address=%lu\n", *precompile_exit_address); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (*precompile_written_address > *precompile_read_address) + { + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + return 0; + } + } + + printf("ERROR: wait_for_prec_avail() unreachable code\n"); + fflush(stdout); + fflush(stderr); + exit(-1); +} \ No newline at end of file diff --git a/emulator-asm/src/c_provided.hpp b/emulator-asm/src/c_provided.hpp new file mode 100644 index 000000000..1b67b537d --- /dev/null +++ b/emulator-asm/src/c_provided.hpp @@ -0,0 +1,12 @@ +#ifndef EMULATOR_ASM_C_PROVIDED_HPP +#define EMULATOR_ASM_C_PROVIDED_HPP + +#include + +extern int _print_regs(); +extern int _print_pc (uint64_t pc, uint64_t c); +extern void _chunk_done(); +extern void _realloc_trace (void); +int _wait_for_prec_avail (void); + +#endif // EMULATOR_ASM_C_PROVIDED_HPP \ No newline at end of file diff --git a/emulator-asm/src/client.c b/emulator-asm/src/client.c new file mode 100644 index 000000000..544ddb9e5 --- /dev/null +++ b/emulator-asm/src/client.c @@ -0,0 +1,1502 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "constants.hpp" +#include "client.hpp" +#include "globals.hpp" +#include "emu.hpp" + +/**********/ +/* CLIENT */ +/**********/ + +void client_setup (void) +{ + assert(!server); + assert(client); + + int result; + + /***********************/ + /* INPUT MINIMAL TRACE */ + /***********************/ + + // Input MT trace + if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) + { + // Create the output shared memory + shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); + if (shmem_mt_fd < 0) + { + printf("ERROR: Failed calling trace shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map it to the trace address +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); +#endif + if (pTrace == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pTrace != TRACE_ADDR) + { + printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); + } + + /**********************/ + /* PRECOMPILE_RESULTS */ + /**********************/ + + if (precompile_results_enabled) + { + /**************/ + /* PRECOMPILE */ + /**************/ + + // Create the precompile results shared memory + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pPrecompile == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_precompile_address = pPrecompile; + precompile_results_address = (uint64_t *)pPrecompile; + + if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); + + /*****************/ + /* CONTROL INPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map control input address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_INPUT_ADDR) + { + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + + /*****************/ + /* CONTROL OUTPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR, 0666); + if (shmem_control_output_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map control input address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); + + /*************************/ + /* PRECOMPILE SEMAPHORES */ + /*************************/ + + // Create the semaphore for precompile results available signal + assert(strlen(sem_prec_avail_name) > 0); + + sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT, 0666, 0); + if (sem_prec_avail == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_avail_name); + + // Create the semaphore for precompile results read signal + assert(strlen(sem_prec_read_name) > 0); + + sem_prec_read = sem_open(sem_prec_read_name, O_CREAT, 0666, 0); + if (sem_prec_read == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_read_name); + } +} + +typedef enum { + PrecompileReadMode_NoPrefix, + PrecompileReadMode_Prefixed +} PrecompileReadMode; + +PrecompileReadMode precompile_read_mode = PrecompileReadMode_NoPrefix; +//PrecompileReadMode precompile_read_mode = PrecompileReadMode_Prefixed; + +typedef enum { + PrecompileWriteMode_Full, + PrecompileWriteMode_OnePrecAtATime +} PrecompileWriteMode; + +PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_Full; +//PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_OnePrecAtATime; + +//#define PRECOMPILE_FIXED_SIZE 25 // Keccak-f state size in u64s +#define PRECOMPILE_FIXED_SIZE 4 // SHA-256 state size in u64s + +void client_write_precompile_results (void) +{ + int result; + +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + + // Open input file + FILE * precompile_fp = fopen(precompile_file_name, "r"); + if (precompile_fp == NULL) + { + printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Get input file size + if (fseek(precompile_fp, 0, SEEK_END) == -1) + { + printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + long precompile_data_size = ftell(precompile_fp); + if (precompile_data_size == -1) + { + printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((precompile_data_size & 0x7) != 0) + { + printf("ERROR: Precompile results file (%s) size (%lu) is not a multiple of 8 B\n", precompile_file_name, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Go back to the first byte + if (fseek(precompile_fp, 0, SEEK_SET) == -1) + { + printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + assert(precompile_read_mode == PrecompileReadMode_NoPrefix || precompile_read_mode == PrecompileReadMode_Prefixed); + assert(precompile_write_mode == PrecompileWriteMode_Full || precompile_write_mode == PrecompileWriteMode_OnePrecAtATime); + + /*************/ + /* NO PREFIX */ + /*************/ + + if (precompile_read_mode == PrecompileReadMode_NoPrefix) + { + if (precompile_write_mode == PrecompileWriteMode_Full) + { + // Check the precompile data size is inside the proper range + if (precompile_data_size > MAX_PRECOMPILE_SIZE) + { + printf("ERROR: Size of precompile results file (%s) is too long (%lu)\n", precompile_file_name, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Copy input data into input memory + size_t precompile_read = fread(precompile_results_address, 1, precompile_data_size, precompile_fp); + if (precompile_read != precompile_data_size) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Initialize precompile written address + *precompile_written_address = precompile_data_size >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + else if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) + { + // Check the precompile data size is inside the proper range + if (precompile_data_size % (PRECOMPILE_FIXED_SIZE * 8) != 0) + { + printf("ERROR: Size of precompile results file (%s) is not a multiple %u * 8 B\n", precompile_file_name, PRECOMPILE_FIXED_SIZE); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Initialize precompile written address to zero + *precompile_written_address = 0; // in u64s + + // Copy in chunks of PRECOMPILE_FIXED_SIZE*8 bytes (Keccak-f state size) + uint64_t precompile_read_so_far = 0; + uint64_t data[PRECOMPILE_FIXED_SIZE]; + while (precompile_read_so_far < (uint64_t)precompile_data_size) + { + // Wait for server to read precompile results + //printf("Waiting for sem_prec_read()\n"); + result = sem_wait(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Number of bytes to read from file and write to shared memory in every loop + uint64_t bytes_to_read = sizeof(data); + + // Copy input data into input memory + size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); + if (precompile_read != bytes_to_read) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Copy data to shared memory + for (int i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); + precompile_read_so_far += 8; + } + + // Notify server that precompile results are available + *precompile_written_address = precompile_read_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + } + } + + /************/ + /* PREFIXED */ + /************/ + + else if (precompile_read_mode == PrecompileReadMode_Prefixed) + { +#define CTRL_START 0x00 +#define CTRL_END 0x01 +#define CTRL_CANCEL 0x02 +#define CTRL_ERROR 0x03 +#define HINTS_TYPE_RESULT 0x04 +#define HINTS_TYPE_ECRECOVER 0x05 +#define NUM_HINT_TYPES 0x06 + + uint64_t precompile_read_so_far = 0; + uint64_t precompile_written_so_far = 0; + + while (precompile_read_so_far < (uint64_t)precompile_data_size) + { + uint64_t data; + uint64_t bytes_to_read = sizeof(data); + + // Copy input data into input memory + size_t precompile_read = fread(&data, 1, bytes_to_read, precompile_fp); + if (precompile_read != bytes_to_read) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); + fflush(stdout); + fflush(stderr); + exit(-1); + } + precompile_read_so_far += bytes_to_read; + switch (data >> 32) + { + case CTRL_START: + //printf("Precompile CTRL_START\n"); + assert(precompile_read_so_far == 8); + break; + case CTRL_END: + //printf("Precompile CTRL_END\n"); + assert(precompile_read_so_far == precompile_data_size); + break; + // case CTRL_CANCEL: + // printf("Precompile CTRL_CANCEL\n"); + // break; + // case CTRL_ERROR: + // printf("Precompile CTRL_ERROR\n"); + // break; + case HINTS_TYPE_RESULT: + { + //printf("Precompile HINTS_TYPE_RESULT\n"); + if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) + { + // Wait for server to read precompile results + //printf("Waiting for sem_prec_read()\n"); + result = sem_wait(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + uint64_t result_length = data & 0xFFFFFFFF; + if (result_length > (precompile_data_size - precompile_read_so_far)) + { + printf("ERROR: Precompile HINTS_TYPE_RESULT length=%lu exceeds remaining file size %lu\n", result_length, precompile_data_size - precompile_read_so_far); + fflush(stdout); + fflush(stderr); + exit(-1); + } + //printf("Precompile HINTS_TYPE_RESULT result_length=%lu\n", result_length); + for (uint64_t i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &value, 8); + precompile_read_so_far += 8; + precompile_written_so_far += 8; + //printf(" Precompile result[%lu] = 0x%016lx\n", i, value); + } + + if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) + { + // Notify server that precompile results are available + *precompile_written_address = precompile_written_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + } + break; + // case HINTS_TYPE_ECRECOVER: + // { + // // Not implemented + // printf("Precompile HINTS_TYPE_ECRECOVER not implemented\n"); + // } + // break; + default: + printf("ERROR: Unknown precompile prefix type %lu\n", data >> 32); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + if (precompile_write_mode == PrecompileWriteMode_Full) + { + // Notify server that precompile results are available + *precompile_written_address = precompile_written_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + + } + + // Close the file pointer + fclose(precompile_fp); + +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + printf("client (precompile): done in %lu us\n", duration); +#endif +} + +void client_run (void) +{ + printf("client_run(): Starting client...\n"); + assert(client); + assert(!server); + + int result; + + /************************/ + /* Read input file data */ + /************************/ + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + + // Open input file + FILE * input_fp = fopen(input_file, "r"); + if (input_fp == NULL) + { + printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Get input file size + if (fseek(input_fp, 0, SEEK_END) == -1) + { + printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + long input_data_size = ftell(input_fp); + if (input_data_size == -1) + { + printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Go back to the first byte + if (fseek(input_fp, 0, SEEK_SET) == -1) + { + printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check the input data size is inside the proper range + if (input_data_size > (MAX_INPUT_SIZE - 16)) + { + printf("ERROR: Size of input file (%s) is too long (%lu)\n", input_file, input_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Open input shared memory + shmem_input_fd = shm_open(shmem_input_name, O_RDWR, 0666); + if (shmem_input_fd < 0) + { + printf("ERROR: Failed calling input shm_open(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map the shared memory object into the process address space + shmem_input_address = mmap(NULL, MAX_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmem_input_fd, 0); + if (shmem_input_address == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Write the input size in the first 64 bits + *(uint64_t *)shmem_input_address = (uint64_t)0; // free input + *(uint64_t *)(shmem_input_address + 8)= (uint64_t)input_data_size; + + // Copy input data into input memory + size_t input_read = fread(shmem_input_address + 16, 1, input_data_size, input_fp); + if (input_read != input_data_size) + { + printf("ERROR: Input read (%lu) != input file size (%lu)\n", input_read, input_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Close the file pointer + fclose(input_fp); + + // Unmap input + result = munmap(shmem_input_address, MAX_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + printf("client (input): done in %lu us\n", duration); +#endif + + } + + /*****************************/ + /* Read precompile file data */ + /*****************************/ + if (precompile_results_enabled) + { + // reset written counter + *precompile_written_address = 0; + + //client_write_precompile_results(); + } + + /*************************/ + /* Connect to the server */ + /*************************/ + + // Create socket to connect to server + int socket_fd; + socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd < 0) + { + printf("ERROR: socket() failed socket_fd=%d errno=%d=%s\n", socket_fd, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Configure server address + struct sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + + result = inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); + if (result <= 0) + { + printf("ERROR: inet_pton() failed. Invalid address/Address not supported result=%d errno=%d=%s\n", result, errno, strerror(errno)); + exit(-1); + } + + // Connect to server + result = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); + if (result < 0) + { + printf("ERROR: connect() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); + exit(-1); + } + if (verbose) printf("connect()'d to port=%u\n", port); + + // Request and response + uint64_t request[5]; + uint64_t response[5]; + + /********/ + /* Ping */ + /********/ + + gettimeofday(&start_time, NULL); + + // Prepare message to send + request[0] = TYPE_PING; + request[1] = 0; + request[2] = 0; + request[3] = 0; + request[4] = 0; + + // Send data to server + result = send(socket_fd, request, sizeof(request), 0); + if (result < 0) + { + printf("ERROR: send() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Read server response + ssize_t bytes_received = recv(socket_fd, response, sizeof(response), MSG_WAITALL); + if (bytes_received < 0) + { + printf("ERROR: recv() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (bytes_received != sizeof(response)) + { + printf("ERROR: recv() returned bytes_received=%ld errno=%d=%s\n", bytes_received, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (response[0] != TYPE_PONG) + { + printf("ERROR: recv() returned unexpected type=%lu\n", response[0]); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (response[1] != gen_method) + { + printf("ERROR: recv() returned unexpected gen_method=%lu\n", response[1]); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + printf("client (PING): done in %lu us\n", duration); + + /*****************/ + /* Minimal trace */ + /*****************/ + for (uint64_t i=0; i +#include +#include +#include +#include +#include "configuration.hpp" +#include "globals.hpp" +#include "asm_provided.hpp" + +/*******************************/ +/* ARGUMENTS AND CONFIGURATION */ +/*******************************/ + +// To be overwritten by arguments, if provided; otherwise, default values per generation method are used +uint16_t arguments_port = 0; + +// Print usage information: valid arguments +void print_usage (void) +{ + printf("Usage: ziskemuasm\n"); + printf("\t-s(server)\n"); + printf("\t-c(client)\n"); + printf("\t-i \n"); + printf("\t-p \n"); + printf("\t--gen=0|--generate_fast\n"); + printf("\t--gen=1|--generate_minimal_trace\n"); + printf("\t--gen=2|--generate_rom_histogram\n"); + printf("\t--gen=3|--generate_main_trace\n"); + printf("\t--gen=4|--generate_chunks\n"); + printf("\t--gen=6|--generate_zip\n"); + printf("\t--gen=9|--generate_mem_reads\n"); + printf("\t--gen=10|--generate_chunk_player_mem_reads\n"); + printf("\t--chunk \n"); + printf("\t--shutdown\n"); + printf("\t--mt \n"); + printf("\t-o output on\n"); + printf("\t--output_riscof output riscof on\n"); + printf("\t--silent silent on\n"); + printf("\t--shm_prefix (default: ZISK)\n"); + printf("\t-m metrics on\n"); + printf("\t-t trace on\n"); + printf("\t-tt trace_trace on\n"); + printf("\t-f(save to file)\n"); + printf("\t-a chunk_address\n"); + printf("\t-v verbose on\n"); + printf("\t-u unlock physical memory in mmap\n"); + printf("\t--share_input_shm share input shared memories\n"); + printf("\t--open_input_shm open existing input shared memories\n"); +#ifdef ASM_PRECOMPILE_CACHE + printf("\t--precompile-cache-store store precompile results in cache file\n"); + printf("\t--precompile-cache-load load precompile results from cache file\n"); +#endif + if (precompile_results_enabled) + { + printf("\t-r \n"); + } + printf("\t--redirect-output-to-file redirect output to file\n"); + printf("\t-h/--help print this\n"); +} + +// Parse main function arguments and configure global variables accordingly +void parse_arguments(int argc, char *argv[]) +{ + strcpy(shm_prefix, "ZISK"); + uint64_t number_of_selected_generation_methods = 0; + if (argc > 1) + { + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "-s") == 0) + { + server = true; + continue; + } + if (strcmp(argv[i], "-c") == 0) + { + client = true; + continue; + } + if ( (strcmp(argv[i], "--gen=0") == 0) || (strcmp(argv[i], "--generate_fast") == 0)) + { + gen_method = Fast; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=1") == 0) || (strcmp(argv[i], "--generate_minimal_trace") == 0)) + { + gen_method = MinimalTrace; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=2") == 0) || (strcmp(argv[i], "--generate_rom_histogram") == 0)) + { + gen_method = RomHistogram; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=3") == 0) || (strcmp(argv[i], "--generate_main_trace") == 0)) + { + gen_method = MainTrace; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=4") == 0) || (strcmp(argv[i], "--generate_chunks") == 0)) + { + gen_method = ChunksOnly; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=6") == 0) || (strcmp(argv[i], "--generate_zip") == 0)) + { + gen_method = Zip; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=7") == 0) || (strcmp(argv[i], "--generate_mem_op") == 0)) + { + gen_method = MemOp; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=8") == 0) || (strcmp(argv[i], "--generate_chunk_player_mt_collect_mem") == 0)) + { + gen_method = ChunkPlayerMTCollectMem; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=9") == 0) || (strcmp(argv[i], "--generate_mem_reads") == 0)) + { + gen_method = MemReads; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=10") == 0) || (strcmp(argv[i], "--generate_chunk_player_mem_reads") == 0)) + { + gen_method = ChunkPlayerMemReadsCollectMain; + number_of_selected_generation_methods++; + continue; + } + if (strcmp(argv[i], "-o") == 0) + { + output = true; + continue; + } + if (strcmp(argv[i], "--output_riscof") == 0) + { + output_riscof = true; + continue; + } + if (strcmp(argv[i], "--silent") == 0) + { + silent = true; + continue; + } + if (strcmp(argv[i], "-m") == 0) + { + metrics = true; + continue; + } + if (strcmp(argv[i], "-t") == 0) + { + trace = true; + continue; + } + if (strcmp(argv[i], "-tt") == 0) + { + trace = true; + trace_trace = true; + continue; + } + if (strcmp(argv[i], "-v") == 0) + { + verbose = true; + //emu_verbose = true; + continue; + } + if (strcmp(argv[i], "-u") == 0) + { + map_locked_flag = 0; + continue; + } + if (strcmp(argv[i], "-h") == 0) + { + print_usage(); + exit(0); + } + if (strcmp(argv[i], "--help") == 0) + { + print_usage(); + continue; + } + if (strcmp(argv[i], "-i") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -i in the last position; please provide input file after it\n"); + print_usage(); + exit(-1); + } + if (strlen(argv[i]) > 4095) + { + printf("ERROR: Detected argument -i but next argument is too long\n"); + print_usage(); + exit(-1); + } + strcpy(input_file, argv[i]); + continue; + } + if (strcmp(argv[i], "--shm_prefix") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -i in the last position; please provide shared mem prefix after it\n"); + print_usage(); + exit(-1); + } + if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) + { + printf("ERROR: Detected argument -i but next argument is too long\n"); + print_usage(); + exit(-1); + } + strcpy(shm_prefix, argv[i]); + continue; + } + if (strcmp(argv[i], "--chunk") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -c in the last position; please provide chunk number after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + chunk_mask = strtoul(argv[i], &endptr, 10); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Chunk number is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argv[i]) { + printf("ERROR: No digits found while parsing chunk number\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after chunk number: %s\n", endptr); + print_usage(); + exit(-1); + } else if (chunk_mask > MAX_CHUNK_MASK) { + printf("ERROR: Invalid chunk number: %lu\n", chunk_mask); + print_usage(); + exit(-1); + } else { + printf("Got chunk_mask= %lu\n", chunk_mask); + } + continue; + } + if (strcmp(argv[i], "--shutdown") == 0) + { + do_shutdown = true; + continue; + } + if (strcmp(argv[i], "--mt") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -mt in the last position; please provide number of MT requests after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + number_of_mt_requests = strtoul(argv[i], &endptr, 10); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Number of MT requests is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argv[i]) { + printf("ERROR: No digits found while parsing number of MT requests\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after number of MT requests: %s\n", endptr); + print_usage(); + exit(-1); + } else if (number_of_mt_requests > 1000000) { + printf("ERROR: Invalid number of MT requests: %lu\n", number_of_mt_requests); + print_usage(); + exit(-1); + } else { + printf("Got number of MT requests= %lu\n", number_of_mt_requests); + } + continue; + } + if (strcmp(argv[i], "-p") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -p in the last position; please provide port number after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + arguments_port = strtoul(argv[i], &endptr, 10); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Port number is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argv[i]) { + printf("ERROR: No digits found while parsing port number\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after port number: %s\n", endptr); + print_usage(); + exit(-1); + } else { + printf("Got port number= %u\n", arguments_port); + } + continue; + } + if (strcmp(argv[i], "-f") == 0) + { + save_to_file = true; + continue; + } + if (strcmp(argv[i], "-a") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -a in the last position; please provide chunk address after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + char * argument = argv[i]; + if ((argument[0] == '0') && (argument[1] == 'x')) argument += 2; + chunk_player_address = strtoul(argv[i], &endptr, 16); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Chunk address is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argument) { + printf("ERROR: No digits found while parsing chunk addresss\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after chunk address: %s\n", endptr); + print_usage(); + exit(-1); + } else { + printf("Got chunk address= %p\n", (void *)chunk_player_address); + } + continue; + } + if (strcmp(argv[i], "--share_input_shm") == 0) + { + share_input_shm = true; + continue; + } + if (strcmp(argv[i], "--open_input_shm") == 0) + { + open_input_shm = true; + continue; + } + if (strcmp(argv[i], "--redirect-output-to-file") == 0) + { + redirect_output_to_file = true; + continue; + } +#ifdef ASM_PRECOMPILE_CACHE + if (strcmp(argv[i], "--precompile-cache-store") == 0) + { + precompile_cache_enabled = true; + precompile_cache_store_init(); + continue; + } + if (strcmp(argv[i], "--precompile-cache-load") == 0) + { + precompile_cache_enabled = true; + precompile_cache_load_init(); + continue; + } + +#endif + if (precompile_results_enabled && (strcmp(argv[i], "-r") == 0)) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -r in the last position; please provide precompile results file after it\n"); + print_usage(); + exit(-1); + } + if (strlen(argv[i]) > 4095) + { + printf("ERROR: Detected argument -r but next argument is too long\n"); + print_usage(); + exit(-1); + } + strcpy(precompile_file_name, argv[i]); + continue; + } + printf("ERROR: parse_arguments() Unrecognized argument: %s\n", argv[i]); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } +#ifdef ASM_PRECOMPILE_CACHE + if (precompile_cache_enabled == false) + { + printf("ERROR: parse_arguments() when in precompile cache mode, you need to use an argument: either --precompile-cache-store or --precompile-cache-load\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } +#endif + + // Check that only one generation method was selected as an argument + if (number_of_selected_generation_methods != 1) + { + printf("ERROR! parse_arguments() Invalid arguments: select 1 generation method, and only one\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check that the generation method selected by the process launcher is the same as the one + // for which the assembly code was generated + uint64_t asm_gen_method = get_gen_method(); + if (asm_gen_method != gen_method) + { + printf("ERROR! parse_arguments() Inconsistency: C generation method is %u but ASM generation method is %lu\n", + gen_method, + asm_gen_method); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check server/client + if (server && client) + { + printf("ERROR! parse_arguments() Inconsistency: both server and client at the same time is not possible\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (!server && !client) + { + printf("ERROR! parse_arguments() Inconsistency: select server or client\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (precompile_results_enabled && client && (strlen(precompile_file_name) == 0)) + { + printf("ERROR! parse_arguments() when in precompile results mode, you need to provide a precompile results file using -r \n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +// Configure global variables based on generation method and other arguments +void configure (void) +{ + // Select configuration based on generation method + switch (gen_method) + { + case Fast: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_FT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_FT_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_FT_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_FT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_FT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_FT_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, ""); + strcpy(sem_chunk_done_name, ""); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_FT_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_FT"); + port = 23120; + break; + } + case MinimalTrace: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MT_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MT_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MT_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MT_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MT_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MT_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MT"); + call_chunk_done = true; + port = 23115; + break; + } + case RomHistogram: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_RH_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_RH_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_RH_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_RH_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_RH_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_RH_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_RH_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_RH_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_RH_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_RH"); + call_chunk_done = true; + port = 23116; + break; + } + case MainTrace: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MA_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MA_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MA_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MA_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MA_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MA_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MA_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MA_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MA_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MA"); + call_chunk_done = true; + port = 23118; + break; + } + case ChunksOnly: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CH_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CH_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_CH_input"); + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_CH_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_CH_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_CH_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_CH"); + call_chunk_done = true; + port = 23115; + break; + } + // case BusOp: + // { + // strcpy(shmem_input_name, "ZISKBO_input"); + // strcpy(shmem_output_name, "ZISKBO_output"); + // strcpy(sem_chunk_done_name, "ZISKBO_chunk_done"); + // chunk_done = true; + // port = 23115; + // break; + // } + case Zip: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_ZP_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_ZP_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_ZP_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_ZP_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_ZP_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_ZP_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_ZP_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_ZP_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_ZP_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_ZP"); + call_chunk_done = true; + port = 23115; + break; + } + case MemOp: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MO_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MO_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MO_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MO_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MO_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MO_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MO_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MO_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MO_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MO"); + call_chunk_done = true; + port = 23117; + break; + } + case ChunkPlayerMTCollectMem: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CM_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CM_control_output"); + strcpy(shmem_input_name, ""); + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_CM_output"); + strcpy(sem_chunk_done_name, ""); + strcpy(sem_shutdown_done_name, ""); + strcpy(shmem_mt_name, shm_prefix); + strcat(shmem_mt_name, "_MT_output"); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_CM"); + call_chunk_done = false; + port = 23119; + break; + } + case MemReads: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MT_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MT_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MT_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MT_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MT_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MT_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MT"); + call_chunk_done = true; + port = 23115; + break; + } + case ChunkPlayerMemReadsCollectMain: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CA_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CA_control_output"); + strcpy(shmem_input_name, ""); + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_CA_output"); + strcpy(sem_chunk_done_name, ""); + strcpy(sem_shutdown_done_name, ""); + strcpy(shmem_mt_name, shm_prefix); + strcat(shmem_mt_name, "_MT_output"); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_CA"); + call_chunk_done = false; + port = 23120; + break; + } + default: + { + printf("ERROR: configure() Invalid gen_method = %u\n", gen_method); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + if (precompile_results_enabled && (gen_method == ChunkPlayerMTCollectMem || gen_method == ChunkPlayerMemReadsCollectMain)) + { + printf("ERROR: configure() precompile results enabled is not compatible with generation method %u\n", gen_method); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (arguments_port != 0) + { + port = arguments_port; + } + + if (verbose) + { + printf("ziskemuasm configuration:\n"); + printf("\tgen_method=%u\n", gen_method); + printf("\tshm_prefix=%s\n", shm_prefix); + printf("\tfile_lock_name=%s\n", file_lock_name); + printf("\tlog_name=%s\n", log_name); + printf("\tport=%u\n", port); + printf("\tcall_chunk_done=%u\n", call_chunk_done); + printf("\tchunk_size=%lu\n", chunk_size); + printf("\tshmem_control_input=%s\n", shmem_control_input_name); + printf("\tshmem_control_output=%s\n", shmem_control_output_name); + printf("\tshmem_input=%s\n", shmem_input_name); + printf("\tshmem_precompile=%s\n", shmem_precompile_name); + printf("\tshmem_output=%s\n", shmem_output_name); + printf("\tshmem_mt=%s\n", shmem_mt_name); + printf("\tsem_chunk_done=%s\n", sem_chunk_done_name); + printf("\tsem_shutdown_done=%s\n", sem_shutdown_done_name); + printf("\tsem_prec_avail=%s\n", sem_prec_avail_name); + printf("\tsem_prec_read=%s\n", sem_prec_read_name); + printf("\tmap_locked_flag=%d\n", map_locked_flag); + printf("\toutput=%u\n", output); + printf("\tprecompile_results_enabled=%u\n", precompile_results_enabled); + printf("\toutput_riscof=%u\n", output_riscof); + } +} \ No newline at end of file diff --git a/emulator-asm/src/configuration.hpp b/emulator-asm/src/configuration.hpp new file mode 100644 index 000000000..8c9cc2c40 --- /dev/null +++ b/emulator-asm/src/configuration.hpp @@ -0,0 +1,7 @@ +#ifndef EMULATOR_ASM_CONFIGURATION_HPP +#define EMULATOR_ASM_CONFIGURATION_HPP + +void parse_arguments(int argc, char *argv[]); +void configure (void); + +#endif // EMULATOR_ASM_CONFIGURATION_HPP \ No newline at end of file diff --git a/emulator-asm/src/constants.hpp b/emulator-asm/src/constants.hpp new file mode 100644 index 000000000..9860a6a22 --- /dev/null +++ b/emulator-asm/src/constants.hpp @@ -0,0 +1,117 @@ +#ifndef EMULATOR_ASM_CONSTANTS_HPP +#define EMULATOR_ASM_CONSTANTS_HPP + +/***************/ +/* Definitions */ +/***************/ + +// Address map +// There definitions must match the ZisK rust code ones at core/src/mem.rs used to generate the +// assembly code, and that are used by the assembly code to access memory and generate the trace +#define ROM_ADDR (uint64_t)0x80000000 +#define ROM_SIZE (uint64_t)0x08000000 // 128MB +#define INPUT_ADDR (uint64_t)0x90000000 +#define MAX_INPUT_SIZE (uint64_t)0x08000000 // 128MB + +#define RAM_ADDR (uint64_t)0xA0000000 +#define RAM_SIZE (uint64_t)0x20000000 // 512MB +#define SYS_ADDR RAM_ADDR +#define SYS_SIZE (uint64_t)0x10000 +#define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) + +#ifdef TRACE_TARGET_MO + #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ + #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ +#elif defined(TRACE_TARGET_RH) + #define TRACE_INITIAL_SIZE (uint64_t)0x004000000 /* 64MB */ + #define TRACE_DELTA_SIZE (uint64_t)0x004000000 /* 64MB */ +#else + #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ + #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ +#endif + +#define TRACE_ADDR (uint64_t)0xd0000000 +#define TRACE_MAX_SIZE (uint64_t)0x1000000000 // 64GB +#define TRACE_NUMBER_OF_CHUNKS (((TRACE_MAX_SIZE - TRACE_INITIAL_SIZE) / TRACE_DELTA_SIZE) + 1) +#define TRACE_SIZE_GRANULARITY (1014*1014) // ROM histogram trace size is round up to a multiple of this granularity + +// Control input and output shared memory configuration. +// Control input is used to tell the assembly code how many precompile result u64 fields have been +// written by the client. Control output is used to tell the client how many precompile result u64 +// fields have been read by the assembly code, so the client can know when it can write new +// precompile results. Assembly code waits when the number of read fields is not lower than the +// number of written fields, and client waits when the number of written fields would exceed the +// number of read fields plus the available precompile shared memory size, which is a circular buffer +#define CONTROL_INPUT_ADDR (uint64_t)0x70000000 +#define CONTROL_INPUT_SIZE (uint64_t)0x1000 // 4kB +#define CONTROL_OUTPUT_ADDR (uint64_t)0x70001000 +#define CONTROL_OUTPUT_SIZE (uint64_t)0x1000 // 4kB +#define CONTROL_RETRY_DELAY_US 1000 // 1ms +#define CONTROL_NUMBER_OF_RETRIES 1000 // 1s max total + +// Maximum number of steps to execute, used by the client to limit the execution steps of the +// assembly code. This limit is set by the ZisK PIL constraints. +#define MAX_STEPS (1ULL << 36) + +// Assembly service request/response types +// Only the methods supported by the configured generation method will be implemented by the server, +// e.g. gen_method=1 => PING, MT and SHUTDOWN; the rest will fail with an error response. +#define TYPE_PING 1 // Ping +#define TYPE_PONG 2 +#define TYPE_MT_REQUEST 3 // Minimal trace +#define TYPE_MT_RESPONSE 4 +#define TYPE_RH_REQUEST 5 // ROM histogram +#define TYPE_RH_RESPONSE 6 +#define TYPE_MO_REQUEST 7 // Memory opcode +#define TYPE_MO_RESPONSE 8 +#define TYPE_MA_REQUEST 9 // Main packed trace +#define TYPE_MA_RESPONSE 10 +#define TYPE_CM_REQUEST 11 // Collect memory trace +#define TYPE_CM_RESPONSE 12 +#define TYPE_FA_REQUEST 13 // Fast mode, do not generate any trace +#define TYPE_FA_RESPONSE 14 +#define TYPE_MR_REQUEST 15 // Mem reads +#define TYPE_MR_RESPONSE 16 +#define TYPE_CA_REQUEST 17 // Collect main trace +#define TYPE_CA_RESPONSE 18 +#define TYPE_SD_REQUEST 1000000 // Shutdown +#define TYPE_SD_RESPONSE 1000001 + +// Server IP address, used by the client to connect to the server +#define SERVER_IP "127.0.0.1" // Change to your server IP; otherwise use localhost IP address + +// Chunk size used in generation methods that generate a trace chunk at every N steps, e.g. gen_method=1 or gen_method=7. +// It must be a power of two, and it is used to calculate the trace address threshold at which the next chunk must be mapped, +// to avoid reaching the end of the currently mapped trace memory. +#define CHUNK_SIZE (1ULL << 18) + +// Maximum trace chunk size, used to determine when the trace address is close to the end of the +// currently mapped trace memory and the next chunk must be mapped. It is calculated based on the +// maximum number of bytes that can be generated in a chunk +// Worst case: every chunk instruction is a keccak operation, with an input data of 200 bytes +// (let's use 256 bytes to be safe), and the trace includes the access to 2 source registers, 2 +// destination registers and 3 memory addresses (e.g. for a keccak operation with 3 memory operands), +// which are the maximum number of registers and memory addresses that can be accessed by a chunk +// instruction, according to the ZisK assembly code generation configuration. + +#define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) +#define MAX_TRACE_CHUNK_INFO ((44*8) + 32) +#define MAX_BYTES_DIRECT_MTRACE 256 +#define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) +#define MAX_CHUNK_TRACE_SIZE ((CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) + +// Maximum precompile results share memory size +// It is a circular buffer +#define MAX_PRECOMPILE_SIZE (uint64_t)0x400000 // 4MB + +// Maximum chunk mask for zip generation method, which indicates which chunks are included in the trace, +// and must be between 0 and 7 (inclusive), as it is used to generate a mask of 8 bits where each +// bit indicates if the corresponding chunk is included in the trace or not. +#define MAX_CHUNK_MASK 7 + +// Maximum length of the shared memory prefix, e.g. "ZISK_12345" +// This prefix is used to generate the names of the shared memories and semaphores used for +// communication and synchronization between the server and the client, +#define MAX_SHM_PREFIX_LENGTH 64 + +#endif // EMULATOR_ASM_CONSTANTS_HPP \ No newline at end of file diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index baf58a523..f61338ae7 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -612,9 +612,9 @@ extern int _opcode_blake2(uint64_t * address) #endif #ifdef DEBUG #ifdef ASM_CALL_METRICS - if (emu_verbose) printf("opcode_blake2() calling blake2b() counter=%lu address=%08lx\n", asm_call_metrics.blake2_counter, address); + if (emu_verbose) printf("opcode_blake2() calling blake2b() counter=%lu address=%p\n", asm_call_metrics.blake2_counter, address); #else - if (emu_verbose) printf("opcode_blake2() calling blake2b() address=%08lx\n", address); + if (emu_verbose) printf("opcode_blake2() calling blake2b() address=%p\n", address); #endif #endif diff --git a/emulator-asm/src/emu.hpp b/emulator-asm/src/emu.hpp index cae983a9c..057966094 100644 --- a/emulator-asm/src/emu.hpp +++ b/emulator-asm/src/emu.hpp @@ -2,6 +2,9 @@ #define EMU_ASM_HPP #include +#include + +uint64_t TimeDiff(const struct timeval startTime, const struct timeval endTime); #ifdef DEBUG extern bool emu_verbose; diff --git a/emulator-asm/src/globals.c b/emulator-asm/src/globals.c new file mode 100644 index 000000000..adb546b41 --- /dev/null +++ b/emulator-asm/src/globals.c @@ -0,0 +1,136 @@ +#define _GNU_SOURCE +#include +#include "constants.hpp" +#include "globals.hpp" + +// Configuration globals, set by arguments +bool output = false; +bool output_riscof = false; +bool silent = false; +bool metrics = false; +bool trace = false; +bool trace_trace = false; +bool verbose = false; +bool save_to_file = false; +bool share_input_shm = false; +bool open_input_shm = false; +char input_file[4096] = {0}; +bool redirect_output_to_file = false; +bool server = false; +bool client = false; +char shm_prefix[MAX_SHM_PREFIX_LENGTH] = {0}; +int map_locked_flag = MAP_LOCKED; +uint64_t chunk_mask = 0x0; +bool do_shutdown = false; +uint64_t number_of_mt_requests = 1; +uint16_t port = 0; +uint64_t chunk_player_address = 0; +char precompile_file_name[4096] = {0}; +char shmem_control_input_name[128] = {0}; +char shmem_control_output_name[128] = {0}; +char shmem_input_name[128] = {0}; +char shmem_output_name[128] = {0}; +char shmem_mt_name[128] = {0}; +char shmem_precompile_name[128] = {0}; +char sem_prec_avail_name[128] = {0}; +char sem_prec_read_name[128] = {0}; +char sem_chunk_done_name[128] = {0}; +char sem_shutdown_done_name[128] = {0}; +char file_lock_name[128] = {0}; +char log_name[128] = {0}; +bool call_chunk_done = false; + +// Configuration set by assembly code, accessed by C +bool precompile_results_enabled = false; + +// Default generation method, can be overridden by the --gen argument +GenMethod gen_method = Fast; + +// To be used when calculating partial durations +// Time measurements cannot be overlapped +struct timeval start_time; +struct timeval stop_time; +uint64_t duration; + +/*****************/ +/* SHARED MEMORY */ +/*****************/ + +// Input shared memory +int shmem_input_fd = -1; +uint64_t shmem_input_size = 0; +void * shmem_input_address = NULL; + +// Output trace shared memory +int shmem_output_fd = -1; + +// Input MT trace shared memory +int shmem_mt_fd = -1; + +// Chunk done semaphore: notifies the caller when a new chunk has been processed +sem_t * sem_chunk_done = NULL; + +// Shutdown done semaphore: notifies the caller when a shutdown has been processed +sem_t * sem_shutdown_done = NULL; + +/**************************/ +/* PRECOMPILE AND CONTROL */ +/**************************/ + +uint64_t * precompile_results_address = NULL; + +// Precompile results shared memory +int shmem_precompile_fd = -1; +uint64_t shmem_precompile_size = 0; +void * shmem_precompile_address = NULL; + +// Precompile results semaphores +sem_t * sem_prec_avail = NULL; +sem_t * sem_prec_read = NULL; + +// Control input shared memory +int shmem_control_input_fd = -1; +uint64_t * shmem_control_input_address = NULL; +volatile uint64_t * precompile_written_address = NULL; +volatile uint64_t * precompile_exit_address = NULL; + +// Control output shared memory +int shmem_control_output_fd = -1; +uint64_t * shmem_control_output_address = NULL; +volatile uint64_t * precompile_read_address = NULL; + +/**************/ +/* TRACE SIZE */ +/**************/ + +uint64_t initial_trace_size = TRACE_INITIAL_SIZE; +uint64_t trace_address = TRACE_ADDR; +uint64_t trace_size = TRACE_INITIAL_SIZE; +uint64_t trace_used_size = 0; +uint64_t trace_address_threshold = TRACE_ADDR + TRACE_INITIAL_SIZE - MAX_CHUNK_TRACE_SIZE; + +// To be used when calculating the assembly duration +uint64_t assembly_duration; + +// Counters used in functions called from assembly code +uint64_t realloc_counter = 0; +uint64_t wait_counter = 0; +uint64_t print_pc_counter = 0; + +// Chunk player globals +uint64_t chunk_player_mt_size = TRACE_INITIAL_SIZE; + +// Maximum number of steps to execute, used by the client to limit the execution steps of the +// assembly code. +uint64_t max_steps = (1ULL << 32); + +// Pointers to the input, RAM, ROM and trace memory, used by both C and assembly code to access these memories +uint64_t * pInputTrace = (uint64_t *)TRACE_ADDR; // Used for trace consumption, i.e. chunk player +uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; // Used for trace generation, i.e. assembly code writes the trace to this address, and client reads it from this address + +/**************/ +/* CHUNK SIZE */ +/**************/ + +uint64_t chunk_size = CHUNK_SIZE; +uint64_t chunk_size_mask = CHUNK_SIZE - 1; \ No newline at end of file diff --git a/emulator-asm/src/globals.hpp b/emulator-asm/src/globals.hpp new file mode 100644 index 000000000..f66e0044c --- /dev/null +++ b/emulator-asm/src/globals.hpp @@ -0,0 +1,166 @@ +#ifndef EMULATOR_ASM_GLOBALS_HPP +#define EMULATOR_ASM_GLOBALS_HPP + +#include +#include +#include +#include +#include "constants.hpp" + +// Configuration globals, set by arguments +extern bool output; +extern bool output_riscof; +extern bool silent; +extern bool metrics; +extern bool trace; +extern bool trace_trace; +extern bool verbose; +extern bool save_to_file; +extern bool share_input_shm; // Shares input shared memories: input, precompile results and control input, using a common name +extern bool open_input_shm; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) +extern char input_file[4096]; +extern bool redirect_output_to_file; +extern bool server; // Indicates that this process is a server +extern bool client; // Indicates that this process is a client (used for testing the server) +extern char shm_prefix[MAX_SHM_PREFIX_LENGTH]; // Shared memory prefix +extern int map_locked_flag; // Flag used in mmap to indicate if the physical memory is locked in RAM (MAP_LOCKED) or can be swapped (0). By default it is locked, but it can be unlocked with the -u argument, which can be useful for testing and debugging purposes, e.g. to allow core dumps when the assembly code crashes +extern uint64_t chunk_mask; // ZIP: 0, 1, 2, 3, 4, 5, 6 or 7 +extern bool do_shutdown; // If true, the client will perform a shutdown request to the server when done +extern uint64_t number_of_mt_requests; // Loop to send this number of minimal trace requests +extern uint16_t port; // Service TCP port +extern uint64_t chunk_player_address; // Chunk player address, used for generation methods that use the chunk player, i.e. gen_method=8 or gen_method=10 +extern char precompile_file_name[4096]; // Precompile results file name (used by client) +extern char shmem_control_input_name[128]; +extern char shmem_control_output_name[128]; +extern char shmem_input_name[128]; +extern char shmem_output_name[128]; +extern char shmem_mt_name[128]; +extern char shmem_precompile_name[128]; +extern char sem_prec_avail_name[128]; +extern char sem_prec_read_name[128]; +extern char sem_chunk_done_name[128]; +extern char sem_shutdown_done_name[128]; +extern char file_lock_name[128]; +extern char log_name[128]; +extern bool call_chunk_done; + +// Configuration set by assembly code, accessed by C +extern bool precompile_results_enabled; + +/*********************/ +/* Generation method */ +/*********************/ + +// Specifies how the assembly code generates the trace, and what information it includes. +// It is specified with the mandatory argument --gen= +// It must match the value returned by the assembly function get_gen_method() +// The enum names are equivalent to the rust ones defined in core/src/riscv2zisk.rs as AsmGenerationMethod +// ZisK uses generation methods 1 (minimal trace), 2 (ROM histogram) and 7 (memory operations) +// but the rest of methods can be used for testing and debugging purposes +typedef enum { + Fast = 0, + MinimalTrace = 1, + RomHistogram = 2, + MainTrace = 3, + ChunksOnly = 4, + //BusOp = 5, + Zip = 6, + MemOp = 7, + ChunkPlayerMTCollectMem = 8, + MemReads = 9, + ChunkPlayerMemReadsCollectMain = 10, +} GenMethod; + +// Default generation method, can be overridden by the --gen argument +extern GenMethod gen_method; + +// To be used when calculating partial durations +// Time measurements cannot be overlapped +extern struct timeval start_time; +extern struct timeval stop_time; +extern uint64_t duration; + +/*****************/ +/* SHARED MEMORY */ +/*****************/ + +// Input shared memory +extern int shmem_input_fd; +extern uint64_t shmem_input_size; +extern void * shmem_input_address; + +// Output trace shared memory +extern int shmem_output_fd; + +// Input MT trace shared memory +extern int shmem_mt_fd; + +// Chunk done semaphore: notifies the caller when a new chunk has been processed +extern sem_t * sem_chunk_done; + +// Shutdown done semaphore: notifies the caller when a shutdown has been processed +extern sem_t * sem_shutdown_done; + +/**************************/ +/* PRECOMPILE AND CONTROL */ +/**************************/ + +extern uint64_t * precompile_results_address; + +// Precompile results shared memory +extern int shmem_precompile_fd; +extern uint64_t shmem_precompile_size; +extern void * shmem_precompile_address; + +// Precompile results semaphores +extern sem_t * sem_prec_avail; +extern sem_t * sem_prec_read; + +// Control input shared memory +extern int shmem_control_input_fd; +extern uint64_t * shmem_control_input_address; +extern volatile uint64_t * precompile_written_address; +extern volatile uint64_t * precompile_exit_address; + +// Control output shared memory +extern int shmem_control_output_fd; +extern uint64_t * shmem_control_output_address; +extern volatile uint64_t * precompile_read_address; + +/**************/ +/* TRACE SIZE */ +/**************/ + +extern uint64_t initial_trace_size; +extern uint64_t trace_address; +extern uint64_t trace_size; +extern uint64_t trace_used_size; +extern uint64_t trace_address_threshold; + +// To be used when calculating the assembly duration +extern uint64_t assembly_duration; + +// Counters used in functions called from assembly code +extern uint64_t realloc_counter; +extern uint64_t wait_counter; +extern uint64_t print_pc_counter; + +// Chunk player globals +extern uint64_t chunk_player_mt_size; + +// Maximum number of steps to execute, used by the client to limit the execution steps of the +// assembly code. +extern uint64_t max_steps; + +// Pointers to the input, RAM, ROM and trace memory, used by both C and assembly code to access these memories +extern uint64_t * pInputTrace; // Used for trace consumption, i.e. chunk player +extern uint64_t * pOutputTrace; // Used for trace generation, i.e. assembly code writes the trace to this address, and client reads it from this address + +/**************/ +/* CHUNK SIZE */ +/**************/ + +extern uint64_t chunk_size; +extern uint64_t chunk_size_mask; + +#endif // EMULATOR_ASM_GLOBALS_HPP \ No newline at end of file diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 154efe8d4..fe1708be2 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -19,191 +19,14 @@ #include #include #include +#include "constants.hpp" #include "emu.hpp" - -/**************************/ -/* Assembly-provided code */ -/**************************/ - -// This is the emulator assembly code start function, which will execute the code in the ROM until -// it ends, and generate the trace in the output trace memory. -// It is called from C to start the execution of the assembly code. -void emulator_start(void); - -// These functions are implemented in assembly and provide access to configuration parameters used -// to generate the assembly code, and that in some cases must match the C main program configuration -uint64_t get_max_bios_pc(void); -uint64_t get_max_program_pc(void); -uint64_t get_gen_method(void); // Must match the C main program provided argument -uint64_t get_precompile_results(void); - -// These variables are updated by the assembly code to provide information about the execution -// status and trace generation, accessed by C to generate the response to the client -extern uint64_t MEM_STEP; // Current step, i.e. number of executed instructions, updated by assembly at every step or at the end of every chunk, depending on the generation method -extern uint64_t MEM_END; // Indicates the end of execution -extern uint64_t MEM_ERROR; // Indicates an error during execution -extern uint64_t MEM_TRACE_ADDRESS; // Address of the trace memory -extern uint64_t MEM_CHUNK_ADDRESS; // Address of the current chunk -extern uint64_t MEM_CHUNK_START_STEP; // Step at which the current chunk started - -/***************/ -/* Definitions */ -/***************/ - -// Address map -// There definitions must match the ZisK rust code ones at core/src/mem.rs used to generate the -// assembly code, and that are used by the assembly code to access memory and generate the trace -#define ROM_ADDR (uint64_t)0x80000000 -#define ROM_SIZE (uint64_t)0x08000000 // 128MB -#define INPUT_ADDR (uint64_t)0x90000000 -#define MAX_INPUT_SIZE (uint64_t)0x08000000 // 128MB - -#define RAM_ADDR (uint64_t)0xA0000000 -#define RAM_SIZE (uint64_t)0x20000000 // 512MB -#define SYS_ADDR RAM_ADDR -#define SYS_SIZE (uint64_t)0x10000 -#define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) - -#ifdef TRACE_TARGET_MO - #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ - #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ -#elif defined(TRACE_TARGET_RH) - #define TRACE_INITIAL_SIZE (uint64_t)0x004000000 /* 64MB */ - #define TRACE_DELTA_SIZE (uint64_t)0x004000000 /* 64MB */ -#else - #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ - #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ -#endif - -#define TRACE_ADDR (uint64_t)0xd0000000 -#define TRACE_MAX_SIZE (uint64_t)0x1000000000 // 64GB -#define TRACE_NUMBER_OF_CHUNKS (((TRACE_MAX_SIZE - TRACE_INITIAL_SIZE) / TRACE_DELTA_SIZE) + 1) -#define TRACE_SIZE_GRANULARITY (1014*1014) // ROM histogram trace size is round up to a multiple of this granularity - -// Worst case: every chunk instruction is a keccak operation, with an input data of 256 bytes - -// #if defined(TRACE_TARGET_MO) -// #define MAX_MTRACE_REGS_ACCESS_SIZE (3 * 8) -// #define MAX_BYTES_DIRECT_MTRACE 80 // PRE/POST = ((1 PRE + 2 SRC + 1 WR DST) * 2 + BLOCK_READ + BLOCK_WRITE) * 8 -// #define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) -// #define MAX_CHUNK_TRACE_SIZE (CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) -// #elif defined(TRACE_TARGET_RH) -// #define MAX_CHUNK_TRACE_SIZE 0 -// #else -// #define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) -// #define MAX_TRACE_CHUNK_INFO ((44*8) + 32) -// #define MAX_BYTES_DIRECT_MTRACE 256 -// #define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) -// #define MAX_CHUNK_TRACE_SIZE ((CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) -// #endif -// uint64_t trace_resize_request = 0; -// uint64_t trace_address_threshold = TRACE_ADDR + INITIAL_TRACE_SIZE - MAX_CHUNK_TRACE_SIZE; - -// Control input and output shared memory configuration. -// Control input is used to tell the assembly code how many precompile result u64 fields have been -// written by the client. Control output is used to tell the client how many precompile result u64 -// fields have been read by the assembly code, so the client can know when it can write new -// precompile results. Assembly code waits when the number of read fields is not lower than the -// number of written fields, and client waits when the number of written fields would exceed the -// number of read fields plus the available precompile shared memory size, which is a circular buffer -#define CONTROL_INPUT_ADDR (uint64_t)0x70000000 -#define CONTROL_INPUT_SIZE (uint64_t)0x1000 // 4kB -#define CONTROL_OUTPUT_ADDR (uint64_t)0x70001000 -#define CONTROL_OUTPUT_SIZE (uint64_t)0x1000 // 4kB -#define CONTROL_RETRY_DELAY_US 1000 // 1ms -#define CONTROL_NUMBER_OF_RETRIES 1000 // 1s max total - -// Maximum number of steps to execute, used by the client to limit the execution steps of the -// assembly code. This limit is set by the ZisK PIL constraints. -#define MAX_STEPS (1ULL << 36) - -// Assembly service request/response types -// Only the methods supported by the configured generation method will be implemented by the server, -// e.g. gen_method=1 => PING, MT and SHUTDOWN; the rest will fail with an error response. -#define TYPE_PING 1 // Ping -#define TYPE_PONG 2 -#define TYPE_MT_REQUEST 3 // Minimal trace -#define TYPE_MT_RESPONSE 4 -#define TYPE_RH_REQUEST 5 // ROM histogram -#define TYPE_RH_RESPONSE 6 -#define TYPE_MO_REQUEST 7 // Memory opcode -#define TYPE_MO_RESPONSE 8 -#define TYPE_MA_REQUEST 9 // Main packed trace -#define TYPE_MA_RESPONSE 10 -#define TYPE_CM_REQUEST 11 // Collect memory trace -#define TYPE_CM_RESPONSE 12 -#define TYPE_FA_REQUEST 13 // Fast mode, do not generate any trace -#define TYPE_FA_RESPONSE 14 -#define TYPE_MR_REQUEST 15 // Mem reads -#define TYPE_MR_RESPONSE 16 -#define TYPE_CA_REQUEST 17 // Collect main trace -#define TYPE_CA_RESPONSE 18 -#define TYPE_SD_REQUEST 1000000 // Shutdown -#define TYPE_SD_RESPONSE 1000001 - -// Server IP address, used by the client to connect to the server -#define SERVER_IP "127.0.0.1" // Change to your server IP; otherwise use localhost IP address - -// Chunk size used in generation methods that generate a trace chunk at every N steps, e.g. gen_method=1 or gen_method=7. -// It must be a power of two, and it is used to calculate the trace address threshold at which the next chunk must be mapped, -// to avoid reaching the end of the currently mapped trace memory. -#define CHUNK_SIZE (1ULL << 18) - -// Maximum trace chunk size, used to determine when the trace address is close to the end of the -// currently mapped trace memory and the next chunk must be mapped. It is calculated based on the -// maximum number of bytes that can be generated in a chunk -// Worst case: every chunk instruction is a keccak operation, with an input data of 200 bytes -// (let's use 256 bytes to be safe), and the trace includes the access to 2 source registers, 2 -// destination registers and 3 memory addresses (e.g. for a keccak operation with 3 memory operands), -// which are the maximum number of registers and memory addresses that can be accessed by a chunk -// instruction, according to the ZisK assembly code generation configuration. - -#define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) -#define MAX_TRACE_CHUNK_INFO ((44*8) + 32) -#define MAX_BYTES_DIRECT_MTRACE 256 -#define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) -#define MAX_CHUNK_TRACE_SIZE ((CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) - -// Maximum precompile results share memory size -// It is a circular buffer -#define MAX_PRECOMPILE_SIZE (uint64_t)0x400000 // 4MB - -// Maximum chunk mask for zip generation method, which indicates which chunks are included in the trace, -// and must be between 0 and 7 (inclusive), as it is used to generate a mask of 8 bits where each -// bit indicates if the corresponding chunk is included in the trace or not. -#define MAX_CHUNK_MASK 7 - -// Maximum length of the shared memory prefix, e.g. "ZISK_12345" -// This prefix is used to generate the names of the shared memories and semaphores used for -// communication and synchronization between the server and the client, -#define MAX_SHM_PREFIX_LENGTH 64 - -/*********************/ -/* Generation method */ -/*********************/ - -// Specifies how the assembly code generates the trace, and what information it includes. -// It is specified with the mandatory argument --gen= -// It must match the value returned by the assembly function get_gen_method() -// The enum names are equivalent to the rust ones defined in core/src/riscv2zisk.rs as AsmGenerationMethod -// ZisK uses generation methods 1 (minimal trace), 2 (ROM histogram) and 7 (memory operations) -// but the rest of methods can be used for testing and debugging purposes -typedef enum { - Fast = 0, - MinimalTrace = 1, - RomHistogram = 2, - MainTrace = 3, - ChunksOnly = 4, - //BusOp = 5, - Zip = 6, - MemOp = 7, - ChunkPlayerMTCollectMem = 8, - MemReads = 9, - ChunkPlayerMemReadsCollectMain = 10, -} GenMethod; - -// Default generation method, can be overridden by the --gen argument -GenMethod gen_method = Fast; +#include "asm_provided.hpp" +#include "globals.hpp" +#include "configuration.hpp" +#include "server.hpp" +#include "client.hpp" +#include "trace.hpp" // Returns the acronym of the generation method, used for logging and file naming const char * gen_method_acronym(GenMethod method) @@ -225,44 +48,11 @@ const char * gen_method_acronym(GenMethod method) } } -// Pointers to the input, RAM, ROM and trace memory, used by both C and assembly code to access these memories -uint64_t * pInputTrace = (uint64_t *)TRACE_ADDR; // Used for trace consumption, i.e. chunk player -uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; // Used for trace generation, i.e. assembly code writes the trace to this address, and client reads it from this address - -// Service TCP parameters -uint16_t port = 0; -uint16_t arguments_port = 0; - -// Type of execution -bool server = false; -bool client = false; -bool call_chunk_done = false; -bool do_shutdown = false; // If true, the client will perform a shutdown request to the server when done -uint64_t number_of_mt_requests = 1; // Loop to send this number of minimal trace requests - -// To be used when calculating partial durations -// Time measurements cannot be overlapped -struct timeval start_time; -struct timeval stop_time; -uint64_t duration; - // To be used when calculating total duration struct timeval total_start_time; struct timeval total_stop_time; uint64_t total_duration; -// To be used when calculating the assembly duration -uint64_t assembly_duration; - -// Counters used in functions called from assembly code -uint64_t realloc_counter = 0; -uint64_t wait_counter = 0; -uint64_t print_pc_counter = 0; - -// Chunk player globals -uint64_t chunk_player_address = 0; -uint64_t chunk_player_mt_size = TRACE_INITIAL_SIZE; - // Checks if a number is a power of two, used to validate the max steps and chunk size provided by the client bool is_power_of_two (uint64_t number) { @@ -273,10 +63,6 @@ bool is_power_of_two (uint64_t number) /* MAX STEPS */ /*************/ -// Maximum number of steps to execute, used by the client to limit the execution steps of the -// assembly code. -uint64_t max_steps = (1ULL << 32); - // Sets the maximum number of steps provided by the client in the request void set_max_steps (uint64_t new_max_steps) { @@ -290,39 +76,10 @@ void set_max_steps (uint64_t new_max_steps) max_steps = new_max_steps; } -uint64_t trace_address_threshold = TRACE_ADDR + TRACE_INITIAL_SIZE - MAX_CHUNK_TRACE_SIZE; - -int map_locked_flag = MAP_LOCKED; - - -/**************/ -/* TRACE SIZE */ -/**************/ - -uint64_t initial_trace_size = TRACE_INITIAL_SIZE; -uint64_t trace_address = TRACE_ADDR; -uint64_t trace_size = TRACE_INITIAL_SIZE; -uint64_t trace_used_size = 0; - -void set_trace_size (uint64_t new_trace_size) -{ - // Update trace global variables - // printf("%s trace resize (trace_resize_request: %ld): %ld MB => %ld MB\n", log_name, trace_resize_request, trace_size >> 20, new_trace_size >> 20); - - // trace_resize_request = 0; - - trace_size = new_trace_size; - trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; - pOutputTrace[2] = trace_size; -} - /**************/ /* CHUNK SIZE */ /**************/ -uint64_t chunk_size = CHUNK_SIZE; -uint64_t chunk_size_mask = CHUNK_SIZE - 1; - void set_chunk_size (uint64_t new_chunk_size) { if (!is_power_of_two(new_chunk_size)) @@ -337,350 +94,25 @@ void set_chunk_size (uint64_t new_chunk_size) trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; } -void parse_arguments(int argc, char *argv[]); -uint64_t TimeDiff(const struct timeval startTime, const struct timeval endTime); -void configure (void); -void server_setup (void); -void server_reset_fast (void); -void server_reset_slow (void); -void server_reset_trace (void); -void server_run (void); -void server_cleanup (void); -void client_setup (void); -void client_run (void); -void client_cleanup (void); -void _chunk_done(void); -void log_minimal_trace(void); -void log_histogram(void); -void log_main_trace(void); -void log_mem_trace(void); -void log_mem_op(void); -void save_mem_op_to_files(void); -void log_chunk_player_main_trace(void); -int recv_all_with_timeout (int sockfd, void *buffer, size_t length, int flags, int timeout_sec); -void file_lock(void); - -// Configuration globals, set by arguments -bool output = false; -bool output_riscof = false; -bool silent = false; -bool metrics = false; -bool trace = false; -bool trace_trace = false; -bool verbose = false; -bool save_to_file = false; -bool share_input_shm = false; // Shares input shared memories: input, precompile results and control input, using a common name -bool open_input_shm = false; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) -char input_file[4096] = {0}; -bool redirect_output_to_file = false; +//#define USE_FILE_LOCK -// ROM histogram -uint64_t histogram_size = 0; -uint64_t bios_size = 0; -uint64_t program_size = 0; +#ifdef USE_FILE_LOCK -// Zip -uint64_t chunk_mask = 0x0; // 0, 1, 2, 3, 4, 5, 6 or 7 - -/*****************/ -/* SHARED MEMORY */ -/*****************/ - -// Shared memory prefix -char shm_prefix[MAX_SHM_PREFIX_LENGTH]; - -// Input shared memory -char shmem_input_name[128]; -int shmem_input_fd = -1; -uint64_t shmem_input_size = 0; -void * shmem_input_address = NULL; - -// Output trace shared memory -char shmem_output_name[128]; -int shmem_output_fd = -1; - -// Input MT trace shared memory -char shmem_mt_name[128]; -int shmem_mt_fd = -1; - -// Chunk done semaphore: notifies the caller when a new chunk has been processed -char sem_chunk_done_name[128]; -sem_t * sem_chunk_done = NULL; - -// Shutdown done semaphore: notifies the caller when a shutdown has been processed -char sem_shutdown_done_name[128]; -sem_t * sem_shutdown_done = NULL; +void file_lock(void); // File lock name, used to lock a file that indicates that the assembly emulator process is running, // to prevent multiple instances of the server from running at the same time. -char file_lock_name[128]; int file_lock_fd = -1; -// Log name -char log_name[128]; +#endif // USE_FILE_LOCK // Process id int process_id = 0; -/**************************/ -/* PRECOMPILE AND CONTROL */ -/**************************/ - #ifdef ASM_PRECOMPILE_CACHE bool precompile_cache_enabled = false; #endif -bool precompile_results_enabled = false; -uint64_t * precompile_results_address = NULL; - -// Precompile results shared memory -char shmem_precompile_name[128]; -int shmem_precompile_fd = -1; -uint64_t shmem_precompile_size = 0; -void * shmem_precompile_address = NULL; - -// Precompile results semaphores -char sem_prec_avail_name[128]; -sem_t * sem_prec_avail = NULL; -char sem_prec_read_name[128]; -sem_t * sem_prec_read = NULL; - -// Precompile results file name (used by client) -char precompile_file_name[4096] = {0}; - -// Control input shared memory -char shmem_control_input_name[128]; -int shmem_control_input_fd = -1; -uint64_t * shmem_control_input_address = NULL; -volatile uint64_t * precompile_written_address = NULL; -volatile uint64_t * precompile_exit_address = NULL; - -// Control output shared memory -char shmem_control_output_name[128]; -int shmem_control_output_fd = -1; -uint64_t * shmem_control_output_address = NULL; -volatile uint64_t * precompile_read_address = NULL; - -/********************/ -/* TRACE ALLOCATION */ -/********************/ - -void trace_virtual_alloc (void) -{ - void * addr = mmap((void *)TRACE_ADDR, TRACE_MAX_SIZE, PROT_NONE, MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (addr != (void *)TRACE_ADDR) - { - printf("ERROR: trace_virtual_alloc() failed to reserve trace memory at address 0x%lx\n", TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -uint64_t next_chunk_id = 0; // Next trace chunk id to be mapped, starting from 0 -int trace_chunk_fd[TRACE_NUMBER_OF_CHUNKS]; // File descriptors for each chunk -uint64_t trace_total_mapped_size = 0; // Total mapped trace size - -void * trace_get_chunk_address (uint64_t chunk_id) -{ - assert(gen_method != RomHistogram || chunk_id == 0); - - if (chunk_id == 0) - { - return (void *)TRACE_ADDR; - } - else - { - return (void *)(TRACE_ADDR + TRACE_INITIAL_SIZE + ((chunk_id - 1) * TRACE_DELTA_SIZE)); - } -} - -uint64_t trace_get_chunk_size (uint64_t chunk_id) -{ - if (gen_method == RomHistogram) { - assert(chunk_id == 0); - return trace_size; - } - - if (chunk_id == 0) - { - return TRACE_INITIAL_SIZE; - } - else - { - return TRACE_DELTA_SIZE; - } -} - -void trace_generate_shmem_chunk_name(char * shmem_chunk_name, size_t shmem_chunk_name_size, uint64_t chunk_id) -{ - int result = snprintf(shmem_chunk_name, shmem_chunk_name_size, "%s_%lu", shmem_output_name, chunk_id); - if (result < 0 || result >= (int)shmem_chunk_name_size) - { - printf("ERROR: trace_generate_shmem_chunk_name() failed to create chunk shared memory name\n"); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -void trace_cleanup (void) -{ - // Unmap all mapped chunks - for (uint64_t chunk_id = 0; chunk_id < next_chunk_id; chunk_id++) - { - uint64_t chunk_size = trace_get_chunk_size(chunk_id); - void * chunk_address = trace_get_chunk_address(chunk_id); - int result = munmap(chunk_address, chunk_size); - if (result != 0) - { - printf("ERROR: trace_cleanup() failed calling munmap() chunk id=%lu size=%lu B address=0x%lx errno=%d=%s\n", chunk_id, chunk_size, (uint64_t)chunk_address, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Close the chunk shared memory file descriptor - close(trace_chunk_fd[chunk_id]); - trace_chunk_fd[chunk_id] = -1; - - // Build the chunk shared memory name - char shmem_chunk_name[128]; - trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); - - // Make sure the chunk shared memory is deleted - shm_unlink(shmem_chunk_name); - } - - // Reset next chunk id - next_chunk_id = 0; -} - -void trace_preventive_cleanup (void) -{ - // Unmap all mapped chunks - for (uint64_t chunk_id = 0; chunk_id < TRACE_NUMBER_OF_CHUNKS; chunk_id++) - { - // Build the chunk shared memory name - char shmem_chunk_name[128]; - trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); - - // Make sure the chunk shared memory is deleted - int result = shm_unlink(shmem_chunk_name); - if (result != 0) - { - break; - } - if (verbose) printf("trace_preventive_cleanup() unlinked chunk shared memory %s\n", shmem_chunk_name); - } -} - -void trace_map_next_chunk (void) -{ - // Get the next chunk id, size and address - uint64_t chunk_id = next_chunk_id; - if (chunk_id >= TRACE_NUMBER_OF_CHUNKS) - { - printf("ERROR: trace_map_next_chunk() exceeded maximum number of chunks %lu\n", TRACE_NUMBER_OF_CHUNKS); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t chunk_size = trace_get_chunk_size(chunk_id); - void * chunk_address = trace_get_chunk_address(chunk_id); - - if (verbose) printf("trace_map_next_chunk() mapping chunk id=%lu size=%lu B address=0x%lx\n", chunk_id, chunk_size, (uint64_t)chunk_address); - - // Build the chunk shared memory name - char shmem_chunk_name[128]; - trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); - - // Make sure the chunk shared memory is deleted - shm_unlink(shmem_chunk_name); - - // Create the output shared memory - trace_chunk_fd[chunk_id] = shm_open(shmem_chunk_name, O_RDWR | O_CREAT | O_EXCL, 0666); - if (trace_chunk_fd[chunk_id] < 0) - { - printf("ERROR: trace_map_next_chunk() failed calling trace shm_open(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Size it - int result = ftruncate(trace_chunk_fd[chunk_id], chunk_size); - if (result != 0) - { - printf("ERROR: trace_map_next_chunk() failed calling ftruncate(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Sync - fsync(trace_chunk_fd[chunk_id]); - - // Map it to the trace address - if (verbose) gettimeofday(&start_time, NULL); - void * requested_address; - if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) - { - requested_address = 0; - } - else - { - requested_address = (void *)chunk_address; - } - int flags = MAP_SHARED | map_locked_flag; - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - flags |= MAP_FIXED; - } - void * pTrace = mmap(requested_address, chunk_size, PROT_READ | PROT_WRITE, flags, trace_chunk_fd[chunk_id], 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pTrace == MAP_FAILED) - { - printf("ERROR: trace_map_next_chunk() failed calling mmap(pTrace) name=%s errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && ((uint64_t)pTrace != (uint64_t)requested_address)) - { - printf("ERROR: trace_map_next_chunk() called mmap(trace) but returned address = %p != 0x%lx\n", pTrace, (uint64_t)requested_address); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("trace_map_next_chunk() mapped %lu B to %s and returned address %p in %lu us\n", chunk_size, shmem_chunk_name, pTrace, duration); - - // Update total mapped size - trace_total_mapped_size += chunk_size; - - // Increment next chunk id - next_chunk_id++; -} - -void trace_map_initialize (void) -{ - // Perform preventive cleanup of any leftover shared memory chunks - trace_preventive_cleanup(); - - // Reserve the full virtual trace address space - trace_virtual_alloc(); - - // Map the first chunk, i.e. chunk 0 - trace_map_next_chunk(); - - trace_address = TRACE_ADDR; - pOutputTrace = (uint64_t *)TRACE_ADDR; -} - /********/ /* MAIN */ /********/ @@ -739,11 +171,13 @@ int main(int argc, char *argv[]) // Configure based on parguments configure(); +#ifdef USE_FILE_LOCK // Lock file - // if (server) - // { - // file_lock(); - // } + if (server) + { + file_lock(); + } +#endif // Send a message to stderr // fprintf(stderr, "%s stderr test (not an error): Starting Ziskemu ASM emulator process id=%d server=%d client=%d gen_method=%d port=%u\n", log_name, process_id, server, client, gen_method, port); @@ -1262,4418 +696,32 @@ int main(int argc, char *argv[]) #endif } -/*******************************/ -/* ARGUMENTS AND CONFIGURATION */ -/*******************************/ +/*************/ +/* FILE LOCK */ +/*************/ + +#ifdef USE_FILE_LOCK -// Print usage information: valid arguments -void print_usage (void) +// Lock file exclusively to ensure that only one instance of the program is running at a time +void file_lock(void) { - printf("Usage: ziskemuasm\n"); - printf("\t-s(server)\n"); - printf("\t-c(client)\n"); - printf("\t-i \n"); - printf("\t-p \n"); - printf("\t--gen=0|--generate_fast\n"); - printf("\t--gen=1|--generate_minimal_trace\n"); - printf("\t--gen=2|--generate_rom_histogram\n"); - printf("\t--gen=3|--generate_main_trace\n"); - printf("\t--gen=4|--generate_chunks\n"); - printf("\t--gen=6|--generate_zip\n"); - printf("\t--gen=9|--generate_mem_reads\n"); - printf("\t--gen=10|--generate_chunk_player_mem_reads\n"); - printf("\t--chunk \n"); - printf("\t--shutdown\n"); - printf("\t--mt \n"); - printf("\t-o output on\n"); - printf("\t--output_riscof output riscof on\n"); - printf("\t--silent silent on\n"); - printf("\t--shm_prefix (default: ZISK)\n"); - printf("\t-m metrics on\n"); - printf("\t-t trace on\n"); - printf("\t-tt trace_trace on\n"); - printf("\t-f(save to file)\n"); - printf("\t-a chunk_address\n"); - printf("\t-v verbose on\n"); - printf("\t-u unlock physical memory in mmap\n"); - printf("\t--share_input_shm share input shared memories\n"); - printf("\t--open_input_shm open existing input shared memories\n"); -#ifdef ASM_PRECOMPILE_CACHE - printf("\t--precompile-cache-store store precompile results in cache file\n"); - printf("\t--precompile-cache-load load precompile results from cache file\n"); -#endif - if (precompile_results_enabled) - { - printf("\t-r \n"); + // Open (or create) the lock file. We don't need to write to it. + file_lock_fd = open(file_lock_name, O_CREAT | O_RDONLY, 0644); + if (file_lock_fd == -1) { + printf("ERROR: file_lock() failed calling open(%s) errno=%d=%s\n", file_lock_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(1); + } + + // Try to acquire an exclusive lock, non-blocking. + if (flock(file_lock_fd, LOCK_EX | LOCK_NB) == -1) { + // If we fail to get the lock, another instance is running. + printf("ERROR: Another instance of this program is already running.\n"); + fflush(stdout); + fflush(stderr); + exit(1); } - printf("\t--redirect-output-to-file redirect output to file\n"); - printf("\t-h/--help print this\n"); } -// Parse main function arguments and configure global variables accordingly -void parse_arguments(int argc, char *argv[]) -{ - strcpy(shm_prefix, "ZISK"); - uint64_t number_of_selected_generation_methods = 0; - if (argc > 1) - { - for (int i = 1; i < argc; i++) - { - if (strcmp(argv[i], "-s") == 0) - { - server = true; - continue; - } - if (strcmp(argv[i], "-c") == 0) - { - client = true; - continue; - } - if ( (strcmp(argv[i], "--gen=0") == 0) || (strcmp(argv[i], "--generate_fast") == 0)) - { - gen_method = Fast; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=1") == 0) || (strcmp(argv[i], "--generate_minimal_trace") == 0)) - { - gen_method = MinimalTrace; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=2") == 0) || (strcmp(argv[i], "--generate_rom_histogram") == 0)) - { - gen_method = RomHistogram; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=3") == 0) || (strcmp(argv[i], "--generate_main_trace") == 0)) - { - gen_method = MainTrace; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=4") == 0) || (strcmp(argv[i], "--generate_chunks") == 0)) - { - gen_method = ChunksOnly; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=6") == 0) || (strcmp(argv[i], "--generate_zip") == 0)) - { - gen_method = Zip; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=7") == 0) || (strcmp(argv[i], "--generate_mem_op") == 0)) - { - gen_method = MemOp; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=8") == 0) || (strcmp(argv[i], "--generate_chunk_player_mt_collect_mem") == 0)) - { - gen_method = ChunkPlayerMTCollectMem; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=9") == 0) || (strcmp(argv[i], "--generate_mem_reads") == 0)) - { - gen_method = MemReads; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=10") == 0) || (strcmp(argv[i], "--generate_chunk_player_mem_reads") == 0)) - { - gen_method = ChunkPlayerMemReadsCollectMain; - number_of_selected_generation_methods++; - continue; - } - if (strcmp(argv[i], "-o") == 0) - { - output = true; - continue; - } - if (strcmp(argv[i], "--output_riscof") == 0) - { - output_riscof = true; - continue; - } - if (strcmp(argv[i], "--silent") == 0) - { - silent = true; - continue; - } - if (strcmp(argv[i], "-m") == 0) - { - metrics = true; - continue; - } - if (strcmp(argv[i], "-t") == 0) - { - trace = true; - continue; - } - if (strcmp(argv[i], "-tt") == 0) - { - trace = true; - trace_trace = true; - continue; - } - if (strcmp(argv[i], "-v") == 0) - { - verbose = true; - //emu_verbose = true; - continue; - } - if (strcmp(argv[i], "-u") == 0) - { - map_locked_flag = 0; - continue; - } - if (strcmp(argv[i], "-h") == 0) - { - print_usage(); - exit(0); - } - if (strcmp(argv[i], "--help") == 0) - { - print_usage(); - continue; - } - if (strcmp(argv[i], "-i") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -i in the last position; please provide input file after it\n"); - print_usage(); - exit(-1); - } - if (strlen(argv[i]) > 4095) - { - printf("ERROR: Detected argument -i but next argument is too long\n"); - print_usage(); - exit(-1); - } - strcpy(input_file, argv[i]); - continue; - } - if (strcmp(argv[i], "--shm_prefix") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -i in the last position; please provide shared mem prefix after it\n"); - print_usage(); - exit(-1); - } - if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) - { - printf("ERROR: Detected argument -i but next argument is too long\n"); - print_usage(); - exit(-1); - } - strcpy(shm_prefix, argv[i]); - continue; - } - if (strcmp(argv[i], "--chunk") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -c in the last position; please provide chunk number after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - chunk_mask = strtoul(argv[i], &endptr, 10); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Chunk number is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argv[i]) { - printf("ERROR: No digits found while parsing chunk number\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after chunk number: %s\n", endptr); - print_usage(); - exit(-1); - } else if (chunk_mask > MAX_CHUNK_MASK) { - printf("ERROR: Invalid chunk number: %lu\n", chunk_mask); - print_usage(); - exit(-1); - } else { - printf("Got chunk_mask= %lu\n", chunk_mask); - } - continue; - } - if (strcmp(argv[i], "--shutdown") == 0) - { - do_shutdown = true; - continue; - } - if (strcmp(argv[i], "--mt") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -mt in the last position; please provide number of MT requests after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - number_of_mt_requests = strtoul(argv[i], &endptr, 10); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Number of MT requests is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argv[i]) { - printf("ERROR: No digits found while parsing number of MT requests\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after number of MT requests: %s\n", endptr); - print_usage(); - exit(-1); - } else if (number_of_mt_requests > 1000000) { - printf("ERROR: Invalid number of MT requests: %lu\n", number_of_mt_requests); - print_usage(); - exit(-1); - } else { - printf("Got number of MT requests= %lu\n", number_of_mt_requests); - } - continue; - } - if (strcmp(argv[i], "-p") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -p in the last position; please provide port number after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - arguments_port = strtoul(argv[i], &endptr, 10); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Port number is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argv[i]) { - printf("ERROR: No digits found while parsing port number\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after port number: %s\n", endptr); - print_usage(); - exit(-1); - } else { - printf("Got port number= %u\n", arguments_port); - } - continue; - } - if (strcmp(argv[i], "-f") == 0) - { - save_to_file = true; - continue; - } - if (strcmp(argv[i], "-a") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -a in the last position; please provide chunk address after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - char * argument = argv[i]; - if ((argument[0] == '0') && (argument[1] == 'x')) argument += 2; - chunk_player_address = strtoul(argv[i], &endptr, 16); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Chunk address is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argument) { - printf("ERROR: No digits found while parsing chunk addresss\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after chunk address: %s\n", endptr); - print_usage(); - exit(-1); - } else { - printf("Got chunk address= %p\n", (void *)chunk_player_address); - } - continue; - } - if (strcmp(argv[i], "--share_input_shm") == 0) - { - share_input_shm = true; - continue; - } - if (strcmp(argv[i], "--open_input_shm") == 0) - { - open_input_shm = true; - continue; - } - if (strcmp(argv[i], "--redirect-output-to-file") == 0) - { - redirect_output_to_file = true; - continue; - } -#ifdef ASM_PRECOMPILE_CACHE - if (strcmp(argv[i], "--precompile-cache-store") == 0) - { - precompile_cache_enabled = true; - precompile_cache_store_init(); - continue; - } - if (strcmp(argv[i], "--precompile-cache-load") == 0) - { - precompile_cache_enabled = true; - precompile_cache_load_init(); - continue; - } - -#endif - if (precompile_results_enabled && (strcmp(argv[i], "-r") == 0)) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -r in the last position; please provide precompile results file after it\n"); - print_usage(); - exit(-1); - } - if (strlen(argv[i]) > 4095) - { - printf("ERROR: Detected argument -r but next argument is too long\n"); - print_usage(); - exit(-1); - } - strcpy(precompile_file_name, argv[i]); - continue; - } - printf("ERROR: parse_arguments() Unrecognized argument: %s\n", argv[i]); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } -#ifdef ASM_PRECOMPILE_CACHE - if (precompile_cache_enabled == false) - { - printf("ERROR: parse_arguments() when in precompile cache mode, you need to use an argument: either --precompile-cache-store or --precompile-cache-load\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } -#endif - - // Check that only one generation method was selected as an argument - if (number_of_selected_generation_methods != 1) - { - printf("ERROR! parse_arguments() Invalid arguments: select 1 generation method, and only one\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check that the generation method selected by the process launcher is the same as the one - // for which the assembly code was generated - uint64_t asm_gen_method = get_gen_method(); - if (asm_gen_method != gen_method) - { - printf("ERROR! parse_arguments() Inconsistency: C generation method is %u but ASM generation method is %lu\n", - gen_method, - asm_gen_method); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check server/client - if (server && client) - { - printf("ERROR! parse_arguments() Inconsistency: both server and client at the same time is not possible\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (!server && !client) - { - printf("ERROR! parse_arguments() Inconsistency: select server or client\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (precompile_results_enabled && client && (strlen(precompile_file_name) == 0)) - { - printf("ERROR! parse_arguments() when in precompile results mode, you need to provide a precompile results file using -r \n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -// Configure global variables based on generation method and other arguments -void configure (void) -{ - // Select configuration based on generation method - switch (gen_method) - { - case Fast: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_FT_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_FT_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_FT_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_FT_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_FT_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_FT_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, ""); - strcpy(sem_chunk_done_name, ""); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_FT_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_FT"); - port = 23120; - break; - } - case MinimalTrace: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MT_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MT_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MT_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MT_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MT_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MT_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MT_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MT_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MT_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MT"); - call_chunk_done = true; - port = 23115; - break; - } - case RomHistogram: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_RH_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_RH_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_RH_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_RH_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_RH_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_RH_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_RH_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_RH_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_RH_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_RH"); - call_chunk_done = true; - port = 23116; - break; - } - case MainTrace: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MA_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MA_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MA_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MA_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MA_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MA_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MA_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MA_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MA_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MA"); - call_chunk_done = true; - port = 23118; - break; - } - case ChunksOnly: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_CH_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_CH_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_CH_input"); - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_CH_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_CH_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_CH_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_CH"); - call_chunk_done = true; - port = 23115; - break; - } - // case BusOp: - // { - // strcpy(shmem_input_name, "ZISKBO_input"); - // strcpy(shmem_output_name, "ZISKBO_output"); - // strcpy(sem_chunk_done_name, "ZISKBO_chunk_done"); - // chunk_done = true; - // port = 23115; - // break; - // } - case Zip: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_ZP_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_ZP_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_ZP_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_ZP_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_ZP_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_ZP_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_ZP_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_ZP_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_ZP_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_ZP"); - call_chunk_done = true; - port = 23115; - break; - } - case MemOp: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MO_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MO_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MO_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MO_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MO_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MO_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MO_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MO_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MO_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MO"); - call_chunk_done = true; - port = 23117; - break; - } - case ChunkPlayerMTCollectMem: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_CM_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_CM_control_output"); - strcpy(shmem_input_name, ""); - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_CM_output"); - strcpy(sem_chunk_done_name, ""); - strcpy(sem_shutdown_done_name, ""); - strcpy(shmem_mt_name, shm_prefix); - strcat(shmem_mt_name, "_MT_output"); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_CM"); - call_chunk_done = false; - port = 23119; - break; - } - case MemReads: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MT_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MT_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MT_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MT_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MT_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MT_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MT_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MT_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MT_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MT"); - call_chunk_done = true; - port = 23115; - break; - } - case ChunkPlayerMemReadsCollectMain: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_CA_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_CA_control_output"); - strcpy(shmem_input_name, ""); - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_CA_output"); - strcpy(sem_chunk_done_name, ""); - strcpy(sem_shutdown_done_name, ""); - strcpy(shmem_mt_name, shm_prefix); - strcat(shmem_mt_name, "_MT_output"); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_CA"); - call_chunk_done = false; - port = 23120; - break; - } - default: - { - printf("ERROR: configure() Invalid gen_method = %u\n", gen_method); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - if (precompile_results_enabled && (gen_method == ChunkPlayerMTCollectMem || gen_method == ChunkPlayerMemReadsCollectMain)) - { - printf("ERROR: configure() precompile results enabled is not compatible with generation method %u\n", gen_method); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (arguments_port != 0) - { - port = arguments_port; - } - - if (verbose) - { - printf("ziskemuasm configuration:\n"); - printf("\tgen_method=%u\n", gen_method); - printf("\tshm_prefix=%s\n", shm_prefix); - printf("\tfile_lock_name=%s\n", file_lock_name); - printf("\tlog_name=%s\n", log_name); - printf("\tport=%u\n", port); - printf("\tcall_chunk_done=%u\n", call_chunk_done); - printf("\tchunk_size=%lu\n", chunk_size); - printf("\tshmem_control_input=%s\n", shmem_control_input_name); - printf("\tshmem_control_output=%s\n", shmem_control_output_name); - printf("\tshmem_input=%s\n", shmem_input_name); - printf("\tshmem_precompile=%s\n", shmem_precompile_name); - printf("\tshmem_output=%s\n", shmem_output_name); - printf("\tshmem_mt=%s\n", shmem_mt_name); - printf("\tsem_chunk_done=%s\n", sem_chunk_done_name); - printf("\tsem_shutdown_done=%s\n", sem_shutdown_done_name); - printf("\tsem_prec_avail=%s\n", sem_prec_avail_name); - printf("\tsem_prec_read=%s\n", sem_prec_read_name); - printf("\tmap_locked_flag=%d\n", map_locked_flag); - printf("\toutput=%u\n", output); - printf("\tprecompile_results_enabled=%u\n", precompile_results_enabled); - printf("\toutput_riscof=%u\n", output_riscof); - } -} - -/**********/ -/* CLIENT */ -/**********/ - -void client_setup (void) -{ - assert(!server); - assert(client); - - int result; - - /***********************/ - /* INPUT MINIMAL TRACE */ - /***********************/ - - // Input MT trace - if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) - { - // Create the output shared memory - shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); - if (shmem_mt_fd < 0) - { - printf("ERROR: Failed calling trace shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map it to the trace address -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); -#endif - if (pTrace == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((uint64_t)pTrace != TRACE_ADDR) - { - printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); - } - - /**********************/ - /* PRECOMPILE_RESULTS */ - /**********************/ - - if (precompile_results_enabled) - { - /**************/ - /* PRECOMPILE */ - /**************/ - - // Create the precompile results shared memory - shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); - if (shmem_precompile_fd < 0) - { - printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map precompile address space - if (verbose) gettimeofday(&start_time, NULL); - void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pPrecompile == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(precompile) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_precompile_address = pPrecompile; - precompile_results_address = (uint64_t *)pPrecompile; - - if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); - - /*****************/ - /* CONTROL INPUT */ - /*****************/ - - // Create the control input shared memory - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR, 0666); - if (shmem_control_input_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map control input address space - if (verbose) gettimeofday(&start_time, NULL); - void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_INPUT_ADDR) - { - printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_control_input_address = (uint64_t *)pControl; - precompile_written_address = &shmem_control_input_address[0]; - precompile_exit_address = &shmem_control_input_address[1]; - if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); - - /*****************/ - /* CONTROL OUTPUT */ - /*****************/ - - // Create the control input shared memory - shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR, 0666); - if (shmem_control_output_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map control input address space - if (verbose) gettimeofday(&start_time, NULL); - pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_OUTPUT_ADDR) - { - printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_control_output_address = (uint64_t *)pControl; - precompile_read_address = &shmem_control_output_address[0]; - if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); - - /*************************/ - /* PRECOMPILE SEMAPHORES */ - /*************************/ - - // Create the semaphore for precompile results available signal - assert(strlen(sem_prec_avail_name) > 0); - - sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT, 0666, 0); - if (sem_prec_avail == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_avail_name); - - // Create the semaphore for precompile results read signal - assert(strlen(sem_prec_read_name) > 0); - - sem_prec_read = sem_open(sem_prec_read_name, O_CREAT, 0666, 0); - if (sem_prec_read == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_read_name); - } -} - -typedef enum { - PrecompileReadMode_NoPrefix, - PrecompileReadMode_Prefixed -} PrecompileReadMode; - -PrecompileReadMode precompile_read_mode = PrecompileReadMode_NoPrefix; -//PrecompileReadMode precompile_read_mode = PrecompileReadMode_Prefixed; - -typedef enum { - PrecompileWriteMode_Full, - PrecompileWriteMode_OnePrecAtATime -} PrecompileWriteMode; - -PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_Full; -//PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_OnePrecAtATime; - -//#define PRECOMPILE_FIXED_SIZE 25 // Keccak-f state size in u64s -#define PRECOMPILE_FIXED_SIZE 4 // SHA-256 state size in u64s - -void client_write_precompile_results (void) -{ - int result; - -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - - // Open input file - FILE * precompile_fp = fopen(precompile_file_name, "r"); - if (precompile_fp == NULL) - { - printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Get input file size - if (fseek(precompile_fp, 0, SEEK_END) == -1) - { - printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - long precompile_data_size = ftell(precompile_fp); - if (precompile_data_size == -1) - { - printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((precompile_data_size & 0x7) != 0) - { - printf("ERROR: Precompile results file (%s) size (%lu) is not a multiple of 8 B\n", precompile_file_name, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Go back to the first byte - if (fseek(precompile_fp, 0, SEEK_SET) == -1) - { - printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - assert(precompile_read_mode == PrecompileReadMode_NoPrefix || precompile_read_mode == PrecompileReadMode_Prefixed); - assert(precompile_write_mode == PrecompileWriteMode_Full || precompile_write_mode == PrecompileWriteMode_OnePrecAtATime); - - /*************/ - /* NO PREFIX */ - /*************/ - - if (precompile_read_mode == PrecompileReadMode_NoPrefix) - { - if (precompile_write_mode == PrecompileWriteMode_Full) - { - // Check the precompile data size is inside the proper range - if (precompile_data_size > MAX_PRECOMPILE_SIZE) - { - printf("ERROR: Size of precompile results file (%s) is too long (%lu)\n", precompile_file_name, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Copy input data into input memory - size_t precompile_read = fread(precompile_results_address, 1, precompile_data_size, precompile_fp); - if (precompile_read != precompile_data_size) - { - printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Initialize precompile written address - *precompile_written_address = precompile_data_size >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - else if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) - { - // Check the precompile data size is inside the proper range - if (precompile_data_size % (PRECOMPILE_FIXED_SIZE * 8) != 0) - { - printf("ERROR: Size of precompile results file (%s) is not a multiple %u * 8 B\n", precompile_file_name, PRECOMPILE_FIXED_SIZE); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Initialize precompile written address to zero - *precompile_written_address = 0; // in u64s - - // Copy in chunks of PRECOMPILE_FIXED_SIZE*8 bytes (Keccak-f state size) - uint64_t precompile_read_so_far = 0; - uint64_t data[PRECOMPILE_FIXED_SIZE]; - while (precompile_read_so_far < (uint64_t)precompile_data_size) - { - // Wait for server to read precompile results - //printf("Waiting for sem_prec_read()\n"); - result = sem_wait(sem_prec_read); - if (result == -1) - { - printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Number of bytes to read from file and write to shared memory in every loop - uint64_t bytes_to_read = sizeof(data); - - // Copy input data into input memory - size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); - if (precompile_read != bytes_to_read) - { - printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Copy data to shared memory - for (int i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); - precompile_read_so_far += 8; - } - - // Notify server that precompile results are available - *precompile_written_address = precompile_read_so_far >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - } - } - - /************/ - /* PREFIXED */ - /************/ - - else if (precompile_read_mode == PrecompileReadMode_Prefixed) - { -#define CTRL_START 0x00 -#define CTRL_END 0x01 -#define CTRL_CANCEL 0x02 -#define CTRL_ERROR 0x03 -#define HINTS_TYPE_RESULT 0x04 -#define HINTS_TYPE_ECRECOVER 0x05 -#define NUM_HINT_TYPES 0x06 - - uint64_t precompile_read_so_far = 0; - uint64_t precompile_written_so_far = 0; - - while (precompile_read_so_far < (uint64_t)precompile_data_size) - { - uint64_t data; - uint64_t bytes_to_read = sizeof(data); - - // Copy input data into input memory - size_t precompile_read = fread(&data, 1, bytes_to_read, precompile_fp); - if (precompile_read != bytes_to_read) - { - printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); - fflush(stdout); - fflush(stderr); - exit(-1); - } - precompile_read_so_far += bytes_to_read; - switch (data >> 32) - { - case CTRL_START: - //printf("Precompile CTRL_START\n"); - assert(precompile_read_so_far == 8); - break; - case CTRL_END: - //printf("Precompile CTRL_END\n"); - assert(precompile_read_so_far == precompile_data_size); - break; - // case CTRL_CANCEL: - // printf("Precompile CTRL_CANCEL\n"); - // break; - // case CTRL_ERROR: - // printf("Precompile CTRL_ERROR\n"); - // break; - case HINTS_TYPE_RESULT: - { - //printf("Precompile HINTS_TYPE_RESULT\n"); - if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) - { - // Wait for server to read precompile results - //printf("Waiting for sem_prec_read()\n"); - result = sem_wait(sem_prec_read); - if (result == -1) - { - printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - uint64_t result_length = data & 0xFFFFFFFF; - if (result_length > (precompile_data_size - precompile_read_so_far)) - { - printf("ERROR: Precompile HINTS_TYPE_RESULT length=%lu exceeds remaining file size %lu\n", result_length, precompile_data_size - precompile_read_so_far); - fflush(stdout); - fflush(stderr); - exit(-1); - } - //printf("Precompile HINTS_TYPE_RESULT result_length=%lu\n", result_length); - for (uint64_t i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &value, 8); - precompile_read_so_far += 8; - precompile_written_so_far += 8; - //printf(" Precompile result[%lu] = 0x%016lx\n", i, value); - } - - if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) - { - // Notify server that precompile results are available - *precompile_written_address = precompile_written_so_far >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - } - break; - // case HINTS_TYPE_ECRECOVER: - // { - // // Not implemented - // printf("Precompile HINTS_TYPE_ECRECOVER not implemented\n"); - // } - // break; - default: - printf("ERROR: Unknown precompile prefix type %lu\n", data >> 32); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - if (precompile_write_mode == PrecompileWriteMode_Full) - { - // Notify server that precompile results are available - *precompile_written_address = precompile_written_so_far >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - - } - - // Close the file pointer - fclose(precompile_fp); - -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - printf("client (precompile): done in %lu us\n", duration); -#endif -} - -void client_run (void) -{ - printf("client_run(): Starting client...\n"); - assert(client); - assert(!server); - - int result; - - /************************/ - /* Read input file data */ - /************************/ - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - - // Open input file - FILE * input_fp = fopen(input_file, "r"); - if (input_fp == NULL) - { - printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Get input file size - if (fseek(input_fp, 0, SEEK_END) == -1) - { - printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - long input_data_size = ftell(input_fp); - if (input_data_size == -1) - { - printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Go back to the first byte - if (fseek(input_fp, 0, SEEK_SET) == -1) - { - printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check the input data size is inside the proper range - if (input_data_size > (MAX_INPUT_SIZE - 16)) - { - printf("ERROR: Size of input file (%s) is too long (%lu)\n", input_file, input_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Open input shared memory - shmem_input_fd = shm_open(shmem_input_name, O_RDWR, 0666); - if (shmem_input_fd < 0) - { - printf("ERROR: Failed calling input shm_open(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map the shared memory object into the process address space - shmem_input_address = mmap(NULL, MAX_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmem_input_fd, 0); - if (shmem_input_address == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Write the input size in the first 64 bits - *(uint64_t *)shmem_input_address = (uint64_t)0; // free input - *(uint64_t *)(shmem_input_address + 8)= (uint64_t)input_data_size; - - // Copy input data into input memory - size_t input_read = fread(shmem_input_address + 16, 1, input_data_size, input_fp); - if (input_read != input_data_size) - { - printf("ERROR: Input read (%lu) != input file size (%lu)\n", input_read, input_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Close the file pointer - fclose(input_fp); - - // Unmap input - result = munmap(shmem_input_address, MAX_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - printf("client (input): done in %lu us\n", duration); -#endif - - } - - /*****************************/ - /* Read precompile file data */ - /*****************************/ - if (precompile_results_enabled) - { - // reset written counter - *precompile_written_address = 0; - - //client_write_precompile_results(); - } - - /*************************/ - /* Connect to the server */ - /*************************/ - - // Create socket to connect to server - int socket_fd; - socket_fd = socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd < 0) - { - printf("ERROR: socket() failed socket_fd=%d errno=%d=%s\n", socket_fd, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Configure server address - struct sockaddr_in server_addr; - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(port); - - result = inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); - if (result <= 0) - { - printf("ERROR: inet_pton() failed. Invalid address/Address not supported result=%d errno=%d=%s\n", result, errno, strerror(errno)); - exit(-1); - } - - // Connect to server - result = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); - if (result < 0) - { - printf("ERROR: connect() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); - exit(-1); - } - if (verbose) printf("connect()'d to port=%u\n", port); - - // Request and response - uint64_t request[5]; - uint64_t response[5]; - - /********/ - /* Ping */ - /********/ - - gettimeofday(&start_time, NULL); - - // Prepare message to send - request[0] = TYPE_PING; - request[1] = 0; - request[2] = 0; - request[3] = 0; - request[4] = 0; - - // Send data to server - result = send(socket_fd, request, sizeof(request), 0); - if (result < 0) - { - printf("ERROR: send() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Read server response - ssize_t bytes_received = recv(socket_fd, response, sizeof(response), MSG_WAITALL); - if (bytes_received < 0) - { - printf("ERROR: recv_all_with_timeout() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (bytes_received != sizeof(response)) - { - printf("ERROR: recv_all_with_timeout() returned bytes_received=%ld errno=%d=%s\n", bytes_received, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (response[0] != TYPE_PONG) - { - printf("ERROR: recv_all_with_timeout() returned unexpected type=%lu\n", response[0]); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (response[1] != gen_method) - { - printf("ERROR: recv_all_with_timeout() returned unexpected gen_method=%lu\n", response[1]); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - printf("client (PING): done in %lu us\n", duration); - - /*****************/ - /* Minimal trace */ - /*****************/ - for (uint64_t i=0; i 0); - - sem_unlink(sem_prec_avail_name); - - sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_prec_avail == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded sem_prec_avail=%p\n", sem_prec_avail_name, sem_prec_avail); - - // Create the semaphore for precompile results read signal - assert(strlen(sem_prec_read_name) > 0); - - sem_unlink(sem_prec_read_name); - - sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_prec_read == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded sem_prec_read=%p\n", sem_prec_read_name, sem_prec_read); - } - - /*******/ - /* RAM */ - /*******/ - - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - - if (verbose) gettimeofday(&start_time, NULL); - void * pRam = mmap((void *)RAM_ADDR, RAM_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | map_locked_flag, -1, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pRam == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(ram) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((uint64_t)pRam != RAM_ADDR) - { - printf("ERROR: Called mmap(ram) but returned address = %p != 0x%08lx\n", pRam, RAM_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("mmap(ram) mapped %lu B and returned address %p in %lu us\n", RAM_SIZE, pRam, duration); - } - - /****************/ - /* OUTPUT TRACE */ - /****************/ - - // If ROM histogram, configure trace size - if (gen_method == RomHistogram) - { - // Get max PC values for low and high addresses - uint64_t max_bios_pc = get_max_bios_pc(); - uint64_t max_program_pc = get_max_program_pc(); - assert(max_bios_pc >= 0x1000); - assert((max_bios_pc & 0x3) == 0); - assert(max_program_pc >= 0x80000000); - - // Calculate sizes - bios_size = ((max_bios_pc - 0x1000) >> 2) + 1; - program_size = max_program_pc - 0x80000000 + 1; - histogram_size = (4 + 1 + bios_size + 1 + program_size)*8; - initial_trace_size = ((histogram_size/TRACE_SIZE_GRANULARITY) + 1) * TRACE_SIZE_GRANULARITY; - trace_size = initial_trace_size; - } - - // Output trace - if ((gen_method == MinimalTrace) || - (gen_method == RomHistogram) || - (gen_method == MainTrace) || - (gen_method == Zip) || - (gen_method == MemOp) || - (gen_method == ChunkPlayerMTCollectMem) || - (gen_method == MemReads) || - (gen_method == ChunkPlayerMemReadsCollectMain)) - { - trace_map_initialize(); - } - - /***********************/ - /* INPUT MINIMAL TRACE */ - /***********************/ - - // Input MT trace - if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) - { - // Create the output shared memory - shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); - if (shmem_mt_fd < 0) - { - printf("ERROR: Failed calling mt shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map it to the trace address -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); -#endif - if (pTrace == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((uint64_t)pTrace != TRACE_ADDR) - { - printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); - } - - /******************/ - /* SEM CHUNK DONE */ - /******************/ - - if (call_chunk_done) - { - assert(strlen(sem_chunk_done_name) > 0); - - sem_unlink(sem_chunk_done_name); - - sem_chunk_done = sem_open(sem_chunk_done_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_chunk_done == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_chunk_done_name); - } - - /*********************/ - /* SEM SHUTDOWN DONE */ - /*********************/ - - assert(strlen(sem_shutdown_done_name) > 0); - - sem_unlink(sem_shutdown_done_name); - - sem_shutdown_done = sem_open(sem_shutdown_done_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_shutdown_done == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); -} - -void server_reset_fast (void) -{ - // Reset precompile read address for next emulation - if (precompile_results_enabled) - { - // Set precompile read counter to 0 for next emulation - *precompile_read_address = 0; - - // Sync control output shared memory so that the writer can see the precompile reads we have - // done, and thus update the precompile_written_address if needed - if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: server_reset_fast() msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } -} - -void server_reset_slow (void) -{ - // Reset RAM data for next emulation - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - memset((void *)RAM_ADDR, 0, RAM_SIZE); -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - if (verbose) printf("server_reset_slow() memset(ram) in %lu us\n", duration); -#endif - } -} - -void server_reset_trace (void) -{ - // Reset trace header and trace_used_size for next emulation - if ( (gen_method != ChunkPlayerMTCollectMem) && - (gen_method != ChunkPlayerMemReadsCollectMain) && - (gen_method != Fast) && - (gen_method != RomHistogram) ) - { - // Reset trace: init output header data - pOutputTrace[0] = 0x000100; // Version, e.g. v1.0.0 [8] - pOutputTrace[1] = 1; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - pOutputTrace[2] = trace_size; // MT allocated size [8] -> to be updated after reallocation - pOutputTrace[3] = 0; // MT used size [8] -> to be updated after completion - - // Reset trace used size - trace_used_size = 0; - } -} - -void server_run (void) -{ - // If ROM histogram, reset the trace area to 0 for the histogram data since it represents the - // ROM instruction multiplicity and one of them will be increased at every executed instruction - if ((gen_method == RomHistogram)) { - memset((void *)trace_address, 0, trace_size); - } - -#ifdef ASM_CALL_METRICS - reset_asm_call_metrics(); -#endif - - // Init trace header - server_reset_trace(); - - // Sync input shared memory - if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) - { - printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (precompile_results_enabled) - { - // Sync control input shared memory - if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Sync precompile shared memory - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - /*******/ - /* ASM */ - /*******/ - - // Call emulator assembly code - gettimeofday(&start_time,NULL); - if (verbose) - { - printf("Before calling emulator_start() trace_address=%lx\n", trace_address); - fflush(stdout); - fflush(stderr); - } - emulator_start(); - if (verbose) - { - printf("After calling emulator_start() trace_address=%lx\n", trace_address); - fflush(stdout); - fflush(stderr); - } - gettimeofday(&stop_time,NULL); - assembly_duration = TimeDiff(start_time, stop_time); - - // Reset precompile read address for next emulation - if (precompile_results_enabled) - { - *precompile_read_address = 0; - } - - uint64_t final_trace_size = MEM_CHUNK_ADDRESS - MEM_TRACE_ADDRESS; - trace_used_size = final_trace_size + 32; - - if ( metrics ) - { - uint64_t duration = assembly_duration; - uint64_t steps = MEM_STEP; - uint64_t end = MEM_END; - uint64_t error = MEM_ERROR; - uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; - uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; - uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; - printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", - duration, - realloc_counter, - wait_counter, - steps, - step_duration_ns, - step_tp_sec, - MEM_CHUNK_ADDRESS, - MEM_TRACE_ADDRESS, - final_trace_size, - final_trace_size_percentage, - trace_size, - end, - error, - max_steps, - chunk_size, - precompile_written_address ? *precompile_written_address : 0, - precompile_read_address ? *precompile_read_address : 0 - ); - fflush(stdout); - fflush(stderr); - if (gen_method == RomHistogram) - { - printf("Rom histogram size=%lu\n", histogram_size); - fflush(stdout); - fflush(stderr); - } - } - if (MEM_ERROR) - { - printf("Emulation ended with error code %lu\n", MEM_ERROR); - fflush(stdout); - fflush(stderr); - } - - // Log output - if (output) - { - unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; - unsigned int output_size = 64; -#ifdef DEBUG - if (verbose) - { - printf("Output size=%d\n", output_size); - fflush(stdout); - fflush(stderr); - } -#endif - - for (unsigned int i = 0; i < output_size; i++) - { - printf("%08x\n", *pOutput); - pOutput++; - } - fflush(stdout); - fflush(stderr); - } - - // Log output for riscof tests - if (output_riscof) - { - unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; - unsigned int output_size = *pOutput; -#ifdef DEBUG - if (verbose) - { - printf("Output size=%d\n", output_size); - fflush(stdout); - fflush(stderr); - } -#endif - - for (unsigned int i = 0; i < output_size; i++) - { - pOutput++; - printf("%08x\n", *pOutput); - } - fflush(stdout); - fflush(stderr); - } - - // Complete output header data - if ((gen_method == MinimalTrace) || - (gen_method == RomHistogram) || - (gen_method == Zip) || - (gen_method == MainTrace) || - (gen_method == MemOp) || - (gen_method == MemReads) || - (gen_method == ChunkPlayerMemReadsCollectMain)) - { - uint64_t * pOutput = (uint64_t *)trace_address; - pOutput[0] = 0x000100; // Version, e.g. v1.0.0 [8] - pOutput[1] = MEM_ERROR; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - pOutput[2] = trace_size; // MT allocated size [8] - //assert(final_trace_size > 32); - if (gen_method == RomHistogram) - { - pOutput[3] = MEM_STEP; - pOutput[4] = bios_size; - pOutput[4 + bios_size + 1] = program_size; - } - else - { - pOutput[3] = trace_used_size; // MT used size [8] - } - } - - // Notify client - if (gen_method == RomHistogram) - { - _chunk_done(); - } - - - // Notify the caller that the trace is ready to be consumed - // if (!is_file) - // { - // result = sem_post(sem_input); - // if (result == -1) - // { - // printf("Failed calling sem_post(%s) errno=%d=%s\n", sem_input_name, errno, strerror(errno)); - // fflush(stdout); - // fflush(stderr); - // exit(-1); - // } - // } - - -#ifdef ASM_CALL_METRICS - print_asm_call_metrics(assembly_duration); -#endif - - // Log trace - if (((gen_method == MinimalTrace) || (gen_method == Zip)) && trace) - { - log_minimal_trace(); - } - if ((gen_method == RomHistogram) && trace) - { - log_histogram(); - } - if ((gen_method == MainTrace) && trace) - { - log_main_trace(); - } - if ((gen_method == MemOp) && trace) - { - log_mem_op(); - } - if ((gen_method == MemOp) && save_to_file) - { - save_mem_op_to_files(); - } - if ((gen_method == ChunkPlayerMTCollectMem) && trace) - { - log_mem_trace(); - } - if ((gen_method == MemReads) && trace) - { - log_minimal_trace(); - } - if ((gen_method == ChunkPlayerMemReadsCollectMain) && trace) - { - log_chunk_player_main_trace(); - } -} - -void server_cleanup (void) -{ - // Cleanup ROM - int result = munmap((void *)ROM_ADDR, ROM_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(rom) errno=%d=%s\n", errno, strerror(errno)); - } - - // Cleanup RAM - result = munmap((void *)RAM_ADDR, RAM_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(ram) errno=%d=%s\n", errno, strerror(errno)); - } - - // Cleanup INPUT - result = munmap((void *)INPUT_ADDR, MAX_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_input_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - } - - if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - // Cleanup PRECOMPILE - result = munmap((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(precompile) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_precompile_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - } - - // Cleanup CONTROL - result = munmap((void *)shmem_control_input_address, CONTROL_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_control_input_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - } - result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_control_output_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); - } - - // Semaphores cleanup - result = sem_close(sem_prec_avail); - if (result == -1) - { - printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - } - result = sem_unlink(sem_prec_avail_name); - if (result == -1) - { - printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - } - result = sem_close(sem_prec_read); - if (result == -1) - { - printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - } - result = sem_unlink(sem_prec_read_name); - if (result == -1) - { - printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - } - } - - // Cleanup trace - trace_cleanup(); - - // Cleanup chunk done semaphore - if (call_chunk_done) - { - result = sem_close(sem_chunk_done); - if (result == -1) - { - printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - } - result = sem_unlink(sem_chunk_done_name); - if (result == -1) - { - printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - } - } - - // Post shutdown donw semaphore - result = sem_post(sem_shutdown_done); - if (result == -1) - { - printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); - } -} - -/**************/ -/* PRINT REGS */ -/**************/ - -//#define PRINT_REGS -#ifdef PRINT_REGS -extern uint64_t reg_0; -extern uint64_t reg_1; -extern uint64_t reg_2; -extern uint64_t reg_3; -extern uint64_t reg_4; -extern uint64_t reg_5; -extern uint64_t reg_6; -extern uint64_t reg_7; -extern uint64_t reg_8; -extern uint64_t reg_9; -extern uint64_t reg_10; -extern uint64_t reg_11; -extern uint64_t reg_12; -extern uint64_t reg_13; -extern uint64_t reg_14; -extern uint64_t reg_15; -extern uint64_t reg_16; -extern uint64_t reg_17; -extern uint64_t reg_18; -extern uint64_t reg_19; -extern uint64_t reg_20; -extern uint64_t reg_21; -extern uint64_t reg_22; -extern uint64_t reg_23; -extern uint64_t reg_24; -extern uint64_t reg_25; -extern uint64_t reg_26; -extern uint64_t reg_27; -extern uint64_t reg_28; -extern uint64_t reg_29; -extern uint64_t reg_30; -extern uint64_t reg_31; -extern uint64_t reg_32; -extern uint64_t reg_33; -extern uint64_t reg_34; -#endif - -// Used for debugging purposes -extern int _print_regs() -{ -#ifdef PRINT_REGS - printf("print_regs()\n"); - printf("\treg[ 0]=%lu=0x%lx=@%p\n", reg_0, reg_0, ®_0); - printf("\treg[ 1]=%lu=0x%lx=@%p\n", reg_1, reg_1, ®_1); - printf("\treg[ 2]=%lu=0x%lx=@%p\n", reg_2, reg_2, ®_2); - printf("\treg[ 3]=%lu=0x%lx=@%p\n", reg_3, reg_3, ®_3); - printf("\treg[ 4]=%lu=0x%lx=@%p\n", reg_4, reg_4, ®_4); - printf("\treg[ 5]=%lu=0x%lx=@%p\n", reg_5, reg_5, ®_5); - printf("\treg[ 6]=%lu=0x%lx=@%p\n", reg_6, reg_6, ®_6); - printf("\treg[ 7]=%lu=0x%lx=@%p\n", reg_7, reg_7, ®_7); - printf("\treg[ 8]=%lu=0x%lx=@%p\n", reg_8, reg_8, ®_8); - printf("\treg[ 9]=%lu=0x%lx=@%p\n", reg_9, reg_9, ®_9); - printf("\treg[10]=%lu=0x%lx=@%p\n", reg_10, reg_10, ®_10); - printf("\treg[11]=%lu=0x%lx=@%p\n", reg_11, reg_11, ®_11); - printf("\treg[12]=%lu=0x%lx=@%p\n", reg_12, reg_12, ®_12); - printf("\treg[13]=%lu=0x%lx=@%p\n", reg_13, reg_13, ®_13); - printf("\treg[14]=%lu=0x%lx=@%p\n", reg_14, reg_14, ®_14); - printf("\treg[15]=%lu=0x%lx=@%p\n", reg_15, reg_15, ®_15); - printf("\treg[16]=%lu=0x%lx=@%p\n", reg_16, reg_16, ®_16); - printf("\treg[17]=%lu=0x%lx=@%p\n", reg_17, reg_17, ®_17); - printf("\treg[18]=%lu=0x%lx=@%p\n", reg_18, reg_18, ®_18); - printf("\treg[19]=%lu=0x%lx=@%p\n", reg_19, reg_19, ®_19); - printf("\treg[20]=%lu=0x%lx=@%p\n", reg_20, reg_20, ®_20); - printf("\treg[21]=%lu=0x%lx=@%p\n", reg_21, reg_21, ®_21); - printf("\treg[22]=%lu=0x%lx=@%p\n", reg_22, reg_22, ®_22); - printf("\treg[23]=%lu=0x%lx=@%p\n", reg_23, reg_23, ®_23); - printf("\treg[24]=%lu=0x%lx=@%p\n", reg_24, reg_24, ®_24); - printf("\treg[25]=%lu=0x%lx=@%p\n", reg_25, reg_25, ®_25); - printf("\treg[26]=%lu=0x%lx=@%p\n", reg_26, reg_26, ®_26); - printf("\treg[27]=%lu=0x%lx=@%p\n", reg_27, reg_27, ®_27); - printf("\treg[28]=%lu=0x%lx=@%p\n", reg_28, reg_28, ®_28); - printf("\treg[29]=%lu=0x%lx=@%p\n", reg_29, reg_29, ®_29); - printf("\treg[30]=%lu=0x%lx=@%p\n", reg_30, reg_30, ®_30); - printf("\treg[31]=%lu=0x%lx=@%p\n", reg_31, reg_31, ®_31); - printf("\treg[32]=%lu=0x%lx=@%p\n", reg_32, reg_32, ®_32); - printf("\treg[33]=%lu=0x%lx=@%p\n", reg_33, reg_33, ®_33); - printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); - printf("\n"); -#endif -} - -/************/ -/* PRINT PC */ -/************/ - -//#define PRINT_PC_DURATION -#ifdef PRINT_PC_DURATION -struct timeval print_pc_tv; -#endif - -// Used for debugging purposes -extern int _print_pc (uint64_t pc, uint64_t c) -{ -#ifdef PRINT_PC_DURATION - print_pc_counter++; - { - struct timeval tv; - gettimeofday(&tv, NULL); - uint64_t duration = TimeDiff(print_pc_tv, tv); - if (duration > 900) - { - uint64_t chunk = print_pc_counter / chunk_size; - printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); - fflush(stdout); - } - print_pc_tv = tv; - } -#endif - - printf("s=%lu pc=%lx c=%lx", print_pc_counter, pc, c); - -//#define PRINT_PC_REGS -#ifdef PRINT_PC_REGS - /* Used for debugging */ - printf(" r0=%lx", reg_0); - printf(" r1=%lx", reg_1); - printf(" r2=%lx", reg_2); - printf(" r3=%lx", reg_3); - printf(" r4=%lx", reg_4); - printf(" r5=%lx", reg_5); - printf(" r6=%lx", reg_6); - printf(" r7=%lx", reg_7); - printf(" r8=%lx", reg_8); - printf(" r9=%lx", reg_9); - printf(" r10=%lx", reg_10); - printf(" r11=%lx", reg_11); - printf(" r12=%lx", reg_12); - printf(" r13=%lx", reg_13); - printf(" r14=%lx", reg_14); - printf(" r15=%lx", reg_15); - printf(" r16=%lx", reg_16); - printf(" r17=%lx", reg_17); - printf(" r18=%lx", reg_18); - printf(" r19=%lx", reg_19); - printf(" r20=%lx", reg_20); - printf(" r21=%lx", reg_21); - printf(" r22=%lx", reg_22); - printf(" r23=%lx", reg_23); - printf(" r24=%lx", reg_24); - printf(" r25=%lx", reg_25); - printf(" r26=%lx", reg_26); - printf(" r27=%lx", reg_27); - printf(" r28=%lx", reg_28); - printf(" r29=%lx", reg_29); - printf(" r30=%lx", reg_30); - printf(" r31=%lx", reg_31); -#endif - - printf("\n"); - fflush(stdout); - print_pc_counter++; -} - -/**************/ -/* CHUNK DONE */ -/**************/ - -//#define CHUNK_DONE_DURATION -#ifdef CHUNK_DONE_DURATION -uint64_t chunk_done_counter = 0; -struct timeval chunk_done_tv; -#endif - -//#define CHUNK_DONE_SYNC_DURATION -#ifdef CHUNK_DONE_SYNC_DURATION -struct timeval sync_start, sync_stop; -uint64_t sync_duration = 0; -#endif - -// Called by the assembly to notify that a chunk is done and its trace is ready to be consumed -extern void _chunk_done() -{ -#ifdef CHUNK_DONE_DURATION - chunk_done_counter++; - if ((chunk_done_counter & 0xFF) == 0) - { - struct timeval tv; - gettimeofday(&tv, NULL); - uint64_t duration = TimeDiff(chunk_done_tv, tv); - if (duration > 5000) - { - printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); - fflush(stdout); - } - chunk_done_tv = tv; - } -#endif - -#ifdef CHUNK_DONE_SYNC_DURATION - gettimeofday(&sync_start, NULL); -#endif - - __sync_synchronize(); - -#ifdef CHUNK_DONE_SYNC_DURATION - gettimeofday(&sync_stop, NULL); - sync_duration += TimeDiff(sync_start, sync_stop); - printf("chunk_done() sync_duration=%lu\n", sync_duration); -#endif - - // Notify the caller that a new chunk is done and its trace is ready to be consumed - assert(call_chunk_done); - int result = sem_post(sem_chunk_done); - if (result == -1) - { - printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -/*****************/ -/* REALLOC TRACE */ -/*****************/ - -// Called by the assembly to reallocate the trace when needed, e.g. for the next chunk, -// to increase the trace size by another chunk size -extern void _realloc_trace (void) -{ - // Increase realloc counter - realloc_counter++; - - // Map next chunk of the trace shared memory - trace_map_next_chunk(); - - // Update trace global variables - set_trace_size(trace_total_mapped_size); - -#ifdef DEBUG - if (verbose) printf("realloc_trace() realloc counter=%lu trace_address=0x%lx trace_size=%lu=%lx max_address=0x%lx trace_address_threshold=0x%lx chunk_size=%lu\n", realloc_counter, trace_address, trace_size, trace_size, trace_address + trace_size, trace_address_threshold, chunk_size); -#endif -} - -/*****************/ -/* LOG FUNCTIONS */ -/*****************/ - -/* Trace data structure - [8B] Number of chunks: C - - Chunk 0: - Start state: - [8B] pc - [8B] sp - [8B] c - [8B] step - [8B] register[1] - … - [8B] register[31] - [8B] register[32] - [8B] register[33] - Last state: - [8B] c - End: - [8B] end - Steps: - [8B] steps = chunk size except for the last chunk - [8B] mem_reads_size - [8B] mem_reads[0] - [8B] mem_reads[1] - … - [8B] mem_reads[mem_reads_size - 1] - - Chunk 1: - … - Chunk C-1: - … -*/ -void log_minimal_trace(void) -{ - uint64_t * pOutput = (uint64_t *)TRACE_ADDR; - printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] - printf("Minimal trace used size = %lu B\n", pOutput[3]); // Minimal trace used size [8] - - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; - uint64_t number_of_chunks = trace[0]; - printf("Number of chunks=%lu\n", number_of_chunks); - if (number_of_chunks > 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (trace_trace) - { - for (uint64_t m=0; m 100000000) - { - printf("ERROR: Bios size is too high=%lu\n", bios_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (trace_trace) - { - uint64_t * bios = trace + 1; - for (uint64_t i=0; i 100000000) - { - printf("ERROR: Program size is too high=%lu\n", program_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (trace_trace) - { - uint64_t * program = trace + 1 + bios_size + 1; - for (uint64_t i=0; i 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Main_trace size is too high=%lu\n", main_trace_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (trace_trace) - { - for (uint64_t m=0; m 0) - { - size_t bytes_written = fwrite(buffer_address, 1, buffer_length, file); - if (bytes_written != buffer_length) - { - printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%lu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - fclose(file); - exit(-1); - } - } - - if (fclose(file) != 0) - { - printf("ERROR: buffer2file() failed calling fclose(%s) errno=%d=%s\n", file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -/* Memory operations structure - [8B] Number of chunks = C - - Chunk 0: - [8b] end - [8B] mem_op_trace_size - [8B] mem_op_trace[0] - [8B] mem_op_trace[1] - … - [8B] mem_op_trace[mem_op_trace_size - 1] - - Chunk 1: - … - Chunk C-1: - … -*/ -void log_mem_op(void) -{ - // Log header - uint64_t * pOutput = (uint64_t *)TRACE_ADDR; - printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] - - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; - uint64_t number_of_chunks = trace[0]; - printf("Number of chunks=%lu\n", number_of_chunks); - if (number_of_chunks > 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - for (uint64_t m=0; m> 49) & 0x1; - uint64_t write = (chunk[i] >> 48) & 0x1; - uint64_t width = (chunk[i] >> 32) & 0xF; - uint64_t address = chunk[i] & 0xFFFFFFFF; - bool inside_range = - ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || - ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || - ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); - if (trace_trace || !inside_range) - { - printf("\t\tchunk[%lu].mem_op_trace[%lu] = %016lx = rest_are_zeros=%lx, write=%lx, width=%lx, address=%lx%s\n", - c, - m, - chunk[i], - rest_are_zeros, - write, - width, - address, - inside_range ? "" : " ERROR!!!!!!!!!!!!!!" - ); - } - i += 1; - } - - //Set next chunk pointer - chunk = chunk + i; - } - printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); -} - -/* Memory trace structure (for 1 chunk) - [8B] mem_trace_size - [16B] mem_trace[0] - [8B] mem operacion - [4B] address (LE) - [1B] width (1, 2, 4, 8) + write (0, 1) << 4 - [3B] - [16B] mem_trace[1] - … - [16B] mem_trace[mem_trace_size - 1] -*/ -void log_mem_trace(void) -{ - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)trace_address; - printf("log_mem_trace() trace_address=%p\n", trace); - uint64_t i=0; - printf("Version = 0x%06lx\n", trace[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", trace[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", trace[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", trace[3]); // Main trace used size [8] - i += 4; - uint64_t number_of_entries = trace[i]; - i++; - printf("Trace size=%lu\n", number_of_entries); - - for (uint64_t m = 0; m < number_of_entries; m++) - { - uint64_t addr_step = trace[i]; - i++; - - // addr_step = [@0, @1, @2, @3, width + write<<4, supra_step] - uint64_t address = addr_step & 0xFFFFFFFF; - uint64_t width = (addr_step >> (4*8)) & 0xF; - uint64_t write = (addr_step >> ((4*8) + 4)) & 0x1; - uint64_t micro_step = (addr_step >> (5*8)) & 0x3; - uint64_t incremental_step = (addr_step >> ((5*8) + 2)); - bool address_is_inside_range = - ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || - ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || - ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); - bool width_is_valid = (width == 1) || (width == 2) || (width == 4) || (width == 8); - bool bError = !(address_is_inside_range && width_is_valid); - if (trace_trace || bError) - { - printf("\tmem_trace[%lu] = %016lx = [inc_step=%lu, u_step=%lu, write=%lx, width=%lx, address=%lx] %s\n", - m, - addr_step, - incremental_step, - micro_step, - write, - width, - address, - bError ? " ERROR!!!!!!!!!!!!!!" : "" - ); - } - - // u-step: - // 0: a=SRC_MEM - // 1: b=SRC_MEM or b=SRC_IND - // 2: precompiled_read - // 3: c=STORE_MEM, c=STORE_IND or precompiled_write - - bool address_is_aligned = (address & 0x7) == 0; - uint64_t aligned_address = address & 0xFFFFFFF8; - uint64_t number_of_read_values = 0; - uint64_t number_of_write_values = 0; - - switch (micro_step) - { - case 0: // a=SRC_MEM - { - assert_perror(width == 8); - if (address_is_aligned) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - break; - } - case 1: // b=SRC_MEM or b=SRC_IND - { - if (address_is_aligned) - { - number_of_read_values = 1; - } - else - { - if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - } - break; - } - case 2: // precompiled_read - { - assert_perror(width == 8); - if (address_is_aligned) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - break; - } - case 3: // c=STORE_MEM, c=STORE_IND or precompiled_write - { - if (address_is_aligned && (width == 8)) - { - number_of_read_values = 0; - } - else - { - if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - } - number_of_write_values = 1; - break; - } - } - - for (uint64_t r = 0; r < number_of_read_values; r++) - { - uint64_t value = trace[i]; - i++; - m++; - if (trace_trace) - { - printf("\t\tread_value[%lu] = 0x%lx\n", i, value); - } - } - - for (uint64_t w = 0; w < number_of_write_values; w++) - { - uint64_t value = trace[i]; - i++; - m++; - if (trace_trace) - { - printf("\t\twrite_value[%lu] = 0x%lx\n", i, value); - } - } - } - printf("Trace=%p number_of_entries=%lu\n", trace, number_of_entries); -} - -void save_mem_op_to_files(void) -{ - // Log header - uint64_t * pOutput = (uint64_t *)TRACE_ADDR; - printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] - - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; - uint64_t number_of_chunks = trace[0]; - printf("Number of chunks=%lu\n", number_of_chunks); - if (number_of_chunks > 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - printf("Chunk %lu: file=%s length=%lu\n", c, file_name, mem_op_trace_size); - - buffer2file(&chunk[i], mem_op_trace_size * 8, file_name); - - //Set next chunk pointer - chunk = chunk + mem_op_trace_size + 1; - } - printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); -} - -/* Trace data structure - [8B] Number of elements - - A series of elements with the following structure: - [8B] op: instruction opcode - [8B] a: register a value - [8B] b: register b value - [8B] precompiled_memory_address: memory read address of the precompiled input data -*/ -void log_chunk_player_main_trace(void) -{ - uint64_t * chunk = (uint64_t *)trace_address; - uint64_t i = 0; - - printf("Version = 0x%06lx\n", chunk[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", chunk[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", chunk[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", chunk[3]); // Main trace used size [8] - i = 4; - - uint64_t mem_reads_size = chunk[i]; - i++; - printf("mem_reads_size=%lu\n", mem_reads_size); - if (mem_reads_size > 10000000) - { - printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - //if (trace_trace) - { - for (uint64_t m=0; m 0xFF) - { - printf("ERROR!! Invalid op=%lu=0x%lx\n", op, op); - } - if (trace_trace) printf("\tmem_reads[%lu] a=0x%08lx\n", m, chunk[i]); - i++; - m++; - if (trace_trace) printf("\tmem_reads[%lu] b=0x%08lx\n", m, chunk[i]); - i++; - m++; - if ( (op == 0xf1) // Keccak - || (op == 0xf9) // SHA256 - || (op == 0xf2) // Arith256 - || (op == 0xf3) // Arith256Mod - || (op == 0xf4) // Secp256k1Add - || (op == 0xf5) // Secp256k1Dbl - ) - { - if (trace_trace) printf("\tmem_reads[%lu] precompiled_address=%08lx\n", m, chunk[i]); - i++; - m++; - } - } - } - - printf("Chunk=%p size=%lu\n", chunk, mem_reads_size); -} - -/*************/ -/* FILE LOCK */ -/*************/ - -// Lock file exclusively to ensure that only one instance of the program is running at a time -void file_lock(void) -{ - // Open (or create) the lock file. We don't need to write to it. - file_lock_fd = open(file_lock_name, O_CREAT | O_RDONLY, 0644); - if (file_lock_fd == -1) { - printf("ERROR: file_lock() failed calling open(%s) errno=%d=%s\n", file_lock_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(1); - } - - // Try to acquire an exclusive lock, non-blocking. - if (flock(file_lock_fd, LOCK_EX | LOCK_NB) == -1) { - // If we fail to get the lock, another instance is running. - printf("ERROR: Another instance of this program is already running.\n"); - fflush(stdout); - fflush(stderr); - exit(1); - } -} - -/*********************************/ -/* WAIT FOR PRECOMPILE AVAILABLE */ -/*********************************/ - -// Called by the assembly when prec_written == prec_read, to wait for new precompile results to be available -int _wait_for_prec_avail (void) -{ - // Increment wait counter - wait_counter++; - - // Sync control output shared memory so that the writer can see the precompile reads we have - // done, and thus update the precompile_written_address if needed - if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Tell the writer that we have read some precompile results - sem_post(sem_prec_read); - - // Make sure the precompile available semaphore is reset before checking the condition, - // since the caller may have posted it (even several times) before we called sem_wait() - while (sem_trywait(sem_prec_avail) == 0) {/*printf("Purging sem_prec_avail\n");*/}; - - // Sync control input shared memory so that we can see the latest precompile_written_address value - if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check if there are already precompile results available - if (*precompile_written_address > *precompile_read_address) - { - // Sync precompile shared memory - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - return 0; - } - - // Wait again, but blocking this time - while (true) - { - struct timespec ts; - int result = clock_gettime(CLOCK_REALTIME, &ts); - if (result == -1) - { - printf("ERROR: wait_for_prec_avail() failed calling clock_gettime() errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - ts.tv_sec += 5; // 5 seconds timeout - - //printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); - result = sem_timedwait(sem_prec_avail, &ts); - //printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); - if ((result == -1) && (errno != ETIMEDOUT)) - { - printf("ERROR: wait_for_prec_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Sync control input shared memory so that we can see the latest precompile_written_address value - if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (*precompile_exit_address != 0) - { - printf("ERROR: wait_for_prec_avail() found precompile_exit_address=%lu\n", *precompile_exit_address); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (*precompile_written_address > *precompile_read_address) - { - // Sync precompile shared memory - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - return 0; - } - } - - printf("ERROR: wait_for_prec_avail() unreachable code\n"); - fflush(stdout); - fflush(stderr); - exit(-1); -} \ No newline at end of file +#endif // USE_FILE_LOCK \ No newline at end of file diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c new file mode 100644 index 000000000..60fdb49f4 --- /dev/null +++ b/emulator-asm/src/server.c @@ -0,0 +1,958 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "server.hpp" +#include "globals.hpp" +#include "asm_provided.hpp" +#include "trace_logs.hpp" +#include "trace.hpp" +#include "emu.hpp" +#include "c_provided.hpp" + +/**********/ +/* SERVER */ +/**********/ + +// ROM histogram +uint64_t histogram_size = 0; +uint64_t bios_size = 0; +uint64_t program_size = 0; + +void server_setup (void) +{ + assert(server); + assert(!client); + + int result; + + /*******/ + /* ROM */ + /*******/ + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + + if (verbose) gettimeofday(&start_time, NULL); + void * pRom = mmap((void *)ROM_ADDR, ROM_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | map_locked_flag, -1, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pRom == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(rom) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pRom != ROM_ADDR) + { + printf("ERROR: Called mmap(rom) but returned address = %p != 0x%lx\n", pRom, ROM_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(rom) mapped %ld B and returned address %p in %lu us\n", ROM_SIZE, pRom, duration); + } + + /*********/ + /* INPUT */ + /*********/ + + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + if (!open_input_shm) + { + // Make sure the input shared memory is deleted + shm_unlink(shmem_input_name); + + // Create the input shared memory + shmem_input_fd = shm_open(shmem_input_name, O_RDWR | O_CREAT | O_EXCL, 0666); + if (shmem_input_fd < 0) + { + printf("ERROR: Failed calling input RW shm_open(%s) as read-write errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_input_fd, MAX_INPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(shmem_input_fd); + + // Close the descriptor + if (close(shmem_input_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + // Open the input shared memory as read-only + shmem_input_fd = shm_open(shmem_input_name, O_RDONLY | O_EXCL, 0666); + if (shmem_input_fd < 0) + { + printf("ERROR: Failed calling input RO shm_open(%s) as read-only errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map input address space + if (verbose) gettimeofday(&start_time, NULL); + void * pInput = mmap((void *)INPUT_ADDR, MAX_INPUT_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pInput == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pInput != INPUT_ADDR) + { + printf("ERROR: Called mmap(pInput) but returned address = %p != 0x%lx\n", pInput, INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(input) mapped %lu B and returned address %p in %lu us\n", MAX_INPUT_SIZE, pInput, duration); + } + + /**********************/ + /* PRECOMPILE_RESULTS */ + /**********************/ + + if (precompile_results_enabled) + { + /**************/ + /* PRECOMPILE */ + /**************/ + + if (!open_input_shm) + { + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_precompile_name); + + // Create the precompile results shared memory + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR | O_CREAT, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_precompile_fd, MAX_PRECOMPILE_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(shmem_precompile_fd); + + // Close the descriptor + if (close(shmem_precompile_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + // Open the precompile shared memory as read-only + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDONLY | O_EXCL, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pPrecompile == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_precompile_address = pPrecompile; + precompile_results_address = (uint64_t *)pPrecompile; + if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); + + /*****************/ + /* CONTROL INPUT */ + /*****************/ + + if (!open_input_shm) + { + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_input_name); + + // Create the control shared memory + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_control_input_fd, CONTROL_INPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(shmem_control_input_fd); + + // Close the descriptor + if (close(shmem_control_input_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + // Open the control input shared memory as read-only + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY | O_EXCL, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_INPUT_ADDR) + { + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + + /******************/ + /* CONTROL OUTPUT */ + /******************/ + + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_output_name); + + // Create the control shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_output_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_control_output_fd, CONTROL_OUTPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); + + /*************************/ + /* PRECOMPILE SEMAPHORES */ + /*************************/ + + // Create the semaphore for precompile results available signal + assert(strlen(sem_prec_avail_name) > 0); + + sem_unlink(sem_prec_avail_name); + + sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_avail == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded sem_prec_avail=%p\n", sem_prec_avail_name, sem_prec_avail); + + // Create the semaphore for precompile results read signal + assert(strlen(sem_prec_read_name) > 0); + + sem_unlink(sem_prec_read_name); + + sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_read == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded sem_prec_read=%p\n", sem_prec_read_name, sem_prec_read); + } + + /*******/ + /* RAM */ + /*******/ + + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + + if (verbose) gettimeofday(&start_time, NULL); + void * pRam = mmap((void *)RAM_ADDR, RAM_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | map_locked_flag, -1, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pRam == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(ram) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pRam != RAM_ADDR) + { + printf("ERROR: Called mmap(ram) but returned address = %p != 0x%08lx\n", pRam, RAM_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(ram) mapped %lu B and returned address %p in %lu us\n", RAM_SIZE, pRam, duration); + } + + /****************/ + /* OUTPUT TRACE */ + /****************/ + + // If ROM histogram, configure trace size + if (gen_method == RomHistogram) + { + // Get max PC values for low and high addresses + uint64_t max_bios_pc = get_max_bios_pc(); + uint64_t max_program_pc = get_max_program_pc(); + assert(max_bios_pc >= 0x1000); + assert((max_bios_pc & 0x3) == 0); + assert(max_program_pc >= 0x80000000); + + // Calculate sizes + bios_size = ((max_bios_pc - 0x1000) >> 2) + 1; + program_size = max_program_pc - 0x80000000 + 1; + histogram_size = (4 + 1 + bios_size + 1 + program_size)*8; + initial_trace_size = ((histogram_size/TRACE_SIZE_GRANULARITY) + 1) * TRACE_SIZE_GRANULARITY; + trace_size = initial_trace_size; + } + + // Output trace + if ((gen_method == MinimalTrace) || + (gen_method == RomHistogram) || + (gen_method == MainTrace) || + (gen_method == Zip) || + (gen_method == MemOp) || + (gen_method == ChunkPlayerMTCollectMem) || + (gen_method == MemReads) || + (gen_method == ChunkPlayerMemReadsCollectMain)) + { + trace_map_initialize(); + } + + /***********************/ + /* INPUT MINIMAL TRACE */ + /***********************/ + + // Input MT trace + if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) + { + // Create the output shared memory + shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); + if (shmem_mt_fd < 0) + { + printf("ERROR: Failed calling mt shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map it to the trace address +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); +#endif + if (pTrace == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pTrace != TRACE_ADDR) + { + printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); + } + + /******************/ + /* SEM CHUNK DONE */ + /******************/ + + if (call_chunk_done) + { + assert(strlen(sem_chunk_done_name) > 0); + + sem_unlink(sem_chunk_done_name); + + sem_chunk_done = sem_open(sem_chunk_done_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_chunk_done == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_chunk_done_name); + } + + /*********************/ + /* SEM SHUTDOWN DONE */ + /*********************/ + + assert(strlen(sem_shutdown_done_name) > 0); + + sem_unlink(sem_shutdown_done_name); + + sem_shutdown_done = sem_open(sem_shutdown_done_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_shutdown_done == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); +} + +void server_reset_fast (void) +{ + // Reset precompile read address for next emulation + if (precompile_results_enabled) + { + // Set precompile read counter to 0 for next emulation + *precompile_read_address = 0; + + // Sync control output shared memory so that the writer can see the precompile reads we have + // done, and thus update the precompile_written_address if needed + if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: server_reset_fast() msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } +} + +void server_reset_slow (void) +{ + // Reset RAM data for next emulation + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + memset((void *)RAM_ADDR, 0, RAM_SIZE); +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + if (verbose) printf("server_reset_slow() memset(ram) in %lu us\n", duration); +#endif + } +} + +void server_reset_trace (void) +{ + // Reset trace header and trace_used_size for next emulation + if ( (gen_method != ChunkPlayerMTCollectMem) && + (gen_method != ChunkPlayerMemReadsCollectMain) && + (gen_method != Fast) && + (gen_method != RomHistogram) ) + { + // Reset trace: init output header data + pOutputTrace[0] = 0x000100; // Version, e.g. v1.0.0 [8] + pOutputTrace[1] = 1; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + pOutputTrace[2] = trace_size; // MT allocated size [8] -> to be updated after reallocation + pOutputTrace[3] = 0; // MT used size [8] -> to be updated after completion + + // Reset trace used size + trace_used_size = 0; + } +} + +void server_run (void) +{ + // If ROM histogram, reset the trace area to 0 for the histogram data since it represents the + // ROM instruction multiplicity and one of them will be increased at every executed instruction + if ((gen_method == RomHistogram)) { + memset((void *)trace_address, 0, trace_size); + } + +#ifdef ASM_CALL_METRICS + reset_asm_call_metrics(); +#endif + + // Init trace header + server_reset_trace(); + + // Sync input shared memory + if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) + { + printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (precompile_results_enabled) + { + // Sync control input shared memory + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + /*******/ + /* ASM */ + /*******/ + + // Call emulator assembly code + gettimeofday(&start_time,NULL); + if (verbose) + { + printf("Before calling emulator_start() trace_address=%lx\n", trace_address); + fflush(stdout); + fflush(stderr); + } + emulator_start(); + if (verbose) + { + printf("After calling emulator_start() trace_address=%lx\n", trace_address); + fflush(stdout); + fflush(stderr); + } + gettimeofday(&stop_time,NULL); + assembly_duration = TimeDiff(start_time, stop_time); + + // Reset precompile read address for next emulation + if (precompile_results_enabled) + { + *precompile_read_address = 0; + } + + uint64_t final_trace_size = MEM_CHUNK_ADDRESS - MEM_TRACE_ADDRESS; + trace_used_size = final_trace_size + 32; + + if ( metrics ) + { + uint64_t duration = assembly_duration; + uint64_t steps = MEM_STEP; + uint64_t end = MEM_END; + uint64_t error = MEM_ERROR; + uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; + uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; + uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; + printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", + duration, + realloc_counter, + wait_counter, + steps, + step_duration_ns, + step_tp_sec, + MEM_CHUNK_ADDRESS, + MEM_TRACE_ADDRESS, + final_trace_size, + final_trace_size_percentage, + trace_size, + end, + error, + max_steps, + chunk_size, + precompile_written_address ? *precompile_written_address : 0, + precompile_read_address ? *precompile_read_address : 0 + ); + fflush(stdout); + fflush(stderr); + if (gen_method == RomHistogram) + { + printf("Rom histogram size=%lu\n", histogram_size); + fflush(stdout); + fflush(stderr); + } + } + if (MEM_ERROR) + { + printf("Emulation ended with error code %lu\n", MEM_ERROR); + fflush(stdout); + fflush(stderr); + } + + // Log output + if (output) + { + unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; + unsigned int output_size = 64; +#ifdef DEBUG + if (verbose) + { + printf("Output size=%d\n", output_size); + fflush(stdout); + fflush(stderr); + } +#endif + + for (unsigned int i = 0; i < output_size; i++) + { + printf("%08x\n", *pOutput); + pOutput++; + } + fflush(stdout); + fflush(stderr); + } + + // Log output for riscof tests + if (output_riscof) + { + unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; + unsigned int output_size = *pOutput; +#ifdef DEBUG + if (verbose) + { + printf("Output size=%d\n", output_size); + fflush(stdout); + fflush(stderr); + } +#endif + + for (unsigned int i = 0; i < output_size; i++) + { + pOutput++; + printf("%08x\n", *pOutput); + } + fflush(stdout); + fflush(stderr); + } + + // Complete output header data + if ((gen_method == MinimalTrace) || + (gen_method == RomHistogram) || + (gen_method == Zip) || + (gen_method == MainTrace) || + (gen_method == MemOp) || + (gen_method == MemReads) || + (gen_method == ChunkPlayerMemReadsCollectMain)) + { + uint64_t * pOutput = (uint64_t *)trace_address; + pOutput[0] = 0x000100; // Version, e.g. v1.0.0 [8] + pOutput[1] = MEM_ERROR; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + pOutput[2] = trace_size; // MT allocated size [8] + //assert(final_trace_size > 32); + if (gen_method == RomHistogram) + { + pOutput[3] = MEM_STEP; + pOutput[4] = bios_size; + pOutput[4 + bios_size + 1] = program_size; + } + else + { + pOutput[3] = trace_used_size; // MT used size [8] + } + } + + // Notify client + if (gen_method == RomHistogram) + { + _chunk_done(); + } + + + // Notify the caller that the trace is ready to be consumed + // if (!is_file) + // { + // result = sem_post(sem_input); + // if (result == -1) + // { + // printf("Failed calling sem_post(%s) errno=%d=%s\n", sem_input_name, errno, strerror(errno)); + // fflush(stdout); + // fflush(stderr); + // exit(-1); + // } + // } + + +#ifdef ASM_CALL_METRICS + print_asm_call_metrics(assembly_duration); +#endif + + // Log trace + if (((gen_method == MinimalTrace) || (gen_method == Zip)) && trace) + { + log_minimal_trace(); + } + if ((gen_method == RomHistogram) && trace) + { + log_histogram(); + } + if ((gen_method == MainTrace) && trace) + { + log_main_trace(); + } + if ((gen_method == MemOp) && trace) + { + log_mem_op(); + } + if ((gen_method == MemOp) && save_to_file) + { + save_mem_op_to_files(); + } + if ((gen_method == ChunkPlayerMTCollectMem) && trace) + { + log_mem_trace(); + } + if ((gen_method == MemReads) && trace) + { + log_minimal_trace(); + } + if ((gen_method == ChunkPlayerMemReadsCollectMain) && trace) + { + log_chunk_player_main_trace(); + } +} + +void server_cleanup (void) +{ + // Cleanup ROM + int result = munmap((void *)ROM_ADDR, ROM_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(rom) errno=%d=%s\n", errno, strerror(errno)); + } + + // Cleanup RAM + result = munmap((void *)RAM_ADDR, RAM_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(ram) errno=%d=%s\n", errno, strerror(errno)); + } + + // Cleanup INPUT + result = munmap((void *)INPUT_ADDR, MAX_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_input_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + } + + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + // Cleanup PRECOMPILE + result = munmap((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_precompile_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + } + + // Cleanup CONTROL + result = munmap((void *)shmem_control_input_address, CONTROL_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_input_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + } + result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_output_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + } + + // Semaphores cleanup + result = sem_close(sem_prec_avail); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + } + result = sem_unlink(sem_prec_avail_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + } + result = sem_close(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + } + result = sem_unlink(sem_prec_read_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + } + } + + // Cleanup trace + trace_cleanup(); + + // Cleanup chunk done semaphore + if (call_chunk_done) + { + result = sem_close(sem_chunk_done); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + } + result = sem_unlink(sem_chunk_done_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + } + } + + // Post shutdown donw semaphore + result = sem_post(sem_shutdown_done); + if (result == -1) + { + printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); + } +} \ No newline at end of file diff --git a/emulator-asm/src/server.hpp b/emulator-asm/src/server.hpp new file mode 100644 index 000000000..c76cefee9 --- /dev/null +++ b/emulator-asm/src/server.hpp @@ -0,0 +1,11 @@ +#ifndef EMULATOR_ASM_SERVER_HPP +#define EMULATOR_ASM_SERVER_HPP + +void server_setup (void); +void server_reset_fast (void); +void server_reset_slow (void); +void server_reset_trace (void); +void server_run (void); +void server_cleanup (void); + +#endif // EMULATOR_ASM_SERVER_HPP \ No newline at end of file diff --git a/emulator-asm/src/trace.c b/emulator-asm/src/trace.c new file mode 100644 index 000000000..f3fe2863d --- /dev/null +++ b/emulator-asm/src/trace.c @@ -0,0 +1,238 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "trace.hpp" +#include "constants.hpp" +#include "globals.hpp" +#include "emu.hpp" + +/********************/ +/* TRACE ALLOCATION */ +/********************/ + +void trace_virtual_alloc (void) +{ + void * addr = mmap((void *)TRACE_ADDR, TRACE_MAX_SIZE, PROT_NONE, MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr != (void *)TRACE_ADDR) + { + printf("ERROR: trace_virtual_alloc() failed to reserve trace memory at address 0x%lx\n", TRACE_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +uint64_t next_chunk_id = 0; // Next trace chunk id to be mapped, starting from 0 +int trace_chunk_fd[TRACE_NUMBER_OF_CHUNKS]; // File descriptors for each chunk +uint64_t trace_total_mapped_size = 0; // Total mapped trace size + +void * trace_get_chunk_address (uint64_t chunk_id) +{ + assert(gen_method != RomHistogram || chunk_id == 0); + + if (chunk_id == 0) + { + return (void *)TRACE_ADDR; + } + else + { + return (void *)(TRACE_ADDR + TRACE_INITIAL_SIZE + ((chunk_id - 1) * TRACE_DELTA_SIZE)); + } +} + +uint64_t trace_get_chunk_size (uint64_t chunk_id) +{ + if (gen_method == RomHistogram) { + assert(chunk_id == 0); + return trace_size; + } + + if (chunk_id == 0) + { + return TRACE_INITIAL_SIZE; + } + else + { + return TRACE_DELTA_SIZE; + } +} + +void trace_generate_shmem_chunk_name(char * shmem_chunk_name, size_t shmem_chunk_name_size, uint64_t chunk_id) +{ + int result = snprintf(shmem_chunk_name, shmem_chunk_name_size, "%s_%lu", shmem_output_name, chunk_id); + if (result < 0 || result >= (int)shmem_chunk_name_size) + { + printf("ERROR: trace_generate_shmem_chunk_name() failed to create chunk shared memory name\n"); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +void trace_cleanup (void) +{ + // Unmap all mapped chunks + for (uint64_t chunk_id = 0; chunk_id < next_chunk_id; chunk_id++) + { + uint64_t chunk_size = trace_get_chunk_size(chunk_id); + void * chunk_address = trace_get_chunk_address(chunk_id); + int result = munmap(chunk_address, chunk_size); + if (result != 0) + { + printf("ERROR: trace_cleanup() failed calling munmap() chunk id=%lu size=%lu B address=0x%lx errno=%d=%s\n", chunk_id, chunk_size, (uint64_t)chunk_address, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Close the chunk shared memory file descriptor + close(trace_chunk_fd[chunk_id]); + trace_chunk_fd[chunk_id] = -1; + + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + shm_unlink(shmem_chunk_name); + } + + // Reset next chunk id + next_chunk_id = 0; +} + +void trace_preventive_cleanup (void) +{ + // Unmap all mapped chunks + for (uint64_t chunk_id = 0; chunk_id < TRACE_NUMBER_OF_CHUNKS; chunk_id++) + { + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + int result = shm_unlink(shmem_chunk_name); + if (result != 0) + { + break; + } + if (verbose) printf("trace_preventive_cleanup() unlinked chunk shared memory %s\n", shmem_chunk_name); + } +} + +void trace_map_next_chunk (void) +{ + // Get the next chunk id, size and address + uint64_t chunk_id = next_chunk_id; + if (chunk_id >= TRACE_NUMBER_OF_CHUNKS) + { + printf("ERROR: trace_map_next_chunk() exceeded maximum number of chunks %lu\n", TRACE_NUMBER_OF_CHUNKS); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t chunk_size = trace_get_chunk_size(chunk_id); + void * chunk_address = trace_get_chunk_address(chunk_id); + + if (verbose) printf("trace_map_next_chunk() mapping chunk id=%lu size=%lu B address=0x%lx\n", chunk_id, chunk_size, (uint64_t)chunk_address); + + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + shm_unlink(shmem_chunk_name); + + // Create the output shared memory + trace_chunk_fd[chunk_id] = shm_open(shmem_chunk_name, O_RDWR | O_CREAT | O_EXCL, 0666); + if (trace_chunk_fd[chunk_id] < 0) + { + printf("ERROR: trace_map_next_chunk() failed calling trace shm_open(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + int result = ftruncate(trace_chunk_fd[chunk_id], chunk_size); + if (result != 0) + { + printf("ERROR: trace_map_next_chunk() failed calling ftruncate(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(trace_chunk_fd[chunk_id]); + + // Map it to the trace address + if (verbose) gettimeofday(&start_time, NULL); + void * requested_address; + if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) + { + requested_address = 0; + } + else + { + requested_address = (void *)chunk_address; + } + int flags = MAP_SHARED | map_locked_flag; + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + flags |= MAP_FIXED; + } + void * pTrace = mmap(requested_address, chunk_size, PROT_READ | PROT_WRITE, flags, trace_chunk_fd[chunk_id], 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pTrace == MAP_FAILED) + { + printf("ERROR: trace_map_next_chunk() failed calling mmap(pTrace) name=%s errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && ((uint64_t)pTrace != (uint64_t)requested_address)) + { + printf("ERROR: trace_map_next_chunk() called mmap(trace) but returned address = %p != 0x%lx\n", pTrace, (uint64_t)requested_address); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("trace_map_next_chunk() mapped %lu B to %s and returned address %p in %lu us\n", chunk_size, shmem_chunk_name, pTrace, duration); + + // Update total mapped size + trace_total_mapped_size += chunk_size; + + // Increment next chunk id + next_chunk_id++; +} + +void trace_map_initialize (void) +{ + // Perform preventive cleanup of any leftover shared memory chunks + trace_preventive_cleanup(); + + // Reserve the full virtual trace address space + trace_virtual_alloc(); + + // Map the first chunk, i.e. chunk 0 + trace_map_next_chunk(); + + trace_address = TRACE_ADDR; + pOutputTrace = (uint64_t *)TRACE_ADDR; +} \ No newline at end of file diff --git a/emulator-asm/src/trace.hpp b/emulator-asm/src/trace.hpp new file mode 100644 index 000000000..84983df20 --- /dev/null +++ b/emulator-asm/src/trace.hpp @@ -0,0 +1,12 @@ +#ifndef EMULATOR_ASM_TRACE_HPP +#define EMULATOR_ASM_TRACE_HPP + +#include + +extern uint64_t trace_total_mapped_size; // Total mapped trace size + +void trace_map_initialize (void); +void trace_map_next_chunk (void); +void trace_cleanup (void); + +#endif // EMULATOR_ASM_TRACE_HPP \ No newline at end of file diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c new file mode 100644 index 000000000..3272c0027 --- /dev/null +++ b/emulator-asm/src/trace_logs.c @@ -0,0 +1,678 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include "constants.hpp" +#include "globals.hpp" +#include "asm_provided.hpp" +#include "globals.hpp" + +/*****************/ +/* LOG FUNCTIONS */ +/*****************/ + +/* Trace data structure + [8B] Number of chunks: C + + Chunk 0: + Start state: + [8B] pc + [8B] sp + [8B] c + [8B] step + [8B] register[1] + … + [8B] register[31] + [8B] register[32] + [8B] register[33] + Last state: + [8B] c + End: + [8B] end + Steps: + [8B] steps = chunk size except for the last chunk + [8B] mem_reads_size + [8B] mem_reads[0] + [8B] mem_reads[1] + … + [8B] mem_reads[mem_reads_size - 1] + + Chunk 1: + … + Chunk C-1: + … +*/ +void log_minimal_trace(void) +{ + uint64_t * pOutput = (uint64_t *)TRACE_ADDR; + printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] + printf("Minimal trace used size = %lu B\n", pOutput[3]); // Minimal trace used size [8] + + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; + uint64_t number_of_chunks = trace[0]; + printf("Number of chunks=%lu\n", number_of_chunks); + if (number_of_chunks > 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (trace_trace) + { + for (uint64_t m=0; m 100000000) + { + printf("ERROR: Bios size is too high=%lu\n", bios_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (trace_trace) + { + uint64_t * bios = trace + 1; + for (uint64_t i=0; i 100000000) + { + printf("ERROR: Program size is too high=%lu\n", program_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (trace_trace) + { + uint64_t * program = trace + 1 + bios_size + 1; + for (uint64_t i=0; i 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Main_trace size is too high=%lu\n", main_trace_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (trace_trace) + { + for (uint64_t m=0; m 0) + { + size_t bytes_written = fwrite(buffer_address, 1, buffer_length, file); + if (bytes_written != buffer_length) + { + printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%lu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + fclose(file); + exit(-1); + } + } + + if (fclose(file) != 0) + { + printf("ERROR: buffer2file() failed calling fclose(%s) errno=%d=%s\n", file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +/* Memory operations structure + [8B] Number of chunks = C + + Chunk 0: + [8b] end + [8B] mem_op_trace_size + [8B] mem_op_trace[0] + [8B] mem_op_trace[1] + … + [8B] mem_op_trace[mem_op_trace_size - 1] + + Chunk 1: + … + Chunk C-1: + … +*/ +void log_mem_op(void) +{ + // Log header + uint64_t * pOutput = (uint64_t *)TRACE_ADDR; + printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] + + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; + uint64_t number_of_chunks = trace[0]; + printf("Number of chunks=%lu\n", number_of_chunks); + if (number_of_chunks > 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + for (uint64_t m=0; m> 49) & 0x1; + uint64_t write = (chunk[i] >> 48) & 0x1; + uint64_t width = (chunk[i] >> 32) & 0xF; + uint64_t address = chunk[i] & 0xFFFFFFFF; + bool inside_range = + ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || + ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || + ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); + if (trace_trace || !inside_range) + { + printf("\t\tchunk[%lu].mem_op_trace[%lu] = %016lx = rest_are_zeros=%lx, write=%lx, width=%lx, address=%lx%s\n", + c, + m, + chunk[i], + rest_are_zeros, + write, + width, + address, + inside_range ? "" : " ERROR!!!!!!!!!!!!!!" + ); + } + i += 1; + } + + //Set next chunk pointer + chunk = chunk + i; + } + printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); +} + +/* Memory trace structure (for 1 chunk) + [8B] mem_trace_size + [16B] mem_trace[0] + [8B] mem operacion + [4B] address (LE) + [1B] width (1, 2, 4, 8) + write (0, 1) << 4 + [3B] + [16B] mem_trace[1] + … + [16B] mem_trace[mem_trace_size - 1] +*/ +void log_mem_trace(void) +{ + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)TRACE_ADDR; + printf("log_mem_trace() trace_address=%p\n", trace); + uint64_t i=0; + printf("Version = 0x%06lx\n", trace[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", trace[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", trace[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", trace[3]); // Main trace used size [8] + i += 4; + uint64_t number_of_entries = trace[i]; + i++; + printf("Trace size=%lu\n", number_of_entries); + + for (uint64_t m = 0; m < number_of_entries; m++) + { + uint64_t addr_step = trace[i]; + i++; + + // addr_step = [@0, @1, @2, @3, width + write<<4, supra_step] + uint64_t address = addr_step & 0xFFFFFFFF; + uint64_t width = (addr_step >> (4*8)) & 0xF; + uint64_t write = (addr_step >> ((4*8) + 4)) & 0x1; + uint64_t micro_step = (addr_step >> (5*8)) & 0x3; + uint64_t incremental_step = (addr_step >> ((5*8) + 2)); + bool address_is_inside_range = + ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || + ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || + ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); + bool width_is_valid = (width == 1) || (width == 2) || (width == 4) || (width == 8); + bool bError = !(address_is_inside_range && width_is_valid); + if (trace_trace || bError) + { + printf("\tmem_trace[%lu] = %016lx = [inc_step=%lu, u_step=%lu, write=%lx, width=%lx, address=%lx] %s\n", + m, + addr_step, + incremental_step, + micro_step, + write, + width, + address, + bError ? " ERROR!!!!!!!!!!!!!!" : "" + ); + } + + // u-step: + // 0: a=SRC_MEM + // 1: b=SRC_MEM or b=SRC_IND + // 2: precompiled_read + // 3: c=STORE_MEM, c=STORE_IND or precompiled_write + + bool address_is_aligned = (address & 0x7) == 0; + uint64_t aligned_address = address & 0xFFFFFFF8; + uint64_t number_of_read_values = 0; + uint64_t number_of_write_values = 0; + + switch (micro_step) + { + case 0: // a=SRC_MEM + { + assert(width == 8); + if (address_is_aligned) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + break; + } + case 1: // b=SRC_MEM or b=SRC_IND + { + if (address_is_aligned) + { + number_of_read_values = 1; + } + else + { + if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + } + break; + } + case 2: // precompiled_read + { + assert(width == 8); + if (address_is_aligned) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + break; + } + case 3: // c=STORE_MEM, c=STORE_IND or precompiled_write + { + if (address_is_aligned && (width == 8)) + { + number_of_read_values = 0; + } + else + { + if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + } + number_of_write_values = 1; + break; + } + } + + for (uint64_t r = 0; r < number_of_read_values; r++) + { + uint64_t value = trace[i]; + i++; + m++; + if (trace_trace) + { + printf("\t\tread_value[%lu] = 0x%lx\n", i, value); + } + } + + for (uint64_t w = 0; w < number_of_write_values; w++) + { + uint64_t value = trace[i]; + i++; + m++; + if (trace_trace) + { + printf("\t\twrite_value[%lu] = 0x%lx\n", i, value); + } + } + } + printf("Trace=%p number_of_entries=%lu\n", trace, number_of_entries); +} + +void save_mem_op_to_files(void) +{ + // Log header + uint64_t * pOutput = (uint64_t *)TRACE_ADDR; + printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] + + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; + uint64_t number_of_chunks = trace[0]; + printf("Number of chunks=%lu\n", number_of_chunks); + if (number_of_chunks > 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + printf("Chunk %lu: file=%s length=%lu\n", c, file_name, mem_op_trace_size); + + buffer2file(&chunk[i], mem_op_trace_size * 8, file_name); + + //Set next chunk pointer + chunk = chunk + mem_op_trace_size + 1; + } + printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); +} + +/* Trace data structure + [8B] Number of elements + + A series of elements with the following structure: + [8B] op: instruction opcode + [8B] a: register a value + [8B] b: register b value + [8B] precompiled_memory_address: memory read address of the precompiled input data +*/ +void log_chunk_player_main_trace(void) +{ + uint64_t * chunk = (uint64_t *)TRACE_ADDR; + uint64_t i = 0; + + printf("Version = 0x%06lx\n", chunk[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", chunk[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", chunk[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", chunk[3]); // Main trace used size [8] + i = 4; + + uint64_t mem_reads_size = chunk[i]; + i++; + printf("mem_reads_size=%lu\n", mem_reads_size); + if (mem_reads_size > 10000000) + { + printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + //if (trace_trace) + { + for (uint64_t m=0; m 0xFF) + { + printf("ERROR!! Invalid op=%lu=0x%lx\n", op, op); + } + if (trace_trace) printf("\tmem_reads[%lu] a=0x%08lx\n", m, chunk[i]); + i++; + m++; + if (trace_trace) printf("\tmem_reads[%lu] b=0x%08lx\n", m, chunk[i]); + i++; + m++; + if ( (op == 0xf1) // Keccak + || (op == 0xf9) // SHA256 + || (op == 0xf2) // Arith256 + || (op == 0xf3) // Arith256Mod + || (op == 0xf4) // Secp256k1Add + || (op == 0xf5) // Secp256k1Dbl + ) + { + if (trace_trace) printf("\tmem_reads[%lu] precompiled_address=%08lx\n", m, chunk[i]); + i++; + m++; + } + } + } + + printf("Chunk=%p size=%lu\n", chunk, mem_reads_size); +} \ No newline at end of file diff --git a/emulator-asm/src/trace_logs.hpp b/emulator-asm/src/trace_logs.hpp new file mode 100644 index 000000000..258804f23 --- /dev/null +++ b/emulator-asm/src/trace_logs.hpp @@ -0,0 +1,14 @@ +#ifndef EMULATOR_ASM_TRACE_LOGS_HPP +#define EMULATOR_ASM_TRACE_LOGS_HPP + +#include + +void log_minimal_trace(void); +void log_histogram(void); +void log_main_trace(void); +void log_mem_trace(void); +void log_mem_op(void); +void save_mem_op_to_files(void); +void log_chunk_player_main_trace(void); + +#endif // EMULATOR_ASM_TRACE_LOGS_HPP \ No newline at end of file From fe45d07a79e6cb252c0fcaced1ffa23d33d61c27 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 28 Feb 2026 10:27:51 +0000 Subject: [PATCH 671/782] added ZiskExecutorSummary --- cli/src/commands/execute.rs | 9 +++-- cli/src/commands/prove.rs | 22 +++++------- cli/src/commands/verify_constraints.rs | 16 ++++----- cli/src/ux.rs | 23 ++++++++++++ common/src/types.rs | 26 +++++++++++--- executor/src/executor.rs | 21 +++++++++-- executor/src/state.rs | 12 +++---- sdk/src/prover/backend.rs | 8 ++--- sdk/src/prover/mod.rs | 50 +++++++++++++------------- 9 files changed, 120 insertions(+), 67 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index bba869268..0abe59a0c 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -7,7 +7,7 @@ use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::ElfBinaryFromFile; use zisk_sdk::{ProverClient, ZiskExecuteResult}; -use crate::ux::{print_banner, print_banner_command, print_banner_field}; +use crate::ux::{print_banner, print_banner_command, print_banner_field, print_execution_summary}; use zisk_common::io::{StreamSource, ZiskStdin}; #[derive(Parser)] @@ -120,7 +120,12 @@ impl ZiskExecute { let result = if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, hints_stream)? }; - info!("Execution completed in {:.2?}, steps: {}", result.duration, result.execution.steps); + info!("{}", "--- EXECUTE SUMMARY ------------------------".bright_green().bold()); + print_execution_summary( + &result.executor_summary.executor_time, + result.total_duration, + result.executor_summary.steps, + ); Ok(()) } diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 12ba1da0d..57d9e729d 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -1,10 +1,10 @@ -use crate::ux::{print_banner, print_banner_command, print_banner_field}; +use crate::ux::{print_banner, print_banner_command, print_banner_field, print_execution_summary}; use anyhow::Result; use colored::Colorize; use proofman_common::ParamsGPU; use std::path::PathBuf; -use tracing::warn; +use tracing::{info, warn}; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ElfBinaryFromFile; @@ -182,12 +182,7 @@ impl ZiskProve { }; if world_rank == 0 { - let elapsed = result.get_duration().as_secs_f64(); - tracing::info!(""); - tracing::info!( - "{}", - "--- PROVE SUMMARY ------------------------".bright_green().bold() - ); + info!("{}", "--- PROVE SUMMARY ------------------------".bright_green().bold()); if let Some(proof_id) = &result.get_proof_id() { let output_dir = match result.get_proof() { @@ -202,13 +197,12 @@ impl ZiskProve { } }; result.save_proof_with_publics(output_dir)?; - tracing::info!(" Proof ID: {}", proof_id); + info!("Proof ID: {}", proof_id); } - tracing::info!(" ► Statistics"); - tracing::info!( - " time: {} seconds, steps: {}", - elapsed, - result.get_execution_steps() + print_execution_summary( + &result.executor_summary.executor_time, + result.duration, + result.executor_summary.steps, ); } diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index 2e94385aa..d2bfb7312 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -1,10 +1,10 @@ -use crate::ux::{print_banner, print_banner_command, print_banner_field}; +use crate::ux::{print_banner, print_banner_command, print_banner_field, print_execution_summary}; use anyhow::Result; use clap::Parser; use colored::Colorize; use std::path::PathBuf; -use tracing::warn; +use tracing::{info, warn}; use zisk_build::ZISK_VERSION_MESSAGE; use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ElfBinaryFromFile; @@ -133,16 +133,14 @@ impl ZiskVerifyConstraints { let result = if emulator { self.run_emu(stdin)? } else { self.run_asm(stdin, hints_stream)? }; - tracing::info!(""); - tracing::info!( + info!( "{}", "--- VERIFY CONSTRAINTS SUMMARY ------------------------".bright_green().bold() ); - tracing::info!(" ► Statistics"); - tracing::info!( - " time: {:.2} seconds, steps: {}", - result.duration.as_secs_f32(), - result.execution.steps + print_execution_summary( + &result.executor_summary.executor_time, + result.duration, + result.executor_summary.steps, ); Ok(()) diff --git a/cli/src/ux.rs b/cli/src/ux.rs index e8823082a..3e5fde8d6 100644 --- a/cli/src/ux.rs +++ b/cli/src/ux.rs @@ -1,5 +1,7 @@ use colored::Colorize; use sysinfo::System; +use tracing::info; +use zisk_common::ZiskExecutorTime; pub fn print_banner() { println!(); @@ -57,3 +59,24 @@ pub fn print_banner_command(command: impl std::fmt::Display) { pub fn print_banner_field(label: &str, value: impl std::fmt::Display) { println!("{} {}", format!("{: >12}", label).bright_green().bold(), value); } + +pub fn print_execution_summary( + executor_time: &ZiskExecutorTime, + total_duration: std::time::Duration, + steps: u64, +) { + info!("Execution completed in {:.2?}, steps: {}", total_duration, steps); + info!( + "Execution summary: {} {:.2?} + {} {:.2?} + {} {:.2?} + {} {:.2?}", + "Proofman".dimmed(), + total_duration - executor_time.total_duration, + "Execution".dimmed(), + executor_time.execution_duration, + "Count&Plan".dimmed(), + executor_time.count_and_plan_duration, + "Count&Plan MO".dimmed(), + executor_time.count_and_plan_mo_duration, + ); + + /*●⎿✔◼✽*/ +} diff --git a/common/src/types.rs b/common/src/types.rs index 91d1b7a4d..409a61643 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -2,6 +2,7 @@ use anyhow::Result; use std::fmt; use std::fs; use std::path::Path; +use std::time::Duration; use std::time::Instant; /// Type representing a chunk identifier. @@ -143,14 +144,31 @@ impl fmt::Display for StatsCostPerType { } #[derive(Debug, Default, Clone)] -pub struct ZiskExecutionResult { +pub struct ZiskExecutorTime { + /// Total executor duration of the entire execution process. + pub total_duration: Duration, + /// Duration of the execution phase. + pub execution_duration: Duration, + /// Duration of the counting and planning phase for main state machines. + pub count_and_plan_duration: Duration, + /// Duration of the counting and planning phase for memory operations from ASM runner. + pub count_and_plan_mo_duration: Duration, +} + +#[derive(Debug, Default, Clone)] +pub struct ZiskExecutorSummary { pub steps: u64, + pub executor_time: ZiskExecutorTime, pub cost_per_type: StatsCostPerType, } -impl ZiskExecutionResult { - pub fn new(executed_steps: u64, cost_per_type: StatsCostPerType) -> Self { - Self { steps: executed_steps, cost_per_type } +impl ZiskExecutorSummary { + pub fn new( + executed_steps: u64, + execution_time: ZiskExecutorTime, + cost_per_type: StatsCostPerType, + ) -> Self { + Self { steps: executed_steps, executor_time: execution_time, cost_per_type } } } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index fdb2a81d9..74aead2c5 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -34,7 +34,7 @@ use std::{ use witness::WitnessComponent; use zisk_common::{ io::ZiskStdin, stats_begin, stats_end, BusDeviceMetrics, ChunkId, ExecutorStatsHandle, - StatsCostPerType, StatsType, ZiskExecutionResult, + StatsCostPerType, StatsType, ZiskExecutorSummary, ZiskExecutorTime, }; use zisk_core::ZiskRom; use zisk_pil::ZiskPublicValues; @@ -112,7 +112,7 @@ impl ZiskExecutor { /// Gets the execution result and stats. #[allow(clippy::type_complexity)] - pub fn get_execution_result(&self) -> (ZiskExecutionResult, ExecutorStatsHandle) { + pub fn get_execution_result(&self) -> (ZiskExecutorSummary, ExecutorStatsHandle) { (self.state.get_execution_result(), self.state.get_stats()) } @@ -130,6 +130,7 @@ impl WitnessComponent for ZiskExecutor { sctx: Arc>, global_ids: &RwLock>, ) -> ProofmanResult<()> { + let start_total = Instant::now(); self.state.reset(); stats_begin!(self.state.stats, 0, _exec_scope, "EXECUTE", 0); @@ -139,6 +140,7 @@ impl WitnessComponent for ZiskExecutor { // Phase 1: Execute ROM to collect minimal traces timer_start_info!(COMPUTE_MINIMAL_TRACE); + let start_partial = Instant::now(); let zisk_rom = self .state @@ -153,12 +155,15 @@ impl WitnessComponent for ZiskExecutor { &_exec_scope, ); + let execution_duration = start_partial.elapsed(); timer_stop_and_log_info!(COMPUTE_MINIMAL_TRACE); // Phase 2: Plan main instances stats_begin!(self.state.stats, &_exec_scope, _main_plan_scope, "MAIN_PLAN", 0); timer_start_info!(PLAN); + let start_partial = Instant::now(); + let main_output = self.planner.plan_main::(&output.min_traces, output.main_count); *self.state.min_traces.write().unwrap() = Some(output.min_traces); @@ -175,10 +180,12 @@ impl WitnessComponent for ZiskExecutor { let mut secn_planning = self.planner.plan_secondary(self.registry.sm_bundle(), &mut secn_count); + let count_and_plan_duration = start_partial.elapsed(); timer_stop_and_log_info!(PLAN); timer_start_info!(PLAN_MEM_CPP); stats_end!(self.state.stats, &_secn_plan_scope); + let start_partial = Instant::now(); // Handle memory operations from ASM runner if let Some(handle_mo) = output.handle_mo { @@ -198,6 +205,7 @@ impl WitnessComponent for ZiskExecutor { stats_end!(self.state.stats, &_mo_add_scope); } + let count_and_plan_mo_duration = start_partial.elapsed(); timer_stop_and_log_info!(PLAN_MEM_CPP); // Phase 4: Configure and assign secondary instances @@ -255,8 +263,15 @@ impl WitnessComponent for ZiskExecutor { cost_per_type.add_cost(StatsType::Tables, cost); } + let zisk_execution_time = ZiskExecutorTime { + execution_duration, + count_and_plan_duration, + count_and_plan_mo_duration, + total_duration: start_total.elapsed(), + }; // Store the execution result - let execution_result = ZiskExecutionResult::new(output.steps, cost_per_type); + let execution_result = + ZiskExecutorSummary::new(output.steps, zisk_execution_time, cost_per_type); // Store the execution result self.state.set_execution_result(execution_result); diff --git a/executor/src/state.rs b/executor/src/state.rs index 08e71cc27..b3931be41 100644 --- a/executor/src/state.rs +++ b/executor/src/state.rs @@ -10,7 +10,7 @@ use std::{ Arc, Mutex, RwLock, }, }; -use zisk_common::{BusDevice, EmuTrace, ExecutorStatsHandle, Instance, Plan, ZiskExecutionResult}; +use zisk_common::{BusDevice, EmuTrace, ExecutorStatsHandle, Instance, Plan, ZiskExecutorSummary}; use zisk_core::ZiskRom; /// Type alias for chunk collectors: (chunk_id, collector) @@ -36,7 +36,7 @@ pub struct ExecutionState { pub collectors_by_instance: Arc>>>>, /// Execution result, including the number of executed steps. - pub execution_result: Mutex, + pub execution_result: Mutex, /// Statistics collected during the execution. pub stats: ExecutorStatsHandle, @@ -58,7 +58,7 @@ impl ExecutionState { main_instances: RwLock::new(HashMap::new()), secn_instances: RwLock::new(HashMap::new()), collectors_by_instance: Arc::new(RwLock::new(HashMap::new())), - execution_result: Mutex::new(ZiskExecutionResult::default()), + execution_result: Mutex::new(ZiskExecutorSummary::default()), stats: ExecutorStatsHandle::new(), is_rom_initialized: AtomicBool::new(false), use_hints: AtomicBool::new(false), @@ -95,7 +95,7 @@ impl ExecutionState { /// Resets all internal state to default values. pub fn reset(&self) { - *self.execution_result.lock().unwrap() = ZiskExecutionResult::default(); + *self.execution_result.lock().unwrap() = ZiskExecutorSummary::default(); *self.min_traces.write().unwrap() = None; *self.secn_planning.write().unwrap() = Vec::new(); self.main_instances.write().unwrap().clear(); @@ -105,12 +105,12 @@ impl ExecutionState { } /// Gets a clone of the execution result. - pub fn get_execution_result(&self) -> ZiskExecutionResult { + pub fn get_execution_result(&self) -> ZiskExecutorSummary { self.execution_result.lock().unwrap().clone() } /// Sets the execution result. - pub fn set_execution_result(&self, result: ZiskExecutionResult) { + pub fn set_execution_result(&self, result: ZiskExecutorSummary) { *self.execution_result.lock().unwrap() = result; } diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index fd3ee5af4..c389ae768 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -24,7 +24,7 @@ use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; use zisk_common::stats_mark; -use zisk_common::{io::ZiskStdin, ElfBinaryLike, ExecutorStatsHandle, ZiskExecutionResult}; +use zisk_common::{io::ZiskStdin, ElfBinaryLike, ExecutorStatsHandle, ZiskExecutorSummary}; pub(crate) struct ProverBackend { proofman: Option>, @@ -101,7 +101,7 @@ impl ProverBackend { Ok(()) } - pub fn execution_result(&self) -> Result<(ZiskExecutionResult, ExecutorStatsHandle)> { + pub fn execution_result(&self) -> Result<(ZiskExecutorSummary, ExecutorStatsHandle)> { let executor = self.executor.as_ref().ok_or_else(|| { anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") })?; @@ -142,7 +142,7 @@ impl ProverBackend { let publics = proofman.get_publics(); - Ok(ZiskExecuteResult::new(result, planning_info, elapsed, &publics)) + Ok(ZiskExecuteResult::new(elapsed, result, planning_info, &publics)) } pub(crate) fn stats( @@ -197,7 +197,7 @@ impl ProverBackend { ) .map_err(|e| anyhow::anyhow!("Error generating execution: {}", e))?; - let (_, stats): (ZiskExecutionResult, ExecutorStatsHandle) = + let (_, stats): (ZiskExecutorSummary, ExecutorStatsHandle) = executor.get_execution_result(); Ok((rank_info.world_rank, rank_info.n_processes, Some(stats))) diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index b885d5504..ec46e4a42 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -27,24 +27,24 @@ use std::{ use tracing::info; use zisk_common::io::StreamSource; use zisk_common::ElfBinaryLike; -use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, StatsCostPerType, ZiskExecutionResult}; +use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, StatsCostPerType, ZiskExecutorSummary}; use zisk_core::ZiskRom; pub struct ZiskExecuteResult { - pub execution: ZiskExecutionResult, + pub total_duration: Duration, + pub executor_summary: ZiskExecutorSummary, pub planning_info: PlanningInfo, - pub duration: Duration, pub publics: ZiskPublics, } impl ZiskExecuteResult { pub fn new( - execution: ZiskExecutionResult, + total_duration: Duration, + executor_summary: ZiskExecutorSummary, planning_info: PlanningInfo, - duration: Duration, publics: &[u8], ) -> Self { - Self { execution, planning_info, duration, publics: ZiskPublics::new(publics) } + Self { total_duration, executor_summary, planning_info, publics: ZiskPublics::new(publics) } } pub fn get_publics(&self) -> &ZiskPublics { @@ -58,24 +58,24 @@ impl ZiskExecuteResult { } pub fn get_execution_steps(&self) -> u64 { - self.execution.steps + self.executor_summary.steps } pub fn get_execution_total_cost(&self) -> u64 { - self.execution.cost_per_type.total_cost() + self.executor_summary.cost_per_type.total_cost() } pub fn get_execution_cost_per_type(&self) -> &StatsCostPerType { - &self.execution.cost_per_type + &self.executor_summary.cost_per_type } pub fn get_duration(&self) -> Duration { - self.duration + self.total_duration } } pub struct ZiskVerifyConstraintsResult { - pub execution: ZiskExecutionResult, + pub executor_summary: ZiskExecutorSummary, pub duration: Duration, pub stats: ExecutorStatsHandle, pub publics: ZiskPublics, @@ -83,12 +83,12 @@ pub struct ZiskVerifyConstraintsResult { impl ZiskVerifyConstraintsResult { pub fn new( - execution: ZiskExecutionResult, + execution: ZiskExecutorSummary, duration: Duration, stats: ExecutorStatsHandle, publics: &[u8], ) -> Self { - Self { execution, duration, stats, publics: ZiskPublics::new(publics) } + Self { executor_summary: execution, duration, stats, publics: ZiskPublics::new(publics) } } pub fn get_publics(&self) -> &ZiskPublics { @@ -102,15 +102,15 @@ impl ZiskVerifyConstraintsResult { } pub fn get_execution_steps(&self) -> u64 { - self.execution.steps + self.executor_summary.steps } pub fn get_execution_total_cost(&self) -> u64 { - self.execution.cost_per_type.total_cost() + self.executor_summary.cost_per_type.total_cost() } pub fn get_execution_cost_per_type(&self) -> &StatsCostPerType { - &self.execution.cost_per_type + &self.executor_summary.cost_per_type } pub fn get_duration(&self) -> Duration { @@ -634,8 +634,8 @@ impl ZiskProofWithPublicValues { } pub struct ZiskProveResult { - execution: ZiskExecutionResult, - duration: Duration, + pub executor_summary: ZiskExecutorSummary, + pub duration: Duration, stats: ExecutorStatsHandle, proof_id: Option, proof_with_publics: ZiskProofWithPublicValues, @@ -643,22 +643,22 @@ pub struct ZiskProveResult { impl ZiskProveResult { pub fn new( - execution: ZiskExecutionResult, + execution: ZiskExecutorSummary, duration: Duration, stats: ExecutorStatsHandle, proof_id: Option, proof_with_publics: ZiskProofWithPublicValues, ) -> Self { - Self { execution, duration, stats, proof_id, proof_with_publics } + Self { executor_summary: execution, duration, stats, proof_id, proof_with_publics } } pub fn new_null( - execution: ZiskExecutionResult, + execution: ZiskExecutorSummary, duration: Duration, stats: ExecutorStatsHandle, ) -> Self { Self { - execution, + executor_summary: execution, duration, stats, proof_id: None, @@ -679,15 +679,15 @@ impl ZiskProveResult { } pub fn get_execution_steps(&self) -> u64 { - self.execution.steps + self.executor_summary.steps } pub fn get_execution_total_cost(&self) -> u64 { - self.execution.cost_per_type.total_cost() + self.executor_summary.cost_per_type.total_cost() } pub fn get_execution_cost_per_type(&self) -> &StatsCostPerType { - &self.execution.cost_per_type + &self.executor_summary.cost_per_type } pub fn get_proof_id(&self) -> Option<&String> { From 1d6662db1b81ea5bd60479fd9c9bd059a7ff4c32 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Mon, 2 Mar 2026 17:11:17 +0000 Subject: [PATCH 672/782] Refactor HintBuffer to flush writer buf when WRITE_BUFFER_FLUSH_LEN threshold is reached --- ziskos/entrypoint/src/hints/hint_buffer.rs | 69 ++++++++++++---------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index 17dfe1960..da52b4186 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -6,6 +6,7 @@ use zisk_common::{CTRL_END, CTRL_START}; pub const DEFAULT_BUFFER_LEN: usize = 1 << 20; // 1 MiB // TODO: Set MAX_WRITE_LEN based on writer type (file or socket) pub const MAX_WRITER_LEN: usize = 128 * 1024; // 128KB is the max write size for Unix sockets +pub const WRITE_BUFFER_FLUSH_LEN: usize = 64 * 1024; // Flush writer buffer once it exceeds 64KB pub const HEADER_LEN: usize = 8; pub struct HintBuffer { @@ -134,6 +135,22 @@ impl HintBuffer { Ok(()) }; + fn flush_write_buf(write_all: &mut F, buf: &mut Vec) -> io::Result<()> + where + F: FnMut(&[u8]) -> io::Result<()>, + { + if buf.is_empty() { + return Ok(()); + } + + debug_assert!(buf.len() <= MAX_WRITER_LEN); + write_all(buf)?; + buf.clear(); + + Ok(()) + } + + let mut write_buf = Vec::with_capacity(WRITE_BUFFER_FLUSH_LEN); 'drain: loop { // Get chunk of hints to write from HintBuffer (under lock) let chunk: Bytes = { @@ -160,10 +177,6 @@ impl HintBuffer { let chunk_len = chunk.len(); let chunk_base = chunk.as_ptr(); - // Start and end of the write buffer (MAX_WRITER_LEN) - let mut buf_start = 0usize; - let mut buf_end = 0usize; - while chunk_pos < chunk_len { let hint_header = unsafe { let header_bytes = core::slice::from_raw_parts(chunk_base.add(chunk_pos), 8); @@ -180,20 +193,10 @@ impl HintBuffer { let pad = (8 - (hint_data_len & 7)) & 7; let hint_len = HEADER_LEN + hint_data_len + pad; - // If adding this hint exceeds max write size, flush current write buffer - if buf_end - buf_start + hint_len > MAX_WRITER_LEN { - let buf: &[u8] = unsafe { - core::slice::from_raw_parts(chunk_base.add(buf_start), buf_end - buf_start) - }; - write_all(buf)?; - - // Reset write buffer - buf_start = chunk_pos; - buf_end = chunk_pos; - } - - // If single hint exceeds MAX_WRITER_LEN, write it in chunks + // If single hint exceeds MAX_WRITER_LEN, write it in chunks directly if hint_len > MAX_WRITER_LEN { + flush_write_buf(&mut write_all, &mut write_buf)?; + let mut hint_pos = 0usize; while hint_pos < hint_len { let chunk_size = std::cmp::min(MAX_WRITER_LEN, hint_len - hint_pos); @@ -208,28 +211,30 @@ impl HintBuffer { hint_pos += chunk_size; } - // Advance to next hint - chunk_pos += hint_len; - // Reset write buffer - buf_start = chunk_pos; - buf_end = chunk_pos; - } else { - // Accumulate current hint into write buffer - buf_end += hint_len; - // Advance to next hint + chunk_pos += hint_len; + continue; + } + + let hint_bytes: &[u8] = + unsafe { core::slice::from_raw_parts(chunk_base.add(chunk_pos), hint_len) }; + + if write_buf.len() + hint_len > MAX_WRITER_LEN { + flush_write_buf(&mut write_all, &mut write_buf)?; } + + write_buf.extend_from_slice(hint_bytes); + + chunk_pos += hint_len; } - // Write any remaining data in write buffer - if buf_end > buf_start { - let buf: &[u8] = unsafe { - core::slice::from_raw_parts(chunk_base.add(buf_start), buf_end - buf_start) - }; - write_all(buf)?; + if write_buf.len() >= WRITE_BUFFER_FLUSH_LEN { + flush_write_buf(&mut write_all, &mut write_buf)?; } } + flush_write_buf(&mut write_all, &mut write_buf)?; + // Flush the writer and debug writer at the end writer.flush()?; if let Some(debug_writer) = debug_writer.as_deref_mut() { From d7dd28cdc1e462bb38617203ff0c314c28decda8 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Mon, 2 Mar 2026 17:26:36 +0000 Subject: [PATCH 673/782] Add configurable write flush threshold to HintBuffer --- ziskos/entrypoint/src/hints/hint_buffer.rs | 8 ++++++-- ziskos/entrypoint/src/hints/mod.rs | 21 +++++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index da52b4186..53a0d45f9 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -119,6 +119,7 @@ impl HintBuffer { &self, writer: &mut W, mut debug_writer: Option<&mut D>, + write_flush_threshold: usize, ) -> io::Result<()> where W: Write + ?Sized, @@ -150,7 +151,10 @@ impl HintBuffer { Ok(()) } - let mut write_buf = Vec::with_capacity(WRITE_BUFFER_FLUSH_LEN); + let mut flush_threshold = std::cmp::min(write_flush_threshold, MAX_WRITER_LEN); + flush_threshold = flush_threshold.max(1); + + let mut write_buf = Vec::with_capacity(flush_threshold); 'drain: loop { // Get chunk of hints to write from HintBuffer (under lock) let chunk: Bytes = { @@ -228,7 +232,7 @@ impl HintBuffer { chunk_pos += hint_len; } - if write_buf.len() >= WRITE_BUFFER_FLUSH_LEN { + if write_buf.len() >= flush_threshold { flush_write_buf(&mut write_all, &mut write_buf)?; } } diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 9d8805981..6dbe36914 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -14,7 +14,7 @@ mod sha256f; #[cfg(zisk_hints_metrics)] mod metrics; -use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer}; +use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer, MAX_WRITER_LEN}; use anyhow::{anyhow, Result}; use once_cell::sync::Lazy; use std::cell::UnsafeCell; @@ -130,6 +130,7 @@ pub fn init_hints_file(hints_file_path: PathBuf, ready: Option, + write_flush_threshold: usize, ready: Option>, ) -> Result<()> { wait_for_hints_writer()?; @@ -152,7 +153,9 @@ pub fn init_hints_socket( init_hints(); - let handle = thread::spawn(move || write_hints_to_socket(socket_writer, debug_file)); + let handle = thread::spawn(move || { + write_hints_to_socket(socket_writer, debug_file, write_flush_threshold) + }); HINT_WRITER_HANDLE.store(handle); Ok(()) @@ -188,9 +191,10 @@ pub fn close_hints() -> Result<()> { pub fn write_hints( writer: &mut W, debug_writer: Option<&mut dyn Write>, + write_flush_threshold: usize, ) -> io::Result<()> { // Write hints from the buffer - HINT_BUFFER.drain_to_writer(writer, debug_writer)?; + HINT_BUFFER.drain_to_writer(writer, debug_writer, write_flush_threshold)?; #[cfg(zisk_hints_metrics)] crate::hints::metrics::print_metrics(); @@ -204,7 +208,7 @@ fn write_hints_to_file(path: PathBuf) -> io::Result<()> { let file = std::fs::File::create(path)?; let mut file_writer = BufWriter::with_capacity(1 << 20, file); - write_hints(&mut file_writer, None)?; + write_hints(&mut file_writer, None, MAX_WRITER_LEN)?; Ok(()) } @@ -253,15 +257,20 @@ impl Write for UnixSocketWriter { fn write_hints_to_socket( mut socket_writer: UnixSocketWriter, debug_file: Option, + write_flush_threshold: usize, ) -> io::Result<()> { debug_assert!(cfg!(target_endian = "little")); if let Some(path) = debug_file { let file = std::fs::File::create(path)?; let mut debug_writer = BufWriter::with_capacity(1 << 20, file); // 1 MiB buffer - write_hints(&mut socket_writer, Some(&mut debug_writer as &mut dyn Write))?; + write_hints( + &mut socket_writer, + Some(&mut debug_writer as &mut dyn Write), + write_flush_threshold, + )?; } else { - write_hints(&mut socket_writer, None)?; + write_hints(&mut socket_writer, None, write_flush_threshold)?; } socket_writer.close().map_err(io::Error::other)?; From bbc6c9aab5ede0b878eb1a4777b68c5448035f19 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Mon, 2 Mar 2026 17:28:37 +0000 Subject: [PATCH 674/782] Change write_flush_threshold to Option type in init_hints_socket and provide default value --- ziskos/entrypoint/src/hints/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 6dbe36914..5b155f8bf 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -130,7 +130,7 @@ pub fn init_hints_file(hints_file_path: PathBuf, ready: Option, - write_flush_threshold: usize, + write_flush_threshold: Option, ready: Option>, ) -> Result<()> { wait_for_hints_writer()?; @@ -154,7 +154,8 @@ pub fn init_hints_socket( init_hints(); let handle = thread::spawn(move || { - write_hints_to_socket(socket_writer, debug_file, write_flush_threshold) + let flush_threshold = write_flush_threshold.unwrap_or(MAX_WRITER_LEN); + write_hints_to_socket(socket_writer, debug_file, flush_threshold) }); HINT_WRITER_HANDLE.store(handle); From 308ab1af53451852c87b47bdc1c42aeb4e42adb6 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Mon, 2 Mar 2026 17:30:57 +0000 Subject: [PATCH 675/782] Update init_hints_socket to use WRITE_BUFFER_FLUSH_LEN as default flush threshold --- ziskos/entrypoint/src/hints/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index 5b155f8bf..cbb3057c4 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -14,7 +14,9 @@ mod sha256f; #[cfg(zisk_hints_metrics)] mod metrics; -use crate::hints::hint_buffer::{build_hint_buffer, HintBuffer, MAX_WRITER_LEN}; +use crate::hints::hint_buffer::{ + build_hint_buffer, HintBuffer, MAX_WRITER_LEN, WRITE_BUFFER_FLUSH_LEN, +}; use anyhow::{anyhow, Result}; use once_cell::sync::Lazy; use std::cell::UnsafeCell; @@ -154,7 +156,7 @@ pub fn init_hints_socket( init_hints(); let handle = thread::spawn(move || { - let flush_threshold = write_flush_threshold.unwrap_or(MAX_WRITER_LEN); + let flush_threshold = write_flush_threshold.unwrap_or(WRITE_BUFFER_FLUSH_LEN); write_hints_to_socket(socket_writer, debug_file, flush_threshold) }); HINT_WRITER_HANDLE.store(handle); From 50ddc3391513c0aa8ea9ee1a65efbdd969f71a01 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 2 Mar 2026 22:02:46 +0100 Subject: [PATCH 676/782] Fix copilot issues --- emulator-asm/src/c_provided.c | 2 ++ emulator-asm/src/configuration.c | 10 +++++----- emulator-asm/src/server.c | 6 +++--- emulator-asm/src/trace_logs.c | 12 +++++++++--- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/emulator-asm/src/c_provided.c b/emulator-asm/src/c_provided.c index cecec70cf..643dc1ad0 100644 --- a/emulator-asm/src/c_provided.c +++ b/emulator-asm/src/c_provided.c @@ -117,6 +117,7 @@ extern int _print_regs() printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); printf("\n"); #endif + return 0; } /************/ @@ -189,6 +190,7 @@ extern int _print_pc (uint64_t pc, uint64_t c) printf("\n"); fflush(stdout); print_pc_counter++; + return 0; } /**************/ diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index 90cdf5781..251a9c153 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -187,7 +187,7 @@ void parse_arguments(int argc, char *argv[]) if (strcmp(argv[i], "--help") == 0) { print_usage(); - continue; + exit(0); } if (strcmp(argv[i], "-i") == 0) { @@ -212,13 +212,13 @@ void parse_arguments(int argc, char *argv[]) i++; if (i >= argc) { - printf("ERROR: Detected argument -i in the last position; please provide shared mem prefix after it\n"); + printf("ERROR: Detected argument --shm_prefix in the last position; please provide shared mem prefix after it\n"); print_usage(); exit(-1); } if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) { - printf("ERROR: Detected argument -i but next argument is too long\n"); + printf("ERROR: Detected argument --shm_prefix but next argument is too long\n"); print_usage(); exit(-1); } @@ -230,7 +230,7 @@ void parse_arguments(int argc, char *argv[]) i++; if (i >= argc) { - printf("ERROR: Detected argument -c in the last position; please provide chunk number after it\n"); + printf("ERROR: Detected argument --chunk in the last position; please provide chunk number after it\n"); print_usage(); exit(-1); } @@ -270,7 +270,7 @@ void parse_arguments(int argc, char *argv[]) i++; if (i >= argc) { - printf("ERROR: Detected argument -mt in the last position; please provide number of MT requests after it\n"); + printf("ERROR: Detected argument --mt in the last position; please provide number of MT requests after it\n"); print_usage(); exit(-1); } diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index 60fdb49f4..369dc15e7 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -112,7 +112,7 @@ void server_setup (void) } // Open the input shared memory as read-only - shmem_input_fd = shm_open(shmem_input_name, O_RDONLY | O_EXCL, 0666); + shmem_input_fd = shm_open(shmem_input_name, O_RDONLY, 0666); if (shmem_input_fd < 0) { printf("ERROR: Failed calling input RO shm_open(%s) as read-only errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); @@ -195,7 +195,7 @@ void server_setup (void) } // Open the precompile shared memory as read-only - shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDONLY | O_EXCL, 0666); + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDONLY, 0666); if (shmem_precompile_fd < 0) { printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); @@ -266,7 +266,7 @@ void server_setup (void) } // Open the control input shared memory as read-only - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY | O_EXCL, 0666); + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY, 0666); if (shmem_control_input_fd < 0) { printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index 3272c0027..cd583c36a 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -9,7 +9,6 @@ #include "constants.hpp" #include "globals.hpp" #include "asm_provided.hpp" -#include "globals.hpp" /*****************/ /* LOG FUNCTIONS */ @@ -265,7 +264,7 @@ void log_main_trace(void) printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); } -void buffer2file (const void * buffer_address, size_t buffer_length, const char * file_name) +static void buffer2file (const void * buffer_address, size_t buffer_length, const char * file_name) { if (!file_name) { @@ -588,7 +587,14 @@ void save_mem_op_to_files(void) for (uint64_t c=0; c= sizeof(file_name)) + { + fprintf(stderr, "ERROR: Failed to construct file name for chunk=%lu\n", c); + fflush(stdout); + fflush(stderr); + exit(-1); + } uint64_t i=0; uint64_t mem_op_trace_size = chunk[i]; From 263041435b54e8ee57e1d39bbc088c9ef943cfef Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 3 Mar 2026 10:21:54 +0100 Subject: [PATCH 677/782] Fix copilot issues --- emulator-asm/src/configuration.c | 4 ++-- emulator-asm/src/constants.hpp | 2 ++ emulator-asm/src/trace_logs.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index 251a9c153..7ac488f9f 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -349,7 +349,7 @@ void parse_arguments(int argc, char *argv[]) char *endptr; char * argument = argv[i]; if ((argument[0] == '0') && (argument[1] == 'x')) argument += 2; - chunk_player_address = strtoul(argv[i], &endptr, 16); + chunk_player_address = strtoul(argument, &endptr, 16); // Check for errors if (errno == ERANGE) { @@ -357,7 +357,7 @@ void parse_arguments(int argc, char *argv[]) print_usage(); exit(-1); } else if (endptr == argument) { - printf("ERROR: No digits found while parsing chunk addresss\n"); + printf("ERROR: No digits found while parsing chunk address\n"); print_usage(); exit(-1); } else if (*endptr != '\0') { diff --git a/emulator-asm/src/constants.hpp b/emulator-asm/src/constants.hpp index 9860a6a22..c9a88259a 100644 --- a/emulator-asm/src/constants.hpp +++ b/emulator-asm/src/constants.hpp @@ -1,6 +1,8 @@ #ifndef EMULATOR_ASM_CONSTANTS_HPP #define EMULATOR_ASM_CONSTANTS_HPP +#include + /***************/ /* Definitions */ /***************/ diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index cd583c36a..9d0c9b5b1 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -295,7 +295,7 @@ static void buffer2file (const void * buffer_address, size_t buffer_length, cons size_t bytes_written = fwrite(buffer_address, 1, buffer_length, file); if (bytes_written != buffer_length) { - printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%lu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); + printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%zu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); fflush(stdout); fflush(stderr); fclose(file); From 82be4527ca45a91f03cd2aaf0be0bb894beb8ced Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 3 Mar 2026 11:21:02 +0100 Subject: [PATCH 678/782] Fix copilot issues --- emulator-asm/src/trace_logs.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index 9d0c9b5b1..d3e0df35a 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -10,6 +10,10 @@ #include "globals.hpp" #include "asm_provided.hpp" +// This file contains trace logging functions that are used only for debugging purposes, to log the +// content of the generated traces in a human-readable format. These functions are not used by the +// assembly code, and are not optimized for performance. + /*****************/ /* LOG FUNCTIONS */ /*****************/ @@ -597,6 +601,7 @@ void save_mem_op_to_files(void) } uint64_t i=0; + i++; // Skip end uint64_t mem_op_trace_size = chunk[i]; i++; if (mem_op_trace_size > 10000000) From 19f93db64f8b1475c680c505471a98eae905e3db Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 3 Mar 2026 12:47:08 +0100 Subject: [PATCH 679/782] Fix copilot issues --- emulator-asm/Makefile | 2 +- emulator-asm/src/configuration.c | 11 +++++++++-- emulator-asm/src/globals.hpp | 1 + emulator-asm/src/server.c | 2 +- emulator-asm/src/trace.c | 1 - emulator-asm/src/trace_logs.c | 4 ++-- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 75835a40b..4a963aa4b 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -56,7 +56,7 @@ build/dma.o: $(DMA_OBJS) ld -r $(DMA_OBJS) -o $@ # Compile the final executable -$(OUT_PATH): build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c build/dma.o +$(OUT_PATH): build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c build/dma.o mkdir -p $(OUT_DIR) gcc $(CFLAGS) src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index 7ac488f9f..ef33c7308 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -216,7 +216,7 @@ void parse_arguments(int argc, char *argv[]) print_usage(); exit(-1); } - if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) + if (strlen(argv[i]) >= MAX_SHM_PREFIX_LENGTH) { printf("ERROR: Detected argument --shm_prefix but next argument is too long\n"); print_usage(); @@ -311,7 +311,14 @@ void parse_arguments(int argc, char *argv[]) } errno = 0; char *endptr; - arguments_port = strtoul(argv[i], &endptr, 10); + uint64_t arguments_port_u64 = strtoul(argv[i], &endptr, 10); + if (arguments_port_u64 > 0xFFFF) + { + printf("ERROR: Port number is too large, must be at most 65535\n"); + print_usage(); + exit(-1); + } + arguments_port = arguments_port_u64 & 0xFFFF; // Keep only lower 16 bits, since port numbers are 16 bits // Check for errors if (errno == ERANGE) { diff --git a/emulator-asm/src/globals.hpp b/emulator-asm/src/globals.hpp index f66e0044c..3d1bf6ecf 100644 --- a/emulator-asm/src/globals.hpp +++ b/emulator-asm/src/globals.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "constants.hpp" // Configuration globals, set by arguments diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index 369dc15e7..e16ddb624 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -949,7 +949,7 @@ void server_cleanup (void) } } - // Post shutdown donw semaphore + // Post shutdown done semaphore result = sem_post(sem_shutdown_done); if (result == -1) { diff --git a/emulator-asm/src/trace.c b/emulator-asm/src/trace.c index f3fe2863d..45ddca19d 100644 --- a/emulator-asm/src/trace.c +++ b/emulator-asm/src/trace.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index d3e0df35a..d63c0f973 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -616,8 +616,8 @@ void save_mem_op_to_files(void) buffer2file(&chunk[i], mem_op_trace_size * 8, file_name); - //Set next chunk pointer - chunk = chunk + mem_op_trace_size + 1; + //Set next chunk pointer: skip [end] and [mem_op_trace_size] headers plus data + chunk = chunk + mem_op_trace_size + 2; } printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); } From 4530bf34cb105a35ebcb22ca650bf56684c01853 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Tue, 3 Mar 2026 15:55:57 +0000 Subject: [PATCH 680/782] Enhance hint metrics to track hint size --- ziskos/entrypoint/src/hints/hint_buffer.rs | 12 +++++++----- ziskos/entrypoint/src/hints/metrics.rs | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index 53a0d45f9..7097012ac 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -187,16 +187,18 @@ impl HintBuffer { u64::from_le_bytes(header_bytes.try_into().unwrap()) }; + let hint_data_len = (hint_header & 0xFFFF_FFFF) as usize; + let pad = (8 - (hint_data_len & 7)) & 7; + let hint_len = HEADER_LEN + hint_data_len + pad; + #[cfg(zisk_hints_metrics)] { + use std::hint; + let hint_id = (hint_header >> 32) as u32 & 0x7FFF_FFFF; - crate::hints::metrics::inc_hint_count(hint_id); + crate::hints::metrics::inc_hint_count(hint_id, hint_len as u64); } - let hint_data_len = (hint_header & 0xFFFF_FFFF) as usize; - let pad = (8 - (hint_data_len & 7)) & 7; - let hint_len = HEADER_LEN + hint_data_len + pad; - // If single hint exceeds MAX_WRITER_LEN, write it in chunks directly if hint_len > MAX_WRITER_LEN { flush_write_buf(&mut write_all, &mut write_buf)?; diff --git a/ziskos/entrypoint/src/hints/metrics.rs b/ziskos/entrypoint/src/hints/metrics.rs index 1b6ff2a46..decbefe81 100644 --- a/ziskos/entrypoint/src/hints/metrics.rs +++ b/ziskos/entrypoint/src/hints/metrics.rs @@ -8,6 +8,7 @@ pub(crate) static HINTS_METRICS: Lazy>> = pub(crate) struct HintRegisterInfo { pub hint_name: String, pub count: u64, + pub size: u64, } pub(crate) fn register_hint(hint_id: u32, hint_name: String) { @@ -17,10 +18,11 @@ pub(crate) fn register_hint(hint_id: u32, hint_name: String) { .insert(hint_id, HintRegisterInfo { hint_name, count: 0 }); } -pub(crate) fn inc_hint_count(hint_id: u32) { +pub(crate) fn inc_hint_count(hint_id: u32, hint_size: u64) { if let Ok(mut hints) = HINTS_METRICS.write() { if let Some(info) = hints.get_mut(&hint_id) { info.count += 1; + info.size += hint_size; } } } @@ -28,14 +30,25 @@ pub(crate) fn inc_hint_count(hint_id: u32) { pub(crate) fn print_metrics() { let hints = HINTS_METRICS.read().expect("HINTS_METRICS poisoned"); let mut total_hints = 0; + let mut total_size = 0; println!("Hints usage summary:"); + for (_, info) in hints.iter() { + total_hints += info.count; + total_size += info.size; + } for (_, info) in hints.iter() { if info.count > 0 { - println!(" {}: {}", info.hint_name, info.count); + println!( + " {}: {}, {} bytes ({}%)", + info.hint_name, + info.count, + info.size, + info.size * 100 / total_size + ); } - total_hints += info.count; } println!("Total hints: {}", total_hints); + println!("Total size: {} bytes", total_size); } pub(crate) fn reset_metrics() { From f1a00fe95df369c231ab33371be21b39cfb3e5d9 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Tue, 3 Mar 2026 16:46:35 +0000 Subject: [PATCH 681/782] Add size initialization to HintRegisterInfo --- ziskos/entrypoint/src/hints/metrics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ziskos/entrypoint/src/hints/metrics.rs b/ziskos/entrypoint/src/hints/metrics.rs index decbefe81..9a0a40c6c 100644 --- a/ziskos/entrypoint/src/hints/metrics.rs +++ b/ziskos/entrypoint/src/hints/metrics.rs @@ -15,7 +15,7 @@ pub(crate) fn register_hint(hint_id: u32, hint_name: String) { HINTS_METRICS .write() .expect("HINTS_METRICS poisoned") - .insert(hint_id, HintRegisterInfo { hint_name, count: 0 }); + .insert(hint_id, HintRegisterInfo { hint_name, count: 0, size: 0 }); } pub(crate) fn inc_hint_count(hint_id: u32, hint_size: u64) { From 11e1bc92cd2770729e15802da826dbeedf81f9c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Wed, 4 Mar 2026 11:03:50 +0100 Subject: [PATCH 682/782] Fix BN254 (#827) --- ziskos-hints/src/handlers/bn254.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ziskos-hints/src/handlers/bn254.rs b/ziskos-hints/src/handlers/bn254.rs index 0bdbe7a4f..2c5953925 100644 --- a/ziskos-hints/src/handlers/bn254.rs +++ b/ziskos-hints/src/handlers/bn254.rs @@ -59,11 +59,6 @@ pub fn bn254_pairing_check_hint(data: &[u64]) -> Result> { let num_pairs = data[0] as usize; - // Prevent absurd sizes early (optional but defensive) - if num_pairs == 0 { - anyhow::bail!("BN254_PAIRING_CHECK: num_pairs is zero"); - } - let expected_len = 1 + num_pairs * PAIR_WORDS; validate_hint_length(data, expected_len, "PAIRING_BATCH_BN254")?; From 89c077fe611b238434fcfae086c837b5fba8da32 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:45:01 +0000 Subject: [PATCH 683/782] added input hint --- common/src/hints.rs | 13 +++++++++++++ precompiles/hints/src/hints_processor.rs | 3 +++ 2 files changed, 16 insertions(+) diff --git a/common/src/hints.rs b/common/src/hints.rs index a8030f690..8a98a74a1 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -58,6 +58,9 @@ pub const CTRL_END: u32 = 0x0001; pub const CTRL_CANCEL: u32 = 0x0002; pub const CTRL_ERROR: u32 = 0x0003; +// === INPUT HINT CODES === +pub const HINT_INPUT: u32 = 0xF000; + // === BUILT-IN HINT CODES === // SHA256 hint codes pub const HINT_SHA256: u32 = 0x0100; @@ -139,6 +142,10 @@ impl TryFrom for CtrlHint { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u32)] pub enum BuiltInHint { + // INPUT hint types. + /// Input data hint. + Input = HINT_INPUT, + // SHA256 hint types. /// Compute SHA-256 hash Sha256 = HINT_SHA256, @@ -197,6 +204,8 @@ pub enum BuiltInHint { impl Display for BuiltInHint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = match self { + // INPUT hint types + BuiltInHint::Input => "INPUT", // SHA256 hint types BuiltInHint::Sha256 => "SHA256", // BN254 Hints @@ -237,6 +246,8 @@ impl TryFrom for BuiltInHint { fn try_from(value: u32) -> Result { match value { + // INPUT hint types + HINT_INPUT => Ok(Self::Input), // SHA256 hint types HINT_SHA256 => Ok(Self::Sha256), // BN254 Hints @@ -322,6 +333,8 @@ impl HintCode { HintCode::Ctrl(CtrlHint::Error) => CTRL_ERROR, // Built-In Hint Codes + // INPUT hint types + HintCode::BuiltIn(BuiltInHint::Input) => HINT_INPUT, // SHA256 Hints HintCode::BuiltIn(BuiltInHint::Sha256) => HINT_SHA256, // BN254 Hints diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index bf9ef5e26..2b56a43a3 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -668,6 +668,9 @@ impl HintsProcessor { data_len_bytes: usize, ) -> Result> { match hint { + // Input Hint Codes + BuiltInHint::Input => unimplemented!("Input hint unimplemented"), + // SHA256 Hint Codes BuiltInHint::Sha256 => sha256_hint(&data, data_len_bytes), From 97fb54920c66b02c86aa97d0004187f4f047e595 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 4 Mar 2026 12:12:40 +0000 Subject: [PATCH 684/782] Fix size percentage calculation in print_metrics --- ziskos/entrypoint/src/hints/metrics.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ziskos/entrypoint/src/hints/metrics.rs b/ziskos/entrypoint/src/hints/metrics.rs index 9a0a40c6c..706e7b64b 100644 --- a/ziskos/entrypoint/src/hints/metrics.rs +++ b/ziskos/entrypoint/src/hints/metrics.rs @@ -38,12 +38,17 @@ pub(crate) fn print_metrics() { } for (_, info) in hints.iter() { if info.count > 0 { + let percentage = if total_size == 0 { + 0.0 + } else { + ((info.size as f64 * 100.0) / total_size as f64 * 10.0).round() / 10.0 + }; println!( - " {}: {}, {} bytes ({}%)", + " {}: {}, {} bytes ({:.1}%)", info.hint_name, info.count, info.size, - info.size * 100 / total_size + percentage ); } } From df4da679a2df438389e8bb0073704c68395751aa Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 4 Mar 2026 12:15:11 +0000 Subject: [PATCH 685/782] Fix cfmt --- ziskos/entrypoint/src/hints/metrics.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ziskos/entrypoint/src/hints/metrics.rs b/ziskos/entrypoint/src/hints/metrics.rs index 706e7b64b..41dff6b1b 100644 --- a/ziskos/entrypoint/src/hints/metrics.rs +++ b/ziskos/entrypoint/src/hints/metrics.rs @@ -15,7 +15,7 @@ pub(crate) fn register_hint(hint_id: u32, hint_name: String) { HINTS_METRICS .write() .expect("HINTS_METRICS poisoned") - .insert(hint_id, HintRegisterInfo { hint_name, count: 0, size: 0 }); + .insert(hint_id, HintRegisterInfo { hint_name, count: 0, size: 0 }); } pub(crate) fn inc_hint_count(hint_id: u32, hint_size: u64) { @@ -45,10 +45,7 @@ pub(crate) fn print_metrics() { }; println!( " {}: {}, {} bytes ({:.1}%)", - info.hint_name, - info.count, - info.size, - percentage + info.hint_name, info.count, info.size, percentage ); } } From 0ec4d7ee5d8fa57228ba260a734b2ac6b5dfda3e Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 4 Mar 2026 13:38:09 +0100 Subject: [PATCH 686/782] Implement FCALL_INPUT_READY_ID in ziskemu --- core/src/inst_context.rs | 4 ++ core/src/zisk_ops.rs | 41 ++++++++++++++++--- emulator/src/emu_context.rs | 8 ++-- ziskos/entrypoint/src/zisklib/fcalls/input.rs | 23 +++++++++++ ziskos/entrypoint/src/zisklib/fcalls/mod.rs | 2 + 5 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls/input.rs diff --git a/core/src/inst_context.rs b/core/src/inst_context.rs index a0846f0f8..cb8fbf622 100644 --- a/core/src/inst_context.rs +++ b/core/src/inst_context.rs @@ -125,6 +125,9 @@ pub struct InstContext { pub extended_arg: i64, pub stats_hint: u64, + + /// Input data length, stored in the context to be used by the FCALL_INPUT_READY_ID fcall + pub input_len: u64, } /// RisK instruction context implementation @@ -149,6 +152,7 @@ impl InstContext { data_ext_len: 0, extended_arg: 0, stats_hint: 0, + input_len: 0, } } diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 9e58d912e..5a8a764d9 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -12,6 +12,10 @@ use precompiles_helpers::DmaInfo; use ziskos_hints::zisklib::fcall_proxy; +use crate::{ + blake2br, sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, + EXTRA_PARAMS_ADDR, INPUT_ADDR, M64, MAX_INPUT_SIZE, REG_A0, SYS_ADDR, +}; use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; use paste::paste; use std::{ @@ -21,11 +25,7 @@ use std::{ str::FromStr, }; use tiny_keccak::keccakf; - -use crate::{ - blake2br, sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, - EXTRA_PARAMS_ADDR, M64, REG_A0, SYS_ADDR, -}; +use ziskos_hints::zisklib::FCALL_INPUT_READY_ID; use lib_c::{inverse_fn_ec_c, inverse_fp_ec_c, sqrt_fp_ec_parity_c, Fcall, FcallContext}; @@ -2500,7 +2500,36 @@ pub fn opc_fcall(ctx: &mut InstContext) { // Get function id from a let function_id = ctx.a; - let iresult = fcall_proxy(function_id, &ctx.fcall.parameters, &mut ctx.fcall.result); + let iresult = if function_id == FCALL_INPUT_READY_ID as u64 { + let required_address = ctx.fcall.parameters[0] as u64; + if required_address < INPUT_ADDR + 8 { + panic!( + "opc_fcall() FCALL_INPUT_READY_ID called with required_address {:#x} < {:#x}", + required_address, + INPUT_ADDR + 8 + ); + } + if required_address >= INPUT_ADDR + MAX_INPUT_SIZE as u64 - 1 { + panic!( + "opc_fcall() FCALL_INPUT_READY_ID called with required_address {:#x} > {:#x}", + required_address, + INPUT_ADDR + MAX_INPUT_SIZE as u64 - 1 + ); + } + + let required_bytes = required_address - INPUT_ADDR - 8 + 1; // + 1 because required_address is the address of the last required byte + if required_bytes > ctx.input_len { + panic!( + "opc_fcall() FCALL_INPUT_READY_ID called with required_address {:#x} requiring {} bytes, but only {} bytes available", + required_address, + required_bytes, + ctx.input_len + ); + } + 0 + } else { + fcall_proxy(function_id, &ctx.fcall.parameters, &mut ctx.fcall.result) + }; if iresult < 0 { panic!("opc_fcall() failed calling Fcall() function_id={function_id} iresult={iresult}"); diff --git a/emulator/src/emu_context.rs b/emulator/src/emu_context.rs index 0eec2486b..997e1a51c 100644 --- a/emulator/src/emu_context.rs +++ b/emulator/src/emu_context.rs @@ -15,7 +15,6 @@ pub struct EmuContext { pub last_callback_step: u64, pub trace: EmuTrace, pub do_stats: bool, - pub max_input_mem: u64, pub stats: Stats, } @@ -35,19 +34,18 @@ impl EmuContext { last_callback_step: 0, do_stats: false, stats: Stats::default(), - max_input_mem: options.max_input_mem, // 128 MiB }; // Check the input data size is inside the proper range - if input.len() > (ctx.max_input_mem - 16) as usize { + if input.len() > (options.max_input_mem - 16) as usize { panic!("EmuContext::new() input size too big size={}", input.len()); } // Add the length and input data read sections - let input_len = input.len() as u64; + ctx.inst_ctx.input_len = input.len() as u64; let free_input = 0u64; ctx.inst_ctx.mem.add_read_section(INPUT_ADDR, &free_input.to_le_bytes()); - ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8, &input_len.to_le_bytes()); + ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8, &ctx.inst_ctx.input_len.to_le_bytes()); ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 16, &input); // Add the write section diff --git a/ziskos/entrypoint/src/zisklib/fcalls/input.rs b/ziskos/entrypoint/src/zisklib/fcalls/input.rs new file mode 100644 index 000000000..b4ebbadd0 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls/input.rs @@ -0,0 +1,23 @@ +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { + use core::arch::asm; + use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; + use super::FCALL_INPUT_READY_ID; + } +} + +#[allow(unused_variables)] +pub fn fcall_input_ready(address: &u64) { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + { + // TODO: wait for input to be ready at the given address, then check the input length vs. address and return an error if the input is not long enough. For now, we just return immediately. + unimplemented!("fcall_input_ready is not implemented for non-zisk targets"); + } + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + ziskos_fcall_param!(address, 1); // Number of inputs + ziskos_fcall!(FCALL_INPUT_READY_ID); + } +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs index 35cf72e47..c28d61487 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs @@ -21,12 +21,14 @@ pub const FCALL_BIN_DECOMP_ID: u16 = 18; pub const FCALL_BLS12_381_FP2_SQRT_ID: u16 = 19; pub const FCALL_SECP256K1_ECDSA_VERIFY_ID: u16 = 20; pub const FCALL_SECP256R1_ECDSA_VERIFY_ID: u16 = 21; +pub const FCALL_INPUT_READY_ID: u16 = 22; mod big_int256_div; mod big_int_div; mod bin_decomp; mod bls12_381; mod bn254; +mod input; mod msb_pos_256; mod msb_pos_384; mod secp256k1; From a548530dbdb0c7758c16d13684629ce2cec70e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Thu, 5 Mar 2026 09:34:37 +0100 Subject: [PATCH 687/782] Feat/logs coordinator (#826) * Updating coordinator with asm logs * Updating aggregate time real * Cargo fmt * Adding averages of phase1 and phase2 * Fix * added debug messages to coordinaotr+worker * Adding more logs * Adding logs for coordinator * Cargo update * Fix test * Fix * Fixing delay log * Removing unnecessary logs * Fix clippy * Cargo update --------- Co-authored-by: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> --- Cargo.lock | 144 ++++---- common/src/types.rs | 14 + distributed/crates/common/Cargo.toml | 1 + distributed/crates/common/src/dto.rs | 30 +- distributed/crates/common/src/types.rs | 13 +- .../crates/coordinator/src/coordinator.rs | 337 ++++++++++++++---- .../grpc-api/proto/zisk_distributed_api.proto | 21 +- .../crates/grpc-api/src/conversions.rs | 27 +- distributed/crates/worker/Cargo.toml | 1 + distributed/crates/worker/src/worker.rs | 84 +++-- distributed/crates/worker/src/worker_node.rs | 44 ++- emulator-asm/asm-runner/src/asm_mt_runner.rs | 7 +- examples/Cargo.lock | 109 +++--- examples/multiple-programs/host/src/main.rs | 4 +- examples/sha-hasher/host/src/main.rs | 2 +- executor/src/emu_asm.rs | 33 +- executor/src/emu_asm_stub.rs | 13 +- executor/src/emu_rust.rs | 7 +- executor/src/executor.rs | 13 +- executor/src/lib.rs | 20 +- executor/src/rom_executor.rs | 28 +- sdk/src/prover/asm.rs | 6 +- sdk/src/prover/backend.rs | 18 +- sdk/src/prover/emu.rs | 6 +- sdk/src/prover/mod.rs | 13 +- 25 files changed, 711 insertions(+), 284 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9ced45ba..f3534851b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,9 +394,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" dependencies = [ "aws-lc-sys", "zeroize", @@ -404,9 +404,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" dependencies = [ "cc", "cmake", @@ -1116,7 +1116,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "fields", "num-bigint", @@ -1388,9 +1388,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" dependencies = [ "serde", "serde_core", @@ -1470,7 +1470,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "cfg-if", "num-bigint", @@ -1647,20 +1647,20 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] @@ -2058,9 +2058,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" @@ -2165,9 +2165,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.90" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -2265,11 +2265,10 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags", "libc", ] @@ -2765,7 +2764,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "colored", "fields", @@ -2784,18 +2783,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", @@ -2804,9 +2803,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -3128,11 +3127,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit 0.25.4+spec-1.1.0", ] [[package]] @@ -3147,7 +3146,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "bincode", "blake3", @@ -3183,7 +3182,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "bincode", "borsh", @@ -3214,7 +3213,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "fields", "itoa", @@ -3227,7 +3226,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "proc-macro2", "quote", @@ -3237,7 +3236,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "crossbeam-channel", "tracing", @@ -3246,7 +3245,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "bincode", "bytemuck", @@ -3258,7 +3257,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "bytemuck", "fields", @@ -3399,9 +3398,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -3412,6 +3411,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -4285,7 +4290,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -4419,9 +4424,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -4436,9 +4441,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -4524,6 +4529,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -4540,12 +4554,12 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", "winnow", ] @@ -4884,7 +4898,7 @@ version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "serde_core", "wasm-bindgen", @@ -4992,9 +5006,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -5005,9 +5019,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.63" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a89f4650b770e4521aa6573724e2aed4704372151bd0de9d16a3bbabb87441a" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", "futures-util", @@ -5019,9 +5033,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5029,9 +5043,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -5042,9 +5056,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -5098,9 +5112,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.90" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705eceb4ce901230f8625bd1d665128056ccbe4b7408faa625eec1ba80f59a97" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -5695,7 +5709,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "colored", "fields", @@ -5781,18 +5795,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", @@ -5949,6 +5963,7 @@ dependencies = [ "tracing-appender", "tracing-subscriber", "uuid", + "zisk-common", ] [[package]] @@ -6012,6 +6027,7 @@ dependencies = [ "asm-runner", "borsh", "cargo-zisk", + "chrono", "clap", "colored", "config", diff --git a/common/src/types.rs b/common/src/types.rs index 409a61643..e323dc45b 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -153,6 +153,8 @@ pub struct ZiskExecutorTime { pub count_and_plan_duration: Duration, /// Duration of the counting and planning phase for memory operations from ASM runner. pub count_and_plan_mo_duration: Duration, + /// Execution duration of the ASM runner. + pub asm_execution_duration: Option, } #[derive(Debug, Default, Clone)] @@ -316,3 +318,15 @@ impl ElfBinaryLike for ElfBinary { self.with_hints } } + +#[derive(Default, Debug, Clone)] +pub struct AsmExecutionInfo { + pub time: f32, + pub mhz: f32, +} + +impl fmt::Display for AsmExecutionInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:.3}s ({:.0} MHz)", self.time, self.mhz) + } +} diff --git a/distributed/crates/common/Cargo.toml b/distributed/crates/common/Cargo.toml index 2189ed914..295ace4ac 100644 --- a/distributed/crates/common/Cargo.toml +++ b/distributed/crates/common/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" proofman = { workspace = true } proofman-common = { workspace = true } proofman-util = { workspace = true } +zisk-common = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } tracing-appender = { workspace = true } diff --git a/distributed/crates/common/src/dto.rs b/distributed/crates/common/src/dto.rs index 5f9540e5a..011944d16 100644 --- a/distributed/crates/common/src/dto.rs +++ b/distributed/crates/common/src/dto.rs @@ -204,13 +204,36 @@ pub struct ProveParamsDto { } #[derive(Clone)] -pub struct ExecutionInfoDto { - pub execution_time: f32, +pub struct WitnessInfoDto { + /// Witness computation time in milliseconds + pub witness_time: f32, pub publics: Vec, pub proof_values: Vec, pub summary_info: String, } +#[derive(Clone)] +pub struct ZiskExecutorTimeDto { + /// Total duration in milliseconds + pub total_duration: f32, + /// Execution duration in milliseconds + pub execution_duration: f32, + /// Count and plan duration in milliseconds + pub count_and_plan_duration: f32, + /// Count and plan memory operations duration in milliseconds + pub count_and_plan_mo_duration: f32, + /// ASM execution info (time in milliseconds) + pub asm_execution_duration: Option, + /// Time when task was received by worker (milliseconds since UNIX epoch, f64 for precision) + pub task_received_time: f64, +} + +#[derive(Clone)] +pub struct AsmExecutionInfoDto { + pub time: f32, + pub mhz: f32, +} + #[derive(Clone)] pub struct ChallengesDto { pub worker_index: u32, @@ -246,7 +269,8 @@ pub struct ExecuteTaskResponseDto { pub struct ContributionsResultDataDto { pub challenges: Vec, - pub execution_info: ExecutionInfoDto, + pub witness_info: WitnessInfoDto, + pub zisk_executor_time: ZiskExecutorTimeDto, } pub enum ExecuteTaskResponseResultDataDto { diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index a0f479610..5ef666bc5 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -6,7 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use chrono::{DateTime, Utc}; -use proofman::{ContributionsInfo, ExecutionInfo}; +use proofman::{ContributionsInfo, WitnessInfo}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -14,6 +14,7 @@ use std::{ ops::Range, }; use tracing::error; +use zisk_common::ZiskExecutorTime; use crate::{HintsModeDto, HintsSourceDto, InputSourceDto, InputsModeDto}; @@ -239,6 +240,7 @@ impl Debug for JobStats { pub struct Job { pub job_id: JobId, pub start_times: HashMap>, + pub task_received_time: Option>, pub duration_ms: Option, pub state: JobState, pub data_id: DataId, @@ -252,7 +254,7 @@ pub struct Job { pub results: HashMap>, pub stats: HashMap, pub challenges: Option>, - pub execution_info: Option, + pub witness_info: Option, pub execution_mode: JobExecutionMode, pub final_proof: Option>, pub executed_steps: Option, @@ -285,8 +287,9 @@ impl Job { partitions, results: HashMap::new(), stats: HashMap::new(), + task_received_time: None, challenges: None, - execution_info: None, + witness_info: None, execution_mode, final_proof: None, executed_steps: None, @@ -389,7 +392,9 @@ pub struct AggProofData { #[derive(Debug, Clone)] pub struct ContributionsResult { pub challenges: Vec, - pub execution_info: ExecutionInfo, + pub witness_info: WitnessInfo, + pub zisk_executor_time: ZiskExecutorTime, + pub task_received_time: Option>, } #[derive(Debug, Clone)] diff --git a/distributed/crates/coordinator/src/coordinator.rs b/distributed/crates/coordinator/src/coordinator.rs index 265f9169f..af3b89455 100644 --- a/distributed/crates/coordinator/src/coordinator.rs +++ b/distributed/crates/coordinator/src/coordinator.rs @@ -39,7 +39,7 @@ use crate::{ use chrono::{DateTime, Utc}; use colored::Colorize; use dashmap::DashMap; -use proofman::{ContributionsInfo, ExecutionInfo}; +use proofman::{ContributionsInfo, WitnessInfo}; use std::{ collections::HashMap, fs, @@ -52,6 +52,8 @@ use std::{ use tokio::sync::RwLock; use tracing::{error, info, warn}; use zisk_common::io::{StreamSource, ZiskStream}; +use zisk_common::AsmExecutionInfo; +use zisk_common::ZiskExecutorTime; use zisk_distributed_common::{ AggParamsDto, AggProofData, ChallengesDto, ComputeCapacity, ContributionParamsDto, ContributionsResult, CoordinatorMessageDto, DataId, ExecuteTaskRequestDto, @@ -665,9 +667,6 @@ impl Coordinator { let active_workers = active_workers.to_vec(); let total_workers = active_workers.len() as u32; - use futures::stream::{self, StreamExt}; - - // TODO!!!!! Can we avoid this clone ???? let cloned_active_workers = active_workers.clone(); let tasks = active_workers.into_iter().enumerate().map(|(rank_id, worker_id)| { let job_id = job.job_id.clone(); @@ -707,7 +706,9 @@ impl Coordinator { }); // Process tasks with a concurrency limit - let results: Vec<_> = stream::iter(tasks).buffer_unordered(16).collect().await; + use futures::stream::StreamExt; + + let results: Vec<_> = futures::stream::iter(tasks).buffer_unordered(16).collect().await; // Check for any errors for (worker_id, send_result, state_result) in results { @@ -1265,16 +1266,40 @@ impl Coordinator { }) .collect(); - let execution_info = ExecutionInfo { - summary_info: ch_list.execution_info.summary_info, - publics: ch_list.execution_info.publics, - proof_values: ch_list.execution_info.proof_values, - execution_time: ch_list.execution_info.execution_time, + let witness_info = WitnessInfo { + summary_info: ch_list.witness_info.summary_info, + publics: ch_list.witness_info.publics, + proof_values: ch_list.witness_info.proof_values, + witness_time: ch_list.witness_info.witness_time, + }; + + let zisk_executor_time = ZiskExecutorTime { + total_duration: Duration::from_secs_f32( + ch_list.zisk_executor_time.total_duration / 1000.0, + ), + execution_duration: Duration::from_secs_f32( + ch_list.zisk_executor_time.execution_duration / 1000.0, + ), + count_and_plan_duration: Duration::from_secs_f32( + ch_list.zisk_executor_time.count_and_plan_duration / 1000.0, + ), + count_and_plan_mo_duration: Duration::from_secs_f32( + ch_list.zisk_executor_time.count_and_plan_mo_duration / 1000.0, + ), + asm_execution_duration: ch_list.zisk_executor_time.asm_execution_duration.map( + |asm_info| AsmExecutionInfo { time: asm_info.time, mhz: asm_info.mhz }, + ), }; Ok(JobResultData::Challenges(ContributionsResult { - execution_info, + witness_info, challenges: contributions, + zisk_executor_time, + task_received_time: chrono::DateTime::::from_timestamp( + (ch_list.zisk_executor_time.task_received_time / 1000.0) as i64, + ((ch_list.zisk_executor_time.task_received_time % 1000.0) * 1_000_000.0) + as u32, + ), })) } _ => Err(CoordinatorError::InvalidRequest( @@ -1292,14 +1317,11 @@ impl Coordinator { /// /// * `job` - Reference to the job containing Phase 1 results fn print_execution_summary(&self, job: &Job) { - // Find the first completed contribution result to extract ExecutionInfo summary + // Find the first completed contribution result to extract WitnessInfo summary if let Some(contributions_results) = job.results.get(&JobPhase::Contributions) { if let Some((_worker_id, job_result)) = contributions_results.iter().next() { if let JobResultData::Challenges(contributions_result) = &job_result.data { - info!( - "Execution Summary: {}", - contributions_result.execution_info.summary_info - ); + info!("Execution Summary: {}", contributions_result.witness_info.summary_info); } } } @@ -1315,35 +1337,65 @@ impl Coordinator { job.results.get(&JobPhase::Contributions).map(|r| r.len()).unwrap_or(0); let end_time = Utc::now(); - let duration = end_time.signed_duration_since( - job.start_times.get(&JobPhase::Contributions).unwrap_or_else(|| { - error!("Missing start time for Phase1 in job {}", job.job_id); - &end_time - }), - ); + let phase_start_time = job.start_times.get(&JobPhase::Contributions).unwrap_or_else(|| { + error!("Missing start time for Phase1 in job {}", job.job_id); + &end_time + }); + let duration = end_time.signed_duration_since(phase_start_time); let duration_ms = Duration::from_millis(duration.num_milliseconds() as u64); - // Get execution time from the worker's result - let exec_time_info = job - .results - .get(&JobPhase::Contributions) - .and_then(|results| results.get(worker_id)) - .and_then(|job_result| match &job_result.data { + // Get execution info from the worker's result + let worker_result = + job.results.get(&JobPhase::Contributions).and_then(|results| results.get(worker_id)); + + let (asm_info_str, witness_time_str, delay_time_str) = if let Some(job_result) = + worker_result + { + match &job_result.data { JobResultData::Challenges(contributions_result) => { - Some(contributions_result.execution_info.execution_time) + // Calculate delay: time from coordinator sending job to worker receiving task + let delay_duration = contributions_result + .task_received_time + .map(|task_received| task_received.signed_duration_since(*phase_start_time)) + .unwrap_or_else(chrono::Duration::zero); + let delay_ms = delay_duration.num_milliseconds().max(0) as f32; + let delay_str = format!(", Delay: {:.3}s", delay_ms / 1000.0); + + let asm_str = contributions_result + .zisk_executor_time + .asm_execution_duration + .as_ref() + .map(|asm_info| { + format!( + ", Asm Execution: {:.3}s at {} MHz", + asm_info.time, asm_info.mhz + ) + }) + .unwrap_or_default(); + + let witness_str = format!( + ", Witness: {:.3}s", + contributions_result.witness_info.witness_time / 1000.0 + ); + + (asm_str, witness_str, delay_str) } - _ => None, - }) - .unwrap_or(0.0); + _ => (String::new(), String::new(), String::new()), + } + } else { + (String::new(), String::new(), String::new()) + }; info!( - "[Phase1] {} finished phase 1 for {} ({}/{} workers done, {:.3}s (execution {:.3}s))", + "[Phase1] {} finished phase 1 for {} ({}/{} workers done, Phase: {:.3}s{}{}{})", worker_id, job.job_id, phase1_results_len, job.workers.len(), duration_ms.as_secs_f32(), - exec_time_info + delay_time_str, + witness_time_str, + asm_info_str, ); // Ensure we have results from all assigned workers before proceeding. @@ -1415,24 +1467,24 @@ impl Coordinator { } else { // Standard mode: aggregate challenges from all participating workers // Each worker contributes their portion of the overall challenge space - let (challenges, execution_info): (Vec>, Vec) = + let (challenges, witness_info): (Vec>, Vec) = phase1_results .values() .map(|results| match &results.data { JobResultData::Challenges(values) => { - (values.challenges.clone(), values.execution_info.clone()) + (values.challenges.clone(), values.witness_info.clone()) } _ => unreachable!("Expected Challenges data in Phase1 results"), }) .unzip(); - let first = execution_info.first().ok_or_else(|| { - CoordinatorError::Internal(format!("No execution info found in job {}", job.job_id)) + let first = witness_info.first().ok_or_else(|| { + CoordinatorError::Internal(format!("No witness info found in job {}", job.job_id)) })?; let mut mismatched_workers = Vec::new(); - for (worker_idx, info) in execution_info.iter().enumerate() { + for (worker_idx, info) in witness_info.iter().enumerate() { if info.publics != first.publics || info.proof_values != first.proof_values { mismatched_workers.push((worker_idx, info)); } @@ -1451,7 +1503,7 @@ impl Coordinator { .collect(); return Err(CoordinatorError::Internal(format!( - "ExecutionInfo mismatch in job {}:\n{}", + "WitnessInfo mismatch in job {}:\n{}", job.job_id, mismatch_report.join("\n") ))); @@ -1579,6 +1631,10 @@ impl Coordinator { let all_done = self.check_phase2_completion(&job, &worker_id).await?; + if all_done { + job.start_times.insert(JobPhase::Aggregate, Utc::now()); + } + let proofs = self.collect_worker_proofs(&job, &agg_worker_id, &worker_id)?; drop(job); // Release jobs lock early @@ -1988,21 +2044,6 @@ impl Coordinator { let duration = Duration::from_millis(job.duration_ms.unwrap_or(0)); - // Extract execution time from Phase 1 results to split phase1 into execution + contributions - let phase1_execution_time = job - .results - .get(&JobPhase::Contributions) - .and_then(|results| results.values().next()) - .and_then(|job_result| match &job_result.data { - JobResultData::Challenges(contributions_result) => { - Some(contributions_result.execution_info.execution_time) - } - _ => None, - }) - .unwrap_or(0.0); - - let phase1_contributions_time = phase1_duration.as_seconds_f32() - phase1_execution_time; - let header = format!("[Job] Finished {} successfully ✔", job_id).green(); let duration_str = format!("Duration: {:.3}s", duration.as_secs_f32()).bold(); let steps_str = if let Some(executed_steps) = job.executed_steps { @@ -2011,11 +2052,10 @@ impl Coordinator { "Steps: N/A".to_string().red().bold() }; info!( - "{} {} ({:.3}s+{:.3}s+{:.3}s+{:.3}s) {} Inputs: {:?}, Capacity: {} ", + "{} {} ({:.3}s+{:.3}s+{:.3}s) {} Inputs: {:?}, Capacity: {} ", header, duration_str, - phase1_execution_time, - phase1_contributions_time, + phase1_duration.as_seconds_f32(), phase2_duration.as_seconds_f32(), phase3_duration.as_seconds_f32(), steps_str, @@ -2023,11 +2063,188 @@ impl Coordinator { job.compute_capacity, ); + let workers = job.workers.clone(); + + if workers.len() > 1 { + for phase in [JobPhase::Contributions, JobPhase::Prove] { + if let Some(results) = job.results.get(&phase) { + if let Some(start_time) = job.start_times.get(&phase) { + let mut durations_ms: Vec<(WorkerId, i64)> = results + .iter() + .map(|(worker_id, result)| { + let duration = result.end_time.signed_duration_since(start_time); + (worker_id.clone(), duration.num_milliseconds()) + }) + .collect(); + + if durations_ms.len() > 1 { + durations_ms.sort_by_key(|(_, duration)| *duration); + + let (best_worker, best_duration) = &durations_ms[0]; + let (worst_worker, worst_duration) = durations_ms.last().unwrap(); + + let avg_duration = durations_ms.iter().map(|(_, d)| d).sum::() + as f64 + / durations_ms.len() as f64; + + let diff_percentage = if *best_duration > 0 { + ((*worst_duration - *best_duration) as f64 / *best_duration as f64) + * 100.0 + } else { + 0.0 + }; + + info!( + "[Job] {:?} Performance - Avg: {:.3}s, Best: {} ({:.3}s), Worst: {} ({:.3}s), Diff: {:.1}%", + phase, + avg_duration / 1000.0, + best_worker, + *best_duration as f64 / 1000.0, + worst_worker, + *worst_duration as f64 / 1000.0, + diff_percentage + ); + } + + // For Phase 1, also show delay, witness, and ASM execution statistics + if phase == JobPhase::Contributions && durations_ms.len() > 1 { + // Extract delay times (coordinator send to worker start) + let mut delays_ms: Vec<(WorkerId, i64)> = results + .iter() + .filter_map(|(worker_id, result)| { + if let JobResultData::Challenges(contrib) = &result.data { + contrib.task_received_time.map(|task_received| { + let delay = + task_received.signed_duration_since(start_time); + (worker_id.clone(), delay.num_milliseconds().max(0)) + }) + } else { + None + } + }) + .collect(); + + if !delays_ms.is_empty() { + delays_ms.sort_by_key(|(_, delay)| *delay); + let (best_delay_worker, best_delay) = &delays_ms[0]; + let (worst_delay_worker, worst_delay) = delays_ms.last().unwrap(); + let avg_delay = delays_ms.iter().map(|(_, d)| d).sum::() + as f64 + / delays_ms.len() as f64; + + let delay_diff_percentage = if *best_delay > 0 { + ((*worst_delay - *best_delay) as f64 / *best_delay as f64) + * 100.0 + } else { + 0.0 + }; + + info!( + "[Job] Contributions Delay - Avg: {:.3}s, Best: {} ({:.3}s), Worst: {} ({:.3}s), Diff: {:.1}%", + avg_delay / 1000.0, + best_delay_worker, + *best_delay as f64 / 1000.0, + worst_delay_worker, + *worst_delay as f64 / 1000.0, + delay_diff_percentage + ); + } + + // Extract witness times + let mut witness_times: Vec<(WorkerId, f32)> = results + .iter() + .filter_map(|(worker_id, result)| { + if let JobResultData::Challenges(contrib) = &result.data { + Some((worker_id.clone(), contrib.witness_info.witness_time)) + } else { + None + } + }) + .collect(); + + if !witness_times.is_empty() { + witness_times.sort_by(|(_, a), (_, b)| { + a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) + }); + let (best_witness_worker, best_witness) = &witness_times[0]; + let (worst_witness_worker, worst_witness) = + witness_times.last().unwrap(); + let avg_witness = + witness_times.iter().map(|(_, t)| *t as f64).sum::() + / witness_times.len() as f64; + + let witness_diff_percentage = if *best_witness > 0.0 { + ((*worst_witness - *best_witness) as f64 / *best_witness as f64) + * 100.0 + } else { + 0.0 + }; + + info!( + "[Job] Contributions Witness - Avg: {:.3}s, Best: {} ({:.3}s), Worst: {} ({:.3}s), Diff: {:.1}%", + avg_witness / 1000.0, + best_witness_worker, + *best_witness as f64 / 1000.0, + worst_witness_worker, + *worst_witness as f64 / 1000.0, + witness_diff_percentage + ); + } + + // Extract ASM execution times + let mut asm_times: Vec<(WorkerId, f32, f32)> = results + .iter() + .filter_map(|(worker_id, result)| { + if let JobResultData::Challenges(contrib) = &result.data { + contrib + .zisk_executor_time + .asm_execution_duration + .as_ref() + .map(|asm| (worker_id.clone(), asm.time, asm.mhz)) + } else { + None + } + }) + .collect(); + + if !asm_times.is_empty() { + asm_times.sort_by(|(_, a, _), (_, b, _)| { + a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal) + }); + let (best_asm_worker, best_asm, best_mhz) = &asm_times[0]; + let (worst_asm_worker, worst_asm, worst_mhz) = + asm_times.last().unwrap(); + let avg_asm = + asm_times.iter().map(|(_, t, _)| *t as f64).sum::() + / asm_times.len() as f64; + + let asm_diff_percentage = if *best_asm > 0.0 { + ((*worst_asm - *best_asm) as f64 / *best_asm as f64) * 100.0 + } else { + 0.0 + }; + + info!( + "[Job] Contributions ASM - Avg: {:.3}s, Best: {} ({:.3}s @ {:.1}MHz), Worst: {} ({:.3}s @ {:.1}MHz), Diff: {:.1}%", + avg_asm, + best_asm_worker, + *best_asm, + *best_mhz, + worst_asm_worker, + *worst_asm, + *worst_mhz, + asm_diff_percentage + ); + } + } + } + } + } + } + // Print summary of the job let job_phases = vec![JobPhase::Contributions, JobPhase::Prove, JobPhase::Aggregate]; - let workers = job.workers.clone(); - info!("[Job] Summary for {}", job_id); for phase in job_phases { if let Some(result) = job.results.get(&phase) { diff --git a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto index efa788dc1..3d1652a57 100644 --- a/distributed/crates/grpc-api/proto/zisk_distributed_api.proto +++ b/distributed/crates/grpc-api/proto/zisk_distributed_api.proto @@ -296,16 +296,31 @@ message ExecuteTaskResponse { } } -message ExecuteInfo { - float execution_time = 1; +message WitnessExecInfo { + float witness_time = 1; repeated uint64 publics = 2; repeated uint64 proof_values = 3; string summary_info = 4; } +message AsmExecuteInfo { + float time = 1; + float mhz = 2; +} + +message ZiskExecuteTime { + float total_duration = 1; + float execution_duration = 2; + float count_and_plan_duration = 3; + float count_and_plan_mo_duration = 4; + optional AsmExecuteInfo asm_execution_duration = 5; + double task_received_time = 6; // Time when task was received by worker (in milliseconds since UNIX epoch) +} + message ChallengesList { repeated Challenges challenges = 1; - ExecuteInfo execution_info = 2; + WitnessExecInfo witness_info = 2; + ZiskExecuteTime zisk_execution_time = 3; } message Challenges { diff --git a/distributed/crates/grpc-api/src/conversions.rs b/distributed/crates/grpc-api/src/conversions.rs index e484909a6..df2ac368c 100644 --- a/distributed/crates/grpc-api/src/conversions.rs +++ b/distributed/crates/grpc-api/src/conversions.rs @@ -419,16 +419,29 @@ impl From for ExecuteTaskResponseDto { challenge: c.challenge, }) .collect(); - let exec_info = challenges_list.execution_info.unwrap(); - let execution_info = ExecutionInfoDto { - execution_time: exec_info.execution_time, - publics: exec_info.publics, - proof_values: exec_info.proof_values, - summary_info: exec_info.summary_info, + let witness_info = challenges_list.witness_info.unwrap(); + let witness_info = WitnessInfoDto { + witness_time: witness_info.witness_time, + publics: witness_info.publics, + proof_values: witness_info.proof_values, + summary_info: witness_info.summary_info, }; + let exec_time = challenges_list.zisk_execution_time.unwrap(); + let zisk_executor_time = ZiskExecutorTimeDto { + task_received_time: exec_time.task_received_time, + total_duration: exec_time.total_duration, + execution_duration: exec_time.execution_duration, + count_and_plan_duration: exec_time.count_and_plan_duration, + count_and_plan_mo_duration: exec_time.count_and_plan_mo_duration, + asm_execution_duration: exec_time.asm_execution_duration.map(|asm_info| { + AsmExecutionInfoDto { time: asm_info.time, mhz: asm_info.mhz } + }), + }; + Some(ExecuteTaskResponseResultDataDto::Challenges(ContributionsResultDataDto { - execution_info, + witness_info, challenges, + zisk_executor_time, })) } Some(execute_task_response::ResultData::Proofs(proof_list)) => { diff --git a/distributed/crates/worker/Cargo.toml b/distributed/crates/worker/Cargo.toml index 316c906e2..7afcf4274 100644 --- a/distributed/crates/worker/Cargo.toml +++ b/distributed/crates/worker/Cargo.toml @@ -28,6 +28,7 @@ witness = { workspace = true } rom-setup = { workspace = true } zisk-pil = { workspace = true } zisk-sdk = { workspace = true } +chrono = { version = "0.4", features = ["serde"] } precompiles-hints = { workspace = true } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 0fcfd5d68..12eeb6484 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -10,6 +10,7 @@ use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; use zisk_common::io::{StreamProcessor, StreamSource, ZiskStdin}; use zisk_common::ElfBinaryFromFile; +use zisk_common::ZiskExecutorTime; use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; use zisk_distributed_common::{ComputeCapacity, JobId, WorkerId}; use zisk_distributed_common::{HintsSourceDto, StreamDataDto, StreamMessageKind}; @@ -17,8 +18,8 @@ use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProgramPK, ZiskProver}; use crate::stream_ordering::StreamOrderingActor; -use proofman::ExecutionInfo; use proofman::ProvePhaseInputs; +use proofman::WitnessInfo; use proofman_common::ParamsGPU; use proofman_common::ProofOptions; use proofman_common::{json_to_debug_instances_map, DebugInfo}; @@ -27,13 +28,31 @@ use tracing::{error, info}; use crate::config::ProverServiceConfigDto; +/// Message structures for MPI broadcast to ensure type safety +#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] +struct ContributionsMessage { + job_id: JobId, + phase_inputs: ProvePhaseInputs, + options: ProofOptions, + input_source: InputSourceDto, + hints_source: HintsSourceDto, +} + +#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] +struct ProveMessage { + job_id: JobId, + phase_inputs: ProvePhaseInputs, + options: ProofOptions, +} + /// Result from computation tasks #[derive(Debug)] pub enum ComputationResult { Challenge { job_id: JobId, success: bool, - result: Result<(ExecutionInfo, Vec)>, + result: Result<(WitnessInfo, ZiskExecutorTime, Vec)>, + task_received_time: Option>, }, Proofs { job_id: JobId, @@ -232,6 +251,7 @@ pub struct JobContext { pub total_compute_units: u32, // Total compute units for the whole job pub phase: JobPhase, pub executed_steps: u64, + pub task_received_time: Option>, } pub struct Worker { @@ -375,6 +395,8 @@ impl Worker { } } + #[allow(clippy::type_complexity)] + #[allow(clippy::too_many_arguments)] pub fn new_job( &mut self, job_id: JobId, @@ -383,6 +405,7 @@ impl Worker { total_workers: u32, allocation: Vec, total_compute_units: u32, + task_received_time: Option>, ) -> Arc> { let current_job = Arc::new(Mutex::new(JobContext { job_id: job_id.clone(), @@ -393,6 +416,7 @@ impl Worker { total_compute_units, phase: JobPhase::Contributions, executed_steps: 0, + task_received_time, })); self.current_job = Some(current_job.clone()); @@ -418,14 +442,15 @@ impl Worker { let options = self.get_proof_options(false); - borsh::to_vec(&( - JobPhase::Contributions, - job.job_id.clone(), + let message = ContributionsMessage { + job_id: job.job_id.clone(), phase_inputs, options, - job.data_ctx.input_source.clone(), - )) - .unwrap() + input_source: job.data_ctx.input_source.clone(), + hints_source: job.data_ctx.hints_source.clone(), + }; + + borsh::to_vec(&(JobPhase::Contributions, message)).unwrap() }; self.prover.mpi_broadcast(&mut serialized)?; @@ -449,13 +474,14 @@ impl Worker { ) -> Result<()> { let mut serialized = { let job = job.lock().await; - let job_id = job.job_id.clone(); let phase_inputs = proofman::ProvePhaseInputs::Internal(challenges); let options = self.get_proof_options(false); - borsh::to_vec(&(JobPhase::Prove, job_id, phase_inputs, options)).unwrap() + let message = ProveMessage { job_id: job.job_id.clone(), phase_inputs, options }; + + borsh::to_vec(&(JobPhase::Prove, message)).unwrap() }; self.prover.mpi_broadcast(&mut serialized)?; @@ -503,17 +529,20 @@ impl Worker { let mut guard = job.blocking_lock(); guard.executed_steps = prover.executed_steps(); + let task_received_time = guard.task_received_time; drop(guard); - let execution_info = - prover.get_execution_info().unwrap_or_else(|_| ExecutionInfo::default()); + let (witness_info, zisk_execution_time) = prover + .get_execution_info() + .unwrap_or_else(|_| (WitnessInfo::default(), ZiskExecutorTime::default())); match result { Ok(data) => { let _ = tx.send(ComputationResult::Challenge { job_id, success: true, - result: Ok((execution_info, data)), + result: Ok((witness_info, zisk_execution_time, data)), + task_received_time, }); } Err(error) => { @@ -522,6 +551,7 @@ impl Worker { job_id, success: false, result: Err(error), + task_received_time, }); } } @@ -838,32 +868,30 @@ impl Worker { match phase { JobPhase::Contributions => { - let (job_id, phase_inputs, options, input_source_dto, hints_source_dto): ( - JobId, - ProvePhaseInputs, - ProofOptions, - InputSourceDto, - HintsSourceDto, - ) = borsh::from_slice(&bytes[1..]).unwrap(); + let message: ContributionsMessage = borsh::from_slice(&bytes[1..]).unwrap(); let result = Self::execute_contribution_task( - job_id, + message.job_id, &self.prover, - phase_inputs, - input_source_dto, - hints_source_dto, + message.phase_inputs, + message.input_source, + message.hints_source, &self.pk, - options, + message.options, ); if let Err(e) = result { error!("Error during Contributions MPI broadcast execution: {}. Waiting for new job...", e); } } JobPhase::Prove => { - let (job_id, phase_inputs, options): (JobId, ProvePhaseInputs, ProofOptions) = - borsh::from_slice(&bytes[1..]).unwrap(); + let message: ProveMessage = borsh::from_slice(&bytes[1..]).unwrap(); - let result = Self::execute_prove_task(job_id, &self.prover, phase_inputs, options); + let result = Self::execute_prove_task( + message.job_id, + &self.prover, + message.phase_inputs, + message.options, + ); if let Err(e) = result { error!( "Error during Prove MPI broadcast execution: {}. Waiting for new job...", diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 271ada78d..6dc2aee5d 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -1,6 +1,6 @@ use crate::{worker::ComputationResult, ProverConfig, Worker}; use anyhow::{anyhow, Result}; -use proofman::{AggProofs, ContributionsInfo, ExecutionInfo}; +use proofman::{AggProofs, ContributionsInfo, WitnessInfo}; use std::path::Path; use std::{path::PathBuf, time::Duration}; use tokio::sync::mpsc; @@ -8,6 +8,7 @@ use tokio_stream::StreamExt; use tonic::transport::Channel; use tonic::Request; use tracing::{error, info}; +use zisk_common::ZiskExecutorTime; use zisk_distributed_common::{ AggProofData, AggregationParams, DataCtx, HintsSourceDto, InputSourceDto, StreamDataDto, WorkerState, @@ -238,8 +239,15 @@ impl WorkerNodeGrpc { message_sender: &mpsc::UnboundedSender, ) -> Result<()> { match result { - ComputationResult::Challenge { job_id, success, result } => { - self.send_partial_contribution(job_id, success, result, message_sender).await + ComputationResult::Challenge { job_id, success, result, task_received_time } => { + self.send_partial_contribution( + job_id, + success, + result, + message_sender, + task_received_time, + ) + .await } ComputationResult::Proofs { job_id, success, result } => { self.send_proof(job_id, success, result, message_sender).await @@ -254,8 +262,9 @@ impl WorkerNodeGrpc { &mut self, job_id: JobId, success: bool, - result: Result<(ExecutionInfo, Vec)>, + result: Result<(WitnessInfo, ZiskExecutorTime, Vec)>, message_sender: &mpsc::UnboundedSender, + task_received_time: Option>, ) -> Result<()> { if let Some(handle) = self.worker.take_current_computation() { handle.await?; @@ -276,12 +285,12 @@ impl WorkerNodeGrpc { "Inconsistent state: operation reported success but returned Err result" )); } - ((ExecutionInfo::default(), vec![]), e.to_string()) + ((WitnessInfo::default(), ZiskExecutorTime::default(), vec![]), e.to_string()) } }; let challenges: Vec = result_data - .1 + .2 .into_iter() .map(|cont| Challenges { worker_index: cont.worker_index, @@ -290,13 +299,25 @@ impl WorkerNodeGrpc { }) .collect(); - let execution_info: ExecuteInfo = ExecuteInfo { - execution_time: result_data.0.execution_time, + let witness_info = WitnessExecInfo { + witness_time: result_data.0.witness_time, publics: result_data.0.publics, proof_values: result_data.0.proof_values, summary_info: result_data.0.summary_info, }; + let zisk_execution_time = ZiskExecuteTime { + total_duration: result_data.1.total_duration.as_millis() as f32, + execution_duration: result_data.1.execution_duration.as_millis() as f32, + count_and_plan_duration: result_data.1.count_and_plan_duration.as_millis() as f32, + count_and_plan_mo_duration: result_data.1.count_and_plan_mo_duration.as_millis() as f32, + asm_execution_duration: result_data + .1 + .asm_execution_duration + .map(|asm_info| AsmExecuteInfo { time: asm_info.time, mhz: asm_info.mhz }), + task_received_time: task_received_time.unwrap().timestamp_millis() as f64, + }; + let message = WorkerMessage { payload: Some(worker_message::Payload::ExecuteTaskResponse(ExecuteTaskResponse { worker_id: self.worker_config.worker.worker_id.as_string(), @@ -305,7 +326,8 @@ impl WorkerNodeGrpc { success, result_data: Some(ResultData::Challenges(ChallengesList { challenges, - execution_info: Some(execution_info), + witness_info: Some(witness_info), + zisk_execution_time: Some(zisk_execution_time), })), error_message, })), @@ -519,6 +541,7 @@ impl WorkerNodeGrpc { request: ExecuteTaskRequest, computation_tx: &mpsc::UnboundedSender, ) -> Result<()> { + let task_received_time = chrono::Utc::now(); info!("Starting Partial Contribution for {}", request.job_id); // Cancel any existing computation @@ -573,12 +596,13 @@ impl WorkerNodeGrpc { DataCtx { data_id: DataId::from(params.data_id), input_source, hints_source }; let job = self.worker.new_job( - job_id, + job_id.clone(), data_ctx, params.rank_id, params.total_workers, params.worker_allocation, params.job_compute_units, + Some(task_received_time), ); // Start computation in background task diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 0aff7e3c3..445b3b683 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -1,5 +1,5 @@ use named_sem::NamedSemaphore; -use zisk_common::{stats_begin, stats_end, stats_mark}; +use zisk_common::{stats_begin, stats_end, stats_mark, AsmExecutionInfo}; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] use zisk_common::{ChunkId, EmuTrace, ExecutorStatsHandle}; @@ -64,7 +64,7 @@ impl AsmRunnerMT { local_rank: i32, base_port: Option, _stats: ExecutorStatsHandle, - ) -> Result>> { + ) -> Result<(Vec>, AsmExecutionInfo)> { stats_begin!(_stats, 0, _runner_scope, "ASM_MT_RUNNER", 0); let port = AsmServices::port_base_for(base_port, local_rank); @@ -190,6 +190,7 @@ impl AsmRunnerMT { let total_steps = emu_traces.iter().map(|x| x.steps).sum::(); let mhz = (total_steps as f64 / elapsed.as_secs_f64()) / 1_000_000.0; + let asm_execution_info = AsmExecutionInfo { time: elapsed.as_secs_f32(), mhz: mhz as f32 }; info!("··· Assembly execution speed: {}MHz ({:2?})", mhz.round(), elapsed); let response = handle.map_err(AsmRunError::ServiceError)?; @@ -199,6 +200,6 @@ impl AsmRunnerMT { assert!(response.trace_len <= response.allocated_len); stats_end!(_stats, &_runner_scope); - Ok(emu_traces) + Ok((emu_traces, asm_execution_info)) } } diff --git a/examples/Cargo.lock b/examples/Cargo.lock index c2d74d7a6..3d5552be5 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -334,9 +334,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" dependencies = [ "aws-lc-sys", "zeroize", @@ -344,9 +344,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" dependencies = [ "cc", "cmake", @@ -814,7 +814,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "fields", "num-bigint", @@ -1124,7 +1124,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "cfg-if", "num-bigint", @@ -1219,20 +1219,20 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] @@ -1516,9 +1516,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.90" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -1979,7 +1979,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "colored", "fields", @@ -1998,9 +1998,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" @@ -2281,11 +2281,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit 0.25.4+spec-1.1.0", ] [[package]] @@ -2300,7 +2300,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "bincode", "blake3", @@ -2336,7 +2336,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "bincode", "borsh", @@ -2367,7 +2367,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "fields", "itoa", @@ -2380,7 +2380,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "proc-macro2", "quote", @@ -2390,7 +2390,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "crossbeam-channel", "tracing", @@ -2399,7 +2399,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "bincode", "bytemuck", @@ -2411,7 +2411,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "bytemuck", "fields", @@ -2479,9 +2479,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -2492,6 +2492,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -3239,7 +3245,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -3363,9 +3369,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -3380,9 +3386,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -3425,9 +3431,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.0.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" dependencies = [ "serde_core", ] @@ -3448,12 +3454,12 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", "winnow", ] @@ -3633,7 +3639,7 @@ version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -3731,9 +3737,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -3744,9 +3750,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3754,9 +3760,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -3767,9 +3773,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -4379,7 +4385,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#efda6719b1e961aafd72e136b098274bb47dfb38" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" dependencies = [ "colored", "fields", @@ -4454,18 +4460,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", @@ -4622,6 +4628,7 @@ dependencies = [ "tracing-appender", "tracing-subscriber", "uuid", + "zisk-common", ] [[package]] diff --git a/examples/multiple-programs/host/src/main.rs b/examples/multiple-programs/host/src/main.rs index 98b5093c8..fdbb24f68 100644 --- a/examples/multiple-programs/host/src/main.rs +++ b/examples/multiple-programs/host/src/main.rs @@ -27,7 +27,7 @@ fn main() -> Result<()> { println!( "Program executed successfully: {} cycles in {:.2?}", - result.execution.steps, result.duration + result.get_execution_steps(), result.get_duration() ); println!("Generating proof for first program..."); @@ -49,7 +49,7 @@ fn main() -> Result<()> { println!( "Program executed successfully: {} cycles in {:.2?}", - result2.execution.steps, result2.duration + result2.get_execution_steps(), result2.get_duration() ); println!("Generating proof for second program..."); diff --git a/examples/sha-hasher/host/src/main.rs b/examples/sha-hasher/host/src/main.rs index 4077855ad..7a370ad63 100644 --- a/examples/sha-hasher/host/src/main.rs +++ b/examples/sha-hasher/host/src/main.rs @@ -21,7 +21,7 @@ fn main() -> Result<()> { println!( "ZisK has executed program with {} cycles in {:?}", - result.execution.steps, result.duration + result.executor_summary.steps, result.total_duration ); let proof_opts = ProofOpts::default().minimal_memory(); diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index 63073eedf..eac7b4f28 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -17,7 +17,8 @@ use fields::PrimeField64; use proofman_common::ProofCtx; use sm_rom::RomSM; use zisk_common::{ - io::ZiskStdin, stats_begin, stats_end, ChunkId, EmuTrace, ExecutorStatsHandle, StatsScope, + io::ZiskStdin, stats_begin, stats_end, AsmExecutionInfo, ChunkId, EmuTrace, + ExecutorStatsHandle, StatsScope, }; use zisk_core::{ZiskRom, MAX_INPUT_SIZE}; use ziskemu::ZiskEmulator; @@ -41,6 +42,8 @@ pub struct EmulatorAsm { /// Assembly resources including shared memory and hints stream. asm_resources: Mutex>, + + asm_execution_info: Mutex>, } impl EmulatorAsm { @@ -60,6 +63,7 @@ impl EmulatorAsm { chunk_size, rom_sm, asm_resources: Mutex::new(None), + asm_execution_info: Mutex::new(None), } } @@ -67,6 +71,10 @@ impl EmulatorAsm { self.chunk_size } + pub fn get_asm_execution_info(&self) -> Option { + self.asm_execution_info.lock().unwrap().clone() + } + pub fn set_asm_resources(&self, asm_resources: AsmResources) { *self.asm_resources.lock().unwrap() = Some(asm_resources); } @@ -75,6 +83,10 @@ impl EmulatorAsm { self.asm_resources.lock().unwrap().as_ref().unwrap().reset(); } + pub fn set_rh_data(&self, rh_data: AsmRunnerRH) { + self.rom_sm.as_ref().unwrap().set_rh_data(rh_data); + } + /// Computes minimal traces by processing the ZisK ROM with given public inputs. /// /// # Arguments @@ -107,6 +119,7 @@ impl EmulatorAsm { DeviceMetricsList, NestedDeviceMetricsList, Option>, + Option>, u64, ) { let asm_resources_guard = self.asm_resources.lock().unwrap(); @@ -193,23 +206,12 @@ impl EmulatorAsm { drop(asm_resources_guard); let (min_traces, main_count, secn_count) = self.run_mt_assembly(zisk_rom, sm_bundle, stats); - // Store execute steps let steps = min_traces.iter().map(|trace| trace.steps).sum::(); - // If the world rank is 0, wait for the ROM Histogram thread to finish and collect the result - if has_rom_sm { - let rh_data = handle_rh - .expect("ROM Histogram thread was not spawned") - .join() - .expect("Error during ROM Histogram thread execution"); - - self.rom_sm.as_ref().unwrap().set_rh_data(rh_data); - } - stats_end!(stats, &_exec_scope); - (min_traces, main_count, secn_count, Some(handle_mo), steps) + (min_traces, main_count, secn_count, Some(handle_mo), handle_rh, steps) } fn run_mt_assembly( @@ -226,7 +228,7 @@ impl EmulatorAsm { #[allow(unused_variables)] let mt_scope_id = _mt_scope.id(); - let emu_traces = rayon::in_place_scope(|scope| { + let (emu_traces, asm_execution_info) = rayon::in_place_scope(|scope| { let on_chunk = |idx: usize, emu_trace: std::sync::Arc| { let chunk_id = ChunkId(idx); let results_ref = &results_mu; @@ -267,6 +269,8 @@ impl EmulatorAsm { result }); + self.asm_execution_info.lock().unwrap().replace(asm_execution_info); + // Unwrap the Arc pointers now that all rayon tasks have completed let emu_traces = emu_traces .into_iter() @@ -318,6 +322,7 @@ impl crate::Emulator for EmulatorAsm { DeviceMetricsList, NestedDeviceMetricsList, Option>, + Option>, u64, ) { self.execute(zisk_rom, stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope) diff --git a/executor/src/emu_asm_stub.rs b/executor/src/emu_asm_stub.rs index bb8fb52e3..f7daf784d 100644 --- a/executor/src/emu_asm_stub.rs +++ b/executor/src/emu_asm_stub.rs @@ -4,13 +4,13 @@ use std::{ }; use crate::{DeviceMetricsList, NestedDeviceMetricsList, StaticSMBundle}; -use asm_runner::AsmRunnerMO; +use asm_runner::{AsmRunnerMO, AsmRunnerRH}; use crate::AsmResources; use fields::PrimeField64; use proofman_common::ProofCtx; use sm_rom::RomSM; -use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; +use zisk_common::{io::ZiskStdin, AsmExecutionInfo, EmuTrace, ExecutorStatsHandle, StatsScope}; use zisk_core::ZiskRom; pub struct EmulatorAsm {} @@ -44,6 +44,7 @@ impl EmulatorAsm { DeviceMetricsList, NestedDeviceMetricsList, Option>, + Option>, u64, ) { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); @@ -53,6 +54,10 @@ impl EmulatorAsm { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); } + pub fn set_rh_data(&self, rh_data: AsmRunnerRH) { + unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); + } + pub fn get_chunk_size(&self) -> u64 { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); } @@ -60,4 +65,8 @@ impl EmulatorAsm { pub fn reset_hints_stream(&self) { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); } + + pub fn get_asm_execution_info(&self) -> Option { + unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); + } } diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index e2bb22081..ee99bc7ce 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; -use asm_runner::AsmRunnerMO; +use asm_runner::{AsmRunnerMO, AsmRunnerRH}; use data_bus::DataBusTrait; use fields::PrimeField64; use proofman_common::ProofCtx; @@ -50,6 +50,7 @@ impl EmulatorRust { /// * `NestedDeviceMetricsList` - Metrics for secondary/nested devices. /// * `None`. /// * `u64` - Total number of steps. + #[allow(clippy::type_complexity)] pub fn execute( &self, zisk_rom: &ZiskRom, @@ -60,6 +61,7 @@ impl EmulatorRust { DeviceMetricsList, NestedDeviceMetricsList, Option>, + Option>, u64, ) { let min_traces = self.run_emulator(zisk_rom, Self::NUM_THREADS, &stdin.lock().unwrap()); @@ -71,7 +73,7 @@ impl EmulatorRust { let (main_count, secn_count) = self.count(zisk_rom, &min_traces, sm_bundle); timer_stop_and_log_info!(COUNT); - (min_traces, main_count, secn_count, None, steps) + (min_traces, main_count, secn_count, None, None, steps) } fn run_emulator( @@ -175,6 +177,7 @@ impl crate::Emulator for EmulatorRust { DeviceMetricsList, NestedDeviceMetricsList, Option>, + Option>, u64, ) { self.execute(zisk_rom, stdin, sm_bundle) diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 74aead2c5..388ce340b 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -183,7 +183,7 @@ impl WitnessComponent for ZiskExecutor { let count_and_plan_duration = start_partial.elapsed(); timer_stop_and_log_info!(PLAN); - timer_start_info!(PLAN_MEM_CPP); + timer_start_info!(WAIT_PLAN_MEM_CPP); stats_end!(self.state.stats, &_secn_plan_scope); let start_partial = Instant::now(); @@ -206,7 +206,15 @@ impl WitnessComponent for ZiskExecutor { } let count_and_plan_mo_duration = start_partial.elapsed(); - timer_stop_and_log_info!(PLAN_MEM_CPP); + timer_stop_and_log_info!(WAIT_PLAN_MEM_CPP); + + if let Some(handle_rh) = output.handle_rh { + timer_start_info!(WAIT_ASM_RH); + let rh_data = handle_rh.join().expect("Error during ROM Histogram thread execution"); + + self.rom_executor.set_rh_data(rh_data); + timer_stop_and_log_info!(WAIT_ASM_RH); + } // Phase 4: Configure and assign secondary instances stats_begin!(self.state.stats, &_exec_scope, _config_scope, "CONFIGURE_INSTANCES", 0); @@ -268,6 +276,7 @@ impl WitnessComponent for ZiskExecutor { count_and_plan_duration, count_and_plan_mo_duration, total_duration: start_total.elapsed(), + asm_execution_duration: self.rom_executor.get_asm_execution_info(), }; // Store the execution result let execution_result = diff --git a/executor/src/lib.rs b/executor/src/lib.rs index aa47d4478..47cbbfe73 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -45,14 +45,15 @@ use zisk_core::ZiskRom; pub type DeviceMetricsList = Vec; pub type NestedDeviceMetricsList = HashMap; -use asm_runner::AsmRunnerMO; +use asm_runner::{AsmRunnerMO, AsmRunnerRH}; use fields::PrimeField64; use proofman_common::ProofCtx; use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; -use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; +use zisk_common::{io::ZiskStdin, AsmExecutionInfo, EmuTrace, ExecutorStatsHandle, StatsScope}; /// Trait for unified execution across different emulator backends #[allow(clippy::too_many_arguments)] +#[allow(clippy::type_complexity)] pub trait Emulator: Send + Sync { /// Execute the emulator fn execute( @@ -69,6 +70,7 @@ pub trait Emulator: Send + Sync { DeviceMetricsList, NestedDeviceMetricsList, Option>, + Option>, u64, ); } @@ -99,12 +101,25 @@ impl EmulatorKind { }; } + pub fn get_asm_execution_info(&self) -> Option { + match self { + Self::Asm(e) => e.get_asm_execution_info(), + Self::Rust(_) => None, // No ASM execution info in Rust emulator + } + } pub fn reset_hints_stream(&self) { match self { Self::Asm(e) => e.reset_hints_stream(), Self::Rust(_) => (), // No hints stream in Rust emulator } } + + pub fn set_rh_data(&self, rh_data: AsmRunnerRH) { + match self { + Self::Asm(e) => e.set_rh_data(rh_data), + Self::Rust(_) => (), + } + } } impl Emulator for EmulatorKind { @@ -122,6 +137,7 @@ impl Emulator for EmulatorKind { DeviceMetricsList, NestedDeviceMetricsList, Option>, + Option>, u64, ) { match self { diff --git a/executor/src/rom_executor.rs b/executor/src/rom_executor.rs index bcb5482f2..aa1a7b879 100644 --- a/executor/src/rom_executor.rs +++ b/executor/src/rom_executor.rs @@ -7,11 +7,11 @@ use crate::{ AsmResources, DeviceMetricsList, Emulator, EmulatorKind, NestedDeviceMetricsList, StaticSMBundle, }; -use asm_runner::AsmRunnerMO; +use asm_runner::{AsmRunnerMO, AsmRunnerRH}; use fields::PrimeField64; use proofman_common::ProofCtx; use std::{sync::Mutex, thread::JoinHandle}; -use zisk_common::{io::ZiskStdin, EmuTrace, ExecutorStatsHandle, StatsScope}; +use zisk_common::{io::ZiskStdin, AsmExecutionInfo, EmuTrace, ExecutorStatsHandle, StatsScope}; use zisk_core::ZiskRom; /// Output from ROM execution. @@ -24,6 +24,8 @@ pub struct RomExecutionOutput { pub secn_count: NestedDeviceMetricsList, /// Handle to memory operations thread (for ASM emulator). pub handle_mo: Option>, + /// Handle to hints runner thread (for ASM emulator). + pub handle_rh: Option>, /// Execution result with step counts. pub steps: u64, } @@ -60,6 +62,14 @@ impl RomExecutor { self.emulator.reset_hints_stream() } + pub fn get_asm_execution_info(&self) -> Option { + self.emulator.get_asm_execution_info() + } + + pub fn set_rh_data(&self, rh_data: AsmRunnerRH) { + self.emulator.set_rh_data(rh_data); + } + /// Executes the ROM program and collects minimal traces. /// /// # Arguments @@ -81,16 +91,10 @@ impl RomExecutor { stats: &ExecutorStatsHandle, caller_stats_scope: &StatsScope, ) -> RomExecutionOutput { - let (min_traces, main_count, secn_count, handle_mo, steps) = self.emulator.execute( - zisk_rom, - &self.stdin, - pctx, - sm_bundle, - use_hints, - stats, - caller_stats_scope, - ); + let (min_traces, main_count, secn_count, handle_mo, handle_rh, steps) = self + .emulator + .execute(zisk_rom, &self.stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope); - RomExecutionOutput { min_traces, main_count, secn_count, handle_mo, steps } + RomExecutionOutput { min_traces, main_count, secn_count, handle_mo, handle_rh, steps } } } diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 4ebd1facd..54cfed502 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -10,8 +10,7 @@ use crate::{ProofMode, ProofOpts}; use asm_runner::{AsmRunnerOptions, AsmServices}; use executor::{get_packed_info, init_executor_asm, AsmResources}; use proofman::{ - AggProofs, AggProofsRegister, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, - SnarkWrapper, + AggProofs, AggProofsRegister, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper, WitnessInfo, }; use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, RowInfo, VerboseMode}; use proofman_util::{timer_start_info, timer_stop_and_log_info}; @@ -22,6 +21,7 @@ use std::sync::Arc; use zisk_common::io::ZiskStdin; use zisk_common::ElfBinaryLike; use zisk_common::ExecutorStatsHandle; +use zisk_common::ZiskExecutorTime; use zisk_core::Riscv2zisk; use zisk_distributed_common::LoggingConfig; @@ -197,7 +197,7 @@ impl ProverEngine for AsmProver { )) } - fn get_execution_info(&self) -> Result { + fn get_execution_info(&self) -> Result<(WitnessInfo, ZiskExecutorTime)> { self.core_prover.backend.get_execution_info() } diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index c389ae768..e17175f63 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -15,8 +15,8 @@ use colored::Colorize; use executor::ZiskExecutor; use fields::Goldilocks; use proofman::{ - AggProofs, AggProofsRegister, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, - ProvePhaseResult, SnarkProtocol, SnarkWrapper, + AggProofs, AggProofsRegister, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult, + SnarkProtocol, SnarkWrapper, WitnessInfo, }; use proofman_common::{ProofCtx, ProofOptions, RowInfo}; use proofman_util::VadcopFinalProof; @@ -24,6 +24,7 @@ use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; use zisk_common::stats_mark; +use zisk_common::ZiskExecutorTime; use zisk_common::{io::ZiskStdin, ElfBinaryLike, ExecutorStatsHandle, ZiskExecutorSummary}; pub(crate) struct ProverBackend { @@ -549,12 +550,21 @@ impl ProverBackend { Ok(proofman.is_first_partition()) } - pub(crate) fn get_execution_info(&self) -> Result { + pub(crate) fn get_execution_info(&self) -> Result<(WitnessInfo, ZiskExecutorTime)> { let proofman = self .proofman .as_ref() .ok_or_else(|| anyhow::anyhow!("Cannot get execution info in verifier mode"))?; - Ok(proofman.get_execution_info()) + + let witness_info = proofman.get_witness_info(); + + let executor = self.executor.as_ref().ok_or_else(|| { + anyhow::anyhow!("Executor is not initialized. Please initialize it before use.") + })?; + + let (execution_result, _) = executor.get_execution_result(); + + Ok((witness_info, execution_result.executor_time)) } pub(crate) fn register_aggregated_proofs( diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index df2568c1d..a178d7025 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -8,8 +8,7 @@ use crate::{ use crate::{ensure_custom_commits, ProofMode, ProofOpts}; use executor::{get_packed_info, init_executor_emu}; use proofman::{ - AggProofs, AggProofsRegister, ExecutionInfo, ProofMan, ProvePhase, ProvePhaseInputs, - SnarkWrapper, + AggProofs, AggProofsRegister, ProofMan, ProvePhase, ProvePhaseInputs, SnarkWrapper, WitnessInfo, }; use proofman_common::{initialize_logger, ParamsGPU, ProofOptions, RankInfo, RowInfo}; use std::path::PathBuf; @@ -17,6 +16,7 @@ use std::sync::Arc; use zisk_common::io::ZiskStdin; use zisk_common::ElfBinaryLike; use zisk_common::ExecutorStatsHandle; +use zisk_common::ZiskExecutorTime; use zisk_core::Riscv2zisk; use zisk_distributed_common::LoggingConfig; @@ -115,7 +115,7 @@ impl ProverEngine for EmuProver { .unwrap_or(0) } - fn get_execution_info(&self) -> Result { + fn get_execution_info(&self) -> Result<(WitnessInfo, ZiskExecutorTime)> { self.core_prover.backend.get_execution_info() } diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index ec46e4a42..9e429d89d 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -5,8 +5,8 @@ pub use asm::*; use backend::*; pub use emu::*; use proofman::{ - AggProofs, AggProofsRegister, ExecutionInfo, ProvePhase, ProvePhaseInputs, ProvePhaseResult, - SnarkProtocol, + AggProofs, AggProofsRegister, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProtocol, + WitnessInfo, }; use proofman_common::{ProofOptions, RankInfo, RowInfo}; use proofman_util::VadcopFinalProof; @@ -27,6 +27,7 @@ use std::{ use tracing::info; use zisk_common::io::StreamSource; use zisk_common::ElfBinaryLike; +use zisk_common::ZiskExecutorTime; use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, StatsCostPerType, ZiskExecutorSummary}; use zisk_core::ZiskRom; @@ -147,6 +148,10 @@ impl ZiskProgramPK { } Ok(()) } + + pub fn is_asm(&self) -> bool { + self.asm_services.is_some() + } } impl Drop for ZiskProgramPK { @@ -742,7 +747,7 @@ pub trait ProverEngine { fn executed_steps(&self) -> u64; - fn get_execution_info(&self) -> Result; + fn get_execution_info(&self) -> Result<(WitnessInfo, ZiskExecutorTime)>; fn get_instance_trace( &self, @@ -890,7 +895,7 @@ impl ZiskProver { self.prover.executed_steps() } - pub fn get_execution_info(&self) -> Result { + pub fn get_execution_info(&self) -> Result<(WitnessInfo, ZiskExecutorTime)> { self.prover.get_execution_info() } From df97e929ad918c8d5fb318f9b062000bd2bcce73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Thu, 5 Mar 2026 12:30:07 +0100 Subject: [PATCH 688/782] Fix/mpi sevilla (#829) * Fix mpi sevilla * Cargo update * Clippy mac --- Cargo.lock | 22 +++++++++---------- cli/src/commands/prove.rs | 1 + examples/Cargo.lock | 22 +++++++++---------- executor/src/emu_asm_stub.rs | 2 +- sdk/src/prover/asm.rs | 41 ++++++++++++++++++++---------------- 5 files changed, 47 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f3534851b..4597d554b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1116,7 +1116,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "fields", "num-bigint", @@ -1470,7 +1470,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "cfg-if", "num-bigint", @@ -2764,7 +2764,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "colored", "fields", @@ -3146,7 +3146,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "bincode", "blake3", @@ -3182,7 +3182,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "bincode", "borsh", @@ -3213,7 +3213,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "fields", "itoa", @@ -3226,7 +3226,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "proc-macro2", "quote", @@ -3236,7 +3236,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "crossbeam-channel", "tracing", @@ -3245,7 +3245,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "bincode", "bytemuck", @@ -3257,7 +3257,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "bytemuck", "fields", @@ -5709,7 +5709,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "colored", "fields", diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 57d9e729d..9d3e5e560 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -198,6 +198,7 @@ impl ZiskProve { }; result.save_proof_with_publics(output_dir)?; info!("Proof ID: {}", proof_id); + info!("Proof Time: {:.3} seconds", result.duration.as_secs_f64()); } print_execution_summary( &result.executor_summary.executor_time, diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 3d5552be5..d10a8cb0f 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -814,7 +814,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "fields", "num-bigint", @@ -1124,7 +1124,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "cfg-if", "num-bigint", @@ -1979,7 +1979,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "colored", "fields", @@ -2300,7 +2300,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "bincode", "blake3", @@ -2336,7 +2336,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "bincode", "borsh", @@ -2367,7 +2367,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "fields", "itoa", @@ -2380,7 +2380,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "proc-macro2", "quote", @@ -2390,7 +2390,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "crossbeam-channel", "tracing", @@ -2399,7 +2399,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "bincode", "bytemuck", @@ -2411,7 +2411,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "bytemuck", "fields", @@ -4385,7 +4385,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#d388b994ed37eaa873b2521a589aecaa87b6436d" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" dependencies = [ "colored", "fields", diff --git a/executor/src/emu_asm_stub.rs b/executor/src/emu_asm_stub.rs index f7daf784d..f3039fc72 100644 --- a/executor/src/emu_asm_stub.rs +++ b/executor/src/emu_asm_stub.rs @@ -54,7 +54,7 @@ impl EmulatorAsm { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); } - pub fn set_rh_data(&self, rh_data: AsmRunnerRH) { + pub fn set_rh_data(&self, _rh_data: AsmRunnerRH) { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); } diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 54cfed502..10cfb571c 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -138,28 +138,33 @@ impl ProverEngine for AsmProver { if check_paths_exist(&asm_mt_path).is_err() || check_paths_exist(&asm_rh_path).is_err() { if self.core_prover.no_auto_setup { return Err(anyhow::anyhow!( - "Assembly files not found for ELF {}. Force ROM setup is enabled, but assembly files are still missing. Please ensure that the assembly generation process has been completed successfully.", - elf.name() - )); + "Assembly files not found for ELF {}. Force ROM setup is enabled, but assembly files are still missing. Please ensure that the assembly generation process has been completed successfully.", + elf.name() + )); } - tracing::info!( - ">>> ROM SETUP (one time only) - Generating assembly files for ELF: {}", - elf.name() - ); - timer_start_info!(ROM_SETUP); - let output_path = get_output_path(&None)?; - generate_assembly( - elf.elf(), - elf.name(), - &output_path, - elf.with_hints(), - self.core_prover.verbose != VerboseMode::Info, - )?; - timer_stop_and_log_info!(ROM_SETUP); - tracing::info!("<<< ROM SETUP complete - Assembly files cached for future use"); + if pctx.mpi_ctx.rank == 0 { + tracing::info!( + ">>> ROM SETUP (one time only) - Generating assembly files for ELF: {}", + elf.name() + ); + timer_start_info!(ROM_SETUP); + let output_path = get_output_path(&None)?; + generate_assembly( + elf.elf(), + elf.name(), + &output_path, + elf.with_hints(), + self.core_prover.verbose != VerboseMode::Info, + )?; + timer_stop_and_log_info!(ROM_SETUP); + tracing::info!("<<< ROM SETUP complete - Assembly files cached for future use"); + } + pctx.mpi_ctx.barrier(); } + pctx.mpi_ctx.barrier(); + timer_start_info!(STARTING_ASM_MICROSERVICES); let asm_services = AsmServices::new(world_rank, local_rank, base_port); From e62c254f7bcf7e2144faf228fad3597709a6d609 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:56:10 +0000 Subject: [PATCH 689/782] fix input hint code --- common/src/hints.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 8a98a74a1..86d5abf3d 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -59,7 +59,7 @@ pub const CTRL_CANCEL: u32 = 0x0002; pub const CTRL_ERROR: u32 = 0x0003; // === INPUT HINT CODES === -pub const HINT_INPUT: u32 = 0xF000; +pub const HINT_INPUT: u32 = 0xF0000; // === BUILT-IN HINT CODES === // SHA256 hint codes From a2aab0eb158ac1803b293e5ba36538c686a99947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Thu, 5 Mar 2026 13:39:45 +0100 Subject: [PATCH 690/782] Fix set partition for mpi processes (#830) --- distributed/crates/common/src/types.rs | 7 ++++++ distributed/crates/worker/src/worker.rs | 26 ++++++++++++++++++-- distributed/crates/worker/src/worker_node.rs | 6 ----- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index 5ef666bc5..e01f8f7cc 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -460,3 +460,10 @@ pub struct AggregationParams { pub final_proof: bool, pub compressed: bool, } + +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub struct PartitionInfo { + pub total_compute_units: usize, + pub allocation: Vec, + pub worker_idx: usize, +} diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 12eeb6484..19186180a 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -12,7 +12,7 @@ use zisk_common::io::{StreamProcessor, StreamSource, ZiskStdin}; use zisk_common::ElfBinaryFromFile; use zisk_common::ZiskExecutorTime; use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; -use zisk_distributed_common::{ComputeCapacity, JobId, WorkerId}; +use zisk_distributed_common::{ComputeCapacity, JobId, PartitionInfo, WorkerId}; use zisk_distributed_common::{HintsSourceDto, StreamDataDto, StreamMessageKind}; use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProgramPK, ZiskProver}; @@ -36,6 +36,7 @@ struct ContributionsMessage { options: ProofOptions, input_source: InputSourceDto, hints_source: HintsSourceDto, + partition_info: PartitionInfo, } #[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] @@ -448,6 +449,11 @@ impl Worker { options, input_source: job.data_ctx.input_source.clone(), hints_source: job.data_ctx.hints_source.clone(), + partition_info: PartitionInfo { + total_compute_units: job.total_compute_units as usize, + allocation: job.allocation.clone(), + worker_idx: job.rank_id as usize, + }, }; borsh::to_vec(&(JobPhase::Contributions, message)).unwrap() @@ -515,14 +521,19 @@ impl Worker { let phase_inputs = proofman::ProvePhaseInputs::Contributions(); let inputs_source = guard.data_ctx.input_source.clone(); let hints_source = guard.data_ctx.hints_source.clone(); + let partition_info = PartitionInfo { + total_compute_units: guard.total_compute_units as usize, + allocation: guard.allocation.clone(), + worker_idx: guard.rank_id as usize, + }; drop(guard); - let result = Self::execute_contribution_task( job_id.clone(), &prover, phase_inputs, inputs_source, hints_source, + partition_info, &pk, options, ); @@ -558,12 +569,14 @@ impl Worker { }) } + #[allow(clippy::too_many_arguments)] pub fn execute_contribution_task( job_id: JobId, prover: &ZiskProver, phase_inputs: ProvePhaseInputs, input_source: InputSourceDto, hints_source: HintsSourceDto, + partition_info: PartitionInfo, pk: &ZiskProgramPK, options: ProofOptions, ) -> Result> { @@ -594,6 +607,14 @@ impl Worker { prover.register_program(pk)?; + if matches!(phase_inputs, ProvePhaseInputs::Contributions()) { + prover.set_partition( + partition_info.total_compute_units, + partition_info.allocation.clone(), + partition_info.worker_idx, + )?; + } + let challenge = match prover.prove_phase(phase_inputs, options, phase) { Ok(proofman::ProvePhaseResult::Contributions(challenge)) => { info!("Contribution computation successful for {job_id}"); @@ -876,6 +897,7 @@ impl Worker { message.phase_inputs, message.input_source, message.hints_source, + message.partition_info, &self.pk, message.options, ); diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 6dc2aee5d..82cc693bc 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -552,12 +552,6 @@ impl WorkerNodeGrpc { return Err(anyhow!("Expected ContributionParams for Partial Contribution task")); }; - self.worker.set_partition( - params.job_compute_units as usize, - params.worker_allocation.clone(), - params.rank_id as usize, - )?; - let job_id = JobId::from(request.job_id); let input_source = match params.input_source { Some(InputSource::InputPath(ref inputs_uris)) => { From 54d391e6155615164688ed3b17e9146ca99b5c3b Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 5 Mar 2026 13:55:40 +0100 Subject: [PATCH 691/782] Implement assembly fcall for input ready --- core/src/zisk_rom_2_asm.rs | 102 +++++- emulator-asm/src/c_provided.c | 98 +++++- emulator-asm/src/c_provided.hpp | 3 +- emulator-asm/src/client.c | 168 ++++----- emulator-asm/src/configuration.c | 25 ++ emulator-asm/src/globals.c | 6 +- emulator-asm/src/globals.hpp | 6 +- emulator-asm/src/server.c | 322 ++++++++++-------- emulator/src/emu_context.rs | 12 +- ziskos/entrypoint/src/zisklib/fcalls/input.rs | 2 +- 10 files changed, 491 insertions(+), 253 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index ce5c61cce..0d1f825b3 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -3,10 +3,13 @@ //! Generates i86_64 assembly code that implements the Zisk ROM program use std::path::Path; +use ziskos_hints::zisklib::FCALL_INPUT_READY_ID; + use crate::{ zisk_ops::ZiskOp, AsmGenerationMethod, ZiskInst, ZiskRom, EXTRA_PARAMS_ADDR, - FLOAT_LIB_ROM_ADDR, FREE_INPUT_ADDR, M64, P2_32, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, SRC_C, - SRC_IMM, SRC_IND, SRC_MEM, SRC_REG, SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, + FLOAT_LIB_ROM_ADDR, FREE_INPUT_ADDR, INPUT_ADDR, M64, P2_32, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, + SRC_C, SRC_IMM, SRC_IND, SRC_MEM, SRC_REG, SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, + STORE_REG, }; // Regs rax, rcx, rdx, rdi, rsi, rsp, and r8-r11 are caller-save, not saved across function calls. @@ -150,6 +153,7 @@ pub struct ZiskAsmContext { mem_precompile_results_address: String, // Address where precompile results are read from mem_precompile_written_address: String, // Address where precompile written counter is stored mem_precompile_read_address: String, // Address where precompile read counter is stored + mem_input_written_address: String, // Address where input written counter is stored comments: bool, // true if we want to generate comments in the assembly source code boc: String, // begin of comment: '/*', ';', '#', etc. @@ -555,6 +559,7 @@ impl ZiskRom2Asm { ctx.mem_precompile_written_address = format!("qword {}[0x70000000]", ctx.ptr); ctx.mem_precompile_read_address = format!("qword {}[0x70001000]", ctx.ptr); } + ctx.mem_input_written_address = format!("qword {}[0x70000010]", ctx.ptr); // Preamble *code += ".intel_syntax noprefix\n"; @@ -6182,23 +6187,28 @@ impl ZiskRom2Asm { ctx.comment_str("ctx.function id = a") ); - // Set the fcall context address as the first parameter - *code += &format!( - "\tlea rdi, {} {}\n", - ctx.fcall_ctx, - ctx.comment_str("rdi = fcall context") - ); - // Get result from precompile results data - if ctx.precompile_results_fcall() { - Self::precompile_results_fcall(ctx, code, unusual_code, "rdi"); + if ctx.a.constant_value == FCALL_INPUT_READY_ID as u64 { + Self::wait_for_input_ready(ctx, code, unusual_code, REG_C); } else { - // Call the fcall function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_fcall\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Set the fcall context address as the first parameter + *code += &format!( + "\tlea rdi, {} {}\n", + ctx.fcall_ctx, + ctx.comment_str("rdi = fcall context") + ); + + // Get result from precompile results data + if ctx.precompile_results_fcall() { + Self::precompile_results_fcall(ctx, code, unusual_code, "rdi"); + } else { + // Call the fcall function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_fcall\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // If ctx.result_size == 0 => free_input = 0 @@ -8788,6 +8798,64 @@ impl ZiskRom2Asm { ctx.wait_for_prec_counter += 1; } + // Waits for the requested reg_address input address (and previous) to be available, waiting + // if necessary address in reg_address, + fn wait_for_input_ready( + ctx: &mut ZiskAsmContext, + code: &mut String, + unusual_code: &mut String, + reg_address: &str, // REG_C + ) { + *code += &ctx.full_line_comment("Wait for input data available".to_string()); + + // Calculate number of bytes until the requested address from the current read address + // required_bytes = (required_address - INPUT_ADDR - 8 + 1 + 7) & ~0x7; + // + 1 because required_address is the address of the last required byte + // + 7 & ~0x7 because if we require any byte of the last u64, we need to wait for the whole u64 to be available + *code += + &format!("\tmov rdi, {} {}\n", reg_address, ctx.comment_str("rdi = required_address")); + *code += &format!( + "\tmov {}, {} {}\n", + REG_AUX, + INPUT_ADDR, + ctx.comment_str("aux = INPUT_ADDRESS") + ); + *code += &format!( + "\tsub rdi, {} {}\n", + REG_AUX, + ctx.comment_str("rdi = required_address - INPUT_ADDRESS") + ); + *code += &format!("\tand rdi, ~0x7 {}\n", ctx.comment_str("rdi &= 0x7")); + + // if input_written == input_ready then call wait_for_input_avail + *code += &format!( + "\tmov {}, {} {}\n", + REG_AUX, + ctx.mem_input_written_address, + ctx.comment_str("aux = input_written") + ); + *code += &format!( + "\tcmp rdi, {} {}\n", + ctx.mem_input_written_address, + ctx.comment_str("required <= written") + ); + *code += &format!( + "\tja pc_{:x}_wait_for_input_avail {}\n", + ctx.pc, + ctx.comment_str("if there is data, done") + ); + *code += &format!("pc_{:x}_wait_for_input_avail_done:\n", ctx.pc); + + // Call wait_for_input_avail() + *unusual_code += &format!("pc_{:x}_wait_for_input_avail:\n", ctx.pc); + Self::push_internal_registers(ctx, unusual_code, false); + *unusual_code += "\tcall _wait_for_input_avail\n"; + *unusual_code += "\tcmp rax, 0\n"; + *unusual_code += "\tjne execute_pop_internal_regs_and_end\n"; + Self::pop_internal_registers(ctx, unusual_code, false); + *unusual_code += &format!("\tjmp pc_{:x}_wait_for_input_avail_done\n", ctx.pc); + } + /*******************/ /* CHUNK START/END */ /*******************/ diff --git a/emulator-asm/src/c_provided.c b/emulator-asm/src/c_provided.c index 643dc1ad0..1b1edcdf7 100644 --- a/emulator-asm/src/c_provided.c +++ b/emulator-asm/src/c_provided.c @@ -282,7 +282,7 @@ extern void _realloc_trace (void) int _wait_for_prec_avail (void) { // Increment wait counter - wait_counter++; + wait_prec_avail_counter++; // Sync control output shared memory so that the writer can see the precompile reads we have // done, and thus update the precompile_written_address if needed @@ -380,4 +380,100 @@ int _wait_for_prec_avail (void) fflush(stdout); fflush(stderr); exit(-1); +} + +/****************************/ +/* WAIT FOR INPUT AVAILABLE */ +/****************************/ + +// Called by the assembly when input_written == input_read, to wait for new input to be available +int _wait_for_input_avail (uint64_t required_input_bytes) +{ + // Increment wait counter + wait_input_avail_counter++; + + // Make sure the input available semaphore is reset before checking the condition, + // since the caller may have posted it (even several times) before we called sem_wait() + while (sem_trywait(sem_input_avail) == 0) {/*printf("Purging sem_input_avail\n");*/}; + + // Sync control input shared memory so that we can see the latest input_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check if there is already input data available + if (*input_written_address > required_input_bytes) + { + // Sync input shared memory + if (msync((void *)shmem_input_address, MAX_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + return 0; + } + + // Wait again, but blocking this time + while (true) + { + struct timespec ts; + int result = clock_gettime(CLOCK_REALTIME, &ts); + if (result == -1) + { + printf("ERROR: wait_for_input_avail() failed calling clock_gettime() errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + ts.tv_sec += 5; // 5 seconds timeout + + //printf("_wait_for_input_avail() calling sem_wait input_written_address=%lu required_input_bytes=%lu\n", *input_written_address, required_input_bytes); + result = sem_timedwait(sem_input_avail, &ts); + //printf("_wait_for_input_avail() called sem_wait input_written_address=%lu required_input_bytes=%lu\n", *input_written_address, required_input_bytes); + if ((result == -1) && (errno != ETIMEDOUT)) + { + printf("ERROR: wait_for_input_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_input_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync control input shared memory so that we can see the latest input_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (*precompile_exit_address != 0) + { + printf("ERROR: wait_for_input_avail() found precompile_exit_address=%lu\n", *precompile_exit_address); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (*input_written_address > required_input_bytes) + { + // Sync input shared memory + if (msync((void *)shmem_input_address, MAX_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + return 0; + } + } + + printf("ERROR: wait_for_input_avail() unreachable code\n"); + fflush(stdout); + fflush(stderr); + exit(-1); } \ No newline at end of file diff --git a/emulator-asm/src/c_provided.hpp b/emulator-asm/src/c_provided.hpp index 1b67b537d..4d56a7429 100644 --- a/emulator-asm/src/c_provided.hpp +++ b/emulator-asm/src/c_provided.hpp @@ -7,6 +7,7 @@ extern int _print_regs(); extern int _print_pc (uint64_t pc, uint64_t c); extern void _chunk_done(); extern void _realloc_trace (void); -int _wait_for_prec_avail (void); +extern int _wait_for_prec_avail (void); +extern int _wait_for_input_avail (uint64_t required_input_bytes); #endif // EMULATOR_ASM_C_PROVIDED_HPP \ No newline at end of file diff --git a/emulator-asm/src/client.c b/emulator-asm/src/client.c index 544ddb9e5..35e731e24 100644 --- a/emulator-asm/src/client.c +++ b/emulator-asm/src/client.c @@ -114,87 +114,6 @@ void client_setup (void) if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); - /*****************/ - /* CONTROL INPUT */ - /*****************/ - - // Create the control input shared memory - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR, 0666); - if (shmem_control_input_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map control input address space - if (verbose) gettimeofday(&start_time, NULL); - void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_INPUT_ADDR) - { - printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_control_input_address = (uint64_t *)pControl; - precompile_written_address = &shmem_control_input_address[0]; - precompile_exit_address = &shmem_control_input_address[1]; - if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); - - /*****************/ - /* CONTROL OUTPUT */ - /*****************/ - - // Create the control input shared memory - shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR, 0666); - if (shmem_control_output_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map control input address space - if (verbose) gettimeofday(&start_time, NULL); - pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_OUTPUT_ADDR) - { - printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_control_output_address = (uint64_t *)pControl; - precompile_read_address = &shmem_control_output_address[0]; - if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); - /*************************/ /* PRECOMPILE SEMAPHORES */ /*************************/ @@ -225,6 +144,88 @@ void client_setup (void) } if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_read_name); } + + /*****************/ + /* CONTROL INPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map control input address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_INPUT_ADDR) + { + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; + input_written_address = &shmem_control_input_address[2]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + + /*****************/ + /* CONTROL OUTPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR, 0666); + if (shmem_control_output_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map control input address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); } typedef enum { @@ -638,6 +639,9 @@ void client_run (void) exit(-1); } + // Set written counter + *input_written_address = input_data_size + 8; // in bytes, including the 8 bytes of input size at the beginning + #ifdef DEBUG gettimeofday(&stop_time, NULL); duration = TimeDiff(start_time, stop_time); @@ -651,7 +655,7 @@ void client_run (void) /*****************************/ if (precompile_results_enabled) { - // reset written counter + // Reset written counter *precompile_written_address = 0; //client_write_precompile_results(); diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index ef33c7308..83cb93ba9 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -525,12 +525,15 @@ void configure (void) strcat(sem_prec_avail_name, "_FT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_FT_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_FT_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, ""); strcpy(sem_chunk_done_name, ""); @@ -570,12 +573,15 @@ void configure (void) strcat(sem_prec_avail_name, "_MT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MT_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MT_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); @@ -618,12 +624,15 @@ void configure (void) strcat(sem_prec_avail_name, "_RH_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_RH_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_RH_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_RH_output"); @@ -666,12 +675,15 @@ void configure (void) strcat(sem_prec_avail_name, "_MA_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MA_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MA_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MA_output"); @@ -706,6 +718,7 @@ void configure (void) strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CH_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -756,12 +769,15 @@ void configure (void) strcat(sem_prec_avail_name, "_ZP_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_ZP_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_ZP_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_ZP_output"); @@ -804,12 +820,15 @@ void configure (void) strcat(sem_prec_avail_name, "_MO_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MO_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MO_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MO_output"); @@ -840,6 +859,7 @@ void configure (void) strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CM_output"); strcpy(sem_chunk_done_name, ""); @@ -880,12 +900,15 @@ void configure (void) strcat(sem_prec_avail_name, "_MT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MT_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MT_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); @@ -916,6 +939,7 @@ void configure (void) strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CA_output"); strcpy(sem_chunk_done_name, ""); @@ -973,6 +997,7 @@ void configure (void) printf("\tsem_shutdown_done=%s\n", sem_shutdown_done_name); printf("\tsem_prec_avail=%s\n", sem_prec_avail_name); printf("\tsem_prec_read=%s\n", sem_prec_read_name); + printf("\tsem_input_avail=%s\n", sem_input_avail_name); printf("\tmap_locked_flag=%d\n", map_locked_flag); printf("\toutput=%u\n", output); printf("\tprecompile_results_enabled=%u\n", precompile_results_enabled); diff --git a/emulator-asm/src/globals.c b/emulator-asm/src/globals.c index adb546b41..333c60c5d 100644 --- a/emulator-asm/src/globals.c +++ b/emulator-asm/src/globals.c @@ -36,6 +36,7 @@ char sem_prec_avail_name[128] = {0}; char sem_prec_read_name[128] = {0}; char sem_chunk_done_name[128] = {0}; char sem_shutdown_done_name[128] = {0}; +char sem_input_avail_name[128] = {0}; char file_lock_name[128] = {0}; char log_name[128] = {0}; bool call_chunk_done = false; @@ -87,12 +88,14 @@ void * shmem_precompile_address = NULL; // Precompile results semaphores sem_t * sem_prec_avail = NULL; sem_t * sem_prec_read = NULL; +sem_t * sem_input_avail = NULL; // Control input shared memory int shmem_control_input_fd = -1; uint64_t * shmem_control_input_address = NULL; volatile uint64_t * precompile_written_address = NULL; volatile uint64_t * precompile_exit_address = NULL; +volatile uint64_t * input_written_address = NULL; // Control output shared memory int shmem_control_output_fd = -1; @@ -114,7 +117,8 @@ uint64_t assembly_duration; // Counters used in functions called from assembly code uint64_t realloc_counter = 0; -uint64_t wait_counter = 0; +uint64_t wait_prec_avail_counter = 0; +uint64_t wait_input_avail_counter = 0; uint64_t print_pc_counter = 0; // Chunk player globals diff --git a/emulator-asm/src/globals.hpp b/emulator-asm/src/globals.hpp index 3d1bf6ecf..d9db361e2 100644 --- a/emulator-asm/src/globals.hpp +++ b/emulator-asm/src/globals.hpp @@ -41,6 +41,7 @@ extern char sem_prec_avail_name[128]; extern char sem_prec_read_name[128]; extern char sem_chunk_done_name[128]; extern char sem_shutdown_done_name[128]; +extern char sem_input_avail_name[128]; extern char file_lock_name[128]; extern char log_name[128]; extern bool call_chunk_done; @@ -116,12 +117,14 @@ extern void * shmem_precompile_address; // Precompile results semaphores extern sem_t * sem_prec_avail; extern sem_t * sem_prec_read; +extern sem_t * sem_input_avail; // Control input shared memory extern int shmem_control_input_fd; extern uint64_t * shmem_control_input_address; extern volatile uint64_t * precompile_written_address; extern volatile uint64_t * precompile_exit_address; +extern volatile uint64_t * input_written_address; // Control output shared memory extern int shmem_control_output_fd; @@ -143,7 +146,8 @@ extern uint64_t assembly_duration; // Counters used in functions called from assembly code extern uint64_t realloc_counter; -extern uint64_t wait_counter; +extern uint64_t wait_prec_avail_counter; +extern uint64_t wait_input_avail_counter; extern uint64_t print_pc_counter; // Chunk player globals diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index e16ddb624..3de9dd451 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -223,172 +223,173 @@ void server_setup (void) precompile_results_address = (uint64_t *)pPrecompile; if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); - /*****************/ - /* CONTROL INPUT */ - /*****************/ - - if (!open_input_shm) - { - // Make sure the precompile results shared memory is deleted - shm_unlink(shmem_control_input_name); - - // Create the control shared memory - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR | O_CREAT, 0666); - if (shmem_control_input_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Size it - result = ftruncate(shmem_control_input_fd, CONTROL_INPUT_SIZE); - if (result != 0) - { - printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + /*************************/ + /* PRECOMPILE SEMAPHORES */ + /*************************/ - // Sync - fsync(shmem_control_input_fd); + // Create the semaphore for precompile results available signal + assert(strlen(sem_prec_avail_name) > 0); - // Close the descriptor - if (close(shmem_control_input_fd) != 0) - { - printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } + sem_unlink(sem_prec_avail_name); - // Open the control input shared memory as read-only - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY, 0666); - if (shmem_control_input_fd < 0) + sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_avail == SEM_FAILED) { - printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } + if (verbose) printf("sem_open(%s) succeeded sem_prec_avail=%p\n", sem_prec_avail_name, sem_prec_avail); - // Map precompile address space - if (verbose) gettimeofday(&start_time, NULL); - void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_INPUT_ADDR) + // Create the semaphore for precompile results read signal + assert(strlen(sem_prec_read_name) > 0); + + sem_unlink(sem_prec_read_name); + + sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_read == SEM_FAILED) { - printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } - shmem_control_input_address = (uint64_t *)pControl; - precompile_written_address = &shmem_control_input_address[0]; - precompile_exit_address = &shmem_control_input_address[1]; - if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + if (verbose) printf("sem_open(%s) succeeded sem_prec_read=%p\n", sem_prec_read_name, sem_prec_read); + } - /******************/ - /* CONTROL OUTPUT */ - /******************/ + /*****************/ + /* CONTROL INPUT */ + /*****************/ + if (!open_input_shm) + { // Make sure the precompile results shared memory is deleted - shm_unlink(shmem_control_output_name); + shm_unlink(shmem_control_input_name); // Create the control shared memory - shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR | O_CREAT, 0666); - if (shmem_control_output_fd < 0) + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_input_fd < 0) { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } // Size it - result = ftruncate(shmem_control_output_fd, CONTROL_OUTPUT_SIZE); + result = ftruncate(shmem_control_input_fd, CONTROL_INPUT_SIZE); if (result != 0) { - printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } - // Map precompile address space - if (verbose) gettimeofday(&start_time, NULL); - pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_OUTPUT_ADDR) + // Sync + fsync(shmem_control_input_fd); + + // Close the descriptor + if (close(shmem_control_input_fd) != 0) { - printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } - shmem_control_output_address = (uint64_t *)pControl; - precompile_read_address = &shmem_control_output_address[0]; - if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); + } - /*************************/ - /* PRECOMPILE SEMAPHORES */ - /*************************/ + // Open the control input shared memory as read-only + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - // Create the semaphore for precompile results available signal - assert(strlen(sem_prec_avail_name) > 0); + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_INPUT_ADDR) + { + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; + input_written_address = &shmem_control_input_address[2]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); - sem_unlink(sem_prec_avail_name); + /******************/ + /* CONTROL OUTPUT */ + /******************/ - sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_prec_avail == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded sem_prec_avail=%p\n", sem_prec_avail_name, sem_prec_avail); + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_output_name); - // Create the semaphore for precompile results read signal - assert(strlen(sem_prec_read_name) > 0); + // Create the control shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_output_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - sem_unlink(sem_prec_read_name); + // Size it + result = ftruncate(shmem_control_output_fd, CONTROL_OUTPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_prec_read == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded sem_prec_read=%p\n", sem_prec_read_name, sem_prec_read); + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); /*******/ /* RAM */ @@ -536,7 +537,25 @@ void server_setup (void) fflush(stderr); exit(-1); } - if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); + if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); // Create the semaphore for input available signal + + /***********************/ + /* SEM INPUT AVAILABLE */ + /***********************/ + + assert(strlen(sem_input_avail_name) > 0); + + sem_unlink(sem_input_avail_name); + + sem_input_avail = sem_open(sem_input_avail_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_input_avail == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_input_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded sem_input_avail=%p\n", sem_input_avail_name, sem_input_avail); } void server_reset_fast (void) @@ -677,10 +696,11 @@ void server_run (void) uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; - printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", + printf("Duration = %lu us, realloc counter = %lu, wait prec counter = %lu, wait input counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", duration, realloc_counter, - wait_counter, + wait_prec_avail_counter, + wait_input_avail_counter, steps, step_duration_ns, step_tp_sec, @@ -886,28 +906,6 @@ void server_cleanup (void) printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); } - // Cleanup CONTROL - result = munmap((void *)shmem_control_input_address, CONTROL_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_control_input_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - } - result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_control_output_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); - } - // Semaphores cleanup result = sem_close(sem_prec_avail); if (result == -1) @@ -929,6 +927,33 @@ void server_cleanup (void) { printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); } + result = sem_close(sem_input_avail); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_input_avail_name, errno, strerror(errno)); + } + } + + // Cleanup CONTROL + result = munmap((void *)shmem_control_input_address, CONTROL_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_input_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + } + result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_output_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); } // Cleanup trace @@ -949,6 +974,13 @@ void server_cleanup (void) } } + // Cleanup input available semaphore + result = sem_unlink(sem_input_avail_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_input_avail_name, errno, strerror(errno)); + } + // Post shutdown done semaphore result = sem_post(sem_shutdown_done); if (result == -1) diff --git a/emulator/src/emu_context.rs b/emulator/src/emu_context.rs index 997e1a51c..350144f5f 100644 --- a/emulator/src/emu_context.rs +++ b/emulator/src/emu_context.rs @@ -37,16 +37,20 @@ impl EmuContext { }; // Check the input data size is inside the proper range - if input.len() > (options.max_input_mem - 16) as usize { + if input.len() > (options.max_input_mem - 8) as usize { panic!("EmuContext::new() input size too big size={}", input.len()); } // Add the length and input data read sections - ctx.inst_ctx.input_len = input.len() as u64; + ctx.inst_ctx.input_len = (input.len() as u64 + 7) & !7; // Round up to nearest multiple of 8 let free_input = 0u64; ctx.inst_ctx.mem.add_read_section(INPUT_ADDR, &free_input.to_le_bytes()); - ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8, &ctx.inst_ctx.input_len.to_le_bytes()); - ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 16, &input); + ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8, &input); + let zeros_to_write = ctx.inst_ctx.input_len - input.len() as u64; + for i in 0..zeros_to_write { + let zero = [0u8; 1]; + ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8 + input.len() as u64 + i, &zero); + } // Add the write section ctx.inst_ctx.mem.add_write_section(RAM_ADDR, RAM_SIZE); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/input.rs b/ziskos/entrypoint/src/zisklib/fcalls/input.rs index b4ebbadd0..dc9fd7d72 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/input.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/input.rs @@ -17,7 +17,7 @@ pub fn fcall_input_ready(address: &u64) { } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { - ziskos_fcall_param!(address, 1); // Number of inputs + ziskos_fcall_param!(address, 1); // Highest input address required to be ready to read ziskos_fcall!(FCALL_INPUT_READY_ID); } } From bf0e437d727160bcdfc7be16018c7a81e35302ed Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 27 Feb 2026 21:17:38 +0100 Subject: [PATCH 692/782] Split main.c into several code files --- emulator-asm/Makefile | 4 +- emulator-asm/src/asm_provided.hpp | 31 + emulator-asm/src/c_provided.c | 381 +++ emulator-asm/src/c_provided.hpp | 12 + emulator-asm/src/client.c | 1502 +++++++++ emulator-asm/src/client.hpp | 8 + emulator-asm/src/configuration.c | 974 ++++++ emulator-asm/src/configuration.hpp | 7 + emulator-asm/src/constants.hpp | 117 + emulator-asm/src/emu.c | 4 +- emulator-asm/src/emu.hpp | 3 + emulator-asm/src/globals.c | 136 + emulator-asm/src/globals.hpp | 166 + emulator-asm/src/main.c | 5034 +--------------------------- emulator-asm/src/server.c | 958 ++++++ emulator-asm/src/server.hpp | 11 + emulator-asm/src/trace.c | 238 ++ emulator-asm/src/trace.hpp | 12 + emulator-asm/src/trace_logs.c | 678 ++++ emulator-asm/src/trace_logs.hpp | 14 + 20 files changed, 5293 insertions(+), 4997 deletions(-) create mode 100644 emulator-asm/src/asm_provided.hpp create mode 100644 emulator-asm/src/c_provided.c create mode 100644 emulator-asm/src/c_provided.hpp create mode 100644 emulator-asm/src/client.c create mode 100644 emulator-asm/src/client.hpp create mode 100644 emulator-asm/src/configuration.c create mode 100644 emulator-asm/src/configuration.hpp create mode 100644 emulator-asm/src/constants.hpp create mode 100644 emulator-asm/src/globals.c create mode 100644 emulator-asm/src/globals.hpp create mode 100644 emulator-asm/src/server.c create mode 100644 emulator-asm/src/server.hpp create mode 100644 emulator-asm/src/trace.c create mode 100644 emulator-asm/src/trace.hpp create mode 100644 emulator-asm/src/trace_logs.c create mode 100644 emulator-asm/src/trace_logs.hpp diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 1425688d8..75835a40b 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -56,9 +56,9 @@ build/dma.o: $(DMA_OBJS) ld -r $(DMA_OBJS) -o $@ # Compile the final executable -$(OUT_PATH): build/emu.o src/main.c src/emu.c src/chfast/keccak.c build/dma.o +$(OUT_PATH): build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c build/dma.o mkdir -p $(OUT_DIR) - gcc $(CFLAGS) src/main.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ + gcc $(CFLAGS) src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ clean: rm -rf build diff --git a/emulator-asm/src/asm_provided.hpp b/emulator-asm/src/asm_provided.hpp new file mode 100644 index 000000000..9a6b42e5b --- /dev/null +++ b/emulator-asm/src/asm_provided.hpp @@ -0,0 +1,31 @@ +#ifndef EMULATOR_ASM_ASM_PROVIDED_HPP +#define EMULATOR_ASM_ASM_PROVIDED_HPP + +#include + +/**************************/ +/* Assembly-provided code */ +/**************************/ + +// This is the emulator assembly code start function, which will execute the code in the ROM until +// it ends, and generate the trace in the output trace memory. +// It is called from C to start the execution of the assembly code. +void emulator_start(void); + +// These functions are implemented in assembly and provide access to configuration parameters used +// to generate the assembly code, and that in some cases must match the C main program configuration +uint64_t get_max_bios_pc(void); +uint64_t get_max_program_pc(void); +uint64_t get_gen_method(void); // Must match the C main program provided argument +uint64_t get_precompile_results(void); + +// These variables are updated by the assembly code to provide information about the execution +// status and trace generation, accessed by C to generate the response to the client +extern uint64_t MEM_STEP; // Current step, i.e. number of executed instructions, updated by assembly at every step or at the end of every chunk, depending on the generation method +extern uint64_t MEM_END; // Indicates the end of execution +extern uint64_t MEM_ERROR; // Indicates an error during execution +extern uint64_t MEM_TRACE_ADDRESS; // Address of the trace memory +extern uint64_t MEM_CHUNK_ADDRESS; // Address of the current chunk +extern uint64_t MEM_CHUNK_START_STEP; // Step at which the current chunk started + +#endif // EMULATOR_ASM_ASM_PROVIDED_HPP \ No newline at end of file diff --git a/emulator-asm/src/c_provided.c b/emulator-asm/src/c_provided.c new file mode 100644 index 000000000..cecec70cf --- /dev/null +++ b/emulator-asm/src/c_provided.c @@ -0,0 +1,381 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "c_provided.hpp" +#include "globals.hpp" +#include "trace.hpp" +#include "emu.hpp" + +/**************/ +/* TRACE SIZE */ +/**************/ + +void set_trace_size (uint64_t new_trace_size) +{ + // Update trace global variables + // printf("%s trace resize (trace_resize_request: %ld): %ld MB => %ld MB\n", log_name, trace_resize_request, trace_size >> 20, new_trace_size >> 20); + + // trace_resize_request = 0; + + trace_size = new_trace_size; + trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; + pOutputTrace[2] = trace_size; +} + +/**************/ +/* PRINT REGS */ +/**************/ + +//#define PRINT_REGS +#ifdef PRINT_REGS +extern uint64_t reg_0; +extern uint64_t reg_1; +extern uint64_t reg_2; +extern uint64_t reg_3; +extern uint64_t reg_4; +extern uint64_t reg_5; +extern uint64_t reg_6; +extern uint64_t reg_7; +extern uint64_t reg_8; +extern uint64_t reg_9; +extern uint64_t reg_10; +extern uint64_t reg_11; +extern uint64_t reg_12; +extern uint64_t reg_13; +extern uint64_t reg_14; +extern uint64_t reg_15; +extern uint64_t reg_16; +extern uint64_t reg_17; +extern uint64_t reg_18; +extern uint64_t reg_19; +extern uint64_t reg_20; +extern uint64_t reg_21; +extern uint64_t reg_22; +extern uint64_t reg_23; +extern uint64_t reg_24; +extern uint64_t reg_25; +extern uint64_t reg_26; +extern uint64_t reg_27; +extern uint64_t reg_28; +extern uint64_t reg_29; +extern uint64_t reg_30; +extern uint64_t reg_31; +extern uint64_t reg_32; +extern uint64_t reg_33; +extern uint64_t reg_34; +#endif + +// Used for debugging purposes +extern int _print_regs() +{ +#ifdef PRINT_REGS + printf("print_regs()\n"); + printf("\treg[ 0]=%lu=0x%lx=@%p\n", reg_0, reg_0, ®_0); + printf("\treg[ 1]=%lu=0x%lx=@%p\n", reg_1, reg_1, ®_1); + printf("\treg[ 2]=%lu=0x%lx=@%p\n", reg_2, reg_2, ®_2); + printf("\treg[ 3]=%lu=0x%lx=@%p\n", reg_3, reg_3, ®_3); + printf("\treg[ 4]=%lu=0x%lx=@%p\n", reg_4, reg_4, ®_4); + printf("\treg[ 5]=%lu=0x%lx=@%p\n", reg_5, reg_5, ®_5); + printf("\treg[ 6]=%lu=0x%lx=@%p\n", reg_6, reg_6, ®_6); + printf("\treg[ 7]=%lu=0x%lx=@%p\n", reg_7, reg_7, ®_7); + printf("\treg[ 8]=%lu=0x%lx=@%p\n", reg_8, reg_8, ®_8); + printf("\treg[ 9]=%lu=0x%lx=@%p\n", reg_9, reg_9, ®_9); + printf("\treg[10]=%lu=0x%lx=@%p\n", reg_10, reg_10, ®_10); + printf("\treg[11]=%lu=0x%lx=@%p\n", reg_11, reg_11, ®_11); + printf("\treg[12]=%lu=0x%lx=@%p\n", reg_12, reg_12, ®_12); + printf("\treg[13]=%lu=0x%lx=@%p\n", reg_13, reg_13, ®_13); + printf("\treg[14]=%lu=0x%lx=@%p\n", reg_14, reg_14, ®_14); + printf("\treg[15]=%lu=0x%lx=@%p\n", reg_15, reg_15, ®_15); + printf("\treg[16]=%lu=0x%lx=@%p\n", reg_16, reg_16, ®_16); + printf("\treg[17]=%lu=0x%lx=@%p\n", reg_17, reg_17, ®_17); + printf("\treg[18]=%lu=0x%lx=@%p\n", reg_18, reg_18, ®_18); + printf("\treg[19]=%lu=0x%lx=@%p\n", reg_19, reg_19, ®_19); + printf("\treg[20]=%lu=0x%lx=@%p\n", reg_20, reg_20, ®_20); + printf("\treg[21]=%lu=0x%lx=@%p\n", reg_21, reg_21, ®_21); + printf("\treg[22]=%lu=0x%lx=@%p\n", reg_22, reg_22, ®_22); + printf("\treg[23]=%lu=0x%lx=@%p\n", reg_23, reg_23, ®_23); + printf("\treg[24]=%lu=0x%lx=@%p\n", reg_24, reg_24, ®_24); + printf("\treg[25]=%lu=0x%lx=@%p\n", reg_25, reg_25, ®_25); + printf("\treg[26]=%lu=0x%lx=@%p\n", reg_26, reg_26, ®_26); + printf("\treg[27]=%lu=0x%lx=@%p\n", reg_27, reg_27, ®_27); + printf("\treg[28]=%lu=0x%lx=@%p\n", reg_28, reg_28, ®_28); + printf("\treg[29]=%lu=0x%lx=@%p\n", reg_29, reg_29, ®_29); + printf("\treg[30]=%lu=0x%lx=@%p\n", reg_30, reg_30, ®_30); + printf("\treg[31]=%lu=0x%lx=@%p\n", reg_31, reg_31, ®_31); + printf("\treg[32]=%lu=0x%lx=@%p\n", reg_32, reg_32, ®_32); + printf("\treg[33]=%lu=0x%lx=@%p\n", reg_33, reg_33, ®_33); + printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); + printf("\n"); +#endif +} + +/************/ +/* PRINT PC */ +/************/ + +//#define PRINT_PC_DURATION +#ifdef PRINT_PC_DURATION +struct timeval print_pc_tv; +#endif + +// Used for debugging purposes +extern int _print_pc (uint64_t pc, uint64_t c) +{ +#ifdef PRINT_PC_DURATION + print_pc_counter++; + { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t duration = TimeDiff(print_pc_tv, tv); + if (duration > 900) + { + uint64_t chunk = print_pc_counter / chunk_size; + printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); + fflush(stdout); + } + print_pc_tv = tv; + } +#endif + + printf("s=%lu pc=%lx c=%lx", print_pc_counter, pc, c); + +//#define PRINT_PC_REGS +#ifdef PRINT_PC_REGS + /* Used for debugging */ + printf(" r0=%lx", reg_0); + printf(" r1=%lx", reg_1); + printf(" r2=%lx", reg_2); + printf(" r3=%lx", reg_3); + printf(" r4=%lx", reg_4); + printf(" r5=%lx", reg_5); + printf(" r6=%lx", reg_6); + printf(" r7=%lx", reg_7); + printf(" r8=%lx", reg_8); + printf(" r9=%lx", reg_9); + printf(" r10=%lx", reg_10); + printf(" r11=%lx", reg_11); + printf(" r12=%lx", reg_12); + printf(" r13=%lx", reg_13); + printf(" r14=%lx", reg_14); + printf(" r15=%lx", reg_15); + printf(" r16=%lx", reg_16); + printf(" r17=%lx", reg_17); + printf(" r18=%lx", reg_18); + printf(" r19=%lx", reg_19); + printf(" r20=%lx", reg_20); + printf(" r21=%lx", reg_21); + printf(" r22=%lx", reg_22); + printf(" r23=%lx", reg_23); + printf(" r24=%lx", reg_24); + printf(" r25=%lx", reg_25); + printf(" r26=%lx", reg_26); + printf(" r27=%lx", reg_27); + printf(" r28=%lx", reg_28); + printf(" r29=%lx", reg_29); + printf(" r30=%lx", reg_30); + printf(" r31=%lx", reg_31); +#endif + + printf("\n"); + fflush(stdout); + print_pc_counter++; +} + +/**************/ +/* CHUNK DONE */ +/**************/ + +//#define CHUNK_DONE_DURATION +#ifdef CHUNK_DONE_DURATION +uint64_t chunk_done_counter = 0; +struct timeval chunk_done_tv; +#endif + +//#define CHUNK_DONE_SYNC_DURATION +#ifdef CHUNK_DONE_SYNC_DURATION +struct timeval sync_start, sync_stop; +uint64_t sync_duration = 0; +#endif + +// Called by the assembly to notify that a chunk is done and its trace is ready to be consumed +extern void _chunk_done() +{ +#ifdef CHUNK_DONE_DURATION + chunk_done_counter++; + if ((chunk_done_counter & 0xFF) == 0) + { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t duration = TimeDiff(chunk_done_tv, tv); + if (duration > 5000) + { + printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); + fflush(stdout); + } + chunk_done_tv = tv; + } +#endif + +#ifdef CHUNK_DONE_SYNC_DURATION + gettimeofday(&sync_start, NULL); +#endif + + __sync_synchronize(); + +#ifdef CHUNK_DONE_SYNC_DURATION + gettimeofday(&sync_stop, NULL); + sync_duration += TimeDiff(sync_start, sync_stop); + printf("chunk_done() sync_duration=%lu\n", sync_duration); +#endif + + // Notify the caller that a new chunk is done and its trace is ready to be consumed + assert(call_chunk_done); + int result = sem_post(sem_chunk_done); + if (result == -1) + { + printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +/*****************/ +/* REALLOC TRACE */ +/*****************/ + +// Called by the assembly to reallocate the trace when needed, e.g. for the next chunk, +// to increase the trace size by another chunk size +extern void _realloc_trace (void) +{ + // Increase realloc counter + realloc_counter++; + + // Map next chunk of the trace shared memory + trace_map_next_chunk(); + + // Update trace global variables + set_trace_size(trace_total_mapped_size); + +#ifdef DEBUG + if (verbose) printf("realloc_trace() realloc counter=%lu trace_address=0x%lx trace_size=%lu=%lx max_address=0x%lx trace_address_threshold=0x%lx chunk_size=%lu\n", realloc_counter, trace_address, trace_size, trace_size, trace_address + trace_size, trace_address_threshold, chunk_size); +#endif +} + +/*********************************/ +/* WAIT FOR PRECOMPILE AVAILABLE */ +/*********************************/ + +// Called by the assembly when prec_written == prec_read, to wait for new precompile results to be available +int _wait_for_prec_avail (void) +{ + // Increment wait counter + wait_counter++; + + // Sync control output shared memory so that the writer can see the precompile reads we have + // done, and thus update the precompile_written_address if needed + if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Tell the writer that we have read some precompile results + sem_post(sem_prec_read); + + // Make sure the precompile available semaphore is reset before checking the condition, + // since the caller may have posted it (even several times) before we called sem_wait() + while (sem_trywait(sem_prec_avail) == 0) {/*printf("Purging sem_prec_avail\n");*/}; + + // Sync control input shared memory so that we can see the latest precompile_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check if there are already precompile results available + if (*precompile_written_address > *precompile_read_address) + { + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + return 0; + } + + // Wait again, but blocking this time + while (true) + { + struct timespec ts; + int result = clock_gettime(CLOCK_REALTIME, &ts); + if (result == -1) + { + printf("ERROR: wait_for_prec_avail() failed calling clock_gettime() errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + ts.tv_sec += 5; // 5 seconds timeout + + //printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + result = sem_timedwait(sem_prec_avail, &ts); + //printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + if ((result == -1) && (errno != ETIMEDOUT)) + { + printf("ERROR: wait_for_prec_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync control input shared memory so that we can see the latest precompile_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (*precompile_exit_address != 0) + { + printf("ERROR: wait_for_prec_avail() found precompile_exit_address=%lu\n", *precompile_exit_address); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (*precompile_written_address > *precompile_read_address) + { + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + return 0; + } + } + + printf("ERROR: wait_for_prec_avail() unreachable code\n"); + fflush(stdout); + fflush(stderr); + exit(-1); +} \ No newline at end of file diff --git a/emulator-asm/src/c_provided.hpp b/emulator-asm/src/c_provided.hpp new file mode 100644 index 000000000..1b67b537d --- /dev/null +++ b/emulator-asm/src/c_provided.hpp @@ -0,0 +1,12 @@ +#ifndef EMULATOR_ASM_C_PROVIDED_HPP +#define EMULATOR_ASM_C_PROVIDED_HPP + +#include + +extern int _print_regs(); +extern int _print_pc (uint64_t pc, uint64_t c); +extern void _chunk_done(); +extern void _realloc_trace (void); +int _wait_for_prec_avail (void); + +#endif // EMULATOR_ASM_C_PROVIDED_HPP \ No newline at end of file diff --git a/emulator-asm/src/client.c b/emulator-asm/src/client.c new file mode 100644 index 000000000..544ddb9e5 --- /dev/null +++ b/emulator-asm/src/client.c @@ -0,0 +1,1502 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "constants.hpp" +#include "client.hpp" +#include "globals.hpp" +#include "emu.hpp" + +/**********/ +/* CLIENT */ +/**********/ + +void client_setup (void) +{ + assert(!server); + assert(client); + + int result; + + /***********************/ + /* INPUT MINIMAL TRACE */ + /***********************/ + + // Input MT trace + if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) + { + // Create the output shared memory + shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); + if (shmem_mt_fd < 0) + { + printf("ERROR: Failed calling trace shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map it to the trace address +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); +#endif + if (pTrace == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pTrace != TRACE_ADDR) + { + printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); + } + + /**********************/ + /* PRECOMPILE_RESULTS */ + /**********************/ + + if (precompile_results_enabled) + { + /**************/ + /* PRECOMPILE */ + /**************/ + + // Create the precompile results shared memory + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pPrecompile == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_precompile_address = pPrecompile; + precompile_results_address = (uint64_t *)pPrecompile; + + if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); + + /*****************/ + /* CONTROL INPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map control input address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_INPUT_ADDR) + { + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + + /*****************/ + /* CONTROL OUTPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR, 0666); + if (shmem_control_output_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map control input address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); + + /*************************/ + /* PRECOMPILE SEMAPHORES */ + /*************************/ + + // Create the semaphore for precompile results available signal + assert(strlen(sem_prec_avail_name) > 0); + + sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT, 0666, 0); + if (sem_prec_avail == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_avail_name); + + // Create the semaphore for precompile results read signal + assert(strlen(sem_prec_read_name) > 0); + + sem_prec_read = sem_open(sem_prec_read_name, O_CREAT, 0666, 0); + if (sem_prec_read == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_read_name); + } +} + +typedef enum { + PrecompileReadMode_NoPrefix, + PrecompileReadMode_Prefixed +} PrecompileReadMode; + +PrecompileReadMode precompile_read_mode = PrecompileReadMode_NoPrefix; +//PrecompileReadMode precompile_read_mode = PrecompileReadMode_Prefixed; + +typedef enum { + PrecompileWriteMode_Full, + PrecompileWriteMode_OnePrecAtATime +} PrecompileWriteMode; + +PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_Full; +//PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_OnePrecAtATime; + +//#define PRECOMPILE_FIXED_SIZE 25 // Keccak-f state size in u64s +#define PRECOMPILE_FIXED_SIZE 4 // SHA-256 state size in u64s + +void client_write_precompile_results (void) +{ + int result; + +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + + // Open input file + FILE * precompile_fp = fopen(precompile_file_name, "r"); + if (precompile_fp == NULL) + { + printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Get input file size + if (fseek(precompile_fp, 0, SEEK_END) == -1) + { + printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + long precompile_data_size = ftell(precompile_fp); + if (precompile_data_size == -1) + { + printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((precompile_data_size & 0x7) != 0) + { + printf("ERROR: Precompile results file (%s) size (%lu) is not a multiple of 8 B\n", precompile_file_name, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Go back to the first byte + if (fseek(precompile_fp, 0, SEEK_SET) == -1) + { + printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + assert(precompile_read_mode == PrecompileReadMode_NoPrefix || precompile_read_mode == PrecompileReadMode_Prefixed); + assert(precompile_write_mode == PrecompileWriteMode_Full || precompile_write_mode == PrecompileWriteMode_OnePrecAtATime); + + /*************/ + /* NO PREFIX */ + /*************/ + + if (precompile_read_mode == PrecompileReadMode_NoPrefix) + { + if (precompile_write_mode == PrecompileWriteMode_Full) + { + // Check the precompile data size is inside the proper range + if (precompile_data_size > MAX_PRECOMPILE_SIZE) + { + printf("ERROR: Size of precompile results file (%s) is too long (%lu)\n", precompile_file_name, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Copy input data into input memory + size_t precompile_read = fread(precompile_results_address, 1, precompile_data_size, precompile_fp); + if (precompile_read != precompile_data_size) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Initialize precompile written address + *precompile_written_address = precompile_data_size >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + else if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) + { + // Check the precompile data size is inside the proper range + if (precompile_data_size % (PRECOMPILE_FIXED_SIZE * 8) != 0) + { + printf("ERROR: Size of precompile results file (%s) is not a multiple %u * 8 B\n", precompile_file_name, PRECOMPILE_FIXED_SIZE); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Initialize precompile written address to zero + *precompile_written_address = 0; // in u64s + + // Copy in chunks of PRECOMPILE_FIXED_SIZE*8 bytes (Keccak-f state size) + uint64_t precompile_read_so_far = 0; + uint64_t data[PRECOMPILE_FIXED_SIZE]; + while (precompile_read_so_far < (uint64_t)precompile_data_size) + { + // Wait for server to read precompile results + //printf("Waiting for sem_prec_read()\n"); + result = sem_wait(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Number of bytes to read from file and write to shared memory in every loop + uint64_t bytes_to_read = sizeof(data); + + // Copy input data into input memory + size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); + if (precompile_read != bytes_to_read) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Copy data to shared memory + for (int i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); + precompile_read_so_far += 8; + } + + // Notify server that precompile results are available + *precompile_written_address = precompile_read_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + } + } + + /************/ + /* PREFIXED */ + /************/ + + else if (precompile_read_mode == PrecompileReadMode_Prefixed) + { +#define CTRL_START 0x00 +#define CTRL_END 0x01 +#define CTRL_CANCEL 0x02 +#define CTRL_ERROR 0x03 +#define HINTS_TYPE_RESULT 0x04 +#define HINTS_TYPE_ECRECOVER 0x05 +#define NUM_HINT_TYPES 0x06 + + uint64_t precompile_read_so_far = 0; + uint64_t precompile_written_so_far = 0; + + while (precompile_read_so_far < (uint64_t)precompile_data_size) + { + uint64_t data; + uint64_t bytes_to_read = sizeof(data); + + // Copy input data into input memory + size_t precompile_read = fread(&data, 1, bytes_to_read, precompile_fp); + if (precompile_read != bytes_to_read) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); + fflush(stdout); + fflush(stderr); + exit(-1); + } + precompile_read_so_far += bytes_to_read; + switch (data >> 32) + { + case CTRL_START: + //printf("Precompile CTRL_START\n"); + assert(precompile_read_so_far == 8); + break; + case CTRL_END: + //printf("Precompile CTRL_END\n"); + assert(precompile_read_so_far == precompile_data_size); + break; + // case CTRL_CANCEL: + // printf("Precompile CTRL_CANCEL\n"); + // break; + // case CTRL_ERROR: + // printf("Precompile CTRL_ERROR\n"); + // break; + case HINTS_TYPE_RESULT: + { + //printf("Precompile HINTS_TYPE_RESULT\n"); + if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) + { + // Wait for server to read precompile results + //printf("Waiting for sem_prec_read()\n"); + result = sem_wait(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + uint64_t result_length = data & 0xFFFFFFFF; + if (result_length > (precompile_data_size - precompile_read_so_far)) + { + printf("ERROR: Precompile HINTS_TYPE_RESULT length=%lu exceeds remaining file size %lu\n", result_length, precompile_data_size - precompile_read_so_far); + fflush(stdout); + fflush(stderr); + exit(-1); + } + //printf("Precompile HINTS_TYPE_RESULT result_length=%lu\n", result_length); + for (uint64_t i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &value, 8); + precompile_read_so_far += 8; + precompile_written_so_far += 8; + //printf(" Precompile result[%lu] = 0x%016lx\n", i, value); + } + + if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) + { + // Notify server that precompile results are available + *precompile_written_address = precompile_written_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + } + break; + // case HINTS_TYPE_ECRECOVER: + // { + // // Not implemented + // printf("Precompile HINTS_TYPE_ECRECOVER not implemented\n"); + // } + // break; + default: + printf("ERROR: Unknown precompile prefix type %lu\n", data >> 32); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + if (precompile_write_mode == PrecompileWriteMode_Full) + { + // Notify server that precompile results are available + *precompile_written_address = precompile_written_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + + } + + // Close the file pointer + fclose(precompile_fp); + +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + printf("client (precompile): done in %lu us\n", duration); +#endif +} + +void client_run (void) +{ + printf("client_run(): Starting client...\n"); + assert(client); + assert(!server); + + int result; + + /************************/ + /* Read input file data */ + /************************/ + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + + // Open input file + FILE * input_fp = fopen(input_file, "r"); + if (input_fp == NULL) + { + printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Get input file size + if (fseek(input_fp, 0, SEEK_END) == -1) + { + printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + long input_data_size = ftell(input_fp); + if (input_data_size == -1) + { + printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Go back to the first byte + if (fseek(input_fp, 0, SEEK_SET) == -1) + { + printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check the input data size is inside the proper range + if (input_data_size > (MAX_INPUT_SIZE - 16)) + { + printf("ERROR: Size of input file (%s) is too long (%lu)\n", input_file, input_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Open input shared memory + shmem_input_fd = shm_open(shmem_input_name, O_RDWR, 0666); + if (shmem_input_fd < 0) + { + printf("ERROR: Failed calling input shm_open(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map the shared memory object into the process address space + shmem_input_address = mmap(NULL, MAX_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmem_input_fd, 0); + if (shmem_input_address == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Write the input size in the first 64 bits + *(uint64_t *)shmem_input_address = (uint64_t)0; // free input + *(uint64_t *)(shmem_input_address + 8)= (uint64_t)input_data_size; + + // Copy input data into input memory + size_t input_read = fread(shmem_input_address + 16, 1, input_data_size, input_fp); + if (input_read != input_data_size) + { + printf("ERROR: Input read (%lu) != input file size (%lu)\n", input_read, input_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Close the file pointer + fclose(input_fp); + + // Unmap input + result = munmap(shmem_input_address, MAX_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + printf("client (input): done in %lu us\n", duration); +#endif + + } + + /*****************************/ + /* Read precompile file data */ + /*****************************/ + if (precompile_results_enabled) + { + // reset written counter + *precompile_written_address = 0; + + //client_write_precompile_results(); + } + + /*************************/ + /* Connect to the server */ + /*************************/ + + // Create socket to connect to server + int socket_fd; + socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd < 0) + { + printf("ERROR: socket() failed socket_fd=%d errno=%d=%s\n", socket_fd, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Configure server address + struct sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + + result = inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); + if (result <= 0) + { + printf("ERROR: inet_pton() failed. Invalid address/Address not supported result=%d errno=%d=%s\n", result, errno, strerror(errno)); + exit(-1); + } + + // Connect to server + result = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); + if (result < 0) + { + printf("ERROR: connect() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); + exit(-1); + } + if (verbose) printf("connect()'d to port=%u\n", port); + + // Request and response + uint64_t request[5]; + uint64_t response[5]; + + /********/ + /* Ping */ + /********/ + + gettimeofday(&start_time, NULL); + + // Prepare message to send + request[0] = TYPE_PING; + request[1] = 0; + request[2] = 0; + request[3] = 0; + request[4] = 0; + + // Send data to server + result = send(socket_fd, request, sizeof(request), 0); + if (result < 0) + { + printf("ERROR: send() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Read server response + ssize_t bytes_received = recv(socket_fd, response, sizeof(response), MSG_WAITALL); + if (bytes_received < 0) + { + printf("ERROR: recv() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (bytes_received != sizeof(response)) + { + printf("ERROR: recv() returned bytes_received=%ld errno=%d=%s\n", bytes_received, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (response[0] != TYPE_PONG) + { + printf("ERROR: recv() returned unexpected type=%lu\n", response[0]); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (response[1] != gen_method) + { + printf("ERROR: recv() returned unexpected gen_method=%lu\n", response[1]); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + printf("client (PING): done in %lu us\n", duration); + + /*****************/ + /* Minimal trace */ + /*****************/ + for (uint64_t i=0; i +#include +#include +#include +#include +#include "configuration.hpp" +#include "globals.hpp" +#include "asm_provided.hpp" + +/*******************************/ +/* ARGUMENTS AND CONFIGURATION */ +/*******************************/ + +// To be overwritten by arguments, if provided; otherwise, default values per generation method are used +uint16_t arguments_port = 0; + +// Print usage information: valid arguments +void print_usage (void) +{ + printf("Usage: ziskemuasm\n"); + printf("\t-s(server)\n"); + printf("\t-c(client)\n"); + printf("\t-i \n"); + printf("\t-p \n"); + printf("\t--gen=0|--generate_fast\n"); + printf("\t--gen=1|--generate_minimal_trace\n"); + printf("\t--gen=2|--generate_rom_histogram\n"); + printf("\t--gen=3|--generate_main_trace\n"); + printf("\t--gen=4|--generate_chunks\n"); + printf("\t--gen=6|--generate_zip\n"); + printf("\t--gen=9|--generate_mem_reads\n"); + printf("\t--gen=10|--generate_chunk_player_mem_reads\n"); + printf("\t--chunk \n"); + printf("\t--shutdown\n"); + printf("\t--mt \n"); + printf("\t-o output on\n"); + printf("\t--output_riscof output riscof on\n"); + printf("\t--silent silent on\n"); + printf("\t--shm_prefix (default: ZISK)\n"); + printf("\t-m metrics on\n"); + printf("\t-t trace on\n"); + printf("\t-tt trace_trace on\n"); + printf("\t-f(save to file)\n"); + printf("\t-a chunk_address\n"); + printf("\t-v verbose on\n"); + printf("\t-u unlock physical memory in mmap\n"); + printf("\t--share_input_shm share input shared memories\n"); + printf("\t--open_input_shm open existing input shared memories\n"); +#ifdef ASM_PRECOMPILE_CACHE + printf("\t--precompile-cache-store store precompile results in cache file\n"); + printf("\t--precompile-cache-load load precompile results from cache file\n"); +#endif + if (precompile_results_enabled) + { + printf("\t-r \n"); + } + printf("\t--redirect-output-to-file redirect output to file\n"); + printf("\t-h/--help print this\n"); +} + +// Parse main function arguments and configure global variables accordingly +void parse_arguments(int argc, char *argv[]) +{ + strcpy(shm_prefix, "ZISK"); + uint64_t number_of_selected_generation_methods = 0; + if (argc > 1) + { + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "-s") == 0) + { + server = true; + continue; + } + if (strcmp(argv[i], "-c") == 0) + { + client = true; + continue; + } + if ( (strcmp(argv[i], "--gen=0") == 0) || (strcmp(argv[i], "--generate_fast") == 0)) + { + gen_method = Fast; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=1") == 0) || (strcmp(argv[i], "--generate_minimal_trace") == 0)) + { + gen_method = MinimalTrace; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=2") == 0) || (strcmp(argv[i], "--generate_rom_histogram") == 0)) + { + gen_method = RomHistogram; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=3") == 0) || (strcmp(argv[i], "--generate_main_trace") == 0)) + { + gen_method = MainTrace; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=4") == 0) || (strcmp(argv[i], "--generate_chunks") == 0)) + { + gen_method = ChunksOnly; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=6") == 0) || (strcmp(argv[i], "--generate_zip") == 0)) + { + gen_method = Zip; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=7") == 0) || (strcmp(argv[i], "--generate_mem_op") == 0)) + { + gen_method = MemOp; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=8") == 0) || (strcmp(argv[i], "--generate_chunk_player_mt_collect_mem") == 0)) + { + gen_method = ChunkPlayerMTCollectMem; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=9") == 0) || (strcmp(argv[i], "--generate_mem_reads") == 0)) + { + gen_method = MemReads; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=10") == 0) || (strcmp(argv[i], "--generate_chunk_player_mem_reads") == 0)) + { + gen_method = ChunkPlayerMemReadsCollectMain; + number_of_selected_generation_methods++; + continue; + } + if (strcmp(argv[i], "-o") == 0) + { + output = true; + continue; + } + if (strcmp(argv[i], "--output_riscof") == 0) + { + output_riscof = true; + continue; + } + if (strcmp(argv[i], "--silent") == 0) + { + silent = true; + continue; + } + if (strcmp(argv[i], "-m") == 0) + { + metrics = true; + continue; + } + if (strcmp(argv[i], "-t") == 0) + { + trace = true; + continue; + } + if (strcmp(argv[i], "-tt") == 0) + { + trace = true; + trace_trace = true; + continue; + } + if (strcmp(argv[i], "-v") == 0) + { + verbose = true; + //emu_verbose = true; + continue; + } + if (strcmp(argv[i], "-u") == 0) + { + map_locked_flag = 0; + continue; + } + if (strcmp(argv[i], "-h") == 0) + { + print_usage(); + exit(0); + } + if (strcmp(argv[i], "--help") == 0) + { + print_usage(); + continue; + } + if (strcmp(argv[i], "-i") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -i in the last position; please provide input file after it\n"); + print_usage(); + exit(-1); + } + if (strlen(argv[i]) > 4095) + { + printf("ERROR: Detected argument -i but next argument is too long\n"); + print_usage(); + exit(-1); + } + strcpy(input_file, argv[i]); + continue; + } + if (strcmp(argv[i], "--shm_prefix") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -i in the last position; please provide shared mem prefix after it\n"); + print_usage(); + exit(-1); + } + if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) + { + printf("ERROR: Detected argument -i but next argument is too long\n"); + print_usage(); + exit(-1); + } + strcpy(shm_prefix, argv[i]); + continue; + } + if (strcmp(argv[i], "--chunk") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -c in the last position; please provide chunk number after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + chunk_mask = strtoul(argv[i], &endptr, 10); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Chunk number is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argv[i]) { + printf("ERROR: No digits found while parsing chunk number\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after chunk number: %s\n", endptr); + print_usage(); + exit(-1); + } else if (chunk_mask > MAX_CHUNK_MASK) { + printf("ERROR: Invalid chunk number: %lu\n", chunk_mask); + print_usage(); + exit(-1); + } else { + printf("Got chunk_mask= %lu\n", chunk_mask); + } + continue; + } + if (strcmp(argv[i], "--shutdown") == 0) + { + do_shutdown = true; + continue; + } + if (strcmp(argv[i], "--mt") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -mt in the last position; please provide number of MT requests after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + number_of_mt_requests = strtoul(argv[i], &endptr, 10); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Number of MT requests is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argv[i]) { + printf("ERROR: No digits found while parsing number of MT requests\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after number of MT requests: %s\n", endptr); + print_usage(); + exit(-1); + } else if (number_of_mt_requests > 1000000) { + printf("ERROR: Invalid number of MT requests: %lu\n", number_of_mt_requests); + print_usage(); + exit(-1); + } else { + printf("Got number of MT requests= %lu\n", number_of_mt_requests); + } + continue; + } + if (strcmp(argv[i], "-p") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -p in the last position; please provide port number after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + arguments_port = strtoul(argv[i], &endptr, 10); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Port number is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argv[i]) { + printf("ERROR: No digits found while parsing port number\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after port number: %s\n", endptr); + print_usage(); + exit(-1); + } else { + printf("Got port number= %u\n", arguments_port); + } + continue; + } + if (strcmp(argv[i], "-f") == 0) + { + save_to_file = true; + continue; + } + if (strcmp(argv[i], "-a") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -a in the last position; please provide chunk address after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + char * argument = argv[i]; + if ((argument[0] == '0') && (argument[1] == 'x')) argument += 2; + chunk_player_address = strtoul(argv[i], &endptr, 16); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Chunk address is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argument) { + printf("ERROR: No digits found while parsing chunk addresss\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after chunk address: %s\n", endptr); + print_usage(); + exit(-1); + } else { + printf("Got chunk address= %p\n", (void *)chunk_player_address); + } + continue; + } + if (strcmp(argv[i], "--share_input_shm") == 0) + { + share_input_shm = true; + continue; + } + if (strcmp(argv[i], "--open_input_shm") == 0) + { + open_input_shm = true; + continue; + } + if (strcmp(argv[i], "--redirect-output-to-file") == 0) + { + redirect_output_to_file = true; + continue; + } +#ifdef ASM_PRECOMPILE_CACHE + if (strcmp(argv[i], "--precompile-cache-store") == 0) + { + precompile_cache_enabled = true; + precompile_cache_store_init(); + continue; + } + if (strcmp(argv[i], "--precompile-cache-load") == 0) + { + precompile_cache_enabled = true; + precompile_cache_load_init(); + continue; + } + +#endif + if (precompile_results_enabled && (strcmp(argv[i], "-r") == 0)) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -r in the last position; please provide precompile results file after it\n"); + print_usage(); + exit(-1); + } + if (strlen(argv[i]) > 4095) + { + printf("ERROR: Detected argument -r but next argument is too long\n"); + print_usage(); + exit(-1); + } + strcpy(precompile_file_name, argv[i]); + continue; + } + printf("ERROR: parse_arguments() Unrecognized argument: %s\n", argv[i]); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } +#ifdef ASM_PRECOMPILE_CACHE + if (precompile_cache_enabled == false) + { + printf("ERROR: parse_arguments() when in precompile cache mode, you need to use an argument: either --precompile-cache-store or --precompile-cache-load\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } +#endif + + // Check that only one generation method was selected as an argument + if (number_of_selected_generation_methods != 1) + { + printf("ERROR! parse_arguments() Invalid arguments: select 1 generation method, and only one\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check that the generation method selected by the process launcher is the same as the one + // for which the assembly code was generated + uint64_t asm_gen_method = get_gen_method(); + if (asm_gen_method != gen_method) + { + printf("ERROR! parse_arguments() Inconsistency: C generation method is %u but ASM generation method is %lu\n", + gen_method, + asm_gen_method); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check server/client + if (server && client) + { + printf("ERROR! parse_arguments() Inconsistency: both server and client at the same time is not possible\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (!server && !client) + { + printf("ERROR! parse_arguments() Inconsistency: select server or client\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (precompile_results_enabled && client && (strlen(precompile_file_name) == 0)) + { + printf("ERROR! parse_arguments() when in precompile results mode, you need to provide a precompile results file using -r \n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +// Configure global variables based on generation method and other arguments +void configure (void) +{ + // Select configuration based on generation method + switch (gen_method) + { + case Fast: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_FT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_FT_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_FT_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_FT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_FT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_FT_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, ""); + strcpy(sem_chunk_done_name, ""); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_FT_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_FT"); + port = 23120; + break; + } + case MinimalTrace: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MT_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MT_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MT_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MT_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MT_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MT_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MT"); + call_chunk_done = true; + port = 23115; + break; + } + case RomHistogram: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_RH_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_RH_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_RH_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_RH_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_RH_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_RH_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_RH_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_RH_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_RH_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_RH"); + call_chunk_done = true; + port = 23116; + break; + } + case MainTrace: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MA_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MA_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MA_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MA_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MA_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MA_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MA_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MA_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MA_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MA"); + call_chunk_done = true; + port = 23118; + break; + } + case ChunksOnly: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CH_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CH_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_CH_input"); + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_CH_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_CH_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_CH_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_CH"); + call_chunk_done = true; + port = 23115; + break; + } + // case BusOp: + // { + // strcpy(shmem_input_name, "ZISKBO_input"); + // strcpy(shmem_output_name, "ZISKBO_output"); + // strcpy(sem_chunk_done_name, "ZISKBO_chunk_done"); + // chunk_done = true; + // port = 23115; + // break; + // } + case Zip: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_ZP_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_ZP_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_ZP_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_ZP_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_ZP_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_ZP_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_ZP_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_ZP_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_ZP_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_ZP"); + call_chunk_done = true; + port = 23115; + break; + } + case MemOp: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MO_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MO_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MO_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MO_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MO_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MO_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MO_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MO_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MO_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MO"); + call_chunk_done = true; + port = 23117; + break; + } + case ChunkPlayerMTCollectMem: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CM_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CM_control_output"); + strcpy(shmem_input_name, ""); + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_CM_output"); + strcpy(sem_chunk_done_name, ""); + strcpy(sem_shutdown_done_name, ""); + strcpy(shmem_mt_name, shm_prefix); + strcat(shmem_mt_name, "_MT_output"); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_CM"); + call_chunk_done = false; + port = 23119; + break; + } + case MemReads: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MT_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MT_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MT_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MT_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MT_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MT_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MT"); + call_chunk_done = true; + port = 23115; + break; + } + case ChunkPlayerMemReadsCollectMain: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CA_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CA_control_output"); + strcpy(shmem_input_name, ""); + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_CA_output"); + strcpy(sem_chunk_done_name, ""); + strcpy(sem_shutdown_done_name, ""); + strcpy(shmem_mt_name, shm_prefix); + strcat(shmem_mt_name, "_MT_output"); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_CA"); + call_chunk_done = false; + port = 23120; + break; + } + default: + { + printf("ERROR: configure() Invalid gen_method = %u\n", gen_method); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + if (precompile_results_enabled && (gen_method == ChunkPlayerMTCollectMem || gen_method == ChunkPlayerMemReadsCollectMain)) + { + printf("ERROR: configure() precompile results enabled is not compatible with generation method %u\n", gen_method); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (arguments_port != 0) + { + port = arguments_port; + } + + if (verbose) + { + printf("ziskemuasm configuration:\n"); + printf("\tgen_method=%u\n", gen_method); + printf("\tshm_prefix=%s\n", shm_prefix); + printf("\tfile_lock_name=%s\n", file_lock_name); + printf("\tlog_name=%s\n", log_name); + printf("\tport=%u\n", port); + printf("\tcall_chunk_done=%u\n", call_chunk_done); + printf("\tchunk_size=%lu\n", chunk_size); + printf("\tshmem_control_input=%s\n", shmem_control_input_name); + printf("\tshmem_control_output=%s\n", shmem_control_output_name); + printf("\tshmem_input=%s\n", shmem_input_name); + printf("\tshmem_precompile=%s\n", shmem_precompile_name); + printf("\tshmem_output=%s\n", shmem_output_name); + printf("\tshmem_mt=%s\n", shmem_mt_name); + printf("\tsem_chunk_done=%s\n", sem_chunk_done_name); + printf("\tsem_shutdown_done=%s\n", sem_shutdown_done_name); + printf("\tsem_prec_avail=%s\n", sem_prec_avail_name); + printf("\tsem_prec_read=%s\n", sem_prec_read_name); + printf("\tmap_locked_flag=%d\n", map_locked_flag); + printf("\toutput=%u\n", output); + printf("\tprecompile_results_enabled=%u\n", precompile_results_enabled); + printf("\toutput_riscof=%u\n", output_riscof); + } +} \ No newline at end of file diff --git a/emulator-asm/src/configuration.hpp b/emulator-asm/src/configuration.hpp new file mode 100644 index 000000000..8c9cc2c40 --- /dev/null +++ b/emulator-asm/src/configuration.hpp @@ -0,0 +1,7 @@ +#ifndef EMULATOR_ASM_CONFIGURATION_HPP +#define EMULATOR_ASM_CONFIGURATION_HPP + +void parse_arguments(int argc, char *argv[]); +void configure (void); + +#endif // EMULATOR_ASM_CONFIGURATION_HPP \ No newline at end of file diff --git a/emulator-asm/src/constants.hpp b/emulator-asm/src/constants.hpp new file mode 100644 index 000000000..9860a6a22 --- /dev/null +++ b/emulator-asm/src/constants.hpp @@ -0,0 +1,117 @@ +#ifndef EMULATOR_ASM_CONSTANTS_HPP +#define EMULATOR_ASM_CONSTANTS_HPP + +/***************/ +/* Definitions */ +/***************/ + +// Address map +// There definitions must match the ZisK rust code ones at core/src/mem.rs used to generate the +// assembly code, and that are used by the assembly code to access memory and generate the trace +#define ROM_ADDR (uint64_t)0x80000000 +#define ROM_SIZE (uint64_t)0x08000000 // 128MB +#define INPUT_ADDR (uint64_t)0x90000000 +#define MAX_INPUT_SIZE (uint64_t)0x08000000 // 128MB + +#define RAM_ADDR (uint64_t)0xA0000000 +#define RAM_SIZE (uint64_t)0x20000000 // 512MB +#define SYS_ADDR RAM_ADDR +#define SYS_SIZE (uint64_t)0x10000 +#define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) + +#ifdef TRACE_TARGET_MO + #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ + #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ +#elif defined(TRACE_TARGET_RH) + #define TRACE_INITIAL_SIZE (uint64_t)0x004000000 /* 64MB */ + #define TRACE_DELTA_SIZE (uint64_t)0x004000000 /* 64MB */ +#else + #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ + #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ +#endif + +#define TRACE_ADDR (uint64_t)0xd0000000 +#define TRACE_MAX_SIZE (uint64_t)0x1000000000 // 64GB +#define TRACE_NUMBER_OF_CHUNKS (((TRACE_MAX_SIZE - TRACE_INITIAL_SIZE) / TRACE_DELTA_SIZE) + 1) +#define TRACE_SIZE_GRANULARITY (1014*1014) // ROM histogram trace size is round up to a multiple of this granularity + +// Control input and output shared memory configuration. +// Control input is used to tell the assembly code how many precompile result u64 fields have been +// written by the client. Control output is used to tell the client how many precompile result u64 +// fields have been read by the assembly code, so the client can know when it can write new +// precompile results. Assembly code waits when the number of read fields is not lower than the +// number of written fields, and client waits when the number of written fields would exceed the +// number of read fields plus the available precompile shared memory size, which is a circular buffer +#define CONTROL_INPUT_ADDR (uint64_t)0x70000000 +#define CONTROL_INPUT_SIZE (uint64_t)0x1000 // 4kB +#define CONTROL_OUTPUT_ADDR (uint64_t)0x70001000 +#define CONTROL_OUTPUT_SIZE (uint64_t)0x1000 // 4kB +#define CONTROL_RETRY_DELAY_US 1000 // 1ms +#define CONTROL_NUMBER_OF_RETRIES 1000 // 1s max total + +// Maximum number of steps to execute, used by the client to limit the execution steps of the +// assembly code. This limit is set by the ZisK PIL constraints. +#define MAX_STEPS (1ULL << 36) + +// Assembly service request/response types +// Only the methods supported by the configured generation method will be implemented by the server, +// e.g. gen_method=1 => PING, MT and SHUTDOWN; the rest will fail with an error response. +#define TYPE_PING 1 // Ping +#define TYPE_PONG 2 +#define TYPE_MT_REQUEST 3 // Minimal trace +#define TYPE_MT_RESPONSE 4 +#define TYPE_RH_REQUEST 5 // ROM histogram +#define TYPE_RH_RESPONSE 6 +#define TYPE_MO_REQUEST 7 // Memory opcode +#define TYPE_MO_RESPONSE 8 +#define TYPE_MA_REQUEST 9 // Main packed trace +#define TYPE_MA_RESPONSE 10 +#define TYPE_CM_REQUEST 11 // Collect memory trace +#define TYPE_CM_RESPONSE 12 +#define TYPE_FA_REQUEST 13 // Fast mode, do not generate any trace +#define TYPE_FA_RESPONSE 14 +#define TYPE_MR_REQUEST 15 // Mem reads +#define TYPE_MR_RESPONSE 16 +#define TYPE_CA_REQUEST 17 // Collect main trace +#define TYPE_CA_RESPONSE 18 +#define TYPE_SD_REQUEST 1000000 // Shutdown +#define TYPE_SD_RESPONSE 1000001 + +// Server IP address, used by the client to connect to the server +#define SERVER_IP "127.0.0.1" // Change to your server IP; otherwise use localhost IP address + +// Chunk size used in generation methods that generate a trace chunk at every N steps, e.g. gen_method=1 or gen_method=7. +// It must be a power of two, and it is used to calculate the trace address threshold at which the next chunk must be mapped, +// to avoid reaching the end of the currently mapped trace memory. +#define CHUNK_SIZE (1ULL << 18) + +// Maximum trace chunk size, used to determine when the trace address is close to the end of the +// currently mapped trace memory and the next chunk must be mapped. It is calculated based on the +// maximum number of bytes that can be generated in a chunk +// Worst case: every chunk instruction is a keccak operation, with an input data of 200 bytes +// (let's use 256 bytes to be safe), and the trace includes the access to 2 source registers, 2 +// destination registers and 3 memory addresses (e.g. for a keccak operation with 3 memory operands), +// which are the maximum number of registers and memory addresses that can be accessed by a chunk +// instruction, according to the ZisK assembly code generation configuration. + +#define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) +#define MAX_TRACE_CHUNK_INFO ((44*8) + 32) +#define MAX_BYTES_DIRECT_MTRACE 256 +#define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) +#define MAX_CHUNK_TRACE_SIZE ((CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) + +// Maximum precompile results share memory size +// It is a circular buffer +#define MAX_PRECOMPILE_SIZE (uint64_t)0x400000 // 4MB + +// Maximum chunk mask for zip generation method, which indicates which chunks are included in the trace, +// and must be between 0 and 7 (inclusive), as it is used to generate a mask of 8 bits where each +// bit indicates if the corresponding chunk is included in the trace or not. +#define MAX_CHUNK_MASK 7 + +// Maximum length of the shared memory prefix, e.g. "ZISK_12345" +// This prefix is used to generate the names of the shared memories and semaphores used for +// communication and synchronization between the server and the client, +#define MAX_SHM_PREFIX_LENGTH 64 + +#endif // EMULATOR_ASM_CONSTANTS_HPP \ No newline at end of file diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index baf58a523..f61338ae7 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -612,9 +612,9 @@ extern int _opcode_blake2(uint64_t * address) #endif #ifdef DEBUG #ifdef ASM_CALL_METRICS - if (emu_verbose) printf("opcode_blake2() calling blake2b() counter=%lu address=%08lx\n", asm_call_metrics.blake2_counter, address); + if (emu_verbose) printf("opcode_blake2() calling blake2b() counter=%lu address=%p\n", asm_call_metrics.blake2_counter, address); #else - if (emu_verbose) printf("opcode_blake2() calling blake2b() address=%08lx\n", address); + if (emu_verbose) printf("opcode_blake2() calling blake2b() address=%p\n", address); #endif #endif diff --git a/emulator-asm/src/emu.hpp b/emulator-asm/src/emu.hpp index cae983a9c..057966094 100644 --- a/emulator-asm/src/emu.hpp +++ b/emulator-asm/src/emu.hpp @@ -2,6 +2,9 @@ #define EMU_ASM_HPP #include +#include + +uint64_t TimeDiff(const struct timeval startTime, const struct timeval endTime); #ifdef DEBUG extern bool emu_verbose; diff --git a/emulator-asm/src/globals.c b/emulator-asm/src/globals.c new file mode 100644 index 000000000..adb546b41 --- /dev/null +++ b/emulator-asm/src/globals.c @@ -0,0 +1,136 @@ +#define _GNU_SOURCE +#include +#include "constants.hpp" +#include "globals.hpp" + +// Configuration globals, set by arguments +bool output = false; +bool output_riscof = false; +bool silent = false; +bool metrics = false; +bool trace = false; +bool trace_trace = false; +bool verbose = false; +bool save_to_file = false; +bool share_input_shm = false; +bool open_input_shm = false; +char input_file[4096] = {0}; +bool redirect_output_to_file = false; +bool server = false; +bool client = false; +char shm_prefix[MAX_SHM_PREFIX_LENGTH] = {0}; +int map_locked_flag = MAP_LOCKED; +uint64_t chunk_mask = 0x0; +bool do_shutdown = false; +uint64_t number_of_mt_requests = 1; +uint16_t port = 0; +uint64_t chunk_player_address = 0; +char precompile_file_name[4096] = {0}; +char shmem_control_input_name[128] = {0}; +char shmem_control_output_name[128] = {0}; +char shmem_input_name[128] = {0}; +char shmem_output_name[128] = {0}; +char shmem_mt_name[128] = {0}; +char shmem_precompile_name[128] = {0}; +char sem_prec_avail_name[128] = {0}; +char sem_prec_read_name[128] = {0}; +char sem_chunk_done_name[128] = {0}; +char sem_shutdown_done_name[128] = {0}; +char file_lock_name[128] = {0}; +char log_name[128] = {0}; +bool call_chunk_done = false; + +// Configuration set by assembly code, accessed by C +bool precompile_results_enabled = false; + +// Default generation method, can be overridden by the --gen argument +GenMethod gen_method = Fast; + +// To be used when calculating partial durations +// Time measurements cannot be overlapped +struct timeval start_time; +struct timeval stop_time; +uint64_t duration; + +/*****************/ +/* SHARED MEMORY */ +/*****************/ + +// Input shared memory +int shmem_input_fd = -1; +uint64_t shmem_input_size = 0; +void * shmem_input_address = NULL; + +// Output trace shared memory +int shmem_output_fd = -1; + +// Input MT trace shared memory +int shmem_mt_fd = -1; + +// Chunk done semaphore: notifies the caller when a new chunk has been processed +sem_t * sem_chunk_done = NULL; + +// Shutdown done semaphore: notifies the caller when a shutdown has been processed +sem_t * sem_shutdown_done = NULL; + +/**************************/ +/* PRECOMPILE AND CONTROL */ +/**************************/ + +uint64_t * precompile_results_address = NULL; + +// Precompile results shared memory +int shmem_precompile_fd = -1; +uint64_t shmem_precompile_size = 0; +void * shmem_precompile_address = NULL; + +// Precompile results semaphores +sem_t * sem_prec_avail = NULL; +sem_t * sem_prec_read = NULL; + +// Control input shared memory +int shmem_control_input_fd = -1; +uint64_t * shmem_control_input_address = NULL; +volatile uint64_t * precompile_written_address = NULL; +volatile uint64_t * precompile_exit_address = NULL; + +// Control output shared memory +int shmem_control_output_fd = -1; +uint64_t * shmem_control_output_address = NULL; +volatile uint64_t * precompile_read_address = NULL; + +/**************/ +/* TRACE SIZE */ +/**************/ + +uint64_t initial_trace_size = TRACE_INITIAL_SIZE; +uint64_t trace_address = TRACE_ADDR; +uint64_t trace_size = TRACE_INITIAL_SIZE; +uint64_t trace_used_size = 0; +uint64_t trace_address_threshold = TRACE_ADDR + TRACE_INITIAL_SIZE - MAX_CHUNK_TRACE_SIZE; + +// To be used when calculating the assembly duration +uint64_t assembly_duration; + +// Counters used in functions called from assembly code +uint64_t realloc_counter = 0; +uint64_t wait_counter = 0; +uint64_t print_pc_counter = 0; + +// Chunk player globals +uint64_t chunk_player_mt_size = TRACE_INITIAL_SIZE; + +// Maximum number of steps to execute, used by the client to limit the execution steps of the +// assembly code. +uint64_t max_steps = (1ULL << 32); + +// Pointers to the input, RAM, ROM and trace memory, used by both C and assembly code to access these memories +uint64_t * pInputTrace = (uint64_t *)TRACE_ADDR; // Used for trace consumption, i.e. chunk player +uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; // Used for trace generation, i.e. assembly code writes the trace to this address, and client reads it from this address + +/**************/ +/* CHUNK SIZE */ +/**************/ + +uint64_t chunk_size = CHUNK_SIZE; +uint64_t chunk_size_mask = CHUNK_SIZE - 1; \ No newline at end of file diff --git a/emulator-asm/src/globals.hpp b/emulator-asm/src/globals.hpp new file mode 100644 index 000000000..f66e0044c --- /dev/null +++ b/emulator-asm/src/globals.hpp @@ -0,0 +1,166 @@ +#ifndef EMULATOR_ASM_GLOBALS_HPP +#define EMULATOR_ASM_GLOBALS_HPP + +#include +#include +#include +#include +#include "constants.hpp" + +// Configuration globals, set by arguments +extern bool output; +extern bool output_riscof; +extern bool silent; +extern bool metrics; +extern bool trace; +extern bool trace_trace; +extern bool verbose; +extern bool save_to_file; +extern bool share_input_shm; // Shares input shared memories: input, precompile results and control input, using a common name +extern bool open_input_shm; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) +extern char input_file[4096]; +extern bool redirect_output_to_file; +extern bool server; // Indicates that this process is a server +extern bool client; // Indicates that this process is a client (used for testing the server) +extern char shm_prefix[MAX_SHM_PREFIX_LENGTH]; // Shared memory prefix +extern int map_locked_flag; // Flag used in mmap to indicate if the physical memory is locked in RAM (MAP_LOCKED) or can be swapped (0). By default it is locked, but it can be unlocked with the -u argument, which can be useful for testing and debugging purposes, e.g. to allow core dumps when the assembly code crashes +extern uint64_t chunk_mask; // ZIP: 0, 1, 2, 3, 4, 5, 6 or 7 +extern bool do_shutdown; // If true, the client will perform a shutdown request to the server when done +extern uint64_t number_of_mt_requests; // Loop to send this number of minimal trace requests +extern uint16_t port; // Service TCP port +extern uint64_t chunk_player_address; // Chunk player address, used for generation methods that use the chunk player, i.e. gen_method=8 or gen_method=10 +extern char precompile_file_name[4096]; // Precompile results file name (used by client) +extern char shmem_control_input_name[128]; +extern char shmem_control_output_name[128]; +extern char shmem_input_name[128]; +extern char shmem_output_name[128]; +extern char shmem_mt_name[128]; +extern char shmem_precompile_name[128]; +extern char sem_prec_avail_name[128]; +extern char sem_prec_read_name[128]; +extern char sem_chunk_done_name[128]; +extern char sem_shutdown_done_name[128]; +extern char file_lock_name[128]; +extern char log_name[128]; +extern bool call_chunk_done; + +// Configuration set by assembly code, accessed by C +extern bool precompile_results_enabled; + +/*********************/ +/* Generation method */ +/*********************/ + +// Specifies how the assembly code generates the trace, and what information it includes. +// It is specified with the mandatory argument --gen= +// It must match the value returned by the assembly function get_gen_method() +// The enum names are equivalent to the rust ones defined in core/src/riscv2zisk.rs as AsmGenerationMethod +// ZisK uses generation methods 1 (minimal trace), 2 (ROM histogram) and 7 (memory operations) +// but the rest of methods can be used for testing and debugging purposes +typedef enum { + Fast = 0, + MinimalTrace = 1, + RomHistogram = 2, + MainTrace = 3, + ChunksOnly = 4, + //BusOp = 5, + Zip = 6, + MemOp = 7, + ChunkPlayerMTCollectMem = 8, + MemReads = 9, + ChunkPlayerMemReadsCollectMain = 10, +} GenMethod; + +// Default generation method, can be overridden by the --gen argument +extern GenMethod gen_method; + +// To be used when calculating partial durations +// Time measurements cannot be overlapped +extern struct timeval start_time; +extern struct timeval stop_time; +extern uint64_t duration; + +/*****************/ +/* SHARED MEMORY */ +/*****************/ + +// Input shared memory +extern int shmem_input_fd; +extern uint64_t shmem_input_size; +extern void * shmem_input_address; + +// Output trace shared memory +extern int shmem_output_fd; + +// Input MT trace shared memory +extern int shmem_mt_fd; + +// Chunk done semaphore: notifies the caller when a new chunk has been processed +extern sem_t * sem_chunk_done; + +// Shutdown done semaphore: notifies the caller when a shutdown has been processed +extern sem_t * sem_shutdown_done; + +/**************************/ +/* PRECOMPILE AND CONTROL */ +/**************************/ + +extern uint64_t * precompile_results_address; + +// Precompile results shared memory +extern int shmem_precompile_fd; +extern uint64_t shmem_precompile_size; +extern void * shmem_precompile_address; + +// Precompile results semaphores +extern sem_t * sem_prec_avail; +extern sem_t * sem_prec_read; + +// Control input shared memory +extern int shmem_control_input_fd; +extern uint64_t * shmem_control_input_address; +extern volatile uint64_t * precompile_written_address; +extern volatile uint64_t * precompile_exit_address; + +// Control output shared memory +extern int shmem_control_output_fd; +extern uint64_t * shmem_control_output_address; +extern volatile uint64_t * precompile_read_address; + +/**************/ +/* TRACE SIZE */ +/**************/ + +extern uint64_t initial_trace_size; +extern uint64_t trace_address; +extern uint64_t trace_size; +extern uint64_t trace_used_size; +extern uint64_t trace_address_threshold; + +// To be used when calculating the assembly duration +extern uint64_t assembly_duration; + +// Counters used in functions called from assembly code +extern uint64_t realloc_counter; +extern uint64_t wait_counter; +extern uint64_t print_pc_counter; + +// Chunk player globals +extern uint64_t chunk_player_mt_size; + +// Maximum number of steps to execute, used by the client to limit the execution steps of the +// assembly code. +extern uint64_t max_steps; + +// Pointers to the input, RAM, ROM and trace memory, used by both C and assembly code to access these memories +extern uint64_t * pInputTrace; // Used for trace consumption, i.e. chunk player +extern uint64_t * pOutputTrace; // Used for trace generation, i.e. assembly code writes the trace to this address, and client reads it from this address + +/**************/ +/* CHUNK SIZE */ +/**************/ + +extern uint64_t chunk_size; +extern uint64_t chunk_size_mask; + +#endif // EMULATOR_ASM_GLOBALS_HPP \ No newline at end of file diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 154efe8d4..fe1708be2 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -19,191 +19,14 @@ #include #include #include +#include "constants.hpp" #include "emu.hpp" - -/**************************/ -/* Assembly-provided code */ -/**************************/ - -// This is the emulator assembly code start function, which will execute the code in the ROM until -// it ends, and generate the trace in the output trace memory. -// It is called from C to start the execution of the assembly code. -void emulator_start(void); - -// These functions are implemented in assembly and provide access to configuration parameters used -// to generate the assembly code, and that in some cases must match the C main program configuration -uint64_t get_max_bios_pc(void); -uint64_t get_max_program_pc(void); -uint64_t get_gen_method(void); // Must match the C main program provided argument -uint64_t get_precompile_results(void); - -// These variables are updated by the assembly code to provide information about the execution -// status and trace generation, accessed by C to generate the response to the client -extern uint64_t MEM_STEP; // Current step, i.e. number of executed instructions, updated by assembly at every step or at the end of every chunk, depending on the generation method -extern uint64_t MEM_END; // Indicates the end of execution -extern uint64_t MEM_ERROR; // Indicates an error during execution -extern uint64_t MEM_TRACE_ADDRESS; // Address of the trace memory -extern uint64_t MEM_CHUNK_ADDRESS; // Address of the current chunk -extern uint64_t MEM_CHUNK_START_STEP; // Step at which the current chunk started - -/***************/ -/* Definitions */ -/***************/ - -// Address map -// There definitions must match the ZisK rust code ones at core/src/mem.rs used to generate the -// assembly code, and that are used by the assembly code to access memory and generate the trace -#define ROM_ADDR (uint64_t)0x80000000 -#define ROM_SIZE (uint64_t)0x08000000 // 128MB -#define INPUT_ADDR (uint64_t)0x90000000 -#define MAX_INPUT_SIZE (uint64_t)0x08000000 // 128MB - -#define RAM_ADDR (uint64_t)0xA0000000 -#define RAM_SIZE (uint64_t)0x20000000 // 512MB -#define SYS_ADDR RAM_ADDR -#define SYS_SIZE (uint64_t)0x10000 -#define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) - -#ifdef TRACE_TARGET_MO - #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ - #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ -#elif defined(TRACE_TARGET_RH) - #define TRACE_INITIAL_SIZE (uint64_t)0x004000000 /* 64MB */ - #define TRACE_DELTA_SIZE (uint64_t)0x004000000 /* 64MB */ -#else - #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ - #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ -#endif - -#define TRACE_ADDR (uint64_t)0xd0000000 -#define TRACE_MAX_SIZE (uint64_t)0x1000000000 // 64GB -#define TRACE_NUMBER_OF_CHUNKS (((TRACE_MAX_SIZE - TRACE_INITIAL_SIZE) / TRACE_DELTA_SIZE) + 1) -#define TRACE_SIZE_GRANULARITY (1014*1014) // ROM histogram trace size is round up to a multiple of this granularity - -// Worst case: every chunk instruction is a keccak operation, with an input data of 256 bytes - -// #if defined(TRACE_TARGET_MO) -// #define MAX_MTRACE_REGS_ACCESS_SIZE (3 * 8) -// #define MAX_BYTES_DIRECT_MTRACE 80 // PRE/POST = ((1 PRE + 2 SRC + 1 WR DST) * 2 + BLOCK_READ + BLOCK_WRITE) * 8 -// #define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) -// #define MAX_CHUNK_TRACE_SIZE (CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) -// #elif defined(TRACE_TARGET_RH) -// #define MAX_CHUNK_TRACE_SIZE 0 -// #else -// #define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) -// #define MAX_TRACE_CHUNK_INFO ((44*8) + 32) -// #define MAX_BYTES_DIRECT_MTRACE 256 -// #define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) -// #define MAX_CHUNK_TRACE_SIZE ((CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) -// #endif -// uint64_t trace_resize_request = 0; -// uint64_t trace_address_threshold = TRACE_ADDR + INITIAL_TRACE_SIZE - MAX_CHUNK_TRACE_SIZE; - -// Control input and output shared memory configuration. -// Control input is used to tell the assembly code how many precompile result u64 fields have been -// written by the client. Control output is used to tell the client how many precompile result u64 -// fields have been read by the assembly code, so the client can know when it can write new -// precompile results. Assembly code waits when the number of read fields is not lower than the -// number of written fields, and client waits when the number of written fields would exceed the -// number of read fields plus the available precompile shared memory size, which is a circular buffer -#define CONTROL_INPUT_ADDR (uint64_t)0x70000000 -#define CONTROL_INPUT_SIZE (uint64_t)0x1000 // 4kB -#define CONTROL_OUTPUT_ADDR (uint64_t)0x70001000 -#define CONTROL_OUTPUT_SIZE (uint64_t)0x1000 // 4kB -#define CONTROL_RETRY_DELAY_US 1000 // 1ms -#define CONTROL_NUMBER_OF_RETRIES 1000 // 1s max total - -// Maximum number of steps to execute, used by the client to limit the execution steps of the -// assembly code. This limit is set by the ZisK PIL constraints. -#define MAX_STEPS (1ULL << 36) - -// Assembly service request/response types -// Only the methods supported by the configured generation method will be implemented by the server, -// e.g. gen_method=1 => PING, MT and SHUTDOWN; the rest will fail with an error response. -#define TYPE_PING 1 // Ping -#define TYPE_PONG 2 -#define TYPE_MT_REQUEST 3 // Minimal trace -#define TYPE_MT_RESPONSE 4 -#define TYPE_RH_REQUEST 5 // ROM histogram -#define TYPE_RH_RESPONSE 6 -#define TYPE_MO_REQUEST 7 // Memory opcode -#define TYPE_MO_RESPONSE 8 -#define TYPE_MA_REQUEST 9 // Main packed trace -#define TYPE_MA_RESPONSE 10 -#define TYPE_CM_REQUEST 11 // Collect memory trace -#define TYPE_CM_RESPONSE 12 -#define TYPE_FA_REQUEST 13 // Fast mode, do not generate any trace -#define TYPE_FA_RESPONSE 14 -#define TYPE_MR_REQUEST 15 // Mem reads -#define TYPE_MR_RESPONSE 16 -#define TYPE_CA_REQUEST 17 // Collect main trace -#define TYPE_CA_RESPONSE 18 -#define TYPE_SD_REQUEST 1000000 // Shutdown -#define TYPE_SD_RESPONSE 1000001 - -// Server IP address, used by the client to connect to the server -#define SERVER_IP "127.0.0.1" // Change to your server IP; otherwise use localhost IP address - -// Chunk size used in generation methods that generate a trace chunk at every N steps, e.g. gen_method=1 or gen_method=7. -// It must be a power of two, and it is used to calculate the trace address threshold at which the next chunk must be mapped, -// to avoid reaching the end of the currently mapped trace memory. -#define CHUNK_SIZE (1ULL << 18) - -// Maximum trace chunk size, used to determine when the trace address is close to the end of the -// currently mapped trace memory and the next chunk must be mapped. It is calculated based on the -// maximum number of bytes that can be generated in a chunk -// Worst case: every chunk instruction is a keccak operation, with an input data of 200 bytes -// (let's use 256 bytes to be safe), and the trace includes the access to 2 source registers, 2 -// destination registers and 3 memory addresses (e.g. for a keccak operation with 3 memory operands), -// which are the maximum number of registers and memory addresses that can be accessed by a chunk -// instruction, according to the ZisK assembly code generation configuration. - -#define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) -#define MAX_TRACE_CHUNK_INFO ((44*8) + 32) -#define MAX_BYTES_DIRECT_MTRACE 256 -#define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) -#define MAX_CHUNK_TRACE_SIZE ((CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) - -// Maximum precompile results share memory size -// It is a circular buffer -#define MAX_PRECOMPILE_SIZE (uint64_t)0x400000 // 4MB - -// Maximum chunk mask for zip generation method, which indicates which chunks are included in the trace, -// and must be between 0 and 7 (inclusive), as it is used to generate a mask of 8 bits where each -// bit indicates if the corresponding chunk is included in the trace or not. -#define MAX_CHUNK_MASK 7 - -// Maximum length of the shared memory prefix, e.g. "ZISK_12345" -// This prefix is used to generate the names of the shared memories and semaphores used for -// communication and synchronization between the server and the client, -#define MAX_SHM_PREFIX_LENGTH 64 - -/*********************/ -/* Generation method */ -/*********************/ - -// Specifies how the assembly code generates the trace, and what information it includes. -// It is specified with the mandatory argument --gen= -// It must match the value returned by the assembly function get_gen_method() -// The enum names are equivalent to the rust ones defined in core/src/riscv2zisk.rs as AsmGenerationMethod -// ZisK uses generation methods 1 (minimal trace), 2 (ROM histogram) and 7 (memory operations) -// but the rest of methods can be used for testing and debugging purposes -typedef enum { - Fast = 0, - MinimalTrace = 1, - RomHistogram = 2, - MainTrace = 3, - ChunksOnly = 4, - //BusOp = 5, - Zip = 6, - MemOp = 7, - ChunkPlayerMTCollectMem = 8, - MemReads = 9, - ChunkPlayerMemReadsCollectMain = 10, -} GenMethod; - -// Default generation method, can be overridden by the --gen argument -GenMethod gen_method = Fast; +#include "asm_provided.hpp" +#include "globals.hpp" +#include "configuration.hpp" +#include "server.hpp" +#include "client.hpp" +#include "trace.hpp" // Returns the acronym of the generation method, used for logging and file naming const char * gen_method_acronym(GenMethod method) @@ -225,44 +48,11 @@ const char * gen_method_acronym(GenMethod method) } } -// Pointers to the input, RAM, ROM and trace memory, used by both C and assembly code to access these memories -uint64_t * pInputTrace = (uint64_t *)TRACE_ADDR; // Used for trace consumption, i.e. chunk player -uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; // Used for trace generation, i.e. assembly code writes the trace to this address, and client reads it from this address - -// Service TCP parameters -uint16_t port = 0; -uint16_t arguments_port = 0; - -// Type of execution -bool server = false; -bool client = false; -bool call_chunk_done = false; -bool do_shutdown = false; // If true, the client will perform a shutdown request to the server when done -uint64_t number_of_mt_requests = 1; // Loop to send this number of minimal trace requests - -// To be used when calculating partial durations -// Time measurements cannot be overlapped -struct timeval start_time; -struct timeval stop_time; -uint64_t duration; - // To be used when calculating total duration struct timeval total_start_time; struct timeval total_stop_time; uint64_t total_duration; -// To be used when calculating the assembly duration -uint64_t assembly_duration; - -// Counters used in functions called from assembly code -uint64_t realloc_counter = 0; -uint64_t wait_counter = 0; -uint64_t print_pc_counter = 0; - -// Chunk player globals -uint64_t chunk_player_address = 0; -uint64_t chunk_player_mt_size = TRACE_INITIAL_SIZE; - // Checks if a number is a power of two, used to validate the max steps and chunk size provided by the client bool is_power_of_two (uint64_t number) { @@ -273,10 +63,6 @@ bool is_power_of_two (uint64_t number) /* MAX STEPS */ /*************/ -// Maximum number of steps to execute, used by the client to limit the execution steps of the -// assembly code. -uint64_t max_steps = (1ULL << 32); - // Sets the maximum number of steps provided by the client in the request void set_max_steps (uint64_t new_max_steps) { @@ -290,39 +76,10 @@ void set_max_steps (uint64_t new_max_steps) max_steps = new_max_steps; } -uint64_t trace_address_threshold = TRACE_ADDR + TRACE_INITIAL_SIZE - MAX_CHUNK_TRACE_SIZE; - -int map_locked_flag = MAP_LOCKED; - - -/**************/ -/* TRACE SIZE */ -/**************/ - -uint64_t initial_trace_size = TRACE_INITIAL_SIZE; -uint64_t trace_address = TRACE_ADDR; -uint64_t trace_size = TRACE_INITIAL_SIZE; -uint64_t trace_used_size = 0; - -void set_trace_size (uint64_t new_trace_size) -{ - // Update trace global variables - // printf("%s trace resize (trace_resize_request: %ld): %ld MB => %ld MB\n", log_name, trace_resize_request, trace_size >> 20, new_trace_size >> 20); - - // trace_resize_request = 0; - - trace_size = new_trace_size; - trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; - pOutputTrace[2] = trace_size; -} - /**************/ /* CHUNK SIZE */ /**************/ -uint64_t chunk_size = CHUNK_SIZE; -uint64_t chunk_size_mask = CHUNK_SIZE - 1; - void set_chunk_size (uint64_t new_chunk_size) { if (!is_power_of_two(new_chunk_size)) @@ -337,350 +94,25 @@ void set_chunk_size (uint64_t new_chunk_size) trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; } -void parse_arguments(int argc, char *argv[]); -uint64_t TimeDiff(const struct timeval startTime, const struct timeval endTime); -void configure (void); -void server_setup (void); -void server_reset_fast (void); -void server_reset_slow (void); -void server_reset_trace (void); -void server_run (void); -void server_cleanup (void); -void client_setup (void); -void client_run (void); -void client_cleanup (void); -void _chunk_done(void); -void log_minimal_trace(void); -void log_histogram(void); -void log_main_trace(void); -void log_mem_trace(void); -void log_mem_op(void); -void save_mem_op_to_files(void); -void log_chunk_player_main_trace(void); -int recv_all_with_timeout (int sockfd, void *buffer, size_t length, int flags, int timeout_sec); -void file_lock(void); - -// Configuration globals, set by arguments -bool output = false; -bool output_riscof = false; -bool silent = false; -bool metrics = false; -bool trace = false; -bool trace_trace = false; -bool verbose = false; -bool save_to_file = false; -bool share_input_shm = false; // Shares input shared memories: input, precompile results and control input, using a common name -bool open_input_shm = false; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) -char input_file[4096] = {0}; -bool redirect_output_to_file = false; +//#define USE_FILE_LOCK -// ROM histogram -uint64_t histogram_size = 0; -uint64_t bios_size = 0; -uint64_t program_size = 0; +#ifdef USE_FILE_LOCK -// Zip -uint64_t chunk_mask = 0x0; // 0, 1, 2, 3, 4, 5, 6 or 7 - -/*****************/ -/* SHARED MEMORY */ -/*****************/ - -// Shared memory prefix -char shm_prefix[MAX_SHM_PREFIX_LENGTH]; - -// Input shared memory -char shmem_input_name[128]; -int shmem_input_fd = -1; -uint64_t shmem_input_size = 0; -void * shmem_input_address = NULL; - -// Output trace shared memory -char shmem_output_name[128]; -int shmem_output_fd = -1; - -// Input MT trace shared memory -char shmem_mt_name[128]; -int shmem_mt_fd = -1; - -// Chunk done semaphore: notifies the caller when a new chunk has been processed -char sem_chunk_done_name[128]; -sem_t * sem_chunk_done = NULL; - -// Shutdown done semaphore: notifies the caller when a shutdown has been processed -char sem_shutdown_done_name[128]; -sem_t * sem_shutdown_done = NULL; +void file_lock(void); // File lock name, used to lock a file that indicates that the assembly emulator process is running, // to prevent multiple instances of the server from running at the same time. -char file_lock_name[128]; int file_lock_fd = -1; -// Log name -char log_name[128]; +#endif // USE_FILE_LOCK // Process id int process_id = 0; -/**************************/ -/* PRECOMPILE AND CONTROL */ -/**************************/ - #ifdef ASM_PRECOMPILE_CACHE bool precompile_cache_enabled = false; #endif -bool precompile_results_enabled = false; -uint64_t * precompile_results_address = NULL; - -// Precompile results shared memory -char shmem_precompile_name[128]; -int shmem_precompile_fd = -1; -uint64_t shmem_precompile_size = 0; -void * shmem_precompile_address = NULL; - -// Precompile results semaphores -char sem_prec_avail_name[128]; -sem_t * sem_prec_avail = NULL; -char sem_prec_read_name[128]; -sem_t * sem_prec_read = NULL; - -// Precompile results file name (used by client) -char precompile_file_name[4096] = {0}; - -// Control input shared memory -char shmem_control_input_name[128]; -int shmem_control_input_fd = -1; -uint64_t * shmem_control_input_address = NULL; -volatile uint64_t * precompile_written_address = NULL; -volatile uint64_t * precompile_exit_address = NULL; - -// Control output shared memory -char shmem_control_output_name[128]; -int shmem_control_output_fd = -1; -uint64_t * shmem_control_output_address = NULL; -volatile uint64_t * precompile_read_address = NULL; - -/********************/ -/* TRACE ALLOCATION */ -/********************/ - -void trace_virtual_alloc (void) -{ - void * addr = mmap((void *)TRACE_ADDR, TRACE_MAX_SIZE, PROT_NONE, MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (addr != (void *)TRACE_ADDR) - { - printf("ERROR: trace_virtual_alloc() failed to reserve trace memory at address 0x%lx\n", TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -uint64_t next_chunk_id = 0; // Next trace chunk id to be mapped, starting from 0 -int trace_chunk_fd[TRACE_NUMBER_OF_CHUNKS]; // File descriptors for each chunk -uint64_t trace_total_mapped_size = 0; // Total mapped trace size - -void * trace_get_chunk_address (uint64_t chunk_id) -{ - assert(gen_method != RomHistogram || chunk_id == 0); - - if (chunk_id == 0) - { - return (void *)TRACE_ADDR; - } - else - { - return (void *)(TRACE_ADDR + TRACE_INITIAL_SIZE + ((chunk_id - 1) * TRACE_DELTA_SIZE)); - } -} - -uint64_t trace_get_chunk_size (uint64_t chunk_id) -{ - if (gen_method == RomHistogram) { - assert(chunk_id == 0); - return trace_size; - } - - if (chunk_id == 0) - { - return TRACE_INITIAL_SIZE; - } - else - { - return TRACE_DELTA_SIZE; - } -} - -void trace_generate_shmem_chunk_name(char * shmem_chunk_name, size_t shmem_chunk_name_size, uint64_t chunk_id) -{ - int result = snprintf(shmem_chunk_name, shmem_chunk_name_size, "%s_%lu", shmem_output_name, chunk_id); - if (result < 0 || result >= (int)shmem_chunk_name_size) - { - printf("ERROR: trace_generate_shmem_chunk_name() failed to create chunk shared memory name\n"); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -void trace_cleanup (void) -{ - // Unmap all mapped chunks - for (uint64_t chunk_id = 0; chunk_id < next_chunk_id; chunk_id++) - { - uint64_t chunk_size = trace_get_chunk_size(chunk_id); - void * chunk_address = trace_get_chunk_address(chunk_id); - int result = munmap(chunk_address, chunk_size); - if (result != 0) - { - printf("ERROR: trace_cleanup() failed calling munmap() chunk id=%lu size=%lu B address=0x%lx errno=%d=%s\n", chunk_id, chunk_size, (uint64_t)chunk_address, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Close the chunk shared memory file descriptor - close(trace_chunk_fd[chunk_id]); - trace_chunk_fd[chunk_id] = -1; - - // Build the chunk shared memory name - char shmem_chunk_name[128]; - trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); - - // Make sure the chunk shared memory is deleted - shm_unlink(shmem_chunk_name); - } - - // Reset next chunk id - next_chunk_id = 0; -} - -void trace_preventive_cleanup (void) -{ - // Unmap all mapped chunks - for (uint64_t chunk_id = 0; chunk_id < TRACE_NUMBER_OF_CHUNKS; chunk_id++) - { - // Build the chunk shared memory name - char shmem_chunk_name[128]; - trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); - - // Make sure the chunk shared memory is deleted - int result = shm_unlink(shmem_chunk_name); - if (result != 0) - { - break; - } - if (verbose) printf("trace_preventive_cleanup() unlinked chunk shared memory %s\n", shmem_chunk_name); - } -} - -void trace_map_next_chunk (void) -{ - // Get the next chunk id, size and address - uint64_t chunk_id = next_chunk_id; - if (chunk_id >= TRACE_NUMBER_OF_CHUNKS) - { - printf("ERROR: trace_map_next_chunk() exceeded maximum number of chunks %lu\n", TRACE_NUMBER_OF_CHUNKS); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t chunk_size = trace_get_chunk_size(chunk_id); - void * chunk_address = trace_get_chunk_address(chunk_id); - - if (verbose) printf("trace_map_next_chunk() mapping chunk id=%lu size=%lu B address=0x%lx\n", chunk_id, chunk_size, (uint64_t)chunk_address); - - // Build the chunk shared memory name - char shmem_chunk_name[128]; - trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); - - // Make sure the chunk shared memory is deleted - shm_unlink(shmem_chunk_name); - - // Create the output shared memory - trace_chunk_fd[chunk_id] = shm_open(shmem_chunk_name, O_RDWR | O_CREAT | O_EXCL, 0666); - if (trace_chunk_fd[chunk_id] < 0) - { - printf("ERROR: trace_map_next_chunk() failed calling trace shm_open(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Size it - int result = ftruncate(trace_chunk_fd[chunk_id], chunk_size); - if (result != 0) - { - printf("ERROR: trace_map_next_chunk() failed calling ftruncate(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Sync - fsync(trace_chunk_fd[chunk_id]); - - // Map it to the trace address - if (verbose) gettimeofday(&start_time, NULL); - void * requested_address; - if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) - { - requested_address = 0; - } - else - { - requested_address = (void *)chunk_address; - } - int flags = MAP_SHARED | map_locked_flag; - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - flags |= MAP_FIXED; - } - void * pTrace = mmap(requested_address, chunk_size, PROT_READ | PROT_WRITE, flags, trace_chunk_fd[chunk_id], 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pTrace == MAP_FAILED) - { - printf("ERROR: trace_map_next_chunk() failed calling mmap(pTrace) name=%s errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && ((uint64_t)pTrace != (uint64_t)requested_address)) - { - printf("ERROR: trace_map_next_chunk() called mmap(trace) but returned address = %p != 0x%lx\n", pTrace, (uint64_t)requested_address); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("trace_map_next_chunk() mapped %lu B to %s and returned address %p in %lu us\n", chunk_size, shmem_chunk_name, pTrace, duration); - - // Update total mapped size - trace_total_mapped_size += chunk_size; - - // Increment next chunk id - next_chunk_id++; -} - -void trace_map_initialize (void) -{ - // Perform preventive cleanup of any leftover shared memory chunks - trace_preventive_cleanup(); - - // Reserve the full virtual trace address space - trace_virtual_alloc(); - - // Map the first chunk, i.e. chunk 0 - trace_map_next_chunk(); - - trace_address = TRACE_ADDR; - pOutputTrace = (uint64_t *)TRACE_ADDR; -} - /********/ /* MAIN */ /********/ @@ -739,11 +171,13 @@ int main(int argc, char *argv[]) // Configure based on parguments configure(); +#ifdef USE_FILE_LOCK // Lock file - // if (server) - // { - // file_lock(); - // } + if (server) + { + file_lock(); + } +#endif // Send a message to stderr // fprintf(stderr, "%s stderr test (not an error): Starting Ziskemu ASM emulator process id=%d server=%d client=%d gen_method=%d port=%u\n", log_name, process_id, server, client, gen_method, port); @@ -1262,4418 +696,32 @@ int main(int argc, char *argv[]) #endif } -/*******************************/ -/* ARGUMENTS AND CONFIGURATION */ -/*******************************/ +/*************/ +/* FILE LOCK */ +/*************/ + +#ifdef USE_FILE_LOCK -// Print usage information: valid arguments -void print_usage (void) +// Lock file exclusively to ensure that only one instance of the program is running at a time +void file_lock(void) { - printf("Usage: ziskemuasm\n"); - printf("\t-s(server)\n"); - printf("\t-c(client)\n"); - printf("\t-i \n"); - printf("\t-p \n"); - printf("\t--gen=0|--generate_fast\n"); - printf("\t--gen=1|--generate_minimal_trace\n"); - printf("\t--gen=2|--generate_rom_histogram\n"); - printf("\t--gen=3|--generate_main_trace\n"); - printf("\t--gen=4|--generate_chunks\n"); - printf("\t--gen=6|--generate_zip\n"); - printf("\t--gen=9|--generate_mem_reads\n"); - printf("\t--gen=10|--generate_chunk_player_mem_reads\n"); - printf("\t--chunk \n"); - printf("\t--shutdown\n"); - printf("\t--mt \n"); - printf("\t-o output on\n"); - printf("\t--output_riscof output riscof on\n"); - printf("\t--silent silent on\n"); - printf("\t--shm_prefix (default: ZISK)\n"); - printf("\t-m metrics on\n"); - printf("\t-t trace on\n"); - printf("\t-tt trace_trace on\n"); - printf("\t-f(save to file)\n"); - printf("\t-a chunk_address\n"); - printf("\t-v verbose on\n"); - printf("\t-u unlock physical memory in mmap\n"); - printf("\t--share_input_shm share input shared memories\n"); - printf("\t--open_input_shm open existing input shared memories\n"); -#ifdef ASM_PRECOMPILE_CACHE - printf("\t--precompile-cache-store store precompile results in cache file\n"); - printf("\t--precompile-cache-load load precompile results from cache file\n"); -#endif - if (precompile_results_enabled) - { - printf("\t-r \n"); + // Open (or create) the lock file. We don't need to write to it. + file_lock_fd = open(file_lock_name, O_CREAT | O_RDONLY, 0644); + if (file_lock_fd == -1) { + printf("ERROR: file_lock() failed calling open(%s) errno=%d=%s\n", file_lock_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(1); + } + + // Try to acquire an exclusive lock, non-blocking. + if (flock(file_lock_fd, LOCK_EX | LOCK_NB) == -1) { + // If we fail to get the lock, another instance is running. + printf("ERROR: Another instance of this program is already running.\n"); + fflush(stdout); + fflush(stderr); + exit(1); } - printf("\t--redirect-output-to-file redirect output to file\n"); - printf("\t-h/--help print this\n"); } -// Parse main function arguments and configure global variables accordingly -void parse_arguments(int argc, char *argv[]) -{ - strcpy(shm_prefix, "ZISK"); - uint64_t number_of_selected_generation_methods = 0; - if (argc > 1) - { - for (int i = 1; i < argc; i++) - { - if (strcmp(argv[i], "-s") == 0) - { - server = true; - continue; - } - if (strcmp(argv[i], "-c") == 0) - { - client = true; - continue; - } - if ( (strcmp(argv[i], "--gen=0") == 0) || (strcmp(argv[i], "--generate_fast") == 0)) - { - gen_method = Fast; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=1") == 0) || (strcmp(argv[i], "--generate_minimal_trace") == 0)) - { - gen_method = MinimalTrace; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=2") == 0) || (strcmp(argv[i], "--generate_rom_histogram") == 0)) - { - gen_method = RomHistogram; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=3") == 0) || (strcmp(argv[i], "--generate_main_trace") == 0)) - { - gen_method = MainTrace; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=4") == 0) || (strcmp(argv[i], "--generate_chunks") == 0)) - { - gen_method = ChunksOnly; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=6") == 0) || (strcmp(argv[i], "--generate_zip") == 0)) - { - gen_method = Zip; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=7") == 0) || (strcmp(argv[i], "--generate_mem_op") == 0)) - { - gen_method = MemOp; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=8") == 0) || (strcmp(argv[i], "--generate_chunk_player_mt_collect_mem") == 0)) - { - gen_method = ChunkPlayerMTCollectMem; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=9") == 0) || (strcmp(argv[i], "--generate_mem_reads") == 0)) - { - gen_method = MemReads; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=10") == 0) || (strcmp(argv[i], "--generate_chunk_player_mem_reads") == 0)) - { - gen_method = ChunkPlayerMemReadsCollectMain; - number_of_selected_generation_methods++; - continue; - } - if (strcmp(argv[i], "-o") == 0) - { - output = true; - continue; - } - if (strcmp(argv[i], "--output_riscof") == 0) - { - output_riscof = true; - continue; - } - if (strcmp(argv[i], "--silent") == 0) - { - silent = true; - continue; - } - if (strcmp(argv[i], "-m") == 0) - { - metrics = true; - continue; - } - if (strcmp(argv[i], "-t") == 0) - { - trace = true; - continue; - } - if (strcmp(argv[i], "-tt") == 0) - { - trace = true; - trace_trace = true; - continue; - } - if (strcmp(argv[i], "-v") == 0) - { - verbose = true; - //emu_verbose = true; - continue; - } - if (strcmp(argv[i], "-u") == 0) - { - map_locked_flag = 0; - continue; - } - if (strcmp(argv[i], "-h") == 0) - { - print_usage(); - exit(0); - } - if (strcmp(argv[i], "--help") == 0) - { - print_usage(); - continue; - } - if (strcmp(argv[i], "-i") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -i in the last position; please provide input file after it\n"); - print_usage(); - exit(-1); - } - if (strlen(argv[i]) > 4095) - { - printf("ERROR: Detected argument -i but next argument is too long\n"); - print_usage(); - exit(-1); - } - strcpy(input_file, argv[i]); - continue; - } - if (strcmp(argv[i], "--shm_prefix") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -i in the last position; please provide shared mem prefix after it\n"); - print_usage(); - exit(-1); - } - if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) - { - printf("ERROR: Detected argument -i but next argument is too long\n"); - print_usage(); - exit(-1); - } - strcpy(shm_prefix, argv[i]); - continue; - } - if (strcmp(argv[i], "--chunk") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -c in the last position; please provide chunk number after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - chunk_mask = strtoul(argv[i], &endptr, 10); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Chunk number is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argv[i]) { - printf("ERROR: No digits found while parsing chunk number\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after chunk number: %s\n", endptr); - print_usage(); - exit(-1); - } else if (chunk_mask > MAX_CHUNK_MASK) { - printf("ERROR: Invalid chunk number: %lu\n", chunk_mask); - print_usage(); - exit(-1); - } else { - printf("Got chunk_mask= %lu\n", chunk_mask); - } - continue; - } - if (strcmp(argv[i], "--shutdown") == 0) - { - do_shutdown = true; - continue; - } - if (strcmp(argv[i], "--mt") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -mt in the last position; please provide number of MT requests after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - number_of_mt_requests = strtoul(argv[i], &endptr, 10); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Number of MT requests is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argv[i]) { - printf("ERROR: No digits found while parsing number of MT requests\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after number of MT requests: %s\n", endptr); - print_usage(); - exit(-1); - } else if (number_of_mt_requests > 1000000) { - printf("ERROR: Invalid number of MT requests: %lu\n", number_of_mt_requests); - print_usage(); - exit(-1); - } else { - printf("Got number of MT requests= %lu\n", number_of_mt_requests); - } - continue; - } - if (strcmp(argv[i], "-p") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -p in the last position; please provide port number after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - arguments_port = strtoul(argv[i], &endptr, 10); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Port number is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argv[i]) { - printf("ERROR: No digits found while parsing port number\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after port number: %s\n", endptr); - print_usage(); - exit(-1); - } else { - printf("Got port number= %u\n", arguments_port); - } - continue; - } - if (strcmp(argv[i], "-f") == 0) - { - save_to_file = true; - continue; - } - if (strcmp(argv[i], "-a") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -a in the last position; please provide chunk address after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - char * argument = argv[i]; - if ((argument[0] == '0') && (argument[1] == 'x')) argument += 2; - chunk_player_address = strtoul(argv[i], &endptr, 16); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Chunk address is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argument) { - printf("ERROR: No digits found while parsing chunk addresss\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after chunk address: %s\n", endptr); - print_usage(); - exit(-1); - } else { - printf("Got chunk address= %p\n", (void *)chunk_player_address); - } - continue; - } - if (strcmp(argv[i], "--share_input_shm") == 0) - { - share_input_shm = true; - continue; - } - if (strcmp(argv[i], "--open_input_shm") == 0) - { - open_input_shm = true; - continue; - } - if (strcmp(argv[i], "--redirect-output-to-file") == 0) - { - redirect_output_to_file = true; - continue; - } -#ifdef ASM_PRECOMPILE_CACHE - if (strcmp(argv[i], "--precompile-cache-store") == 0) - { - precompile_cache_enabled = true; - precompile_cache_store_init(); - continue; - } - if (strcmp(argv[i], "--precompile-cache-load") == 0) - { - precompile_cache_enabled = true; - precompile_cache_load_init(); - continue; - } - -#endif - if (precompile_results_enabled && (strcmp(argv[i], "-r") == 0)) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -r in the last position; please provide precompile results file after it\n"); - print_usage(); - exit(-1); - } - if (strlen(argv[i]) > 4095) - { - printf("ERROR: Detected argument -r but next argument is too long\n"); - print_usage(); - exit(-1); - } - strcpy(precompile_file_name, argv[i]); - continue; - } - printf("ERROR: parse_arguments() Unrecognized argument: %s\n", argv[i]); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } -#ifdef ASM_PRECOMPILE_CACHE - if (precompile_cache_enabled == false) - { - printf("ERROR: parse_arguments() when in precompile cache mode, you need to use an argument: either --precompile-cache-store or --precompile-cache-load\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } -#endif - - // Check that only one generation method was selected as an argument - if (number_of_selected_generation_methods != 1) - { - printf("ERROR! parse_arguments() Invalid arguments: select 1 generation method, and only one\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check that the generation method selected by the process launcher is the same as the one - // for which the assembly code was generated - uint64_t asm_gen_method = get_gen_method(); - if (asm_gen_method != gen_method) - { - printf("ERROR! parse_arguments() Inconsistency: C generation method is %u but ASM generation method is %lu\n", - gen_method, - asm_gen_method); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check server/client - if (server && client) - { - printf("ERROR! parse_arguments() Inconsistency: both server and client at the same time is not possible\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (!server && !client) - { - printf("ERROR! parse_arguments() Inconsistency: select server or client\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (precompile_results_enabled && client && (strlen(precompile_file_name) == 0)) - { - printf("ERROR! parse_arguments() when in precompile results mode, you need to provide a precompile results file using -r \n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -// Configure global variables based on generation method and other arguments -void configure (void) -{ - // Select configuration based on generation method - switch (gen_method) - { - case Fast: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_FT_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_FT_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_FT_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_FT_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_FT_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_FT_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, ""); - strcpy(sem_chunk_done_name, ""); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_FT_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_FT"); - port = 23120; - break; - } - case MinimalTrace: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MT_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MT_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MT_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MT_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MT_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MT_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MT_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MT_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MT_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MT"); - call_chunk_done = true; - port = 23115; - break; - } - case RomHistogram: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_RH_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_RH_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_RH_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_RH_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_RH_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_RH_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_RH_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_RH_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_RH_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_RH"); - call_chunk_done = true; - port = 23116; - break; - } - case MainTrace: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MA_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MA_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MA_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MA_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MA_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MA_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MA_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MA_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MA_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MA"); - call_chunk_done = true; - port = 23118; - break; - } - case ChunksOnly: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_CH_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_CH_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_CH_input"); - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_CH_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_CH_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_CH_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_CH"); - call_chunk_done = true; - port = 23115; - break; - } - // case BusOp: - // { - // strcpy(shmem_input_name, "ZISKBO_input"); - // strcpy(shmem_output_name, "ZISKBO_output"); - // strcpy(sem_chunk_done_name, "ZISKBO_chunk_done"); - // chunk_done = true; - // port = 23115; - // break; - // } - case Zip: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_ZP_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_ZP_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_ZP_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_ZP_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_ZP_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_ZP_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_ZP_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_ZP_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_ZP_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_ZP"); - call_chunk_done = true; - port = 23115; - break; - } - case MemOp: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MO_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MO_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MO_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MO_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MO_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MO_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MO_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MO_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MO_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MO"); - call_chunk_done = true; - port = 23117; - break; - } - case ChunkPlayerMTCollectMem: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_CM_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_CM_control_output"); - strcpy(shmem_input_name, ""); - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_CM_output"); - strcpy(sem_chunk_done_name, ""); - strcpy(sem_shutdown_done_name, ""); - strcpy(shmem_mt_name, shm_prefix); - strcat(shmem_mt_name, "_MT_output"); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_CM"); - call_chunk_done = false; - port = 23119; - break; - } - case MemReads: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MT_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MT_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MT_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MT_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MT_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MT_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MT_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MT_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MT_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MT"); - call_chunk_done = true; - port = 23115; - break; - } - case ChunkPlayerMemReadsCollectMain: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_CA_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_CA_control_output"); - strcpy(shmem_input_name, ""); - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_CA_output"); - strcpy(sem_chunk_done_name, ""); - strcpy(sem_shutdown_done_name, ""); - strcpy(shmem_mt_name, shm_prefix); - strcat(shmem_mt_name, "_MT_output"); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_CA"); - call_chunk_done = false; - port = 23120; - break; - } - default: - { - printf("ERROR: configure() Invalid gen_method = %u\n", gen_method); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - if (precompile_results_enabled && (gen_method == ChunkPlayerMTCollectMem || gen_method == ChunkPlayerMemReadsCollectMain)) - { - printf("ERROR: configure() precompile results enabled is not compatible with generation method %u\n", gen_method); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (arguments_port != 0) - { - port = arguments_port; - } - - if (verbose) - { - printf("ziskemuasm configuration:\n"); - printf("\tgen_method=%u\n", gen_method); - printf("\tshm_prefix=%s\n", shm_prefix); - printf("\tfile_lock_name=%s\n", file_lock_name); - printf("\tlog_name=%s\n", log_name); - printf("\tport=%u\n", port); - printf("\tcall_chunk_done=%u\n", call_chunk_done); - printf("\tchunk_size=%lu\n", chunk_size); - printf("\tshmem_control_input=%s\n", shmem_control_input_name); - printf("\tshmem_control_output=%s\n", shmem_control_output_name); - printf("\tshmem_input=%s\n", shmem_input_name); - printf("\tshmem_precompile=%s\n", shmem_precompile_name); - printf("\tshmem_output=%s\n", shmem_output_name); - printf("\tshmem_mt=%s\n", shmem_mt_name); - printf("\tsem_chunk_done=%s\n", sem_chunk_done_name); - printf("\tsem_shutdown_done=%s\n", sem_shutdown_done_name); - printf("\tsem_prec_avail=%s\n", sem_prec_avail_name); - printf("\tsem_prec_read=%s\n", sem_prec_read_name); - printf("\tmap_locked_flag=%d\n", map_locked_flag); - printf("\toutput=%u\n", output); - printf("\tprecompile_results_enabled=%u\n", precompile_results_enabled); - printf("\toutput_riscof=%u\n", output_riscof); - } -} - -/**********/ -/* CLIENT */ -/**********/ - -void client_setup (void) -{ - assert(!server); - assert(client); - - int result; - - /***********************/ - /* INPUT MINIMAL TRACE */ - /***********************/ - - // Input MT trace - if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) - { - // Create the output shared memory - shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); - if (shmem_mt_fd < 0) - { - printf("ERROR: Failed calling trace shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map it to the trace address -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); -#endif - if (pTrace == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((uint64_t)pTrace != TRACE_ADDR) - { - printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); - } - - /**********************/ - /* PRECOMPILE_RESULTS */ - /**********************/ - - if (precompile_results_enabled) - { - /**************/ - /* PRECOMPILE */ - /**************/ - - // Create the precompile results shared memory - shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); - if (shmem_precompile_fd < 0) - { - printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map precompile address space - if (verbose) gettimeofday(&start_time, NULL); - void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pPrecompile == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(precompile) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_precompile_address = pPrecompile; - precompile_results_address = (uint64_t *)pPrecompile; - - if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); - - /*****************/ - /* CONTROL INPUT */ - /*****************/ - - // Create the control input shared memory - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR, 0666); - if (shmem_control_input_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map control input address space - if (verbose) gettimeofday(&start_time, NULL); - void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_INPUT_ADDR) - { - printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_control_input_address = (uint64_t *)pControl; - precompile_written_address = &shmem_control_input_address[0]; - precompile_exit_address = &shmem_control_input_address[1]; - if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); - - /*****************/ - /* CONTROL OUTPUT */ - /*****************/ - - // Create the control input shared memory - shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR, 0666); - if (shmem_control_output_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map control input address space - if (verbose) gettimeofday(&start_time, NULL); - pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_OUTPUT_ADDR) - { - printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_control_output_address = (uint64_t *)pControl; - precompile_read_address = &shmem_control_output_address[0]; - if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); - - /*************************/ - /* PRECOMPILE SEMAPHORES */ - /*************************/ - - // Create the semaphore for precompile results available signal - assert(strlen(sem_prec_avail_name) > 0); - - sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT, 0666, 0); - if (sem_prec_avail == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_avail_name); - - // Create the semaphore for precompile results read signal - assert(strlen(sem_prec_read_name) > 0); - - sem_prec_read = sem_open(sem_prec_read_name, O_CREAT, 0666, 0); - if (sem_prec_read == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_read_name); - } -} - -typedef enum { - PrecompileReadMode_NoPrefix, - PrecompileReadMode_Prefixed -} PrecompileReadMode; - -PrecompileReadMode precompile_read_mode = PrecompileReadMode_NoPrefix; -//PrecompileReadMode precompile_read_mode = PrecompileReadMode_Prefixed; - -typedef enum { - PrecompileWriteMode_Full, - PrecompileWriteMode_OnePrecAtATime -} PrecompileWriteMode; - -PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_Full; -//PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_OnePrecAtATime; - -//#define PRECOMPILE_FIXED_SIZE 25 // Keccak-f state size in u64s -#define PRECOMPILE_FIXED_SIZE 4 // SHA-256 state size in u64s - -void client_write_precompile_results (void) -{ - int result; - -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - - // Open input file - FILE * precompile_fp = fopen(precompile_file_name, "r"); - if (precompile_fp == NULL) - { - printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Get input file size - if (fseek(precompile_fp, 0, SEEK_END) == -1) - { - printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - long precompile_data_size = ftell(precompile_fp); - if (precompile_data_size == -1) - { - printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((precompile_data_size & 0x7) != 0) - { - printf("ERROR: Precompile results file (%s) size (%lu) is not a multiple of 8 B\n", precompile_file_name, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Go back to the first byte - if (fseek(precompile_fp, 0, SEEK_SET) == -1) - { - printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - assert(precompile_read_mode == PrecompileReadMode_NoPrefix || precompile_read_mode == PrecompileReadMode_Prefixed); - assert(precompile_write_mode == PrecompileWriteMode_Full || precompile_write_mode == PrecompileWriteMode_OnePrecAtATime); - - /*************/ - /* NO PREFIX */ - /*************/ - - if (precompile_read_mode == PrecompileReadMode_NoPrefix) - { - if (precompile_write_mode == PrecompileWriteMode_Full) - { - // Check the precompile data size is inside the proper range - if (precompile_data_size > MAX_PRECOMPILE_SIZE) - { - printf("ERROR: Size of precompile results file (%s) is too long (%lu)\n", precompile_file_name, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Copy input data into input memory - size_t precompile_read = fread(precompile_results_address, 1, precompile_data_size, precompile_fp); - if (precompile_read != precompile_data_size) - { - printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Initialize precompile written address - *precompile_written_address = precompile_data_size >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - else if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) - { - // Check the precompile data size is inside the proper range - if (precompile_data_size % (PRECOMPILE_FIXED_SIZE * 8) != 0) - { - printf("ERROR: Size of precompile results file (%s) is not a multiple %u * 8 B\n", precompile_file_name, PRECOMPILE_FIXED_SIZE); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Initialize precompile written address to zero - *precompile_written_address = 0; // in u64s - - // Copy in chunks of PRECOMPILE_FIXED_SIZE*8 bytes (Keccak-f state size) - uint64_t precompile_read_so_far = 0; - uint64_t data[PRECOMPILE_FIXED_SIZE]; - while (precompile_read_so_far < (uint64_t)precompile_data_size) - { - // Wait for server to read precompile results - //printf("Waiting for sem_prec_read()\n"); - result = sem_wait(sem_prec_read); - if (result == -1) - { - printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Number of bytes to read from file and write to shared memory in every loop - uint64_t bytes_to_read = sizeof(data); - - // Copy input data into input memory - size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); - if (precompile_read != bytes_to_read) - { - printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Copy data to shared memory - for (int i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); - precompile_read_so_far += 8; - } - - // Notify server that precompile results are available - *precompile_written_address = precompile_read_so_far >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - } - } - - /************/ - /* PREFIXED */ - /************/ - - else if (precompile_read_mode == PrecompileReadMode_Prefixed) - { -#define CTRL_START 0x00 -#define CTRL_END 0x01 -#define CTRL_CANCEL 0x02 -#define CTRL_ERROR 0x03 -#define HINTS_TYPE_RESULT 0x04 -#define HINTS_TYPE_ECRECOVER 0x05 -#define NUM_HINT_TYPES 0x06 - - uint64_t precompile_read_so_far = 0; - uint64_t precompile_written_so_far = 0; - - while (precompile_read_so_far < (uint64_t)precompile_data_size) - { - uint64_t data; - uint64_t bytes_to_read = sizeof(data); - - // Copy input data into input memory - size_t precompile_read = fread(&data, 1, bytes_to_read, precompile_fp); - if (precompile_read != bytes_to_read) - { - printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); - fflush(stdout); - fflush(stderr); - exit(-1); - } - precompile_read_so_far += bytes_to_read; - switch (data >> 32) - { - case CTRL_START: - //printf("Precompile CTRL_START\n"); - assert(precompile_read_so_far == 8); - break; - case CTRL_END: - //printf("Precompile CTRL_END\n"); - assert(precompile_read_so_far == precompile_data_size); - break; - // case CTRL_CANCEL: - // printf("Precompile CTRL_CANCEL\n"); - // break; - // case CTRL_ERROR: - // printf("Precompile CTRL_ERROR\n"); - // break; - case HINTS_TYPE_RESULT: - { - //printf("Precompile HINTS_TYPE_RESULT\n"); - if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) - { - // Wait for server to read precompile results - //printf("Waiting for sem_prec_read()\n"); - result = sem_wait(sem_prec_read); - if (result == -1) - { - printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - uint64_t result_length = data & 0xFFFFFFFF; - if (result_length > (precompile_data_size - precompile_read_so_far)) - { - printf("ERROR: Precompile HINTS_TYPE_RESULT length=%lu exceeds remaining file size %lu\n", result_length, precompile_data_size - precompile_read_so_far); - fflush(stdout); - fflush(stderr); - exit(-1); - } - //printf("Precompile HINTS_TYPE_RESULT result_length=%lu\n", result_length); - for (uint64_t i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &value, 8); - precompile_read_so_far += 8; - precompile_written_so_far += 8; - //printf(" Precompile result[%lu] = 0x%016lx\n", i, value); - } - - if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) - { - // Notify server that precompile results are available - *precompile_written_address = precompile_written_so_far >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - } - break; - // case HINTS_TYPE_ECRECOVER: - // { - // // Not implemented - // printf("Precompile HINTS_TYPE_ECRECOVER not implemented\n"); - // } - // break; - default: - printf("ERROR: Unknown precompile prefix type %lu\n", data >> 32); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - if (precompile_write_mode == PrecompileWriteMode_Full) - { - // Notify server that precompile results are available - *precompile_written_address = precompile_written_so_far >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - - } - - // Close the file pointer - fclose(precompile_fp); - -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - printf("client (precompile): done in %lu us\n", duration); -#endif -} - -void client_run (void) -{ - printf("client_run(): Starting client...\n"); - assert(client); - assert(!server); - - int result; - - /************************/ - /* Read input file data */ - /************************/ - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - - // Open input file - FILE * input_fp = fopen(input_file, "r"); - if (input_fp == NULL) - { - printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Get input file size - if (fseek(input_fp, 0, SEEK_END) == -1) - { - printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - long input_data_size = ftell(input_fp); - if (input_data_size == -1) - { - printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Go back to the first byte - if (fseek(input_fp, 0, SEEK_SET) == -1) - { - printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check the input data size is inside the proper range - if (input_data_size > (MAX_INPUT_SIZE - 16)) - { - printf("ERROR: Size of input file (%s) is too long (%lu)\n", input_file, input_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Open input shared memory - shmem_input_fd = shm_open(shmem_input_name, O_RDWR, 0666); - if (shmem_input_fd < 0) - { - printf("ERROR: Failed calling input shm_open(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map the shared memory object into the process address space - shmem_input_address = mmap(NULL, MAX_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmem_input_fd, 0); - if (shmem_input_address == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Write the input size in the first 64 bits - *(uint64_t *)shmem_input_address = (uint64_t)0; // free input - *(uint64_t *)(shmem_input_address + 8)= (uint64_t)input_data_size; - - // Copy input data into input memory - size_t input_read = fread(shmem_input_address + 16, 1, input_data_size, input_fp); - if (input_read != input_data_size) - { - printf("ERROR: Input read (%lu) != input file size (%lu)\n", input_read, input_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Close the file pointer - fclose(input_fp); - - // Unmap input - result = munmap(shmem_input_address, MAX_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - printf("client (input): done in %lu us\n", duration); -#endif - - } - - /*****************************/ - /* Read precompile file data */ - /*****************************/ - if (precompile_results_enabled) - { - // reset written counter - *precompile_written_address = 0; - - //client_write_precompile_results(); - } - - /*************************/ - /* Connect to the server */ - /*************************/ - - // Create socket to connect to server - int socket_fd; - socket_fd = socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd < 0) - { - printf("ERROR: socket() failed socket_fd=%d errno=%d=%s\n", socket_fd, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Configure server address - struct sockaddr_in server_addr; - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(port); - - result = inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); - if (result <= 0) - { - printf("ERROR: inet_pton() failed. Invalid address/Address not supported result=%d errno=%d=%s\n", result, errno, strerror(errno)); - exit(-1); - } - - // Connect to server - result = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); - if (result < 0) - { - printf("ERROR: connect() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); - exit(-1); - } - if (verbose) printf("connect()'d to port=%u\n", port); - - // Request and response - uint64_t request[5]; - uint64_t response[5]; - - /********/ - /* Ping */ - /********/ - - gettimeofday(&start_time, NULL); - - // Prepare message to send - request[0] = TYPE_PING; - request[1] = 0; - request[2] = 0; - request[3] = 0; - request[4] = 0; - - // Send data to server - result = send(socket_fd, request, sizeof(request), 0); - if (result < 0) - { - printf("ERROR: send() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Read server response - ssize_t bytes_received = recv(socket_fd, response, sizeof(response), MSG_WAITALL); - if (bytes_received < 0) - { - printf("ERROR: recv_all_with_timeout() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (bytes_received != sizeof(response)) - { - printf("ERROR: recv_all_with_timeout() returned bytes_received=%ld errno=%d=%s\n", bytes_received, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (response[0] != TYPE_PONG) - { - printf("ERROR: recv_all_with_timeout() returned unexpected type=%lu\n", response[0]); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (response[1] != gen_method) - { - printf("ERROR: recv_all_with_timeout() returned unexpected gen_method=%lu\n", response[1]); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - printf("client (PING): done in %lu us\n", duration); - - /*****************/ - /* Minimal trace */ - /*****************/ - for (uint64_t i=0; i 0); - - sem_unlink(sem_prec_avail_name); - - sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_prec_avail == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded sem_prec_avail=%p\n", sem_prec_avail_name, sem_prec_avail); - - // Create the semaphore for precompile results read signal - assert(strlen(sem_prec_read_name) > 0); - - sem_unlink(sem_prec_read_name); - - sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_prec_read == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded sem_prec_read=%p\n", sem_prec_read_name, sem_prec_read); - } - - /*******/ - /* RAM */ - /*******/ - - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - - if (verbose) gettimeofday(&start_time, NULL); - void * pRam = mmap((void *)RAM_ADDR, RAM_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | map_locked_flag, -1, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pRam == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(ram) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((uint64_t)pRam != RAM_ADDR) - { - printf("ERROR: Called mmap(ram) but returned address = %p != 0x%08lx\n", pRam, RAM_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("mmap(ram) mapped %lu B and returned address %p in %lu us\n", RAM_SIZE, pRam, duration); - } - - /****************/ - /* OUTPUT TRACE */ - /****************/ - - // If ROM histogram, configure trace size - if (gen_method == RomHistogram) - { - // Get max PC values for low and high addresses - uint64_t max_bios_pc = get_max_bios_pc(); - uint64_t max_program_pc = get_max_program_pc(); - assert(max_bios_pc >= 0x1000); - assert((max_bios_pc & 0x3) == 0); - assert(max_program_pc >= 0x80000000); - - // Calculate sizes - bios_size = ((max_bios_pc - 0x1000) >> 2) + 1; - program_size = max_program_pc - 0x80000000 + 1; - histogram_size = (4 + 1 + bios_size + 1 + program_size)*8; - initial_trace_size = ((histogram_size/TRACE_SIZE_GRANULARITY) + 1) * TRACE_SIZE_GRANULARITY; - trace_size = initial_trace_size; - } - - // Output trace - if ((gen_method == MinimalTrace) || - (gen_method == RomHistogram) || - (gen_method == MainTrace) || - (gen_method == Zip) || - (gen_method == MemOp) || - (gen_method == ChunkPlayerMTCollectMem) || - (gen_method == MemReads) || - (gen_method == ChunkPlayerMemReadsCollectMain)) - { - trace_map_initialize(); - } - - /***********************/ - /* INPUT MINIMAL TRACE */ - /***********************/ - - // Input MT trace - if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) - { - // Create the output shared memory - shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); - if (shmem_mt_fd < 0) - { - printf("ERROR: Failed calling mt shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map it to the trace address -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); -#endif - if (pTrace == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((uint64_t)pTrace != TRACE_ADDR) - { - printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); - } - - /******************/ - /* SEM CHUNK DONE */ - /******************/ - - if (call_chunk_done) - { - assert(strlen(sem_chunk_done_name) > 0); - - sem_unlink(sem_chunk_done_name); - - sem_chunk_done = sem_open(sem_chunk_done_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_chunk_done == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_chunk_done_name); - } - - /*********************/ - /* SEM SHUTDOWN DONE */ - /*********************/ - - assert(strlen(sem_shutdown_done_name) > 0); - - sem_unlink(sem_shutdown_done_name); - - sem_shutdown_done = sem_open(sem_shutdown_done_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_shutdown_done == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); -} - -void server_reset_fast (void) -{ - // Reset precompile read address for next emulation - if (precompile_results_enabled) - { - // Set precompile read counter to 0 for next emulation - *precompile_read_address = 0; - - // Sync control output shared memory so that the writer can see the precompile reads we have - // done, and thus update the precompile_written_address if needed - if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: server_reset_fast() msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } -} - -void server_reset_slow (void) -{ - // Reset RAM data for next emulation - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - memset((void *)RAM_ADDR, 0, RAM_SIZE); -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - if (verbose) printf("server_reset_slow() memset(ram) in %lu us\n", duration); -#endif - } -} - -void server_reset_trace (void) -{ - // Reset trace header and trace_used_size for next emulation - if ( (gen_method != ChunkPlayerMTCollectMem) && - (gen_method != ChunkPlayerMemReadsCollectMain) && - (gen_method != Fast) && - (gen_method != RomHistogram) ) - { - // Reset trace: init output header data - pOutputTrace[0] = 0x000100; // Version, e.g. v1.0.0 [8] - pOutputTrace[1] = 1; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - pOutputTrace[2] = trace_size; // MT allocated size [8] -> to be updated after reallocation - pOutputTrace[3] = 0; // MT used size [8] -> to be updated after completion - - // Reset trace used size - trace_used_size = 0; - } -} - -void server_run (void) -{ - // If ROM histogram, reset the trace area to 0 for the histogram data since it represents the - // ROM instruction multiplicity and one of them will be increased at every executed instruction - if ((gen_method == RomHistogram)) { - memset((void *)trace_address, 0, trace_size); - } - -#ifdef ASM_CALL_METRICS - reset_asm_call_metrics(); -#endif - - // Init trace header - server_reset_trace(); - - // Sync input shared memory - if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) - { - printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (precompile_results_enabled) - { - // Sync control input shared memory - if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Sync precompile shared memory - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - /*******/ - /* ASM */ - /*******/ - - // Call emulator assembly code - gettimeofday(&start_time,NULL); - if (verbose) - { - printf("Before calling emulator_start() trace_address=%lx\n", trace_address); - fflush(stdout); - fflush(stderr); - } - emulator_start(); - if (verbose) - { - printf("After calling emulator_start() trace_address=%lx\n", trace_address); - fflush(stdout); - fflush(stderr); - } - gettimeofday(&stop_time,NULL); - assembly_duration = TimeDiff(start_time, stop_time); - - // Reset precompile read address for next emulation - if (precompile_results_enabled) - { - *precompile_read_address = 0; - } - - uint64_t final_trace_size = MEM_CHUNK_ADDRESS - MEM_TRACE_ADDRESS; - trace_used_size = final_trace_size + 32; - - if ( metrics ) - { - uint64_t duration = assembly_duration; - uint64_t steps = MEM_STEP; - uint64_t end = MEM_END; - uint64_t error = MEM_ERROR; - uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; - uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; - uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; - printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", - duration, - realloc_counter, - wait_counter, - steps, - step_duration_ns, - step_tp_sec, - MEM_CHUNK_ADDRESS, - MEM_TRACE_ADDRESS, - final_trace_size, - final_trace_size_percentage, - trace_size, - end, - error, - max_steps, - chunk_size, - precompile_written_address ? *precompile_written_address : 0, - precompile_read_address ? *precompile_read_address : 0 - ); - fflush(stdout); - fflush(stderr); - if (gen_method == RomHistogram) - { - printf("Rom histogram size=%lu\n", histogram_size); - fflush(stdout); - fflush(stderr); - } - } - if (MEM_ERROR) - { - printf("Emulation ended with error code %lu\n", MEM_ERROR); - fflush(stdout); - fflush(stderr); - } - - // Log output - if (output) - { - unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; - unsigned int output_size = 64; -#ifdef DEBUG - if (verbose) - { - printf("Output size=%d\n", output_size); - fflush(stdout); - fflush(stderr); - } -#endif - - for (unsigned int i = 0; i < output_size; i++) - { - printf("%08x\n", *pOutput); - pOutput++; - } - fflush(stdout); - fflush(stderr); - } - - // Log output for riscof tests - if (output_riscof) - { - unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; - unsigned int output_size = *pOutput; -#ifdef DEBUG - if (verbose) - { - printf("Output size=%d\n", output_size); - fflush(stdout); - fflush(stderr); - } -#endif - - for (unsigned int i = 0; i < output_size; i++) - { - pOutput++; - printf("%08x\n", *pOutput); - } - fflush(stdout); - fflush(stderr); - } - - // Complete output header data - if ((gen_method == MinimalTrace) || - (gen_method == RomHistogram) || - (gen_method == Zip) || - (gen_method == MainTrace) || - (gen_method == MemOp) || - (gen_method == MemReads) || - (gen_method == ChunkPlayerMemReadsCollectMain)) - { - uint64_t * pOutput = (uint64_t *)trace_address; - pOutput[0] = 0x000100; // Version, e.g. v1.0.0 [8] - pOutput[1] = MEM_ERROR; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - pOutput[2] = trace_size; // MT allocated size [8] - //assert(final_trace_size > 32); - if (gen_method == RomHistogram) - { - pOutput[3] = MEM_STEP; - pOutput[4] = bios_size; - pOutput[4 + bios_size + 1] = program_size; - } - else - { - pOutput[3] = trace_used_size; // MT used size [8] - } - } - - // Notify client - if (gen_method == RomHistogram) - { - _chunk_done(); - } - - - // Notify the caller that the trace is ready to be consumed - // if (!is_file) - // { - // result = sem_post(sem_input); - // if (result == -1) - // { - // printf("Failed calling sem_post(%s) errno=%d=%s\n", sem_input_name, errno, strerror(errno)); - // fflush(stdout); - // fflush(stderr); - // exit(-1); - // } - // } - - -#ifdef ASM_CALL_METRICS - print_asm_call_metrics(assembly_duration); -#endif - - // Log trace - if (((gen_method == MinimalTrace) || (gen_method == Zip)) && trace) - { - log_minimal_trace(); - } - if ((gen_method == RomHistogram) && trace) - { - log_histogram(); - } - if ((gen_method == MainTrace) && trace) - { - log_main_trace(); - } - if ((gen_method == MemOp) && trace) - { - log_mem_op(); - } - if ((gen_method == MemOp) && save_to_file) - { - save_mem_op_to_files(); - } - if ((gen_method == ChunkPlayerMTCollectMem) && trace) - { - log_mem_trace(); - } - if ((gen_method == MemReads) && trace) - { - log_minimal_trace(); - } - if ((gen_method == ChunkPlayerMemReadsCollectMain) && trace) - { - log_chunk_player_main_trace(); - } -} - -void server_cleanup (void) -{ - // Cleanup ROM - int result = munmap((void *)ROM_ADDR, ROM_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(rom) errno=%d=%s\n", errno, strerror(errno)); - } - - // Cleanup RAM - result = munmap((void *)RAM_ADDR, RAM_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(ram) errno=%d=%s\n", errno, strerror(errno)); - } - - // Cleanup INPUT - result = munmap((void *)INPUT_ADDR, MAX_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_input_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - } - - if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - // Cleanup PRECOMPILE - result = munmap((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(precompile) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_precompile_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - } - - // Cleanup CONTROL - result = munmap((void *)shmem_control_input_address, CONTROL_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_control_input_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - } - result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_control_output_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); - } - - // Semaphores cleanup - result = sem_close(sem_prec_avail); - if (result == -1) - { - printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - } - result = sem_unlink(sem_prec_avail_name); - if (result == -1) - { - printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - } - result = sem_close(sem_prec_read); - if (result == -1) - { - printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - } - result = sem_unlink(sem_prec_read_name); - if (result == -1) - { - printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - } - } - - // Cleanup trace - trace_cleanup(); - - // Cleanup chunk done semaphore - if (call_chunk_done) - { - result = sem_close(sem_chunk_done); - if (result == -1) - { - printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - } - result = sem_unlink(sem_chunk_done_name); - if (result == -1) - { - printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - } - } - - // Post shutdown donw semaphore - result = sem_post(sem_shutdown_done); - if (result == -1) - { - printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); - } -} - -/**************/ -/* PRINT REGS */ -/**************/ - -//#define PRINT_REGS -#ifdef PRINT_REGS -extern uint64_t reg_0; -extern uint64_t reg_1; -extern uint64_t reg_2; -extern uint64_t reg_3; -extern uint64_t reg_4; -extern uint64_t reg_5; -extern uint64_t reg_6; -extern uint64_t reg_7; -extern uint64_t reg_8; -extern uint64_t reg_9; -extern uint64_t reg_10; -extern uint64_t reg_11; -extern uint64_t reg_12; -extern uint64_t reg_13; -extern uint64_t reg_14; -extern uint64_t reg_15; -extern uint64_t reg_16; -extern uint64_t reg_17; -extern uint64_t reg_18; -extern uint64_t reg_19; -extern uint64_t reg_20; -extern uint64_t reg_21; -extern uint64_t reg_22; -extern uint64_t reg_23; -extern uint64_t reg_24; -extern uint64_t reg_25; -extern uint64_t reg_26; -extern uint64_t reg_27; -extern uint64_t reg_28; -extern uint64_t reg_29; -extern uint64_t reg_30; -extern uint64_t reg_31; -extern uint64_t reg_32; -extern uint64_t reg_33; -extern uint64_t reg_34; -#endif - -// Used for debugging purposes -extern int _print_regs() -{ -#ifdef PRINT_REGS - printf("print_regs()\n"); - printf("\treg[ 0]=%lu=0x%lx=@%p\n", reg_0, reg_0, ®_0); - printf("\treg[ 1]=%lu=0x%lx=@%p\n", reg_1, reg_1, ®_1); - printf("\treg[ 2]=%lu=0x%lx=@%p\n", reg_2, reg_2, ®_2); - printf("\treg[ 3]=%lu=0x%lx=@%p\n", reg_3, reg_3, ®_3); - printf("\treg[ 4]=%lu=0x%lx=@%p\n", reg_4, reg_4, ®_4); - printf("\treg[ 5]=%lu=0x%lx=@%p\n", reg_5, reg_5, ®_5); - printf("\treg[ 6]=%lu=0x%lx=@%p\n", reg_6, reg_6, ®_6); - printf("\treg[ 7]=%lu=0x%lx=@%p\n", reg_7, reg_7, ®_7); - printf("\treg[ 8]=%lu=0x%lx=@%p\n", reg_8, reg_8, ®_8); - printf("\treg[ 9]=%lu=0x%lx=@%p\n", reg_9, reg_9, ®_9); - printf("\treg[10]=%lu=0x%lx=@%p\n", reg_10, reg_10, ®_10); - printf("\treg[11]=%lu=0x%lx=@%p\n", reg_11, reg_11, ®_11); - printf("\treg[12]=%lu=0x%lx=@%p\n", reg_12, reg_12, ®_12); - printf("\treg[13]=%lu=0x%lx=@%p\n", reg_13, reg_13, ®_13); - printf("\treg[14]=%lu=0x%lx=@%p\n", reg_14, reg_14, ®_14); - printf("\treg[15]=%lu=0x%lx=@%p\n", reg_15, reg_15, ®_15); - printf("\treg[16]=%lu=0x%lx=@%p\n", reg_16, reg_16, ®_16); - printf("\treg[17]=%lu=0x%lx=@%p\n", reg_17, reg_17, ®_17); - printf("\treg[18]=%lu=0x%lx=@%p\n", reg_18, reg_18, ®_18); - printf("\treg[19]=%lu=0x%lx=@%p\n", reg_19, reg_19, ®_19); - printf("\treg[20]=%lu=0x%lx=@%p\n", reg_20, reg_20, ®_20); - printf("\treg[21]=%lu=0x%lx=@%p\n", reg_21, reg_21, ®_21); - printf("\treg[22]=%lu=0x%lx=@%p\n", reg_22, reg_22, ®_22); - printf("\treg[23]=%lu=0x%lx=@%p\n", reg_23, reg_23, ®_23); - printf("\treg[24]=%lu=0x%lx=@%p\n", reg_24, reg_24, ®_24); - printf("\treg[25]=%lu=0x%lx=@%p\n", reg_25, reg_25, ®_25); - printf("\treg[26]=%lu=0x%lx=@%p\n", reg_26, reg_26, ®_26); - printf("\treg[27]=%lu=0x%lx=@%p\n", reg_27, reg_27, ®_27); - printf("\treg[28]=%lu=0x%lx=@%p\n", reg_28, reg_28, ®_28); - printf("\treg[29]=%lu=0x%lx=@%p\n", reg_29, reg_29, ®_29); - printf("\treg[30]=%lu=0x%lx=@%p\n", reg_30, reg_30, ®_30); - printf("\treg[31]=%lu=0x%lx=@%p\n", reg_31, reg_31, ®_31); - printf("\treg[32]=%lu=0x%lx=@%p\n", reg_32, reg_32, ®_32); - printf("\treg[33]=%lu=0x%lx=@%p\n", reg_33, reg_33, ®_33); - printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); - printf("\n"); -#endif -} - -/************/ -/* PRINT PC */ -/************/ - -//#define PRINT_PC_DURATION -#ifdef PRINT_PC_DURATION -struct timeval print_pc_tv; -#endif - -// Used for debugging purposes -extern int _print_pc (uint64_t pc, uint64_t c) -{ -#ifdef PRINT_PC_DURATION - print_pc_counter++; - { - struct timeval tv; - gettimeofday(&tv, NULL); - uint64_t duration = TimeDiff(print_pc_tv, tv); - if (duration > 900) - { - uint64_t chunk = print_pc_counter / chunk_size; - printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); - fflush(stdout); - } - print_pc_tv = tv; - } -#endif - - printf("s=%lu pc=%lx c=%lx", print_pc_counter, pc, c); - -//#define PRINT_PC_REGS -#ifdef PRINT_PC_REGS - /* Used for debugging */ - printf(" r0=%lx", reg_0); - printf(" r1=%lx", reg_1); - printf(" r2=%lx", reg_2); - printf(" r3=%lx", reg_3); - printf(" r4=%lx", reg_4); - printf(" r5=%lx", reg_5); - printf(" r6=%lx", reg_6); - printf(" r7=%lx", reg_7); - printf(" r8=%lx", reg_8); - printf(" r9=%lx", reg_9); - printf(" r10=%lx", reg_10); - printf(" r11=%lx", reg_11); - printf(" r12=%lx", reg_12); - printf(" r13=%lx", reg_13); - printf(" r14=%lx", reg_14); - printf(" r15=%lx", reg_15); - printf(" r16=%lx", reg_16); - printf(" r17=%lx", reg_17); - printf(" r18=%lx", reg_18); - printf(" r19=%lx", reg_19); - printf(" r20=%lx", reg_20); - printf(" r21=%lx", reg_21); - printf(" r22=%lx", reg_22); - printf(" r23=%lx", reg_23); - printf(" r24=%lx", reg_24); - printf(" r25=%lx", reg_25); - printf(" r26=%lx", reg_26); - printf(" r27=%lx", reg_27); - printf(" r28=%lx", reg_28); - printf(" r29=%lx", reg_29); - printf(" r30=%lx", reg_30); - printf(" r31=%lx", reg_31); -#endif - - printf("\n"); - fflush(stdout); - print_pc_counter++; -} - -/**************/ -/* CHUNK DONE */ -/**************/ - -//#define CHUNK_DONE_DURATION -#ifdef CHUNK_DONE_DURATION -uint64_t chunk_done_counter = 0; -struct timeval chunk_done_tv; -#endif - -//#define CHUNK_DONE_SYNC_DURATION -#ifdef CHUNK_DONE_SYNC_DURATION -struct timeval sync_start, sync_stop; -uint64_t sync_duration = 0; -#endif - -// Called by the assembly to notify that a chunk is done and its trace is ready to be consumed -extern void _chunk_done() -{ -#ifdef CHUNK_DONE_DURATION - chunk_done_counter++; - if ((chunk_done_counter & 0xFF) == 0) - { - struct timeval tv; - gettimeofday(&tv, NULL); - uint64_t duration = TimeDiff(chunk_done_tv, tv); - if (duration > 5000) - { - printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); - fflush(stdout); - } - chunk_done_tv = tv; - } -#endif - -#ifdef CHUNK_DONE_SYNC_DURATION - gettimeofday(&sync_start, NULL); -#endif - - __sync_synchronize(); - -#ifdef CHUNK_DONE_SYNC_DURATION - gettimeofday(&sync_stop, NULL); - sync_duration += TimeDiff(sync_start, sync_stop); - printf("chunk_done() sync_duration=%lu\n", sync_duration); -#endif - - // Notify the caller that a new chunk is done and its trace is ready to be consumed - assert(call_chunk_done); - int result = sem_post(sem_chunk_done); - if (result == -1) - { - printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -/*****************/ -/* REALLOC TRACE */ -/*****************/ - -// Called by the assembly to reallocate the trace when needed, e.g. for the next chunk, -// to increase the trace size by another chunk size -extern void _realloc_trace (void) -{ - // Increase realloc counter - realloc_counter++; - - // Map next chunk of the trace shared memory - trace_map_next_chunk(); - - // Update trace global variables - set_trace_size(trace_total_mapped_size); - -#ifdef DEBUG - if (verbose) printf("realloc_trace() realloc counter=%lu trace_address=0x%lx trace_size=%lu=%lx max_address=0x%lx trace_address_threshold=0x%lx chunk_size=%lu\n", realloc_counter, trace_address, trace_size, trace_size, trace_address + trace_size, trace_address_threshold, chunk_size); -#endif -} - -/*****************/ -/* LOG FUNCTIONS */ -/*****************/ - -/* Trace data structure - [8B] Number of chunks: C - - Chunk 0: - Start state: - [8B] pc - [8B] sp - [8B] c - [8B] step - [8B] register[1] - … - [8B] register[31] - [8B] register[32] - [8B] register[33] - Last state: - [8B] c - End: - [8B] end - Steps: - [8B] steps = chunk size except for the last chunk - [8B] mem_reads_size - [8B] mem_reads[0] - [8B] mem_reads[1] - … - [8B] mem_reads[mem_reads_size - 1] - - Chunk 1: - … - Chunk C-1: - … -*/ -void log_minimal_trace(void) -{ - uint64_t * pOutput = (uint64_t *)TRACE_ADDR; - printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] - printf("Minimal trace used size = %lu B\n", pOutput[3]); // Minimal trace used size [8] - - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; - uint64_t number_of_chunks = trace[0]; - printf("Number of chunks=%lu\n", number_of_chunks); - if (number_of_chunks > 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (trace_trace) - { - for (uint64_t m=0; m 100000000) - { - printf("ERROR: Bios size is too high=%lu\n", bios_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (trace_trace) - { - uint64_t * bios = trace + 1; - for (uint64_t i=0; i 100000000) - { - printf("ERROR: Program size is too high=%lu\n", program_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (trace_trace) - { - uint64_t * program = trace + 1 + bios_size + 1; - for (uint64_t i=0; i 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Main_trace size is too high=%lu\n", main_trace_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (trace_trace) - { - for (uint64_t m=0; m 0) - { - size_t bytes_written = fwrite(buffer_address, 1, buffer_length, file); - if (bytes_written != buffer_length) - { - printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%lu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - fclose(file); - exit(-1); - } - } - - if (fclose(file) != 0) - { - printf("ERROR: buffer2file() failed calling fclose(%s) errno=%d=%s\n", file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -/* Memory operations structure - [8B] Number of chunks = C - - Chunk 0: - [8b] end - [8B] mem_op_trace_size - [8B] mem_op_trace[0] - [8B] mem_op_trace[1] - … - [8B] mem_op_trace[mem_op_trace_size - 1] - - Chunk 1: - … - Chunk C-1: - … -*/ -void log_mem_op(void) -{ - // Log header - uint64_t * pOutput = (uint64_t *)TRACE_ADDR; - printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] - - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; - uint64_t number_of_chunks = trace[0]; - printf("Number of chunks=%lu\n", number_of_chunks); - if (number_of_chunks > 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - for (uint64_t m=0; m> 49) & 0x1; - uint64_t write = (chunk[i] >> 48) & 0x1; - uint64_t width = (chunk[i] >> 32) & 0xF; - uint64_t address = chunk[i] & 0xFFFFFFFF; - bool inside_range = - ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || - ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || - ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); - if (trace_trace || !inside_range) - { - printf("\t\tchunk[%lu].mem_op_trace[%lu] = %016lx = rest_are_zeros=%lx, write=%lx, width=%lx, address=%lx%s\n", - c, - m, - chunk[i], - rest_are_zeros, - write, - width, - address, - inside_range ? "" : " ERROR!!!!!!!!!!!!!!" - ); - } - i += 1; - } - - //Set next chunk pointer - chunk = chunk + i; - } - printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); -} - -/* Memory trace structure (for 1 chunk) - [8B] mem_trace_size - [16B] mem_trace[0] - [8B] mem operacion - [4B] address (LE) - [1B] width (1, 2, 4, 8) + write (0, 1) << 4 - [3B] - [16B] mem_trace[1] - … - [16B] mem_trace[mem_trace_size - 1] -*/ -void log_mem_trace(void) -{ - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)trace_address; - printf("log_mem_trace() trace_address=%p\n", trace); - uint64_t i=0; - printf("Version = 0x%06lx\n", trace[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", trace[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", trace[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", trace[3]); // Main trace used size [8] - i += 4; - uint64_t number_of_entries = trace[i]; - i++; - printf("Trace size=%lu\n", number_of_entries); - - for (uint64_t m = 0; m < number_of_entries; m++) - { - uint64_t addr_step = trace[i]; - i++; - - // addr_step = [@0, @1, @2, @3, width + write<<4, supra_step] - uint64_t address = addr_step & 0xFFFFFFFF; - uint64_t width = (addr_step >> (4*8)) & 0xF; - uint64_t write = (addr_step >> ((4*8) + 4)) & 0x1; - uint64_t micro_step = (addr_step >> (5*8)) & 0x3; - uint64_t incremental_step = (addr_step >> ((5*8) + 2)); - bool address_is_inside_range = - ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || - ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || - ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); - bool width_is_valid = (width == 1) || (width == 2) || (width == 4) || (width == 8); - bool bError = !(address_is_inside_range && width_is_valid); - if (trace_trace || bError) - { - printf("\tmem_trace[%lu] = %016lx = [inc_step=%lu, u_step=%lu, write=%lx, width=%lx, address=%lx] %s\n", - m, - addr_step, - incremental_step, - micro_step, - write, - width, - address, - bError ? " ERROR!!!!!!!!!!!!!!" : "" - ); - } - - // u-step: - // 0: a=SRC_MEM - // 1: b=SRC_MEM or b=SRC_IND - // 2: precompiled_read - // 3: c=STORE_MEM, c=STORE_IND or precompiled_write - - bool address_is_aligned = (address & 0x7) == 0; - uint64_t aligned_address = address & 0xFFFFFFF8; - uint64_t number_of_read_values = 0; - uint64_t number_of_write_values = 0; - - switch (micro_step) - { - case 0: // a=SRC_MEM - { - assert_perror(width == 8); - if (address_is_aligned) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - break; - } - case 1: // b=SRC_MEM or b=SRC_IND - { - if (address_is_aligned) - { - number_of_read_values = 1; - } - else - { - if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - } - break; - } - case 2: // precompiled_read - { - assert_perror(width == 8); - if (address_is_aligned) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - break; - } - case 3: // c=STORE_MEM, c=STORE_IND or precompiled_write - { - if (address_is_aligned && (width == 8)) - { - number_of_read_values = 0; - } - else - { - if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - } - number_of_write_values = 1; - break; - } - } - - for (uint64_t r = 0; r < number_of_read_values; r++) - { - uint64_t value = trace[i]; - i++; - m++; - if (trace_trace) - { - printf("\t\tread_value[%lu] = 0x%lx\n", i, value); - } - } - - for (uint64_t w = 0; w < number_of_write_values; w++) - { - uint64_t value = trace[i]; - i++; - m++; - if (trace_trace) - { - printf("\t\twrite_value[%lu] = 0x%lx\n", i, value); - } - } - } - printf("Trace=%p number_of_entries=%lu\n", trace, number_of_entries); -} - -void save_mem_op_to_files(void) -{ - // Log header - uint64_t * pOutput = (uint64_t *)TRACE_ADDR; - printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] - - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; - uint64_t number_of_chunks = trace[0]; - printf("Number of chunks=%lu\n", number_of_chunks); - if (number_of_chunks > 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - printf("Chunk %lu: file=%s length=%lu\n", c, file_name, mem_op_trace_size); - - buffer2file(&chunk[i], mem_op_trace_size * 8, file_name); - - //Set next chunk pointer - chunk = chunk + mem_op_trace_size + 1; - } - printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); -} - -/* Trace data structure - [8B] Number of elements - - A series of elements with the following structure: - [8B] op: instruction opcode - [8B] a: register a value - [8B] b: register b value - [8B] precompiled_memory_address: memory read address of the precompiled input data -*/ -void log_chunk_player_main_trace(void) -{ - uint64_t * chunk = (uint64_t *)trace_address; - uint64_t i = 0; - - printf("Version = 0x%06lx\n", chunk[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", chunk[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", chunk[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", chunk[3]); // Main trace used size [8] - i = 4; - - uint64_t mem_reads_size = chunk[i]; - i++; - printf("mem_reads_size=%lu\n", mem_reads_size); - if (mem_reads_size > 10000000) - { - printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - //if (trace_trace) - { - for (uint64_t m=0; m 0xFF) - { - printf("ERROR!! Invalid op=%lu=0x%lx\n", op, op); - } - if (trace_trace) printf("\tmem_reads[%lu] a=0x%08lx\n", m, chunk[i]); - i++; - m++; - if (trace_trace) printf("\tmem_reads[%lu] b=0x%08lx\n", m, chunk[i]); - i++; - m++; - if ( (op == 0xf1) // Keccak - || (op == 0xf9) // SHA256 - || (op == 0xf2) // Arith256 - || (op == 0xf3) // Arith256Mod - || (op == 0xf4) // Secp256k1Add - || (op == 0xf5) // Secp256k1Dbl - ) - { - if (trace_trace) printf("\tmem_reads[%lu] precompiled_address=%08lx\n", m, chunk[i]); - i++; - m++; - } - } - } - - printf("Chunk=%p size=%lu\n", chunk, mem_reads_size); -} - -/*************/ -/* FILE LOCK */ -/*************/ - -// Lock file exclusively to ensure that only one instance of the program is running at a time -void file_lock(void) -{ - // Open (or create) the lock file. We don't need to write to it. - file_lock_fd = open(file_lock_name, O_CREAT | O_RDONLY, 0644); - if (file_lock_fd == -1) { - printf("ERROR: file_lock() failed calling open(%s) errno=%d=%s\n", file_lock_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(1); - } - - // Try to acquire an exclusive lock, non-blocking. - if (flock(file_lock_fd, LOCK_EX | LOCK_NB) == -1) { - // If we fail to get the lock, another instance is running. - printf("ERROR: Another instance of this program is already running.\n"); - fflush(stdout); - fflush(stderr); - exit(1); - } -} - -/*********************************/ -/* WAIT FOR PRECOMPILE AVAILABLE */ -/*********************************/ - -// Called by the assembly when prec_written == prec_read, to wait for new precompile results to be available -int _wait_for_prec_avail (void) -{ - // Increment wait counter - wait_counter++; - - // Sync control output shared memory so that the writer can see the precompile reads we have - // done, and thus update the precompile_written_address if needed - if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Tell the writer that we have read some precompile results - sem_post(sem_prec_read); - - // Make sure the precompile available semaphore is reset before checking the condition, - // since the caller may have posted it (even several times) before we called sem_wait() - while (sem_trywait(sem_prec_avail) == 0) {/*printf("Purging sem_prec_avail\n");*/}; - - // Sync control input shared memory so that we can see the latest precompile_written_address value - if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check if there are already precompile results available - if (*precompile_written_address > *precompile_read_address) - { - // Sync precompile shared memory - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - return 0; - } - - // Wait again, but blocking this time - while (true) - { - struct timespec ts; - int result = clock_gettime(CLOCK_REALTIME, &ts); - if (result == -1) - { - printf("ERROR: wait_for_prec_avail() failed calling clock_gettime() errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - ts.tv_sec += 5; // 5 seconds timeout - - //printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); - result = sem_timedwait(sem_prec_avail, &ts); - //printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); - if ((result == -1) && (errno != ETIMEDOUT)) - { - printf("ERROR: wait_for_prec_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Sync control input shared memory so that we can see the latest precompile_written_address value - if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (*precompile_exit_address != 0) - { - printf("ERROR: wait_for_prec_avail() found precompile_exit_address=%lu\n", *precompile_exit_address); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (*precompile_written_address > *precompile_read_address) - { - // Sync precompile shared memory - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - return 0; - } - } - - printf("ERROR: wait_for_prec_avail() unreachable code\n"); - fflush(stdout); - fflush(stderr); - exit(-1); -} \ No newline at end of file +#endif // USE_FILE_LOCK \ No newline at end of file diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c new file mode 100644 index 000000000..60fdb49f4 --- /dev/null +++ b/emulator-asm/src/server.c @@ -0,0 +1,958 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "server.hpp" +#include "globals.hpp" +#include "asm_provided.hpp" +#include "trace_logs.hpp" +#include "trace.hpp" +#include "emu.hpp" +#include "c_provided.hpp" + +/**********/ +/* SERVER */ +/**********/ + +// ROM histogram +uint64_t histogram_size = 0; +uint64_t bios_size = 0; +uint64_t program_size = 0; + +void server_setup (void) +{ + assert(server); + assert(!client); + + int result; + + /*******/ + /* ROM */ + /*******/ + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + + if (verbose) gettimeofday(&start_time, NULL); + void * pRom = mmap((void *)ROM_ADDR, ROM_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | map_locked_flag, -1, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pRom == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(rom) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pRom != ROM_ADDR) + { + printf("ERROR: Called mmap(rom) but returned address = %p != 0x%lx\n", pRom, ROM_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(rom) mapped %ld B and returned address %p in %lu us\n", ROM_SIZE, pRom, duration); + } + + /*********/ + /* INPUT */ + /*********/ + + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + if (!open_input_shm) + { + // Make sure the input shared memory is deleted + shm_unlink(shmem_input_name); + + // Create the input shared memory + shmem_input_fd = shm_open(shmem_input_name, O_RDWR | O_CREAT | O_EXCL, 0666); + if (shmem_input_fd < 0) + { + printf("ERROR: Failed calling input RW shm_open(%s) as read-write errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_input_fd, MAX_INPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(shmem_input_fd); + + // Close the descriptor + if (close(shmem_input_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + // Open the input shared memory as read-only + shmem_input_fd = shm_open(shmem_input_name, O_RDONLY | O_EXCL, 0666); + if (shmem_input_fd < 0) + { + printf("ERROR: Failed calling input RO shm_open(%s) as read-only errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map input address space + if (verbose) gettimeofday(&start_time, NULL); + void * pInput = mmap((void *)INPUT_ADDR, MAX_INPUT_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pInput == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pInput != INPUT_ADDR) + { + printf("ERROR: Called mmap(pInput) but returned address = %p != 0x%lx\n", pInput, INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(input) mapped %lu B and returned address %p in %lu us\n", MAX_INPUT_SIZE, pInput, duration); + } + + /**********************/ + /* PRECOMPILE_RESULTS */ + /**********************/ + + if (precompile_results_enabled) + { + /**************/ + /* PRECOMPILE */ + /**************/ + + if (!open_input_shm) + { + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_precompile_name); + + // Create the precompile results shared memory + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR | O_CREAT, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_precompile_fd, MAX_PRECOMPILE_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(shmem_precompile_fd); + + // Close the descriptor + if (close(shmem_precompile_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + // Open the precompile shared memory as read-only + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDONLY | O_EXCL, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pPrecompile == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_precompile_address = pPrecompile; + precompile_results_address = (uint64_t *)pPrecompile; + if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); + + /*****************/ + /* CONTROL INPUT */ + /*****************/ + + if (!open_input_shm) + { + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_input_name); + + // Create the control shared memory + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_control_input_fd, CONTROL_INPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(shmem_control_input_fd); + + // Close the descriptor + if (close(shmem_control_input_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + // Open the control input shared memory as read-only + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY | O_EXCL, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_INPUT_ADDR) + { + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + + /******************/ + /* CONTROL OUTPUT */ + /******************/ + + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_output_name); + + // Create the control shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_output_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_control_output_fd, CONTROL_OUTPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); + + /*************************/ + /* PRECOMPILE SEMAPHORES */ + /*************************/ + + // Create the semaphore for precompile results available signal + assert(strlen(sem_prec_avail_name) > 0); + + sem_unlink(sem_prec_avail_name); + + sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_avail == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded sem_prec_avail=%p\n", sem_prec_avail_name, sem_prec_avail); + + // Create the semaphore for precompile results read signal + assert(strlen(sem_prec_read_name) > 0); + + sem_unlink(sem_prec_read_name); + + sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_read == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded sem_prec_read=%p\n", sem_prec_read_name, sem_prec_read); + } + + /*******/ + /* RAM */ + /*******/ + + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + + if (verbose) gettimeofday(&start_time, NULL); + void * pRam = mmap((void *)RAM_ADDR, RAM_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | map_locked_flag, -1, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pRam == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(ram) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pRam != RAM_ADDR) + { + printf("ERROR: Called mmap(ram) but returned address = %p != 0x%08lx\n", pRam, RAM_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(ram) mapped %lu B and returned address %p in %lu us\n", RAM_SIZE, pRam, duration); + } + + /****************/ + /* OUTPUT TRACE */ + /****************/ + + // If ROM histogram, configure trace size + if (gen_method == RomHistogram) + { + // Get max PC values for low and high addresses + uint64_t max_bios_pc = get_max_bios_pc(); + uint64_t max_program_pc = get_max_program_pc(); + assert(max_bios_pc >= 0x1000); + assert((max_bios_pc & 0x3) == 0); + assert(max_program_pc >= 0x80000000); + + // Calculate sizes + bios_size = ((max_bios_pc - 0x1000) >> 2) + 1; + program_size = max_program_pc - 0x80000000 + 1; + histogram_size = (4 + 1 + bios_size + 1 + program_size)*8; + initial_trace_size = ((histogram_size/TRACE_SIZE_GRANULARITY) + 1) * TRACE_SIZE_GRANULARITY; + trace_size = initial_trace_size; + } + + // Output trace + if ((gen_method == MinimalTrace) || + (gen_method == RomHistogram) || + (gen_method == MainTrace) || + (gen_method == Zip) || + (gen_method == MemOp) || + (gen_method == ChunkPlayerMTCollectMem) || + (gen_method == MemReads) || + (gen_method == ChunkPlayerMemReadsCollectMain)) + { + trace_map_initialize(); + } + + /***********************/ + /* INPUT MINIMAL TRACE */ + /***********************/ + + // Input MT trace + if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) + { + // Create the output shared memory + shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); + if (shmem_mt_fd < 0) + { + printf("ERROR: Failed calling mt shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map it to the trace address +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); +#endif + if (pTrace == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pTrace != TRACE_ADDR) + { + printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); + } + + /******************/ + /* SEM CHUNK DONE */ + /******************/ + + if (call_chunk_done) + { + assert(strlen(sem_chunk_done_name) > 0); + + sem_unlink(sem_chunk_done_name); + + sem_chunk_done = sem_open(sem_chunk_done_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_chunk_done == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_chunk_done_name); + } + + /*********************/ + /* SEM SHUTDOWN DONE */ + /*********************/ + + assert(strlen(sem_shutdown_done_name) > 0); + + sem_unlink(sem_shutdown_done_name); + + sem_shutdown_done = sem_open(sem_shutdown_done_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_shutdown_done == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); +} + +void server_reset_fast (void) +{ + // Reset precompile read address for next emulation + if (precompile_results_enabled) + { + // Set precompile read counter to 0 for next emulation + *precompile_read_address = 0; + + // Sync control output shared memory so that the writer can see the precompile reads we have + // done, and thus update the precompile_written_address if needed + if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: server_reset_fast() msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } +} + +void server_reset_slow (void) +{ + // Reset RAM data for next emulation + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + memset((void *)RAM_ADDR, 0, RAM_SIZE); +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + if (verbose) printf("server_reset_slow() memset(ram) in %lu us\n", duration); +#endif + } +} + +void server_reset_trace (void) +{ + // Reset trace header and trace_used_size for next emulation + if ( (gen_method != ChunkPlayerMTCollectMem) && + (gen_method != ChunkPlayerMemReadsCollectMain) && + (gen_method != Fast) && + (gen_method != RomHistogram) ) + { + // Reset trace: init output header data + pOutputTrace[0] = 0x000100; // Version, e.g. v1.0.0 [8] + pOutputTrace[1] = 1; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + pOutputTrace[2] = trace_size; // MT allocated size [8] -> to be updated after reallocation + pOutputTrace[3] = 0; // MT used size [8] -> to be updated after completion + + // Reset trace used size + trace_used_size = 0; + } +} + +void server_run (void) +{ + // If ROM histogram, reset the trace area to 0 for the histogram data since it represents the + // ROM instruction multiplicity and one of them will be increased at every executed instruction + if ((gen_method == RomHistogram)) { + memset((void *)trace_address, 0, trace_size); + } + +#ifdef ASM_CALL_METRICS + reset_asm_call_metrics(); +#endif + + // Init trace header + server_reset_trace(); + + // Sync input shared memory + if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) + { + printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (precompile_results_enabled) + { + // Sync control input shared memory + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + /*******/ + /* ASM */ + /*******/ + + // Call emulator assembly code + gettimeofday(&start_time,NULL); + if (verbose) + { + printf("Before calling emulator_start() trace_address=%lx\n", trace_address); + fflush(stdout); + fflush(stderr); + } + emulator_start(); + if (verbose) + { + printf("After calling emulator_start() trace_address=%lx\n", trace_address); + fflush(stdout); + fflush(stderr); + } + gettimeofday(&stop_time,NULL); + assembly_duration = TimeDiff(start_time, stop_time); + + // Reset precompile read address for next emulation + if (precompile_results_enabled) + { + *precompile_read_address = 0; + } + + uint64_t final_trace_size = MEM_CHUNK_ADDRESS - MEM_TRACE_ADDRESS; + trace_used_size = final_trace_size + 32; + + if ( metrics ) + { + uint64_t duration = assembly_duration; + uint64_t steps = MEM_STEP; + uint64_t end = MEM_END; + uint64_t error = MEM_ERROR; + uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; + uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; + uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; + printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", + duration, + realloc_counter, + wait_counter, + steps, + step_duration_ns, + step_tp_sec, + MEM_CHUNK_ADDRESS, + MEM_TRACE_ADDRESS, + final_trace_size, + final_trace_size_percentage, + trace_size, + end, + error, + max_steps, + chunk_size, + precompile_written_address ? *precompile_written_address : 0, + precompile_read_address ? *precompile_read_address : 0 + ); + fflush(stdout); + fflush(stderr); + if (gen_method == RomHistogram) + { + printf("Rom histogram size=%lu\n", histogram_size); + fflush(stdout); + fflush(stderr); + } + } + if (MEM_ERROR) + { + printf("Emulation ended with error code %lu\n", MEM_ERROR); + fflush(stdout); + fflush(stderr); + } + + // Log output + if (output) + { + unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; + unsigned int output_size = 64; +#ifdef DEBUG + if (verbose) + { + printf("Output size=%d\n", output_size); + fflush(stdout); + fflush(stderr); + } +#endif + + for (unsigned int i = 0; i < output_size; i++) + { + printf("%08x\n", *pOutput); + pOutput++; + } + fflush(stdout); + fflush(stderr); + } + + // Log output for riscof tests + if (output_riscof) + { + unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; + unsigned int output_size = *pOutput; +#ifdef DEBUG + if (verbose) + { + printf("Output size=%d\n", output_size); + fflush(stdout); + fflush(stderr); + } +#endif + + for (unsigned int i = 0; i < output_size; i++) + { + pOutput++; + printf("%08x\n", *pOutput); + } + fflush(stdout); + fflush(stderr); + } + + // Complete output header data + if ((gen_method == MinimalTrace) || + (gen_method == RomHistogram) || + (gen_method == Zip) || + (gen_method == MainTrace) || + (gen_method == MemOp) || + (gen_method == MemReads) || + (gen_method == ChunkPlayerMemReadsCollectMain)) + { + uint64_t * pOutput = (uint64_t *)trace_address; + pOutput[0] = 0x000100; // Version, e.g. v1.0.0 [8] + pOutput[1] = MEM_ERROR; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + pOutput[2] = trace_size; // MT allocated size [8] + //assert(final_trace_size > 32); + if (gen_method == RomHistogram) + { + pOutput[3] = MEM_STEP; + pOutput[4] = bios_size; + pOutput[4 + bios_size + 1] = program_size; + } + else + { + pOutput[3] = trace_used_size; // MT used size [8] + } + } + + // Notify client + if (gen_method == RomHistogram) + { + _chunk_done(); + } + + + // Notify the caller that the trace is ready to be consumed + // if (!is_file) + // { + // result = sem_post(sem_input); + // if (result == -1) + // { + // printf("Failed calling sem_post(%s) errno=%d=%s\n", sem_input_name, errno, strerror(errno)); + // fflush(stdout); + // fflush(stderr); + // exit(-1); + // } + // } + + +#ifdef ASM_CALL_METRICS + print_asm_call_metrics(assembly_duration); +#endif + + // Log trace + if (((gen_method == MinimalTrace) || (gen_method == Zip)) && trace) + { + log_minimal_trace(); + } + if ((gen_method == RomHistogram) && trace) + { + log_histogram(); + } + if ((gen_method == MainTrace) && trace) + { + log_main_trace(); + } + if ((gen_method == MemOp) && trace) + { + log_mem_op(); + } + if ((gen_method == MemOp) && save_to_file) + { + save_mem_op_to_files(); + } + if ((gen_method == ChunkPlayerMTCollectMem) && trace) + { + log_mem_trace(); + } + if ((gen_method == MemReads) && trace) + { + log_minimal_trace(); + } + if ((gen_method == ChunkPlayerMemReadsCollectMain) && trace) + { + log_chunk_player_main_trace(); + } +} + +void server_cleanup (void) +{ + // Cleanup ROM + int result = munmap((void *)ROM_ADDR, ROM_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(rom) errno=%d=%s\n", errno, strerror(errno)); + } + + // Cleanup RAM + result = munmap((void *)RAM_ADDR, RAM_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(ram) errno=%d=%s\n", errno, strerror(errno)); + } + + // Cleanup INPUT + result = munmap((void *)INPUT_ADDR, MAX_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_input_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + } + + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + // Cleanup PRECOMPILE + result = munmap((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_precompile_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + } + + // Cleanup CONTROL + result = munmap((void *)shmem_control_input_address, CONTROL_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_input_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + } + result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_output_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + } + + // Semaphores cleanup + result = sem_close(sem_prec_avail); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + } + result = sem_unlink(sem_prec_avail_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + } + result = sem_close(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + } + result = sem_unlink(sem_prec_read_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + } + } + + // Cleanup trace + trace_cleanup(); + + // Cleanup chunk done semaphore + if (call_chunk_done) + { + result = sem_close(sem_chunk_done); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + } + result = sem_unlink(sem_chunk_done_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + } + } + + // Post shutdown donw semaphore + result = sem_post(sem_shutdown_done); + if (result == -1) + { + printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); + } +} \ No newline at end of file diff --git a/emulator-asm/src/server.hpp b/emulator-asm/src/server.hpp new file mode 100644 index 000000000..c76cefee9 --- /dev/null +++ b/emulator-asm/src/server.hpp @@ -0,0 +1,11 @@ +#ifndef EMULATOR_ASM_SERVER_HPP +#define EMULATOR_ASM_SERVER_HPP + +void server_setup (void); +void server_reset_fast (void); +void server_reset_slow (void); +void server_reset_trace (void); +void server_run (void); +void server_cleanup (void); + +#endif // EMULATOR_ASM_SERVER_HPP \ No newline at end of file diff --git a/emulator-asm/src/trace.c b/emulator-asm/src/trace.c new file mode 100644 index 000000000..f3fe2863d --- /dev/null +++ b/emulator-asm/src/trace.c @@ -0,0 +1,238 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "trace.hpp" +#include "constants.hpp" +#include "globals.hpp" +#include "emu.hpp" + +/********************/ +/* TRACE ALLOCATION */ +/********************/ + +void trace_virtual_alloc (void) +{ + void * addr = mmap((void *)TRACE_ADDR, TRACE_MAX_SIZE, PROT_NONE, MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr != (void *)TRACE_ADDR) + { + printf("ERROR: trace_virtual_alloc() failed to reserve trace memory at address 0x%lx\n", TRACE_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +uint64_t next_chunk_id = 0; // Next trace chunk id to be mapped, starting from 0 +int trace_chunk_fd[TRACE_NUMBER_OF_CHUNKS]; // File descriptors for each chunk +uint64_t trace_total_mapped_size = 0; // Total mapped trace size + +void * trace_get_chunk_address (uint64_t chunk_id) +{ + assert(gen_method != RomHistogram || chunk_id == 0); + + if (chunk_id == 0) + { + return (void *)TRACE_ADDR; + } + else + { + return (void *)(TRACE_ADDR + TRACE_INITIAL_SIZE + ((chunk_id - 1) * TRACE_DELTA_SIZE)); + } +} + +uint64_t trace_get_chunk_size (uint64_t chunk_id) +{ + if (gen_method == RomHistogram) { + assert(chunk_id == 0); + return trace_size; + } + + if (chunk_id == 0) + { + return TRACE_INITIAL_SIZE; + } + else + { + return TRACE_DELTA_SIZE; + } +} + +void trace_generate_shmem_chunk_name(char * shmem_chunk_name, size_t shmem_chunk_name_size, uint64_t chunk_id) +{ + int result = snprintf(shmem_chunk_name, shmem_chunk_name_size, "%s_%lu", shmem_output_name, chunk_id); + if (result < 0 || result >= (int)shmem_chunk_name_size) + { + printf("ERROR: trace_generate_shmem_chunk_name() failed to create chunk shared memory name\n"); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +void trace_cleanup (void) +{ + // Unmap all mapped chunks + for (uint64_t chunk_id = 0; chunk_id < next_chunk_id; chunk_id++) + { + uint64_t chunk_size = trace_get_chunk_size(chunk_id); + void * chunk_address = trace_get_chunk_address(chunk_id); + int result = munmap(chunk_address, chunk_size); + if (result != 0) + { + printf("ERROR: trace_cleanup() failed calling munmap() chunk id=%lu size=%lu B address=0x%lx errno=%d=%s\n", chunk_id, chunk_size, (uint64_t)chunk_address, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Close the chunk shared memory file descriptor + close(trace_chunk_fd[chunk_id]); + trace_chunk_fd[chunk_id] = -1; + + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + shm_unlink(shmem_chunk_name); + } + + // Reset next chunk id + next_chunk_id = 0; +} + +void trace_preventive_cleanup (void) +{ + // Unmap all mapped chunks + for (uint64_t chunk_id = 0; chunk_id < TRACE_NUMBER_OF_CHUNKS; chunk_id++) + { + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + int result = shm_unlink(shmem_chunk_name); + if (result != 0) + { + break; + } + if (verbose) printf("trace_preventive_cleanup() unlinked chunk shared memory %s\n", shmem_chunk_name); + } +} + +void trace_map_next_chunk (void) +{ + // Get the next chunk id, size and address + uint64_t chunk_id = next_chunk_id; + if (chunk_id >= TRACE_NUMBER_OF_CHUNKS) + { + printf("ERROR: trace_map_next_chunk() exceeded maximum number of chunks %lu\n", TRACE_NUMBER_OF_CHUNKS); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t chunk_size = trace_get_chunk_size(chunk_id); + void * chunk_address = trace_get_chunk_address(chunk_id); + + if (verbose) printf("trace_map_next_chunk() mapping chunk id=%lu size=%lu B address=0x%lx\n", chunk_id, chunk_size, (uint64_t)chunk_address); + + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + shm_unlink(shmem_chunk_name); + + // Create the output shared memory + trace_chunk_fd[chunk_id] = shm_open(shmem_chunk_name, O_RDWR | O_CREAT | O_EXCL, 0666); + if (trace_chunk_fd[chunk_id] < 0) + { + printf("ERROR: trace_map_next_chunk() failed calling trace shm_open(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + int result = ftruncate(trace_chunk_fd[chunk_id], chunk_size); + if (result != 0) + { + printf("ERROR: trace_map_next_chunk() failed calling ftruncate(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(trace_chunk_fd[chunk_id]); + + // Map it to the trace address + if (verbose) gettimeofday(&start_time, NULL); + void * requested_address; + if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) + { + requested_address = 0; + } + else + { + requested_address = (void *)chunk_address; + } + int flags = MAP_SHARED | map_locked_flag; + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + flags |= MAP_FIXED; + } + void * pTrace = mmap(requested_address, chunk_size, PROT_READ | PROT_WRITE, flags, trace_chunk_fd[chunk_id], 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pTrace == MAP_FAILED) + { + printf("ERROR: trace_map_next_chunk() failed calling mmap(pTrace) name=%s errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && ((uint64_t)pTrace != (uint64_t)requested_address)) + { + printf("ERROR: trace_map_next_chunk() called mmap(trace) but returned address = %p != 0x%lx\n", pTrace, (uint64_t)requested_address); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("trace_map_next_chunk() mapped %lu B to %s and returned address %p in %lu us\n", chunk_size, shmem_chunk_name, pTrace, duration); + + // Update total mapped size + trace_total_mapped_size += chunk_size; + + // Increment next chunk id + next_chunk_id++; +} + +void trace_map_initialize (void) +{ + // Perform preventive cleanup of any leftover shared memory chunks + trace_preventive_cleanup(); + + // Reserve the full virtual trace address space + trace_virtual_alloc(); + + // Map the first chunk, i.e. chunk 0 + trace_map_next_chunk(); + + trace_address = TRACE_ADDR; + pOutputTrace = (uint64_t *)TRACE_ADDR; +} \ No newline at end of file diff --git a/emulator-asm/src/trace.hpp b/emulator-asm/src/trace.hpp new file mode 100644 index 000000000..84983df20 --- /dev/null +++ b/emulator-asm/src/trace.hpp @@ -0,0 +1,12 @@ +#ifndef EMULATOR_ASM_TRACE_HPP +#define EMULATOR_ASM_TRACE_HPP + +#include + +extern uint64_t trace_total_mapped_size; // Total mapped trace size + +void trace_map_initialize (void); +void trace_map_next_chunk (void); +void trace_cleanup (void); + +#endif // EMULATOR_ASM_TRACE_HPP \ No newline at end of file diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c new file mode 100644 index 000000000..3272c0027 --- /dev/null +++ b/emulator-asm/src/trace_logs.c @@ -0,0 +1,678 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include "constants.hpp" +#include "globals.hpp" +#include "asm_provided.hpp" +#include "globals.hpp" + +/*****************/ +/* LOG FUNCTIONS */ +/*****************/ + +/* Trace data structure + [8B] Number of chunks: C + + Chunk 0: + Start state: + [8B] pc + [8B] sp + [8B] c + [8B] step + [8B] register[1] + … + [8B] register[31] + [8B] register[32] + [8B] register[33] + Last state: + [8B] c + End: + [8B] end + Steps: + [8B] steps = chunk size except for the last chunk + [8B] mem_reads_size + [8B] mem_reads[0] + [8B] mem_reads[1] + … + [8B] mem_reads[mem_reads_size - 1] + + Chunk 1: + … + Chunk C-1: + … +*/ +void log_minimal_trace(void) +{ + uint64_t * pOutput = (uint64_t *)TRACE_ADDR; + printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] + printf("Minimal trace used size = %lu B\n", pOutput[3]); // Minimal trace used size [8] + + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; + uint64_t number_of_chunks = trace[0]; + printf("Number of chunks=%lu\n", number_of_chunks); + if (number_of_chunks > 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (trace_trace) + { + for (uint64_t m=0; m 100000000) + { + printf("ERROR: Bios size is too high=%lu\n", bios_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (trace_trace) + { + uint64_t * bios = trace + 1; + for (uint64_t i=0; i 100000000) + { + printf("ERROR: Program size is too high=%lu\n", program_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (trace_trace) + { + uint64_t * program = trace + 1 + bios_size + 1; + for (uint64_t i=0; i 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Main_trace size is too high=%lu\n", main_trace_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (trace_trace) + { + for (uint64_t m=0; m 0) + { + size_t bytes_written = fwrite(buffer_address, 1, buffer_length, file); + if (bytes_written != buffer_length) + { + printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%lu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + fclose(file); + exit(-1); + } + } + + if (fclose(file) != 0) + { + printf("ERROR: buffer2file() failed calling fclose(%s) errno=%d=%s\n", file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +/* Memory operations structure + [8B] Number of chunks = C + + Chunk 0: + [8b] end + [8B] mem_op_trace_size + [8B] mem_op_trace[0] + [8B] mem_op_trace[1] + … + [8B] mem_op_trace[mem_op_trace_size - 1] + + Chunk 1: + … + Chunk C-1: + … +*/ +void log_mem_op(void) +{ + // Log header + uint64_t * pOutput = (uint64_t *)TRACE_ADDR; + printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] + + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; + uint64_t number_of_chunks = trace[0]; + printf("Number of chunks=%lu\n", number_of_chunks); + if (number_of_chunks > 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + for (uint64_t m=0; m> 49) & 0x1; + uint64_t write = (chunk[i] >> 48) & 0x1; + uint64_t width = (chunk[i] >> 32) & 0xF; + uint64_t address = chunk[i] & 0xFFFFFFFF; + bool inside_range = + ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || + ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || + ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); + if (trace_trace || !inside_range) + { + printf("\t\tchunk[%lu].mem_op_trace[%lu] = %016lx = rest_are_zeros=%lx, write=%lx, width=%lx, address=%lx%s\n", + c, + m, + chunk[i], + rest_are_zeros, + write, + width, + address, + inside_range ? "" : " ERROR!!!!!!!!!!!!!!" + ); + } + i += 1; + } + + //Set next chunk pointer + chunk = chunk + i; + } + printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); +} + +/* Memory trace structure (for 1 chunk) + [8B] mem_trace_size + [16B] mem_trace[0] + [8B] mem operacion + [4B] address (LE) + [1B] width (1, 2, 4, 8) + write (0, 1) << 4 + [3B] + [16B] mem_trace[1] + … + [16B] mem_trace[mem_trace_size - 1] +*/ +void log_mem_trace(void) +{ + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)TRACE_ADDR; + printf("log_mem_trace() trace_address=%p\n", trace); + uint64_t i=0; + printf("Version = 0x%06lx\n", trace[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", trace[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", trace[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", trace[3]); // Main trace used size [8] + i += 4; + uint64_t number_of_entries = trace[i]; + i++; + printf("Trace size=%lu\n", number_of_entries); + + for (uint64_t m = 0; m < number_of_entries; m++) + { + uint64_t addr_step = trace[i]; + i++; + + // addr_step = [@0, @1, @2, @3, width + write<<4, supra_step] + uint64_t address = addr_step & 0xFFFFFFFF; + uint64_t width = (addr_step >> (4*8)) & 0xF; + uint64_t write = (addr_step >> ((4*8) + 4)) & 0x1; + uint64_t micro_step = (addr_step >> (5*8)) & 0x3; + uint64_t incremental_step = (addr_step >> ((5*8) + 2)); + bool address_is_inside_range = + ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || + ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || + ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); + bool width_is_valid = (width == 1) || (width == 2) || (width == 4) || (width == 8); + bool bError = !(address_is_inside_range && width_is_valid); + if (trace_trace || bError) + { + printf("\tmem_trace[%lu] = %016lx = [inc_step=%lu, u_step=%lu, write=%lx, width=%lx, address=%lx] %s\n", + m, + addr_step, + incremental_step, + micro_step, + write, + width, + address, + bError ? " ERROR!!!!!!!!!!!!!!" : "" + ); + } + + // u-step: + // 0: a=SRC_MEM + // 1: b=SRC_MEM or b=SRC_IND + // 2: precompiled_read + // 3: c=STORE_MEM, c=STORE_IND or precompiled_write + + bool address_is_aligned = (address & 0x7) == 0; + uint64_t aligned_address = address & 0xFFFFFFF8; + uint64_t number_of_read_values = 0; + uint64_t number_of_write_values = 0; + + switch (micro_step) + { + case 0: // a=SRC_MEM + { + assert(width == 8); + if (address_is_aligned) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + break; + } + case 1: // b=SRC_MEM or b=SRC_IND + { + if (address_is_aligned) + { + number_of_read_values = 1; + } + else + { + if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + } + break; + } + case 2: // precompiled_read + { + assert(width == 8); + if (address_is_aligned) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + break; + } + case 3: // c=STORE_MEM, c=STORE_IND or precompiled_write + { + if (address_is_aligned && (width == 8)) + { + number_of_read_values = 0; + } + else + { + if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + } + number_of_write_values = 1; + break; + } + } + + for (uint64_t r = 0; r < number_of_read_values; r++) + { + uint64_t value = trace[i]; + i++; + m++; + if (trace_trace) + { + printf("\t\tread_value[%lu] = 0x%lx\n", i, value); + } + } + + for (uint64_t w = 0; w < number_of_write_values; w++) + { + uint64_t value = trace[i]; + i++; + m++; + if (trace_trace) + { + printf("\t\twrite_value[%lu] = 0x%lx\n", i, value); + } + } + } + printf("Trace=%p number_of_entries=%lu\n", trace, number_of_entries); +} + +void save_mem_op_to_files(void) +{ + // Log header + uint64_t * pOutput = (uint64_t *)TRACE_ADDR; + printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] + + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; + uint64_t number_of_chunks = trace[0]; + printf("Number of chunks=%lu\n", number_of_chunks); + if (number_of_chunks > 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + printf("Chunk %lu: file=%s length=%lu\n", c, file_name, mem_op_trace_size); + + buffer2file(&chunk[i], mem_op_trace_size * 8, file_name); + + //Set next chunk pointer + chunk = chunk + mem_op_trace_size + 1; + } + printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); +} + +/* Trace data structure + [8B] Number of elements + + A series of elements with the following structure: + [8B] op: instruction opcode + [8B] a: register a value + [8B] b: register b value + [8B] precompiled_memory_address: memory read address of the precompiled input data +*/ +void log_chunk_player_main_trace(void) +{ + uint64_t * chunk = (uint64_t *)TRACE_ADDR; + uint64_t i = 0; + + printf("Version = 0x%06lx\n", chunk[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", chunk[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", chunk[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", chunk[3]); // Main trace used size [8] + i = 4; + + uint64_t mem_reads_size = chunk[i]; + i++; + printf("mem_reads_size=%lu\n", mem_reads_size); + if (mem_reads_size > 10000000) + { + printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + //if (trace_trace) + { + for (uint64_t m=0; m 0xFF) + { + printf("ERROR!! Invalid op=%lu=0x%lx\n", op, op); + } + if (trace_trace) printf("\tmem_reads[%lu] a=0x%08lx\n", m, chunk[i]); + i++; + m++; + if (trace_trace) printf("\tmem_reads[%lu] b=0x%08lx\n", m, chunk[i]); + i++; + m++; + if ( (op == 0xf1) // Keccak + || (op == 0xf9) // SHA256 + || (op == 0xf2) // Arith256 + || (op == 0xf3) // Arith256Mod + || (op == 0xf4) // Secp256k1Add + || (op == 0xf5) // Secp256k1Dbl + ) + { + if (trace_trace) printf("\tmem_reads[%lu] precompiled_address=%08lx\n", m, chunk[i]); + i++; + m++; + } + } + } + + printf("Chunk=%p size=%lu\n", chunk, mem_reads_size); +} \ No newline at end of file diff --git a/emulator-asm/src/trace_logs.hpp b/emulator-asm/src/trace_logs.hpp new file mode 100644 index 000000000..258804f23 --- /dev/null +++ b/emulator-asm/src/trace_logs.hpp @@ -0,0 +1,14 @@ +#ifndef EMULATOR_ASM_TRACE_LOGS_HPP +#define EMULATOR_ASM_TRACE_LOGS_HPP + +#include + +void log_minimal_trace(void); +void log_histogram(void); +void log_main_trace(void); +void log_mem_trace(void); +void log_mem_op(void); +void save_mem_op_to_files(void); +void log_chunk_player_main_trace(void); + +#endif // EMULATOR_ASM_TRACE_LOGS_HPP \ No newline at end of file From ef263a7d5ee58db2739cd6bcb6578bcffbc2b3c9 Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 2 Mar 2026 22:02:46 +0100 Subject: [PATCH 693/782] Fix copilot issues --- emulator-asm/src/c_provided.c | 2 ++ emulator-asm/src/configuration.c | 10 +++++----- emulator-asm/src/server.c | 6 +++--- emulator-asm/src/trace_logs.c | 12 +++++++++--- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/emulator-asm/src/c_provided.c b/emulator-asm/src/c_provided.c index cecec70cf..643dc1ad0 100644 --- a/emulator-asm/src/c_provided.c +++ b/emulator-asm/src/c_provided.c @@ -117,6 +117,7 @@ extern int _print_regs() printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); printf("\n"); #endif + return 0; } /************/ @@ -189,6 +190,7 @@ extern int _print_pc (uint64_t pc, uint64_t c) printf("\n"); fflush(stdout); print_pc_counter++; + return 0; } /**************/ diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index 90cdf5781..251a9c153 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -187,7 +187,7 @@ void parse_arguments(int argc, char *argv[]) if (strcmp(argv[i], "--help") == 0) { print_usage(); - continue; + exit(0); } if (strcmp(argv[i], "-i") == 0) { @@ -212,13 +212,13 @@ void parse_arguments(int argc, char *argv[]) i++; if (i >= argc) { - printf("ERROR: Detected argument -i in the last position; please provide shared mem prefix after it\n"); + printf("ERROR: Detected argument --shm_prefix in the last position; please provide shared mem prefix after it\n"); print_usage(); exit(-1); } if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) { - printf("ERROR: Detected argument -i but next argument is too long\n"); + printf("ERROR: Detected argument --shm_prefix but next argument is too long\n"); print_usage(); exit(-1); } @@ -230,7 +230,7 @@ void parse_arguments(int argc, char *argv[]) i++; if (i >= argc) { - printf("ERROR: Detected argument -c in the last position; please provide chunk number after it\n"); + printf("ERROR: Detected argument --chunk in the last position; please provide chunk number after it\n"); print_usage(); exit(-1); } @@ -270,7 +270,7 @@ void parse_arguments(int argc, char *argv[]) i++; if (i >= argc) { - printf("ERROR: Detected argument -mt in the last position; please provide number of MT requests after it\n"); + printf("ERROR: Detected argument --mt in the last position; please provide number of MT requests after it\n"); print_usage(); exit(-1); } diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index 60fdb49f4..369dc15e7 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -112,7 +112,7 @@ void server_setup (void) } // Open the input shared memory as read-only - shmem_input_fd = shm_open(shmem_input_name, O_RDONLY | O_EXCL, 0666); + shmem_input_fd = shm_open(shmem_input_name, O_RDONLY, 0666); if (shmem_input_fd < 0) { printf("ERROR: Failed calling input RO shm_open(%s) as read-only errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); @@ -195,7 +195,7 @@ void server_setup (void) } // Open the precompile shared memory as read-only - shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDONLY | O_EXCL, 0666); + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDONLY, 0666); if (shmem_precompile_fd < 0) { printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); @@ -266,7 +266,7 @@ void server_setup (void) } // Open the control input shared memory as read-only - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY | O_EXCL, 0666); + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY, 0666); if (shmem_control_input_fd < 0) { printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index 3272c0027..cd583c36a 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -9,7 +9,6 @@ #include "constants.hpp" #include "globals.hpp" #include "asm_provided.hpp" -#include "globals.hpp" /*****************/ /* LOG FUNCTIONS */ @@ -265,7 +264,7 @@ void log_main_trace(void) printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); } -void buffer2file (const void * buffer_address, size_t buffer_length, const char * file_name) +static void buffer2file (const void * buffer_address, size_t buffer_length, const char * file_name) { if (!file_name) { @@ -588,7 +587,14 @@ void save_mem_op_to_files(void) for (uint64_t c=0; c= sizeof(file_name)) + { + fprintf(stderr, "ERROR: Failed to construct file name for chunk=%lu\n", c); + fflush(stdout); + fflush(stderr); + exit(-1); + } uint64_t i=0; uint64_t mem_op_trace_size = chunk[i]; From a774a72b9b5595e86fd9b3efa48123ef22f605ce Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 3 Mar 2026 10:21:54 +0100 Subject: [PATCH 694/782] Fix copilot issues --- emulator-asm/src/configuration.c | 4 ++-- emulator-asm/src/constants.hpp | 2 ++ emulator-asm/src/trace_logs.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index 251a9c153..7ac488f9f 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -349,7 +349,7 @@ void parse_arguments(int argc, char *argv[]) char *endptr; char * argument = argv[i]; if ((argument[0] == '0') && (argument[1] == 'x')) argument += 2; - chunk_player_address = strtoul(argv[i], &endptr, 16); + chunk_player_address = strtoul(argument, &endptr, 16); // Check for errors if (errno == ERANGE) { @@ -357,7 +357,7 @@ void parse_arguments(int argc, char *argv[]) print_usage(); exit(-1); } else if (endptr == argument) { - printf("ERROR: No digits found while parsing chunk addresss\n"); + printf("ERROR: No digits found while parsing chunk address\n"); print_usage(); exit(-1); } else if (*endptr != '\0') { diff --git a/emulator-asm/src/constants.hpp b/emulator-asm/src/constants.hpp index 9860a6a22..c9a88259a 100644 --- a/emulator-asm/src/constants.hpp +++ b/emulator-asm/src/constants.hpp @@ -1,6 +1,8 @@ #ifndef EMULATOR_ASM_CONSTANTS_HPP #define EMULATOR_ASM_CONSTANTS_HPP +#include + /***************/ /* Definitions */ /***************/ diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index cd583c36a..9d0c9b5b1 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -295,7 +295,7 @@ static void buffer2file (const void * buffer_address, size_t buffer_length, cons size_t bytes_written = fwrite(buffer_address, 1, buffer_length, file); if (bytes_written != buffer_length) { - printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%lu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); + printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%zu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); fflush(stdout); fflush(stderr); fclose(file); From a1526e84cd27acb843800b3b699484f14f62d7c4 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 3 Mar 2026 11:21:02 +0100 Subject: [PATCH 695/782] Fix copilot issues --- emulator-asm/src/trace_logs.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index 9d0c9b5b1..d3e0df35a 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -10,6 +10,10 @@ #include "globals.hpp" #include "asm_provided.hpp" +// This file contains trace logging functions that are used only for debugging purposes, to log the +// content of the generated traces in a human-readable format. These functions are not used by the +// assembly code, and are not optimized for performance. + /*****************/ /* LOG FUNCTIONS */ /*****************/ @@ -597,6 +601,7 @@ void save_mem_op_to_files(void) } uint64_t i=0; + i++; // Skip end uint64_t mem_op_trace_size = chunk[i]; i++; if (mem_op_trace_size > 10000000) From 3aebf504df06a52d20f0a99c28df6bba5db840a2 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 3 Mar 2026 12:47:08 +0100 Subject: [PATCH 696/782] Fix copilot issues --- emulator-asm/Makefile | 2 +- emulator-asm/src/configuration.c | 11 +++++++++-- emulator-asm/src/globals.hpp | 1 + emulator-asm/src/server.c | 2 +- emulator-asm/src/trace.c | 1 - emulator-asm/src/trace_logs.c | 4 ++-- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 75835a40b..4a963aa4b 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -56,7 +56,7 @@ build/dma.o: $(DMA_OBJS) ld -r $(DMA_OBJS) -o $@ # Compile the final executable -$(OUT_PATH): build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c build/dma.o +$(OUT_PATH): build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c build/dma.o mkdir -p $(OUT_DIR) gcc $(CFLAGS) src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index 7ac488f9f..ef33c7308 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -216,7 +216,7 @@ void parse_arguments(int argc, char *argv[]) print_usage(); exit(-1); } - if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) + if (strlen(argv[i]) >= MAX_SHM_PREFIX_LENGTH) { printf("ERROR: Detected argument --shm_prefix but next argument is too long\n"); print_usage(); @@ -311,7 +311,14 @@ void parse_arguments(int argc, char *argv[]) } errno = 0; char *endptr; - arguments_port = strtoul(argv[i], &endptr, 10); + uint64_t arguments_port_u64 = strtoul(argv[i], &endptr, 10); + if (arguments_port_u64 > 0xFFFF) + { + printf("ERROR: Port number is too large, must be at most 65535\n"); + print_usage(); + exit(-1); + } + arguments_port = arguments_port_u64 & 0xFFFF; // Keep only lower 16 bits, since port numbers are 16 bits // Check for errors if (errno == ERANGE) { diff --git a/emulator-asm/src/globals.hpp b/emulator-asm/src/globals.hpp index f66e0044c..3d1bf6ecf 100644 --- a/emulator-asm/src/globals.hpp +++ b/emulator-asm/src/globals.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "constants.hpp" // Configuration globals, set by arguments diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index 369dc15e7..e16ddb624 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -949,7 +949,7 @@ void server_cleanup (void) } } - // Post shutdown donw semaphore + // Post shutdown done semaphore result = sem_post(sem_shutdown_done); if (result == -1) { diff --git a/emulator-asm/src/trace.c b/emulator-asm/src/trace.c index f3fe2863d..45ddca19d 100644 --- a/emulator-asm/src/trace.c +++ b/emulator-asm/src/trace.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index d3e0df35a..d63c0f973 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -616,8 +616,8 @@ void save_mem_op_to_files(void) buffer2file(&chunk[i], mem_op_trace_size * 8, file_name); - //Set next chunk pointer - chunk = chunk + mem_op_trace_size + 1; + //Set next chunk pointer: skip [end] and [mem_op_trace_size] headers plus data + chunk = chunk + mem_op_trace_size + 2; } printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); } From d0645962170fc85c05efcd9e3dd25731995cbe82 Mon Sep 17 00:00:00 2001 From: fractasy Date: Wed, 4 Mar 2026 13:38:09 +0100 Subject: [PATCH 697/782] Implement FCALL_INPUT_READY_ID in ziskemu --- core/src/inst_context.rs | 4 ++ core/src/zisk_ops.rs | 41 ++++++++++++++++--- emulator/src/emu_context.rs | 8 ++-- ziskos/entrypoint/src/zisklib/fcalls/input.rs | 23 +++++++++++ ziskos/entrypoint/src/zisklib/fcalls/mod.rs | 2 + 5 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 ziskos/entrypoint/src/zisklib/fcalls/input.rs diff --git a/core/src/inst_context.rs b/core/src/inst_context.rs index a0846f0f8..cb8fbf622 100644 --- a/core/src/inst_context.rs +++ b/core/src/inst_context.rs @@ -125,6 +125,9 @@ pub struct InstContext { pub extended_arg: i64, pub stats_hint: u64, + + /// Input data length, stored in the context to be used by the FCALL_INPUT_READY_ID fcall + pub input_len: u64, } /// RisK instruction context implementation @@ -149,6 +152,7 @@ impl InstContext { data_ext_len: 0, extended_arg: 0, stats_hint: 0, + input_len: 0, } } diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 9e58d912e..5a8a764d9 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -12,6 +12,10 @@ use precompiles_helpers::DmaInfo; use ziskos_hints::zisklib::fcall_proxy; +use crate::{ + blake2br, sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, + EXTRA_PARAMS_ADDR, INPUT_ADDR, M64, MAX_INPUT_SIZE, REG_A0, SYS_ADDR, +}; use fields::{poseidon2_hash, Goldilocks, Poseidon16, PrimeField64}; use paste::paste; use std::{ @@ -21,11 +25,7 @@ use std::{ str::FromStr, }; use tiny_keccak::keccakf; - -use crate::{ - blake2br, sha256f, EmulationMode, InstContext, Mem, ZiskOperationType, ZiskRequiredOperation, - EXTRA_PARAMS_ADDR, M64, REG_A0, SYS_ADDR, -}; +use ziskos_hints::zisklib::FCALL_INPUT_READY_ID; use lib_c::{inverse_fn_ec_c, inverse_fp_ec_c, sqrt_fp_ec_parity_c, Fcall, FcallContext}; @@ -2500,7 +2500,36 @@ pub fn opc_fcall(ctx: &mut InstContext) { // Get function id from a let function_id = ctx.a; - let iresult = fcall_proxy(function_id, &ctx.fcall.parameters, &mut ctx.fcall.result); + let iresult = if function_id == FCALL_INPUT_READY_ID as u64 { + let required_address = ctx.fcall.parameters[0] as u64; + if required_address < INPUT_ADDR + 8 { + panic!( + "opc_fcall() FCALL_INPUT_READY_ID called with required_address {:#x} < {:#x}", + required_address, + INPUT_ADDR + 8 + ); + } + if required_address >= INPUT_ADDR + MAX_INPUT_SIZE as u64 - 1 { + panic!( + "opc_fcall() FCALL_INPUT_READY_ID called with required_address {:#x} > {:#x}", + required_address, + INPUT_ADDR + MAX_INPUT_SIZE as u64 - 1 + ); + } + + let required_bytes = required_address - INPUT_ADDR - 8 + 1; // + 1 because required_address is the address of the last required byte + if required_bytes > ctx.input_len { + panic!( + "opc_fcall() FCALL_INPUT_READY_ID called with required_address {:#x} requiring {} bytes, but only {} bytes available", + required_address, + required_bytes, + ctx.input_len + ); + } + 0 + } else { + fcall_proxy(function_id, &ctx.fcall.parameters, &mut ctx.fcall.result) + }; if iresult < 0 { panic!("opc_fcall() failed calling Fcall() function_id={function_id} iresult={iresult}"); diff --git a/emulator/src/emu_context.rs b/emulator/src/emu_context.rs index 0eec2486b..997e1a51c 100644 --- a/emulator/src/emu_context.rs +++ b/emulator/src/emu_context.rs @@ -15,7 +15,6 @@ pub struct EmuContext { pub last_callback_step: u64, pub trace: EmuTrace, pub do_stats: bool, - pub max_input_mem: u64, pub stats: Stats, } @@ -35,19 +34,18 @@ impl EmuContext { last_callback_step: 0, do_stats: false, stats: Stats::default(), - max_input_mem: options.max_input_mem, // 128 MiB }; // Check the input data size is inside the proper range - if input.len() > (ctx.max_input_mem - 16) as usize { + if input.len() > (options.max_input_mem - 16) as usize { panic!("EmuContext::new() input size too big size={}", input.len()); } // Add the length and input data read sections - let input_len = input.len() as u64; + ctx.inst_ctx.input_len = input.len() as u64; let free_input = 0u64; ctx.inst_ctx.mem.add_read_section(INPUT_ADDR, &free_input.to_le_bytes()); - ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8, &input_len.to_le_bytes()); + ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8, &ctx.inst_ctx.input_len.to_le_bytes()); ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 16, &input); // Add the write section diff --git a/ziskos/entrypoint/src/zisklib/fcalls/input.rs b/ziskos/entrypoint/src/zisklib/fcalls/input.rs new file mode 100644 index 000000000..b4ebbadd0 --- /dev/null +++ b/ziskos/entrypoint/src/zisklib/fcalls/input.rs @@ -0,0 +1,23 @@ +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { + use core::arch::asm; + use crate::{ziskos_fcall, ziskos_fcall_get, ziskos_fcall_param}; + use super::FCALL_INPUT_READY_ID; + } +} + +#[allow(unused_variables)] +pub fn fcall_input_ready(address: &u64) { + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + { + // TODO: wait for input to be ready at the given address, then check the input length vs. address and return an error if the input is not long enough. For now, we just return immediately. + unimplemented!("fcall_input_ready is not implemented for non-zisk targets"); + } + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + ziskos_fcall_param!(address, 1); // Number of inputs + ziskos_fcall!(FCALL_INPUT_READY_ID); + } +} diff --git a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs index 35cf72e47..c28d61487 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs @@ -21,12 +21,14 @@ pub const FCALL_BIN_DECOMP_ID: u16 = 18; pub const FCALL_BLS12_381_FP2_SQRT_ID: u16 = 19; pub const FCALL_SECP256K1_ECDSA_VERIFY_ID: u16 = 20; pub const FCALL_SECP256R1_ECDSA_VERIFY_ID: u16 = 21; +pub const FCALL_INPUT_READY_ID: u16 = 22; mod big_int256_div; mod big_int_div; mod bin_decomp; mod bls12_381; mod bn254; +mod input; mod msb_pos_256; mod msb_pos_384; mod secp256k1; From 49cb9e2fc0fb5d9ec0bcc5c1736e78bfedaf30f5 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:45:01 +0000 Subject: [PATCH 698/782] added input hint --- common/src/hints.rs | 13 +++++++++++++ precompiles/hints/src/hints_processor.rs | 3 +++ 2 files changed, 16 insertions(+) diff --git a/common/src/hints.rs b/common/src/hints.rs index a8030f690..8a98a74a1 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -58,6 +58,9 @@ pub const CTRL_END: u32 = 0x0001; pub const CTRL_CANCEL: u32 = 0x0002; pub const CTRL_ERROR: u32 = 0x0003; +// === INPUT HINT CODES === +pub const HINT_INPUT: u32 = 0xF000; + // === BUILT-IN HINT CODES === // SHA256 hint codes pub const HINT_SHA256: u32 = 0x0100; @@ -139,6 +142,10 @@ impl TryFrom for CtrlHint { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u32)] pub enum BuiltInHint { + // INPUT hint types. + /// Input data hint. + Input = HINT_INPUT, + // SHA256 hint types. /// Compute SHA-256 hash Sha256 = HINT_SHA256, @@ -197,6 +204,8 @@ pub enum BuiltInHint { impl Display for BuiltInHint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = match self { + // INPUT hint types + BuiltInHint::Input => "INPUT", // SHA256 hint types BuiltInHint::Sha256 => "SHA256", // BN254 Hints @@ -237,6 +246,8 @@ impl TryFrom for BuiltInHint { fn try_from(value: u32) -> Result { match value { + // INPUT hint types + HINT_INPUT => Ok(Self::Input), // SHA256 hint types HINT_SHA256 => Ok(Self::Sha256), // BN254 Hints @@ -322,6 +333,8 @@ impl HintCode { HintCode::Ctrl(CtrlHint::Error) => CTRL_ERROR, // Built-In Hint Codes + // INPUT hint types + HintCode::BuiltIn(BuiltInHint::Input) => HINT_INPUT, // SHA256 Hints HintCode::BuiltIn(BuiltInHint::Sha256) => HINT_SHA256, // BN254 Hints diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index bf9ef5e26..2b56a43a3 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -668,6 +668,9 @@ impl HintsProcessor { data_len_bytes: usize, ) -> Result> { match hint { + // Input Hint Codes + BuiltInHint::Input => unimplemented!("Input hint unimplemented"), + // SHA256 Hint Codes BuiltInHint::Sha256 => sha256_hint(&data, data_len_bytes), From abce4bacd61a9872c31e4c3411c6400c44c656ab Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 5 Mar 2026 13:55:40 +0100 Subject: [PATCH 699/782] Implement assembly fcall for input ready --- core/src/zisk_rom_2_asm.rs | 102 +++++- emulator-asm/src/c_provided.c | 98 +++++- emulator-asm/src/c_provided.hpp | 3 +- emulator-asm/src/client.c | 168 ++++----- emulator-asm/src/configuration.c | 25 ++ emulator-asm/src/globals.c | 6 +- emulator-asm/src/globals.hpp | 6 +- emulator-asm/src/server.c | 322 ++++++++++-------- emulator/src/emu_context.rs | 12 +- ziskos/entrypoint/src/zisklib/fcalls/input.rs | 2 +- 10 files changed, 491 insertions(+), 253 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index ce5c61cce..0d1f825b3 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -3,10 +3,13 @@ //! Generates i86_64 assembly code that implements the Zisk ROM program use std::path::Path; +use ziskos_hints::zisklib::FCALL_INPUT_READY_ID; + use crate::{ zisk_ops::ZiskOp, AsmGenerationMethod, ZiskInst, ZiskRom, EXTRA_PARAMS_ADDR, - FLOAT_LIB_ROM_ADDR, FREE_INPUT_ADDR, M64, P2_32, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, SRC_C, - SRC_IMM, SRC_IND, SRC_MEM, SRC_REG, SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, STORE_REG, + FLOAT_LIB_ROM_ADDR, FREE_INPUT_ADDR, INPUT_ADDR, M64, P2_32, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY, + SRC_C, SRC_IMM, SRC_IND, SRC_MEM, SRC_REG, SRC_STEP, STORE_IND, STORE_MEM, STORE_NONE, + STORE_REG, }; // Regs rax, rcx, rdx, rdi, rsi, rsp, and r8-r11 are caller-save, not saved across function calls. @@ -150,6 +153,7 @@ pub struct ZiskAsmContext { mem_precompile_results_address: String, // Address where precompile results are read from mem_precompile_written_address: String, // Address where precompile written counter is stored mem_precompile_read_address: String, // Address where precompile read counter is stored + mem_input_written_address: String, // Address where input written counter is stored comments: bool, // true if we want to generate comments in the assembly source code boc: String, // begin of comment: '/*', ';', '#', etc. @@ -555,6 +559,7 @@ impl ZiskRom2Asm { ctx.mem_precompile_written_address = format!("qword {}[0x70000000]", ctx.ptr); ctx.mem_precompile_read_address = format!("qword {}[0x70001000]", ctx.ptr); } + ctx.mem_input_written_address = format!("qword {}[0x70000010]", ctx.ptr); // Preamble *code += ".intel_syntax noprefix\n"; @@ -6182,23 +6187,28 @@ impl ZiskRom2Asm { ctx.comment_str("ctx.function id = a") ); - // Set the fcall context address as the first parameter - *code += &format!( - "\tlea rdi, {} {}\n", - ctx.fcall_ctx, - ctx.comment_str("rdi = fcall context") - ); - // Get result from precompile results data - if ctx.precompile_results_fcall() { - Self::precompile_results_fcall(ctx, code, unusual_code, "rdi"); + if ctx.a.constant_value == FCALL_INPUT_READY_ID as u64 { + Self::wait_for_input_ready(ctx, code, unusual_code, REG_C); } else { - // Call the fcall function - Self::push_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); - *code += "\tcall _opcode_fcall\n"; - Self::pop_internal_registers(ctx, code, false); - //Self::assert_rsp_is_aligned(ctx, code); + // Set the fcall context address as the first parameter + *code += &format!( + "\tlea rdi, {} {}\n", + ctx.fcall_ctx, + ctx.comment_str("rdi = fcall context") + ); + + // Get result from precompile results data + if ctx.precompile_results_fcall() { + Self::precompile_results_fcall(ctx, code, unusual_code, "rdi"); + } else { + // Call the fcall function + Self::push_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + *code += "\tcall _opcode_fcall\n"; + Self::pop_internal_registers(ctx, code, false); + //Self::assert_rsp_is_aligned(ctx, code); + } } // If ctx.result_size == 0 => free_input = 0 @@ -8788,6 +8798,64 @@ impl ZiskRom2Asm { ctx.wait_for_prec_counter += 1; } + // Waits for the requested reg_address input address (and previous) to be available, waiting + // if necessary address in reg_address, + fn wait_for_input_ready( + ctx: &mut ZiskAsmContext, + code: &mut String, + unusual_code: &mut String, + reg_address: &str, // REG_C + ) { + *code += &ctx.full_line_comment("Wait for input data available".to_string()); + + // Calculate number of bytes until the requested address from the current read address + // required_bytes = (required_address - INPUT_ADDR - 8 + 1 + 7) & ~0x7; + // + 1 because required_address is the address of the last required byte + // + 7 & ~0x7 because if we require any byte of the last u64, we need to wait for the whole u64 to be available + *code += + &format!("\tmov rdi, {} {}\n", reg_address, ctx.comment_str("rdi = required_address")); + *code += &format!( + "\tmov {}, {} {}\n", + REG_AUX, + INPUT_ADDR, + ctx.comment_str("aux = INPUT_ADDRESS") + ); + *code += &format!( + "\tsub rdi, {} {}\n", + REG_AUX, + ctx.comment_str("rdi = required_address - INPUT_ADDRESS") + ); + *code += &format!("\tand rdi, ~0x7 {}\n", ctx.comment_str("rdi &= 0x7")); + + // if input_written == input_ready then call wait_for_input_avail + *code += &format!( + "\tmov {}, {} {}\n", + REG_AUX, + ctx.mem_input_written_address, + ctx.comment_str("aux = input_written") + ); + *code += &format!( + "\tcmp rdi, {} {}\n", + ctx.mem_input_written_address, + ctx.comment_str("required <= written") + ); + *code += &format!( + "\tja pc_{:x}_wait_for_input_avail {}\n", + ctx.pc, + ctx.comment_str("if there is data, done") + ); + *code += &format!("pc_{:x}_wait_for_input_avail_done:\n", ctx.pc); + + // Call wait_for_input_avail() + *unusual_code += &format!("pc_{:x}_wait_for_input_avail:\n", ctx.pc); + Self::push_internal_registers(ctx, unusual_code, false); + *unusual_code += "\tcall _wait_for_input_avail\n"; + *unusual_code += "\tcmp rax, 0\n"; + *unusual_code += "\tjne execute_pop_internal_regs_and_end\n"; + Self::pop_internal_registers(ctx, unusual_code, false); + *unusual_code += &format!("\tjmp pc_{:x}_wait_for_input_avail_done\n", ctx.pc); + } + /*******************/ /* CHUNK START/END */ /*******************/ diff --git a/emulator-asm/src/c_provided.c b/emulator-asm/src/c_provided.c index 643dc1ad0..1b1edcdf7 100644 --- a/emulator-asm/src/c_provided.c +++ b/emulator-asm/src/c_provided.c @@ -282,7 +282,7 @@ extern void _realloc_trace (void) int _wait_for_prec_avail (void) { // Increment wait counter - wait_counter++; + wait_prec_avail_counter++; // Sync control output shared memory so that the writer can see the precompile reads we have // done, and thus update the precompile_written_address if needed @@ -380,4 +380,100 @@ int _wait_for_prec_avail (void) fflush(stdout); fflush(stderr); exit(-1); +} + +/****************************/ +/* WAIT FOR INPUT AVAILABLE */ +/****************************/ + +// Called by the assembly when input_written == input_read, to wait for new input to be available +int _wait_for_input_avail (uint64_t required_input_bytes) +{ + // Increment wait counter + wait_input_avail_counter++; + + // Make sure the input available semaphore is reset before checking the condition, + // since the caller may have posted it (even several times) before we called sem_wait() + while (sem_trywait(sem_input_avail) == 0) {/*printf("Purging sem_input_avail\n");*/}; + + // Sync control input shared memory so that we can see the latest input_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check if there is already input data available + if (*input_written_address > required_input_bytes) + { + // Sync input shared memory + if (msync((void *)shmem_input_address, MAX_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + return 0; + } + + // Wait again, but blocking this time + while (true) + { + struct timespec ts; + int result = clock_gettime(CLOCK_REALTIME, &ts); + if (result == -1) + { + printf("ERROR: wait_for_input_avail() failed calling clock_gettime() errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + ts.tv_sec += 5; // 5 seconds timeout + + //printf("_wait_for_input_avail() calling sem_wait input_written_address=%lu required_input_bytes=%lu\n", *input_written_address, required_input_bytes); + result = sem_timedwait(sem_input_avail, &ts); + //printf("_wait_for_input_avail() called sem_wait input_written_address=%lu required_input_bytes=%lu\n", *input_written_address, required_input_bytes); + if ((result == -1) && (errno != ETIMEDOUT)) + { + printf("ERROR: wait_for_input_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_input_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync control input shared memory so that we can see the latest input_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (*precompile_exit_address != 0) + { + printf("ERROR: wait_for_input_avail() found precompile_exit_address=%lu\n", *precompile_exit_address); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (*input_written_address > required_input_bytes) + { + // Sync input shared memory + if (msync((void *)shmem_input_address, MAX_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + return 0; + } + } + + printf("ERROR: wait_for_input_avail() unreachable code\n"); + fflush(stdout); + fflush(stderr); + exit(-1); } \ No newline at end of file diff --git a/emulator-asm/src/c_provided.hpp b/emulator-asm/src/c_provided.hpp index 1b67b537d..4d56a7429 100644 --- a/emulator-asm/src/c_provided.hpp +++ b/emulator-asm/src/c_provided.hpp @@ -7,6 +7,7 @@ extern int _print_regs(); extern int _print_pc (uint64_t pc, uint64_t c); extern void _chunk_done(); extern void _realloc_trace (void); -int _wait_for_prec_avail (void); +extern int _wait_for_prec_avail (void); +extern int _wait_for_input_avail (uint64_t required_input_bytes); #endif // EMULATOR_ASM_C_PROVIDED_HPP \ No newline at end of file diff --git a/emulator-asm/src/client.c b/emulator-asm/src/client.c index 544ddb9e5..35e731e24 100644 --- a/emulator-asm/src/client.c +++ b/emulator-asm/src/client.c @@ -114,87 +114,6 @@ void client_setup (void) if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); - /*****************/ - /* CONTROL INPUT */ - /*****************/ - - // Create the control input shared memory - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR, 0666); - if (shmem_control_input_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map control input address space - if (verbose) gettimeofday(&start_time, NULL); - void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_INPUT_ADDR) - { - printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_control_input_address = (uint64_t *)pControl; - precompile_written_address = &shmem_control_input_address[0]; - precompile_exit_address = &shmem_control_input_address[1]; - if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); - - /*****************/ - /* CONTROL OUTPUT */ - /*****************/ - - // Create the control input shared memory - shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR, 0666); - if (shmem_control_output_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map control input address space - if (verbose) gettimeofday(&start_time, NULL); - pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_OUTPUT_ADDR) - { - printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_control_output_address = (uint64_t *)pControl; - precompile_read_address = &shmem_control_output_address[0]; - if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); - /*************************/ /* PRECOMPILE SEMAPHORES */ /*************************/ @@ -225,6 +144,88 @@ void client_setup (void) } if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_read_name); } + + /*****************/ + /* CONTROL INPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map control input address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_INPUT_ADDR) + { + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; + input_written_address = &shmem_control_input_address[2]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + + /*****************/ + /* CONTROL OUTPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR, 0666); + if (shmem_control_output_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map control input address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); } typedef enum { @@ -638,6 +639,9 @@ void client_run (void) exit(-1); } + // Set written counter + *input_written_address = input_data_size + 8; // in bytes, including the 8 bytes of input size at the beginning + #ifdef DEBUG gettimeofday(&stop_time, NULL); duration = TimeDiff(start_time, stop_time); @@ -651,7 +655,7 @@ void client_run (void) /*****************************/ if (precompile_results_enabled) { - // reset written counter + // Reset written counter *precompile_written_address = 0; //client_write_precompile_results(); diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index ef33c7308..83cb93ba9 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -525,12 +525,15 @@ void configure (void) strcat(sem_prec_avail_name, "_FT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_FT_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_FT_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, ""); strcpy(sem_chunk_done_name, ""); @@ -570,12 +573,15 @@ void configure (void) strcat(sem_prec_avail_name, "_MT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MT_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MT_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); @@ -618,12 +624,15 @@ void configure (void) strcat(sem_prec_avail_name, "_RH_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_RH_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_RH_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_RH_output"); @@ -666,12 +675,15 @@ void configure (void) strcat(sem_prec_avail_name, "_MA_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MA_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MA_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MA_output"); @@ -706,6 +718,7 @@ void configure (void) strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CH_output"); strcpy(sem_chunk_done_name, shm_prefix); @@ -756,12 +769,15 @@ void configure (void) strcat(sem_prec_avail_name, "_ZP_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_ZP_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_ZP_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_ZP_output"); @@ -804,12 +820,15 @@ void configure (void) strcat(sem_prec_avail_name, "_MO_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MO_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MO_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MO_output"); @@ -840,6 +859,7 @@ void configure (void) strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CM_output"); strcpy(sem_chunk_done_name, ""); @@ -880,12 +900,15 @@ void configure (void) strcat(sem_prec_avail_name, "_MT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MT_prec_read"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MT_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); @@ -916,6 +939,7 @@ void configure (void) strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); + strcpy(sem_input_avail_name, ""); strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_CA_output"); strcpy(sem_chunk_done_name, ""); @@ -973,6 +997,7 @@ void configure (void) printf("\tsem_shutdown_done=%s\n", sem_shutdown_done_name); printf("\tsem_prec_avail=%s\n", sem_prec_avail_name); printf("\tsem_prec_read=%s\n", sem_prec_read_name); + printf("\tsem_input_avail=%s\n", sem_input_avail_name); printf("\tmap_locked_flag=%d\n", map_locked_flag); printf("\toutput=%u\n", output); printf("\tprecompile_results_enabled=%u\n", precompile_results_enabled); diff --git a/emulator-asm/src/globals.c b/emulator-asm/src/globals.c index adb546b41..333c60c5d 100644 --- a/emulator-asm/src/globals.c +++ b/emulator-asm/src/globals.c @@ -36,6 +36,7 @@ char sem_prec_avail_name[128] = {0}; char sem_prec_read_name[128] = {0}; char sem_chunk_done_name[128] = {0}; char sem_shutdown_done_name[128] = {0}; +char sem_input_avail_name[128] = {0}; char file_lock_name[128] = {0}; char log_name[128] = {0}; bool call_chunk_done = false; @@ -87,12 +88,14 @@ void * shmem_precompile_address = NULL; // Precompile results semaphores sem_t * sem_prec_avail = NULL; sem_t * sem_prec_read = NULL; +sem_t * sem_input_avail = NULL; // Control input shared memory int shmem_control_input_fd = -1; uint64_t * shmem_control_input_address = NULL; volatile uint64_t * precompile_written_address = NULL; volatile uint64_t * precompile_exit_address = NULL; +volatile uint64_t * input_written_address = NULL; // Control output shared memory int shmem_control_output_fd = -1; @@ -114,7 +117,8 @@ uint64_t assembly_duration; // Counters used in functions called from assembly code uint64_t realloc_counter = 0; -uint64_t wait_counter = 0; +uint64_t wait_prec_avail_counter = 0; +uint64_t wait_input_avail_counter = 0; uint64_t print_pc_counter = 0; // Chunk player globals diff --git a/emulator-asm/src/globals.hpp b/emulator-asm/src/globals.hpp index 3d1bf6ecf..d9db361e2 100644 --- a/emulator-asm/src/globals.hpp +++ b/emulator-asm/src/globals.hpp @@ -41,6 +41,7 @@ extern char sem_prec_avail_name[128]; extern char sem_prec_read_name[128]; extern char sem_chunk_done_name[128]; extern char sem_shutdown_done_name[128]; +extern char sem_input_avail_name[128]; extern char file_lock_name[128]; extern char log_name[128]; extern bool call_chunk_done; @@ -116,12 +117,14 @@ extern void * shmem_precompile_address; // Precompile results semaphores extern sem_t * sem_prec_avail; extern sem_t * sem_prec_read; +extern sem_t * sem_input_avail; // Control input shared memory extern int shmem_control_input_fd; extern uint64_t * shmem_control_input_address; extern volatile uint64_t * precompile_written_address; extern volatile uint64_t * precompile_exit_address; +extern volatile uint64_t * input_written_address; // Control output shared memory extern int shmem_control_output_fd; @@ -143,7 +146,8 @@ extern uint64_t assembly_duration; // Counters used in functions called from assembly code extern uint64_t realloc_counter; -extern uint64_t wait_counter; +extern uint64_t wait_prec_avail_counter; +extern uint64_t wait_input_avail_counter; extern uint64_t print_pc_counter; // Chunk player globals diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index e16ddb624..3de9dd451 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -223,172 +223,173 @@ void server_setup (void) precompile_results_address = (uint64_t *)pPrecompile; if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); - /*****************/ - /* CONTROL INPUT */ - /*****************/ - - if (!open_input_shm) - { - // Make sure the precompile results shared memory is deleted - shm_unlink(shmem_control_input_name); - - // Create the control shared memory - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR | O_CREAT, 0666); - if (shmem_control_input_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Size it - result = ftruncate(shmem_control_input_fd, CONTROL_INPUT_SIZE); - if (result != 0) - { - printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } + /*************************/ + /* PRECOMPILE SEMAPHORES */ + /*************************/ - // Sync - fsync(shmem_control_input_fd); + // Create the semaphore for precompile results available signal + assert(strlen(sem_prec_avail_name) > 0); - // Close the descriptor - if (close(shmem_control_input_fd) != 0) - { - printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } + sem_unlink(sem_prec_avail_name); - // Open the control input shared memory as read-only - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY, 0666); - if (shmem_control_input_fd < 0) + sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_avail == SEM_FAILED) { - printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } + if (verbose) printf("sem_open(%s) succeeded sem_prec_avail=%p\n", sem_prec_avail_name, sem_prec_avail); - // Map precompile address space - if (verbose) gettimeofday(&start_time, NULL); - void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_INPUT_ADDR) + // Create the semaphore for precompile results read signal + assert(strlen(sem_prec_read_name) > 0); + + sem_unlink(sem_prec_read_name); + + sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_read == SEM_FAILED) { - printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } - shmem_control_input_address = (uint64_t *)pControl; - precompile_written_address = &shmem_control_input_address[0]; - precompile_exit_address = &shmem_control_input_address[1]; - if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + if (verbose) printf("sem_open(%s) succeeded sem_prec_read=%p\n", sem_prec_read_name, sem_prec_read); + } - /******************/ - /* CONTROL OUTPUT */ - /******************/ + /*****************/ + /* CONTROL INPUT */ + /*****************/ + if (!open_input_shm) + { // Make sure the precompile results shared memory is deleted - shm_unlink(shmem_control_output_name); + shm_unlink(shmem_control_input_name); // Create the control shared memory - shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR | O_CREAT, 0666); - if (shmem_control_output_fd < 0) + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_input_fd < 0) { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } // Size it - result = ftruncate(shmem_control_output_fd, CONTROL_OUTPUT_SIZE); + result = ftruncate(shmem_control_input_fd, CONTROL_INPUT_SIZE); if (result != 0) { - printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } - // Map precompile address space - if (verbose) gettimeofday(&start_time, NULL); - pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_OUTPUT_ADDR) + // Sync + fsync(shmem_control_input_fd); + + // Close the descriptor + if (close(shmem_control_input_fd) != 0) { - printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); fflush(stdout); fflush(stderr); exit(-1); } - shmem_control_output_address = (uint64_t *)pControl; - precompile_read_address = &shmem_control_output_address[0]; - if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); + } - /*************************/ - /* PRECOMPILE SEMAPHORES */ - /*************************/ + // Open the control input shared memory as read-only + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - // Create the semaphore for precompile results available signal - assert(strlen(sem_prec_avail_name) > 0); + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_INPUT_ADDR) + { + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; + input_written_address = &shmem_control_input_address[2]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); - sem_unlink(sem_prec_avail_name); + /******************/ + /* CONTROL OUTPUT */ + /******************/ - sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_prec_avail == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded sem_prec_avail=%p\n", sem_prec_avail_name, sem_prec_avail); + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_output_name); - // Create the semaphore for precompile results read signal - assert(strlen(sem_prec_read_name) > 0); + // Create the control shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_output_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - sem_unlink(sem_prec_read_name); + // Size it + result = ftruncate(shmem_control_output_fd, CONTROL_OUTPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } - sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_prec_read == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded sem_prec_read=%p\n", sem_prec_read_name, sem_prec_read); + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); /*******/ /* RAM */ @@ -536,7 +537,25 @@ void server_setup (void) fflush(stderr); exit(-1); } - if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); + if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); // Create the semaphore for input available signal + + /***********************/ + /* SEM INPUT AVAILABLE */ + /***********************/ + + assert(strlen(sem_input_avail_name) > 0); + + sem_unlink(sem_input_avail_name); + + sem_input_avail = sem_open(sem_input_avail_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_input_avail == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_input_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded sem_input_avail=%p\n", sem_input_avail_name, sem_input_avail); } void server_reset_fast (void) @@ -677,10 +696,11 @@ void server_run (void) uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; - printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", + printf("Duration = %lu us, realloc counter = %lu, wait prec counter = %lu, wait input counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", duration, realloc_counter, - wait_counter, + wait_prec_avail_counter, + wait_input_avail_counter, steps, step_duration_ns, step_tp_sec, @@ -886,28 +906,6 @@ void server_cleanup (void) printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); } - // Cleanup CONTROL - result = munmap((void *)shmem_control_input_address, CONTROL_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_control_input_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - } - result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_control_output_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); - } - // Semaphores cleanup result = sem_close(sem_prec_avail); if (result == -1) @@ -929,6 +927,33 @@ void server_cleanup (void) { printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); } + result = sem_close(sem_input_avail); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_input_avail_name, errno, strerror(errno)); + } + } + + // Cleanup CONTROL + result = munmap((void *)shmem_control_input_address, CONTROL_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_input_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + } + result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_output_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); } // Cleanup trace @@ -949,6 +974,13 @@ void server_cleanup (void) } } + // Cleanup input available semaphore + result = sem_unlink(sem_input_avail_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_input_avail_name, errno, strerror(errno)); + } + // Post shutdown done semaphore result = sem_post(sem_shutdown_done); if (result == -1) diff --git a/emulator/src/emu_context.rs b/emulator/src/emu_context.rs index 997e1a51c..350144f5f 100644 --- a/emulator/src/emu_context.rs +++ b/emulator/src/emu_context.rs @@ -37,16 +37,20 @@ impl EmuContext { }; // Check the input data size is inside the proper range - if input.len() > (options.max_input_mem - 16) as usize { + if input.len() > (options.max_input_mem - 8) as usize { panic!("EmuContext::new() input size too big size={}", input.len()); } // Add the length and input data read sections - ctx.inst_ctx.input_len = input.len() as u64; + ctx.inst_ctx.input_len = (input.len() as u64 + 7) & !7; // Round up to nearest multiple of 8 let free_input = 0u64; ctx.inst_ctx.mem.add_read_section(INPUT_ADDR, &free_input.to_le_bytes()); - ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8, &ctx.inst_ctx.input_len.to_le_bytes()); - ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 16, &input); + ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8, &input); + let zeros_to_write = ctx.inst_ctx.input_len - input.len() as u64; + for i in 0..zeros_to_write { + let zero = [0u8; 1]; + ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8 + input.len() as u64 + i, &zero); + } // Add the write section ctx.inst_ctx.mem.add_write_section(RAM_ADDR, RAM_SIZE); diff --git a/ziskos/entrypoint/src/zisklib/fcalls/input.rs b/ziskos/entrypoint/src/zisklib/fcalls/input.rs index b4ebbadd0..dc9fd7d72 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/input.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/input.rs @@ -17,7 +17,7 @@ pub fn fcall_input_ready(address: &u64) { } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { - ziskos_fcall_param!(address, 1); // Number of inputs + ziskos_fcall_param!(address, 1); // Highest input address required to be ready to read ziskos_fcall!(FCALL_INPUT_READY_ID); } } From 9c5f28a69547dcdd93f78e13a823ebc9c25f57dc Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:56:10 +0000 Subject: [PATCH 700/782] fix input hint code --- common/src/hints.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 8a98a74a1..86d5abf3d 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -59,7 +59,7 @@ pub const CTRL_CANCEL: u32 = 0x0002; pub const CTRL_ERROR: u32 = 0x0003; // === INPUT HINT CODES === -pub const HINT_INPUT: u32 = 0xF000; +pub const HINT_INPUT: u32 = 0xF0000; // === BUILT-IN HINT CODES === // SHA256 hint codes From d431024d847dc35aaeaabda143f3d19c0589cd9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Thu, 5 Mar 2026 16:02:55 +0100 Subject: [PATCH 701/782] Feature/input stream sdk merge (#832) * Input stream sdk * Minor changes --- common/src/io/stdin/memory.rs | 38 ++++++++++++++- core/src/zisk_ops.rs | 6 +-- emulator/src/emu_context.rs | 10 +--- examples/sha-hasher/host/bin/execute.rs | 2 +- examples/sha-hasher/host/src/main.rs | 2 +- ziskos/entrypoint/src/io.rs | 2 + ziskos/entrypoint/src/lib.rs | 48 ++++++++++++++++--- ziskos/entrypoint/src/zisklib/fcalls/input.rs | 2 +- ziskos/entrypoint/src/zisklib/fcalls/mod.rs | 1 + 9 files changed, 88 insertions(+), 23 deletions(-) diff --git a/common/src/io/stdin/memory.rs b/common/src/io/stdin/memory.rs index 89558b3be..5ed8d5653 100644 --- a/common/src/io/stdin/memory.rs +++ b/common/src/io/stdin/memory.rs @@ -55,15 +55,51 @@ impl ZiskIO for ZiskMemoryStdin { fn write(&self, data: &T) { let mut tmp = Vec::new(); bincode::serialize_into(&mut tmp, data).expect("Failed to serialize data into memory"); + + // Calculate padding for 8-byte alignment + let data_len = tmp.len(); + let total_len = 8 + data_len; // header + data + let padding = (8 - (total_len % 8)) % 8; + + // Write 8-byte length header (includes padding) + let len_bytes = data_len.to_le_bytes(); + + self.data.lock().unwrap().extend_from_slice(&len_bytes); self.data.lock().unwrap().extend_from_slice(&tmp); + + // Add padding + if padding > 0 { + self.data.lock().unwrap().extend_from_slice(&vec![0u8; padding]); + } + let mut cursor = self.cursor.lock().unwrap(); + cursor.get_mut().extend_from_slice(&len_bytes); cursor.get_mut().extend_from_slice(&tmp); + if padding > 0 { + cursor.get_mut().extend_from_slice(&vec![0u8; padding]); + } } fn write_slice(&self, data: &[u8]) { - let mut cursor = self.cursor.lock().unwrap(); + let data_len = data.len(); + let total_len = 8 + data_len; + let padding = (8 - (total_len % 8)) % 8; + + let len_bytes = data_len.to_le_bytes(); + + self.data.lock().unwrap().extend_from_slice(&len_bytes); self.data.lock().unwrap().extend_from_slice(data); + + if padding > 0 { + self.data.lock().unwrap().extend_from_slice(&vec![0u8; padding]); + } + + let mut cursor = self.cursor.lock().unwrap(); + cursor.get_mut().extend_from_slice(&len_bytes); cursor.get_mut().extend_from_slice(data); + if padding > 0 { + cursor.get_mut().extend_from_slice(&vec![0u8; padding]); + } } fn save(&self, path: &Path) -> Result<()> { diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 5a8a764d9..45945c17a 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -2501,7 +2501,7 @@ pub fn opc_fcall(ctx: &mut InstContext) { let function_id = ctx.a; let iresult = if function_id == FCALL_INPUT_READY_ID as u64 { - let required_address = ctx.fcall.parameters[0] as u64; + let required_address = ctx.fcall.parameters[0]; if required_address < INPUT_ADDR + 8 { panic!( "opc_fcall() FCALL_INPUT_READY_ID called with required_address {:#x} < {:#x}", @@ -2509,11 +2509,11 @@ pub fn opc_fcall(ctx: &mut InstContext) { INPUT_ADDR + 8 ); } - if required_address >= INPUT_ADDR + MAX_INPUT_SIZE as u64 - 1 { + if required_address >= INPUT_ADDR + MAX_INPUT_SIZE - 1 { panic!( "opc_fcall() FCALL_INPUT_READY_ID called with required_address {:#x} > {:#x}", required_address, - INPUT_ADDR + MAX_INPUT_SIZE as u64 - 1 + INPUT_ADDR + MAX_INPUT_SIZE - 1 ); } diff --git a/emulator/src/emu_context.rs b/emulator/src/emu_context.rs index 350144f5f..69f58e7ba 100644 --- a/emulator/src/emu_context.rs +++ b/emulator/src/emu_context.rs @@ -41,16 +41,8 @@ impl EmuContext { panic!("EmuContext::new() input size too big size={}", input.len()); } - // Add the length and input data read sections ctx.inst_ctx.input_len = (input.len() as u64 + 7) & !7; // Round up to nearest multiple of 8 - let free_input = 0u64; - ctx.inst_ctx.mem.add_read_section(INPUT_ADDR, &free_input.to_le_bytes()); - ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8, &input); - let zeros_to_write = ctx.inst_ctx.input_len - input.len() as u64; - for i in 0..zeros_to_write { - let zero = [0u8; 1]; - ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8 + input.len() as u64 + i, &zero); - } + ctx.inst_ctx.mem.add_read_section(INPUT_ADDR, &input); // Add the write section ctx.inst_ctx.mem.add_write_section(RAM_ADDR, RAM_SIZE); diff --git a/examples/sha-hasher/host/bin/execute.rs b/examples/sha-hasher/host/bin/execute.rs index ef728d0a6..05a599209 100644 --- a/examples/sha-hasher/host/bin/execute.rs +++ b/examples/sha-hasher/host/bin/execute.rs @@ -22,7 +22,7 @@ fn main() -> Result<()> { // Create a `ProverClient` method. println!("Building prover client..."); - let client = ProverClient::builder().asm().base_port(54321).build().unwrap(); + let client = ProverClient::builder().emu().verify_constraints().build().unwrap(); println!("Setting up program..."); let (pk, _) = client.setup(&ELF)?; diff --git a/examples/sha-hasher/host/src/main.rs b/examples/sha-hasher/host/src/main.rs index 7a370ad63..590d230c1 100644 --- a/examples/sha-hasher/host/src/main.rs +++ b/examples/sha-hasher/host/src/main.rs @@ -21,7 +21,7 @@ fn main() -> Result<()> { println!( "ZisK has executed program with {} cycles in {:?}", - result.executor_summary.steps, result.total_duration + result.get_execution_steps(), result.get_duration() ); let proof_opts = ProofOpts::default().minimal_memory(); diff --git a/ziskos/entrypoint/src/io.rs b/ziskos/entrypoint/src/io.rs index 7c17956ab..a1d27a449 100644 --- a/ziskos/entrypoint/src/io.rs +++ b/ziskos/entrypoint/src/io.rs @@ -19,6 +19,8 @@ use serde::{de::DeserializeOwned, Serialize}; /// /// let data: MyStruct = ziskos::io::read(); /// ``` +/// +/// Note: This uses zero-copy deserialization on zkvm to avoid unnecessary data copies. pub fn read() -> T { let bytes = read_input_slice(); bincode::deserialize(&bytes).expect("Deserialization failed") diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 87b069718..11b45b290 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -48,9 +48,48 @@ macro_rules! entrypoint { #[allow(unused_imports)] use crate::ziskos_definitions::ziskos_config::*; +/// Pointer to the current position in the input buffer. +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +static mut INPUT_POS: usize = 0; + +/// Reset the input position to the beginning. +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +pub fn read_reset() { + unsafe { INPUT_POS = 0 }; +} + +/// Read a slice directly from INPUT_ADDR without copying (zero-copy). +/// +/// This returns a slice pointing directly to the input memory region. +/// Use this when you want to deserialize directly without an intermediate copy. +/// The INPUT_POS is advanced after this call. +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +pub fn read_slice_zerocopy<'a>() -> &'a [u8] { + // SAFETY: Single threaded, so nothing else can touch INPUT_POS while we're working. + let input_pos = unsafe { INPUT_POS }; + let addr = (INPUT_ADDR as usize) + input_pos; + + // Ensure the 8-byte length prefix is ready and read it + crate::zisklib::fcall_input_ready(&((addr + 7) as u64)); + let len = unsafe { + let bytes = core::slice::from_raw_parts(addr as *const u8, 8); + u64::from_le_bytes(bytes.try_into().unwrap()) as usize + }; + + // Ensure the data is ready + let data_addr = addr + 8; + crate::zisklib::fcall_input_ready(&((data_addr + len + 7) as u64)); + + // Update input position: move past length (8 bytes) + data + unsafe { INPUT_POS = input_pos + 8 + (len + 7) & !0x7 }; + + // Return slice pointing directly to the input data (zero-copy) + unsafe { core::slice::from_raw_parts(data_addr as *const u8, len) } +} + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] pub(crate) fn read_input() -> Vec { - read_input_slice().to_vec() + read_slice_zerocopy().to_vec() } #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] @@ -66,12 +105,7 @@ pub(crate) fn read_input() -> Vec { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] pub fn read_input_slice<'a>() -> &'a [u8] { - // Create a slice of the first 8 bytes to get the size - let bytes = unsafe { core::slice::from_raw_parts((INPUT_ADDR as *const u8).add(8), 8) }; - // Convert the slice to a u64 (little-endian) - let size: u64 = u64::from_le_bytes(bytes.try_into().unwrap()); - - unsafe { core::slice::from_raw_parts((INPUT_ADDR as *const u8).add(16), size as usize) } + read_slice_zerocopy() } #[allow(unused)] diff --git a/ziskos/entrypoint/src/zisklib/fcalls/input.rs b/ziskos/entrypoint/src/zisklib/fcalls/input.rs index dc9fd7d72..0a25488a1 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/input.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/input.rs @@ -17,7 +17,7 @@ pub fn fcall_input_ready(address: &u64) { } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] { - ziskos_fcall_param!(address, 1); // Highest input address required to be ready to read + ziskos_fcall_param!(*address, 1); // Number of inputs ziskos_fcall!(FCALL_INPUT_READY_ID); } } diff --git a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs index c28d61487..769a702f7 100644 --- a/ziskos/entrypoint/src/zisklib/fcalls/mod.rs +++ b/ziskos/entrypoint/src/zisklib/fcalls/mod.rs @@ -39,6 +39,7 @@ pub use big_int_div::*; pub use bin_decomp::*; pub use bls12_381::*; pub use bn254::*; +pub use input::*; pub use msb_pos_256::*; pub use msb_pos_384::*; pub use secp256k1::*; From 86e1216301f47c1f46296af7f09f376c342f3572 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 5 Mar 2026 16:03:09 +0000 Subject: [PATCH 702/782] Fix emu context --- emulator/src/emu_context.rs | 9 ++++++++- ziskos/entrypoint/src/lib.rs | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/emulator/src/emu_context.rs b/emulator/src/emu_context.rs index 69f58e7ba..56d24da40 100644 --- a/emulator/src/emu_context.rs +++ b/emulator/src/emu_context.rs @@ -42,7 +42,14 @@ impl EmuContext { } ctx.inst_ctx.input_len = (input.len() as u64 + 7) & !7; // Round up to nearest multiple of 8 - ctx.inst_ctx.mem.add_read_section(INPUT_ADDR, &input); + let free_input = 0u64; + ctx.inst_ctx.mem.add_read_section(INPUT_ADDR, &free_input.to_le_bytes()); + ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8, &input); + let zeros_to_write = ctx.inst_ctx.input_len - input.len() as u64; + for i in 0..zeros_to_write { + let zero = [0u8; 1]; + ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8 + input.len() as u64 + i, &zero); + } // Add the write section ctx.inst_ctx.mem.add_write_section(RAM_ADDR, RAM_SIZE); diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 11b45b290..962412e43 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -50,12 +50,12 @@ use crate::ziskos_definitions::ziskos_config::*; /// Pointer to the current position in the input buffer. #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -static mut INPUT_POS: usize = 0; +static mut INPUT_POS: usize = 8; /// Reset the input position to the beginning. #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] pub fn read_reset() { - unsafe { INPUT_POS = 0 }; + unsafe { INPUT_POS = 8 }; } /// Read a slice directly from INPUT_ADDR without copying (zero-copy). From 7d7f4268f50b753c3a4fd4350c08166851bb8cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20i=20Buxadera?= Date: Thu, 5 Mar 2026 16:19:59 +0100 Subject: [PATCH 703/782] Input stream write proof --- Cargo.lock | 106 ++++---- Cargo.toml | 16 +- cli/Cargo.toml | 1 - common/src/io/stdin/file.rs | 6 + common/src/io/stdin/memory.rs | 8 +- common/src/io/stdin/null.rs | 3 + common/src/io/stdin/zisk_stdin.rs | 15 ++ emulator/src/emu.rs | 1 + examples/Cargo.lock | 127 +++++---- examples/Cargo.toml | 11 +- examples/aggregation/guest/Cargo.lock | 293 +++++++++++++++++++++ examples/aggregation/guest/Cargo.toml | 8 + examples/aggregation/guest/src/main.rs | 26 ++ examples/aggregation/guest_agg/Cargo.lock | 293 +++++++++++++++++++++ examples/aggregation/guest_agg/Cargo.toml | 8 + examples/aggregation/guest_agg/src/main.rs | 27 ++ examples/aggregation/host/Cargo.toml | 18 ++ examples/aggregation/host/build.rs | 4 + examples/aggregation/host/src/main.rs | 63 +++++ sdk/Cargo.toml | 2 +- sdk/src/prover/asm.rs | 9 + sdk/src/prover/backend.rs | 34 ++- sdk/src/prover/emu.rs | 9 + sdk/src/prover/mod.rs | 16 ++ sdk/src/verifier.rs | 14 +- verifier/Cargo.toml | 2 - verifier/src/verifier.rs | 35 +-- ziskos-hints/Cargo.toml | 1 + ziskos/entrypoint/Cargo.toml | 2 + ziskos/entrypoint/src/io.rs | 11 +- ziskos/entrypoint/src/lib.rs | 12 +- 31 files changed, 1049 insertions(+), 132 deletions(-) create mode 100644 examples/aggregation/guest/Cargo.lock create mode 100644 examples/aggregation/guest/Cargo.toml create mode 100644 examples/aggregation/guest/src/main.rs create mode 100644 examples/aggregation/guest_agg/Cargo.lock create mode 100644 examples/aggregation/guest_agg/Cargo.toml create mode 100644 examples/aggregation/guest_agg/src/main.rs create mode 100644 examples/aggregation/host/Cargo.toml create mode 100644 examples/aggregation/host/build.rs create mode 100644 examples/aggregation/host/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 4597d554b..5c093b84a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -604,7 +604,7 @@ dependencies = [ "colored", "dirs", "executor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "futures", "indicatif", "mpi", @@ -627,7 +627,6 @@ dependencies = [ "zisk-core", "zisk-pil", "zisk-sdk", - "zisk-verifier", ] [[package]] @@ -1116,9 +1115,9 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "num-traits", ] @@ -1415,7 +1414,7 @@ dependencies = [ "asm-runner", "crossbeam", "data-bus", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "itertools 0.14.0", "mem-common", "mem-planner-cpp", @@ -1467,6 +1466,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fields" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" +dependencies = [ + "cfg-if", + "num-bigint", + "paste", + "serde", +] + [[package]] name = "fields" version = "0.16.0" @@ -2337,7 +2347,7 @@ name = "mem-common" version = "0.16.0" dependencies = [ "clap", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "num-traits", "proofman-common", @@ -2764,10 +2774,10 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "num-traits", "proofman-common", @@ -2886,7 +2896,7 @@ dependencies = [ "ark-secp256k1", "ark-secp256r1", "ark-std", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "lazy_static", "lib-c", "mem-common", @@ -2922,7 +2932,7 @@ dependencies = [ "ark-ff", "ark-secp256k1", "ark-std", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "lazy_static", "lib-c", "mem-common", @@ -2953,7 +2963,7 @@ dependencies = [ name = "precomp-big-int" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "generic-array", "lib-c", "mem-common", @@ -2974,7 +2984,7 @@ dependencies = [ name = "precomp-blake2" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "pil-std-lib", "precompiles-common", @@ -2993,7 +3003,7 @@ dependencies = [ name = "precomp-dma" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "generic-array", "lib-c", "mem-common", @@ -3017,7 +3027,7 @@ name = "precomp-keccakf" version = "0.16.0" dependencies = [ "circuit", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "path-clean", "pil-std-lib", "precompiles-common", @@ -3037,7 +3047,7 @@ dependencies = [ name = "precomp-poseidon2" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "pil-std-lib", "precompiles-common", @@ -3057,7 +3067,7 @@ dependencies = [ name = "precomp-sha256f" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "pil-std-lib", "precompiles-common", @@ -3076,7 +3086,7 @@ dependencies = [ name = "precompiles-common" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "sm-mem", "zisk-common", @@ -3146,7 +3156,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "bincode", "blake3", @@ -3157,7 +3167,7 @@ dependencies = [ "crossbeam-channel", "csv", "curves", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "libloading", "mpi", "num-bigint", @@ -3182,7 +3192,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "bincode", "borsh", @@ -3192,7 +3202,7 @@ dependencies = [ "crossbeam-queue", "csv", "env", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "lazy_static", "libloading", "mpi", @@ -3213,9 +3223,9 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "itoa", "proofman-common", "proofman-starks-lib-c", @@ -3226,7 +3236,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "proc-macro2", "quote", @@ -3236,7 +3246,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "crossbeam-channel", "tracing", @@ -3245,7 +3255,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "bincode", "bytemuck", @@ -3257,10 +3267,10 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "bytemuck", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "proofman-util", "rayon", "tracing", @@ -3625,7 +3635,7 @@ dependencies = [ "anyhow", "blake3", "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "proofman-common", "sm-rom", "tracing", @@ -4047,7 +4057,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "sm-arith" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "pil-std-lib", "proofman-common", @@ -4067,7 +4077,7 @@ dependencies = [ name = "sm-binary" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "pil-std-lib", "proofman-common", @@ -4087,7 +4097,7 @@ name = "sm-frequent-ops" version = "0.16.0" dependencies = [ "clap", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "proofman-common", "proofman-util", @@ -4101,7 +4111,7 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "num-bigint", "pil-std-lib", @@ -4120,7 +4130,7 @@ dependencies = [ name = "sm-mem" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "num-bigint", "num-traits", @@ -4141,7 +4151,7 @@ name = "sm-rom" version = "0.16.0" dependencies = [ "asm-runner", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "itertools 0.14.0", "proofman-common", "proofman-macros", @@ -5709,10 +5719,10 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "libloading", "proofman-common", "proofman-util", @@ -5905,7 +5915,7 @@ version = "0.16.0" dependencies = [ "anyhow", "bincode", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "libc", "mpi", "proofman", @@ -5928,7 +5938,7 @@ name = "zisk-core" version = "0.16.0" dependencies = [ "elf", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "lib-c", "lib-float", "paste", @@ -6031,7 +6041,7 @@ dependencies = [ "clap", "colored", "config", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "precompiles-hints", "proofman", "proofman-common", @@ -6057,7 +6067,7 @@ dependencies = [ name = "zisk-pil" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "proofman-common", "proofman-macros", "rayon", @@ -6074,10 +6084,11 @@ dependencies = [ "bincode", "colored", "executor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "proofman", "proofman-common", "proofman-util", + "proofman-verifier", "rom-setup", "serde", "sha2", @@ -6086,7 +6097,6 @@ dependencies = [ "zisk-common", "zisk-core", "zisk-distributed-common", - "zisk-verifier", "ziskemu", ] @@ -6094,8 +6104,6 @@ dependencies = [ name = "zisk-verifier" version = "0.16.0" dependencies = [ - "anyhow", - "proofman-util", "proofman-verifier", ] @@ -6114,7 +6122,7 @@ dependencies = [ "clap", "criterion 0.5.1", "data-bus", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "memmap2", "num-format", @@ -6143,7 +6151,7 @@ dependencies = [ "bytes", "cfg-if", "ctor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -6160,6 +6168,7 @@ dependencies = [ "tokio", "zisk-common", "zisk-definitions", + "zisk-verifier", ] [[package]] @@ -6169,7 +6178,7 @@ dependencies = [ "anyhow", "bincode", "cfg-if", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -6182,6 +6191,7 @@ dependencies = [ "serde", "sha2", "tiny-keccak", + "zisk-verifier", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2704f738f..6de4573e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,14 +112,14 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } # Proofman Local development # proofman = { path = "../pil2-proofman/proofman" } # proofman-common = { path = "../pil2-proofman/common" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 36284b92b..1350fe385 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -34,7 +34,6 @@ serde = { workspace = true } serde_json = { workspace = true } anyhow = { workspace = true } zisk-sdk = { workspace = true } -zisk-verifier = { workspace = true } clap = { workspace = true } dirs = "6" diff --git a/common/src/io/stdin/file.rs b/common/src/io/stdin/file.rs index 8ae39c0d0..2a9a1aae9 100644 --- a/common/src/io/stdin/file.rs +++ b/common/src/io/stdin/file.rs @@ -68,6 +68,12 @@ impl ZiskIO for ZiskFileStdin { panic!("Write operations are not supported for FileStdin"); } + fn write_proof(&self, _proof: &[u8], _vk: &[u8]) { + // This is a read-only stdin implementation + // Writing is not supported for file-based stdin + panic!("Write operations are not supported for FileStdin"); + } + fn save(&self, _path: &Path) -> Result<()> { // This is a read-only stdin implementation // Saving is not supported for file-based stdin diff --git a/common/src/io/stdin/memory.rs b/common/src/io/stdin/memory.rs index 5ed8d5653..8f33aeed1 100644 --- a/common/src/io/stdin/memory.rs +++ b/common/src/io/stdin/memory.rs @@ -55,7 +55,7 @@ impl ZiskIO for ZiskMemoryStdin { fn write(&self, data: &T) { let mut tmp = Vec::new(); bincode::serialize_into(&mut tmp, data).expect("Failed to serialize data into memory"); - + // Calculate padding for 8-byte alignment let data_len = tmp.len(); let total_len = 8 + data_len; // header + data @@ -102,6 +102,12 @@ impl ZiskIO for ZiskMemoryStdin { } } + fn write_proof(&self, proof: &[u8], vk: &[u8]) { + let proof_len = proof.len() as u64; + self.write_slice(proof); + self.write_slice(vk); + } + fn save(&self, path: &Path) -> Result<()> { std::fs::write(path, self.data.lock().unwrap().as_slice())?; Ok(()) diff --git a/common/src/io/stdin/null.rs b/common/src/io/stdin/null.rs index 12285b0dd..15811b9ff 100644 --- a/common/src/io/stdin/null.rs +++ b/common/src/io/stdin/null.rs @@ -22,6 +22,9 @@ impl ZiskIO for ZiskNullStdin { fn write_slice(&self, _data: &[u8]) { warn!("NullStdin does not support writing"); } + fn write_proof(&self, _proof: &[u8], _vk: &[u8]) { + warn!("NullStdin does not support writing"); + } fn save(&self, _path: &Path) -> Result<()> { warn!("NullStdin does not support saving"); Ok(()) diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index 531c5b309..048825112 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -23,6 +23,9 @@ pub trait ZiskIO: Send + Sync { /// Write a slice of bytes to the buffer. fn write_slice(&self, data: &[u8]); + /// Write proof + fn write_proof(&self, proof: &[u8], vk: &[u8]); + fn save(&self, path: &Path) -> Result<()>; } @@ -81,6 +84,14 @@ impl ZiskIO for ZiskIOVariant { } } + fn write_proof(&self, proof: &[u8], vk: &[u8]) { + match self { + ZiskIOVariant::File(file_stdin) => file_stdin.write_proof(proof, vk), + ZiskIOVariant::Null(null_stdin) => null_stdin.write_proof(proof, vk), + ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write_proof(proof, vk), + } + } + fn save(&self, path: &Path) -> Result<()> { match self { ZiskIOVariant::File(file_stdin) => file_stdin.save(path), @@ -120,6 +131,10 @@ impl ZiskIO for ZiskStdin { self.io.write_slice(data) } + fn write_proof(&self, proof: &[u8], vk: &[u8]) { + self.io.write_proof(proof, vk) + } + fn save(&self, path: &Path) -> Result<()> { self.io.save(path) } diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 37bd108a6..9058e6ab6 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -1537,6 +1537,7 @@ impl<'a> Emu<'a> { self.ctx = self.create_emu_context(inputs.clone(), options); let mut elf = ElfSymbolReader::new(); + println!("READ SYMBOLS={}", options.read_symbols); if options.read_symbols { if let Some(elf_file) = &options.elf { println!("Loading symbols from ELF file: {elf_file}"); diff --git a/examples/Cargo.lock b/examples/Cargo.lock index d10a8cb0f..d50f31427 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -8,6 +8,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aggregation-host" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "sha2", + "zisk-sdk", +] + [[package]] name = "ahash" version = "0.8.12" @@ -814,9 +824,9 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "num-traits", ] @@ -1043,7 +1053,7 @@ dependencies = [ "asm-runner", "crossbeam", "data-bus", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "itertools 0.14.0", "mem-common", "mem-planner-cpp", @@ -1121,6 +1131,17 @@ dependencies = [ "zisk-sdk", ] +[[package]] +name = "fields" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" +dependencies = [ + "cfg-if", + "num-bigint", + "paste", + "serde", +] + [[package]] name = "fields" version = "0.16.0" @@ -1256,6 +1277,22 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "guest" +version = "0.1.0" +dependencies = [ + "byteorder", + "ziskos", +] + +[[package]] +name = "guest-agg" +version = "0.1.0" +dependencies = [ + "byteorder", + "ziskos", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -1662,7 +1699,7 @@ name = "mem-common" version = "0.16.0" dependencies = [ "clap", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "num-traits", "proofman-common", @@ -1979,10 +2016,10 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "num-traits", "proofman-common", @@ -2041,7 +2078,7 @@ dependencies = [ "ark-secp256k1", "ark-secp256r1", "ark-std", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "lazy_static", "lib-c", "mem-common", @@ -2077,7 +2114,7 @@ dependencies = [ "ark-ff", "ark-secp256k1", "ark-std", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "lazy_static", "lib-c", "mem-common", @@ -2108,7 +2145,7 @@ dependencies = [ name = "precomp-big-int" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "generic-array", "lib-c", "mem-common", @@ -2129,7 +2166,7 @@ dependencies = [ name = "precomp-blake2" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "pil-std-lib", "precompiles-common", @@ -2148,7 +2185,7 @@ dependencies = [ name = "precomp-dma" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "generic-array", "lib-c", "mem-common", @@ -2172,7 +2209,7 @@ name = "precomp-keccakf" version = "0.16.0" dependencies = [ "circuit", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "path-clean", "pil-std-lib", "precompiles-common", @@ -2192,7 +2229,7 @@ dependencies = [ name = "precomp-poseidon2" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "pil-std-lib", "precompiles-common", @@ -2212,7 +2249,7 @@ dependencies = [ name = "precomp-sha256f" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "pil-std-lib", "precompiles-common", @@ -2231,7 +2268,7 @@ dependencies = [ name = "precompiles-common" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "sm-mem", "zisk-common", @@ -2300,7 +2337,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "bincode", "blake3", @@ -2311,7 +2348,7 @@ dependencies = [ "crossbeam-channel", "csv", "curves", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "libloading", "mpi", "num-bigint", @@ -2336,7 +2373,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "bincode", "borsh", @@ -2346,7 +2383,7 @@ dependencies = [ "crossbeam-queue", "csv", "env", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "lazy_static", "libloading", "mpi", @@ -2367,9 +2404,9 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "itoa", "proofman-common", "proofman-starks-lib-c", @@ -2380,7 +2417,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "proc-macro2", "quote", @@ -2390,7 +2427,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "crossbeam-channel", "tracing", @@ -2399,7 +2436,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "bincode", "bytemuck", @@ -2411,10 +2448,10 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "bytemuck", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "proofman-util", "rayon", "tracing", @@ -2654,7 +2691,7 @@ dependencies = [ "anyhow", "blake3", "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "proofman-common", "sm-rom", "tracing", @@ -3017,7 +3054,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "sm-arith" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "pil-std-lib", "proofman-common", @@ -3037,7 +3074,7 @@ dependencies = [ name = "sm-binary" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "pil-std-lib", "proofman-common", @@ -3057,7 +3094,7 @@ name = "sm-frequent-ops" version = "0.16.0" dependencies = [ "clap", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", "proofman-common", "proofman-util", @@ -3071,7 +3108,7 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "num-bigint", "pil-std-lib", @@ -3090,7 +3127,7 @@ dependencies = [ name = "sm-mem" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "num-bigint", "num-traits", @@ -3111,7 +3148,7 @@ name = "sm-rom" version = "0.16.0" dependencies = [ "asm-runner", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "itertools 0.14.0", "proofman-common", "proofman-macros", @@ -4385,10 +4422,10 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#cf622d299875c45eebfc4c4b205012ab6bb76875" dependencies = [ "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "libloading", "proofman-common", "proofman-util", @@ -4570,7 +4607,7 @@ version = "0.16.0" dependencies = [ "anyhow", "bincode", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "libc", "mpi", "proofman", @@ -4593,7 +4630,7 @@ name = "zisk-core" version = "0.16.0" dependencies = [ "elf", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "lib-c", "lib-float", "paste", @@ -4635,7 +4672,7 @@ dependencies = [ name = "zisk-pil" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "proofman-common", "proofman-macros", "rayon", @@ -4652,10 +4689,11 @@ dependencies = [ "bincode", "colored", "executor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "proofman", "proofman-common", "proofman-util", + "proofman-verifier", "rom-setup", "serde", "sha2", @@ -4664,7 +4702,6 @@ dependencies = [ "zisk-common", "zisk-core", "zisk-distributed-common", - "zisk-verifier", "ziskemu", ] @@ -4672,8 +4709,6 @@ dependencies = [ name = "zisk-verifier" version = "0.16.0" dependencies = [ - "anyhow", - "proofman-util", "proofman-verifier", ] @@ -4683,7 +4718,7 @@ version = "0.16.0" dependencies = [ "clap", "data-bus", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "mem-common", "memmap2", "num-format", @@ -4712,7 +4747,7 @@ dependencies = [ "bytes", "cfg-if", "ctor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -4729,6 +4764,7 @@ dependencies = [ "tokio", "zisk-common", "zisk-definitions", + "zisk-verifier", ] [[package]] @@ -4738,7 +4774,7 @@ dependencies = [ "anyhow", "bincode", "cfg-if", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -4751,6 +4787,7 @@ dependencies = [ "serde", "sha2", "tiny-keccak", + "zisk-verifier", ] [[package]] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index ff3658290..bae7853f6 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,5 +1,14 @@ [workspace] -members = ["sha-hasher/host", "sha-hasher/guest", "multiple-programs/host", "multiple-programs/guest", "multiple-programs/guest_2"] +members = [ + "sha-hasher/host", + "sha-hasher/guest", + "multiple-programs/host", + "multiple-programs/guest", + "multiple-programs/guest_2", + "aggregation/host", + "aggregation/guest", + "aggregation/guest_agg", +] resolver = "2" diff --git a/examples/aggregation/guest/Cargo.lock b/examples/aggregation/guest/Cargo.lock new file mode 100644 index 000000000..a31235f0c --- /dev/null +++ b/examples/aggregation/guest/Cargo.lock @@ -0,0 +1,293 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lib-c" +version = "0.13.1" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "sha-hasher-guest" +version = "0.1.0" +dependencies = [ + "byteorder", + "sha2", + "ziskos", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ziskos" +version = "0.13.1" +dependencies = [ + "cfg-if", + "getrandom", + "lazy_static", + "lib-c", + "num-bigint", + "num-integer", + "num-traits", + "rand", + "static_assertions", + "tiny-keccak", +] diff --git a/examples/aggregation/guest/Cargo.toml b/examples/aggregation/guest/Cargo.toml new file mode 100644 index 000000000..2f3da51c9 --- /dev/null +++ b/examples/aggregation/guest/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "guest" +version = "0.1.0" +edition = "2021" + +[dependencies] +byteorder = "1.5.0" +ziskos = { path = "../../../ziskos/entrypoint" } diff --git a/examples/aggregation/guest/src/main.rs b/examples/aggregation/guest/src/main.rs new file mode 100644 index 000000000..ed657d058 --- /dev/null +++ b/examples/aggregation/guest/src/main.rs @@ -0,0 +1,26 @@ +// This example program takes a number `n` as input and computes the SHA-256 hash `n` times sequentially. + +// Mark the main function as the entry point for ZisK +#![no_main] +ziskos::entrypoint!(main); + +fn main() { + // Read the input data + let n: u32 = ziskos::io::read(); + + let module = 233; + + ziskos::io::commit(&n); + ziskos::io::commit(&module); + + let mut a = 0; + let mut b = 1; + for _ in 0..n { + let mut c = a + b; + c %= module; + a = b; + b = c; + } + + ziskos::io::commit(&b); +} diff --git a/examples/aggregation/guest_agg/Cargo.lock b/examples/aggregation/guest_agg/Cargo.lock new file mode 100644 index 000000000..a31235f0c --- /dev/null +++ b/examples/aggregation/guest_agg/Cargo.lock @@ -0,0 +1,293 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lib-c" +version = "0.13.1" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "sha-hasher-guest" +version = "0.1.0" +dependencies = [ + "byteorder", + "sha2", + "ziskos", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ziskos" +version = "0.13.1" +dependencies = [ + "cfg-if", + "getrandom", + "lazy_static", + "lib-c", + "num-bigint", + "num-integer", + "num-traits", + "rand", + "static_assertions", + "tiny-keccak", +] diff --git a/examples/aggregation/guest_agg/Cargo.toml b/examples/aggregation/guest_agg/Cargo.toml new file mode 100644 index 000000000..ba42fdd0d --- /dev/null +++ b/examples/aggregation/guest_agg/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "guest-agg" +version = "0.1.0" +edition = "2021" + +[dependencies] +byteorder = "1.5.0" +ziskos = { path = "../../../ziskos/entrypoint" } diff --git a/examples/aggregation/guest_agg/src/main.rs b/examples/aggregation/guest_agg/src/main.rs new file mode 100644 index 000000000..e13b1c88a --- /dev/null +++ b/examples/aggregation/guest_agg/src/main.rs @@ -0,0 +1,27 @@ +// This example program takes a number `n` as input and computes the SHA-256 hash `n` times sequentially. + +// Mark the main function as the entry point for ZisK +#![no_main] +ziskos::entrypoint!(main); + +fn main() { + let input = ziskos::io::read_vec(); + + println!("Input length: {}", input.len()); + + let mut offset = 0; + let (proof1, zisk_vk1) = ziskos::io::read_proof(); + let (proof2, zisk_vk2) = ziskos::io::read_proof(); + + // Verify the first proof + let valid_proof1 = ziskos::verify_zisk_proof(&proof1, &zisk_vk1); + if !valid_proof1 { + panic!("Proof 1 verification failed"); + } + + // Verify the second proof + let valid_proof2 = ziskos::verify_zisk_proof(&proof2, &zisk_vk2); + if !valid_proof2 { + panic!("Proof 2 verification failed"); + } +} diff --git a/examples/aggregation/host/Cargo.toml b/examples/aggregation/host/Cargo.toml new file mode 100644 index 000000000..c2c6993e5 --- /dev/null +++ b/examples/aggregation/host/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "aggregation-host" +version = "0.1.0" +edition = "2021" + +[dependencies] +zisk-sdk = { workspace = true } +anyhow = "1.0" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sha2 = "0.10.8" + +[build-dependencies] +zisk-sdk = { workspace = true } + +[features] +default = [] +packed = ["zisk-sdk/packed"] +gpu = ["zisk-sdk/gpu", "packed"] \ No newline at end of file diff --git a/examples/aggregation/host/build.rs b/examples/aggregation/host/build.rs new file mode 100644 index 000000000..a45234df9 --- /dev/null +++ b/examples/aggregation/host/build.rs @@ -0,0 +1,4 @@ +fn main() { + zisk_sdk::build_program("../guest"); + zisk_sdk::build_program("../guest_agg"); +} diff --git a/examples/aggregation/host/src/main.rs b/examples/aggregation/host/src/main.rs new file mode 100644 index 000000000..a85fa300b --- /dev/null +++ b/examples/aggregation/host/src/main.rs @@ -0,0 +1,63 @@ +use anyhow::Result; +use zisk_sdk::{include_elf, ziskemu, EmuOptions, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; + +pub const ELF: ElfBinary = include_elf!("guest"); +pub const ELF2: ElfBinary = include_elf!("guest-agg"); + +fn main() -> Result<()> { + println!("Starting ZisK Prover Client...\n"); + + // Create an input stream and write '1000' to it. + let n = 1000u32; + let stdin = ZiskStdin::new(); + stdin.write(&n); + + // Create a `ProverClient` method. + let client = ProverClient::builder().build().unwrap(); + + println!("Setting up first program..."); + let (pk, vkey) = client.setup(&ELF)?; + + println!("Setting up second program..."); + let (pk2, vkey2) = client.setup(&ELF2)?; + + // Execute the program using the `ProverClient.execute` method, without generating a proof. + println!("Executing first program..."); + let result = client.execute(&pk, stdin.clone())?; + + println!( + "Program executed successfully: {} cycles in {:.2?}", + result.execution.steps, result.duration + ); + + println!("Generating first proof for program..."); + let proof_opts = ProofOpts::default().minimal_memory(); + let vadcop_result1 = client.prove(&pk, stdin).with_proof_options(proof_opts).run()?; + + let n = 2000u32; + let stdin2 = ZiskStdin::new(); + stdin2.write(&n); + + println!("Generating second proof for program..."); + let proof_opts = ProofOpts::default().minimal_memory(); + let vadcop_result2 = client.prove(&pk, stdin2).with_proof_options(proof_opts).run()?; + + // Write the proofs, publics, and verification keys to be verified by the guest + let stdin_aggregation = ZiskStdin::new(); + + let (proof1, zisk_vk1) = client.prepare_send_proof(&vadcop_result1.get_proof(), &vadcop_result1.get_publics(), &vkey)?; + let (proof2, zisk_vk2) = client.prepare_send_proof(&vadcop_result2.get_proof(), &vadcop_result2.get_publics(), &vkey)?; + + stdin_aggregation.write_proof(&proof1, &zisk_vk1); + stdin_aggregation.write_proof(&proof2, &zisk_vk2); + + let proof_opts = ProofOpts::default().minimal_memory(); + let emu_options = EmuOptions { stats: true, read_symbols: true, top_roi_detail: true, elf: Some("/home/roger/zisk/examples/target/elf/riscv64ima-zisk-zkvm-elf/release/guest-agg".to_string()),..EmuOptions::default() }; + ziskemu(&ELF2, stdin_aggregation.clone(), &emu_options)?; + let result_aggregation = + client.prove(&pk2, stdin_aggregation).with_proof_options(proof_opts).run()?; + + client.verify(result_aggregation.get_proof(), result_aggregation.get_publics(), &vkey2)?; + + Ok(()) +} diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 08f329e61..6a9f8ac4d 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -8,6 +8,7 @@ repository = { workspace = true } categories = { workspace = true } [dependencies] +proofman-verifier = { workspace = true } proofman-common = { workspace = true } proofman-util = { workspace = true } zisk-common = { workspace = true } @@ -21,7 +22,6 @@ tracing = { workspace = true } zisk-distributed-common = { workspace = true } ziskemu = { workspace = true } zisk-core = { workspace = true } -zisk-verifier = { workspace = true } zisk-build = { workspace = true } sha2 = { workspace = true } executor = { workspace = true } diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 10cfb571c..6cd634e6a 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -342,6 +342,15 @@ impl ProverEngine for AsmProver { fn mpi_broadcast(&self, data: &mut Vec) -> Result<()> { self.core_prover.backend.mpi_broadcast(data) } + + fn prepare_send_proof( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, + ) -> Result<(Vec, Vec)> { + self.core_prover.backend.prepare_send_proof(proof, publics, program_vk) + } } pub struct AsmCoreProver { diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index e17175f63..730092752 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -3,7 +3,7 @@ use crate::ZiskProofWithPublicValues; use crate::ZiskPublics; use crate::{ get_program_vk_with_proving_key, verify_zisk_proof_with_proving_key, - verify_zisk_snark_proof_with_proving_key, + verify_zisk_snark_proof_with_proving_key, ZISK_PUBLICS, }; use crate::{ProofMode, ProofOpts}; use crate::{ @@ -15,8 +15,8 @@ use colored::Colorize; use executor::ZiskExecutor; use fields::Goldilocks; use proofman::{ - AggProofs, AggProofsRegister, ProofMan, ProvePhase, ProvePhaseInputs, ProvePhaseResult, - SnarkProtocol, SnarkWrapper, WitnessInfo, + get_vadcop_final_proof_vkey, AggProofs, AggProofsRegister, ProofMan, ProvePhase, + ProvePhaseInputs, ProvePhaseResult, SnarkProtocol, SnarkWrapper, WitnessInfo, }; use proofman_common::{ProofCtx, ProofOptions, RowInfo}; use proofman_util::VadcopFinalProof; @@ -610,6 +610,34 @@ impl ProverBackend { Ok(()) } + pub(crate) fn prepare_send_proof( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, + ) -> Result<(Vec, Vec)> { + match &proof { + ZiskProof::Null() | ZiskProof::Plonk(_) | ZiskProof::Fflonk(_) => Err(anyhow::anyhow!("Proof not suitable for preparing to send. Only VadcopFinal and VadcopFinalCompressed proofs can be prepared for sending.")), + ZiskProof::VadcopFinal(proof_bytes) | ZiskProof::VadcopFinalCompressed(proof_bytes) => { + let compressed = matches!(proof, ZiskProof::VadcopFinalCompressed(_)); + + let vk = get_vadcop_final_proof_vkey(&self.proving_key_path, compressed)?; + + let mut pubs = program_vk.vk.clone(); + pubs.extend(publics.public_bytes()); + + // Format: [compressed(8)][pubs_len(8)][pubs][proof_bytes] + let mut proof = Vec::new(); + proof.extend_from_slice(&(compressed as u64).to_le_bytes()); + proof.extend_from_slice(&(ZISK_PUBLICS + 4).to_le_bytes()); + proof.extend_from_slice(&pubs); + proof.extend_from_slice(proof_bytes); + + Ok((proof, vk)) + } + } + } + pub(crate) fn verify( &self, proof: &ZiskProof, diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index a178d7025..ddfe50c8d 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -255,6 +255,15 @@ impl ProverEngine for EmuProver { fn mpi_broadcast(&self, data: &mut Vec) -> Result<()> { self.core_prover.backend.mpi_broadcast(data) } + + fn prepare_send_proof( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, + ) -> Result<(Vec, Vec)> { + self.core_prover.backend.prepare_send_proof(proof, publics, program_vk) + } } pub struct EmuCoreProver { diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 9e429d89d..c883cfb50 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -849,6 +849,13 @@ pub trait ProverEngine { ) -> Result>; fn mpi_broadcast(&self, data: &mut Vec) -> Result<()>; + + fn prepare_send_proof( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, + ) -> Result<(Vec, Vec)>; } pub trait ZiskBackend: Send + Sync { @@ -1045,6 +1052,15 @@ impl ZiskProver { pub fn mpi_broadcast(&self, data: &mut Vec) -> Result<()> { self.prover.mpi_broadcast(data) } + + pub fn prepare_send_proof( + &self, + proof: &ZiskProof, + publics: &ZiskPublics, + program_vk: &ZiskProgramVK, + ) -> Result<(Vec, Vec)> { + self.prover.prepare_send_proof(proof, publics, program_vk) + } } /// Builder for configuring and running a proof. diff --git a/sdk/src/verifier.rs b/sdk/src/verifier.rs index 8abe1c0ba..303cf13ce 100644 --- a/sdk/src/verifier.rs +++ b/sdk/src/verifier.rs @@ -4,11 +4,11 @@ use crate::{ use anyhow::{anyhow, Ok, Result}; use proofman::{get_vadcop_final_proof_vkey, verify_snark_proof, SnarkProof, SnarkProtocol}; use proofman_util::VadcopFinalProof; +use proofman_verifier::{verify_vadcop_final, verify_vadcop_final_compressed}; use rom_setup::rom_merkle_setup_verkey; use sha2::{Digest, Sha256}; use std::path::PathBuf; use zisk_common::ElfBinaryLike; -use zisk_verifier::verify_vadcop_final_proof; pub fn verify_zisk_snark_proof( proof: &ZiskProof, @@ -101,7 +101,17 @@ pub fn verify_zisk_proof_with_proving_key( let vadcop_final_proof = VadcopFinalProof::new(proof_bytes.clone(), pubs, compressed); let vk = get_vadcop_final_proof_vkey(&proving_key, compressed)?; - verify_vadcop_final_proof(&vadcop_final_proof, &vk) + let is_valid = if compressed { + verify_vadcop_final_compressed(&vadcop_final_proof, &vk) + } else { + verify_vadcop_final(&vadcop_final_proof, &vk) + }; + + if !is_valid { + Err(anyhow!("Zisk Proof was not verified")) + } else { + Ok(()) + } } _ => Err(anyhow!("Not a Vadcop final proof.")), } diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml index 9b2fa6363..734ae3378 100644 --- a/verifier/Cargo.toml +++ b/verifier/Cargo.toml @@ -9,8 +9,6 @@ categories = { workspace = true } [dependencies] proofman-verifier = { workspace = true, features = ["verify"] } -proofman-util = { workspace = true } -anyhow = { workspace = true } [features] default = [] diff --git a/verifier/src/verifier.rs b/verifier/src/verifier.rs index a134f2be6..b11227508 100644 --- a/verifier/src/verifier.rs +++ b/verifier/src/verifier.rs @@ -1,20 +1,25 @@ -use anyhow::{anyhow, Ok, Result}; -use proofman_util::VadcopFinalProof; -use proofman_verifier::{verify_vadcop_final, verify_vadcop_final_compressed}; +use proofman_verifier::{verify_vadcop_final_bytes, verify_vadcop_final_compressed_bytes}; -pub fn verify_vadcop_final_proof( - zisk_proof: &VadcopFinalProof, - vadcop_final_vk: &[u8], -) -> Result<()> { - let is_valid = if zisk_proof.compressed { - verify_vadcop_final_compressed(zisk_proof, vadcop_final_vk) - } else { - verify_vadcop_final(zisk_proof, vadcop_final_vk) - }; +pub fn verify_vadcop_final_proof(zisk_proof: &[u8], vadcop_final_vk: &[u8]) -> bool { + // Format: [compressed(8)][pubs_len(8)][pubs][proof_bytes] + + // Read compressed flag (8 bytes, u64 little-endian) + let compressed = u64::from_le_bytes([ + zisk_proof[0], + zisk_proof[1], + zisk_proof[2], + zisk_proof[3], + zisk_proof[4], + zisk_proof[5], + zisk_proof[6], + zisk_proof[7], + ]) == 1; + + let vadcop_proof = &zisk_proof[8..]; - if !is_valid { - Err(anyhow!("Zisk Proof was not verified")) + if compressed { + verify_vadcop_final_compressed_bytes(vadcop_proof, vadcop_final_vk) } else { - Ok(()) + verify_vadcop_final_bytes(vadcop_proof, vadcop_final_vk) } } diff --git a/ziskos-hints/Cargo.toml b/ziskos-hints/Cargo.toml index 0c7b72163..0d1427842 100644 --- a/ziskos-hints/Cargo.toml +++ b/ziskos-hints/Cargo.toml @@ -35,6 +35,7 @@ sha2 = { workspace = true } fields = { workspace = true } anyhow = { workspace = true } +zisk-verifier = { workspace = true } [features] default = ["hints"] diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index 41ddb4637..847c0ff4c 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -22,6 +22,8 @@ getrandom = { version = "0.2", features = ["custom"] } cfg-if = "1.0" tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } + +zisk-verifier = { workspace = true } bincode = { workspace = true } zisk-definitions = { path = "../../definitions" } diff --git a/ziskos/entrypoint/src/io.rs b/ziskos/entrypoint/src/io.rs index a1d27a449..33c55a775 100644 --- a/ziskos/entrypoint/src/io.rs +++ b/ziskos/entrypoint/src/io.rs @@ -19,7 +19,7 @@ use serde::{de::DeserializeOwned, Serialize}; /// /// let data: MyStruct = ziskos::io::read(); /// ``` -/// +/// /// Note: This uses zero-copy deserialization on zkvm to avoid unnecessary data copies. pub fn read() -> T { let bytes = read_input_slice(); @@ -36,6 +36,15 @@ pub fn read_vec() -> Vec { read_input() } +/// Read proof data prepared by prepare_send_proof and written with write_proof +/// Format: [proof_len(4)][compressed(8)][pubs_len(8)][pubs][proof_bytes][vk(32)] +pub fn read_proof() -> (Vec, Vec) { + let proof_data = read_vec(); + let vk = read_vec(); + + (proof_data, vk) +} + /// Commit a serializable value to public outputs. /// The value is serialized with bincode and written as 32-bit chunks. pub fn commit(value: &T) { diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 962412e43..0ff1e66bc 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -68,21 +68,21 @@ pub fn read_slice_zerocopy<'a>() -> &'a [u8] { // SAFETY: Single threaded, so nothing else can touch INPUT_POS while we're working. let input_pos = unsafe { INPUT_POS }; let addr = (INPUT_ADDR as usize) + input_pos; - + // Ensure the 8-byte length prefix is ready and read it crate::zisklib::fcall_input_ready(&((addr + 7) as u64)); let len = unsafe { let bytes = core::slice::from_raw_parts(addr as *const u8, 8); u64::from_le_bytes(bytes.try_into().unwrap()) as usize }; - + // Ensure the data is ready let data_addr = addr + 8; crate::zisklib::fcall_input_ready(&((data_addr + len + 7) as u64)); - + // Update input position: move past length (8 bytes) + data unsafe { INPUT_POS = input_pos + 8 + (len + 7) & !0x7 }; - + // Return slice pointing directly to the input data (zero-copy) unsafe { core::slice::from_raw_parts(data_addr as *const u8, len) } } @@ -322,3 +322,7 @@ mod ziskos { //core::arch::global_asm!(include_str!("dma/inputcpy.s")); core::arch::global_asm!(include_str!("dma/memset.s")); } + +pub fn verify_zisk_proof(zisk_proof: &[u8], vk: &[u8]) -> bool { + zisk_verifier::verify_vadcop_final_proof(zisk_proof, vk) +} From 0310dce2717e9fd5d306134f1a7847af36e14cf7 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 5 Mar 2026 15:50:38 +0000 Subject: [PATCH 704/782] Input stream working and write proof working --- common/src/io/stdin/file.rs | 2 +- common/src/io/stdin/memory.rs | 4 +-- common/src/io/stdin/null.rs | 2 +- common/src/io/stdin/zisk_stdin.rs | 14 +++++------ core/src/zisk_ops.rs | 4 +-- examples/aggregation/guest_agg/src/main.rs | 13 +++------- examples/aggregation/host/src/main.rs | 24 ++++++++++++------ examples/multiple-programs/host/src/main.rs | 6 +++-- examples/sha-hasher/host/src/main.rs | 3 ++- sdk/src/prover/asm.rs | 2 +- sdk/src/prover/backend.rs | 7 +++--- sdk/src/prover/emu.rs | 2 +- sdk/src/prover/mod.rs | 4 +-- ziskos/entrypoint/src/io.rs | 27 +++++++++++++++------ ziskos/entrypoint/src/lib.rs | 16 +++--------- 15 files changed, 69 insertions(+), 61 deletions(-) diff --git a/common/src/io/stdin/file.rs b/common/src/io/stdin/file.rs index 2a9a1aae9..2350212f8 100644 --- a/common/src/io/stdin/file.rs +++ b/common/src/io/stdin/file.rs @@ -68,7 +68,7 @@ impl ZiskIO for ZiskFileStdin { panic!("Write operations are not supported for FileStdin"); } - fn write_proof(&self, _proof: &[u8], _vk: &[u8]) { + fn write_proof(&self, _proof: &[u8]) { // This is a read-only stdin implementation // Writing is not supported for file-based stdin panic!("Write operations are not supported for FileStdin"); diff --git a/common/src/io/stdin/memory.rs b/common/src/io/stdin/memory.rs index 8f33aeed1..2721d8dc6 100644 --- a/common/src/io/stdin/memory.rs +++ b/common/src/io/stdin/memory.rs @@ -102,10 +102,8 @@ impl ZiskIO for ZiskMemoryStdin { } } - fn write_proof(&self, proof: &[u8], vk: &[u8]) { - let proof_len = proof.len() as u64; + fn write_proof(&self, proof: &[u8]) { self.write_slice(proof); - self.write_slice(vk); } fn save(&self, path: &Path) -> Result<()> { diff --git a/common/src/io/stdin/null.rs b/common/src/io/stdin/null.rs index 15811b9ff..474c3b788 100644 --- a/common/src/io/stdin/null.rs +++ b/common/src/io/stdin/null.rs @@ -22,7 +22,7 @@ impl ZiskIO for ZiskNullStdin { fn write_slice(&self, _data: &[u8]) { warn!("NullStdin does not support writing"); } - fn write_proof(&self, _proof: &[u8], _vk: &[u8]) { + fn write_proof(&self, _proof: &[u8]) { warn!("NullStdin does not support writing"); } fn save(&self, _path: &Path) -> Result<()> { diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index 048825112..825563996 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -24,7 +24,7 @@ pub trait ZiskIO: Send + Sync { fn write_slice(&self, data: &[u8]); /// Write proof - fn write_proof(&self, proof: &[u8], vk: &[u8]); + fn write_proof(&self, proof: &[u8]); fn save(&self, path: &Path) -> Result<()>; } @@ -84,11 +84,11 @@ impl ZiskIO for ZiskIOVariant { } } - fn write_proof(&self, proof: &[u8], vk: &[u8]) { + fn write_proof(&self, proof: &[u8]) { match self { - ZiskIOVariant::File(file_stdin) => file_stdin.write_proof(proof, vk), - ZiskIOVariant::Null(null_stdin) => null_stdin.write_proof(proof, vk), - ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write_proof(proof, vk), + ZiskIOVariant::File(file_stdin) => file_stdin.write_proof(proof), + ZiskIOVariant::Null(null_stdin) => null_stdin.write_proof(proof), + ZiskIOVariant::Memory(memory_stdin) => memory_stdin.write_proof(proof), } } @@ -131,8 +131,8 @@ impl ZiskIO for ZiskStdin { self.io.write_slice(data) } - fn write_proof(&self, proof: &[u8], vk: &[u8]) { - self.io.write_proof(proof, vk) + fn write_proof(&self, proof: &[u8]) { + self.io.write_proof(proof) } fn save(&self, path: &Path) -> Result<()> { diff --git a/core/src/zisk_ops.rs b/core/src/zisk_ops.rs index 45945c17a..3b84765af 100644 --- a/core/src/zisk_ops.rs +++ b/core/src/zisk_ops.rs @@ -2502,11 +2502,11 @@ pub fn opc_fcall(ctx: &mut InstContext) { let iresult = if function_id == FCALL_INPUT_READY_ID as u64 { let required_address = ctx.fcall.parameters[0]; - if required_address < INPUT_ADDR + 8 { + if required_address < INPUT_ADDR { panic!( "opc_fcall() FCALL_INPUT_READY_ID called with required_address {:#x} < {:#x}", required_address, - INPUT_ADDR + 8 + INPUT_ADDR ); } if required_address >= INPUT_ADDR + MAX_INPUT_SIZE - 1 { diff --git a/examples/aggregation/guest_agg/src/main.rs b/examples/aggregation/guest_agg/src/main.rs index e13b1c88a..472b7d5fc 100644 --- a/examples/aggregation/guest_agg/src/main.rs +++ b/examples/aggregation/guest_agg/src/main.rs @@ -5,22 +5,17 @@ ziskos::entrypoint!(main); fn main() { - let input = ziskos::io::read_vec(); - - println!("Input length: {}", input.len()); - - let mut offset = 0; - let (proof1, zisk_vk1) = ziskos::io::read_proof(); - let (proof2, zisk_vk2) = ziskos::io::read_proof(); + let proof1 = ziskos::io::read_proof(); + let proof2 = ziskos::io::read_proof(); // Verify the first proof - let valid_proof1 = ziskos::verify_zisk_proof(&proof1, &zisk_vk1); + let valid_proof1 = ziskos::verify_zisk_proof(&proof1); if !valid_proof1 { panic!("Proof 1 verification failed"); } // Verify the second proof - let valid_proof2 = ziskos::verify_zisk_proof(&proof2, &zisk_vk2); + let valid_proof2 = ziskos::verify_zisk_proof(&proof2); if !valid_proof2 { panic!("Proof 2 verification failed"); } diff --git a/examples/aggregation/host/src/main.rs b/examples/aggregation/host/src/main.rs index a85fa300b..552b39e09 100644 --- a/examples/aggregation/host/src/main.rs +++ b/examples/aggregation/host/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use zisk_sdk::{include_elf, ziskemu, EmuOptions, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; +use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; pub const ELF: ElfBinary = include_elf!("guest"); pub const ELF2: ElfBinary = include_elf!("guest-agg"); @@ -27,7 +27,8 @@ fn main() -> Result<()> { println!( "Program executed successfully: {} cycles in {:.2?}", - result.execution.steps, result.duration + result.get_execution_steps(), + result.get_duration() ); println!("Generating first proof for program..."); @@ -45,15 +46,22 @@ fn main() -> Result<()> { // Write the proofs, publics, and verification keys to be verified by the guest let stdin_aggregation = ZiskStdin::new(); - let (proof1, zisk_vk1) = client.prepare_send_proof(&vadcop_result1.get_proof(), &vadcop_result1.get_publics(), &vkey)?; - let (proof2, zisk_vk2) = client.prepare_send_proof(&vadcop_result2.get_proof(), &vadcop_result2.get_publics(), &vkey)?; + let proof1 = client.prepare_send_proof( + &vadcop_result1.get_proof(), + &vadcop_result1.get_publics(), + &vkey, + )?; + let proof2 = client.prepare_send_proof( + &vadcop_result2.get_proof(), + &vadcop_result2.get_publics(), + &vkey, + )?; - stdin_aggregation.write_proof(&proof1, &zisk_vk1); - stdin_aggregation.write_proof(&proof2, &zisk_vk2); + stdin_aggregation.write_proof(&proof1); + stdin_aggregation.write_proof(&proof2); let proof_opts = ProofOpts::default().minimal_memory(); - let emu_options = EmuOptions { stats: true, read_symbols: true, top_roi_detail: true, elf: Some("/home/roger/zisk/examples/target/elf/riscv64ima-zisk-zkvm-elf/release/guest-agg".to_string()),..EmuOptions::default() }; - ziskemu(&ELF2, stdin_aggregation.clone(), &emu_options)?; + let result_aggregation = client.prove(&pk2, stdin_aggregation).with_proof_options(proof_opts).run()?; diff --git a/examples/multiple-programs/host/src/main.rs b/examples/multiple-programs/host/src/main.rs index fdbb24f68..b8f0d502d 100644 --- a/examples/multiple-programs/host/src/main.rs +++ b/examples/multiple-programs/host/src/main.rs @@ -27,7 +27,8 @@ fn main() -> Result<()> { println!( "Program executed successfully: {} cycles in {:.2?}", - result.get_execution_steps(), result.get_duration() + result.get_execution_steps(), + result.get_duration() ); println!("Generating proof for first program..."); @@ -49,7 +50,8 @@ fn main() -> Result<()> { println!( "Program executed successfully: {} cycles in {:.2?}", - result2.get_execution_steps(), result2.get_duration() + result2.get_execution_steps(), + result2.get_duration() ); println!("Generating proof for second program..."); diff --git a/examples/sha-hasher/host/src/main.rs b/examples/sha-hasher/host/src/main.rs index 590d230c1..53e6666a2 100644 --- a/examples/sha-hasher/host/src/main.rs +++ b/examples/sha-hasher/host/src/main.rs @@ -21,7 +21,8 @@ fn main() -> Result<()> { println!( "ZisK has executed program with {} cycles in {:?}", - result.get_execution_steps(), result.get_duration() + result.get_execution_steps(), + result.get_duration() ); let proof_opts = ProofOpts::default().minimal_memory(); diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 6cd634e6a..67b3a7def 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -348,7 +348,7 @@ impl ProverEngine for AsmProver { proof: &ZiskProof, publics: &ZiskPublics, program_vk: &ZiskProgramVK, - ) -> Result<(Vec, Vec)> { + ) -> Result> { self.core_prover.backend.prepare_send_proof(proof, publics, program_vk) } } diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index 730092752..dec8faf96 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -615,7 +615,7 @@ impl ProverBackend { proof: &ZiskProof, publics: &ZiskPublics, program_vk: &ZiskProgramVK, - ) -> Result<(Vec, Vec)> { + ) -> Result> { match &proof { ZiskProof::Null() | ZiskProof::Plonk(_) | ZiskProof::Fflonk(_) => Err(anyhow::anyhow!("Proof not suitable for preparing to send. Only VadcopFinal and VadcopFinalCompressed proofs can be prepared for sending.")), ZiskProof::VadcopFinal(proof_bytes) | ZiskProof::VadcopFinalCompressed(proof_bytes) => { @@ -626,14 +626,15 @@ impl ProverBackend { let mut pubs = program_vk.vk.clone(); pubs.extend(publics.public_bytes()); - // Format: [compressed(8)][pubs_len(8)][pubs][proof_bytes] + // Format: [compressed(8)][pubs_len(8)][pubs][proof_bytes][zisk_vk] let mut proof = Vec::new(); proof.extend_from_slice(&(compressed as u64).to_le_bytes()); proof.extend_from_slice(&(ZISK_PUBLICS + 4).to_le_bytes()); proof.extend_from_slice(&pubs); proof.extend_from_slice(proof_bytes); + proof.extend_from_slice(&vk); - Ok((proof, vk)) + Ok(proof) } } } diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index ddfe50c8d..99a2a5be3 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -261,7 +261,7 @@ impl ProverEngine for EmuProver { proof: &ZiskProof, publics: &ZiskPublics, program_vk: &ZiskProgramVK, - ) -> Result<(Vec, Vec)> { + ) -> Result> { self.core_prover.backend.prepare_send_proof(proof, publics, program_vk) } } diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index c883cfb50..824bee37d 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -855,7 +855,7 @@ pub trait ProverEngine { proof: &ZiskProof, publics: &ZiskPublics, program_vk: &ZiskProgramVK, - ) -> Result<(Vec, Vec)>; + ) -> Result>; } pub trait ZiskBackend: Send + Sync { @@ -1058,7 +1058,7 @@ impl ZiskProver { proof: &ZiskProof, publics: &ZiskPublics, program_vk: &ZiskProgramVK, - ) -> Result<(Vec, Vec)> { + ) -> Result> { self.prover.prepare_send_proof(proof, publics, program_vk) } } diff --git a/ziskos/entrypoint/src/io.rs b/ziskos/entrypoint/src/io.rs index 33c55a775..3677c443c 100644 --- a/ziskos/entrypoint/src/io.rs +++ b/ziskos/entrypoint/src/io.rs @@ -2,7 +2,7 @@ //! //! This module provides a high-level API for reading inputs and committing public outputs. -use crate::{read_input, read_input_slice, set_output}; +use crate::{read_input, set_output}; use serde::{de::DeserializeOwned, Serialize}; /// Read a deserializable object from the input stream. @@ -36,13 +36,26 @@ pub fn read_vec() -> Vec { read_input() } -/// Read proof data prepared by prepare_send_proof and written with write_proof -/// Format: [proof_len(4)][compressed(8)][pubs_len(8)][pubs][proof_bytes][vk(32)] -pub fn read_proof() -> (Vec, Vec) { - let proof_data = read_vec(); - let vk = read_vec(); +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +pub fn read_input_slice<'a>() -> &'a [u8] { + crate::read_slice_zerocopy() +} + +#[allow(unused)] +#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] +pub fn read_input_slice() -> Box<[u8]> { + read_input().into_boxed_slice() +} + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +pub fn read_proof<'a>() -> &'a [u8] { + crate::read_slice_zerocopy() +} - (proof_data, vk) +#[allow(unused)] +#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] +pub fn read_proof() -> Box<[u8]> { + read_input().into_boxed_slice() } /// Commit a serializable value to public outputs. diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 0ff1e66bc..fdd73f9ae 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -103,17 +103,6 @@ pub(crate) fn read_input() -> Vec { buffer } -#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -pub fn read_input_slice<'a>() -> &'a [u8] { - read_slice_zerocopy() -} - -#[allow(unused)] -#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] -pub fn read_input_slice() -> Box<[u8]> { - read_input().into_boxed_slice() -} - #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] pub(crate) fn set_output(id: usize, value: u32) { use std::arch::asm; @@ -323,6 +312,7 @@ mod ziskos { core::arch::global_asm!(include_str!("dma/memset.s")); } -pub fn verify_zisk_proof(zisk_proof: &[u8], vk: &[u8]) -> bool { - zisk_verifier::verify_vadcop_final_proof(zisk_proof, vk) +pub fn verify_zisk_proof(zisk_proof: &[u8]) -> bool { + let (proof, vk) = zisk_proof.split_at(zisk_proof.len() - 32); + zisk_verifier::verify_vadcop_final_proof(proof, vk) } From b1999fd9d6ab8d2b55eca413b61563e173fd52fe Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 5 Mar 2026 16:26:37 +0000 Subject: [PATCH 705/782] Fix --- ziskos/entrypoint/src/lib.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index fdd73f9ae..28ce990ef 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -76,12 +76,13 @@ pub fn read_slice_zerocopy<'a>() -> &'a [u8] { u64::from_le_bytes(bytes.try_into().unwrap()) as usize }; - // Ensure the data is ready + // Ensure the data is ready (8-byte aligned) let data_addr = addr + 8; - crate::zisklib::fcall_input_ready(&((data_addr + len + 7) as u64)); + let aligned_len = (len + 7) & !0x7; + crate::zisklib::fcall_input_ready(&((data_addr + aligned_len - 1) as u64)); - // Update input position: move past length (8 bytes) + data - unsafe { INPUT_POS = input_pos + 8 + (len + 7) & !0x7 }; + // Update input position: move past length (8 bytes) + data (8-byte aligned) + unsafe { INPUT_POS = input_pos + 8 + aligned_len }; // Return slice pointing directly to the input data (zero-copy) unsafe { core::slice::from_raw_parts(data_addr as *const u8, len) } From 6e114aef69b0b15fec880e178d5186aa0209e45f Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 5 Mar 2026 17:02:47 +0000 Subject: [PATCH 706/782] Add hint_input_data --- tools/update-rust/update-rust.sh | 6 ++- ziskos/entrypoint/src/hints/input_data.rs | 34 ++++++++++++ ziskos/entrypoint/src/hints/mod.rs | 2 + ziskos/entrypoint/src/lib.rs | 64 ++++++++++++++++++++++- 4 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 ziskos/entrypoint/src/hints/input_data.rs diff --git a/tools/update-rust/update-rust.sh b/tools/update-rust/update-rust.sh index 60317e334..1483e49b7 100755 --- a/tools/update-rust/update-rust.sh +++ b/tools/update-rust/update-rust.sh @@ -103,7 +103,7 @@ fi for line in "${commits_array[@]}"; do commit=$(echo "$line" | awk '{print $1}') msg=$(echo "$line" | cut -d' ' -f2-) - + log_info "Applying cherry pick for commit: ${msg} (${commit})" output=$(git cherry-pick $commit -n 2>&1) if ! [[ $? -eq 0 ]]; then @@ -121,7 +121,9 @@ done # Final instructions echo -log_info "Now test build Zisk tool chain using the rust code in the directory ${ZISK_RUST_DIR} and new branch 'zisk-rust-${TO_VERSION}'" +log_info "Now test build Zisk tool chain using the rust code in the directory ${ZISK_RUST_DIR} and new branch 'zisk-rust-${TO_VERSION}':" +log "ZISK_BUILD_DIR=${ZISK_RUST_DIR} cargo-zisk sdk build-toolchain" +echo log_info "When successfully tested, execute the following command to commit/merge the changes to 'zisk' branch and generate the release:" echo log "./release-rust.sh ${TO_VERSION} " diff --git a/ziskos/entrypoint/src/hints/input_data.rs b/ziskos/entrypoint/src/hints/input_data.rs new file mode 100644 index 000000000..626d52037 --- /dev/null +++ b/ziskos/entrypoint/src/hints/input_data.rs @@ -0,0 +1,34 @@ +use crate::hints::macros::define_hint_ptr; +use zisk_common::HINT_INPUT_DATA; + +#[no_mangle] +pub unsafe extern "C" fn hint_input_data(input_data_ptr: *const u8, input_data_len: usize) { + if !crate::hints::HINT_BUFFER.is_enabled() { + return; + } + + #[cfg(zisk_hints_single_thread)] + if !crate::hints::check_main_thread() { + return; + } + + let pad = (8 - (input_data_len & 7)) & 7; + let mut w = crate::hints::HINT_BUFFER.begin_hint(HINT_INPUT_DATA, 8 + input_data_len + pad, false); + + // Write the length of the input data as the first 8 bytes of the hint data, + // followed by the input data itself, and then pad with zeros if necessary + let input_data_len_bytes: [u8; 8] = (input_data_len as u64).to_le_bytes(); + w.write_hint_data_slice(&input_data_len_bytes); + w.write_hint_data_ptr(input_data_ptr, input_data_len); + if pad > 0 { + const ZERO_PAD: [u8; 8] = [0; 8]; + w.write_hint_data_slice(&ZERO_PAD[..pad]); + } + w.commit(); +} + +#[cfg(zisk_hints_metrics)] +#[ctor::ctor] +fn input_data_register_meta() { + crate::hints::metrics::register_hint(HINT_INPUT_DATA, stringify!(input_data).to_string()); +} diff --git a/ziskos/entrypoint/src/hints/mod.rs b/ziskos/entrypoint/src/hints/mod.rs index cbb3057c4..f6c25bbc7 100644 --- a/ziskos/entrypoint/src/hints/mod.rs +++ b/ziskos/entrypoint/src/hints/mod.rs @@ -3,6 +3,7 @@ mod bls12_381; mod bn254; mod custom; mod hint_buffer; +mod input_data; mod keccak256; mod kzg; mod macros; @@ -40,6 +41,7 @@ pub use blake2b::*; pub use bls12_381::*; pub use bn254::*; pub use custom::*; +pub use input_data::*; pub use keccak256::*; pub use kzg::*; pub use modexp::*; diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 28ce990ef..8d5971aa2 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -24,6 +24,34 @@ pub mod ziskos_definitions; ))] pub mod hints; +#[cfg(all(not(all(target_os = "zkvm", target_vendor = "zisk")), zisk_hints))] +extern "C" { + fn hint_input_data(input_data_ptr: *const u8, input_data_len: usize); +} + +#[cfg(all(not(all(target_os = "zkvm", target_vendor = "zisk")), zisk_hints_debug))] +extern "C" { + fn hint_log_c(msg: *const c_char); +} + +#[cfg(zisk_hints_debug)] +pub fn hint_log>(msg: S) { + // On native we call external C function to log hints, since it controls if hints are paused or not + #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] + { + use std::ffi::CString; + + if let Ok(c) = CString::new(msg.as_ref()) { + unsafe { hint_log_c(c.as_ptr()) }; + } + } + // On zkvm/zisk, we can just print directly + #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] + { + println!("{}", msg.as_ref()); + } +} + #[macro_export] macro_rules! entrypoint { ($path:path) => { @@ -90,7 +118,21 @@ pub fn read_slice_zerocopy<'a>() -> &'a [u8] { #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] pub(crate) fn read_input() -> Vec { - read_slice_zerocopy().to_vec() + let vec = read_slice_zerocopy().to_vec(); + + #[cfg(zisk_hints_debug)] + { + let start_bytes = &vec[..vec.len().min(64)]; + let ellipsis = if vec.len() > 64 { "..." } else { "" }; + hint_log_c(format!( + "hint_input_data (input_data: {:x?}{} , input_data_len: {}", + start_bytes, + ellipsis, + vec.len() + )); + } + + vec } #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] @@ -101,6 +143,24 @@ pub(crate) fn read_input() -> Vec { File::open("build/input.bin").expect("Error opening input file at: build/input.bin"); let mut buffer = Vec::new(); file.read_to_end(&mut buffer).unwrap(); + + #[cfg(zisk_hints)] + unsafe { + hint_input_data(buffer.as_ptr(), buffer.len()); + } + + #[cfg(zisk_hints_debug)] + { + let start_bytes = &buffer[..buffer.len().min(64)]; + let ellipsis = if buffer.len() > 64 { "..." } else { "" }; + hint_log_c(format!( + "hint_input_data (input_data: {:x?}{} , input_data_len: {}", + start_bytes, + ellipsis, + buffer.len() + )); + } + buffer } @@ -313,7 +373,7 @@ mod ziskos { core::arch::global_asm!(include_str!("dma/memset.s")); } -pub fn verify_zisk_proof(zisk_proof: &[u8]) -> bool { +pub fn verify_zisk_proof(zisk_proof: &[u8]) -> bool { let (proof, vk) = zisk_proof.split_at(zisk_proof.len() - 32); zisk_verifier::verify_vadcop_final_proof(proof, vk) } From aaa2e6eab311fc8a9a0f5ab56de071ecebc2aaf0 Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 5 Mar 2026 19:14:24 +0100 Subject: [PATCH 707/782] Fix ziskemu for input hints file format --- emulator/src/emu_context.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/emulator/src/emu_context.rs b/emulator/src/emu_context.rs index 350144f5f..872a26c5d 100644 --- a/emulator/src/emu_context.rs +++ b/emulator/src/emu_context.rs @@ -40,17 +40,14 @@ impl EmuContext { if input.len() > (options.max_input_mem - 8) as usize { panic!("EmuContext::new() input size too big size={}", input.len()); } + if input.len() & 7 != 0 { + panic!("EmuContext::new() input size must be a multiple of 8 size={}", input.len()); + } - // Add the length and input data read sections - ctx.inst_ctx.input_len = (input.len() as u64 + 7) & !7; // Round up to nearest multiple of 8 + ctx.inst_ctx.input_len = input.len() as u64; let free_input = 0u64; ctx.inst_ctx.mem.add_read_section(INPUT_ADDR, &free_input.to_le_bytes()); ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8, &input); - let zeros_to_write = ctx.inst_ctx.input_len - input.len() as u64; - for i in 0..zeros_to_write { - let zero = [0u8; 1]; - ctx.inst_ctx.mem.add_read_section(INPUT_ADDR + 8 + input.len() as u64 + i, &zero); - } // Add the write section ctx.inst_ctx.mem.add_write_section(RAM_ADDR, RAM_SIZE); From a9e3be116c4f7414953d4c2235c168698b4fc06d Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 5 Mar 2026 20:02:01 +0100 Subject: [PATCH 708/782] Fix infinite loop in ziskemu --- emulator/src/emu.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 9058e6ab6..3d7d8a4b7 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -1497,6 +1497,13 @@ impl<'a> Emu<'a> { // }; self.source_a(instruction); self.source_b(instruction); + + if instruction.input_size > 0 { + self.ctx.inst_ctx.extended_arg = instruction.jmp_offset1; + } else { + self.ctx.inst_ctx.extended_arg = 0; + } + (instruction.func)(&mut self.ctx.inst_ctx); self.store_c(instruction); From 4d6e50b585c611c966692c9661333b4d1cecfaed Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Thu, 5 Mar 2026 20:19:53 +0100 Subject: [PATCH 709/782] increase inputdata to 1gb --- cli/src/commands/run.rs | 4 +- core/src/mem.rs | 12 +-- core/src/zisk_rom_2_asm.rs | 2 +- emulator-asm/src/constants.hpp | 4 +- emulator/src/emu.rs | 12 ++- emulator/src/emu_options.rs | 2 +- pil/src/pil_helpers/traces.rs | 4 +- pil/zisk.pil | 2 +- state-machines/mem-common/src/mem_counters.rs | 6 +- state-machines/mem-cpp/cpp/mem_config.hpp | 10 +- .../mem-cpp/cpp/mem_count_and_plan.cpp | 8 +- state-machines/mem-cpp/cpp/mem_counter.hpp | 96 +++++++++++++------ state-machines/mem-cpp/cpp/mem_test.hpp | 2 +- state-machines/mem/src/input_data_sm.rs | 44 +++++---- state-machines/mem/src/mem_test.rs | 6 +- ziskos/entrypoint/src/ziskos_definitions.rs | 2 +- 16 files changed, 141 insertions(+), 75 deletions(-) diff --git a/cli/src/commands/run.rs b/cli/src/commands/run.rs index 422ca8772..f6595db0c 100644 --- a/cli/src/commands/run.rs +++ b/cli/src/commands/run.rs @@ -124,8 +124,8 @@ impl ZiskRun { qemu-system-riscv64 \ -cpu rv64 \ -machine virt \ - -device loader,file=./{},addr=0x90000000 \ - -device loader,file=./{},addr=0x90000008 \ + -device loader,file=./{},addr=0x40000000 \ + -device loader,file=./{},addr=0x40000008 \ -m 1G \ -s \ {} \ diff --git a/core/src/mem.rs b/core/src/mem.rs index cd9c18900..cba4f24ff 100644 --- a/core/src/mem.rs +++ b/core/src/mem.rs @@ -14,6 +14,10 @@ //! `|` //! `|---------------` //! ` ...` +//! `|--------------- INPUT_ADDR (0x40000000)` +//! `|` +//! `| Contains program input data.` +//! `|` //! `|--------------- ROM_ADDR: first program instruction (0x80000000)` //! `|` //! `| Contains program instructions.` @@ -27,10 +31,6 @@ //! `|` //! `| Initial value of the float library stack pointer.` //! `|` -//! `|--------------- INPUT_ADDR (0x90000000)` -//! `|` -//! `| Contains program input data.` -//! `|` //! `|--------------- SYS_ADDR (= RAM_ADDR = REG_FIRST) (0xa0000000)` //! `|` //! `| Contains system address.` @@ -102,9 +102,9 @@ use crate::{M16, M3, M32, M8, REG_FIRST, REG_LAST}; use core::fmt; /// Fist input data memory address -pub const INPUT_ADDR: u64 = 0x90000000; +pub const INPUT_ADDR: u64 = 0x4000_0000; /// Maximum size of the input data -pub const MAX_INPUT_SIZE: u64 = 0x08000000; // 128M, +pub const MAX_INPUT_SIZE: u64 = 0x4000_0000; // 128M, /// Free input data memory address = first input address pub const FREE_INPUT_ADDR: u64 = INPUT_ADDR; /// First global RW memory address diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index ce5c61cce..8cadaf70a 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -146,7 +146,7 @@ pub struct ZiskAsmContext { mem_chunk_id: String, // 0, 1, 2, 3, 4... mem_chunk_mask: String, // Module 8 of the chunks we want to activate, e.g. 0x03 mem_rsp: String, // Backup of rsp register value from caller - mem_free_input: String, // Free input address (0x90000000) used in free call operations, but stored in memory to allow sharing the input shared memory + mem_free_input: String, // Free input address (0x40000000) used in free call operations, but stored in memory to allow sharing the input shared memory mem_precompile_results_address: String, // Address where precompile results are read from mem_precompile_written_address: String, // Address where precompile written counter is stored mem_precompile_read_address: String, // Address where precompile read counter is stored diff --git a/emulator-asm/src/constants.hpp b/emulator-asm/src/constants.hpp index c9a88259a..70f1668ba 100644 --- a/emulator-asm/src/constants.hpp +++ b/emulator-asm/src/constants.hpp @@ -12,8 +12,8 @@ // assembly code, and that are used by the assembly code to access memory and generate the trace #define ROM_ADDR (uint64_t)0x80000000 #define ROM_SIZE (uint64_t)0x08000000 // 128MB -#define INPUT_ADDR (uint64_t)0x90000000 -#define MAX_INPUT_SIZE (uint64_t)0x08000000 // 128MB +#define INPUT_ADDR (uint64_t)0x40000000 +#define MAX_INPUT_SIZE (uint64_t)0x40000000 // 1024MB #define RAM_ADDR (uint64_t)0xA0000000 #define RAM_SIZE (uint64_t)0x20000000 // 512MB diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 37bd108a6..0a33bdb6a 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -1471,6 +1471,7 @@ impl<'a> Emu<'a> { #[inline(always)] pub fn step_fast(&mut self) { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + // println!("TRACE PC:0x{:0X} {}", self.ctx.inst_ctx.pc, instruction.verbose); // let debug = instruction.op >= 0xf6; // let initial_regs = if debug { // print!( @@ -1497,6 +1498,11 @@ impl<'a> Emu<'a> { // }; self.source_a(instruction); self.source_b(instruction); + if instruction.input_size > 0 { + self.ctx.inst_ctx.extended_arg = instruction.jmp_offset1; + } else { + self.ctx.inst_ctx.extended_arg = 0; + } (instruction.func)(&mut self.ctx.inst_ctx); self.store_c(instruction); @@ -1921,7 +1927,7 @@ impl<'a> Emu<'a> { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); let pc = self.ctx.inst_ctx.pc; - // println!("PCLOG={}", instruction.to_text()); + println!("PCLOG={}", instruction.to_text()); // Build the 'a' register value based on the source specified by the current instruction self.source_a(instruction); @@ -2039,6 +2045,7 @@ impl<'a> Emu<'a> { #[inline(always)] pub fn par_step_my_block(&mut self, emu_full_trace_vec: &mut EmuTrace) { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + println!("TRACE PC:0x{:0X} {}", self.ctx.inst_ctx.pc, instruction.verbose); // Extract the Vec once for all mem_reads operations let mem_reads = emu_full_trace_vec.mem_reads.to_mut(); @@ -2050,6 +2057,8 @@ impl<'a> Emu<'a> { mem_reads.len() ); + // println!("PC:0x{:08X} {}", self.ctx.inst_ctx.pc, instruction.verbose); + // Build the 'a' register value based on the source specified by the current instruction self.source_a_mem_reads_generate(instruction, mem_reads); @@ -2239,6 +2248,7 @@ impl<'a> Emu<'a> { self.ctx.inst_ctx.step, mem_reads_index ); + println!("TRACE PC:0x{:0X} {}", self.ctx.inst_ctx.pc, instruction.verbose); self.source_a_mem_reads_consume_no_mem_ops(instruction, mem_reads, mem_reads_index); self.source_b_mem_reads_consume_no_mem_ops(instruction, mem_reads, mem_reads_index); // If this is a precompiled, get the required input data from mem_reads diff --git a/emulator/src/emu_options.rs b/emulator/src/emu_options.rs index 222c02876..1c1205105 100644 --- a/emulator/src/emu_options.rs +++ b/emulator/src/emu_options.rs @@ -167,7 +167,7 @@ impl Default for EmuOptions { no_thousands_sep: false, top_roi_filter: false, disasm: None, - max_input_mem: MAX_INPUT_SIZE, // 128 MiB + max_input_mem: MAX_INPUT_SIZE, } } } diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index a6625dd03..b9d9a51be 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "b86dab0c9c7c324ad5667e56c7eb399b53ddd1f86efae71242f5be294cfdb812"; +pub const PILOUT_HASH: &str = "d44ddbfff9071ead61b56c5952ed29c5d5c6388c9394c37b973a12f1c3fe5c2d"; pub const MERKLE_TREE_ARITY: u64 = 4; @@ -659,7 +659,7 @@ values!(RomDataAirValues { }); values!(InputDataAirValues { - segment_id: F, is_first_segment: F, is_last_segment: F, previous_segment_value: [F; 2], previous_segment_step: F, previous_segment_addr: F, segment_last_value: [F; 2], segment_last_step: F, segment_last_addr: F, im_direct: [FieldExtension; 4], + segment_id: F, is_first_segment: F, is_last_segment: F, previous_segment_value: [F; 2], previous_segment_step: F, previous_segment_addr: F, segment_last_value: [F; 2], segment_last_step: F, segment_last_addr: F, distance_base: [F; 2], distance_end: [F; 2], im_direct: [FieldExtension; 6], }); values!(MemAlignByteAirValues { diff --git a/pil/zisk.pil b/pil/zisk.pil index 704efbccf..1f5e2a1ef 100644 --- a/pil/zisk.pil +++ b/pil/zisk.pil @@ -106,7 +106,7 @@ airgroup Zisk { // Memory Mem(N: 2**22, base_address: 0xA000_0000, size_mb: 512, large_mem: 1, dual_mem: 1); Mem(N: 2**21, base_address: 0x8000_0000, size_mb: 128, immutable: 1, enable_flag: enable_rom_data) alias RomData; - Mem(N: 2**21, base_address: 0x9000_0000, size_mb: 128, free_input_mem: 1, enable_flag: enable_input_data) alias InputData; + Mem(N: 2**21, base_address: 0x4000_0000, size_mb: 1024, large_mem: 1, free_input_mem: 1, enable_flag: enable_input_data) alias InputData; MemAlign(N: 2**21); MemAlignByte(N: 2**22, read: 1, write: 1); diff --git a/state-machines/mem-common/src/mem_counters.rs b/state-machines/mem-common/src/mem_counters.rs index 030928a06..64d4851f8 100644 --- a/state-machines/mem-common/src/mem_counters.rs +++ b/state-machines/mem-common/src/mem_counters.rs @@ -72,10 +72,10 @@ impl MemCounters { let point = addr_vector.partition_point(|x| x.0 < (0xA000_0000 / 8)); self.addr_sorted[2] = addr_vector.split_off(point); - let point = addr_vector.partition_point(|x| x.0 < (0x9000_0000 / 8)); - self.addr_sorted[1] = addr_vector.split_off(point); + let point = addr_vector.partition_point(|x| x.0 < (0x8000_0000 / 8)); + self.addr_sorted[0] = addr_vector.split_off(point); - self.addr_sorted[0] = addr_vector; + self.addr_sorted[1] = addr_vector; } #[inline(always)] fn incr_st_counter_aligned(count: u32, is_write: bool) -> u32 { diff --git a/state-machines/mem-cpp/cpp/mem_config.hpp b/state-machines/mem-cpp/cpp/mem_config.hpp index d198a850c..2db06bd68 100644 --- a/state-machines/mem-cpp/cpp/mem_config.hpp +++ b/state-machines/mem-cpp/cpp/mem_config.hpp @@ -2,9 +2,13 @@ #define __MEM_CONFIG_HPP__ #define ROM_ADDR 0x80000000 -#define INPUT_ADDR 0x90000000 +#define INPUT_ADDR 0x40000000 #define RAM_ADDR 0xA0000000 +#define ROM_SIZE_MB 128 +#define INPUT_SIZE_MB 1024 +#define RAM_SIZE_MB 512 + #define CHUNK_SIZE_BITS 18 #define CHUNK_SIZE (1 << CHUNK_SIZE_BITS) #define MAX_LOCATORS 2048 @@ -36,7 +40,7 @@ #define MAX_THREADS (1 << THREAD_BITS) #define ADDR_MASK ((MAX_THREADS - 1) * 8) -#define MAX_PAGES 12 +#define MAX_PAGES 26 #define ADDR_PAGE_BITS (23 - THREAD_BITS) #define ADDR_PAGE_SIZE (1 << ADDR_PAGE_BITS) #define RELATIVE_OFFSET_MASK (ADDR_PAGE_SIZE - 1) @@ -47,7 +51,7 @@ #define ADDR_SLOT_BITS 5 #define ADDR_SLOT_SIZE (1 << ADDR_SLOT_BITS) #define ADDR_SLOT_MASK (0xFFFFFFFF << ADDR_SLOT_BITS) -#define ADDR_SLOTS ((1024 * 1024 * 64) / MAX_THREADS) +#define ADDR_SLOTS ((1024 * 1024 * 128) / MAX_THREADS) #define ADDR_SLOTS_SIZE (ADDR_SLOT_SIZE * ADDR_SLOTS) #define TIME_US_BY_CHUNK 173 diff --git a/state-machines/mem-cpp/cpp/mem_count_and_plan.cpp b/state-machines/mem-cpp/cpp/mem_count_and_plan.cpp index 9a710bc3c..5778a978c 100644 --- a/state-machines/mem-cpp/cpp/mem_count_and_plan.cpp +++ b/state-machines/mem-cpp/cpp/mem_count_and_plan.cpp @@ -63,12 +63,12 @@ void MemCountAndPlan::prepare() { mem_align_counter = std::make_unique(context); plan_workers.clear(); plan_workers.reserve(MAX_MEM_PLANNERS); - rom_data_planner = std::make_unique(ROM_ROWS, ROM_ADDR, 128, false); + rom_data_planner = std::make_unique(ROM_ROWS, ROM_ADDR, ROM_SIZE_MB, false); rom_data_planner->set_last_addr(ROM_ADDR - 8); - input_data_planner = std::make_unique(INPUT_ROWS, INPUT_ADDR, 128, false); - quick_mem_planner = std::make_unique(0, RAM_ROWS, RAM_ADDR, 512); + input_data_planner = std::make_unique(INPUT_ROWS, INPUT_ADDR, INPUT_SIZE_MB, false); + quick_mem_planner = std::make_unique(0, RAM_ROWS, RAM_ADDR, RAM_SIZE_MB); for (int i = 0; i < MAX_MEM_PLANNERS; ++i) { - plan_workers.emplace_back(i+1, RAM_ROWS, RAM_ADDR, 512); + plan_workers.emplace_back(i+1, RAM_ROWS, RAM_ADDR, RAM_SIZE_MB); } t_prepare_us = get_usec() - init; } diff --git a/state-machines/mem-cpp/cpp/mem_counter.hpp b/state-machines/mem-cpp/cpp/mem_counter.hpp index 075c0eb2c..e5a3f5a37 100644 --- a/state-machines/mem-cpp/cpp/mem_counter.hpp +++ b/state-machines/mem-cpp/cpp/mem_counter.hpp @@ -221,18 +221,32 @@ uint32_t MemCounter::addr_to_offset(uint32_t addr, uint32_t chunk_id) { // ROM: 128 MB case (ROM_ADDR_MASK + 0x00): return ((addr - (ROM_ADDR + 0x00000000)) >> (ADDR_LOW_BITS)); case (ROM_ADDR_MASK + 0x04): return ((addr - (ROM_ADDR + 0x04000000)) >> (ADDR_LOW_BITS)) + ADDR_PAGE_SIZE; - // INPUT: 128 MB + // INPUT: 1024 MB case (INPUT_ADDR_MASK + 0x00): return ((addr - (INPUT_ADDR + 0x00000000)) >> (ADDR_LOW_BITS)) + 2 * ADDR_PAGE_SIZE; case (INPUT_ADDR_MASK + 0x04): return ((addr - (INPUT_ADDR + 0x04000000)) >> (ADDR_LOW_BITS)) + 3 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x08): return ((addr - (INPUT_ADDR + 0x08000000)) >> (ADDR_LOW_BITS)) + 4 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x0C): return ((addr - (INPUT_ADDR + 0x0C000000)) >> (ADDR_LOW_BITS)) + 5 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x10): return ((addr - (INPUT_ADDR + 0x10000000)) >> (ADDR_LOW_BITS)) + 6 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x14): return ((addr - (INPUT_ADDR + 0x14000000)) >> (ADDR_LOW_BITS)) + 7 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x18): return ((addr - (INPUT_ADDR + 0x18000000)) >> (ADDR_LOW_BITS)) + 8 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x1C): return ((addr - (INPUT_ADDR + 0x1C000000)) >> (ADDR_LOW_BITS)) + 9 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x20): return ((addr - (INPUT_ADDR + 0x20000000)) >> (ADDR_LOW_BITS)) + 10 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x24): return ((addr - (INPUT_ADDR + 0x24000000)) >> (ADDR_LOW_BITS)) + 11 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x28): return ((addr - (INPUT_ADDR + 0x28000000)) >> (ADDR_LOW_BITS)) + 12 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x2C): return ((addr - (INPUT_ADDR + 0x2C000000)) >> (ADDR_LOW_BITS)) + 13 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x30): return ((addr - (INPUT_ADDR + 0x30000000)) >> (ADDR_LOW_BITS)) + 14 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x34): return ((addr - (INPUT_ADDR + 0x34000000)) >> (ADDR_LOW_BITS)) + 15 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x38): return ((addr - (INPUT_ADDR + 0x38000000)) >> (ADDR_LOW_BITS)) + 16 * ADDR_PAGE_SIZE; + case (INPUT_ADDR_MASK + 0x3C): return ((addr - (INPUT_ADDR + 0x3C000000)) >> (ADDR_LOW_BITS)) + 17 * ADDR_PAGE_SIZE; // RAM: 512 MB - case (RAM_ADDR_MASK + 0x00): return ((addr - (RAM_ADDR + 0x00000000)) >> (ADDR_LOW_BITS)) + 4 * ADDR_PAGE_SIZE; - case (RAM_ADDR_MASK + 0x04): return ((addr - (RAM_ADDR + 0x04000000)) >> (ADDR_LOW_BITS)) + 5 * ADDR_PAGE_SIZE; - case (RAM_ADDR_MASK + 0x08): return ((addr - (RAM_ADDR + 0x08000000)) >> (ADDR_LOW_BITS)) + 6 * ADDR_PAGE_SIZE; - case (RAM_ADDR_MASK + 0x0C): return ((addr - (RAM_ADDR + 0x0C000000)) >> (ADDR_LOW_BITS)) + 7 * ADDR_PAGE_SIZE; - case (RAM_ADDR_MASK + 0x10): return ((addr - (RAM_ADDR + 0x10000000)) >> (ADDR_LOW_BITS)) + 8 * ADDR_PAGE_SIZE; - case (RAM_ADDR_MASK + 0x14): return ((addr - (RAM_ADDR + 0x14000000)) >> (ADDR_LOW_BITS)) + 9 * ADDR_PAGE_SIZE; - case (RAM_ADDR_MASK + 0x18): return ((addr - (RAM_ADDR + 0x18000000)) >> (ADDR_LOW_BITS)) + 10 * ADDR_PAGE_SIZE; - case (RAM_ADDR_MASK + 0x1C): return ((addr - (RAM_ADDR + 0x1C000000)) >> (ADDR_LOW_BITS)) + 11 * ADDR_PAGE_SIZE; + case (RAM_ADDR_MASK + 0x00): return ((addr - (RAM_ADDR + 0x00000000)) >> (ADDR_LOW_BITS)) + 18 * ADDR_PAGE_SIZE; + case (RAM_ADDR_MASK + 0x04): return ((addr - (RAM_ADDR + 0x04000000)) >> (ADDR_LOW_BITS)) + 19 * ADDR_PAGE_SIZE; + case (RAM_ADDR_MASK + 0x08): return ((addr - (RAM_ADDR + 0x08000000)) >> (ADDR_LOW_BITS)) + 20 * ADDR_PAGE_SIZE; + case (RAM_ADDR_MASK + 0x0C): return ((addr - (RAM_ADDR + 0x0C000000)) >> (ADDR_LOW_BITS)) + 21 * ADDR_PAGE_SIZE; + case (RAM_ADDR_MASK + 0x10): return ((addr - (RAM_ADDR + 0x10000000)) >> (ADDR_LOW_BITS)) + 22 * ADDR_PAGE_SIZE; + case (RAM_ADDR_MASK + 0x14): return ((addr - (RAM_ADDR + 0x14000000)) >> (ADDR_LOW_BITS)) + 23 * ADDR_PAGE_SIZE; + case (RAM_ADDR_MASK + 0x18): return ((addr - (RAM_ADDR + 0x18000000)) >> (ADDR_LOW_BITS)) + 24 * ADDR_PAGE_SIZE; + case (RAM_ADDR_MASK + 0x1C): return ((addr - (RAM_ADDR + 0x1C000000)) >> (ADDR_LOW_BITS)) + 25 * ADDR_PAGE_SIZE; } std::ostringstream msg; msg << "ERROR: addr_to_offset: 0x" << std::hex << addr << " (" << std::dec << chunk_id << ")"; @@ -244,18 +258,32 @@ uint32_t MemCounter::addr_to_page(uint32_t addr, uint32_t chunk_id) { // ROM: 128 MB case (ROM_ADDR_MASK + 0x00): return 0; case (ROM_ADDR_MASK + 0x04): return 1; - // INPUT: 128 MB + // INPUT: 1024 MB case (INPUT_ADDR_MASK + 0x00): return 2; case (INPUT_ADDR_MASK + 0x04): return 3; + case (INPUT_ADDR_MASK + 0x08): return 4; + case (INPUT_ADDR_MASK + 0x0C): return 5; + case (INPUT_ADDR_MASK + 0x10): return 6; + case (INPUT_ADDR_MASK + 0x14): return 7; + case (INPUT_ADDR_MASK + 0x18): return 8; + case (INPUT_ADDR_MASK + 0x1C): return 9; + case (INPUT_ADDR_MASK + 0x20): return 10; + case (INPUT_ADDR_MASK + 0x24): return 11; + case (INPUT_ADDR_MASK + 0x28): return 12; + case (INPUT_ADDR_MASK + 0x2C): return 13; + case (INPUT_ADDR_MASK + 0x30): return 14; + case (INPUT_ADDR_MASK + 0x34): return 15; + case (INPUT_ADDR_MASK + 0x38): return 16; + case (INPUT_ADDR_MASK + 0x3C): return 17; // RAM: 512 MB - case (RAM_ADDR_MASK + 0x00): return 4; - case (RAM_ADDR_MASK + 0x04): return 5; - case (RAM_ADDR_MASK + 0x08): return 6; - case (RAM_ADDR_MASK + 0x0C): return 7; - case (RAM_ADDR_MASK + 0x10): return 8; - case (RAM_ADDR_MASK + 0x14): return 9; - case (RAM_ADDR_MASK + 0x18): return 10; - case (RAM_ADDR_MASK + 0x1C): return 11; + case (RAM_ADDR_MASK + 0x00): return 18; + case (RAM_ADDR_MASK + 0x04): return 19; + case (RAM_ADDR_MASK + 0x08): return 20; + case (RAM_ADDR_MASK + 0x0C): return 21; + case (RAM_ADDR_MASK + 0x10): return 22; + case (RAM_ADDR_MASK + 0x14): return 23; + case (RAM_ADDR_MASK + 0x18): return 24; + case (RAM_ADDR_MASK + 0x1C): return 25; } std::ostringstream msg; msg << "ERROR: addr_to_page: 0x" << std::hex << addr << " (" << std::dec << chunk_id << ")"; @@ -267,18 +295,32 @@ uint32_t MemCounter::page_to_addr(uint8_t page) { // ROM: 128 MB case 0: return (ROM_ADDR + 0x00000000); case 1: return (ROM_ADDR + 0x04000000); - // INPUT: 128 MB + // INPUT: 1024 MB case 2: return (INPUT_ADDR + 0x00000000); case 3: return (INPUT_ADDR + 0x04000000); + case 4: return (INPUT_ADDR + 0x08000000); + case 5: return (INPUT_ADDR + 0x0C000000); + case 6: return (INPUT_ADDR + 0x10000000); + case 7: return (INPUT_ADDR + 0x14000000); + case 8: return (INPUT_ADDR + 0x18000000); + case 9: return (INPUT_ADDR + 0x1C000000); + case 10: return (INPUT_ADDR + 0x20000000); + case 11: return (INPUT_ADDR + 0x24000000); + case 12: return (INPUT_ADDR + 0x28000000); + case 13: return (INPUT_ADDR + 0x2C000000); + case 14: return (INPUT_ADDR + 0x30000000); + case 15: return (INPUT_ADDR + 0x34000000); + case 16: return (INPUT_ADDR + 0x38000000); + case 17: return (INPUT_ADDR + 0x3C000000); // RAM: 512 MB - case 4: return (RAM_ADDR + 0x00000000); - case 5: return (RAM_ADDR + 0x04000000); - case 6: return (RAM_ADDR + 0x08000000); - case 7: return (RAM_ADDR + 0x0C000000); - case 8: return (RAM_ADDR + 0x10000000); - case 9: return (RAM_ADDR + 0x14000000); - case 10: return (RAM_ADDR + 0x18000000); - case 11: return (RAM_ADDR + 0x1C000000); + case 18: return (RAM_ADDR + 0x00000000); + case 19: return (RAM_ADDR + 0x04000000); + case 20: return (RAM_ADDR + 0x08000000); + case 21: return (RAM_ADDR + 0x0C000000); + case 22: return (RAM_ADDR + 0x10000000); + case 23: return (RAM_ADDR + 0x14000000); + case 24: return (RAM_ADDR + 0x18000000); + case 25: return (RAM_ADDR + 0x1C000000); case 0xFF: return 0xFFFFFFFF; } std::ostringstream msg; diff --git a/state-machines/mem-cpp/cpp/mem_test.hpp b/state-machines/mem-cpp/cpp/mem_test.hpp index 0e9968162..1a2250815 100644 --- a/state-machines/mem-cpp/cpp/mem_test.hpp +++ b/state-machines/mem-cpp/cpp/mem_test.hpp @@ -70,7 +70,7 @@ class MemTest { // if (chunk_id == 999999) { for (int32_t i = 0; i < chunk_size; ++i) { const uint32_t addr = chunk_data[i].addr; - if (addr < 0x80000000 || addr >= 0x90000000) continue; + if (addr < 0x40000000 || addr >= 0x80000000) continue; const uint8_t bytes = chunk_data[i].flags & 0xFF; const uint8_t is_write = chunk_data[i].flags >> 16; if (addr == 0x80000000) printf("=================> ADDR 0x80000000\n"); diff --git a/state-machines/mem/src/input_data_sm.rs b/state-machines/mem/src/input_data_sm.rs index 227aa802c..2f198b511 100644 --- a/state-machines/mem/src/input_data_sm.rs +++ b/state-machines/mem/src/input_data_sm.rs @@ -30,8 +30,8 @@ const _: () = { "INPUT_DATA memory exceeds the 32-bit addressable range" ); assert!( - (MAX_INPUT_SIZE - 1) <= (128 << 20), - "INPUT_DATA is too large. Input size must be <= 128MB" + (MAX_INPUT_SIZE - 1) <= (1024 << 20), + "INPUT_DATA is too large. Input size must be <= 1024MB" ); }; @@ -43,7 +43,7 @@ pub struct InputDataSM { range_id: usize, /// Range check ID for the 16-bit chunks of the input values - range_chunks_id: usize, + range_16bits_id: usize, } #[allow(unused, unused_variables)] @@ -55,7 +55,7 @@ impl InputDataSM { let range_chunks_id = std.get_range_id(0, (1 << 16) - 1, None).expect("Failed to get range ID"); - Arc::new(Self { range_chunks_id, std: std.clone(), range_id }) + Arc::new(Self { range_16bits_id: range_chunks_id, std: std.clone(), range_id }) } fn get_u16_values(&self, value: u64) -> [u16; 4] { [value as u16, (value >> 16) as u16, (value >> 32) as u16, (value >> 48) as u16] @@ -105,17 +105,11 @@ impl MemModule for InputDataSM { num_rows ); - let mut range_check_data: Vec = vec![0; 1 << 16]; - - // range of instance - self.std.range_check( - self.range_id, - (previous_segment.addr - INPUT_DATA_W_ADDR_INIT) as i64, - 1, - ); + let mut range_16bits: Vec = vec![0; 1 << 16]; let mut max_range_distance_count = 0; + let distance_base = previous_segment.addr - INPUT_DATA_W_ADDR_INIT; let mut last_addr: u32 = previous_segment.addr; let mut last_step: u64 = previous_segment.step; let mut last_value: u64 = previous_segment.value; @@ -161,7 +155,7 @@ impl MemModule for InputDataSM { i += 1; } - range_check_data[0] += 4 * internal_reads; + range_16bits[0] += 4 * internal_reads; if incomplete { break; } @@ -175,7 +169,7 @@ impl MemModule for InputDataSM { let value = mem_op.value; let value_words = self.get_u16_values(value); for j in 0..4 { - range_check_data[value_words[j] as usize] += 1; + range_16bits[value_words[j] as usize] += 1; trace[i].set_value_word(j, value_words[j]); } @@ -218,19 +212,19 @@ impl MemModule for InputDataSM { // address doesn't change in padding rows, no range check is required } + let distance_end = INPUT_DATA_W_ADDR_END - last_addr; + self.std.range_check( self.range_id, SEGMENT_ADDR_MAX_RANGE as i64, max_range_distance_count, ); - self.std.range_check(self.range_id, (INPUT_DATA_W_ADDR_END - last_addr) as i64, 1); // range of chunks for j in 0..4 { let value = trace[last_row_idx].get_value_word(j); - range_check_data[value as usize] += padding_size as u32; + range_16bits[value as usize] += padding_size as u32; } - self.std.range_checks(self.range_chunks_id, range_check_data); let mut air_values = InputDataAirValues::::new(); air_values.segment_id = F::from_usize(segment_id.into()); @@ -247,6 +241,22 @@ impl MemModule for InputDataSM { air_values.segment_last_value[0] = F::from_u32(last_value as u32); air_values.segment_last_value[1] = F::from_u32((last_value >> 32) as u32); + let distance_base = [distance_base as u16, (distance_base >> 16) as u16]; + let distance_end = [distance_end as u16, (distance_end >> 16) as u16]; + + air_values.distance_base[0] = F::from_u16(distance_base[0]); + air_values.distance_base[1] = F::from_u16(distance_base[1]); + + air_values.distance_end[0] = F::from_u16(distance_end[0]); + air_values.distance_end[1] = F::from_u16(distance_end[1]); + + range_16bits[distance_base[0] as usize] += 1; + range_16bits[distance_base[1] as usize] += 1; + range_16bits[distance_end[0] as usize] += 1; + range_16bits[distance_end[1] as usize] += 1; + + self.std.range_checks(self.range_16bits_id, range_16bits); + Ok(AirInstance::new_from_trace(FromTrace::new(&mut trace).with_air_values(&mut air_values))) } } diff --git a/state-machines/mem/src/mem_test.rs b/state-machines/mem/src/mem_test.rs index 97a803c14..c51332657 100644 --- a/state-machines/mem/src/mem_test.rs +++ b/state-machines/mem/src/mem_test.rs @@ -12,7 +12,7 @@ fn generate_test_plans( ) -> Vec { let addr_index = match from_addr { 0x8000_0000 => 0, - 0x9000_0000 => 1, + 0x4000_0000 => 1, 0xA000_0000 => 2, _ => panic!("invalid addr 0x{from_addr:X}"), }; @@ -148,9 +148,9 @@ fn test_counters() { add_mem_write64(&mut counter, 0xA000_0000, 80, 0x2222_2222_6666_1111); add_mem_read64(&mut counter, 0xA000_0016, 85, 0x3333_3333_3333_3333); add_mem_read64(&mut counter, 0xA000_0000, 90, 0x2222_2222_6666_1111); // dual => 2 (A000_0002) + 5 rows = 7 - add_mem_data(&mut counter, 10, 0x9000_0000, 10, 0x4041_4243_4445_4647, 8, false, &cfg); + add_mem_data(&mut counter, 10, 0x4000_0000, 10, 0x4041_4243_4445_4647, 8, false, &cfg); counter.close(); - assert_eq!(format!("{counter:?}"), "[MEM_0,#:10 => 0x80000000:2 0x80000008:2 0x80000010:2 0x80000018:2 0x80000020:2 0x80000028:2 0x80000030:2 0x80000038:2 0x80000040:2 0x80000048:2][MEM_1,#:10 => 0x90000000:1 0x90000008:1 0x90000010:1 0x90000018:1 0x90000020:1 0x90000028:1 0x90000030:1 0x90000038:1 0x90000040:1 0x90000048:1][MEM_2,#:4 => 0xA0000000:7 0xA0000008:2 0xA0000010:1 0xA0000018:1]"); + assert_eq!(format!("{counter:?}"), "[MEM_0,#:10 => 0x80000000:2 0x80000008:2 0x80000010:2 0x80000018:2 0x80000020:2 0x80000028:2 0x80000030:2 0x80000038:2 0x80000040:2 0x80000048:2][MEM_1,#:10 => 0x40000000:1 0x40000008:1 0x40000010:1 0x40000018:1 0x40000020:1 0x40000028:1 0x40000030:1 0x40000038:1 0x40000040:1 0x40000048:1][MEM_2,#:4 => 0xA0000000:7 0xA0000008:2 0xA0000010:1 0xA0000018:1]"); } #[test] diff --git a/ziskos/entrypoint/src/ziskos_definitions.rs b/ziskos/entrypoint/src/ziskos_definitions.rs index a606e1651..87fd20688 100644 --- a/ziskos/entrypoint/src/ziskos_definitions.rs +++ b/ziskos/entrypoint/src/ziskos_definitions.rs @@ -3,7 +3,7 @@ pub mod ziskos_config { pub const QEMU_EXIT_ADDR: u64 = 0x100000; pub const QEMU_EXIT_CODE: u64 = 0x5555; - pub const INPUT_ADDR: u64 = 0x9000_0000; + pub const INPUT_ADDR: u64 = 0x4000_0000; pub const OUTPUT_ADDR: u64 = 0xa001_0000; pub const UART_ADDR: u64 = 0xa000_0200; pub const ARCH_ID_ZISK: u64 = 0xFFFEEEE; // TEMPORARY // TODO register one From 63f4b5e122056ab3c0cb11aac4f909e7f38bb30e Mon Sep 17 00:00:00 2001 From: fractasy Date: Thu, 5 Mar 2026 20:50:29 +0100 Subject: [PATCH 710/782] Fix assembly with input file --- core/src/zisk_rom_2_asm.rs | 102 +++++++++++++++---------------- emulator-asm/src/client.c | 7 +-- emulator-asm/src/configuration.c | 35 +++++------ emulator-asm/src/server.c | 2 +- 4 files changed, 69 insertions(+), 77 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index 0d1f825b3..c3b88f290 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -6174,30 +6174,30 @@ impl ZiskRom2Asm { *code += &ctx.full_line_comment("Fcall".to_string()); assert!(ctx.store_b_in_c); + if !ctx.chunk_player_mt_collect_mem() && !ctx.chunk_player_mem_reads_collect_main() { - // Store a (function id) in context - assert!(ctx.a.is_constant); - *code += &format!( - "\tmov qword {}[{} + {}*8], {} {}\n", - ctx.ptr, - ctx.fcall_ctx, - FCALL_FUNCTION_ID, - ctx.a.constant_value, - ctx.comment_str("ctx.function id = a") - ); - // Get result from precompile results data if ctx.a.constant_value == FCALL_INPUT_READY_ID as u64 { - Self::wait_for_input_ready(ctx, code, unusual_code, REG_C); + Self::wait_for_input_ready(ctx, code, unusual_code); } else { + // Store a (function id) in context + assert!(ctx.a.is_constant); + *code += &format!( + "\tmov qword {}[{} + {}*8], {} {}\n", + ctx.ptr, + ctx.fcall_ctx, + FCALL_FUNCTION_ID, + ctx.a.constant_value, + ctx.comment_str("ctx.function id = a") + ); + // Set the fcall context address as the first parameter *code += &format!( "\tlea rdi, {} {}\n", ctx.fcall_ctx, ctx.comment_str("rdi = fcall context") ); - // Get result from precompile results data if ctx.precompile_results_fcall() { Self::precompile_results_fcall(ctx, code, unusual_code, "rdi"); @@ -6209,36 +6209,36 @@ impl ZiskRom2Asm { Self::pop_internal_registers(ctx, code, false); //Self::assert_rsp_is_aligned(ctx, code); } - } - // If ctx.result_size == 0 => free_input = 0 - *code += &format!( - "\tmov {}, qword {}[{} + {}*8] {}\n", - REG_AUX, - ctx.ptr, - ctx.fcall_ctx, - FCALL_RESULT_SIZE, - ctx.comment_str("aux = ctx.result_size") - ); - *code += &format!("\tcmp {REG_AUX}, 0\n"); - *code += &format!("\tjz pc_{:x}_fcall_result_zero\n", ctx.pc); + // If ctx.result_size == 0 => free_input = 0 + *code += &format!( + "\tmov {}, qword {}[{} + {}*8] {}\n", + REG_AUX, + ctx.ptr, + ctx.fcall_ctx, + FCALL_RESULT_SIZE, + ctx.comment_str("aux = ctx.result_size") + ); + *code += &format!("\tcmp {REG_AUX}, 0\n"); + *code += &format!("\tjz pc_{:x}_fcall_result_zero\n", ctx.pc); - // Copy ctx.result[0] to free input address - *code += &format!( - "\tmov {}, qword {}[{} + {}*8] {}\n", - REG_VALUE, - ctx.ptr, - ctx.fcall_ctx, - FCALL_RESULT, - ctx.comment_str("value = ctx.result[0]") - ); - *code += &format!( - "\tmov {}, {} {}\n", - ctx.mem_free_input, - REG_VALUE, - ctx.comment_str("free_input = value") - ); - *code += &format!("\tjmp pc_{:x}_fcall_result_done\n", ctx.pc); + // Copy ctx.result[0] to free input address + *code += &format!( + "\tmov {}, qword {}[{} + {}*8] {}\n", + REG_VALUE, + ctx.ptr, + ctx.fcall_ctx, + FCALL_RESULT, + ctx.comment_str("value = ctx.result[0]") + ); + *code += &format!( + "\tmov {}, {} {}\n", + ctx.mem_free_input, + REG_VALUE, + ctx.comment_str("free_input = value") + ); + *code += &format!("\tjmp pc_{:x}_fcall_result_done\n", ctx.pc); + } *code += &format!("pc_{:x}_fcall_result_zero:\n", ctx.pc); *code += &format!( @@ -8804,7 +8804,7 @@ impl ZiskRom2Asm { ctx: &mut ZiskAsmContext, code: &mut String, unusual_code: &mut String, - reg_address: &str, // REG_C + //reg_address: &str, // fcall structure address ) { *code += &ctx.full_line_comment("Wait for input data available".to_string()); @@ -8812,10 +8812,16 @@ impl ZiskRom2Asm { // required_bytes = (required_address - INPUT_ADDR - 8 + 1 + 7) & ~0x7; // + 1 because required_address is the address of the last required byte // + 7 & ~0x7 because if we require any byte of the last u64, we need to wait for the whole u64 to be available - *code += - &format!("\tmov rdi, {} {}\n", reg_address, ctx.comment_str("rdi = required_address")); + assert!(ctx.a.is_constant); *code += &format!( - "\tmov {}, {} {}\n", + "\tmov rdi, qword {}[{} + {}*8] {}\n", + ctx.ptr, + ctx.fcall_ctx, + FCALL_PARAMS, + ctx.comment_str("rdi = params[0] = required_address") + ); + *code += &format!( + "\tmov {}, 0x{:x} {}\n", REG_AUX, INPUT_ADDR, ctx.comment_str("aux = INPUT_ADDRESS") @@ -8828,12 +8834,6 @@ impl ZiskRom2Asm { *code += &format!("\tand rdi, ~0x7 {}\n", ctx.comment_str("rdi &= 0x7")); // if input_written == input_ready then call wait_for_input_avail - *code += &format!( - "\tmov {}, {} {}\n", - REG_AUX, - ctx.mem_input_written_address, - ctx.comment_str("aux = input_written") - ); *code += &format!( "\tcmp rdi, {} {}\n", ctx.mem_input_written_address, diff --git a/emulator-asm/src/client.c b/emulator-asm/src/client.c index 35e731e24..640f7e0b7 100644 --- a/emulator-asm/src/client.c +++ b/emulator-asm/src/client.c @@ -612,12 +612,11 @@ void client_run (void) exit(-1); } - // Write the input size in the first 64 bits + // Write the free input value as 0 in the first 64 bits *(uint64_t *)shmem_input_address = (uint64_t)0; // free input - *(uint64_t *)(shmem_input_address + 8)= (uint64_t)input_data_size; // Copy input data into input memory - size_t input_read = fread(shmem_input_address + 16, 1, input_data_size, input_fp); + size_t input_read = fread(shmem_input_address + 8, 1, input_data_size, input_fp); if (input_read != input_data_size) { printf("ERROR: Input read (%lu) != input file size (%lu)\n", input_read, input_data_size); @@ -640,7 +639,7 @@ void client_run (void) } // Set written counter - *input_written_address = input_data_size + 8; // in bytes, including the 8 bytes of input size at the beginning + *input_written_address = input_data_size; // in bytes #ifdef DEBUG gettimeofday(&stop_time, NULL); diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index 83cb93ba9..183bbaab6 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -525,20 +525,19 @@ void configure (void) strcat(sem_prec_avail_name, "_FT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_FT_prec_read"); - strcpy(sem_input_avail_name, shm_prefix); - strcat(sem_input_avail_name, "_FT_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); - strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, ""); strcpy(sem_chunk_done_name, ""); strcpy(sem_shutdown_done_name, shm_prefix); strcat(sem_shutdown_done_name, "_FT_shutdown_done"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_FT_input_avail"); strcpy(shmem_mt_name, ""); strcpy(file_lock_name, "/tmp/"); strcat(file_lock_name, shm_prefix); @@ -573,15 +572,12 @@ void configure (void) strcat(sem_prec_avail_name, "_MT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MT_prec_read"); - strcpy(sem_input_avail_name, shm_prefix); - strcat(sem_input_avail_name, "_MT_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); - strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); @@ -589,6 +585,8 @@ void configure (void) strcat(sem_chunk_done_name, "_MT_chunk_done"); strcpy(sem_shutdown_done_name, shm_prefix); strcat(sem_shutdown_done_name, "_MT_shutdown_done"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MT_input_avail"); strcpy(shmem_mt_name, ""); strcpy(file_lock_name, "/tmp/"); strcat(file_lock_name, shm_prefix); @@ -624,15 +622,12 @@ void configure (void) strcat(sem_prec_avail_name, "_RH_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_RH_prec_read"); - strcpy(sem_input_avail_name, shm_prefix); - strcat(sem_input_avail_name, "_RH_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); - strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_RH_output"); @@ -640,6 +635,8 @@ void configure (void) strcat(sem_chunk_done_name, "_RH_chunk_done"); strcpy(sem_shutdown_done_name, shm_prefix); strcat(sem_shutdown_done_name, "_RH_shutdown_done"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_RH_input_avail"); strcpy(shmem_mt_name, ""); strcpy(file_lock_name, "/tmp/"); strcat(file_lock_name, shm_prefix); @@ -675,15 +672,12 @@ void configure (void) strcat(sem_prec_avail_name, "_MA_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MA_prec_read"); - strcpy(sem_input_avail_name, shm_prefix); - strcat(sem_input_avail_name, "_MA_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); - strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MA_output"); @@ -691,6 +685,8 @@ void configure (void) strcat(sem_chunk_done_name, "_MA_chunk_done"); strcpy(sem_shutdown_done_name, shm_prefix); strcat(sem_shutdown_done_name, "_MA_shutdown_done"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MA_input_avail"); strcpy(shmem_mt_name, ""); strcpy(file_lock_name, "/tmp/"); strcat(file_lock_name, shm_prefix); @@ -769,15 +765,12 @@ void configure (void) strcat(sem_prec_avail_name, "_ZP_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_ZP_prec_read"); - strcpy(sem_input_avail_name, shm_prefix); - strcat(sem_input_avail_name, "_ZP_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); - strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_ZP_output"); @@ -785,6 +778,8 @@ void configure (void) strcat(sem_chunk_done_name, "_ZP_chunk_done"); strcpy(sem_shutdown_done_name, shm_prefix); strcat(sem_shutdown_done_name, "_ZP_shutdown_done"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_ZP_input_avail"); strcpy(shmem_mt_name, ""); strcpy(file_lock_name, "/tmp/"); strcat(file_lock_name, shm_prefix); @@ -820,15 +815,12 @@ void configure (void) strcat(sem_prec_avail_name, "_MO_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MO_prec_read"); - strcpy(sem_input_avail_name, shm_prefix); - strcat(sem_input_avail_name, "_MO_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); - strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MO_output"); @@ -836,6 +828,8 @@ void configure (void) strcat(sem_chunk_done_name, "_MO_chunk_done"); strcpy(sem_shutdown_done_name, shm_prefix); strcat(sem_shutdown_done_name, "_MO_shutdown_done"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MO_input_avail"); strcpy(shmem_mt_name, ""); strcpy(file_lock_name, "/tmp/"); strcat(file_lock_name, shm_prefix); @@ -900,15 +894,12 @@ void configure (void) strcat(sem_prec_avail_name, "_MT_prec_avail"); strcpy(sem_prec_read_name, shm_prefix); strcat(sem_prec_read_name, "_MT_prec_read"); - strcpy(sem_input_avail_name, shm_prefix); - strcat(sem_input_avail_name, "_MT_input_avail"); } else { strcpy(shmem_precompile_name, ""); strcpy(sem_prec_avail_name, ""); strcpy(sem_prec_read_name, ""); - strcpy(sem_input_avail_name, ""); } strcpy(shmem_output_name, shm_prefix); strcat(shmem_output_name, "_MT_output"); @@ -916,6 +907,8 @@ void configure (void) strcat(sem_chunk_done_name, "_MT_chunk_done"); strcpy(sem_shutdown_done_name, shm_prefix); strcat(sem_shutdown_done_name, "_MT_shutdown_done"); + strcpy(sem_input_avail_name, shm_prefix); + strcat(sem_input_avail_name, "_MT_input_avail"); strcpy(shmem_mt_name, ""); strcpy(file_lock_name, "/tmp/"); strcat(file_lock_name, shm_prefix); diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index 3de9dd451..a146a4020 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -555,7 +555,7 @@ void server_setup (void) fflush(stderr); exit(-1); } - if (verbose) printf("sem_open(%s) succeeded sem_input_avail=%p\n", sem_input_avail_name, sem_input_avail); + if (verbose) printf("sem_open(%s) succeeded\n", sem_input_avail_name); } void server_reset_fast (void) From 9443454924295db5b4caf333227cd2b4ead1b595 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Thu, 5 Mar 2026 21:46:05 +0100 Subject: [PATCH 711/782] remove log --- emulator/src/emu.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 0a33bdb6a..e9156231f 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -2045,7 +2045,7 @@ impl<'a> Emu<'a> { #[inline(always)] pub fn par_step_my_block(&mut self, emu_full_trace_vec: &mut EmuTrace) { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); - println!("TRACE PC:0x{:0X} {}", self.ctx.inst_ctx.pc, instruction.verbose); + // println!("TRACE PC:0x{:0X} {}", self.ctx.inst_ctx.pc, instruction.verbose); // Extract the Vec once for all mem_reads operations let mem_reads = emu_full_trace_vec.mem_reads.to_mut(); @@ -2248,7 +2248,7 @@ impl<'a> Emu<'a> { self.ctx.inst_ctx.step, mem_reads_index ); - println!("TRACE PC:0x{:0X} {}", self.ctx.inst_ctx.pc, instruction.verbose); + // println!("TRACE PC:0x{:0X} {}", self.ctx.inst_ctx.pc, instruction.verbose); self.source_a_mem_reads_consume_no_mem_ops(instruction, mem_reads, mem_reads_index); self.source_b_mem_reads_consume_no_mem_ops(instruction, mem_reads, mem_reads_index); // If this is a precompiled, get the required input data from mem_reads From 1be9b421adc859c041bd1c80b4a91eee44c84658 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 5 Mar 2026 23:43:44 +0000 Subject: [PATCH 712/782] Add input_data buffer in HintBuffer --- core/src/zisk_rom_2_asm.rs | 9 +- ziskos/entrypoint/src/hints/blake2b.rs | 39 ++------ ziskos/entrypoint/src/hints/custom.rs | 4 +- ziskos/entrypoint/src/hints/hint_buffer.rs | 100 +++++++++++++-------- ziskos/entrypoint/src/hints/input_data.rs | 13 +-- ziskos/entrypoint/src/hints/keccak256.rs | 11 +-- ziskos/entrypoint/src/hints/macros.rs | 16 ++-- ziskos/entrypoint/src/hints/sha256f.rs | 11 +-- ziskos/entrypoint/src/lib.rs | 8 +- 9 files changed, 100 insertions(+), 111 deletions(-) diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index c3b88f290..b28926313 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -257,10 +257,12 @@ impl ZiskAsmContext { self.precompile_results } pub fn precompile_results_keccak(&self) -> bool { - self.precompile_results() + //self.precompile_results() + false } pub fn precompile_results_sha256(&self) -> bool { - self.precompile_results() + //self.precompile_results() + false } pub fn precompile_results_arith256(&self) -> bool { self.precompile_results() @@ -320,7 +322,8 @@ impl ZiskAsmContext { self.precompile_results() } pub fn precompile_results_blake2(&self) -> bool { - self.precompile_results() + //self.precompile_results() + false } pub fn call_wait_for_prec_avail(&self) -> bool { self.precompile_results() diff --git a/ziskos/entrypoint/src/hints/blake2b.rs b/ziskos/entrypoint/src/hints/blake2b.rs index a9c4e66b1..30fdb07d9 100644 --- a/ziskos/entrypoint/src/hints/blake2b.rs +++ b/ziskos/entrypoint/src/hints/blake2b.rs @@ -1,38 +1,9 @@ -use crate::hints::{macros::register_hint_meta, HINT_BUFFER}; -use zisk_common::HINT_BLAKE2B_COMPRESS; - #[no_mangle] pub unsafe extern "C" fn hint_blake2b_compress( - rounds: u32, - state: *mut u64, - message: *const u64, - offset: *const u64, - final_block: u8, + _rounds: u32, + _state: *mut u64, + _message: *const u64, + _offset: *const u64, + _final_block: u8, ) { - if !HINT_BUFFER.is_enabled() { - return; - } - - #[cfg(zisk_hints_single_thread)] - if !check_main_thread() { - return; - } - - let total_len = 8 + 64 + 128 + 16 + 8; // rounds + state + message + offset + final_block - - let mut w = HINT_BUFFER.begin_hint(HINT_BLAKE2B_COMPRESS, total_len, false); - - let rounds_bytes: [u8; 8] = (rounds as u64).to_le_bytes(); - w.write_hint_data_slice(&rounds_bytes); - - w.write_hint_data_ptr(state as *const u8, 64); - w.write_hint_data_ptr(message as *const u8, 128); - w.write_hint_data_ptr(offset as *const u8, 16); - - let final_block_bytes: [u8; 8] = (final_block as u64).to_le_bytes(); - w.write_hint_data_slice(&final_block_bytes); - - w.commit(); } - -register_hint_meta!(blake2b_compress, HINT_BLAKE2B_COMPRESS); diff --git a/ziskos/entrypoint/src/hints/custom.rs b/ziskos/entrypoint/src/hints/custom.rs index 7c16bf735..01e1f6032 100644 --- a/ziskos/entrypoint/src/hints/custom.rs +++ b/ziskos/entrypoint/src/hints/custom.rs @@ -18,12 +18,12 @@ pub unsafe extern "C" fn hint_custom( let mut w = HINT_BUFFER.begin_hint(hint_id, data_len, is_result != 0); - w.write_hint_data_ptr(data_ptr, data_len); + w.write_data_ptr(data_ptr, data_len); let pad = (8 - (data_len & 7)) & 7; if pad > 0 { const ZERO_PAD: [u8; 8] = [0; 8]; - w.write_hint_data_slice(&ZERO_PAD[..pad]); + w.write_data_slice(&ZERO_PAD[..pad]); } w.commit(); diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index 7097012ac..a2aaf3160 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -1,42 +1,46 @@ use bytes::{Bytes, BytesMut}; use std::io::{self, Write}; use std::sync::{Arc, Condvar, Mutex, MutexGuard}; -use zisk_common::{CTRL_END, CTRL_START}; +use zisk_common::{CTRL_END, CTRL_START, HINT_INPUT}; pub const DEFAULT_BUFFER_LEN: usize = 1 << 20; // 1 MiB // TODO: Set MAX_WRITE_LEN based on writer type (file or socket) pub const MAX_WRITER_LEN: usize = 128 * 1024; // 128KB is the max write size for Unix sockets pub const WRITE_BUFFER_FLUSH_LEN: usize = 64 * 1024; // Flush writer buffer once it exceeds 64KB +const MAX_INPUT_DATA_CHUNK: usize = 128 * 1024 - 8; // Max input data chunk size is 128KB minus 8 bytes for the header (length) pub const HEADER_LEN: usize = 8; pub struct HintBuffer { - inner: Mutex, + precompiles: Mutex, + input_data: Mutex, not_empty: Condvar, + closed: Mutex, + paused: Mutex, } struct HintBufferInner { buf: BytesMut, commit_pos: usize, - closed: bool, - paused: bool, - // counter: u64, } -pub struct HintWrite<'a> { +pub struct WriteBuffer<'a> { hb: &'a HintBuffer, g: MutexGuard<'a, HintBufferInner>, } pub fn build_hint_buffer() -> Arc { Arc::new(HintBuffer { - inner: Mutex::new(HintBufferInner { + precompiles: Mutex::new(HintBufferInner { + buf: BytesMut::with_capacity(DEFAULT_BUFFER_LEN), + commit_pos: 0, + }), + input_data: Mutex::new(HintBufferInner { buf: BytesMut::with_capacity(DEFAULT_BUFFER_LEN), commit_pos: 0, - closed: true, - paused: false, - // counter: 0, }), not_empty: Condvar::new(), + closed: Mutex::new(true), + paused: Mutex::new(false), }) } @@ -54,53 +58,55 @@ impl HintBufferInner { impl HintBuffer { pub fn close(&self) { - let mut g = self.inner.lock().unwrap(); - g.closed = true; + *self.closed.lock().unwrap() = true; self.not_empty.notify_all(); } pub fn reset(&self) { - let mut g = self.inner.lock().unwrap(); + let mut g = self.precompiles.lock().unwrap(); g.buf.clear(); g.commit_pos = 0; - g.closed = false; - g.paused = false; - // g.counter = 0; + let mut i = self.input_data.lock().unwrap(); + i.buf.clear(); + i.commit_pos = 0; + + *self.closed.lock().unwrap() = false; + *self.paused.lock().unwrap() = false; self.not_empty.notify_all(); } #[inline(always)] pub fn pause(&self) { - self.inner.lock().unwrap().paused = true; + *self.paused.lock().unwrap() = true; } #[inline(always)] pub fn resume(&self) { - self.inner.lock().unwrap().paused = false; + *self.paused.lock().unwrap() = false; } #[inline(always)] pub fn is_paused(&self) -> bool { - let g = self.inner.lock().unwrap(); - g.paused + *self.paused.lock().unwrap() } #[inline(always)] pub fn is_enabled(&self) -> bool { - let g = self.inner.lock().unwrap(); - !g.paused && !g.closed + let paused = *self.paused.lock().unwrap(); + let closed = *self.closed.lock().unwrap(); + !paused && !closed } #[inline(always)] - pub fn begin_hint(&self, hint_id: u32, len: usize, is_result: bool) -> HintWrite<'_> { + pub fn begin_hint(&self, hint_id: u32, len: usize, is_result: bool) -> WriteBuffer<'_> { let header = ((((if is_result { 0x8000_0000u64 } else { 0 }) | hint_id as u64) << 32) | (len as u64)) .to_le_bytes(); - let mut g = self.inner.lock().unwrap(); + let mut g = self.precompiles.lock().unwrap(); g.write_bytes(&header); - HintWrite { hb: self, g } + WriteBuffer { hb: self, g } } #[inline(always)] @@ -115,6 +121,11 @@ impl HintBuffer { w.commit(); } + #[inline(always)] + pub fn begin_input_data(&self) -> WriteBuffer<'_> { + WriteBuffer { hb: self, g: self.input_data.lock().unwrap() } + } + pub fn drain_to_writer( &self, writer: &mut W, @@ -158,22 +169,39 @@ impl HintBuffer { 'drain: loop { // Get chunk of hints to write from HintBuffer (under lock) let chunk: Bytes = { - let mut g = self.inner.lock().unwrap(); + let mut g = self.precompiles.lock().unwrap(); + let mut i = self.input_data.lock().unwrap(); + let closed = *self.closed.lock().unwrap(); - // Wait until there's data to write or buffer is closed - while g.commit_pos == 0 && !g.closed { + if g.commit_pos == 0 && i.commit_pos == 0 && !closed { + // Wait until there's data to write or buffer is closed g = self.not_empty.wait(g).unwrap(); } // If buffer is empty and closed, we're done, we can exit the drain loop - if g.commit_pos == 0 && g.closed { + if g.commit_pos == 0 && i.commit_pos == 0 && closed { break 'drain; } // Take the committed chunk of hints to write - let n = g.commit_pos; - g.commit_pos = 0; - g.buf.split_to(n).freeze() + if g.commit_pos > 0 { + let n = g.commit_pos; + g.commit_pos = 0; + g.buf.split_to(n).freeze() + } else { + let n = i.commit_pos.min(MAX_INPUT_DATA_CHUNK); + i.commit_pos = i.commit_pos.saturating_sub(n); + + let input_chunk = i.buf.split_to(n); + + let hint_input_header: [u8; 8] = + (((HINT_INPUT as u64) << 32) | (input_chunk.len() as u64)).to_le_bytes(); + + let mut chunk = BytesMut::with_capacity(HEADER_LEN + input_chunk.len()); + chunk.extend_from_slice(&hint_input_header); + chunk.unsplit(input_chunk); + chunk.freeze() + } }; // Write hints from chunk without holding the lock @@ -251,19 +279,19 @@ impl HintBuffer { } } -impl<'a> HintWrite<'a> { +impl<'a> WriteBuffer<'a> { #[inline(always)] - pub fn write_hint_data_ptr(&mut self, data: *const u8, len: usize) { + pub fn write_data_ptr(&mut self, data: *const u8, len: usize) { if len == 0 { return; } - debug_assert!(!data.is_null(), "write_hint_data_ptr called with null data pointer"); + debug_assert!(!data.is_null(), "write_data_ptr called with null data pointer"); let payload = unsafe { std::slice::from_raw_parts(data, len) }; self.g.write_bytes(payload); } #[inline(always)] - pub fn write_hint_data_slice(&mut self, payload: &[u8]) { + pub fn write_data_slice(&mut self, payload: &[u8]) { if payload.is_empty() { return; } diff --git a/ziskos/entrypoint/src/hints/input_data.rs b/ziskos/entrypoint/src/hints/input_data.rs index 626d52037..2d6642981 100644 --- a/ziskos/entrypoint/src/hints/input_data.rs +++ b/ziskos/entrypoint/src/hints/input_data.rs @@ -1,8 +1,9 @@ use crate::hints::macros::define_hint_ptr; -use zisk_common::HINT_INPUT_DATA; +use zisk_common::HINT_INPUT; #[no_mangle] pub unsafe extern "C" fn hint_input_data(input_data_ptr: *const u8, input_data_len: usize) { + println!("hint_input_data (input_data_ptr: {:p}, input_data_len: {})", input_data_ptr, input_data_len); if !crate::hints::HINT_BUFFER.is_enabled() { return; } @@ -13,16 +14,16 @@ pub unsafe extern "C" fn hint_input_data(input_data_ptr: *const u8, input_data_l } let pad = (8 - (input_data_len & 7)) & 7; - let mut w = crate::hints::HINT_BUFFER.begin_hint(HINT_INPUT_DATA, 8 + input_data_len + pad, false); + let mut w = crate::hints::HINT_BUFFER.begin_input_data(); // Write the length of the input data as the first 8 bytes of the hint data, // followed by the input data itself, and then pad with zeros if necessary let input_data_len_bytes: [u8; 8] = (input_data_len as u64).to_le_bytes(); - w.write_hint_data_slice(&input_data_len_bytes); - w.write_hint_data_ptr(input_data_ptr, input_data_len); + w.write_data_slice(&input_data_len_bytes); + w.write_data_ptr(input_data_ptr, input_data_len); if pad > 0 { const ZERO_PAD: [u8; 8] = [0; 8]; - w.write_hint_data_slice(&ZERO_PAD[..pad]); + w.write_data_slice(&ZERO_PAD[..pad]); } w.commit(); } @@ -30,5 +31,5 @@ pub unsafe extern "C" fn hint_input_data(input_data_ptr: *const u8, input_data_l #[cfg(zisk_hints_metrics)] #[ctor::ctor] fn input_data_register_meta() { - crate::hints::metrics::register_hint(HINT_INPUT_DATA, stringify!(input_data).to_string()); + crate::hints::metrics::register_hint(HINT_INPUT, stringify!(input_data).to_string()); } diff --git a/ziskos/entrypoint/src/hints/keccak256.rs b/ziskos/entrypoint/src/hints/keccak256.rs index ebb388ed1..1e44b0ed5 100644 --- a/ziskos/entrypoint/src/hints/keccak256.rs +++ b/ziskos/entrypoint/src/hints/keccak256.rs @@ -1,10 +1,3 @@ -use crate::hints::macros::define_hint_ptr; -use zisk_common::HINT_KECCAK256; - -define_hint_ptr! { - keccak256 => { - hint_id: HINT_KECCAK256, - param: input, - is_result: false, - } +#[no_mangle] +pub unsafe extern "C" fn hint_keccak256(_input_ptr: *const u8, _input_len: usize) { } diff --git a/ziskos/entrypoint/src/hints/macros.rs b/ziskos/entrypoint/src/hints/macros.rs index abf385a5a..97c6f63b8 100644 --- a/ziskos/entrypoint/src/hints/macros.rs +++ b/ziskos/entrypoint/src/hints/macros.rs @@ -28,7 +28,7 @@ macro_rules! define_hint { ); $( - w.write_hint_data_ptr($arg, $len); + w.write_data_ptr($arg, $len); )+ w.commit(); @@ -66,9 +66,9 @@ macro_rules! define_hint_pairs { ); let num_pairs_bytes: [u8; 8] = (num_pairs as u64).to_le_bytes(); - w.write_hint_data_slice(&num_pairs_bytes); + w.write_data_slice(&num_pairs_bytes); - w.write_hint_data_ptr(pairs, num_pairs * ($pair_len as usize)); + w.write_data_ptr(pairs, num_pairs * ($pair_len as usize)); w.commit(); } @@ -104,11 +104,11 @@ macro_rules! define_hint_ptr { $is_result, ); - w.write_hint_data_ptr([<$arg _ptr>], [<$arg _len>]); + w.write_data_ptr([<$arg _ptr>], [<$arg _len>]); if pad > 0 { const ZERO_PAD: [u8; 8] = [0; 8]; - w.write_hint_data_slice(&ZERO_PAD[..pad]); + w.write_data_slice(&ZERO_PAD[..pad]); } w.commit(); @@ -152,15 +152,15 @@ macro_rules! define_hint_ptr { $( { let len_bytes: [u8; 8] = ([<$arg _len>] as u64).to_le_bytes(); - w.write_hint_data_slice(&len_bytes); + w.write_data_slice(&len_bytes); - w.write_hint_data_ptr([<$arg _ptr>], [<$arg _len>]); + w.write_data_ptr([<$arg _ptr>], [<$arg _len>]); } )+ if pad > 0 { const ZERO_PAD: [u8; 8] = [0; 8]; - w.write_hint_data_slice(&ZERO_PAD[..pad]); + w.write_data_slice(&ZERO_PAD[..pad]); } w.commit(); diff --git a/ziskos/entrypoint/src/hints/sha256f.rs b/ziskos/entrypoint/src/hints/sha256f.rs index fd0708394..3195caee9 100644 --- a/ziskos/entrypoint/src/hints/sha256f.rs +++ b/ziskos/entrypoint/src/hints/sha256f.rs @@ -1,10 +1,3 @@ -use crate::hints::macros::define_hint_ptr; -use zisk_common::HINT_SHA256; - -define_hint_ptr! { - sha256 => { - hint_id: HINT_SHA256, - param: f, - is_result: false, - } +#[no_mangle] +pub unsafe extern "C" fn hint_sha256(_f_ptr: *const u8, _f_len: usize) { } diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 8d5971aa2..796253e5d 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -31,7 +31,7 @@ extern "C" { #[cfg(all(not(all(target_os = "zkvm", target_vendor = "zisk")), zisk_hints_debug))] extern "C" { - fn hint_log_c(msg: *const c_char); + fn hint_log_c(msg: *const std::os::raw::c_char); } #[cfg(zisk_hints_debug)] @@ -124,7 +124,7 @@ pub(crate) fn read_input() -> Vec { { let start_bytes = &vec[..vec.len().min(64)]; let ellipsis = if vec.len() > 64 { "..." } else { "" }; - hint_log_c(format!( + hint_log(format!( "hint_input_data (input_data: {:x?}{} , input_data_len: {}", start_bytes, ellipsis, @@ -153,14 +153,14 @@ pub(crate) fn read_input() -> Vec { { let start_bytes = &buffer[..buffer.len().min(64)]; let ellipsis = if buffer.len() > 64 { "..." } else { "" }; - hint_log_c(format!( + hint_log(format!( "hint_input_data (input_data: {:x?}{} , input_data_len: {}", start_bytes, ellipsis, buffer.len() )); } - + buffer } From 1e6ef4fe0c2407e36805ef131b97e3a998a91310 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 5 Mar 2026 23:44:51 +0000 Subject: [PATCH 713/782] Remove debug print statement from hint_input_data function --- ziskos/entrypoint/src/hints/input_data.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ziskos/entrypoint/src/hints/input_data.rs b/ziskos/entrypoint/src/hints/input_data.rs index 2d6642981..574e64e65 100644 --- a/ziskos/entrypoint/src/hints/input_data.rs +++ b/ziskos/entrypoint/src/hints/input_data.rs @@ -3,7 +3,6 @@ use zisk_common::HINT_INPUT; #[no_mangle] pub unsafe extern "C" fn hint_input_data(input_data_ptr: *const u8, input_data_len: usize) { - println!("hint_input_data (input_data_ptr: {:p}, input_data_len: {})", input_data_ptr, input_data_len); if !crate::hints::HINT_BUFFER.is_enabled() { return; } From 17a2bb4a3254947e8730197a93c6e1eda0fdf212 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:37:23 +0000 Subject: [PATCH 714/782] prepare code to allow input hints --- distributed/crates/worker/src/worker.rs | 10 +- emulator-asm/asm-runner/src/asm_mo_runner.rs | 8 +- emulator-asm/asm-runner/src/asm_mt_runner.rs | 6 +- emulator-asm/asm-runner/src/asm_rh_runner.rs | 8 +- emulator-asm/asm-runner/src/control_shmem.rs | 52 +++++++++ emulator-asm/asm-runner/src/hints_shmem.rs | 39 ++++--- emulator-asm/asm-runner/src/inputs_shmem.rs | 95 ++++++++++++++++ emulator-asm/asm-runner/src/lib.rs | 8 ++ emulator-asm/asm-runner/src/shmem_utils.rs | 28 +---- executor/src/asm_resources.rs | 106 ++++++++++++------ executor/src/emu_asm.rs | 43 ++----- precompiles/hints/benches/hints_benchmarks.rs | 11 +- precompiles/hints/src/hints_processor.rs | 90 +++++++++++---- sdk/src/prover/asm.rs | 2 +- 14 files changed, 357 insertions(+), 149 deletions(-) create mode 100644 emulator-asm/asm-runner/src/control_shmem.rs create mode 100644 emulator-asm/asm-runner/src/inputs_shmem.rs diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 19186180a..268eae8c5 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use asm_runner::HintsShmem; +use asm_runner::{ControlShmem, HintsShmem, InputsShmemWriter}; use cargo_zisk::commands::get_proving_key; use precompiles_hints::HintsProcessor; use proofman::{AggProofs, AggProofsRegister, ContributionsInfo}; @@ -694,11 +694,15 @@ impl Worker { let local_rank = self.prover.local_rank(); let unlock_mapped_memory = self.prover_config.unlock_mapped_memory; + let control_writer = + Arc::new(ControlShmem::new(base_port, local_rank, unlock_mapped_memory)?); + // HintsShmem::new and HintsProcessor::build perform OS-level shared-memory operations; // run them on the blocking thread pool to avoid stalling Tokio workers. let processor = tokio::task::spawn_blocking(move || -> Result { - let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory)?; - HintsProcessor::builder(hints_shmem) + let hints_shmem = + HintsShmem::new(base_port, local_rank, unlock_mapped_memory, control_writer)?; + HintsProcessor::builder(hints_shmem, None::) .build() .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e)) }) diff --git a/emulator-asm/asm-runner/src/asm_mo_runner.rs b/emulator-asm/asm-runner/src/asm_mo_runner.rs index 9049b1496..686c52529 100644 --- a/emulator-asm/asm-runner/src/asm_mo_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mo_runner.rs @@ -21,13 +21,13 @@ use anyhow::{Context, Result}; #[cfg(feature = "save_mem_plans")] use mem_common::save_plans; -pub struct MOOutputShmem { +pub struct MOShMemReader { pub output_shmem: AsmMultiSharedMemory, mem_planner: Option, handle_mo: Option>, } -impl MOOutputShmem { +impl MOShMemReader { pub fn new( local_rank: i32, base_port: Option, @@ -53,7 +53,7 @@ impl MOOutputShmem { } } -impl Drop for MOOutputShmem { +impl Drop for MOShMemReader { fn drop(&mut self) { if let Some(handle_mo) = self.handle_mo.take() { match handle_mo.join() { @@ -81,7 +81,7 @@ impl AsmRunnerMO { #[allow(clippy::too_many_arguments)] pub fn run( - preloaded: &mut MOOutputShmem, + preloaded: &mut MOShMemReader, max_steps: u64, chunk_size: u64, world_rank: i32, diff --git a/emulator-asm/asm-runner/src/asm_mt_runner.rs b/emulator-asm/asm-runner/src/asm_mt_runner.rs index 445b3b683..9244f2eef 100644 --- a/emulator-asm/asm-runner/src/asm_mt_runner.rs +++ b/emulator-asm/asm-runner/src/asm_mt_runner.rs @@ -18,11 +18,11 @@ use crate::{ use anyhow::{Context, Result}; -pub struct MTOutputShmem { +pub struct MTShMemReader { pub output_shmem: AsmMultiSharedMemory, } -impl MTOutputShmem { +impl MTShMemReader { pub fn new( local_rank: i32, base_port: Option, @@ -56,7 +56,7 @@ impl AsmRunnerMT { #[allow(clippy::too_many_arguments)] pub fn run_and_count)>( - preloaded: &mut MTOutputShmem, + preloaded: &mut MTShMemReader, max_steps: u64, chunk_size: u64, mut on_chunk: F, diff --git a/emulator-asm/asm-runner/src/asm_rh_runner.rs b/emulator-asm/asm-runner/src/asm_rh_runner.rs index 4d3f3022a..acb47bd95 100644 --- a/emulator-asm/asm-runner/src/asm_rh_runner.rs +++ b/emulator-asm/asm-runner/src/asm_rh_runner.rs @@ -9,11 +9,11 @@ use zisk_common::{stats_begin, stats_end, ExecutorStatsHandle}; use anyhow::{Context, Result}; -pub struct RHOutputShmem { +pub struct RHShMemReader { pub output_shmem: AsmSharedMemory, } -impl RHOutputShmem { +impl RHShMemReader { pub fn new( local_rank: i32, base_port: Option, @@ -48,7 +48,7 @@ impl AsmRunnerRH { } pub fn run( - asm_shared_memory: &mut Option, + asm_shared_memory: &mut Option, max_steps: u64, world_rank: i32, local_rank: i32, @@ -91,7 +91,7 @@ impl AsmRunnerRH { if asm_shared_memory.is_none() { *asm_shared_memory = - Some(RHOutputShmem::new(local_rank, base_port, unlock_mapped_memory)?); + Some(RHShMemReader::new(local_rank, base_port, unlock_mapped_memory)?); } let asm_rowh_output = diff --git a/emulator-asm/asm-runner/src/control_shmem.rs b/emulator-asm/asm-runner/src/control_shmem.rs new file mode 100644 index 000000000..6affbe016 --- /dev/null +++ b/emulator-asm/asm-runner/src/control_shmem.rs @@ -0,0 +1,52 @@ +use crate::{shmem_control_writer_name, AsmServices, SharedMemoryWriter}; + +use anyhow::Result; + +pub struct ControlShmem { + writer: SharedMemoryWriter, +} + +#[derive(Copy, Clone)] +pub enum ControlShmemOffsets { + PrecompilesSize = 0, + ShutdownFlag = 8, + InputsSize = 16, +} + +impl ControlShmem { + pub const CONTROL_WRITER_SIZE: u64 = 0x1000; // 4KB + + pub fn new( + base_port: Option, + local_rank: i32, + unlock_mapped_memory: bool, + ) -> Result { + let port = AsmServices::port_base_for(base_port, local_rank); + Ok(Self { + writer: SharedMemoryWriter::new( + &shmem_control_writer_name(port, local_rank), + Self::CONTROL_WRITER_SIZE as usize, + unlock_mapped_memory, + )?, + }) + } + + pub fn read_u64_at(&self, offset: ControlShmemOffsets) -> u64 { + self.writer.read_u64_at(offset as usize) + } + + pub fn write_u64_at(&self, offset: ControlShmemOffsets, size: u64) { + self.writer.write_u64_at(offset as usize, size); + } + + pub fn increment_u64_at(&self, offset: ControlShmemOffsets, size: usize) { + let current_size = self.read_u64_at(offset); + self.write_u64_at(offset, current_size + size as u64); + } + + pub fn reset(&self) { + self.write_u64_at(ControlShmemOffsets::PrecompilesSize, 0); + self.write_u64_at(ControlShmemOffsets::ShutdownFlag, 0); + self.write_u64_at(ControlShmemOffsets::InputsSize, 0); + } +} diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index 5a78639d0..df2139e39 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -4,14 +4,18 @@ //! using SharedMemoryWriter instances. use crate::{ - sem_available_name, sem_read_name, shmem_control_reader_name, shmem_control_writer_name, - shmem_precompile_name, AsmService, AsmServices, SharedMemoryReader, SharedMemoryWriter, + sem_available_name, sem_read_name, shmem_control_reader_name, shmem_precompile_name, + AsmService, AsmServices, ControlShmem, ControlShmemOffsets, SharedMemoryReader, + SharedMemoryWriter, }; use anyhow::Result; use named_sem::NamedSemaphore; use std::{ cell::RefCell, - sync::atomic::{fence, AtomicBool, Ordering}, + sync::{ + atomic::{fence, AtomicBool, Ordering}, + Arc, + }, }; use tracing::debug; use zisk_common::io::StreamSink; @@ -46,7 +50,7 @@ struct SeparateResources { /// Unified resources shared across all asm services struct UnifiedResources { /// Control shared memory writer (single write_pos) - control_writer: SharedMemoryWriter, + control_writer: Arc, /// Data shared memory writer (single data buffer) data_writer: SharedMemoryWriter, } @@ -81,6 +85,7 @@ impl HintsShmem { base_port: Option, local_rank: i32, unlock_mapped_memory: bool, + control_writer: Arc, ) -> Result { // Use the first service's port for shared resources naming let first_service = &AsmServices::SERVICES[0]; @@ -91,9 +96,13 @@ impl HintsShmem { }; // Create unified resources (single data buffer and control writer) - let unified = - Self::create_unified_resources(shared_port, local_rank, unlock_mapped_memory)?; - unified.control_writer.write_u64_at(0, 0); + let unified = Self::create_unified_resources( + shared_port, + local_rank, + unlock_mapped_memory, + control_writer, + )?; + unified.control_writer.reset(); // Create separate resources let separate_names: Vec = AsmServices::SERVICES @@ -119,17 +128,13 @@ impl HintsShmem { port: u16, local_rank: i32, unlock_mapped_memory: bool, + control_writer: Arc, ) -> Result { debug!("Initializing unified resources for precompile hints"); - let control_name = shmem_control_writer_name(port, local_rank); let data_name = shmem_precompile_name(port, local_rank); Ok(UnifiedResources { - control_writer: SharedMemoryWriter::new( - &control_name, - Self::CONTROL_PRECOMPILE_SIZE as usize, - unlock_mapped_memory, - )?, + control_writer, data_writer: SharedMemoryWriter::new( &data_name, Self::MAX_PRECOMPILE_SIZE as usize, @@ -213,7 +218,7 @@ impl StreamSink for HintsShmem { }; // Read current write position once - let write_pos = unified.control_writer.read_u64_at(0); + let write_pos = unified.control_writer.read_u64_at(ControlShmemOffsets::PrecompilesSize); // Flow control: wait until all consumers have advanced enough // We need to wait for the slowest consumer (minimum read position) @@ -263,7 +268,9 @@ impl StreamSink for HintsShmem { fence(Ordering::Release); // Update write position ONCE in control memory - unified.control_writer.write_u64_at(0, write_pos + data_size); + unified + .control_writer + .write_u64_at(ControlShmemOffsets::PrecompilesSize, write_pos + data_size); fence(Ordering::Release); @@ -278,7 +285,7 @@ impl StreamSink for HintsShmem { fn reset(&self) { // Reset control writer and data writer to initial state for next stream let mut unified = self.unified.borrow_mut(); - unified.control_writer.write_u64_at(0, 0); + unified.control_writer.reset(); unified.data_writer.reset(); // Drain stale semaphore signals from previous execution diff --git a/emulator-asm/asm-runner/src/inputs_shmem.rs b/emulator-asm/asm-runner/src/inputs_shmem.rs new file mode 100644 index 000000000..8c5d27528 --- /dev/null +++ b/emulator-asm/asm-runner/src/inputs_shmem.rs @@ -0,0 +1,95 @@ +use std::sync::{Arc, Mutex}; + +use named_sem::NamedSemaphore; +use zisk_common::{io::StreamSink, reinterpret_vec}; +use zisk_core::MAX_INPUT_SIZE; + +use crate::{ + shmem_input_avail_name, shmem_input_name, AsmInputHeader, AsmServices, ControlShmem, + ControlShmemOffsets, SharedMemoryWriter, +}; + +use anyhow::Result; + +pub struct InputsShmemWriter { + writer: SharedMemoryWriter, + control_writer: Arc, + sem_avail: Mutex, +} + +unsafe impl Send for InputsShmemWriter {} +unsafe impl Sync for InputsShmemWriter {} + +impl InputsShmemWriter { + pub fn new( + base_port: Option, + local_rank: i32, + unlock_mapped_memory: bool, + control_writer: Arc, + ) -> Result { + let port = AsmServices::port_base_for(base_port, local_rank); + + let writer = SharedMemoryWriter::new( + &shmem_input_name(port, local_rank), + MAX_INPUT_SIZE as usize, + unlock_mapped_memory, + )?; + + let sem_avail = Mutex::new(NamedSemaphore::create( + shmem_input_avail_name(port, local_rank).clone(), + 0, + )?); + + Ok(Self { writer, control_writer, sem_avail }) + } + + pub fn write_input(&self, inputs: &[u8]) -> Result<()> { + const HEADER_SIZE: usize = size_of::(); + + let shmem_input_size = (HEADER_SIZE + inputs.len() + 7) & !7; + + let mut full_input = Vec::with_capacity(shmem_input_size); + + let header = AsmInputHeader { zero: 0, input_data_size: 0 }; + full_input.extend_from_slice(&header.to_bytes()); + full_input.extend_from_slice(inputs); + while full_input.len() < shmem_input_size { + full_input.push(0); + } + + self.writer.write_input(&full_input)?; + + self.writer.write_u64_at(8, inputs.len() as u64); + + Ok(()) + } + + pub fn write_input2(&self, inputs: &[u8]) -> Result<()> { + const HEADER_SIZE: usize = size_of::(); + + let shmem_input_size = (HEADER_SIZE + inputs.len() + 7) & !7; + + let mut full_input = Vec::with_capacity(shmem_input_size); + + let header = AsmInputHeader { zero: 0, input_data_size: 0 }; + full_input.extend_from_slice(&header.to_bytes()); + full_input.extend_from_slice(inputs); + while full_input.len() < shmem_input_size { + full_input.push(0); + } + + // self.writer.write_input(&full_input)?; + + self.control_writer.increment_u64_at(ControlShmemOffsets::InputsSize, inputs.len()); + + self.sem_avail.lock().unwrap().post()?; + + Ok(()) + } +} + +impl StreamSink for InputsShmemWriter { + fn submit(&self, hints: Vec) -> anyhow::Result<()> { + self.write_input2(&reinterpret_vec(hints)?) + } +} diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 9e743c996..8650975b9 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -17,11 +17,13 @@ mod asm_rh_runner; mod asm_rh_runner_stub; mod asm_runner; mod asm_services; +mod control_shmem; mod hints_file; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod hints_shmem; #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] mod hints_shmem_stub; +mod inputs_shmem; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod multi_shmem; mod shmem_reader; @@ -45,11 +47,13 @@ pub use asm_rh_runner::*; pub use asm_rh_runner_stub::*; pub use asm_runner::*; pub use asm_services::*; +pub use control_shmem::*; pub use hints_file::*; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub use hints_shmem::*; #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] pub use hints_shmem_stub::*; +pub use inputs_shmem::*; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub use multi_shmem::*; pub use shmem_reader::*; @@ -102,6 +106,10 @@ pub fn shmem_input_name(port: u16, local_rank: i32) -> String { build_shmem_name2(port, local_rank, "input") } +pub fn shmem_input_avail_name(port: u16, local_rank: i32) -> String { + build_shmem_name2(port, local_rank, "input_avail") +} + /// Shared memory name for precompile hints data pub fn shmem_precompile_name(port: u16, local_rank: i32) -> String { build_shmem_name2(port, local_rank, "precompile") diff --git a/emulator-asm/asm-runner/src/shmem_utils.rs b/emulator-asm/asm-runner/src/shmem_utils.rs index 2e1855dd3..6f160bf83 100644 --- a/emulator-asm/asm-runner/src/shmem_utils.rs +++ b/emulator-asm/asm-runner/src/shmem_utils.rs @@ -12,11 +12,8 @@ use std::{ sync::atomic::{fence, Ordering}, }; use tracing::info; -use zisk_common::io::{ZiskIO, ZiskStdin}; -use crate::{AsmInputHeader, SharedMemoryWriter}; -use anyhow::anyhow; -use anyhow::Result; +use anyhow::{anyhow, Result}; pub enum AsmSharedMemoryMode { ReadOnly, @@ -279,7 +276,7 @@ impl AsmSharedMemory { } pub fn open_shmem(name: &str, flags: i32, mode: u32) -> Result { - let c_name = CString::new(name).expect("CString::new failed"); + let c_name = CString::new(name)?; let fd = unsafe { shm_open(c_name.as_ptr(), flags, mode) }; if fd == -1 { @@ -333,24 +330,3 @@ pub unsafe fn unmap(ptr: *mut c_void, size: usize) { tracing::error!("munmap failed: {:?}", io::Error::last_os_error()); } } - -pub fn write_input(stdin: &ZiskStdin, shmem_input_writer: &SharedMemoryWriter) { - const HEADER_SIZE: usize = size_of::(); - - let inputs = stdin.read_bytes(); - - let shmem_input_size = (HEADER_SIZE + inputs.len() + 7) & !7; - - let mut full_input = Vec::with_capacity(shmem_input_size); - - let header = AsmInputHeader { zero: 0, input_data_size: 0 }; - full_input.extend_from_slice(&header.to_bytes()); - full_input.extend_from_slice(&inputs); - while full_input.len() < shmem_input_size { - full_input.push(0); - } - - shmem_input_writer.write_input(&full_input).expect("Failed to write input to shared memory"); - - shmem_input_writer.write_u64_at(8, inputs.len() as u64); -} diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index ea8a9b45d..27126a461 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -1,17 +1,21 @@ use std::sync::{Arc, Mutex}; use anyhow::Result; +use asm_runner::ControlShmem; use asm_runner::HintsFile; use asm_runner::HintsShmem; +use asm_runner::InputsShmemWriter; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] -use asm_runner::{MOOutputShmem, MTOutputShmem, RHOutputShmem, SharedMemoryWriter}; +use asm_runner::{MOShMemReader, MTShMemReader, RHShMemReader}; use precompiles_hints::HintsProcessor; use std::sync::atomic::{AtomicBool, Ordering}; +use zisk_common::io::ZiskIO; +use zisk_common::io::ZiskStdin; use zisk_common::io::{StreamSource, ZiskStream}; -/// Encapsulates assembly-related resources including shared memory and hints stream. +/// Configuration for assembly resources. #[derive(Clone)] -pub struct AsmResources { +pub struct AsmResourcesConfig { /// Optional baseline port to communicate with assembly microservices. pub base_port: Option, @@ -20,16 +24,31 @@ pub struct AsmResources { /// Map unlocked flag. pub unlock_mapped_memory: bool, +} + +impl std::fmt::Debug for AsmResourcesConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AsmResources") + .field("base_port", &self.base_port) + .field("local_rank", &self.local_rank) + .field("unlock_mapped_memory", &self.unlock_mapped_memory) + .finish_non_exhaustive() + } +} + +/// Encapsulates assembly-related resources including shared memory and hints stream. +#[derive(Clone)] +pub struct AsmResources { + config: AsmResourcesConfig, #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - pub asm_shmem_mt: Arc>, - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - pub asm_shmem_mo: Arc>, + pub mt_shmem_reader: Arc>, #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - pub asm_shmem_rh: Arc>>, - /// Shared memory writers for each assembly service. + pub mo_shmem_reader: Arc>, #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - pub shmem_input_writer: Arc>>, + pub rh_shmem_reader: Arc>>, + + pub inputs_shmem_writer: Arc, /// Pipeline for handling precompile hints. pub hints_stream: Option>>, @@ -40,9 +59,8 @@ pub struct AsmResources { impl std::fmt::Debug for AsmResources { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AsmResources") - .field("base_port", &self.base_port) - .field("local_rank", &self.local_rank) - .field("unlock_mapped_memory", &self.unlock_mapped_memory) + .field("config", &self.config) + .field("hints_stream_initialized", &self.hints_stream_initialized) .finish_non_exhaustive() } } @@ -54,37 +72,43 @@ impl AsmResources { unlock_mapped_memory: bool, verbose_mode: proofman_common::VerboseMode, with_hints: bool, - ) -> Self { + ) -> Result { #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - let asm_shmem_mt = MTOutputShmem::new(local_rank, base_port, unlock_mapped_memory) - .expect("asm_resources: Failed to create PreloadedMT"); + let asm_shmem_mt = MTShMemReader::new(local_rank, base_port, unlock_mapped_memory)?; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - let asm_shmem_mo = MOOutputShmem::new(local_rank, base_port, unlock_mapped_memory) - .expect("asm_resources: Failed to create PreloadedMO"); + let asm_shmem_mo = MOShMemReader::new(local_rank, base_port, unlock_mapped_memory)?; + + let control_writer = + Arc::new(ControlShmem::new(base_port, local_rank, unlock_mapped_memory)?); + + let config = AsmResourcesConfig { base_port, local_rank, unlock_mapped_memory }; + + let inputs_shmem_writer = Arc::new(InputsShmemWriter::new( + base_port, + local_rank, + unlock_mapped_memory, + control_writer.clone(), + )?); // Create hints pipeline with null hints stream initially. // Debug flag: true = HintsShmem (shared memory), false = HintsFile (file output) - const USE_SHARED_MEMORY_HINTS: bool = true; let hints_stream = if with_hints { let hints_processor = if USE_SHARED_MEMORY_HINTS { - let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory) - .expect("asm_resources: Failed to create HintsShmem"); + let hints_shmem = + HintsShmem::new(base_port, local_rank, unlock_mapped_memory, control_writer)?; - HintsProcessor::builder(hints_shmem) + HintsProcessor::builder2(hints_shmem, Some(inputs_shmem_writer.clone())) .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) - .build() - .expect("asm_resources: Failed to create PrecompileHintsProcessor") + .build()? } else { - let hints_file = HintsFile::new(format!("hints_results_{}.bin", local_rank)) - .expect("asm_resources: Failed to create HintsFile"); + let hints_file = HintsFile::new(format!("hints_results_{}.bin", local_rank))?; - HintsProcessor::builder(hints_file) + HintsProcessor::builder2(hints_file, Some(inputs_shmem_writer.clone())) .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) - .build() - .expect("asm_resources: Failed to create PrecompileHintsProcessor") + .build()? }; Some(Arc::new(Mutex::new(ZiskStream::new(hints_processor)))) @@ -92,21 +116,19 @@ impl AsmResources { None }; - Self { + Ok(Self { + config, hints_stream, hints_stream_initialized: Arc::new(AtomicBool::new(false)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_mt: Arc::new(Mutex::new(asm_shmem_mt)), + mt_shmem_reader: Arc::new(Mutex::new(asm_shmem_mt)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_mo: Arc::new(Mutex::new(asm_shmem_mo)), + mo_shmem_reader: Arc::new(Mutex::new(asm_shmem_mo)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - asm_shmem_rh: Arc::new(Mutex::new(None)), + rh_shmem_reader: Arc::new(Mutex::new(None)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - shmem_input_writer: Arc::new(Mutex::new(None)), - base_port, - local_rank, - unlock_mapped_memory, - } + inputs_shmem_writer, + }) } pub fn start_stream(&self) -> Result<()> { @@ -137,4 +159,14 @@ impl AsmResources { self.hints_stream_initialized.store(false, Ordering::SeqCst); } } + + pub fn config(&self) -> &AsmResourcesConfig { + &self.config + } + + pub fn write_input(&self, stdin: &ZiskStdin) -> Result<()> { + let inputs = stdin.read_bytes(); + + self.inputs_shmem_writer.write_input(&inputs) + } } diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index eac7b4f28..a97712479 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -8,10 +8,7 @@ use crate::AsmResources; use crate::{ DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, MAX_NUM_STEPS, }; -use asm_runner::{ - shmem_input_name, write_input, AsmRunnerMO, AsmRunnerMT, AsmRunnerRH, AsmService, AsmServices, - SharedMemoryWriter, -}; +use asm_runner::{AsmRunnerMO, AsmRunnerMT, AsmRunnerRH}; use data_bus::DataBusTrait; use fields::PrimeField64; use proofman_common::ProofCtx; @@ -20,7 +17,7 @@ use zisk_common::{ io::ZiskStdin, stats_begin, stats_end, AsmExecutionInfo, ChunkId, EmuTrace, ExecutorStatsHandle, StatsScope, }; -use zisk_core::{ZiskRom, MAX_INPUT_SIZE}; +use zisk_core::ZiskRom; use ziskemu::ZiskEmulator; pub struct EmulatorAsm { @@ -134,27 +131,11 @@ impl EmulatorAsm { stats_begin!(stats, &_exec_scope, _write_scope, "ASM_WRITE_INPUT", 0); - asm_resources.shmem_input_writer.lock().unwrap().get_or_insert_with(|| { - let port = - AsmServices::port_base_for(asm_resources.base_port, asm_resources.local_rank); - let shmem_input_name = shmem_input_name(port, asm_resources.local_rank); - tracing::debug!( - "Initializing SharedMemoryWriter for service {:?} at '{}'", - AsmService::MO, - shmem_input_name - ); - SharedMemoryWriter::new( - &shmem_input_name, - MAX_INPUT_SIZE as usize, - asm_resources.unlock_mapped_memory, - ) - .expect("Failed to create SharedMemoryWriter") - }); + let config = asm_resources.config(); - write_input( - &stdin.lock().unwrap(), - asm_resources.shmem_input_writer.lock().unwrap().as_ref().unwrap(), - ); + asm_resources + .write_input(&stdin.lock().unwrap()) + .expect("Failed to write input to shared memory"); stats_end!(stats, &_write_scope); @@ -165,8 +146,8 @@ impl EmulatorAsm { // Run the assembly Memory Operations (MO) runner thread let handle_mo = std::thread::spawn({ - let asm_shmem_mo = asm_resources.asm_shmem_mo.clone(); - let base_port = asm_resources.base_port; + let asm_shmem_mo = asm_resources.mo_shmem_reader.clone(); + let base_port = config.base_port; move || { AsmRunnerMO::run( &mut asm_shmem_mo.lock().unwrap(), @@ -187,8 +168,8 @@ impl EmulatorAsm { let _stats = stats.clone(); let handle_rh = (has_rom_sm).then(|| { - let asm_shmem_rh = asm_resources.asm_shmem_rh.clone(); - let base_port = asm_resources.base_port; + let asm_shmem_rh = asm_resources.rh_shmem_reader.clone(); + let base_port = config.base_port; let unlock_mapped_memory = self.unlock_mapped_memory; std::thread::spawn(move || { AsmRunnerRH::run( @@ -255,13 +236,13 @@ impl EmulatorAsm { let asm_resources_guard = self.asm_resources.lock().unwrap(); let asm_resources = asm_resources_guard.as_ref().expect("AsmResources not initialized"); let result = AsmRunnerMT::run_and_count( - &mut asm_resources.asm_shmem_mt.lock().unwrap(), + &mut asm_resources.mt_shmem_reader.lock().unwrap(), MAX_NUM_STEPS, self.chunk_size, on_chunk, self.world_rank, self.local_rank, - asm_resources.base_port, + asm_resources.config().base_port, stats.clone(), ) .expect("Error during ASM execution"); diff --git a/precompiles/hints/benches/hints_benchmarks.rs b/precompiles/hints/benches/hints_benchmarks.rs index 7681624cc..20485541a 100644 --- a/precompiles/hints/benches/hints_benchmarks.rs +++ b/precompiles/hints/benches/hints_benchmarks.rs @@ -48,7 +48,7 @@ fn parallel_speedup_benchmark(c: &mut Criterion) { let received_clone = received.clone(); let sink = BenchSink { received: received_clone }; - let p = HintsProcessor::builder(sink) + let p = HintsProcessor::builder(sink, None::) .num_threads(threads) .custom_hint(FAST_HINT, |data: &[u64]| -> Result> { thread::sleep(Duration::from_millis(1)); @@ -121,7 +121,7 @@ fn microsecond_hints_benchmark(c: &mut Criterion) { let received_clone = received.clone(); let sink = BenchSink { received: received_clone }; - let p = HintsProcessor::builder(sink) + let p = HintsProcessor::builder(sink, None::) .num_threads(16) .custom_hint(hint_code, move |data: &[u64]| -> Result> { thread::sleep(Duration::from_micros(micros as u64)); @@ -173,7 +173,7 @@ fn workload_patterns_benchmark(c: &mut Criterion) { let received_clone = received.clone(); let sink = BenchSink { received: received_clone }; - let p = HintsProcessor::builder(sink) + let p = HintsProcessor::builder(sink, None::) .num_threads(8) .custom_hint(VERY_FAST, |data: &[u64]| -> Result> { thread::sleep(Duration::from_micros(500)); @@ -241,7 +241,10 @@ fn noop_throughput_benchmark(c: &mut Criterion) { for &count in &hint_counts { group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, &num_hints| { b.iter(|| { - let p = HintsProcessor::builder(NullSink).num_threads(32).build().unwrap(); + let p = HintsProcessor::builder(NullSink, None::) + .num_threads(32) + .build() + .unwrap(); let mut data = Vec::with_capacity(num_hints * 2); for i in 0..num_hints { diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 2b56a43a3..00f772ba1 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -90,6 +90,7 @@ pub type CustomHintHandler = Arc Result> + Send + Syn /// Builder for configuring and constructing a [`HintsProcessor`]. pub struct HintsProcessorBuilder { hints_sink: Arc, + inputs_sink: Option>, num_threads: usize, enable_stats: bool, custom_handlers: HashMap, @@ -147,6 +148,7 @@ impl HintsProcessorBuilder { let state = Arc::new(HintProcessorState::new()); let hints_sink = self.hints_sink; + let inputs_sink = self.inputs_sink; // Spawn drainer thread let drainer_state = Arc::clone(&state); @@ -161,6 +163,7 @@ impl HintsProcessorBuilder { state, stats: if self.enable_stats { Some(Mutex::new(HashMap::new())) } else { None }, hints_sink, + inputs_sink, drainer_thread: ManuallyDrop::new(drainer_thread), custom_handlers: Arc::new(self.custom_handlers), stream_active: AtomicBool::new(false), @@ -188,9 +191,11 @@ pub struct HintsProcessor { stats: Option>>, /// The hints sink used to submit processed hints (kept for ownership). - #[allow(dead_code)] hints_sink: Arc, + /// The inputs sink used to submit processed input hints (if any). + inputs_sink: Option>, + /// Handle to the drainer thread (wrapped in ManuallyDrop to join in Drop) drainer_thread: ManuallyDrop>, @@ -225,9 +230,26 @@ impl HintsProcessor { /// .enable_stats(false) /// .build()?; /// ``` - pub fn builder(hints_sink: impl StreamSink) -> HintsProcessorBuilder { + pub fn builder( + hints_sink: impl StreamSink, + inputs_sink: Option, + ) -> HintsProcessorBuilder { HintsProcessorBuilder { hints_sink: Arc::new(hints_sink), + inputs_sink: inputs_sink.map(|s| Arc::new(s) as Arc), + num_threads: Self::DEFAULT_NUM_THREADS, + enable_stats: false, + custom_handlers: HashMap::new(), + } + } + + pub fn builder2( + hints_sink: impl StreamSink, + inputs_sink: Option>, + ) -> HintsProcessorBuilder { + HintsProcessorBuilder { + hints_sink: Arc::new(hints_sink), + inputs_sink: inputs_sink.map(|s| s as Arc), num_threads: Self::DEFAULT_NUM_THREADS, enable_stats: false, custom_handlers: HashMap::new(), @@ -382,7 +404,19 @@ impl HintsProcessor { _ => {} // Built-in data hint or custom hint; continue processing } - // Capture generation outside mutex - SeqCst provides sufficient ordering + // If the hint is an input hint, write it to the inputs sink instead of processing + if hint.hint_code == HintCode::BuiltIn(BuiltInHint::Input) { + self.inputs_sink + .as_ref() + .ok_or_else(|| { + anyhow::anyhow!("Received input hint but no inputs sink configured") + })? + .submit(hint.data)?; + // Continue to next hint without spawning worker + idx += length; + continue; + } + let generation = self.state.generation.load(Ordering::SeqCst); // Atomically reserve slot - use Relaxed for seq since mutex provides ordering @@ -668,9 +702,6 @@ impl HintsProcessor { data_len_bytes: usize, ) -> Result> { match hint { - // Input Hint Codes - BuiltInHint::Input => unimplemented!("Input hint unimplemented"), - // SHA256 Hint Codes BuiltInHint::Sha256 => sha256_hint(&data, data_len_bytes), @@ -708,6 +739,11 @@ impl HintsProcessor { // Blake2b Hint Codes BuiltInHint::Blake2bCompress => blake2b_compress_hint(&data), + + // Input Hint Codes + BuiltInHint::Input => unreachable!( + "Input hints should be handled separately and not dispatched to workers" + ), } } @@ -780,7 +816,7 @@ mod tests { const TEST_PASSTHROUGH_HINT: u32 = 0x8000_0000 | 0x7FFF_0000; fn processor() -> HintsProcessor { - HintsProcessor::builder(NullHints) + HintsProcessor::builder(NullHints, None::) .num_threads(2) .custom_hint(0x7FFF_0000, |data| { // This should never be called for pass-through hints @@ -1033,7 +1069,8 @@ mod tests { let received = Arc::new(Mutex::new(Vec::new())); let sink = RecordingSink { received: Arc::clone(&received) }; - let p = HintsProcessor::builder(sink).num_threads(2).build().unwrap(); + let p = + HintsProcessor::builder(sink, None::).num_threads(2).build().unwrap(); // Send some data (16 bytes = 2 u64s, 8 bytes = 1 u64) let data = vec![ @@ -1075,7 +1112,7 @@ mod tests { let should_fail = Arc::new(AtomicBool::new(false)); let sink = FailingSink { should_fail: Arc::clone(&should_fail) }; - let p = HintsProcessor::builder(sink).num_threads(2).build().unwrap(); + let p = HintsProcessor::builder(sink, None::).num_threads(2).build().unwrap(); // First batch succeeds (8 bytes = 1 u64) let data1 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x01]; @@ -1098,26 +1135,36 @@ mod tests { #[test] fn test_builder_configuration() { // Default builder - stats disabled - let p1 = HintsProcessor::builder(NullHints).build().unwrap(); + let p1 = HintsProcessor::builder(NullHints, None::).build().unwrap(); assert!(p1.stats.is_none()); // Explicitly disabled stats - let p2 = HintsProcessor::builder(NullHints).enable_stats(false).build().unwrap(); + let p2 = HintsProcessor::builder(NullHints, None::) + .enable_stats(false) + .build() + .unwrap(); assert!(p2.stats.is_none()); // Stats enabled - let p3 = HintsProcessor::builder(NullHints).enable_stats(true).build().unwrap(); + let p3 = HintsProcessor::builder(NullHints, None::) + .enable_stats(true) + .build() + .unwrap(); assert!(p3.stats.is_some()); // Custom threads - let p4 = HintsProcessor::builder(NullHints).num_threads(4).build().unwrap(); + let p4 = + HintsProcessor::builder(NullHints, None::).num_threads(4).build().unwrap(); let data = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x42]; assert!(p4.process_hints(&data, false).is_ok()); assert!(p4.wait_for_completion().is_ok()); // Chaining multiple options - let p5 = - HintsProcessor::builder(NullHints).num_threads(8).enable_stats(true).build().unwrap(); + let p5 = HintsProcessor::builder(NullHints, None::) + .num_threads(8) + .enable_stats(true) + .build() + .unwrap(); assert!(p5.stats.is_some()); } @@ -1126,7 +1173,8 @@ mod tests { fn test_stress_throughput() { use std::time::Instant; - let p = HintsProcessor::builder(NullHints).num_threads(32).build().unwrap(); + let p = + HintsProcessor::builder(NullHints, None::).num_threads(32).build().unwrap(); // Generate a large batch of hints const NUM_HINTS: usize = 100_000; @@ -1159,7 +1207,8 @@ mod tests { fn test_stress_concurrent_batches() { use std::time::Instant; - let p = HintsProcessor::builder(NullHints).num_threads(32).build().unwrap(); + let p = + HintsProcessor::builder(NullHints, None::).num_threads(32).build().unwrap(); const NUM_BATCHES: usize = 1_000; const HINTS_PER_BATCH: usize = 100; @@ -1198,7 +1247,8 @@ mod tests { fn test_stress_with_resets() { use std::time::Instant; - let p = HintsProcessor::builder(NullHints).num_threads(32).build().unwrap(); + let p = + HintsProcessor::builder(NullHints, None::).num_threads(32).build().unwrap(); const ITERATIONS: usize = 100; const HINTS_PER_ITER: usize = 1_000; @@ -1268,7 +1318,7 @@ mod tests { const SLOW_HINT: u32 = 0x7FFF_0001; // Delays 10ms const MED_HINT: u32 = 0x7FFF_0002; // Delays 5ms - let p = HintsProcessor::builder(sink) + let p = HintsProcessor::builder(sink, None::) .num_threads(8) .custom_hint(FAST_HINT, |data| { // No delay - returns immediately @@ -1636,7 +1686,7 @@ mod tests { const VARIABLE_HINT: u32 = 0x7FFF_0100; - let p = HintsProcessor::builder(sink) + let p = HintsProcessor::builder(sink, None::) .num_threads(16) .custom_hint(VARIABLE_HINT, |data| { // Pseudo-random delay based on hash of input value (0-15ms range) diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 67b3a7def..34b192b38 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -185,7 +185,7 @@ impl ProverEngine for AsmProver { unlock_mapped_memory, verbose_mode, elf.with_hints(), - ); + )?; self.n_setups.fetch_add(1, Ordering::SeqCst); From 40e2ae45301e49e0ff25f3237485520d8a254c14 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:37:58 +0000 Subject: [PATCH 715/782] remove expects --- executor/src/emu_asm.rs | 22 ++++++++++++---------- executor/src/emu_rust.rs | 12 +++++++----- executor/src/executor.rs | 19 +++++++++++-------- executor/src/lib.rs | 9 +++++---- executor/src/rom_executor.rs | 18 +++++++++++++----- 5 files changed, 48 insertions(+), 32 deletions(-) diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index a97712479..92373986a 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -20,6 +20,8 @@ use zisk_common::{ use zisk_core::ZiskRom; use ziskemu::ZiskEmulator; +use anyhow::Result; + pub struct EmulatorAsm { /// World rank for distributed execution. Default to 0 for single-node execution. world_rank: i32, @@ -111,20 +113,22 @@ impl EmulatorAsm { use_hints: bool, stats: &ExecutorStatsHandle, _caller_stats_scope: &StatsScope, - ) -> ( + ) -> Result<( Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, Option>, u64, - ) { + )> { let asm_resources_guard = self.asm_resources.lock().unwrap(); - let asm_resources = asm_resources_guard.as_ref().expect("AsmResources not initialized"); + let asm_resources = asm_resources_guard + .as_ref() + .ok_or_else(|| anyhow::anyhow!("AsmResources not initialized"))?; let has_hints_stream = asm_resources.is_hints_stream_initialized(); if use_hints && has_hints_stream { - asm_resources.start_stream().expect("Failed to start hints stream"); + asm_resources.start_stream()?; } stats_begin!(stats, _caller_stats_scope, _exec_scope, "EXECUTE_WITH_ASSEMBLY", 0); @@ -133,9 +137,7 @@ impl EmulatorAsm { let config = asm_resources.config(); - asm_resources - .write_input(&stdin.lock().unwrap()) - .expect("Failed to write input to shared memory"); + asm_resources.write_input(&stdin.lock().unwrap())?; stats_end!(stats, &_write_scope); @@ -192,7 +194,7 @@ impl EmulatorAsm { stats_end!(stats, &_exec_scope); - (min_traces, main_count, secn_count, Some(handle_mo), handle_rh, steps) + Ok((min_traces, main_count, secn_count, Some(handle_mo), handle_rh, steps)) } fn run_mt_assembly( @@ -298,14 +300,14 @@ impl crate::Emulator for EmulatorAsm { use_hints: bool, stats: &ExecutorStatsHandle, caller_stats_scope: &StatsScope, - ) -> ( + ) -> Result<( Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, Option>, u64, - ) { + )> { self.execute(zisk_rom, stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope) } } diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index ee99bc7ce..8584e0ba4 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -17,6 +17,8 @@ use crate::{ DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, MAX_NUM_STEPS, }; +use anyhow::Result; + pub struct EmulatorRust { /// Chunk size for processing. chunk_size: u64, @@ -56,14 +58,14 @@ impl EmulatorRust { zisk_rom: &ZiskRom, stdin: &Mutex, sm_bundle: &StaticSMBundle, - ) -> ( + ) -> Result<( Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, Option>, u64, - ) { + )> { let min_traces = self.run_emulator(zisk_rom, Self::NUM_THREADS, &stdin.lock().unwrap()); // Store execute steps @@ -73,7 +75,7 @@ impl EmulatorRust { let (main_count, secn_count) = self.count(zisk_rom, &min_traces, sm_bundle); timer_stop_and_log_info!(COUNT); - (min_traces, main_count, secn_count, None, None, steps) + Ok((min_traces, main_count, secn_count, None, None, steps)) } fn run_emulator( @@ -172,14 +174,14 @@ impl crate::Emulator for EmulatorRust { _use_hints: bool, _stats: &ExecutorStatsHandle, _caller_stats_scope: &zisk_common::StatsScope, - ) -> ( + ) -> Result<( Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, Option>, u64, - ) { + )> { self.execute(zisk_rom, stdin, sm_bundle) } } diff --git a/executor/src/executor.rs b/executor/src/executor.rs index 388ce340b..e6ac901c0 100644 --- a/executor/src/executor.rs +++ b/executor/src/executor.rs @@ -146,14 +146,17 @@ impl WitnessComponent for ZiskExecutor { .state .get_rom() .map_err(|e| proofman_common::ProofmanError::InvalidSetup(e.to_string()))?; - let output = self.rom_executor.execute( - &zisk_rom, - &pctx, - self.registry.sm_bundle(), - self.state.use_hints.load(std::sync::atomic::Ordering::SeqCst), - &self.state.stats, - &_exec_scope, - ); + let output = self + .rom_executor + .execute( + &zisk_rom, + &pctx, + self.registry.sm_bundle(), + self.state.use_hints.load(std::sync::atomic::Ordering::SeqCst), + &self.state.stats, + &_exec_scope, + ) + .expect("Failed to execute ROM and collect minimal traces"); let execution_duration = start_partial.elapsed(); timer_stop_and_log_info!(COMPUTE_MINIMAL_TRACE); diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 47cbbfe73..6ddbdfff2 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -51,6 +51,7 @@ use proofman_common::ProofCtx; use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; use zisk_common::{io::ZiskStdin, AsmExecutionInfo, EmuTrace, ExecutorStatsHandle, StatsScope}; +use anyhow::Result; /// Trait for unified execution across different emulator backends #[allow(clippy::too_many_arguments)] #[allow(clippy::type_complexity)] @@ -65,14 +66,14 @@ pub trait Emulator: Send + Sync { use_hints: bool, stats: &ExecutorStatsHandle, caller_stats_scope: &StatsScope, - ) -> ( + ) -> Result<( Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, Option>, u64, - ); + )>; } /// Enum wrapper for different emulator backends (no heap allocation) @@ -132,14 +133,14 @@ impl Emulator for EmulatorKind { use_hints: bool, stats: &ExecutorStatsHandle, caller_stats_scope: &StatsScope, - ) -> ( + ) -> Result<( Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, Option>, u64, - ) { + )> { match self { Self::Asm(e) => { e.execute(zisk_rom, stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope) diff --git a/executor/src/rom_executor.rs b/executor/src/rom_executor.rs index aa1a7b879..fd58aa0b8 100644 --- a/executor/src/rom_executor.rs +++ b/executor/src/rom_executor.rs @@ -14,6 +14,8 @@ use std::{sync::Mutex, thread::JoinHandle}; use zisk_common::{io::ZiskStdin, AsmExecutionInfo, EmuTrace, ExecutorStatsHandle, StatsScope}; use zisk_core::ZiskRom; +use anyhow::Result; + /// Output from ROM execution. pub struct RomExecutionOutput { /// Minimal traces collected during execution. @@ -90,11 +92,17 @@ impl RomExecutor { use_hints: bool, stats: &ExecutorStatsHandle, caller_stats_scope: &StatsScope, - ) -> RomExecutionOutput { - let (min_traces, main_count, secn_count, handle_mo, handle_rh, steps) = self - .emulator - .execute(zisk_rom, &self.stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope); + ) -> Result { + let (min_traces, main_count, secn_count, handle_mo, handle_rh, steps) = self.emulator.execute( + zisk_rom, + &self.stdin, + pctx, + sm_bundle, + use_hints, + stats, + caller_stats_scope, + )?; - RomExecutionOutput { min_traces, main_count, secn_count, handle_mo, handle_rh, steps } + Ok(RomExecutionOutput { min_traces, main_count, secn_count, handle_mo, handle_rh, steps }) } } From 1ea522c426199454b060ff4ce09ad77dc877ac3c Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:23:05 +0000 Subject: [PATCH 716/782] added helper functions in control_shmem.rs --- emulator-asm/asm-runner/src/control_shmem.rs | 26 ++++++++++++++++---- emulator-asm/asm-runner/src/hints_shmem.rs | 9 +++---- emulator-asm/asm-runner/src/inputs_shmem.rs | 4 +-- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/emulator-asm/asm-runner/src/control_shmem.rs b/emulator-asm/asm-runner/src/control_shmem.rs index 6affbe016..5a923d67c 100644 --- a/emulator-asm/asm-runner/src/control_shmem.rs +++ b/emulator-asm/asm-runner/src/control_shmem.rs @@ -39,14 +39,30 @@ impl ControlShmem { self.writer.write_u64_at(offset as usize, size); } - pub fn increment_u64_at(&self, offset: ControlShmemOffsets, size: usize) { - let current_size = self.read_u64_at(offset); - self.write_u64_at(offset, current_size + size as u64); - } - pub fn reset(&self) { self.write_u64_at(ControlShmemOffsets::PrecompilesSize, 0); self.write_u64_at(ControlShmemOffsets::ShutdownFlag, 0); self.write_u64_at(ControlShmemOffsets::InputsSize, 0); } + + pub fn set_prec_hints_size(&self, size: u64) { + self.write_u64_at(ControlShmemOffsets::PrecompilesSize, size); + } + + pub fn prec_hints_size(&self) -> u64 { + self.read_u64_at(ControlShmemOffsets::PrecompilesSize) + } + + pub fn set_shutdown_flag(&self) { + self.write_u64_at(ControlShmemOffsets::ShutdownFlag, 1); + } + + pub fn set_inputs_size(&self, size: u64) { + self.write_u64_at(ControlShmemOffsets::InputsSize, size); + } + + pub fn inc_inputs_size(&self, size: usize) { + let current_size = self.read_u64_at(ControlShmemOffsets::InputsSize); + self.write_u64_at(ControlShmemOffsets::InputsSize, current_size + size as u64); + } } diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index df2139e39..fbbb0ac10 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -5,8 +5,7 @@ use crate::{ sem_available_name, sem_read_name, shmem_control_reader_name, shmem_precompile_name, - AsmService, AsmServices, ControlShmem, ControlShmemOffsets, SharedMemoryReader, - SharedMemoryWriter, + AsmService, AsmServices, ControlShmem, SharedMemoryReader, SharedMemoryWriter, }; use anyhow::Result; use named_sem::NamedSemaphore; @@ -218,7 +217,7 @@ impl StreamSink for HintsShmem { }; // Read current write position once - let write_pos = unified.control_writer.read_u64_at(ControlShmemOffsets::PrecompilesSize); + let write_pos = unified.control_writer.prec_hints_size(); // Flow control: wait until all consumers have advanced enough // We need to wait for the slowest consumer (minimum read position) @@ -268,9 +267,7 @@ impl StreamSink for HintsShmem { fence(Ordering::Release); // Update write position ONCE in control memory - unified - .control_writer - .write_u64_at(ControlShmemOffsets::PrecompilesSize, write_pos + data_size); + unified.control_writer.set_prec_hints_size(write_pos + data_size); fence(Ordering::Release); diff --git a/emulator-asm/asm-runner/src/inputs_shmem.rs b/emulator-asm/asm-runner/src/inputs_shmem.rs index 8c5d27528..a7208af7a 100644 --- a/emulator-asm/asm-runner/src/inputs_shmem.rs +++ b/emulator-asm/asm-runner/src/inputs_shmem.rs @@ -6,7 +6,7 @@ use zisk_core::MAX_INPUT_SIZE; use crate::{ shmem_input_avail_name, shmem_input_name, AsmInputHeader, AsmServices, ControlShmem, - ControlShmemOffsets, SharedMemoryWriter, + SharedMemoryWriter, }; use anyhow::Result; @@ -80,7 +80,7 @@ impl InputsShmemWriter { // self.writer.write_input(&full_input)?; - self.control_writer.increment_u64_at(ControlShmemOffsets::InputsSize, inputs.len()); + self.control_writer.inc_inputs_size(inputs.len()); self.sem_avail.lock().unwrap().post()?; From 3868ab22636c75feb5bd8d37cdb93e3d84bdd58b Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:07:23 +0000 Subject: [PATCH 717/782] hints stream worker working --- distributed/crates/worker/src/worker.rs | 9 ++- executor/src/asm_resources.rs | 4 +- executor/src/emu_rust.rs | 24 ++------ executor/src/lib.rs | 27 ++++----- precompiles/hints/benches/hints_benchmarks.rs | 8 +-- precompiles/hints/src/hints_processor.rs | 60 +++++++++---------- 6 files changed, 59 insertions(+), 73 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 268eae8c5..c76974862 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -697,12 +697,19 @@ impl Worker { let control_writer = Arc::new(ControlShmem::new(base_port, local_rank, unlock_mapped_memory)?); + let inputs_shmem_writer = Arc::new(InputsShmemWriter::new( + base_port, + local_rank, + unlock_mapped_memory, + control_writer.clone(), + )?); + // HintsShmem::new and HintsProcessor::build perform OS-level shared-memory operations; // run them on the blocking thread pool to avoid stalling Tokio workers. let processor = tokio::task::spawn_blocking(move || -> Result { let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory, control_writer)?; - HintsProcessor::builder(hints_shmem, None::) + HintsProcessor::builder(hints_shmem, Some(inputs_shmem_writer)) .build() .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e)) }) diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index 27126a461..721775269 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -100,13 +100,13 @@ impl AsmResources { let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory, control_writer)?; - HintsProcessor::builder2(hints_shmem, Some(inputs_shmem_writer.clone())) + HintsProcessor::builder(hints_shmem, Some(inputs_shmem_writer.clone())) .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) .build()? } else { let hints_file = HintsFile::new(format!("hints_results_{}.bin", local_rank))?; - HintsProcessor::builder2(hints_file, Some(inputs_shmem_writer.clone())) + HintsProcessor::builder(hints_file, Some(inputs_shmem_writer.clone())) .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) .build()? }; diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index 8584e0ba4..343492df0 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -1,6 +1,5 @@ -use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; +use std::{collections::HashMap, sync::Mutex}; -use asm_runner::{AsmRunnerMO, AsmRunnerRH}; use data_bus::DataBusTrait; use fields::PrimeField64; use proofman_common::ProofCtx; @@ -14,7 +13,8 @@ use zisk_core::ZiskRom; use ziskemu::{EmuOptions, ZiskEmulator}; use crate::{ - DeviceMetricsList, DummyCounter, NestedDeviceMetricsList, StaticSMBundle, MAX_NUM_STEPS, + DeviceMetricsList, DummyCounter, EmulatorResult, NestedDeviceMetricsList, StaticSMBundle, + MAX_NUM_STEPS, }; use anyhow::Result; @@ -58,14 +58,7 @@ impl EmulatorRust { zisk_rom: &ZiskRom, stdin: &Mutex, sm_bundle: &StaticSMBundle, - ) -> Result<( - Vec, - DeviceMetricsList, - NestedDeviceMetricsList, - Option>, - Option>, - u64, - )> { + ) -> Result { let min_traces = self.run_emulator(zisk_rom, Self::NUM_THREADS, &stdin.lock().unwrap()); // Store execute steps @@ -174,14 +167,7 @@ impl crate::Emulator for EmulatorRust { _use_hints: bool, _stats: &ExecutorStatsHandle, _caller_stats_scope: &zisk_common::StatsScope, - ) -> Result<( - Vec, - DeviceMetricsList, - NestedDeviceMetricsList, - Option>, - Option>, - u64, - )> { + ) -> Result { self.execute(zisk_rom, stdin, sm_bundle) } } diff --git a/executor/src/lib.rs b/executor/src/lib.rs index 6ddbdfff2..cc22d63ee 100644 --- a/executor/src/lib.rs +++ b/executor/src/lib.rs @@ -51,6 +51,15 @@ use proofman_common::ProofCtx; use std::{collections::HashMap, sync::Mutex, thread::JoinHandle}; use zisk_common::{io::ZiskStdin, AsmExecutionInfo, EmuTrace, ExecutorStatsHandle, StatsScope}; +pub type EmulatorResult = ( + Vec, + DeviceMetricsList, + NestedDeviceMetricsList, + Option>, + Option>, + u64, +); + use anyhow::Result; /// Trait for unified execution across different emulator backends #[allow(clippy::too_many_arguments)] @@ -66,14 +75,7 @@ pub trait Emulator: Send + Sync { use_hints: bool, stats: &ExecutorStatsHandle, caller_stats_scope: &StatsScope, - ) -> Result<( - Vec, - DeviceMetricsList, - NestedDeviceMetricsList, - Option>, - Option>, - u64, - )>; + ) -> Result; } /// Enum wrapper for different emulator backends (no heap allocation) @@ -133,14 +135,7 @@ impl Emulator for EmulatorKind { use_hints: bool, stats: &ExecutorStatsHandle, caller_stats_scope: &StatsScope, - ) -> Result<( - Vec, - DeviceMetricsList, - NestedDeviceMetricsList, - Option>, - Option>, - u64, - )> { + ) -> Result { match self { Self::Asm(e) => { e.execute(zisk_rom, stdin, pctx, sm_bundle, use_hints, stats, caller_stats_scope) diff --git a/precompiles/hints/benches/hints_benchmarks.rs b/precompiles/hints/benches/hints_benchmarks.rs index 20485541a..fefb7f1a0 100644 --- a/precompiles/hints/benches/hints_benchmarks.rs +++ b/precompiles/hints/benches/hints_benchmarks.rs @@ -48,7 +48,7 @@ fn parallel_speedup_benchmark(c: &mut Criterion) { let received_clone = received.clone(); let sink = BenchSink { received: received_clone }; - let p = HintsProcessor::builder(sink, None::) + let p = HintsProcessor::builder(sink, None::>) .num_threads(threads) .custom_hint(FAST_HINT, |data: &[u64]| -> Result> { thread::sleep(Duration::from_millis(1)); @@ -121,7 +121,7 @@ fn microsecond_hints_benchmark(c: &mut Criterion) { let received_clone = received.clone(); let sink = BenchSink { received: received_clone }; - let p = HintsProcessor::builder(sink, None::) + let p = HintsProcessor::builder(sink, None::>) .num_threads(16) .custom_hint(hint_code, move |data: &[u64]| -> Result> { thread::sleep(Duration::from_micros(micros as u64)); @@ -173,7 +173,7 @@ fn workload_patterns_benchmark(c: &mut Criterion) { let received_clone = received.clone(); let sink = BenchSink { received: received_clone }; - let p = HintsProcessor::builder(sink, None::) + let p = HintsProcessor::builder(sink, None::>) .num_threads(8) .custom_hint(VERY_FAST, |data: &[u64]| -> Result> { thread::sleep(Duration::from_micros(500)); @@ -241,7 +241,7 @@ fn noop_throughput_benchmark(c: &mut Criterion) { for &count in &hint_counts { group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, &num_hints| { b.iter(|| { - let p = HintsProcessor::builder(NullSink, None::) + let p = HintsProcessor::builder(NullSink, None::>) .num_threads(32) .build() .unwrap(); diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 00f772ba1..057082cec 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -231,19 +231,6 @@ impl HintsProcessor { /// .build()?; /// ``` pub fn builder( - hints_sink: impl StreamSink, - inputs_sink: Option, - ) -> HintsProcessorBuilder { - HintsProcessorBuilder { - hints_sink: Arc::new(hints_sink), - inputs_sink: inputs_sink.map(|s| Arc::new(s) as Arc), - num_threads: Self::DEFAULT_NUM_THREADS, - enable_stats: false, - custom_handlers: HashMap::new(), - } - } - - pub fn builder2( hints_sink: impl StreamSink, inputs_sink: Option>, ) -> HintsProcessorBuilder { @@ -816,7 +803,7 @@ mod tests { const TEST_PASSTHROUGH_HINT: u32 = 0x8000_0000 | 0x7FFF_0000; fn processor() -> HintsProcessor { - HintsProcessor::builder(NullHints, None::) + HintsProcessor::builder(NullHints, None::>) .num_threads(2) .custom_hint(0x7FFF_0000, |data| { // This should never be called for pass-through hints @@ -1069,8 +1056,10 @@ mod tests { let received = Arc::new(Mutex::new(Vec::new())); let sink = RecordingSink { received: Arc::clone(&received) }; - let p = - HintsProcessor::builder(sink, None::).num_threads(2).build().unwrap(); + let p = HintsProcessor::builder(sink, None::>) + .num_threads(2) + .build() + .unwrap(); // Send some data (16 bytes = 2 u64s, 8 bytes = 1 u64) let data = vec![ @@ -1112,7 +1101,8 @@ mod tests { let should_fail = Arc::new(AtomicBool::new(false)); let sink = FailingSink { should_fail: Arc::clone(&should_fail) }; - let p = HintsProcessor::builder(sink, None::).num_threads(2).build().unwrap(); + let p = + HintsProcessor::builder(sink, None::>).num_threads(2).build().unwrap(); // First batch succeeds (8 bytes = 1 u64) let data1 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x01]; @@ -1135,32 +1125,34 @@ mod tests { #[test] fn test_builder_configuration() { // Default builder - stats disabled - let p1 = HintsProcessor::builder(NullHints, None::).build().unwrap(); + let p1 = HintsProcessor::builder(NullHints, None::>).build().unwrap(); assert!(p1.stats.is_none()); // Explicitly disabled stats - let p2 = HintsProcessor::builder(NullHints, None::) + let p2 = HintsProcessor::builder(NullHints, None::>) .enable_stats(false) .build() .unwrap(); assert!(p2.stats.is_none()); // Stats enabled - let p3 = HintsProcessor::builder(NullHints, None::) + let p3 = HintsProcessor::builder(NullHints, None::>) .enable_stats(true) .build() .unwrap(); assert!(p3.stats.is_some()); // Custom threads - let p4 = - HintsProcessor::builder(NullHints, None::).num_threads(4).build().unwrap(); + let p4 = HintsProcessor::builder(NullHints, None::>) + .num_threads(4) + .build() + .unwrap(); let data = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x42]; assert!(p4.process_hints(&data, false).is_ok()); assert!(p4.wait_for_completion().is_ok()); // Chaining multiple options - let p5 = HintsProcessor::builder(NullHints, None::) + let p5 = HintsProcessor::builder(NullHints, None::>) .num_threads(8) .enable_stats(true) .build() @@ -1173,8 +1165,10 @@ mod tests { fn test_stress_throughput() { use std::time::Instant; - let p = - HintsProcessor::builder(NullHints, None::).num_threads(32).build().unwrap(); + let p = HintsProcessor::builder(NullHints, None::>) + .num_threads(32) + .build() + .unwrap(); // Generate a large batch of hints const NUM_HINTS: usize = 100_000; @@ -1207,8 +1201,10 @@ mod tests { fn test_stress_concurrent_batches() { use std::time::Instant; - let p = - HintsProcessor::builder(NullHints, None::).num_threads(32).build().unwrap(); + let p = HintsProcessor::builder(NullHints, None::>) + .num_threads(32) + .build() + .unwrap(); const NUM_BATCHES: usize = 1_000; const HINTS_PER_BATCH: usize = 100; @@ -1247,8 +1243,10 @@ mod tests { fn test_stress_with_resets() { use std::time::Instant; - let p = - HintsProcessor::builder(NullHints, None::).num_threads(32).build().unwrap(); + let p = HintsProcessor::builder(NullHints, None::>) + .num_threads(32) + .build() + .unwrap(); const ITERATIONS: usize = 100; const HINTS_PER_ITER: usize = 1_000; @@ -1318,7 +1316,7 @@ mod tests { const SLOW_HINT: u32 = 0x7FFF_0001; // Delays 10ms const MED_HINT: u32 = 0x7FFF_0002; // Delays 5ms - let p = HintsProcessor::builder(sink, None::) + let p = HintsProcessor::builder(sink, None::>) .num_threads(8) .custom_hint(FAST_HINT, |data| { // No delay - returns immediately @@ -1686,7 +1684,7 @@ mod tests { const VARIABLE_HINT: u32 = 0x7FFF_0100; - let p = HintsProcessor::builder(sink, None::) + let p = HintsProcessor::builder(sink, None::>) .num_threads(16) .custom_hint(VARIABLE_HINT, |data| { // Pseudo-random delay based on hash of input value (0-15ms range) From 8df9b7bca131ba00c1d02288fd9ae2bc92613e6d Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:19:54 +0000 Subject: [PATCH 718/782] add appen_input --- emulator-asm/asm-runner/src/control_shmem.rs | 19 +++---- emulator-asm/asm-runner/src/inputs_shmem.rs | 52 +++++++++++--------- emulator-asm/asm-runner/src/shmem_writer.rs | 38 ++++++++++++++ 3 files changed, 76 insertions(+), 33 deletions(-) diff --git a/emulator-asm/asm-runner/src/control_shmem.rs b/emulator-asm/asm-runner/src/control_shmem.rs index 5a923d67c..7082a3b16 100644 --- a/emulator-asm/asm-runner/src/control_shmem.rs +++ b/emulator-asm/asm-runner/src/control_shmem.rs @@ -40,29 +40,30 @@ impl ControlShmem { } pub fn reset(&self) { - self.write_u64_at(ControlShmemOffsets::PrecompilesSize, 0); - self.write_u64_at(ControlShmemOffsets::ShutdownFlag, 0); - self.write_u64_at(ControlShmemOffsets::InputsSize, 0); + self.writer.write_u64_at(ControlShmemOffsets::PrecompilesSize as usize, 0); + self.writer.write_u64_at(ControlShmemOffsets::ShutdownFlag as usize, 0); + self.writer.write_u64_at(ControlShmemOffsets::InputsSize as usize, 0); } pub fn set_prec_hints_size(&self, size: u64) { - self.write_u64_at(ControlShmemOffsets::PrecompilesSize, size); + self.writer.write_u64_at(ControlShmemOffsets::PrecompilesSize as usize, size); } pub fn prec_hints_size(&self) -> u64 { - self.read_u64_at(ControlShmemOffsets::PrecompilesSize) + self.writer.read_u64_at(ControlShmemOffsets::PrecompilesSize as usize) } pub fn set_shutdown_flag(&self) { - self.write_u64_at(ControlShmemOffsets::ShutdownFlag, 1); + self.writer.write_u64_at(ControlShmemOffsets::ShutdownFlag as usize, 1); } pub fn set_inputs_size(&self, size: u64) { - self.write_u64_at(ControlShmemOffsets::InputsSize, size); + self.writer.write_u64_at(ControlShmemOffsets::InputsSize as usize, size); } pub fn inc_inputs_size(&self, size: usize) { - let current_size = self.read_u64_at(ControlShmemOffsets::InputsSize); - self.write_u64_at(ControlShmemOffsets::InputsSize, current_size + size as u64); + let current_size = self.writer.read_u64_at(ControlShmemOffsets::InputsSize as usize); + self.writer + .write_u64_at(ControlShmemOffsets::InputsSize as usize, current_size + size as u64); } } diff --git a/emulator-asm/asm-runner/src/inputs_shmem.rs b/emulator-asm/asm-runner/src/inputs_shmem.rs index a7208af7a..6d1131342 100644 --- a/emulator-asm/asm-runner/src/inputs_shmem.rs +++ b/emulator-asm/asm-runner/src/inputs_shmem.rs @@ -12,7 +12,7 @@ use crate::{ use anyhow::Result; pub struct InputsShmemWriter { - writer: SharedMemoryWriter, + writer: Mutex, control_writer: Arc, sem_avail: Mutex, } @@ -29,18 +29,21 @@ impl InputsShmemWriter { ) -> Result { let port = AsmServices::port_base_for(base_port, local_rank); - let writer = SharedMemoryWriter::new( + let mut writer = SharedMemoryWriter::new( &shmem_input_name(port, local_rank), MAX_INPUT_SIZE as usize, unlock_mapped_memory, )?; + writer.reset(); + writer.append_input(&0u64.to_le_bytes())?; + let sem_avail = Mutex::new(NamedSemaphore::create( shmem_input_avail_name(port, local_rank).clone(), 0, )?); - Ok(Self { writer, control_writer, sem_avail }) + Ok(Self { writer: Mutex::new(writer), control_writer, sem_avail }) } pub fn write_input(&self, inputs: &[u8]) -> Result<()> { @@ -57,39 +60,40 @@ impl InputsShmemWriter { full_input.push(0); } - self.writer.write_input(&full_input)?; - - self.writer.write_u64_at(8, inputs.len() as u64); + let writer = self.writer.lock().unwrap(); + writer.write_input(&full_input)?; + writer.write_u64_at(8, inputs.len() as u64); Ok(()) } - pub fn write_input2(&self, inputs: &[u8]) -> Result<()> { - const HEADER_SIZE: usize = size_of::(); - - let shmem_input_size = (HEADER_SIZE + inputs.len() + 7) & !7; - - let mut full_input = Vec::with_capacity(shmem_input_size); - - let header = AsmInputHeader { zero: 0, input_data_size: 0 }; - full_input.extend_from_slice(&header.to_bytes()); - full_input.extend_from_slice(inputs); - while full_input.len() < shmem_input_size { - full_input.push(0); - } - - // self.writer.write_input(&full_input)?; - + pub fn append_input(&self, inputs: &[u8]) -> Result<()> { + self.writer.lock().unwrap().append_input(&inputs)?; self.control_writer.inc_inputs_size(inputs.len()); - self.sem_avail.lock().unwrap().post()?; Ok(()) } + + pub fn reset(&self) { + let mut writer = self.writer.lock().unwrap(); + writer.reset(); + writer + .append_input(&0u64.to_le_bytes()) + .expect("Failed to write initial header after reset"); + + self.control_writer.reset(); + let mut sem_avail_guard = self.sem_avail.lock().unwrap(); + while sem_avail_guard.try_wait().is_ok() {} + } } impl StreamSink for InputsShmemWriter { fn submit(&self, hints: Vec) -> anyhow::Result<()> { - self.write_input2(&reinterpret_vec(hints)?) + self.append_input(&reinterpret_vec(hints)?) + } + + fn reset(&self) { + self.reset(); } } diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index 13e994707..d87155247 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -115,6 +115,44 @@ impl SharedMemoryWriter { Ok(()) } + /// Writes data to the shared memory, always from the start + /// + /// # Type Parameters + /// * `T` - The element type of the slice (e.g., u8, u64) + /// + /// # Arguments + /// * `data` - A slice of data to write to shared memory + /// + /// # Returns + /// * `Ok(())` - If data was successfully written + /// * `Err` - If data size exceeds shared memory capacity or msync fails + pub fn append_input(&mut self, data: &[T]) -> Result<()> { + let byte_size = std::mem::size_of_val(data); + + if byte_size > self.size { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "Data size ({} bytes) exceeds shared memory capacity ({}) for '{}'", + byte_size, self.size, self.name + ), + )); + } + + unsafe { + ptr::copy_nonoverlapping(data.as_ptr() as *const u8, self.current_ptr, byte_size); + // Force changes to be flushed to the shared memory + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + if msync(self.ptr as *mut _, self.size, MS_SYNC) != 0 { + return Err(io::Error::last_os_error()); + } + } + + self.current_ptr = unsafe { self.current_ptr.add(byte_size) }; + + Ok(()) + } + /// Writes data to the shared memory as a ring buffer, handling wraparound automatically /// /// Uses internal pointer tracking with automatic wraparound. From 57656b5cb42ee1f26907fca3b790f47e130f3a59 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 6 Mar 2026 07:33:48 +0000 Subject: [PATCH 719/782] fix hints doc and schema --- common/src/hints.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/src/hints.rs b/common/src/hints.rs index 86d5abf3d..1455ce225 100644 --- a/common/src/hints.rs +++ b/common/src/hints.rs @@ -22,11 +22,13 @@ //! ├─────────────────────────────────────────────────────────────┤ //! │ ... │ //! ├─────────────────────────────────────────────────────────────┤ -//! │ Data[length-1] (u64) │ +//! │ Data[N-1] (u64) │ //! └─────────────────────────────────────────────────────────────┘ //! +//! where N = ceil(Length / 8) +//! //! - Hint Code — Control code or Data Hint Type -//! - Length — Number of following u64 data words +//! - Length — Data length in bytes //! //! ## Hint Type Layout //! From 2ead33f7f749dd9be10f5da01fc4cfab6f8f905b Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 6 Mar 2026 08:16:02 +0000 Subject: [PATCH 720/782] cago fmt --- executor/src/rom_executor.rs | 19 ++++++++++--------- ziskos/entrypoint/src/hints/keccak256.rs | 3 +-- ziskos/entrypoint/src/hints/sha256f.rs | 3 +-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/executor/src/rom_executor.rs b/executor/src/rom_executor.rs index fd58aa0b8..de6ca38d5 100644 --- a/executor/src/rom_executor.rs +++ b/executor/src/rom_executor.rs @@ -93,15 +93,16 @@ impl RomExecutor { stats: &ExecutorStatsHandle, caller_stats_scope: &StatsScope, ) -> Result { - let (min_traces, main_count, secn_count, handle_mo, handle_rh, steps) = self.emulator.execute( - zisk_rom, - &self.stdin, - pctx, - sm_bundle, - use_hints, - stats, - caller_stats_scope, - )?; + let (min_traces, main_count, secn_count, handle_mo, handle_rh, steps) = + self.emulator.execute( + zisk_rom, + &self.stdin, + pctx, + sm_bundle, + use_hints, + stats, + caller_stats_scope, + )?; Ok(RomExecutionOutput { min_traces, main_count, secn_count, handle_mo, handle_rh, steps }) } diff --git a/ziskos/entrypoint/src/hints/keccak256.rs b/ziskos/entrypoint/src/hints/keccak256.rs index 1e44b0ed5..82b8058c2 100644 --- a/ziskos/entrypoint/src/hints/keccak256.rs +++ b/ziskos/entrypoint/src/hints/keccak256.rs @@ -1,3 +1,2 @@ #[no_mangle] -pub unsafe extern "C" fn hint_keccak256(_input_ptr: *const u8, _input_len: usize) { -} +pub unsafe extern "C" fn hint_keccak256(_input_ptr: *const u8, _input_len: usize) {} diff --git a/ziskos/entrypoint/src/hints/sha256f.rs b/ziskos/entrypoint/src/hints/sha256f.rs index 3195caee9..4379df561 100644 --- a/ziskos/entrypoint/src/hints/sha256f.rs +++ b/ziskos/entrypoint/src/hints/sha256f.rs @@ -1,3 +1,2 @@ #[no_mangle] -pub unsafe extern "C" fn hint_sha256(_f_ptr: *const u8, _f_len: usize) { -} +pub unsafe extern "C" fn hint_sha256(_f_ptr: *const u8, _f_len: usize) {} From 529fb9daa8270ab8e0283c17fc82e360fe1c81ac Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 6 Mar 2026 08:16:51 +0000 Subject: [PATCH 721/782] added write_at fn --- emulator-asm/asm-runner/src/inputs_shmem.rs | 42 +++++++++++---------- emulator-asm/asm-runner/src/shmem_writer.rs | 15 ++++++-- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/emulator-asm/asm-runner/src/inputs_shmem.rs b/emulator-asm/asm-runner/src/inputs_shmem.rs index 6d1131342..485e7b745 100644 --- a/emulator-asm/asm-runner/src/inputs_shmem.rs +++ b/emulator-asm/asm-runner/src/inputs_shmem.rs @@ -1,12 +1,12 @@ use std::sync::{Arc, Mutex}; use named_sem::NamedSemaphore; +use tracing::info; use zisk_common::{io::StreamSink, reinterpret_vec}; use zisk_core::MAX_INPUT_SIZE; use crate::{ - shmem_input_avail_name, shmem_input_name, AsmInputHeader, AsmServices, ControlShmem, - SharedMemoryWriter, + shmem_input_avail_name, shmem_input_name, AsmServices, ControlShmem, SharedMemoryWriter, }; use anyhow::Result; @@ -47,28 +47,32 @@ impl InputsShmemWriter { } pub fn write_input(&self, inputs: &[u8]) -> Result<()> { - const HEADER_SIZE: usize = size_of::(); - - let shmem_input_size = (HEADER_SIZE + inputs.len() + 7) & !7; - - let mut full_input = Vec::with_capacity(shmem_input_size); - - let header = AsmInputHeader { zero: 0, input_data_size: 0 }; - full_input.extend_from_slice(&header.to_bytes()); - full_input.extend_from_slice(inputs); - while full_input.len() < shmem_input_size { - full_input.push(0); - } - - let writer = self.writer.lock().unwrap(); - writer.write_input(&full_input)?; - writer.write_u64_at(8, inputs.len() as u64); + self.writer.lock().unwrap().write_at(8, inputs)?; + self.control_writer.inc_inputs_size(inputs.len()); + self.sem_avail.lock().unwrap().post()?; Ok(()) } pub fn append_input(&self, inputs: &[u8]) -> Result<()> { - self.writer.lock().unwrap().append_input(&inputs)?; + info!("Appending input of size {} bytes to shared memory", inputs.len()); + // Print the first 4 u64 in hexadecimal for debugging + for (i, chunk) in inputs.chunks(8).take(4).enumerate() { + let value = u64::from_le_bytes(chunk.try_into().unwrap_or_default()); + info!("Input chunk {}: 0x{:016x}", i, value); + } + // Printhe two last u64 in hexadecimal for debugging + for (i, chunk) in inputs.chunks(8).rev().take(2).enumerate() { + let value = u64::from_le_bytes(chunk.try_into().unwrap_or_default()); + info!("Input chunk {}: 0x{:016x}", inputs.len() - (i + 1) * 8, value); + } + // Print if the input is divisible by 8tes + if inputs.len() % 8 == 0 { + info!("Input size is divisible by 8 bytes"); + } else { + info!("Input size is not divisible by 8 bytes"); + } + self.writer.lock().unwrap().append_input(inputs)?; self.control_writer.inc_inputs_size(inputs.len()); self.sem_avail.lock().unwrap().post()?; diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index d87155247..7c77ec02e 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -2,6 +2,7 @@ use libc::{mmap, msync, shm_open, MAP_FAILED, MAP_SHARED, MS_SYNC}; use std::io::{self, Result}; use std::ptr; +use tracing::info; use libc::{c_void, close, munmap, PROT_READ, PROT_WRITE, S_IRUSR, S_IWUSR}; @@ -79,18 +80,19 @@ impl SharedMemoryWriter { } } - /// Writes data to the shared memory, always from the start + /// Writes data to the shared memory, starting at the specified offset /// /// # Type Parameters /// * `T` - The element type of the slice (e.g., u8, u64) /// /// # Arguments + /// * `offset` - Byte offset from the start of shared memory where data should be written /// * `data` - A slice of data to write to shared memory /// /// # Returns /// * `Ok(())` - If data was successfully written /// * `Err` - If data size exceeds shared memory capacity or msync fails - pub fn write_input(&self, data: &[T]) -> Result<()> { + pub fn write_at(&self, offset: usize, data: &[T]) -> Result<()> { let byte_size = std::mem::size_of_val(data); if byte_size > self.size { @@ -104,7 +106,7 @@ impl SharedMemoryWriter { } unsafe { - ptr::copy_nonoverlapping(data.as_ptr() as *const u8, self.ptr, byte_size); + ptr::copy_nonoverlapping(data.as_ptr() as *const u8, self.ptr.add(offset), byte_size); // Force changes to be flushed to the shared memory #[cfg(all(target_os = "linux", target_arch = "x86_64"))] if msync(self.ptr as *mut _, self.size, MS_SYNC /*| MS_INVALIDATE*/) != 0 { @@ -139,6 +141,13 @@ impl SharedMemoryWriter { )); } + unsafe { + let offset = self.current_ptr.offset_from(self.ptr) as usize; + info!( + "Appending data of size {} bytes to shared memory '{}' at offset {} (current_ptr: {:p})", + byte_size, self.name, offset, self.current_ptr + ); + } unsafe { ptr::copy_nonoverlapping(data.as_ptr() as *const u8, self.current_ptr, byte_size); // Force changes to be flushed to the shared memory From 06901f8493ce856d2429f9394ee551929986573c Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Fri, 6 Mar 2026 09:36:56 +0100 Subject: [PATCH 722/782] add tool for testing and debug hint files --- tools/hint_file/Cargo.lock | 193 ++++++++++++++ tools/hint_file/Cargo.toml | 14 + tools/hint_file/README.md | 310 ++++++++++++++++++++++ tools/hint_file/src/main.rs | 515 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1032 insertions(+) create mode 100644 tools/hint_file/Cargo.lock create mode 100644 tools/hint_file/Cargo.toml create mode 100644 tools/hint_file/README.md create mode 100644 tools/hint_file/src/main.rs diff --git a/tools/hint_file/Cargo.lock b/tools/hint_file/Cargo.lock new file mode 100644 index 000000000..64c945643 --- /dev/null +++ b/tools/hint_file/Cargo.lock @@ -0,0 +1,193 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hint_file" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/tools/hint_file/Cargo.toml b/tools/hint_file/Cargo.toml new file mode 100644 index 000000000..b339c5b24 --- /dev/null +++ b/tools/hint_file/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "hint_file" +version = "0.1.0" +edition = "2021" + +[workspace] + +[[bin]] +name = "hint_file" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0" +clap = { version = "4.5", features = ["derive"] } diff --git a/tools/hint_file/README.md b/tools/hint_file/README.md new file mode 100644 index 000000000..c2f0ce09e --- /dev/null +++ b/tools/hint_file/README.md @@ -0,0 +1,310 @@ +# Hint File + +Utility to read, analyze, and filter Zisk hints files. + +## Features + +- **Summary mode**: Display statistics by hint type (count, total bytes, min/max) +- **Detail mode**: Show detailed list of all hints in the file +- **Filter mode**: Exclude specific hint types from output (remove unwanted hints) +- **Extract mode**: Include only specific hint types in output (keep wanted hints) +- **Inject mode**: Interleave binary input file chunks with existing hints +- **Validation**: Check file integrity and detect garbage data + +## Hints File Format + +The hints file has the following structure: + +1. **8-byte header** at the beginning (skipped during processing) +2. **Multiple hints**, each with: + - 8-byte header (length + type) + - Data payload (8-byte aligned) +3. **Final tag**: A hint with type=1 and length=0 marking the end + +Each hint has the following structure: + +``` +┌────────────────────────────────┐ +│ Length (u32 LE) - 4 bytes │ ← Actual length in bytes +├────────────────────────────────┤ +│ Type (u32 LE) - 4 bytes │ ← Hint type +├────────────────────────────────┤ +│ Data - aligned bytes │ ← Hint payload (padded to 8-byte boundary) +└────────────────────────────────┘ +``` + +- **Length**: Actual number of bytes of data (u32, little-endian) +- **Type**: Hint type identifier (e.g., 0xF0000 for INPUT, 0x0100 for SHA256, etc.) +- **Data**: The hint payload, stored in multiples of 8 bytes (padded if needed) + +⚠️ **Important notes**: +- The length field contains the actual data size in bytes +- Data is stored aligned to 8-byte boundaries (e.g., 12 bytes of data will occupy 16 bytes in the file) +- The length does NOT include the 8-byte hint header (u32 + u32) +- The file ends when a hint with type=1 and length=0 is found +- The tool verifies there is no garbage data after the final tag + +## Build + +```bash +cd tools/hint_file +cargo build --release +``` + +The binary will be generated at `target/release/hint_file`. + +## Usage + +### Basic Usage (Summary Mode) + +By default, the tool shows a summary of hints found in the file: + +```bash +./target/release/hint_file +``` + +Or: + +```bash +cargo run --release -- +``` + +### Show Detailed List + +Use `-d` or `--detail` to see all hints: + +```bash +./target/release/hint_file --detail +``` + +### Filter Hints (Exclude) + +Remove specific hint types from the output file using `-f` or `--filter` and `-o` or `--output`: + +```bash +# Filter out single type (hex) - keeps all EXCEPT 0xF0000 +./target/release/hint_file -f 0xF0000 -o output.bin input.bin + +# Filter out multiple types - keeps all EXCEPT these +./target/release/hint_file -f 0xF0000,256,0x0100 -o output.bin input.bin + +# Filter with detail view +./target/release/hint_file -d -f 0x0100 -o no_sha256.bin hints.bin +``` + +### Extract Hints (Include) + +Keep only specific hint types in the output file using `-e` or `--extract` and `-o` or `--output`: + +```bash +# Extract single type (hex) - keeps ONLY 0xF0000 +./target/release/hint_file -e 0xF0000 -o output.bin input.bin + +# Extract multiple types - keeps ONLY these +./target/release/hint_file -e 0xF0000,256,0x0100 -o output.bin input.bin + +# Extract with detail view +./target/release/hint_file -d -e 0x0100 -o sha256_only.bin hints.bin +``` + +### Inject Input (Interleave) + +Interleave chunks from a binary input file with existing hints using `--inject-input` and related options: + +```bash +# Basic inject - split input.dat into chunks and interleave with hints +./target/release/hint_file --inject-input input.dat -o output.bin hints.bin + +# Custom chunk size (1024 bytes including 8-byte header = 1016 bytes of data per chunk) +./target/release/hint_file --inject-input input.dat --inject-chunk-size 1024 -o output.bin hints.bin + +# Start injection after first 10 hints +./target/release/hint_file --inject-input input.dat --inject-start 10 -o output.bin hints.bin + +# Inject 2 chunks at a time, then skip 3 original hints, repeat +./target/release/hint_file --inject-input input.dat \ + --inject-group-size 2 \ + --inject-distance 3 \ + -o output.bin hints.bin + +# Full example with all parameters +./target/release/hint_file --inject-input input.dat \ + --inject-start 5 \ + --inject-chunk-size 512 \ + --inject-group-size 3 \ + --inject-distance 2 \ + --inject-type 0xF0000 \ + -o output.bin hints.bin +``` + +**Inject Parameters:** +- `--inject-input `: Binary file to split and inject (required for inject mode) +- `--inject-start `: Start position (0 = beginning, 1 = after first hint) [default: 0] +- `--inject-chunk-size `: Size of each chunk including 8-byte header [default: 1024] +- `--inject-group-size `: Number of chunks to inject together [default: 1] +- `--inject-distance `: Number of original hints between groups [default: 1] +- `--inject-type `: Hint type for injected chunks (hex/decimal) [default: 0xF0000] + +**How it works:** +1. The input file is split into chunks of `inject-chunk-size` (minus 8 bytes for header) +2. Injection starts at position `inject-start` in the original hints +3. Each group of `inject-group-size` chunks is written together +4. Between groups, `inject-distance` original hints are preserved +5. Any remaining chunks are appended at the end + +**Example pattern** (start=1, group=2, distance=3): +``` +Original: [H0, H1, H2, H3, H4, H5, H6, H7, ...] +Result: [H0, I0, I1, H1, H2, H3, I2, I3, H4, H5, H6, I4, I5, ...] + └─┘ └────┘ └────────┘ └────┘ └────────┘ └────┘ + start group distance group distance group +``` + +### Combined Options + +```bash +# Show both detail and summary +./target/release/hint_file --detail --summary + +# Filter (exclude types) and show summary +./target/release/hint_file -f 0xF0000 -o filtered.bin -s input.bin + +# Extract (include only types) and show summary +./target/release/hint_file -e 0x0100,0x0200 -o extracted.bin -s input.bin + +# Inject with detail view +./target/release/hint_file --inject-input data.bin --detail -o output.bin hints.bin +``` + +## Command-Line Options + +- ``: Input hints file (required) +- `-d, --detail`: Show detailed list of all hints +- `-s, --summary`: Show summary statistics (default if --detail not used) +- `-o, --output `: Output file for filtered/extracted/injected hints +- `-f, --filter `: Exclude hint types (comma-separated, keeps all EXCEPT these) + - Supports decimal: `-f 256,512` + - Supports hexadecimal: `-f 0xF0000,0x0100` + - Mixed formats: `-f 0xF0000,256,0x0100` + - Cannot be used with `--extract` +- `-e, --extract `: Include only hint types (comma-separated, keeps ONLY these) + - Supports decimal: `-e 256,512` + - Supports hexadecimal: `-e 0xF0000,0x0100` + - Mixed formats: `-e 0xF0000,256,0x0100` + - Cannot be used with `--filter` +- `--inject-input `: Binary input file to inject as hints (requires `--output`) +- `--inject-start `: Start position for injection [default: 0] +- `--inject-chunk-size `: Chunk size including header [default: 1024] +- `--inject-group-size `: Chunks per group [default: 1] +- `--inject-distance `: Original hints between groups [default: 1] +- `--inject-type `: Hint type for injected chunks [default: 0xF0000] + +## Example Outputs + +### Summary Mode (Default) + +``` +=== Summary === +File: hints_results_0.bin +Total hints: 150 + + Type (hex) | Count | Total (bytes) | Min (bytes) | Max (bytes) +-------------------------------------------------------------------------------- + 0x00000100 | 10 | 2560 | 256 | 256 + 0x00000200 | 20 | 2560 | 128 | 128 + 0x000F0000 | 120 | 122880 | 1024 | 1024 +``` + +### Detail Mode + +``` +Reading hints file: hints_results_0.bin +Header: [00, 00, 00, 00, 00, 00, 00, 00] +-------------------------------------------------------------------------------- + Index | Type (hex) | Len (bytes) | Aligned (bytes) +-------------------------------------------------------------------------------- + 0 | 0x000F0000 | 1024 | 1024 + 1 | 0x00000100 | 256 | 256 + 2 | 0x00000200 | 128 | 128 + 3 | 0x00000300 | 12 | 16 +-------------------------------------------------------------------------------- +Total hints processed: 4 +Final tag: length=0, type=1 (0x00000001) +``` + +### Filter Mode (Exclude) + +``` +=== Summary === +File: input.bin +Total hints: 150 + + Type (hex) | Count | Total (bytes) | Min (bytes) | Max (bytes) +-------------------------------------------------------------------------------- + 0x00000100 | 10 | 2560 | 256 | 256 + 0x00000200 | 20 | 2560 | 128 | 128 + +Filtered hints written to: output.bin (excluded types: 0x000F0000) +``` + +### Extract Mode (Include) + +``` +=== Summary === +File: input.bin +Total hints: 150 + + Type (hex) | Count | Total (bytes) | Min (bytes) | Max (bytes) +-------------------------------------------------------------------------------- + 0x000F0000 | 120 | 122880 | 1024 | 1024 + +Extracted hints written to: output.bin (included types: 0x000F0000) +``` + +### Inject Mode (Interleave) + +``` +=== Summary === +File: input.bin +Total hints: 150 + + Type (hex) | Count | Total (bytes) | Min (bytes) | Max (bytes) +-------------------------------------------------------------------------------- + 0x00000100 | 10 | 2560 | 256 | 256 + 0x00000200 | 20 | 2560 | 128 | 128 + 0x000F0000 | 145 | 147456 | 1016 | 1024 + +Hints with injected input written to: output.bin (25 chunks injected, type: 0x000F0000) + Inject parameters: start=0, chunk_size=1024, group_size=1, distance=5 +``` + +## Known Hint Codes + +### Control Codes +- `0x0000` - CTRL_START: Reset state +- `0x0001` - CTRL_END: End processing +- `0x0002` - CTRL_CANCEL: Cancel stream +- `0x0003` - CTRL_ERROR: Error + +### Data Hints +- `0xF0000` - HINT_INPUT: Input data +- `0x0100` - HINT_SHA256: SHA-256 +- `0x0200` - HINT_BN254_G1_ADD: BN254 G1 Add +- `0x0201` - HINT_BN254_G1_MUL: BN254 G1 Mul +- `0x0205` - HINT_BN254_PAIRING_CHECK: BN254 Pairing +- `0x0300` - HINT_SECP256K1_ECDSA_ADDRESS_RECOVER: secp256k1 recover +- `0x0301` - HINT_SECP256K1_ECDSA_VERIFY_ADDRESS_RECOVER: secp256k1 verify+recover +- `0x0380` - HINT_SECP256R1_ECDSA_VERIFY: secp256r1 verify +- `0x0400` - HINT_BLS12_381_G1_ADD: BLS12-381 G1 Add +- `0x0401` - HINT_BLS12_381_G1_MSM: BLS12-381 G1 MSM +- `0x0405` - HINT_BLS12_381_G2_ADD: BLS12-381 G2 Add +- `0x0406` - HINT_BLS12_381_G2_MSM: BLS12-381 G2 MSM +- `0x040A` - HINT_BLS12_381_PAIRING_CHECK: BLS12-381 Pairing +- `0x0410` - HINT_BLS12_381_FP_TO_G1: BLS12-381 Fp to G1 +- `0x0411` - HINT_BLS12_381_FP2_TO_G2: BLS12-381 Fp2 to G2 +- `0x0500` - HINT_MODEXP: Modular exponentiation +- `0x0600` - HINT_VERIFY_KZG_PROOF: KZG verification +- `0x0700` - HINT_KECCAK256: Keccak-256 +- `0x0800` - HINT_BLAKE2B_COMPRESS: Blake2b + +For more details, see `common/src/hints.rs`. diff --git a/tools/hint_file/src/main.rs b/tools/hint_file/src/main.rs new file mode 100644 index 000000000..cb6c57896 --- /dev/null +++ b/tools/hint_file/src/main.rs @@ -0,0 +1,515 @@ +use anyhow::{Context, Result}; +use clap::Parser; +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufReader, BufWriter, Read, Write}; +use std::path::PathBuf; + +/// Utility to read, analyze, and filter Zisk hints files +#[derive(Parser, Debug)] +#[command(name = "hint_file")] +#[command(about = "Read and process Zisk hints files", long_about = None)] +struct Args { + /// Input hints file + input: PathBuf, + + /// Show detailed list of hints + #[arg(short, long)] + detail: bool, + + /// Show summary statistics (enabled by default if --detail is not used) + #[arg(short, long)] + summary: bool, + + /// Output file for filtered hints + #[arg(short, long)] + output: Option, + + /// Filter out (exclude) hint types from output (comma-separated, decimal or hex with 0x prefix) + /// Example: --filter 0xF0000,256,0x0100 + #[arg(short, long, value_delimiter = ',', conflicts_with = "extract")] + filter: Vec, + + /// Extract (include only) specific hint types to output (comma-separated, decimal or hex with 0x prefix) + /// Example: --extract 0xF0000,256,0x0100 + #[arg(short, long, value_delimiter = ',', conflicts_with = "filter")] + extract: Vec, + + /// Input file to inject as hints (binary file without format) + #[arg(long, requires = "output")] + inject_input: Option, + + /// Index position where to start injecting input chunks (0 = beginning, 1 = after first hint, etc.) + #[arg(long, default_value = "0", requires = "inject_input")] + inject_start: usize, + + /// Size of each input chunk in bytes (including 8-byte header) + #[arg(long, default_value = "1024", requires = "inject_input")] + inject_chunk_size: usize, + + /// Number of input chunks to inject together as a group + #[arg(long, default_value = "1", requires = "inject_input")] + inject_group_size: usize, + + /// Number of original hints to keep between injected groups + #[arg(long, default_value = "1", requires = "inject_input")] + inject_distance: usize, + + /// Hint type for injected input chunks (hex or decimal) + #[arg(long, default_value = "0xF0000", requires = "inject_input")] + inject_type: String, +} + +/// Reads a u32 in little-endian format from the buffer +fn read_u32_le(reader: &mut R) -> std::io::Result { + let mut buf = [0u8; 4]; + reader.read_exact(&mut buf)?; + Ok(u32::from_le_bytes(buf)) +} + +/// Writes a u32 in little-endian format to the writer +fn write_u32_le(writer: &mut W, value: u32) -> std::io::Result<()> { + writer.write_all(&value.to_le_bytes()) +} + +/// Structure representing a hint +#[derive(Debug, Clone)] +struct Hint { + length_bytes: u32, // Actual length in bytes (from header) + length_aligned: usize, // Aligned length (multiple of 8 bytes) + hint_type: u32, // Hint type + data: Vec, // Hint data (aligned) +} + +impl Hint { + fn from_reader(reader: &mut R) -> std::io::Result> { + // Try to read the first 4 bytes (length in bytes) + let length_bytes = match read_u32_le(reader) { + Ok(len) => len, + Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None), + Err(e) => return Err(e), + }; + + // Read the next 4 bytes (hint type) + let hint_type = read_u32_le(reader)?; + + // Calculate aligned length (rounded up to multiple of 8 bytes) + let length_aligned = ((length_bytes as usize + 7) / 8) * 8; + + // Read the hint data (aligned to 8 bytes) + let mut data = vec![0u8; length_aligned]; + reader.read_exact(&mut data)?; + + Ok(Some(Hint { length_bytes, length_aligned, hint_type, data })) + } + + fn write_to(&self, writer: &mut W) -> std::io::Result<()> { + write_u32_le(writer, self.length_bytes)?; + write_u32_le(writer, self.hint_type)?; + writer.write_all(&self.data)?; + Ok(()) + } +} + +#[derive(Debug, Default)] +struct HintStats { + count: usize, + total_bytes: u64, + min_bytes: u32, + max_bytes: u32, +} + +impl HintStats { + fn update(&mut self, length: u32) { + self.count += 1; + self.total_bytes += length as u64; + if self.count == 1 { + self.min_bytes = length; + self.max_bytes = length; + } else { + self.min_bytes = self.min_bytes.min(length); + self.max_bytes = self.max_bytes.max(length); + } + } +} + +fn parse_hint_types(filter_strings: &[String]) -> Result> { + let mut types = Vec::new(); + for s in filter_strings { + let s = s.trim(); + let value = if s.starts_with("0x") || s.starts_with("0X") { + u32::from_str_radix(&s[2..], 16) + .with_context(|| format!("Invalid hex hint type: {}", s))? + } else { + s.parse::().with_context(|| format!("Invalid decimal hint type: {}", s))? + }; + types.push(value); + } + Ok(types) +} + +fn parse_hint_type(type_string: &str) -> Result { + let s = type_string.trim(); + let value = if s.starts_with("0x") || s.starts_with("0X") { + u32::from_str_radix(&s[2..], 16).with_context(|| format!("Invalid hex hint type: {}", s))? + } else { + s.parse::().with_context(|| format!("Invalid decimal hint type: {}", s))? + }; + Ok(value) +} + +/// Read input file and split it into chunks as Hints +fn create_input_chunks( + input_path: &PathBuf, + chunk_size: usize, + hint_type: u32, +) -> Result> { + if chunk_size <= 8 { + anyhow::bail!("Chunk size must be greater than 8 bytes (to accommodate header)"); + } + + let mut file = File::open(input_path) + .with_context(|| format!("Cannot open inject input file: {}", input_path.display()))?; + + let mut input_data = Vec::new(); + file.read_to_end(&mut input_data).context("Failed to read inject input file")?; + + if input_data.is_empty() { + anyhow::bail!("Inject input file is empty"); + } + + let data_size_per_chunk = chunk_size - 8; // Subtract 8 bytes for header + let mut chunks = Vec::new(); + + for chunk_data in input_data.chunks(data_size_per_chunk) { + let length_bytes = chunk_data.len() as u32; + let length_aligned = ((length_bytes as usize + 7) / 8) * 8; + + // Pad data to 8-byte alignment + let mut data = chunk_data.to_vec(); + data.resize(length_aligned, 0); + + chunks.push(Hint { length_bytes, length_aligned, hint_type, data }); + } + + Ok(chunks) +} + +/// Interleave input chunks with original hints and write to output +fn write_interleaved_hints( + writer: &mut W, + original_hints: &[Hint], + inject_chunks: &[Hint], + start_index: usize, + group_size: usize, + distance: usize, + stats: &mut HashMap, +) -> Result<()> { + let mut original_idx = 0; + let mut inject_idx = 0; + + // Write initial hints before inject_start + while original_idx < start_index && original_idx < original_hints.len() { + original_hints[original_idx].write_to(writer)?; + original_idx += 1; + } + + // Interleave inject chunks with original hints + while inject_idx < inject_chunks.len() && original_idx < original_hints.len() { + // Write a group of inject chunks + let mut group_count = 0; + while group_count < group_size && inject_idx < inject_chunks.len() { + let chunk = &inject_chunks[inject_idx]; + chunk.write_to(writer)?; + + // Update statistics for injected chunk + stats + .entry(chunk.hint_type) + .or_insert_with(HintStats::default) + .update(chunk.length_bytes); + + inject_idx += 1; + group_count += 1; + } + + // Write distance number of original hints + let mut distance_count = 0; + while distance_count < distance && original_idx < original_hints.len() { + original_hints[original_idx].write_to(writer)?; + original_idx += 1; + distance_count += 1; + } + } + + // Write remaining original hints + while original_idx < original_hints.len() { + original_hints[original_idx].write_to(writer)?; + original_idx += 1; + } + + // Write remaining inject chunks (if any left) + while inject_idx < inject_chunks.len() { + let chunk = &inject_chunks[inject_idx]; + chunk.write_to(writer)?; + + // Update statistics for injected chunk + stats.entry(chunk.hint_type).or_insert_with(HintStats::default).update(chunk.length_bytes); + + inject_idx += 1; + } + + Ok(()) +} + +fn process_hints_file(args: &Args) -> Result<()> { + let file = File::open(&args.input) + .with_context(|| format!("Cannot open file: {}", args.input.display()))?; + + let mut reader = BufReader::new(file); + + // Read the 8-byte header at the beginning of the file + let mut header = [0u8; 8]; + reader + .read_exact(&mut header) + .context("Failed to read 8-byte header at the beginning of the file")?; + + // Parse filter types (exclude) or extract types (include only) + let filter_types = + if !args.filter.is_empty() { Some(parse_hint_types(&args.filter)?) } else { None }; + + let extract_types = + if !args.extract.is_empty() { Some(parse_hint_types(&args.extract)?) } else { None }; + + // Read input chunks if inject mode is enabled + let inject_chunks = if let Some(ref inject_path) = args.inject_input { + let inject_type = parse_hint_type(&args.inject_type)?; + Some(create_input_chunks(inject_path, args.inject_chunk_size, inject_type)?) + } else { + None + }; + + let mut hints = Vec::new(); + let mut stats: HashMap = HashMap::new(); + let mut hint_index = 0; + + // Show detail header if requested + if args.detail { + println!("Reading hints file: {}", args.input.display()); + println!("Header: {:02x?}", header); + println!("{:-<80}", ""); + println!( + "{:>6} | {:>12} | {:>12} | {:>14}", + "Index", "Type (hex)", "Len (bytes)", "Aligned (bytes)" + ); + println!("{:-<80}", ""); + } + + // Read all hints from the input file + let mut final_tag: Option = None; + loop { + match Hint::from_reader(&mut reader) { + Ok(Some(hint)) => { + // Check if this is the final tag (type=1) + if hint.hint_type == 1 { + if args.detail { + println!("{:-<80}", ""); + println!("Total hints processed: {}", hint_index); + println!( + "Final tag: length={}, type=1 (0x{:08X})", + hint.length_bytes, hint.hint_type + ); + } + + if hint.length_bytes != 0 { + eprintln!( + "Warning: Expected length=0 in final tag, got {}", + hint.length_bytes + ); + } + + // Check for garbage after the final tag + let mut garbage_buf = [0u8; 1]; + match reader.read(&mut garbage_buf) { + Ok(0) => { + // No more data, good + } + Ok(n) => { + eprintln!( + "Warning: Found {} extra bytes after final tag (garbage data)", + n + ); + // Try to read more to see how much garbage there is + let mut extra_buf = Vec::new(); + if let Ok(extra) = reader.read_to_end(&mut extra_buf) { + eprintln!("Warning: Total garbage bytes: {}", n + extra); + } + } + Err(_) => { + // Error reading, probably end of file + } + } + + final_tag = Some(hint); + break; + } + + // Not the final tag, it's a normal hint + if args.detail { + println!( + "{:>6} | {:>12} | {:>12} | {:>14}", + hint_index, + format!("0x{:08X}", hint.hint_type), + hint.length_bytes, + hint.length_aligned + ); + } + + // Update statistics + stats + .entry(hint.hint_type) + .or_insert_with(HintStats::default) + .update(hint.length_bytes); + + hints.push(hint); + hint_index += 1; + } + Ok(None) => { + // End of file without final tag + if args.detail { + println!("{:-<80}", ""); + println!("Total hints processed: {}", hint_index); + } + eprintln!("Warning: Reached end of file without finding final tag (type=1)"); + break; + } + Err(e) => { + // Read error + if hint_index == 0 { + return Err(e).context("Error reading first hint"); + } else { + eprintln!("Error reading hint {}: {}", hint_index, e); + break; + } + } + } + } + + // Process output if needed (inject mode or filter/extract mode) + if let Some(ref output_path) = args.output { + let output_file = File::create(output_path) + .with_context(|| format!("Cannot create output file: {}", output_path.display()))?; + let mut writer = BufWriter::new(output_file); + + // Write the header to output file + writer.write_all(&header)?; + + if let Some(ref inject_chunks) = inject_chunks { + // Inject mode: interleave input chunks with original hints + write_interleaved_hints( + &mut writer, + &hints, + inject_chunks, + args.inject_start, + args.inject_group_size, + args.inject_distance, + &mut stats, + )?; + } else { + // Filter/extract mode: write hints based on filter/extract criteria + for hint in &hints { + let should_write = if let Some(ref extract) = extract_types { + // Extract mode: write only if hint type is in the list + extract.contains(&hint.hint_type) + } else if let Some(ref filter) = filter_types { + // Filter mode: write only if hint type is NOT in the list (exclude) + !filter.contains(&hint.hint_type) + } else { + // No filter or extract, write all hints + true + }; + + if should_write { + hint.write_to(&mut writer)?; + } + } + } + + // Write final tag if present + if let Some(ref tag) = final_tag { + tag.write_to(&mut writer)?; + } + + writer.flush()?; + } + + // Show summary if requested or if detail is not shown + if args.summary || !args.detail { + println!(); + println!("=== Summary ==="); + println!("File: {}", args.input.display()); + println!("Total hints: {}", hint_index); + println!(); + println!( + "{:>12} | {:>8} | {:>14} | {:>12} | {:>12}", + "Type (hex)", "Count", "Total (bytes)", "Min (bytes)", "Max (bytes)" + ); + println!("{:-<80}", ""); + + // Sort by hint type for consistent output + let mut sorted_stats: Vec<_> = stats.iter().collect(); + sorted_stats.sort_by_key(|(type_id, _)| *type_id); + + for (hint_type, stat) in sorted_stats { + println!( + "{:>12} | {:>8} | {:>14} | {:>12} | {:>12}", + format!("0x{:08X}", hint_type), + stat.count, + stat.total_bytes, + stat.min_bytes, + stat.max_bytes + ); + } + } + + // Report output file if created + if let Some(ref output_path) = args.output { + println!(); + if let Some(ref inject_chunks) = inject_chunks { + let inject_type = parse_hint_type(&args.inject_type)?; + println!( + "Hints with injected input written to: {} ({} chunks injected, type: 0x{:08X})", + output_path.display(), + inject_chunks.len(), + inject_type + ); + println!( + " Inject parameters: start={}, chunk_size={}, group_size={}, distance={}", + args.inject_start, + args.inject_chunk_size, + args.inject_group_size, + args.inject_distance + ); + } else if let Some(ref extract) = extract_types { + println!( + "Extracted hints written to: {} (included types: {})", + output_path.display(), + extract.iter().map(|t| format!("0x{:08X}", t)).collect::>().join(", ") + ); + } else if let Some(ref filter) = filter_types { + println!( + "Filtered hints written to: {} (excluded types: {})", + output_path.display(), + filter.iter().map(|t| format!("0x{:08X}", t)).collect::>().join(", ") + ); + } else { + println!("All hints written to: {}", output_path.display()); + } + } + + Ok(()) +} + +fn main() -> Result<()> { + let args = Args::parse(); + process_hints_file(&args) +} From 11d520d272c3e56a2f3b20e5d7cdd8d85f4a6690 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 6 Mar 2026 08:46:12 +0000 Subject: [PATCH 723/782] Adding sdk example for big programs up to 1 GB --- Cargo.lock | 30 +- debug.json | 3 +- examples/Cargo.lock | 42 ++- examples/Cargo.toml | 2 +- examples/big-program/guest/Cargo.lock | 293 ++++++++++++++++++++ examples/big-program/guest/Cargo.toml | 10 + examples/big-program/guest/src/main.rs | 24 ++ examples/big-program/host/Cargo.toml | 18 ++ examples/big-program/host/build.rs | 45 +++ examples/big-program/host/src/main.rs | 46 +++ examples/multiple-programs/host/src/main.rs | 6 +- pil/src/pil_helpers/traces.rs | 2 +- 12 files changed, 490 insertions(+), 31 deletions(-) create mode 100644 examples/big-program/guest/Cargo.lock create mode 100644 examples/big-program/guest/Cargo.toml create mode 100644 examples/big-program/guest/src/main.rs create mode 100644 examples/big-program/host/Cargo.toml create mode 100644 examples/big-program/host/build.rs create mode 100644 examples/big-program/host/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 4597d554b..f0d14be97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1116,7 +1116,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "fields", "num-bigint", @@ -1470,7 +1470,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "cfg-if", "num-bigint", @@ -2764,7 +2764,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "colored", "fields", @@ -3146,7 +3146,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "bincode", "blake3", @@ -3182,7 +3182,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "bincode", "borsh", @@ -3213,7 +3213,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "fields", "itoa", @@ -3226,7 +3226,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "proc-macro2", "quote", @@ -3236,7 +3236,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "crossbeam-channel", "tracing", @@ -3245,7 +3245,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "bincode", "bytemuck", @@ -3257,7 +3257,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "bytemuck", "fields", @@ -4894,9 +4894,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -5611,9 +5611,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] @@ -5709,7 +5709,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "colored", "fields", diff --git a/debug.json b/debug.json index fa544d1cb..5f935055d 100644 --- a/debug.json +++ b/debug.json @@ -3,5 +3,6 @@ "print_to_file": true, "n_vals": 1000000, "fast_mode": false - } + }, + "store_row_info": true } diff --git a/examples/Cargo.lock b/examples/Cargo.lock index d10a8cb0f..2e36afdbd 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -360,6 +360,26 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "big-program-guest" +version = "0.1.0" +dependencies = [ + "byteorder", + "serde", + "sha2", + "ziskos", +] + +[[package]] +name = "big-program-host" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "sha2", + "zisk-sdk", +] + [[package]] name = "bincode" version = "1.3.3" @@ -814,7 +834,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "fields", "num-bigint", @@ -1124,7 +1144,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "cfg-if", "num-bigint", @@ -1979,7 +1999,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "colored", "fields", @@ -2300,7 +2320,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "bincode", "blake3", @@ -2336,7 +2356,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "bincode", "borsh", @@ -2367,7 +2387,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "fields", "itoa", @@ -2380,7 +2400,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "proc-macro2", "quote", @@ -2390,7 +2410,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "crossbeam-channel", "tracing", @@ -2399,7 +2419,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "bincode", "bytemuck", @@ -2411,7 +2431,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "bytemuck", "fields", @@ -4385,7 +4405,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#61886d27038a3c5d0d3334b056d8c66cac659e5b" dependencies = [ "colored", "fields", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index ff3658290..4ea793271 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["sha-hasher/host", "sha-hasher/guest", "multiple-programs/host", "multiple-programs/guest", "multiple-programs/guest_2"] +members = ["sha-hasher/host", "sha-hasher/guest", "multiple-programs/host", "multiple-programs/guest", "multiple-programs/guest_2", "big-program/host", "big-program/guest"] resolver = "2" diff --git a/examples/big-program/guest/Cargo.lock b/examples/big-program/guest/Cargo.lock new file mode 100644 index 000000000..a31235f0c --- /dev/null +++ b/examples/big-program/guest/Cargo.lock @@ -0,0 +1,293 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lib-c" +version = "0.13.1" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "sha-hasher-guest" +version = "0.1.0" +dependencies = [ + "byteorder", + "sha2", + "ziskos", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ziskos" +version = "0.13.1" +dependencies = [ + "cfg-if", + "getrandom", + "lazy_static", + "lib-c", + "num-bigint", + "num-integer", + "num-traits", + "rand", + "static_assertions", + "tiny-keccak", +] diff --git a/examples/big-program/guest/Cargo.toml b/examples/big-program/guest/Cargo.toml new file mode 100644 index 000000000..738c86f86 --- /dev/null +++ b/examples/big-program/guest/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "big-program-guest" +version = "0.1.0" +edition = "2021" + +[dependencies] +byteorder = "1.5.0" +sha2 = "0.10.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } +ziskos = { path = "../../../ziskos/entrypoint" } diff --git a/examples/big-program/guest/src/main.rs b/examples/big-program/guest/src/main.rs new file mode 100644 index 000000000..f59e1c169 --- /dev/null +++ b/examples/big-program/guest/src/main.rs @@ -0,0 +1,24 @@ +// This example program processes large u64 input data (250MB - 1GB+) +// Input size is controlled by INPUT_SIZE_MB environment variable in host build.rs + +#![no_main] +ziskos::entrypoint!(main); + +fn main() { + // Get zero-copy slice directly from INPUT_ADDR (no RAM allocation!) + let data_bytes = ziskos::read_input_slice(); + + // Reinterpret bytes as &[u64] - still zero-copy + let data: &[u64] = unsafe { + core::slice::from_raw_parts(data_bytes.as_ptr() as *const u64, data_bytes.len() / 8) + }; + + // Sum all values - no heap allocation needed + let mut sum: u64 = 0; + for &value in data { + sum = sum.wrapping_add(value); + } + + // Commit the result + ziskos::io::commit(&sum); +} diff --git a/examples/big-program/host/Cargo.toml b/examples/big-program/host/Cargo.toml new file mode 100644 index 000000000..5ce9625c0 --- /dev/null +++ b/examples/big-program/host/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "big-program-host" +version = "0.1.0" +edition = "2021" + +[dependencies] +zisk-sdk = { workspace = true } +anyhow = "1.0" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sha2 = "0.10.8" + +[build-dependencies] +zisk-sdk = { workspace = true } + +[features] +default = [] +packed = [] +gpu = ["zisk-sdk/gpu"] \ No newline at end of file diff --git a/examples/big-program/host/build.rs b/examples/big-program/host/build.rs new file mode 100644 index 000000000..df415660c --- /dev/null +++ b/examples/big-program/host/build.rs @@ -0,0 +1,45 @@ +use std::path::PathBuf; +use zisk_sdk::{build_program, ZiskIO, ZiskStdin}; + +fn main() { + build_program("../guest"); + + // Read input size from environment variable (in MB), default to 250MB + let size_mb: usize = + std::env::var("INPUT_SIZE_MB").ok().and_then(|s| s.parse().ok()).unwrap_or(250); + + // Make the size available to main.rs at compile time + println!("cargo:rustc-env=INPUT_SIZE_MB={}", size_mb); + println!("cargo:rerun-if-env-changed=INPUT_SIZE_MB"); + + // Calculate number of u64 values + // 1MB = 1,048,576 bytes = 131,072 u64 values + const BYTES_PER_MB: usize = 1024 * 1024; + const NUM_U64_PER_MB: usize = BYTES_PER_MB / 8; + let num_u64 = size_mb * NUM_U64_PER_MB; + + println!("Generating {} u64 values (~{}MB of data)...", num_u64, size_mb); + + let mut data = Vec::with_capacity(num_u64); + for i in 0..num_u64 { + // Generate pseudo-random but deterministic data + data.push((i as u64).wrapping_mul(1103515245).wrapping_add(12345)); + } + + println!("Writing input data..."); + let stdin_save = ZiskStdin::new(); + stdin_save.write(&data); + + // Save to file + let path = PathBuf::from(format!("tmp/big_program_input_{}mb.bin", size_mb)); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).unwrap(); + } + stdin_save.save(&path).unwrap(); + + println!("Input data saved to: {}", path.display()); + let file_size_mb = std::fs::metadata(&path).unwrap().len() / 1024 / 1024; + println!("File size: {}MB", file_size_mb); + println!("\nConfigured size: {}MB", size_mb); + println!("To change: INPUT_SIZE_MB=512 cargo build --release"); +} diff --git a/examples/big-program/host/src/main.rs b/examples/big-program/host/src/main.rs new file mode 100644 index 000000000..e9b71001e --- /dev/null +++ b/examples/big-program/host/src/main.rs @@ -0,0 +1,46 @@ +use anyhow::Result; +use std::path::PathBuf; +use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; + +pub const ELF: ElfBinary = include_elf!("big-program-guest"); + +fn main() -> Result<()> { + println!("Starting ZisK Prover Client..."); + + // Read the input size that was configured during build + let size_mb: usize = env!("INPUT_SIZE_MB").parse().unwrap(); + + // Use CARGO_MANIFEST_DIR to get absolute path to the crate directory + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let input_path = + PathBuf::from(manifest_dir).join(format!("tmp/big_program_input_{}mb.bin", size_mb)); + println!("Loading input from: {} ({}MB)", input_path.display(), size_mb); + + let stdin = ZiskStdin::from_file(&input_path)?; + println!("Input loaded successfully"); + + // Create a `ProverClient` method. + let client = ProverClient::builder() + .asm() + .verify_constraints() + .proving_key_path_opt(Some("/home/roger/zisk/build/provingKey".into())) + .build() + .unwrap(); + + let (pk, vkey) = client.setup(&ELF)?; + + // Execute the program using the `ProverClient.execute` method, without generating a proof. + let result = client.execute(&pk, stdin.clone())?; + + println!( + "ZisK has executed program with {} cycles in {:?}", + result.executor_summary.steps, result.total_duration + ); + + println!("Verifying constraints (no proof generation)..."); + client.verify_constraints(&pk, stdin.clone())?; + + println!("\u{2713} VerifyConstraints completed successfully!"); + + Ok(()) +} diff --git a/examples/multiple-programs/host/src/main.rs b/examples/multiple-programs/host/src/main.rs index fdbb24f68..b8f0d502d 100644 --- a/examples/multiple-programs/host/src/main.rs +++ b/examples/multiple-programs/host/src/main.rs @@ -27,7 +27,8 @@ fn main() -> Result<()> { println!( "Program executed successfully: {} cycles in {:.2?}", - result.get_execution_steps(), result.get_duration() + result.get_execution_steps(), + result.get_duration() ); println!("Generating proof for first program..."); @@ -49,7 +50,8 @@ fn main() -> Result<()> { println!( "Program executed successfully: {} cycles in {:.2?}", - result2.get_execution_steps(), result2.get_duration() + result2.get_execution_steps(), + result2.get_duration() ); println!("Generating proof for second program..."); diff --git a/pil/src/pil_helpers/traces.rs b/pil/src/pil_helpers/traces.rs index b9d9a51be..ad332037b 100644 --- a/pil/src/pil_helpers/traces.rs +++ b/pil/src/pil_helpers/traces.rs @@ -16,7 +16,7 @@ use std::fmt; #[allow(dead_code)] type FieldExtension = [F; 3]; -pub const PILOUT_HASH: &str = "d44ddbfff9071ead61b56c5952ed29c5d5c6388c9394c37b973a12f1c3fe5c2d"; +pub const PILOUT_HASH: &str = "34091f7d4eb8b70ad073856a46151341e3beabded43ca6c43eaca854c797df12"; pub const MERKLE_TREE_ARITY: u64 = 4; From 0d3dba66970f40400bb3233e41f4ebc4da22db24 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Fri, 6 Mar 2026 09:50:20 +0000 Subject: [PATCH 724/782] removed debug messages --- emulator-asm/asm-runner/src/inputs_shmem.rs | 18 ------------------ emulator-asm/asm-runner/src/shmem_writer.rs | 12 ++---------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/emulator-asm/asm-runner/src/inputs_shmem.rs b/emulator-asm/asm-runner/src/inputs_shmem.rs index 485e7b745..abe21f317 100644 --- a/emulator-asm/asm-runner/src/inputs_shmem.rs +++ b/emulator-asm/asm-runner/src/inputs_shmem.rs @@ -1,7 +1,6 @@ use std::sync::{Arc, Mutex}; use named_sem::NamedSemaphore; -use tracing::info; use zisk_common::{io::StreamSink, reinterpret_vec}; use zisk_core::MAX_INPUT_SIZE; @@ -55,23 +54,6 @@ impl InputsShmemWriter { } pub fn append_input(&self, inputs: &[u8]) -> Result<()> { - info!("Appending input of size {} bytes to shared memory", inputs.len()); - // Print the first 4 u64 in hexadecimal for debugging - for (i, chunk) in inputs.chunks(8).take(4).enumerate() { - let value = u64::from_le_bytes(chunk.try_into().unwrap_or_default()); - info!("Input chunk {}: 0x{:016x}", i, value); - } - // Printhe two last u64 in hexadecimal for debugging - for (i, chunk) in inputs.chunks(8).rev().take(2).enumerate() { - let value = u64::from_le_bytes(chunk.try_into().unwrap_or_default()); - info!("Input chunk {}: 0x{:016x}", inputs.len() - (i + 1) * 8, value); - } - // Print if the input is divisible by 8tes - if inputs.len() % 8 == 0 { - info!("Input size is divisible by 8 bytes"); - } else { - info!("Input size is not divisible by 8 bytes"); - } self.writer.lock().unwrap().append_input(inputs)?; self.control_writer.inc_inputs_size(inputs.len()); self.sem_avail.lock().unwrap().post()?; diff --git a/emulator-asm/asm-runner/src/shmem_writer.rs b/emulator-asm/asm-runner/src/shmem_writer.rs index 7c77ec02e..bedc6145e 100644 --- a/emulator-asm/asm-runner/src/shmem_writer.rs +++ b/emulator-asm/asm-runner/src/shmem_writer.rs @@ -2,7 +2,6 @@ use libc::{mmap, msync, shm_open, MAP_FAILED, MAP_SHARED, MS_SYNC}; use std::io::{self, Result}; use std::ptr; -use tracing::info; use libc::{c_void, close, munmap, PROT_READ, PROT_WRITE, S_IRUSR, S_IWUSR}; @@ -141,13 +140,6 @@ impl SharedMemoryWriter { )); } - unsafe { - let offset = self.current_ptr.offset_from(self.ptr) as usize; - info!( - "Appending data of size {} bytes to shared memory '{}' at offset {} (current_ptr: {:p})", - byte_size, self.name, offset, self.current_ptr - ); - } unsafe { ptr::copy_nonoverlapping(data.as_ptr() as *const u8, self.current_ptr, byte_size); // Force changes to be flushed to the shared memory @@ -155,9 +147,9 @@ impl SharedMemoryWriter { if msync(self.ptr as *mut _, self.size, MS_SYNC) != 0 { return Err(io::Error::last_os_error()); } - } - self.current_ptr = unsafe { self.current_ptr.add(byte_size) }; + self.current_ptr = self.current_ptr.add(byte_size); + } Ok(()) } From 5f67a86af4ef99b0fc8bcd1e15323c4b12545afc Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 6 Mar 2026 10:53:50 +0100 Subject: [PATCH 725/782] Fix INPUT_ADDR usage in wait_for_input_ready() --- emulator-asm/src/c_provided.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/emulator-asm/src/c_provided.c b/emulator-asm/src/c_provided.c index 1b1edcdf7..c5aa701b0 100644 --- a/emulator-asm/src/c_provided.c +++ b/emulator-asm/src/c_provided.c @@ -284,6 +284,8 @@ int _wait_for_prec_avail (void) // Increment wait counter wait_prec_avail_counter++; + //printf("wait_for_prec_avail() counter=%lu\n", wait_prec_avail_counter); + // Sync control output shared memory so that the writer can see the precompile reads we have // done, and thus update the precompile_written_address if needed if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { @@ -392,6 +394,8 @@ int _wait_for_input_avail (uint64_t required_input_bytes) // Increment wait counter wait_input_avail_counter++; + //printf("wait_for_input_avail() required_input_bytes=%lu counter=%lu\n", required_input_bytes, wait_input_avail_counter); + // Make sure the input available semaphore is reset before checking the condition, // since the caller may have posted it (even several times) before we called sem_wait() while (sem_trywait(sem_input_avail) == 0) {/*printf("Purging sem_input_avail\n");*/}; @@ -408,7 +412,8 @@ int _wait_for_input_avail (uint64_t required_input_bytes) if (*input_written_address > required_input_bytes) { // Sync input shared memory - if (msync((void *)shmem_input_address, MAX_INPUT_SIZE, MS_SYNC) != 0) { + if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) + { printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); @@ -461,7 +466,7 @@ int _wait_for_input_avail (uint64_t required_input_bytes) if (*input_written_address > required_input_bytes) { // Sync input shared memory - if (msync((void *)shmem_input_address, MAX_INPUT_SIZE, MS_SYNC) != 0) { + if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) { printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); fflush(stdout); fflush(stderr); From ae8e82e9f076b4190de4d293f130eb05bcf4bc91 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 6 Mar 2026 11:28:56 +0000 Subject: [PATCH 726/782] Tentative fix mpi --- distributed/crates/worker/src/worker.rs | 16 ++++++++-------- distributed/crates/worker/src/worker_node.rs | 7 +++++-- .../asm-runner/src/asm_services/services.rs | 2 +- sdk/src/prover/asm.rs | 4 ---- sdk/src/prover/backend.rs | 9 --------- sdk/src/prover/emu.rs | 4 ---- sdk/src/prover/mod.rs | 6 ------ 7 files changed, 14 insertions(+), 34 deletions(-) diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index c76974862..52bdd611b 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -641,12 +641,16 @@ impl Worker { /// - `Data` / `End`: enqueues the message into the actor's channel — O(1), non-blocking. /// /// The actor thread owns the reorder buffer and calls `process_hints` in sequence order. - pub async fn route_stream_data(&mut self, stream_data: StreamDataDto) -> Result<()> { + pub async fn route_stream_data( + &mut self, + stream_data: StreamDataDto, + is_first_partition: bool, + ) -> Result<()> { match &stream_data.stream_type { StreamMessageKind::Start => { let job_id = stream_data.job_id.clone(); - self.ensure_hints_processor().await?; + self.ensure_hints_processor(is_first_partition).await?; let hints_processor = self.hints_processor.as_ref().unwrap(); @@ -671,10 +675,6 @@ impl Worker { Ok(()) } - pub fn is_first_partition(&self) -> Result { - self.prover.is_first_partition() - } - pub fn set_partition( &self, total_compute_units: usize, @@ -685,7 +685,7 @@ impl Worker { } /// Lazily initialises the `HintsProcessor` using configuration from the current job. - async fn ensure_hints_processor(&mut self) -> Result<()> { + async fn ensure_hints_processor(&mut self, is_first_partition: bool) -> Result<()> { if self.hints_processor.is_some() { return Ok(()); } @@ -716,7 +716,7 @@ impl Worker { .await .map_err(|e| anyhow::anyhow!("hints processor init panicked: {e}"))??; - processor.set_has_rom_sm(self.prover.is_first_partition()?); + processor.set_has_rom_sm(is_first_partition); self.hints_processor = Some(Arc::new(processor)); diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 82cc693bc..d1ce46282 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -772,7 +772,10 @@ impl WorkerNodeGrpc { } let job = self.worker.current_job().unwrap(); - let current_job_id = job.lock().await.job_id.clone(); + let (current_job_id, is_first_partition) = { + let job_guard = job.lock().await; + (job_guard.job_id.clone(), job_guard.allocation.contains(&0)) + }; let stream_data_dto: StreamDataDto = stream_data.into(); @@ -784,6 +787,6 @@ impl WorkerNodeGrpc { )); } - self.worker.route_stream_data(stream_data_dto).await + self.worker.route_stream_data(stream_data_dto, is_first_partition).await } } diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 0f9fc4a32..d753339dc 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -135,7 +135,7 @@ impl AsmServices { self.start_asm_service(service, trimmed_path, &options, &shm_prefix); } - for service in &Self::SERVICES { + for service in &Self::SERVICES[1..] { Self::wait_for_service_ready( service, Self::port_for(service, self.base_port, self.local_rank), diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 34b192b38..022e4f16c 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -321,10 +321,6 @@ impl ProverEngine for AsmProver { self.core_prover.backend.set_partition(total_compute_units, allocation, rank_id) } - fn is_first_partition(&self) -> Result { - self.core_prover.backend.is_first_partition() - } - fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()> { self.core_prover.backend.register_aggregated_proofs(agg_proofs) } diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index dec8faf96..a0b90f637 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -541,15 +541,6 @@ impl ProverBackend { Ok(proofman.set_partition(total_compute_units, allocation, rank_id)?) } - pub(crate) fn is_first_partition(&self) -> Result { - let proofman = self - .proofman - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Cannot set partition in verifier mode"))?; - - Ok(proofman.is_first_partition()) - } - pub(crate) fn get_execution_info(&self) -> Result<(WitnessInfo, ZiskExecutorTime)> { let proofman = self .proofman diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index 99a2a5be3..07679ca82 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -234,10 +234,6 @@ impl ProverEngine for EmuProver { self.core_prover.backend.set_partition(total_compute_units, allocation, rank_id) } - fn is_first_partition(&self) -> Result { - self.core_prover.backend.is_first_partition() - } - fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()> { self.core_prover.backend.register_aggregated_proofs(agg_proofs) } diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 824bee37d..e867b46bf 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -836,8 +836,6 @@ pub trait ProverEngine { rank_id: usize, ) -> Result<()>; - fn is_first_partition(&self) -> Result; - fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()>; fn aggregate_proofs( @@ -1030,10 +1028,6 @@ impl ZiskProver { self.prover.set_partition(total_compute_units, allocation, rank_id) } - pub fn is_first_partition(&self) -> Result { - self.prover.is_first_partition() - } - pub fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()> { self.prover.register_aggregated_proofs(agg_proofs) } From 12fc8b605ef44dd44732550acc62f07364cc69a7 Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 6 Mar 2026 17:43:14 +0100 Subject: [PATCH 727/782] Implement wait_flag --- emulator-asm/src/c_provided.c | 4 ++++ emulator-asm/src/client.c | 2 ++ emulator-asm/src/globals.c | 13 +++++-------- emulator-asm/src/globals.hpp | 11 ++++------- emulator-asm/src/main.c | 1 - emulator-asm/src/server.c | 35 +++++++++++++++++++++++++++++------ 6 files changed, 44 insertions(+), 22 deletions(-) diff --git a/emulator-asm/src/c_provided.c b/emulator-asm/src/c_provided.c index c5aa701b0..87826e54e 100644 --- a/emulator-asm/src/c_provided.c +++ b/emulator-asm/src/c_provided.c @@ -339,7 +339,9 @@ int _wait_for_prec_avail (void) ts.tv_sec += 5; // 5 seconds timeout //printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + if (wait_flag) *waiting_for_precompile_address = wait_prec_avail_counter << 1; // Leave a mark in shmem that we are waiting; for debugging purposes result = sem_timedwait(sem_prec_avail, &ts); + if (wait_flag) *waiting_for_precompile_address = (wait_prec_avail_counter << 1) + 1; // Clear the mark in shmem that we are waiting; for debugging purposes //printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); if ((result == -1) && (errno != ETIMEDOUT)) { @@ -438,7 +440,9 @@ int _wait_for_input_avail (uint64_t required_input_bytes) ts.tv_sec += 5; // 5 seconds timeout //printf("_wait_for_input_avail() calling sem_wait input_written_address=%lu required_input_bytes=%lu\n", *input_written_address, required_input_bytes); + if (wait_flag) *waiting_for_input_address = wait_input_avail_counter << 1; // Leave a mark in shmem that we are waiting; for debugging purposes result = sem_timedwait(sem_input_avail, &ts); + if (wait_flag) *waiting_for_input_address = (wait_input_avail_counter << 1) + 1; // Clear the mark in shmem that we are waiting; for debugging purposes //printf("_wait_for_input_avail() called sem_wait input_written_address=%lu required_input_bytes=%lu\n", *input_written_address, required_input_bytes); if ((result == -1) && (errno != ETIMEDOUT)) { diff --git a/emulator-asm/src/client.c b/emulator-asm/src/client.c index 640f7e0b7..065fe8bb8 100644 --- a/emulator-asm/src/client.c +++ b/emulator-asm/src/client.c @@ -20,6 +20,8 @@ #include "globals.hpp" #include "emu.hpp" +void * shmem_input_address = NULL; + /**********/ /* CLIENT */ /**********/ diff --git a/emulator-asm/src/globals.c b/emulator-asm/src/globals.c index 333c60c5d..d6507a034 100644 --- a/emulator-asm/src/globals.c +++ b/emulator-asm/src/globals.c @@ -25,6 +25,8 @@ bool do_shutdown = false; uint64_t number_of_mt_requests = 1; uint16_t port = 0; uint64_t chunk_player_address = 0; +bool wait_flag = true; + char precompile_file_name[4096] = {0}; char shmem_control_input_name[128] = {0}; char shmem_control_output_name[128] = {0}; @@ -59,8 +61,6 @@ uint64_t duration; // Input shared memory int shmem_input_fd = -1; -uint64_t shmem_input_size = 0; -void * shmem_input_address = NULL; // Output trace shared memory int shmem_output_fd = -1; @@ -71,9 +71,6 @@ int shmem_mt_fd = -1; // Chunk done semaphore: notifies the caller when a new chunk has been processed sem_t * sem_chunk_done = NULL; -// Shutdown done semaphore: notifies the caller when a shutdown has been processed -sem_t * sem_shutdown_done = NULL; - /**************************/ /* PRECOMPILE AND CONTROL */ /**************************/ @@ -82,7 +79,6 @@ uint64_t * precompile_results_address = NULL; // Precompile results shared memory int shmem_precompile_fd = -1; -uint64_t shmem_precompile_size = 0; void * shmem_precompile_address = NULL; // Precompile results semaphores @@ -101,6 +97,8 @@ volatile uint64_t * input_written_address = NULL; int shmem_control_output_fd = -1; uint64_t * shmem_control_output_address = NULL; volatile uint64_t * precompile_read_address = NULL; +volatile uint64_t * waiting_for_precompile_address = NULL; +volatile uint64_t * waiting_for_input_address = NULL; /**************/ /* TRACE SIZE */ @@ -136,5 +134,4 @@ uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; // Used for trace generation, /* CHUNK SIZE */ /**************/ -uint64_t chunk_size = CHUNK_SIZE; -uint64_t chunk_size_mask = CHUNK_SIZE - 1; \ No newline at end of file +uint64_t chunk_size = CHUNK_SIZE; \ No newline at end of file diff --git a/emulator-asm/src/globals.hpp b/emulator-asm/src/globals.hpp index d9db361e2..0d3f09b87 100644 --- a/emulator-asm/src/globals.hpp +++ b/emulator-asm/src/globals.hpp @@ -30,6 +30,8 @@ extern bool do_shutdown; // If true, the client will perform a shutdown request extern uint64_t number_of_mt_requests; // Loop to send this number of minimal trace requests extern uint16_t port; // Service TCP port extern uint64_t chunk_player_address; // Chunk player address, used for generation methods that use the chunk player, i.e. gen_method=8 or gen_method=10 +extern bool wait_flag; // If true, the shmem will get a flag set to 1 if we are waiting for a semaphore, and set it back to 0 when we are not waiting anymore. This can be used for debugging purposes to know if the assembly code is waiting for a semaphore or not. + extern char precompile_file_name[4096]; // Precompile results file name (used by client) extern char shmem_control_input_name[128]; extern char shmem_control_output_name[128]; @@ -88,8 +90,6 @@ extern uint64_t duration; // Input shared memory extern int shmem_input_fd; -extern uint64_t shmem_input_size; -extern void * shmem_input_address; // Output trace shared memory extern int shmem_output_fd; @@ -100,9 +100,6 @@ extern int shmem_mt_fd; // Chunk done semaphore: notifies the caller when a new chunk has been processed extern sem_t * sem_chunk_done; -// Shutdown done semaphore: notifies the caller when a shutdown has been processed -extern sem_t * sem_shutdown_done; - /**************************/ /* PRECOMPILE AND CONTROL */ /**************************/ @@ -111,7 +108,6 @@ extern uint64_t * precompile_results_address; // Precompile results shared memory extern int shmem_precompile_fd; -extern uint64_t shmem_precompile_size; extern void * shmem_precompile_address; // Precompile results semaphores @@ -130,6 +126,8 @@ extern volatile uint64_t * input_written_address; extern int shmem_control_output_fd; extern uint64_t * shmem_control_output_address; extern volatile uint64_t * precompile_read_address; +extern volatile uint64_t * waiting_for_precompile_address; +extern volatile uint64_t * waiting_for_input_address; /**************/ /* TRACE SIZE */ @@ -166,6 +164,5 @@ extern uint64_t * pOutputTrace; // Used for trace generation, i.e. assembly code /**************/ extern uint64_t chunk_size; -extern uint64_t chunk_size_mask; #endif // EMULATOR_ASM_GLOBALS_HPP \ No newline at end of file diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index fe1708be2..a44002c12 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -90,7 +90,6 @@ void set_chunk_size (uint64_t new_chunk_size) exit(-1); } chunk_size = new_chunk_size; - chunk_size_mask = chunk_size - 1; trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; } diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index a146a4020..4a214038a 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -30,6 +30,9 @@ uint64_t histogram_size = 0; uint64_t bios_size = 0; uint64_t program_size = 0; +// Shutdown done semaphore: notifies the caller when a shutdown has been processed +sem_t * sem_shutdown_done = NULL; + void server_setup (void) { assert(server); @@ -389,6 +392,8 @@ void server_setup (void) } shmem_control_output_address = (uint64_t *)pControl; precompile_read_address = &shmem_control_output_address[0]; + waiting_for_precompile_address = &shmem_control_output_address[1]; + waiting_for_input_address = &shmem_control_output_address[2]; if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); /*******/ @@ -611,6 +616,18 @@ void server_reset_trace (void) // Reset trace used size trace_used_size = 0; } + + // Reset flags + if (wait_flag) + { + *waiting_for_precompile_address = 0; + *waiting_for_input_address = 0; + } + + // Reset counters + wait_prec_avail_counter = 0; + wait_input_avail_counter = 0; + print_pc_counter = 0; } void server_run (void) @@ -940,20 +957,26 @@ void server_cleanup (void) { printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); } - result = shm_unlink(shmem_control_input_name); - if (result == -1) + if (!wait_flag) { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + result = shm_unlink(shmem_control_input_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + } } result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); if (result == -1) { printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); } - result = shm_unlink(shmem_control_output_name); - if (result == -1) + if (!wait_flag) { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + result = shm_unlink(shmem_control_output_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + } } // Cleanup trace From ac4414db4fbd146c3adeee71b867e315b984d3be Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sat, 7 Mar 2026 06:59:27 +0000 Subject: [PATCH 728/782] improve ASM services start function --- .../asm-runner/src/asm_services/services.rs | 99 +++++++++---------- 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index d753339dc..9679557bc 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -11,9 +11,9 @@ use std::{ net::TcpStream, path::Path, process::Command, - thread::sleep, time::{Duration, Instant}, }; +use tracing::debug; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AsmService { @@ -72,7 +72,6 @@ impl AsmServices { ziskemuasm_path: &Path, mut options: AsmRunnerOptions, ) -> Result<()> { - // ! TODO Remove this when we have a proper way to find the path let path_str = ziskemuasm_path.to_string_lossy(); let trimmed_path = &path_str[..path_str.len().saturating_sub(7)]; @@ -97,55 +96,33 @@ impl AsmServices { self.local_rank, ); - let service = &Self::SERVICES[0]; - tracing::debug!( - ">>> [{}] Starting ASM service: {} on port {}", - self.world_rank, - service, - Self::port_for(service, self.base_port, self.local_rank) - ); + let mut pending_wait = Vec::new(); - // First service has to create the shared memory, the rest can use it options.share_input_shmem = true; - options.open_input_shmem = false; - self.start_asm_service(service, trimmed_path, &options, &shm_prefix); - Self::wait_for_service_ready( - service, - Self::port_for(service, self.base_port, self.local_rank), - ); - tracing::debug!( - ">>> [{}] ASM service {} is ready on port {}", - self.world_rank, - service, - Self::port_for(service, self.base_port, self.local_rank) - ); + for (i, service) in Self::SERVICES.iter().enumerate() { + let port = Self::port_for(service, self.base_port, self.local_rank); + let wr = self.world_rank; - for service in &Self::SERVICES[1..] { - tracing::debug!( - ">>> [{}] Starting ASM service: {} on port {}", - self.world_rank, - service, - Self::port_for(service, self.base_port, self.local_rank) - ); + debug!(">>> [{}] Starting ASM service: {} on port {}", wr, service, port); - options.share_input_shmem = true; - options.open_input_shmem = true; + options.open_input_shmem = i != 0; self.start_asm_service(service, trimmed_path, &options, &shm_prefix); + + if i == 0 { + // For the first service, wait until it is ready before starting the others, + // since it may initialize the shared memory used by the rest. + Self::wait_for_service_ready(self.world_rank, service, port)?; + } else { + // For the other services, we can start them in parallel and wait for them after + pending_wait.push((service, port)); + } } - for service in &Self::SERVICES[1..] { - Self::wait_for_service_ready( - service, - Self::port_for(service, self.base_port, self.local_rank), - ); - tracing::debug!( - ">>> [{}] ASM service {} is ready on port {}", - self.world_rank, - service, - Self::port_for(service, self.base_port, self.local_rank) - ); + // Wait for the remaining services to be ready + for (service, port) in pending_wait { + Self::wait_for_service_ready(self.world_rank, service, port)?; } // Ping status for all services @@ -173,22 +150,40 @@ impl AsmServices { Ok(()) } - fn wait_for_service_ready(service: &AsmService, port: u16) { - let addr = format!("127.0.0.1:{port}"); - let timeout = Duration::from_secs(60); - let retry_delay = Duration::from_millis(100); - let start = Instant::now(); + fn wait_for_service_ready(world_rank: i32, service: &AsmService, port: u16) -> Result<()> { + const TIMEOUT: Duration = Duration::from_secs(60); + const CONNECT_TIMEOUT: Duration = Duration::from_millis(100); + const LOG_INTERVAL: Duration = Duration::from_secs(5); + + let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port)); - while start.elapsed() < timeout { - match TcpStream::connect(&addr) { + let start = Instant::now(); + let mut last_log = start; + while start.elapsed() < TIMEOUT { + match TcpStream::connect_timeout(&addr, CONNECT_TIMEOUT) { Ok(_) => { - return; + debug!( + ">>> [{}] ASM service {} is ready on port {}", + world_rank, service, port + ); + return Ok(()); + } + Err(_) => { + if last_log.elapsed() >= LOG_INTERVAL { + debug!( + ">>> [{}] Waiting for ASM service {} on port {} ({:.0}s elapsed), retrying...", + world_rank, + service, + port, + start.elapsed().as_secs_f32() + ); + last_log = Instant::now(); + } } - Err(_) => sleep(retry_delay), } } - panic!("Timeout: service `{service}` not ready on {addr}"); + Err(anyhow::anyhow!("Timeout: service `{service}` not ready on {addr}")) } fn start_asm_service( From 2c68472943b7f45b06e5006dc2091d91b1cd676e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:35:25 +0000 Subject: [PATCH 729/782] added redirect-output-to-file flag to execute CLI command --- cli/src/commands/execute.rs | 6 ++++++ emulator-asm/asm-runner/src/asm_runner.rs | 11 +++++++++++ sdk/src/builder.rs | 13 +++++++++++++ sdk/src/prover/asm.rs | 10 +++++++++- 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index 0abe59a0c..cd5c14325 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -67,6 +67,11 @@ pub struct ZiskExecute { #[clap(short = 'u', long, conflicts_with = "emulator")] pub unlock_mapped_memory: bool, + /// Redirect ASM emulator output to file + /// This option is mutually exclusive with `--emulator` + #[clap(long, conflicts_with = "emulator", default_value_t = false)] + pub asm_out_file: bool, + /// Verbosity (-v, -vv) #[arg(short = 'v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] pub verbose: u8, // Using u8 to hold the number of `-v` @@ -160,6 +165,7 @@ impl ZiskExecute { .no_auto_setup(self.no_auto_setup) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) + .asm_out_file(self.asm_out_file) .print_command_info() .build()?; diff --git a/emulator-asm/asm-runner/src/asm_runner.rs b/emulator-asm/asm-runner/src/asm_runner.rs index 08f8fb417..66ea0846d 100644 --- a/emulator-asm/asm-runner/src/asm_runner.rs +++ b/emulator-asm/asm-runner/src/asm_runner.rs @@ -40,6 +40,7 @@ pub struct AsmRunnerOptions { pub local_rank: i32, pub base_port: Option, pub unlock_mapped_memory: bool, + pub asm_out_file: bool, pub share_input_shmem: bool, pub open_input_shmem: bool, } @@ -63,6 +64,7 @@ impl AsmRunnerOptions { local_rank: 0, base_port: None, unlock_mapped_memory: false, + asm_out_file: false, share_input_shmem: false, open_input_shmem: false, } @@ -118,6 +120,11 @@ impl AsmRunnerOptions { self } + pub fn with_asm_out_file(mut self, value: bool) -> Self { + self.asm_out_file = value; + self + } + pub fn with_share_input_shmem(mut self, value: bool) -> Self { self.share_input_shmem = value; self @@ -151,6 +158,10 @@ impl AsmRunnerOptions { command.arg("-u"); } + if self.asm_out_file { + command.arg("--redirect-output-to-file"); + } + command.arg("--shm_prefix").arg(shm_prefix); match asm_service { diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 50fc6a848..1b81c4f32 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -59,6 +59,7 @@ pub struct ProverClientBuilder { asm_path: Option, base_port: Option, unlock_mapped_memory: bool, + asm_out_file: bool, no_auto_setup: bool, // Prove-specific fields (only available when Operation = Prove) @@ -235,6 +236,12 @@ impl ProverClientBuilder { self.unlock_mapped_memory = unlock; self } + + #[must_use] + pub fn asm_out_file(mut self, asm_out_file: bool) -> Self { + self.asm_out_file = asm_out_file; + self + } } // Prove-specific methods (available for any operation state - will use defaults if not in Prove mode) @@ -413,6 +420,7 @@ impl ProverClientBuilder { self.shared_tables, self.base_port, self.unlock_mapped_memory, + self.asm_out_file, self.no_auto_setup, self.gpu_params, self.logging_config, @@ -453,6 +461,8 @@ impl From> for ProverClientBuilder { asm_path: None, base_port: None, unlock_mapped_memory: false, + asm_out_file: false, + no_auto_setup: false, _backend: std::marker::PhantomData, @@ -482,6 +492,7 @@ impl From> for ProverClientBuilder { asm_path: builder.asm_path, base_port: builder.base_port, unlock_mapped_memory: builder.unlock_mapped_memory, + asm_out_file: builder.asm_out_file, no_auto_setup: builder.no_auto_setup, _backend: std::marker::PhantomData, @@ -513,6 +524,7 @@ impl From> asm_path: builder.asm_path, base_port: builder.base_port, unlock_mapped_memory: builder.unlock_mapped_memory, + asm_out_file: builder.asm_out_file, no_auto_setup: builder.no_auto_setup, _backend: std::marker::PhantomData, @@ -542,6 +554,7 @@ impl From> for ProverClientBuilder, unlock_mapped_memory: bool, + asm_out_file: bool, no_auto_setup: bool, gpu_params: ParamsGPU, logging_config: Option, @@ -64,6 +65,7 @@ impl AsmProver { shared_tables, base_port, unlock_mapped_memory, + asm_out_file, no_auto_setup, gpu_params, logging_config, @@ -112,6 +114,7 @@ impl ProverEngine for AsmProver { let local_rank = self.core_prover.rank_info.local_rank; let n_processes = self.core_prover.rank_info.n_processes; let unlock_mapped_memory = self.core_prover.unlock_mapped_memory; + let asm_out_file = self.core_prover.asm_out_file; let verbose_mode = self.core_prover.verbose; let rank_info = self.core_prover.rank_info.clone(); let base_port = Some(AsmServices::port_base_offset( @@ -174,7 +177,8 @@ impl ProverEngine for AsmProver { .with_local_rank(local_rank) .with_verbose(verbose_mode == VerboseMode::Debug) .with_metrics(verbose_mode == VerboseMode::Debug) - .with_unlock_mapped_memory(unlock_mapped_memory); + .with_unlock_mapped_memory(unlock_mapped_memory) + .with_asm_out_file(asm_out_file); asm_services.start_asm_services(&asm_mt_path, asm_runner_options)?; timer_stop_and_log_info!(STARTING_ASM_MICROSERVICES); @@ -354,6 +358,7 @@ pub struct AsmCoreProver { rank_info: RankInfo, base_port: Option, unlock_mapped_memory: bool, + asm_out_file: bool, verbose: VerboseMode, no_auto_setup: bool, } @@ -370,6 +375,7 @@ impl AsmCoreProver { shared_tables: bool, base_port: Option, unlock_mapped_memory: bool, + asm_out_file: bool, no_auto_setup: bool, gpu_params: ParamsGPU, logging_config: Option, @@ -428,6 +434,7 @@ impl AsmCoreProver { rank_info, base_port, unlock_mapped_memory, + asm_out_file, verbose: verbose.into(), no_auto_setup, }) @@ -442,6 +449,7 @@ impl AsmCoreProver { rank_info: RankInfo { world_rank: 0, local_rank: 0, n_processes: 1 }, base_port: None, unlock_mapped_memory: false, + asm_out_file: false, verbose: VerboseMode::Info, no_auto_setup: false, }) From 089ce00cbfcd7d9ad71374ad2ba70044c5185ef7 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 9 Mar 2026 07:07:31 +0000 Subject: [PATCH 730/782] added asm_out_file to prove, verify constraints and stats CLI commands and zisk-worker distributed command --- cli/src/commands/prove.rs | 6 ++++++ cli/src/commands/stats.rs | 6 ++++++ cli/src/commands/verify_constraints.rs | 6 ++++++ distributed/crates/worker/src/cli/main.rs | 6 ++++++ distributed/crates/worker/src/config.rs | 2 ++ distributed/crates/worker/src/worker.rs | 5 +++++ 6 files changed, 31 insertions(+) diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index 9d3e5e560..e56144efb 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -84,6 +84,11 @@ pub struct ZiskProve { #[clap(short = 'u', long, conflicts_with = "emulator")] pub unlock_mapped_memory: bool, + /// Redirect ASM emulator output to file + /// This option is mutually exclusive with `--emulator` + #[clap(long, conflicts_with = "emulator", default_value_t = false)] + pub asm_out_file: bool, + /// Verbosity (-v, -vv) #[arg(short ='v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] pub verbose: u8, // Using u8 to hold the number of `-v` @@ -270,6 +275,7 @@ impl ZiskProve { .base_port_opt(self.port) .no_auto_setup(self.no_auto_setup) .unlock_mapped_memory(self.unlock_mapped_memory) + .asm_out_file(self.asm_out_file) .gpu(gpu_params) .print_command_info() .build()?; diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index c47eb728b..255972376 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -66,6 +66,11 @@ pub struct ZiskStats { #[clap(short = 'u', long, conflicts_with = "emulator")] pub unlock_mapped_memory: bool, + /// Redirect ASM emulator output to file + /// This option is mutually exclusive with `--emulator` + #[clap(long, conflicts_with = "emulator", default_value_t = false)] + pub asm_out_file: bool, + /// Verbosity (-v, -vv) #[arg(short = 'v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] pub verbose: u8, // Using u8 to hold the number of `-v` @@ -193,6 +198,7 @@ impl ZiskStats { .no_auto_setup(self.no_auto_setup) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) + .asm_out_file(self.asm_out_file) .print_command_info() .build()?; diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index d2bfb7312..a4f9e28f0 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -63,6 +63,11 @@ pub struct ZiskVerifyConstraints { #[clap(short = 'u', long, conflicts_with = "emulator")] pub unlock_mapped_memory: bool, + /// Redirect ASM emulator output to file + /// This option is mutually exclusive with `--emulator` + #[clap(long, conflicts_with = "emulator", default_value_t = false)] + pub asm_out_file: bool, + #[clap(short = 'n', long, default_value_t = false)] pub no_auto_setup: bool, @@ -177,6 +182,7 @@ impl ZiskVerifyConstraints { .no_auto_setup(self.no_auto_setup) .base_port_opt(self.port) .unlock_mapped_memory(self.unlock_mapped_memory) + .asm_out_file(self.asm_out_file) .print_command_info() .build()?; diff --git a/distributed/crates/worker/src/cli/main.rs b/distributed/crates/worker/src/cli/main.rs index c4c5d38ab..631588938 100644 --- a/distributed/crates/worker/src/cli/main.rs +++ b/distributed/crates/worker/src/cli/main.rs @@ -80,6 +80,11 @@ struct Cli { #[clap(long, conflicts_with = "emulator")] pub unlock_mapped_memory: bool, + /// Redirect ASM emulator output to file + /// This option is mutually exclusive with `--emulator` + #[clap(long, conflicts_with = "emulator", default_value_t = false)] + pub asm_out_file: bool, + /// Verbosity (-v, -vv) #[arg(short ='v', long, action = clap::ArgAction::Count, help = "Increase verbosity level")] pub verbose: u8, // Using u8 to hold the number of `-v` @@ -138,6 +143,7 @@ async fn main() -> Result<()> { proving_key: cli.proving_key.clone(), asm_port: cli.asm_port, unlock_mapped_memory: cli.unlock_mapped_memory, + asm_out_file: cli.asm_out_file, verbose: cli.verbose, debug: cli.debug.clone(), verify_constraints: cli.verify_constraints, diff --git a/distributed/crates/worker/src/config.rs b/distributed/crates/worker/src/config.rs index cb845ce1b..169138147 100644 --- a/distributed/crates/worker/src/config.rs +++ b/distributed/crates/worker/src/config.rs @@ -151,6 +151,7 @@ pub struct ProverServiceConfigDto { pub proving_key: Option, pub asm_port: Option, pub unlock_mapped_memory: bool, + pub asm_out_file: bool, pub verbose: u8, pub debug: Option>, pub verify_constraints: bool, @@ -174,6 +175,7 @@ impl Default for ProverServiceConfigDto { proving_key: None, asm_port: None, unlock_mapped_memory: false, + asm_out_file: false, verbose: 0, debug: None, verify_constraints: false, diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 52bdd611b..fc811c931 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -99,6 +99,9 @@ pub struct ProverConfig { /// Flag to unlock mapped memory pub unlock_mapped_memory: bool, + /// Flag to redirect ASM emulator output to file + pub asm_out_file: bool, + /// Flag to verify constraints pub verify_constraints: bool, @@ -230,6 +233,7 @@ impl ProverConfig { debug_info, asm_port: prover_service_config.asm_port, unlock_mapped_memory: prover_service_config.unlock_mapped_memory, + asm_out_file: prover_service_config.asm_out_file, verify_constraints: prover_service_config.verify_constraints, aggregation: prover_service_config.aggregation, gpu_params, @@ -321,6 +325,7 @@ impl Worker { .asm_path_opt(prover_config.asm.clone()) .base_port_opt(prover_config.asm_port) .unlock_mapped_memory(prover_config.unlock_mapped_memory) + .asm_out_file(prover_config.asm_out_file) .gpu(prover_config.gpu_params.clone()) .build()?, ); From 32b2fe2019f80a81d8f8bb28e433d71c61b5f9be Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 9 Mar 2026 07:43:47 +0000 Subject: [PATCH 731/782] added conflicts between hints and inputs in CLI commands --- cli/src/commands/execute.rs | 4 ++-- cli/src/commands/prove.rs | 4 ++-- cli/src/commands/stats.rs | 4 ++-- cli/src/commands/verify_constraints.rs | 4 ++-- distributed/crates/coordinator/src/cli/main.rs | 16 ++++++++++++---- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/cli/src/commands/execute.rs b/cli/src/commands/execute.rs index cd5c14325..990d41268 100644 --- a/cli/src/commands/execute.rs +++ b/cli/src/commands/execute.rs @@ -36,11 +36,11 @@ pub struct ZiskExecute { pub emulator: bool, /// Input path - #[clap(short = 'i', long, alias = "input")] + #[clap(short = 'i', long, alias = "input", conflicts_with = "hints")] pub inputs: Option, /// Precompiles Hints path - #[clap(short = 'H', long)] + #[clap(short = 'H', long, conflicts_with = "inputs")] pub hints: Option, /// Force ROM setup diff --git a/cli/src/commands/prove.rs b/cli/src/commands/prove.rs index e56144efb..26ee0778d 100644 --- a/cli/src/commands/prove.rs +++ b/cli/src/commands/prove.rs @@ -37,11 +37,11 @@ pub struct ZiskProve { pub emulator: bool, /// Input path - #[clap(short = 'i', long, alias = "input")] + #[clap(short = 'i', long, alias = "input", conflicts_with = "hints")] pub inputs: Option, /// Precompiles Hints path - #[clap(short = 'H', long)] + #[clap(short = 'H', long, conflicts_with = "inputs")] pub hints: Option, /// Setup folder path diff --git a/cli/src/commands/stats.rs b/cli/src/commands/stats.rs index 255972376..756e028b1 100644 --- a/cli/src/commands/stats.rs +++ b/cli/src/commands/stats.rs @@ -39,11 +39,11 @@ pub struct ZiskStats { pub emulator: bool, /// Input path - #[clap(short = 'i', long, alias = "input")] + #[clap(short = 'i', long, alias = "input", conflicts_with = "hints")] pub inputs: Option, /// Precompiles Hints path - #[clap(short = 'H', long)] + #[clap(short = 'H', long, conflicts_with = "inputs")] pub hints: Option, /// Setup folder path diff --git a/cli/src/commands/verify_constraints.rs b/cli/src/commands/verify_constraints.rs index a4f9e28f0..1fc5beb4e 100644 --- a/cli/src/commands/verify_constraints.rs +++ b/cli/src/commands/verify_constraints.rs @@ -36,11 +36,11 @@ pub struct ZiskVerifyConstraints { pub emulator: bool, /// Input path - #[clap(short = 'i', long, alias = "input")] + #[clap(short = 'i', long, alias = "input", conflicts_with = "hints")] pub inputs: Option, /// Precompiles Hints path - #[clap(short = 'H', long)] + #[clap(short = 'H', long, conflicts_with = "inputs")] pub hints: Option, /// Setup folder path diff --git a/distributed/crates/coordinator/src/cli/main.rs b/distributed/crates/coordinator/src/cli/main.rs index 1020e5e05..db7107dc2 100644 --- a/distributed/crates/coordinator/src/cli/main.rs +++ b/distributed/crates/coordinator/src/cli/main.rs @@ -69,19 +69,27 @@ enum ZiskCoordinatorCommands { data_id: Option, /// Path to the input file - #[arg(long, help = "Path to the input file for proof generation")] + #[arg( + long, + help = "Path to the input file for proof generation", + conflicts_with = "hints_uri" + )] inputs_uri: Option, /// Precompiles Hints path - #[arg(long, help = "Path to the precompiles hints file for proof generation")] + #[arg( + long, + help = "Path to the precompiles hints file for proof generation", + conflicts_with = "inputs_uri" + )] hints_uri: Option, /// Whether to send the input data directly - #[clap(short = 'x', long, default_value_t = false)] + #[clap(short = 'x', long, default_value_t = false, conflicts_with = "hints_uri")] direct_inputs: bool, /// Whether to send the input data directly - #[clap(long, default_value_t = false)] + #[clap(long, default_value_t = false, conflicts_with = "inputs_uri")] stream_hints: bool, /// Compute capacity needed to generate the proof From 7005feb5fe04c402b2fef0b10b6674f6ac135c61 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 9 Mar 2026 08:31:59 +0000 Subject: [PATCH 732/782] added error trace when ASM services timeout --- emulator-asm/asm-runner/src/asm_services/services.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 9679557bc..4c2dfbead 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -183,6 +183,13 @@ impl AsmServices { } } + tracing::error!( + ">>> [{}] Timeout waiting for ASM service {} to be ready on port {} after {:?}", + world_rank, + service, + port, + start.elapsed() + ); Err(anyhow::anyhow!("Timeout: service `{service}` not ready on {addr}")) } From 4e4fe8483922934ece9082f744331419958e1eb2 Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 9 Mar 2026 09:26:17 +0000 Subject: [PATCH 733/782] improved services.rs code and added debug info --- .../asm-runner/src/asm_services/services.rs | 106 +++++++++--------- 1 file changed, 54 insertions(+), 52 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 0f9fc4a32..4c2dfbead 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -11,9 +11,9 @@ use std::{ net::TcpStream, path::Path, process::Command, - thread::sleep, time::{Duration, Instant}, }; +use tracing::debug; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AsmService { @@ -72,7 +72,6 @@ impl AsmServices { ziskemuasm_path: &Path, mut options: AsmRunnerOptions, ) -> Result<()> { - // ! TODO Remove this when we have a proper way to find the path let path_str = ziskemuasm_path.to_string_lossy(); let trimmed_path = &path_str[..path_str.len().saturating_sub(7)]; @@ -97,55 +96,33 @@ impl AsmServices { self.local_rank, ); - let service = &Self::SERVICES[0]; - tracing::debug!( - ">>> [{}] Starting ASM service: {} on port {}", - self.world_rank, - service, - Self::port_for(service, self.base_port, self.local_rank) - ); + let mut pending_wait = Vec::new(); - // First service has to create the shared memory, the rest can use it options.share_input_shmem = true; - options.open_input_shmem = false; - self.start_asm_service(service, trimmed_path, &options, &shm_prefix); - Self::wait_for_service_ready( - service, - Self::port_for(service, self.base_port, self.local_rank), - ); - tracing::debug!( - ">>> [{}] ASM service {} is ready on port {}", - self.world_rank, - service, - Self::port_for(service, self.base_port, self.local_rank) - ); + for (i, service) in Self::SERVICES.iter().enumerate() { + let port = Self::port_for(service, self.base_port, self.local_rank); + let wr = self.world_rank; - for service in &Self::SERVICES[1..] { - tracing::debug!( - ">>> [{}] Starting ASM service: {} on port {}", - self.world_rank, - service, - Self::port_for(service, self.base_port, self.local_rank) - ); + debug!(">>> [{}] Starting ASM service: {} on port {}", wr, service, port); - options.share_input_shmem = true; - options.open_input_shmem = true; + options.open_input_shmem = i != 0; self.start_asm_service(service, trimmed_path, &options, &shm_prefix); + + if i == 0 { + // For the first service, wait until it is ready before starting the others, + // since it may initialize the shared memory used by the rest. + Self::wait_for_service_ready(self.world_rank, service, port)?; + } else { + // For the other services, we can start them in parallel and wait for them after + pending_wait.push((service, port)); + } } - for service in &Self::SERVICES { - Self::wait_for_service_ready( - service, - Self::port_for(service, self.base_port, self.local_rank), - ); - tracing::debug!( - ">>> [{}] ASM service {} is ready on port {}", - self.world_rank, - service, - Self::port_for(service, self.base_port, self.local_rank) - ); + // Wait for the remaining services to be ready + for (service, port) in pending_wait { + Self::wait_for_service_ready(self.world_rank, service, port)?; } // Ping status for all services @@ -173,22 +150,47 @@ impl AsmServices { Ok(()) } - fn wait_for_service_ready(service: &AsmService, port: u16) { - let addr = format!("127.0.0.1:{port}"); - let timeout = Duration::from_secs(60); - let retry_delay = Duration::from_millis(100); - let start = Instant::now(); + fn wait_for_service_ready(world_rank: i32, service: &AsmService, port: u16) -> Result<()> { + const TIMEOUT: Duration = Duration::from_secs(60); + const CONNECT_TIMEOUT: Duration = Duration::from_millis(100); + const LOG_INTERVAL: Duration = Duration::from_secs(5); + + let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port)); - while start.elapsed() < timeout { - match TcpStream::connect(&addr) { + let start = Instant::now(); + let mut last_log = start; + while start.elapsed() < TIMEOUT { + match TcpStream::connect_timeout(&addr, CONNECT_TIMEOUT) { Ok(_) => { - return; + debug!( + ">>> [{}] ASM service {} is ready on port {}", + world_rank, service, port + ); + return Ok(()); + } + Err(_) => { + if last_log.elapsed() >= LOG_INTERVAL { + debug!( + ">>> [{}] Waiting for ASM service {} on port {} ({:.0}s elapsed), retrying...", + world_rank, + service, + port, + start.elapsed().as_secs_f32() + ); + last_log = Instant::now(); + } } - Err(_) => sleep(retry_delay), } } - panic!("Timeout: service `{service}` not ready on {addr}"); + tracing::error!( + ">>> [{}] Timeout waiting for ASM service {} to be ready on port {} after {:?}", + world_rank, + service, + port, + start.elapsed() + ); + Err(anyhow::anyhow!("Timeout: service `{service}` not ready on {addr}")) } fn start_asm_service( From 675b843dea301502686b0d1b70221f8938aea8cd Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Mon, 9 Mar 2026 09:28:47 +0000 Subject: [PATCH 734/782] sequentiallize asm startup --- emulator-asm/asm-runner/src/asm_services/services.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 4c2dfbead..146404bac 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -110,14 +110,14 @@ impl AsmServices { self.start_asm_service(service, trimmed_path, &options, &shm_prefix); - if i == 0 { + // if i == 0 { // For the first service, wait until it is ready before starting the others, // since it may initialize the shared memory used by the rest. Self::wait_for_service_ready(self.world_rank, service, port)?; - } else { + // } else { // For the other services, we can start them in parallel and wait for them after - pending_wait.push((service, port)); - } + // pending_wait.push((service, port)); + // } } // Wait for the remaining services to be ready From 092bd8b5789df810a08bdade08f46084771cdf75 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Mon, 9 Mar 2026 15:23:46 +0100 Subject: [PATCH 735/782] fix assemblymapping bug, reduce memory mem plan and count --- emulator-asm/Makefile | 11 +++++++++-- .../asm-runner/src/asm_services/services.rs | 10 +++++----- emulator-asm/src/server.c | 19 ++++++++++++++++++- emulator-asm/src/trace.c | 19 ------------------- precompiles/dma/dma.md | 14 -------------- state-machines/mem-cpp/cpp/mem_config.hpp | 2 +- 6 files changed, 33 insertions(+), 42 deletions(-) delete mode 100644 precompiles/dma/dma.md diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 4a963aa4b..8cbe6a122 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -55,10 +55,17 @@ build/dma/%.o: src/dma/%.asm src/dma/dma_constants.inc build/dma.o: $(DMA_OBJS) ld -r $(DMA_OBJS) -o $@ +build/zisk.ld: + mkdir -p build + ld --verbose 2>/dev/null | \ + awk '/^=+$$/{found++; next} found==1{print}' | \ + sed '/\/DISCARD\//i\ .my_region 0x40000000 (NOLOAD) : { . = . + 0x1090000000; }' \ + > build/zisk.ld + # Compile the final executable -$(OUT_PATH): build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c build/dma.o +$(OUT_PATH): build/zisk.ld build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c build/dma.o mkdir -p $(OUT_DIR) - gcc $(CFLAGS) src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ + gcc $(CFLAGS) -Wl,-T,build/zisk.ld src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ clean: rm -rf build diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 146404bac..f0615b804 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -111,12 +111,12 @@ impl AsmServices { self.start_asm_service(service, trimmed_path, &options, &shm_prefix); // if i == 0 { - // For the first service, wait until it is ready before starting the others, - // since it may initialize the shared memory used by the rest. - Self::wait_for_service_ready(self.world_rank, service, port)?; + // For the first service, wait until it is ready before starting the others, + // since it may initialize the shared memory used by the rest. + Self::wait_for_service_ready(self.world_rank, service, port)?; // } else { - // For the other services, we can start them in parallel and wait for them after - // pending_wait.push((service, port)); + // For the other services, we can start them in parallel and wait for them after + // pending_wait.push((service, port)); // } } diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index e16ddb624..e1035db35 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -75,10 +75,14 @@ void server_setup (void) { if (!open_input_shm) { + printf("PATH open_input_shm(%s) 0\n", shmem_input_name); + fflush(stdout); // Make sure the input shared memory is deleted shm_unlink(shmem_input_name); // Create the input shared memory + printf("PATH open_input_shm(%s) 1\n", shmem_input_name); + fflush(stdout); shmem_input_fd = shm_open(shmem_input_name, O_RDWR | O_CREAT | O_EXCL, 0666); if (shmem_input_fd < 0) { @@ -89,6 +93,8 @@ void server_setup (void) } // Size it + printf("PATH open_input_shm(%s) 2\n", shmem_input_name); + fflush(stdout); result = ftruncate(shmem_input_fd, MAX_INPUT_SIZE); if (result != 0) { @@ -99,9 +105,13 @@ void server_setup (void) } // Sync + printf("PATH open_input_shm(%s) 3\n", shmem_input_name); + fflush(stdout); fsync(shmem_input_fd); // Close the descriptor + printf("PATH open_input_shm(%s) 4\n", shmem_input_name); + fflush(stdout); if (close(shmem_input_fd) != 0) { printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); @@ -109,9 +119,13 @@ void server_setup (void) fflush(stderr); exit(-1); } + printf("PATH open_input_shm(%s) 5\n", shmem_input_name); + fflush(stdout); } // Open the input shared memory as read-only + printf("BEFORE shm_open(%s)\n", shmem_input_name); + fflush(stdout); shmem_input_fd = shm_open(shmem_input_name, O_RDONLY, 0666); if (shmem_input_fd < 0) { @@ -143,7 +157,10 @@ void server_setup (void) fflush(stderr); exit(-1); } - if (verbose) printf("mmap(input) mapped %lu B and returned address %p in %lu us\n", MAX_INPUT_SIZE, pInput, duration); + if (verbose) { + printf("mmap(input) mapped %lu B and returned address %p in %lu us\n", MAX_INPUT_SIZE, pInput, duration); + fflush(stdout); + } } /**********************/ diff --git a/emulator-asm/src/trace.c b/emulator-asm/src/trace.c index 45ddca19d..4020a301d 100644 --- a/emulator-asm/src/trace.c +++ b/emulator-asm/src/trace.c @@ -16,22 +16,6 @@ #include "globals.hpp" #include "emu.hpp" -/********************/ -/* TRACE ALLOCATION */ -/********************/ - -void trace_virtual_alloc (void) -{ - void * addr = mmap((void *)TRACE_ADDR, TRACE_MAX_SIZE, PROT_NONE, MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (addr != (void *)TRACE_ADDR) - { - printf("ERROR: trace_virtual_alloc() failed to reserve trace memory at address 0x%lx\n", TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - uint64_t next_chunk_id = 0; // Next trace chunk id to be mapped, starting from 0 int trace_chunk_fd[TRACE_NUMBER_OF_CHUNKS]; // File descriptors for each chunk uint64_t trace_total_mapped_size = 0; // Total mapped trace size @@ -226,9 +210,6 @@ void trace_map_initialize (void) // Perform preventive cleanup of any leftover shared memory chunks trace_preventive_cleanup(); - // Reserve the full virtual trace address space - trace_virtual_alloc(); - // Map the first chunk, i.e. chunk 0 trace_map_next_chunk(); diff --git a/precompiles/dma/dma.md b/precompiles/dma/dma.md deleted file mode 100644 index 4824d297f..000000000 --- a/precompiles/dma/dma.md +++ /dev/null @@ -1,14 +0,0 @@ -## Translate - -ELF - -ADD X0, DST, SRC ==> DMA_MEMCPY DST, SRC (JMP +8) -CSRW XXX, COUNT ==> FLAG COUNT - - -DST_PRE_DATA -DST_POST_DATA -SRC_DATA ..... (without redundancy) - - -extra param count \ No newline at end of file diff --git a/state-machines/mem-cpp/cpp/mem_config.hpp b/state-machines/mem-cpp/cpp/mem_config.hpp index 2db06bd68..d1dad8e10 100644 --- a/state-machines/mem-cpp/cpp/mem_config.hpp +++ b/state-machines/mem-cpp/cpp/mem_config.hpp @@ -51,7 +51,7 @@ #define ADDR_SLOT_BITS 5 #define ADDR_SLOT_SIZE (1 << ADDR_SLOT_BITS) #define ADDR_SLOT_MASK (0xFFFFFFFF << ADDR_SLOT_BITS) -#define ADDR_SLOTS ((1024 * 1024 * 128) / MAX_THREADS) +#define ADDR_SLOTS ((1024 * 1024 * 24) / MAX_THREADS) #define ADDR_SLOTS_SIZE (ADDR_SLOT_SIZE * ADDR_SLOTS) #define TIME_US_BY_CHUNK 173 From 8b083d076a892ecde9e254665cac3ad289391786 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Mon, 9 Mar 2026 15:29:01 +0100 Subject: [PATCH 736/782] remove logs --- emulator-asm/src/server.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index e1035db35..b9d50457c 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -75,14 +75,10 @@ void server_setup (void) { if (!open_input_shm) { - printf("PATH open_input_shm(%s) 0\n", shmem_input_name); - fflush(stdout); // Make sure the input shared memory is deleted shm_unlink(shmem_input_name); // Create the input shared memory - printf("PATH open_input_shm(%s) 1\n", shmem_input_name); - fflush(stdout); shmem_input_fd = shm_open(shmem_input_name, O_RDWR | O_CREAT | O_EXCL, 0666); if (shmem_input_fd < 0) { @@ -93,8 +89,6 @@ void server_setup (void) } // Size it - printf("PATH open_input_shm(%s) 2\n", shmem_input_name); - fflush(stdout); result = ftruncate(shmem_input_fd, MAX_INPUT_SIZE); if (result != 0) { @@ -105,13 +99,9 @@ void server_setup (void) } // Sync - printf("PATH open_input_shm(%s) 3\n", shmem_input_name); - fflush(stdout); fsync(shmem_input_fd); // Close the descriptor - printf("PATH open_input_shm(%s) 4\n", shmem_input_name); - fflush(stdout); if (close(shmem_input_fd) != 0) { printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); @@ -119,13 +109,9 @@ void server_setup (void) fflush(stderr); exit(-1); } - printf("PATH open_input_shm(%s) 5\n", shmem_input_name); - fflush(stdout); } // Open the input shared memory as read-only - printf("BEFORE shm_open(%s)\n", shmem_input_name); - fflush(stdout); shmem_input_fd = shm_open(shmem_input_name, O_RDONLY, 0666); if (shmem_input_fd < 0) { From 82c761815b0f494e7858289de34a18a9ad844702 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Mon, 9 Mar 2026 15:23:46 +0100 Subject: [PATCH 737/782] Cherry-pick virtual address fix (1/2) --- emulator-asm/Makefile | 11 +++++++++-- emulator-asm/src/server.c | 19 ++++++++++++++++++- emulator-asm/src/trace.c | 19 ------------------- precompiles/dma/dma.md | 14 -------------- state-machines/mem-cpp/cpp/mem_config.hpp | 2 +- 5 files changed, 28 insertions(+), 37 deletions(-) delete mode 100644 precompiles/dma/dma.md diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 4a963aa4b..8cbe6a122 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -55,10 +55,17 @@ build/dma/%.o: src/dma/%.asm src/dma/dma_constants.inc build/dma.o: $(DMA_OBJS) ld -r $(DMA_OBJS) -o $@ +build/zisk.ld: + mkdir -p build + ld --verbose 2>/dev/null | \ + awk '/^=+$$/{found++; next} found==1{print}' | \ + sed '/\/DISCARD\//i\ .my_region 0x40000000 (NOLOAD) : { . = . + 0x1090000000; }' \ + > build/zisk.ld + # Compile the final executable -$(OUT_PATH): build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c build/dma.o +$(OUT_PATH): build/zisk.ld build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c build/dma.o mkdir -p $(OUT_DIR) - gcc $(CFLAGS) src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ + gcc $(CFLAGS) -Wl,-T,build/zisk.ld src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ clean: rm -rf build diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index 4a214038a..4f66abe68 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -78,10 +78,14 @@ void server_setup (void) { if (!open_input_shm) { + printf("PATH open_input_shm(%s) 0\n", shmem_input_name); + fflush(stdout); // Make sure the input shared memory is deleted shm_unlink(shmem_input_name); // Create the input shared memory + printf("PATH open_input_shm(%s) 1\n", shmem_input_name); + fflush(stdout); shmem_input_fd = shm_open(shmem_input_name, O_RDWR | O_CREAT | O_EXCL, 0666); if (shmem_input_fd < 0) { @@ -92,6 +96,8 @@ void server_setup (void) } // Size it + printf("PATH open_input_shm(%s) 2\n", shmem_input_name); + fflush(stdout); result = ftruncate(shmem_input_fd, MAX_INPUT_SIZE); if (result != 0) { @@ -102,9 +108,13 @@ void server_setup (void) } // Sync + printf("PATH open_input_shm(%s) 3\n", shmem_input_name); + fflush(stdout); fsync(shmem_input_fd); // Close the descriptor + printf("PATH open_input_shm(%s) 4\n", shmem_input_name); + fflush(stdout); if (close(shmem_input_fd) != 0) { printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); @@ -112,9 +122,13 @@ void server_setup (void) fflush(stderr); exit(-1); } + printf("PATH open_input_shm(%s) 5\n", shmem_input_name); + fflush(stdout); } // Open the input shared memory as read-only + printf("BEFORE shm_open(%s)\n", shmem_input_name); + fflush(stdout); shmem_input_fd = shm_open(shmem_input_name, O_RDONLY, 0666); if (shmem_input_fd < 0) { @@ -146,7 +160,10 @@ void server_setup (void) fflush(stderr); exit(-1); } - if (verbose) printf("mmap(input) mapped %lu B and returned address %p in %lu us\n", MAX_INPUT_SIZE, pInput, duration); + if (verbose) { + printf("mmap(input) mapped %lu B and returned address %p in %lu us\n", MAX_INPUT_SIZE, pInput, duration); + fflush(stdout); + } } /**********************/ diff --git a/emulator-asm/src/trace.c b/emulator-asm/src/trace.c index 45ddca19d..4020a301d 100644 --- a/emulator-asm/src/trace.c +++ b/emulator-asm/src/trace.c @@ -16,22 +16,6 @@ #include "globals.hpp" #include "emu.hpp" -/********************/ -/* TRACE ALLOCATION */ -/********************/ - -void trace_virtual_alloc (void) -{ - void * addr = mmap((void *)TRACE_ADDR, TRACE_MAX_SIZE, PROT_NONE, MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (addr != (void *)TRACE_ADDR) - { - printf("ERROR: trace_virtual_alloc() failed to reserve trace memory at address 0x%lx\n", TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - uint64_t next_chunk_id = 0; // Next trace chunk id to be mapped, starting from 0 int trace_chunk_fd[TRACE_NUMBER_OF_CHUNKS]; // File descriptors for each chunk uint64_t trace_total_mapped_size = 0; // Total mapped trace size @@ -226,9 +210,6 @@ void trace_map_initialize (void) // Perform preventive cleanup of any leftover shared memory chunks trace_preventive_cleanup(); - // Reserve the full virtual trace address space - trace_virtual_alloc(); - // Map the first chunk, i.e. chunk 0 trace_map_next_chunk(); diff --git a/precompiles/dma/dma.md b/precompiles/dma/dma.md deleted file mode 100644 index 4824d297f..000000000 --- a/precompiles/dma/dma.md +++ /dev/null @@ -1,14 +0,0 @@ -## Translate - -ELF - -ADD X0, DST, SRC ==> DMA_MEMCPY DST, SRC (JMP +8) -CSRW XXX, COUNT ==> FLAG COUNT - - -DST_PRE_DATA -DST_POST_DATA -SRC_DATA ..... (without redundancy) - - -extra param count \ No newline at end of file diff --git a/state-machines/mem-cpp/cpp/mem_config.hpp b/state-machines/mem-cpp/cpp/mem_config.hpp index d198a850c..105352280 100644 --- a/state-machines/mem-cpp/cpp/mem_config.hpp +++ b/state-machines/mem-cpp/cpp/mem_config.hpp @@ -47,7 +47,7 @@ #define ADDR_SLOT_BITS 5 #define ADDR_SLOT_SIZE (1 << ADDR_SLOT_BITS) #define ADDR_SLOT_MASK (0xFFFFFFFF << ADDR_SLOT_BITS) -#define ADDR_SLOTS ((1024 * 1024 * 64) / MAX_THREADS) +#define ADDR_SLOTS ((1024 * 1024 * 24) / MAX_THREADS) #define ADDR_SLOTS_SIZE (ADDR_SLOT_SIZE * ADDR_SLOTS) #define TIME_US_BY_CHUNK 173 From 2a670aa8c00ad4247f814943d3465719b1ba5eeb Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Mon, 9 Mar 2026 15:29:01 +0100 Subject: [PATCH 738/782] remove logs --- emulator-asm/src/server.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index 4f66abe68..36b4d33c6 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -78,14 +78,10 @@ void server_setup (void) { if (!open_input_shm) { - printf("PATH open_input_shm(%s) 0\n", shmem_input_name); - fflush(stdout); // Make sure the input shared memory is deleted shm_unlink(shmem_input_name); // Create the input shared memory - printf("PATH open_input_shm(%s) 1\n", shmem_input_name); - fflush(stdout); shmem_input_fd = shm_open(shmem_input_name, O_RDWR | O_CREAT | O_EXCL, 0666); if (shmem_input_fd < 0) { @@ -96,8 +92,6 @@ void server_setup (void) } // Size it - printf("PATH open_input_shm(%s) 2\n", shmem_input_name); - fflush(stdout); result = ftruncate(shmem_input_fd, MAX_INPUT_SIZE); if (result != 0) { @@ -108,13 +102,9 @@ void server_setup (void) } // Sync - printf("PATH open_input_shm(%s) 3\n", shmem_input_name); - fflush(stdout); fsync(shmem_input_fd); // Close the descriptor - printf("PATH open_input_shm(%s) 4\n", shmem_input_name); - fflush(stdout); if (close(shmem_input_fd) != 0) { printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); @@ -122,13 +112,9 @@ void server_setup (void) fflush(stderr); exit(-1); } - printf("PATH open_input_shm(%s) 5\n", shmem_input_name); - fflush(stdout); } // Open the input shared memory as read-only - printf("BEFORE shm_open(%s)\n", shmem_input_name); - fflush(stdout); shmem_input_fd = shm_open(shmem_input_name, O_RDONLY, 0666); if (shmem_input_fd < 0) { From d074c42f67a8b29c1268828d701b80d6b67a798d Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Fri, 6 Mar 2026 15:06:45 +0000 Subject: [PATCH 739/782] Tentative solution mpi hints --- Cargo.lock | 2 + common/src/io/stream/zisk_stream.rs | 4 +- distributed/crates/common/src/types.rs | 32 ++++- distributed/crates/worker/src/worker.rs | 61 ++++++---- distributed/crates/worker/src/worker_node.rs | 19 ++- emulator-asm/asm-runner/src/hints_file.rs | 2 +- emulator-asm/asm-runner/src/hints_shmem.rs | 4 +- emulator-asm/asm-runner/src/inputs_shmem.rs | 4 +- executor/src/asm_resources.rs | 58 ++++++--- precompiles/hints/Cargo.toml | 2 + precompiles/hints/src/hints_processor.rs | 118 ++++++++++++++----- 11 files changed, 227 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c093b84a..0c2bc3145 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3115,6 +3115,7 @@ name = "precompiles-hints" version = "0.16.0" dependencies = [ "anyhow", + "borsh", "criterion 0.8.2", "lib-c", "precompiles-helpers", @@ -3122,6 +3123,7 @@ dependencies = [ "rustls", "tracing", "zisk-common", + "zisk-distributed-common", "ziskos-hints", ] diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index 2c07b2f98..2567ba1c5 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -23,13 +23,13 @@ pub trait StreamProcessor: Send + Sync + 'static { /// Trait for submitting processed hints to a sink. /// /// # Arguments -/// * `processed` - A vector of processed hints as u64 values. +/// * `processed` - A slice of processed hints as u64 values. /// /// # Returns /// * `Ok(())` - If hints were successfully submitted /// * `Err` - If submission fails pub trait StreamSink: Send + Sync + 'static { - fn submit(&self, processed: Vec) -> anyhow::Result<()>; + fn submit(&self, processed: &[u64]) -> anyhow::Result<()>; fn reset(&self) {} diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index e01f8f7cc..883f26a85 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -6,7 +6,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use chrono::{DateTime, Utc}; -use proofman::{ContributionsInfo, WitnessInfo}; +use proofman::{ContributionsInfo, ProvePhaseInputs, WitnessInfo}; +use proofman_common::ProofOptions; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -423,6 +424,8 @@ pub enum JobPhase { Contributions, Prove, Aggregate, + ContributionsInputsStream, + ContributionsHintsStream, } impl TryFrom for JobPhase { @@ -433,6 +436,8 @@ impl TryFrom for JobPhase { 0 => Ok(JobPhase::Contributions), 1 => Ok(JobPhase::Prove), 2 => Ok(JobPhase::Aggregate), + 3 => Ok(JobPhase::ContributionsInputsStream), + 4 => Ok(JobPhase::ContributionsHintsStream), _ => Err(anyhow::anyhow!("Invalid JobPhase byte: {}", value)), } } @@ -444,6 +449,8 @@ impl fmt::Display for JobPhase { JobPhase::Contributions => write!(f, "Contributions"), JobPhase::Prove => write!(f, "Prove"), JobPhase::Aggregate => write!(f, "Aggregate"), + JobPhase::ContributionsInputsStream => write!(f, "ContributionsInputsStream"), + JobPhase::ContributionsHintsStream => write!(f, "ContributionsHintsStream"), } } } @@ -467,3 +474,26 @@ pub struct PartitionInfo { pub allocation: Vec, pub worker_idx: usize, } + +/// Message structures for MPI broadcast to ensure type safety +#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] +pub struct ContributionsMessage { + pub job_id: JobId, + pub phase_inputs: ProvePhaseInputs, + pub options: ProofOptions, + pub input_source: InputSourceDto, + pub hints_source: HintsSourceDto, + pub partition_info: PartitionInfo, +} + +#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] +pub struct ProveMessage { + pub job_id: JobId, + pub phase_inputs: ProvePhaseInputs, + pub options: ProofOptions, +} + +#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] +pub struct StreamMessage { + pub data: Vec, +} diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index fc811c931..c38286974 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -13,8 +13,9 @@ use zisk_common::ElfBinaryFromFile; use zisk_common::ZiskExecutorTime; use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; use zisk_distributed_common::{ComputeCapacity, JobId, PartitionInfo, WorkerId}; +use zisk_distributed_common::{ContributionsMessage, ProveMessage, StreamMessage}; use zisk_distributed_common::{HintsSourceDto, StreamDataDto, StreamMessageKind}; -use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProgramPK, ZiskProver}; +use zisk_sdk::{Asm, Emu, ProverClient, StreamSink, ZiskBackend, ZiskProgramPK, ZiskProver}; use crate::stream_ordering::StreamOrderingActor; @@ -28,24 +29,6 @@ use tracing::{error, info}; use crate::config::ProverServiceConfigDto; -/// Message structures for MPI broadcast to ensure type safety -#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] -struct ContributionsMessage { - job_id: JobId, - phase_inputs: ProvePhaseInputs, - options: ProofOptions, - input_source: InputSourceDto, - hints_source: HintsSourceDto, - partition_info: PartitionInfo, -} - -#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] -struct ProveMessage { - job_id: JobId, - phase_inputs: ProvePhaseInputs, - options: ProofOptions, -} - /// Result from computation tasks #[derive(Debug)] pub enum ComputationResult { @@ -401,6 +384,10 @@ impl Worker { } } + pub fn pk(&self) -> Arc { + Arc::clone(&self.pk) + } + #[allow(clippy::type_complexity)] #[allow(clippy::too_many_arguments)] pub fn new_job( @@ -709,12 +696,20 @@ impl Worker { control_writer.clone(), )?); + let prover = self.prover.clone(); + // HintsShmem::new and HintsProcessor::build perform OS-level shared-memory operations; // run them on the blocking thread pool to avoid stalling Tokio workers. let processor = tokio::task::spawn_blocking(move || -> Result { - let hints_shmem = - HintsShmem::new(base_port, local_rank, unlock_mapped_memory, control_writer)?; + let hints_shmem = Arc::new(HintsShmem::new( + base_port, + local_rank, + unlock_mapped_memory, + control_writer, + )?); + HintsProcessor::builder(hints_shmem, Some(inputs_shmem_writer)) + .with_mpi_broadcast(move |data| prover.mpi_broadcast(data)) .build() .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e)) }) @@ -895,7 +890,11 @@ impl Worker { // MPI Broadcast handlers for receiving and executing tasks // -------------------------------------------------------------------------- - pub async fn handle_mpi_broadcast_request(&self) -> Result<()> { + pub async fn handle_mpi_broadcast_request( + &self, + inputs_shmem_writer: Arc, + hints_sink: Option>, + ) -> Result<()> { let mut bytes: Vec = Vec::new(); self.prover.mpi_broadcast(&mut bytes)?; @@ -940,6 +939,24 @@ impl Worker { JobPhase::Aggregate => { unreachable!("Aggregate phase is not supported in MPI broadcast"); } + JobPhase::ContributionsInputsStream => { + let message: StreamMessage = borsh::from_slice(&bytes[1..]).unwrap(); + let reinterpreted_data = unsafe { + std::slice::from_raw_parts( + message.data.as_ptr() as *const u8, + message.data.len() * std::mem::size_of::(), + ) + }; + inputs_shmem_writer.append_input(reinterpreted_data)?; + } + JobPhase::ContributionsHintsStream => { + if let Some(hints_sink) = hints_sink { + let message: StreamMessage = borsh::from_slice(&bytes[1..]).unwrap(); + hints_sink.submit(&message.data)?; + } else { + unimplemented!("Hints sink is not configured for ContributionsHintsStream"); + } + } } Ok(()) } diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index d1ce46282..06744f878 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -1,7 +1,9 @@ use crate::{worker::ComputationResult, ProverConfig, Worker}; use anyhow::{anyhow, Result}; +use asm_runner::InputsShmemWriter; use proofman::{AggProofs, ContributionsInfo, WitnessInfo}; use std::path::Path; +use std::sync::Arc; use std::{path::PathBuf, time::Duration}; use tokio::sync::mpsc; use tokio_stream::StreamExt; @@ -17,7 +19,7 @@ use zisk_distributed_common::{DataId, JobId}; use zisk_distributed_grpc_api::contribution_params::InputSource; use zisk_distributed_grpc_api::execute_task_response::ResultData; use zisk_distributed_grpc_api::*; -use zisk_sdk::{Asm, Emu, ZiskBackend}; +use zisk_sdk::{Asm, Emu, StreamSink, ZiskBackend}; use crate::config::WorkerServiceConfig; @@ -80,11 +82,17 @@ impl WorkerNode { pub struct WorkerNodeMpi { worker: Worker, + inputs_shmem_writer: Arc, + hints_sink: Option>, } impl WorkerNodeMpi { pub async fn new(worker: Worker) -> Result { - Ok(Self { worker }) + let pk = worker.pk(); + let inputs_shmem_writer = pk.asm_resources.as_ref().unwrap().inputs_shmem_writer.clone(); + let hints_sink = pk.asm_resources.as_ref().unwrap().hints_sink.clone(); + + Ok(Self { worker, inputs_shmem_writer, hints_sink }) } pub fn world_rank(&self) -> i32 { @@ -96,7 +104,12 @@ impl WorkerNodeMpi { loop { // Non-rank 0 workers are executing inside a cluster and only receives MPI requests - self.worker.handle_mpi_broadcast_request().await?; + self.worker + .handle_mpi_broadcast_request( + self.inputs_shmem_writer.clone(), + self.hints_sink.clone(), + ) + .await?; } } } diff --git a/emulator-asm/asm-runner/src/hints_file.rs b/emulator-asm/asm-runner/src/hints_file.rs index ebb208eb6..a00bfe17b 100644 --- a/emulator-asm/asm-runner/src/hints_file.rs +++ b/emulator-asm/asm-runner/src/hints_file.rs @@ -40,7 +40,7 @@ impl StreamSink for HintsFile { /// * `Ok(())` - If hints were successfully written to the file /// * `Err` - If writing to the file fails #[inline] - fn submit(&self, processed: Vec) -> anyhow::Result<()> { + fn submit(&self, processed: &[u64]) -> anyhow::Result<()> { let mut file = self.file.lock().unwrap(); // Write each u64 as 8 bytes (little-endian) diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index fbbb0ac10..fc9a17b35 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -190,7 +190,7 @@ impl StreamSink for HintsShmem { /// * `Ok(())` - If hints were successfully written to shared memory /// * `Err` - If writing to shared memory fails #[inline] - fn submit(&self, processed: Vec) -> anyhow::Result<()> { + fn submit(&self, processed: &[u64]) -> anyhow::Result<()> { let data_size = processed.len() as u64; // Early return for empty data @@ -262,7 +262,7 @@ impl StreamSink for HintsShmem { } // Write data ONCE to the unified shared memory buffer - unified.data_writer.write_ring_buffer(&processed)?; + unified.data_writer.write_ring_buffer(processed)?; fence(Ordering::Release); diff --git a/emulator-asm/asm-runner/src/inputs_shmem.rs b/emulator-asm/asm-runner/src/inputs_shmem.rs index abe21f317..fa14a0a4d 100644 --- a/emulator-asm/asm-runner/src/inputs_shmem.rs +++ b/emulator-asm/asm-runner/src/inputs_shmem.rs @@ -75,8 +75,8 @@ impl InputsShmemWriter { } impl StreamSink for InputsShmemWriter { - fn submit(&self, hints: Vec) -> anyhow::Result<()> { - self.append_input(&reinterpret_vec(hints)?) + fn submit(&self, hints: &[u64]) -> anyhow::Result<()> { + self.append_input(&reinterpret_vec(hints.to_vec())?) } fn reset(&self) { diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index 721775269..69cf9fb35 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -9,6 +9,7 @@ use asm_runner::InputsShmemWriter; use asm_runner::{MOShMemReader, MTShMemReader, RHShMemReader}; use precompiles_hints::HintsProcessor; use std::sync::atomic::{AtomicBool, Ordering}; +use zisk_common::io::StreamSink; use zisk_common::io::ZiskIO; use zisk_common::io::ZiskStdin; use zisk_common::io::{StreamSource, ZiskStream}; @@ -50,6 +51,8 @@ pub struct AsmResources { pub inputs_shmem_writer: Arc, + pub hints_sink: Option>, + /// Pipeline for handling precompile hints. pub hints_stream: Option>>, @@ -95,25 +98,43 @@ impl AsmResources { // Debug flag: true = HintsShmem (shared memory), false = HintsFile (file output) const USE_SHARED_MEMORY_HINTS: bool = true; - let hints_stream = if with_hints { - let hints_processor = if USE_SHARED_MEMORY_HINTS { - let hints_shmem = - HintsShmem::new(base_port, local_rank, unlock_mapped_memory, control_writer)?; - - HintsProcessor::builder(hints_shmem, Some(inputs_shmem_writer.clone())) - .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) - .build()? - } else { - let hints_file = HintsFile::new(format!("hints_results_{}.bin", local_rank))?; - - HintsProcessor::builder(hints_file, Some(inputs_shmem_writer.clone())) - .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) - .build()? - }; - - Some(Arc::new(Mutex::new(ZiskStream::new(hints_processor)))) + let (hints_stream, hints_sink) = if with_hints { + let (hints_processor, hints_sink): (HintsProcessor, Arc) = + if USE_SHARED_MEMORY_HINTS { + let hints_shmem = Arc::new(HintsShmem::new( + base_port, + local_rank, + unlock_mapped_memory, + control_writer, + )?); + + ( + HintsProcessor::builder( + hints_shmem.clone(), + Some(inputs_shmem_writer.clone()), + ) + .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) + .build()?, + hints_shmem, + ) + } else { + let hints_file = + Arc::new(HintsFile::new(format!("hints_results_{}.bin", local_rank))?); + + ( + HintsProcessor::builder( + hints_file.clone(), + Some(inputs_shmem_writer.clone()), + ) + .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) + .build()?, + hints_file, + ) + }; + + (Some(Arc::new(Mutex::new(ZiskStream::new(hints_processor)))), Some(hints_sink)) } else { - None + (None, None) }; Ok(Self { @@ -128,6 +149,7 @@ impl AsmResources { rh_shmem_reader: Arc::new(Mutex::new(None)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] inputs_shmem_writer, + hints_sink, }) } diff --git a/precompiles/hints/Cargo.toml b/precompiles/hints/Cargo.toml index eaf0d6b18..1e842a52d 100644 --- a/precompiles/hints/Cargo.toml +++ b/precompiles/hints/Cargo.toml @@ -28,6 +28,8 @@ rayon = { workspace = true } tracing = { workspace = true } zisk-common = { workspace = true } rustls = { version = "0.23", features = ["ring"] } +borsh = { workspace = true } +zisk-distributed-common = { workspace = true } [dev-dependencies] criterion = "0.8" diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index 057082cec..bb9eab904 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -17,6 +17,7 @@ use zisk_common::{ BuiltInHint, CtrlHint, HintCode, PartialPrecompileHint, PrecompileHint, PrecompileHintParseResult, }; +use zisk_distributed_common::{JobPhase, StreamMessage}; use ziskos_hints::handlers::blake2b::blake2b_compress_hint; use ziskos_hints::handlers::bls381::{ bls12_381_fp2_to_g2_hint, bls12_381_fp_to_g1_hint, bls12_381_g1_add_hint, @@ -87,6 +88,9 @@ impl HintProcessorState { /// Type alias for custom hint handler functions. pub type CustomHintHandler = Arc Result> + Send + Sync>; +/// Type alias for MPI broadcast callback function. +pub type MpiBroadcastFn = Arc) -> Result<()> + Send + Sync>; + /// Builder for configuring and constructing a [`HintsProcessor`]. pub struct HintsProcessorBuilder { hints_sink: Arc, @@ -94,6 +98,7 @@ pub struct HintsProcessorBuilder { num_threads: usize, enable_stats: bool, custom_handlers: HashMap, + mpi_broadcast_fn: Option, } impl HintsProcessorBuilder { @@ -134,6 +139,17 @@ impl HintsProcessorBuilder { self } + /// Sets an MPI broadcast function to be called during initialization. + /// + /// This allows synchronization of initialization data across MPI ranks. + pub fn with_mpi_broadcast(mut self, broadcast_fn: F) -> Self + where + F: Fn(&mut Vec) -> Result<()> + Send + Sync + 'static, + { + self.mpi_broadcast_fn = Some(Arc::new(broadcast_fn)); + self + } + /// Builds the [`HintsProcessor`] with the configured settings. /// /// # Returns @@ -153,8 +169,9 @@ impl HintsProcessorBuilder { // Spawn drainer thread let drainer_state = Arc::clone(&state); let drainer_sink = Arc::clone(&hints_sink); + let drainer_broadcast = self.mpi_broadcast_fn.clone(); let drainer_thread = std::thread::spawn(move || { - HintsProcessor::drainer_thread(drainer_state, drainer_sink); + HintsProcessor::drainer_thread(drainer_state, drainer_sink, drainer_broadcast); }); Ok(HintsProcessor { @@ -169,6 +186,7 @@ impl HintsProcessorBuilder { stream_active: AtomicBool::new(false), instant: Mutex::new(None), pending_partial: Mutex::new(None), + mpi_broadcast_fn: self.mpi_broadcast_fn, }) } } @@ -210,6 +228,9 @@ pub struct HintsProcessor { /// Buffer for incomplete hint data between batches pending_partial: Mutex>, + + /// Optional MPI broadcast function for initialization synchronization + mpi_broadcast_fn: Option, } impl HintsProcessor { @@ -231,16 +252,32 @@ impl HintsProcessor { /// .build()?; /// ``` pub fn builder( - hints_sink: impl StreamSink, + hints_sink: Arc, inputs_sink: Option>, ) -> HintsProcessorBuilder { HintsProcessorBuilder { - hints_sink: Arc::new(hints_sink), + hints_sink, inputs_sink: inputs_sink.map(|s| s as Arc), num_threads: Self::DEFAULT_NUM_THREADS, enable_stats: false, custom_handlers: HashMap::new(), + mpi_broadcast_fn: None, + } + } + + /// Executes the MPI broadcast callback if one was configured. + /// + /// This allows manual control over when MPI synchronization occurs. + /// + /// # Returns + /// + /// * `Ok(())` - Broadcast completed successfully or no callback configured + /// * `Err` - If the broadcast callback returns an error + pub fn mpi_broadcast(&self, data: &mut Vec) -> Result<()> { + if let Some(broadcast_fn) = &self.mpi_broadcast_fn { + broadcast_fn(data)?; } + Ok(()) } /// Processes hints in parallel with non-blocking, ordered output. @@ -393,12 +430,19 @@ impl HintsProcessor { // If the hint is an input hint, write it to the inputs sink instead of processing if hint.hint_code == HintCode::BuiltIn(BuiltInHint::Input) { + let mut serialized = borsh::to_vec(&( + JobPhase::ContributionsInputsStream, + StreamMessage { data: hint.data.clone() }, + )) + .unwrap(); + + self.mpi_broadcast(&mut serialized)?; self.inputs_sink .as_ref() .ok_or_else(|| { anyhow::anyhow!("Received input hint but no inputs sink configured") })? - .submit(hint.data)?; + .submit(&hint.data)?; // Continue to next hint without spawning worker idx += length; continue; @@ -560,7 +604,11 @@ impl HintsProcessor { } /// Drainer thread that waits for hints to complete and drains ready results from queue. - fn drainer_thread(state: Arc, hints_sink: Arc) { + fn drainer_thread( + state: Arc, + hints_sink: Arc, + mpi_broadcast_fn: Option, + ) { loop { let mut queue = state.queue.lock().unwrap(); @@ -584,13 +632,24 @@ impl HintsProcessor { drop(queue); // Submit to sink - if let Err(e) = hints_sink.submit(data_to_submit) { + if let Err(e) = hints_sink.submit(&data_to_submit) { eprintln!("Error submitting to sink: {}", e); state.error_flag.store(true, Ordering::Release); state.drain_signal.notify_all(); return; } + if let Some(broadcast_fn) = &mpi_broadcast_fn { + let mut serialized = borsh::to_vec(&( + JobPhase::ContributionsHintsStream, + StreamMessage { data: data_to_submit.clone() }, + )) + .unwrap(); + + broadcast_fn(&mut serialized) + .expect("MPI broadcast failed in drainer thread"); + } + // Re-acquire lock for next iteration queue = state.queue.lock().unwrap(); } @@ -785,7 +844,7 @@ mod tests { struct NullHints; impl StreamSink for NullHints { - fn submit(&self, _processed: Vec) -> Result<()> { + fn submit(&self, _processed: &[u64]) -> Result<()> { Ok(()) } } @@ -803,7 +862,7 @@ mod tests { const TEST_PASSTHROUGH_HINT: u32 = 0x8000_0000 | 0x7FFF_0000; fn processor() -> HintsProcessor { - HintsProcessor::builder(NullHints, None::>) + HintsProcessor::builder(Arc::new(NullHints), None::>) .num_threads(2) .custom_hint(0x7FFF_0000, |data| { // This should never be called for pass-through hints @@ -1048,15 +1107,15 @@ mod tests { } impl StreamSink for RecordingSink { - fn submit(&self, processed: Vec) -> Result<()> { - self.received.lock().unwrap().push(processed); + fn submit(&self, processed: &[u64]) -> Result<()> { + self.received.lock().unwrap().push(processed.to_vec()); Ok(()) } } let received = Arc::new(Mutex::new(Vec::new())); let sink = RecordingSink { received: Arc::clone(&received) }; - let p = HintsProcessor::builder(sink, None::>) + let p = HintsProcessor::builder(Arc::new(sink), None::>) .num_threads(2) .build() .unwrap(); @@ -1090,7 +1149,7 @@ mod tests { } impl StreamSink for FailingSink { - fn submit(&self, _processed: Vec) -> Result<()> { + fn submit(&self, _processed: &[u64]) -> Result<()> { if self.should_fail.load(Ordering::Acquire) { Err(anyhow::anyhow!("Sink error")) } else { @@ -1101,8 +1160,10 @@ mod tests { let should_fail = Arc::new(AtomicBool::new(false)); let sink = FailingSink { should_fail: Arc::clone(&should_fail) }; - let p = - HintsProcessor::builder(sink, None::>).num_threads(2).build().unwrap(); + let p = HintsProcessor::builder(Arc::new(sink), None::>) + .num_threads(2) + .build() + .unwrap(); // First batch succeeds (8 bytes = 1 u64) let data1 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x01]; @@ -1125,25 +1186,26 @@ mod tests { #[test] fn test_builder_configuration() { // Default builder - stats disabled - let p1 = HintsProcessor::builder(NullHints, None::>).build().unwrap(); + let p1 = + HintsProcessor::builder(Arc::new(NullHints), None::>).build().unwrap(); assert!(p1.stats.is_none()); // Explicitly disabled stats - let p2 = HintsProcessor::builder(NullHints, None::>) + let p2 = HintsProcessor::builder(Arc::new(NullHints), None::>) .enable_stats(false) .build() .unwrap(); assert!(p2.stats.is_none()); // Stats enabled - let p3 = HintsProcessor::builder(NullHints, None::>) + let p3 = HintsProcessor::builder(Arc::new(NullHints), None::>) .enable_stats(true) .build() .unwrap(); assert!(p3.stats.is_some()); // Custom threads - let p4 = HintsProcessor::builder(NullHints, None::>) + let p4 = HintsProcessor::builder(Arc::new(NullHints), None::>) .num_threads(4) .build() .unwrap(); @@ -1152,7 +1214,7 @@ mod tests { assert!(p4.wait_for_completion().is_ok()); // Chaining multiple options - let p5 = HintsProcessor::builder(NullHints, None::>) + let p5 = HintsProcessor::builder(Arc::new(NullHints), None::>) .num_threads(8) .enable_stats(true) .build() @@ -1165,7 +1227,7 @@ mod tests { fn test_stress_throughput() { use std::time::Instant; - let p = HintsProcessor::builder(NullHints, None::>) + let p = HintsProcessor::builder(Arc::new(NullHints), None::>) .num_threads(32) .build() .unwrap(); @@ -1201,7 +1263,7 @@ mod tests { fn test_stress_concurrent_batches() { use std::time::Instant; - let p = HintsProcessor::builder(NullHints, None::>) + let p = HintsProcessor::builder(Arc::new(NullHints), None::>) .num_threads(32) .build() .unwrap(); @@ -1243,7 +1305,7 @@ mod tests { fn test_stress_with_resets() { use std::time::Instant; - let p = HintsProcessor::builder(NullHints, None::>) + let p = HintsProcessor::builder(Arc::new(NullHints), None::>) .num_threads(32) .build() .unwrap(); @@ -1302,8 +1364,8 @@ mod tests { } impl StreamSink for RecordingSink { - fn submit(&self, processed: Vec) -> Result<()> { - self.received.lock().unwrap().push(processed); + fn submit(&self, processed: &[u64]) -> Result<()> { + self.received.lock().unwrap().push(processed.to_vec()); Ok(()) } } @@ -1316,7 +1378,7 @@ mod tests { const SLOW_HINT: u32 = 0x7FFF_0001; // Delays 10ms const MED_HINT: u32 = 0x7FFF_0002; // Delays 5ms - let p = HintsProcessor::builder(sink, None::>) + let p = HintsProcessor::builder(Arc::new(sink), None::>) .num_threads(8) .custom_hint(FAST_HINT, |data| { // No delay - returns immediately @@ -1673,8 +1735,8 @@ mod tests { } impl StreamSink for RecordingSink { - fn submit(&self, processed: Vec) -> Result<()> { - self.received.lock().unwrap().push(processed); + fn submit(&self, processed: &[u64]) -> Result<()> { + self.received.lock().unwrap().push(processed.to_vec()); Ok(()) } } @@ -1684,7 +1746,7 @@ mod tests { const VARIABLE_HINT: u32 = 0x7FFF_0100; - let p = HintsProcessor::builder(sink, None::>) + let p = HintsProcessor::builder(Arc::new(sink), None::>) .num_threads(16) .custom_hint(VARIABLE_HINT, |data| { // Pseudo-random delay based on hash of input value (0-15ms range) From 8ec912d495c4b3bd8c1dc33a9e75fac14dfd38c2 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Mon, 9 Mar 2026 13:53:25 +0000 Subject: [PATCH 740/782] Applying hints mpi solution to stream branch --- Cargo.lock | 32 +-- common/src/io/stream/zisk_stream.rs | 10 +- .../crates/coordinator/src/hints_relay.rs | 4 + distributed/crates/worker/src/worker.rs | 198 ++++++++---------- distributed/crates/worker/src/worker_node.rs | 19 +- executor/src/asm_resources.rs | 45 ++-- precompiles/hints/benches/hints_benchmarks.rs | 14 +- precompiles/hints/src/hints_processor.rs | 40 ++-- precompiles/hints/src/lib.rs | 2 +- sdk/src/builder.rs | 12 ++ sdk/src/prover/asm.rs | 16 ++ sdk/src/prover/mod.rs | 18 +- 12 files changed, 214 insertions(+), 196 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c2bc3145..4f008017a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1480,7 +1480,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "cfg-if", "num-bigint", @@ -2222,9 +2222,9 @@ version = "0.16.0" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libffi" @@ -3373,9 +3373,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "fastbloom", @@ -4173,12 +4173,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4906,9 +4906,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -5623,9 +5623,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] @@ -5807,18 +5807,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "96e13bc581734df6250836c59a5f44f3c57db9f9acb9dc8e3eaabdaf6170254d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "3545ea9e86d12ab9bba9fcd99b54c1556fd3199007def5a03c375623d05fac1c" dependencies = [ "proc-macro2", "quote", diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index 2567ba1c5..ab1294ee4 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -1,6 +1,7 @@ //! ZiskStream is responsible for reading precompile hints from a stream source and sent to a hints processor. use anyhow::Result; +use std::any::Any; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Sender}; use std::sync::Arc; @@ -8,7 +9,7 @@ use std::thread::{self, JoinHandle}; use crate::io::{StreamRead, StreamSource}; -pub trait StreamProcessor: Send + Sync + 'static { +pub trait StreamProcessor: Any + Send + Sync + 'static { /// Process data and return the processed result along with a flag indicating if CTRL_END was encountered. /// /// # Returns @@ -18,6 +19,9 @@ pub trait StreamProcessor: Send + Sync + 'static { fn process(&self, data: &[u64], first_batch: bool) -> anyhow::Result; fn reset(&self) {} + + /// Convert Arc to Arc for downcasting + fn as_any(self: Arc) -> Arc; } /// Trait for submitting processed hints to a sink. @@ -182,6 +186,10 @@ impl ZiskStream { Err(anyhow::anyhow!("No background thread running. Call set_hints_stream first.")) } } + + pub fn get_processor(&self) -> Arc { + self.hints_processor.clone() + } } impl Drop for ZiskStream { diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index bc974ab1f..1199584f9 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -155,4 +155,8 @@ impl StreamProcessor for PrecompileHintsRelay { fn reset(&self) { self.reset_state(); } + + fn as_any(self: Arc) -> Arc { + self + } } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index c38286974..d5b781d43 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use asm_runner::{ControlShmem, HintsShmem, InputsShmemWriter}; use cargo_zisk::commands::get_proving_key; use precompiles_hints::HintsProcessor; use proofman::{AggProofs, AggProofsRegister, ContributionsInfo}; @@ -8,14 +7,14 @@ use std::fs; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; -use zisk_common::io::{StreamProcessor, StreamSource, ZiskStdin}; +use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ElfBinaryFromFile; use zisk_common::ZiskExecutorTime; use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; use zisk_distributed_common::{ComputeCapacity, JobId, PartitionInfo, WorkerId}; use zisk_distributed_common::{ContributionsMessage, ProveMessage, StreamMessage}; use zisk_distributed_common::{HintsSourceDto, StreamDataDto, StreamMessageKind}; -use zisk_sdk::{Asm, Emu, ProverClient, StreamSink, ZiskBackend, ZiskProgramPK, ZiskProver}; +use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProgramPK, ZiskProver}; use crate::stream_ordering::StreamOrderingActor; @@ -254,7 +253,6 @@ pub struct Worker { stream_actor: Option, pk: Arc, - hints_processor: Option>, } impl Worker { @@ -288,7 +286,6 @@ impl Worker { prover_config, pk: Arc::new(pk), stream_actor: None, - hints_processor: None, }) } @@ -310,6 +307,7 @@ impl Worker { .unlock_mapped_memory(prover_config.unlock_mapped_memory) .asm_out_file(prover_config.asm_out_file) .gpu(prover_config.gpu_params.clone()) + .is_distributed(true) .build()?, ); @@ -326,7 +324,6 @@ impl Worker { prover_config, pk: Arc::new(pk), stream_actor: None, - hints_processor: None, }) } @@ -384,10 +381,6 @@ impl Worker { } } - pub fn pk(&self) -> Arc { - Arc::clone(&self.pk) - } - #[allow(clippy::type_complexity)] #[allow(clippy::too_many_arguments)] pub fn new_job( @@ -642,13 +635,22 @@ impl Worker { StreamMessageKind::Start => { let job_id = stream_data.job_id.clone(); - self.ensure_hints_processor(is_first_partition).await?; + self.pk.reset(); - let hints_processor = self.hints_processor.as_ref().unwrap(); + let processor_trait = self.pk.get_hints_processor().ok_or_else(|| { + anyhow::anyhow!("HintsProcessor not found for job {}", job_id) + })?; - hints_processor.reset(); + // Downcast Arc to Arc + let hints_processor = + processor_trait.as_any().downcast::().map_err(|_| { + anyhow::anyhow!( + "Failed to downcast StreamProcessor to HintsProcessor for job {}", + job_id + ) + })?; - let hints_processor = Arc::clone(hints_processor); + hints_processor.set_has_rom_sm(is_first_partition); // Replace any existing actor (handles reconnect / job restart) self.stream_actor = Some(StreamOrderingActor::new(hints_processor, job_id)); @@ -676,53 +678,6 @@ impl Worker { self.prover.set_partition(total_compute_units, allocation, worker_idx) } - /// Lazily initialises the `HintsProcessor` using configuration from the current job. - async fn ensure_hints_processor(&mut self, is_first_partition: bool) -> Result<()> { - if self.hints_processor.is_some() { - return Ok(()); - } - - let base_port = self.prover_config.asm_port; - let local_rank = self.prover.local_rank(); - let unlock_mapped_memory = self.prover_config.unlock_mapped_memory; - - let control_writer = - Arc::new(ControlShmem::new(base_port, local_rank, unlock_mapped_memory)?); - - let inputs_shmem_writer = Arc::new(InputsShmemWriter::new( - base_port, - local_rank, - unlock_mapped_memory, - control_writer.clone(), - )?); - - let prover = self.prover.clone(); - - // HintsShmem::new and HintsProcessor::build perform OS-level shared-memory operations; - // run them on the blocking thread pool to avoid stalling Tokio workers. - let processor = tokio::task::spawn_blocking(move || -> Result { - let hints_shmem = Arc::new(HintsShmem::new( - base_port, - local_rank, - unlock_mapped_memory, - control_writer, - )?); - - HintsProcessor::builder(hints_shmem, Some(inputs_shmem_writer)) - .with_mpi_broadcast(move |data| prover.mpi_broadcast(data)) - .build() - .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e)) - }) - .await - .map_err(|e| anyhow::anyhow!("hints processor init panicked: {e}"))??; - - processor.set_has_rom_sm(is_first_partition); - - self.hints_processor = Some(Arc::new(processor)); - - Ok(()) - } - pub fn prove( &self, job: Arc>, @@ -890,56 +845,31 @@ impl Worker { // MPI Broadcast handlers for receiving and executing tasks // -------------------------------------------------------------------------- - pub async fn handle_mpi_broadcast_request( - &self, - inputs_shmem_writer: Arc, - hints_sink: Option>, - ) -> Result<()> { + pub async fn handle_mpi_broadcast_request(&self) -> Result<()> { let mut bytes: Vec = Vec::new(); self.prover.mpi_broadcast(&mut bytes)?; // extract byte 0 to decide the option - let phase = borsh::from_slice(&bytes[0..1]).unwrap(); - - match phase { - JobPhase::Contributions => { - let message: ContributionsMessage = borsh::from_slice(&bytes[1..]).unwrap(); - - let result = Self::execute_contribution_task( - message.job_id, - &self.prover, - message.phase_inputs, - message.input_source, - message.hints_source, - message.partition_info, - &self.pk, - message.options, - ); - if let Err(e) = result { - error!("Error during Contributions MPI broadcast execution: {}. Waiting for new job...", e); - } - } - JobPhase::Prove => { - let message: ProveMessage = borsh::from_slice(&bytes[1..]).unwrap(); - - let result = Self::execute_prove_task( - message.job_id, - &self.prover, - message.phase_inputs, - message.options, - ); - if let Err(e) = result { - error!( - "Error during Prove MPI broadcast execution: {}. Waiting for new job...", - e - ); + let phase: JobPhase = borsh::from_slice(&bytes[0..1]).unwrap(); + + let prover = self.prover.clone(); + let pk = self.pk.clone(); + let options = self.get_proof_options(false); + + if phase == JobPhase::ContributionsHintsStream { + if let Some(hints_sink) = pk.asm_resources.as_ref().and_then(|r| r.hints_sink.clone()) { + let message: StreamMessage = borsh::from_slice(&bytes[1..]).unwrap(); + if let Err(e) = hints_sink.submit(&message.data) { + tracing::error!("Failed to submit hints: {}", e); } + } else { + tracing::error!("Hints sink is not configured for ContributionsHintsStream"); } - JobPhase::Aggregate => { - unreachable!("Aggregate phase is not supported in MPI broadcast"); - } - JobPhase::ContributionsInputsStream => { + } else if phase == JobPhase::ContributionsInputsStream { + if let Some(inputs_shmem_writer) = + pk.asm_resources.as_ref().map(|r| r.inputs_shmem_writer.clone()) + { let message: StreamMessage = borsh::from_slice(&bytes[1..]).unwrap(); let reinterpreted_data = unsafe { std::slice::from_raw_parts( @@ -947,16 +877,60 @@ impl Worker { message.data.len() * std::mem::size_of::(), ) }; - inputs_shmem_writer.append_input(reinterpreted_data)?; - } - JobPhase::ContributionsHintsStream => { - if let Some(hints_sink) = hints_sink { - let message: StreamMessage = borsh::from_slice(&bytes[1..]).unwrap(); - hints_sink.submit(&message.data)?; - } else { - unimplemented!("Hints sink is not configured for ContributionsHintsStream"); + if let Err(e) = inputs_shmem_writer.append_input(reinterpreted_data) { + tracing::error!("Failed to submit inputs: {}", e); } + } else { + tracing::error!("Inputs sink is not configured for ContributionsInputsStream"); } + } else { + tokio::task::spawn_blocking(move || match phase { + JobPhase::Contributions => { + let message: ContributionsMessage = borsh::from_slice(&bytes[1..]).unwrap(); + + let result = Self::execute_contribution_task( + message.job_id, + &prover, + message.phase_inputs, + message.input_source, + message.hints_source, + message.partition_info, + &pk, + message.options, + ); + if let Err(e) = result { + tracing::error!( + "Error during Contributions MPI broadcast execution: {}. Waiting for new job...", + e + ); + } + } + JobPhase::Prove => { + let message: ProveMessage = borsh::from_slice(&bytes[1..]).unwrap(); + + let result = Self::execute_prove_task( + message.job_id, + &prover, + message.phase_inputs, + options, + ); + if let Err(e) = result { + error!( + "Error during Prove MPI broadcast execution: {}. Waiting for new job...", + e + ); + } + } + + JobPhase::Aggregate => { + unreachable!("Aggregate phase is not supported in MPI broadcast"); + } + JobPhase::ContributionsHintsStream | JobPhase::ContributionsInputsStream => { + unreachable!( + "Stream phases should be handled separately and not reach this point" + ); + } + }); } Ok(()) } diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 06744f878..d1ce46282 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -1,9 +1,7 @@ use crate::{worker::ComputationResult, ProverConfig, Worker}; use anyhow::{anyhow, Result}; -use asm_runner::InputsShmemWriter; use proofman::{AggProofs, ContributionsInfo, WitnessInfo}; use std::path::Path; -use std::sync::Arc; use std::{path::PathBuf, time::Duration}; use tokio::sync::mpsc; use tokio_stream::StreamExt; @@ -19,7 +17,7 @@ use zisk_distributed_common::{DataId, JobId}; use zisk_distributed_grpc_api::contribution_params::InputSource; use zisk_distributed_grpc_api::execute_task_response::ResultData; use zisk_distributed_grpc_api::*; -use zisk_sdk::{Asm, Emu, StreamSink, ZiskBackend}; +use zisk_sdk::{Asm, Emu, ZiskBackend}; use crate::config::WorkerServiceConfig; @@ -82,17 +80,11 @@ impl WorkerNode { pub struct WorkerNodeMpi { worker: Worker, - inputs_shmem_writer: Arc, - hints_sink: Option>, } impl WorkerNodeMpi { pub async fn new(worker: Worker) -> Result { - let pk = worker.pk(); - let inputs_shmem_writer = pk.asm_resources.as_ref().unwrap().inputs_shmem_writer.clone(); - let hints_sink = pk.asm_resources.as_ref().unwrap().hints_sink.clone(); - - Ok(Self { worker, inputs_shmem_writer, hints_sink }) + Ok(Self { worker }) } pub fn world_rank(&self) -> i32 { @@ -104,12 +96,7 @@ impl WorkerNodeMpi { loop { // Non-rank 0 workers are executing inside a cluster and only receives MPI requests - self.worker - .handle_mpi_broadcast_request( - self.inputs_shmem_writer.clone(), - self.hints_sink.clone(), - ) - .await?; + self.worker.handle_mpi_broadcast_request().await?; } } } diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index 69cf9fb35..1c43ce8ef 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -7,12 +7,12 @@ use asm_runner::HintsShmem; use asm_runner::InputsShmemWriter; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] use asm_runner::{MOShMemReader, MTShMemReader, RHShMemReader}; -use precompiles_hints::HintsProcessor; +use precompiles_hints::{HintsProcessor, MpiBroadcastFn}; use std::sync::atomic::{AtomicBool, Ordering}; use zisk_common::io::StreamSink; use zisk_common::io::ZiskIO; use zisk_common::io::ZiskStdin; -use zisk_common::io::{StreamSource, ZiskStream}; +use zisk_common::io::{StreamProcessor, StreamSource, ZiskStream}; /// Configuration for assembly resources. #[derive(Clone)] @@ -75,6 +75,7 @@ impl AsmResources { unlock_mapped_memory: bool, verbose_mode: proofman_common::VerboseMode, with_hints: bool, + mpi_broadcast_fn: Option, ) -> Result { #[cfg(all(target_os = "linux", target_arch = "x86_64"))] let asm_shmem_mt = MTShMemReader::new(local_rank, base_port, unlock_mapped_memory)?; @@ -108,28 +109,32 @@ impl AsmResources { control_writer, )?); - ( - HintsProcessor::builder( - hints_shmem.clone(), - Some(inputs_shmem_writer.clone()), - ) - .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) - .build()?, - hints_shmem, + let mut builder = HintsProcessor::builder( + hints_shmem.clone(), + Some(inputs_shmem_writer.clone()), ) + .enable_stats(verbose_mode != proofman_common::VerboseMode::Info); + + if let Some(broadcast_fn) = mpi_broadcast_fn.clone() { + builder = builder.with_mpi_broadcast(move |data| broadcast_fn(data)); + } + + (builder.build().expect("Failed to build HintsProcessor"), hints_shmem) } else { let hints_file = Arc::new(HintsFile::new(format!("hints_results_{}.bin", local_rank))?); - ( - HintsProcessor::builder( - hints_file.clone(), - Some(inputs_shmem_writer.clone()), - ) - .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) - .build()?, - hints_file, + let mut builder = HintsProcessor::builder( + hints_file.clone(), + Some(inputs_shmem_writer.clone()), ) + .enable_stats(verbose_mode != proofman_common::VerboseMode::Info); + + if let Some(broadcast_fn) = mpi_broadcast_fn.clone() { + builder = builder.with_mpi_broadcast(move |data| broadcast_fn(data)); + } + + (builder.build().expect("Failed to build HintsProcessor"), hints_file) }; (Some(Arc::new(Mutex::new(ZiskStream::new(hints_processor)))), Some(hints_sink)) @@ -175,6 +180,10 @@ impl AsmResources { self.hints_stream_initialized.load(Ordering::SeqCst) } + pub fn get_hints_processor(&self) -> Option> { + self.hints_stream.as_ref().map(|stream| stream.lock().unwrap().get_processor()) + } + pub fn reset(&self) { if let Some(hints_stream) = &self.hints_stream { hints_stream.lock().unwrap().reset(); diff --git a/precompiles/hints/benches/hints_benchmarks.rs b/precompiles/hints/benches/hints_benchmarks.rs index fefb7f1a0..9eae01729 100644 --- a/precompiles/hints/benches/hints_benchmarks.rs +++ b/precompiles/hints/benches/hints_benchmarks.rs @@ -12,8 +12,8 @@ struct BenchSink { } impl StreamSink for BenchSink { - fn submit(&self, processed: Vec) -> Result<()> { - self.received.lock().unwrap().push(processed); + fn submit(&self, processed: &[u64]) -> Result<()> { + self.received.lock().unwrap().push(processed.to_vec()); Ok(()) } } @@ -46,7 +46,7 @@ fn parallel_speedup_benchmark(c: &mut Criterion) { b.iter(|| { let received = Arc::new(Mutex::new(Vec::new())); let received_clone = received.clone(); - let sink = BenchSink { received: received_clone }; + let sink = Arc::new(BenchSink { received: received_clone }); let p = HintsProcessor::builder(sink, None::>) .num_threads(threads) @@ -119,7 +119,7 @@ fn microsecond_hints_benchmark(c: &mut Criterion) { b.iter(|| { let received = Arc::new(Mutex::new(Vec::new())); let received_clone = received.clone(); - let sink = BenchSink { received: received_clone }; + let sink = Arc::new(BenchSink { received: received_clone }); let p = HintsProcessor::builder(sink, None::>) .num_threads(16) @@ -171,7 +171,7 @@ fn workload_patterns_benchmark(c: &mut Criterion) { b.iter(|| { let received = Arc::new(Mutex::new(Vec::new())); let received_clone = received.clone(); - let sink = BenchSink { received: received_clone }; + let sink = Arc::new(BenchSink { received: received_clone }); let p = HintsProcessor::builder(sink, None::>) .num_threads(8) @@ -225,7 +225,7 @@ fn noop_throughput_benchmark(c: &mut Criterion) { struct NullSink; impl StreamSink for NullSink { - fn submit(&self, _processed: Vec) -> Result<()> { + fn submit(&self, _processed: &[u64]) -> Result<()> { Ok(()) } } @@ -241,7 +241,7 @@ fn noop_throughput_benchmark(c: &mut Criterion) { for &count in &hint_counts { group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, &num_hints| { b.iter(|| { - let p = HintsProcessor::builder(NullSink, None::>) + let p = HintsProcessor::builder(Arc::new(NullSink), None::>) .num_threads(32) .build() .unwrap(); diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index bb9eab904..de3e99a55 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -186,7 +186,7 @@ impl HintsProcessorBuilder { stream_active: AtomicBool::new(false), instant: Mutex::new(None), pending_partial: Mutex::new(None), - mpi_broadcast_fn: self.mpi_broadcast_fn, + mpi_broadcast_fn: self.mpi_broadcast_fn.clone(), }) } } @@ -229,7 +229,7 @@ pub struct HintsProcessor { /// Buffer for incomplete hint data between batches pending_partial: Mutex>, - /// Optional MPI broadcast function for initialization synchronization + /// Optional MPI broadcast function for distributed execution mpi_broadcast_fn: Option, } @@ -265,21 +265,6 @@ impl HintsProcessor { } } - /// Executes the MPI broadcast callback if one was configured. - /// - /// This allows manual control over when MPI synchronization occurs. - /// - /// # Returns - /// - /// * `Ok(())` - Broadcast completed successfully or no callback configured - /// * `Err` - If the broadcast callback returns an error - pub fn mpi_broadcast(&self, data: &mut Vec) -> Result<()> { - if let Some(broadcast_fn) = &self.mpi_broadcast_fn { - broadcast_fn(data)?; - } - Ok(()) - } - /// Processes hints in parallel with non-blocking, ordered output. /// /// This method dispatches each hint to the thread pool for parallel processing. @@ -430,13 +415,16 @@ impl HintsProcessor { // If the hint is an input hint, write it to the inputs sink instead of processing if hint.hint_code == HintCode::BuiltIn(BuiltInHint::Input) { - let mut serialized = borsh::to_vec(&( - JobPhase::ContributionsInputsStream, - StreamMessage { data: hint.data.clone() }, - )) - .unwrap(); + if let Some(broadcast_fn) = &self.mpi_broadcast_fn { + let mut serialized = borsh::to_vec(&( + JobPhase::ContributionsInputsStream, + StreamMessage { data: hint.data.clone() }, + )) + .unwrap(); + + broadcast_fn(&mut serialized).expect("MPI broadcast failed for input hint"); + } - self.mpi_broadcast(&mut serialized)?; self.inputs_sink .as_ref() .ok_or_else(|| { @@ -833,6 +821,10 @@ impl StreamProcessor for HintsProcessor { fn reset(&self) { self.reset(); } + + fn as_any(self: Arc) -> Arc { + self + } } #[cfg(test)] @@ -1746,7 +1738,7 @@ mod tests { const VARIABLE_HINT: u32 = 0x7FFF_0100; - let p = HintsProcessor::builder(Arc::new(sink), None::>) + let p = HintsProcessor::builder(Arc::new(sink), None::>) .num_threads(16) .custom_hint(VARIABLE_HINT, |data| { // Pseudo-random delay based on hash of input value (0-15ms range) diff --git a/precompiles/hints/src/lib.rs b/precompiles/hints/src/lib.rs index 82eefa35c..02826fe01 100644 --- a/precompiles/hints/src/lib.rs +++ b/precompiles/hints/src/lib.rs @@ -1,3 +1,3 @@ mod hints_processor; -pub use hints_processor::HintsProcessor; +pub use hints_processor::{HintsProcessor, MpiBroadcastFn}; diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 1b81c4f32..76f6b9c29 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -61,6 +61,7 @@ pub struct ProverClientBuilder { unlock_mapped_memory: bool, asm_out_file: bool, no_auto_setup: bool, + is_distributed: bool, // Prove-specific fields (only available when Operation = Prove) gpu_params: ParamsGPU, @@ -219,6 +220,12 @@ impl ProverClientBuilder { self } + #[must_use] + pub fn is_distributed(mut self, is_distributed: bool) -> Self { + self.is_distributed = is_distributed; + self + } + #[must_use] pub fn base_port(mut self, base_port: u16) -> Self { self.base_port = Some(base_port); @@ -423,6 +430,7 @@ impl ProverClientBuilder { self.asm_out_file, self.no_auto_setup, self.gpu_params, + self.is_distributed, self.logging_config, )? }; @@ -464,6 +472,7 @@ impl From> for ProverClientBuilder { asm_out_file: false, no_auto_setup: false, + is_distributed: false, _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -494,6 +503,7 @@ impl From> for ProverClientBuilder { unlock_mapped_memory: builder.unlock_mapped_memory, asm_out_file: builder.asm_out_file, no_auto_setup: builder.no_auto_setup, + is_distributed: builder.is_distributed, _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -526,6 +536,7 @@ impl From> unlock_mapped_memory: builder.unlock_mapped_memory, asm_out_file: builder.asm_out_file, no_auto_setup: builder.no_auto_setup, + is_distributed: builder.is_distributed, _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -556,6 +567,7 @@ impl From> for ProverClientBuilder, ) -> Result { let core_prover = AsmCoreProver::new( @@ -68,6 +69,7 @@ impl AsmProver { asm_out_file, no_auto_setup, gpu_params, + is_distributed, logging_config, )?; @@ -113,6 +115,7 @@ impl ProverEngine for AsmProver { let world_rank = self.core_prover.rank_info.world_rank; let local_rank = self.core_prover.rank_info.local_rank; let n_processes = self.core_prover.rank_info.n_processes; + let is_distributed = self.core_prover.is_distributed; let unlock_mapped_memory = self.core_prover.unlock_mapped_memory; let asm_out_file = self.core_prover.asm_out_file; let verbose_mode = self.core_prover.verbose; @@ -183,12 +186,21 @@ impl ProverEngine for AsmProver { asm_services.start_asm_services(&asm_mt_path, asm_runner_options)?; timer_stop_and_log_info!(STARTING_ASM_MICROSERVICES); + let mpi_broadcast_fn = (is_distributed && n_processes > 1 && elf.with_hints()).then(|| { + let pctx = pctx.clone(); + Arc::new(move |data: &mut Vec| { + pctx.mpi_ctx.broadcast(data); + Ok(()) + }) as Arc) -> Result<()> + Send + Sync> + }); + let asm_resources = AsmResources::new( local_rank, base_port, unlock_mapped_memory, verbose_mode, elf.with_hints(), + mpi_broadcast_fn, )?; self.n_setups.fetch_add(1, Ordering::SeqCst); @@ -356,6 +368,7 @@ impl ProverEngine for AsmProver { pub struct AsmCoreProver { backend: ProverBackend, rank_info: RankInfo, + is_distributed: bool, base_port: Option, unlock_mapped_memory: bool, asm_out_file: bool, @@ -378,6 +391,7 @@ impl AsmCoreProver { asm_out_file: bool, no_auto_setup: bool, gpu_params: ParamsGPU, + is_distributed: bool, logging_config: Option, ) -> Result { check_paths_exist(&proving_key)?; @@ -437,6 +451,7 @@ impl AsmCoreProver { asm_out_file, verbose: verbose.into(), no_auto_setup, + is_distributed, }) } @@ -452,6 +467,7 @@ impl AsmCoreProver { asm_out_file: false, verbose: VerboseMode::Info, no_auto_setup: false, + is_distributed: false, }) } } diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index e867b46bf..635b0f001 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -28,7 +28,9 @@ use tracing::info; use zisk_common::io::StreamSource; use zisk_common::ElfBinaryLike; use zisk_common::ZiskExecutorTime; -use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, StatsCostPerType, ZiskExecutorSummary}; +use zisk_common::{ + io::StreamProcessor, io::ZiskStdin, ExecutorStatsHandle, StatsCostPerType, ZiskExecutorSummary, +}; use zisk_core::ZiskRom; pub struct ZiskExecuteResult { @@ -152,6 +154,20 @@ impl ZiskProgramPK { pub fn is_asm(&self) -> bool { self.asm_services.is_some() } + + pub fn get_hints_processor(&self) -> Option> { + if let Some(asm_resources) = &self.asm_resources { + asm_resources.get_hints_processor() + } else { + None + } + } + + pub fn reset(&self) { + if let Some(asm_resources) = &self.asm_resources { + asm_resources.reset(); + } + } } impl Drop for ZiskProgramPK { From 0b5532b69186e65ac74ba6b0204fda6b342b7152 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Tue, 10 Mar 2026 06:24:50 +0100 Subject: [PATCH 741/782] restore original non-sequencial assembly startup --- .../asm-runner/src/asm_services/services.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index f0615b804..4c2dfbead 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -110,14 +110,14 @@ impl AsmServices { self.start_asm_service(service, trimmed_path, &options, &shm_prefix); - // if i == 0 { - // For the first service, wait until it is ready before starting the others, - // since it may initialize the shared memory used by the rest. - Self::wait_for_service_ready(self.world_rank, service, port)?; - // } else { - // For the other services, we can start them in parallel and wait for them after - // pending_wait.push((service, port)); - // } + if i == 0 { + // For the first service, wait until it is ready before starting the others, + // since it may initialize the shared memory used by the rest. + Self::wait_for_service_ready(self.world_rank, service, port)?; + } else { + // For the other services, we can start them in parallel and wait for them after + pending_wait.push((service, port)); + } } // Wait for the remaining services to be ready From 96a5fbf3617c0d9516f5cc71e21ae16548d09c9c Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Tue, 10 Mar 2026 08:31:51 +0100 Subject: [PATCH 742/782] remove pclog trace --- emulator/src/emu.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index e9156231f..29e525b64 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -1927,7 +1927,6 @@ impl<'a> Emu<'a> { let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); let pc = self.ctx.inst_ctx.pc; - println!("PCLOG={}", instruction.to_text()); // Build the 'a' register value based on the source specified by the current instruction self.source_a(instruction); From 3fdf98bef2e33d0057c060660d5d7f5ea7be5520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Tue, 10 Mar 2026 09:40:04 +0100 Subject: [PATCH 743/782] Test/no hasher hints (#839) * Disable hasher hints * Tentative fix mpi * Tentative fix mpi hints distributed * Hints working with MPI * Cargo update * Test/no hasher hints debug (#838) * Debug 1 * fix mpi distribution context * set has_rom_sm when it should be and by default false * reset has_rom_sm in hints_shmem.rs * Removing unnecessary logs * temporal fix to use all separate en hints_shmem.rs * Tentative fix --------- Co-authored-by: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> * Fix macos --------- Co-authored-by: agnusmor Co-authored-by: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> --- Cargo.lock | 142 ++++++++------- Cargo.toml | 16 +- common/src/io/stream/zisk_stream.rs | 14 +- core/src/zisk_rom_2_asm.rs | 9 +- distributed/crates/common/src/types.rs | 29 ++- .../crates/coordinator/src/hints_relay.rs | 4 + distributed/crates/worker/src/worker.rs | 166 ++++++++---------- distributed/crates/worker/src/worker_node.rs | 7 +- .../asm-runner/src/asm_services/services.rs | 2 +- emulator-asm/asm-runner/src/hints_file.rs | 2 +- emulator-asm/asm-runner/src/hints_shmem.rs | 8 +- .../asm-runner/src/hints_shmem_stub.rs | 2 +- examples/Cargo.lock | 55 +++--- executor/src/asm_resources.rs | 68 ++++--- executor/src/emu_asm.rs | 2 +- executor/src/planner.rs | 2 +- precompiles/hints/Cargo.toml | 2 + precompiles/hints/benches/hints_benchmarks.rs | 15 +- precompiles/hints/src/hints_processor.rs | 96 +++++++--- precompiles/hints/src/lib.rs | 2 +- sdk/src/builder.rs | 12 ++ sdk/src/prover/asm.rs | 20 ++- sdk/src/prover/backend.rs | 9 - sdk/src/prover/emu.rs | 4 - sdk/src/prover/mod.rs | 24 ++- ziskos/entrypoint/src/hints/blake2b.rs | 39 +--- ziskos/entrypoint/src/hints/keccak256.rs | 12 +- ziskos/entrypoint/src/hints/sha256f.rs | 12 +- 28 files changed, 429 insertions(+), 346 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4597d554b..299fe5464 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -604,7 +604,7 @@ dependencies = [ "colored", "dirs", "executor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "futures", "indicatif", "mpi", @@ -616,7 +616,7 @@ dependencies = [ "rom-setup", "serde", "serde_json", - "sysinfo 0.38.0", + "sysinfo 0.38.4", "target-lexicon", "tokio", "tracing", @@ -1116,9 +1116,9 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "num-bigint", "num-traits", ] @@ -1415,7 +1415,7 @@ dependencies = [ "asm-runner", "crossbeam", "data-bus", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "itertools 0.14.0", "mem-common", "mem-planner-cpp", @@ -1470,7 +1470,18 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" +dependencies = [ + "cfg-if", + "num-bigint", + "paste", + "serde", +] + +[[package]] +name = "fields" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "cfg-if", "num-bigint", @@ -2212,9 +2223,9 @@ version = "0.16.0" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libffi" @@ -2337,7 +2348,7 @@ name = "mem-common" version = "0.16.0" dependencies = [ "clap", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "num-bigint", "num-traits", "proofman-common", @@ -2764,10 +2775,10 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" dependencies = [ "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "num-bigint", "num-traits", "proofman-common", @@ -2886,7 +2897,7 @@ dependencies = [ "ark-secp256k1", "ark-secp256r1", "ark-std", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "lazy_static", "lib-c", "mem-common", @@ -2922,7 +2933,7 @@ dependencies = [ "ark-ff", "ark-secp256k1", "ark-std", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "lazy_static", "lib-c", "mem-common", @@ -2953,7 +2964,7 @@ dependencies = [ name = "precomp-big-int" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "generic-array", "lib-c", "mem-common", @@ -2974,7 +2985,7 @@ dependencies = [ name = "precomp-blake2" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "mem-common", "pil-std-lib", "precompiles-common", @@ -2993,7 +3004,7 @@ dependencies = [ name = "precomp-dma" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "generic-array", "lib-c", "mem-common", @@ -3017,7 +3028,7 @@ name = "precomp-keccakf" version = "0.16.0" dependencies = [ "circuit", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "path-clean", "pil-std-lib", "precompiles-common", @@ -3037,7 +3048,7 @@ dependencies = [ name = "precomp-poseidon2" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "mem-common", "pil-std-lib", "precompiles-common", @@ -3057,7 +3068,7 @@ dependencies = [ name = "precomp-sha256f" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "mem-common", "pil-std-lib", "precompiles-common", @@ -3076,7 +3087,7 @@ dependencies = [ name = "precompiles-common" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "mem-common", "sm-mem", "zisk-common", @@ -3105,6 +3116,7 @@ name = "precompiles-hints" version = "0.16.0" dependencies = [ "anyhow", + "borsh", "criterion 0.8.2", "lib-c", "precompiles-helpers", @@ -3112,6 +3124,7 @@ dependencies = [ "rustls", "tracing", "zisk-common", + "zisk-distributed-common", "ziskos-hints", ] @@ -3146,7 +3159,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" dependencies = [ "bincode", "blake3", @@ -3157,7 +3170,7 @@ dependencies = [ "crossbeam-channel", "csv", "curves", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "libloading", "mpi", "num-bigint", @@ -3169,7 +3182,6 @@ dependencies = [ "proofman-starks-lib-c", "proofman-util", "proofman-verifier", - "rand 0.9.2", "rayon", "serde", "serde_json", @@ -3182,7 +3194,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" dependencies = [ "bincode", "borsh", @@ -3192,7 +3204,7 @@ dependencies = [ "crossbeam-queue", "csv", "env", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "lazy_static", "libloading", "mpi", @@ -3213,9 +3225,9 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "itoa", "proofman-common", "proofman-starks-lib-c", @@ -3226,7 +3238,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" dependencies = [ "proc-macro2", "quote", @@ -3236,7 +3248,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" dependencies = [ "crossbeam-channel", "tracing", @@ -3245,7 +3257,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" dependencies = [ "bincode", "bytemuck", @@ -3257,10 +3269,10 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" dependencies = [ "bytemuck", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "proofman-util", "rayon", "tracing", @@ -3361,9 +3373,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "fastbloom", @@ -3625,7 +3637,7 @@ dependencies = [ "anyhow", "blake3", "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "proofman-common", "sm-rom", "tracing", @@ -4047,7 +4059,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "sm-arith" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "num-bigint", "pil-std-lib", "proofman-common", @@ -4067,7 +4079,7 @@ dependencies = [ name = "sm-binary" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "num-bigint", "pil-std-lib", "proofman-common", @@ -4087,7 +4099,7 @@ name = "sm-frequent-ops" version = "0.16.0" dependencies = [ "clap", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "num-bigint", "proofman-common", "proofman-util", @@ -4101,7 +4113,7 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "mem-common", "num-bigint", "pil-std-lib", @@ -4120,7 +4132,7 @@ dependencies = [ name = "sm-mem" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "mem-common", "num-bigint", "num-traits", @@ -4141,7 +4153,7 @@ name = "sm-rom" version = "0.16.0" dependencies = [ "asm-runner", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "itertools 0.14.0", "proofman-common", "proofman-macros", @@ -4161,12 +4173,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4265,9 +4277,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.38.0" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe840c5b1afe259a5657392a4dbb74473a14c8db999c3ec2f4ae812e028a94da" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" dependencies = [ "libc", "memchr", @@ -4894,9 +4906,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -5611,9 +5623,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] @@ -5709,10 +5721,10 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" dependencies = [ "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "libloading", "proofman-common", "proofman-util", @@ -5795,18 +5807,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", @@ -5905,7 +5917,7 @@ version = "0.16.0" dependencies = [ "anyhow", "bincode", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "libc", "mpi", "proofman", @@ -5928,7 +5940,7 @@ name = "zisk-core" version = "0.16.0" dependencies = [ "elf", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "lib-c", "lib-float", "paste", @@ -6031,7 +6043,7 @@ dependencies = [ "clap", "colored", "config", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "precompiles-hints", "proofman", "proofman-common", @@ -6057,7 +6069,7 @@ dependencies = [ name = "zisk-pil" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "proofman-common", "proofman-macros", "rayon", @@ -6074,7 +6086,7 @@ dependencies = [ "bincode", "colored", "executor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "proofman", "proofman-common", "proofman-util", @@ -6114,7 +6126,7 @@ dependencies = [ "clap", "criterion 0.5.1", "data-bus", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "mem-common", "memmap2", "num-format", @@ -6127,7 +6139,7 @@ dependencies = [ "sm-binary", "symbolic-common", "symbolic-demangle", - "sysinfo 0.38.0", + "sysinfo 0.38.4", "vergen-git2", "zisk-common", "zisk-core", @@ -6143,7 +6155,7 @@ dependencies = [ "bytes", "cfg-if", "ctor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -6169,7 +6181,7 @@ dependencies = [ "anyhow", "bincode", "cfg-if", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", "getrandom 0.2.17", "lazy_static", "lib-c", diff --git a/Cargo.toml b/Cargo.toml index 2704f738f..8ba8cc2c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,14 +112,14 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } # Proofman Local development # proofman = { path = "../pil2-proofman/proofman" } # proofman-common = { path = "../pil2-proofman/common" } diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index 2c07b2f98..ab1294ee4 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -1,6 +1,7 @@ //! ZiskStream is responsible for reading precompile hints from a stream source and sent to a hints processor. use anyhow::Result; +use std::any::Any; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Sender}; use std::sync::Arc; @@ -8,7 +9,7 @@ use std::thread::{self, JoinHandle}; use crate::io::{StreamRead, StreamSource}; -pub trait StreamProcessor: Send + Sync + 'static { +pub trait StreamProcessor: Any + Send + Sync + 'static { /// Process data and return the processed result along with a flag indicating if CTRL_END was encountered. /// /// # Returns @@ -18,18 +19,21 @@ pub trait StreamProcessor: Send + Sync + 'static { fn process(&self, data: &[u64], first_batch: bool) -> anyhow::Result; fn reset(&self) {} + + /// Convert Arc to Arc for downcasting + fn as_any(self: Arc) -> Arc; } /// Trait for submitting processed hints to a sink. /// /// # Arguments -/// * `processed` - A vector of processed hints as u64 values. +/// * `processed` - A slice of processed hints as u64 values. /// /// # Returns /// * `Ok(())` - If hints were successfully submitted /// * `Err` - If submission fails pub trait StreamSink: Send + Sync + 'static { - fn submit(&self, processed: Vec) -> anyhow::Result<()>; + fn submit(&self, processed: &[u64]) -> anyhow::Result<()>; fn reset(&self) {} @@ -182,6 +186,10 @@ impl ZiskStream { Err(anyhow::anyhow!("No background thread running. Call set_hints_stream first.")) } } + + pub fn get_processor(&self) -> Arc { + self.hints_processor.clone() + } } impl Drop for ZiskStream { diff --git a/core/src/zisk_rom_2_asm.rs b/core/src/zisk_rom_2_asm.rs index ce5c61cce..dd7c75f9a 100644 --- a/core/src/zisk_rom_2_asm.rs +++ b/core/src/zisk_rom_2_asm.rs @@ -253,10 +253,12 @@ impl ZiskAsmContext { self.precompile_results } pub fn precompile_results_keccak(&self) -> bool { - self.precompile_results() + //self.precompile_results() + false } pub fn precompile_results_sha256(&self) -> bool { - self.precompile_results() + //self.precompile_results() + false } pub fn precompile_results_arith256(&self) -> bool { self.precompile_results() @@ -316,7 +318,8 @@ impl ZiskAsmContext { self.precompile_results() } pub fn precompile_results_blake2(&self) -> bool { - self.precompile_results() + //self.precompile_results() + false } pub fn call_wait_for_prec_avail(&self) -> bool { self.precompile_results() diff --git a/distributed/crates/common/src/types.rs b/distributed/crates/common/src/types.rs index e01f8f7cc..37bf6a308 100644 --- a/distributed/crates/common/src/types.rs +++ b/distributed/crates/common/src/types.rs @@ -6,7 +6,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use chrono::{DateTime, Utc}; -use proofman::{ContributionsInfo, WitnessInfo}; +use proofman::{ContributionsInfo, ProvePhaseInputs, WitnessInfo}; +use proofman_common::ProofOptions; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -423,6 +424,7 @@ pub enum JobPhase { Contributions, Prove, Aggregate, + ContributionsHintsStream, } impl TryFrom for JobPhase { @@ -433,6 +435,7 @@ impl TryFrom for JobPhase { 0 => Ok(JobPhase::Contributions), 1 => Ok(JobPhase::Prove), 2 => Ok(JobPhase::Aggregate), + 3 => Ok(JobPhase::ContributionsHintsStream), _ => Err(anyhow::anyhow!("Invalid JobPhase byte: {}", value)), } } @@ -444,6 +447,7 @@ impl fmt::Display for JobPhase { JobPhase::Contributions => write!(f, "Contributions"), JobPhase::Prove => write!(f, "Prove"), JobPhase::Aggregate => write!(f, "Aggregate"), + JobPhase::ContributionsHintsStream => write!(f, "ContributionsHintsStream"), } } } @@ -467,3 +471,26 @@ pub struct PartitionInfo { pub allocation: Vec, pub worker_idx: usize, } + +/// Message structures for MPI broadcast to ensure type safety +#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] +pub struct ContributionsMessage { + pub job_id: JobId, + pub phase_inputs: ProvePhaseInputs, + pub options: ProofOptions, + pub input_source: InputSourceDto, + pub hints_source: HintsSourceDto, + pub partition_info: PartitionInfo, +} + +#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] +pub struct ProveMessage { + pub job_id: JobId, + pub phase_inputs: ProvePhaseInputs, + pub options: ProofOptions, +} + +#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] +pub struct StreamMessage { + pub data: Vec, +} diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index bc974ab1f..1199584f9 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -155,4 +155,8 @@ impl StreamProcessor for PrecompileHintsRelay { fn reset(&self) { self.reset_state(); } + + fn as_any(self: Arc) -> Arc { + self + } } diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index 19186180a..db461ef6c 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use asm_runner::HintsShmem; use cargo_zisk::commands::get_proving_key; use precompiles_hints::HintsProcessor; use proofman::{AggProofs, AggProofsRegister, ContributionsInfo}; @@ -8,11 +7,12 @@ use std::fs; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; -use zisk_common::io::{StreamProcessor, StreamSource, ZiskStdin}; +use zisk_common::io::{StreamSource, ZiskStdin}; use zisk_common::ElfBinaryFromFile; use zisk_common::ZiskExecutorTime; use zisk_distributed_common::{AggregationParams, DataCtx, InputSourceDto, JobPhase, WorkerState}; use zisk_distributed_common::{ComputeCapacity, JobId, PartitionInfo, WorkerId}; +use zisk_distributed_common::{ContributionsMessage, ProveMessage, StreamMessage}; use zisk_distributed_common::{HintsSourceDto, StreamDataDto, StreamMessageKind}; use zisk_sdk::{Asm, Emu, ProverClient, ZiskBackend, ZiskProgramPK, ZiskProver}; @@ -28,24 +28,6 @@ use tracing::{error, info}; use crate::config::ProverServiceConfigDto; -/// Message structures for MPI broadcast to ensure type safety -#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] -struct ContributionsMessage { - job_id: JobId, - phase_inputs: ProvePhaseInputs, - options: ProofOptions, - input_source: InputSourceDto, - hints_source: HintsSourceDto, - partition_info: PartitionInfo, -} - -#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)] -struct ProveMessage { - job_id: JobId, - phase_inputs: ProvePhaseInputs, - options: ProofOptions, -} - /// Result from computation tasks #[derive(Debug)] pub enum ComputationResult { @@ -267,7 +249,6 @@ pub struct Worker { stream_actor: Option, pk: Arc, - hints_processor: Option>, } impl Worker { @@ -301,7 +282,6 @@ impl Worker { prover_config, pk: Arc::new(pk), stream_actor: None, - hints_processor: None, }) } @@ -322,6 +302,7 @@ impl Worker { .base_port_opt(prover_config.asm_port) .unlock_mapped_memory(prover_config.unlock_mapped_memory) .gpu(prover_config.gpu_params.clone()) + .is_distributed(true) .build()?, ); @@ -338,7 +319,6 @@ impl Worker { prover_config, pk: Arc::new(pk), stream_actor: None, - hints_processor: None, }) } @@ -641,18 +621,31 @@ impl Worker { /// - `Data` / `End`: enqueues the message into the actor's channel — O(1), non-blocking. /// /// The actor thread owns the reorder buffer and calls `process_hints` in sequence order. - pub async fn route_stream_data(&mut self, stream_data: StreamDataDto) -> Result<()> { + pub async fn route_stream_data( + &mut self, + stream_data: StreamDataDto, + is_first_partition: bool, + ) -> Result<()> { match &stream_data.stream_type { StreamMessageKind::Start => { let job_id = stream_data.job_id.clone(); - self.ensure_hints_processor().await?; + self.pk.reset(); - let hints_processor = self.hints_processor.as_ref().unwrap(); + let processor_trait = self.pk.get_hints_processor().ok_or_else(|| { + anyhow::anyhow!("HintsProcessor not found for job {}", job_id) + })?; - hints_processor.reset(); + // Downcast Arc to Arc + let hints_processor = + processor_trait.as_any().downcast::().map_err(|_| { + anyhow::anyhow!( + "Failed to downcast StreamProcessor to HintsProcessor for job {}", + job_id + ) + })?; - let hints_processor = Arc::clone(hints_processor); + hints_processor.set_has_rom_sm(is_first_partition); // Replace any existing actor (handles reconnect / job restart) self.stream_actor = Some(StreamOrderingActor::new(hints_processor, job_id)); @@ -671,10 +664,6 @@ impl Worker { Ok(()) } - pub fn is_first_partition(&self) -> Result { - self.prover.is_first_partition() - } - pub fn set_partition( &self, total_compute_units: usize, @@ -684,34 +673,6 @@ impl Worker { self.prover.set_partition(total_compute_units, allocation, worker_idx) } - /// Lazily initialises the `HintsProcessor` using configuration from the current job. - async fn ensure_hints_processor(&mut self) -> Result<()> { - if self.hints_processor.is_some() { - return Ok(()); - } - - let base_port = self.prover_config.asm_port; - let local_rank = self.prover.local_rank(); - let unlock_mapped_memory = self.prover_config.unlock_mapped_memory; - - // HintsShmem::new and HintsProcessor::build perform OS-level shared-memory operations; - // run them on the blocking thread pool to avoid stalling Tokio workers. - let processor = tokio::task::spawn_blocking(move || -> Result { - let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory)?; - HintsProcessor::builder(hints_shmem) - .build() - .map_err(|e| anyhow::anyhow!("Failed to initialize hints processor: {}", e)) - }) - .await - .map_err(|e| anyhow::anyhow!("hints processor init panicked: {e}"))??; - - processor.set_has_rom_sm(self.prover.is_first_partition()?); - - self.hints_processor = Some(Arc::new(processor)); - - Ok(()) - } - pub fn prove( &self, job: Arc>, @@ -885,45 +846,66 @@ impl Worker { self.prover.mpi_broadcast(&mut bytes)?; // extract byte 0 to decide the option - let phase = borsh::from_slice(&bytes[0..1]).unwrap(); - - match phase { - JobPhase::Contributions => { - let message: ContributionsMessage = borsh::from_slice(&bytes[1..]).unwrap(); - - let result = Self::execute_contribution_task( - message.job_id, - &self.prover, - message.phase_inputs, - message.input_source, - message.hints_source, - message.partition_info, - &self.pk, - message.options, - ); - if let Err(e) = result { - error!("Error during Contributions MPI broadcast execution: {}. Waiting for new job...", e); + let phase: JobPhase = borsh::from_slice(&bytes[0..1]).unwrap(); + + let prover = self.prover.clone(); + let pk = self.pk.clone(); + let options = self.get_proof_options(false); + + if phase == JobPhase::ContributionsHintsStream { + if let Some(hints_sink) = pk.asm_resources.as_ref().and_then(|r| r.hints_sink.clone()) { + let message: StreamMessage = borsh::from_slice(&bytes[1..]).unwrap(); + if let Err(e) = hints_sink.submit(&message.data) { + tracing::error!("Failed to submit hints: {}", e); } + } else { + tracing::error!("Hints sink is not configured for ContributionsHintsStream"); } - JobPhase::Prove => { - let message: ProveMessage = borsh::from_slice(&bytes[1..]).unwrap(); - - let result = Self::execute_prove_task( - message.job_id, - &self.prover, - message.phase_inputs, - message.options, - ); - if let Err(e) = result { - error!( + } else { + tokio::task::spawn_blocking(move || { + match phase { + JobPhase::Contributions => { + let message: ContributionsMessage = borsh::from_slice(&bytes[1..]).unwrap(); + + let result = Self::execute_contribution_task( + message.job_id, + &prover, + message.phase_inputs, + message.input_source, + message.hints_source, + message.partition_info, + &pk, + message.options, + ); + if let Err(e) = result { + tracing::error!( + "Error during Contributions MPI broadcast execution: {}. Waiting for new job...", + e + ); + } + } + JobPhase::Prove => { + let message: ProveMessage = borsh::from_slice(&bytes[1..]).unwrap(); + + let result = Self::execute_prove_task( + message.job_id, + &prover, + message.phase_inputs, + options, + ); + if let Err(e) = result { + error!( "Error during Prove MPI broadcast execution: {}. Waiting for new job...", e ); + } } + JobPhase::Aggregate => { + unreachable!("Aggregate phase is not supported in MPI broadcast"); + } + JobPhase::ContributionsHintsStream => unreachable!("ContributionsHintsStream is handled separately and should not reach this point"), } - JobPhase::Aggregate => { - unreachable!("Aggregate phase is not supported in MPI broadcast"); - } + }); } Ok(()) } diff --git a/distributed/crates/worker/src/worker_node.rs b/distributed/crates/worker/src/worker_node.rs index 82cc693bc..d1ce46282 100644 --- a/distributed/crates/worker/src/worker_node.rs +++ b/distributed/crates/worker/src/worker_node.rs @@ -772,7 +772,10 @@ impl WorkerNodeGrpc { } let job = self.worker.current_job().unwrap(); - let current_job_id = job.lock().await.job_id.clone(); + let (current_job_id, is_first_partition) = { + let job_guard = job.lock().await; + (job_guard.job_id.clone(), job_guard.allocation.contains(&0)) + }; let stream_data_dto: StreamDataDto = stream_data.into(); @@ -784,6 +787,6 @@ impl WorkerNodeGrpc { )); } - self.worker.route_stream_data(stream_data_dto).await + self.worker.route_stream_data(stream_data_dto, is_first_partition).await } } diff --git a/emulator-asm/asm-runner/src/asm_services/services.rs b/emulator-asm/asm-runner/src/asm_services/services.rs index 0f9fc4a32..d753339dc 100644 --- a/emulator-asm/asm-runner/src/asm_services/services.rs +++ b/emulator-asm/asm-runner/src/asm_services/services.rs @@ -135,7 +135,7 @@ impl AsmServices { self.start_asm_service(service, trimmed_path, &options, &shm_prefix); } - for service in &Self::SERVICES { + for service in &Self::SERVICES[1..] { Self::wait_for_service_ready( service, Self::port_for(service, self.base_port, self.local_rank), diff --git a/emulator-asm/asm-runner/src/hints_file.rs b/emulator-asm/asm-runner/src/hints_file.rs index ebb208eb6..a00bfe17b 100644 --- a/emulator-asm/asm-runner/src/hints_file.rs +++ b/emulator-asm/asm-runner/src/hints_file.rs @@ -40,7 +40,7 @@ impl StreamSink for HintsFile { /// * `Ok(())` - If hints were successfully written to the file /// * `Err` - If writing to the file fails #[inline] - fn submit(&self, processed: Vec) -> anyhow::Result<()> { + fn submit(&self, processed: &[u64]) -> anyhow::Result<()> { let mut file = self.file.lock().unwrap(); // Write each u64 as 8 bytes (little-endian) diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index 5a78639d0..e410c4029 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -110,7 +110,7 @@ impl HintsShmem { Ok(Self { unified: RefCell::new(unified), separate: RefCell::new(separate), - has_rom_sm: AtomicBool::new(true), + has_rom_sm: AtomicBool::new(false), }) } @@ -186,7 +186,7 @@ impl StreamSink for HintsShmem { /// * `Ok(())` - If hints were successfully written to shared memory /// * `Err` - If writing to shared memory fails #[inline] - fn submit(&self, processed: Vec) -> anyhow::Result<()> { + fn submit(&self, processed: &[u64]) -> anyhow::Result<()> { let data_size = processed.len() as u64; // Early return for empty data @@ -258,7 +258,7 @@ impl StreamSink for HintsShmem { } // Write data ONCE to the unified shared memory buffer - unified.data_writer.write_ring_buffer(&processed)?; + unified.data_writer.write_ring_buffer(processed)?; fence(Ordering::Release); @@ -292,6 +292,8 @@ impl StreamSink for HintsShmem { "Control reader position should be reset to 0" ); } + + self.has_rom_sm.store(false, std::sync::atomic::Ordering::SeqCst); } fn set_has_rom_sm(&self, has_rom_sm: bool) { diff --git a/emulator-asm/asm-runner/src/hints_shmem_stub.rs b/emulator-asm/asm-runner/src/hints_shmem_stub.rs index 202d8a35e..d8c0afcbe 100644 --- a/emulator-asm/asm-runner/src/hints_shmem_stub.rs +++ b/emulator-asm/asm-runner/src/hints_shmem_stub.rs @@ -17,7 +17,7 @@ impl HintsShmem { } impl StreamSink for HintsShmem { - fn submit(&self, _processed: Vec) -> anyhow::Result<()> { + fn submit(&self, _processed: &[u64]) -> anyhow::Result<()> { unreachable!( "HintsShmem::submit() is not supported on this platform. Only Linux x86_64 is supported." ); diff --git a/examples/Cargo.lock b/examples/Cargo.lock index d10a8cb0f..710537cc3 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -814,7 +814,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "fields", "num-bigint", @@ -1124,7 +1124,7 @@ dependencies = [ [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "cfg-if", "num-bigint", @@ -1552,9 +1552,9 @@ version = "0.16.0" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libffi" @@ -1979,7 +1979,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "colored", "fields", @@ -2260,12 +2260,14 @@ name = "precompiles-hints" version = "0.16.0" dependencies = [ "anyhow", + "borsh", "lib-c", "precompiles-helpers", "rayon", "rustls", "tracing", "zisk-common", + "zisk-distributed-common", "ziskos-hints", ] @@ -2300,7 +2302,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "bincode", "blake3", @@ -2323,7 +2325,6 @@ dependencies = [ "proofman-starks-lib-c", "proofman-util", "proofman-verifier", - "rand 0.9.2", "rayon", "serde", "serde_json", @@ -2336,7 +2337,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "bincode", "borsh", @@ -2367,7 +2368,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "fields", "itoa", @@ -2380,7 +2381,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "proc-macro2", "quote", @@ -2390,7 +2391,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "crossbeam-channel", "tracing", @@ -2399,7 +2400,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "bincode", "bytemuck", @@ -2411,7 +2412,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "bytemuck", "fields", @@ -2442,9 +2443,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "fastbloom", @@ -3131,12 +3132,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3635,9 +3636,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -4287,9 +4288,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] @@ -4385,7 +4386,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#a256e942216eceda47a8f528a0e820c6f5d25037" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" dependencies = [ "colored", "fields", @@ -4460,18 +4461,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index ea8a9b45d..34629f24e 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -5,9 +5,10 @@ use asm_runner::HintsFile; use asm_runner::HintsShmem; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] use asm_runner::{MOOutputShmem, MTOutputShmem, RHOutputShmem, SharedMemoryWriter}; -use precompiles_hints::HintsProcessor; +use precompiles_hints::{HintsProcessor, MpiBroadcastFn}; use std::sync::atomic::{AtomicBool, Ordering}; -use zisk_common::io::{StreamSource, ZiskStream}; +use zisk_common::io::StreamSink; +use zisk_common::io::{StreamProcessor, StreamSource, ZiskStream}; /// Encapsulates assembly-related resources including shared memory and hints stream. #[derive(Clone)] @@ -31,6 +32,8 @@ pub struct AsmResources { #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub shmem_input_writer: Arc>>, + pub hints_sink: Option>, + /// Pipeline for handling precompile hints. pub hints_stream: Option>>, @@ -54,6 +57,7 @@ impl AsmResources { unlock_mapped_memory: bool, verbose_mode: proofman_common::VerboseMode, with_hints: bool, + mpi_broadcast_fn: Option, ) -> Self { #[cfg(all(target_os = "linux", target_arch = "x86_64"))] let asm_shmem_mt = MTOutputShmem::new(local_rank, base_port, unlock_mapped_memory) @@ -68,28 +72,41 @@ impl AsmResources { const USE_SHARED_MEMORY_HINTS: bool = true; - let hints_stream = if with_hints { - let hints_processor = if USE_SHARED_MEMORY_HINTS { - let hints_shmem = HintsShmem::new(base_port, local_rank, unlock_mapped_memory) - .expect("asm_resources: Failed to create HintsShmem"); - - HintsProcessor::builder(hints_shmem) - .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) - .build() - .expect("asm_resources: Failed to create PrecompileHintsProcessor") - } else { - let hints_file = HintsFile::new(format!("hints_results_{}.bin", local_rank)) - .expect("asm_resources: Failed to create HintsFile"); - - HintsProcessor::builder(hints_file) - .enable_stats(verbose_mode != proofman_common::VerboseMode::Info) - .build() - .expect("asm_resources: Failed to create PrecompileHintsProcessor") - }; - - Some(Arc::new(Mutex::new(ZiskStream::new(hints_processor)))) + let (hints_stream, hints_sink) = if with_hints { + let (hints_processor, hints_sink): (HintsProcessor, Arc) = + if USE_SHARED_MEMORY_HINTS { + let hints_shmem = Arc::new( + HintsShmem::new(base_port, local_rank, unlock_mapped_memory) + .expect("Failed to create HintsShmem"), + ); + + let mut builder = HintsProcessor::builder(hints_shmem.clone()) + .enable_stats(verbose_mode != proofman_common::VerboseMode::Info); + + if let Some(broadcast_fn) = mpi_broadcast_fn.clone() { + builder = builder.with_mpi_broadcast(move |data| broadcast_fn(data)); + } + + (builder.build().expect("Failed to build HintsProcessor"), hints_shmem) + } else { + let hints_file = Arc::new( + HintsFile::new(format!("hints_results_{}.bin", local_rank)) + .expect("Failed to create HintsFile"), + ); + + let mut builder = HintsProcessor::builder(hints_file.clone()) + .enable_stats(verbose_mode != proofman_common::VerboseMode::Info); + + if let Some(broadcast_fn) = mpi_broadcast_fn.clone() { + builder = builder.with_mpi_broadcast(move |data| broadcast_fn(data)); + } + + (builder.build().expect("Failed to build HintsProcessor"), hints_file) + }; + + (Some(Arc::new(Mutex::new(ZiskStream::new(hints_processor)))), Some(hints_sink)) } else { - None + (None, None) }; Self { @@ -106,6 +123,7 @@ impl AsmResources { base_port, local_rank, unlock_mapped_memory, + hints_sink, } } @@ -131,6 +149,10 @@ impl AsmResources { self.hints_stream_initialized.load(Ordering::SeqCst) } + pub fn get_hints_processor(&self) -> Option> { + self.hints_stream.as_ref().map(|stream| stream.lock().unwrap().get_processor()) + } + pub fn reset(&self) { if let Some(hints_stream) = &self.hints_stream { hints_stream.lock().unwrap().reset(); diff --git a/executor/src/emu_asm.rs b/executor/src/emu_asm.rs index eac7b4f28..844409709 100644 --- a/executor/src/emu_asm.rs +++ b/executor/src/emu_asm.rs @@ -182,7 +182,7 @@ impl EmulatorAsm { }); // Run the ROM histogram only on partition 0 as it is always computed by this partition - let has_rom_sm = pctx.dctx_is_first_partition(); + let has_rom_sm = pctx.dctx_is_first_process(); let _stats = stats.clone(); diff --git a/executor/src/planner.rs b/executor/src/planner.rs index 6559880c6..b2a5debae 100644 --- a/executor/src/planner.rs +++ b/executor/src/planner.rs @@ -133,7 +133,7 @@ impl InstancePlanner { for plan in plans.iter_mut() { // ROM instances need special first partition assignment let global_id = if AirClassifier::is_rom_instance(plan.airgroup_id, plan.air_id) { - pctx.add_instance_assign_first_partition(plan.airgroup_id, plan.air_id) + pctx.add_instance_assign_first_process(plan.airgroup_id, plan.air_id) .expect("Failed to add ROM instance") } else { match plan.instance_type { diff --git a/precompiles/hints/Cargo.toml b/precompiles/hints/Cargo.toml index eaf0d6b18..1e842a52d 100644 --- a/precompiles/hints/Cargo.toml +++ b/precompiles/hints/Cargo.toml @@ -28,6 +28,8 @@ rayon = { workspace = true } tracing = { workspace = true } zisk-common = { workspace = true } rustls = { version = "0.23", features = ["ring"] } +borsh = { workspace = true } +zisk-distributed-common = { workspace = true } [dev-dependencies] criterion = "0.8" diff --git a/precompiles/hints/benches/hints_benchmarks.rs b/precompiles/hints/benches/hints_benchmarks.rs index 7681624cc..df5205f16 100644 --- a/precompiles/hints/benches/hints_benchmarks.rs +++ b/precompiles/hints/benches/hints_benchmarks.rs @@ -12,8 +12,8 @@ struct BenchSink { } impl StreamSink for BenchSink { - fn submit(&self, processed: Vec) -> Result<()> { - self.received.lock().unwrap().push(processed); + fn submit(&self, processed: &[u64]) -> Result<()> { + self.received.lock().unwrap().push(processed.to_vec()); Ok(()) } } @@ -46,7 +46,7 @@ fn parallel_speedup_benchmark(c: &mut Criterion) { b.iter(|| { let received = Arc::new(Mutex::new(Vec::new())); let received_clone = received.clone(); - let sink = BenchSink { received: received_clone }; + let sink = Arc::new(BenchSink { received: received_clone }); let p = HintsProcessor::builder(sink) .num_threads(threads) @@ -119,7 +119,7 @@ fn microsecond_hints_benchmark(c: &mut Criterion) { b.iter(|| { let received = Arc::new(Mutex::new(Vec::new())); let received_clone = received.clone(); - let sink = BenchSink { received: received_clone }; + let sink = Arc::new(BenchSink { received: received_clone }); let p = HintsProcessor::builder(sink) .num_threads(16) @@ -171,7 +171,7 @@ fn workload_patterns_benchmark(c: &mut Criterion) { b.iter(|| { let received = Arc::new(Mutex::new(Vec::new())); let received_clone = received.clone(); - let sink = BenchSink { received: received_clone }; + let sink = Arc::new(BenchSink { received: received_clone }); let p = HintsProcessor::builder(sink) .num_threads(8) @@ -225,7 +225,7 @@ fn noop_throughput_benchmark(c: &mut Criterion) { struct NullSink; impl StreamSink for NullSink { - fn submit(&self, _processed: Vec) -> Result<()> { + fn submit(&self, _processed: &[u64]) -> Result<()> { Ok(()) } } @@ -241,7 +241,8 @@ fn noop_throughput_benchmark(c: &mut Criterion) { for &count in &hint_counts { group.bench_with_input(BenchmarkId::from_parameter(count), &count, |b, &num_hints| { b.iter(|| { - let p = HintsProcessor::builder(NullSink).num_threads(32).build().unwrap(); + let p = + HintsProcessor::builder(Arc::new(NullSink)).num_threads(32).build().unwrap(); let mut data = Vec::with_capacity(num_hints * 2); for i in 0..num_hints { diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index bf9ef5e26..434dafd03 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -17,6 +17,7 @@ use zisk_common::{ BuiltInHint, CtrlHint, HintCode, PartialPrecompileHint, PrecompileHint, PrecompileHintParseResult, }; +use zisk_distributed_common::{JobPhase, StreamMessage}; use ziskos_hints::handlers::blake2b::blake2b_compress_hint; use ziskos_hints::handlers::bls381::{ bls12_381_fp2_to_g2_hint, bls12_381_fp_to_g1_hint, bls12_381_g1_add_hint, @@ -87,12 +88,16 @@ impl HintProcessorState { /// Type alias for custom hint handler functions. pub type CustomHintHandler = Arc Result> + Send + Sync>; +/// Type alias for MPI broadcast callback function. +pub type MpiBroadcastFn = Arc) -> Result<()> + Send + Sync>; + /// Builder for configuring and constructing a [`HintsProcessor`]. pub struct HintsProcessorBuilder { hints_sink: Arc, num_threads: usize, enable_stats: bool, custom_handlers: HashMap, + mpi_broadcast_fn: Option, } impl HintsProcessorBuilder { @@ -133,13 +138,24 @@ impl HintsProcessorBuilder { self } + /// Sets an MPI broadcast function to be called during initialization. + /// + /// This allows synchronization of initialization data across MPI ranks. + pub fn with_mpi_broadcast(mut self, broadcast_fn: F) -> Self + where + F: Fn(&mut Vec) -> Result<()> + Send + Sync + 'static, + { + self.mpi_broadcast_fn = Some(Arc::new(broadcast_fn)); + self + } + /// Builds the [`HintsProcessor`] with the configured settings. /// /// # Returns /// /// * `Ok(HintsProcessor)` - Successfully constructed processor /// * `Err` - If the thread pool fails to initialize - pub fn build(self) -> Result { + pub fn build(mut self) -> Result { let pool = ThreadPoolBuilder::new() .num_threads(self.num_threads) .build() @@ -151,8 +167,9 @@ impl HintsProcessorBuilder { // Spawn drainer thread let drainer_state = Arc::clone(&state); let drainer_sink = Arc::clone(&hints_sink); + let drainer_broadcast = self.mpi_broadcast_fn.take(); let drainer_thread = std::thread::spawn(move || { - HintsProcessor::drainer_thread(drainer_state, drainer_sink); + HintsProcessor::drainer_thread(drainer_state, drainer_sink, drainer_broadcast); }); Ok(HintsProcessor { @@ -225,12 +242,13 @@ impl HintsProcessor { /// .enable_stats(false) /// .build()?; /// ``` - pub fn builder(hints_sink: impl StreamSink) -> HintsProcessorBuilder { + pub fn builder(hints_sink: Arc) -> HintsProcessorBuilder { HintsProcessorBuilder { - hints_sink: Arc::new(hints_sink), + hints_sink, num_threads: Self::DEFAULT_NUM_THREADS, enable_stats: false, custom_handlers: HashMap::new(), + mpi_broadcast_fn: None, } } @@ -539,7 +557,11 @@ impl HintsProcessor { } /// Drainer thread that waits for hints to complete and drains ready results from queue. - fn drainer_thread(state: Arc, hints_sink: Arc) { + fn drainer_thread( + state: Arc, + hints_sink: Arc, + mpi_broadcast_fn: Option, + ) { loop { let mut queue = state.queue.lock().unwrap(); @@ -563,13 +585,24 @@ impl HintsProcessor { drop(queue); // Submit to sink - if let Err(e) = hints_sink.submit(data_to_submit) { + if let Err(e) = hints_sink.submit(&data_to_submit) { eprintln!("Error submitting to sink: {}", e); state.error_flag.store(true, Ordering::Release); state.drain_signal.notify_all(); return; } + if let Some(broadcast_fn) = &mpi_broadcast_fn { + let mut serialized = borsh::to_vec(&( + JobPhase::ContributionsHintsStream, + StreamMessage { data: data_to_submit.clone() }, + )) + .unwrap(); + + broadcast_fn(&mut serialized) + .expect("MPI broadcast failed in drainer thread"); + } + // Re-acquire lock for next iteration queue = state.queue.lock().unwrap(); } @@ -748,6 +781,10 @@ impl StreamProcessor for HintsProcessor { fn reset(&self) { self.reset(); } + + fn as_any(self: Arc) -> Arc { + self + } } #[cfg(test)] @@ -759,7 +796,7 @@ mod tests { struct NullHints; impl StreamSink for NullHints { - fn submit(&self, _processed: Vec) -> Result<()> { + fn submit(&self, _processed: &[u64]) -> Result<()> { Ok(()) } } @@ -777,7 +814,7 @@ mod tests { const TEST_PASSTHROUGH_HINT: u32 = 0x8000_0000 | 0x7FFF_0000; fn processor() -> HintsProcessor { - HintsProcessor::builder(NullHints) + HintsProcessor::builder(Arc::new(NullHints)) .num_threads(2) .custom_hint(0x7FFF_0000, |data| { // This should never be called for pass-through hints @@ -1022,15 +1059,15 @@ mod tests { } impl StreamSink for RecordingSink { - fn submit(&self, processed: Vec) -> Result<()> { - self.received.lock().unwrap().push(processed); + fn submit(&self, processed: &[u64]) -> Result<()> { + self.received.lock().unwrap().push(processed.to_vec()); Ok(()) } } let received = Arc::new(Mutex::new(Vec::new())); let sink = RecordingSink { received: Arc::clone(&received) }; - let p = HintsProcessor::builder(sink).num_threads(2).build().unwrap(); + let p = HintsProcessor::builder(Arc::new(sink)).num_threads(2).build().unwrap(); // Send some data (16 bytes = 2 u64s, 8 bytes = 1 u64) let data = vec![ @@ -1061,7 +1098,7 @@ mod tests { } impl StreamSink for FailingSink { - fn submit(&self, _processed: Vec) -> Result<()> { + fn submit(&self, _processed: &[u64]) -> Result<()> { if self.should_fail.load(Ordering::Acquire) { Err(anyhow::anyhow!("Sink error")) } else { @@ -1072,7 +1109,7 @@ mod tests { let should_fail = Arc::new(AtomicBool::new(false)); let sink = FailingSink { should_fail: Arc::clone(&should_fail) }; - let p = HintsProcessor::builder(sink).num_threads(2).build().unwrap(); + let p = HintsProcessor::builder(Arc::new(sink)).num_threads(2).build().unwrap(); // First batch succeeds (8 bytes = 1 u64) let data1 = vec![make_header(TEST_PASSTHROUGH_HINT, 8), 0x01]; @@ -1095,26 +1132,29 @@ mod tests { #[test] fn test_builder_configuration() { // Default builder - stats disabled - let p1 = HintsProcessor::builder(NullHints).build().unwrap(); + let p1 = HintsProcessor::builder(Arc::new(NullHints)).build().unwrap(); assert!(p1.stats.is_none()); // Explicitly disabled stats - let p2 = HintsProcessor::builder(NullHints).enable_stats(false).build().unwrap(); + let p2 = HintsProcessor::builder(Arc::new(NullHints)).enable_stats(false).build().unwrap(); assert!(p2.stats.is_none()); // Stats enabled - let p3 = HintsProcessor::builder(NullHints).enable_stats(true).build().unwrap(); + let p3 = HintsProcessor::builder(Arc::new(NullHints)).enable_stats(true).build().unwrap(); assert!(p3.stats.is_some()); // Custom threads - let p4 = HintsProcessor::builder(NullHints).num_threads(4).build().unwrap(); + let p4 = HintsProcessor::builder(Arc::new(NullHints)).num_threads(4).build().unwrap(); let data = vec![make_header(TEST_PASSTHROUGH_HINT, 1), 0x42]; assert!(p4.process_hints(&data, false).is_ok()); assert!(p4.wait_for_completion().is_ok()); // Chaining multiple options - let p5 = - HintsProcessor::builder(NullHints).num_threads(8).enable_stats(true).build().unwrap(); + let p5 = HintsProcessor::builder(Arc::new(NullHints)) + .num_threads(8) + .enable_stats(true) + .build() + .unwrap(); assert!(p5.stats.is_some()); } @@ -1123,7 +1163,7 @@ mod tests { fn test_stress_throughput() { use std::time::Instant; - let p = HintsProcessor::builder(NullHints).num_threads(32).build().unwrap(); + let p = HintsProcessor::builder(Arc::new(NullHints)).num_threads(32).build().unwrap(); // Generate a large batch of hints const NUM_HINTS: usize = 100_000; @@ -1156,7 +1196,7 @@ mod tests { fn test_stress_concurrent_batches() { use std::time::Instant; - let p = HintsProcessor::builder(NullHints).num_threads(32).build().unwrap(); + let p = HintsProcessor::builder(Arc::new(NullHints)).num_threads(32).build().unwrap(); const NUM_BATCHES: usize = 1_000; const HINTS_PER_BATCH: usize = 100; @@ -1195,7 +1235,7 @@ mod tests { fn test_stress_with_resets() { use std::time::Instant; - let p = HintsProcessor::builder(NullHints).num_threads(32).build().unwrap(); + let p = HintsProcessor::builder(Arc::new(NullHints)).num_threads(32).build().unwrap(); const ITERATIONS: usize = 100; const HINTS_PER_ITER: usize = 1_000; @@ -1251,8 +1291,8 @@ mod tests { } impl StreamSink for RecordingSink { - fn submit(&self, processed: Vec) -> Result<()> { - self.received.lock().unwrap().push(processed); + fn submit(&self, processed: &[u64]) -> Result<()> { + self.received.lock().unwrap().push(processed.to_vec()); Ok(()) } } @@ -1265,7 +1305,7 @@ mod tests { const SLOW_HINT: u32 = 0x7FFF_0001; // Delays 10ms const MED_HINT: u32 = 0x7FFF_0002; // Delays 5ms - let p = HintsProcessor::builder(sink) + let p = HintsProcessor::builder(Arc::new(sink)) .num_threads(8) .custom_hint(FAST_HINT, |data| { // No delay - returns immediately @@ -1622,8 +1662,8 @@ mod tests { } impl StreamSink for RecordingSink { - fn submit(&self, processed: Vec) -> Result<()> { - self.received.lock().unwrap().push(processed); + fn submit(&self, processed: &[u64]) -> Result<()> { + self.received.lock().unwrap().push(processed.to_vec()); Ok(()) } } @@ -1633,7 +1673,7 @@ mod tests { const VARIABLE_HINT: u32 = 0x7FFF_0100; - let p = HintsProcessor::builder(sink) + let p = HintsProcessor::builder(Arc::new(sink)) .num_threads(16) .custom_hint(VARIABLE_HINT, |data| { // Pseudo-random delay based on hash of input value (0-15ms range) diff --git a/precompiles/hints/src/lib.rs b/precompiles/hints/src/lib.rs index 82eefa35c..02826fe01 100644 --- a/precompiles/hints/src/lib.rs +++ b/precompiles/hints/src/lib.rs @@ -1,3 +1,3 @@ mod hints_processor; -pub use hints_processor::HintsProcessor; +pub use hints_processor::{HintsProcessor, MpiBroadcastFn}; diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 50fc6a848..0af7ef9d6 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -60,6 +60,7 @@ pub struct ProverClientBuilder { base_port: Option, unlock_mapped_memory: bool, no_auto_setup: bool, + is_distributed: bool, // Prove-specific fields (only available when Operation = Prove) gpu_params: ParamsGPU, @@ -218,6 +219,12 @@ impl ProverClientBuilder { self } + #[must_use] + pub fn is_distributed(mut self, is_distributed: bool) -> Self { + self.is_distributed = is_distributed; + self + } + #[must_use] pub fn base_port(mut self, base_port: u16) -> Self { self.base_port = Some(base_port); @@ -415,6 +422,7 @@ impl ProverClientBuilder { self.unlock_mapped_memory, self.no_auto_setup, self.gpu_params, + self.is_distributed, self.logging_config, )? }; @@ -454,6 +462,7 @@ impl From> for ProverClientBuilder { base_port: None, unlock_mapped_memory: false, no_auto_setup: false, + is_distributed: false, _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -483,6 +492,7 @@ impl From> for ProverClientBuilder { base_port: builder.base_port, unlock_mapped_memory: builder.unlock_mapped_memory, no_auto_setup: builder.no_auto_setup, + is_distributed: builder.is_distributed, _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -514,6 +524,7 @@ impl From> base_port: builder.base_port, unlock_mapped_memory: builder.unlock_mapped_memory, no_auto_setup: builder.no_auto_setup, + is_distributed: builder.is_distributed, _backend: std::marker::PhantomData, _operation: std::marker::PhantomData, @@ -543,6 +554,7 @@ impl From> for ProverClientBuilder, ) -> Result { let core_prover = AsmCoreProver::new( @@ -66,6 +67,7 @@ impl AsmProver { unlock_mapped_memory, no_auto_setup, gpu_params, + is_distributed, logging_config, )?; @@ -111,6 +113,7 @@ impl ProverEngine for AsmProver { let world_rank = self.core_prover.rank_info.world_rank; let local_rank = self.core_prover.rank_info.local_rank; let n_processes = self.core_prover.rank_info.n_processes; + let is_distributed = self.core_prover.is_distributed; let unlock_mapped_memory = self.core_prover.unlock_mapped_memory; let verbose_mode = self.core_prover.verbose; let rank_info = self.core_prover.rank_info.clone(); @@ -179,12 +182,21 @@ impl ProverEngine for AsmProver { asm_services.start_asm_services(&asm_mt_path, asm_runner_options)?; timer_stop_and_log_info!(STARTING_ASM_MICROSERVICES); + let mpi_broadcast_fn = (is_distributed && n_processes > 1 && elf.with_hints()).then(|| { + let pctx = pctx.clone(); + Arc::new(move |data: &mut Vec| { + pctx.mpi_ctx.broadcast(data); + Ok(()) + }) as Arc) -> Result<()> + Send + Sync> + }); + let asm_resources = AsmResources::new( local_rank, base_port, unlock_mapped_memory, verbose_mode, elf.with_hints(), + mpi_broadcast_fn, ); self.n_setups.fetch_add(1, Ordering::SeqCst); @@ -321,10 +333,6 @@ impl ProverEngine for AsmProver { self.core_prover.backend.set_partition(total_compute_units, allocation, rank_id) } - fn is_first_partition(&self) -> Result { - self.core_prover.backend.is_first_partition() - } - fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()> { self.core_prover.backend.register_aggregated_proofs(agg_proofs) } @@ -347,6 +355,7 @@ impl ProverEngine for AsmProver { pub struct AsmCoreProver { backend: ProverBackend, rank_info: RankInfo, + is_distributed: bool, base_port: Option, unlock_mapped_memory: bool, verbose: VerboseMode, @@ -367,6 +376,7 @@ impl AsmCoreProver { unlock_mapped_memory: bool, no_auto_setup: bool, gpu_params: ParamsGPU, + is_distributed: bool, logging_config: Option, ) -> Result { check_paths_exist(&proving_key)?; @@ -425,6 +435,7 @@ impl AsmCoreProver { unlock_mapped_memory, verbose: verbose.into(), no_auto_setup, + is_distributed, }) } @@ -439,6 +450,7 @@ impl AsmCoreProver { unlock_mapped_memory: false, verbose: VerboseMode::Info, no_auto_setup: false, + is_distributed: false, }) } } diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index e17175f63..9c613a679 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -541,15 +541,6 @@ impl ProverBackend { Ok(proofman.set_partition(total_compute_units, allocation, rank_id)?) } - pub(crate) fn is_first_partition(&self) -> Result { - let proofman = self - .proofman - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Cannot set partition in verifier mode"))?; - - Ok(proofman.is_first_partition()) - } - pub(crate) fn get_execution_info(&self) -> Result<(WitnessInfo, ZiskExecutorTime)> { let proofman = self .proofman diff --git a/sdk/src/prover/emu.rs b/sdk/src/prover/emu.rs index a178d7025..328f29a34 100644 --- a/sdk/src/prover/emu.rs +++ b/sdk/src/prover/emu.rs @@ -234,10 +234,6 @@ impl ProverEngine for EmuProver { self.core_prover.backend.set_partition(total_compute_units, allocation, rank_id) } - fn is_first_partition(&self) -> Result { - self.core_prover.backend.is_first_partition() - } - fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()> { self.core_prover.backend.register_aggregated_proofs(agg_proofs) } diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 9e429d89d..04c73422b 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -28,7 +28,9 @@ use tracing::info; use zisk_common::io::StreamSource; use zisk_common::ElfBinaryLike; use zisk_common::ZiskExecutorTime; -use zisk_common::{io::ZiskStdin, ExecutorStatsHandle, StatsCostPerType, ZiskExecutorSummary}; +use zisk_common::{ + io::StreamProcessor, io::ZiskStdin, ExecutorStatsHandle, StatsCostPerType, ZiskExecutorSummary, +}; use zisk_core::ZiskRom; pub struct ZiskExecuteResult { @@ -152,6 +154,20 @@ impl ZiskProgramPK { pub fn is_asm(&self) -> bool { self.asm_services.is_some() } + + pub fn get_hints_processor(&self) -> Option> { + if let Some(asm_resources) = &self.asm_resources { + asm_resources.get_hints_processor() + } else { + None + } + } + + pub fn reset(&self) { + if let Some(asm_resources) = &self.asm_resources { + asm_resources.reset(); + } + } } impl Drop for ZiskProgramPK { @@ -836,8 +852,6 @@ pub trait ProverEngine { rank_id: usize, ) -> Result<()>; - fn is_first_partition(&self) -> Result; - fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()>; fn aggregate_proofs( @@ -1023,10 +1037,6 @@ impl ZiskProver { self.prover.set_partition(total_compute_units, allocation, rank_id) } - pub fn is_first_partition(&self) -> Result { - self.prover.is_first_partition() - } - pub fn register_aggregated_proofs(&self, agg_proofs: Vec) -> Result<()> { self.prover.register_aggregated_proofs(agg_proofs) } diff --git a/ziskos/entrypoint/src/hints/blake2b.rs b/ziskos/entrypoint/src/hints/blake2b.rs index a9c4e66b1..30fdb07d9 100644 --- a/ziskos/entrypoint/src/hints/blake2b.rs +++ b/ziskos/entrypoint/src/hints/blake2b.rs @@ -1,38 +1,9 @@ -use crate::hints::{macros::register_hint_meta, HINT_BUFFER}; -use zisk_common::HINT_BLAKE2B_COMPRESS; - #[no_mangle] pub unsafe extern "C" fn hint_blake2b_compress( - rounds: u32, - state: *mut u64, - message: *const u64, - offset: *const u64, - final_block: u8, + _rounds: u32, + _state: *mut u64, + _message: *const u64, + _offset: *const u64, + _final_block: u8, ) { - if !HINT_BUFFER.is_enabled() { - return; - } - - #[cfg(zisk_hints_single_thread)] - if !check_main_thread() { - return; - } - - let total_len = 8 + 64 + 128 + 16 + 8; // rounds + state + message + offset + final_block - - let mut w = HINT_BUFFER.begin_hint(HINT_BLAKE2B_COMPRESS, total_len, false); - - let rounds_bytes: [u8; 8] = (rounds as u64).to_le_bytes(); - w.write_hint_data_slice(&rounds_bytes); - - w.write_hint_data_ptr(state as *const u8, 64); - w.write_hint_data_ptr(message as *const u8, 128); - w.write_hint_data_ptr(offset as *const u8, 16); - - let final_block_bytes: [u8; 8] = (final_block as u64).to_le_bytes(); - w.write_hint_data_slice(&final_block_bytes); - - w.commit(); } - -register_hint_meta!(blake2b_compress, HINT_BLAKE2B_COMPRESS); diff --git a/ziskos/entrypoint/src/hints/keccak256.rs b/ziskos/entrypoint/src/hints/keccak256.rs index ebb388ed1..82b8058c2 100644 --- a/ziskos/entrypoint/src/hints/keccak256.rs +++ b/ziskos/entrypoint/src/hints/keccak256.rs @@ -1,10 +1,2 @@ -use crate::hints::macros::define_hint_ptr; -use zisk_common::HINT_KECCAK256; - -define_hint_ptr! { - keccak256 => { - hint_id: HINT_KECCAK256, - param: input, - is_result: false, - } -} +#[no_mangle] +pub unsafe extern "C" fn hint_keccak256(_input_ptr: *const u8, _input_len: usize) {} diff --git a/ziskos/entrypoint/src/hints/sha256f.rs b/ziskos/entrypoint/src/hints/sha256f.rs index fd0708394..4379df561 100644 --- a/ziskos/entrypoint/src/hints/sha256f.rs +++ b/ziskos/entrypoint/src/hints/sha256f.rs @@ -1,10 +1,2 @@ -use crate::hints::macros::define_hint_ptr; -use zisk_common::HINT_SHA256; - -define_hint_ptr! { - sha256 => { - hint_id: HINT_SHA256, - param: f, - is_result: false, - } -} +#[no_mangle] +pub unsafe extern "C" fn hint_sha256(_f_ptr: *const u8, _f_len: usize) {} From 66687264d277e409f12fd4c078236c27ecf94996 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 10 Mar 2026 08:43:58 +0000 Subject: [PATCH 744/782] Pointing to pre-develop-0.16.0 --- Cargo.lock | 105 ++++++++++++++++++++++++----------------------------- Cargo.toml | 16 ++++---- 2 files changed, 55 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 299fe5464..e5660d757 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -604,7 +604,7 @@ dependencies = [ "colored", "dirs", "executor", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "futures", "indicatif", "mpi", @@ -1116,9 +1116,9 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "num-bigint", "num-traits", ] @@ -1415,7 +1415,7 @@ dependencies = [ "asm-runner", "crossbeam", "data-bus", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "itertools 0.14.0", "mem-common", "mem-planner-cpp", @@ -1470,18 +1470,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" -dependencies = [ - "cfg-if", - "num-bigint", - "paste", - "serde", -] - -[[package]] -name = "fields" -version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#cd887d6caca80145c88197dbd7f55ddb361bfe52" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" dependencies = [ "cfg-if", "num-bigint", @@ -2285,9 +2274,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.24" +version = "1.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839" +checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" dependencies = [ "cc", "libc", @@ -2348,7 +2337,7 @@ name = "mem-common" version = "0.16.0" dependencies = [ "clap", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "num-bigint", "num-traits", "proofman-common", @@ -2775,10 +2764,10 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" dependencies = [ "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "num-bigint", "num-traits", "proofman-common", @@ -2897,7 +2886,7 @@ dependencies = [ "ark-secp256k1", "ark-secp256r1", "ark-std", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "lazy_static", "lib-c", "mem-common", @@ -2933,7 +2922,7 @@ dependencies = [ "ark-ff", "ark-secp256k1", "ark-std", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "lazy_static", "lib-c", "mem-common", @@ -2964,7 +2953,7 @@ dependencies = [ name = "precomp-big-int" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "generic-array", "lib-c", "mem-common", @@ -2985,7 +2974,7 @@ dependencies = [ name = "precomp-blake2" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -3004,7 +2993,7 @@ dependencies = [ name = "precomp-dma" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "generic-array", "lib-c", "mem-common", @@ -3028,7 +3017,7 @@ name = "precomp-keccakf" version = "0.16.0" dependencies = [ "circuit", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "path-clean", "pil-std-lib", "precompiles-common", @@ -3048,7 +3037,7 @@ dependencies = [ name = "precomp-poseidon2" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -3068,7 +3057,7 @@ dependencies = [ name = "precomp-sha256f" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -3087,7 +3076,7 @@ dependencies = [ name = "precompiles-common" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "mem-common", "sm-mem", "zisk-common", @@ -3159,7 +3148,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" dependencies = [ "bincode", "blake3", @@ -3170,7 +3159,7 @@ dependencies = [ "crossbeam-channel", "csv", "curves", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "libloading", "mpi", "num-bigint", @@ -3194,7 +3183,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" dependencies = [ "bincode", "borsh", @@ -3204,7 +3193,7 @@ dependencies = [ "crossbeam-queue", "csv", "env", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "lazy_static", "libloading", "mpi", @@ -3225,9 +3214,9 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "itoa", "proofman-common", "proofman-starks-lib-c", @@ -3238,7 +3227,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" dependencies = [ "proc-macro2", "quote", @@ -3248,7 +3237,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" dependencies = [ "crossbeam-channel", "tracing", @@ -3257,7 +3246,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" dependencies = [ "bincode", "bytemuck", @@ -3269,10 +3258,10 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" dependencies = [ "bytemuck", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "proofman-util", "rayon", "tracing", @@ -3637,7 +3626,7 @@ dependencies = [ "anyhow", "blake3", "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "proofman-common", "sm-rom", "tracing", @@ -4059,7 +4048,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "sm-arith" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "num-bigint", "pil-std-lib", "proofman-common", @@ -4079,7 +4068,7 @@ dependencies = [ name = "sm-binary" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "num-bigint", "pil-std-lib", "proofman-common", @@ -4099,7 +4088,7 @@ name = "sm-frequent-ops" version = "0.16.0" dependencies = [ "clap", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "num-bigint", "proofman-common", "proofman-util", @@ -4113,7 +4102,7 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "mem-common", "num-bigint", "pil-std-lib", @@ -4132,7 +4121,7 @@ dependencies = [ name = "sm-mem" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "mem-common", "num-bigint", "num-traits", @@ -4153,7 +4142,7 @@ name = "sm-rom" version = "0.16.0" dependencies = [ "asm-runner", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "itertools 0.14.0", "proofman-common", "proofman-macros", @@ -5721,10 +5710,10 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom#e82966333039d1de97e93b289c0043f5ada1bdfa" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" dependencies = [ "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "libloading", "proofman-common", "proofman-util", @@ -5917,7 +5906,7 @@ version = "0.16.0" dependencies = [ "anyhow", "bincode", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "libc", "mpi", "proofman", @@ -5940,7 +5929,7 @@ name = "zisk-core" version = "0.16.0" dependencies = [ "elf", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "lib-c", "lib-float", "paste", @@ -6043,7 +6032,7 @@ dependencies = [ "clap", "colored", "config", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "precompiles-hints", "proofman", "proofman-common", @@ -6069,7 +6058,7 @@ dependencies = [ name = "zisk-pil" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "proofman-common", "proofman-macros", "rayon", @@ -6086,7 +6075,7 @@ dependencies = [ "bincode", "colored", "executor", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "proofman", "proofman-common", "proofman-util", @@ -6126,7 +6115,7 @@ dependencies = [ "clap", "criterion 0.5.1", "data-bus", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "mem-common", "memmap2", "num-format", @@ -6155,7 +6144,7 @@ dependencies = [ "bytes", "cfg-if", "ctor", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", + "fields", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -6181,7 +6170,7 @@ dependencies = [ "anyhow", "bincode", "cfg-if", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=fix%2Fmpi-hints-rom)", + "fields", "getrandom 0.2.17", "lazy_static", "lib-c", diff --git a/Cargo.toml b/Cargo.toml index 8ba8cc2c3..2704f738f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,14 +112,14 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "fix/mpi-hints-rom" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } # Proofman Local development # proofman = { path = "../pil2-proofman/proofman" } # proofman-common = { path = "../pil2-proofman/common" } From ae7d4defe0d60e4c8b2682e19da1b71d121ff404 Mon Sep 17 00:00:00 2001 From: fractasy Date: Fri, 27 Feb 2026 21:17:38 +0100 Subject: [PATCH 745/782] Split main.c into several code files --- emulator-asm/Makefile | 4 +- emulator-asm/src/asm_provided.hpp | 31 + emulator-asm/src/c_provided.c | 381 +++ emulator-asm/src/c_provided.hpp | 12 + emulator-asm/src/client.c | 1502 +++++++++ emulator-asm/src/client.hpp | 8 + emulator-asm/src/configuration.c | 974 ++++++ emulator-asm/src/configuration.hpp | 7 + emulator-asm/src/constants.hpp | 117 + emulator-asm/src/emu.c | 4 +- emulator-asm/src/emu.hpp | 3 + emulator-asm/src/globals.c | 136 + emulator-asm/src/globals.hpp | 166 + emulator-asm/src/main.c | 5034 +--------------------------- emulator-asm/src/server.c | 958 ++++++ emulator-asm/src/server.hpp | 11 + emulator-asm/src/trace.c | 238 ++ emulator-asm/src/trace.hpp | 12 + emulator-asm/src/trace_logs.c | 678 ++++ emulator-asm/src/trace_logs.hpp | 14 + 20 files changed, 5293 insertions(+), 4997 deletions(-) create mode 100644 emulator-asm/src/asm_provided.hpp create mode 100644 emulator-asm/src/c_provided.c create mode 100644 emulator-asm/src/c_provided.hpp create mode 100644 emulator-asm/src/client.c create mode 100644 emulator-asm/src/client.hpp create mode 100644 emulator-asm/src/configuration.c create mode 100644 emulator-asm/src/configuration.hpp create mode 100644 emulator-asm/src/constants.hpp create mode 100644 emulator-asm/src/globals.c create mode 100644 emulator-asm/src/globals.hpp create mode 100644 emulator-asm/src/server.c create mode 100644 emulator-asm/src/server.hpp create mode 100644 emulator-asm/src/trace.c create mode 100644 emulator-asm/src/trace.hpp create mode 100644 emulator-asm/src/trace_logs.c create mode 100644 emulator-asm/src/trace_logs.hpp diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 1425688d8..75835a40b 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -56,9 +56,9 @@ build/dma.o: $(DMA_OBJS) ld -r $(DMA_OBJS) -o $@ # Compile the final executable -$(OUT_PATH): build/emu.o src/main.c src/emu.c src/chfast/keccak.c build/dma.o +$(OUT_PATH): build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c build/dma.o mkdir -p $(OUT_DIR) - gcc $(CFLAGS) src/main.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ + gcc $(CFLAGS) src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ clean: rm -rf build diff --git a/emulator-asm/src/asm_provided.hpp b/emulator-asm/src/asm_provided.hpp new file mode 100644 index 000000000..9a6b42e5b --- /dev/null +++ b/emulator-asm/src/asm_provided.hpp @@ -0,0 +1,31 @@ +#ifndef EMULATOR_ASM_ASM_PROVIDED_HPP +#define EMULATOR_ASM_ASM_PROVIDED_HPP + +#include + +/**************************/ +/* Assembly-provided code */ +/**************************/ + +// This is the emulator assembly code start function, which will execute the code in the ROM until +// it ends, and generate the trace in the output trace memory. +// It is called from C to start the execution of the assembly code. +void emulator_start(void); + +// These functions are implemented in assembly and provide access to configuration parameters used +// to generate the assembly code, and that in some cases must match the C main program configuration +uint64_t get_max_bios_pc(void); +uint64_t get_max_program_pc(void); +uint64_t get_gen_method(void); // Must match the C main program provided argument +uint64_t get_precompile_results(void); + +// These variables are updated by the assembly code to provide information about the execution +// status and trace generation, accessed by C to generate the response to the client +extern uint64_t MEM_STEP; // Current step, i.e. number of executed instructions, updated by assembly at every step or at the end of every chunk, depending on the generation method +extern uint64_t MEM_END; // Indicates the end of execution +extern uint64_t MEM_ERROR; // Indicates an error during execution +extern uint64_t MEM_TRACE_ADDRESS; // Address of the trace memory +extern uint64_t MEM_CHUNK_ADDRESS; // Address of the current chunk +extern uint64_t MEM_CHUNK_START_STEP; // Step at which the current chunk started + +#endif // EMULATOR_ASM_ASM_PROVIDED_HPP \ No newline at end of file diff --git a/emulator-asm/src/c_provided.c b/emulator-asm/src/c_provided.c new file mode 100644 index 000000000..cecec70cf --- /dev/null +++ b/emulator-asm/src/c_provided.c @@ -0,0 +1,381 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "c_provided.hpp" +#include "globals.hpp" +#include "trace.hpp" +#include "emu.hpp" + +/**************/ +/* TRACE SIZE */ +/**************/ + +void set_trace_size (uint64_t new_trace_size) +{ + // Update trace global variables + // printf("%s trace resize (trace_resize_request: %ld): %ld MB => %ld MB\n", log_name, trace_resize_request, trace_size >> 20, new_trace_size >> 20); + + // trace_resize_request = 0; + + trace_size = new_trace_size; + trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; + pOutputTrace[2] = trace_size; +} + +/**************/ +/* PRINT REGS */ +/**************/ + +//#define PRINT_REGS +#ifdef PRINT_REGS +extern uint64_t reg_0; +extern uint64_t reg_1; +extern uint64_t reg_2; +extern uint64_t reg_3; +extern uint64_t reg_4; +extern uint64_t reg_5; +extern uint64_t reg_6; +extern uint64_t reg_7; +extern uint64_t reg_8; +extern uint64_t reg_9; +extern uint64_t reg_10; +extern uint64_t reg_11; +extern uint64_t reg_12; +extern uint64_t reg_13; +extern uint64_t reg_14; +extern uint64_t reg_15; +extern uint64_t reg_16; +extern uint64_t reg_17; +extern uint64_t reg_18; +extern uint64_t reg_19; +extern uint64_t reg_20; +extern uint64_t reg_21; +extern uint64_t reg_22; +extern uint64_t reg_23; +extern uint64_t reg_24; +extern uint64_t reg_25; +extern uint64_t reg_26; +extern uint64_t reg_27; +extern uint64_t reg_28; +extern uint64_t reg_29; +extern uint64_t reg_30; +extern uint64_t reg_31; +extern uint64_t reg_32; +extern uint64_t reg_33; +extern uint64_t reg_34; +#endif + +// Used for debugging purposes +extern int _print_regs() +{ +#ifdef PRINT_REGS + printf("print_regs()\n"); + printf("\treg[ 0]=%lu=0x%lx=@%p\n", reg_0, reg_0, ®_0); + printf("\treg[ 1]=%lu=0x%lx=@%p\n", reg_1, reg_1, ®_1); + printf("\treg[ 2]=%lu=0x%lx=@%p\n", reg_2, reg_2, ®_2); + printf("\treg[ 3]=%lu=0x%lx=@%p\n", reg_3, reg_3, ®_3); + printf("\treg[ 4]=%lu=0x%lx=@%p\n", reg_4, reg_4, ®_4); + printf("\treg[ 5]=%lu=0x%lx=@%p\n", reg_5, reg_5, ®_5); + printf("\treg[ 6]=%lu=0x%lx=@%p\n", reg_6, reg_6, ®_6); + printf("\treg[ 7]=%lu=0x%lx=@%p\n", reg_7, reg_7, ®_7); + printf("\treg[ 8]=%lu=0x%lx=@%p\n", reg_8, reg_8, ®_8); + printf("\treg[ 9]=%lu=0x%lx=@%p\n", reg_9, reg_9, ®_9); + printf("\treg[10]=%lu=0x%lx=@%p\n", reg_10, reg_10, ®_10); + printf("\treg[11]=%lu=0x%lx=@%p\n", reg_11, reg_11, ®_11); + printf("\treg[12]=%lu=0x%lx=@%p\n", reg_12, reg_12, ®_12); + printf("\treg[13]=%lu=0x%lx=@%p\n", reg_13, reg_13, ®_13); + printf("\treg[14]=%lu=0x%lx=@%p\n", reg_14, reg_14, ®_14); + printf("\treg[15]=%lu=0x%lx=@%p\n", reg_15, reg_15, ®_15); + printf("\treg[16]=%lu=0x%lx=@%p\n", reg_16, reg_16, ®_16); + printf("\treg[17]=%lu=0x%lx=@%p\n", reg_17, reg_17, ®_17); + printf("\treg[18]=%lu=0x%lx=@%p\n", reg_18, reg_18, ®_18); + printf("\treg[19]=%lu=0x%lx=@%p\n", reg_19, reg_19, ®_19); + printf("\treg[20]=%lu=0x%lx=@%p\n", reg_20, reg_20, ®_20); + printf("\treg[21]=%lu=0x%lx=@%p\n", reg_21, reg_21, ®_21); + printf("\treg[22]=%lu=0x%lx=@%p\n", reg_22, reg_22, ®_22); + printf("\treg[23]=%lu=0x%lx=@%p\n", reg_23, reg_23, ®_23); + printf("\treg[24]=%lu=0x%lx=@%p\n", reg_24, reg_24, ®_24); + printf("\treg[25]=%lu=0x%lx=@%p\n", reg_25, reg_25, ®_25); + printf("\treg[26]=%lu=0x%lx=@%p\n", reg_26, reg_26, ®_26); + printf("\treg[27]=%lu=0x%lx=@%p\n", reg_27, reg_27, ®_27); + printf("\treg[28]=%lu=0x%lx=@%p\n", reg_28, reg_28, ®_28); + printf("\treg[29]=%lu=0x%lx=@%p\n", reg_29, reg_29, ®_29); + printf("\treg[30]=%lu=0x%lx=@%p\n", reg_30, reg_30, ®_30); + printf("\treg[31]=%lu=0x%lx=@%p\n", reg_31, reg_31, ®_31); + printf("\treg[32]=%lu=0x%lx=@%p\n", reg_32, reg_32, ®_32); + printf("\treg[33]=%lu=0x%lx=@%p\n", reg_33, reg_33, ®_33); + printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); + printf("\n"); +#endif +} + +/************/ +/* PRINT PC */ +/************/ + +//#define PRINT_PC_DURATION +#ifdef PRINT_PC_DURATION +struct timeval print_pc_tv; +#endif + +// Used for debugging purposes +extern int _print_pc (uint64_t pc, uint64_t c) +{ +#ifdef PRINT_PC_DURATION + print_pc_counter++; + { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t duration = TimeDiff(print_pc_tv, tv); + if (duration > 900) + { + uint64_t chunk = print_pc_counter / chunk_size; + printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); + fflush(stdout); + } + print_pc_tv = tv; + } +#endif + + printf("s=%lu pc=%lx c=%lx", print_pc_counter, pc, c); + +//#define PRINT_PC_REGS +#ifdef PRINT_PC_REGS + /* Used for debugging */ + printf(" r0=%lx", reg_0); + printf(" r1=%lx", reg_1); + printf(" r2=%lx", reg_2); + printf(" r3=%lx", reg_3); + printf(" r4=%lx", reg_4); + printf(" r5=%lx", reg_5); + printf(" r6=%lx", reg_6); + printf(" r7=%lx", reg_7); + printf(" r8=%lx", reg_8); + printf(" r9=%lx", reg_9); + printf(" r10=%lx", reg_10); + printf(" r11=%lx", reg_11); + printf(" r12=%lx", reg_12); + printf(" r13=%lx", reg_13); + printf(" r14=%lx", reg_14); + printf(" r15=%lx", reg_15); + printf(" r16=%lx", reg_16); + printf(" r17=%lx", reg_17); + printf(" r18=%lx", reg_18); + printf(" r19=%lx", reg_19); + printf(" r20=%lx", reg_20); + printf(" r21=%lx", reg_21); + printf(" r22=%lx", reg_22); + printf(" r23=%lx", reg_23); + printf(" r24=%lx", reg_24); + printf(" r25=%lx", reg_25); + printf(" r26=%lx", reg_26); + printf(" r27=%lx", reg_27); + printf(" r28=%lx", reg_28); + printf(" r29=%lx", reg_29); + printf(" r30=%lx", reg_30); + printf(" r31=%lx", reg_31); +#endif + + printf("\n"); + fflush(stdout); + print_pc_counter++; +} + +/**************/ +/* CHUNK DONE */ +/**************/ + +//#define CHUNK_DONE_DURATION +#ifdef CHUNK_DONE_DURATION +uint64_t chunk_done_counter = 0; +struct timeval chunk_done_tv; +#endif + +//#define CHUNK_DONE_SYNC_DURATION +#ifdef CHUNK_DONE_SYNC_DURATION +struct timeval sync_start, sync_stop; +uint64_t sync_duration = 0; +#endif + +// Called by the assembly to notify that a chunk is done and its trace is ready to be consumed +extern void _chunk_done() +{ +#ifdef CHUNK_DONE_DURATION + chunk_done_counter++; + if ((chunk_done_counter & 0xFF) == 0) + { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t duration = TimeDiff(chunk_done_tv, tv); + if (duration > 5000) + { + printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); + fflush(stdout); + } + chunk_done_tv = tv; + } +#endif + +#ifdef CHUNK_DONE_SYNC_DURATION + gettimeofday(&sync_start, NULL); +#endif + + __sync_synchronize(); + +#ifdef CHUNK_DONE_SYNC_DURATION + gettimeofday(&sync_stop, NULL); + sync_duration += TimeDiff(sync_start, sync_stop); + printf("chunk_done() sync_duration=%lu\n", sync_duration); +#endif + + // Notify the caller that a new chunk is done and its trace is ready to be consumed + assert(call_chunk_done); + int result = sem_post(sem_chunk_done); + if (result == -1) + { + printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +/*****************/ +/* REALLOC TRACE */ +/*****************/ + +// Called by the assembly to reallocate the trace when needed, e.g. for the next chunk, +// to increase the trace size by another chunk size +extern void _realloc_trace (void) +{ + // Increase realloc counter + realloc_counter++; + + // Map next chunk of the trace shared memory + trace_map_next_chunk(); + + // Update trace global variables + set_trace_size(trace_total_mapped_size); + +#ifdef DEBUG + if (verbose) printf("realloc_trace() realloc counter=%lu trace_address=0x%lx trace_size=%lu=%lx max_address=0x%lx trace_address_threshold=0x%lx chunk_size=%lu\n", realloc_counter, trace_address, trace_size, trace_size, trace_address + trace_size, trace_address_threshold, chunk_size); +#endif +} + +/*********************************/ +/* WAIT FOR PRECOMPILE AVAILABLE */ +/*********************************/ + +// Called by the assembly when prec_written == prec_read, to wait for new precompile results to be available +int _wait_for_prec_avail (void) +{ + // Increment wait counter + wait_counter++; + + // Sync control output shared memory so that the writer can see the precompile reads we have + // done, and thus update the precompile_written_address if needed + if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Tell the writer that we have read some precompile results + sem_post(sem_prec_read); + + // Make sure the precompile available semaphore is reset before checking the condition, + // since the caller may have posted it (even several times) before we called sem_wait() + while (sem_trywait(sem_prec_avail) == 0) {/*printf("Purging sem_prec_avail\n");*/}; + + // Sync control input shared memory so that we can see the latest precompile_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check if there are already precompile results available + if (*precompile_written_address > *precompile_read_address) + { + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + return 0; + } + + // Wait again, but blocking this time + while (true) + { + struct timespec ts; + int result = clock_gettime(CLOCK_REALTIME, &ts); + if (result == -1) + { + printf("ERROR: wait_for_prec_avail() failed calling clock_gettime() errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + ts.tv_sec += 5; // 5 seconds timeout + + //printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + result = sem_timedwait(sem_prec_avail, &ts); + //printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); + if ((result == -1) && (errno != ETIMEDOUT)) + { + printf("ERROR: wait_for_prec_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync control input shared memory so that we can see the latest precompile_written_address value + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (*precompile_exit_address != 0) + { + printf("ERROR: wait_for_prec_avail() found precompile_exit_address=%lu\n", *precompile_exit_address); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (*precompile_written_address > *precompile_read_address) + { + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + return 0; + } + } + + printf("ERROR: wait_for_prec_avail() unreachable code\n"); + fflush(stdout); + fflush(stderr); + exit(-1); +} \ No newline at end of file diff --git a/emulator-asm/src/c_provided.hpp b/emulator-asm/src/c_provided.hpp new file mode 100644 index 000000000..1b67b537d --- /dev/null +++ b/emulator-asm/src/c_provided.hpp @@ -0,0 +1,12 @@ +#ifndef EMULATOR_ASM_C_PROVIDED_HPP +#define EMULATOR_ASM_C_PROVIDED_HPP + +#include + +extern int _print_regs(); +extern int _print_pc (uint64_t pc, uint64_t c); +extern void _chunk_done(); +extern void _realloc_trace (void); +int _wait_for_prec_avail (void); + +#endif // EMULATOR_ASM_C_PROVIDED_HPP \ No newline at end of file diff --git a/emulator-asm/src/client.c b/emulator-asm/src/client.c new file mode 100644 index 000000000..544ddb9e5 --- /dev/null +++ b/emulator-asm/src/client.c @@ -0,0 +1,1502 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "constants.hpp" +#include "client.hpp" +#include "globals.hpp" +#include "emu.hpp" + +/**********/ +/* CLIENT */ +/**********/ + +void client_setup (void) +{ + assert(!server); + assert(client); + + int result; + + /***********************/ + /* INPUT MINIMAL TRACE */ + /***********************/ + + // Input MT trace + if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) + { + // Create the output shared memory + shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); + if (shmem_mt_fd < 0) + { + printf("ERROR: Failed calling trace shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map it to the trace address +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); +#endif + if (pTrace == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pTrace != TRACE_ADDR) + { + printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); + } + + /**********************/ + /* PRECOMPILE_RESULTS */ + /**********************/ + + if (precompile_results_enabled) + { + /**************/ + /* PRECOMPILE */ + /**************/ + + // Create the precompile results shared memory + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pPrecompile == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_precompile_address = pPrecompile; + precompile_results_address = (uint64_t *)pPrecompile; + + if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); + + /*****************/ + /* CONTROL INPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map control input address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_INPUT_ADDR) + { + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + + /*****************/ + /* CONTROL OUTPUT */ + /*****************/ + + // Create the control input shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR, 0666); + if (shmem_control_output_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map control input address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); + + /*************************/ + /* PRECOMPILE SEMAPHORES */ + /*************************/ + + // Create the semaphore for precompile results available signal + assert(strlen(sem_prec_avail_name) > 0); + + sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT, 0666, 0); + if (sem_prec_avail == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_avail_name); + + // Create the semaphore for precompile results read signal + assert(strlen(sem_prec_read_name) > 0); + + sem_prec_read = sem_open(sem_prec_read_name, O_CREAT, 0666, 0); + if (sem_prec_read == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_read_name); + } +} + +typedef enum { + PrecompileReadMode_NoPrefix, + PrecompileReadMode_Prefixed +} PrecompileReadMode; + +PrecompileReadMode precompile_read_mode = PrecompileReadMode_NoPrefix; +//PrecompileReadMode precompile_read_mode = PrecompileReadMode_Prefixed; + +typedef enum { + PrecompileWriteMode_Full, + PrecompileWriteMode_OnePrecAtATime +} PrecompileWriteMode; + +PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_Full; +//PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_OnePrecAtATime; + +//#define PRECOMPILE_FIXED_SIZE 25 // Keccak-f state size in u64s +#define PRECOMPILE_FIXED_SIZE 4 // SHA-256 state size in u64s + +void client_write_precompile_results (void) +{ + int result; + +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + + // Open input file + FILE * precompile_fp = fopen(precompile_file_name, "r"); + if (precompile_fp == NULL) + { + printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Get input file size + if (fseek(precompile_fp, 0, SEEK_END) == -1) + { + printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + long precompile_data_size = ftell(precompile_fp); + if (precompile_data_size == -1) + { + printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((precompile_data_size & 0x7) != 0) + { + printf("ERROR: Precompile results file (%s) size (%lu) is not a multiple of 8 B\n", precompile_file_name, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Go back to the first byte + if (fseek(precompile_fp, 0, SEEK_SET) == -1) + { + printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + assert(precompile_read_mode == PrecompileReadMode_NoPrefix || precompile_read_mode == PrecompileReadMode_Prefixed); + assert(precompile_write_mode == PrecompileWriteMode_Full || precompile_write_mode == PrecompileWriteMode_OnePrecAtATime); + + /*************/ + /* NO PREFIX */ + /*************/ + + if (precompile_read_mode == PrecompileReadMode_NoPrefix) + { + if (precompile_write_mode == PrecompileWriteMode_Full) + { + // Check the precompile data size is inside the proper range + if (precompile_data_size > MAX_PRECOMPILE_SIZE) + { + printf("ERROR: Size of precompile results file (%s) is too long (%lu)\n", precompile_file_name, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Copy input data into input memory + size_t precompile_read = fread(precompile_results_address, 1, precompile_data_size, precompile_fp); + if (precompile_read != precompile_data_size) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, precompile_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Initialize precompile written address + *precompile_written_address = precompile_data_size >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + else if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) + { + // Check the precompile data size is inside the proper range + if (precompile_data_size % (PRECOMPILE_FIXED_SIZE * 8) != 0) + { + printf("ERROR: Size of precompile results file (%s) is not a multiple %u * 8 B\n", precompile_file_name, PRECOMPILE_FIXED_SIZE); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Initialize precompile written address to zero + *precompile_written_address = 0; // in u64s + + // Copy in chunks of PRECOMPILE_FIXED_SIZE*8 bytes (Keccak-f state size) + uint64_t precompile_read_so_far = 0; + uint64_t data[PRECOMPILE_FIXED_SIZE]; + while (precompile_read_so_far < (uint64_t)precompile_data_size) + { + // Wait for server to read precompile results + //printf("Waiting for sem_prec_read()\n"); + result = sem_wait(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Number of bytes to read from file and write to shared memory in every loop + uint64_t bytes_to_read = sizeof(data); + + // Copy input data into input memory + size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); + if (precompile_read != bytes_to_read) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Copy data to shared memory + for (int i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); + precompile_read_so_far += 8; + } + + // Notify server that precompile results are available + *precompile_written_address = precompile_read_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + } + } + + /************/ + /* PREFIXED */ + /************/ + + else if (precompile_read_mode == PrecompileReadMode_Prefixed) + { +#define CTRL_START 0x00 +#define CTRL_END 0x01 +#define CTRL_CANCEL 0x02 +#define CTRL_ERROR 0x03 +#define HINTS_TYPE_RESULT 0x04 +#define HINTS_TYPE_ECRECOVER 0x05 +#define NUM_HINT_TYPES 0x06 + + uint64_t precompile_read_so_far = 0; + uint64_t precompile_written_so_far = 0; + + while (precompile_read_so_far < (uint64_t)precompile_data_size) + { + uint64_t data; + uint64_t bytes_to_read = sizeof(data); + + // Copy input data into input memory + size_t precompile_read = fread(&data, 1, bytes_to_read, precompile_fp); + if (precompile_read != bytes_to_read) + { + printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); + fflush(stdout); + fflush(stderr); + exit(-1); + } + precompile_read_so_far += bytes_to_read; + switch (data >> 32) + { + case CTRL_START: + //printf("Precompile CTRL_START\n"); + assert(precompile_read_so_far == 8); + break; + case CTRL_END: + //printf("Precompile CTRL_END\n"); + assert(precompile_read_so_far == precompile_data_size); + break; + // case CTRL_CANCEL: + // printf("Precompile CTRL_CANCEL\n"); + // break; + // case CTRL_ERROR: + // printf("Precompile CTRL_ERROR\n"); + // break; + case HINTS_TYPE_RESULT: + { + //printf("Precompile HINTS_TYPE_RESULT\n"); + if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) + { + // Wait for server to read precompile results + //printf("Waiting for sem_prec_read()\n"); + result = sem_wait(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + uint64_t result_length = data & 0xFFFFFFFF; + if (result_length > (precompile_data_size - precompile_read_so_far)) + { + printf("ERROR: Precompile HINTS_TYPE_RESULT length=%lu exceeds remaining file size %lu\n", result_length, precompile_data_size - precompile_read_so_far); + fflush(stdout); + fflush(stderr); + exit(-1); + } + //printf("Precompile HINTS_TYPE_RESULT result_length=%lu\n", result_length); + for (uint64_t i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &value, 8); + precompile_read_so_far += 8; + precompile_written_so_far += 8; + //printf(" Precompile result[%lu] = 0x%016lx\n", i, value); + } + + if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) + { + // Notify server that precompile results are available + *precompile_written_address = precompile_written_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + } + break; + // case HINTS_TYPE_ECRECOVER: + // { + // // Not implemented + // printf("Precompile HINTS_TYPE_ECRECOVER not implemented\n"); + // } + // break; + default: + printf("ERROR: Unknown precompile prefix type %lu\n", data >> 32); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + if (precompile_write_mode == PrecompileWriteMode_Full) + { + // Notify server that precompile results are available + *precompile_written_address = precompile_written_so_far >> 3; // in u64s + + //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); + sem_post(sem_prec_avail); + } + + } + + // Close the file pointer + fclose(precompile_fp); + +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + printf("client (precompile): done in %lu us\n", duration); +#endif +} + +void client_run (void) +{ + printf("client_run(): Starting client...\n"); + assert(client); + assert(!server); + + int result; + + /************************/ + /* Read input file data */ + /************************/ + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + + // Open input file + FILE * input_fp = fopen(input_file, "r"); + if (input_fp == NULL) + { + printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Get input file size + if (fseek(input_fp, 0, SEEK_END) == -1) + { + printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + long input_data_size = ftell(input_fp); + if (input_data_size == -1) + { + printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Go back to the first byte + if (fseek(input_fp, 0, SEEK_SET) == -1) + { + printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", input_file, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check the input data size is inside the proper range + if (input_data_size > (MAX_INPUT_SIZE - 16)) + { + printf("ERROR: Size of input file (%s) is too long (%lu)\n", input_file, input_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Open input shared memory + shmem_input_fd = shm_open(shmem_input_name, O_RDWR, 0666); + if (shmem_input_fd < 0) + { + printf("ERROR: Failed calling input shm_open(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map the shared memory object into the process address space + shmem_input_address = mmap(NULL, MAX_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmem_input_fd, 0); + if (shmem_input_address == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Write the input size in the first 64 bits + *(uint64_t *)shmem_input_address = (uint64_t)0; // free input + *(uint64_t *)(shmem_input_address + 8)= (uint64_t)input_data_size; + + // Copy input data into input memory + size_t input_read = fread(shmem_input_address + 16, 1, input_data_size, input_fp); + if (input_read != input_data_size) + { + printf("ERROR: Input read (%lu) != input file size (%lu)\n", input_read, input_data_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Close the file pointer + fclose(input_fp); + + // Unmap input + result = munmap(shmem_input_address, MAX_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + printf("client (input): done in %lu us\n", duration); +#endif + + } + + /*****************************/ + /* Read precompile file data */ + /*****************************/ + if (precompile_results_enabled) + { + // reset written counter + *precompile_written_address = 0; + + //client_write_precompile_results(); + } + + /*************************/ + /* Connect to the server */ + /*************************/ + + // Create socket to connect to server + int socket_fd; + socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd < 0) + { + printf("ERROR: socket() failed socket_fd=%d errno=%d=%s\n", socket_fd, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Configure server address + struct sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + + result = inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); + if (result <= 0) + { + printf("ERROR: inet_pton() failed. Invalid address/Address not supported result=%d errno=%d=%s\n", result, errno, strerror(errno)); + exit(-1); + } + + // Connect to server + result = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); + if (result < 0) + { + printf("ERROR: connect() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); + exit(-1); + } + if (verbose) printf("connect()'d to port=%u\n", port); + + // Request and response + uint64_t request[5]; + uint64_t response[5]; + + /********/ + /* Ping */ + /********/ + + gettimeofday(&start_time, NULL); + + // Prepare message to send + request[0] = TYPE_PING; + request[1] = 0; + request[2] = 0; + request[3] = 0; + request[4] = 0; + + // Send data to server + result = send(socket_fd, request, sizeof(request), 0); + if (result < 0) + { + printf("ERROR: send() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Read server response + ssize_t bytes_received = recv(socket_fd, response, sizeof(response), MSG_WAITALL); + if (bytes_received < 0) + { + printf("ERROR: recv() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (bytes_received != sizeof(response)) + { + printf("ERROR: recv() returned bytes_received=%ld errno=%d=%s\n", bytes_received, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (response[0] != TYPE_PONG) + { + printf("ERROR: recv() returned unexpected type=%lu\n", response[0]); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (response[1] != gen_method) + { + printf("ERROR: recv() returned unexpected gen_method=%lu\n", response[1]); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + printf("client (PING): done in %lu us\n", duration); + + /*****************/ + /* Minimal trace */ + /*****************/ + for (uint64_t i=0; i +#include +#include +#include +#include +#include "configuration.hpp" +#include "globals.hpp" +#include "asm_provided.hpp" + +/*******************************/ +/* ARGUMENTS AND CONFIGURATION */ +/*******************************/ + +// To be overwritten by arguments, if provided; otherwise, default values per generation method are used +uint16_t arguments_port = 0; + +// Print usage information: valid arguments +void print_usage (void) +{ + printf("Usage: ziskemuasm\n"); + printf("\t-s(server)\n"); + printf("\t-c(client)\n"); + printf("\t-i \n"); + printf("\t-p \n"); + printf("\t--gen=0|--generate_fast\n"); + printf("\t--gen=1|--generate_minimal_trace\n"); + printf("\t--gen=2|--generate_rom_histogram\n"); + printf("\t--gen=3|--generate_main_trace\n"); + printf("\t--gen=4|--generate_chunks\n"); + printf("\t--gen=6|--generate_zip\n"); + printf("\t--gen=9|--generate_mem_reads\n"); + printf("\t--gen=10|--generate_chunk_player_mem_reads\n"); + printf("\t--chunk \n"); + printf("\t--shutdown\n"); + printf("\t--mt \n"); + printf("\t-o output on\n"); + printf("\t--output_riscof output riscof on\n"); + printf("\t--silent silent on\n"); + printf("\t--shm_prefix (default: ZISK)\n"); + printf("\t-m metrics on\n"); + printf("\t-t trace on\n"); + printf("\t-tt trace_trace on\n"); + printf("\t-f(save to file)\n"); + printf("\t-a chunk_address\n"); + printf("\t-v verbose on\n"); + printf("\t-u unlock physical memory in mmap\n"); + printf("\t--share_input_shm share input shared memories\n"); + printf("\t--open_input_shm open existing input shared memories\n"); +#ifdef ASM_PRECOMPILE_CACHE + printf("\t--precompile-cache-store store precompile results in cache file\n"); + printf("\t--precompile-cache-load load precompile results from cache file\n"); +#endif + if (precompile_results_enabled) + { + printf("\t-r \n"); + } + printf("\t--redirect-output-to-file redirect output to file\n"); + printf("\t-h/--help print this\n"); +} + +// Parse main function arguments and configure global variables accordingly +void parse_arguments(int argc, char *argv[]) +{ + strcpy(shm_prefix, "ZISK"); + uint64_t number_of_selected_generation_methods = 0; + if (argc > 1) + { + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "-s") == 0) + { + server = true; + continue; + } + if (strcmp(argv[i], "-c") == 0) + { + client = true; + continue; + } + if ( (strcmp(argv[i], "--gen=0") == 0) || (strcmp(argv[i], "--generate_fast") == 0)) + { + gen_method = Fast; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=1") == 0) || (strcmp(argv[i], "--generate_minimal_trace") == 0)) + { + gen_method = MinimalTrace; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=2") == 0) || (strcmp(argv[i], "--generate_rom_histogram") == 0)) + { + gen_method = RomHistogram; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=3") == 0) || (strcmp(argv[i], "--generate_main_trace") == 0)) + { + gen_method = MainTrace; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=4") == 0) || (strcmp(argv[i], "--generate_chunks") == 0)) + { + gen_method = ChunksOnly; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=6") == 0) || (strcmp(argv[i], "--generate_zip") == 0)) + { + gen_method = Zip; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=7") == 0) || (strcmp(argv[i], "--generate_mem_op") == 0)) + { + gen_method = MemOp; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=8") == 0) || (strcmp(argv[i], "--generate_chunk_player_mt_collect_mem") == 0)) + { + gen_method = ChunkPlayerMTCollectMem; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=9") == 0) || (strcmp(argv[i], "--generate_mem_reads") == 0)) + { + gen_method = MemReads; + number_of_selected_generation_methods++; + continue; + } + if ( (strcmp(argv[i], "--gen=10") == 0) || (strcmp(argv[i], "--generate_chunk_player_mem_reads") == 0)) + { + gen_method = ChunkPlayerMemReadsCollectMain; + number_of_selected_generation_methods++; + continue; + } + if (strcmp(argv[i], "-o") == 0) + { + output = true; + continue; + } + if (strcmp(argv[i], "--output_riscof") == 0) + { + output_riscof = true; + continue; + } + if (strcmp(argv[i], "--silent") == 0) + { + silent = true; + continue; + } + if (strcmp(argv[i], "-m") == 0) + { + metrics = true; + continue; + } + if (strcmp(argv[i], "-t") == 0) + { + trace = true; + continue; + } + if (strcmp(argv[i], "-tt") == 0) + { + trace = true; + trace_trace = true; + continue; + } + if (strcmp(argv[i], "-v") == 0) + { + verbose = true; + //emu_verbose = true; + continue; + } + if (strcmp(argv[i], "-u") == 0) + { + map_locked_flag = 0; + continue; + } + if (strcmp(argv[i], "-h") == 0) + { + print_usage(); + exit(0); + } + if (strcmp(argv[i], "--help") == 0) + { + print_usage(); + continue; + } + if (strcmp(argv[i], "-i") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -i in the last position; please provide input file after it\n"); + print_usage(); + exit(-1); + } + if (strlen(argv[i]) > 4095) + { + printf("ERROR: Detected argument -i but next argument is too long\n"); + print_usage(); + exit(-1); + } + strcpy(input_file, argv[i]); + continue; + } + if (strcmp(argv[i], "--shm_prefix") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -i in the last position; please provide shared mem prefix after it\n"); + print_usage(); + exit(-1); + } + if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) + { + printf("ERROR: Detected argument -i but next argument is too long\n"); + print_usage(); + exit(-1); + } + strcpy(shm_prefix, argv[i]); + continue; + } + if (strcmp(argv[i], "--chunk") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -c in the last position; please provide chunk number after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + chunk_mask = strtoul(argv[i], &endptr, 10); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Chunk number is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argv[i]) { + printf("ERROR: No digits found while parsing chunk number\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after chunk number: %s\n", endptr); + print_usage(); + exit(-1); + } else if (chunk_mask > MAX_CHUNK_MASK) { + printf("ERROR: Invalid chunk number: %lu\n", chunk_mask); + print_usage(); + exit(-1); + } else { + printf("Got chunk_mask= %lu\n", chunk_mask); + } + continue; + } + if (strcmp(argv[i], "--shutdown") == 0) + { + do_shutdown = true; + continue; + } + if (strcmp(argv[i], "--mt") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -mt in the last position; please provide number of MT requests after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + number_of_mt_requests = strtoul(argv[i], &endptr, 10); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Number of MT requests is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argv[i]) { + printf("ERROR: No digits found while parsing number of MT requests\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after number of MT requests: %s\n", endptr); + print_usage(); + exit(-1); + } else if (number_of_mt_requests > 1000000) { + printf("ERROR: Invalid number of MT requests: %lu\n", number_of_mt_requests); + print_usage(); + exit(-1); + } else { + printf("Got number of MT requests= %lu\n", number_of_mt_requests); + } + continue; + } + if (strcmp(argv[i], "-p") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -p in the last position; please provide port number after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + arguments_port = strtoul(argv[i], &endptr, 10); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Port number is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argv[i]) { + printf("ERROR: No digits found while parsing port number\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after port number: %s\n", endptr); + print_usage(); + exit(-1); + } else { + printf("Got port number= %u\n", arguments_port); + } + continue; + } + if (strcmp(argv[i], "-f") == 0) + { + save_to_file = true; + continue; + } + if (strcmp(argv[i], "-a") == 0) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -a in the last position; please provide chunk address after it\n"); + print_usage(); + exit(-1); + } + errno = 0; + char *endptr; + char * argument = argv[i]; + if ((argument[0] == '0') && (argument[1] == 'x')) argument += 2; + chunk_player_address = strtoul(argv[i], &endptr, 16); + + // Check for errors + if (errno == ERANGE) { + printf("ERROR: Chunk address is too large\n"); + print_usage(); + exit(-1); + } else if (endptr == argument) { + printf("ERROR: No digits found while parsing chunk addresss\n"); + print_usage(); + exit(-1); + } else if (*endptr != '\0') { + printf("ERROR: Extra characters after chunk address: %s\n", endptr); + print_usage(); + exit(-1); + } else { + printf("Got chunk address= %p\n", (void *)chunk_player_address); + } + continue; + } + if (strcmp(argv[i], "--share_input_shm") == 0) + { + share_input_shm = true; + continue; + } + if (strcmp(argv[i], "--open_input_shm") == 0) + { + open_input_shm = true; + continue; + } + if (strcmp(argv[i], "--redirect-output-to-file") == 0) + { + redirect_output_to_file = true; + continue; + } +#ifdef ASM_PRECOMPILE_CACHE + if (strcmp(argv[i], "--precompile-cache-store") == 0) + { + precompile_cache_enabled = true; + precompile_cache_store_init(); + continue; + } + if (strcmp(argv[i], "--precompile-cache-load") == 0) + { + precompile_cache_enabled = true; + precompile_cache_load_init(); + continue; + } + +#endif + if (precompile_results_enabled && (strcmp(argv[i], "-r") == 0)) + { + i++; + if (i >= argc) + { + printf("ERROR: Detected argument -r in the last position; please provide precompile results file after it\n"); + print_usage(); + exit(-1); + } + if (strlen(argv[i]) > 4095) + { + printf("ERROR: Detected argument -r but next argument is too long\n"); + print_usage(); + exit(-1); + } + strcpy(precompile_file_name, argv[i]); + continue; + } + printf("ERROR: parse_arguments() Unrecognized argument: %s\n", argv[i]); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } +#ifdef ASM_PRECOMPILE_CACHE + if (precompile_cache_enabled == false) + { + printf("ERROR: parse_arguments() when in precompile cache mode, you need to use an argument: either --precompile-cache-store or --precompile-cache-load\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } +#endif + + // Check that only one generation method was selected as an argument + if (number_of_selected_generation_methods != 1) + { + printf("ERROR! parse_arguments() Invalid arguments: select 1 generation method, and only one\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check that the generation method selected by the process launcher is the same as the one + // for which the assembly code was generated + uint64_t asm_gen_method = get_gen_method(); + if (asm_gen_method != gen_method) + { + printf("ERROR! parse_arguments() Inconsistency: C generation method is %u but ASM generation method is %lu\n", + gen_method, + asm_gen_method); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Check server/client + if (server && client) + { + printf("ERROR! parse_arguments() Inconsistency: both server and client at the same time is not possible\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (!server && !client) + { + printf("ERROR! parse_arguments() Inconsistency: select server or client\n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (precompile_results_enabled && client && (strlen(precompile_file_name) == 0)) + { + printf("ERROR! parse_arguments() when in precompile results mode, you need to provide a precompile results file using -r \n"); + print_usage(); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +// Configure global variables based on generation method and other arguments +void configure (void) +{ + // Select configuration based on generation method + switch (gen_method) + { + case Fast: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_FT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_FT_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_FT_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_FT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_FT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_FT_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, ""); + strcpy(sem_chunk_done_name, ""); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_FT_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_FT"); + port = 23120; + break; + } + case MinimalTrace: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MT_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MT_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MT_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MT_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MT_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MT_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MT"); + call_chunk_done = true; + port = 23115; + break; + } + case RomHistogram: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_RH_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_RH_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_RH_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_RH_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_RH_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_RH_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_RH_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_RH_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_RH_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_RH"); + call_chunk_done = true; + port = 23116; + break; + } + case MainTrace: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MA_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MA_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MA_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MA_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MA_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MA_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MA_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MA_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MA_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MA"); + call_chunk_done = true; + port = 23118; + break; + } + case ChunksOnly: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CH_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CH_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_CH_input"); + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_CH_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_CH_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_CH_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_CH"); + call_chunk_done = true; + port = 23115; + break; + } + // case BusOp: + // { + // strcpy(shmem_input_name, "ZISKBO_input"); + // strcpy(shmem_output_name, "ZISKBO_output"); + // strcpy(sem_chunk_done_name, "ZISKBO_chunk_done"); + // chunk_done = true; + // port = 23115; + // break; + // } + case Zip: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_ZP_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_ZP_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_ZP_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_ZP_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_ZP_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_ZP_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_ZP_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_ZP_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_ZP_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_ZP"); + call_chunk_done = true; + port = 23115; + break; + } + case MemOp: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MO_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MO_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MO_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MO_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MO_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MO_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MO_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MO_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MO_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MO"); + call_chunk_done = true; + port = 23117; + break; + } + case ChunkPlayerMTCollectMem: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CM_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CM_control_output"); + strcpy(shmem_input_name, ""); + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_CM_output"); + strcpy(sem_chunk_done_name, ""); + strcpy(sem_shutdown_done_name, ""); + strcpy(shmem_mt_name, shm_prefix); + strcat(shmem_mt_name, "_MT_output"); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_CM"); + call_chunk_done = false; + port = 23119; + break; + } + case MemReads: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_MT_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_MT_control_output"); + strcpy(shmem_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_input_name, "_input"); + else + strcat(shmem_input_name, "_MT_input"); + if (precompile_results_enabled) + { + strcpy(shmem_precompile_name, shm_prefix); + if (share_input_shm) + strcat(shmem_precompile_name, "_precompile"); + else + strcat(shmem_precompile_name, "_MT_precompile"); + strcpy(sem_prec_avail_name, shm_prefix); + strcat(sem_prec_avail_name, "_MT_prec_avail"); + strcpy(sem_prec_read_name, shm_prefix); + strcat(sem_prec_read_name, "_MT_prec_read"); + } + else + { + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + } + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_MT_output"); + strcpy(sem_chunk_done_name, shm_prefix); + strcat(sem_chunk_done_name, "_MT_chunk_done"); + strcpy(sem_shutdown_done_name, shm_prefix); + strcat(sem_shutdown_done_name, "_MT_shutdown_done"); + strcpy(shmem_mt_name, ""); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_MT"); + call_chunk_done = true; + port = 23115; + break; + } + case ChunkPlayerMemReadsCollectMain: + { + strcpy(shmem_control_input_name, shm_prefix); + if (share_input_shm) + strcat(shmem_control_input_name, "_control_input"); + else + strcat(shmem_control_input_name, "_CA_control_input"); + strcpy(shmem_control_output_name, shm_prefix); + strcat(shmem_control_output_name, "_CA_control_output"); + strcpy(shmem_input_name, ""); + strcpy(shmem_precompile_name, ""); + strcpy(sem_prec_avail_name, ""); + strcpy(sem_prec_read_name, ""); + strcpy(shmem_output_name, shm_prefix); + strcat(shmem_output_name, "_CA_output"); + strcpy(sem_chunk_done_name, ""); + strcpy(sem_shutdown_done_name, ""); + strcpy(shmem_mt_name, shm_prefix); + strcat(shmem_mt_name, "_MT_output"); + strcpy(file_lock_name, "/tmp/"); + strcat(file_lock_name, shm_prefix); + strcat(file_lock_name, ".lock"); + strcpy(log_name, shm_prefix); + strcat(log_name, "_CA"); + call_chunk_done = false; + port = 23120; + break; + } + default: + { + printf("ERROR: configure() Invalid gen_method = %u\n", gen_method); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + if (precompile_results_enabled && (gen_method == ChunkPlayerMTCollectMem || gen_method == ChunkPlayerMemReadsCollectMain)) + { + printf("ERROR: configure() precompile results enabled is not compatible with generation method %u\n", gen_method); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (arguments_port != 0) + { + port = arguments_port; + } + + if (verbose) + { + printf("ziskemuasm configuration:\n"); + printf("\tgen_method=%u\n", gen_method); + printf("\tshm_prefix=%s\n", shm_prefix); + printf("\tfile_lock_name=%s\n", file_lock_name); + printf("\tlog_name=%s\n", log_name); + printf("\tport=%u\n", port); + printf("\tcall_chunk_done=%u\n", call_chunk_done); + printf("\tchunk_size=%lu\n", chunk_size); + printf("\tshmem_control_input=%s\n", shmem_control_input_name); + printf("\tshmem_control_output=%s\n", shmem_control_output_name); + printf("\tshmem_input=%s\n", shmem_input_name); + printf("\tshmem_precompile=%s\n", shmem_precompile_name); + printf("\tshmem_output=%s\n", shmem_output_name); + printf("\tshmem_mt=%s\n", shmem_mt_name); + printf("\tsem_chunk_done=%s\n", sem_chunk_done_name); + printf("\tsem_shutdown_done=%s\n", sem_shutdown_done_name); + printf("\tsem_prec_avail=%s\n", sem_prec_avail_name); + printf("\tsem_prec_read=%s\n", sem_prec_read_name); + printf("\tmap_locked_flag=%d\n", map_locked_flag); + printf("\toutput=%u\n", output); + printf("\tprecompile_results_enabled=%u\n", precompile_results_enabled); + printf("\toutput_riscof=%u\n", output_riscof); + } +} \ No newline at end of file diff --git a/emulator-asm/src/configuration.hpp b/emulator-asm/src/configuration.hpp new file mode 100644 index 000000000..8c9cc2c40 --- /dev/null +++ b/emulator-asm/src/configuration.hpp @@ -0,0 +1,7 @@ +#ifndef EMULATOR_ASM_CONFIGURATION_HPP +#define EMULATOR_ASM_CONFIGURATION_HPP + +void parse_arguments(int argc, char *argv[]); +void configure (void); + +#endif // EMULATOR_ASM_CONFIGURATION_HPP \ No newline at end of file diff --git a/emulator-asm/src/constants.hpp b/emulator-asm/src/constants.hpp new file mode 100644 index 000000000..9860a6a22 --- /dev/null +++ b/emulator-asm/src/constants.hpp @@ -0,0 +1,117 @@ +#ifndef EMULATOR_ASM_CONSTANTS_HPP +#define EMULATOR_ASM_CONSTANTS_HPP + +/***************/ +/* Definitions */ +/***************/ + +// Address map +// There definitions must match the ZisK rust code ones at core/src/mem.rs used to generate the +// assembly code, and that are used by the assembly code to access memory and generate the trace +#define ROM_ADDR (uint64_t)0x80000000 +#define ROM_SIZE (uint64_t)0x08000000 // 128MB +#define INPUT_ADDR (uint64_t)0x90000000 +#define MAX_INPUT_SIZE (uint64_t)0x08000000 // 128MB + +#define RAM_ADDR (uint64_t)0xA0000000 +#define RAM_SIZE (uint64_t)0x20000000 // 512MB +#define SYS_ADDR RAM_ADDR +#define SYS_SIZE (uint64_t)0x10000 +#define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) + +#ifdef TRACE_TARGET_MO + #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ + #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ +#elif defined(TRACE_TARGET_RH) + #define TRACE_INITIAL_SIZE (uint64_t)0x004000000 /* 64MB */ + #define TRACE_DELTA_SIZE (uint64_t)0x004000000 /* 64MB */ +#else + #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ + #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ +#endif + +#define TRACE_ADDR (uint64_t)0xd0000000 +#define TRACE_MAX_SIZE (uint64_t)0x1000000000 // 64GB +#define TRACE_NUMBER_OF_CHUNKS (((TRACE_MAX_SIZE - TRACE_INITIAL_SIZE) / TRACE_DELTA_SIZE) + 1) +#define TRACE_SIZE_GRANULARITY (1014*1014) // ROM histogram trace size is round up to a multiple of this granularity + +// Control input and output shared memory configuration. +// Control input is used to tell the assembly code how many precompile result u64 fields have been +// written by the client. Control output is used to tell the client how many precompile result u64 +// fields have been read by the assembly code, so the client can know when it can write new +// precompile results. Assembly code waits when the number of read fields is not lower than the +// number of written fields, and client waits when the number of written fields would exceed the +// number of read fields plus the available precompile shared memory size, which is a circular buffer +#define CONTROL_INPUT_ADDR (uint64_t)0x70000000 +#define CONTROL_INPUT_SIZE (uint64_t)0x1000 // 4kB +#define CONTROL_OUTPUT_ADDR (uint64_t)0x70001000 +#define CONTROL_OUTPUT_SIZE (uint64_t)0x1000 // 4kB +#define CONTROL_RETRY_DELAY_US 1000 // 1ms +#define CONTROL_NUMBER_OF_RETRIES 1000 // 1s max total + +// Maximum number of steps to execute, used by the client to limit the execution steps of the +// assembly code. This limit is set by the ZisK PIL constraints. +#define MAX_STEPS (1ULL << 36) + +// Assembly service request/response types +// Only the methods supported by the configured generation method will be implemented by the server, +// e.g. gen_method=1 => PING, MT and SHUTDOWN; the rest will fail with an error response. +#define TYPE_PING 1 // Ping +#define TYPE_PONG 2 +#define TYPE_MT_REQUEST 3 // Minimal trace +#define TYPE_MT_RESPONSE 4 +#define TYPE_RH_REQUEST 5 // ROM histogram +#define TYPE_RH_RESPONSE 6 +#define TYPE_MO_REQUEST 7 // Memory opcode +#define TYPE_MO_RESPONSE 8 +#define TYPE_MA_REQUEST 9 // Main packed trace +#define TYPE_MA_RESPONSE 10 +#define TYPE_CM_REQUEST 11 // Collect memory trace +#define TYPE_CM_RESPONSE 12 +#define TYPE_FA_REQUEST 13 // Fast mode, do not generate any trace +#define TYPE_FA_RESPONSE 14 +#define TYPE_MR_REQUEST 15 // Mem reads +#define TYPE_MR_RESPONSE 16 +#define TYPE_CA_REQUEST 17 // Collect main trace +#define TYPE_CA_RESPONSE 18 +#define TYPE_SD_REQUEST 1000000 // Shutdown +#define TYPE_SD_RESPONSE 1000001 + +// Server IP address, used by the client to connect to the server +#define SERVER_IP "127.0.0.1" // Change to your server IP; otherwise use localhost IP address + +// Chunk size used in generation methods that generate a trace chunk at every N steps, e.g. gen_method=1 or gen_method=7. +// It must be a power of two, and it is used to calculate the trace address threshold at which the next chunk must be mapped, +// to avoid reaching the end of the currently mapped trace memory. +#define CHUNK_SIZE (1ULL << 18) + +// Maximum trace chunk size, used to determine when the trace address is close to the end of the +// currently mapped trace memory and the next chunk must be mapped. It is calculated based on the +// maximum number of bytes that can be generated in a chunk +// Worst case: every chunk instruction is a keccak operation, with an input data of 200 bytes +// (let's use 256 bytes to be safe), and the trace includes the access to 2 source registers, 2 +// destination registers and 3 memory addresses (e.g. for a keccak operation with 3 memory operands), +// which are the maximum number of registers and memory addresses that can be accessed by a chunk +// instruction, according to the ZisK assembly code generation configuration. + +#define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) +#define MAX_TRACE_CHUNK_INFO ((44*8) + 32) +#define MAX_BYTES_DIRECT_MTRACE 256 +#define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) +#define MAX_CHUNK_TRACE_SIZE ((CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) + +// Maximum precompile results share memory size +// It is a circular buffer +#define MAX_PRECOMPILE_SIZE (uint64_t)0x400000 // 4MB + +// Maximum chunk mask for zip generation method, which indicates which chunks are included in the trace, +// and must be between 0 and 7 (inclusive), as it is used to generate a mask of 8 bits where each +// bit indicates if the corresponding chunk is included in the trace or not. +#define MAX_CHUNK_MASK 7 + +// Maximum length of the shared memory prefix, e.g. "ZISK_12345" +// This prefix is used to generate the names of the shared memories and semaphores used for +// communication and synchronization between the server and the client, +#define MAX_SHM_PREFIX_LENGTH 64 + +#endif // EMULATOR_ASM_CONSTANTS_HPP \ No newline at end of file diff --git a/emulator-asm/src/emu.c b/emulator-asm/src/emu.c index baf58a523..f61338ae7 100644 --- a/emulator-asm/src/emu.c +++ b/emulator-asm/src/emu.c @@ -612,9 +612,9 @@ extern int _opcode_blake2(uint64_t * address) #endif #ifdef DEBUG #ifdef ASM_CALL_METRICS - if (emu_verbose) printf("opcode_blake2() calling blake2b() counter=%lu address=%08lx\n", asm_call_metrics.blake2_counter, address); + if (emu_verbose) printf("opcode_blake2() calling blake2b() counter=%lu address=%p\n", asm_call_metrics.blake2_counter, address); #else - if (emu_verbose) printf("opcode_blake2() calling blake2b() address=%08lx\n", address); + if (emu_verbose) printf("opcode_blake2() calling blake2b() address=%p\n", address); #endif #endif diff --git a/emulator-asm/src/emu.hpp b/emulator-asm/src/emu.hpp index cae983a9c..057966094 100644 --- a/emulator-asm/src/emu.hpp +++ b/emulator-asm/src/emu.hpp @@ -2,6 +2,9 @@ #define EMU_ASM_HPP #include +#include + +uint64_t TimeDiff(const struct timeval startTime, const struct timeval endTime); #ifdef DEBUG extern bool emu_verbose; diff --git a/emulator-asm/src/globals.c b/emulator-asm/src/globals.c new file mode 100644 index 000000000..adb546b41 --- /dev/null +++ b/emulator-asm/src/globals.c @@ -0,0 +1,136 @@ +#define _GNU_SOURCE +#include +#include "constants.hpp" +#include "globals.hpp" + +// Configuration globals, set by arguments +bool output = false; +bool output_riscof = false; +bool silent = false; +bool metrics = false; +bool trace = false; +bool trace_trace = false; +bool verbose = false; +bool save_to_file = false; +bool share_input_shm = false; +bool open_input_shm = false; +char input_file[4096] = {0}; +bool redirect_output_to_file = false; +bool server = false; +bool client = false; +char shm_prefix[MAX_SHM_PREFIX_LENGTH] = {0}; +int map_locked_flag = MAP_LOCKED; +uint64_t chunk_mask = 0x0; +bool do_shutdown = false; +uint64_t number_of_mt_requests = 1; +uint16_t port = 0; +uint64_t chunk_player_address = 0; +char precompile_file_name[4096] = {0}; +char shmem_control_input_name[128] = {0}; +char shmem_control_output_name[128] = {0}; +char shmem_input_name[128] = {0}; +char shmem_output_name[128] = {0}; +char shmem_mt_name[128] = {0}; +char shmem_precompile_name[128] = {0}; +char sem_prec_avail_name[128] = {0}; +char sem_prec_read_name[128] = {0}; +char sem_chunk_done_name[128] = {0}; +char sem_shutdown_done_name[128] = {0}; +char file_lock_name[128] = {0}; +char log_name[128] = {0}; +bool call_chunk_done = false; + +// Configuration set by assembly code, accessed by C +bool precompile_results_enabled = false; + +// Default generation method, can be overridden by the --gen argument +GenMethod gen_method = Fast; + +// To be used when calculating partial durations +// Time measurements cannot be overlapped +struct timeval start_time; +struct timeval stop_time; +uint64_t duration; + +/*****************/ +/* SHARED MEMORY */ +/*****************/ + +// Input shared memory +int shmem_input_fd = -1; +uint64_t shmem_input_size = 0; +void * shmem_input_address = NULL; + +// Output trace shared memory +int shmem_output_fd = -1; + +// Input MT trace shared memory +int shmem_mt_fd = -1; + +// Chunk done semaphore: notifies the caller when a new chunk has been processed +sem_t * sem_chunk_done = NULL; + +// Shutdown done semaphore: notifies the caller when a shutdown has been processed +sem_t * sem_shutdown_done = NULL; + +/**************************/ +/* PRECOMPILE AND CONTROL */ +/**************************/ + +uint64_t * precompile_results_address = NULL; + +// Precompile results shared memory +int shmem_precompile_fd = -1; +uint64_t shmem_precompile_size = 0; +void * shmem_precompile_address = NULL; + +// Precompile results semaphores +sem_t * sem_prec_avail = NULL; +sem_t * sem_prec_read = NULL; + +// Control input shared memory +int shmem_control_input_fd = -1; +uint64_t * shmem_control_input_address = NULL; +volatile uint64_t * precompile_written_address = NULL; +volatile uint64_t * precompile_exit_address = NULL; + +// Control output shared memory +int shmem_control_output_fd = -1; +uint64_t * shmem_control_output_address = NULL; +volatile uint64_t * precompile_read_address = NULL; + +/**************/ +/* TRACE SIZE */ +/**************/ + +uint64_t initial_trace_size = TRACE_INITIAL_SIZE; +uint64_t trace_address = TRACE_ADDR; +uint64_t trace_size = TRACE_INITIAL_SIZE; +uint64_t trace_used_size = 0; +uint64_t trace_address_threshold = TRACE_ADDR + TRACE_INITIAL_SIZE - MAX_CHUNK_TRACE_SIZE; + +// To be used when calculating the assembly duration +uint64_t assembly_duration; + +// Counters used in functions called from assembly code +uint64_t realloc_counter = 0; +uint64_t wait_counter = 0; +uint64_t print_pc_counter = 0; + +// Chunk player globals +uint64_t chunk_player_mt_size = TRACE_INITIAL_SIZE; + +// Maximum number of steps to execute, used by the client to limit the execution steps of the +// assembly code. +uint64_t max_steps = (1ULL << 32); + +// Pointers to the input, RAM, ROM and trace memory, used by both C and assembly code to access these memories +uint64_t * pInputTrace = (uint64_t *)TRACE_ADDR; // Used for trace consumption, i.e. chunk player +uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; // Used for trace generation, i.e. assembly code writes the trace to this address, and client reads it from this address + +/**************/ +/* CHUNK SIZE */ +/**************/ + +uint64_t chunk_size = CHUNK_SIZE; +uint64_t chunk_size_mask = CHUNK_SIZE - 1; \ No newline at end of file diff --git a/emulator-asm/src/globals.hpp b/emulator-asm/src/globals.hpp new file mode 100644 index 000000000..f66e0044c --- /dev/null +++ b/emulator-asm/src/globals.hpp @@ -0,0 +1,166 @@ +#ifndef EMULATOR_ASM_GLOBALS_HPP +#define EMULATOR_ASM_GLOBALS_HPP + +#include +#include +#include +#include +#include "constants.hpp" + +// Configuration globals, set by arguments +extern bool output; +extern bool output_riscof; +extern bool silent; +extern bool metrics; +extern bool trace; +extern bool trace_trace; +extern bool verbose; +extern bool save_to_file; +extern bool share_input_shm; // Shares input shared memories: input, precompile results and control input, using a common name +extern bool open_input_shm; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) +extern char input_file[4096]; +extern bool redirect_output_to_file; +extern bool server; // Indicates that this process is a server +extern bool client; // Indicates that this process is a client (used for testing the server) +extern char shm_prefix[MAX_SHM_PREFIX_LENGTH]; // Shared memory prefix +extern int map_locked_flag; // Flag used in mmap to indicate if the physical memory is locked in RAM (MAP_LOCKED) or can be swapped (0). By default it is locked, but it can be unlocked with the -u argument, which can be useful for testing and debugging purposes, e.g. to allow core dumps when the assembly code crashes +extern uint64_t chunk_mask; // ZIP: 0, 1, 2, 3, 4, 5, 6 or 7 +extern bool do_shutdown; // If true, the client will perform a shutdown request to the server when done +extern uint64_t number_of_mt_requests; // Loop to send this number of minimal trace requests +extern uint16_t port; // Service TCP port +extern uint64_t chunk_player_address; // Chunk player address, used for generation methods that use the chunk player, i.e. gen_method=8 or gen_method=10 +extern char precompile_file_name[4096]; // Precompile results file name (used by client) +extern char shmem_control_input_name[128]; +extern char shmem_control_output_name[128]; +extern char shmem_input_name[128]; +extern char shmem_output_name[128]; +extern char shmem_mt_name[128]; +extern char shmem_precompile_name[128]; +extern char sem_prec_avail_name[128]; +extern char sem_prec_read_name[128]; +extern char sem_chunk_done_name[128]; +extern char sem_shutdown_done_name[128]; +extern char file_lock_name[128]; +extern char log_name[128]; +extern bool call_chunk_done; + +// Configuration set by assembly code, accessed by C +extern bool precompile_results_enabled; + +/*********************/ +/* Generation method */ +/*********************/ + +// Specifies how the assembly code generates the trace, and what information it includes. +// It is specified with the mandatory argument --gen= +// It must match the value returned by the assembly function get_gen_method() +// The enum names are equivalent to the rust ones defined in core/src/riscv2zisk.rs as AsmGenerationMethod +// ZisK uses generation methods 1 (minimal trace), 2 (ROM histogram) and 7 (memory operations) +// but the rest of methods can be used for testing and debugging purposes +typedef enum { + Fast = 0, + MinimalTrace = 1, + RomHistogram = 2, + MainTrace = 3, + ChunksOnly = 4, + //BusOp = 5, + Zip = 6, + MemOp = 7, + ChunkPlayerMTCollectMem = 8, + MemReads = 9, + ChunkPlayerMemReadsCollectMain = 10, +} GenMethod; + +// Default generation method, can be overridden by the --gen argument +extern GenMethod gen_method; + +// To be used when calculating partial durations +// Time measurements cannot be overlapped +extern struct timeval start_time; +extern struct timeval stop_time; +extern uint64_t duration; + +/*****************/ +/* SHARED MEMORY */ +/*****************/ + +// Input shared memory +extern int shmem_input_fd; +extern uint64_t shmem_input_size; +extern void * shmem_input_address; + +// Output trace shared memory +extern int shmem_output_fd; + +// Input MT trace shared memory +extern int shmem_mt_fd; + +// Chunk done semaphore: notifies the caller when a new chunk has been processed +extern sem_t * sem_chunk_done; + +// Shutdown done semaphore: notifies the caller when a shutdown has been processed +extern sem_t * sem_shutdown_done; + +/**************************/ +/* PRECOMPILE AND CONTROL */ +/**************************/ + +extern uint64_t * precompile_results_address; + +// Precompile results shared memory +extern int shmem_precompile_fd; +extern uint64_t shmem_precompile_size; +extern void * shmem_precompile_address; + +// Precompile results semaphores +extern sem_t * sem_prec_avail; +extern sem_t * sem_prec_read; + +// Control input shared memory +extern int shmem_control_input_fd; +extern uint64_t * shmem_control_input_address; +extern volatile uint64_t * precompile_written_address; +extern volatile uint64_t * precompile_exit_address; + +// Control output shared memory +extern int shmem_control_output_fd; +extern uint64_t * shmem_control_output_address; +extern volatile uint64_t * precompile_read_address; + +/**************/ +/* TRACE SIZE */ +/**************/ + +extern uint64_t initial_trace_size; +extern uint64_t trace_address; +extern uint64_t trace_size; +extern uint64_t trace_used_size; +extern uint64_t trace_address_threshold; + +// To be used when calculating the assembly duration +extern uint64_t assembly_duration; + +// Counters used in functions called from assembly code +extern uint64_t realloc_counter; +extern uint64_t wait_counter; +extern uint64_t print_pc_counter; + +// Chunk player globals +extern uint64_t chunk_player_mt_size; + +// Maximum number of steps to execute, used by the client to limit the execution steps of the +// assembly code. +extern uint64_t max_steps; + +// Pointers to the input, RAM, ROM and trace memory, used by both C and assembly code to access these memories +extern uint64_t * pInputTrace; // Used for trace consumption, i.e. chunk player +extern uint64_t * pOutputTrace; // Used for trace generation, i.e. assembly code writes the trace to this address, and client reads it from this address + +/**************/ +/* CHUNK SIZE */ +/**************/ + +extern uint64_t chunk_size; +extern uint64_t chunk_size_mask; + +#endif // EMULATOR_ASM_GLOBALS_HPP \ No newline at end of file diff --git a/emulator-asm/src/main.c b/emulator-asm/src/main.c index 154efe8d4..fe1708be2 100644 --- a/emulator-asm/src/main.c +++ b/emulator-asm/src/main.c @@ -19,191 +19,14 @@ #include #include #include +#include "constants.hpp" #include "emu.hpp" - -/**************************/ -/* Assembly-provided code */ -/**************************/ - -// This is the emulator assembly code start function, which will execute the code in the ROM until -// it ends, and generate the trace in the output trace memory. -// It is called from C to start the execution of the assembly code. -void emulator_start(void); - -// These functions are implemented in assembly and provide access to configuration parameters used -// to generate the assembly code, and that in some cases must match the C main program configuration -uint64_t get_max_bios_pc(void); -uint64_t get_max_program_pc(void); -uint64_t get_gen_method(void); // Must match the C main program provided argument -uint64_t get_precompile_results(void); - -// These variables are updated by the assembly code to provide information about the execution -// status and trace generation, accessed by C to generate the response to the client -extern uint64_t MEM_STEP; // Current step, i.e. number of executed instructions, updated by assembly at every step or at the end of every chunk, depending on the generation method -extern uint64_t MEM_END; // Indicates the end of execution -extern uint64_t MEM_ERROR; // Indicates an error during execution -extern uint64_t MEM_TRACE_ADDRESS; // Address of the trace memory -extern uint64_t MEM_CHUNK_ADDRESS; // Address of the current chunk -extern uint64_t MEM_CHUNK_START_STEP; // Step at which the current chunk started - -/***************/ -/* Definitions */ -/***************/ - -// Address map -// There definitions must match the ZisK rust code ones at core/src/mem.rs used to generate the -// assembly code, and that are used by the assembly code to access memory and generate the trace -#define ROM_ADDR (uint64_t)0x80000000 -#define ROM_SIZE (uint64_t)0x08000000 // 128MB -#define INPUT_ADDR (uint64_t)0x90000000 -#define MAX_INPUT_SIZE (uint64_t)0x08000000 // 128MB - -#define RAM_ADDR (uint64_t)0xA0000000 -#define RAM_SIZE (uint64_t)0x20000000 // 512MB -#define SYS_ADDR RAM_ADDR -#define SYS_SIZE (uint64_t)0x10000 -#define OUTPUT_ADDR (SYS_ADDR + SYS_SIZE) - -#ifdef TRACE_TARGET_MO - #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ - #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ -#elif defined(TRACE_TARGET_RH) - #define TRACE_INITIAL_SIZE (uint64_t)0x004000000 /* 64MB */ - #define TRACE_DELTA_SIZE (uint64_t)0x004000000 /* 64MB */ -#else - #define TRACE_INITIAL_SIZE (uint64_t)0x180000000 /* 6GB */ - #define TRACE_DELTA_SIZE (uint64_t)0x080000000 /* 2GB */ -#endif - -#define TRACE_ADDR (uint64_t)0xd0000000 -#define TRACE_MAX_SIZE (uint64_t)0x1000000000 // 64GB -#define TRACE_NUMBER_OF_CHUNKS (((TRACE_MAX_SIZE - TRACE_INITIAL_SIZE) / TRACE_DELTA_SIZE) + 1) -#define TRACE_SIZE_GRANULARITY (1014*1014) // ROM histogram trace size is round up to a multiple of this granularity - -// Worst case: every chunk instruction is a keccak operation, with an input data of 256 bytes - -// #if defined(TRACE_TARGET_MO) -// #define MAX_MTRACE_REGS_ACCESS_SIZE (3 * 8) -// #define MAX_BYTES_DIRECT_MTRACE 80 // PRE/POST = ((1 PRE + 2 SRC + 1 WR DST) * 2 + BLOCK_READ + BLOCK_WRITE) * 8 -// #define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) -// #define MAX_CHUNK_TRACE_SIZE (CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) -// #elif defined(TRACE_TARGET_RH) -// #define MAX_CHUNK_TRACE_SIZE 0 -// #else -// #define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) -// #define MAX_TRACE_CHUNK_INFO ((44*8) + 32) -// #define MAX_BYTES_DIRECT_MTRACE 256 -// #define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) -// #define MAX_CHUNK_TRACE_SIZE ((CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) -// #endif -// uint64_t trace_resize_request = 0; -// uint64_t trace_address_threshold = TRACE_ADDR + INITIAL_TRACE_SIZE - MAX_CHUNK_TRACE_SIZE; - -// Control input and output shared memory configuration. -// Control input is used to tell the assembly code how many precompile result u64 fields have been -// written by the client. Control output is used to tell the client how many precompile result u64 -// fields have been read by the assembly code, so the client can know when it can write new -// precompile results. Assembly code waits when the number of read fields is not lower than the -// number of written fields, and client waits when the number of written fields would exceed the -// number of read fields plus the available precompile shared memory size, which is a circular buffer -#define CONTROL_INPUT_ADDR (uint64_t)0x70000000 -#define CONTROL_INPUT_SIZE (uint64_t)0x1000 // 4kB -#define CONTROL_OUTPUT_ADDR (uint64_t)0x70001000 -#define CONTROL_OUTPUT_SIZE (uint64_t)0x1000 // 4kB -#define CONTROL_RETRY_DELAY_US 1000 // 1ms -#define CONTROL_NUMBER_OF_RETRIES 1000 // 1s max total - -// Maximum number of steps to execute, used by the client to limit the execution steps of the -// assembly code. This limit is set by the ZisK PIL constraints. -#define MAX_STEPS (1ULL << 36) - -// Assembly service request/response types -// Only the methods supported by the configured generation method will be implemented by the server, -// e.g. gen_method=1 => PING, MT and SHUTDOWN; the rest will fail with an error response. -#define TYPE_PING 1 // Ping -#define TYPE_PONG 2 -#define TYPE_MT_REQUEST 3 // Minimal trace -#define TYPE_MT_RESPONSE 4 -#define TYPE_RH_REQUEST 5 // ROM histogram -#define TYPE_RH_RESPONSE 6 -#define TYPE_MO_REQUEST 7 // Memory opcode -#define TYPE_MO_RESPONSE 8 -#define TYPE_MA_REQUEST 9 // Main packed trace -#define TYPE_MA_RESPONSE 10 -#define TYPE_CM_REQUEST 11 // Collect memory trace -#define TYPE_CM_RESPONSE 12 -#define TYPE_FA_REQUEST 13 // Fast mode, do not generate any trace -#define TYPE_FA_RESPONSE 14 -#define TYPE_MR_REQUEST 15 // Mem reads -#define TYPE_MR_RESPONSE 16 -#define TYPE_CA_REQUEST 17 // Collect main trace -#define TYPE_CA_RESPONSE 18 -#define TYPE_SD_REQUEST 1000000 // Shutdown -#define TYPE_SD_RESPONSE 1000001 - -// Server IP address, used by the client to connect to the server -#define SERVER_IP "127.0.0.1" // Change to your server IP; otherwise use localhost IP address - -// Chunk size used in generation methods that generate a trace chunk at every N steps, e.g. gen_method=1 or gen_method=7. -// It must be a power of two, and it is used to calculate the trace address threshold at which the next chunk must be mapped, -// to avoid reaching the end of the currently mapped trace memory. -#define CHUNK_SIZE (1ULL << 18) - -// Maximum trace chunk size, used to determine when the trace address is close to the end of the -// currently mapped trace memory and the next chunk must be mapped. It is calculated based on the -// maximum number of bytes that can be generated in a chunk -// Worst case: every chunk instruction is a keccak operation, with an input data of 200 bytes -// (let's use 256 bytes to be safe), and the trace includes the access to 2 source registers, 2 -// destination registers and 3 memory addresses (e.g. for a keccak operation with 3 memory operands), -// which are the maximum number of registers and memory addresses that can be accessed by a chunk -// instruction, according to the ZisK assembly code generation configuration. - -#define MAX_MTRACE_REGS_ACCESS_SIZE ((2 + 2 + 3) * 8) -#define MAX_TRACE_CHUNK_INFO ((44*8) + 32) -#define MAX_BYTES_DIRECT_MTRACE 256 -#define MAX_BYTES_MTRACE_STEP (MAX_BYTES_DIRECT_MTRACE + MAX_MTRACE_REGS_ACCESS_SIZE) -#define MAX_CHUNK_TRACE_SIZE ((CHUNK_SIZE * MAX_BYTES_MTRACE_STEP) + MAX_TRACE_CHUNK_INFO) - -// Maximum precompile results share memory size -// It is a circular buffer -#define MAX_PRECOMPILE_SIZE (uint64_t)0x400000 // 4MB - -// Maximum chunk mask for zip generation method, which indicates which chunks are included in the trace, -// and must be between 0 and 7 (inclusive), as it is used to generate a mask of 8 bits where each -// bit indicates if the corresponding chunk is included in the trace or not. -#define MAX_CHUNK_MASK 7 - -// Maximum length of the shared memory prefix, e.g. "ZISK_12345" -// This prefix is used to generate the names of the shared memories and semaphores used for -// communication and synchronization between the server and the client, -#define MAX_SHM_PREFIX_LENGTH 64 - -/*********************/ -/* Generation method */ -/*********************/ - -// Specifies how the assembly code generates the trace, and what information it includes. -// It is specified with the mandatory argument --gen= -// It must match the value returned by the assembly function get_gen_method() -// The enum names are equivalent to the rust ones defined in core/src/riscv2zisk.rs as AsmGenerationMethod -// ZisK uses generation methods 1 (minimal trace), 2 (ROM histogram) and 7 (memory operations) -// but the rest of methods can be used for testing and debugging purposes -typedef enum { - Fast = 0, - MinimalTrace = 1, - RomHistogram = 2, - MainTrace = 3, - ChunksOnly = 4, - //BusOp = 5, - Zip = 6, - MemOp = 7, - ChunkPlayerMTCollectMem = 8, - MemReads = 9, - ChunkPlayerMemReadsCollectMain = 10, -} GenMethod; - -// Default generation method, can be overridden by the --gen argument -GenMethod gen_method = Fast; +#include "asm_provided.hpp" +#include "globals.hpp" +#include "configuration.hpp" +#include "server.hpp" +#include "client.hpp" +#include "trace.hpp" // Returns the acronym of the generation method, used for logging and file naming const char * gen_method_acronym(GenMethod method) @@ -225,44 +48,11 @@ const char * gen_method_acronym(GenMethod method) } } -// Pointers to the input, RAM, ROM and trace memory, used by both C and assembly code to access these memories -uint64_t * pInputTrace = (uint64_t *)TRACE_ADDR; // Used for trace consumption, i.e. chunk player -uint64_t * pOutputTrace = (uint64_t *)TRACE_ADDR; // Used for trace generation, i.e. assembly code writes the trace to this address, and client reads it from this address - -// Service TCP parameters -uint16_t port = 0; -uint16_t arguments_port = 0; - -// Type of execution -bool server = false; -bool client = false; -bool call_chunk_done = false; -bool do_shutdown = false; // If true, the client will perform a shutdown request to the server when done -uint64_t number_of_mt_requests = 1; // Loop to send this number of minimal trace requests - -// To be used when calculating partial durations -// Time measurements cannot be overlapped -struct timeval start_time; -struct timeval stop_time; -uint64_t duration; - // To be used when calculating total duration struct timeval total_start_time; struct timeval total_stop_time; uint64_t total_duration; -// To be used when calculating the assembly duration -uint64_t assembly_duration; - -// Counters used in functions called from assembly code -uint64_t realloc_counter = 0; -uint64_t wait_counter = 0; -uint64_t print_pc_counter = 0; - -// Chunk player globals -uint64_t chunk_player_address = 0; -uint64_t chunk_player_mt_size = TRACE_INITIAL_SIZE; - // Checks if a number is a power of two, used to validate the max steps and chunk size provided by the client bool is_power_of_two (uint64_t number) { @@ -273,10 +63,6 @@ bool is_power_of_two (uint64_t number) /* MAX STEPS */ /*************/ -// Maximum number of steps to execute, used by the client to limit the execution steps of the -// assembly code. -uint64_t max_steps = (1ULL << 32); - // Sets the maximum number of steps provided by the client in the request void set_max_steps (uint64_t new_max_steps) { @@ -290,39 +76,10 @@ void set_max_steps (uint64_t new_max_steps) max_steps = new_max_steps; } -uint64_t trace_address_threshold = TRACE_ADDR + TRACE_INITIAL_SIZE - MAX_CHUNK_TRACE_SIZE; - -int map_locked_flag = MAP_LOCKED; - - -/**************/ -/* TRACE SIZE */ -/**************/ - -uint64_t initial_trace_size = TRACE_INITIAL_SIZE; -uint64_t trace_address = TRACE_ADDR; -uint64_t trace_size = TRACE_INITIAL_SIZE; -uint64_t trace_used_size = 0; - -void set_trace_size (uint64_t new_trace_size) -{ - // Update trace global variables - // printf("%s trace resize (trace_resize_request: %ld): %ld MB => %ld MB\n", log_name, trace_resize_request, trace_size >> 20, new_trace_size >> 20); - - // trace_resize_request = 0; - - trace_size = new_trace_size; - trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; - pOutputTrace[2] = trace_size; -} - /**************/ /* CHUNK SIZE */ /**************/ -uint64_t chunk_size = CHUNK_SIZE; -uint64_t chunk_size_mask = CHUNK_SIZE - 1; - void set_chunk_size (uint64_t new_chunk_size) { if (!is_power_of_two(new_chunk_size)) @@ -337,350 +94,25 @@ void set_chunk_size (uint64_t new_chunk_size) trace_address_threshold = TRACE_ADDR + trace_size - MAX_CHUNK_TRACE_SIZE; } -void parse_arguments(int argc, char *argv[]); -uint64_t TimeDiff(const struct timeval startTime, const struct timeval endTime); -void configure (void); -void server_setup (void); -void server_reset_fast (void); -void server_reset_slow (void); -void server_reset_trace (void); -void server_run (void); -void server_cleanup (void); -void client_setup (void); -void client_run (void); -void client_cleanup (void); -void _chunk_done(void); -void log_minimal_trace(void); -void log_histogram(void); -void log_main_trace(void); -void log_mem_trace(void); -void log_mem_op(void); -void save_mem_op_to_files(void); -void log_chunk_player_main_trace(void); -int recv_all_with_timeout (int sockfd, void *buffer, size_t length, int flags, int timeout_sec); -void file_lock(void); - -// Configuration globals, set by arguments -bool output = false; -bool output_riscof = false; -bool silent = false; -bool metrics = false; -bool trace = false; -bool trace_trace = false; -bool verbose = false; -bool save_to_file = false; -bool share_input_shm = false; // Shares input shared memories: input, precompile results and control input, using a common name -bool open_input_shm = false; // Opens existing input shared memories, without creating them. They must be previously created by another process (assembly emulator or witness computation) -char input_file[4096] = {0}; -bool redirect_output_to_file = false; +//#define USE_FILE_LOCK -// ROM histogram -uint64_t histogram_size = 0; -uint64_t bios_size = 0; -uint64_t program_size = 0; +#ifdef USE_FILE_LOCK -// Zip -uint64_t chunk_mask = 0x0; // 0, 1, 2, 3, 4, 5, 6 or 7 - -/*****************/ -/* SHARED MEMORY */ -/*****************/ - -// Shared memory prefix -char shm_prefix[MAX_SHM_PREFIX_LENGTH]; - -// Input shared memory -char shmem_input_name[128]; -int shmem_input_fd = -1; -uint64_t shmem_input_size = 0; -void * shmem_input_address = NULL; - -// Output trace shared memory -char shmem_output_name[128]; -int shmem_output_fd = -1; - -// Input MT trace shared memory -char shmem_mt_name[128]; -int shmem_mt_fd = -1; - -// Chunk done semaphore: notifies the caller when a new chunk has been processed -char sem_chunk_done_name[128]; -sem_t * sem_chunk_done = NULL; - -// Shutdown done semaphore: notifies the caller when a shutdown has been processed -char sem_shutdown_done_name[128]; -sem_t * sem_shutdown_done = NULL; +void file_lock(void); // File lock name, used to lock a file that indicates that the assembly emulator process is running, // to prevent multiple instances of the server from running at the same time. -char file_lock_name[128]; int file_lock_fd = -1; -// Log name -char log_name[128]; +#endif // USE_FILE_LOCK // Process id int process_id = 0; -/**************************/ -/* PRECOMPILE AND CONTROL */ -/**************************/ - #ifdef ASM_PRECOMPILE_CACHE bool precompile_cache_enabled = false; #endif -bool precompile_results_enabled = false; -uint64_t * precompile_results_address = NULL; - -// Precompile results shared memory -char shmem_precompile_name[128]; -int shmem_precompile_fd = -1; -uint64_t shmem_precompile_size = 0; -void * shmem_precompile_address = NULL; - -// Precompile results semaphores -char sem_prec_avail_name[128]; -sem_t * sem_prec_avail = NULL; -char sem_prec_read_name[128]; -sem_t * sem_prec_read = NULL; - -// Precompile results file name (used by client) -char precompile_file_name[4096] = {0}; - -// Control input shared memory -char shmem_control_input_name[128]; -int shmem_control_input_fd = -1; -uint64_t * shmem_control_input_address = NULL; -volatile uint64_t * precompile_written_address = NULL; -volatile uint64_t * precompile_exit_address = NULL; - -// Control output shared memory -char shmem_control_output_name[128]; -int shmem_control_output_fd = -1; -uint64_t * shmem_control_output_address = NULL; -volatile uint64_t * precompile_read_address = NULL; - -/********************/ -/* TRACE ALLOCATION */ -/********************/ - -void trace_virtual_alloc (void) -{ - void * addr = mmap((void *)TRACE_ADDR, TRACE_MAX_SIZE, PROT_NONE, MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (addr != (void *)TRACE_ADDR) - { - printf("ERROR: trace_virtual_alloc() failed to reserve trace memory at address 0x%lx\n", TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -uint64_t next_chunk_id = 0; // Next trace chunk id to be mapped, starting from 0 -int trace_chunk_fd[TRACE_NUMBER_OF_CHUNKS]; // File descriptors for each chunk -uint64_t trace_total_mapped_size = 0; // Total mapped trace size - -void * trace_get_chunk_address (uint64_t chunk_id) -{ - assert(gen_method != RomHistogram || chunk_id == 0); - - if (chunk_id == 0) - { - return (void *)TRACE_ADDR; - } - else - { - return (void *)(TRACE_ADDR + TRACE_INITIAL_SIZE + ((chunk_id - 1) * TRACE_DELTA_SIZE)); - } -} - -uint64_t trace_get_chunk_size (uint64_t chunk_id) -{ - if (gen_method == RomHistogram) { - assert(chunk_id == 0); - return trace_size; - } - - if (chunk_id == 0) - { - return TRACE_INITIAL_SIZE; - } - else - { - return TRACE_DELTA_SIZE; - } -} - -void trace_generate_shmem_chunk_name(char * shmem_chunk_name, size_t shmem_chunk_name_size, uint64_t chunk_id) -{ - int result = snprintf(shmem_chunk_name, shmem_chunk_name_size, "%s_%lu", shmem_output_name, chunk_id); - if (result < 0 || result >= (int)shmem_chunk_name_size) - { - printf("ERROR: trace_generate_shmem_chunk_name() failed to create chunk shared memory name\n"); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -void trace_cleanup (void) -{ - // Unmap all mapped chunks - for (uint64_t chunk_id = 0; chunk_id < next_chunk_id; chunk_id++) - { - uint64_t chunk_size = trace_get_chunk_size(chunk_id); - void * chunk_address = trace_get_chunk_address(chunk_id); - int result = munmap(chunk_address, chunk_size); - if (result != 0) - { - printf("ERROR: trace_cleanup() failed calling munmap() chunk id=%lu size=%lu B address=0x%lx errno=%d=%s\n", chunk_id, chunk_size, (uint64_t)chunk_address, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Close the chunk shared memory file descriptor - close(trace_chunk_fd[chunk_id]); - trace_chunk_fd[chunk_id] = -1; - - // Build the chunk shared memory name - char shmem_chunk_name[128]; - trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); - - // Make sure the chunk shared memory is deleted - shm_unlink(shmem_chunk_name); - } - - // Reset next chunk id - next_chunk_id = 0; -} - -void trace_preventive_cleanup (void) -{ - // Unmap all mapped chunks - for (uint64_t chunk_id = 0; chunk_id < TRACE_NUMBER_OF_CHUNKS; chunk_id++) - { - // Build the chunk shared memory name - char shmem_chunk_name[128]; - trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); - - // Make sure the chunk shared memory is deleted - int result = shm_unlink(shmem_chunk_name); - if (result != 0) - { - break; - } - if (verbose) printf("trace_preventive_cleanup() unlinked chunk shared memory %s\n", shmem_chunk_name); - } -} - -void trace_map_next_chunk (void) -{ - // Get the next chunk id, size and address - uint64_t chunk_id = next_chunk_id; - if (chunk_id >= TRACE_NUMBER_OF_CHUNKS) - { - printf("ERROR: trace_map_next_chunk() exceeded maximum number of chunks %lu\n", TRACE_NUMBER_OF_CHUNKS); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t chunk_size = trace_get_chunk_size(chunk_id); - void * chunk_address = trace_get_chunk_address(chunk_id); - - if (verbose) printf("trace_map_next_chunk() mapping chunk id=%lu size=%lu B address=0x%lx\n", chunk_id, chunk_size, (uint64_t)chunk_address); - - // Build the chunk shared memory name - char shmem_chunk_name[128]; - trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); - - // Make sure the chunk shared memory is deleted - shm_unlink(shmem_chunk_name); - - // Create the output shared memory - trace_chunk_fd[chunk_id] = shm_open(shmem_chunk_name, O_RDWR | O_CREAT | O_EXCL, 0666); - if (trace_chunk_fd[chunk_id] < 0) - { - printf("ERROR: trace_map_next_chunk() failed calling trace shm_open(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Size it - int result = ftruncate(trace_chunk_fd[chunk_id], chunk_size); - if (result != 0) - { - printf("ERROR: trace_map_next_chunk() failed calling ftruncate(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Sync - fsync(trace_chunk_fd[chunk_id]); - - // Map it to the trace address - if (verbose) gettimeofday(&start_time, NULL); - void * requested_address; - if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) - { - requested_address = 0; - } - else - { - requested_address = (void *)chunk_address; - } - int flags = MAP_SHARED | map_locked_flag; - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - flags |= MAP_FIXED; - } - void * pTrace = mmap(requested_address, chunk_size, PROT_READ | PROT_WRITE, flags, trace_chunk_fd[chunk_id], 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pTrace == MAP_FAILED) - { - printf("ERROR: trace_map_next_chunk() failed calling mmap(pTrace) name=%s errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && ((uint64_t)pTrace != (uint64_t)requested_address)) - { - printf("ERROR: trace_map_next_chunk() called mmap(trace) but returned address = %p != 0x%lx\n", pTrace, (uint64_t)requested_address); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("trace_map_next_chunk() mapped %lu B to %s and returned address %p in %lu us\n", chunk_size, shmem_chunk_name, pTrace, duration); - - // Update total mapped size - trace_total_mapped_size += chunk_size; - - // Increment next chunk id - next_chunk_id++; -} - -void trace_map_initialize (void) -{ - // Perform preventive cleanup of any leftover shared memory chunks - trace_preventive_cleanup(); - - // Reserve the full virtual trace address space - trace_virtual_alloc(); - - // Map the first chunk, i.e. chunk 0 - trace_map_next_chunk(); - - trace_address = TRACE_ADDR; - pOutputTrace = (uint64_t *)TRACE_ADDR; -} - /********/ /* MAIN */ /********/ @@ -739,11 +171,13 @@ int main(int argc, char *argv[]) // Configure based on parguments configure(); +#ifdef USE_FILE_LOCK // Lock file - // if (server) - // { - // file_lock(); - // } + if (server) + { + file_lock(); + } +#endif // Send a message to stderr // fprintf(stderr, "%s stderr test (not an error): Starting Ziskemu ASM emulator process id=%d server=%d client=%d gen_method=%d port=%u\n", log_name, process_id, server, client, gen_method, port); @@ -1262,4418 +696,32 @@ int main(int argc, char *argv[]) #endif } -/*******************************/ -/* ARGUMENTS AND CONFIGURATION */ -/*******************************/ +/*************/ +/* FILE LOCK */ +/*************/ + +#ifdef USE_FILE_LOCK -// Print usage information: valid arguments -void print_usage (void) +// Lock file exclusively to ensure that only one instance of the program is running at a time +void file_lock(void) { - printf("Usage: ziskemuasm\n"); - printf("\t-s(server)\n"); - printf("\t-c(client)\n"); - printf("\t-i \n"); - printf("\t-p \n"); - printf("\t--gen=0|--generate_fast\n"); - printf("\t--gen=1|--generate_minimal_trace\n"); - printf("\t--gen=2|--generate_rom_histogram\n"); - printf("\t--gen=3|--generate_main_trace\n"); - printf("\t--gen=4|--generate_chunks\n"); - printf("\t--gen=6|--generate_zip\n"); - printf("\t--gen=9|--generate_mem_reads\n"); - printf("\t--gen=10|--generate_chunk_player_mem_reads\n"); - printf("\t--chunk \n"); - printf("\t--shutdown\n"); - printf("\t--mt \n"); - printf("\t-o output on\n"); - printf("\t--output_riscof output riscof on\n"); - printf("\t--silent silent on\n"); - printf("\t--shm_prefix (default: ZISK)\n"); - printf("\t-m metrics on\n"); - printf("\t-t trace on\n"); - printf("\t-tt trace_trace on\n"); - printf("\t-f(save to file)\n"); - printf("\t-a chunk_address\n"); - printf("\t-v verbose on\n"); - printf("\t-u unlock physical memory in mmap\n"); - printf("\t--share_input_shm share input shared memories\n"); - printf("\t--open_input_shm open existing input shared memories\n"); -#ifdef ASM_PRECOMPILE_CACHE - printf("\t--precompile-cache-store store precompile results in cache file\n"); - printf("\t--precompile-cache-load load precompile results from cache file\n"); -#endif - if (precompile_results_enabled) - { - printf("\t-r \n"); + // Open (or create) the lock file. We don't need to write to it. + file_lock_fd = open(file_lock_name, O_CREAT | O_RDONLY, 0644); + if (file_lock_fd == -1) { + printf("ERROR: file_lock() failed calling open(%s) errno=%d=%s\n", file_lock_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(1); + } + + // Try to acquire an exclusive lock, non-blocking. + if (flock(file_lock_fd, LOCK_EX | LOCK_NB) == -1) { + // If we fail to get the lock, another instance is running. + printf("ERROR: Another instance of this program is already running.\n"); + fflush(stdout); + fflush(stderr); + exit(1); } - printf("\t--redirect-output-to-file redirect output to file\n"); - printf("\t-h/--help print this\n"); } -// Parse main function arguments and configure global variables accordingly -void parse_arguments(int argc, char *argv[]) -{ - strcpy(shm_prefix, "ZISK"); - uint64_t number_of_selected_generation_methods = 0; - if (argc > 1) - { - for (int i = 1; i < argc; i++) - { - if (strcmp(argv[i], "-s") == 0) - { - server = true; - continue; - } - if (strcmp(argv[i], "-c") == 0) - { - client = true; - continue; - } - if ( (strcmp(argv[i], "--gen=0") == 0) || (strcmp(argv[i], "--generate_fast") == 0)) - { - gen_method = Fast; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=1") == 0) || (strcmp(argv[i], "--generate_minimal_trace") == 0)) - { - gen_method = MinimalTrace; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=2") == 0) || (strcmp(argv[i], "--generate_rom_histogram") == 0)) - { - gen_method = RomHistogram; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=3") == 0) || (strcmp(argv[i], "--generate_main_trace") == 0)) - { - gen_method = MainTrace; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=4") == 0) || (strcmp(argv[i], "--generate_chunks") == 0)) - { - gen_method = ChunksOnly; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=6") == 0) || (strcmp(argv[i], "--generate_zip") == 0)) - { - gen_method = Zip; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=7") == 0) || (strcmp(argv[i], "--generate_mem_op") == 0)) - { - gen_method = MemOp; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=8") == 0) || (strcmp(argv[i], "--generate_chunk_player_mt_collect_mem") == 0)) - { - gen_method = ChunkPlayerMTCollectMem; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=9") == 0) || (strcmp(argv[i], "--generate_mem_reads") == 0)) - { - gen_method = MemReads; - number_of_selected_generation_methods++; - continue; - } - if ( (strcmp(argv[i], "--gen=10") == 0) || (strcmp(argv[i], "--generate_chunk_player_mem_reads") == 0)) - { - gen_method = ChunkPlayerMemReadsCollectMain; - number_of_selected_generation_methods++; - continue; - } - if (strcmp(argv[i], "-o") == 0) - { - output = true; - continue; - } - if (strcmp(argv[i], "--output_riscof") == 0) - { - output_riscof = true; - continue; - } - if (strcmp(argv[i], "--silent") == 0) - { - silent = true; - continue; - } - if (strcmp(argv[i], "-m") == 0) - { - metrics = true; - continue; - } - if (strcmp(argv[i], "-t") == 0) - { - trace = true; - continue; - } - if (strcmp(argv[i], "-tt") == 0) - { - trace = true; - trace_trace = true; - continue; - } - if (strcmp(argv[i], "-v") == 0) - { - verbose = true; - //emu_verbose = true; - continue; - } - if (strcmp(argv[i], "-u") == 0) - { - map_locked_flag = 0; - continue; - } - if (strcmp(argv[i], "-h") == 0) - { - print_usage(); - exit(0); - } - if (strcmp(argv[i], "--help") == 0) - { - print_usage(); - continue; - } - if (strcmp(argv[i], "-i") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -i in the last position; please provide input file after it\n"); - print_usage(); - exit(-1); - } - if (strlen(argv[i]) > 4095) - { - printf("ERROR: Detected argument -i but next argument is too long\n"); - print_usage(); - exit(-1); - } - strcpy(input_file, argv[i]); - continue; - } - if (strcmp(argv[i], "--shm_prefix") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -i in the last position; please provide shared mem prefix after it\n"); - print_usage(); - exit(-1); - } - if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) - { - printf("ERROR: Detected argument -i but next argument is too long\n"); - print_usage(); - exit(-1); - } - strcpy(shm_prefix, argv[i]); - continue; - } - if (strcmp(argv[i], "--chunk") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -c in the last position; please provide chunk number after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - chunk_mask = strtoul(argv[i], &endptr, 10); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Chunk number is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argv[i]) { - printf("ERROR: No digits found while parsing chunk number\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after chunk number: %s\n", endptr); - print_usage(); - exit(-1); - } else if (chunk_mask > MAX_CHUNK_MASK) { - printf("ERROR: Invalid chunk number: %lu\n", chunk_mask); - print_usage(); - exit(-1); - } else { - printf("Got chunk_mask= %lu\n", chunk_mask); - } - continue; - } - if (strcmp(argv[i], "--shutdown") == 0) - { - do_shutdown = true; - continue; - } - if (strcmp(argv[i], "--mt") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -mt in the last position; please provide number of MT requests after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - number_of_mt_requests = strtoul(argv[i], &endptr, 10); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Number of MT requests is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argv[i]) { - printf("ERROR: No digits found while parsing number of MT requests\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after number of MT requests: %s\n", endptr); - print_usage(); - exit(-1); - } else if (number_of_mt_requests > 1000000) { - printf("ERROR: Invalid number of MT requests: %lu\n", number_of_mt_requests); - print_usage(); - exit(-1); - } else { - printf("Got number of MT requests= %lu\n", number_of_mt_requests); - } - continue; - } - if (strcmp(argv[i], "-p") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -p in the last position; please provide port number after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - arguments_port = strtoul(argv[i], &endptr, 10); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Port number is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argv[i]) { - printf("ERROR: No digits found while parsing port number\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after port number: %s\n", endptr); - print_usage(); - exit(-1); - } else { - printf("Got port number= %u\n", arguments_port); - } - continue; - } - if (strcmp(argv[i], "-f") == 0) - { - save_to_file = true; - continue; - } - if (strcmp(argv[i], "-a") == 0) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -a in the last position; please provide chunk address after it\n"); - print_usage(); - exit(-1); - } - errno = 0; - char *endptr; - char * argument = argv[i]; - if ((argument[0] == '0') && (argument[1] == 'x')) argument += 2; - chunk_player_address = strtoul(argv[i], &endptr, 16); - - // Check for errors - if (errno == ERANGE) { - printf("ERROR: Chunk address is too large\n"); - print_usage(); - exit(-1); - } else if (endptr == argument) { - printf("ERROR: No digits found while parsing chunk addresss\n"); - print_usage(); - exit(-1); - } else if (*endptr != '\0') { - printf("ERROR: Extra characters after chunk address: %s\n", endptr); - print_usage(); - exit(-1); - } else { - printf("Got chunk address= %p\n", (void *)chunk_player_address); - } - continue; - } - if (strcmp(argv[i], "--share_input_shm") == 0) - { - share_input_shm = true; - continue; - } - if (strcmp(argv[i], "--open_input_shm") == 0) - { - open_input_shm = true; - continue; - } - if (strcmp(argv[i], "--redirect-output-to-file") == 0) - { - redirect_output_to_file = true; - continue; - } -#ifdef ASM_PRECOMPILE_CACHE - if (strcmp(argv[i], "--precompile-cache-store") == 0) - { - precompile_cache_enabled = true; - precompile_cache_store_init(); - continue; - } - if (strcmp(argv[i], "--precompile-cache-load") == 0) - { - precompile_cache_enabled = true; - precompile_cache_load_init(); - continue; - } - -#endif - if (precompile_results_enabled && (strcmp(argv[i], "-r") == 0)) - { - i++; - if (i >= argc) - { - printf("ERROR: Detected argument -r in the last position; please provide precompile results file after it\n"); - print_usage(); - exit(-1); - } - if (strlen(argv[i]) > 4095) - { - printf("ERROR: Detected argument -r but next argument is too long\n"); - print_usage(); - exit(-1); - } - strcpy(precompile_file_name, argv[i]); - continue; - } - printf("ERROR: parse_arguments() Unrecognized argument: %s\n", argv[i]); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } -#ifdef ASM_PRECOMPILE_CACHE - if (precompile_cache_enabled == false) - { - printf("ERROR: parse_arguments() when in precompile cache mode, you need to use an argument: either --precompile-cache-store or --precompile-cache-load\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } -#endif - - // Check that only one generation method was selected as an argument - if (number_of_selected_generation_methods != 1) - { - printf("ERROR! parse_arguments() Invalid arguments: select 1 generation method, and only one\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check that the generation method selected by the process launcher is the same as the one - // for which the assembly code was generated - uint64_t asm_gen_method = get_gen_method(); - if (asm_gen_method != gen_method) - { - printf("ERROR! parse_arguments() Inconsistency: C generation method is %u but ASM generation method is %lu\n", - gen_method, - asm_gen_method); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check server/client - if (server && client) - { - printf("ERROR! parse_arguments() Inconsistency: both server and client at the same time is not possible\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (!server && !client) - { - printf("ERROR! parse_arguments() Inconsistency: select server or client\n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (precompile_results_enabled && client && (strlen(precompile_file_name) == 0)) - { - printf("ERROR! parse_arguments() when in precompile results mode, you need to provide a precompile results file using -r \n"); - print_usage(); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -// Configure global variables based on generation method and other arguments -void configure (void) -{ - // Select configuration based on generation method - switch (gen_method) - { - case Fast: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_FT_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_FT_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_FT_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_FT_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_FT_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_FT_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, ""); - strcpy(sem_chunk_done_name, ""); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_FT_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_FT"); - port = 23120; - break; - } - case MinimalTrace: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MT_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MT_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MT_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MT_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MT_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MT_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MT_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MT_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MT_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MT"); - call_chunk_done = true; - port = 23115; - break; - } - case RomHistogram: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_RH_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_RH_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_RH_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_RH_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_RH_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_RH_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_RH_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_RH_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_RH_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_RH"); - call_chunk_done = true; - port = 23116; - break; - } - case MainTrace: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MA_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MA_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MA_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MA_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MA_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MA_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MA_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MA_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MA_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MA"); - call_chunk_done = true; - port = 23118; - break; - } - case ChunksOnly: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_CH_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_CH_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_CH_input"); - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_CH_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_CH_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_CH_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_CH"); - call_chunk_done = true; - port = 23115; - break; - } - // case BusOp: - // { - // strcpy(shmem_input_name, "ZISKBO_input"); - // strcpy(shmem_output_name, "ZISKBO_output"); - // strcpy(sem_chunk_done_name, "ZISKBO_chunk_done"); - // chunk_done = true; - // port = 23115; - // break; - // } - case Zip: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_ZP_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_ZP_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_ZP_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_ZP_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_ZP_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_ZP_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_ZP_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_ZP_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_ZP_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_ZP"); - call_chunk_done = true; - port = 23115; - break; - } - case MemOp: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MO_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MO_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MO_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MO_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MO_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MO_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MO_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MO_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MO_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MO"); - call_chunk_done = true; - port = 23117; - break; - } - case ChunkPlayerMTCollectMem: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_CM_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_CM_control_output"); - strcpy(shmem_input_name, ""); - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_CM_output"); - strcpy(sem_chunk_done_name, ""); - strcpy(sem_shutdown_done_name, ""); - strcpy(shmem_mt_name, shm_prefix); - strcat(shmem_mt_name, "_MT_output"); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_CM"); - call_chunk_done = false; - port = 23119; - break; - } - case MemReads: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_MT_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_MT_control_output"); - strcpy(shmem_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_input_name, "_input"); - else - strcat(shmem_input_name, "_MT_input"); - if (precompile_results_enabled) - { - strcpy(shmem_precompile_name, shm_prefix); - if (share_input_shm) - strcat(shmem_precompile_name, "_precompile"); - else - strcat(shmem_precompile_name, "_MT_precompile"); - strcpy(sem_prec_avail_name, shm_prefix); - strcat(sem_prec_avail_name, "_MT_prec_avail"); - strcpy(sem_prec_read_name, shm_prefix); - strcat(sem_prec_read_name, "_MT_prec_read"); - } - else - { - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - } - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_MT_output"); - strcpy(sem_chunk_done_name, shm_prefix); - strcat(sem_chunk_done_name, "_MT_chunk_done"); - strcpy(sem_shutdown_done_name, shm_prefix); - strcat(sem_shutdown_done_name, "_MT_shutdown_done"); - strcpy(shmem_mt_name, ""); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_MT"); - call_chunk_done = true; - port = 23115; - break; - } - case ChunkPlayerMemReadsCollectMain: - { - strcpy(shmem_control_input_name, shm_prefix); - if (share_input_shm) - strcat(shmem_control_input_name, "_control_input"); - else - strcat(shmem_control_input_name, "_CA_control_input"); - strcpy(shmem_control_output_name, shm_prefix); - strcat(shmem_control_output_name, "_CA_control_output"); - strcpy(shmem_input_name, ""); - strcpy(shmem_precompile_name, ""); - strcpy(sem_prec_avail_name, ""); - strcpy(sem_prec_read_name, ""); - strcpy(shmem_output_name, shm_prefix); - strcat(shmem_output_name, "_CA_output"); - strcpy(sem_chunk_done_name, ""); - strcpy(sem_shutdown_done_name, ""); - strcpy(shmem_mt_name, shm_prefix); - strcat(shmem_mt_name, "_MT_output"); - strcpy(file_lock_name, "/tmp/"); - strcat(file_lock_name, shm_prefix); - strcat(file_lock_name, ".lock"); - strcpy(log_name, shm_prefix); - strcat(log_name, "_CA"); - call_chunk_done = false; - port = 23120; - break; - } - default: - { - printf("ERROR: configure() Invalid gen_method = %u\n", gen_method); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - if (precompile_results_enabled && (gen_method == ChunkPlayerMTCollectMem || gen_method == ChunkPlayerMemReadsCollectMain)) - { - printf("ERROR: configure() precompile results enabled is not compatible with generation method %u\n", gen_method); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (arguments_port != 0) - { - port = arguments_port; - } - - if (verbose) - { - printf("ziskemuasm configuration:\n"); - printf("\tgen_method=%u\n", gen_method); - printf("\tshm_prefix=%s\n", shm_prefix); - printf("\tfile_lock_name=%s\n", file_lock_name); - printf("\tlog_name=%s\n", log_name); - printf("\tport=%u\n", port); - printf("\tcall_chunk_done=%u\n", call_chunk_done); - printf("\tchunk_size=%lu\n", chunk_size); - printf("\tshmem_control_input=%s\n", shmem_control_input_name); - printf("\tshmem_control_output=%s\n", shmem_control_output_name); - printf("\tshmem_input=%s\n", shmem_input_name); - printf("\tshmem_precompile=%s\n", shmem_precompile_name); - printf("\tshmem_output=%s\n", shmem_output_name); - printf("\tshmem_mt=%s\n", shmem_mt_name); - printf("\tsem_chunk_done=%s\n", sem_chunk_done_name); - printf("\tsem_shutdown_done=%s\n", sem_shutdown_done_name); - printf("\tsem_prec_avail=%s\n", sem_prec_avail_name); - printf("\tsem_prec_read=%s\n", sem_prec_read_name); - printf("\tmap_locked_flag=%d\n", map_locked_flag); - printf("\toutput=%u\n", output); - printf("\tprecompile_results_enabled=%u\n", precompile_results_enabled); - printf("\toutput_riscof=%u\n", output_riscof); - } -} - -/**********/ -/* CLIENT */ -/**********/ - -void client_setup (void) -{ - assert(!server); - assert(client); - - int result; - - /***********************/ - /* INPUT MINIMAL TRACE */ - /***********************/ - - // Input MT trace - if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) - { - // Create the output shared memory - shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); - if (shmem_mt_fd < 0) - { - printf("ERROR: Failed calling trace shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map it to the trace address -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); -#endif - if (pTrace == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((uint64_t)pTrace != TRACE_ADDR) - { - printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); - } - - /**********************/ - /* PRECOMPILE_RESULTS */ - /**********************/ - - if (precompile_results_enabled) - { - /**************/ - /* PRECOMPILE */ - /**************/ - - // Create the precompile results shared memory - shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR, 0666); - if (shmem_precompile_fd < 0) - { - printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map precompile address space - if (verbose) gettimeofday(&start_time, NULL); - void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pPrecompile == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(precompile) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_precompile_address = pPrecompile; - precompile_results_address = (uint64_t *)pPrecompile; - - if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); - - /*****************/ - /* CONTROL INPUT */ - /*****************/ - - // Create the control input shared memory - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR, 0666); - if (shmem_control_input_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map control input address space - if (verbose) gettimeofday(&start_time, NULL); - void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_INPUT_ADDR) - { - printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_control_input_address = (uint64_t *)pControl; - precompile_written_address = &shmem_control_input_address[0]; - precompile_exit_address = &shmem_control_input_address[1]; - if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); - - /*****************/ - /* CONTROL OUTPUT */ - /*****************/ - - // Create the control input shared memory - shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR, 0666); - if (shmem_control_output_fd < 0) - { - printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map control input address space - if (verbose) gettimeofday(&start_time, NULL); - pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pControl == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (pControl != (void *)CONTROL_OUTPUT_ADDR) - { - printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - shmem_control_output_address = (uint64_t *)pControl; - precompile_read_address = &shmem_control_output_address[0]; - if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); - - /*************************/ - /* PRECOMPILE SEMAPHORES */ - /*************************/ - - // Create the semaphore for precompile results available signal - assert(strlen(sem_prec_avail_name) > 0); - - sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT, 0666, 0); - if (sem_prec_avail == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_avail_name); - - // Create the semaphore for precompile results read signal - assert(strlen(sem_prec_read_name) > 0); - - sem_prec_read = sem_open(sem_prec_read_name, O_CREAT, 0666, 0); - if (sem_prec_read == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_prec_read_name); - } -} - -typedef enum { - PrecompileReadMode_NoPrefix, - PrecompileReadMode_Prefixed -} PrecompileReadMode; - -PrecompileReadMode precompile_read_mode = PrecompileReadMode_NoPrefix; -//PrecompileReadMode precompile_read_mode = PrecompileReadMode_Prefixed; - -typedef enum { - PrecompileWriteMode_Full, - PrecompileWriteMode_OnePrecAtATime -} PrecompileWriteMode; - -PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_Full; -//PrecompileWriteMode precompile_write_mode = PrecompileWriteMode_OnePrecAtATime; - -//#define PRECOMPILE_FIXED_SIZE 25 // Keccak-f state size in u64s -#define PRECOMPILE_FIXED_SIZE 4 // SHA-256 state size in u64s - -void client_write_precompile_results (void) -{ - int result; - -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - - // Open input file - FILE * precompile_fp = fopen(precompile_file_name, "r"); - if (precompile_fp == NULL) - { - printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Get input file size - if (fseek(precompile_fp, 0, SEEK_END) == -1) - { - printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - long precompile_data_size = ftell(precompile_fp); - if (precompile_data_size == -1) - { - printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((precompile_data_size & 0x7) != 0) - { - printf("ERROR: Precompile results file (%s) size (%lu) is not a multiple of 8 B\n", precompile_file_name, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Go back to the first byte - if (fseek(precompile_fp, 0, SEEK_SET) == -1) - { - printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", precompile_file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - assert(precompile_read_mode == PrecompileReadMode_NoPrefix || precompile_read_mode == PrecompileReadMode_Prefixed); - assert(precompile_write_mode == PrecompileWriteMode_Full || precompile_write_mode == PrecompileWriteMode_OnePrecAtATime); - - /*************/ - /* NO PREFIX */ - /*************/ - - if (precompile_read_mode == PrecompileReadMode_NoPrefix) - { - if (precompile_write_mode == PrecompileWriteMode_Full) - { - // Check the precompile data size is inside the proper range - if (precompile_data_size > MAX_PRECOMPILE_SIZE) - { - printf("ERROR: Size of precompile results file (%s) is too long (%lu)\n", precompile_file_name, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Copy input data into input memory - size_t precompile_read = fread(precompile_results_address, 1, precompile_data_size, precompile_fp); - if (precompile_read != precompile_data_size) - { - printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, precompile_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Initialize precompile written address - *precompile_written_address = precompile_data_size >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - else if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) - { - // Check the precompile data size is inside the proper range - if (precompile_data_size % (PRECOMPILE_FIXED_SIZE * 8) != 0) - { - printf("ERROR: Size of precompile results file (%s) is not a multiple %u * 8 B\n", precompile_file_name, PRECOMPILE_FIXED_SIZE); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Initialize precompile written address to zero - *precompile_written_address = 0; // in u64s - - // Copy in chunks of PRECOMPILE_FIXED_SIZE*8 bytes (Keccak-f state size) - uint64_t precompile_read_so_far = 0; - uint64_t data[PRECOMPILE_FIXED_SIZE]; - while (precompile_read_so_far < (uint64_t)precompile_data_size) - { - // Wait for server to read precompile results - //printf("Waiting for sem_prec_read()\n"); - result = sem_wait(sem_prec_read); - if (result == -1) - { - printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Number of bytes to read from file and write to shared memory in every loop - uint64_t bytes_to_read = sizeof(data); - - // Copy input data into input memory - size_t precompile_read = fread(data, 1, bytes_to_read, precompile_fp); - if (precompile_read != bytes_to_read) - { - printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Copy data to shared memory - for (int i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &data[i], 8); - precompile_read_so_far += 8; - } - - // Notify server that precompile results are available - *precompile_written_address = precompile_read_so_far >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - } - } - - /************/ - /* PREFIXED */ - /************/ - - else if (precompile_read_mode == PrecompileReadMode_Prefixed) - { -#define CTRL_START 0x00 -#define CTRL_END 0x01 -#define CTRL_CANCEL 0x02 -#define CTRL_ERROR 0x03 -#define HINTS_TYPE_RESULT 0x04 -#define HINTS_TYPE_ECRECOVER 0x05 -#define NUM_HINT_TYPES 0x06 - - uint64_t precompile_read_so_far = 0; - uint64_t precompile_written_so_far = 0; - - while (precompile_read_so_far < (uint64_t)precompile_data_size) - { - uint64_t data; - uint64_t bytes_to_read = sizeof(data); - - // Copy input data into input memory - size_t precompile_read = fread(&data, 1, bytes_to_read, precompile_fp); - if (precompile_read != bytes_to_read) - { - printf("ERROR: Input read (%lu) != expected read size (%lu)\n", precompile_read, bytes_to_read); - fflush(stdout); - fflush(stderr); - exit(-1); - } - precompile_read_so_far += bytes_to_read; - switch (data >> 32) - { - case CTRL_START: - //printf("Precompile CTRL_START\n"); - assert(precompile_read_so_far == 8); - break; - case CTRL_END: - //printf("Precompile CTRL_END\n"); - assert(precompile_read_so_far == precompile_data_size); - break; - // case CTRL_CANCEL: - // printf("Precompile CTRL_CANCEL\n"); - // break; - // case CTRL_ERROR: - // printf("Precompile CTRL_ERROR\n"); - // break; - case HINTS_TYPE_RESULT: - { - //printf("Precompile HINTS_TYPE_RESULT\n"); - if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) - { - // Wait for server to read precompile results - //printf("Waiting for sem_prec_read()\n"); - result = sem_wait(sem_prec_read); - if (result == -1) - { - printf("ERROR: Failed calling sem_wait(sem_prec_read) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - uint64_t result_length = data & 0xFFFFFFFF; - if (result_length > (precompile_data_size - precompile_read_so_far)) - { - printf("ERROR: Precompile HINTS_TYPE_RESULT length=%lu exceeds remaining file size %lu\n", result_length, precompile_data_size - precompile_read_so_far); - fflush(stdout); - fflush(stderr); - exit(-1); - } - //printf("Precompile HINTS_TYPE_RESULT result_length=%lu\n", result_length); - for (uint64_t i=0; i> 3) % (MAX_PRECOMPILE_SIZE >> 3)], &value, 8); - precompile_read_so_far += 8; - precompile_written_so_far += 8; - //printf(" Precompile result[%lu] = 0x%016lx\n", i, value); - } - - if (precompile_write_mode == PrecompileWriteMode_OnePrecAtATime) - { - // Notify server that precompile results are available - *precompile_written_address = precompile_written_so_far >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - } - break; - // case HINTS_TYPE_ECRECOVER: - // { - // // Not implemented - // printf("Precompile HINTS_TYPE_ECRECOVER not implemented\n"); - // } - // break; - default: - printf("ERROR: Unknown precompile prefix type %lu\n", data >> 32); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - if (precompile_write_mode == PrecompileWriteMode_Full) - { - // Notify server that precompile results are available - *precompile_written_address = precompile_written_so_far >> 3; // in u64s - - //printf("Posting sem_prec_avail() precompile_written=%lu precompile_read=%lu\n", *precompile_written_address, *precompile_read_address); - sem_post(sem_prec_avail); - } - - } - - // Close the file pointer - fclose(precompile_fp); - -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - printf("client (precompile): done in %lu us\n", duration); -#endif -} - -void client_run (void) -{ - printf("client_run(): Starting client...\n"); - assert(client); - assert(!server); - - int result; - - /************************/ - /* Read input file data */ - /************************/ - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - - // Open input file - FILE * input_fp = fopen(input_file, "r"); - if (input_fp == NULL) - { - printf("ERROR: Failed calling fopen(%s) errno=%d=%s; does it exist?\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Get input file size - if (fseek(input_fp, 0, SEEK_END) == -1) - { - printf("ERROR: Failed calling fseek(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - long input_data_size = ftell(input_fp); - if (input_data_size == -1) - { - printf("ERROR: Failed calling ftell(%s) errno=%d=%s\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Go back to the first byte - if (fseek(input_fp, 0, SEEK_SET) == -1) - { - printf("ERROR: Failed calling fseek(%s, 0) errno=%d=%s\n", input_file, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check the input data size is inside the proper range - if (input_data_size > (MAX_INPUT_SIZE - 16)) - { - printf("ERROR: Size of input file (%s) is too long (%lu)\n", input_file, input_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Open input shared memory - shmem_input_fd = shm_open(shmem_input_name, O_RDWR, 0666); - if (shmem_input_fd < 0) - { - printf("ERROR: Failed calling input shm_open(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map the shared memory object into the process address space - shmem_input_address = mmap(NULL, MAX_INPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shmem_input_fd, 0); - if (shmem_input_address == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Write the input size in the first 64 bits - *(uint64_t *)shmem_input_address = (uint64_t)0; // free input - *(uint64_t *)(shmem_input_address + 8)= (uint64_t)input_data_size; - - // Copy input data into input memory - size_t input_read = fread(shmem_input_address + 16, 1, input_data_size, input_fp); - if (input_read != input_data_size) - { - printf("ERROR: Input read (%lu) != input file size (%lu)\n", input_read, input_data_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Close the file pointer - fclose(input_fp); - - // Unmap input - result = munmap(shmem_input_address, MAX_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - printf("client (input): done in %lu us\n", duration); -#endif - - } - - /*****************************/ - /* Read precompile file data */ - /*****************************/ - if (precompile_results_enabled) - { - // reset written counter - *precompile_written_address = 0; - - //client_write_precompile_results(); - } - - /*************************/ - /* Connect to the server */ - /*************************/ - - // Create socket to connect to server - int socket_fd; - socket_fd = socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd < 0) - { - printf("ERROR: socket() failed socket_fd=%d errno=%d=%s\n", socket_fd, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Configure server address - struct sockaddr_in server_addr; - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(port); - - result = inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); - if (result <= 0) - { - printf("ERROR: inet_pton() failed. Invalid address/Address not supported result=%d errno=%d=%s\n", result, errno, strerror(errno)); - exit(-1); - } - - // Connect to server - result = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); - if (result < 0) - { - printf("ERROR: connect() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); - exit(-1); - } - if (verbose) printf("connect()'d to port=%u\n", port); - - // Request and response - uint64_t request[5]; - uint64_t response[5]; - - /********/ - /* Ping */ - /********/ - - gettimeofday(&start_time, NULL); - - // Prepare message to send - request[0] = TYPE_PING; - request[1] = 0; - request[2] = 0; - request[3] = 0; - request[4] = 0; - - // Send data to server - result = send(socket_fd, request, sizeof(request), 0); - if (result < 0) - { - printf("ERROR: send() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Read server response - ssize_t bytes_received = recv(socket_fd, response, sizeof(response), MSG_WAITALL); - if (bytes_received < 0) - { - printf("ERROR: recv_all_with_timeout() failed result=%d errno=%d=%s\n", result, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (bytes_received != sizeof(response)) - { - printf("ERROR: recv_all_with_timeout() returned bytes_received=%ld errno=%d=%s\n", bytes_received, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (response[0] != TYPE_PONG) - { - printf("ERROR: recv_all_with_timeout() returned unexpected type=%lu\n", response[0]); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (response[1] != gen_method) - { - printf("ERROR: recv_all_with_timeout() returned unexpected gen_method=%lu\n", response[1]); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - printf("client (PING): done in %lu us\n", duration); - - /*****************/ - /* Minimal trace */ - /*****************/ - for (uint64_t i=0; i 0); - - sem_unlink(sem_prec_avail_name); - - sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_prec_avail == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded sem_prec_avail=%p\n", sem_prec_avail_name, sem_prec_avail); - - // Create the semaphore for precompile results read signal - assert(strlen(sem_prec_read_name) > 0); - - sem_unlink(sem_prec_read_name); - - sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_prec_read == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded sem_prec_read=%p\n", sem_prec_read_name, sem_prec_read); - } - - /*******/ - /* RAM */ - /*******/ - - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - - if (verbose) gettimeofday(&start_time, NULL); - void * pRam = mmap((void *)RAM_ADDR, RAM_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | map_locked_flag, -1, 0); - if (verbose) - { - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - } - if (pRam == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(ram) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((uint64_t)pRam != RAM_ADDR) - { - printf("ERROR: Called mmap(ram) but returned address = %p != 0x%08lx\n", pRam, RAM_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("mmap(ram) mapped %lu B and returned address %p in %lu us\n", RAM_SIZE, pRam, duration); - } - - /****************/ - /* OUTPUT TRACE */ - /****************/ - - // If ROM histogram, configure trace size - if (gen_method == RomHistogram) - { - // Get max PC values for low and high addresses - uint64_t max_bios_pc = get_max_bios_pc(); - uint64_t max_program_pc = get_max_program_pc(); - assert(max_bios_pc >= 0x1000); - assert((max_bios_pc & 0x3) == 0); - assert(max_program_pc >= 0x80000000); - - // Calculate sizes - bios_size = ((max_bios_pc - 0x1000) >> 2) + 1; - program_size = max_program_pc - 0x80000000 + 1; - histogram_size = (4 + 1 + bios_size + 1 + program_size)*8; - initial_trace_size = ((histogram_size/TRACE_SIZE_GRANULARITY) + 1) * TRACE_SIZE_GRANULARITY; - trace_size = initial_trace_size; - } - - // Output trace - if ((gen_method == MinimalTrace) || - (gen_method == RomHistogram) || - (gen_method == MainTrace) || - (gen_method == Zip) || - (gen_method == MemOp) || - (gen_method == ChunkPlayerMTCollectMem) || - (gen_method == MemReads) || - (gen_method == ChunkPlayerMemReadsCollectMain)) - { - trace_map_initialize(); - } - - /***********************/ - /* INPUT MINIMAL TRACE */ - /***********************/ - - // Input MT trace - if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) - { - // Create the output shared memory - shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); - if (shmem_mt_fd < 0) - { - printf("ERROR: Failed calling mt shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Map it to the trace address -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); -#endif - if (pTrace == MAP_FAILED) - { - printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if ((uint64_t)pTrace != TRACE_ADDR) - { - printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); - } - - /******************/ - /* SEM CHUNK DONE */ - /******************/ - - if (call_chunk_done) - { - assert(strlen(sem_chunk_done_name) > 0); - - sem_unlink(sem_chunk_done_name); - - sem_chunk_done = sem_open(sem_chunk_done_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_chunk_done == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_chunk_done_name); - } - - /*********************/ - /* SEM SHUTDOWN DONE */ - /*********************/ - - assert(strlen(sem_shutdown_done_name) > 0); - - sem_unlink(sem_shutdown_done_name); - - sem_shutdown_done = sem_open(sem_shutdown_done_name, O_CREAT | O_EXCL, 0666, 0); - if (sem_shutdown_done == SEM_FAILED) - { - printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); -} - -void server_reset_fast (void) -{ - // Reset precompile read address for next emulation - if (precompile_results_enabled) - { - // Set precompile read counter to 0 for next emulation - *precompile_read_address = 0; - - // Sync control output shared memory so that the writer can see the precompile reads we have - // done, and thus update the precompile_written_address if needed - if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: server_reset_fast() msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } -} - -void server_reset_slow (void) -{ - // Reset RAM data for next emulation - if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { -#ifdef DEBUG - gettimeofday(&start_time, NULL); -#endif - memset((void *)RAM_ADDR, 0, RAM_SIZE); -#ifdef DEBUG - gettimeofday(&stop_time, NULL); - duration = TimeDiff(start_time, stop_time); - if (verbose) printf("server_reset_slow() memset(ram) in %lu us\n", duration); -#endif - } -} - -void server_reset_trace (void) -{ - // Reset trace header and trace_used_size for next emulation - if ( (gen_method != ChunkPlayerMTCollectMem) && - (gen_method != ChunkPlayerMemReadsCollectMain) && - (gen_method != Fast) && - (gen_method != RomHistogram) ) - { - // Reset trace: init output header data - pOutputTrace[0] = 0x000100; // Version, e.g. v1.0.0 [8] - pOutputTrace[1] = 1; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - pOutputTrace[2] = trace_size; // MT allocated size [8] -> to be updated after reallocation - pOutputTrace[3] = 0; // MT used size [8] -> to be updated after completion - - // Reset trace used size - trace_used_size = 0; - } -} - -void server_run (void) -{ - // If ROM histogram, reset the trace area to 0 for the histogram data since it represents the - // ROM instruction multiplicity and one of them will be increased at every executed instruction - if ((gen_method == RomHistogram)) { - memset((void *)trace_address, 0, trace_size); - } - -#ifdef ASM_CALL_METRICS - reset_asm_call_metrics(); -#endif - - // Init trace header - server_reset_trace(); - - // Sync input shared memory - if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) - { - printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (precompile_results_enabled) - { - // Sync control input shared memory - if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Sync precompile shared memory - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - } - - /*******/ - /* ASM */ - /*******/ - - // Call emulator assembly code - gettimeofday(&start_time,NULL); - if (verbose) - { - printf("Before calling emulator_start() trace_address=%lx\n", trace_address); - fflush(stdout); - fflush(stderr); - } - emulator_start(); - if (verbose) - { - printf("After calling emulator_start() trace_address=%lx\n", trace_address); - fflush(stdout); - fflush(stderr); - } - gettimeofday(&stop_time,NULL); - assembly_duration = TimeDiff(start_time, stop_time); - - // Reset precompile read address for next emulation - if (precompile_results_enabled) - { - *precompile_read_address = 0; - } - - uint64_t final_trace_size = MEM_CHUNK_ADDRESS - MEM_TRACE_ADDRESS; - trace_used_size = final_trace_size + 32; - - if ( metrics ) - { - uint64_t duration = assembly_duration; - uint64_t steps = MEM_STEP; - uint64_t end = MEM_END; - uint64_t error = MEM_ERROR; - uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; - uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; - uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; - printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", - duration, - realloc_counter, - wait_counter, - steps, - step_duration_ns, - step_tp_sec, - MEM_CHUNK_ADDRESS, - MEM_TRACE_ADDRESS, - final_trace_size, - final_trace_size_percentage, - trace_size, - end, - error, - max_steps, - chunk_size, - precompile_written_address ? *precompile_written_address : 0, - precompile_read_address ? *precompile_read_address : 0 - ); - fflush(stdout); - fflush(stderr); - if (gen_method == RomHistogram) - { - printf("Rom histogram size=%lu\n", histogram_size); - fflush(stdout); - fflush(stderr); - } - } - if (MEM_ERROR) - { - printf("Emulation ended with error code %lu\n", MEM_ERROR); - fflush(stdout); - fflush(stderr); - } - - // Log output - if (output) - { - unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; - unsigned int output_size = 64; -#ifdef DEBUG - if (verbose) - { - printf("Output size=%d\n", output_size); - fflush(stdout); - fflush(stderr); - } -#endif - - for (unsigned int i = 0; i < output_size; i++) - { - printf("%08x\n", *pOutput); - pOutput++; - } - fflush(stdout); - fflush(stderr); - } - - // Log output for riscof tests - if (output_riscof) - { - unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; - unsigned int output_size = *pOutput; -#ifdef DEBUG - if (verbose) - { - printf("Output size=%d\n", output_size); - fflush(stdout); - fflush(stderr); - } -#endif - - for (unsigned int i = 0; i < output_size; i++) - { - pOutput++; - printf("%08x\n", *pOutput); - } - fflush(stdout); - fflush(stderr); - } - - // Complete output header data - if ((gen_method == MinimalTrace) || - (gen_method == RomHistogram) || - (gen_method == Zip) || - (gen_method == MainTrace) || - (gen_method == MemOp) || - (gen_method == MemReads) || - (gen_method == ChunkPlayerMemReadsCollectMain)) - { - uint64_t * pOutput = (uint64_t *)trace_address; - pOutput[0] = 0x000100; // Version, e.g. v1.0.0 [8] - pOutput[1] = MEM_ERROR; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - pOutput[2] = trace_size; // MT allocated size [8] - //assert(final_trace_size > 32); - if (gen_method == RomHistogram) - { - pOutput[3] = MEM_STEP; - pOutput[4] = bios_size; - pOutput[4 + bios_size + 1] = program_size; - } - else - { - pOutput[3] = trace_used_size; // MT used size [8] - } - } - - // Notify client - if (gen_method == RomHistogram) - { - _chunk_done(); - } - - - // Notify the caller that the trace is ready to be consumed - // if (!is_file) - // { - // result = sem_post(sem_input); - // if (result == -1) - // { - // printf("Failed calling sem_post(%s) errno=%d=%s\n", sem_input_name, errno, strerror(errno)); - // fflush(stdout); - // fflush(stderr); - // exit(-1); - // } - // } - - -#ifdef ASM_CALL_METRICS - print_asm_call_metrics(assembly_duration); -#endif - - // Log trace - if (((gen_method == MinimalTrace) || (gen_method == Zip)) && trace) - { - log_minimal_trace(); - } - if ((gen_method == RomHistogram) && trace) - { - log_histogram(); - } - if ((gen_method == MainTrace) && trace) - { - log_main_trace(); - } - if ((gen_method == MemOp) && trace) - { - log_mem_op(); - } - if ((gen_method == MemOp) && save_to_file) - { - save_mem_op_to_files(); - } - if ((gen_method == ChunkPlayerMTCollectMem) && trace) - { - log_mem_trace(); - } - if ((gen_method == MemReads) && trace) - { - log_minimal_trace(); - } - if ((gen_method == ChunkPlayerMemReadsCollectMain) && trace) - { - log_chunk_player_main_trace(); - } -} - -void server_cleanup (void) -{ - // Cleanup ROM - int result = munmap((void *)ROM_ADDR, ROM_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(rom) errno=%d=%s\n", errno, strerror(errno)); - } - - // Cleanup RAM - result = munmap((void *)RAM_ADDR, RAM_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(ram) errno=%d=%s\n", errno, strerror(errno)); - } - - // Cleanup INPUT - result = munmap((void *)INPUT_ADDR, MAX_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_input_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); - } - - if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) - { - // Cleanup PRECOMPILE - result = munmap((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(precompile) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_precompile_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); - } - - // Cleanup CONTROL - result = munmap((void *)shmem_control_input_address, CONTROL_INPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_control_input_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); - } - result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); - if (result == -1) - { - printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); - } - result = shm_unlink(shmem_control_output_name); - if (result == -1) - { - printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); - } - - // Semaphores cleanup - result = sem_close(sem_prec_avail); - if (result == -1) - { - printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - } - result = sem_unlink(sem_prec_avail_name); - if (result == -1) - { - printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - } - result = sem_close(sem_prec_read); - if (result == -1) - { - printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - } - result = sem_unlink(sem_prec_read_name); - if (result == -1) - { - printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); - } - } - - // Cleanup trace - trace_cleanup(); - - // Cleanup chunk done semaphore - if (call_chunk_done) - { - result = sem_close(sem_chunk_done); - if (result == -1) - { - printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - } - result = sem_unlink(sem_chunk_done_name); - if (result == -1) - { - printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - } - } - - // Post shutdown donw semaphore - result = sem_post(sem_shutdown_done); - if (result == -1) - { - printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); - } -} - -/**************/ -/* PRINT REGS */ -/**************/ - -//#define PRINT_REGS -#ifdef PRINT_REGS -extern uint64_t reg_0; -extern uint64_t reg_1; -extern uint64_t reg_2; -extern uint64_t reg_3; -extern uint64_t reg_4; -extern uint64_t reg_5; -extern uint64_t reg_6; -extern uint64_t reg_7; -extern uint64_t reg_8; -extern uint64_t reg_9; -extern uint64_t reg_10; -extern uint64_t reg_11; -extern uint64_t reg_12; -extern uint64_t reg_13; -extern uint64_t reg_14; -extern uint64_t reg_15; -extern uint64_t reg_16; -extern uint64_t reg_17; -extern uint64_t reg_18; -extern uint64_t reg_19; -extern uint64_t reg_20; -extern uint64_t reg_21; -extern uint64_t reg_22; -extern uint64_t reg_23; -extern uint64_t reg_24; -extern uint64_t reg_25; -extern uint64_t reg_26; -extern uint64_t reg_27; -extern uint64_t reg_28; -extern uint64_t reg_29; -extern uint64_t reg_30; -extern uint64_t reg_31; -extern uint64_t reg_32; -extern uint64_t reg_33; -extern uint64_t reg_34; -#endif - -// Used for debugging purposes -extern int _print_regs() -{ -#ifdef PRINT_REGS - printf("print_regs()\n"); - printf("\treg[ 0]=%lu=0x%lx=@%p\n", reg_0, reg_0, ®_0); - printf("\treg[ 1]=%lu=0x%lx=@%p\n", reg_1, reg_1, ®_1); - printf("\treg[ 2]=%lu=0x%lx=@%p\n", reg_2, reg_2, ®_2); - printf("\treg[ 3]=%lu=0x%lx=@%p\n", reg_3, reg_3, ®_3); - printf("\treg[ 4]=%lu=0x%lx=@%p\n", reg_4, reg_4, ®_4); - printf("\treg[ 5]=%lu=0x%lx=@%p\n", reg_5, reg_5, ®_5); - printf("\treg[ 6]=%lu=0x%lx=@%p\n", reg_6, reg_6, ®_6); - printf("\treg[ 7]=%lu=0x%lx=@%p\n", reg_7, reg_7, ®_7); - printf("\treg[ 8]=%lu=0x%lx=@%p\n", reg_8, reg_8, ®_8); - printf("\treg[ 9]=%lu=0x%lx=@%p\n", reg_9, reg_9, ®_9); - printf("\treg[10]=%lu=0x%lx=@%p\n", reg_10, reg_10, ®_10); - printf("\treg[11]=%lu=0x%lx=@%p\n", reg_11, reg_11, ®_11); - printf("\treg[12]=%lu=0x%lx=@%p\n", reg_12, reg_12, ®_12); - printf("\treg[13]=%lu=0x%lx=@%p\n", reg_13, reg_13, ®_13); - printf("\treg[14]=%lu=0x%lx=@%p\n", reg_14, reg_14, ®_14); - printf("\treg[15]=%lu=0x%lx=@%p\n", reg_15, reg_15, ®_15); - printf("\treg[16]=%lu=0x%lx=@%p\n", reg_16, reg_16, ®_16); - printf("\treg[17]=%lu=0x%lx=@%p\n", reg_17, reg_17, ®_17); - printf("\treg[18]=%lu=0x%lx=@%p\n", reg_18, reg_18, ®_18); - printf("\treg[19]=%lu=0x%lx=@%p\n", reg_19, reg_19, ®_19); - printf("\treg[20]=%lu=0x%lx=@%p\n", reg_20, reg_20, ®_20); - printf("\treg[21]=%lu=0x%lx=@%p\n", reg_21, reg_21, ®_21); - printf("\treg[22]=%lu=0x%lx=@%p\n", reg_22, reg_22, ®_22); - printf("\treg[23]=%lu=0x%lx=@%p\n", reg_23, reg_23, ®_23); - printf("\treg[24]=%lu=0x%lx=@%p\n", reg_24, reg_24, ®_24); - printf("\treg[25]=%lu=0x%lx=@%p\n", reg_25, reg_25, ®_25); - printf("\treg[26]=%lu=0x%lx=@%p\n", reg_26, reg_26, ®_26); - printf("\treg[27]=%lu=0x%lx=@%p\n", reg_27, reg_27, ®_27); - printf("\treg[28]=%lu=0x%lx=@%p\n", reg_28, reg_28, ®_28); - printf("\treg[29]=%lu=0x%lx=@%p\n", reg_29, reg_29, ®_29); - printf("\treg[30]=%lu=0x%lx=@%p\n", reg_30, reg_30, ®_30); - printf("\treg[31]=%lu=0x%lx=@%p\n", reg_31, reg_31, ®_31); - printf("\treg[32]=%lu=0x%lx=@%p\n", reg_32, reg_32, ®_32); - printf("\treg[33]=%lu=0x%lx=@%p\n", reg_33, reg_33, ®_33); - printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); - printf("\n"); -#endif -} - -/************/ -/* PRINT PC */ -/************/ - -//#define PRINT_PC_DURATION -#ifdef PRINT_PC_DURATION -struct timeval print_pc_tv; -#endif - -// Used for debugging purposes -extern int _print_pc (uint64_t pc, uint64_t c) -{ -#ifdef PRINT_PC_DURATION - print_pc_counter++; - { - struct timeval tv; - gettimeofday(&tv, NULL); - uint64_t duration = TimeDiff(print_pc_tv, tv); - if (duration > 900) - { - uint64_t chunk = print_pc_counter / chunk_size; - printf("print_pc() pc=%lx counter=%lu sec=%lu usec=%lu duration=%lu chunk=%lu\n", pc, print_pc_counter, tv.tv_sec, tv.tv_usec, duration, chunk); - fflush(stdout); - } - print_pc_tv = tv; - } -#endif - - printf("s=%lu pc=%lx c=%lx", print_pc_counter, pc, c); - -//#define PRINT_PC_REGS -#ifdef PRINT_PC_REGS - /* Used for debugging */ - printf(" r0=%lx", reg_0); - printf(" r1=%lx", reg_1); - printf(" r2=%lx", reg_2); - printf(" r3=%lx", reg_3); - printf(" r4=%lx", reg_4); - printf(" r5=%lx", reg_5); - printf(" r6=%lx", reg_6); - printf(" r7=%lx", reg_7); - printf(" r8=%lx", reg_8); - printf(" r9=%lx", reg_9); - printf(" r10=%lx", reg_10); - printf(" r11=%lx", reg_11); - printf(" r12=%lx", reg_12); - printf(" r13=%lx", reg_13); - printf(" r14=%lx", reg_14); - printf(" r15=%lx", reg_15); - printf(" r16=%lx", reg_16); - printf(" r17=%lx", reg_17); - printf(" r18=%lx", reg_18); - printf(" r19=%lx", reg_19); - printf(" r20=%lx", reg_20); - printf(" r21=%lx", reg_21); - printf(" r22=%lx", reg_22); - printf(" r23=%lx", reg_23); - printf(" r24=%lx", reg_24); - printf(" r25=%lx", reg_25); - printf(" r26=%lx", reg_26); - printf(" r27=%lx", reg_27); - printf(" r28=%lx", reg_28); - printf(" r29=%lx", reg_29); - printf(" r30=%lx", reg_30); - printf(" r31=%lx", reg_31); -#endif - - printf("\n"); - fflush(stdout); - print_pc_counter++; -} - -/**************/ -/* CHUNK DONE */ -/**************/ - -//#define CHUNK_DONE_DURATION -#ifdef CHUNK_DONE_DURATION -uint64_t chunk_done_counter = 0; -struct timeval chunk_done_tv; -#endif - -//#define CHUNK_DONE_SYNC_DURATION -#ifdef CHUNK_DONE_SYNC_DURATION -struct timeval sync_start, sync_stop; -uint64_t sync_duration = 0; -#endif - -// Called by the assembly to notify that a chunk is done and its trace is ready to be consumed -extern void _chunk_done() -{ -#ifdef CHUNK_DONE_DURATION - chunk_done_counter++; - if ((chunk_done_counter & 0xFF) == 0) - { - struct timeval tv; - gettimeofday(&tv, NULL); - uint64_t duration = TimeDiff(chunk_done_tv, tv); - if (duration > 5000) - { - printf("chunk_done() counter=%lu sec=%lu usec=%lu duration=%lu\n", chunk_done_counter, tv.tv_sec, tv.tv_usec, duration); - fflush(stdout); - } - chunk_done_tv = tv; - } -#endif - -#ifdef CHUNK_DONE_SYNC_DURATION - gettimeofday(&sync_start, NULL); -#endif - - __sync_synchronize(); - -#ifdef CHUNK_DONE_SYNC_DURATION - gettimeofday(&sync_stop, NULL); - sync_duration += TimeDiff(sync_start, sync_stop); - printf("chunk_done() sync_duration=%lu\n", sync_duration); -#endif - - // Notify the caller that a new chunk is done and its trace is ready to be consumed - assert(call_chunk_done); - int result = sem_post(sem_chunk_done); - if (result == -1) - { - printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -/*****************/ -/* REALLOC TRACE */ -/*****************/ - -// Called by the assembly to reallocate the trace when needed, e.g. for the next chunk, -// to increase the trace size by another chunk size -extern void _realloc_trace (void) -{ - // Increase realloc counter - realloc_counter++; - - // Map next chunk of the trace shared memory - trace_map_next_chunk(); - - // Update trace global variables - set_trace_size(trace_total_mapped_size); - -#ifdef DEBUG - if (verbose) printf("realloc_trace() realloc counter=%lu trace_address=0x%lx trace_size=%lu=%lx max_address=0x%lx trace_address_threshold=0x%lx chunk_size=%lu\n", realloc_counter, trace_address, trace_size, trace_size, trace_address + trace_size, trace_address_threshold, chunk_size); -#endif -} - -/*****************/ -/* LOG FUNCTIONS */ -/*****************/ - -/* Trace data structure - [8B] Number of chunks: C - - Chunk 0: - Start state: - [8B] pc - [8B] sp - [8B] c - [8B] step - [8B] register[1] - … - [8B] register[31] - [8B] register[32] - [8B] register[33] - Last state: - [8B] c - End: - [8B] end - Steps: - [8B] steps = chunk size except for the last chunk - [8B] mem_reads_size - [8B] mem_reads[0] - [8B] mem_reads[1] - … - [8B] mem_reads[mem_reads_size - 1] - - Chunk 1: - … - Chunk C-1: - … -*/ -void log_minimal_trace(void) -{ - uint64_t * pOutput = (uint64_t *)TRACE_ADDR; - printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] - printf("Minimal trace used size = %lu B\n", pOutput[3]); // Minimal trace used size [8] - - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; - uint64_t number_of_chunks = trace[0]; - printf("Number of chunks=%lu\n", number_of_chunks); - if (number_of_chunks > 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (trace_trace) - { - for (uint64_t m=0; m 100000000) - { - printf("ERROR: Bios size is too high=%lu\n", bios_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (trace_trace) - { - uint64_t * bios = trace + 1; - for (uint64_t i=0; i 100000000) - { - printf("ERROR: Program size is too high=%lu\n", program_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (trace_trace) - { - uint64_t * program = trace + 1 + bios_size + 1; - for (uint64_t i=0; i 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Main_trace size is too high=%lu\n", main_trace_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (trace_trace) - { - for (uint64_t m=0; m 0) - { - size_t bytes_written = fwrite(buffer_address, 1, buffer_length, file); - if (bytes_written != buffer_length) - { - printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%lu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - fclose(file); - exit(-1); - } - } - - if (fclose(file) != 0) - { - printf("ERROR: buffer2file() failed calling fclose(%s) errno=%d=%s\n", file_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } -} - -/* Memory operations structure - [8B] Number of chunks = C - - Chunk 0: - [8b] end - [8B] mem_op_trace_size - [8B] mem_op_trace[0] - [8B] mem_op_trace[1] - … - [8B] mem_op_trace[mem_op_trace_size - 1] - - Chunk 1: - … - Chunk C-1: - … -*/ -void log_mem_op(void) -{ - // Log header - uint64_t * pOutput = (uint64_t *)TRACE_ADDR; - printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] - - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; - uint64_t number_of_chunks = trace[0]; - printf("Number of chunks=%lu\n", number_of_chunks); - if (number_of_chunks > 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - for (uint64_t m=0; m> 49) & 0x1; - uint64_t write = (chunk[i] >> 48) & 0x1; - uint64_t width = (chunk[i] >> 32) & 0xF; - uint64_t address = chunk[i] & 0xFFFFFFFF; - bool inside_range = - ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || - ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || - ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); - if (trace_trace || !inside_range) - { - printf("\t\tchunk[%lu].mem_op_trace[%lu] = %016lx = rest_are_zeros=%lx, write=%lx, width=%lx, address=%lx%s\n", - c, - m, - chunk[i], - rest_are_zeros, - write, - width, - address, - inside_range ? "" : " ERROR!!!!!!!!!!!!!!" - ); - } - i += 1; - } - - //Set next chunk pointer - chunk = chunk + i; - } - printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); -} - -/* Memory trace structure (for 1 chunk) - [8B] mem_trace_size - [16B] mem_trace[0] - [8B] mem operacion - [4B] address (LE) - [1B] width (1, 2, 4, 8) + write (0, 1) << 4 - [3B] - [16B] mem_trace[1] - … - [16B] mem_trace[mem_trace_size - 1] -*/ -void log_mem_trace(void) -{ - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)trace_address; - printf("log_mem_trace() trace_address=%p\n", trace); - uint64_t i=0; - printf("Version = 0x%06lx\n", trace[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", trace[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", trace[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", trace[3]); // Main trace used size [8] - i += 4; - uint64_t number_of_entries = trace[i]; - i++; - printf("Trace size=%lu\n", number_of_entries); - - for (uint64_t m = 0; m < number_of_entries; m++) - { - uint64_t addr_step = trace[i]; - i++; - - // addr_step = [@0, @1, @2, @3, width + write<<4, supra_step] - uint64_t address = addr_step & 0xFFFFFFFF; - uint64_t width = (addr_step >> (4*8)) & 0xF; - uint64_t write = (addr_step >> ((4*8) + 4)) & 0x1; - uint64_t micro_step = (addr_step >> (5*8)) & 0x3; - uint64_t incremental_step = (addr_step >> ((5*8) + 2)); - bool address_is_inside_range = - ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || - ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || - ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); - bool width_is_valid = (width == 1) || (width == 2) || (width == 4) || (width == 8); - bool bError = !(address_is_inside_range && width_is_valid); - if (trace_trace || bError) - { - printf("\tmem_trace[%lu] = %016lx = [inc_step=%lu, u_step=%lu, write=%lx, width=%lx, address=%lx] %s\n", - m, - addr_step, - incremental_step, - micro_step, - write, - width, - address, - bError ? " ERROR!!!!!!!!!!!!!!" : "" - ); - } - - // u-step: - // 0: a=SRC_MEM - // 1: b=SRC_MEM or b=SRC_IND - // 2: precompiled_read - // 3: c=STORE_MEM, c=STORE_IND or precompiled_write - - bool address_is_aligned = (address & 0x7) == 0; - uint64_t aligned_address = address & 0xFFFFFFF8; - uint64_t number_of_read_values = 0; - uint64_t number_of_write_values = 0; - - switch (micro_step) - { - case 0: // a=SRC_MEM - { - assert_perror(width == 8); - if (address_is_aligned) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - break; - } - case 1: // b=SRC_MEM or b=SRC_IND - { - if (address_is_aligned) - { - number_of_read_values = 1; - } - else - { - if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - } - break; - } - case 2: // precompiled_read - { - assert_perror(width == 8); - if (address_is_aligned) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - break; - } - case 3: // c=STORE_MEM, c=STORE_IND or precompiled_write - { - if (address_is_aligned && (width == 8)) - { - number_of_read_values = 0; - } - else - { - if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) - { - number_of_read_values = 1; - } - else - { - number_of_read_values = 2; - } - } - number_of_write_values = 1; - break; - } - } - - for (uint64_t r = 0; r < number_of_read_values; r++) - { - uint64_t value = trace[i]; - i++; - m++; - if (trace_trace) - { - printf("\t\tread_value[%lu] = 0x%lx\n", i, value); - } - } - - for (uint64_t w = 0; w < number_of_write_values; w++) - { - uint64_t value = trace[i]; - i++; - m++; - if (trace_trace) - { - printf("\t\twrite_value[%lu] = 0x%lx\n", i, value); - } - } - } - printf("Trace=%p number_of_entries=%lu\n", trace, number_of_entries); -} - -void save_mem_op_to_files(void) -{ - // Log header - uint64_t * pOutput = (uint64_t *)TRACE_ADDR; - printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] - - printf("Trace content:\n"); - uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; - uint64_t number_of_chunks = trace[0]; - printf("Number of chunks=%lu\n", number_of_chunks); - if (number_of_chunks > 1000000) - { - printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); - fflush(stdout); - fflush(stderr); - exit(-1); - } - uint64_t * chunk = trace + 1; - for (uint64_t c=0; c 10000000) - { - printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - printf("Chunk %lu: file=%s length=%lu\n", c, file_name, mem_op_trace_size); - - buffer2file(&chunk[i], mem_op_trace_size * 8, file_name); - - //Set next chunk pointer - chunk = chunk + mem_op_trace_size + 1; - } - printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); -} - -/* Trace data structure - [8B] Number of elements - - A series of elements with the following structure: - [8B] op: instruction opcode - [8B] a: register a value - [8B] b: register b value - [8B] precompiled_memory_address: memory read address of the precompiled input data -*/ -void log_chunk_player_main_trace(void) -{ - uint64_t * chunk = (uint64_t *)trace_address; - uint64_t i = 0; - - printf("Version = 0x%06lx\n", chunk[0]); // Version, e.g. v1.0.0 [8] - printf("Exit code = %lu\n", chunk[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] - printf("Allocated size = %lu B\n", chunk[2]); // Allocated size [8] - printf("Memory operations trace used size = %lu B\n", chunk[3]); // Main trace used size [8] - i = 4; - - uint64_t mem_reads_size = chunk[i]; - i++; - printf("mem_reads_size=%lu\n", mem_reads_size); - if (mem_reads_size > 10000000) - { - printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); - fflush(stdout); - fflush(stderr); - exit(-1); - } - //if (trace_trace) - { - for (uint64_t m=0; m 0xFF) - { - printf("ERROR!! Invalid op=%lu=0x%lx\n", op, op); - } - if (trace_trace) printf("\tmem_reads[%lu] a=0x%08lx\n", m, chunk[i]); - i++; - m++; - if (trace_trace) printf("\tmem_reads[%lu] b=0x%08lx\n", m, chunk[i]); - i++; - m++; - if ( (op == 0xf1) // Keccak - || (op == 0xf9) // SHA256 - || (op == 0xf2) // Arith256 - || (op == 0xf3) // Arith256Mod - || (op == 0xf4) // Secp256k1Add - || (op == 0xf5) // Secp256k1Dbl - ) - { - if (trace_trace) printf("\tmem_reads[%lu] precompiled_address=%08lx\n", m, chunk[i]); - i++; - m++; - } - } - } - - printf("Chunk=%p size=%lu\n", chunk, mem_reads_size); -} - -/*************/ -/* FILE LOCK */ -/*************/ - -// Lock file exclusively to ensure that only one instance of the program is running at a time -void file_lock(void) -{ - // Open (or create) the lock file. We don't need to write to it. - file_lock_fd = open(file_lock_name, O_CREAT | O_RDONLY, 0644); - if (file_lock_fd == -1) { - printf("ERROR: file_lock() failed calling open(%s) errno=%d=%s\n", file_lock_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(1); - } - - // Try to acquire an exclusive lock, non-blocking. - if (flock(file_lock_fd, LOCK_EX | LOCK_NB) == -1) { - // If we fail to get the lock, another instance is running. - printf("ERROR: Another instance of this program is already running.\n"); - fflush(stdout); - fflush(stderr); - exit(1); - } -} - -/*********************************/ -/* WAIT FOR PRECOMPILE AVAILABLE */ -/*********************************/ - -// Called by the assembly when prec_written == prec_read, to wait for new precompile results to be available -int _wait_for_prec_avail (void) -{ - // Increment wait counter - wait_counter++; - - // Sync control output shared memory so that the writer can see the precompile reads we have - // done, and thus update the precompile_written_address if needed - if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Tell the writer that we have read some precompile results - sem_post(sem_prec_read); - - // Make sure the precompile available semaphore is reset before checking the condition, - // since the caller may have posted it (even several times) before we called sem_wait() - while (sem_trywait(sem_prec_avail) == 0) {/*printf("Purging sem_prec_avail\n");*/}; - - // Sync control input shared memory so that we can see the latest precompile_written_address value - if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Check if there are already precompile results available - if (*precompile_written_address > *precompile_read_address) - { - // Sync precompile shared memory - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - return 0; - } - - // Wait again, but blocking this time - while (true) - { - struct timespec ts; - int result = clock_gettime(CLOCK_REALTIME, &ts); - if (result == -1) - { - printf("ERROR: wait_for_prec_avail() failed calling clock_gettime() errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - ts.tv_sec += 5; // 5 seconds timeout - - //printf("_wait_for_prec_avail() calling sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); - result = sem_timedwait(sem_prec_avail, &ts); - //printf("_wait_for_prec_avail() called sem_wait precompile_written_address=%lu precompile_read_address=%lu\n", *precompile_written_address, *precompile_read_address); - if ((result == -1) && (errno != ETIMEDOUT)) - { - printf("ERROR: wait_for_prec_avail() failed calling sem_wait(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - // Sync control input shared memory so that we can see the latest precompile_written_address value - if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - if (*precompile_exit_address != 0) - { - printf("ERROR: wait_for_prec_avail() found precompile_exit_address=%lu\n", *precompile_exit_address); - fflush(stdout); - fflush(stderr); - exit(-1); - } - if (*precompile_written_address > *precompile_read_address) - { - // Sync precompile shared memory - if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { - printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); - fflush(stdout); - fflush(stderr); - exit(-1); - } - - return 0; - } - } - - printf("ERROR: wait_for_prec_avail() unreachable code\n"); - fflush(stdout); - fflush(stderr); - exit(-1); -} \ No newline at end of file +#endif // USE_FILE_LOCK \ No newline at end of file diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c new file mode 100644 index 000000000..60fdb49f4 --- /dev/null +++ b/emulator-asm/src/server.c @@ -0,0 +1,958 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "server.hpp" +#include "globals.hpp" +#include "asm_provided.hpp" +#include "trace_logs.hpp" +#include "trace.hpp" +#include "emu.hpp" +#include "c_provided.hpp" + +/**********/ +/* SERVER */ +/**********/ + +// ROM histogram +uint64_t histogram_size = 0; +uint64_t bios_size = 0; +uint64_t program_size = 0; + +void server_setup (void) +{ + assert(server); + assert(!client); + + int result; + + /*******/ + /* ROM */ + /*******/ + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + + if (verbose) gettimeofday(&start_time, NULL); + void * pRom = mmap((void *)ROM_ADDR, ROM_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | map_locked_flag, -1, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pRom == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(rom) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pRom != ROM_ADDR) + { + printf("ERROR: Called mmap(rom) but returned address = %p != 0x%lx\n", pRom, ROM_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(rom) mapped %ld B and returned address %p in %lu us\n", ROM_SIZE, pRom, duration); + } + + /*********/ + /* INPUT */ + /*********/ + + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + if (!open_input_shm) + { + // Make sure the input shared memory is deleted + shm_unlink(shmem_input_name); + + // Create the input shared memory + shmem_input_fd = shm_open(shmem_input_name, O_RDWR | O_CREAT | O_EXCL, 0666); + if (shmem_input_fd < 0) + { + printf("ERROR: Failed calling input RW shm_open(%s) as read-write errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_input_fd, MAX_INPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(shmem_input_fd); + + // Close the descriptor + if (close(shmem_input_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + // Open the input shared memory as read-only + shmem_input_fd = shm_open(shmem_input_name, O_RDONLY | O_EXCL, 0666); + if (shmem_input_fd < 0) + { + printf("ERROR: Failed calling input RO shm_open(%s) as read-only errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map input address space + if (verbose) gettimeofday(&start_time, NULL); + void * pInput = mmap((void *)INPUT_ADDR, MAX_INPUT_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pInput == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pInput != INPUT_ADDR) + { + printf("ERROR: Called mmap(pInput) but returned address = %p != 0x%lx\n", pInput, INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(input) mapped %lu B and returned address %p in %lu us\n", MAX_INPUT_SIZE, pInput, duration); + } + + /**********************/ + /* PRECOMPILE_RESULTS */ + /**********************/ + + if (precompile_results_enabled) + { + /**************/ + /* PRECOMPILE */ + /**************/ + + if (!open_input_shm) + { + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_precompile_name); + + // Create the precompile results shared memory + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDWR | O_CREAT, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile shm_open(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_precompile_fd, MAX_PRECOMPILE_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(shmem_precompile_fd); + + // Close the descriptor + if (close(shmem_precompile_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + // Open the precompile shared memory as read-only + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDONLY | O_EXCL, 0666); + if (shmem_precompile_fd < 0) + { + printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pPrecompile = mmap(NULL, MAX_PRECOMPILE_SIZE, PROT_READ, MAP_SHARED | map_locked_flag, shmem_precompile_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pPrecompile == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_precompile_address = pPrecompile; + precompile_results_address = (uint64_t *)pPrecompile; + if (verbose) printf("mmap(precompile) mapped %lu B and returned address %p in %lu us\n", MAX_PRECOMPILE_SIZE, precompile_results_address, duration); + + /*****************/ + /* CONTROL INPUT */ + /*****************/ + + if (!open_input_shm) + { + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_input_name); + + // Create the control shared memory + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_control_input_fd, CONTROL_INPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(shmem_control_input_fd); + + // Close the descriptor + if (close(shmem_control_input_fd) != 0) + { + printf("ERROR: Failed calling close(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + // Open the control input shared memory as read-only + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY | O_EXCL, 0666); + if (shmem_control_input_fd < 0) + { + printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + void * pControl = mmap((void *)CONTROL_INPUT_ADDR, CONTROL_INPUT_SIZE, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_input_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_INPUT_ADDR) + { + printf("ERROR: Called mmap(control_input) but returned address = %p != 0x%08lx\n", pControl, CONTROL_INPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_input_address = (uint64_t *)pControl; + precompile_written_address = &shmem_control_input_address[0]; + precompile_exit_address = &shmem_control_input_address[1]; + if (verbose) printf("mmap(control_input) mapped %lu B and returned address %p in %lu us\n", CONTROL_INPUT_SIZE, shmem_control_input_address, duration); + + /******************/ + /* CONTROL OUTPUT */ + /******************/ + + // Make sure the precompile results shared memory is deleted + shm_unlink(shmem_control_output_name); + + // Create the control shared memory + shmem_control_output_fd = shm_open(shmem_control_output_name, O_RDWR | O_CREAT, 0666); + if (shmem_control_output_fd < 0) + { + printf("ERROR: Failed calling control shm_open(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + result = ftruncate(shmem_control_output_fd, CONTROL_OUTPUT_SIZE); + if (result != 0) + { + printf("ERROR: Failed calling ftruncate(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map precompile address space + if (verbose) gettimeofday(&start_time, NULL); + pControl = mmap((void *)CONTROL_OUTPUT_ADDR, CONTROL_OUTPUT_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_control_output_fd, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pControl == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (pControl != (void *)CONTROL_OUTPUT_ADDR) + { + printf("ERROR: Called mmap(control_output) but returned address = %p != 0x%08lx\n", pControl, CONTROL_OUTPUT_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + shmem_control_output_address = (uint64_t *)pControl; + precompile_read_address = &shmem_control_output_address[0]; + if (verbose) printf("mmap(control_output) mapped %lu B and returned address %p in %lu us\n", CONTROL_OUTPUT_SIZE, shmem_control_output_address, duration); + + /*************************/ + /* PRECOMPILE SEMAPHORES */ + /*************************/ + + // Create the semaphore for precompile results available signal + assert(strlen(sem_prec_avail_name) > 0); + + sem_unlink(sem_prec_avail_name); + + sem_prec_avail = sem_open(sem_prec_avail_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_avail == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded sem_prec_avail=%p\n", sem_prec_avail_name, sem_prec_avail); + + // Create the semaphore for precompile results read signal + assert(strlen(sem_prec_read_name) > 0); + + sem_unlink(sem_prec_read_name); + + sem_prec_read = sem_open(sem_prec_read_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_prec_read == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded sem_prec_read=%p\n", sem_prec_read_name, sem_prec_read); + } + + /*******/ + /* RAM */ + /*******/ + + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + + if (verbose) gettimeofday(&start_time, NULL); + void * pRam = mmap((void *)RAM_ADDR, RAM_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | map_locked_flag, -1, 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pRam == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(ram) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pRam != RAM_ADDR) + { + printf("ERROR: Called mmap(ram) but returned address = %p != 0x%08lx\n", pRam, RAM_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(ram) mapped %lu B and returned address %p in %lu us\n", RAM_SIZE, pRam, duration); + } + + /****************/ + /* OUTPUT TRACE */ + /****************/ + + // If ROM histogram, configure trace size + if (gen_method == RomHistogram) + { + // Get max PC values for low and high addresses + uint64_t max_bios_pc = get_max_bios_pc(); + uint64_t max_program_pc = get_max_program_pc(); + assert(max_bios_pc >= 0x1000); + assert((max_bios_pc & 0x3) == 0); + assert(max_program_pc >= 0x80000000); + + // Calculate sizes + bios_size = ((max_bios_pc - 0x1000) >> 2) + 1; + program_size = max_program_pc - 0x80000000 + 1; + histogram_size = (4 + 1 + bios_size + 1 + program_size)*8; + initial_trace_size = ((histogram_size/TRACE_SIZE_GRANULARITY) + 1) * TRACE_SIZE_GRANULARITY; + trace_size = initial_trace_size; + } + + // Output trace + if ((gen_method == MinimalTrace) || + (gen_method == RomHistogram) || + (gen_method == MainTrace) || + (gen_method == Zip) || + (gen_method == MemOp) || + (gen_method == ChunkPlayerMTCollectMem) || + (gen_method == MemReads) || + (gen_method == ChunkPlayerMemReadsCollectMain)) + { + trace_map_initialize(); + } + + /***********************/ + /* INPUT MINIMAL TRACE */ + /***********************/ + + // Input MT trace + if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) + { + // Create the output shared memory + shmem_mt_fd = shm_open(shmem_mt_name, O_RDONLY, 0666); + if (shmem_mt_fd < 0) + { + printf("ERROR: Failed calling mt shm_open(%s) errno=%d=%s\n", shmem_mt_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Map it to the trace address +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + void * pTrace = mmap((void *)TRACE_ADDR, chunk_player_mt_size, PROT_READ, MAP_SHARED | MAP_FIXED | map_locked_flag, shmem_mt_fd, 0); +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); +#endif + if (pTrace == MAP_FAILED) + { + printf("ERROR: Failed calling mmap(MT) errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((uint64_t)pTrace != TRACE_ADDR) + { + printf("ERROR: Called mmap(MT) but returned address = %p != 0x%lx\n", pTrace, TRACE_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("mmap(MT) returned %p in %lu us\n", pTrace, duration); + } + + /******************/ + /* SEM CHUNK DONE */ + /******************/ + + if (call_chunk_done) + { + assert(strlen(sem_chunk_done_name) > 0); + + sem_unlink(sem_chunk_done_name); + + sem_chunk_done = sem_open(sem_chunk_done_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_chunk_done == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_chunk_done_name); + } + + /*********************/ + /* SEM SHUTDOWN DONE */ + /*********************/ + + assert(strlen(sem_shutdown_done_name) > 0); + + sem_unlink(sem_shutdown_done_name); + + sem_shutdown_done = sem_open(sem_shutdown_done_name, O_CREAT | O_EXCL, 0666, 0); + if (sem_shutdown_done == SEM_FAILED) + { + printf("ERROR: Failed calling sem_open(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("sem_open(%s) succeeded\n", sem_shutdown_done_name); +} + +void server_reset_fast (void) +{ + // Reset precompile read address for next emulation + if (precompile_results_enabled) + { + // Set precompile read counter to 0 for next emulation + *precompile_read_address = 0; + + // Sync control output shared memory so that the writer can see the precompile reads we have + // done, and thus update the precompile_written_address if needed + if (msync((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: server_reset_fast() msync failed for shmem_control_output_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } +} + +void server_reset_slow (void) +{ + // Reset RAM data for next emulation + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { +#ifdef DEBUG + gettimeofday(&start_time, NULL); +#endif + memset((void *)RAM_ADDR, 0, RAM_SIZE); +#ifdef DEBUG + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + if (verbose) printf("server_reset_slow() memset(ram) in %lu us\n", duration); +#endif + } +} + +void server_reset_trace (void) +{ + // Reset trace header and trace_used_size for next emulation + if ( (gen_method != ChunkPlayerMTCollectMem) && + (gen_method != ChunkPlayerMemReadsCollectMain) && + (gen_method != Fast) && + (gen_method != RomHistogram) ) + { + // Reset trace: init output header data + pOutputTrace[0] = 0x000100; // Version, e.g. v1.0.0 [8] + pOutputTrace[1] = 1; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + pOutputTrace[2] = trace_size; // MT allocated size [8] -> to be updated after reallocation + pOutputTrace[3] = 0; // MT used size [8] -> to be updated after completion + + // Reset trace used size + trace_used_size = 0; + } +} + +void server_run (void) +{ + // If ROM histogram, reset the trace area to 0 for the histogram data since it represents the + // ROM instruction multiplicity and one of them will be increased at every executed instruction + if ((gen_method == RomHistogram)) { + memset((void *)trace_address, 0, trace_size); + } + +#ifdef ASM_CALL_METRICS + reset_asm_call_metrics(); +#endif + + // Init trace header + server_reset_trace(); + + // Sync input shared memory + if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) + { + printf("ERROR: msync failed for shmem_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (precompile_results_enabled) + { + // Sync control input shared memory + if (msync((void *)shmem_control_input_address, CONTROL_INPUT_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_control_input_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync precompile shared memory + if (msync((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE, MS_SYNC) != 0) { + printf("ERROR: msync failed for shmem_precompile_address errno=%d=%s\n", errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + } + + /*******/ + /* ASM */ + /*******/ + + // Call emulator assembly code + gettimeofday(&start_time,NULL); + if (verbose) + { + printf("Before calling emulator_start() trace_address=%lx\n", trace_address); + fflush(stdout); + fflush(stderr); + } + emulator_start(); + if (verbose) + { + printf("After calling emulator_start() trace_address=%lx\n", trace_address); + fflush(stdout); + fflush(stderr); + } + gettimeofday(&stop_time,NULL); + assembly_duration = TimeDiff(start_time, stop_time); + + // Reset precompile read address for next emulation + if (precompile_results_enabled) + { + *precompile_read_address = 0; + } + + uint64_t final_trace_size = MEM_CHUNK_ADDRESS - MEM_TRACE_ADDRESS; + trace_used_size = final_trace_size + 32; + + if ( metrics ) + { + uint64_t duration = assembly_duration; + uint64_t steps = MEM_STEP; + uint64_t end = MEM_END; + uint64_t error = MEM_ERROR; + uint64_t step_duration_ns = steps == 0 ? 0 : (duration * 1000) / steps; + uint64_t step_tp_sec = duration == 0 ? 0 : steps * 1000000 / duration; + uint64_t final_trace_size_percentage = (final_trace_size * 100) / trace_size; + printf("Duration = %lu us, realloc counter = %lu, wait counter = %lu, steps = %lu, step duration = %lu ns, tp = %lu steps/s, trace size = 0x%lx - 0x%lx = %lu B(%lu%% of %lu), end=%lu, error=%lu, max steps=%lu, chunk size=%lu, prec_written=%lu, prec_read=%lu\n", + duration, + realloc_counter, + wait_counter, + steps, + step_duration_ns, + step_tp_sec, + MEM_CHUNK_ADDRESS, + MEM_TRACE_ADDRESS, + final_trace_size, + final_trace_size_percentage, + trace_size, + end, + error, + max_steps, + chunk_size, + precompile_written_address ? *precompile_written_address : 0, + precompile_read_address ? *precompile_read_address : 0 + ); + fflush(stdout); + fflush(stderr); + if (gen_method == RomHistogram) + { + printf("Rom histogram size=%lu\n", histogram_size); + fflush(stdout); + fflush(stderr); + } + } + if (MEM_ERROR) + { + printf("Emulation ended with error code %lu\n", MEM_ERROR); + fflush(stdout); + fflush(stderr); + } + + // Log output + if (output) + { + unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; + unsigned int output_size = 64; +#ifdef DEBUG + if (verbose) + { + printf("Output size=%d\n", output_size); + fflush(stdout); + fflush(stderr); + } +#endif + + for (unsigned int i = 0; i < output_size; i++) + { + printf("%08x\n", *pOutput); + pOutput++; + } + fflush(stdout); + fflush(stderr); + } + + // Log output for riscof tests + if (output_riscof) + { + unsigned int * pOutput = (unsigned int *)OUTPUT_ADDR; + unsigned int output_size = *pOutput; +#ifdef DEBUG + if (verbose) + { + printf("Output size=%d\n", output_size); + fflush(stdout); + fflush(stderr); + } +#endif + + for (unsigned int i = 0; i < output_size; i++) + { + pOutput++; + printf("%08x\n", *pOutput); + } + fflush(stdout); + fflush(stderr); + } + + // Complete output header data + if ((gen_method == MinimalTrace) || + (gen_method == RomHistogram) || + (gen_method == Zip) || + (gen_method == MainTrace) || + (gen_method == MemOp) || + (gen_method == MemReads) || + (gen_method == ChunkPlayerMemReadsCollectMain)) + { + uint64_t * pOutput = (uint64_t *)trace_address; + pOutput[0] = 0x000100; // Version, e.g. v1.0.0 [8] + pOutput[1] = MEM_ERROR; // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + pOutput[2] = trace_size; // MT allocated size [8] + //assert(final_trace_size > 32); + if (gen_method == RomHistogram) + { + pOutput[3] = MEM_STEP; + pOutput[4] = bios_size; + pOutput[4 + bios_size + 1] = program_size; + } + else + { + pOutput[3] = trace_used_size; // MT used size [8] + } + } + + // Notify client + if (gen_method == RomHistogram) + { + _chunk_done(); + } + + + // Notify the caller that the trace is ready to be consumed + // if (!is_file) + // { + // result = sem_post(sem_input); + // if (result == -1) + // { + // printf("Failed calling sem_post(%s) errno=%d=%s\n", sem_input_name, errno, strerror(errno)); + // fflush(stdout); + // fflush(stderr); + // exit(-1); + // } + // } + + +#ifdef ASM_CALL_METRICS + print_asm_call_metrics(assembly_duration); +#endif + + // Log trace + if (((gen_method == MinimalTrace) || (gen_method == Zip)) && trace) + { + log_minimal_trace(); + } + if ((gen_method == RomHistogram) && trace) + { + log_histogram(); + } + if ((gen_method == MainTrace) && trace) + { + log_main_trace(); + } + if ((gen_method == MemOp) && trace) + { + log_mem_op(); + } + if ((gen_method == MemOp) && save_to_file) + { + save_mem_op_to_files(); + } + if ((gen_method == ChunkPlayerMTCollectMem) && trace) + { + log_mem_trace(); + } + if ((gen_method == MemReads) && trace) + { + log_minimal_trace(); + } + if ((gen_method == ChunkPlayerMemReadsCollectMain) && trace) + { + log_chunk_player_main_trace(); + } +} + +void server_cleanup (void) +{ + // Cleanup ROM + int result = munmap((void *)ROM_ADDR, ROM_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(rom) errno=%d=%s\n", errno, strerror(errno)); + } + + // Cleanup RAM + result = munmap((void *)RAM_ADDR, RAM_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(ram) errno=%d=%s\n", errno, strerror(errno)); + } + + // Cleanup INPUT + result = munmap((void *)INPUT_ADDR, MAX_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(input) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_input_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); + } + + if (precompile_results_enabled && (gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + // Cleanup PRECOMPILE + result = munmap((void *)shmem_precompile_address, MAX_PRECOMPILE_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(precompile) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_precompile_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); + } + + // Cleanup CONTROL + result = munmap((void *)shmem_control_input_address, CONTROL_INPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control_input) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_input_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); + } + result = munmap((void *)shmem_control_output_address, CONTROL_OUTPUT_SIZE); + if (result == -1) + { + printf("ERROR: Failed calling munmap(control_output) errno=%d=%s\n", errno, strerror(errno)); + } + result = shm_unlink(shmem_control_output_name); + if (result == -1) + { + printf("ERROR: Failed calling shm_unlink(%s) errno=%d=%s\n", shmem_control_output_name, errno, strerror(errno)); + } + + // Semaphores cleanup + result = sem_close(sem_prec_avail); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + } + result = sem_unlink(sem_prec_avail_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_avail_name, errno, strerror(errno)); + } + result = sem_close(sem_prec_read); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + } + result = sem_unlink(sem_prec_read_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_prec_read_name, errno, strerror(errno)); + } + } + + // Cleanup trace + trace_cleanup(); + + // Cleanup chunk done semaphore + if (call_chunk_done) + { + result = sem_close(sem_chunk_done); + if (result == -1) + { + printf("ERROR: Failed calling sem_close(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + } + result = sem_unlink(sem_chunk_done_name); + if (result == -1) + { + printf("ERROR: Failed calling sem_unlink(%s) errno=%d=%s\n", sem_chunk_done_name, errno, strerror(errno)); + } + } + + // Post shutdown donw semaphore + result = sem_post(sem_shutdown_done); + if (result == -1) + { + printf("ERROR: Failed calling sem_post(%s) errno=%d=%s\n", sem_shutdown_done_name, errno, strerror(errno)); + } +} \ No newline at end of file diff --git a/emulator-asm/src/server.hpp b/emulator-asm/src/server.hpp new file mode 100644 index 000000000..c76cefee9 --- /dev/null +++ b/emulator-asm/src/server.hpp @@ -0,0 +1,11 @@ +#ifndef EMULATOR_ASM_SERVER_HPP +#define EMULATOR_ASM_SERVER_HPP + +void server_setup (void); +void server_reset_fast (void); +void server_reset_slow (void); +void server_reset_trace (void); +void server_run (void); +void server_cleanup (void); + +#endif // EMULATOR_ASM_SERVER_HPP \ No newline at end of file diff --git a/emulator-asm/src/trace.c b/emulator-asm/src/trace.c new file mode 100644 index 000000000..f3fe2863d --- /dev/null +++ b/emulator-asm/src/trace.c @@ -0,0 +1,238 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "trace.hpp" +#include "constants.hpp" +#include "globals.hpp" +#include "emu.hpp" + +/********************/ +/* TRACE ALLOCATION */ +/********************/ + +void trace_virtual_alloc (void) +{ + void * addr = mmap((void *)TRACE_ADDR, TRACE_MAX_SIZE, PROT_NONE, MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr != (void *)TRACE_ADDR) + { + printf("ERROR: trace_virtual_alloc() failed to reserve trace memory at address 0x%lx\n", TRACE_ADDR); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +uint64_t next_chunk_id = 0; // Next trace chunk id to be mapped, starting from 0 +int trace_chunk_fd[TRACE_NUMBER_OF_CHUNKS]; // File descriptors for each chunk +uint64_t trace_total_mapped_size = 0; // Total mapped trace size + +void * trace_get_chunk_address (uint64_t chunk_id) +{ + assert(gen_method != RomHistogram || chunk_id == 0); + + if (chunk_id == 0) + { + return (void *)TRACE_ADDR; + } + else + { + return (void *)(TRACE_ADDR + TRACE_INITIAL_SIZE + ((chunk_id - 1) * TRACE_DELTA_SIZE)); + } +} + +uint64_t trace_get_chunk_size (uint64_t chunk_id) +{ + if (gen_method == RomHistogram) { + assert(chunk_id == 0); + return trace_size; + } + + if (chunk_id == 0) + { + return TRACE_INITIAL_SIZE; + } + else + { + return TRACE_DELTA_SIZE; + } +} + +void trace_generate_shmem_chunk_name(char * shmem_chunk_name, size_t shmem_chunk_name_size, uint64_t chunk_id) +{ + int result = snprintf(shmem_chunk_name, shmem_chunk_name_size, "%s_%lu", shmem_output_name, chunk_id); + if (result < 0 || result >= (int)shmem_chunk_name_size) + { + printf("ERROR: trace_generate_shmem_chunk_name() failed to create chunk shared memory name\n"); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +void trace_cleanup (void) +{ + // Unmap all mapped chunks + for (uint64_t chunk_id = 0; chunk_id < next_chunk_id; chunk_id++) + { + uint64_t chunk_size = trace_get_chunk_size(chunk_id); + void * chunk_address = trace_get_chunk_address(chunk_id); + int result = munmap(chunk_address, chunk_size); + if (result != 0) + { + printf("ERROR: trace_cleanup() failed calling munmap() chunk id=%lu size=%lu B address=0x%lx errno=%d=%s\n", chunk_id, chunk_size, (uint64_t)chunk_address, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Close the chunk shared memory file descriptor + close(trace_chunk_fd[chunk_id]); + trace_chunk_fd[chunk_id] = -1; + + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + shm_unlink(shmem_chunk_name); + } + + // Reset next chunk id + next_chunk_id = 0; +} + +void trace_preventive_cleanup (void) +{ + // Unmap all mapped chunks + for (uint64_t chunk_id = 0; chunk_id < TRACE_NUMBER_OF_CHUNKS; chunk_id++) + { + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + int result = shm_unlink(shmem_chunk_name); + if (result != 0) + { + break; + } + if (verbose) printf("trace_preventive_cleanup() unlinked chunk shared memory %s\n", shmem_chunk_name); + } +} + +void trace_map_next_chunk (void) +{ + // Get the next chunk id, size and address + uint64_t chunk_id = next_chunk_id; + if (chunk_id >= TRACE_NUMBER_OF_CHUNKS) + { + printf("ERROR: trace_map_next_chunk() exceeded maximum number of chunks %lu\n", TRACE_NUMBER_OF_CHUNKS); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t chunk_size = trace_get_chunk_size(chunk_id); + void * chunk_address = trace_get_chunk_address(chunk_id); + + if (verbose) printf("trace_map_next_chunk() mapping chunk id=%lu size=%lu B address=0x%lx\n", chunk_id, chunk_size, (uint64_t)chunk_address); + + // Build the chunk shared memory name + char shmem_chunk_name[128]; + trace_generate_shmem_chunk_name(shmem_chunk_name, sizeof(shmem_chunk_name), chunk_id); + + // Make sure the chunk shared memory is deleted + shm_unlink(shmem_chunk_name); + + // Create the output shared memory + trace_chunk_fd[chunk_id] = shm_open(shmem_chunk_name, O_RDWR | O_CREAT | O_EXCL, 0666); + if (trace_chunk_fd[chunk_id] < 0) + { + printf("ERROR: trace_map_next_chunk() failed calling trace shm_open(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Size it + int result = ftruncate(trace_chunk_fd[chunk_id], chunk_size); + if (result != 0) + { + printf("ERROR: trace_map_next_chunk() failed calling ftruncate(%s) errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + // Sync + fsync(trace_chunk_fd[chunk_id]); + + // Map it to the trace address + if (verbose) gettimeofday(&start_time, NULL); + void * requested_address; + if ((gen_method == ChunkPlayerMTCollectMem) || (gen_method == ChunkPlayerMemReadsCollectMain)) + { + requested_address = 0; + } + else + { + requested_address = (void *)chunk_address; + } + int flags = MAP_SHARED | map_locked_flag; + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain)) + { + flags |= MAP_FIXED; + } + void * pTrace = mmap(requested_address, chunk_size, PROT_READ | PROT_WRITE, flags, trace_chunk_fd[chunk_id], 0); + if (verbose) + { + gettimeofday(&stop_time, NULL); + duration = TimeDiff(start_time, stop_time); + } + if (pTrace == MAP_FAILED) + { + printf("ERROR: trace_map_next_chunk() failed calling mmap(pTrace) name=%s errno=%d=%s\n", shmem_chunk_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if ((gen_method != ChunkPlayerMTCollectMem) && (gen_method != ChunkPlayerMemReadsCollectMain) && ((uint64_t)pTrace != (uint64_t)requested_address)) + { + printf("ERROR: trace_map_next_chunk() called mmap(trace) but returned address = %p != 0x%lx\n", pTrace, (uint64_t)requested_address); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (verbose) printf("trace_map_next_chunk() mapped %lu B to %s and returned address %p in %lu us\n", chunk_size, shmem_chunk_name, pTrace, duration); + + // Update total mapped size + trace_total_mapped_size += chunk_size; + + // Increment next chunk id + next_chunk_id++; +} + +void trace_map_initialize (void) +{ + // Perform preventive cleanup of any leftover shared memory chunks + trace_preventive_cleanup(); + + // Reserve the full virtual trace address space + trace_virtual_alloc(); + + // Map the first chunk, i.e. chunk 0 + trace_map_next_chunk(); + + trace_address = TRACE_ADDR; + pOutputTrace = (uint64_t *)TRACE_ADDR; +} \ No newline at end of file diff --git a/emulator-asm/src/trace.hpp b/emulator-asm/src/trace.hpp new file mode 100644 index 000000000..84983df20 --- /dev/null +++ b/emulator-asm/src/trace.hpp @@ -0,0 +1,12 @@ +#ifndef EMULATOR_ASM_TRACE_HPP +#define EMULATOR_ASM_TRACE_HPP + +#include + +extern uint64_t trace_total_mapped_size; // Total mapped trace size + +void trace_map_initialize (void); +void trace_map_next_chunk (void); +void trace_cleanup (void); + +#endif // EMULATOR_ASM_TRACE_HPP \ No newline at end of file diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c new file mode 100644 index 000000000..3272c0027 --- /dev/null +++ b/emulator-asm/src/trace_logs.c @@ -0,0 +1,678 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include "constants.hpp" +#include "globals.hpp" +#include "asm_provided.hpp" +#include "globals.hpp" + +/*****************/ +/* LOG FUNCTIONS */ +/*****************/ + +/* Trace data structure + [8B] Number of chunks: C + + Chunk 0: + Start state: + [8B] pc + [8B] sp + [8B] c + [8B] step + [8B] register[1] + … + [8B] register[31] + [8B] register[32] + [8B] register[33] + Last state: + [8B] c + End: + [8B] end + Steps: + [8B] steps = chunk size except for the last chunk + [8B] mem_reads_size + [8B] mem_reads[0] + [8B] mem_reads[1] + … + [8B] mem_reads[mem_reads_size - 1] + + Chunk 1: + … + Chunk C-1: + … +*/ +void log_minimal_trace(void) +{ + uint64_t * pOutput = (uint64_t *)TRACE_ADDR; + printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] + printf("Minimal trace used size = %lu B\n", pOutput[3]); // Minimal trace used size [8] + + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; + uint64_t number_of_chunks = trace[0]; + printf("Number of chunks=%lu\n", number_of_chunks); + if (number_of_chunks > 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (trace_trace) + { + for (uint64_t m=0; m 100000000) + { + printf("ERROR: Bios size is too high=%lu\n", bios_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (trace_trace) + { + uint64_t * bios = trace + 1; + for (uint64_t i=0; i 100000000) + { + printf("ERROR: Program size is too high=%lu\n", program_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + if (trace_trace) + { + uint64_t * program = trace + 1 + bios_size + 1; + for (uint64_t i=0; i 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Main_trace size is too high=%lu\n", main_trace_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + if (trace_trace) + { + for (uint64_t m=0; m 0) + { + size_t bytes_written = fwrite(buffer_address, 1, buffer_length, file); + if (bytes_written != buffer_length) + { + printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%lu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + fclose(file); + exit(-1); + } + } + + if (fclose(file) != 0) + { + printf("ERROR: buffer2file() failed calling fclose(%s) errno=%d=%s\n", file_name, errno, strerror(errno)); + fflush(stdout); + fflush(stderr); + exit(-1); + } +} + +/* Memory operations structure + [8B] Number of chunks = C + + Chunk 0: + [8b] end + [8B] mem_op_trace_size + [8B] mem_op_trace[0] + [8B] mem_op_trace[1] + … + [8B] mem_op_trace[mem_op_trace_size - 1] + + Chunk 1: + … + Chunk C-1: + … +*/ +void log_mem_op(void) +{ + // Log header + uint64_t * pOutput = (uint64_t *)TRACE_ADDR; + printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] + + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; + uint64_t number_of_chunks = trace[0]; + printf("Number of chunks=%lu\n", number_of_chunks); + if (number_of_chunks > 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + for (uint64_t m=0; m> 49) & 0x1; + uint64_t write = (chunk[i] >> 48) & 0x1; + uint64_t width = (chunk[i] >> 32) & 0xF; + uint64_t address = chunk[i] & 0xFFFFFFFF; + bool inside_range = + ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || + ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || + ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); + if (trace_trace || !inside_range) + { + printf("\t\tchunk[%lu].mem_op_trace[%lu] = %016lx = rest_are_zeros=%lx, write=%lx, width=%lx, address=%lx%s\n", + c, + m, + chunk[i], + rest_are_zeros, + write, + width, + address, + inside_range ? "" : " ERROR!!!!!!!!!!!!!!" + ); + } + i += 1; + } + + //Set next chunk pointer + chunk = chunk + i; + } + printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); +} + +/* Memory trace structure (for 1 chunk) + [8B] mem_trace_size + [16B] mem_trace[0] + [8B] mem operacion + [4B] address (LE) + [1B] width (1, 2, 4, 8) + write (0, 1) << 4 + [3B] + [16B] mem_trace[1] + … + [16B] mem_trace[mem_trace_size - 1] +*/ +void log_mem_trace(void) +{ + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)TRACE_ADDR; + printf("log_mem_trace() trace_address=%p\n", trace); + uint64_t i=0; + printf("Version = 0x%06lx\n", trace[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", trace[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", trace[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", trace[3]); // Main trace used size [8] + i += 4; + uint64_t number_of_entries = trace[i]; + i++; + printf("Trace size=%lu\n", number_of_entries); + + for (uint64_t m = 0; m < number_of_entries; m++) + { + uint64_t addr_step = trace[i]; + i++; + + // addr_step = [@0, @1, @2, @3, width + write<<4, supra_step] + uint64_t address = addr_step & 0xFFFFFFFF; + uint64_t width = (addr_step >> (4*8)) & 0xF; + uint64_t write = (addr_step >> ((4*8) + 4)) & 0x1; + uint64_t micro_step = (addr_step >> (5*8)) & 0x3; + uint64_t incremental_step = (addr_step >> ((5*8) + 2)); + bool address_is_inside_range = + ((address >= RAM_ADDR) && (address < (RAM_ADDR + RAM_SIZE))) || + ((address >= ROM_ADDR) && (address < (ROM_ADDR + ROM_SIZE))) || + ((address >= INPUT_ADDR) && (address < (INPUT_ADDR + MAX_INPUT_SIZE))); + bool width_is_valid = (width == 1) || (width == 2) || (width == 4) || (width == 8); + bool bError = !(address_is_inside_range && width_is_valid); + if (trace_trace || bError) + { + printf("\tmem_trace[%lu] = %016lx = [inc_step=%lu, u_step=%lu, write=%lx, width=%lx, address=%lx] %s\n", + m, + addr_step, + incremental_step, + micro_step, + write, + width, + address, + bError ? " ERROR!!!!!!!!!!!!!!" : "" + ); + } + + // u-step: + // 0: a=SRC_MEM + // 1: b=SRC_MEM or b=SRC_IND + // 2: precompiled_read + // 3: c=STORE_MEM, c=STORE_IND or precompiled_write + + bool address_is_aligned = (address & 0x7) == 0; + uint64_t aligned_address = address & 0xFFFFFFF8; + uint64_t number_of_read_values = 0; + uint64_t number_of_write_values = 0; + + switch (micro_step) + { + case 0: // a=SRC_MEM + { + assert(width == 8); + if (address_is_aligned) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + break; + } + case 1: // b=SRC_MEM or b=SRC_IND + { + if (address_is_aligned) + { + number_of_read_values = 1; + } + else + { + if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + } + break; + } + case 2: // precompiled_read + { + assert(width == 8); + if (address_is_aligned) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + break; + } + case 3: // c=STORE_MEM, c=STORE_IND or precompiled_write + { + if (address_is_aligned && (width == 8)) + { + number_of_read_values = 0; + } + else + { + if (((address + width - 1) & 0xFFFFFFF8) == aligned_address) + { + number_of_read_values = 1; + } + else + { + number_of_read_values = 2; + } + } + number_of_write_values = 1; + break; + } + } + + for (uint64_t r = 0; r < number_of_read_values; r++) + { + uint64_t value = trace[i]; + i++; + m++; + if (trace_trace) + { + printf("\t\tread_value[%lu] = 0x%lx\n", i, value); + } + } + + for (uint64_t w = 0; w < number_of_write_values; w++) + { + uint64_t value = trace[i]; + i++; + m++; + if (trace_trace) + { + printf("\t\twrite_value[%lu] = 0x%lx\n", i, value); + } + } + } + printf("Trace=%p number_of_entries=%lu\n", trace, number_of_entries); +} + +void save_mem_op_to_files(void) +{ + // Log header + uint64_t * pOutput = (uint64_t *)TRACE_ADDR; + printf("Version = 0x%06lx\n", pOutput[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", pOutput[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", pOutput[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", pOutput[3]); // Main trace used size [8] + + printf("Trace content:\n"); + uint64_t * trace = (uint64_t *)MEM_TRACE_ADDRESS; + uint64_t number_of_chunks = trace[0]; + printf("Number of chunks=%lu\n", number_of_chunks); + if (number_of_chunks > 1000000) + { + printf("ERROR: Number of chunks is too high=%lu\n", number_of_chunks); + fflush(stdout); + fflush(stderr); + exit(-1); + } + uint64_t * chunk = trace + 1; + for (uint64_t c=0; c 10000000) + { + printf("ERROR: Mem op trace size is too high=%lu\n", mem_op_trace_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + + printf("Chunk %lu: file=%s length=%lu\n", c, file_name, mem_op_trace_size); + + buffer2file(&chunk[i], mem_op_trace_size * 8, file_name); + + //Set next chunk pointer + chunk = chunk + mem_op_trace_size + 1; + } + printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); +} + +/* Trace data structure + [8B] Number of elements + + A series of elements with the following structure: + [8B] op: instruction opcode + [8B] a: register a value + [8B] b: register b value + [8B] precompiled_memory_address: memory read address of the precompiled input data +*/ +void log_chunk_player_main_trace(void) +{ + uint64_t * chunk = (uint64_t *)TRACE_ADDR; + uint64_t i = 0; + + printf("Version = 0x%06lx\n", chunk[0]); // Version, e.g. v1.0.0 [8] + printf("Exit code = %lu\n", chunk[1]); // Exit code: 0=successfully completed, 1=not completed (written at the beginning of the emulation), etc. [8] + printf("Allocated size = %lu B\n", chunk[2]); // Allocated size [8] + printf("Memory operations trace used size = %lu B\n", chunk[3]); // Main trace used size [8] + i = 4; + + uint64_t mem_reads_size = chunk[i]; + i++; + printf("mem_reads_size=%lu\n", mem_reads_size); + if (mem_reads_size > 10000000) + { + printf("ERROR: Mem reads size is too high=%lu\n", mem_reads_size); + fflush(stdout); + fflush(stderr); + exit(-1); + } + //if (trace_trace) + { + for (uint64_t m=0; m 0xFF) + { + printf("ERROR!! Invalid op=%lu=0x%lx\n", op, op); + } + if (trace_trace) printf("\tmem_reads[%lu] a=0x%08lx\n", m, chunk[i]); + i++; + m++; + if (trace_trace) printf("\tmem_reads[%lu] b=0x%08lx\n", m, chunk[i]); + i++; + m++; + if ( (op == 0xf1) // Keccak + || (op == 0xf9) // SHA256 + || (op == 0xf2) // Arith256 + || (op == 0xf3) // Arith256Mod + || (op == 0xf4) // Secp256k1Add + || (op == 0xf5) // Secp256k1Dbl + ) + { + if (trace_trace) printf("\tmem_reads[%lu] precompiled_address=%08lx\n", m, chunk[i]); + i++; + m++; + } + } + } + + printf("Chunk=%p size=%lu\n", chunk, mem_reads_size); +} \ No newline at end of file diff --git a/emulator-asm/src/trace_logs.hpp b/emulator-asm/src/trace_logs.hpp new file mode 100644 index 000000000..258804f23 --- /dev/null +++ b/emulator-asm/src/trace_logs.hpp @@ -0,0 +1,14 @@ +#ifndef EMULATOR_ASM_TRACE_LOGS_HPP +#define EMULATOR_ASM_TRACE_LOGS_HPP + +#include + +void log_minimal_trace(void); +void log_histogram(void); +void log_main_trace(void); +void log_mem_trace(void); +void log_mem_op(void); +void save_mem_op_to_files(void); +void log_chunk_player_main_trace(void); + +#endif // EMULATOR_ASM_TRACE_LOGS_HPP \ No newline at end of file From 1d2b5c40c923e91c14d88cb6a6452bd3e5bb9bee Mon Sep 17 00:00:00 2001 From: fractasy Date: Mon, 2 Mar 2026 22:02:46 +0100 Subject: [PATCH 746/782] Fix copilot issues --- emulator-asm/src/c_provided.c | 2 ++ emulator-asm/src/configuration.c | 10 +++++----- emulator-asm/src/server.c | 6 +++--- emulator-asm/src/trace_logs.c | 12 +++++++++--- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/emulator-asm/src/c_provided.c b/emulator-asm/src/c_provided.c index cecec70cf..643dc1ad0 100644 --- a/emulator-asm/src/c_provided.c +++ b/emulator-asm/src/c_provided.c @@ -117,6 +117,7 @@ extern int _print_regs() printf("\treg[34]=%lu=0x%lx=@%p\n", reg_34, reg_34, ®_34); printf("\n"); #endif + return 0; } /************/ @@ -189,6 +190,7 @@ extern int _print_pc (uint64_t pc, uint64_t c) printf("\n"); fflush(stdout); print_pc_counter++; + return 0; } /**************/ diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index 90cdf5781..251a9c153 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -187,7 +187,7 @@ void parse_arguments(int argc, char *argv[]) if (strcmp(argv[i], "--help") == 0) { print_usage(); - continue; + exit(0); } if (strcmp(argv[i], "-i") == 0) { @@ -212,13 +212,13 @@ void parse_arguments(int argc, char *argv[]) i++; if (i >= argc) { - printf("ERROR: Detected argument -i in the last position; please provide shared mem prefix after it\n"); + printf("ERROR: Detected argument --shm_prefix in the last position; please provide shared mem prefix after it\n"); print_usage(); exit(-1); } if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) { - printf("ERROR: Detected argument -i but next argument is too long\n"); + printf("ERROR: Detected argument --shm_prefix but next argument is too long\n"); print_usage(); exit(-1); } @@ -230,7 +230,7 @@ void parse_arguments(int argc, char *argv[]) i++; if (i >= argc) { - printf("ERROR: Detected argument -c in the last position; please provide chunk number after it\n"); + printf("ERROR: Detected argument --chunk in the last position; please provide chunk number after it\n"); print_usage(); exit(-1); } @@ -270,7 +270,7 @@ void parse_arguments(int argc, char *argv[]) i++; if (i >= argc) { - printf("ERROR: Detected argument -mt in the last position; please provide number of MT requests after it\n"); + printf("ERROR: Detected argument --mt in the last position; please provide number of MT requests after it\n"); print_usage(); exit(-1); } diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index 60fdb49f4..369dc15e7 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -112,7 +112,7 @@ void server_setup (void) } // Open the input shared memory as read-only - shmem_input_fd = shm_open(shmem_input_name, O_RDONLY | O_EXCL, 0666); + shmem_input_fd = shm_open(shmem_input_name, O_RDONLY, 0666); if (shmem_input_fd < 0) { printf("ERROR: Failed calling input RO shm_open(%s) as read-only errno=%d=%s\n", shmem_input_name, errno, strerror(errno)); @@ -195,7 +195,7 @@ void server_setup (void) } // Open the precompile shared memory as read-only - shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDONLY | O_EXCL, 0666); + shmem_precompile_fd = shm_open(shmem_precompile_name, O_RDONLY, 0666); if (shmem_precompile_fd < 0) { printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_precompile_name, errno, strerror(errno)); @@ -266,7 +266,7 @@ void server_setup (void) } // Open the control input shared memory as read-only - shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY | O_EXCL, 0666); + shmem_control_input_fd = shm_open(shmem_control_input_name, O_RDONLY, 0666); if (shmem_control_input_fd < 0) { printf("ERROR: Failed calling precompile RO shm_open(%s) as read-only errno=%d=%s\n", shmem_control_input_name, errno, strerror(errno)); diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index 3272c0027..cd583c36a 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -9,7 +9,6 @@ #include "constants.hpp" #include "globals.hpp" #include "asm_provided.hpp" -#include "globals.hpp" /*****************/ /* LOG FUNCTIONS */ @@ -265,7 +264,7 @@ void log_main_trace(void) printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); } -void buffer2file (const void * buffer_address, size_t buffer_length, const char * file_name) +static void buffer2file (const void * buffer_address, size_t buffer_length, const char * file_name) { if (!file_name) { @@ -588,7 +587,14 @@ void save_mem_op_to_files(void) for (uint64_t c=0; c= sizeof(file_name)) + { + fprintf(stderr, "ERROR: Failed to construct file name for chunk=%lu\n", c); + fflush(stdout); + fflush(stderr); + exit(-1); + } uint64_t i=0; uint64_t mem_op_trace_size = chunk[i]; From 1de751c773582962185abe12e471531061c89c59 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 3 Mar 2026 10:21:54 +0100 Subject: [PATCH 747/782] Fix copilot issues --- emulator-asm/src/configuration.c | 4 ++-- emulator-asm/src/constants.hpp | 2 ++ emulator-asm/src/trace_logs.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index 251a9c153..7ac488f9f 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -349,7 +349,7 @@ void parse_arguments(int argc, char *argv[]) char *endptr; char * argument = argv[i]; if ((argument[0] == '0') && (argument[1] == 'x')) argument += 2; - chunk_player_address = strtoul(argv[i], &endptr, 16); + chunk_player_address = strtoul(argument, &endptr, 16); // Check for errors if (errno == ERANGE) { @@ -357,7 +357,7 @@ void parse_arguments(int argc, char *argv[]) print_usage(); exit(-1); } else if (endptr == argument) { - printf("ERROR: No digits found while parsing chunk addresss\n"); + printf("ERROR: No digits found while parsing chunk address\n"); print_usage(); exit(-1); } else if (*endptr != '\0') { diff --git a/emulator-asm/src/constants.hpp b/emulator-asm/src/constants.hpp index 9860a6a22..c9a88259a 100644 --- a/emulator-asm/src/constants.hpp +++ b/emulator-asm/src/constants.hpp @@ -1,6 +1,8 @@ #ifndef EMULATOR_ASM_CONSTANTS_HPP #define EMULATOR_ASM_CONSTANTS_HPP +#include + /***************/ /* Definitions */ /***************/ diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index cd583c36a..9d0c9b5b1 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -295,7 +295,7 @@ static void buffer2file (const void * buffer_address, size_t buffer_length, cons size_t bytes_written = fwrite(buffer_address, 1, buffer_length, file); if (bytes_written != buffer_length) { - printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%lu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); + printf("ERROR: buffer2file() failed calling fwrite(%s) buffer_address=%p buffer_length=%zu errno=%d=%s\n", file_name, buffer_address, buffer_length, errno, strerror(errno)); fflush(stdout); fflush(stderr); fclose(file); From 4bb9c90726e96494047512e07d3bd160cd603fc4 Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 3 Mar 2026 11:21:02 +0100 Subject: [PATCH 748/782] Fix copilot issues --- emulator-asm/src/trace_logs.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index 9d0c9b5b1..d3e0df35a 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -10,6 +10,10 @@ #include "globals.hpp" #include "asm_provided.hpp" +// This file contains trace logging functions that are used only for debugging purposes, to log the +// content of the generated traces in a human-readable format. These functions are not used by the +// assembly code, and are not optimized for performance. + /*****************/ /* LOG FUNCTIONS */ /*****************/ @@ -597,6 +601,7 @@ void save_mem_op_to_files(void) } uint64_t i=0; + i++; // Skip end uint64_t mem_op_trace_size = chunk[i]; i++; if (mem_op_trace_size > 10000000) From 93099cf36b3ddbaa57f6a58a1bd79bcc970a187c Mon Sep 17 00:00:00 2001 From: fractasy Date: Tue, 3 Mar 2026 12:47:08 +0100 Subject: [PATCH 749/782] Fix copilot issues --- emulator-asm/Makefile | 2 +- emulator-asm/src/configuration.c | 11 +++++++++-- emulator-asm/src/globals.hpp | 1 + emulator-asm/src/server.c | 2 +- emulator-asm/src/trace.c | 1 - emulator-asm/src/trace_logs.c | 4 ++-- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 75835a40b..4a963aa4b 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -56,7 +56,7 @@ build/dma.o: $(DMA_OBJS) ld -r $(DMA_OBJS) -o $@ # Compile the final executable -$(OUT_PATH): build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c build/dma.o +$(OUT_PATH): build/emu.o src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c build/dma.o mkdir -p $(OUT_DIR) gcc $(CFLAGS) src/main.c src/globals.c src/configuration.c src/trace_logs.c src/server.c src/client.c src/c_provided.c src/trace.c src/emu.c src/chfast/keccak.c src/bcon/bcon_sha256.c -L../lib-c/c/lib -L../../bin -L../target/release -L../target/debug -lc build/emu.o -lc build/dma.o -lziskc -lziskclib -lgmp -lstdc++ -lgmpxx -o $@ diff --git a/emulator-asm/src/configuration.c b/emulator-asm/src/configuration.c index 7ac488f9f..ef33c7308 100644 --- a/emulator-asm/src/configuration.c +++ b/emulator-asm/src/configuration.c @@ -216,7 +216,7 @@ void parse_arguments(int argc, char *argv[]) print_usage(); exit(-1); } - if (strlen(argv[i]) > MAX_SHM_PREFIX_LENGTH) + if (strlen(argv[i]) >= MAX_SHM_PREFIX_LENGTH) { printf("ERROR: Detected argument --shm_prefix but next argument is too long\n"); print_usage(); @@ -311,7 +311,14 @@ void parse_arguments(int argc, char *argv[]) } errno = 0; char *endptr; - arguments_port = strtoul(argv[i], &endptr, 10); + uint64_t arguments_port_u64 = strtoul(argv[i], &endptr, 10); + if (arguments_port_u64 > 0xFFFF) + { + printf("ERROR: Port number is too large, must be at most 65535\n"); + print_usage(); + exit(-1); + } + arguments_port = arguments_port_u64 & 0xFFFF; // Keep only lower 16 bits, since port numbers are 16 bits // Check for errors if (errno == ERANGE) { diff --git a/emulator-asm/src/globals.hpp b/emulator-asm/src/globals.hpp index f66e0044c..3d1bf6ecf 100644 --- a/emulator-asm/src/globals.hpp +++ b/emulator-asm/src/globals.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "constants.hpp" // Configuration globals, set by arguments diff --git a/emulator-asm/src/server.c b/emulator-asm/src/server.c index 369dc15e7..e16ddb624 100644 --- a/emulator-asm/src/server.c +++ b/emulator-asm/src/server.c @@ -949,7 +949,7 @@ void server_cleanup (void) } } - // Post shutdown donw semaphore + // Post shutdown done semaphore result = sem_post(sem_shutdown_done); if (result == -1) { diff --git a/emulator-asm/src/trace.c b/emulator-asm/src/trace.c index f3fe2863d..45ddca19d 100644 --- a/emulator-asm/src/trace.c +++ b/emulator-asm/src/trace.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include diff --git a/emulator-asm/src/trace_logs.c b/emulator-asm/src/trace_logs.c index d3e0df35a..d63c0f973 100644 --- a/emulator-asm/src/trace_logs.c +++ b/emulator-asm/src/trace_logs.c @@ -616,8 +616,8 @@ void save_mem_op_to_files(void) buffer2file(&chunk[i], mem_op_trace_size * 8, file_name); - //Set next chunk pointer - chunk = chunk + mem_op_trace_size + 1; + //Set next chunk pointer: skip [end] and [mem_op_trace_size] headers plus data + chunk = chunk + mem_op_trace_size + 2; } printf("Trace=%p chunk=%p size=%lu\n", trace, chunk, (uint64_t)chunk - (uint64_t)trace); } From 4e70baf7a323928d2693e032159a371cbc6c8879 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 10 Mar 2026 10:09:08 +0000 Subject: [PATCH 750/782] Trace assembly memory reduced to 32 GB --- emulator-asm/Makefile | 2 +- emulator-asm/src/constants.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 8cbe6a122..aea484a0f 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -59,7 +59,7 @@ build/zisk.ld: mkdir -p build ld --verbose 2>/dev/null | \ awk '/^=+$$/{found++; next} found==1{print}' | \ - sed '/\/DISCARD\//i\ .my_region 0x40000000 (NOLOAD) : { . = . + 0x1090000000; }' \ + sed '/\/DISCARD\//i\ .my_region 0x40000000 (NOLOAD) : { . = . + 0x890000000; }' \ > build/zisk.ld # Compile the final executable diff --git a/emulator-asm/src/constants.hpp b/emulator-asm/src/constants.hpp index 70f1668ba..71f3bacb2 100644 --- a/emulator-asm/src/constants.hpp +++ b/emulator-asm/src/constants.hpp @@ -33,7 +33,7 @@ #endif #define TRACE_ADDR (uint64_t)0xd0000000 -#define TRACE_MAX_SIZE (uint64_t)0x1000000000 // 64GB +#define TRACE_MAX_SIZE (uint64_t)0x800000000 // 32GB #define TRACE_NUMBER_OF_CHUNKS (((TRACE_MAX_SIZE - TRACE_INITIAL_SIZE) / TRACE_DELTA_SIZE) + 1) #define TRACE_SIZE_GRANULARITY (1014*1014) // ROM histogram trace size is round up to a multiple of this granularity From 02ff9b9aefb793d2ce6659e6549e9f1e8ba0fd2e Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:56:04 +0000 Subject: [PATCH 751/782] added 3 sem avail for inputs --- emulator-asm/asm-runner/src/inputs_shmem.rs | 54 ++++++++++++++------- emulator-asm/asm-runner/src/lib.rs | 5 ++ 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/emulator-asm/asm-runner/src/inputs_shmem.rs b/emulator-asm/asm-runner/src/inputs_shmem.rs index fa14a0a4d..a77d10095 100644 --- a/emulator-asm/asm-runner/src/inputs_shmem.rs +++ b/emulator-asm/asm-runner/src/inputs_shmem.rs @@ -1,19 +1,20 @@ -use std::sync::{Arc, Mutex}; +use std::cell::RefCell; +use std::sync::Arc; use named_sem::NamedSemaphore; use zisk_common::{io::StreamSink, reinterpret_vec}; use zisk_core::MAX_INPUT_SIZE; use crate::{ - shmem_input_avail_name, shmem_input_name, AsmServices, ControlShmem, SharedMemoryWriter, + sem_input_avail_name, shmem_input_name, AsmServices, ControlShmem, SharedMemoryWriter, }; use anyhow::Result; pub struct InputsShmemWriter { - writer: Mutex, + writer: RefCell, control_writer: Arc, - sem_avail: Mutex, + sem_avails: RefCell>, } unsafe impl Send for InputsShmemWriter {} @@ -37,40 +38,59 @@ impl InputsShmemWriter { writer.reset(); writer.append_input(&0u64.to_le_bytes())?; - let sem_avail = Mutex::new(NamedSemaphore::create( - shmem_input_avail_name(port, local_rank).clone(), - 0, - )?); - - Ok(Self { writer: Mutex::new(writer), control_writer, sem_avail }) + // Create one semaphore per ASM service + let sem_avails: Vec = AsmServices::SERVICES + .iter() + .map(|service| { + let name = sem_input_avail_name(port, *service, local_rank); + NamedSemaphore::create(&name, 0) + .map_err(|e| anyhow::anyhow!("Failed to create semaphore '{}': {}", name, e)) + }) + .collect::>>()?; + + Ok(Self { + writer: RefCell::new(writer), + control_writer, + sem_avails: RefCell::new(sem_avails), + }) } pub fn write_input(&self, inputs: &[u8]) -> Result<()> { - self.writer.lock().unwrap().write_at(8, inputs)?; + self.writer.borrow_mut().write_at(8, inputs)?; self.control_writer.inc_inputs_size(inputs.len()); - self.sem_avail.lock().unwrap().post()?; + self.notify_all_services()?; Ok(()) } pub fn append_input(&self, inputs: &[u8]) -> Result<()> { - self.writer.lock().unwrap().append_input(inputs)?; + self.writer.borrow_mut().append_input(inputs)?; self.control_writer.inc_inputs_size(inputs.len()); - self.sem_avail.lock().unwrap().post()?; + self.notify_all_services()?; + + Ok(()) + } + /// Notify all ASM services that new input data is available + fn notify_all_services(&self) -> Result<()> { + for sem in self.sem_avails.borrow_mut().iter_mut() { + sem.post()?; + } Ok(()) } pub fn reset(&self) { - let mut writer = self.writer.lock().unwrap(); + let mut writer = self.writer.borrow_mut(); writer.reset(); writer .append_input(&0u64.to_le_bytes()) .expect("Failed to write initial header after reset"); self.control_writer.reset(); - let mut sem_avail_guard = self.sem_avail.lock().unwrap(); - while sem_avail_guard.try_wait().is_ok() {} + // Drain all the semaphore signals from all services + for sem in self.sem_avails.borrow_mut().iter_mut() { + while sem.try_wait().is_ok() {} + } } } diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index 8650975b9..d4c9e022b 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -110,6 +110,11 @@ pub fn shmem_input_avail_name(port: u16, local_rank: i32) -> String { build_shmem_name2(port, local_rank, "input_avail") } +/// Semaphore name for input availability (per service) +pub fn sem_input_avail_name(port: u16, asm_service: AsmService, local_rank: i32) -> String { + build_sem_name(port, asm_service, local_rank, "input_avail") +} + /// Shared memory name for precompile hints data pub fn shmem_precompile_name(port: u16, local_rank: i32) -> String { build_shmem_name2(port, local_rank, "precompile") From 84fe492f1c21561a1048252825f5b5e66d921461 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 10 Mar 2026 11:57:26 +0000 Subject: [PATCH 752/782] Fixing Rom SM && and wait assembly --- emulator-asm/src/c_provided.c | 2 +- executor/src/asm_resources.rs | 5 +++++ sdk/src/prover/asm.rs | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/emulator-asm/src/c_provided.c b/emulator-asm/src/c_provided.c index 87826e54e..9611679de 100644 --- a/emulator-asm/src/c_provided.c +++ b/emulator-asm/src/c_provided.c @@ -467,7 +467,7 @@ int _wait_for_input_avail (uint64_t required_input_bytes) fflush(stderr); exit(-1); } - if (*input_written_address > required_input_bytes) + if (*input_written_address >= required_input_bytes) { // Sync input shared memory if (msync((void *)INPUT_ADDR, MAX_INPUT_SIZE, MS_SYNC) != 0) { diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index 1c43ce8ef..9f69df980 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -76,6 +76,7 @@ impl AsmResources { verbose_mode: proofman_common::VerboseMode, with_hints: bool, mpi_broadcast_fn: Option, + init_rom: bool, ) -> Result { #[cfg(all(target_os = "linux", target_arch = "x86_64"))] let asm_shmem_mt = MTShMemReader::new(local_rank, base_port, unlock_mapped_memory)?; @@ -137,6 +138,10 @@ impl AsmResources { (builder.build().expect("Failed to build HintsProcessor"), hints_file) }; + if init_rom { + hints_processor.set_has_rom_sm(true); + } + (Some(Arc::new(Mutex::new(ZiskStream::new(hints_processor)))), Some(hints_sink)) } else { (None, None) diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 699080c73..58e1be025 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -194,6 +194,8 @@ impl ProverEngine for AsmProver { }) as Arc) -> Result<()> + Send + Sync> }); + let init_rom = !is_distributed && world_rank == 0; + let asm_resources = AsmResources::new( local_rank, base_port, @@ -201,6 +203,7 @@ impl ProverEngine for AsmProver { verbose_mode, elf.with_hints(), mpi_broadcast_fn, + init_rom, )?; self.n_setups.fetch_add(1, Ordering::SeqCst); From acea339d1e1f709396615234e7f8c1f2546ffa40 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 10 Mar 2026 12:13:21 +0000 Subject: [PATCH 753/782] AsmResources reset input stream --- executor/src/asm_resources.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index 9f69df980..23e0d54f8 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -194,6 +194,7 @@ impl AsmResources { hints_stream.lock().unwrap().reset(); self.hints_stream_initialized.store(false, Ordering::SeqCst); } + self.inputs_shmem_writer.reset(); } pub fn config(&self) -> &AsmResourcesConfig { From 8ce4262a17e7d6a3bd659442c3059c5601c2ba8a Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 10 Mar 2026 12:38:21 +0000 Subject: [PATCH 754/782] Adding read_input for native to work as new input stream --- examples/aggregation/guest_agg/src/main.rs | 4 +- zisk-contracts/ZiskVerifier.sol | 2 +- ziskos/entrypoint/src/io.rs | 5 ++ ziskos/entrypoint/src/lib.rs | 81 ++++++++++++++-------- 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/examples/aggregation/guest_agg/src/main.rs b/examples/aggregation/guest_agg/src/main.rs index 472b7d5fc..aebbcc3a9 100644 --- a/examples/aggregation/guest_agg/src/main.rs +++ b/examples/aggregation/guest_agg/src/main.rs @@ -9,13 +9,13 @@ fn main() { let proof2 = ziskos::io::read_proof(); // Verify the first proof - let valid_proof1 = ziskos::verify_zisk_proof(&proof1); + let valid_proof1 = ziskos::io::verify_zisk_proof(&proof1); if !valid_proof1 { panic!("Proof 1 verification failed"); } // Verify the second proof - let valid_proof2 = ziskos::verify_zisk_proof(&proof2); + let valid_proof2 = ziskos::io::verify_zisk_proof(&proof2); if !valid_proof2 { panic!("Proof 2 verification failed"); } diff --git a/zisk-contracts/ZiskVerifier.sol b/zisk-contracts/ZiskVerifier.sol index 55e0ea77f..2debb8e1d 100644 --- a/zisk-contracts/ZiskVerifier.sol +++ b/zisk-contracts/ZiskVerifier.sol @@ -19,7 +19,7 @@ contract ZiskVerifier is PlonkVerifier, IZiskVerifier { } function getRootCVadcopFinal() external pure returns (uint64[4] memory) { - return [uint64(18197808752607027641), uint64(10557759893038425521), uint64(2118750178470058523), uint64(17544832608316643878)]; + return [uint64(9211010158316595036), uint64(7055235338110277438), uint64(2391371252028311145), uint64(10691781997660262077)]; } // Modulus zkSNARK diff --git a/ziskos/entrypoint/src/io.rs b/ziskos/entrypoint/src/io.rs index 3677c443c..8d9deaebf 100644 --- a/ziskos/entrypoint/src/io.rs +++ b/ziskos/entrypoint/src/io.rs @@ -79,3 +79,8 @@ pub fn write(buf: &[u8]) { set_output(i, val); } } + +pub fn verify_zisk_proof(zisk_proof: &[u8]) -> bool { + let (proof, vk) = zisk_proof.split_at(zisk_proof.len() - 32); + zisk_verifier::verify_vadcop_final_proof(proof, vk) +} diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 796253e5d..4a2b112b1 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -76,14 +76,20 @@ macro_rules! entrypoint { #[allow(unused_imports)] use crate::ziskos_definitions::ziskos_config::*; -/// Pointer to the current position in the input buffer. +/// Initial offset for input reading. +/// zkvm: 8 bytes offset due to INPUT_ADDR memory layout +/// native: 0 bytes offset (file starts at position 0) #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -static mut INPUT_POS: usize = 8; +const INPUT_INITIAL_OFFSET: usize = 8; +#[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] +const INPUT_INITIAL_OFFSET: usize = 0; + +/// Pointer to the current position in the input buffer/file. +static mut INPUT_POS: usize = INPUT_INITIAL_OFFSET; /// Reset the input position to the beginning. -#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] pub fn read_reset() { - unsafe { INPUT_POS = 8 }; + unsafe { INPUT_POS = INPUT_INITIAL_OFFSET }; } /// Read a slice directly from INPUT_ADDR without copying (zero-copy). @@ -92,7 +98,7 @@ pub fn read_reset() { /// Use this when you want to deserialize directly without an intermediate copy. /// The INPUT_POS is advanced after this call. #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -pub fn read_slice_zerocopy<'a>() -> &'a [u8] { +pub(crate) fn read_slice_zerocopy<'a>() -> &'a [u8] { // SAFETY: Single threaded, so nothing else can touch INPUT_POS while we're working. let input_pos = unsafe { INPUT_POS }; let addr = (INPUT_ADDR as usize) + input_pos; @@ -112,56 +118,76 @@ pub fn read_slice_zerocopy<'a>() -> &'a [u8] { // Update input position: move past length (8 bytes) + data (8-byte aligned) unsafe { INPUT_POS = input_pos + 8 + aligned_len }; - // Return slice pointing directly to the input data (zero-copy) - unsafe { core::slice::from_raw_parts(data_addr as *const u8, len) } -} - -#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -pub(crate) fn read_input() -> Vec { - let vec = read_slice_zerocopy().to_vec(); + let data_slice = unsafe { core::slice::from_raw_parts(data_addr as *const u8, len) }; #[cfg(zisk_hints_debug)] { - let start_bytes = &vec[..vec.len().min(64)]; - let ellipsis = if vec.len() > 64 { "..." } else { "" }; + let start_bytes = &data_slice[..data_slice.len().min(64)]; + let ellipsis = if data_slice.len() > 64 { "..." } else { "" }; hint_log(format!( "hint_input_data (input_data: {:x?}{} , input_data_len: {}", start_bytes, ellipsis, - vec.len() + data_slice.len() )); } - vec + data_slice +} + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +pub(crate) fn read_input() -> Vec { + read_slice_zerocopy().to_vec() } #[cfg(not(all(target_os = "zkvm", target_vendor = "zisk")))] pub(crate) fn read_input() -> Vec { - use std::{fs::File, io::Read}; + use std::{ + fs::File, + io::{Read, Seek, SeekFrom}, + }; + + let input_pos = unsafe { INPUT_POS }; let mut file = File::open("build/input.bin").expect("Error opening input file at: build/input.bin"); - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).unwrap(); + + // Seek to the current position + file.seek(SeekFrom::Start(input_pos as u64)).expect("Failed to seek in input file"); + + // Read the 8-byte length prefix + let mut len_bytes = [0u8; 8]; + file.read_exact(&mut len_bytes).expect("Failed to read length prefix from input file"); + let len = u64::from_le_bytes(len_bytes) as usize; + + // Read the actual data + let mut data = vec![0u8; len]; + file.read_exact(&mut data).expect("Failed to read data from input file"); + + // Advance INPUT_POS: 8 bytes for length + 8-byte aligned data + let aligned_len = (len + 7) & !0x7; + unsafe { + INPUT_POS = input_pos + 8 + aligned_len; + } #[cfg(zisk_hints)] unsafe { - hint_input_data(buffer.as_ptr(), buffer.len()); + hint_input_data(data.as_ptr(), data.len()); } #[cfg(zisk_hints_debug)] { - let start_bytes = &buffer[..buffer.len().min(64)]; - let ellipsis = if buffer.len() > 64 { "..." } else { "" }; + let start_bytes = &data[..data.len().min(64)]; + let ellipsis = if data.len() > 64 { "..." } else { "" }; hint_log(format!( - "hint_input_data (input_data: {:x?}{} , input_data_len: {}", + "hint_input_data (input_data: {:x?}{} , input_data_len: {})", start_bytes, ellipsis, - buffer.len() + data.len() )); } - buffer + data } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] @@ -372,8 +398,3 @@ mod ziskos { //core::arch::global_asm!(include_str!("dma/inputcpy.s")); core::arch::global_asm!(include_str!("dma/memset.s")); } - -pub fn verify_zisk_proof(zisk_proof: &[u8]) -> bool { - let (proof, vk) = zisk_proof.split_at(zisk_proof.len() - 32); - zisk_verifier::verify_vadcop_final_proof(proof, vk) -} From 609ca6f34f28780661f5891f4ed5e2e61efbb6cd Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Tue, 10 Mar 2026 16:33:52 +0000 Subject: [PATCH 755/782] Simplification ZiskIO --- common/src/io/stdin/zisk_stdin.rs | 43 ++++++++++++ examples/Cargo.lock | 52 +++++++------- examples/aggregation/host/src/main.rs | 2 +- examples/big-program/guest/src/main.rs | 2 +- examples/big-program/host/build.rs | 2 +- examples/big-program/host/src/main.rs | 4 +- examples/multiple-programs/guest/Cargo.toml | 2 +- examples/multiple-programs/guest_2/Cargo.toml | 2 +- examples/multiple-programs/host/Cargo.toml | 2 +- examples/multiple-programs/host/src/main.rs | 6 +- examples/sha-hasher/host/bin/compressed.rs | 2 +- examples/sha-hasher/host/bin/execute.rs | 2 +- examples/sha-hasher/host/bin/plonk.rs | 2 +- examples/sha-hasher/host/bin/prove.rs | 2 +- .../sha-hasher/host/bin/verify-constraints.rs | 2 +- examples/sha-hasher/host/bin/ziskemu.rs | 2 +- examples/sha-hasher/host/build.rs | 2 +- examples/sha-hasher/host/src/main.rs | 2 +- executor/src/asm_resources.rs | 1 - executor/src/emu_rust.rs | 5 +- sdk/src/prover/asm.rs | 70 +++++++++++-------- sdk/src/ziskemu.rs | 2 +- 22 files changed, 130 insertions(+), 81 deletions(-) diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index 825563996..48b7dde4b 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -192,4 +192,47 @@ impl ZiskStdin { ZiskStdin::from_file(uri.as_str()) } } + + // Inherent methods that delegate to ZiskIO trait + // This allows using these methods without importing the trait + + /// Read a value from the buffer. + pub fn read_bytes(&self) -> Vec { + ZiskIO::read_bytes(self) + } + + /// Read a slice of bytes from the buffer. + pub fn read_slice(&self, slice: &mut [u8]) { + ZiskIO::read_slice(self, slice) + } + + /// Read bytes into the provided buffer. + pub fn read_into(&self, buffer: &mut [u8]) { + ZiskIO::read_into(self, buffer) + } + + /// Read and deserialize a value from the buffer. + pub fn read(&self) -> Result { + ZiskIO::read(self) + } + + /// Write a serialized value to the buffer. + pub fn write(&self, data: &T) { + ZiskIO::write(self, data) + } + + /// Write a slice of bytes to the buffer. + pub fn write_slice(&self, data: &[u8]) { + ZiskIO::write_slice(self, data) + } + + /// Write proof + pub fn write_proof(&self, proof: &[u8]) { + ZiskIO::write_proof(self, proof) + } + + /// Save to file + pub fn save(&self, path: &Path) -> Result<()> { + ZiskIO::save(self, path) + } } diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 015c59244..ce3c3c79f 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -1125,32 +1125,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "fibonacci-guest" -version = "0.1.0" -dependencies = [ - "byteorder", - "ziskos", -] - -[[package]] -name = "fibonacci-guest-2" -version = "0.1.0" -dependencies = [ - "byteorder", - "ziskos", -] - -[[package]] -name = "fibonacci-host" -version = "0.1.0" -dependencies = [ - "anyhow", - "serde", - "sha2", - "zisk-sdk", -] - [[package]] name = "fields" version = "0.16.0" @@ -1823,6 +1797,32 @@ dependencies = [ "itoa", ] +[[package]] +name = "multiple-program-guest" +version = "0.1.0" +dependencies = [ + "byteorder", + "ziskos", +] + +[[package]] +name = "multiple-program-guest-2" +version = "0.1.0" +dependencies = [ + "byteorder", + "ziskos", +] + +[[package]] +name = "multiple-program-host" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "sha2", + "zisk-sdk", +] + [[package]] name = "named-sem" version = "0.2.3" diff --git a/examples/aggregation/host/src/main.rs b/examples/aggregation/host/src/main.rs index 552b39e09..f0a393c28 100644 --- a/examples/aggregation/host/src/main.rs +++ b/examples/aggregation/host/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; +use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskStdin}; pub const ELF: ElfBinary = include_elf!("guest"); pub const ELF2: ElfBinary = include_elf!("guest-agg"); diff --git a/examples/big-program/guest/src/main.rs b/examples/big-program/guest/src/main.rs index f59e1c169..9957a2d87 100644 --- a/examples/big-program/guest/src/main.rs +++ b/examples/big-program/guest/src/main.rs @@ -6,7 +6,7 @@ ziskos::entrypoint!(main); fn main() { // Get zero-copy slice directly from INPUT_ADDR (no RAM allocation!) - let data_bytes = ziskos::read_input_slice(); + let data_bytes = ziskos::io::read_input_slice(); // Reinterpret bytes as &[u64] - still zero-copy let data: &[u64] = unsafe { diff --git a/examples/big-program/host/build.rs b/examples/big-program/host/build.rs index df415660c..d9218b4c6 100644 --- a/examples/big-program/host/build.rs +++ b/examples/big-program/host/build.rs @@ -1,5 +1,5 @@ use std::path::PathBuf; -use zisk_sdk::{build_program, ZiskIO, ZiskStdin}; +use zisk_sdk::{build_program, ZiskStdin}; fn main() { build_program("../guest"); diff --git a/examples/big-program/host/src/main.rs b/examples/big-program/host/src/main.rs index e9b71001e..ea0f12e21 100644 --- a/examples/big-program/host/src/main.rs +++ b/examples/big-program/host/src/main.rs @@ -1,6 +1,6 @@ use anyhow::Result; use std::path::PathBuf; -use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; +use zisk_sdk::{include_elf, ElfBinary, ProverClient, ZiskStdin}; pub const ELF: ElfBinary = include_elf!("big-program-guest"); @@ -27,7 +27,7 @@ fn main() -> Result<()> { .build() .unwrap(); - let (pk, vkey) = client.setup(&ELF)?; + let (pk, _vkey) = client.setup(&ELF)?; // Execute the program using the `ProverClient.execute` method, without generating a proof. let result = client.execute(&pk, stdin.clone())?; diff --git a/examples/multiple-programs/guest/Cargo.toml b/examples/multiple-programs/guest/Cargo.toml index 9b0c3ab8b..b9fc0e0f4 100644 --- a/examples/multiple-programs/guest/Cargo.toml +++ b/examples/multiple-programs/guest/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "fibonacci-guest" +name = "multiple-program-guest" version = "0.1.0" edition = "2021" diff --git a/examples/multiple-programs/guest_2/Cargo.toml b/examples/multiple-programs/guest_2/Cargo.toml index 1953a492a..e0d7a89e2 100644 --- a/examples/multiple-programs/guest_2/Cargo.toml +++ b/examples/multiple-programs/guest_2/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "fibonacci-guest-2" +name = "multiple-program-guest-2" version = "0.1.0" edition = "2021" diff --git a/examples/multiple-programs/host/Cargo.toml b/examples/multiple-programs/host/Cargo.toml index 3d32922b9..98db47fda 100644 --- a/examples/multiple-programs/host/Cargo.toml +++ b/examples/multiple-programs/host/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "fibonacci-host" +name = "multiple-program-host" version = "0.1.0" edition = "2021" diff --git a/examples/multiple-programs/host/src/main.rs b/examples/multiple-programs/host/src/main.rs index b8f0d502d..de8493274 100644 --- a/examples/multiple-programs/host/src/main.rs +++ b/examples/multiple-programs/host/src/main.rs @@ -1,8 +1,8 @@ use anyhow::Result; -use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; +use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskStdin}; -pub const ELF: ElfBinary = include_elf!("fibonacci-guest"); -pub const ELF2: ElfBinary = include_elf!("fibonacci-guest-2"); +pub const ELF: ElfBinary = include_elf!("multiple-program-guest"); +pub const ELF2: ElfBinary = include_elf!("multiple-program-guest-2"); fn main() -> Result<()> { println!("Starting ZisK Prover Client...\n"); diff --git a/examples/sha-hasher/host/bin/compressed.rs b/examples/sha-hasher/host/bin/compressed.rs index 8508e5be8..9d5db0ccb 100644 --- a/examples/sha-hasher/host/bin/compressed.rs +++ b/examples/sha-hasher/host/bin/compressed.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; +use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskStdin}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/execute.rs b/examples/sha-hasher/host/bin/execute.rs index 05a599209..8575054e1 100644 --- a/examples/sha-hasher/host/bin/execute.rs +++ b/examples/sha-hasher/host/bin/execute.rs @@ -1,6 +1,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -use zisk_sdk::{include_elf, ElfBinary, ProverClient, ZiskIO, ZiskStdin}; +use zisk_sdk::{include_elf, ElfBinary, ProverClient, ZiskStdin}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/plonk.rs b/examples/sha-hasher/host/bin/plonk.rs index bbbf54a87..c6f253fde 100644 --- a/examples/sha-hasher/host/bin/plonk.rs +++ b/examples/sha-hasher/host/bin/plonk.rs @@ -1,6 +1,6 @@ use anyhow::Result; use zisk_sdk::{ - include_elf, ElfBinary, ProverClient, ZiskIO, ZiskProofWithPublicValues, ZiskStdin, + include_elf, ElfBinary, ProverClient, ZiskProofWithPublicValues, ZiskStdin, }; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/prove.rs b/examples/sha-hasher/host/bin/prove.rs index 70734438b..d1254aa4a 100644 --- a/examples/sha-hasher/host/bin/prove.rs +++ b/examples/sha-hasher/host/bin/prove.rs @@ -2,7 +2,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use zisk_sdk::{ - include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskProof, ZiskProofWithPublicValues, + include_elf, ElfBinary, ProofOpts, ProverClient, ZiskProof, ZiskProofWithPublicValues, ZiskPublics, ZiskStdin, }; diff --git a/examples/sha-hasher/host/bin/verify-constraints.rs b/examples/sha-hasher/host/bin/verify-constraints.rs index e5d988f3d..7fe165784 100644 --- a/examples/sha-hasher/host/bin/verify-constraints.rs +++ b/examples/sha-hasher/host/bin/verify-constraints.rs @@ -1,7 +1,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use zisk_sdk::{elf_path, ElfBinaryFromFile, ProverClient, ZiskIO, ZiskStdin}; +use zisk_sdk::{elf_path, ElfBinaryFromFile, ProverClient, ZiskStdin}; #[derive(Serialize, Deserialize, Debug)] struct Output { diff --git a/examples/sha-hasher/host/bin/ziskemu.rs b/examples/sha-hasher/host/bin/ziskemu.rs index 7c2a8d6ee..37f4c8205 100644 --- a/examples/sha-hasher/host/bin/ziskemu.rs +++ b/examples/sha-hasher/host/bin/ziskemu.rs @@ -1,6 +1,6 @@ use anyhow::Result; use std::path::PathBuf; -use zisk_sdk::{elf_path, ziskemu, ElfBinaryFromFile, EmuOptions, ZiskIO, ZiskStdin}; +use zisk_sdk::{elf_path, ziskemu, ElfBinaryFromFile, EmuOptions, ZiskStdin}; fn main() -> Result<()> { let elf_path = elf_path!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/build.rs b/examples/sha-hasher/host/build.rs index 7c4531e6b..ecc7c594a 100644 --- a/examples/sha-hasher/host/build.rs +++ b/examples/sha-hasher/host/build.rs @@ -1,5 +1,5 @@ use std::path::PathBuf; -use zisk_sdk::{build_program, ZiskIO, ZiskStdin}; +use zisk_sdk::{build_program, ZiskStdin}; fn main() { build_program("../guest"); diff --git a/examples/sha-hasher/host/src/main.rs b/examples/sha-hasher/host/src/main.rs index 53e6666a2..bd5139592 100644 --- a/examples/sha-hasher/host/src/main.rs +++ b/examples/sha-hasher/host/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskIO, ZiskStdin}; +use zisk_sdk::{include_elf, ElfBinary, ProofOpts, ProverClient, ZiskStdin}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index 23e0d54f8..64c932563 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -10,7 +10,6 @@ use asm_runner::{MOShMemReader, MTShMemReader, RHShMemReader}; use precompiles_hints::{HintsProcessor, MpiBroadcastFn}; use std::sync::atomic::{AtomicBool, Ordering}; use zisk_common::io::StreamSink; -use zisk_common::io::ZiskIO; use zisk_common::io::ZiskStdin; use zisk_common::io::{StreamProcessor, StreamSource, ZiskStream}; diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index 343492df0..5fbf56b73 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -5,10 +5,7 @@ use fields::PrimeField64; use proofman_common::ProofCtx; use proofman_util::{timer_start_info, timer_stop_and_log_info}; use rayon::prelude::*; -use zisk_common::{ - io::{ZiskIO, ZiskStdin}, - ChunkId, EmuTrace, ExecutorStatsHandle, -}; +use zisk_common::{io::ZiskStdin, ChunkId, EmuTrace, ExecutorStatsHandle}; use zisk_core::ZiskRom; use ziskemu::{EmuOptions, ZiskEmulator}; diff --git a/sdk/src/prover/asm.rs b/sdk/src/prover/asm.rs index 58e1be025..c63600470 100644 --- a/sdk/src/prover/asm.rs +++ b/sdk/src/prover/asm.rs @@ -35,7 +35,6 @@ impl ZiskBackend for Asm { pub struct AsmProver { pub(crate) core_prover: AsmCoreProver, - pub n_setups: AtomicU64, } impl AsmProver { @@ -73,13 +72,13 @@ impl AsmProver { logging_config, )?; - Ok(Self { core_prover, n_setups: AtomicU64::new(0) }) + Ok(Self { core_prover }) } pub fn new_verifier(proving_key: PathBuf, proving_key_snark: PathBuf) -> Result { let core_prover = AsmCoreProver::new_verifier(proving_key, proving_key_snark)?; - Ok(Self { core_prover, n_setups: AtomicU64::new(0) }) + Ok(Self { core_prover }) } } @@ -115,15 +114,15 @@ impl ProverEngine for AsmProver { let world_rank = self.core_prover.rank_info.world_rank; let local_rank = self.core_prover.rank_info.local_rank; let n_processes = self.core_prover.rank_info.n_processes; - let is_distributed = self.core_prover.is_distributed; - let unlock_mapped_memory = self.core_prover.unlock_mapped_memory; - let asm_out_file = self.core_prover.asm_out_file; - let verbose_mode = self.core_prover.verbose; + let is_distributed = self.core_prover.asm_info.is_distributed; + let unlock_mapped_memory = self.core_prover.asm_info.unlock_mapped_memory; + let asm_out_file = self.core_prover.asm_info.asm_out_file; + let verbose_mode = self.core_prover.asm_info.verbose; let rank_info = self.core_prover.rank_info.clone(); let base_port = Some(AsmServices::port_base_offset( - self.core_prover.base_port, + self.core_prover.asm_info.base_port, n_processes, - self.n_setups.load(Ordering::SeqCst), + self.core_prover.asm_info.n_setups.load(Ordering::SeqCst), )); let rv2zk = Riscv2zisk::new(elf.elf()); @@ -142,7 +141,7 @@ impl ProverEngine for AsmProver { let asm_rh_path = default_cache_path.join(asm_rh_filename); if check_paths_exist(&asm_mt_path).is_err() || check_paths_exist(&asm_rh_path).is_err() { - if self.core_prover.no_auto_setup { + if self.core_prover.asm_info.no_auto_setup { return Err(anyhow::anyhow!( "Assembly files not found for ELF {}. Force ROM setup is enabled, but assembly files are still missing. Please ensure that the assembly generation process has been completed successfully.", elf.name() @@ -161,7 +160,7 @@ impl ProverEngine for AsmProver { elf.name(), &output_path, elf.with_hints(), - self.core_prover.verbose != VerboseMode::Info, + self.core_prover.asm_info.verbose != VerboseMode::Info, )?; timer_stop_and_log_info!(ROM_SETUP); tracing::info!("<<< ROM SETUP complete - Assembly files cached for future use"); @@ -206,7 +205,7 @@ impl ProverEngine for AsmProver { init_rom, )?; - self.n_setups.fetch_add(1, Ordering::SeqCst); + self.core_prover.asm_info.n_setups.fetch_add(1, Ordering::SeqCst); Ok(( ZiskProgramPK { @@ -368,15 +367,20 @@ impl ProverEngine for AsmProver { } } +pub struct AsmInfo { + pub is_distributed: bool, + pub base_port: Option, + pub unlock_mapped_memory: bool, + pub asm_out_file: bool, + pub verbose: VerboseMode, + pub no_auto_setup: bool, + pub n_setups: AtomicU64, +} + pub struct AsmCoreProver { backend: ProverBackend, rank_info: RankInfo, - is_distributed: bool, - base_port: Option, - unlock_mapped_memory: bool, - asm_out_file: bool, - verbose: VerboseMode, - no_auto_setup: bool, + asm_info: AsmInfo, } impl AsmCoreProver { @@ -449,12 +453,15 @@ impl AsmCoreProver { Ok(Self { backend: core, rank_info, - base_port, - unlock_mapped_memory, - asm_out_file, - verbose: verbose.into(), - no_auto_setup, - is_distributed, + asm_info: AsmInfo { + is_distributed, + base_port, + unlock_mapped_memory, + asm_out_file, + verbose: verbose.into(), + no_auto_setup, + n_setups: AtomicU64::new(0), + }, }) } @@ -465,12 +472,15 @@ impl AsmCoreProver { Ok(Self { backend: core_prover, rank_info: RankInfo { world_rank: 0, local_rank: 0, n_processes: 1 }, - base_port: None, - unlock_mapped_memory: false, - asm_out_file: false, - verbose: VerboseMode::Info, - no_auto_setup: false, - is_distributed: false, + asm_info: AsmInfo { + is_distributed: false, + base_port: None, + unlock_mapped_memory: false, + asm_out_file: false, + verbose: VerboseMode::Info, + no_auto_setup: false, + n_setups: AtomicU64::new(0), + }, }) } } diff --git a/sdk/src/ziskemu.rs b/sdk/src/ziskemu.rs index 050a276ce..5abb660d4 100644 --- a/sdk/src/ziskemu.rs +++ b/sdk/src/ziskemu.rs @@ -1,5 +1,5 @@ use std::fmt::Write; -use zisk_common::io::{ZiskIO, ZiskStdin}; +use zisk_common::io::ZiskStdin; use zisk_common::ElfBinaryLike; use zisk_core::Riscv2zisk; pub use ziskemu::EmuOptions; From 2dec8afa747c6d79d230092b10ebfd6a23a9843c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Taul=C3=A9=20Buxadera?= Date: Wed, 11 Mar 2026 10:23:37 +0100 Subject: [PATCH 756/782] RefCell to Mutex (#840) --- emulator-asm/asm-runner/src/inputs_shmem.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/emulator-asm/asm-runner/src/inputs_shmem.rs b/emulator-asm/asm-runner/src/inputs_shmem.rs index a77d10095..80752e9d9 100644 --- a/emulator-asm/asm-runner/src/inputs_shmem.rs +++ b/emulator-asm/asm-runner/src/inputs_shmem.rs @@ -1,5 +1,4 @@ -use std::cell::RefCell; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use named_sem::NamedSemaphore; use zisk_common::{io::StreamSink, reinterpret_vec}; @@ -12,9 +11,9 @@ use crate::{ use anyhow::Result; pub struct InputsShmemWriter { - writer: RefCell, + writer: Mutex, control_writer: Arc, - sem_avails: RefCell>, + sem_avails: Mutex>, } unsafe impl Send for InputsShmemWriter {} @@ -49,14 +48,14 @@ impl InputsShmemWriter { .collect::>>()?; Ok(Self { - writer: RefCell::new(writer), + writer: Mutex::new(writer), control_writer, - sem_avails: RefCell::new(sem_avails), + sem_avails: Mutex::new(sem_avails), }) } pub fn write_input(&self, inputs: &[u8]) -> Result<()> { - self.writer.borrow_mut().write_at(8, inputs)?; + self.writer.lock().unwrap().write_at(8, inputs)?; self.control_writer.inc_inputs_size(inputs.len()); self.notify_all_services()?; @@ -64,7 +63,7 @@ impl InputsShmemWriter { } pub fn append_input(&self, inputs: &[u8]) -> Result<()> { - self.writer.borrow_mut().append_input(inputs)?; + self.writer.lock().unwrap().append_input(inputs)?; self.control_writer.inc_inputs_size(inputs.len()); self.notify_all_services()?; @@ -73,14 +72,14 @@ impl InputsShmemWriter { /// Notify all ASM services that new input data is available fn notify_all_services(&self) -> Result<()> { - for sem in self.sem_avails.borrow_mut().iter_mut() { + for sem in self.sem_avails.lock().unwrap().iter_mut() { sem.post()?; } Ok(()) } pub fn reset(&self) { - let mut writer = self.writer.borrow_mut(); + let mut writer = self.writer.lock().unwrap(); writer.reset(); writer .append_input(&0u64.to_le_bytes()) @@ -88,7 +87,7 @@ impl InputsShmemWriter { self.control_writer.reset(); // Drain all the semaphore signals from all services - for sem in self.sem_avails.borrow_mut().iter_mut() { + for sem in self.sem_avails.lock().unwrap().iter_mut() { while sem.try_wait().is_ok() {} } } From 7ca12f4a2a174382a083a502e429a37326711e74 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Wed, 11 Mar 2026 10:01:27 +0000 Subject: [PATCH 757/782] Cargo.lock --- Cargo.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 970ead1ea..5d03dd337 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" dependencies = [ "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", @@ -1469,7 +1469,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" dependencies = [ "cfg-if", "num-bigint", @@ -2774,7 +2774,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" dependencies = [ "colored", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", @@ -3158,7 +3158,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" dependencies = [ "bincode", "blake3", @@ -3193,7 +3193,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" dependencies = [ "bincode", "borsh", @@ -3224,7 +3224,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" dependencies = [ "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "itoa", @@ -3237,7 +3237,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" dependencies = [ "proc-macro2", "quote", @@ -3247,7 +3247,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" dependencies = [ "crossbeam-channel", "tracing", @@ -3256,7 +3256,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" dependencies = [ "bincode", "bytemuck", @@ -3268,7 +3268,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" dependencies = [ "bytemuck", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", @@ -3831,9 +3831,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -4296,9 +4296,9 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", @@ -5720,7 +5720,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" dependencies = [ "colored", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", From 3d9dd48ec9a802be3550b62c22f26bb3000358b0 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 11 Mar 2026 10:48:20 +0000 Subject: [PATCH 758/782] Fix drain_to_writer input_data lock --- ziskos/entrypoint/src/hints/hint_buffer.rs | 23 +++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/ziskos/entrypoint/src/hints/hint_buffer.rs b/ziskos/entrypoint/src/hints/hint_buffer.rs index a2aaf3160..c52fc8934 100644 --- a/ziskos/entrypoint/src/hints/hint_buffer.rs +++ b/ziskos/entrypoint/src/hints/hint_buffer.rs @@ -168,40 +168,35 @@ impl HintBuffer { let mut write_buf = Vec::with_capacity(flush_threshold); 'drain: loop { // Get chunk of hints to write from HintBuffer (under lock) - let chunk: Bytes = { + let chunk: Bytes = loop { let mut g = self.precompiles.lock().unwrap(); let mut i = self.input_data.lock().unwrap(); let closed = *self.closed.lock().unwrap(); if g.commit_pos == 0 && i.commit_pos == 0 && !closed { - // Wait until there's data to write or buffer is closed + drop(i); // Release input_data lock before waiting g = self.not_empty.wait(g).unwrap(); + continue; // Re-acquire both locks in the next iteration } - // If buffer is empty and closed, we're done, we can exit the drain loop if g.commit_pos == 0 && i.commit_pos == 0 && closed { break 'drain; } - // Take the committed chunk of hints to write - if g.commit_pos > 0 { + break if g.commit_pos > 0 { let n = g.commit_pos; g.commit_pos = 0; g.buf.split_to(n).freeze() } else { let n = i.commit_pos.min(MAX_INPUT_DATA_CHUNK); - i.commit_pos = i.commit_pos.saturating_sub(n); - + i.commit_pos -= n; let input_chunk = i.buf.split_to(n); - - let hint_input_header: [u8; 8] = - (((HINT_INPUT as u64) << 32) | (input_chunk.len() as u64)).to_le_bytes(); - - let mut chunk = BytesMut::with_capacity(HEADER_LEN + input_chunk.len()); - chunk.extend_from_slice(&hint_input_header); + let header = (((HINT_INPUT as u64) << 32) | n as u64).to_le_bytes(); + let mut chunk = BytesMut::with_capacity(HEADER_LEN + n); + chunk.extend_from_slice(&header); chunk.unsplit(input_chunk); chunk.freeze() - } + }; }; // Write hints from chunk without holding the lock From 5addb962b42573b280ae354db1e12bc792ccd431 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Wed, 11 Mar 2026 11:13:01 +0000 Subject: [PATCH 759/782] Assign keccak in round robin to minimize unbalancing between workers --- executor/src/air_classifier.rs | 13 ++++++++++++- executor/src/planner.rs | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/executor/src/air_classifier.rs b/executor/src/air_classifier.rs index fc5d378fc..b004c591f 100644 --- a/executor/src/air_classifier.rs +++ b/executor/src/air_classifier.rs @@ -4,7 +4,8 @@ //! centralizing the scattered `*_AIR_IDS.contains()` checks throughout the executor. use zisk_pil::{ - INPUT_DATA_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, ZISK_AIRGROUP_ID, + INPUT_DATA_AIR_IDS, KECCAKF_AIR_IDS, MAIN_AIR_IDS, MEM_AIR_IDS, ROM_AIR_IDS, ROM_DATA_AIR_IDS, + ZISK_AIRGROUP_ID, }; /// Helper for classifying AIR instances by their ID. @@ -26,6 +27,11 @@ impl AirClassifier { air_id == ROM_AIR_IDS[0] } + #[inline] + pub fn is_keccakf(air_id: usize) -> bool { + air_id == KECCAKF_AIR_IDS[0] + } + /// Checks if the plan targets the ROM instance that requires special handling. /// /// ROM instances need to be added to the proof context with first partition assignment. @@ -34,6 +40,11 @@ impl AirClassifier { airgroup_id == ZISK_AIRGROUP_ID && Self::is_rom(air_id) } + #[inline] + pub fn is_keccakf_instance(airgroup_id: usize, air_id: usize) -> bool { + airgroup_id == ZISK_AIRGROUP_ID && Self::is_keccakf(air_id) + } + /// Checks if the AIR ID corresponds to a memory-related state machine. /// /// Memory-related AIRs include MEM, ROM_DATA, and INPUT_DATA. diff --git a/executor/src/planner.rs b/executor/src/planner.rs index b2a5debae..9d8d856cf 100644 --- a/executor/src/planner.rs +++ b/executor/src/planner.rs @@ -135,6 +135,9 @@ impl InstancePlanner { let global_id = if AirClassifier::is_rom_instance(plan.airgroup_id, plan.air_id) { pctx.add_instance_assign_first_process(plan.airgroup_id, plan.air_id) .expect("Failed to add ROM instance") + } else if AirClassifier::is_keccakf_instance(plan.airgroup_id, plan.air_id) { + pctx.add_instance_assign(plan.airgroup_id, plan.air_id) + .expect("Failed to add KeccakF instance") } else { match plan.instance_type { InstanceType::Instance => pctx From 5f6816d77a1320b15abfee3087334555ebc6e88e Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Wed, 11 Mar 2026 11:53:36 +0000 Subject: [PATCH 760/782] Minor improving ziskemu with sdk --- common/src/types.rs | 10 ++++++++++ emulator-asm/asm-runner/src/inputs_shmem.rs | 6 +----- examples/sha-hasher/host/bin/plonk.rs | 4 +--- examples/sha-hasher/host/bin/ziskemu.rs | 18 ++++++++++-------- sdk/src/lib.rs | 9 +-------- sdk/src/ziskemu.rs | 2 +- 6 files changed, 24 insertions(+), 25 deletions(-) diff --git a/common/src/types.rs b/common/src/types.rs index e323dc45b..741bcc7a0 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -269,12 +269,14 @@ pub trait ElfBinaryLike { fn elf(&self) -> &[u8]; fn name(&self) -> &str; fn with_hints(&self) -> bool; + fn path(&self) -> Option; } pub struct ElfBinaryFromFile { pub elf: Vec, pub name: String, pub with_hints: bool, + pub path: Option, } impl ElfBinaryFromFile { @@ -285,6 +287,7 @@ impl ElfBinaryFromFile { elf: elf_bin, name: elf.file_stem().unwrap().to_str().unwrap().to_string(), with_hints, + path: Some(elf.to_str().unwrap().to_string()), }) } } @@ -299,12 +302,16 @@ impl ElfBinaryLike for ElfBinaryFromFile { fn with_hints(&self) -> bool { self.with_hints } + fn path(&self) -> Option { + self.path.clone() + } } pub struct ElfBinary { pub elf: &'static [u8], pub name: &'static str, pub with_hints: bool, + pub path: Option<&'static str>, } impl ElfBinaryLike for ElfBinary { @@ -317,6 +324,9 @@ impl ElfBinaryLike for ElfBinary { fn with_hints(&self) -> bool { self.with_hints } + fn path(&self) -> Option { + self.path.map(|s| s.to_string()) + } } #[derive(Default, Debug, Clone)] diff --git a/emulator-asm/asm-runner/src/inputs_shmem.rs b/emulator-asm/asm-runner/src/inputs_shmem.rs index 80752e9d9..8a83274be 100644 --- a/emulator-asm/asm-runner/src/inputs_shmem.rs +++ b/emulator-asm/asm-runner/src/inputs_shmem.rs @@ -47,11 +47,7 @@ impl InputsShmemWriter { }) .collect::>>()?; - Ok(Self { - writer: Mutex::new(writer), - control_writer, - sem_avails: Mutex::new(sem_avails), - }) + Ok(Self { writer: Mutex::new(writer), control_writer, sem_avails: Mutex::new(sem_avails) }) } pub fn write_input(&self, inputs: &[u8]) -> Result<()> { diff --git a/examples/sha-hasher/host/bin/plonk.rs b/examples/sha-hasher/host/bin/plonk.rs index c6f253fde..a34731218 100644 --- a/examples/sha-hasher/host/bin/plonk.rs +++ b/examples/sha-hasher/host/bin/plonk.rs @@ -1,7 +1,5 @@ use anyhow::Result; -use zisk_sdk::{ - include_elf, ElfBinary, ProverClient, ZiskProofWithPublicValues, ZiskStdin, -}; +use zisk_sdk::{include_elf, ElfBinary, ProverClient, ZiskProofWithPublicValues, ZiskStdin}; pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); diff --git a/examples/sha-hasher/host/bin/ziskemu.rs b/examples/sha-hasher/host/bin/ziskemu.rs index 37f4c8205..2d0b52d29 100644 --- a/examples/sha-hasher/host/bin/ziskemu.rs +++ b/examples/sha-hasher/host/bin/ziskemu.rs @@ -1,12 +1,9 @@ use anyhow::Result; -use std::path::PathBuf; -use zisk_sdk::{elf_path, ziskemu, ElfBinaryFromFile, EmuOptions, ZiskStdin}; +use zisk_sdk::{include_elf, ziskemu, ElfBinary, EmuOptions, ZiskStdin}; -fn main() -> Result<()> { - let elf_path = elf_path!("sha-hasher-guest"); - println!("Loading ELF binary from path: {}", elf_path); - let elf = ElfBinaryFromFile::new(&PathBuf::from(elf_path), false)?; +pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); +fn main() -> Result<()> { let current_dir = std::env::current_dir()?; let stdin = ZiskStdin::from_file(current_dir.join("sha-hasher/host/tmp/verify_constraints_input.bin"))?; @@ -15,8 +12,13 @@ fn main() -> Result<()> { println!("Input prepared: {} iterations", n); println!("Running ZisK Emulator..."); - let emu_options = EmuOptions { stats: true, ..EmuOptions::default() }; - ziskemu(&elf, stdin, &emu_options)?; + let emu_options = EmuOptions { + stats: true, + read_symbols: true, + top_roi_detail: true, + ..EmuOptions::default() + }; + ziskemu(&ELF, stdin, &emu_options)?; println!("ZisK Emulator completed successfully!"); Ok(()) diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 52f8801a5..df13db768 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -28,14 +28,7 @@ macro_rules! include_elf { elf: include_bytes!(env!(concat!("ZISK_ELF_", $arg))), name: $arg, with_hints: WITH_HINTS, + path: Some(env!(concat!("ZISK_ELF_", $arg))), } }}; } - -/// Macro to get the ELF file path at compile time -#[macro_export] -macro_rules! elf_path { - ($arg:literal) => {{ - env!(concat!("ZISK_ELF_", $arg)) - }}; -} diff --git a/sdk/src/ziskemu.rs b/sdk/src/ziskemu.rs index 5abb660d4..6dc9784e2 100644 --- a/sdk/src/ziskemu.rs +++ b/sdk/src/ziskemu.rs @@ -20,7 +20,7 @@ pub fn ziskemu( let inputs = stdin.read_bytes(); - let options = EmuOptions { log_output: true, ..options.clone() }; + let options = EmuOptions { elf: elf.path(), ..options.clone() }; let result = ZiskEmulator::process_rom(&zisk_rom, &inputs, &options, callback); match result { Ok(result) => { From 763a5f3ac3f31faed48c38d46a7d29ded3c012cb Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Wed, 11 Mar 2026 16:08:44 +0000 Subject: [PATCH 761/782] Fixing read from stdin --- common/src/io/stdin/file.rs | 49 +++++++++++++++++++++++++++++---- common/src/io/stdin/memory.rs | 52 ++++++++++++++++++++++++++++++----- 2 files changed, 88 insertions(+), 13 deletions(-) diff --git a/common/src/io/stdin/file.rs b/common/src/io/stdin/file.rs index 2350212f8..8edf42f37 100644 --- a/common/src/io/stdin/file.rs +++ b/common/src/io/stdin/file.rs @@ -33,6 +33,26 @@ impl ZiskFileStdin { let file = File::open(&path_buf)?; Ok(ZiskFileStdin { path: path_buf, reader: Mutex::new(BufReader::new(file)) }) } + + fn read_raw_data(&self) -> std::io::Result> { + let mut reader = self.reader.lock().unwrap(); + + let mut len_bytes = [0u8; 8]; + reader.read_exact(&mut len_bytes)?; + let len = usize::from_le_bytes(len_bytes); + + let mut data = vec![0u8; len]; + reader.read_exact(&mut data)?; + + let total_len = 8 + len; + let padding = (8 - (total_len % 8)) % 8; + if padding > 0 { + let mut padding_bytes = vec![0u8; padding]; + reader.read_exact(&mut padding_bytes)?; + } + + Ok(data) + } } impl ZiskIO for ZiskFileStdin { @@ -41,18 +61,35 @@ impl ZiskIO for ZiskFileStdin { } fn read_slice(&self, slice: &mut [u8]) { - let mut reader = self.reader.lock().unwrap(); - reader.read_exact(slice).expect("Failed to read slice"); + let data = self.read_raw_data().expect("Failed to read slice"); + assert_eq!( + slice.len(), + data.len(), + "Slice length mismatch: expected {}, got {}", + data.len(), + slice.len() + ); + slice.copy_from_slice(&data); } fn read_into(&self, buffer: &mut [u8]) { - let mut reader = self.reader.lock().unwrap(); - reader.read_exact(buffer).expect("Failed to read into buffer"); + let data = self.read_raw_data().expect("Failed to read into buffer"); + assert_eq!( + buffer.len(), + data.len(), + "Buffer length mismatch: expected {}, got {}", + data.len(), + buffer.len() + ); + buffer.copy_from_slice(&data); } fn read(&self) -> Result { - let mut reader = self.reader.lock().unwrap(); - bincode::deserialize_from(&mut *reader) + let data = self + .read_raw_data() + .map_err(|e| anyhow::anyhow!("Failed to read data from file: {}", e))?; + + bincode::deserialize(&data) .map_err(|e| anyhow::anyhow!("Failed to deserialize from file: {}", e)) } diff --git a/common/src/io/stdin/memory.rs b/common/src/io/stdin/memory.rs index 2721d8dc6..dc71a0f5b 100644 --- a/common/src/io/stdin/memory.rs +++ b/common/src/io/stdin/memory.rs @@ -30,25 +30,63 @@ impl ZiskMemoryStdin { } } +impl ZiskMemoryStdin { + fn read_raw_data(&self) -> std::io::Result> { + let mut cursor = self.cursor.lock().unwrap(); + + let mut len_bytes = [0u8; 8]; + cursor.read_exact(&mut len_bytes)?; + let len = usize::from_le_bytes(len_bytes); + + let mut data = vec![0u8; len]; + cursor.read_exact(&mut data)?; + + let total_len = 8 + len; + let padding = (8 - (total_len % 8)) % 8; + if padding > 0 { + let mut padding_bytes = vec![0u8; padding]; + cursor.read_exact(&mut padding_bytes)?; + } + + Ok(data) + } +} + impl ZiskIO for ZiskMemoryStdin { fn read_bytes(&self) -> Vec { - // Return all the data self.data.lock().unwrap().clone() } fn read_slice(&self, slice: &mut [u8]) { - let mut cursor = self.cursor.lock().unwrap(); - cursor.read_exact(slice).expect("Failed to read slice from memory"); + let data = self.read_raw_data().expect("Failed to read slice from memory"); + assert_eq!( + slice.len(), + data.len(), + "Slice length mismatch: expected {}, got {}", + data.len(), + slice.len() + ); + slice.copy_from_slice(&data); } fn read_into(&self, buffer: &mut [u8]) { - let mut cursor = self.cursor.lock().unwrap(); - cursor.read_exact(buffer).expect("Failed to read into buffer from memory"); + let data = self.read_raw_data().expect("Failed to read into buffer from memory"); + assert_eq!( + buffer.len(), + data.len(), + "Buffer length mismatch: expected {}, got {}", + data.len(), + buffer.len() + ); + buffer.copy_from_slice(&data); } fn read(&self) -> Result { - let mut cursor = self.cursor.lock().unwrap(); - bincode::deserialize_from(&mut *cursor) + let data = self + .read_raw_data() + .map_err(|e| anyhow::anyhow!("Failed to read data from memory: {}", e))?; + + bincode::deserialize(&data) .map_err(|e| anyhow::anyhow!("Failed to deserialize from memory: {}", e)) } From 28a88edee946ec232db23f7af2cdbe02584262d6 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Wed, 11 Mar 2026 16:37:56 +0000 Subject: [PATCH 762/782] Adding read_raw_bytes to ZiskStdin --- common/src/io/stdin/file.rs | 18 ++++---------- common/src/io/stdin/memory.rs | 18 ++++---------- common/src/io/stdin/null.rs | 7 +++++- common/src/io/stdin/zisk_stdin.rs | 39 +++++++++++++++---------------- executor/src/asm_resources.rs | 2 +- executor/src/emu_rust.rs | 2 +- sdk/src/ziskemu.rs | 2 +- 7 files changed, 38 insertions(+), 50 deletions(-) diff --git a/common/src/io/stdin/file.rs b/common/src/io/stdin/file.rs index 8edf42f37..a2c6a139f 100644 --- a/common/src/io/stdin/file.rs +++ b/common/src/io/stdin/file.rs @@ -56,10 +56,14 @@ impl ZiskFileStdin { } impl ZiskIO for ZiskFileStdin { - fn read_bytes(&self) -> Vec { + fn read_raw_bytes(&self) -> Vec { fs::read(&self.path).expect("Could not read inputs file") } + fn read_bytes(&self) -> Vec { + self.read_raw_data().expect("Failed to read into buffer") + } + fn read_slice(&self, slice: &mut [u8]) { let data = self.read_raw_data().expect("Failed to read slice"); assert_eq!( @@ -72,18 +76,6 @@ impl ZiskIO for ZiskFileStdin { slice.copy_from_slice(&data); } - fn read_into(&self, buffer: &mut [u8]) { - let data = self.read_raw_data().expect("Failed to read into buffer"); - assert_eq!( - buffer.len(), - data.len(), - "Buffer length mismatch: expected {}, got {}", - data.len(), - buffer.len() - ); - buffer.copy_from_slice(&data); - } - fn read(&self) -> Result { let data = self .read_raw_data() diff --git a/common/src/io/stdin/memory.rs b/common/src/io/stdin/memory.rs index dc71a0f5b..8a88b3764 100644 --- a/common/src/io/stdin/memory.rs +++ b/common/src/io/stdin/memory.rs @@ -53,10 +53,14 @@ impl ZiskMemoryStdin { } impl ZiskIO for ZiskMemoryStdin { - fn read_bytes(&self) -> Vec { + fn read_raw_bytes(&self) -> Vec { self.data.lock().unwrap().clone() } + fn read_bytes(&self) -> Vec { + self.read_raw_data().expect("Failed to read into buffer from memory") + } + fn read_slice(&self, slice: &mut [u8]) { let data = self.read_raw_data().expect("Failed to read slice from memory"); assert_eq!( @@ -69,18 +73,6 @@ impl ZiskIO for ZiskMemoryStdin { slice.copy_from_slice(&data); } - fn read_into(&self, buffer: &mut [u8]) { - let data = self.read_raw_data().expect("Failed to read into buffer from memory"); - assert_eq!( - buffer.len(), - data.len(), - "Buffer length mismatch: expected {}, got {}", - data.len(), - buffer.len() - ); - buffer.copy_from_slice(&data); - } - fn read(&self) -> Result { let data = self .read_raw_data() diff --git a/common/src/io/stdin/null.rs b/common/src/io/stdin/null.rs index 474c3b788..b039eb374 100644 --- a/common/src/io/stdin/null.rs +++ b/common/src/io/stdin/null.rs @@ -8,11 +8,16 @@ use std::path::Path; pub struct ZiskNullStdin; impl ZiskIO for ZiskNullStdin { + fn read_raw_bytes(&self) -> Vec { + Vec::new() + } + fn read_bytes(&self) -> Vec { Vec::new() } + fn read_slice(&self, _slice: &mut [u8]) {} - fn read_into(&self, _buffer: &mut [u8]) {} + fn read(&self) -> Result { Err(anyhow::anyhow!("NullStdin does not support reading")) } diff --git a/common/src/io/stdin/zisk_stdin.rs b/common/src/io/stdin/zisk_stdin.rs index 48b7dde4b..8cb40e03f 100644 --- a/common/src/io/stdin/zisk_stdin.rs +++ b/common/src/io/stdin/zisk_stdin.rs @@ -5,15 +5,14 @@ use std::path::Path; use std::sync::Arc; pub trait ZiskIO: Send + Sync { + fn read_raw_bytes(&self) -> Vec; + /// Read a value from the buffer. fn read_bytes(&self) -> Vec; /// Read a slice of bytes from the buffer. fn read_slice(&self, slice: &mut [u8]); - /// Read bytes into the provided buffer. - fn read_into(&self, buffer: &mut [u8]); - /// Read and deserialize a value from the buffer. fn read(&self) -> Result; @@ -36,6 +35,14 @@ pub enum ZiskIOVariant { } impl ZiskIO for ZiskIOVariant { + fn read_raw_bytes(&self) -> Vec { + match self { + ZiskIOVariant::File(file_stdin) => file_stdin.read_raw_bytes(), + ZiskIOVariant::Null(null_stdin) => null_stdin.read_raw_bytes(), + ZiskIOVariant::Memory(memory_stdin) => memory_stdin.read_raw_bytes(), + } + } + fn read_bytes(&self) -> Vec { match self { ZiskIOVariant::File(file_stdin) => file_stdin.read_bytes(), @@ -52,14 +59,6 @@ impl ZiskIO for ZiskIOVariant { } } - fn read_into(&self, buffer: &mut [u8]) { - match self { - ZiskIOVariant::File(file_stdin) => file_stdin.read_into(buffer), - ZiskIOVariant::Null(null_stdin) => null_stdin.read_into(buffer), - ZiskIOVariant::Memory(memory_stdin) => memory_stdin.read_into(buffer), - } - } - fn read(&self) -> Result { match self { ZiskIOVariant::File(file_stdin) => file_stdin.read(), @@ -107,6 +106,10 @@ pub struct ZiskStdin { } impl ZiskIO for ZiskStdin { + fn read_raw_bytes(&self) -> Vec { + self.io.read_raw_bytes() + } + fn read_bytes(&self) -> Vec { self.io.read_bytes() } @@ -115,10 +118,6 @@ impl ZiskIO for ZiskStdin { self.io.read_slice(slice) } - fn read_into(&self, buffer: &mut [u8]) { - self.io.read_into(buffer) - } - fn read(&self) -> Result { self.io.read() } @@ -196,6 +195,11 @@ impl ZiskStdin { // Inherent methods that delegate to ZiskIO trait // This allows using these methods without importing the trait + /// Read raw bytes + pub fn read_raw_bytes(&self) -> Vec { + ZiskIO::read_raw_bytes(self) + } + /// Read a value from the buffer. pub fn read_bytes(&self) -> Vec { ZiskIO::read_bytes(self) @@ -206,11 +210,6 @@ impl ZiskStdin { ZiskIO::read_slice(self, slice) } - /// Read bytes into the provided buffer. - pub fn read_into(&self, buffer: &mut [u8]) { - ZiskIO::read_into(self, buffer) - } - /// Read and deserialize a value from the buffer. pub fn read(&self) -> Result { ZiskIO::read(self) diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index 64c932563..85b636927 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -201,7 +201,7 @@ impl AsmResources { } pub fn write_input(&self, stdin: &ZiskStdin) -> Result<()> { - let inputs = stdin.read_bytes(); + let inputs = stdin.read_raw_bytes(); self.inputs_shmem_writer.write_input(&inputs) } diff --git a/executor/src/emu_rust.rs b/executor/src/emu_rust.rs index 5fbf56b73..ee848b55c 100644 --- a/executor/src/emu_rust.rs +++ b/executor/src/emu_rust.rs @@ -75,7 +75,7 @@ impl EmulatorRust { stdin: &ZiskStdin, ) -> Vec { // Call emulate with these options - let input_data = stdin.read_bytes(); + let input_data = stdin.read_raw_bytes(); // Settings for the emulator let emu_options = EmuOptions { diff --git a/sdk/src/ziskemu.rs b/sdk/src/ziskemu.rs index 6dc9784e2..513a08706 100644 --- a/sdk/src/ziskemu.rs +++ b/sdk/src/ziskemu.rs @@ -18,7 +18,7 @@ pub fn ziskemu( let callback = None::>; - let inputs = stdin.read_bytes(); + let inputs = stdin.read_raw_bytes(); let options = EmuOptions { elf: elf.path(), ..options.clone() }; let result = ZiskEmulator::process_rom(&zisk_rom, &inputs, &options, callback); From 73323b7384ddcc48b2a394ab000d60d7fbc431ad Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Wed, 11 Mar 2026 21:14:32 +0100 Subject: [PATCH 763/782] add new emdedded allocators, add --steps and --with-progress options to ziskemu --- emulator-asm/Makefile | 2 +- emulator/src/emu.rs | 53 ++++++++++-- emulator/src/emu_options.rs | 10 +++ state-machines/mem-cpp/cpp/Makefile | 6 +- .../mem-cpp/cpp/mem_align_counter.cpp | 8 ++ state-machines/mem-cpp/cpp/mem_config.hpp | 5 +- state-machines/mem-cpp/cpp/mem_context.cpp | 17 ++-- state-machines/mem-cpp/cpp/mem_context.hpp | 8 ++ .../mem-cpp/cpp/mem_count_and_plan.cpp | 16 ++-- state-machines/mem-cpp/cpp/mem_counter.cpp | 29 ++++++- state-machines/mem-cpp/cpp/mem_counter.hpp | 2 +- state-machines/mem-cpp/cpp/mem_planner.cpp | 8 ++ ziskos/entrypoint/Cargo.toml | 9 ++ ziskos/entrypoint/src/alloc/alloc.rs | 58 +++++++++++++ ziskos/entrypoint/src/alloc/bump.rs | 39 +++++++++ ziskos/entrypoint/src/alloc/embedded_buddy.rs | 38 ++++++++ .../entrypoint/src/alloc/embedded_dlmalloc.rs | 86 +++++++++++++++++++ ziskos/entrypoint/src/alloc/embedded_lla.rs | 35 ++++++++ ziskos/entrypoint/src/alloc/embedded_llff.rs | 22 +++++ ziskos/entrypoint/src/alloc/embedded_talc.rs | 15 ++++ ziskos/entrypoint/src/alloc/embedded_tlfs.rs | 38 ++++++++ ziskos/entrypoint/src/alloc/kernel_heap.rs | 7 ++ ziskos/entrypoint/src/alloc/mod.rs | 37 ++++++++ ziskos/entrypoint/src/dma.rs | 26 ++++++ ziskos/entrypoint/src/dma/memset.s | 6 ++ ziskos/entrypoint/src/lib.rs | 75 ++++++++-------- 26 files changed, 593 insertions(+), 62 deletions(-) create mode 100644 ziskos/entrypoint/src/alloc/alloc.rs create mode 100644 ziskos/entrypoint/src/alloc/bump.rs create mode 100644 ziskos/entrypoint/src/alloc/embedded_buddy.rs create mode 100644 ziskos/entrypoint/src/alloc/embedded_dlmalloc.rs create mode 100644 ziskos/entrypoint/src/alloc/embedded_lla.rs create mode 100644 ziskos/entrypoint/src/alloc/embedded_llff.rs create mode 100644 ziskos/entrypoint/src/alloc/embedded_talc.rs create mode 100644 ziskos/entrypoint/src/alloc/embedded_tlfs.rs create mode 100644 ziskos/entrypoint/src/alloc/kernel_heap.rs create mode 100644 ziskos/entrypoint/src/alloc/mod.rs diff --git a/emulator-asm/Makefile b/emulator-asm/Makefile index 8cbe6a122..73fcfd429 100644 --- a/emulator-asm/Makefile +++ b/emulator-asm/Makefile @@ -59,7 +59,7 @@ build/zisk.ld: mkdir -p build ld --verbose 2>/dev/null | \ awk '/^=+$$/{found++; next} found==1{print}' | \ - sed '/\/DISCARD\//i\ .my_region 0x40000000 (NOLOAD) : { . = . + 0x1090000000; }' \ + sed '/\/DISCARD\//i\ .zisk_region 0x40000000 (NOLOAD) : { . = . + 0x890000000; }' \ > build/zisk.ld # Compile the final executable diff --git a/emulator/src/emu.rs b/emulator/src/emu.rs index 29e525b64..99a8ca7fc 100644 --- a/emulator/src/emu.rs +++ b/emulator/src/emu.rs @@ -1454,8 +1454,14 @@ impl<'a> Emu<'a> { /// Run the whole program, fast #[inline(always)] pub fn run_fast(&mut self, options: &EmuOptions) { - while !self.ctx.inst_ctx.end && (self.ctx.inst_ctx.step < options.max_steps) { - self.step_fast(); + if options.with_progress { + while !self.ctx.inst_ctx.end && (self.ctx.inst_ctx.step < options.max_steps) { + self.step_fast_with_progress(); + } + } else { + while !self.ctx.inst_ctx.end && (self.ctx.inst_ctx.step < options.max_steps) { + self.step_fast(); + } } // Detect and report error @@ -1467,6 +1473,35 @@ impl<'a> Emu<'a> { } } + #[inline(always)] + pub fn step_fast_with_progress(&mut self) { + let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); + if self.ctx.inst_ctx.step & 0xFF_FFFF == 0 { + let pc = self.ctx.inst_ctx.pc; + println!( + "running 0x{pc:08x} MS:{} {}", + self.ctx.inst_ctx.step >> 20, + instruction.verbose + ); + } + self.source_a(instruction); + self.source_b(instruction); + if instruction.input_size > 0 { + self.ctx.inst_ctx.extended_arg = instruction.jmp_offset1; + } else { + self.ctx.inst_ctx.extended_arg = 0; + } + (instruction.func)(&mut self.ctx.inst_ctx); + self.store_c(instruction); + + // #[cfg(feature = "sp")] + // self.set_sp(instruction); + + self.set_pc(instruction); + self.ctx.inst_ctx.end = instruction.end; + self.ctx.inst_ctx.step += 1; + } + /// Performs one single step of the emulation #[inline(always)] pub fn step_fast(&mut self) { @@ -1660,7 +1695,10 @@ impl<'a> Emu<'a> { // Call run_fast if only essential work is needed if options.is_fast() { - return self.run_fast(options); + self.run_fast(options); + if options.steps { + println!("STEPS: {}", self.ctx.inst_ctx.step); + } } if options.generate_minimal_traces { let par_emu_options = @@ -1926,8 +1964,13 @@ impl<'a> Emu<'a> { let pc = self.ctx.inst_ctx.pc; let instruction = self.rom.get_instruction(self.ctx.inst_ctx.pc); - let pc = self.ctx.inst_ctx.pc; - + if options.with_progress && self.ctx.inst_ctx.step & 0xF_FFFF == 0 { + println!( + "running 0x{pc:08x} MS:{} {}", + self.ctx.inst_ctx.step >> 20, + instruction.verbose + ); + } // Build the 'a' register value based on the source specified by the current instruction self.source_a(instruction); diff --git a/emulator/src/emu_options.rs b/emulator/src/emu_options.rs index 1c1205105..174ca00ee 100644 --- a/emulator/src/emu_options.rs +++ b/emulator/src/emu_options.rs @@ -129,6 +129,12 @@ pub struct EmuOptions { pub disasm: Option, #[clap(long, value_name = "MAX_INPUT_MEM", default_value = "134217728")] // 128 MiB pub max_input_mem: u64, + /// In mode fast, without stats, show coverage steps consumed without stats. + #[clap(long, default_value = "false")] + pub steps: bool, + /// In mode fast, without stats, show executing lines each 16Msteps. + #[clap(long, default_value = "false")] + pub with_progress: bool, } impl Default for EmuOptions { @@ -168,6 +174,8 @@ impl Default for EmuOptions { top_roi_filter: false, disasm: None, max_input_mem: MAX_INPUT_SIZE, + steps: false, + with_progress: false, } } } @@ -205,6 +213,8 @@ impl fmt::Display for EmuOptions { writeln!(f, "TOP_ROI_FILTER: {:?}", self.top_roi_filter)?; writeln!(f, "DISASM: {:?}", self.disasm)?; writeln!(f, "MAX_INPUT_MEM: {:?}", self.max_input_mem)?; + writeln!(f, "STEPS: {:?}", self.steps)?; + writeln!(f, "WITH_PROGRESS: {:?}", self.with_progress)?; Ok(()) } } diff --git a/state-machines/mem-cpp/cpp/Makefile b/state-machines/mem-cpp/cpp/Makefile index 9c12e3405..dcae195f2 100644 --- a/state-machines/mem-cpp/cpp/Makefile +++ b/state-machines/mem-cpp/cpp/Makefile @@ -14,12 +14,16 @@ SRCS := tools.cpp api.cpp mem_count_and_plan.cpp immutable_mem_planner.cpp \ OBJS := $(addprefix $(OUT_DIR)/, $(SRCS:.cpp=.o)) +# Find all header files to track dependencies +HEADERS := $(wildcard *.hpp) + all: $(OUT_DIR)/$(TARGET) $(OUT_DIR)/$(TARGET): $(OBJS) ar rcs $@ $^ -$(OUT_DIR)/%.o: %.cpp +# Each object file depends on all header files to trigger rebuild when headers change +$(OUT_DIR)/%.o: %.cpp $(HEADERS) mkdir -p $(OUT_DIR) $(CXX) $(CXXFLAGS) -c $< -o $@ diff --git a/state-machines/mem-cpp/cpp/mem_align_counter.cpp b/state-machines/mem-cpp/cpp/mem_align_counter.cpp index 08923c01e..b329b3547 100644 --- a/state-machines/mem-cpp/cpp/mem_align_counter.cpp +++ b/state-machines/mem-cpp/cpp/mem_align_counter.cpp @@ -22,9 +22,17 @@ void MemAlignCounter::execute() uint32_t chunk_id = 0; int64_t elapsed_us = 0; #ifdef MEM_CONTEXT_SEM + #ifdef CHUNK_STATS while ((chunk = context->get_chunk(MAX_THREADS, chunk_id, elapsed_us)) != nullptr) #else + while ((chunk = context->get_chunk(MAX_THREADS, chunk_id)) != nullptr) + #endif + #else + #ifdef CHUNK_STATS while ((chunk = context->get_chunk(chunk_id, elapsed_us)) != nullptr) + #else + while ((chunk = context->get_chunk(chunk_id)) != nullptr) + #endif #endif { execute_chunk(chunk_id, chunk->data, chunk->count); diff --git a/state-machines/mem-cpp/cpp/mem_config.hpp b/state-machines/mem-cpp/cpp/mem_config.hpp index d1dad8e10..135c8b200 100644 --- a/state-machines/mem-cpp/cpp/mem_config.hpp +++ b/state-machines/mem-cpp/cpp/mem_config.hpp @@ -48,10 +48,13 @@ #define OFFSET_BITS (25 + 4 - THREAD_BITS) // 4 bits (3 bits for 6 pages, 1 bit security) #define OFFSET_PAGE_SHIFT_BITS (OFFSET_BITS - 3) +#define MAX_SLOT_GB 6 #define ADDR_SLOT_BITS 5 +#define ADDR_SLOT_BYTES 4 #define ADDR_SLOT_SIZE (1 << ADDR_SLOT_BITS) #define ADDR_SLOT_MASK (0xFFFFFFFF << ADDR_SLOT_BITS) -#define ADDR_SLOTS ((1024 * 1024 * 24) / MAX_THREADS) +#define ADDR_TOTAL_SLOTS ((((size_t)MAX_SLOT_GB) << 30) / (ADDR_SLOT_SIZE * ADDR_SLOT_BYTES)) +#define ADDR_SLOTS (ADDR_TOTAL_SLOTS / MAX_THREADS) #define ADDR_SLOTS_SIZE (ADDR_SLOT_SIZE * ADDR_SLOTS) #define TIME_US_BY_CHUNK 173 diff --git a/state-machines/mem-cpp/cpp/mem_context.cpp b/state-machines/mem-cpp/cpp/mem_context.cpp index dbce22d8b..2bbb03c55 100644 --- a/state-machines/mem-cpp/cpp/mem_context.cpp +++ b/state-machines/mem-cpp/cpp/mem_context.cpp @@ -23,9 +23,14 @@ void MemContext::clear () { } #ifdef MEM_CONTEXT_SEM +#ifdef CHUNK_STATS const MemChunk *MemContext::get_chunk(uint32_t thread_id, uint32_t chunk_id, int64_t &elapsed_us) { +#else +const MemChunk *MemContext::get_chunk(uint32_t thread_id, uint32_t chunk_id) { +#endif + #ifdef CHUNK_STATS uint64_t t_ini = get_usec(); - + #endif // semaphore used for synchronization, means that a new chunk data is available while (sem_wait(&semaphores[thread_id]) < 0) { if (errno != EINTR) { @@ -37,28 +42,30 @@ const MemChunk *MemContext::get_chunk(uint32_t thread_id, uint32_t chunk_id, int #ifdef COUNT_CHUNK_STATS #ifdef CHUNK_STATS elapsed_us = (int64_t)chunks_us[chunk_id] - (int64_t)get_usec(); - #else - elapsed_us = 0; #endif #endif return &chunks[chunk_id]; } if (chunks_completed.load(std::memory_order_acquire)) { + #ifdef CHUNK_STATS elapsed_us = get_usec() - t_ini; + #endif return nullptr; } assert(false); } #else +#ifdef CHUNK_STATS const MemChunk *MemContext::get_chunk(uint32_t chunk_id, int64_t &elapsed_us) { +#else +const MemChunk *MemContext::get_chunk(uint32_t chunk_id) { +#endif if (chunk_id < chunks_count.load(std::memory_order_acquire)) { #ifdef COUNT_CHUNK_STATS #ifdef CHUNK_STATS elapsed_us = (int64_t)chunks_us[chunk_id] - (int64_t)get_usec(); - #else - elapsed_us = 0; #endif #endif return &chunks[chunk_id]; diff --git a/state-machines/mem-cpp/cpp/mem_context.hpp b/state-machines/mem-cpp/cpp/mem_context.hpp index 27681eff0..022554119 100644 --- a/state-machines/mem-cpp/cpp/mem_context.hpp +++ b/state-machines/mem-cpp/cpp/mem_context.hpp @@ -47,9 +47,17 @@ class MemContext { #endif void clear (); #ifdef MEM_CONTEXT_SEM +#ifdef CHUNK_STATS const MemChunk *get_chunk(uint32_t thread_id, uint32_t chunk_id, int64_t &elapsed_us); #else + const MemChunk *get_chunk(uint32_t thread_id, uint32_t chunk_id); +#endif +#else +#ifdef CHUNK_STATS const MemChunk *get_chunk(uint32_t chunk_id, int64_t &elapsed_us); +#else + const MemChunk *get_chunk(uint32_t chunk_id); +#endif #endif MemContext(); ~MemContext(); diff --git a/state-machines/mem-cpp/cpp/mem_count_and_plan.cpp b/state-machines/mem-cpp/cpp/mem_count_and_plan.cpp index 5778a978c..ca1902570 100644 --- a/state-machines/mem-cpp/cpp/mem_count_and_plan.cpp +++ b/state-machines/mem-cpp/cpp/mem_count_and_plan.cpp @@ -45,8 +45,9 @@ void MemCountAndPlan::clear() { context->clear(); } void MemCountAndPlan::prepare() { +#ifdef MEM_STATS_ACTIVE uint64_t init = get_usec(); - +#endif // Clear existing workers to avoid memory leaks if prepare() called multiple times for (auto* worker : count_workers) { delete worker; @@ -70,7 +71,9 @@ void MemCountAndPlan::prepare() { for (int i = 0; i < MAX_MEM_PLANNERS; ++i) { plan_workers.emplace_back(i+1, RAM_ROWS, RAM_ADDR, RAM_SIZE_MB); } +#ifdef MEM_STATS_ACTIVE t_prepare_us = get_usec() - init; +#endif } void MemCountAndPlan::add_chunk(MemCountersBusData *chunk_data, uint32_t chunk_size) { @@ -92,7 +95,9 @@ void MemCountAndPlan::count_phase() { clock_gettime(CLOCK_REALTIME, &start_time); #endif // MEM_STATS_ACTIVE +#ifdef MEM_STATS_ACTIVE uint64_t init = t_init_us = get_usec(); +#endif std::vector threads; context->init(); @@ -124,9 +129,9 @@ void MemCountAndPlan::count_phase() { wait_mem_align_counters(); +#ifdef MEM_STATS_ACTIVE t_count_us = (uint32_t) (get_usec() - init); -#ifdef MEM_STATS_ACTIVE // Add stats for count phase struct timespec end_time; clock_gettime(CLOCK_REALTIME, &end_time); @@ -145,9 +150,9 @@ void MemCountAndPlan::plan_phase() { // Get start time for stats struct timespec start_time; clock_gettime(CLOCK_REALTIME, &start_time); -#endif // MEM_STATS_ACTIVE uint64_t init = get_usec(); +#endif // MEM_STATS_ACTIVE std::vector threads; plan_threads.emplace_back([this](){ quick_mem_planner->generate_locators(count_workers, context->locators);}); @@ -163,8 +168,9 @@ void MemCountAndPlan::plan_phase() { for (auto& t : plan_threads) { t.join(); } +#ifdef MEM_STATS_ACTIVE t_plan_us = (uint32_t) (get_usec() - init); - +#endif segments[ROM_ID].clear(); rom_data_planner->collect_segments(segments[ROM_ID]); @@ -332,7 +338,7 @@ void MemCountAndPlan::wait() { void MemCountAndPlan::detach_execute() { count_phase(); plan_phase(); - //stats(); + // stats(); // printf("MemCountAndPlan count(ms):%ld plan(ms):%ld tot(ms):%ld\n", // t_count_us / 1000, t_plan_us / 1000, (t_count_us + t_plan_us) / 1000); } diff --git a/state-machines/mem-cpp/cpp/mem_counter.cpp b/state-machines/mem-cpp/cpp/mem_counter.cpp index c13d839ac..39e4195f2 100644 --- a/state-machines/mem-cpp/cpp/mem_counter.cpp +++ b/state-machines/mem-cpp/cpp/mem_counter.cpp @@ -40,15 +40,24 @@ MemCounter::~MemCounter() { } void MemCounter::execute() { +#ifdef COUNT_CHUNK_STATS uint64_t init_us = get_usec(); int64_t elapsed_us = 0; - +#endif const MemChunk *chunk = #ifdef MEM_CONTEXT_SEM +#ifdef CHUNK_STATS context->get_chunk(id, 0, elapsed_us); #else - context->get_chunk(0, elapsed_us); + context->get_chunk(id, 0); +#endif +#else +#ifdef CHUNK_STATS + context->get_chunk(0, elapsed_us); +#else + context->get_chunk(0); +#endif #endif #ifdef COUNT_CHUNK_STATS wait_chunks_us[0] = elapsed_us; @@ -60,15 +69,27 @@ void MemCounter::execute() { chunks_us[0] = get_usec() - start_execute_us; tot_wait_us += elapsed_us > 0 ? elapsed_us : 0; #else + #ifdef CHUNK_STATS tot_wait_us += elapsed_us; #endif + #endif + #ifdef CHUNK_STATS first_chunk_us = get_usec() - init_us; + #endif uint32_t chunk_id = 1; #ifdef MEM_CONTEXT_SEM +#ifdef CHUNK_STATS while ((chunk = context->get_chunk(id, chunk_id, elapsed_us)) != nullptr) #else + while ((chunk = context->get_chunk(id, chunk_id)) != nullptr) +#endif +#else +#ifdef CHUNK_STATS while ((chunk = context->get_chunk(chunk_id, elapsed_us)) != nullptr) +#else + while ((chunk = context->get_chunk(chunk_id)) != nullptr) +#endif #endif { #ifdef COUNT_CHUNK_STATS @@ -80,15 +101,19 @@ void MemCounter::execute() { chunks_us[chunk_id] = get_usec() - start_execute_us; tot_wait_us += elapsed_us > 0 ? elapsed_us : 0; #else + #ifdef CHUNK_STATS tot_wait_us += elapsed_us; #endif + #endif ++chunk_id; } #ifdef COUNT_CHUNK_STATS wait_chunks_us[chunk_id] = elapsed_us; #endif } + #ifdef CHUNK_STATS elapsed_ms = ((get_usec() - init_us) / 1000); + #endif } void MemCounter::execute_chunk(uint32_t chunk_id, const MemCountersBusData *chunk_data, uint32_t chunk_size) { diff --git a/state-machines/mem-cpp/cpp/mem_counter.hpp b/state-machines/mem-cpp/cpp/mem_counter.hpp index e5a3f5a37..9193a1a85 100644 --- a/state-machines/mem-cpp/cpp/mem_counter.hpp +++ b/state-machines/mem-cpp/cpp/mem_counter.hpp @@ -190,7 +190,7 @@ uint32_t MemCounter::get_count_table(uint32_t index) const { uint32_t MemCounter::get_next_slot_pos() { if (free_slot >= ADDR_SLOTS) { std::ostringstream msg; - msg << "ERROR: MemCounter no more free slots on thread" << id; + msg << "ERROR: MemCounter: no free slots left for this thread(" << id << "). Increase MAX_SLOT_GB in state-machines/mem-cpp/cpp/mem_config.hpp and recompile zisk."; throw std::runtime_error(msg.str()); } return (free_slot++) * ADDR_SLOT_SIZE; diff --git a/state-machines/mem-cpp/cpp/mem_planner.cpp b/state-machines/mem-cpp/cpp/mem_planner.cpp index 18a9f06a0..5f3f1b6ac 100644 --- a/state-machines/mem-cpp/cpp/mem_planner.cpp +++ b/state-machines/mem-cpp/cpp/mem_planner.cpp @@ -67,7 +67,9 @@ const MemLocator *MemPlanner::get_next_locator(MemLocators &locators, uint32_t & } void MemPlanner::execute_from_locators(const std::vector &workers, MemLocators &locators, MemSegments &segments) { + #ifdef MEM_PLANNER_STATS uint64_t init = get_usec(); + #endif const MemLocator *locator; uint32_t segment_id = 0; while (true) { @@ -78,7 +80,9 @@ void MemPlanner::execute_from_locators(const std::vector &workers, segments.set(segment_id, current_segment); current_segment = nullptr; } + #ifdef MEM_PLANNER_STATS elapsed = get_usec() - init; + #endif } void MemPlanner::execute_from_locator(const std::vector &workers, uint32_t segment_id, const MemLocator *locator) { @@ -153,7 +157,9 @@ void MemPlanner::update_segment_stats(uint32_t addr_count, uint32_t offset_count #endif void MemPlanner::generate_locators(const std::vector &workers, MemLocators &locators) { + #ifdef MEM_PLANNER_STATS uint64_t init = get_usec(); + #endif rows_available = rows; uint32_t count; uint32_t offset, max_offset; @@ -203,7 +209,9 @@ void MemPlanner::generate_locators(const std::vector &workers, Mem } } locators.set_completed(); + #ifdef MEM_PLANNER_STATS elapsed = get_usec() - init; + #endif } void MemPlanner::get_offset_limits(const std::vector &workers, uint32_t page, uint32_t &first_offset, uint32_t &last_offset) { diff --git a/ziskos/entrypoint/Cargo.toml b/ziskos/entrypoint/Cargo.toml index 41ddb4637..1ab2da7e4 100644 --- a/ziskos/entrypoint/Cargo.toml +++ b/ziskos/entrypoint/Cargo.toml @@ -24,6 +24,10 @@ tiny-keccak = { version = "2.0.0", features = ["keccak"] } serde = { workspace = true, features = ["derive"] } bincode = { workspace = true } zisk-definitions = { path = "../../definitions" } +critical-section = { version = "1.2.0", optional = true } +embedded-alloc = { version = "0.6.0", optional = true } +talc = { version = "4.4.3", features = ["lock_api"], optional = true } +dlmalloc = { version = "0.2", default-features = false, optional = true } sha2 = { workspace = true } fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0", features = [ @@ -45,6 +49,11 @@ ctor = { version = "0.2", optional = true } default = ["user-hints", "inputcpy"] user-hints = ["dep:zisk-common", "dep:bytes", "dep:paste", "dep:once_cell", "dep:ctor", "dep:anyhow", "dep:tokio"] inputcpy = [] +zisk-custom-alloc = [] +zisk-embedded-alloc = ["zisk-embedded-dlmalloc-alloc"] +zisk-embedded-dlmalloc-alloc = ["dep:embedded-alloc", "dep:critical-section", "dep:dlmalloc"] +zisk-embedded-talc-alloc = ["dep:embedded-alloc", "dep:critical-section", "dep:talc"] +zisk-embedded-tlfs-alloc = ["dep:embedded-alloc", "dep:critical-section"] [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ diff --git a/ziskos/entrypoint/src/alloc/alloc.rs b/ziskos/entrypoint/src/alloc/alloc.rs new file mode 100644 index 000000000..0fec67ade --- /dev/null +++ b/ziskos/entrypoint/src/alloc/alloc.rs @@ -0,0 +1,58 @@ +static mut HEAP_POS: usize = 0; +static mut HEAP_TOP: usize = 0; + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +#[no_mangle] +#[warn(dead_code)] +pub unsafe extern "C" fn init_sys_alloc() { + extern "C" { + static _kernel_heap_bottom: u8; + static _kernel_heap_top: u8; + } + + unsafe { + HEAP_POS = &_kernel_heap_bottom as *const u8 as usize; + HEAP_TOP = &_kernel_heap_top as *const u8 as usize; + }; +} + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +#[no_mangle] +#[inline(never)] +pub unsafe extern "C" fn sys_alloc_aligned(bytes: usize, align: usize) -> *mut u8 { + // SAFETY: Single threaded, so nothing else can touch this while we're working. + let mut heap_pos = unsafe { HEAP_POS }; + + let offset = heap_pos & (align - 1); + if offset != 0 { + heap_pos += align - offset; + } + + let ptr = heap_pos as *mut u8; + heap_pos += bytes; + + // Check to make sure heap doesn't collide with SYSTEM memory. + if HEAP_TOP < heap_pos { + panic!("OOM limit of heap with bump allocator"); + } + + unsafe { HEAP_POS = heap_pos }; + + ptr +} + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +use std::ptr; + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +#[no_mangle] +static mut SINK: u64 = 0; + +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +#[no_mangle] +#[inline(never)] +pub unsafe extern "C" fn sys_alloc_log(op: u64, ptr: *mut u8, bytes: usize, align: usize) { + unsafe { + ptr::write_volatile(&raw mut SINK, bytes as u64 + op + (ptr as u64 & 0x02) + align as u64); + } +} diff --git a/ziskos/entrypoint/src/alloc/bump.rs b/ziskos/entrypoint/src/alloc/bump.rs new file mode 100644 index 000000000..0a040fb69 --- /dev/null +++ b/ziskos/entrypoint/src/alloc/bump.rs @@ -0,0 +1,39 @@ +use core::alloc::{GlobalAlloc, Layout}; + +use crate::alloc::{sys_alloc_aligned, sys_alloc_log}; +use crate::ziskos_memcpy; + +#[global_allocator] +pub static HEAP: BumpPointerAlloc = BumpPointerAlloc; + +pub struct BumpPointerAlloc; + +unsafe impl GlobalAlloc for BumpPointerAlloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + sys_alloc_aligned(layout.size(), layout.align()) + // let ptr = sys_alloc_aligned(layout.size(), layout.align()); + // sys_alloc_log(0, ptr, layout.size(), layout.align()); + // ptr + } + + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { + // sys_alloc_log(1, ptr, layout.size(), layout.align()) + // this allocator never deallocates memory + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + self.alloc(layout) + } + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + let new_layout = Layout::from_size_align_unchecked(new_size, layout.align()); + + let new_ptr = self.alloc(new_layout); + + if !new_ptr.is_null() { + let copy_size = layout.size().min(new_size); + ziskos_memcpy!(ptr: new_ptr, ptr, copy_size); + } + + new_ptr + } +} diff --git a/ziskos/entrypoint/src/alloc/embedded_buddy.rs b/ziskos/entrypoint/src/alloc/embedded_buddy.rs new file mode 100644 index 000000000..a0fe557a3 --- /dev/null +++ b/ziskos/entrypoint/src/alloc/embedded_buddy.rs @@ -0,0 +1,38 @@ +use core::alloc::{GlobalAlloc, Layout}; +use critical_section::RawRestoreState; +use embedded_alloc::TlsfHeap as Heap; + +use super::kernel_heap::*; + +#[global_allocator] +static HEAP: EmbeddedAlloc = EmbeddedAlloc; + +static INNER_HEAP: Heap = Heap::empty(); + +struct CriticalSection; +critical_section::set_impl!(CriticalSection); + +unsafe impl critical_section::Impl for CriticalSection { + unsafe fn acquire() -> RawRestoreState {} + unsafe fn release(_token: RawRestoreState) {} +} + +pub fn init() { + unsafe { + let heap_start = &_kernel_heap_bottom as *const u8 as usize; + let heap_size = &_kernel_heap_size as *const u8 as usize; + INNER_HEAP.init(heap_start, heap_size); + } +} + +struct EmbeddedAlloc; + +unsafe impl GlobalAlloc for EmbeddedAlloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + INNER_HEAP.alloc(layout) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + INNER_HEAP.dealloc(ptr, layout) + } +} diff --git a/ziskos/entrypoint/src/alloc/embedded_dlmalloc.rs b/ziskos/entrypoint/src/alloc/embedded_dlmalloc.rs new file mode 100644 index 000000000..32a093354 --- /dev/null +++ b/ziskos/entrypoint/src/alloc/embedded_dlmalloc.rs @@ -0,0 +1,86 @@ +use crate::ziskos::sys_write; +use core::alloc::{GlobalAlloc, Layout}; +use core::ptr::addr_of_mut; +use dlmalloc::{Allocator as DlAllocator, Dlmalloc}; + +use super::kernel_heap::*; + +// Implementar el backend que le da memoria a dlmalloc +struct ZiskSystem; + +unsafe impl DlAllocator for ZiskSystem { + // Equivalente a sbrk — dlmalloc pide más memoria aquí + fn alloc(&self, size: usize) -> (*mut u8, usize, u32) { + unsafe { + // Devuelves un bloque de tu heap reservado + let ptr = BUMP_PTR; + let aligned = (ptr + 7) & !7; + BUMP_PTR = aligned + size; + if BUMP_PTR > BUMP_END { + return (core::ptr::null_mut(), 0, 0); + } + (aligned as *mut u8, size, 0) + } + } + + fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 { + core::ptr::null_mut() // no soportado + } + + fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool { + false // no soportado + } + + fn free(&self, _ptr: *mut u8, _size: usize) -> bool { + false // devolver memoria al sistema — no necesario + } + + fn can_release_part(&self, _flags: u32) -> bool { + false + } + + fn allocates_zeros(&self) -> bool { + false + } + + fn page_size(&self) -> usize { + 4096 + } +} + +static mut BUMP_PTR: usize = 0; +static mut BUMP_END: usize = 0; + +static mut DLMALLOC: Dlmalloc = Dlmalloc::new_with_allocator(ZiskSystem); + +struct Allocator; + +#[global_allocator] +static GLOBAL: Allocator = Allocator; + +unsafe impl core::alloc::GlobalAlloc for Allocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + (*addr_of_mut!(DLMALLOC)).malloc(layout.size(), layout.align()) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + (*addr_of_mut!(DLMALLOC)).free(ptr, layout.size(), layout.align()) + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + (*addr_of_mut!(DLMALLOC)).calloc(layout.size(), layout.align()) + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + (*addr_of_mut!(DLMALLOC)).realloc(ptr, layout.size(), layout.align(), new_size) + } +} + +pub fn init() { + unsafe { + let heap_start = &_kernel_heap_bottom as *const u8 as usize; + let heap_end = &_kernel_heap_top as *const u8 as usize; + BUMP_PTR = heap_start; + BUMP_END = heap_end; + } +} diff --git a/ziskos/entrypoint/src/alloc/embedded_lla.rs b/ziskos/entrypoint/src/alloc/embedded_lla.rs new file mode 100644 index 000000000..7427a3a7c --- /dev/null +++ b/ziskos/entrypoint/src/alloc/embedded_lla.rs @@ -0,0 +1,35 @@ +use core::alloc::{GlobalAlloc, Layout}; +use core::ptr::addr_of_mut; +use linked_list_allocator::Heap; + +use super::kernel_heap::*; + +static mut HEAP: Heap = Heap::empty(); + +struct Allocator; + +#[global_allocator] +static GLOBAL: Allocator = Allocator; + +unsafe impl GlobalAlloc for Allocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + (*addr_of_mut!(HEAP)) + .allocate_first_fit(layout) + .map(|p| p.as_ptr()) + .unwrap_or(core::ptr::null_mut()) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + if let Some(nn) = core::ptr::NonNull::new(ptr) { + (*addr_of_mut!(HEAP)).deallocate(nn, layout); + } + } +} + +pub fn init() { + unsafe { + let heap_start = &_kernel_heap_bottom as *const u8 as usize; + let heap_size = &_kernel_heap_size as *const u8 as usize; + (*addr_of_mut!(HEAP)).init(heap_start as *mut u8, heap_size); + } +} diff --git a/ziskos/entrypoint/src/alloc/embedded_llff.rs b/ziskos/entrypoint/src/alloc/embedded_llff.rs new file mode 100644 index 000000000..efa73b612 --- /dev/null +++ b/ziskos/entrypoint/src/alloc/embedded_llff.rs @@ -0,0 +1,22 @@ +use core::alloc::{GlobalAlloc, Layout}; +use critical_section::RawRestoreState; +use embedded_alloc::LlffHeap as Heap; + +#[global_allocator] +static HEAP: Heap = Heap::empty(); + +struct CriticalSection; +critical_section::set_impl!(CriticalSection); + +unsafe impl critical_section::Impl for CriticalSection { + unsafe fn acquire() -> RawRestoreState {} + unsafe fn release(_token: RawRestoreState) {} +} + +pub fn init() { + unsafe { + let heap_start = &_kernel_heap_bottom as *const u8 as usize; + let heap_size = &_kernel_heap_size as *const u8 as usize; + HEAP.init(heap_start, heap_size) + } +} diff --git a/ziskos/entrypoint/src/alloc/embedded_talc.rs b/ziskos/entrypoint/src/alloc/embedded_talc.rs new file mode 100644 index 000000000..b8bea9f9a --- /dev/null +++ b/ziskos/entrypoint/src/alloc/embedded_talc.rs @@ -0,0 +1,15 @@ +use super::kernel_heap::*; +use core::alloc::{GlobalAlloc, Layout}; +use talc::{ErrOnOom, Talc, Talck}; + +#[global_allocator] +static HEAP: Talck = Talc::new(ErrOnOom).lock(); + +pub fn init() { + unsafe { + let heap_start = &_kernel_heap_bottom as *const u8 as usize; + let heap_size = &_kernel_heap_size as *const u8 as usize; + let heap_span = talc::Span::from_base_size(heap_start as *mut u8, heap_size); + HEAP.lock().claim(heap_span).unwrap(); + } +} diff --git a/ziskos/entrypoint/src/alloc/embedded_tlfs.rs b/ziskos/entrypoint/src/alloc/embedded_tlfs.rs new file mode 100644 index 000000000..a0fe557a3 --- /dev/null +++ b/ziskos/entrypoint/src/alloc/embedded_tlfs.rs @@ -0,0 +1,38 @@ +use core::alloc::{GlobalAlloc, Layout}; +use critical_section::RawRestoreState; +use embedded_alloc::TlsfHeap as Heap; + +use super::kernel_heap::*; + +#[global_allocator] +static HEAP: EmbeddedAlloc = EmbeddedAlloc; + +static INNER_HEAP: Heap = Heap::empty(); + +struct CriticalSection; +critical_section::set_impl!(CriticalSection); + +unsafe impl critical_section::Impl for CriticalSection { + unsafe fn acquire() -> RawRestoreState {} + unsafe fn release(_token: RawRestoreState) {} +} + +pub fn init() { + unsafe { + let heap_start = &_kernel_heap_bottom as *const u8 as usize; + let heap_size = &_kernel_heap_size as *const u8 as usize; + INNER_HEAP.init(heap_start, heap_size); + } +} + +struct EmbeddedAlloc; + +unsafe impl GlobalAlloc for EmbeddedAlloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + INNER_HEAP.alloc(layout) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + INNER_HEAP.dealloc(ptr, layout) + } +} diff --git a/ziskos/entrypoint/src/alloc/kernel_heap.rs b/ziskos/entrypoint/src/alloc/kernel_heap.rs new file mode 100644 index 000000000..abee0a76a --- /dev/null +++ b/ziskos/entrypoint/src/alloc/kernel_heap.rs @@ -0,0 +1,7 @@ +//! Kernel heap symbols defined in the linker script + +extern "C" { + pub static _kernel_heap_bottom: u8; + pub static _kernel_heap_size: u8; + pub static _kernel_heap_top: u8; +} diff --git a/ziskos/entrypoint/src/alloc/mod.rs b/ziskos/entrypoint/src/alloc/mod.rs new file mode 100644 index 000000000..25743448f --- /dev/null +++ b/ziskos/entrypoint/src/alloc/mod.rs @@ -0,0 +1,37 @@ +mod alloc; +pub use alloc::*; + +mod kernel_heap; + +#[cfg(all( + not(feature = "zisk-embedded-alloc"), + not(feature = "zisk-custom-alloc"), + not(feature = "zisk-embedded-dlmalloc-alloc"), + not(feature = "zisk-embedded-talc-alloc"), + not(feature = "zisk-embedded-tlfs-alloc") +))] +pub mod bump; + +#[cfg(any(feature = "zisk-embedded-alloc", feature = "zisk-embedded-dlmalloc-alloc"))] +pub mod embedded_dlmalloc; + +#[cfg(feature = "zisk-embedded-talc-alloc")] +pub mod embedded_talc; + +#[cfg(feature = "zisk-embedded-tlfs-alloc")] +pub mod embedded_tlfs; + +#[cfg(any(feature = "zisk-embedded-alloc", feature = "zisk-embedded-dlmalloc-alloc"))] +pub use embedded_dlmalloc as embedded; + +#[cfg(feature = "zisk-embedded-talc-alloc")] +pub use embedded_talc as embedded; + +#[cfg(feature = "zisk-embedded-tlfs-alloc")] +pub use embedded_tlfs as embedded; + +// disabled, worse performance +// pub mod embedded_lla; +// pub mod embedded_llff; +// pub use embedded_llff as embedded; +// pub use embedded_lla as embedded; diff --git a/ziskos/entrypoint/src/dma.rs b/ziskos/entrypoint/src/dma.rs index 9e465843f..2df391440 100644 --- a/ziskos/entrypoint/src/dma.rs +++ b/ziskos/entrypoint/src/dma.rs @@ -82,6 +82,19 @@ macro_rules! ziskos_memcpy { ); } }}; + (ptr: $dst:expr, $src:expr, $size:expr) => {{ + unsafe { + core::arch::asm!( + "csrs {port}, {src}", + "add x0, {dst}, {size}", + port = const zisk_definitions::SYSCALL_DMA_MEMCPY_ID, + size = in(reg) $size, + dst = in(reg) $dst, // ya es *mut u8, sin as_mut_ptr() + src = in(reg) $src, // ya es *mut u8, sin as_ptr() + options(nostack, preserves_flags), + ); + } + }}; } /// Compares two memory regions for equality using DMA operations. @@ -177,6 +190,19 @@ macro_rules! ziskos_memset { ); } }}; + (ptr: $dst:expr, $value: literal, $size:expr) => {{ + unsafe { + core::arch::asm!( + "csrs {port}, {dst}", + "addi x0, {size}, {value}", + port = const zisk_definitions::SYSCALL_DMA_MEMSET_ID, + size = in(reg) $size, + value = const $value, + dst = in(reg) $dst, + options(nostack, preserves_flags), + ); + } + }}; ($dst:expr, $value: expr, $size:expr) => {{ unsafe { core::arch::asm!( diff --git a/ziskos/entrypoint/src/dma/memset.s b/ziskos/entrypoint/src/dma/memset.s index 5b66c2506..fc70badeb 100644 --- a/ziskos/entrypoint/src/dma/memset.s +++ b/ziskos/entrypoint/src/dma/memset.s @@ -10,6 +10,12 @@ # a1 = fill value (c) # a2 = byte count (n) memset: + bnez a1, .L_memset_non_zero + csrs 0x816, a0 + addi x0, a2, 0 + ret + +.L_memset_non_zero: andi a1, a1, 0xff # Mask to byte value slli a1, a1, 4 la t0, .Ljump_table diff --git a/ziskos/entrypoint/src/lib.rs b/ziskos/entrypoint/src/lib.rs index 87b069718..bd37dd047 100644 --- a/ziskos/entrypoint/src/lib.rs +++ b/ziskos/entrypoint/src/lib.rs @@ -8,6 +8,9 @@ mod dma; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] mod fcall; +#[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] +mod alloc; + mod profile; #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] pub use fcall::*; @@ -110,7 +113,7 @@ pub(crate) fn set_output(id: usize, value: u32) { } #[cfg(all(target_os = "zkvm", target_vendor = "zisk"))] -mod ziskos { +pub mod ziskos { use crate::ziskos_definitions::ziskos_config::*; use core::arch::asm; @@ -170,12 +173,27 @@ mod ziskos { extern "C" { fn main(); } + #[cfg(any( + feature = "zisk-embedded-alloc", + feature = "zisk-embedded-dlmalloc-alloc", + feature = "zisk-embedded-talc-alloc", + feature = "zisk-embedded-tlfs-alloc" + ))] + crate::alloc::embedded::init(); + #[cfg(all( + not(feature = "zisk-embedded-alloc"), + not(feature = "zisk-embedded-dlmalloc-alloc"), + not(feature = "zisk-embedded-talc-alloc"), + not(feature = "zisk-embedded-tlfs-alloc") + ))] + crate::alloc::init_sys_alloc(); + main() } } #[no_mangle] - extern "C" fn sys_write(_fd: u32, write_ptr: *const u8, nbytes: usize) { + pub extern "C" fn sys_write(_fd: u32, write_ptr: *const u8, nbytes: usize) { let arch_id_zisk: usize; let mut addr: *mut u8 = 0x1000_0000 as *mut u8; @@ -240,48 +258,23 @@ mod ziskos { unimplemented!("sys_argv"); } - #[no_mangle] - pub unsafe extern "C" fn sys_alloc_aligned(bytes: usize, align: usize) -> *mut u8 { - use core::arch::asm; - let heap_bottom: usize; - // UNSAFE: This is fine, just loading some constants. - unsafe { - // using inline assembly is easier to access linker constants - asm!( - "la {heap_bottom}, _kernel_heap_bottom", - heap_bottom = out(reg) heap_bottom, - options(nomem) - ) - }; - - // Pointer to next heap address to use, or 0 if the heap has not yet been - // initialized. - static mut HEAP_POS: usize = 0; - - // SAFETY: Single threaded, so nothing else can touch this while we're working. - let mut heap_pos = unsafe { HEAP_POS }; - - if heap_pos == 0 { - heap_pos = heap_bottom; + pub extern "C" fn sys_print_hex(val: usize, ln: bool) { + let mut buf = [0u8; 19]; // "0x" + 16 hex + \n — stack, no heap + buf[0] = b'0'; + buf[1] = b'x'; + let mut v = val; + for i in (2..18).rev() { + buf[i] = b"0123456789abcdef"[v & 0xF]; + v >>= 4; } - - let offset = heap_pos & (align - 1); - if offset != 0 { - heap_pos += align - offset; + if ln { + buf[18] = b'\n'; + sys_write(1, buf.as_ptr(), buf.len()); + } else { + sys_write(1, buf.as_ptr(), buf.len() - 1); } - - let ptr = heap_pos as *mut u8; - heap_pos += bytes; - - // Check to make sure heap doesn't collide with SYSTEM memory. - //if SYSTEM_START < heap_pos { - // panic!(); - // } - - unsafe { HEAP_POS = heap_pos }; - - ptr } + core::arch::global_asm!(include_str!("dma/memcpy.s")); core::arch::global_asm!(include_str!("dma/memmove.s")); core::arch::global_asm!(include_str!("dma/memcmp.s")); From 022f5c00588f438019a7e207b0514d063344ab9b Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Wed, 11 Mar 2026 23:07:12 +0000 Subject: [PATCH 764/782] Fix reset hint size metrics --- ziskos/entrypoint/src/hints/metrics.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ziskos/entrypoint/src/hints/metrics.rs b/ziskos/entrypoint/src/hints/metrics.rs index 41dff6b1b..40ab92aca 100644 --- a/ziskos/entrypoint/src/hints/metrics.rs +++ b/ziskos/entrypoint/src/hints/metrics.rs @@ -57,5 +57,6 @@ pub(crate) fn reset_metrics() { let mut hints = HINTS_METRICS.write().expect("HINTS_METRICS poisoned"); for (_, info) in hints.iter_mut() { info.count = 0; + info.size = 0; } } From 26f0f31da4c6c90ee4cb86bc5812560ccae1ec76 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Thu, 12 Mar 2026 05:43:13 +0100 Subject: [PATCH 765/782] add code review report from earlier release --- audits/Zisk_Binary_and_Main_Review.pdf | Bin 0 -> 154655 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 audits/Zisk_Binary_and_Main_Review.pdf diff --git a/audits/Zisk_Binary_and_Main_Review.pdf b/audits/Zisk_Binary_and_Main_Review.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f9296b5a61e0472c3064a42e686c7ca56614c936 GIT binary patch literal 154655 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 z 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 Date: Thu, 12 Mar 2026 09:17:34 +0100 Subject: [PATCH 766/782] Hints processor and asm resources refactor (#843) * RefCell to Mutex * Cargo update * hints processor and asm resources refactor * Cargo update --------- Co-authored-by: RogerTaule Co-authored-by: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Co-authored-by: zkronos73 --- Cargo.lock | 224 +++++++++++----- common/src/io/stream/zisk_stream.rs | 53 ++-- .../crates/coordinator/src/hints_relay.rs | 6 +- .../crates/worker/src/stream_ordering.rs | 20 +- distributed/crates/worker/src/worker.rs | 22 +- emulator-asm/asm-runner/src/hints_shmem.rs | 37 ++- executor/Cargo.toml | 1 + executor/src/asm_resources.rs | 138 +++++----- precompiles/hints/benches/hints_benchmarks.rs | 64 +++-- precompiles/hints/src/hint_handlers.rs | 121 +++++++++ precompiles/hints/src/hints_processor.rs | 251 ++++-------------- precompiles/hints/src/lib.rs | 2 + sdk/Cargo.toml | 5 +- sdk/src/prover/backend.rs | 2 +- sdk/src/prover/mod.rs | 25 +- 15 files changed, 531 insertions(+), 440 deletions(-) create mode 100644 precompiles/hints/src/hint_handlers.rs diff --git a/Cargo.lock b/Cargo.lock index 5d03dd337..6d63a866b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,7 +186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -199,7 +199,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -260,7 +260,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -332,7 +332,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -344,7 +344,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -366,7 +366,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -377,7 +377,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -457,6 +457,12 @@ dependencies = [ "tower-service", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.22.1" @@ -489,7 +495,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn", + "syn 2.0.117", ] [[package]] @@ -544,7 +550,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -775,7 +781,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -847,10 +853,16 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width", + "unicode-width 0.2.2", "windows-sys 0.59.0", ] +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + [[package]] name = "const-random" version = "0.1.18" @@ -1009,6 +1021,12 @@ dependencies = [ "itertools 0.13.0", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam" version = "0.8.4" @@ -1109,13 +1127,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" dependencies = [ "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "num-bigint", @@ -1149,7 +1167,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.117", ] [[package]] @@ -1160,7 +1178,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1241,7 +1259,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1251,7 +1269,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.117", ] [[package]] @@ -1293,7 +1311,18 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "dlmalloc" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6738d2e996274e499bc7b0d693c858b7720b9cd2543a0643a3087e6cb0a4fa16" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.61.2", ] [[package]] @@ -1320,7 +1349,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1335,6 +1364,18 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -1367,7 +1408,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1469,7 +1510,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" dependencies = [ "cfg-if", "num-bigint", @@ -1593,7 +1634,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1886,7 +1927,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -2062,7 +2103,7 @@ dependencies = [ "console", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.2.2", "web-time", ] @@ -2294,6 +2335,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2707,7 +2754,7 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64", + "base64 0.22.1", "serde_core", ] @@ -2747,7 +2794,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2774,7 +2821,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" dependencies = [ "colored", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", @@ -2808,7 +2855,7 @@ checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3134,7 +3181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -3158,7 +3205,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" dependencies = [ "bincode", "blake3", @@ -3193,7 +3240,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" dependencies = [ "bincode", "borsh", @@ -3224,7 +3271,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" dependencies = [ "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", "itoa", @@ -3237,17 +3284,17 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" dependencies = [ "crossbeam-channel", "tracing", @@ -3256,7 +3303,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" dependencies = [ "bincode", "bytemuck", @@ -3268,7 +3315,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" dependencies = [ "bytemuck", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", @@ -3304,7 +3351,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn", + "syn 2.0.117", "tempfile", ] @@ -3318,7 +3365,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3576,7 +3623,7 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", "futures-util", @@ -3629,6 +3676,19 @@ dependencies = [ name = "riscv" version = "0.16.0" +[[package]] +name = "rlsf" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1646a59a9734b8b7a0ac51689388a60fe1625d4b956348e9de07591a1478457a" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "rustversion", + "svgbobdoc", +] + [[package]] name = "rom-setup" version = "0.16.0" @@ -3925,7 +3985,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4204,6 +4264,19 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64 0.13.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width 0.1.14", +] + [[package]] name = "symbolic-common" version = "12.17.2" @@ -4229,6 +4302,17 @@ dependencies = [ "symbolic-common", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -4257,7 +4341,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4288,6 +4372,15 @@ dependencies = [ "windows 0.62.2", ] +[[package]] +name = "talc" +version = "4.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ae828aa394de34c7de08f522d1b86bd1c182c668d27da69caadda00590f26d" +dependencies = [ + "lock_api", +] + [[package]] name = "target-lexicon" version = "0.13.5" @@ -4333,7 +4426,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4344,7 +4437,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4458,7 +4551,7 @@ checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4604,7 +4697,7 @@ checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", "axum", - "base64", + "base64 0.22.1", "bytes", "h2", "http", @@ -4634,7 +4727,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4659,7 +4752,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn", + "syn 2.0.117", "tempfile", "tonic-build", ] @@ -4758,7 +4851,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4861,6 +4954,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-width" version = "0.2.2" @@ -5061,7 +5160,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -5289,7 +5388,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5300,7 +5399,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5659,7 +5758,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -5675,7 +5774,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -5720,7 +5819,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#ed06d7825cfa7f2cf09da804da1f8bd0a84187c8" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" dependencies = [ "colored", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", @@ -5800,7 +5899,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -5821,7 +5920,7 @@ checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5841,7 +5940,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -5862,7 +5961,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5895,7 +5994,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -6086,6 +6185,7 @@ dependencies = [ "colored", "executor", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "precompiles-hints", "proofman", "proofman-common", "proofman-util", @@ -6151,7 +6251,10 @@ dependencies = [ "bincode", "bytes", "cfg-if", + "critical-section", "ctor", + "dlmalloc", + "embedded-alloc", "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", "getrandom 0.2.17", "lazy_static", @@ -6165,6 +6268,7 @@ dependencies = [ "rand 0.8.5", "serde", "sha2", + "talc", "tiny-keccak", "tokio", "zisk-common", diff --git a/common/src/io/stream/zisk_stream.rs b/common/src/io/stream/zisk_stream.rs index ab1294ee4..9312dfd53 100644 --- a/common/src/io/stream/zisk_stream.rs +++ b/common/src/io/stream/zisk_stream.rs @@ -1,7 +1,6 @@ //! ZiskStream is responsible for reading precompile hints from a stream source and sent to a hints processor. use anyhow::Result; -use std::any::Any; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Sender}; use std::sync::Arc; @@ -9,19 +8,14 @@ use std::thread::{self, JoinHandle}; use crate::io::{StreamRead, StreamSource}; -pub trait StreamProcessor: Any + Send + Sync + 'static { - /// Process data and return the processed result along with a flag indicating if CTRL_END was encountered. +pub trait StreamProcessor: Send + Sync + 'static { + /// Process a batch of hint data. /// /// # Returns - /// A tuple of (processed_data, has_ctrl_end) where: - /// - processed_data: Vec - The processed data - /// - has_ctrl_end: bool - True if CTRL_END was found (signals end of batch) - fn process(&self, data: &[u64], first_batch: bool) -> anyhow::Result; + /// `true` if CTRL_END was encountered (signals end of stream), `false` otherwise. + fn process_hints(&self, data: &[u64], first_batch: bool) -> anyhow::Result; fn reset(&self) {} - - /// Convert Arc to Arc for downcasting - fn as_any(self: Arc) -> Arc; } /// Trait for submitting processed hints to a sink. @@ -36,8 +30,6 @@ pub trait StreamSink: Send + Sync + 'static { fn submit(&self, processed: &[u64]) -> anyhow::Result<()>; fn reset(&self) {} - - fn set_has_rom_sm(&self, _has_rom_sm: bool) {} } enum ThreadCommand { @@ -46,9 +38,9 @@ enum ThreadCommand { } /// ZiskStream struct manages the processing of precompile hints and writing them to shared memory. -pub struct ZiskStream { +pub struct ZiskStream { /// The hints processor used to process hints before writing. - hints_processor: Arc, + hints_processor: Arc

, /// Channel sender to communicate with the background thread. tx: Option>, @@ -56,10 +48,10 @@ pub struct ZiskStream { /// Join handle for the background thread. thread_handle: Option>, - hints_stream_initialized: AtomicBool, + initialized: AtomicBool, } -impl ZiskStream { +impl ZiskStream

{ /// Create a new ZiskStream with the given processor. /// /// # Arguments @@ -67,12 +59,12 @@ impl ZiskStream { /// /// # Returns /// A new `ZiskStream` instance without a running thread. - pub fn new(hints_processor: impl StreamProcessor) -> Self { + pub fn new(hints_processor: P) -> Self { Self { hints_processor: Arc::new(hints_processor), tx: None, thread_handle: None, - hints_stream_initialized: AtomicBool::new(false), + initialized: AtomicBool::new(false), } } @@ -113,7 +105,7 @@ impl ZiskStream { self.thread_handle = Some(thread_handle); - self.hints_stream_initialized.store(true, Ordering::SeqCst); + self.initialized.store(true, Ordering::SeqCst); Ok(()) } @@ -121,7 +113,7 @@ impl ZiskStream { /// Background thread function that processes hints when requested. fn background_thread( mut stream: StreamSource, - hints_processor: Arc, + hints_processor: Arc

, rx: Receiver, ) { while let Ok(ThreadCommand::Process) = rx.recv() { @@ -135,15 +127,12 @@ impl ZiskStream { /// Process all hints from the stream. /// /// Processes hints in batches until CTRL_END is encountered or the stream ends. - fn process_stream( - stream: &mut StreamSource, - hints_processor: &dyn StreamProcessor, - ) -> Result<()> { + fn process_stream(stream: &mut StreamSource, hints_processor: &P) -> Result<()> { let mut first_batch = true; while let Some(hints) = stream.next()? { let hints = crate::reinterpret_vec(hints)?; - let has_ctrl_end = hints_processor.process(&hints, first_batch)?; + let has_ctrl_end = hints_processor.process_hints(&hints, first_batch)?; first_batch = false; @@ -158,7 +147,7 @@ impl ZiskStream { pub fn reset(&mut self) { self.hints_processor.reset(); - self.hints_stream_initialized.store(false, Ordering::SeqCst); + self.initialized.store(false, Ordering::SeqCst); } /// Trigger the background thread to process hints asynchronously. @@ -171,7 +160,7 @@ impl ZiskStream { /// * `Ok(())` - If the command was successfully sent /// * `Err` - If there's no active thread or the channel is closed pub fn start_stream(&mut self) -> Result<()> { - if !self.hints_stream_initialized.load(Ordering::SeqCst) { + if !self.initialized.load(Ordering::SeqCst) { return Err(anyhow::anyhow!( "Hints stream is not initialized. Call set_hints_stream_src first." )); @@ -187,12 +176,16 @@ impl ZiskStream { } } - pub fn get_processor(&self) -> Arc { - self.hints_processor.clone() + pub fn is_initialized(&self) -> bool { + self.initialized.load(Ordering::SeqCst) + } + + pub fn get_processor(&self) -> Arc

{ + Arc::clone(&self.hints_processor) } } -impl Drop for ZiskStream { +impl Drop for ZiskStream

{ fn drop(&mut self) { self.stop_thread(); } diff --git a/distributed/crates/coordinator/src/hints_relay.rs b/distributed/crates/coordinator/src/hints_relay.rs index 1199584f9..7c63e51af 100644 --- a/distributed/crates/coordinator/src/hints_relay.rs +++ b/distributed/crates/coordinator/src/hints_relay.rs @@ -148,15 +148,11 @@ impl PrecompileHintsRelay { } impl StreamProcessor for PrecompileHintsRelay { - fn process(&self, data: &[u64], first_batch: bool) -> Result { + fn process_hints(&self, data: &[u64], first_batch: bool) -> Result { self.process_hints(data, first_batch) } fn reset(&self) { self.reset_state(); } - - fn as_any(self: Arc) -> Arc { - self - } } diff --git a/distributed/crates/worker/src/stream_ordering.rs b/distributed/crates/worker/src/stream_ordering.rs index 5a5dcd807..81e8dadcb 100644 --- a/distributed/crates/worker/src/stream_ordering.rs +++ b/distributed/crates/worker/src/stream_ordering.rs @@ -1,10 +1,10 @@ use anyhow::Result; -use precompiles_hints::HintsProcessor; use std::cmp::Reverse; use std::collections::BinaryHeap; use std::sync::mpsc; use std::sync::Arc; use tracing::{error, info}; +use zisk_common::io::StreamProcessor; use zisk_common::reinterpret_vec; use zisk_distributed_common::{JobId, StreamDataDto, StreamMessageKind}; @@ -17,10 +17,10 @@ pub struct StreamOrderingActor { impl StreamOrderingActor { /// Spawns the ordering thread and returns the actor handle. - pub fn new(hints_processor: Arc, job_id: JobId) -> Self { + pub fn new(processor: Arc

, job_id: JobId) -> Self { let (tx, rx) = mpsc::channel::(); - let handle = std::thread::spawn(move || Self::run(rx, hints_processor, job_id)); + let handle = std::thread::spawn(move || Self::run(rx, processor, job_id)); Self { sender: Some(tx), thread_handle: Some(handle) } } @@ -38,15 +38,19 @@ impl StreamOrderingActor { // Error propagation: when run_inner returns Err, rx is dropped, closing the channel. // The next actor.send() in the gRPC loop then returns Err. - fn run(rx: mpsc::Receiver, hints_processor: Arc, job_id: JobId) { - if let Err(e) = Self::run_inner(rx, &hints_processor, &job_id) { + fn run( + rx: mpsc::Receiver, + processor: Arc

, + job_id: JobId, + ) { + if let Err(e) = Self::run_inner(rx, &*processor, &job_id) { error!("Stream ordering actor failed for job {}: {}", job_id, e); } } - fn run_inner( + fn run_inner( rx: mpsc::Receiver, - hints_processor: &HintsProcessor, + processor: &P, job_id: &JobId, ) -> Result<()> { // Min-heap ordered by sequence number (Reverse makes BinaryHeap a min-heap) @@ -92,7 +96,7 @@ impl StreamOrderingActor { let hints = reinterpret_vec(combined)?; let first = std::mem::replace(&mut is_first, false); - hints_processor.process_hints(&hints, first)?; + processor.process_hints(&hints, first)?; } StreamMessageKind::Start => { return Err(anyhow::anyhow!( diff --git a/distributed/crates/worker/src/worker.rs b/distributed/crates/worker/src/worker.rs index d5b781d43..c13fab4c8 100644 --- a/distributed/crates/worker/src/worker.rs +++ b/distributed/crates/worker/src/worker.rs @@ -1,6 +1,5 @@ use anyhow::Result; use cargo_zisk::commands::get_proving_key; -use precompiles_hints::HintsProcessor; use proofman::{AggProofs, AggProofsRegister, ContributionsInfo}; use rom_setup::{get_elf_data_hash, DEFAULT_CACHE_PATH}; use std::fs; @@ -637,23 +636,16 @@ impl Worker { self.pk.reset(); - let processor_trait = self.pk.get_hints_processor().ok_or_else(|| { + let processor = self.pk.get_hints_processor().ok_or_else(|| { anyhow::anyhow!("HintsProcessor not found for job {}", job_id) })?; - // Downcast Arc to Arc - let hints_processor = - processor_trait.as_any().downcast::().map_err(|_| { - anyhow::anyhow!( - "Failed to downcast StreamProcessor to HintsProcessor for job {}", - job_id - ) - })?; - - hints_processor.set_has_rom_sm(is_first_partition); + if let Some(r) = self.pk.asm_resources.as_ref() { + r.set_active_services(is_first_partition)?; + } // Replace any existing actor (handles reconnect / job restart) - self.stream_actor = Some(StreamOrderingActor::new(hints_processor, job_id)); + self.stream_actor = Some(StreamOrderingActor::new(processor, job_id)); } StreamMessageKind::Data | StreamMessageKind::End => match &self.stream_actor { Some(actor) => actor.send(stream_data)?, @@ -858,9 +850,9 @@ impl Worker { let options = self.get_proof_options(false); if phase == JobPhase::ContributionsHintsStream { - if let Some(hints_sink) = pk.asm_resources.as_ref().and_then(|r| r.hints_sink.clone()) { + if let Some(r) = pk.asm_resources.as_ref() { let message: StreamMessage = borsh::from_slice(&bytes[1..]).unwrap(); - if let Err(e) = hints_sink.submit(&message.data) { + if let Err(e) = r.submit_hint_direct(&message.data) { tracing::error!("Failed to submit hints: {}", e); } } else { diff --git a/emulator-asm/asm-runner/src/hints_shmem.rs b/emulator-asm/asm-runner/src/hints_shmem.rs index a6285c3e0..88b4305ae 100644 --- a/emulator-asm/asm-runner/src/hints_shmem.rs +++ b/emulator-asm/asm-runner/src/hints_shmem.rs @@ -12,7 +12,7 @@ use named_sem::NamedSemaphore; use std::{ cell::RefCell, sync::{ - atomic::{fence, AtomicBool, Ordering}, + atomic::{fence, AtomicUsize, Ordering}, Arc, }, }; @@ -56,7 +56,8 @@ struct UnifiedResources { /// HintsShmem struct manages the writing of processed precompile hints to shared memory. pub struct HintsShmem { - has_rom_sm: AtomicBool, + /// Number of active ASM services to notify on submit + active_count: AtomicUsize, /// Unified resources (single data buffer and control writer) unified: RefCell, /// Separate resources (control_reader and semaphores for each service) @@ -85,6 +86,7 @@ impl HintsShmem { local_rank: i32, unlock_mapped_memory: bool, control_writer: Arc, + active_services: &[AsmService], ) -> Result { // Use the first service's port for shared resources naming let first_service = &AsmServices::SERVICES[0]; @@ -118,10 +120,26 @@ impl HintsShmem { Ok(Self { unified: RefCell::new(unified), separate: RefCell::new(separate), - has_rom_sm: AtomicBool::new(false), + active_count: AtomicUsize::new(active_services.len()), }) } + /// Update the number of active ASM services notified on each submit. + /// + /// This is a deployment-time configuration — call once per job partition, + /// not on every stream reset. `services.len()` must not exceed `AsmServices::SERVICES.len()`. + pub fn set_active_services(&self, services: &[AsmService]) -> Result<()> { + if services.len() > AsmServices::SERVICES.len() { + return Err(anyhow::anyhow!( + "active_services count {} exceeds allocated separate resources {}", + services.len(), + AsmServices::SERVICES.len() + )); + } + self.active_count.store(services.len(), Ordering::SeqCst); + Ok(()) + } + /// Create the unified resources (single data buffer and control writer). fn create_unified_resources( port: u16, @@ -210,11 +228,8 @@ impl StreamSink for HintsShmem { let mut unified = self.unified.borrow_mut(); let mut separate = self.separate.borrow_mut(); - let separate = if self.has_rom_sm.load(std::sync::atomic::Ordering::SeqCst) { - &mut separate - } else { - &mut separate[0..2] - }; + let active = self.active_count.load(Ordering::SeqCst); + let separate = &mut separate[0..active]; // Read current write position once let write_pos = unified.control_writer.prec_hints_size(); @@ -296,11 +311,5 @@ impl StreamSink for HintsShmem { "Control reader position should be reset to 0" ); } - - self.has_rom_sm.store(false, std::sync::atomic::Ordering::SeqCst); - } - - fn set_has_rom_sm(&self, has_rom_sm: bool) { - self.has_rom_sm.store(has_rom_sm, std::sync::atomic::Ordering::SeqCst); } } diff --git a/executor/Cargo.toml b/executor/Cargo.toml index dcf71ecdc..1bce1efde 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -55,6 +55,7 @@ default = [] disable_distributed = [] gpu = ["proofman-common/gpu", "proofman/gpu", "packed"] packed = [ + "proofman/packed", "proofman-common/packed", "precomp-keccakf/packed", "precomp-sha256f/packed", diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index 85b636927..816818567 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -1,17 +1,11 @@ use std::sync::{Arc, Mutex}; use anyhow::Result; -use asm_runner::ControlShmem; -use asm_runner::HintsFile; -use asm_runner::HintsShmem; -use asm_runner::InputsShmemWriter; +use asm_runner::{AsmServices, ControlShmem, HintsShmem, InputsShmemWriter}; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] use asm_runner::{MOShMemReader, MTShMemReader, RHShMemReader}; use precompiles_hints::{HintsProcessor, MpiBroadcastFn}; -use std::sync::atomic::{AtomicBool, Ordering}; -use zisk_common::io::StreamSink; -use zisk_common::io::ZiskStdin; -use zisk_common::io::{StreamProcessor, StreamSource, ZiskStream}; +use zisk_common::io::{StreamSink, StreamSource, ZiskStdin, ZiskStream}; /// Configuration for assembly resources. #[derive(Clone)] @@ -39,30 +33,28 @@ impl std::fmt::Debug for AsmResourcesConfig { /// Encapsulates assembly-related resources including shared memory and hints stream. #[derive(Clone)] pub struct AsmResources { + /// Configuration for assembly resources. config: AsmResourcesConfig, + /// Shared memory for writing inputs to the assembly microservices. + pub inputs_shmem_writer: Arc, + + /// Pipeline for handling precompile hints. + hints_stream: Option>>>>, + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub mt_shmem_reader: Arc>, #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub mo_shmem_reader: Arc>, #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub rh_shmem_reader: Arc>>, - - pub inputs_shmem_writer: Arc, - - pub hints_sink: Option>, - - /// Pipeline for handling precompile hints. - pub hints_stream: Option>>, - - hints_stream_initialized: Arc, } impl std::fmt::Debug for AsmResources { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AsmResources") .field("config", &self.config) - .field("hints_stream_initialized", &self.hints_stream_initialized) + .field("hints_stream", &self.hints_stream.is_some()) .finish_non_exhaustive() } } @@ -95,61 +87,36 @@ impl AsmResources { control_writer.clone(), )?); - // Create hints pipeline with null hints stream initially. - // Debug flag: true = HintsShmem (shared memory), false = HintsFile (file output) - const USE_SHARED_MEMORY_HINTS: bool = true; - - let (hints_stream, hints_sink) = if with_hints { - let (hints_processor, hints_sink): (HintsProcessor, Arc) = - if USE_SHARED_MEMORY_HINTS { - let hints_shmem = Arc::new(HintsShmem::new( - base_port, - local_rank, - unlock_mapped_memory, - control_writer, - )?); - - let mut builder = HintsProcessor::builder( - hints_shmem.clone(), - Some(inputs_shmem_writer.clone()), - ) - .enable_stats(verbose_mode != proofman_common::VerboseMode::Info); - - if let Some(broadcast_fn) = mpi_broadcast_fn.clone() { - builder = builder.with_mpi_broadcast(move |data| broadcast_fn(data)); - } + let hints_stream = if with_hints { + let active_services = + if init_rom { &AsmServices::SERVICES[..] } else { &AsmServices::SERVICES[..2] }; - (builder.build().expect("Failed to build HintsProcessor"), hints_shmem) - } else { - let hints_file = - Arc::new(HintsFile::new(format!("hints_results_{}.bin", local_rank))?); + let hints_shmem = Arc::new(HintsShmem::new( + base_port, + local_rank, + unlock_mapped_memory, + control_writer, + active_services, + )?); - let mut builder = HintsProcessor::builder( - hints_file.clone(), - Some(inputs_shmem_writer.clone()), - ) + let mut builder = + HintsProcessor::builder(hints_shmem, Some(inputs_shmem_writer.clone())) .enable_stats(verbose_mode != proofman_common::VerboseMode::Info); - if let Some(broadcast_fn) = mpi_broadcast_fn.clone() { - builder = builder.with_mpi_broadcast(move |data| broadcast_fn(data)); - } - - (builder.build().expect("Failed to build HintsProcessor"), hints_file) - }; - - if init_rom { - hints_processor.set_has_rom_sm(true); + if let Some(broadcast_fn) = mpi_broadcast_fn { + builder = builder.with_mpi_broadcast(move |data| broadcast_fn(data)); } - (Some(Arc::new(Mutex::new(ZiskStream::new(hints_processor)))), Some(hints_sink)) + let hints_processor = builder.build()?; + + Some(Arc::new(Mutex::new(ZiskStream::new(hints_processor)))) } else { - (None, None) + None }; Ok(Self { config, hints_stream, - hints_stream_initialized: Arc::new(AtomicBool::new(false)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] mt_shmem_reader: Arc::new(Mutex::new(asm_shmem_mt)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] @@ -158,10 +125,44 @@ impl AsmResources { rh_shmem_reader: Arc::new(Mutex::new(None)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] inputs_shmem_writer, - hints_sink, }) } + /// Returns the concrete hints processor for passing to `StreamOrderingActor`. + pub fn get_hints_processor(&self) -> Option>> { + self.hints_stream.as_ref().map(|stream| stream.lock().unwrap().get_processor()) + } + + /// Update the active ASM services for this partition. + /// + /// Call once per partition start (not per stream reset). + /// `is_first_partition` controls whether the ROM histogram service (RH) is active. + pub fn set_active_services(&self, is_first_partition: bool) -> Result<()> { + if let Some(stream) = &self.hints_stream { + let processor = stream.lock().unwrap().get_processor(); + let sink = processor.hints_sink(); + let services = if is_first_partition { + &AsmServices::SERVICES[..] + } else { + &AsmServices::SERVICES[..2] + }; + sink.set_active_services(services)?; + } + Ok(()) + } + + /// Submit hint data directly to the shmem sink, bypassing the processing pipeline. + /// + /// Used in the gRPC streaming path where hints arrive pre-processed. + pub fn submit_hint_direct(&self, data: &[u64]) -> Result<()> { + if let Some(stream) = &self.hints_stream { + let processor = stream.lock().unwrap().get_processor(); + processor.hints_sink().submit(data) + } else { + Err(anyhow::anyhow!("Hints stream not configured")) + } + } + pub fn start_stream(&self) -> Result<()> { if let Some(hints_stream) = &self.hints_stream { hints_stream.lock().unwrap().start_stream() @@ -172,26 +173,19 @@ impl AsmResources { pub fn set_hints_stream_src(&self, stream: StreamSource) -> Result<()> { if let Some(hints_stream) = &self.hints_stream { - hints_stream.lock().unwrap().set_hints_stream_src(stream)?; + hints_stream.lock().unwrap().set_hints_stream_src(stream) } else { - return Err(anyhow::anyhow!("Hints stream not initialized")); + Err(anyhow::anyhow!("Hints stream not initialized")) } - self.hints_stream_initialized.store(true, Ordering::SeqCst); - Ok(()) } pub fn is_hints_stream_initialized(&self) -> bool { - self.hints_stream_initialized.load(Ordering::SeqCst) - } - - pub fn get_hints_processor(&self) -> Option> { - self.hints_stream.as_ref().map(|stream| stream.lock().unwrap().get_processor()) + self.hints_stream.as_ref().map(|s| s.lock().unwrap().is_initialized()).unwrap_or(false) } pub fn reset(&self) { if let Some(hints_stream) = &self.hints_stream { hints_stream.lock().unwrap().reset(); - self.hints_stream_initialized.store(false, Ordering::SeqCst); } self.inputs_shmem_writer.reset(); } diff --git a/precompiles/hints/benches/hints_benchmarks.rs b/precompiles/hints/benches/hints_benchmarks.rs index 9eae01729..9e84eb1e4 100644 --- a/precompiles/hints/benches/hints_benchmarks.rs +++ b/precompiles/hints/benches/hints_benchmarks.rs @@ -1,6 +1,6 @@ use anyhow::Result; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use precompiles_hints::HintsProcessor; +use precompiles_hints::{HintHandlers, HintsProcessor}; use std::hint::black_box; use std::sync::{Arc, Mutex}; use std::thread; @@ -50,18 +50,21 @@ fn parallel_speedup_benchmark(c: &mut Criterion) { let p = HintsProcessor::builder(sink, None::>) .num_threads(threads) - .custom_hint(FAST_HINT, |data: &[u64]| -> Result> { - thread::sleep(Duration::from_millis(1)); - Ok(vec![data[0] + 1]) - }) - .custom_hint(MEDIUM_HINT, |data: &[u64]| -> Result> { - thread::sleep(Duration::from_millis(5)); - Ok(vec![data[0] + 2]) - }) - .custom_hint(SLOW_HINT, |data: &[u64]| -> Result> { - thread::sleep(Duration::from_millis(10)); - Ok(vec![data[0] + 3]) - }) + .with_hint_handlers( + HintHandlers::default() + .register(FAST_HINT, |data: &[u64]| -> Result> { + thread::sleep(Duration::from_millis(1)); + Ok(vec![data[0] + 1]) + }) + .register(MEDIUM_HINT, |data: &[u64]| -> Result> { + thread::sleep(Duration::from_millis(5)); + Ok(vec![data[0] + 2]) + }) + .register(SLOW_HINT, |data: &[u64]| -> Result> { + thread::sleep(Duration::from_millis(10)); + Ok(vec![data[0] + 3]) + }), + ) .build() .unwrap(); @@ -109,9 +112,9 @@ fn microsecond_hints_benchmark(c: &mut Criterion) { group.sample_size(50); let test_cases = vec![ - ("ultra_fast_10us", ULTRA_FAST, 10), - ("very_fast_50us", VERY_FAST, 50), - ("fast_100us", FAST, 100), + ("ultra_fast_10us", ULTRA_FAST, 10u64), + ("very_fast_50us", VERY_FAST, 50u64), + ("fast_100us", FAST, 100u64), ]; for (name, hint_code, micros) in test_cases { @@ -123,10 +126,13 @@ fn microsecond_hints_benchmark(c: &mut Criterion) { let p = HintsProcessor::builder(sink, None::>) .num_threads(16) - .custom_hint(hint_code, move |data: &[u64]| -> Result> { - thread::sleep(Duration::from_micros(micros as u64)); - Ok(vec![data[0] + 1]) - }) + .with_hint_handlers(HintHandlers::default().register( + hint_code, + move |data: &[u64]| { + thread::sleep(Duration::from_micros(micros)); + Ok(vec![data[0] + 1]) + }, + )) .build() .unwrap(); @@ -173,28 +179,30 @@ fn workload_patterns_benchmark(c: &mut Criterion) { let received_clone = received.clone(); let sink = Arc::new(BenchSink { received: received_clone }); - let p = HintsProcessor::builder(sink, None::>) - .num_threads(8) - .custom_hint(VERY_FAST, |data: &[u64]| -> Result> { + let handlers = HintHandlers::default() + .register(VERY_FAST, |data: &[u64]| { thread::sleep(Duration::from_micros(500)); Ok(vec![data[0] + 1]) }) - .custom_hint(FAST, |data: &[u64]| -> Result> { + .register(FAST, |data: &[u64]| { thread::sleep(Duration::from_millis(2)); Ok(vec![data[0] + 1]) }) - .custom_hint(MEDIUM, |data: &[u64]| -> Result> { + .register(MEDIUM, |data: &[u64]| { thread::sleep(Duration::from_millis(5)); Ok(vec![data[0] + 1]) }) - .custom_hint(SLOW, |data: &[u64]| -> Result> { + .register(SLOW, |data: &[u64]| { thread::sleep(Duration::from_millis(10)); Ok(vec![data[0] + 1]) }) - .custom_hint(VERY_SLOW, |data: &[u64]| -> Result> { + .register(VERY_SLOW, |data: &[u64]| { thread::sleep(Duration::from_millis(20)); Ok(vec![data[0] + 1]) - }) + }); + let p = HintsProcessor::builder(sink, None::>) + .num_threads(8) + .with_hint_handlers(handlers) .build() .unwrap(); diff --git a/precompiles/hints/src/hint_handlers.rs b/precompiles/hints/src/hint_handlers.rs new file mode 100644 index 000000000..628be6ceb --- /dev/null +++ b/precompiles/hints/src/hint_handlers.rs @@ -0,0 +1,121 @@ +use anyhow::Result; +use std::collections::HashMap; +use zisk_common::{BuiltInHint, HintCode, PrecompileHint}; +use ziskos_hints::handlers::blake2b::blake2b_compress_hint; +use ziskos_hints::handlers::bls381::{ + bls12_381_fp2_to_g2_hint, bls12_381_fp_to_g1_hint, bls12_381_g1_add_hint, + bls12_381_g1_msm_hint, bls12_381_g2_add_hint, bls12_381_g2_msm_hint, + bls12_381_pairing_check_hint, +}; +use ziskos_hints::handlers::bn254::{ + bn254_g1_add_hint, bn254_g1_mul_hint, bn254_pairing_check_hint, +}; +use ziskos_hints::handlers::keccak256::keccak256_hint; +use ziskos_hints::handlers::kzg::verify_kzg_proof_hint; +use ziskos_hints::handlers::modexp::modexp_hint; +use ziskos_hints::handlers::secp256k1::{ + secp256k1_ecdsa_address_recover, secp256k1_ecdsa_verify_address_recover, +}; +use ziskos_hints::handlers::secp256r1::secp256r1_ecdsa_verify_hint; +use ziskos_hints::handlers::sha256::sha256_hint; + +/// Type alias for custom hint handler functions. +pub type CustomHintHandler = Box Result> + Send + Sync>; + +/// Bundles built-in and custom hint dispatch logic. +/// +/// This is the single table that maps hint codes to compute functions. +/// Passed via `Arc` to each Rayon worker for parallel, allocation-free dispatch. +#[derive(Default)] +pub struct HintHandlers { + custom: HashMap, +} + +impl HintHandlers { + /// Register a custom hint handler for the given hint code. + pub fn register(mut self, hint_code: u32, handler: F) -> Self + where + F: Fn(&[u64]) -> Result> + Send + Sync + 'static, + { + self.custom.insert(hint_code, Box::new(handler)); + self + } + + pub fn has_custom_hint_code(&self, code: u32) -> bool { + self.custom.contains_key(&code) + } + + /// Dispatch a hint to the appropriate handler. + /// + /// Control hints and Input hints must be handled before calling this. + #[inline] + pub fn dispatch(&self, hint: PrecompileHint) -> Result> { + match hint.hint_code { + HintCode::BuiltIn(builtin) => { + Self::dispatch_builtin(builtin, hint.data, hint.data_len_bytes) + } + HintCode::Custom(code) => self + .custom + .get(&code) + .map(|handler| handler(&hint.data)) + .unwrap_or_else(|| Err(anyhow::anyhow!("Unknown custom hint"))), + _ => unreachable!("Control hints handled before dispatch"), + } + } + + /// Dispatches built-in hints to their corresponding handler functions. + /// The `data_len_bytes` parameter is used for hints that operate on byte arrays (e.g., SHA256, Keccak256) + /// to indicate the actual length of the data in bytes, since the `data` field is a `Vec` and may contain padding. + /// The BuiltInHint::Input is intentionally not handled here, as input hints require special handling and should be processed separately before dispatching to workers. + #[inline] + fn dispatch_builtin( + hint: BuiltInHint, + data: Vec, + data_len_bytes: usize, + ) -> Result> { + match hint { + // SHA256 Hint Codes + BuiltInHint::Sha256 => sha256_hint(&data, data_len_bytes), + + // BN254 Hint Codes + BuiltInHint::Bn254G1Add => bn254_g1_add_hint(&data), + BuiltInHint::Bn254G1Mul => bn254_g1_mul_hint(&data), + BuiltInHint::Bn254PairingCheck => bn254_pairing_check_hint(&data), + + // Secp256k1 Hints + BuiltInHint::Secp256k1EcdsaAddressRecover => secp256k1_ecdsa_address_recover(&data), + BuiltInHint::Secp256k1EcdsaVerifyAddressRecover => { + secp256k1_ecdsa_verify_address_recover(&data) + } + + // Secp256r1 Hints + BuiltInHint::Secp256r1EcdsaVerify => secp256r1_ecdsa_verify_hint(&data), + + // BLS12-381 Hint Codes + BuiltInHint::Bls12_381G1Add => bls12_381_g1_add_hint(&data), + BuiltInHint::Bls12_381G1Msm => bls12_381_g1_msm_hint(&data), + BuiltInHint::Bls12_381G2Add => bls12_381_g2_add_hint(&data), + BuiltInHint::Bls12_381G2Msm => bls12_381_g2_msm_hint(&data), + BuiltInHint::Bls12_381PairingCheck => bls12_381_pairing_check_hint(&data), + BuiltInHint::Bls12_381FpToG1 => bls12_381_fp_to_g1_hint(&data), + BuiltInHint::Bls12_381Fp2ToG2 => bls12_381_fp2_to_g2_hint(&data), + + // Modular Exponentiation Hint Codes + BuiltInHint::ModExp => modexp_hint(&data), + + // KZG Hint Codes + BuiltInHint::VerifyKzgProof => verify_kzg_proof_hint(&data), + + // Keccak256 Hint Codes + BuiltInHint::Keccak256 => keccak256_hint(&data, data_len_bytes), + + // Blake2b Hint Codes + BuiltInHint::Blake2bCompress => blake2b_compress_hint(&data), + + // Input Hint Codes + BuiltInHint::Input => unreachable!( + "Input hints should be handled separately and not dispatched to workers" + ), + } + } +} diff --git a/precompiles/hints/src/hints_processor.rs b/precompiles/hints/src/hints_processor.rs index de3e99a55..15967e4cb 100644 --- a/precompiles/hints/src/hints_processor.rs +++ b/precompiles/hints/src/hints_processor.rs @@ -18,23 +18,8 @@ use zisk_common::{ PrecompileHintParseResult, }; use zisk_distributed_common::{JobPhase, StreamMessage}; -use ziskos_hints::handlers::blake2b::blake2b_compress_hint; -use ziskos_hints::handlers::bls381::{ - bls12_381_fp2_to_g2_hint, bls12_381_fp_to_g1_hint, bls12_381_g1_add_hint, - bls12_381_g1_msm_hint, bls12_381_g2_add_hint, bls12_381_g2_msm_hint, - bls12_381_pairing_check_hint, -}; -use ziskos_hints::handlers::bn254::{ - bn254_g1_add_hint, bn254_g1_mul_hint, bn254_pairing_check_hint, -}; -use ziskos_hints::handlers::keccak256::keccak256_hint; -use ziskos_hints::handlers::kzg::verify_kzg_proof_hint; -use ziskos_hints::handlers::modexp::modexp_hint; -use ziskos_hints::handlers::secp256k1::{ - secp256k1_ecdsa_address_recover, secp256k1_ecdsa_verify_address_recover, -}; -use ziskos_hints::handlers::secp256r1::secp256r1_ecdsa_verify_hint; -use ziskos_hints::handlers::sha256::sha256_hint; + +use crate::hint_handlers::HintHandlers; /// Ordered result buffer with drain state. /// @@ -85,23 +70,20 @@ impl HintProcessorState { } } -/// Type alias for custom hint handler functions. -pub type CustomHintHandler = Arc Result> + Send + Sync>; - /// Type alias for MPI broadcast callback function. pub type MpiBroadcastFn = Arc) -> Result<()> + Send + Sync>; /// Builder for configuring and constructing a [`HintsProcessor`]. -pub struct HintsProcessorBuilder { - hints_sink: Arc, +pub struct HintsProcessorBuilder { + hints_sink: Arc, inputs_sink: Option>, num_threads: usize, enable_stats: bool, - custom_handlers: HashMap, + handlers: HintHandlers, mpi_broadcast_fn: Option, } -impl HintsProcessorBuilder { +impl HintsProcessorBuilder { /// Sets the number of worker threads in the thread pool. pub fn num_threads(mut self, num_threads: usize) -> Self { self.num_threads = num_threads; @@ -114,31 +96,6 @@ impl HintsProcessorBuilder { self } - /// Registers a custom hint handler for a specific hint code. - /// - /// # Arguments - /// - /// * `hint_code` - The u32 hint code identifier (should not conflict with built-in codes) - /// * `handler` - Function that processes the hint data and returns the result - /// - /// # Examples - /// - /// ```ignore - /// let processor = HintsProcessor::builder(my_sink) - /// .custom_hint(0x10, |data| { - /// // Custom processing logic - /// Ok(vec![data[0] * 2]) - /// }) - /// .build()?; - /// ``` - pub fn custom_hint(mut self, hint_code: u32, handler: F) -> Self - where - F: Fn(&[u64]) -> Result> + Send + Sync + 'static, - { - self.custom_handlers.insert(hint_code, Arc::new(handler)); - self - } - /// Sets an MPI broadcast function to be called during initialization. /// /// This allows synchronization of initialization data across MPI ranks. @@ -150,13 +107,19 @@ impl HintsProcessorBuilder { self } + /// Sets the hint dispatch table. + pub fn with_hint_handlers(mut self, handlers: HintHandlers) -> Self { + self.handlers = handlers; + self + } + /// Builds the [`HintsProcessor`] with the configured settings. /// /// # Returns /// - /// * `Ok(HintsProcessor)` - Successfully constructed processor + /// * `Ok(HintsProcessor)` - The configured hints processor /// * `Err` - If the thread pool fails to initialize - pub fn build(self) -> Result { + pub fn build(self) -> Result> { let pool = ThreadPoolBuilder::new() .num_threads(self.num_threads) .build() @@ -182,7 +145,7 @@ impl HintsProcessorBuilder { hints_sink, inputs_sink, drainer_thread: ManuallyDrop::new(drainer_thread), - custom_handlers: Arc::new(self.custom_handlers), + handlers: Arc::new(self.handlers), stream_active: AtomicBool::new(false), instant: Mutex::new(None), pending_partial: Mutex::new(None), @@ -196,7 +159,7 @@ impl HintsProcessorBuilder { /// This struct provides methods to parse and process a stream of concatenated /// hints, using a dedicated Rayon thread pool for parallel processing while /// preserving the original order of results. -pub struct HintsProcessor { +pub struct HintsProcessor { /// The thread pool used for parallel hint processing. pool: ThreadPool, @@ -208,8 +171,8 @@ pub struct HintsProcessor { /// Optional statistics collected during hint processing (for debugging). stats: Option>>, - /// The hints sink used to submit processed hints (kept for ownership). - hints_sink: Arc, + /// The hints sink used to submit processed hints. + hints_sink: Arc, /// The inputs sink used to submit processed input hints (if any). inputs_sink: Option>, @@ -217,8 +180,8 @@ pub struct HintsProcessor { /// Handle to the drainer thread (wrapped in ManuallyDrop to join in Drop) drainer_thread: ManuallyDrop>, - /// Custom hint handlers registered by the user - custom_handlers: Arc>, + /// Hint dispatch table (built-in + custom) + handlers: Arc, /// Tracks whether a stream is currently active (between CTRL_START and CTRL_END) stream_active: AtomicBool, @@ -233,7 +196,7 @@ pub struct HintsProcessor { mpi_broadcast_fn: Option, } -impl HintsProcessor { +impl HintsProcessor { /// Default number of worker threads in the thread pool. const DEFAULT_NUM_THREADS: usize = 32; @@ -252,15 +215,15 @@ impl HintsProcessor { /// .build()?; /// ``` pub fn builder( - hints_sink: Arc, + hints_sink: Arc, inputs_sink: Option>, - ) -> HintsProcessorBuilder { + ) -> HintsProcessorBuilder { HintsProcessorBuilder { hints_sink, inputs_sink: inputs_sink.map(|s| s as Arc), num_threads: Self::DEFAULT_NUM_THREADS, enable_stats: false, - custom_handlers: HashMap::new(), + handlers: HintHandlers::default(), mpi_broadcast_fn: None, } } @@ -321,10 +284,10 @@ impl HintsProcessor { self.num_hint.fetch_add(1, Ordering::Relaxed); - // Check if custom handler is registered for custom hints (skip pass-through) + // Fail fast if a custom hint code has no registered handler (skip pass-through) if !hint.is_passthrough { if let HintCode::Custom(code) = hint.hint_code { - if !self.custom_handlers.contains_key(&code) { + if !self.handlers.has_custom_hint_code(code) { return Err(anyhow::anyhow!( "Unknown custom hint code {:#x}: no handler registered", code @@ -463,9 +426,9 @@ impl HintsProcessor { // Spawn processing task for async hints (Noop already handled above) let state = Arc::clone(&self.state); - let custom_handlers = Arc::clone(&self.custom_handlers); + let handlers = Arc::clone(&self.handlers); self.pool.spawn(move || { - Self::worker_thread(state, hint, generation, seq_id, custom_handlers); + Self::worker_thread(state, hint, generation, seq_id, handlers); }); idx += length; @@ -516,13 +479,13 @@ impl HintsProcessor { /// * `hint` - The hint to process /// * `generation` - Generation number for detecting stale workers /// * `seq_id` - Sequence ID for ordering results - /// * `custom_handlers` - Custom hint handlers + /// * `handlers` - Hint Handlers for dispatching the hint fn worker_thread( state: Arc, hint: PrecompileHint, generation: usize, seq_id: usize, - custom_handlers: Arc>, + handlers: Arc, ) { // Check generation first to detect stale workers (before processing) let current_gen = state.generation.load(Ordering::SeqCst); @@ -537,7 +500,7 @@ impl HintsProcessor { if state.error_flag.load(Ordering::Acquire) { Err(anyhow::anyhow!("Processing stopped due to error")) } else { - Self::dispatch_hint(hint, custom_handlers) + handlers.dispatch(hint) } })) .unwrap_or_else(|panic_info| { @@ -594,7 +557,7 @@ impl HintsProcessor { /// Drainer thread that waits for hints to complete and drains ready results from queue. fn drainer_thread( state: Arc, - hints_sink: Arc, + hints_sink: Arc, mpi_broadcast_fn: Option, ) { loop { @@ -698,90 +661,7 @@ impl HintsProcessor { Ok(()) } - /// Dispatches a single hint to its appropriate handler based on hint type. - /// - /// # Arguments - /// - /// * `hint` - The parsed hint to dispatch - /// * `custom_handlers` - Custom hint handlers - /// - /// # Returns - /// - /// The result produced by the selected hint handler. - /// - /// # Note - /// - /// Control codes and Noop hints are handled before this function is called. - #[inline] - fn dispatch_hint( - hint: PrecompileHint, - custom_handlers: Arc>, - ) -> Result> { - match hint.hint_code { - HintCode::BuiltIn(builtin) => { - Self::dispatch_builtin_hint(builtin, hint.data, hint.data_len_bytes) - } - HintCode::Custom(code) => custom_handlers - .get(&code) - .map(|handler| handler(&hint.data)) - .unwrap_or_else(|| Err(anyhow::anyhow!("Unknown custom hint"))), - _ => unreachable!("Control hints handled before dispatch"), - } - } - - #[inline] - fn dispatch_builtin_hint( - hint: BuiltInHint, - data: Vec, - data_len_bytes: usize, - ) -> Result> { - match hint { - // SHA256 Hint Codes - BuiltInHint::Sha256 => sha256_hint(&data, data_len_bytes), - - // BN254 Hint Codes - BuiltInHint::Bn254G1Add => bn254_g1_add_hint(&data), - BuiltInHint::Bn254G1Mul => bn254_g1_mul_hint(&data), - BuiltInHint::Bn254PairingCheck => bn254_pairing_check_hint(&data), - - // Secp256k1 Hints - BuiltInHint::Secp256k1EcdsaAddressRecover => secp256k1_ecdsa_address_recover(&data), - BuiltInHint::Secp256k1EcdsaVerifyAddressRecover => { - secp256k1_ecdsa_verify_address_recover(&data) - } - - // Secp256r1 Hints - BuiltInHint::Secp256r1EcdsaVerify => secp256r1_ecdsa_verify_hint(&data), - - // BLS12-381 Hint Codes - BuiltInHint::Bls12_381G1Add => bls12_381_g1_add_hint(&data), - BuiltInHint::Bls12_381G1Msm => bls12_381_g1_msm_hint(&data), - BuiltInHint::Bls12_381G2Add => bls12_381_g2_add_hint(&data), - BuiltInHint::Bls12_381G2Msm => bls12_381_g2_msm_hint(&data), - BuiltInHint::Bls12_381PairingCheck => bls12_381_pairing_check_hint(&data), - BuiltInHint::Bls12_381FpToG1 => bls12_381_fp_to_g1_hint(&data), - BuiltInHint::Bls12_381Fp2ToG2 => bls12_381_fp2_to_g2_hint(&data), - - // Modular Exponentiation Hint Codes - BuiltInHint::ModExp => modexp_hint(&data), - - // KZG Hint Codes - BuiltInHint::VerifyKzgProof => verify_kzg_proof_hint(&data), - - // Keccak256 Hint Codes - BuiltInHint::Keccak256 => keccak256_hint(&data, data_len_bytes), - - // Blake2b Hint Codes - BuiltInHint::Blake2bCompress => blake2b_compress_hint(&data), - - // Input Hint Codes - BuiltInHint::Input => unreachable!( - "Input hints should be handled separately and not dispatched to workers" - ), - } - } - - fn reset(&self) { + pub fn reset_state(&self) { self.num_hint.store(0, Ordering::Relaxed); self.state.reset(); if let Some(stats) = self.stats.as_ref() { @@ -793,12 +673,12 @@ impl HintsProcessor { self.pending_partial.lock().unwrap().take(); } - pub fn set_has_rom_sm(&self, has_rom_sm: bool) { - self.hints_sink.set_has_rom_sm(has_rom_sm); + pub fn hints_sink(&self) -> Arc { + Arc::clone(&self.hints_sink) } } -impl Drop for HintsProcessor { +impl Drop for HintsProcessor { fn drop(&mut self) { // Signal drainer thread to shut down self.state.shutdown.store(true, Ordering::Release); @@ -813,17 +693,13 @@ impl Drop for HintsProcessor { } } -impl StreamProcessor for HintsProcessor { - fn process(&self, data: &[u64], first_batch: bool) -> Result { +impl StreamProcessor for HintsProcessor { + fn process_hints(&self, data: &[u64], first_batch: bool) -> Result { self.process_hints(data, first_batch) } fn reset(&self) { - self.reset(); - } - - fn as_any(self: Arc) -> Arc { - self + self.reset_state(); } } @@ -853,14 +729,9 @@ mod tests { // Use high value (0x7FFF_xxxx range) to avoid conflicting with any built-in hint codes const TEST_PASSTHROUGH_HINT: u32 = 0x8000_0000 | 0x7FFF_0000; - fn processor() -> HintsProcessor { + fn processor() -> HintsProcessor { HintsProcessor::builder(Arc::new(NullHints), None::>) .num_threads(2) - .custom_hint(0x7FFF_0000, |data| { - // This should never be called for pass-through hints - // but we register it to avoid "no handler" errors - Ok(data.to_vec()) - }) .build() .unwrap() } @@ -969,7 +840,7 @@ mod tests { assert!(result.unwrap_err().to_string().contains("Unknown custom hint code")); // Reset should clear any error state - p.reset(); + p.reset_state(); assert!(!p.state.error_flag.load(Ordering::Acquire)); // Should be able to process new hints after reset (8 bytes = 1 u64) @@ -1370,22 +1241,19 @@ mod tests { const SLOW_HINT: u32 = 0x7FFF_0001; // Delays 10ms const MED_HINT: u32 = 0x7FFF_0002; // Delays 5ms - let p = HintsProcessor::builder(Arc::new(sink), None::>) - .num_threads(8) - .custom_hint(FAST_HINT, |data| { - // No delay - returns immediately - Ok(vec![data[0] * 2]) - }) - .custom_hint(SLOW_HINT, |data| { - // Long delay to complete last + let handlers = HintHandlers::default() + .register(FAST_HINT, |data| Ok(vec![data[0] * 2])) + .register(SLOW_HINT, |data| { thread::sleep(Duration::from_millis(10)); Ok(vec![data[0] * 3]) }) - .custom_hint(MED_HINT, |data| { - // Medium delay + .register(MED_HINT, |data| { thread::sleep(Duration::from_millis(5)); Ok(vec![data[0] * 4]) - }) + }); + let p = HintsProcessor::builder(Arc::new(sink), None::>) + .num_threads(8) + .with_hint_handlers(handlers) .build() .unwrap(); @@ -1668,7 +1536,7 @@ mod tests { } // Reset should clear pending partial - p.reset(); + p.reset_state(); // Verify pending partial is cleared { @@ -1738,18 +1606,17 @@ mod tests { const VARIABLE_HINT: u32 = 0x7FFF_0100; + let handlers = HintHandlers::default().register(VARIABLE_HINT, |data| { + let hash = data[0].wrapping_mul(2654435761); + let delay_ms = hash % 16; + if delay_ms > 0 { + thread::sleep(Duration::from_millis(delay_ms)); + } + Ok(vec![data[0] + 1000]) + }); let p = HintsProcessor::builder(Arc::new(sink), None::>) .num_threads(16) - .custom_hint(VARIABLE_HINT, |data| { - // Pseudo-random delay based on hash of input value (0-15ms range) - // This creates unpredictable completion order across runs - let hash = data[0].wrapping_mul(2654435761); - let delay_ms = hash % 16; - if delay_ms > 0 { - thread::sleep(Duration::from_millis(delay_ms)); - } - Ok(vec![data[0] + 1000]) - }) + .with_hint_handlers(handlers) .build() .unwrap(); diff --git a/precompiles/hints/src/lib.rs b/precompiles/hints/src/lib.rs index 02826fe01..de936e3cc 100644 --- a/precompiles/hints/src/lib.rs +++ b/precompiles/hints/src/lib.rs @@ -1,3 +1,5 @@ +mod hint_handlers; mod hints_processor; +pub use hint_handlers::HintHandlers; pub use hints_processor::{HintsProcessor, MpiBroadcastFn}; diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 6a9f8ac4d..8218763df 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -27,6 +27,7 @@ sha2 = { workspace = true } executor = { workspace = true } bincode = { workspace = true } serde = { workspace = true } +precompiles-hints = { workspace = true } [features] default = [] @@ -36,9 +37,9 @@ gpu = [ "rom-setup/gpu", "executor/gpu", "ziskemu/gpu", - "packed" + "packed", ] -packed = ["proofman-common/packed", "executor/packed"] +packed = ["proofman/packed", "proofman-common/packed", "executor/packed"] stats = [] disable_distributed = [ "proofman/disable_distributed", diff --git a/sdk/src/prover/backend.rs b/sdk/src/prover/backend.rs index a0b90f637..e5ad4dd7e 100644 --- a/sdk/src/prover/backend.rs +++ b/sdk/src/prover/backend.rs @@ -85,7 +85,7 @@ impl ProverBackend { executor.set_asm_resources(asm_resources.clone()); } - executor.set_rom(program_pk.zisk_rom.clone(), program_pk.use_hints); + executor.set_rom(program_pk.zisk_rom.clone(), program_pk.use_hints()); let custom_commits_map = HashMap::from([("rom".to_string(), program_pk.elf_bin_path.clone())]); diff --git a/sdk/src/prover/mod.rs b/sdk/src/prover/mod.rs index 635b0f001..002cce3b4 100644 --- a/sdk/src/prover/mod.rs +++ b/sdk/src/prover/mod.rs @@ -4,6 +4,7 @@ mod emu; pub use asm::*; use backend::*; pub use emu::*; +use precompiles_hints::HintsProcessor; use proofman::{ AggProofs, AggProofsRegister, ProvePhase, ProvePhaseInputs, ProvePhaseResult, SnarkProtocol, WitnessInfo, @@ -13,7 +14,7 @@ use proofman_util::VadcopFinalProof; use sha2::{Digest, Sha256}; use anyhow::{Context, Result}; -use asm_runner::AsmServices; +use asm_runner::{AsmServices, HintsShmem}; use executor::AsmResources; use proofman::PlanningInfo; use serde::{Deserialize, Serialize}; @@ -25,11 +26,9 @@ use std::{ time::Duration, }; use tracing::info; -use zisk_common::io::StreamSource; -use zisk_common::ElfBinaryLike; -use zisk_common::ZiskExecutorTime; use zisk_common::{ - io::StreamProcessor, io::ZiskStdin, ExecutorStatsHandle, StatsCostPerType, ZiskExecutorSummary, + io::StreamSource, io::ZiskStdin, ElfBinaryLike, ExecutorStatsHandle, StatsCostPerType, + ZiskExecutorSummary, ZiskExecutorTime, }; use zisk_core::ZiskRom; @@ -128,12 +127,16 @@ pub struct ZiskProgramPK { pub asm_resources: Option, pub asm_services: Option, pub rank_info: RankInfo, - pub use_hints: bool, + use_hints: bool, } impl ZiskProgramPK { + pub fn use_hints(&self) -> bool { + self.use_hints + } + pub fn register_hints_stream(&self, stream: StreamSource) -> Result<()> { - if self.use_hints { + if self.use_hints() { if let Some(asm_resources) = &self.asm_resources { asm_resources .set_hints_stream_src(stream) @@ -155,12 +158,8 @@ impl ZiskProgramPK { self.asm_services.is_some() } - pub fn get_hints_processor(&self) -> Option> { - if let Some(asm_resources) = &self.asm_resources { - asm_resources.get_hints_processor() - } else { - None - } + pub fn get_hints_processor(&self) -> Option>> { + self.asm_resources.as_ref().and_then(|r| r.get_hints_processor()) } pub fn reset(&self) { From 984af1e67c21155b871eaea4a6a0855f3b915466 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Mar 2026 08:37:40 +0000 Subject: [PATCH 767/782] Adding inputs shmem stub --- .../asm-runner/src/inputs_shmem_stub.rs | 54 +++++++++++++++++++ emulator-asm/asm-runner/src/lib.rs | 3 ++ 2 files changed, 57 insertions(+) create mode 100644 emulator-asm/asm-runner/src/inputs_shmem_stub.rs diff --git a/emulator-asm/asm-runner/src/inputs_shmem_stub.rs b/emulator-asm/asm-runner/src/inputs_shmem_stub.rs new file mode 100644 index 000000000..df551bd9d --- /dev/null +++ b/emulator-asm/asm-runner/src/inputs_shmem_stub.rs @@ -0,0 +1,54 @@ +use std::sync::Arc; + +use zisk_common::io::StreamSink; + +use crate::ControlShmem; + +use anyhow::Result; + +pub struct InputsShmemWriter; + +impl InputsShmemWriter { + pub fn new( + _base_port: Option, + _local_rank: i32, + _unlock_mapped_memory: bool, + _control_writer: Arc, + ) -> Result { + unreachable!( + "InputsShmemWriter::new() is not supported on this platform. Only Linux x86_64 is supported." + ); + } + + pub fn write_input(&self, inputs: &[u8]) -> Result<()> { + unreachable!( + "InputsShmemWriter::write_input() is not supported on this platform. Only Linux x86_64 is supported." + ); + } + + pub fn append_input(&self, inputs: &[u8]) -> Result<()> { + unreachable!( + "InputsShmemWriter::append_input() is not supported on this platform. Only Linux x86_64 is supported." + ); + } + + pub fn reset(&self) { + unreachable!( + "InputsShmemWriter::reset() is not supported on this platform. Only Linux x86_64 is supported." + ); + } +} + +impl StreamSink for InputsShmemWriter { + fn submit(&self, hints: &[u64]) -> anyhow::Result<()> { + unreachable!( + "InputsShmemWriter::submit() is not supported on this platform. Only Linux x86_64 is supported." + ); + } + + fn reset(&self) { + unreachable!( + "InputsShmemWriter::reset() is not supported on this platform. Only Linux x86_64 is supported." + ); + } +} diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index d4c9e022b..fda141fff 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -23,7 +23,10 @@ mod hints_file; mod hints_shmem; #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] mod hints_shmem_stub; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod inputs_shmem; +#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] +mod inputs_shmem_stub; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod multi_shmem; mod shmem_reader; From 1529549c5feae3fbbad481e318f19d40cfee05f8 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Mar 2026 08:44:21 +0000 Subject: [PATCH 768/782] Clippy fixes --- emulator-asm/asm-runner/src/inputs_shmem_stub.rs | 6 +++--- emulator-asm/asm-runner/src/lib.rs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/emulator-asm/asm-runner/src/inputs_shmem_stub.rs b/emulator-asm/asm-runner/src/inputs_shmem_stub.rs index df551bd9d..095d732d0 100644 --- a/emulator-asm/asm-runner/src/inputs_shmem_stub.rs +++ b/emulator-asm/asm-runner/src/inputs_shmem_stub.rs @@ -20,13 +20,13 @@ impl InputsShmemWriter { ); } - pub fn write_input(&self, inputs: &[u8]) -> Result<()> { + pub fn write_input(&self, _inputs: &[u8]) -> Result<()> { unreachable!( "InputsShmemWriter::write_input() is not supported on this platform. Only Linux x86_64 is supported." ); } - pub fn append_input(&self, inputs: &[u8]) -> Result<()> { + pub fn append_input(&self, _inputs: &[u8]) -> Result<()> { unreachable!( "InputsShmemWriter::append_input() is not supported on this platform. Only Linux x86_64 is supported." ); @@ -40,7 +40,7 @@ impl InputsShmemWriter { } impl StreamSink for InputsShmemWriter { - fn submit(&self, hints: &[u64]) -> anyhow::Result<()> { + fn submit(&self, _hints: &[u64]) -> anyhow::Result<()> { unreachable!( "InputsShmemWriter::submit() is not supported on this platform. Only Linux x86_64 is supported." ); diff --git a/emulator-asm/asm-runner/src/lib.rs b/emulator-asm/asm-runner/src/lib.rs index fda141fff..d8f000124 100644 --- a/emulator-asm/asm-runner/src/lib.rs +++ b/emulator-asm/asm-runner/src/lib.rs @@ -56,7 +56,10 @@ pub use hints_file::*; pub use hints_shmem::*; #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] pub use hints_shmem_stub::*; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub use inputs_shmem::*; +#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] +pub use inputs_shmem_stub::*; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub use multi_shmem::*; pub use shmem_reader::*; From 70e1d7306b846c82fbe030afc32c65a0f349c850 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Mar 2026 08:58:13 +0000 Subject: [PATCH 769/782] More clippy fixes --- emulator-asm/asm-runner/src/hints_shmem_stub.rs | 9 +++++++++ executor/src/asm_resources.rs | 1 + executor/src/emu_asm_stub.rs | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/emulator-asm/asm-runner/src/hints_shmem_stub.rs b/emulator-asm/asm-runner/src/hints_shmem_stub.rs index d8c0afcbe..6aed6b850 100644 --- a/emulator-asm/asm-runner/src/hints_shmem_stub.rs +++ b/emulator-asm/asm-runner/src/hints_shmem_stub.rs @@ -1,3 +1,4 @@ +use crate::{AsmService, ControlShmem}; use anyhow::Result; use zisk_common::io::StreamSink; @@ -9,11 +10,19 @@ impl HintsShmem { _base_port: Option, _local_rank: i32, _unlock_mapped_memory: bool, + _control_writer: Arc, + _active_services: &[AsmService], ) -> Result { unreachable!( "HintsShmem::new() is not supported on this platform. Only Linux x86_64 is supported." ); } + + pub fn set_active_services(&self, _active_services: &[AsmService]) { + unreachable!( + "HintsShmem::set_active_services() is not supported on this platform. Only Linux x86_64 is supported." + ); + } } impl StreamSink for HintsShmem { diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index 816818567..b78929612 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -37,6 +37,7 @@ pub struct AsmResources { config: AsmResourcesConfig, /// Shared memory for writing inputs to the assembly microservices. + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub inputs_shmem_writer: Arc, /// Pipeline for handling precompile hints. diff --git a/executor/src/emu_asm_stub.rs b/executor/src/emu_asm_stub.rs index f3039fc72..645612db7 100644 --- a/executor/src/emu_asm_stub.rs +++ b/executor/src/emu_asm_stub.rs @@ -39,14 +39,14 @@ impl EmulatorAsm { _use_hints: bool, _stats: &ExecutorStatsHandle, _caller_stats_scope: &StatsScope, - ) -> ( + ) -> Result<( Vec, DeviceMetricsList, NestedDeviceMetricsList, Option>, Option>, u64, - ) { + )> { unimplemented!("AsmRunner is only supported on Linux x86_64 platforms."); } From 341a7a6880bee5de11a825f9cb2033393f3909c3 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Mar 2026 09:06:41 +0000 Subject: [PATCH 770/782] Missing Arc Stub --- emulator-asm/asm-runner/src/hints_shmem_stub.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/emulator-asm/asm-runner/src/hints_shmem_stub.rs b/emulator-asm/asm-runner/src/hints_shmem_stub.rs index 6aed6b850..833904922 100644 --- a/emulator-asm/asm-runner/src/hints_shmem_stub.rs +++ b/emulator-asm/asm-runner/src/hints_shmem_stub.rs @@ -1,5 +1,6 @@ use crate::{AsmService, ControlShmem}; use anyhow::Result; +use std::sync::Arc; use zisk_common::io::StreamSink; /// HintsShmem struct manages the writing of processed precompile hints to shared memory. From 9f72ba90c1a64ed868ee83e379bd66bbe5b13a9d Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Mar 2026 09:21:23 +0000 Subject: [PATCH 771/782] Final fixes --- emulator-asm/asm-runner/src/hints_shmem_stub.rs | 2 +- executor/src/asm_resources.rs | 2 -- executor/src/emu_asm_stub.rs | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/emulator-asm/asm-runner/src/hints_shmem_stub.rs b/emulator-asm/asm-runner/src/hints_shmem_stub.rs index 833904922..0927b7735 100644 --- a/emulator-asm/asm-runner/src/hints_shmem_stub.rs +++ b/emulator-asm/asm-runner/src/hints_shmem_stub.rs @@ -19,7 +19,7 @@ impl HintsShmem { ); } - pub fn set_active_services(&self, _active_services: &[AsmService]) { + pub fn set_active_services(&self, _active_services: &[AsmService]) -> Result<()> { unreachable!( "HintsShmem::set_active_services() is not supported on this platform. Only Linux x86_64 is supported." ); diff --git a/executor/src/asm_resources.rs b/executor/src/asm_resources.rs index b78929612..b56752654 100644 --- a/executor/src/asm_resources.rs +++ b/executor/src/asm_resources.rs @@ -37,7 +37,6 @@ pub struct AsmResources { config: AsmResourcesConfig, /// Shared memory for writing inputs to the assembly microservices. - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] pub inputs_shmem_writer: Arc, /// Pipeline for handling precompile hints. @@ -124,7 +123,6 @@ impl AsmResources { mo_shmem_reader: Arc::new(Mutex::new(asm_shmem_mo)), #[cfg(all(target_os = "linux", target_arch = "x86_64"))] rh_shmem_reader: Arc::new(Mutex::new(None)), - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] inputs_shmem_writer, }) } diff --git a/executor/src/emu_asm_stub.rs b/executor/src/emu_asm_stub.rs index 645612db7..aabefe8e6 100644 --- a/executor/src/emu_asm_stub.rs +++ b/executor/src/emu_asm_stub.rs @@ -4,6 +4,7 @@ use std::{ }; use crate::{DeviceMetricsList, NestedDeviceMetricsList, StaticSMBundle}; +use anyhow::Result; use asm_runner::{AsmRunnerMO, AsmRunnerRH}; use crate::AsmResources; From a58a1e17a362011e9e0ffb33e838d628088823e9 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Mar 2026 11:10:44 +0000 Subject: [PATCH 772/782] Cargo update --- Cargo.lock | 105 ++++++++--------- Cargo.toml | 16 +-- examples/Cargo.lock | 110 ++++++++---------- .../sha-hasher/host/bin/verify-constraints.rs | 9 +- 4 files changed, 109 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d63a866b..8a7cd2353 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -610,7 +610,7 @@ dependencies = [ "colored", "dirs", "executor", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "futures", "indicatif", "mpi", @@ -1133,9 +1133,9 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "num-traits", ] @@ -1455,7 +1455,7 @@ dependencies = [ "asm-runner", "crossbeam", "data-bus", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "itertools 0.14.0", "mem-common", "mem-planner-cpp", @@ -1510,18 +1510,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" -dependencies = [ - "cfg-if", - "num-bigint", - "paste", - "serde", -] - -[[package]] -name = "fields" -version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "cfg-if", "num-bigint", @@ -2394,7 +2383,7 @@ name = "mem-common" version = "0.16.0" dependencies = [ "clap", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "num-traits", "proofman-common", @@ -2659,9 +2648,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -2821,10 +2810,10 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "num-traits", "proofman-common", @@ -2943,7 +2932,7 @@ dependencies = [ "ark-secp256k1", "ark-secp256r1", "ark-std", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "lazy_static", "lib-c", "mem-common", @@ -2979,7 +2968,7 @@ dependencies = [ "ark-ff", "ark-secp256k1", "ark-std", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "lazy_static", "lib-c", "mem-common", @@ -3010,7 +2999,7 @@ dependencies = [ name = "precomp-big-int" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "generic-array", "lib-c", "mem-common", @@ -3031,7 +3020,7 @@ dependencies = [ name = "precomp-blake2" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -3050,7 +3039,7 @@ dependencies = [ name = "precomp-dma" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "generic-array", "lib-c", "mem-common", @@ -3074,7 +3063,7 @@ name = "precomp-keccakf" version = "0.16.0" dependencies = [ "circuit", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "path-clean", "pil-std-lib", "precompiles-common", @@ -3094,7 +3083,7 @@ dependencies = [ name = "precomp-poseidon2" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -3114,7 +3103,7 @@ dependencies = [ name = "precomp-sha256f" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -3133,7 +3122,7 @@ dependencies = [ name = "precompiles-common" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "sm-mem", "zisk-common", @@ -3205,7 +3194,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "bincode", "blake3", @@ -3216,7 +3205,7 @@ dependencies = [ "crossbeam-channel", "csv", "curves", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "libloading", "mpi", "num-bigint", @@ -3240,7 +3229,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "bincode", "borsh", @@ -3250,7 +3239,7 @@ dependencies = [ "crossbeam-queue", "csv", "env", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "lazy_static", "libloading", "mpi", @@ -3271,9 +3260,9 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "itoa", "proofman-common", "proofman-starks-lib-c", @@ -3284,7 +3273,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "proc-macro2", "quote", @@ -3294,7 +3283,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "crossbeam-channel", "tracing", @@ -3303,7 +3292,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "bincode", "bytemuck", @@ -3315,10 +3304,10 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "bytemuck", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "proofman-util", "rayon", "tracing", @@ -3696,7 +3685,7 @@ dependencies = [ "anyhow", "blake3", "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "proofman-common", "sm-rom", "tracing", @@ -4118,7 +4107,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "sm-arith" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "pil-std-lib", "proofman-common", @@ -4138,7 +4127,7 @@ dependencies = [ name = "sm-binary" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "pil-std-lib", "proofman-common", @@ -4158,7 +4147,7 @@ name = "sm-frequent-ops" version = "0.16.0" dependencies = [ "clap", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "proofman-common", "proofman-util", @@ -4172,7 +4161,7 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "num-bigint", "pil-std-lib", @@ -4191,7 +4180,7 @@ dependencies = [ name = "sm-mem" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "num-bigint", "num-traits", @@ -4212,7 +4201,7 @@ name = "sm-rom" version = "0.16.0" dependencies = [ "asm-runner", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "itertools 0.14.0", "proofman-common", "proofman-macros", @@ -5819,10 +5808,10 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#05531ae9f2f2a9003fbd8de044a6697709d34a13" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "libloading", "proofman-common", "proofman-util", @@ -6015,7 +6004,7 @@ version = "0.16.0" dependencies = [ "anyhow", "bincode", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "libc", "mpi", "proofman", @@ -6038,7 +6027,7 @@ name = "zisk-core" version = "0.16.0" dependencies = [ "elf", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "lib-c", "lib-float", "paste", @@ -6141,7 +6130,7 @@ dependencies = [ "clap", "colored", "config", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "precompiles-hints", "proofman", "proofman-common", @@ -6167,7 +6156,7 @@ dependencies = [ name = "zisk-pil" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "proofman-common", "proofman-macros", "rayon", @@ -6184,7 +6173,7 @@ dependencies = [ "bincode", "colored", "executor", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "precompiles-hints", "proofman", "proofman-common", @@ -6223,7 +6212,7 @@ dependencies = [ "clap", "criterion 0.5.1", "data-bus", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "memmap2", "num-format", @@ -6255,7 +6244,7 @@ dependencies = [ "ctor", "dlmalloc", "embedded-alloc", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", + "fields", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -6283,7 +6272,7 @@ dependencies = [ "anyhow", "bincode", "cfg-if", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "getrandom 0.2.17", "lazy_static", "lib-c", diff --git a/Cargo.toml b/Cargo.toml index 6de4573e6..2704f738f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,14 +112,14 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "feature/write-proof" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } # Proofman Local development # proofman = { path = "../pil2-proofman/proofman" } # proofman-common = { path = "../pil2-proofman/common" } diff --git a/examples/Cargo.lock b/examples/Cargo.lock index ce3c3c79f..b5e1a962b 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -844,9 +844,9 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "num-traits", ] @@ -1073,7 +1073,7 @@ dependencies = [ "asm-runner", "crossbeam", "data-bus", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "itertools 0.14.0", "mem-common", "mem-planner-cpp", @@ -1128,18 +1128,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" -dependencies = [ - "cfg-if", - "num-bigint", - "paste", - "serde", -] - -[[package]] -name = "fields" -version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#98d42f020b419b71f3995a5f6f314abd4d32992c" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "cfg-if", "num-bigint", @@ -1693,7 +1682,7 @@ name = "mem-common" version = "0.16.0" dependencies = [ "clap", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "num-traits", "proofman-common", @@ -1966,9 +1955,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -2036,10 +2025,10 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "num-traits", "proofman-common", @@ -2098,7 +2087,7 @@ dependencies = [ "ark-secp256k1", "ark-secp256r1", "ark-std", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "lazy_static", "lib-c", "mem-common", @@ -2134,7 +2123,7 @@ dependencies = [ "ark-ff", "ark-secp256k1", "ark-std", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "lazy_static", "lib-c", "mem-common", @@ -2165,7 +2154,7 @@ dependencies = [ name = "precomp-big-int" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "generic-array", "lib-c", "mem-common", @@ -2186,7 +2175,7 @@ dependencies = [ name = "precomp-blake2" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -2205,7 +2194,7 @@ dependencies = [ name = "precomp-dma" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "generic-array", "lib-c", "mem-common", @@ -2229,7 +2218,7 @@ name = "precomp-keccakf" version = "0.16.0" dependencies = [ "circuit", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "path-clean", "pil-std-lib", "precompiles-common", @@ -2249,7 +2238,7 @@ dependencies = [ name = "precomp-poseidon2" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -2269,7 +2258,7 @@ dependencies = [ name = "precomp-sha256f" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "pil-std-lib", "precompiles-common", @@ -2288,7 +2277,7 @@ dependencies = [ name = "precompiles-common" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "sm-mem", "zisk-common", @@ -2359,7 +2348,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "bincode", "blake3", @@ -2370,7 +2359,7 @@ dependencies = [ "crossbeam-channel", "csv", "curves", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "libloading", "mpi", "num-bigint", @@ -2394,7 +2383,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "bincode", "borsh", @@ -2404,7 +2393,7 @@ dependencies = [ "crossbeam-queue", "csv", "env", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "lazy_static", "libloading", "mpi", @@ -2425,9 +2414,9 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "itoa", "proofman-common", "proofman-starks-lib-c", @@ -2438,7 +2427,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "proc-macro2", "quote", @@ -2448,7 +2437,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "crossbeam-channel", "tracing", @@ -2457,7 +2446,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "bincode", "bytemuck", @@ -2469,10 +2458,10 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "bytemuck", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "proofman-util", "rayon", "tracing", @@ -2712,7 +2701,7 @@ dependencies = [ "anyhow", "blake3", "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "proofman-common", "sm-rom", "tracing", @@ -2883,9 +2872,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -3075,7 +3064,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "sm-arith" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "pil-std-lib", "proofman-common", @@ -3095,7 +3084,7 @@ dependencies = [ name = "sm-binary" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "pil-std-lib", "proofman-common", @@ -3115,7 +3104,7 @@ name = "sm-frequent-ops" version = "0.16.0" dependencies = [ "clap", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "num-bigint", "proofman-common", "proofman-util", @@ -3129,7 +3118,7 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "num-bigint", "pil-std-lib", @@ -3148,7 +3137,7 @@ dependencies = [ name = "sm-mem" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "num-bigint", "num-traits", @@ -3169,7 +3158,7 @@ name = "sm-rom" version = "0.16.0" dependencies = [ "asm-runner", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "itertools 0.14.0", "proofman-common", "proofman-macros", @@ -3298,9 +3287,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", @@ -4443,10 +4432,10 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof#f352400887bc87929df4b143671e6abd78cfd072" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" dependencies = [ "colored", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "libloading", "proofman-common", "proofman-util", @@ -4628,7 +4617,7 @@ version = "0.16.0" dependencies = [ "anyhow", "bincode", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "libc", "mpi", "proofman", @@ -4651,7 +4640,7 @@ name = "zisk-core" version = "0.16.0" dependencies = [ "elf", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "lib-c", "lib-float", "paste", @@ -4693,7 +4682,7 @@ dependencies = [ name = "zisk-pil" version = "0.16.0" dependencies = [ - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "proofman-common", "proofman-macros", "rayon", @@ -4710,7 +4699,8 @@ dependencies = [ "bincode", "colored", "executor", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", + "precompiles-hints", "proofman", "proofman-common", "proofman-util", @@ -4739,7 +4729,7 @@ version = "0.16.0" dependencies = [ "clap", "data-bus", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "mem-common", "memmap2", "num-format", @@ -4768,7 +4758,7 @@ dependencies = [ "bytes", "cfg-if", "ctor", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", + "fields", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -4795,7 +4785,7 @@ dependencies = [ "anyhow", "bincode", "cfg-if", - "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=feature%2Fwrite-proof)", + "fields", "getrandom 0.2.17", "lazy_static", "lib-c", diff --git a/examples/sha-hasher/host/bin/verify-constraints.rs b/examples/sha-hasher/host/bin/verify-constraints.rs index 7fe165784..0013596b7 100644 --- a/examples/sha-hasher/host/bin/verify-constraints.rs +++ b/examples/sha-hasher/host/bin/verify-constraints.rs @@ -1,7 +1,8 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use zisk_sdk::{elf_path, ElfBinaryFromFile, ProverClient, ZiskStdin}; +use zisk_sdk::{include_elf, ElfBinary, ProverClient, ZiskStdin}; + +pub const ELF: ElfBinary = include_elf!("sha-hasher-guest"); #[derive(Serialize, Deserialize, Debug)] struct Output { @@ -13,8 +14,6 @@ struct Output { fn main() -> Result<()> { println!("Starting ZisK Prover Client..."); - let elf = ElfBinaryFromFile::new(&PathBuf::from(elf_path!("sha-hasher-guest")), false)?; - let current_dir = std::env::current_dir()?; let stdin = ZiskStdin::from_file(current_dir.join("sha-hasher/host/tmp/verify_constraints_input.bin"))?; @@ -27,7 +26,7 @@ fn main() -> Result<()> { let client = ProverClient::builder().emu().verify_constraints().build().unwrap(); println!("Setting up program..."); - let (pk, _vkey) = client.setup(&elf)?; + let (pk, _vkey) = client.setup(&ELF)?; println!("Setup completed successfully"); println!("Verifying constraints (no proof generation)..."); From e6d051e4c1de3469da5a9e798584ea64065c41e9 Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Mar 2026 11:22:26 +0000 Subject: [PATCH 773/782] Cargo update --- Cargo.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a7cd2353..4c4019683 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,7 +1133,7 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" dependencies = [ "fields", "num-bigint", @@ -1510,7 +1510,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fields" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" dependencies = [ "cfg-if", "num-bigint", @@ -2810,7 +2810,7 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" dependencies = [ "colored", "fields", @@ -3194,7 +3194,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" dependencies = [ "bincode", "blake3", @@ -3229,7 +3229,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" dependencies = [ "bincode", "borsh", @@ -3260,7 +3260,7 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" dependencies = [ "fields", "itoa", @@ -3273,7 +3273,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" dependencies = [ "proc-macro2", "quote", @@ -3283,7 +3283,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" dependencies = [ "crossbeam-channel", "tracing", @@ -3292,7 +3292,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" dependencies = [ "bincode", "bytemuck", @@ -3304,7 +3304,7 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" dependencies = [ "bytemuck", "fields", @@ -5808,7 +5808,7 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#c590eccceb4a88e7f65f998f02f59e3b487d0317" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" dependencies = [ "colored", "fields", From 9a7e673468750ade2ca9cdd776c01b9df6c6b870 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Thu, 12 Mar 2026 13:52:17 +0100 Subject: [PATCH 774/782] add legacy-inputs options to ziskemu --- emulator/src/emu_options.rs | 4 ++++ emulator/src/emulator.rs | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/emulator/src/emu_options.rs b/emulator/src/emu_options.rs index 174ca00ee..078690f8c 100644 --- a/emulator/src/emu_options.rs +++ b/emulator/src/emu_options.rs @@ -27,6 +27,9 @@ pub struct EmuOptions { /// Sets the input data file path #[clap(short, long, value_name = "INPUT_FILE")] pub inputs: Option, + /// Sets the legacy input data file path + #[clap(long, value_name = "LEGACY_INPUT_FILE")] + pub legacy_inputs: Option, /// Sets the output data file path #[clap(short, long, value_name = "OUTPUT_FILE")] pub output: Option, @@ -176,6 +179,7 @@ impl Default for EmuOptions { max_input_mem: MAX_INPUT_SIZE, steps: false, with_progress: false, + legacy_inputs: None, } } } diff --git a/emulator/src/emulator.rs b/emulator/src/emulator.rs index f9eb41b91..e50bd6ac3 100644 --- a/emulator/src/emulator.rs +++ b/emulator/src/emulator.rs @@ -325,6 +325,29 @@ impl Emulator for ZiskEmulator { inputs = fs::read(path).expect("Could not read inputs file"); } + // Build an input data buffer either from the provided inputs path (if provided), or leave + // it empty + if options.legacy_inputs.is_some() { + if options.inputs.is_some() { + return Err(ZiskEmulatorErr::WrongArguments(ErrWrongArguments::new( + "Legacy input file and input file options are incompatible", + ))); + } + // Read inputs data from the provided inputs path + let path = PathBuf::from(options.legacy_inputs.clone().unwrap()); + let file_data = fs::read(path).expect("Could not read inputs file"); + + // Build legacy format: 8 bytes length (native endianness) + file content + padding to multiple of 8 + let file_len = file_data.len() as u64; + let total_len = 8 + file_data.len(); + let padding = (8 - (total_len % 8)) % 8; + + inputs = Vec::with_capacity(total_len + padding); + inputs.extend_from_slice(&file_len.to_ne_bytes()); + inputs.extend_from_slice(&file_data); + inputs.resize(total_len + padding, 0); + } + // If a rom file path is provided, load the rom from it if options.rom.is_some() { // Get the rom file name From fe6562134324b5e3c6b97cbea563bcd239e811ef Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Mar 2026 13:50:40 +0000 Subject: [PATCH 775/782] Adding legacy call to convert inputs to be suitable with newer versions --- cli/src/bin/cargo-zisk.rs | 8 ++++-- cli/src/commands/convert_input.rs | 44 +++++++++++++++++++++++++++++++ cli/src/commands/mod.rs | 2 ++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 cli/src/commands/convert_input.rs diff --git a/cli/src/bin/cargo-zisk.rs b/cli/src/bin/cargo-zisk.rs index a45883e63..3d4893d5a 100644 --- a/cli/src/bin/cargo-zisk.rs +++ b/cli/src/bin/cargo-zisk.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Context, Result}; use cargo_zisk::commands::{ - ZiskBuild, ZiskCheckSetup, ZiskClean, ZiskExecute, ZiskProve, ZiskProveSnark, ZiskRomSetup, - ZiskRun, ZiskSdk, ZiskStats, ZiskVerify, ZiskVerifyConstraints, ZiskVerifySnark, + ZiskBuild, ZiskCheckSetup, ZiskClean, ZiskConvertInput, ZiskExecute, ZiskProve, ZiskProveSnark, + ZiskRomSetup, ZiskRun, ZiskSdk, ZiskStats, ZiskVerify, ZiskVerifyConstraints, ZiskVerifySnark, }; use clap::Parser; use zisk_build::ZISK_VERSION_MESSAGE; @@ -17,6 +17,7 @@ use zisk_build::ZISK_VERSION_MESSAGE; )] pub enum Cargo { Build(ZiskBuild), + ConvertInput(ZiskConvertInput), CheckSetup(ZiskCheckSetup), Clean(ZiskClean), Execute(ZiskExecute), @@ -39,6 +40,9 @@ fn main() -> Result<()> { Cargo::Build(cmd) => { cmd.run().context("Error executing Build command")?; } + Cargo::ConvertInput(cmd) => { + cmd.run().context("Error executing ConvertInput command")?; + } Cargo::CheckSetup(cmd) => { cmd.run().context("Error executing CheckSetup command")?; } diff --git a/cli/src/commands/convert_input.rs b/cli/src/commands/convert_input.rs new file mode 100644 index 000000000..807e77172 --- /dev/null +++ b/cli/src/commands/convert_input.rs @@ -0,0 +1,44 @@ +use anyhow::Result; +use clap::Parser; +use std::path::PathBuf; + +use crate::ux::print_banner; +use crate::ux::print_banner_field; +use zisk_sdk::{setup_logger, ZiskStdin}; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +#[command(propagate_version = true)] +pub struct ZiskConvertInput { + #[clap(short = 'i', long)] + pub input_path: PathBuf, + + /// Output path + #[clap(short = 'o', long)] + pub output_path: PathBuf, + + #[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity level")] + pub verbose: u8, +} + +impl ZiskConvertInput { + pub fn run(&self) -> Result<()> { + setup_logger(self.verbose.into()); + + print_banner(); + + print_banner_field("Command", "Convert Input"); + print_banner_field("Input", self.input_path.display()); + print_banner_field("Output", self.output_path.display()); + + let input = std::fs::read(&self.input_path)?; + let zisk_stdin = ZiskStdin::new(); + zisk_stdin.write_slice(&input); + zisk_stdin.read_bytes(); + zisk_stdin.save(&self.output_path)?; + + println!("Input conversion completed successfully!"); + + Ok(()) + } +} diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index d366eba74..a06594528 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -2,6 +2,7 @@ mod build; mod check_setup; mod clean; mod common; +mod convert_input; mod execute; mod prove; mod prove_snark; @@ -17,6 +18,7 @@ pub use build::*; pub use check_setup::*; pub use clean::*; pub use common::*; +pub use convert_input::*; pub use execute::*; pub use prove::*; pub use prove_snark::*; From 019a3ea8b68d3b2cf797d8808ba10f29163be29f Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Mar 2026 14:24:27 +0000 Subject: [PATCH 776/782] Adding option to convert full directories --- cli/src/commands/convert_input.rs | 167 +++++++++++++++++++++++++++--- ziskup/ziskup | 75 ++++++++++++++ 2 files changed, 230 insertions(+), 12 deletions(-) diff --git a/cli/src/commands/convert_input.rs b/cli/src/commands/convert_input.rs index 807e77172..9cdc398dd 100644 --- a/cli/src/commands/convert_input.rs +++ b/cli/src/commands/convert_input.rs @@ -1,6 +1,7 @@ -use anyhow::Result; +use anyhow::{bail, Result}; use clap::Parser; -use std::path::PathBuf; +use std::fs; +use std::path::{Path, PathBuf}; use crate::ux::print_banner; use crate::ux::print_banner_field; @@ -10,12 +11,25 @@ use zisk_sdk::{setup_logger, ZiskStdin}; #[command(version, about, long_about = None)] #[command(propagate_version = true)] pub struct ZiskConvertInput { + /// Input file to convert #[clap(short = 'i', long)] - pub input_path: PathBuf, + pub input_file: Option, - /// Output path + /// Output file path #[clap(short = 'o', long)] - pub output_path: PathBuf, + pub output_file: Option, + + /// Input directory containing files to convert + #[clap(short = 'd', long)] + pub input_dir: Option, + + /// Output directory for converted files + #[clap(short = 't', long)] + pub output_dir: Option, + + /// Process subdirectories recursively + #[clap(short = 'r', long)] + pub recursive: bool, #[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity level")] pub verbose: u8, @@ -26,18 +40,147 @@ impl ZiskConvertInput { setup_logger(self.verbose.into()); print_banner(); - print_banner_field("Command", "Convert Input"); - print_banner_field("Input", self.input_path.display()); - print_banner_field("Output", self.output_path.display()); - let input = std::fs::read(&self.input_path)?; + // Validate arguments + let use_files = self.input_file.is_some() || self.output_file.is_some(); + let use_dirs = self.input_dir.is_some() || self.output_dir.is_some(); + + if use_files && use_dirs { + bail!("Cannot use both file and directory modes. Use either -i/-o or --input-dir/--output-dir"); + } + + if use_files { + // File mode - both input and output files must be provided + let input_file = self.input_file.as_ref().ok_or_else(|| { + anyhow::anyhow!("Input file (-i) is required when using file mode") + })?; + let output_file = self.output_file.as_ref().ok_or_else(|| { + anyhow::anyhow!("Output file (-o) is required when using file mode") + })?; + + print_banner_field("Input File", input_file.display()); + print_banner_field("Output File", output_file.display()); + + self.convert_file(input_file, output_file)?; + } else if use_dirs { + // Directory mode - both input and output dirs must be provided + let input_dir = self.input_dir.as_ref().ok_or_else(|| { + anyhow::anyhow!( + "Input directory (--input-dir) is required when using directory mode" + ) + })?; + let output_dir = self.output_dir.as_ref().ok_or_else(|| { + anyhow::anyhow!( + "Output directory (--output-dir) is required when using directory mode" + ) + })?; + + print_banner_field("Input Directory", input_dir.display()); + print_banner_field("Output Directory", output_dir.display()); + print_banner_field("Recursive", if self.recursive { "Yes" } else { "No" }); + + self.convert_directory(input_dir, output_dir)?; + } else { + bail!( + "Either file mode (-i/-o) or directory mode (--input-dir/--output-dir) is required" + ); + } + + println!("\n✓ Input conversion completed successfully!"); + + Ok(()) + } + + fn convert_file(&self, input_path: &PathBuf, output_path: &Path) -> Result<()> { + println!("Converting: {} -> {}", input_path.display(), output_path.display()); + + let input = std::fs::read(input_path)?; let zisk_stdin = ZiskStdin::new(); zisk_stdin.write_slice(&input); - zisk_stdin.read_bytes(); - zisk_stdin.save(&self.output_path)?; + zisk_stdin.save(output_path)?; + + Ok(()) + } + + fn convert_directory(&self, input_dir: &PathBuf, output_dir: &PathBuf) -> Result<()> { + if !input_dir.is_dir() { + bail!("Input directory does not exist or is not a directory: {}", input_dir.display()); + } + + fs::create_dir_all(output_dir)?; + + let mut files_converted = 0; + + if self.recursive { + self.convert_directory_recursive( + input_dir, + output_dir, + input_dir, + &mut files_converted, + )?; + } else { + self.convert_directory_flat(input_dir, output_dir, &mut files_converted)?; + } + + println!("\n✓ Converted {} file(s)", files_converted); + + Ok(()) + } + + fn convert_directory_flat( + &self, + input_dir: &PathBuf, + output_dir: &Path, + files_converted: &mut usize, + ) -> Result<()> { + for entry in fs::read_dir(input_dir)? { + let entry = entry?; + let path = entry.path(); + + if path.is_file() { + let file_name = path.file_name().ok_or_else(|| { + anyhow::anyhow!("Failed to get filename for: {}", path.display()) + })?; + let output_path = output_dir.join(file_name); + + self.convert_file(&path, &output_path)?; + *files_converted += 1; + } + } + + Ok(()) + } + + fn convert_directory_recursive( + &self, + current_dir: &PathBuf, + output_base: &PathBuf, + input_base: &PathBuf, + files_converted: &mut usize, + ) -> Result<()> { + for entry in fs::read_dir(current_dir)? { + let entry = entry?; + let path = entry.path(); + + if path.is_file() { + // Compute relative path from input base + let relative_path = path + .strip_prefix(input_base) + .map_err(|_| anyhow::anyhow!("Failed to compute relative path"))?; + let output_path = output_base.join(relative_path); + + // Create parent directory if needed + if let Some(parent) = output_path.parent() { + fs::create_dir_all(parent)?; + } - println!("Input conversion completed successfully!"); + self.convert_file(&path, &output_path)?; + *files_converted += 1; + } else if path.is_dir() { + self.convert_directory_recursive(&path, output_base, input_base, files_converted)?; + } + } Ok(()) } diff --git a/ziskup/ziskup b/ziskup/ziskup index 07844b47a..969cd9ea0 100755 --- a/ziskup/ziskup +++ b/ziskup/ziskup @@ -16,6 +16,12 @@ mkdir -p "${ZISK_BIN_DIR}" main() { need_cmd curl + # Check if this is a setup_snark command + if [[ "$1" == "setup_snark" ]]; then + setup_snark_command + exit 0 + fi + while [[ -n $1 ]]; do case $1 in --) @@ -265,6 +271,9 @@ OPTIONS: --provingkey Install the proving key --verifykey Install the verify key --nokey No proving/verify key installation + +COMMANDS: + setup_snark Install SNARK proving key (provingKeySnark) EOF } @@ -365,6 +374,72 @@ install_setup() { fi } +# Install the SNARK proving key +install_setup_snark() { + local KEY_FILE="zisk-provingkey-pre-0.16.0-plonk-inputs.tar.gz" + + step "Downloading SNARK proving key version ${SETUP_VERSION}. This may take a while..." + ensure download "${BUCKET_URL}/${KEY_FILE}" "${KEY_FILE}" + ensure download "${BUCKET_URL}/${KEY_FILE}.md5" "${KEY_FILE}.md5" + + # Verify the md5 checksum + ensure md5sum -c "${KEY_FILE}.md5" + + # Delete old provingKey, verifyKey and cache folders + rm -rf "${HOME}/.zisk/provingKeySnark" + + # Extract the key + step "Installing SNARK proving key version ${SETUP_VERSION}..." + if [ "${PLATFORM}" = "linux" ]; then + ensure tar --overwrite -xf "${KEY_FILE}" -C "${HOME}/.zisk" + else + ensure tar -xf "${KEY_FILE}" -C "${HOME}/.zisk" + fi + + rm -f "${KEY_FILE}" + rm -f "${KEY_FILE}.md5" + say "Installed SNARK proving key version ${SETUP_VERSION}" +} + +# Setup SNARK command handler +setup_snark_command() { + # Print banner + banner + + # Detect platform + uname_s=$(uname -s) + PLATFORM=$(tolower "${uname_s}") + + case "${PLATFORM}" in + linux) ;; + darwin | mac*) ;; + *) + err "unsupported platform ${PLATFORM}" + exit 1 + ;; + esac + + # Determine version from cargo-zisk + if [[ ! -f "${CARGO_ZISK}" ]]; then + err "cargo-zisk not found. Please install ZisK first with 'ziskup'" + exit 1 + fi + + ZISK_VERSION=$("${CARGO_ZISK}" --version | awk '{print $2}') + + # Parse version to get setup version + IFS='.' read -r major minor patch <<< "${ZISK_VERSION}" + SETUP_VERSION="${major}.${minor}.0" + + step "Installing SNARK proving key for ZisK version ${ZISK_VERSION}..." + + # Install the SNARK proving key + install_setup_snark + + step "Done! SNARK proving key installation completed." + echo +} + # Banner Function for ZisK banner() { printf " From 8cdb49f92b76b4e47a7941abd0614f6bed320aff Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 12 Mar 2026 14:33:08 +0000 Subject: [PATCH 777/782] Fix pr gha inputs --- .github/workflows/pr.yml | 4 ++-- tools/test-env/.env | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6e596cdf7..f8235bec9 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -100,7 +100,7 @@ jobs: - name: Verify Constraints Ethereum block env: DISABLE_PROVE: "1" - BLOCK_INPUTS: "mainnet_24341018_290_29_zec_reth.bin" + BLOCK_INPUTS: "mainnet_24626900_221_16_zec_reth.bin" run: | cd "$GITHUB_WORKSPACE/tools/test-env" ./test_eth_block.sh @@ -108,7 +108,7 @@ jobs: - name: Prove Ethereum block env: DISABLE_ROM_SETUP: "1" - BLOCK_INPUTS_DISTRIBUTED: "mainnet_24341035_74_5_zec_reth.bin" + BLOCK_INPUTS_DISTRIBUTED: "mainnet_24628607_66_7_zec_reth.bin" run: | cd "$GITHUB_WORKSPACE/tools/test-env" ./test_eth_block.sh diff --git a/tools/test-env/.env b/tools/test-env/.env index 8f357ad44..a7d4e0070 100644 --- a/tools/test-env/.env +++ b/tools/test-env/.env @@ -11,8 +11,8 @@ SETUP_ADD_DYLIBS=0 PP_INPUTS=pp_input_1_1.bin PP_INPUTS_DISTRIBUTED=pp_input_1_1.bin,pp_input_20_20.bin -BLOCK_INPUTS=mainnet_24341035_74_5_zec_reth.bin -BLOCK_INPUTS_DISTRIBUTED=mainnet_24341035_74_5_zec_reth.bin,mainnet_24341018_290_29_zec_reth.bin +BLOCK_INPUTS=mainnet_24628607_66_7_zec_reth.bin +BLOCK_INPUTS_DISTRIBUTED=mainnet_24628607_66_7_zec_reth.bin,mainnet_24626900_221_16_zec_reth.bin BLOCK_FOLDER= DISTRIBUTED_PROCESSES=2 From 604fefeb58f2ab8b56dfafb6ace69b1b26e1562a Mon Sep 17 00:00:00 2001 From: RogerTaule Date: Thu, 12 Mar 2026 14:34:23 +0000 Subject: [PATCH 778/782] Adding ziskup setup_snark in docs --- book/getting_started/installation.md | 5 +++++ ziskup/ziskup | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/book/getting_started/installation.md b/book/getting_started/installation.md index e88089c6f..c389fb127 100644 --- a/book/getting_started/installation.md +++ b/book/getting_started/installation.md @@ -85,6 +85,11 @@ To update ZisK to the latest version, simply run: You can use the flags `--provingkey`, `--verifykey` or `--nokey` to specify the installation setup and skip the selection prompt. +To install the PLONK proving key (provingKeySnark), run: + ```bash + ziskup setup_snark + ``` + ### Option 2: Building from Source diff --git a/ziskup/ziskup b/ziskup/ziskup index 969cd9ea0..bf32467c9 100755 --- a/ziskup/ziskup +++ b/ziskup/ziskup @@ -376,7 +376,7 @@ install_setup() { # Install the SNARK proving key install_setup_snark() { - local KEY_FILE="zisk-provingkey-pre-0.16.0-plonk-inputs.tar.gz" + local KEY_FILE="zisk-provingkey-plonk-inputs-${SETUP_VERSION}.tar.gz" step "Downloading SNARK proving key version ${SETUP_VERSION}. This may take a while..." ensure download "${BUCKET_URL}/${KEY_FILE}" "${KEY_FILE}" From 9de288dcdbc959699e5c2a8548673c5c1b1b763b Mon Sep 17 00:00:00 2001 From: Xavier Pinsach <10213118+xavi-pinsach@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:04:19 +0000 Subject: [PATCH 779/782] update hints docs --- book/SUMMARY.md | 2 +- book/getting_started/distributed_execution.md | 4 +- .../{precompile_hints.md => hints_stream.md} | 50 ++++++++++++++----- distributed/README.md | 4 +- 4 files changed, 42 insertions(+), 18 deletions(-) rename book/getting_started/{precompile_hints.md => hints_stream.md} (88%) diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 6d7f8df13..3955c382c 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -9,7 +9,7 @@ - [Writing Programs](./getting_started/writing_programs.md) - [Precompiles](./getting_started/precompiles.md) - [Distributed Execution](./getting_started/distributed_execution.md) -- [Precompile Hints](./getting_started/precompile_hints.md) +- [Hints Stream](./getting_started/hints_stream.md) # Developer Guide - [Ziskof](./developer/ziskof.md) diff --git a/book/getting_started/distributed_execution.md b/book/getting_started/distributed_execution.md index d2b7e8573..0c0a6cf99 100644 --- a/book/getting_started/distributed_execution.md +++ b/book/getting_started/distributed_execution.md @@ -449,7 +449,7 @@ The `--compute-capacity` flag indicates the total compute units required to gene | `--coordinator-url` | - | String | http://127.0.0.1:50051 | URL of the coordinator to send the request to | | `--data-id` | - | String | Auto (from filename or UUID) | Custom identifier for the proof job | | `--hints-uri` | - | String | - | Path/URI to the precompile hints source | -| `--stream-hints` | - | Boolean | false | Stream hints from the coordinator to workers via gRPC (see [Precompile Hints](precompile_hints.md)) | +| `--stream-hints` | - | Boolean | false | Stream hints from the coordinator to workers via gRPC (see [Hints Stream](hints_stream.md)) | | `--direct-inputs` | `-x` | Boolean | false | Send input data inline via gRPC instead of as a file path | | `--minimal-compute-capacity` | `-m` | Number | Same as `--compute-capacity` | Minimum acceptable compute capacity (allows partial worker allocation) | | `--simulated-node` | - | Number | - | Simulated node ID (for testing) | @@ -464,7 +464,7 @@ The `prove` subcommand supports two modes for delivering inputs and hints to wor **Hints modes** (controlled by `--hints-uri` and `--stream-hints`): - **Path mode** (default): The coordinator sends the hints URI to workers. Each worker loads hints from the specified path independently. -- **Streaming mode** (`--stream-hints`): The coordinator reads hints from the URI and broadcasts them to all workers in real-time via gRPC. See the [Precompile Hints documentation](precompile_hints.md) for details. +- **Streaming mode** (`--stream-hints`): The coordinator reads hints from the URI and broadcasts them to all workers in real-time via gRPC. See the [Hints Stream documentation](hints_stream.md) for details. **Examples:** ```bash diff --git a/book/getting_started/precompile_hints.md b/book/getting_started/hints_stream.md similarity index 88% rename from book/getting_started/precompile_hints.md rename to book/getting_started/hints_stream.md index 42ab078d6..f1180f2da 100644 --- a/book/getting_started/precompile_hints.md +++ b/book/getting_started/hints_stream.md @@ -1,6 +1,11 @@ -# Precompile Hints System +# Hints Stream -Cryptographic precompiles (SHA-256, Keccak-256, elliptic curve operations, pairings, etc.) are computationally expensive inside a zkVM. The precompile hints system accelerates proof generation by offloading these expensive computations outside the zkVM execution, then feeding the results back as verifiable data through a high-performance, parallel pipeline. Hints are preprocessed results that allow cryptographic operations to be handled externally while remaining fully verifiable inside the VM. The system is designed around three core principles: +The hints stream accelerates proof generation by offloading expensive operations outside the zkVM execution, then feeding the results back as verifiable data through a high-performance, parallel pipeline. Hints are preprocessed results that allow operations to be handled externally while remaining fully verifiable inside the VM. The system supports two categories of hints: + +1. **Precompile hints**: Cryptographic operations (SHA-256, Keccak-256, elliptic curve operations, pairings, etc.) that are computationally expensive inside a zkVM. +2. **Input hints**: Data that needs to be passed to the zkVM as input during execution. + +The system is designed around three core principles: 1. **Pre-computing results outside the VM**: The guest program emits hint requests describing the operation and its inputs. 2. **Streaming results back**: A dedicated pipeline processes these requests in parallel, maintaining order, and feeds results to the prover via shared memory. @@ -43,8 +48,9 @@ Hints are transmitted as a stream of `u64` values. Each hint request consists of ├─────────────────────────────────────────────────────────────┤ │ ... │ ├─────────────────────────────────────────────────────────────┤ -│ Data[length-1] (u64) │ +│ Data[N-1] (u64) │ └─────────────────────────────────────────────────────────────┘ +where N = ceil(Length / 8) ``` - **Hint Code** (upper 32 bits): Control code or Data Hint Type - **Length** (lower 32 bits): Payload data size in **bytes**. The last `u64` may contain padding bytes. @@ -101,11 +107,12 @@ When bit 31 of the hint code is set (e.g., `0x8000_0000 | actual_code`), the hin ### 1.4. Hint Code Types -| Category | Code Range | Description | -|--------------|-------------------|-------------------------------------| -| **Control** | `0x0000`-`0x000F` | Stream lifecycle management | -| **Built-in** | `0x0100`-`0x0700` | Cryptographic precompile operations | -| **Custom** | User-defined | Application-specific handlers | +| Category | Code Range | Description | +|--------------|---------------------|-------------------------------------| +| **Control** | `0x0000`-`0x000F` | Stream lifecycle management | +| **Built-in** | `0x0100`-`0x0800` | Cryptographic precompile operations | +| **Input** | `0xF0000` | Input data hints | +| **Custom** | User-defined | Application-specific handlers | > **Note:** Custom hint codes can technically use any value not occupied by control or built-in codes. By convention, codes `0xA000`-`0xFFFF` are recommended for custom use to avoid future conflicts as new built-in types are added. The processor does not enforce a range restriction — any unrecognized code is treated as custom. @@ -141,10 +148,25 @@ Control codes manage the stream lifecycle and do not carry computational data: | `0x0500` | `ModExp` | Modular exponentiation | | `0x0600` | `VerifyKzgProof` | KZG polynomial commitment proof verification | | `0x0700` | `Keccak256` | Keccak-256 hash computation | +| `0x0800` | `Blake2bCompress` | Blake2b compression function | + +#### 1.4.3. Input Hint Type + +Input hints allow passing data to the zkVM during execution. Unlike precompile hints that are processed by worker threads, input hints are forwarded directly to a separate inputs sink. + +| Code | Name | Description | +|------|------|-------------| +| `0xF0000` | `Input` | Input data for the zkVM | + +The input hint payload format is: +- **First 8 bytes**: Length of the input data (as `u64` little-endian) +- **Remaining bytes**: The actual input data, padded to 8-byte alignment + +Input hints are not processed by the parallel worker pool; instead, they are immediately submitted to the inputs sink for consumption by the zkVM. -#### 1.4.3. Custom Hint Types +#### 1.4.4. Custom Hint Types -Custom hint types allow users to define their own hint handlers for application-specific logic. Users can register custom handlers via the `HintsProcessor` builder API, providing a mapping from hint code to a processing function (see [Custom Hint Handlers](#4-custom-hint-handlers)). By convention, codes in the range `0xA000`-`0xFFFF` are recommended for custom use to avoid conflicts with current and future built-in types. If a data hint is received with an unregistered code, the processor returns an error and stops processing immediately. +Custom hint types allow users to define their own hint handlers for application-specific logic. Users can register custom handlers via the `HintsProcessor` builder API, providing a mapping from hint code to a processing function (see [Custom Hint Handlers](#4-custom-hint-handlers)). By convention, codes in the range `0xA000`-`0xEFFFF` are recommended for custom use to avoid conflicts with current and future built-in types. If a data hint is received with an unregistered code, the processor returns an error and stops processing immediately. ### 1.5. Stream Protocol @@ -152,7 +174,7 @@ A valid hint stream follows this protocol: ``` CTRL_START ← Reset state, begin stream - [Hint_1] [Hint_2] ... [Hint_N] ← Data hints (any order of types) + [Hint_1] [Hint_2] ... [Hint_N] ← Data hints (precompile, input, or custom) CTRL_END ← Wait for completion, end stream ``` @@ -239,7 +261,7 @@ zisk-coordinator prove --hints-uri unix:///tmp/hints.sock --stream-hints ... #### 3.2.2 Worker Hints non-Streaming Mode -To start a worker in non-streaming mode, provide the `--hints-uri` option with a URI that points to the local workers path where hints are stored, without the `--stream-hints` option. In this mode the worker(s) will load the precompile hints from the specified URI instead of receiving them from the coordinator. This mode is useful for debugging or when hints are pre-generated and stored in a file. +To start a worker in non-streaming mode, provide the `--hints-uri` option with a URI that points to the local workers path where hints are stored, without the `--stream-hints` option. In this mode the worker(s) will load hints from the specified URI instead of receiving them from the coordinator. This mode is useful for debugging or when hints are pre-generated and stored in a file. ## 4. Custom Hint Handlers @@ -395,9 +417,11 @@ Using threads or iterating over non-deterministically ordered data structures ma | `0x0500` | `fn hint_modexp_bytes(base_ptr: *const u8, base_len: usize, exp_ptr: *const u8, exp_len: usize, modulus_ptr: *const u8, modulus_len: usize);` | | `0x0600` | `fn hint_verify_kzg_proof(z: *const u8, y: *const u8, commitment: *const u8, proof: *const u8);` | | `0x0700` | `fn hint_keccak256(input_ptr: *const u8, input_len: usize);` | +| `0x0800` | `fn hint_blake2b_compress(...);` | +| `0xF0000` | `fn hint_input_data(input_data_ptr: *const u8, input_data_len: usize);` | ### 5.6 Custom Hints Generation -To extend the built-in precompile hints, you can generate custom hints for new precompiles. The first step is to register the new hint in the `HintsProcessor`, as explained in section [Custom Hint Handlers](#4-custom-hint-handlers). Once the precompile hint is registered, you can generate hints for it from the guest program using the following FFI function: +To extend the built-in hints, you can generate custom hints for new operations. The first step is to register the new hint in the `HintsProcessor`, as explained in section [Custom Hint Handlers](#4-custom-hint-handlers). Once the hint is registered, you can generate hints for it from the guest program using the following FFI function: ```rust fn hint_custom(hint_id: u32, data_ptr: *const u8, data_len: usize, is_result: u8); diff --git a/distributed/README.md b/distributed/README.md index e1fc9362d..399380669 100644 --- a/distributed/README.md +++ b/distributed/README.md @@ -440,7 +440,7 @@ The `--compute-capacity` flag indicates the total compute units required to gene | `--coordinator-url` | - | String | http://127.0.0.1:50051 | URL of the coordinator to send the request to | | `--data-id` | - | String | Auto (from filename or UUID) | Custom identifier for the proof job | | `--hints-uri` | - | String | - | Path/URI to the precompile hints source | -| `--stream-hints` | - | Boolean | false | Stream hints from the coordinator to workers via gRPC (see [Precompile Hints](../book/getting_started/precompile_hints.md)) | +| `--stream-hints` | - | Boolean | false | Stream hints from the coordinator to workers via gRPC (see [Hints Stream](../book/getting_started/hints_stream.md)) | | `--direct-inputs` | `-x` | Boolean | false | Send input data inline via gRPC instead of as a file path | | `--minimal-compute-capacity` | `-m` | Number | Same as `--compute-capacity` | Minimum acceptable compute capacity (allows partial worker allocation) | | `--simulated-node` | - | Number | - | Simulated node ID (for testing) | @@ -455,7 +455,7 @@ The `prove` subcommand supports two modes for delivering inputs and hints to wor **Hints modes** (controlled by `--hints-uri` and `--stream-hints`): - **Path mode** (default): The coordinator sends the hints URI to workers. Each worker loads hints from the specified path independently. -- **Streaming mode** (`--stream-hints`): The coordinator reads hints from the URI and broadcasts them to all workers in real-time via gRPC. See the [Precompile Hints documentation](../book/getting_started/precompile_hints.md) for details. +- **Streaming mode** (`--stream-hints`): The coordinator reads hints from the URI and broadcasts them to all workers in real-time via gRPC. See the [Hints Stream documentation](../book/getting_started/hints_stream.md) for details. **Examples:** ```bash From dea7ffbb662278c783eabeb283df6d98a6a9db61 Mon Sep 17 00:00:00 2001 From: zkronos73 Date: Thu, 12 Mar 2026 16:46:54 +0100 Subject: [PATCH 780/782] update documentaion --- book/getting_started/precompiles.md | 2 +- book/getting_started/proof.md | 76 --------- book/getting_started/quickstart.md | 6 +- book/getting_started/quickstart_dev.md | 205 ------------------------- 4 files changed, 3 insertions(+), 286 deletions(-) delete mode 100644 book/getting_started/proof.md delete mode 100644 book/getting_started/quickstart_dev.md diff --git a/book/getting_started/precompiles.md b/book/getting_started/precompiles.md index 043c6de19..3aa3d2bf6 100644 --- a/book/getting_started/precompiles.md +++ b/book/getting_started/precompiles.md @@ -2,7 +2,7 @@ Precompiles are built-in system functions within ZisK’s operating system that accelerate computationally expensive and frequently used operations such as the Keccak-f permutation and Secp256k1 addition and doubling. -These precompiles improve proving efficiency by offloading intensive computations from ZisK programs to dedicated, pre-integrated sub-processors. ZisK manages precompiles as system calls using the RISC-V `ecall` instruction. +These precompiles improve proving efficiency by offloading intensive computations from ZisK programs to dedicated, pre-integrated sub-processors. ## How Precompiles Work diff --git a/book/getting_started/proof.md b/book/getting_started/proof.md deleted file mode 100644 index fa508315b..000000000 --- a/book/getting_started/proof.md +++ /dev/null @@ -1,76 +0,0 @@ -## Steps to verify constraints or generate proof - -compile pils: -``` -node ../pil2-compiler/src/pil.js pil/fork_0/pil/zisk.pil -I lib/std/pil -o pil/fork_0/pil/zisk.pilout -``` - -generate "structs" for different airs: -`(cd ../pil2-proofman; cargo run --bin proofman-cli pil-helpers --pilout ../zisk/pil/fork_0/pil/zisk.pilout --path ../zisk/pil/fork_0/src/ -o)` - -prepare "fast tools" (only first time): -`(cd ../zkevm-prover && git switch develop_rust_lib && git submodule init && git submodule update && make -j bctree && make starks_lib -j)` - -setup for pil, this step is necessary **only when pil change**: -`node ../pil2-proofman-js/src/main_setup.js -a pil/fork_0/pil/zisk.pilout -b build -t ../zkevm-prover/build/bctree` - -this step should be done once and is optional. Edit file pil2-proofman/provers/starks-lib-c/Cargo.toml to remove "no_lib_link" from line 12: -`nano ../pil2-proofman/provers/starks-lib-c/Cargo.toml` - -compile witness computation library (libzisk_witness.so). If you haven't nightly mode as default, must add +nightly when do build. -`cargo build --release` - -In the following steps to verify constraints or generate prove, select one of these inputs: -- input.bin: large number of sha -- input_one_segment.bin: only one sha -- input_two_segments.bin: 512 shas - -To **verify constraints** use: -`(cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey)` - -To **generate proof** use: -`(cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey)` - -## Steps to compile a verifiable rust program - -### Setup -Install qemu: -`sudo apt-get install qemu-system` -Add tokens to access repos: -``` -export GITHUB_ACCESS_TOKEN=.... -export ZISK_TOKEN=.... -``` -### Create new hello_world project -Create project with toolchain: -```bash -cargo-zisk sdk new hello_world -cd hello_world -``` - -Compile and execute in **riscv mode**: -`cargo-zisk run --release` - -Compile and execute in **zisk mode**: -`cargo-zisk run --release --sim` - -Execute with ziskemu: -`ziskemu -i build/input.bin -x -e target/riscv64ima-zisk-zkvm-elf/release/fibonacci` - -### Update toolchain -``` -ziskup -``` -If ziskup fails, could update ziskemu manually. - -### Update ziskemu manually -```bash -cd zisk -git pull -cargo install --path emulator -cp ~/.cargo/bin/ziskemu ~/.zisk/bin/ -``` - -```bash -ziskemu -i build/input.bin -x -e target/riscv64ima-zisk-zkvm-elf/debug/fibonacci -``` diff --git a/book/getting_started/quickstart.md b/book/getting_started/quickstart.md index e76cecc2e..62dafac18 100644 --- a/book/getting_started/quickstart.md +++ b/book/getting_started/quickstart.md @@ -64,14 +64,12 @@ This will create a project with the following structure: The example program takes a number `n` as input and computes the SHA-256 hash `n` times. -The `build.rs` file generates an `input.bin` file containing the value of `n` (e.g., 20). This file is used in `main.rs` as input to calculate the hash. - ## Build -The next step is to build the program using the `cargo-zisk` command to generate an ELF file (RISC-V), which will be used later to generate the proof. Execute: +The next step is to build the program to generate an ELF file (RISC-V), which will be used later to generate the proof. Execute: ```bash -cargo-zisk build --release +cargo build --release ``` This command builds the program using the `zkvm` target. The resulting `sha_hasher` ELF file (without extension) is generated in the `./target/elf/riscv64ima-zisk-zkvm-elf/release` directory. diff --git a/book/getting_started/quickstart_dev.md b/book/getting_started/quickstart_dev.md deleted file mode 100644 index 245fd3a25..000000000 --- a/book/getting_started/quickstart_dev.md +++ /dev/null @@ -1,205 +0,0 @@ -# Quickstart - -In this guide, we will walk you through the steps to create a simple Zisk project. - -## Requirements - -Before you begin, ensure that you have [Rust](https://www.rust-lang.org/tools/install) installed on your system. - -Optional recommendations: - -- Use the [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) extension for VS Code to enhance your Rust development experience. -- Use the [PIL2 Highlight syntax code](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) for VS Code to highlight PIL2 code when writing it. - -## Clone Repositories - -Run the following commands to clone the necessary repositories: - -```bash -git clone -b develop https://github.com/0xPolygonHermez/pil2-compiler.git -git clone -b develop https://github.com/0xPolygonHermez/zisk.git -git clone -b develop https://github.com/0xPolygonHermez/pil2-proofman.git -git clone -b develop https://github.com/0xPolygonHermez/pil2-proofman-js -``` - -## Compile a Verifiable Rust Program - -### Setup -Install qemu: -`sudo apt-get install qemu-system` - -### Create New Hello World Project -Create a new project using the Zisk toolchain: - -```bash -cargo-zisk sdk new hello_world -cd hello_world -``` - -Edit file `build.rs` file to modify the `OUTPUT_DIR` variable to `build`: - -```rust=3 -use std::path::Path; - -// Define constants for the directory and file names -const OUTPUT_DIR: &str = "build"; -const FILE_NAME: &str = "input.bin"; -``` - -### Compile and Run - -- RISC-V mode: -```bash -cargo-zisk run --release -``` - -- Zisk mode: -```bash -cargo-zisk run --release --sim -``` - -- Ziskemu execution: -```bash -ziskemu -i build/input.bin -x -e target/riscv64ima-zisk-zkvm-elf/release/hello_world -``` - -### Updating the Toolchain -To update the Zisk toolchain: - -```bash -ziskup -``` - -If `ziskup` fails, you can manually update `ziskemu`. - -### Manual Ziskemu Update -```bash -cd zisk -git pull -cargo install --path emulator -cp ~/.cargo/bin/ziskemu ~/.zisk/bin/ -``` - -Run the emulator with: - -```bash -ziskemu -i build/input.bin -x -e target/riscv64ima-zisk-zkvm-elf/debug/hello_world -``` - -### Easy Input Update for 64-bit Values -To put `0x0100`, reverse hex sequence: -```bash -echo -en "\x00\x01\x00\x00\x00\x00\x00\x00" > input_two_segments.bin -``` -To input `0x0234`: -```bash -echo -en "\x34\x02\x00\x00\x00\x00\x00\x00" > input_two_segments.bin -``` - -## Prepare Your Setup - -All following commands should be executed in the `zisk` folder. - -### Compile Zisk PIL - -!!!!!! Download pil2-proofman to be able to compile the std -node --max-old-space-size=131072 --stack-size=1500 ../pil2-proofman-js/src/main_setup.js -a pil/zisk_pre_040.pilout -b build/build_pre_040 -t ../pil2-proofman/pil2-stark/build/bctree -i ./build/keccakf_fixed.bin -com es genera el fixed.bin??? - -cargo run --release --bin keccakf_fixed_gen -```bash -node --max-old-space-size=65536 ../pil2-compiler/src/pil.js pil/zisk.pil -I pil,../pil2-proofman/pil2-components/lib/std/pil,state-machines,precompiles -o pil/zisk.pilout -``` - -### Compile PILs with `std_mock` (for testing without `std`): -```bash -node ../pil2-compiler/src/pil.js pil/zisk.pil -I pil,../pil2-components/lib/std_mock/pil,state-machines -o pil/zisk.pilout -``` - -### Compile the PIl2 Stark C++ Library (run only once): -```bash -(cd ../pil2-proofman/pil2-stark && git submodule init && git submodule update && make clean && make -j starks_lib && make -j bctree) -``` - -### Generate PIL-Helpers Rust Code -Run this whenever the `.pilout` file changes: - -```bash -(cd ../pil2-proofman; cargo run --bin proofman-cli pil-helpers --pilout ../zisk/pil/zisk.pilout --path ../zisk/pil/src/ -o) -``` - -### Generate Setup Data -Run this whenever the `.pilout` file changes: - -```bash[] -node --max-old-space-size=131072 --stack-size=1500 ../pil2-proofman-js/src/main_setup.js -a pil/zisk.pilout -b build -t ../pil2-proofman/pil2-stark/build/bctree -``` - -### Compile Witness Computation library (`libzisk_witness.so`) -```bash -cargo build --release -``` - -> If you get a library not found error, set the path manually: -> ```bash -> export RUSTFLAGS="-L native={path to your pil2-stark folder}/pil2-stark/lib" -> ``` - -## Generate & Verify Proofs - -Sample inputs are located in `zisk/emulator/benches/data`: -- `input_one_segment.bin`: single SHA -- `input_two_segments.bin`: 512 SHA -- `input.bin`: large number of SHA - -### Verify Constraints Only -```bash -// Using input_one_segment.bin -(cargo build --release && cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --witness-lib ../zisk/target/release/libzisk_witness.so --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input_one_segment.bin --proving-key ../zisk/build/provingKey) - -// Using input_two_segments.bin -(cargo build --release && cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --witness-lib ../zisk/target/release/libzisk_witness.so --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input_two_segments.bin --proving-key ../zisk/build/provingKey)` - -// Using input.bin -(cargo build --release && cd ../pil2-proofman; cargo run --release --bin proofman-cli verify-constraints --witness-lib ../zisk/target/release/libzisk_witness.so --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey)` -``` - -### Generate a Proof - -To generate the aggregated proofs, add `-a` - -```bash -// Using input_one_segment.bin -(cargo build --release && cd ../pil2-proofman; cargo run --release --bin proofman-cli prove --witness-lib ../zisk/target/release/libzisk_witness.so --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input_one_segment.bin --proving-key ../zisk/build/provingKey --output-dir ../zisk/proofs -a -v) - -// Using input_two_segments.bin -(cargo build --release && cd ../pil2-proofman; cargo run --release --bin proofman-cli prove --witness-lib ../zisk/target/release/libzisk_witness.so --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input_two_segments.bin --proving-key ../zisk/build/provingKey --output-dir ../zisk/proofs -a -v) - -// Using input.bin -(cargo build --release && cd ../pil2-proofman; cargo run --release --bin proofman-cli prove --witness-lib ../zisk/target/release/libzisk_witness.so --rom ../zisk/emulator/benches/data/my.elf -i ../zisk/emulator/benches/data/input.bin --proving-key ../zisk/build/provingKey --output-dir ../zisk/proofs -a -v) -``` - -### Distributed prove - -Zisk can run proves using multiple processes in the same server or in multiple servers. To use zisk in distributed mode you need to have installed a mpi library. To use the distributed mode the compilation command is: - -```bash -cargo-zisk build --release --features distributed -``` - -Then the execution command will be: - -```bash -mpirun --bind-to none -np -x OMP_NUM_THREADS= target/release/cargo-zisk prove -e target/riscv64ima-zisk-zkvm-elf/release/sha_hasher -i build/input.bin -w $HOME/.zisk/bin/libzisk_witness.so -k $HOME/.zisk/provingKey -o proof -a -y -``` -### Verify the Proof -```bash -node ../pil2-proofman-js/src/main_verify -k ./build/provingKey -p ./proofs -``` - -### Verify the aggregated Proof -If the aggregation proofs are being generated, can be verified with the following command: - -```bash -node ../pil2-proofman-js/src/main_verify -k ./build/provingKey/ -p ./proofs -t vadcop_final -``` From 438128871795fe7031600ecc4d88a672efab8481 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 12 Mar 2026 15:50:35 +0000 Subject: [PATCH 781/782] Update dependencies and branch references for version 0.16.0 --- Cargo.lock | 99 +++++++++++++++++++++++++-------------------- Cargo.toml | 20 ++++----- tools/test-env/.env | 6 +-- 3 files changed, 68 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c4019683..85adaec5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -610,7 +610,7 @@ dependencies = [ "colored", "dirs", "executor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "futures", "indicatif", "mpi", @@ -1133,9 +1133,9 @@ dependencies = [ [[package]] name = "curves" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0#d61d40bee66b3c7f02bd4ab8661f9f29ad6730a4" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "num-bigint", "num-traits", ] @@ -1455,7 +1455,7 @@ dependencies = [ "asm-runner", "crossbeam", "data-bus", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "itertools 0.14.0", "mem-common", "mem-planner-cpp", @@ -1507,6 +1507,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fields" +version = "0.16.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0#d61d40bee66b3c7f02bd4ab8661f9f29ad6730a4" +dependencies = [ + "cfg-if", + "num-bigint", + "paste", + "serde", +] + [[package]] name = "fields" version = "0.16.0" @@ -2383,7 +2394,7 @@ name = "mem-common" version = "0.16.0" dependencies = [ "clap", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "num-bigint", "num-traits", "proofman-common", @@ -2810,10 +2821,10 @@ dependencies = [ [[package]] name = "pil-std-lib" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0#d61d40bee66b3c7f02bd4ab8661f9f29ad6730a4" dependencies = [ "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "num-bigint", "num-traits", "proofman-common", @@ -2932,7 +2943,7 @@ dependencies = [ "ark-secp256k1", "ark-secp256r1", "ark-std", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "lazy_static", "lib-c", "mem-common", @@ -2968,7 +2979,7 @@ dependencies = [ "ark-ff", "ark-secp256k1", "ark-std", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "lazy_static", "lib-c", "mem-common", @@ -2999,7 +3010,7 @@ dependencies = [ name = "precomp-big-int" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "generic-array", "lib-c", "mem-common", @@ -3020,7 +3031,7 @@ dependencies = [ name = "precomp-blake2" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "mem-common", "pil-std-lib", "precompiles-common", @@ -3039,7 +3050,7 @@ dependencies = [ name = "precomp-dma" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "generic-array", "lib-c", "mem-common", @@ -3063,7 +3074,7 @@ name = "precomp-keccakf" version = "0.16.0" dependencies = [ "circuit", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "path-clean", "pil-std-lib", "precompiles-common", @@ -3083,7 +3094,7 @@ dependencies = [ name = "precomp-poseidon2" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "mem-common", "pil-std-lib", "precompiles-common", @@ -3103,7 +3114,7 @@ dependencies = [ name = "precomp-sha256f" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "mem-common", "pil-std-lib", "precompiles-common", @@ -3122,7 +3133,7 @@ dependencies = [ name = "precompiles-common" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "mem-common", "sm-mem", "zisk-common", @@ -3194,7 +3205,7 @@ dependencies = [ [[package]] name = "proofman" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0#d61d40bee66b3c7f02bd4ab8661f9f29ad6730a4" dependencies = [ "bincode", "blake3", @@ -3205,7 +3216,7 @@ dependencies = [ "crossbeam-channel", "csv", "curves", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "libloading", "mpi", "num-bigint", @@ -3229,7 +3240,7 @@ dependencies = [ [[package]] name = "proofman-common" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0#d61d40bee66b3c7f02bd4ab8661f9f29ad6730a4" dependencies = [ "bincode", "borsh", @@ -3239,7 +3250,7 @@ dependencies = [ "crossbeam-queue", "csv", "env", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "lazy_static", "libloading", "mpi", @@ -3260,9 +3271,9 @@ dependencies = [ [[package]] name = "proofman-hints" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0#d61d40bee66b3c7f02bd4ab8661f9f29ad6730a4" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "itoa", "proofman-common", "proofman-starks-lib-c", @@ -3273,7 +3284,7 @@ dependencies = [ [[package]] name = "proofman-macros" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0#d61d40bee66b3c7f02bd4ab8661f9f29ad6730a4" dependencies = [ "proc-macro2", "quote", @@ -3283,7 +3294,7 @@ dependencies = [ [[package]] name = "proofman-starks-lib-c" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0#d61d40bee66b3c7f02bd4ab8661f9f29ad6730a4" dependencies = [ "crossbeam-channel", "tracing", @@ -3292,7 +3303,7 @@ dependencies = [ [[package]] name = "proofman-util" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0#d61d40bee66b3c7f02bd4ab8661f9f29ad6730a4" dependencies = [ "bincode", "bytemuck", @@ -3304,10 +3315,10 @@ dependencies = [ [[package]] name = "proofman-verifier" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0#d61d40bee66b3c7f02bd4ab8661f9f29ad6730a4" dependencies = [ "bytemuck", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "proofman-util", "rayon", "tracing", @@ -3685,7 +3696,7 @@ dependencies = [ "anyhow", "blake3", "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "proofman-common", "sm-rom", "tracing", @@ -4107,7 +4118,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "sm-arith" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "num-bigint", "pil-std-lib", "proofman-common", @@ -4127,7 +4138,7 @@ dependencies = [ name = "sm-binary" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "num-bigint", "pil-std-lib", "proofman-common", @@ -4147,7 +4158,7 @@ name = "sm-frequent-ops" version = "0.16.0" dependencies = [ "clap", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "num-bigint", "proofman-common", "proofman-util", @@ -4161,7 +4172,7 @@ dependencies = [ name = "sm-main" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "mem-common", "num-bigint", "pil-std-lib", @@ -4180,7 +4191,7 @@ dependencies = [ name = "sm-mem" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "mem-common", "num-bigint", "num-traits", @@ -4201,7 +4212,7 @@ name = "sm-rom" version = "0.16.0" dependencies = [ "asm-runner", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "itertools 0.14.0", "proofman-common", "proofman-macros", @@ -5808,10 +5819,10 @@ dependencies = [ [[package]] name = "witness" version = "0.16.0" -source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0#620e7c89f39ee2b608bd0f5d43765dc89728ff67" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0#d61d40bee66b3c7f02bd4ab8661f9f29ad6730a4" dependencies = [ "colored", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "libloading", "proofman-common", "proofman-util", @@ -6004,7 +6015,7 @@ version = "0.16.0" dependencies = [ "anyhow", "bincode", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "libc", "mpi", "proofman", @@ -6027,7 +6038,7 @@ name = "zisk-core" version = "0.16.0" dependencies = [ "elf", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "lib-c", "lib-float", "paste", @@ -6130,7 +6141,7 @@ dependencies = [ "clap", "colored", "config", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "precompiles-hints", "proofman", "proofman-common", @@ -6156,7 +6167,7 @@ dependencies = [ name = "zisk-pil" version = "0.16.0" dependencies = [ - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "proofman-common", "proofman-macros", "rayon", @@ -6173,7 +6184,7 @@ dependencies = [ "bincode", "colored", "executor", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "precompiles-hints", "proofman", "proofman-common", @@ -6212,7 +6223,7 @@ dependencies = [ "clap", "criterion 0.5.1", "data-bus", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "mem-common", "memmap2", "num-format", @@ -6244,7 +6255,7 @@ dependencies = [ "ctor", "dlmalloc", "embedded-alloc", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?branch=pre-develop-0.16.0)", "getrandom 0.2.17", "lazy_static", "lib-c", @@ -6272,7 +6283,7 @@ dependencies = [ "anyhow", "bincode", "cfg-if", - "fields", + "fields 0.16.0 (git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v0.16.0)", "getrandom 0.2.17", "lazy_static", "lib-c", diff --git a/Cargo.toml b/Cargo.toml index 2704f738f..74300c57a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ repository = "https://github.com/0xPolygonHermez/zisk" categories = ["cryptography"] [workspace.metadata] -gha_pil2_proofman_js_branch = "pre-develop-0.16.0" -gha_pil2_compiler_branch = "develop-0.9.0" +gha_pil2_proofman_js_branch = "tags/v0.16.0" +gha_pil2_compiler_branch = "tags/v0.9.0" [workspace] members = [ @@ -112,14 +112,14 @@ zisk-distributed-grpc-api = { path = "distributed/crates/grpc-api" } zisk-distributed-prover = { path = "distributed/crates/worker" } # Proofman -proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } -fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", branch = "pre-develop-0.16.0" } +proofman = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", tag = "v0.16.0" } +proofman-common = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", tag = "v0.16.0" } +proofman-macros = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", tag = "v0.16.0" } +proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", tag = "v0.16.0" } +proofman-util = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", tag = "v0.16.0" } +pil-std-lib = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", tag = "v0.16.0" } +witness = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", tag = "v0.16.0" } +fields = { git = "https://github.com/0xPolygonHermez/pil2-proofman.git", tag = "v0.16.0" } # Proofman Local development # proofman = { path = "../pil2-proofman/proofman" } # proofman-common = { path = "../pil2-proofman/common" } diff --git a/tools/test-env/.env b/tools/test-env/.env index a7d4e0070..bb48f0488 100644 --- a/tools/test-env/.env +++ b/tools/test-env/.env @@ -1,6 +1,6 @@ -ZISK_BRANCH=pre-develop-0.16.0 -PIL2_PROOFMAN_BRANCH=pre-develop-0.16.0 -PIL2_PROOFMAN_JS_BRANCH=pre-develop-0.16.0 +ZISK_BRANCH=main +PIL2_PROOFMAN_BRANCH=tags/v0.16.0 +PIL2_PROOFMAN_JS_BRANCH=tags/v0.16.0 PIL2_COMPILER_BRANCH=tags/v0.9.0 ZISK_TESTVECTORS_BRANCH=main ZISK_TEMPLATE_BRANCH=pre-develop-0.16.0 From 5a4a57c6795af3b74a791576cf7e1bee7dec7d93 Mon Sep 17 00:00:00 2001 From: Fran Segovia Date: Thu, 12 Mar 2026 16:17:24 +0000 Subject: [PATCH 782/782] Update hints_stream.md to use bn254_g1_add as example --- book/getting_started/hints_stream.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/book/getting_started/hints_stream.md b/book/getting_started/hints_stream.md index f1180f2da..1fb720167 100644 --- a/book/getting_started/hints_stream.md +++ b/book/getting_started/hints_stream.md @@ -302,24 +302,24 @@ https://github.com/0xPolygonHermez/zisk-eth-client/tree/main-reth/bin/guest `zec-reth` relies on `reth` crates, which expose a `Crypto` trait that allows a guest program to override precompile implementations. This enables zkVM-optimized implementations while also emitting hints so the computation can be performed outside the zkVM. -For example, the SHA-256 implementation for the `Crypto` trait can be found here: +For example, the BN254 elliptic curve addition (`bn254_g1_add`) implementation for the `Crypto` trait can be found here: -https://github.com/0xPolygonHermez/zisk-eth-client/blob/c855d2fb401648828cc3ac044da2999b4f641917/crates/crypto/src/lib.rs#L164 +https://github.com/0xPolygonHermez/zisk-eth-client/blob/86b71b39d35efb9894696cab115a1177f3e47dbf/crates/guest-reth/src/crypto/impls.rs#L87 In that file, two target-specific implementations are provided: one for `zkvm/zisk` and one for native (non-zkVM) targets. When compiling with `--cfg zisk_hints` for the native target, the zkVM-specific implementation emits a hint request using the FFI helper: ```rust #[cfg(zisk_hints)] unsafe { - hint_sha256(input.as_ptr(), input.len()); + pub fn hint_bn254_g1_add(p1: *const u8, p2: *const u8); } ``` -This call generates the hint input data using the exact input values that will later be used by the ZisK zkVM when executing the `zkvm/zisk` target code. This hint input data is consumed later by the `HintsProcessor`, allowing the SHA-256 computation to be performed outside the zkVM while remaining fully verifiable inside the circuit. +This call generates the hint input data using the exact input values that will later be used by the ZisK zkVM when executing the `zkvm/zisk` target code. This hint input data is consumed later by the `HintsProcessor`, allowing the `bn254_g1_add` computation to be performed outside the zkVM while remaining fully verifiable inside the circuit. -After the hint generation, execution continues in the native target code to compute the SHA-256 result. +After the hint generation, execution continues in the native target code to compute the `bn254_g1_add` result. -From the guest program, we generate hints containing the input data for the corresponding `zisklib` functions (in this example, the `sha256_c` function). These `zisklib` functions may internally invoke one or more precompiles to produce the final result. +From the guest program, we generate hints containing the input data for the corresponding `zisklib` functions (in this example, the `bn254_g1_add_c` function). These `zisklib` functions may internally invoke one or more precompiles to produce the final result. When the hints are processed by the `HintsProcessor`, it executes the same `zisklib` function using the implementation code for the zkvm/zisk target. This produces the exact precompile results expected when executing the guest ELF inside the zkVM.

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